
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;
PBYTE Ret_Addr;
DWORD Param[0];
} 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
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)
{
if (Get_Module_By_Ret_Addr(Ebp->Ret_Addr, Module_Name, Module_Addr))
{
Str_Len += wsprintf(Str + Str_Len,
NL "%08X %s",
Module_Addr, Module_Name);
Str_Len += wsprintf(Str + Str_Len,
NL " +%08X",
Ebp->Ret_Addr - Module_Addr);
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;
}
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];
}
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-

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

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;
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:

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 :)