Output dell'assembly GCC di un programma vuoto su x86, win32

Output dell'assembly GCC di un programma vuoto su x86, win32


Scrivo programmi vuoti per infastidire a morte i programmatori di stackoverflow, NON. Sto solo esplorando la toolchain di gnu.


Ora quanto segue potrebbe essere troppo approfondito per me, ma per continuare la saga del programma vuoto ho iniziato a esaminare l'output del compilatore C, la roba che GNU consuma.


gcc version 4.4.0 (TDM-1 mingw32)

test.c:


int main()
{
return 0;
}

gcc -S test.c


    .file   "test.c"
.def ___main; .scl 2; .type 32; .endef
.text
.globl _main
.def _main; .scl 2; .type 32; .endef
_main:
pushl %ebp
movl %esp, %ebp
andl $-16, %esp
call ___main
movl $0, %eax
leave
ret

Puoi spiegare cosa succede qui? Ecco il mio sforzo per capirlo. Ho usato il as manuale e la mia conoscenza minima di x86 ASM:



  • .file "test.c" è la direttiva per il nome file logico.

  • .def :secondo i documenti "Inizia a definire le informazioni di debug per un nome di simbolo" . Che cos'è un simbolo (nome di una funzione/variabile?) e che tipo di informazioni di debugging?

  • .scl :i documenti dicono "La classe di archiviazione può segnalare se un simbolo è statico o esterno" . È lo stesso statico ed esterno lo so da C? E cos'è quel "2"?

  • .type :memorizza il parametro "come attributo di tipo di una voce della tabella dei simboli" , non ne ho idea.

  • .endef :nessun problema.

  • .text :Ora questo è problematico, sembra essere qualcosa chiamato sezione e ho letto che è il posto per il codice, ma i documenti non mi hanno detto molto.

  • .globl "rende il simbolo visibile a ld." , il manuale è abbastanza chiaro su questo.

  • _main: Questo potrebbe essere l'indirizzo iniziale (?) per la mia funzione principale

  • pushl_ :un push lungo (32 bit), che mette l'EBP sullo stack

  • movl :spostamento a 32 bit. Pseudo-C:EBP = ESP;

  • andl :E logico. Pseudo-C:ESP = -16 & ESP , Non vedo davvero qual è il senso di questo.

  • call :spinge l'IP nello stack (in modo che la procedura chiamata possa ritrovare la via del ritorno) e continua dove __main è. (cos'è __principale?)

  • movl :questo zero deve essere la costante che restituisco alla fine del mio codice. Il MOV inserisce questo zero in EAX.

  • leave :ripristina lo stack dopo un'istruzione ENTER (?). Perché?

  • ret :torna all'indirizzo dell'istruzione salvato nello stack


Grazie per il tuo aiuto!


Risposte:



Comandi che iniziano con . sono direttive per l'assemblatore. Questo dice solo che questo è "file.c", che le informazioni possono essere esportate nelle informazioni di debug dell'exe.



Le direttive .def definiscono un simbolo di debug. scl 2 significa classe di archiviazione 2 (classe di archiviazione esterna) .type 32 dice che questo sumbol è una funzione. Questi numeri saranno definiti dal formato pe-coff exe


___main è una funzione chiamata che si occupa del bootstrap di cui ha bisogno gcc (farà cose come eseguire inizializzatori statici c++ e altre operazioni di pulizia necessarie).



Inizia una sezione di testo:il codice risiede qui.



definisce il simbolo _main come globale, che lo renderà visibile al linker e agli altri moduli a cui è collegato.



Stessa cosa di _main , crea simboli di debug che affermano che _main è una funzione. Questo può essere utilizzato dai debugger.



Inizia una nuova etichetta (finirà un indirizzo). la direttiva .globl sopra rende questo indirizzo visibile ad altre entità.



Salva il vecchio puntatore del frame (registro ebp) nello stack (in modo che possa essere riposizionato al termine di questa funzione)



Sposta il puntatore dello stack nel registro ebp. ebp è spesso chiamato frame pointer, punta in cima ai valori dello stack all'interno del "frame" corrente (funzione di solito), (riferirsi alle variabili nello stack tramite ebp può aiutare i debugger)



E lo stack con fffffff0 che lo allinea efficacemente su un limite di 16 byte. L'accesso ai valori allineati nello stack è molto più rapido che se non fossero allineati. Tutte queste istruzioni precedenti sono praticamente un prologo di funzioni standard.


call        ___main

Chiama la funzione ___main che eseguirà l'inizializzazione delle cose di cui gcc ha bisogno. Call spingerà il puntatore dell'istruzione corrente sullo stack e salterà all'indirizzo di ___main



sposta 0 nel registro eax, (lo 0 in cambio di 0;) il registro eax viene utilizzato per contenere i valori di ritorno della funzione per la convenzione di chiamata stdcall.



L'istruzione di congedo è praticamente un'abbreviazione per



cioè "annulla" le operazioni eseguite all'inizio della funzione, ripristinando il puntatore del frame e lo stack al suo stato precedente.



Ritorna a chi ha chiamato questa funzione. Aprirà il puntatore dell'istruzione dallo stack (che un'istruzione di chiamata corrispondente avrà posizionato lì) e salterà lì.