取消引用 NSCoder 的decodeBytesForKey 返回的指针时,iOS 设备崩溃

发布于 2024-12-09 10:02:47 字数 923 浏览 0 评论 0 原文

当使用 Apple LLVM 编译器 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;

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

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

ldrd    r2, r3, [r0]

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

谁能解释一下这个问题?

I've found an unusual crasher with NSCoder when using the Apple LLVM Compiler 3.0 and compiled with -O3. It only crashes on devices. I've tested an iPhone 4 running iOS 5, an iPad 2 running iOS 5 and an iPad 1 running iOS 4. All crash identically. Here's the relevant section of code:

-(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;
}

And here's what a BBPointI32 is:

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

The EXC_BAD_ACCESS happens when data is dereferenced. This is not a null pointer issue. If I attach GDB, I can see that length is 8, sizeof(BBPointI) is also 8 and the data is correct.

If I look at the disassembly, the crash is happening on:

ldrd    r2, r3, [r0]

Which looks fine. r0 contains 0xb546e, which is the address of data. When I inspect that memory, I can see that it contains the data I expect. For anyone who's interested, r2 contains 72 (not sure what that is) and r3 contains 8 (most probably the value of length).

Can anyone shed some light on this issue?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

泼猴你往哪里跑 2024-12-16 10:02:47

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

ldrd needs the address to be 8-byte aligned. The *(BBPointI32 *)data idiom is not safe since data is not 8-byte aligned. Use memcpy to get the bytes into the struct instead.

胡渣熟男 2024-12-16 10:02:47

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

这是相同的 内部指针问题垃圾收集有。您可以 CFRetain/CFRelease 解码器来延长其生命周期,或者只需稍后在方法中添加 [decoder self] 即可使其保持活动状态直到那时。

我怀疑您也可以通过用 __attribute__((objc_precise_lifetime)) 注释 decoder 来解决这个问题,但是我对 该属性有点模糊。

Are you using ARC? If so, I believe the issue is that the compiler is free to release decoder after the decodeBytesForKey: call (hence releasing the buffer to which the return value pointed).

It's the same interior pointer issue garbage collection has. You can CFRetain/CFRelease your decoder to extend its lifetime, or else just add a [decoder self] later in the method to keep it alive until that point.

I suspect you might also be able to solve this problem by annotating decoder with __attribute__((objc_precise_lifetime)), but my understanding of that attribute is somewhat hazy.

时光暖心i 2024-12-16 10:02:47

你的例子留下了很多变量供任何潜在的帮助者质疑。例如:如果这个解档器有一些奇怪的东西怎么办?内存管理是否正确?

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

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 错误的提及,作为错误访问异常的原因。我在谷歌上搜索了一篇博客文章,它似乎暗示了相同的症状,并且可能导致崩溃,正如您在这里看到的那样:

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

事实上,通过将上面的行更改

decodedStruct = *(BBPointI32*)data;

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

崩溃行为似乎得到了缓解,并且代码的行为符合预期。

Your example leaves a lot of variables to be questioned by any potential helper. For example: what if there's something funky with this unarchiver? Is memory being managed correctly?

I was able to reproduce the crash you're seeing, and can confirm it only occurs when -O3 is enabled, and not when e.g. None is selected for optimization. Here is a reduction of the crashing code that eliminates outside variables such as memory management of the coders, etc. Note the code below purposefully retains all objects to eliminate the possibility that the crash is related to an accidental over-release, or a side-effect of using ARC, as suggested by Andy in another answer:

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];
}

I think this gives a more succinct description of the problem that anybody who wants to help can use as a basis for diving in. My hunch thus far is this is an optimization bug in LLVM 3.0, but maybe somebody else will have a better theory about what is going on.

A point that you didn't mention in your question, but which I notice in the crash on my device, is the failure is accompanied by mention of a EXC_ARM_DA_ALIGN error as the reason for the bad access exception. I Googled a blog post that seems to allude to the same symptoms and probably cause for crashing as you are seeing here:

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

Indeed, by changing the line above

decodedStruct = *(BBPointI32*)data;

to

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

The crashing behavior seems to be alleviated and the code behaves as expected.

╰沐子 2024-12-16 10:02:47

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

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

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

I got to this thread, googling "EXC_ARM_DA_ALIGN" and "EXC_BAD_ACCESS". None of the other answers helped me, as this error cropped up because of something relatively simple. I had written:

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

i.e. I had left off a @ in front of a string literal. Putting it back in solved the error.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文