HardBreaker

PC-Hardware-Debugger in Theorie und Praxis

Wissen | Know-how

Debugger ("Entwanzer") lassen sich in zwei Kategorien einteilen: reine Software-Debugger und hardwareunterstützte Debugger. Die erste Gruppe wird von so bekannten Programmen wie DEBUG, SYMDEB, AFD-Pro, CodeView, Turbo Debugger gestellt. In der zweiten Gruppe finden sich Exoten, die aus einer Kombination von Software-Debugger und Zusatzhardware bestehen. In diesem c't-Projekt geht es um eben diese Zusatzhardware, die allerdings mit normalen Debuggern zusammenarbeiten kann.

Die heute wohl bekanntesten Hardware-Debugger werden von der Firma Periscope Company Inc., Atlanta hergestellt. Diese sind sehr leistungsfähig, haben aber auch ihren Preis: für das "Flaggschiff" Periscope IV, das die Echtzeitanalyse aller Prozessoraktivitäten erlaubt, kann man locker bis zu 9500 DM ausgeben.

Wer es etwas kleiner haben wollte, mußte bisher in die Röhre gucken. Denn in Europa gab es unseres Wissens bisher keinen "kleinen" Hardware-Debugger für PCs. Aber ... hier ist er! Und da das Kind selbstverständlich auch einen Namen haben muß, haben wir es "c't-HardBreaker" genannt. Der HardBreaker kann mit einer ganzen Reihe gängiger Software-Debugger zusammenarbeiten und ist selbst ohne Debugger noch ganz fidel. Als "Aussteuer" bekommt er im zweiten Teil einen Gerätetreiber für die Zusammenarbeit mit dem Turbo Debugger mit. Für danach stehen noch eine Befehlserweiterung für AFD-Pro und ein per Hot-Key bedienbares TSR-Programm auf dem Programm.

Was sind die Vorteile eines Hardware-Debuggers? Angenommen Sie interessieren sich dafür, was das unten abgedruckte, zugegebenermaßen etwas phantasielose Turbo-Pascal-Programm in der vorletzten Zeile aus dem Port $3BD (das ist der Printer Status Port der Druckerschnittstelle auf einer Hercules-Karte) einliest. Klar, mit dem eingebauten Sourcecode-Debugger von Turbo Pascal kein Problem: Dann muß nur der Cursor auf die letzte Zeile gesetzt und F4 gedrückt werden (ab TP-Version 5.0). Und schon sind tausendundein "x"se ausgegeben, der Portinhalt wurde eingelesen, das Ergebnis steht in der Variablen a bereit und kann abgefragt werden.

program speed_test; 
VAR a, i : WORD;
BEGIN
for i := 0 to 1000 do write('x');
a := Port[$03BD];
END.

Schwieriger wird"s, wenn statt Source nur die kompilierte Version vorliegt, wenn es sich bei dem Testprogramm um ein TSR-Programm (terminate and stay resident), um einen Gerätetreiber, um zeitkritische Routinen oder irgend etwas anderes "Unnormales" handelt.

Gute Dienste leistet dann ein auf "Assembler-Ebene" arbeitender Software-Debugger. Die meisten Vertreter dieser Gattung lassen auch globale Breakpoints zu. Bei obigem Beispiel könnte ein solcher globaler Breakpoint gesetzt werden, wenn DX=03BDh ist (gesucht wird ja nach der Sequenz "IN AL,DX" mit DX=03BDh). Falls das zu untersuchende Programm den Software-Debugger nicht vorher gewollt oder ungewollt abhängt, funktioniert das sogar und man findet höchstwahrscheinlich die gesuchte Stelle im Programm.

Ärgerliches gibt"s dabei aber von der Programmlaufzeit zu vermelden: Normalerweise hat das Testprogrämmchen eine Laufzeit von wenigen Millisekunden. Wenn dasselbe Programm aber unter der Kontrolle eines Software-Debuggers läuft und dabei ein globaler Breakpoint gesetzt ist, bläht sich der Zwerg zu einem Riesen auf und die Programmlaufzeit ändert sich dramatisch. So benötigt beispielsweise AFD-Pro bis zum Erreichen dieses globalen Breakpoints auf einem 8-MHz-XT gut 6 Minuten und der Turbo Debugger "prüft" sogar 33 Minuten (wenn das CPU-Window aktiv ist). 8-MHz-XTs sind zwar heutzutage nicht mehr unbedingt Stand der (Hobby-)Technik, aber die Relationen dürften deutlich werden. Außerdem soll es ja auch umfangreichere Testprogramme als das oben abgedruckte geben, so daß durchaus auch 50-MHz-486er zu Gebetsmühlen werden können, vor denen man lange Zeit däumchendrehenderweise sitzen muß, bevor sich der Debugger wieder meldet.

Abhilfe ist möglich mit den virtuellen Debuggern, insbesondere mit Borlands TD386. Dieser kann in Verbindung mit dem Treiber TDH386.SYS unter dem Menüpunkt "Hardware-Breakpoints" die Debug-Register der 386/486-Prozessoren nutzen sowie die im virtuellen Modus mögliche Überwachung aller I/O-Ports. Der Nachteil eines virtuellen Debuggers liegt auf der Hand: zum einen bleibt die gesamte XT- und AT-286er Welt außen vor (immerhin noch mehr als 50 Prozent des PC-Bestandes "draußen im Lande"), zum anderen knirscht es allzuoft, wenn andere Virtuelle im Spiel sind (etwa Memory-Manager wie EMM386). Manche zu debuggende Software mag den virtuellen Modus eh nicht - zum Beispiel, wenn sie gemäß [1] im Real-Modus mit einem 4-GB-Adreßraum umgehen möchte oder wenn sie andere "harmlose" Verstöße gegen Protected-Mode-Bedingungen enthält. Auch die zum Teil erheblichen Verzögerungen im virtuellen Modus bei einem Interrupt sind manchmal unakzeptabel.

In all diesen Fällen hilft ein echter Hardware-Debugger, der seinem überlasteten Software-Kumpan hilfreich zur Seite steht. Zur Terminologie sei hier bemerkt: Der Software-Debugger ist ein Programm - der Hardware-Debugger ist ein Stückchen Hardware im Rechner und kann nur mit einer entsprechenden Bediensoftware, in der Regel einem Software-Debugger sinnvoll arbeiten. Die Hardware wird im folgenden "HD-Board" und unser ganz spezielles Board wird "HardBreaker" genannt.

Bei der Teamarbeit dieses Duos passiert im Prinzip nun Folgendes: Das HD-Board lauscht an der Hardware des Rechners (daher sein Name) und löst einen nicht maskierbaren Interrupt (NMI) aus, wenn die vorher vom Software-Debugger eingestellte Bedingung tatsächlich eintritt. In der Zwischenzeit läuft das Testprogramm unter der Kontrolle des Software-Debuggers mit der normalen Geschwindigkeit und so, "als wenn nichts wäre". Für die obige Breakpoint-Bedingung heißt das: Wenn auf dem Adreßbus die Adresse 03BDH auftaucht UND ein gleichzeitiges IOR auf dem Steuerbus UND in dem Zyklus kein DMA stattfindet, dann wird ein NMI erzeugt. Dieser NMI weckt den vor sich hin dösenden Software-Debugger, der nun die Programmausführung unterbricht und sich wie nach einem "normalen" Breakpoint präsentiert.

Die unterste Kommunikationsebene zwischen Hard- und Softwareteil ist also die NMI-Leitung. Die Einstellung der Breakpoint-Bedingung auf dem HD-Board wird dagegen von der Software über Portadressen realisiert. Natürlich sind nicht alle oben aufgeführten Software-Debugger in der Lage, ein universelles beziehungsweise fremdes HD-Board einigermaßen komfortabel zu bedienen. Das Erkennen des NMI mag ja noch funktionieren, das bequeme Einstellen eines Hardware-Breakpoints über ein Menü im Software-Debugger ist aber bei kaum einem Debugger möglich.

Von den Dünnbrett-Debuggern DEBUG und SYMDEB werden wir in dieser Hinsicht auch diesmal nicht positiv überrascht, auch CodeView tut sich schwer. Rühmliche Ausnahmen sind allerdings AFD-Pro und Turbo Debugger, ohne die das Arbeiten mit unserem HardBreaker nur halb soviel Spaß machen würde. Obwohl diese beiden Arbeitspferde ganz unterschiedlichen Bedienphilosophien folgen, sind sie für unsere Zwecke doch fast ideal. Aber dazu später mehr ...

Fast jeder Mikroprozessor oder -controller kann mit einem "nicht maskierbaren Interrupt (NMI)" beglückt werden. Dieser Hardware-Interrupt hat nach dem Reset (auch das ist ein Interrupt!) die höchste Priorität aller Interrupts und unterbricht das momentan laufende Programm in jedem Fall. Im allgemeinen wird eine solch drastische Weise der Programmunterbrechung nur dann vorgenommen, wenn"s eilig oder Gefahr im Verzuge ist, wenn zum Beispiel die Betriebsspannung abgeschaltet wird (nicht beim PC) und dem Prozessor nur noch wenige Millisekunden Arbeitszeit zur Verfügung stehen, wichtige Systemvariablen in ein batteriegepuffertes RAM oder in ein EEPROM zu retten. Der Prozessor muß also in jedem Fall sofort mit dem nächsten Befehl reagieren und nicht erst nach dem Aufbau der nächsten Bildschirmmaske, nach dem Abschluß eines Diskettenzugriffs oder einer mathematischen Berechnung.

In der Regel können "störende" Hardware-Interrupts über die Software gesperrt werden, bis auf den NMI. Bei PCs können die Hardware-Interrupts einzeln oder "en bloc" abgeschaltet werden. Sollen nur bestimmte Interrupts unterdrückt werden, muß man sie beim Interrupt-Controller 8259A maskieren [2]. Wenn dagegen alle Interrupts (bis auf den NMI) gesperrt werden sollen, genügt der CLI-Befehl (Clear Interrupt Enable Flag) im Programm. IBM wäre aber wohl nicht IBM, wenn man uns nicht die eine oder andere Extrawurst in den PC gelegt hätte! So auch die Sache mit dem NMI: Da der NMI im Prozessor selbst nicht gesperrt werden kann, wurde eben die NMI-Peripherie mit entsprechenden Fähigkeiten ausgestattet. Also ... bei allen PCs kann auch der NMI von der Software stillgelegt werden! Was hat dieser degradierte NMI in PCs eigentlich noch für Aufgaben?

In "normalen" PC/XT/AT werkeln mindestens 640 KByte also circa 5,2 Mio. Bits im RAM vor sich hin, meistens aber sogar noch wesentlich mehr. Wenn auch nur ein einziges davon umkippt, kann das zum Totalabsturz des ganzen Systems führen - das ist aber noch die harmloseste Folgeerscheinung. Schlimmer ist, wenn hinfort mit falschen Daten gearbeitet wird, oder wenn im Extrem-Fall (Daten-GAU) viele Daten auf der Festplatte vernichtet werden.

Um zumindest den GAU zu vermeiden, hat IBM eine primitive Fehlererkennung für das RAM eingebaut. Jedes Byte wird mit einem Paritätsbit gesichert. Dieses Prinzip der Fehlererkennung ist einfach und läßt nur die Einfach-, Dreifach-, Fünffach- ... -Fehlerbiterkennung zu. Wenn beim Lesen die Parität nicht mehr stimmt, erzeugt die Parity-Logik einen Hardware-Impuls, der beim Prozessor als NMI ankommt und das ganze System mit einer entsprechenden Fehlermeldung stoppt ... wenn der NMI nicht gerade von der Software gesperrt ist.

Neben den Paritätsfehlern kennen die PC/ATs noch weitere Quellen, so meldet etwa beim PC der mathematische Coprozessor Fehlerbedingungen via NMI (bei AT läuft das über einen "normalen" Interrupt). PS/2-Systeme haben einen Watchdog-Timer, der über NMI zuschlägt.

Auch Zusatzkarten können einen NMI auslösen. Einige Video-Adapter wie zum Beispiel die ATi-Wonder-Karte machen davon auch Gebrauch. Die entsprechende Signalleitung auf dem Slot heißt /IOCHCK und liegt auf dem Platz A01 (nicht zu verwechseln mit der Adreßleitung A1). Dieses Signal war wohl ursprünglich dafür gedacht, einen Parity-Fehler auf gesteckten RAM-Karten anzuzeigen. Im Zeitalter der "xx MB on board" dürfte diese NMI-Signalisierung aber wohl etwas angestaubt sein.

Das Signal /IOCHCK liegt normalerweise auf High-Potential (über einen Pullup-Widerstand von 4,7 K[OMEGA] [4]) und kann von der Hardware einer Zusatzkarte auf Low gezogen werden. Da die NMI-Ansteuerungen mehrerer Karten parallel geschaltet sein können, sollte das unbedingt über eine Open-Kollektor-Stufe erfolgen (siehe Schaltplan des HardBreakers).

Ein auf dem Motherboard über /IOCHCK ankommender Low-Impuls (genauer die Flanke von High nach Low) wird in einem Flipflop zwischengespeichert und so von der Flankentriggerung in die für NMI nötige Pegeltriggerung gewandelt. Das Flipflop hält den aktiven Zustand so lange, bis es definitiv von der NMI-Behandlungssoftware wieder zurückgesetzt (scharf gemacht) wird - siehe Kasten.

Wenn ein NMI ausgelöst worden ist, weiß der Prozessor erst einmal nicht, wer sich da gemeldet hat: der Parity-Checker, irgendeine Zusatzkarte, ein Watchdog oder beim PC vielleicht der Coprozessor. Die aktivierte NMI-Routine muß daher die Quelle des NMI über einen Port ermitteln können, um daraufhin entsprechend zu reagieren.

Kam der NMI vom Parity-Checker, so sollte die NMI-Routine im Motherboard-BIOS die fehlerhafte RAM-Adresse feststellen, diese anzeigen und das System mit einer Fehlermeldung stoppen. Wenn als NMI-Quelle eine Zusatzkarte ausgemacht wird, hat"s nach Meinung des BIOS ebenfalls einen RAM-Parity-Error gegeben, nun allerdings auf einer gesteckten Speicherkarte. Auch diese fehlerhafte RAM-Adresse wird ermittelt (wenn möglich) und mit einer Fehlermeldung kundgetan.

Kommt der NMI nicht aus der Parity- oder I/O-"Ecke", ist das Motherboard-BIOS ratlos und gibt die Programmkontrolle unverrichteterdinge mit einem IRET wieder an das aktuelle beziehungsweise unterbrochene Programm zurück.

Eine Zusatzkarte, die über /IOCHCK mehr als Paritätsfehler signalisieren will, muß sich also immer per Adapter-BIOS oder Treiberprogramm in die NMI-Vektorkette einhängen. Da möglicherweise mehrere Karten sich per /IOCHCK und NMI bemerkbar machen wollen, muß jeglicher NMI-Treiber nach einem aufgetretenen NMI erkennen können, ob dieser NMI von "seiner" Hardware erzeugt wurde oder nicht. Alle NMI-Routinen sollten so programmiert sein, daß sie sich in die bereits vorhandene Vektorkette einklinken und die weiter hinten liegenden BIOSse nur dann abhängen, wenn der aufgetretene NMI zweifelsfrei von der eigenen Karte kam. Der Endpunkt dieser Kette liegt normalerweise immer im Motherboard-BIOS.

Der NMI-Vektor befindet sich in der Interrupt-Tabelle bei INT 2 (Adresse 0000:0008) und wird während des Bootens fleißig hin- und hergebogen. Das BIOS setzt den Originalvektor auf die Adresse F000:E2C3 (im XT und AT), das Videokarten-BIOS will eventuell aber auch mitreden bei der Behandlung von aufgetretenen NMIs und das DOS ebenso. Unter Umständen biegt auch noch das eine oder andere Programm an diesem Vektor herum und die Programme für den HardBreaker biegen erst recht ...

Über drei Eingabeleitungen kann der Prozessor einen externen Interrupt erhalten: RESET, NMI und INTR. Der RESET ist natürlich der kompromißloseste Interrupt und vom Sprungziel (FFFF:0000h) her nicht beeinflußbar. Er hat auch die höchste Priorität. INTR ist dagegen sperrbar und das Sprungziel kann bestimmt werden. Der "nicht maskierbare Interrupt" liegt zwischen diesen beiden Extremen.

Bei einem NMI wird zunächst der momentan bearbeitete Befehl beendet; auch REPs (etwa REP MOVSW) werden unterbrochen. Im Anschluß daran pusht der Prozessor zuerst das Flag-, dann das CS- und schließlich das IP-Register (vom nächsten Befehl) auf den Stack und löscht das Interrupt-Flag IF sowie das Trap-Flag TF. Damit sind weitere Interrupt-Anforderungen über INTR gesperrt. Danach setzt die CPU den Programm-Pointer CS:IP auf den INT-2-Vektor auf 0:8h.

Die anderen Hardware-Interrupts sind demgegenüber etwas aufwendiger - und somit auch langsamer -, da hier zuvor noch der Interrupt-Controller abgefragt werden muß.

Zurück in das unterbrochene Programm geht"s nach einem NMI genauso wie nach einem normalen Interrupt mit dem Befehl IRET, der sich den "alten" IP- und CS-Registerinhalt vom Stapel holt und auch das Flag-Register wieder restauriert.

Nach der bisher aufgewärmten Theorie und vor der Hardwarebeschreibung des HardBreakers kommt hier noch ein ganz, ganz kleiner und einfacher Light-Hardware-Debugger. Er besteht aus ... einem Draht! Wie das möglich ist? Das Grundprinzip des Hardware-Debuggers ist ja, daß er bei einem bestimmten Port- oder Speicherzugriff einen Low-Impuls erzeugt, den er als NMI zum Prozessor schickt.

Wer sich nun seine parallelen Druckerschnittstellen genauer ansieht, wird schnell feststellen, daß auch hier solche Low-Pulse ein Rolle spielen. Ausgaben an den Drucker etwa werden in der Regel durch ein /Strobe-Signal signalisiert. Dieses läßt sich also bequem mit der /IOCHCK-Leitung auf A01 verbinden. Eine NMI-Routine könnte dann alle Druckerausgaben protokollieren - eben auch diejenigen, die am INT 17h vorbei erfolgen.

Nur muß die NMI-Routine /STROBE wieder zurücksetzen, um Dauer-NMI zu vermeiden. Einfacher geht"s bei älteren Druckerports, welche diskret mit TTLs (LS374, LS244 etc.) aufgebaut sind. Hier kann man die CLK- und ENABLE-Signale anzapfen, welche sogar getrennt für Lese- und Schreibzugriffe an den Latches und Treibern anliegen.

Wenn sich nun den Dongle-Herstellern die Nackenhaare aufgestellt haben sollten (schließlich sind auch ihre "Dongle-Wirte" betroffen), so ist das verständlich - wiewohl modernere Dongle-Konzepte sich durch einfaches Mitlauschen noch längst nicht knacken lassen. Weiterhin läßt sich die geordnete Arbeit mit dem HardBreaker auch behindern, allerdings nicht ganz verhindern (wie, das folgt später).

Wenn oben gesagt wurde, daß nur ein Draht benötigt wird, so ist das etwas untertrieben, sorry. Zusätzlich muß nämlich noch ein Schalter in die Strippe eingebaut sein. Das ist nötig, weil sich der Rechner sonst bereits in der Initialisierungsphase mit einem "PARITY ERROR" verabschiedet. Denn bereits der POST ("Power-on-Self-Test") im BIOS spricht die Druckerports an. Sobald aber der DOS-Prompt ausgegeben wird und der Cursor ungeduldig blinkt, kann man getrost den Schalter umlegen und den HardBreaker "en miniature" scharfmachen.

Genausogut kann man das Chip-Select-Signal einer seriellen Schnittstelle, einer Netzwerkkarte et cetera anzapfen. Wie man sieht, sind aber immer Lötkolben-Eingriffe nötig, für eine gewünschte Datenrichtung muß man eventuell noch OR-Gatter hinzufügen. Das Ganze ist ausgesprochen unflexibel, und an moderne Chipsätze kommt man - wenn überhaupt - nur sehr mühsam heran.

Und genau hier bringt sich eben der Hardware-Debugger ins Spiel, der alle Portzugriffe aber zum Teil auch Speicherzugriffe abfangen kann. Letzteres ist bei allen HD-Boards leider etwas eingeschränkt, da bei den moderneren Boards die lokalen Speicher-Zyklen oft nicht vollständig auf dem Bus zu sehen sind. Die Kommandosignale /MEMR und /MEMW bleiben dann weg; immerhin sind - je nach Chipsatz - zumeist die Adressen noch "draußen" sichtbar. Den c't-HardBreaker kann man "glücklicherweise" auch so programmieren, daß er unabhängig von den Kommando-Signalen allein auf Adressen reagiert - wobei man dann die eine oder andere "Fehltriggerung" in Kauf nehmen muß. Hardwareerfahrenen Spezialisten bietet der HardBreaker auch an, die fehlenden Kommando-Signale vom Board zu holen, etwa /MEMW vom /WE-Signal an den DRAM-SIMMs.

Zwei weitere Dinge sind beim Umgang mit Speicher-Breakpoints zu berücksichtigen. Zum einen schlagen Hardware-Debugger bei Breakpoints auf Programmcode (Opcode Fetches) mitunter vor der Zeit zu, da der Prozessor immer vorausschauend die nächsten Code-Bytes einliest (Prefetch) - das ließe sich softwaremäßig jedoch erkennen und abfangen. Weiterhin beschränkt sich der c't-HardBreaker als 8-Bit-Karte auf den dazugehörigen Adreßbereich (1 MByte + 64 KB).

Doch nun zur Schaltung: sie gliedert sich in folgende Funktionsgruppen:

  • programmierbare Inverter (IC1 - IC5)
  • Signalmaskierung (IC6 - IC10)
  • Logik für Steuersignale (IC13)
  • Verknüpfung der internen Signale (IC11 - IC12)
  • Breakpoint-Register (IC14 - IC19)
  • Kontrollogik (IC20, IC21)
  • Adreßdekodierung (IC22, IC23)
  • manuelle "Notbremse" beziehungsweise Break-Taster

Ein Beispiel verdeutlicht die Arbeitsweise des Hardware-Debuggers: Es soll eine Unterbrechung (Hardware-Breakpoint) erfolgen, wenn der Prozessor einen Lese- oder Schreibzugriff auf die Ports 0278H-027Bh ausführt (Druckerport LPTx). Die Register des HardBreakers sind also so einzustellen, daß ein NMI erzeugt wird, wenn auf den Adreßleitungen folgendes Bitmuster auftaucht:

0000 0000 0010 0111 1000 (0278H)

oder

0000 0000 0010 0111 1001 (0279H 

oder

0000 0000 0010 0111 1010 (027AH)

oder

0000 0000 0010 0111 1011 (027BH)

Zusätzlich muß auch Folgendes zutreffen:

entweder

READ-Portzugriff /IOR = 0

oder

WRITE-Portzugriff /IOW = 0

und kein

READ-Speicherzugriff /MEMR = 1

und kein

WRITE-Speichergriff /MEMW = 1

und kein

DMA-Zugriff AEN = 0

Auslöser für den NMI ist dann das Bitmuster 1 (und nur dieses Bitmuster).

Die Breakpoint-Register (IC14 - IC16) für die programmierbaren Inverter (IC1 - IC5) werden von der Steuersoftware so eingestellt, daß alle in der gewünschten Adresse vorkommenden Nullen invertiert werden. Nach dieser Invertierung (die Inverter bestehen aus XOR-Gattern) entsteht Bitmuster 2.

Da eine gültige Portadresse in PCs (mit ISA-Bus) aber sowieso nur 10 Bit lang sein kann (000H-3FFH) und die Bits A1 und A0 in diesem Beispiel uninteressant sind, erfolgt in der nächsten Stufe (IC6-IC10) eine Maskierung der überflüssigen Adreßleitungen (A19-A10, A1, A0) mit dem Bitmuster 3.

Die Adreß-Bits des gesuchten Hardware-Breakpoints würden also nach dem Durchlaufen der beiden Stufen (Inverter, Maskierung) lauter Einsen erzeugen.

Die Steuerleitungen /IOR, /IOW, /MEMR, /MEMW und AEN werden gesondert behandelt, um auch ODER-Verknüpfungen für "Lesen ODER Schreiben" zu berücksichtigen. Diese Verknüpfungen werden mit IC13 und IC16 durchgeführt. Ganz am Ende entstehen auch hier wie bei der Adreßumformung Einsen, wenn die richtige Breakpoint-Bedingung von den Steuerleitungen erfüllt wird.

Zugegeben ein kleines TTL- - beziehungsweise hier stromsparender -CMOS-Grab, aber alles nur Standard-Chips.

Sobald die über die Breakpoint-Register (IC14-IC19) eingestellte Breakpoint-Bedingung restlos erfüllt ist, erzeugt die Logik des GALs (IC21) den heißersehnten Impuls für den NMI. Dieser Impuls wird korrekt über eine Open-Kollektor-Stufe geführt und dann über die Steuerleitung /IOCHCK dem Prozessor präsentiert. Die Länge dieses Impulses entspricht der Länge des I/O- oder /MEM-Zugriffs.

Aufs Slot-Blech haben wir verzichtet - das würde eh mehr stören als nützen, also selbst aufs richtige Einstecken achten! Die GALs haben noch viel Platz für potentielle Erweiterungen.

Wer jetzt etwas Phantasie und Logikkenntnisse besitzt, kann sich die Funktionsweise des HardBreakers auch bei allen anderen Arten von Port- und Speicherzugriffen zusammenreimen.

Vielleicht nun noch einige Worte zu den Steuerregistern der Kontrollogik (IC20 und IC21): Es gibt nur ein (physikalisch vorhandenes) HardBreaker-Register, das ausgelesen werden kann, und das ist IC20. Dieser Buffer (74HC540) kann allerdings über alle acht HardBreaker-Portadressen ausgelesen werden. Mit Hilfe dieser Redundanz läßt sich die Anwesenheit des HardBreakers in einem Rechner einigermaßen sicher feststellen und zwar ohne einen einzigen Schreibzugriff. So kann jede Steuersoftware die HardBreaker-Basisadresse selbst ermitteln, man muß sie also nicht einmal in der Kommandozeile übergeben. Der HardBreaker liefert eine signifikante Kennung (siehe Memory-Map), die im Zusammenhang mit den Adreßleitungen A1 und A0 entsteht. Das "blinde" Probeschreiben in ein mutmaßliches HardBreaker-Register kann man sich also sparen, denn immerhin könnte dort ja auch etwas ganz anderes "stecken": eine Netzwerkkarte, ein Streamer- oder sonstiger Controller.

Außer der Kennung kann die Steuersoftware über das HardBreaker-Register den aktuellen Zustand des Break-Tasters abfragen. Wie später gezeigt wird, kann der Turbo Debugger zwischen einem "richtigen" Hardware-Breakpoint und einem über den Taster erzwungenen NMI unterscheiden. Die Quelle für diese Weisheit ist das Bit D6 im Steuerregister. Das Bit D7 liefert das momentane Potential der NMI-Logik im HardBreaker und die Bits D0-D3 beinhalten den aktuellen Modus des HardBreakers. Es stehen also folgende Flags zur Verfügung:

  • D7: Zustand der NMI-Logik im HardBreaker (1 = kein NMI)
  • D6: Zustand des Break-Tasters (1 = betätigt)
  • D5, D4: Adreßleitungen A1 und A0, erzeugen die Kennung
  • D3-D0: eingestellter HardBreaker-Modus

Beim Auslesen und Interpretieren bitte beachten: IC20 ist ein invertierender Buffer!

Ferner sollte man darauf achten, daß der verwendete Break-Taster nicht allzusehr prellt (Digi-Taster). Zwar kann man auch softwaremäßig "langzeitentprellen", aber auch das hat irgendwo Grenzen.

Obenstehende Tabelle zeigt die Portbelegung aller HardBreaker-Register. Diejenigen Register, die die Bitmuster für die programmierbaren Inverter aufnehmen, tragen die Bezeichnung XAn. Die E...-Signale beeinflussen die Verknüpfungen der Steuersignale. In die OAn-Register werden die Maskierungsinformationen und in die vier Bits C3-C0 der HardBreaker-Modus geschrieben. Insgesamt sind drei HardBreaker-Modi vorgesehen (in IC 21 programmiert).

Auf die "komische" Zuordnung der drei gültigen Modi innerhalb der 16 möglichen Adressen wird weiter unten eingegangen.

Die letzten noch nicht beschriebenen Bits sind EMEMW, EMEMR, EIOW und EIOR. Diese Abkürzungen stehen jeweils für "Enable MEMW" und so weiter (alle Signale sind high-aktiv) und bestimmen den zu kontrollierenden Zugriff. Ein "I/O lesen oder schreiben" würde zum Beispiel mit EMEMW = 0, EMEMR = 0, EIOW = 1 und EIOR = 1 voreingestellt.

Die Programmierung der GALs mit dem bißchen Logik für Adreßdekodierung und anderen Verknüpfungen besteht aus ganz "einfacher Hausmannskost", nichts Besonderes (siehe GAL-Sources). Als mögliche Basisadressen wurden 200h, 280h, 300h und 380h vorgesehen. Diese Adressen können über S1 und S0 eingestellt werden (0 = kein Jumper, 1 = Jumper):

S1 	S0

200h 1 1
280h 1 0
300h 0 1
380h 0 0

Wer über einen GAL-Programmer verfügt, kann durch ein Umprogrammieren von IC22 aber natürlich auch eine andere individuelle Adreßlage vorgeben.

Beim Debuggen von ganz besonders "sensiblen" oder unfreundlichen Programmen kann es passieren, daß der NMI von der Software dauerhaft gesperrt wird. Damit bleibt der HardBreaker im Falle eines Falles außen vor. Wer das verhindern will, hat zwei Möglichkeiten: Mit dem HardBreaker lassen sich ja auch zunächst alle Zugriffe aufs NMI-Mask-Register abfangen und gegebenenfalls abwürgen. Ganz Mutigen bietet der HardBreaker auch einen anderen Weg an, nämlich direkt an den NMI-Eingang der CPU zu gehen (der ist nämlich wirklich nicht maskierbar). Das Unterfangen sollten nur erfahrene Löter und auch nur bei gesockelten CPUs mit DIL-Fassung (8088/V20, 80286) oder PGA (286, 386DX, 486DX) wagen, denen man einen passenden Zwischensockel spendiert. Bei diesem unterbricht man die NMI-Leitung und führt an die "Bruchstellen" je ein Käbelchen, welche auf der anderen Seite an den HardBreaker an die mit NMI markierten Pfostenstifte angeschlossen werden.

Zur Prüfung des HardBreakers schaltet man den zunächst NMI ab oder entfernt das IC24 (7406). Wenn die HB-Basisadresse etwa auf 300h gejumpert ist, dann wird das Read-Register über die Ports 300h-308h ausgelesen (mit DEBUG, AFD ... ). Bei nicht betätigtem Break-Taster muß auf allen acht Adressen Bit D6 = 0 und Bit D7 = 1 sein. Bei gedrücktem Taster sollte dementsprechend D6 = 1 und D7 = 0 herauskommen. Über die Bits D0-D3 muß man das invertierte obere Nibble von Port 305h auslesen können und über D4 und D5 die Adreßleitungen A0 und A1 (siehe Portbelegung). Nachdem in den Port 305h also eine 00 geschrieben wurde und der Taster nicht betätigt wird, muß an Port 300h ein 0BFh erscheinen, an Port 301h ein AFh und so weiter

Weiter geht es mit der "groben" Prüfung der ICs 6-13 und 17-19. Dazu programmiert man Folgendes:

Out 302h, 10h	; IOR 
Out 303h, FFh ; OR: 0..7 auf 1
Out 304h, FFh ; OR: 8..15 auf 1
Out 305h, 3Fh ; OR: 16..19 auf 1
; und scharfmachen
In 300h => 3Ch ; muß sein

Mit dem letzten Out-Befehl auf Port 305h wird der HardBreaker scharf gemacht (genauer die 3h im oberen Nibble (=3H). Als Reaktion darauf muß über Port 300h ein 3Ch einzulesen sein.

Mit dieser kleinen Testreihe werden schon fast alle ICs einschließlich der Adreßdekodierung überprüft. Die noch übrigbleibenden ICs 1-5 und 14-16 können dann direkt im "harten" Einsatz unter Echtzeitbedingungen geprüft werden.

Auch für den NMI-Test läßt sich DEBUG nutzen. Da es jetzt ernst werden soll, muß man vorher aber wieder das IC24 einsetzen. Außerdem sollte man das folgende kleine Programm eintippen (ab Adresse 0100H) und vorsichtshalber abspeichern:

MOV DX,03BC 
MOV AL,0
OUT DX,AL
JMP 100

In diesem kleinen Progrämmchen ist nur die Zeile OUT DX,AL interessant, denn das soll der NMI-Auslöser werden. Der HardBreaker ist dann folgendermaßen einzustellen:

Out 300h, 43h	;A7..0 = not BC 
Out 301h, FCh ;A15..8 = not 03
Out 302h, 2Fh ;A19..16 = not 00
; und IOW
Out 303h, 00h ;A7..0 OR 00h
Out 304h, FCh ;A15..8 OR FCh
Out 305h, 3Fh ;A19..16 OR 0Fh
; und scharf

Die ersten drei "XOR"-Outs setzen die Break-Adresse und IOW, die nächsten drei "OR"-Outs maskieren die unteren 10 Bits aus und aktivieren die NMI-Logik. Nun startet man obiges Programm mit G=100 - was dann passiert, hängt vom Debugger oder vom BIOS ab.

Der AFD etwa bleibt bei dem auf Out folgenden Befehl (JMP 100) stehen, unter DEBUG wertet das BIOS den NMI aus, den es als externen Paritätsfehler erkennen sollte. Teilweise darf man dann mit "S)hut off NMI" weitermachen ... oder eben den Reset-Knopf bedienen. Hätte man statt auf OUT DX,AL den HardBreaker auf IN AL,DX "getriggert", würde man an dieser Stelle eine Überraschung erleben. Die HardBreaker-Initialisierung überlebt nämlich auch den Reset (wird absichtlich nicht durch RESET DRV zurückgesetzt). Da aber das BIOS beim POST von dem überwachten Druckerport liest, erhielte man wieder einen NMI, folglich Paritätsfehler, folglich Reset ... Aus dieser "Schleife" kommt man tatsächlich nur heraus, wenn man den Rechner ausschaltet und damit die Registerinhalte auf dem HD-Board löscht.

An dieser Stelle müßte auch klar werden, warum zum "Scharfmachen" des HardBreakers die Bitkombination 0011 (C3-C0 in Port Basisadresse+5) gewählt wurde: Diese Kombination dürfte sich beim Power On kaum von selbst einstellen, so daß ein NMI zur Unzeit (etwa in der POST-Phase) gar nicht erst auftreten wird. Ähnlich verhält es sich mit den Befehlen zum dauerhaften Einschalten von IOCHK und OUTNMI (1010 und 1100), die ja eigentlich noch mehr Unsinn machen könnten.

Wer die nächste Ausgabe der c't nicht abwarten kann, darf sich schon jetzt selbst auf die Programmierung einer NMI-Service-Routine oder eines Turbo-Debugger-Gerätetreibers stürzen, das Rüstzeug in Form eines HD-Boards ist ja jetzt vorhanden. (as)

[1] Harald Albrecht, Grenzenlos, c't 1/90, S. 212

[2] Ernst/Stiller, PC-Bausteine, c't 8/88, S. 174

[3] Ernst/Stiller, PC-Bausteine, c't 7/88, S. 164

[4] Referenzhandbücher XT, AT

[5] Ascherl, Von Wanzen und ihren Jägern, c't 2-4/91

HardBreaker Teil 2, c't 2/93, Seite 206

Break-Helfer, c't 4/93, Seite 228


PCs und ATs gehen etwas unterschiedlich mit ihren "nicht maskierbaren Maskierungen" um.

NMI-Flipflop rücksetzen und NMI freigeben:
IN AL,61H
OR AL,00110000B
OUT 61H,AL
AND AL,11001111B ;NMI-Flipflop rücksetzen
OUT 61H,AL
MOV AL,10000000B ;NMI freigeben
OUT 0A0H,AL ;NMI-Steuerregister

NMI sperren:
MOV AL,00000000B
OUT 0A0H,AL

Beispiel für eine NMI-Routine (Interrupt-Service-Routine):

NMI_INT: PUSH AX
IN AL,62H ;NMI-Quelle ermitteln:
TEST AL,10000000B ;PARITY-ERROR ?
JNE NMI_PARITY
TEST AL,01000000B ;I/O-ERROR ?
JNE NMI_IO
POP AX ;weder PARITY- n.I/O-ERROR
IRET

NMI_PARITY: ;Programmteil für PARITY-NMI
........
POP AX
IRET

NMI_IO: ;Programmteil für I/O-NMI
........
POP AX
IRET

Die Belegung der Ports für die NMI-Behandlung ist bei XT und AT
unterschiedlich [3]. Für den XT gilt folgendes:

Parity-NMI freigeben/sperren (0/1) Port 61H / Bit D4
I/O-NMI freigeben/sperren (0/1) Port 61H / Bit D5
NMI insgesamt freigeben/sperren (1/0) Port A0H / Bit D7

I/O-NMI aufgetreten (1) Port 62H / Bit D6
Parity-NMI aufgetreten (1) Port 62H / Bit D7

Auch der AT hat natürlich diese fünf Funktionen, allerdings anders
verteilt. Die Funktion "NMI insgesamt freigeben/sperren" verdient hier
besonderes Augenmerk: im XT erfolgt die Freigabe mit einer 1 und beim AT
mit einer 0! Die Polarität aller anderen Signale ist gleich. Außerdem
sollte nach einem WRITE auf den Port 70H immer ein Dummy-READ auf Port 71H
erfolgen, damit die Echtzeituhr nicht durcheinanderkommt [3].

Parity-NMI freigeben/sperren (0/1) Port 61H / Bit D2
I/O-NMI freigeben/sperren (0/1) Port 61H / Bit D3
NMI insgesamt freigeben/sperren (0/1) Port 70H / Bit D7

I/O-NMI aufgetreten (1) Port 61H / Bit D6
Parity-NMI aufgetreten (1) Port 61H / Bit D7

(1) A19-------------------A0 /IOR /IOW /MEMR /MEMW AEN 
0000 0000 0010 0111 10xx 0 v 0 1 1 0
(2) A19-------------------A0 /IOR /IOW /MEMR /MEMW AEN
0000 0000 0010 0111 10xx 0 v 0 1 1 0
XOR
XA19-----------------XA0
1111 1111 1101 1000 0100
________________________________
1111 1111 1111 1111 1100
(3) OR
OA19-----------------OA0
1111 1111 1100 0000 0011
________________________________
1111 1111 1111 1111 1111

Vier Standard-Adreßbereiche sollten genügen ... wenn nicht, muß man sich eben ein anderes GAL brennen.


' HardBreaker, GAL13, Breakpoint-Verknüpfung
' W. Schrader, 19.11.92
' Assembled by the SPRINT PLD Macro Assembler #3/92

DEVICE 16V8;

"Signal Pin

IA16 13
IA17 12
IA18 6
IA19 5
#IOR 7
#IOW 8
#MEMR 9
#MEMW 11
EIOR 1
EIOW 2
EMEMR 3
EMEMW 4
#XMEMR 17
#XMEMW 18
GND 10

#BR3 19
VCC 20

MACRO SEL IA19 * IA18 *IA17 * IA16 ;

START

#BR3 /= &SEL * /#IOR * EIOR
+ &SEL * /#IOW * EIOW
+ &SEL * /#MEMR * EMEMR
+ &SEL * /#MEMW * EMEMW
+ &SEL * /XMEMR * EMEMR
+ &SEL * /XMEMW * EMEMW
+ &SEL * EIOR * EIOW * EMEMR * EMEMW ;
END

' HardBreaker, GAL21, NMI-Erzeugung
' W. Schrader, 19.11.92
' Assembled by the SPRINT PLD Macro Assembler #3/92

DEVICE 16V8;

"Signal Pin

#BR1 1
#BR2 5
#BR3 11
C0 9
C1 8
C2 7
C3 6
AEN 2
#BS 3
INNMI 4
GND 10

OUTNMI 18
IOCHK 19
VCC 20

MACRO AKT C0 * C1 * /C2 * /C3 * /AEN ;

START

IOCHK = /#BR1 * /#BR2 * /#BR3 * &AKT
+ /C0 * C1 * /C2 * C3
+ /#BS ;

OUTNMI = INNMI
+ /#BR1 * /#BR2 * /#BR3 * &AKT
+ /C0 * /C1 * C2 * C3
+ /#BS ;

END

' " HardBreaker, GAL22, Adreßdekodierung
' W. Schrader, 19.11.92
' Assembled by the SPRINT PLD Macro Assembler #3/92

DEVICE 16V8;

'Signal Pin

A3 1
A4 2
A5 6
A6 4
A7 7
A8 3
A9 5
AEN 8
#IOW 9
#IOR 11
S0 12
S1 13
CSI 19
GND 10

CSO 16
#CSR 18
CSW 17
VCC 20

MACRO &SELW /#IOW * /AEN * CSI ;
MACRO &SELR /#IOR * /AEN * CSI ;

START

CSW = &SELW * /A8 * /A7 * S1 * S0
+ &SELW * /A8 * A7 * S1 * /S0
+ &SELW * A8 * /A7 * /S1 * S0
+ &SELW * A8 * A7 * /S1 * /S0 ;
#CSR /= &SELR * /A8 * /A7 * S1 * S0
+ &SELR * /A8 * A7 * S1 * /S0
+ &SELR * A8 * /A7 * /S1 * S0
+ &SELR * A8 * A7 * /S1 * /S0 ;
END

HardBreaker

ICs
IC1 74HC86
IC2 74HC86
IC3 74HC86
IC4 74HC86
IC5 74HC86
IC6 74HC32
IC7 74HC32
IC8 74HC32
IC9 74HC32
IC10 74HC32
IC11 74HC30
IC12 74HC30
IC13 GAL16V8
IC14 74HC573
IC15 74HC573
IC16 74HC573
IC17 74HC573
IC18 74HC573
IC19 74HC573
IC20 74HC573
IC21 GAL16V8
IC22 GAL16V8
IC23 74HC237
IC24 7406
Kondensatoren 
C1 ..C16 100 nF
C17, C18 4µ7
Widerstände 
R1...R5 4k7
Jumper/Stecker 
S0,S1 Pfosten 2×1
SW1,NMI Pfosten 2×1
14 Fassungen DIL 16polig
10 Fassungen DIL 20polig

WRITE: READ:
D7...D0 D7...D0

Basisadresse XA7-XA0 xx00 xxxx
Basisadresse+1 XA15-XA8 xx01 xxxx
Basisadresse+2 EMEMW,EMEMR,EIOW,EIOR,XA19-XA16 xx10 xxxx
Basisadresse+3 OA7-OA0 xx11 xxxx
Basisadresse+4 OA15-OA8 xx00 xxxx
Basisadresse+5 C3-C0,OA19-OA16 xx01 xxxx
Basisadresse+6 xx10 xxxx
Basisadresse+7 xx11 xxxx

C3-C0:
0011 = HardBreaker-Board aktivieren
1010 = HardBreaker-Board testen ("Dauer-NMI")
1100 = HardBreaker-Board testen ("Dauer-NMI")
alle anderen = HardBreaker-Board inaktiv

Ergänzung aus c't 2/93

IC20 muß in der Stückliste 74HC4573 heißen (im Schaltplan ist es richtig). Alle ICs können natürlich auch HCT-Bausteine sein. Ferner wurde der Anschluß für XR und XW (für alternative READ und Write-Signale) noch etwas geändert.

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