В результате компиляции и компоновки ассемблером GAS создается файл формата ELF (Executable and Linkable Format), который содержит всю информацию, необходимую операционной системе для загрузки и запуска программы. Условно файл ELF состоит из трех частей:
Заголовок файла ELF
Заголовки программы
Заголовки разделов
Инструменты Arm GNU Toolchain предоставляют специальную утилиту для исследования ELF-файла - readelf. Мы ее можем найти в той же папке, где и располгааются исполняемые файлы компилятора gcc для arm64:
Например, в пакете Arm GNU Toolchain aarch64-none-elf эта утилита называется aarch64-none-elf-readelf
. Передавая этой утилите различные параметры, можно получить представление
различных частей файла в формате ELF. Для получения полной справки по данной утиилите ей надо передать опцию -H. Отмечу лишь некоторые основные опции, которые
мы можем использовать для исследования файла в формате ELF:
-a или --all: вывод всей информации, аналогично применению опций -h -l -S -s -r -d -V -A -I
-h или --file-header : вывод заголовка файла
-l или --program-headers: вывод заголовков программы
-S или --section-headers (либо --sections ): вывод заголовков разделов
-g или --section-groups: вывод групп разделов
-t или --section-details: вывод информации о разделах
-e или --headers: вывод всех заголовков, эквивалентно набору опций -h -l -S
-s или --syms: вывод таблицы символов
Рассмотрим на примере простейшей программы, которая выводит приветственное соощение на консоль. Допустим, у нас есть файл hello.s со следующим кодом:
.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" // данные для вывода
Скомпилируем из этого файла объектный файл:
aarch64-none-elf-as hello.s -o hello.o
Затем из скомпилированного объектного файла hello.o создадим бинарный файл в формате ELF:
aarch64-none-elf-ld hello.o -o hello.so
Итак, мы получили бинарный исполныемый файл hello.so в формате ELF. Теперь проанализируем его.
Для получения заголовка выполним команду
aarch64-none-elf-readelf -h hello.so
В результате мы должны получить вывод наподобие следующего:
c:\arm>aarch64-none-elf-readelf -h hello.so ELF Header: Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 Class: ELF64 Data: 2's complement, little endian Version: 1 (current) OS/ABI: UNIX - System V ABI Version: 0 Type: EXEC (Executable file) Machine: AArch64 Version: 0x1 Entry point address: 0x400000 Start of program headers: 64 (bytes into file) Start of section headers: 66152 (bytes into file) Flags: 0x0 Size of this header: 64 (bytes) Size of program headers: 56 (bytes) Number of program headers: 2 Size of section headers: 64 (bytes) Number of section headers: 6 Section header string table index: 5 c:\arm>
Это и есть заголовок программы. Разберем его.
Вначале идет поле Magic. Оно всегда имеет размер 16 байт и призван указать, что файл является корректным файлом ELF. Это поле
всегда начинается с байта 0x7f
, за которым следуют 3 байта, соответствующие символам "ELF" из таблицы ASCII .
Поле Class указывает на разрядность. Значение ELF64
говорит, что файл использует 64-разрядный формат. В 32-разрядных программах это поле имело бы значение
ELF32
.
Поле Data сообщает операционной системе и загрузчику, в каком порядке должны считываться поля файла ELF - big-endian (обратный порядок) или little-endian (прямой порядок). Файлы ELF на Arm обычно используют кодировку little-endian для самого формата файла ELF.
Поле Version указывает на версию файла.
Поле Type указывает на тип файла
Поле Machine сообщает загрузчику, для какого типа процессоров предназначена программа. Наша 64-битная программа устанавливает в этом поле значение AArch64,
указывая, что файл ELF будет работать только на 64-битных процессорах Arm. В случае с 32-разрядной программой это поле имело бы значение ARM
, что означало бы,
что она работает только на 32-разрядных процессорах Arm.
Поле Entry point address сообщает загрузчику, где находится точка входа программы. Когда программа была подготовлена в памяти операционной
системой или загрузчиком и готова начать выполнение, это поле указывает, откуда начинать выполнение программы. В данном случае это адрес 0x400000
- стандартный адрес для ARM64.
Поле Flags указывает дополнительную информацию, которая может понадобиться загрузчику. Это поле зависит от архитектуры. В 64-битной программе,
например, не определены флаги, зависящие от архитектуры, и это поле всегда будет содержать нулевое значение 0x0
. Для 32-битной программы это поле может принимать ряд значений,
которые информируют, что программа ожидает аппаратную поддержку операций с плавающей запятой:
EF_ARM_ABIMASK (0xff000000)
: старшие 8 бит значения содержат версию ABI, которая применяется файлом ELF. В настоящее время этот старший байт
должен содержать значение 5 (т. е. 0x05000000), что означает, что файл ELF использует версию EABI 5.
EF_ARM_BE8 (0x00800000)
: файл ELF содержит код BE-8 для выполнения на процессоре Arm v6. Этот флаг должен быть установлен только для исполняемого файла.
EF_ARM_ABI_FLOAT_HARD (0x00000400)
: указывает, что файл соответствует стандарту вызова аппаратных процедур с плавающей точкой и что процессор будет Armv7 или
выше и будет поддерживать аппаратное расширение VFP3-D16 с плавающей точкой.
EF_ARM_ABI_FLOAT_SOFT (0x00000200)
: указывает, что файл соответствует стандарту программного вызова процедур с плавающей точкой. Операции с плавающей точкой
обрабатываются через вызовы библиотечных функций.
Поле Size of this header описывает размер заголовка файла.
Остальные поля описывают расположение и количество заголовков программы и разделов в файле:
Start of program headers: начало заголовков программы
Start of section headers: начало заголовков разделов
Size of program headers: размер заголовков программы
Number of program headers: количество заголовков программы
Size of section headers: размер заголовков разделов
Number of section headers: количество заголовков разделов
Загрузчик использует эти поля для подготовки ELF-файла в памяти к выполнению.