asm - use assembler in c
Asma en línea GCC/x86: ¿Cómo le dice a gcc que la sección de ensamblaje en línea modificará% esp? (1)
Al intentar hacer que algún viejo código funcione nuevamente ( https://github.com/chaos4ever/chaos/blob/master/libraries/system/system_calls.h#L387 , FWIW) descubrí que algunas de las semánticas de gcc parecen tener cambió de una manera bastante sutil pero peligrosa en los últimos 10-15 años ...: P
El código solía funcionar bien con versiones anteriores de gcc , como 2.95. De todos modos, aquí está el código:
static inline return_type system_call_service_get(const char *protocol_name, service_parameter_type *service_parameter,
tag_type *identification)
{
return_type return_value;
asm volatile("pushl %2/n"
"pushl %3/n"
"pushl %4/n"
"lcall %5, $0"
: "=a" (return_value),
"=g" (*service_parameter)
: "g" (identification),
"g" (service_parameter),
"g" (protocol_name),
"n" (SYSTEM_CALL_SERVICE_GET << 3));
return return_value;
}
El problema con el código anterior es que gcc (4.7 en mi caso) compilará esto con el siguiente código asm (sintaxis AT & T):
# 392 "../system/system_calls.h" 1
pushl 68(%esp) # This pointer (%esp + 0x68) is valid when the inline asm is entered.
pushl %eax
pushl 48(%esp) # ...but this one is not (%esp + 0x48), since two dwords have now been pushed onto the stack, so %esp is not what the compiler expects it to be
lcall $456, $0
# Restoration of %esp at this point is done in the called method (i.e. lret $12)
El problema: las variables ( identification y protocol_name ) están en la pila en el contexto de llamada. Entonces gcc (con optimizaciones resultantes, sin saber si importa) obtendrá los valores de allí y se los entregará a la sección de asme en línea. Pero como estoy empujando cosas en la pila, las compensaciones que calcula el gcc estarán desactivadas en 8 en la tercera llamada ( pushl 48(%esp) ). :)
Esto me llevó mucho tiempo descubrirlo, no era todo obvio para mí al principio.
La manera más fácil de evitar esto es, por supuesto, usar la restricción de entrada r , para asegurar que el valor esté en un registro en su lugar. ¿Pero hay otra, mejor manera? Una forma obvia sería, por supuesto, reescribir toda la interfaz de llamadas del sistema para no meter cosas en la pila en primer lugar (y usar registros en su lugar, como por ejemplo, Linux), pero esa no es una refactorización que me gustaría hacer esta noche ...
¿Hay alguna forma de decirle a gcc inline asm que "la pila es volátil"? ¿Cómo han estado manejando cosas como esta en el pasado?
Actualización más tarde esa misma noche : Encontré un hilo de ML de gcc relevante ( https://gcc.gnu.org/ml/gcc-help/2011-06/msg00206.html ) pero no pareció ayudar. Parece que especificar %esp en la lista de clobber debería hacer compensaciones desde %ebp , pero no funciona y sospecho que el -O2 -fomit-frame-pointer tiene un efecto aquí. Tengo ambos indicadores habilitados.
Lo que funciona y lo que no:
Intenté omitir
-fomit-frame-pointer. Sin efecto alguno. Incluí%esp,espyspen la lista de clobbers .Intenté omitir
-fomit-frame-pointery-O3. Esto realmente produce código que funciona, ya que depende de%ebplugar de%esp.pushl 16(%ebp) pushl 12(%ebp) pushl 8(%ebp) lcall $456, $0Intenté con tener
-O3y no-fomit-frame-pointerespecificado en mi línea de comando. Crea un código defectuoso, roto (se basa en que%espsea constante dentro de todo el bloque de ensamblaje, es decir, sin marco de pila).Intenté omitir
-fomit-frame-pointery simplemente usar-O2. Código roto, sin marco de pila.Intenté simplemente con `-O1. Código roto, sin marco de pila.
Traté de agregar
cccomo clobber. No se puede hacer, no hace ninguna diferencia en absoluto.Traté de cambiar las restricciones de entrada a
ri, dando el código de entrada y salida a continuación. Esto, por supuesto, funciona, pero es un poco menos elegante de lo que esperaba. Por otra parte, perfecto es el enemigo del bien, así que tal vez tenga que vivir con esto por ahora.
Código de entrada C:
static inline return_type system_call_service_get(const char *protocol_name, service_parameter_type *service_parameter,
tag_type *identification)
{
return_type return_value;
asm volatile("pushl %2/n"
"pushl %3/n"
"pushl %4/n"
"lcall %5, $0"
: "=a" (return_value),
"=g" (*service_parameter)
: "ri" (identification),
"ri" (service_parameter),
"ri" (protocol_name),
"n" (SYSTEM_CALL_SERVICE_GET << 3));
return return_value;
}
Código asm de salida. Como se puede ver, usando registros en su lugar, que siempre deberían ser seguros (pero tal vez algo menos efectivos ya que el compilador tiene que mover cosas):
#APP
# 392 "../system/system_calls.h" 1
pushl %esi
pushl %eax
pushl %ebx
lcall $456, $0