Tuleohutuse entsüklopeedia

Kuidas avada elf-faili Windowsis. Mis on .ELF-faililaiend? Andmeid kirjeldavad sõlmed

Standardsed arendustööriistad kompileerivad teie programmi ELF-failiks (käivitatav ja lingitav vorming) koos võimalusega lisada silumisinfot. Vormingu spetsifikatsiooni saab lugeda. Lisaks on igal arhitektuuril oma omadused, näiteks ARM-i omad. Heidame sellele vormingule kiire pilgu.
ELF-vormingus käivitatav fail koosneb järgmistest osadest:
1. Päis (ELF-i päis)
Sisaldab üldist teavet faili ja selle põhiomaduste kohta.
2. Programmi päiste tabel
See on failisektsioonide ja mälusegmentide vastavuse tabel, mis annab laadijale teada, millisesse mälupiirkonda iga jaotis kirjutada.
3. Sektsioonid
Jaotised sisaldavad kogu failis olevat teavet (programm, andmed, silumise teave jne)
Igal jaotisel on tüüp, nimi ja muud parameetrid. Jaotises ".text" salvestatakse tavaliselt kood, ".symtab" - programmisümbolite tabel (failide nimed, protseduurid ja muutujad), ".strtab" - stringide tabel, jaotised eesliitega ".debug_" - silumisinfo jne. .d. Lisaks peab failis olema tühi jaotis indeksiga 0.
4. Jaotise päise tabel
See on tabel, mis sisaldab sektsioonide päiste massiivi.
Vormist on täpsemalt juttu ELF-i loomine rubriigis.

DWARF ülevaade

DWARF on standardiseeritud silumise teabe vorming. Standardi saab alla laadida ametlikult veebisaidilt. Samuti on vormingust suurepärane ülevaade: Sissejuhatus DWARF-i silumisvormingusse (Michael J. Eager).
Miks on vaja silumisinfot? See lubab:
  • määrake katkestuspunktid mitte füüsilisele aadressile, vaid lähtekoodifaili reanumbrile või funktsiooni nimele
  • kuvada ja muuta globaalsete ja kohalike muutujate väärtusi ning funktsiooniparameetreid
  • kuva kõnede pinu (tagasijälgimine)
  • käivitada programm samm-sammult mitte ühe koostejuhise, vaid lähtekoodi ridade kaupa
See teave salvestatakse puustruktuuri. Igal puusõlmel on vanem, sellel võivad olla lapsed ja seda nimetatakse silumisinfo sisestuseks (DIE). Igal sõlmel on oma silt (tüüp) ja sõlme kirjeldavate atribuutide (atribuutide) loend. Atribuudid võivad sisaldada kõike, näiteks andmeid või linke teistele sõlmedele. Lisaks on puust väljas talletatud info.
Sõlmed jagunevad kahte põhitüüpi: andmeid kirjeldavad sõlmed ja koodi kirjeldavad sõlmed.
Andmeid kirjeldavad sõlmed:
  1. Andmetüübid:
    • Põhiandmetüübid (sõlm tüübiga DW_TAG_base_type), näiteks int-tüüp C-s.
    • Liitandmetüübid (osutajad jne)
    • Massiivid
    • Struktuurid, klassid, ametiühingud, liidesed
  2. Andmeobjektid:
    • konstandid
    • funktsiooni parameetrid
    • muutujad
    • jne.
Igal andmeobjektil on atribuut DW_AT_location, mis määrab, kuidas arvutatakse aadress, kus andmed asuvad. Näiteks võib muutujal olla fikseeritud aadress, see võib olla registris või pinus, olla klassi liige või objekt. Seda aadressi saab arvutada üsna keerulisel viisil, seetõttu näeb standard ette nn asukohaavaldised, mis võivad sisaldada spetsiaalse sisemise virna masina lausejada.
Koodi kirjeldavad sõlmed:
  1. Protseduurid (funktsioonid) – DW_TAG_alamprogrammi sildiga sõlmed. Järeltulijad sõlmed võivad sisaldada muutujate kirjeldusi – funktsiooni parameetreid ja funktsiooni kohalikke muutujaid.
  2. Koostamise üksus. Sisaldab teavet programmi jaoks ja on kõigi teiste sõlmede vanem.
Ülalkirjeldatud teave on jaotistes ".debug_info" ja ".debug_abbrev".
Muu info:
  • Teave reanumbrite kohta (jaotis "debug_line")
  • Makroteave (jaotis "debug_macinfo")
  • Kõneraami teave (jaotis ".debug_frame")

ELFi looming

Loome EFL-failid, kasutades elfutilsi paketi libelf teeki. Veebis on hea artikkel libelfi kasutamise kohta - LibELF by Example (kahjuks on selles väga lühidalt kirjeldatud failide loomist) kui ka dokumentatsiooni.
Faili loomine koosneb mitmest etapist:
  1. laimu initsialiseerimine
  2. Faili päise loomine (ELF päis)
  3. Programmi päise tabeli loomine
  4. Loo jaotised
  5. Kirjutage fail
Mõelge samme üksikasjalikumalt
laimu initsialiseerimine
Kõigepealt peate kutsuma funktsiooni elf_version(EV_CURRENT) ja kontrollima tulemust. Kui see on võrdne väärtusega EV_NONE, on ilmnenud viga ja edasisi toiminguid teha ei saa. Seejärel peame looma kettal vajaliku faili, hankima selle deskriptori ja edastama selle funktsioonile elf_begin:
Elf * elf_begin(int fd, Elf_Cmd cmd, Elf *elf)
  • fd – äsja avatud faili failideskriptor
  • cmd - režiim (ELF_C_READ teabe lugemiseks, ELF_C_WRITE kirjutamiseks või ELF_C_RDWR lugemiseks/kirjutamiseks), peab vastama avatud faili režiimile (meie puhul ELF_C_WRITE)
  • elf - vajalik ainult arhiivifailidega töötamiseks (.a), meie puhul peate läbima 0
Funktsioon tagastab kursori loodud käepidemele, mida kasutatakse kõigis laimufunktsioonides, vea korral tagastatakse 0.
Loo päis
Uue faili päise loob funktsioon elf32_newehdr:
Elf32_Ehdr * elf32_newehdr(Päkapikk *päkapikk);
  • elf – funktsiooni elf_begin tagastatud käepide
Tagastab vea korral 0 või osutab struktuurile – ELF-faili päis:
#define EI_NIDENT 16 typedef struktuure (märgistamata sümbolid 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;

Mõned selle väljad on täidetud standardsel viisil, mõned peame täitma:

  • e_ident – ​​identifitseerimise baidimassiivil on järgmised indeksid:
    • EI_MAG0, EI_MAG1, EI_MAG2, EI_MAG3 – need 4 baiti peaksid sisaldama märke 0x7f, "ELF", mida funktsioon elf32_newehdr on meie jaoks juba teinud
    • EI_DATA – näitab andmete kodeeringu tüüpi failis: ELFDATA2LSB või ELFDATA2MSB. Peate määrama ELFDATA2LSB järgmiselt: e_ident = ELFDATA2LSB
    • EI_VERSION – faili päise versioon, meile juba määratud
    • EI_PAD – ärge puudutage
  • e_type - failitüüp, võib olla ET_NONE - tüüp puudub, ET_REL - ümberpaigutav fail, ET_EXEC - käivitatav fail, ET_DYN - jagatud objekti fail jne. Peame määrama failitüübiks ET_EXEC
  • e_machine - selle faili jaoks vajalik arhitektuur, näiteks EM_386 - Inteli arhitektuuri jaoks, ARM-i jaoks peame siia kirjutama EM_ARM (40) - vaadake ARM-i arhitektuuri ELF-i
  • e_version – faili versioon, peab olema seatud väärtusele EV_CURRENT
  • e_entry - sisenemispunkti aadress, pole meile vajalik
  • e_phoff - nihe programmi päise failis, e_shoff - jaotise päise nihe, ei täida
  • e_flags – protsessoripõhised lipud, meie arhitektuuri (Cortex-M3) jaoks tuleks seada väärtusele 0x05000000 (ABI versioon 5)
  • e_ehsize, e_phentsize, e_phnum, e_shentsize, e_shnum – ärge puudutage
  • e_shstrndx - sisaldab selle jaotise numbrit, milles asub jaotise päistega stringide tabel. Kuna meil veel ühtegi jaotist pole, siis paneme selle numbri paika hiljem.
Programmi päise loomine
Nagu juba mainitud, on programmi päise tabel failiosade ja mälusegmentide vastavustabel, mis ütleb laadijale, kuhu iga jaotis kirjutada. Päis luuakse funktsiooni elf32_newphdr abil:
Elf32_Phdr * elf32_newphdr(Päkapikk *elf, suurus_t arv);
  • päkapikk - meie käepide
  • count – lootavate tabelielementide arv. Kuna meil on ainult üks jaotis (koos programmikoodiga), võrdub loendus 1-ga.
Tagastab vea korral 0 või osuti programmi päisele.
Iga päisetabeli elementi kirjeldatakse järgmise struktuuriga:
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__align2; Elf32_Word p__alignr3; Elf322W;
  • p_type - segmendi (sektsiooni) tüüp, siin peame määrama PT_LOAD - laaditav segment
  • p_offset - nihked failis, kust algavad mällu laaditava jaotise andmed. Meil on .text osa, mis asub kohe pärast faili päist ja programmi päist, nihke saame arvutada nende päiste pikkuste summana. Mis tahes tüüpi pikkuse saab hankida funktsiooni elf32_fsize abil:
    suurus_t elf32_fsize(Elf_Type type, size_t count, signed int version); tüüp - siin on ELF_T_xxx konstant, vajame suurusi ELF_T_EHDR ja ELF_T_PHDR; count - soovitud tüüpi elementide arv, versioon - tuleb määrata väärtusele EV_CURRENT
  • p_vaddr, p_paddr - virtuaalne ja füüsiline aadress, kuhu jaotise sisu laaditakse. Kuna meil pole virtuaalseid aadresse, siis määrame selle võrdseks füüsilisega, kõige lihtsamal juhul - 0, sest see on koht, kus meie programm laaditakse.
  • p_filesz, p_memsz - jaotise suurus failis ja mälus. Meil on need samad, aga kuna programmi koodiga jaotist veel pole, siis paigaldame need hiljem
  • p_flags – laaditud mälusegmendi load. Võib olla PF_R – lugeda, PF_W – kirjutada, PF_X – käivitada või mõlema kombinatsioon. Määrake p_flags väärtuseks PF_R + PF_X
  • p_align – segmendi joondamine, meil on 4
Loo jaotised
Pärast pealkirjade loomist võite alustada jaotiste loomist. Funktsiooni elf_newscn abil luuakse tühi jaotis:
Elf_Scn * elf_newscn(Päkapikk *päkapikk);
  • elf – funktsiooni elf_begin poolt varem tagastatud käepide
Funktsioon tagastab vea korral jaotise osuti või 0.
Pärast jaotise loomist peate täitma jaotise päise ja looma jaotise andmete deskriptor.
Osuti jaotise päisele saame funktsiooni elf32_getshdr abil:
Elf32_Shdr * elf32_getshdr(Elf_Scn *scn);
  • scn on jaotise osuti, mille saime funktsioonist elf_newscn.
Jaotise päis näeb välja selline:
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)
  • sh_name - jaotise nimi - nihe jaotise päiste stringitabelis (section.shstrtab) - vt allpool jaotist "Stringitabelid"
  • sh_type - jaotise sisutüüp, programmikoodiga jaotise jaoks määrake SHT_PROGBITS, stringitabeliga jaotiste jaoks SHT_STRTAB, sümbolitabeli jaoks SHT_SYMTAB
  • sh_flags - sektsiooni lipud, mida saab kombineerida ja millest meil on vaja ainult kolme:
    • SHF_ALLOC - tähendab, et jaotis laaditakse mällu
    • SHF_EXECINSTR – jaotis sisaldab käivitatavat koodi
    • SHF_STRINGS – jaotis sisaldab stringitabelit
    Seetõttu peate programmi jaotise .text jaoks määrama lipud SHF_ALLOC + SHF_EXECINSTR
  • sh_addr - aadress, kus jaotis mällu laaditakse
  • sh_offset - jaotise nihe failis - ärge puudutage, teek installib meie eest
  • sh_size - sektsiooni suurus - ärge puudutage
  • sh_link - sisaldab lingitud jaotise numbrit, mis on vajalik jaotise linkimiseks vastava stringitabeliga (vt allpool)
  • sh_info – lisateave olenevalt jaotise tüübist, seatud väärtusele 0
  • sh_addralign - aadressi joondamine, ärge puudutage
  • sh_entsize - kui sektsioon koosneb mitmest sama pikkusega elemendist, näitab sellise elemendi pikkust, ärge puudutage
Pärast päise täitmist peate funktsiooni elf_newdata abil looma jaotise andmete deskriptori:
Elf_Data * elf_newdata(Elf_Scn *scn);
  • scn on äsja omandatud kursor uuele jaotisele.
Funktsioon tagastab vea korral väärtuse 0 või täitmiseks Elf_Data struktuurile:
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 - osuti jaotisesse kirjutatavatele andmetele
  • d_type - andmetüüp, ELF_T_BYTE sobib meile kõikjal
  • d_size – andmete suurus
  • d_off – lõigu nihe, seatud väärtusele 0
  • d_align - joondus, saab määrata väärtusele 1 - joondus puudub
  • d_version – versioon, peab olema seatud väärtusele EV_CURRENT
Spetsiaalsed sektsioonid
Oma eesmärkidel peame looma minimaalse vajaliku jaotiste komplekti:
  • .text - osa programmi koodiga
  • .symtab – faili sümbolitabel
  • .strtab - stringide tabel, mis sisaldab sümbolite nimesid jaotisest .symtab, kuna viimane ei salvesta nimesid endid, vaid nende indekseid
  • .shstrtab – jaotiste nimesid sisaldav stringitabel
Kõik jaotised luuakse eelmises jaotises kirjeldatud viisil, kuid igal eriosal on oma omadused.
Sektsioon.tekst
See jaotis sisaldab käivitatavat koodi, seega peate määrama sh_type väärtuseks SHT_PROGBITS, sh_flags väärtuseks SHF_EXECINSTR + SHF_ALLOC ja sh_addr aadressiks, kuhu see kood laaditakse
Section.symtab
Jaotis sisaldab kõigi programmi sümbolite (funktsioonide) ja failide kirjeldust, milles neid kirjeldati. See koosneb järgmistest elementidest, millest igaüks on 16 baiti pikk:
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 – sümboli nimi (indeks stringitabelis.strtab)
  • st_value - väärtus (funktsiooni sisendaadress või faili jaoks 0). Kuna Cortex-M3-l on käskude komplekt Thumb-2, peab see aadress olema paaritu (tegelik aadress + 1)
  • st_size – funktsiooni koodi pikkus (faili puhul 0)
  • st_info - sümboli tüüp ja selle ulatus. Selle välja väärtuse määratlemiseks on olemas makro
    #define ELF32_ST_INFO(b,t) (((b)<<4)+((t)&0xf))
    kus b on ulatus ja t on märgi tüüp
    Ulatus võib olla STB_LOCAL (sümbol ei ole teistest objektifailidest nähtav) või STB_GLOBAL (nähtav). Lihtsustamiseks kasutame STB_GLOBAL.
    Sümboli tüüp - STT_FUNC funktsiooni jaoks, STT_FILE faili jaoks
  • st_other – seatud väärtusele 0
  • st_shndx – selle jaotise register, mille jaoks sümbol on määratletud (jaotis indeks.tekst) või faili SHN_ABS.
    Sektsiooniindeksit saab selle scn-käepideme järgi määrata kasutades elf_ndxscn:
    suurus_t elf_ndxscn(Elf_Scn *scn);

See jaotis luuakse tavapärasel viisil, ainult sh_type tuleb määrata väärtusele SHT_SYMTAB ja register.strtab tuleb kirjutada väljale sh_link, et need jaotised saaksid lingitud.
Section.strtab
See jaotis sisaldab kõigi sümbolite nimesid jaotises .symtab. Loodud nagu tavaline jaotis, kuid sh_type peab olema seatud väärtusele SHT_STRTAB, sh_flags väärtuseks SHF_STRINGS, nii et sellest jaotisest saab stringitabel.
Jao andmeid saab koguda lähteteksti läbimisel massiivi, mille osuti kirjutatakse seejärel jaotise andmete deskriptorisse (d_buf).
Section.shstrtab
Sektsioon - stringide tabel, sisaldab faili kõigi jaotiste päiseid, sealhulgas oma päist. See luuakse samamoodi nagu jaotis .strtab. Pärast loomist tuleb selle indeks kirjutada faili päise väljale e_shstrndx.
Nöörilauad
Stringitabelid sisaldavad järjestikuseid stringe, mis lõpevad nullbaidiga, selle tabeli esimene bait peab samuti olema 0. Tabeli rea indeks on lihtsalt nihe baitides tabeli algusest, seega on esimesel stringil "nimi" indeks 1, järgmisel stringil "var" on indeks 6.
Indeks 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
Kirjutage fail
Niisiis, päised ja jaotised on juba moodustatud, nüüd tuleb need faili kirjutada ja lõpetada laimuga. Kirjutamise teeb funktsioon elf_update:
off_t elf_update(Elf *elf, Elf_Cmd cmd);
  • päkapikk - käepide
  • cmd – käsk, kirjutamiseks peab olema võrdne käsuga ELF_C_WRITE.
Funktsioon tagastab vea korral -1. Veateksti saab hankida funktsiooni elf_errmsg(-1) kutsumisega, mis tagastab kursori veaga reale.
Lõpetame raamatukoguga töötamise funktsiooniga elf_end, millele edastame oma deskriptori. Jääb vaid eelnevalt avatud fail sulgeda.
Kuid meie loodud fail ei sisalda silumise teavet, mille lisame järgmises jaotises.

DWARFi loomine

Loome silumisinfot kasutades teeki, millega on kaasas pdf-fail koos dokumentatsiooniga (libdwarf2p.1.pdf – A Producer Library Interface to DWARF).
Silumisinfo loomine koosneb järgmistest sammudest.
  1. Sõlmede loomine (DIE – silumisinfo sisestus)
  2. Sõlme atribuutide loomine
  3. Andmetüüpide loomine
  4. Protseduuride (funktsioonide) loomine
Mõelge samme üksikasjalikumalt
Libdwarfi tootja lähtestamine
Loome silumisteabe kompileerimise ajal samaaegselt sümbolite loomisega jaotises .symtab, nii et teegi lähtestamine peaks toimuma pärast laimu lähtestamist, ELF-i päise ja programmi päise loomist, enne jaotiste loomist.
Initsialiseerimiseks kasutame funktsiooni dwarf_producer_init_c. Teegil on veel mitu lähtestamisfunktsiooni (dwarf_producer_init, dwarf_producer_init_b), mis erinevad mõne dokumentatsioonis kirjeldatud nüansi poolest. Põhimõtteliselt saab kasutada ükskõik millist neist.

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

  • lipud - kombinatsioon "või" mitmest konstandist, mis määravad mõned parameetrid, näiteks teabe bitisügavus, baitide järjekord (little-endian, big-endian), ümberpaigutamise formaat, millest kindlasti vajame DW_DLC_WRITE ja DW_DLC_SYMBOLIC_RELOCATIONS
  • func – tagasihelistamisfunktsioon, mida kutsutakse silumisinfoga ELF-i sektsioonide loomisel. Lisateabe saamiseks vaadake allolevat jaotist "Silumisteabega jaotiste loomine".
  • errhand on osuti funktsioonile, mis tuleb vea ilmnemisel välja kutsuda. Saab läbida 0
  • errarg - andmed, mis edastatakse errhand-funktsioonile, saab määrata väärtusele 0
  • user_data – funktsioonile func edastatavad andmed saab määrata väärtusele 0
  • viga – tagastatud veakood
Funktsioon tagastab Dwarf_P_Debug - deskriptori, mida kasutatakse kõigis järgmistes funktsioonides või -1 vea korral, samas kui viga sisaldab veakoodi (veateate teksti saate selle koodi järgi funktsiooni dwarf_errmsg abil, edastades selle koodi sellele)
Sõlme loomine (DIE – silumisinfo sisestus)
Nagu ülalpool kirjeldatud, moodustab silumisinfo puustruktuuri. Selle puu sõlme loomiseks vajate:
  • loo see funktsiooniga dwarf_new_die
  • lisage sellele atribuudid (iga atribuuditüüp lisatakse oma funktsiooniga, mida kirjeldatakse hiljem)
Sõlm luuakse funktsiooni dwarf_new_die abil:
Dwarf_P_Die dwarf_new_die(Dwarf_P_Debug dbg, Dwarf_Tag new_tag, Dwarf_P_Die vanem, Dwarf_P_Die lapse, Dwarf_P_Die vasak_õde, Dwarf_P_Die right_sibling, Dwarf_Error *viga)
  • new_tag - sõlme silt (tüüp) - konstant DW_TAG_xxxx, mille leiate failist libdwarf.h
  • vanem, laps, vasak_õde, paremõde - vastavalt sõlme vanem, laps, vasak ja parem naaber. Kõiki neid parameetreid pole vaja määrata, piisab ühe määramisest, ülejäänud asemel pane 0. Kui kõik parameetrid on 0, on sõlm kas juur- või isoleeritud
  • error - sisaldab veakoodi, kui see ilmneb
Funktsioon tagastab ebaõnnestumise korral DW_DLV_BADADDR või õnnestumise korral Dwarf_P_Die sõlme käepideme
Sõlme atribuutide loomine
Sõlme atribuutide loomiseks on terve hulk funktsioone dwarf_add_AT_xxxx. Mõnikord on problemaatiline kindlaks teha, milline funktsioon peab vajaliku atribuudi looma, nii et ma süvenesin isegi mitu korda teegi lähtekoodi. Mõnda funktsiooni kirjeldatakse siin, mõnda allpool – vastavates jaotistes. Kõik nad võtavad omaniku parameetri, sõlme, millele atribuut lisatakse, käepideme ja tagastavad veaparameetris veakoodi.
Funktsioon dwarf_add_AT_name lisab sõlmele atribuudi "name" (DW_AT_name). Enamikul sõlmedel peab olema nimi (nt protseduurid, muutujad, konstandid), mõnel ei pruugi nime olla (nt kompileerimisüksus)
Dwarf_P_Attribute dwarf_add_AT_name(Dwarf_P_Die ownerdie, char *name, Dwarf_Error *error)
  • nimi - atribuudi tegelik väärtus (sõlme nimi)

Funktsioonid dwarf_add_AT_signed_const, dwarf_add_AT_unsigned_const lisavad sõlmele määratud atribuudi ja selle allkirjastatud (allkirjata) väärtuse. Signeeritud ja märgita atribuute kasutatakse konstantsete väärtuste, suuruste, reanumbrite jms määramiseks. Funktsiooni formaat:
Dwarf_P_Attribute dwarf_add_AT_(un)signed_const(Dwarf_P_Debug dbg, Dwarf_P_Die ownerdie, Dwarf_Half attr, Dwarf_Signed value, Dwarf_Error *viga)
  • dbg – Dwarf_P_Debug deskriptor saadi teegi initsialiseerimise ajal
  • attr - atribuut, mille väärtus on määratud - DW_AT_xxxx konstant, mille leiate failist libdwarf.h
  • väärtus – atribuudi väärtus
Tagasta vea korral DW_DLV_BADADDR või õnnestumise korral atribuudikäepide.
Kompileerimisüksuse loomine
Igal puul peab olema juur - meie puhul on see kompileerimisüksus, mis sisaldab teavet programmi kohta (näiteks põhifaili nimi, kasutatav programmeerimiskeel, kompilaatori nimi, märkide tundlikkus (muutujad, funktsioonid) tähtkuju, programmi põhifunktsioon, algusaadress jne). Põhimõtteliselt pole atribuute vaja. Näiteks loome info põhifaili ja kompilaatori kohta.
Teave põhifaili kohta
Nimeatribuuti (DW_AT_name) kasutatakse põhifaili teabe salvestamiseks, kasutage funktsiooni dwarf_add_AT_name, nagu on näidatud jaotises "Sõlme atribuutide loomine".
Kompilaatori teave
Kasutame funktsiooni dwarf_add_AT_producer:
Dwarf_P_Attribute dwarf_add_AT_name (Dwarf_P_Die ownerdie, char *producer_string, Dwarf_Error *error)
  • tootja_string – string infotekstiga
Tagastab vea puhul DW_DLV_BADADDR või edu korral atribuudikäepideme.
Ühise teabekirje loomine
Tavaliselt paigutatakse funktsiooni (alamprogrammi) kutsumisel pinu selle parameetrid ja tagastusaadress (kuigi iga kompilaator võib seda teha omal moel), nimetatakse seda kõike kõneraamiks. Silur vajab teavet kaadrivormingu kohta, et õigesti määrata funktsiooni tagastusaadress ja ehitada tagasijälg - funktsioonikutsete ahel, mis viis meid praeguse funktsioonini, ja nende funktsioonide parameetrid. Tavaliselt määrab see ka pinusse salvestatud protsessoriregistrid. Koodi, mis jätab pinu ruumi ja salvestab protsessori registrid, nimetatakse funktsiooni proloogiks, koodi, mis taastab registrid ja pinu, nimetatakse epiloogiks.
See teave sõltub suuresti kompilaatorist. Näiteks ei pea proloog ja epiloog olema funktsiooni päris alguses ja lõpus; mõnikord kasutatakse raami, mõnikord mitte; protsessori registreid saab salvestada teistesse registritesse jne.
Seega peab silur teadma, kuidas protsessoriregistrid oma väärtust muudavad ja kuhu need protseduuri sisestamisel salvestatakse. Seda teavet nimetatakse Call Frame Information – kaadrivormingu teave. Iga programmi aadressi jaoks (mis sisaldab koodi) näidatakse mälus oleva kaadri aadress (Canonical Frame Address - CFA) ja teave protsessori registrite kohta, näiteks saate määrata, et:
  • juhtum ei ole menetluses säilinud
  • register oma väärtust menetluses ei muuda
  • register salvestatakse pinusse aadressil CFA+n
  • register salvestatakse teises registris
  • register on mällu salvestatud mingile aadressile, mida saab üsna mitteilmselgelt arvutada
  • jne.
Kuna teave tuleb koodis iga aadressi kohta täpsustada, on see väga mahukas ja salvestatakse tihendatud kujul jaotisesse .debug_frame. Kuna see muutub aadressiti vähe, kodeeritakse ainult selle muudatused DW_CFA_хххх juhiste kujul. Iga juhend näitab ühte muudatust, näiteks:
  • DW_CFA_set_loc – osutab programmi praegusele aadressile
  • DW_CFA_advance_loc – viib kursorit teatud arvu baitide võrra edasi
  • DW_CFA_def_cfa – määrab virnaraami aadressi (arvuline konstant)
  • DW_CFA_def_cfa_register – määrab pinu kaadri aadressi (võetud protsessori registrist)
  • DW_CFA_def_cfa_expression – määrab, kuidas pinu kaadri aadressi arvutatakse
  • DW_CFA_same_value – näitab, et suurtähti ei muudeta
  • DW_CFA_register – näitab, et register on salvestatud teises registris
  • jne.
Jaotise .debug_frame elemendid on kirjed, mida võib olla kahte tüüpi: ühine teabesisestus (CIE) ja raami kirjelduse kirje (FDE). CIE sisaldab teavet, mis on ühine paljudele FDE kirjetele, jämedalt öeldes kirjeldab see teatud tüüpi protseduuri. FDE kirjeldab iga konkreetset protseduuri. Protseduuri sisestamisel täidab silur esmalt CIE ja seejärel FDE juhised.
Minu kompilaator genereerib protseduurid, kus CFA on registris sp (r13). Loome kõigi protseduuride jaoks CIE. Selle jaoks on funktsioon dwarf_add_frame_cie:
Dwarf_Unsigned dwarf_add_frame_cie(Dwarf_P_Debug dbg, char *augmenter, Dwarf_Small code_align, Dwarf_Small data_align, Dwarf_Small ret_addr_reg, Dwarf_Ptr init_bytes, Dwarf_Unsigned_Er init, *_byfr_lenor, Dwarf_bytes_Er initor);
  • augmenter – UTF-8 kodeeritud string, mille olemasolu näitab, et CIE või FDE jaoks on olemas täiendav platvormipõhine teave. Pange tühi rida
  • code_align – koodi joondamine baitides (meil on 2)
  • data_align - andmete joondamine kaadris (seadistatud -4, mis tähendab, et kõik parameetrid võtavad virnas 4 baiti ja see kasvab mälus alla)
  • ret_addr_reg - register, mis sisaldab protseduuri tagastusaadressi (meil on 14)
  • init_bytes – massiiv, mis sisaldab DW_CFA_xxxx juhiseid. Kahjuks puudub mugav viis selle massiivi genereerimiseks. Saate selle käsitsi moodustada või piiluda C-kompilaatori poolt loodud elf-faili, mida ma ka tegin. Minu puhul sisaldab see 3 baiti: 0x0C, 0x0D, 0, mis tähistab DW_CFA_def_cfa: r13 ofs 0 (CFA on registris r13, nihe on 0)
  • init_bytes_len – init_bytes massiivi pikkus
Funktsioon tagastab vea korral DW_DLV_NOCOUNT või CIE-käepideme, mida tuleks kasutada iga protseduuri jaoks FDE loomisel, mida käsitleme hiljem jaotises "FDE protseduuri loomine".
Andmetüüpide loomine
Enne protseduuride ja muutujate loomist tuleb esmalt luua andmetüüpidele vastavad sõlmed. Andmetüüpe on palju, kuid need kõik põhinevad põhitüüpidel (elementaartüübid nagu int, double jne), muud tüübid on üles ehitatud põhitüüpidest.
Põhitüüp on sõlm, millel on sildiga DW_TAG_base_type. Sellel peavad olema järgmised atribuudid:
  • "nimi" (DW_AT_name)
  • "kodeering" (DW_AT_encoding) – tähendab täpselt, milliseid andmeid see baastüüp kirjeldab (näiteks DW_ATE_boolean – tõeväärtus, DW_ATE_float – ujukoma, DW_ATE_signed – märgiga täisarv, DW_ATE_unsigned – märgita täisarv jne)
  • "size" (DW_AT_byte_size – suurus baitides või DW_AT_bit_size – suurus bittides)
Sõlm võib sisaldada ka muid valikulisi atribuute.
Näiteks 32-bitise täisarvulise märgiga põhitüübi "int" loomiseks peame looma sõlme sildiga DW_TAG_base_type ja määrama selle atribuudid DW_AT_name - "int", DW_AT_encoding - DW_ATE_signed, DW_AT_byte_size - 4.
Pärast baastüüpide loomist saate luua nendest tuletisi. Sellised sõlmed peavad sisaldama atribuuti DW_AT_type – viidet nende baastüübile. Näiteks kursor int - sõlm, millel on silt DW_TAG_pointer_type, peab sisaldama viidet varem loodud "int" tüübile atribuudis DW_AT_type.
Atribuut, mis viitab teisele sõlmele, luuakse funktsiooni dwarf_add_AT_reference abil:
Dwarf_P_Attribute dwarf_add_AT_reference(Dwarf_P_Debug dbg, Dwarf_P_Die omanik, Dwarf_Half attr, Dwarf_P_Die otherdie, Dwarf_Error *viga)
  • attr - atribuut, antud juhul DW_AT_type
  • otherdie – viidatava tüübi sõlme käepide
Protseduuride loomine
Protseduuride loomiseks pean selgitama veel üht tüüpi silumisinfot – reanumbriteavet. Selle eesmärk on kaardistada iga masina käsk konkreetsele lähtekoodi reale ja võimaldada ka programmi rida-realt silumist. See teave salvestatakse jaotisesse .debug_line. Kui meil oleks piisavalt ruumi, salvestatakse see maatriksina, iga käsu jaoks üks rida selliste veergudega:
  • lähtefaili nimi
  • rea number selles failis
  • veeru number failis
  • kas käsk on lause või lauseploki algus
  • jne.
Selline maatriks oleks väga suur, nii et see tuleb kokku suruda. Esiteks eemaldatakse topeltread ja teiseks ei salvestata ridu endid, vaid ainult nende muudatusi. Need muudatused näevad välja nagu piiratud olekuga masina käsud ja teavet ennast peetakse juba programmiks, mida see masin "käidab". Selle programmi käsud näevad välja sellised: DW_LNS_advance_pc – liiguta programmiloendurit mõnele aadressile, DW_LNS_set_file – määra fail, milles protseduur on määratletud, DW_LNS_const_add_pc – liigub programmiloendurit mõne baidi võrra edasi jne.
Seda teavet on nii madalal tasemel raske luua, seetõttu pakub libdwarfi teek mitmeid funktsioone selle ülesande hõlbustamiseks.
Iga käsu failinime salvestamine on kallis, nii et nime asemel salvestatakse selle indeks spetsiaalsesse tabelisse. Failiindeksi loomiseks kasutage funktsiooni dwarf_add_file_decl:
Dwarf_Unsigned dwarf_add_file_decl(Dwarf_P_Debug dbg, char *nimi, Dwarf_Unsigned dir_idx, Dwarf_Unsigned time_mod, Dwarf_Unsigned pikkus, Dwarf_Error *viga)
  • nimi – faili nimi
  • dir_idx - kausta indeks, kus fail asub. Indeksi saab hankida funktsiooni dwarf_add_directory_decl abil. Kui kasutatakse täisteid, saate määrata kaustaindeksiks 0 ja mitte kasutada kataloogi dwarf_add_directory_decl
  • time_mod - faili muutmise aeg, võib ära jätta (0)
  • pikkus – faili suurus, ka valikuline (0)
Funktsioon tagastab faili indeksi või vea korral DW_DLV_NOCOUNT.
Reanumbriteabe loomiseks on kolm funktsiooni dwarf_add_line_entry_b, dwarf_lne_set_address, dwarf_lne_end_sequence, mida käsitleme allpool.
Protseduuri silumimisteabe loomine läbib mitu etappi.
  • protseduuri sümboli loomine jaotises .symtab
  • atribuutidega protseduurisõlme loomine
  • FDE protseduuri loomine
  • protseduuri parameetrite loomine
  • reanumbri teabe genereerimine
Protseduuri sümboli loomine
Protseduuri sümbol luuakse ülaltoodud jaotises "Section.symtab" kirjeldatud viisil. Selles on protseduuride sümbolid pikitud nende failide sümbolitega, milles asub nende protseduuride lähtekood. Kõigepealt loome faili sümboli, seejärel protseduurid. See muudab faili aktuaalseks ja kui järgmine protseduur on aktiivses failis, ei pea faili sümbolit uuesti looma.
Protseduurisõlme loomine atribuutidega
Esiteks loome sõlme, kasutades funktsiooni dwarf_new_die (vt jaotist "Sõlmede loomine"), määrates sildina alamprogrammi sildi DW_TAG_ ja kompileerimisüksuse (kui see on globaalne protseduur) või vastava DIE (kui see on lokaalne) lapsevanem. Järgmisena loome atribuudid:
  • protseduuri nimi (funktsioon dwarf_add_AT_name, vt "Sõlmu atribuutide loomine")
  • rea number failis, kus protseduuri kood algab (atribuut DW_AT_decl_line), funktsioon dwarf_add_AT_unsigned_const (vt "Sõlme atribuutide loomine")
  • protseduuri algusaadress (atribuut DW_AT_low_pc), funktsioon dwarf_add_AT_targ_address, vt allpool
  • protseduuri lõpuaadress (atribuut DW_AT_high_pc), funktsioon dwarf_add_AT_targ_address, vt allpool
  • protseduuri poolt tagastatud tulemuse tüüp (atribuut DW_AT_type on link varem loodud tüübile, vt "Andmetüüpide loomine"). Kui protseduur midagi ei tagasta, pole seda atribuuti vaja luua.
Atribuudid DW_AT_low_pc ja DW_AT_high_pc tuleb luua spetsiaalselt selleks loodud funktsiooni dwarf_add_AT_targ_address_b abil:
Dwarf_P_Attribute dwarf_add_AT_targ_address_b(Dwarf_P_Debug dbg, Dwarf_P_Die omanik, Dwarf_Half attr, Dwarf_Unsigned pc_value, Dwarf_Unsigned sym_index, Dwarf_Error *error)
  • attr – atribuut (DW_AT_low_pc või DW_AT_high_pc)
  • pc_value – aadressi väärtus
  • sym_index – protseduuri sümboli indeks tabelis .symtab. Valikuline, võite sooritada 0
Funktsioon tagastab vea korral DW_DLV_BADADDR.
FDE protseduuri loomine
Nagu eespool jaotises "Ühise teabesisestuse loomine" mainitud, peate iga protseduuri jaoks looma raami deskriptori, mis toimub mitmes etapis.
  • uue FDE loomine (vt Ühise teabekirje loomine)
  • loodud FDE lisamine üldloendisse
  • juhiste lisamine loodud FDE-sse
Saate luua uue FDE funktsiooniga dwarf_new_fde:
Dwarf_P_Fde dwarf_new_fde(Dwarf_P_Debug dbg, Dwarf_Error *viga)
Funktsioon tagastab vea korral käepideme uuele FDE-le või DW_DLV_BADADDR-ile.
Saate lisada loendisse uue FDE käsuga dwarf_add_frame_fde:
Dwarf_Unsigned dwarf_add_frame_fde(Dwarf_P_Debug dbg, Dwarf_P_Fde fde, Dwarf_P_Die die, Dwarf_Unsigned cie, Dwarf_Addr virt_addr, Dwarf_Unsigned code_len, Dwarf_UnsignedErxr, Dwarf_idxr*)
  • fde – just saadud käepide
  • die – protseduur DIE (vt Protseduurisõlme loomine atribuutidega)
  • cie – CIE deskriptor (vt Ühise teabekirje loomine)
  • virt_addr – meie protseduuri algusaadress
  • code_len – protseduuri pikkus baitides
Funktsioon tagastab vea korral DW_DLV_NOCOUNT.
Pärast kõike seda saame lisada DW_CFA_хххх juhised meie FDE-sse. Seda tehakse funktsioonidega dwarf_add_fde_inst ja dwarf_fde_cfa_offset. Esimene lisab antud juhise loendisse:
Dwarf_P_Fde dwarf_add_fde_inst(Dwarf_P_Fde fde, Dwarf_Small op, Dwarf_Unsigned val1, Dwarf_Unsigned val2, Dwarf_Error *viga)
  • op – juhise kood (DW_CFA_хххх)
  • val1, val2 – käsu parameetrid (iga käsu puhul erinevad, vt standardi jaotist 6.4.2 Kõneraami juhised)
Funktsioon dwarf_fde_cfa_offset lisab käsu DW_CFA_offset:
Dwarf_P_Fde dwarf_fde_cfa_offset(Dwarf_P_Fde fde, Dwarf_Unsigned reg, Dwarf_Signed nihe, Dwarf_Error *viga)
  • fde – loodud FDE käepide
  • reg – register, mis raamile kirjutatakse
  • nihe – selle nihe kaadris (mitte baitides, vaid raami elementides, vt Ühise teabekirje loomine, data_align)
Näiteks loob kompilaator protseduuri, mille prolog salvestab registri lr (r14) pinu raami. Kõigepealt tuleb lisada käsk DW_CFA_advance_loc, mille esimene parameeter on võrdne 1-ga, mis tähendab arvutiregistri edasiarendamist 2 baiti võrra (vt Ühise teabekirje loomine, code_align), seejärel lisage DW_CFA_def_cfa_offset parameetriga 4 (seade andmete nihe kaadris 4 baiti) ja kutsuda välja funktsioon dwarf_fde_cfa_offset parameetriga reg=14 offset=1, mis tähendab registri r14 kirjutamist kaadrisse nihkega -4 baiti CFA-st.
Protseduuri parameetrite loomine
Protseduuri parameetrite loomine sarnaneb tavaliste muutujate loomisega, vt "Muutujate ja konstantide loomine"
Reanumbri teabe genereerimine
See teave luuakse järgmiselt:
  • protseduuri alguses alustame käsuplokki funktsiooniga dwarf_lne_set_address
  • loome iga koodirea (või masinakäsu) jaoks teabe lähtekoodi kohta (dwarf_add_line_entry)
  • protseduuri lõpus lõpetame juhiste ploki funktsiooniga dwarf_lne_end_sequence
Funktsioon dwarf_lne_set_address määrab aadressi, kust käsuplokk algab:
Dwarf_Unsigned dwarf_lne_set_address(Dwarf_P_Debug dbg, Dwarf_Addr offs, Dwarf_Unsigned symidx, Dwarf_Error *viga)
  • offs – protseduuri aadress (esimese masinakäsu aadress)
  • sym_idx – sümbolite register (valikuline, saate määrata 0)

Funktsioon dwarf_add_line_entry_b lisab teavet lähtekoodi ridade kohta jaotisesse .debug_line. Kutsun seda funktsiooni iga masinajuhise jaoks:
Dwarf_Unsigned dwarf_add_line_entry_b (Dwarf_P_Debug dbg, Dwarf_Unsigned file_index, Dwarf_Addr code_offset, Dwarf_Unsigned REANR, 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 Diskrimineerija, Dwarf_Error * error)
  • file_index – funktsiooni dwarf_add_file_decl poolt varem saadud lähtekoodifaili indeks (vt "Loomisprotseduurid")
  • code_offset – kehtiva masinakäsu aadress
  • lineno – lähtekoodifaili rea number
  • veeru_number – veeru number lähtekoodi failis
  • is_source_stmt_begin - 1, kui praegune käsk on lineno rea koodis esimene (kasutan alati 1)
  • is_basic_block_begin - 1, kui praegune käsk on lauseplokis esimene (kasutan alati 0)
  • is_epilogue_begin - 1, kui praegune käsk on protseduuri epiloogi esimene (pole vajalik, mul on alati 0)
  • is_prologue_end - 1, kui praegune käsk on protseduuri proloogis viimane (kohustuslik!)
  • isa - käsukomplekti arhitektuur (käsukomplekti arhitektuur). ARM Cortex M3 jaoks määrake kindlasti DW_ISA_ARM_thumb!
  • diskrimineerija. Lähtekoodi üks positsioon (fail, rida, veerg) võib vastata erinevatele masinakäskudele. Sel juhul tuleb selliste juhiste kogumitele määrata erinevad diskriminaatorid. Kui selliseid juhtumeid pole, peaks see olema 0
Funktsioon tagastab 0 (edukus) või DW_DLV_NOCOUNT (viga).
Lõpuks lõpetab funktsioon dwarf_lne_end_sequence protseduuri:
Dwarf_Unsigned dwarf_lne_end_sequence(Dwarf_P_Debug dbg, Dwarf_Addr aadress; Dwarf_Error *viga)
  • aadress – kehtiva masinajuhise aadress
Tagastab 0 (õnnestus) või DW_DLV_NOCOUNT (viga).
See lõpetab protseduuri loomise.
Muutujate ja konstantide loomine
Üldiselt on muutujad üsna lihtsad. Neil on nimi, mälukoht (või protsessoriregister), kus nende andmed asuvad, ja nende andmete tüüp. Kui muutuja on globaalne - selle ülem peaks olema kompileerimisüksus, kui kohalik - vastav sõlm (see kehtib eriti protseduuri parameetrite kohta, neil peab olema protseduur ise). Samuti saate määrata, millises failis, real ja veerus muutuja deklaratsioon asub.
Lihtsamal juhul paikneb muutuja väärtus mingil fikseeritud aadressil, kuid paljud muutujad tekivad dünaamiliselt protseduuri sisestamisel pinu või registrisse, mõnikord võib väärtuse aadressi arvutamine olla üsna ebatriviaalne. Standard pakub mehhanismi, mille abil saab kirjeldada, kus muutuja väärtus asub – asukohaavaldised. Aadressi avaldis on käskude komplekt (DW_OP_xxxx konstandid) neljanda sarnase virna masina jaoks, tegelikult on see eraldi keel harude, protseduuride ja aritmeetiliste operatsioonidega. Me ei vaata seda keelt täielikult üle, vaid oleme huvitatud vaid mõnest juhisest:
  • DW_OP_addr – määrab muutuja aadressi
  • DW_OP_fbreg – näitab muutuja nihet baasregistrist (tavaliselt pinu osutist)
  • DW_OP_reg0 ... DW_OP_reg31 - näitab, et muutuja on salvestatud vastavasse registrisse
Sihtavaldise loomiseks tuleb esmalt luua tühi avaldis (dwarf_new_expr), lisada sellele juhised (dwarf_add_expr_addr, dwarf_add_expr_gen jne) ja lisada see sõlme atribuudi DW_AT_location väärtusena (dwarf_add_AT_location_expression).
Tühja aadressiavaldise loomise funktsioon tagastab vea korral oma käepideme või 0:
Dwarf_Expr dwarf_new_expr(Dwarf_P_Debug dbg, Dwarf_Error *viga)
Avaldisele juhiste lisamiseks kasutage funktsiooni 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 *viga)
  • opkood – operatsioonikood, konstantne DW_OP_хххх
  • val1, val2 - juhiste parameetrid (vt standard)

Muutuja aadressi selgeks määramiseks tuleks eelmise funktsiooni asemel kasutada funktsiooni dwarf_add_expr_addr:
Dwarf_Unsigned dwarf_add_expr_addr(Dwarf_P_Expr expr, Dwarf_Unsigned address, Dwarf_Signed sym_index, Dwarf_Error *viga)
  • avaldis - aadressi avaldise käepide, millele käsk lisatakse
  • aadress - muutuv aadress
  • sym_index – sümbolite register .symtab tabelis. Valikuline, võite sooritada 0
Funktsioon tagastab vea korral ka DW_DLV_NOCOUNT.
Ja lõpuks saate loodud aadressi avaldise lisada sõlme, kasutades funktsiooni dwarf_add_AT_location_expr:
Dwarf_P_Attribute dwarf_add_AT_location_expr(Dwarf_P_Debug dbg, Dwarf_P_Die omanik, Dwarf_Half attr, Dwarf_P_Expr loc_expr, Dwarf_Error *viga)
  • ownerdie – sõlm, millele avaldis lisatakse
  • attr - atribuut (meie puhul DW_AT_location)
  • loc_expr - eelnevalt loodud aadressi avaldise käepide
Funktsioon tagastab atribuudi käepideme või vea korral DW_DLV_NOCOUNT.
Muutujad (nagu ka protseduuri parameetrid) ja konstandid on tavalised sõlmed, millel on vastavalt märge DW_TAG_variable, DW_TAG_formal_parameter ja DW_TAG_const_type. Nad vajavad järgmisi atribuute:
  • muutuja/konstanti nimi (funktsioon dwarf_add_AT_name, vt "Sõlme atribuutide loomine")
  • rea number failis, kus muutuja deklareeritakse (atribuut DW_AT_decl_line), funktsioon dwarf_add_AT_unsigned_const (vt "Sõlme atribuutide loomine")
  • failinime indeks (DW_AT_decl_file atribuut), funktsioon dwarf_add_AT_unsigned_const (vt "Sõlme atribuutide loomine")
  • muutuja/konstantne andmetüüp (atribuut DW_AT_type on link varem loodud tüübile, vt "Andmetüüpide loomine")
  • aadressi avaldis (vt eespool) – vajalik muutuja või protseduuri parameetri jaoks
  • või väärtus – konstandi jaoks (atribuut DW_AT_const_value, vt jaotist "Sõlme atribuutide loomine")
Silumisinfoga jaotiste loomine
Pärast kõigi silumisinfopuu sõlmede loomist saate sellega hakata moodustama elfi sektsioone. See toimub kahes etapis:
  • esmalt tuleb kutsuda funktsioon dwarf_transform_to_disk_form, mis kutsub välja funktsiooni, mille me kirjutasime, et luua iga sektsiooni jaoks üks kord soovitud elfi sektsioonid
  • iga jaotise puhul tagastab funktsioon dwarf_get_section_bytes meile andmed, mis tuleb kirjutada vastavasse jaotisesse
Funktsioon
dwarf_transform_to_disk_form (Dwarf_P_Debug dbg, Dwarf_Error* viga)
teisendab meie loodud silumisinfo binaarvormingusse, kuid ei kirjuta kettale midagi. See tagastab loodud elfi sektsioonide arvu või vea korral DW_DLV_NOCOUNT. Sel juhul kutsutakse iga jaotise jaoks tagasihelistamise funktsioon, mille me teegi lähtestamisel edastasime funktsioonile dwarf_producer_init_c. Peame selle funktsiooni ise kirjutama. Selle spetsifikatsioon on järgmine:
typedef int (*Dwarf_Callback_Func_c)(char* nimi, int suurus, Dwarf_Unsigned tüüp, Dwarf_Unsigned lipud, Dwarf_Unsigned link, Dwarf_Unsigned info, Dwarf_Unsigned* sect_name_index, void * user_data, int* error)
  • nimi - loodava päkapiku sektsiooni nimi
  • suurus - sektsiooni suurus
  • tüüp - sektsiooni tüüp
  • lipud – jaolipud
  • link - jaotise lingi väli
  • info - jaotise teabeväli
  • sect_name_index – peate tagastama ümberpaigutamistega jaotise indeksi (valikuline)
  • user_data – edastatakse meile samamoodi, nagu me selle teegi lähtestamisfunktsioonis määrasime
  • viga - siin saate veakoodi edastada
Selles funktsioonis peame:
  • loo uus jaotis (funktsioon elf_newscn, vt jaotiste loomine)
  • loo jaotise päis (funktsioon elf32_getshdr, ibid.)
  • täitke see õigesti (vt ibid.). See on lihtne, kuna jaotise päise väljad vastavad meie funktsiooni parameetritele. Puuduvad väljad sh_addr, sh_offset, sh_entsize seatakse väärtusele 0 ja sh_addralign väärtusele 1
  • tagastab loodud jaotise indeksi (funktsioon elf_ndxscn, vt "Section.symtab") või vea korral -1 (määrates veakoodi väärtuseks error)
  • samuti peame vahele jätma jaotise ".rel" (meie puhul), tagastades funktsioonist naastes 0
Lõpetamisel tagastab funktsioon dwarf_transform_to_disk_form loodud partitsioonide arvu. Peame liikuma 0-st läbi iga jaotise, järgides neid samme:
  • looge jaotisesse kirjutamiseks andmed funktsiooni dwarf_get_section_bytes abil:
    Dwarf_Ptr dwarf_get_section_bytes(Dwarf_P_Debug dbg, Dwarf_Signed dwarf_section, Dwarf_Signed *elf_section_index, Dwarf_Unsigned *pikkus, Dwarf_Error* viga)
    • kääbus_sektsioon – jaotise number. Peaks olema vahemikus 0..n, kus n on arv, mille meile tagastab funktsioon dwarf_transform_to_disk_form
    • elf_section_index – tagastab jaotise indeksi, kuhu andmed kirjutada
    • pikkus – nende andmete pikkus
    • viga - pole kasutatud
    Funktsioon tagastab kursori vastuvõetud andmetele või 0 (kui
    kui loomiseks pole enam jaotisi)
  • looge praegusele jaotisele andmedeskriptor (funktsioon elf_newdata, vt jaotiste loomine) ja täitke see (vt ibid.), määrates:
    • d_buf - osutab eelmisest funktsioonist saadud andmetele
    • d_size – nende andmete suurus (ibid.)
Töö lõpetamine raamatukoguga
Pärast sektsioonide moodustamist saate libdwarfiga töötamise lõpetada funktsiooniga dwarf_producer_finish:
Dwarf_Unsigned dwarf_producer_finish(Dwarf_P_Debug dbg, Dwarf_Error* viga)
Funktsioon tagastab vea korral DW_DLV_NOCOUNT.
Märgin, et selles etapis kettale kirjutamist ei tehta. Salvestamine peab toimuma jaotise "ELFi loomine – faili kirjutamine" funktsioonide abil.

Järeldus

See on kõik.
Kordan, silumisinfo loomine on väga mahukas teema ja paljusid teemasid ma ei puudutanud, avasin vaid loori. Soovijad võivad minna sügavale lõpmatusse.
Kui teil on küsimusi, siis püüan neile vastata.

ELF formaat

ELF-vormingus on mitut tüüpi faile, millele oleme seni viidanud erinevalt, näiteks käivitatav fail või objektifail. ELF-i standard eristab aga järgmisi tüüpe:

1. Faili teisaldamine(ümberpaigutatav fail), mis salvestab juhiseid ja andmeid, mida saab linkida teiste objektifailidega. Sellise linkimise tulemuseks võib olla käivitatav fail või jagatud objektifail.

2. Jagatud objektifail(jagatud objektifail) sisaldab ka juhiseid ja andmeid, kuid seda saab kasutada kahel viisil. Esimesel juhul saab selle linkida teiste ümberpaigutatavate failide ja jagatud objektifailidega, mille tulemusena luuakse uus objektifail. Teisel juhul, kui programm käivitatakse täitmiseks, saab operatsioonisüsteem selle dünaamiliselt linkida programmi käivitatava failiga, mille tulemusena luuakse programmist käivitatav pilt. Viimasel juhul räägime jagatud raamatukogudest.

3. Täidetav salvestab täieliku kirjelduse, mis võimaldab süsteemil luua protsessist pildi. See sisaldab juhiseid, andmeid, nõutavate jagatud objektifailide kirjeldust ning vajalikku sümboolset ja silumise teavet.

Joonisel fig. 2.4 näitab käivitatava faili struktuuri, millega operatsioonisüsteem saab luua programmipildi ja käivitada programmi täitmiseks.

Riis. 2.4. Käivitatava faili struktuur ELF-vormingus

Päisel on failis kindel asukoht. Ülejäänud komponendid paigutatakse vastavalt päisesse salvestatud teabele. Seega sisaldab päis failistruktuuri üldist kirjeldust, üksikute komponentide asukohta ja nende suurusi.

Kuna ELF-faili päis määrab selle struktuuri, siis käsitleme seda üksikasjalikumalt (tabel 2.4).

Tabel 2.3. ELFi päiseväljad

Väli Kirjeldus
e_ident Baitide massiiv, millest igaüks määratleb faili mõne üldise omaduse: failivorming (ELF), versiooninumber, süsteemiarhitektuur (32-bitine või 64-bitine) jne.
e_tüüp Failitüüp ELF-vormingus toetab mitut tüüpi
e_masin Riistvaraplatvormi arhitektuur, mille jaoks see fail loodi. Tabelis. 2.4 näitab selle välja võimalikke väärtusi
e_versioon ELF-vormingu versiooni number. Tavaliselt määratletakse kui EV_CURRENC (praegune), mis tähendab uusimat versiooni
e_entry Virtuaalne aadress, millele süsteem pärast programmi laadimist juhtimise üle annab (sisenemispunkt)
e_phoff Programmi päise tabeli asukoht (nihke faili algusest).
e_shoff Jaotise päise tabeli asukoht
e_ehsize Päise suurus
e_phentsize Iga programmi päise suurus
e_phnum Programmi pealkirjade arv
e_shentsize Iga segmendi (jaotise) päise suurus
e_shnum Segmendi (jaotise) pealkirjade arv
e_shstrndx Stringitabelit sisaldava segmendi asukoht

Tabel 2.4. ELF-faili päise välja e_machine väärtused

Tähendus Riistvaraplatvorm
EM_M32 AT&T WE 32100
EM_SPARC Päike SPARC
EM_386 Intel 80386
EM_68K Motorola 68000
EM_88K Motorola 88000
EM_486 Intel 80486
EM_860 Intel i860
EM_MIPS MIPS RS3000 Big Endian
EM_MIPS_RS3_LE MIPS RS3000 Little Endian
EM_RS6000 RS6000
EM_PA_RISC PA-RISC
EM_nCUBE nCUBE
EM_VPP500 Fujitsu VPP500
EM_SPARC32PLUS Päike SPARC 32+

Programmi päise tabelis sisalduv teave ütleb kernelile, kuidas segmentidest protsessi kujutist luua. Enamik segmente kopeeritakse (vastastatakse) mällu ja need esindavad protsessi vastavaid segmente selle käivitamisel, näiteks koodi- või andmesegmente.

Iga programmisegmendi päis kirjeldab ühte segmenti ja sisaldab järgmist teavet:

Segmendi tüüp ja operatsioonisüsteemi toimingud selle segmendiga

Segmendi asukoht failis

Segmendi algusaadress protsessi virtuaalmälus

Faili segmendi suurus

Mälu segmendi suurus

Segmendi juurdepääsu lipud (kirjutamine, lugemine, käivitamine)

Mõnel segmendil on tüüp LOAD, mis käsib kernelil luua nendele segmentidele vastavaid andmestruktuure, nn. alad, mis määratlevad protsessi virtuaalmälu külgnevad osad ja nendega seotud atribuudid. Segment, mille asukoht ELF failis on märgitud vastavas programmi päises, kaardistatakse loodud alaga, mille virtuaalne algusaadress on samuti programmi päises märgitud. Seda tüüpi segmendid hõlmavad näiteks programmi juhiseid (koodi) ja selle andmeid sisaldavad segmendid. Kui segmendi suurus on väiksem kui ala suurus, võib kasutamata ruumi täita nullidega. Sellist mehhanismi kasutatakse eelkõige initsialiseerimata protsessiandmete (BSS) loomisel. Valdkondadest räägime lähemalt 3. peatükis.

INTERP tüüpi segment salvestab programmitõlgi. Seda segmenditüüpi kasutatakse programmide jaoks, mis nõuavad dünaamilist linkimist. Dünaamilise linkimise olemus seisneb selles, et käivitatava faili üksikud komponendid (jagatud objektifailid) ühendatakse mitte kompileerimisetapis, vaid programmi käivitamise etapis. Selle faili nimi, mis on dünaamiline linkide redaktor, salvestatakse sellesse segmenti. Programmi täitmise ajal loob kernel protsessi pildi, kasutades selleks määratud linkerit. Seega ei laadita algselt mällu mitte originaalprogramm, vaid dünaamiline linker. Järgmises etapis töötab dünaamiline linker koos UNIX-i tuumaga täieliku käivitatava kujutise loomiseks. Dünaamiline redaktor laadib vajalikud jagatud objektifailid, mille nimed on salvestatud lähtefaili eraldi segmentidesse, ning teostab vajaliku paigutuse ja linkimise. Lõpuks kantakse juhtimine üle algsele programmile.

Lõpuks lõpetab päisetabel faili. lõigud või lõigud(jaotis). Sektsioonid (sektsioonid) määratlevad faili jaotised, mida kasutatakse kompileerimise või dünaamilise linkimise ajal teiste moodulitega linkimiseks. Sellest tulenevalt sisaldavad pealkirjad kogu nende jaotiste kirjeldamiseks vajalikku teavet. Reeglina sisaldavad jaotised segmentide kohta üksikasjalikumat teavet. Näiteks võib koodisegment koosneda mitmest jaotisest, näiteks räsitabelist programmis kasutatavate sümbolite indeksite salvestamiseks, osast programmi lähtestamiskoodi jaoks, dünaamilise redaktori kasutatavast linkitabelist ja jaotis, mis sisaldab tegelikke programmi juhiseid.

Pöördume ELF-vormingu juurde tagasi peatükis 3, kui käsitleme protsessi virtuaalmälu korraldust, kuid praegu liigume edasi järgmise levinud vormingu, COFF-i juurde.

Raamatust The Art of Unix Programming autor Raymond Eric Steven

Raamatust Arvutiõpetus autor Kolisnitšenko Deniss Nikolajevitš

Raamatust Abstrakt, kursusetöö, diplom arvutis autor Balovsyak Nadežda Vassiljevna

5.2.6. Windowsi INI-vorming Paljud Microsoft Windowsi programmid kasutavad tekstipõhist andmevormingut, nagu näide näites 5-6. Selles näites on valikulised ressursid nimega konto, kataloog, numbriline_id ja arendaja lingitud nimetatud projektidega python, sng, f etchmail ja py-howto. Salvestuses

Raamatust Viimane arvutiõpetus autor Beluntsov Valeri

14.5.3. Lahtri vorming Vorming määrab, kuidas lahtri väärtust kuvatakse. Vorming on tihedalt seotud lahtri andmetüübiga. Tüüp on teie otsustada. Kui sisestasite numbri, on see numbriline andmetüüp. Excel ise proovib vormingut andmetüübi järgi määrata. Näiteks kui sisestasite teksti, siis

Raamatust The Art of Unix Programming autor Raymond Eric Steven

PDF-vorming PDF tähendab Portable Document Format (Portable Document Format). See vorming loodi spetsiaalselt failides teabe kuvamisega seotud probleemide kõrvaldamiseks. Selle eeliseks on see, et esiteks on PDF-vormingus salvestatud dokument sama

Raamatust TCP/IP Architecture, Protocols, Implementation (sh IP versioon 6 ja IP Security) autor Faith Sidney M

Failivorming Kui kasutaja alustab failiga töötamist, peab süsteem teadma, mis formaadis see on kirjutatud ja millise programmiga see avada. Näiteks kui fail sisaldab lihtteksti, siis saab seda lugeda mis tahes tekstiprogrammis

Raamatust Yandex kõigile autor Abramzon M. G.

5.2.2. RFC 822 vorming RFC 822 metavorming tuletatakse Interneti-e-kirjade tekstivormingust. RFC 822 on peamine Interneti-RFC standard, mis seda vormingut kirjeldab (hiljem asendas RFC 2822). MIME (Multipurpose Internet Media Extension) vorming

Macromedia Flash Professional 8. Graafika ja animatsioon autor Dronov V. A.

5.2.3. Cookie-Jar vorming Cookie-jar vormingut kasutab fortune(1) oma juhuslike tsitaatide andmebaasi jaoks. See sobib kirjetele, mis on lihtsalt struktureerimata tekstiplokid. Kirje eraldaja selles vormingus on märk

Raamatust Arvuti helitöötlus autor Zagumennov Aleksander Petrovitš

5.2.4. Record-jar-vorming Cookie-jar-kirjeeraldajad sobivad hästi RFC 822 metavorminguga kirjete jaoks, mis moodustavad vormingus, mida selles raamatus nimetatakse "record-jar". Mõnikord on vaja tekstivormingut, mis toetab mitut erineva selgesõnalise nimega kirjet

Raamatust UNIX operatsioonisüsteem autor Robatševski Andrei M.

5.2.6. Windowsi INI-vorming Paljud Microsoft Windowsi programmid kasutavad tekstipõhist andmevormingut, nagu näide näites 5-6. Selles näites on valikulised ressursid nimega konto, kataloog, numbriline_id ja arendaja lingitud nimega python, sng, fetchmail ja py-howto projektidega. Salvestuses

Raamatust Kontoriarvuti naistele autor Pasternak Evgeniya

19.5 Üldine URL-i vorming Ülaltoodut kokku võttes märgime, et:? URL algab kasutatava juurdepääsuprotokolliga.? Kõigi rakenduste puhul, välja arvatud veebiuudised ja meilisõnumid, järgneb sellele eraldaja://.? Seejärel määratakse serveri hostinimi.? Lõpuks

Autori raamatust

3.3.1. RSS-vorming Saate lugeda saidiuudiseid erineval viisil. Lihtsaim viis on aeg-ajalt saiti külastada ja uusi sõnumeid vaadata. Saate panna saate, mis ühendub uudistekanaliga ja saab ise uudiste pealkirju või märkusi, vastavalt

Autori raamatust

MP3-vorming MP3-vorming loodi MPEG 1 level 3 kodekiga tihendatud muusikafailide levitamiseks.See on praegu kõige populaarsem vorming muusika levitamiseks Internetis ja mujal. Seda toetavad absoluutselt kõik heli salvestamise ja töötlemise programmid

Autori raamatust

MP3-vorming Rahvusvahelise organisatsiooni MPEG (Moving Pictures Experts Group – Video Recording Experts Group) välja pakutud heli tihendamise meetod, aga ka tihendatud helifailide vorming põhineb tajuheli kodeerimisel. Töötage tõhusate kodeerimisalgoritmide loomisega

Autori raamatust

ELF-vorming ELF-vormingus on mitut tüüpi faile, mida oleme seni nimetanud erinevalt, näiteks käivitatav fail või objektifail. ELF-i standard eristab aga järgmisi tüüpe:1. Ümberpaigutatav fail, mis sisaldab juhiseid ja andmeid, mida saab

Autori raamatust

Numbrivorming Lõpuks jõudsime numbrivorminguni. Olen seda juba korduvalt maininud, nüüd panen kõik riiulitele (kuigi üldisest tähendusest sai juba aru) Excelis saab numbreid kuvada erinevates formaatides. Selles jaotises räägime sellest, millised numbrivormingud on olemas ja kuidas

Selles ülevaates räägime ainult selle vormingu 32-bitisest versioonist, sest me ei vaja veel 64-bitist versiooni.

Iga ELF-fail (sh selle vormingu objektimoodulid) koosneb järgmistest osadest:

  • ELF faili päis;
  • Programmi osade tabel (objektmoodulites võib puududa);
  • ELF-i faili osad;
  • Sektsioonitabel (ei pruugi käivitatavas moodulis olla);
  • Jõulisuse huvides ei kasuta ELF-vorming bitivälju. Ja kõik struktuurid on tavaliselt joondatud 4-baidiselt.

Vaatame nüüd ELF-failide päistes kasutatavaid tüüpe:

Nüüd kaaluge faili päist:

#define EI_NIDENT 16 struktuure elf32_hdr (märgistamata sümbolid e_ident; Elf32_Half e_type; Elf32_Half e_machine; Elf32_Word e_version; Elf32_Addr e_entry; / * Sisenemispunkt * / 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; );

Massiiv e_ident sisaldab teavet süsteemi kohta ja koosneb mitmest alamväljast.

Struktuur ( unsigned char ei_magic; unsigned char ei_class; unsigned char ei_data; unsigned char ei_version; unsigned char ei_pad; )

  • ei_magic – kõigi ELF-failide konstantne väärtus, võrdne ( 0x7f, "E", "L", "F")
  • ei_class – ELF-failiklass (1–32 bitti, 2–64 bitti, mida me ei arvesta)
  • ei_data – määrab selle faili baitide järjekorra (see järjekord sõltub platvormist ja võib olla otsene (LSB või 1) või vastupidine (MSB või 2)) Inteli protsessorite puhul on lubatud ainult väärtus 1.
  • ei_version on üsna kasutu väli ja kui see pole võrdne 1-ga (EV_CURRENT), loetakse fail kehtetuks.

Väli ei_pad on koht, kus operatsioonisüsteemid salvestavad oma identifitseerimisteabe. See väli võib olla tühi. See pole ka meile oluline.

Päiseväli e_type võib sisaldada mitut väärtust, käivitatavate failide puhul peab see olema ET_EXEC võrdne 2-ga

e_machine - määrab protsessori, millel see käivitatav fail võib töötada (meie jaoks on EM_386 väärtus 3)

Väli e_version vastab päises olevale väljale ei_version.

Väli e_entry määrab programmi algusaadressi, mis asetatakse eip-i enne programmi käivitamist.

Väli e_phoff määrab programmide mällu laadimiseks kasutatava nihke faili algusest, kus asub programmiosa tabel.

Kõikide väljade eesmärki ma ei loetle, kõiki pole laadimiseks vaja. Kirjeldan veel ainult kahte.

Väli e_phentsize määrab programmi jaotise tabelis oleva kirje suuruse.

Ja väli e_phnum määrab programmi sektsiooni tabeli kirjete arvu.

Sektsioonitabelit (mitteprogrammi) kasutatakse programmide linkimiseks. me ei võta seda arvesse. Samuti ei võta me arvesse dünaamiliselt seotud mooduleid. See teema on üsna keeruline, esmatutvuse jaoks ei sobi. :)

Nüüd programmiosadest. Programmi jaotise tabeli kirje vorming on järgmine:

Struktuur elf32_phdr( Elf32_Word p_type; Elf32_Off p_offset; Elf32_Addr p_vaddr; Elf32_Addr p_paddr; Elf32_Word p_filesz; Elf32_Word p_memsz; Elf32__Word; Elf32_lagsW; Elf3 p_f3);

Veel põldude kohta.

  • p_type – määrab programmiosa tüübi. See võib võtta mitut väärtust, kuid meid huvitab ainult üks. PT_LOAD(1). Kui sektsioon on seda tüüpi, on see ette nähtud mällu laadimiseks.
  • p_offset – määrab nihke failis, millest see jaotis algab.
  • p_vaddr Määrab virtuaalse aadressi, kuhu see jaotis mällu laadida.
  • p_paddr – määrab füüsilise aadressi, kuhu see jaotis tuleks laadida. Seda välja ei pea kasutama ja see on tähenduslik ainult mõne platvormi jaoks.
  • p_filesz – määrab failis oleva jaotise suuruse.
  • p_memsz - määrab mälus oleva jaotise suuruse. See väärtus võib olla suurem kui eelmine. Väli p_flag määrab juurdepääsu tüübi mälu jaotistele. Mõned lõigud on lubatud esitada, mõned salvestada. Olemasolevates süsteemides on kõik lugemiseks saadaval.

ELF-vormingu laadimine.

Pealkirjaga saime sellest natuke aru. Nüüd annan algoritmi ELF-vormingus binaarfaili laadimiseks. Algoritm on skemaatiline, te ei tohiks seda pidada toimivaks programmiks.

Int LoadELF (märgita tähemärk *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 || // Kontrollklass EH->e_ident != ELFDATA2LSB || // baitide järjestus EH->e_ident != EV_CURRENT || // versioon EH->e_type != ET_EXEC || // tüüp EH->e_machine != EM_386 || // platvorm EH->e_version != EV_CURRENT) // ja veel kord versioon, igaks juhuks tagasta ELF_WRONG; EPH = (struct elf32_phdr *)(bin + EH->e_phoff); while (EH->e_phnum--) ( if (EPH->p_type == PT_LOAD) memcpy (EPH->p_vaddr, bin + EPH->p_offset, EPH->p_filesz); EPH = (struct elf32_phdr *)((märgita tähemärk *)EPH + EH->e_phentsize)); ) tagastab ELF_OK; )

Tõsiselt võetuna tasub analüüsida EPH->p_flags välju ja määrata vastavatele lehtedele ligipääsuõigused ning lihtsalt kopeerimine siin ei toimi, aga see ei kehti enam vormingu, vaid mälujaotuse kohta. Seetõttu me sellest praegu ei räägi.

PE-vorming.

Paljuski sarnaneb see ELF-vorminguga ja pole üllatav, et allalaadimiseks peaks olema ka jaotisi.

Nagu kõik Microsoftis :) PE-vorming põhineb EXE-vormingul. Faili struktuur on järgmine:

  • 00h - EXE päis (ma ei võta seda arvesse, see on vana kui Dos. :)
  • 20h - OEM-i päis (selles pole midagi olulist);
  • 3ch – tegelik PE päise nihe failis (dword).
  • stub liikumislaud;
  • stub;
  • PE päis;
  • objektitabel;
  • failiobjektid;

stub on programm, mis töötab reaalrežiimis ja teeb eeltööd. See ei pruugi olla saadaval, kuid mõnikord võib seda vaja minna.

Oleme huvitatud millestki muust, PE-päisest.

Selle struktuur on järgmine:

Struktuur pe_hdr ( unsigned long pe_sign; unsigned short pe_cputype; unsigned short pe_objnum; unsigned long pe_time; unsigned long pe_cofftbl_off; unsigned long pe_cofftbl_size; unsigned short pe_nthdr_size; unsigned short pe_nthdr_size; unsigned short pe_ uncodeed_ shortsignedverdaic; unsigned short pe_ shortsigned_magize; unsignize_ shortsigned_magize; unsignize ; allkirjastamata pikk pe_entry; unsigned long pe_code_base; signed long pe_data_base; unsigned long pe_image_base; signed long pe_obj_align; signed long pe_file_align; // ... no ja palju muud, ebaoluline. );

Seal on palju asju. Piisab, kui öelda, et selle päise suurus on 248 baiti.

Ja peaasi, et enamik neist väljadest on kasutamata. (Kes nii ehitab?) Ei, neil on teada-tuntud otstarve, aga näiteks minu testprogrammis on väljadel pe_code_base, pe_code_size jne nullid, aga see töötab hästi. Järeldus viitab iseenesest, et fail laaditakse objektide tabeli põhjal. Sellest me räägimegi.

Objektitabel järgneb vahetult pärast PE päist. Selle tabeli kirjed on järgmises vormingus:

Struktuur pe_ohdr ( unsigned char o_name; unsigned long o_vsize; unsigned long o_vaddr; unsigned long o_psize; unsigned long o_poff; unsigned char o_reserved; unsigned long o_ flags; );

  • o_nimi - jaotise nimi, see on laadimisel täiesti ükskõikne;
  • o_vsize - sektsiooni suurus mälus;
  • o_vaddr - ImageBase'i suhteline mäluaadress;
  • o_psize - jaotise suurus failis;
  • o_poff - jaotise nihe failis;
  • o_lipud - sektsiooni lipud;

Siin tasub lippudel põhjalikumalt peatuda.

  • 00000004h – kasutatakse 16 bitise nihkega koodi jaoks
  • 00000020h - koodijaotis
  • 00000040h - lähtestatud andmete jaotis
  • 00000080h - initsialiseerimata andmejaotis
  • 00000200h - kommentaarid või mis tahes muud tüüpi teave
  • 00000400h - ülekatte sektsioon
  • 00000800h – ei kuulu programmi kujutisse
  • 00001000h - üldandmed
  • 00500000h – vaikimisi joondus, kui pole teisiti määratud
  • 02000000h - saab mälust maha laadida
  • 04000000h – pole vahemällu salvestatud
  • 08000000h – pole lehitsetav
  • 10000000h - jagatud
  • 20000000h - teostatav
  • 40000000h - saab lugeda
  • 80000000h - saate kirjutada

Jällegi, ma ei tegele jagatud ja ülekattega sektsioonidega, meid huvitavad kood, andmed ja juurdepääsuõigused.

Üldiselt piisab sellest teabest juba binaarfaili allalaadimiseks.

PE-vormingu laadimine.

int LoadPE (märgita char *bin) ( struct elf32_hdr *PH = (struct pe_hdr *) (bin + *((märgita pikk *)&bin)); // Muidugi pole kombinatsioon selge... võtke lihtsalt sõna nihkega 0x3c / / Ja arvutage PE päise aadress failipildis struct elf32_phdr *POH; if (PH == NULL || // Juhtige PH->pe_sign osutit != 0x4550 || // PE-signatuur ("P" , "E", 0, 0) PH->pe_cputype != 0x14c || // i386 (PH->pe_flags & 2) == 0) // faili ei saa käivitada! return PE_WRONG; POH = (struct pe_ohdr *) ((märgita tähemärk *)PH + 0xf8); while (PH->pe_obj_num--) ( if ((POH->p_lips & 0x60) != 0) // kas kood või lähtestatud andmemälu (PE->pe_image_base + POH ->o_vaddr, bin + POH- >o_poff, POH->o_psize); POH = (struct pe_ohdr *)((märgita tähemärk *)POH + suurus (struct pe_ohdr)); ) tagastab PE_OK; )

See ei ole jällegi valmis programm, vaid laadimisalgoritm.

Ja jällegi, paljud punktid jäävad käsitlemata, kuna need lähevad teemast kaugemale.

Kuid nüüd tasub veidi rääkida olemasolevatest süsteemifunktsioonidest.

Süsteemi omadused.

Vaatamata protsessorites saadaolevate kaitsete paindlikkusele (kaitse deskriptorite tabelite tasemel, kaitse segmendi tasemel, lehetaseme kaitse), kasutatakse olemasolevates süsteemides (nii Windowsis kui ka Unixis) täielikult ainult lehekaitset, mis, kuigi see võib takistada koodi kirjutamist, kuid ei saa takistada andmete käivitamist. (Võib-olla on see süsteemi haavatavuste rohkuse põhjus?)

Kõik segmendid on adresseeritud lineaarsest aadressist nullist ja ulatuvad lineaarse mälu lõpuni. Protsessi piiritlemine toimub ainult lehe tabeli tasemel.

Sellega seoses on kõik moodulid lingitud mitte algusaadressidest, vaid segmendis piisavalt suure nihkega. Windowsis on segmendi baasaadress 0x400000, Unixis (Linux või FreeBSD) on see 0x8048000.

Mõned funktsioonid on seotud ka mälu otsimisega.

ELF-failid on lingitud nii, et sektsioonide piirid ja suurused langevad faili 4 kilobaidistele plokkidele.

Ja PE-vormingus, hoolimata asjaolust, et vorming ise võimaldab teil joondada 512-baidiseid sektsioone, kasutatakse 4k sektsioonide joondamist, ei peeta Windowsis madalamat joondamist õigeks.

Kui teie arvutis on viirusetõrjeprogramm saab skannida kõiki arvutis olevaid faile ja ka iga faili eraldi. Saate skannida mis tahes faili, paremklõpsates failil ja valides faili viiruste kontrollimiseks sobiva valiku.

Näiteks sellel joonisel fail minu-fail.elf, siis peate sellel failil paremklõpsama ja valige failimenüüst suvand "skanni AVG-ga". Selle valiku valimine avab AVG Antivirus ja kontrollib faili viiruste suhtes.


Mõnikord võib viga tuleneda sellest tarkvara vale installimine, mis võib olla tingitud installiprotsessi käigus ilmnenud probleemist. See võib teie operatsioonisüsteemi häirida seostage oma ELF-fail õige tarkvararakendusega, mõjutades nn "faililaiendi seosed".

Mõnikord lihtne Dolphini (emulaatori) uuesti installimine võib teie probleemi lahendada, ühendades ELF-i õigesti Dolphiniga (emulaatoriga). Muudel juhtudel võivad failiühenduse probleemid tuleneda sellest halb tarkvara programmeerimine arendaja ja peate võib-olla edasise abi saamiseks arendajaga ühendust võtma.


Nõuanne: Proovige värskendada Dolphini (emulaatorit) uusimale versioonile, et veenduda, et teil on uusimad paigad ja värskendused.


See võib tunduda liiga ilmne, kuid sageli ELF-fail ise võib probleemi põhjustada. Kui saite faili meilimanuse kaudu või laadisite selle veebisaidilt alla ja allalaadimisprotsess katkes (näiteks elektrikatkestuse või muu põhjuse tõttu), fail võib olla rikutud. Võimalusel proovige hankida ELF-failist uus koopia ja proovige see uuesti avada.


Hoolikalt: Rikutud fail võib põhjustada täiendavat kahju teie arvuti varasemale või olemasolevale pahavarale, mistõttu on oluline hoida oma arvuti ajakohase viirusetõrjega kursis.


Kui teie ELF-fail mis on seotud teie arvuti riistvaraga vajaliku faili avamiseks värskendage seadme draivereid seotud selle seadmega.

See probleem tavaliselt seotud meediumifailitüüpidega, mis sõltuvad näiteks arvuti sees oleva riistvara edukast avamisest, helikaart või videokaart. Näiteks kui proovite avada helifaili, kuid ei saa seda avada, peate võib-olla seda tegema värskendage helikaardi draivereid.


Nõuanne: Kui proovite ELF-faili avada, saate .SYS-failiga seotud veateade, võib probleem tõenäoliselt olla mis on seotud rikutud või aegunud seadmedraiveritega mida tuleb uuendada. Seda protsessi saab hõlbustada draiveri värskendustarkvara (nt DriverDoc) abil.


Kui sammud probleemi ei lahendanud ja teil on endiselt probleeme ELF-failide avamisega, selle põhjuseks võib olla saadaolevate süsteemiressursside puudumine. Mõned ELF-failide versioonid võivad teie arvutis korralikult avamiseks nõuda märkimisväärsel hulgal ressursse (nt mälu/RAM, töötlemisvõimsus). See probleem on üsna tavaline, kui kasutate korraga üsna vana arvuti riistvara ja palju uuemat operatsioonisüsteemi.

See probleem võib ilmneda siis, kui arvutil on ülesande täitmisega raskusi, kuna operatsioonisüsteem (ja muud taustal töötavad teenused) võivad kulutab ELF-faili avamiseks liiga palju ressursse. Enne Nintendo Wii mängufaili avamist proovige sulgeda arvutis kõik rakendused. Vabastades arvutis kõik saadaolevad ressursid, tagate parimad tingimused ELF-faili avamiseks.


Kui sa lõpetanud kõik ülaltoodud sammud ja teie ELF-fail ikka ei avane, peate võib-olla käivitama riistvara uuendamine. Enamikul juhtudel, isegi vanemate riistvaraversioonide puhul, võib töötlemisvõimsusest enamiku kasutajarakenduste jaoks siiski enam kui piisav olla (välja arvatud juhul, kui teete palju protsessorimahukat tööd, nagu 3D-renderdamine, finants-/teaduste modelleerimine või intensiivne meediatöö). . Sellel viisil, tõenäoliselt pole teie arvutil piisavalt mälu(sagedamini nimetatakse seda "RAM" või RAM), et täita faili avamise ülesannet.

Selle vastuse versioon hea TOC ja muu sisuga: http://www.cirosantilli.com/elf-hello-world (klõpsake siin 30 000 tähemärgi piirang)

Standardid

ELFi annab LSB:

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

LSB viitab enamasti teistele väiksemate laiendustega standarditele, eelkõige:

    üldine (mõlemad SCO):

    • System V ABI 4.1 (1997) http://www.sco.com/developers/devspecs/gabi41.pdf, mitte 64-bitine, kuigi selle jaoks on reserveeritud maagiline number. Sama kehtib ka põhifailide kohta.
    • System V ABI värskenduse DRAFT 17 (2003) http://www.sco.com/developers/gabi/2003-12-17/contents.html lisab 64 bitti. Värskendab ainult eelmise dokumendi 4. ja 5. peatükke: ülejäänud jäävad kehtima ja neile viidatakse.
  • spetsiifiline arhitektuur:

    • IA-32: http://refspecs.linuxfoundation.org/LSB_4.1.0/LSB-Core-IA32/LSB-Core-IA32/elf-ia32.html viitab peamiselt aadressile 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 , viitab põhimõtteliselt http://www.x86-64.org/ dokumentatsioonile /abi.pdf

Mugava CV leiate aadressilt:

Selle struktuuri saab uurida kasutajasõbralike viisidega, nagu readelf ja objdump.

Loo näide

Jagame lahti minimaalse Linuxi x86-64 käivitatava näite:

Jaotis .data hello_world db "Tere maailm!", 10 hello_world_len equ $ - hello_world .text global _start _start: mov rax, 1 mov rdi, 1 mov rsi, hello_world mov rdx, hello_world syld syld0 sylld

Koostatud koos

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

  • NASM 2.10.09
  • Binutils versioon 2.24 (sisaldab ld-d)
  • Ubuntu 14.04

Me ei kasuta C-programmi, kuna see raskendaks analüüsi, mis oleks 2. tasemel :-)

kahendarvu kuueteistkümnendkujulised esitused

hd hello_world.o hd hello_world.out

Globaalne failistruktuur

ELF-fail sisaldab järgmisi osi:

  • ELFi päis. Näitab jaotise päiste tabeli ja programmi päise tabeli asukohta.

    Jaotise päise tabel (käivitusfailis valikuline). Igal neist on jaotise päised e_shnum , millest igaüks näitab jaotise asukohta.

    N vaheseint N-ga<= e_shnum (необязательно в исполняемом файле)

    Programmi päise tabel (ainult käivitatavate failide jaoks). Kõigil neil on programmi e_phnum päised, millest igaüks näitab segmendi asukohta.

    N segmenti koos N-ga<= e_phnum (необязательно в исполняемом файле)

Nende osade järjekord pole fikseeritud: ainus fikseeritud asi on ELF-i päis, mis peab olema failis esimene: Levinud dokumendid ütlevad:

ELFi päis

Lihtsaim viis päise vaatamiseks on:

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

Bait objektifailis:

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 | [e-postiga kaitstud]| 00000030 00 00 00 00 40 00 00 00 00 00 40 00 07 00 03 00 |[e-postiga kaitstud]@.....|

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 |..> [e-postiga kaitstud]| 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 |[e-postiga kaitstud]@.....|

Esitatud struktuur:

Typedef struktuure (märgistamata sümbolid 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;

Käsitsi lagunemine:

    0 0: EI_MAG = 7f 45 4c 46 = 0x7f "E", "L", "F" : ELF maagiline arv

    0 4: EI_CLASS=02=ELFCLASS64: 64-bitine elf

    0 5: EI_DATA = 01 = ELFDATA2LSB: suured andmed

    0 6: EI_VERSION = 01: vormingu versioon

    0 7: EI_OSABI (ainult 2003) = 00 = ELFOSABI_NONE: laiendusi pole.

    0 8: EI_PAD = 8x 00: reserveeritud baidid. Peab olema seatud väärtusele 0.

    1 0: e_type = 01 00 = 1 (big endian) = ET_REl: ümberpaigutatav vorming

    ET_EXEC täitmisfailis 02 00.

    1 2: e_machine = 3e 00 = 62 = EM_X86_64: AMD64 arhitektuur

    1 4: e_version = 01 00 00 00: peaks olema 1

    1 8: e_entry = 8x 00: täitmisaadressi sisestuspunkt või 0, kui see pole kohaldatav, nagu objektifaili puhul, kuna sisestuspunkti pole.

    Käivitatavas failis on see b0 00 40 00 00 00 00 00 . TODO: mida veel saame installida? Tundub, et kernel paneb IP-aadressi otse sellesse väärtusesse, see pole kõvakoodiga.

    2 0: e_phoff = 8x 00: programmi päise tabeli nihe, 0 kui mitte.

    40 00 00 00 käivitatavas failis, mis tähendab, et see algab kohe pärast ELF-i päist.

    2 8: e_shoff = 40 7x 00 = 0x40: jaotise päise tabeli faili nihe, 0, kui seda pole.

    3 0: e_lipud = 00 00 00 00 ÜLESANNE. Spetsiaalselt Arch jaoks.

    3 4: e_ehsize = 40 00: selle päkapiku päise suurus. Miks see väli on? Kuidas see muutuda saab?

    3 6: e_phentsize = 00 00: iga programmi päise suurus, 0 kui mitte.

    38 00 käivitatavas failis: faili pikkus on 56 baiti

    3 8: e_phnum = 00 00: programmi päise kirjete arv, 0, kui mitte ühtegi.

    02 00 käivitatavas failis: seal on 2 kirjet.

    3 A: e_shentsize ja e_shnum = 40 00 07 00: jaotise päise suurus ja kirjete arv

Jaotise päise tabel

Elf64_Shdr struktuuride massiiv.

Iga kirje sisaldab selle jaotise metaandmeid.

ELF-i päise e_shoff annab siin lähtepositsiooni, 0x40.

ELF-i päises olevad e_shentsize ja e_shnum ütlevad, et meil on 7 kirjet, millest igaüks on 0x40 pikk.

Seega võtab tabel baite vahemikus 0x40 kuni 0x40 + 7 + 0x40 - 1 = 0x1FF.

Mõned jaotiste pealkirjad on reserveeritud teatud tüüpi jaotistele: http://www.sco.com/developers/gabi/2003-12-17/ch4.sheader.html#special_sections näiteks. .text nõuab tüüpi SHT_PROGBITS ja SHF_ALLOC + SHF_EXECINSTR

readelf -S hello_world.o:

On 7 lõik päised, alustades nihe 0x40: jaotiste päised: Nimi Liik Aadress Offset Suurus EntSize Lipud Link Info Align [0] NULL 0000000000000000 00000000 0000000000000000 0000000000000000 0 0 0 [1] .data PROGBITS 0000000000000000 00000200 0000000000000000 000000000000000d WA 0 0 4 [ 2] .text PROGBITS 0000000000000000 00000210 0000000000000027 0000000000000000 AX 0 0 16 [3] .shstrtab STRTAB 0000000000000000 00000240 0000000000000032 0000000000000000 0 0 1 [4] .symtab SYMTAB 0000000000000000 00000280 0000000000000018 00000000000000a8 5 6 4 [5] .strtab STRTAB 00000330 0000000000000000 0000000000000034 0000000000000000 0 0 1 [ 6] .rela.text RELA 00000000000000000 00000370 0000000000000018 0000000000000018 4 2 4 Lippude võti: W (kirjutamine), A (täitmine), (st) I (teave), L (lingi järjekord), G (rühm), T (TLS), E (välista), x (teadmata) O (vajalik OS-i lisatöötlus) o (OS-spetsiifiline), p (protsessoripõhine)

struct , mida esindab iga kirje:

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_Sõna sh_link; Elf64_Sõna sh_link; Elf64_sh_info6; Elf64_sõna Elf64X;

Sektsioonid

Indeksi jaotis 0

Sisaldub baitides 0x40 kuni 0x7F.

Esimene jaotis on alati maagiline: http://www.sco.com/developers/gabi/2003-12-17/ch4.sheader.html ütleb:

Kui partitsioonide arv on SHN_LORESERVE (0xff00) või sellega võrdne, määratakse e_shnum väärtuseks SHN_UNDEF (0) ja partitsiooni päise tabeli kirjete tegelik arv sisaldub partitsiooni päise väljal sh_size indeksi 0 juures (muidu algkirje sh_size liige sisaldab 0).

Joonisel 4-7: Sektsioonide eriindeksid on ka teisi maagilisi jaotisi.

Indeksis 0 on SHT_NULL nõutav. Kas sellel on muid kasutusviise: Mis kasu on ELF-i jaotisest SHT_NULL? ?

.andmete jaotis

Andmed on jaotises 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 |................|

    Siin ütleb 1, et selle jaotise nimi algab selle jaotise esimesest märgist ja lõpeb esimese NUL-märgiga, moodustades string.data .

    Andmed on üks jaotiste nimedest, millel on eelnevalt määratletud tähendus http://www.sco.com/developers/gabi/2003-12-17/ch4.strtab.html

    Need jaotised salvestavad lähtestatud andmed, mis aitavad kaasa programmi mälupildile.

  • 80 4: sh_type = 01 00 00 00: SHT_PROGBITS: ELF ei määra sektsiooni sisu, vaid see, kuidas programm seda tõlgendab. See on okei, kuna .data .

    80 8: sh_flags = 03 7x 00: SHF_ALLOC ja SHF_EXECINSTR: http://www.sco.com/developers/gabi/2003-12-17/ch4.sheader.html#sh_flags , nagu nõutakse jaotises .data

    90 0: sh_addr = 8x 00: millisele virtuaalsele aadressile sektsioon käitusajal paigutatakse, 0, kui seda ei paigutata

    90 8: sh_offset = 00 02 00 00 00 00 00 00 = 0x200: baitide arv programmi algusest kuni esimese baidini selles jaotises

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

    Kui võtame 0xD baiti alates sh_offset 200-st, näeme:

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

    AHA! Nii et meie string "Tere maailm!" on andmete jaotises, nagu me ütlesime, on see NASM-is.

    Kui oleme HD lõpetanud, vaatame seda järgmiselt:

    Readelf -x .data hello_world.o

    mis väljastab:

    Kuueteistkümnendväljavõte jaotisest ".andmed": 0x00000000 48656c6c 6f20776f 726c6421 0a Tere maailm!.

    NASM määrab selle jaotise jaoks korralikud omadused, kuna see viitab võluväel .datale: http://www.nasm.us/doc/nasmdoc7.html#section-7.9.2

    Pange tähele ka seda, et see oli vale partitsiooni valik: hea C-kompilaator paneks rea hoopis .rodata faili, kuna see on kirjutuskaitstud ja see võimaldaks OS-i optimeerimist jätkata.

    a0 8: sh_link ja sh_info = 8x 0: ei kehti selle jaotise tüübi kohta. http://www.sco.com/developers/gabi/2003-12-17/ch4.sheader.html#special_sections

    b0 0: sh_addralign = 04 = TODO: miks on see joondus vajalik? Kas see on ainult sh_addr ja ka sh_addr sees olevate märkide jaoks?

    b0 8: sh_entsize = 00 = jaotis ei sisalda tabelit. Kui != 0, tähendab see, et jaotis sisaldab kindla suurusega kirjete tabelit. Selles failis näeme readelfi väljundist, et see kehtib jaotiste .symtab ja .rela.text puhul.

.teksti jaotis

Nüüd, kui oleme ühe jaotise käsitsi teinud, lõpetame ja kasutame teiste jaotiste readelf -S.

Nimi Tüüp Aadress Nihe Suurus EntSize Lipud Lingi teave Joonda [ 2] .text

Tekst on käivitatav, kuid mitte kirjutatav: kui proovime sellele kirjutada Linuxi segfaults. Vaatame, kas meil on tõesti kood:

Objdump -d hello_world.o

Hello_world.o: failivorming elf64-x86-64 Sektsiooni .text lahtivõtmine: 00000000000000000<_start>: 0: b8 01 00 00 00 liikumine $0x1,%eax 5: bf 01 00 00 00 liikumine $0x1,%edi a: 48 be 00 00 00 00 00 movabs $0x0,%rsi 11:00 14:00 0d 00 00 00 liikumine $0xd,%edx 19: 0f 05 syscall 1b: b8 3c 00 00 00 liikumine $0x3c,%eax 20: bf 00 00 00 00 mov $0x0,%edi 205: syscall 205

Kui meil on hd-l grep b8 01 00 00, näeme, et see juhtub ainult numbril 00000210, mida see jaotis ütleb. Ja suurus on 27, mis sobib ka. Seetõttu peame rääkima õigest jaotisest.

See näeb välja nagu õige kood: kirjutamine, millele järgneb väljumine .

Kõige huvitavam osa on rida a , mis teeb:

Movabs $0x0,%rsi

edasta stringi aadress süsteemikutsele. Praegu on 0x0 vaid kohatäide. Pärast sidumist muutub see:

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

See muudatus on võimalik tänu jaotises .rela.text olevatele andmetele.

SHT_STRTAB

Sektsioone sh_type == SHT_STRTAB nimetatakse stringitabeliteks.

Selliseid jaotisi kasutavad stringinimede kasutamisel teised jaotised. Kasutamise jaotis ütleb:

  • mis liini nad kasutavad
  • milline on indeks sihtridade tabelis, kust rida algab

Näiteks võiks meil olla stringitabel, mis sisaldab: TODO: kas peaksime alustama tähega \0 ?

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

Ja kui mõni teine ​​jaotis soovib kasutada stringi d e f , peavad nad osutama selle jaotise indeksile 5 (täht d).

Tuntud stringitabelid:

  • .shstrtab
  • .strtab

.shstrtab

Jao tüüp: sh_type == SHT_STRTAB .

Üldnimetus: jaotise pealkirja päise rida.

Jaotis name.shstrtab on reserveeritud. Standard ütleb:

See jaotis sisaldab jaotiste nimesid.

See jaotis määrab ELF-i päise enda välja e_shstrnd.

Selle jaotise reaindeksid on tähistatud ridu tähistavate jaotiste päiste väljaga sh_name.

See jaotis ei määra SHF_ALLOC, seega ei kuvata seda käivitatavas failis.

Readelf -x .shstrtab hello_world.o

Hex prügila rubriigis ".shstrtab": 0x00000000 002e6461 7461002e 74657874 73747274 002e7368 ..data..text..sh 0x00000010 6162002e 73796d74 6162002e strtab..symtab .. 0x00000020 73747274 6162002e 72656c61 2e746578 strtab..rela.tex 0x00000030 7400 t.

Selles jaotises olevad andmed on fikseeritud vormingus: http://www.sco.com/developers/gabi/2003-12-17/ch4.strtab.html

Kui vaatame teisi jaotiste nimesid, näeme, et need kõik sisaldavad numbreid, nt. .teksti jaotis on nummerdatud 7 .

Seejärel lõpeb iga rida siis, kui on leitud esimene NUL-märk, nt. märk 12 \0 kohe pärast .text\0 .

.symtab

Jao tüüp: sh_type == SHT_SYMTAB .

Üldnimetus: sümbolite tabel.

Kõigepealt paneme tähele, et:

  • sh_link = 5
  • sh_info=6

Jaotises SHT_SYMTAB tähendavad need numbrid järgmist:

  • Stringid
  • mis annavad sümbolite nimesid, on jaotises 5, .strtab
  • ümberpaigutamise andmed on jaotises 6, .rela.text

Hea kõrgetasemeline tööriist selle jaotise lahtivõtmiseks:

Nm hello_world.o

mis annab:

0000000000000000 T _start 0000000000000000 d hello_world 000000000000000d a hello_world_len

See on siiski kõrgetasemeline esitus, mis jätab teatud tüüpi märgid välja ja tähistab märke. Üksikasjalikuma jaotuse saate kasutades:

Readelf -s hello_world.o

mis annab:

Sümbol tabel ".syMtab" Sisaldab 7 kirjet: Num: Väärtus Suurus tüüp seonduvad NDX nimi 0: 0000000000000000 0 Notype Kohalik vaikimisi und 1: 0000000000000000 0 Faili kohaliku vaikimisi abs hello_world.ASM 2: 0000000000000000 0 lõik Kohalik vaikimisi 1 3: 0000000000000000 0 _

Tabeli binaarvorming on dokumenteeritud aadressil http://www.sco.com/developers/gabi/2003-12-17/ch4.symtab.html

Readelf -x .symtab hello_world.o

Mis annab:

Jaotise ".symtab" kuueteistkümnendväljatõmme: 0x00000000 00000000 00000000 00000000 00000000 ................ 0x00000010 000000000 00000000 00000000 00000000 00000000 ............... . 0x00000020 00000000 00000000 00000000 00000000 00000000 00000000 .....00..00..00 00000000 0x00000000 00000000 0x00000000 00000000 000000 .....00..00000000 000000 .....00..00 0000000000 00000000 0000000000 0x00000000 ............. 00000000 ................ 0x00000090 2d000000 10000200 00000000 00000000 -............. 0x000000a0 00000000 00000000 .......

Kirjed on tüüpi:

Typedef struct ( Elf64_Word st_name; unsigned char st_info; unsigned char st_other; Elf64_Half st_shndx; Elf64_Addr st_value; Elf64_Xword st_size; ) Elf64_Sym;

Sarnaselt partitsioonitabelile on esimene kirje maagiline ja seatud fikseeritud mõttetutele väärtustele.

Kirjel 1 on ELF64_R_TYPE == STT_FILE . ELF64_R_TYPE jätkab st_info sees.

Baitianalüüs:

    10 8: st_name = 01000000 = tähemärk 1 failis .strtab , mis kuni järgmise \0 teeb tere_world.asm

    Seda teabefaili fragmenti saab linker kasutada, et määrata, millised segmendi segmendid on tulemas.

    10 12: st_info = 04

    Bitid 0-3 = ELF64_R_TYPE = Tüüp = 4 = STT_FILE: Selle kirje põhieesmärk on kasutada st_name, et määrata selle objektifaili poolt genereeritud faili nimi.

    Bitid 4-7 = ELF64_ST_BIND = Side = 0 = STB_LOCAL. STT_FILE nõutav väärtus.

    10 13: st_shndx = Sümbolitabel Päiste tabel Indeks = f1ff = SHN_ABS . Nõutav faili STT_FILE jaoks.

    20 0: st_value = 8x 00: nõutav STT_FILE väärtuse jaoks

    20 8: st_size = 8x 00: suurus puudub

Readelfist tõlgendame nüüd kiiresti ülejäänu.

STT_SECTION

Selliseid elemente on kaks, üks osutab failile .data ja teine ​​tekstile (jaotise indeksid 1 ja 2).

Arv: Väärtus Suurus Tüüp Köide Vis Ndx Nimi 2: 0000000000000000 0 JAOTIS LOCAL VAIKESEADMED 1 3: 00000000000000000 0 SECTION LOCAL DEFAULT 2

TODO, mis on nende eesmärk?

STT_NOTYPE

Seejärel sisestage kõige olulisemad märgid:

Arv: Väärtus Suurus Tüüp Köide Vis Ndx Nimi 4: 00000000000000000 0 NOTÜÜP LOCAL DEFAULT 1 hello_world 5: 000000000000000d 0 NOTYPE LOCAL DEFAULT ABS_0GL000 start

hello_world on jaotises .data (indeks 1). See väärtus on 0: see osutab selle jaotise esimesele baidile.

Algus on tähistatud GLOBAALSE nähtavusega, nagu me kirjutasime:

global_start

NASMis. See on vajalik, kuna seda tuleks käsitleda sisenemispunktina. Erinevalt C-st on NASM-i sildid vaikimisi kohalikud.

hello_world_len osutab erilisele st_shndx == SHN_ABS == 0xF1FF .

0xF1FF valitakse nii, et see ei oleks vastuolus teiste jaotistega.

st_value == 0xD == 13 , mis on väärtus, mille me koostamisel sinna salvestasime: stringi pikkus Tere maailm! .

See tähendab, et liigutus ei mõjuta seda väärtust: see on konstant.

See on väike optimeerimine, mille meie monteerija meie eest teeb ja millel on ELF-i tugi.

Kui kasutaksime aadressi hello_world_len igal pool, ei saaks monteerija seda SHN_ABS-iks märkida ja linker saaks hiljem täiendavalt ümber paigutada.

SHT_SYMTAB käivitatavas failis

Vaikimisi paigutab NASM käivitatavasse faili .symtab.

Seda kasutatakse ainult silumiseks. Ilma sümboliteta oleme täiesti pimedad ja peame kõik ümber kujundama.

Saate selle eemaldada objcopy abil ja käivitatav fail töötab endiselt. Selliseid käivitatavaid faile nimetatakse jagatud käivitatavateks failideks.

.strtab

Hoiab tähemärkide tabeli stringe.

Selles jaotises sh_type == SHT_STRTAB .

Osutab .symtab sektsiooni sh_link == 5-le.

Readelf -x .strtab hello_world.o

Hex prügila rubriigis ".strtab": 0x00000000 0068656c 6c6f5f77 6f726c64 2e61736d .hello_world.asm 0x00000010 0068656c 6c6f5f77 6f726c64 0068656c .hello_world.hel 0x00000020 6c6f5f77 6f726c64 5f6c656e 005f7374 lo_world_len._st 0x00000030 61727400 art.

See tähendab, et tegemist on ELF-taseme piiranguga, et globaalsed muutujad ei tohi sisaldada NUL-märke.

.rela.tekst

Jao tüüp: sh_type == SHT_RELA .

Üldnimetus: liiguta jaotist.

Rela.text sisaldab ümberpaigutamisandmeid, mis täpsustavad, kuidas aadressi tuleks viimase käivitatava faili lingimisel muuta. See näitab tekstiala baite, mida tuleks õigete mälukohtadega linkimisel muuta.

Põhimõtteliselt teisendab see 0x0 kohahoidja aadressi sisaldava objekti teksti:

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

tegelikule käivitatavale koodile, mis sisaldab lõplikku 0x6000d8:

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

Määrati jaotise .symtab väärtus sh_info = 6.

readelf -r hello_world.o annab:

Ümberpaigutamise jaotis ".rela.text" nihkes 0x3b0 sisaldab 1 kirjet: Nihketeabe tüüp Sym. Value Sym. Nimi + lisa 00000000000c 000200000001 R_X86_64_64 00000000000000000 .data + 0

Seda jaotist käivitatavas failis pole.

Tegelikud baidid:

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

Esindatud struktuur:

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

    370 0: r_offset = 0xC: aadress aadressile.tekst, mille aadressi muudetakse

    370 8: r_info = 0x200000001. Sisaldab 2 välja:

    • ELF64_R_TYPE = 0x1: väärtus sõltub täpsest arhitektuurist.
    • ELF64_R_SYM = 0x2: selle partitsiooni indeks, millele aadress osutab, seega .data , mis asub indeksis 2.

    AMD64 ABI ütleb, et tüüp 1 kannab nime R_X86_64_64 ja see esindab S+A toimingut, kus:

    • S: sümboli väärtus objektifailis, siin 0, sest me osutame movabs $0x0,%rsi väärtusele 00 00 00 00 00 00 00 00
    • a: täiendus, mis on väljal r_added

    See aadress lisatakse partitsioonile, kus teisaldamine toimub.

    See liigutusoperatsioon töötab 8 baidil.

    380 0: r_addend = 0

Seega järeldame meie näites, et uus aadress on: S + A = .data + 0 ja seega andmejaotises esimene.

Programmi pealkirjade tabel

Kuvatakse ainult käivitatavas failis.

Sisaldab teavet selle kohta, kuidas käivitatav fail protsessi virtuaalmällu paigutada.

Käivitatava faili loob linkeri objektifail. Peamised ülesanded, mida linker täidab:

    määrake, millised objektifailide osad lähevad käivitatava faili millistesse segmentidesse.

    Binutilsis taandub see ehitaja skripti sõelumisele ja paljude vaikeseadetega töötamisele.

    Linkeri skripti saate kasutada koos ld --verbose ja installida kohandatud skripti käsuga ld -T.

    tekstiosades navigeerimiseks. See sõltub sellest, kuidas mitu partitsiooni mällu mahub.

readelf -l hello_world.out annab:

Elf failitüüp on exec (käivitatava faili) Sisenemise koht 0x4000b0 Seal on 2 programmi päised, alustades nihe 64 programmi päised: Liik Nihe VirtAddr PhysAddr FileSiz MemSiz Lipud Align LOAD 0x0000000000000000 0x0000000000400000 0x0000000000400000 0x00000000000000d7 0x00000000000000d7 RE 200000 LOAD 0x00000000000000d8 0x00000000006000d8 0x00000000006000d8 0x000000000000000d 0x000000000000000d RW 200000 jagu kuni Segmendi kaardistamine: Segment Sections... 00 .text 01 .data

ELF-i päises e_phoff , e_phnum ja e_phentsize ütlesid meile, et on kaks programmipäist, mis algavad 0x40 ja igaüks on 0x38 baiti, seega on need:

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 |[e-postiga kaitstud]@.....| 00000060 d7 00 00 00 00 00 00 00 d7 00 00 00 00 00 00 00 |................| 00000070 00 00 20 00 00 00 00 00 |... ..... |

00000070 01 00 00 00 06 00 00 00 | ........| 00000080 d8 00 00 00 00 00 00 00 d8 00 60 00 00 00 00 00 |........`.....| 00000090 d8 00 60 00 00 00 00 00 0d 00 00 00 00 00 00 00 |..`.............| 000000a0 0d 00 00 00 00 00 00 00 00 00 20 00 00 00 00 00 |......... .....| typedef struct ( Elf64_Word p_type; Elf64_Word p_flags; Elf64_Off p_offset; Elf64_Addr p_vaddr; Elf64_Addr p_paddr; Elf64_Xword p_filesz; Elf64_Xword p_memsz_HD; Elf_4_hdword)

Esimese jaotus:

  • 40 0: p_tüüp = 01 00 00 00 = PT_LOAD: ÜLESANNE. Ma arvan, et see tähendab, et see laaditakse mällu. Muud tüübid ei pruugi tingimata olla.
  • 40 4: p_flags = 05 00 00 00 = täitmis- ja lugemisõigused, ära kirjuta TODO
  • 40 8: p_offset = 8x 00 TODO: mis see on? See näeb välja nagu nihked segmentide algusest. Kuid kas see tähendaks, et mõned segmendid on omavahel põimunud? Sellega saab natuke mängida: gcc -Wl,-Ttext-segment=0x400030 hello_world.c
  • 50 0: p_vaddr = 00 00 40 00 00 00 00 00: virtuaalse mälu algusaadress selle segmendi laadimiseks
  • 50 8: p_paddr = 00 00 40 00 00 00 00 00: algne füüsiline aadress mällu laadimiseks. Küsimused ainult süsteemidele, kus programm saab määrata füüsilise aadressi. Vastasel juhul, nagu ka System V süsteemide puhul, võib kõike juhtuda. Näib, et NASM lihtsalt kopeerib faili 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: ÜLESANNE
  • 70 0: p_align = 00 00 20 00 00 00 00 00: 0 või 1 tähendab, et TODO pole vaja joondada, mida see tähendab? muidu teiste väljadega üleliigne

Teine on sarnane.

Segmendi vastendamine:

Readelfi jaotis ütleb meile, et:

  • 0 - segment.tekst . Jah, sellepärast on see käivitatav ja mitte kirjutatav.
  • 1 - segment.andmed .

Sarnased postitused