分类: <span>Debug</span>

部分 Crash 分析记录

想着记录一下平时分析过的 crash dump,作为一个总结和参考。不过因为涉及到公司代码,所以一些细节没法描述太明白,crash 堆栈和对应的源代码也不方便贴出来了。这里先整理了几个比较有代表性的,其他一眼就能看出问题的就不写到这里了。

2023.Q3 ui 代码没有清理 framework api 指针

当用户开启计算机锁屏时自动切断 channel 的策略并触发了锁屏行为后,UI 会调用 api 切断所有 channel,并且立即清理上层保存的数据,但问题就在这个清理上。
原本调用触发切断的 api 后会有一个回调通知,上层是在回调里做清理动作的。但是现在代码中主动调用了一个 channel_mgr.Clear();,这个 Clear() 函数里清理的不完全,相比之下另一个成员函数 Delete(channel_id) 才是做到了彻底清理。同时回调函数里还做了许多额外的清理操作,这里一个 Clear() 把其他操作也都忽略掉了。
其实究其原因就是外部保存了 ChannelItem 的裸指针,但是又存在这样一个 corner case 没有将它清理掉,等 ChannelItem 实例析构后这个指针就野了。关键在于怎么发现这个代码路径。
因为 framework 层在释放前总是会通过回调函数通知 UI 清理,费好大劲排除 framework 没有漏掉的地方后把目标放到了 UI 上。来来回回翻代码后才发现这个地方。


卸载 Dll 过程中隐藏的异常

最近测试时发现代码中一处关于 std::function 函数对象的问题。在卸载某个 Dll(记为 A)的时候会触发该模块中一个单例对象的析构,在它析构内部的 std::function 成员时产生了内存访问异常(0xc0000005)。而触发这个异常的 std::function 其实是复制于另一个 Dll(记为 B),卸载 Dll A 时,Dll B 已经卸载掉了。

此时调用它关联的函数会抛异常容易理解,但这时并没有进行调用操作。原因在于函数对象析构时会调用关联的 _Delete_this 方法释放资源,而这个函数一般定义在 std 的 _Func_impl / _Func_impl_no_alloc 模板类中。所以当实例化 function 时对应删除函数的代码也是属于当前模块里的(也就是 Dll B)。现在 Dll B 已经卸载,那么只要有任何触发相关代码执行的操作都会引发异常。


Appverif 启动后闪退

最近发现 Appverif 打不开了,公司不少同事的电脑上也遇到这个问题。

涉事的 appverif 版本是: 10.0.18362.1
win 10 的版本是 20H2(19042.867)

主要现象就是启动 appverif.exe 时命令行窗口一闪而过,UI 窗口没有创建就退出了。
用 windbg 调了一下,发现 appverif 在初始化 UI 窗口前会去检查并登记注册表 Image File Execution Options 键值下的 exe,而当遍历到其中的 MsSense.exe 时因为没有权限失败了(STATUS_ACCESS_DENIED)

具体堆栈如下:

00 vrfcore!VfCoreOpenImageFileExecutionOptionsKey+0xc4
01 vrfcore!VerifierOpenLayerProperties+0x4b
02 appverifUI!AppVerif::GetVerifiedImages+0xd6
03 appverifUI!StartUI+0x64
04 appverif!wWinMain+0x43e
05 appverif!__wmainCRTStartup+0x153
06 KERNEL32!BaseThreadInitThunk+0x19
07 ntdll!__RtlUserThreadStart+0x2f
08 ntdll!_RtlUserThreadStart+0x1b

VfCoreOpenImageFileExecutionOptionsKey 中会调用 NtOpenKey 打开每个项,而轮到 MsSense.exe 时因为返回 STATUS_ACCESS_DENIED 而导致初始化过程失败,进程退出。
用注册表编辑器打开一看 admin 确实是没有访问权限,那么给它加上就可以了。

可以用 NSudo 启动一个注册表编辑器,给 MsSense.exe 手动加上管理员组的读取权限,这样 Appverif 就恢复正常了。

当然,新的 Appverif 已经修复了这个问题,直接更新到最新版就没问题了。


多网卡设为同一网段导致数据包发送失败

之前处理了一个 bug,软件在一些使用场景下会发不出 udp 包,排查之后发现是调用 sendto() 函数成功,然而使用 wireshark 却抓不到 udp 包。

然后在排查过程中查看 arp 表时,结果意外发现居然有两张网卡是被设置成同一个网段 的 ip 了,摔啊!

于是看了一下路由表,发现果然是未使用的网卡 B(192.168.2.56)的记录在网卡 A(192.168.2.204)之前,而设备是连接在网卡 A 上的。

这就导致通讯时系统会首先使用 B 来发送数据包,而当设备的 mac 地址没有被网卡缓存时,B 会首先发送 arp 广播包获取设备的 mac 地址。当然连在网卡 A 上的设备 C 是没法回应的,然后数据包会因为 B 没有目的 ip 的物理地址而发送失败。

另外当 B 的 arp 失败后会在 B 的 arp 表中生成一个该 ip 地址的记录项,指示该 ip 对应无效的 mac 地址(00-00-00-00-00-00),根据微软的描述,默认情况下该记录项会在15到45秒后刷新。在 B 的记录清除之前再发起通讯请求时系统会选择使用 A 发送 arp 广播,这时设备便能接收数据和正常通讯。否则当 B 中的无效记录被清除后会继续使用 B,通讯依旧会失败。

总结一下就是两张同网段 ip 的网卡,当两张网卡的 arp 表都没有对应的记录时,系统会根据路由表中记录的顺序选择第一个网卡进行通讯(启用、禁用网卡可改变路由表的记录顺序)。如果网卡的 arp 表中缓存了对应的 mac 则会直接使用相应的网卡进行通讯。
当然最好还是不要设为同一个网段……


sse 指令中 xmm 寄存器复制到内存出现异常

在将我的东方弹幕游戏拖到 win10 虚拟机中运行时发现程序一运行就崩溃了,报了内存访问异常(0xc0000005),而且多运行几次现象也会不同,有时是直接崩溃,有时还能坚持到显示完启动画面,但进入游戏时还是会高概率的崩溃 – -b 。

于是挂上 windbg 看一下,发现断在了这里

0012da40 55              push    ebp
0012da41 8bec            mov     ebp,esp
0012da43 8b4508          mov     eax,dword ptr [ebp+8]
0012da46 0f2800          movaps  xmm0,xmmword ptr [eax]
0012da49 0f2901          movaps  xmmword ptr [ecx],xmm0    ; <--- 0xc0000005
0012da4c 0f284010        movaps  xmm0,xmmword ptr [eax+10h]
0012da50 0f294110        movaps  xmmword ptr [ecx+10h],xmm0

Duilib无法连续创建两个模态对话框

调试一个 Duilib 界面程序时遇到一个问题:当在主函数里使用 ShowModal() 显示第一个对话框并关闭对话框后,再一次使用 ShowModal() 显示第二个对话框时第二个对话框便直接关闭退出了。


指针类型的类成员变量突然指向无效内存

最近在查一个Bug,好几天下来一直没有头绪,而且重现条件挺看RP……查了一阵子遇到阻碍,感觉没有什么思路,总之先加了一些日志,顺便在这里记录一下状况。


软件中出现纯虚函数被调用的错误

我的情况是退出软件时偶尔产生这种错误,也算是多线程中的一个使用不当的情况。
简化后的模型如下:


win7在任务栏上固定图标后运行时会出现两个图标的问题

有些应用在往任务栏(win7及以后)上固定图标后,单击图标运行实例时会生成一个新的图标,win7下似乎还能再次固定新的图标。

造成两个图标的原因在于应用使用了自定义的AppUserModelID
win7及以后的任务栏通过AppUserModelID来关联程序的图标,也就是说相同AppUserModelID的进程会在任务栏上共用一个图标。比如资源管理器,当你打开多个资源管理器窗口时,任务栏上始终只有一个对应的图标,鼠标移至图标上时就会显示出所有打开的窗口,也就是所有窗口被分为同一组了。应用程序可以根据实际需要来设置这个ID相同与否来达到控制应用在任务栏上的图标显示,特别是对于一些多进程、多显示窗口的应用用处会比较大。