Hvis du har installert på datamaskinen antivirusprogram Kan skann alle filer på datamaskinen din, så vel som hver fil individuelt. Du kan skanne hvilken som helst fil ved å høyreklikke på filen og velge riktig alternativ for å skanne filen for virus.

For eksempel, i denne figuren er det uthevet fil min-fil.elf, så må du høyreklikke på denne filen og velge alternativet i filmenyen "skann med AVG". Når du velger dette alternativet, vil AVG Antivirus åpne og skanne filen for virus.


Noen ganger kan det oppstå en feil som et resultat feil programvareinstallasjon, som kan skyldes et problem som oppstod under installasjonsprosessen. Dette kan forstyrre operativsystemet ditt koble ELF-filen til riktig applikasjonsprogramvare, påvirke den såkalte "filtypetilknytninger".

Noen ganger enkelt reinstallere Dolphin (emulator) kan løse problemet ved å koble ELF med Dolphin (emulator) på riktig måte. I andre tilfeller kan det oppstå problemer med filtilknytninger dårlig programvareprogrammering utvikler, og du må kanskje kontakte utvikleren for ytterligere hjelp.


Råd: Prøv å oppdatere Dolphin (emulator) til den nyeste versjonen for å sikre at du har de siste oppdateringene og oppdateringene.


Dette kan virke for åpenbart, men ofte Det kan hende at ELF-filen i seg selv forårsaker problemet. Hvis du mottok en fil via et e-postvedlegg eller lastet den ned fra et nettsted og nedlastingsprosessen ble avbrutt (for eksempel strømbrudd eller annen årsak), filen kan bli skadet. Hvis mulig, prøv å få en ny kopi av ELF-filen og prøv å åpne den igjen.


Forsiktig: En skadet fil kan forårsake skade på tidligere eller eksisterende skadelig programvare på PC-en din, så det er viktig å holde datamaskinen oppdatert med et oppdatert antivirus.


Hvis filen din er ELF relatert til maskinvaren på datamaskinen din for å åpne filen du kanskje trenger oppdatere enhetsdrivere knyttet til dette utstyret.

Dette problemet vanligvis assosiert med mediefiltyper, som er avhengig av vellykket åpning av maskinvaren inne i datamaskinen, f.eks. lydkort eller skjermkort. For eksempel, hvis du prøver å åpne en lydfil, men ikke kan åpne den, kan det hende du må oppdater drivere for lydkort.


Råd: Hvis du mottar en ELF-fil når du prøver å åpne .SYS-fil feilmelding, problemet kan nok være assosiert med ødelagte eller utdaterte enhetsdrivere som må oppdateres. Denne prosessen kan gjøres enklere ved å bruke driveroppdateringsprogramvare som DriverDoc.


Hvis trinnene ikke løser problemet og du fortsatt har problemer med å åpne ELF filer, kan dette skyldes mangel på tilgjengelige systemressurser. Noen versjoner av ELF-filer kan kreve en betydelig mengde ressurser (f.eks. minne/RAM, prosessorkraft) for å åpnes ordentlig på datamaskinen. Dette problemet er ganske vanlig hvis du bruker ganske gammel maskinvare og samtidig et mye nyere operativsystem.

Dette problemet kan oppstå når datamaskinen har problemer med å holde tritt med en oppgave fordi operativsystemet (og andre tjenester som kjører i bakgrunnen) kan bruker for mange ressurser til å åpne en ELF-fil. Prøv å lukke alle programmer på PC-en din før du åpner Nintendo Wii-spillfilen. Ved å frigjøre alle tilgjengelige ressurser på datamaskinen din vil du være i den beste posisjonen til å prøve å åpne filen ELF.


Hvis du fullførte alle trinnene beskrevet ovenfor og ELF-filen din fortsatt ikke åpnes, kan det hende du må kjøre utstyrsoppdatering. I de fleste tilfeller, selv når du bruker eldre versjoner av maskinvare, kan prosessorkraften fortsatt være mer enn tilstrekkelig for de fleste brukerapplikasjoner (med mindre du gjør mye CPU-intensivt arbeid, for eksempel 3D-gjengivelse, økonomisk/vitenskapelig modellering, eller intensivt multimediaarbeid). Dermed, det er sannsynlig at datamaskinen ikke har nok minne(ofte kalt "RAM" eller random access memory) for å utføre oppgaven med å åpne en fil.

Vi håper at vi har hjulpet deg med å løse problemet med ELF-filen. Hvis du ikke vet hvor du kan laste ned en applikasjon fra listen vår, klikk på lenken (dette er navnet på programmet) - Du finner mer detaljert informasjon om hvor du kan laste ned den sikre installasjonsversjonen av den nødvendige applikasjonen.

Et besøk på denne siden bør hjelpe deg å svare spesifikt på disse eller lignende spørsmål:

  • Hvordan åpne en fil med en ELF-utvidelse?
  • Hvordan konvertere en ELF-fil til et annet format?
  • Hva er filformatet ELF?
  • Hvilke programmer støtter ELF-filen?

Hvis du, etter å ha sett på materialet på denne siden, fortsatt ikke har mottatt et tilfredsstillende svar på noen av spørsmålene ovenfor, betyr dette at informasjonen som presenteres her om filen ELF er ufullstendig. Kontakt oss ved å bruke kontaktskjemaet og skriv hvilken informasjon du ikke fant.

Hva annet kan forårsake problemer?

Det kan være flere grunner til at du ikke kan åpne filen ELF (ikke bare mangelen på et tilsvarende program).
for det første- ELF-filen kan være feil koblet (inkompatibel) med applikasjonen som er installert for å støtte den. I dette tilfellet må du endre denne tilkoblingen selv. For å gjøre dette, høyreklikk på ELF-filen du vil redigere og klikk på alternativet "Å åpne med" og velg deretter programmet du installerte fra listen. Etter denne handlingen skal problemer med å åpne ELF-filen forsvinne helt.
for det andre- filen du vil åpne kan ganske enkelt være skadet. I dette tilfellet ville det være best å finne en ny versjon av den, eller laste den ned igjen fra samme kilde (kanskje av en eller annen grunn i forrige økt ble ikke nedlastingen av ELF-filen ferdig og den kunne ikke åpnes på riktig måte) .

Vil du hjelpe?

Hvis du har tilleggsopplysninger om filtypen ELF, vil vi sette stor pris hvis du deler dem med brukere av nettstedet vårt. Bruk skjemaet nedenfor og send oss ​​opplysninger som du har om filtypen ELF.

ELF-format

ELF-formatet har flere filtyper, som vi hittil har kalt forskjellig, for eksempel kjørbar fil eller objektfil. Imidlertid skiller ELF-standarden følgende typer:

1. Fil som skal flyttes(flyttbar fil) som lagrer instruksjoner og data som kan kobles til andre objektfiler. Resultatet av slik kobling kan være en kjørbar fil eller en delt objektfil.

2. Delt objektfil(delt objektfil) inneholder også instruksjoner og data, men kan brukes på to måter. I det første tilfellet kan den kobles til andre flyttbare filer og delte objektfiler, noe som resulterer i at en ny objektfil opprettes. I det andre tilfellet, når et program startes for kjøring, kan operativsystemet dynamisk koble det til programmets kjørbare fil, som et resultat av at et kjørbart bilde av programmet vil bli opprettet. I sistnevnte tilfelle snakker vi om delte biblioteker.

3. Kjørbar fil lagrer en fullstendig beskrivelse som lar systemet lage et bilde av prosessen. Den inneholder instruksjoner, data, beskrivelser av de nødvendige delte objektfilene og nødvendig symbolsk informasjon og feilsøkingsinformasjon.

I fig. 2.4 viser strukturen til den kjørbare filen, ved hjelp av hvilken operativsystemet kan lage et programbilde og starte programmet for kjøring.

Ris. 2.4. Struktur av en kjørbar fil i ELF-format

Overskriften har en fast plassering i filen. De resterende komponentene plasseres i henhold til informasjonen som er lagret i overskriften. Dermed inneholder overskriften en generell beskrivelse av filstrukturen, plasseringen av individuelle komponenter og deres størrelser.

Siden overskriften til en ELF-fil bestemmer strukturen, la oss se på den mer detaljert (tabell 2.4).

Tabell 2.3. ELF-overskriftsfelt

Felt Beskrivelse
e_ident En rekke byte, som hver definerer noen generelle kjennetegn ved filen: filformat (ELF), versjonsnummer, systemarkitektur (32-bit eller 64-bit), etc.
e_type Filtype, siden ELF-formatet støtter flere typer
e_maskin Arkitekturen til maskinvareplattformen som denne filen ble opprettet for. I tabellen 2.4 viser de mulige verdiene for dette feltet
e_versjon ELF-format versjonsnummer. Vanligvis definert som EV_CURRENC (gjeldende), som betyr siste versjon
e_entry Den virtuelle adressen som systemet vil overføre kontrollen til etter å ha lastet programmet (inngangspunkt)
e_phoff Plassering (forskyvning fra begynnelsen av filen) for programoverskriftstabellen
e_shoff Plassering av seksjonsoverskriftstabell
e_ehsize Topptekststørrelse
e_phentsize Størrelsen på hver programoverskrift
e_phnum Antall programoverskrifter
e_shentsize Størrelsen på hver segmentoverskrift (seksjon)
e_shnum Antall segmentoverskrifter (seksjoner)
e_shstrndx Plassering av segmentet som inneholder strengtabellen

Tabell 2.4. ELF-filoverskrift e_machine-feltverdier

Betydning Maskinvareplattform
EM_M32 AT&T WE 32100
EM_SPARC Sol 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+

Informasjonen i programoverskriftstabellen forteller kjernen hvordan man lager et prosessbilde fra segmenter. De fleste segmenter er kopiert (tilordnet) inn i minnet og representerer relevante segmenter av prosessen mens den utføres, for eksempel kode eller datasegmenter.

Hver programsegmentoverskrift beskriver ett segment og inneholder følgende informasjon:

Segmenttype og operativsystemhandlinger med dette segmentet

Segmentplassering i filen

Startadressen til segmentet i prosessens virtuelle minne

Segmentstørrelse i fil

Minnesegmentstørrelse

Segmenter tilgangsflagg (skriv, les, utfør)

Noen segmenter er av typen LOAD, som instruerer kjernen, når du starter programmet for kjøring, å lage datastrukturer som tilsvarer disse segmentene, kalt regioner, som definerer sammenhengende deler av en prosess virtuelle minne og deres tilknyttede attributter. Segmentet, hvis plassering i ELF-filen er angitt i den tilsvarende programoverskriften, vil bli kartlagt til det opprettede området, den virtuelle adressen til begynnelsen av dette er også angitt i programoverskriften. Segmenter av denne typen inkluderer for eksempel segmenter som inneholder programinstruksjoner (kode) og dets data. Hvis segmentstørrelsen er mindre enn regionstørrelsen, kan den ubrukte plassen fylles med nuller. Denne mekanismen brukes spesielt når du oppretter uinitialiserte prosessdata (BSS). Vi vil snakke mer om områder i kapittel 3.

INTERP-segmentet lagrer programtolken. Denne segmenttypen brukes for programmer som krever dynamisk kobling. Essensen av dynamisk kobling er at de individuelle komponentene i den kjørbare filen (delte objektfiler) kobles ikke sammen på kompileringsstadiet, men på stadiet for å starte programmet for kjøring. Navnet på filen som er editor for dynamisk lenke, er lagret i dette segmentet. Når et program startes for kjøring, lager kjernen et prosessbilde ved å bruke den spesifiserte lenkeredigereren. Dermed er det i utgangspunktet ikke kildeprogrammet som lastes inn i minnet, men editoren for dynamiske koblinger. I neste trinn jobber redigeringsprogrammet for dynamiske koblinger med UNIX-kjernen for å lage et fullstendig bilde av den kjørbare filen. Den dynamiske editoren laster de nødvendige delte objektfilene, hvis navn er lagret i separate segmenter av den kjørbare kildefilen, og utfører den nødvendige plasseringen og koblingen. Til slutt overføres kontrollen til det opprinnelige programmet.

Til slutt ender filen med en overskriftstabell seksjoner eller seksjoner(seksjon). Seksjoner (seksjoner) definerer deler av en fil som brukes til å koble til andre moduler under kompilering eller under dynamisk kobling. Følgelig inneholder overskriftene all nødvendig informasjon for å beskrive disse avsnittene. Som regel inneholder seksjoner mer detaljert informasjon om segmenter. Et kodesegment kan for eksempel bestå av flere seksjoner, for eksempel en hashtabell for lagring av indekser av symboler som brukes i programmet, en seksjon av programmets initialiseringskode, en koblingstabell brukt av den dynamiske editoren, og en seksjon som inneholder den faktiske programinstruksjoner.

Vi kommer tilbake til ELF-formatet i kapittel 3 når vi diskuterer organiseringen av prosessvirtuelt minne, men foreløpig går vi videre til det neste vanlige formatet, COFF.

Fra boken The Art of Programming for Unix forfatter Raymond Eric Stephen

Fra boken Self-instruction manual for arbeid på en datamaskin forfatter Kolisnichenko Denis Nikolaevich

Fra boken Abstract, coursework, diplom on a computer forfatter Balovsyak Nadezhda Vasilievna

5.2.6. Windows INI-format Mange Microsoft Windows-programmer bruker et tekstdataformat som ligner det som er vist i eksempel 5.6. Dette eksemplet knytter valgfrie ressurser som heter konto, katalog, numeric_id og utvikler med prosjekter kalt python, sng, fetchmail og py-howto. I opptak

Fra boken Den nyeste selvinstruksjonsmanualen for arbeid på datamaskin forfatter Beluntsov Valery

14.5.3. Celleformat Formatet angir hvordan cellens verdi skal vises. Formatet er nært knyttet til cellens datatype. Du setter typen selv. Hvis du skrev inn et tall, er det en numerisk datatype. Excel prøver selv å bestemme formatet basert på datatypen. For eksempel, hvis du skrev inn tekst, da

Fra boken The Art of Programming for Unix forfatter Raymond Eric Stephen

PDF-format PDF står for Portable Document Format. Dette formatet ble laget spesielt for å eliminere problemer med å vise informasjon i filer. Fordelen er at for det første vil et dokument lagret i PDF-format være det samme

Fra boken TCP/IP Architecture, Protocols, Implementation (inkludert IP versjon 6 og IP Security) av Faith Sydney M

Filformat Når en bruker begynner å jobbe med en fil, må systemet vite i hvilket format den er skrevet og med hvilket program den må åpnes. For eksempel, hvis en fil inneholder ren tekst, kan den leses i et hvilket som helst tekstprogram

Fra boken Yandex for alle forfatter Abramzon M. G.

5.2.2. RFC 822-format RFC 822-metaformatet er avledet fra tekstformatet til Internett-e-postmeldinger. RFC 822 er den viktigste Internett-RFC-standarden som beskriver dette formatet (senere erstattet av RFC 2822). MIME-format (Multipurpose Internet Media Extension).

Fra boken Macromedia Flash Professional 8. Graphics and Animation forfatter Dronov V.A.

5.2.3. Cookie-Jar-format Cookie-jar-formatet brukes av fortune(1) for sin egen database med tilfeldige sitater. Den passer for innlegg som bare er blokker med ustrukturert tekst. Symbolet brukes som en postseparator i dette formatet

Fra boken Computer Sound Processing forfatter Zagumennov Alexander Petrovich

5.2.4. Record-jar-formatet Cookie-jar-format-postseparatorene fungerer godt med RFC 822-metaforformatet for poster, og danner et format som kalles "record-jar" i denne boken. Noen ganger kreves et tekstformat som støtter flere oppføringer med et annet sett med eksplisitte navn

Fra boken UNIX Operating System forfatter Robachevsky Andrey M.

5.2.6. Windows INI-format Mange Microsoft Windows-programmer bruker et tekstdataformat som ligner det som er vist i eksempel 5.6. Dette eksemplet knytter valgfrie ressurser som heter konto, katalog, numeric_id og utvikler med prosjekter kalt python, sng, fetchmail og py-howto. I opptak

Fra boken Office Computer for Women forfatter Pasternak Evgeniya

19.5 Generalisert URL-format For å oppsummere det ovenstående, merker vi at:? URL-en begynner med tilgangsprotokollen som brukes.? For alle applikasjoner unntatt elektroniske nyheter og e-post, følges følgende av skilletegnet: //.? Deretter er vertsnavnet til serveren spesifisert.? Endelig

Fra forfatterens bok

3.3.1. RSS-format Du kan lese nettsidenyheter på forskjellige måter. Den enkleste måten er å besøke siden fra tid til annen og se på nye meldinger. Du kan installere et program som kobles til en nyhetskanal og selv mottar overskrifter eller nyhetsoppsummeringer, iht

Fra forfatterens bok

MP3-format MP3-formatet ble laget for å distribuere musikkfiler komprimert med MPEG 1 nivå 3-kodeken. Foreløpig er det det mest populære formatet for distribusjon av musikk over Internett, og ikke bare. Støttes av absolutt alle lydopptaks- og prosesseringsprogrammer, for

Fra forfatterens bok

MP3-format En lydkomprimeringsmetode, samt et komprimert lydfilformat foreslått av den internasjonale organisasjonen MPEG (Moving Pictures Experts Group), basert på perseptuell lydkoding. Arbeid med å lage effektive kodealgoritmer

Fra forfatterens bok

ELF-format ELF-formatet har flere filtyper, som vi så langt har kalt med forskjellige navn, for eksempel kjørbar fil eller objektfil. Imidlertid skiller ELF-standarden følgende typer:1. En flyttbar fil som lagrer instruksjoner og data som kan være

Fra forfatterens bok

Tallformat Vi kom endelig til tallformatet. Jeg har allerede nevnt det mer enn en gang, nå skal jeg dele det ned (selv om du kanskje allerede forstår den generelle betydningen Tall i Excel kan vises i forskjellige formater). I denne delen skal vi snakke om hvilke tallformater som finnes og hvordan

I denne anmeldelsen vil vi bare snakke om 32-bitsversjonen av dette formatet, fordi vi ikke trenger 64-bitsversjonen ennå.

Enhver ELF-fil (inkludert objektmoduler i dette formatet) består av følgende deler:

  • ELF filoverskrift;
  • Tabell over programseksjoner (kan være fraværende i objektmoduler);
  • ELF-filseksjoner;
  • Seksjonstabell (finnes kanskje ikke i den utførende modulen);
  • Av ytelsesgrunner bruker ikke ELF-format bitfelt. Og alle strukturer er vanligvis 4 byte justert.

La oss nå se på typene som brukes i ELF-filoverskrifter:

La oss nå se på filoverskriften:

#define EI_NIDENT 16 struct elf32_hdr ( usignert char e_ident; Elf32_Half e_type; Elf32_Half e_machine; Elf32_Word e_version; Elf32_Addr e_entry; /* Entry point */ Elf3p_Wfhoff; ord e_fla gs Elf32_Half e_ehsize;

e_ident-matrisen inneholder informasjon om systemet og består av flere underfelt.

Struct (usignert char ei_magic; unsigned char ei_class; unsigned char ei_data; unsigned char ei_version; unsigned char ei_pad;)

  • ei_magic - konstant verdi for alle ELF-filer, lik (0x7f, "E", "L", "F")
  • ei_class - ELF-filklasse (1 - 32 biter, 2 - 64 biter som vi ikke vurderer)
  • ei_data - bestemmer byte-rekkefølgen for denne filen (denne rekkefølgen avhenger av plattformen og kan være fremover (LSB eller 1) eller revers (MSB eller 2)) For Intel-prosessorer er kun verdien 1 tillatt.
  • ei_version er et ganske ubrukelig felt, og hvis det ikke er lik 1 (EV_CURRENT) så anses filen som feil.

Operativsystemer lagrer sin identifikasjonsinformasjon i ei_pad-feltet. Dette feltet kan være tomt. Det spiller ingen rolle for oss heller.

e_type header-feltet kan inneholde flere verdier, for kjørbare filer bør det være ET_EXEC lik 2

e_machine - bestemmer prosessoren som denne kjørbare filen kan kjøres på (for oss er den akseptable verdien EM_386 3)

Feltet e_version tilsvarer ei_version-feltet fra overskriften.

Feltet e_entry definerer startadressen til programmet, som plasseres i eip før programmet starter.

Feltet e_phoff spesifiserer forskyvningen fra begynnelsen av filen der programdeltabellen som brukes til å laste programmer inn i minnet, befinner seg.

Jeg vil ikke liste opp formålet med alle feltene som er nødvendige for lasting. Jeg skal bare beskrive to til.

Feltet e_phentsize spesifiserer størrelsen på oppføringen i programdeltabellen.

Og feltet e_phnum bestemmer antall oppføringer i programdeltabellen.

Seksjonstabellen (ikke programseksjoner) brukes til å koble programmer. vi vil ikke vurdere det. Vi vil heller ikke vurdere dynamisk koblede moduler. Dette emnet er ganske komplekst og ikke egnet for et første bekjentskap. :)

Nå om programdelene. Oppføringsformatet for programdeltabellen er som følger:

Struct elf32_phdr ( Elf32_Word p_type; Elf32_Off p_offset; Elf32_Addr p_vaddr; Elf32_Addr p_paddr; Elf32_Word p_filesz; Elf32_Word p_memsz; Elf32_Word p_32align_W; p_32align_W);

Lær mer om felt.

  • p_type - bestemmer typen programdel. Det kan ta flere verdier, men vi er kun interessert i én. PT_LOAD(1). Hvis delen er av denne typen, er den ment å lastes inn i minnet.
  • p_offset - bestemmer offset i filen som denne delen begynner fra.
  • p_vaddr - definerer den virtuelle adressen som denne delen skal lastes inn i minnet.
  • p_paddr - definerer den fysiske adressen som denne delen skal lastes på. Dette feltet er valgfritt og gir bare mening på enkelte plattformer.
  • p_filesz - bestemmer størrelsen på delen i filen.
  • p_memsz - bestemmer størrelsen på minneseksjonen. Denne verdien kan være større enn den forrige. P_flag-feltet bestemmer typen tilgang til seksjoner i minnet. Noen avsnitt kan utføres, noen kan skrives ned. Alle er tilgjengelige for lesing i eksisterende systemer.

Laster inn ELF-format.

Vi fant ut tittelen litt. Nå vil jeg gi en algoritme for å laste en binær fil i ELF-format. Algoritmen er skjematisk og bør ikke betraktes som et arbeidsprogram.

Int LoadELF (usignert char *bin) ( struct elf32_hdr *EH = (struct elf32_hdr *)bin; struct elf32_phdr *EPH; if (EH->e_ident != 0x7f || // Control MAGIC EH->e_ident != "E" ||. EH->e_ident != "L" || EH->e_ident != "F" ||. ELFCLASS32 || e_ident != EV_CURRENT ||. // versjon EH->e_type != ET_EXEC || // type EH->e_machine != EM_386 || // plattform EH->e_version != EV_CURRENT) return ELF_WRONG; >p_offset, EPH->p_filesz = (struct elf32_phdr *)((usignert char *)EPH + EH->e_phentsize)); )

Seriøst, det er verdt å analysere EPH->p_flags-feltene og angi tilgangsrettigheter til de aktuelle sidene, og ganske enkelt kopiering vil ikke fungere her, men dette er ikke lenger knyttet til formatet, men til minneallokering. Derfor skal vi ikke snakke om dette nå.

PE-format.

På mange måter ligner det ELF-formatet, og ikke overraskende bør det også ha seksjoner tilgjengelig for nedlasting.

Som alt annet hos Microsoft:) er PE-formatet basert på EXE-formatet. Filstrukturen er som følger:

  • 00h - EXE-header (jeg vil ikke se på den, den er like gammel som Dos. :)
  • 20h - OEM-header (ingenting vesentlig i den);
  • 3сh - forskyvning av den virkelige PE-headeren i filen (dword).
  • stubb bevegelse tabellen;
  • stubbe;
  • PE header;
  • objekt tabellen;
  • filobjekter;

stub er et program som kjører i ekte modus og utfører noen foreløpige handlinger. Det kan være fraværende, men noen ganger kan det være nødvendig.

Vi er interessert i noe litt annerledes, PE-headeren.

Strukturen er slik:

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_flags_size; unsigned long pe_flags_ ; usignert lang pe_idata_size ; unsigned long pe_udata_size; unsigned long pe_obj_align;

Det er mye ting der. Det er nok å si at størrelsen på denne overskriften er 248 byte.

Og det viktigste er at de fleste av disse feltene ikke brukes. (Hvem bygger slik?) Nei, de har selvfølgelig en hensikt som er ganske kjent, men testprogrammet mitt inneholder for eksempel nuller i feltene pe_code_base, pe_code_size osv., men det fungerer fint. Konklusjonen er at filen lastes inn basert på objekttabellen. Det er det vi skal snakke om.

Objekttabellen følger umiddelbart etter PE-overskriften. Oppføringene i denne tabellen har følgende format:

Struct pe_ohdr ( usignert char o_name; unsigned char o_vsize; unsigned long o_vaddr; unsigned long o_psize; unsigned long o_poff; unsigned char o_reserved; unsigned long o_flags; );

  • o_name - navnet på seksjonen, det er helt likegyldig til å laste;
  • o_vsize - størrelsen på minneseksjonen;
  • o_vaddr - minneadresse i forhold til ImageBase;
  • o_psize - seksjonsstørrelse i filen;
  • o_poff - seksjonsforskyvning i filen;
  • o_flagg - seksjonsflagg;

Det er verdt å se nærmere på flaggene.

  • 00000004h - brukes til kode med 16 bits forskyvninger
  • 00000020h - kodeseksjon
  • 00000040h - initialisert dataseksjon
  • 00000080h - uinitialisert dataseksjon
  • 00000200h - kommentarer eller annen type informasjon
  • 00000400h - overleggsseksjon
  • 00000800h - vil ikke være en del av programbildet
  • 00001000h - generelle data
  • 00500000h - standardjustering med mindre annet er spesifisert
  • 02000000h - kan lastes ut fra minnet
  • 04000000h - ikke bufret
  • 08000000h - ikke sidesøkt
  • 10000000t - delt
  • 20000000t - gjennomførbart
  • 40000000h - kan leses
  • 80000000h - du kan skrive

Igjen, jeg vil ikke snakke om delte og overleggsseksjoner, vi er interessert i kode, data og tilgangsrettigheter.

Generelt er denne informasjonen allerede nok til å laste ned den binære filen.

Laster PE-format.

int LoadPE (usignert char *bin) ( struct elf32_hdr *PH = (struct pe_hdr *) (bin + *((usigned long *)&bin)); // Kombinasjonen er selvfølgelig ikke klar... bare ta dword ved offset 0x3c / / Og beregn PE-headeradressen i filbildestrukturen elf32_phdr *POH; if (PH == NULL || // Kontroller PH->pe_sign-pekeren != 0x4550 || // PE-signatur ("P" , "E", 0, 0) PH->pe_cputype != 0x14c || i386 (PH->pe_flags & 2) == 0) // fil kan ikke kjøres. >pe_obj_num--) ( if ((POH->p_flags & 0x60) != 0) // enten kode eller initialisert dataminne (PE->pe_image_base + POH->o_vaddr, bin + POH- >o_poff, POH->o_psize ); POH = (struct pe_ohdr *)((usigned char *)POH + sizeof (struct pe_ohdr));

Dette er igjen ikke et ferdig program, men en lastealgoritme.

Og igjen, mange punkter er ikke dekket, da de går utover omfanget av emnet.

Men nå er det verdt å snakke litt om de eksisterende systemfunksjonene.

Systemfunksjoner.

Til tross for fleksibiliteten til beskyttelsesverktøyene som er tilgjengelige i prosessorer (beskyttelse på nivået av deskriptortabeller, beskyttelse på segmentnivå, beskyttelse på sidenivå), i eksisterende systemer (både Windows og Unix) brukes kun sidebeskyttelse fullt ut, som, selv om den kan beskytte kode mot å bli skrevet, men den kan ikke beskytte data mot kjøring. (Kanskje dette er årsaken til overfloden av systemsårbarheter?)

Alle segmenter adresseres fra lineær adresse null og strekker seg til slutten av lineært minne. Prosessavgrensning gjøres kun på nivå med sidetabeller.

I denne forbindelse er alle moduler koblet ikke fra startadresser, men med en tilstrekkelig stor forskyvning i segmentet. I Windows er basisadressen i segmentet 0x400000, i Unix (Linux eller FreeBSD) - 0x8048000.

Noen funksjoner er også relatert til sideorganiseringen av minnet.

ELF-filer er koblet på en slik måte at grensene og størrelsene på seksjoner faller innenfor 4 kilobyte blokker av filen.

Og i PE-formatet, til tross for at formatet i seg selv lar deg justere seksjoner på 512 byte, blir justering av seksjoner brukt ved 4k, en mindre justering i Windows anses ikke som riktig.

Standard utviklingsverktøy kompilerer programmet til en ELF-fil (Executable and Linkable Format) med muligheten til å inkludere feilsøkingsinformasjon. Formatspesifikasjonen kan leses. I tillegg har hver arkitektur sine egne funksjoner, for eksempel ARM-funksjoner. La oss ta en kort titt på dette formatet.
En kjørbar fil i ELF-format består av følgende deler:
1. Overskrift (ELF-overskrift)
Inneholder generell informasjon om filen og dens hovedegenskaper.
2. Programoverskriftstabell
Dette er en tabell over korrespondanse mellom filseksjoner og minnesegmenter, den forteller bootloaderen hvilket minneområde den skal skrive hver seksjon til.
3. Seksjoner
Seksjoner inneholder all informasjon i filen (program, data, feilsøkingsinformasjon osv.)
Hver seksjon har en type, navn og andre parametere. ".text"-delen lagrer vanligvis koden, ".symtab" - en tabell med programsymboler (navn på filer, prosedyrer og variabler), ".strtab" - en tabell med strenger, seksjoner med prefikset ".debug_" - feilsøkingsinformasjon osv. .d. I tillegg må filen ha en tom seksjon med indeks 0.
4. Seksjonsoverskriftstabell
Dette er en tabell som inneholder en rekke seksjonsoverskrifter.
Formatet er omtalt mer detaljert i avsnittet Opprette ELF.

DVERG Oversikt

DWARF er et standardisert feilsøkingsinformasjonsformat. Standarden kan lastes ned fra den offisielle nettsiden. Det er også en flott kort oversikt over formatet: Introduksjon til DWARF Debugging Format (Michael J. Eager).
Hvorfor er feilsøkingsinformasjon nødvendig? Det tillater:
  • angi bruddpunkter ikke til en fysisk adresse, men til linjenummeret i kildekodefilen eller til navnet på funksjonen
  • vise og endre verdiene til globale og lokale variabler, samt funksjonsparametere
  • vise anropsstakken (tilbakesporing)
  • kjør programmet trinn for trinn, ikke i henhold til en monteringsinstruksjon, men i henhold til kildekodelinjer
Denne informasjonen lagres i en trestruktur. Hver trenode har en forelder, kan ha barn og kalles en DIE (Debugging Information Entry). Hver node har sin egen tag (type) og en liste over attributter (egenskaper) som beskriver noden. Attributter kan inneholde hva som helst, for eksempel data eller lenker til andre noder. I tillegg er det informasjon lagret utenfor treet.
Noder er delt inn i to hovedtyper: noder som beskriver data og noder som beskriver kode.
Noder som beskriver data:
  1. Datatyper:
    • Basedatatyper (node ​​med type DW_TAG_base_type), for eksempel int-typen i C.
    • Sammensatte datatyper (pekere osv.)
    • Matriser
    • Strukturer, klasser, fagforeninger, grensesnitt
  2. Dataobjekter:
    • konstanter
    • funksjonsparametere
    • variabler
    • etc.
Hvert dataobjekt har et attributt DW_AT_location, som spesifiserer hvordan adressen der dataene befinner seg beregnes. For eksempel kan en variabel ha en fast adresse, være i et register eller på stabelen, eller være medlem av en klasse eller et objekt. Denne adressen kan beregnes på en ganske kompleks måte, så standarden gir såkalte Location Expressions, som kan inneholde en sekvens av operatører av en spesiell intern stabelmaskin.
Noder som beskriver koden:
  1. Prosedyrer (funksjoner) - noder med taggen DW_TAG_underprogram. Descendant noder kan inneholde beskrivelser av variabler - funksjonsparametere og lokale funksjonsvariabler.
  2. Kompileringsenhet. Inneholder programinformasjon og er overordnet for alle andre noder.
Informasjonen beskrevet ovenfor finner du i delene ".debug_info" og ".debug_abbrev".
Annen informasjon:
  • Informasjon om linjenumre (seksjonen ".debug_line")
  • Informasjon om makroer (seksjon ".debug_macinfo")
  • Call Frame Information (seksjon ".debug_frame")

Opprettelse av ELF

Vi vil lage filer i EFL-format ved å bruke libelf-biblioteket fra elfutils-pakken. Det er en god artikkel på Internett om bruk av libelf - LibELF by Example (dessverre beskriver den opprettingen av filer veldig kort) samt dokumentasjon.
Oppretting av en fil består av flere trinn:
  1. Initialisering av injurier
  2. Opprette en filoverskrift (ELF-overskrift)
  3. Opprette en programoverskriftstabell
  4. Opprette seksjoner
  5. Skriv en fil
La oss se nærmere på stadiene
Initialisering av injurier
Først må du kalle opp elf_version(EV_CURRENT) funksjonen og sjekke resultatet. Hvis den er lik EV_NONE, har det oppstått en feil og ytterligere handlinger kan ikke utføres. Deretter må vi lage filen vi trenger på disk, hente beskrivelsen og sende den til elf_begin-funksjonen:
Elf * elf_begin(int fd, Elf_Cmd cmd, Elf *elf)
  • fd - beskrivelse av den nylig åpnede filen
  • cmd - modus (ELF_C_READ for lesing av informasjon, ELF_C_WRITE for skriving eller ELF_C_RDWR for lesing/skriving), den må samsvare med åpen filmodus (ELF_C_WRITE i vårt tilfelle)
  • elf - bare nødvendig for å jobbe med arkivfiler (.a), i vårt tilfelle må du passere 0
Funksjonen returnerer en peker til det opprettede håndtaket, som vil bli brukt i alle libelf-funksjoner, 0 returneres ved feil.
Opprette en tittel
En ny filoverskrift opprettes av elf32_newehdr-funksjonen:
Elf32_Ehdr * elf32_newehdr(Elf *elf);
  • elf - håndtak returnert av elf_begin-funksjonen
Returnerer 0 ved feil eller en peker til strukturen - overskriften til ELF-filen:
nr Halv e_ehs ize Elf32_Halv e_phentsize;

Noen av feltene er fylt ut på en standard måte, noen må vi fylle ut:

  • e_ident - identifikasjonsbyte-array, har følgende indekser:
    • EI_MAG0, EI_MAG1, EI_MAG2, EI_MAG3 - disse 4 bytene må inneholde tegnene 0x7f,"ELF", som elf32_newehdr-funksjonen allerede har gjort for oss
    • EI_DATA - indikerer typen datakoding i filen: ELFDATA2LSB eller ELFDATA2MSB. Du må installere ELFDATA2LSB slik: e_ident = ELFDATA2LSB
    • EI_VERSION - filoverskriftsversjon, allerede satt for oss
    • EI_PAD - ikke rør
  • e_type - filtype, kan være ET_NONE - ingen type, ET_REL - flyttbar fil, ET_EXEC - kjørbar fil, ET_DYN - delt objektfil, etc. Vi må sette filtypen til ET_EXEC
  • e_machine - arkitekturen som kreves for denne filen, for eksempel EM_386 - for Intel-arkitekturen, for ARM må vi skrive EM_ARM (40) her - se ELF for ARM-arkitekturen
  • e_version - filversjon, må settes til EV_CURRENT
  • e_entry - adresse for inngangspunkt, ikke nødvendig for oss
  • e_phoff - offset i programoverskriftsfilen, e_shoff - offset av seksjonsoverskriften, ikke fyll ut
  • e_flags - prosessorspesifikke flagg, for vår arkitektur (Cortex-M3) bør settes til 0x05000000 (ABI versjon 5)
  • e_ehsize, e_phentsize, e_phnum, e_shentsize, e_shnum - ikke rør
  • e_shstrndx - inneholder nummeret på seksjonen der tabellen med rader med seksjonsoverskrifter er plassert. Siden vi ikke har noen seksjoner ennå, vil vi angi dette nummeret senere
Opprette en programoverskrift
Som allerede nevnt, er Program Header Table en tabell over korrespondanse mellom filseksjoner og minnesegmenter, som forteller bootloaderen hvor hver seksjon skal skrives. Tittelen er opprettet ved hjelp av elf32_newphdr-funksjonen:
Elf32_Phdr * elf32_newphdr(Elf *elf, størrelse_t telling);
  • alven er håndtaket vårt
  • count - antall tabellelementer som skal opprettes. Siden vi bare vil ha én seksjon (med programkode), vil antallet være lik 1.
Returnerer 0 ved feil eller en peker til programoverskriften.
Hvert element i overskriftstabellen er beskrevet av følgende struktur:
TYPEDEF STRUCT (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 - segment (seksjon) type, her må vi spesifisere PT_LOAD - laste segment
  • p_offset - forskyvninger i filen der dataene til delen som skal lastes inn i minnet begynner. For oss er dette .text-delen, som vil bli plassert umiddelbart etter filoverskriften og programoverskriften, vi kan beregne offset som summen av lengdene til disse overskriftene. Lengden av enhver type kan fås ved å bruke elf32_fsize-funksjonen:
    size_t elf32_fsize(Elf_Type type, size_t count, unsigned int version); type - her konstanten ELF_T_xxx, vi trenger størrelsene ELF_T_EHDR og ELF_T_PHDR; count - antall elementer av ønsket type, versjon - må settes til EV_CURRENT
  • p_vaddr, p_paddr - virtuell og fysisk adresse som innholdet i seksjonen skal lastes til. Siden vi ikke har virtuelle adresser, setter vi den lik den fysiske, i det enkleste tilfellet - 0, fordi det er her programmet vårt skal lastes.
  • p_filesz, p_memsz - seksjonsstørrelse i fil og minne. Vi har de samme, men siden det ikke er noen seksjon med programkoden ennå, vil vi installere dem senere
  • p_flags - tillatelser for det lastede minnesegmentet. Kan være PF_R - lese, PF_W - skrive, PF_X - utføre eller en kombinasjon av dem. Sett p_flags til PF_R + PF_X
  • p_align - segmentjustering, vi har 4
Opprette seksjoner
Etter å ha opprettet overskriftene, kan du begynne å lage seksjoner. En tom seksjon opprettes ved å bruke elf_newscn-funksjonen:
Elf_Scn * elf_newscn(Elf *elf);
  • elf - håndtaket returnert tidligere av elf_begin-funksjonen
Funksjonen returnerer en peker til seksjonen eller 0 ved feil.
Etter å ha opprettet en seksjon, må du fylle ut seksjonsoverskriften og opprette en seksjonsdatabeskrivelse.
Vi kan få en peker til seksjonsoverskriften ved å bruke elf32_getshdr-funksjonen:
Elf32_Shdr * elf32_getshdr(Elf_Scn *scn);
  • scn er en peker til delen vi mottok fra elf_newscn-funksjonen.
Seksjonsoverskriften ser slik ut:
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_Word; Elf32_Word sh_link; 3_2_Word; _Word sh_entsize; ) Elf32_Shdr;
  • sh_name - seksjonsnavn - forskyvning i strengtabellen for seksjonsoverskrifter (section.shstrtab) - se "Stringtabeller" nedenfor
  • sh_type - seksjonsinnholdstype, for en seksjon med programkode må du sette SHT_PROGBITS, for seksjoner med en strengtabell - SHT_STRTAB, for en symboltabell - SHT_SYMTAB
  • sh_flags er seksjonsflagg som kan kombineres, og som vi bare trenger tre av:
    • SHF_ALLOC - betyr at seksjonen vil bli lastet inn i minnet
    • SHF_EXECINSTR - delen inneholder kjørbar kode
    • SHF_STRINGS - delen inneholder en tabell med strenger
    Følgelig, for .tekst-delen med programmet må du sette flaggene SHF_ALLOC + SHF_EXECINSTR
  • sh_addr - adresse hvor delen vil bli lastet inn i minnet
  • sh_offset - forskyvning av delen i filen - ikke rør den, biblioteket vil installere den for oss
  • sh_size - seksjonsstørrelse - ikke rør
  • sh_link - inneholder nummeret til den koblede delen det er nødvendig for å koble delen med dens tilsvarende radtabell (se nedenfor)
  • sh_info - tilleggsinformasjon avhengig av seksjonstype, satt til 0
  • sh_addraalign - adressejustering, ikke berør
  • sh_entsize - hvis en seksjon består av flere elementer av samme lengde, indikerer lengden på et slikt element, ikke berør
Etter å ha fylt ut overskriften, må du lage en seksjonsdatabeskrivelse med elf_newdata-funksjonen:
Elf_Data * elf_newdata(Elf_Scn *scn);
  • scn er den nylig mottatte pekeren til den nye delen.
Funksjonen returnerer 0 ved feil, eller en peker til Elf_Data-strukturen som må fylles:
typedef struct ( void* d_buf; Elf_Type d_type; size_t d_size; off_t d_off; size_t d_align; unsigned d_version; ) Elf_Data;
  • d_buf - peker til dataene som skal skrives til seksjonen
  • d_type - datatype, ELF_T_BYTE passer for oss overalt
  • d_size - datastørrelse
  • d_off - offset i seksjon, satt til 0
  • d_align - justering, kan settes til 1 - ingen justering
  • d_version - versjon, må settes til EV_CURRENT
Spesielle seksjoner
For våre formål må vi opprette minimumskravene med seksjoner:
  • .tekst - seksjon med programkode
  • .symtab - filsymboltabell
  • .strtab er en strengtabell som inneholder navn på symboler fra .symtab-delen, siden sistnevnte ikke lagrer selve navnene, men deres indekser
  • .shstrtab - strengtabell som inneholder seksjonsnavn
Alle seksjoner opprettes som beskrevet i forrige seksjon, men hver spesialseksjon har sine egne egenskaper.
Seksjon.tekst
Denne delen inneholder kjørbar kode, så du må sette sh_type til SHT_PROGBITS, sh_flags til SHF_EXECINSTR + SHF_ALLOC, sh_addr til adressen der denne koden skal lastes inn
Section.symtab
Avsnittet inneholder en beskrivelse av alle symboler (funksjoner) til programmet og filene de ble beskrevet i. Den består av følgende elementer, hver 16 byte lang:
typedef struct ( Elf32_Word st_name; Elf32_Addr st_verdi; Elf32_Word st_size; usignert char st_info; usignert char st_other; Elf32_Half st_shndx; ) Elf32_Sym;
  • st_name - symbolnavn (indeks i streng table.strtab)
  • st_value - verdi (oppføringsadresse for en funksjon eller 0 for en fil). Siden Cortex-M3 har et Thumb-2 instruksjonssett, må denne adressen være oddetall (reell adresse + 1)
  • st_size - funksjonskodelengde (0 for fil)
  • st_info - symboltype og dens omfang. Det er en makro for å bestemme verdien av dette feltet
    #define ELF32_ST_INFO(b,t) (((b)<<4)+((t)&0xf))
    der b er omfanget og t er tegntypen
    Omfanget kan være STB_LOCAL (symbol ikke synlig fra andre objektfiler) eller STB_GLOBAL (synlig). For å forenkle ting bruker vi STB_GLOBAL.
    Symboltype - STT_FUNC for funksjon, STT_FILE for fil
  • st_other - satt til 0
  • st_shndx - indeks for seksjonen som symbolet er definert for (seksjon index.text), eller SHN_ABS for filen.
    Seksjonsindeksen fra sin scn-beskrivelse kan bestemmes ved å bruke elf_ndxscn:
    size_t elf_ndxscn(Elf_Scn *scn);

Denne seksjonen er opprettet på vanlig måte, bare sh_type må settes til SHT_SYMTAB, og seksjonen index.strtab skal skrives til sh_link-feltet, slik at disse seksjonene blir koblet.
Section.strtab
Denne delen inneholder navnene på alle symbolene fra .symtab-delen. Laget som en vanlig seksjon, men sh_type må settes til SHT_STRTAB, sh_flags til SHF_STRINGS, så denne seksjonen blir en strengtabell.
Data for en seksjon kan samles inn ved å føre gjennom kildeteksten inn i en matrise, hvor pekeren så skrives til seksjonsdatabeskrivelsen (d_buf).
Section.shstrtab
En seksjon er en tabell med strenger som inneholder overskriftene til alle delene av filen, inkludert sin egen overskrift. Den er opprettet på samme måte som .strtab-delen. Etter opprettelsen må indeksen skrives i e_shstrndx-feltet i filoverskriften.
Strengebord
Strengtabeller inneholder påfølgende rader som slutter med en nullbyte, den første byten i denne tabellen må også være 0. Indeksen til en rad i tabellen er rett og slett forskyvningen i byte fra begynnelsen av tabellen, så den første raden "navn" har indeks 1, neste rad "var" har indeks 6.
Indeks 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 \0 n a m e \0 v a r \0
Skriv en fil
Så overskriftene og seksjonene er allerede dannet, nå må de skrives til en fil og fullføre arbeidet med injurier. Opptaket utføres av elf_update-funksjonen:
off_t elf_update(Elf *elf, Elf_Cmd cmd);
  • alv - håndtak
  • cmd - kommando, må være lik ELF_C_WRITE for å skrive.
Funksjonen returnerer -1 ved feil. Feilteksten kan fås ved å kalle elf_errmsg(-1) funksjonen, som vil returnere en peker til linjen med feilen.
Vi avslutter arbeidet med biblioteket med elf_end-funksjonen, som vi sender vår deskriptor til. Alt som gjenstår er å lukke den tidligere åpnede filen.
Den genererte filen vår inneholder imidlertid ingen feilsøkingsinformasjon, som vi vil legge til i neste avsnitt.

Opprettelse av DVERG

Vi vil lage feilsøkingsinformasjon ved hjelp av biblioteket, som kommer med en pdf-fil med dokumentasjon (libdwarf2p.1.pdf - A Producer Library Interface to DWARF).
Oppretting av feilsøkingsinformasjon består av følgende trinn:
  1. Opprette noder (DIE - Debugging Information Entry)
  2. Opprette nodeattributter
  3. Opprette datatyper
  4. Opprette prosedyrer (funksjoner)
La oss se nærmere på stadiene
Initialiserer libdwarf-produsent
Vi vil lage feilsøkingsinformasjon på kompileringstidspunktet samtidig som vi oppretter symbolene i .symtab-delen, så biblioteket må initialiseres etter at libelf er initialisert, ELF-hodet og programhodet er opprettet, og før seksjonene opprettes.
For initialisering vil vi bruke funksjonen dwarf_producer_init_c. Biblioteket har flere initialiseringsfunksjoner (dwarf_producer_init, dwarf_producer_init_b), som er forskjellige i noen nyanser beskrevet i dokumentasjonen. I prinsippet kan du bruke hvilken som helst av dem.

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

  • flagg - en kombinasjon av "eller" av flere konstanter som bestemmer noen parametere, for eksempel informasjonsbitdybde, bytesekvens (little-endian, big-endian), flytteformat, som vi definitivt trenger DW_DLC_WRITE og DW_DLC_SYMBOLIC_RELOCATIONS
  • func er en tilbakeringingsfunksjon som vil bli kalt når du oppretter ELF-seksjoner med feilsøkingsinformasjon. For mer informasjon, se nedenfor i avsnittet "Opprette seksjoner med feilsøkingsinformasjon"
  • errhand er en peker til en funksjon som vil bli kalt når feil oppstår. Du kan sende 0
  • feil - data som sendes til feilfunksjonen kan settes til 0
  • user_data - data som vil bli sendt til func-funksjonen kan settes til 0
  • feil - returnerte feilkode
Funksjonen returnerer Dwarf_P_Debug - en deskriptor som brukes i alle påfølgende funksjoner, eller -1 i tilfelle en feil, og feilen vil inneholde feilkoden (du kan få teksten til feilmeldingen ved å bruke dens kode ved å bruke dwarf_errmsg-funksjonen, sende denne koden til det)
Opprette noder (DIE - Debugging Information Entry)
Som beskrevet ovenfor danner feilsøkingsinformasjon en trestruktur. For å lage en node av dette treet, trenger du:
  • lag den ved å bruke funksjonen dwarf_new_die
  • legg til attributter til det (hver type attributt legges til av sin egen funksjon, som vil bli beskrevet nedenfor)
Noden er opprettet ved hjelp av funksjonen dwarf_new_die:
Dwarf_P_Die dwarf_new_die(Dwarf_P_Debug dbg, Dwarf_Tag new_tag, Dwarf_P_Die parent, Dwarf_P_Die child, Dwarf_P_Die left_sibling, Dwarf_P_Die right_sibling, Dwarf_Error *error)
  • new_tag - node-tag (type) - konstant DW_TAG_xxxx, som finnes i libdwarf.h-filen
  • forelder, barn, venstresøsken, høyresøsken - henholdsvis forelder, barn, venstre og høyre nabo til noden. Det er ikke nødvendig å spesifisere alle disse parameterne, det er nok å spesifisere en og sette 0 i stedet for resten. Hvis alle parametere er 0, vil noden være enten rot eller isolert
  • feil - vil inneholde feilkoden når den oppstår
Funksjonen returnerer DW_DLV_BADADDR ved feil eller et Dwarf_P_Die-nodehåndtak ved suksess
Opprette nodeattributter
For å lage nodeattributter er det en hel familie av funksjoner dwarf_add_AT_xxxx. Noen ganger er det vanskelig å finne ut hvilken funksjon som må lage den nødvendige attributten, så jeg har til og med gravd i kildekoden til biblioteket flere ganger. Noen av funksjonene vil bli beskrevet her, noen nedenfor i de aktuelle avsnittene. De godtar alle en eierparameter - et håndtak til noden som attributtet vil bli lagt til, og returnerer en feilkode i feilparameteren.
Funksjonen dwarf_add_AT_name legger til et "navn"-attributt (DW_AT_name) til en node. De fleste noder må ha et navn (for eksempel prosedyrer, variabler, konstanter), noen kan ikke ha et navn (for eksempel Compilation Unit)
Dwarf_P_Attribute dwarf_add_AT_name(Dwarf_P_Die ownerdie, char *name, Dwarf_Error *error)
  • navn - den faktiske attributtverdien (nodenavn)

Funksjonene dwarf_add_AT_signed_const, dwarf_add_AT_unsigned_const legger til det spesifiserte attributtet og dets signerte (usignerte) verdi til noden. Signerte og usignerte attributter brukes til å spesifisere konstante verdier, størrelser, linjenummer osv. Funksjonsformat:
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 *error)
  • dbg - Dwarf_P_Debug descriptor oppnådd under bibliotekinitialisering
  • attr - attributtet hvis verdi er satt - konstanten DW_AT_xxxx, som finnes i filen libdwarf.h
  • verdi - attributtverdi
Returner DW_DLV_BADADDR ved feil, eller et attributthåndtak ved suksess.
Opprette en kompileringsenhet
Ethvert tre må ha en rot - i vårt tilfelle er dette en kompileringsenhet som inneholder informasjon om programmet (for eksempel navnet på hovedfilen, programmeringsspråket som brukes, navnet på kompilatoren, sensitiviteten til store og små bokstaver for symboler ( variabler, funksjoner), hovedfunksjonen til programmet, startadressen osv. osv.). I prinsippet kreves ingen attributter. La oss for eksempel lage informasjon om hovedfilen og kompilatoren.
Hovedfilinformasjon
For å lagre informasjon om hovedfilen, bruk navneattributtet (DW_AT_name), bruk funksjonen dwarf_add_AT_name som vist i avsnittet "Opprette nodeattributter".
Kompilatorinformasjon
Vi bruker funksjonen dwarf_add_AT_producer:
Dwarf_P_Attribute dwarf_add_AT_name(Dwarf_P_Die ownerdie, char *producer_string, Dwarf_Error *error)
  • producer_string - streng med informasjonstekst
Returnerer DW_DLV_BADADDR ved feil, eller et attributthåndtak ved suksess.
Opprette en felles informasjonsoppføring
Vanligvis, når en funksjon (subrutine) kalles, blir dens parametere og returadresse presset inn i stabelen (selv om hver kompilator kan gjøre dette annerledes), alt dette kalles en Call Frame. Feilsøkeren trenger informasjon om rammeformatet for å korrekt bestemme returadressen fra en funksjon og bygge en tilbakesporing - en kjede av funksjonskall som førte oss til gjeldende funksjon, og parametrene til disse funksjonene. Det er også vanlig å spesifisere prosessorregistre som er lagret på stabelen. Koden som reserverer plass på stabelen og lagrer prosessorregistre kalles en funksjonsprolog, koden som gjenoppretter registre og stabelen kalles en epilog.
Denne informasjonen er svært kompilatoravhengig. For eksempel trenger ikke prologen og epilogen være helt i begynnelsen og slutten av funksjonen; noen ganger brukes rammen, noen ganger ikke; prosessorregistre kan lagres i andre registre osv.
Så feilsøkeren må vite hvordan prosessorregistrene endrer verdien og hvor de vil bli lagret når de går inn i prosedyren. Denne informasjonen kalles Call Frame Information - informasjon om rammeformatet. For hver adresse i programmet (som inneholder kode), er rammeadressen i minnet (Canonical Frame Address - CFA) og informasjon om prosessorregistrene angitt, for eksempel kan du indikere at:
  • Saken er ikke bevart i prosedyren
  • registeret endrer ikke verdien i prosedyren
  • registeret er lagret på stabelen på adressen CFA+n
  • register er lagret i et annet register
  • registeret er lagret i minnet på en eller annen adresse, som kan beregnes på en ganske uopplagt måte
  • etc.
Siden informasjonen må spesifiseres for hver adresse i koden, er den svært omfangsrik og lagres i komprimert form i .debug_frame-delen. Siden den endres lite fra adresse til adresse, er det bare endringene som kodes i form av DW_CFA_xxxx-instruksjoner. Hver instruksjon indikerer én endring, for eksempel:
  • DW_CFA_set_loc - peker på gjeldende adresse i programmet
  • DW_CFA_advance_loc - fremfører pekeren med et visst antall byte
  • DW_CFA_def_cfa - indikerer adressen til stabelrammen (numerisk konstant)
  • DW_CFA_def_cfa_register - indikerer adressen til stabelrammen (hentet fra prosessorregisteret)
  • DW_CFA_def_cfa_expression - spesifiserer hvordan stabelrammeadressen skal beregnes
  • DW_CFA_same_value - indikerer at registeret ikke er endret
  • DW_CFA_register - indikerer at registeret er lagret i et annet register
  • etc.
Elementer i .debug_frame-delen er oppføringer som kan være av to typer: Common Information Entry (CIE) og Frame Description Entry (FDE). CIE inneholder informasjon som er felles for mange FDE-poster, grovt sett beskriver den en bestemt type prosedyre. FDE-er beskriver hver spesifikk prosedyre. Når du går inn i en prosedyre, utfører feilsøkeren først instruksjoner fra CIE og deretter fra FDE.
Min kompilator lager prosedyrer der CFA er i sp-registeret (r13). La oss lage en CIE for alle prosedyrer. Det er en funksjon for dette, 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_Unror;
  • augmenter er en UTF-8-kodet streng, hvis tilstedeværelse indikerer at det er ytterligere plattformavhengig informasjon for CIE eller FDE. Sett en tom linje
  • code_align - kodejustering i byte (vi har 2)
  • data_align - justering av data i rammen (sett -4, som betyr at alle parametere tar 4 byte på stabelen og det vokser ned i minnet)
  • ret_addr_reg - et register som inneholder returadressen fra prosedyren (vi har 14)
  • init_bytes - en matrise som inneholder DW_CFA_xxxx-instruksjoner. Dessverre er det ingen praktisk måte å generere denne matrisen på. Du kan generere den manuelt eller se på den i elf-filen som ble generert av C-kompilatoren, som er det jeg gjorde. For mitt tilfelle inneholder den 3 byte: 0x0C, 0x0D, 0, som står for DW_CFA_def_cfa: r13 ofs 0 (CFA er i register r13, offset er 0)
  • init_bytes_len - lengden på init_bytes-matrisen
Funksjonen returnerer DW_DLV_NOCOUNT ved feil eller et CIE-håndtak som skal brukes når man oppretter en FDE for hver prosedyre, som vi skal se på senere i avsnittet "Opprette en FDE-prosedyre"
Opprette datatyper
Før du kan lage prosedyrer og variabler, må du først opprette noder som tilsvarer datatypene. Det er mange datatyper, men de er alle basert på grunnleggende typer (elementære typer som int, double, etc.), andre typer er bygget fra grunnleggende.
Basistypen er noden med taggen DW_TAG_base_type. Den må ha følgende attributter:
  • "navn" (DW_AT_name)
  • "encoding" (DW_AT_encoding) - betyr hva slags data som beskriver denne grunnleggende typen (for eksempel DW_ATE_boolean - logisk, DW_ATE_float - flytende komma, DW_ATE_signed - signed integer, DW_ATE_unsigned - unsigned heltall, etc.)
  • "størrelse" (DW_AT_byte_size - størrelse i byte eller DW_AT_bit_size - størrelse i biter)
En node kan også inneholde andre valgfrie attributter.
For å lage en 32-bits signert heltallsbasetype "int", må vi for eksempel opprette en node med taggen DW_TAG_base_type og sette dens attributter DW_AT_name - "int", DW_AT_encoding - DW_ATE_signed, DW_AT_byte_size - 4.
Når basetyper er opprettet, kan du utlede derivater fra dem. Slike noder må inneholde attributtet DW_AT_type - en referanse til deres basistype. For eksempel, en peker til int - en node med DW_TAG_pointer_type-taggen må inneholde i DW_AT_type-attributtet en kobling til den tidligere opprettede "int"-typen.
Et attributt med en referanse til en annen node er opprettet av funksjonen dwarf_add_AT_reference:
Dwarf_P_Attribute dwarf_add_AT_reference(Dwarf_P_Debug dbg, Dwarf_P_Die ownerdie, Dwarf_Half attr, Dwarf_P_Die otherdie, Dwarf_Error *error)
  • attr - attributt, i dette tilfellet DW_AT_type
  • otherdie - et håndtak til noden av typen det refereres til
Opprette prosedyrer
For å lage prosedyrer må jeg forklare en annen type feilsøkingsinformasjon - Linjenummerinformasjon. Det tjener til å kartlegge hver maskininstruksjon til en spesifikk linje med kildekode og også for å tillate linje-for-linje feilsøking av programmet. Denne informasjonen lagres i .debug_line-delen. Hvis vi hadde nok plass, ville den blitt lagret som en matrise, en rad for hver instruksjon med kolonner som dette:
  • kildefilnavn
  • linjenummer i denne filen
  • kolonnenummer i filen
  • om instruksjonen er starten på et utsagn eller en blokk med utsagn
  • etc.
En slik matrise ville være veldig stor, så den må komprimeres. For det første slettes dupliserte linjer, og for det andre lagres ikke linjene i seg selv, men bare endringene i dem. Disse endringene ser ut som kommandoer for en endelig tilstandsmaskin, og selve informasjonen regnes allerede som et program som vil bli "utført" av denne maskinen. Kommandoene til dette programmet ser for eksempel slik ut: DW_LNS_advance_pc - forover programtelleren til en bestemt adresse, DW_LNS_set_file - still inn filen som prosedyren er definert i, DW_LNS_const_add_pc - forover programtelleren med flere byte osv.
Det er vanskelig å generere denne informasjonen på et så lavt nivå, så libdwarf har flere funksjoner for å gjøre denne oppgaven enklere.
Det er dyrt å lagre filnavnet for hver instruksjon, så i stedet for navnet lagres indeksen i en spesiell tabell. For å lage en filindeks, må du bruke funksjonen dwarf_add_file_decl:
Dwarf_Unsigned dwarf_add_file_decl(Dwarf_P_Debug dbg, char *name, Dwarf_Unsigned dir_idx, Dwarf_Unsigned time_mod, Dwarf_Unsigned length, Dwarf_Error *error)
  • navn - filnavn
  • dir_idx - indeks for mappen der filen ligger. Indeksen kan fås ved å bruke funksjonen dwarf_add_directory_decl. Hvis fulle baner brukes, kan du sette 0 som mappeindeks og ikke bruke dwarf_add_directory_decl i det hele tatt
  • time_mod - filendringstid, kan ikke spesifiseres (0)
  • lengde - filstørrelse, også valgfritt (0)
Funksjonen vil returnere filindeksen eller DW_DLV_NOCOUNT ved feil.
For å lage informasjon om linjenummer er det tre funksjoner dwarf_add_line_entry_b, dwarf_lne_set_address, dwarf_lne_end_sequence, som vi skal se på nedenfor.
Oppretting av feilsøkingsinformasjon for en prosedyre skjer i flere trinn:
  • opprette et prosedyresymbol i .symtab-delen
  • lage en prosedyrenode med attributter
  • opprette en FDE-prosedyre
  • lage prosedyreparametere
  • opprette linjenummerinformasjon
Opprette et prosedyresymbol
Prosedyresymbolet opprettes som beskrevet ovenfor i avsnittet Section.symtab. I den er symboler for prosedyrer ispedd symboler for filer der kildekoden til disse prosedyrene er plassert. Først lager vi et filsymbol, deretter en prosedyre. Dette gjør filen aktuell, og hvis neste prosedyre er i gjeldende fil, trenger ikke filsymbolet å opprettes på nytt.
Opprette en prosedyrenode med attributter
Først lager vi en node ved å bruke funksjonen dwarf_new_die (se avsnittet "Opprette noder"), og spesifiserer DW_TAG_subprogram som tag, og Compilation Unit (hvis dette er en global prosedyre) eller den tilsvarende DIE (hvis lokal) som overordnet. Deretter lager vi attributtene:
  • prosedyrenavn (funksjon dwarf_add_AT_name, se "Opprette nodeattributter")
  • linjenummer i filen der prosedyrekoden begynner (attributt DW_AT_decl_line), funksjon dwarf_add_AT_unsigned_const (se "Opprette nodeattributter")
  • startadresse for prosedyren (attributt DW_AT_low_pc), funksjon dwarf_add_AT_targ_address, se nedenfor
  • endelig adresse til prosedyren (attributt DW_AT_high_pc), funksjon dwarf_add_AT_targ_address, se nedenfor
  • typen resultat som returneres av prosedyren (attributtet DW_AT_type er en kobling til en tidligere opprettet type, se "Opprette datatyper"). Hvis prosedyren ikke returnerer noe, trenger ikke denne attributten å opprettes
Attributtene DW_AT_low_pc og DW_AT_high_pc må opprettes ved hjelp av funksjonen dwarf_add_AT_targ_address_b som er spesielt utviklet for dette formålet:
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 - attributt (DW_AT_low_pc eller DW_AT_high_pc)
  • pc_value - adresseverdi
  • sym_index - indeks for prosedyresymbolet i tabellen.symtab. Valgfritt, 0 kan bestås
Funksjonen vil returnere DW_DLV_BADADDR ved feil.
Opprette en FDE-prosedyre
Som diskutert ovenfor i delen "Opprette en felles informasjonsoppføring", for hver prosedyre må du opprette en rammebeskrivelse, som skjer i flere stadier:
  • opprette en ny FDE (se Opprette en felles informasjonsoppføring)
  • bli med den opprettede FDE til den generelle listen
  • legge til instruksjoner til den opprettede FDE
Du kan opprette en ny FDE ved å bruke funksjonen dwarf_new_fde:
Dwarf_P_Fde dwarf_new_fde(Dwarf_P_Debug dbg, Dwarf_Error *feil)
Funksjonen vil returnere et håndtak til den nye FDE eller DW_DLV_BADADDR ved feil.
Du kan legge til en ny FDE til listen ved å bruke 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_Error* error
  • fde - håndtaket nettopp mottatt
  • die - DIE-prosedyrer (se Opprette en prosedyrenode med attributter)
  • cie - CIE-deskriptor (se Opprette en felles informasjonsoppføring)
  • virt_addr - startadressen til prosedyren vår
  • code_len - prosedyrelengde i byte
Funksjonen vil returnere DW_DLV_NOCOUNT ved feil.
Etter alt dette kan du legge til DW_CFA_xxxx-instruksjoner til vår FDE. Dette gjøres av funksjonene dwarf_add_fde_inst og dwarf_fde_cfa_offset. Den første legger til den gitte instruksjonen til listen:
Dwarf_P_Fde dwarf_add_fde_inst(Dwarf_P_Fde fde, Dwarf_Small op, Dwarf_Unsigned val1, Dwarf_Unsigned val2, Dwarf_Error *feil)
  • op - instruksjonskode (DW_CFA_хххх)
  • val1, val2 - instruksjonsparametere (forskjellig for hver instruksjon, se Standard, avsnitt 6.4.2 Call Frame-instruksjoner)
Dwarf_fde_cfa_offset-funksjonen legger til DW_CFA_offset-instruksjonen:
Dwarf_P_Fde dwarf_fde_cfa_offset(Dwarf_P_Fde fde, Dwarf_Unsigned reg, Dwarf_Signed offset, Dwarf_Error *feil)
  • fde - håndtere til den opprettede FDE
  • reg - register som skrives til rammen
  • offset - forskyvningen i rammen (ikke i byte, men i rammeelementer, se Opprette en felles informasjonsoppføring, data_align)
For eksempel lager kompilatoren en prosedyre hvis prolog lagrer registeret lr (r14) i stabelrammen. Det første trinnet er å legge til instruksjonen DW_CFA_advance_loc med den første parameteren lik 1, som betyr å fremme pc-registeret med 2 byte (se Opprette en felles informasjonsinnføring, code_align), og deretter legge til DW_CFA_def_cfa_offset med parameter 4 (sette dataforskyvningen i frame med 4 byte) og kall opp funksjonen dwarf_fde_cfa_offset med parameteren reg=14 offset=1, som betyr å skrive r14-registeret til rammen med en offset på -4 byte fra CFA.
Opprette prosedyreparametere
Å lage prosedyreparametere ligner på å lage vanlige variabler, se "Opprette variabler og konstanter"
Opprette informasjon om linjenummer
Denne informasjonen er laget slik:
  • i begynnelsen av prosedyren starter vi en blokk med instruksjoner med funksjonen dwarf_lne_set_address
  • for hver kodelinje (eller maskininstruksjon) lager vi informasjon om kildekoden (dwarf_add_line_entry)
  • på slutten av prosedyren fullfører vi blokken med instruksjoner med funksjonen dwarf_lne_end_sequence
Funksjonen dwarf_lne_set_address angir adressen der en blokk med instruksjoner begynner:
Dwarf_Unsigned dwarf_lne_set_address(Dwarf_P_Debug dbg, Dwarf_Addr offs, Dwarf_Unsigned symidx, Dwarf_Error *feil)
  • offs - prosedyreadresse (adressen til den første maskininstruksjonen)
  • sym_idx - symbolindeks (valgfritt, du kan angi 0)

Funksjonen dwarf_add_line_entry_b legger til informasjon om kildekodelinjer til .debug_line-delen. Jeg kaller denne funksjonen for hver maskininstruksjon:
Dwarf_Unsigned dwarf_add_line_entry_b(Dwarf_P_Debug dbg, Dwarf_Unsigned file_index, Dwarf_Addr code_offset, Dwarf_Unsigned lineno, Dwarf_Signed column_number, Dwarf_Bool is_source_stmt_beoole, Dwarf_Boole pilogue _begin, Dwarf_Bool is_prologue_end, Dwarf_Unsigned isa, Dwarf_Unsigned discriminator, Dwarf_Error *error)
  • file_index - indeks for kildekodefilen hentet tidligere av funksjonen dwarf_add_file_decl (se "Opprette prosedyrer")
  • code_offset - adressen til gjeldende maskininstruksjon
  • lineno - linjenummer i kildekodefilen
  • column_number - kolonnenummer i kildekodefilen
  • is_source_stmt_begin - 1 hvis gjeldende instruksjon er den første i koden på lineno-linjen (jeg bruker alltid 1)
  • is_basic_block_begin - 1 hvis gjeldende instruksjon er den første i setningsblokken (jeg bruker alltid 0)
  • is_epilogue_begin - 1 hvis gjeldende instruksjon er den første i epilogen til prosedyren (ikke nødvendig, jeg har alltid 0)
  • is_prologue_end - 1 hvis gjeldende instruksjon er den siste i prologen til prosedyren (obligatorisk!)
  • isa - instruksjonssettarkitektur. Sørg for å spesifisere DW_ISA_ARM_thumb for ARM Cortex M3!
  • diskriminator. Én posisjon (fil, linje, kolonne) av kildekoden kan svare til forskjellige maskininstruksjoner. I dette tilfellet må forskjellige diskriminatorer installeres for sett med slike instruksjoner. Hvis det ikke er slike tilfeller, bør det være 0
Funksjonen returnerer 0 (suksess) eller DW_DLV_NOCOUNT (feil).
Til slutt fullfører funksjonen dwarf_lne_end_sequence prosedyren:
Dwarf_Unsigned dwarf_lne_end_sequence(Dwarf_P_Debug dbg, Dwarf_Addr-adresse; Dwarf_Error *feil)
  • adresse - adresse til gjeldende maskininstruksjon
Returnerer 0 (suksess) eller DW_DLV_NOCOUNT (feil).
Dette fullfører opprettelsen av prosedyren.
Opprette variabler og konstanter
Generelt er variablene ganske enkle. De har et navn, en minneplassering (eller prosessorregister) der dataene deres er plassert, og typen av disse dataene. Hvis variabelen er global, må dens overordnede være Compilation Unit, hvis lokal, den tilsvarende noden (dette gjelder spesielt for prosedyreparametere, deres overordnede må være selve prosedyren). Du kan også spesifisere hvilken fil, rad og kolonne variabeldeklarasjonen er i.
I det enkleste tilfellet er verdien til en variabel plassert på en eller annen fast adresse, men mange variabler skapes dynamisk når man går inn i en prosedyre på stabelen eller registeret, noen ganger kan det være ganske lite trivielt å beregne adressen til verdien. Standarden gir en mekanisme for å beskrive hvor verdien av en variabel befinner seg – lokasjonsuttrykk. Et adresseuttrykk er et sett med instruksjoner (DW_OP_xxxx konstanter) for en Fort-lignende stabelmaskin, faktisk er det et eget språk med grener, prosedyrer og aritmetiske operasjoner. Vi vil ikke vurdere dette språket i sin helhet, vi vil faktisk bare være interessert i noen få instruksjoner:
  • DW_OP_addr - indikerer adressen til variabelen
  • DW_OP_fbreg - indikerer variabelens offset fra basisregisteret (vanligvis stabelpekeren)
  • DW_OP_reg0… DW_OP_reg31 - indikerer at variabelen er lagret i det tilsvarende registeret
For å lage et adresseuttrykk, må du først lage et tomt uttrykk (dwarf_new_expr), legge til instruksjoner til det (dwarf_add_expr_addr, dwarf_add_expr_gen, etc.) og legge det til noden som verdien av DW_AT_location-attributtet (dwarf_add_AT_location_expression).
Funksjonen for å lage et tomt adresseuttrykk returnerer håndtaket eller 0 ved feil:
Dwarf_Expr dwarf_new_expr(Dwarf_P_Debug dbg, Dwarf_Error *feil)
For å legge til instruksjoner til et uttrykk, må du bruke funksjonen 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 *feil)
  • opcode - operasjonskode, konstant DW_OP_хххх
  • val1, val2 - instruksjonsparametere (se standard)

For å eksplisitt angi adressen til en variabel, må dwarf_add_expr_addr-funksjonen brukes i stedet for den forrige:
Dwarf_Unsigned dwarf_add_expr_addr(Dwarf_P_Expr expr, Dwarf_Unsigned address, Dwarf_Signed sym_index, Dwarf_Error *feil)
  • expr er et håndtak til adresseuttrykket som instruksjonen er lagt til
  • adresse - variabel adresse
  • sym_index - indeks for symbolet i tabellen.symtab. Valgfritt, 0 kan bestås
Funksjonen returnerer også DW_DLV_NOCOUNT ved feil.
Til slutt kan du legge til det opprettede adresseuttrykket til noden ved å bruke funksjonen 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 *error)
  • ownerdie - noden som uttrykket er lagt til
  • attr - attributt (i vårt tilfelle DW_AT_location)
  • loc_expr - håndtere til et tidligere opprettet adresseuttrykk
Funksjonen returnerer attributthåndtaket eller DW_DLV_NOCOUNT ved feil.
Variabler (samt prosedyreparametere) og konstanter er vanlige noder med henholdsvis taggen DW_TAG_variabel, DW_TAG_formal_parameter og DW_TAG_const_type. De krever følgende attributter:
  • variabel/konstant navn (funksjon dwarf_add_AT_name, se "Opprette nodeattributter")
  • linjenummer i filen der variabelen er deklarert (attributt DW_AT_decl_line), funksjon dwarf_add_AT_unsigned_const (se "Opprette nodeattributter")
  • filnavnindeks (DW_AT_decl_file-attributt), dwarf_add_AT_unsigned_const-funksjon (se "Opprette nodeattributter")
  • variabel/konstant datatype (DW_AT_type-attributt er en kobling til en tidligere opprettet type, se "Opprette datatyper")
  • adresseuttrykk (se ovenfor) - nødvendig for en variabel eller prosedyreparameter
  • eller verdi - for en konstant (attributt DW_AT_const_value, se "Opprette nodeattributter")
Opprette seksjoner med feilsøkingsinformasjon
Etter å ha opprettet alle nodene til feilsøkingsinformasjonstreet, kan du begynne å lage alveseksjoner med det. Dette skjer i to trinn:
  • først må vi kalle funksjonen dwarf_transform_to_disk_form, som kaller funksjonen vi skrev for å lage de nødvendige alveseksjonene én gang for hver seksjon
  • for hver seksjon vil dwarf_get_section_bytes-funksjonen returnere oss data som må skrives til den tilsvarende seksjonen
Funksjon
dwarf_transform_to_disk_form (Dwarf_P_Debug dbg, Dwarf_Error*-feil)
konverterer feilsøkingsinformasjonen vi opprettet til binært format, men skriver ikke noe til disken. Det vil returnere antall opprettede alveseksjoner eller DW_DLV_NOCOUNT ved feil. I dette tilfellet vil tilbakeringingsfunksjonen kalles for hver seksjon, som vi sendte til funksjonen dwarf_producer_init_c ved initialisering av biblioteket. Vi må skrive denne funksjonen selv. Dens spesifikasjon er som følger:
typedef int (*Dwarf_Callback_Func_c)(char* navn, int størrelse, Dwarf_Unsigned type, Dwarf_Unsigned flagg, Dwarf_Unsigned link, Dwarf_Unsigned info, Dwarf_Unsigned* sect_name_index, void * user_data, int*
  • navn – navnet på alveseksjonen som skal opprettes
  • størrelse - seksjonsstørrelse
  • type - seksjonstype
  • flagg - seksjonsflagg
  • lenke - seksjonslenkefelt
  • info - seksjonsinformasjonsfelt
  • sect_name_index - du må returnere indeksen for seksjonen med omplasseringer (valgfritt)
  • user_data - sendes til oss på samme måte som vi angir det i bibliotekets initialiseringsfunksjon
  • feil - her kan du sende feilkoden
I denne funksjonen må vi:
  • opprette en ny seksjon (elf_newscn-funksjon, se Opprette seksjoner)
  • lag en seksjonsoverskrift (funksjon elf32_getshdr, ibid.)
  • fyll den ut riktig (se ibid.). Dette er enkelt fordi seksjonsoverskriftsfeltene tilsvarer parametrene til funksjonen vår. Sett de manglende feltene sh_addr, sh_offset, sh_entsize til 0 og sh_addralign til 1
  • returner indeksen til den opprettede delen (funksjon elf_ndxscn, se "Section.symtab") eller -1 ved feil (ved å sette feilkoden til feil)
  • Vi bør også hoppe over ".rel"-delen (i vårt tilfelle), og returnere 0 når vi returnerer fra funksjonen
Når den er fullført, vil dwarf_transform_to_disk_form-funksjonen returnere antall opprettede seksjoner. Vi må gå gjennom hver seksjon i en løkke fra 0, ved å følge disse trinnene:
  • lag data som skal skrives til en seksjon ved hjelp av funksjonen dwarf_get_section_bytes:
    Dwarf_Ptr dwarf_get_section_bytes(Dwarf_P_Debug dbg, Dwarf_Signed dwarf_section, Dwarf_Signed *elf_section_index, Dwarf_Unsigned *length, Dwarf_Error* error)
    • dvergseksjon - seksjonsnummer. Må være i området 0..n, der n er tallet som returneres til oss av funksjonen dwarf_transform_to_disk_form
    • elf_section_index - returnerer indeksen til seksjonen som data skal skrives til
    • lengde - lengden på disse dataene
    • feil - ikke brukt
    Funksjonen returnerer en peker til de mottatte dataene eller 0 (i tilfellet
    når det ikke er flere seksjoner igjen å opprette)
  • lag en datadeskriptor for gjeldende seksjon (funksjon elf_newdata, se Opprette seksjoner) og fyll den (se der) ved å sette:
    • d_buf - en peker til dataene vi mottok fra forrige funksjon
    • d_size - størrelsen på disse dataene (ibid.)
Avslutter arbeidet med biblioteket
Etter at seksjonene er dannet, kan du fullføre arbeidet med libdwarf ved å bruke funksjonen dwarf_producer_finish:
Dwarf_Unsigned dwarf_producer_finish(Dwarf_P_Debug dbg, Dwarf_Error*-feil)
Funksjonen returnerer DW_DLV_NOCOUNT ved feil.
Vær oppmerksom på at opptak til disk ikke utføres på dette stadiet. Opptaket må gjøres ved å bruke funksjonene fra avsnittet "Opprette ELF - Skrive en fil".

Konklusjon

Det er alt.
Jeg gjentar, opprettelsen av feilsøkingsinformasjon er et veldig bredt emne, og jeg berørte ikke mange emner, bare løftet sløret. De som ønsker kan gå dypere i det uendelige.
Hvis du har spørsmål, vil jeg prøve å svare på dem.

Lukk