Pierwszą rzeczą, której potrzebujesz, jest coś takiego jak ten plik . Jest to baza danych instrukcji dla procesorów x86 używanych przez asembler NASM (który pomogłem napisać, choć nie części, które faktycznie tłumaczą instrukcje). Wybierzmy dowolną linię z bazy danych:
ADD rm32,imm8 [mi: hle o32 83 /0 ib,s] 386,LOCK
Oznacza to, że opisuje instrukcję ADD
. Istnieje wiele wariantów tej instrukcji, a konkretny, który jest tutaj opisany, jest wariantem, który przyjmuje 32-bitowy rejestr lub adres pamięci i dodaje natychmiastową wartość 8-bitową (tj. Stałą bezpośrednio zawartą w instrukcji). Przykładowa instrukcja montażu, która użyłaby tej wersji, to:
add eax, 42
Teraz musisz wprowadzić tekst i parsować go w poszczególnych instrukcjach i operandach. W przypadku powyższej instrukcji prawdopodobnie spowodowałoby to strukturę zawierającą instrukcję ADD
oraz tablicę operandów (odniesienie do rejestru EAX
i wartości 42
). Po zbudowaniu tej struktury, przeszukujesz bazę danych instrukcji i znajdujesz wiersz, który pasuje zarówno do nazwy instrukcji, jak i do typów operandów. Jeśli nie znajdziesz dopasowania, oznacza to błąd, który musi zostać przedstawiony użytkownikowi („niedozwolona kombinacja opkodu i operandów” lub podobnym jest zwykłym tekstem).
Kiedy mamy już wiersz z bazy danych, patrzymy na trzecią kolumnę, która dla tej instrukcji to:
[mi: hle o32 83 /0 ib,s]
To jest zestaw instrukcji opisujących, jak wygenerować wymaganą instrukcję kodu maszynowego:
mi
Jest descriptiuon z operandów: onu modr/m
(rejestr lub pamięć) operand (co oznacza, że musimy dołączyć modr/m
bajt do końca instrukcji, która Przyjdziemy później) i jeden natychmiastowy instrukcji (która być użyte w opisie instrukcji).
- Dalej jest
hle
. To określa, w jaki sposób obsługujemy prefiks „blokady”. Nie użyliśmy „blokady”, więc go ignorujemy.
- Dalej jest
o32
. To mówi nam, że jeśli gromadzimy kod dla 16-bitowego formatu wyjściowego, instrukcja wymaga przedrostka wielkości operandu. Gdybyśmy produkowali 16-bitowe wyjście, wyprodukowalibyśmy teraz prefiks ( 0x66
), ale zakładam, że nie jesteśmy i będziemy kontynuować.
- Dalej jest
83
. Jest to dosłowny bajt w systemie szesnastkowym. Wydajemy to.
Dalej jest /0
. To określa dodatkowe bity, które będą nam potrzebne w bajcie modr / m, i spowoduje, że je wygenerujemy. modr/m
Bajt służy do rejestrów koduje lub pośrednie odniesienia pamięci. Mamy jeden taki operand, rejestr. Rejestr ma numer określony w innym pliku danych :
eax REG_EAX reg32 0
Sprawdzamy, czy reg32
zgadza się z wymaganym rozmiarem instrukcji z oryginalnej bazy danych (tak jest). Jest 0
to numer rejestru. modr/m
Bajt jest strukturą danych określony przez procesor, który wygląda tak:
(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)
Ponieważ pracujemy z rejestrem, mod
pole jest 0b11
.
reg
Pole jest numer rejestru używamy,0b000
- Ponieważ w tej instrukcji jest tylko jeden rejestr, musimy
rm
coś wypełnić . To właśnie te dodatkowe dane określone w /0
był za, więc stawiamy, że w rm
polu 0b000
.
modr/m
Bajt jest zatem 0b11000000
albo 0xC0
. Wyprowadzamy to.
- Dalej jest
ib,s
. Określa podpisany natychmiastowy bajt. Patrzymy na operandy i zauważamy, że mamy dostępną natychmiastową wartość. Konwertujemy go na bajt ze znakiem i wyprowadzamy ( 42
=> 0x2A
).
Pełna instrukcja montażu jest zatem: 0x83 0xC0 0x2A
. Wyślij go do modułu wyjściowego wraz z uwagą, że żaden z bajtów nie stanowi odniesienia do pamięci (moduł wyjściowy może wymagać wiedzieć, czy tak jest).
Powtórz dla każdej instrukcji. Śledź etykiety, abyś wiedział, co wstawić, gdy są do nich odniesienia. Dodaj udogodnienia dla makr i dyrektyw, które są przekazywane do modułów wyjściowych plików obiektowych. I tak w zasadzie działa asembler.