ensamblador - convertir c en asm
gcc elimina el código del ensamblador en línea (7)
¿Funcionaría, lo haría para que gcc no pueda saber que es inalcanzable?
int main(void)
{
volatile int y = 1;
if (y) goto exit;
handler:
__asm__ __volatile__("jmp 0x0");
exit:
return 0;
}
Parece que gcc 4.6.2 elimina el código que considera no utilizado de las funciones.
prueba.c
int main(void) {
goto exit;
handler:
__asm__ __volatile__("jmp 0x0");
exit:
return 0;
}
Desmontaje de main()
0x08048404 <+0>: push ebp
0x08048405 <+1>: mov ebp,esp
0x08048407 <+3>: nop # <-- This is all whats left of my jmp.
0x08048408 <+4>: mov eax,0x0
0x0804840d <+9>: pop ebp
0x0804840e <+10>: ret
Opciones de compilación
No hay optimizaciones habilitadas, solo gcc -m32 -o test test.c
( -m32
porque estoy en una máquina de 64 bits).
¿Cómo puedo detener este comportamiento?
Edición: Preferiblemente utilizando las opciones del compilador, no modificando el código.
No creo que exista una forma confiable de usar solo las opciones de compilación para resolver esto. El mecanismo preferible es algo que hará el trabajo y funcionará en futuras versiones del compilador, independientemente de las opciones utilizadas para compilar.
Comentario sobre la respuesta aceptada
En la respuesta aceptada hay una edición del original que sugiere esta solución:
int main(void) {
__asm__ ("jmp exit");
handler:
__asm__ __volatile__("jmp $0x0");
exit:
return 0;
}
Primero, jmp $0x0
debería ser jmp 0x0
. En segundo lugar, las etiquetas C suelen traducirse en etiquetas locales. jmp exit
realidad no salta a la etiqueta exit
en la función C , sino que salta a la función exit
en la biblioteca C , pasando por alto el return 0
en la parte inferior de main
. Al utilizar Godbolt con GCC 4.6.4 obtenemos esta salida no optimizada (he recortado las etiquetas que no nos interesan):
main:
pushl %ebp
movl %esp, %ebp
jmp exit
jmp 0x0
.L3:
movl $0, %eax
popl %ebp
ret
.L3
es en realidad la etiqueta local para exit
. No encontrará la etiqueta de exit
en el ensamblaje generado. Puede compilarse y enlazarse si la biblioteca C está presente. No utilice las etiquetas Goto locales de C en ensamblajes en línea como este.
Usa asm goto como la solución
A partir de GCC 4.5 (OP utiliza 4.6.x), hay soporte para asm goto
extendido plantillas de ensamblaje . asm goto
permite especificar objetivos de salto que el ensamblaje en línea puede usar:
6.45.2.7 Ir a etiquetas
asm goto permite que el código de ensamblaje salte a una o más etiquetas C. La sección GotoLabels en una declaración asm goto contiene una lista separada por comas de todas las etiquetas C a las que puede saltar el código del ensamblador. GCC asume que la ejecución de asm pasa a la siguiente declaración (si este no es el caso, considere usar el intrínseco __builtin_unreachable después de la declaración de asm). La optimización de asm goto puede mejorarse utilizando los atributos de etiqueta caliente y fría (consulte la sección Atributos de la etiqueta).
Una instrucción asm goto no puede tener salidas. Esto se debe a una restricción interna del compilador: las instrucciones de transferencia de control no pueden tener salidas. Si el código del ensamblador no modifica nada, use el botón "memoria" para forzar a los optimizadores a vaciar todos los valores de registro en la memoria y volver a cargarlos si es necesario después de la declaración de asm.
También tenga en cuenta que una declaración asm goto siempre se considera implícitamente volátil.
Para hacer referencia a una etiqueta en la plantilla del ensamblador, prefílela con ''% l'' (minúscula ''L'') seguida de su posición (basada en cero) en GotoLabels más el número de operandos de entrada. Por ejemplo, si el asm tiene tres entradas y hace referencia a dos etiquetas, consulte la primera etiqueta como ''% l3'' y la segunda como ''% l4'').
Alternativamente, puede hacer referencia a las etiquetas utilizando el nombre real de la etiqueta C entre corchetes. Por ejemplo, para hacer referencia a una etiqueta llamada carry, puede usar ''% l [carry]''. La etiqueta aún debe estar incluida en la sección GotoLabels cuando se utiliza este enfoque.
El código se podría escribir de esta manera:
int main(void) {
__asm__ goto ("jmp %l[exit]" :::: exit);
handler:
__asm__ __volatile__("jmp 0x0");
exit:
return 0;
}
Podemos usar asm goto
. Prefiero __asm__
sobre asm
ya que no emitirá advertencias si compila con -ansi
o -std=?
opciones Después de los clobbers puede enumerar los objetivos de salto que puede usar el ensamblaje en línea. C no sabe realmente si saltamos o no, ya que GCC no analiza el código real en la plantilla de ensamblaje en línea. No puede eliminar este salto, ni puede asumir que lo que viene después es un código muerto. Al usar Godbolt con GCC 4.6.4, el código no optimizado (recortado) se ve así:
main:
pushl %ebp
movl %esp, %ebp
jmp .L2 # <------ this is the goto exit
jmp 0x0
.L2: # <------ exit label
movl $0, %eax
popl %ebp
ret
La salida de Godbolt con GCC 4.6.4 todavía se ve correcta y aparece como:
main:
jmp .L2 # <------ this is the goto exit
jmp 0x0
.L2: # <------ exit label
xorl %eax, %eax
ret
Este mecanismo también debería funcionar si tiene optimizaciones activadas o desactivadas, y no importa si está compilando para objetivos x86 de 64 bits o de 32 bits.
Otras observaciones
Cuando no hay restricciones de salida en una plantilla de ensamblaje en línea extendida, la declaración
asm
es implícitamente volátil. La línea__asm__ __volatile__("jmp 0x0");
Se puede escribir como:
__asm__ ("jmp 0x0");
asm goto
sentencias se consideran implícitamente volátiles. Tampoco requieren un modificadorvolatile
.
Nunca he oído hablar de una manera de evitar que gcc elimine el código inalcanzable; Parece que no importa lo que hagas, una vez que gcc detecta el código inalcanzable, siempre lo elimina (usa la opción gcc''s -Wunreachable-code
para ver qué se considera inaccesible).
Dicho esto, aún puedes poner este código en una función estática y no se optimizará:
static int func()
{
__asm__ __volatile__("jmp $0x0");
}
int main(void)
{
goto exit;
handler:
func();
exit:
return 0;
}
PD
Esta solución es particularmente útil si desea evitar la redundancia de código al implantar el mismo bloque de código "manejador" en más de un lugar en el código original.
Parece que así es como es: cuando gcc
ve que el código dentro de una función es inalcanzable, lo elimina. Otros compiladores pueden ser diferentes.
En gcc
, una fase temprana en la compilación es construir el "gráfico de flujo de control", un gráfico de "bloques básicos", cada uno de ellos sin condiciones, conectado por ramas. Al emitir el código real, se descartan partes del gráfico, que no son accesibles desde la raíz.
Esto no es parte de la fase de optimización y, por lo tanto, no se ve afectado por las opciones de compilación.
Así que cualquier solución implicaría hacer que gcc
piense que el código es accesible.
Mi sugerencia:
En lugar de colocar su código de ensamblaje en un lugar inalcanzable (donde GCC puede eliminarlo), puede colocarlo en un lugar accesible y omitir la instrucción problemática:
int main(void) {
goto exit;
exit:
__asm__ __volatile__ (
"jmp 1f/n"
"jmp $0x0/n"
"1:/n"
);
return 0;
}
También, vea este hilo sobre el tema .
Si un compilador piensa que puede engañarte, solo trálalo: (solo GCC)
int main(void) {
{
/* Place this code anywhere in the same function, where
* control flow is known to still be active (such as at the start) */
extern volatile unsigned int some_undefined_symbol;
__asm__ __volatile__(".pushsection .discard" : : : "memory");
if (some_undefined_symbol) goto handler;
__asm__ __volatile__(".popsection" : : : "memory");
}
goto exit;
handler:
__asm__ __volatile__("jmp 0x0");
exit:
return 0;
}
Esta solución no agregará ninguna sobrecarga adicional para las instrucciones sin sentido, aunque solo funciona para GCC cuando se usa con AS (como es el valor predeterminado).
Explicación: .pushsection
cambia la salida de texto del compilador a otra sección, en este caso .discard
(que se elimina durante la vinculación de forma predeterminada). La bola de "memory"
evita que GCC intente mover otro texto dentro de la sección que se descartará. Sin embargo, GCC no se da cuenta (y nunca podría porque los __asm__
s son __volatile__
) que todo lo que ocurra entre las 2 declaraciones se descartará.
En cuanto a some_undefined_symbol
, eso es literalmente cualquier símbolo que nunca se está definiendo (o en realidad está definido, no debería importar). Y dado que la sección del código que lo usa se descartará durante el enlace, tampoco producirá ningún error de referencia no resuelto.
Finalmente, el salto condicional a la etiqueta que desea que aparezca como si fuera alcanzable hace exactamente eso. Además del hecho de que no aparecerá en el binario de salida, GCC se da cuenta de que no puede saber nada acerca de some_undefined_symbol
, lo que significa que no tiene más remedio que asumir que las dos ramas de if son accesibles, lo que significa que hasta se trata, el flujo de control puede continuar al llegar a goto exit
o saltar al handler
(aunque no habrá ningún código que pueda hacer esto)
Sin embargo, tenga cuidado al habilitar la recolección de basura en su enlazador ld --gc-sections
(está deshabilitado de manera predeterminada), ya que de lo contrario podría tener la idea de deshacerse de la etiqueta aún no utilizada.
EDIT: Olvídate de todo eso. Solo haz esto:
int main(void) {
__asm__ __volatile__ goto("" : : : : handler);
goto exit;
handler:
__asm__ __volatile__("jmp 0x0");
exit:
return 0;
}
gcc puede duplicar sentencias asm dentro de las funciones y eliminarlas durante la optimización (incluso en -O0), por lo que esto nunca funcionará de manera confiable.
una forma de hacerlo de manera confiable es usar una declaración global de asm (es decir, una declaración de asm fuera de cualquier función). gcc copiará esto directamente en la salida y puede usar etiquetas globales sin ningún problema.
Actualización 2012/6/18
Pensándolo bien, uno puede poner la goto exit
en un bloque asm, lo que significa que solo necesita cambiar una línea de código:
int main(void) {
__asm__ ("jmp exit");
handler:
__asm__ __volatile__("jmp $0x0");
exit:
return 0;
}
Eso es significativamente más limpio que mi otra solución a continuación (y posiblemente mejor que la actual de @ugoren también).
Esto es bastante intrincado, pero parece funcionar: oculte el controlador en un condicional que nunca se puede seguir en condiciones normales, pero evite que se elimine impidiendo que el compilador pueda realizar su análisis correctamente con un ensamblador en línea.
int main (void) {
int x = 0;
__asm__ __volatile__ ("" : "=r"(x));
// compiler can''t tell what the value of x is now, but it''s always 0
if (x) {
handler:
__asm__ __volatile__ ("jmp $0x0");
}
return 0;
}
Incluso con jmp
el jmp
se conserva:
testl %eax, %eax
je .L2
.L3:
jmp $0x0
.L2:
xorl %eax, %eax
ret
(Esto parece realmente poco fiable, así que espero que haya una mejor manera de hacerlo. Edite solo poniendo un objeto volatile
en frente de x
para que no sea necesario hacer el truco de Asm en línea).