C语言 Win32 - 从 C 代码回溯
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/5693192/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Win32 - Backtrace from C code
提问by Macmade
I'm currently looking for a way to get backtrace information under Windows, from C code (no C++).
我目前正在寻找一种在 Windows 下从 C 代码(无 C++)获取回溯信息的方法。
I'm building a cross-platform C library, with reference-counting memory management. It also have an integrated memory debugger that provides informations about memory mistakes (XEOS C Foundation Library).
我正在构建一个具有引用计数内存管理的跨平台 C 库。它还具有一个集成的内存调试器,可提供有关内存错误的信息(XEOS C 基础库)。
When a fault occurs, the debugger is launched, providing information about the fault, and the memory record involved.
发生故障时,调试器会启动,提供有关故障的信息以及所涉及的内存记录。


On Linux or Mac OS X, I can look for execinfo.hin order to use the backtracefunction, so I can display additional infos about the memory fault.
在 Linux 或 Mac OS X 上,我可以查找execinfo.h以使用该backtrace功能,因此我可以显示有关内存故障的附加信息。
I'm looking for the same thing on Windows.
我在 Windows 上寻找同样的东西。
I've seen How can one grab a stack trace in C?on Stack Overflow. I don't want to use a third-party library, so the CaptureStackBackTraceor StackWalkfunctions looks good.
我已经看到如何在 C 中获取堆栈跟踪?在堆栈溢出上。我不想使用第三方库,所以CaptureStackBackTraceorStackWalk函数看起来不错。
The only problem is that I just don't get how to use them, even with the Microsoft documentation.
唯一的问题是我不知道如何使用它们,即使是使用 Microsoft 文档也是如此。
I'm not used to Windows programming, as I usually work on POSIX compliant systems.
我不习惯 Windows 编程,因为我通常在 POSIX 兼容系统上工作。
What are some explanations for those functions, and maybe some examples?
这些功能有哪些解释,也许还有一些例子?
EDIT
编辑
I'm now considering using the CaptureStackBackTracefunction from DbgHelp.lib, as is seems there's a little less overhead...
我现在正在考虑使用CaptureStackBackTracefrom的函数DbgHelp.lib,因为似乎开销少了一点......
Here's what I've tried so far:
这是我迄今为止尝试过的:
unsigned int i;
void * stack[ 100 ];
unsigned short frames;
SYMBOL_INFO symbol;
HANDLE process;
process = GetCurrentProcess();
SymInitialize( process, NULL, TRUE );
frames = CaptureStackBackTrace( 0, 100, stack, NULL );
for( i = 0; i < frames; i++ )
{
SymFromAddr( process, ( DWORD64 )( stack[ i ] ), 0, &symbol );
printf( "%s\n", symbol.Name );
}
I'm just getting junk. I guess I should use something else than SymFromAddr.
我只是变得垃圾。我想我应该使用除SymFromAddr.
回答by Macmade
Alright, now I got it. : )
好的,现在我明白了。:)
The problem was in the SYMBOL_INFO structure. It needs to be allocated on the heap, reserving space for the symbol name, and initialized properly.
问题出在 SYMBOL_INFO 结构中。它需要在堆上分配,为符号名称保留空间,并正确初始化。
Here's the final code:
这是最终的代码:
void printStack( void );
void printStack( void )
{
unsigned int i;
void * stack[ 100 ];
unsigned short frames;
SYMBOL_INFO * symbol;
HANDLE process;
process = GetCurrentProcess();
SymInitialize( process, NULL, TRUE );
frames = CaptureStackBackTrace( 0, 100, stack, NULL );
symbol = ( SYMBOL_INFO * )calloc( sizeof( SYMBOL_INFO ) + 256 * sizeof( char ), 1 );
symbol->MaxNameLen = 255;
symbol->SizeOfStruct = sizeof( SYMBOL_INFO );
for( i = 0; i < frames; i++ )
{
SymFromAddr( process, ( DWORD64 )( stack[ i ] ), 0, symbol );
printf( "%i: %s - 0x%0X\n", frames - i - 1, symbol->Name, symbol->Address );
}
free( symbol );
}
Output is:
输出是:
6: printStack - 0xD2430
5: wmain - 0xD28F0
4: __tmainCRTStartup - 0xE5010
3: wmainCRTStartup - 0xE4FF0
2: BaseThreadInitThunk - 0x75BE3665
1: RtlInitializeExceptionChain - 0x770F9D0F
0: RtlInitializeExceptionChain - 0x770F9D0F
回答by Jon Bright
Here's my super-low-fi alternative, as used for reading stacks from a C++ Builder app. This code is executed within the process itself when it crashes and gets a stack into the cs array.
这是我的超低保真替代方案,用于从 C++ Builder 应用程序读取堆栈。当进程崩溃并将堆栈放入 cs 数组时,此代码在进程本身内执行。
int cslev = 0;
void* cs[300];
void* it = <ebp at time of crash>;
void* rm[2];
while(it && cslev<300)
{
/* Could just memcpy instead of ReadProcessMemory, but who knows if
the stack's valid? If it's invalid, memcpy could cause an AV, which is
pretty much exactly what we don't want
*/
err=ReadProcessMemory(GetCurrentProcess(),it,(LPVOID)rm,sizeof(rm),NULL);
if(!err)
break;
it=rm[0];
cs[cslev++]=(void*)rm[1];
}
UPDATE
更新
Once I've got the stack, I then go about translating it into names. I do this by cross-referencing with the .mapfile that C++Builder outputs. The same thing could be done with a map file from another compiler, although the formatting would be somewhat different. The following code works for C++Builder maps. This is again quite low-fi and probably not the canonical MS way of doing things, but it works in my situation. The code below isn't delivered to end users.
一旦我获得了堆栈,我就会将其翻译成名称。我通过交叉引用.mapC++Builder 输出的文件来做到这一点。同样的事情可以用来自另一个编译器的映射文件来完成,尽管格式会有所不同。以下代码适用于 C++Builder 地图。这也是非常低保真,可能不是规范的 MS 做事方式,但它适用于我的情况。下面的代码不会交付给最终用户。
char linbuf[300];
char *pars;
unsigned long coff,lngth,csect;
unsigned long thisa,sect;
char *fns[300];
unsigned int maxs[300];
FILE *map;
map = fopen(mapname, "r");
if (!map)
{
...Add error handling for missing map...
}
do
{
fgets(linbuf,300,map);
} while (!strstr(linbuf,"CODE"));
csect=strtoul(linbuf,&pars,16); /* Find out code segment number */
pars++; /* Skip colon */
coff=strtoul(pars,&pars,16); /* Find out code offset */
lngth=strtoul(pars,NULL,16); /* Find out code length */
do
{
fgets(linbuf,300,map);
} while (!strstr(linbuf,"Publics by Name"));
for(lop=0;lop!=cslev;lop++)
{
fns[lop] = NULL;
maxs[lop] = 0;
}
do
{
fgets(linbuf,300,map);
sect=strtoul(linbuf,&pars,16);
if(sect!=csect)
continue;
pars++;
thisa=strtoul(pars,&pars,16);
for(lop=0;lop!=cslev;lop++)
{
if(cs[lop]<coff || cs[lop]>coff+lngth)
continue;
if(thisa<cs[lop]-coff && thisa>maxs[lop])
{
maxs[lop]=thisa;
while(*pars==' ')
pars++;
fns[lop] = fnsbuf+(100*lop);
fnlen = strlen(pars);
if (fnlen>100)
fnlen = 100;
strncpy(fns[lop], pars, 99);
fns[lop][fnlen-1]=' $file = fopen($mapdir.$app."-".$appversion.".map","r");
if (!$file)
... Error handling for missing map ...
do
{
$mapline = fgets($file);
} while (!strstr($mapline,"CODE"));
$tokens = split("[[:space:]\:]", $mapline);
$codeseg = $tokens[1];
$codestart = intval($tokens[2],16);
$codelen = intval($tokens[3],16);
do
{
$mapline = fgets($file);
} while (!strstr($mapline,"Publics by Value"));
fgets($file); // Blank
$addrnum = 0;
$lastaddr = 0;
while (1)
{
if (feof($file))
break;
$mapline = fgets($file);
$tokens = split("[[:space:]\:]", $mapline);
$thisseg = $tokens[1];
if ($thisseg!=$codeseg)
break;
$addrs[$addrnum] = intval($tokens[2],16);
if ($addrs[$addrnum]==$lastaddr)
continue;
$lastaddr = $addrs[$addrnum];
$funcs[$addrnum] = trim(substr($mapline, 16));
$addrnum++;
}
fclose($file);
';
}
}
} while (!feof(map));
fclose(map);
After running this code, the fnsarray contains the best-matching function from the .map file.
运行此代码后,该fns数组包含 .map 文件中的最佳匹配函数。
In my situation, I actually have the call stack as produced by the first piece of code submitting to a PHP script - I do the equivalent of the C code above using a piece of PHP. This first bit parses the map file (Again, this works with C++Builder maps but could be easily adapted to other map file formats):
在我的情况下,我实际上拥有由提交给 PHP 脚本的第一段代码生成的调用堆栈 - 我使用一段 PHP 执行上述 C 代码的等效操作。第一个位解析地图文件(同样,这适用于 C++Builder 地图,但可以轻松适应其他地图文件格式):
$thisaddr = intval($rowaddr,16);
$thisaddr -= $codestart;
if ($thisaddr>=0 && $thisaddr<=$codelen)
{
for ($lop=0; $lop!=$addrnum; $lop++)
if ($thisaddr<$addrs[$lop])
break;
}
else
$lop = $addrnum;
if ($lop!=$addrnum)
{
$lop--;
$lines[$ix] = substr($line,0,13).$rowaddr." : ".$funcs[$lop]." (+".sprintf("%04X",$thisaddr-$addrs[$lop]).")";
$stack .= $rowaddr;
}
else
{
$lines[$ix] = substr($line,0,13).$rowaddr." : external";
}
Then this bit translates an address (in $rowaddr) into a given function (as well as the offset after the function):
然后该位将地址 (in $rowaddr) 转换为给定的函数(以及函数后的偏移量):
NT_TIB* pTEB = GetTEB();
UINT_PTR ebp = GetEBPForStackTrace();
HANDLE hCurProc = ::GetCurrentProcess();
while (
((ebp & 3) == 0) &&
ebp + 2*sizeof(VOID*) < (UINT_PTR)pTEB->StackBase &&
ebp >= (UINT_PTR)pTEB->StackLimit &&
nAddresses < nTraceBuffers)
{
pTraces[nAddresses++]._EIP = ((UINT_PTR*)ebp)[1];
ebp = ((UINT_PTR*)ebp)[0];
}
回答by chksr
@Jon Bright: You say "who known whether the stack is valid...": Well there's a way to find out, as the stack addresses are known. Assuming you need a trace in the current thread, of course:
@Jon Bright:你说“谁知道堆栈是否有效......”:嗯,有一种方法可以找出,因为堆栈地址是已知的。假设您需要在当前线程中进行跟踪,当然:
inline __declspec(naked) UINT_PTR GetEBPForStackTrace()
{
__asm
{
mov eax, ebp
ret
}
}
My "GetTEB()" is NtCurrentTeb() from NTDLL.DLL - and it is not only Windows 7 and above as stated in the current MSDN. MS junks up the documentation. It was there for a long time. Using the ThreadEnvironment Block (TEB), you do not need ReadProcessMemory() as you know the stack's lower and upper limit. I assume this is the fastest way to do it.
我的“GetTEB()”是来自 NTDLL.DLL 的 NtCurrentTeb() - 它不仅是当前 MSDN 中所述的 Windows 7 及更高版本。MS 破坏了文档。它在那里很久了。使用线程环境块 (TEB),您不需要 ReadProcessMemory(),因为您知道堆栈的下限和上限。我认为这是最快的方法。
Using the MS compiler, GetEBPForStackTrace() can be
使用 MS 编译器,GetEBPForStackTrace() 可以
##代码##as easy way to get EBP of the current thread (but you can pass any valid EBP to this loop as long as it is for the current thread).
作为获取当前线程的 EBP 的简单方法(但您可以将任何有效的 EBP 传递给此循环,只要它适用于当前线程)。
Limitation: This is valid for x86 under Windows.
限制:这对 Windows 下的 x86 有效。

