8

在使用 Apple LLVM Compiler 3.0 并使用 -O3 编译时,我发现了一个不寻常的 NSCoder 崩溃程序。它只会在设备上崩溃。我测试了运行 iOS 5 的 iPhone 4、运行 iOS 5 的 iPad 2 和运行 iOS 4 的 iPad 1。所有崩溃都相同。这是代码的相关部分:

-(id)initWithCoder:(NSCoder*)decoder
{
    if (![super init])
    {
        return nil;
    }

    NSUInteger length = 0;

    uint8_t* data = (uint8_t*)[decoder decodeBytesForKey:BBKey returnedLength:&length];

    m_value = *(BBPointI32*)data;

    return self;
}

BBPointI32 是这样的:

typedef struct
{
    NSInteger x;
    NSInteger y;
}
BBPointI32;

取消引用EXC_BAD_ACCESS时会发生这种情况。data不是空指针问题。如果我附加 GDB,我可以看到长度为 8,sizeof(BBPointI) 也是 8,并且数据是正确的。

如果我查看反汇编,崩溃发生在:

ldrd    r2, r3, [r0]

看起来不错。r0 包含 0xb546e,即data. 当我检查该内存时,我可以看到它包含我期望的数据。对于任何感兴趣的人,r2 包含 72(不确定那是什么),而 r3 包含 8(很可能是 的值length)。

任何人都可以对这个问题有所了解吗?

4

4 回答 4

10

ldrd 需要地址为 8 字节对齐。*(BBPointI32 *)data 习惯用法不安全,因为数据不是 8 字节对齐的。改为使用 memcpy 将字节放入结构中。

于 2011-10-11T17:10:27.263 回答
4

你在用ARC吗?如果是这样,我认为问题在于编译器decoder在调用后可以自由释放decodeBytesForKey:(因此释放返回值指向的缓冲区)。

这与垃圾收集具有相同的内部指针问题。你可以CFRetain/CFRelease你的解码器来延长它的生命周期,或者只是[decoder self]在方法的后面添加一个让它保持活跃直到那个时候。

我怀疑你也可以通过注释来解决这个问题decoder__attribute__((objc_precise_lifetime))但我对该属性的理解有些模糊。

于 2011-10-11T16:44:36.813 回答
3

您的示例留下了许多变量,需要任何潜在的帮助者质疑。例如:如果这个 unarchiver 有什么奇怪的地方怎么办?内存管理是否正确?

我能够重现您所看到的崩溃,并且可以确认它仅在启用 -O3 时发生,而不是在选择例如 None 进行优化时发生。这是崩溃代码的减少,它消除了外部变量,例如编码器的内存管理等。请注意,下面的代码有意保留所有对象,以消除崩溃与意外过度释放或侧面有关的可能性-正如安迪在另一个答案中所建议的那样,使用 ARC 的效果:

typedef struct
{
    NSInteger x;
    NSInteger y;
}
BBPointI32;

- (void) testDecoding
{
    NSString* myKey = @"Testing";

    // First get an coder with bytes in it
    NSMutableData* myData = [[NSMutableData data] retain];
    NSKeyedArchiver* myCoder = [[NSKeyedArchiver alloc] initForWritingWithMutableData:myData];

    BBPointI32 encodedStruct = {1234, 5678};
    [myCoder encodeBytes:(const uint8_t *)&encodedStruct length:sizeof(encodedStruct) forKey:myKey];
    [myCoder finishEncoding];

    // Now decode it
    BBPointI32 decodedStruct;
    NSUInteger decodedLength = 0;
    NSKeyedUnarchiver* myDecoder = [[NSKeyedUnarchiver alloc] initForReadingWithData:myData];
    uint8_t* data = (uint8_t*)[myDecoder decodeBytesForKey:myKey returnedLength:&decodedLength];
    decodedStruct = *(BBPointI32*)data;
    NSLog(@"Got decoded struct with x = %ld, y = %ld, length = %lu", decodedStruct.x, decodedStruct.y, decodedLength);
}

- (void)applicationDidFinishLaunching:(UIApplication *)application {    
    NSLog(@"Testing decoding");
    [self testDecoding];
}

我认为这对问题提供了更简洁的描述,任何想要帮助的人都可以将其用作深入研究的基础。到目前为止,我的预感是这是 LLVM 3.0 中的一个优化错误,但也许其他人会有更好的理论到底是怎么回事。

您在问题中没有提到但我在设备崩溃中注意到的一点是,失败伴随着提到 EXC_ARM_DA_ALIGN 错误作为错误访问异常的原因。我在 Google 上搜索了一篇博客文章,该文章似乎暗示了相同的症状,并且可能导致您在此处看到的崩溃:

http://www.galloway.me.uk/2010/10/arm-hacking-exc_arm_da_align-exception/

事实上,通过改变上面的行

decodedStruct = *(BBPointI32*)data;

memcpy(&decodedStruct, data, sizeof(decodedStruct));

崩溃行为似乎得到缓解,代码表现如预期。

于 2011-10-11T17:01:21.943 回答
1

我到了这个线程,谷歌搜索“EXC_ARM_DA_ALIGN”和“EXC_BAD_ACCESS”。其他答案都没有帮助我,因为这个错误的出现是因为一些相对简单的事情。我曾写过:

theArray = [[NSArray alloc] initWithObjects:@"first", @"second", @"third",
                        @"fourth", @"fifth", "sixth", nil];

即我在字符串文字前留下了一个@。把它放回去解决了这个错误。

于 2012-11-21T21:14:53.447 回答