Analysis on Design and Implementation of Win32 Debug Interface

xiaoxiao2021-03-05  25

The so-called debugger is actually a broad concept. Any procedure that can monitor other programs in some form can be called debuggers. On the Windows platform, depending on the principle of debuggers can be divided into three categories: kernel debuggers, user status modulators, and pseudocode debuggers.

The kernel state debugger directly operates in the operating system core, debugging the system between the hardware and the operating system, common Softice, Windbg, WDEB386 and I386KD, etc., the user status is provided via the operating system Debug interface, debugging a user program between the operating system and the user state program, commonly available in various development environments such as debuggers, OLLYDBG, etc. with VC / Delphi; the pseudocode debugger uses the target system Definition debug interface, debug scripting language or virtual machine code supported by user state programs, common as JVM / CLR debugging tools, VB's PCode debugger, Active Script debugger, and more.

Because the pseudo code debugger is too strong with the specific system, it does not have a versatility of the principle level. This series does not involve its content, and if there is an opportunity to discuss the debug interface provided by JVM / CLR / Active Script; The user state initiator is the most widely used, and the reference material is also more complete. I will spend more energy and everyone; the core strategy is tightly combined with the operating system, plus me is not too familiar, I can only try my best. Ha ha. Welcome everyone to make criticism and advice :)

In addition, John Robbins is highly recommended in MSDN's Bugslayer column, as well as the book (Chinese version "Application Debug Technology"), which has a comprehensive explanation of debuggers from principle to applications.

[1] Probe into the structure of the user state debugger

The user state debugger directly uses the debug interface provided by the Win32 API, follows Win32's event-driven design ideas, and its ideas are very simple, the basic framework pseudo code is as follows:

// Start the process you want to debug or the mount adjuvement to the running process

CreateProcess (..., Debug_Process, ...) or debugactiveProcess (dwprocessid)

Debug_event de;

Bool bcontinue = true;

DWORD DWCONTINUESTATUSTAS;

While (bcontinue)

{

BContinue = Waitfordebugevent (& de, Infinite);

Switch (de.dwdebugeventcode)

{

...

DEFAULT:

{

DWCONTINUESTATUS = DBG_CONTINUE;

Break;

}

}

ContinueDebugevent (de.dwprocessid, de.dwthreadid, dwcontinuestus);

}

When the debugger starts debugging, the new process or mount (Attach) to the debugger is started to a running process, and the win32 system will start the server side of the debug interface; then the debugger calls the waitfordebugevent function Waiting for the debug server The end debug event is triggered; the debugger is processed according to the debug event; finally calls the ContinueDeBuGevent function request debug server to continue executing the debugged process to wait and process the next debug event.

First, we will roughly look at the server-side implementation ideas of the debug interface: the server-side interface of the debugging service is actually the debug port that exists in the debugged process. This core object implementation is similar to the Win32 completed port. A core queue implemented LPC port. The startup server is actually the template of the Win32 to the debug process and constructs the debug port within the debug process. The debugger communicates through debugging the port and Win32 debug subsystem; debugging the sub-system response system operation, and distributes the debug event to the core state / user status via the debug port. When establishing a new process of the debugger, you need to set the debug_only_this_process or debug_process flag to indicate that the new process needs to be debugged in the CreateProcess function. The call path of the CreateProcess function is as follows

CreateProcessa / CreateProcessw (kernel32.dll)

CreateProcessInternalw (kernel32.dll)

NtcreateProcessex (Ntoskrnl.dll)

PSpCreateProcess (NTOS / PS / CREATE.C: 969)

The CreateProcessinternalW function determines whether to construct the port core object to debug the port according to the incoming dwcreationFlags parameter, and set the corresponding debug flag of the PEB; PSPCREATEPROCESS selects the debugging of the target process based on the debug option and port object handle of the incoming parameter. Port; if you want to create, the incoming port handle is converted into a kernel object reference, saved in the eProcess-> Debugport field of the debugged program process.

The ISDebuggerPresent function provided by the Win32 API is to determine whether the current process is debugged by judging the flag bits set in the PEB by judging the flag bits set in the PEB. IsdebuggerPresent function pseudo code as follows:

Bool isdebuggerpresent (void)

{

Return ntcurrentteb () -> processenvironmentblock-> beingdebugged;

}

The structure of TEB and PEB can be found on http://www.ntinternals.net/.

However, this method is easy to directly modify the PEB memory structure directly by the debugger, so there is another method that is directly debugged by checking if the eProcess-> DebugPort field is used. There have been several discussions on the water on the water, such as the code given by the "Method of Detecting the Method of Debugger" in Blowfish. Windows XP / 2003 begins by the CHECKREMOTEDEBUGERPRESENT function provided by the Win32 API and uses the same idea. By calling the NTQueryInformationProcess function querying the debugging port is implemented, the pseudo code is as follows:

Bool CheckRemoteDebuggerPresent (Handle Hprocess, Pbool PbdebuggerPresent)

{

ENUM process_info_class {processdebugport = 7};

IF (HProcess && PbdebuggerPresent)

{

Handle Hport;

* pbdebuggerpresent = nt_success (NTQueryInformationProcess (HProcess, ProcessDebugport, & Hport, SizeOf (Hport), NULL)? True: false;

Return * pbdebuggerpresent;

}

Return false;}

Unlike the new process of creating the debugger, debug the debugActiveProcess function that is started to first connect to the port of the Win32 system debug server, and then activate the debug port currently running the debug process. The pseudo code for DebugActiveProcess is as follows:

Bool DebugActiveProcess (DWORD DWPROCESSID)

{

IF (dbguiconnecttodbg ())

{

Handle hprocess = processidtohandle (dwprocessid);

IF (HProcess)

{

DBGUIDEBUGACTIVEPROCESS (HPROCESS);

NTClose (HProcess);

}

}

Return False;

}

DBGUICONNECTTODBG function (NTOS / DLL / DLLUISTB.C: 27) Try connecting the core-provided debug subsystem port (name "/ dbguiapiPort"), if successful connection will get a port object (save in dbgusSReServed [ 1]), with a debug state converted signal script (saved in DBGSTATECHANGESEMAPHORE NTCURRENTTEB () -> DBGSSRESERVED [0]) to wait for debug events. The pseudo code is as follows:

#define dbgStatechangeseMaphore (NTCurrentteb () -> DBGSSRESERVED [0])

#define dbguiapiport (NTCurrentteb () -> DBGSSRESERVED [1])

NTSTATUS DBGUICONNECTTODBG (Void)

{

NTSTATUS ST = NTCONNECTPORT (& DBGUIAPIPORT, L "// dbguiapipody", ..., & dbgStateChangesemaphore;

IF (Nt_Success (ST))

{

NtregisterThreadterminaTeport (dbguiapiport);

}

Else

{

DBGUIAPIPORT = NULL;

}

Return ST;

}

If the connection tester is successful, call the NTREGISTERTHREADTERMINATEPORT function (NTOS / PS / PSDelete.c: 1202) Add the debug port to the termination port list of the current thread-controlled block (Ethread-> TerminationPortlist). Before the thread is over, the port in this list is activated, and the chance to the debugger is cleaned up.

The DBGUIDebugActiveProcess function completes the function of the debug server that activates the debugged process. The pseudo code is as follows:

#define dbguiapiport (NTCurrentteb () -> DBGSSRESERVED [1])

Void DBGuidebugActiveProcess (Handle Hprocess)

{

Return NTDebugActiveProcess (DBGUIAPIPORT) &&

DBGUIISSUEREMOTEBREAKIN (HPROCESS) &&

DBGUISTOPDEBUGGING (HPROCESS);

}

As for the specific implementation of these functions, the following chapters detailed analysis of Win32 debug subsystems, then explain in detail, huh

After the debug process starts debugging support, the debugger calls the waitfordeBugevent function Waiting for the occurrence of the debug event. This function is actually a simple package for the DBGuiWaitStateChange function (NTOS / DLL / DLLUISTB.C: 93), and completes the actual functionality by waiting for the debug event signal to wait for the DBGuiconnectTodBG function. If you successfully get a debug event, you will also report the DBGuiWaitStateChangeApi message to the debug server via the NTREQUESTWAITREPLYPORT function (NTOS / LPC / LPCSEND.C: 717). After processing the debug event, the debugger called the ContinueDeBugevent function is a simple packaging of the dbguicontinue function, but also informing the debug server to the debug server using the NTREQUESTWAITREPLYPORT function.

After completing the debug function, WinXP / 2003 also provides the debugactiveProcessStop function to stop debugging. The pseudo code is as follows:

Bool DebugActiveProcessStop (DWORD DWPROCESSID)

{

Handle hprocess = processidtohandle (dwprocessid);

IF (HProcess)

{

CloseallProcessHandles (dwprocessid);

DBGUISTOPDEBUGGING (HPROCESS);

IF (ntclose (hprocess))

Return True;

}

Return False;

}

DBGUISTOPDEBUGGING function (NTDLL.DLL) Call the zwremoveProcessDebug function (ntoskrnl.exe) Turns off the debugging port of the specified process, and implements the transfer port handle and process handle, call the 0xC7 system service to complete the final function. This is not discussed at this time, and you will hit: P

After understanding these, the implementation of the user status invoker should have a framework for understanding: its structure is an event-based model and then requests debug events to the debug subsystem and complete the specific operation.

[2] debugging incident

As mentioned earlier, the user status debugger under Win32 is actually a while loop. First, I will wait for a debug event first, then process, and finally the control right is also added to the debug server, as if a window message loop is the same. The core of debugging event is actually a debug_event structure, defined in the winbase.h file as follows:

Typedef strunt _debug_event {

DWORD DWDEBUGEVENTCODE;

DWORD DWPROCESSID;

DWORD DWTHREADID;

Union {

EXCEPTION_DEBUG_INFO EXCEPTION;

CREATE_THREAD_DEBUG_INFO CREATTHREAD;

Create_process_debug_info createprocessinfo;

EXIT_THREAD_DEBUG_INFO EXITTHREAD;

EXIT_PROCESS_DEBUG_INFO EXITPROCESS;

LOAD_DLL_DEBUG_INFO LOADDLL;

UNLOAD_DLL_DEBUG_INFO UNLOADDLL;

Output_debug_string_info debugstring;

RIP_INFO RIPINFO;

} u;

} Debug_event, * lpdebug_event;

The DWDeBugeventCode field gives the type of this debug event, DWProcessID and DWTHREADID fields give the process and thread ID number that occurred in debug events, respectively.

Debugging events generally have the following categories:

#define exception_debug_event 1 # define create_thread_debug_event 2

#define create_process_debug_event 3

#define EXIT_THREAD_DEBUG_EVENT 4

#define EXIT_PROCESS_DEBUG_EVENT 5

#define load_dll_debug_event 6

#DEFINE UNLOAD_DLL_DEBUG_EVENT 7

#define output_debug_string_event 8

#define rip_event 9

Every time there is a new / exit a thread CREATE_THREAD_DEBUG_EVENT / EXIT_THREAD_DEBUG_EVENT event is; CREATE_PROCESS_DEBUG_EVENT event is triggered when you create a new process of the first thread; EXIT_PROCESS_DEBUG_EVENT corresponding event in the process being debugged end of the last thread running was triggered Start; Each time you load / uninstall a DLL, there will be a load_dll_debug_event / unload_dll_debug_event / unload_dll_debug_event event; the debugger uses the outputdebugstring function to output a debug string when the debugger accepts an Output_debug_string_event event; an exception is triggered when the debugger accepts the debugger A first time EXCEPTION_DEBUG_EVENT event, if the debugger does not process this exception, enter the normal SEH call chain of the debugged program, if the debug process is not processed, then this event will be triggered again; Rip_Event is generally used to report an error event .

Generally, the program debugging event is initiated in order:

Create_process_debug_event

LOAD_DLL_DEBUG_EVENT X N // Static Loaded DLL

CREATE_THREAD_DEBUG_EVENT & EXIT_THREAD_DEBUG_EVENT // Multi-threaded program

LOAD_DLL_DEBUG_EVENT & Unload_dll_debug_event // Dynamically loaded DLL

EXCEPTION_DEBUG_EVENT X N // Random appears

OUTPUT_DEBUG_STRING_EVENT X N // The program is written when writing debug information

EXIT_PROCESS_DEBUG_EVENT

Next we analyze the causes and timing of each debug event trigger in detail. The specific debug event content is not coming here, and friends who are interested in writing the debugger can refer to the relevant content in MSDN and .

The first is the create_process_debug_event event of the process and create a CREATE_THREAD_DEBUG_EVENT event for the establishment of a thread. Both events are triggered by the DBGKCREATTHREAD function (NTOS / DBGK / DBGKPROC.H: 211). This function first checks if the current thread is an active thread with the debug port; then check if the current thread is the first thread created by the process; if it is not the first thread, or the debugger is an active process (Determination is based on whether this process takes up the user-based CPU time), trigger the CREATE_THREAD_DEBUG_EVENT event to the debug server of the tester; otherwise, the CREATE_PROCESS_DEBUG_EVENT event is reported.

DBGKCREATTHREAD function is as follows: Void DBGKCREATTHREAD (PVOID Startaddress)

{

IF (! psgetcurrentprocess () -> debugport || psgetcurrentthread () -> deadthread)

{

Return;

}

PslockProcess (Process, kernelMode, PslockWaitForever); // All threads in the process

IF (psgetcurrentprocess () -> pcb.usertime &&

PsgetCurrentProcess () -> CREATEPROCESSREPORTED == false)

{

PsgetCurrentProcess () -> CREATEPROCESSREPORTED = true;

// trigger CREATE_PROCESS_DEBUG_EVENT event

}

Else

{

// trigger CREATE_THREAD_DEBUG_EVENT event

}

PsunlockProcess (psgetcurrentprocess ());

}

When Win32 is creating a user-state thread, the approximate process is as follows:

CreateThread (kernel32.dll)

CreateremoteThread (kernel32.dll)

NtcreateThread (Ntoskrnl.exe)

PSpCreateThread (NTOS / PS / CREATE.C: 237)

When the PSPCreateThread function is created, use the pspuserthreadstartup function (NTOS / PS / CREATE.C: 1639) as a thread portfolio, so the thread is created directly into this function. The pspuserthreadstartup function initiates its APC for a non-derivation thread and the endless thread; then calls the DBGKCREATTHREAD function Notification The debugger takes the corresponding action; finally set the user's NOT CPU time to 1 to indicate that this process is started. For a special thread, a non-zombie thread has stopped when thread starts, then DBGKCREATTHREAD is called directly to call PSPEXITTHREAD to notify the debugger to take the corresponding action. PSPUSERTHREADSTARTUP function is as follows:

Void pspuserThreadStartup (in pkstart_routine startroutine, in pvoid startcontext)

{

IF (! psgetcurrentthread () -> deadthread &&! psgetcurrentthread () -> HASTERMINATED)

{

// Initialization thread APC

}

Else

{

IF (! psgetcurrentthread () -> deadthread)

{

DBGKCREATTHREAD (STARTCONTEXT);

}

PSPEXITTHREAD (status_thread_is_terminating);

}

DBGKCREATTHREAD (STARTCONTEXT);

IF (psgetcurrentprocess () -> pcb.usertime == 0)

{

PsgetCurrentProcess () -> pcb.usertime = 1;

}

}

And DbgkCreateThread function corresponds DbgkExitThread function (ntos / dbgk / dbgkproc.c: 384) and DbgkExitProcess function (ntos / dbgk / dbgkproc.c: 439), initiating the EXIT_THREAD_DEBUG_EVENT and EXIT_PROCESS_DEBUG_EVENT events to debug server. These two functions are called by the system kernel exiting the thread (NTOS / PS / PSDelete.c: 622) calls at the appropriate time. The PSPEXITTHREAD function detects whether the current process PCB thread list is only a thread of the current thread. If there is no other thread, the DBGKEXITTHREAD function is called. Otherwise, call the DBGKEXITTHREAD function.

In the Win32 system, the DLL is loaded and uninstalled, and the actual function call flow is as follows:

LoadLibrary (kernel32.dll)

LoadLibraryEx (kernel32.dll)

BaseploadLibraryAsDatafile (kernel32.dll)

NTMapViewOfsection (NTOS / MM / MapView.c: 204)

MmmapViewOfsection (NTOS / MM / MapView.c: 699)

The NTMapViewOfSecion function is called after the MMMapViewOfSECTION function (NTOS / MM / MAPVIEW.C: 699) completes the actual memory file mapping, depending on whether the marker bit of the map and the target process are the current process, it is determined whether to call the DBGKMapViewOfSECTION function (NTOS / DBGK) /DBGKPROC.C: 495) Notifying the debug server has a new image file being loaded. In response to the MMunmapViewOfSecion function (NTOS / MM / UmapView.c: 88) also calls the DBGKUNMAPVIEWOFSECTION function at the end of the function (NTOS / DBGK / DBGKPROC.C: 567) in the end of the function (NTOS / DBGK / DBGKPROC.C: 567) notification debug server An image file is uninstalled.

Unlike the previous events, the OutputDebugString function (kernel32.dll) is actually achieved by exception. And interesting is that this function is an example of a suffix ANSI version to complete the actual function. OutputDebugstringA function (kernel32.dll) actually uses the RaiseException function to trigger an exception of the software exception of 0x40010006, and passed the pointer and length of the string as an exception parameter.

DBGKFORWARDEXCEPTION function (NTOS / DBGK / DBGKPORT.C: 96) is called as a function that actually triggered an Exception_Debug_event debug event, is called in the system's exception (NTOS / KE / I386 / Exceptn.c: 797). The KidispatChexception function completions the core and user state's exception processing work according to the state of the abnormality being triggered.

For the core state, first give the core debugger a handling opportunity, then try to distribute the frame-based SEH exception chain, no processed, then give the core debugger a chance, if it is still not being processed, you can only call KebugCheckex Function (NTOS / Ke / Bugcheck.c: 157) blue screen, huh, huh.

For the user's state, or first try to make the core debugger process, if not, call the DBGKForwardException function distribution, if it is not processed, try, if it is still not processed, stop the thread and report an exception to the user. The kidispatchexception function is as follows:

Void Kidispatchexception (in PEXCEPTION_RECORD EXCEPTIONRECORD, IN PKEXCEPTION_FRAME EXCEPTIONFRAME, IN PKTRAP_FRAME TRAPFRAME, IN KPROCESSOR_MODE PREVIOUSMODE, IN BOOLEAN FIRSTCHANCE)

{

Context contextframe;

KeconTextFromkframes (TrapFrame, & ContextFrame); // Abnormal context from core abnormal frame (FRAME) Context

IF (ExceptionRecord-> exceptioncode == status_breakpoint) // Processing Trial breakpoint INT 3

{

ContextFrame.eiP -;

}

IF (PreviousMode == KernelMode)

{

IF (firstchance == true)

{

IF (kidebugroutine && kidebugroutine (..., false)! = false) goto handle1

IF (RTLDISPATCHEXCEPTION (ExceptionRecord, & contextFrame) == true) goto handled1;

}

IF (kidebugroutine && kidebugroutine (..., true)! = false) goto handle1

KebugCheckex (...); // core error, with a controlled way --_- b is white, it is Deadth Blue Screen, huh, huh

}

Else // previousmode = usermode

{

IF (firstchance == true)

{

IF (kidebugroutine && kidebugroutine (..., false)! = false) goto handle1

IF (DBGKFORwardException (ExceptionRecord, true, false) goto handled2;

// Translate exception information to user mode and try distribution

}

IF (DBGKFORwardException (ExceptionRecord, True, True)

{

Goto handled2;

}

Else IF (ExceptionRecord, False, True)

{

Goto handled2;

}

Else

{

ZWTERMINATTHREAD (NTCurrentThread (), ExceptionRecord-> ExceptionCode);

KebugCheckex (...);

}

}

Handled1:

KecontextTokframes (Trapframe, ExceptionFrame, & ContextFrame,

ContextFrame.ContextFlags, PreviousMode;

Handled2:

}

The DBGKForwardException function is called for three combinations of DebugException and SecondChance parameters. DEBUGEXCEPTION Sends information to the debug port when True, otherwise send it to the exception port.

At this point, we have a matter of probably aware of several common debug events. The next section will introduce the implementation ideas of win32 in the Win32 associated with these debug events and end-user status debugging.

转载请注明原文地址:https://www.9cbs.com/read-32405.html

New Post(0)