【问题标题】:Program segfaults on alpine linux. How do I resolve it?alpine linux 上的程序段错误。我该如何解决?
【发布时间】:2018-07-12 14:47:58
【问题描述】:

我一直在研究 C/C++ 中的 webrtc 数据通道库和 C 中的 wrote a program

  1. 从同一进程创建两个对等点。
  2. 在它们之间建立连接。
  3. 如果连接成功,请关闭连接。

在 debian docker 容器和我的主机 opensuse tumbleweed(所有 x86_64 和 64 位)上一切正常,但在 alpine linux 容器(64 位 x86_64)上,我在子进程中获得了 SEGFAULT:

上面的函数来自程序的依赖“libnice”。似乎 *agent == NULL 并且在 caller's 范围内无法设为 null。我什至在函数调用之前插入了printf("Argument is %p", agent);,它打印出它的内存,我可以验证它不为空。从反汇编来看,复制代理的内容 (0x557a1d20) 作为被调用者堆栈中的局部变量会导致段错误。即使在make clean 和重新编译之后,段错误总是发生在这一点上。激活记录失败?堆栈损坏?

更新:我制作了一个更轻量级的容器并运行它,现在它在同一 priv_conn_keepalive_tick_unlocked 的不同位置出现了段错误。该参数似乎已设置(注意 0x7ffff7f9ad08):

因为我认为我可能会达到libmusl's 的默认堆栈限制 80k,所以我使用getrlimit(RLIMIT_STACK, &rl) 来获取堆栈大小,看起来它已经是 8 MB 而不是 80k。进一步增加这个限制似乎没有任何区别,除了如果我分配超过 8 MB,我的程序会提前 Gdb 中崩溃。 Gdb 说它收到一个未知信号“??”;在 gdb 之外,它会在正常情况下崩溃,而不会更改堆栈大小。

我不确定到底是什么问题(堆栈损坏?)以及接下来要解决的问题。

这是我的程序流程:

对于创建的每个对等点,都会使用 fork() 创建一个子进程。父 子通信由 ZeroMQ 完成,我使用协议缓冲区将子进程内部触发的任何回调(及其参数)转发到父进程中运行的事件循环。

所以对于上面的程序,有2个子进程和1个父进程。

重现步骤:

【问题讨论】:

  • 你有没有考虑过这个问题的根源在所有平台上都存在,只选择了一个出现?解决方案是大量调试并找到根本原因,在大多数平台上隐藏起来可能会变得更加困难。
  • 假设你可以随意重现崩溃,我要做的第一件事就是插入一个 printf("calling priv_conn_keepalive_tick_unlocked with argument %p\n", agentPtr);紧接在调用该函数之前的行——这样你就可以验证调用是(或不是)使用 NULL 参数进行的。
  • 此外,函数名称以后缀 _unlocked 结尾的事实让我怀疑函数的作者试图传达该函数不执行任何自己的互斥锁定,而是取决于在调用代码上锁定适当的互斥锁在调用函数之前。未能首先锁定必要的互斥体很可能会导致仅在某些情况下(例如仅在一个特定操作系统下)发生的崩溃,这与您的观察结果相符。
  • @JeremyFriesner 是的,我确实在函数调用之前添加了带有agent 参数的打印语句,并且指针不为空。
  • 您尝试过哪些其他 Linux 版本?也不要认为你对正在发生的事情的分析是正确的。我怀疑您的堆栈损坏,并且您的局部变量已成为“垃圾”。

标签: c++ c linux alpine stack-corruption


【解决方案1】:

在进一步调查中,崩溃是在一条指令写入距堆栈基指针稍大的负偏移量时,因此它可能只是一个简单的堆栈溢出。

解决此问题的正确方法是减少多余的堆栈使用或在pthread_create 时间明确请求一个大堆栈,但我看不到从哪里调用pthread_create。快速检查以验证这是不是问题所在,可以通过在程序早期的某处执行以下操作来覆盖新线程的默认堆栈大小:

pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setstacksize(&attr, 1<<20); // 1 MB
pthread_setattr_default_np(&attr);

【讨论】:

    【解决方案2】:

    -Werror=implicit-function-declaration 添加到您的 CFLAGS 中,您将立即找到原因。关键线索是指针值 0x557a1d20,这几乎肯定是将指针截断为 32 位的结果。当您未能声明一个返回指针的函数并且编译器(通过一个可怕的向后默认值)假设它返回 int 而不是产生错误,然后随后允许从 int 到指针的隐式转换,尽管 C 语言不允许它,就会发生这种情况。

    【讨论】:

    • 哈!是的,可能就是这样!
    • 但是,我认为输出只是rdi 处的一个整数——gdb 的x 命令的默认大小是int
    • 在 libnice 或我的程序编译中都没有这样的隐式函数声明警告。如前所述,我添加了 -Werror 并且都成功编译。
    • 好吧,再看看它几乎肯定只是堆栈溢出。我将添加一个新答案
    猜你喜欢
    • 1970-01-01
    • 2015-01-24
    • 2013-08-22
    • 1970-01-01
    • 2019-06-24
    • 2016-07-21
    • 2018-11-10
    • 2016-09-09
    • 2015-03-23
    相关资源
    最近更新 更多