Как сделать исполняемый файл ELF в Linux с помощью шестнадцатеричного редактора?
Просто любопытно. Это, очевидно, не очень хорошее решение для реального программирования, но, скажем, я хотел сделать исполняемый файл в Bless (шестнадцатеричный редактор).
Моя архитектура x86. Какую очень простую программу я могу сделать? Привет, мир? Бесконечный цикл? Похоже на этот вопрос, но в Linux.
2 ответа
Как упоминалось в моем комментарии, вы, по сути, будете писать свой собственный elf-заголовок для исполняемого файла, исключая ненужные разделы. Есть еще несколько обязательных разделов. Документация в Muppetlabs-TinyPrograms делает хорошую работу, объясняя этот процесс. Для развлечения вот пара примеров:
Эквивалент /bin/true (45 байт):
00000000 7F 45 4C 46 01 00 00 00 00 00 00 00 00 00 49 25 |.ELF..........I%|
00000010 02 00 03 00 1A 00 49 25 1A 00 49 25 04 00 00 00 |......I%..I%....|
00000020 5B 5F F2 AE 40 22 5F FB CD 80 20 00 01 |[_..@"_... ..|
0000002d
Ваш классический 'Hello World!' (160 байт):
00000000 7f 45 4c 46 01 01 01 03 00 00 00 00 00 00 00 00 |.ELF............|
00000010 02 00 03 00 01 00 00 00 74 80 04 08 34 00 00 00 |........t...4...|
00000020 00 00 00 00 00 00 00 00 34 00 20 00 02 00 28 00 |........4. ...(.|
00000030 00 00 00 00 01 00 00 00 74 00 00 00 74 80 04 08 |........t...t...|
00000040 74 80 04 08 1f 00 00 00 1f 00 00 00 05 00 00 00 |t...............|
00000050 00 10 00 00 01 00 00 00 93 00 00 00 93 90 04 08 |................|
00000060 93 90 04 08 0d 00 00 00 0d 00 00 00 06 00 00 00 |................|
00000070 00 10 00 00 b8 04 00 00 00 bb 01 00 00 00 b9 93 |................|
00000080 90 04 08 ba 0d 00 00 00 cd 80 b8 01 00 00 00 31 |...............1|
00000090 db cd 80 48 65 6c 6c 6f 20 77 6f 72 6c 64 21 0a |...Hello world!.|
000000a0
Не забудьте сделать их исполняемыми...
Декомпилировать привет мир NASM и понять каждый байт в нем
Версия этого ответа с хорошим оглавлением и большим содержанием: http://www.cirosantilli.com/elf-hello-world (превышение лимита 30k здесь)
стандарты
ELF определяется LSB:
- базовый общий: http://refspecs.linuxfoundation.org/LSB_4.1.0/LSB-Core-generic/LSB-Core-generic/elf-generic.html
- ядро AMD64: http://refspecs.linuxfoundation.org/LSB_4.1.0/LSB-Core-AMD64/LSB-Core-AMD64/book1.html
LSB в основном ссылается на другие стандарты с небольшими расширениями, в частности:
общий (оба от ШОС):
- System V ABI 4.1 (1997) http://www.sco.com/developers/devspecs/gabi41.pdf, без 64-битной версии, хотя для него зарезервировано магическое число. То же самое для основных файлов.
- Обновление системы V ABI ПРОЕКТ 17 (2003) http://www.sco.com/developers/gabi/2003-12-17/contents.html, добавляет 64-разрядную версию. Обновляет только главы 4 и 5 предыдущего документа: остальные остаются в силе и по-прежнему ссылаются.
специфичная для архитектуры:
- IA-32: http://refspecs.linuxfoundation.org/LSB_4.1.0/LSB-Core-IA32/LSB-Core-IA32/elf-ia32.html, в основном указывает на http://www.sco.com/developers/devspecs/abi386-4.pdf
- AMD64: http://refspecs.linuxfoundation.org/LSB_4.1.0/LSB-Core-AMD64/LSB-Core-AMD64/elf-amd64.html, в основном указывает на http://www.x86-64.org/documentation/abi.pdf
Удобное резюме можно найти по адресу:
man elf
Его структура может быть изучена в удобочитаемом виде с помощью таких утилит, как readelf
а также objdump
,
Генерация примера
Давайте разберем минимальный работающий пример Linux x86-64:
section .data
hello_world db "Hello world!", 10
hello_world_len equ $ - hello_world
section .text
global _start
_start:
mov rax, 1
mov rdi, 1
mov rsi, hello_world
mov rdx, hello_world_len
syscall
mov rax, 60
mov rdi, 0
syscall
Составлено с:
nasm -w+all -f elf64 -o 'hello_world.o' 'hello_world.asm'
ld -o 'hello_world.out' 'hello_world.o'
Версии:
- NASM 2.10.09
- Binutils версия 2.24 (содержит
ld
) - Убунту 14.04
Мы не используем программу на C, так как это усложнит анализ, это будет уровень 2:-)
шестнадцатеричные представления бинарный
hd hello_world.o
hd hello_world.out
Вывод по адресу: https://gist.github.com/cirosantilli/7b03f6df2d404c0862c6
Глобальная файловая структура
ELF-файл содержит следующие части:
ELF заголовок. Указывает на положение таблицы заголовков разделов и таблицы заголовков программ.
Таблица заголовка раздела (необязательно для исполняемого файла). У каждого есть
e_shnum
заголовки разделов, каждый из которых указывает на положение раздела.N секций, с
N <= e_shnum
(необязательно для исполняемого файла)Таблица заголовков программы (только для исполняемого файла). У каждого есть
e_phnum
заголовки программы, каждый из которых указывает на положение сегмента.N сегментов, с
N <= e_phnum
(необязательно для исполняемого файла)
Порядок этих частей не фиксирован: единственная фиксированная вещь - это заголовок ELF, который должен быть первым в файле: Общие документы говорят:
ELF заголовок
Самый простой способ соблюсти заголовок:
readelf -h hello_world.o
readelf -h hello_world.out
Вывод по адресу: https://gist.github.com/cirosantilli/7b03f6df2d404c0862c6
Байты в объектном файле:
00000000 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 |.ELF............|
00000010 01 00 3e 00 01 00 00 00 00 00 00 00 00 00 00 00 |..>.............|
00000020 00 00 00 00 00 00 00 00 40 00 00 00 00 00 00 00 |........@.......|
00000030 00 00 00 00 40 00 00 00 00 00 40 00 07 00 03 00 |....@.....@.....|
Исполняемые:
00000000 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 |.ELF............|
00000010 02 00 3e 00 01 00 00 00 b0 00 40 00 00 00 00 00 |..>.......@.....|
00000020 40 00 00 00 00 00 00 00 10 01 00 00 00 00 00 00 |@...............|
00000030 00 00 00 00 40 00 38 00 02 00 40 00 06 00 03 00 |....@.8...@.....|
Структура представлена:
typedef struct {
unsigned char e_ident[EI_NIDENT];
Elf64_Half e_type;
Elf64_Half e_machine;
Elf64_Word e_version;
Elf64_Addr e_entry;
Elf64_Off e_phoff;
Elf64_Off e_shoff;
Elf64_Word e_flags;
Elf64_Half e_ehsize;
Elf64_Half e_phentsize;
Elf64_Half e_phnum;
Elf64_Half e_shentsize;
Elf64_Half e_shnum;
Elf64_Half e_shstrndx;
} Elf64_Ehdr;
Ручная разбивка:
0 0:
EI_MAG
знак равно7f 45 4c 46
знак равно0x7f 'E', 'L', 'F'
: Магический номер эльфа0 4:
EI_CLASS
знак равно02
знак равноELFCLASS64
: 64-битный эльф0 5:
EI_DATA
знак равно01
знак равноELFDATA2LSB
: большие порядковые данные0 6:
EI_VERSION
знак равно01
: форматная версия0 7:
EI_OSABI
(только в обновлении 2003) =00
знак равноELFOSABI_NONE
: без расширений.0 8:
EI_PAD
= 8x00
: зарезервированные байты. Должно быть установлено на 0.1 0:
e_type
знак равно01 00
= 1 (большой порядковый номер) =ET_REl
: перемещаемый форматНа исполняемом файле это
02 00
заET_EXEC
,1 2:
e_machine
знак равно3e 00
знак равно62
знак равноEM_X86_64
: Архитектура AMD641 4:
e_version
знак равно01 00 00 00
: должно быть 11 8:
e_entry
= 8x00
: точка входа адреса выполнения или 0, если не применимо, как для объектного файла, так как точки входа нет.На исполняемом файле это
b0 00 40 00 00 00 00 00
, ТОДО: что еще мы можем установить это? Кажется, что ядро ставит IP непосредственно на это значение, оно не жестко закодировано.2 0:
e_phoff
= 8x00
: смещение таблицы заголовков программы, 0, если отсутствует.40 00 00 00
на исполняемом файле, то есть начинается сразу после заголовка ELF.2 8:
e_shoff
знак равно40
7x00
знак равно0x40
: смещение файла таблицы заголовка раздела, 0, если не присутствует.3 0:
e_flags
знак равно00 00 00 00
СДЕЛАТЬ. Арка специфическая.3 4:
e_ehsize
знак равно40 00
: размер этого заголовка эльфа. ТОДО, почему это поле? Как это может варьироваться?3 6:
e_phentsize
знак равно00 00
: размер каждого заголовка программы, 0, если нет.38 00
на исполняемом файле: длина 56 байтов3 8:
e_phnum
знак равно00 00
: количество записей заголовка программы, 0, если не присутствует.02 00
на исполняемый файл: есть 2 записи.3 А:
e_shentsize
а такжеe_shnum
знак равно40 00 07 00
: размер заголовка раздела и количество записей3 E:
e_shstrndx
(Section Header STRing iNDeX
знак равно03 00
: индекс.shstrtab
раздел.
Таблица заголовка раздела
Массив Elf64_Shdr
Структуры.
Каждая запись содержит метаданные о данном разделе.
e_shoff
заголовка ELF дает начальную позицию, 0x40 здесь.
e_shentsize
а также e_shnum
из заголовка ELF говорят, что у нас есть 7 записей, каждая 0x40
длинные байты.
Таким образом, таблица принимает байты от 0x40 до 0x40 + 7 + 0x40 - 1
= 0x1FF.
Некоторые имена разделов зарезервированы для определенных типов разделов: http://www.sco.com/developers/gabi/2003-12-17/ch4.sheader.html, например: .text
требует SHT_PROGBITS
тип и SHF_ALLOC
+ SHF_EXECINSTR
readelf -S hello_world.o
:
There are 7 section headers, starting at offset 0x40:
Section Headers:
[Nr] Name Type Address Offset
Size EntSize Flags Link Info Align
[ 0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[ 1] .data PROGBITS 0000000000000000 00000200
000000000000000d 0000000000000000 WA 0 0 4
[ 2] .text PROGBITS 0000000000000000 00000210
0000000000000027 0000000000000000 AX 0 0 16
[ 3] .shstrtab STRTAB 0000000000000000 00000240
0000000000000032 0000000000000000 0 0 1
[ 4] .symtab SYMTAB 0000000000000000 00000280
00000000000000a8 0000000000000018 5 6 4
[ 5] .strtab STRTAB 0000000000000000 00000330
0000000000000034 0000000000000000 0 0 1
[ 6] .rela.text RELA 0000000000000000 00000370
0000000000000018 0000000000000018 4 2 4
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings), l (large)
I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)
O (extra OS processing required) o (OS specific), p (processor specific)
struct
представлены каждой записи:
typedef struct {
Elf64_Word sh_name;
Elf64_Word sh_type;
Elf64_Xword sh_flags;
Elf64_Addr sh_addr;
Elf64_Off sh_offset;
Elf64_Xword sh_size;
Elf64_Word sh_link;
Elf64_Word sh_info;
Elf64_Xword sh_addralign;
Elf64_Xword sh_entsize;
} Elf64_Shdr;
Разделы
Индекс 0 раздел
Содержится в байтах от 0x40 до 0x7F.
Первый раздел всегда волшебный: http://www.sco.com/developers/gabi/2003-12-17/ch4.sheader.html гласит:
Если количество разделов больше или равно SHN_LORESERVE (0xff00), e_shnum имеет значение SHN_UNDEF (0) и фактическое количество записей в таблице заголовков разделов содержится в поле sh_size заголовка раздела с индексом 0 (в противном случае, Элемент sh_size начальной записи содержит 0).
Есть также другие магические разделы, подробно описанные в Figure 4-7: Special Section Indexes
,
В индексе 0, SHT_NULL
является обязательным. Существуют ли другие способы его использования: для чего нужен раздел SHT_NULL в ELF??
раздел.data
.data
Раздел 1:
00000080 01 00 00 00 01 00 00 00 03 00 00 00 00 00 00 00 |................|
00000090 00 00 00 00 00 00 00 00 00 02 00 00 00 00 00 00 |................|
000000a0 0d 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
000000b0 04 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
80 0:
sh_name
знак равно01 00 00 00
: индекс 1 в.shstrtab
таблица строкВот,
1
говорит, что имя этого раздела начинается с первого символа этого раздела и заканчивается первым NUL-символом, составляя строку.data
,.data
это одно из названий разделов, которое имеет предопределенное значение http://www.sco.com/developers/gabi/2003-12-17/ch4.strtab.htmlЭти разделы содержат инициализированные данные, которые вносят вклад в образ памяти программы.
80 4:
sh_type
знак равно01 00 00 00
:SHT_PROGBITS
: содержание раздела не определяется ELF, только тем, как программа его интерпретирует. Нормальный с.data
раздел.80 8:
sh_flags
знак равно03
7x00
:SHF_ALLOC
а такжеSHF_EXECINSTR
: http://www.sco.com/developers/gabi/2003-12-17/ch4.sheader.html, как требуется от.data
раздел90 0:
sh_addr
= 8x00
: по какому виртуальному адресу раздел будет размещен во время исполнения,0
если не размещен90 8:
sh_offset
знак равно00 02 00 00 00 00 00 00
знак равно0x200
: количество байтов от начала программы до первого байта в этом разделеа0 0:
sh_size
знак равно0d 00 00 00 00 00 00 00
Если мы возьмем 0xD байтов, начиная с
sh_offset
200 мы видим:00000200 48 65 6c 6c 6f 20 77 6f 72 6c 64 21 0a 00 |Hello world!.. |
АГА! Так что наши
"Hello world!"
Строка находится в разделе данных, как мы сказали, чтобы быть на NASM.Как только мы закончим
hd
мы посмотрим на это как:readelf -x .data hello_world.o
какие выводы:
Hex dump of section '.data': 0x00000000 48656c6c 6f20776f 726c6421 0a Hello world!.
NASM устанавливает достойные свойства для этого раздела, потому что он обрабатывает
.data
волшебным образом: http://www.nasm.us/doc/nasmdoc7.htmlТакже обратите внимание, что это был неправильный выбор раздела: хороший компилятор C поместил бы строку в
.rodata
вместо этого, потому что это только для чтения, и это позволит для дальнейшей оптимизации ОС.а0 8:
sh_link
а такжеsh_info
= 8x 0: не применяется к этому типу раздела. http://www.sco.com/developers/gabi/2003-12-17/ch4.sheader.htmlb0 0:
sh_addralign
знак равно04
= TODO: почему это выравнивание необходимо? Это только дляsh_addr
или также для символов внутриsh_addr
?b0 8:
sh_entsize
знак равно00
= раздел не содержит таблицы. Если!= 0, это означает, что раздел содержит таблицу записей фиксированного размера. В этом файле мы видим изreadelf
вывод, что это имеет место для.symtab
а также.rela.text
разделы.
раздел текста
Теперь, когда мы сделали один раздел вручную, давайте закончим и используем readelf -S
из других разделов.
[Nr] Name Type Address Offset
Size EntSize Flags Link Info Align
[ 2] .text PROGBITS 0000000000000000 00000210
0000000000000027 0000000000000000 AX 0 0 16
.text
является исполняемым, но не доступным для записи: если мы пытаемся записать в него ошибки в Linux. Давайте посмотрим, есть ли у нас действительно код там:
objdump -d hello_world.o
дает:
hello_world.o: file format elf64-x86-64
Disassembly of section .text:
0000000000000000 <_start>:
0: b8 01 00 00 00 mov $0x1,%eax
5: bf 01 00 00 00 mov $0x1,%edi
a: 48 be 00 00 00 00 00 movabs $0x0,%rsi
11: 00 00 00
14: ba 0d 00 00 00 mov $0xd,%edx
19: 0f 05 syscall
1b: b8 3c 00 00 00 mov $0x3c,%eax
20: bf 00 00 00 00 mov $0x0,%edi
25: 0f 05 syscall
Если мы grep b8 01 00 00
на hd
мы видим, что это происходит только в 00000210
Это то, что говорится в разделе. И размер 27, что также соответствует. Поэтому мы должны говорить о правильном разделе.
Это выглядит как правильный код: write
с последующим exit
,
Самая интересная часть это линия a
который делает:
movabs $0x0,%rsi
передать адрес строки системному вызову. В настоящее время 0x0
это просто заполнитель. После связывания оно будет изменено и будет содержать:
4000ba: 48 be d8 00 60 00 00 movabs $0x6000d8,%rsi
Эта модификация возможна из-за данных .rela.text
раздел.
SHT_STRTAB
Разделы с sh_type == SHT_STRTAB
называются строковыми таблицами.
Они содержат пустой массив строк.
Такие разделы используются другими разделами, когда должны использоваться имена строк. В разделе об использовании говорится:
- какую таблицу строк они используют
- каков индекс в целевой таблице строк, где начинается строка
Так, например, мы могли бы иметь таблицу строк, содержащую: TODO: нужно ли начинать с \0
?
Data: \0 a b c \0 d e f \0
Index: 0 1 2 3 4 5 6 7 8
И если другой раздел хочет использовать строку d e f
, они должны указывать на индекс 5
этого раздела (письмо d
).
Известные разделы таблицы строк:
.shstrtab
.strtab
.shstrtab
Тип раздела: sh_type == SHT_STRTAB
,
Общее название: таблица строк заголовка раздела.
Название раздела .shstrtab
зарезервировано Стандарт гласит:
Этот раздел содержит названия разделов.
На этот раздел указывает e_shstrnd
поле самого заголовка ELF.
Строковые индексы этого раздела указываются sh_name
поле заголовков разделов, которые обозначают строки.
Этот раздел не имеет SHF_ALLOC
отмечен, поэтому он не будет отображаться в исполняющей программе.
readelf -x .shstrtab hello_world.o
дает:
Hex dump of section '.shstrtab':
0x00000000 002e6461 7461002e 74657874 002e7368 ..data..text..sh
0x00000010 73747274 6162002e 73796d74 6162002e strtab..symtab..
0x00000020 73747274 6162002e 72656c61 2e746578 strtab..rela.tex
0x00000030 7400 t.
Данные в этом разделе имеют фиксированный формат: http://www.sco.com/developers/gabi/2003-12-17/ch4.strtab.html
Если мы посмотрим на названия других разделов, то увидим, что все они содержат числа, например .text
раздел номер 7
,
Затем каждая строка заканчивается, когда найден первый символ NUL, например, символ 12
является \0
сразу после .text\0
,
.symtab
Тип раздела: sh_type == SHT_SYMTAB
,
Общее название: таблица символов.
Сначала отметим, что:
sh_link
знак равно5
sh_info
знак равно6
За SHT_SYMTAB
разделы, эти цифры означают, что:
- строки, которые дают имена символов, находятся в разделе 5,
.strtab
- данные о перемещении находятся в разделе 6,
.rela.text
Хороший инструмент высокого уровня для разборки этого раздела:
nm hello_world.o
который дает:
0000000000000000 T _start
0000000000000000 d hello_world
000000000000000d a hello_world_len
Это, однако, высокоуровневое представление, которое пропускает некоторые типы символов и в котором типы символов. Более подробную разборку можно получить с помощью:
readelf -s hello_world.o
который дает:
Symbol table '.symtab' contains 7 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FILE LOCAL DEFAULT ABS hello_world.asm
2: 0000000000000000 0 SECTION LOCAL DEFAULT 1
3: 0000000000000000 0 SECTION LOCAL DEFAULT 2
4: 0000000000000000 0 NOTYPE LOCAL DEFAULT 1 hello_world
5: 000000000000000d 0 NOTYPE LOCAL DEFAULT ABS hello_world_len
6: 0000000000000000 0 NOTYPE GLOBAL DEFAULT 2 _start
Двоичный формат таблицы задокументирован по адресу http://www.sco.com/developers/gabi/2003-12-17/ch4.symtab.html
Данные:
readelf -x .symtab hello_world.o
Который дает:
Hex dump of section '.symtab':
0x00000000 00000000 00000000 00000000 00000000 ................
0x00000010 00000000 00000000 01000000 0400f1ff ................
0x00000020 00000000 00000000 00000000 00000000 ................
0x00000030 00000000 03000100 00000000 00000000 ................
0x00000040 00000000 00000000 00000000 03000200 ................
0x00000050 00000000 00000000 00000000 00000000 ................
0x00000060 11000000 00000100 00000000 00000000 ................
0x00000070 00000000 00000000 1d000000 0000f1ff ................
0x00000080 0d000000 00000000 00000000 00000000 ................
0x00000090 2d000000 10000200 00000000 00000000 -...............
0x000000a0 00000000 00000000 ........
Записи имеют тип:
typedef struct {
Elf64_Word st_name;
unsigned char st_info;
unsigned char st_other;
Elf64_Half st_shndx;
Elf64_Addr st_value;
Elf64_Xword st_size;
} Elf64_Sym;
Как и в таблице разделов, первая запись магическая и имеет фиксированные бессмысленные значения.
STT_FILEВступление 1 имеет ELF64_R_TYPE == STT_FILE
, ELF64_R_TYPE
продолжается внутри st_info
,
Байт-анализ:
10 8:
st_name
знак равно01000000
= символ 1 в.strtab
, который до следующего\0
маркиhello_world.asm
Этот файл информации может использоваться компоновщиком для выбора сегментов сегмента.
10 12:
st_info
знак равно04
Биты 0-3 =
ELF64_R_TYPE
= Тип =4
знак равноSTT_FILE
: основной целью этой записи является использованиеst_name
указать имя файла, который сгенерировал этот объектный файл.Биты 4-7 =
ELF64_ST_BIND
= Связывание =0
знак равноSTB_LOCAL
, Обязательное значение дляSTT_FILE
,10 13:
st_shndx
= Таблица символов Заголовок раздела Индекс =f1ff
знак равноSHN_ABS
, Требуется дляSTT_FILE
,20 0:
st_value
= 8x00
: требуется для значения дляSTT_FILE
20 8:
st_size
= 8x00
: нет выделенного размера
Теперь из readelf
Мы интерпретируем другие быстро.
Есть две такие записи, одна указывает на .data
а другой к .text
(раздел указатели 1
а также 2
).
Num: Value Size Type Bind Vis Ndx Name
2: 0000000000000000 0 SECTION LOCAL DEFAULT 1
3: 0000000000000000 0 SECTION LOCAL DEFAULT 2
ТОДО, какова их цель?
STT_NOTYPEЗатем идут самые важные символы:
Num: Value Size Type Bind Vis Ndx Name
4: 0000000000000000 0 NOTYPE LOCAL DEFAULT 1 hello_world
5: 000000000000000d 0 NOTYPE LOCAL DEFAULT ABS hello_world_len
6: 0000000000000000 0 NOTYPE GLOBAL DEFAULT 2 _start
hello_world
строка находится в .data
раздел (индекс 1). Его значение равно 0: оно указывает на первый байт этого раздела.
_start
отмечен GLOBAL
видимость, так как мы написали:
global _start
в NASM. Это необходимо, поскольку оно должно рассматриваться как точка входа. В отличие от C, по умолчанию метки NASM являются локальными.
SHN_ABShello_world_len
указывает на особый st_shndx == SHN_ABS == 0xF1FF
,
0xF1FF
выбирается так, чтобы не конфликтовать с другими разделами.
st_value == 0xD == 13
это значение, которое мы сохранили в сборке: длина строки Hello World!
,
Это означает, что перемещение не повлияет на это значение: оно является константой.
Это небольшая оптимизация, которую делает для нас наш ассемблер и которая поддерживает ELF.
Если бы мы использовали адрес hello_world_len
где-нибудь ассемблер не смог бы пометить его как SHN_ABS
и у компоновщика будет дополнительная работа по перемещению позже.
По умолчанию NASM помещает .symtab
на исполняемый файл, а также.
Это используется только для отладки. Без символов мы полностью слепы и должны все перепроектировать.
Вы можете раздеть это с objcopy
и исполняемый файл все равно будет работать. Такие исполняемые файлы называются раздетыми исполняемыми файлами.
.strtab
Содержит строки для таблицы символов.
Этот раздел имеет sh_type == SHT_STRTAB
,
На это указывает sh_link == 5
из .symtab
раздел.
readelf -x .strtab hello_world.o
дает:
Hex dump of section '.strtab':
0x00000000 0068656c 6c6f5f77 6f726c64 2e61736d .hello_world.asm
0x00000010 0068656c 6c6f5f77 6f726c64 0068656c .hello_world.hel
0x00000020 6c6f5f77 6f726c64 5f6c656e 005f7374 lo_world_len._st
0x00000030 61727400 art.
Это подразумевает, что это ограничение уровня ELF, что глобальные переменные не могут содержать символы NUL.
.rela.text
Тип раздела: sh_type == SHT_RELA
,
Общее название: раздел переезда.
.rela.text
содержит данные о перемещении, которые говорят, как адрес должен быть изменен, когда конечный исполняемый файл связан. Это указывает на байты текстовой области, которые должны быть изменены, когда связывание указывает на правильные области памяти.
По сути, он переводит текст объекта, содержащий адрес заполнителя 0x0:
a: 48 be 00 00 00 00 00 movabs $0x0,%rsi
11: 00 00 00
к фактическому исполняемому коду, содержащему финальный 0x6000d8:
4000ba: 48 be d8 00 60 00 00 movabs $0x6000d8,%rsi
4000c1: 00 00 00
Это было указано sh_info
знак равно 6
из .symtab
раздел.
readelf -r hello_world.o
дает:
Relocation section '.rela.text' at offset 0x3b0 contains 1 entries:
Offset Info Type Sym. Value Sym. Name + Addend
00000000000c 000200000001 R_X86_64_64 0000000000000000 .data + 0
Раздел не существует в исполняемом файле.
Фактические байты:
00000370 0c 00 00 00 00 00 00 00 01 00 00 00 02 00 00 00 |................|
00000380 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
struct
представлено это:
typedef struct {
Elf64_Addr r_offset;
Elf64_Xword r_info;
Elf64_Sxword r_addend;
} Elf64_Rela;
Так:
370 0:
r_offset
= 0xC: адрес в.text
чей адрес это перемещение изменит370 8:
r_info
= 0x200000001. Содержит 2 поля:ELF64_R_TYPE
= 0x1: значение зависит от точной архитектуры.ELF64_R_SYM
= 0x2: индекс раздела, на который указывает адрес, поэтому.data
который находится в индексе 2.
AMD64 ABI говорит, что тип
1
называетсяR_X86_64_64
и что он представляет собой операциюS + A
где:S
: значение символа в объектном файле, здесь0
потому что мы указываем на00 00 00 00 00 00 00 00
изmovabs $0x0,%rsi
A
: добавление, присутствующее в полеr_added
Этот адрес добавляется в раздел, в котором выполняется перемещение.
Эта операция перемещения занимает всего 8 байтов.
380 0:
r_addend
= 0
Итак, в нашем примере мы заключаем, что новый адрес будет: S + A
знак равно .data + 0
и, следовательно, первым делом в разделе данных.
Таблица заголовков программ
Только появляется в исполняемом файле.
Содержит информацию о том, как исполняемый файл должен быть помещен в виртуальную память процесса.
Исполняемый файл генерируется из объектных файлов компоновщиком. Основные задания, которые выполняет компоновщик:
определить, какие разделы объектных файлов войдут в какие сегменты исполняемого файла.
В Binutils это сводится к синтаксическому анализу сценария компоновщика и работе со множеством значений по умолчанию.
Вы можете получить скрипт компоновщика, используемый с
ld --verbose
и установите пользовательский с помощьюld -T
,сделать перемещение по текстовым разделам. Это зависит от того, как несколько разделов помещаются в память.
readelf -l hello_world.out
дает:
Elf file type is EXEC (Executable file)
Entry point 0x4000b0
There are 2 program headers, starting at offset 64
Program Headers:
Type Offset VirtAddr PhysAddr
FileSiz MemSiz Flags Align
LOAD 0x0000000000000000 0x0000000000400000 0x0000000000400000
0x00000000000000d7 0x00000000000000d7 R E 200000
LOAD 0x00000000000000d8 0x00000000006000d8 0x00000000006000d8
0x000000000000000d 0x000000000000000d RW 200000
Section to Segment mapping:
Segment Sections...
00 .text
01 .data
На заголовке ELF, e_phoff
, e_phnum
а также e_phentsize
сказал нам, что есть 2 заголовка программы, которые начинаются с 0x40
и являются 0x38
длина байта каждая, поэтому они:
00000040 01 00 00 00 05 00 00 00 00 00 00 00 00 00 00 00 |................|
00000050 00 00 40 00 00 00 00 00 00 00 40 00 00 00 00 00 |..@.......@.....|
00000060 d7 00 00 00 00 00 00 00 d7 00 00 00 00 00 00 00 |................|
00000070 00 00 20 00 00 00 00 00 |.. ..... |
а также:
00000070 01 00 00 00 06 00 00 00 | ........|
00000080 d8 00 00 00 00 00 00 00 d8 00 60 00 00 00 00 00 |..........`.....|
00000090 d8 00 60 00 00 00 00 00 0d 00 00 00 00 00 00 00 |..`.............|
000000a0 0d 00 00 00 00 00 00 00 00 00 20 00 00 00 00 00 |.......... .....|
Структура представлена http://www.sco.com/developers/gabi/2003-12-17/ch5.pheader.html:
typedef struct {
Elf64_Word p_type;
Elf64_Word p_flags;
Elf64_Off p_offset;
Elf64_Addr p_vaddr;
Elf64_Addr p_paddr;
Elf64_Xword p_filesz;
Elf64_Xword p_memsz;
Elf64_Xword p_align;
} Elf64_Phdr;
Разбивка первого:
- 40 0:
p_type
знак равно01 00 00 00
знак равноPT_LOAD
: СДЕЛАТЬ. Я думаю, что это означает, что он будет фактически загружен в память. Другие типы могут не обязательно быть. - 40 4:
p_flags
знак равно05 00 00 00
= выполнить и прочитать разрешения, без записи TODO - 40 8:
p_offset
= 8x00
ТОДО: что это? Выглядит как смещения от начала сегментов. Но это будет означать, что некоторые сегменты переплетаются? С ним можно немного поиграть:gcc -Wl,-Ttext-segment=0x400030 hello_world.c
- 50 0:
p_vaddr
знак равно00 00 40 00 00 00 00 00
: начальный адрес виртуальной памяти для загрузки этого сегмента - 50 8:
p_paddr
знак равно00 00 40 00 00 00 00 00
: начальный физический адрес для загрузки в память. Имеет значение только для систем, в которых программа может установить свой физический адрес. Иначе, как в System V, как в системах, может быть что угодно. NASM, кажется, просто копируетp_vaddrr
- 60 0:
p_filesz
знак равноd7 00 00 00 00 00 00 00
: ТОДО противp_memsz
- 60 8:
p_memsz
знак равноd7 00 00 00 00 00 00 00
: СДЕЛАТЬ - 70 0:
p_align
знак равно00 00 20 00 00 00 00 00
: 0 или 1 означает, что выравнивание не требуется. TODO, что это значит? в противном случае избыточно с другими полями
Второе аналогично.
Тогда:
Section to Segment mapping:
раздел readelf
говорит нам, что:
- 0 это
.text
сегмент. Ага, так вот почему он исполняемый, а не записываемый - 1 является
.data
сегмент.