Занимают ли переменные, инициализированные нулевым разделом .bss, место в файле elf?
Вопрос
Если я правильно понимаю, то .bss
раздел в файлах ELF используется для выделения места для переменных с нулевой инициализацией.Наша цепочка инструментов создает файлы ELF, отсюда и мой вопрос:делает ли .bss
раздел действительно должен содержать все эти нули?Это кажется такой ужасной тратой пространства, что когда, скажем, я выделяю глобальный десятимегабайтный массив, это приводит к получению десяти мегабайт нулей в файле ELF.Что я здесь не так вижу?
Решение
Прошло некоторое время с тех пор, как я работал с 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 по умолчанию сохраняет неинициализированные глобальные переменные в ОБЩЕМ разделе для совместимости со старыми компиляторами, которые позволяют определять глобальные переменные дважды в программе без множественных ошибок определения.Использование -fno-common
чтобы заставить GCC использовать разделы .bss для объектных файлов (это не имеет значения для конечного связанного исполняемого файла, потому что, как вы видите, он все равно попадет в раздел вывода .bss.Это контролируется скрипт компоновщика.Отобразите это с помощью ld -verbose
).Но это не должно вас пугать, это просто внутренняя деталь.Смотрите справочную страницу gcc.
Другие советы
Тот Самый .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
Обычно это один из первых сюрпризы с которыми сталкиваются разработчики встраиваемых систем...никогда не инициализируйте статику нулем явно.Загрузчик среды выполнения (обычно) заботится об этом.Как только вы что-либо явно инициализируете, вы сообщаете компилятору / компоновщику включить данные в исполняемый образ.
A .bss
раздел не хранится в исполняемом файле.Из наиболее распространенных разделов (.text
, .data
, .bss
), только .text
(фактический код) и .data
(инициализированные данные) присутствуют в файле ELF.
Это верно, .bss физически отсутствует в файле, скорее, присутствует только информация о его размере, чтобы динамический загрузчик выделил раздел .bss для прикладной программы.Как правило, ЗАГРУЖАЕТСЯ только TLS-сегмент, который получает память для прикладной программы, остальные используются для динамического загрузчика.
Что касается статического исполняемого файла, разделам bss также отводится место в исполняемом файле
Встроенное приложение, в котором нет загрузчика, это обычное дело.
Суман