2009-03-04 21 views
41

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í?¿Las variables inicializadas cero de la sección .bss ocupan espacio en el archivo elf?

+0

manera rápida para responder a ella: hacer un mundo hola con un 'int es [1000000]' y otro sin, compilar y ver los tamaños compilados :-) A continuación, para comprender realmente, descompilarlo con binutils, o compilar al código de ensamblaje con '-S'. –

Respuesta

63

Ha pasado algún 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 la sección ha asignado cuando 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 

encabezados de tipo LOAD es el que se copia en la memoria virtual cuando se carga el archivo para su ejecución. Otros encabezados contienen otra información, como las bibliotecas compartidas que se necesitan. Como se ve, la FileSize y MemSiz difieren significativamente para el encabezado que contiene la sección bss (la segunda LOAD uno):

0x00104 (file-size) 0x61bac (mem-size) 

Para este ejemplo de código:

int a[100000]; 
int main() { } 

La especificación ELF dice que la parte de un segmento que el tamaño de la memoria es mayor que el tamaño del archivo se completa con ceros en la memoria virtual. El segmento de sección de asignación de la segunda cabecera LOAD es así:

03  .ctors .dtors .jcr .dynamic .got .got.plt .data .bss 

lo que hay algunas otras secciones de 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 particular utilizando la opción del vinculador -M. Para gcc, usa -Wl,-M para pasar la opción al vinculador. El ejemplo anterior muestra que a está asignado dentro de .bss. Puede ayudar a verificar que los objetos no inicializados realmente terminan 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 globales sin inicializar en una sección común de forma predeterminada, para la compatibilidad con los compiladores de edad, que permiten tener variables globales definidas dos veces en un programa sin errores de definición múltiple. Use -fno-common para hacer que GCC use las secciones .bss para archivos de objeto (no hace una diferencia para el ejecutable enlazado final, porque como ve, va a entrar en una sección de salida .bss de todos modos. Esto está controlado por el script del enlazador .Muestrelo con ld -verbose). Pero eso no debería asustarte, es solo un detalle interno. Ver la página de manual de gcc.

+0

+1. Bien hecho. Agradable y completo – Eddie

+0

Supongo que el tipo de sección NOBITS debe establecerse para permitir esto? –

+0

Wouter. hmm, nunca usé esa bandera. mi archivo de encabezado de máquina para gcc se ve como #define BSS_SECTION_ASM_OP "\ t.section \ t.bss, \" aw \ "" –

2

Una sección de .bss no se almacena en un archivo ejecutable. De las secciones más comunes (.text, .data, .bss), solo .text (código actual) y .data (datos inicializados) están presentes en un archivo ELF.

+1

Eso no es lo que me dice un readelf en un ejecutable arbitrario. Hay una horseload de secciones en el archivo, incluida la sección .bss. –

+0

No depende de ELF sino de su cadena de compilación (idiomas, herramientas, opciones como depuración, ...). También puede tener sus propias secciones personalizadas. – mouviciel

+0

La sección '.bss' se almacena en el archivo ejecutable para al menos ELF. Pero su contenido no está almacenado, por lo que el tamaño de '.bss' en el archivo es una pequeña constante. En los sistemas operativos con protección de memoria, la sección '.bss' necesita almacenarse de alguna manera para que el cargador pueda organizar la memoria grabable en esa ubicación. Por supuesto, sería razonable pensar que todo lo que sobra de '.bss' en algunos formatos es una contribución a un campo de tamaño asignado pero no copiado. – textshell

20

La sección .bss en un archivo ELF se utiliza para datos estáticos que se no inicializado programación, pero es la garantía de ser puesto a cero en 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 el .bss ya que es inicializado. bss_test2 sin embargo, 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 el .bss y lo pone a cero antes de que el código de usuario empiece a ejecutarse.

se puede ver la diferencia mediante 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 

Este suele ser uno de los primeros sorprende que los desarrolladores integrados topan ... Nunca inicializar la estática a cero de forma explícita. 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.

+0

en mi plataforma gcc ponga bss_test2 en la sección .bss. podrías haber mencionado la opción de compilación -fno-zero-initialized-in-bss que controla esto. – tristan

+0

Del manual: "Si el destino admite una sección BSS, GCC coloca por defecto las variables que se inicializan a cero en BSS". – OrangeDog

1

Correcto, .bss no está presente físicamente 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, el segmento TLS obtiene la memoria para el programa de aplicación, el resto se usa para el cargador dinámico.

Acerca archivo ejecutable estático, bss secciones también se da en el espacio execuatble aplicación

Embedded donde no hay cargador de esto es común.

Suman

+0

dices, ¿TLS también están cargados, como PT_LOAD? Veo que PT_TLS está incluido en PT_LOAD – osgx

Cuestiones relacionadas