2013-04-18 14 views
5

Chcę poznać konwencję wywoływania C. Aby to zrobić, napisałem poniższy kod:Zrozumienie C rozmontowanego połączenia

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

struct tstStruct 
{ 
    void *sp; 
    int k; 
}; 

void my_func(struct tstStruct*); 

typedef struct tstStruct strc; 

int main() 
{ 
    char a; 
    a = 'b'; 
    strc* t1 = (strc*) malloc(sizeof(strc)); 
    t1 -> sp = &a; 
    t1 -> k = 40; 
    my_func(t1); 
    return 0; 
} 

void my_func(strc* s1) 
{ 
     void* n = s1 -> sp + 121; 
     int d = s1 -> k + 323; 
} 

Następnie użyłem GCC za pomocą następującego polecenia:

gcc -S test3.c 

i wymyślił jego montażu. Nie pokażę całego kodu, ale mam wkleić kod funkcji my_func. Jest to:

my_func: 
.LFB1: 
.cfi_startproc 
pushq %rbp 
.cfi_def_cfa_offset 16 
.cfi_offset 6, -16 
movq %rsp, %rbp 
.cfi_def_cfa_register 6 
movq %rdi, -24(%rbp) 
movq -24(%rbp), %rax 
movq (%rax), %rax 
addq $121, %rax 
movq %rax, -16(%rbp) 
movq -24(%rbp), %rax 
movl 8(%rax), %eax 
addl $323, %eax 
movl %eax, -4(%rbp) 
popq %rbp 
.cfi_def_cfa 7, 8 
ret 
.cfi_endproc 

O ile zrozumiałem, to co się dzieje: Najpierw dzwoniący baza wskaźnik jest wpychana do stosu, a jej wskaźnik stosu jest nowy wskaźnik podstawy do utworzenia stosu dla nowych funkcjonować. Ale reszta nie rozumiem. O ile mi wiadomo, argumenty (lub wskaźnik do argumentu) są przechowywane w stosie. Jeśli tak to, co jest celem drugiego instrukcji,

movq  -24(%rbp), %rax 

Tutaj zawartość rejestru% RAX zostanie przeniesiona do adresu 24 bajtów od adresu w rejestrze% RBP. Ale co jest w% rax ???? Nic tam nie jest początkowo przechowywane? Myślę, że jestem zdezorientowany. Pomóż zrozumieć, jak działa ta funkcja. Z góry dziękuję!

+2

skompilować z 'gcc -fverbose-ASM -S' a może nawet' gcc -fverbose-ASM -O -S'; zobacz także [tę odpowiedź] (http://stackoverflow.com/a/16088155/841108), która podaje * lot * referencji. –

+0

Dzięki za wszystkie referencje i końcówkę kompilacji. – user2290802

Odpowiedz

9

Pomylić składnię AT z & T ze składnią Intel.

 
movq -24(%rbp), %rax 

W składni intel byłoby

 
mov rax,[rbp-24] 

więc przenosi dane skierowane przez rbp do rax, a nie odwrotnie. Kolejność operandów to src, dest w AT & T, natomiast w składni Intela to dest, src.

Następnie, aby pozbyć dyrektyw GAZU aby demontaż łatwiej czytać, I montowane kodu z GCC po prostu z gcc test3.c i zdemontować go ndisasm -b 64 a.out. Uwaga demontaż my_func funkcji produkowanego przez NDISASM poniżej jest w składni Intel:

 
000005EF 55    push rbp 
000005F0 4889E5   mov rbp,rsp  ; create the stack frame. 
000005F3 48897DE8   mov [rbp-0x18],rdi ; s1 into a local variable. 
000005F7 488B45E8   mov rax,[rbp-0x18] ; rax = s1 (it's a pointer) 
000005FB 488B00   mov rax,[rax]  ; dereference rax, store into rax. 
000005FE 4883C079   add rax,byte +0x79 ; rax = rax + 121 
00000602 488945F8   mov [rbp-0x8],rax ; void* n = s1 -> sp + 121 
00000606 488B45E8   mov rax,[rbp-0x18] ; rax = pointer to s1 
0000060A 8B4008   mov eax,[rax+0x8] ; dereference rax+8, store into eax. 
0000060D 0543010000  add eax,0x143  ; eax = eax + 323 
00000612 8945F4   mov [rbp-0xc],eax ; int d = s1 -> k + 323 
00000615 5D    pop rbp 
00000616 C3    ret 

Więcej informacji na temat Linuksa x86-64 wzywającą konwencję (System V ABI), patrz odpowiedź na What are the calling conventions for UNIX & Linux system calls on x86-64.

+0

A więc jakie jest znaczenie polecenia movq% rdi, -24 (% rbp)? Co zawiera rejestr% rdi? – user2290802

+0

@ user2290802 '% rdi' jest pierwszym argumentem, w tym przypadku' strc * s1'. Zobacz moją zredagowaną odpowiedź na wyjaśnienie demontażu. – nrz

+2

Cóż, jeśli chcesz produkować asm w formacie Intel, możesz użyć 'gcc -masm = intel -S'. To powinno wystarczyć. – perror

6

Funkcja rozkłada się następująco (ignorować zbędne linie)

Po pierwsze, nie jest oszczędność poprzedniego stosu ramy:

pushq %rbp 
movq %rsp, %rbp 

Tutaj stary %rbp jest wciskana stos do przechowywania do końca funkcji. Następnie %rbp jest ustawiona na wartość nowego %rsp (jest to jedna linia poniżej zapisanego %rbp jako zarejestrowanego push).

movq %rdi, -24(%rbp) 

Tutaj najpierw trzeba wiedzieć, jeden z głównych różnic pomiędzy i386 system V ABI i amd64 system V ABI.

W Systemie V ABI argumenty funkcji są przekazywane przez stos (i tylko przez stos). Wręcz przeciwnie, w amd64 System V ABI, argumenty są najpierw przekazywane za pośrednictwem rejestrów (%rdi, %rsi, %rdx, %rcx, %r8 i %r9 jeśli jest całkowite i %xmm0 do %xmm7 jeśli to pływaki). Gdy liczba rejestrów zostanie wyczerpana, pozostałe argumenty są przekazywane do stosu, jak w i386.

W tym miejscu maszyna właśnie ładuje na stos pierwszy tymczasowy argument funkcji (która jest liczbą całkowitą).

movq -24(%rbp), %rax 

Ponieważ nie można przesyłać dane bezpośrednio z jednego rejestru do drugiego, treść %rdi jest następnie załadowane do %rax. Tak więc, %rax przechowuje teraz pierwszy (i jedyny) argument tej funkcji.

movq (%rax), %rax 

Ta instrukcja jest tylko dereferencji wskaźnika, a także przechowywania wynik z powrotem w %rax.

addq $121, %rax 

Dodajemy 121 do wskazanej wartości.

movq %rax, -16(%rbp) 

Przechowujemy uzyskaną wartość na stosie.

movq -24(%rbp), %rax 

Ładujemy, znowu pierwszy argument funkcji w %rax (należy pamiętać, że przechowywane pierwszy argument na -24(%rbp)).

movl 8(%rax), %eax 
addl $323, %eax 

Jak poprzednio, mamy nieprawidłowego wskaźnika i przechowywania uzyskanych wartości w %eax a następnie dodamy do niego 323 i umieścić go z powrotem do %eax.

Uwaga, tutaj, że przeszli z %rax do %eax ponieważ wartość, że jesteśmy obsługi nie jest już a void* (64bits) jak poprzednio ale int (32-bitowego).

movl %eax, -4(%rbp) 

Wreszcie możemy zapisać wynik tego obliczenia do stosu (co wydaje się być bezużyteczne, ale to chyba coś niepotrzebnego, że kompilator nie wykryć w czasie kompilacji).

popq %rbp 
ret 

Dwa ostatnie instrukcje są tylko przywracając poprzednią ramkę stosu przed podając rękę z powrotem do funkcji main.

Mam nadzieję, że teraz to zachowanie będzie bardziej przejrzyste.

+0

Dzięki temu wygląda jasno. Więc znalazłem źródło mojego zamieszania. Jest to problem 32-bitowy i 64-bitowy. – user2290802

+0

@perror Małe wyjaśnienie: "Tutaj stary% rbp jest wypychany na stos, który ma być przechowywany do końca funkcji. Następnie% rsp jest ustawiony na wartość nowego% rbp (jest to jedna linia poniżej zapisano% rbp jako naciśnięcie). " -> Myślę, że zmieszałeś rsp i rbp. "movq% rsp,% rbp" ustawia RBP na wartość RSP, a nie na odwrót ... (Składnia AT & T) – libjup

+0

Masz rację, miałem na myśli: "Wtedy,'% rbp' jest ustawione na wartość nowy '% rsp' ..." (Wymieniłem rsp i rbp). Naprawiłem to w tekście. Dzięki za zwrócenie na to uwagi. – perror

1

można zmienić składni intel wprowadzając następującą komendę:

$ gcc -S -masm=intel test3.c -o test3.s 
+0

Witam. Witamy w StackOverflow. Zapoznaj się z tym, jak dobrze odpowiedzieć na pytanie :) Pamiętaj, że twoja odpowiedź nie jest odpowiedzią, ale podpowiedzią i dlatego powinna zostać umieszczona w komentarzu. Twoje zdrowie :) – DawidPi