Iczelion - 28 - Win32 Debug API Teil 1

Tutorial 28: Win32 Debug API Teil 1



In diesem Tutorial werden Sie lernen, was Win32 Entwicklern an Debugging-Möglichkeiten bietet. Sie werden wissen, wie Sie ein Programm debuggen können, wenn Sie mit diesem Tutorial durch sind.
Laden Sie das Beispiel herunter.

Theorie:

Win32 hat verschiedene APIs, welche dem Programmierer es erlauben ein wenig die Macht eines Debuggers zu benutzen. Sie werden Win32 Debug APIs oder ähnlich gennant. Mit ihnen können Sie:

  • ein Programm laden oder an ein laufendes Programm sich anhängen, um es zu debuggen.
  • Low-Level Informationen über das Programm, dass Sie debuggen herausfinden, so wie Prozess ID, Adresse des Einsprungspunkt, und so weiter.
  • Debugging-relevante Ereignisse bemerken, wie wenn ein Prozess/Thread startet/endet, DLLs geladen werden/entfernt werden, etc.
  • Den Prozess/Thread, der debugged wird, modifizieren.
Kurz gesagt, Sie können einen einfachen Debugger mit diesen APIs programmieren. Da dieses Thema sehr umfassend ist, werde ich es in verschiedene Teile teilen: Dieses Tutorial beginnt mit dem erste Teil. Ich werde die Basis-Konzepte und das generelle Framework erklären, um Win32 Debug APIs nutzen zu können.
Die Schritte, um das Win32 Debug API zu nutzen, sind:

  1. Erzeugen Sie einen Prozess oder hängen Sie ihr Programm an einen laufenden Prozess. . Das ist der erste Schritt um die Win32 Debug APIs zu nutzen. Da sich Ihr Programm wie ein Debugger verhält, brauchen Sie ein Programm zum debuggen. Das Programm das debugged wird wird auch Debuggee genannt. Sie können ein Debugge auf zwei Arten erhalten:
    • Sie erzeugen selbst einen Prozess mit CreateProcess . Um einen Prozess zum debuggen zu erstellen, müssen Sie das DEBUG_PROCESS Flag spezifizieren. Dieses Flag teil Windows mit, dass wir den Prozess debuggen wollen. Windows wird dann Benachrichtigungen wichtiger Debugging-relevanten Ereignisse (Debug-Ereignisse), die im Debugge auftreten, an ihr Programm leiten. Der Debuggee Prozess wird unverzüglich angehalten, bis Ihr Programm fertig ist. Wenn der Debuggee Prozess auch Child-Prozesse erzeugt hat, wird Windows auch Debug-Ereignisse, die in Child-Prozessen auftreten, an Ihr Programm weiterleiten. Dieses Verhalten ist in der Regel unbefriedigend. Sie können dieses Verhalten unterbinden, indem Sie das DEBUG_ONLY_THIS_PROCESS Flag in Kombination mit dem DEBUG_PROCESS Flag angeben.
    • Sie können mit DebugActiveProcess Ihr Programm an einen laufenden Prozess anhängen.
  2. Auf Debug-Ereignisse warten . Nachdem Ihr Programm ein Debuggee hat, wird der Haupt-Thread des Debuggee's angehalten und zwar so lange, bis Ihr Programm WaitForDebugEvent aufruft. Diese Funktion arbeitet wie die anderen WaitForXXX-Funktionen, z.B. wird der aufrufende Thread geblockt, bis das Ereigniss, auf das gewartet wird, auftritt. In diesem Fall, wird auf ein Debug-Ereignis von Windows gewartet. Schauen wir uns die Definition an:
    WaitForDebugEvent proto lpDebugEvent:DWORD, dwMilliseconds:DWORD


    lpDebugEvent ist die Adresse einer DEBUG_EVENT Struktur, die mit den Informationen über das Debug-Ereigniss gefüllt wird, welches im Debuggee auftritt.

    dwMilliseconds ist die Zeitspanne in Millisekunden, die die Funktion auf das Debug-Ereignis wartet. Wenn diese Spanne überschritten wurde und kein Debug-Ereignis aufgetreten ist, kehrt WaitForDebugEvent zum Aufrufer zurück. Wenn Sie andererseits aber, INFINITE als Parameter angeben, wird die Funktion so lange nicht zurückkehren, bis ein Debug-Ereignis aufgetreten ist.

    Nehmen wir die DEBUG_EVENT Struktur genauer unter die Lupe.

    DEBUG_EVENT STRUCT dwDebugEventCode dd ? dwProcessId dd ? dwThreadId dd ? u DEBUGSTRUCT DEBUG_EVENT ENDS


    dwDebugEventCode enthält den Wert, der die Art des Debug-Ereignisses näher spezifiziert. Kurz gesagt gibt es jede Menge Arten von Ereignissen, Ihr Programm muss den Wert in diesem Feld überprüfen um zu wissen welche Art von Debug-Ereigniss aufgetreten ist, um angemessen zu reagieren. Mögliche Werte sind:

  3. Value Meanings
    CREATE_PROCESS_DEBUG_EVENT Ein Prozess wurde erzeugt. Dieses Ereigniss wird gesendet, wenn der Debuggee Prozess (und noch nicht läuft) oder wenn das Programm sich selbst an einen laufenden Prozess mit DebugActiveProcess hängt. Das wird das erste Ereigniss sein, dass Ihr Programm erhalten wird.
    EXIT_PROCESS_DEBUG_EVENT Ein Prozess wird beendet.
    CREATE_THEAD_DEBUG_EVENT Ein neuer Thread ist innerhalb des Debuggee-Prozesses erzeugt worden oder Ihr Programm hängt sich selbst das erste Mal in einen laufenden Prozess. Beachten Sie, dass Sie diese Benachrichtigung nicht erhalten, wenn der Haupt-Thread des Debuggee erzeugt wird.
    EXIT_THREAD_DEBUG_EVENT Ein Thread in dem Debuggee-Prozess wird beendet. Ihr Programm wird dieses Ereigniss nicht empfangen, wenn es sich dabei um den Haupt-Thread handelt. Sie können sich den Haupt-Thread des Debuggee auch als den Debuggee-Prozess an sich vorstellen. Wenn Ihr Programm also CREATE_PROCESS_DEBUG_EVENT erhält, ist es für den Haupt-Thread eigentlich CREATE_THREAD_DEBUG_EVENT .
    LOAD_DLL_DEBUG_EVENT Das Debuggee lädt eine DLL. Sie erhalten dieses Ereigniss, wenn der PE-Loader das erste Mal die Links auf die DLLs auflöst (Sie rufen CreateProcess auf, um das Debuggee zu laden) und wenn das Debuggee LoadLibrary aufruft.
    UNLOAD_DLL_DEBUG_EVENT Eine DLL wird vom Debuggee-Prozess aus dem Speicher entfernt.
    EXCEPTION_DEBUG_EVENT Eine Exception tritt in dem Debuggee-Prozess auf.Wichtig: Dieses Ereignis tritt einmal auf, genau bevor das Debuggee mit der Ausführung des ersten Befehls beginnt. Die Exception ist tatsächlich ein Debug-Break (int 3h). Wenn Sie das Debuggee fortsetzen wollen, rufen Sie ContinueDebugEvent mit DBG_CONTINUE als Parameter auf. Benutzen Sie nicht DBG_EXCEPTION_NOT_HANDLED da das Debuggee ansonsten unter NT nicht laufen wird (unter Win98 läuft es einwandfrei).
    OUTPUT_DEBUG_STRING_EVENT Dieses Ereigniss wird generiert, wenn das Debuggee die DebugOutputString Funktion aufruft, um einen Nachrichten-String an Ihr Programm zu senden.
    RIP_EVENT Ein System debugging Fehler ist aufgetreten.
    dwProcessId und dwThreadId sind der Prozess und die Thread-ID des Prozesses, welcher die Debug-Ereignisse erzeugt hat. Sie können diese Werte benutzen, als Identifizierer des Prozesses/Threads benutzen, in dem Sie interessiert sind. Beachten Sie, dass wenn Sie CreateProcess benutzen, um das Debuggee zu laden, bekommen Sie ebenfalls die Prozess und Thread ID des Debuggee in der PROCESS_INFO Struktur. Sie können diese Werte dazu benutzen, um zwischen den Debug-Ereignissen im Debuggee und seinen Child-Prozessen zu unterscheiden (für den Fall, dass Sie nicht DEBUG_ONLY_THIS_PROCESS spezifiziert haben).

    u ist eine Union, die mehr über das Debug-Ereigniss enthält. Es kann eine der folgenden Strukturen sein, abhängig von dem Wert in dwDebugEventCode .

    Wert in dwDebugEventCode Interpretation von u
    CREATE_PROCESS_DEBUG_EVENT Eine CREATE_PROCESS_DEBUG_INFO Struktur namens CreateProcessInfo
    EXIT_PROCESS_DEBUG_EVENT Eine EXIT_PROCESS_DEBUG_INFO Struktur namens ExitProcess
    CREATE_THREAD_DEBUG_EVENT Eine CREATE_THREAD_DEBUG_INFO Struktur namens CreateThread
    EXIT_THREAD_DEBUG_EVENT Eine EXIT_THREAD_DEBUG_EVENT Struktur namens ExitThread
    LOAD_DLL_DEBUG_EVENT Eine LOAD_DLL_DEBUG_INFO Structur namens LoadDll
    UNLOAD_DLL_DEBUG_EVENT Eine UNLOAD_DLL_DEBUG_INFO Struktur namens UnloadDll
    EXCEPTION_DEBUG_EVENT Eine EXCEPTION_DEBUG_INFO Struktur namens Exception
    OUTPUT_DEBUG_STRING_EVENT Eine OUTPUT_DEBUG_STRING_INFO Struktur namens DebugString
    RIP_EVENT Eine RIP_INFO Struktur namens RipInfo
    Ich werde hier nicht weiter ins Detail bei den einzelnen Strukturen gehen, nur die CREATE_PROCESS_DEBUG_INFO Struktur soll hier angesprochen werden.
    Angenommen, unser Programm ruft WaitForDebugEvent auf und kehrt zurück. Das erste was wir machen sollten, ist den Wert in dwDebugEventCode untersuchen, um zu sehen, was für Art von Debug-Ereigniss im Debuggee-Prozess aufgetreten ist. Wenn der Wert in dwDebugEventCode zum Beispiel gleich CREATE_PROCESS_DEBUG_EVENT ist, können Sie das Element in u als CreateProcessInfo interpretieren und mit u.CreateProcessInfo drauf zugreifen.

  4. Machen Sie was Ihr Programm auch machen will, um auf ein Debug-Ereigniss zu antworten . Wenn WaitForDebugEvent zurückkehrt, bedeutet das, dass gerade ein Debug-Ereigniss im Debuggee-Prozess oder ein Timeout aufgetreten ist. Ihr Programm muss den Wert in dwDebugEventCode untersuchen, um angemessen auf das Ereigniss zu reagieren. In dieser Hinsicht, ist wie das Bearbeiten von Windows-Nachricht: einige bearbeiten Sie und andere ignorieren Sie.
  5. Lassen Sie das Debuggee seine Ausführung fortfahren . Wenn ein Debug-Ereignis auftritt, hält Windows das Debuggee an. Wenn Sie fertig mit der Ereigniss-Behandlung sind, müssen Sie das Debuggee wieder starten. Das machen Sie, indem Sie die ContinueDebugEvent Funktion aufrufen.
    ContinueDebugEvent proto ProcessId:DWORD, dwThreadId:DWORD, dwContinueStatus:DWORD


    Diese Funktion setzt den Thread fort, der zuvor auf Grund eines Debug-Ereignisses angehalten wurde.
    dwProcessId und dwThreadId sind die Prozess und Thread IDs des Threads, der fortgesetzt wird. Normalerweise nehmen Sie diese Werte aus den dwProcessId und dwThreadId Elementen der DEBUG_EVENT Struktur.
    dwContinueStatus spezifiziert wie der Thread, in dem das Debug-Ereigniss aufgetreten ist, fortgesetzt werden soll. Es gibt zwei mögliche Werte: DBG_CONTINUE und DBG_EXCEPTION_NOT_HANDLED . Für alle anderen Debug-Ereignisse machen diese beiden Werte das Selbe: den Thread fortsetzen. Die Exception ist das EXCEPTION_DEBUG_EVENT . Wenn der Thread ein Exception-Debug-Ereigniss berichtet, bedeutet das, dass eine Exception in dem Debuggee aufgetreten ist. Wenn Sie DBG_CONTINUE spezifizieren, wird der Thread sein eigenes Exception-Handling ignorieren und mit der Ausführung fortfahren. In diesem Szenario muss Ihr Programm selbst die Exception untersuchen und lösen, bevor der Thread mit DBG_CONTINUE fortgesetzt wird, ansonsten wird die Exception immer wieder und wieder und wieder... auftreten. Wenn Sie DBG_EXCEPTION_NOT_HANDLED spezifizieren, teilt Ihr Programm Windows mit, dass es die Exception nicht behandelt: Windows soll den Standard-Exception-Handler benutzen, um die Exception zu behandeln.
    Zusammgefasst kann man sagen, dass, wenn das Debug-Ereignis sich auf eine Exception im Debuggee-Prozess bezieht, sollten Sie ContinueDebugEvent mit DBG_CONTINUE aufrufen, wenn Ihr Programm schon den Grund der Exception entfernt hat. Ansonsten muss Ihr Programm ContinueDebugEvent mit DBG_EXCEPTION_NOT_HANDLED aufrufen. Außer in einem Fall, wo Sie immer DBG_CONTINUE benutzen müssen: das erste EXCEPTION_DEBUG_EVENT welches den Wert EXCEPTION_BREAKPOINT im ExceptionCode Element hat. Wenn das Debuggee seinen aller ersten Befehl ausführt, wird Ihr Programm das Exception-Debug-Ereigniss erhalten. Eigentlich ist es ein Debug-Break (int 3h). Wenn Sie als Anwort ContinueDebugEvent mit DBG_EXCEPTION_NOT_HANDLED aufrufen, wird Windows NT sich weigern, das Debuggee laufen zu lassen (da sich keiner drum kümmert). Sie müssen immer DBG_CONTINUE in einem solchen Fall benutzen, um Windows mitzuteilen, dass Sie den Thread fortsetzen möchten.

  6. Wiederholen Sie diesen Zyklus in einer Endlos-Schleife, bis der Debuggee-Prozess beendet wird . Ihr Programm muss in einer Endlos-Schleife sein, ähnlich wie bei der Message-Loop, bis das Debuggee beendet wird. Die Schleife sieht ungefähr so aus:
    .while TRUE invoke WaitForDebugEvent, addr DebugEvent, INFINITE .break .if DebugEvent.dwDebugEventCode==EXIT_PROCESS_DEBUG_EVENT <Behandel Debug-Ereignisse> invoke ContinueDebugEvent, DebugEvent.dwProcessId, DebugEvent.dwThreadId, DBG_EXCEPTION_NOT_HANDLED .endw


    Das liegt am folgenden: Wenn Sie ein Programm angefangen haben zu debuggen, kommen Sie nur davon wieder los, wenn das Debuggee beendet wird.

Lassen Sie uns die Schritte noch einmal zusammefassen:

  1. Erzeugen eines Prozesses oder hängen Sie Ihr Programm an einen laufenden Prozess .
  2. Auf Debug-Ereignisse warten .
  3. Machen Sie was Ihr Programm auch machen will, um auf ein Debug-Ereigniss zu antworten .
  4. Lassen Sie das Debuggee seine Ausführung fortfahren .
  5. Wiederholen Sie diesen Zyklus in einer Endlos-Schleife, bis der Debuggee-Prozess beendet wird

Beispiel:

Dieses Beispiel debugged ein Win32 Programm und zeigt wichtige Informationen wie Prozess-Handle, Prozess-ID und so weiter an.

.386 .model flat,stdcall option casemap:none include \masm32\include\windows.inc include \masm32\include\kernel32.inc include \masm32\include\comdlg32.inc include \masm32\include\user32.inc includelib \masm32\lib\kernel32.lib includelib \masm32\lib\comdlg32.lib includelib \masm32\lib\user32.lib .data AppName db "Win32 Debug Example no.1",0 ofn OPENFILENAME FilterString db "Executable Files",0,"*.exe",0 db "All Files",0,"*.*",0,0 ExitProc db "The debuggee exits",0 NewThread db "A new thread is created",0 EndThread db "A thread is destroyed",0 ProcessInfo db "File Handle: %lx ",0dh,0Ah db "Process Handle: %lx",0Dh,0Ah db "Thread Handle: %lx",0Dh,0Ah db "Image Base: %lx",0Dh,0Ah db "Start Address: %lx",0 .data? buffer db 512 dup(?) startinfo STARTUPINFO pi PROCESS_INFORMATION DBEvent DEBUG_EVENT .code start: mov ofn.lStructSize,sizeof ofn mov ofn.lpstrFilter, offset FilterString mov ofn.lpstrFile, offset buffer mov ofn.nMaxFile,512 mov ofn.Flags, OFN_FILEMUSTEXIST or OFN_PATHMUSTEXIST or OFN_LONGNAMES or OFN_EXPLORER or OFN_HIDEREADONLY invoke GetOpenFileName, ADDR ofn .if eax==TRUE invoke GetStartupInfo,addr startinfo invoke CreateProcess, addr buffer, NULL, NULL, NULL, FALSE, DEBUG_PROCESS+ DEBUG_ONLY_THIS_PROCESS, NULL, NULL, addr startinfo, addr pi .while TRUE invoke WaitForDebugEvent, addr DBEvent, INFINITE .if DBEvent.dwDebugEventCode==EXIT_PROCESS_DEBUG_EVENT invoke MessageBox, 0, addr ExitProc, addr AppName, MB_OK+MB_ICONINFORMATION .break .elseif DBEvent.dwDebugEventCode==CREATE_PROCESS_DEBUG_EVENT invoke wsprintf, addr buffer, addr ProcessInfo, DBEvent.u.CreateProcessInfo.hFile, DBEvent.u.CreateProcessInfo.hProcess, DBEvent.u.CreateProcessInfo.hThread, DBEvent.u.CreateProcessInfo.lpBaseOfImage, DBEvent.u.CreateProcessInfo.lpStartAddress invoke MessageBox,0, addr buffer, addr AppName, MB_OK+MB_ICONINFORMATION .elseif DBEvent.dwDebugEventCode==EXCEPTION_DEBUG_EVENT .if DBEvent.u.Exception.pExceptionRecord.ExceptionCode==EXCEPTION_BREAKPOINT invoke ContinueDebugEvent, DBEvent.dwProcessId, DBEvent.dwThreadId, DBG_CONTINUE .continue .endif .elseif DBEvent.dwDebugEventCode==CREATE_THREAD_DEBUG_EVENT invoke MessageBox,0, addr NewThread, addr AppName, MB_OK+MB_ICONINFORMATION .elseif DBEvent.dwDebugEventCode==EXIT_THREAD_DEBUG_EVENT invoke MessageBox,0, addr EndThread, addr AppName, MB_OK+MB_ICONINFORMATION .endif invoke ContinueDebugEvent, DBEvent.dwProcessId, DBEvent.dwThreadId, DBG_EXCEPTION_NOT_HANDLED .endw invoke CloseHandle,pi.hProcess invoke CloseHandle,pi.hThread .endif invoke ExitProcess, 0 end start


Analyse:

Das Programm füllt die OPENFILENAME Struktur und ruft dann GetOpenFileName auf, um den Benutzer das gewählte Programm zu debuggen.

invoke GetStartupInfo,addr startinfo invoke CreateProcess, addr buffer, NULL, NULL, NULL, FALSE, DEBUG_PROCESS+ DEBUG_ONLY_THIS_PROCESS, NULL, NULL, addr startinfo, addr pi


Wenn der Benutzer eins auswählt, wird CreateProcess aufgerufen, um das Programm zu laden. Danach wird GetStartupInfo aufrgerufen, um die STARTUPINFO Struktur mit den Standard-Werten zu belegen. Beachten Sie, dass wir DEBUG_PROCESS benutzen, kombiniert mit DEBUG_ONLY_THIS_PROCESS um nur dieses Programm zu debuggen und nicht seine Child-Prozesse.

.while TRUE invoke WaitForDebugEvent, addr DBEvent, INFINITE


Wenn das Debuggee geladen ist, betreten wir eine Endlos-Debug-Schleife, indem wir WaitForDebugEvent aufrufen. WaitForDebugEvent wird solange nicht zurückkehren, bis ein Debug-Ereignis im Debuggee auftritt, da wir INFINITE als zweiten Parameter angegeben haben. Wenn ein Debug-Ereignis auftritt, kehrt WaitForDebugEvent zurück und DBEvent wird mit den Informationen über das Debug-Ereigniss gefüllt.

.if DBEvent.dwDebugEventCode==EXIT_PROCESS_DEBUG_EVENT invoke MessageBox, 0, addr ExitProc, addr AppName, MB_OK+MB_ICONINFORMATION .break


Als erstes überprüfen wir den Wert in dwDebugEventCode . Wenn er gleich EXIT_PROCESS_DEBUG_EVENT ist, zeigen wir eine MessageBox an, die "The debuggee exits" enthält und springen aus der Debug-Loop.

.elseif DBEvent.dwDebugEventCode==CREATE_PROCESS_DEBUG_EVENT invoke wsprintf, addr buffer, addr ProcessInfo, DBEvent.u.CreateProcessInfo.hFile, DBEvent.u.CreateProcessInfo.hProcess, DBEvent.u.CreateProcessInfo.hThread, DBEvent.u.CreateProcessInfo.lpBaseOfImage, DBEvent.u.CreateProcessInfo.lpStartAddress invoke MessageBox,0, addr buffer, addr AppName, MB_OK+MB_ICONINFORMATION


Wenn der Wert in dwDebugEventCode gleich CREATE_PROCESS_DEBUG_EVENT ist, dann zeigen wir verschiedene interessante Informationen über das Debuggee in einer MessageBox an. Wir erhalten diese Informationen von u.CreateProcessInfo . CreateProcessInfo ist eine Struktur des Typen CREATE_PROCESS_DEBUG_INFO . Sie können mehr über diese Struktur in der Win32 API Referenz erfahren.

.elseif DBEvent.dwDebugEventCode==EXCEPTION_DEBUG_EVENT .if DBEvent.u.Exception.pExceptionRecord.ExceptionCode==EXCEPTION_BREAKPOINT invoke ContinueDebugEvent, DBEvent.dwProcessId, DBEvent.dwThreadId, DBG_CONTINUE .continue .endif


Wenn der Wert in dwDebugEventCode gleich EXCEPTION_DEBUG_EVENT ist, müssen wir weitere Überprüfungen vornehmen, um den genauen Typen der Exception zu ermitteln. Es ist eine lange Reihe an verschachtelten Struktur-Referenzen, aber Sie können die Art der Exception aus dem ExceptionCode Element erhalten. Wenn der Wert in ExceptionCode gleich EXCEPTION_BREAKPOINT ist und es das erste Mal auftritt (oder wir sicher sind, dass kein int 3h im Debuggee vorkommt), können wir sicher sein, dass diese Exception aufgetreten ist, als das Debuggee seinen ersten Befehl ausgeführt hat. Wenn wir mit der Bearbeitung fertig sind, müssen wir ContinueDebugEvent mit dem DBG_CONTINUE Flag aufrufen, um das Debuggee weiterlaufen zu lassen. Dann gehen wir zurücl und warten auf das nächste Debug-Ereignis.

.elseif DBEvent.dwDebugEventCode==CREATE_THREAD_DEBUG_EVENT invoke MessageBox,0, addr NewThread, addr AppName, MB_OK+MB_ICONINFORMATION .elseif DBEvent.dwDebugEventCode==EXIT_THREAD_DEBUG_EVENT invoke MessageBox,0, addr EndThread, addr AppName, MB_OK+MB_ICONINFORMATION .endif


Wenn der Wert in dwDebugEventCode gleich CREATE_THREAD_DEBUG_EVENT oder EXIT_THREAD_DEBUG_EVENT ist, zeigen wir eine Message Box an, die das mitteilt.

invoke ContinueDebugEvent, DBEvent.dwProcessId, DBEvent.dwThreadId, DBG_EXCEPTION_NOT_HANDLED .endw


Außer für den EXCEPTION_DEBUG_EVENT Fall oben, rufen wir ContinueDebugEvent mit dem DBG_EXCEPTION_NOT_HANDLED Flag auf, um das Debuggee fortzusetzen.

invoke CloseHandle,pi.hProcess invoke CloseHandle,pi.hThread


Wenn das Debuggee sich beendet, verlassen wir die Debug-Loop und müssen beides Prozess- und Thread-Handle des Debuggee schließen. Schließen der Handle bedeutet nicht, dass der Prozess/Thread gekillt wird. Es bedeutet nur, dass wir die Handles nicht mehr benutzen wollen, um auf den Prozess/Thread zuzugreifen.


Deutsche Übersetzung: Joachim Rohde
Die original Win32Asm-Tutorials stammen von Iczelion's Win32 Assembly HomePage