   #Start Prev Next Contents

   Jak pisac programy w jezyku asembler pod Linuksem?

Czesc 9 - Narzedzia programisty, czyli co moze nam pomagac w programowaniu
     _________________________________________________________________

Debugery

   (przeskocz debugery)

   Wszystkim sie moze zdarzyc, ze nieustanne, wielogodzinne gapienie sie
   w kod programu nic nie daje i program ciagle nie chce nam dzialac.
   Wtedy z pomoca przychodza debugery. W tej czesci zaprezentuje kilka
   wartych uwagi programow tego typu.

   Debugery programow Linuksowych:
    1. GDB, czyli Gnu Debugger + nakladki, na przyklad DDD
       Podstawowy debuger pracujacy w trybie tekstowym (nakladka DDD - w
       graficznym). Skladnia podstawowa to AT&T (odwrotna do zwyklej
       skladni Intela), podobnie jak w Gnu as i GCC.
       Aby uzywac GDB, nasz program musimy skompilowac BEZ opcji -s u
       linkera (aby zostaly zachowane symbole).
       Krotki kurs obslugi:
          + Uruchomienie jest proste - wystarczy gdb naszprog.
          + Aby gdb stanal na wybranej funkcji, nalezy wpisac break
            nazwa_funkcji. To powinno ustawic pulapke (breakpoint) na
            pierwszej instrukcji tej funkcji. Nie zawsze jednak
            breakpoint dziala na procedurze poczatkowej _start - wtedy
            tuz po _start: (w naszym programie) nalezy wstawic instrukcje
            nop i tuz po niej postawic etykiete, na ktorej juz bez
            problemow ustawimy dzialajacy breakpoint.
          + Aby zdisasemblowac konkretna funkcje, piszemy disassemble
            nazwa_funkcji. Zostanie wyswietlony kod podanej funkcji do
            najblizszej etykiety. Jesli wolimy skladnie Intela, piszemy
            set disassembly-flavor intel. Jesli nie, to set
            disassembly-flavor att.
          + Aby wyswietlic rejestry, piszemy info r, aby wyswietlic
            konkretny rejestr, piszemy na przyklad print /x $eax.
          + Aby zmienic wartosc rejestru, piszemy na przyklad set
            $ebx=33.
          + Aby wyswietlic rejestry koprocesora, piszemy info float.
          + Aby wyswietlic zawartosc pamieci, piszemy na przyklad x
            0x08048081 lub print /x zmienna. Aby wyswietlic wiecej niz
            jedno 32-bitowe slowo, dajemy liczbe slow po ukosniku po
            komendzie x, na przyklad x/8 0x08048081. Zamiast adresu mozna
            podac etykiete.
          + Aby zmienic wartosc zmiennej w pamieci, uzywamy na przyklad
            set variable var1 = 0x1 lub set variable *0x8049094 = 0x2,
            jesli znamy tylko adres.
          + Aby pobrac adres zmiennej, uzywamy na przyklad print /x
            &var1.
          + Liste zdefiniowanych funkcji dostajemy po info functions.
          + Biezacy stos wywolan mozna otrzymac komenda info stack.
          + Aby przejsc o 1 instrukcje dalej, piszemy stepi.
          + Pomoc mozemy wyswietlic, wpisujac help.
    2. Private ICE - PICE
       Ze zrzutow ekranowych na jego stronie domowej (pice.sf.net)
       wyglada calkiem obiecujaco. Poza tym, jest to system-level
       debugger, czyli moze on wnikac w zakamarki systemu. Szczegolow
       obslugi rowniez niestety nie znam, gdyz kompilacja wymaga zabawy z
       kodem i posiadania zrodel jadra.
    3. Bochs
       Jest to emulator procesora, przydatny zwlaszcza w pisaniu i
       testowaniu bootsektorow i miniaturowych systemow operacyjnych, ale
       nie tylko. Posiada wbudowany debuger, dzieki ktoremu mozna
       debugowac programy w takich srodowiskach, w ktorych nie da sie
       uruchomic tradycyjnego debugera (na przyklad wlasnie w czasie
       bootowania systemu).
    4. Valgrind
       Moze nie do konca jest to debuger, ale narzedzie do analizy
       pamieci. Pozwala wykryc miedzy innymi wycieki pamieci, miejsca
       spowalniajace program oraz poprawic wydajnosc pamieci podrecznej.

   Wiem, ze nie wszyscy od razu z entuzjazmem rzuca sie do sciagania i
   testowania przedstawionych wyzej programow i do debugowania wlasnych.
   Niektorzy moga uwazac, ze odpluskwiacz nie jest im potrzebny. Moze i
   tak byc, ale nie zawsze i nie u wszystkich. Czasem (zwykle po dlugim
   sterczeniu przed ekranem) przychodzi chec do uzycia czegos, co tak
   bardzo moze ulatwic nam wszystkim zycie.
   Pomyslcie, ze gdyby nie bylo debugerow, znajdowanie bledow w programie
   musielibysmy pozostawic naszej nie zawsze wycwiczonej wyobrazni.
   Dlatego zachecam Was do korzystania z programow tego typu (tylko tych
   posiadanych legalnie, oczywiscie).

   Warto jeszcze wspomniec o dwoch programach: strace i ltrace. Pozwalaja
   one na sledzenie, ktorych funkcji systemowych i kiedy dany program
   uzywa. Jesli cos Wam nie dziala, mozna spojrzec, na ktorych
   wywolaniach funkcji sa jakies problemy. Uruchomienie jest proste:
   strace ./waszprogram
     _________________________________________________________________

Edytory i disasemblery/hex-edytory

   (przeskocz ten dzial)

   Do pisania programow w asemblerze wystarczy najzwyklejszy edytor
   tekstu (Emacs, VI, Joe, PICO, LPE, ...), ale jesli nie podoba sie Wam
   zaden z edytorow, to mozecie wejsc na strone The Free Country.com -
   edytory, gdzie przedstawionych jest wiele edytorow dla programistow.
   Moze znajdziecie cos dla siebie.
   Zawsze mozna tez przeszukac SourceForge.net

   Kolejna przydatna rzecza moze okazac sie disasembler lub hex-edytor.
   Jest to program, ktory podobnie jak debugger czyta plik i ewentualnie
   tlumaczy zawarte w nim bajty na instrukcje asemblera, jednak bez
   mozliwosci uruchomienia czytanego programu.
   Disasemblery moga byc przydatne w wielu sytuacjach, na przyklad gdy
   chcemy modyfikowac pojedyncze bajty po kompilacji programu, zobaczyc
   adresy zmiennych, itp.
   Oto 2 przyklady programow tego typu:
     * Biew: biew.sf.net
     * HTE: hte.sf.net

   I ponownie, jesli nie spodoba sie Wam zaden z wymienionych, to mozecie
   wejsc na strone The Free Country.com - disasemblery lub na
   SourceForge.net aby poszukac wsrod pokazanych tam programow czegos dla
   siebie.
     _________________________________________________________________

Programy typu MAKE

   Programy typu MAKE sluza do automatyzacji budowania duzych i malych
   projektow. Taki program dziala dosc prosto: uruchamiamy go, a on szuka
   pliku o nazwie "Makefile" w biezacym katalogu i wykonuje komendy w nim
   zawarte. Teraz zajmiemy sie omowieniem podstaw skladni pliku
   "Makefile".

   W pliku takim sa zadania do wykonania. Nazwa zadania zaczyna sie w
   pierwszej kolumnie, konczy dwukropkiem. Po dwukropku sa podane nazwy
   zadan (lub plikow) , od wykonania ktorych zalezy wykonanie tego
   zadania. W kolejnych wierszach sa komendy sluzace do wykonania danego
   zadania.
   UWAGA: komendy NIE MOGA zaczynac sie od pierwszej kolumny! Nalezy je
   pisac je po jednym tabulatorze (ale nie wolno zamiast tabulatora
   stawiac osmiu spacji).
   Aby wykonac dane zadanie, wydajemy komende make nazwa_zadania. Jesli
   nie podamy nazwy zadania (co jest czesto spotykane), wykonywane jest
   zadanie o nazwie "all" (wszystko).

   A teraz krotki przyklad:
   (przeskocz przyklad)
all:    kompilacja linkowanie
        echo "Wszystko zakonczone pomyslnie"

kompilacja:
        nasm -O999 -f elf -o plik1.o plik1.asm
        nasm -O999 -f elf -o plik2.o plik2.asm
        nasm -O999 -f elf -o plik3.o plik3.asm

        fasm plik4.asm plik4.o
        fasm plik5.asm plik5.o
        fasm plik6.asm plik6.o

linkowanie:     plik1.o plik2.o plik3.o plik4.o plik5.o plik6.o
        ld -s -o wynik plik1.o plik2.o plik3.o plik4.o \
                 plik5.o plik6.o

help:
        echo "Wpisz make bez argumentow"

   Ale MAKE jest madrzejszy, niz moze sie to wydawac!
   Mianowicie: jesli stwierdzi, ze wynik zostal stworzony POZNIEJ niz
   pliki .o podane w linii zaleznosci, to nie wykona bloku "linkowanie",
   bo nie ma to sensu skoro program wynikowy i tak jest aktualny. MAKE
   robi tylko to, co trzeba. Oczywiscie, niezaleznie od "wieku" plikow .o
   , dzial "kompilacja" i tak zostanie wykonany (bo nie ma zaleznosci,
   wiec MAKE nie bedzie sprawdzal wieku plikow).

   Znak odwrotnego ukosnika "\" powoduje zrozumienie, ze nastepna linia
   jest kontynuacja biezacej, znak krzyzyka "#" powoduje traktowanie
   reszty linijki jako komentarza.

   Jesli w czasie wykonywanie ktoregokolwiek z polecen w bloku wystapi
   blad (scisle mowiac, to gdy blad zwroci wykonywane polecenie, jak u
   nas FASM czy NASM), to MAKE natychmiast przerywa dzialanie z
   informacja o bledzie i nie wykona zadnych dalszych polecen
   (pamietajcie wiec o umieszczeniu w zmiennej srodowiskowej PATH sciezki
   do kompilatorow).

   W powyzszym pliku widac jeszcze jedno: zmiana nazwy ktoregos z plikow
   lub jakies opcji sprawi, ze trzeba ja bedzie zmieniac wielokrotnie, w
   wielu miejscach pliku. Bardzo niewygodne w utrzymaniu, prawda?
   Na szczescie z pomoca przychodza nam ... zmienne, ktore mozemy
   deklarowac w "Makefile" i ktore zrozumie program MAKE.
   Skladnia deklaracji zmiennej jest wyjatkowo prosta i wyglada tak:
                NAZWA_ZMIENNEJ = wartosc

   A uzycie:
                $(NAZWA_ZMIENNEJ)

   Polecam nazwy zmiennych pisac wielkimi literami w celu odroznienia ich
   od innych elementow. Pole wartosci zmiennej moze zawierac dowolny ciag
   znakow.

   Jesli chcemy, aby tresc polecenia NIE pojawiala sie na ekranie, do
   nazwy tego polecenia dopisujemy z przodu znak malpki "@", na przyklad
                @echo "Wszystko zakonczone pomyslnie"

   Uzbrojeni w te informacje, przepisujemy nasz wczesniejszy "Makefile":
   (przeskocz drugi przyklad)
# Moj pierwszy Makefile

FASM            = fasm  # ale mozna tu w przyszlosci wpisac pelna sciezke

NASM            = nasm
NASM_OPCJE      = -O999 -f elf

LD              = ld
LD_OPCJE        = -s

PLIKI_O         = plik1.o plik2.o plik3.o plik4.o plik5.o plik6.o
PROGRAM         = wynik

all:    kompilacja linkowanie
        @echo "Wszystko zakonczone pomyslnie"

kompilacja:
        $(NASM) $(NASM_OPCJE) -o plik1.o plik1.asm
        $(NASM) $(NASM_OPCJE) -o plik2.o plik2.asm
        $(NASM) $(NASM_OPCJE) -o plik3.o plik3.asm

        $(FASM) plik4.asm plik4.o
        $(FASM) plik5.asm plik5.o
        $(FASM) plik6.asm plik6.o

linkowanie:     $(PLIKI_O)
        $(LD) $(LD_OPCJE) -o $(PROGRAM) $(PLIKI_O)

help:
        @echo "Wpisz make bez argumentow"

   Oczywiscie, w koncowym "Makefile" nalezy napisac takie regulki, ktore
   pozwola na ewentualna kompilacje pojedynczych plikow, na przyklad
plik1.o:        plik1.asm plik1.inc
        $(NASM) $(NASM_OPCJE) -o plik1.o plik1.asm

   Choc na razie byc moze niepotrzebna, umiejetnosc pisania "Makefile"'ow
   moze sie przydac juz przy projektach zawierajacych tylko kilka modulow
   (bo nikt nigdy nie pamieta, ktore pliki sa aktualne, a ktore nie).
   O tym, ile "Makefile" moze zaoszczedzic czasu przekonalem sie sam,
   piszac swoja biblioteke - kiedys kompilowalem kazdy modul z osobna,
   teraz wydaje jedno jedyne polecenie make i wszystko sie samo robi.
   "Makefile" z biblioteki jest spakowany razem z nia i mozecie go sobie
   zobaczyc.

   Poprzednia czesc kursu (klawisz dostepu 3)
   Kolejna czesc kursu (klawisz dostepu 4)
   Spis tresci off-line (klawisz dostepu 1)
   Spis tresci on-line (klawisz dostepu 2)
   Ulatwienia dla niepelnosprawnych (klawisz dostepu 0)
