一次函数析构时崩溃排查过程

问题场景

  1. 只用某一特定软件版本才能复现,小于该版本或大于该版本的都无法复现

  2. 只用poly sync 40作为HID设备,并在windows系统上,当选中poly 40作为当前麦克风设备时,几乎每次都crash。

  3. 不挑windows机器,用不同的两台windows,装alpha17都能复现。

详细排查过程

崩溃堆栈:

从栈顶看,memmove时,原地址esi = 0x2,这个地址显然不对,做内存拷贝时源地址有问题导致crash了,其他看不出有用信息,下载dmp加载符号解析:

这里可以看出来,是在HIDDeviceImpl::SetFeatureInner函数的局部变量ScopedFuncLogger析构时crash了,这个ScopedFuncLogger析构时原本是要打出exit HIDDeviceImpl::SetFeatureInner, cost xxx这种log的。

照理说,这个HIDDeviceImpl::SetFeatureInner是函数名,是用编译宏__FUNCTION__在构造ScopedFuncLogger对象时传给ScopedFuncLogger构造函数的,不可能有其他人改这个对象,但现在的确是析构时crash了,查看析构代码:

析构时,会访问_moduleName和 _functionName这两个成员变量,那是哪个变量被写坏了呢?

.frame 0b; dv; dt -b this可以查看当时所有的成员变量:

_functionName的内存布局:

_moduleName的内存布局:

可以看到_functionName的ptr刚好为0x2,和出错的内存地址对上了,而且_functionName的长度是MySize = 0x1e, 如果切到.frame 1, dv, 可以看到end - begin 刚好等于0x1e

所以目前可以确定是_functionName这个变量被写坏了。

从log中可以看到: entry HIDDeviceImpl::SetFeatureInner这条log是正常的,那就是执行这一个函数中被改了,可是如何确定是哪里被改了呢?

幸好,这个问题复现的概率比较高,用visual studio attach上去,在函数执行中每一行逐个加断点,定位到了一个地方:

经过多次调试确定:在执行到1时,_functionName还是正常的,但是执行到2的时候,_functionName中char的地址就变成0x2了。

一个奇怪的地方是,如果在1下断点,再走到2时就是正常的,不会crash,必须在2下断点之前,不能有任何断点,那到2时,_functionName就被写坏了。

也就是,断点1和断点2不能同时下?猜测是下断点导致程序暂停,暂停了就不会出现这个问题了。

那怎么确定一定是这里写坏了呢?

此时,可以借助windbg的下断点后,可以打印这一块附近的内存,然后再继续执行,这样就既可以看出内存的变化,又能保证程序一直在执行:

断点1:bp HIDDeviceImpl::WriteDataToDevice+0x375 ".printf \"WaitFor\";dd (ebp-0x4c) L256;g"

断点2:bp HIDDeviceImpl::WriteDataToDevice+0x45A ".printf \"GetOver\";dd eax L256;g"

通过.frame 11; dv, 得知_functionName的地址是0x0eefef38:

可以看出,在WaitForSignalObject前后,_functionName确实被改写!

WriteDataToDevice这个函数是在做写入HID Report操作,照理说这里不可能改_functionName的值,出现这种问题,只能怀疑为设备问题,设备在WriteFile返回后,在很短的时间内,还在操作附近的内存地址,所以才会出现被写坏,而稍微停一下,就不会出这个问题了。

该问题在某一个新的软件包上又出现了,还是用poly 40,切换为该设备的时候崩溃:

可以看出来是WriteDataToDevice已经出来了,把返回值和m_outputReportByteLength作比较时出了问题

byteview_bytertc!HIDDeviceImpl::SetFeatureInner+0x7c2 [C:\18573\bv-falcon\byteview-bytertc\src\hid\win\HIDDeviceWinImpl.cpp @ 282]:

282 00007ffc`71e4bfe2 448b4620 mov r8d,dword ptr [rsi+20h]

282 00007ffc`71e4bfe6 488b5628 mov rdx,qword ptr [rsi+28h]

282 00007ffc`71e4bfea 488bce mov rcx,rsi

282 00007ffc`71e4bfed e80e020000 call byteview_bytertc!HIDDeviceImpl::WriteDataToDevice (00007ffc`71e4c200)

282 00007ffc`71e4bff2 3b4620 cmp eax,dword ptr [rsi+20h]

282 00007ffc`71e4bff5 0f94c3 sete bl

出问题的汇编代码是cmp eax,dword ptr [rsi+20h],查看此时的寄存器:

rax=0000000000000002 rbx=0000015ea159f780 rcx=0000015e87740000

rdx=0000015e87740000 rsi=0000000000000002 rdi=0000000000000000

rip=00007ffc71e4bff2 rsp=0000003aaecfede0 rbp=0000003aaecfeee0

r8=0000015e87625580 r9=0000000000000001 r10=0000015e87620000

r11=0000003aaecfeb80 r12=0000000000000008 r13=0000000000000000

r14=0000000000000000 r15=0000000000000000

根据x64汇编约定,eax是函数返回值,所以dword ptr [rsi+20h]应该就是m_outputReportByteLength,而看寻址方式,rsi应该保存的是this指针,也就是HIDDeviceImpl的地址,+20h是找它的成员变量。

rsi = 0x2是有问题的,所以出现了异常。

根据win-x64约定,rsi属于callee save寄存器,也就是说,call完了之后,这个值不会变,但这里确实是变了,看这段汇编代码,call前后,这个rsi应该都是保存this指针的。

查看WriteDataToDevice的汇编代码,发现的确是有push和pop rsi的,那为什么会变呢?

联系到第一个crash的排查过程,就清楚了,应该还是栈上的内存被破坏了,所以pop rsi时,就不再是push时的那个值。

解决方案

目前只能用work around去解决,把ol放在堆上,避免和funtionName在内存布局上靠的太近,并且把ol保存到类的成员变量一个map中,持久化ol,防止ol过早的析构:

这样改完后,再也没有出现过崩溃。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值