2015-05-13 8 views
13

Nel famoso documento "Smashing Stack per divertimento e profitto", il suo autore prende una funzione Callineamento di memoria di oggi e 20 anni fa

void function(int a, int b, int c) { 
    char buffer1[5]; 
    char buffer2[10]; 
} 

e genera l'uscita codice assembly corrispondente

pushl %ebp 
movl %esp,%ebp 
subl $20,%esp 

L'autore spiega che poiché i computer indirizzano la memoria in multipli di dimensione della parola, il compilatore ha riservato 20 byte nello stack (8 byte per il buffer1, 12 byte per il buffer2).

ho cercato di ricreare questo esempio e ha ottenuto la seguente

pushl %ebp 
movl %esp, %ebp 
subl $16, %esp 

un risultato diverso! Ho provato varie combinazioni di dimensioni per buffer1 e buffer2, e sembra che il moderno gcc non riempia più le dimensioni del buffer in multipli di word size. Invece si abbandona l'opzione -mpreferred-stack-boundary.

Come illustrazione - utilizzando le regole aritmetiche della carta, per buffer1 [5] e buffer2 [13] ottengo 8 + 16 = 24 byte riservati nello stack. Ma in realtà ho avuto 32 byte.

La carta è piuttosto vecchia e da allora sono successe molte cose. Mi piacerebbe sapere, cosa ha motivato esattamente questo cambiamento di comportamento? È la mossa verso le macchine a 64 bit? O qualcos'altro?

Modifica

Il codice è compilato su una macchina x86_64 usando gcc version 4.8.2 (Ubuntu 4.8.2-19ubuntu1) così:

$ gcc -S -o example1.s example1.c -fno-stack-protector -m32

+4

8 + 16 è 24, non 20. A proposito, sembra probabile che il compilatore sia diventato un po 'più intelligente, il suo pass di analisi delle variabili locali ha preso in considerazione entrambi gli array di caratteri, e poiché gli array di caratteri non hanno bisogno di alcun allineamento, li ha semplicemente incollati e allineati alla matrice "compatta" risultante. –

+4

C'è qualche punto nell'allineamento dei valori di 'char' in ogni caso? –

+2

si trova su x86 o x86_64? – rmmh

risposta

7

Quello che è cambiato è SSE, che richiede 16 allineamento di byte, questo è coperto in questo documento gcc più vecchia per -mpreferred-stack-boundary=num che dice (sottolineatura mia):

Il Pentium e PentiumPro, doppie e long double i valori devono essere allineati a un limite di 8 byte (vedere -malign-double) o subire significative penalizzazioni delle prestazioni durante l'esecuzione. Su Pentium III, il tipo di dati Streaming SIMD Extension (SSE) __m128 subisce sanzioni simili se non è allineato a 16 byte.

Questo è anche sostenuta dalla carta Smashing The Modern Stack For Fun And Profit che copre questo un altre variazioni moderne che rompono Smashing Stack per divertimento e profitto.

0

non ho provato che versione specifica del compilatore o della versione di distribuzione che hai segnalato. La mia ipotesi sarebbe il 16 è dai requisiti di allineamento dei byte sullo stack (cioè tutte le regolazioni dello stack sarebbero allineate con x byte e x potrebbero essere 16 per l'invocazione).

Nota che l'allineamento delle variabili con cui sembra che tu abbia iniziato, è leggermente diverso da quanto sopra ed è controllato dai segni di allineamento sulla variabile in gcc. Prova a utilizzarli e dovresti vedere una differenza.

2

L'allineamento della memoria di quale allineamento dello stack è solo un aspetto dipende dall'architettura. È parzialmente definito nell'interfaccia binaria Applicaion della lingua e in uno standard di chiamata alla procedura (a volte è in una singola specifica) per l'architettura (CPU, potrebbe anche variare a seconda della piattaforma) e dipende anche dal compilatore/toolchain dove i precedenti documenti lasciano spazio a variazioni.

I primi due documenti (i nomi possono variare) sono principalmente per l'interfaccia esterna tra le funzioni; potrebbero lasciare la struttura interna alla toolchain. Comunque, questo deve corrispondere all'architettura. Normalmente l'hardware richiede un allineamento minimo, ma consente un allineamento più grande per motivi di prestazioni (ad es. Minimo di allineamento di byte, ma ciò richiederebbe più cicli di bus per leggere una parola a 32 bit, quindi il compilatore usa un allineamento a 32 bit).

Normalmente, il compilatore (seguendo il PCS) utilizza un allineamento ottimale per l'architettura e sotto controllo delle impostazioni di ottimizzazione (ottimizzazione per velocità o dimensioni). Prende in considerazione non solo le dimensioni dell'oggetto (allineato al suo limite naturale), ma anche le dimensioni dei bus interni (ad esempio un 32 bit x86 ha bus interni a 64 o 128 bit, le CPU ARM hanno interni da 32 a 128 (forse anche più ampio) bus di bit), cache, ecc.Per le variabili locali, può anche tener conto dei pattern di accesso, quindi due variabili adiacenti possono essere caricate in parallelo in una coppia di registri invece di due carichi separati o anche riordinare tali variabili.

Lo stackpointer potrebbe richiedere un allineamento più elevato, ad esempio, in modo che la CPU possa inserire in un frame di interrupt due registri contemporaneamente, premere registri vettoriali che richiedono un allineamento superiore, ecc. È possibile scrivere un libro piuttosto spessa su questo argomento (e Scommetto che qualcuno ha già).

Quindi, in generale, non esiste un'unica regola per allineamento singolo. Tuttavia, per struct e array packing, lo standard C fa definisce alcune regole per il packing/allineamento, principalmente per garantire la coerenza di ad es. sizeof (tipo) e l'indirizzo in un array (richiesto per correggere malloc()).

Anche gli array di caratteri potrebbero essere allineati per un layout di cache ottimale. Si noti che non è solo la CPU che potrebbe avere cache, ma anche bridge PCIe, per non parlare dei trasferimenti PCIe stessi alle pagine DRAM.