Jak číst soubor elfů v c. Formáty ELF a PE EXE

V této recenzi budeme hovořit pouze o 32bitové verzi tohoto formátu, protože zatím nepotřebujeme 64bitovou verzi.

Libovolný soubor ELF (včetně objektových modulů tohoto formátu) se skládá z následujících částí:

  • Záhlaví souboru ELF;
  • Tabulka programových sekcí (může chybět v modulech objektů);
  • Části souboru ELF;
  • Tabulka oddílů (ve spustitelném modulu může chybět);
  • Z důvodů výkonu ELF nepoužívá bitová pole. A všechny struktury jsou obvykle zarovnány na 4 bajty.

Nyní se podívejme na typy používané v záhlaví souborů ELF:

Nyní zvažte záhlaví souboru:

# define EI_NIDENT 16 struct elf32_hdr (unsigned char e_ident; Elf32_Half e_type, Elf32_Half e_machine, Elf32_Word e_version, Elf32_Addr e_entry; / * Vstupní bod * / 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;);

Pole e_ident obsahuje informace o systému a skládá se z několika podpolí.

Struct (nepodepsaný char ei_magic; nepodepsaný char ei_class; nepodepsaný char ei_data; nepodepsaný char ei_version; nepodepsaný char ei_pad;)

  • ei_magic - konstantní hodnota pro všechny soubory ELF, rovná (0x7f, "E", "L", "F")
  • ei_class - třída souboru ELF (1 - 32 bitů, 2 - 64 bitů, které neuvažujeme)
  • ei_data - definuje pořadí bajtů pro tohoto souboru (toto pořadí závisí na platformě a může být vpřed (LSB nebo 1) nebo vzad (MSB nebo 2)) procesory Intel povolena je pouze hodnota 1.
  • ei_version je poměrně zbytečné pole, a pokud se nerovná 1 (EV_CURRENT), pak je soubor považován za neplatný.

Operační systémy ukládají své přihlašovací údaje do pole ei_pad. Toto pole může být prázdné. Nám to také nevadí.

Pole záhlaví e_type může obsahovat více hodnot, pro spustitelné soubory musí být ET_EXEC rovna 2

e_machine - určuje procesor, na kterém tento spustitelný soubor může fungovat (pro nás je přijatelná hodnota EM_386 rovna 3)

Pole e_version odpovídá poli ei_version ze záhlaví.

Pole e_entry definuje počáteční adresu programu, který je umístěn do eip před spuštěním programu.

Pole e_phoff definuje posun od začátku souboru, ve kterém je umístěna tabulka sekcí programu, sloužící k načtení programů do paměti.

Nebudu uvádět účel všech polí, ne všechna jsou potřebná pro načtení. Popíšu jen dva další.

Pole e_phentsize definuje velikost položky v tabulce programových částí.

A pole e_phnum definuje počet záznamů v tabulce programových částí.

Pro propojení programů se používá tabulka sekcí (nikoli software). nebudeme to zvažovat. Také nebudeme uvažovat o dynamicky propojených modulech. Toto téma je poměrně složité, není vhodné pro první seznámení. :)

Nyní o programových částech. Formát záznamu tabulky programových částí je následující:

Struct elf32_phdr (Elf32_Word p_type; Elf32_Off p_offset; Elf32_Addr p_vaddr; Elf32_Addr p_paddr; Elf32_Word p_filesz; Elf32_Word p_memsz; Elf32_Word p_flags; Elf32_Word p_fl.

Další informace o polích.

  • p_type - definuje typ programové sekce. Může mít několik hodnot, ale zajímá nás jen jedna. PT_LOAD (1). Pokud je sekce tohoto typu, je určena k načtení do paměti.
  • p_offset - definuje posun v souboru, od kterého začíná tato část.
  • p_vaddr - definuje virtuální adresu, na kterou má být tato sekce načtena do paměti.
  • p_paddr - definuje fyzickou adresu, kam má být tato sekce načtena. Toto pole není nutné používat a má smysl pouze na některých platformách.
  • p_filesz - určuje velikost sekce v souboru.
  • p_memsz - Určuje velikost sekce v paměti. Tato hodnota může být vyšší než ta předchozí. Pole p_flag definuje typ přístupu k oddílům v paměti. Některé sekce je možné provádět, jiné zaznamenávat. Pro čtení stávající systémy všechny jsou k dispozici.

Načítání formátu ELF.

Trochu jsme zjistili název. Nyní uvedu algoritmus pro načtení binárního souboru ELF. Algoritmus je schematický, neměli byste jej považovat za pracovní program.

Int LoadELF (unsigned char * bin) (struct elf32_hdr * EH \u003d (struct elf32_hdr *) bin; struct elf32_phdr * EPH; if (EH-\u003e e_ident! \u003d 0x7f || // Controlling MAGIC EH-\u003e e_ident! \u003d "E" || EH-\u003e e_ident! \u003d "L" || EH-\u003e e_ident! \u003d "F" || EH-\u003e e_ident! \u003d ELFCLASS32 || // Ovládání třídy EH-\u003e e_ident! \u003d ELFDATA2LSB || // pořadí bytů EH-\u003e e_ident! \u003d EV_CURRENT || // verze EH-\u003e e_type! \u003d ET_EXEC || // typ EH-\u003e e_machine! \u003d EM_386 || // platforma EH-\u003e e_version! \u003d EV_CURRENT) // a znovu verze, jen v případě, že se vrátíte ELF_WRONG; EPH \u003d (struct elf32_phdr *) (bin + EH-\u003e e_phoff); while (EH-\u003e e_phnum-) (if (EPH-\u003e p_type \u003d\u003d PT_LOAD) memcpy (EPH-\u003e p_vaddr, bin + EPH-\u003e p_offset, EPH-\u003e p_filesz); EPH \u003d (struktura elf32_phdr *) ((nepodepsaný znak *) EPH + EH-\u003e e_phentsize));) vrátit ELF_OK; )

Vážně, stojí za to také analyzovat pole EPH-\u003e p_flags a umístit přístupová práva na odpovídající stránky a jednoduché kopírování zde nebude fungovat, ale to neplatí pro formát, ale pro alokaci paměti. Proto o tom teď nebudeme mluvit.

PE formát.

V mnoha ohledech je to podobné formátu ELF, není divu, že by měly být k dispozici také sekce ke stažení.

Jako všechno v Microsoftu :) formát PE je založen na formátu EXE. Struktura souboru je následující:

  • 00h - hlavička EXE (nebudu to brát v úvahu, je to staré jako Dos. :)
  • 20h - záhlaví OEM (není v něm nic významného);
  • 3сh - offset skutečné PE hlavičky v souboru (dword).
  • stůl s pahýlem;
  • pahýl;
  • Záhlaví PE;
  • tabulka objektů;
  • souborové objekty;

stub je program v reálném čase, který provádí předběžné práce. Může to chybět, ale někdy to může být nutné.

Zajímá nás něco jiného, \u200b\u200bnadpis PE.

Jeho struktura je následující:

Struktura pe_hdr (nepodepsané dlouhé pe_sign; nepodepsané krátké pe_cputpe; nepodepsané krátké pe_objnum; nepodepsané dlouhé pe_time; nepodepsané dlouhé pe_cofftbl_off; nepodepsané dlouhé pe_cofftbl_size; nepodepsané krátké pe_nthdr_size; nepodepsané krátké pe_flags nepodepsané short_size; nepodepsané krátké nepodepsané pe_size ; nepodepsané dlouhé pe_udata_size; nepodepsané dlouhé pe_entry; nepodepsané dlouhé pe_code_base; nepodepsané dlouhé pe_data_base; nepodepsané dlouhé pe_image_base; nepodepsané dlouhé pe_obj_align; nepodepsané dlouhé pe_file_align; // ... no, a mnoho dalších věcí, na kterých nezáleží.);

Je tam spousta věcí. Stačí říci, že velikost této hlavičky je 248 bajtů.

A hlavní věc je, že většina z těchto polí se nepoužívá. (Kdo staví takto?) Ne, určitě mají dobře známý účel, ale například můj testovací program obsahuje nuly v polích pe_code_base, pe_code_size atd., Ale funguje to skvěle. Závěr sám naznačuje, že soubor je načten na základě tabulky objektů. Zde o ní budeme mluvit.

Tabulka funkcí následuje bezprostředně za hlavičkou PE. Položky v této tabulce mají následující formát:

Struct pe_ohdr (nepodepsaný char o_name; nepodepsaný dlouhý o_vsize; nepodepsaný dlouhý o_vaddr; nepodepsaný dlouhý o_psize; nepodepsaný dlouhý o_poff; nepodepsaný char o_reserved; nepodepsaný dlouhý o_flags;);

  • o_name - název sekce, naprosto lhostejný pro načítání;
  • o_vsize - velikost sekce v paměti;
  • o_vaddr - adresa paměti vzhledem k ImageBase;
  • o_psize - velikost sekce v souboru;
  • o_poff - posun sekce v souboru;
  • o_flags - příznaky sekce;

Stojí za to podrobněji se zabývat vlajkami.

  • 00000004h - používá se pro kód s 16bitovými offsety
  • 00000020h - sekce kódu
  • 00000040h - část inicializovaných dat
  • 00000080h - část neinicializovaných dat
  • 00000200h - komentáře nebo jakýkoli jiný typ informací
  • 00000400h - překryvná část
  • 00000800h - nebude součástí obrazu programu
  • 00001000h - obecná data
  • 00500000h - výchozí zarovnání, pokud není uvedeno jinak
  • 02000000h - lze uvolnit z paměti
  • 04000000h - není uloženo v mezipaměti
  • 08000000h - nepodléhá převodu stránky
  • 10 000 000 h - sdílené
  • 20000000 h - proveditelné
  • 40000000h - lze číst
  • 80000000h - můžete psát

Opět nebudu se sdílenými a překryvnými částmi, zajímáme se o kód, data a přístupová práva.

Obecně jsou tyto informace již dostatečné ke stažení binárního souboru.

Načítání formátu PE.

int LoadPE (unsigned char * bin) (struct elf32_hdr * PH \u003d (struct pe_hdr *) (bin + * ((unsigned long *) & bin)); // Samozřejmě kombinace není jasná ... stačí vzít dword na offsetu 0x3c / / A vypočítat adresu záhlaví PE na obrázku souboru elf32_phdr * POH; if (PH \u003d\u003d NULL || // Ovládání ukazatele PH-\u003e pe_sign! \u003d 0x4550 || // podpis PE ("P", "E", 0, 0) PH-\u003e pe_cputpe! \u003d 0x14c || // i386 (PH-\u003e pe_flags & 2) \u003d\u003d 0) // soubor nelze spustit! Vrátit PE_WRONG; POH \u003d (struct pe_ohdr *) ((nepodepsaný znak *) PH + 0xf8); while (PH-\u003e pe_obj_num--) (if ((POH-\u003e p_flags & 0x60)! \u003d 0) // buď kód, nebo inicializovaná data memcpy (PE-\u003e pe_image_base + POH-\u003e o_vaddr, bin + POH- \u003e o_poff, POH-\u003e o_psize); POH \u003d (struct pe_ohdr *) ((unsigned char *) POH + sizeof (struct pe_ohdr));) návrat PE_OK;)

Opět nejde o hotový program, ale o zaváděcí algoritmus.

Mnoho bodů opět není zahrnuto, protože jdou nad rámec tohoto tématu.

Nyní ale stojí za to trochu si promluvit o stávajících funkcích systému.

Vlastnosti systému.

Navzdory flexibilitě ochrany dostupné v procesorech (ochrana na úrovni tabulek deskriptorů, ochrana na úrovni segmentů, ochrana na úrovni stránky) ve stávajících systémech (ve Windows i Unixu) je plně využívána pouze ochrana stránky, která, i když může uložit kód před zápisem, ale nelze uložit data z provedení. (Možná to je důvod hojnosti zranitelností systému?)

Všechny segmenty jsou adresovány od nulové lineární adresy a rozšiřují se až na konec lineární paměti. Vymezení procesů se provádí pouze na úrovni tabulek stránek.

V tomto ohledu nejsou všechny moduly propojeny z počátečních adres, ale s dostatečně velkým posunem v segmentu. Ve Windows je základní adresa v segmentu 0x400000, v Unixu (Linux nebo FreeBSD) - 0x8048000.

Některé funkce jsou také spojeny s stránkovací pamětí.

Soubory ELF jsou propojeny takovým způsobem, že hranice a velikosti sekcí spadají na bloky souborů o velikosti 4 kilobajty.

A ve formátu PE, navzdory skutečnosti, že samotný formát umožňuje zarovnání sekcí o 512 bajtů, je zarovnání sekcí 4k, menší zarovnání ve Windows se nepovažuje za správné.

Formát ELF

Formát ELF obsahuje několik typů souborů, které jsme dosud nazývali jinak, například spustitelný soubor nebo soubor objektu. Standard ELF však rozlišuje mezi následujícími typy:

1. Přemístitelný soubor (přemístitelný soubor), který ukládá pokyny a data, která lze přidružit k jiným souborům objektů. Výsledkem tohoto propojení může být spustitelný soubor nebo soubor sdíleného objektu.

2. Soubor sdíleného objektu (soubor sdílených objektů) obsahuje také pokyny a data, ale lze je použít dvěma způsoby. V prvním případě může být propojen s jinými přemístitelnými soubory a soubory sdílených objektů, což má za následek vytvoření nového souboru objektu. V druhém případě, když je program spuštěn k provedení, může jej operační systém dynamicky propojit se spustitelným souborem programu, v důsledku čehož bude vytvořen spustitelný obraz programu. V druhém případě mluvíme o sdílených knihovnách.

3. Spustitelný soubor ukládá kompletní popis, který umožňuje systému vytvořit obraz procesu. Obsahuje pokyny, data, popis požadovaných souborů sdílených objektů a potřebné symbolické a ladicí informace.

Na obr. 2.4 ukazuje strukturu spustitelného souboru, pomocí které může operační systém vytvořit obraz programu a spustit program k provedení.

Postava: 2.4... Struktura spustitelného souboru ve formátu ELF

Záhlaví má v souboru pevné umístění. Zbytek komponent je umístěn podle informací uložených v záhlaví. Záhlaví tedy obsahuje obecný popis struktury souboru, umístění jednotlivých komponent a jejich velikosti.

Protože záhlaví souboru ELF určuje jeho strukturu, uvažujme o tom podrobněji (tabulka 2.4).

Tabulka 2.3... Pole záhlaví ELF

Pole Popis
e_ident Pole bajtů, z nichž každý definuje některé obecné charakteristiky formát souboru: formát souboru (ELF), číslo verze, architektura systému (32bitová nebo 64bitová) atd.
e_type Typ souboru ve formátu ELF podporuje více typů
e_machine Architektura hardwarové platformy, pro kterou byl tento soubor vytvořen. Stůl 2.4 ukazuje možné hodnoty tohoto pole
e_verze Číslo verze ELF. Obvykle definováno jako EV_CURRENC (aktuální), což znamená nejnovější verze
e_entry Virtuální adresa, na kterou systém přenese kontrolu po načtení programu (vstupní bod)
e_phoff Umístění (posun od začátku souboru) tabulky záhlaví programu
e_shoff Umístění tabulky záhlaví sekce
e_ehsize Velikost záhlaví
e_phentsize Velikost záhlaví každého programu
e_phnum Počet záhlaví programu
e_shentsize Velikost záhlaví každého segmentu (sekce)
e_shnum Počet záhlaví segmentů (sekcí)
e_shstrndx Umístění segmentu obsahujícího tabulku řádků

Tabulka 2.4... Hodnoty pole záhlaví ELF e_machine

Hodnota Hardwarová platforma
EM_M32 AT&T WE 32100
ЕМ_SPARC Sun SPARC
EM_386 Intel 80386
_М_68K Motorola 68000
EM_88K Motorola 88000
EM_486 Intel 80486
EM_860 Intel i860
ЕМ_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+

Informace v tabulce záhlaví programu sdělují jádru, jak vytvořit obraz procesu ze segmentů. Většina segmentů je kopírována (mapována) do paměti a představuje odpovídající segmenty procesu, který běží, jako jsou segmenty kódu nebo dat.

Každé záhlaví segmentu programu popisuje jeden segment a obsahuje následující informace:

Typ segmentu a akce operačního systému s tímto segmentem

Umístění segmentu v souboru

Počáteční adresa segmentu ve virtuální paměti procesu

Velikost segmentu v souboru

Velikost segmentu v paměti

Příznaky přístupu k segmentu (zápis, čtení, spuštění)

Některé segmenty jsou typu LOAD, který dává jádru pokyn k vytvoření datových struktur odpovídajících těmto segmentům, tzv oblastechkteré definují souvislé sekce virtuální paměti pro proces a jejich přidružené atributy. Segment, jehož umístění v souboru ELF je specifikováno v příslušném záhlaví programu, bude namapován na vytvořenou oblast, jejíž virtuální adresa je také uvedena v záhlaví programu. Tento typ segmentu zahrnuje například segmenty obsahující programové instrukce (kód) a jejich data. Pokud je velikost segmentu menší než velikost oblasti, může být nevyužitý prostor vyplněn nulami. Tento mechanismus se používá zejména při vytváření neinicializovaných procesních dat (BSS). Více si povíme o oblastech v kapitole 3.

Segment typu INTERP ukládá programového tlumočníka. Tenhle typ segment se používá pro programy, které vyžadují dynamické propojení. Podstata dynamického propojení spočívá v tom, že jednotlivé komponenty spustitelného souboru (soubory sdílených objektů) nejsou připojeny ve fázi kompilace, ale ve fázi spouštění programu pro provedení. Název souboru, který je editor dynamických odkazů, je uložen v tomto segmentu. V procesu spouštění programu pro spuštění jádro vytvoří obraz procesu pomocí zadaného linkeru. Nejprve se tedy do paměti nenačte původní program, ale dynamický linker. V dalším kroku pracuje dynamický linker s jádrem UNIX a vytvoří kompletní obraz spustitelného souboru. Dynamický editor načte potřebné soubory sdílených objektů, jejichž jména jsou uložena v samostatných segmentech původního spustitelného souboru, a provede požadované umístění a propojení. Nakonec je kontrola přenesena do původního programu.

Nakonec soubor v záhlaví ukončí tabulka sekce nebo sekce (sekce). Sekce (sekce) definují sekce souboru, které se používají k propojení s jinými moduly během kompilace nebo dynamického propojení. Záhlaví tedy obsahují všechny nezbytné informace popsat tyto oddíly. Sekce obvykle obsahují podrobnější informace o segmentech. Například segment kódu může sestávat z několika sekcí, jako je hashovací tabulka pro ukládání indexů symbolů použitých v programu, část inicializačního kódu programu, vazebná tabulka používaná dynamickým editorem a část obsahující skutečné pokyny k programu.

V diskusi o organizaci virtuální paměti procesu se vrátíme k formátu ELF v kapitole 3, ale prozatím přejdeme k dalšímu běžnému formátu COFF.

Z knihy The Art of Unix Programming autor Raymond Eric Stephen

Z knihy Průvodce samostudiem pro práci na počítači autor Kolisnichenko Denis Nikolaevich

Z knihy Abstrakt, seminární práce, diplom na počítači autor Balovsyak Nadezhda Vasilievna

5.2.6. Windows INI formát Mnoho programů v Microsoft Windows použití textový formát data, podobně jako úryvek uvedený v příkladu 5.6. V tento příklad volitelné zdroje s názvem account, directory, numeric_id a developer are linked to named projects python, sng, f etchmail, and py-howto. Při nahrávání

Z knihy Nejnovější výukový program pro práci na počítači autor Beluntsov Valery

14.5.3. Formát buňky Formát určuje, jak se zobrazí hodnota buňky. Formát úzce souvisí s datovým typem buňky. Typ nastavujete vy. Pokud jste zadali číslo, jedná se o číselný datový typ. Samotný Excel se pokouší určit formát podle datového typu. Pokud jste například zadali text, pak

Z knihy The Art of Unix Programming autor Raymond Eric Stephen

Formát PDF PDF znamená Portable Document Format (přenosný formát dokumentu). Tento formát byl vytvořen speciálně za účelem odstranění problémů se zobrazováním informací v souborech. Jeho výhodou je, že za prvé, dokument uložen v formát PDFbude stejný

Z knihy TCP / IP Architecture, Protocols, Implementation (včetně IP verze 6 a IP Security) autor: Faith Sidney M.

Formát souboru Když uživatel začne pracovat se souborem, systém musí vědět, v jakém formátu je zapsán a s jakým programem je třeba jej otevřít. Například pokud soubor obsahuje prostý text, lze jej číst v libovolném textovém programu

Z knihy Yandex pro každého autor Abramzon M.G.

5.2.2. Formát RFC 822 Metaformát RFC 822 pochází z textového formátu e-mailových zpráv na internetu. RFC 822 je hlavní internetový standard RFC pro tento formát (později nahrazen RFC 2822). Formát MIME (Multipurpose Internet Media Extension)

Z knihy Macromedia Flash Professional 8. Grafika a animace autor Dronov V.A.

5.2.3. Formát cookie-jar Formát cookie-jar používá fortune (1) pro svou vlastní databázi náhodných nabídek. Je vhodný pro záznamy, které jsou jednoduše bloky nestrukturovaného textu. Jako oddělovač záznamů v tento formát je použit symbol

Z knihy Zpracování zvuku počítače autor Zagumennov Alexander Petrovič

5.2.4. Formát záznamové nádoby Oddělovače záznamů záznamové nádoby dobře fungují s metaformátem RFC 822 pro záznamy, které tvoří to, co tato kniha nazývá „záznamová nádoba“. Někdy je vyžadován textový formát, který podporuje více položek s jinou sadou explicitních jmen

Z knihy UNIX Operating System autor Robachevsky Andrey M.

5.2.6. Formát Windows INI Mnoho programů v systému Microsoft Windows používá formát textových dat, jako je úryvek uvedený v příkladu 5.6. V tomto příkladu jsou volitelné prostředky s názvem account, directory, numeric_id a developer associated with named projects python, sng, fetchmail, and py-howto. Při nahrávání

Z knihy Kancelářský počítač pro ženy autor Pasternak Evgeniya

19.5 Zobecněný formát URL Chcete-li shrnout výše uvedené, nezapomeňte, že:? URL začíná použitým přístupovým protokolem.? U aplikací jiných než webové novinky a e-mail následuje následující oddělovač: //.? Poté je zadán název hostitele serveru.? Konečně

Z knihy autora

3.3.1. Formát RSS Zprávy na webu můžete číst různými způsoby. Nejjednodušší je čas od času navštívit web a zobrazit nové zprávy. Můžete si nainstalovat program, který se připojuje ke zpravodajskému kanálu a sám přijímá titulky nebo anotace zpráv od

Z knihy autora

Formát MP3 Formát MP3 byl vytvořen k distribuci hudebních souborů komprimovaných kodekem úrovně MPEG 1. Úroveň 3. V současné době je to nejoblíbenější formát pro distribuci hudby přes internet i mimo něj. Podporováno absolutně všemi programy pro nahrávání a zpracování zvuku, pro

Z knihy autora

Formát MP3 Metoda komprese zvuku i komprimovaný formát zvukové souborynavržený mezinárodní organizací MPEG (Moving Pictures Experts Group) je založen na percepčním audio kódování. Práce na vytvoření efektivních kódovacích algoritmů

Z knihy autora

Formát ELF Formát ELF obsahuje několik typů souborů, které jsme dosud nazývali odlišně, například spustitelný soubor nebo objektový soubor. Standard ELF však rozlišuje mezi následujícími typy: 1. Přemístitelný soubor, ve kterém jsou uloženy pokyny a data, která mohou být

Z knihy autora

Formát čísel Nakonec jsme se dostali k formátu čísel. Již jsem to zmínil vícekrát, nyní dám všechno na police (i když už jste pochopili obecný význam). Čísla v aplikaci Excel lze zobrazit v různých formátech. V této části si povíme, jaké formáty čísel existují a jak

Verze této odpovědi s dobrým obsahem a spoustou obsahu: http://www.cirosantilli.com/elf-hello-world (kliknutím sem získáte limit 30 kB)

Standardy

ELF je dán LSB:

  • základní generické: http://refspecs.linuxfoundation.org/LSB_4.1.0/LSB-Core-generic/LSB-Core-generic/elf-generic.html
  • jádro AMD64: http://refspecs.linuxfoundation.org/LSB_4.1.0/LSB-Core-AMD64/LSB-Core-AMD64/book1.html

LSB většinou odkazuje na jiné standardy s menšími rozšířeními, zejména:

    obecné (obě podle SCO):

    • System V ABI 4.1 (1997) http://www.sco.com/developers/devspecs/gabi41.pdf, ne 64 bitů, i když je pro něj vyhrazeno magické číslo. Totéž platí pro hlavní soubory.
    • Aktualizace System V ABI DRAFT 17 (2003) http://www.sco.com/developers/gabi/2003-12-17/contents.html přidává 64 bitů. Aktualizuje pouze kapitoly 4 a 5 předchozího dokumentu: zbytek zůstává platný a stále se na něj odkazuje.
  • specifická architektura:

    • IA-32: http://refspecs.linuxfoundation.org/LSB_4.1.0/LSB-Core-IA32/LSB-Core-IA32/elf-ia32.html ukazuje hlavně na 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, v zásadě odkazuje na http://www.x86-64.org/ dokumentace / abi.pdf

Praktický životopis najdete na:

Jeho strukturu lze zobrazit pomocí uživatelsky přívětivých metod, jako je readelf a objdump.

Vytvořte příklad

Pojďme si rozdělit minimální spustitelný příklad Linux x86-64:

Sekce .data hello_world db "Hello world!", 10 hello_world_len equ $ - hello_world sekce .text global _start _start: mov rax, 1 mov rdi, 1 mov rsi, hello_world mov rdx, hello_world_len syscall mov rax, 60 mov rdi, 0 syscall

Zkompilováno s

Nasm -w + vše -f elf64 -o "hello_world.o" "hello_world.asm" ld -o "hello_world.out" "hello_world.o"

  • NASM 2.10.09
  • Binutils verze 2.24 (obsahuje ld)
  • Ubuntu 14.04

Nepoužíváme program C, protože by to zkomplikovalo analýzu, která by byla na úrovni 2 :-)

hexadecimální reprezentace binárního souboru

hd hello_world.o hd hello_world.out

Globální struktura souborů

Soubor ELF obsahuje následující části:

  • Záhlaví ELF. Označuje polohu tabulky záhlaví sekcí a tabulek záhlaví programu.

    Tabulka záhlaví sekce (volitelná ve spustitelném souboru). Každá z nich má záhlaví sekce e_shnum, z nichž každá označuje polohu sekce.

    N oddílů s N<= e_shnum (необязательно в исполняемом файле)

    Tabulka záhlaví programu (pouze pro spustitelné soubory). Každý z nich má záhlaví programu e_phnum, z nichž každý ukazuje na polohu segmentu.

    N segmentů, s N<= e_phnum (необязательно в исполняемом файле)

Pořadí těchto částí není pevné: jedinou pevnou věcí je hlavička ELF, která musí být v souboru na prvním místě: Obecné dokumenty říkají:

Záhlaví ELF

Nejjednodušší způsob sledování titulu:

Readelf -h hello_world.o readelf -h hello_world.out

Bajt v souboru objektu:

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 | ..\u003e ............. | 00000020 00 00 00 00 00 00 00 00 40 00 00 00 00 00 00 00 | [chráněno e-mailem]| 00000030 00 00 00 00 40 00 00 00 00 00 40 00 07 00 03 00 |[chráněno e-mailem]@.....|

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 | ..\u003e .. [chráněno e-mailem]| 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 |[chráněno e-mailem]@.....|

Prezentovaná struktura:

Typedef struct (unsigned char e_ident; Elf64_Half e_type, Elf64_Half e_machine, Elf64_Word e_version, Elf64_Addr e_entry, Elf64_Off e_phoff, Elf64_Off e_shoff, Elf64_Word e_flags, Elf64_Half e_ehsize, Elf64_Half e_phentsize, Elf64_Half e_phnum, Elf64_Half e_shentsize, Elf64_Half e_shnum, Elf64_Half e_shstrndx) Elf64_Ehdr;

Ruční rozpad:

    0 0: EI_MAG \u003d 7f 45 4c 46 \u003d 0x7f „E“, „L“, „F“: magické číslo ELF

    0 4: EI_CLASS \u003d 02 \u003d ELFCLASS64: 64bitový elf

    05: EI_DATA \u003d 01 \u003d ELFDATA2LSB: Big End Data

    0 6: EI_VERSION \u003d 01: verze formátu

    0 7: EI_OSABI (pouze 2003) \u003d 00 \u003d ELFOSABI_NONE: žádná rozšíření.

    0 8: EI_PAD \u003d 8x 00: Vyhrazené bajty. Mělo by být nastaveno na 0.

    1 0: e_type \u003d 01 00 \u003d 1 (big endian) \u003d ET_REl: přemístitelný formát

    Spustitelný soubor je 02 00 pro ET_EXEC.

    1 2: e_machine \u003d 3e 00 \u003d 62 \u003d EM_X86_64: architektura AMD64

    1 4: e_version \u003d 01 00 00 00: by měl být 1

    1 8: e_entry \u003d 8x 00: vstupní bod adresy spuštění, nebo 0, pokud není použitelné jako u souboru objektu, protože neexistuje žádný vstupní bod.

    Ve spustitelném souboru je to b0 00 40 00 00 00 00 00. TODO: co jiného můžeme nainstalovat? Zdá se, že jádro vkládá IP přímo do této hodnoty, není pevně zakódováno.

    2 0: e_phoff \u003d 8x 00: offset tabulky záhlaví programu, 0 pokud není.

    40 00 00 00 ve spustitelném souboru, to znamená, že začíná hned po hlavičce ELF.

    2 8: e_shoff \u003d 40 7x 00 \u003d 0x40: offset souboru tabulky záhlaví sekce, 0 pokud není.

    3 0: e_flags \u003d 00 00 00 00 TODO. Speciálně pro Arch.

    3 4: e_ehsize \u003d 40 00: Velikost této hlavičky elfa. Proč toto pole? Jak se to může změnit?

    3 6: e_phentsize \u003d 00 00: Velikost každého záhlaví programu, 0, pokud není.

    38 00 ve spustitelném souboru: délka souboru je 56 bajtů

    3 8: e_phnum \u003d 00 00: Počet položek záhlaví programu, 0, pokud ne.

    02 00 ve spustitelném souboru: existují 2 položky.

    3 A: e_shentsize a e_shnum \u003d 40 00 07 00: velikost záhlaví sekce a počet záznamů

Tabulka záhlaví sekce

Pole struktur Elf64_Shdr.

Každá položka obsahuje metadata o této sekci.

e_shoff záhlaví ELF zde dává počáteční pozici, 0x40.

e_shentsize a e_shnum z hlavičky ELF říkají, že máme 7 záznamů, každý dlouhý 0x40.

Takže tabulka bere bajty od 0x40 do 0x40 + 7 + 0x40 - 1 \u003d 0x1FF.

Některé názvy sekcí jsou vyhrazeny pro určité typy sekcí: například http://www.sco.com/developers/gabi/2003-12-17/ch4.sheader.html#special_sections. .text vyžaduje typ SHT_PROGBITS a SHF_ALLOC + SHF_EXECINSTR

readelf -S hello_world.o:

K dispozici je 7 hlaviček sekcí, počínaje offsetem 0x40: Záhlaví sekcí: Název Typ Adresa Posun Velikost Velikost Parametry odkazu Informace Zarovnat [0] NULL 0000000000000000 00000000 0000000000000000 0000000000000000 0 0 [1]. Data PROGBITS 0000000000000000 00000200 000000000000000000000000000000 2] .text PROGBITS 0000000000000000 00000210 00000000000000 00 0000000000000000 AX 0 0 16 [3] .shstrtab STRTAB 0000000000000000 00000240 0000000000000032 0000000000000000 0 0 1 [4] .symtab000000000 1 [6] .rela.text RELA 0000000000000000 00000370 0000000000000018 0000000000000018 4 2 4 Klíč k příznakům: W (zápis), A (aloc), X (provedení), M (sloučení), S (řetězce), l (velký) I (informace), L (pořadí odkazů), G (skupina), T (TLS), E (vyloučit), x (neznámé) O (je vyžadováno další zpracování OS) o (specifické pro OS), p (specifické pro procesor)

struktura představovaná každou položkou:

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_Xign; Elfd64_Word) elfd64_Word

Sekce

Sekce rejstříku 0

Obsažené v bajtech od 0x40 do 0x7F.

První část je vždy kouzelná: http://www.sco.com/developers/gabi/2003-12-17/ch4.sheader.html říká:

Pokud je počet sekcí větší nebo roven SHN_LORESERVE (0xff00), je e_shnum SHN_UNDEF (0) a skutečný počet položek tabulky záhlaví sekce je obsažen v poli sh_size záhlaví sekce s indexem 0 (jinak člen sh_size původního záznamu obsahuje 0).

Na obrázku 4-7 jsou další magické sekce: Rejstříky speciálních sekcí.

U indexu 0 je vyžadován SHT_NULL. Existují pro to další použití: Jaké je použití klauzule SHT_NULL v ELF? ?

datová část

Data jsou v sekci 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 02 00 00 00 00 00 00 | ................ | 000000a0 0d 00 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 00 | ................ |

    Zde 1 říká, že název této sekce začíná u prvního znaku této sekce a končí u prvního znaku NUL, který tvoří řetězec.data.

    Data jsou jedním z názvů sekcí, které mají předdefinovaný význam http://www.sco.com/developers/gabi/2003-12-17/ch4.strtab.html

    Tyto sekce ukládají inicializovaná data, která přispívají k obrazu paměti programu.

  • 80 4: sh_type \u003d 01 00 00 00: SHT_PROGBITS: Obsah sekce není specifikován ELF, pouze tím, jak jej program interpretuje. Dobře, protože .data.

    80 8: sh_flags \u003d 03 7x 00: SHF_ALLOC a SHF_EXECINSTR: http://www.sco.com/developers/gabi/2003-12-17/ch4.sheader.html#sh_flags podle požadavků z datové sekce

    90 0: sh_addr \u003d 8x 00: na jakou virtuální adresu bude sekce umístěna za běhu, 0 pokud není umístěna

    90 8: sh_offset \u003d 00 02 00 00 00 00 00 00 \u003d 0x200: počet bytů od začátku programu do prvního bajtu v této sekci

    a0 0: sh_size \u003d 0d 00 00 00 00 00 00 00

    Pokud vezmeme 0xD bajtů počínaje sh_offset 200, vidíme:

    00000200 48 65 6c 6c 6f 20 77 6f 72 6c 64 21 0a 00 | Hello world! .. |

    AHA! Takže náš řetězec „Hello world!“ je v datové sekci, jak jsme řekli, je to na NASM.

    Jakmile skončíme s hd, podíváme se na to jako:

    Readelf -x .data hello_world.o

    které výstupy:

    Šestihranný výpis sekce „.data“: 0x00000000 48656c6c 6f20776f 726c6421 0a Ahoj světe!.

    NASM nastavuje pro tuto sekci slušné vlastnosti, protože na ni kouzelně odkazuje. Data: http://www.nasm.us/doc/nasmdoc7.html#section-7.9.2

    Všimněte si také, že to byla špatná volba sekce: dobrý kompilátor C by místo toho vložil řádek do .rodata, protože je jen pro čtení, což by OS umožnilo pokračovat v optimalizaci.

    a0 8: sh_link a sh_info \u003d 8x 0: nevztahují se na typ této sekce. http://www.sco.com/developers/gabi/2003-12-17/ch4.sheader.html#special_sections

    b0 0: sh_addralign \u003d 04 \u003d TODO: proč je toto zarovnání nutné? Je to jen pro sh_addr a také pro postavy uvnitř sh_addr?

    b0 8: sh_entsize \u003d 00 \u003d Oddíl neobsahuje tabulku. Pokud! \u003d 0, znamená to, že oddíl obsahuje tabulku záznamů pevné velikosti. V tomto souboru vidíme z výstupu čtení, že tomu tak je pro sekce .symtab a .rela.text.

.textová sekce

Nyní, když jsme udělali jednu sekci ručně, dejte ji absolvovat a použijte readelf -S ostatních sekcí.

Název Typ Adresa Ofsetová velikost EntSize Vlajky Informace o odkazu Zarovnat [2] .text PROGBITS 0000000000000000 00000210 0000000000000027 0000000000000000 AX 0 0 16

Text je spustitelný, ale nelze jej zapisovat: pokud se pokusíme do něj zapsat segfaults Linuxu. Uvidíme, jestli kód opravdu máme:

Objdump -d hello_world.o

Hello_world.o: formát souboru elf64-x86-64 Demontáž sekce .text: 0000000000000000<_start>: 0: b8 01 00 00 00 mov $ 0x1,% eax 5: bf 01 00 00 00 mov $ 0x1,% edi a: 48 be 00 00 00 00 00 movabs $ 0x0,% rsi 11: 00 00 00 14: ba 0d 00 00 00 mov $ 0xd,% edx 19: 0f 05 syscall 1b: b8 3c 00 00 00 mov $ 0x3c,% eax 20: bf 00 00 00 00 mov $ 0x0,% edi 25: 0f 05 syscall

Pokud máme grep b8 01 00 00 na hd, vidíme, že se to děje pouze na 00000210, což říká tato část. A velikost je 27, což také odpovídá. Proto musíme mluvit o správné části.

Vypadá to jako správný kód: zápis následovaný ukončením.

Nejzajímavější částí je linka a, která dělá:

Movabs $ 0x0,% rsi

předat adresu řetězce do systémového volání. Aktuálně je 0x0 pouze zástupný symbol. Po propojení se změní:

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

Tato úprava je možná kvůli údajům v sekci .rela.text.

SHT_STRTAB

Sekce s sh_type \u003d\u003d SHT_STRTAB se nazývají řetězcové tabulky.

Takové oddíly používají jiné oddíly, když se mají použít názvy řetězců. Sekce Použití říká:

  • jakou linku používají
  • jaký je index v cílové tabulce řádků, kde řádek začíná

Například bychom mohli mít tabulku řetězců obsahující: TODO: měli bychom začít s \\ 0?

Data: \\ 0 a b c \\ 0 d e f \\ 0 Index: 0 1 2 3 4 5 6 7 8

A pokud chce jiná sekce použít linii d e f, musí ukazovat na index 5 této sekce (písmeno d).

Pozoruhodné tabulky řetězců:

  • .ststab
  • .stab

.ststab

Typ sekce: sh_type \u003d\u003d SHT_STRTAB.

Obecný název: Záhlaví záhlaví sekce.

Název sekce .shstrtab je vyhrazen. Norma říká:

Tato část obsahuje názvy sekcí.

Tato část určuje pole e_shstrnd samotné hlavičky ELF.

Indexy řádků této sekce jsou označeny polem sh_name záhlaví sekce, které označují řetězce.

Tato část neurčuje SHF_ALLOC, takže se ve spustitelném souboru nezobrazí.

Readelf -x .shstrtab hello_world.o

Šestihranný výpis sekce „.shstrtab“: 0x00000000 002e6461 7461002e 74657874 002e7368 ..data..text..sh 0x00000010 73747274 6162002e 73796d74 6162002e strtab..symtab .. 0x00000020 73747e7650000 600 0200 700 0

Data v této části jsou ve pevném formátu: http://www.sco.com/developers/gabi/2003-12-17/ch4.strtab.html

Podíváme-li se na názvy ostatních částí, zjistíme, že všechny obsahují například čísla. section.text má číslo 7.

Poté každý řádek končí, když je nalezen první znak NUL, např. znak 12 \\ 0 bezprostředně za .text \\ 0.

.symtab

Typ sekce: sh_type \u003d\u003d SHT_SYMTAB.

Obecný název: tabulka symbolů.

Nejprve si všimněte, že:

  • sh_link \u003d 5
  • sh_info \u003d 6

V části SHT_SYMTAB tato čísla znamenají, že:

  • Struny
  • které dávají názvy symbolů jsou v sekci 5, .strtab
  • údaje o přemístění jsou v části 6, .rela.text

Dobrý nástroj na vysoké úrovni pro demontáž této sekce:

Nm hello_world.o

který dává:

0000000000000000 T _start 0000000000000000 d hello_world 00000000000000000d hello_world_len

Toto je však reprezentace na vysoké úrovni, která vynechává určité typy symbolů a označuje symboly. Podrobnější demontáž lze získat pomocí:

Readelf -s hello_world.o

který dává:

Tabulka symbolů ".symtab" obsahuje 7 záznamů: Číslo: Hodnota Velikost Typ Vazba Vis Ndx Název 0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND 1: 0000000000000000 0 FILE LOCAL DEFAULT ABS hello_world.asm 2: 00000000000000 0 SEKCE LOKÁLNÍ VÝCHOZÍ 1 00:00 SEKCE LOCAL DEFAULT 2 4: 0000000000000000 0 NOTYPE LOCAL DEFAULT 1 hello_world 5: 000000000000000d 0 NOTYPE LOCAL DEFAULT ABS hello_world_len 6: 0000000000000000 0 NOTYPE GLOBAL DEFAULT 2 _start

Formát binární tabulky je dokumentován na adrese http://www.sco.com/developers/gabi/2003-12-17/ch4.symtab.html

Readelf -x .symtab hello_world.o

Co dává:

Šestihranný výpis sekce „.symtab“: 0x00000000 00000000 00000000 00000000 00000000 ................ 0x00000010 00000000 00000000 01000000 0400f1ff ............... . 0x00000020 00000000 00000000 00000000 00000000 ................ 0x00000030 00000000 03000100 00000000 00000000 ................ 0x00000040 00000000 00000000 00000000 03000200 .. .............. 0x00000050 00000000 00000000 00000000 00000000 ................ 0x00000060 11000000 00000100 00000000 00000000 .......... ...... 0x00000070 00000000 00000000 1d000000 0000f1ff ................ 0x00000080 0d000000 00000000 00000000 00000000 ................ 0x00000090 2d000000 10000200 00000000 00000000 -............... 0x000000a0 00000000 00000000 ........

Záznamy jsou typu:

Typedef struct (Elf64_Word st_name; nepodepsaný znak st_info; nepodepsaný znak st_other; Elf64_Half st_shndx; Elf64_Addr st_value; Elf64_Xword st_size;) Elf64_Sym;

Stejně jako u tabulky oddílů je první položka magická a dána pevnými nesmyslnými hodnotami.

Záznam 1 má ELF64_R_TYPE \u003d\u003d STT_FILE. ELF64_R_TYPE pokračuje uvnitř st_info.

Analýza bytů:

    10 8: st_name \u003d 01000000 \u003d znak 1 v souboru .strtab, který až do dalšího \\ 0 dělá hello_world.asm

    Tento soubor s informacemi může linker použít k určení, které segmenty segmentu přejdou.

    10 12: st_info \u003d 04

    Bity 0-3 \u003d ELF64_R_TYPE \u003d Typ \u003d 4 \u003d STT_FILE: Hlavním účelem této položky je použít st_name k označení názvu souboru vygenerovaného tímto souborem objektu.

    Bity 4-7 \u003d ELF64_ST_BIND \u003d Vazba \u003d 0 \u003d STB_LOCAL. Požadovaná hodnota pro STT_FILE.

    10 13: st_shndx \u003d Tabulka znaků Tabulka záhlaví Index \u003d f1ff \u003d SHN_ABS. Vyžadováno pro STT_FILE.

    20 0: st_value \u003d 8x 00: vyžadováno pro hodnotu pro STT_FILE

    20 8: st_size \u003d 8x 00: žádná přidělená velikost

Nyní od sebe rychle interpretujeme zbytek.

STT_SECTION

Existují dva takové prvky, jeden směřující k .data a druhý k .text (indexy oddílů 1 a 2).

Číslo: Hodnota Velikost Typ Vazba Vis Ndx Název 2: 0000000000000000 0 SEKCE MÍSTNÍ VÝCHOZÍ 1 3: 0000000000000000 0 SEKCE MÍSTNÍ VÝCHOZÍ 2

TODO, jaký je jejich účel?

STT_NOTYPE

Poté zadejte nejdůležitější znaky:

Číslo: Hodnota Velikost Typ Vazba Ndx Název 4: 0000000000000000 0 NOTYPE LOCAL DEFAULT 1 hello_world 5: 000000000000000d 0 NOTYPE LOCAL DEFAULT ABS hello_world_len 6: 0000000000000000 0 NOTY GLOBAL DEFAULT 2 _start String

hello_world je v sekci .data (index 1). Tato hodnota je 0: ukazuje na první bajt této sekce.

Start je označen GLOBÁLNÍ viditelností, jak jsme psali:

Globální _start

na NASM. To je nutné, protože to musí být považováno za vstupní bod. Na rozdíl od C jsou štítky NASM ve výchozím nastavení místní.

hello_world_len ukazuje na speciální st_shndx \u003d\u003d SHN_ABS \u003d\u003d 0xF1FF.

0xF1FF je vybrán tak, aby nedocházelo ke konfliktu s jinými částmi.

st_value \u003d\u003d 0xD \u003d\u003d 13, což je hodnota, kterou jsme tam uložili v sestavě: délka Hello World! ...

To znamená, že pohyb nebude mít na tuto hodnotu vliv: jedná se o konstantu.

Toto je malá optimalizace, kterou pro nás provádí náš assembler a má podporu ELF.

Pokud bychom kdekoli použili adresu hello_world_len, assembler by ji nemohl označit jako SHN_ABS a později by linker měl další tah.

SHT_SYMTAB ve spustitelném souboru

Ve výchozím nastavení NASM umístí .symtab do spustitelného souboru.

Používá se pouze pro ladění. Bez symbolů jsme úplně slepí a musíme všechno přepracovat.

Můžete jej odstranit pomocí objcopy a spustitelný soubor bude i nadále fungovat. Takové spustitelné soubory se nazývají rozdělené spustitelné soubory.

.stab

Obsahuje řádky pro tabulku symbolů.

V této části sh_type \u003d\u003d SHT_STRTAB.

Ukázáno na sh_link \u003d\u003d 5 section.symtab.

Readelf -x .strtab hello_world.o

Šestihranný výpis sekce „.strtab“: 0x00000000 0068656c 6c6f5f77 6f726c64 2e61736d .hello_world.asm 0x00000010 0068656c 6c6f5f77 6f726c64 0068656c .hello_world.hel 0x0000f5000_ 6f62636c

To znamená, že se jedná o omezení úrovně ELF, že globály nemohou obsahovat znaky NUL.

.rela.text

Typ sekce: sh_type \u003d\u003d SHT_RELA.

Obecný název: přesunout sekci.

Rela.text obsahuje data o přemístění, která určují, jak by měla být adresa změněna, když je propojen poslední spustitelný soubor. To označuje bajty textové oblasti, které se mají změnit při propojení se správnými paměťovými místy.

V zásadě převádí text objektu obsahující zástupnou adresu 0x0:

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

ke skutečnému spustitelnému kódu obsahujícímu finální 0x6000d8:

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

Specifikovaný sh_info \u003d 6 section.symtab.

readelf -r hello_world.o dává:

Sekce přemístění ".rela.text" na offsetu 0x3b0 obsahuje 1 záznamů: Informace o ofsetu Typ Sym. Hodnota Sym. Název + Přidat 00000000000c 000200000001 R_X86_64_64 0000000000000000. Data + 0

Sekce ve spustitelném souboru neexistuje.

Skutečné bajty:

00000370 0c 00 00 00 00 00 00 00 00 01 00 00 00 02 00 00 00 | ................ | 00000 380 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |

Představená struktura:

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

    370 0: r_offset \u003d 0xC: adresa na adresu.text, jehož adresa bude změněna

    370 8: r_info \u003d 0x200000001. Obsahuje 2 pole:

    • ELF64_R_TYPE \u003d 0x1: Hodnota závisí na přesné architektuře.
    • ELF64_R_SYM \u003d 0x2: index části, na kterou adresa odkazuje, tedy data. Která jsou v indexu 2.

    AMD64 ABI říká, že typ 1 se nazývá R_X86_64_64 a že představuje operaci S + A, kde:

    • S: hodnota znaku v souboru objektu, zde 0, protože ukazujeme na 00 00 00 00 00 00 00 00 z movabs $ 0x0,% rsi
    • a: doplněk přítomný v poli r_added

    Tato adresa je přidána do sekce, ve které přesun funguje.

    Tato operace přesunutí funguje na 8 bajtech.

    380 0: r_addend \u003d 0

V našem příkladu jsme tedy dospěli k závěru, že nová adresa bude: S + A \u003d .data + 0, a tedy první v datové sekci.

Tabulka s názvy programů

Zobrazeno pouze ve spustitelném souboru.

Obsahuje informace o tom, jak má být spustitelný soubor umístěn do virtuální paměti procesu.

Spustitelný soubor je vytvořen souborem objektu linkerem. Hlavní úkoly, které linker provádí:

    určit, které části objektových souborů přejdou do kterých segmentů spustitelného souboru.

    V Binutils se scvrkává na analýzu linkerového skriptu a práci s mnoha výchozími hodnotami.

    Můžete získat linkerový skript používaný s ld --verbose a nastavit vlastní pomocí ld -T.

    procházet textovými částmi. Záleží na tom, jak se do paměti vejde více sekcí.

readelf -l hello_world.out dává:

Typ souboru elfů je EXEC (Spustitelný soubor) Vstupní bod 0x4000b0 K dispozici jsou 2 záhlaví programu, počínaje offsetem 64 Záhlaví programu: Typ Offset VirtAddr PhysAddr FileSiz MemSiz Flags Align LOAD 0x0000000000000000 0x0000000000400000 0x00000000400000000000000000000000000000000000000000na mapování segmentů: Segmentové sekce ... 00 .text 01. data

V záhlaví ELF nám e_phoff, e_phnum a e_phentsize řekly, že existují 2 programová záhlaví, která začínají na 0x40 a jsou každá dlouhá 0x38 bajtů, takže jsou:

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 |[chráněno e-mailem]@ ..... | 00000060 d7 00 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 00 d8 00 60 00 00 00 00 00 | .......... `..... | 00000090 d8 00 60 00 00 00 00 00 0d 00 00 00 00 00 00 00 | ..` ............. | 000000a0 0d 00 00 00 00 00 00 00 00 00 20 00 00 00 00 00 | .......... ..... | typedef struct (Elf64_Word p_type; Elf64_Word p_flags; Elf64_Off p_offset; Elf64_Addr p_vaddr; Elf64_Addr p_paddr; Elf64_Xword p_filesz; Elf64_Xword p_memsz; Elf64_Xword p_vaddr)

Rozpis první:

  • 40 0: p_type \u003d 01 00 00 00 \u003d PT_LOAD: TODO. Myslím, že to znamená, že bude načten do paměti. Jiné typy nemusí být.
  • 40 4: p_flags \u003d 05 00 00 00 \u003d oprávnění ke spouštění a čtení, nepište TODO
  • 40 8: p_offset \u003d 8x 00 TODO: co to je? Vypadá to jako vyrovnání od začátku segmentů. Bude to ale znamenat, že některé segmenty jsou propletené? Můžete si s tím trochu hrát: gcc -Wl, -Ttext-segment \u003d 0x400030 hello_world.c
  • 50 0: p_vaddr \u003d 00 00 40 00 00 00 00 00: počáteční adresa virtuální paměti, do které se má tento segment načíst
  • 50 8: p_paddr \u003d 00 00 40 00 00 00 00 00: počáteční fyzická adresa pro načtení do paměti. Pouze otázky pro systémy, kde program může zjistit fyzickou adresu. Jinak jako v systémy V může být cokoli. Vypadá to, že NASM zkopíruje pouze p_vaddrr
  • 60 0: p_filesz \u003d d7 00 00 00 00 00 00 00: TODO vs p_memsz
  • 60 8: p_memsz \u003d d7 00 00 00 00 00 00 00: TODO
  • 70 0: p_align \u003d 00 00 20 00 00 00 00 00: 0 nebo 1 znamená, že není nutné žádné zarovnání TODO, co to znamená? jinak nadbytečné s jinými poli

Druhý je podobný.

Mapování řezů do segmentů:

část readelf nám říká, že:

  • 0 - segment.text. Ano, takže je spustitelný a nelze do něj zapisovat.
  • 1 - segment.data.
Standardní nástroje Vývojáři zkompilují váš program do souboru ELF (Executable and Linkable Format) s možností zahrnout informace o ladění. Specifikaci formátu lze přečíst. Každá architektura má navíc svá vlastní specifika, například funkce ARM. Podívejme se krátce na tento formát.
Spustitelný soubor ELF se skládá z následujících částí:
1. Záhlaví (záhlaví ELF)
Obsahuje obecná informace o souboru a jeho hlavních charakteristikách.
2. Tabulka záhlaví programu
Toto je tabulka korespondence sekcí souborů s segmenty paměti, indikuje zavaděči, do které oblasti paměti má zapisovat jednotlivé sekce.
3. Sekce
Sekce obsahují všechny informace v souboru (program, data, informace o ladění atd.)
Každá sekce má typ, název a další parametry. Sekce „.text“ obvykle ukládá kód, „.symtab“ obsahuje tabulku symbolů programu (názvy souborů, procedur a proměnných), „.strtab“ obsahuje tabulku řetězců, sekce s předponou „.debug_“ obsahují informace o ladění atd. .d. Soubor musí navíc obsahovat prázdnou část s indexem 0.
4. Tabulka záhlaví sekce
Toto je tabulka obsahující řadu záhlaví sekcí.
Formát je podrobněji popsán v části Vytváření ELF.

Přehled DWARF

DWARF je standardizovaný formát informací o ladění. Standard lze stáhnout z oficiálních webových stránek. K dispozici je také vynikající shrnutí formátu: Úvod do formátu ladění DWARF (Michael J. Eager).
Proč potřebuji informace o ladění? Umožňuje vám:
  • nastavit zarážky ne na fyzickou adresu, ale na číslo řádku ve zdrojovém souboru nebo na název funkce
  • zobrazovat a měnit hodnoty globálních a lokálních proměnných a také funkční parametry
  • zobrazit zásobník hovorů (backtrace)
  • spusťte program krok za krokem nikoli pomocí jedné montážní instrukce, ale řádky zdrojového kódu
Tyto informace jsou uloženy ve stromové struktuře. Každý uzel stromu má rodiče, může mít potomky a nazývá se DIE (položka ladění informací). Každý uzel má vlastní značku (typ) a seznam atributů (vlastností), které uzel popisují. Atributy mohou obsahovat cokoli, co se vám líbí, například data nebo odkazy na jiné uzly. Kromě toho jsou mimo strom uloženy informace.
Uzly jsou rozděleny do dvou hlavních typů: uzly popisující data a uzly popisující kód.
Uzly popisující údaje:
  1. Typy dat:
    • Základní datové typy (uzel s typem DW_TAG_base_type), například int typu v C.
    • Složené datové typy (ukazatele atd.)
    • Pole
    • Struktury, třídy, odbory, rozhraní
  2. Datové objekty:
    • konstanty
    • funkční parametry
    • proměnné
    • atd.
Každý datový objekt má atribut DW_AT_location, který označuje způsob výpočtu adresy, kde jsou data umístěna. Proměnná může mít například pevnou adresu, může být v registru nebo v zásobníku nebo může být členem třídy nebo objektu. Tuto adresu lze vypočítat poměrně složitým způsobem, proto standard poskytuje takzvané Location Expressions, které mohou obsahovat posloupnost příkazů speciálního interního zásobníku.
Uzly popisující kód:
  1. Procedury (funkce) - uzly se značkou DW_TAG_subprogram. Potomci uzlů mohou obsahovat popis proměnných - parametrů funkce a lokálních proměnných funkce.
  2. Kompilační jednotka. Obsahuje informace o programu a je nadřazeným prvkem všech ostatních uzlů.
Informace popsané výše jsou v částech „.debug_info“ a „.debug_abbrev“.
Jiná informace:
  • Informace o čísle řádku (část „.debug_line“)
  • Makro informace (část „.debug_macinfo“)
  • Informace o rámcovém volání (část „.debug_frame“)

Vytvoření ELF

Vytvoříme soubory ve formátu EFL pomocí knihovny libelf z balíčku elfutils. Na internetu existuje dobrý článek o používání libelfu - LibELF by Example (vytváření souborů v něm je bohužel popsáno velmi stručně) a také dokumentace.
Vytváření souborů se skládá z několika fází:
  1. Inicializace svobody
  2. Vytvoření záhlaví souboru (záhlaví ELF)
  3. Vytvoření tabulky záhlaví programu
  4. Vytváření sekcí
  5. Nahrávání souborů
Podívejme se na fáze podrobněji
Inicializace svobody
Nejprve budete muset zavolat funkci elf_version (EV_CURRENT) a zkontrolovat výsledek. Pokud je EV_NONE, došlo k chybě a nelze podniknout žádné další kroky. Pak musíme vytvořit soubor, který na disku potřebujeme, získat jeho deskriptor a předat jej funkci elf_begin:
Elf * elf_begin (int fd, Elf_Cmd cmd, Elf * elf)
  • fd - popisovač právě otevřeného souboru
  • cmd - režim (ELF_C_READ pro čtení informací, ELF_C_WRITE pro zápis nebo ELF_C_RDWR pro čtení / zápis), musí odpovídat režimu otevřeného souboru (v našem případě ELF_C_WRITE)
  • elf - potřebné pouze pro práci s archivními soubory (.a), v našem případě je třeba přenést 0
Funkce vrátí ukazatel na vygenerovaný deskriptor, který se použije ve všech libelf funkcích, při chybě se vrátí 0.
Vytvoření záhlaví
Nové záhlaví souboru je vytvořeno funkcí elf32_newehdr:
Elf32_Ehdr * elf32_newehdr (Elf * elf);
  • elf - popisovač vrácený elf_begin
Vrátí 0 při chybě nebo ukazatel na strukturu - záhlaví souboru ELF:
# define EI_NIDENT 16 typedef struct (unsigned char e_ident; Elf32_Half e_type, Elf32_Half e_machine, Elf32_Word e_version, Elf32_Addr e_entry, Elf32_Off e_phoff, Elf32_Off e_shoff, Elf32_Word e_flags, Elf32_Half e_ehsize, Elf32_Half e_phentsize, Elf32_Half e_phnum, Elf32_Half e_shentsize, Elf32_Half e_shnum, Elf32_Half e_shstrndx; ) Elf32_Ehdr;

Některá z jeho polí jsou vyplněna standardním způsobem, některá je třeba vyplnit:

  • e_ident - bajtové pole identifikace, má následující indexy:
    • EI_MAG0, EI_MAG1, EI_MAG2, EI_MAG3 - tyto 4 bajty by měly obsahovat symboly 0x7f, „ELF“, které pro nás již funkce elf32_newehdr udělala
    • EI_DATA - označuje typ kódování dat v souboru: ELFDATA2LSB nebo ELFDATA2MSB. Musíte nainstalovat ELFDATA2LSB takto: e_ident \u003d ELFDATA2LSB
    • EI_VERSION - verze záhlaví souboru, která je pro nás již nainstalována
    • EI_PAD - nedotýkejte se
  • e_type - typ souboru, může být ET_NONE - žádný typ, ET_REL - přemístitelný soubor, ET_EXEC - spustitelný soubor, ET_DYN - soubor sdíleného objektu atd. Musíme nastavit typ souboru na ET_EXEC
  • e_machine - pro tento soubor je vyžadována architektura, například EM_386 - pro architekturu Intel, pro ARM sem musíme napsat EM_ARM (40) - viz ARF architektura ELF
  • e_version - verze souboru, musí být nastavena na EV_CURRENT
  • e_entry - adresa vstupního bodu, není pro nás nezbytná
  • e_phoff - offset v souboru záhlaví programu, e_shoff - offset záhlaví sekce, nevyplňovat
  • e_flags - příznaky specifické pro procesor, pro naši architekturu (Cortex-M3) by mělo být nastaveno na 0x05000000 (ABI verze 5)
  • e_ehsize, e_phentsize, e_phnum, e_shentsize, e_shnum - nedotýkejte se
  • e_shstrndx - obsahuje číslo sekce, která obsahuje tabulku řetězců se záhlavími sekce. Protože zatím nemáme žádné sekce, nainstalujeme toto číslo později.
Vytvoření záhlaví programu
Jak již bylo zmíněno, Tabulka záhlaví programu je tabulka mapování oddílů ze souboru do paměti, která sděluje zavaděči, kam má zapsat každou sekci. Záhlaví je vytvořeno pomocí funkce elf32_newphdr:
Elf32_Phdr * elf32_newphdr (Elf * elf, count_t count);
  • elf je náš deskriptor
  • count - počet prvků tabulky, které se mají vytvořit. Protože budeme mít pouze jednu sekci (s programovým kódem), bude se počet rovnat 1.
Vrátí 0 při chybě nebo ukazatel na záhlaví programu.
Každý prvek v tabulce záhlaví je popsán touto strukturou:
typedef struct (Elf32_Word p_type; Elf32_Off p_offset; Elf32_Addr p_vaddr; Elf32_Addr p_paddr; Elf32_Word p_filesz; Elf32_Word p_memsz; Elf32_Word p_flags; Elfal32_Word p
  • p_type - typ segmentu (sekce), zde musíme specifikovat PT_LOAD - načtený segment
  • p_offset - posuny v souboru, odkud začínají data sekce, která bude načtena do paměti. Máme tento oddíl.text, který bude umístěn bezprostředně za záhlaví souboru a záhlaví programu, můžeme vypočítat offset jako součet délek těchto záhlaví. Délka libovolného typu lze zjistit pomocí funkce elf32_fsize:
    size_t elf32_fsize (typ Elf_Type, počet size_t, nepodepsaná verze int); typ - zde konstanta ELF_T_xxx, budeme potřebovat velikosti ELF_T_EHDR a ELF_T_PHDR; count - počet prvků požadovaného typu, verze - musí být nastaven na EV_CURRENT
  • p_vaddr, p_paddr - virtuální a fyzická adresa, na kterou se načte obsah sekce. Protože nemáme žádné virtuální adresy, nastavili jsme ji rovnou fyzické, v nejjednodušším případě - 0, protože právě zde bude načten náš program.
  • p_filesz, p_memsz - velikost sekce v souboru a paměti. Jsou pro nás stejné, ale protože zatím neexistují žádné sekce s programovým kódem, nainstalujeme je později.
  • p_flags - oprávnění pro načtený segment paměti. Může být PF_R - čtení, PF_W - zápis, PF_X - provedení nebo kombinace obou. Nastavte p_flags rovné PF_R + PF_X
  • p_align - zarovnání segmentu, máme 4
Vytváření sekcí
Po vytvoření nadpisů můžete začít vytvářet sekce. Prázdná část se vytvoří pomocí funkce elf_newscn:
Elf_Scn * elf_newscn (Elf * elf);
  • elf - popisovač, který dříve vrátil elf_begin
Funkce vrátí ukazatel na sekci nebo 0 při chybě.
Po vytvoření sekce musíte vyplnit záhlaví sekce a vytvořit deskriptor dat sekce.
Ukazatel na záhlaví sekce můžeme získat pomocí funkce elf32_getshdr:
Elf32_Shdr * elf32_getshdr (Elf_Scn * scn);
  • scn je ukazatel sekce, který jsme dostali z funkce elf_newscn.
Záhlaví sekce vypadá takto:
typedef struct (Elf32_Word sh_name; Elf32_Word sh_type; Elf32_Word sh_flags; Elf32_Addr sh_addr; Elf32_Off sh_offset; Elf32_Word sh_size; Elf32_Word sh_link; Elf32_Word sh_Wign; Elf32_Word_info_info
  • sh_name - název sekce - offset v řetězcové tabulce hlaviček sekcí (section.shstrtab) - viz níže „Řetězcové tabulky“
  • sh_type - typ obsahu sekce, pro sekci s programovým kódem musíte nastavit SHT_PROGBITS, pro sekce s řetězcovou tabulkou - SHT_STRTAB, pro tabulku symbolů - SHT_SYMTAB
  • sh_flags jsou příznaky sekce, které lze kombinovat, a které potřebujeme pouze tři:
    • SHF_ALLOC - znamená, že sekce bude načtena do paměti
    • SHF_EXECINSTR - sekce obsahuje spustitelný kód
    • SHF_STRINGS - sekce obsahuje tabulku řetězců
    Proto pro sekci .text s programem musíte nastavit příznaky SHF_ALLOC + SHF_EXECINSTR
  • sh_addr - adresa, na kterou se sekce načte do paměti
  • sh_offset - posun sekce v souboru - nedotýkejte se, knihovna se nainstaluje za nás
  • sh_size - velikost sekce - nedotýkejte se
  • sh_link - obsahuje číslo propojené sekce, je nutné ji propojit s odpovídající tabulkou řádků (viz níže)
  • sh_info - další informace v závislosti na typu sekce, nastaveno na 0
  • sh_addralign - zarovnání adresy, nedotýkejte se
  • sh_entsize - pokud se část skládá z několika prvků stejné délky, označuje délku takového prvku, nedotýkejte se
Po vyplnění záhlaví musíte vytvořit deskriptor dat sekce pomocí funkce elf_newdata:
Elf_Data * elf_newdata (Elf_Scn * scn);
  • scn je právě přijatý ukazatel na novou sekci.
Funkce vrací 0 při chybě nebo ukazatel na strukturu Elf_Data, která má být vyplněna:
typedef struct (void * d_buf; Elf_Type d_type; size_t d_size; off_t d_off; size_t d_align; nepodepsané d_version;) Elf_Data;
  • d_buf - ukazatel na data, která mají být zapsána do sekce
  • d_type - datový typ, ELF_T_BYTE je pro nás vhodný všude
  • d_size - velikost dat
  • d_off - offset v sekci, nastaveno na 0
  • d_align - zarovnání, lze nastavit na 1 - žádné zarovnání
  • d_version - verze, nezapomeňte nastavit na EV_CURRENT
Speciální sekce
Pro naše účely budeme muset vytvořit minimální požadovanou sadu sekcí:
  • .text - sekce s programovým kódem
  • .symtab - tabulka symbolů souboru
  • .strtab - tabulka řetězců obsahující názvy symbolů z části .symtab, protože ta neukládá samotné názvy, ale jejich indexy
  • .shstrtab - tabulka řetězců obsahující názvy sekcí
Všechny sekce jsou vytvořeny tak, jak je popsáno v předchozí části, ale každá speciální sekce má své vlastní charakteristiky.
Sekce. Text
Tato část obsahuje spustitelný kód, takže je třeba nastavit sh_type na SHT_PROGBITS, sh_flags - na SHF_EXECINSTR + SHF_ALLOC, sh_addr - nastavit na stejnou adresu, kam bude tento kód načten
Sekce.symtab
Tato část obsahuje popis všech symbolů (funkcí) programu a souborů, ve kterých byly popsány. Skládá se z následujících prvků o délce 16 bajtů:
typedef struct (Elf32_Word st_name; Elf32_Addr st_value; Elf32_Word st_size; nepodepsaný char st_info; nepodepsaný char st_other; Elf32_Half st_shndx;) Elf32_Sym;
  • st_name - název symbolu (index v tabulce řetězců.strtab)
  • st_value - hodnota (vstupní adresa pro funkci nebo 0 pro soubor). Protože Cortex-M3 má sadu instrukcí Thumb-2, musí být tato adresa lichá (skutečná adresa + 1)
  • st_size - délka kódu funkce (0 pro soubor)
  • st_info - typ symbolu a jeho rozsah. K určení hodnoty tohoto pole existuje makro
    #define ELF32_ST_INFO (b, t) (((b)<<4)+((t)&0xf))
    kde b je rozsah at je typ znaku
    Rozsah může být STB_LOCAL (symbol není viditelný z jiných souborů objektů) nebo STB_GLOBAL (viditelný). Pro zjednodušení používáme STB_GLOBAL.
    Typ symbolu - STT_FUNC pro funkci, STT_FILE pro soubor
  • st_other - nastaveno na 0
  • st_shndx - index sekce, pro kterou je definován symbol (sekce index.text), nebo SHN_ABS pro soubor.
    Index sekce podle jejího scn deskriptoru lze určit pomocí elf_ndxscn:
    size_t elf_ndxscn (Elf_Scn * scn);

Tato sekce je vytvořena obvyklým způsobem, pouze sh_type je třeba nastavit na SHT_SYMTAB a index sekce .strtab je zapsán do pole sh_link, takže se tyto sekce propojí.
Strtab sekce
Tato část obsahuje názvy všech symbolů z části .symtab. Vytvořeno jako normální sekce, ale sh_type je třeba nastavit na SHT_STRTAB, sh_flags na SHF_STRINGS, takže se z této sekce stane tabulka řádků.
Data pro sekci lze sbírat při průchodu zdrojovým textem do pole, jehož ukazatel se poté zapíše do deskriptoru dat sekce (d_buf).
Sekce .shstrtab
Sekce - tabulka řetězců, obsahuje názvy všech částí souboru, včetně vlastního názvu. Je vytvořen stejným způsobem jako sekce .strtab. Po jeho vytvoření musí být jeho index zapsán do pole e_shstrndx v záhlaví souboru.
Řádkové tabulky
Řádkové tabulky obsahují souvislé řádky končící nulovým bajtem, první bajt v této tabulce musí být také 0. Index řádků v tabulce je pouze posun v bajtech od začátku tabulky, takže první řádek "name" má index 1, další řádek " var "má index 6.
Rejstřík 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
Nahrávání souborů
Záhlaví a sekce jsou tedy již vytvořeny, nyní je třeba je zapsat do souboru a dokončit pomocí libelf. Záznam se provádí pomocí funkce elf_update:
off_t elf_update (Elf * elf, Elf_Cmd cmd);
  • elf - deskriptor
  • cmd - příkaz, pro zápis se musí rovnat ELF_C_WRITE.
Funkce vrátí -1 při chybě. Text chyby lze získat voláním funkce elf_errmsg (-1), která vrátí ukazatel na chybový řetězec.
Dokončujeme práci s knihovnou pomocí funkce elf_end, které předáváme náš deskriptor. Zbývá pouze zavřít dříve otevřený soubor.
Náš vygenerovaný soubor však neobsahuje informace o ladění, které přidáme v další části.

Vytvoření DWARF

Informace o ladění vytvoříme pomocí knihovny, která je dodávána se souborem PDF s dokumentací (libdwarf2p.1.pdf - rozhraní knihovny producentů pro DWARF).
Vytváření informací o ladění se skládá z následujících fází:
  1. Záznam informací o ladění (DIE)
  2. Vytváření atributů uzlů
  3. Vytváření datových typů
  4. Tvorba postupů (funkcí)
Podívejme se na fáze podrobněji
Inicializace producenta libdwarf
Budeme vytvářet informace o ladění v době kompilace současně s vytvářením symbolů v sekci .symtab, takže knihovna musí být inicializována po inicializaci libelf, vytvoření hlavičky ELF a hlavičky programu, před vytvořením sekcí.
Pro inicializaci použijeme funkci dwarf_producer_init_c. Knihovna má několik dalších inicializačních funkcí (dwarf_producer_init, dwarf_producer_init_b), které se liší v některých nuancích popsaných v dokumentaci. V zásadě můžete použít kterýkoli z nich.

Dwarf_P_Debug dwarf_producer_init_c (příznaky Dwarf_Unsigned, Dwarf_Callback_Func_c func, Dwarf_Handler errhand, Dwarf_Ptr errarg, void * user_data, Dwarf_Error * chyba)

  • příznaky - kombinace „nebo“ několika konstant, které definují některé parametry, například bitovou šířku, pořadí bajtů (small-endian, big-endian), formát přemístění, z nichž rozhodně potřebujeme DW_DLC_WRITE a DW_DLC_SYMBOLIC_RELOCATIONS
  • func je funkce zpětného volání, která bude volána při vytváření sekcí ELF s informacemi o ladění. Další informace najdete níže v části „Vytváření sekcí ladění“.
  • errhand je ukazatel na funkci, která bude volána, když dojde k chybě. Může projít 0
  • errarg - data, která budou předána funkci errhand, lze nastavit na 0
  • user_data - data, která budou předána funkci func, lze nastavit na 0
  • chyba - vrácený chybový kód
Funkce vrací Dwarf_P_Debug - deskriptor používaný ve všech následujících funkcích, nebo -1 v případě chyby, zatímco chyba bude obsahovat chybový kód (text chybové zprávy získáte jejím kódem pomocí funkce dwarf_errmsg předáním tohoto kódu)
Záznam informací o ladění (DIE)
Jak je popsáno výše, informace o ladění tvoří stromovou strukturu. Chcete-li vytvořit uzel tohoto stromu, potřebujete:
  • vytvořte jej pomocí funkce dwarf_new_die
  • přidejte do něj atributy (každý typ atributů je přidán vlastní funkcí, která bude popsána později)
Uzel je vytvořen pomocí funkce dwarf_new_die:
Dwarf_P_Die dwarf_new_die (Dwarf_P_Debug dbg, Dwarf_Tag new_tag, Dwarf_P_Die rodič, Dwarf_P_Die dítě, Dwarf_P_Die left_sibling, Dwarf_P_Die right_sibling, Dwarf_Error)
  • new_tag - značka uzlu (typ) - konstanta DW_TAG_xxxx, kterou najdete v souboru libdwarf.h
  • parent, child, left_sibling, right_sibling - respektive parent, child, left and right Neighbors of the node. Není nutné specifikovat všechny tyto parametry, stačí zadat jeden, místo ostatních dát 0. Pokud jsou všechny parametry rovny 0, uzel bude buď root nebo izolovaný
  • chyba - bude obsahovat chybový kód, když k němu dojde
Funkce vrátí DW_DLV_BADADDR při chybě nebo uzel zvládne Dwarf_P_Die při úspěchu
Vytváření atributů uzlů
Existuje celá rodina funkcí dwarf_add_AT_xxxx pro vytváření atributů uzlů. Někdy je problematické určit, která funkce potřebuje k vytvoření požadovaného atributu, takže jsem několikrát dokonce zakopal do zdrojového kódu knihovny. Některé funkce zde budou popsány, některé níže v příslušných částech. Všichni vezmou parametr vlastníka, popisovač do uzlu, ke kterému bude atribut přidán, a vrátí chybový kód v parametru chyby.
Funkce dwarf_add_AT_name přidá do uzlu atribut jména (DW_AT_name). Většina uzlů musí mít název (například procedury, proměnné, konstanty), některé nemusí mít název (například Compilation Unit)
Dwarf_P_Attribute dwarf_add_AT_name (Dwarf_P_Die ownerdie, char * name, Dwarf_Error * error)
  • name - skutečná hodnota atributu (název uzlu)

Funkce dwarf_add_AT_signed_const, dwarf_add_AT_unsigned_const přidávají do uzlu zadaný atribut a jeho podepsanou (nepodepsanou) hodnotu. Podepsané a nepodepsané atributy se používají k nastavení konstantních hodnot, velikostí, čísel řádků atd. Formát funkce:
Dwarf_P_Attribute dwarf_add_AT_ (un) signed_const (Dwarf_P_Debug dbg, Dwarf_P_Die ownerdie, Dwarf_Half attr, Dwarf_Signed hodnota, Dwarf_Error * chyba)
  • dbg - deskriptor Dwarf_P_Debug, získaný během inicializace knihovny
  • attr - atribut, jehož hodnota je nastavena, - konstanta DW_AT_xxxx, kterou najdete v souboru libdwarf.h
  • value - hodnota atributu
Vrátí DW_DLV_BADADDR při chybě nebo popisovač atributu při úspěchu.
Vytvoření kompilační jednotky
Každý strom musí mít kořen - máme tuto kompilační jednotku, která obsahuje informace o programu (například název hlavního souboru, použitý programovací jazyk, název překladače, citlivost symbolů (proměnné, funkce) na velká a malá písmena, hlavní funkce programu, počáteční adresa atd. atd.). V zásadě nejsou vyžadovány žádné atributy. Například vytvořme informace o hlavním souboru a kompilátoru.
Informace o hlavním souboru
Atribut name (DW_AT_name) se používá k ukládání informací o hlavním souboru, použijte funkci dwarf_add_AT_name, jak je uvedeno v části „Vytváření atributů uzlů“.
Informace o kompilátoru
Pojďme použít funkci dwarf_add_AT_producer:
Dwarf_P_Attribute dwarf_add_AT_name (Dwarf_P_Die vlastník, char * producent_string, chyba Dwarf_Error *)
  • producer_string - řetězec s informačním textem
Vrátí DW_DLV_BADADDR při chybě nebo popisovač atributu při úspěchu.
Vytvoření společného vstupu informací
Obvykle, když je volána funkce (podprogram), její parametry a zpáteční adresa jsou vloženy do zásobníku (ačkoli každý kompilátor to může udělat jinak), toto vše se nazývá Call Frame. Debugger potřebuje informace o formátu rámce, aby správně určil zpáteční adresu z funkce a vytvořil backtrace - řetězec volání funkcí, které nás vedly k aktuální funkci, a parametry těchto funkcí. Obvykle také označuje registry procesorů, které jsou uloženy v zásobníku. Kód, který si vyhrazuje místo v zásobníku a zachovává registry procesorů, se nazývá prolog funkce, kód, který obnovuje registry a zásobník, se nazývá epilog.
Tyto informace jsou vysoce závislé na kompilátoru. Například prolog a epilog nemusí být na samém začátku a na konci funkce; někdy je použit rám, někdy ne; registry procesorů mohou být uloženy v jiných registrech atd.
Takže debugger potřebuje vědět, jak registry procesorů mění svou hodnotu a kde budou uloženy při vstupu do procedury. Tato informace se nazývá Call Frame Information - informace o formátu rámce. U každé adresy v programu (obsahující kód) je uvedena adresa rámce v paměti (Canonical Frame Address - CFA) a informace o registrech procesorů, můžete například určit, že:
  • registr není uložen v proceduře
  • registr nezmění svou hodnotu v proceduře
  • registr je uložen do zásobníku na CFA + n
  • registr je uložen v jiném registru
  • registr je uložen v paměti na nějaké adrese, kterou lze vypočítat poměrně nezřetelným způsobem
  • atd.
Vzhledem k tomu, že informace musí být zadána pro každou adresu v kódu, jsou velmi objemné a jsou uloženy v komprimované formě v části .debug_frame. Vzhledem k tomu, že se z adresy na adresu mění jen málo, jsou zakódovány pouze její změny ve formě instrukcí DW_CFA_хххх. Každé prohlášení ukazuje na jednu změnu, například:
  • DW_CFA_set_loc - označuje aktuální adresu v programu
  • DW_CFA_advance_loc - posune ukazatel o určitý počet bajtů
  • DW_CFA_def_cfa - označuje adresu rámce zásobníku (číselná konstanta)
  • DW_CFA_def_cfa_register - označuje adresu rámce zásobníku (převzato z registru procesoru)
  • DW_CFA_def_cfa_expression - označuje, jak vypočítat adresu rámce zásobníku
  • DW_CFA_same_value - označuje, že velikost písmen se nezmění
  • DW_CFA_register - označte, že registr je uložen v jiném registru
  • atd.
Prvky sekce Debug_frame jsou položky, které mohou být dvou typů: Common Information Entry (CIE) a Frame Description Entry (FDE). CIE obsahuje informace, které jsou společné mnoha záznamům FDE, zhruba popisuje konkrétní typ postupu. FDE popisují každý konkrétní postup. Při zadávání procedury ladicí program nejprve provede pokyny z CIE a poté z FDE.
Můj kompilátor vytváří procedury, ve kterých je CFA v registru sp (r13). Vytvořme CIE pro všechny postupy. K tomu existuje funkce dwarf_add_frame_cie:
Dwarf_Unsigned dwarf_add_frame_cie (Dwarf_P_Debug dbg, char * augmenter, Dwarf_Small code_align, Dwarf_Small data_align, Dwarf_Small ret_addr_reg, Dwarf_Ptrror init_bytes, Dwarf_Ptrror init_bytes
  • augmenter je řetězec kódovaný UTF-8, jehož přítomnost naznačuje, že pro CIE nebo FDE existují další informace specifické pro platformu. Dali jsme prázdný řádek
  • code_align - zarovnání kódu v bajtech (máme 2)
  • data_align - zarovnání dat v rámci (sada -4, což znamená, že všechny parametry v zásobníku zabírají 4 bajty a rostou dolů v paměti)
  • ret_addr_reg - registr obsahující zpáteční adresu z procedury (máme 14)
  • init_bytes je pole obsahující instrukce DW_CFA_хххх. Bohužel neexistuje žádný pohodlný způsob, jak toto pole vygenerovat. Můžete jej generovat ručně nebo jej špehovat v souboru elfů, který byl vygenerován kompilátorem C, což jsem udělal. Pro můj případ obsahuje 3 bajty: 0x0C, 0x0D, 0, což je zkratka pro DW_CFA_def_cfa: r13 ofs 0 (CFA je v registru r13, offset je 0)
  • init_bytes_len - délka pole init_bytes
Funkce vrací DW_DLV_NOCOUNT při chybě nebo CIE popisovač, který by měl být použit při vytváření FDE pro každý postup, o kterém pojednáme dále v části „Vytvoření postupu FDE“
Vytváření datových typů
Před vytvořením procedur a proměnných musíte nejprve vytvořit uzly odpovídající datovým typům. Existuje mnoho typů dat, ale všechny jsou založeny na základních typech (elementární typy jako int, double atd.), Ostatní typy jsou postaveny ze základních.
Základním typem je uzel se značkou DW_TAG_base_type. Musí mít atributy:
  • "Jméno" (DW_AT_name)
  • „Kódování“ (DW_AT_encoding) - znamená, jaký typ dat tento základní typ popisuje (například DW_ATE_boolean - boolean, DW_ATE_float - plovoucí desetinná čárka, DW_ATE_signed - celé číslo se znaménkem, DW_ATE_unsigned - celé číslo bez znaménka atd.)
  • "Velikost" (DW_AT_byte_size - velikost v bajtech nebo DW_AT_bit_size - velikost v bitech)
Uzel může také obsahovat další volitelné atributy.
Například k vytvoření 32bitového celočíselného základního typu se znaménkem „int“ budeme muset vytvořit uzel se značkou DW_TAG_base_type a nastavit jeho atributy DW_AT_name na „int“, DW_AT_encoding na DW_ATE_signed, DW_AT_byte_size - 4.
Jakmile jsou základní typy vytvořeny, můžete z nich odvodit. Takové uzly musí obsahovat atribut DW_AT_type - odkaz na jejich základní typ. Například ukazatel na int - uzel se značkou DW_TAG_pointer_type musí obsahovat v atributu DW_AT_type odkaz na dříve vytvořený typ „int“.
Atribut s odkazem na jiný uzel je vytvořen funkcí dwarf_add_AT_reference:
Dwarf_P_Attribute dwarf_add_AT_reference (Dwarf_P_Debug dbg, Dwarf_P_Die vlastník, Dwarf_Half attr, Dwarf_P_Die otherdie, chyba Dwarf_Error *)
  • attr - atribut, v tomto případě DW_AT_type
  • otherdie - zpracovat uzel typu, na který odkazujeme
Tvorba postupů
K vytvoření postupů potřebuji vyjasnit ještě jeden typ informací o ladění - informace o čísle linky. Slouží k mapování každé strojové instrukce na konkrétní řádek zdrojového kódu a také k umožnění ladění programu po řádku. Tyto informace jsou uloženy v sekci .debug_line. Pokud bychom měli dostatek místa, pak by to bylo uloženo jako matice, jeden řádek pro každou instrukci se sloupci, jako je tento:
  • název zdrojového souboru
  • číslo řádku v tomto souboru
  • číslo sloupce v souboru
  • zda je příkaz začátkem příkazu nebo bloku výpisu
  • atd.
Taková matice by byla velmi velká, takže musí být komprimována. Nejprve se odstraní duplicitní řádky a zadruhé se neuloží samotné řádky, ale pouze jejich změny. Tyto změny vypadají jako příkazy pro stavový stroj a samotné informace jsou již považovány za program, který bude tento stroj „vykonán“. Příkazy tohoto programu vypadají například takto: DW_LNS_advance_pc - posunout čítač příkazů na nějakou adresu, DW_LNS_set_file - nainstalovat soubor, ve kterém je definován postup, DW_LNS_const_add_pc - posunout čítač příkazů o několik bajtů atd.
Tuto informaci je obtížné vytvořit na tak nízké úrovni, takže libdwarf poskytuje několik funkcí, které usnadňují tento úkol.
Je nákladné ukládat název souboru pro každou instrukci, takže místo názvu je jeho index uložen ve speciální tabulce. Chcete-li vytvořit index souboru, použijte funkci dwarf_add_file_decl:
Dwarf_Unsigned dwarf_add_file_decl (Dwarf_P_Debug dbg, char * name, Dwarf_Unsigned dir_idx, Dwarf_Unsigned time_mod, Dwarf_Unsigned délka, Dwarf_Error * chyba)
  • name - název souboru
  • dir_idx - index složky, kde je soubor umístěn. Index lze získat pomocí funkce dwarf_add_directory_decl. Pokud jsou použity úplné cesty, můžete zadat 0 jako index složky a vůbec nepoužívat dwarf_add_directory_decl
  • time_mod - čas úpravy souboru, můžete jej vynechat (0)
  • délka - velikost souboru, také volitelná (0)
Funkce při chybě vrátí index souboru nebo DW_DLV_NOCOUNT.
Existují tři funkce dwarf_add_line_entry_b, dwarf_lne_set_address, dwarf_lne_end_sequence ke generování informací o čísle řádku, na které se podíváme níže.
Vytváření ladicích informací pro proceduru probíhá v několika fázích:
  • vytvoření symbolu procedury v sekci .symtab
  • vytvoření uzlu procedury s atributy
  • vytvoření procedury FDE
  • vytváření parametrů procedury
  • generování informace o čísle linky
Vytvoření symbolu procedury
Symbol procedury je vytvořen tak, jak je popsáno v části „Section.symtab“ výše. V něm jsou symboly procedur proložené symboly souborů, které obsahují zdrojový kód těchto procedur. Nejprve vytvoříme symbol souboru, pak postupy. V tomto případě se soubor stane aktuálním a pokud je další postup v aktuálním souboru, není nutné znovu vytvářet symbol souboru.
Vytvoření uzlu procedury s atributy
Nejprve vytvoříme uzel pomocí funkce dwarf_new_die (viz část „Vytváření uzlů“), zadáme DW_TAG_subprogram jako značku a kompilační jednotku (pokud se jedná o globální postup) nebo odpovídající DIE (pokud je místní) jako nadřazený. Dále vytvoříme atributy:
  • název procedury (funkce dwarf_add_AT_name, viz „Vytváření atributů uzlů“)
  • číslo řádku v souboru, kde začíná kód procedury (atribut DW_AT_decl_line), funkce dwarf_add_AT_unsigned_const (viz „Vytváření atributů uzlů“)
  • počáteční adresa procedury (atribut DW_AT_low_pc), funkce dwarf_add_AT_targ_address, viz níže
  • konečná adresa procedury (atribut DW_AT_high_pc), funkce dwarf_add_AT_targ_address, viz níže
  • typ výsledku vráceného procedurou (atribut DW_AT_type je odkaz na dříve vytvořený typ, viz „Vytváření datových typů“). Pokud procedura nic nevrátí, není nutné tento atribut vytvářet.
Atributy DW_AT_low_pc a DW_AT_high_pc musí být vytvořeny pomocí funkce dwarf_add_AT_targ_address_b speciálně navržené pro toto:
Dwarf_P_Attribute dwarf_add_AT_targ_address_b (Dwarf_P_Debug dbg, Dwarf_P_Die vlastník, Dwarf_Half attr, Dwarf_Unsigned pc_value, Dwarf_Unsigned sym_index, Dwarf_Error *
  • attr - atribut (DW_AT_low_pc nebo DW_AT_high_pc)
  • pc_value - hodnota adresy
  • sym_index je index symbolu procedury v tabulce.symtab. Volitelně můžete předat 0
Funkce při chybě vrátí DW_DLV_BADADDR.
Vytvoření postupu FDE
Jak bylo uvedeno výše v části „Vytváření společného záznamu informací“, musíte pro každý postup vytvořit popisovač rámečku, který probíhá v několika fázích:
  • vytvoření nového FDE (viz Vytvoření společného záznamu informací)
  • připojení vytvořeného FDE k obecnému seznamu
  • přidání pokynů k vygenerovanému FDE
Nové FDE můžete vytvořit pomocí funkce dwarf_new_fde:
Dwarf_P_Fde dwarf_new_fde (Dwarf_P_Debug dbg, chyba Dwarf_Error *)
Funkce při chybě vrátí popisovač novému FDE nebo DW_DLV_BADADDR.
K seznamu můžete připojit nové FDE pomocí dwarf_add_frame_fde:
Dwarf_Unsigned dwarf_add_frame_fde (Dwarf_P_Debug dbg, Dwarf_P_Fde fde, Dwarf_P_Die die, Dwarf_Unsigned cie, Dwarf_Addr virt_addr, Dwarf_Unsigned code_rlen, Dwarf_Unsigned code_lenrign, Dwarf__Urign, Dwarf
  • fde - právě přijatý deskriptor
  • die - DIE procedury (viz Vytvoření uzlu procedury s atributy)
  • cie - deskriptor CIE (viz Vytvoření společného záznamu informací)
  • virt_addr je počáteční adresa našeho postupu
  • code_len - délka procedury v bajtech
Při chybě funkce vrátí DW_DLV_NOCOUNT.
Poté můžete do našeho FDE přidat pokyny DW_CFA_xxxx. To se provádí pomocí funkcí dwarf_add_fde_inst a dwarf_fde_cfa_offset. První přidá danou instrukci do seznamu:
Dwarf_P_Fde dwarf_add_fde_inst (Dwarf_P_Fde fde, Dwarf_Small op, Dwarf_Unsigned val1, Dwarf_Unsigned val2, chyba Dwarf_Error *)
  • op - kód instrukce (DW_CFA_хххх)
  • val1, val2 - parametry instrukce (různé pro každou instrukci, viz Standard, část 6.4.2 Pokyny rámce volání)
Funkce dwarf_fde_cfa_offset přidává příkaz DW_CFA_offset:
Dwarf_P_Fde dwarf_fde_cfa_offset (Dwarf_P_Fde fde, Dwarf_Unsigned reg, Dwarf_Signed offset, Dwarf_Error * chyba)
  • fde - popisovač generovaného FDE
  • reg - registr zapsaný do rámce
  • offset - jeho offset v rámci (ne v bajtech, ale v prvcích rámce, viz Vytvoření společného záznamu informací, data_align)
Například kompilátor vytvoří proceduru v prologu, jehož registr lr (r14) je uložen v rámci zásobníku. Nejprve musíte přidat instrukci DW_CFA_advance_loc s prvním parametrem rovným 1, což znamená posunout registr počítače o 2 bajty (viz Vytvoření společného informačního záznamu, code_align), poté přidat DW_CFA_def_cfa_offset s parametrem 4 (nastavení offsetu dat v rámci o 4 bajty) a zavolat funkce dwarf_fde_cfa_offset s parametrem reg \u003d 14 offset \u003d 1, což znamená zápis registru r14 do rámce s offsetem -4 bajtů z CFA.
Vytvořte parametry procedury
Vytváření parametrů procedury je podobné vytváření normálních proměnných, viz „Vytváření proměnných a konstant“
Generování informací o čísle linky
Tyto informace se generují takto:
  • na začátku procedury spusťte blok instrukcí pomocí funkce dwarf_lne_set_address
  • pro každý řádek kódu (nebo strojovou instrukci) vytvořte informace o zdrojovém kódu (dwarf_add_line_entry)
  • na konci procedury ukončíme blok instrukcí funkcí dwarf_lne_end_sequence
Funkce dwarf_lne_set_address nastavuje adresu, na které blok instrukcí začíná:
Dwarf_Unsigned dwarf_lne_set_address (Dwarf_P_Debug dbg, Dwarf_Addr offs, Dwarf_Unsigned symidx, Dwarf_Error * chyba)
  • off - adresa procedury (adresa první strojové instrukce)
  • sym_idx - index symbolů (volitelné, můžete zadat 0)

Funkce dwarf_add_line_entry_b přidá informace o zdrojovém řádku do sekce .debug_line. Tuto funkci volám pro každou strojovou instrukci:
Dwarf_Unsigned dwarf_add_line_entry_b (Dwarf_P_Debug dbg, Dwarf_Unsigned file_index, Dwarf_Addr code_offset, Dwarf_Unsigned lineno, Dwarf_Signed COLUMN_NUMBER, Dwarf_Bool is_source_stmt_begin, Dwarf_Bool is_basic_block_begin, Dwarf_Bool is_epilogue_begin, Dwarf_Bool is_prologue_end, Dwarf_Unsigned ISA, Dwarf_Unsigned diskriminátor, Dwarf_Error * chyba)
  • file_index - index souboru zdrojového kódu získaného dříve funkcí dwarf_add_file_decl (viz „Vytváření postupů“)
  • code_offset - adresa aktuální strojové instrukce
  • lineno je číslo řádku ve zdrojovém souboru
  • column_number - číslo sloupce ve zdrojovém souboru
  • is_source_stmt_begin - 1, pokud je aktuální instrukce první v kódu na linenové lince (vždy používám 1)
  • is_basic_block_begin - 1, pokud je aktuální příkaz první v bloku prohlášení (vždy používám 0)
  • is_epilogue_begin - 1, pokud je aktuální instrukce první v epilogu procedury (volitelně, vždy mám 0)
  • is_prologue_end - 1, pokud je aktuální instrukce poslední v prologu procedury (vyžadováno!)
  • isa - architektura sady instrukcí. Nezapomeňte zadat DW_ISA_ARM_thumb pro ARM Cortex M3!
  • diskriminátor. Jedna pozice (soubor, řádek, sloupec) zdrojového kódu může odpovídat různým strojovým instrukcím. V takovém případě musí být pro soubory těchto pokynů nastaveny různé diskriminátory. Pokud takové případy neexistují, měla by být 0
Funkce vrací 0 (úspěch) nebo DW_DLV_NOCOUNT (chyba).
Nakonec procedura dwarf_lne_end_sequence ukončí postup:
Dwarf_Unsigned dwarf_lne_end_sequence (Dwarf_P_Debug dbg, adresa Dwarf_Addr; chyba Dwarf_Error *)
  • address - adresa aktuální strojové instrukce
Vrátí 0 (úspěch) nebo DW_DLV_NOCOUNT (chyba).
Tím je dokončení postupu dokončeno.
Vytváření proměnných a konstant
Obecně platí, že proměnné jsou velmi jednoduché. Mají název, umístění v paměti (nebo registr procesoru), kde jsou uložena jejich data, a typ těchto dat. Pokud je proměnná globální - její nadřazený objekt by měl být kompilační jednotkou, pokud je lokální - odpovídající uzel (zejména pro parametry procedury by měl mít samotný postup jako nadřazený). Můžete také určit, ve kterém souboru, řádku a sloupci se deklarace proměnné nachází.
V nejjednodušším případě je hodnota proměnné umístěna na nějaké pevné adrese, ale mnoho proměnných se vytváří dynamicky při zadávání procedury na zásobníku nebo registru, někdy může být výpočet hodnoty hodnoty docela netriviální. Norma poskytuje mechanismus pro popis, kde je hodnota proměnné - adresové výrazy (lokalizační výrazy). Výraz adresy je sada instrukcí (DW_OP_хххх konstanty) pro stroj typu stack podobný Fort; ve skutečnosti je to samostatný jazyk s větvemi, procedurami a aritmetickými operacemi. Tento jazyk nebudeme plně kontrolovat, bude nás zajímat jen několik pokynů:
  • DW_OP_addr - označuje adresu proměnné
  • DW_OP_fbreg - označuje posunutí proměnné od základního registru (obvykle ukazatel zásobníku)
  • DW_OP_reg0… DW_OP_reg31 - označuje, že proměnná je uložena v příslušném registru
Chcete-li vytvořit výraz adresy, musíte nejprve vytvořit prázdný výraz (dwarf_new_expr), přidat k němu pokyny (dwarf_add_expr_addr, dwarf_add_expr_gen atd.) A přidat jej do uzlu jako hodnotu atributu DW_AT_location (dwarf_add_AT_location_expression).
Funkce pro vytvoření prázdného výrazu adresy vrátí jeho deskriptor nebo 0 při chybě:
Dwarf_Expr dwarf_new_expr (Dwarf_P_Debug dbg, chyba Dwarf_Error *)
Chcete-li přidat pokyny k výrazu, použijte funkci 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 * chyba)
  • opcode - operační kód, konstanta DW_OP_хххх
  • val1, val2 - parametry instrukce (viz Standard)

Chcete-li explicitně nastavit adresu proměnné namísto předchozí, měla by se použít funkce dwarf_add_expr_addr:
Dwarf_Unsigned dwarf_add_expr_addr (Dwarf_P_Expr expr, Dwarf_Unsigned adresa, Dwarf_Signed sym_index, Dwarf_Error * chyba)
  • expr je popisovač výrazu adresy, ke kterému je příkaz přidán
  • address - adresa proměnné
  • sym_index - index symbolů v tabulce .symtab. Volitelně můžete předat 0
Funkce také vrátí DW_DLV_NOCOUNT při chybě.
A konečně můžete přidat vytvořený výraz adresy do uzlu pomocí funkce dwarf_add_AT_location_expr:
Dwarf_P_Attribute dwarf_add_AT_location_expr (Dwarf_P_Debug dbg, Dwarf_P_Die vlastník, Dwarf_Half attr, Dwarf_P_Expr loc_expr, chyba Dwarf_Error *)
  • ownerdie - uzel, ke kterému je výraz přidán
  • attr - atribut (v našem případě DW_AT_location)
  • loc_expr - zpracovává dříve vytvořený výraz adresy
Funkce při chybě vrátí deskriptor atributu nebo DW_DLV_NOCOUNT.
Proměnné (stejně jako parametry procedury) a konstanty jsou obyčejné uzly se značkami DW_TAG_variable, DW_TAG_formal_parameter a DW_TAG_const_type. Potřebují následující atributy:
  • název proměnné / konstanty (funkce dwarf_add_AT_name, viz „Vytváření atributů uzlů“)
  • číslo řádku v souboru, kde je deklarována proměnná (atribut DW_AT_decl_line), funkce dwarf_add_AT_unsigned_const (viz „Vytváření atributů uzlů“)
  • index názvu souboru (atribut DW_AT_decl_file), funkce dwarf_add_AT_unsigned_const (viz „Vytváření atributů uzlů“)
  • datový typ proměnné / konstanty (atribut DW_AT_type je odkaz na dříve vytvořený typ, viz „Vytváření datových typů“)
  • výraz adresy (viz výše) - potřebné pro parametr proměnné nebo procedury
  • nebo hodnota - pro konstantu (atribut DW_AT_const_value, viz „Vytváření atributů uzlů“)
Vytváření ladicích sekcí
Po vytvoření všech uzlů stromu s informacemi o ladění můžete s ním začít vytvářet elfí sekce. To se děje ve dvou fázích:
  • nejprve musíte zavolat funkci dwarf_transform_to_disk_form, která zavolá funkci, kterou jsme napsali, abychom vytvořili potřebné sekce elfů jednou pro každou sekci
  • pro každou sekci nám funkce dwarf_get_section_bytes vrátí data, která bude třeba zapsat do příslušné sekce
Funkce
dwarf_transform_to_disk_form (Dwarf_P_Debug dbg, chyba Dwarf_Error *)
překládá ladicí informace, které jsme vytvořili, do binárního formátu, ale na disk nic nezapíše. Vrátí nám počet vytvořených sekcí elfů nebo DW_DLV_NOCOUNT při chybě. V tomto případě bude pro každou sekci volána funkce zpětného volání, kterou jsme předali během inicializace knihovny funkci dwarf_producer_init_c. Tuto funkci si musíme napsat sami. Jeho specifikace je následující:
typedef int (* Dwarf_Callback_Func_c) (char * name, int size, Dwarf_Unsigned type, Dwarf_Unsigned flags, Dwarf_Unsigned link, Dwarf_Unsigned info, Dwarf_Unsigned * sect_name_index, void * user_data, int * error
  • name - název elfové sekce, kterou chcete vytvořit
  • velikost - velikost sekce
  • typ - typ sekce
  • vlajky - vlajky sekce
  • odkaz - pole odkaz na sekci
  • info - informační pole sekce
  • sect_name_index - musíte vrátit index sekce s přemístěním (volitelně)
  • user_data - předáno nám stejně, jako jsme je nastavili ve funkci inicializace knihovny
  • chyba - zde můžete předat chybový kód
V této funkci musíme:
  • vytvořit novou sekci (funkce elf_newscn, viz Vytváření sekcí)
  • vytvořit záhlaví sekce (funkce elf32_getshdr, tamtéž)
  • vyplňte správně (viz tamtéž). Je to snadné, protože pole záhlaví sekce odpovídají parametrům naší funkce. Chybějící pole sh_addr, sh_offset, sh_entsize jsou nastavena na 0 a sh_addralign na 1
  • vrátit index vytvořené sekce (funkce elf_ndxscn, viz "Section.symtab") nebo -1 při chybě (nastavení chybového kódu na chybu)
  • také musíme přeskočit sekci „.rel“ (v našem případě) a při návratu z funkce vrátit 0
Po dokončení funkce dwarf_transform_to_disk_form vrátí počet vytvořených sekcí. Budeme muset projít každou sekci ve smyčce od 0, podle těchto kroků:
  • vytvořte data pro zápis do sekce pomocí funkce 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 * délka, chyba Dwarf_Error *)
    • dwarf_section - číslo sekce. Musí být v rozsahu 0..n, kde n je číslo, které nám vrátila funkce dwarf_transform_to_disk_form
    • elf_section_index - Vrátí index sekce, do které mají být zapsána data
    • délka - délka těchto dat
    • chyba - nepoužívá se
    Funkce vrátí ukazatel na přijatá data nebo 0 (v případě,
    když už nejsou k dispozici žádné další sekce)
  • vytvořte deskriptor dat pro aktuální sekci (funkce elf_newdata, viz Vytváření sekcí) a vyplňte ji (viz tamtéž) nastavením:
    • d_buf - ukazatel na data, která jsme získali z předchozí funkce
    • d_size - velikost těchto dat (tamtéž)
Dokončování práce s knihovnou
Po vytvoření sekcí můžete dokončit práci s libdwarf pomocí funkce dwarf_producer_finish:
Dwarf_Unsigned dwarf_producer_finish (Dwarf_P_Debug dbg, chyba Dwarf_Error *)
Funkce vrátí DW_DLV_NOCOUNT při chybě.
Pamatujte, že v této fázi není žádný zápis na disk. Záznam musí být proveden pomocí funkcí z části "Vytvořit ELF - zapsat soubor".

Závěr

To je vše.
Vytvoření ladicích informací je opět velmi rozsáhlé téma a mnoha témat jsem se nedotkl, pouze jsem otevřel oponu. Kdo si přeje, může se ponořit do nekonečna.
Máte-li jakékoli dotazy, pokusím se na ně odpovědět.

Pokud je nainstalován počítač antivirový software umět skenovat všechny soubory v počítači a také každý soubor zvlášť... Můžete skenovat libovolný soubor kliknutím pravým tlačítkem na soubor a výběrem příslušné možnosti prohledat soubor na přítomnost virů.

Například na tomto obrázku soubor my-file.elf, pak musíte kliknout pravým tlačítkem na tento soubor a vybrat možnost z nabídky souboru „Skenovat pomocí AVG“... Výběrem této možnosti se otevře AVG Antivirus, který prohledá tento soubor, zda neobsahuje viry.


Někdy může dojít k chybě z nesprávná instalace softwaru, což může být způsobeno problémem během instalace. Může to narušit váš operační systém propojte svůj soubor ELF se správným aplikačním softwaremovlivňování tzv "Přidružení přípon souborů".

Někdy jednoduché přeinstalování Dolphin (emulátor) může vyřešit váš problém správným propojením ELF s Dolphin (emulátor). V ostatních případech může dojít k problémům s přidružení souborů špatné programování softwaru vývojáře a pro další pomoc budete možná muset kontaktovat vývojáře.


Rada: Zkuste aktualizovat Dolphin (emulátor) na nejnovější verzi a ujistěte se, že jsou nainstalovány nejnovější opravy a aktualizace.


Může se to zdát příliš zřejmé, ale často problém může způsobovat samotný soubor ELF... Pokud jste soubor obdrželi prostřednictvím přílohy e-mailu nebo jste jej stáhli z webu a proces stahování byl přerušen (například výpadek napájení nebo jiný důvod), soubor může být poškozen... Pokud je to možné, zkuste získat novou kopii souboru ELF a zkuste jej znovu otevřít.


Pozor: Poškozený soubor může způsobit vedlejší poškození předchozího nebo již existujícího malwaru ve vašem počítači, takže je velmi důležité, abyste na svém počítači měli neustále spuštěný aktualizovaný antivirus.


Pokud váš soubor ELF související s hardwarem v počítačiotevřete soubor, který možná budete potřebovat aktualizovat ovladače zařízenísouvisející s tímto zařízením.

Tento problém obvykle spojené s typy mediálních souborůkteré závisí například na úspěšném otevření hardwaru uvnitř počítače zvuková karta nebo grafická karta... Pokud se například pokoušíte otevřít zvukový soubor, ale nemůžete jej otevřít, možná budete muset aktualizovat ovladače zvukové karty.


Rada: Pokud při pokusu o otevření souboru ELF dostanete chybová zpráva týkající se souboru SYS, problém by pravděpodobně mohl být související s poškozenými nebo neaktuálními ovladači zařízeníkteré je třeba aktualizovat. Tento proces lze usnadnit pomocí softwaru pro aktualizaci ovladačů, jako je DriverDoc.


{!LANG-6d01708de8dc3e91af9dd690ba4dbdfa!}{!LANG-0ecea0a33bbb9bc84474768d71ea5d03!} {!LANG-95c59ae475c2fe0d175e130b023ebdc3!}{!LANG-b937494e46d3e64fa65b3b752b0a7af9!}

{!LANG-8493da115dd9404819261d3714672b91!} {!LANG-d3939402bf899d3858cda9e60de8ff5b!}{!LANG-1bc5f70be0f2b4351c62d18d7c2d91bd!}


{!LANG-8d8c10a37dd64873566ae3eb4d99e618!} {!LANG-69897abc4788425e057ed01dcb134591!}{!LANG-173090c988b3d1d5be2da9c421f138d5!} {!LANG-f9c508e70d49ba269d69902d58dee796!}{!LANG-ec2d1b83bf803d675d4e40a2cc6e32ec!} {!LANG-92108c99da3ee49d49335840dd0f2427!}{!LANG-1aa07746c6efb8d0d67b0b8673c317e3!}