2016-08-02 19 views
0

Ich übe derzeit mit dem Lesen der Baugruppe, indem ich C-Programme zerlege und versuche zu verstehen, was sie tun.Assembly - Übergeben von Parametern an einen Funktionsaufruf

Ich bin mit einem trivialen stecken: ein einfaches Hallo Welt Programm.

#include <stdio.h> 
#include <stdlib.h> 

int main() { 
    printf("Hello, world!"); 
    return(0); 
} 

Wenn ich die Haupt zerlegen:

(gdb) disassemble main 
Dump of assembler code for function main: 
    0x0000000000400526 <+0>: push rbp 
    0x0000000000400527 <+1>: mov rbp,rsp 
    0x000000000040052a <+4>: mov edi,0x4005c4 
    0x000000000040052f <+9>: mov eax,0x0 
    0x0000000000400534 <+14>: call 0x400400 <[email protected]> 
    0x0000000000400539 <+19>: mov eax,0x0 
    0x000000000040053e <+24>: pop rbp 
    0x000000000040053f <+25>: ret 

Ich verstehe die ersten zwei Zeilen: der Basiszeiger wird auf dem Stapel gespeichert (durch Druck RBP, die den Wert des Stapelzeigers bewirkt, daß um 8 verringert, weil es "gewachsen" ist) und der Wert des Stapelzeigers im Basiszeiger gespeichert wird (so dass Parameter und lokale Variable leicht durch positive bzw. negative Offsets erreicht werden können, während der Stack "wachsen" kann) ").

Die dritte Zeile stellt das erste Problem dar: Warum wird 0x4005c4 (die Adresse der Zeichenfolge "Hello, World!") Im EDI-Register verschoben, anstatt es auf dem Stapel zu verschieben? Sollte die printf-Funktion nicht die Adresse dieser Zeichenkette als Parameter annehmen? Soweit ich weiß, nehmen Funktionen Parameter aus dem Stapel (aber hier sieht es so aus, als wäre der Parameter in diesem Register: edi)

In einem anderen Beitrag hier auf StackOverflow habe ich gelesen, dass "printf @ ptl" wie ein Stub ist Funktion, die die echte printf-Funktion aufruft. Ich habe versucht, diese Funktion zu zerlegen, aber es wird noch verwirrender:

(gdb) disassemble printf 
Dump of assembler code for function __printf: 
    0x00007ffff7a637b0 <+0>: sub rsp,0xd8 
    0x00007ffff7a637b7 <+7>: test al,al 
    0x00007ffff7a637b9 <+9>: mov QWORD PTR [rsp+0x28],rsi 
    0x00007ffff7a637be <+14>: mov QWORD PTR [rsp+0x30],rdx 
    0x00007ffff7a637c3 <+19>: mov QWORD PTR [rsp+0x38],rcx 
    0x00007ffff7a637c8 <+24>: mov QWORD PTR [rsp+0x40],r8 
    0x00007ffff7a637cd <+29>: mov QWORD PTR [rsp+0x48],r9 
    0x00007ffff7a637d2 <+34>: je  0x7ffff7a6380b <__printf+91> 
    0x00007ffff7a637d4 <+36>: movaps XMMWORD PTR [rsp+0x50],xmm0 
    0x00007ffff7a637d9 <+41>: movaps XMMWORD PTR [rsp+0x60],xmm1 
    0x00007ffff7a637de <+46>: movaps XMMWORD PTR [rsp+0x70],xmm2 
    0x00007ffff7a637e3 <+51>: movaps XMMWORD PTR [rsp+0x80],xmm3 
    0x00007ffff7a637eb <+59>: movaps XMMWORD PTR [rsp+0x90],xmm4 
    0x00007ffff7a637f3 <+67>: movaps XMMWORD PTR [rsp+0xa0],xmm5 
    0x00007ffff7a637fb <+75>: movaps XMMWORD PTR [rsp+0xb0],xmm6 
    0x00007ffff7a63803 <+83>: movaps XMMWORD PTR [rsp+0xc0],xmm7 
    0x00007ffff7a6380b <+91>: lea rax,[rsp+0xe0] 
    0x00007ffff7a63813 <+99>: mov rsi,rdi 
    0x00007ffff7a63816 <+102>: lea rdx,[rsp+0x8] 
    0x00007ffff7a6381b <+107>: mov QWORD PTR [rsp+0x10],rax 
    0x00007ffff7a63820 <+112>: lea rax,[rsp+0x20] 
    0x00007ffff7a63825 <+117>: mov DWORD PTR [rsp+0x8],0x8 
    0x00007ffff7a6382d <+125>: mov DWORD PTR [rsp+0xc],0x30 
    0x00007ffff7a63835 <+133>: mov QWORD PTR [rsp+0x18],rax 
    0x00007ffff7a6383a <+138>: mov rax,QWORD PTR [rip+0x36d70f]  # 0x7ffff7dd0f50 
    0x00007ffff7a63841 <+145>: mov rdi,QWORD PTR [rax] 
    0x00007ffff7a63844 <+148>: call 0x7ffff7a5b130 <_IO_vfprintf_internal> 
    0x00007ffff7a63849 <+153>: add rsp,0xd8 
    0x00007ffff7a63850 <+160>: ret  
End of assembler dump. 

Die beiden mov Operationen auf EAX (mov eax, 0x0) stört mich ein wenig, als gut, da ich sie nicht Rolle bekommen hier (Aber ich beschäftige mich mehr mit dem, was ich gerade beschrieben habe). Vielen Dank im Voraus.

+2

Suche nach [X86-64 Funktion Args Stack] (http://StackOverflow.com/search?q=x86-64+Function+args+Stack) findet Tonnen verwandter Fragen. Keiner von denen, die ich angeschaut habe, scheint ein genaues Duplikat zu sein, aber wenn du das nächste Mal verwirrt bist, suche bitte nach den relevanten Stichwörtern. –

+0

Als ein Vorschlag kann das Zerlegen von der Hauptleitung manchmal schwierig sein. Es ist fast immer einfacher, mit dem Aufruf einer Funktion von main und disassembling anzufangen. –

Antwort

2

es gibt nichts triviales über printf, nicht die erste Wahl für das, was Sie versuchen zu tun, aber erwies sich als nicht allzu kompliziert.

Etwas einfacher:

extern unsigned int more_fun (unsigned int); 
unsigned int fun (unsigned int x) 
{ 
    return(more_fun(x)+7); 
} 
0000000000000000 <fun>: 
    0: 48 83 ec 08    sub $0x8,%rsp 
    4: e8 00 00 00 00   callq 9 <fun+0x9> 
    9: 48 83 c4 08    add $0x8,%rsp 
    d: 83 c0 07    add $0x7,%eax 
    10: c3      retq 

und der Stapel verwendet wird. eax für die Rückkehr verwendet.

verwenden jetzt einen Zeiger

extern unsigned int more_fun (unsigned int *); 
unsigned int fun (unsigned int x) 
{ 
    return(more_fun(&x)+7); 
} 
0000000000000000 <fun>: 
    0: 48 83 ec 18    sub $0x18,%rsp 
    4: 89 7c 24 0c    mov %edi,0xc(%rsp) 
    8: 48 8d 7c 24 0c   lea 0xc(%rsp),%rdi 
    d: e8 00 00 00 00   callq 12 <fun+0x12> 
    12: 48 83 c4 18    add $0x18,%rsp 
    16: 83 c0 07    add $0x7,%eax 
    19: c3      retq 

und dort gehen Sie edi wie in Ihrem Fall verwendet.

zwei Zeiger

extern unsigned int more_fun (unsigned int *, unsigned int *); 
unsigned int fun (unsigned int x, unsigned int y) 
{ 
    return(more_fun(&x,&y)+7); 
} 
0000000000000000 <fun>: 
    0: 48 83 ec 18    sub $0x18,%rsp 
    4: 89 7c 24 0c    mov %edi,0xc(%rsp) 
    8: 89 74 24 08    mov %esi,0x8(%rsp) 
    c: 48 8d 7c 24 0c   lea 0xc(%rsp),%rdi 
    11: 48 8d 74 24 08   lea 0x8(%rsp),%rsi 
    16: e8 00 00 00 00   callq 1b <fun+0x1b> 
    1b: 48 83 c4 18    add $0x18,%rsp 
    1f: 83 c0 07    add $0x7,%eax 
    22: c3      retq 

jetzt edi und esi verwendet werden. alle sieht aus wie es die Aufrufkonvention zu mir ...

ein String

extern unsigned int more_fun (const char *); 
unsigned int fun (void ) 
{ 
    return(more_fun("Hello World")+7); 
} 
0000000000000000 <fun>: 
    0: 48 83 ec 08    sub $0x8,%rsp 
    4: bf 00 00 00 00   mov $0x0,%edi 
    9: e8 00 00 00 00   callq e <fun+0xe> 
    e: 48 83 c4 08    add $0x8,%rsp 
    12: 83 c0 07    add $0x7,%eax 
    15: c3      retq 

EAX nicht wie in printf vorbereitet ist, so vielleicht eax hat etwas mit der Anzahl von Parametern zu tun, die folgen, versuchen Setzen Sie mehr Parameter auf Ihren Printf und sehen Sie, ob eax Änderungen durchführt.

Wenn ich -m32 auf meiner Kommandozeile anschließe, wird edi nicht benutzt.

00000000 <fun>: 
    0: 83 ec 18    sub $0x18,%esp 
    3: 68 00 00 00 00   push $0x0 
    8: e8 fc ff ff ff   call 9 <fun+0x9> 
    d: 83 c4 1c    add $0x1c,%esp 
    10: 83 c0 07    add $0x7,%eax 
    13: c3 

Ich vermute, dass der Push ein Platzhalter für die Linke ist die Adresse der Zeichenfolge zu drücken, wenn der Linker die binären Patches auf, dies war nur ein Objekt. Also meine Vermutung ist, wenn Sie einen 64-Bit-Zeiger haben, gehen die ersten ein oder zwei in Register, dann wird der Stapel verwendet, nachdem er keine Register mehr hat.

Offensichtlich arbeitet der Compiler so, dass dies der Aufrufkonvention des Compilers entspricht.

extern unsigned int more_fun (unsigned int); 
unsigned int fun (unsigned int x) 
{ 
    return(more_fun(x+5)+7); 
} 
0000000000000000 <fun>: 
    0: 48 83 ec 08    sub $0x8,%rsp 
    4: 83 c7 05    add $0x5,%edi 
    7: e8 00 00 00 00   callq c <fun+0xc> 
    c: 48 83 c4 08    add $0x8,%rsp 
    10: 83 c0 07    add $0x7,%eax 
    13: c3      retq 

Korrektur basierend auf Peters Kommentar. Ja, es scheint, dass hier Register verwendet werden.

Und da er 6 Parameter erwähnt, lässt 7.

extern unsigned int more_fun 
(
unsigned int, 
unsigned int, 
unsigned int, 
unsigned int, 
unsigned int, 
unsigned int, 
unsigned int 
); 
unsigned int fun (
unsigned int a, 
unsigned int b, 
unsigned int c, 
unsigned int d, 
unsigned int e, 
unsigned int f, 
unsigned int g 
) 
{ 
    return(more_fun(a+1,b+2,c+3,d+4,e+5,f+6,g+7)+17); 
} 
0000000000000000 <fun>: 
    0: 48 83 ec 10    sub $0x10,%rsp 
    4: 83 c1 04    add $0x4,%ecx 
    7: 83 c2 03    add $0x3,%edx 
    a: 8b 44 24 18    mov 0x18(%rsp),%eax 
    e: 83 c6 02    add $0x2,%esi 
    11: 83 c7 01    add $0x1,%edi 
    14: 41 83 c1 06    add $0x6,%r9d 
    18: 41 83 c0 05    add $0x5,%r8d 
    1c: 83 c0 07    add $0x7,%eax 
    1f: 50      push %rax 
    20: e8 00 00 00 00   callq 25 <fun+0x25> 
    25: 48 83 c4 18    add $0x18,%rsp 
    29: 83 c0 11    add $0x11,%eax 
    2c: c3      retq 

und sicher genug versuchen, dass der 7. Parameter aus dem modifizierten Stapel gezogen wurde und vor dem Aufruf auf den Stapel gelegt zurück. Die anderen 6 in Registern.

+0

Ihr erstes Beispiel passt nur '% rsp' so an, dass es vor dem' call' auf 16B ausgerichtet ist. Keine Argumente werden auf dem Stapel übergeben. Und ja, "% al" enthält die Anzahl von FP-Argumenten, die in xmm-Registern (bis zu 8) in dem SysV x86-64 ABI übergeben werden. Die ersten 6 ganzzahligen Argumente gehen in Register (nicht nur 1 oder 2). –

+0

Die $ 0x0 ist ein Platzhalter, weil Sie die '.o' statt einer verknüpften Binärdatei zerlegt haben, oder die 'gcc-S'-Ausgabe betrachtet haben. Wenn Sie 'objdump -dr' verwendet haben, würden Sie in dieser Zeile Informationen zum Verschieben von Symbolen sehen. –

+0

Ohne Verknüpfung war ich vage, ob der Push ein Platzhalter für die Adresse war oder nicht, ein 32-Bit-Sofort macht keinen Sinn für eine 64-Bit-Adresse, eindeutig nur ein Offset. Nicht interessant in der Verknüpfung, nur um dem OP zu versichern, dass sie auf dem richtigen Weg sind mit dem, was sie tun (Kompilieren, Zerlegen und Untersuchen der Ergebnisse). Wäre die Zeichenfolge in der Funktion nicht erstmalig verwendet worden, wäre die edi-Änderung nicht erfolgt. Eine Übung für das OP zur Bestätigung. –

5

GCC zielt auf die x86-64 System V ABI, die von allen x86-64-Systemen außer Windows (für various historical reasons) verwendet wird. Seine Aufrufkonvention übergibt die ersten paar Argumente in Registern, bevor sie auf den Stapel zurückfallen. (Siehe auch die Wikipedia basic summary of this calling convention.)

Und ja, das ist anders als die kniffligen alten 32-Bit-Aufrufkonventionen, die den Stapel für alles verwenden. Das ist eine gute Sache. Siehe auch das Tag-Wiki für weitere Links zu ABI-Dokumenten und Tonnen anderer Dinge.

0x0000000000400526: push rbp 
    0x0000000000400527: mov rbp,rsp   # stack-frame boilerplate 
    0x000000000040052a: mov edi,0x4005c4 # first arg 
    0x000000000040052f: mov eax,0x0   # 0 FP args in vector registers 
    0x0000000000400534: call 0x400400 <[email protected]> 
    0x0000000000400539: mov eax,0x0   # return 0. If you'd compiled with optimization, this and the previous mov would be xor eax,eax 
    0x000000000040053e: pop rbp    # clean up stack frame 
    0x000000000040053f: ret 

Zeiger auf statische Daten passen in 32 Bit, weshalb es mov edi, imm32 statt movabs rdi, imm64 verwenden können.

Floating-Point-Argumente werden in SSE-Registern (xmm0-xmm7) übergeben, sogar für var-args-Funktionen. al gibt an, wie viele FP-Argumente sich in Vektorregistern befinden. (Beachten Sie, dass die Regeln für die Promotion von C bedeuten, dass float Argumente für variadische Funktionen immer auf double hochgestuft werden, weshalb printf keine Formatbezeichner für float, nur double und long double hat).


[email protected] ist wie eine Stub-Funktion, die die reale printf Funktion aufruft.

Ja, das stimmt. Der Prozedurverknüpfungstabelle-Eintrag beginnt als jmp mit einer dynamischen Linkerroutine, die das Symbol auflöst und den Code in der PLT so umwandelt, dass er direkt in die Adresse jmp wird, in der die printf-Definition der libc zugeordnet ist. printf ist ein schwaches Alias ​​für __printf, weshalb gdb das __printf-Label für diese Adresse auswählt, nachdem Sie nach dem Zerlegen von printf gefragt haben.

Dump of assembler code for function __printf: 
    0x00007ffff7a637b0 <+0>: sub rsp,0xd8    # reserve space 
    0x00007ffff7a637b7 <+7>: test al,al     # check if there were any FP args 
    0x00007ffff7a637b9 <+9>: mov QWORD PTR [rsp+0x28],rsi # store the integer arg-passing registers to local scratch space 
    0x00007ffff7a637be <+14>: mov QWORD PTR [rsp+0x30],rdx 
    0x00007ffff7a637c3 <+19>: mov QWORD PTR [rsp+0x38],rcx 
    0x00007ffff7a637c8 <+24>: mov QWORD PTR [rsp+0x40],r8 
    0x00007ffff7a637cd <+29>: mov QWORD PTR [rsp+0x48],r9 
    0x00007ffff7a637d2 <+34>: je  0x7ffff7a6380b <__printf+91> # skip storing the FP arg-passing regs if there were no FP args 
    0x00007ffff7a637d4 <+36>: movaps XMMWORD PTR [rsp+0x50],xmm0 
    0x00007ffff7a637d9 <+41>: movaps XMMWORD PTR [rsp+0x60],xmm1 
    0x00007ffff7a637de <+46>: movaps XMMWORD PTR [rsp+0x70],xmm2 
    0x00007ffff7a637e3 <+51>: movaps XMMWORD PTR [rsp+0x80],xmm3 
    0x00007ffff7a637eb <+59>: movaps XMMWORD PTR [rsp+0x90],xmm4 
    0x00007ffff7a637f3 <+67>: movaps XMMWORD PTR [rsp+0xa0],xmm5 
    0x00007ffff7a637fb <+75>: movaps XMMWORD PTR [rsp+0xb0],xmm6 
    0x00007ffff7a63803 <+83>: movaps XMMWORD PTR [rsp+0xc0],xmm7 
     branch_target_from_test_je: 
    0x00007ffff7a6380b <+91>: lea rax,[rsp+0xe0]   # some more stuff 

So printf ‚s Implementierung hält die var-args einfache Handhabung, indem alle Arg-Passing-Register speichert (mit Ausnahme der ersten die Formatzeichenfolge hält), um den lokalen Arrays. Es kann einen Zeiger durch sie gehen, anstatt einen schalterähnlichen Code zu benötigen, um die rechte Ganzzahl oder FP arg zu extrahieren.Es muss immer noch die ersten 5 Ganzzahl- und die ersten 8 FP-Argumente im Auge behalten, da sie nicht mit den restlichen Argumenten zusammenhängen, die vom Aufrufer auf den Stapel geschoben werden.

Die Windows 64-Bit-Aufrufkonvention Shadow Space vereinfacht dies um providing space for a function to dump its register args to the stack contiguous with the args already on the stack, aber das ist nicht wert, 32 Byte Stapel bei jedem Aufruf, IMO zu verschwenden. (Siehe meine Antwort und Kommentare zu anderen Antworten auf Why does Windows64 use a different calling convention from all other OSes on x86-64?)

Verwandte Themen