3

我们有一个通过 COM+ 在 Windows 2003 服务器上运行的本机 C++ 应用程序。我最近从事件查看器中注意到它抛出异常,特别是 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:::! )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);

}

这是我们公司的一位成员在 10 多年前编写的代码,他早已离开,所以我不认为确切知道这是在做什么,但我确实知道它涉及从“今天”重命名日志目录的过程' 到特定日期,例如 %20090329。数组索引、memcpy 和运算符的地址确实使它看起来相当可疑。

我们似乎遇到的另一个问题是,这只发生在生产系统上,我们永远无法在我们的测试系统或开发系统上重现它,这将允许我们附加调试器。

非常感激!安迪

4

3 回答 3

6

其他人在字里行间说过这一点,但没有明确表示。看着:

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

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

所以,是的,得到正确的符号,然后它应该是轻而易举的。UTIL_GetDateFromLogByDayDirectory 在这里没有错。

于 2009-08-06T02:41:46.523 回答
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

于 2009-07-31T11:28:07.317 回答
1

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

第 3 点。任何调试器。这就是他们的目的。将调试器设置为中断此特殊异常。您应该能够通过调用堆栈单击自己并找到堆栈上的不同调用(至少 delphi 可以做到这一点,所以 Visual Studio 也应该能够)。如果可能,不进行优化编译。OllyDBG 也可以工作——也许结合它的跟踪功能。

第 2 点。有关 x86 汇编程序、逆向工程的任何信息...尝试:OpenRCENASM 文档ASM 社区

第 1 点。调用堆栈告诉你函数。我不知道它是按顺序还是相反顺序编写的 - 所以第一行可能是最后一个调用的函数或第一个调用的函数。在调试器的帮助下跟踪调用。有时您可以在 asm 和代码之间进行切换(取决于调试器、映射文件……)。如果您没有源代码 - 学习汇编程序,请阅读逆向工程。阅读您在第三方组件中调用的函数的文档。也许你不满足一个先决条件。

如果您可以详细介绍一下程序(您有源代码的哪些部分,是否涉及库调用?,...)


现在一些代码阅读:

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

该函数检查字符串是否采用特定格式(% 后跟 8 位数字,后跟 \0)。如果不是这种情况,则返回 false。此检查(大 if)在没有任何有效性检查的情况下访问指针。不检查长度,如果指针指向野外某处,则访问该空间。我不知道较短的字符串是否会导致问题。这不应该是因为 && 的评估方式。

然后在堆栈上分配一些内存。字符串的数字部分被复制到其中(没关系),缓冲区得到它的 \0 终止。atois 提取数字。由于使用了不同的起始位置和每个部分之后的 \0-终止,这将起作用。有点棘手但很好。一些评论会让一切都清楚。

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

无论如何-除了缺少对字符串指针的检查外,此功能是合理的,而不是您的问题的原因。它只是引发异常的地方。搜索传递给此函数的参数。它们总是有效的吗?做一些记录。

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

于 2009-07-31T11:18:31.707 回答