Le variabili inizializzate con zero sezione .bss occupano spazio nel file elf?
Domanda
Se ho capito bene, la sezione .bss
nei file ELF viene utilizzata per allocare spazio per variabili a zero inizializzazione. La nostra catena di strumenti produce file ELF, quindi la mia domanda: la sezione .bss
deve effettivamente contenere tutti quegli zero? Sembra un terribile spreco di spazi che quando, diciamo, allocare un array globale da dieci megabyte, si ottengono dieci megabyte di zero nel file ELF. Cosa vedo di sbagliato qui?
Soluzione
È da un po 'di tempo che non lavoro con ELF. Ma penso di ricordare ancora questa roba. No, non contiene fisicamente quegli zeri. Se guardi un'intestazione del programma di file ELF, vedrai che ogni intestazione ha due numeri: uno è la dimensione nel file. E un'altra è la dimensione della sezione quando allocata nella memoria virtuale ( readelf -l ./a.out
):
Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
PHDR 0x000034 0x08048034 0x08048034 0x000e0 0x000e0 R E 0x4
INTERP 0x000114 0x08048114 0x08048114 0x00013 0x00013 R 0x1
[Requesting program interpreter: /lib/ld-linux.so.2]
LOAD 0x000000 0x08048000 0x08048000 0x00454 0x00454 R E 0x1000
LOAD 0x000454 0x08049454 0x08049454 0x00104 0x61bac RW 0x1000
DYNAMIC 0x000468 0x08049468 0x08049468 0x000d0 0x000d0 RW 0x4
NOTE 0x000128 0x08048128 0x08048128 0x00020 0x00020 R 0x4
GNU_STACK 0x000000 0x00000000 0x00000000 0x00000 0x00000 RW 0x4
Le intestazioni di tipo LOAD
sono quelle che vengono copiate nella memoria virtuale quando il file viene caricato per l'esecuzione. Altre intestazioni contengono altre informazioni, come le librerie condivise necessarie. Come vedi, FileSize
e MemSiz
differiscono in modo significativo per l'intestazione che contiene la sezione bss
(la seconda LOAD
uno):
0x00104 (file-size) 0x61bac (mem-size)
Per questo codice di esempio:
int a[100000];
int main() { }
La specifica ELF afferma che la parte di un segmento in cui la dimensione del mem è maggiore della dimensione del file è appena compilata con zeri nella memoria virtuale. La mappatura da segmento a sezione della seconda intestazione LOAD
è la seguente:
03 .ctors .dtors .jcr .dynamic .got .got.plt .data .bss
Quindi ci sono anche altre sezioni. Per costruttore / distruttore C ++. La stessa cosa per Java. Quindi contiene una copia della sezione .dynamic
e altre cose utili per il collegamento dinamico (credo che questo sia il posto che contiene le librerie condivise necessarie tra le altre cose). Successivamente la sezione .data
che contiene i globuli inizializzati e le variabili statiche locali. Alla fine, viene visualizzata la sezione .bss
, che viene riempita da zeri al momento del caricamento perché la dimensione del file non lo copre.
A proposito, puoi vedere in quale sezione di output verrà posizionato un particolare simbolo usando l'opzione del linker -M
. Per gcc, usi -Wl, -M
per passare l'opzione al linker. L'esempio sopra mostra che a
è allocato in .bss
. Può aiutarti a verificare che i tuoi oggetti non inizializzati finiscano davvero in .bss
e non altrove:
.bss 0x08049560 0x61aa0
[many input .o files...]
*(COMMON)
*fill* 0x08049568 0x18 00
COMMON 0x08049580 0x61a80 /tmp/cc2GT6nS.o
0x08049580 a
0x080ab000 . = ALIGN ((. != 0x0)?0x4:0x1)
0x080ab000 . = ALIGN (0x4)
0x080ab000 . = ALIGN (0x4)
0x080ab000 _end = .
GCC mantiene i globi non inizializzati in una sezione COMUNE per impostazione predefinita, per compatibilità con i vecchi compilatori, che consentono di definire i globi due volte in un programma senza errori di definizione multipla. Usa -fno-common
per fare in modo che GCC usi le sezioni .bss per i file oggetto (non fa differenza per l'eseguibile collegato finale, perché come vedi entrerà comunque in una sezione di output .bss Questo è controllato dallo script linker . Visualizzalo con ld -verbose
). Ma questo non dovrebbe spaventarti, è solo un dettaglio interno. Vedi la manpage di gcc.
Altri suggerimenti
La sezione .bss
in un file ELF viene utilizzata per i dati statici che non è inizializzato a livello di codice ma è garantito che sia impostato su zero in fase di esecuzione. Ecco un piccolo esempio che spiegherà la differenza.
int main() {
static int bss_test1[100];
static int bss_test2[100] = {0};
return 0;
}
In questo caso bss_test1
viene inserito nel .bss
poiché non è inizializzato. bss_test2
viene tuttavia inserito nel segmento .data
insieme a un gruppo di zeri. Il caricatore di runtime sostanzialmente alloca la quantità di spazio riservato per il .bss
e lo azzera prima che inizi a eseguire qualsiasi codice userland.
Puoi vedere la differenza usando objdump
, nm
o utilità simili:
moozletoots$ objdump -t a.out | grep bss_test
08049780 l O .bss 00000190 bss_test1.3
080494c0 l O .data 00000190 bss_test2.4
Questa è di solito una delle prime sorprese in cui si imbattono gli sviluppatori embedded ... non inizializzano mai la statica a zero in modo esplicito. Il caricatore di runtime (di solito) se ne occupa. Non appena inizializzi qualcosa in modo esplicito, stai dicendo al compilatore / linker di includere i dati nell'immagine eseguibile.
Una sezione .bss
non è memorizzata in un file eseguibile. Delle sezioni più comuni ( .text
, .data
, .bss
), solo .text
(codice effettivo ) e .data
(dati inizializzati) sono presenti in un file ELF.
È corretto, .bss non è presente fisicamente nel file, ma solo le informazioni sulla sua dimensione sono presenti per il caricatore dinamico per allocare la sezione .bss per il programma applicativo. Poiché la regola del pollice è solo LOAD, il segmento TLS ottiene la memoria per il programma applicativo, gli altri vengono utilizzati per il caricatore dinamico.
Sul file eseguibile statico, anche nelle sezioni bss viene dato spazio nell'eseguibile
Applicazione incorporata in cui non è presente alcun caricatore, questo è comune.
Suman