Programowanie w asemblerze Uruchamianie programów 17 stycznia 2017
Uruchamianie programów Przy uruchamianiu i badaniu zachowania programów systemowych używa się wielu narzędzi. Prostsze z ich, takie jak strace, pozwalaja pasywnie śledzić działanie programu. Bardziej skomplikowane sa debuggery, pozwalajace zatrzymywać program i analizować zawartość pamięci.
Programy śledzace Do śledzenia wywołań systemowych służy w Linuksie program strace (w innych Uniksach istnieja podobne narzędzia, np. truss). Informacje prezentowane przez strace można również wydobyć z podsystemu /proc. Majac kultowy program w C #include <stdio.h> int main (int argc, char **argv) printf("witaj świecie\n"); spróbujemy go prześledzić używajac strace.
Programy śledzace katastrofa:~/akpn$ gcc -static -o hello hello.c katastrofa:~/akpn$ strace./hello execve("./hello", ["./hello"], [/* 54 vars */]) = 0 fcntl64(0, F_GETFD) = 0 fcntl64(1, F_GETFD) = 0 fcntl64(2, F_GETFD) = 0 geteuid32() = 1000 getuid32() = 1000 getegid32() = 100 getgid32() = 100 brk(0) = 0x80ad8f4 brk(0x80ae8f4) = 0x80ae8f4 brk(0x80af000) = 0x80af000 fstat64(1, st_mode=s_ifchr 0620, st_rdev=makedev(136, 3),...) = 0 old_mmap(null, 4096, PROT_READ PROT_WRITE, MAP_PRIVATE MAP_ANONYMOUS, write(1, "Witaj \266wiecie\n", 14Witaj świecie ) = 14 munmap(0x40000000, 4096) = 0 exit_group(14) =? katastrofa:~/akpn$
Programy śledzace Uwaga: Opcja -static znaczaco zmniejsza długość śladu, ale nie należy jej używać bez potrzeby. strace pokazuje wszystkie wykonane wywołania systemowe wraz z argumentami i zwracanymi wartościami.
Programy śledzace Popatrzmy na wersję w asemblerze section.text global _start ;dla linkera (ld) msg db Witaj świecie!,0xa ;napis do wypisania len equ $ - msg ;i jego długość _start: ;początek mov edx,len ;długość mov ecx,msg ;adres napisu mov ebx,1 ;deskryptor (stdout) mov eax,4 ;wywołanie systemowe write int 0x80 mov eax,1 ;wywołanie systemowe exit int 0x80
Programy śledzace Teraz ślad będzie znacznie krótszy katastrofa:~/akpn$ nasm -f elf hello.asm katastrofa:~/akpn$ ld -o hello hello.o katastrofa:~/akpn$ strace./hello execve("./hello", ["./hello"], [/* 54 vars */]) = 0 write(1, "Witaj świecie!\n", 15Witaj świecie! ) = 15 _exit(1) =? katastrofa:~/akpn$
Programy śledzace Na zakończenie szef kuchni poleca katastrofa:~/akpn$ strace strace./hello
Posługiwanie się debuggerem Debugger jest to narzędzie, które pozwala zatrzymać wykonywany program i zbadać lub zmienić jego stan. Typowy debugger pozwala: określać miejsca (nazywane przystankami breakpoints), w których program powinien zatrzymać się i przekazać sterowanie debuggerowi; wykonywać program krokowo, tzn. zatrzymywać się każdorazowo po wykonaniu pojedynczej instrukcji; ogladać i zmieniać wartości zmiennych zatrzymanego programu (a także instrukcje), po czym wznawiać jego działanie. Dodatkowo niektóre debuggery pozwalaja określać warunki, których spełnienie powoduje zatrzymanie programu.
Posługiwanie się debuggerem Modyfikacje programu przez debugger sa dokonywane bezpośrednio na obrazie programu w pamięci i nie zmieniaja kodu źródłowego. Z NASMem dobrze współpracował program ald przeznaczony przede wszystkim do znajdowania błędów w programach systemowych. Niestety od jakiegoś czasu (2004) nie jest już rozwijany i pozostało nam gdb ale nauczyło się już składni NASMa.
Posługiwanie się debuggerem Wróćmy do naszego pliku hello.asm Po zasemblowaniu i zlinkowaniu przystapimy do ogladania i modyfikacji naszego pliku. $ nasm -g -f elf hello.asm $ ld -o hello hello.o... Najpierw musimy wywołać program debuggera: $ gdb hello... (gdb) Debugger oczekuje na nasze polecenia, sygnalizujac to promptem gdb.
Posługiwanie się debuggerem Kończymy pracę poleceniem quit (wiele poleceń można skracać, w tym przypadku wystarcza q) (gdb) q $ _ Moim ulubionym poleceniem jest help goraco polecam (bo podręcznik gdb ma kilkaset stron)!
Posługiwanie się debuggerem Do ogladania zawartości rejestrów służy polecenie (gdb) info registers eax 0x22c8e0 2279648 ecx 0x226070 2252912 edx 0x21bd10 2211088 ebx 0x22bfc4 2277316 esp 0xbffff2a0 0xbffff2a0 ebp 0x0 0x0 esi 0xbffff2ac -1073745236 edi 0x80481c0 134513088 eip 0x80481c4 0x80481c4 <_start+4> eflags 0x200282 [ SF IF ID ] cs 0x73 115 ss 0x7b 123 ds 0x7b 123 es 0x7b 123 fs 0x0 0 gs 0x33 51
Posługiwanie się debuggerem Polecenie info ma wiele innych przydatnych argumentów. Zawartość rejestru można zmienić poleceniem set $eax=1 Poleceniem p (print) (gdb) print $xmm0 można dokładniej obejrzeć pojedynczy rejestr (zwłaszcza gdy zawiera wartości upakowane). Polecenie x (od examine) pozwala precyzyjnie sterować wyświetlaniem. Polecenie where wyświetla stos wywołań.
Programowanie Podstawowym przeznaczeniem debuggera (dla nas) jest testowanie niewielkich programów w języku wewnętrznym. Polecenie list pozwala obejrzeć kod (części) programu: parametr to numer środkowej linii w wyświetlanym ciagu instrukcji.
Programowanie Używajac set disassembly-flavor intel będziemy mieli składnię zbliżona do NASM. (gdb) list 8 3 section.data 4 output: 5 db The processor Vendor ID is xxxxxxxxxxxx \n 6 output_len equ $-output 7 8 section.text 9 global _start 10 _start: 11 mov eax,0 12 cpuid
Uruchomienie wykonania Do uruchamiania programu przeznaczone sa następujace polecenia. Polecenie (gdb) run powoduje uruchomienie wykonania programu od poczatku. Polecenie (gdb) continue kontynuuje wykonanie zatrzymanego program poczynajac od bieżacej instrukcji (zgodnie z bieżac a zawartościa rejestru EIP).
Punkty zatrzymania Wykonanie programu odbywa się do końca, chyba że określono przystanki (breakpoints). Sa one adresami instrukcji, po dojściu do których wykonywanie programu ma zostać przerwane, a sterowanie powróci do debuggera. Ustawiamy je poleceniem break, podajac np. numer instrukcji (numery instrukcji można uzyskać poleceniem list): (gdb) break 25
Tryb krokowy Dodatkowe polecenia uruchamiajace sa przeznaczone do pracy w trybie krokowym. Polecenie step w najprostszej postaci powoduje wykonanie pojedynczej instrukcji. (gdb) step 26 mov ebx, 0x8048231 Opcjonalny argument pozwala określić liczbę rozkazów, które maja zostać wykonane. (gdb) step 5
Tryb krokowy Uwaga: Jeśli bieżac a instrukcja jest int lub call, to użycie polecenia step spowoduje wejście w śledzenie procedury obsługi przerwania lub procedury bibliotecznej (która bywa dłuuuuga). Polecenie next pozwala uniknac wchodzenia w głab procedur. (gdb) next 5 27 test eax, eax
Dodatkowe informacje Przystanki sa realizowane zwykle przez wstawienie w odpowiednie miejsca w programie poniższej instrukcji (z zachowaniem poprzedniej zawartości i odtworzeniem jej po jakimkolwiek zatrzymaniu). int 3 Instrukcję taka warto również umieszczać na końcu testowanego programu. Śledzenie krokowe realizuje się przez ustawienie flagi TF w procesorze.