Click here to Skip to main content
Click here to Skip to main content
Go to top

Own Crash Minidump with Call Stack

, 18 Nov 2004
Rate this:
Please Sign up or sign in to vote.
How to create and analyse your own minidump.

screenshot

Introduction

We explain how to create your own minidump with Call Stack on crash or at any given moment. The demo program creates a dump (shown on the picture) and tries to create the system one with the MiniDumpWriteDump() function of DbgHelp.dll. System dump is not supported in systems other than Win XP without individual installation. The article shows how to interpret the own minidump, and what modifications to your project settings are needed. Our competitor's minidump produced by MiniDumpWriteDump() is also discussed in brief. We refer to the demo project throughout the article, so it could be wise to build it.

The Call Stack

Call Stack is the central part of our dump. Call Stack is a list of the following frames allocated in stack:

typedef struct STACK_FRAME
{
    STACK_FRAME *   Ebp;   //address of the calling function frame
    PBYTE   Ret_Addr;      //return address
    DWORD   Param[0];      //parameter list - could be empty
} STACK_FRAME, * PSTACK_FRAME;

Each function call adds such a frame to the list, each return from function deletes the frame. So, it is a LIFO list. The address of the current frame is in the Ebp register. Ebp register of the calling function is being saved to the frame by the called function. Parameters and return address are stored to the frame by the call statement. So, if we know the current Ebp value, we can unwind the whole list. On exception, this value is in the EXCEPTION_POINTERS structure returned by the GetExceptionInformation() call. Otherwise, we can get the current Ebp subtracting the frame size (eight bytes) from the address of the first parameter of the current function. Get_Call_Stack() fills its Str parameter with Call Stack info. This is a simplified version of the Get_Call_Stack() of the demo project.

int WINAPI Get_Call_Stack(PEXCEPTION_POINTERS pException, PCHAR Str)
{
    CHAR    Module_Name[MAX_PATH];
    PBYTE   Module_Addr;
    int     Str_Len;
    PSTACK_FRAME  Ebp;

    if (pException)
        Ebp = (PSTACK_FRAME)pException->ContextRecord->Ebp;
    else
       // Frame address of Get_Call_Stack()
       Ebp = (PSTACK_FRAME)&pException - 1;

    Str[0] = 0;
    Str_Len = 0;

    for (int Ret_Addr_I = 0;
        (Ret_Addr_I < 20) && !IsBadReadPtr(Ebp, sizeof(PSTACK_FRAME)) &&
            !IsBadCodePtr(FARPROC(Ebp->Ret_Addr));
        Ret_Addr_I++, Ebp = Ebp->Ebp)
    {
        // Find the module by a return address inside that module
        if (Get_Module_By_Ret_Addr(Ebp->Ret_Addr, Module_Name, Module_Addr))
        {
            // Save module's address and path 
            Str_Len += wsprintf(Str + Str_Len, 
                NL "%08X  %s", 
                Module_Addr, Module_Name);
            // Save offset of return address
            Str_Len += wsprintf(Str + Str_Len, 
                NL "  +%08X", 
                Ebp->Ret_Addr - Module_Addr);

            // Save 5 parameters. We don't know the real number of parameters!
            if (!IsBadReadPtr(Ebp, sizeof(PSTACK_FRAME) + 5 * sizeof(DWORD)))
            {
                Str_Len += wsprintf(Str + Str_Len, "  (%X, %X, %X, %X, %X)",
                    Ebp->Param[0], Ebp->Param[1], 
                    Ebp->Param[2], Ebp->Param[3], Ebp->Param[4]);
            }
        }
    }

    return Str_Len;
} //Get_Call_Stack

The Get_Module_By_Ret_Addr() function finds the module by a return address inside that module, and returns its path and address. It uses the Tool Help library to list all the modules of the current process.

BOOL WINAPI Get_Module_By_Ret_Addr(PBYTE Ret_Addr, 
                PCHAR Module_Name, PBYTE & Module_Addr)
{
    MODULEENTRY32  M = {sizeof(M)};
    HANDLE  hSnapshot = NULL;

    Module_Name[0] = 0;

    hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, 0);
    
    if ((hSnapshot != INVALID_HANDLE_VALUE) &&
        Module32First(hSnapshot, &M))
    {
        do
        {
            if (DWORD(Ret_Addr - M.modBaseAddr) < M.modBaseSize)
            {
                lstrcpyn(Module_Name, M.szExePath, MAX_PATH);
                Module_Addr = M.modBaseAddr;
                break;
            }
        } while (Module32Next(hSnapshot, &M));
    }

    CloseHandle(hSnapshot);

    return !!Module_Name[0];
} //Get_Module_By_Ret_Addr

Project Settings Modification

Since minidump is primarily needed for release version, we discuss only that version. The same settings can be recommended if you want to use the MiniDumpWriteDump() function of DbgHelp.dll. After applying all these modifications, the size and speed of your program would change only negligibly, most likely would not change at all.

The Program Database (/Zi compiler option to generate .pdb file) is needed for both our own minidump and the system one:

Project Menu >> Settings >> C/C++ Tab >> Category: General >> Debug info: Program Database

Add the /Oy- compiler optimization option (along with the existent /O1 or /O2 option) to guarantee stack frame creation for all the functions:

Project Menu >> Settings >> C/C++ Tab >> Project Options: /Oy-

screenshot

Check the following checkboxes to add Linker's debug info (/debug) and MAP file:

Project Menu >> Settings >> Link Tab >> Category: General >> Generate debug info, Generate mapfile

Add the /opt:ref and /mapinfo:lines Linker options to eliminate functions and/or data that is never referenced from the resulting executable module, and include line-number info to the MAP file:

Project Menu >> Settings >> Link Tab >> Project Options: /opt:ref /mapinfo:lines

screenshot

Finding Call Stack offset location in the Source code

We can do it in three different ways. First, two methods rely on the MAP file (MiniDump.map) created by Linker, the third method is used with debug session. Suppose we've got a crash dump shown on the picture at the beginning of the article. An exception occurred at offset 16F3 in MiniDump.exe module of our demo project.

Method 1: Line numbers table of the MAP file

The Line numbers table is located at the end of the MAP file:

Line numbers for \MiniDump.obj(C:\SED\WFun\MiniDump.cpp) segment .text

68 0001:00000000    72 0001:0000000a    74 0001:00000029    76 0001:00000037
79 0001:0000003f    83 0001:00000056    89 0001:0000006d    85 0001:00000093

Here 68 0001:00000000 means <line number> <segment number>:<offset in the segment>. So, first we need to find the segment's offset in the module. It could be done by taking into account the load address of the module and the start address of the segment:

 Preferred load address is 00400000
Address       Publics by Value                                  Rva+Base

0001:00000000 ?Get_Module_By_Ret_Addr@@YGHPAEPADAAPAE@Z         00401000
0001:000000d0 ?Get_Call_Stack@@YGHPAU_EXCEPTION_POINTERS@@PAD@Z 004010d0

The segment's start 0001:00000000 address is 00401000, and the offset is 00401000 - 00400000 = 1000. Now we are ready to return to the Line numbers table and find the line number of our offset 16F3 in the module, that corresponds to offset 16F3 - 1000 = 000006F3 in the segment:

328 0001:000006c9   329 0001:000006d5   334 0001:000006e0   335 0001:000006e3
337 0001:000006f7   338 0001:00000709   343 0001:00000710   344 0001:00000713

It's easy to figure out that 335 0001:000006e3 is the culprit.

Method 2: Map file and Assembly listing

To generate the assembly listing creation, we need to set:

Project Menu >> Settings >> C/C++ Tab >> Category: Listing Files >> Listing file type: Assembly, Machine Code, and Source

Let's first find our offset 16F3 in the Publics by Value list in the MAP file:

Address       Publics by Value                                     Rva+Base

0001:00000000 ?Get_Module_By_Ret_Addr@@YGHPAEPADAAPAE@Z            00401000
0001:000000d0 ?Get_Call_Stack@@YGHPAU_EXCEPTION_POINTERS@@PAD@Z    004010d0
0001:00000240 ?Get_Version_Str@@YGHPAD@Z                           00401240
0001:000002f0 ?Get_Exception_Info@@YGPADPAU_EXCEPTION_POINTERS@@@Z 004012f0
0001:00000580 ?Create_Dump@@YGXPAU_EXCEPTION_POINTERS@@HH@Z        00401580
0001:000006e0 ?Func_3@@YGXPAX0KH@Z                                 004016e0
0001:00000710 ?Func_2@@YGXPAX0KH@Z                                 00401710

The module's address is 400000 (see "Preferred load address" in the MAP file), so we need to find out the function that contains address 4016F3. It is ?Func_3@@YGXPAX0KH@Z 004016e0 (corresponds to Func_3 in the source code), with offset in the function 4016F3 - 004016e0 = 00013. Now we can find the function by its name in the assembly listing (MiniDump.cod) and the command with offset 00013:

?Func_3@@YGXPAX0KH@Z PROC NEAR      ; Func_3, COMDAT
  334  : {
  00000 55         push  ebp
  00001 8b ec      mov   ebp, esp

  335  :     memcpy(Addr_1, Addr_2, Size);
  00003 8b 4d 10   mov   ecx, DWORD PTR _Size$[ebp]
  00006 56         push  esi
  00007 8b 75 0c   mov   esi, DWORD PTR _Addr_2$[ebp]
  0000a 8b c1      mov   eax, ecx
  0000c 57         push  edi
  0000d 8b 7d 08   mov   edi, DWORD PTR _Addr_1$[ebp]
  00010 c1 e9 02   shr   ecx, 2
  00013 f3 a5      rep movsd

No wonder we've found the same line number 335 as with Method 1.

Method 3: Debug Session

Start the Debug session with F8, F10, or F7 key. We suppose you've done that many times before. Don't forget we're debugging the release version. If you are using MFC and want to debug its code, we'd recommend to change NDEBUG to _DEBUG in:

Project Menu >> Settings >> C/C++ Tab >> Category: General >> Preprocessor definitions:

Our task is still to find the offset 16F3 in MiniDump.exe and in MiniDump.cpp. Activate the Disassembly window:

View Menu >> Debug Windows >> Disassembly Alt+8

Since this window uses addresses (not offsets), we need to determine the module's address:

Debug Menu >> Modules... >> Module List

MiniDump.exe module's address is 400000 (we have never seen this number before), so our sought address is 004016F3. Now we are very close to our goal -- enter 004016F3 in:

Edit Menu >> Go To... Ctrl+G >> Go to what: >> Address:

Press hard the Go To button, and scroll up to the beginning of the statement:

335:      memcpy(Addr_1, Addr_2, Size);
004016E3 8B 4D 10  mov   ecx,dword ptr [ebp+10h]
004016E6 56        push  esi
004016E7 8B 75 0C  mov   esi,dword ptr [ebp+0Ch]
004016EA 8B C1     mov   eax,ecx
004016EC 57        push  edi
004016ED 8B 7D 08  mov   edi,dword ptr [Addr_1]
004016F0 C1 E9 02  shr   ecx,2
004016F3 F3 A5     rep movs    dword ptr [edi],dword ptr [esi]

By the way, if you want to find an address in a DLL, do exactly the same: get the module's address from the Module List, add the offset, and Go To there. No need for setting a break point and executing the module.

Just a few words on MiniDumpWriteDump(), DbgHelp.dll, and WinDbg.exe

Though our dump with Call Stack is quite sufficient in many cases, you may also consider using the MiniDumpWriteDump() function of DbgHelp.dll. This function is available in Win XP/2003 without additional installation, and in all other systems as a redistributable. DbgHelp.dll and WinDbg.exe (needed to analyze the dump if you are not using VS.NET) can be downloaded at Debugging Tools for Windows. Don't forget to include the SDK with dbghelp.h into the download. The system minidump is especially valuable if you want to know the value of your global variables.

In the demo program, we first check to see if MiniDumpWriteDump() is available, then call it to create a minidump with the same name as of our .exe file:

    hDbgHelp = LoadLibrary("DBGHELP.DLL");
    MiniDumpWriteDump_ = (MINIDUMP_WRITE_DUMP)GetProcAddress(hDbgHelp, 
                          "MiniDumpWriteDump");

    if (MiniDumpWriteDump_)
    {
        MINIDUMP_EXCEPTION_INFORMATION  M;
        HANDLE  hDump_File;
        CHAR    Dump_Path[MAX_PATH];

        M.ThreadId = GetCurrentThreadId();
        M.ExceptionPointers = pException;  //got by GetExceptionInformation()
        M.ClientPointers = 0;

        GetModuleFileName(NULL, Dump_Path, sizeof(Dump_Path));
        lstrcpy(Dump_Path + lstrlen(Dump_Path) - 3, "dmp");

        hDump_File = CreateFile(Dump_Path, GENERIC_WRITE, 0, 
                         NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);

        MiniDumpWriteDump_(GetCurrentProcess(), GetCurrentProcessId(),
            hDump_File, MiniDumpNormal,
            (pException) ? &M : NULL, NULL, NULL);

        CloseHandle(hDump_File);
    }

Now you can run WinDbg.exe to open and interpret the MiniDump.dmp:

File Menu >> Open Crash Dump... Ctrl+D

View Menu >> Command Alt+1

Enter .ecxr (Display Exception Context Record) command, and enjoy the view:

screenshot

Summary

Proposed Call Stack creation method can be implemented in C/C++ and many other languages on x86 machines.

Feel free to modify and use the demo source code for whatever you want, and don't forget to rate the article Smile | :)

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

Share

About the Author

Vladimir Sedach

Ukraine Ukraine
No Biography provided

Comments and Discussions

 
Questioncannot download project PinmemberLOUIS Christian27-Apr-12 3:50 
GeneralMy vote of 5 Pinmembercapcom062-Feb-11 0:23 
QuestionIf ebp has been over written,how can i do? Pinmemberforeverjiangwei22-Jun-09 20:12 
QuestionMiniDumpWriteDump generates empty dumps file Pinmemberbishnupatro31-Jul-08 1:05 
AnswerRe: MiniDumpWriteDump generates empty dumps file PinmemberAnandChavali14-Oct-08 5:55 
GeneralRe: MiniDumpWriteDump generates empty dumps file Pinmemberbishnupatro14-Oct-08 17:28 
GeneralType information of structures, classes etc Pinmemberachimschoen13-Apr-06 5:21 
GeneralMiniDumpWriteDump and stack overflow Pinmemberachimschoen11-Apr-06 9:28 
GeneralRe: MiniDumpWriteDump and stack overflow PinmemberVladimir Sedach11-Apr-06 21:39 
GeneralRe: MiniDumpWriteDump and stack overflow Pinmemberachimschoen12-Apr-06 22:15 
GeneralRe: MiniDumpWriteDump and stack overflow Pinmembernoob123428-Jun-09 17:11 
Generaltest PinmemberVladimir Sedach4-Jan-06 21:53 
QuestionDoes exist something similar on Windows Mobile/Pocket PC OS? PinmemberTony Kmoch9-Sep-05 0:52 
QuestionMixed mode/managed code stakc walk? Pinmembermikestrat29-Nov-04 5:11 
GeneralStrongly recommend using dbghelp.dll Pinmembergfoot28-Nov-04 23:30 
GeneralRe: Strongly recommend using dbghelp.dll Pinmembercpede7-Dec-04 23:03 
GeneralRe: Strongly recommend using dbghelp.dll Pinmembergfoot7-Dec-04 23:30 
GeneralGetting Module names from addresses Pinmemberits_andyw25-Nov-04 1:21 
GeneralRe: Getting Module names from addresses PinmemberVladimir Sedach11-Apr-06 21:47 
AnswerRe: Getting Module names from addresses Pinmemberits_andyw12-Apr-06 3:16 
GeneralRe: Getting Module names from addresses PinmemberVladimir Sedach12-Apr-06 6:16 
GeneralRe: Getting Module names from addresses Pinmemberits_andyw12-Apr-06 21:29 
Generalreference list needed PinsussAnonymous17-Nov-04 19:07 
GeneralRe: reference list needed PinmemberVladimir Sedach18-Nov-04 18:49 
GeneralNice work PinmemberJerry Evans17-Nov-04 1:06 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web01 | 2.8.140916.1 | Last Updated 19 Nov 2004
Article Copyright 2004 by Vladimir Sedach
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid