Занимают ли нулевые инициализированные переменные в разделе .bss место в файле elf? - PullRequest
43 голосов
/ 04 марта 2009

Если я правильно понимаю, раздел .bss в файлах ELF используется для выделения пространства для инициализируемых нулем переменных. Наша цепочка инструментов создает файлы ELF, поэтому мой вопрос: должен ли раздел .bss содержать все эти нули? Это кажется такой ужасной тратой пространства, что когда, скажем, я выделяю глобальный массив из десяти мегабайт, это приводит к десяти мегабайтам нулей в файле ELF. Что я здесь не так вижу?

Ответы [ 4 ]

67 голосов
/ 04 марта 2009

Прошло некоторое время с тех пор, как я работал с ELF. Но я думаю, что все еще помню этот материал. Нет, он физически не содержит этих нулей. Если вы загляните в программный заголовок файла ELF, то увидите, что у каждого заголовка есть два числа: один - это размер файла. И еще один размер, который имеет раздел при выделении в виртуальной памяти (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

Заголовки типа LOAD - это те, которые копируются в виртуальную память, когда файл загружается для выполнения. Другие заголовки содержат другую информацию, например, необходимые общие библиотеки. Как видите, FileSize и MemSiz значительно отличаются для заголовка, который содержит раздел bss (второй LOAD один):

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

Для этого примера кода:

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

Спецификация ELF гласит, что часть сегмента, размер которой больше размера файла, чем размер файла, просто заполняется нулями в виртуальной памяти. Отображение сегмента в раздел второго заголовка LOAD выглядит следующим образом:

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

Так что там есть и другие разделы. Для C ++ конструктор / деструкторы. То же самое для Java. Затем он содержит копию раздела .dynamic и другие вещи, полезные для динамического связывания (я считаю, что это место, где содержатся необходимые общие библиотеки среди прочего). После этого раздел .data, который содержит инициализированные глобальные переменные и локальные статические переменные. В конце появляется раздел .bss, который заполняется нулями во время загрузки, поскольку размер файла не покрывает его.

Кстати, вы можете увидеть, в какой выходной раздел будет помещен конкретный символ, с помощью опции компоновщика -M. Для gcc вы используете -Wl,-M, чтобы передать параметр компоновщику. Приведенный выше пример показывает, что a выделяется в пределах .bss. Это может помочь вам убедиться, что ваши неинициализированные объекты действительно оказываются в .bss, а не в другом месте:

.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 по умолчанию хранит неинициализированные глобальные переменные в разделе COMMON для совместимости со старыми компиляторами, которые позволяют дважды определять глобальные переменные в программе без множественных ошибок определения. Используйте -fno-common, чтобы заставить GCC использовать секции .bss для объектных файлов (не имеет значения для конечного связанного исполняемого файла, потому что, как вы видите, он все равно попадет в секцию вывода .bss. Это контролируется скрипт компоновщика . Показать его с помощью ld -verbose). Но это не должно вас пугать, это просто внутренняя деталь. Смотрите страницу руководства gcc.

21 голосов
/ 04 марта 2009

Раздел .bss в файле ELF используется для статических данных, которые не инициализируются программно, но гарантированно будут установлены на ноль во время выполнения. Вот небольшой пример, который объяснит разницу.

int main() {
    static int bss_test1[100];
    static int bss_test2[100] = {0};
    return 0;
}

В этом случае bss_test1 помещается в .bss, поскольку оно не инициализировано. bss_test2 однако помещается в сегмент .data вместе с кучей нулей. Загрузчик времени выполнения в основном выделяет объем пространства, зарезервированный для .bss, и обнуляет его до того, как начинает выполняться любой код пользователя.

Вы можете увидеть разницу, используя objdump, nm или аналогичные утилиты:

moozletoots$ objdump -t a.out | grep bss_test
08049780 l     O .bss   00000190              bss_test1.3
080494c0 l     O .data  00000190              bss_test2.4

Обычно это один из первых сюрпризов , с которыми сталкиваются разработчики встроенных программ ... никогда не инициализировать статику равной нулю в явном виде. Загрузчик времени выполнения (обычно) позаботится об этом. Как только вы что-то явно инициализируете, вы говорите компилятору / компоновщику включить данные в исполняемый образ.

3 голосов
/ 04 марта 2009

Раздел .bss не сохраняется в исполняемом файле. Из наиболее распространенных разделов (.text, .data, .bss) в файле ELF присутствуют только .text (фактический код) и .data (инициализированные данные).

1 голос
/ 27 марта 2009

Это верно, .bss физически не присутствует в файле, скорее только информация о его размере присутствует для динамического загрузчика, чтобы выделить раздел .bss для прикладной программы. Так как правило большого пальца только LOAD, сегмент TLS получает память для прикладной программы, остальные используются для динамического загрузчика.

О статическом исполняемом файле, разделам bss также дается место в исполняемом файле

Встроенное приложение, в котором нет загрузчика, это часто встречается.

Суман

...