Разбор ELF: Формат объектных файлов под микроскопом
Секции
• В первом посте мы разобрали общую структуру ELF-файла, а сегодня заглянем внутрь объектного файла и посмотрим, из каких секций он состоит.
• Секция (section) — это участок файла с данными одного вида, код, инициализированные переменные, таблица символов и так далее. Все секции перечислены в таблице заголовков секций, где у каждой записаны имя, тип, размер и другие свойства.
• Посмотрим на практике. Возьмём программу с тремя глобальными переменными:
#include <stdio.h>
int counter = 5;
int buffer[1000];
const char msg[] = "hello";
int main(void) {
printf("%s %d\n", msg, counter);
return 0;
}
• Соберём объектный файл (
gcc -c hello.c -o hello.o) и спросим у nm, что в какую секцию попало:0000000000000000 B buffer
0000000000000000 D counter
0000000000000000 T main
0000000000000000 R msg
U printf
• Код функции лежит в
.text (T), переменная с начальным значением в .data (D), константа в .rodata (R), а переменная без начального значения в .bss (B). Только printf нигде не лежит (U): его найдёт компоновщик.• Теперь загадка: массив
buffer занимает 4000 байт, а весь hello.o весит всего около 1,7 КБ. Куда делись данные? Смотрим таблицу секций командой readelf -SW hello.o (оставил две нужные строки и три столбца):Name Type Size
.data PROGBITS 000004
.bss NOBITS 000fa0
• Размер
.bss равен 0xfa0, то есть те самые 4000 байт, но тип у неё NOBITS, содержимого в файле нет. Хранить 4000 нулей незачем, достаточно записать "нужно столько-то памяти", а загрузчик выделит её и заполнит нулями при запуске. У .data тип PROGBITS: её байты лежат прямо в файле, objdump -s -j .data hello.o покажет 05 00 00 00, то есть число 5 из counter.• Проверим экспериментом. Увеличим массив до миллиона элементов, то есть до 4 МБ. Файл останется маленьким, но стоит добавить начальное значение, и он раздуется:
int buffer[1000000]; ≈ 1,7 КБ
int buffer[1000000] = {1}; ≈ 4 МБ
• Во втором случае
nm покажет D вместо B: массив переехал из .bss в .data, и все 4 МБ пришлось хранить в файле.• Секции это взгляд компоновщика на файл, он собирает однотипные секции из разных объектных файлов и склеивает их вместе. А загрузчику секции не нужны, ему важны сегменты. Про них поговорим позже, а в следующем посте разберём таблицу символов. Удачи!
• Поддержать автора монеткой: @v_meshke
