Hogyan haladunk az összeszereléstől a gépi kódig (kódgenerálás)

Van-e egyszerű módszer a kód összeállítása a gépi kód közötti lépés vizualizálására?

Ha például egy bináris fájlt nyit meg a jegyzettömbben, akkor a gépi kód szövegesen formázott ábrázolása látható. Feltételezem, hogy minden egyes látott bájt (szimbólum) a megfelelő bináris értékű ascii karakter?

De hogyan haladunk az összeállításból a binárisba, mi folyik a kulisszák mögött ??

Válasz

Nézze meg az utasításkészlet dokumentációját, és ehhez hasonló bejegyzéseket talál a egy pic mikrovezérlő minden utasításhoz:

példa addlw utasításra

A “kódolás” sor megmondja hogy néz ki ez az utasítás bináris formában. Ebben az esetben mindig 5 eggyel kezdődik, majd nem kell ellátni egy bitet (ami lehet egy vagy nulla), majd a “k” jelölik az Ön által betöltött szó betűjét.

az első néhány bitet “opkódnak” hívják, az egyes utasításokhoz egyediek. A CPU alapvetően megnézi az opkódot, hogy megnézze, milyen utasításról van szó, majd tudja a “k” -eket dekódolni hozzáadandó számként.

Unalmas, de nem olyan nehéz kódolni és dekódolni. Volt egy undergrad osztályom, ahol kézzel kellett elvégeznünk a vizsgákon.

A teljes futtatható fájl elkészítéséhez olyan dolgokat is meg kell tennie, mint a memória lefoglalása, az elágazási eltolások kiszámítása és egy formátum, például ELF , az operációs rendszertől függően.

Válasz

Az összeszerelési opkódok többnyire egy az egyben megfelelnek az alapul szolgáló gépi utasításoknak. Tehát csak annyit kell tennie, hogy azonosítja az egyes opkódokat az összeállítási nyelven, leképezi a megfelelő gépi utasításokra, és kiírja a gépi utasításokat egy fájlba a megfelelő paraméterekkel együtt (ha vannak ilyenek). Ezután megismétli a folyamatot a forrásfájl minden további opkódjára.

Természetesen ennél többre van szükség egy olyan futtatható fájl létrehozásához, amely megfelelően betöltődik és fut egy operációs rendszeren, és a legtöbb tisztességes összeállító ezt megteszi. van néhány további képessége az opkódok gépi utasításokhoz történő egyszerű leképezésén túl (például makrók).

Válasz

Az első amire szüksége van, olyasmi, mint ez a fájl . Ez az x86-os processzorok utasítás-adatbázisa, amelyet a NASM assembler használ (amit én segítettem megírni, bár nem azok a részek, amelyek valóban lefordítják az utasításokat). Válasszon egy tetszőleges sort az adatbázisból:

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

Ez azt jelenti, hogy leírja a ADD utasítást. Ennek az utasításnak több változata létezik, és az itt leírt specifikus az a változat, amely vagy 32 bites regiszter- vagy memóriacímet vesz fel, és azonnali 8 bites értéket ad hozzá (vagyis egy konstansot, amely közvetlenül szerepel az utasításban). A következő verziót használó szerelési utasítás a következő:

 add eax, 42  

Most, meg kell vennie a szövegbevitelt, és egyéni utasításokba és operandusokba kell elemeznie. A fenti utasítás esetében valószínűleg ez egy olyan struktúrát eredményezne, amely tartalmazza az utasítást, a ADD utasítást és az operandusok tömbjét (hivatkozás a EAX és az érték 42). Miután megvan ez a struktúra, átfut az utasítások adatbázisában, és megtalálja azt a sort, amely megfelel mind az utasítás nevének, mind az operandus típusainak. Ha nem talál egyezést, akkor ez egy hiba, amelyet be kell mutatni a felhasználónak (“az opcode és az operandus illegális kombinációja” vagy hasonló a szokásos szöveg.

Miután “ve” megkapta a sort az adatbázisból, megnézzük a harmadik oszlopot, amely ehhez az utasításhoz a következő:

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

Ez egy olyan utasításkészlet, amely leírja a szükséges gépkód-utasítás létrehozását:

  • A mi az operandusok leírása: egy modr/m (regisztráció vagy memória) operandus (ami azt jelenti, hogy egy modr/m bájtot hozzá kell fűznünk az utasítás vége, amelyre később eljutunk) és egy azonnali utasítás (amelyet az utasítás leírásában használunk).
  • A következő a következő: hle. Ez azonosítja, hogyan kezeljük a “lock” előtagot. Még nem használtuk a “lock” funkciót, ezért figyelmen kívül hagyjuk.
  • A következő a következő: o32. Ez azt mondja nekünk, hogy ha egy 16- bit kimeneti formátum, az utasításnak operandus méretű felülírási előtagra van szüksége.Ha 16 bites kimenetet produkáltunk, akkor most előállítanánk az előtagot (0x66), de feltételezem, hogy nem vagyunk és folytatjuk.
  • A következő a következő: 83. Ez egy szó szerinti bájt hexadecimális értékkel. Kimeneti.
  • A következő a következő: /0. Ez meghatároz néhány extra bitet, amelyekre szükségünk lesz a modr / m bytem-ben, és előidézi őket. A modr/m bájt regiszterek vagy közvetett memória-hivatkozások kódolására szolgál. Van egyetlen ilyen operandusunk, egy regiszterünk. A regiszternek van egy száma, amelyet a másik adatfájlban adunk meg:

     eax REG_EAX reg32 0  
  • Ellenőrizzük, hogy a reg32 egyetért-e az utasítás szükséges mérete az eredeti adatbázisból (megteszi). A 0 a regiszter száma. A modr/m bájt a processzor által megadott adatstruktúra, amely így néz ki:

      (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)  
  • Mivel regisztrációval dolgozunk, a mod mező 0b11.

  • A reg mező az általunk használt regisztráció száma, 0b000
  • Mivel ebben az utasításban csak egy regisztráció van, ezért a rm mezőt ki kell töltenünk valamivel. Erre valók a /0 mezőben megadott extra adatok, ezért ezt a rm mezőbe helyeztük, 0b000.
  • A modr/m bájt tehát 0b11000000 vagy 0xC0. Ezt adjuk ki.
  • Ezután következik a ib,s. Ez egy aláírt azonnali bájtot ad meg. Megnézzük az operandusokat, és megjegyezzük, hogy azonnali elérhető érték. Átalakítjuk aláírt bájttá, és kimeneti (42 => 0x2A).

A teljes összerakott utasítás tehát a következő: 0x83 0xC0 0x2A. Küldje el a kimeneti modulnak, egy megjegyzéssel együtt, hogy egyik bájt sem képez memória hivatkozást (a kimeneti modulnak tudnia kell

Ismételje meg minden utasítást. Kövesse nyomon a címkéket, hogy tudja, mit kell beilleszteni, amikor hivatkoznak rájuk. Adjon hozzá olyan makrókat és irányelveket, amelyek átkerülnek az objektumfájl kimeneti moduljaihoz. És egy összeállító alapvetően így működik.

Megjegyzések

  • Köszönöm. Remek magyarázat, de ‘ t ” 0x83 0xC0 0x2A ” helyett ” 0x83 0xB0 0x2A ” mert 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 … igen, te ‘ teljesen igazad van. 🙂

Válasz

A gyakorlatban egy összeszerelő általában nem állít elő közvetlenül valamilyen bináris futtatható fájlt , de néhányat objektumfájl (később át kell tölteni a linkelőre ). Vannak azonban kivételek (néhány összeszerelővel közvetlenül létrehozhat néhány bináris futtatható fájlt ; ritkák).

Először is vegye figyelembe, hogy sok összeszerelő ma ingyenes szoftver program. Tehát töltse le és fordítsa le a számítógépére a forrást A GNU kódja (a binutils része) és a nasm . Ezután tanulmányozza a forráskódjukat. BTW, javaslom a Linux használatát erre a célra (ez egy nagyon fejlesztőbarát és szabad szoftver-barát operációs rendszer).

Az assembler által létrehozott objektumfájl egy kódszegmenst tartalmaz és áthelyezési utasítások. Jól dokumentált fájlformátumban van megszervezve, amely az operációs rendszertől függ. Linux rendszeren ez a formátum (objektumfájlokhoz, megosztott könyvtárakhoz, központi kiírásokhoz és futtatható fájlokhoz használatos) ELF . Ezt az objektumfájlt később beviszi a linkelőbe (amely végül futtatható fájlt hoz létre). Az áthelyezéseket az ABI határozza meg (pl. x86-64 ABI ). További információért olvassa el Levine Linkerek és betöltők könyvét. lyukakkal ellátott gépkód (a linkelő kitölti az áthelyezési információk segítségével). Az összeszerelő által generált (áthelyezhető) gépi kód nyilvánvalóan egy utasításkészletre jellemző építészet .A x86 vagy x86-64 (a legtöbb laptop vagy asztali processzor használják) ISA-k rettenetesen összetett részleteikben. De oktatási célokra találtak le egy egyszerűsített részhalmazt, az úgynevezett y86 vagy y86-64 nevet. Olvassa el rajtuk a diákat . Erre a kérdésre adott más válaszok is megmagyarázzák ezt. Érdemes elolvasnia egy jó könyvet a számítógép-építészetről .

A legtöbb összeszerelő a két lépés , a második áthelyezést bocsát ki, vagy javítja az első menet kimenetének egy részét. Mostanában szokásos elemzési technikákat alkalmaznak (olvassa el talán a A sárkány könyvet ).

Hogyan indít egy futtatható fájlt az OS kernel (pl. hogyan működik a execve rendszerhívás Linux rendszeren ) egy másik (és összetett) kérdés. Általában létrehoz egy virtuális címteret (a folyamatban , ezzel execve (2) …), majd újra inicializálja a folyamat belső állapotát (beleértve a felhasználói módú regisztereket is). Egy dinamikus linker – például ld-linux.so (8) Linux alatt – lehet futás közben. Olvasson el egy jó könyvet, például Operációs rendszer: Három egyszerű darab . A OSDEV wiki szintén hasznos információkat nyújt.

PS. Kérdése olyan széles, hogy több könyvet is el kell olvasnia róla. Adtam néhány (nagyon hiányos) referenciát. Többet kellene találnia belőlük.

Megjegyzések

  • Az objektumfájl-formátumok tekintetében kezdőnek I ‘ d javasoljuk, hogy nézze meg a NASM által készített RDOFF formátumot. Ezt szándékosan úgy tervezték, hogy a lehető legegyszerűbb legyen, és a lehető legtöbb helyzetben működjön. A NASM forrás tartalmaz linkert és betöltőt a formátumhoz. (Teljes nyilvánosságra hozatal – ezeket mind megterveztem és megírtam)

Vélemény, hozzászólás?

Az email címet nem tesszük közzé. A kötelező mezőket * karakterrel jelöltük