Hvordan går vi fra samling til maskinkode (kodegenerering)

Er der en nem måde at visualisere trinnet mellem samling af kode til maskinkode?

Hvis du f.eks. åbner en binær fil i notesblok, ser du en tekstformateret repræsentation af maskinkoden. Jeg antager, at hver byte (symbol), du ser, er det tilsvarende ascii-tegn for dets “binære værdi?

Men hvordan går vi fra samling til binær, hvad sker der bag kulisserne ??

Svar

Se på instruktionssættets dokumentation, så finder du poster som denne fra en pic-mikrocontroller til hver instruktion:

eksempel addlw instruktion

Linjen “kodning” fortæller hvordan denne instruktion ser ud i binær. I dette tilfælde starter det altid med 5, derefter en plejebit (som kan være enten en eller nul), så står “k” for den bogstavelige, du tilføjer.

de første par bits kaldes en “opcode”, de er unikke for hver instruktion. CPUen ser grundlæggende på opcode for at se, hvilken instruktion det er, så ved den at afkode “k” som et tal, der skal tilføjes.

Det er kedeligt, men ikke så svært at kode og afkode. Jeg havde en undervisningskursus, hvor vi var nødt til at gøre det i hånden i eksamener.

For faktisk at lave en fuld eksekverbar fil skal du også gøre ting som at allokere hukommelse, beregne grenforskydninger og sætte den i en format som ELF , afhængigt af dit operativsystem.

Svar

Samlingskoder har for det meste en en-til-en korrespondance med de underliggende maskininstruktioner. Så alt hvad du skal gøre er at identificere hver opcode på samlingens sprog, kortlægge den til den tilsvarende maskininstruktion og skrive maskininstruktionen ud til en fil sammen med dens tilsvarende parametre (hvis nogen). Du gentager derefter processen for hver ekstra opcode i kildefilen.

Det tager selvfølgelig mere end det at oprette en eksekverbar fil, der korrekt indlæses og kører på et operativsystem, og de fleste anstændige montører gør har nogle yderligere funktioner ud over simpel kortlægning af opkoder til maskininstruktioner (f.eks. makroer).

Svar

Det første ting du har brug for, er noget som denne fil . Dette er instruktionsdatabasen til x86-processorer, som den bruges af NASM-samleren (som jeg hjalp med at skrive, men ikke de dele, der faktisk oversætter instruktioner). Lad os vælge en vilkårlig linje fra databasen:

 ADD rm32,imm8 [mi: hle o32 83 /0 ib,s] 386,LOCK  

Hvad dette betyder er, at den beskriver instruktionen ADD. Der er flere varianter af denne instruktion, og den specifikke, der beskrives her, er den variant, der tager enten et 32-bit register eller en hukommelsesadresse og tilføjer en øjeblikkelig 8-bit værdi (dvs. en konstant, der er direkte inkluderet i instruktionen). Et eksempel på en monteringsinstruktion, der bruger denne version, er denne:

 add eax, 42  

Nu, du skal tage din tekstindtastning og analysere den i individuelle instruktioner og operander. For ovenstående instruktion ville dette sandsynligvis resultere i en struktur, der indeholder instruktionen ADD og en række operander (en henvisning til registeret EAX og værdien 42). Når du har denne struktur, løber du gennem instruktionsdatabasen og finder den linje, der matcher både instruktionsnavnet og typerne af operander. Hvis du ikke finder et match, er det en fejl, der skal præsenteres for brugeren (“ulovlig kombination af opcode og operander” eller lignende er den sædvanlige tekst).

Når vi først har fik linjen fra databasen, vi ser på den tredje kolonne, som for denne instruktion er:

 [mi: hle o32 83 /0 ib,s]  

Dette er et sæt instruktioner, der beskriver, hvordan man genererer den maskinkodeinstruktion, der kræves:

  • mi er en beskrivelse af operanderne: en a modr/m (register eller hukommelse) operand (hvilket betyder, at vi “skal tilføje en modr/m byte til slutningen af instruktionen, som vi senere kommer til) og en øjeblikkelig instruktion (som vil blive brugt i beskrivelsen af instruktionen).
  • Næste er hle. Dette identificerer, hvordan vi håndterer præfikset “lås”. Vi har ikke brugt “lås”, så vi ignorerer det.
  • Næste er o32. Dette fortæller os, at hvis vi samler kode til en 16- bit outputformat, skal instruktionen have et tilsidesættelsespræfiks i operandstørrelse.Hvis vi producerede 16-bit output, ville vi producere præfikset nu (0x66), men jeg antager, at vi ikke fortsætter.
  • Næste er 83. Dette er en bogstavelig byte i hexadecimal. Vi udsender den.
  • Næste er /0 Dette specificerer nogle ekstra bits, som vi har brug for i modr / m bytem, og får os til at generere det. modr/m byte bruges til at kode registre eller indirekte hukommelsesreferencer. Vi har en enkelt sådan operand, et register. Registret har et nummer, der er angivet i en anden datafil :

     eax REG_EAX reg32 0  
  • Vi kontrollerer, at reg32 er enig med den krævede størrelse af instruktionen fra den oprindelige database (den gør det). 0 er registerets nummer. En modr/m byte er en datastruktur, der er specificeret af processoren, der ser sådan ud:

      (most significant bit) 2 bits mod - 00 => indirect, e.g. [eax] 01 => indirect plus byte offset 10 => indirect plus word offset 11 => register 3 bits reg - identifies register 3 bits rm - identifies second register or additional data (least significant bit)  
  • Fordi vi arbejder med et register, er mod -feltet 0b11.

  • reg -feltet er nummeret på det register, vi bruger, 0b000
  • Fordi der kun er et register i denne instruktion, skal vi udfylde rm -feltet med noget. Det var, hvad de ekstra data, der er angivet i /0, var til, så vi placerede det i rm -feltet, 0b000.
  • modr/m byte er derfor 0b11000000 eller 0xC0. Vi sender dette.
  • Næste er ib,s. Dette angiver en underskrevet øjeblikkelig byte. Vi ser på operanderne og bemærker, at vi har en øjeblikkelig tilgængelig værdi. Vi konverterer den til en signeret byte og output den (42 => 0x2A).

Den komplette samlede instruktion er derfor: 0x83 0xC0 0x2A. Send den til dit outputmodul sammen med en note om, at ingen af bytes udgør hukommelsesreferencer (outputmodulet skal muligvis vide hvis de gør det).

Gentag for hver instruktion. Hold styr på etiketterne, så du ved, hvad du skal indsætte, når de henvises til. Tilføj faciliteter til makroer og direktiver, der sendes til dine objektfiloutputmoduler. Og sådan fungerer en samler grundlæggende.

Kommentarer

  • Tak. Stor forklaring, men det burde ikke være ‘ t ” 0x83 0xC0 0x2A ” i stedet for ” 0x83 0xB0 0x2A ” fordi 0b11000000 = 0xC0
  • @Kamran – $ cat > test.asm bits 32 add eax,42 $ nasm -f bin test.asm -o test.bin $ od -t x1 test.bin 0000000 83 c0 2a 0000003 … ja, du ‘ har ret. 🙂

Svar

I praksis er en assembler producerer normalt ikke direkte noget binært eksekverbart , men noget objektfil (tilføres senere til linker ). Der er dog undtagelser (du kan bruge nogle samlere til direkte at producere nogle binære eksekverbare ; de er sjældne).

Bemærk først, at mange montører i dag er gratis software programmer. Så download og kompilér kilden på din computer kode for GNU som (en del af binutils ) og af nasm . Så studer deres kildekode. BTW, jeg anbefaler at bruge Linux til dette formål (det er et meget udviklervenligt og fri software-venligt OS).

Objektfilen produceret af en samler indeholder især et kodesegment og flytning instruktioner. Det er organiseret i et veldokumenteret filformat, som afhænger af operativsystemet. På Linux er dette format (brugt til objektfiler, delte biblioteker, core dumps og eksekverbare filer) ELF . Objektfilen er senere input til linker (som endelig producerer en eksekverbar fil). Flytninger er specificeret af ABI (f.eks. x86-64 ABI ). Læs Levines bog Linkers og Loaders for mere.

Kodesegmentet i en sådan objektfil indeholder maskinkode med huller (udfyldes ved hjælp af omplaceringsoplysninger af linkeren). Den (flytbare) maskinkode genereret af en samler er tydeligvis specifik for et instruktions sæt arkitektur . x86 eller x86-64 (bruges i de fleste bærbare eller stationære processorer) ISAer er frygtelige komplekse i deres detaljer. Men en forenklet delmængde, kaldet y86 eller y86-64, er opfundet til undervisningsformål. Læs dias på dem. Andre svar på dette spørgsmål forklarer også lidt af det. Det kan være en god idé at læse en god bog om computerarkitektur .

De fleste montører arbejder i to passerer , den anden udsender flytning eller korrigerer noget af output fra det første pas. De bruger nu sædvanlige parsing teknikker (så læs måske Dragon Book ).

Sådan startes en eksekverbar af OS kernen (f.eks. hvordan execve systemopkald fungerer på Linux ) er et andet (og komplekst) spørgsmål. Det opretter normalt noget virtuelt adresseområde (i processen gør det execve (2) …) geninitialiser derefter procesens interne tilstand (inklusive bruger-mode -registre). En dynamisk linker -som ld-linux.so (8) på Linux- måske være involveret i løbetid. Læs en god bog, såsom Operativsystem: Three Easy Pieces . OSDEV wiki giver også nyttige oplysninger.

PS. Dit spørgsmål er så bredt, at du skal læse flere bøger om det. Jeg har givet nogle (meget ufuldstændige) referencer. Du bør finde flere af dem.

Kommentarer

  • Med hensyn til objektfilformater, for en nybegynder ‘ d anbefaler at se på RDOFF-format produceret af NASM. Dette var bevidst designet til at være så simpelt som realistisk muligt og stadig fungere i en række forskellige situationer. NASM-kilden inkluderer en linker og en loader til formatet. (Fuld offentliggørelse – jeg designede og skrev alle disse)

Skriv et svar

Din e-mailadresse vil ikke blive publiceret. Krævede felter er markeret med *