Hintergründe zum Programm und Implementierungshinweise

Diese Seite soll dem, der sich für ausgewählte Teile der Realisierung des Programms, der internen
Darstellung, wie auch für Möglichkeiten der Realisierung eines Simulators mit gleichem Aufbau für andere
Prozessoren interessiert, Einblick in die Hintergründe geben. Das Programm wurde mit MS-VisualC++ 5.0 erstellt.
Die Klassen für die dockenden Sichten sind freie Software der Firma Micro Focus Inc. und sind auf der
Homepage von Mark Conway zu finden. MRCEXT wurde unter der GNU General Public License veröffentlicht.

Interne Darstellung von Typdefinitionen

Die interne Darstellung von Datentypen orientiert sich an der durch das OMF51-EXT Format vorgegebenen Struktur,
ist aber prinzipiell für jeden Prozessor geeignet.
Zunächst sind 11 verschiedene Grundtypen, d.h. die in C eingebauten Datentypen mit einer Nummer definiert.

0 untyped
1 Bit
2 signed char
3 unsigned char
4 signed short
5 unsigned short
6 signed long
7 unsigned long
8 float (4 Byte)
9 Double (==float)
10 Codelabel
11 void
12 float-long (8-Byte) (nicht implementiert bei Keil-C51!)

Letztendlich führt jeder noch so komplexe Datentyp im letzten Zweig auf einen dieser Typen. Komplexe Datentypen
werden nun dadurch beschrieben, daß eine verkettete Liste von verschiedenen Typdescriptoren aufgebaut wird.
Dazu sind weitere Typen definiert, die allerdings nicht den Typ selbst, sondern seinen Aufbau beschreiben.
Definiert sind:

0x20 Listen Descriptor, beschreibt eine Liste von Elementen.
0x21 Pointer Descriptor, beschreibt einen Keil-C51- Generic Pointer
und ist deshalb nur in Verbindung mit diesem Compiler relevant.
0x22 Array Descriptor, beschreibt Dimensionen und Größe jeder Dimension eines Arrays.
0x23 Function Descriptor, beschreibt ParameterListe und Rückgabewert einer Funktion.
0x24 Typ Descriptor, beschreibt den Namen eines definierten Typs.
0x25 Struktur Descriptor, beschreibt Größe und Anzahl der Elemente einer Struktur.
0x26 BitArray Descriptor
0x27 Spaced Pointer Descriptor, beschreibt einen Zeiger und den Speicherbereich, auf den er zeigt.
0x28 Generic Pointerdescriptor, beide Pointerdescriptoren sind in diese Form überführbar.

Also ein Beispiel:

Es sei eine solche C-Struktur definiert:

typedef struct {    
    unsigned short a;
    signed long l;
  } test_t;
und eine Variable x:   test_t x;

Folgende (einfache) Liste von Typdescriptoren wird nun aufgebaut:

1. StrukturDescriptor (Size=6)  
   
2. Listdescriptor (2 Elemente)  
  |--- Typ unsigned short (name="a",offset=0)
  |--- Typ signed long (name="l",offset=2)

Dabei wird jeder Typdescriptor, mit Ausnahme der Standardtypen durch die Instanz einer Klasse (CTypdesc)
realisiert, die sich gegenseitig über Pointer referenzieren. Der Listdescriptor enthält seinerseits eine Liste von
Zeigern auf seine Elemente. Ist der Wert der Referenz (des Zeigers) kleiner als 0x1E, ist es der endgültige Typ,
ansonsten ein Zeiger auf den Descriptor des Typs. Diese Unterscheidung ist möglich, da der Befehl new keinen
Speicher in diesem Adreßbereich 0-0x1E allokiert.
Es ist leicht vorzustellen, daß sich so auch kompliziert verschachtelte Strukturen beschreiben lassen. Ein Sonderfall
sind Pointer, die rekursiv auf den einen Referenzdatentyp zeigen. In diesem Fall ist die verkettete Liste in sich
geschlossen. Die Referenz zu einem bestimmten Symbol wird dadurch hergestellt, daß die Verwaltungsstruktur des
Labels (labeldef_t) den Zeiger auf das erste Element der Liste (hier also auf den Strukturdescriptor) zugewiesen bekommt.
Wurde nicht mit OBJEXT übersetzt, ist der Typ eines Label 0, d.h. ohne Typinformation. Ein Typ kann dann, wie unter
Datentypen ohne "OBJECTEXTEND" beschrieben, zugewiesen werden.

Aufbau der verketteten Liste für verschiedene Datentypen

Der Descriptor eines Elements kann seinerseits wieder eine der folgenden Kombinationen enthalten.

Struktur:
-> Strukturdescriptor -> Listdescriptor -> Descriptor für Element 0
-> Descriptor für Element 1
 .
-> Descriptor für Element n

Array:
-> Arraydescriptor -> Descriptor der Arrayelemente

Pointer:
-> Pointerdescriptor -> Descriptor des Typs, auf den der Zeiger weist

Die Belegung der einzelnen Elemente der Klasse "CTypdesc" findet sich im Header "typdesc.h".


Die Klasse CProc und ihre Funktionen

Die Klasse CProc stellt eine virtuelle Basisklasse dar, die ein möglichst universelles Interface zur Modellierung
verschiedenster Prozessoren realisiert. D.h. diese Klasse deklariert eine Reihe von Methoden, die innerhalb einer
konkreten Klasse zu realisieren sind, stellt also das Gerüst zur Einbindung verschiedener Prozessoren in die
Simulatoroberfläche zur Verfügung.
Natürlich besitzen die unterschiedlichen CPUs unterschiedliche Spezialitäten, die sich einer generischen
Verallgemeinerung entziehen (z.B. die Speicherbereiche des 8051).
Es ist also immer eine Anpassungsarbeit bei der Einbindung eines anderen Prozessors zu leisten, die nicht
nur in der Implementierung der durch CProc geforderten Funktionen besteht. Der Aufwand wird aber durch diese
Strukturierung erheblich verringert. Beispielsweise Derivate der 8051-Familie lassen sich so sehr einfach
implementieren, da nur die Realisierung der Klasse CProc51 zu ändern ist. Das Hauptprogramm enthält immer
eine Instanz der konkreten Prozessorklasse. (Also JSTEPApp besitzt die Membervariable CProc51 p51).
Alle anderen Module referenzieren diese aber über den Header "proc.h", der die virtuelle Basisklasse deklariert,
d.h. alle anderen Module kennen nur den "Universalprozessor". Nicht alle Funktionen können und müssen immer
unterstützt werden (z.B. Memorymapping, Breakpoints auf Datenzugriffe). In diesem Fall müssen die Funktionen
dennoch als Dummy vorhanden sein und einen entsprechenden Returncode liefern.
(Funktionen die unbedingt notwendig für die Funktion sind, werden im Folgenden mit [M] gekennzeichnet.)

!! In der Version 1 wird Memorymapping auch von der Oberfläche nicht unterstützt.

Funktionen und ihre Bedeutung

void Init( )
Initialisiert den Prozessor, d.h. löscht den Speicher und setzt alle Register auf den Resetwert.

void ResetCPU()
Setzt die simulierte CPU in den Resetzustand. Dieser sollte dem Zustand nach einem Hardwarereset entsprechen.

ULONG GetProgramCounter( ) [M]
Die Funktion liefert den aktuellen Stand des Programcounters als Long.

BOOL GetPointer(    
  ULONG* pointeraddr,  
  ULONG* pointerval,  
  CTypdesc* pointtyp=NULL,  
  USHORT pointmem=0,  
  ULONG* pointtomem=0 ) [M]

Die Funktion liefert den Wert eines Zeigers und den durch den Zeiger referenzierten Speicherbereich zurück. Die Funktion
ist sehr 8051-spezifisch, da andere Prozessoren in der Regel nicht verschiedene Zeiger auf verschiedene Speicherbereiche
besitzen. In einem solchen Fall sind die letzten drei Parameter nicht zu unterstützen.

*pointeraddr   Die Adresse des Zeigers. Da die tatsächliche Adresse sich
u.U. erst aus dem Lowteil und P2 ergibt, ist die Referenz auf
eine Variable zu liefern, die dann in der Prozedur modifiziert
wird.
*pointerval Die Referenz auf eine Variable, in die der Wert des Zeigers
eingetragen wird.
pointtyp Typ des Zeigers pointtyp->typ gibt die Art des Zeigers an.
pointmem Der Speicherbereich, in dem der Zeiger steht.
pointomem In diesen Parameter wird der Speicherbereich eingetragen,
auf den der Zeiger weist.

Kann ein Zeiger nicht ermittelt werden (z.b. ungültiger Speicherspezifizierer bei Generic-Pointer), liefert die Funktion FALSE,
ansonsten TRUE zurück.

ULONG GetMemSize(ULONG mempec=0) [M]
Liefert die Größe in Byte -1 des spezifizierten Speicherbereichs zurück. Prozessoren, die nur über einen Speicher verfügen,
unterstützen den Parameter nicht. Die Konstanten für den 8051 sind ebenfalls in "proc.h" definiert.

int GetMemAlignment() [M]
Liefert den Typ der Ablage von Werten im Speicher zurück. Diese Angabe ist insbesondere für die Darstellung in allen Watch-
und Speicherwindows erforderlich.
0 = H/L
1 = L/H

int ParseObjFile(CObjInfo* poi ,LPCSTR filename) [M]
Die Funktion wird beim Öffnen eines Absolutfiles aufgerufen. Als Parameter werden der Zeiger auf die Klasse "CObjInfo" und der
komplette Name und Pfad des Absolutfiles mitgegeben. Diese Funktion hat die Aufgabe, das Absolutfile einzulesen, den simulierten
Programmspeicher zu füllen, die Modul- und Prozedurdefinitionen anzulegen und letztendlich die Symbole, Labels und
HLL-Zeileninformationen entsprechend anzulegen. Kann das Absolutfile nicht richtig eingelesen werden, liefert die Funktion
-1, sonst 0 zurück. Ehe ein File eingelesen werden kann, muß der zu belegende Speicherbereich allokiert sein, bzw. per Default
vorhanden sein. Die aufgetretenen Fehler sollten mit einer MessageBox angezeigt werden.

BOOL LoadHexfile(CObjInfo* poi ,CString& hexfileName)
Die Funktion lädt ein Intel-Hexfile. Als Parameter werden der Zeiger auf die Klasse "CObjInfo" und der
komplette Name und Pfad des Absolutfiles mitgegeben.
Da das Hexfile keine Symbolinformationen enthält, wird faktisch
nur der Code-Speicher gefüllt.
Kann das Hexfile nicht richtig eingelesen werden, liefert die Funktion -1, sonst 0 zurück.
Ehe ein File eingelesen werden kann, muß der zu belegende Speicherbereich allokiert sein, bzw. per Default vorhanden sein.
Die aufgetretenen Fehler sollten mit einer MessageBox angezeigt werden.

UINT Reassemble( ULONG code, CString& ms, CObjInfo* pobj=NULL, int modID=-1) [M]
Die Funktion wird aufgerufen, um ab dem aktuellen Programcounter eine Anweisung zu reassemblieren.
Der gelieferte Text steht in ms. Damit auch Symbole richtig dargestellt werden können, wird der Zeiger auf die Objektinformation
bereitgestellt und auch die Modulkennung des Moduls, in dem zu reassemblieren ist.

int ExecNextCmd( ) [M]
Die Funktion simuliert den nächsten Assemblerbefehl ab dem momentanen Programcounterstand. Wird der Befehl ausgeführt,
liefert die Funktion 0 zurück. Läuft die Funktion auf einen Programm-Breakpoint, muß der Returnwert -1 zurückgegeben werden.
Läuft die Funktion auf einen Daten-Breakpoint, muß die Funktion -2 zurückgeben. Nebenbei sind noch folgende Aufgaben zu
realisieren:

- Ein Zähler, der mit "GetCycleCnt" abzufragen ist, ist je nach erforderlichen CPU-Zyklen zu erhöhen.
- Bei allen "Call"-Aufrufen ist die Funktion "AddToCallStackWnd" der Klasse CMainFrame mit der Adresse
des Call-Befehls aufzurufen.
- Bei allen "RET(i)"-Aufrufen ist die Funktion "RemoveFromCallStackWnd" der Klasse CMainFrame mit der
Rückkehradresse aufzurufen.
- Wenn Tracelog eingeschaltet ist, sind entsprechend dem Tracemodus der komplette Befehl mit Adresse und
Registerinformation oder aber die getraceten Variablen in ein File namens temptrace.log zu schreiben.

void SetProgramCounter(ULONG pc)
Setzt den Programcounter auf die übergebene Adresse.

ULONG GetStartUpAddress( ) [M]
Liefert die Startadresse nach einem Reset.

CRegInfo* GetRegInfo( ) [M]
Die Simulation des Prozessors soll eine Klasse CRegInfo enthalten, die die Informationen über die Register eines
Prozessors (Name, Adresse) enthält. Über diese Klasse wird vom Registerwindow auf die einzelnen Register
zugegriffen. Die Klasse CRegInfo ist bei der Initialisierung aufzubauen, indem für jedes Register ein Eintrag mit
"CRegInfo::AddReg" anzulegen ist (-> "reginfo.h"). Der Parameter ploc beim Aufruf der Funktion ist ein Zeiger
auf ein Register. (Beim 8051 liegen die Register mit im Speicher. Für andere Prozessoren müssen die Register
getrennt simuliert werden.)

ULONG GetRegOffset(int index=0) [M]
Liefert bei Registern, deren Adresse nicht konstant ist (Registerbank), den aktuell eingestellten Offset zurück.

BOOL SetBreakpoint(ULONG addr, USHORT fmt) [M]
Setzt an der spezifizierten Adresse einen Breakpoint. Die Formatangabe gibt an, ob es sich um einen permanenten
oder temporären Breakpoint im Programmcode, oder um einen Lese/Schreib-Breakpoint in einem bestimmten
Speicherbereich handelt. Ist an der Adresse kein Speicher vorhanden oder kann die Funktion nicht ausgeführt werden
weil das Format nicht unterstützt wird, liefert die Funktion FALSE, sonst TRUE.

BOOL RemoveBreakpoint(ULONG addr, USHORT fmt=0) [M]
Löscht den Breakpoint an der spezifizierten Adresse. Ist an der Adresse kein Breakpoint gesetzt, liefert die Funktion
FALSE, sonst TRUE.

BOOL IsBreakpointAtAddr(ULONG addr, USHORT fmt=0) [M]
Testet, ob sich an der spezifizierten Adresse ein Breakpoint befindet, der der Formatspezifikation entspricht. Wenn ja,
liefert die Funktion TRUE. Ist fmt=0, dann wird nach nur nach einem beliebigen Programm-Breakpoint an der Adresse
gesucht.

void RestoreOpcode(ULONG addr)
Stellt den originalen OP-Code wieder her, wenn der Programm-Breakpoint durch Setzen eines illegalen OP-Codes erzeugt
wurde. Der Breakpoint wird aber nicht gelöscht. Werden Programm-Breakpoints anders realisiert, ist die Funktion nur als
Dummy zu implementieren bzw. anzupassen.

void RestoreBkpt(ULONG addr)
Stellt an einer Adresse wieder einen Breakpoint durch Einfügen eines illegalen OP-Codes her. Der Breakpoint selbst muß
schon existieren. Werden Programm-Breakpoints anders realisiert, ist die Funktion nur als Dummy zu implementieren bzw.
anzupassen.

USHORT GetBkptFormat(ULONG addr, USHORT fmt=0)
Liefert das Format des Breakpoints auf der angegebenen Adresse, wenn vorhanden. Formate können sein:

BKPT_CODE     
BKPT_XDATA 0x0200 //(nur 8051!!)
BKPT_DATA 0x0300 //(nur 8051!!)
BKPT_IDATA 0x0700 //(nur 8051!!)
BKPT_TMP 0x0800  
BKPT_DISABLED
TRACEPOINT
RUNTIMEMPT

BOOL ClrTempBkpt(ULONG addr)
Löscht, wenn vorhanden, einen temporären Breakpoint und setzt den originalen OP-Code wieder ein. Wurde der temporäre
Breakpoint gefunden, ist der Rückgabewert TRUE.

bkpt_t* GetNextBreakpoint(POSITION& pos) [M]
Listet alle vorhandenen Breakpoints. Der erste Zeiger wird bei pos=0 und von da an bis pos wieder =0 geliefert.

ULONG GetStepOverAddr(ULONG addr) [M]
Liefert die Adresse des auf den aktuellen ASM-Befehl folgenden Befehls. Diese Funktion wird für die ASM-StepOver Funktion
benötigt. Bei allen nicht "call"-Befehlen ist diese Adresse gleich der übergebenen Adresse.

BOOL IsReturn(ULONG addr) [M]
Testet, ob an der spezifizierten Adresse ein RET oder RETI steht. Wenn ja, Returnwert=TRUE.

BOOL GetMemFromAddr(ULONG addr,ULONG* valp,ULONG memspec=0) [M]
Liefert ein Byte von der spezifizierten Speicheradresse im angegebenen Speicherbereich in *valp. Wenn erfolgreich, ist der
Returnwert=TRUE. Ist an der Adresse kein Speicher vorhanden, ist der Returnwert=FALSE.

BOOL SetMemAtAddr(ULONG addr,ULONG* val, ULONG memspec=0) [M]
Setzt ein Byte an der angegebenen Adresse und liefert in *val den alten Wert zurück. Ist die Funktion erfolgreich, ist der
Returnwert=TRUE. Ist an der Adresse kein Speicher vorhanden, ist der Returnwert=FALSE.

BOOL IsMemAtAddr(ULONG addr, ULONG memspec=0)
Testet, ob an der angegebenen Adresse im angegebenen Speicherbereich Speicher vorhanden ist. Wenn ja, ist der
Returnwert=TRUE.

int CreateMemInRange(ULONG startaddr, ULONG endaddr, ULONG memspec)
Legt für den Bereich Speicher der gewünschten Größe an. Ist das nicht möglich, kann der Speicher nicht allokiert werden,
oder der simulierte Prozessor kann den Bereich gar nicht adressieren, liefert die Funktion 0 zurück. Wird die Funktion
nicht unterstützt, ist der Rückgabewert = -1. Im anderen Fall einen Index für den allokierten Bereich.

BOOL DeleteMem(int memspec=-1, int index=-1)
Löscht den unter index angelegten Speicherbereich. Ist ein Index = -1, werden alle Bereiche unter dem Speicherspezifizierer
aus dem Mapping gelöscht. Ist der Speicherspezifizierer -1, werden unter dem Index alle Speicherbereiche aus dem
Mapping entfernt. Wenn erfolgreich, ist der Returnwert=TRUE, sonst FALSE. Wird die Funktion nicht unterstützt, ist der
Rückgabewert = FALSE.

CPtrList* GetMemMapping(ULONG memspec=0)
Liefert einen Zeiger auf eine Pointerliste, die alle allokierten Bereiche des angegebenen Speicherbereichs enthält, zurück. Jedes
Element der Liste ist ein Zeiger auf eine Struktur mit den Elementen Startadresse, Endadresse, Name des Speicherbereichs
und Zeiger auf den Anfang im PC-Speicher. Wird die Funktion nicht unterstützt, ist der Rückgabewert = NULL.

ULONG GetDestinationAddress(ULONG addr,ULONG memspec=0)
Liefert die tatsächliche, physische Adresse einer Speicherzelle. Normalerweise ist die logische = physische Adresse.
Ausnahme beim 8051 ist zum Beispiel der PDATA-Bereich, wo der höherwertige Adreßteil durch das P2-Register bestimmt
wird.

ULONG GetCyclCnt(BOOL delete=FALSE)
Liefert den aktuellen Stand des CPU-Zykluszählers und setzt diesen zurück.

BOOL EnableTraceLog(LPCSTR logfile=NULL,int tracetyp=0)
Wenn Enable=TRUE wird der Trace eingeschaltet. Das bedeutet, alle Daten werden hexkodiert in ein File "logfile.log"geschrieben.
Ist Logfile=NULL wird der Trace abgeschaltet. Wird die Funktion nicht unterstützt, ist der Rückgabewert = FALSE.

void SetMeasurePoint(CMeasurePoint* pmpt)
Setzt einen Meßpunkt. (-> Beschreibung Analysator-Fenster) Der Parameter ist ein Zeiger auf die Klasse CMeasurePoint. Bei Erreichen der Adresse, auf die der Meßpunkt gesetzt ist, sind die entsprechenden Membervariablen innerhalb der Funktion "ExecCmd" zu setzen. CMeasurePoint ist in "objinfo.h" definiert.

void SetStackWnd(void* ps)
Setzt den Zeiger auf das Stackfenster. Damit können durch die Simulation Stackoperationen in das Fenster eingetragen werden.

Der Zeilenparser

Der Zeilenparser stellt dem Benutzer eine einzige Funktion (Evaluate) zur Berechnung beliebiger C-Ausdrücke zur Verfügung.
Folgende Operatoren sind zulässig:

+,-,*,/,%,&,^,|,||,&&,[],(),~,->,.,>>,<<,>,>,==,sizeof

Zahlen können ganzzahlig dezimal, hexadezimal oder als Fließkommazahl eingegeben werden.

Das Argument von sizeof muß zur Vereinfachung immer geklammert sein.
Typecasts werden NICHT unterstützt!

Als Übergabeparameter ist ein Zeiger auf eine Struktur vom Typ eval_t mitzugeben. (-> parser.h)
Der Parameter "pexpression" ist ein nicht konstanter Zeiger auf den nullterminierten String des Ausdrucks,
der zu berechnen ist. Die Auflösung des Ausdrucks erfolgt folgendermaßen:
Jedem Operator ist eine Priorität zugeordnet. Prinzipiell wird davon ausgegangen, daß sich der Ausdruck
immer in Unterausdrücke der Form: Operand1-Operator1-Operand2-Operator2 zerlegen läßt.

  1. Hat Operator1 die gleiche oder eine höhere Priorität als Operator2, wird der Ausdruck:
    Operand1-Operator1-Operand2 berechnet und das Ergebnis Operand1 zugewiesen.
    Im Anschluß erfolgt dann die Berechnung: Operand1(Ergebnis von Operation1)-Operator2-Operand3.
     
  2. Besitzt, ausgehend von 1.), Operator1 eine niedrigere Priorität als Operator2, so erfolgt ein rekursiver Aufruf
    der Evaluierungsfunktion mit dem Unterausdruck Operand2-Operator2-Operand3.
    Operand2 bekommt das Ergebnis zugewiesen. Unter der Voraussetzung, daß ein vorhandener 3. Operator
    nicht auch noch eine höhere Priorität als Operator1 besitzt, wird im Anschluß die Berechnung Operand1-Operator1-Operand2(Ergebnis der 1.Operation) durchgeführt.

In dieser Weise werden auch tief verschachtelte Ausdrücke berechnet. Ein besonderes Problem stellen dabei die
monadischen Operatoren dar. Sie haben immer eine höhere Priorität als die diadischen Operatoren (ausgenommen Klammern).
Sie werden bei der Zerlegung eines Ausdrucks dem Operanden zugeordnet und gleich
bei der Berechnung des Wertes des Operanden berücksichtigt.
Die Operatoren . und -> zerlegen dagegen einen Operanden. Das ist deshalb erforderlich, da z.B. Ausdrücke der Form:
Name[ i ].element berechnet werden müssen. Das geschieht dann in folgender Weise:

  1. Berechnung der Basisadresse von "Name". Der Datentyp ist "array".
  2. Berechnung des Offsets, also   &Name + i*sizeof(Name)
    Der Datentyp des Ergebnisses ist der Typ "struct".
  3. Berechnung des Offsets von "element" bezogen auf das Ergebnis von 2.
  4. Returnwert ist der Wert von "element" an der Adresse berechnet unter 3.
    mit dem Datentyp von "element".

Zur genaueren Angabe des Gültigkeitsbereiches von Variablen können diese in der Form:
Modulname:Prozedurname:Variablename angegeben werden.
Der Typ des Ergebnisses ist immer der des Operanden1, ausgenommen boolsche Ausdrücke. Kann ein Ausdruck
evaluiert werden, liefert die Funktion den Rückgabewert=0.
Kann der Ausdruck nicht berechnet werden (z.B. Variable nicht gefunden oder Typinformation paßt nicht),
liefert die Funktion -1 zurück. Sind die Klammerebenen nicht paarig, ist der Rückgabewert = -2.


Einlesen eines Absolutfiles und interne Darstellung von Symbolen

Das Absolutfile liefert sog. Debug-Records. Diese beschreiben Symbole, die entweder Labels, Variablen oder
HLL-Zeilen identifizieren. Die symbolischen HLL-Adressen werden vom Compiler selbst erzeugt. Sie werden
benötigt, um den C-Code dem Assemblercode zuzuordnen. Um den Aufwand bei der Suche nach Variablen
und Labels zu verringern, werden die Zeileninformationen in getrennten Listen verwaltet.
Für jedes Label, Variable wird eine Struktur labeldef_t angelegt und in einer Zeigerliste verwaltet. Die Struktur
(definiert in objinfo.h) enthält folgende Angaben über das Symbol:

Der Datentyp ist entweder ein Standardtyp wie oben beschrieben, oder aber ein Zeiger auf einen Typdescriptor.
Zum schnellen Auffinden der Labeldescriptoren sind diese in einer Hash-Tabelle abgelegt, deren Key der
Labelname ist. Die Zeileninformationen werden ebenso in einer Hash-Tabelle verwaltet, deren Key die Adresse
der HLL-Zeile ist. Eine solche Tabelle wird für jedes Modul angelegt.
Die Daten der Klasse CObjinfo sind prinzipiell so strukturiert:

CObjinfo
|
|- Liste der geladenen Module
| |
| |- Modul 0
| | |
| | |- Modulname
| | |- Module-ID
| | |- Pfad zum Sourcefile
| | |- niedrigste Adresse
| | |- höchste Adresse
| | |- Liste der zum Modul gehörigen Prozeduren
| | | |
| | | |- Prozedurdefinition 0
| | | | |
| | | | |- Prozedurname
| | | | |- Startadresse
| | | | |- Endadresse
| | | :
| | | |- Prozedurdefinition n
| | |
| | |
| | |- Hashtable HLL-Zeilen (Key=Adresse)
| :
| |- Modul n
|
|
|- Hashtabelle Labels (Key=Name)
|
|- Labelliste1
| |- Labelinstanz 0
| | |
| | |- labelname
| | |- Adresse
| | |- Speicherbereich
| | |- Gültigkeitsbereich
| | |- Modul
| | |- Prozedur
| :
| |- Labelinstanz n
:
|- Labelliste n

Die Klasse CObjinfo stellt einige Funktionen zum Anlegen der notwendigen Verwaltungsinformationen für
Labels, HLL-Zeilen, Module und Prozeduren bereit.


int AddModuleInfo( LPCSTR modname=NULL, LPCSTR modpath=NULL)
Diese Funktion legt die Struktur für ein neues Modul an und liefert eine ID für das angelegte Modul zurück.
(Den Zeiger auf das Modul erhält man über objinfo.modules.GetAt(ID) ). Eine Prozedur wird in das Modul
mit der Funktion
CModDef.AddProc eingetragen.
Modulname ist der aus dem Absolutfile gelesene Modulbezeichner.
Der Pfad zum Quellfile muß nicht angegeben werden, wenn das Quellfile
im gleichen Pfad wie das Absolutfile steht.

int AddDbgInfo (ULONG addr,int type,char* label,int modid=-1,CProcDef* pproc=NULL,CTypdesc* ptd=NULL)

Die Funktion fügt der Labeltabelle ein neues Symbol hinzu.

addr  

Die Adresse des Symbols
wird gebildet durch die Addition von Speicherbereich (8051-spezifisch) und Gültigkeitsbereich des Symbols.
Der Gültigkeitsbereich kann folgende Werte annehmen:

Local      0
Public 1
Segment 2

Der Speicherbereich kann folgende Werte annehmen:

typ  
0x0100      CODE   (default)
0x0200 XADTA
0x0400 DATA
0x0800 IDATA
0x1000 BIT
0x2000 NUMBER
0x4000 Segmentlabel
0x8000 PDATA
label   Der Name des Labels.
modID   Die ID des Moduls, in dem das Symbol definiert ist.
pproc   Zeiger auf die Prozedurverwaltungsstruktur, in der das
Symbol definiert ist.
ptd   Entweder ein Standardtyp oder ein Zeiger auf einen Typdescriptor.
Enthält das Absolutfile keine Typinformationen, kann hier ein Typ
eingetragen werden. Das Symbol wird dann im Watchfenster mit
diesem Typ angezeigt. (Empfohlen: der int-Typ des Prozessors.)

int AddDbgInfo(ULONG addr, int lineno, int modid)
Die Funktion trägt in die HLL-Zeilentabelle des Moduls die Zeilennummer ein. Damit kann unter Angabe einer Adresse
erkannt werden, ob zur Adresse eine entsprechende HLL-Zeile gehört.

UCHAR* GetOMFRec(CFile* fp)
Die Funktion liefert einen Record eines unter fp geöffneten Absolutfiles zurück. Funktioniert natürlich nur bei Files,
in denen die Records in der Form Recordtyp(16)-Recordlänge(16)-Daten(x)-Checksumme(8) abgelegt sind.
Liefert die Funktion 0 zurück, ist das Ende des Files erreicht.