Esempi minimizzabili di Linux con analisi di disassemblaggio
Dato che si tratta di un dettaglio dell'implementazione non specificato dagli standard, diamo un'occhiata a cosa sta facendo il compilatore in una particolare implementazione.
In questa risposta, collegherò a risposte specifiche che eseguono l'analisi, o fornirò l'analisi direttamente qui, e riassumerò tutti i risultati qui.
Tutti questi sono in varie versioni di Ubuntu / GCC e i risultati sono probabilmente abbastanza stabili tra le versioni, ma se troviamo delle variazioni specificiamo versioni più precise.
Variabile locale all'interno di una funzione
Sia esso main
o qualsiasi altra funzione:
void f(void) {
int my_local_var;
}
Come mostrato in: Cosa significa <valore ottimizzato> in gdb?
-O0
: stack
-O3
: registra se non si rovesciano, impila diversamente
Per motivi sul perché esiste lo stack, vedere: Qual è la funzione delle istruzioni push / pop utilizzate sui registri nell'assembly x86?
Variabili globali e static
variabili di funzione
/* BSS */
int my_global_implicit;
int my_global_implicit_explicit_0 = 0;
/* DATA */
int my_global_implicit_explicit_1 = 1;
void f(void) {
/* BSS */
static int my_static_local_var_implicit;
static int my_static_local_var_explicit_0 = 0;
/* DATA */
static int my_static_local_var_explicit_1 = 1;
}
char *
e char c[]
Come mostrato in: Dove sono archiviate le variabili statiche in C e C ++?
void f(void) {
/* RODATA / TEXT */
char *a = "abc";
/* Stack. */
char b[] = "abc";
char c[] = {'a', 'b', 'c', '\0'};
}
TODO verranno messi in pila anche letterali di stringhe molto grandi? Oppure .data
? O la compilazione fallisce?
Argomenti di funzione
void f(int i, int j);
È necessario seguire la convenzione di chiamata pertinente, ad esempio: https://en.wikipedia.org/wiki/X86_calling_conventions per X86, che specifica registri specifici o posizioni di stack per ogni variabile.
Quindi, come mostrato in Cosa significa <valore ottimizzato> in gdb? , -O0
quindi inserisce tutto nello stack, mentre -O3
tenta di utilizzare i registri il più possibile.
Se la funzione viene tuttavia incorporata, vengono trattate come normali locali.
const
Credo che non faccia alcuna differenza perché puoi scriverlo via.
Al contrario, se il compilatore è in grado di determinare che alcuni dati non vengono mai scritti, in teoria potrebbe posizionarli .rodata
anche se non cost.
Analisi TODO.
puntatori
Sono variabili (che contengono indirizzi, che sono numeri), così come tutto il resto :-)
malloc
La domanda non ha molto senso per malloc
, poiché malloc
è una funzione e in:
int *i = malloc(sizeof(int));
*i
è una variabile che contiene un indirizzo, quindi rientra nel caso precedente.
Per quanto riguarda il funzionamento interno di malloc, quando lo chiami il kernel Linux segna alcuni indirizzi come scrivibili sulle sue strutture di dati interne e quando vengono toccati inizialmente dal programma, si verifica un errore e il kernel abilita le tabelle delle pagine, che consentono l'accesso succede senza segfaul: come funziona il paging x86?
Si noti tuttavia che questo è fondamentalmente esattamente ciò che fa il exec
syscall quando si tenta di eseguire un eseguibile: contrassegna le pagine in cui si desidera caricare e scrive lì il programma, vedere anche: In che modo il kernel ottiene un file binario eseguibile in esecuzione sotto Linux? Tranne che exec
ha alcune limitazioni extra su dove caricare (ad es. Il codice non è trasferibile ).
L'esatto syscall usato malloc
è mmap
nelle moderne implementazioni 2020 e in passato è brk
stato usato: malloc () usa brk () o mmap ()?
Librerie dinamiche
Fondamentalmente mmap
vai in memoria: /unix/226524/what-system-call-is-used-to-load-libraries-in-linux/462710#462710
variabili envinroment e main
'sargv
Sopra lo stack iniziale: /unix/75939/where-is-the-environment-string-actual-stored TODO perché non in .data?