Заголовки разделов (section headers) обеспечивают разбивку файла ELF на логические единицы. Заголовок файла содержит количество и расположение таблицы заголовков разделов в файле ELF. Для получения заголовков разделов передадим утилите readelf опцию -S или -SW
Например, возьмем простейшую программу:
.global _start // устанавливаем стартовый адрес программы _start: mov X0, #1 // 1 = StdOut - поток вывода ldr X1, =hello // строка для вывода на экран mov X2, #19 // длина строки mov X8, #64 // устанавливаем функцию Linux svc 0 // вызываем функцию Linux для вывода строки mov X0, #0 // Устанавливаем 0 как код возврата mov X8, #93 // код 93 представляет завершение программы svc 0 // вызываем функцию Linux для выхода из программы .data hello: .ascii "Hello METANIT.COM!\n" // данные для вывода
Пусть она скомпилирована в файл hello.so. Выведем ее заголовки разделов:
c:\arm>aarch64-none-elf-readelf hello.so -SW There are 6 section headers, starting at offset 0x10268: Section Headers: [Nr] Name Type Address Off Size ES Flg Lk Inf Al [ 0] NULL 0000000000000000 000000 000000 00 0 0 0 [ 1] .text PROGBITS 0000000000400000 010000 000028 00 AX 0 0 8 [ 2] .data PROGBITS 0000000000410028 010028 000013 00 WA 0 0 1 [ 3] .symtab SYMTAB 0000000000000000 010040 000198 18 4 7 8 [ 4] .strtab STRTAB 0000000000000000 0101d8 000063 00 0 0 1 [ 5] .shstrtab STRTAB 0000000000000000 01023b 000027 00 0 0 1 Key to Flags: W (write), A (alloc), X (execute), M (merge), S (strings), I (info), L (link order), O (extra OS processing required), G (group), T (TLS), C (compressed), x (unknown), o (OS specific), E (exclude), D (mbind), p (processor specific) c:\arm>
Другой способ вывода заголовков разделов представляет утилита objdump с передачей ей опции -h:
c:\arm>aarch64-none-elf-objdump hello.so -h hello.so: file format elf64-littleaarch64 Sections: Idx Name Size VMA LMA File off Algn 0 .text 00000028 0000000000400000 0000000000400000 00010000 2**3 CONTENTS, ALLOC, LOAD, READONLY, CODE 1 .data 00000013 0000000000410028 0000000000410028 00010028 2**0 CONTENTS, ALLOC, LOAD, DATA c:\arm>
Заголовок каждого раздела содержит адрес раздела и размер, который он занимает. У каждого заголовка раздела также есть имя, тип и ряд полей вспомогательных флагов,
которые описывают, как следует интерпретировать заголовок раздела. Например, раздел .text
помечен как код, доступный только для чтения, а раздел
.data
помечен как данные и доступен для чтения/записи.
Для просмотра конкретного раздела утилите objdump передаются параметры -s -j
. Например, просмотрим секцию .data
:
c:\arm>aarch64-none-elf-objdump hello.so -s -j .data hello.so: file format elf64-littleaarch64 Contents of section .data: 410028 48656c6c 6f204d45 54414e49 542e434f Hello METANIT.CO 410038 4d210a M!. c:\arm>
Рассмотрим вкратце основные разделы.
По соглашению все инструкции машинного кода, сгенерированные компилятором, помещаюься в секцию .text
бинарного файла программы. Раздел .text
помечен как доступный для чтения и выполнения, но не для записи. Это означает, что если программа попытается случайно изменить свой программный код,
программа вызовет ошибку сегментации.
Глобальные переменные или статические локальные переменные функций должны иметь уникальный адрес,
который является статическим и не меняется на протяжении всего времени существования программы. По умолчанию этим глобальным переменным будут назначены адреса в разделе
.data
и присвоены начальные значения.
Раздел .data
обычно доступен для чтения/записи. И программа может свободно читать и перезаписывать глобальные переменные в разделе .data во время выполнения программы.
Для глобальных переменных, которые либо не инициализированы, либо инициализированы нулем, в файле ELF предусмотрен раздел Block Starting Symbol
или .bss
. Этот раздел работает так же, как и раздел .data, за исключением того, что переменные внутри него автоматически обнуляются перед запуском программы.
Раздел .rodata
(read-only data - данные только для чтения) используется для хранения глобальных данных в программе, которые не должны изменяться во время
выполнения программы. В этом разделе хранятся глобальные переменные, помеченные как const, то есть константы, а также обычно хранятся константные литералы C-строки,
используемые в данной программе.
Два раздела файла ELF являются метаразделами, которые используются для поиска в других таблицах разделов. Это таблица строк (string table), которая определяет строки, используемые файлом ELF, и таблица символов (symbol table), которая определяет символы, на которые ссылаются в других разделах ELF.
Таблица строк определяет все строки, необходимые для формата файла ELF, но обычно не содержит строковых литералов, используемых программой. Таблица строк представляет объединение всех строк, используемых файлом ELF, каждая из которых заканчивается нулевым байтом.
Таблица строк используется различными структурами в файле ELF, которые имеют строковое поле. Эти структуры определяют значение строки с помощью смещения в таблице строк. Таблица разделов является одной из таких структур. Каждому разделу дается имя, например .text, .data или .strtab. Заголовок файла ELF предоставляет прямой указатель на таблицу строк. Это позволяет загрузчику отслеживать таблицу строк перед тем, как анализировать другие разделы ELF.
Таблица символов или .symtab
определяет символы, используемые или определяемые двоичным кодом программы. Символ представляет собой именованное место в программе или внешне определенный символ. Символы, определенные в программе, указываются в основной таблице символов файла ELF.
И функции, и глобальные объекты данных могут иметь имена символов, связанные с ними, но символы также могут быть назначены локальным переменным потока,
внутренним объектам среды выполнения, таким как глобальная таблица смещений, и даже меткам внутри данной функции. Для просмотра таблицы символов утилите readelf
передается флаг -s:
c:\arm>aarch64-none-elf-readelf hello.so -s Symbol table '.symtab' contains 17 entries: Num: Value Size Type Bind Vis Ndx Name 0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND 1: 0000000000400000 0 SECTION LOCAL DEFAULT 1 .text 2: 0000000000410028 0 SECTION LOCAL DEFAULT 2 .data 3: 0000000000000000 0 FILE LOCAL DEFAULT ABS hello.o 4: 0000000000400000 0 NOTYPE LOCAL DEFAULT 1 $x 5: 0000000000410028 0 NOTYPE LOCAL DEFAULT 2 hello 6: 0000000000400020 0 NOTYPE LOCAL DEFAULT 1 $d 7: 000000000041003b 0 NOTYPE GLOBAL DEFAULT 2 _bss_end__ 8: 000000000041003b 0 NOTYPE GLOBAL DEFAULT 2 __bss_start__ 9: 000000000041003b 0 NOTYPE GLOBAL DEFAULT 2 __bss_end__ 10: 0000000000400000 0 NOTYPE GLOBAL DEFAULT 1 _start 11: 000000000041003b 0 NOTYPE GLOBAL DEFAULT 2 __bss_start 12: 0000000000410040 0 NOTYPE GLOBAL DEFAULT 2 __end__ 13: 000000000041003b 0 NOTYPE GLOBAL DEFAULT 2 _edata 14: 0000000000410040 0 NOTYPE GLOBAL DEFAULT 2 _end 15: 0000000000080000 0 NOTYPE GLOBAL DEFAULT 2 _stack 16: 0000000000410028 0 NOTYPE GLOBAL DEFAULT 2 __data_start c:\arm>
Каждая запись символа в таблице символов определяет следующие атрибуты:
Num: номер символа.
Name: имя символа.
Value: значение символа, которое обычно является его адресом в памяти.
Size: размер символа. Для объектов данных это обычно размер объекта данных в байтах, а для функций — длина функции в байтах.
Type: тип символа, который обычно является одним из следующих значений:
STT_NOTYPE
: тип символа не указан
STT_OBJECT
: cимвол соответствует глобальной переменной
STT_FUNC
: cимвол соответствует функции
STT_SECTION
: cимвол соответствует разделу
STT_FILE
: cимвол соответствует файлу исходного кода. Эти символы иногда используются при отладке программы.
STT_TLS
: cимвол соответствует локальной переменной данных потока, определенной в заголовке TLS
STT_GNU_IFUNC
: cимвол представляет собой специфичную для GNU косвенную функцию
Bind: атрибуты привязки символа. Они определяют, должен ли символ быть видимым для других программ в процессе связывания/компоновки. Символ может быть локальным
(значение STB_LOCAL
), глобальным (STB_GLOBAL
) или ни тем, ни другим. Локальные символы не видны программам за пределами текущего
ELF-файла. Загрузчик игнорирует эти символы в процессе динамической компоновки. Глобальные символы, напротив, могут использоваться вне программы
или разделяемой библиотеки.
Символы также можно могут быть слабыми (weak). Слабые символы полезны для создания стандартных реализаций функций,
которые могут быть переопределены другими библиотеками. Программы на C и C++, скомпилированные с использованием GCC, могут помечать функции и данные как слабые с помощью
атрибута __attribute__((weak))
или с помощью директивы #pragma weak
. Например, функция malloc
и ряд других функций выделения памяти часто
определяются с использованием слабых символов. Это позволяет программам переопределить подобные реализации по умолчанию
без перехвата функций.
Ndx: номер раздела, в котором находится символ. Значение ABS
указывает, что символ не привязан к определенному разделу
В колонке имени символов можно встретить специальные символы - $x
и $d
. Это символы сопоставления (mapping symbols) — специальные символы, которые
используются, чтобы помочь отладчикам и дизассемблерам определить, как следует интерпретировать байты в разделе .text
. Эти символы носят только информативный характер
и не влияют на то, как процессор интерпретирует данные в разделе. Могут применяться следующие символы сопоставления:
$a
: последовательность, следующая за этим символом, представляет собой инструкции, закодированные в
набор инструкций A32.
$t
: последовательность, следующая за этим символом, представляет собой инструкции, закодированные в
набор инструкций T32 (Thumb).
$x
: последовательность, следующая за этим символом, представляет собой инструкции, закодированные в
набор инструкций A64.
$d
: последовательность, следующая за этим символом, представляет собой константные данные