【问题标题】:SEGFAULT on writing to stack variableSEGFAULT 写入堆栈变量
【发布时间】:2017-10-19 12:18:43
【问题描述】:

我在 ARM linux 平台上遇到了一个由简单代码引起的非常奇怪的崩溃。 问题是它很少重现(一天一次),另一个问题是它在实际无法重现的地方崩溃。

让我们从 C++ 代码开始。线程函数这样做:

    event_obj events[EVENTS_MAX]; // EVENTS_MAX = 32
    int num = 0;
    m_engine->getEvents(events, &num);

engine 是指向目前只有一个实现的基本抽象类的指针。 getEvents 是纯虚方法。

getEvents 经过一些更改后什么都不做

int engine::getEvents(event_obj*, int* num)
{
    if (num != nullptr)
    {
        *num = 0; // SEGMENTATION FAULT
    }
    return 1; // ok
}

SEGFAULT 在尝试将 0 存储到 num 时发生。首先我认为这是堆栈损坏,但在我检查生成的汇编代码之后,似乎没有任何东西存储在堆栈中。这种方法甚至没有生成堆栈保护(启用-fstack-protector-strong),两个参数都存储在寄存器r1和r2中。 让我们看看函数调用的代码:

        event_obj events[EVENTS_MAX];
        int num = 0;
   236f8:       2300            movs    r3, #0
   236fa:       ac06            add     r4, sp, #24
   236fc:       9306            str     r3, [sp, #24]
        m_engine->getEvents(events, &num);
   236fe:       6803            ldr     r3, [r0, #0]
   23700:       691b            ldr     r3, [r3, #16]
   23702:       4622            mov     r2, r4
   23704:       a90c            add     r1, sp, #48     ; 0x30
   23706:       4798            blx     r3

以及函数本身的代码:

int engine::getEvents(event_obj*, int* num)
{
    if (num != nullptr)
   251f8:       4613            mov     r3, r2
   251fa:       b10a            cbz     r2, 25200 <_Z18engine_thread_funcPv+0x9e0>
    {
        *num = 0;
   251fc:       2200            movs    r2, #0
   251fe:       601a            str     r2, [r3, #0]
    }
    return 1; // ok
}
   25200:       2001            movs    r0, #1
   25202:       4770            bx      lr
    return 1; // ok
}

从生成的代码中可以看出,指针被放入r1 和r2 寄存器中。

   23702:       4622            mov     r2, r4
   23704:       a90c            add     r1, sp, #48     ; 0x30

即使堆栈损坏,它也可能损坏num 变量的值,但是它如何损坏寄存器中的指针呢?同样从崩溃日志中我可以看到LR 地址是错误的。

CRASH 信号 11 分段错误地址 0xf0000000 PC 0x251fe LR 0x6c3c533c

从这里我唯一看不到的是跳转地址(blx r3),因为调用的方法是虚拟的。 我有一个非常不可能的假设,即不是跳转到虚拟方法主体的第一行,而是在此之前跳转到几行并损坏了寄存器,但我不明白这怎么可能。即使在更改代码之后,它也总是在同一行崩溃。这很奇怪。

有人可以建议尝试一下吗?有什么想法吗?

提前致谢。

【问题讨论】:

  • 您唯一可以尝试的就是生成minimal reproducible example。没有minimal reproducible examplestackoverflow.com 上的任何人都无法为您提供帮助。仅仅因为程序在特定点崩溃并不意味着这就是错误所在。错误可以在任何地方。欢迎使用 C++。例如,如果错误的逻辑导致无限递归,这可能是超出允许的堆栈空间的地方,由于尝试写入未映射的页面而导致页面错误。只是众多可能性中的一个例子,没有minimal reproducible example,其他什么都说不出来。
  • &lt;_Z18engine_thread_funcPv+0x9e0&gt; 也相当可疑。拆卸器正在输入一个相当奇怪的地址,应该是engine::getEvents。
  • 问题是它很少重现(一天一次),另一个问题是它在实际上不能发生的地方崩溃很可能是 UB somwhere。
  • 您描述的代码不是问题所在。奇怪的是,其他一些代码表现出未定义的行为,而这恰好会导致您显示的代码中断。根据 Sam 的评论,您需要专注于生成 minimal reproducible example。在这样做的过程中,你要么有一个“啊哈!”时刻并自己解决问题,否则您最终会得到一个其他人可以使用的代码示例来展示您的问题。
  • 我知道 C++ 代码在这里很好,这显然是其他地方的 UB。这很清楚,但是代码很大,我不明白我到底应该寻找什么来找到问题。这就是为什么我试图分析生成的汇编代码以了解这里可能出现的问题,但我看不到原因。这里发生了崩溃,所以其他地方的 UB 肯定影响了这部分代码。

标签: c++ segmentation-fault disassembly armasm


【解决方案1】:

由于引擎不再有效而发生故障。包含引擎的方法可能已被释放 - 即,您的线程内存已消失。因此,engine-getevents 在内存中甚至无效。您的代码中的其他地方发生了一些事情,线程应该已经停止运行 - 并退出。他们没有。这很像一个正在退出的应用程序的回调。

【讨论】:

  • 谢谢!这是要检查的。我将尝试检查这是否真的可行,并为对象释放添加额外的日志记录。谢谢你的想法。
  • 我查看了代码,似乎我之前已经检查过。引擎在创建线程之前在构造函数中创建并在 pthread_join 之后立即删除,因此该线程无法在已删除对象上调用 getEvents。我想知道可能对 getEvents 的调用是从另一个地方发出的?从代码中我看到这是唯一调用它的地方,但是如果由于 UB 而不是另一个函数调用它怎么办?
猜你喜欢
  • 1970-01-01
  • 2021-12-24
  • 1970-01-01
  • 2015-01-03
  • 2019-02-03
  • 2019-06-15
  • 2011-04-13
  • 2014-12-09
  • 1970-01-01
相关资源
最近更新 更多