Przykład - Debugger Język pewnego procesora zawiera następujące instrukcje: MOV l, r l:=r ADD l, r l := l + r SUB l, r l := l - r JMP instr skok bezwarunkowy IFZERO r, instr skok jeśli r - 0 IFMINUS r, instr skok jeśli r < 0 "r" oznacza jedną z następujacych postaci wartości: stala rejestr wartosc zapisana w rejestrze [rejestr+stała] wartość w pamięci pod adresem (wartość w rejestrze + stała) adres wartość w pamięci pod podanym adresem "l" oznacza miejsce na wynik; pierwsza postać nie jest dopuszczalna, pozostałe określaja miejsce, gdzie ma się znależć wynik. ZADANIE: wykonać model, projekt i zapisac w Smalltalku system klas będący częścią systemu wspomagającego uruchamianie i testowanie programu na maszynie z danym językiem. Nasz system powinien uwzględniać pułapki (breakpoint) zwykłe i warunkowe. Musi być możliwe wykonanie programu począwszy od danej instrukcji aż do końca lub do momentu przerwania przez pułapkę. Musi być możliwość "cofania" programu od instrukcji na ktorej ostatnio się zatrzymaliśmy, do momentu spotkania pułapki lub instrukcji, od której w ogóle rozpoczęliśmy wykonanie programu.
Zmienne globalne KLASY Pamięć - zmienna bliżej nieznanej klasy Dostępne operacje : at: adres at: adres put: wartość gdzie adres - l. całkowita Rejestry - zmienna bliżej nieznanej klasy Dostepne operacje: at: rejestr at: adres put: wartość gdzie rejestr - obiekt klasy Rejestr. KLASY Rejestr nic o niej nie wiemy, ma tylko istnieć Musimy w jakis sposób reprezentować wartości "r" i "l", tak, aby instrukcja nie musiała martwić się o to, jakiej postaci ma argumenty. Propozycja: Argument - podklasa klasy Object atrybuty: ?? operacje: dajWartość zwraca wartość argumentu wstawWartość: w wstawia wartość pod właściwy adres pamięci lub do właściwego rejestru Widać, że operacje są istotnie zależne od rodzaju argumentu - dziedziczenie!
Argument (atrybuty: brak) - klasa abstrakcyjna Hierarchia klas: Argument (atrybuty: brak) - klasa abstrakcyjna ArgStała (atr: wartość - przechowuje liczbę całk.) ArgRejestr (atr: rej - przechowuje obiekt typu Rejestr) ArgRejestrStała (atr: rej, wartość) ArgAdres (atr: adr - przechowuje liczbę) Object subclass: #Argument instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' dajWartość " Zaimplementowana w podklasach" wstawWartość: w Argument subclass: #ArgStała instanceVariableNames: ' wartość' poolDictionaries:'' wartość: w " do inicjalizacji" wartość := w ^wartość self error: "Nie da się"
Argument subclass: #ArgRejestr instanceVariableNames: ' rej' classVariableNames: '' poolDictionaries:'' ... Inicjalizacja dajWartość ^Rejestry at: rej wstawWartość: w Rejestry at: rej put: w Argument subclass: #ArgRejestrStała instanceVariableNames: ' rej wartość' ^Pamięć at: ((Rejestry at: rej) + wartość) Pamięć at: ((Rejestry at: rej) + wartość) put: w Argument subclass: #ArgAdres instanceVariableNames: ' adr' ^Pamięć at: adr Pamięć at: adr put: w
Instrukcja podklasa klasy Object atrybuty: zależne od typu instrukcji metody: breakpoint ustawia pułapkę na instrukcji breakpointCond: war ustawia pułapkę warunkową war - obiekt klasy Warunek o której wiemy tylko tyle, że ma metodę oblicz zwracajacą true lub false clearBreakpoint usuwa pułapkę run rozpoczyna wykonanie od tej instrukcji unrun rozpoczyna cofanie od tej instrukcji Możemy założyć, że zmienne Pamięć i Rejestry mają zawsze jakąś wartość (czyli zawsze jest jakaś zawartość pamięci i rejestrów). Atrybutem każdej instrukcji powinnna być pułapka - możemy na razie przyjąć, że jest to obiekt klasy Breakpoint z metodą czyStop zwracającą true, jeśli należy się zatrzymać. Ponadto każda instrukcja powinna wiedzieć, jaka jest następna po niej. W przypadku skoków - co byłoby wykonane, gdyby nie było skoku. Problem: cofanie programu 1. Kazda instrukcja musi wiedzieć, co było wykonane przed nią (jeśli nic, tzn. jest to pierwsza wykonywana instrukcja, to nie można się cofnąć. 2. Są przecież pętle! Więc każda instrukcja musi mieć stos instrukcji, które przed nią były wykonane. 3. Instrukcje zmieniające stan pamięci lub rejestrów muszą mieć zapamiętaną zmienianą wartość. 4. Znowu ze względu na pętle to musi być stos wartości!
ADD SUB Hierachia klas: Instrukcja (atr: nast - następna instrukcja do wykonania; poprzednie - stos poprzednich instrukcji; bp - pułapka, obiekt klasy Breakpoint lub nil) Operacja (atr: l, r - obiekty klasy Argument wartości - stos zmienianych wartości) MOV ADD SUB Skok (atr: instr - gdzie skoczyć, obiekt klasy Instrukcja) SkokWar (atr: arg - obiekt klasy Argument) IfZero IfMinus Zakładamy, że mamy klasę Stos ze zwyczajowymi metodami. Object subclass: #Instrukcja instanceVariableNames: ' nast poprzednie bp ' classVariableNames: '' poolDictionaries:'' breakpoint bp := Breakpoint new breapointCond: war bp := BreakpointCond new: war clearBreakpoint bp := nil run bp isNil ifFalse: [bp czyStop ifTrue: [^self]]. self wykonaj run: instr poprzednie push: instr. self run unrun self cofnij. poprzednie isEmpty ifFalse: [ poprzednie pop unrun]
Instrukcja subclass: #Operacja instanceVariableNames: ' l r wartości ' ...... wykonaj wartości push: l dajWartość. self oblicz. nast isNil ifFalse: [nast wykonaj: self] cofnij l wstawWartość: (wartości pop) Operacja subclass: #MOV instanceVariableNames: '' ........ oblicz l wstawWartość: (r dajWartość) Instrukcja subclass: #Skok instanceVariableNames: ' instr ' instr wykonaj: self. Skok subclass: #IfZero instanceVariableNames: 'war' ..... (arg dajWartość = 0) ifTrue: [instr wykonaj: self] ifFalse: [ nast isNil ifFalse: [nast wykonaj: self]]