2017-02-25 3 views
2

Ich ging kürzlich durch eine Assembler-Sprachbuch von Richard Blum, wo es ein Thema auf dem C-Programm zu Assembly-Konvertierung war.Wie wirken ESP- und EBP-Register, wenn ein neues Programm ausgeführt wird?

Betrachten Sie das folgende C-Programm:

#include <stdio.h> 

int main(){ 
    int a=100; 
    int b=25; 
    if (a>b) 
     printf("The higher value is %d\n", a); 
    else 
     printf("The higher value is %d\n", b); 
    return 0; 
} 

, wenn ich das obige Programm mit -S Parameter zusammengestellt wie:

gcc -S abc.c 

ich folgendes Ergebnis bekam:

 .file "abc.c" 
     .section  .rodata 
.LC0: 
     .string "The higher value is %d\n" 
     .text 
     .globl main 
     .type main, @function 
main: 
.LFB0: 
     .cfi_startproc 
     leal 4(%esp), %ecx 
     .cfi_def_cfa 1, 0 
     andl $-16, %esp 
     pushl -4(%ecx) 
     pushl %ebp 
     .cfi_escape 0x10,0x5,0x2,0x75,0 
     movl %esp, %ebp 
     pushl %ecx 
     .cfi_escape 0xf,0x3,0x75,0x7c,0x6 
     subl $20, %esp 
     movl $100, -16(%ebp) 
     movl $25, -12(%ebp) 
     movl -16(%ebp), %eax 
     cmpl -12(%ebp), %eax 
     jle  .L2 
     subl $8, %esp 
     pushl -16(%ebp) 
     pushl $.LC0 
     call printf 
     addl $16, %esp 
     jmp  .L3 
.L2: 
     subl $8, %esp 
     pushl -12(%ebp) 
     pushl $.LC0 
     call printf 
     addl $16, %esp 
.L3: 
     movl $0, %eax 
     movl -4(%ebp), %ecx 
     .cfi_def_cfa 1, 0 
     leave 
     .cfi_restore 5 
     leal -4(%ecx), %esp 
     .cfi_def_cfa 4, 4 
     ret 
     .cfi_endproc 
.LFE0: 
     .size main, .-main 
     .ident "GCC: (Ubuntu 6.2.0-5ubuntu12) 6.2.0 20161005" 
     .section  .note.GNU-stack,"",@progbits 

Was ich kann nicht verstehen, ist dies:

Snippet

.LFB0: 
     .cfi_startproc 
     leal 4(%esp), %ecx 
     .cfi_def_cfa 1, 0 
     andl $-16, %esp 
     pushl -4(%ecx) 
     pushl %ebp 
     .cfi_escape 0x10,0x5,0x2,0x75,0 
     movl %esp, %ebp 
     pushl %ecx 
     .cfi_escape 0xf,0x3,0x75,0x7c,0x6 
     subl $20, %esp 

Ich bin nicht in der Lage, vorherzusagen, was mit dem Register ESP und EBP geschieht. Über EBP kann ich bis zu einem gewissen Grad verstehen, dass es als lokaler Stapel verwendet wird, und so wird sein Wert durch das Aufschieben auf den Stapel gespart.

Können Sie bitte das obige Snippet ausarbeiten?

+1

Es ist nur den Stapelzeiger auf 16-Byte-Grenze ausgerichtet werden. "ebp" wird auf den Stapel geschoben, da es sich um ein gespeichertes Register handelt. Es muss nicht als Rahmenzeiger verwendet werden, obwohl es in diesem Code ist. – Jester

Antwort

0

Dies ist eine spezielle Form der Funktionseingabe-Sequenz, die für die Hauptfunktion() geeignet ist. Der Compiler weiß, dass main() wirklich als main(int argc, char **argv, char **envp) aufgerufen wird, und kompiliert diese Funktion nach diesem sehr speziellen Verhalten. Was also auf dem Stapel liegt, wenn dieser Code erreicht wird, sind vier lange Werte in dieser Reihenfolge: envp, argv, argc, return_address.

So bedeutet das, dass der Eingabe-Sequenz-Code wird so etwas wie dies zu tun (neu geschrieben Intel-Syntax zu verwenden, was ehrlich gesagt viel mehr Sinn als AT & T Syntax macht):

; Copy esp+4 into ecx. The value at [esp] has the return address, 
; so esp+4 is 'argc', or the start of the function's arguments. 
lea ecx, [esp+4] 

; Round esp down (align esp down) to the nearest 16-byte boundary. 
; This ensures that regardless of what esp was before, esp is now 
; starting at an address that can store any register this processor 
; has, from the one-byte registers all the way up to the 16-byte xmm 
; registers 
and esp, 0xFFFFFFF0 

; Since we copied esp+4 into ecx above, that means that [ecx] is 'argc', 
; [ecx+4] is 'argv', and [ecx+8] is 'envp'. For whatever reason, the 
; compiler decided to push a duplicate copy of 'argv' onto the function's 
; new local frame. 
push dword ptr [ecx+4] 

; Preserve 'ebp'. The C ABI requires us not to damage 'ebp' across 
; function calls, so we save its old value on the stack before we 
; change it. 
push ebp 

; Set 'ebp' to the current stack pointer to set up the function's 
; stack frame for real. The "stack frame" is the place on the stack 
; where this function will store all its local variables. 
mov ebp, esp 

; Preserve 'ecx'. Ecx tells us what 'esp' was before we munged 'esp' 
; in the 'and'-instruction above, so we'll need it later to restore 
; 'esp' before we return. 
push ecx 

; Finally, allocate space on the stack frame for the local variables, 
; 20 bytes worth. 'ebp' points to 'esp' plus 24 by this point, and 
; the compiler will use 'ebp-16' and 'ebp-12' to store the values of 
; 'a' and 'b', respectively. (So under 'ebp', going down the stack, 
; the values will look like this: [ecx, unused, unused, a, b, unused]. 
; Those unused slots are probably used by the .cfi pseudo-ops for 
; something related to exception handling.) 
sub esp, 20 

Bei der anderen Ende der Funktion, die inversen Operationen werden verwendet, um den Stapel zurück, wie es war, bevor die Funktion aufgerufen wurde; es kann sein, hilfreich zu untersuchen, was sie auch zu verstehen, sind zu tun, was am Anfang passiert:

; Return values are always passed in 'eax' in the x86 C ABI, so set 
; 'eax' to the return value of 0. 
mov eax, 0 

; We pushed 'ecx' onto the stack a while back to save it. This 
; instruction pulls 'ecx' back off the stack, but does so without 
; popping (which would alter 'esp', which doesn't currently point 
; to the right location). 
mov ecx, [ebp+4] 

; Magic instruction! The 'leave' instruction is designed to shorten 
; instruction sequences by "undoing" the stack in a single op. 
; So here, 'leave' means specifically to do the following two 
; operations, in order: esp = ebp/pop ebp 
leave 

; 'esp' is now set to what it was before we pushed 'ecx', and 'ebp' 
; is back to the value that was used when this function was called. 
; But that's still not quite right, so we set 'esp' specifically to 
; 'ecx+4', which is the exact opposite of the very first instruction 
; in the function. 
lea esp, [ecx+4] 

; Finally, the stack is back to the way it was when we were called, 
; so we can just return. 
ret 
+0

Nichts für ungut, aber selbst ein kleines bisschen Forschung hätte diese Frage für Sie beantwortet. Google 'gcc omit frame pointer' und der erste Link auf der Seite (https://gcc.gnu.org/onlinedocs/gcc-3.4.4/gcc/Optimize-Options.html) weist darauf hin, dass Sie einfach' 'hinzufügen Option "fomit-frame-pointer", die standardmäßig aktiviert ist, wenn Sie die Optimierungsoption "-O" verwenden. –

Verwandte Themen