Създайте ELF файл с информация за отстраняване на грешки (DWARF) ръчно (за ARM микроконтролери). ELF и PE EXE форматират Elf файл

ELF формат

Форматът ELF има няколко типа файлове, които досега наричахме по различен начин, например изпълним файл или обектен файл. Стандартът ELF обаче прави разлика между следните типове:

1. Преместваем файл(файл с възможност за преместване), който съхранява инструкции и данни, които могат да бъдат свързани с други обектни файлове. Резултатът от това свързване може да бъде изпълним файл или споделен обектен файл.

2. Споделен обектен файл(споделен обектен файл) също съдържа инструкции и данни, но може да се използва по два начина. В първия случай той може да бъде свързан с други преместваеми файлове и споделени обектни файлове, което води до създаване на нов обектен файл. Във втория случай, когато програмата се стартира за изпълнение, операционната система може динамично да я свърже с изпълнимия файл на програмата, в резултат на което ще се създаде изпълним образ на програмата. В последния случай говорим за споделени библиотеки.

3. Изпълним файлсъхранява пълно описание, което позволява на системата да създаде образ на процеса. Той съдържа инструкции, данни, описание на необходимите споделени обектни файлове и необходимата символична информация и информация за отстраняване на грешки.

На фиг. 2.4 показва структурата на изпълним файл, с който операционната система може да създаде образ на програмата и да стартира програмата за изпълнение.

Ориз. 2.4... Структурата на изпълнимия файл във формат ELF

Заглавката има фиксирано местоположение във файла. Останалите компоненти се поставят според информацията, съхранена в заглавката. По този начин заглавката съдържа общо описание на файловата структура, местоположението на отделните компоненти и техните размери.

Тъй като заглавката на ELF файла определя неговата структура, нека го разгледаме по-подробно (Таблица 2.4).

Таблица 2.3... ELF заглавни полета

Поле Описание
e_identi Масив от байтове, всеки от които дефинира някаква обща характеристика на файла: файлов формат (ELF), номер на версията, системна архитектура (32-битова или 64-битова) и т.н.
e_type Тип файл, тъй като форматът ELF поддържа няколко типа
електронна_машина Архитектурата на хардуерната платформа, за която е създаден този файл. Таблица 2.4 показва възможните стойности на това поле
електронна_версия Номер на версията на ELF. Обикновено се дефинира като EV_CURRENC (текущ), което означава най-новата версия
e_entry Виртуален адрес, на който системата ще прехвърли управлението след зареждане на програмата (входна точка)
e_phoff Местоположение (отместено от началото на файла) на таблицата за заглавки на програмата
e_shoff Местоположение на таблицата за заглавката на раздел
e_ehsize Размер на заглавието
e_phentsize Размер на заглавката на всяка програма
e_phnum Брой заглавки на програмата
e_shentsize Размер на заглавката на всеки сегмент (секция)
e_shnum Брой заглавки на сегмента (секции)
e_shstrndx Местоположение на сегмента, съдържащ таблицата с редове

Таблица 2.4... ELF Header e_machine Стойности на полета

смисъл Хардуерна платформа
EM_M32 AT&T WE 32100
ЕМ_SPARC Слънце SPARC
EM_386 Intel 80386
ЕМ_68K Motorola 68000
EM_88K Motorola 88000
EM_486 Intel 80486
EM_860 Intel i860
EM_MIPS MIPS RS3000 Big-Endian
EM_MIPS_RS3_LE MIPS RS3000 Little-Endian
EM_RS6000 RS6000
EM_PA_RISC PA-RISC
EM_nCUBE nCUBE
EM_VPP500 Fujitsu VPP500
EM_SPARC32PLUS Слънце SPARC 32+

Информацията в таблицата за заглавки на програмата казва на ядрото как да създаде изображение на процес от сегменти. Повечето от сегментите се копират (картографират) в паметта и представляват съответните сегменти на процеса, докато се изпълнява, като сегменти от код или данни.

Заглавката на всеки програмен сегмент описва един сегмент и съдържа следната информация:

Тип сегмент и действия на операционната система с този сегмент

Местоположение на сегмента във файла

Сегментирайте началния адрес във виртуалната памет на процеса

Размер на сегмента във файла

Размер на сегмента в паметта

Флагове за достъп до сегменти (запис, четене, изпълнение)

Някои от сегментите са от типа LOAD, което инструктира ядрото, когато програмата се стартира за изпълнение, да създаде структури от данни, съответстващи на тези сегменти, т.нар. областикоито дефинират последователни части от виртуална памет в процес и свързаните с тях атрибути. Сегментът, чието местоположение във файла ELF е посочено в съответната заглавка на програмата, ще бъде съпоставено със създадената област, виртуалният адрес на която също е посочен в заглавката на програмата. Сегментите от този тип включват например сегменти, съдържащи програмни инструкции (код) и нейните данни. Ако размерът на сегмента е по-малък от размера на областта, неизползваното пространство може да бъде запълнено с нули. Този механизъм се използва по-специално при създаване на неинициализирани данни за процеса (BSS). Ще говорим повече за областите в глава 3.

Сегмент от тип INTERP съхранява програмен интерпретатор. Този тип сегмент се използва за програми, които се нуждаят от динамично свързване. Същността на динамичното свързване е, че отделните компоненти на изпълнимия файл (споделени обектни файлове) се свързват не на етапа на компилация, а на етапа на стартиране на програмата за изпълнение. Името на файла, който е редактор на динамични връзки, се съхранява в този сегмент. В процеса на стартиране на програмата за изпълнение, ядрото създава изображение на процеса с помощта на посочения линкер. Така първоначално в паметта се зарежда не оригиналната програма, а динамичният линкер. В следващата стъпка динамичният линкер работи с ядрото на UNIX, за да създаде пълно изображение на изпълнимия файл. Динамичният редактор зарежда необходимите споделени обектни файлове, чиито имена се съхраняват в отделни сегменти на оригиналния изпълним файл и извършва необходимото поставяне и свързване. Накрая контролът се прехвърля към оригиналната програма.

Накрая таблицата за заглавки завършва файла секцииили секции(раздел). Разделите (секции) дефинират секциите на файл, които се използват за свързване към други модули по време на компилация или динамично свързване. Съответно заглавията съдържат цялата информация, необходима за описване на тези раздели. Обикновено секциите съдържат по-подробна информация за сегментите. Така например, един кодов сегмент може да се състои от няколко секции, като хеш таблица за съхраняване на индексите на символите, използвани в програмата, секция от кода за инициализация на програмата, таблица за свързване, използвана от динамичен редактор, и раздел съдържащи действителните инструкции за програмата.

Ще се върнем към формата ELF в глава 3, когато обсъждаме организацията на виртуалната памет в процес, но засега нека преминем към следващия често срещан формат, COFF.

От книгата Изкуството на Unix програмирането автора Реймънд Ерик Стивън

От книгата Ръководство за самообучение за работа на компютър автора Колисниченко Денис Николаевич

От книгата Реферат, курсова работа, диплома на компютър автора Баловсяк Надежда Василиевна

5.2.6. Windows INI формат Много програми на Microsoft Windows използват текстов формат на данни като фрагмента, показан в Пример 5.6. В този пример незадължителните ресурси с име акаунт, директория, numeric_id и разработчик са свързани с наименувани проекти python, sng, f etchmail и py-howto. В записа

От книгата Най-новият урок за работа на компютър автора Белунцов Валери

14.5.3. Форматът на клетката Форматът указва как се показва стойността на клетката. Форматът е тясно свързан с типа данни на клетката. Вие сами определяте вида. Ако сте въвели число, това е числов тип данни. Самият Excel се опитва да определи формата по тип данни. Например, ако сте въвели текст, тогава

От книгата Изкуството на Unix програмирането автора Реймънд Ерик Стивън

PDF формат PDF е съкращение от Portable Document Format. Този формат е създаден специално за премахване на проблеми с показването на информация във файлове. Предимството му е, че първо, документът, запазен в PDF формат, ще бъде същият

От книгата TCP / IP архитектура, протоколи, внедряване (включително IP версия 6 и IP сигурност) от Фейт Сидни М

Формат на файла Когато потребителят започне да работи с файл, системата трябва да знае в какъв формат е написан и с коя програма трябва да бъде отворен. Например, ако файлът съдържа обикновен текст, тогава той може да бъде прочетен във всяка текстова програма

От книгата Yandex за всички авторът Абрамзон М.Г.

5.2.2. Формат RFC 822 Метаформатът RFC 822 произлиза от текстовия формат на имейл съобщенията в Интернет. RFC 822 е основният интернет RFC стандарт за този формат (впоследствие заменен от RFC 2822). Формат MIME (многофункционално разширение за интернет медия).

От книгата Macromedia Flash Professional 8. Графика и анимация автор Дронов V.A.

5.2.3. Формат Cookie-Jar Форматът cookie-jar се използва от Fortune (1) за собствената му база данни с произволни котировки. Подходящ е за записи, които са просто блокове от неструктуриран текст. В този формат символът се използва като разделител за записи

От книгата Компютърна обработка на звука автора Загуменов Александър Петрович

5.2.4. Форматът record-jar Разделителите за запис в cookie-jar работят добре с мета-формата RFC 822 за записи, които образуват това, което тази книга нарича "record-jar". Понякога се изисква текстов формат, който поддържа множество записи с различен набор от изрични имена

От книгата UNIX Operating System автора Робачевски Андрей М.

5.2.6. Windows INI формат Много програми на Microsoft Windows използват текстов формат на данни като фрагмента, показан в Пример 5.6. В този пример незадължителните ресурси с име акаунт, директория, numeric_id и разработчик са свързани с наименувани проекти python, sng, fetchmail и py-howto. В записа

От книгата Офис компютър за жени автора Пастернак Евгения

19.5 Обобщен формат на URL За да обобщим горното, имайте предвид, че :? URL адресът започва с използвания протокол за достъп.? За приложения, различни от уеб новини и електронна поща, следва следният разделител: //.? След това се посочва името на хоста на сървъра.? Накрая

От книгата на автора

3.3.1. RSS формат Можете да четете новини от уебсайта по различни начини. Най-лесният начин е да посещавате сайта от време на време и да виждате нови съобщения. Можете да инсталирате програма, която се свързва с новинарски канал и сама получава заглавия или анотации на новини, като

От книгата на автора

MP3 формат MP3 форматът е създаден за разпространение на музикални файлове, компресирани с кодека MPEG 1 ниво 3. В момента е най-популярният формат за разпространение на музика през Интернет и извън него. Поддържа се от абсолютно всички програми за запис и обработка на звук, за

От книгата на автора

MP3 формат Международният метод за компресиране на аудио MPEG (Moving Pictures Experts Group) и форматът на компресиран аудио файл се основават на възприятие на аудио кодиране. Работете върху създаването на ефективни алгоритми за кодиране

От книгата на автора

Формат ELF Форматът ELF има няколко типа файлове, които досега сме наричали по различен начин, например изпълним файл или обектен файл. Стандартът ELF обаче прави разлика между следните типове: 1. Преместваем файл, който съхранява инструкции и данни, които могат да бъдат

От книгата на автора

Формат на числата Най-накрая стигнахме до числовия формат. Вече го споменах повече от веднъж, сега ще сложа всичко по рафтовете (въпреки че вече можете да разберете общото значение) Числата в Excel могат да се показват в различни формати. В този раздел ще говорим за това какви числови формати съществуват и как

Надяваме се, че сме ви помогнали да решите проблема с файла ELF. Ако не знаете къде можете да изтеглите приложение от нашия списък, кликнете върху връзката (това е името на програмата) - ще намерите по-подробна информация относно мястото, откъдето да изтеглите версията за защитена инсталация на необходимото приложение .

Посещението на тази страница трябва да ви помогне да отговорите конкретно на тези или подобни въпроси:

  • Как да отворя файлове с разширение ELF?
  • Как мога да конвертирам ELF файл в различен формат?
  • Какво е разширението на файловия формат ELF?
  • Какви програми поддържат файла ELF?

Ако след като сте прегледали нещата на този сайт, все още не сте получили задоволителен отговор на някой от горните въпроси, това означава, че информацията относно файла ELF, представена тук, не е пълна. Свържете се с нас чрез формата за контакт и напишете каква информация не сте намерили.

Какво друго може да причини проблема?

Може да има повече причини, поради които не можете да отворите ELF файл (не само липсата на подходящо приложение).
Първо- ELF файлът може да е неправилно свързан (несъвместим) с инсталираното приложение за неговата поддръжка. В този случай трябва сами да промените тази връзка. За тази цел щракнете с десния бутон върху ELF файла, който искате да редактирате, щракнете върху опцията "За отваряне с"и след това изберете програмата, която сте инсталирали от списъка. След такова действие проблемите с отварянето на ELF файла трябва напълно да изчезнат.
Второ- файлът, който искате да отворите, може просто да е повреден. Тогава най-доброто решение е да намерите нова версия или да я изтеглите отново от същия източник както преди (може би по някаква причина в предишната сесия изтеглянето на ELF файла не е приключило и той не може да бъде отворен правилно).

Искате ли да помогнете?

Ако имате допълнителна информация относно разширението на файла ELF, ще бъдем благодарни, ако я споделите с потребителите на нашия сайт. Използвайте предоставения формуляр и ни изпратете вашата информация за ELF файла.

Стандартните инструменти за разработка компилират вашата програма в изпълним и свързващ формат (ELF) файл с опция за включване на информация за отстраняване на грешки. Спецификацията на формата може да се прочете. Освен това всяка архитектура има свои собствени специфики, като ARM функции. Нека да разгледаме набързо този формат.
Изпълним файл ELF се състои от следните части:
1. Заглавие (ELF Header)
Съдържа обща информация за файла и основните му характеристики.
2. Таблица за заглавки на програмата
Това е таблица на съответствието на файловите секции със сегментите на паметта, указва на зареждача в коя област на паметта да запише всяка секция.
3. Раздели
Секциите съдържат цялата информация във файла (програма, данни, информация за отстраняване на грешки и т.н.)
Всеки раздел има тип, име и други параметри. Секцията ".text" обикновено съхранява кода, ".symtab" - таблицата със символи на програмата (имена на файлове, процедури и променливи), ".strtab" - таблицата с низове, секциите с префикс ".debug_" - отстраняване на грешки информация и др. .d. Освен това файлът трябва да съдържа празна секция с индекс 0.
4. Таблица за заглавки на раздел
Това е таблица, съдържаща масив от заглавки на секции.
Форматът е разгледан по-подробно в раздела Създаване на ELF.

Преглед на DWARF

DWARF е стандартизиран формат на информация за отстраняване на грешки. Стандартът може да бъде изтеглен от официалния уебсайт. Има и страхотен преглед на формата: Въведение във формата за отстраняване на грешки DWARF (Майкъл Дж. Игър).
Защо имам нужда от информация за отстраняване на грешки? Тя ви позволява да:
  • задайте точки на прекъсване не на физическия адрес, а на номера на реда в изходния файл или на името на функцията
  • показване и промяна на стойностите на глобалните и локалните променливи, както и параметрите на функцията
  • показване на стека от повиквания (обратно проследяване)
  • изпълнете програмата стъпка по стъпка не с една инструкция за асембиране, а с редове от изходен код
Тази информация се съхранява в дървовидна структура. Всеки възел на дървото има родител, може да има деца и се нарича DIE (Debugging Information Entry). Всеки възел има свой собствен таг (тип) и списък с атрибути (свойства), които описват възела. Атрибутите могат да съдържат всичко, което желаете, като данни или връзки към други възли. Освен това има информация, съхранявана извън дървото.
Възлите са разделени на два основни типа: възли, които описват данни и възли, които описват код.
Възли, описващи данни:
  1. Типове данни:
    • Основни типове данни (възел с тип DW_TAG_base_type), като тип int в C.
    • Съставни типове данни (указатели и др.)
    • масиви
    • Структури, класове, съюзи, интерфейси
  2. Обекти с данни:
    • константи
    • функционални параметри
    • променливи
    • и т.н.
Всеки обект с данни има атрибут DW_AT_location, който показва как се изчислява адресът, където се намират данните. Например променливата може да има фиксиран адрес, да бъде в регистър или в стека или да бъде член на клас или обект. Този адрес може да се изчисли по доста сложен начин, поради което стандартът предвижда така наречените изрази за местоположение, които могат да съдържат последователност от оператори на специална вътрешна машина за подреждане.
Възли, описващи кода:
  1. Процедури (функции) - възли с етикет DW_TAG_subprogram. Дъщерните възли могат да съдържат описания на променливи - параметри на функцията и локални променливи на функцията.
  2. Компилационна единица. Съдържа информация за програмата и е родител на всички останали възли.
Информацията, описана по-горе, се намира в секциите ".debug_info" и ".debug_abbrev".
Друга информация:
  • Информация за номера на реда (секция ".debug_line")
  • Информация за макроса (секция ".debug_macinfo")
  • Информация за рамката на обаждането (секция ".debug_frame")

Създаване на ELF

Ще създадем файлове във формат EFL, използвайки библиотеката libelf от пакета elfutils. В интернет има добра статия за използването на libelf - LibELF by Example (за съжаление, създаването на файлове е описано много накратко в нея), както и документация.
Създаването на файл се състои от няколко етапа:
  1. Инициализация на клевета
  2. Създаване на заглавка на файл (ELF Header)
  3. Създаване на таблица за заглавки на програмата
  4. Създаване на раздели
  5. Запис на файл
Нека разгледаме етапите по-подробно
Инициализация на клевета
Първо ще трябва да извикате функцията 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_begin
Връща 0 при грешка или указател към структурата - заглавката на ELF файла:
# определят EI_NIDENT 16 typedef структура (неподписан овъгляване 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 за ARM архитектура
  • e_version - версията на файла, трябва да бъде настроена на EV_CURRENT
  • e_entry - адресът на входната точка, не е необходим за нас
  • e_phoff - отместване в заглавния файл на програмата, e_shoff - отместване за заглавката на секцията, не се попълва
  • e_flags - специфични за процесора флагове, за нашата архитектура (Cortex-M3) трябва да бъдат настроени на 0x05000000 (ABI версия 5)
  • e_ehsize, e_phentsize, e_phnum, e_shentsize, e_shnum - не докосвайте
  • e_shstrndx - съдържа номера на секцията, в която се намира таблицата с низове със заглавки на секции. Тъй като все още нямаме секции, ще инсталираме този номер по-късно.
Създаване на заглавка на програмата
Както бе споменато, таблицата за заглавки на програмата е таблица за съпоставяне на раздел файл към памет, която казва на зареждащия къде да запише всяка секция. Заглавката се създава с помощта на функцията elf32_newphdr:
Elf32_Phdr * elf32_newphdr (Elf * elf, size_t count);
  • елф е нашият дескриптор
  • 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_W Elfor p_flags;
  • p_type - тип сегмент (секция), тук трябва да посочим PT_LOAD - зареден сегмент
  • p_offset - отмествания във файла, откъдето започват данните на секцията, която ще се зареди в паметта. Имаме този section.text, който ще се намира непосредствено след заглавката на файла и заглавката на програмата, можем да изчислим отместването като сума от дължините на тези заглавки. Дължината на всеки тип може да бъде получена с помощта на функцията elf32_fsize:
    size_t elf32_fsize (тип Elf_Type, брой size_t, неподписана int версия); тип - тук константата ELF_T_ххх, ще ни трябват размерите ELF_T_EHDR и ELF_T_PHDR; count - броят на елементите от необходимия тип, версия - трябва да бъде настроен на 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 Elf Elf sh3_link; Elf32_Word Elf sh_info;
  • sh_name - име на раздел - изместване в таблицата с низове на заглавките на раздели (section.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 - да зададете равно на адреса, където ще бъде зареден този код
Раздел.symtab
Разделът съдържа описание на всички символи (функции) на програмата и файловете, в които са описани. Състои се от следните елементи с дължина 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 - името на символа (индекс в низ table.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 - индекс на секцията, за която е дефиниран символът (секция index.text), или SHN_ABS за файл.
    Индексът на раздел от неговия scn дескриптор може да се определи с помощта на elf_ndxscn:
    size_t elf_ndxscn (Elf_Scn * scn);

Този раздел се създава по обичайния начин, само sh_type трябва да бъде зададен на SHT_SYMTAB, а индексът на секцията .strtab се записва в полето sh_link, така че тези секции стават свързани.
Раздел Strtab
Този раздел съдържа имената на всички символи от секцията .symtab. Създаден като нормален раздел, но sh_type трябва да бъде настроен на SHT_STRTAB, sh_flags на SHF_STRINGS, така че този раздел се превръща в таблица с редове.
Данните за секция могат да бъдат събрани при преминаване през изходния текст в масив, указателят към който след това се записва в дескриптора на данни на секцията (d_buf).
Разделът.shstrtab
Раздел - таблица с низове, съдържа заглавията на всички раздели на файла, включително собственото му заглавие. Създава се по същия начин като секцията .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);
  • елф - дескриптор
  • cmd - команда, трябва да е равна на ELF_C_WRITE за писане.
Функцията връща -1 при грешка. Текстът на грешката може да бъде получен чрез извикване на функцията elf_errmsg (-1), която ще върне указател към низа за грешка.
Завършваме работата с библиотеката с функцията elf_end, на която предаваме нашия дескриптор. Остава само да затворите предварително отворения файл.
Въпреки това, нашият генериран файл не съдържа информация за отстраняване на грешки, която ще добавим в следващия раздел.

Създаване на джуджета

Ще създадем информация за отстраняване на грешки с помощта на библиотека, която идва с pdf файл с документация (libdwarf2p.1.pdf - интерфейс на библиотеката на производителя към DWARF).
Създаването на информация за отстраняване на грешки се състои от следните етапи:
  1. Въвеждане на информация за отстраняване на грешки (DIE)
  2. Създаване на атрибути на възел
  3. Създаване на типове данни
  4. Създаване на процедури (функции)
Нека разгледаме етапите по-подробно
Инициализира се производител на libdwarf
Ще създадем информация за отстраняване на грешки по време на компилиране едновременно със създаването на символи в секцията .symtab, така че библиотеката трябва да бъде инициализирана след инициализиране на libelf, създаване на заглавката на ELF и заглавката на програмата, преди създаването на секциите.
За инициализация ще използваме функцията dwarf_producer_init_c. В библиотеката има още няколко функции за инициализация (dwarf_producer_init, dwarf_producer_init_b), които се различават в някои нюанси, описани в документацията. По принцип можете да използвате всеки от тях.

Dwarf_P_Debug dwarf_producer_init_c (Dwarf_Unsigned флагове, Dwarf_Callback_Func_c func, Dwarf_Handler errhand, Dwarf_Ptr errarg, void * user_data, Dwarf_Error * грешка)

  • флагове - комбинация от "или" от няколко константи, които определят някои параметри, например битовата ширина на информацията, последователността от байтове (little-endian, big-endian), форматът на преместване, от който определено се нуждаем от DW_DLC_WRITE и DW_DLC_SYMBOLIC_RELOCATIONS
  • func е функция за обратно извикване, която ще се извиква при създаване на ELF секции с информация за отстраняване на грешки. За повече подробности вижте раздела „Създаване на раздели с информация за отстраняване на грешки“ по-долу.
  • errhand е указател към функция, която ще бъде извикана, когато възникне грешка. Може да се предава 0
  • errarg - данните, които ще бъдат предадени на функцията errhand, могат да бъдат зададени на 0
  • user_data - данните, които ще бъдат предадени на функцията func, могат да бъдат зададени на 0
  • грешка - върнатият код за грешка
Функцията връща Dwarf_P_Debug - дескриптор, използван във всички следващи функции, или -1 в случай на грешка, докато грешката ще съдържа код за грешка (можете да получите текста на съобщението за грешка чрез неговия код, като използвате функцията dwarf_errmsg, като подадете този код към него)
Въвеждане на информация за отстраняване на грешки (DIE)
Както е описано по-горе, информацията за отстраняване на грешки формира дървовидна структура. За да създадете възел на това дърво, трябва:
  • създайте го с функцията dwarf_new_die
  • добавяне на атрибути към него (всеки тип атрибути се добавя от собствена функция, която ще бъде описана по-късно)
Възелът се създава с помощта на функцията dwarf_new_die:
Dwarf_P_Die dwarf_new_die (Dwarf_P_Debug dbg, Dwarf_Tag new_tag, Dwarf_P_Die родител, Dwarf_P_Die дете, Dwarf_P_Die left_sibling, Dwarf_P_Die right_sibling, Dwarf_Error)
  • new_tag - маркер на възел (тип) - константа DW_TAG_xxxx, която може да се намери във файла libdwarf.h
  • родител, дете, left_sibling, right_sibling - съответно родител, дете, ляв и десен съсед на възела. Не е необходимо да посочвате всички тези параметри, достатъчно е да посочите един, вместо останалите да поставите 0. Ако всички параметри са равни на 0, възелът ще бъде или коренен, или изолиран
  • грешка - ще съдържа кода на грешката, когато възникне
Функцията връща DW_DLV_BADADDR при грешка или манипулатор на възел Dwarf_P_Die при успех
Създаване на атрибути на възел
Има цяло семейство функции dwarf_add_AT_xxxx за създаване на атрибути на възел. Понякога е проблематично да се определи коя функция трябва да създаде необходимия атрибут, така че дори се задълбочих в изходния код на библиотеката няколко пъти. Някои от функциите ще бъдат описани тук, някои по-долу - в съответните раздели. Всички те приемат параметъра ownerdie, манипулатора на възела, към който ще бъде добавен атрибутът, и връщат код за грешка в параметъра за грешка.
Функцията dwarf_add_AT_name добавя атрибут на име (DW_AT_name) към възела. Повечето възли трябва да имат име (например процедури, променливи, константи), някои може да нямат име (например Compilation Unit)
Dwarf_P_Attribute dwarf_add_AT_name (Dwarf_P_Die ownerdie, char * име, Dwarf_Error * грешка)
  • име - действителната стойност на атрибута (име на възел)

Функциите 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 стойност, Dwarf_Error * грешка)
  • dbg - Dwarf_P_Debug дескриптор, получен по време на инициализацията на библиотеката
  • attr - атрибутът, чиято стойност се задава - константа DW_AT_xxxx, която може да се намери във файла libdwarf.h
  • value - стойността на атрибута
Връща DW_DLV_BADADDR при грешка или манипулатор на атрибут при успех.
Създаване на компилационна единица
Всяко дърво трябва да има корен - имаме компилационна единица, която съдържа информация за програмата (например името на основния файл, използвания език за програмиране, името на компилатора, чувствителността на символите (променливи, функции) към случай, основната функция на програмата, началния адрес и т.н. и т.н.). По принцип не се изискват никакви атрибути. Например, нека създадем информация за основния файл и компилатора.
Информация за главния файл
Атрибутът name (DW_AT_name) се използва за съхраняване на информацията за основния файл, използвайте функцията dwarf_add_AT_name, както е показано в раздела „Създаване на атрибути на възел“.
Информация за компилатора
Нека използваме функцията dwarf_add_AT_producer:
Dwarf_P_Attribute dwarf_add_AT_name (Dwarf_P_Die ownerdie, char * производителен_низ, Dwarf_Error * грешка)
  • producer_string - низ с информационен текст
Връща DW_DLV_BADADDR при грешка или манипулатор на атрибут при успех.
Създаване на общ информационен вход
Обикновено, когато се извиква функция (подпрограма), нейните параметри и адрес за връщане се изтласкват в стека (въпреки че всеки компилатор може да го направи различно), това се нарича рамка за извикване. Дебъгерът се нуждае от информация за формата на рамката, за да определи правилно адреса на връщане от функцията и да изгради обратно проследяване - веригата от извиквания на функции, които ни доведоха до текущата функция, и параметрите на тези функции. Той също така обикновено определя регистрите на процесора, които се съхраняват в стека. Кодът, който запазва място в стека и запазва регистрите на процесора, се нарича пролог на функцията, кодът, който възстановява регистрите и стека, се нарича епилог.
Тази информация е силно зависима от компилатора. Например, прологът и епилогът не е задължително да са в самото начало и в края на функция; понякога рамката се използва, понякога не; регистрите на процесора могат да се съхраняват в други регистри и т.н.
Така че, дебъгерът трябва да знае как регистрите на процесора променят стойността си и къде ще бъдат съхранени при влизане в процедурата. Тази информация се нарича 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_Ptrror init_bytestrf_dwarf, Dwarf_Small
  • augmenter - низ, кодиран с UTF-8, чието присъствие показва, че има допълнителна специфична за платформата информация към CIE или FDE. Поставяме празен ред
  • code_align - подравняване на кода в байтове (имаме 2)
  • data_align - подравняване на данните в рамката (задайте -4, което означава, че всички параметри заемат 4 байта в стека и расте надолу в паметта)
  • ret_addr_reg - регистър, съдържащ адреса за връщане от процедурата (имаме 14)
  • init_bytes е масив, съдържащ инструкции DW_CFA_хххх. За съжаление няма удобен начин за генериране на този масив. Можете да го генерирате ръчно или да го шпионирате в elf файла, който беше генериран от C компилатора, което направих аз. За моя случай той съдържа 3 байта: 0x0C, 0x0D, 0, което означава DW_CFA_def_cfa: r13 от 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 * грешка)
  • attr - атрибутът, в този случай DW_AT_type
  • otherdie - манипулиране на възела от типа, който имаме предвид
Създаване на процедури
За да създам процедури, трябва да изясня друг тип информация за отстраняване на грешки - Информация за номера на реда. Той служи за съпоставяне на всяка машинна инструкция към определен ред изходен код, както и за разрешаване на отстраняване на грешки ред по ред на програмата. Тази информация се съхранява в секцията .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 * име, Dwarf_Unsigned dir_idx, Dwarf_Unsigned time_mod, Dwarf_Unsigned дължина, Dwarf_Error * грешка)
  • име - име на файла
  • dir_idx - индекс на папката, в която се намира файлът. Индексът може да бъде получен с помощта на функцията dwarf_add_directory_decl. Ако се използват пълни пътища, можете да поставите 0 като индекс на папката и изобщо да не използвате dwarf_add_directory_decl
  • time_mod - време за промяна на файла, можете да го пропуснете (0)
  • дължина - размер на файла, също по избор (0)
Функцията ще върне индекса на файла или DW_DLV_NOCOUNT при грешка.
Има три функции dwarf_add_line_entry_b, dwarf_lne_set_address, dwarf_lne_end_sequence за генериране на информация за номера на ред, които ще разгледаме по-долу.
Създаването на информация за отстраняване на грешки за процедура се извършва на няколко етапа:
  • създаване на символ на процедура в секцията .symtab
  • създаване на възел на процедурата с атрибути
  • създаване на FDE процедура
  • създаване на параметри на процедурата
  • генериране на информация за номера на ред
Създайте символ за процедура
Символът на процедурата се създава, както е описано в раздела "Section.symtab" по-горе. В него символите на процедурите са разпръснати със символите на файловете, в които се намира изходният код на тези процедури. Първо създаваме символа на файла, след това процедурите. В този случай файлът става текущ и ако следващата процедура е в текущия файл, символът на файла не трябва да се създава отново.
Създаване на възел на процедурата с атрибути
Първо, ние създаваме възел, използвайки функцията dwarf_new_die (вижте раздела "Създаване на възли"), като указваме DW_TAG_subprogram като маркер и Компилационна единица (ако е глобална процедура) или съответния 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_Err
  • attr - атрибут (DW_AT_low_pc или DW_AT_high_pc)
  • pc_value - стойност на адреса
  • sym_index е индексът на символа на процедурата в таблицата.symtab. По избор можете да предадете 0
Функцията ще върне DW_DLV_BADADDR при грешка.
Създаване на FDE процедура
Както бе споменато по-горе в раздела „Създаване на общ информационен запис“, за всяка процедура трябва да създадете манипулатор на рамка, което се случва на няколко етапа:
  • създаване на нов FDE (вижте Създаване на общ информационен запис)
  • присъединяване на създадения FDE към общия списък
  • добавяне на инструкции към генерирания FDE
Можете да създадете нов FDE с помощта на функцията dwarf_new_fde:
Dwarf_P_Fde dwarf_new_fde (Dwarf_P_Debug dbg, Dwarf_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_rlen, Dwarf_P_Fde fde, Dwarf_P_Die die, Dwarf_Unsigned cie, Dwarf_Addr virt_addr, Dwarf_Unsigned code_rlen, Dwarf_Unsignn code_rlen, Dwarf_signn *Urlen_U
  • fde - току-що полученият дескриптор
  • die - DIE процедури (вижте Създаване на възел на процедура с атрибути)
  • cie - CIE дескриптор (вижте Създаване на общ информационен запис)
  • virt_addr е началният адрес на нашата процедура
  • code_len - дължина на процедурата в байтове
Функцията ще върне DW_DLV_NOCOUNT при грешка.
След всичко това можете да добавите инструкции DW_CFA_xxxx към нашия 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 * грешка)
  • op - код на инструкциите (DW_CFA_хххх)
  • val1, val2 - параметри на инструкциите (различни за всяка инструкция, вижте Стандарт, раздел 6.4.2 Инструкции за рамка на повикване)
Функцията 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 * грешка)
  • fde - манипулатор на генерирания FDE
  • reg - регистърът, който се записва в рамката
  • отместване - неговото изместване в рамката (не в байтове, а в елементи на рамката, вижте Създаване на общ информационен запис, data_align)
Например, компилаторът създава процедура, в пролога на която регистър lr (r14) се съхранява в рамката на стека. На първо място, трябва да добавите инструкцията DW_CFA_advance_loc с първия параметър равен на 1, което означава напредване на компютърния регистър с 2 байта (вижте Създаване на общ информационен запис, code_align), след това да добавите DW_CFA_def_cfa_offset с параметър 4 (задаване на отместване на данните в рамката с 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 * грешка)
  • 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 дискриминатор, Dwarf_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 - архитектура на набора от инструкции. Не забравяйте да посочите DW_ISA_ARM_thumb за ARM Cortex M3!
  • дискриминатор. Една позиция (файл, ред, колона) на изходния код може да съответства на различни машинни инструкции. В този случай трябва да бъдат зададени различни дискриминатори за наборите от такива инструкции. Ако няма такива случаи, трябва да е 0
Функцията връща 0 (успех) или DW_DLV_NOCOUNT (грешка).
И накрая, функцията dwarf_lne_end_sequence приключва процедурата:
Dwarf_Unsigned dwarf_lne_end_sequence (Dwarf_P_Debug dbg, Dwarf_Addr адрес; Dwarf_Error * грешка)
  • адрес - адресът на текущата машинна инструкция
Връща 0 (успех) или DW_DLV_NOCOUNT (грешка).
Това завършва създаването на процедурата.
Създаване на променливи и константи
Като цяло променливите са доста прости. Те имат име, местоположение на паметта (или регистър на процесора), където се намират техните данни, и вида на тези данни. Ако променливата е глобална, нейният родител трябва да бъде Компилационна единица, ако е локален, тя трябва да бъде съответен възел (особено за параметрите на процедурата, те трябва да имат самата процедура като родител). Можете също да посочите в кой файл, ред и колона се намира декларацията на променливата.
В най-простия случай стойността на променлива се намира на някакъв фиксиран адрес, но много променливи се създават динамично при въвеждане на процедура в стека или регистъра, понякога изчисляването на адреса на стойността може да бъде доста нетривиално. Стандартът предоставя механизъм за описание къде е стойността на променливата - адресни изрази (изрази за местоположение). Адресният израз е набор от инструкции (DW_OP_хххх константи) за стекова машина, подобна на fort; всъщност това е отделен език с клонове, процедури и аритметични операции. Няма да прегледаме този език изцяло, всъщност ще ни интересуват само няколко инструкции:
  • 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_expression).
Функцията за създаване на празен адресен израз връща неговия дескриптор или 0 при грешка:
Dwarf_Expr dwarf_new_expr (Dwarf_P_Debug dbg, Dwarf_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 * грешка)
  • opcode - код на операцията, константа DW_OP_хххх
  • val1, val2 - параметри на инструкциите (вижте Стандарт)

За да зададете изрично адреса на променлива вместо предишната, трябва да се използва функцията dwarf_add_expr_addr:
Dwarf_Unsigned dwarf_add_expr_addr (Dwarf_P_Expr expr, Dwarf_Unsigned адрес, Dwarf_Signed sym_index, Dwarf_Error * грешка)
  • expr е манипулатор на адресния израз, към който е добавен изразът
  • адрес - адресът на променливата
  • sym_index - индекс на символа в таблицата .symtab. По избор можете да предадете 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 * грешка)
  • 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 * грешка)
превежда информацията за отстраняване на грешки, която създадохме, в двоичен формат, но не записва нищо на диска. Той ще ни върне броя на създадените елф секции или DW_DLV_NOCOUNT при грешка. В този случай за всеки раздел ще бъде извикана функцията за обратно извикване, която предадохме по време на инициализацията на библиотеката на функцията dwarf_producer_init_c. Трябва сами да напишем тази функция. Неговата спецификация е както следва:
typedef int (* Dwarf_Callback_Func_c) (char * име, int размер, Dwarf_Unsigned тип, Dwarf_Unsigned флагове, Dwarf_Unsigned връзка, Dwarf_Unsigned информация, Dwarf_Unsigned * sect_name_index, void * user_data, в
  • name - името на секцията elf, която трябва да се създаде
  • size - размер на секцията
  • type - типът на секцията
  • флагове - знамена на секцията
  • връзка - поле за връзка към раздел
  • info - информационно поле на раздела
  • sect_name_index - трябва да върнете индекса на секцията с премествания (по избор)
  • user_data - предава ни същото, както сме го задали във функцията за инициализация на библиотеката
  • грешка - можете да предадете кода за грешка тук
В тази функция трябва:
  • създайте нов раздел (функция elf_newscn, вижте Създаване на секции)
  • създайте заглавка на раздел (функция elf32_getshdr, пак там.)
  • попълнете го правилно (виж пак там). Лесно е, защото полетата на заглавката на раздела съответстват на параметрите на нашата функция. Липсващите полета sh_addr, sh_offset, sh_entsize са зададени на 0, а sh_addralign на 1
  • върнете индекса на създадената секция (функция elf_ndxscn, вижте "Section.symtab") или -1 при грешка (задаване на кода за грешка на грешка)
  • също така трябва да пропуснем секцията ".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 * дължина, Dwarf_Error * грешка)
    • dwarf_section - номер на раздел. Трябва да е в диапазона 0..n, където n е числото, върнато ни от функцията dwarf_transform_to_disk_form
    • elf_section_index - Връща индекса на секцията, в която трябва да бъдат записани данните
    • дължина - дължината на тези данни
    • грешка - не се използва
    Функцията връща указател към получените данни или 0 (в случай че
    когато няма повече секции за създаване)
  • създайте дескриптор на данни за текущата секция (функция elf_newdata, вижте Създаване на секции) и го попълнете (вижте пак там), като зададете:
    • d_buf - указател към данните, които получихме от предишната функция
    • d_size - размерът на тези данни (пак там)
Завършване на работата с библиотеката
След като оформите секциите, можете да завършите работата с libdwarf, като използвате функцията dwarf_producer_finish:
Dwarf_Unsigned dwarf_producer_finish (Dwarf_P_Debug dbg, Dwarf_Error * грешка)
Функцията връща DW_DLV_NOCOUNT при грешка.
Имайте предвид, че на този етап няма запис на диск. Записването трябва да се извърши с помощта на функциите от секцията "Създаване на ELF - Запис на файл".

Заключение

Това е всичко.
Още веднъж, създаването на информация за отстраняване на грешки е много широка тема и не засегнах много теми, а само отворих завесата. Желаещите могат да се ровят в безкрайността.
Ако имате въпроси, ще се опитам да им отговоря.

В този преглед ще говорим само за 32-битовата версия на този формат, тъй като все още не се нуждаем от 64-битова версия.

Всеки ELF файл (включително обектни модули от този формат) се състои от следните части:

  • ELF заглавка на файла;
  • Таблица на програмни раздели (може да отсъства в обектните модули);
  • ELF файлови секции;
  • Таблица на разделите (може да отсъства в изпълнимия модул);
  • От съображения за производителност ELF не използва битови полета. И всички структури обикновено са подравнени по 4 байта.

Сега нека да разгледаме типовете, използвани в заглавките на ELF файлове:

Сега нека разгледаме заглавката на файла:

# определят EI_NIDENT 16 структура elf32_hdr (неподписан овъгляване 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;);

Масивът e_ident съдържа информация за системата и се състои от няколко подполета.

Структура (unsigned char ei_magic; unsigned char ei_class; unsigned char ei_data; unsigned char ei_version; unsigned char ei_pad;)

  • ei_magic - постоянна стойност за всички ELF файлове, равна на (0x7f, "E", "L", "F")
  • ei_class - класът на ELF файла (1 - 32 бита, 2 - 64 бита, които не разглеждаме)
  • ei_data – Определя реда на байтовете за този файл (този ред зависи от платформата и може да бъде напред (LSB или 1) или обратен (MSB или 2)) За процесори на Intel е разрешено само 1.
  • ei_version е доста безполезно поле и ако не е равно на 1 (EV_CURRENT), тогава файлът се счита за невалиден.

Операционните системи съхраняват своите идентификационни данни в полето ei_pad. Това поле може да е празно. За нас също няма значение.

Полето на заглавката e_type може да съдържа множество стойности, за изпълними файлове трябва да е ET_EXEC равно на 2

e_machine - определя процесора, на който може да работи този изпълним файл (За нас стойността EM_386, равна на 3, е приемлива)

Полето e_version съответства на полето ei_version от заглавката.

Полето e_entry дефинира началния адрес на програмата, който се поставя в eip преди стартиране на програмата.

Полето e_phoff дефинира отместването от началото на файла, в който се намира таблицата на програмните секции, което се използва за зареждане на програми в паметта.

Няма да изброявам предназначението на всички полета, не всички са необходими за зареждане. Ще опиша само още две.

Полето e_phentsize дефинира размера на записа в таблицата на секциите на програмата.

И полето e_phnum дефинира броя на записите в таблицата на разделите на програмата.

Таблицата на разделите (не софтуер) се използва за свързване на програми. няма да го разглеждаме. Също така няма да разглеждаме динамично свързани модули. Тази тема е доста сложна, не е подходяща за първо запознанство. :)

Сега за секциите на програмата. Форматът на запис на таблицата с разделите на програмата е както следва:

Структурирайте elf32_phdr (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 Elf32_Word p_W_)

Научете повече за полетата.

  • p_type - дефинира типа на секцията на програмата. Може да приеме няколко стойности, но ние се интересуваме само от една. PT_LOAD (1). Ако секцията е от този тип, тогава тя е предназначена да бъде заредена в паметта.
  • p_offset - дефинира отместването във файла, от който започва този раздел.
  • p_vaddr - дефинира виртуалния адрес, на който този раздел трябва да бъде зареден в паметта.
  • p_paddr - дефинира физическия адрес, където трябва да се зареди тази секция. Това поле не е задължително да се използва и има смисъл само на някои платформи.
  • p_filesz - определя размера на секцията във файла.
  • p_memsz - Определя размера на секцията в паметта. Тази стойност може да е по-висока от предишната. Полето p_flag определя типа достъп до секции в паметта. Някои секции могат да се изпълняват, други да се записват. Всички са четими в съществуващи системи.

Зарежда се формат ELF.

Разбрахме малко заглавието. Сега ще дам алгоритъм за зареждане на ELF двоичен файл. Алгоритъмът е схематичен, не трябва да го разглеждате като работеща програма.

Int LoadELF (неподписан char * bin) (struct elf32_hdr * EH = (struct elf32_hdr *) bin; struct elf32_phdr * EPH; if (EH-> e_ident! = 0x7f || // Контролиране на MAGIC EH-> e_ident! = "E" ident!) || EH-> e_ident! = "L" || EH-> e_ident! = "F" || EH-> e_ident! = ELFCLASS32 || // Контролиране на класа EH-> e_ident! = ELFDATA2LSB || // байт поръчка EH-> e_ident! = EV_CURRENT || // версия EH-> e_type! = ET_EXEC || // тип EH-> e_machine! = EM_386 || // платформа EH-> e_version! = EV_CURRENT) // и отново версия , за всеки случай върнете ELF_WRONG; EPH = (struct elf32_phdr *) (bin + EH-> e_phoff); докато (EH-> e_phnum--) (ако (EPH-> p_type == PT_LOAD) memcpy (EPH-> p_vaddr, bin + EPH-> p_offset, EPH-> p_filesz); EPH = (struct elf32_phdr *) ((unsigned char *) EPH + EH-> e_phentsize));) връщане ELF_OK; )

Сериозно, също така си струва да анализирате полетата EPH-> p_flags и да поставите права за достъп на съответните страници, а простото копиране няма да работи тук, но това не се отнася за формата, а за разпределението на паметта. Затова сега няма да говорим за това.

PE формат.

В много отношения той е подобен на формата ELF, добре, не е изненадващо, че там също трябва да има секции за изтегляне.

Както всичко в Microsoft :), PE форматът е базиран на EXE формата. Структурата на файла е както следва:

  • 00h - EXE хедър (няма да го разглеждам, стар е като Dos. :)
  • 20h - OEM заглавка (в нея няма нищо съществено);
  • 3сh - изместване на реалната PE заглавка във файла (dword).
  • маса за преместване;
  • мъниче;
  • PE заглавка;
  • маса с предмети;
  • файлови обекти;

stub е програма в реално време, която извършва някаква предварителна работа. Може да отсъства, но понякога може да се наложи.

Интересува ни нещо друго, рубриката PE.

Структурата му е както следва:

Структура pe_hdr (unsigned long pe_sign; unsigned short pe_cputpe; unsigned short pe_objnum; unsigned long pe_time; unsigned long pe_cofftbl_off; unsigned long pe_cofftbl_size; unsigned short pe_nthdr_size; unsigned short pe_nthdr_size; unsigned short peigned_flags pe_nthdr_size; unsigned short peigned_flags pef long unsigned long unsigned long unsigned long unsigned_flags; pe_code_base; unsigned long pe_data_base; unsigned long pe_image_base; unsigned long pe_obj_align; unsigned long pe_file_align; // ... добре, и много други неща, които нямат значение.);

Там има много неща. Достатъчно е да се каже, че размерът на този хедър е 248 байта.

И основното е, че повечето от тези полета не се използват. (Кой строи така?) Не, те, разбира се, имат добре познато предназначение, но моята тестова програма, например, съдържа нули в полетата pe_code_base, pe_code_size и т.н., но работи чудесно. Изводът се навежда на мисълта, че файлът се зарежда на базата на таблицата с обекти. Тук ще говорим за нея.

Таблицата с обекти следва непосредствено след PE заглавката. Записите в тази таблица имат следния формат:

Структура pe_ohdr (unsigned char o_name; unsigned long o_vsize; unsigned long o_vaddr; unsigned long o_psize; unsigned long o_poff; unsigned char o_reserved; unsigned long o_flags;);

  • o_name - име на раздел, абсолютно безразличен за зареждане;
  • o_vsize - размер на раздела в паметта;
  • o_vaddr - адрес на паметта спрямо ImageBase;
  • o_psize - размер на раздела във файла;
  • o_poff - отместване на секцията във файла;
  • o_flags - флагове на секции;

Струва си да се спрем на знамената по-подробно.

  • 00000004h - използва се за код с 16 битови отмествания
  • 00000020h - кодов раздел
  • 00000040h - раздел с инициализирани данни
  • 00000080h - раздел с неинициализирани данни
  • 00000200h - коментари или всякакъв друг вид информация
  • 00000400h - секция за наслагване
  • 00000800h - няма да бъде част от изображението на програмата
  • 00001000h - общи данни
  • 00500000h - подравняване по подразбиране, освен ако не е отбелязано друго
  • 02000000h - може да се разтовари от паметта
  • 04000000h - не е кеширано
  • 08000000h - не е оповестено
  • 10000000h - споделено
  • 20000000h - изпълнимо
  • 40000000h - може да се чете
  • 80000000h - можете да пишете

Отново няма да бъда със споделени и наслагващи се раздели, интересуваме се от код, данни и права за достъп.

Като цяло тази информация вече е достатъчна за изтегляне на двоичния файл.

Зарежда се PE формат.

int LoadPE (unsigned char * bin) (struct elf32_hdr * PH = (struct pe_hdr *) (bin + * ((unsigned long *) & bin)); // Разбира се, комбинацията не е ясна ... просто вземете dword на отместване 0x3c // И изчислете адреса на PE заглавката в изображението на файла struct elf32_phdr * POH; if (PH == NULL || // Контролиране на показалеца PH-> pe_sign! = 0x4550 || // подпис PE ( "P", "E", 0, 0) PH-> pe_cputpe! = 0x14c || // i386 (PH-> pe_flags & 2) == 0) // файлът не може да се стартира! Върнете PE_WRONG; POH = (struct pe_ohdr *) ((неподписан char *) PH + 0xf8); докато (PH-> pe_obj_num--) (ако ((POH-> p_flags & 0x60)! = 0) // или код, или инициализирани данни memcpy (PE-> pe_image_base + POH-> o_vaddr, bin + POH- > o_poff, POH-> o_psize); POH = (struct pe_ohdr *) ((unsigned char *) POH + sizeof (struct pe_ohdr));) връщане PE_OK;)

Отново, това не е готова програма, а алгоритъм за зареждане.

И отново, много точки не са обхванати, тъй като те излизат извън рамките на темата.

Но сега си струва да поговорим малко за съществуващите системни функции.

Характеристики на системата.

Въпреки гъвкавостта на наличните в процесорите инструменти за защита (защита на ниво дескрипторни таблици, защита на ниво сегмент, защита на ниво страница) в съществуващите системи (както в Windows, така и в Unix), само защитата на страницата се използва напълно, който, въпреки че може да спаси код от писане, но не може да запази данни от изпълнение. (Може би това е причината за изобилието от системни уязвимости?)

Всички сегменти се адресират от линеен адрес нула и се простират до края на линейната памет. Разграничаването на процесите се извършва само на ниво таблици на страници.

В тази връзка всички модули не са свързани от началните адреси, а с достатъчно голямо изместване в сегмента. В Windows основният адрес в сегмента е 0x400000, в Unix (Linux или FreeBSD) - 0x8048000.

Някои функции също са свързани с паметта за пейджинг.

ELF файловете са свързани по такъв начин, че границите и размерите на секциите попадат върху 4-килобайтни файлови блокове.

И във формат PE, въпреки факта, че самият формат позволява подравняване на секции с 512 байта, подравняването на секциите е 4k, по-малкото подравняване не се счита за правилно в Windows.