【问题标题】:How to track down access violation "at address 00000000"如何追踪访问冲突“地址 00000000”
【发布时间】:2011-01-15 06:34:11
【问题描述】:

当错误消息包含实际地址时,我知道如何创建 .map 文件来跟踪访问冲突错误。

但是如果错误信息显示怎么办

Access violation at address 00000000. Read of address 00000000.

我从哪里开始寻找这个问题的原因...?

【问题讨论】:

  • 这让我想起了在 Turbo C 下,“空指针赋值”,当它以错误的方式管理时......
  • 它可以重现吗?如果没有,请添加类似 madExcept 的工具并等待错误报告,否则使用调试器并查看堆栈跟踪。
  • 这是从旧的 Turbo C 时代开始,在调试器下,对指向位置 0 的指针变量添加一个监视,即单步执行时刚刚执行的行,导致指针变量指向位置0 变化值是搞砸的行。主要是指针错误...
  • Tommie,可能有 许多 个指针变量保持地址值为零。为什么要在其中任何一个上设置手表? 更改其中之一不会导致此错误。当 instruction pointer 为零时会发生此错误,但您不能对此设置监视,因为它在每条指令上都会更改。哪个调试器告诉你 previous 执行的行?该调试器将使解决此问题变得非常容易。
  • 你接受的答案并没有告诉你太多,IMO。

标签: delphi


【解决方案1】:

接受的答案并不能说明全部情况。

是的,只要你看到零,就会涉及到一个NULL 指针。这是因为NULL根据定义为零。所以调用零NULL 可能没什么意思。

您收到的消息有趣的是NULL 被提及两次。事实上,您报告的消息看起来有点像 Windows 品牌操作系统向用户显示的消息。

消息显示 地址 NULL 试图读取 NULL。那是什么意思?具体来说,地址是如何读取自身的?

我们通常认为地址处的指令从特定地址的内存中读取和写入。知道这一点使我们能够解析错误消息。该消息试图阐明 地址 NULL指令 试图读取 NULL

当然,地址NULL 没有指令,这就是为什么我们认为NULL 在我们的代码中是特殊的。但是每条指令都可以被认为是从尝试阅读自身开始的。如果 CPU EIP 寄存器位于地址 NULL,则 CPU 将尝试从地址 0x00000000 (NULL) 读取指令的操作码。读取NULL 的尝试将失败,并生成您收到的消息。

在调试器中,当您收到此消息时,请注意 EIP 等于 0x00000000。这证实了我给你的描述。

那么问题就变成了,“为什么我的程序会尝试执行NULL 地址。”脑海中浮现出三种可能性:

  • 您试图通过您声明的函数指针进行函数调用,分配给NULL,从未以其他方式初始化,并且正在取消引用。
  • 同样,您可能正在调用“抽象”C++ 方法,该方法在对象的 vtable 中有一个 NULL 条目。这些是在您的代码中创建的,语法为 virtual function_name()=0
  • 在您的代码中,堆栈缓冲区在写入零时溢出。零已被写入堆栈缓冲区末尾之外的保留返回地址。当函数稍后执行它的ret 指令时,值 0x00000000 (NULL) 从被覆盖的内存点加载。这种类型的错误,堆栈溢出,是我们论坛的同名词。

由于您提到您正在调用第三方库,我将指出这可能是库希望您提供非NULL 函数指针作为某些API 的输入的情况。这些有时被称为“回调”函数。

您将不得不使用调试器进一步缩小问题的原因,但上述可能性应该可以帮助您解开谜团。

【讨论】:

    【解决方案2】:

    这是一个真正快速的临时修复,至少在您再次重新启动之前是这样,但它将摆脱持久访问。我安装了一个运行良好的程序,但由于某种原因,在正确的文件中没有正确安装一点。因此,当它无法访问该文件时,它会弹出拒绝访问但不仅仅是一个,它会不断尝试启动它,因此即使搜索永久停止它的位置,它也会越来越多地弹出3 秒。要至少暂时阻止这种情况发生,请执行以下操作...

    1. Ctl+Alt+Del
    2. 打开您的任务管理器
    3. 记下请求访问的程序的名称(您可能会在应用程序的选项卡中看到它)
    4. 单击“进程”选项卡
    5. 滚动浏览,直到找到与程序名称匹配的进程并单击它
    6. 点击结束进程

    这将防止窗口持续弹出,至少在您重新启动之前是这样。我知道这并不能解决问题,但就像任何事情一样,有一个消除过程,这里的这一步至少会让它不那么烦人。

    【讨论】:

    • 这绝对是无用的废话。 A/V 发生在发帖人编写的代码中,因此发帖人确切知道是什么程序导致了它。该问题被标记为 Delphi,它是一种编程语言。此答案中的任何内容都与所提出的问题无关。
    【解决方案3】:

    如果您收到“地址 00000000 的访问冲突。”,您正在调用尚未分配的函数指针 - 可能是事件处理程序或回调函数。

    例如

    type
    TTest = class(TForm);
    protected
      procedure DoCustomEvent;
    public
      property OnCustomEvent : TNotifyEvent read FOnCustomEvent  write FOnCustomEvent;
    end;
    
    procedure TTest.DoCustomEvent;
    begin
      FOnCustomEvent(Self);  
    end;
    

    代替

    procedure TTest.DoCustomEvent;
    begin
      if Assigned(FOnCustomEvent) then // need to check event handler is assigned!
        FOnCustomEvent(Self);  
    end;
    

    如果错误出在第三方组件中,并且您可以跟踪违规代码,请使用空事件处理程序来阻止 AV。

    【讨论】:

      【解决方案4】:

      我会第二次使用 madExcept 和类似的工具,比如 Eurekalog,但我认为你也可以使用 FastMM。启用完整调试模式后,它应该会为您提供一些错误线索。

      尽管如此,尽管 Delphi 默认使用 FastMM,但值得获得完整的 FastMM,因为它可以额外控制日志记录。

      【讨论】:

        【解决方案5】:

        使用 MadExcept。或 JclDebug。

        【讨论】:

          【解决方案6】:

          您开始查看您知道运行的代码,当您到达您知道未运行的代码时,您停止查看。

          您正在寻找的可能是您的程序通过函数指针调用函数的某个地方,但该指针为空。

          也有可能是堆栈损坏。您可能已经用零覆盖了函数的返回地址,并且异常发生在函数的末尾。检查可能的缓冲区溢出,如果您正在调用任何 DLL 函数,请确保您使用了正确的调用约定和参数计数。

          这不是使用空指针的普通情况,例如未分配的对象引用或 PChar。在这些情况下,您将有一个非零的“地址 x”值。由于指令发生在地址 0,因此您知道 CPU 的指令指针没有指向任何有效指令。这就是为什么调试器不能告诉你是哪一行代码导致了问题——没有行代码。您需要通过查找导致 CPU 跳转到无效地址的位置的代码来找到它。

          调用堆栈可能仍然完好无损,这至少应该让您非常接近目标。但是,如果您有堆栈损坏,您可能无法信任调用堆栈。

          【讨论】:

            【解决方案7】:

            当我偶然发现这个问题时,我通常会开始查看 FreeAndNil() 或只是 xxx := NIL; 的地方。变量和之后的代码。

            当没有其他帮助时,我添加了一个 Log() 函数来在执行期间从各个可疑位置输出消息,然后查看该日志以跟踪代码中访问冲突的位置。

            当然有许多更优雅的解决方案可用于追踪这些违规行为,但如果您没有这些解决方案,那么老式的试错法也可以正常工作。

            【讨论】:

              【解决方案8】:

              这可能是因为您直接或间接通过库调用访问了 NULL 指针。在这种特殊情况下,您似乎跳转到了一个 NULL 地址,这有点麻烦。

              根据我的经验,跟踪这些问题的最简单方法是使用调试器运行它,然后转储堆栈跟踪。

              或者,您可以“手动”完成并添加大量日志记录,直到您可以准确地追踪到该违规发生在哪个函数(可能还有 LOC)。

              看看Stack Tracer,它可能会帮助您改进调试。

              【讨论】:

                【解决方案9】:

                地址“00000000”附近任何地方的访问冲突表示空指针访问。您在创建之前、很可能或在 FreeAndNil()'d 之后使用的东西。

                很多时候,这是由于在创建表单期间访问了错误位置的组件,或者让您的主表单尝试访问尚未创建的数据模块中的某些内容。

                MadExcept 可以很容易地追踪这些东西,并且可以免费用于非商业用途。 (实际上,商业使用许可证也很便宜,而且物有所值。)

                【讨论】:

                • 我将此标记为“正确”响应,因为有关 MadExcept 的提示非常有用。但是,在我的具体情况下,错误似乎发生在第三方库中(希望可以通过应用供应商补丁来修复),所以这次我不需要进一步研究。
                • 表示你申请的对象已经销毁或者还未创建
                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2015-12-26
                • 1970-01-01
                相关资源
                最近更新 更多