Подтвердить что ты не робот

Нужна ли строка .bss нулевые инициализированные переменные занимают пространство в файле elf?

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

4b9b3361

Ответ 1

Прошло некоторое время с тех пор, как я работал с 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

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

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

Ответ 2

Раздел .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

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

Ответ 4

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

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

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

Суман