2.7 Skoki bezwarunkoweInstrukcje zaliczane do skoków bezwarunkowych to instrukcje, które dokonują modyfikacji rejestru IP lub CS:IP bez testowania stanu rejestru flag. Czyli różnica względem skoków warunkowych jest taka, że skok bezwarunkowy wykonywany jest zawsze. Dodatkowo można za pomocą niektórych instrukcji skoczyć do innego segmentu zmieniając wartość rejestru CS co w przypadku skoków warunkowych jest niemożliwe. Podstawowe instrukcje zaliczające się do takich skoków to JMP, CALL, RET, INT. JMPInstrukcja powodująca bezpośredni skok do pamięci o wskazanym adresie jako argument. Skoki JMP (Jump) rozróżnia się ze względu na to czy skok modyfikuje zawartość obu rejestrów CS:IP (skok daleki FAR) czy tylko rejestru IP (skok bliski NEAR). Skoki bliskie można podzielić jeszcze ze względu na to czy adres docelowy podany jest bezpośrednio czy też jako przesunięcie względem obecnej wartości rejestru IP. Wszystkie te rodzaje skoków postaram się teraz opisać. Skok daleki, "Far"Skoki dalekie charakteryzują się tym, że zmieniają jednocześnie wartości rejestrów CS i IP. W kodzie można stosować dwa warianty tego typu instrukcji różniące się tym w jaki sposób przekazujemy nowe wartości rejestrów. Najczęściej wartości określające miejsce skoku przekazuje się w postaci bezpośredniej (direct) np.: far direct:
jmp 12:101
Podajemy w pierwszej kolejności numer segmentu, a po dwukropku przesunięcie względem jego początku. Oczywiście numer segmentu jest zawsze liczbą 16 bitową, a przesunięcie w zależności od trybu pracy procesora może być 16,32 lub 64 bitowe. Drugą możliwością jest przekazanie adresu w sposób pośredni (indirect) przez pamięć. W tym przypadku adres docelowy skoku zostaje umieszczony gdzieś w pamięci, a jako parametr dla instrukcji przekazujemy adres tej pamięci np.: far indirect:
jmp far [update_cs]
...
update_cs dw 101
dw 12
Skok bliski bezwzględny, "Near absolute"Skok ten należy do grupy skoków bliskich (Near), a więc modyfikuje jedynie zawartość rejestru wskaźnika IP. Pierwszą przedstawioną wersją jest skok pośredni, oznacza to iż adres podawany przy skoku nie jest kodowany w instrukcji tylko znajduje się w pamięci lub w rejestrze. W przypadku podawania adresu po przez pamięć należy podać w wywołaniu instrukcji adres pamięci np.: near absolute indirect:
jmp near [update]
...
update dw 101
W przypadku pominięcia dyrektywy near skok i tak zostanie standardowo zinterpretowany przez kompilator jako bliski jednak lepiej samemu zadbać o poprawny kod. Po wykonaniu przedstawionego skoku do rejestru IP zostanie wpisana wartość znajdująca się w pamięci pod etykietą update. Adres docelowy skoku można również przekazać pośrednio przez rejestr np. bx. near absolute direct:
mov bx, 101
jmp bx
W tym przypadku do rejestru IP zostanie skopiowana zawartość rejestru bx. Skok bliski względny, "Near retative"Różnica w stosunku do skoku bezwzględnego polega jedynie na formie podawanego adresu, który jest zapisany jako względne przesunięcie. Instrukcja skoku przyjmuje argument bezpośredni, który zostaje zapisany w kodzie instrukcji. Docelowa wartość rejestru IP po skoku będzie wynosiła IP = adres_względny + IP org 0x100 mov ax, 0 jmp near skip1 skip0: mov ax, 0x4C00 int 0x21 skip1: xor bx, bx jmp skip0 Po przekompilowaniu wygląda następująco: 0747:0100 B80000 MOV AX,0000
0747:0103 E90500 JMP 010B
0747:0106 B8004C MOV AX,4C00
0747:0109 CD21 INT 21
0747:010B 31DB XOR BX,BX
0747:010D EBF7 JMP 0106
Pod adresem 0x747:0x103 mamy instrukcję skoku bezwarunkowego. Jego kod składa się z trzech kolejnych bajtów 0xE9, 0x05 i 0x00. Pierwszy bajt jest kodem informującym o tym iż jest to instrukcja skoku zawierająca w dwóch kolejnych bajtach wartość względnego przesunięcia. W komputerach klasy PC wartości danych składających się z więcej niż z jednego bajta zapisywane są w pamięci w konwencji little endian. Oznacza to iż bajty odpowiadające najmniej znaczącym częścią liczby zapisywane są w mniejszych adresach. Tak więc aby złożyć wartości 0x05 i 0x00 w wartość o rozmiarze dwóch bajtów należy przestawić je w odwrotnej kolejności czyli otrzymujemy 0x0005. Jest to przesunięcie względne dla danego skoku. Liczone jest ono względem adresu instrukcji następującej po instrukcji skoku. Adres instrukcji następującej to 0x747:0x106 aby otrzymać adres docelowy skoku należy do części offsetowej dodać 0x0005 czyli otrzymujemy adres 0x747:0x10B. Pod adresem 0x747:0x10D mamy kolejny skok, który jest tym samym typem skoku czyli bliskim względnym. Różnica polega jedynie na tym iż ten skok względne przesunięcie zapisuje w jednym bajcie. Kod instrukcji to 0xEB, a przesunięcie to 0xF7. W tym przypadku przeliczając adres docelowy skoku na początek bierzemy adres następnej instrukcji czyli 0x747:0x10F. Przesunięcie jest zapisywane w kodzie U2 ponieważ może przyjmować zarówno wartości ujemne jak i dodatnie. Wartość 0xF7 dekodujemy zgodnie z U2 na -9, w związku z tym musimy odjąć od części offsetowej -9 co daje w wyniku adres 0x747:0x106. W poprzednim programie jak widać wprowadziłem dodatkowo kolejny podział skoków na krótkie i długie. Jak widać w programie prog12 instrukcje bliskie o adresie względnym standardowo przez NASM kompilowane są do kodu z przesunięciem względnym w jednym bajcie. Gdyby było inaczej to możemy wymusić takie przesunięcie po przez prefiks short. Jeżeli chcemy aby została użyta instrukcja z adresem o rozmiarze dwóch bajtów trzeba użyć dyrektywy near. To jakiej standardowo formy użyje kompilator NASM zależy od tego czy włączyliśmy optymalizowanie kodu. Dla zainteresowanych odsyłam do dokumentacji NASM:The -O Option: Specifying Multipass Optimization Jako, że ilość typów skoków może być trudna do ogarnięcia postaram się je przedstawić w tabeli.
Dogłębna wiedza na temat soków bezwarunkowych w formie opisanej przeze mnie jest potrzebna jeżeli chcemy bardzo dobrze poznać język. Dla osób chcących jedynie pisać programy bez szczególnej wiedzy jak one są później zapisywane w postaci kodu maszynowego wystarczy zapamiętać podstawowy podział skoków. Skoki bliskie zmieniają tylko zawartość rejestru IP i te są najczęściej używane. Jeżeli z jakiś względów będziemy musieli dodatkowo zmienić zawartość rejestru CS to wtedy zastosujemy skok daleki FAR. Cały powyższy tekst miał jedynie na celu skatalogowanie wszystkich możliwych form zapisu adresu docelowego w tych dwóch rodzajach skoków.
|