4.4 Datenstrukturen

 

4.4.1 I/O Request Packets (IRPs) zurück nach oben nächster Abschnitt Inhaltsverzeichnis

Wenn ein Usermode Programm eine I/O Anfrage startet, wird zunächst der I/O Manager aufgerufen. Dieser erzeugt nun eine Datenstruktur in einem nicht ausgelagerten Speicherbereich des System, die alle notwendigen Daten zur Abarbeitung der Anfrage enthält. Das IRP genannte Objekt wird danach an eine entsprechende Dispatch-Routine eines Treibers weitergeleitet. Die Dispatch Routine überprüft die an sie übergebenen Parameter und ruft entweder die Start I/O Routine oder eine weitere Dispatch-Routine eines tieferliegenden Treibers auf. Nach der vollständigen Bearbeitung der Anfrage werden Statusinformationen im IRP gespeichert und es wird wieder an den I/O Manager übergeben. Der übergibt die Daten des IRP an das Usermode Programm und schließt die Anfrage ab.

 

    Der Aufbau eines IRP

    Ein IRP besteht aus einem festen Teil, dem IRP Header, und einem oder mehreren Parameterblöcken, sogenannten I/O Stack Locations.

    Der Header enthält u.a. Daten über die Art und Größe der Anfrage, Statusinformationen und einen Zeiger auf einen Puffer, der zum Datenaustausch zwischen Treiber und Benutzeranwendung verwendet wird.

    Der Parameterblock enthält den Funktionscode der Anfrage und funktionsspezifische Parameter.

    Abbildung 4.4.1-1: IRP Aufbau

     

    Es sind nicht alle Teile der IRP Struktur dokumentiert, allerdings kann man in der Datei ntddk.h die vollständige Deklaration finden. In [Kernel96] ist der Aufbau wie folgt beschrieben:

     

    
    typedef struct _IRP {
        .
        .
        PMDL MdlAddress;
        ULONG Flags;
        union {
            struct _IRP *MasterIrp;
            .
            .
            PVOID SystemBuffer;
        } AssociatedIrp;
        .
        .
        IO_STATUS_BLOCK IoStatus;
        KPROCESSOR_MODE RequestorMode;
        .
        .
        BOOLEAN Cancel;
        KIRQL CancelIrql;
        .
        .
        PDRIVER_CANCEL CancelRoutine;
        PVOID UserBuffer;
        union {
            struct {
            .
            .
            union {
                KDEVICE_QUEUE_ENTRY DeviceQueueEntry;
                struct {
                    PVOID DriverContext[4];
                };
            };
            .
            .
            PETHREAD Thread;
            .
            .
            LIST_ENTRY ListEntry;
            .
            .
            } Overlay;
        .
        .
        } Tail;
    } IRP, *PIRP;
    
    

     

     

  1. MdlAdress
  2. zeigt auf eine Speicherbeschreibungsliste (Memory Descriptor List – MDL) für den Benutzerpuffer, wenn der Treiber im Direct I/O Modus arbeitet und eine IRP_MJ_READ oder IRP_MJ_WRITE Aktion durchgeführt werden soll.

  3. Flags
  4. Dieses Feld darf nur gelesen werden und kann folgende Zustände annehmen:

    IRP_NOCACHE
    IRP_PAGING_IO
    IRP_MOUNT_COMPLETION
    IRP_SYNCHRONOUS_API
    IRP_ASSOCIATED_IRP
    IRP_BUFFERED_IO
    IRP_DEALLOCATE_BUFFER
    IRP_INPUT_OPERATION
    IRP_SYNCHRONOUS_PAGING_IO
    IRP_CREATE_OPERATION
    IRP_READ_OPERATION
    IRP_WRITE_OPERATION
    IRP_CLOSE_OPERATION
    IRP_DEFER_IO_COMPLETION

     

  5. AssociatedIrp.MasterIrp
  6. zeigt auf das Master IRP, das von einem Treiber in der höchsten Ebene durch einen Aufruf von IoMakeAssociatedIrp erzeugt wurde.

     

  7. AssociatedIrp.SystemBuffer
  8. zeigt auf einen Systemspeicherbereich für eine der folgenden Aktionen:

    In jedem der Fälle werden Daten von bzw. auf diesen Speicherbereich übertragen.

     

  9. IOStatus
  10. ist der I/O Statusblock, in den vor Aufruf der Funktion IoCompleteRequest Statusinformationen gespeichert werden.

     

  11. RequestorMode
  12. gibt den Modus an, aus dem die Anfrage gestartet wurde, und hat entweder den Wert UserMode oder KernelMode

     

  13. Cancel
  14. Hat diese Variable den Wert TRUE, wurde oder wird die Anfrage abgebrochen.

  15. CancelIrql
  16. gibt den IRQL des Treibers an, wenn IoAcquireCancelSpinLock aufgerufen wird.

     

  17. CancelRoutine
  18. gibt die Adresse einer Cancel Routine an, die aufgerufen werden soll, wenn die Anfrage abgebrochen werden soll. Ist der Wert NULL, kann die Anfrage nicht abgebrochen werden.

     

  19. UserBuffer
  20. gibt die Adresse eines Ausgabepuffers an, wenn der Major Funktionscode den Wert IRP_MJ_INTERNAL_DEVICE_CONTROL und der I/O Control Code den Wert METHOD_NEITHER hat.

     

  21. Tail.Overlay.DeviceQueueEntry
  22. Wenn IRPs in der Gerätewarteschlange stehen, verbindet dieser Wert die IRPs in der Warteschlange. Der Wert ist nur gültig, wenn das IRP gerade vom Treiber verarbeitet wird.

     

  23. Tail.Overlay.DriverContext
  24. Wenn keine IRPs in der Warteschlange stehen, kann der Treiber hier bis zu vier Zeiger ablegen. Auf das Feld kann nur zugegriffen werden, wenn das IRP dem Treiber selbst gehört.

     

  25. Tail.Overlay.Thread
  26. zeigt auf den Thread Control Block des Aufrufers.

     

  27. Tail.Overlay.ListEntry
  28. Wenn der Treiber eine eigene Warteschlange für IRPs verwaltet, wird dieser Wert benutzt um die IRPs miteinander zu verbinden.

     

4.4.2 Das Treiberobjekt – Driver_Object zurück nach oben nächster Abschnitt Inhaltsverzeichnis

Jedes Treiberobjekt repräsentiert ein Abbild eines geladenen Kernelmode Treibers und enthält alle Einsprungadressen des Treibers. Wenn ein Treiber geladen wird, erzeugt der I/O Manager ein neues Treiberobjekt und ruft die Initialisierungsroutine DriverEntry auf. Dort werden alle weiteren Einsprungadressen in das Objekt eingetragen. Sollte der Treiber sich nicht initialisieren können, wird das Treiberobjekt wieder gelöscht. Bei einer I/O Anfrage benutzt der I/O Manager das Objekt, um die richtige Dispatch Routine zu finden und aufzurufen. Die Abbildung 4.4.2-1 zeigt die Struktur eines Treiberobjekts.

 

Abbildung 4.4.2-1: Das Treiberobjekt

 

Das Treiberobjekt ist nicht vollständig dokumentiert, die Deklaration findet man aber in der Datei ntddk.h. In [Kernel96] sind folgende Teile beschrieben:

 

  1. PDEVICE_OBJECT DeviceObject
  2. zeigt auf ein oder mehrere Device Objects, die vom Treiber erstellt wurden. Dieser Wert wird automatisch aktualisiert, wenn die Funktion IoCreateDevice erfolgreich aufgerufen wurde. In der Unload Routine werden dieser Wert und der Wert NextDevice von dem Device Object benutzt, um die Funktion IoDeleteDevice für alle vom Treiber erzeugten Device Objects aufzurufen.

     

  3. PUNICODE_STRING HardwareDatabase
  4. zeigt auf die Hardware Konfigurationsinformationen in der Registry.

     

  5. PFAST_IO_DISPATCH FastIoDispatch
  6. zeigt auf eine Struktur, die die Einsprungadressen des Treibers für Fast I/O enthält. Dieser Wert wird nur von FSDs und Netzwerk Transport Treibern benutzt.

     

  7. PDRIVER_INITIALIZE DriverInit
  8. zeigt auf den Eintrittspunkt der DriverEntry Routine und wird vom I/O Manager initialisiert. Die DriverEntry Routine ist wie folgt deklariert:

    NTSTATUS
    (*PDRIVER_INITIALIZE) (
    IN PDRIVER_OBJECT DriverObject,
    IN PUNICODE_STRING RegistryPath
    );

     

  9. PDRIVER_STARTIO DriverStartIo
  10. zeigt auf die Einsprungadresse der StartIo Routine und wird von der DriverEntry Routine initialisiert. Hat der Treiber keine StartIo Routine, ist der Wert NULL. Die StartIo Routine ist wie folgt deklariert:

    VOID
    (*PDRIVER_STARTIO) (
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp
    );

     

  11. PDRIVER_UNLOAD DriverUnload
  12. Läßt sich der Treiber entladen, wird der Eintrittspunkt der Unload Routine während der Initialisierung des Treibers von der DriverEntry Routine hier abgelegt, ansonsten ist der Wert NULL. Die Unload Routine ist wie folgt deklariert:

    VOID
    (*PDRIVER_UNLOAD) (
    IN PDRIVER_OBJECT DriverObject
    );

     

  13. PDRIVER_DISPATCH MajorFunction[IRP_MJ_MAXIMUM_FUNCTION]
  14. ist ein Feld mit einem und mehreren Eintrittspunkten von Dispatch Routinen des Treibers. Hier muß jeder Treiber mindestens einen Eintrittspunkt für IRP_MJ_XXX Funktionen, die er behandelt, festlegen. Es können so viele Eintrittspunkte festgelegt werden, wie IRP_MY_XXX Codes behandelt werden.

    Eine Dispatch Routine ist wie folgt deklariert:

    NTSTATUS
    (*PDRIVER_DISPATCH) (
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp
    );

 

4.4.3 Das Geräteobjekt und Geräteerweiterungen (DeviceObject / DeviceExtension) zurück nach oben nächster Abschnitt Inhaltsverzeichnis

Für jedes virtuelle, logische und physische Gerät im System gibt es ein Geräteobjekt, das Informationen über die Eigenschaften und den Status des Gerätes enthält. Die Abbildung 4.4.3-1 zeigt die allgemeine Struktur eines Geräteobjektes und die Beziehung zu anderen Objekten.

 

Abbildung 4.4.3-1: Das Geräteobjekt

 

Die DriverEntry Routine erzeugt für jedes Gerät, das der Treiber kontrolliert, ein Geräteobjekt mit Hilfe der Funktion IoCreateDevice. Über die Variable NextDevice kann der Treiber dann nacheinander die verschiedenen Geräte abfragen. Die Treiberfunktionen können einen Teil des Geräteobjektes, die Geräteerweiterung (DeviceExtension), zum Speichern von Daten und Statusinformationen verwenden. Dabei wird die Struktur der Erweiterung vom Treiber selbst festgelegt.

Die Unload Routine des Treibers ist für das Entfernen der/des Objekt/es zuständig. Wenn mehrere Geräteobjekte erstellt wurden, muß der Treiber über NextDevice alle Objekte abfragen und löschen.

Auch das Geräteobjekt ist nicht vollständig dokumentiert. Die komplette Deklaration ist wiederum in der Datei ntddk.h zu finden. In [Kernel96] werden folgende Strukturen beschrieben:

 

  1. PDRIVER_OBJECT DriverObject
  2. zeigt auf das zugehörige Treiberobjekt des Gerätes.

     

  3. PDEVICE_OBJECT NextDevice
  4. zeigt auf das nächste Geräteobjekt, wenn der Treiber mehrere Geräte verwaltet. Der I/O Manager aktualisiert die Liste nach jedem erfolgreichen Aufruf der Funktion IoCreateDevice.

     

  5. PIRP CurrentIRP
  6. zeigt auf das gegenwärtige IRP, wenn der Treiber gerade ein IRP verarbeitet. Ansonsten ist der Wert NULL.

     

  7. ULONG Flags
  8. Nachdem das Geräteobjekt erzeugt wurde, setzt der Treiber diesen Wert auf "Flags OR BufferAccess" wobei BufferAccess entweder DO_BUFFERED_IO oder DO_DIRECT_IO ist. HigherLevel-Treiber führen "Flags OR LowerLevelDriverFlags" aus.

     

  9. ULONG Characteristics
  10. wird auf einen der folgenden Werte gesetzt, wenn ein Treiber für Wechseldatenträger die Funktion IoCreateDevice mit einem der entsprechenden Werte aufruft:

     

  11. PVOID DeviceExtension
  12. zeigt auf die Geräteerweiterung. Die Struktur und der Inhalt der Geräteerweiterung wird vom Treiber festgelegt. Die Größe der Struktur muß der Funktion IoCreateDevice als Parameter übergeben werden.

     

  13. DEVICE_TYPE DeviceType
  14. wird beim Aufruf der Funktion IoCreateDevice auf den Wert des Parameters DeviceType der Funktion gesetzt und legt den Typ des Gerätes fest. Wenn keine der Systemkonstanten FILE_DEVICE_XXX zutreffend sind, können eigene Werte im Bereich von 32768 bis 65535 definiert werden. Eine Liste der FILE_DEVICE_XXX Werte findet man in [Kernel96] Reference Part 2. Der Bereich von 0 bis 32767 ist von Microsoft reserviert.

     

  15. CCHAR StackSize
  16. gibt die minimale Anzahl der StackLocations in einem IRP an, das dem Treiber übergeben wird. Die Funktion IoCreateDevice setzt diesen Wert auf 1. Der I/O Manager aktualisiert den Wert automatisch, wenn übergeordnete Treiber die Funktion IoAttachDevice oder IoAttachDeviceToDeviceStack aufrufen. Höher liegende Treiber, die sich selbst mit IoGetDevicePointer über andere Treiber legen, müssen StackSize in ihrem eigenen Treiberobjekt explizit auf (1 + StackSize des tieferliegenden Treibers) setzen.

     

  17. ULONG AlignmentRequirement
  18. Jeder Treiber setzt diesen Wert auf das benötigte Alignment des Treibers – 1 oder auf einen der Systemwerte:

    Höher liegende Treiber benutzen den Wert des zugrundeliegenden Treibers.

     

4.5 Debugging

 

4.5.1 Debugging mit WinDbg zurück nach oben nächster Abschnitt Inhaltsverzeichnis

WinDbg ist ein Debugger mit grafischer Oberfläche, der das Debuggen von Kernelmode Treibern auf Quellcodeebene ermöglicht. Wie im vorherigen Kapitel beschrieben, müssen dazu zwei Rechner vorhanden sein, die mit einem seriellen Kabel verbunden sind.

Hinweis: Der beim MSDN mitgelieferte WinDbg Version 4.00 ist offensichtlich veraltet und außerdem sehr instabil. Deshalb empfiehlt der Autor die Version WinDbg 5.00.1719.1, die separat im Internet von Microsofts Webseiten bezogen werden kann.

Um das Debugging zur aktivieren, muß am Zielrechner die Option /DEBUG in der Datei boot.ini eingetragen werden (Fußnote 10) und der Treiber muß in der Checked Build Umgebung übersetzt werden. Anschließend muß entweder der übersetzte Treiber oder das extrahierte Symbolfile in das Symbol-Verzeichnis des Debuggers kopiert werden. Zum Auslesen der Symbolinformationen aus dem Treiber verwendet man die beiden Tools dumpbin und rebase. Das Batch MAKEDBG.BAT demonstriert den Aufruf der Tools:

@echo off
e:
if %"1"=="" goto fehler
cd %1\i386\checked
echo %1
dir
dumpbin /headers %2.sys | findstr /i /c:"image base"
echo    10000 image base - OK ??? Ctrl+C fuer Abbruch
pause
rebase -b 0x10000 -x sys %2.sys
copy sys\sys\%2.dbg e:\checked\symbols\sys
goto ende
:fehler
echo Aufruf:
echo.
echo makedbg Verzeichnis Treiber
echo.
:ende

 

Im Debugger selbst muß unter Optionen das Kernedebugging aktiviert werden. Die Abbildung 4.5.1-1 zeigt das Dialogfenster. Die dort eingestellten Werte sind Empfehlungen des Autors.

Abbildung 4.5.1-1: Aktivierung des Kerneldebuggings in WinDbg

 

    Debugfunktionen

Für das Debugging im Kernelmodus stehen zwei Funktionen zur Verfügung:

Da der Compiler in der Checked Build Umgebung das Symbol DBG definiert, kann man außerdem mit Hilfe der bedingten Compilierung zusätzlichen Code zur Fehlersuche erzeugen. Der Autor verwendet diese Funktionalität in allen Treibern in einem Macro, das die Debugmeldungen nur in der Checked Build Umgebung in den Treiber einbindet:

#if DBG

#define DbgPrint(arg) DbgPrint arg

#else

#define DbgPrint(arg)

#endif

 

    Einige WinDbg Kommandos

Die Tabelle 4.5.1-1 zeigt eine kleine Auswahl von WindDbg Kommandos. Eine vollständige Beschreibung alle Kommandos findet man in der Online Hilfe des Debuggers.

 

Kommando

Funktion

help

zeigt eine kurze Hilfe für die grundlegenden Kommandos an

!help

zeigt eine kurze Hilfe für die Standarderweiterungen von WinDbg an

k, kb, kn, ks, kv

Ausgabe eines Ausschnitts des gegenwärtigen Kernelmode Stacks

!process 0 0

listet alle Prozesse des Systems

!drivers

listet alle geladenen Kernelmode Treiber

!pcr

zeigt die Kontextinformationen der 80x86 CPU

!irpzone

listet die zur Zeit benutzten IRPs

!irp adresse

zeigt den Inhalt eines IRPs

!devobj adresse

zeigt den Inhalt eines Geräteobjektes

!drvobj adresse

zeigt den Inhalt eines Treiberobjektes

.kill PID

beendet den angegebenen Prozess

BE, BD, BL

Breakpoints aktivieren (BE)

Breakpoints deaktivieren (BD)

Breakpoints auflisten (BL)

Tabelle 4.5.1-1: WinDbg Kommandos

 

4.5.2 Der Blue Screen of Death (BSOD) zurück nach oben nächster Abschnitt Inhaltsverzeichnis

Wer sich längere Zeit mit der Treiberentwicklung für Windows NT beschäftigt, wird mit ziemlicher Sicherheit früher oder später mit einem blauen Bildschirm konfrontiert, der allgemein als Blue Screen of Death bekannt ist. Dieser Bildschirm wird angezeigt, wenn durch einen schweren Fehler die Arbeit des Betriebssystems nicht fortgesetzt werden kann bzw. dessen Stabilität stark beeinträchtigt wäre. Die Ursache für den Absturz kann eine unbehandelte Exception (Fußnote 12) sein, die im Kernelmodus aufgetreten ist. Es kann aber auch ein Treiber im Falle eines Fehlers mit Hilfe der Funktion KeBugCheck die Beendigung des Betriebssystems veranlassen. Der BSOD enthält einige nützliche Informationen, die Auskunft über die Ursache des Absturzes geben können. Diese Informationen sind in mehrere Abschnitte gegliedert, die nachfolgend kurz erläutert werden. Die Abbildung 4.5.2-1 erklärt die Aufteilung der Abschnitte.

Abbildung 4.5.2-1: Der Blue Screen of Death

  1. Fehlerinformationen
  2. Dieser Abschnitt gibt Auskunft über die Art des Fehlers. In der ersten Zeile werden ein Bugcheck Code und bis zu vier zusätzliche Bugcheck Parameter angezeigt.

    Die zweite Zeile enthält den symbolischen Namen, der mit dem Bugcheck Code verknüpft ist, sofern der Code von Microsoft definiert wurde (Fußnote 13). Eine Erläuterung zu den Bugcheck Codes findet man in [WorkGuide97] und in [Baker97]. Sollte einer der Bugcheck Parameter eine Adresse angeben, werden in der zweiten Zeile auch die Basisadresse und der Name des Moduls angezeigt, das diese Speicheradresse belegt.

    In der dritten Zeile werden der Prozessortyp , der IRQL zur Zeit des Absturzes und die Build Nummer von Windows NT angegeben. Da die Funktion KeBugCheck den IRQL auf HIGH_LEVEL erhöht, ist auf Intel Rechnern der IRQL immer 0x1F (auf Alpha Rechnern 0x07). Das höchstwertige Byte der Build Nummer gibt an, ob es sich um ein Free (0xF0) oder um ein Checked (0xC0) Build handelt.

     

  3. Treiber Informationen
  4. Hier werden einige Informationen über die geladenen Treiber angezeigt. Die erste Spalte gibt die Basisadresse an, die zweite einen Zeitstempel und die dritte Spalte den Namen des Treibers. Der Zeitstempel enthält das Erstellungsdatum des Treibers in Sekunden seit 1970.

     

  5. Stack Ausschnitt
  6. Der dritte Abschnitt zeigt einen Teil des Stacks. Jede Zeile repräsentiert ein Stack Frame, wobei das zuletzt aktive an erster Stelle steht. Die erste Spalte gibt die Adresse des Stack Frames an. Die nächsten beiden Spalten enthalten die Rücksprungadresse (Fußnote 14) und die vier Spalten danach geben die ersten vier DWORD Parameter an, die beim Aufruf der Funktion übergeben wurden. In der letzten Spalte wird der Name des Moduls angezeigt, auf das die Rücksprungadresse in Spalte 2 und 3 zeigt.

     

  7. Anweisungen zur Wiederherstellung
  8. Im letzten Abschnitt wird angezeigt, ob eine Datei mit dem Speicherabbild (memory dump) erstellt wurde, und daß man beim wiederholtem Auftreten des Absturzes doch bitte seinen Administrator oder den technischen Support kontaktieren sollte. Ist der Kerneldebugger aktiv, werden außerdem Informationen über den Debugger Status ausgegeben.

     

4.6 Eventlogging zurück nach oben nächster Abschnitt Inhaltsverzeichnis

Windows NT bietet mit dem Eventlogging einen standardisierten Mechanismus, um wichtige Ereignisse in einem systemweiten Logfile aufzuzeichnen. Gerade für Treiber, die normalerweise keine Möglichkeit haben, auf direkten Weg mit dem Anwender zu kommunizieren, ist dies oft der einzige Weg, um Informationen über bestimmte Vorgänge aufzuzeichnen.

Um möglichst unabhängig von der nationalen Sprache zu sein, werden beim Eventlogging nur Codes gespeichert. Bei Anzeige des Logfiles lädt der Viewer, je nach der aktuell ausgewählten Sprache, die passenden Nachrichten aus einem sogenannten Messagefile.

In der Abbildung 4.6-1 wird der Informationsfluß bei der Speicherung und beim Anzeigen der Daten vereinfacht dargestellt.

 

Abbildung 4.6-1: Eventlogging

 

Im folgenden werden die Grundlagen des Eventlogging erläutert. Dabei wird auf Beispiele verzichtet. Der Treiber k, der im nächsten Kapitel vorgestellt wird, demonstriert die hier vorgestellten Techniken ausführlich.

 

4.6.1 Aufbau der Message Codes zurück nach oben nächster Abschnitt Inhaltsverzeichnis

Ein Message Code ist ein 32 Bit Wert, der in mehrere Felder eingeteilt ist. Die Abbildung 4.6.1-1 erläutert den Aufbau und die Bedeutung der Felder. In der Datei ntiologc.h des Windows NT DDK sind eine Reihe von Standardcodes definiert. Die dazugehörigen Meldungen sind in der DLL iologmsg.dll abgelegt.

 

Abbildung 4.6.1-1: Aufbau der Message Codes

 

Um eigene Meldungen zu verwenden, muß man zunächst ein Definitionsfile erstellen und dies mit dem Messagecompiler (mc) übersetzen. Danach müssen die erzeugten Files in den Treiber eingebunden und der Treiber in der Registry als Eventlog-Komponente registriert werden.

 

4.6.2 Erstellen eines Definitionsfiles für den Messagecompiler zurück nach oben nächster Abschnitt Inhaltsverzeichnis

Das Definitionsfile hat die Dateiendung .mc und ist in zwei Abschnitte gegliedert. Der erste Abschnitt, auch Header Section genannt, enthält die Definitionen für Werte, die im zweiten Abschnitt, der Message Section, verwendet werden. Die folgenden Schlüsselworte können in der Header Section verwendet werden:

In der Message Section stehen folgende Schlüsselworte zur Verfügung:

Beim Übersetzen des Definitionsfiles mit dem Message Compiler (Fußnote 15) werden folgende Dateien erzeugt:

 

4.6.3 Einbinden in den Treiber zurück nach oben nächster Abschnitt Inhaltsverzeichnis

Die erzeugten Dateien müssen noch weiterverarbeitet werden. Dafür gibt es zwei Möglichkeiten. Man kann sie in eine externe DLL einbinden oder man integriert sie direkt in den Treiber. Hier soll nur die zweite Möglichkeit kurz erläutert werden.

Das Einbinden der Dateien in den Treiber erfolgt über die SOURCES Datei (Fußnote 16). Dazu wird dem Wert SOURCES der Name des Ressource Control Scripts hinzugefügt. Da das Build Tool den Message Compiler nicht automatisch aufruft, muß der Aufruf von Hand erfolgen, wenn sich das Definitionsfile geändert hat.

 

4.6.4 Registry Einträge zurück nach oben nächster Abschnitt Inhaltsverzeichnis

Als nächstes muß der Treiber als Event Source (Ereignisquelle) im System registriert werden. Dies erfolgt über mehrerer Registry Einträge. Dem Wert Sources im nachfolgend angegebenen Schlüssel muß der Name des Treibers ohne Dateiendung hinzugefügt werden.

Registry Schlüssel:

HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\EventLog\System

Hier wird auch ein neuer Schlüssel angelegt, der ebenfalls nach dem Treiber benannt wird. In diesem Schlüssel werden zwei neue Werte eingetragen: als erstes der Wert EventMessageFile vom Typ REG_EXPAND_SZ, der die Namen (inklusive Pfadangabe) der verwendeten Message Dateien enthält. Sollte der Treiber nur eigene Meldungen verwenden, muß hier nur der Treiber selbst eingetragen werden (Fußnote 17), andernfalls müssen die anderen Dateien, mit Semikola voneinander getrennt, hinzugefügt werden. Der nächste Wert TypesSupported vom Type REG_WORD gibt eine Bitmaske für die verwendeten Arten der Meldungen an. Normalerweise wird hier 0x7 eingetragen, was alle Arten einschließt.

 

4.6.5 Erzeugen einer Eventlog Meldung zurück nach oben nächster Abschnitt Inhaltsverzeichnis

Nachdem alle Vorarbeiten abgeschlossen sind, kann der Treiber nun Meldungen im Eventlog erzeugen. Dazu sind folgende Schritte notwendig:

 

 

Das IO_ERROR_LOG_PACKET besteht aus einem Header, einem Datenfeld, dessen Länge vom Treiber festgelegt wird, und einem oder mehreren nullterminierten Unicode Strings. Die Abbildung 4.6.5-1 beschreibt den Aufbau genauer.

 

Abbildung 4.6.5-1: Das IO_ERROR_LOG_PACKET

 

 

Die Größe des Pakets, die der Funktion IoAllocateErrorLogEntry als zweiter Parameter übergeben werden muß, kann mit Hilfe folgender Formel berechnet werden:

PaketGroesse = sizeof(IO_ERROR_LOG_PACKET) +
DumpDataSize +
sizeof(InsertionStrings)

Wobei DumpDataSize die Größe des Datenfelds angibt und InsertionStrings alle zu übergebenden Strings enthält.

Als ersten Parameter der Funktion IoAllocateErrorLogEntry gibt man entweder ein Geräte- oder ein Treiberobjekt an, je nachdem, was am besten zu der Meldung paßt.

 

Wenn das IO_ERROR_LOG_PACKET erfolgreich reserviert werden konnte, werden die entsprechenden Daten in die Felder eingetragen. Zum Abschluß wird die Funktion IoWriteErrorLogEntry aufgerufen, die als einzigen Parameter einen Zeiger auf das IO_ERROR_LOG_PACKET übergeben bekommt. Danach schreibt der Logging Thread den Eintrag in das Eventlogfile und gibt anschließend den für das Paket reservierten Speicherplatz wieder frei (Fußnote 18).

 

4.7 Betriebssystemfunktionen im Kernelmodus zurück nach oben nächster Abschnitt Inhaltsverzeichnis

Windows NT stellt im Kernelmodus eine große Anzahl verschiedener Funktionen zur Verfügung. Abhängig von dem Modul, das die Funktionen zur Verfügung stellt, kann man mehrere Kategorien unterscheiden. Die Tabelle 4.6.5-1 gibt einen groben Überblick über die vorhandenen Funktionen und Kategorien.

 

Kategorie

Funktionen für ...

Funktionsnamen

Ausführungsschicht

(Executive)

Speicherreservierung,

Interlocked Queues,

Lookaside Lists,

System Worker Threads

ExXxx()

Hardware Abstraktionsschicht

(Hardware Abstraction Layer – HAL)

Zugriff auf Geräteregister,

Buszugriffe

HalXxx()

I/O Manager

Generelle Treiberunterstützung

IoXxx()

Kernel

Synchronisation,

DPCs

KeXxx()

Speicher Manager

virtuelles auf physikalisches Mapping,

Speicherreservierung

MmXxx()

Objekt Manager

Management von Handles

ObXxx()

Process Manager

Management von System Threads

PsXxx()

Laufzeitbibliothek

String Manipulation,

Arithmetikfunktionen,

Zugriff auf die Registry,

Sicherheitsfunktionen,

Zeit- und Datumsfunktionen,

Queue und Listenfunktionen

RtlXxx() (meist)

Sicherheitsüberwachung

Privilegierungsüberprüfung,

Security descriptor Funktionen

SeXxx()

Alle

Interne Systemdienste

ZwXxx()

Tabelle 4.6.5-1: Betriebssystemfunktionen im Kernelmodus (aus [Baker97])





written 1998/1999 by Guido Wischrop
all rights reserved