Im up to building mini debugger in C# and I already checked out mdbg project
and use NativeDebugWraper project in my mini debugger.
My question is : What is the simplest way to get a call stack when my
debugee is suspended?
I considered already function StackWalk64 from dbghelp.dll but I can not
find any suitable example calling it from C#.
There is also a way using DIA tools but I dont want to use it since they
require VS to be installed.
RtlCaptureStackBackTrace is however another suitable function from kernel32
(not sure) but it is not well documented.
I would like to get output something similar to the Visual Studio Call Stack
window.It would be very nice If this is possible.
Any help would be greatly appreciated.
Thank you very much in advance.
Best regards,
Civa
I have never called these from C#. Here are some samples calling them
from C++:
By the way, these are to capture the stack of the currenty running
thread, so you will most likely have to use the StackWalk version,
since that one takes a hProcess handle.
#if 1
DWORD SP3DPERF_API getStackTrace( size_t stack[], int FramesToCapture,
int FramesToSkip )
{
// get stack - (FramesToSkip + FramesToCapture) must be less than 64
ULONG numFrames = RtlCaptureStackBackTrace( FramesToSkip,
FramesToCapture, (PVOID*)stack, 0 );
if (numFrames)
{ // remove bottom of the frame invalid addresses
long lastEntry = numFrames - 1;
if ( ((stack[lastEntry] & 0xFFFFfff0) == 0xFFFFfff0) // 0xFFFFfffc
or 0XFFFFfffd or 0xFFFFfffa or ...
|| (stack[lastEntry] < 0x00400000) // no code below 4MB
boundary
|| (stack[lastEntry] == 0xCCCCcccc) ) // this shows up with
some frequency also
{
stack[lastEntry] = 0;
numFrames--;
}
}
return numFrames;
}
#else
CSpinLock stackSpin;
#pragma warning( disable : 4748 ) //asm stmt causes C4748: /GS can not
protect parameters and local variables from local buffer overrun
because optimizations are disabled in function
DWORD SP3DPERF_API getStackTrace( DWORD stack[], int reqNumEntries,
int skipNumEntries )
{
DWORD st;
int numEntriesReturned = 0;
int numEntriesProcessed = 0;
HANDLE hThread = GetCurrentThread();
HANDLE hProcess = GetCurrentProcess();
IMAGEHLP_MODULE modInfo;
modInfo.SizeOfStruct = sizeof(modInfo);
STACKFRAME stackFrame;
ZeroMemory( &stackFrame, sizeof(stackFrame) );
// where was the stack when we entered this function ?
stackFrame.AddrPC.Mode = AddrModeFlat;
stackFrame.AddrFrame.Mode = AddrModeFlat;
_asm {
here: lea eax, here // how to get the
current Eip :-)
mov stackFrame.AddrPC.Offset, eax // oops, it is not
current anymore :-P
mov stackFrame.AddrFrame.Offset, ebp
}
// get stack trace
{
DWORD lastAddr = -1;
long numValidEntries = 0;
long bSkippedSP3DPerfHeader = FALSE;
CEnterSpinLock stackTrace(stackSpin);
while (numEntriesReturned < reqNumEntries)
{
// walk one frame at a time, making sure there is only one thread
here at a time
st = StackWalk( IMAGE_FILE_MACHINE_I386, hProcess
, hThread, &stackFrame, NULL
, (PREAD_PROCESS_MEMORY_ROUTINE) ReadProcessMemory
, (PFUNCTION_TABLE_ACCESS_ROUTINE)
SymbolEngine::SEfunctionTableAccess
#ifdef SYM_ACTIVE
, (PGET_MODULE_BASE_ROUTINE) SymbolEngine::SEgetModuleBase
#else
, (PGET_MODULE_BASE_ROUTINE) NULL
#endif
, (PTRANSLATE_ADDRESS_ROUTINE) NULL);
// check if we can't go any further
if ((st == 0) || (0xffffffff == stackFrame.AddrReturn.Offset) ||
(lastAddr == stackFrame.AddrReturn.Offset))
break;
lastAddr = stackFrame.AddrReturn.Offset;
// skip all SP3DPerf callers until the first non-SP3DPerf caller
HMODULE lastModuleGuess = (HMODULE) (lastAddr & 0xFFFF0000);
if ((FALSE == bSkippedSP3DPerfHeader) && (lastModuleGuess ==
g_hModuleSp3dPerf))
continue;
bSkippedSP3DPerfHeader = TRUE;
// skip all user requested entries
if (skipNumEntries-- <= 0)
// ... record it
stack[numEntriesReturned++] = stackFrame.AddrPC.Offset;
}
}
// zero other entries for cleanliness
int i = numEntriesReturned;
while (i < reqNumEntries)
stack[i++] = 0;
// make sure it is 0 terminated
stack[reqNumEntries-1] = 0;
return numEntriesReturned;
}
#pragma warning( default : 4748 )
#endif
Now that captures the stack. If you want to have same output as Visual
Studio Call Stack, then you will need to symbolize those hex numbers
from the stack capture. That can be done using something similar to
these functions below, which you will have to call for each entry in
the captured stack:
int SymbolEngine::SEgetSymbolForAddress(size_t addr //in: address we
want symbol for
, char* buf //in: string buffer
space
, int lenAvail //in: in(string
buffer len),[0..lenAvail-1]
) //returns count of
bytes used
{
// get module
size_t st = 0;
Symbol symbol;
__int64 displacement = 0;
IMAGEHLP_MODULE modInfo = {0};
// get module information for this address
st = this->SEgetModuleInfo(addr, modInfo);
if ((st) && (modInfo.SymType == SymNone) && (0 !=
modInfo.BaseOfImage) && (0 != modInfo.ImageName[0]))
{
// no symbols; lets attempt to load them
// unload whatever is currently loaded for this module and
load the real one
st = SymUnloadModule64(GetCurrentProcess(),
modInfo.BaseOfImage);
st = static_cast<size_t>(SymLoadModule64(GetCurrentProcess(),
NULL, modInfo.ImageName, NULL, modInfo.BaseOfImage, 0));
}
// get symbol from address; if fails, we will attempt to load the
symbol file associated
if (st)
{
st = SymGetSymFromAddr64( GetCurrentProcess(), addr,
(PDWORD64)&displacement, (PIMAGEHLP_SYMBOL64)&symbol);
}
if ((0 == st) || (displacement > 100000) || ( '\0' ==
symbol.sym.Name[0]))
{
// there is no symbol information for this address, so output
the RVA (displacement from BaseAddress)
_snprintf_s(symbol.sym.Name, _countof(symbol.sym.Name),
_TRUNCATE, "0x%08x", (addr - modInfo.BaseOfImage));
}
else // there is a symbol; add displacement, if any
if (0 != displacement)
{
char dispBuffer[64];
strcat_s(symbol.sym.Name, symbol.sym.MaxNameLength, "+");
_itoa_s((int)displacement, dispBuffer, sizeof(dispBuffer), 10);
strcat_s(symbol.sym.Name, symbol.sym.MaxNameLength,
dispBuffer);
}
// assemble results in form DLLNAME![symbol+]displacement, making
sure it is null terminated
*buf = '\0';
strncpy_s(buf, lenAvail, modInfo.ModuleName, _TRUNCATE);
strcat_s(buf, lenAvail, "!");
strcat_s(buf, lenAvail, symbol.sym.Name);
int symLen = (int)strlen(buf);
assert(symLen != (lenAvail-1)); // oops, symbol did not fit
return symLen; // does not include terminating '\0'
}
// returns strlen(buf) and buf contains "filename(lineno)" string, if
available
int SymbolEngine::SEgetLineForAddress(size_t addr //in: address we
want filename(line) for
, char* buf //in: string buffer
space
, int lenAvail //in out: in(string
buffer len),
)
{
// get module
BOOL st = 0;
Line line;
DWORD displacement;
char lineBuffer[64];
st = SymGetLineFromAddr( GetCurrentProcess(), addr, &displacement,
(PIMAGEHLP_LINE)&line);
if (0 == st)
{
st = GetLastError();
if (ERROR_INVALID_ADDRESS != st)
{
//assert(0); too many asserts; we won't have symbols for
everything... :-(
}
return 0; // no filename(lineno) information
}
// same as _snprintf(buf, "%s(%d)", line.Filename, line.LineNumber);
but hopefully faster
lenAvail--;
*buf = '\0';
strncpy_s(buf, lenAvail, line.FileName, _TRUNCATE);
strcat_s(buf, lenAvail, "(");
_itoa_s(line.LineNumber, lineBuffer, sizeof(lineBuffer), 10);
strcat_s(buf, lenAvail, lineBuffer);
strcat_s(buf, lenAvail, ")");
int lenUsed = (int)strlen(buf);
assert(lenUsed <= lenAvail); //oops, the symbol did not
fit
return lenUsed; // does not include terminating '\0'
}
Hope this helps,
Osiris
PS: If you get these working directly from C#, I would love the see a
sample of these calls.
I will try to get these to work in C# and as I promissed also on the MSDN
forum I will be glad to post my working source code!!
Best regards,
Civa
"opedroso" wrote:
> .
>