Paloturvallisuuden tietosanakirja

Kuinka avata elf-tiedosto Windowsissa. Mikä on ELF-tiedostotunniste? Dataa kuvaavat solmut

Vakiokehitystyökalut kääntävät ohjelmasi ELF-tiedostoon (Executable and Linkable Format), johon voidaan sisällyttää virheenkorjaustietoja. Muotokuvauksen voi lukea. Lisäksi jokaisella arkkitehtuurilla on omat ominaisuudet, kuten ARM-ominaisuudet. Katsotaanpa lyhyesti tätä muotoa.
ELF-muodossa oleva suoritettava tiedosto koostuu seuraavista osista:
1. Otsikko (ELF-otsikko)
Sisältää yleistä tietoa tiedostosta ja sen tärkeimmistä ominaisuuksista.
2. Ohjelman otsikkotaulukko
Tämä on tiedostoosien ja muistisegmenttien välinen vastaavuustaulukko, joka kertoo käynnistyslataimelle, mihin muistialueeseen kukin osa tulee kirjoittaa.
3. Osat
Osiot sisältävät kaikki tiedoston tiedot (ohjelma, tiedot, virheenkorjaustiedot jne.)
Jokaisella osalla on tyyppi, nimi ja muut parametrit. ".text"-osio tallentaa yleensä koodin, ".symtab" - ohjelmasymbolien taulukko (tiedostojen nimet, menettelyt ja muuttujat), ".strtab" - merkkijonotaulukko, osiot ".debug_"-etuliitteellä - virheenkorjaustiedot jne. .d. Lisäksi tiedostossa on oltava tyhjä osio, jonka indeksi on 0.
4. Osion otsikkotaulukko
Tämä on taulukko, joka sisältää joukon osion otsikoita.
Muotoa käsitellään tarkemmin osiossa ELF:n luominen.

DWARF Yleiskatsaus

DWARF on standardoitu virheenkorjaustietomuoto. Standardi voidaan ladata viralliselta verkkosivustolta. Mukana on myös loistava lyhyt yleiskatsaus muotoon: Johdatus DWARF-virheenkorjausmuotoon (Michael J. Eager).
Miksi virheenkorjaustietoja tarvitaan? Se sallii:
  • aseta keskeytyspisteitä ei fyysiseen osoitteeseen, vaan lähdekooditiedoston rivinumeroon tai funktion nimeen
  • näyttää ja muuttaa globaalien ja paikallisten muuttujien arvoja sekä toimintoparametreja
  • näytä puhelupino (backtrace)
  • suorita ohjelma askel askeleelta, ei yhden kokoonpanokäskyn mukaan, vaan lähdekoodirivien mukaan
Nämä tiedot tallennetaan puurakenteeseen. Jokaisella puusolmulla on vanhempi, sillä voi olla lapsia, ja sitä kutsutaan DIE:ksi (Debugging Information Entry). Jokaisella solmulla on oma tunniste (tyyppi) ja luettelo attribuuteista (ominaisuuksista), jotka kuvaavat solmua. Attribuutit voivat sisältää mitä tahansa, kuten tietoja tai linkkejä muihin solmuihin. Lisäksi puun ulkopuolelle on tallennettu tietoa.
Solmut on jaettu kahteen päätyyppiin: dataa kuvaaviin solmuihin ja koodia kuvaaviin solmuihin.
Dataa kuvaavat solmut:
  1. Tietotyypit:
    • Perustietotyypit (solmu, jonka tyyppi on DW_TAG_base_type), kuten int-tyyppi C:ssä.
    • Yhdistelmätietotyypit (osoittimet jne.)
    • Taulukot
    • Rakenteet, luokat, liitot, rajapinnat
  2. Tietoobjektit:
    • vakioita
    • toimintoparametreja
    • muuttujia
    • jne.
Jokaisella tietoobjektilla on attribuutti DW_AT_location, joka määrittää kuinka osoite, jossa tiedot sijaitsevat, lasketaan. Esimerkiksi muuttujalla voi olla kiinteä osoite, se voi olla rekisterissä tai pinossa tai olla luokan tai objektin jäsen. Tämä osoite voidaan laskea melko monimutkaisella tavalla, joten standardi tarjoaa ns. sijaintilausekkeet, jotka voivat sisältää tietyn sisäisen pinokoneen operaattoreita.
Koodia kuvaavat solmut:
  1. Proseduurit (funktiot) - solmut tunnisteella DW_TAG_aliohjelma. Jälkisolmut voivat sisältää muuttujien kuvauksia - funktioparametreja ja paikallisia funktiomuuttujia.
  2. Kokoonpanoyksikkö. Sisältää ohjelmatiedot ja on kaikkien muiden solmujen emo.
Yllä kuvatut tiedot sijaitsevat osioissa ".debug_info" ja ".debug_abbrev".
Muita tietoja:
  • Tiedot rivinumeroista (osio ".debug_line")
  • Tietoja makroista (osio ".debug_macinfo")
  • Kutsukehyksen tiedot (osio ".debug_frame")

ELF:n luominen

Luomme tiedostot EFL-muodossa käyttämällä elfutils-paketin libelf-kirjastoa. Internetissä on hyvä artikkeli libelfin käytöstä - LibELF by Esimerkki (valitettavasti se kuvaa tiedostojen luomista hyvin lyhyesti) sekä dokumentaatiota.
Tiedoston luominen koostuu useista vaiheista:
  1. Alastetaan kunnianloukkaus
  2. Tiedoston otsikon luominen (ELF-otsikko)
  3. Ohjelman otsikkotaulukon luominen
  4. Osioiden luominen
  5. Kirjoita tiedosto
Katsotaanpa vaiheita tarkemmin
Alastetaan kunnianloukkaus
Ensin sinun on kutsuttava elf_version(EV_CURRENT)-funktio ja tarkistettava tulos. Jos se on yhtä suuri kuin EV_NONE, on tapahtunut virhe eikä muita toimintoja voida suorittaa. Sitten meidän on luotava tarvitsemamme tiedosto levylle, hankittava sen kuvaaja ja välitettävä se elf_begin-funktiolle:
Elf * elf_begin(int fd, Elf_Cmd cmd, Elf *elf)
  • fd - juuri avatun tiedoston kuvaaja
  • cmd - tila (ELF_C_READ tietojen lukemiseen, ELF_C_WRITE kirjoittamiseen tai ELF_C_RDWR lukemiseen/kirjoitukseen), sen on vastattava avoimen tiedoston tilaa (tapauksessamme ELF_C_WRITE)
  • tonttu - tarvitaan vain arkistotiedostojen (.a) kanssa työskentelemiseen, meidän tapauksessamme sinun on läpäistävä 0
Funktio palauttaa osoittimen luotuun kahvaan, jota käytetään kaikissa kunnianloukkausfunktioissa, virhe palauttaa 0.
Otsikon luominen
Elf32_newehdr-funktio luo uuden tiedoston otsikon:
Elf32_Ehdr * elf32_newehdr(Tonttu *tonttu);
  • elf - elf_begin-funktion palauttama kahva
Palauttaa 0:n virheestä tai osoittimen rakenteeseen - ELF-tiedoston otsikkoon:
#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_2fffoff; lags; Elf32_Half e_phentsize;

Osa sen kentistä on täytetty normaalilla tavalla, osa meidän on täytettävä:

  • e_ident - tunnistustavutaulukko, jolla on seuraavat indeksit:
    • EI_MAG0, EI_MAG1, EI_MAG2, EI_MAG3 - näiden 4 tavun tulee sisältää merkit 0x7f"ELF", jonka elf32_newehdr-funktio on jo tehnyt puolestamme
    • EI_DATA - ilmaisee tiedoston koodauksen tyypin: ELFDATA2LSB tai ELFDATA2MSB. Sinun on asennettava ELFDATA2LSB seuraavasti: e_ident = ELFDATA2LSB
    • EI_VERSION - tiedoston otsikkoversio, joka on jo asetettu meille
    • EI_PAD - älä koske
  • e_type - tiedostotyyppi, voi olla ET_NONE - ei tyyppiä, ET_REL - uudelleensijoitettava tiedosto, ET_EXEC - suoritettava tiedosto, ET_DYN - jaettu objektitiedosto jne. Meidän on asetettava tiedostotyypiksi ET_EXEC
  • e_machine - tälle tiedostolle vaadittu arkkitehtuuri, esimerkiksi EM_386 - Intel-arkkitehtuurille, ARM:lle meidän on kirjoitettava EM_ARM (40) tähän - katso ELF ARM-arkkitehtuurista
  • e_version - tiedostoversio, on asetettava arvoon EV_CURRENT
  • e_entry - sisääntulopisteen osoite, ei meille välttämätöntä
  • e_phoff - offset ohjelman otsikkotiedostossa, e_shoff - osan otsikon siirtymä, älä täytä
  • e_flags - prosessorikohtaiset liput, arkkitehtuurimme (Cortex-M3) arvoksi tulee asettaa 0x05000000 (ABI-versio 5)
  • e_ehsize, e_phentsize, e_phnum, e_shentsize, e_shnum - älä koske
  • e_shstrndx - sisältää sen osan numeron, jossa osion otsikoiden rivitaulukko sijaitsee. Koska meillä ei ole vielä osioita, asetamme tämän numeron myöhemmin
Ohjelman otsikon luominen
Kuten jo mainittiin, ohjelman otsikkotaulukko on tiedostoosien ja muistisegmenttien vastaavuustaulukko, joka kertoo käynnistyslataimelle, mihin kukin osio kirjoittaa. Otsikko luodaan käyttämällä elf32_newphdr-funktiota:
Elf32_Phdr * elf32_newphdr(Tonttu *tonttu, koko_t-määrä);
  • tonttu on kahvamme
  • count - luotavien taulukkoelementtien lukumäärä. Koska meillä on vain yksi osa (ohjelmakoodilla), määrä on yhtä suuri kuin 1.
Palauttaa 0:n virheestä tai osoittimen ohjelman otsikkoon.
Jokainen otsikkotaulukon elementti on kuvattu seuraavalla rakenteella:
TYPEDEF RAKENNE (ELF32_WORD P_TYPE; ELF32_OFF P_OFFSET; ELF32_ADDR P_VADDR; ELF32_ADDR P_PADDDR; ELF32_WORD P_Filesz; ELF32_WORD P_MEMSZZ; ELF32_WORD P_FL AGS;
  • p_type - segmentin (osion) tyyppi, tässä on määritettävä PT_LOAD - lataussegmentti
  • p_offset - offset tiedostossa, josta muistiin ladattavan osan tiedot alkavat. Meille tämä on .text-osio, joka sijaitsee heti tiedoston otsikon ja ohjelman otsikon jälkeen, voimme laskea offsetin näiden otsikoiden pituuksien summana. Minkä tahansa tyypin pituus voidaan saada käyttämällä elf32_fsize-funktiota:
    size_t elf32_fsize(Elf_Type type, size_t count, unsigned int version); kirjoita - tässä vakio ELF_T_xxx, tarvitsemme koot ELF_T_EHDR ja ELF_T_PHDR; count - halutun tyyppisten elementtien lukumäärä, versio - on asetettava arvoon EV_CURRENT
  • p_vaddr, p_paddr - virtuaalinen ja fyysinen osoite, johon osion sisältö ladataan. Koska meillä ei ole virtuaalisia osoitteita, asetamme sen yhtä suureksi kuin fyysinen, yksinkertaisimmassa tapauksessa - 0, koska ohjelmamme ladataan tähän.
  • p_filesz, p_memsz - osion koko tiedostossa ja muistissa. Meillä on ne samat, mutta koska ohjelmakoodia ei vielä ole, asennamme ne myöhemmin
  • p_flags - ladatun muistisegmentin käyttöoikeudet. Voi olla PF_R - lukea, PF_W - kirjoittaa, PF_X - suorittaa tai näiden yhdistelmä. Aseta p_flags arvoon PF_R + PF_X
  • p_align - segmenttien kohdistus, meillä on 4
Osioiden luominen
Otsikkeiden luomisen jälkeen voit aloittaa osioiden luomisen. Tyhjä osio luodaan elf_newscn-funktiolla:
Elf_Scn * elf_newscn(Tonttu *tonttu);
  • elf - elf_begin-funktion aiemmin palauttama kahva
Funktio palauttaa osoittimen osaan tai 0:n virheen sattuessa.
Kun olet luonut osan, sinun on täytettävä osion otsikko ja luotava osion tietokuvaaja.
Saamme osoittimen osion otsikkoon käyttämällä elf32_getshdr-funktiota:
Elf32_Shdr * elf32_getshdr(Elf_Scn *scn);
  • scn on osoitin osioon, jonka saimme elf_newscn-funktiosta.
Osion otsikko näyttää tältä:
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_W_link; Elf32ord3 Word sh_entsize ) Elf32_Shdr;
  • sh_name - osion nimi - offset osion otsikoiden merkkijonotaulukossa (section.shstrtab) - katso "Merkkijonotaulukot" alla
  • sh_type - osion sisältötyyppi, ohjelmakoodia sisältävälle jaksolle on asetettava SHT_PROGBITS, osille, joissa on merkkijonotaulukko - SHT_STRTAB, symbolitaulukolle - SHT_SYMTAB
  • sh_flags ovat osion lippuja, jotka voidaan yhdistää ja joista tarvitsemme vain kolme:
    • SHF_ALLOC - tarkoittaa, että osa ladataan muistiin
    • SHF_EXECINSTR - osio sisältää suoritettavan koodin
    • SHF_STRINGS - osio sisältää merkkijonotaulukon
    Vastaavasti ohjelman .text-osion liput on asetettava SHF_ALLOC + SHF_EXECINSTR
  • sh_addr - osoite, johon osa ladataan muistiin
  • sh_offset - tiedoston osion siirtymä - älä koske siihen, kirjasto asentaa sen puolestamme
  • sh_size - osan koko - älä koske
  • sh_link - sisältää linkitetyn osan numeron, jota tarvitaan linkittämään osio sitä vastaavaan rivitaulukkoon (katso alla)
  • sh_info - lisätiedot osion tyypistä riippuen, arvoksi 0
  • sh_addralign - osoitteen kohdistus, älä koske
  • sh_entsize - jos osa koostuu useista samanpituisista elementeistä, ilmaisee tällaisen elementin pituuden, älä kosketa
Kun olet täyttänyt otsikon, sinun on luotava osion datakuvaaja elf_newdata-funktiolla:
Elf_Data * elf_newdata(Elf_Scn *scn);
  • scn on juuri vastaanotettu osoitin uuteen osioon.
Funktio palauttaa 0:n virheessä tai osoittimen Elf_Data-rakenteeseen, joka on täytettävä:
typedef struct ( void* d_buf; Elf_Type d_type; koko_t d_size; off_t d_off; size_t d_align; unsigned d_version; ) Elf_Data;
  • d_buf - osoitin osioon kirjoitettaviin tietoihin
  • d_type - tietotyyppi, ELF_T_BYTE sopii meille kaikkialle
  • d_size - tiedon koko
  • d_off - poikkeama osassa, asetettu arvoon 0
  • d_align - kohdistus, voidaan asettaa arvoon 1 - ei kohdistusta
  • d_version - versio, on asetettava arvoon EV_CURRENT
Erikoisosat
Meidän on luotava tarvittava vähimmäismäärä osioita varten:
  • .text - osa ohjelmakoodilla
  • .symtab - tiedostosymbolitaulukko
  • .strtab on merkkijonotaulukko, joka sisältää symbolien nimet .symtab-osiosta, koska jälkimmäinen ei tallenna itse nimiä, vaan niiden indeksejä
  • .shstrtab - merkkijonotaulukko, joka sisältää osien nimet
Kaikki osiot luodaan edellisessä osiossa kuvatulla tavalla, mutta jokaisella erikoisosalla on omat ominaisuutensa.
Section.text
Tämä osio sisältää suoritettavan koodin, joten sinun on asetettava sh_type arvoksi SHT_PROGBITS, sh_flags arvoksi SHF_EXECINSTR + SHF_ALLOC, sh_addr osoitteeksi, johon tämä koodi ladataan.
Section.symtab
Osio sisältää kuvauksen kaikista ohjelman symboleista (toiminnoista) ja tiedostoista, joissa ne on kuvattu. Se koostuu seuraavista elementeistä, kukin 16 tavua pitkä:
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 - symbolin nimi (hakemisto merkkijonotaulukossa.strtab)
  • st_value - arvo (syöttöosoite funktiolle tai 0 tiedostolle). Koska Cortex-M3:ssa on peukalo-2-käskysarja, tämän osoitteen on oltava pariton (todellinen osoite + 1)
  • st_size - funktiokoodin pituus (0 tiedostolle)
  • st_info - symbolityyppi ja sen laajuus. Tämän kentän arvon määrittämiseksi on makro
    #define ELF32_ST_INFO(b,t) (((b)<<4)+((t)&0xf))
    missä b on laajuus ja t on merkkityyppi
    Laajuus voi olla STB_LOCAL (symboli ei näy muista objektitiedostoista) tai STB_GLOBAL (näkyvä). Yksinkertaistaaksemme asioita, käytämme STB_GLOBAL.
    Symbolityyppi - STT_FUNC funktiolle, STT_FILE tiedostolle
  • st_other - aseta arvoksi 0
  • st_shndx - sen osan indeksi, jolle symboli on määritetty (section index.text), tai SHN_ABS tiedostolle.
    Osion indeksi sen scn-kuvaajan perusteella voidaan määrittää komennolla elf_ndxscn:
    koko_t elf_ndxscn(Elf_Scn *scn);

Tämä osio luodaan tavalliseen tapaan, vain sh_type on asetettava arvoon SHT_SYMTAB, ja osio index.strtab tulee kirjoittaa sh_link-kenttään, jotta nämä osat linkitetään.
Section.strtab
Tämä osio sisältää kaikkien .symtab-osion symbolien nimet. Luotu kuten normaali osio, mutta sh_type on asetettava arvoon SHT_STRTAB ja sh_flags arvoon SHF_STRINGS, joten tästä osiosta tulee merkkijonotaulukko.
Osion tiedot voidaan kerätä siirtämällä lähdeteksti taulukkoon, jonka osoitin kirjoitetaan sitten osion datakuvaajaan (d_buf).
Section.shstrtab
Osio on merkkijonotaulukko, joka sisältää tiedoston kaikkien osien otsikot, mukaan lukien sen oman otsikon. Se luodaan samalla tavalla kuin .strtab-osio. Luomisen jälkeen sen indeksi on kirjoitettava tiedoston otsikon e_shstrndx-kenttään.
String taulukot
Merkkijonotaulukot sisältävät peräkkäisiä rivejä, jotka päättyvät nollatavuun, myös tämän taulukon ensimmäisen tavun on oltava 0. Taulukon rivin indeksi on yksinkertaisesti taulukon alusta tavuina oleva siirtymä, joten ensimmäinen rivi "nimi" on indeksi 1, seuraavalla rivillä "var" on indeksi 6.
Hakemisto 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
Kirjoita tiedosto
Joten, otsikot ja osiot on jo muodostettu, nyt ne on kirjoitettava tiedostoon ja lopetettava työskentely libelfin kanssa. Tallennuksen suorittaa elf_update-toiminto:
off_t elf_update(Tonttu *elf, Elf_Cmd cmd);
  • tonttu - kahva
  • cmd - komento, on oltava yhtä suuri kuin ELF_C_WRITE kirjoittaaksesi.
Funktio palauttaa virheen yhteydessä -1. Virheteksti saadaan kutsumalla elf_errmsg(-1)-funktio, joka palauttaa osoittimen virheen sisältävälle riville.
Lopetamme työskentelyn kirjaston kanssa elf_end-funktiolla, jolle välitämme kuvaajamme. Jäljelle jää vain sulkea aiemmin avattu tiedosto.
Luotu tiedostomme ei kuitenkaan sisällä virheenkorjaustietoja, jotka lisäämme seuraavassa osiossa.

DWARFin luominen

Luomme virheenkorjaustiedot käyttämällä kirjastoa, jonka mukana tulee pdf-tiedosto ja dokumentaatio (libdwarf2p.1.pdf - A Producer Library Interface to DWARF).
Vianetsintätietojen luominen koostuu seuraavista vaiheista:
  1. Solmujen luominen (DIE - Debugging Information Entry)
  2. Solmumääritteiden luominen
  3. Tietotyyppien luominen
  4. Menettelyjen (funktioiden) luominen
Katsotaanpa vaiheita tarkemmin
Alustetaan libdwarf-tuottajaa
Luomme virheenkorjaustiedot käännösvaiheessa samaan aikaan kun luomme .symtab-osion symboleja, joten kirjasto on alustettava, kun libelf on alustettu, ELF-otsikko ja ohjelman otsikko on luotu ja ennen osien luomista.
Alustamiseen käytämme funktiota dwarf_producer_init_c. Kirjastossa on useita muita alustustoimintoja (dwarf_producer_init, dwarf_producer_init_b), jotka eroavat joissakin dokumentaatiossa kuvatuissa vivahteissa. Periaatteessa voit käyttää mitä tahansa niistä.

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

  • liput - yhdistelmä "tai" useista vakioista, jotka määrittävät joitain parametreja, esimerkiksi tiedon bittisyvyyden, tavusekvenssin (little-endian, big-endian), uudelleensijoitusmuodon, joista tarvitsemme ehdottomasti DW_DLC_WRITE ja DW_DLC_SYMBOLIC_RELOCATIONS
  • func on takaisinsoittotoiminto, jota kutsutaan luotaessa ELF-osioita, joissa on virheenkorjaustietoja. Katso lisätietoja alla olevasta osiosta "Virheenkorjaustietoja sisältävien osioiden luominen"
  • errhand on osoitin funktioon, jota kutsutaan virheiden sattuessa. Voit lähettää 0
  • errarg - tiedot, jotka välitetään errhand-funktiolle, voidaan asettaa arvoon 0
  • user_data - tiedot, jotka välitetään func-funktiolle, voidaan asettaa arvoon 0
  • virhe - palautettu virhekoodi
Funktio palauttaa Dwarf_P_Debug - kuvaajan, jota käytetään kaikissa myöhemmissä funktioissa, tai -1 virheen sattuessa ja virhe sisältää virhekoodin (saat virheilmoituksen tekstin koodin mukaan käyttämällä funktiota dwarf_errmsg, välittämällä tämän koodin siihen)
Solmujen luominen (DIE - virheenkorjaustietojen syöttö)
Kuten edellä on kuvattu, virheenkorjaustiedot muodostavat puurakenteen. Tämän puun solmun luomiseksi tarvitset:
  • luo se käyttämällä funktiota dwarf_new_die
  • lisää siihen attribuutteja (jokainen attribuuttityyppi lisätään omalla funktiollaan, joka kuvataan alla)
Solmu luodaan dwarf_new_die-funktiolla:
Dwarf_P_Die dwarf_new_die(Dwarf_P_Debug dbg, Dwarf_Tag new_tag, Dwarf_P_Die vanhempi, Dwarf_P_Die child, Dwarf_P_Die left_sibling, Dwarf_P_Die right_sibling, Dwarf_Error *error)
  • new_tag - solmutunniste (tyyppi) - vakio DW_TAG_xxxx, joka löytyy tiedostosta libdwarf.h
  • vanhempi, lapsi, vasen_sisarus, oikea_sisarus - vastaavasti solmun vanhempi, lapsi, vasen ja oikea naapuri. Kaikkia näitä parametreja ei tarvitse määrittää, riittää, että määrität yhden ja asetat 0:n muiden sijasta
  • virhe - sisältää virhekoodin, kun se tapahtuu
Funktio palauttaa DW_DLV_BADADDR virheen tai Dwarf_P_Die-solmukahvan onnistumisen yhteydessä
Solmumääritteiden luominen
Solmuattribuuttien luomiseksi on olemassa koko perhe funktioita dwarf_add_AT_xxxx. Joskus on vaikea määrittää, minkä funktion on luotava vaadittu attribuutti, joten kaivelin jopa kirjaston lähdekoodia useita kertoja. Joitakin toimintoja kuvataan tässä, jotkin alla asianmukaisissa osioissa. Ne kaikki hyväksyvät omistajaparametrin - kahvan solmuun, johon attribuutti lisätään, ja palauttavat virhekoodin virheparametriin.
Dwarf_add_AT_name-funktio lisää "name"-attribuutin (DW_AT_name) solmuun. Useimmilla solmuilla on oltava nimi (esimerkiksi menettelyt, muuttujat, vakiot), joillakin ei välttämättä ole nimeä (esimerkiksi käännösyksikkö)
Dwarf_P_Attribute dwarf_add_AT_name(Dwarf_P_Die ownerdie, char *name, Dwarf_Error *error)
  • nimi - todellinen attribuutin arvo (solmun nimi)

Funktiot dwarf_add_AT_signed_const, dwarf_add_AT_unsigned_const lisäävät määritetyn attribuutin ja sen allekirjoitetun (allekirjoittamattoman) arvon solmuun. Signed ja unsigned attribuutteja käytetään määrittämään vakioarvoja, kokoja, rivinumeroita jne. Toiminnon muoto:
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 *virhe)
  • dbg - Dwarf_P_Debug-kuvaaja, joka on saatu kirjaston alustuksen aikana
  • attr - attribuutti, jonka arvo on asetettu - DW_AT_xxxx vakio, joka löytyy tiedostosta libdwarf.h
  • arvo - attribuutin arvo
Palauta DW_DLV_BADADDR virheen yhteydessä tai attribuutin kahva onnistumisen yhteydessä.
Kokoonpanoyksikön luominen
Jokaisella puulla on oltava juuri - tässä tapauksessa tämä on käännösyksikkö, joka sisältää tietoja ohjelmasta (esimerkiksi päätiedoston nimi, käytetty ohjelmointikieli, kääntäjän nimi, symbolien kirjainherkkyys ( muuttujat, funktiot), ohjelman päätoiminto, aloitusosoite jne.). Periaatteessa attribuutteja ei vaadita. Luodaan esimerkiksi tiedot päätiedostosta ja kääntäjästä.
Päätiedoston tiedot
Tallentaaksesi tietoja päätiedostosta, käytä attribuuttia nimi (DW_AT_name), käytä funktiota dwarf_add_AT_name osiossa "Solmumääritteiden luominen" kuvatulla tavalla.
Kääntäjän tiedot
Käytämme funktiota dwarf_add_AT_producer:
Dwarf_P_Attribute dwarf_add_AT_name(Dwarf_P_Die ownerdie, char *producer_string, Dwarf_Error *error)
  • tuottaja_merkkijono - merkkijono tietotekstillä
Palauttaa DW_DLV_BADADDR virheen tai attribuutin kahvan onnistumisesta.
Yhteisen tietomerkinnän luominen
Tyypillisesti kun funktiota (alirutiinia) kutsutaan, sen parametrit ja paluuosoite työnnetään pinoon (vaikka jokainen kääntäjä voi tehdä tämän eri tavalla), kaikkea tätä kutsutaan kutsukehykseksi. Debuggeri tarvitsee tietoja kehysmuodosta määrittääkseen oikein funktion paluuosoitteen ja rakentaakseen paluujäljityksen - funktiokutsujen ketjun, joka johti meidät nykyiseen funktioon ja näiden funktioiden parametreihin. On myös yleistä määrittää pinoon tallennetut prosessorirekisterit. Koodia, joka varaa pinoon tilaa ja tallentaa prosessorin rekistereitä, kutsutaan funktion prologiksi, koodia, joka palauttaa rekistereitä ja pinon, kutsutaan epilogiksi.
Nämä tiedot ovat erittäin riippuvaisia ​​kääntäjästä. Esimerkiksi prologin ja epilogin ei tarvitse olla aivan funktion alussa ja lopussa; joskus kehystä käytetään, joskus ei; prosessorirekisterit voidaan tallentaa muihin rekistereihin jne.
Joten virheenkorjaajan on tiedettävä, kuinka prosessorirekisterit muuttavat arvoaan ja mihin ne tallennetaan, kun se astuu prosessiin. Näitä tietoja kutsutaan nimellä Call Frame Information - tiedot kehysmuodosta. Jokaiselle ohjelman osoitteelle (sisältää koodin) ilmoitetaan muistissa oleva kehysosoite (Canonical Frame Address - CFA) ja tiedot prosessorirekistereistä. Voit esimerkiksi ilmoittaa, että:
  • tapausta ei säilytetä menettelyssä
  • rekisteri ei muuta arvoaan menettelyssä
  • rekisteri tallennetaan pinoon osoitteessa CFA+n
  • rekisteri tallennetaan toiseen rekisteriin
  • rekisteri on tallennettu muistiin johonkin osoitteeseen, joka voidaan laskea melko epäselvällä tavalla
  • jne.
Koska tiedot on määritettävä jokaiselle koodin osoitteelle, se on erittäin laaja ja tallennetaan pakatussa muodossa .debug_frame-osioon. Koska se muuttuu vähän osoitteesta toiseen, vain sen muutokset koodataan DW_CFA_xxxx-käskyjen muodossa. Jokainen ohje osoittaa yhden muutoksen, esimerkiksi:
  • DW_CFA_set_loc - osoittaa ohjelman nykyiseen osoitteeseen
  • DW_CFA_advance_loc - siirtää osoitinta tietyllä tavumäärällä eteenpäin
  • DW_CFA_def_cfa - osoittaa pinokehyksen osoitteen (numeerinen vakio)
  • DW_CFA_def_cfa_register - osoittaa pinokehyksen osoitteen (otettu prosessorirekisteristä)
  • DW_CFA_def_cfa_expression - määrittää kuinka pinon kehysosoite lasketaan
  • DW_CFA_same_value - osoittaa, että rekisteriä ei ole muutettu
  • DW_CFA_register - osoittavat, että rekisteri on tallennettu toiseen rekisteriin
  • jne.
.debug_frame-osion elementit ovat merkintöjä, joita voi olla kahta tyyppiä: Common Information Entry (CIE) ja Frame Description Entry (FDE). CIE sisältää tietoa, joka on yhteistä monille FDE-tietueille, se kuvaa tietyntyyppistä menettelyä. FDE:t kuvaavat jokaisen erityisen menettelyn. Menetelmää syöttäessään debuggeri suorittaa ensin käskyt CIE:stä ja sitten FDE:stä.
Kääntäjäni luo menettelyjä, joissa CFA on sp-rekisterissä (r13). Luodaan CIE kaikille toimenpiteille. Tätä varten on toiminto, 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, *_bytes_Er init);
  • augmenter on UTF-8-koodattu merkkijono, jonka olemassaolo osoittaa, että CIE:lle tai FDE:lle on lisätietoa alustasta riippuen. Laita tyhjä rivi
  • code_align - koodin tasaus tavuissa (meillä on 2)
  • data_align - tietojen kohdistaminen kehyksessä (joukko -4, mikä tarkoittaa, että kaikki parametrit vievät pinosta 4 tavua ja se kasvaa muistissa)
  • ret_addr_reg - rekisteri, joka sisältää palautusosoitteen menettelystä (meillä on 14)
  • init_bytes - taulukko, joka sisältää DW_CFA_xxxx-ohjeet. Valitettavasti ei ole kätevää tapaa luoda tätä taulukkoa. Voit luoda sen manuaalisesti tai katsoa sitä elf-tiedostosta, jonka C-kääntäjä on luonut, minkä tein. Minun tapauksessani se sisältää 3 tavua: 0x0C, 0x0D, 0, mikä tarkoittaa DW_CFA_def_cfa: r13 ofs 0 (CFA on rekisterissä r13, offset on 0)
  • init_bytes_len - init_bytes-taulukon pituus
Funktio palauttaa virheen DW_DLV_NOCOUNT tai CIE-kahvan, jota tulee käyttää luotaessa FDE jokaiselle toimintosarjalle, jota tarkastellaan myöhemmin osiossa "FDE-proseduurin luominen"
Tietotyyppien luominen
Ennen kuin voit luoda proseduureja ja muuttujia, sinun on ensin luotava tietotyyppejä vastaavat solmut. Tietotyyppejä on monia, mutta ne kaikki perustuvat perustyyppeihin (alkeistyypit kuten int, double jne.), muut tyypit rakennetaan perustyypeistä.
Perustyyppi on solmu, jonka tunniste on DW_TAG_base_type. Sillä on oltava seuraavat attribuutit:
  • "nimi" (DW_AT_name)
  • "koodaus" (DW_AT_encoding) - tarkoittaa, millainen tieto kuvaa tätä perustyyppiä (esim. DW_ATE_boolean - looginen, DW_ATE_float - liukuluku, DW_ATE_signed - etumerkillinen kokonaisluku, DW_ATE_unsigned - etumerkitön kokonaisluku jne.)
  • "size" (DW_AT_byte_size - koko tavuina tai DW_AT_bit_size - koko bitteinä)
Solmu voi sisältää myös muita valinnaisia ​​attribuutteja.
Jos esimerkiksi haluat luoda 32-bittisen etumerkillisen kokonaisluvun perustyypin "int", meidän on luotava solmu tunnisteella DW_TAG_base_type ja asetettava sen attribuutit DW_AT_name - "int", DW_AT_encoding - DW_ATE_signed, DW_AT_byte_size - 4.
Kun perustyypit on luotu, voit johtaa niistä johdannaisia. Tällaisten solmujen on sisällettävä attribuutti DW_AT_type - viittaus niiden perustyyppiin. Esimerkiksi osoittimen int - solmun, jossa on DW_TAG_pointer_type-tunniste, täytyy sisältää DW_AT_type-attribuutissa linkki aiemmin luotuun "int"-tyyppiin.
Attribuutti, joka viittaa toiseen solmuun, luodaan funktiolla dwarf_add_AT_reference:
Dwarf_P_Attribute dwarf_add_AT_reference(Dwarf_P_Debug dbg, Dwarf_P_Die omistaja, Dwarf_Half attr, Dwarf_P_Die otherdie, Dwarf_Error *virhe)
  • attr - attribuutti, tässä tapauksessa DW_AT_type
  • otherdie - viitatun tyypin solmun kahva
Menettelyjen luominen
Proseduurien luomiseksi minun on selitettävä vielä yksi virheenkorjaustietojen tyyppi - rivinumerotiedot. Sen tehtävänä on kartoittaa jokainen konekäsky tiettyyn lähdekoodiriviin ja mahdollistaa myös ohjelman rivi riviltä tapahtuvan virheenkorjauksen. Nämä tiedot tallennetaan .debug_line-osioon. Jos meillä olisi tarpeeksi tilaa, se tallennettaisiin matriisina, yksi rivi jokaiselle käskylle ja sarakkeilla seuraavasti:
  • lähdetiedoston nimi
  • rivinumero tässä tiedostossa
  • sarakkeen numero tiedostossa
  • onko käsky lauseen alku vai lausekelohko
  • jne.
Tällainen matriisi olisi hyvin suuri, joten se on pakattava. Ensinnäkin päällekkäiset rivit poistetaan, ja toiseksi itse rivejä ei tallenneta, vaan vain niihin tehdyt muutokset. Nämä muutokset näyttävät komennoista äärellisen tilan koneelle, ja itse tieto katsotaan jo ohjelmaksi, jonka tämä kone "suorittaa". Tämän ohjelman komennot näyttävät esimerkiksi tältä: DW_LNS_advance_pc - siirtää ohjelmalaskuria tiettyyn osoitteeseen, DW_LNS_set_file - aseta tiedosto, jossa proseduuri määritellään, DW_LNS_const_add_pc - siirtää ohjelmalaskuria useilla tavuilla jne.
Näiden tietojen luominen niin alhaisella tasolla on vaikeaa, joten libdwarf tarjoaa useita toimintoja helpottamaan tätä tehtävää.
Jokaisen käskyn tiedostonimen tallentaminen on kallista, joten nimen sijasta sen hakemisto tallennetaan erityiseen taulukkoon. Tiedostohakemiston luomiseksi sinun on käytettävä funktiota 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 pituus, Dwarf_Error *virhe)
  • nimi - tiedoston nimi
  • dir_idx - sen kansion hakemisto, jossa tiedosto sijaitsee. Hakemisto saadaan dwarf_add_directory_decl-funktiolla. Jos täydet polut ovat käytössä, voit asettaa 0:n kansiohakemistoksi etkä käytä dwarf_add_directory_decl.
  • time_mod - tiedoston muokkausaika, ei voida määrittää (0)
  • pituus - tiedostokoko, myös valinnainen (0)
Toiminto palauttaa tiedostoindeksin tai DW_DLV_NOCOUNT virheen sattuessa.
Tietojen luomiseksi rivinumeroista on kolme funktiota dwarf_add_line_entry_b, dwarf_lne_set_address, dwarf_lne_end_sequence, joita tarkastelemme alla.
Vianetsintätietojen luominen menettelyä varten tapahtuu useissa vaiheissa:
  • proseduurisymbolin luominen .symtab-osioon
  • proseduurisolmun luominen määritteillä
  • FDE-menettelyn luominen
  • proseduuriparametrien luominen
  • rivinumerotietojen luominen
Menettelysymbolin luominen
Proseduurisymboli luodaan edellä Section.symtab-osiossa kuvatulla tavalla. Siinä proseduurien symbolit ovat välissä niiden tiedostojen symbolien kanssa, joissa näiden toimintojen lähdekoodi sijaitsee. Ensin luodaan tiedostosymboli, sitten menettely. Tämä tekee tiedostosta ajan tasalla, ja jos seuraava toimenpide on nykyisessä tiedostossa, tiedostosymbolia ei tarvitse luoda uudelleen.
Proseduurisolmun luominen määritteillä
Ensin luodaan solmu käyttämällä funktiota dwarf_new_die (katso osio "Solmujen luominen"), määrittämällä tunnisteeksi DW_TAG_subprogram ja ylätason käännösyksikön (jos tämä on globaali toimintosarja) tai vastaavan DIE:n (jos paikallinen). Seuraavaksi luomme attribuutit:
  • toimintosarjan nimi (funktio dwarf_add_AT_name, katso "Solmumääritteiden luominen")
  • tiedoston rivinumero, josta toimintokoodi alkaa (attribuutti DW_AT_decl_line), funktio dwarf_add_AT_unsigned_const (katso "Solmumääritteiden luominen")
  • proseduurin aloitusosoite (attribuutti DW_AT_low_pc), funktio dwarf_add_AT_targ_address, katso alla
  • proseduurin lopullinen osoite (attribuutti DW_AT_high_pc), funktio dwarf_add_AT_targ_address, katso alla
  • proseduurin palauttaman tuloksen tyyppi (attribuutti DW_AT_type on linkki aiemmin luotuun tyyppiin, katso "Tietotyyppien luominen"). Jos menettely ei palauta mitään, tätä määritettä ei tarvitse luoda
Attribuutit DW_AT_low_pc ja DW_AT_high_pc on luotava käyttämällä erityisesti tähän tarkoitukseen suunniteltua funktiota dwarf_add_AT_targ_address_b:
Dwarf_P_Attribute dwarf_add_AT_targ_address_b(Dwarf_P_Debug dbg, Dwarf_P_Die ownerdie, Dwarf_Half attr, Dwarf_Unsigned pc_value, Dwarf_Unsigned sym_index, Dwarf_Error *error)
  • attr - attribuutti (DW_AT_low_pc tai DW_AT_high_pc)
  • pc_value - osoitteen arvo
  • sym_index - proseduurisymbolin indeksi taulukossa.symtab. Valinnainen, 0 voidaan hyväksyä
Funktio palauttaa DW_DLV_BADADDR virheen sattuessa.
FDE-menettelyn luominen
Kuten edellä kohdassa "Yhteisen tietomerkinnän luominen" on käsitelty, jokaiselle toimenpiteelle on luotava kehyskuvaus, joka tapahtuu useissa vaiheissa:
  • uuden FDE:n luominen (katso Yhteisen tietomerkinnän luominen)
  • luodun FDE:n liittäminen yleiseen luetteloon
  • ohjeiden lisääminen luotuun FDE:hen
Voit luoda uuden FDE:n käyttämällä funktiota dwarf_new_fde:
Dwarf_P_Fde dwarf_new_fde(Dwarf_P_Debug dbg, Dwarf_Error *virhe)
Toiminto palauttaa kahvan uudelle FDE:lle tai DW_DLV_BADADDR:lle virheen sattuessa.
Voit liittää luetteloon uuden FDE:n komennolla 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_UnsignedEr, Dwarf_idxr*Dwarf)
  • fde - juuri vastaanotettu kahva
  • die - DIE-menettelyt (katso Proseduurisolmun luominen määritteillä)
  • cie - CIE-kuvaaja (katso Yhteisen tietomerkinnän luominen)
  • virt_addr - menettelymme aloitusosoite
  • code_len - prosessin pituus tavuina
Funktio palauttaa DW_DLV_NOCOUNT virheen sattuessa.
Tämän jälkeen voit lisätä DW_CFA_xxxx-ohjeet FDE:hen. Tämä tehdään funktioilla dwarf_add_fde_inst ja dwarf_fde_cfa_offset. Ensimmäinen lisää luetteloon annetun ohjeen:
Dwarf_P_Fde dwarf_add_fde_inst(Dwarf_P_Fde fde, Dwarf_Small op, Dwarf_Unsigned val1, Dwarf_Unsigned val2, Dwarf_Error *virhe)
  • op - ohjekoodi (DW_CFA_хххх)
  • arvo1, arvo2 - käskyparametrit (eri kullekin käskylle, katso standardi, osa 6.4.2 Kutsukehyksen ohjeet)
Funktio dwarf_fde_cfa_offset lisää DW_CFA_offset-käskyn:
Dwarf_P_Fde dwarf_fde_cfa_offset(Dwarf_P_Fde fde, Dwarf_Unsigned reg, Dwarf_Signed offset, Dwarf_Error *virhe)
  • fde - kahva luotuun FDE:hen
  • reg - kehykseen kirjoitettu rekisteri
  • offset - sen siirtymä kehyksessä (ei tavuissa, vaan kehyselementeissä, katso Yhteisen tietomerkinnän luominen, data_align)
Esimerkiksi kääntäjä luo proseduurin, jonka prologi tallentaa rekisterin lr (r14) pinokehykseen. Ensimmäinen vaihe on lisätä käsky DW_CFA_advance_loc, jonka ensimmäinen parametri on yhtä suuri kuin 1, mikä tarkoittaa PC-rekisterin siirtämistä 2 tavulla (katso Yhteisen tietosyötön luominen, code_align), lisää sitten DW_CFA_def_cfa_offset parametrilla 4 (asettamalla dataoffsetin kehys 4 tavulla) ja kutsu dwarf_fde_cfa_offset-funktiota parametrilla reg=14 offset=1, mikä tarkoittaa r14-rekisterin kirjoittamista kehykseen -4 tavun siirtymällä CFA:sta.
Menettelyparametrien luominen
Proseduuriparametrien luominen on samanlaista kuin tavallisten muuttujien luominen, katso "Muutujien ja vakioiden luominen"
Tietojen luominen rivinumeroista
Nämä tiedot luodaan seuraavasti:
  • toimenpiteen alussa aloitamme komentolohkon funktiolla dwarf_lne_set_address
  • jokaiselle koodiriville (tai konekäskylle) luomme tiedot lähdekoodista (dwarf_add_line_entry)
  • toimenpiteen lopussa täydennämme komentolohkon funktiolla dwarf_lne_end_sequence
Funktio dwarf_lne_set_address asettaa osoitteen, josta komentolohko alkaa:
Dwarf_Unsigned dwarf_lne_set_address(Dwarf_P_Debug dbg, Dwarf_Addr offs, Dwarf_Unsigned symidx, Dwarf_Error *virhe)
  • offs - prosessiosoite (ensimmäisen konekäskyn osoite)
  • sym_idx - symboliindeksi (valinnainen, voit määrittää 0)

Funktio dwarf_add_line_entry_b lisää tietoja lähdekoodiriveistä .debug_line-osioon. Kutsun tätä toimintoa jokaiselle konekäskylle:
Dwarf_Unsigned dwarf_add_line_entry_b(Dwarf_P_Debug dbg, Dwarf_Unsigned file_index, Dwarf_Addr code_offset, Dwarf_Unsigned lineno, Dwarf_Signed sarakkeen_numero, Dwarf_Bool is_source_stmt_blog is_begin_block ue _begin, Dwarf_Bool is_prologue_end, Dwarf_Unsigned isa, Dwarf_Unsigned discriminator, Dwarf_Error *error)
  • file_index - dwarf_add_file_decl-funktiolla aiemmin hankitun lähdekooditiedoston indeksi (katso "Proseduurien luonti")
  • code_offset - nykyisen konekäskyn osoite
  • lineno - rivinumero lähdekooditiedostossa
  • sarakkeen_numero - sarakkeen numero lähdekooditiedostossa
  • is_source_stmt_begin - 1, jos nykyinen käsky on ensimmäinen koodissa lineno-rivillä (käytän aina 1:tä)
  • is_basic_block_begin - 1, jos nykyinen käsky on ensimmäinen käskylohkossa (käytän aina 0)
  • is_epilogue_begin - 1, jos nykyinen käsky on ensimmäinen toimenpiteen epilogissa (ei välttämätön, minulla on aina 0)
  • is_prologue_end - 1, jos nykyinen käsky on viimeinen proseduurin prologissa (pakollinen!)
  • isa - ohjesarjan arkkitehtuuri. Muista määrittää DW_ISA_ARM_thumb ARM Cortex M3:lle!
  • syrjijä. Yksi lähdekoodin paikka (tiedosto, rivi, sarake) voi vastata eri konekäskyjä. Tässä tapauksessa tällaisten ohjeiden sarjoille on asennettava erilaiset erottimet. Jos tällaisia ​​tapauksia ei ole, sen pitäisi olla 0
Funktio palauttaa 0 (onnistuminen) tai DW_DLV_NOCOUNT (virhe).
Lopuksi dwarf_lne_end_sequence-funktio suorittaa toimenpiteen loppuun:
Dwarf_Unsigned dwarf_lne_end_sequence(Dwarf_P_Debug dbg, Dwarf_Addr-osoite; Dwarf_Error *virhe)
  • osoite - nykyisen konekäskyn osoite
Palauttaa 0 (onnistuminen) tai DW_DLV_NOCOUNT (virhe).
Tämä päättää menettelyn luomisen.
Muuttujien ja vakioiden luominen
Yleensä muuttujat ovat melko yksinkertaisia. Heillä on nimi, muistipaikka (tai prosessorirekisteri), jossa heidän tietonsa sijaitsevat, ja näiden tietojen tyyppi. Jos muuttuja on globaali, sen ylätason tulee olla Compilation Unit, jos paikallinen, vastaava solmu (tämä pätee erityisesti proseduuriparametreihin, niiden ylätason tulee olla itse proseduuri). Voit myös määrittää, missä tiedostossa, rivissä ja sarakkeessa muuttujan määritys on.
Yksinkertaisimmassa tapauksessa muuttujan arvo sijaitsee jossain kiinteässä osoitteessa, mutta monet muuttujat syntyvät dynaamisesti pinoon tai rekisteriin syötettäessä proseduuria, joskus arvon osoitteen laskeminen voi olla melko epätriviaalia. Standardi tarjoaa mekanismin, jolla voidaan kuvata, missä muuttujan arvo sijaitsee - sijaintilausekkeet. Osoitelauseke on joukko käskyjä (DW_OP_xxxx vakioita) Fort-tyyppiselle pinokoneelle, itse asiassa se on erillinen kieli haaroilla, menettelyillä ja aritmeettisilla operaatioilla. Emme tarkastele tätä kieltä kokonaisuudessaan, olemme itse asiassa kiinnostuneita vain muutamasta ohjeesta:
  • DW_OP_addr - ilmaisee muuttujan osoitteen
  • DW_OP_fbreg - ilmaisee muuttujan poikkeaman perusrekisteristä (yleensä pinoosoittimesta)
  • DW_OP_reg0… DW_OP_reg31 - ilmaisee, että muuttuja on tallennettu vastaavaan rekisteriin
Osoitelausekkeen luomiseksi sinun on ensin luotava tyhjä lauseke (kääpiö_uusi_lauseke), lisättävä siihen ohjeet (dwarf_add_expr_addr, dwarf_add_expr_gen jne.) ja lisättävä se solmuun DW_AT_location-attribuutin (dwarf_add_AT_location_expression) arvoksi.
Tyhjän osoitelausekkeen luontifunktio palauttaa kahvansa tai 0:n virheen sattuessa:
Dwarf_Expr dwarf_new_expr(Dwarf_P_Debug dbg, Dwarf_Error *virhe)
Jos haluat lisätä lausekkeeseen ohjeita, sinun on käytettävä funktiota 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 *virhe)
  • opcode - toimintakoodi, vakio DW_OP_хххх
  • arvo1, arvo2 - ohjeparametrit (katso standardi)

Muuttujan osoitteen määrittämiseksi eksplisiittisesti on käytettävä funktiota dwarf_add_expr_addr edellisen sijasta:
Dwarf_Unsigned dwarf_add_expr_addr(Dwarf_P_Expr expr, Dwarf_Unsigned address, Dwarf_Signed sym_index, Dwarf_Error *virhe)
  • expr on kahva osoitelausekkeelle, johon käsky lisätään
  • osoite - muuttuva osoite
  • sym_index - symbolin indeksi taulukossa.symtab. Valinnainen, 0 voidaan hyväksyä
Funktio palauttaa myös DW_DLV_NOCOUNT virheen sattuessa.
Lopuksi voit lisätä luodun osoitelausekkeen solmuun käyttämällä funktiota dwarf_add_AT_location_expr:
Dwarf_P_Attribute dwarf_add_AT_location_expr(Dwarf_P_Debug dbg, Dwarf_P_Die ownerdie, Dwarf_Half attr, Dwarf_P_Expr loc_expr, Dwarf_Error *virhe)
  • ownerdie - solmu, johon lauseke lisätään
  • attr - attribuutti (tässä tapauksessa DW_AT_location)
  • loc_expr - käsittelee aiemmin luotua osoitelauseketta
Funktio palauttaa attribuutin kahvan tai DW_DLV_NOCOUNT virheen sattuessa.
Muuttujat (sekä menettelyparametrit) ja vakiot ovat tavallisia solmuja, joiden tunniste on DW_TAG_variable, DW_TAG_formal_parameter ja DW_TAG_const_type. Ne vaativat seuraavat attribuutit:
  • muuttujan/vakionimi (funktio dwarf_add_AT_name, katso "Solmumääritteiden luominen")
  • rivinumero tiedostossa, jossa muuttuja on ilmoitettu (attribuutti DW_AT_decl_line), funktio dwarf_add_AT_unsigned_const (katso "Solmuattribuuttien luominen")
  • tiedostonimiindeksi (DW_AT_decl_file-attribuutti), dwarf_add_AT_unsigned_const-funktio (katso "Solmumääritteiden luominen")
  • muuttuja/vakiotietotyyppi (attribuutti DW_AT_type on linkki aiemmin luotuun tyyppiin, katso "Tietotyyppien luominen")
  • osoitelauseke (katso yllä) - tarvitaan muuttuja- tai toimintoparametrille
  • tai arvo - vakio (attribuutti DW_AT_const_value, katso "Solmumääritteiden luominen")
Luodaan virheenkorjaustietoja sisältäviä osioita
Kun olet luonut kaikki virheenkorjaustietopuun solmut, voit alkaa luoda tonttu-osioita sen avulla. Tämä tapahtuu kahdessa vaiheessa:
  • Ensin meidän on kutsuttava funktio dwarf_transform_to_disk_form, joka kutsuu funktiota, jonka kirjoitimme luodakseen tarvittavat tonttu-osat kerran jokaiselle osalle
  • jokaisessa osassa dwarf_get_section_bytes-funktio palauttaa meille tiedot, jotka on kirjoitettava vastaavaan osioon
Toiminto
dwarf_transform_to_disk_form (Dwarf_P_Debug dbg, Dwarf_Error* -virhe)
muuntaa luomamme virheenkorjaustiedot binäärimuotoon, mutta ei kirjoita mitään levylle. Se palauttaa luotujen elf-osien määrän tai DW_DLV_NOCOUNT virheen sattuessa. Tässä tapauksessa jokaisessa osassa kutsutaan takaisinsoittotoimintoa, jonka välitimme dwarf_producer_init_c-funktiolle kirjaston alustuksen yhteydessä. Meidän on kirjoitettava tämä funktio itse. Sen erittely on seuraava:
typedef int (*Dwarf_Callback_Func_c)(char* nimi, int-koko, Dwarf_Unsigned-tyyppi, Dwarf_Unsigned-liput, Dwarf_Unsigned-linkki, Dwarf_Unsigned-tiedot, Dwarf_Unsigned* sekt_nimi_indeksi, void * user_data, int* error)
  • nimi - luotavan tonttu-osion nimi
  • koko - osan koko
  • tyyppi - osion tyyppi
  • liput - osion liput
  • linkki - osion linkkikenttä
  • info - osion tietokenttä
  • sect_name_index - sinun on palautettava osion hakemisto, jossa on siirrot (valinnainen)
  • user_data - välitetään meille samalla tavalla kuin asetamme sen kirjaston alustustoiminnossa
  • virhe - tästä voit lähettää virhekoodin
Tässä toiminnossa meidän on:
  • luo uusi osio (elf_newscn-toiminto, katso Osioiden luominen)
  • luo osion otsikko (funktio elf32_getshdr, ibid.)
  • täytä se oikein (katso ibid.). Tämä on helppoa, koska osion otsikkokentät vastaavat funktiomme parametreja. Aseta puuttuvat kentät sh_addr, sh_offset, sh_entsize arvoksi 0 ja sh_addralign arvoksi 1
  • palauttaa luodun osion indeksin (funktio elf_ndxscn, katso "Section.symtab") tai -1 virheen sattuessa (asettamalla virhekoodin virheeksi)
  • meidän tulisi myös ohittaa ".rel"-osio (meidän tapauksessamme), palauttaen 0 palattaessa funktiosta
Kun funktio dwarf_transform_to_disk_form on valmis, se palauttaa luotujen osien määrän. Meidän on käytävä kukin osio läpi silmukassa 0:sta alkaen seuraavasti:
  • luo osioon kirjoitettavat tiedot dwarf_get_section_bytes-funktiolla:
    Dwarf_Ptr dwarf_get_section_bytes(Dwarf_P_Debug dbg, Dwarf_Signed dwarf_section, Dwarf_Signed *elf_section_index, Dwarf_Unsigned *pituus, Dwarf_Error* virhe)
    • dwarf_section - osion numero. Sen on oltava välillä 0..n, missä n on kääpiö_transform_to_disk_form-funktion meille palauttama luku
    • elf_section_index - palauttaa sen osan indeksin, johon tiedot tulee kirjoittaa
    • pituus - tämän tiedon pituus
    • virhe - ei käytetty
    Funktio palauttaa osoittimen vastaanotettuun dataan tai 0:n (tapauksessa
    kun luotavia osioita ei ole enää jäljellä)
  • luo datakuvaaja nykyiselle osalle (funktio elf_newdata, katso Osioiden luominen) ja täytä se (katso sieltä) asettamalla:
    • d_buf - osoitin tietoihin, jotka saimme edellisestä funktiosta
    • d_size - näiden tietojen koko (ibid.)
Kirjastotyön viimeistely
Kun osiot on muodostettu, voit lopettaa työskentelyn libdwarfilla käyttämällä funktiota dwarf_producer_finish:
Dwarf_Unsigned dwarf_producer_finish(Dwarf_P_Debug dbg, Dwarf_Error* virhe)
Funktio palauttaa DW_DLV_NOCOUNT virheen sattuessa.
Huomaa, että levylle tallennusta ei suoriteta tässä vaiheessa. Tallennus on tehtävä osiossa "ELF:n luominen - Tiedoston kirjoittaminen" olevien toimintojen avulla.

Johtopäätös

Siinä kaikki.
Toistan, virheenkorjaustietojen luominen on erittäin laaja aihe, enkä koskettanut monia aiheita, vain nostaen verhon. Halukkaat voivat mennä syvemmälle loputtomasti.
Jos sinulla on kysyttävää, yritän vastata niihin.

ELF-muoto

ELF-muodossa on useita tiedostotyyppejä, joita olemme tähän mennessä kutsuneet eri tavalla, kuten suoritettava tiedosto tai objektitiedosto. ELF-standardi erottaa kuitenkin seuraavat tyypit:

1. Tiedosto siirrettävä(uudelleensijoitettava tiedosto), joka tallentaa ohjeet ja tiedot, jotka voidaan linkittää muihin objektitiedostoihin. Tällaisen linkityksen tulos voi olla suoritettava tiedosto tai jaettu objektitiedosto.

2. Jaettu objektitiedosto(jaettu objektitiedosto) sisältää myös ohjeita ja tietoja, mutta sitä voidaan käyttää kahdella tavalla. Ensimmäisessä tapauksessa se voidaan linkittää muihin uudelleensijoitettaviin tiedostoihin ja jaettuihin objektitiedostoihin, jolloin luodaan uusi objektitiedosto. Toisessa tapauksessa, kun ohjelma käynnistetään suoritettavaksi, käyttöjärjestelmä voi dynaamisesti linkittää sen ohjelman suoritettavaan tiedostoon, minkä seurauksena ohjelmasta luodaan suoritettava kuva. Jälkimmäisessä tapauksessa puhumme jaetuista kirjastoista.

3. Suoritettava tiedosto tallentaa täydellisen kuvauksen, jonka avulla järjestelmä voi luoda kuvan prosessista. Se sisältää ohjeita, tietoja, kuvauksia vaadituista jaetuista objektitiedostoista sekä tarvittavat symboliset ja virheenkorjaustiedot.

Kuvassa 2.4 näyttää suoritettavan tiedoston rakenteen, jonka avulla käyttöjärjestelmä voi luoda ohjelmakuvan ja käynnistää ohjelman suoritettavaksi.

Riisi. 2.4. Suoritettavan tiedoston rakenne ELF-muodossa

Otsikolla on kiinteä sijainti tiedostossa. Loput komponentit sijoitetaan otsikkoon tallennettujen tietojen mukaan. Siten otsikko sisältää yleisen kuvauksen tiedostorakenteesta, yksittäisten komponenttien sijainnista ja niiden koosta.

Koska ELF-tiedoston otsikko määrittää sen rakenteen, katsotaanpa sitä tarkemmin (taulukko 2.4).

Taulukko 2.3. ELF-otsikkokentät

Ala Kuvaus
e_ident Tavujen joukko, joista jokainen määrittelee tiedoston yleisiä ominaisuuksia: tiedostomuoto (ELF), versionumero, järjestelmäarkkitehtuuri (32-bittinen tai 64-bittinen) jne.
e_type Tiedostotyyppi, koska ELF-muoto tukee useita tyyppejä
e_machine Sen laitteistoalustan arkkitehtuuri, jota varten tämä tiedosto luotiin. Taulukossa 2.4 näyttää tämän kentän mahdolliset arvot
e_versio ELF-muotoinen versionumero. Tyypillisesti määritellään EV_CURRENC (nykyinen), mikä tarkoittaa viimeisintä versiota
e_entry Virtuaalinen osoite, johon järjestelmä siirtää ohjauksen ohjelman lataamisen jälkeen (sisääntulopiste)
e_phoff Ohjelman otsikkotaulukon sijainti (poikkeama tiedoston alusta).
e_shoff Osion otsikkotaulukon sijainti
e_ehsize Otsikon koko
e_phentsize Kunkin ohjelman otsikon koko
e_phnum Ohjelman otsikoiden määrä
e_shentsize Kunkin segmentin otsikon (osion) koko
e_shnum Segmenttien otsikoiden määrä (osiot)
e_shstrndx Merkkijonotaulukon sisältävän segmentin sijainti

Taulukko 2.4. ELF-tiedoston otsikon e_machine-kentän arvot

Merkitys Laitteistoalusta
EM_M32 AT&T WE 32100
EM_SPARC Aurinko 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 Sun SPARC 32+

Ohjelman otsikkotaulukon sisältämät tiedot kertovat ytimelle, kuinka segmenteistä luodaan prosessikuva. Useimmat segmentit kopioidaan (kartoitetaan) muistiin ja edustavat prosessin olennaisia ​​segmenttejä sen suorittaessa, kuten koodi- tai datasegmenttejä.

Jokainen ohjelmalohkon otsikko kuvaa yhtä segmenttiä ja sisältää seuraavat tiedot:

Segmentin tyyppi ja käyttöjärjestelmätoiminnot tällä segmentillä

Segmentin sijainti tiedostossa

Segmentin aloitusosoite prosessin virtuaalimuistissa

Segmentin koko tiedostossa

Muistin segmentin koko

Segmenttien käyttöoikeudet (kirjoita, lue, suorita)

Jotkut segmentit ovat LOAD-tyyppisiä, jotka käskevät ytimen luomaan näitä segmenttejä vastaavat tietorakenteet käynnistettäessä ohjelman suoritusta, ns. alueilla, joka määrittää prosessin virtuaalimuistin ja niihin liittyvien attribuuttien vierekkäiset alueet. Segmentti, jonka sijainti ELF-tiedostossa on ilmoitettu vastaavassa ohjelman otsikossa, kartoitetaan luodulle alueelle, jonka alun virtuaaliosoite ilmoitetaan myös ohjelman otsikossa. Tämän tyyppisiä segmenttejä ovat esimerkiksi ohjelmakäskyt (koodi) ja sen tiedot sisältävät segmentit. Jos segmentin koko on pienempi kuin alueen koko, käyttämätön tila voidaan täyttää nollilla. Tätä mekanismia käytetään erityisesti luotaessa alustamatonta prosessidataa (BSS). Puhumme alueista lisää luvussa 3.

INTERP-segmentti tallentaa ohjelmatulkin. Tätä segmenttityyppiä käytetään ohjelmissa, jotka vaativat dynaamista linkitystä. Dynaamisen linkityksen ydin on, että suoritettavan tiedoston yksittäiset komponentit (jaetut objektitiedostot) yhdistetään ei käännösvaiheessa, vaan ohjelman käynnistämisvaiheessa. Sen tiedoston nimi, joka on dynaaminen linkkieditori, on tallennettu tähän segmenttiin. Kun ohjelma käynnistetään suoritettavaksi, ydin luo prosessin kuvan käyttämällä määritettyä linkkieditoria. Aluksi muistiin ei siis ladata lähdeohjelmaa, vaan dynaamisen linkin editori. Seuraavassa vaiheessa dynaaminen linkkieditori toimii UNIX-ytimen kanssa ja luo täydellisen kuvan suoritettavasta tiedostosta. Dynaaminen editori lataa tarvittavat jaetut objektitiedostot, joiden nimet on tallennettu erillisiin lähdesuoritettavan tiedoston segmentteihin, ja suorittaa tarvittavat sijoittelut ja linkittäminen. Lopuksi ohjaus siirretään alkuperäiseen ohjelmaan.

Lopuksi tiedosto päättyy otsikkotaulukkoon osiot tai osiot(osio). Osat määrittelevät tiedoston osia, joita käytetään linkittämiseen muihin moduuleihin kääntämisen tai dynaamisen linkityksen aikana. Näin ollen otsikot sisältävät kaikki tarvittavat tiedot näiden osioiden kuvaamiseksi. Pääsääntöisesti osiot sisältävät tarkempaa tietoa segmenteistä. Esimerkiksi koodisegmentti voi koostua useista osista, kuten hash-taulukosta ohjelmassa käytettyjen symbolien indeksien tallentamiseen, ohjelman alustuskoodin osasta, dynaamisen editorin käyttämästä linkitystaulukosta ja osuudesta, joka sisältää todellisen ohjelman ohjeet.

Palaamme ELF-muotoon luvussa 3, kun keskustelemme prosessin virtuaalisen muistin organisoinnista, mutta toistaiseksi siirrymme seuraavaan yleiseen muotoon, COFF.

Kirjasta The Art of Programming for Unix kirjoittaja Raymond Eric Stephen

Kirjasta Itseopastus tietokoneella työskentelyyn kirjoittaja Kolisnichenko Denis Nikolaevich

Kirjasta Abstrakti, työselostus, tutkinto tietokoneella kirjoittaja Balovsyak Nadezhda Vasilievna

5.2.6. Windows INI -muoto Monet Microsoft Windows -ohjelmat käyttävät tekstitietomuotoa, joka on samanlainen kuin esimerkissä 5.6. Tämä esimerkki yhdistää valinnaiset resurssit nimeltä tili, hakemisto, numeric_id ja developer projekteihin nimeltä python, sng, fetchmail ja py-howto. Äänityksessä

Kirjasta Uusin itseopastus tietokoneella työskentelyyn kirjoittaja Beluntsov Valeri

14.5.3. Solumuoto Muoto määrittää, kuinka solun arvo näytetään. Muoto liittyy läheisesti solun tietotyyppiin. Asetat tyypin itse. Jos syötit numeron, se on numeerinen tietotyyppi. Excel itse yrittää määrittää muodon tietotyypin perusteella. Jos esimerkiksi syötit tekstiä, niin

Kirjasta The Art of Programming for Unix kirjoittaja Raymond Eric Stephen

PDF-muoto PDF on lyhenne sanoista Portable Document Format. Tämä muoto on luotu erityisesti poistamaan ongelmia tiedostojen tietojen näyttämisessä. Sen etuna on, että ensinnäkin PDF-muotoon tallennettu asiakirja on sama

Kirjasta TCP/IP Architecture, Protocols, Implementation (mukaan lukien IP-versio 6 ja IP Security) Kirjailija: Faith Sydney M

Tiedostomuoto Kun käyttäjä aloittaa työskentelyn tiedoston kanssa, järjestelmän on tiedettävä, missä muodossa se on kirjoitettu ja millä ohjelmalla se pitää avata. Esimerkiksi jos tiedosto sisältää pelkkää tekstiä, se voidaan lukea missä tahansa tekstiohjelmassa

Kirjasta Yandex kaikille kirjailija Abramzon M. G.

5.2.2. RFC 822 -muoto RFC 822 -metamuoto on johdettu Internet-sähköpostiviestien tekstimuodosta. RFC 822 on tärkein Internetin RFC-standardi, joka kuvaa tätä muotoa (korvattiin myöhemmin RFC 2822:lla). MIME (Multipurpose Internet Media Extension) -muoto

Kirjasta Macromedia Flash Professional 8. Graphics and Animation kirjailija Dronov V. A.

5.2.3. Cookie-Jar-muoto Fortune(1) käyttää eväste-jar-muotoa omaan satunnaisten lainausten tietokantaansa. Se sopii viesteille, jotka ovat yksinkertaisesti jäsentämätöntä tekstiä. Symbolia käytetään tietueen erottimena tässä muodossa

Kirjasta Computer Sound Processing kirjoittaja Zagumennov Aleksanteri Petrovitš

5.2.4. Record-jar-muoto Cookie-jar-muotoiset tietueerottimet toimivat hyvin tietueiden RFC 822 -metaforamuodon kanssa muodostaen muodon, jota kutsutaan tässä kirjassa "record-jar". Joskus tarvitaan tekstimuoto, joka tukee useita merkintöjä, joilla on erilainen nimenomainen joukko

Kirjasta UNIX-käyttöjärjestelmä kirjoittaja Robačevski Andrei M.

5.2.6. Windows INI -muoto Monet Microsoft Windows -ohjelmat käyttävät tekstitietomuotoa, joka on samanlainen kuin esimerkissä 5.6. Tämä esimerkki yhdistää valinnaiset resurssit nimeltä tili, hakemisto, numeric_id ja developer projekteihin nimeltä python, sng, fetchmail ja py-howto. Äänityksessä

Kirjasta Toimistotietokone naisille kirjoittaja Pasternak Evgenia

19.5 Yleinen URL-muoto Yhteenvetona yllä olevasta huomaamme, että:? URL-osoite alkaa käytetyllä yhteysprotokollalla.? Kaikissa sovelluksissa paitsi online-uutisissa ja sähköpostissa seuraavaa seuraa erotin: //.? Sitten määritetään palvelimen isäntänimi.? Lopulta

Kirjailijan kirjasta

3.3.1. RSS-muoto Voit lukea verkkosivuston uutisia eri tavoin. Helpoin tapa on vierailla sivustolla silloin tällöin ja katsoa uusia viestejä. Voit asentaa ohjelman, joka muodostaa yhteyden uutiskanavaan ja vastaanottaa itse uutisotsikot tai uutisyhteenvedot sen mukaan

Kirjailijan kirjasta

MP3-muoto MP3-muoto luotiin MPEG 1 level 3 -koodekilla pakattujen musiikkitiedostojen jakeluun. Tällä hetkellä se on suosituin muoto musiikin jakamiseen Internetin kautta. Tukee ehdottomasti kaikki äänen tallennus- ja käsittelyohjelmat

Kirjailijan kirjasta

MP3-muoto Äänenpakkausmenetelmä sekä kansainvälisen MPEG-järjestön (Moving Pictures Experts Group) ehdottama pakattu äänitiedostomuoto, joka perustuu havainnolliseen äänen koodaukseen. Työskentele tehokkaiden koodausalgoritmien luomiseksi

Kirjailijan kirjasta

ELF-muoto ELF-muodossa on useita tiedostotyyppejä, joita olemme tähän mennessä kutsuneet eri nimillä, kuten suoritettava tiedosto tai objektitiedosto. ELF-standardi erottaa kuitenkin seuraavat tyypit:1. Uudelleensijoitettava tiedosto, joka tallentaa ohjeita ja tietoja, joita voi olla

Kirjailijan kirjasta

Numeromuoto Pääsimme vihdoin numeromuotoon. Olen jo maininnut sen useammin kuin kerran, nyt rikotan sen (vaikka saatat jo ymmärtää yleisen merkityksen Excelissä voidaan näyttää eri muodoissa). Tässä osiossa puhumme siitä, mitä numeromuotoja on olemassa ja miten

Tässä katsauksessa puhumme vain tämän muodon 32-bittisestä versiosta, koska emme vielä tarvitse 64-bittistä versiota.

Mikä tahansa ELF-tiedosto (mukaan lukien tämän muotoiset objektimoduulit) koostuu seuraavista osista:

  • ELF-tiedoston otsikko;
  • Ohjelman osien taulukko (voi puuttua objektimoduuleista);
  • ELF-tiedoston osiot;
  • Osiotaulukko (ei ehkä ole suoritusmoduulissa);
  • Suorituskykysyistä ELF-muoto ei käytä bittikenttiä. Ja kaikki rakenteet ovat yleensä 4-tavuisia.

Katsotaanpa nyt ELF-tiedostojen otsikoissa käytettyjä tyyppejä:

Katsotaanpa nyt tiedoston otsikkoa:

#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; /* Elf_32 e_ffoff; Elf32_Word e_f gs; Elf32_Half e_shnum;

e_ident-taulukko sisältää tietoa järjestelmästä ja koostuu useista alikentistä.

Rakenne (signed char ei_magic; unsigned char ei_class; unsigned char ei_data; unsigned char ei_version; unsigned char ei_pad; )

  • ei_magic - vakioarvo kaikille ELF-tiedostoille, yhtä suuri kuin (0x7f, "E", "L", "F")
  • ei_class - ELF-tiedostoluokka (1 - 32 bittiä, 2 - 64 bittiä, joita emme ota huomioon)
  • ei_data - määrittää tämän tiedoston tavujärjestyksen (tämä järjestys riippuu alustasta ja voi olla eteenpäin (LSB tai 1) tai taaksepäin (MSB tai 2)) Intel-suorittimissa vain arvo 1 on sallittu.
  • ei_version on melko hyödytön kenttä, ja jos se ei ole yhtä suuri kuin 1 (EV_CURRENT), tiedostoa pidetään virheellisenä.

Käyttöjärjestelmät tallentavat tunnistetietonsa ei_pad-kenttään. Tämä kenttä voi olla tyhjä. Ei sillä meillekään ole väliä.

e_type otsikkokenttä voi sisältää useita arvoja, suoritettavissa tiedostoissa sen tulee olla ET_EXEC yhtä suuri kuin 2

e_machine - määrittää prosessorin, jolla tämä suoritettava tiedosto voi toimia (meille hyväksyttävä arvo EM_386 on 3)

E_version-kenttä vastaa otsikon ei_version-kenttää.

e_entry-kenttä määrittelee ohjelman aloitusosoitteen, joka sijoitetaan eip:iin ennen ohjelman käynnistymistä.

Kenttä e_phoff määrittää siirtymän sen tiedoston alusta, jossa ohjelmien muistiin lataamiseen käytetty ohjelmaosataulukko sijaitsee.

En luettele kaikkien kenttien tarkoitusta, kaikkia ei tarvita lataamiseen. Kuvailen vain kaksi muuta.

Kenttä e_phentsize määrittää merkinnän koon ohjelmaosan taulukossa.

Ja e_phnum-kenttä määrittää merkintöjen määrän ohjelman osataulukossa.

Osataulukkoa (ei ohjelman osia) käytetään ohjelmien linkittämiseen. emme ota sitä huomioon. Emme myöskään ota huomioon dynaamisesti linkitettyjä moduuleja. Tämä aihe on melko monimutkainen eikä sovi ensituttaville. :)

Nyt ohjelman osioista. Ohjelman osataulukon syöttömuoto on seuraava:

Rakenne 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_lags;

Lisätietoja kentistä.

  • p_type - määrittää ohjelmaosan tyypin. Se voi vaatia useita arvoja, mutta olemme kiinnostuneita vain yhdestä. PT_LOAD(1). Jos osio on tämän tyyppinen, se on tarkoitettu ladattavaksi muistiin.
  • p_offset - määrittää siirtymän tiedostossa, josta tämä osio alkaa.
  • p_vaddr - määrittää virtuaalisen osoitteen, jossa tämä osio ladataan muistiin.
  • p_paddr - määrittää fyysisen osoitteen, johon tämä osio ladataan. Tämä kenttä on valinnainen, ja se on järkevä vain joillakin alustoilla.
  • p_filesz - määrittää tiedoston osion koon.
  • p_memsz - määrittää muistiosan koon. Tämä arvo voi olla suurempi kuin edellinen. P_flag-kenttä määrittää muistin osien käyttötavan. Jotkut osat voidaan suorittaa, jotkut voidaan kirjoittaa muistiin. Kaikki ovat luettavissa olemassa olevissa järjestelmissä.

Ladataan ELF-muotoa.

Selvitimme vähän otsikkoa. Nyt annan algoritmin binääritiedoston lataamiseen ELF-muodossa. Algoritmi on kaavamainen, eikä sitä tule pitää toimivana ohjelmana.

Int LoadELF (signed char *bin) ( struct elf32_hdr *EH = (struct elf32_hdr *)bin; struct elf32_phdr *EPH; if (EH->e_ident != 0x7f || // Ohjaus MAGIC EH->e_ident != "E" ||. EH->e_ident != "L" ||. EH->e_ident != ELFCLASS32 ||. Ohjaa luokkaa EH->e_ident ! e_ident != EV_CURRENT ||. // versio EH->e_type != ET_EXEC ||. // EH->e_machine != EM_386 ||. // alusta EH->e_versio != EV_CURRENT) // return ELF_WRONG = (struct elf32_phdr *)(bin + EH->e_phnum-) (jos (EPH->p_type == PT_LOAD) memcpy (EPH->p_vaddr, bin + EPH-); >p_poikkeama, EPH->p_filesz); )

Vakavasti, kannattaa analysoida EPH->p_flags-kentät ja asettaa käyttöoikeudet sopiville sivuille, eikä pelkkä kopiointi toimi täällä, mutta tämä ei enää liity muotoon, vaan muistin varaamiseen. Siksi emme puhu tästä nyt.

PE-muoto.

Se on monella tapaa samanlainen kuin ELF-muoto, eikä ole yllättävää, että siinä pitäisi olla myös ladattavia osioita.

Kuten kaikki muukin Microsoftilla:) PE-muoto perustuu EXE-muotoon. Tiedoston rakenne on seuraava:

  • 00h - EXE-otsikko (en katso sitä, se on yhtä vanha kuin Dos. :)
  • 20h - OEM-otsikko (ei mitään merkittävää siinä);
  • 3сh - tiedoston todellisen PE-otsikon siirtymä (dword).
  • tynkä liike taulukko;
  • tynkä;
  • PE-otsikko;
  • esineen taulukko;
  • tiedostoobjektit;

stub on ohjelma, joka toimii reaalitilassa ja suorittaa joitain alustavia toimintoja. Se voi olla poissa, mutta joskus se saattaa olla tarpeen.

Meitä kiinnostaa hieman erilainen, PE-otsikko.

Sen rakenne on seuraavanlainen:

Struct 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_pe_codes; unsigned shortversigns; unsigned koko, allekirjoittamaton pitkä pe_idata_size ; unsigned long pe_code_base // ja paljon muuta merkityksetöntä

Siellä on paljon tavaraa. Riittää, kun sanotaan, että tämän otsikon koko on 248 tavua.

Ja pääasia on, että suurinta osaa näistä kentistä ei käytetä. (Kuka rakentaa näin?) Ei, niillä on toki aika hyvin tiedossa oleva tarkoitus, mutta esimerkiksi testiohjelmassani on nollia kentissä pe_code_base, pe_code_size jne., mutta se toimii hyvin. Johtopäätös on, että tiedosto ladataan objektitaulukon perusteella. Siitä me puhumme.

Kohdetaulukko seuraa välittömästi PE-otsikon jälkeen. Tämän taulukon merkinnät ovat seuraavassa muodossa:

Rakenne pe_ohdr (signed char o_name; allekirjoittamaton pitkä o_vsize; allekirjoittamaton pitkä o_vaddr; allekirjoittamaton pitkä o_psize; allekirjoittamaton pitkä o_poff; allekirjoittamaton char o_reserved; allekirjoittamaton pitkä o_liput; );

  • o_nimi - osion nimi, se on täysin välinpitämätön latauksen suhteen;
  • o_vsize - muistiosan koko;
  • o_vaddr - muistiosoite suhteessa ImageBaseen;
  • o_psize - osion koko tiedostossa;
  • o_poff - osion siirtymä tiedostossa;
  • o_liput - osion liput;

Kannattaa tutustua lippuihin tarkemmin.

  • 00000004h - käytetään koodille, jossa on 16 bitin siirtymä
  • 00000020h - koodiosio
  • 00000040h - alustettu tietoosa
  • 00000080h - alustamaton tietoosa
  • 00000200h - kommentit tai muu tieto
  • 00000400h - peittoalue
  • 00000800h - ei ole osa ohjelmakuvaa
  • 00001000h - yleistiedot
  • 00500000h - oletuskohdistus, ellei toisin mainita
  • 02000000h - voidaan purkaa muistista
  • 04000000h - ei välimuistissa
  • 08000000h - ei sivuttu
  • 10000000h - jaettu
  • 20000000h - toteutettavissa
  • 40000000h - voidaan lukea
  • 80000000h - voit kirjoittaa

Jälleen, en puhu jaetuista ja päällekkäisistä osioista, olemme kiinnostuneita koodista, tiedoista ja käyttöoikeuksista.

Yleensä nämä tiedot ovat jo riittävät binaaritiedoston lataamiseen.

Ladataan PE-muotoa.

int LoadPE (signed char *bin) ( struct elf32_hdr *PH = (struct pe_hdr *) (bin + *((signed long *)&bin)); // Tietenkään yhdistelmä ei ole selvä... ota vain sana at offset 0x3c / / Ja laske PE-otsikon osoite tiedostokuvassa elf32_phdr *POH if (PH == NULL || // Ohjaa PH->pe_sign-osoitinta != 0x4550 || // PE-allekirjoitus ("P"); , "E", 0, 0) PH->pe_cputype != 0x14c || >pe_obj_num--) ( if ((POH->p_liput & 0x60) != 0) // joko koodi tai alustettu datamuisti (PE->pe_image_base + POH->o_vaddr, bin + POH- >o_poff, POH->o_psize );

Tämä taas ei ole valmis ohjelma, vaan latausalgoritmi.

Ja jälleen, monia kohtia ei käsitellä, koska ne ylittävät aiheen soveltamisalan.

Mutta nyt kannattaa puhua hieman olemassa olevista järjestelmän ominaisuuksista.

Järjestelmän ominaisuudet.

Huolimatta prosessoreissa saatavilla olevien suojaustyökalujen joustavuudesta (suojaus kuvaajataulukoiden tasolla, suojaus segmenttitasolla, suojaus sivutasolla), olemassa olevissa järjestelmissä (sekä Windowsissa että Unixissa) käytetään täysimääräisesti vain sivun suojausta, mikä vaikka se voi suojata koodia kirjoittamiselta, mutta ei voi suojata tietoja suorittamiselta. (Ehkä tämä on syy järjestelmän haavoittuvuuksien runsaudelle?)

Kaikki segmentit osoitetaan lineaarisesta osoitteesta nolla ja ne ulottuvat lineaarisen muistin loppuun. Prosessin rajaus tehdään vain sivutaulukoiden tasolla.

Tässä suhteessa kaikki moduulit ei ole linkitetty aloitusosoitteista, vaan riittävän suurella siirtymällä segmentissä. Windowsissa segmentin perusosoite on 0x400000, Unixissa (Linux tai FreeBSD) - 0x8048000.

Jotkut ominaisuudet liittyvät myös muistin sivujärjestelyyn.

ELF-tiedostot linkitetään siten, että osioiden rajat ja koot ovat 4 kilotavun lohkoissa tiedostosta.

Ja PE-muodossa huolimatta siitä, että muoto itsessään mahdollistaa 512 tavun osien tasaamisen, osien kohdistusta käytetään 4k:ssä pienempää kohdistusta Windowsissa ei pidetä oikeana.

Jos olet asentanut tietokoneellesi virustorjuntaohjelma Voi skannaa kaikki tietokoneellasi olevat tiedostot sekä jokainen tiedosto erikseen. Voit tarkistaa minkä tahansa tiedoston napsauttamalla tiedostoa hiiren kakkospainikkeella ja valitsemalla sopivan vaihtoehdon tiedoston virusten varalta.

Esimerkiksi tässä kuvassa se on korostettu tiedosto my-file.elf, napsauta tätä tiedostoa hiiren kakkospainikkeella ja valitse tiedostovalikosta vaihtoehto "skannaa AVG:llä". Kun valitset tämän vaihtoehdon, AVG Antivirus avaa ja tarkistaa tiedoston virusten varalta.


Joskus seurauksena voi olla virhe virheellinen ohjelmiston asennus, joka saattaa johtua asennuksen aikana havaitusta ongelmasta. Tämä saattaa häiritä käyttöjärjestelmääsi linkitä ELF -tiedostosi oikeaan sovellusohjelmistoon, jotka vaikuttavat ns "tiedostopääteyhteydet".

Joskus yksinkertaista Dolphinin (emulaattorin) uudelleenasentaminen voi ratkaista ongelmasi yhdistämällä ELF:n Dolphiniin (emulaattori) oikein. Muissa tapauksissa tiedostojen yhdistämisongelmia voi johtua huono ohjelmisto ohjelmointi kehittäjälle, ja saatat joutua ottamaan yhteyttä kehittäjään lisäapua varten.


Neuvoja: Yritä päivittää Dolphin (emulaattori) uusimpaan versioon varmistaaksesi, että sinulla on uusimmat korjaustiedostot ja päivitykset.


Tämä saattaa tuntua liian ilmeiseltä, mutta usein ELF-tiedosto itse saattaa aiheuttaa ongelman. Jos sait tiedoston sähköpostin liitteenä tai latasit sen verkkosivustolta ja latausprosessi keskeytyy (kuten sähkökatkos tai muu syy), tiedosto saattaa vaurioitua. Jos mahdollista, yritä hankkia uusi kopio ELF tiedostosta ja yritä avata se uudelleen.


Huolellisesti: Vioittunut tiedosto voi aiheuttaa lisävaurioita tietokoneellasi oleville tai olemassa oleville haittaohjelmille, joten on tärkeää pitää tietokoneesi ajan tasalla ajan tasalla olevalla virustorjuntaohjelmalla.


Jos tiedostosi on ELF liittyvät tietokoneesi laitteistoon avataksesi mahdollisesti tarvitsemasi tiedoston päivitä laiteohjaimet liittyvät tähän laitteeseen.

Tämä ongelma yleensä liittyy mediatiedostotyyppeihin, jotka riippuvat tietokoneen sisällä olevan laitteiston onnistuneesta avaamisesta, esim. äänikortti tai videokortti. Jos esimerkiksi yrität avata äänitiedostoa, mutta et voi avata sitä, sinun on ehkä avattava se päivitä äänikortin ajurit.


Neuvoja: Jos kun yrität avata ELF -tiedoston, saat .SYS-tiedoston virheilmoitus, ongelma saattaa olla liittyvät vioittuneisiin tai vanhentuneisiin laiteajureihin jotka pitää päivittää. Tätä prosessia voidaan helpottaa käyttämällä ohjaimen päivitysohjelmistoa, kuten DriverDoc.


Jos vaiheet eivät ratkaise ongelmaa ja sinulla on edelleen ongelmia ELF tiedostojen avaamisessa, tämä voi johtua käytettävissä olevien järjestelmäresurssien puute. Jotkut ELF-tiedostojen versiot voivat vaatia huomattavan määrän resursseja (esim. muistia/RAM-muistia, prosessointitehoa) avautuakseen oikein tietokoneellasi. Tämä ongelma on melko yleinen, jos käytät melko vanhaa tietokonelaitteistoa ja samalla paljon uudempaa käyttöjärjestelmää.

Tämä ongelma voi ilmetä, kun tietokoneella on vaikeuksia pysyä tehtävässä, koska käyttöjärjestelmä (ja muut taustalla toimivat palvelut) kuluttaa liikaa resursseja ELF-tiedoston avaamiseen. Yritä sulkea kaikki sovellukset tietokoneellasi ennen Nintendo Wii -pelitiedoston avaamista. Vapauttamalla kaikki saatavilla olevat resurssit tietokoneeltasi, olet parhaassa asennossa yrittää avata ELF -tiedostosi.


Jos sinä suorittanut kaikki yllä kuvatut vaiheet ja ELF -tiedostosi ei vieläkään avaudu, sinun on ehkä suoritettava laitepäivitys. Useimmissa tapauksissa, jopa käytettäessä vanhempia laitteistoversioita, prosessointiteho voi silti olla enemmän kuin riittävä useimmille käyttäjäsovelluksille (ellet tee paljon suoritinintensiivistä työtä, kuten 3D-renderöintiä, taloudellista/tieteellistä mallintamista tai intensiivinen multimediatyö). Täten, on todennäköistä, että tietokoneessasi ei ole tarpeeksi muistia(tunnetaan yleisesti nimellä "RAM" tai hajasaantimuisti) tiedoston avaamiseen.

Versio tästä vastauksesta, jossa on hyvä sisällysluettelo ja muuta sisältöä: http://www.cirosantilli.com/elf-hello-world (klikkaa tästä nähdäksesi 30 000 merkin raja)

Standardit

ELF on LSB:n antama:

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

LSB viittaa pääasiassa muihin standardeihin pienillä laajennuksilla, erityisesti:

    yleinen (molemmat SCO:lta):

    • System V ABI 4.1 (1997) http://www.sco.com/developers/devspecs/gabi41.pdf, ei 64-bittinen, vaikka sille on varattu maaginen numero. Sama koskee päätiedostoja.
    • System V ABI Update DRAFT 17 (2003) http://www.sco.com/developers/gabi/2003-12-17/contents.html lisää 64 bittiä. Päivittää vain edellisen asiakirjan luvut 4 ja 5: loput pysyvät voimassa ja niihin viitataan edelleen.
  • erityinen arkkitehtuuri:

    • IA-32: http://refspecs.linuxfoundation.org/LSB_4.1.0/LSB-Core-IA32/LSB-Core-IA32/elf-ia32.html viittaa pääasiassa osoitteeseen 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, viittaa pääasiassa http://www.x86-64.org/ dokumentaatioon /abi.pdf

Kätevä ansioluettelo löytyy osoitteesta:

Sen rakennetta voidaan tarkastella käyttäjäystävällisillä menetelmillä, kuten readelf ja objdump.

Luo esimerkki

Eritelkäämme minimaalisen suoritettavan Linux x86-64 -esimerkin:

Section .data hello_world db "Hei maailma!", 10 hello_world_len equ $ - hello_world section .text global _start _start: mov rax, 1 mov rdi, 1 mov rsi, hello_world mov rdx, mov_vxs, mov_vx scall

Koottu kanssa

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 versio 2.24 (sisältää ld:n)
  • Ubuntu 14.04

Emme käytä C-ohjelmaa, koska se vaikeuttaa analyysiä, joka olisi taso 2 :-)

binäärimuodon heksadesimaaliesitys

hd hello_world.o hd hello_world.out

Globaali tiedostorakenne

ELF-tiedosto sisältää seuraavat osat:

  • ELF-otsikko. Osoittaa osan otsikkotaulukon ja ohjelman otsikkotaulukon sijainnin.

    Osion otsikkotaulukko (valinnainen suoritettavassa tiedostossa). Jokaisella niistä on osion otsikot e_shnum , joista jokainen osoittaa osion sijainnin.

    N osiota N:llä<= e_shnum (необязательно в исполняемом файле)

    Ohjelman otsikkotaulukko (vain suoritettavat tiedostot). Jokaisessa niistä on e_phnum-ohjelman otsikot, joista jokainen ilmaisee segmentin sijainnin.

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

Näiden osien järjestys ei ole kiinteä: ainoa kiinteä asia on ELF-otsikko, jonka on oltava ensin tiedostossa: Yleiset dokumentit sanovat:

ELF-otsikko

Helpoin tapa katsella otsikkoa on:

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

Tavu objektitiedostossa:

00000000 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 |.ELF............| 00000010 01 00 3e 00 01 00 00 00 00 00 00 00 00 00 00 00 |..>.............| 00000020 00 00 00 00 00 00 00 00 40 00 00 00 00 00 00 00 |........@......| 00000030 00 00 00 00 40 00 00 00 00 00 40 00 07 00 03 00 |...@.....@.....|

00000000 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 |.ELF............| 00000010 02 00 3e 00 01 00 00 00 b0 00 40 00 00 00 00 00 |..>......@.....| 00000020 40 00 00 00 00 00 00 00 10 01 00 00 00 00 00 00 |@...............| 00000030 00 00 00 00 40 00 38 00 02 00 40 00 06 00 03 00 | [sähköposti suojattu]...@.....|

Esitetty rakenne:

Typedef struct ( unsigned char e_ident; Elf64_Half e_type; Elf64_Half e_machine; Elf64_Word e_versio; Elf64_Addr e_entry; Elf64_Off e_phoff; Elf64_Off_Elf6H_4ffal; e_ehsize; Elf64_Half e_phnum;

Manuaalinen hajottaminen:

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

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

    0 5: EI_DATA = 01 = ELFDATA2LSB: big end -data

    0 6: EI_VERSION = 01: muotoinen versio

    0 7: EI_OSABI (vain 2003) = 00 = ELFOSABI_NONE: ei laajennuksia.

    0 8: EI_PAD = 8x 00: varatut tavut. On asetettava arvoon 0.

    1 0: e_type = 01 00 = 1 (big endian) = ET_REl: uudelleensijoitettava muoto

    Ohjelman ET_EXEC suoritettavassa tiedostossa 02 00.

    1 2: e_machine = 3e 00 = 62 = EM_X86_64: AMD64-arkkitehtuuri

    1 4: e_versio = 01 00 00 00: täytyy olla 1

    1 8: e_entry = 8x 00: suoritusosoitteen syöttöpiste tai 0, jos ei sovelleta, kuten objektitiedostossa, koska aloituspistettä ei ole.

    Suoritettavassa tiedostossa se on b0 00 40 00 00 00 00 00 . TODO: mitä muuta voimme asentaa? Ydin näyttää laittavan IP-osoitteen suoraan tähän arvoon, se ei ole kovakoodattu.

    2 0: e_phoff = 8x 00: Ohjelman otsikkotaulukon siirtymä, 0, jos ei mitään.

    40 00 00 00 suoritettavassa tiedostossa, eli se alkaa heti ELF-otsikon jälkeen.

    2 8: e_shoff = 40 7x 00 = 0x40: Osion otsikkotaulukkotiedoston siirtymä, 0, jos ei mitään.

    3 0: e_flags = 00 00 00 00 TODO. Varsinkin Archille.

    3 4: e_ehsize = 40 00: Tämän tontun otsikon koko. Miksi tämä kenttä? Miten tämä voi muuttua?

    3 6: e_phentsize = 00 00: kunkin ohjelman otsikon koko, 0 jos ei yhtään.

    38 00 suoritettavassa tiedostossa: tiedoston pituus on 56 tavua

    3 8: e_phnum = 00 00: ohjelman otsikkomerkintöjen määrä, 0 jos ei yhtään.

    02 00 suoritettavassa tiedostossa: on 2 merkintää.

    3 A: e_shentsize ja e_shnum = 40 00 07 00: osion otsikon koko ja merkintöjen määrä

Osion otsikkotaulukko

Joukko Elf64_Shdr-rakenteita.

Jokainen merkintä sisältää metatietoja kyseisestä osiosta.

ELF-otsikko e_shoff antaa lähtöpaikan tässä, 0x40.

e_shentsize ja e_shnum ELF-otsikosta sanovat, että meillä on 7 merkintää, jokainen 0x40 pitkä.

Joten taulukko ottaa tavuja välillä 0x40 - 0x40 + 7 + 0x40 - 1 = 0x1FF.

Jotkut osioiden otsikot on varattu tietyille osiotyypeille: http://www.sco.com/developers/gabi/2003-12-17/ch4.sheader.html#special_sections, esimerkiksi. .text vaatii tyypin SHT_PROGBITS ja SHF_ALLOC + SHF_EXECINSTR

readelf -S hello_world.o:

Osaotsikoita on 7, alkaen offsetista 0x40: Osion otsikot: Nimi Tyyppi Osoite Siirtymä Koko EntSize Liput Linkin tiedot Tasaa [ 0] NULL 00000000 0000000000000000 000 000 000 000 PROGBITS 0000000000000000 00000200 0000000000000000d 00000000000000000 WA 0 0 4 [ 2] .Text Progbits 0000000000000000 00000210 0000000000000027 0000000000000000 AX 0 16 [3] .SHSTRTAB STRTAB 0000000000000000 00000240 0000 0000000032 00000000000000 0,28]. 0000000000A8 0000000000000018 5 6 4 [5] RTAB 0000000000000000 00000330 0000000000000034 0000000000000000 0 0 0 1 [ 6].rela.text RELA 00000000000000000 00000370 0000000000000018 000000000000018 4 2 4 Lippujen avain: W (kirjoita), A (alloc), M (suorita) (info), L (linkkijärjestys), G (ryhmä), T (TLS), E (poissulje), x (tuntematon) O (tarvitaan ylimääräinen käyttöjärjestelmäkäsittely) o (käyttöjärjestelmäkohtainen), p (prosessorikohtainen)

kunkin merkinnän edustama rakenne:

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

Osat

Hakemistoosio 0

Sisältyy tavuihin 0x40 - 0x7F.

Ensimmäinen osa on aina maaginen: http://www.sco.com/developers/gabi/2003-12-17/ch4.sheader.html sanoo:

Jos osioiden määrä on suurempi tai yhtä suuri kuin SHN_LORESERVE (0xff00), e_shnum asetetaan arvoon SHN_UNDEF (0), ja osion otsikkotaulukkomerkintöjen todellinen määrä sisältyy osion otsikon sh_size-kenttään indeksissä 0 (muuten alkumerkinnän sh_size-jäsen sisältää 0).

Kuvassa 4-7: Erikoisosion indeksit ovat muita taikaosia.

Indeksissä 0 vaaditaan SHT_NULL. Onko tälle muita käyttötarkoituksia: Mitä hyötyä SHT_NULL-osiosta ELF:ssä on? ?

.data-osio

Tiedot ovat osa 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 |............|

    Tässä 1 sanoo, että tämän osion nimi alkaa tämän osan ensimmäisellä merkillä ja päättyy ensimmäiseen NUL-merkkiin, mikä muodostaa string.data .

    Data on yksi osion nimistä, jolla on ennalta määritetty merkitys http://www.sco.com/developers/gabi/2003-12-17/ch4.strtab.html

    Nämä osat tallentavat alustettuja tietoja, jotka vaikuttavat ohjelman muistikuvaan.

  • 80 4: sh_type = 01 00 00 00: SHT_PROGBITS: ELF ei määrittele osion sisältöä, vain sen mukaan, miten ohjelma tulkitsee sen. OK, koska .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 .data-osiossa vaaditulla tavalla

    90 0: sh_addr = 8x 00: mihin virtuaaliseen osoitteeseen osio sijoitetaan ajon aikana, 0 jos sitä ei ole sijoitettu

    90 8: sh_offset = 00 02 00 00 00 00 00 00 = 0x200: tavujen määrä ohjelman alusta tämän osan ensimmäiseen tavuun

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

    Jos otamme 0xD tavua alkaen sh_offset 200:sta, näemme:

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

    AHA! Joten merkkijonomme "Hei maailma!" on dataosiossa, kuten sanoimme, se on NASM:ssa.

    Kun olemme saaneet hd:n valmiiksi, katsomme sitä seuraavasti:

    Readelf -x .data hello_world.o

    mikä tuottaa:

    Osion ".data" heksadesimaalivedos: 0x00000000 48656c6c 6f20776f 726c6421 0a Hei maailma!.

    NASM asettaa kunnolliset ominaisuudet tälle osalle, koska se viittaa maagisesti .dataan: http://www.nasm.us/doc/nasmdoc7.html#section-7.9.2

    Huomaa myös, että tämä oli väärä osion valinta: hyvä C-kääntäjä olisi laittanut merkkijonon sen sijaan .rodata-muotoon, koska se on vain luku -tilassa, ja tämä mahdollistaa käyttöjärjestelmän lisäoptimoinnin.

    a0 8: sh_link ja sh_info = 8x 0: eivät koske tämän osion tyyppiä. http://www.sco.com/developers/gabi/2003-12-17/ch4.sheader.html#special_sections

    b0 0: sh_addralign = 04 = TODO: miksi tämä kohdistus on tarpeen? Onko tämä vain sh_addr:lle ja myös sh_addr:n sisällä oleville hahmoille?

    b0 8: sh_entsize = 00 = osio ei sisällä taulukkoa. Jos != 0, tämä tarkoittaa, että osio sisältää kiinteän kokoisen merkinnän taulukon. Tässä tiedostossa näemme readelf-tulosta, että tämä koskee .symtab- ja .rela.text-osioita.

.teksti-osio

Nyt kun olemme tehneet yhden osion manuaalisesti, siirrytään ja käytämme readelf -S:ää muissa osissa.

Nimi Tyyppi Osoite Siirtymä Koko EntSize Liput Linkin tiedot Tasaa [ 2].teksti PROGBITS 0000000000000027 00000000000000000 AX 0 00000000000

Teksti on suoritettavaa, mutta ei kirjoitettavaa: jos yritämme kirjoittaa siihen Linuxin segfaults. Katsotaan, onko meillä todella koodi:

Objdump -d hello_world.o

Hello_world.o: tiedostomuoto elf64-x86-64 Osan purkaminen .text: 000000000000000000<_start>: 0: b8 01 00 00 00 siirto $0x1,%eax 5: bf 01 00 00 00 mov $0x1,%edi a: 48 be 00 00 00 00 00 movabs $0x0,%rsi 11:00 14:00 0d 00 00 00 siirto $0xd,%edx 19: 0f 05 syscall 1b: b8 3c 00 00 00 mov $0x3c,%eax 20: bf 00 00 00 00 siirto $0x0,%edi 205

Jos meillä on grep b8 01 00 00 HD:llä, näemme, että se tapahtuu vain numerolla 00000210, mikä on mitä tässä osiossa sanotaan. Ja koko on 27, mikä myös vastaa. Siksi meidän on puhuttava oikeasta osiosta.

Tämä näyttää oikealta koodilta: kirjoitus, jota seuraa exit .

Mielenkiintoisin osa on rivi, joka tekee:

Movabs $0x0,%rsi

välitä merkkijonon osoite järjestelmäkutsulle. Tällä hetkellä 0x0 on vain paikkamerkki. Sidonnan jälkeen se muuttuu:

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

Tämä muutos on mahdollinen data.rela.text -osion vuoksi.

SHT_STRTAB

Osioita, joissa on sh_type == SHT_STRTAB, kutsutaan merkkijonotaulukoiksi.

Tällaisia ​​osioita käyttävät muut osiot, kun merkkijonon nimiä on käytettävä. Käyttökohdassa lukee:

  • mitä merkkijonoa he käyttävät
  • mikä on kohderivitaulukon indeksi, josta rivi alkaa

Joten meillä voisi olla esimerkiksi merkkijonotaulukko, joka sisältää: TODO: pitäisikö meidän aloittaa \0 ?

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

Ja jos toinen osa haluaa käyttää riviä d e f , heidän on osoitettava kyseisen jakson indeksiin 5 (kirjain d).

Tunnetut merkkijonotaulukot:

  • .shstrtab
  • .strtab

.shstrtab

Osion tyyppi: sh_type == SHT_STRTAB .

Yleisnimi: Osion otsikon otsikkomerkkijono.

Osio name.shstrtab on varattu. Standardi sanoo:

Tämä osio sisältää osioiden nimet.

Tämä osio määrittää itse ELF-otsikon e_shstrnd-kentän.

Tämän osion riviindeksit ilmaistaan ​​rivejä edustavien osion otsikoiden sh_name-kentällä.

Tässä osiossa ei ole määritetty SHF_ALLOC:ta, joten se ei näy suoritettavassa ohjelmassa.

Readelf -x .shstrtab hello_world.o

Osion ".shstrtab" heksadesimaattinen vedos: 0x00000000 002e6461 7461002e 74657874 002e7368 ..data..text..sh 0x00000010 73747274 616202e ym tab.. 0x00000020 73747274 6162002e 72656c61 2e746578 strtab..rela.tex 0x00000030 7400 t.

Tämän osion tiedot ovat kiinteässä muodossa: http://www.sco.com/developers/gabi/2003-12-17/ch4.strtab.html

Jos katsomme muiden osien nimiä, voimme nähdä, että ne kaikki sisältävät numeroita, esim. section.text on numeroitu 7.

Sitten jokainen rivi päättyy, kun esimerkiksi ensimmäinen NUL-merkki löytyy. merkki 12 \0 välittömästi .text\0 jälkeen.

.symtab

Osion tyyppi: sh_type == SHT_SYMTAB .

Yleisnimi: symbolitaulukko.

Huomioikaa ensin, että:

  • sh_link = 5
  • sh_info = 6

SHT_SYMTAB-osiossa nämä numerot tarkoittavat seuraavaa:

  • jouset
  • jotka antavat symbolien nimet ovat osiossa 5, .strtab
  • liiketiedot ovat kohdassa 6, .rela.text

Hyvä korkeatasoinen työkalu tämän osan purkamiseen:

Nm hello_world.o

joka antaa:

0000000000000000 T _aloitus 0000000000000000 d hello_world 000000000000000d a hello_world_len

Tämä on kuitenkin korkean tason esitys, joka jättää pois joitakin merkkityyppejä ja jossa merkit on määritetty. Tarkemman purkamisen saat käyttämällä:

Readelf -s hello_world.o

joka antaa:

Symbolitaulukko ".symtab" sisältää 7 merkintää: Num: Arvo Koko Tyyppi Bind Vis Ndx Nimi 0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND 1: 0000000000000000 0 FILE LOCAL DEFAULT A.00as 0000 0 OSA PAIKALLINEN OLETUS 1 3: 000000000000000000 0 Osio Paikallinen oletus 2 4: 000000000000000000 0 Notype Paikallinen oletus 1 Hello_World 5: 000000000000000d 0 Ei tyyppi Paikallinen oletus Abs Hello_world_len 6: 000000000 EF00000000 EF00000000

Taulukon binaarimuoto on dokumentoitu osoitteessa http://www.sco.com/developers/gabi/2003-12-17/ch4.symtab.html

Readelf -x .symtab hello_world.o

Mikä antaa:

Osion ".symtab" heksadesimaalivedos: 0x00000000 00000000 00000000 00000000 00000000 ................ 0x00000010 00000000 00000000 00000000 00000000 00000000 ............... 0x00000000 00000000 00000000 00000000 00000000 ................ 0x00000030 00000000 03000100 00000000 00000000 00000000 00000000 00000 00000000 03000200 .. ............. ...... 00200 00000000 00000000 -............... 0x000000a0 00000000 00000000 .......

Tietueet ovat tyyppiä:

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;

Kuten osiotaulukko, ensimmäinen merkintä on taikuutta ja sille annetaan kiinteät, merkityksettömät arvot.

Tietueessa 1 on ELF64_R_TYPE == STT_FILE . ELF64_R_TYPE jatkuu st_infossa.

Tavuanalyysi:

    10 8: st_name = 01000000 = merkki 1 .strtabissa, joka ennen seuraavaa \0 tekee osoitteen hello_world.asm

    Linkkijä voi käyttää tätä tietotiedostoa määrittääkseen, mitkä segmentin segmentit ovat menossa.

    10 12: st_info = 04

    Bitit 0-3 = ELF64_R_TYPE = Tyyppi = 4 = STT_FILE: Tämän merkinnän päätarkoitus on käyttää parametria st_name osoittamaan tämän objektitiedoston luoman tiedoston nimi.

    Bitit 4-7 = ELF64_ST_BIND = Sidonta = 0 = STB_LOCAL. Pakollinen arvo tiedostolle STT_FILE .

    10 13: st_shndx = Symbolitaulukko Otsikkotaulukko Indeksi = f1ff = SHN_ABS . Pakollinen tiedostolle STT_FILE.

    20 0: st_value = 8x 00: vaaditaan arvolle STT_FILE

    20 8: st_size = 8x 00: kokoa ei ole määritetty

Nyt readelfistä tulkitsemme nopeasti loput.

STT_SECTION

Tällaisia ​​elementtejä on kaksi, joista toinen osoittaa .dataan ja toinen .textiin (osion hakemistot 1 ja 2).

Num: Arvo Koko Tyyppi Sido Vis Ndx Nimi 2: 0000000000000000 0 OSASTO PAIKALLINEN OLETUS 1 3: 00000000000000000 0 OSA PAIKALLINEN OLETUS 2

TODO, mikä on niiden tarkoitus?

STT_NOTYPE

Kirjoita sitten tärkeimmät merkit:

Num: Arvo Koko Tyyppi Bind Vis Ndx Nimi 4: 0000000000000000 0 NOTYPE LOCAL OLETUS 1 hello_world 5: 000000000000000d 0 NOTYPE LOCAL DEFAULT ABS_helo_wor0000000000000 PE GLOBAL 2 _start String

hello_world on.data-osiossa (indeksi 1). Tämä arvo on 0: se osoittaa tämän osan ensimmäiseen tavuun.

Aloitus on merkitty GLOBAL-näkyvyydellä, koska kirjoitimme:

Global_start

NASM:ssä. Tämä on välttämätöntä, koska sitä tulisi pitää sisääntulopisteenä. Toisin kuin C, NASM-tunnisteet ovat oletuksena paikallisia.

hello_world_len osoittaa erityiseen st_shndx == SHN_ABS == 0xF1FF .

0xF1FF valitaan siten, ettei se ole ristiriidassa muiden osien kanssa.

st_value == 0xD == 13, joka on arvo, jonka tallensimme sinne kokoonpanoon: merkkijonon pituus Hello World! .

Tämä tarkoittaa, että liikkuminen ei vaikuta tähän arvoon: se on vakio.

Tämä on pieni optimointi, jonka kokoajamme tekee puolestamme ja jolla on ELF-tuki.

Jos käyttäisimme hello_world_len-osoitetta missä tahansa, kokoaja ei pystyisi merkitsemään sitä SHN_ABS:ksi ja linkittäjä joutuisi myöhemmin siirtämään ylimääräistä paikkaa.

SHT_SYMTAB suoritettavassa tiedostossa

Oletusarvoisesti NASM sijoittaa .symtab-tiedoston suoritettavaan tiedostoon.

Tätä käytetään vain virheenkorjaustarkoituksiin. Ilman symboleja olemme täysin sokeita ja joudumme kääntämään kaiken.

Voit poistaa sen käyttämällä objcopya ja suoritettava tiedosto toimii edelleen. Tällaisia ​​suoritettavia tiedostoja kutsutaan jaetuiksi suoritettaviksi.

.strtab

Pitää merkkijonoja symbolitaulukolle.

Tässä osiossa sh_type == SHT_STRTAB .

Osoitti osoitteeseen sh_link == 5 section.symtab .

Readelf -x .strtab hello_world.o

Osion ".strtab" heksadesimaaliveto: 0x00000000 0068656c 6c6f5f77 6f726c64 2e61736d .hello_world.asm 0x00000010 0068656c 6c66f7f7c 6c6f5c7 world.hel 0x00000020 6c6f5f77 6f726c64 5f6c656e 005f7374 lo_world_len._st 0x00000030 61727400 art.

Tämä tarkoittaa, että ELF-tason rajoitus on, että globaalit muuttujat eivät voi sisältää NUL-merkkejä.

.rela.text

Osion tyyppi: sh_type == SHT_RELA .

Yleisnimi: Siirrä osio.

Rela.text sisältää siirtotiedot, jotka määrittelevät kuinka osoite tulee muuttaa, kun viimeinen suoritettava tiedosto linkitetään. Tämä viittaa tekstialueen tavuihin, jotka on muutettava, kun sidonta tapahtuu oikeisiin muistipaikkoihin.

Pohjimmiltaan se muuntaa 0x0-paikkamerkin osoitteen sisältävän objektin tekstin:

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

todelliseen suoritettavaan koodiin, joka sisältää viimeisen 0x6000d8:

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

sh_info = 6 sections.symtab määritettiin.

readelf -r hello_world.o antaa:

Siirto-osio ".rela.text" offsetissa 0x3b0 sisältää 1 merkinnän: Offset Info Type Sym. Arvo Sym. Nimi + lisäys 00000000000c 000200000001 R_X86_64_64 00000000000000000 .data + 0

Osaa ei ole suoritettavassa tiedostossa.

Todelliset tavut:

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

Lähetetty rakenne:

Typedef-rakenne ( Elf64_Addr r_offset; Elf64_Xword r_info; Elf64_Sxword r_addend; ) Elf64_Rela;

    370 0: r_offset = 0xC: osoite osoitetekstissä, jonka osoitetta muutetaan

    370 8: r_info = 0x200000001. Sisältää 2 kenttää:

    • ELF64_R_TYPE = 0x1: Arvo riippuu tarkasta arkkitehtuurista.
    • ELF64_R_SYM = 0x2: sen osion indeksi, johon osoite osoittaa, eli .data, joka on indeksissä 2.

    AMD64 ABI sanoo, että tyyppiä 1 kutsutaan nimellä R_X86_64_64 ja että se edustaa S+A-toimintoa, jossa:

    • S: symbolin arvo objektitiedostossa, tässä 0, koska osoitamme 00 00 00 00 00 00 00 00 movabsista $0x0,%rsi
    • a: r_added-kentässä oleva lisäys

    Tämä osoite lisätään osioon, jossa siirto on käynnissä.

    Tämä siirtotoiminto vaikuttaa 8 tavuun.

    380 0: r_addend = 0

Joten esimerkissämme päättelemme, että uusi osoite on: S + A = .data + 0 ja siten ensimmäinen tieto-osiossa.

Ohjelman otsikkotaulukko

Näkyy vain suoritettavassa tiedostossa.

Sisältää tietoja siitä, kuinka suoritettava tiedosto tulee sijoittaa prosessin virtuaalimuistiin.

Linkkerin objektitiedosto luo suoritettavan tiedoston. Linkittäjän suorittamat päätehtävät:

    määrittää, mitkä objektitiedostojen osat menevät mihinkin suoritettavan tiedoston osiin.

    Binutilsissa tämä tarkoittaa linkkiohjelman jäsentämistä ja monien oletusasetusten käyttöä.

    Voit saada komentosarjalinkittäjän käytettäväksi ld --verbose kanssa ja asentaa mukautetun linkittimen komennolla ld -T.

    selata tekstiosioita. Se riippuu siitä, kuinka monta osiota mahtuu muistiin.

readelf -l hello_world.out antaa:

ELF -tiedostotyyppi on Exec (suoritettava tiedosto). x00000000006000D8 0x00000000006000D8 0x00000000 0000000D 0x000000000000000D RW 200000 Osasta segmenttiin kartoitus: Segmenttiosuudet... 00 .text 01 .data

ELF-otsikossa e_phoff , e_phnum ja e_phentsize kertoivat meille, että on olemassa 2 ohjelman otsikkoa, jotka alkavat 0x40:stä ja ovat kukin 0x38 tavua pitkiä, joten ne ovat:

00000040 01 00 00 00 05 00 00 00 00 00 00 00 00 00 00 00 |............| 00000050 00 00 40 00 00 00 00 00 00 00 40 00 00 00 00 00 |..@.......@.....| 00000060 d7 00 00 00 00 00 00 00 d7 00 00 00 00 00 00 00 |............| 00000070 00 00 20 00 00 00 00 00 |... ..... |

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

Ensimmäisen erittely:

  • 40 0: p_tyyppi = 01 00 00 00 = PT_LOAD: TODO. Luulen, että tämä tarkoittaa, että se ladataan muistiin. Muita tyyppejä ei välttämättä ole olemassa.
  • 40 4: p_flags = 05 00 00 00 = suoritus- ja lukuoikeudet, älä kirjoita TODO
  • 40 8: p_offset = 8x 00 TODO: mikä tämä on? Se näyttää siirtymiltä segmenttien alusta. Mutta tarkoittaako tämä sitä, että jotkin segmentit kietoutuvat toisiinsa? Voit leikkiä sillä hieman: gcc -Wl,-Ttext-segment=0x400030 hello_world.c
  • 50 0: p_vaddr = 00 00 40 00 00 00 00 00: virtuaalisen muistin aloitusosoite, johon tämä segmentti ladataan
  • 50 8: p_paddr = 00 00 40 00 00 00 00 00: fyysinen osoite ladattavaksi muistiin. Vain kysymyksiä järjestelmille, joissa ohjelma voi asettaa fyysisen osoitteen. Muuten, kuten System V -järjestelmissä, mitä tahansa voi tapahtua. Näyttää siltä, ​​​​että NASM vain kopioi p_vaddrr
  • 60 0: p_filesz = d7 00 00 00 00 00 00 00: TODO vs p_memsz
  • 60 8: p_memsz = d7 00 00 00 00 00 00 00: TODO
  • 70 0: p_align = 00 00 20 00 00 00 00 00: 0 tai 1 tarkoittaa, että kohdistamista ei tarvita TODO, mitä tämä tarkoittaa? muuten tarpeeton muiden kenttien kanssa

Toinen on samanlainen.

Osasta segmenttiin kartoitus:

Readelf-osio kertoo meille, että:

  • 0 - segmentti.teksti . Kyllä, joten se on suoritettava eikä kirjoitettava.
  • 1 - segmentti.tiedot.

Aiheeseen liittyvät julkaisut