2011-08-26 10 views
6

Sto facendo qualche esperimento con l'assembly x86-64. Dopo aver compilato questa funzione fittizia:argomento gcc registro che si rovescia su x86-64

long myfunc(long a, long b, long c, long d, 
      long e, long f, long g, long h) 
{ 
    long xx = a * b * c * d * e * f * g * h; 
    long yy = a + b + c + d + e + f + g + h; 
    long zz = utilfunc(xx, yy, xx % yy); 
    return zz + 20; 
} 

Con gcc -O0 -g Sono rimasto sorpreso di trovare quanto segue all'inizio del montaggio della funzione:

0000000000400520 <myfunc>: 
    400520:  55      push rbp 
    400521:  48 89 e5    mov rbp,rsp 
    400524:  48 83 ec 50    sub rsp,0x50 
    400528:  48 89 7d d8    mov QWORD PTR [rbp-0x28],rdi 
    40052c:  48 89 75 d0    mov QWORD PTR [rbp-0x30],rsi 
    400530:  48 89 55 c8    mov QWORD PTR [rbp-0x38],rdx 
    400534:  48 89 4d c0    mov QWORD PTR [rbp-0x40],rcx 
    400538:  4c 89 45 b8    mov QWORD PTR [rbp-0x48],r8 
    40053c:  4c 89 4d b0    mov QWORD PTR [rbp-0x50],r9 
    400540:  48 8b 45 d8    mov rax,QWORD PTR [rbp-0x28] 
    400544:  48 0f af 45 d0   imul rax,QWORD PTR [rbp-0x30] 
    400549:  48 0f af 45 c8   imul rax,QWORD PTR [rbp-0x38] 
    40054e:  48 0f af 45 c0   imul rax,QWORD PTR [rbp-0x40] 
    400553:  48 0f af 45 b8   imul rax,QWORD PTR [rbp-0x48] 
    400558:  48 0f af 45 b0   imul rax,QWORD PTR [rbp-0x50] 
    40055d:  48 0f af 45 10   imul rax,QWORD PTR [rbp+0x10] 
    400562:  48 0f af 45 18   imul rax,QWORD PTR [rbp+0x18] 

gcc riversa in modo molto strano tutto argomento registra nello stack e poi prende li dalla memoria per ulteriori operazioni.

Questo succede solo su -O0 (con -O1 non ci sono problemi), ma ancora, perché? Questo mi sembra un anti-ottimizzazione: perché lo farebbe gcc?

+6

Penso che potresti averlo indietro. Sono abbastanza sicuro che quanto sopra è come GCC genera sempre (inizialmente) il codice, è solo che non lo vedrete normalmente perché è banalmente ottimizzato (ma ovviamente solo se le ottimizzazioni sono abilitate). – user786653

+0

Questo non è l'anti-ottimizzazione, non è solo l'ottimizzazione. – hirschhornsalz

+0

Avevo appena visto questo esempio da qualche parte: http://eli.thegreenplace.net/2011/09/06/stack-frame-layout-on-x86-64/ :-) –

risposta

7

Non sono affatto un esperto di GCC internals, ma ci provo. Sfortunatamente la maggior parte delle informazioni sull'assegnazione dei registri GCC e sullo spargimento sembra non essere aggiornata (facendo riferimento a file come local-alloc.c che non esistono più).

Sto guardando il codice sorgente di gcc-4.5-20110825.

In GNU C Compiler Internals viene indicato che il codice funzione iniziale viene generato da expand_function_start in gcc/function.c. Ci troviamo quanto segue per i parametri di movimentazione:

4462 /* Initialize rtx for parameters and local variables. 
4463  In some cases this requires emitting insns. */ 
4464 assign_parms (subr); 

Nel assign_parms il codice che gestisce in cui ogni argomento è memorizzato è la seguente:

3207  if (assign_parm_setup_block_p (&data)) 
3208   assign_parm_setup_block (&all, parm, &data); 
3209  else if (data.passed_pointer || use_register_for_decl (parm)) 
    assign_parm_setup_reg (&all, parm, &data); 
3211  else 
3212   assign_parm_setup_stack (&all, parm, &data); 

gestisce i tipi di dati aggregati e non è applicabile in questo caso e poiché i dati non vengono passati come un puntatore, GCC controlla use_register_for_decl.

Qui la parte rilevante è:

1972 if (optimize) 
1973  return true; 
1974 
1975 if (!DECL_REGISTER (decl)) 
1976  return false; 

DECL_REGISTER verifica se la variabile è stata dichiarata con la parola chiave register. E ora abbiamo la nostra risposta: la maggior parte dei parametri rimane in pila quando le ottimizzazioni non sono abilitate e vengono quindi gestite da assign_parm_setup_stack. Il percorso effettuato attraverso il codice sorgente prima che finisca per spargere il valore è leggermente più complicato per gli argomenti del puntatore, ma se sei curioso puoi rintracciarlo nello stesso file.

Perché GCC svuota tutti gli argomenti e le variabili locali con le ottimizzazioni disabilitate? Per aiutare il debug. Considerate questo semplice funzione:

1 extern int bar(int); 
2 int foo(int a) { 
3   int b = bar(a | 1); 
4   b += 42; 
5   return b; 
6 } 

Compilato con gcc -O1 -c questo genera il seguente sulla mia macchina:

0: 48 83 ec 08    sub $0x8,%rsp 
4: 83 cf 01    or  $0x1,%edi 
7: e8 00 00 00 00   callq c <foo+0xc> 
c: 83 c0 2a    add $0x2a,%eax 
f: 48 83 c4 08    add $0x8,%rsp 
13: c3      retq 

Che è bene, tranne se si rompe sulla linea 5 e tenta di stampare il valore di una, si ottenere

(gdb) print a 
$1 = <value optimized out> 

come argomento viene sovrascritto dal momento che non è usato dopo la chiamata a bar.

6

Un paio di motivi:

  1. Nel caso generale, un argomento a una funzione deve essere trattato come una variabile locale perché potrebbe essere memorizzata o avere l'indirizzo taken all'interno della funzione. Pertanto, è più semplice allocare uno slot di stack per ogni argomento.
  2. Le informazioni di debug diventano molto più semplici da emettere con le posizioni dello stack: il valore dell'argomento è sempre in una posizione specifica, invece di spostarsi tra registri e memoria.

Quando si guarda il codice -O0 in generale, si consideri che le priorità principali del compilatore riducono il tempo di compilazione il più possibile e generano informazioni di debug di alta qualità.

+1

Sì, e senza ottimizzazioni, il compilatore in particolare rende tutte le linee indipendenti, ricaricando sempre da variabili reali e memorizzandole immediatamente, che consente di spostare la CPU su un'altra linea, o modificare il valore di qualsiasi variabile nel debugger, e farlo funzionare correttamente. – doug65536