Нулевые инициализированные сегменты/разделы в файлах ELF

#operating-system #elf

Вопрос:

Мне интересно узнать, каковы соглашения для нулевых инициализированных сегментов/разделов в файлах ELF.

Обычно пишется, что раздел .bss файла ELF должен быть инициализирован нулем перед использованием.

Мне интересно, указано ли в соглашении/спецификации, должно ли указанное обнуление выполняться загрузчиком ELF или это должно выполняться самими инструкциями ELF.

В последнем случае, я полагаю, это будут некоторые инструкции/функции между точкой входа ELF и основной функцией программы…

Спасибо

Ответ №1:

Мне интересно, указывает ли соглашение/спецификация, должно ли указанное обнуление выполняться загрузчиком ELF

Да, косвенно.

или если это должно быть сделано самими инструкциями ЭЛЬФОВ.

Не существует такого понятия, как «инструкции ЭЛЬФА». Формат ELF (для исполняемой или общей библиотеки) указывает загрузчику, как загрузить и запустить объект ELF. Но весь разбор и настройка, которые происходят в загрузчике.


Так как же на самом деле происходит обнуление?

 int foo[40960];
int main() { return 0; }

gcc t.c -no-pie

readelf -Wl a.out

 
 Elf file type is EXEC (Executable file)
Entry point 0x401020
There are 11 program headers, starting at offset 64

Program Headers:
  Type           Offset   VirtAddr           PhysAddr           FileSiz  MemSiz   Flg Align
  PHDR           0x000040 0x0000000000400040 0x0000000000400040 0x000268 0x000268 R   0x8
  INTERP         0x0002a8 0x00000000004002a8 0x00000000004002a8 0x00001c 0x00001c R   0x1
      [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
  LOAD           0x000000 0x0000000000400000 0x0000000000400000 0x000400 0x000400 R   0x1000
  LOAD           0x001000 0x0000000000401000 0x0000000000401000 0x00017d 0x00017d R E 0x1000
  LOAD           0x002000 0x0000000000402000 0x0000000000402000 0x000110 0x000110 R   0x1000
  LOAD           0x002e50 0x0000000000403e50 0x0000000000403e50 0x0001d8 0x028210 RW  0x1000
  DYNAMIC        0x002e60 0x0000000000403e60 0x0000000000403e60 0x000190 0x000190 RW  0x8
  NOTE           0x0002c4 0x00000000004002c4 0x00000000004002c4 0x000044 0x000044 R   0x4
  GNU_EH_FRAME   0x002004 0x0000000000402004 0x0000000000402004 0x000034 0x000034 R   0x4
  GNU_STACK      0x000000 0x0000000000000000 0x0000000000000000 0x000000 0x000000 RW  0x10
  GNU_RELRO      0x002e50 0x0000000000403e50 0x0000000000403e50 0x0001b0 0x0001b0 R   0x1

 Section to Segment mapping:
  Segment Sections...
   00
   01     .interp
   02     .interp .note.gnu.build-id .note.ABI-tag .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn
   03     .init .text .fini
   04     .rodata .eh_frame_hdr .eh_frame
   05     .init_array .fini_array .dynamic .got .got.plt .data .bss
   06     .dynamic
   07     .note.gnu.build-id .note.ABI-tag
   08     .eh_frame_hdr
   09
   10     .init_array .fini_array .dynamic .got
 

Здесь вы можете видеть, что

  1. .bss находится в 5-м сегменте.
  2. Этот LOAD сегмент имеет крошечный .p_filesz == 0x1d8 , но довольно большой .p_memsz == 0x28210 размер .

Загрузчик выполняется mmap(...) со смещением файла 0x2e50 , округленным до размера страницы (т. е. 0x2000 ) и размера 0x028210 0xe50 == 0x29060 , который больше файла ( 0x3d20 в моем случае файл занимает только байты).

Это mmap системный вызов, который предоставляет страницы с нулевым заполнением для любого отображения, которое продолжается после конца файла, поэтому фактические инструкции по вводу нуля являются частью ядра, а не загрузчика.

P.S. Вы можете убедиться в этом, посмотрев на readelf вывод в вашей системе и сравнив его с выводом из strace ./a.out .