Hier werden die Unterschiede zwischen zwei Versionen gezeigt.
tux:debug_hello_world [2011/10/09 13:26] wikisysop [Disassemble Binary] |
tux:debug_hello_world [2011/10/10 15:13] (aktuell) wikisysop [Disassemble Binary] |
||
---|---|---|---|
Zeile 1: | Zeile 1: | ||
[[:tux|{{ :linux.png?40|}}]] | [[:tux|{{ :linux.png?40|}}]] | ||
- | ===== "Hello, world" im Debugger ===== | + | ===== "Hello World!" im Debugger ===== |
Das Debuggen eines Programms gehört sicherlich zu den anspruchsvollsten Angelegenheiten, kann aber unter gegebenen Umständen äußerst nützlich sein. | Das Debuggen eines Programms gehört sicherlich zu den anspruchsvollsten Angelegenheiten, kann aber unter gegebenen Umständen äußerst nützlich sein. | ||
Zeile 69: | Zeile 69: | ||
End of assembler dump.</xterm> | End of assembler dump.</xterm> | ||
- | <xterm><fc #800000>(gdb)</fc><fc #008000>break main</fc> | + | <xterm><fc #800000>(gdb)</fc> <fc #008000>break main</fc> |
Breakpoint 1 at 0x80483bd: file firstprog.c, line 6.</xterm> | Breakpoint 1 at 0x80483bd: file firstprog.c, line 6.</xterm> | ||
Zeile 166: | Zeile 166: | ||
Nicht weiter von Belang aber die mov-Instruktion möchte die Adresse >><fc #FF00FF>0x80484b0</fc><< dort hin schreiben. Aber warum? Was ist so besonders an der Speicheradresse >><fc #FF00FF>0x80484b0</fc><<? Finden wir es raus und lassen wir uns mal die ersten 8 Byte dieser Adresse anzeigen: | Nicht weiter von Belang aber die mov-Instruktion möchte die Adresse >><fc #FF00FF>0x80484b0</fc><< dort hin schreiben. Aber warum? Was ist so besonders an der Speicheradresse >><fc #FF00FF>0x80484b0</fc><<? Finden wir es raus und lassen wir uns mal die ersten 8 Byte dieser Adresse anzeigen: | ||
- | <xterm><fc #800000>(gdb)</fc> (gdb) <fc #008000>x/8xb 0x80484b0</fc> | + | <xterm><fc #800000>(gdb)</fc> <fc #008000>x/8xb 0x80484b0</fc> |
<fc #FF00FF>0x80484b0</fc>: 0x48 0x65 0x6c 0x6c 0x6f 0x20 0x57 0x6f</xterm> | <fc #FF00FF>0x80484b0</fc>: 0x48 0x65 0x6c 0x6c 0x6f 0x20 0x57 0x6f</xterm> | ||
Zeile 192: | Zeile 192: | ||
<xterm><fc #800000>(gdb)</fc> <fc #008000>x/i $eip</fc> | <xterm><fc #800000>(gdb)</fc> <fc #008000>x/i $eip</fc> | ||
=> <fc #FF00FF>0x80483ce</fc> <main+26>: call 0x80482f0 <puts@plt></xterm> | => <fc #FF00FF>0x80483ce</fc> <main+26>: call 0x80482f0 <puts@plt></xterm> | ||
+ | |||
+ | An dieser Stelle erfolgt der eigentliche Aufruf der <fc #008000>printf()</fc>-Funktion, welche den Datenstring <fc #0000FF>"Hello World!"</fc> ausgibt: | ||
+ | |||
+ | <xterm><fc #800000>(gdb)</fc> <fc #008000>nexti</fc> | ||
+ | <fc #0000FF>Hello World!</fc> | ||
+ | 6 for(i=0; i < 10; i++)</xterm> | ||
+ | |||
+ | Der Befehlszeiger ist zur nächsten Adresse >><fc #FF00FF>0x80483d3</fc><< gesprungen, in welcher eine >><fc #0000FF>add</fc><<-Instruktion gespeichert ist: | ||
+ | |||
+ | <xterm><fc #800000>(gdb)</fc> <fc #008000>x/i $eip</fc> | ||
+ | => <fc #FF00FF>0x80483d3</fc> <main+31>: <fc #0000FF>add DWORD PTR [esp+0x1c],0x1</fc></xterm> | ||
+ | |||
+ | Hier wird der Wert >><fc #0000FF>0x1</fc><< zu dem uns bereits bekannten Wert >><fc #0000FF>0x0</fc><< in Adresse >><fc #0000FF>[esp+0x1c]</fc><< addiert. Somit wird die letzte Anweisung in der >><fc #FF0000>for()</fc><<-Schleife -> >><fc #FF0000>i++</fc><< ausgeführt und der Wert >><fc #FF0000>i</fc><< um >><fc #FF0000>1</fc><< inkrementiert: | ||
+ | |||
+ | <xterm><fc #800000>(gdb)</fc> <fc #008000>nexti</fc> | ||
+ | 0x080483d8 6 <fc #FF0000>for(i=0; i < 10; i++)</fc> | ||
+ | <fc #800000>(gdb)</fc> <fc #008000>x/4xb $esp + 0x1c</fc> | ||
+ | 0xbffff6fc: <fc #0000FF>0x01</fc> 0x00 0x00 0x00</xterm> | ||
+ | |||
+ | Der erste Durchlauf der >><fc #FF0000>for()</fc><<-Schleife ist nun abgeschlossen und der Befehlszeiger springt zur nächsten Adresse, welche wieder die >><fc #0000FF>cmp</fc><<-Instruktion enthält und einen neuen Durchlauf der >><fc #FF0000>for()</fc><<-Schleife anstößt. Dieses ganze Prozedere wiederholt sich nun solange, wie der Wert >><fc #FF0000>i</fc><< in Adresse >><fc #FF00FF>$esp + 0x1c</fc><< ≤ <fc #FF0000>9</fc> ist. | ||
+ | |||
+ | <xterm><fc #800000>(gdb</fc>) <fc #008000>x/2i $eip</fc> | ||
+ | => 0x80483d8 <main+36>: <fc #0000FF>cmp DWORD PTR [esp+0x1c],0x9</fc> | ||
+ | 0x80483dd <main+41>: jle 0x80483c7 <main+19></xterm> | ||
+ | |||
+ | Hier folgt nun der zweite Durchlauf der for()-Schleife im Zusammenhang. Die kursiv gestellte >>cmp<<-Instruktion repräsentiert dann bereits den übernächsten Durchlauf der for()-Schleife: | ||
+ | |||
+ | <xterm><fc #800000>(gdb)</fc> <fc #008000>x/i $eip</fc> | ||
+ | <fc #0000FF>=> 0x80483d8 <main+36>: cmp DWORD PTR [esp+0x1c],0x9</fc> | ||
+ | <fc #800000>(gdb)</fc> <fc #008000>nexti</fc> | ||
+ | 0x080483dd 6 for(i=0; i < 10; i++) | ||
+ | <fc #800000>(gdb)</fc> <fc #008000>x/i $eip</fc> | ||
+ | <fc #0000FF>=> 0x80483dd <main+41>: jle 0x80483c7 <main+19></fc> | ||
+ | <fc #800000>(gdb)</fc> <fc #008000>nexti</fc> | ||
+ | 8 printf("Hello World!\n"); | ||
+ | <fc #800000>(gdb)</fc> <fc #008000>x/i $eip</fc> | ||
+ | <fc #0000FF>=> 0x80483c7 <main+19>: mov DWORD PTR [esp],0x80484b0</fc> | ||
+ | <fc #800000>(gdb)</fc> <fc #008000>nexti</fc> | ||
+ | 0x080483ce 8 printf("Hello World!\n"); | ||
+ | <fc #800000>(gdb)</fc> <fc #008000>x/i $eip</fc> | ||
+ | <fc #0000FF>=> 0x80483ce <main+26>: call 0x80482f0 <puts@plt></fc> | ||
+ | <fc #800000>(gdb)</fc> <fc #008000>nexti</fc> | ||
+ | Hello World! | ||
+ | 6 for(i=0; i < 10; i++) | ||
+ | <fc #800000>(gdb)</fc> <fc #008000>x/i $eip</fc> | ||
+ | <fc #0000FF>=> 0x80483d3 <main+31>: add DWORD PTR [esp+0x1c],0x1</fc> | ||
+ | <fc #800000>(gdb)</fc> <fc #008000>nexti</fc> | ||
+ | 0x080483d8 6 for(i=0; i < 10; i++) | ||
+ | <fc #800000>(gdb)</fc> <fc #008000>x/4xb $esp + 0x1c</fc> | ||
+ | 0xbffff6fc: 0x02 0x00 0x00 0x00 | ||
+ | //<fc #808080>(gdb)x/i $eip | ||
+ | => 0x80483d8 <main+36>: cmp DWORD PTR [esp+0x1c],0x9 | ||
+ | (gdb)</fc>//</xterm> | ||
+ | |||
+ | Schauen wir uns nun noch einmal das gesamte Disassembling an, kann der Verlauf der Schleife bzw. der gesamte Programmverlauf nachvollzogen werden: | ||
+ | |||
+ | <xterm><fc #800000>(gdb)</fc> <fc #008000>disassemble main</fc> | ||
+ | Dump of assembler code for function main: | ||
+ | //0x080483b4 <+0>: push ebp | ||
+ | 0x080483b5 <+1>: mov ebp,esp | ||
+ | 0x080483b7 <+3>: and esp,0xfffffff0 | ||
+ | 0x080483ba <+6>: sub esp,0x20// | ||
+ | <fc #008080>0x080483bd <+9>: mov DWORD PTR [esp+0x1c],0x0</fc> | ||
+ | <fc #FF00FF>0x080483c5 <+17>: jmp 0x80483d8 <main+36></fc> | ||
+ | ⇓ <fc #0000FF>0x080483c7 <+19>: mov DWORD PTR [esp],0x80484b0 | ||
+ | 0x080483ce <+26>: call 0x80482f0 <puts@plt> | ||
+ | 0x080483d3 <+31>: add DWORD PTR [esp+0x1c],0x1 | ||
+ | 0x080483d8 <+36>: cmp DWORD PTR [esp+0x1c],0x9 | ||
+ | 0x080483dd <+41>: jle 0x80483c7 <main+19></fc> | ||
+ | 0x080483df <+43>: leave | ||
+ | 0x080483e0 <+44>: ret | ||
+ | End of assembler dump.</xterm> | ||
+ | |||
+ | Die dunklegrün markierte Zeile mit der ersten >><fc #008080>mov</fc><<-Instruktion initialisiert die Variable >><fc #008080>i</fc><< und belegt diese mit >><fc #008080>0x0</fc><<, die pink markierte Zeile mit der >><fc #FF00FF>jmp</fc><<-Instruktion springt in die Schleife zur >><fc #0000FF>cmp</fc><<-Instruktion und die blau markierten Zeilen repräsentieren die eigentliche >><fc #FF0000>for()</fc><<-Schleife in dem zuerst verglichen wird >><fc #0000FF>cmp</fc><< und solange das Ergebnis dieses Vergleichs ≤ 9 ist >><fc #0000FF>jle</fc><< der Instruktion Pointer >>EIP<< zur zweiten >><fc #0000FF>mov</fc><<-Instruktion springt, wo die Adresse >>0x80484b0<< in >>ESP<< geschrieben wird (Wir erinnern uns, in dieser Adresse ist der String >>"Hello World!<< gespeichert), danach springt >>EIP<< weiter an die nächste Adresse mit der >><fc #0000FF>call</fc><<-Instruktion, welche den in ESP gespeicherten String >>"Hello World!"<< ausgibt. >>EIP<< springt nun weiter zu >><fc #0000FF>add</fc><<-Instruktion, wo >><fc #0000FF>0x1</fc><< zu der Adresse >>[esp+0x1c]<< hinzu addiert wird und der Vergleich dieser Adresse mit ≤ 9 wird erneut durchgeführt. | ||
+ | |||
+ | Der Vollständigkeit wegen sollte noch gezeigt werden, dass die Schleife auch wie vorgesehen verlassen (>><fc #0000FF>leave</fc><<) wird, wenn die Variable >>i<< den Wert >><fc #FF0000>0xa</fc><< (10<sub>10</sub>) erreicht hat: | ||
+ | |||
+ | <xterm><fc #800000>(gdb)</fc> <fc #008000>x/4xb $esp + 0x1c</fc> | ||
+ | 0xbffff6fc: <fc #FF0000>0x0a</fc> 0x00 0x00 0x00 | ||
+ | (gdb) x/i $eip | ||
+ | => 0x80483d8 <main+36>: cmp DWORD PTR [esp+0x1c],0x9 | ||
+ | <fc #800000>(gdb)</fc> nexti | ||
+ | 0x080483dd 6 for(i=0; i < 10; i++) | ||
+ | <fc #800000>(gdb)</fc> <fc #008000>x/i $eip</fc> | ||
+ | => 0x80483dd <main+41>: jle 0x80483c7 <main+19> | ||
+ | <fc #800000>(gdb)</fc> <fc #008000>nexti</fc> | ||
+ | <fc #0000FF>10 }</fc> | ||
+ | <fc #800000>(gdb)</fc> x/i $eip | ||
+ | => 0x80483df <main+43>: <fc #0000FF>leave</fc> | ||
+ | <fc #800000>(gdb)</fc></xterm> | ||
+ | |||
+ | Mächtig viel Zenober für ein Programm, welches in gerade mal 2 Millisekunden vom Prozessor ausgeführt wird. Ich habe ein ganzes Wochenende mit diesem Artikel verbracht, habe aber viel dabei gelernt ;-) | ||
+ | |||
+ | <xterm>$ <fc #008000>time ./a.out</fc> | ||
+ | Hello World! | ||
+ | Hello World! | ||
+ | Hello World! | ||
+ | Hello World! | ||
+ | Hello World! | ||
+ | Hello World! | ||
+ | Hello World! | ||
+ | Hello World! | ||
+ | Hello World! | ||
+ | Hello World! | ||
+ | |||
+ | real 0m0.002s | ||
+ | user 0m0.000s | ||
+ | sys 0m0.000s</xterm> | ||
+ | |||
+ | --- //pronto 2011/10/09 15:21// |