Просто любопытно. Это, очевидно, не очень хорошее решение для реального программирования, но я хочу сделать исполняемый файл в Bless (шестнадцатеричный редактор).
Моя архитектура - x86. Какую очень простую программу я могу сделать? Привет, мир? Бесконечный цикл? Подобно этому вопросу, но в Linux.
Ответ 2
Декомпилировать мир приветствия NASM и понять каждый байт в нем
Версия этого ответа с хорошим TOC и большим количеством контента: http://www.cirosantilli.com/elf-hello-world (нажмите здесь ограничение 30k char)
Стандарты
ELF задается LSB:
LSB в основном ссылается на другие стандарты с незначительными расширениями, в частности:
Удобное резюме можно найти по адресу:
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
)
- Ubuntu 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 |[email protected]|
00000030 00 00 00 00 40 00 00 00 00 00 40 00 07 00 03 00 |[email protected]@.....|
Executable:
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 |..>[email protected]|
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 |[email protected]@.....|
Представленная структура:
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'
: магическое число ELF
-
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
= 8x 00
: зарезервированные байты. Должно быть установлено в 0.
-
1 0: e_type
= 01 00
= 1 (big endian) = ET_REl
: перемещаемый формат
В исполняемом файле 02 00
для ET_EXEC
.
-
1 2: e_machine
= 3e 00
= 62
= EM_X86_64
: архитектура AMD64
-
1 4: e_version
= 01 00 00 00
: должно быть 1
-
1 8: e_entry
= 8x 00
: точка ввода адреса выполнения, или 0, если не применимо, как для объектного файла, так как нет точки входа.
В исполняемом файле это b0 00 40 00 00 00 00 00
. TODO: что еще мы можем установить? Ядро, кажется, помещает IP непосредственно в это значение, оно не является жестко запрограммированным.
-
2 0: e_phoff
= 8x 00
: смещение таблицы заголовка программы, 0, если нет.
40 00 00 00
в исполняемом файле, то есть он начинается сразу после заголовка ELF.
-
2 8: e_shoff
= 40
7x 00
= 0x40
: смещение файла таблицы заголовка раздела, 0, если нет.
-
3 0: e_flags
= 00 00 00 00
TODO. Специально для Arch.
-
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 A: 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#special_sections, например. .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
есть другие магические разделы.
SHT_NULL
В индексе 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, только тем, как программа интерпретирует его. Нормально, так как a .data
.
-
80 8: sh_flags
= 03
7x 00
: SHF_ALLOC
и SHF_EXECINSTR
: http://www.sco.com/developers/gabi/2003-12-17/ch4.sheader.html#sh_flags, как требуется из раздела .data
-
90 0: sh_addr
= 8x 00
: в каком виртуальном адресе раздел будет помещен во время выполнения, 0
если не помещен
-
90 8: sh_offset
= 00 02 00 00 00 00 00 00
= 0x200
: количество байт от начала программы до первого байта в этом разделе
-
a0 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!.. |
AHA! Итак, наша строка "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#section-7.9.2
Также обратите внимание, что это был неправильный выбор раздела: хороший компилятор C поместил бы строку в .rodata
вместо этого, потому что он доступен только для чтения, и это позволит продолжить оптимизацию ОС.
-
a0 8: sh_link
и sh_info
= 8x 0: не применяются к типу этого раздела. http://www.sco.com/developers/gabi/2003-12-17/ch4.sheader.html#special_sections
-
b0 0: sh_addralign
= 04
= TODO: зачем это выравнивание необходимо? Это только для sh_addr
, а также для символов внутри sh_addr
?
-
b0 8: sh_entsize
= 00
= раздел не содержит таблицы. Если!= 0, это означает, что раздел содержит таблицу записей фиксированного размера. В этом файле мы видим из вывода readelf
, что это имеет место для разделов .symtab
и .rela.text
.
.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 segfaults. Посмотрим, действительно ли у нас есть код:
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, что также соответствует. Поэтому мы должны говорить о правильном разделе.
Это выглядит как правильный код: a 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
Тип раздела: 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
.
Общее имя: таблица символов.
Сначала отметим, что:
В разделе 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
Bits 0-3 = ELF64_R_TYPE
= Type = 4
= STT_FILE
: основная цель этой записи - использовать st_name
для указания имени файла, сгенерированного этим объектным файлом.
Биты 4-7 = ELF64_ST_BIND
= Binding = 0
= STB_LOCAL
. Требуемое значение для STT_FILE
.
-
10 13: st_shndx
= Таблица символов Таблица заголовков Индекс = f1ff
= SHN_ABS
. Требуется для STT_FILE
.
-
20 0: st_value
= 8x 00
: требуется для значения для STT_FILE
-
20 8: st_size
= 8x 00
: нет выделенного размера
Теперь из readelf
мы быстро интерпретируем остальные.
STT_SECTION
Есть два таких элемента, один указывает на .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
TODO, какова их цель?
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_ABS
hello_world_len
указывает на специальный st_shndx == SHN_ABS == 0xF1FF
.
0xF1FF
выбирается так, чтобы не противоречить другим разделам.
st_value == 0xD == 13
, который является значением, которое мы сохранили там на сборке: длина строки Hello World!
.
Это означает, что перемещение не повлияет на это значение: оно является константой.
Это небольшая оптимизация, которую делает наш ассемблер для нас и имеет поддержку ELF.
Если бы мы использовали адрес hello_world_len
в любом месте, ассемблер не смог бы пометить его как SHN_ABS
, и позже у компоновщика будет дополнительное перемещение.
SHT_SYMTAB в исполняемом файле
По умолчанию 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 это сводится к анализу компоновщика script и работе с множеством значений по умолчанию.
Вы можете получить компоновщик script, используемый с 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 |[email protected]@.....|
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
: TODO. Я думаю, это означает, что он будет загружен в память. Другие типы могут не обязательно быть.
- 40 4:
p_flags
= 05 00 00 00
= выполнять и читать разрешения, не писать TODO
- 40 8:
p_offset
= 8x 00
TODO: что это? Похоже на смещения от начала сегментов. Но это будет означать, что некоторые сегменты переплетаются? С ним можно немного поиграть: 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
: TODO vs p_memsz
- 60 8:
p_memsz
= d7 00 00 00 00 00 00 00
: TODO
- 70 0:
p_align
= 00 00 20 00 00 00 00 00
: 0 или 1 означает, что никакого выравнивания не требуется TODO, что это значит? в противном случае избыточно с другими полями
Вторая аналогична.
Тогда:
Section to Segment mapping:
раздела readelf
говорит нам, что:
- 0 - сегмент
.text
. Ага, поэтому он является исполняемым и не доступен для записи.
- 1 - сегмент
.data
.