【发布时间】:2019-05-04 21:51:25
【问题描述】:
在一些调用虚拟成员函数的代码中偶尔会出现一些奇怪的段错误。 Segfault 大约平均在 30k 次调用中发生一次。
我正在使用虚拟方法来实现模板方法模式。
它出现的代码行是第一行
GenericDevice::updateValue()
{
...
double tmpValue=getValue();
Value=tmpValue;
...
}
与
class GenericDevice
{
public:
void updateValue();
void print(string& result);
...
protected:
virtual double getValue()const=0;
...
private:
std::atomic<double> Value;
...
}
稍后通过在运行时加载动态库来提供一个类 GenericDevice
class SpecializedDeviced : public
{
...
virtual double getValue()const final;
...
}
当问题发生时,我能够获得一个核心转储,并查看了汇编代码:
0x55cd3ef036f4 GenericDevice::updateValue()+92 mov -0x38(%rbp),%rax
0x55cd3ef036f8 GenericDevice::updateValue()+96 mov (%rax),%rax
0x55cd3ef036fb GenericDevice::updateValue()+99 add $0x40,%rax
0x55cd3ef036ff GenericDevice::updateValue()+103 mov (%rax),%rax
0x55cd3ef03702 GenericDevice::updateValue()+106 mov -0x38(%rbp),%rdx
0x55cd3ef03706 GenericDevice::updateValue()+110 mov %rdx,%rdi
0x55cd3ef03709 GenericDevice::updateValue()+113 callq *%rax
0x55cd3ef0370b <GenericDevice::updateValue()+115> movq %xmm0,%rax
0x55cd3ef03710 <GenericDevice::updateValue()+120> mov %rax,-0x28(%rbp)
0x55cd3ef03714 <GenericDevice::updateValue()+124> mov -0x38(%rbp),%rax
0x55cd3ef03718 <GenericDevice::updateValue()+128> lea 0x38(%rax),%rdx
0x55cd3ef0371c <GenericDevice::updateValue()+132> mov -0x28(%rbp),%rax
0x55cd3ef03720 <GenericDevice::updateValue()+136> mov %rax,-0x40(%rbp)
0x55cd3ef03724 <GenericDevice::updateValue()+140> movsd -0x40(%rbp),%xmm0
段错误预计发生在 0x55cd3ef03709 GenericDevice::updateValue()+113。
where
#0 0x000055cd3ef0370a in MyNamespace::GenericDevice::updateValue (this=0x55cd40586698) at ../src/GenericDevice.cpp:22
#1 0x000055cd3ef038d2 in MyNamespace::GenericDevice::print (this=0x55cd40586698,result="REDACTED"...) at ../src/GenericDevice.cpp:50
...
GenericDevice::updateValue() 函数按预期调用
<GenericDevice::print(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&)+301> callq 0x55cd3ef03698 <GenericDevice::updateValue()>
将 rax 设置为 0x0 的原因。
Register group: general
rax 0x0 0
rbx 0x5c01b8a2 1543616674
rcx 0x2 2
rdx 0x28 40
rsi 0x2 2
rdi 0x55cd40586630 94340036191792
rbp 0x7ffe39086e60 0x7ffe39086e60
rsp 0x7ffe39086e20 0x7ffe39086e20
r8 0x7fbb06e7e8a0 140441251473568
r9 0x3 3
r10 0x33 51
r11 0x206 518
r12 0x55cd3ef19438 94340012676152
r13 0x7ffe39089010 140729855283216
r14 0x0 0
r15 0x0 0
rip 0x55cd3ef0370a 0x55cd3ef0370a<GenericDevice::updateValue()+114> eflags 0x10206 [ PF IF RF ]
cs 0x33 51
ss 0x2b 43
ds 0x0 0
es 0x0 0
fs 0x0 0
gs 0x0 0
通过执行汇编摘录中的计算,我能够确认汇编代码及其使用的数据与预期的虚函数调用相匹配,并以正确的数据开头:
-
对象的this指针被使用
(gdb) x /g $rbp-0x38 0x7ffe39086e28: 0x000055cd40586698 (gdb) p this $1 = (GenericDevice * const) 0x55cd40586698 -
指向 vtable 的指针正确(*this 的第一个元素)
(gdb) x 0x000055cd40586698 0x55cd40586698: 0x00007fbb070c1aa0 (gdb) info vtbl this vtable for 'GenericDevice' @ 0x7fbb070c1aa0 (subobject @ 0x55cd40586698): -
vtable 包含我们正在寻找的方法的地址。
(gdb) info vtbl this vtable for 'GenericDevice' @ 0x7fbb070c1aa0 (subobject @ 0x55cd40586698): ... [8]: 0x7fbb06e7bf50 non-virtual thunk to MyNamespace::SpecializedDevice::getValue() const. -
使用了正确的 vtable 偏移量
(gdb) x 0x00007fbb070c1aa0+0x40 0x7fbb070c1ae0 <_ZTVN12MyNamespace11SpecializedDeviceE+168>: 0x00007fbb06e7bf50
目前的结论: 通过逐步检查汇编代码,对正确数据和指令的使用进行了验证。
- 使用了正确的数据:可以排除内存损坏。
- 汇编指令似乎正确:可以排除编码/编译错误
- vtable 看起来没问题:可以排除在运行时加载库时的错误:函数通常可以正常运行数万次。
请随时指出我推理中的任何错误。
但寄存器 rax 中的值仍然为零,而不是预期的 0x7fbb070c1ae0
- 这是否表明一个(很少使用的)cpu 内核出现硬件错误? 将解释罕见和随机发生,但我预计其他程序和操作系统也会出现问题。
处理器型号为 Intel(R) Core(TM) i7-4770 CPU @ 3.40GHz
提前致谢!
更新:
我找到了 $RIP 标记0x55cd3ef0370a MyNamespace::GenericDevice::updateValue()+114 shlb 0x48(%rsi)
gdb 显示的程序集似乎在滚动后发生了变化。这就是为什么我在第一次尝试时没有看到标记。启动 gdb 并输入 layout asm 后,我得到:
>0x55cd3ef0370a <MyNamespace::GenericDevicer::updateValue()+114> shlb 0x48(%rsi)
0x55cd3ef0370d <MyNamespace::GenericDevicer::updateValue()+117> movd %mm0,%eax
0x55cd3ef03710 <MyNamespace::GenericDevicer::updateValue()+120> mov %rax,-0x28(%rbp)
0x55cd3ef03714 <MyNamespace::GenericDevicer::updateValue()+124> mov -0x38(%rbp),%rax
0x55cd3ef03718 <MyNamespace::GenericDevicer::updateValue()+128> lea 0x38(%rax),%rdx
0x55cd3ef0371c <MyNamespace::GenericDevicer::updateValue()+132> mov -0x28(%rbp),%rax
0x55cd3ef03720 <MyNamespace::GenericDevicer::updateValue()+136> mov %rax,-0x40(%rbp)
0x55cd3ef03724 <MyNamespace::GenericDevicer::updateValue()+140> movsd -0x40(%rbp),%xmm0
...
在 gdb 中滚动 ams 后,我得到了原始问题中发布的代码。原始问题中的代码与可执行文件中的代码相匹配。上面发布的代码确实与可执行文件有部分偏差。
shlb 指令对我来说毫无意义。甚至找不到说明 Intel® 64 and IA-32 Architectures Software Developer’s Manual。 最接近的匹配是 shl。
【问题讨论】:
-
如果我们可以相信您的寄存器转储
rdx和rdi也是错误的,更重要的是,即使刚刚执行了mov %rdx,%rdi,它们也不相等。要么你的寄存器转储不是来自那个地方,要么发生了一些非常奇怪的事情。 -
您的函数作为非 void 返回不是吗?返回类型是什么,程序集看起来很奇怪。
-
rip 0x55cd3ef0370a非常错误,在指令中间。那是您的错误的实际原因,执行来自其他地方并击中了该指令的中间。 -
能否请您澄清一下“执行来自其他地方”?
-
是的。这可能正确执行,但是当该函数尝试返回时,它使用了一个损坏的地址,因此继续在
call的中间而不是在下面的指令中。
标签: c++ assembly gdb cpu-registers vtable