代码之家  ›  专栏  ›  技术社区  ›  onnodb

如何猎杀海森氏虫

  •  7
  • onnodb  · 技术社区  · 15 年前

    最近,我们收到一个用户的错误报告:屏幕上的某些内容在我们的软件中显示不正确。不知何故,我们无法在开发环境中重现这一点(Delphi2007)。

    经过进一步的研究,似乎这个错误 只有 当“代码优化”打开时显示自身 .

    这里有没有人有这样的经验 Heisenbug ?在Delphi软件中,是否有任何特定的构造或编码错误通常会导致这样的问题?有什么地方可以找吗?

    我也将以通常的方式开始调试整个程序,但是任何与优化相关的bug(*)的特定提示都将是非常受欢迎的!

    (*)注:我不是说这个bug是 引起 通过优化器;我认为更可能的是,代码中的一些奇怪的构造被优化器“推到了边缘”。

    更新

    似乎这个错误归根结底就是在没有代码优化的情况下用零完全初始化一个记录,而在没有优化的情况下,包含一些随机数据的同一个记录 优化。在这种情况下,随机数据似乎会导致枚举类型包含无效数据(令我吃惊!).

    解决方案

    解决方案最终涉及到代码深处某个统一的局部记录变量。显然,没有优化记录 重置(堆?),并在优化后 记录中充满了平常的垃圾。感谢你们所有人的贡献——一路上我学到了很多东西!

    10 回复  |  直到 15 年前
        1
  •  12
  •   moonshadow    15 年前

    通常,这种形式的错误是由无效的内存访问(读取未初始化的数据,读取缓冲区的末尾…)或线程争用条件引起的。

    前者将受到优化的影响,导致数据布局在内存中重新排列,和/或可能受到调试代码的影响,调试代码将新分配的内存初始化为某个值;导致不正确的代码“意外工作”。

    后者将受到优化级别之间的时间变化的影响。前者的可能性更大。

    如果您有一种自动化的方法,在将新分配的内存传递给程序之前,用一些常量值填充它,这会使崩溃消失,或者在调试构建中变得可复制,这将为开始跟踪提供一个很好的起点。

        2
  •  6
  •   Francesca    15 年前

    很可能是内存对寄存器的问题:您的程序在空闲后依赖于内存持久性而正常运行。
    我建议在完全调试模式下使用fastmm4运行应用程序,以确保内存管理。
    在这种情况下,另一个(不是免费的)工具是Eurekalog。

    我看到的另一件事是:当调用一些外部代码(dll,com…)时,FPU寄存器崩溃,而调试器一切正常。

        3
  •  3
  •   Deltics    15 年前

    根据不同的编译器设置,包含不同数据的记录告诉我一件事:记录没有被显式初始化。

    您可能会发现,编译器优化标志的设置只是一个可能影响该记录内容的因素-对于任何未初始化的数据结构,您可以依赖的一点是,您不能依赖结构的初始内容。

    简单来说:

    • 类的新实例的类成员数据已初始化(为零)

    • 局部变量(在函数和过程中)和单元变量没有初始化,除非在一些特定的情况下:接口引用、动态数组和字符串,我认为(但需要检查)记录是否包含将要初始化的类型的一个或多个字段(字符串、接口引用等)。

    现在所说的问题有点误导人,因为你似乎觉得你的“海森堡”相当容易。现在的问题是如何处理它,而答案只是显式地初始化您的记录,这样您就不依赖于编译器的任何行为或副作用,有时为您处理,有时不为您处理。

        4
  •  2
  •   Robert Giesecke    15 年前

    尤其是在纯本地语言中,比如德尔福,你应该更加小心,不要滥用任何东西都可以投射到任何东西上的自由。 IOW:我看到的一件事是,有人将类的定义(例如,从rtl或vcl的实现部分)复制到自己的代码中,然后将原始类的实例强制转换到自己的副本中。 现在,在升级了原始类来自的库之后,您可能会体验到各种奇怪的东西。比如跳入错误的方法或者缓冲区溢出。

    也有使用有符号整数作为指针的习惯,反之亦然。(而不是红衣主教) 只要您的进程只有2GB的地址空间,这就可以完美地工作。但是使用/3GB开关启动,你会看到很多应用程序开始疯狂。它们至少在某个地方假设了“指针=有符号整数”。 您的客户使用64位Windows?很有可能,他可能有更大的32位应用程序地址空间。如果没有这样的测试系统,很难进行调试。

    然后,还有比赛条件。 就像有两条线,其中一条非常非常慢。所以你本能地认为它总是最后一个,所以没有代码来处理“captn slow”首先完成的场景。 基础技术的变化可能会使这些假设非常错误,甚至非常迅速。 看看即将推出的基于闪存的超级超高速服务器存储。 每秒可读写千兆字节的系统。如果应用程序假定IO数据比内存值的某些计算速度慢得多,那么在这种快速存储上很容易失败。

    我可以不停地跑,但我必须马上跑… 干杯

        5
  •  2
  •   Ozan HELPY    15 年前

    代码优化并不一定意味着必须省略调试符号。使用代码优化进行调试构建,然后您仍然可以调试程序,可能现在发生了错误。

        6
  •  2
  •   APZ28    15 年前

    一个简单的方法是打开编译器警告和提示,重新生成项目,然后修复所有警告/提示。

    干杯

        7
  •  2
  •   Marco van de Voort    15 年前

    如果它是Delphi业务代码,带有数据感知组件等,那么下面的内容可能不适用。

    但是我在写机器视觉代码,这有点计算性。大多数单元测试都是基于控制台的。我也参与过FPC,多年来我在FPC做了很多测试。部分是出于爱好,部分是在绝望的情况下,我想要任何预感。

    我尝试的一些标准技巧(降低有用性)

    1. 使用-gv和valgrind代码(实际上这意味着应用程序需要在linux/freebsd上运行。但对于可实现的计算代码和单元测试)
    2. 使用fpc param-gt编译(=trash local vars,随机化procedure init上的local vars)
    3. 修改heapManager使其输出的块的数据随机化(也可应用于delphi代码)
    4. 尝试fpc的范围/溢出检查和编译器提示。
    5. 在Mac Mini(PowerPC)或Win64上运行。由于完全不同的规则和内存布局,它可以捕获相当时髦的东西。

    2和3加在一起几乎可以找到大多数初始化问题(如果不是所有初始化问题)。

    试着找到任何线索,然后返回Delphi,搜索更多的焦点,调试等。

    我知道这并不容易。我有很多FPC的经验,而且不必从零开始就为这些案件找到所有的线索。不过,这可能值得一试,也可能是开始建立非可视系统和单元测试兼容的FPC和独立平台的动力。不管怎样,大部分的工作都是需要的,看看Delphi的路线图。

        8
  •  1
  •   RED SOFT ADAIR    15 年前

    在这种情况下,我总是建议使用日志文件。

    问题:您能否确定源代码中的错误显示?

    如果没有,我的回答对你没有帮助。

    如果是,请检查是否不正确,一旦找到错误,就将堆栈转储到日志文件中。(见 post mortem debugging 有关转储和重新编译堆栈的详细信息)。

    如果您看到一些数据已损坏,但您不知道如何进行,然后发生这种情况,则提取一个进行有效性测试的函数(如果失败,则进行日志记录),并在程序执行过程中(即每次菜单调用后)从越来越多的地方调用该函数。如果你重复这种方法几次,你就有很好的机会找到问题所在。

        9
  •  1
  •   Jeroen Wiert Pluimers    15 年前

    这是过程或函数中的局部变量吗?

    如果是这样,那么它就住在堆栈上,并且将包含垃圾。根据执行路径和编译器设置,垃圾将发生变化,可能会将逻辑“推到边缘”。

    ——杰罗恩

        10
  •  0
  •   Loren Pechtel    15 年前

    考虑到您对这个问题的描述,我认为您没有使用优化器就可以获得未初始化的数据,但随着优化的进行而崩溃。