在一些调用虚拟成员函数的代码中出现了一些奇怪的段错误。 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;
...
}
发生问题时,我能够获取coredump并查看汇编代码:
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
通过执行汇编摘录I中的计算,可以确认汇编代码和它使用的数据与预期的虚函数调用匹配并以正确的数据开头:
使用该对象的指针
(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
到目前为止的结论: 通过逐步执行汇编代码,验证了正确数据和指令的使用。
请随时指出我的推理中的任何错误。
但是寄存器rax中的值仍为零,而不是预期的0x7fbb070c1ae0
处理器型号为Intel(R)Core(TM)i7-4770 CPU @ 3.40GHz
谢谢!
更新:
我找到了$ RIP标记
0x55cd3ef0370a MyNamespace::GenericDevice::updateValue()+114 shlb 0x48(%rsi)
gdb显示的程序集在滚动后似乎已更改。这就是为什么我在第一次尝试中没有看到标记的原因。启动gdb并输入布局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。
答案 0 :(得分:1)
@Jester指出,您的其他寄存器值与您说崩溃发生的代码不匹配。
发生问题时,我能够获得coredump并查看汇编代码:...段错误发生在汇编摘录的最后一行。
你怎么知道的? where
的输出是什么?
通常,应该有一个当前的$RIP
标记,如下所示:
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
=> 0x55cd3ef0370e GenericDevice::updateValue()+118 ....
您看到那个标记了吗?
否则,您的崩溃可能发生在其他地方(但是很好地分析了数据)。
如果您确实看到了标记,则其他细节(例如 exact 处理器的品牌和型号)可能很重要(例如,请参见this问答)。
答案 1 :(得分:1)
在执行被调用函数之前,调用语句将返回地址压入堆栈。资源 Intel® 64 and IA-32 Architectures Software Developer’s Manual第225页。 另一个线程在同一堆栈上保留对变量的无效引用,并将其递减,该变量是存储的返回地址。 基本上,该线程应该持有对计数器的引用,该计数器对GenericDevice :: updateValue()仍有多少待处理的作业进行计数。超时后,计数器将超出范围,但正在执行的线程仍保留现在无效的引用。超时很少发生,并且仅在读取设备而不是模型的情况下发生。因此,存储在堆栈中的返回地址被偶尔破坏了。