Створюємо ELF-файл з налагоджувальною інформацією (DWARF) вручну (для мікроконтролерів ARM). Формати ELF та PE EXE Elf розширення файлу

Головна / Додатковий функціонал

Версія цієї відповіді з хорошим TOC та великою кількістю контенту: http://www.cirosantilli.com/elf-hello-world (натисніть тут обмеження 30k char)

Стандарти

ELF задається LSB:

  • core generic: http://refspecs.linuxfoundation.org/LSB_4.1.0/LSB-Core-generic/LSB-Core-generic/elf-generic.html
  • core AMD64: http://refspecs.linuxfoundation.org/LSB_4.1.0/LSB-Core-AMD64/LSB-Core-AMD64/book1.html

LSB переважно посилається на інші стандарти з незначними розширеннями, зокрема:

    generic (обидва SCO):

    • System V ABI 4.1 (1997) http://www.sco.com/developers/devspecs/gabi41.pdf, не 64 біт, хоча для нього зарезервовано магічне число. Те саме для основних файлів.
    • System V ABI Update DRAFT 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

Зручне резюме можна знайти за адресою:

Його структура може бути розглянута за допомогою зручних для користувача способів, таких як readelf та objdump .

Створити приклад

Дозвольте зламати мінімальний приклад, що виконується Linux x86-64:

Section .data hello_world db "Hello world!", 10 hello_world_len equ $ - hello_world section .text

Скомпільовано за допомогою

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

Глобальна файлова структура

Файл 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

Байт в об'єктному файлі:

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]@.....|

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; 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: немає розширень.

    08: 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 = 00000000 TODO. Спеціально для Arch.

    3 4: e_ehsize = 4000: розмір цього заголовка ельфа. Чому це поле? Як це може змінитись?

    3 6: e_phentsize = 00 00: Розмір кожного заголовка програми, 0, якщо ні.

    38 00 у виконуваному файлі: довжина файлу складає 56 байтів

    3 8: e_phnum = 0000: кількість записів заголовка програми, 0, якщо ні.

    02 00 у виконуваному файлі: є 2 записи.

    3 A: e_shentsize та e_shnum = 40 00 07 00: розмір заголовка розділу та кількість записів

Таблиця заголовків розділів

Масив структур 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: 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 00000000000000000 00000370 0000000000000018 0000000000000018 4 (2) 4 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; 64;

Розділи

Розділ індексу 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 |................|

    Тут 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 , як потрібно з розділу.

    90 0: sh_addr = 8x 00: в якій віртуальній адресі розділ буде розміщений під час виконання, якщо не розміщений

    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 інших розділів.

Name Type Address Offset Size EntSize Flags Link Info Align [ 2 ] .text PROGBITS 0000000000000000 00000210 000000000000020 000

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 0 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: 0

Якщо ми маємо 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
  • .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: 00000000000

Бінарний формат таблиці документується на 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 01000 ... . 0x00000020 00000000 00000000 00000000 00000000 ................ 0x00000030 00000000 03000100 00000000 00000000 ................ 0x00000040 00000000 00000000 00000000 03000200 .. .............. 0x00000050 00000000 00000000 00000000 00000000 ................ 0x00000060 11000000 00000100 00000000 000 ...... 0x00000070 00000000 00000000 1d000000 0000f1ff ................ 0x00000080 0d000000 00000000 00000000 00000000 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;

Як і таблиці розділів, перший запис чарівна і задана фіксованими безглуздими значеннями.

Запис 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).

Номер: 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 00 0

hello_world знаходиться у розділі.data (індекс 1). Це значення дорівнює 0: він свідчить про перший байт цього розділу.

Start відзначений видимістю GLOBAL, тому що ми написали:

Global _start

у NASM. Це необхідно, оскільки воно має розглядатися як точка входу. На відміну від C, за промовчанням мітки NASM є локальними.

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 розділу.

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 ru 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 розділу.

readelf -r hello_world.o дає:

Relocation section ".rela.text" при offset 0x3b0 містить 1 пункти: 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 до 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 |.......... .....| 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_X

Пробій першого:

  • 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.
Стандартні засоби розробки компілюють вашу програму на файл ELF (Executable and Linkable Format) з можливістю включення налагоджувальної інформації. Специфікацію формату можна прочитати. З іншого боку, кожної архітектури є свої особливості, наприклад особливості ARM . Розглянемо коротко цей формат.
Виконуваний файл формату ELF складається з таких частин:
1. Заголовок (ELF Header)
Містить загальну інформацію про файл та його основні характеристики.
2. Заголовок програми (Program Header Table)
Це таблиця відповідності секцій файлу сегментам пам'яті, вказує завантажувачу, яку область пам'яті писати кожну секцію.
3. Секції
Секції містять всю інформацію у файлі (програма, дані, налагоджувальна інформація тощо)
Кожна секція має тип, ім'я та інші параметри. У секції ".text" зазвичай зберігається код, в ".symtab" - таблиця символів програми (імена файлів, процедур та змінних), в ".strtab" - таблиця рядків, у секціях з префіксом ".debug_" - налагоджувальна інформація і т.д. .д. Крім того, у файлі повинна бути порожня секція з індексом 0.
4. Заголовок секцій (Section Header Table)
Це таблиця, що містить масив заголовків секцій.
Докладніше формат розглядається у розділі Створення ELF.

Огляд DWARF

DWARF – це стандартизований формат налагоджувальної інформації. Стандарт можна завантажити на офіційному сайті. Там лежить чудовий короткий огляд формату: Introduction to the DWARF Debugging Format (Michael J. Eager).
Навіщо потрібна налагоджувальна інформація? Вона дозволяє:
  • встановлювати точки зупинки (breakpoints) не так на фізичну адресу, але в номер рядка у файлі вихідного коду чи ім'я функції
  • відображати та змінювати значення глобальних та локальних змінних, а також параметрів функції
  • відображати стек викликів (backtrace)
  • виконувати програму покроково не за однією інструкцією асемблера, а за рядками вихідного коду
Ця інформація зберігається у вигляді деревоподібної структури. Кожен вузол дерева має батька, може мати нащадків і називається DIE (Debugging Information Entry). Кожен вузол має свій тег (тип) та список атрибутів (властивостей), що описують вузол. Атрибути можуть містити все, що завгодно, наприклад дані або посилання на інші вузли. Крім того, існує інформація, що зберігається поза деревом.
Вузли поділяються на два основні типи: вузли, що описують дані, та вузли, що описують код.
Вузли, що описують дані:
  1. Типи даних:
    • Базові типи даних (вузол з типом DW_TAG_base_type), наприклад такі як тип int C.
    • Складові типи даних (покажчики тощо)
    • Масиви
    • Структури, класи, об'єднання, інтерфейси
  2. Об'єкти даних:
    • константи
    • параметри функцій
    • змінні
    • і т.д.
Кожен об'єкт даних має атрибут DW_AT_location, який вказує, як обчислюється адресу, за якою знаходиться дані. Наприклад, змінна може мати фіксовану адресу, перебувати в регістрі або на стеку, бути членом класу або об'єкта. Ця адреса може обчислюватися досить складно, тому стандарт передбачає так звані Location Expressions, які можуть містити послідовність операторів спеціальної внутрішньої стікової машини.
Вузли, що описують код:
  1. Процедури (функції) – вузли з тегом DW_TAG_subprogram. Вузли-нащадки можуть містити описи змінних – параметрів функції та локальних змінних функції.
  2. Одиниця компіляції (Compilation Unit). Містить інформацію програмі та є батьком решти вузлів.
Інформація, описана вище, знаходиться в секціях ".debug_info" та ".debug_abbrev".
Інша інформація:
  • Інформація про номери рядків (секція ".debug_line")
  • Інформація про макроси (секція ".debug_macinfo")
  • Інформація про формат кадру (Call Frame Information) (секція ".debug_frame")

Створення ELF

Створювати файли у форматі EFL ми будемо за допомогою бібліотеки libelf із пакету elfutils. У мережі є хороша стаття з використання libelf - LibELF by Example (на жаль, створення файлів в ній описано дуже коротко) а також документація.
Створення файлу складається з кількох етапів:
  1. Ініціалізація libelf
  2. Створення заголовка файлу (ELF Header)
  3. Створення заголовка програми (Program Header Table)
  4. Створення секцій
  5. Запис файлу
Розглянемо етапи докладніше
Ініціалізація libelf
Спочатку вам потрібно буде викликати функцію elf_version(EV_CURRENT) та перевірити результат. Якщо він дорівнює EV_NONE – виникла помилка та подальші дії робити не можна. Потім потрібно створити потрібний файл на диску, отримати його дескриптор і передати його в функцію elf_begin:
Elf * elf_begin (int fd, Elf_Cmd cmd, Elf * elf)
  • fd - дескриптор щойно відкритого файлу
  • cmd - режим (ELF_C_READ для читання інформації, ELF_C_WRITE для запису або ELF_C_RDWR для читання/запису), він повинен відповідати режиму відкритого файлу (ELF_C_WRITE у нашому випадку)
  • elf - потрібен лише для роботи з файлами архівів (.a), у нашому випадку потрібно передати 0
Функція повертає покажчик на створений дескриптор, який використовуватиметься у всіх функціях libelf, 0 повертається у разі помилки.
Створення заголовка
Новий заголовок файлу створюється функцією elf32_newehdr:
Elf32_Ehdr * elf32_newehdr (Elf * elf);
  • elf – дескриптор, повернутий функцією elf_begin
Повертає 0 при помилці або покажчик на структуру - заголовок ELF-файлу:
#define EI_NIDENT 16 typedef struct ( unsigned char e_ident; Elf32_Half e_type; Elf32_Half e_machine; Elf32_Word e_version; Elf32_Addr e_entry; Elf32_Off e_phoff; Elf32_Off e_shoff; Elf32_Word e_flags; Elf32_Half e_ehsize; Elf32_Half e_phentsize; Elf32_Half e_phnum; Elf32_Half e_shentsize; Elf32_Half e_shnum; Elf32_Half e_shstrndx; ) Elf32_Ehdr;

Деякі поля її заповнені стандартним чином, деякі потрібно заповнити нам:

  • e_ident - байтовий масив ідентифікації, що має такі індекси:
    • EI_MAG0, EI_MAG1, EI_MAG2, EI_MAG3 - ці 4 байти повинні містити символи 0x7f, "ELF", що за нас вже зробила функція elf32_newehdr
    • EI_DATA - вказує на тип кодування даних у файлі: ELFDATA2LSB чи ELFDATA2MSB. Потрібно встановити ELFDATA2LSB так: e_ident = ELFDATA2LSB
    • EI_VERSION - версія заголовка файлу, що вже встановлена ​​за нас
    • EI_PAD - не чіпаємо
  • e_type - тип файлу, може бути ET_NONE - без типу, ET_REL - файл, що переміщається, ET_EXEC - виконуваний файл, ET_DYN - розділений об'єктний файл і т.д. Нам потрібно встановити тип файлу в ET_EXEC
  • e_machine - архітектура, потрібна для цього файлу, наприклад EM_386 - для архітектури Intel, для ARM нам потрібно записати сюди EM_ARM (40) - див. ELF for the ARM Architecture
  • e_version - версія файлу, потрібно обов'язково встановити у EV_CURRENT
  • e_entry – адреса точки входу, для нас не обов'язково
  • e_phoff - усунення у файлі заголовка програми, e_shoff - усунення заголовка секцій, не заповнюємо
  • e_flags – специфічні для процесора прапори, для нашої архітектури (Cortex-M3) потрібно встановити рівним 0x05000000 (ABI version 5)
  • e_ehsize, e_phentsize, e_phnum, e_shentsize, e_shnum - не чіпаємо
  • e_shstrndx - містить номер секції, де знаходиться таблиця рядків із заголовками секцій. Оскільки жодних секцій у нас ще немає, цей номер ми встановимо пізніше
Створення заголовка програми
Як мовилося раніше, заголовок програми (Program Header Table) - це таблиця відповідності секцій файлу сегментам пам'яті, яка вказує завантажувачу, куди писати кожну секцію. Заговок створюється за допомогою функції elf32_newphdr:
Elf32_Phdr * elf32_newphdr (Elf * elf, size_t count);
  • elf – наш дескриптор
  • count – кількість створюваних елементів таблиці. Так як у нас буде тільки одна секція (з програмним кодом), то count дорівнюватиме 1.
Повертає 0 при помилці або вказівник на заголовок програми.
Кожна елемент у таблиці заголовка описується такою структурою:
typedef struct ( Elf32_Word p_type; Elf32_Off p_offset; Elf32_Addr p_vaddr; Elf32_Addr p_paddr; Elf32_Word p_filesz; Elf32_Word p_memsz; Elf32_Word p_flags; Elf32_W;
  • p_type - тип сегмента (секції), тут ми повинні вказати PT_LOAD - сегмент, що завантажується
  • p_offset - зміщень у файлі, звідки починається дані секції, яка завантажуватиметься на згадку. У нас це секція.text, яка буде знаходитися відразу після заголовка файлу та заголовка програми, усунення ми можемо обчислити як суму довжин цих заголовків. Довжину будь-якого типу можна отримати за допомогою функції elf32_fsize:
    size_t elf32_fsize(Elf_Type type, size_t count, unsigned int version); type - тут константа ELF_T_ххх, нам потрібні будуть розміри ELF_T_EHDR та ELF_T_PHDR; count – кількість елементів потрібного типу, version – потрібно встановити в EV_CURRENT
  • p_vaddr, p_paddr - віртуальна та фізична адреса, за якою буде завантажено вміст секції. Так як у нас віртуальних адрес немає, встановлюємо його рівним фізичному, в найпростішому випадку - 0, тому що саме сюди завантажуватиметься наша програма.
  • p_filesz, p_memsz - розмір секції у файлі та пам'яті. У нас вони однакові, але оскільки секції з програмним кодом ще немає, встановимо їх пізніше
  • p_flags – дозволи для завантаженого сегмента пам'яті. Можуть бути PF_R - читання, PF_W - запис, PF_X - виконання чи його комбінацією. Встановимо p_flags рівним PF_R + PF_X
  • p_align - вирівнювання сегмента, у нас 4
Створення секцій
Після створення заголовків можна розпочинати створення секцій. Порожня секція створюється за допомогою функції elf_newscn:
Elf_Scn * elf_newscn (Elf * elf);
  • elf – дескриптор, повернутий раніше функцією elf_begin
Функція повертає вказівник на секцію або 0 за помилки.
Після створення секції потрібно заповнити заголовок секції та створити описник даних секції.
Вказівник на заголовок секції ми можемо отримати за допомогою функції elf32_getshdr:
Elf32_Shdr * elf32_getshdr (Elf_Scn * scn);
  • scn – покажчик на секцію, який ми отримали з функції elf_newscn.
Заголовок секції виглядає так:
typedef struct ( Elf32_Word sh_name; Elf32_Word sh_type; Elf32_Word sh_flags; Elf32_Addr sh_addr; Elf32_Off sh_offset; Elf32_Word sh_size; Elf32_Word sh_link; Elf3__d_sh;
  • sh_name - ім'я секції - усунення у рядковій таблиці заголовків секцій (секція.shstrtab) - див. «Таблиці рядків» далі
  • sh_type - тип вмісту секції, для секції з кодом програми необхідно встановити SHT_PROGBITS, для секцій з таблицею рядків - SHT_STRTAB, для таблиці символів - SHT_SYMTAB
  • sh_flags - прапори секції, які можна комбінувати, і з яких нам потрібні лише три:
    • SHF_ALLOC - означає, що секція завантажуватиметься на згадку
    • SHF_EXECINSTR - секція містить виконуваний код
    • SHF_STRINGS - секція містить таблицю рядків
    Відповідно, для секції.text із програмою потрібно встановити прапори SHF_ALLOC + SHF_EXECINSTR
  • sh_addr - адреса, за якою секція буде завантажена на згадку
  • sh_offset - усунення секції у файлі - не чіпаємо, бібліотека встановить за нас
  • sh_size - розмір секції - не чіпаємо
  • sh_link - містить номер зв'язаної секції, потрібна для зв'язку секції з відповідною таблицею рядків (див. далі)
  • sh_info - додаткова інформація, яка залежить від типу секції, встановлюємо в 0
  • sh_addralign - вирівнювання адреси, не чіпаємо
  • sh_entsize - якщо секція складається з кількох елементів однакової довжини, вказує на довжину такого елемента, не чіпаємо
Після заповнення заголовка необхідно створити описник даних секції функцією elf_newdata:
Elf_Data * elf_newdata (Elf_Scn * scn);
  • scn - щойно отриманий покажчик на нову секцію.
Функція повертає 0 за помилки, або покажчик на структуру Elf_Data, яку потрібно буде заповнити:
typedef struct ( void * d_buf; Elf_Type d_type; size_t d_size; off_t d_off; size_t d_align; unsigned d_version; ) Elf_Data;
  • d_buf - покажчик на дані, які потрібно записати до секції
  • d_type - тип даних, нам скрізь підійде ELF_T_BYTE
  • d_size – розмір даних
  • d_off - усунення в секції, встановити в 0
  • d_align - вирівнювання, можна встановити в 1 - без вирівнювання
  • d_version - версія, обов'язково встановити у EV_CURRENT
Спеціальні секції
Для наших цілей нам потрібно буде створити мінімально необхідний набір секцій:
  • .text - секція з кодом програми
  • .symtab - таблиця символів файлу
  • .strtab - таблиця рядків, що містить імена символів із секції.symtab, оскільки в останній зберігаються не самі імена, а їх індекси
  • .shstrtab - таблиця рядків, що містить імена секцій
Усі секції створюються так, як описано в попередньому розділі, але кожна спеціальна секція має свої особливості.
Секція.
Ця секція містить код, що виконується, тому потрібно sh_type встановити в SHT_PROGBITS, sh_flags - в SHF_EXECINSTR + SHF_ALLOC, sh_addr - встановити рівним адресою, за якою буде завантажений цей код
Секція.
Секція містить опис усіх символів (функцій) програми та файлів, у яких вони були описані. Вона складається з таких елементів завдовжки по 16 байт:
typedef struct ( Elf32_Word st_name; Elf32_Addr st_value; Elf32_Word st_size; unsigned char st_info; unsigned char st_other; Elf32_Half st_shndx; ) Elf32_Sym;
  • st_name - ім'я символу (індекс у таблиці рядків. strtab)
  • st_value – значення (адреса входу для функції або 0 для файлу). Оскільки Cortex-M3 має систему команд Thumb-2, ця адреса обов'язково має бути непарною (реальна адреса + 1)
  • st_size – довжина коду функції (0 для файлу)
  • st_info - тип символу та його область видимості. Для визначення значення цього поля існує макрос
    #define ELF32_ST_INFO(b,t) (((b)<<4)+((t)&0xf))
    де b – область видимості, а t – тип символу
    Область видимості може бути STB_LOCAL (символ не видно з інших об'єктних файлів) або STB_GLOBAL (видимий). Для спрощення використовуємо STB_GLOBAL.
    Тип символу – STT_FUNC для функції, STT_FILE для файлу
  • st_other - встановити в 0
  • st_shndx – індекс секції, для якої визначено символ (індекс секції.text), або SHN_ABS для файлу.
    Індекс секції за її дескриптором scn можна визначити за допомогою elf_ndxscn:
    size_t elf_ndxscn(Elf_Scn *scn);

Ця секція створюється звичайним чином, тільки sh_type потрібно встановити в SHT_SYMTAB, а індекс секції.strtab записати в поле sh_link, таким чином, ці секції стануть пов'язаними.
Секція.
Ця секція містить імена всіх символів із секції.symtab. Створюється як звичайна секція, але sh_type потрібно встановити SHT_STRTAB, sh_flags - в SHF_STRINGS, таким чином ця секція стає таблицею рядків.
Дані для секції можна збирати при проході по вихідному тексту масив, покажчик на який потім записати в описувач даних секції (d_buf).
Секція.
Секція - таблиця рядків, що містить заголовки всіх секцій файлу, у тому числі і свій заголовок. Створюється так, як і секція.strtab. Після створення її індексу потрібно записати в поле e_shstrndx заголовка файлу.
Таблиці рядків
Таблиці рядків містять рядки, що йдуть підряд, закінчуються нульовим байтом, перший байт у цій таблиці повинен бути також 0. Індекс рядка в таблиці - це просто зміщення в байтах від початку таблиці, таким чином, перший рядок "name" має індекс 1, наступний рядок " var" має індекс 6.
Індекс 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 \0 n a m e \0 v a r \0
Запис файлу
Отже, заголовки та секції вже сформовані, тепер їх потрібно записати у файл та завершити роботу з libelf. Запис виконує функцію elf_update:
off_t elf_update(Elf * elf, Elf_Cmd cmd);
  • elf - дескриптор
  • cmd - команда, що дорівнює ELF_C_WRITE для запису.
Функція повертає -1 за помилки. Текст помилки можна отримати, викликавши функцію elf_errmsg(-1), яка поверне покажчик на рядок з помилкою.
Закінчуємо роботу з бібліотекою функцією elf_end, якою передаємо наш дескриптор. Залишилося лише закрити раніше відкритий файл.
Однак створений файл не містить налагоджувальної інформації, яку ми додамо в наступному розділі.

Створення DWARF

Створюватимемо налагоджувальну інформацію за допомогою бібліотеки, в комплекті з якою йде pdf-файл з документацією (libdwarf2p.1.pdf - A Producer Library Interface to DWARF).
Створення налагоджувальної інформації складається з таких етапів:
  1. Створення вузлів (DIE - Debugging Information Entry)
  2. Створення атрибутів вузла
  3. Створення типів даних
  4. Створення процедур (функцій)
Розглянемо етапи докладніше
Ініціалізація libdwarf producer
Ми будемо створювати налагоджувальну інформацію під час компіляції одночасно зі створенням символів у секції.
Для ініціалізації будемо використовувати функцію dwarf_producer_init_c. У бібліотеці є ще кілька функцій ініціалізації (dwarf_producer_init, dwarf_producer_init_b), які відрізняються деякими аспектами, описаними в документації. В принципі, можна використати будь-яку з них.

Dwarf_P_Debug dwarf_producer_init_c(Dwarf_Unsigned flags, Dwarf_Callback_Func_c func, Dwarf_Handler errhand, Dwarf_Ptr errarg, void *user_data, Dwarf_Error *error)

  • flags - комбінація по «або» кількох констант які визначають деякі параметри, наприклад розрядність інформації, слідування байтів (little-endian, big-endian), формат релокацій, з яких нам обов'язково потрібні DW_DLC_WRITE та DW_DLC_SYMBOLIC_RELOCATIONS
  • func - callback-функція, яка буде викликатися при створенні ELF-секцій з налагоджувальною інформацією. Докладніше див. у розділі «Створення секцій з налагоджувальною інформацією»
  • errhand - покажчик на функцію, яка буде викликатись при виникненні помилок. Можна передати 0
  • errarg - дані, які передаватимуться в функцію errhand, можна ставити 0
  • user_data - дані, які будуть передані до функції func, можна ставити 0
  • error - код помилки, що повертається
Функція повертає Dwarf_P_Debug - дескриптор, який використовується у всіх наступних функціях, або -1 у разі помилки, при цьому в error буде код помилки (отримати текст повідомлення про помилку за його кодом можна за допомогою функції dwarf_errmsg, передавши їй цей код)
Створення Вузлів (DIE - Debugging Information Entry)
Як було описано вище, налагоджувальна інформація утворює деревоподібну структуру. Для того, щоб створити вузол цього дерева, потрібно:
  • створити його функцією dwarf_new_die
  • додати до нього атрибути (кожен тип атрибутів додається своєю функцією, які будуть описані далі)
Вузол створюється за допомогою функції dwarf_new_die:
Dwarf_P_Die dwarf_new_die(Dwarf_P_Debug dbg, Dwarf_Tag new_tag, Dwarf_P_Die parent, Dwarf_P_Die child, Dwarf_P_Die left_sibling, Dwarf_P_Die right_sibling, Dwarf
  • new_tag – тег (тип) вузла – константа DW_TAG_xxxx, які можна знайти у файлі libdwarf.h
  • parent, child, left_sibling, right_sibling - відповідно батько, нащадок, лівий та правий сусіди вузла. Необов'язково вказувати всі ці параметри, достатньо вказати один, замість інших поставити 0. Якщо всі параметри дорівнюють 0, вузол буде або кореневим, або ізольованим
  • error - міститиме код помилки при її виникненні
Функція повертає DW_DLV_BADADDR за помилки або дескриптор вузла Dwarf_P_Die у разі успіху
Створення атрибутів вузла
Для створення атрибутів вузла є ціла родина функцій dwarf_add_AT_хххх. Іноді проблематично визначити, якою функцією потрібно створювати необхідний атрибут, тому я навіть кілька разів копався у вихідному коді бібліотеки. Деякі з функцій буде описано тут, деякі нижче - у відповідних розділах. Всі вони приймають параметр ownerdie - дескриптор вузла, до якого буде додано атрибут, та повертають код помилки у параметрі error.
Функція dwarf_add_AT_name додає до сайту атрибут «ім'я» (DW_AT_name). У більшості вузлів має бути ім'я (наприклад у процедур, змінних, констант), у деяких імені може і не бути (наприклад, у Compilation Unit)
Dwarf_P_Attribute dwarf_add_AT_name(Dwarf_P_Die ownerdie, char *name, Dwarf_Error *error)
  • name – власне значення атрибуту (ім'я вузла)

Функції dwarf_add_AT_signed_const, dwarf_add_AT_unsigned_const додають до вузла вказаний атрибут та його знакове (беззнакове) значення. Знакові та беззнакові атрибути використовуються для завдання значень констант, розмірів, номерів рядків тощо. Формат функцій:
Dwarf_P_Attribute dwarf_add_AT_(un)signed_const(Dwarf_P_Debug dbg, Dwarf_P_Die ownerdie, Dwarf_Half attr, Dwarf_Signed value, Dwarf_Error *error)
  • dbg – дескриптор Dwarf_P_Debug, отриманий при ініціалізації бібліотеки
  • attr - атрибут, значення якого задається - константа DW_AT_xxxx, які можна знайти у файлі libdwarf.h
  • value - значення атрибуту
Повертають DW_DLV_BADADDR у разі помилки або дескриптор атрибута при успішному завершенні.
Створення Одиниці Компіляції (Compilation Unit)
У будь-якому дереві має бути корінь - у нас це одиниця компіляції, яка містить інформацію про програму (наприклад, ім'я головного файлу, мову програмування, що використовується, назва компілятора, чутливість символів (змінних, функцій) до регістру, головну функцію програми, початкову адресу та. і т.д). У принципі, жодні атрибути є обов'язковими. Для прикладу створимо інформацію про головний файл та компілятор.
Інформація про головний файл
Для збереження інформації про головний файл використовується атрибут "ім'я" (DW_AT_name), застосовуйте функцію dwarf_add_AT_name, як показано в розділі "Створення атрибутів вузла".
Інформація про компілятор
Використовуємо функцію dwarf_add_AT_producer:
Dwarf_P_Attribute dwarf_add_AT_name(Dwarf_P_Die ownerdie, char *producer_string, Dwarf_Error *error)
  • producer_string - рядок із текстом інформації
Повертає DW_DLV_BADADDR у разі помилки або дескриптор атрибута при успішному завершенні.
Створення Common Information Entry
Зазвичай при виклику функції (підпрограми) її параметри та адреса повернення поміщається у стек (хоча кожен компілятор може робити це по-своєму), це називається Call Frame. Відладчику потрібна інформація про формат кадру щоб правильно визначити адресу повернення з функції та побудувати backtrace - ланцюжок викликів функцій, який привів нас у поточну функцію, та параметри цих функцій. Також зазвичай вказуються регістри процесора, які зберігаються на стеку. Код, який резервує місце на стеку та зберігає регістри процесора, називається прологом функції, код, що відновлює регістри та стек - епілогом.
Ця інформація залежить від компілятора. Наприклад, пролог та епілог необов'язково повинні бути на самому початку та наприкінці функції; іноді кадр використовується, іноді ні; регістри процесора можуть зберігатися в інших регістрах і т.д.
Отже, відладчику потрібно знати, як змінюють значення регістри процесора і де вони будуть збережені при вході в процедуру. Ця інформація називається Call Frame Information – інформація про формат кадру. Для кожної адреси в програмі (що містить код) вказується адреса кадру в пам'яті (Canonical Frame Address - CFA) та інформація про регістри процесора, наприклад можна вказати, що:
  • регістр не зберігається у процедурі
  • регістр не змінює свого значення у процедурі
  • регістр зберігається на стеку за адресою CFA+n
  • регістр зберігається в іншому регістрі
  • регістр зберігається в пам'яті за деякою адресою, яка може обчислюватися досить неочевидним способом
  • і т.д.
Оскільки інформація повинна вказуватися для кожної адреси коду, вона дуже об'ємна і зберігається в стислому вигляді в секції.debug_frame. Так як від адреси до адреси вона змінюється мало, кодуються тільки її зміни у вигляді інструкцій DW_CFA_хххх. Кожна інструкція вказує на одну зміну, наприклад:
  • DW_CFA_set_loc - вказує на поточну адресу у програмі
  • DW_CFA_advance_loc - просуває покажчик на кілька байт
  • DW_CFA_def_cfa - вказує адресу стекового кадру (числова константа)
  • DW_CFA_def_cfa_register - вказує адресу стекового кадру (береться з регістру процесора)
  • DW_CFA_def_cfa_expression - вказує, як потрібно обчислити адресу стекового кадру
  • DW_CFA_same_value - вказує, що регістр не змінюється
  • DW_CFA_register - вказуйте, що регістр зберігається в іншому регістрі
  • і т.д.
Елементи секції.debug_frame - це записи, які можуть бути двох типів: Common Information Entry (CIE) та Frame Description Entry (FDE). CIE містить інформацію, яка є спільною для багатьох записів FDE, грубо кажучи, вона описує певний тип процедур. FDE описують кожну конкретну процедуру. При вході в процедуру відладчик спочатку виконує інструкції із CIE, а потім із FDE.
Мій компілятор створює процедури, у яких CFA знаходиться у регістрі sp(r13). Створимо CIE для всіх процедур. Для цього є функція dwarf_add_frame_cie:
Dwarf_Unsigned dwarf_add_frame_cie(Dwarf_P_Debug dbg, char *augmenter, Dwarf_Small code_align, Dwarf_Small data_align, Dwarf_Small ret_addr_reg, Dwarf_Ptr init_bytes, Dwarf_Un
  • augmenter - рядок у кодуванні UTF-8, наявність якого показує, що до CIE або FDE є додаткова платформозалежна інформація. Ставимо порожній рядок
  • code_align - вирівнювання коду в байтах (у нас 2)
  • data_align - вирівнювання даних у кадрі (ставимо -4, що означає всі параметри займають по 4 байти на стеку і він росте в пам'яті вниз)
  • ret_addr_reg - регістр, що містить адресу повернення з процедури (у нас 14)
  • init_bytes – масив, що містить інструкції DW_CFA_хххх. На жаль, немає зручного способу згенерувати цей масив. Можна сформувати його вручну або підглянути його в elf-файлі, який був згенерований компілятором, що я і зробив. Для мого випадку він містить 3 байти: 0x0C, 0x0D, 0, що розшифровується як DW_CFA_def_cfa: r13 ofs 0 (CFA знаходиться в регістрі r13, зсув дорівнює 0)
  • init_bytes_len – довжина масиву init_bytes
Функція повертає DW_DLV_NOCOUNT при помилці або дескриптор CIE, який має бути використаний під час створення FDE для кожної процедури, що ми розглянемо далі у розділі «Створення процедури FDE»
Створення типів даних
Перед тим, як створювати процедури та змінні, потрібно спочатку створити вузли, що відповідають типам даних. Типів даних існує безліч, але вони ґрунтуються на базових типах (елементарні типи на кшталт int, double тощо.), інші типи будуються з базових.
Базовий тип - це вузол із тегом DW_TAG_base_type. У нього мають бути атрибути:
  • "ім'я" (DW_AT_name)
  • «кодування» (DW_AT_encoding) - означає, які саме дані описує даний базовий тип (наприклад, DW_ATE_boolean - логічний, DW_ATE_float - з плаваючою точкою, DW_ATE_signed - цілий знаковий, DW_ATE_unsigned - цілий беззнаковий і т.д)
  • «розмір» (DW_AT_byte_size – розмір у байтах або DW_AT_bit_size – розмір у бітах)
Також вузол може містити інші необов'язкові атрибути.
Наприклад, щоб створити 32-бітовий цілий знаковий базовий тип int, нам потрібно буде створити вузол з тегом DW_TAG_base_type і встановити йому атрибути DW_AT_name - int, DW_AT_encoding - DW_ATE_signed, DW_AT_byte_size - 4.
Після створення базових типів можна створювати похідні від них. Такі вузли повинні містити атрибут DW_AT_type - посилання їх базовий тип. Наприклад, покажчик на int - вузол з тегом DW_TAG_pointer_type повинен містити в атрибуті DW_AT_type посилання на раніше створений тип «int».
Атрибут із посиланням на інший вузол створюється функцією dwarf_add_AT_reference:
Dwarf_P_Attribute dwarf_add_AT_reference(Dwarf_P_Debug dbg, Dwarf_P_Die ownerdie, Dwarf_Half attr, Dwarf_P_Die otherdie, Dwarf_Error *error)
  • attr - атрибут, у разі DW_AT_type
  • otherdie - дескриптор вузла типу, на який посилаємось
Створення процедур
Для створення процедур мені необхідно пояснити ще один тип налагоджувальної інформації – інформація про номери рядків (Line Number Information). Вона служить для зіставлення кожної машинної інструкції певному рядку вихідного коду і для можливості построкової налагодження програми. Ця інформація зберігається у секції.debug_line. Якби ми мали достатньо місця, то вона зберігалася б у вигляді матриці, по одному рядку для кожної інструкції з такими колонками:
  • ім'я файлу з вихідним кодом
  • номер рядка у цьому файлі
  • номер колонки у файлі
  • чи є інструкція початком оператора чи блоку операторів
  • і т.д.
Така матриця була б дуже великою, тому її доводиться стискати. По-перше, рядки, що дублюються, видаляються, і по-друге, зберігаються не самі рядки, а тільки зміни в них. Ці зміни виглядають як команди для кінцевого автомата, а сама інформація вже вважається програмою, яка «виконуватиметься» цим автоматом. Команди цієї програми виглядають, наприклад так: DW_LNS_advance_pc - просунути лічильник команд на деяку адресу, DW_LNS_set_file - встановити файл, у якому визначено процедуру, DW_LNS_const_add_pc - просунути лічильник команд кілька байт тощо.
На такому низькому рівні створювати цю інформацію складно, тому в бібліотеці libdwarf передбачено кілька функцій, що полегшують це завдання.
Зберігати ім'я файлу для кожної інструкції накладно, тому замість імені зберігається його індекс у спеціальній таблиці. Для створення індексу файлу потрібно використовувати функцію dwarf_add_file_decl:
Dwarf_Unsigned dwarf_add_file_decl(Dwarf_P_Debug dbg, char *name, Dwarf_Unsigned dir_idx, Dwarf_Unsigned time_mod, Dwarf_Unsigned length, Dwarf_Error *error)
  • name - ім'я файлу
  • dir_idx – індекс папки, в якій знаходиться файл. Індекс можна отримати за допомогою функції dwarf_add_directory_decl. Якщо використовуються повні шляхи, можна ставити 0 як індекс папки і не використовувати dwarf_add_directory_decl зовсім
  • time_mod – час модифікації файлу, можна не вказувати (0)
  • length – розмір файлу, також не обов'язково (0)
Функція поверне індекс файлу або DW_DLV_NOCOUNT у разі помилки.
Для створення інформації про номери рядків є три функції: dwarf_add_line_entry_b, dwarf_lne_set_address, dwarf_lne_end_sequence, які ми розглянемо нижче.
Створення налагоджувальної інформації для процедури відбувається у кілька етапів:
  • створення символу процедури у секції.
  • створення вузла процедури з атрибутами
  • створення FDE процедури
  • створення параметрів процедури
  • створення інформації про номери рядків
Створення символу процедури
Символ процедури створюється, як описано вище в розділі «Секція.symtab». У ній симоли процедур перемежуються із символами файлів у яких перебуває вихідний код цих процедур. Спершу створюємо символ файлу, потім процедури. У цьому файл стає поточним, і якщо така процедура перебуває у поточному файлі, символ файлу знову створювати не потрібно.
Створення вузла процедури з атрибутами
Спочатку створюємо вузол за допомогою функції dwarf_new_die (див. розділ "Створення Вузлів"), вказавши як тег DW_TAG_subprogram, а як батько - Compilation Unit (якщо це глобальна процедура) або відповідний DIE (якщо локальна). Далі створюємо атрибути:
  • ім'я процедури (функція dwarf_add_AT_name, див. "Створення атрибутів вузла")
  • номер рядка у файлі, де починається код процедури (атрибут DW_AT_decl_line), функція dwarf_add_AT_unsigned_const (див. «Створення атрибутів вузла»)
  • початкова адреса процедури (атрибут DW_AT_low_pc), функція dwarf_add_AT_targ_address, див.
  • кінцева адреса процедури (атрибут DW_AT_high_pc), функція dwarf_add_AT_targ_address, див.
  • тип результату, що повертається процедурою (атрибут DW_AT_type - посилання на раніше створений тип, див. «Створення типів даних»). Якщо процедура нічого не повертає – цей атрибут створювати не потрібно
Атрибути DW_AT_low_pc та DW_AT_high_pc потрібно створювати спеціально призначеною для цього функцією dwarf_add_AT_targ_address_b:
Dwarf_P_Attribute dwarf_add_AT_targ_address_b(Dwarf_P_Debug dbg, Dwarf_P_Die ownerdie, Dwarf_Half attr, Dwarf_Unsigned pc_value, Dwarf_Unsigned sym_index, Dwarf_Error *error)
  • attr - атрибут (DW_AT_low_pc або DW_AT_high_pc)
  • pc_value - значення адреси
  • sym_index – індекс символу процедури в таблиці. Необов'язковий, можна передати 0
Функція поверне DW_DLV_BADADDR за помилки.
Створення FDE процедури
Як говорилося вище в розділі "Створення Common Information Entry", для кожної процедури потрібно створити описник кадру, що відбувається в кілька етапів:
  • створення нового FDE (див. Створення Common Information Entry)
  • приєднання створеного FDE до спільного списку
  • додавання інструкцій до створеного FDE
Створити новий FDE можна функцією dwarf_new_fde:
Dwarf_P_Fde dwarf_new_fde(Dwarf_P_Debug dbg, Dwarf_Error *error)
Функція поверне дескриптор нового FDE або DW_DLV_BADADDR за помилки.
Приєднати новий FDE до списку можна за допомогою dwarf_add_frame_fde:
Dwarf_Unsigned dwarf_add_frame_fde(Dwarf_P_Debug dbg, Dwarf_P_Fde fde, Dwarf_P_Die die, Dwarf_Unsigned cie, Dwarf_Addr virt_addr, Dwarf_Unsigned code_len, Dwarf_Unsigned sym
  • fde - щойно отриманий дескриптор
  • die - DIE процедури (див. Створення вузла процедури з атрибутами)
  • cie – дескриптор CIE (див. Створення Common Information Entry)
  • virt_addr - початкова адреса нашої процедури
  • code_len - довжина процедури в байтах
Функція поверне DW_DLV_NOCOUNT у разі помилки.
Після цього можна додавати інструкції DW_CFA_хххх до нашого FDE. Це робиться функціями dwarf_add_fde_inst і dwarf_fde_cfa_offset. Перша додає до списку задану інструкцію:
Dwarf_P_Fde dwarf_add_fde_inst(Dwarf_P_Fde fde, Dwarf_Small op, Dwarf_Unsigned val1, Dwarf_Unsigned val2, Dwarf_Error *error)
  • op - код інструкції (DW_CFA_хххх)
  • val1, val2 - параметри інструкції (різні для кожної інструкції, див. Стандарт, розділ 6.4.2 Call Frame Instructions)
Функція dwarf_fde_cfa_offset додає інструкцію DW_CFA_offset:
Dwarf_P_Fde dwarf_fde_cfa_offset(Dwarf_P_Fde fde, Dwarf_Unsigned reg, Dwarf_Signed offset, Dwarf_Error *error)
  • fde – дескриптор створеного FDE
  • reg - регістр, який записується у кадр
  • offset - його зміщення у кадрі (не в байтах, а в елементах кадру, див. Створення Common Information Entry, data_align)
Наприклад, компілятор створює процедуру, у пролозі якої у стіковий кадр зберігається регістр lr (r14). Насамперед потрібно додати інструкцію DW_CFA_advance_loc з першим параметром, рівним 1, що означає просування регістра pc на 2 байти (див. Створення Common Information Entry, code_align), потім додати DW_CFA_def_cfa_offset з параметром 4 (завдання зміщення даних у ф функцію dwarf_fde_cfa_offset з параметром reg=14 offset=1, що означає запис регістра r14 у кадр зі зміщенням -4 байти від CFA.
Створення параметрів процедури
Створення параметрів процедури аналогічно до створення звичайних змінних, див. «Створення змінних та констант»
Створення інформації про номери рядків
Створення цієї інформації відбувається так:
  • на початку процедури починаємо блок інструкцій функцією dwarf_lne_set_address
  • для кожного рядка коду (або машинної інструкції) створюємо інформацію про вихідний код (dwarf_add_line_entry)
  • наприкінці процедури завершуємо блок інструкцій функцією dwarf_lne_end_sequence
Функція dwarf_lne_set_address задає адресу, за якою починається блок інструкцій:
Dwarf_Unsigned dwarf_lne_set_address(Dwarf_P_Debug dbg, Dwarf_Addr offs, Dwarf_Unsigned symidx, Dwarf_Error *error)
  • offs - адреса процедури (адреса першої машинної інструкції)
  • sym_idx – індекс символу (не обов'язковий, можна вказати 0)

Функція dwarf_add_line_entry_b додає до секції.debug_line інформацію про рядки вихідного коду. Цю функцію я викликаю для кожної машинної інструкції:
Dwarf_Unsigned dwarf_add_line_entry_b(Dwarf_P_Debug dbg, Dwarf_Unsigned file_index, Dwarf_Addr code_offset, Dwarf_Unsigned lineno, Dwarf_Signed column_number, Dwarf_Bool is_source_stmt_begin, Dwarf_Bool is_basic_block_begin, Dwarf_Bool is_epilogue_begin, Dwarf_Bool is_prologue_end, Dwarf_Unsigned isa, Dwarf_Unsigned discriminator, Dwarf_Error *error)
  • file_index - індекс файлу вихідного коду, отриманий раніше функцією dwarf_add_file_decl (див. "Створення процедур")
  • code_offset - адреса поточної машинної інструкції
  • lineno - номер рядка у файлі вихідного коду
  • column_number - номер колонки у файлі вихідного коду
  • is_source_stmt_begin - 1 якщо поточна інструкція перша в коді в рядку lineno (я завжди використовую 1)
  • is_basic_block_begin - 1 якщо поточна інструкція перша в блоці операторів (я завжди використовую 0)
  • is_epilogue_begin - 1 якщо поточна інструкція перша в епілозі процедури (не обов'язково, у мене завжди 0)
  • is_prologue_end - 1 якщо поточна інструкція остання у пролозі процедури (обов'язково!)
  • isa - instruction set architecture (архітектура набору команд). Обов'язково слід вказати DW_ISA_ARM_thumb для ARM Cortex M3!
  • discriminator. Одна позиція (файл, рядок, стовпчик) вихідного коду може відповідати різним машинним інструкціям. У такому разі для наборів таких інструкцій потрібно встановлювати різні дискримінатори. Якщо таких випадків немає, має бути 0
Функція повертає 0 (успіх) або DW_DLV_NOCOUNT (помилка).
І нарешті, функція dwarf_lne_end_sequence завершує процедуру:
Dwarf_Unsigned dwarf_lne_end_sequence(Dwarf_P_Debug dbg, Dwarf_Addr address; Dwarf_Error *error)
  • address - адреса поточної машинної інструкції
Повертає 0 (успіх) або DW_DLV_NOCOUNT (помилка).
На цьому завершуємо створення процедури.
Створення змінних та констант
Загалом змінні досить прості. У них є ім'я, ділянка пам'яті (або регістр процесора), де знаходиться їх дані, а також тип цих даних. Якщо змінна глобальна – її батьком має бути Одиниця Компіляції, якщо локальна – відповідний вузол (особливо це стосується параметрів процедур, у них батьком має бути сама процедура). Також можна вказати, у якому файлі, рядку та колонці знаходиться оголошення змінної.
У найпростішому випадку значення змінної знаходиться за деякою фіксованою адресою, але багато змінних динамічно створюються при вході в процедуру на стеку або регістрі, іноді обчислення адреси значення може бути нетривіальним. У стандарті передбачено механізм опису того, де знаходиться значення змінної – адресні вирази (location expressions). Адресний вираз - це набір інструкцій (константи DW_OP_хххх) для форт-подібної стічної машини, фактично це окрема мова з розгалуженнями, процедурами та арифметичними операціями. Не оглядатимемо повністю цю мову, нас фактично цікавитимуть лише кілька інструкцій:
  • DW_OP_addr - вказує адресу змінної
  • DW_OP_fbreg - вказує усунення змінної від базового регістру (зазвичай покажчика стека)
  • DW_OP_reg0… DW_OP_reg31 - вказує на те, що змінна зберігається у відповідному регістрі
Для того щоб створити адресний вираз, потрібно спочатку створити порожній вираз (dwarf_new_expr), додати до нього інструкції (dwarf_add_expr_addr, dwarf_add_expr_gen та ін) і додати його до вузла як значення атрибуту DW_AT_location (dwarf_add_AT_location_ex.
Функція створення порожнього адресного виразу повертає його дескриптор або 0 за помилки:
Dwarf_Expr dwarf_new_expr(Dwarf_P_Debug dbg, Dwarf_Error *error)
Для додавання вказівок у вираз потрібно використовувати функцію dwarf_add_expr_gen:
Dwarf_Unsigned dwarf_add_expr_gen(Dwarf_P_Expr expr, Dwarf_Small opcode, Dwarf_Unsigned val1, Dwarf_Unsigned val2, Dwarf_Error *error)
  • opcode - код операції, константа DW_OP_хххх
  • val1, val2 – параметри інструкції (див. Стандарт)

Для явного завдання змінної адреси замість попередньої повинна використовуватися функція dwarf_add_expr_addr:
Dwarf_Unsigned dwarf_add_expr_addr(Dwarf_P_Expr expr, Dwarf_Unsigned address, Dwarf_Signed sym_index, Dwarf_Error *error)
  • expr - дескриптор адресного виразу, до якого додається інструкція
  • address - адреса змінної
  • sym_index - індекс символу в таблиці. Необов'язковий, можна передати 0
Функція також повертає DW_DLV_NOCOUNT у разі помилки.
І нарешті, додати створений адресний вираз до сайту можна функцією dwarf_add_AT_location_expr:
Dwarf_P_Attribute dwarf_add_AT_location_expr(Dwarf_P_Debug dbg, Dwarf_P_Die ownerdie, Dwarf_Half attr, Dwarf_P_Expr loc_expr, Dwarf_Error *error)
  • ownerdie - вузол, до якого додається вираз
  • attr - атрибут (у разі DW_AT_location)
  • loc_expr – дескриптор раніше створеного адресного виразу
Функція повертає дескриптор атрибута або DW_DLV_NOCOUNT у разі помилки.
Змінні (а також параметри процедур) та константи - це звичайні вузли з тегом DW_TAG_variable, DW_TAG_formal_parameter та DW_TAG_const_type відповідно. Для них потрібні такі атрибути:
  • ім'я змінної/константи (функція dwarf_add_AT_name, див. «Створення атрибутів вузла»)
  • номер рядка у файлі, де оголошено змінну (атрибут DW_AT_decl_line), функція dwarf_add_AT_unsigned_const (див. «Створення атрибутів вузла»)
  • індекс імені файлу (атрибут DW_AT_decl_file), функція dwarf_add_AT_unsigned_const (див. "Створення атрибутів вузла")
  • тип даних змінної/константи (атрибут DW_AT_type - посилання на раніше створений тип, див. "Створення типів даних")
  • адресний вираз (див. вище) - потрібний для змінної або параметра процедури
  • або значення для константи (атрибут DW_AT_const_value, див. «Створення атрибутів вузла»)
Створення секцій з налагоджувальною інформацією
Після створення всіх вузлів дерева налагоджувальної інформації можна приступати до формування elf-секцій з нею. Це відбувається у два етапи:
  • спочатку потрібно викликати функцію dwarf_transform_to_disk_form, яка викликатиме написану нами функцію для створення потрібних elf-секцій один раз для кожної секції
  • для кожної секції функція dwarf_get_section_bytes поверне нам дані, які потрібно буде записати у відповідну секцію
Функція
dwarf_transform_to_disk_form (Dwarf_P_Debug dbg, Dwarf_Error* error)
переводить створену нами налагоджувальну інформацію в бінарний формат, але нічого не записує на диск. Вона поверне нам кількість створених elf-секцій або DW_DLV_NOCOUNT за помилки. Для кожної секції буде викликана callback-функція, яку ми передали при ініціалізації бібліотеки в функцію dwarf_producer_init_c. Цю функцію маємо написати ми самі. Її специфікація така:
typedef int (*Dwarf_Callback_Func_c)(char* name, int size, Dwarf_Unsigned type, Dwarf_Unsigned flags, Dwarf_Unsigned link, Dwarf_Unsigned info, Dwarf_Unsigned* sect_name_index, void * user_da,
  • name – ім'я elf-секції, яку потрібно створити
  • size – розмір секції
  • type - тип секції
  • flags - прапори секції
  • link - поле зв'язку секції
  • info - поле інформації секції
  • sect_name_index – потрібно повернути індекс секції з релокейшенами (не обов'язково)
  • user_data - передається нам таким самим, яким ми його задали у функції ініціалізації бібліотеки
  • error - сюди можна передати код помилки
У цій функції ми повинні:
  • створити нову секцію (функція elf_newscn, див. Створення секцій)
  • створити заголовок секції (функція elf32_getshdr, там же)
  • правильно його заповнити (див. там же). Це просто, тому що поля заголовка секції відповідають параметрам нашої функції. поля sh_addr, sh_offset, sh_entsize встановимо в 0, а sh_addralign в 1
  • повернути індекс створеної секції (функція elf_ndxscn, див. «Секція.symtab») або -1 за помилки (встановивши в error код помилки)
  • також ми повинні пропустити секцію ".rel" (у нашому випадку), повернувши 0 при поверненні з функції
Після завершення функція dwarf_transform_to_disk_form поверне нам кількість створених секцій. Нам потрібно буде пройтися в циклі від 0 до кожної секції, виконавши такі кроки:
  • створити дані для запису в розділ функцією dwarf_get_section_bytes:
    Dwarf_Ptr dwarf_get_section_bytes(Dwarf_P_Debug dbg, Dwarf_Signed dwarf_section, Dwarf_Signed *elf_section_index, Dwarf_Unsigned *length, Dwarf_Error* error)
    • dwarf_section – номер секції. Має бути в інтервалі 0..n, де n - число, повернене нам функцією dwarf_transform_to_disk_form
    • elf_section_index – повертає індекс секції, в яку потрібно записувати дані
    • length - довжина цих даних
    • error - не використовується
    Функція повертає покажчик на отримані дані або 0 (у тому випадку,
    коли секцій для створення більше не залишилося)
  • створити дескриптор даних поточної секції (функція elf_newdata, див. Створення секцій) та заповнити його (див. там же), встановивши:
    • d_buf - покажчик на дані, отримані нами з попередньої функції
    • d_size - розмір цих даних (там же)
Закінчення роботи з бібліотекою
Після формування секцій можна завершувати роботу з libdwarf функцією dwarf_producer_finish:
Dwarf_Unsigned dwarf_producer_finish(Dwarf_P_Debug dbg, Dwarf_Error* error)
Функція повертає DW_DLV_NOCOUNT за помилки.
Зауважу, що запис на диск цьому етапі не проводиться. Запис потрібно робити за допомогою функцій розділу «Створення ELF - Запис файлу».

Висновок

На цьому все.
Повторюся, створення налагоджувальної інформації - тема дуже велика, і багатьох тем я не торкнувся, тільки відкривши завісу. Бажаючі можуть поглиблюватися до нескінченності.
Якщо у вас будуть питання - постараюся на них відповісти.

За наявності на комп'ютері встановленої антивірусної програмиможна, можливо сканувати всі файли на комп'ютері, а також кожен файл окремо. Можна сканувати будь-який файл, клацнувши правою кнопкою миші на файл і вибравши відповідну опцію для перевірки файлу на наявність вірусів.

Наприклад, на цьому малюнку виділено файл my-file.elf, далі необхідно клацнути правою кнопкою миші по цьому файлу, і в меню файла вибрати опцію "сканувати за допомогою AVG". При виборі цього параметра відкриється AVG Antivirus, який перевірить цей файл на наявність вірусів.


Іноді помилка може виникнути в результаті неправильної установки програмного забезпеченнящо може бути пов'язане з проблемою, що виникла в процесі встановлення. Це може завадити вашій операційній системі зв'язати ваш файл ELF з правильним програмним засобом, впливаючи на так звані "асоціації розширень файлів".

Іноді проста перевстановлення Dolphin (emulator)може вирішити вашу проблему, правильно зв'язавши ELF із Dolphin (emulator). В інших випадках проблеми з файловими асоціаціями можуть виникнути в результаті поганого програмування програмного забезпеченнярозробником, і вам може знадобитися зв'язатися з розробником для отримання додаткової допомоги.


Порада:Спробуйте оновити Dolphin (emulator) до останньої версії, щоб переконатися, що встановлені останні виправлення та оновлення.


Це може здатися надто очевидним, але часто безпосередньо сам файл ELF може бути причиною проблеми. Якщо ви отримали файл через вкладення електронної пошти або завантажили його з веб-сайту, і процес завантаження був перерваний (наприклад, відключення живлення або з іншої причини), файл може зашкодити. Якщо можливо, спробуйте отримати нову копію ELF-файлу і спробуйте знову відкрити його.


Обережно:Пошкоджений файл може спричинити виникнення супутніх збитків попередньої або вже існуючої шкідливої ​​програми на вашому ПК, тому дуже важливо, щоб на вашому комп'ютері постійно працював оновлений антивірус.


Якщо ваш файл ELF пов'язаний з апаратним забезпеченням на вашому комп'ютері, щоб відкрити файл вам може знадобитися оновити драйвери пристроїв, пов'язаних із цим обладнанням.

Ця проблема зазвичай пов'язані з типами мультимедійних файлів, які залежать від успішного відкриття апаратного забезпечення всередині комп'ютера, наприклад, звукової картки або відеокарти. Наприклад, якщо ви намагаєтеся відкрити аудіофайл, але не можете його відкрити, вам може знадобитися оновити драйвери звукової карти.


Порада:Якщо при спробі відкрити файл ELF ви отримуєте повідомлення про помилку, пов'язану з.SYS file, проблема, ймовірно, може бути пов'язана з пошкодженими або застарілими драйверами пристроїв, які потрібно оновити. Цей процес можна полегшити за допомогою програмного забезпечення для оновлення драйверів, таких як DriverDoc .


Якщо кроки не вирішили проблему, і у вас все ще виникають проблеми з відкриттям файлів ELF, це може бути пов'язано з відсутністю доступних системних ресурсів. Для деяких версій файлів ELF може знадобитися значний обсяг ресурсів (наприклад, пам'ять/ОЗУ, обчислювальна потужність) для належного відкриття на вашому комп'ютері. Така проблема трапляється досить часто, якщо ви використовуєте досить старе комп'ютерне апаратне забезпечення та одночасно набагато нову операційну систему.

Така проблема може виникнути, коли комп'ютеру важко впоратися із завданням, оскільки операційна система (та інші служби, що працюють у фоновому режимі) можуть споживати надто багато ресурсів для відкриття файлу ELF. Спробуйте закрити всі програми на ПК, перш ніж відкривати Nintendo Wii Game File. Звільнивши всі доступні ресурси на комп'ютері, ви забезпечите найкращі умови для спроби відкрити файл ELF.


Якщо ви виконали всі описані вище кроки, а ваш файл ELF, як і раніше, не відкривається, може знадобитися виконати оновлення обладнання. У більшості випадків, навіть при використанні старих версій обладнання, обчислювальна потужність може бути більш ніж достатньою для більшості додатків користувача (якщо ви не виконуєте багато ресурсомісткої роботи процесора, такий як 3D-рендеринг, фінансове/наукове моделювання або інтенсивна мультимедійна робота) . Таким чином, цілком імовірно, що вашому комп'ютеру не вистачає необхідного обсягу пам'яті(частіше званої «ОЗУ», чи оперативної пам'яттю) виконання завдання відкриття файла.

Ми сподіваємось, що допомогли Вам вирішити проблему з файлом ELF. Якщо Ви не знаєте, де можна завантажити програму з нашого списку, натисніть на посилання (ця назва програми) - Ви знайдете докладнішу інформацію щодо місця, звідки завантажити безпечну інсталяційну версію необхідної програми.

Відвідування цієї сторінки має допомогти Вам відповісти конкретно на ці або схожі питання:

  • Як відкрити файл із розширенням ELF?
  • Як конвертувати файл ELF в інший формат?
  • Що таке розширення формату ELF?
  • Які програми обслуговують файл ELF?

Якщо після перегляду матеріалів на цій сторінці, Ви, як і раніше, не отримали задовільної відповіді на якесь із поданих вище питань, це означає, що подана тут інформація про файл ELF неповна. Зв'яжіться з нами, використовуючи контактний формуляр та напишіть, яку інформацію Ви не знайшли.

Що ще може спричинити проблеми?

Причин того, що Ви не можете відкрити файл ELF може бути більше (не тільки відсутність відповідної програми).
По перше- файл ELF може бути неправильно пов'язаний (несумісний) із встановленим додатком для його обслуговування. У такому разі Вам необхідно самостійно змінити цей зв'язок. З цією метою натисніть праву кнопку мишки на файлі ELF, який потрібно редагувати, натисніть опцію "Відкрити за допомогою"а потім виберіть зі списку програму, яку Ви встановили. Після такої дії проблеми з відкриттям файлу ELF повинні повністю зникнути.
По-друге- файл, який Ви хочете відкрити, може бути просто пошкоджений. У такому разі найкраще буде знайти нову його версію, або скачати його повторно з того ж джерела (можливо з якогось приводу в попередній сесії завантаження файлу ELF не закінчилося і він не може бути правильно відкритий).

Ви хочете допомогти?

Якщо у Вас є додаткова інформація про розширення файлу ELF, ми будемо вдячні, якщо Ви поділитеся нею з користувачами нашого сайту. Скористайтеся формуляром і надішліть нам свою інформацію про файл ELF.

© 2022 androidas.ru - Все про Android