Если я правильно понимаю, раздел .bss
в файлах ELF используется для выделения пространства для нулевых инициализированных переменных. Наша цепочка инструментов создает файлы ELF, поэтому мой вопрос: действительно ли раздел .bss
должен содержать все эти нули? Кажется, такая ужасная трата пространств, что, например, я выделяю глобальный массив с десятью мегабайтами, это приводит к десяти мегабайтам нулей в ELF файле. Что я вижу здесь неправильно?
Нужна ли строка .bss нулевые инициализированные переменные занимают пространство в файле elf?
Ответ 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
Встроенное приложение, в котором нет загрузчика, это обычное явление.
Суман