Hur går vi från montering till maskinkod (kodgenerering)

Finns det ett enkelt sätt att visualisera steget mellan att sätta ihop kod till maskinkod?

Om du till exempel öppnar en binär fil i anteckningsblocket ser du en textformaterad representation av maskinkod. Jag antar att varje byte (symbol) du ser är motsvarande ascii-karaktär för dess ”binära värde?

Men hur går vi från montering till binär, vad händer bakom kulisserna ??

Svar

Titta på instruktionsdokumentationen och du hittar poster som den här från en pic-mikrokontroller för varje instruktion:

exempel addlw-instruktion

”kodningsraden” berättar hur den instruktionen ser ut i binär. I det här fallet börjar det alltid med fem, sedan en bit som inte bryr sig (som kan vara antingen en eller noll), då står ”k” för den bokstavliga du lägger till.

de första bitarna kallas en ”opcode”, de är unika för varje instruktion. CPU: n ser i grund och botten på opcode för att se vilken instruktion det är, då vet den att avkoda ”k” som ett nummer som ska läggas till.

Det är tråkigt, men inte så svårt att koda och avkoda. Jag hade en klass på grundnivå där vi var tvungna att göra det för hand i tentor.

För att faktiskt skapa en fullständig körbar fil måste du också göra saker som att tilldela minne, beräkna grenförskjutningar och lägga den i en format som ELF , beroende på vilket operativsystem du använder.

Svar

Monteringsopkoder har för det mesta en en-till-en-korrespondens med de underliggande maskininstruktionerna. Så allt du behöver göra är att identifiera varje opkod på monteringsspråket, mappa den till motsvarande maskininstruktion och skriva maskininstruktionen till en fil tillsammans med dess motsvarande parametrar (om sådana finns). Du upprepar sedan processen för varje ytterligare opkod i källfilen.

Det tar naturligtvis mer än så att skapa en körbar fil som laddas och körs ordentligt på ett operativsystem, och de flesta anständiga montörer gör har några ytterligare funktioner utöver enkel mappning av opkoder till maskininstruktioner (till exempel makron).

Svar

Det första sak du behöver är något som den här filen . Detta är instruktionsdatabasen för x86-processorer som används av NASM-samlare (som jag hjälpte till att skriva, men inte de delar som faktiskt översätter instruktioner). Låt oss välja en godtycklig rad från databasen:

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

Vad detta betyder är att den beskriver instruktionen ADD. Det finns flera varianter av denna instruktion, och den specifika som beskrivs här är den variant som tar antingen ett 32-bitarsregister eller en minnesadress och lägger till ett omedelbart 8-bitarsvärde (dvs. en konstant som ingår direkt i instruktionen). Ett exempel på monteringsanvisning som skulle använda den här versionen är den här:

 add eax, 42  

Nu, du måste ta din textinmatning och analysera den i individuella instruktioner och operander. För instruktionen ovan skulle detta förmodligen resultera i en struktur som innehåller instruktionen, ADD, och en uppsättning operander (en referens till registret EAX och värdet 42). När du väl har den här strukturen går du igenom instruktionsdatabasen och hittar raden som matchar både instruktionsnamnet och typerna av operander. Om du inte hittar en matchning är det ett fel som måste presenteras för användaren (”olaglig kombination av opcode och operander” eller liknande är den vanliga texten).

När vi väl har fick raden från databasen, vi tittar på den tredje kolumnen, som för den här instruktionen är:

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

Detta är en uppsättning instruktioner som beskriver hur man skapar maskinkodinstruktioner som krävs:

  • mi är en beskrivning av operanderna: en a modr/m (register eller minne) operand (vilket betyder att vi måste lägga till en modr/m byte till slutet på instruktionen, som vi kommer till senare) och en omedelbar instruktion (som kommer att användas i beskrivningen av instruktionen).
  • Nästa är hle. Detta identifierar hur vi hanterar prefixet ”lås”. Vi har inte använt ”lås”, så vi ignorerar det.
  • Nästa är o32. Detta berättar för oss att om vi monterar kod för en 16- bit-utdataformat behöver instruktionen ett prefix för åsidosättande av operandstorlek.Om vi producerade 16-bitars utdata skulle vi producera prefixet nu (0x66), men jag antar att vi inte fortsätter.
  • Nästa är 83. Detta är en bokstav i hexadecimal. Vi matar ut den.
  • Nästa är /0 Detta specificerar några extra bitar som vi behöver i modr / m bytem och får oss att generera det. modr/m byte används för att koda register eller indirekta minnesreferenser. Vi har en enda sådan operand, ett register. Registret har ett nummer som anges i en annan datafil :

     eax REG_EAX reg32 0  
  • Vi kontrollerar att reg32 håller med den önskade storleken på instruktionen från den ursprungliga databasen (den gör det). 0 är registrets nummer. En modr/m byte är en datastruktur som specificeras av processorn, som ser ut så här:

      (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)  
  • Eftersom vi arbetar med ett register är mod fältet 0b11.

  • Fältet reg är numret på det register vi använder, 0b000
  • Eftersom det bara finns ett register i denna instruktion måste vi fylla i fältet rm med något. Det var vad de extra data som anges i /0 var för, så vi lade det i fältet rm, 0b000.
  • modr/m byte är därför 0b11000000 eller 0xC0. Vi skickar ut detta.
  • Nästa är ib,s. Detta anger en signerad omedelbar byte. Vi tittar på operanderna och noterar att vi har en omedelbar tillgängligt värde. Vi konverterar det till en signerad byte och matar ut den (42 => 0x2A).

Den kompletta monterade instruktionen är därför: 0x83 0xC0 0x2A. Skicka den till din utdatamodul, tillsammans med en anteckning om att ingen av byte utgör minnesreferenser (utdatamodulen kan behöva veta om de gör det.

Upprepa för varje instruktion. Håll koll på etiketter så att du vet vad du ska infoga när de refereras till. Lägg till faciliteter för makron och direktiv som skickas till dina objektfilutmatningsmoduler. Och det är i princip hur en monterare fungerar.

Kommentarer

  • Tack. Bra förklaring men borde inte vara ’ t ” 0x83 0xC0 0x2A ” snarare än ” 0x83 0xB0 0x2A ” eftersom 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 rätt. 🙂

Svar

I praktiken en assembler producerar vanligtvis inte direkt något binärt körbart , men vissa objektfil (matas senare till linker ). Det finns dock undantag (du kan använda vissa samlare för att direkt producera en binär körbar ; de är ovanliga).

Lägg först märke till att många monterare idag är gratis programvara program. Så ladda ner och kompilera källan på din dator kod för GNU som (en del av binutils ) och av nasm . Studera sedan deras källkod. BTW, jag rekommenderar att du använder Linux för det ändamålet (det är ett mycket utvecklarvänligt och fri programvaruvänligt operativsystem).

Objektfilen som produceras av en samlare innehåller särskilt ett -kodsegment och flyttningsinstruktioner . Den är organiserad i ett väldokumenterat filformat, vilket beror på operativsystemet. På Linux är det formatet (används för objektfiler, delade bibliotek, kärndumpar och körbara filer) ELF . Objektfilen matas senare in till linker (som äntligen producerar en körbar). Omlokaliseringar anges av ABI (t.ex. x86-64 ABI ). Läs Levines bok Linkers and Loaders för mer.

Kodsegmentet i en sådan objektfil innehåller maskinkod med hål (som ska fyllas, med hjälp av omplaceringsinformation, av länken). Den (flyttbara) maskinkoden som genereras av en samlare är uppenbarligen specifik för en instruktionsuppsättning arkitektur . x86 eller x86-64 (används i de flesta bärbara eller stationära processorer) ISA är fruktansvärt komplex i sina detaljer. Men en förenklad delmängd, kallad y86 eller y86-64, har uppfunnits för undervisningsändamål. Läs bilder på dem. Andra svar på denna fråga förklarar också lite av det. Du kanske vill läsa en bra bok om datorarkitektur .

De flesta monterare arbetar i två pass , den andra avger omlokalisering eller korrigerar en del av resultatet från det första passet. De använder nu vanliga parsing -tekniker (så läs kanske The Dragon Book ).

Hur en körbar startas av OS kärnan (t.ex. hur execve systemanrop fungerar på Linux ) är en annan (och komplex) fråga. Det sätter vanligtvis upp något virtuellt adressutrymme (i -processen gör execve (2) …) återinitialisera sedan processens interna tillstånd (inklusive användarläge -register). En dynamisk länkare -som ld-linux.so (8) på Linux- kan vara involverad vid körning. Läs en bra bok, till exempel Operativsystem: Three Easy Pieces . Wiki OSDEV ger också användbar information.

PS. Din fråga är så bred att du behöver läsa flera böcker om den. Jag har gett några (mycket ofullständiga) referenser. Du borde hitta fler av dem.

Kommentarer

  • När det gäller objektfilformat, för en nybörjare ’ Jag rekommenderar att man tittar på RDOFF-formatet producerat av NASM. Detta var avsiktligt utformat för att vara så enkelt som realistiskt möjligt och fortfarande fungera i olika situationer. NASM-källan innehåller en länkare och en lastare för formatet. (Fullständig information – Jag designade och skrev alla dessa)

Lämna ett svar

Din e-postadress kommer inte publiceras. Obligatoriska fält är märkta *