堆栈溢出:线程1:EXC_BAD_ACCESS(代码=2,地址=0x16d09aa00)



碰撞描述

最近,我在一个iOS/Swift项目中遇到了一些非常奇怪的内存问题。我真的不确定发生了什么,也觉得不太容易描述,但无论如何我都会尽力。

它的基本行为如下:

  • 在特定的代码库中,崩溃总是发生在同一个地方(100%可重复)
  • 对代码库的更改可能会解决问题,但也可能会在其他地方弹出
  • 崩溃只发生在真实的设备上,从不发生在模拟器中

当前应用程序崩溃,出现以下错误(3次不同运行的结果):

线程1:EXC_BAD_ACCESS(代码=2,地址=0x16d09aa00)

线程1:EXC_BAD_ACCESS(代码=2,地址=0x16af46a00)

线程1:EXC_BAD_ACCESS(代码=2,地址0x16d526a00)


关于内存地址的推理

WWDC会话

我在WWDC 2018中发现了一个有趣的会话(了解崩溃和崩溃日志),其中一个人指出,有时可以从特定的内存地址中获得更多信息,崩溃就会发生。

不幸的是,它在我的应用程序中崩溃的地址有些完全不同,但也许我们无论如何都能从中得到线索?至少有趣的是,它们都很相似,或者不是吗?

由于启用了诊断选项而发生的更改

进一步的研究表明,前2个字节(16)始终保持不变,然后是4个随机字节,然后是3个字节(a00)。激活诊断时(例如ASan或Scribble),最后3个字节会发生变化(例如3a0或9e0)。但也许这只是一种转变,因为更多的";调试内容";正在添加?我真的不是那种";"记忆家伙";,但我只是想提供我注意到的任何东西。


尝试";诊断选项">

我尝试了不同的诊断选项(来自方案),但它们都没有以任何方式真正改变崩溃,也没有提供任何更多信息。

1.可划线

崩溃不引用0xAA或0x55,所以使用Scribble捕获它没有什么意义?(Xcode-涂鸦,保护边缘和保护malloc)

2.马洛克防护边缘

使用这个也没有发现任何区别。

3.僵尸

使用本指南。

malloc_info --type 0x16b15e9c0

错误:错误:试图将堆栈放入不可读内存中,位置为:0x16b15e920。

4.ASan

使用ASan只会将以下条目放在堆栈跟踪的顶部。不幸的是,我没有发现任何与此相关的帮助。

#0 0x0000000109efbf60 in __asan_alloca_poison ()

5.TSan

在实际设备上不可用(仅在那里发生崩溃)


递归/BOF

可能是递归太长,还是另一种堆栈/堆缓冲区溢出?但实际设备和模拟器上的堆栈大小似乎与524288字节(来自Thread.main.stackSize)完全相同。

所以,因为它不会在模拟器中崩溃,所以它不是BOF?还是架构差异太大,无法在这里得出这样的结论?


分解

我也尝试过";"拆卸";。

disassemble -a 0x16d09aa00

错误:找不到地址0x16d09aa00 的函数边界

disassemble -frame

但我的汇编技能确实落后了,所以目前我无法从这些信息中获得任何东西。


需要帮助

正如你所看到的,我真的没有什么想法了。要么这些崩溃真的很奇怪,要么我没有足够的知识/技能来使用上述工具,让我更接近这些问题的原因。

不管怎样。。。任何帮助、暗示、想法或任何能为我指明正确方向的东西都将不胜感激!

提前谢谢,伙计们。


更新时间:2020年5月19日

我完全忘了提一下,我们在应用程序中大量使用ReSwift,我想崩溃似乎与我们如何使用那里的中间件有关。

我也已经和那里的开发者取得了联系:github.com/ReSwift/ReSwift/issues/271。

最后是一些代码。不幸的是,我无法共享所有的应用程序代码(这可能是必要的!?),也不想让你的代码过多。

当前问题

线程1:EXC_BAD_ACCESS(代码=1,地址=0x16ed82da0)

UserAccountMiddleware.swift

注意:使用这些DispatchQueue.main.async实际上可以消除崩溃。它们确实打破了当前的循环,所以可能发生了某种递归或时间问题?

func userAccountMiddleware() -> Middleware<AppState> {
return { dispatch, getState in
return { next in
return { action in
switch action {
case _ as ReSwiftInit:
//                    DispatchQueue.main.async {
dispatch(UserAccountSetAuthToken(authToken: Defaults.customerAuthToken))
dispatch(UserAccountSetAvatar(index: Defaults.avatarIndex))
//                    }
if let data = Defaults.customer,
let customer = try? JSONDecoder().decode(Customer.self, from: data) {
//                        DispatchQueue.main.async {
dispatch(UserAccountSetCustomerLoggedIn(customer: customer))
//                        }
}
// [...]
default:
break
}
next(action)
}
}
}
}

ReSwift Store.swift

// [...]
open func _defaultDispatch(action: Action) {
guard !isDispatching else {
raiseFatalError(
"ReSwift:ConcurrentMutationError- Action has been dispatched while" +
" a previous action is action is being processed. A reducer" +
" is dispatching an action, or ReSwift is used in a concurrent context" +
" (e.g. from multiple threads)."
)
}
isDispatching = true
let newState = reducer(action, state) // Thread 1: EXC_BAD_ACCESS (code=1, address=0x16ed82da0)
isDispatching = false
state = newState
}
// [...]

Xcode控制台:

(lldb) po state
error: warning: couldn't get required object pointer (substituting NULL): Couldn't load 'self' because its value couldn't be evaluated
error: Trying to put the stack in unreadable memory at: 0x16d95ad00.

汇编程序(崩溃的最后一步):

myapp`type metadata accessor for GlobalState:
0x101f6ac10 <+0>:  sub    sp, sp, #0x30             ; =0x30 
->  0x101f6ac14 <+4>:  stp    x29, x30, [sp, #0x20] // Thread 1: EXC_BAD_ACCESS (code=1, address=0x16ed82da0)
0x101f6ac18 <+8>:  adrp   x8, 3620
0x101f6ac1c <+12>: add    x8, x8, #0x148            ; =0x148 
0x101f6ac20 <+16>: ldr    x8, [x8]
0x101f6ac24 <+20>: mov    x9, #0x0
0x101f6ac28 <+24>: mov    x1, x8
0x101f6ac2c <+28>: str    x0, [sp, #0x18]
0x101f6ac30 <+32>: str    x1, [sp, #0x10]
0x101f6ac34 <+36>: str    x9, [sp, #0x8]
0x101f6ac38 <+40>: cbnz   x8, 0x101f6ac54           ; <+68> at <compiler-generated>
0x101f6ac3c <+44>: adrp   x1, 2122
0x101f6ac40 <+48>: add    x1, x1, #0x1dc            ; =0x1dc 
0x101f6ac44 <+52>: ldr    x0, [sp, #0x18]
0x101f6ac48 <+56>: bl     0x102775358               ; symbol stub for: swift_getSingletonMetadata
0x101f6ac4c <+60>: str    x0, [sp, #0x10]
0x101f6ac50 <+64>: str    x1, [sp, #0x8]
0x101f6ac54 <+68>: ldr    x0, [sp, #0x8]
0x101f6ac58 <+72>: ldr    x1, [sp, #0x10]
0x101f6ac5c <+76>: str    x0, [sp]
0x101f6ac60 <+80>: mov    x0, x1
0x101f6ac64 <+84>: ldr    x1, [sp]
0x101f6ac68 <+88>: ldp    x29, x30, [sp, #0x20]
0x101f6ac6c <+92>: add    sp, sp, #0x30             ; =0x30 
0x101f6ac70 <+96>: ret     

TL;DR

只需将巨大的结构体封装在数组中,就可以将它们移动到堆中。使用@propertyWrappers,这可能是一个至少部分优雅的解决方案。

@propertyWrapper
struct StoredOnHeap<T> {
private var value: [T]
init(wrappedValue: T) {
self.value = [wrappedValue]
}
var wrappedValue: T {
get {
return self.value[0]
}
set {
self.value[0] = newValue
}
}
}
// Usage:
@StoredOnHeap var hugeStruct: HugeStruct

https://gist.github.com/d4rkd3v1l/ab582a7cafd3a8b8c164c8541a3eef96


长版本

我现在几乎100%确信,这是一个堆栈溢出,因为我(最终)在一个小演示项目中成功地再现了这一点:https://github.com/d4rkd3v1l/ReSwift-StackOverflowDemo

现在,我将为其他可能遇到此问题或类似问题的人提供更多细节和解决方案。

iOS上的堆栈大小(截至iOS 13)为512kb,应同时适用于设备和模拟器。为什么我说";应该"?因为几乎可以肯定的是,在模拟器上有一些不同,因为我没有在那里看到那些崩溃。也许Thread.main.stackSize只是告诉512kb,但实际上更大?IDK🤷‍♂️


以下是一些指标,您可能会面临相同的问题:

  • 代码为1或2**的EXC_BAD_ACCESS崩溃并且崩溃发生在高内存地址中,或者至少完全超出了应用程序/堆栈的其他部分的正常位置;生命";。在我的情况下,类似0x16d95ad00
  • 减少你放在堆栈上的东西(值类型,例如非常非常大的structs)或将调用堆栈分解成更小的部分(例如dispatch-async),给堆栈一些";呼吸的时间";防止此崩溃

在后者中,我们已经处于该问题的解决方案的中间。由于堆栈大小不能(甚至可能不应该)增加,您必须减少在那里放置的负载,如第2点所述。

至少这是我们可能会寻求的解决方案。🤞


*至少对于主线程是这样,其他线程可能不同。

**我认为代码0有点像空指针,因此这里不适用。如果我错了,请纠正我。

最新更新