Programowanie w asemblerze Asembler i jego jezyk
Transkrypt
Programowanie w asemblerze Asembler i jego jezyk
Programowanie w asemblerze Asembler i jego jezyk ˛ Zbigniew Jurkiewicz, Instytut Informatyki UW 17 stycznia 2017 Zbigniew Jurkiewicz, Instytut Informatyki UW Programowanie w asemblerze Asembler i jego jezyk ˛ Asembler Asembler = program, przetwarzajacy ˛ plik źródłowy z tekstem programu w symbolicznym jezyku ˛ wewnetrznym ˛ na plik wynikowy, tzw. moduł (ang. object code). Moduł zawiera ten sam program w postaci przetłumaczonej na jezyk ˛ maszynowy. Oprócz pliku wynikowego asembler produkuje jeszcze pliki zawierajace ˛ wydruk programu oraz liste˛ odwołań (ang. cross-references). W trakcie zajeć ˛ posługiwać sie˛ bedziemy ˛ głównie asemblerem nasm. Zbigniew Jurkiewicz, Instytut Informatyki UW Programowanie w asemblerze Asembler i jego jezyk ˛ Asemblacja W celu przetłumaczenia programu znajdujacego ˛ sie˛ na pliku program.asm należy napisać bash-2.04$ nasm -f elf program.asm Zbigniew Jurkiewicz, Instytut Informatyki UW Programowanie w asemblerze Asembler i jego jezyk ˛ Linkowanie Ponieważ program może składać sie˛ z kilku modułów, przed załadowaniem musimy poddać go (być może wraz z innymi modułami) procesowi konsolidacji (zwanemu potocznie linkowaniem). W przypadku programu znajdujacego ˛ sie˛ w pojedynczym pliku wynikowym konsolidator wywołujemy przez bash-2.04$ ld program.o Zbigniew Jurkiewicz, Instytut Informatyki UW Programowanie w asemblerze Asembler i jego jezyk ˛ Linkowanie Konsolidator normalnie tworzy plik wynikowy o nazwie a.out. Chcac ˛ otrzymać plik o innej nazwie, np. program, należy użyć opcji -o bash-2.04$ ld -o program program.o Zbigniew Jurkiewicz, Instytut Informatyki UW Programowanie w asemblerze Asembler i jego jezyk ˛ Asembler Jezyk ˛ asemblera ułatwia zapisywanie programów w jezyku ˛ wewnetrznym, ˛ dzieki ˛ wielu udogodnieniom, nie naruszajacych ˛ jednakże podstawowej zasady: Pojedynczej instrukcji jezyka ˛ wewnetrznego ˛ odpowiada dokładnie jedna instrukcja jezyka ˛ asemblera. Zamiast adresów można używać symbolicznych identyfikatorów. Oprócz instrukcji jezyka ˛ wewnetrznego ˛ w programie moga˛ wystapić ˛ dodatkowe linie — polecenia (zwane też pseudoinstrukcjami). Sa˛ one wykonywane w trakcie asemblacji — przekształcania takiego programu na jezyk ˛ wewnetrzny. ˛ Inaczej mówiac, ˛ steruja˛ przebiegiem asemblacji. Wiele asemblerów oferuje dodatkowe mechanizmy, takie jak definiowanie makrooperacji i asemblacja warunkowa. Zbigniew Jurkiewicz, Instytut Informatyki UW Programowanie w asemblerze Asembler i jego jezyk ˛ Składnia Instrukcje zapisywane w jezyku ˛ asemblera maja˛ nastepuj ˛ ac ˛ a˛ postać: hetykietai: hoperacjai hargumentyi ;hkomentarzi na przykład start: add eax,ebx ;To jest instrukcja Zbigniew Jurkiewicz, Instytut Informatyki UW Programowanie w asemblerze Asembler i jego jezyk ˛ Składnia Wymagane jest jedynie pole operacji, pozostałe moga˛ być puste. Etykieta jest symboliczna˛ reprezentacja˛ adresu instrukcji, używanego w instrukcjach sterujacych, ˛ np. jmp start Etykiety w programie w asemblerze powinny wystapić ˛ w pierwszej kolumnie i być zakończone dwukropkiem. Zbigniew Jurkiewicz, Instytut Informatyki UW Programowanie w asemblerze Asembler i jego jezyk ˛ Składnia Polecenia maja˛ nieco inna˛ postać: hnazwai hpoleceniei hargumentyi Zbigniew Jurkiewicz, Instytut Informatyki UW ;hkomentarzi Programowanie w asemblerze Asembler i jego jezyk ˛ Stałe W programie moga˛ wystapić ˛ nastepuj ˛ ace ˛ rodzaje stałych: binarne ciag ˛ cyfr binarnych zakończony litera˛ B, np. 10110011B. dziesietne ˛ szesnastkowe należy je poprzedzać przez 0x, np. piszemy 0xA5. znakowe ciag ˛ znaków umieszczonych w apostrofach, np. ’Ala’. Zbigniew Jurkiewicz, Instytut Informatyki UW Programowanie w asemblerze Asembler i jego jezyk ˛ Stałe Stałe symboliczne można definiować używajac ˛ polecenia equ rozmiar equ 10 ... add ebx,rozmiar Zbigniew Jurkiewicz, Instytut Informatyki UW Programowanie w asemblerze Asembler i jego jezyk ˛ Symbole Symboliczne nazwy służa˛ do identyfikowania zmiennych, stałych, etykiet, segmentów itp. Z każdym symbolem zwiazane ˛ sa˛ dodatkowe informacje zwane atrybutami, takie jak sekcja, adres i typ. Wyróżniony symbol $ oznacza licznik asemblacji, jego wartościa˛ jest zawsze offset (czyli adres) bieżaco ˛ asemblowanej linii programu. Jest on zerowany przy rozpocz˛eciu asemblacji segmentu. Zbigniew Jurkiewicz, Instytut Informatyki UW Programowanie w asemblerze Asembler i jego jezyk ˛ Wyrażenia W argumentach instrukcji i poleceń moga˛ wystapić ˛ wyrażenia. Asembler zastepuje ˛ je ich obliczona˛ wartościa, ˛ np. poniższy kod używa wyrażenia do określenia długości pewnego obszaru danych info dl_info db 1,2,3,’Jakiś napis’,10 equ $-info Zbigniew Jurkiewicz, Instytut Informatyki UW Programowanie w asemblerze Asembler i jego jezyk ˛ Rezerwacja pamieci ˛ („zmienne”) Polecenia rezerwacji pamieci ˛ służa˛ do deklarowania i rezerwowania komórek danych oraz ewentualnego inicjowania ich zawartości. Rezerwacja jednego lub kilku bajtów ma postać hnazwai db hwartość poczatkowai[,...] ˛ na przykład bajt lista kom db 5 db 1,2,3,4 db ’Komunikat’,10 Zbigniew Jurkiewicz, Instytut Informatyki UW Programowanie w asemblerze Asembler i jego jezyk ˛ Rezerwacja pamieci ˛ („zmienne”) Jeśli nie wymagamy inicjalizacji należy używać polecenia resb klawisz resb 1 z podana˛ specyfikacja˛ liczby rezerwowanych komórek (w tym przypadku 1), co pozwala rezerwować ciag ˛ komórek, np. wektor resb 25 Zbigniew Jurkiewicz, Instytut Informatyki UW Programowanie w asemblerze Asembler i jego jezyk ˛ Rezerwacja pamieci ˛ („zmienne”) Jeśli chcemy zarezerwować obszar danych i wypełnić go powtarzajacymi ˛ sie˛ wartościami, należy skorzystać z prefiksu times, z argumentem podajacym ˛ liczbe˛ powtórzeń liczby: times 17 db 31 okresowe: times 30 db 1,2,3,4 (w ostatnim przypadku kolejne komórki inicjowane bed ˛ a˛ wartościami 1, 2, 3, 4, 1, 2, 3, 4, 1,...). Zbigniew Jurkiewicz, Instytut Informatyki UW Programowanie w asemblerze Asembler i jego jezyk ˛ Rezerwacja pamieci ˛ („zmienne”) Polecenie dw rezerwuje słowo pamieci ˛ (dwa bajty), zaś polecenie dd podwójne słowo (cztery bajty). Przypominamy, że zapis oper dw 0x4315 równoważny jest (na procesorach Intela) oper db 0x15,0x43 Zbigniew Jurkiewicz, Instytut Informatyki UW Programowanie w asemblerze Asembler i jego jezyk ˛ Rezerwacja pamieci ˛ („zmienne”) Niestety asembler NASM nie zapamietuje ˛ rozmiarów zwiazanych ˛ z symbolami („typów”), dlatego czasem trzeba prefiksować argument adresowy podajac ˛ rozmiar, np. mov word [oper],1 Zbigniew Jurkiewicz, Instytut Informatyki UW Programowanie w asemblerze Asembler i jego jezyk ˛ Sekcje Program napisany w asemblerze składa sie˛ z sekcji. Każda sekcja rozpoczyna sie˛ poleceniem section, np. aby zadeklarować sekcje˛ z instrukcjami („kodem”) programu piszemy section .text ... Zbigniew Jurkiewicz, Instytut Informatyki UW Programowanie w asemblerze Asembler i jego jezyk ˛ Sekcje Standardowe nazwy sekcji to .text — zawiera instrukcje wykonywalne, .data — zawiera dane inicjowane oraz .bss — zawiera dane nie inicjowane. Zbigniew Jurkiewicz, Instytut Informatyki UW Programowanie w asemblerze Asembler i jego jezyk ˛ Sekcje Oczywiście można definiować własne sekcje o innych nazwach. Dodatkowymi atrybutami określa sie˛ wtedy przeznaczenie sekcji. Zbigniew Jurkiewicz, Instytut Informatyki UW Programowanie w asemblerze Asembler i jego jezyk ˛ Eksportowanie Procedury wywoływane tylko lokalnie w module równoważne sa˛ etykietom w kodzie i nie musza˛ być deklarowane. Procedury przeznaczone do wywoływania z innych modułów należy eksportować – deklarować jako globalne. Moduł (być może jedyny) zawierajacy ˛ program główny powinien eksportować symbol _start. Zbigniew Jurkiewicz, Instytut Informatyki UW Programowanie w asemblerze Asembler i jego jezyk ˛ Eksportowanie Symbole eksportowane z modułu (np. nazwy procedur) deklaruje sie˛ poleceniem global hsymboli,... natomiast symbole importowane z innych modułów poleceniem extern hsymboli,... Zbigniew Jurkiewicz, Instytut Informatyki UW Programowanie w asemblerze Asembler i jego jezyk ˛ Struktura programu Program w jezyku ˛ asemblera powinien mieć wiec ˛ nastepuj ˛ ac ˛ a˛ strukture: ˛ ;; Definicje stałych ... ;; Sekcja danych section .data ... ;; Sekcja kodu section .text global _start _start: ... ... mov eax,1 int 0x80 Zbigniew Jurkiewicz, Instytut Informatyki UW ;Program główny;-) Programowanie w asemblerze Asembler i jego jezyk ˛ Struktura programu ;; Procedury (w tym samym segmencie) ... ;; Globalne nie inicjowane dane robocze section .bss ... ;; Koniec programu Zbigniew Jurkiewicz, Instytut Informatyki UW Programowanie w asemblerze Asembler i jego jezyk ˛ Proste programy Pierwszy program bedzie ˛ obliczać silnie˛ liczby 4. Wynik zostanie zwrócony na zewnatrz ˛ jako argument wywołania systemowego exit. Wywołania systemowe służa˛ do komunikacji z systemem operacyjnym. W Linuksie sa˛ realizowane przerwaniem programowym 0x80 lub instrukcja˛ syscall. W rejestrze eax umieszcza sie˛ numer wywołania, a w pozostałych argumenty. Zbigniew Jurkiewicz, Instytut Informatyki UW Programowanie w asemblerze Asembler i jego jezyk ˛ Proste programy section .text global _start ;Declaracja dla linkera (ld) ;; Wywołania systemowe SYS_EXIT equ 1 ;; Argument ARG equ 4 Zbigniew Jurkiewicz, Instytut Informatyki UW Programowanie w asemblerze Asembler i jego jezyk ˛ Proste programy _start: l1: mov ebx,1 mov ecx,ARG cmp ecx,1 jle koniec imul ebx,ecx dec ecx jmp l1 koniec: mov eax,SYS_EXIT int 0x80 Zbigniew Jurkiewicz, Instytut Informatyki UW ;Poczatek ˛ programu (punkt wej ;Obliczana wartość ;Czy już koniec? ;Zmniejszamy argument ;numer wywołania systemowego ;wywołanie systemowe Programowanie w asemblerze Asembler i jego jezyk ˛ Proste programy Przed uruchomieniem program musimy zasemblować i zlinkować. $ nasm -f elf pierwszy.asm $ ld -o pierwszy pierwszy.o $ ./pierwszy $ echo $? 24 Zbigniew Jurkiewicz, Instytut Informatyki UW Programowanie w asemblerze Asembler i jego jezyk ˛ Proste programy — wypisywanie Najprostszy program wyświetlajacy ˛ na ekranie konsoli „Witaj, świecie!” mógłby wygladać ˛ tak: STDOUT section .text global _start ;Declaracja dla linkera (ld) equ 1 ;Standardowe wyjście (deskry ;; Wywołania systemowe SYS_EXIT SYS_WRITE equ 1 equ 4 Zbigniew Jurkiewicz, Instytut Informatyki UW Programowanie w asemblerze Asembler i jego jezyk ˛ Proste programy — wypisywanie _start: mov mov mov mov int edx,dlug ecx,napis ebx,STDOUT eax,SYS_WRITE 0x80 mov ebx,0 mov eax,SYS_EXIT int 0x80 ;Poczatek ˛ programu (punkt we ;długość bufora (arg 3) ;adres bufora (arg 2) ;deskryptor pliku (arg 1) ;numer wywołania systemowego ;wywołanie systemowe ;poprawny powrót (arg 1) ;numer wywołania systemowego ;wywołanie systemowe section .data ;; Komunikat do wypisania napis dlug db ’Witaj świecie!’,0xa equ $-msg Zbigniew Jurkiewicz, Instytut Informatyki UW ;napis zakończony znak ;długość napisu Programowanie w asemblerze Asembler i jego jezyk ˛ Proste programy — wypisywanie Sekcja .data w zasadzie była zbedna, ˛ komunikat do wypisania można było umieścić w sekcji .text, ponieważ nie jest on modyfikowany. Zbigniew Jurkiewicz, Instytut Informatyki UW Programowanie w asemblerze Asembler i jego jezyk ˛ Procedury Procedury wywołuje sie˛ instrukcja˛ call hnazwai Zakończenie procedury i powrót do miejsca wywołania nastepuje ˛ jedynie po wykonaniu instrukcji ret Nie ma domyślnego zakończenia po napotkaniu końca tekstu procedury, jak w niektórych jezykach ˛ programowania. Adres powrotny jest przekazywany (automatycznie) na wierzchołku stosu. Zbigniew Jurkiewicz, Instytut Informatyki UW Programowanie w asemblerze Asembler i jego jezyk ˛ Parametry Dopóki korzystamy jedynie z własnych procedur pisanych w asemblerze, sposób przekazywania parametrów i zwracania wyników jest nasza˛ sprawa. ˛ Inaczej bedzie, ˛ gdy program nasz ma wywoływać procedury biblioteczne (np. wywołania systemowe Unixa) lub komunikować sie˛ z procedurami pisanymi w jezyku ˛ C. Zbigniew Jurkiewicz, Instytut Informatyki UW Programowanie w asemblerze Asembler i jego jezyk ˛ Parametry Istnieja˛ dwa typowe sposoby przekazywania parametrów. Pierwszy z nich to umieszczenie wszystkich parametrów na stosie. Z tej metody korzysta sie˛ dla wywołań systemowych w systemach BSD. Drugi sposób to przekazanie parametrów w ustalonych rejestrach. W ten sposób przekazuje sie˛ w systemie Linux parametry dla wywołań systemowych. Zbigniew Jurkiewicz, Instytut Informatyki UW Programowanie w asemblerze Asembler i jego jezyk ˛ Parametry Oczywiście możliwe sa˛ również techniki pośrednie, np. kilka pierwszych parametrów w rejestrach, a reszta na stosie. Jest to konieczne, gdy liczba parametrów jest wieksza ˛ niż liczba dostepnych ˛ rejestrów (nb. dla wywołań systemowych nie ma tego problemu). Zwracana wartość również może znajdować sie˛ w rejestrze lub ustalonym miejscu na stosie. Zbigniew Jurkiewicz, Instytut Informatyki UW Programowanie w asemblerze Asembler i jego jezyk ˛ Parametry Ponieważ korzystamy z systemu Linux, argumenty dla wywołań systemowych przekazywać powinniśmy w kolejnych rejestrach (ebx, ecx, edx dla trybu 32-bitowego), natomiast wynik znajdziemy w rejestrze eax. Zbigniew Jurkiewicz, Instytut Informatyki UW Programowanie w asemblerze Asembler i jego jezyk ˛ Prosty program z procedura˛ Zmodyfikujemy nasz program na liczenie silni tak, aby wydzielić jej obliczanie w osobna˛ procedure. ˛ section .text global _start ;Declaracja dla linkera (ld) ;; Wywołania systemowe SYS_EXIT equ 1 ;; Argument ARG equ 4 Zbigniew Jurkiewicz, Instytut Informatyki UW Programowanie w asemblerze Asembler i jego jezyk ˛ Prosty program z procedura˛ _start: ;Poczatek ˛ programu (punkt we push dword ARG call silnia mov ebx,eax mov eax,SYS_EXIT int 0x80 Zbigniew Jurkiewicz, Instytut Informatyki UW ;Wynik b˛ edzie w eax ;numer wywołania systemowego ;wywołanie systemowe Programowanie w asemblerze Asembler i jego jezyk ˛ Prosty program z procedura˛ silnia: push ebp mov ebp,esp push ebx mov ebx,[ebp+8] mov eax,1 l1: cmp ebx,1 jle koniec imul eax,ebx dec ebx jmp l1 koniec: pop ebx pop ebp ret Zbigniew Jurkiewicz, Instytut Informatyki UW ;Argument (pierwszy i jedyny ;Obliczana wartość ;Czy już koniec? ;Zmniejszamy argument Programowanie w asemblerze Asembler i jego jezyk ˛ Zachowywanie rejestrów Pamietajmy ˛ o zachowywaniu i odtwarzaniu rejestrów, których zawartość jest niszczone przez wywoływana˛ procedure. ˛ Może to być dokonywane w miejscu wywołania procedury (strategia caller saves) lub w samej procedurze (callee saves). Zbigniew Jurkiewicz, Instytut Informatyki UW Programowanie w asemblerze Asembler i jego jezyk ˛ Zachowywanie rejestrów Powszechnie używana jest ta druga strategia. Istnieja˛ po temu dwa powody. Po pierwsze, w przypadku zmian w kodzie procedury powodujacych ˛ użycie nowego rejestru należy odnaleźć i sprawdzić wszystkie wywołania tej procedury. Po drugie, jeśli zachowanie rejestrów znajduje sie˛ w procedurze, to realizujace ˛ je ciagi ˛ instrukcji push i pop wystapi ˛ a˛ tylko raz, a wiec ˛ kod bedzie ˛ krótszy. Zbigniew Jurkiewicz, Instytut Informatyki UW Programowanie w asemblerze Asembler i jego jezyk ˛ Prosty program z procedura˛ T˛e sama˛ procedure˛ można zapisać rekurencyjnie. section .text global _start ;Declaracja dla linkera (ld) ;; Wywołania systemowe SYS_EXIT equ 1 ;; Argument ARG equ 4 _start: ;Poczatek ˛ programu (punkt wej push dword ARG call silnia mov ebx,eax mov eax,SYS_EXIT int 0x80 Zbigniew Jurkiewicz, Instytut Informatyki UW ;Wynik b˛ edzie w eax ;numer wywołania systemowego ;wywołanie systemowe Programowanie w asemblerze Asembler i jego jezyk ˛ Prosty program z procedura˛ silnia: push ebp mov ebp,esp mov eax,[ebp+8] cmp eax,1 jle koniec dec eax push eax call silnia add esp,4 imul eax,[ebp+8] koniec: pop ebp ret Zbigniew Jurkiewicz, Instytut Informatyki UW ;Argument (pierwszy i jedyny) ;Czy już koniec? ;Zmniejszamy argument ;Wynik b˛ edzie w eax ;Czyścimy stos Programowanie w asemblerze Asembler i jego jezyk ˛ Procedury biblioteczne Spróbujmy w programie „powitalnym” zamiast wywołania systemowego wywołać biblioteczna˛ procedure˛ printf. section .text global _start extern printf,exit _start: ;Declaracja dla linkera (ld) ;Poczatek ˛ programu (punkt we push napis call printf push dword 0 call exit section .data ;; Komunikat do wypisania napis db ’Witaj świecie!’,0xa,0 Zbigniew Jurkiewicz, Instytut Informatyki UW ;napis zakończony zn Programowanie w asemblerze Asembler i jego jezyk ˛ Procedury biblioteczne Zmieni sie˛ sposób linkowania programu $ nasm -f elf szosty.asm $ ld -o szosty szosty.o -lc -dynamic-linker /lib/ld-linu $ ./szosty Witaj świecie! Opcja -l podaje nazwe˛ żadanej ˛ biblioteki (z pominieciem ˛ przedrostka lib, natomiast opcja -dynamic-linker umożliwia dynamiczne wiazanie ˛ bibliotek. Zbigniew Jurkiewicz, Instytut Informatyki UW Programowanie w asemblerze Asembler i jego jezyk ˛ Procedury z argumentami bezpośrednio w kodzie Argumenty dla procedur można również umieszczać bezpośrednio w sekcji kodu, choć rzadko korzysta sie˛ z tej możliwości. Przykład takiego wywołania: ... call drukuj db ’Witaj świecie’,0 ... ;Napis zakończony zerem! Procedura drukuj otrzyma wtedy na wierzchołku stosu tylko adres swojego argumentu — napisu. Użyje go do wydrukowania napisu, po czym zamieni na stosie na otrzymany „przy okazji” drukowania adres powrotny (miejsce w kodzie bezpośrednio za argumentami). Zbigniew Jurkiewicz, Instytut Informatyki UW Programowanie w asemblerze Asembler i jego jezyk ˛ Procedury z argumentami bezpośrednio w kodzie drukuj: pop eax l1: cmp byte [eax], 0 je l2 push eax mov edx,1 mov ecx,eax mov ebx,1 mov eax,SYS_WRITE int 0x80 pop eax inc eax jmp l1 l2: inc eax push eax ret Zbigniew Jurkiewicz, Instytut Informatyki UW Programowanie w asemblerze Asembler i jego jezyk ˛ Komunikacja z programami w jezyku ˛ C Cz˛esto procedury pisane w asemblerze stanowia˛ fragment programu pisanego w jezyku ˛ programowania wyższego poziomu, np. C, Lispie lub Pascalu. Procedura taka powinna wtedy uwzgledniać ˛ konwencje stosowane przez używany kompilator. Dla kompilatora gcc argumenty sa˛ przekazywane na stosie podzielonym na ramki (rekordy aktywacji) procedur. Bieżac ˛ a˛ ramk˛e wskazuje rejestr ebp. Aby mieć wygodny dostep ˛ do parametrów, należy ten rejestr zachować i przestawić na wierzchołek swojej ramki. Nazywamy to prologiem procedury. Podobnie przed zakończeniem procedury należy odtworzyć stara˛ zawartość rejestru ramki. Zbigniew Jurkiewicz, Instytut Informatyki UW Programowanie w asemblerze Asembler i jego jezyk ˛ Komunikacja z programami w jezyku ˛ C W sumie szkielet prostej procedury ma nastepuj ˛ ac ˛ a˛ postać proc: push ebp mov ebp,esp mov eax,[ebp+8] ... mov eax,hwyniki pop ebp ret Zbigniew Jurkiewicz, Instytut Informatyki UW ;pierwszy argument Programowanie w asemblerze Asembler i jego jezyk ˛ Komunikacja z programami w jezyku ˛ C Istnieja˛ dwie specjalne instrukcje enter i leave do używania zamiast prologu i epilogu. W najprostszym przypadku powyższa˛ procedure˛ można również zapisać nastepuj ˛ aco ˛ proc: enter 0,0 mov eax,[ebp+8] ... mov eax,hwyniki leave ret Zbigniew Jurkiewicz, Instytut Informatyki UW ;pierwszy argument Programowanie w asemblerze Asembler i jego jezyk ˛ Komunikacja z programami w jezyku ˛ C Ponadto procedury w asemblerze powinny zachować zawartość rejestrów ebx, esi, edi (sa˛ one używane dla zmiennych deklarowanych jako register) oraz cs, ds, es i ss. Zbigniew Jurkiewicz, Instytut Informatyki UW Programowanie w asemblerze Asembler i jego jezyk ˛ Komunikacja z programami w jezyku ˛ C Popatrzmy na (nieco „zakrecony”) ˛ program powitalny w kombinacji asemblera i C. Najpierw cz˛eść w asemblerze hello adres: section .data db ’Witaj swiecie!’,0 section.text global adres lea eax,[hello] ret Zbigniew Jurkiewicz, Instytut Informatyki UW Programowanie w asemblerze Asembler i jego jezyk ˛ Komunikacja z programami w jezyku ˛ C A teraz cz˛eść w C #include <stdio.h> extern char *adres(void); int main() { printf("%s\n", adres()); return 0; } Zbigniew Jurkiewicz, Instytut Informatyki UW Programowanie w asemblerze Asembler i jego jezyk ˛ Przydatne instrukcje Popatrzmy jeszcze na kilka mniej znanych instrukcji. Zaczniemy od instrukcji petli. ˛ W dotychczasowych przykładach do sterowania iteracja˛ używaliśmy porównań w połaczeniu ˛ ze skokami warunkowymi. Zamiast tego można użyć instrukcji loop i jej odmian: W rejestrze licznika ecx/rcx ustawiamy liczbe˛ iteracji. P˛etle˛ rozpoczynamy etykieta, ˛ np. iter1 Na końcu petli ˛ umieszczamy instrukcje˛ loop iter1 P˛etla wykona sie˛ tyle razy, ile wynosiła wartość licznika (ale co najmniej raz!). Zbigniew Jurkiewicz, Instytut Informatyki UW Programowanie w asemblerze Asembler i jego jezyk ˛ Przykład: inicjowanie bufora (od tyłu) mov ecx,rozmiar mov eax,wartosc jecxz dalej iter: mov [bufor-4+ecx*4],eax loop iter dalej: Zbigniew Jurkiewicz, Instytut Informatyki UW Programowanie w asemblerze Asembler i jego jezyk ˛ Przykład: szukanie końca napisu z C mov ecx,100 mov ebx,0 iter: mov al,[bufor+ebx] inc ebx cmp al,0 loopne iter je znaleziono ;długość bufora ;poczatkowy ˛ indeks ;kolejny znak Zbigniew Jurkiewicz, Instytut Informatyki UW Programowanie w asemblerze Asembler i jego jezyk ˛ Pobieranie adresu efektywnego Przy wywołaniach systemowych cz˛esto argumentem jest adres. Nie zawsze jest on stała, ˛ czasem powstaje np. przez zsumowanie adresu poczatku ˛ i indeksu znajdujacego ˛ sie˛ w rejestrze. Wymaga to obliczenia adresu efektywnego mov ecx,bufor add ecx,esi ;Indeks mamy w ESI Ten sam efekt można uzyskać instrukcja˛ lea. Wyznacza ona adres efektywny swojego drugiego argumentu, po czym umieszcza ten adres w rejestrze podanym jako pierwszy argument lea ecx,[bufor+esi] Zbigniew Jurkiewicz, Instytut Informatyki UW Programowanie w asemblerze Asembler i jego jezyk ˛ Pobieranie adresu efektywnego Używajac ˛ zaawansowanych skalowanych trybów adresowych procesora Pentium można cz˛esto w ten sposób obliczać w jednym cyklu złożone wyrażenia, np. lea eax,[edx*4 + edx + 7] powoduje umieszczenie w rejestrze eax wartości wyrażenia 5 * edx + 7. W NASM można zreszta˛ te˛ instrukcje˛ zapisać jeszcze prościej lea eax,[edx*5 + 7] ale to już specyficzne obczujnienie parsera instrukcji. Uwaga: instrukcja lea nie ustawia flag. Zbigniew Jurkiewicz, Instytut Informatyki UW Programowanie w asemblerze Asembler i jego jezyk ˛ Rozszerzanie Cz˛esto w programie chcemy rozszerzyć argument, np. załadować bajt z pamieci ˛ do rejestru czterobajtowego. W przypadku gdy argument zawiera liczbe˛ należy użyć instrukcji movsx hrejestri,hrejestr-lub-pami˛ eći Instrukcja ta rozszerza prawidłowo znak argumentu, np. jeśli rejestr ax zawiera liczbe˛ -2345, to po wykonaniu instrukcji movsx ebx,ax również rejestr ebx bedzie ˛ zawierał -2345 (ale reprezentowane na 32 bitach). Zbigniew Jurkiewicz, Instytut Informatyki UW Programowanie w asemblerze Asembler i jego jezyk ˛ Rozszerzanie Jeśli argument nie zawiera liczby (np. jest to kod ASCII znaku), to należy użyć instrukcji movzx, np. jeśli rejestr al zawiera wartość 177 (kod litery a w kodzie 8859-2), to aby przepisać ja˛ do rejestru ebx użyjemy movzx ebx,al Instrukcja ta dodaje zawsze na poczatku ˛ zera. Zbigniew Jurkiewicz, Instytut Informatyki UW Programowanie w asemblerze Asembler i jego jezyk ˛ Sprawdzanie i ustawianie pojedynczych bitów Podstawowe instrukcje służace ˛ do ustawiania flag warunków to test i and. W procesorach Pentium dodatkowo wystepuje ˛ instrukcja bt źródło,numer-bitu przepisujaca ˛ ze źródła wskazany numerem bit do flagi CF. Numer bitu może być podany stała˛ bajtowa˛ lub rejestrem. Jeśli źródło jest rejestrem, to numer bitu nie może być wiekszy niż rozmiar rejestru, natomiast jeśli jest to adres w pamieci, ˛ to nie ma takich ograniczeń. Ułatwia to implementacje˛ tablic bitowych. Zbigniew Jurkiewicz, Instytut Informatyki UW Programowanie w asemblerze Asembler i jego jezyk ˛ Sprawdzanie i ustawianie pojedynczych bitów Oprócz instrukcji bt istnieja˛ instrukcje btc, bts i btr, służace ˛ do ustawiania pojedynczych bitów. Przydaja˛ sie˛ przede wszystkim do realizacji mechanizmów synchronizacji, takich jak semafory. Należy pamietać, ˛ że dla rejestrów instrukcja bt jest wolniejsza niż test. Zbigniew Jurkiewicz, Instytut Informatyki UW Programowanie w asemblerze Asembler i jego jezyk ˛ Instrukcje blokowe Instrukcje blokowe służa˛ do szybkiego przetwarzania ciagów ˛ bajtów (lub wiekszych ˛ jednostek), np. do szybkiego przeniesienia zawartości fragmentu pamieci ˛ ekranu w inny obszar. Zasady: adres poczatku ˛ bloku źródłowego umieszczamy w rejestrach ds:esi, adres poczatku ˛ bloku docelowego umieszczamy w rejestrach es:edi, długość bloku podaje rejestr ecx (w jednostkach odpowiednich do używanych instrukcji: bajtach, słowach itd.), kolejne dane przechodza˛ przez rejestr al (lub ax, eax). Zbigniew Jurkiewicz, Instytut Informatyki UW Programowanie w asemblerze Asembler i jego jezyk ˛ Instrukcje blokowe Przykład: wypełnienie bufora ekranu o adresie podanym w edi znakiem i atrybutem, podanymi w rejestrach al i ah (można te˛ technik˛e zastosować do bezpośredniego operowania na fizycznej pamieci ˛ ekranu, ale zwykle w Linuksie nie mamy do tego dostatecznych uprawnień). Bez instrukcji blokowych można to zrobić nastepuj ˛ aco: ˛ fill: fill1: push ecx push edi mov cx,25*80 mov [edi],ax inc edi inc edi loop fill1 pop edi pop ecx ret Zbigniew Jurkiewicz, Instytut Informatyki UW Programowanie w asemblerze Asembler i jego jezyk ˛ Instrukcje blokowe Użyjemy teraz instrukcji stosw, która zapisuje zawartość rejestru ax do komórki pamieci ˛ o adresie podanym w edi, po czym automatycznie zwieksza ˛ edi o 2. fill: fill1: push ecx push edi mov cx,25*80 cld stosw loop fill1 pop edi pop ecx ret Instrukcja cld ustawia flage˛ kierunku DF w procesorze. Stan tej flagi decyduje o kierunku kopiowania (dla zera adresy sa˛ zwiekszane). ˛ Zbigniew Jurkiewicz, Instytut Informatyki UW Programowanie w asemblerze Asembler i jego jezyk ˛ Instrukcje blokowe Aby jeszcze bardziej uprościć nasz program możemy skorzystać z przedrostka rep. Jego użycie powoduje wielokrotne wykonanie poprzedzonej nim instrukcji z każdorazowym zmniejszaniem rejestru ecx. Zakończenie nastepuje ˛ po wyzerowaniu rejestru ecx. fill: push ecx push edi mov ecx,25*80 cld rep stosw pop edi pop ecx ret Zbigniew Jurkiewicz, Instytut Informatyki UW Programowanie w asemblerze Asembler i jego jezyk ˛ Instrukcje blokowe Inny przykład: nalezy przekopiować ciag ˛ 60 bajtów, którego adres poczatkowy ˛ znajduje sie˛ w esi w do bufora o adresie podanym w edi (zakładamy, że obszary nie nakładaja˛ sie). ˛ copy: pushf push eax push ecx push esi push edi mov ecx,60 cld rep movsb pop edi pop esi pop ecx pop eax popf ret Zbigniew Jurkiewicz, Instytut Informatyki UW Programowanie w asemblerze Asembler i jego jezyk ˛ Instrukcje blokowe Uwaga: w ostatnim przykładzie zachowujemy poprzedni stan flagi DF na stosie (wraz z innymi flagami procesora). Jest to wymagane w programach systemowych (zwłaszcza obsługujacych ˛ przerwania) i procedurach bibliotecznych. Zbigniew Jurkiewicz, Instytut Informatyki UW Programowanie w asemblerze Asembler i jego jezyk ˛ Wstawianie pliku Do tekstu programu można w trakcie asemblacji właczać ˛ zawartość dodatkowych plików, np. zawierajacych ˛ definicje cz˛esto używanych stałych i makr. Robi sie˛ to poleceniem %include, np. aby wstawić zawartość pliku moje-makra.asm piszemy %include "moje-makra.asm" Zbigniew Jurkiewicz, Instytut Informatyki UW Programowanie w asemblerze Asembler i jego jezyk ˛ Makra Makra w asemblerze NASM dziela˛ sie˛ na jedno i wielowierszowe. Zasada działania jest podobna: makrowołanie w tekście jest w czasie asemblacji zastepowane ˛ rozwiniet ˛ a˛ postacia˛ makra — jego treścia. ˛ Zbigniew Jurkiewicz, Instytut Informatyki UW Programowanie w asemblerze Asembler i jego jezyk ˛ Makra Makra jednowierszowe odpowiadajace ˛ wyrażeniom definiuje sie˛ poleceniem %define. Najprostsze makra jednowierszowe to definicje stałych, np. %define TCGETS 0x5401 Poczawszy ˛ od tego miejsca w programie wszystkie wystapienia ˛ symbolu TCGETS zostana˛ tekstowo zastapione stała˛ 0x5401. Oczywiście ten sam efekt można osiagn ˛ ać ˛ korzystajac ˛ z polecenia equ. Zbigniew Jurkiewicz, Instytut Informatyki UW Programowanie w asemblerze Asembler i jego jezyk ˛ Makra Definicja stałej może być jednak bardziej złożona, np. stanowić ciag ˛ elementów składniowych %define CTRL 0x1F & W tym przypadku wiersz mov byte [ebx+4], CTRL ’D’ zostanie zastapiony ˛ przez mov byte [ebx+4], 0x1F & ’D’ Zbigniew Jurkiewicz, Instytut Informatyki UW Programowanie w asemblerze Asembler i jego jezyk ˛ Makra Makra jednowierszowe moga˛ mieć parametry. Każdy parameter w definicji makra zastapiony ˛ zostanie tekstowo argumentem makrowołania, np. dla makra %define param(n) ([ebp + 4 * (n) + 4]) wiersz mov edx,param(2) zostanie zastapiony ˛ przez mov edx,[ebp+12] Zbigniew Jurkiewicz, Instytut Informatyki UW Programowanie w asemblerze Asembler i jego jezyk ˛ Makra Nawiasy użyte w makrodefinicji zapobiegaja˛ nieodpowiedniej kolejności obliczania. Generalna zasada: otaczamy nawiasami cała˛ treść makra, a wewnatrz ˛ niej otaczamy nawiasami każde wystapienie ˛ parametru. Czasami niektóre nawiasy można pominać, ˛ np. w naszej definicji zewnetrzna ˛ para nawiasów jest zbedna. ˛ Zbigniew Jurkiewicz, Instytut Informatyki UW Programowanie w asemblerze Asembler i jego jezyk ˛ Makra NASM dopuszcza przeciażanie ˛ (overloading) makr – definiowanie tych samych makr z inna˛ liczba˛ parametrów. Pozwala to używać argumentów domyślnych, np. %define zwieksz(x) %define zwieksz(x,y) Zbigniew Jurkiewicz, Instytut Informatyki UW ((x) + 1) ((x) + (y)) Programowanie w asemblerze Asembler i jego jezyk ˛ Makra Makra wielowierszowe definiuje sie˛ poleceniem %macro. Ich makrowołania zastepowane ˛ sa˛ ciagami ˛ instrukcji i poleceń. Najprostsze sa˛ makra bezparametrowe, np. zakończenie pracy programu %macro exit 0 mov eax,1 int 0x80 %endmacro Zbigniew Jurkiewicz, Instytut Informatyki UW Programowanie w asemblerze Asembler i jego jezyk ˛ Makra Tak zdefiniowanego makra można użyć w programie jako nowej „instrukcji” ... exit ... Zbigniew Jurkiewicz, Instytut Informatyki UW Programowanie w asemblerze Asembler i jego jezyk ˛ Makra Makroinstrukcja zerowania rejestru wymaga już parametru %macro clr 1 xor %1,%1 %endmacro i może być wywołana nastepuj ˛ aco ˛ ... clr eax ... Argument w nagłówku makrodefinicji podaje liczbe˛ parametrów, do kolejnych parametrów odwołujemy sie˛ w treści piszac ˛ %1,%2,.... Zbigniew Jurkiewicz, Instytut Informatyki UW Programowanie w asemblerze Asembler i jego jezyk ˛ Makra Podobnie dla standardowego poczatku procedury możemy zdefiniować jednoargumentowe makro prolog %macro prolog 1 push ebp mov ebp,esp push ebx push esi push edi sub esp,%1 %endmacro ;rezerwacja na stosie podanej liczb Tak zdefiniowanego makra można użyć piszac ˛ mojafunkcja: prolog 12 Zbigniew Jurkiewicz, Instytut Informatyki UW Programowanie w asemblerze Asembler i jego jezyk ˛ Makra %0 oznacza liczbe˛ podanych parametrów i może być wykorzystany np. przy asemblacji warunkowej (zob. dalej) ... %rep %0 ... %endrep Zbigniew Jurkiewicz, Instytut Informatyki UW Programowanie w asemblerze Asembler i jego jezyk ˛ Makra W bardziej złożonych makroinstrukcjach (np. zawierajacych ˛ petl ˛ e) ˛ moga˛ być potrzebne etykiety. Przy wielokrotnym wystapieniu makrowołania w kodzie powodowałoby to bład ˛ powtórzenia symbolu, dlatego etykiety lokalne w treści makra należy poprzedzać znakami %%. Podczas każdego rozwijania makra asembler wygeneruje w ich miejsce nowe, unikalne nazwy %macro abs 1 cmp %1,0 jge %%skip neg %1 %%skip: %endmacro Zbigniew Jurkiewicz, Instytut Informatyki UW Programowanie w asemblerze Asembler i jego jezyk ˛ Makra Ostrzeżenie: makra sa˛ niebezpieczne Złożone makra cz˛esto używaja˛ dodatkowych rejestrów, których nie widać w makrowołaniu. Łatwo wtedy zapomnieć o ich zachowywaniu. W jezykach ˛ wyższego poziomu czasem używa sie˛ makr zamiast procedur po to, aby mieć dostep ˛ do „nielokalnych” zmiennych. W asemblerze nie ma takiej potrzeby („wszystko widać”), wiec ˛ nie należy używać makr zamiast procedur. Zbigniew Jurkiewicz, Instytut Informatyki UW Programowanie w asemblerze Asembler i jego jezyk ˛ Asemblacja warunkowa Asemblacja warunkowa pozwala z tego samego tekstu generować programy binarne na różne środowiska obliczeniowe, np. różne wersje systemu operacyjnego. Jednak najcz˛estsze wykorzystanie to właczenie ˛ do tekstu dodatkowych instrukcji, używanych tylko podczas uruchamiania programu. Zbigniew Jurkiewicz, Instytut Informatyki UW Programowanie w asemblerze Asembler i jego jezyk ˛ Asemblacja warunkowa Zwykle używa sie do tego symbolu DEBUG i konstrukcji %ifdef hsymboli hciag ˛ instrukcjii %endif Jeśli hsymboli jest zdefiniowany, to hciag ˛ instrukcjii zostanie przetworzony i właczony ˛ do programu, w przeciwnym razie bedzie ˛ pominiety, ˛ np. %ifdef DEBUG call wypisz_stan %endif spowoduje wywołanie procedury wypisz_stan tylko jeśli w trakcie asemblacji zdefiniowany był symbol DEBUG. Zbigniew Jurkiewicz, Instytut Informatyki UW Programowanie w asemblerze Asembler i jego jezyk ˛ Asemblacja warunkowa Ogólniejsza˛ konstrukcja˛ do asemblacji warunkowej jest %if hwaruneki ... %elif hwaruneki ... %else ... %endif Oczywiście hwarunek i sprawdzany jest w trakcie asemblacji. Zbigniew Jurkiewicz, Instytut Informatyki UW Programowanie w asemblerze Asembler i jego jezyk ˛ Asemblacja warunkowa Inna˛ wygodna˛ konstrukcja˛ jest iteracja %rep hni ... %endrep Powoduje ona hni-krotne przetworzenie swojej treści. Używajac ˛ powyższych konstrukcji można wypełnić obszar danych kolejnymi liczbami całkowitymi. %assign kolejna 0 %rep 64 dd kolejna %assign kolejna kolejna + 1 %endrep Zbigniew Jurkiewicz, Instytut Informatyki UW Programowanie w asemblerze Asembler i jego jezyk ˛ Asemblacja warunkowa Konstrukcja %assign pozwala nadawać i zmieniać wartości zmiennym asemblera, tak jak w powyższym przykładzie. W odróżnieniu od stałych definiowanych przez %define wartość jest natychmiast obliczana i musi być liczba. ˛ Zbigniew Jurkiewicz, Instytut Informatyki UW Programowanie w asemblerze Asembler i jego jezyk ˛ Asemblacja warunkowa Do wcześniejszego wyjścia z petli ˛ %rep służy %exitrep, np. ... %if suma > 65000 %exitrep %endif ... Zbigniew Jurkiewicz, Instytut Informatyki UW Programowanie w asemblerze Asembler i jego jezyk ˛ Struktury danych (rekordy) Strukturalne typy danych (zwane też rekordami) definiujemy używajac ˛ konstrukcji: hpole1i: struc hmój_typi resb 1 ... endstruc Powoduje to utworzenie stałych o nazwach hpole1i,... Wartościa˛ każdej z nich jest offset – odległość poczatku ˛ pola od poczatku ˛ struktury. Zbigniew Jurkiewicz, Instytut Informatyki UW Programowanie w asemblerze Asembler i jego jezyk ˛ Struktury danych (rekordy) Obiekty zdefiniowanego typu deklarujemy używajac ˛ polecenia istruc: hmój_obji: istruc hmój_typi at hpoleii, db hwartość_poczatkowai ˛ ... iend Opcjonalne polecenia at służa˛ do inicjowania pól, musi być przy tym zachowana kolejność pól z definicji. Zbigniew Jurkiewicz, Instytut Informatyki UW Programowanie w asemblerze Asembler i jego jezyk ˛ Struktury danych (rekordy) Do pól rekordu odwołujemy sie˛ przez offset htypi.hpolei lub po prostu hpolei, np. mov eax,[ebx + hpolei] Oczywiście nie ma prawie żadnej kontroli zgodności typów. Zbigniew Jurkiewicz, Instytut Informatyki UW Programowanie w asemblerze Asembler i jego jezyk ˛ Asembler Gnu (gas) Asemblacja: as hello.s -o hello.o Linkowanie (jeśli używamy bibliotek): ld -dynamic-linker /lib/ld-linux.so.2 -o hello hello. Dla biblioteki ld -shared -o hellolib.so hello.o Zbigniew Jurkiewicz, Instytut Informatyki UW Programowanie w asemblerze Asembler i jego jezyk ˛ Składnia AT&T (z kregów ˛ Unixa) Nazwy rejestrów poprzedzane znakiem ‘%’, np. %eax, %dl. Dzieki ˛ temu w programach w asemblerze można używać dowolnych identyfiaktorów z jezyka ˛ C, bez poprzedzania ich znakiem podkreślenia. Kolejność argumentów dla instrukcji binarnych: najpierw argument źródłowy, potem wynikowy, odwrotnie niż w asemblerach „intelowskich”. Np. instrukcja zapisywana w NASM jako mov eax,edx bedzie ˛ w GAS zapisana jako movl %edx, %eax Komentarze poprzedzane znakiem ‘#’. Zbigniew Jurkiewicz, Instytut Informatyki UW Programowanie w asemblerze Asembler i jego jezyk ˛ Składnia AT&T (z kregów ˛ Unixa) Rozmiar argumentów zapisywany musi być podany sufiksem w mnemoniku operacji: b dla bajtu (8 bitów), w dla słowa (16 bitów), l dla long (32 bity) oraz q dla quad (64 bity). Np. zamiast mov piszemy movl dla rejestrów edx i eax. Argumenty bezpośrednie (stałe i wskaźniki) należy poprzedzać prefiksem $, np. movl $5,%eax Pominiecie ˛ prefiksu $ oznacza potraktowanie argumentu jako adresu, np. movl licznik,%eax umieszcza zawartość zmiennej licznik w rejestrze eax. Zbigniew Jurkiewicz, Instytut Informatyki UW Programowanie w asemblerze Asembler i jego jezyk ˛ Składnia AT&T (z kregów ˛ Unixa) Nawiasy używane sa˛ tylko dla rejestrów adresowych, indeksowania itp. testb $0x80,17(%ebp) Inny zapis trybów indeksowych. Zapis w GASie movl %eax,8(%ebx,%edi,4) odpowiada w NASMie mov [8 + ebx + 4 * edi],eax Składnia GASa to: stały-adres(rejestr-przesuniecia,rejestr-indeksowy,rozmiar˛ elementu) Zbedne ˛ rejestry pomijamy movl dane(,%edi,4),%eax Zbigniew Jurkiewicz, Instytut Informatyki UW Programowanie w asemblerze Asembler i jego jezyk ˛ Dyrektywy gas Nazwa zawsze zaczyna sie˛ kropka. ˛ Właczanie ˛ pliku: .include łinux-calls.s" Deklaracja sekcji podobna .section .text Definiowanie stałych: .equ ile,15 helloworld: .ascii "hello world\n" helloworld_end: equ helloworld_len,helloworld_end - helloworld Zbigniew Jurkiewicz, Instytut Informatyki UW Programowanie w asemblerze Asembler i jego jezyk ˛ Dyrektywy gas Symbole globalne (eksport): .globl _start Rezerwacja pamieci ˛ (w .bss): .lcomm <nazwa>,<rozmiar> Rezerwacja pamieci ˛ z inicjowaniem: .ascii "To jest napis\n\0" .byte 10,12,4 .int 234,1487 .long 2345,-345 Repetycja: .rept 50 .byte 1,2 .endr Deklaracja typu symbolu: .type <symbol>,@function Zbigniew Jurkiewicz, Instytut Informatyki UW Programowanie w asemblerze Asembler i jego jezyk ˛