2013-04-18 24 views
5

Voglio conoscere la convenzione di chiamata C. Per fare questo ho scritto il seguente codice:Comprensione C chiamata smontata

#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; 
} 

poi ho usato GCC con il seguente comando:

gcc -S test3.c 

e si avvicinò con il suo montaggio. Non mostrerò l'intero codice che ho ottenuto, ma piuttosto incollerò il codice per la funzione my_func. È questo:

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 

Per quanto ho capito, questo è ciò che accade: primo puntatore base chiamanti viene spinto nella pila ed il suo stack pointer viene fatto il nuovo puntatore base per impostare lo stack per il nuovo funzione. Ma poi il resto non capisco. Per quanto ne so, gli argomenti (o il puntatore all'argomento) sono memorizzati nello stack. In caso affermativo qual è lo scopo della seconda istruzione,

movq  -24(%rbp), %rax 

Qui, il contenuto del registro% rax viene spostato all'indirizzo 24 byte dal indirizzo nel registro% rbp. Ma cosa c'è in% rax ???? Niente è inizialmente memorizzato lì ??? Penso di essere confuso. Si prega di aiutare a capire come funziona questa funzione. Grazie in anticipo!

+2

Compilare con 'gcc -fverbose-asm -s' e forse anche' gcc -fverbose-asm -O -s'; vedi anche [questa risposta] (http://stackoverflow.com/a/16088155/841108) che fornisce un * lotto * di riferimenti. –

+0

Grazie per tutti i riferimenti e il consiglio di compilazione. – user2290802

risposta

9

Si confonde la sintassi AT & con la sintassi Intel.

 
movq -24(%rbp), %rax 

In Intel sintassi sarebbe

 
mov rax,[rbp-24] 

Quindi muove dati affrontate da rbp a rax, e non viceversa. L'ordine degli operandi è src, dest nella sintassi AT &, mentre nella sintassi Intel è dest, src.

Quindi, per eliminare le direttive GAS per rendere più facile la lettura dello smontaggio, ho assemblato il codice con gcc semplicemente con gcc test3.c e lo ho smontato con ndisasm -b 64 a.out. Nota lo smontaggio di my_func funzione prodotta da NDISASM sotto è nella sintassi 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 

Per informazioni su Linux x86-64 convenzione di chiamata (System V ABI), vedere le risposte a What are the calling conventions for UNIX & Linux system calls on x86-64.

+0

Bene, allora qual è il significato del comando movq% rdi, -24 (% rbp)? Cosa c'è nel registro% rdi? – user2290802

+0

@ user2290802 '% rdi' è il primo argomento, in questo caso' strc * s1'. Vedere la mia risposta modificata per la spiegazione dello smontaggio. – nrz

+2

Bene, se vuoi produrre asm nel formato Intel, puoi usare 'gcc -masm = intel -S'. Questo dovrebbe essere sufficiente. – perror

6

La funzione è decomposto simili (ignoro le righe non necessarie):

primo luogo, v'è il risparmio del precedente stack frame:

pushq %rbp 
movq %rsp, %rbp 

Qui, il vecchio %rbp viene calettata la pila da memorizzare fino alla fine della funzione. Quindi, lo %rbp viene impostato sul valore del nuovo %rsp (è una riga sotto lo %rbp salvato come push).

movq %rdi, -24(%rbp) 

Qui è necessario prima conoscere una delle principali differenze tra la i386 system V ABI e amd64 system V ABI.

In i386 System V ABI gli argomenti della funzione vengono passati attraverso lo stack (e solo attraverso lo stack). Al contrario, in amd64 System V ABI, gli argomenti vengono dapprima fatti passare attraverso registri (%rdi, %rsi, %rdx, %rcx, %r8 e %r9 se è interi, e %xmm0 a %xmm7 se questo è galleggia). Una volta esaurito il numero di registri, gli argomenti rimanenti vengono inviati in pila come in i386.

Quindi, qui, la macchina sta solo caricando il primo argomento della funzione (che è un numero intero) temporaneo nello stack.

movq -24(%rbp), %rax 

Poiché non è possibile trasferire i dati direttamente da un registro ad un altro, il contenuto di %rdi viene poi caricato in %rax. Quindi, %rax memorizza ora il primo (e l'unico) argomento di questa funzione.

movq (%rax), %rax 

Questa istruzione è solo dereferenziazione il puntatore e memorizzare il risultato nel %rax.

addq $121, %rax 

Aggiungiamo 121 al valore indicato.

movq %rax, -16(%rbp) 

Memorizziamo il valore ottenuto nello stack.

movq -24(%rbp), %rax 

Carichiamo, ancora una volta il primo argomento della funzione in %rax (ricordiamo che abbiamo memorizzato il primo argomento a -24(%rbp)).

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

Come in precedenza, abbiamo dereference il puntatore e memorizzare il valore ottenuto in %eax e poi aggiungiamo 323 ad esso e rimetterlo a %eax.

nota, qui, che siamo passati %rax-%eax perché il valore che stiamo trattando non è più un void* (64bits) come in precedenza, ma un int (32bit).

movl %eax, -4(%rbp) 

Infine, memorizzare il risultato di questo calcolo per lo stack (che sembra essere inutile qui, ma probabilmente è qualcosa di inutile che il compilatore non ha rilevato al momento della compilazione).

popq %rbp 
ret 

Le due istruzioni finali stanno ripristinando la precedente stack telaio prima di dare la mano posteriore alla funzione main.

Spero che questo rendano questo comportamento più chiaro ora.

+0

Grazie sembra chiaro. Quindi ho trovato la fonte della mia confusione. È il problema a 32 e 64 bit che sto avendo. – user2290802

+0

@perror Piccolo chiarimento: "Qui, il vecchio% rbp viene inserito nello stack per essere archiviato fino alla fine della funzione. Quindi,% rsp è impostato sul valore del nuovo% rbp (è una riga sotto il salvato% rbp come si è verificato un push). " -> Penso che tu abbia mixato rsp e rbp. "movq% rsp,% rbp" imposta RBP sul valore di RSP e non viceversa ... (sintassi AT & T) – libjup

+0

Hai ragione, volevo dire: "Quindi, il'% rbp' è impostato sul valore del nuovo '% rsp' ..." (Ho scambiato rsp e rbp). Ho risolto questo nel testo. Grazie per averlo notato. – perror

1

È possibile modificare a sintassi intel immettendo il seguente comando:

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

Ciao. Benvenuto in StackOverflow. Si prega di prendere dimestichezza con il modo di rispondere alle domande :) Si noti che la risposta non è una risposta, ma piuttosto un suggerimento e pertanto deve essere inserito in un commento. Saluti :) – DawidPi