lenguaje ensamblador convertir asm c gcc x86 compiler-optimization inline-assembly

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 modificador volatile .


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).