【问题标题】:how can i debug a C++ delete call on android NDK?如何在 android NDK 上调试 C++ 删除调用?
【发布时间】:2011-03-23 10:37:01
【问题描述】:

我有一个由 Java 层管理的 Android C++ 应用程序。在这段代码中,我使用了一个旧的物理库(托卡马克),我几乎什么都不做,我像这样创建和删除模拟器:

static neSimulator *gSim;
neV3 gravity; gravity.Set(0.0f, -10.f, 0.0f);
neSimulatorSizeInfo sizeInfo;
sizeInfo.rigidBodiesCount = 1;
sizeInfo.animatedBodiesCount = 1;
sizeInfo.geometriesCount = 2;
sizeInfo.overlappedPairsCount = 2;
gSim = neSimulator::CreateSimulator(sizeInfo, NULL, &gravity);

然后销毁它:

neSimulator::CreateSimulator(gSim);

这可行,当我开始添加几何图形时出现问题:

neV3 ballPos;
rgdBall = gSim->CreateRigidBody();
neGeometry *geoBall = rgdBall->AddGeometry();
geoBall->SetSphereDiameter(1.5f);
rgdBall->UpdateBoundingInfo();
rgdBall->SetMass(2.0f);
rgdBall->SetInertiaTensor(neSphereInertiaTensor(1.5f, 2.0f));
ballPos.Set(0.0f, 5.0f, 0.0f);
rgdBall->SetPos(ballPos);

在这种情况下,当我调用 destroy(并且我只调用一次)时,我得到一个 SIGSEGV(空指针)deadbaad。

我已经调试了析构函数方法的所有日志语句,并且析构函数中的代码完成到最后。于是就有了这段代码:

void neSimulator::DestroySimulator(neSimulator * sim)
{
    __android_log_print(ANDROID_LOG_INFO, "TOKAMAK", "Before cast");
    neFixedTimeStepSimulator * s = reinterpret_cast<neFixedTimeStepSimulator *>(sim);
    __android_log_print(ANDROID_LOG_INFO, "TOKAMAK", "After cast");
    __android_log_print(ANDROID_LOG_INFO, "TOKAMAK", "Before delete");
    delete s;
    __android_log_print(ANDROID_LOG_INFO, "TOKAMAK", "After delete");
}

所以我记录了析构函数:

neFixedTimeStepSimulator::~neFixedTimeStepSimulator()
{
    FreeAllBodies();

    if (perf)
        delete perf;
        __android_log_print(ANDROID_LOG_INFO, "TOKAMAK", "dtor complete");
}

让我感到困惑的是,我在日志上看到了 dtor 完成消息,但没有看到删除后消息和 SIGSEGV 错误。

我怎样才能更好地调查它?

[进一步调查后的更多信息]

所以我使用 addr2line 工具调查堆栈跟踪并将错误跟踪到默认内存分配器。所以我将日志记录添加到所有 alloc 和 free 调用中:

03-23 13:31:14.617: INFO/neAllocatorDefault(326): malloc 0x1b3fd8 size 2292
03-23 13:31:14.617: INFO/neAllocatorDefault(326): malloc 0x1b48d0 size 488
03-23 13:31:14.627: INFO/neAllocatorDefault(326): malloc 0x44ae3008 size 114404
03-23 13:31:14.627: INFO/neAllocatorDefault(326): malloc 0x1a58b8 size 8
03-23 13:31:14.627: INFO/neAllocatorDefault(326): malloc 0x1b4ac0 size 800
03-23 13:31:14.627: INFO/neAllocatorDefault(326): malloc 0x1b4de8 size 416
03-23 13:31:14.627: INFO/neAllocatorDefault(326): malloc 0x1b4f90 size 836
03-23 13:31:14.627: INFO/neAllocatorDefault(326): malloc 0x1aca10 size 44
03-23 13:31:14.627: INFO/neAllocatorDefault(326): malloc 0x1b52d8 size 2500
03-23 13:31:14.627: INFO/neAllocatorDefault(326): malloc 0x1b5ca0 size 2500
03-23 13:31:14.627: INFO/neAllocatorDefault(326): malloc 0x1b6668 size 2500
03-23 13:31:14.637: INFO/neAllocatorDefault(326): malloc 0x1b7030 size 400
03-23 13:31:14.637: INFO/neAllocatorDefault(326): malloc 0x1b71c8 size 800
03-23 13:31:14.637: INFO/neAllocatorDefault(326): malloc 0x424ed008 size 72404
03-23 13:31:14.637: INFO/neAllocatorDefault(326): malloc 0x1b74f0 size 4004
03-23 13:31:14.637: INFO/neAllocatorDefault(326): malloc 0x1b8498 size 2044
03-23 13:31:14.637: INFO/neAllocatorDefault(326): malloc 0x1b8c98 size 6044
03-23 13:31:14.637: INFO/neAllocatorDefault(326): malloc 0x1ba438 size 5004
03-23 13:31:14.637: INFO/neAllocatorDefault(326): malloc 0x1bb7c8 size 11204
03-23 13:31:14.637: INFO/neAllocatorDefault(326): malloc 0x1be390 size 340
03-23 13:31:14.637: INFO/neAllocatorDefault(326): malloc 0x1be4e8 size 4000
03-23 13:31:14.647: INFO/neAllocatorDefault(326): malloc 0x1bf490 size 4000
03-23 13:31:14.647: INFO/neAllocatorDefault(326): malloc 0x1c0438 size 38800
03-23 13:31:14.647: INFO/neAllocatorDefault(326): malloc 0x1c9bd0 size 38800

03-23 13:31:19.508: INFO/neAllocatorDefault(326): free 0x1b71c8
03-23 13:31:19.508: INFO/neAllocatorDefault(326): free 0x1b7030
03-23 13:31:19.508: INFO/neAllocatorDefault(326): free 0x1b6668
03-23 13:31:19.508: INFO/neAllocatorDefault(326): free 0x1b5ca0
03-23 13:31:19.508: INFO/neAllocatorDefault(326): free 0x1b52d8
03-23 13:31:19.508: INFO/neAllocatorDefault(326): free 0x1aca10
03-23 13:31:19.508: INFO/neAllocatorDefault(326): free 0x1b4f90
03-23 13:31:19.508: INFO/neAllocatorDefault(326): free 0x1b4de8
03-23 13:31:19.508: INFO/neAllocatorDefault(326): free 0x1b4ac0
03-23 13:31:19.508: INFO/neAllocatorDefault(326): free 0x1a58b8
03-23 13:31:19.508: INFO/neAllocatorDefault(326): free 0x44ae3008
03-23 13:31:19.508: INFO/neAllocatorDefault(326): free 0x1b48d0

所以 SIGSEGV 发生在尝试释放 0x1b48d0 时,有趣的是有一个先前的 malloc 返回了该指针并且没有先前的空闲。我现在更疑惑了……

【问题讨论】:

    标签: c++ android memory android-ndk


    【解决方案1】:

    我怀疑DestroySimulator 中的reinterpret_cast 是错误的。该对象可能使用与实际不同的类型被删除,这会调用错误的析构函数,进而损坏分配器元数据并导致free 崩溃。

    删除铸造对象时需要注意两点:

    1. 要调用的析构函数是根据delete 中表达式的static 类型选择的。因此类型必须与传递给 new 的类型相同,或者只有当对象具有虚拟析构函数时,才可能是传递给 new 的类型的基类型。
    2. 派生类指针和基类指针之间的转换在数值上可能不等价。 static_cast 知道如何调整指针,但是通过使用 reinterpret_cast 你 明确告诉编译器不要调整它,所以指针可能是错误的(只有在涉及多重继承时才会发生,但它可能在库的某个深处)。

    在 C++ 中使用 reinterpret_cast 几乎总是错误的。

    【讨论】:

    • 不,reinterpret_cast 是正确的,因为这两种类型是相关的,lib 返回的是来自 neFixedTimeStepSimulator 的子类。进行一些验证测试表明两个对象的完整性是正确的。
    • @Paulo: reinterpret_cast 如果它是子类,则特别不正确。如果类型相关,您应该至少使用static_cast。事实上,如果它返回声明类型的子类,它可能是多态的(具有虚拟方法,否则将无用),您可以使用dynamic_cast 在运行时检查对象是否确实属于给定类型。
    【解决方案2】:

    发现问题。 Tokamak 使用一种特殊的分配方法来避免异常(我认为)。对于每个新的对象实例化,它会执行以下操作:

    // assume you have a class MyObject
    // and the default allocator is the C malloc/free functions:
    MyObject *obj = new (malloc(sizeof(MyObject))) MyObject;
    ... do something with the obj ...
    free(obj);
    

    所以这对对象来说工作得很好,问题在于数组。使用数组,托卡马克代码在做:

    MyObject[] *obj = new (malloc(sizeof(MyObject) * elements + 4)) MyObject[];
    ... do something with the obj ...
    free(obj); // Kaboom!!!
    

    这里的事情似乎是 ndk 编译器使用超过 4 字节(32 位 int)作为数组索引,如果我将代码扩展为 +8,那么一切正常。

    【讨论】:

      猜你喜欢
      • 2011-10-12
      • 1970-01-01
      • 1970-01-01
      • 2023-03-08
      • 1970-01-01
      • 1970-01-01
      • 2022-01-03
      • 2023-03-24
      • 1970-01-01
      相关资源
      最近更新 更多