堆栈溢出:线程 1:EXC_BAD_ACCESS(代码=2,地址=0x16d09aa00)
Stack overflow: Thread 1: EXC_BAD_ACCESS (code=2, address=0x16d09aa00)
崩溃描述
最近我在我的一个 iOS/Swift 项目中遇到了一些非常奇怪的内存问题。我真的不知道是怎么回事,感觉也不太好描述,不过我会尽力的。
它的基本行为如下:
- 在某个代码库上,崩溃总是发生在同一个地方(100% 可重现)
- 更改代码库可能会解决问题,但它也可能会在其他地方弹出
- 崩溃只发生在真实设备上,绝不会发生在模拟器中
目前应用程序崩溃并出现以下错误(3 次不同运行的结果):
Thread 1: EXC_BAD_ACCESS (code=2, address=0x16d09aa00)
Thread 1: EXC_BAD_ACCESS (code=2, address=0x16af46a00)
Thread 1: EXC_BAD_ACCESS (code=2, address=0x16d526a00)
关于内存地址的推理
WWDC session
我在 WWDC 2018 上发现了一个有趣的 session (Understanding Crashes and Crash Logs),其中一个人指出有时可以从特定的内存地址中获取更多信息,但会发生崩溃。
不幸的是,它在我的应用程序中崩溃的地址有些完全不同,但也许我们可以从中获得线索?至少有趣的是,它们都非常相似,不是吗?
由于启用诊断选项而发生的变化
进一步调查表明,前 2 个字节 (16) 始终保持不变,然后是 4 个随机字节,然后是 3 个字节 (a00)。当激活诊断(例如 ASan 或 Scribble)时,最后 3 个字节会发生变化(例如 3a0 或 9e0)。但也许这只是由于添加了更多“调试内容”而导致的一种转变?我真的不是那种“记忆力”,只是想提供我注意到的任何东西。
尝试“诊断选项”
我尝试了不同的诊断选项(来自方案),但其中none确实以任何方式改变了崩溃,或提供了更多信息。
1。涂鸦
崩溃不引用 0xAA 或 0x55,所以使用 Scribble 没有什么可以捕捉到的? (Xcode - scribble, guard edges and guard malloc)
2。 Malloc 防护边缘
也没有注意到使用这个有什么不同。
3。僵尸
使用这个guide。
malloc_info --type 0x16b15e9c0
error: error: Trying to put the stack in unreadable memory at: 0x16b15e920.
4。阿三
使用 ASan 只是将以下条目放在堆栈跟踪的顶部。不幸的是,我没有找到任何与此相关的有用信息。
#0 0x0000000109efbf60 in __asan_alloca_poison ()
5。特桑
在真实设备上不可用(崩溃只发生在那里)
递归/BOF?
会不会是递归太长,或者是另一种stack/heap缓冲区溢出?
但看起来真实设备和模拟器上的堆栈大小与 524288
字节(来自 Thread.main.stackSize
)完全相同。
因此,由于它不会在模拟器中崩溃,所以它不是 BOF?还是架构差异太大,不能在这里下这样的结论?
拆卸
我也试过“反汇编”。
disassemble -a 0x16d09aa00
error: Could not find function bounds for address 0x16d09aa00
或disassemble -frame
但是我的汇编技术实在是太差了,所以目前我从那些资料中也没有什么可以得到的。
需要帮助
如您所见,我真的 运行 没主意了。要么崩溃真的很奇怪,要么我只是没有足够的 knowledge/skills 来使用上述工具,让我更接近这些问题的原因。
无论哪种方式...任何帮助、提示、想法或任何可以指引我正确方向的东西都非常感谢!
提前致谢各位。
2020 年 5 月 19 日更新
我完全忘了提及,我们在我们的应用程序中大量使用 ReSwift,我猜崩溃似乎与我们在那里使用中间件的方式有关。
我也已经与那里的开发人员取得联系:github.com/ReSwift/ReSwift/issues/271。
终于有一些代码了。不幸的是,我无法共享所有应用程序代码(这可能是必要的!?),也不想让您负担太多代码。
当前问题
Thread 1: EXC_BAD_ACCESS (code=1, address=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-WhosebugDemo
现在我将提供更多细节和解决方案,供其他人可能 运行 遇到此问题或类似问题。
iOS 上的堆栈大小(截至 iOS 13)为 512kb,应该适用于设备和模拟器。为什么我说“应该”?因为它几乎肯定在模拟器上有些不同,因为我没有在那里看到那些崩溃。所以也许 Thread.main.stackSize
只是告诉 512kb 但实际上更大? IDK ♂️
以下是一些指标,您可能遇到同样的问题:
- 您遇到
EXC_BAD_ACCESS
次崩溃,代码为 1 或 2**。 和 崩溃发生在高内存地址,或者至少完全不在您 app/stack 的其余部分通常“居住”的地方。在我的例子中是 0x16d95ad00
。
- 减少你放在堆栈上的东西(值类型,例如非常非常大的结构)或将调用堆栈分解成更小的部分(例如分派异步)给堆栈一些“时间来breathe”防止了这次崩溃。
在后者这里,我们已经在解决该问题的过程中。由于堆栈大小不能(甚至可能不应该)增加,您必须减少放置在那里的负载,如第 2 点所述。
至少这是我们可能会寻求的解决方案。
*至少主线程是这样,其他线程可能不一样。
**我认为代码 0 有点像空指针异常,因此不适用于此处。如果我对此有误,请纠正我。
崩溃描述
最近我在我的一个 iOS/Swift 项目中遇到了一些非常奇怪的内存问题。我真的不知道是怎么回事,感觉也不太好描述,不过我会尽力的。
它的基本行为如下:
- 在某个代码库上,崩溃总是发生在同一个地方(100% 可重现)
- 更改代码库可能会解决问题,但它也可能会在其他地方弹出
- 崩溃只发生在真实设备上,绝不会发生在模拟器中
目前应用程序崩溃并出现以下错误(3 次不同运行的结果):
Thread 1: EXC_BAD_ACCESS (code=2, address=0x16d09aa00)
Thread 1: EXC_BAD_ACCESS (code=2, address=0x16af46a00)
Thread 1: EXC_BAD_ACCESS (code=2, address=0x16d526a00)
关于内存地址的推理
WWDC session
我在 WWDC 2018 上发现了一个有趣的 session (Understanding Crashes and Crash Logs),其中一个人指出有时可以从特定的内存地址中获取更多信息,但会发生崩溃。
不幸的是,它在我的应用程序中崩溃的地址有些完全不同,但也许我们可以从中获得线索?至少有趣的是,它们都非常相似,不是吗?
由于启用诊断选项而发生的变化
进一步调查表明,前 2 个字节 (16) 始终保持不变,然后是 4 个随机字节,然后是 3 个字节 (a00)。当激活诊断(例如 ASan 或 Scribble)时,最后 3 个字节会发生变化(例如 3a0 或 9e0)。但也许这只是由于添加了更多“调试内容”而导致的一种转变?我真的不是那种“记忆力”,只是想提供我注意到的任何东西。
尝试“诊断选项”
我尝试了不同的诊断选项(来自方案),但其中none确实以任何方式改变了崩溃,或提供了更多信息。
1。涂鸦
崩溃不引用 0xAA 或 0x55,所以使用 Scribble 没有什么可以捕捉到的? (Xcode - scribble, guard edges and guard malloc)
2。 Malloc 防护边缘
也没有注意到使用这个有什么不同。
3。僵尸
使用这个guide。
malloc_info --type 0x16b15e9c0
error: error: Trying to put the stack in unreadable memory at: 0x16b15e920.
4。阿三
使用 ASan 只是将以下条目放在堆栈跟踪的顶部。不幸的是,我没有找到任何与此相关的有用信息。
#0 0x0000000109efbf60 in __asan_alloca_poison ()
5。特桑
在真实设备上不可用(崩溃只发生在那里)
递归/BOF?
会不会是递归太长,或者是另一种stack/heap缓冲区溢出?
但看起来真实设备和模拟器上的堆栈大小与 524288
字节(来自 Thread.main.stackSize
)完全相同。
因此,由于它不会在模拟器中崩溃,所以它不是 BOF?还是架构差异太大,不能在这里下这样的结论?
拆卸
我也试过“反汇编”。
disassemble -a 0x16d09aa00
error: Could not find function bounds for address 0x16d09aa00
或disassemble -frame
但是我的汇编技术实在是太差了,所以目前我从那些资料中也没有什么可以得到的。
需要帮助
如您所见,我真的 运行 没主意了。要么崩溃真的很奇怪,要么我只是没有足够的 knowledge/skills 来使用上述工具,让我更接近这些问题的原因。
无论哪种方式...任何帮助、提示、想法或任何可以指引我正确方向的东西都非常感谢!
提前致谢各位。
2020 年 5 月 19 日更新
我完全忘了提及,我们在我们的应用程序中大量使用 ReSwift,我猜崩溃似乎与我们在那里使用中间件的方式有关。
我也已经与那里的开发人员取得联系:github.com/ReSwift/ReSwift/issues/271。
终于有一些代码了。不幸的是,我无法共享所有应用程序代码(这可能是必要的!?),也不想让您负担太多代码。
当前问题
Thread 1: EXC_BAD_ACCESS (code=1, address=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-WhosebugDemo
现在我将提供更多细节和解决方案,供其他人可能 运行 遇到此问题或类似问题。
iOS 上的堆栈大小(截至 iOS 13)为 512kb,应该适用于设备和模拟器。为什么我说“应该”?因为它几乎肯定在模拟器上有些不同,因为我没有在那里看到那些崩溃。所以也许 Thread.main.stackSize
只是告诉 512kb 但实际上更大? IDK ♂️
以下是一些指标,您可能遇到同样的问题:
- 您遇到
EXC_BAD_ACCESS
次崩溃,代码为 1 或 2**。 和 崩溃发生在高内存地址,或者至少完全不在您 app/stack 的其余部分通常“居住”的地方。在我的例子中是0x16d95ad00
。 - 减少你放在堆栈上的东西(值类型,例如非常非常大的结构)或将调用堆栈分解成更小的部分(例如分派异步)给堆栈一些“时间来breathe”防止了这次崩溃。
在后者这里,我们已经在解决该问题的过程中。由于堆栈大小不能(甚至可能不应该)增加,您必须减少放置在那里的负载,如第 2 点所述。
至少这是我们可能会寻求的解决方案。
*至少主线程是这样,其他线程可能不一样。
**我认为代码 0 有点像空指针异常,因此不适用于此处。如果我对此有误,请纠正我。