Енциклопедија за заштита од пожари

Како да ја отворите датотеката елф во Windows. Што е наставката на датотеката .ELF? Јазли кои опишуваат податоци

Стандардните алатки за развој ја компајлираат вашата програма во датотека ELF (извршен и поврзувачки формат) со опција за вклучување информации за дебагирање. Спецификацијата на форматот може да се прочита. Дополнително, секоја архитектура има свои карактеристики, како оние на АРМ. Ајде брзо да го разгледаме овој формат.
Извршна датотека со формат ELF се состои од следниве делови:
1. Заглавие (ELF заглавие)
Содржи општи информации за датотеката и нејзините главни карактеристики.
2. Табела со заглавие на програмата
Ова е табела на кореспонденција на секциите на датотеки со сегментите на меморијата, му кажува на натоварувачот во која област на меморија да го запише секој дел.
3. Секции
Секциите ги содржат сите информации во датотеката (програма, податоци, информации за отстранување грешки, итн.)
Секој дел има тип, име и други параметри. Во делот „.text“ обично се складира кодот, „.symtab“ - табела со симболи на програмата (имиња на датотеки, процедури и променливи), „.strtab“ - табела со низи, делови со префикс „.debug_“ - информации за дебагирање итн. .d. Покрај тоа, датотеката мора да има празен дел со индекс 0.
4. Табела со заглавие на делот
Ова е табела која содржи низа од заглавија на секции.
Форматот е подетално дискутиран во делот Креирање ELF.

Преглед на џуџе

DWARF е стандардизиран формат на информации за отстранување грешки. Стандардот може да се преземе од официјалната веб-страница. Има и одличен преглед на форматот: Вовед во форматот за дебагирање DWARF (Мајкл Ј. Ејџер).
Зошто се потребни информации за отстранување грешки? Тоа овозможува:
  • поставете точки на прекин не на физичка адреса, туку на број на линија во датотека со изворен код или на име на функција
  • прикажување и менување на вредностите на глобалните и локалните променливи, како и функционалните параметри
  • прикажување на стек на повици (позадинско следење)
  • извршете ја програмата чекор по чекор не со една инструкција за склопување, туку со линии на изворниот код
Оваа информација е зачувана во структура на дрво. Секој јазол на дрво има родител, може да има деца и се нарекува Внес на информации за дебагирање (DIE). Секој јазол има своја ознака (тип) и листа на атрибути (својства) кои го опишуваат јазолот. Атрибутите може да содржат што било, како што се податоци или врски до други јазли. Покрај тоа, има информации зачувани надвор од дрвото.
Јазлите се поделени на два главни типа: јазли кои опишуваат податоци и јазли кои опишуваат код.
Јазли кои ги опишуваат податоците:
  1. Типови на податоци:
    • Основни типови на податоци (јазол со тип DW_TAG_base_type), како што е типот int во C.
    • Композитни типови на податоци (покажувачи, итн.)
    • Низи
    • Структури, класи, синдикати, интерфејси
  2. Објекти на податоци:
    • константи
    • функционални параметри
    • променливи
    • итн.
Секој податочен објект има атрибут DW_AT_location кој одредува како се пресметува адресата каде што се наоѓаат податоците. На пример, променливата може да има фиксна адреса, да биде во регистар или во стек, да биде член на класа или објект. Оваа адреса може да се пресмета на прилично комплициран начин, така што стандардот предвидува т.н. Локациски изрази, кои можат да содржат низа изјави од специјална внатрешна машина за стек.
Јазли кои го опишуваат кодот:
  1. Процедури (функции) - јазли со ознака DW_TAG_подпрограма. Јазлите од потомството може да содржат описи на променливи - параметри на функции и локални променливи на функции.
  2. Единица за составување. Содржи информации за програмата и е родител на сите други јазли.
Информациите опишани погоре се наоѓаат во секциите „.debug_info“ и „.debug_abbrev“.
Други информации:
  • Информации за броевите на линиите (дел „debug_line“)
  • Макро информации (дел „debug_macinfo“)
  • Информации за рамката за повици (дел „.debug_frame“)

Создавање ELF

Ќе креираме EFL-датотеки користејќи ја библиотеката libelf од пакетот elfutils. Има добра статија на интернет за користење на libelf - LibELF by Example (за жал, креирањето датотеки е опишано многу кратко во него) како и документација.
Создавањето датотека се состои од неколку чекори:
  1. иницијализација на libelf
  2. Креирање заглавие на датотека (ELF заглавие)
  3. Креирање на табела со заглавие на програмата
  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 во нашиот случај)
  • елф - потребен е само за работа со архивски датотеки (.a), во нашиот случај, треба да поминете 0
Функцијата враќа покажувач на генерираната рачка што ќе се користи во сите функции на клеветење, 0 се враќа по грешка.
Направете заглавие
Новото заглавие на датотеката е креирано од функцијата elf32_newehdr:
Elf32_Ehdr * elf32_newehdr(Елф *елф);
  • elf - рачката вратена од функцијата elf_begin
Враќа 0 при грешка или покажувач на структура - заглавие на датотеката ELF:
#define EI_NIDENT 16 typedef struct (непотпишана знак 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(Елф *елф, број на големина_t);
  • елф - нашата рачка
  • 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_memsz; Elf32_Word p_flfign;
  • p_type - тип на сегмент (дел), тука мора да наведеме PT_LOAD - сегмент што може да се вчита
  • p_offset - поместувања во датотеката од каде што започнуваат податоците на делот што ќе се вчита во меморијата. Имаме дел .text, кој ќе се наоѓа веднаш по заглавието на датотеката и заглавието на програмата, можеме да го пресметаме поместувањето како збир на должините на овие заглавија. Должината од кој било тип може да се добие со помош на функцијата elf32_fsize:
    size_t elf32_fsize (Тип на Elf_Type, број на size_t, непотпишана int верзија); тип - тука е константата ELF_T_xxx, ќе ни требаат големини 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_begin
Функцијата враќа покажувач на секција или 0 на грешка.
Откако ќе креирате дел, треба да го пополните заглавието на секцијата и да креирате дескриптор за податоци на делот.
Можеме да добиеме покажувач до заглавието на секцијата користејќи ја функцијата elf32_getshdr:
Elf32_Shdr * elf32_getshdr(Elf_Scn *scn);
  • scn е покажувачот на секцијата што го добивме од функцијата elf_newscn.
Заглавието на делот изгледа вака:
структура
  • 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
Специјални делови
За нашите цели, ќе треба да го создадеме минималниот неопходен сет на делови:
  • .текст - дел со програмски код
  • .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 - име на симбол (индекс во табела со низи.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 - индекс на делот за кој е дефиниран симболот (секција индекс.текст), или SHN_ABS за датотеката.
    Индексот на делот според неговата scn рачка може да се одреди со користење на elf_ndxscn:
    size_t elf_ndxscn(Elf_Scn *scn);

Овој дел е креиран на вообичаен начин, само sh_type треба да се постави на SHT_SYMTAB, а индексот section.strtab треба да се запише во полето sh_link, така што овие делови стануваат поврзани.
Дел.strtab
Овој дел ги содржи имињата на сите симболи од делот .symtab. Создаден како обичен дел, но sh_type треба да се постави на SHT_STRTAB, sh_flags на SHF_STRINGS, така што овој дел станува табела со низа.
Податоците за делот може да се соберат кога се поминува низ изворниот текст во низа, чиј покажувач потоа се запишува на дескрипторот за податоци на секцијата (d_buf).
Дел.shstrtab
Секција - табела со низи, ги содржи заглавијата на сите делови од датотеката, вклучувајќи го и сопственото заглавие. Се создава на ист начин како и делот .strtab. По креирањето, неговиот индекс мора да биде напишан во полето e_shstrndx на заглавието на датотеката.
Низични маси
Табелите со низи содржат последователни низи што завршуваат со нула бајт, првиот бајт во таа табела исто така мора да биде 0. Индексот на редови во табелата е едноставно поместување во бајти од почетокот на табелата, така што првата низа „име“ има индекс 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
Напишете датотека
Значи, заглавијата и секциите се веќе формирани, сега треба да се запишат во датотека и да се завршат со клевета. Запишувањето се врши со функцијата 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 flags, Dwarf_Callback_Func_c func, Dwarf_Handler errhand, Dwarf_Ptr error, void * user_data, Dwarf_Error *грешка)

  • знаменца - комбинација од „или“ неколку константи кои дефинираат некои параметри, на пример, длабочината на информациите, редоследот на бајти (малку-ендијански, голем-ендијански), формат на преместување, од кои дефинитивно ни требаат 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_P_Die right_sibling, Dwarf_)
  • new_tag - јазол ознака (тип) - константа DW_TAG_xxxx, што може да се најде во датотеката libdwarf.h
  • родител, дете, лево_брат, десно_брат - соодветно родител, дете, лев и десен сосед на јазолот. Не е неопходно да се наведат сите овие параметри, доволно е да наведете еден, наместо останатите ставете 0. Ако сите параметри се 0, јазолот ќе биде или корен или изолиран
  • грешка - ќе го содржи кодот за грешка кога ќе се појави
Функцијата враќа DW_DLV_BADADDR при неуспех или рачка на јазол Dwarf_P_Die по успех
Креирање на атрибути на јазли
Постои цела фамилија на dwarf_add_AT_xxxx функции за креирање атрибути на јазли. Понекогаш е проблематично да се одреди која функција треба да го создаде потребниот атрибут, па дури неколку пати копав во изворниот код на библиотеката. Некои од функциите ќе бидат опишани овде, некои подолу - во соодветните делови. Сите тие земаат свој параметар, рачка на јазолот на кој ќе се додаде атрибутот и враќаат код за грешка во параметарот за грешка.
Функцијата dwarf_add_AT_name го додава атрибутот „име“ (DW_AT_name) на јазол. Повеќето јазли мора да имаат име (на пример, процедури, променливи, константи), некои може да немаат име (на пример, Единица за компилација)
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 milkdie, Dwarf_Half attr, Dwarf_Signed вредност, Dwarf_Error *грешка)
  • dbg - Dwarf_P_Debug дескриптор примен при иницијализација на библиотеката
  • attr - атрибутот чија вредност е поставена - константата DW_AT_xxxx, која може да се најде во датотеката libdwarf.h
  • вредност - вредноста на атрибутот
Врати DW_DLV_BADADDR на грешка или рачка за атрибут за успех.
Креирање на единица за компилација
Секое дрво мора да има корен - во нашиот случај тоа е компилациона единица која содржи информации за програмата (на пример, името на главната датотека, користениот програмски јазик, името на компајлерот, чувствителноста на знаците (променливи, функции) до букви, главната функција на програмата, почетната адреса итн.) итн.). Во принцип, не се потребни никакви атрибути. На пример, ајде да создадеме информации за главната датотека и компајлерот.
Информации за главната датотека
Атрибутот име (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 *грешка)
  • 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 се записи кои можат да бидат од два вида: Заеднички внес на информации (CIE) и Внес на опис на рамка (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_Urrors_bytes, Dwarf_Ptr init_Urrorfigns, Dwarf_Ptr init_Urrors_bytes;
  • augmenter - UTF-8 кодирана низа, чие присуство покажува дека има дополнителни информации специфични за платформата за CIE или FDE. Ставете празна линија
  • code_align - порамнување на кодот во бајти (имаме 2)
  • data_align - порамнување на податоците во рамката (поставете -4, што значи дека сите параметри земаат 4 бајти на оџакот и се зголемуваат во меморијата)
  • ret_addr_reg - регистар кој ја содржи повратната адреса од постапката (имаме 14)
  • init_bytes - низа која содржи DW_CFA_xxxx инструкции. За жал, не постои пригоден начин за генерирање на оваа низа. Можете да го формирате рачно или да го ѕирнете во датотеката елф што е генерирана од компајлерот 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 мора да содржи референца за претходно креираниот тип "int" во атрибутот DW_AT_type.
Атрибут со упатување на друг јазол е креиран од функцијата 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 length, 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_подпрограма како ознака и единицата за компилација (ако ова е глобална процедура) или соодветната 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, *error_Error)
  • 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 kod_warm_Error, Dwarf_Unsigned sywarm_Uidx, Dwarf_P_Fde, Dwarf_P_Die,
  • fde - рачката штотуку ја прими
  • die - Постапка DIE (види Креирање јазол за процедура со атрибути)
  • cie - CIE дескриптор (видете Креирање заеднички внес на информации)
  • 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 *грешка)
  • 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 Иса, Dwarf_Unsigned дискриминатор, Dwarf_Error * грешка)
  • file_index - индекс на датотеката со изворниот код добиен претходно со функцијата dwarf_add_file_decl (видете „Процедури за создавање“)
  • code_offset - адреса на тековната инструкција на машината
  • lineno - бројот на линијата во датотеката со изворниот код
  • колона_број - број на колона во датотеката со изворниот код
  • 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 address; Dwarf_Error *грешка)
  • адреса - адреса на тековната инструкција на машината
Враќа 0 (успешно) или DW_DLV_NOCOUNT (грешка).
Ова го комплетира создавањето на постапката.
Креирање на променливи и константи
Општо земено, променливите се прилично едноставни. Тие имаат име, мемориска локација (или процесорски регистар) каде што се наоѓаат нивните податоци и видот на тие податоци. Ако променливата е глобална - нејзиниот родител треба да биде Compilation Unit, ако е локален - соодветниот јазол (ова особено важи за параметрите на процедурата, тие мора да ја имаат самата процедура како родител). Можете исто така да одредите во која датотека, линија и колона се наоѓа декларацијата на променливата.
Во наједноставниот случај, вредноста на променливата се наоѓа на некоја фиксна адреса, но многу променливи се динамично креирани при внесување на процедурата на стекот или регистарот, понекогаш пресметувањето на адресата на вредноста може да биде сосема нетривијално. Стандардот обезбедува механизам за опишување каде се наоѓа вредноста на променливата - локациски изрази. Адресниот израз е збир на инструкции (DW_OP_xxxx константи) за машина за стек слична на четврто, всушност тоа е посебен јазик со гранки, процедури и аритметички операции. Ние нема да го разгледаме овој јазик во целост, всушност ќе нè интересираат само неколку упатства:
  • 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, видете „Креирање атрибути на јазли“)
Креирање на делови со информации за отстранување грешки
Откако ќе ги креирате сите јазли на дрвото за информации за отстранување грешки, можете да започнете да формирате делови од елф со него. Ова се случува во две фази:
  • прво треба да ја повикате функцијата dwarf_transform_to_disk_form, која ќе ја повика функцијата што ја напишавме за да ги креираме саканите елф-секции еднаш за секој дел
  • за секој дел, функцијата 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)(знак* име, int големина, Dwarf_Unsigned тип, Dwarf_Unsigned знаменца, Dwarf_Unsigned линк, Dwarf_Unsigned информации, Dwarf_Unsigned* секта_име_индекс, неважечки * кориснички_податоци),
  • име - името на делот елф што треба да се креира
  • големина - големина на делот
  • тип - тип на дел
  • знамиња - знамиња на секции
  • врска - поле за врска со дел
  • информации - поле за информации за делот
  • секта_име_индекс - треба да го вратите индексот на делот со преместувања (по избор)
  • 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 - големината на овие податоци (ibid.)
Крај на работата со библиотеката
Откако ќе ги формирате деловите, можете да ја завршите работата со libdwarf со функцијата dwarf_producer_finish:
Dwarf_Unsigned dwarf_producer_finish(Dwarf_P_Debug dbg, Dwarf_Error* грешка)
Функцијата враќа DW_DLV_NOCOUNT при грешка.
Забележувам дека пишувањето на диск во оваа фаза не се изведува. Снимањето мора да се направи со користење на функциите од делот „Креирање ELF - пишување датотека“.

Заклучок

Тоа е се.
Повторувам, создавањето информации за дебагирање е многу опширна тема и не допрев многу теми, само го отворив превезот. Оние кои сакаат можат да одат длабоко во бесконечноста.
Ако имате прашања, ќе се обидам да одговорам на нив.

ELF формат

Форматот ELF има неколку типови на датотеки на кои досега различно се осврнавме, како што е извршна датотека или објектна датотека. Сепак, стандардот ELF прави разлика помеѓу следниве типови:

1. Датотеката се преместува(датотека што може да се премести) која складира инструкции и податоци што може да се поврзат со други датотеки со објекти. Резултатот од таквото поврзување може да биде извршна датотека или споделена датотека со објект.

2. Заедничка датотека со објект(датотека за споделени објекти) исто така содржи инструкции и податоци, но може да се користи на два начина. Во првиот случај, може да се поврзе со други датотеки што може да се преместат и со споделени датотеки со објекти, што резултира со креирање на нова датотека со објекти. Во вториот случај, кога програмата е стартувана за извршување, оперативниот систем може динамички да ја поврзе со извршната датотека на програмата, како резултат на што ќе се создаде извршна слика на програмата. Во вториот случај, зборуваме за споделени библиотеки.

3. Извршнаскладира целосен опис што му овозможува на системот да создаде слика на процесот. Содржи инструкции, податоци, опис на потребните датотеки со споделени објекти и потребните симболични информации и информации за дебагирање.

На сл. 2.4 ја прикажува структурата на извршната датотека, со која оперативниот систем може да креира програмска слика и да ја изврши програмата за извршување.

Ориз. 2.4. Структурата на извршната датотека во формат ELF

Заглавието има фиксна локација во датотеката. Останатите компоненти се поставени според информациите зачувани во заглавието. Така, заглавието содржи општ опис на структурата на датотеката, локацијата на поединечните компоненти и нивните големини.

Бидејќи заглавието на датотеката ELF ја дефинира нејзината структура, ајде да го разгледаме подетално (Табела 2.4).

Табела 2.3. Полиња за заглавие на ELF

Поле Опис
e_ident Низа од бајти, од кои секој дефинира некои општи карактеристики на датотеката: формат на датотека (ELF), број на верзија, архитектура на системот (32-битна или 64-битна) итн.
е_тип Типот на датотека како формат ELF поддржува повеќе типови
е_машина Архитектурата на хардверската платформа за која е создадена оваа датотека. Во табелата. 2.4 ги прикажува можните вредности на ова поле
е_верзија Број на верзија на форматот ELF. Обично се дефинира како EV_CURRENC (тековно), што значи најнова верзија
е_влез Виртуелна адреса на која системот ќе ја пренесе контролата по вчитувањето на програмата (влезна точка)
e_phoff Локација (поместување од почетокот на датотеката) на табелата за заглавие на програмата
e_shoff Локација на табелата за заглавие на делот
e_ehsize Големина на заглавието
e_phentsize Големина на секое заглавие на програмата
e_phnum Број на наслови на програмата
e_shentsize Големина на заглавието на секој сегмент (дел).
е_шнум Број на наслови на сегменти (делови).
e_shstrndx Локацијата на сегментот што ја содржи стрингната табела

Табела 2.4. Вредности на полето e_machine на заглавието на датотеката ELF

Значење Хардверска платформа
EM_M32 AT&T WE 32100
EM_SPARC Sun SPARC
EM_386 Интел 80386
EM_68K Моторола 68000
EM_88K Моторола 88000
EM_486 Интел 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 Sun 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 (Portable Document Format). Овој формат е создаден специјално за да се елиминираат проблемите со прикажувањето на информациите во датотеките. Неговата предност е што, прво, документот зачуван во PDF формат ќе биде ист

Од книгата TCP/IP Architecture, Protocols, Implementation (вклучувајќи IP верзија 6 и IP безбедност) авторот Фејт Сидни М

Формат на датотека Кога корисникот ќе започне да работи со датотека, системот треба да знае во кој формат е напишана и со која програма треба да се отвори. На пример, ако датотеката содржи обичен текст, тогаш таа може да се чита во која било текстуална програма

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

5.2.2. Формат RFC 822 Метаформатот RFC 822 е изведен од текстуалниот формат на интернет-мејл пораките. RFC 822 е главниот интернет RFC стандард кој го опишува овој формат (подоцна заменет со RFC 2822). Формат MIME (Multipurpose Internet Media Extension).

Од Macromedia Flash Professional 8. Графика и анимација автор Дронов В.А.

5.2.3. Формат на колачиња Форматот на колачиња-тегла го користи fortune(1) за сопствена база на податоци со случајни цитати. Погоден е за записи кои се едноставно блокови од неструктуриран текст. Раздвојувачот на записите во овој формат е знакот

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

5.2.4. Формат на рекорд-тегла Разграничувачите на записи за колачиња добро се вклопуваат со метаформатот RFC 822 за записи што го формираат форматот наведен во оваа книга како „тегла за записи“. Понекогаш е потребен формат на текст кој поддржува повеќе записи со различен сет на експлицитни имиња

Од книгата 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. Моментално е најпопуларниот формат за дистрибуција на музика преку Интернет и пошироко. Поддржан е од апсолутно сите програми за снимање и обработка на звук, за

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

Формат на MP3 Методот за компресија на аудио, како и форматот на компресирани аудио датотеки, предложен од меѓународната организација MPEG (Moving Pictures Experts Group - Video Recording Experts Group), се заснова на перцептивно аудио кодирање. Работете на создавање ефикасни алгоритми за кодирање

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

Формат ELF Форматот ELF има неколку типови на датотеки што досега ги нарекувавме поинаку, како што е извршна датотека или датотека со објект. Меѓутоа, стандардот ELF прави разлика помеѓу следниве типови:1. Датотека што може да се премести што содржи инструкции и податоци што можат да бидат

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

Формат на броеви Конечно стигнавме до форматот на броеви. Веќе го спомнав повеќе од еднаш, сега ќе ставам сè на полиците (иако веќе можевте да го разберете општото значење) Броевите во Excel може да се прикажуваат во различни формати. Во овој дел, ќе зборуваме за тоа кои формати на броеви постојат и како

Во овој преглед, ќе зборуваме само за 32-битната верзија на овој формат, бидејќи сè уште не ни е потребна 64-битната верзија.

Секоја датотека ELF (вклучувајќи ги и објектните модули од овој формат) се состои од следниве делови:

  • Заглавие на датотеката ELF;
  • Табела со програмски секции (може да отсуствува во објектните модули);
  • Секции од датотеката ELF;
  • Табела со секции (може да не е присутна во извршниот модул);
  • Од причини за изведба, ELF форматот не користи битови полиња. И сите структури обично се порамнети со 4 бајти.

Сега да ги погледнеме типовите што се користат во заглавијата на датотеките ELF:

Сега разгледајте го заглавието на датотеката:

#define EI_NIDENT 16 struct 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 содржи информации за системот и се состои од неколку подполиња.

Структ (непотпишан char ei_magic; непотпишан char ei_class; непотпишан char ei_data; непотпишан char ei_version; непотпишан 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; Elf_3_Word p_memsz; Elf_3_Word p_memsz; Elf_3_Word p_memsz;

Повеќе за полињата.

  • 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 || // Control MAGIC EH->e_ident != "E" || 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 *)((непотпишан знак *)EPH + EH->e_phentsize)); ) врати ELF_OK; )

Како сериозна забелешка, вреди да се анализираат полињата EPH->p_flags и да се постават правата за пристап до соодветните страници, а едноставното копирање нема да работи овде, но ова повеќе не важи за форматот, туку за распределбата на меморијата. Затоа, сега нема да зборуваме за тоа.

PE формат.

На многу начини, тој е сличен на форматот ELF, и не е изненадувачки што треба да има и делови достапни за преземање.

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

  • 00h - заглавие EXE (нема да го земам предвид, старо е како Dos. :)
  • 20h - OEM заглавие (ништо значајно во него);
  • 3ch - вистинско поместување на заглавието на PE во датотеката (dword).
  • маса за движење на никулци;
  • никулец;
  • Заглавие на PE;
  • маса за предмети;
  • предмети на датотека;

никулец е програма која работи во реален режим и врши некоја прелиминарна работа. Можеби не е достапно, но понекогаш може да биде потребно.

Ние сме заинтересирани за нешто друго, заглавјето на ЈП.

Неговата структура е вака:

Struct pe_hdr (непотпишана долго pe_sign; непотпишана кратки pe_cputype; непотпишана кратки pe_objnum; непотпишана долго pe_time; непотпишана долго pe_cofftbl_off; непотпишана долго pe_cofftbl_size; непотпишана кратки pe_nthdr_size; непотпишана кратки pe_flags; непотпишана кратки pe_magic; непотпишана кратки pe_link_peverdata_code; непотпишана long_size;; непотпишана долго pe_udata_size ; непотпишан долг pe_entry; непотпишан долг pe_code_base; непотпишан долг pe_data_base; непотпишан долг pe_image_base; непотпишан долг pe_obj_align; непотпишан долг pe_file_align; // ... добро, и многу други работи, неважни. );

Има многу работи. Доволно е да се каже дека големината на ова заглавие е 248 бајти.

И главната работа е што повеќето од овие полиња не се користат. (Кој гради така?) Не, се разбира, тие имаат добро позната намена, но мојата програма за тестирање, на пример, содржи нули во полињата pe_code_base, pe_code_size, итн., но работи добро. Заклучокот сам по себе сугерира дека датотеката е вчитана врз основа на табелата со објекти. За тоа ќе зборуваме.

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

Struct pe_ohdr ( непотпишан char o_name; непотпишан долг o_vsize; непотпишан долг o_vaddr; непотпишан долг o_psize; непотпишан долго o_poff; непотпишан char o_резервиран; непотпишан долг o_знамиња; );

  • 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_cputype != 0x14c || // i386 (PH->pe_flags & 2) == 0) // датотеката не може да се изврши! врати PE_WRONG; POH = (struct pe_ohdr *) ((непотпишан знак *)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 *)((непотпишан char *)POH + sizeof (struct pe_ohdr)); ) врати PE_OK; )

Ова повторно не е завршена програма, туку алгоритам за вчитување.

И повторно, многу точки не се опфатени, бидејќи тие ја надминуваат темата.

Но, сега вреди да се зборува малку за постоечките карактеристики на системот.

Системски карактеристики.

И покрај флексибилноста на заштитите достапни кај процесорите (заштита на ниво на табели со дескриптори, заштита на ниво на сегмент, заштита на ниво на страница), во постоечките системи (и во Windows и во Unix), целосно се користи само заштита на страницата, што, иако може да спречи пишување на кодот, но не може да спречи извршување на податоците. (Можеби ова е причината за изобилството на системски пропусти?)

Сите сегменти се адресираат од линеарната адреса нула и се протегаат до крајот на линеарната меморија. Разграничувањето на процесот се случува само на ниво на табелата со страници.

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

Некои карактеристики се исто така поврзани со страничење на меморијата.

ELF-датотеките се поврзани на таков начин што границите и големините на деловите паѓаат на блокови од 4 килобајти од датотеката.

И во форматот PE, и покрај фактот што самиот формат ви овозможува да порамните делови од 512 бајти, се користи порамнување на делови од 4k, помалото усогласување во Windows не се смета за точно.

Ако вашиот компјутер има антивирусна програмаможе скенирајте ги сите датотеки на компјутерот, како и секоја датотека поединечно. Можете да скенирате која било датотека со десен клик на датотеката и избирање на соодветната опција за скенирање на датотеката за вируси.

На пример, на оваа слика, датотека my-file.elf, потоа треба да кликнете со десното копче на оваа датотека и во менито датотека изберете ја опцијата „скенирајте со AVG“. Избирањето на оваа опција ќе го отвори AVG Antivirus и ќе ја скенира датотеката за вируси.


Понекогаш може да произлезе грешка од неправилна инсталација на софтвер, што може да се должи на проблем што се појавил за време на процесот на инсталација. Тоа може да пречи на вашиот оперативен систем поврзете ја вашата ELF датотека со правилната софтверска апликација, влијаејќи на т.н "асоцијации за проширување на датотеки".

Понекогаш едноставно повторно инсталирање на Dolphin (емулатор)може да го реши вашиот проблем со правилно поврзување на ELF со Dolphin (емулатор). Во други случаи, проблемите со асоцијацијата на датотеки може да произлезат од лошо софтверско програмирањепрограмер, и можеби ќе треба да го контактирате развивачот за дополнителна помош.


Совет:Обидете се да го ажурирате Dolphin (емулатор) на најновата верзија за да бидете сигурни дека ги имате најновите закрпи и ажурирања.


Ова може да изгледа премногу очигледно, но често самата датотека ELF можеби го предизвикува проблемот. Ако сте примиле датотека преку прилог преку е-пошта или сте ја презеле од веб-локација и процесот на преземање бил прекинат (на пример, поради прекин на струја или друга причина), датотеката може да е оштетена. Ако е можно, обидете се да добиете нова копија од датотеката ELF и обидете се повторно да ја отворите.


Внимателно:Оштетената датотека може да предизвика колатерална штета на претходниот или постоечки малициозен софтвер на вашиот компјутер, па затоа е важно да го одржувате компјутерот ажуриран со ажуриран антивирус.


Ако вашата датотека ELF поврзани со хардверот на вашиот компјутерза да ја отворите датотеката што можеби ќе ви треба ажурирајте ги драјверите на уредотповрзани со оваа опрема.

Овој проблем обично се поврзуваат со типови на медиумски датотеки, кои зависат од успешното отворање на хардверот во компјутерот, на пример, звучна картичка или видео картичка. На пример, ако се обидувате да отворите аудио датотека, но не можете да ја отворите, можеби ќе треба ажурирајте ги драјверите за звучна картичка.


Совет:Ако кога ќе се обидете да отворите датотека ELF добивате Порака за грешка поврзана со датотеката .SYS, веројатно проблемот би можел да биде поврзани со оштетени или застарени двигатели на уредоткои треба да се ажурираат. Овој процес може да се олесни со користење на софтвер за ажурирање на драјвери како што е DriverDoc.


Ако чекорите не го решија проблемоти сè уште имате проблеми со отворањето на датотеките ELF, тоа може да се должи на недостаток на достапни системски ресурси. Некои верзии на ELF-датотеки може да бараат значителна количина ресурси (на пр. меморија/RAM, процесорска моќност) за правилно отворање на вашиот компјутер. Овој проблем е доста чест ако користите прилично стар компјутерски хардвер и многу понов оперативен систем во исто време.

Овој проблем може да се појави кога на компјутерот му е тешко да ја заврши задачата бидејќи оперативниот систем (и другите услуги што работат во заднина) можат троши премногу ресурси за да ја отвори датотеката ELF. Обидете се да ги затворите сите апликации на вашиот компјутер пред да ја отворите датотеката за игра на Nintendo Wii. Со ослободување на сите достапни ресурси на вашиот компјутер, ќе обезбедите најдобри услови за обид за отворање на датотеката ELF.


Ако ти ги заврши сите горенаведени чекории вашата датотека ELF сè уште нема да се отвори, можеби ќе треба да ја извршите хардверска надградба. Во повеќето случаи, дури и со постари верзии на хардвер, процесорската моќ сепак може да биде повеќе од доволна за повеќето кориснички апликации (освен ако не работите многу интензивна работа на процесорот, како што се 3D рендерирање, финансиско/научно моделирање или работа интензивна за медиуми ) . На овој начин, веројатно е дека вашиот компјутер нема доволно меморија(почесто се нарекува „RAM“ или RAM) за извршување на задачата за отворање датотека.

Верзија на овој одговор со добар TOC и повеќе содржини: http://www.cirosantilli.com/elf-hello-world (кликнете овде Ограничување на знаци од 30k)

Стандарди

ELF е даден од LSB:

  • основни генерички: http://refspecs.linuxfoundation.org/LSB_4.1.0/LSB-Core-generic/LSB-Core-generic/elf-generic.html
  • јадро AMD64: http://refspecs.linuxfoundation.org/LSB_4.1.0/LSB-Core-AMD64/LSB-Core-AMD64/book1.html

LSB најчесто се однесува на други стандарди со мали проширувања, особено:

    генерички (и од 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/ документацијата /abi.pdf

Корисна биографија може да се најде на:

Неговата структура може да се испита со користење на начини погодни за корисниците, како што се readelf и objdump.

Направете пример

Ајде да разложиме минимален пример за извршување на Linux x86-64:

Секција .data hello_world db "Здраво свет!", 10 hello_world_len equ $ - hello_world секција .текст глобален _start _start: mov rax, 1 mov rdi, 1 mov rsi, hello_world mov rdx, hello_world_len syrllsca,

Составен со

Nasm -w+all -f elf64 -o "hello_world.o" "hello_world.asm" ld -o "hello_world.out" "hello_world.o"

  • НАСМ 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 | [заштитена е-пошта]| 00000030 00 00 00 00 40 00 00 00 00 00 40 00 07 00 03 00 |[заштитена е-пошта]@.....|

00000000 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 |.ELF...........| 00000010 02 00 3e 00 01 00 00 00 b0 00 40 00 00 00 00 00 |...> [заштитена е-пошта]| 00000020 40 00 00 00 00 00 00 00 10 01 00 00 00 00 00 00 |@...............| 00000030 00 00 00 00 40 00 38 00 02 00 40 00 06 00 03 00 |[заштитена е-пошта]@.....|

Презентирана структура:

Typedef struct (непотпишана знак 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: Нема екстензии.

    0 8: EI_PAD = 8x 00: резервирани бајти. Мора да се постави на 0.

    1 0: e_type = 01 00 = 1 (голем ендијан) = ET_REl: формат што може да се премести

    Во извршната датотека 02 00 за ET_EXEC.

    1 2: e_machine = 3e 00 = 62 = EM_X86_64: AMD64 архитектура

    1 4: е_верзија = 01 00 00 00: треба да биде 1

    1 8: e_entry = 8x 00: влезна точка на адресата за извршување, или 0 ако не е применливо, како за датотека со објект, бидејќи нема влезна точка.

    Во извршната датотека, ова е b0 00 40 00 00 00 00 00 . ТОДО: што друго да инсталираме? Се чини дека јадрото ја става IP-а директно во оваа вредност, не е хардкодирано.

    2 0: e_phoff = 8x 00: поместување на табелата за заглавие на програмата, 0 ако не.

    40 00 00 00 во извршната датотека, што значи дека започнува веднаш по заглавјето ELF.

    2 8: e_shoff = 40 7x 00 = 0x40: поместување на датотеката на табелата за заглавие на делот, 0 ако нема.

    3 0: е_знамиња = 00 00 00 00 TODO. Специјално за Арх.

    3 4: e_ehsize = 40 00: големина на ова заглавие на елф. Зошто е ова поле? Како може ова да се промени?

    3 6: e_phentsize = 00 00: големина на секое заглавие на програмата, 0 ако нема.

    38 00 во извршна датотека: должината на датотеката е 56 бајти

    3 8: e_phnum = 00 00: број на записи во заглавието на програмата, 0 ако нема.

    02 00 во извршната датотека: има 2 записи.

    3 A: e_shentsize и e_shnum = 40 00 07 00: големина на заглавието на делот и број на записи

Табела за заглавие на делот

Низа 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 на пример. .текстот бара тип SHT_PROGBITS и SHF_ALLOC + SHF_EXECINSTR

readelf -S hello_world.o:

Постојат 7 заглавија од секција, почнувајќи од Offset 0x40: Заглавија на делот: Име на тип адреса Офсет Големина Офтализа Знамиња Линк Инфо Порамни [0] Null 0000000000000000 00000000 0000000000000000 0 0 [1] .data progbits 0000000000 00000000000000 00 000000000000 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 00.000.280 0000000000000018 00000000000000a8 5 6 4 [5] .strtab STRTAB 00.000.330 0000000000000000 0000000000000034 0000000000000000 0 0 1 [ 6] .rela.текст RELA 00000000000000000 00000370 00000000000000018 0000000000000018 4 2 4 Клучни за знаменца: W (запишување) (запишување) (запишување) (запишување) (запишување) (спојување) (спојување) (сл.) I (информации), L (редослед на врски), G (група), T (TLS), E (исклучи), x (непознато) O (потребна е дополнителна обработка на ОС) o (специфична за ОС), p (специфична за процесорот)

struct , претставена со секој запис:

Typedef struct ( Elf64_Word sh_name; Elf64_Word sh_type; Elf64_Xword sh_flags; Elf64_Addr sh_addr; Elf64_Off sh_offset; Elf64_Xword sh_size; Elf64_Word sh_link; Elf64_Word sh_link; Elf64_Word sh_link; Elf64_Word sh_link; Elf64_Word sh_link; Elf64_Word sh_link; Elf64_Xword sh_flags; Elf64_Addr sh_addr;

Секции

Индекс дел 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).

Постојат и други магични делови на Слика 4-7: Индекси на специјални делови.

На индекс 0, потребно е SHT_NULL. Дали има други употреби за ова: Која е употребата на делот SHT_NULL во ELF? ?

.дел за податоци

Податоците се дел 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 .

    Податоците се едно од имињата на делот што има однапред дефинирана вредност http://www.sco.com/developers/gabi/2003-12-17/ch4.strtab.html

    Овие делови складираат иницијализирани податоци што придонесуваат за сликата на меморијата на програмата.

  • 80 4: sh_type = 01 00 00 00: SHT_PROGBITS: Содржината на делот не е одредена од ELF, само со тоа како програмата ја толкува. Во ред е, бидејќи .податоци .

    80 8: sh_flags = 03 7x 00: SHF_ALLOC и SHF_EXECINSTR: http://www.sco.com/developers/gabi/2003-12-17/ch4.sheader.html#sh_flags , како што се бара од делот .data

    90 0: sh_addr = 8x 00: во која виртуелна адреса делот ќе биде поставен при извршување, 0 ако не е поставен

    90 8: sh_offset = 00 02 00 00 00 00 00 00 = 0x200: број на бајти од почетокот на програмата до првиот бајт во овој дел

    a0 0: sh_size = 0d 00 00 00 00 00 00 00

    Ако земеме 0xD бајти, почнувајќи од sh_offset 200, ќе видиме:

    00000200 48 65 6c 6c 6f 20 77 6f 72 6c 64 21 0a 00 |Здраво свето!.. |

    АХА! Значи нашата низа "Здраво свет!" е во делот за податоци, како што рековме, тоа е на NASM.

    Откако ќе завршиме со HD, ќе го разгледаме вака:

    Readelf -x .податоци hello_world.o

    кои излегуваат:

    Хексадецимален депон од делот „.податоци“: 0x00000000 48656c6c 6f20776f 726c6421 0a Здраво свето!.

    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, тоа значи дека делот содржи табела со записи со фиксна големина. Во оваа датотека, можеме да видиме од излезот за читање дека ова е случај за секциите .symtab и .rela.text.

.текст секција

Сега, кога рачно направивме еден дел, ајде да дипломираме и да користиме readelf -S од другите делови.

Име Тип Адреса Офсет Големина EntSize Знамиња Информации за врската Порамнете [ 2] .текст

Текстот може да се изврши, но не може да се запише: ако се обидеме да му напишеме segfaults на Linux. Ајде да видиме дали навистина го имаме кодот:

Objdump -d hello_world.o

Hello_world.o: формат на датотека elf64-x86-64 Расклопување на делот .текст: 00000000000000000<_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: 000 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 scaf 25:

Ако имаме grep b8 01 00 00 на hd, гледаме дека тоа се случува само на 00000210, што е она што го кажува овој дел. И големината е 27, што исто така одговара. Затоа, мора да зборуваме за правилниот дел.

Ова изгледа како точниот код: пишување проследено со излез .

Најинтересниот дел е линијата 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 ?

Податоци: \0 a b c \0 d e f \0 Индекс: 0 1 2 3 4 5 6 7 8

И ако друг дел сака да ја користи низата d e f , тие мора да укажуваат на индексот 5 од тој дел (буквата г).

Познати табели со жици:

  • .shstrtab
  • .strtab

.shstrtab

Тип на дел: sh_type == SHT_STRTAB .

Заедничко име: линија за заглавие на насловот на делот.

Делот name.shstrtab е резервиран. Стандардот вели:

Овој дел содржи имиња на делови.

Овој дел го одредува полето e_shstrnd на самото заглавие ELF.

Индексите на редовите на овој дел се означени со полето sh_name на заглавијата на секциите што ги означуваат редовите.

Овој дел не специфицира SHF_ALLOC, така што нема да се појави во извршната датотека.

Readelf -x .shstrtab hello_world.o

Двојна депонија на делот ".shstrtab": 0x00000000 002e6461 7461002e 74.657.874 73.747.274 002e7368 ..data..text..sh 0x00000010 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

Ако ги погледнеме имињата на другите делови, можеме да видиме дека сите тие содржат броеви, на пр. делот .текст е нумериран со 7 .

Потоа секоја линија завршува кога ќе се најде првиот NUL знак, на пр. знак 12 \0 веднаш по .текст\0 .

.symtab

Тип на дел: sh_type == SHT_SYMTAB .

Заедничко име: табела со симболи.

Прво да забележиме дека:

  • sh_link = 5
  • sh_info=6

Во делот SHT_SYMTAB, овие бројки значат дека:

  • Стрингови
  • кои даваат имиња на симболи се во делот 5, .strtab
  • податоците за преместување се во делот 6, .rela.text

Добра алатка на високо ниво за расклопување на овој дел:

Nm hello_world.o

кој дава:

00000000000000000 T _start 0000000000000000 d hello_world 000000000000000d a hello_world_len

Сепак, ова е претстава на високо ниво што испушта одредени типови на знаци и означува знаци. Подетален преглед може да се добие користејќи:

Readelf -s hello_world.o

кој дава:

Симбол Табела " _

Бинарниот формат на табелата е документиран на http://www.sco.com/developers/gabi/2003-12-17/ch4.symtab.html

Readelf -x .symtab hello_world.o

Што дава:

Hex депонијата на делот ".symtab": 0x00000000 00000000 00000000 00000000 00000000 ................ 0x00000010 00000000 000000000 00000000 000000000 . 0x00000020 00000000 00000000 00000000 00000000 ................ 0x00000030 00000000 0x00000000 00000000 0x000000 00000000 000000 ................ 0x00000040 00000000 00000000 00000000 0x000000 .............. 0x00000050 00000000 00000000 000000000000 000000000 ...... 00000000 ................ 0x00000090 2d000000 10000200 00000000 00000000 -............. 0x000000a0 00000000 00000000 ........

Записите се од типот:

Typedef struct ( Elf64_Word st_name; непотпишан char st_info; непотпишан 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

    Битови 0-3 = ELF64_R_TYPE = Тип = 4 = STT_FILE: Главната цел на овој запис е да се користи st_name за да се одреди името на датотеката генерирана од оваа објектна датотека.

    Битови 4-7 = ELF64_ST_BIND = Врзување = 0 = STB_LOCAL. Потребна вредност за STT_FILE .

    10 13: st_shndx = Табела со симболи Заглавие табела Индекс = f1ff = SHN_ABS . Потребно за STT_FILE .

    20 0: st_value = 8x 00: потребно за вредност за STT_FILE

    20 8: st_size = 8x 00: нема доделена големина

Сега од читање брзо го толкуваме останатото.

STT_SECTION

Има два такви елементи, едниот покажува на .податоци, а другиот на .текст (индекси на делот 1 и 2).

Број: Вредност Тип Големина Bind Vis Ndx Име 2: 0000000000000000 0 СЕКЦИЈА ЛОКАЛНО ПОСТАНДИРАНО 1 3: 00000000000000000 0 СЕКЦИЈА ЛОКАЛНО ПОСТАНДИРАНО 2

ТОДО, која е нивната цел?

STT_NOTYPE

Потоа внесете ги најважните знаци:

Број: вредност Тип на големина Bind Vis Ndx Име 4: 0000000000000000 0 NOTYPE LOCAL DEFAULT 1 hello_world 5: 000000000000000d 0 NOTYPE LOCAL DEFAULT DEFAULT000000000000

hello_world е во делот .data (индекс 1). Оваа вредност е 0: укажува на првиот бајт од овој дел.

Почетокот е означен со ГЛОБАЛНА видливост, како што напишавме:

глобален_почеток

во НАСМ. Ова е неопходно бидејќи треба да се смета како влезна точка. За разлика од 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 од делот .symtab.

Readelf -x .strtab hello_world.o

Двојна депонија на делот ".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 уметност.

Ова значи дека е ограничување на ниво на ELF дека глобалните променливи не можат да содржат NUL знаци.

.рела.текст

Тип на дел: sh_type == SHT_RELA .

Заедничко име: секција за преместување.

Rela.text содржи податоци за преместување кои одредуваат како треба да се смени адресата кога ќе се поврзе последната извршна датотека. Ова ги означува бајтите од областа на текстот што треба да се сменат кога поврзувањето се случува со точните мемориски локации.

Во основа, го конвертира текстот на објектот што ја содржи адресата на држачот за место 0x0:

A:48 be 00 00 00 00 00 movabs $0x0,%rsi 11:00 00 00

до вистинскиот извршна шифра што ја содржи конечната 0x6000d8:

4000ba: 48 be d8 00 60 00 00 movabs $0x6000d8,%rsi 4000c1: 00 00 00

Наведен е sh_info = 6 од делот .symtab.

readelf -r hello_world.o дава:

Делот за преместување „.rela.text“ со поместување 0x3b0 содржи 1 запис: Тип на информации за офсет Sym. Вредност Sym. Име + Додај 00000000000c 000200000001 R_X86_64_64 00000000000000000 .податоци + 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 |................|

Претставена структура:

Typedef struct ( Elf64_Addr r_offset; Elf64_Xword r_info; Elf64_Sxword r_addend; ) Elf64_Rela;

    370 0: r_offset = 0xC: адреса до адреса.текст чија адреса ќе се смени

    370 8: r_info = 0x200000001. Содржи 2 полиња:

    • ELF64_R_TYPE = 0x1: вредноста зависи од точната архитектура.
    • ELF64_R_SYM = 0x2: Индексот на партицијата на кој укажува адресата, значи .data , кој е на индекс 2.

    AMD64 ABI вели дека типот 1 се нарекува R_X86_64_64 и дека ја претставува операцијата S + A каде што:

    • S: вредноста на симболот во објектната датотека, тука 0 затоа што покажуваме на 00 00 00 00 00 00 00 00 од movabs $0x0,%rsi
    • a: додатокот присутен во полето r_added

    Оваа адреса се додава на партицијата каде што се извршува потегот.

    Оваа операција за движење работи на 8 бајти.

    380 0: r_addend = 0

Така, во нашиот пример, заклучуваме дека новата адреса ќе биде: S + A = .data + 0 , а со тоа и прва во делот за податоци.

Табела со наслови на програмата

Се прикажува само во извршната датотека.

Содржи информации за тоа како извршната датотека треба да се смести во виртуелната меморија на процесот.

Извршната датотека е креирана од датотеката со објект за поврзување. Главните задачи што ги извршува поврзувачот:

    одреди кои делови од објектните датотеки одат во кои сегменти од извршната датотека.

    Во Binutils се сведува на парсирање на скриптата за градител и работа со многу стандардни поставки.

    Можете да ја добиете скриптата за поврзување што се користи со ld --verbose и да ја инсталирате прилагодената со ld -T .

    навигирајте низ текстуалните делови. Тоа зависи од тоа како повеќе партиции се вклопуваат во меморијата.

readelf -l hello_world.out дава:

Елф тип на датотека е exec (извршна датотека) Влегување точка 0x4000b0 Постојат 2 програма заглавија, со почеток во офсет 64 Програма заглавија: Тип Офсет VirtAddr PhysAddr FileSiz MemSiz Знамиња Порамни ТОВАР 0x0000000000000000 0x0000000000400000 0x0000000000400000 0x00000000000000d7 0x00000000000000d7 RE 200000 ТОВАР 0x00000000000000d8 0x00000000006000d8 0x00000000006000d8 0x000000000000000d 0x000000000000000d RW 200000 Дел до Мапирање на сегменти: Секции на сегменти... 00 .текст 01 .податоци

Во заглавието ELF e_phoff , e_phnum и e_phentsize ни кажаа дека има 2 заглавија на програмата кои почнуваат од 0x40 и се со големина од 0x38 бајти, така што тие се:

00000040 01 00 00 00 05 00 00 00 00 00 00 00 00 00 00 00 |................| 00000050 00 00 40 00 00 00 00 00 00 00 40 00 00 00 00 00 |[заштитена е-пошта]@.....| 00000060 d7 00 00 00 00 00 00 00 d7 00 00 00 00 00 00 00 |................| 00000070 00 00 20 00 00 00 00 00 |.. ..... |

00000070 01 00 00 00 06 00 00 00 | ........| 00000080 d8 00 00 00 00 00 00 00 d8 00 60 00 00 00 00 00 |..........`.....| 00000090 d8 00 60 00 00 00 00 00 0d 00 00 00 00 00 00 00 |...`.............| 000000a0 0d 00 00 00 00 00 00 00 00 00 20 00 00 00 00 00 |.......... .....| 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_f_6;

Распределба на првиот:

  • 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: почетна физичка адреса за вчитување во меморијата. Само прашања за системи каде што програмата може да ја постави физичката адреса. Во спротивно, како и кај системите Систем 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, што значи тоа? инаку вишок со други полиња

Вториот е сличен.

Мапирање од дел до сегменти:

делот за читање ни кажува дека:

  • 0 - сегмент.текст . Да, затоа е извршна и не може да се запише.
  • 1 - сегмент.податоци .

Слични објави