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
,esp
ysp
en la lista de clobbers .Intenté omitir
-fomit-frame-pointer
y-O3
. Esto realmente produce código que funciona, ya que depende de%ebp
lugar de%esp
.pushl 16(%ebp) pushl 12(%ebp) pushl 8(%ebp) lcall $456, $0
Intenté con tener
-O3
y no-fomit-frame-pointer
especificado en mi línea de comando. Crea un código defectuoso, roto (se basa en que%esp
sea constante dentro de todo el bloque de ensamblaje, es decir, sin marco de pila).Intenté omitir
-fomit-frame-pointer
y simplemente usar-O2
. Código roto, sin marco de pila.Intenté simplemente con `-O1. Código roto, sin marco de pila.
Traté de agregar
cc
como 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