Talking about Memory Leak (3)

zhaozj2021-02-16  50

Talking about Memory Leak (3)

Use BoundSchecker to detect memory leaks:

Boundschecker uses a technology called Code Injection to intercept the call to allocate memory and release memory. Simply put, when your program starts running, BoundSchecker's DLL is automatically loaded into the address space (this can be implemented via System-Level), then it will modify the function call to memory allocation and release in the process. Let these calls first transfer to its code, then perform the original code. When BoundSchecker is doing these actions, there is no need to modify the source code or engineering profile of the debugged program, which makes it easy and straightforward.

Here we take the malloc function as an example, intercepting other function methods is similar.

The function that needs to be intercepted may be in the DLL, or in the code of the program. For example, if the C-Runtime Library is static, then the code of the Malloc function will be connected to the program. In order to seize the call to such a function, BoundSchecker dynamically modifies the instructions of these functions.

The following two paragraphs of assembly code, a paragraph without Boundschecker, another paragraph has the intervention of BoundSchecker:

126: _CRTIMP VOID * __CDECL MALLOC (

127: SIZE_T NSIZE

128:)

129: {

00403C10 PUSH EBP

00403C11 MOV EBP, ESP

130: Return _nh_malloc_dbg (nsize, _newmode, _normal_block, null, 0);

00403C13 PUSH 0

00403C15 PUSH 0

00403C17 Push 1

00403C19 MOV EAX, [__ newmode (0042376C)]

00403C1E PUSH EAX

00403C1F MOV ECX, DWORD PTR [nsize]

00403C22 PUSH ECX

00403C23 CALL _NH_MALLOC_DBG (00403C80)

00403C28 Add ESP, 14H

131:}

The following code has BoundSchecker intervention:

126: _CRTIMP VOID * __CDECL MALLOC (

127: SIZE_T NSIZE

128:)

129: {

00403C10 JMP 01F41EC8

00403C15 PUSH 0

00403C17 Push 1

00403C19 MOV EAX, [__ newmode (0042376C)]

00403C1E PUSH EAX

00403C1F MOV ECX, DWORD PTR [nsize]

00403C22 PUSH ECX

00403C23 CALL _NH_MALLOC_DBG (00403C80)

00403C28 Add ESP, 14H

131:}

When BoundSchecker is involved, the top three assembly instructions of the function Malloc are replaced with a JMP instruction, and the original three instructions are moved to address 01f41ec. When the program enters Malloc, first JMP to 01F41EC8, execute the original three instructions, and then the BoundSchecker's world. The return address of the function is roughly recorded (the return address of the function is on the stack, so it is easy to modify), then point the return address to the code belonging to the Boundschecker, then jumps to the original directive of the malloc function, that is, at 00403C15 . When the Malloc function is over, since the return address is modified, it will return to the BoundSchecker code, and BoundSchecker records the pointer to the memory allocated by MalloC, and then jumps to the original return address. If the Memory Allocation / Release Function is in the DLL, BoundSchecker uses another method to intercept the call to these functions. Boundschecker lets the function address in Table points to your address by modifying the function address in Table to the purpose of the interception. Regarding how to intercept Windows system function, "Programmer" Magazine 2002, "API Hook revealed (below)," a summary of the modified import address table. I will not go to it again.

Intercept these allocation and release functions, BoundSchecker can record the life cycle of the assigned memory or resource. The next question is how the source code is related, that is, when BoundSchecker detects memory leaks, how do it report which code assignment of this memory block. The answer is debug information. When we compile a Debug version, the compiler records the correspondence between the source code and the binary code, put it into a separate file (.pdb) or directly into the target program. With this information, the debugger can complete the functions such as breakpoint settings, single-step execution, and variables. Boundschecker supports multiple debug information formats, which file can be assigned a source code to allocate a block of memory by directly read the debugging information. Which line is on. Using Code Injection and Debug Information so that BoundSchecker can not only record the source code of the call assignment function, but also record the source code location of the Call Stack when allocated, and the source code location of the function on the Call Stack. This is very useful when using class libraries like MFC, here I use an example:

Void showXItemMenu ()

{

...

CMenu Menu;

Menu.createPopupnupmenu ();

// add menu items.

Menu.trackpropupnupmenu ();

...

}

Void showyItemMenu ()

{

...

CMenu Menu;

Menu.createPopupnupmenu ();

// add menu items.

Menu.trackpropupnupmenu ();

Menu.Detach (); // this will cause Hmenu Leak

...

}

Bool CMenu :: CreatePopupmentu ()

{

...

HMENU = CREATEPOPUPMENU ();

...

}

When calling ShowyItemMenu (), we deliberately cause the HMENU's leakage. However, for BoundSchecker, the leak HMENU is assigned in Class CMenu :: CreatePopUpMenu (). Suppose your program has many places to use cMenu's createPopUpMenu () function, if you just tell you that the leak is caused by cmenu :: createpopupmenu (), you still can't confirm that the root knot is where, in ShowXItemMenu () Still in ShowyItemMenu (), or other places also use createPopupMenu ()? With the information of Call Stack, the problem is easy. Boundschecker will report the leakage of HMENU information: Function File Line Cmenu :: CreatePopupMenu E: /8168/VC98/MFC /8168/VC98/mfc/mfc/include/AFXWIN1.INL 1009 ShowyItemMenu E: /TESTMEMLEAK/Mytest.cpp 100 Here you omitted other functions transfer

In this way, we can easily find a function of a problem that ShowyItemMenu (). When programming class libraries such as MFC, most of the API calls are encapsulated in the class library, with the Call Stack information, we can very easy tracking to truly leak.

Recording the Call Stack information makes the running of the program very slow, so BoundSchecker does not record Call Stack information by default. You can open the option switch of the recorded Call Stack information as follows:

1. Open menu: Boundschecker | Setting ...

2. In the ERROR Detection page, select Custom in the List of Error Detection Scheme

3. Select Pointer and Leak Error Check in Category's Combox

4. Hook on the Report Call Stack checkbox

5. Click OK

Based on Code Injection, BoundSchecker also provides the verification function of API parameter, Memory Over Run. These features are very beneficial for the development of procedures. Since these contents do not belong to this theme of this article, it is not detailed here.

Although BoundSchecker is so powerful, it is still pale with implicit memory leaks. So let's take a look at how to detect memory leaks with Performance Monitor.

Use Performance Monitor to detect memory leaks

NT core has added system monitoring functions during the design, such as CPU usage, memory usage, usage of I / O operations, as a COUNTER, the application can learn about the entire system by reading these Counter Or a certain process health. Performance Monitor is such an application.

In order to detect memory leaks, we can generally monitor the Handle Count, Virutal Bytes, and Working Set of the Process object. The Handle Count records the number of Handle, which is currently open. Monitor this Counter helps us find whether the program has a Handle leak; Virtual Bytes records the size of the virtual memory used in the virtual address space, NT memory allocation The method of two steps, first, retain a space on the virtual address space, when the operating system does not assign physical memory, just retains a piece of address. Then, then submit this space, then the operating system is allocated physical memory. So, Virtual Bytes is generally greater than the program's Working SET. Monitoring Virutal Bytes can help us find some problems under the system; Working Set records the total amount of memory that the operating system has submitted, this value and the total amount of memory applications have a close relationship. If the program has a memory leak, this The value will continue to increase, but Virtual Bytes is an increase in jumping. Monitoring these Counters allow us to understand the situation of the process using memory, if a leak occurs, even implicit memory leaks, these Counters will continue to increase. However, we know that there is a problem, but we don't know where there is a problem, so it usually uses Performance Monitor to verify that there is a memory leak, and use BoundSchecker to find and solve the problem.

When Performance Monitor displays a memory leak, BoundSchecker cannot be detected, there are two possibilities: the first, an interpressive memory leak has occurred. At this time, you have to make sure that the running environment and operation method of the program are consistent with Performance Monitor and using BoundSchecker. The second, an implicit memory leak has occurred. At this time, you have to re-examine the design, then carefully study the changes of the value of the counter recorded in Performance Monitor, analyze the relationship between the changes and procedures running logic, and find some possible reasons. This is a painful process, full of hypothesis, guess, verification, failure, but this is also a great opportunity to accumulate experience.

to sum up

Memory leaks are a big and complicated issue, even with the Gabarge Collection mechanism, there is a possibility of leakage, such as implicit memory leaks. Due to the limitations of space and ability, this article can only be a shallow research on this topic. Other problems, such as leak detection under multi-module, how to analyze memory usage at the time of operation, etc., is the topic of research. If you have any ideas, suggestions or find some errors, welcome to communicate with me.

(All rights reserved, please indicate when reprint)

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

New Post(0)