⬆️ ⬇️

Debugging memory access errors with Application Verifier

Habrayuzer burdakovd set in Q & A a problem about C ++, vector and writing to another's memory. The task, among other things, is good because you can conveniently demonstrate how to use the Application Verifier tool and find out who corrupts the memory.



Application Verifier is a very powerful tool, in addition to diagnosing how to work with hip, it can do a lot of everything else, for example, detect incorrect work with handles, implementation errors of multithreading, emulate a lack of resources to check the correct operation of the program in such conditions, but more on that time.



Instruments



In addition to Application Verifier, we will need WinDBG, a free debugger included in the Microsoft Debugging Tools for Windows . Debugging Tools previously could be downloaded separately, but for some reason now only as part of the Windows SDK or the Windows Driver Kit. But you can still download separately Previous Version , which is perfect for our tasks. Well, or here I posted fresh versions (6.12.2.633) in order not to download the entire SDK: dbg_x86.msi , dbg_amd64.msi .

')

You also need Visual C ++ (any version, newer, perhaps, VS2003, you can Express ) or a C ++ compiler from the Windows SDK. We need a compiler from Microsoft, not MinGW, because we need debugging information in PDB format, which WinDBG understands.



We collect an example



The source is taken in the above-mentioned problem (a copy of the pastie ). We compile with debug information (keys / Zi or / ZI for the compiler and / DEBUG for the linker) and disabled optimization. The command line for building from the console will look something like this:

cl /D_DEBUG /Zi /Od /EHsc /DEBUG /MDd vector_misuse.cpp



Customize Application Verifier

  1. Run AppVerifier with administrator privileges.
  2. Choose File-> Add Application (or Ctrl + A), find our misused_vector.exe, click Open.
  3. Remove all the checkmarks from the Basic node.
  4. Set a check mark on the node Basic-> Heaps. Just in case, we’ll go to the properties of this node (right click on it-> Properties) and make sure that the checkboxes opposite Full (at the top) and opposite Traces (approximately in the middle of the dialog) are turned on. If not enabled, turn on and click OK.
  5. Click the Save button.

Configuring the debugger

  1. Go to File-> Symbol File Path ... and enter the line srv*c:\mysymbols*http://msdl.microsoft.com/download/symbols . This means that the debugger will first look for characters in the c: \ mysymbols directory, and if it does not find it, it will download it from the Internet from the Microsoft Symbol Store. Public symbols are needed to see beautiful callstacks. You can use the command .symfix+ c:\mysymbols , but after the application is loaded into the debugger.
  2. In File-> Open Executable ... (Ctrl + E) select our misused_vector.exe. We agree with the proposal to save the workspace. The debugger will load the image into memory, but will not start execution.
  3. Run the example for execution - Debug-> Go (or F5, or g at the debugger prompt).
If you have not previously worked with WinDBG, it makes sense to look at the menu View-> Font and adjust the font. The one that is installed by default may seem completely insane (or it may not seem) to you.



Find the cause of the fall



After we run the program, it will crash with Access Violation.



We look at the stack - View Call Stack (or Alt + 6 or kp in the invitation) and see what fell in the f function, at the second nesting level. To make the function arguments visible in the Call Stack window, click the Source args button. So that you can see the links to the code lines, click the Source button. The kp command will display this information in the Command window of the debugger. Also, a window with the source text should open and highlight the current line in it.



Ok, we see that the problem is in the string.
 v[i] += f(x / 2); 
but what exactly is wrong with her? The debugger will answer this question if asked correctly. We write to the invitation !analyze -v and press Enter.



The debugger will throw us a sheet of text from which we are interested in the following things:

DEFAULT_BUCKET_ID: INVALID_POINTER_READ - an attempt to read by an invalid pointer

READ_ADDRESS: 060a0ff4 - the actual address itself, which we tried to read.



It will also print a kollstek, which we have already seen, and even a piece of the source code with the marked line, where the exception occurred.



All this is of course very interesting, but I would like to know why this memory cannot be read? Thanks to the settings we made in AppVerifier, the system collected call-stacks and carefully saved each time memory was allocated and then it was kindly provided at our request.



Enter the debugger in the invitation !heap -p -a 060a0ff4 (here you will need to substitute the address that you have in READ_ADDRESS, it will most likely be different. At this debugger, we will answer that this address belongs to such and such, of a size that was released (in free-ed allocation) with such a call stack:
     5da190b2 verifier! AVrfDebugPageHeapFree + 0x000000c2
     77cd1464 ntdll! RtlDebugFreeHeap + 0x0000002f
     77c8ab3a ntdll! RtlpFreeHeap + 0x0000005d
     77c33472 ntdll! RtlFreeHeap + 0x00000142
     75cc14dd kernel32! HeapFree + 0x00000014
     5c677f59 MSVCR100D! _Free_base + 0x00000029
     5c687a4e MSVCR100D! _Free_dbg_nolock + 0x000004ae
     5c687560 MSVCR100D! _Free_dbg + 0x00000050
     5c686629 MSVCR100D! Operator delete + 0x000000b9
     00f71af0 vector_misuse! Std :: allocator <int> :: deallocate + 0x00000010
     00f7193b vector_misuse! Std :: vector <int, std :: allocator <int>> :: reserve + 0x0000010b
     00f716db vector_misuse! Std :: vector <int, std :: allocator <int>> :: _ Reserve + 0x0000005b
     00f714c4 vector_misuse! Std :: vector <int, std :: allocator <int>> :: push_back + 0x000000c4
     00f712dc vector_misuse! F + 0x0000002c
     00f7130b vector_misuse! F + 0x0000005b
     00f7130b vector_misuse! F + 0x0000005b
     00f7134b vector_misuse! Main + 0x0000000b
     00f7323f vector_misuse! __ tmainCRTStartup + 0x000001bf
     00f7306f vector_misuse! MainCRTStartup + 0x0000000f
     75cc33ca kernel32! BaseThreadInitThunk + 0x0000000e
     77c39ed2 ntdll! __ RtlUserThreadStart + 0x00000070
     77c39ea5 ntdll! _RtlUserThreadStart + 0x0000001b 


Thus, we learned that at the third level of recursion nesting, at the next vector :: push_back, the vector decided to change its size (vector :: reserve), which led to the relocation of this same vector (std :: allocator :: deallocate and further down the stack) and subsequent access to the released memory when returning to the second level.



Total



I have always had problems with writing beautiful conclusions and summaries, so there won't be any. People are smart, they will make the necessary conclusions themselves :)



Thanks for attention. :)

Source: https://habr.com/ru/post/117810/



All Articles