GNU/Linux >> Linux Esercitazione >  >> Linux

Qual è il significato di ciascuna riga dell'output dell'assieme di un ciao mondo?

Ecco come va:

        .file   "test.c"

Il nome del file di origine originale (utilizzato dai debugger).

        .section        .rodata
.LC0:
        .string "Hello world!"

Una stringa con terminazione zero è inclusa nella sezione ".rodata" ("ro" significa "sola lettura":l'applicazione sarà in grado di leggere i dati, ma qualsiasi tentativo di scrittura su di essi attiverà un'eccezione).

        .text

Ora scriviamo le cose nella sezione ".text", che è dove va il codice.

.globl main
        .type   main, @function
main:

Definiamo una funzione chiamata "main" e globalmente visibile (altri file oggetto potranno invocarla).

        leal    4(%esp), %ecx

Archiviamo nel registro %ecx il valore 4+%esp (%esp è il puntatore dello stack).

        andl    $-16, %esp

%esp è leggermente modificato in modo che diventi un multiplo di 16. Per alcuni tipi di dati (il formato a virgola mobile corrispondente al double di C e long double ), le prestazioni sono migliori quando gli accessi alla memoria sono a indirizzi multipli di 16. Questo non è veramente necessario qui, ma se usato senza il flag di ottimizzazione (-O2 ...), il compilatore tende a produrre un bel po' di codice generico inutile (cioè codice che potrebbe essere utile in alcuni casi ma non qui).

        pushl   -4(%ecx)

Questo è un po' strano:a quel punto, la parola all'indirizzo -4(%ecx) è la parola che era in cima alla pila prima di andl . Il codice recupera quella parola (che dovrebbe essere l'indirizzo di ritorno, tra l'altro) e la spinge di nuovo. Questo tipo di emula ciò che si otterrebbe con una chiamata da una funzione che aveva uno stack allineato a 16 byte. La mia ipotesi è che questo push è un residuo di una sequenza di copia di argomenti. Poiché la funzione ha modificato il puntatore dello stack, deve copiare gli argomenti della funzione, che erano accessibili tramite il vecchio valore del puntatore dello stack. Qui non ci sono argomenti, eccetto l'indirizzo di ritorno della funzione. Nota che questa parola non verrà utilizzata (ancora una volta, questo è codice senza ottimizzazione).

        pushl   %ebp
        movl    %esp, %ebp

Questo è il prologo della funzione standard:salviamo %ebp (poiché stiamo per modificarlo), quindi imposta %ebp puntare allo stack frame. Successivamente, %ebp verrà utilizzato per accedere agli argomenti della funzione, creando %esp di nuovo libero. (Sì, non ci sono argomenti, quindi è inutile per quella funzione.)

        pushl   %ecx

Salviamo %ecx (ne avremo bisogno all'uscita dalla funzione, per ripristinare %esp al valore che aveva prima di andl ).

        subl    $20, %esp

Riserviamo 32 byte sullo stack (ricorda che lo stack cresce "verso il basso"). Quello spazio verrà utilizzato per memorizzare gli argomenti di printf() (questo è eccessivo, dato che c'è un singolo argomento, che utilizzerà 4 byte [questo è un puntatore]).

        movl    $.LC0, (%esp)
        call    printf

"Spingiamo" l'argomento a printf() (cioè ci assicuriamo che %esp punta a una parola che contiene l'argomento, qui $.LC0 , che è l'indirizzo della stringa costante nella sezione rodata). Quindi chiamiamo printf() .

        addl    $20, %esp

Quando printf() restituisce, rimuoviamo lo spazio allocato per gli argomenti. Questo addl annulla ciò che il subl sopra.

        popl    %ecx

Recuperiamo %ecx (spinto sopra); printf() potrebbe averlo modificato (le convenzioni di chiamata descrivono quali registri possono essere modificati da una funzione senza ripristinarli all'uscita; %ecx è uno di questi registri).

        popl    %ebp

Epilogo della funzione:ripristina %ebp (corrispondente al pushl %ebp sopra).

        leal    -4(%ecx), %esp

Ripristiniamo %esp al suo valore iniziale. L'effetto di questo codice operativo è di memorizzare in %esp il valore %ecx-4 . %ecx è stato impostato nel primo codice operativo della funzione. Questo annulla qualsiasi modifica a %esp , incluso il andl .

        ret

Uscita dalla funzione.

        .size   main, .-main

Questo imposta la dimensione del main() function:in qualsiasi momento durante l'assembly, ". " è un alias per "l'indirizzo a cui stiamo aggiungendo le cose in questo momento". Se un'altra istruzione è stata aggiunta qui, andrebbe all'indirizzo specificato da ". ". Pertanto, ".-main ", qui, è la dimensione esatta del codice della funzione main() . Il .size indica all'assembler di scrivere tali informazioni nel file oggetto.

        .ident  "GCC: (GNU) 4.3.0 20080428 (Red Hat 4.3.0-8)"

GCC ama solo lasciare tracce della sua azione. Questa stringa finisce come una sorta di commento nel file oggetto. Il linker lo rimuoverà.

        .section        .note.GNU-stack,"",@progbits

Una sezione speciale in cui GCC scrive che il codice può contenere uno stack non eseguibile. Questo è il caso normale. Gli stack eseguibili sono necessari per alcuni usi speciali (non C standard). Sui processori moderni, il kernel può creare uno stack non eseguibile (uno stack che attiva un'eccezione se qualcuno tenta di eseguire come codice alcuni dati che si trovano nello stack); questo è visto da alcune persone come una "caratteristica di sicurezza" perché mettere il codice nello stack è un modo comune per sfruttare gli overflow del buffer. Con questa sezione, l'eseguibile sarà contrassegnato come "compatibile con uno stack non eseguibile" che il kernel fornirà felicemente come tale.


    leal    4(%esp), %ecx
    andl    $-16, %esp
    pushl   -4(%ecx)
    pushl   %ebp
    movl    %esp, %ebp
    pushl   %ecx
    subl    $20, %esp

queste istruzioni non si confrontano nel tuo programma c, sono sempre eseguite all'inizio di ogni funzione (ma dipende dal compilatore/piattaforma)

    movl    $.LC0, (%esp)
    call    printf

questo blocco corrisponde alla tua chiamata printf(). la prima istruzione mette nello stack il suo argomento (un puntatore a "ciao mondo") quindi chiama la funzione.

    addl    $20, %esp
    popl    %ecx
    popl    %ebp
    leal    -4(%ecx), %esp
    ret

queste istruzioni sono opposte al primo blocco, sono una sorta di materiale per la manipolazione dello stack. sempre eseguito anch'esso


Ecco qualche supplemento a @Thomas Pornin la risposta di.

  • .LC0 costante locale, ad esempio stringa letterale.
  • .LFB0 inizio della funzione locale,
  • .LFE0 fine della funzione locale,

Il suffisso di queste etichette è un numero e parte da 0.

Questa è la convenzione dell'assembler gcc.


Linux
  1. Come inserire del testo all'inizio di ogni riga in Vim

  2. Quali sono le responsabilità di ciascun componente pseudo-terminale (pty) (software, lato master, lato slave)?

  3. Come dire a quale versione di Os X mi trovo dalla riga di comando?

  4. Contando i caratteri di ogni riga con Wc?

  5. Cosa significa nell'output di Ps?

Iterazione su ogni riga dell'output di ls -l

Qual è il secondo stato nell'output di ip link show

Qual è la colonna dei buffer nell'output da free?

qual è il significato di 1 alla fine dello script awk

Qual è il significato di curl -k -i -X ​​in Linux?

Qual è l'utilità della riga di comando del DNS inverso?