[Win32] Implementation of a debugger (vii) breakpoint

Source: Internet
Author: User

[Win32] Implementation of a debugger (vii) breakpoint

Zplutor
Source: http://www.cnblogs.com/zplutor/
This article copyright belongs to the author and the blog Garden altogether, welcome reprint. However, without the author's consent, this statement must be retained, and in the article page obvious location to the original link, otherwise reserves the right to pursue legal responsibility.

Breakpoints are one of the most basic and important debugging techniques, and this article explains how to implement breakpoints in the debugger.

What is a breakpoint

When debugging, only the debugger can perform operations on it when the debug process pauses execution, such as observing memory content. There is nothing the debugger can do if the debugging process stops. To stop the debugged process, except for a few debug events that occur at a specific time, the only way is to throw an exception.

The breakpoint is the exception used to achieve the above purpose, in the third article of the Exception Code table, there is a Exception_breakpoint exception, it is a breakpoint exception. Although a breakpoint is an exception, it does not mean that the debugging process has been a problem, it is only a means of debugging, so the debugger should distinguish it from other exceptions clearly. There are actually some subtle differences in how Windows handles breakpoint exceptions, as described later in this article.

Breakpoints have software breakpoints and hardware breakpoint points. Hardware breakpoints are set by the registers of the CPU, which is powerful enough to set breakpoints in your code or to set breakpoints in your data, but you can set a limited number of them. A software breakpoint is a breakpoint thrown through an int 3 instruction, the machine code is 0xCC, it can only be set in code, but there is no limit to the number. This article focuses only on software breakpoints.

If you use the Minidebugger in the previous articles to debug the program, you will notice that when the debugger starts to run, there is always a breakpoint exception that occurs at the high address (by the exception code is 0x80000003), this breakpoint is the initial breakpoint. If Windows detects that a program is being debugged, a breakpoint exception is thrown after the program initializes, telling the debugger that everything is ready. The debugger can prepare to receive this breakpoint, such as loading debug symbols. The initial breakpoint is unavoidable, as long as the debugger will throw this breakpoint under Windows.

Distribution of breakpoint Exceptions

Breakpoints are actually exceptions, so it also goes through the process of abnormal distribution as described in the third article. So, does it belong to an error exception or a trap exception? Try to prove it through experimentation. Here, using the Minidebugger of the previous article as the debugger, the program generated by the following code is used as the debugger:

1 int wmain (int argc, wchar_t** argv) {
2 __asm {int 3};
3 return 0;
4}

Start the debugged program first, skip the initial breakpoint, make it execute __asm {int 3}; statement, throw the breakpoint exception:

Execute the L and R commands to view the source code and registers:

As you can see, after executing the int 3 instruction, the EIP points to the next instruction, and if it resumes execution with the G-C command, the return statement is executed and the debug process ends. The conclusion is that the breakpoint anomaly belongs to the trap exception.

As I said earlier, Windows has a subtle difference in handling breakpoint exceptions, so let's look at what's different. Executes the G command, does not handle the exception, and executes the L and R commands the second time an exception is received:

The EIP then rolls back a byte, pointing to the instruction that throws the breakpoint exception. The difference is that Windows does not modify the value of the EIP when distributing other exceptions.

In addition, the debugger receives only one initial breakpoint, and no longer receives the initial breakpoint, either Dbg_continue or Dbg_exception_not_handled call continuedebugevent.

Trap Flags

In addition to breakpoints, the CPU itself provides a single-step function, which can also cause the program to break at some point. In the CPU's flag register, there is a TF (Trap flag) bit, and when the bit is 1 o'clock, the CPU throws an interrupt each time an instruction is executed, and Windows notifies the debugger with a single step exception code of Exception_single_step. Each time an interrupt is raised, the CPU automatically sets the TF bit to 0, so if you want to step through multiple instructions consecutively, you need to reset the TF bit each time you process a single step exception.

One-step exception is an error exception, and the address that throws the exception is the same as the address that the EIP points to.

Breakpoint function principle

The above example inserts an int 3 instruction into the debugger, which is required for experimentation, but it is not possible to have such an instruction in a normal program. In order to be able to set a breakpoint at any instruction, the debugger replaces the first byte of the instruction with the 0xCC (machine code of int 3), receives the breakpoint exception, and then replaces the original byte, starting with the instruction to continue execution. This makes it possible to interrupt at any instruction and has no effect on the original program.

For example, the following assignment statement corresponds to a assembly directive:

int b = 2;
C7 F8-XX, mov dword ptr [b],2

This instruction has 7 bytes. If the debugger wants to set a breakpoint on this statement, it first saves the first byte of the instruction 0xc7 and then replaces it with 0xCC:

CC F8 02 00 00 00

At this point the original MOV instruction becomes an int 3 instruction and 6 bytes of garbage data. The debugger does not know about this change, it executes each instruction, and throws a breakpoint exception after the int 3 instruction, pausing execution. At this point the debugger can no longer execute, because the next 6 bytes is garbage data, try to execute will certainly fail.

The debugger can choose to process the first or second time that a breakpoint exception is received. If processed on the first receive, it will be actively reduced by the EIP of the debug process. If you are processing on a second receive, you do not need to modify the EIP of the debugged process, because Windows has reduced the EIP by 1, as noted above, when the second time the breakpoint is received. Whenever an exception is handled, the debugger replaces the 0xCC back with the original 0xc7, and then continues to be executed by the debug process as dbg_continue.

I recommend that you handle the first time you receive a breakpoint exception, because if you do not process the first time, Windows executes additional code, which can cause some trouble with stepping.

One last question to note is that if a breakpoint is set inside a loop, or is set in a function that is called multiple times, the breakpoint is only interrupted once because it is canceled after the first break. In order for it to continue to work, we need a mechanism to reset the breakpoint after the execution of the instruction where the breakpoint is executing. This can help with the TF bit: When dealing with breakpoint exceptions, set the TF bit immediately after the breakpoint is canceled, and then proceed with the execution, and reset the breakpoint when snapping to a single step exception.

The Complete breakpoint function flowchart is as follows:

Implementing Breakpoint Functionality

Understanding the principle of the breakpoint function, the following step to realize this function. This describes only the approximate idea, how to implement can refer to the sample code.

The first is to determine the address of the breakpoint, which can be obtained by minidebugger the L command to get the address of each line. Note that breakpoints can only be set at the first byte of the instruction, otherwise the structure of the instruction will be broken, causing the debug process to fail to execute.

After the address is determined, the first byte of the instruction is replaced. Read this byte can use the ReadProcessMemory function, write bytes can use the WriteProcessMemory function. The former has been introduced in the fourth article, and the latter is very similar to the method of use, which is no longer detailed here. The restore instruction also uses the WriteProcessMemory function.

The debugger must save a list of breakpoints, preferably with a struct to represent breakpoints, for example:

1 typedef struct {
2 DWORD address; Breakpoint Address
3 BYTE content; First byte of the original instruction
4} break_point;

Next is the way to handle breakpoint exceptions. Breakpoints should be divided into three types: the initial breakpoint, the breakpoint in the process being debugged, and the breakpoint set by the debugger. For the initial breakpoint, no processing is required because it is managed by Windows. If the above processing is applied to the initial breakpoint, the debugging process will not run. Breakpoints in the debugged process are breakpoints that are explicitly added to the code, such as the __asm{INT3} statement in the example above. For such breakpoints, it is only reported to the user when the breakpoint is first received, and no additional processing is required. The breakpoints set by the debugger are handled in the way described above.

If you choose to handle the first time you receive a breakpoint exception, you need to use the SetThreadContext function to set the EIP for the debugged process, which has the same parameters as the GetThreadContext. To avoid modifying the EIP and affecting other registers, you should call GetThreadContext to populate the context structure before calling SetThreadContext. For example:

1 context context;
2 context. Contextflags = Context_control;
3 GetThreadContext (G_hthread, &context);
4 context. Eip-= 1;
5 SetThreadContext (G_hthread, &context);

The way to set the TF bit is consistent with the way the EIP is set, by calling GetThreadContext first, then modifying the value of the EFlags field, and then calling SetThreadContext. The TF bit is the 8th bit in the EFlags register (calculated from 0) and the following statement allows you to set the TF bit:

1 context. EFlags |= 0x100;

When handling a single step exception, it is not easy to assume that EIP minus 1 is the address of the original breakpoint, because the length of the instruction at which the breakpoint is located is indeterminate. In order to reset the breakpoint, you need to save the breakpoint's address, or simply reset all breakpoints once. The specific use of what method is different from person to person.

Finally, when you set a breakpoint, use the D command to observe the memory at the breakpoint, and see the 0xCC after the replacement. You should usually hide this fact from the user, so you should display the original content of the breakpoint at the time the D command is processed.

in the Main function Set Breakpoint

If the initial breakpoint is ignored following the above processing method, a new problem is created: The debugged process does not pause at this time when the initial breakpoint occurs, but runs until the end, and we never have a chance to do anything about it. The way to solve this problem is to set a breakpoint at the entrance of the main function. The main function described here is a general term that refers to the following four entry functions:

Main

WMAin

WinMain

wWinMain

The entry function for a C + + application must be one of the top four.

In order to set a breakpoint at the main function, the first thing to know is its address, which requires the help of debugging symbols. A function is a symbol that can be used to obtain information about a symbol from a symbolic name by using the Symfromname function. The declaration of this function is as follows:

1 BOOL WINAPI Symfromname (
2 HANDLE hprocess,
3 Pctstr Name,
4 Psymbol_info Symbol
5);

The first parameter is the identifier of the symbol processor, the second argument is the name of the symbol, and the third parameter is a pointer to the Symbol_info struct, and the information for the symbol is stored in the struct when the function call succeeds. The structure is defined as follows:

1 typedef struct _SYMBOL_INFO {
2 ULONG sizeofstruct;
3 ULONG Typeindex;
4 ULONG64 reserved[2];
5 ULONG Index;
6 ULONG Size;
7 ULONG64 Modbase;
8 ULONG Flags;
9 ULONG64 Value;
Ten ULONG64 Address;
ULONG Register;
ULONG Scope;
ULONG Tag;
ULONG Namelen;
ULONG Maxnamelen;
TCHAR Name[1];
+} symbol_info, *psymbol_info;

There are many fields in this struct, but for the moment we are only focusing on address, which is the start of the symbol. The structure of Symbol_info is also mentioned in the following article.

The function that gets the address of the main function is probably as follows:

1 DWORD getentrypointaddress () {
2
3 static LPCTSTR entrypointnames[] = {
4 TEXT ("main"),
5 TEXT ("WMAin"),
6 TEXT ("WinMain"),
7 TEXT ("wWinMain"),
8};
9
Ten Symbol_info Symbolinfo = {0};
One symbolinfo.sizeofstruct = sizeof (Symbol_info);
12
A for (int index = 0; Index! = sizeof (entrypointnames)/sizeof (LPCTSTR); ++index) {
14
if (Symfromname (g_hprocess, Entrypointnames[index], &symbolinfo) = = TRUE) {
16
Symbolinfo.address return (DWORD);
18}
19}
20
return 0;
22}

Sample code

This time I added the b command to Minidebugger, which has the function of setting breakpoints and the command format as follows:

b [Address [d]]

Address is the location of the breakpoint, in hexadecimal notation. If the D parameter is taken, the breakpoint is deleted or the breakpoint is set. If you do not take any parameters, all breakpoints that have been set are displayed.

This version of Minidebugger demonstrates how to handle the second time a breakpoint exception is received, which, as mentioned above, can cause problems with stepping, so please note that once you have added the step function, you will change back to the first time you received it. In addition, this version of Minidebugger does not have additional processing of the D command to hide the 0xCC machine code of the breakpoint.

Http://files.cnblogs.com/zplutor/MiniDebugger7.rar

[Win32] Implementation of a debugger (vii) breakpoint

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.