2013-04-01 14 views
5

Próbuję zrozumieć kod zespołu funkcji C. Nie mogłem zrozumieć, dlaczego andl -16 jest wykonywane na głównym. Czy służy do alokowania przestrzeni dla zmiennych lokalnych. Jeśli tak, dlaczego subl 32 jest wykonane dla głównej.kod zespołu funkcji c

Nie mogłem zrozumieć dezasemblacji func1. W miarę czytania stos rośnie z adresu wyższego rzędu do niskiego rzędu dla procesorów 8086. A więc tutaj, dlaczego jest dostęp do pozytywnej strony ebp (dla offsetów parametrów) i dlaczego nie w negatywnej stronie ebp. Zmienne lokalne wewnątrz func1 to 3 + adres zwrotny + zapisane rejestry - więc musi być 20, ale dlaczego jest 24? (subl $24,esp) Kod

#include<stdio.h> 
int add(int a, int b){ 
int res = 0; 
res = a + b; 
return res; 
} 
int func1(int a){ 
int s1,s2,s3; 
s1 = add(a,a); 
s2 = add(s1,a); 
s3 = add(s1,s2); 
return s3; 
} 
int main(){ 
int a,b; 
a = 1;b = 2; 
b = func1(a); 
printf("\n a : %d b : %d \n",a,b); 
return 0; 
} 

montaż:

 .file "sample.c" 
     .text 
.globl add 
     .type add, @function 
add: 
     pushl %ebp 
     movl %esp, %ebp 
     subl $16, %esp 
     movl $0, -4(%ebp) 
     movl 12(%ebp), %eax 
     movl 8(%ebp), %edx 
     leal (%edx,%eax), %eax 
     movl %eax, -4(%ebp) 
     movl -4(%ebp), %eax 
     leave 
     ret 
     .size add, .-add 
.globl func1 
     .type func1, @function 
func1: 
     pushl %ebp 
     movl %esp, %ebp 
     subl $24, %esp 
     movl 8(%ebp), %eax 
     movl %eax, 4(%esp) 
     movl 8(%ebp), %eax 
     movl %eax, (%esp) 
     call add 
     movl %eax, -4(%ebp) 
     movl 8(%ebp), %eax 
     movl %eax, 4(%esp) 
     movl -4(%ebp), %eax 
     movl %eax, (%esp) 
     call add 
     movl %eax, -8(%ebp) 
     movl -8(%ebp), %eax 
     movl %eax, 4(%esp) 
     movl -4(%ebp), %eax 
     movl %eax, (%esp) 
             call add 
     movl %eax, -12(%ebp) 
     movl -12(%ebp), %eax 
     leave 
     ret 
     .size func1, .-func1 
     .section  .rodata 
.LC0: 
     .string "\n a : %d b : %d \n" 
     .text 
.globl main 
     .type main, @function 
main: 
     pushl %ebp 
     movl %esp, %ebp 
     andl $-16, %esp 
     subl $32, %esp 
     movl $1, 28(%esp) 
     movl $2, 24(%esp) 
     movl 28(%esp), %eax 
     movl %eax, (%esp) 
     call func1 
     movl %eax, 24(%esp) 
     movl $.LC0, %eax 
     movl 24(%esp), %edx 
     movl %edx, 8(%esp) 
     movl 28(%esp), %edx 
     movl %edx, 4(%esp) 
     movl %eax, (%esp) 
     call printf 
     movl $0, %eax 
     leave 
     ret 
     .size main, .-main 
     .ident "GCC: (Ubuntu/Linaro 4.4.4-14ubuntu5) 4.4.5" 
     .section  .note.GNU-stack,"",@progbits 

Odpowiedz

5

Wyrównuje wskaźnik stosu do wielokrotności 16 bajtów, usuwając cztery niskie bity.

Jedynymi miejscami, w których wartości dodatnich przesunięć są używane z (%ebp) są dostępy parametrów.

Nie podano, czym jest platforma docelowa lub z jakimi przełącznikami korzystano podczas kompilacji. Kod złożenia pokazuje, że jakiś identyfikator Ubuntu został wstawiony, ale nie znam ABI, którego używa, poza tym prawdopodobnie jest podobny do ABI powszechnie używanych w architekturze Intel x86. Tak więc zamierzam zgadnąć, że ABI wymaga 8-bajtowego wyrównania podczas rutynowych wywołań, a więc kompilator tworzy ramkę stosu func1 24 bajty zamiast 20, aby utrzymać 8-bajtowe wyrównanie.

Będę dalej zgadywać, że kompilator ustawił stos na 16 bitów na początku main jako rodzaj "preferencji" w kompilatorze, w przypadku gdy używa instrukcji SSE, które preferują wyrównanie 16 bajtów, lub innych operacji, które preferuj wyrównanie 16 bajtów.

Więc mamy:

W main The andl $-16, %esp wyrównany stos do wielokrotności 16 bajtów jako preferencja kompilatora. Wewnątrz main, 28(%esp) i 24(%esp) odnoszą się do wartości tymczasowych, które kompilator zapisuje na stosie, podczas gdy 8(%esp), 4(%esp) i (%esp) są używane do przekazywania parametrów do func1 i printf. Widać z faktu, że kod zestawu wywołuje printf, ale w Twoim kodzie jest napisane, że wkleiłeś kod źródłowy C, który różni się od kodu źródłowego C używanego do wygenerowania kodu zespołu: Jest to , a nie poprawny kod zespołu wygenerowany z kodu źródłowego C.

W func1, 24 bajty są przydzielane na stosie zamiast 20, aby zachować wyrównanie 8 bajtów. Wewnątrz func1 parametry są dostępne przez 8(%ebp) i 4(%ebp). Lokalizacje od -12(%ebp) do -4(%ebp) służą do przechowywania wartości zmiennych. 4(%esp) i (%esp) służą do przekazywania parametrów do add.

Oto rama stos func1:

 
    - 4(%ebp) = 20(%esp): s1. 
    - 8(%ebp) = 16(%esp): s2. 
    -12(%ebp) = 12(%esp): s3. 
    -16(%ebp) = 8(%esp): Unused padding. 
    -20(%ebp) = 4(%esp): Passes second parameter of add. 
    -24(%ebp) = 0(%esp): Passes first parameter of add. 
+2

Ten cytat z podręcznika gcc mogą być pomocne: -mpreferred-stosu granica = NUM ​​próbują utrzymać granicę stosu wyrównany do 2 podniesione do num granica bajtów. Jeśli -preferred-border-stack nie jest określony, domyślnie jest to 4 (16 bajtów lub 128 bitów). – teppic

+0

Dziękujemy za wyedukowanie 8-bajtowego wyrównania. Moje wątpliwości są w dodatku -> 1 (zmienna lokalna) + adres zwrotny => 2 * 8 = 16, która jest prawidłowa. Ale w func1 -> 3 (zmienne lokalne) + adres zwrotny => 4 * 8 = 32 powinno tam być. dlaczego jest 24. dlaczego w głównym subl 32 jest zrobione dla wskaźnika bazowego. Jestem trochę zmieszany z wykonanymi obliczeniami. – Angus

+1

@Angus: Przy obliczaniu rozmiaru 'func1', dlaczego mnożysz przez 8? W tej 32-bitowej architekturze obiekty 'int' w' func1' i adresie mają po cztery bajty. 'func1' używa czterech bajtów dla s1, czterech bajtów dla s2, czterech bajtów dla s3, ośmiu bajtów dwa przechodzą dwa parametry do' add' i cztery dodatkowe bajty do pad dla wyrównania. To jest 24 bajty. –

3

Proponuję pracę przez to z wyjściem objdump -S który daje interlisting ze źródła C.