Improved callstack
tracing for PC
The previous article contains solutions
to callstack tracing for several platforms. However, there is one
important thing to improve on the PC routine that was presented. The
PC version contained a low-level implementation that worked fine in
itself, but had one major drawback: it was required that all of the
modules had the compiler optimisation setting ‘omit frame pointers’
disabled. While you can do that for the projects you control
yourself, you often cannot do that for third party projects. Often
that limitation caused crashes with that version of the
StoreCallStack
routine because the stackframe was pointing to unknown memory.
This version from listing 1 uses the
StackWalk64
function from the DbgHelp library, which is part of the
Windows platform SDK.
I would also like to point to a sample
made by Jochen Kalmbach 5, which is much more extensive then
the sample listed in listing 1. Point needs to be taken that the
algorithm used by StackWalk64
is much slower than the StoreCallstack
function that was presented in the previous articles. As a thread
from the Microsoft forums suggest 11, the stackwalk function from
the DIA SDK may be faster than this stackwalk function from
the DbgHelp library. This may be something to look into.
|
|
unsigned int StoreCallStack(
unsigned int* returnAddresses,
unsigned int nrReturnAddresses)
{
unsigned int nrItemsWritten = 0;
STACKFRAME64 stackFrame;
unsigned int* ebpReg = 0;
__asm mov [ebpReg],ebp
if(ebpReg != NULL)
{
unsigned int frame = 0;
unsigned int address = 0;
frame = ebpReg[0];
address = ebpReg[1];
memset(&stackFrame, 0, sizeof(stackFrame));
stackFrame.AddrPC.Offset = (DWORD64)address;
stackFrame.AddrPC.Mode = AddrModeFlat;
stackFrame.AddrFrame.Offset = (DWORD64)frame;
stackFrame.AddrFrame.Mode = AddrModeFlat;
while(nrItemsWritten < nrReturnAddresses)
{
if(StackWalk64(
IMAGE_FILE_MACHINE_I386,
GetCurrentProcess(),
GetCurrentThread(),
&stackFrame,
NULL,
NULL,
SymFunctionTableAccess64,
SymGetModuleBase64,
NULL))
{
address = (unsigned int)stackFrame.AddrPC.Offset;
if(address == NULL)
{
returnAddresses[nrItemsWritten] = 0;
break;
}
else
{
returnAddresses[nrItemsWritten] = address;
}
nrItemsWritten++;
}
else
{
break;
}
}
}
return nrItemsWritten;
}
|
 |
 |
 |
Listing 1: The new
StoreCallStack function for x86.
|
Symbol loading
Before continuing, let’s recapitulate
quickly on function addresses. When retrieving callstack information,
you always retrieve a list of absolute function addresses – the
addresses of the functions as they are present in memory at that
moment in time. An image, for instance, an executable or DLL, may be
loaded on a different location in memory each time the application is
started. The image’s location in memory is called the image
base.
By subtracting the image base address from the function’s
absolute address, we obtain the function’s address relative to the
image base. This is often called the Relative Virtual Address (RVA).
The RVA can be used to retrieve symbol information from, for
instance, a program database (PDB).
When DLLs are used, it gets more
complicated. The callstack may contain addresses that cross DLL
boundaries. This has quite a few consequences, as I will demonstrate.
For starters, it is not possible to
send RVAs out to the tool. Doing so anyway may cause conflicts with
other relative addresses. Think about it: two distinct functions in
different DLLs having different absolute addresses may have the same
relative addresses. That breaks both the analysis algorithms in the
tool and the symbol lookup.
So, we need to send over absolute
addresses, at least for platforms where multiple modules are used.
What follows is that somehow the tool needs to know what images are
loaded and what the base addresses of those images are. That
information will be used to load symbol information. We will now
discuss two solutions to this problem.
|