C++ 无法追踪?尽管已检查是否存在此=nullptr的访问冲突
我正在分析一个相当复杂的项目中罕见但持续的崩溃,这是一个“不可能的堆栈跟踪”,意思是:第一眼和第二眼,一切看起来都很好 崩溃本身是“引发未处理的异常:读取访问冲突。这是nullptr。” 堆栈当前帧处的代码如下所示:C++ 无法追踪?尽管已检查是否存在此=nullptr的访问冲突,c++,assembly,stack-trace,access-violation,C++,Assembly,Stack Trace,Access Violation,我正在分析一个相当复杂的项目中罕见但持续的崩溃,这是一个“不可能的堆栈跟踪”,意思是:第一眼和第二眼,一切看起来都很好 崩溃本身是“引发未处理的异常:读取访问冲突。这是nullptr。” 堆栈当前帧处的代码如下所示: FT6VehiclePathCheckpoint AT6Building::GetVehicleCheckpoint() const { FT6VehiclePathCheckpoint targetCheckpoint = FT6VehiclePathCheckpoint
FT6VehiclePathCheckpoint AT6Building::GetVehicleCheckpoint() const
{
FT6VehiclePathCheckpoint targetCheckpoint = FT6VehiclePathCheckpoint();
if (GetBusStopComponent() != nullptr)
{
...
const AT6Building* portalBuildingByLocation = pathFinder != nullptr ? pathFinder->GetPortalBuildingByLocation(startLocation) : GetNearestDrivewayBuilding();
if (portalBuildingByLocation != nullptr)
{
startCheckpoint = portalBuildingByLocation->GetVehicleCheckpoint();
}
崩溃在“if(GetBusStopComponent()!=nullptr)”行中。函数“GetBusStopComponent()”本身是对象成员变量的内联getter,因此它不会显示在跟踪中。另外,在内存转储中,“this”实际上是0。到目前为止还不错
但是,其正上方的堆栈帧如下所示:
FT6VehiclePathCheckpoint AT6Building::GetVehicleCheckpoint() const
{
FT6VehiclePathCheckpoint targetCheckpoint = FT6VehiclePathCheckpoint();
if (GetBusStopComponent() != nullptr)
{
...
const AT6Building* portalBuildingByLocation = pathFinder != nullptr ? pathFinder->GetPortalBuildingByLocation(startLocation) : GetNearestDrivewayBuilding();
if (portalBuildingByLocation != nullptr)
{
startCheckpoint = portalBuildingByLocation->GetVehicleCheckpoint();
}
(指向行“portalBuildingByLocation->GetVehicleCheckpoint”)。很明显,在调用函数之前有一个nullptr检查。这是第一眼
接下来,我怀疑“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检查”这一说法并不奇怪
有什么见解会导致访问违规吗?有什么可疑的东西能给我线索吗
干杯,伊米。明白了!(或者更确切地说是大卫·沃尔弗德发现的)
“直接在上面的堆栈帧”有误导性,因为代码在同一个函数中多次重复使用此调用(在不同的if分支中)。当查看局部变量时,那里的分支路径实际上是不可能的,我将问题确定为另一个数组中的nullptr
感谢大卫·沃尔弗德!死定了!:) 嗯。。理论:对GetNearestDrivewayBuilding的调用是否会扰乱其堆栈帧的返回地址,并通过nullptr检查“希望”恰好调用GetVehicleCheckpoint?如果这是Visual Studio,您应该能够看到寄存器值(它们有一个调试窗口)。rcx和rsi可能很有趣。为地址rcx或rcx+5F8h打开一个内存窗口也可能提供信息,因为您可以检查该类的所有成员变量。逐个检查它们可能没有效果(考虑到有5f8h字节的价值),但要看看它是否通常是正确的,尤其是在5f8h偏移量附近。RCX确实是0。在第二个堆栈帧中,RSI为0x229239866E0(在最低堆栈帧中,在“mov RSI,rcx”之后,RSI也为0)。但是这个内存位置似乎不是崩溃转储的一部分:(-I get all??当在内存调试中直接查看它时(我假设这是因为外部的“this”位于堆上,并且它是一个“小崩溃转储”)。或者RSI中的值是对某个其他值的偏移量吗?例如,
RelatedPathBuildings[CurrentPath.CurrentSegmentIndex]
为空?虽然我从未这样做过,但如果(this==nullptr)\uu debugbreak()在GetVehicleCheckpoint内。及早发现问题可能会让您在被覆盖之前看到更多内容。@DavidWohlferd:IIRC,在nullptr
上调用成员函数很不幸是UB,因此允许编译器假设它为非null并优化掉该检查,并在实践中这样做。在未优化的构建中,您可以不过,我们还是可以通过调试来解决这个问题。