Back to Home

Debugging memory access errors with Application Verifier

c ++ · debugging · Windows · WinDBG · Application Verifier · AppVerifier · access violation · heap corruption

Debugging memory access errors with Application Verifier

    Habrayuzer burdakovd asked at Q & A puzzle about C ++, vector and entry into another memory. The task, among other things, is good in that you can conveniently demonstrate how to use the Application Verifier tool and find out who spoils the memory.

    Application Verifier is a very powerful tool, in addition to diagnosing how to use heap, it can do a lot of other things, for example, detect improper handling with handles, multithreading implementation errors, emulate a lack of resources to check the correct operation of the program in such conditions, but more about that in another time.

    Instruments


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

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

    Putting an example


    We take the source in the problem mentioned above ( copy on pastie ). We collect it with the debugging information (keys / Zi or / ZI for the compiler and / DEBUG for the linker) and disabled optimization. The command line to build from the console will look something like this:
    cl /D_DEBUG /Zi /Od /EHsc /DEBUG /MDd vector_misuse.cpp

    Configure Application Verifier

    1. We start 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 the checkmark on the node Basic-> Heaps. Just in case, let's go into the properties of this node (right-click on it-> Properties) and make sure that the checkboxes opposite Full (at the very top) and opposite Traces (approximately in the middle of the dialog) are turned on. If it is not turned on, turn it on and click OK.
    5. Click the Save button.

    Configure debugger

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

    Find the reason for 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 at the invitation) and see what fell in the function f, at the second level of nesting. In order for the function arguments to be visible in the Call Stack window, click the Source args button. To see links to lines of code, click the Source button. The kp command will display this information in the Command window of the debugger. A window with the source text should also open and the current line will be highlighted in it.

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

    The debugger will dump us a sheet of text from which we are interested in the following things:
    DEFAULT_BUCKET_ID: INVALID_POINTER_READ - an attempt to read using the invalid pointer
    READ_ADDRESS: 060a0ff4 - the actual address we were trying to read.

    The call stack that we already saw and even a piece of the source with the marked line where the exception occurred will also be printed.

    This is all of course very interesting, but I would like to know why this memory cannot be read? Thanks to the settings that we made in AppVerifier, the system collected call stacks and carefully saved it every time we allocated and freed memory, so that we could kindly provide it at our request.

    We enter in the debugger invitation !heap -p -a 060a0ff4(here you will need to substitute the address that you will have in READ_ADDRESS, it will most likely be different. To this the debugger will reply to us that this address belongs to such a heap, of such and such a size, which was released (in free-ed allocation) like this 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:: deallocate + 0x00000010
        00f7193b vector_misuse! Std :: vector > :: reserve + 0x0000010b
        00f716db vector_misuse! Std :: vector > :: _ Reserve + 0x0000005b
        00f714c4 vector_misuse! Std :: vector > :: 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, with the next vector :: push_back, the vector decided to change its size (vector :: reserve), which led to the reallocation of this vector itself (std :: allocator :: deallocate and further down the stack) and subsequent access to freed memory when returning to the second level.

    Total


    I have always had problems writing beautiful conclusions and summaries; therefore, there will be no problems with them. Smart people, they will draw the necessary conclusions

    for themselves :) Thank you for your attention. :)

    Read Next