2015-07-19 15 views
5

Per apprendere l'assemblaggio, sto visualizzando l'assembly generato da GCC utilizzando il comando -S per alcuni programmi c semplici. Ho una funzione add che accetta alcuni int e alcuni caratteri e li aggiunge. Mi chiedo solo perché i parametri char vengono inseriti nello stack come 8 byte (pushq)? Perché non spingere solo un singolo byte?Perché gcc passa il tipo di char in formato 8 byte per il funzionamento dell'assieme

.file "test.c" 
    .text 
    .globl add 
    .type add, @function 
add: 
.LFB0: 
    .cfi_startproc 
    pushq %rbp 
    .cfi_def_cfa_offset 16 
    .cfi_offset 6, -16 
    movq %rsp, %rbp 
    .cfi_def_cfa_register 6 
    movl %edi, -4(%rbp) 
    movl %esi, -8(%rbp) 
    movl %edx, -12(%rbp) 
    movl %ecx, -16(%rbp) 
    movl %r8d, -20(%rbp) 
    movl %r9d, -24(%rbp) 
    movl 16(%rbp), %ecx 
    movl 24(%rbp), %edx 
    movl 32(%rbp), %eax 
    movb %cl, -28(%rbp) 
    movb %dl, -32(%rbp) 
    movb %al, -36(%rbp) 
    movl -4(%rbp), %edx 
    movl -8(%rbp), %eax 
    addl %eax, %edx 
    movl -12(%rbp), %eax 
    addl %eax, %edx 
    movl -16(%rbp), %eax 
    addl %eax, %edx 
    movl -20(%rbp), %eax 
    addl %eax, %edx 
    movl -24(%rbp), %eax 
    addl %eax, %edx 
    movsbl -28(%rbp), %eax 
    addl %eax, %edx 
    movsbl -32(%rbp), %eax 
    addl %eax, %edx 
    movsbl -36(%rbp), %eax 
    addl %edx, %eax 
    popq %rbp 
    .cfi_def_cfa 7, 8 
    ret 
    .cfi_endproc 
.LFE0: 
    .size add, .-add 
    .globl main 
    .type main, @function 
main: 
.LFB1: 
    .cfi_startproc 
    pushq %rbp 
    .cfi_def_cfa_offset 16 
    .cfi_offset 6, -16 
    movq %rsp, %rbp 
    .cfi_def_cfa_register 6 
    pushq $9 
    pushq $8 
    pushq $7 
    movl $6, %r9d 
    movl $5, %r8d 
    movl $4, %ecx 
    movl $3, %edx 
    movl $2, %esi 
    movl $1, %edi 
    call add 
    addq $24, %rsp 
    leave 
    .cfi_def_cfa 7, 8 
    ret 
    .cfi_endproc 
.LFE1: 
    .size main, .-main 
    .ident "GCC: (Ubuntu 4.9.2-10ubuntu13) 4.9.2" 
    .section .note.GNU-stack,"",@progbits 
#include <stdio.h> 

int add(int a, int b, int c, int d, int e, int f, char g, char h, char i) 
{ 
    return a + b + c + d + e + f + g + h + i; 
} 

int main() 
{ 
    return add(1, 2, 3, 4, 5, 6, 7, 8, 9); 
} 
+6

Lo stack deve essere allineato alla dimensione della parola del sistema. Quindi non avrebbe senso essere in grado di spingere un singolo byte a meno che non fosse la dimensione della parola. –

+0

Non ho visto alcun 'char's. – alk

+0

@alk - nel generato asm, nemmeno l'op, quindi la domanda. I 'char's sono parametri della funzione' add' nel frammento di codice. ;) – enhzflep

risposta

3

Quando spingendo valori nello stack, la spinta deve essere sempre basata sulla dimensione della parola del sistema. Se sei un vecchio timer come me, questo è 16 bit (anche se ho dei sistemi a 12 bit per le parole!), Ma in realtà dipende dal sistema.

Dato che stai parlando di X86_64, parlerai di parole a 64 bit. La mia comprensione è che la dimensione della parola è tipicamente collegata al numero minimo di byte richiesto per indirizzare qualsiasi valore sulla RAM del sistema. Dato che hai uno spazio di memoria a 64 bit, è necessario un 64 bit (o 8 byte, una "quadrupla" basata sulla dimensione della parola originale a 16 bit).

9

È così perché l'x86-64 SystemV ABI lo richiede.

Vedere https://github.com/hjl-tools/x86-psABI/wiki/x86-64-psABI-r252.pdf per una copia della versione corrente della specifica. Si veda anche la tag wiki per i collegamenti di ABI

Vedere pagina 17 del PDF abi (e molto più roba buona.):

Classificazione La dimensione di ogni argomento viene arrotondato per eccesso eightbytes. (nota a piè pagina: quindi lo stack sarà sempre allineato a otto byte).

Ulteriori (pg 16: Stack Telaio):

La fine dell'area argomento di input viene ravvicinato 16 (32, se __m256 viene passato sulla pila) byte confine. In altre parole, il valore (%rsp + 8) è sempre un multiplo di 16 (32) quando il controllo è trasferito al punto di ingresso della funzione.

Se fossero progettati così diversi tipi interi hanno diverse larghezze in pila, ma tipi 8 byte erano ancora sempre allineate a 8 byte, ci sarebbe complicato regole su cui l'imbottitura va, (e quindi dove la funzione chiamata trova i suoi argomenti) a seconda dei tipi di argomenti attuali e precedenti. E significherebbe funzioni variadiche come printf avrebbe bisogno di una convenzione di chiamata diversa che non comprendesse gli argomenti.


Le spinte a 8 bit non sono affatto codificabili. Sono disponibili solo 16 bit (con prefisso 0x66) o 64 bit (senza prefisso o REX.W=1). Il manuale Intel è un po 'confuso su questo, implicando nel testo che push r32 è codificabile in modalità 64 bit (forse con REX.W = 0), ma è non il caso: Vedere How many bytes does the push instruction pushes onto the stack when I don't specify the operand size?.

+0

Probabilmente non è solo l'ABI. Non penso che si possa effettivamente spingere (usando un codice operativo PUSH) qualsiasi cosa eccetto le dimensioni del registro completo (ad esempio EAX per 32 bit o RAX per 64 bit) in modalità x86-32 o x86-64 (ma sono pronto per essere provato sbagliato). Ovviamente è possibile emulare spinte di dimensioni più piccole semplicemente posizionando i valori nello stack e aggiornando il puntatore dello stack, ma ciò non spinge realmente, IMO. –

+0

Sì, hai ragione. 'PUSH r/m32' e' PUSH r32' non sono codificabili in modalità 64 bit. Sono disponibili solo push da 64 e 16 bit (prefisso dimensione operando). 8b push non sono disponibili affatto. –

+0

Perché 16 bit e non 32 bit. Anche perché non 1 byte. – chasep255