【发布时间】:2020-06-04 17:45:41
【问题描述】:
我在这里分析一个非常复杂的项目中罕见但一致的崩溃,它是这些“不可能的堆栈跟踪”之一,这意味着:乍一看,一切看起来都很好。
崩溃本身是“抛出未处理的异常:读取访问冲突。这是 nullptr。”。
堆栈当前帧的代码如下所示:
FT6VehiclePathCheckpoint AT6Building::GetVehicleCheckpoint() const
{
FT6VehiclePathCheckpoint targetCheckpoint = FT6VehiclePathCheckpoint();
if (GetBusStopComponent() != nullptr)
{
...
崩溃发生在“if (GetBusStopComponent() != nullptr)”这一行。函数“GetBusStopComponent()”本身是对象成员变量的内联 getter,因此它不会显示在跟踪中。此外,在内存转储中,“this”确实为 0。到目前为止一切顺利。
但是,它正上方的堆栈帧如下所示:
const AT6Building* portalBuildingByLocation = pathFinder != nullptr ? pathFinder->GetPortalBuildingByLocation(startLocation) : GetNearestDrivewayBuilding();
if (portalBuildingByLocation != nullptr)
{
startCheckpoint = portalBuildingByLocation->GetVehicleCheckpoint();
}
(指向“portalBuildingByLocation->GetVehicleCheckpoint”行)。很明显,在调用函数之前有 一个 nullptr-check。那是第一眼^^。
接下来我怀疑“GetVehicleCheckpoint”中的内存损坏导致堆栈跟踪混乱?但那里实际上什么都没有。 “FT6VehiclePathCheckpoint”初始化甚至没有构造函数——只有一堆直接初始化的字段(它们都像 nullptr 和整数文字)..
代码是用 cl.exe 编译的,开启了优化,所以我怀疑我在某个地方得到了一些 UB,编译器依赖于某些东西......
因此,虽然我在阅读汇编代码方面相当菜鸟,但我尝试过。这是 GetVehicleCheckpoint 函数的拆解后的第一部分:
FT6VehiclePathCheckpoint AT6Building::GetVehicleCheckpoint() const
{
00007FF7304714B0 mov qword ptr [rsp+8],rbx
00007FF7304714B5 mov qword ptr [rsp+10h],rsi
00007FF7304714BA push rdi
00007FF7304714BB sub rsp,40h
FT6VehiclePathCheckpoint targetCheckpoint = FT6VehiclePathCheckpoint();
00007FF7304714BF xor eax,eax
00007FF7304714C1 mov qword ptr [rsp+28h],0FFFFFFFFFFFFFFFFh
00007FF7304714CA mov qword ptr [rsp+30h],rax
00007FF7304714CF mov rsi,rcx
if (GetBusStopComponent() != nullptr)
00007FF7304714D2 mov rcx,qword ptr [rcx+5F8h]
在最后一行出现崩溃。好的,如果我理解正确,它试图从 rcx+5F8h 读取一个 qword 并且可能 rcx 为 0? (但如果是这样,错误不应该是“访问冲突,尝试从 0x5f8 读取”吗?好吧,也许 Visual Studio 想要更有帮助..)。
另外,我验证过,是的:BusStopComponent 确实位于类的偏移量 0x5F8..
好的,所以我尝试将 rcx 跟踪到倒数第二个堆栈帧:
const AT6Building* portalBuildingByLocation = pathFinder != nullptr ? pathFinder->GetPortalBuildingByLocation(startLocation) : GetNearestDrivewayBuilding();
00007FF730410F21 lea rdx,[rbp-29h]
00007FF730410F25 mov rcx,r12
00007FF730410F28 call AT6AStarPathfinder::GetPortalBuildingByLocation (07FF73059CD30h)
00007FF730410F2D jmp UT6Agent::HandleAgentArrivedToUseCar+34Ah (07FF730410F4Ah)
00007FF730410F2F test r12,r12
00007FF730410F32 je UT6Agent::HandleAgentArrivedToUseCar+342h (07FF730410F42h)
00007FF730410F34 lea rdx,[rbp-29h]
00007FF730410F38 mov rcx,r12
00007FF730410F3B call AT6AStarPathfinder::GetPortalBuildingByLocation (07FF73059CD30h)
00007FF730410F40 jmp UT6Agent::HandleAgentArrivedToUseCar+34Ah (07FF730410F4Ah)
00007FF730410F42 mov rcx,rsi
00007FF730410F45 call UT6Agent::GetNearestDrivewayBuilding (07FF73040DA50h)
if (portalBuildingByLocation != nullptr)
00007FF730410F4A test rax,rax
00007FF730410F4D je UT6Agent::HandleAgentArrivedToUseCar+36Ch (07FF730410F6Ch)
{
startCheckpoint = portalBuildingByLocation->GetVehicleCheckpoint();
00007FF730410F4F mov rcx,rax
00007FF730410F52 lea rdx,[rbp-9]
00007FF730410F56 call AT6Building::GetVehicleCheckpoint (07FF7304714B0h)
00007FF730410F5B movups xmm0,xmmword ptr [rax]
再次:我对解释程序集不是很有信心,但仅凭目测,我发现 rcx 被设置为 rax 中的任何内容,并且 rax 被正确测试为 0。所以没有花哨的“编译器删除了 nullptr 检查作为优化”。
任何可能导致访问冲突的见解?有什么可疑的东西可以给我线索吗?
干杯,艾米。
【问题讨论】:
-
hm.. 理论:可能是对 GetNearestDrivewayBuilding 的调用扰乱了其堆栈帧的返回地址,并“希望”通过 nullptr-check 恰好调用 GetVehicleCheckpoint?
-
如果这是 Visual Studio,您应该能够看到寄存器值(它们有一个调试窗口)。 rcx 和 rsi 可能很有趣。为地址 rcx 或 rcx+5F8h 打开内存窗口也可能提供信息,因为您可以检查该类的所有成员变量。一个一个地检查它们可能没有效率(考虑到有 5f8h 字节的价值),但是看看它是否看起来一般正确,尤其是在 5f8h 偏移附近。
-
RCX 确实为 0。在第二个堆栈帧中,RSI 为 0x229239886E0(在最低堆栈帧中,在“mov rsi,rcx”之后,RSI 也是 0)。但是这个内存位置似乎不是故障转储的一部分:( - 我得到了所有?? 当直接在内存调试中查看它时(我认为这是因为外部“this”位于堆上并且它是一个“小型故障转储” “?)。或者 RSI 中的值是其他值的偏移量?
-
例如,如果
RelatedPathBuildings[CurrentPath.CurrentSegmentIndex]为空怎么办?虽然我从未这样做过,但您似乎应该能够在 GetVehicleCheckpoint 内执行if (this == nullptr) __debugbreak();。及早发现问题可能会让您在事情被覆盖之前看到更多。 -
@DavidWohlferd:IIRC,在
nullptr上调用成员函数不幸的是 UB,因此允许编译器假定它是非 null 并优化该检查,并在实践中这样做。不过,在未优化的构建中,您可能会因为调试而侥幸。
标签: c++ assembly stack-trace access-violation