c++ - utilizo - formas de inicializar una variable
¿Las variables inicializadas cero de la sección.bss ocupan espacio en el archivo elf? (4)
Si entiendo correctamente, la sección .bss
en archivos ELF se usa para asignar espacio para variables inicializadas cero. Nuestra cadena de herramientas produce archivos ELF, de ahí mi pregunta: ¿la sección .bss
realmente debe contener todos esos ceros? Parece una pérdida de espacio tan horrible que, cuando, digamos, asigno una matriz global de diez megabytes, resulta en diez megabytes de ceros en el archivo ELF. ¿Qué estoy viendo mal aquí?
Eso es correcto, .bss no está físicamente presente en el archivo, sino que solo la información sobre su tamaño está presente para que el cargador dinámico asigne la sección .bss para el programa de aplicación. Como regla general solo LOAD, TLS Segment obtiene la memoria para el programa de aplicación, rest se usa para el cargador dinámico.
Acerca del archivo ejecutable estático, las secciones bss también tienen espacio en el ejecutable
La aplicación integrada donde no hay cargador es común.
Suman
Ha pasado un tiempo desde que trabajé con ELF. Pero creo que todavía recuerdo esto. No, físicamente no contiene esos ceros. Si observa un encabezado de programa de archivos ELF, verá que cada encabezado tiene dos números: uno es el tamaño del archivo. Y otro es el tamaño que tiene la sección cuando se asigna en la memoria virtual ( 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
Los encabezados de tipo LOAD
son los que se copian en la memoria virtual cuando el archivo se carga para su ejecución. Otros encabezados contienen otra información, como las bibliotecas compartidas que se necesitan. Como puede ver, FileSize
y MemSiz
difieren significativamente para el encabezado que contiene la sección bss
(el segundo LOAD
uno):
0x00104 (file-size) 0x61bac (mem-size)
Para este código de ejemplo:
int a[100000];
int main() { }
La especificación ELF dice que la parte de un segmento que el tamaño de la mem es mayor que el tamaño del archivo simplemente se completa con ceros en la memoria virtual. El mapeo de segmento a sección del segundo encabezado LOAD
es así:
03 .ctors .dtors .jcr .dynamic .got .got.plt .data .bss
Entonces hay algunas otras secciones allí también. Para C ++ constructor / destructores. Lo mismo para Java. Luego contiene una copia de la sección .dynamic
y otras cosas útiles para la vinculación dinámica (creo que este es el lugar que contiene las bibliotecas compartidas necesarias entre otras cosas). Después de eso, la sección .data
que contiene variables globales globales y variables globales estáticas. Al final, aparece la sección .bss
, que se llena con ceros en el momento de la carga porque el tamaño del archivo no lo cubre.
Por cierto, puede ver en qué sección de salida se colocará un símbolo en particular utilizando la opción -M
vinculador. Para gcc, usa -Wl,-M
para pasar la opción al enlazador. El ejemplo anterior muestra que a
se asigna dentro de .bss
. Puede ayudarte a verificar que tus objetos no inicializados realmente terminen en .bss
y no en otro lugar:
.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 variables globales no inicializadas en una sección COMÚN de forma predeterminada, para la compatibilidad con compiladores antiguos, que permiten tener variables globales definidas dos veces en un programa sin múltiples errores de definición. Utilice -fno-common
para hacer que GCC use las secciones .bss para archivos de objetos (no hace una diferencia para el ejecutable enlazado final, porque como verá, va a entrar en la sección de salida .bss de todos modos. Esto es controlado por el enlazador guión . ld -verbose
con ld -verbose
). Pero eso no debería asustarte, es solo un detalle interno. Ver la página de manual de gcc.
La sección .bss
en un archivo ELF se utiliza para datos estáticos que no se inicializan mediante programación, pero se garantiza que se establecerán en cero en el tiempo de ejecución. Aquí hay un pequeño ejemplo que explicará la diferencia.
int main() {
static int bss_test1[100];
static int bss_test2[100] = {0};
return 0;
}
En este caso, bss_test1
se coloca en .bss
ya que no está inicializado. bss_test2
embargo, bss_test2
se coloca en el segmento .data
junto con un montón de ceros. El cargador de tiempo de ejecución básicamente asigna la cantidad de espacio reservado para .bss
y lo pone a cero antes de que el código de usuario empiece a ejecutarse.
Puede ver la diferencia usando objdump
, nm
o utilidades similares:
moozletoots$ objdump -t a.out | grep bss_test
08049780 l O .bss 00000190 bss_test1.3
080494c0 l O .data 00000190 bss_test2.4
Esta suele ser una de las primeras sorpresas con las que se topan los desarrolladores incrustados ... nunca inicialice la estática a cero explícitamente. El cargador de tiempo de ejecución (por lo general) se ocupa de eso. Tan pronto como inicialice algo explícitamente, le está diciendo al compilador / vinculador que incluya los datos en la imagen ejecutable.
Una sección .bss
no se almacena en un archivo ejecutable. De las secciones más comunes ( .text
, .data
, .bss
), solo .text
(código real) y .data
(datos inicializados) están presentes en un archivo ELF.