Jak přejdeme od sestavení ke strojovému kódu (generování kódu)

Existuje snadný způsob, jak si vizualizovat krok mezi sestavením kódu do strojového kódu?

Například pokud v poznámkovém bloku otevřete asi binární soubor, uvidíte textově formátovanou reprezentaci strojového kódu. Předpokládám, že každý bajt (symbol), který vidíte, je odpovídajícím znakem ascii pro jeho binární hodnotu?

Ale jak přejdeme od sestavy k binární, co se děje v zákulisí?

Odpověď

Podívejte se na dokumentaci sady instrukcí a najdete položky jako tato z pic mikrokontrolér pro každou instrukci:

příklad addlw instrukce

Řádek „kódování“ říká jak vypadá tato instrukce v binárním formátu. V tomto případě to vždy začíná 5 jednotkami, pak nezajímaným bitem (který může být buď jeden nebo nula), pak zkratka „k“ znamená doslovný text, který přidáváte.

The prvních pár bitů se nazývá „operační kód“, je pro každou instrukci jedinečných. CPU v podstatě sleduje operační kód, aby zjistilo, o jakou instrukci jde, poté ví, že dekóduje „k“ jako číslo, které má být přidáno.

Je to zdlouhavé, ale není to tak těžké kódovat a dekódovat. Měl jsem třídu undergrad, kde jsme to museli dělat ručně při zkouškách.

Chcete-li skutečně vytvořit úplný spustitelný soubor, musíte také udělat věci, jako je přidělení paměti, výpočet offsetů větví a vložení formát jako ELF v závislosti na operačním systému.

Odpovědět

Opcodes assembleru mají většinou vzájemnou korespondenci s příslušnými strojovými instrukcemi. Jediné, co musíte udělat, je identifikovat každý operační kód v montážním jazyce, namapovat jej na odpovídající strojovou instrukci a zapsat strojovou instrukci do souboru spolu s odpovídajícími parametry (pokud existují). Potom opakujete postup pro každý další operační kód ve zdrojovém souboru.

Vytvoření spustitelného souboru, který se správně načte a spustí v operačním systému, pochopitelně trvá déle než většina slušných asemblerů. mít některé další funkce kromě jednoduchého mapování operačních kódů na strojové pokyny (například makra).

Odpovědět

První potřebujete něco jako tento soubor . Toto je databáze instrukcí pro procesory x86, kterou používá assembler NASM (který jsem pomáhal psát, i když ne části, které skutečně překládají pokyny). Umožňuje vybrat libovolný řádek z databáze:

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

Co to znamená, že popisuje instrukci ADD. Existuje několik variant této instrukce a konkrétní, která je zde popisována, je varianta, která bere buď 32bitový registr nebo adresu paměti a přidává okamžitou 8bitovou hodnotu (tj. Konstantu přímo zahrnutou do instrukce). Příkladem montážního pokynu, který by používal tuto verzi, je tento:

 add eax, 42  

Nyní, musíte vzít svůj textový vstup a rozebrat ho na jednotlivé pokyny a operandy. U výše uvedené instrukce by to pravděpodobně vedlo ke struktuře, která obsahuje instrukci ADD a řadu operandů (odkaz na registr EAX a hodnota 42). Jakmile máte tuto strukturu, projděte databázi instrukcí a najděte řádek, který odpovídá jak názvu instrukce, tak typům operandů. Pokud nenajdete shodu, jedná se o chybu, kterou je třeba uživateli zobrazit (obvyklá je „nelegální kombinace operačních kódů a operandů“ apod.).

Jakmile jsme dostal řádek z databáze, podíváme se na třetí sloupec, který pro tuto instrukci je:

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

Toto je sada pokynů, které popisují, jak generovat instrukci strojového kódu, která je vyžadována:

  • mi je popis operandů: jeden operand modr/m (registr nebo paměť) (což znamená, že budeme muset připojit modr/m bajt konec instrukce, ke kterému přijdeme později) a jedna okamžitá instrukce (která bude použita v popisu instrukce).
  • Další je hle. To určuje, jak zacházíme s předponou „lock“. Nepoužívali jsme „zámek“, takže ho ignorujeme.
  • Další je o32. To nám říká, že pokud sestavujeme kód pro 16- bitový výstupní formát, instrukce vyžaduje předponu přepsání velikosti operandu.Pokud jsme vyráběli 16bitový výstup, vytvoříme nyní předponu (0x66), ale předpokládám, že nebudeme pokračovat a pokračovat.
  • Další je 83. Toto je doslovný bajt v šestnáctkové soustavě. Výstupujeme jej.
  • Další je /0. To určuje některé další bity, které budeme potřebovat v modr / m bytem, a způsobí to jejich generování. modr/m bajt se používá ke kódování registrů nebo nepřímých odkazů na paměť. Máme jediný takový operand, registr. Registr má číslo, které je uvedeno v jiném datovém souboru :

     eax REG_EAX reg32 0  
  • Zkontrolujeme, zda reg32 souhlasí s požadovaná velikost instrukce z původní databáze (ano). 0 je číslo registru. Bajt modr/m je datová struktura určená procesorem, která vypadá takto:

      (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)  
  • Protože pracujeme s registrem, pole mod je 0b11.

  • Pole reg je číslo registru, který používáme, 0b000
  • Protože v této instrukci existuje pouze jeden registr, musíme něčím vyplnit pole rm. K tomu slouží dodatečná data uvedená v /0, takže jsme to vložili do pole rm, 0b000.
  • Bajt modr/m je tedy 0b11000000 nebo 0xC0. Toto vydáme.
  • Další je ib,s. Toto určuje podepsaný okamžitý bajt. Podíváme se na operandy a všimneme si, že máme okamžitý dostupná hodnota. Převedeme ji na podepsaný bajt a vydáme ji (42 => 0x2A).

Kompletní sestavená instrukce je tedy: 0x83 0xC0 0x2A. Zašlete ji do svého výstupního modulu spolu s poznámkou, že žádný z bajtů nepředstavuje odkazy na paměť (výstupní modul může potřebovat vědět pokud ano).

Opakujte pro každou instrukci. Sledujte štítky, abyste věděli, co vložit, když se na ně odkazuje. Přidejte vybavení pro makra a směrnice, které se předávají výstupním modulům souboru objektu. A takto v podstatě funguje assembler.

Komentáře

  • Děkuji. Skvělé vysvětlení, ale nemělo by to být ‚ to by mělo být “ 0x83 0xC0 0x2A “ spíše než “ 0x83 0xB0 0x2A “ protože 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 … jo, máte ‚ úplnou pravdu. 🙂

Odpověď

V praxi je assembler obvykle nevyrábí přímo nějaký binární spustitelný soubor , ale některý objektový soubor (k pozdějšímu odeslání do linkeru ). Existují však výjimky (některá sestavení můžete použít k přímému vytvoření nějakého binárního spustitelného souboru ; jsou neobvyklé).

Nejprve si všimněte, že mnoho assemblerů je dnes bezplatný software . Stáhněte si a zkompilujte zdroj do svého počítače kód GNU as (součást binutils ) a nasm . Pak si prostudujte jejich zdrojový kód. BTW, za tímto účelem doporučuji používat Linux (jedná se o velmi přátelský vývojář a svobodný software).

Soubor objektu vytvořený assemblerem obsahuje zejména segment kódu Pokyny pro a přemístění . Je organizován v dobře zdokumentovaném formátu souboru, který závisí na operačním systému. V systému Linux je tímto formátem (používá se pro soubory objektů, sdílené knihovny, skládky jádra a spustitelné soubory) ELF . Tento objektový soubor je později vložen do linkeru (který nakonec vytvoří spustitelný soubor). Přemístění určuje ABI (např. x86-64 ABI ). Další informace najdete v Levinově knize Linkers and Loaders .

Segment kódu v takovém souboru objektu obsahuje strojový kód s otvory (vyplní pomocí informací o přemístění linker). (přemístitelný) strojový kód vygenerovaný assemblerem je zjevně specifický pro instrukční sadu architektura .ISA x86 nebo x86-64 (používaný ve většině procesorů pro notebooky nebo stolní počítače) jsou strašně v jejich detailech komplexní. Pro účely výuky však byla vyvinuta zjednodušená podmnožina, zvaná y86 nebo y86-64. Přečtěte si na nich snímky . Trochu toho také vysvětlují další odpovědi na tuto otázku. Možná si budete chtít přečíst dobrou knihu o počítačové architektuře .

Většina assemblerů pracuje v dva průchody , druhý vydává přemístění nebo opravuje část výstupu z prvního průchodu. Používají nyní obvyklé techniky syntaktické analýzy (přečtěte si tedy Knihu draků ).

Jak spustitelný soubor spouští jádro OS (např. jak funguje execve systémové volání v systému Linux ) je jiná (a složitá) otázka. Obvykle nastavuje nějaký virtuální adresní prostor (v procesu , který execve (2) …) a poté znovu inicializovat vnitřní stav procesu (včetně registrů uživatelského režimu ). dynamický linker – například ld-linux.so (8) v systému Linux být zapojeni za běhu. Přečtěte si dobrou knihu, například Operating System: Three Easy Pieces . Užitečné informace poskytuje také wiki OSDEV .

PS. Vaše otázka je tak široká, že si o ní musíte přečíst několik knih. Dal jsem několik (velmi neúplných) odkazů. Měli byste je najít více.

Komentáře

  • Pokud jde o formáty souborů objektů, pro začátečníky I ‚ Doporučujeme podívat se na formát RDOFF vytvořený NASM. Toto bylo záměrně navrženo tak, aby bylo co nejjednodušší, jak je reálně možné, a přesto fungovalo v různých situacích. Zdroj NASM obsahuje linker a zavaděč formátu. (Úplné zveřejnění – vše jsem navrhl a napsal)

Napsat komentář

Vaše e-mailová adresa nebude zveřejněna. Vyžadované informace jsou označeny *