Question:
Identifier la fonction variable args
Mellowcandle
2013-03-21 01:34:26 UTC
view on stackexchange narkive permalink

À quoi ressemblerait une fonction d'argument de variable C telle que printf (char * format, ...) lorsqu'elle est désassemblée?

Est-elle toujours identifiée en appelant une convention ou existe-t-il d'autres moyens de l'identifier?

Deux réponses:
#1
+18
Igor Skochinsky
2013-03-21 05:14:36 UTC
view on stackexchange narkive permalink

C'est très simple dans certaines architectures et pas très évident dans d'autres. Je vais en décrire quelques-uns que je connais.

SystemV x86_64 (Linux, OS X, BSD)

Probablement le plus facile à reconnaître. En raison de la décision décidée de spécifier le nombre de registres XMM utilisés dans al , la plupart des fonctions vararg commencent comme ceci:

  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 ptr [rdx-2Fh ], 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 7fh [rdx-6Fh] ], xmm0loc_402DA1:  

Notez comment il utilise al pour déterminer le nombre de registres xmm à déverser sur la pile.

Windows x64 alias AMD64

Dans Win64, c'est moins évident, mais voici o signe ne: les registres qui correspondent aux paramètres elliptiques sont toujours déversés sur la pile et à des positions qui s'alignent avec le reste des arguments passés sur la pile. Par exemple. voici le prologue de printf :

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

Ici, rcx contient l'argument fixe format , et les arguments elliptiques sont passés dans rdx , r8 et r9 puis sur la pile. Nous pouvons observer que rdx , r8 et r9 sont stockés exactement l'un après l'autre, et juste en dessous du reste des arguments, qui commencent à rsp + 0x28 . La zone [rsp + 8..rsp + 0x28] est réservée exactement à cette fin, mais les fonctions non-vararg ne stockent souvent pas tous les arguments de registre là-bas, ou ne réutilisent pas cette zone pour les variables locales. Par exemple, voici un prologue de fonction non -vararg:

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

Vous pouvez voir qu'il utilise la zone réservée pour sauvegarder les registres non volatils, et ne pas renverser les arguments de registre.

ARM

La convention d'appel ARM utilise R0 - R3 pour les premiers arguments, donc les fonctions vararg doivent les répandre sur la pile pour s'aligner avec le reste des paramètres passés sur la pile. Ainsi, vous verrez R0 - R3 (ou R1 - R3 , ou R2 - R3 ou simplement R3 ) étant poussé sur la pile, ce qui généralement ne se produit pas dans les fonctions non vararg. Ce n'est pas un indicateur à 100% infaillible - par ex. Le compilateur de Microsoft pousse parfois R0 - R1 sur la pile et y accède en utilisant SP au lieu de se déplacer vers d'autres registres et de l'utiliser. Mais je pense que c'est un signe assez fiable pour GCC. Voici un exemple de fonction compilée par GCC:

  STMFD SP !, {R0-R3} LDR R3, = dword_86090STR LR, [SP, # 0x10 + var_14]! LDR R1, [SP, # 0x14 + varg_r0]; formatLDR 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  

C'est évidemment une fonction vararg car elle appelle vsprintf , et nous pouvons voir R0 - R3 être poussé dès le début (vous pouvez 'ne poussez rien d'autre avant cela car les arguments potentiels de la pile sont présents à SP et donc les R0 - R3 doivent les précéder).

Génial, merci d'avoir décomposé les différents scénarios avec des exemples!
#2
+10
Rolf Rolles
2013-03-21 01:40:29 UTC
view on stackexchange narkive permalink

(Ma réponse est spécifique à x86).

En interne à la fonction, elle ressemble à n'importe quelle autre fonction. La seule différence étant qu'à un moment donné pendant la fonction, il prendra l'adresse (de pile) du dernier argument non variable et l'incrémentera de la taille du mot sur la plateforme; ceci est ensuite utilisé comme pointeur vers la base des arguments de la variable. En externe à la fonction, vous observerez que différents nombres d'arguments sont passés en tant que paramètres à la fonction (et généralement l'un des arguments non variables sera un indicateur évident en tant que fonction d'argument variable, comme une chaîne de format codée en dur ou quelque chose de similaire). Les fonctions d'argument variable ne peuvent pas être __stdcall , car __stdcall repose sur des instructions ret XXh précompilées, alors que le point d'une fonction d'argument variable est qu'une fonction inconnue quantité de paramètres peut être transmise. Par conséquent, ces fonctions doivent être __cdecl , c'est-à-dire que l'appelant doit corriger la pile pour supprimer tous les arguments poussés.



Ce Q&R a été automatiquement traduit de la langue anglaise.Le contenu original est disponible sur stackexchange, que nous remercions pour la licence cc by-sa 3.0 sous laquelle il est distribué.
Loading...