Windows用户态调试器原理

Windows用户态调试器原理
本文介绍了Windows用户态调试器的工作原理,包括如何创建调试目标、处理调试事件以及查看和修改调试目标。详细讲解了使用CreateProcess和DebugActiveProcess创建调试目标的方法,调试循环中的事件处理流程,以及调试事件的具体类型。

Windows用户态调试器原理

Windows操作系统提供了一组API来支持调试器。

这些API可以分为三类:

创建调试目标的API

在调试循环中处理调试事件的API

查看和修改调试目标的API

接下来将会分别对这三种API进行介绍。

创建调试目标

在调试器工作之前,需要创建调试目标。用户态调试器有两种创建调试目标的方法:一是创建新进程,二是附加到一个运行的进程。采用这两种方法中的任一种后,该进程就成为了调试目标。操作系统将调试器与调试目标关联起来。

调试器创建调试目标是通过调用CreateProcess并传入DEBUG_PROCESS标志。

如:

STARTUPINFO si={0};

si.cb=sizeof(si);

PROCESS_INFORMATION pi={0};

bool ret=CreateProcesss(NULL,argv[1],NULL,NULL,false,

DEBUG_PROCESS,NULL,NULL,&si,&pi);


调试器附加到一个运行的进程是通过调用DebugActiveProcess来实现的。

DebugActiveProcess

此函数允许将调试器捆绑到一个正在运行的进程上。

BOOL DebugActiveProcess(DWORD dwProcessId )


dwProcessId:欲捆绑进程的进程标识符

如果函数成功,则返回非零值;如果失败,则返回零

无论采用哪一种方法,调试器与操作系统的交互都是相同的。这种调试器被称为活动调试器(livingdebuger)。每个调试器只能有一个调试目标。

调试循环

在初学Windows时我们一定接触过消息循环。调试循环与此类似。

while(当调试不结束时)

{

//等待操作系统发送调试事件。

//处理调试事件。

//通知调试目标执行相应操作。

}

在调试目标被调试时,进程执行的一些操作会以事件的方式通知调试器。例如动态库的加载与卸载、新线程的创建和销毁以及代码或处理器抛出的异常都会通知调试器。

当有事件需要通知调试器时,操作系统会首先挂起调试目标的所有线程,然后把事件通知调试器。并且等待调试器通知其继续执行。

调试器会调用WaitForDebugEvent来等待事件通知的到来。当有事件通知到来时此函数返回,返回的事件信息被封装在DEBUG_EVENT结构中。这个结构包含事件的类型等其他信息。

事件类型有以下几种:

WaitForDebugEvent

此函数用来等待被调试进程发生调试事件。

BOOL WaitForDebugEvent(LPDEBUG_ENENT lpDebugEvent, DWORD dwMilliseconds)


lpDebugEvent:指向接收调试事件信息的DEBUG_ENENT结构的指针

dwMilliseconds:指定用来等待调试事件发生的毫秒数,如果这段时间内没有调试事件发生,函数将返回调用者;如果将该参数指定为INFINITE,函数将一直等待直到调试事件发生

如果函数成功,则返回非零值;如果失败,则返回零

在调试器调用WaitForDebugEvent返回后,得到事件通知,然后解析DEBUG_EVENT结构,并对事件进行响应,处理完成后调试器将会调用ContinueDebugEvent,并根据参数来通知调试目标执行相应操作。

ContinueDebugEvent函数

此函数允许调试器恢复先前由于调试事件而挂起的线程。

BOOL ContinueDebugEvent(DWORD dwProcessId,DWORD dwThreadId, DWORD dwContinueStatus )


dwProcessId为被调试进程的进程标识符

dwThreadId为欲恢复线程的线程标识符

dwContinueStatus指定了该线程将以何种方式继续,包含两个定义值DBG_CONTINUEDBG_EXCEPTION_NOT_HANDLED

如果函数成功,则返回非零值;如果失败,则返回零。

具体实现为:

DWORD Condition=DBG_CONTINUE;

while(Condition)

{

    DEBUG_EVENT DebugEvent={0};

WaitForDebugEvent(&DebugEvent,INFINITE);//等待调试事件

ProcessEvenet(DebugEvent)//处理调试事件。

ContinueDebugEvent(DebugEvent.dwProcessId,DebugEvent.dwThreadId,Condition);//通知调试目标继续执行。

}


ProcessEvent用于对调试事件进行处理。它是用户自定义函数。在该函数内会对DEBUG_EVENT结构进行解析。

DEBUG_EVENT结构为:

typedef struct _DEBUG_EVENT {

  DWORD dwDebugEventCode;

  DWORD dwProcessId;

  DWORD dwThreadId;

  union {

    EXCEPTION_DEBUG_INFO      Exception;

    CREATE_THREAD_DEBUG_INFO  CreateThread;

    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;


处理通知代码如下:

DWORD ProcessEvent(DEBUG_EVENT de)

{

   switch(de.dwDebugEvent.Code)

   {

      case EXCEPTION_DEBUG_EVENT:

        {

         }

         break;

     case CREATE_THREAD_DEBUG_EVENT:

        {

         }

         break;

     case CREATE_PROCESS_DEBUG_EVENT:

        {

         }

         break;

     case EXIT_THREAD_DEBUG_EVENT:

        {

         }

         break;

     case EXIT_PROCESS_DEBUG_EVENT:

        {

         }

         break;

      case LOAD_DLL_DEBUG_EVENT:

        {

         }

         break;

      case OUTPUT_DEBUG_STRING_EVENT:

        {

         }

         break;

       ......

}

return DBG_CONTINUE;

}


调试事件介绍

OUTPUT_DEBUG_STRING_EVENT事件

很多程序员在调试程序时喜欢将执行的结果或中间步骤输出,用以检查程序执行的正确与否。在很多系统中这是很不方便的。但我们可以使用调试输出命令,将某些需要显示的结果输出到输出窗口中。如vcTRACE宏。其实在TRACE宏内部是调用OutputDebugString来实现的。调试器会把调试目标输出的字符串通过事件处理代码显示出来。在DEBUG_EVENT结构中有一个DebugString成员。

该结构定义为:

typedef struct _OUTPUT_DEBUG_STRING_INFO {

  LPSTR lpDebugStringData;

  WORD  fUnicode;

  WORD  nDebugStringLength;

} OUTPUT_DEBUG_STRING_INFO, *LPOUTPUT_DEBUG_STRING_INFO;


在此结构中有一个lpDebugStringData成员,它保存被输出字符串的地址。nDebugStringLength为字符串长度。fUnicode表示是ANSI还是UNICODE字符。

下面为处理OUTPUT_DEBUG_STRING_EVENT事件的代码:

case OUTPUT_DEBUG_STRING_EVENT:

 {

   OUTPUT_DEBUG_STRING_INFO oi=de.u.DebugString;

   WCHAR *msg=ReadRemoteString(调试目标句柄,

   oi.lpDebugStringData,oi.nDebugStringLength,oi.fUnicode);

   std::wcout<<msg;

    break;

 }


ReadRemoteString是用户自定义函数。在此函数内部是调用ReadProcessMemory从调试目标进程内读取字符串。具体不再介绍。

ReadProcessMemory

读取指定进程的某区域内的数据。

BOOL ReadProcessMemory(HANDLE hProcess, LPCVOID lpBassAddress, LPVOID lpBuffer,  SIZE_T nSize, SIZE_T * lpNumberOfBytesRead)


hProcess:进程的句柄

lpBassAddress:欲读取区域的基地址

lpBuffer:保存读取数据的缓冲的指针

nSize:欲读取的字节数

lpNumberOfBytesRead:存储已读取字节数的地址指针

如果函数成功,则返回非零值;如果失败,则返回零

处理EXCEPTION_DEBUG_EVENT事件

当调试目标在调试时发生异常时,操作系统将会向调试器发送EXCEPTION_DEBUG_EVENT事件通知

当发生此事件时,DEBUG_EVENT结构包含的是一个EXCEPTION_DEBUG_INFO结构。

typedef struct _EXCEPTION_DEBUG_INFO {

  EXCEPTION_RECORD ExceptionRecord;

  DWORD            dwFirstChance;

} EXCEPTION_DEBUG_INFO, *LPEXCEPTION_DEBUG_INFO;


ExceptionRecord成员包含了异常信息的一个副本。如异常码,异常引发地址以及异常参数等。定义如下:

typedef struct _EXCEPTION_RECORD { 

        DWORD ExceptionCode; 

        DWORD ExceptionFlags; 

        struct _EXCEPTION_RECORD *ExceptionRecord; 

        PVOID ExceptionAddress; 

        DWORD NumberParameters; 

        DWORD ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS]; 

} EXCEPTION_RECORD;


dwFirstChance告诉调试器是否是第一轮通知这个异常。

从操作系统的角度来看,调试器必须对异常进行解析,并且将DBG_CONTINUE或者是DBG_EXECPTION_NOT_HANDLED作为参数传递给ContinueDebugEvent。如果执行DBG_CONTINUE,则操作系统认为该异常已经被妥善处理了。因此从产生异常的地址开始回复程序的执行。如果传入DBG_EXCEPTION_NOT_HANDLED,则告诉操作系统该异常并未被处理,操作系统将继续分发异常。

case EXCEPTION_DBUG_EVENT:

{

  std::cout<<”异常码为”<<std::hex<<debugEvent.u.Exception.ExceptionRecord.ExceptionCode<<std::endl;

   //在switch判断异常类型,并执行相应操作。

   switch(debugEvent.u.Exception.ExceptionRecord.ExceptionCode)

  {

   case EXCEPTION_BREAKPOINT:

    break;

    case EXCEPTION_SINGLE_STEP:

     beak;

     return DBG_CONTINUE;

 }

   break;

}


在调试循环中,从WaitForDebugEvent中返回以及调用ContinueDebugEvent之间的这段时间内,调试目标不会执行,因此它的状态也将保持不变。当调试目标被挂起时,调试器就进入了交互模式,接收用户的各种指令,并按照不同指令执行不同操作。

调试事件到来的顺序

当我们启动调试目标时,调试器接收到的第一个事件是CREATE_THREAD_DEBUG_EVENT。接下来是加载dll的事件。每加载一个,都会产生一个这样的事件。

当所有模块都被加载到进程地址空间后,调试目标就准备好运行了,调试器此时也做好了接收通知的准备。此时是设置断点的最佳时机。

在调试目标退出之前调试器会收到EXIT_DEBUG_PROCESS_EVENT通知。此后调试器不能收到加载到进程地址空间的dll从进程卸载的UNLOAD_DLL_DEBUG_EVENT通知。

前面介绍的调试事件都是由Windows操作系统发出的,来通知调试器。但是调试目标也会发出自己的异常。调试器在处理这些异常时可以选择与其他调试事件一样的处理方式。

Windows操作系统使用结构化异常处理(SEH)机制将处理器引发的异常传递给内核及用户态程序。每个SEH异常都有一个无符号整形的异常码来唯一标识。这个异常码是由系统在异常发生时指定的。这些异常码使用了操作系统开发人员定义的公开异常码。例如访问违规异常异常码为0xC0000005,断点异常为0xC80000003。为了方便记忆,这些异常码被定义为常量。其名字形如STATUS_XXX。如

#defineSTATUS_BREAKPOINT((NTSTATUS)0x80000003L)

由于异常码很难记忆,因此Windows调试器中包含了一些更容易记住的别名来控制调试器的行为。例如断点异常0x80000003的别名是bpeC++异常码0xE06D7363别名为eh

以上内容参考自《Windows高级调试》如有纰漏,请不吝赐教!
2013.1.29 于山西大同
<!--EndFragment-->
1. What is an IDE (Integrated Development Environment), and what are its main components? 2. What is the role of a compiler in the C++ development process? 3. What is the difference between source code (e.g., a .cpp file) and an executable file? 4. In the "Hello, World!" program, what is the purpose of the line #include <iostream>? 5. What is special about the main() function in a C++ program? 6. Why do computers fundamentally operate using the binary (base-2) system? 7. What is the base of the hexadecimal system? Why is it often used by programmers as a shorthand for binary numbers? 8. Explain the "triad" method for converting an octal number to binary. 9. Briefly describe the "division by 2" method for converting a decimal number to binary. 10. What is the decimal value of the binary number 1011? 1. What is the purpose of the std::cout object? Which header file must be included to use it? 2.What is the difference between an escape sequence like \n and a manipulator like std::endl? (Hint: Both create a new line, but they have a subtle difference). 3.How would you print the following text to the console, including the quotes and the backslash: He said: "The file is in C:\Users\"? 4.Is it possible to write an entire multi-line text output using only one std::cout statement? If yes, how? 5.What is a syntax error? Give an example of a syntax error from Task 2. (Task 2: Debugging The following program contains several syntax errors. Copy the code into your IDE, identify the errors, fix them, and run the program to ensure it works correctly. Incorrect Code: */ Now you should not forget your glasses // #include <stream> int main { cout << "If this text" , cout >> " appears on your display, cout << " endl;" cout << 'you can pat yourself on ' << " the back!" << endl. return 0; "; ) Hint: Pay close attention to comments, header files, brackets ({}), operators (<<), semicolons, and how strings and manipulators are written.) 1. What is the difference between variable declaration and initialization? 2.What will be the result of the expression 7 / 2 in C++? Why? 3.What will be the result of the expression 10 % 3? What is the main purpose of the modulus operator? 4. What is the purpose of std::cin and the >> operator? 5. A beginner tries to swap two integer variables a and b with the code a = b; b = a;. Why will this not work correctly? 1. What is an algorithm? Name the primary ways to represent an algorithm. 2.List the main flowchart symbols and explain their purpose. 3.What are the three fundamental types of algorithm structures? Briefly describe each. 4.In a branching algorithm, what determines the flow of execution? 5.What is the key characteristic of a linear algorithm? 6.When is a cyclic algorithm structure used?7. 8. 9. 7.Explain the purpose of a connector in a flowchart. 8.What is the difference between a predefined process block and a standard process block? 9.In the context of solving a quadratic equation algorithm, what condition must be checked before calculating the roots? Why? 1. What are the three main approaches to data input and output offered by C++? 2. What is the purpose of the SetConsoleOutputCP(65001) and SetConsoleCP(65001)
functions in the provided C++ program example? 3. Explain the difference between the cin and cout objects in Stream 1/0. 4. When using formatted 1/0, which header file must be included to use manipulators like setw and setprecision? 5. List three manipulators used for data output in C++ and briefly describe what each one does. 6. In Formatted I/0 using printf), what are the conversion specifications for a decimal integer and a real number in exponential form? 7. What is the difference in how the & (address-of) operator is used when inputting a value for an integer variable versus a string variable using the scanf() function? 8. Which Character I/O function is used to output a single character to the screen, and which is used to output a string? 9. Describe the syntax and function of the ternary operator in C++. 10. What is the difference between the logical AND (&&) and logical OR (I|) operators when combining multiple conditions? 11. When is the default label executed in a C++ switch statement? 12. What is the primary purpose of the break statement within a switch block? 1. What is the main purpose of using loops in programming? 2. Explain the key difference between the for, while, and do while loops. 3. What happens if you forget to include the increment/decrement statement in a while loop? 4. How can you interrupt an infinite loop during program execution? 5. What is the role of the setw() and setfill) manipulators in C++? 6. In a nested loop, how does the inner loop behave relative to the outer loop? 7. What is type casting, and why is it used in loop calculations? 8. How does the do while loop differ from the while loop in terms of condition checking? 9. What output formatting options can be used to align numerical results in columns? 10*. How would you modify a loop to skip certain iterations based on a condition? 1. List the six main biwise operators in C++ and explain the function of each. 2. Why cannot bitwise operations be applied to variables of floating-point type? 3. Explain the purpose of the << (left shift) and >> (right shift) operators. What is the typical effect on the decimal value of a number when it is shifted left by 1? Shifted right by 1? 4. Describe the process of using a mask to check the value of a specific bit within an
integer. 5. How can you use the bitwise AND operator (&) to check if a number is even or odd?
Explain the logic. 6. What is the difference between the logical AND (&&) and the bitwise AND (&)? Provide an example scenario for each. 7. Explain the purpose of the ~ (bitwise NOT) operator. What is the result of applying it to a mask, and how can this be useful? 1. What is the primary goal of program debugging? What types of errors can it help identify? 2. Describe the difference between Step Over (F10) and Step Into (F11) debugging commands. When would you choose one over the other? 3. What is the purpose of a breakpoint in planned debugging? How do you set and remove a breakpoint in Visual Studio? 4. Explain the utility of the "Watch" window compared to the "Autos" or "Locals" windows during a debugging session. 5. What is the key difference between the Debug and Release configurations when building a project? Why is it necessary to create a Release version after successful debugging? 6. List at least three types of files commonly found in a project's Debug folder and briefly state their purpose (e.g., *.pdb). 7. During debugging, you notice a variable has an incorrect value. How can you change its value during runtime to test a hypothesis without modifying the source code? 8. What command is used to exit the debug mode and stop the current debugging session? 1. What is an array in C++? List its three main characteristics. 2. How are array elements numbered in C++? What is the valid index range for an array declared as int data[25];? 3. Explain the difference between array declaration and initialization. Provide an example of each. 4. What is an initializer list? What happens if the initializer list is shorter than the array size? 5. How can you let the compiler automatically determine the size of an array during initialization? 6. What values do elements of a local array contain if it is declared but not explicitly initialized? How does this differ from a global array? 7. What is an array out-of-bounds error? Why is it dangerous, and what are its potential consequences? 8. How do you calculate the number of elements in an array using the sizeof operator?
Provide the formula. What is a significant limitation of this method? 9. Why is it impossible to copy the contents of one array into another using the assignment
operator (arrayB = arrayA;)? What is the correct way to perform this operation? 10. Why does comparing two arrays using the equality operator (arrayA == arrayB) not check if their elements are equal? How should array comparison be done correctly? 11. What does the name of an array represent in terms of memory? 1. What is a pointer in C++ and what are its two main attributes? 2. Explain the difference between the & and * operators when working with pointers. 3. Why is pointer initialization critical and what dangers do uninitialized pointers pose? 4. What is the fundamental relationship between arrays and pointers in C++? 5. How does pointer arithmetic work and why does ptr + 1 advance by the size of the pointed type rather than 1 byte? 6. What is the difference between an array name and a pointer variable? Why can't you increment an array name? 7. What are the differences between const int*, int* const, and const int* const? 8. How can you safely iterate through an array using pointers, and what are the boundary risks? 9. What is a null pointer and why should you check for nullptr before dereferencing? 10. How do you access array elements using pointer syntax, and how does the compiler translate arr[i] internally? 1. What is a multidimensional array? How is a two-dimensional array structured in memory? 2. Explain the concept of an "array of arrays". How does this relate to the declaration int arr/ROWS//COLS;? 3. The name of a two-dimensional array without indices is a pointer constant. What does this pointer point to? What do the expressions *(A + i) and *(*(A + i) +j) mean for a two-dimensional array A? 4. Describe the different ways to access the element A/1/[2/ of a two-dimensional array
using pointers. 5. What is the rule for omitting the size of dimensions when initializing and when passing a multidimensional array to a function? Why is it allowed to omit only the first dimension? 6. Explain the principle of "row-major order" for storing two-dimensional arrays in memory.
How does this affect element access? 7. Why are nested loops the standard tool for processing multidimensional arrays?
Describe the typical pattern for iterating through a matrix. 1. How is a character string stored in memory in C++? What is the role of the null terminator (10), and why is it critical for C-style strings? 2. Why must the size of a char array declared to hold a string be at least one greater than the number of characters you intend to store? 3. The array name without an index is a pointer constant. What does the name of a char array point to? 4. What are the two main ways to initialize a C-style string? What is a common mistake when using the initializer list method, and what is its consequence? 5. Why is it necessary to add _CRT_SECURE_NO_WARNINGS to the preprocessor definitions in Visual Studio when working with many standard C library functions?
What is the alternative approach? 6. What is the key difference between stropy and strncpy? Why might strncpy be considered safer? 7. How does the stremp function determine if one string is "less than" another? Why can't you use the == operator to compare two C-style strings for content equality? 8. Describe the purpose and parameters of the strok function. How do you get all tokens from a string? 9. What do the functions strchr and strrchr do? How do they differ? 10. Explain what the strstr function returns and what it is commonly used for. 11. What is the purpose of the functions in the < cctype> header? Give three examples of such functions and their use. 12. What is the difference between tolower(c) and_tolower(c)? When should you use each? 1. What is a function in C++? Name the three core benefits of using functions in a program. 2. What is the difference between a function declaration (prototype) and a function definition? Provide examples. 3. What is a function signature? Which elements are part of the signature, and which are not? 4. What methods of passing parameters to a function do you know? Explain the difference between pass-by-value, pass-by-pointer, and pass-by-reference. 5. Why can't you pass an array to a function by value? What is the correct way to pass an array to a function? 6. What is variable scope? How is it related to functions? 7. How does a function return a value? What happens if a function with a non-void return type does not return a value on all control paths? 8. Can you use multiple return statements in a single function? Provide an example. 9. What is function overloading? What is it based on? 10. How is interaction between functions organized in a program? Provide an example program with several functions. 11. What are default parameters? How are they specified, and in what cases are they useful? 12. How can you prevent a function from modifying the data passed to it? What modifiers are used for this? 13. What is recursion? Provide an example of a recursive function. 14. What common errors occur when working with functions? How can they be avoided? 15. How do you use pointers to functions? Provide an example of declaring and calling a function through a pointer. 用中文回答
最新发布
11-18
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值