Pregunta:
Identificación de la función de argumentos variables
Mellowcandle
2013-03-21 01:34:26 UTC
view on stackexchange narkive permalink

¿Cómo se vería una función de argumento de variable C como printf (char * format, ...) cuando se desensambla?

¿Siempre se identifica mediante una convención de llamada, o ¿Hay más formas de identificarlo?

Dos respuestas:
Igor Skochinsky
2013-03-21 05:14:36 UTC
view on stackexchange narkive permalink

Es muy simple en algunas arquitecturas y no muy obvio en otras. Describiré algunos con los que estoy familiarizado.

SystemV x86_64 (Linux, OS X, BSD)

Probablemente el más fácil de reconocer. Debido a la decisión tonta de especificar el número de registros XMM usados ​​en al , la mayoría de las funciones de vararg comienzan así:

  push rbp mov rbp, rsp sub rsp, 0E0h mov [rbp + var_A8], rsi mov [rbp + var_A0], rdx mov [rbp + var_98], rcx mov [rbp + var_90], r8 mov [rbp + var_88], r9 movzx eax, al lea rdx, ds: 0 [rax * 4] lea rax, loc_402DA1 sub rax, rdx lea rdx, [rbp + var_1] jmp rax movaps xmmword ptr [rdx-0Fh], xmm7 movaps xmmword ptr [rdx-1Fh], xmm6 movaps xmmword ptrFh [rdx-2 ], xmm5 movaps xmmword ptr [rdx-3Fh], xmm4 movaps xmmword ptr [rdx-4Fh], xmm3 movaps xmmword ptr [rdx-5Fh], xmm2 movaps xmmword ptr [rdx-6Fh], xmm1 movaps xmmword ptr ], xmm0loc_402DA1:  

Observe cómo está usando al para determinar cuántos registros xmm se derramarán en la pila.

Windows x64 también conocido como AMD64

En Win64 es menos obvio, pero aquí hay o signo ne: los registros que corresponden a los parámetros elípticos siempre se derraman en la pila y en posiciones que se alinean con el resto de argumentos pasados ​​en la pila. P.ej. aquí está el prólogo de printf :

  mov rax, rsp mov [rax + 8], rcx mov [rax + 10h], rdx mov [rax + 18h] , r8 mov [rax + 20h], r9  

Aquí, rcx contiene el argumento formato fijo, y los argumentos elípticos se pasan en rdx , r8 y r9 y luego en la pila. Podemos observar que rdx , r8 y r9 se almacenan exactamente uno tras otro, y justo debajo del resto de los argumentos, que comienzan en rsp + 0x28 . El área [rsp + 8..rsp + 0x28] está reservada exactamente para este propósito, pero las funciones que no son vararg a menudo no almacenan todos los argumentos de registro allí, ni reutilizan esa área para variables locales. Por ejemplo, aquí hay un prólogo de función no -vararg:

  mov [rsp + 10h], rbx mov [rsp + 18h], rbp mov [rsp + 20h] , rsi  

Puede ver que está usando el área reservada para guardar registros no volátiles y no derramar los argumentos del registro.

ARM

La convención de llamadas ARM usa R0 - R3 para los primeros argumentos, por lo que las funciones vararg necesitan derramarlos en la pila para alinearse con el resto de parámetros pasados ​​en la pila. Por lo tanto, verá R0 - R3 (o R1 - R3 , o R2 - R3 o simplemente R3 ) que se envía a la pila, lo que normalmente no ocurre en funciones que no son vararg. No es un indicador 100% infalible, p. Ej. El compilador de Microsoft a veces inserta R0 - R1 en la pila y accede a ellos usando SP en lugar de moverse a otros registros y usar eso. Pero creo que es una señal bastante confiable para GCC. A continuación, se muestra un ejemplo de función compilada por GCC:

  STMFD SP !, {R0-R3} LDR R3, = dword_86090STR LR, [SP, # 0x10 + var_14]! LDR R1, [SP, # 0x14 + varg_r0]; formatoLDR R0, [R3]; sADD R2, SP, # 0x14 + varg_r1; argBL vsprintfLDR R3, = dword_86094MOV R2, # 1STR R2, [R3] LDR LR, [SP + 0x14 + var_14], # 4ADD SP, SP, # 0x10RET  

Es obviamente una función vararg porque está llamando a vsprintf , y podemos ver que R0 - R3 se empuja justo al principio (puede No introduzca nada más antes de eso porque los argumentos de pila potenciales están presentes en SP y, por lo tanto, R0 - R3 tienen que precederlos).

Genial, gracias por analizar los diferentes escenarios con ejemplos.
Rolf Rolles
2013-03-21 01:40:29 UTC
view on stackexchange narkive permalink

(Mi respuesta es específica de x86).

Internamente a la función, se parece a cualquier otra función. La única diferencia es que, en algún momento durante la función, tomará la dirección (de pila) del último argumento no variable y la incrementará por el tamaño de la palabra en la plataforma; a continuación, se utiliza como puntero a la base de los argumentos de la variable. Externamente a la función, observará que se pasan diferentes números de argumentos como parámetros a la función (y normalmente uno de los argumentos no variables será algún indicador obvio como una función de argumento variable, como una cadena de formato codificado o algo parecido). Las funciones de argumento variable no pueden ser __stdcall , ya que __stdcall se basa en instrucciones ret XXh precompiladas, mientras que el objetivo de una función de argumento variable es que un se puede pasar una cantidad de parámetros. Por lo tanto, estas funciones deben ser __cdecl , es decir, el llamador debe corregir la pila para eliminar todos los argumentos insertados.



Esta pregunta y respuesta fue traducida automáticamente del idioma inglés.El contenido original está disponible en stackexchange, a quien agradecemos la licencia cc by-sa 3.0 bajo la que se distribuye.
Loading...