【问题标题】:Can invalid Read/Write cause SIGBUS Error?无效的读/写会导致 SIGBUS 错误吗?
【发布时间】:2019-11-07 13:55:20
【问题描述】:

编辑 1:示例程序的平台是 x86_64。

编辑 2:我正在编辑此内容以便更好地理解。下面是两个不同的问题。第一个问题是无效的读/写会导致 SIGBUS 吗?第二个问题是 Valgrind 对 SIGBUS 分析有用吗?示例代码用于第二个问题,以支持我的观点,即 Valgrind 在 SIGBUS 错误的情况下根本没有用。我这里可能是错的。

实际场景:我们有一个屏幕阅读器应用程序在连续测试 2 天后崩溃(曾经由于 SIGBUS 崩溃)。我有一个 coredump 文件,但我没有正确的二进制和调试包。所以基本上我必须在不同的二进制文件中测试它,并且由于调试包不匹配,coredump 在 gdb 中无法正常工作。在 Valgrind 分析期间,我可以在屏幕阅读器模块中看到一些无效的读/写。我的队友建议通过修复这些无效的读/写来解决这个问题,但我认为它不会解决它。以下是我对这两种信号的理解。

SIGSEGV:地址有效,但没有读/写权限。

SIGBUS:地址本身无效(CPU由于对齐错误等原因无法找到地址)

我有一个关于 SIGBUS 信号的问题。我搜索了关于堆栈溢出的类似问题,但没有找到任何明确的答案。

无效的读/写会导致总线错误(SIGBUS)吗?

我的理解是无效的读/写总是会导致分段错误(SIGSEGV),修复总线错误的最佳方法是在应用程序上运行 gdb。在总线错误的情况下进行 Valgrind 分析根本没有帮助。下面的代码更详细地解释了这一点。

#include<stdlib.h>
#include<stdio.h>

typedef struct {
char *name;
int val;
}data;

void fun1()
{
    data *ptr = malloc(sizeof(data));
    ptr->val = 100;
    ptr->name = "name in structure";

    printf("val:%d name:%s\n",ptr->val,ptr->name);
    free(ptr);
    ptr = NULL;
    printf("val:%d name:%s\n",ptr->val,ptr->name); //SIGSEGV
    return;
}

int fun2()
{
    #if defined(__GNUC__) 
    # if defined(__i386__) 
    /* Enable Alignment Checking on x86 */
    __asm__("pushf\norl $0x40000,(%esp)\npopf"); 
    # elif defined(__x86_64__)  
    /* Enable Alignment Checking on x86_64 */
    __asm__("pushf\norl $0x40000,(%rsp)\npopf"); 
    # endif 
    #endif 

    char *cptr = malloc(sizeof(int) + 1);
    char *optr = cptr;
    int *iptr = (int *) ++cptr; 
    *iptr = 42; //SIGBUS
    free(optr);

    return 0; 
}

void fun()
{
    fun2();
    //fun1();
}

int main()
{
    fun();
    return 0;
}

在分段错误的情况下,Valgrind 报告将包含有关导致崩溃的代码的详细信息,但在 SIGBUS 崩溃的情况下,我在 Valgrind 报告中没有找到任何此类详细信息。

SIGSEGV 的 Valgrind 报告:

==28128== Memcheck, a memory error detector
==28128== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==28128== Using Valgrind-3.11.0 and LibVEX; rerun with -h for copyright info
==28128== Command: ./a.out
==28128== Parent PID: 27953
==28128== 
==28128== Invalid read of size 8
==28128==    at 0x400619: fun1 (tmp.c:18)
==28128==    by 0x400695: fun (tmp.c:46)
==28128==    by 0x4006A6: main (tmp.c:51)
==28128==  Address 0x0 is not stack'd, malloc'd or (recently) free'd
==28128== 
==28128== 
==28128== Process terminating with default action of signal 11 (SIGSEGV)
==28128==  Access not within mapped region at address 0x0
==28128==    at 0x400619: fun1 (tmp.c:18)
==28128==    by 0x400695: fun (tmp.c:46)
==28128==    by 0x4006A6: main (tmp.c:51)
==28128==  If you believe this happened as a result of a stack
==28128==  overflow in your program's main thread (unlikely but
==28128==  possible), you can try to increase the size of the
==28128==  main thread stack using the --main-stacksize= flag.
==28128==  The main thread stack size used in this run was 8388608.
==28128== 
==28128== HEAP SUMMARY:
==28128==     in use at exit: 0 bytes in 0 blocks
==28128==   total heap usage: 2 allocs, 2 frees, 1,040 bytes allocated
==28128== 
==28128== All heap blocks were freed -- no leaks are possible
==28128== 
==28128== For counts of detected and suppressed errors, rerun with: -v
==28128== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)

SIGBUS 的 Valgrind 报告:

==28176== Memcheck, a memory error detector
==28176== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==28176== Using Valgrind-3.11.0 and LibVEX; rerun with -h for copyright info
==28176== Command: ./a.out
==28176== Parent PID: 27953
==28176== 
==28176== 
==28176== HEAP SUMMARY:
==28176==     in use at exit: 0 bytes in 0 blocks
==28176==   total heap usage: 1 allocs, 1 frees, 5 bytes allocated
==28176== 
==28176== All heap blocks were freed -- no leaks are possible
==28176== 
==28176== For counts of detected and suppressed errors, rerun with: -v
==28176== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

【问题讨论】:

  • 这将是一个非常特定于平台的答案,并且您没有指定您的平台。例如,在某些平台上,未对齐的读取或写入可能会导致 SIGBUS。
  • 由于严格的别名规则,int *iptr = (int *) ++cptr; 是未定义的行为。
  • @DavidSchwartz。上述示例代码的平台是 x86_64。我知道未对齐的访问可能会导致 SIGBUS,但我怀疑它是否会导致读/写无效?
  • @kiranBiradar 是的。未定义的行为。鉴于malloc 返回一个指向具有最严格对齐的内存的指针,如果编译器尝试执行代码要求它执行的操作,则递增一个字节是保证您将获得未对齐访问的可靠方法。
  • @JL2210 无效读写也可以生成SIGBUS。在这种情况下。 SIGSEGVSEGmentation Violation - 您的进程试图访问它不允许访问的内存。另一方面,请参阅en.wikipedia.org/wiki/Segmentation_fault A SIGBUS 或内存 BUS 错误,这意味着您的进程试图以底层硬件无法完成的方式访问内存 - 您的进程被允许读/写内存,但试图以不受硬件支持的方式进行。见en.wikipedia.org/wiki/Bus_error

标签: c gdb valgrind segmentation-fault sigbus


【解决方案1】:
int *iptr = (int *) ++cptr; 
*iptr = 42; //SIGBUS

违反了 C 标准的多个部分。

你和6.3.2.3 Pointers, paragraph 7发生了冲突:

指向对象类型的指针可以转换为指向不同对象类型的指针。如果结果指针未正确对齐引用类型,则行为未定义。

以及违反6.5 Expressions, paragraph 7的严格别名规则:

对象的存储值只能由具有以下类型之一的左值表达式访问:

  • 与对象的有效类型兼容的类型,
  • 与对象的有效类型兼容的类型的限定版本,
  • 对应于对象有效类型的有符号或无符号类型,
  • 一种有符号或无符号类型,对应于对象有效类型的限定版本,
  • 在其成员中包含上述类型之一的聚合或联合类型(递归地包括子聚合或包含联合的成员),或
  • 一种字符类型。

the Valgrind documentation for Memcheck

4.1.概述

Memcheck 是一个内存错误检测器。它可以检测以下内容 C 和 C++ 程序中常见的问题。

  • 访问你不应该访问的内存,例如堆块溢出和欠载,栈顶溢出,访问内存 在它被释放之后。

  • 使用未定义的值,即尚未初始化的值,或从其他未定义值派生的值。

  • 堆内存释放不正确,例如两次释放堆块,或 malloc/new/new[] 与 免费/删除/删除[]

  • memcpy 和相关函数中的 src 和 dst 指针重叠。

  • 将可疑(可能为负)值传递给内存分配函数的大小参数。

  • 内存泄漏。

注意你的代码

int *iptr = (int *) ++cptr; 
*iptr = 42; //SIGBUS

Valgrind 声称检测到的事情都没有。您没有访问您无权访问的内存,也没有访问您使用malloc() 创建的区域范围之外的内存。你还没有free()'d 内存。您没有未初始化的变量,您没有双倍free()ing 内存,也没有在重叠的源区域和目标区域不正确地使用memcpy()。而且您不会将负数/“可疑”大小传递给分配函数。而且你没有泄漏任何内存。

所以,不,Valgrind 甚至没有声称能够检测到会导致SIGBUS 的代码。

【讨论】:

  • 这是故意造成总线错误的。为了更好地理解,我修改了我原来的问题。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-08-12
  • 2020-09-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多