如何阅读调用栈?

3
我们有一个本地的C++应用程序,通过COM+在Windows 2003服务器上运行。最近我从事件查看器中注意到它会抛出异常,特别是C0000005异常,根据http://blogs.msdn.com/calvin_hsia/archive/2004/06/30/170344.aspx的说法,这意味着进程试图写入不在其地址空间内的内存,也就是访问冲突。
事件查看器中的条目提供了一个调用堆栈:

LibFmwk!UTIL_GetDateFromLogByDayDirectory(char const *,class utilCDate &)+ 0xa26c LibFmwk!UTIL_GetDateFromLogByDayDirectory(char const *,class utilCDate &)+ 0x8af4 LibFmwk!UTIL_GetDateFromLogByDayDirectory(char const *,class utilCDate &)+ 0x13a1 LibFmwk!utilCLogController :: GetFLFInfoLevel(void)const + 0x1070 LibFmwk!utilCLogController :: GetFLFInfoLevel(void)const + 0x186

现在,我明白它给我提供了要查看的方法名称,但是我有一种感觉,每行末尾的地址(例如+0xa26c)试图指向该方法中的特定行或指令。
所以我的问题是:
  1. 是否有人知道我如何使用此地址或调用堆栈中的任何其他信息来确定代码中的哪一行出现了错误?
  2. 是否有任何资源可供我阅读以更好地理解调用堆栈?
  3. 是否有任何免费软件/开源工具可以帮助分析调用堆栈,例如通过连接到调试符号文件和/或二进制文件?
编辑: 根据要求,这是似乎引起问题的方法:
BOOL UTIL_GetDateFromLogByDayDirectory(LPCSTR pszDir, utilCDate& oDate)
{
BOOL bRet = FALSE;

if ((pszDir[0] == '%') &&
    ::isdigit(pszDir[1]) && ::isdigit(pszDir[2]) &&
    ::isdigit(pszDir[3]) && ::isdigit(pszDir[4]) &&
    ::isdigit(pszDir[5]) && ::isdigit(pszDir[6]) &&
    ::isdigit(pszDir[7]) && ::isdigit(pszDir[8]) &&
    !pszDir[9])
{
    char acCopy[9];
    ::memcpy(acCopy, pszDir + 1, 8);
    acCopy[8] = '\0';

    int iDay = ::atoi(&acCopy[6]);
    acCopy[6] = '\0';
    int iMonth = ::atoi(&acCopy[4]);
    acCopy[4] = '\0';
    int iYear = ::atoi(&acCopy[0]);

    oDate.Set(iDay, iMonth, iYear);

    bRet = TRUE;
}

return (bRet);

}

这是公司某位已离职成员十年前编写的代码,因此我不确定它的确切作用,但我知道它涉及将日志目录从“Today”重命名为特定日期,例如%20090329的过程。数组索引、memcpy和地址运算符使它看起来相当可疑。
另一个问题是,这只在生产系统上发生,我们从未能够在测试系统或开发系统上复现它,这使我们无法附加调试器。
非常感谢! 安迪

1
如果您使用调试符号进行构建,则可能会获得实际的行号,而不是字节偏移量。 - John Saunders
你确定传入的 pszDir 至少有 10 个字符吗?如果不是这种情况,它肯定会崩溃。 - jussij
为了检查客户端问题,您需要添加OutputDebugString以打印pszDir的内容,并使用SysInternals' DbgView观察实际值。如大家所见 - 您绝对需要检查pszDir是否为NULL。可能在您的情况下pszDir是无效的,您需要检查调用者。 - Andrey
3个回答

6

其他人已经含蓄地提到过这一点,但没有明确说明。看一下:

LibFmwk!UTIL_GetDateFromLogByDayDirectory(char const *,class utilCDate &) + 0xa26c

0xa26c的偏移量非常大,远远超出了函数的末尾。调试器显然没有LibFmwk的正确符号,因此它依赖DLL导出并显示相对于最接近的导出项的偏移量。

所以,是的,请获取正确的符号,然后就应该很容易了。UTIL_GetDateFromLogByDayDirectory在这里并没有问题。


3

如果你真的需要将这些地址映射到你的功能 - 你需要使用.MAP文件并查看这些地址实际指向哪里。

但是在你的情况下,我宁愿在调试器下调查这个问题(例如MSVS调试器或windbg);作为替代方案(如果崩溃发生在客户端),您可以生成崩溃转储并在本地进行研究 - 这可以通过Windows MiniDumpWriteDump API或SysInternals ProcDump实用程序(http://download.sysinternals.com/Files/procdump.zip)来完成。

确保生成并可用所有所需的符号文件(还要设置Microsoft符号服务器路径以便解析Windows DLL的入口点)。

在我看来,这就是你需要的网站:http://www.dumpanalysis.org - 这是涵盖你所有问题的最佳资源。 还要考虑查看这个PDF - http://windbg.info/download/doc/pdf/WinDbg_A_to_Z_color.pdf


这太棒了!非常非常有趣的链接! - Karel

1

第2点和第3点很容易回答:

第3点:任何调试器都可以。这就是它们的设计用途。将调试器设置为在该特殊异常上中断。您应该能够通过调用堆栈单击查找堆栈上不同的调用(至少delphi可以这样做,因此Visual Studio也应该能够)。如果可能,请不要进行优化编译。OllyDBG也可能有效 - 可能与其跟踪功能结合使用。

第2点:任何有关x86汇编语言、反向工程等的信息...尝试:OpenRCE, NASM Documentation, ASM Community

第一点。调用栈能告诉你函数的执行情况。我不知道它是按顺序还是相反的顺序写的 - 所以可能第一行是最后调用的函数或者第一个调用的函数。使用调试器跟踪函数调用。有时你可以在汇编和代码之间切换(取决于调试器、映射文件等)。如果没有源代码,请学习汇编语言,了解逆向工程。阅读第三方组件中所调用函数的文档。也许你没有满足前提条件。

如果你能更多地介绍该程序(你拥有源代码的哪些部分,是否涉及库调用等等),那就更好了。


现在来看一些代码:

该函数接受一个指向以零结尾的字符串和一个日期对象的引用的指针。假定指针是有效的!

该函数检查字符串是否符合特定格式(%后跟8个数字,然后是\0)。如果不是这种情况,则返回false。这个检查(大if)访问指针而没有任何有效性检查。长度未经检查,如果指针指向某个位置,则访问该空间。我不知道较短的字符串是否会引起问题。由于对&&的评估方式,它不应该出现问题。

然后在堆栈上分配一些内存。将字符串的数字部分复制到其中(这是可以的),并使缓冲区获得其\0终止符。atois提取数字。这将起作用,因为使用了不同的起始位置和每个部分之后的\0终止符。有点棘手但很好。一些注释会使一切变得清晰。

然后将这些数字插入对象中。它应该是有效的,因为它是通过引用传递给函数的。我不知道您是否可以将引用传递给已删除的对象,但如果是这种情况,这也可能是您的问题。

总之,除了缺少字符串指针检查外,这个函数是可靠的,不是你问题的原因。只有调用该函数的地方会抛出异常。搜索传递给此函数的参数。它们始终有效吗?进行一些日志记录。

我希望我没有犯任何大错误,因为我是Delphi程序员。如果我犯了错误,请随时评论。


感谢查看代码,就方法本身而言,我同意您的观点,假设传递的参数指向正确的内存区域,它不会导致访问冲突。这是我需要验证的事情。我希望我能将这两个回答都设置为正确答案。 - A. Murray

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接