online - implement assembly in c




Cosa significa allineare lo stack? (4)

Sono stato un programmatore di alto livello e le architetture sono piuttosto nuove per me, quindi ho deciso di leggere il tutorial su Assembly qui:

http://en.wikibooks.org/wiki/X86_Assembly/Print_Version

In fondo al tutorial, le istruzioni su come convertire Hello World! programma

#include <stdio.h>

int main(void) {
    printf("Hello, world!\n");
    return 0;
}

in codice assembly equivalente è stato dato e il seguente è stato generato:

        .text
LC0:
        .ascii "Hello, world!\12\0"
.globl _main
_main:
        pushl   %ebp
        movl    %esp, %ebp
        subl    $8, %esp
        andl    $-16, %esp
        movl    $0, %eax
        movl    %eax, -4(%ebp)
        movl    -4(%ebp), %eax
        call    __alloca
        call    ___main
        movl    $LC0, (%esp)
        call    _printf
        movl    $0, %eax
        leave
        ret

Per una delle linee,

andl    $-16, %esp

la spiegazione era:

Questo codice "ed" ESP con 0xFFFFFFF0, allinea lo stack con il successivo limite di 16 byte più basso. Un esame del codice sorgente di Mingw rivela che questo potrebbe essere dovuto alle istruzioni SIMD che appaiono nella routine "_main", che operano solo su indirizzi allineati. Poiché la nostra routine non contiene istruzioni SIMD, questa linea non è necessaria.

Non capisco questo punto. Qualcuno può darmi una spiegazione di cosa significa allineare lo stack con il prossimo limite di 16 byte e perché è richiesto? E come sta andl l' andl ?


Dovrebbe essere solo agli indirizzi pari, non a quelli dispari, perché c'è un deficit di prestazioni che li accede.


Immagina questo "disegno"

addresses
 xxx0123456789abcdef01234567 ...
    [------][------][------] ...
registers

Valori agli indirizzi multipli di 8 "slide" facilmente nei registri (64 bit)

addresses
         56789abc ...
    [------][------][------] ...
registers

Ovviamente i registri "camminano" a passi di 8 byte

Ora se vuoi mettere il valore all'indirizzo xxx5 in un registro è molto più difficile :-)

Modifica e -16

-16 è 1111111111111111111111111111110000 in binario

quando "e" qualsiasi cosa con -16 si ottiene un valore con gli ultimi 4 bit impostati su 0 ... o un multiplo di 16.


Questo ha a che fare con l' en.wikipedia.org/wiki/Data_structure_alignment . Alcune architetture richiedono che gli indirizzi utilizzati per un insieme specifico di operazioni siano allineati a specifici limiti di bit.

Cioè, se si desidera l'allineamento a 64 bit per un puntatore, ad esempio, si potrebbe concettualmente suddividere l'intera memoria indirizzabile in blocchi a 64 bit partendo da zero. Un indirizzo verrebbe "allineato" se si adatta esattamente a uno di questi blocchi e non è allineato se ha preso parte a un blocco ea parte di un altro.

Una caratteristica significativa dell'allineamento dei byte (supponendo che il numero sia una potenza di 2) è che i bit X meno significativi dell'indirizzo sono sempre zero. Ciò consente al processore di rappresentare più indirizzi con meno bit semplicemente non utilizzando i bit X inferiori.


Questo non sembra essere uno stack specifico, ma l'allineamento in generale. Forse pensa al termine intero multiplo.

Se nella memoria sono presenti elementi di dimensioni pari a un byte, unità di 1, quindi è sufficiente dire che sono tutti allineati. Le cose che hanno due byte di dimensione, quindi i numeri interi di volte 2 saranno allineati, 0, 2, 4, 6, 8, ecc. E i multipli non interi, 1, 3, 5, 7 non saranno allineati. Gli elementi con dimensione di 4 byte, i multipli interi 0, 4, 8, 12, ecc. Sono allineati, 1,2,3,5,6,7, ecc. Non lo sono. Lo stesso vale per 8, 0,8,16,24 e 16 16,32,48,64, e così via.

Ciò significa che puoi guardare l'indirizzo di base dell'elemento e determinare se è allineato.

size in bytes, address in the form of 
1, xxxxxxx
2, xxxxxx0
4, xxxxx00
8, xxxx000
16,xxx0000
32,xx00000
64,x000000
and so on

Nel caso di un compilatore che mischia dati con istruzioni nel segmento .text, è abbastanza semplice allineare i dati secondo necessità (beh, dipende dall'architettura). Ma lo stack è una cosa runtime, il compilatore normalmente non può determinare dove sarà lo stack in fase di esecuzione. Quindi, in fase di esecuzione se si dispone di variabili locali che devono essere allineate, è necessario che il codice regoli lo stack a livello di codice.

Supponiamo ad esempio di avere due elementi da 8 byte nello stack, 16 byte totali, e li vuoi veramente allineati (su limiti di 8 byte). All'ingresso la funzione dovrebbe sottrarre 16 dal puntatore dello stack come al solito per fare spazio a questi due elementi. Ma per allinearli ci dovrebbe essere più codice. Se volessimo che questi due elementi da 8 byte fossero allineati su limiti di 8 byte e il puntatore dello stack dopo aver sottratto 16 fosse 0xFF82, beh, i 3 bit inferiori non sono 0, quindi non è allineato. I tre bit inferiori sono 0b010. In senso generico, vogliamo sottrarre 2 da 0xFF82 per ottenere 0xFF80. Il modo in cui lo determiniamo è un 2 con l'aggiunta di 0b111 (0x7) e la sottrazione di tale importo. Ciò significa che le operazioni di alu sono una e una sottrazione. Ma possiamo prendere una scorciatoia se noi e con il complemento valore di 0x7 (~ 0x7 = 0xFFFF ... FFF8) otteniamo 0xFF80 usando una operazione alu (a patto che il compilatore e il processore abbiano un unico modo opcode per farlo, in caso contrario potrebbe costarti di più rispetto a e e sottrarre).

Questo sembra essere ciò che il tuo programma stava facendo. Anding con -16 è lo stesso di anding con 0xFFFF .... FFF0, risultante in un indirizzo allineato su un limite di 16 byte.

Quindi, per concludere, se si dispone di qualcosa come un tipico puntatore dello stack che si sposta verso il basso la memoria dagli indirizzi più alti agli indirizzi inferiori, allora si desidera

 
sp = sp & (~(n-1))

dove n è il numero di byte da allineare (devono essere poteri ma va bene la maggior parte dell'allineamento di solito implica poteri di due). Se hai detto di fare un malloc (gli indirizzi aumentano da basso ad alto) e vuoi allineare l'indirizzo di qualcosa (ricorda di malloc più del necessario almeno della dimensione di allineamento) quindi

if(ptr&(~(n-)) { ptr = (ptr+n)&(~(n-1)); }

O se vuoi solo prendere il se là fuori ed eseguire l'add e mask ogni volta.

molte / molte architetture non x86 hanno regole e requisiti di allineamento. x86 è eccessivamente flessibile per quanto riguarda il set di istruzioni, ma per quanto riguarda l'esecuzione si può / si paga una penalità per gli accessi non allineati su un x86, quindi anche se si può fare si dovrebbe sforzarsi di rimanere allineati come si farebbe con qualsiasi altra architettura. Forse è quello che stava facendo questo codice.





assembly