Asembler x86/Jak używać debuggera ALD: Różnice pomiędzy wersjami

Usunięta treść Dodana treść
m poprawa linków do podstron podręcznika Asembler x86 z powodu zmiany nazwy
Przemub (dyskusja | edycje)
m rewert - błędny kod asemblera
Linia 1:
<noinclude>{{Spis treści}}</noinclude>
==Wprowadzenie==
Debugger jest programem służącym do analizy kodu wykonywalnego. Pozwala zrozumieć to, co dzieje się w trakcie wykonywania skompilowanego programu jak również odnaleźć ewentualne błędy (z ang. ''bugs''). Wykorzystamy w tym celu program '''ald''' (''Assembly Language Debugger''). Jest to przykładowy debugger przeznaczony dla języka asembler. Pozwala on śledzić wykonywanie programu liniakrok po liniikroku, jak również zatrzymać się tylko w konkretnych miejscach (tzw. pułapkach), by oglądnąć zawartość rejestrów, pamięci i stosu. Dzięki temu w prosty sposób możemy odnaleźć miejsca programu, gdzie jego działanie poszło nie po naszej myśli.
 
==Instalacja==
Linia 38 ⟶ 37:
</source>
 
W przypadku, gdy nie posiadamy konta administratora, skompilowany plik wykonywalny '''ald''' znajdujemy w katalogu ''ald-x.y.z/source'', skąd możemy go skopiować do katalogu, z którego będziemy go wykonywać. W praktyce może to być ten sam katalog, w którym znajdują się pliki ''sasm''. Należy wówczas pamiętać, że w celu uruchomienia debugera wydajemy polecenie:
 
<source lang=bash>
Linia 112 ⟶ 111:
Widzimy, że pierwsze wiersze przykładowego listingu w ostatniej kolumnie do złudzenia przypominają niedawno kompilowany kod źródłowy napisany w języku asembler. Zauważamy jedynie, że używane etykiety często zastąpione są już konkretnymi adresami liczbowymi w używanej pamięci. Lewa kolumna zawiera kolejno adresy pamięci zajmowanej przez dane polecenie. Środkowa kolumna zawiera polecenia w kodzie maszynowym. W tym momencie nie chcemy znać zawartości dalszej pamięci, więc naciskamy klawisz '''q''' a następnie '''ENTER'''.
 
==Wykonanie linia krok-po linii-kroku==
Aby wykonać pierwszą instrukcję, naciskamy '''n''' po czym klawisz '''ENTER'''.
 
Linia 158 ⟶ 157:
 
ald>
eax = 0x00000001 ebx = 0x000000000x00000005 ecx = 0x080490A4 edx = 0x0000000E
esp = 0xBFE4B130 ebp = 0x00000000 esi = 0x00000000 edi = 0x00000000
ds = 0x007B es = 0x007B fs = 0x0000 gs = 0x0000
Linia 166 ⟶ 165:
 
 
080480A0 CD80 int 80h0x80
ald>
Program terminated normally (Exit status: 0x00000x0005)
ald>
</source>
Linia 175 ⟶ 174:
 
==Wstrzymanie wykonania==
W przypadku dłuższych i bardziej skomplikowanych algorytmów wykonywanie liniakrok po liniikroku może być uciążliwe i czasochłonne. Aby usprawnić ten proces możemy ustawić pułapkę (tzw. ''breakpoint'') tylko w miejscu, w którym chcielibyśmy wykonanie programu wstrzymać, by odczytać jego stan i następnie prześledzić liniakrok po liniikroku, w którym miejscu następuje błąd wykonania.
 
Przykładowo poniższy program miał za zadanie wyświetlenie zawartości rejestru EAX w systemie dziesiętnym.
 
<source lang=asm>
;; decimalPrint.asm
;; Wypisuje zawartość EAX w systemie dziesiętnym
;;
 
segment .text
.global _start
_start:
mov eax,0x0007
movl $0007h, %eax
call _printEAXdecimal
#;wyjscie
incl %mov eax,1
incl %mov ebx,5
int $80h0x80
 
 
 
segment .data
msg: .string db "' "'
 
segment .text
 
_printEAXdecimal:
pushlpush %edx
pushlpush %ecx
pushlpush %ebx
pushlpush %eax
 
movlmov $10,%ebx,10
movlmov $10,%ecx,10
xorlxor %edx,%edx
N1:
div ebx
push %ebx
add edx,48
shrl %ebx,%eax
dec ecx
andl %ebx,(%esp)
mov [msg+ecx],dl
pop %ebx
orl $48,%cmp ecx,0
jne N1
decl %ecx
movl %ecx($msg),%dl
testl %ecx,%ecx
jnz N1
 
movlmov $4,%eax,4
movlmov $1,%ebx,1
movlmov $msg,%ecx,msg
movlmov $10,%edx,10
int $80h0x80
 
pop eax
xorl %eax,%eax
pop ebx
popl %eax
pop ecx
popl %ebx
pop edx
popl %ecx
popl %edx
ret
</source>
 
Na pierwszy rzut oka wszystko wydaje się być w porządku. Kompilacja wraz z linkowaniem przebiegła pomyślnie. Próba wykonania programu daje jednak błądponiższy wykonania.wynik:
 
<source lang=bash>
Program nie miał wykonywać żadnych operacji bitowych. Przypuszczamy jednak, że problem może mieć miejsce w wierszu, w którym wykonujemy operację przesunięcia bitowego w prawo (dzielenia przez potęgę dwójki):
user@myhost:~$ ./decimalPrint
Floating point exception
user@myhost:~$
</source>
 
Program nie miał wykonywać żadnych operacji zmiennoprzecinkowych. Przypuszczamy jednak, że problem może mieć miejsce w wierszu, w którym wykonujemy operację dzielenia całkowitego:
 
<source lang=asm>
shrl %div ebx,%eax
</source>
 
Chcielibyśmy zatem wstrzymać działanie programu tuż przed wykonaniem tej operacji, by dokładnie sprawdzić argumenty instrukcji oraz wynik jej działania. W tym celu uruchamiamy debugger poleceniem:
 
<source lang=bashtext>
./user@myhost:~$ ald decimalPrint
Assembly Language Debugger 0.1.7
Copyright (C) 2000-2004 Patrick Alken
 
decimalPrint: ELF Intel 80386 (32 bit), LSB - little endian, Executable, Version 1 (Current)
Loading debugging symbols...(11 symbols loaded)
ald>
</source>
 
Następnie przeprowadzamy deasemblację (klawisz '''d''' i '''ENTER''') w celu poznania adresu wskaźnika do interesującej na instrukcji:
 
<source lang=bashtext>
ald> d
# 08048080:<_start> B807000000 mov eax, 0x7
# 08048085 E80C000000 call near +0xc (0x8048096:_printEAXdecimal)
# 0804808A B801000000 mov eax, 0x1
# 0804808F BB05000000 mov ebx, 0x5
# 08048094 CD80 int 0x80
# 08048096:<_printEAXdecimal> 52 push edx
# 08048097 51 push ecx
# 08048098 53 push ebx
# 08048099 50 push eax
# 0804809A BB0A000000 mov ebx, 0xa
# 0804809F B90A000000 mov ecx, 0xa
# 080480A4 31D2 xor edx, edx
080480A6:<N1> F7F3 div ebx
# ...
#080480A8 080480A6:<N1> F7F3 81C230000000 add edx, 0x30 shr ebx, eax
080480AE 49 dec ecx
# ...
#080480AF 080480A8 8891D8900408 81C230000000 mov byte or edx[ecx+0x80490d8], 0x30 dl
#080480B5 080480AE 81F900000000 49 deccmp ecx, 0x0
#080480BB 080480AF 75E9 8891D8900408 mov byte [ecx jne +0x80490d8],0xe9 (0x80481a6) dl
#080480BD 080480B5 B804000000 81F900000000 test ecxmov eax, ecx0x4
#080480C2 080480BB BB01000000 75E9 mov ebx, 0x1 jne +0xe9 (0x80481a6)
#080480C7 080480BD B804000000B9D8900408 mov eaxecx, 0x4 0x80490d8
#080480CC 080480C2 BB01000000BA0A000000 mov ebxedx, 0x10xa
#080480D1 080480C7 CD80 B9D8900408 mov ecx, 0x80490d8 int 0x80
#080480D3 080480CC 58 BA0A000000 mov edx, 0xa pop eax
Hit <return> to continue, or <q> to quit
# 080480D1 CD80 int 0x80
# 080480D3 58 pop eax
# Hit <return> to continue, or <q> to quit
</source>
 
Naciskamy klawisz '''q''' oraz '''ENTER''', by nie wyświetlać dalszych obszarów pamięci. Szukany adres to ''0x080480A6''. Trzeba zatem pod wskazanym adresem ustawić pułapkę. Do tego celu służy polecenie '''break''':
 
<source lang=bashtext>
ald> break 0x080480a6
# Breakpoint 1 set for 0x080480A6
ald>
</source>
 
Dla pewności możemy wyświetlić wszystkie zdefiniowane pułapki, aby mieć pewność, czy program nie będzie dodatkowo wstrzymany w żadnym innym miejscu. Do tego celu służy polecenie '''lbreak''':
 
<source lang=bashtext>
ald> lbreak
# Num Type Enabled Address IgnoreCount HitCount
# 1 Breakpoint y 0x080480A6 none 0 (N1+0x0)
ald>
</source>
 
Jeśli wszystko się zgadza, możemy uruchomić program poleceniem '''run''':
 
<source lang=bashtext>
ald> run
# Starting program: decimalPrint
# Breakpoint 1 encountered at 0x080480A6
# eax = 0x00000007 ebx = 0x0000000A ecx = 0x0000000A edx = 0x00000000
# esp = 0xBFC9251C ebp = 0x00000000 esi = 0x00000000 edi = 0x00000000
# ds = 0x007B es = 0x007B fs = 0x0000 gs = 0x0000
# ss = 0x007B cs = 0x0073 eip = 0x080480A6 eflags = 0x00000246
 
# Flags: PF ZF IF
 
 
# 080480A6:<N1> F7F3 shrdiv ebx, eax
ald>
</source>
 
Przede wszystkim interesują nas zawartości rejestrów EAX, EBX oraz EDX, gdyż tylko one są argumentami powyższej instrukcji. Dzielna znajduje się w rejestrach EDX:EAX, natomiast dzielnik umieszczony jest w irejestrze EBX. Na tym etapie nie widać żadnych potencjalnych problemów, możemy więc kontynuować wykonanie programu liniakrok po liniikroku używając klawiszapolecenia ENTER'''n''':
 
<source lang=bashtext>
ald> n
eax = 0x00000000 ebx = 0x0000000A ecx = 0x0000000A edx = 0x00000007
esp = 0xBFC9251C ebp = 0x00000000 esi = 0x00000000 edi = 0x00000000
ds = 0x007B es = 0x007B fs = 0x0000 gs = 0x0000
ss = 0x007B cs = 0x0073 eip = 0x080480A8 eflags = 0x00000246
 
Flags: PF ZF IF
# eax = 0x00000000 ebx = 0x0000000A ecx = 0x0000000A edx = 0x00000007
# esp = 0xBFC9251C ebp = 0x00000000 esi = 0x00000000 edi = 0x00000000
# ds = 0x007B es = 0x007B fs = 0x0000 gs = 0x0000
# ss = 0x007B cs = 0x0073 eip = 0x080480A8 eflags = 0x00000246
 
# Flags: PF ZF IF
 
080480A8 81C230000000 add edx, 0x30
ald>
</source>
 
Dzielenie całkowite zostało wykonane poprawnie. Wynik dzielenia znajduje się w rejestrze EAX, natomiast reszta z dzielenia w rejestrze EDX. Być może w następnym przebiegu pętli ujawnią się jakieś problemy. Kontynuujemy zatem wykonanie programu poleceniem '''continue''' (lub w skrócie '''c'''):
# 080480A8 81C230000000 or edx, 0x30
 
<source lang=text>
ald> c
Breakpoint 1 encountered at 0x080480A6
eax = 0x00000000 ebx = 0x0000000A ecx = 0x00000009 edx = 0x00000037
esp = 0xBFC9251C ebp = 0x00000000 esi = 0x00000000 edi = 0x00000000
ds = 0x007B es = 0x007B fs = 0x0000 gs = 0x0000
ss = 0x007B cs = 0x0073 eip = 0x080480A6 eflags = 0x00000206
 
Flags: PF IF
 
 
080480A6:<N1> F7F3 div ebx
ald>
</source>
 
Na pierwszy rzut oka tym razem również wszystko jest w porządku. Jednak po wnikliwej analizie zawartości rejestrów zauważymy, że w rejestrze EDX znajdują się pozostałości z poprzedniego obiegu pętli. Aby uniknąć błędnych wyników, należy poprawić kod tak, aby zerowanie rejestru EDX przeprowadzać na początku każdego obiegu pętli przed wykonaniem operacji dzielenia. Spróbujemy mimo wszystko kontynuować działanie programu krok po kroku:
Przesunięcie bitowe zostało wykonane poprawnie. Wynik dzielenia znajduje się w rejestrze EAX, natomiast reszta z dzielenia w rejestrze EDX. Być może w następnym przebiegu pętli ujawnią się jakieś problemy. Kontynuujemy zatem wykonanie programu poleceniem '''continue''' (lub w skrócie '''c'''):
 
<source lang=bashtext>
ald> n
c
# Breakpoint 1 encountered at 0x080480A6
# eax = 0x00000000 ebx = 0x0000000A ecx = 0x00000009 edx = 0x00000037
# esp = 0xBFC9251C ebp = 0x00000000 esi = 0x00000000 edi = 0x00000000
# ds = 0x007B es = 0x007B fs = 0x0000 gs = 0x0000
# ss = 0x007B cs = 0x0073 eip = 0x080480A6 eflags = 0x00000206
 
Program received signal SIGFPE (Arithmetic exception)
# Flags: PF IF
Location: 0x080480A6
eax = 0x00000000 ebx = 0x0000000A ecx = 0x00000009 edx = 0x00000037
esp = 0xBFBA9ADC ebp = 0x00000000 esi = 0x00000000 edi = 0x00000000
ds = 0x007B es = 0x007B fs = 0x0000 gs = 0x0000
ss = 0x007B cs = 0x0073 eip = 0x080480A6 eflags = 0x00010206
 
Flags: PF IF RF
 
 
# 080480A6:<N1> F7F3 # shr ebx, eax
080480A6:<N1> F7F3 div ebx
ald>
</source>
 
Widzimy, że operacja dzielenia nie została wykonana i zgłoszony jest wyjątek za pomocą sygnału ''SIGFPE''. Kolejna próba wykonania operacji skończy się zamknięciem programu wraz ze zwróceniem kodu błędu:
Na pierwszy rzut oka tym razem również wszystko jest w porządku. Jednak po wnikliwej analizie zawartości rejestrów zauważymy, że w rejestrze EDX znajdują się pozostałości z poprzedniego obiegu pętli. Aby uniknąć błędnych wyników, należy poprawić kod tak, aby zerowanie rejestru EDX przeprowadzać na początku każdego obiegu pętli przed wykonaniem operacji dzielenia. Spróbujemy mimo wszystko kontynuować działanie programu linia po linii używając klawisza ENTER.
 
<source lang=text>
Widzimy, że operacja przesunięcia nie została wykonana.
ald> n
Program terminated with signal SIGFPE (Arithmetic exception)
ald>
</source>
 
Po wprowadzeniu stosownych poprawek prawidłowy kod źródłowy będzie miał postać:
 
<source lang=asm>
;;
.text
;; Wypisuje zawartość EAX w systemie dziesiętnym
.global _start
;;
 
segment .text
global _start
_start:
mov eax,0x0007
movl $0007h, %eax
call _printEAXdecimal
#;wyjscie
incl %mov eax,1
mov ebx,5
int $80h
int 0x80
 
 
 
segment .data
msg: .string db "' "'
 
segment .text
 
_printEAXdecimal:
pushlpush %edx
pushlpush %ecx
pushlpush %ebx
pushlpush %eax
 
movlmov $10,%ebx,10
movlmov $10,%ecx,10
N1:
xorlxor %edx,%edx
div ebx
pushl %ebx
add edx,48
shrl %ebx, %eax
dec ecx
andl %ebx, (%esp)
mov [msg+ecx],dl
popl %ebx
orlcmp $48ecx,%edx0
jne N1
decl %ecx
 
movl %ecx(%msg),%eax
mov eax,4
testl %ecx,%ecx
mov ebx,1
jnz N1
mov ecx,msg
mov edx,10
int 0x80
 
movlpop $4,%eax
movlpop $1,%ebx
movlpop $msg,%ecx
movlpop $10,%edx
int $80h
popl %eax
popl %ebx
popl %ecx
popl %edx
xorl %eax,%eax
xorl %ebx,%ebx
ret
</source>
Linia 411 ⟶ 448:
 
<source lang=asm>
movlmov $0007h,%eax,0x0007
</source>
 
Linia 417 ⟶ 454:
 
<source lang=asm>
mov $0ffffffffh,%eax,0xffffffff
</source>
 
Linia 430 ⟶ 467:
 
<noinclude>{{Nawigacja|Asembler X86|
[[Asembler x86X86/Podstawy|Podstawy]]|
[[Asembler x86X86/Łączenie z językami wysokiego poziomu|Łączenie z językami wysokiego poziomu]]|
}}</noinclude>