HardBreaker

Die "geheimnisvolle" Schnittstelle zum Turbo Debugger

Wissen | Know-how

Im zweiten Teil des c't-Projekts "HardBreaker" geht's nun gar nicht mehr um Hardware, sondern um den allseits beliebten Turbo Debugger, genauer gesagt darum, wie dessen mäßig dokumentierte Treiberschnittstelle im allgemeinen und für den HardBreaker im besonderen funktioniert.

Aufmacher

Borlands Turbo Debugger ist, jedenfalls im Prinzip, besonders freundlich zu Hardware-Debuggern: die Ansteuerung eines Hardware-Debugger-Boards erfolgt über einen entsprechenden Gerätetreiber. Auch die 386er- und 486er- Systeme können ja bekanntlich wegen ihrer speziellen "Debug"-Fähigkeiten [1] mit dem Gerätetreiber TDH386.SYS angesteuert werden und arbeiten dann ähnlich wie ein Hardware-Debugger. Das Prinzip der Ansteuerung ist in beiden Fällen zwar gleich, im Detail gibt's aber einige Unterschiede:

  • Der TDH386.SYS-Gerätetreiber generiert bei einem erfüllten Hardware-Breakpoint den Interrupt 1, also genau denselben Interrupt, der vom Turbo Debugger beim Single-Step benutzt wird (Trap-Flag gesetzt).
  • Ein externer Hardware-Debugger (also auch der HardBreaker) erzeugt einen NMI-Impuls, der den Interrupt 2 auslöst. Die Service-Routine für diesen Interrupt liegt im Gerätetreiber TDHDEBUG und gibt die Programmkontrolle weiter an den Turbo Debugger. Der Vorteil gegenüber der ersten Variante ist, daß in der NMI-Routine bereits diverse "Voruntersuchungen" stattfinden können, zum Beispiel ob der Break-Taster betätigt wurde oder irgendwelche Register bestimmte Bedingungen erfüllen.

Die kalte Dusche läßt aber nicht lange auf sich warten. Beim Konzipieren des Treibers dämmert es einem allmählich, daß bei dieser Beschreibung doch so einiges im argen liegt. Da HD-Boards in der freien Wildbahn anscheinend "nur vereinzelt auftreten", wurde die Beschreibung der Schnittstelle absolut stiefmütterlich behandelt. So hat man zum Beispiel die mit Fehlern und Ungenauigkeiten gespickte Beschreibung des Gerätetreibers im Handbuch zur Version 1.0 fast ohne Änderungen und mit allen Fehlern übernommen in die Version 1.5 (die Beschreibung wird hier auf der Diskette mitgeliefert) und, man kann's kaum glauben, auch in die Beschreibung des zu C++ gelieferten Turbo Debugger. Offensichtlich hat sich bis heute niemand beschwert.

Nach einer Odyssee erfolgloser Nachfragen bei Borland in München, damals noch Heimsoeth, und der Mutter in den USA, die aufzulisten leider den Rahmen sprengen würde, und einem Reinfall mit einem empfohlenen Fachbuch aus der Borland-Literaturliste: "Using Turbo Debugger and Tools 2.0" von Ben Ezzell (Addison-Wesley) überraschte immerhin Tom Swan mit seinem "Mastering Turbo Debugger" (Hayden Books) positiv.

Obwohl auch dort zum hundertsten Mal erklärt wird, wie die einzelnen Fenster im TD auf- und zugeklappt werden können, enthält das Buch doch eine Fülle von Hintergrundinformationen, guten Tips und sogar ein eigenes Kapitel zum Hardwaredebugging mit dem TD. Über den Gerätetreiber TDHDEBUG läßt zwar auch Tom Swan keine Informationen "raus", aber das ganze Drum und Dran wird sehr gut erklärt.

Mit einer intuitiven Mischung aus dem Borland-Handbuch, Tom Swans Hinweisen und viel, viel Probiererei, ließ sich nun doch ein hoffentlich stimmiges Bild dieser Schnittstelle zimmern, das jedoch ein paar c't-Seiten in Anspruch nimmt.

Zuvor noch ein paar Terminologien, wie sie bei Treibern so üblich sind.

Den Statusblock übergibt der Gerätetreiber auf Aufforderung dem Turbo Debugger. Den Befehlsblock schickt Turbo Debugger an den Gerätetreiber. Code bezeichnet die Nummer einer Standardfunktion im Gerätetreiber, zum Beispiel:

  Code 4 = READ. 

Jeder Gerätetreiber, egal ob Druckertreiber, RAM-Disk et cetera hat eine solche Funktion. Befehl schließlich bezeichnet die Nummer eines vom Turbo Debugger angeordneten Befehls, zum Beispiel:

  Befehl 4 = Hardware-Breakpoint   

Diese Befehle sind natürlich nur in TD-Gerätetreibern enthalten. Vorsicht: In der Originalbeschreibung gehen diese Begriffe allerdings teilweise munter durcheinander!

  DEVICE = TreiberName.Ext   

in CONFIG.SYS, wobei "TreiberName.Ext" der Filename, nicht der Gerätename des Treibers ist.

Die Extension des Treibernamens darf beliebig sein, "SYS" ist dafür am verbreitesten. Das Assemblieren des Gerätetreibers sollte mit TASM Treibername, TLINK Treibername und EXE2BIN TreiberName erfolgen (Endprodukt ist die Datei Treibername.BIN). Der Gerätetreiber selbst muß den Namen "TDHDEBUG" bekommen und die in der Tabelle "Treiberfunktionen" aufgeführten Funktionen unterstützen.

Alle Befehlsblöcke werden mit der WRITE-Funktion (Code 8) zum Gerätetreiber geschickt. Sie beginnen mit einem Byte, das die auszuführende Operation beschreibt (= Befehlsnummer). Die nachfolgenden Bytes enthalten zusätzliche Informationen zum jeweiligen Befehl. Wenn ein Hardware-Breakpoint gesetzt werden soll, dann muß der Gerätetreiber überprüfen, ob ein akzeptabler Befehlsblock übergeben wurde. Die Überprüfung muß vollständig sein! Das Ergebnis einer Operation wird als Return-Code (Byte 1 im Statusblock) zurückgegeben.

Bei Befehl 1 muß der Treiber den Leistungsumfang des HD-Boards übermitteln. Befehl 4 erwartet ein Handle (ein Byte), über das der Turbo Debugger den Breakpoint verwaltet (zum Beispiel beim Löschen desselben). Der Gerätetreiber verwendet dieses Handle, wenn er einen "Breakpoint-Event" an TD meldet.

TD-Menü
Vergrößern
Über dieses schöne TD-Menü kann man mit dem Hardbreaker kommunizieren.

Die gewünschte Einsprungsadresse hat TD mit dem Befehl 0 dem Handler zuvor übermittelt. Im einfachsten Fall sähe der NMI-Handler so aus:

  PUSH AX   ;Original AX sichern  MOV AH,0  ;Breakpoint-Handle = 0  JMP Far TD_Entry  ;Sprung zum TD  

Aber so einfach ist es natürlich auch wieder nicht, insbesondere kann man hier dem "dummen" Hardware-Debugger noch ein klein wenig Intelligenz einhauchen ... dazu später mehr.

Anhand der CS:IP-Registerinhalte auf dem Interrupt-Stack kann der TD feststellen, an welcher Stelle das Testprogramm vom NMI unterbrochen wurde. TD positioniert den Cursor auf dem nächsten Befehl (der zu dem NMI führende Befehl wurde ja bereits ausgeführt und ist somit schon Vergangenheit) und gibt die Breakpoint-Meldung aus. Die Prozessoren vom 286 an aufwärts haben wegen ihrer internen Pipeline-Struktur manchmal auch einen längeren "Bremsweg", dann steht CS : IP auf ein, zwei Befehlen hinter dem NMI-Auslöser ("NMI-Latenzzeit"). Beim 386 (nicht aber 486) kann man dies mit den GE- und LE-Bits im DR7-Register auch (in Grenzen) unterbinden.

Des weiteren muß man bei MEM-Breakpoints alle vorhandenen Caches leider abschalten.

  • "can"t set that sort ofhardware breakpoint"
  • "can't any more hardware breakpoints"
  • "Global breakpoint x at yyyy:zzzz"
  • "Hardware device driver stuck"
  • "NMI Interrupt"

Mit dem Befehl 4 (Hardware-Breakpoint setzen) übergibt TD nur die Breakpoint-Bedingungen, ohne zugleich den Hardware-Breakpoint scharf zu machen! Um etwas Licht in die Befehlsstruktur des Gerätetreibers zu bringen, listet die Tabelle "Break-Ablauf" die komplette Reihenfolge der Funktionsaufrufe einer "Sitzung" auf.

Grundsätzlich übergibt also Turbo Debug einen Befehl per WRITE-Funktion (Code 8) und fragt dann mit READ (Code 4) beim Treiber nach, ob die soeben angeforderte Operation erfolgreich abgewickelt werden konnte. Ausnahme ist natürlich die Initialisierung (Code 1), die ja das DOS durchführt.

Interessanterweise konnte ich bei der praktischen Arbeit mit dem Gerätetreiber nur die Codes 0, 4 und 8 konstatieren, was mit den anderen ist, bleibt weiterhin unklar, oder können Sie etwa mit Code 6 etwas gemäß Originaltext anfangen: "... damit wird angezeigt, daß ein nachfolgender Read-Zugriff vollständig beendet werden kann" ...

Des weiteren wurde auch der TD-Befehl 6 (I/O-Basisadresse festlegen) vom Turbo Debugger nie benutzt.

Unklar bleibt ferner die Bedeutung der letzten beiden Bytes im Statusblock von Befehl 1 (Leistungsumfang des HD-Boards ermitteln). Angeblich soll hier eine "Adresse" auf ein Byte stehen, über das Hardware-Breakpoints gesperrt beziehungsweise aktiviert werden können. Aber was für eine Adresse, ein Offset, eine gepackte Adresse Seg : 0000 oder doch ein FAR-Zeiger? Ich habe alle erdenklichen Möglichkeiten ausprobiert - ohne die gewünschte Wirkung ...

Handle 0 : Breakpoint

Der HardBreaker kann nur einen einzigen Breakpoint setzen, das heißt, wenn ein NMI auftritt, so kommt nur ein "Übeltäter" in Frage: Breakpoint Nr.1 ! TD zeigt diesen "Normalfall" mit dem Meldetext "Global Breakpoint x at yyyy:zzzz" an. Der TD-Cursor im Disassembler-Text wird auf dem Befehl positioniert, der dem NMI-Auslöser folgt.

Handle 1..FDH : reserviert

Diese Handles werden vom HardBreaker-Gerätetreiber nicht übergeben, sie sind überhaupt nicht eingeplant. Sollten sie nach einem NMI fälschlicherweise doch übergeben werden, so ignoriert sie der TD und setzt das Testprogramm fort, als wenn nichts passiert wäre.

Handle FEH: Rekursiver Aufruf

Dieses Handle kann zurückgegeben werden, wenn der Hardware-Breakpoint bis zu 6 Bytes unterhalb des aktuellen Stacks gesetzt wird. Wenn der Turbo Debugger dieses Handle erhält, gibt er die Fehlermeldung "Hardware device driver stuck" aus.

Handle FFH: Break-Taster

Handle FFH muß übergeben werden, wenn der Break-Taster gedrückt wird, das heißt, es soll überhaupt keine Breakpoint-Meldung ausgegeben werden. TD übernimmt ganz einfach die Programmkontrolle und positioniert den Cursor auf dem nächstmöglichen Befehl.

Vorsicht ist geboten, wenn ein Hardware-Breakpoint gesetzt wurde und von Hand (mit F7 = TRACE oder F8 = STEP) über einen Befehl "gesteppt" wird, der diesen Breakpoint auslöst. Dann landet man nämlich in der NMI-Routine des Gerätetreibers. Woran das liegt? Wenn gleichzeitig ein NMI und ein Einzelschrittinterrupt (T-Statusbit gesetzt) auftreten, so hat das T-Statusbit die höhere Priorität. Es wird also der Interrupt 1 ausgelöst und nicht der NMI-Interrupt (INT 2). Für den Turbo Debugger ist also die NMI-Routine die ganz normale Fortsetzung des Programmes. Eigentlich müßte aber die INT-1-Routine im Turbo Debugger diesen Fall erkennen und entsprechend (richtiger) handeln ...

Wie dem auch sei: Wenn man ungewollt in der NMI-Routine des Gerätetreibers landet, kommt man mit einem F9 (= RUN) auf schnellstem Wege in das Testprogramm zurück, wobei der TD dann auch richtig anhält und die gewünschte Breakpoint-Meldung ausgibt.

Diese im Grunde genommen nicht erwünschte Eigenschaft des Turbo Debugger hilft aber enorm, wenn es darum geht, die NMI-Routine nach Änderungen auszutesten. Zum Testen muß man nur einen NMI-auslösenden Befehl "übersteppen". Der nächste vom TD angezeigte Befehl ist der erste in der NMI- Routine. Von hier aus kann mit den üblichen TD-Befehlen nicht nur die gesamte NMI-Routine, sondern auch der Turbo Debugger selbst mit Einzelschritten durchlaufen werden (womit wir bei einer fast philosophischen Frage angelangt sind: Darf ein als Programm gestarteter Debugger in sich selbst "herumdebuggen"?).

  CMP reg,irgendwas   jnz NMI_0   

Falls die Bedingung also nicht erfüllt ist, geht es unverrichteter Dinge wieder zurück ins unterbrochene Programm, aber nicht ohne vorher NMI wieder zurückzusetzen (Unterprogramm RES_NMI).

Da RES_NMI immer erst hinter dem Befehl "mov dx,HB_INPUT" aufgerufen wird, kann man auch Zugriffe auf den HardBreaker selbst abfangen, andernfalls bekäme man bei 8088- und 286-Prozessoren eine tödliche Rekursion. Ab 386er ist das kein Problem mehr, hier bleibt der NMI prozessorseitig bis zum nächsten IRET gesperrt.

Mit den zusätzlichen Abfragen lassen sich Break-Bedingungen bequem UND-Verknüpfen, etwa:

  (WRITE auf Port 378H) UND (BX=1234H)   

Für die Konditionen kann man natürlich beliebig umfangreiche, aufwendige und zeitfressende Prozeduren vorsehen, in der Praxis hat sich allerdings gezeigt, daß in aller Regel eine einzige Kondition hinreicht. Um Code und vor allem Zeit zu sparen, läßt sich diese wirklich am sinnvollsten per Patch einrichten, wozu man nur das zweite Byte im CMP-Befehl für das Register und Byte 3 und 4 für den gewünschten Wert einzupatchen hat (so lange man nicht mit Segment-Registern oder 32-Bit-Registern umgeht):

CODE      Befehl  81FBxxyy  CMP BX,xxyy  ..F8....  ->  AX  ..F9....  ->  CX  ..FA...   ->  DX  ..FC....  ->  SP  ..FD....  ->  BP  ..FE....  ->  SI  ..FF....  ->  DI

Aber Vorsicht: In der Assembler-Source darf an dieser Stelle als Platzhalter nicht CMP AX,0000 erscheinen. Der Assembler erzeugt bei einem CMP Ax,xxyy nämlich nicht den hier erwünschten 4-Byte-Befehl 81F8hxxyy, sondern die kürzere 3-Byte-Fassung 3Dxxyyh. Ähnliches gilt auch bei den 8-Bit-Registern (3C00h für CMP AL,0 aber 80Fx00 sonst).

Um alle Vergleiche der Form: Register - Register, Register - Speicher, Register - unmittelbar einpatchen zu können, werden vier Patch-Bytes benötigt. Und als wenn nun nicht schon Patcherei wäre, nein, auch noch die nächste Zeile kommt als willkommenes "Flickzeug" in Frage. Dort kann man mit einer ganz simplen Änderung des ersten Bytes eine Negation der Abfrage vornehmen. Aus "JE NMI_1" mit dem Programmcode 74xx wird dann ganz schnell "JNE NMI_1" (Code 75xx) oder "JMP NMI_1" (Code EBxx), wenn die Verknüpfung ganz abgeschaltet werden soll.

Da in bestimmten Testsituationen der HardBreaker eventuell sehr häufig NMIs erzeugt, liegt eine äußerst schnelle Abfrage aller Zusatzkonditionen im Interesse einer "echtzeitfähigen" Überwachung, (eben anders als bei den globalen Breakpoints von TD). Und es gibt wohl keine schnellere und effizientere Möglichkeit, sämtliche Variationen von Register-Vergleichen in insgesamt nur sechs Byte Programmcode unterzubringen.

Der geneigte Leser wird sich an dieser Stelle vielleicht fragen, warum eigentlich das Flagregister "gepusht" wird, obwohl das ein Interrupt-Aufruf doch immer automatisch macht? Das PUSHF/POPF sorgt dafür, daß wie gefordert der Turbo Debugger auch die Original-Flags übermittelt bekommt.

Die NMI-Routine enthält nun alles Wichtige: die Break-Taster-Prüfung, die Register-Verknüpfung und die NMI-Freischaltung.

Wer auf die Möglichkeit der oben beschriebenen Breakpoint-Verknüpfung beim Turbo Debugger dennoch nicht verzichten will, muß mit folgender "Krücke" bei der Eingabe eines verknüpften Hardware-Breakpoints leben: Das "Hardware breakpoint options"-Menü des TD bietet einige Möglichkeiten, die der HardBreaker nicht realisieren kann, zum Beispiel "Data match - Range" und "Data match - Not Range". Damit werden Hardware-Breakpoints eingestellt für ein Datum in beziehungsweise außerhalb eines bestimmten Bereichs. Diese TD-Menüpunkte lassen sich aber mißbrauchen, um eine Zusatzbedingung zum "Address match" zu übergeben. Sie erwarten eine Datenbereichsangabe zum Beispiel der Form: "3,10", womit in diesem Fall ein Datenbereich von 03H...12H gemeint ist.

Der HardBreaker-Treiber interpretiert dann diese Daten als Verknüpfungsregister und Wert, etwa mit folgender Zuordnung von (ehemaligem) Startwert und Register:

Wert Reg Wert Reg  0H   AX  10h  AL  1H   CX  11h  CL  2H   DX  12h  DL  3H   BX  13h  BL  4H   SP  14h  AH  5H   BP  15h  CH  6H   SI  16h  DH  7H   DI  17h  BH

Obige Breakpoint-Bedingung :(WRITE auf Port 378H) UND (BX=1234H) kann dann im "Hardware breakpoint options"-Menü eingegeben werden mit

  • Cycle: Output I/O
  • Address match: Equal -> Address value: 378H
  • Data match: Range -> Data value: 3H,1234H

Soll die Verknüpfung mit (BX <> 1234H) hergestellt werden, so muß bei "Data match" lediglich "Not range" angegeben werden. Die "Data match"-Angaben werden von TD in den Feldern H (gesuchte Daten), I (niederwertige Datenangabe) und J (höherwertige Datenangabe) dem Gerätetreiber übergeben.

Wie bereits erwähnt, kann der HardBreaker aufgrund seiner einfachen Beschaffenheit nicht alle im "Hardware Breakpoint options"-Menü aufgeführten 567, zum Teil unsinnigen Möglichkeiten abdecken. Dumm ist nur, daß dieses Menü immer jede Kombination kommentarlos annimmt und sich erst nach der kompletten Eintipperei mit einem freundlichen "can"t set that sort of hardware breakpoint" verabschiedet.

Folgende Menü-Punkte unterstützt der HardBreaker:

Cycle:

Read Memory

Write Memory

Access Memory

Input I/O

Output I/O

Both I/O

Address match:

Equal

Range

Data match:

Match all

Range (positive Registerverkn.

Not Range (neg. Registerverk.

Auch die Kombination der Menüpunkte aus verschiedenen Gruppen ist möglich. Jede Einstellung in dem Menü erfordert die Angaben zum Breakpoint-Cycle und zum "Address match". Der "Data match" als die uminterpretierte Registerverknüpfung ist optional.

Bei der Angabe "Address match : Range" ist folgendes zu bedenken: Die Bestimmung und Festlegung eines Bereichs durch den Gerätetreiber TDHDEBUG erfolgt über die bitmäßige Maskierung der eingegebenen Werte. Für den Port-Bereich 328H bis 32FH heißt das zum Beispiel, daß die letzten 3 Bit (A0. .A2) bei der Beobachtung des Adreßbus unberücksichtigt bleiben. Wird allerdings ein Port-Bereich von 327H bis 32FH angegeben, so verdoppelt sich gleich der überwachte Adreßbereich auf 320H bis 32FH, da der Treiber jetzt 4 Bits ausmaskiert. Besonders drastisch zeigt sich dieser Effekt bei größeren (im I/O-Bereich sehr praxisfremden) "Grenzüberschreitungen", zum Beispiel würden die zwei Bytes von 1FFh bis 200h auf den kaum noch sinnvollen Bereich 100h bis 200h aufgeblasen (in dieser Beziehung hat es der virtuelle TDH386.SYS mit seiner I/O-MAP besser).

In solchen Ausnahmefällen muß man eben zwei Testläufe mit jedem Breakpoint einzeln machen.

Für den ersten Fall haben wir bereits vorgesorgt und zwei zusätzliche Eingangssignale (/XMEMR) und /XMEMW) an IC 13 vorgesehen, allerdings haben wir die Bepinnung am GAL noch minimal gegenüber dem Schaltplan geändert (XMEMR an Pin 17 und XMEMW an Pin 18), und einen Jumper S3 hinzugefügt (siehe unten). Steckt man S3, werden bei MEM-Breakpoints nur die Adressen ausgewertet.

Schaltbild
Vergrößern
Kleine Änderung zum Schaltbild von Teil 1: Die zusätzlichen Eingangssignale /XMEMR und /XMEMW gehören an die Pins 17 und 18, und an Pin 14 kommt noch ein Jumper.

Denken Sie auch daran, bei Arbeit mit dem HardBreaker und MEM-Breakpoints im Setup des Rechners den oder die Caches abzuschalten.

  • Header mit den üblichen Informationen für die Treiberkette, dem Attribut-Code, den Far-Zeigern für die Routinen STRATEGY und ENTRY, Name des Gerätetreibers),
  • STRATEGY
  • ENTRY
  • INIT (CODE 0)
  • READ (CODE 4 und 5)
  • WRITE (CODE 8 oder 9)
  • NMI

Den vollständigen Treiber hier zu veröffenlichen, machte wenig Sinn: vier Seiten Listing-Wüste. Er ist aber auf Sammeldisk und in der c't-Mailbox erhältlich und stellt ein Gerüst dar für eigene Erweiterungen und Verbesserungen. Man kann kompliziertere Verknüpfungen und den vom TD bereitgestellten Durchlaufzähler (Feld E des Befehlsblocks) einbauen, eventuellen Parity-NMI extra behandeln, bestimmte Ports, die für die NMI-Behandlung selbst gebraucht werden (beim AT 61H und 70H) abblocken und vieles mehr - schließlich handelt es sich hier um ein Selbstbau-Projekt!

  begin   Port[$0378] : = 0;   end;   

Wenn man im Menü den Breakpoint entsprechend auf I/O-Write setzt und dieses im TD mit F9 Programm startet, sollte sich als Reaktion ein globaler Breakpoint melden. Komfortabler geht das Ganze aber natürlich mit dem Testprogrämmchen HBT, das alle tatsächlich möglichen Break-Arten im Dialog anbietet. Beim Kompilieren von HBT.PAS muß man darauf achten, den Schalter für "standalone debugging" auf ON zu setzen, sonst klappt die Sache mit der Testvariablen nicht.

HBT bietet nun einige einfache Funktionen an: I)nput und O)utput auf Ports, R)ead und W)rite auf Speicheradressen und mit T) den Zugriff auf die Programmvariable "Testvariable". Stellt man im Hardware-Breakpoint ein I/O-READ auf Port 378H ein, müßte sich der Turbo Debugger bei einem solchen Zugriff melden (CPU-Window mit Meldung "Global breakpoint 1 at yyyy:zzzz"). Der TD-Cursor müßte auf dem Befehl hinter dem den NMI auslösenden IN AL,DX stehen.

Alle anderen Zugriffe, egal ob I/O oder MEM, müssen den "Breaker" kaltlassen (sofern alles in Ordnung ist). An dieser Stelle darf nun all das ausprobiert werden, was vom Testprogramm angeboten wird. Mit Schreibzugriffen auf belegte Ports und Speicheradressen sollte man sich allerdings sehr zurückhalten, man weiß ja nicht immer, was man da gerade kaputtschreibt.

Die eingestellte Breakpointbedingung bleibt so lange erhalten, bis der Breakpoint mit Alt-B D gelöscht wird. Erst danach kann ein neuer Hardware-Breakpoint gesetzt werden.

Will man einen Variablenzugriff überwachen, so muß die Einstellung folgendermaßen vonstatten gehen:

  • mit Alt-B C das Fenster "Changed memory global..." öffnen
  • Variablenname HBT.TESTVARIABLE eingeben
  • Testprogramm HBT.EXE mit F9 starten
  • im Testprogramm mit "t" die Funktion TESTVARIABLE aufrufen (inkrementiert lediglich "testvariable")

Der letzte Test soll dem Break-Taster auf den Zahn fühlen. Solange das Testprogramm HBT.EXE nicht mit F9 gestartet ist, darf auch nach der Betätigung des Break-Tasters nichts passieren. Erst wenn der Taster bei laufendem Testprogramm gedrückt wird, erfolgt eine Unterbrechung. Der Turbo Debugger kann in diesem Fall erkennen, daß (trotz NMI) kein echter globaler Breakpoint aufgetreten ist. Folglich gibt es auch kein Fenster mit der Meldung "Global breakpoint 1 at yyyy:zzzz". Das gerade laufende Testprogramm wird schlicht und einfach unterbrochen, der TD-Cursor wird auf den nächstmöglichen Befehl gesetzt (und hält wahrscheinlich im DOS oder im BIOS).

as

[1] Ascherl, Von Wanzen und ihren Jägern, c't 2/91, S.156

[2] Werner, PC-DOS auf dem c't 86, c't 7/85, S.108

[3] Kirchner, Treiben und getrieben werden, c't 4/90, S.370

[4] Lai, MS-DOS Device Treiber, Addison-Wesley

[5] Tischer, PC Intern 2.0, Data Becker

[6] Swan, Mastering Turbo Debugger, Hayden Books


Zu diesem Artikel existieren Programmbeispiele

0293_206.doc
0293_206.zip

[#anfang Seitenanfang]


INIT-Funktion (Code = 0)

Sie wird nur einmal und zwar beim Laden des Gerätetreibers in CONFIG.SYS aufgerufen. Der Programm-Code dieser Funktion muß den HardBreaker ausschalten.

READ-Funktion (Code = 4)

Hierüber ruft der Turbo Debugger den Treiber auf, um den aktuellen Statusblock zu lesen. Den Statusblock sollte der Treiber in einem internen Datenbereich bereithalten. Nach jeder READ-Anforderung muß er so viele Bytes ab Anfang des Statusblocks wie angefordert zurücksenden. Welche Statusblöcke es gibt, steht im nächsten Abschnitt.

READNOWAIT-Funktion (Code = 5)

Sie veranlaßt den Treiber, das erste Byte des Statusblocks zurückzugegeben (Return-Code). Das Busy-Bit sollte immer 0 sein.

READSTATUS-Funktion (Code = 6)

Setzt das Busy-Bit immer auf 0.

READFLUSH-Funktion (Code = 7)

Setzt das Done-Bit im Return-Status auf 1.

WRITE-Funktion (Code = 8)

Diese Funktion verwendet der TD, wenn er einen Befehlsblock an den Gerätetreiber senden will. Der Befehlsblock hat eine variable Länge, die vom jeweiligen Befehl abhängt. Man kann die Daten entweder in einen Arbeitsbereich des Gerätetreibers kopieren oder direkt auf sie per Zeiger zugreifen.

WRITEVERIFY (Code = 9)

Wie WRITE (Funktionscode 8).

WRITESTATUS (Code = 10)

Setzt das Done-Bit im Return-Status auf 1.

WRITEFLUSH (Code = 11)

Setzt das Done-Bit im Return-Status auf 1.

SONST

Alle anderen Funktionsaufrufe sollten nur das Fehler-Bit (Bit 15) im Return-Status setzen und den Fehler-Code 3 (das ist nicht der Return-Code!) für »Unknown command« im niederwertigen Byte des Statuswortes angeben.

[#anfang Seitenanfang]


Befehl 0: Vektoren installieren

4 Bytes: Dies ist ein 32-Bit-Zeiger (Offset im ersten und Segment im zweiten Wort) auf eine Adresse im Turbo Debugger, die angesprungen wird, wenn ein Hardware-Breakpoint aufgetreten ist . Diese Adresse wird im HardBreaker-Gerätetreiber "TD_Entry" genannt. Der Programm-Code für den Befehl 0 sollte auch alle Interrupt-Vektoren installieren, die der Gerätetreiber benötigt. Diese Routine wird nur einmal aufgerufen, wenn der Turbo Debugger gestartet wird. Der Befehl 7 "Vektoren entfernen" wird ebenfalls nur einmal aufgerufen und zwar wenn TD beendet wird. Dann sollten alle verbogenen Vektoren wieder restauriert und das HD-Board ausgeschaltet werden.

Befehl 1: Leistungsumfang ermitteln

keine zusätzlichen Daten

=> Rückmeldung der HD-Daten im Statusblock

Befehl 2: Hardware Breakpoints einschalten

keine zusätzlichen Daten

Befehl 3: Hardware Breakpoints ausschalten

keine zusätzlichen Daten

Befehl 4: Hardware-Breakpoint setzen

keine zusätzlichen Daten

=> Rückmeldung eines Handles (1 Byte)

1 Byte Breakpoint-Typ (Feld A):         0  Speicher lesen         1  Speicher schreiben         2  Speicher lesen oder schreiben         3  I/O lesen         4  I/O schreiben         5  I/O lesen oder schreiben         6  Befehlszugriff  1 Byte Gesuchte Adressen (Feld B):         0  Alle Adressen         1  Gleich dem Testwert         2  Ungleich dem Testwert         3  Größer als der Testwert         4  Kleiner als der Testwert         5  Kleiner als der Testwert oder gleich         6  Größer als der Testwert oder gleich         7  Innerhalb des Bereichs         8  Außerhalb des Bereichs

Eine Speicheradresse, 32-Bit, linear angeordnet. Wenn der Adressierungsmodus eine oder mehrere Adressen zum Vergleich benötigt, wird hier der erste oder der niederwertige Anteil abgelegt.

4 Bytes: Höherwertige Adresse (Feld D)

Eine Speicheradresse, 32-Bit, linear. Wenn der Adressierungsmodus zwei Adressen für den Vergleich eines Bereichs benötigt, dann wird die zweite oder die höherwertige Adresse angegeben.

2 Bytes Durchlaufzähler (Feld E)  1 Byte  Gesuchte Datengröße (Feld F):          1 = Byte          2 = Wort          4 = Doppelwort  1 Byte  Quelle des gesuchten Bus-Zyklus (Feld G):          1 = CPU          2 = DMA          3 = Entweder DMA oder CPU  1 Byte  Gesuchte Daten (Feld H):          0 Alle Daten          1 Gleich dem Testwert          2 Ungleich dem Testwert          3 Größer als der Testwert          4 Kleiner als der Testwert          5 Kleiner als der Testwert oder gleich          6 Größer als der Testwert oder gleich          7 Innerhalb des Bereichs          8 Außerhalb des Bereichs

Wenn für die gesuchten Daten ein oder mehrere Werte benötigt werden, dann wird in diesem Feld der erste Wert angegeben. Über die Größe der gesuchten Daten wird festgelegt, wie viele Byte in diesem Feld signifikant sind.

4 Bytes: Höherwertige Datenangabe (Feld J)

Wenn für die gesuchten Daten zwei Werte benötigt werden, dann enthält dieses Feld den zweiten Wert. Über die Größe der gesuchten Daten wird festgelegt, wie viele Byte in diesem Feld signifikant sind.

4 Bytes Datenmaske (Feld K)

Wenn die Hardware die Überprüfung einzelner Bits unterstützt, steuert dieses Feld, welche Bits in den Daten für die Vergleichsmaske verwendet werden.

Befehl 5: Hardware-Breakpoint löschen

1 Byte: Das Handle des Breakpoints, der gelöscht werden soll. Dieses Handle wurde vom Gerätetreiber nach dem Befehl "Hardware- Breakpoint setzen" (Code 4) vergeben und dem Turbo Debugger übergeben. Da beim HardBreaker sowieso nur ein Hardware-Breakpoint gesetzt werden kann, ist das Handle immer 0.

Befehl 6: I/O-Basis-Adresse

2 Bytes: Die Basisadresse des Hardware-Debuggers (Anmerkung: ???).

Befehl 7: Vektoren entfernen

keine zusätzlichen Daten

[#anfang Seitenanfang]


0 O.K., der vorausgegangene Befehl war erfolgreich  1 Ungültiges Handle  2 Es können keine Breakpoints mehr gesetzt werden,    auch nicht der soeben angeforderte.  3 Der Breakpoint ist zu kompliziert für die Hardware,    deshalb kann der Breakpoint nicht gesetzt werden.  4 Der Befehl kann nicht durchgeführt werden,     wegen der Beschränkungen, die dem Hardware-    Debugger durch einen vorausgegangenen Befehl    auferlegt wurden (wird beim HardBreaker nicht benutzt).  5 Der Gerätetreiber findet den Hardware-Debugger nicht.  6 Es ist ein Hardware-Fehler aufgetreten.  7 Es wurde ein unzulässiger Befehl an den Gerätetreiber    geschickt.  8 Der Gerätetreiber wurde noch nicht mit der     entsprechenden Funktion (Befehl 0) initialisiert

[#anfang Seitenanfang]


2 Bytes

2 Bytes: Die Versionsnummer der Gerätetreiber-Software. Da sich jede Version eines Gerätetreibers unter Umständen anders verhält, sollte diese variiert werden. Ausgehend von diesem Feld kann Turbo Debugger eventuell besondere Maßnahmen ergreifen (Anmerkung: Hier fehlen in der Originalbeschreibung weitere Hinweise, so daß diese allgemeine Beschreibung völlig wertlos ist).

1 Byte: Maximale Anzahl der Hardware-Breakpoints, die auf dem HD-Board gesetzt werden können.

1 Byte Konfigurationsbits (Achtung: Bitmaske)         Bit  Funktion         0    zwischen CPU- und DMA-Zugriffen         wird unterschieden         1    DMA-Transfers feststellen         2    Datenmasken werden unterstützt         3    Durchlaufzähler existiert in Hardware         4    Daten und Adressen können ermittelt    werden  1 Byte Unterstützte Breakpoint-Arten (Achtung: Bitmaske)         Bit  Funktion         0    Speicher lesen         1    Speicher schreiben         2    Speicher lesen oder schreiben         3    I/O lesen         4    I/O schreiben         5    I/O lesen oder schreiben         6    Befehlszugriff  1 Byte Adreß-Modi (Achtung: Byte)         Byte Funktion         0    alle Adressen         1    Gleich dem Testwert  1 Byte: Adreß-Modi (Achtung: Byte)         Byte Funktion         2    Ungleich dem Testwert         3    Größer als der Testwert         4    Kleiner als der Testwert         5    Kleiner als der Testwert oder gleich         6    Größer als der Testwert oder gleich         7    Innerhalb des Bereichs         8    Außerhalb des Bereichs  1 Byte: Gesuchte Daten (Achtung: Byte)         Byte Funktion         0    Alle Daten         1    Gleich dem Testwert         2    Ungleich dem Testwert         3    Größer als der Testwert         4    Kleiner als der Testwert oder gleich         5    Kleiner als der Testwert oder gleich         6    Größer oder gleich dem Testwert         7    Innerhalb des Bereichs         8    Außerhalb des Bereichs
1 Byte

Dieses Byte wird auf den Wert 1, 2 oder 4 gesetzt, je nach der Datengröße oder der Größe der Suchmaske, die von der Hardware unterstützt wird.

2 Bytes: Speicher in KByte auf dem HD-Board (Anmerkung: weitere Hinweise fehlen)

2 Bytes: Anzahl der Ereignisse, die zurückverfolgt werden können (Anmerkung: ???)

2 Bytes: Adresse des Byte, das anzeigt, ob Hardware-Breakpoints möglich sind. Gibt die Segment-Adresse einer Speicherstelle an, in die TD den Wert 1 einträgt, um hardwareunterstützte Breakpoints einzuschalten. Diese Speicherstelle muß den Wert 0 enthalten, wenn der Treiber diese Möglichkeit nicht unterstützt. Wird sie unterstützt, dann nutzt Turbo Debugger dieses Byte dazu, dem Treiber mitzuteilen, daß er den Zugriff auf den Datenbereich des zu debuggenden Programms beendet hat, und ein folgender Zugriff einen Hardware-Breakpoint auslösen kann. Typ der Adresse ist unklar (siehe Text).

Kasten 5


1.Booten und Laden des Gerätetreibers   Code 0  2.Turbo Debugger starten                Code 8, Befehl 0                                          Code 4                                          Code 8, Befehl 1                                          Code 4  3.Im "hardware breakpoint options"-Menü Code 8, Befehl 4    einen Hardware-Breakpoint setzen      Code 4  4.Testprogramm starten oder STEP        Code 8, Befehl 2    (diese Folge kommt bei jedem STEP)    Code 4                                          Code 8, Befehl 3                                          Code 4  5.Hardware-Breakpoint mit               Code 8, Befehl 5    "Delete all" löschen                  Code 4  6.Turbo Debugger beenden                Code 8, Befehl 7                                          Code 4

[#anfang Seitenanfang]


Gerätetreiber TDHDEBUG geladen + ein aktiver Breakpoint:  0            Stop und TD-Fenster: Global breakpoint 1 at yyyy:zzzz  1.. FD       kein Stop des Testprogramms  FE           Stop und TD-Fenster: Hardware device driver stuck  FF           nur Stop des Testprogramms, aber kein TD-Fenster  Gerätetreiber TDHDEBUG geladen + kein Breakpoint aktiv:  0 - FE       kein Stop des Testprogramms  FF           nur Stop des Testprogramms, aber kein TD-Fenster  Gerätetreiber TDHDEBUG nicht geladen:  kein Handle  TD-Fenster: NMI Interrupt

Kasten 7


 1 NMI_ISR:pushf   2         push     ax   3         push     dx   4         mov      dx,HB_INPUT   5         IN       al,dx                     ;HB-Input-Reg. lesen   6         test     al,00001000B              ;Break-Taster ?   7         pop      dx   8         pop      ax   9         jz       NMI_2                     ;ja!   10         ; hier können noch diverse   11         ; Konditionen stehen   12         ; die mit Zero-Flag enden, falls Bedingung erfüllt   13         je       NMI_1                     ;   14   15 NMI_0:  call     RES_NMI                   ;NMI-FF Reset   16         popf                               ;Flags restaurieren   17         iret                               ;zurück zum Testprogramm   18   19 NMI_1:  call     RES_NMI                   ;NMI-FF Reset   20         popf                               ;Flags restaurieren   21         push     ax                        ;Original AX sichern   22         mov      ah,0                      ;Breakpoint-Handle   23         jmp      NMI_3                     ;zum Turbo Debugger   24   25 NMI_2:  call     RES_NMI                   ;NMI-FF Reset   26         popf                               ;Flags restaurieren   27         push     ax                        ;Original AX sichern   28         mov      ah,0FFH                   ;Break-Taster-Handle   29   30 NMI_3:  jmp      Far TD_Entry              ;zum Turbo Debugger  

[#anfang Seitenanfang]


8( 0)  Befehl 4 (Hardware-Breakpoint setzen)    04  ( 1)   Feld A (Breakpoint-Typ)                  04  ( 2)   Feld B (Gesuchte Adressen)               01  ( 3)   Feld C (Niederwertige Adresse)           00 03 00 00  ( 7)   Feld D (Höherwertige Adresse)            00 00 00 00  (11)   Feld E (Durchlaufzähler)                 01 00  (13)   Feld F (Gesuchte Datengröße)             01  (14)   Feld G (Quelle des gesuchten Bus-Zyklus) 03  (15)   Feld H (Gesuchte Daten)                  07  (16)   Feld I (Niederwertige Datenangabe)       03 00 00 00  (20)   Feld J (Höherwertige Datenangabe)        36 12 00 00  (24)   Feld K (Datenmaske)                      FF FF FF FF

[#anfang Seitenanfang]


  1 PROGRAM HBt;    2    3 USES  dos, crt;    4 VAR testvariable                   : WORD;    5     para                           : ARRAY[1..3] OF WORD;    6     zeile                          : STRING;    7     error                          : BOOLEAN;    8     op                             : CHAR;    9 {---------------------------------------------------------------}   10   11 FUNCTION scan:WORD ;   12 VAR i:BYTE;   13     result:INTEGER;   14     w:WORD;   15 CONST trenn:SET OF CHAR=[" ",",",":"];   16 BEGIN   17 i:=1;   18 WHILE (zeile <> "") AND (zeile [1] IN trenn) DO Delete (zeile,1,1);   19 WHILE (i < Length (zeile)) AND NOT (zeile[i+1] IN trenn) DO INC (i);   20 IF zeile="" THEN EXIT;   21 Val ("$"+Copy(zeile,1,i),w,result);   22 Delete (zeile,1,i+1);   23 scan:=w;   24 error:=result > 0;   25 END;   26   27 VAR i:BYTE;   28 BEGIN   29   ClrScr;   30   REPEAT   31   error:=FALSE;   32      WriteLn;   33      WriteLn ("i x / o x,y / r ssss:wwww / w ssss:wwww  /t /e ");   34      ReadLn (zeile);   35      IF zeile <>"" THEN op:=zeile[1]   36                    ELSE op:=#0;   37      Delete (zeile,1,1);   38      FOR i:=1 TO 3 DO para[i]:=scan;   39      IF NOT error THEN CASE UpCase (op) OF   40      "I": Write (port[para[1]]);   41      "O": port [para[1]]:=Lo (para[2]);   42      "R": Write (mem [para[1]:para[2]]);   43      "W": mem [para[1]:para[2]]:=Lo(para[3]);   44      "T": BEGIN   45           INC (testvariable);   46           Write (testvariable);   47           END;   48      "E":EXIT;   49      END;   50   UNTIL FALSE;   51 END.  

Ergänzung aus c't 7/93

Im Treiber HB.ASM für Turbo Debug (auf Sammeldisk/Mailbox) ist noch ein Testpunkt übriggeblieben, so daß der HardBreaker nur auf Adresse 200h erkannt wird. Abhilfe: hinter `;!!!!: test´ in Zeile 536 muß der Sprung: jmp init_3 weg beziehungsweise ein Semikolon bekommen. Sowohl HB.SYS als auch AFD-HB.COM aus c't 4/93 erkennen allerdings nur 200h und 300h. Um auch 280h und 380h richtig einzuloggen, sind bei INIT_1 die Zeilen AND AL,3 und CMP DL,AL zu ersetzen durch XOR AL,DL und TEST AL,3. Schließlich sollte man noch in AFD-HB.ASM in der Zeile 663 [MSG3] durch [MSG3+1] ersetzen.

Apropos Adresse, bei der Adreß-Tabelle in c't 1/93 gilt 0 für Jumper gesetzt, 1 für nicht gesetzt!

Sollte der Hardbreaker gleich beim Hochfahren des Rechners merkwürdige Schwierigkeiten machen, gar mit Parity-Fehler reagieren, haben Sie wahrscheinlich IC 20 falsch bestückt. Der muß wie im Schaltplan angegeben ein 74HC540 sein, kein 573!

Kommentare

Anzeige