【问题标题】:Why am I not getting a segmentation fault with this code? (Bus error)为什么这段代码没有出现分段错误? (总线错误)
【发布时间】:2012-03-25 06:42:42
【问题描述】:

我的代码中有一个像这样的错误。

char desc[25];
char name[20];
char address[20];
sprintf (desc, "%s %s", name, address);

理想情况下,这应该给出一个段错误。但是,我看到这给出了总线错误。 维基百科说“总线错误”的顺序是程序尝试访问未对齐的内存位置,或者当您尝试访问不存在或不允许的物理(非虚拟)内存位置时。 '

上述语句的第二部分听起来类似于段错误。所以我的问题是,你什么时候得到 SIGBUS,什么时候得到 SIGSEGV?

编辑:- 很多人都提到了上下文。我不确定需要什么上下文,但这是位于从许多其他类函数调用的静态类函数中的缓冲区溢出。如果有更具体的东西我可以提供帮助,请询问。

无论如何,有人评论说我应该编写更好的代码。我想问这个问题的目的是“应用程序开发人员可以从 SIGBUS 与 SIGSEGV 中推断出任何东西吗?” (摘自下面的博文)

【问题讨论】:

  • 澄清一下,这家伙正在寻找 SIGBUS 和 SIGSEGV 之间的区别,而不是发布代码生成其中一个或另一个的原因。
  • @Pochi,我认为 cnicutar 的回答仍然适用。一旦您遇到未定义或非法行为,几乎任何事情都可能发生。这取决于操作系统/硬件。我们甚至不知道错误是否发生在 sprintf 行上,也许它只是破坏了内存,而其他东西会产生实际的错误。
  • @Pochi 嗯,我希望能得到这两个问题的答案。
  • 无法得知所提供的上下文信息。

标签: c++ gcc segmentation-fault


【解决方案1】:

当您使用内存做一些可疑的事情时,永远无法保证会出现分段错误。这完全取决于很多因素(编译器如何在内存中布置程序、优化等)。

对于 C++ 程序来说可能是非法的东西对于一般程序来说可能不是非法的。例如,操作系统不关心您是否跨出阵列。它甚至不知道数组是什么。但是它确实在意你是否触摸了不属于你的记忆。

【讨论】:

  • 这是否意味着它完全不可预测?更准确地说,是否有可能编写一个始终提供 SIGBUS 的程序,即使它只是非法(但对齐)内存访问(我们通常与 SIGSEGV 相关联)?
  • @KarlKnechtel 确实是明智的建议 :-)
【解决方案2】:

鉴于您的字符串由另外两个字符串组成,每个字符串最长为 20 个字符,但您将其放入一个 25 个字符的字段中,这就是您的第一个问题所在。你很有可能超越自己的界限。

变量desc 的长度至少应为 41 个字符(20 + 20 + 1 [对于您插入的空间])。

使用valgrindgdb 找出出现段错误的原因。

【讨论】:

    【解决方案3】:
    char desc[25];
    char name[20];
    char address[20];
    sprintf (desc, "%s %s", name, address);
    

    仅通过查看此代码,我可以假设 nameaddress 每个都可以是 20 字符长。如果是这样,那么这是否意味着desc 应该是最少20+20+1 字符长? (1 char 表示nameaddress 之间的空格,在sprintf 中指定)。

    这可能是段错误的原因之一。也可能有其他原因。例如,如果name20 字符长怎么办?

    所以你最好使用std::string:

    std::string name;
    std::string address;
    std::string desc = name + " " + address;
    char const *char_desc = desc.str(); //if at all you need this
    

    【讨论】:

    • 您错过了在末尾自动附加的额外空字符:cplusplus.com/reference/clibrary/cstdio/sprintf。所以 desc 大小必须是 20+1+20+1 ;)。为了避免这种情况,我更喜欢使用 snprintf 但问题不是为什么会有段错误,而是为什么发生总线错误而不是段错误。
    【解决方案4】:

    在这种特殊情况下,您不知道格式字符串中有什么样的垃圾。该垃圾可能会导致将剩余参数视为“对齐”数据类型的参数(例如intdouble)。在某些系统上,将未对齐区域视为对齐参数肯定会导致 SIGBUS

    【讨论】:

    • 坦率地说,我不确定我记得确切的说法是什么。您是说 sprintf (desc, "%s %d", name, somechar) 其中 somechar 是未对齐的 char[4] 会一直提供 SIGBUS 吗?请注意,我没有收到上述代码的编译时错误...
    【解决方案5】:

    如果您尝试对未映射到您的进程的虚拟地址进行数据访问,则会发生分段错误。在大多数操作系统上,内存被映射为几千字节的页面。这意味着如果你在一个数组的末尾注销,你通常不会出错,因为在内存页面中后面还有其他有效数据。

    总线错误表示更底层的错误;正如您所说,错误对齐的访问或缺少物理地址是两个原因。但是,第一个在这里没有发生,因为您正在处理没有对齐限制的字节;而且我认为第二个只能在内存完全耗尽时发生在数据访问上,这可能不会发生。

    但是,如果您尝试从无效的虚拟地址执行代码,我认为您也可能会遇到总线错误。这很可能是这里发生的事情 - 通过写出 local 数组的末尾,您将覆盖堆栈帧的重要部分,例如函数的返回地址。这将导致函数返回到无效地址,这(我认为)会产生总线错误。这是我对您在这里遇到的特定未定义行为的最佳猜测。

    一般来说,您不能依靠分段错误来捕获缓冲区溢出;我所知道的最好的工具是valgrind,尽管它仍然无法捕获某些类型的溢出。使用字符串时避免溢出的最佳方法是使用std::string,而不是假装您正在编写 C。

    【讨论】:

    • 他没有多说上下文,但看起来他的局部变量缓冲区溢出。所以他可以很容易地修改返回地址(或者sprintf可以修改它的返回地址);错误的返回地址将导致 Sparc 上出现SIGBUS。 (我没有在 Intel 上尝试过。)返回基本上是随机地址可能会导致任何事情。
    • @JamesKanze:确实;这或多或少是我的第三段所说的。
    • 原来如此。这是漫长的一天。
    【解决方案6】:

    您可能已经意识到,根本原因是您的未定义行为 程序。在这种情况下,它会导致硬件检测到错误, 它被操作系统捕获并映射到信号。准确的映射 没有真正指定(我已经看到零结果的积分除法 在SIGFPE) 中,但通常:SIGSEGV 在您访问时发生 bounds,SIGBUS 表示其他访问错误,SIGILL 表示非法 操作说明。在这种情况下,最可能的解释是您的 边界错误已覆盖堆栈上的返回地址。如果 返回地址未正确对齐,您可能会得到一个SIGBUS, 如果是,您将开始执行那里的任何内容,这可能 结果为SIGILL。 (但执行随机字节的可能性为 代码是标准委员会在定义时所考虑的 “未定义的行为”。特别是在没有内存的机器上 保护,您最终可能会直接跳入操作系统。)

    【讨论】:

    • 我无法在测试程序中复制这一点,但这似乎是最合乎逻辑的解释。 Mike Seymour 在下面的回答似乎也说了同样的话。那么你是说可以创建这么小的测试程序还是毫无意义?
    • @owagh 我会说这是毫无意义的,虽然我认为对于特定的架构来说这并不难,如果你知道编译器是如何布置变量的,堆栈帧等。这实际上是缓冲区溢出攻击的基础:如果您知道某些系统代码存在此问题,则向其发送数据以用一些邪恶的机器代码填充缓冲区,并将返回地址设置为指向缓冲区。
    猜你喜欢
    • 2010-10-19
    • 1970-01-01
    • 1970-01-01
    • 2013-03-28
    • 2017-08-18
    • 2017-06-05
    • 2017-11-25
    • 1970-01-01
    相关资源
    最近更新 更多