diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7f95c005eac..a900db7fe44 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -19,6 +19,7 @@
### Features and Fixes
+- feat: Async callstacks are being tracked by wrapping the `dispatch_async` and related APIs. #998
- perf: Avoid allocating dict in BreadcrumbTracker #1027
- feat: Add start and endSession to SentrySDK #1021
- fix: Crash when passing garbage to maxBreadcrumbs #1018
diff --git a/Samples/iOS-ObjectiveC/iOS-ObjectiveC/Base.lproj/Main.storyboard b/Samples/iOS-ObjectiveC/iOS-ObjectiveC/Base.lproj/Main.storyboard
index b5d00445cbb..d4c1995e3dd 100644
--- a/Samples/iOS-ObjectiveC/iOS-ObjectiveC/Base.lproj/Main.storyboard
+++ b/Samples/iOS-ObjectiveC/iOS-ObjectiveC/Base.lproj/Main.storyboard
@@ -83,6 +83,13 @@
+
diff --git a/Samples/iOS-ObjectiveC/iOS-ObjectiveC/ViewController.m b/Samples/iOS-ObjectiveC/iOS-ObjectiveC/ViewController.m
index 22efd4b0ef4..55adca2114e 100644
--- a/Samples/iOS-ObjectiveC/iOS-ObjectiveC/ViewController.m
+++ b/Samples/iOS-ObjectiveC/iOS-ObjectiveC/ViewController.m
@@ -115,6 +115,21 @@ - (IBAction)crash:(id)sender
[SentrySDK crash];
}
+- (IBAction)asyncCrash:(id)sender
+{
+ dispatch_async(dispatch_get_main_queue(), ^{ [self asyncCrash1]; });
+}
+
+- (void)asyncCrash1
+{
+ dispatch_async(dispatch_get_main_queue(), ^{ [self asyncCrash2]; });
+}
+
+- (void)asyncCrash2
+{
+ dispatch_async(dispatch_get_main_queue(), ^{ [SentrySDK crash]; });
+}
+
- (IBAction)oomCrash:(id)sender
{
dispatch_async(dispatch_get_main_queue(), ^{
diff --git a/Samples/iOS-Swift/iOS-Swift/Base.lproj/Main.storyboard b/Samples/iOS-Swift/iOS-Swift/Base.lproj/Main.storyboard
index 92b119fd268..ff75a110015 100644
--- a/Samples/iOS-Swift/iOS-Swift/Base.lproj/Main.storyboard
+++ b/Samples/iOS-Swift/iOS-Swift/Base.lproj/Main.storyboard
@@ -96,6 +96,13 @@
+
diff --git a/Samples/iOS-Swift/iOS-Swift/ViewController.swift b/Samples/iOS-Swift/iOS-Swift/ViewController.swift
index f66dbff9283..7e58903afcb 100644
--- a/Samples/iOS-Swift/iOS-Swift/ViewController.swift
+++ b/Samples/iOS-Swift/iOS-Swift/ViewController.swift
@@ -91,12 +91,30 @@ class ViewController: UIViewController {
SentrySDK.crash()
}
+ @IBAction func asyncCrash(_ sender: Any) {
+ DispatchQueue.main.async {
+ self.asyncCrash1()
+ }
+ }
+
+ func asyncCrash1() {
+ DispatchQueue.main.async {
+ self.asyncCrash2()
+ }
+ }
+
+ func asyncCrash2() {
+ DispatchQueue.main.async {
+ SentrySDK.crash()
+ }
+ }
+
@IBAction func oomCrash(_ sender: Any) {
DispatchQueue.main.async {
let megaByte = 1_024 * 1_024
let memoryPageSize = NSPageSize()
let memoryPages = megaByte / memoryPageSize
-
+
while true {
// Allocate one MB and set one element of each memory page to something.
let ptr = UnsafeMutablePointer.allocate(capacity: megaByte)
diff --git a/Samples/macOS-Swift/macOS-Swift/Base.lproj/Main.storyboard b/Samples/macOS-Swift/macOS-Swift/Base.lproj/Main.storyboard
index 10368c60525..40aec8a9200 100644
--- a/Samples/macOS-Swift/macOS-Swift/Base.lproj/Main.storyboard
+++ b/Samples/macOS-Swift/macOS-Swift/Base.lproj/Main.storyboard
@@ -776,6 +776,17 @@
+
diff --git a/Samples/macOS-Swift/macOS-Swift/ViewController.swift b/Samples/macOS-Swift/macOS-Swift/ViewController.swift
index 7cdb7316153..b18f17b0faf 100644
--- a/Samples/macOS-Swift/macOS-Swift/ViewController.swift
+++ b/Samples/macOS-Swift/macOS-Swift/ViewController.swift
@@ -59,4 +59,22 @@ class ViewController: NSViewController {
@IBAction func sentryCrash(_ sender: Any) {
SentrySDK.crash()
}
+
+ @IBAction func asyncCrash(_ sender: Any) {
+ DispatchQueue.main.async {
+ self.asyncCrash1()
+ }
+ }
+
+ func asyncCrash1() {
+ DispatchQueue.main.async {
+ self.asyncCrash2()
+ }
+ }
+
+ func asyncCrash2() {
+ DispatchQueue.main.async {
+ SentrySDK.crash()
+ }
+ }
}
diff --git a/Samples/tvOS-Swift/tvOS-Swift/ContentView.swift b/Samples/tvOS-Swift/tvOS-Swift/ContentView.swift
index 5fdb2060813..02dacf90e9c 100644
--- a/Samples/tvOS-Swift/tvOS-Swift/ContentView.swift
+++ b/Samples/tvOS-Swift/tvOS-Swift/ContentView.swift
@@ -47,13 +47,25 @@ struct ContentView: View {
transaction.finish()
})
}
-
+
+ func asyncCrash1() {
+ DispatchQueue.main.async {
+ self.asyncCrash2()
+ }
+ }
+
+ func asyncCrash2() {
+ DispatchQueue.main.async {
+ SentrySDK.crash()
+ }
+ }
+
var oomCrashAction: () -> Void = {
DispatchQueue.main.async {
let megaByte = 1_024 * 1_024
let memoryPageSize = NSPageSize()
let memoryPages = megaByte / memoryPageSize
-
+
while true {
// Allocate one MB and set one element of each memory page to something.
let ptr = UnsafeMutablePointer.allocate(capacity: megaByte)
@@ -63,7 +75,7 @@ struct ContentView: View {
}
}
}
-
+
var body: some View {
VStack {
Button(action: addBreadcrumbAction) {
@@ -89,13 +101,21 @@ struct ContentView: View {
Button(action: captureTransactionAction) {
Text("Capture Transaction")
}
-
+
Button(action: {
SentrySDK.crash()
}) {
Text("Crash")
}
+ Button(action: {
+ DispatchQueue.main.async {
+ self.asyncCrash1()
+ }
+ }) {
+ Text("Async Crash")
+ }
+
Button(action: oomCrashAction) {
Text("OOM Crash")
}
diff --git a/Sentry.xcodeproj/project.pbxproj b/Sentry.xcodeproj/project.pbxproj
index a3b624c3251..b5f54f44016 100644
--- a/Sentry.xcodeproj/project.pbxproj
+++ b/Sentry.xcodeproj/project.pbxproj
@@ -449,6 +449,10 @@
8ECC674825C23A20000E2BF6 /* SentryTransaction.m in Sources */ = {isa = PBXBuildFile; fileRef = 8ECC674425C23A1F000E2BF6 /* SentryTransaction.m */; };
8ECC674925C23A20000E2BF6 /* SentrySpanId.m in Sources */ = {isa = PBXBuildFile; fileRef = 8ECC674525C23A20000E2BF6 /* SentrySpanId.m */; };
8ECC674A25C23A20000E2BF6 /* SentryTransactionContext.m in Sources */ = {isa = PBXBuildFile; fileRef = 8ECC674625C23A20000E2BF6 /* SentryTransactionContext.m */; };
+ A2475E1325FB63A3007D9080 /* fishhook.h in Headers */ = {isa = PBXBuildFile; fileRef = A2475E1225FB63A3007D9080 /* fishhook.h */; };
+ A2475E1725FB63AF007D9080 /* SentryHook.h in Headers */ = {isa = PBXBuildFile; fileRef = A2475E1625FB63AF007D9080 /* SentryHook.h */; };
+ A2475E1B25FB63D7007D9080 /* SentryHook.c in Sources */ = {isa = PBXBuildFile; fileRef = A2475E1A25FB63D7007D9080 /* SentryHook.c */; };
+ A2475E1F25FB648B007D9080 /* fishhook.c in Sources */ = {isa = PBXBuildFile; fileRef = A2475E1E25FB648B007D9080 /* fishhook.c */; };
A811D867248E2770008A41EA /* SentrySystemEventsBreadcrumbsTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = A811D866248E2770008A41EA /* SentrySystemEventsBreadcrumbsTest.swift */; };
A839D89824864B80003B7AFD /* SentrySystemEventsBreadcrumbs.h in Headers */ = {isa = PBXBuildFile; fileRef = A839D89724864B80003B7AFD /* SentrySystemEventsBreadcrumbs.h */; };
A839D89A24864BA8003B7AFD /* SentrySystemEventsBreadcrumbs.m in Sources */ = {isa = PBXBuildFile; fileRef = A839D89924864BA8003B7AFD /* SentrySystemEventsBreadcrumbs.m */; };
@@ -950,6 +954,10 @@
8ECC674425C23A1F000E2BF6 /* SentryTransaction.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SentryTransaction.m; sourceTree = ""; };
8ECC674525C23A20000E2BF6 /* SentrySpanId.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SentrySpanId.m; sourceTree = ""; };
8ECC674625C23A20000E2BF6 /* SentryTransactionContext.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SentryTransactionContext.m; sourceTree = ""; };
+ A2475E1225FB63A3007D9080 /* fishhook.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = fishhook.h; sourceTree = ""; };
+ A2475E1625FB63AF007D9080 /* SentryHook.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SentryHook.h; sourceTree = ""; };
+ A2475E1A25FB63D7007D9080 /* SentryHook.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SentryHook.c; sourceTree = ""; };
+ A2475E1E25FB648B007D9080 /* fishhook.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = fishhook.c; sourceTree = ""; };
A811D866248E2770008A41EA /* SentrySystemEventsBreadcrumbsTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentrySystemEventsBreadcrumbsTest.swift; sourceTree = ""; };
A839D89724864B80003B7AFD /* SentrySystemEventsBreadcrumbs.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentrySystemEventsBreadcrumbs.h; path = include/SentrySystemEventsBreadcrumbs.h; sourceTree = ""; };
A839D89924864BA8003B7AFD /* SentrySystemEventsBreadcrumbs.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentrySystemEventsBreadcrumbs.m; sourceTree = ""; };
@@ -1171,20 +1179,20 @@
6383953423ABA269000C1594 /* Integrations */ = {
isa = PBXGroup;
children = (
- 15360CD12432779F00112302 /* SentrySessionTracker.h */,
- 15360CCE2432777400112302 /* SentrySessionTracker.m */,
+ 7DC27EC323997EB7006998B5 /* SentryAutoBreadcrumbTrackingIntegration.h */,
+ 7DC27EC423997EB7006998B5 /* SentryAutoBreadcrumbTrackingIntegration.m */,
15360CD82432835400112302 /* SentryAutoSessionTrackingIntegration.h */,
15360CD52432832400112302 /* SentryAutoSessionTrackingIntegration.m */,
- 7B4E23BD251A2BD500060D68 /* SentrySessionCrashedHandler.h */,
- 7B4E23C1251A2C2B00060D68 /* SentrySessionCrashedHandler.m */,
639889B51EDECFA800EA7442 /* SentryBreadcrumbTracker.h */,
639889B61EDECFA800EA7442 /* SentryBreadcrumbTracker.m */,
7DC831082398283C0043DD9A /* SentryCrashIntegration.h */,
7DC831092398283C0043DD9A /* SentryCrashIntegration.m */,
- 7DC27EC323997EB7006998B5 /* SentryAutoBreadcrumbTrackingIntegration.h */,
- 7DC27EC423997EB7006998B5 /* SentryAutoBreadcrumbTrackingIntegration.m */,
7D7F0A5E23DF3D2C00A4629C /* SentryGlobalEventProcessor.h */,
7DAC588E23D8B2E0001CF26B /* SentryGlobalEventProcessor.m */,
+ 7B4E23BD251A2BD500060D68 /* SentrySessionCrashedHandler.h */,
+ 7B4E23C1251A2C2B00060D68 /* SentrySessionCrashedHandler.m */,
+ 15360CD12432779F00112302 /* SentrySessionTracker.h */,
+ 15360CCE2432777400112302 /* SentrySessionTracker.m */,
A839D89724864B80003B7AFD /* SentrySystemEventsBreadcrumbs.h */,
A839D89924864BA8003B7AFD /* SentrySystemEventsBreadcrumbs.m */,
7B98D7BB25FB607300C5A389 /* SentryOutOfMemoryTracker.h */,
@@ -1201,23 +1209,32 @@
639889D51EDF10BE00EA7442 /* Helper */ = {
isa = PBXGroup;
children = (
- 15E0A8E9240F2C8F00F044E3 /* SentrySerialization.h */,
- 15E0A8EC240F2CB000F044E3 /* SentrySerialization.m */,
+ 632F434E1F581D5400A18A36 /* SentryCrashExceptionApplication.h */,
+ 632F434F1F581D5400A18A36 /* SentryCrashExceptionApplication.m */,
+ 7BE3C7662445C0CA00A38442 /* SentryCurrentDate.h */,
+ 7BE3C7682445C1A800A38442 /* SentryCurrentDate.m */,
+ 7BE3C76A2445C27A00A38442 /* SentryCurrentDateProvider.h */,
+ 7BD729952463E83300EA3610 /* SentryDateUtil.h */,
+ 7BD729972463E93500EA3610 /* SentryDateUtil.m */,
+ 7BE3C76E2445C2F800A38442 /* SentryDefaultCurrentDateProvider.h */,
+ 7BE3C7702445C30D00A38442 /* SentryDefaultCurrentDateProvider.m */,
63AA76951EB9C1C200D153DE /* SentryDefines.h */,
- 7BC3936725B1AB3E004F03D3 /* SentryLevelMapper.h */,
- 7BC3936D25B1AB72004F03D3 /* SentryLevelMapper.m */,
63AA769B1EB9C57A00D153DE /* SentryError.h */,
63AA769C1EB9C57A00D153DE /* SentryError.m */,
- 63AA76961EB9C1C200D153DE /* SentryLog.h */,
- 63AA76781EB8D20500D153DE /* SentryLog.m */,
+ 7BC8522E24581096005A70F0 /* SentryFileContents.h */,
+ 7BC85230245812EC005A70F0 /* SentryFileContents.m */,
7BA61E8925F21A3A0008CAA2 /* SentryLogOutput.h */,
7BA61E9125F21AF80008CAA2 /* SentryLogOutput.m */,
636085111ED47BE600E8599E /* SentryFileManager.h */,
636085121ED47BE600E8599E /* SentryFileManager.m */,
+ 7BC3936725B1AB3E004F03D3 /* SentryLevelMapper.h */,
+ 7BC3936D25B1AB72004F03D3 /* SentryLevelMapper.m */,
+ 63AA76961EB9C1C200D153DE /* SentryLog.h */,
+ 63AA76781EB8D20500D153DE /* SentryLog.m */,
7BE1E33124F7E3B6009D3AD0 /* SentryMigrateSessionInit.h */,
7BE1E33324F7E3CB009D3AD0 /* SentryMigrateSessionInit.m */,
- 7BC8522E24581096005A70F0 /* SentryFileContents.h */,
- 7BC85230245812EC005A70F0 /* SentryFileContents.m */,
+ 15E0A8E9240F2C8F00F044E3 /* SentrySerialization.h */,
+ 15E0A8EC240F2CB000F044E3 /* SentrySerialization.m */,
639889B91EDED18400EA7442 /* SentrySwizzle.h */,
639889BA1EDED18400EA7442 /* SentrySwizzle.m */,
632F434E1F581D5400A18A36 /* SentryCrashExceptionApplication.h */,
@@ -1430,29 +1447,29 @@
63FE6FEC20DA4C1000CDBAE8 /* Monitors */ = {
isa = PBXGroup;
children = (
- 63FE6FED20DA4C1000CDBAE8 /* SentryCrashMonitor_User.h */,
- 63FE6FEE20DA4C1000CDBAE8 /* SentryCrashMonitorContext.h */,
63FE6FEF20DA4C1000CDBAE8 /* SentryCrashMonitor_AppState.c */,
- 63FE6FF020DA4C1000CDBAE8 /* SentryCrashMonitor_NSException.m */,
- 63FE6FF120DA4C1000CDBAE8 /* SentryCrashMonitor_MachException.c */,
- 63FE6FF220DA4C1000CDBAE8 /* SentryCrashMonitor_Deadlock.m */,
- 63FE6FF320DA4C1000CDBAE8 /* SentryCrashMonitor_System.m */,
- 63FE6FF420DA4C1000CDBAE8 /* SentryCrashMonitor_Signal.h */,
- 63FE6FF520DA4C1000CDBAE8 /* SentryCrashMonitorType.c */,
+ 63FE6FFD20DA4C1000CDBAE8 /* SentryCrashMonitor_AppState.h */,
63FE6FF620DA4C1000CDBAE8 /* SentryCrashMonitor_CPPException.cpp */,
- 63FE6FF720DA4C1000CDBAE8 /* SentryCrashMonitor_Zombie.c */,
63FE6FF820DA4C1000CDBAE8 /* SentryCrashMonitor_CPPException.h */,
- 63FE6FF920DA4C1000CDBAE8 /* SentryCrashMonitor.c */,
- 63FE6FFA20DA4C1000CDBAE8 /* SentryCrashMonitor_User.c */,
+ 63FE700120DA4C1000CDBAE8 /* SentryCrashMonitor_Deadlock.h */,
+ 63FE6FF220DA4C1000CDBAE8 /* SentryCrashMonitor_Deadlock.m */,
+ 63FE6FF120DA4C1000CDBAE8 /* SentryCrashMonitor_MachException.c */,
63FE6FFB20DA4C1000CDBAE8 /* SentryCrashMonitor_MachException.h */,
63FE6FFC20DA4C1000CDBAE8 /* SentryCrashMonitor_NSException.h */,
- 63FE6FFD20DA4C1000CDBAE8 /* SentryCrashMonitor_AppState.h */,
- 63FE6FFE20DA4C1000CDBAE8 /* SentryCrashMonitorType.h */,
+ 63FE6FF020DA4C1000CDBAE8 /* SentryCrashMonitor_NSException.m */,
63FE6FFF20DA4C1000CDBAE8 /* SentryCrashMonitor_Signal.c */,
+ 63FE6FF420DA4C1000CDBAE8 /* SentryCrashMonitor_Signal.h */,
63FE700020DA4C1000CDBAE8 /* SentryCrashMonitor_System.h */,
- 63FE700120DA4C1000CDBAE8 /* SentryCrashMonitor_Deadlock.h */,
- 63FE700220DA4C1000CDBAE8 /* SentryCrashMonitor.h */,
+ 63FE6FF320DA4C1000CDBAE8 /* SentryCrashMonitor_System.m */,
+ 63FE6FFA20DA4C1000CDBAE8 /* SentryCrashMonitor_User.c */,
+ 63FE6FED20DA4C1000CDBAE8 /* SentryCrashMonitor_User.h */,
+ 63FE6FF720DA4C1000CDBAE8 /* SentryCrashMonitor_Zombie.c */,
63FE700320DA4C1000CDBAE8 /* SentryCrashMonitor_Zombie.h */,
+ 63FE6FF920DA4C1000CDBAE8 /* SentryCrashMonitor.c */,
+ 63FE700220DA4C1000CDBAE8 /* SentryCrashMonitor.h */,
+ 63FE6FEE20DA4C1000CDBAE8 /* SentryCrashMonitorContext.h */,
+ 63FE6FF520DA4C1000CDBAE8 /* SentryCrashMonitorType.c */,
+ 63FE6FFE20DA4C1000CDBAE8 /* SentryCrashMonitorType.h */,
);
path = Monitors;
sourceTree = "";
@@ -1460,61 +1477,65 @@
63FE700520DA4C1000CDBAE8 /* Tools */ = {
isa = PBXGroup;
children = (
- 63FE700720DA4C1000CDBAE8 /* SentryCrashDate.h */,
- 63FE700820DA4C1000CDBAE8 /* SentryCrashMachineContext_Apple.h */,
- 63FE700920DA4C1000CDBAE8 /* SentryCrashLogger.c */,
- 63FE700A20DA4C1000CDBAE8 /* SentryCrashStackCursor_SelfThread.c */,
- 63FE700B20DA4C1000CDBAE8 /* SentryCrashFileUtils.h */,
- 63FE700C20DA4C1000CDBAE8 /* SentryCrashMach.c */,
- 63FE700D20DA4C1000CDBAE8 /* SentryCrashStackCursor_MachineContext.c */,
+ A2475E1E25FB648B007D9080 /* fishhook.c */,
+ A2475E1225FB63A3007D9080 /* fishhook.h */,
+ 63FE702E20DA4C1000CDBAE8 /* NSError+SentrySimpleConstructor.h */,
63FE700E20DA4C1000CDBAE8 /* NSError+SentrySimpleConstructor.m */,
- 63FE700F20DA4C1000CDBAE8 /* SentryCrashDebug.c */,
- 63FE701120DA4C1000CDBAE8 /* SentryCrashJSONCodec.c */,
- 63FE701220DA4C1000CDBAE8 /* SentryCrashStackCursor_Backtrace.c */,
- 63FE701320DA4C1000CDBAE8 /* SentryCrashMachineContext.c */,
- 63FE701420DA4C1000CDBAE8 /* SentryCrashString.c */,
- 63FE701520DA4C1000CDBAE8 /* SentryCrashCPU_arm64.c */,
- 63FE701620DA4C1000CDBAE8 /* SentryCrashObjC.h */,
- 63FE701720DA4C1000CDBAE8 /* SentryCrashSymbolicator.c */,
- 63FE701820DA4C1000CDBAE8 /* SentryCrashID.h */,
- 63FE701920DA4C1000CDBAE8 /* SentryCrashSignalInfo.c */,
- 63FE701A20DA4C1000CDBAE8 /* SentryCrashThread.c */,
+ 63FE703E20DA4C1000CDBAE8 /* SentryCrashCPU_Apple.h */,
63FE701B20DA4C1000CDBAE8 /* SentryCrashCPU_arm.c */,
- 63FE701C20DA4C1000CDBAE8 /* SentryCrashStackCursor.h */,
- 63FE701D20DA4C1000CDBAE8 /* SentryCrashJSONCodecObjC.m */,
- 63FE701E20DA4C1000CDBAE8 /* SentryCrashSysCtl.c */,
- 63FE701F20DA4C1000CDBAE8 /* SentryCrashDynamicLinker.c */,
- 63FE702020DA4C1000CDBAE8 /* SentryCrashCPU.h */,
- 63FE702120DA4C1000CDBAE8 /* SentryCrashMemory.c */,
+ 63FE701520DA4C1000CDBAE8 /* SentryCrashCPU_arm64.c */,
+ 63FE703320DA4C1000CDBAE8 /* SentryCrashCPU_x86_32.c */,
63FE702220DA4C1000CDBAE8 /* SentryCrashCPU_x86_64.c */,
- 63FE702320DA4C1000CDBAE8 /* SentryCrashMach.h */,
+ 63FE703A20DA4C1000CDBAE8 /* SentryCrashCPU.c */,
+ 63FE702020DA4C1000CDBAE8 /* SentryCrashCPU.h */,
+ 63FE702720DA4C1000CDBAE8 /* SentryCrashDate.c */,
+ 63FE700720DA4C1000CDBAE8 /* SentryCrashDate.h */,
+ 63FE700F20DA4C1000CDBAE8 /* SentryCrashDebug.c */,
+ 63FE702F20DA4C1000CDBAE8 /* SentryCrashDebug.h */,
+ 63FE701F20DA4C1000CDBAE8 /* SentryCrashDynamicLinker.c */,
+ 63FE703820DA4C1000CDBAE8 /* SentryCrashDynamicLinker.h */,
63FE702420DA4C1000CDBAE8 /* SentryCrashFileUtils.c */,
+ 63FE700B20DA4C1000CDBAE8 /* SentryCrashFileUtils.h */,
+ 63FE703620DA4C1000CDBAE8 /* SentryCrashID.c */,
+ 63FE701820DA4C1000CDBAE8 /* SentryCrashID.h */,
+ 63FE701120DA4C1000CDBAE8 /* SentryCrashJSONCodec.c */,
+ 63FE702D20DA4C1000CDBAE8 /* SentryCrashJSONCodec.h */,
+ 63FE703C20DA4C1000CDBAE8 /* SentryCrashJSONCodecObjC.h */,
+ 63FE701D20DA4C1000CDBAE8 /* SentryCrashJSONCodecObjC.m */,
+ 63FE700920DA4C1000CDBAE8 /* SentryCrashLogger.c */,
63FE702520DA4C1000CDBAE8 /* SentryCrashLogger.h */,
- 63FE702620DA4C1000CDBAE8 /* SentryCrashStackCursor_SelfThread.h */,
- 63FE702720DA4C1000CDBAE8 /* SentryCrashDate.c */,
- 63FE702920DA4C1000CDBAE8 /* SentryCrashObjC.c */,
+ 63FE700C20DA4C1000CDBAE8 /* SentryCrashMach.c */,
+ 63FE702320DA4C1000CDBAE8 /* SentryCrashMach.h */,
+ 63FE700820DA4C1000CDBAE8 /* SentryCrashMachineContext_Apple.h */,
+ 63FE701320DA4C1000CDBAE8 /* SentryCrashMachineContext.c */,
63FE702A20DA4C1000CDBAE8 /* SentryCrashMachineContext.h */,
- 63FE702B20DA4C1000CDBAE8 /* SentryCrashStackCursor_Backtrace.h */,
- 63FE702C20DA4C1000CDBAE8 /* SentryCrashString.h */,
- 63FE702D20DA4C1000CDBAE8 /* SentryCrashJSONCodec.h */,
- 63FE702E20DA4C1000CDBAE8 /* NSError+SentrySimpleConstructor.h */,
- 63FE702F20DA4C1000CDBAE8 /* SentryCrashDebug.h */,
+ 63FE702120DA4C1000CDBAE8 /* SentryCrashMemory.c */,
+ 63FE703920DA4C1000CDBAE8 /* SentryCrashMemory.h */,
+ 63FE702920DA4C1000CDBAE8 /* SentryCrashObjC.c */,
+ 63FE701620DA4C1000CDBAE8 /* SentryCrashObjC.h */,
63FE703020DA4C1000CDBAE8 /* SentryCrashObjCApple.h */,
- 63FE703120DA4C1000CDBAE8 /* SentryCrashStackCursor_MachineContext.h */,
- 63FE703220DA4C1000CDBAE8 /* SentryCrashThread.h */,
- 63FE703320DA4C1000CDBAE8 /* SentryCrashCPU_x86_32.c */,
+ 63FE701920DA4C1000CDBAE8 /* SentryCrashSignalInfo.c */,
63FE703420DA4C1000CDBAE8 /* SentryCrashSignalInfo.h */,
- 63FE703520DA4C1000CDBAE8 /* SentryCrashSymbolicator.h */,
- 63FE703620DA4C1000CDBAE8 /* SentryCrashID.c */,
- 63FE703820DA4C1000CDBAE8 /* SentryCrashDynamicLinker.h */,
- 71F11CDEF5952DF5CC69AC74 /* SentryCrashUUIDConversion.h */,
- 7B883F48253D714C00879E62 /* SentryCrashUUIDConversion.c */,
- 63FE703920DA4C1000CDBAE8 /* SentryCrashMemory.h */,
- 63FE703A20DA4C1000CDBAE8 /* SentryCrashCPU.c */,
+ 63FE701220DA4C1000CDBAE8 /* SentryCrashStackCursor_Backtrace.c */,
+ 63FE702B20DA4C1000CDBAE8 /* SentryCrashStackCursor_Backtrace.h */,
+ 63FE700D20DA4C1000CDBAE8 /* SentryCrashStackCursor_MachineContext.c */,
+ 63FE703120DA4C1000CDBAE8 /* SentryCrashStackCursor_MachineContext.h */,
+ 63FE700A20DA4C1000CDBAE8 /* SentryCrashStackCursor_SelfThread.c */,
+ 63FE702620DA4C1000CDBAE8 /* SentryCrashStackCursor_SelfThread.h */,
63FE703B20DA4C1000CDBAE8 /* SentryCrashStackCursor.c */,
- 63FE703C20DA4C1000CDBAE8 /* SentryCrashJSONCodecObjC.h */,
+ 63FE701C20DA4C1000CDBAE8 /* SentryCrashStackCursor.h */,
+ 63FE701420DA4C1000CDBAE8 /* SentryCrashString.c */,
+ 63FE702C20DA4C1000CDBAE8 /* SentryCrashString.h */,
+ 63FE701720DA4C1000CDBAE8 /* SentryCrashSymbolicator.c */,
+ 63FE703520DA4C1000CDBAE8 /* SentryCrashSymbolicator.h */,
+ 63FE701E20DA4C1000CDBAE8 /* SentryCrashSysCtl.c */,
63FE703D20DA4C1000CDBAE8 /* SentryCrashSysCtl.h */,
- 63FE703E20DA4C1000CDBAE8 /* SentryCrashCPU_Apple.h */,
+ 63FE701A20DA4C1000CDBAE8 /* SentryCrashThread.c */,
+ 63FE703220DA4C1000CDBAE8 /* SentryCrashThread.h */,
+ 7B883F48253D714C00879E62 /* SentryCrashUUIDConversion.c */,
+ 71F11CDEF5952DF5CC69AC74 /* SentryCrashUUIDConversion.h */,
+ A2475E1A25FB63D7007D9080 /* SentryHook.c */,
+ A2475E1625FB63AF007D9080 /* SentryHook.h */,
);
path = Tools;
sourceTree = "";
@@ -1908,6 +1929,7 @@
7BA61CBF247CEA8100C130A8 /* SentryHexAddressFormatter.h in Headers */,
63FE70EB20DA4C1000CDBAE8 /* SentryCrashMonitor_MachException.h in Headers */,
7BC8522F24581096005A70F0 /* SentryFileContents.h in Headers */,
+ A2475E1325FB63A3007D9080 /* fishhook.h in Headers */,
7DC8310A2398283C0043DD9A /* SentryCrashIntegration.h in Headers */,
63FE718320DA4C1100CDBAE8 /* SentryCrashReportFixer.h in Headers */,
63FE70F920DA4C1000CDBAE8 /* SentryCrashMonitor.h in Headers */,
@@ -1974,6 +1996,7 @@
63FE707B20DA4C1000CDBAE8 /* Container+SentryDeepSearch.h in Headers */,
6344DDB91EC3115C00D9160D /* SentryCrashReportConverter.h in Headers */,
71F116E8F40D530BB68A2987 /* SentryCrashUUIDConversion.h in Headers */,
+ A2475E1725FB63AF007D9080 /* SentryHook.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -2084,6 +2107,7 @@
7DC27EC723997EB7006998B5 /* SentryAutoBreadcrumbTrackingIntegration.m in Sources */,
63FE717B20DA4C1100CDBAE8 /* SentryCrashReport.c in Sources */,
7B3398652459C15200BD9C96 /* SentryEnvelopeRateLimit.m in Sources */,
+ A2475E1F25FB648B007D9080 /* fishhook.c in Sources */,
631E6D341EBC679C00712345 /* SentryQueueableRequestManager.m in Sources */,
7BDB03BB2513652900BAE198 /* SentryDispatchQueueWrapper.m in Sources */,
63AA76A31EB9CBAA00D153DE /* SentryDsn.m in Sources */,
@@ -2137,6 +2161,7 @@
63FE716720DA4C1100CDBAE8 /* SentryCrashCPU.c in Sources */,
63FE717320DA4C1100CDBAE8 /* SentryCrashC.c in Sources */,
63FE712120DA4C1000CDBAE8 /* SentryCrashSymbolicator.c in Sources */,
+ A2475E1B25FB63D7007D9080 /* SentryHook.c in Sources */,
63FE70D720DA4C1000CDBAE8 /* SentryCrashMonitor_MachException.c in Sources */,
7BBD188B244841FB00427C76 /* SentryHttpDateParser.m in Sources */,
8E4E7C8225DAB2A5006AB9E2 /* SentryTracer.m in Sources */,
diff --git a/Sources/Sentry/SentryCrashIntegration.m b/Sources/Sentry/SentryCrashIntegration.m
index 77b96c70b35..24b628dc4cc 100644
--- a/Sources/Sentry/SentryCrashIntegration.m
+++ b/Sources/Sentry/SentryCrashIntegration.m
@@ -4,6 +4,7 @@
#import "SentryDispatchQueueWrapper.h"
#import "SentryEvent.h"
#import "SentryFrameInAppLogic.h"
+#import "SentryHook.h"
#import "SentryHub.h"
#import "SentryOutOfMemoryLogic.h"
#import "SentrySDK+Private.h"
@@ -72,6 +73,7 @@ - (void)installWithOptions:(nonnull SentryOptions *)options
outOfMemoryLogic:logic];
[self startCrashHandler];
+ sentrycrash_install_async_hooks();
[self configureScope];
}
diff --git a/Sources/SentryCrash/Recording/Tools/SentryCrashStackCursor.c b/Sources/SentryCrash/Recording/Tools/SentryCrashStackCursor.c
index ae61979f757..79d56067f03 100644
--- a/Sources/SentryCrash/Recording/Tools/SentryCrashStackCursor.c
+++ b/Sources/SentryCrash/Recording/Tools/SentryCrashStackCursor.c
@@ -23,6 +23,7 @@
//
#include "SentryCrashStackCursor.h"
+#include "SentryCrashCPU.h"
#include "SentryCrashSymbolicator.h"
#include
@@ -43,6 +44,7 @@ sentrycrashsc_resetCursor(SentryCrashStackCursor *cursor)
{
cursor->state.currentDepth = 0;
cursor->state.hasGivenUp = false;
+ cursor->state.current_async_caller = NULL;
cursor->stackEntry.address = 0;
cursor->stackEntry.imageAddress = 0;
cursor->stackEntry.imageName = NULL;
@@ -56,6 +58,30 @@ sentrycrashsc_initCursor(SentryCrashStackCursor *cursor,
{
cursor->symbolicate = sentrycrashsymbolicator_symbolicate;
cursor->advanceCursor = advanceCursor != NULL ? advanceCursor : g_advanceCursor;
+ cursor->async_caller = NULL;
cursor->resetCursor = resetCursor != NULL ? resetCursor : sentrycrashsc_resetCursor;
cursor->resetCursor(cursor);
}
+
+bool
+sentrycrashsc_advanceAsyncCursor(SentryCrashStackCursor *cursor)
+{
+ sentrycrash_async_backtrace_t *async_caller = cursor->state.current_async_caller;
+ if (async_caller) {
+ if (cursor->state.currentDepth < async_caller->len) {
+ uintptr_t nextAddress = (uintptr_t)async_caller->backtrace[cursor->state.currentDepth];
+ if (nextAddress > 1) {
+ cursor->stackEntry.address
+ = sentrycrashcpu_normaliseInstructionPointer(nextAddress);
+ cursor->state.currentDepth++;
+ return true;
+ }
+ }
+ if (async_caller->async_caller) {
+ cursor->state.current_async_caller = async_caller->async_caller;
+ cursor->state.currentDepth = 0;
+ return sentrycrashsc_advanceAsyncCursor(cursor);
+ }
+ }
+ return false;
+}
diff --git a/Sources/SentryCrash/Recording/Tools/SentryCrashStackCursor.h b/Sources/SentryCrash/Recording/Tools/SentryCrashStackCursor.h
index 65b715a83cd..3c43c51dd09 100644
--- a/Sources/SentryCrash/Recording/Tools/SentryCrashStackCursor.h
+++ b/Sources/SentryCrash/Recording/Tools/SentryCrashStackCursor.h
@@ -30,6 +30,7 @@ extern "C" {
#endif
#include "SentryCrashMachineContext.h"
+#include "SentryHook.h"
#include
#include
@@ -65,6 +66,9 @@ typedef struct SentryCrashStackCursor {
/** If true, cursor has given up walking the stack. */
bool hasGivenUp;
+
+ /** The current async caller we are chaining to. */
+ sentrycrash_async_backtrace_t *current_async_caller;
} state;
/** Reset the cursor back to the beginning. */
@@ -77,6 +81,9 @@ typedef struct SentryCrashStackCursor {
* stackEntry. */
bool (*symbolicate)(struct SentryCrashStackCursor *);
+ /** Pointer to an optional async stacktrace. */
+ sentrycrash_async_backtrace_t *async_caller;
+
/** Internal context-specific information. */
void *context[SentryCrashSC_CONTEXT_SIZE];
} SentryCrashStackCursor;
@@ -102,6 +109,9 @@ void sentrycrashsc_initCursor(SentryCrashStackCursor *cursor,
*/
void sentrycrashsc_resetCursor(SentryCrashStackCursor *cursor);
+/** Advance the cursor to the next stack entry in a chained async stacktrace. */
+bool sentrycrashsc_advanceAsyncCursor(SentryCrashStackCursor *cursor);
+
#ifdef __cplusplus
}
#endif
diff --git a/Sources/SentryCrash/Recording/Tools/SentryCrashStackCursor_Backtrace.c b/Sources/SentryCrash/Recording/Tools/SentryCrashStackCursor_Backtrace.c
index bab4972e22a..b0a7c7581a2 100644
--- a/Sources/SentryCrash/Recording/Tools/SentryCrashStackCursor_Backtrace.c
+++ b/Sources/SentryCrash/Recording/Tools/SentryCrashStackCursor_Backtrace.c
@@ -31,6 +31,11 @@
static bool
advanceCursor(SentryCrashStackCursor *cursor)
{
+ sentrycrash_async_backtrace_t *async_caller = cursor->state.current_async_caller;
+ if (async_caller) {
+ return sentrycrashsc_advanceAsyncCursor(cursor);
+ }
+
SentryCrashStackCursor_Backtrace_Context *context
= (SentryCrashStackCursor_Backtrace_Context *)cursor->context;
int endDepth = context->backtraceLength - context->skippedEntries;
@@ -44,6 +49,10 @@ advanceCursor(SentryCrashStackCursor *cursor)
cursor->state.currentDepth++;
return true;
}
+ } else if (cursor->async_caller) {
+ cursor->state.current_async_caller = cursor->async_caller;
+ cursor->state.currentDepth = 0;
+ return cursor->advanceCursor(cursor);
}
return false;
}
diff --git a/Sources/SentryCrash/Recording/Tools/SentryCrashStackCursor_MachineContext.c b/Sources/SentryCrash/Recording/Tools/SentryCrashStackCursor_MachineContext.c
index 9799e3e4202..dab49f98406 100644
--- a/Sources/SentryCrash/Recording/Tools/SentryCrashStackCursor_MachineContext.c
+++ b/Sources/SentryCrash/Recording/Tools/SentryCrashStackCursor_MachineContext.c
@@ -57,6 +57,11 @@ typedef struct {
static bool
advanceCursor(SentryCrashStackCursor *cursor)
{
+ sentrycrash_async_backtrace_t *async_caller = cursor->state.current_async_caller;
+ if (async_caller) {
+ return sentrycrashsc_advanceAsyncCursor(cursor);
+ }
+
MachineContextCursor *context = (MachineContextCursor *)cursor->context;
uintptr_t nextAddress = 0;
@@ -68,7 +73,7 @@ advanceCursor(SentryCrashStackCursor *cursor)
if (context->instructionAddress == 0) {
context->instructionAddress = sentrycrashcpu_instructionAddress(context->machineContext);
if (context->instructionAddress == 0) {
- return false;
+ goto tryAsyncChain;
}
nextAddress = context->instructionAddress;
goto successfulExit;
@@ -85,7 +90,7 @@ advanceCursor(SentryCrashStackCursor *cursor)
if (context->currentFrame.previous == NULL) {
if (context->isPastFramePointer) {
- return false;
+ goto tryAsyncChain;
}
context->currentFrame.previous
= (struct FrameEntry *)sentrycrashcpu_framePointer(context->machineContext);
@@ -97,7 +102,7 @@ advanceCursor(SentryCrashStackCursor *cursor)
return false;
}
if (context->currentFrame.previous == 0 || context->currentFrame.return_address == 0) {
- return false;
+ goto tryAsyncChain;
}
nextAddress = context->currentFrame.return_address;
@@ -106,6 +111,14 @@ advanceCursor(SentryCrashStackCursor *cursor)
cursor->stackEntry.address = sentrycrashcpu_normaliseInstructionPointer(nextAddress);
cursor->state.currentDepth++;
return true;
+
+tryAsyncChain:
+ if (cursor->async_caller) {
+ cursor->state.current_async_caller = cursor->async_caller;
+ cursor->state.currentDepth = 0;
+ return cursor->advanceCursor(cursor);
+ }
+ return false;
}
static void
@@ -129,4 +142,7 @@ sentrycrashsc_initWithMachineContext(SentryCrashStackCursor *cursor, int maxStac
context->machineContext = machineContext;
context->maxStackDepth = maxStackDepth;
context->instructionAddress = cursor->stackEntry.address;
+
+ SentryCrashThread thread = sentrycrashmc_getThreadFromContext(machineContext);
+ cursor->async_caller = sentrycrash_get_async_caller_for_thread(thread);
}
diff --git a/Sources/SentryCrash/Recording/Tools/SentryCrashStackCursor_SelfThread.c b/Sources/SentryCrash/Recording/Tools/SentryCrashStackCursor_SelfThread.c
index 7eda812619b..38af9638144 100644
--- a/Sources/SentryCrash/Recording/Tools/SentryCrashStackCursor_SelfThread.c
+++ b/Sources/SentryCrash/Recording/Tools/SentryCrashStackCursor_SelfThread.c
@@ -44,4 +44,6 @@ sentrycrashsc_initSelfThread(SentryCrashStackCursor *cursor, int skipEntries)
SelfThreadContext *context = (SelfThreadContext *)cursor->context;
int backtraceLength = backtrace((void **)context->backtrace, MAX_BACKTRACE_LENGTH);
sentrycrashsc_initWithBacktrace(cursor, context->backtrace, backtraceLength, skipEntries + 1);
+
+ cursor->async_caller = sentrycrash_get_async_caller_for_thread(sentrycrashthread_self());
}
diff --git a/Sources/SentryCrash/Recording/Tools/SentryHook.c b/Sources/SentryCrash/Recording/Tools/SentryHook.c
new file mode 100644
index 00000000000..30bfbf25f2c
--- /dev/null
+++ b/Sources/SentryCrash/Recording/Tools/SentryHook.c
@@ -0,0 +1,227 @@
+#include "SentryHook.h"
+#include "fishhook.h"
+#include
+#include
+#include
+#include
+
+// NOTE on accessing thread-locals across threads:
+// We save the async stacktrace as a thread local when dispatching async calls,
+// but the various crash handlers need to access these thread-locals across threads
+// sometimes, which we do here:
+// While `pthread_t` is an opaque type, the offset of `thread specific data` (tsd)
+// is fixed due to backwards compatibility.
+// See:
+// https://github.com/apple/darwin-libpthread/blob/c60d249cc84dfd6097a7e71c68a36b47cbe076d1/src/types_internal.h#L409-L432
+
+#if __LP64__
+# define TSD_OFFSET 224
+#else
+# define TSD_OFFSET 176
+#endif
+
+static pthread_key_t async_caller_key = 0;
+
+sentrycrash_async_backtrace_t *
+sentrycrash_get_async_caller_for_thread(SentryCrashThread thread)
+{
+ const pthread_t pthread = pthread_from_mach_thread_np((thread_t)thread);
+ void **tsd_slots = (void *)((uint8_t *)pthread + TSD_OFFSET);
+
+ return (sentrycrash_async_backtrace_t *)__atomic_load_n(
+ &tsd_slots[async_caller_key], __ATOMIC_SEQ_CST);
+}
+
+void
+sentrycrash__async_backtrace_incref(sentrycrash_async_backtrace_t *bt)
+{
+ if (!bt) {
+ return;
+ }
+ __atomic_fetch_add(&bt->refcount, 1, __ATOMIC_SEQ_CST);
+}
+
+void
+sentrycrash__async_backtrace_decref(sentrycrash_async_backtrace_t *bt)
+{
+ if (!bt) {
+ return;
+ }
+ if (__atomic_fetch_add(&bt->refcount, -1, __ATOMIC_SEQ_CST) == 1) {
+ sentrycrash__async_backtrace_decref(bt->async_caller);
+ free(bt);
+ }
+}
+
+sentrycrash_async_backtrace_t *
+sentrycrash__async_backtrace_capture(void)
+{
+ sentrycrash_async_backtrace_t *bt = malloc(sizeof(sentrycrash_async_backtrace_t));
+ bt->refcount = 1;
+
+ bt->len = backtrace(bt->backtrace, MAX_BACKTRACE_FRAMES);
+
+ sentrycrash_async_backtrace_t *caller = pthread_getspecific(async_caller_key);
+ sentrycrash__async_backtrace_incref(caller);
+ bt->async_caller = caller;
+
+ return bt;
+}
+
+static void (*real_dispatch_async)(dispatch_queue_t queue, dispatch_block_t block);
+
+void
+sentrycrash__hook_dispatch_async(dispatch_queue_t queue, dispatch_block_t block)
+{
+ // create a backtrace, capturing the async callsite
+ sentrycrash_async_backtrace_t *bt = sentrycrash__async_backtrace_capture();
+
+ return real_dispatch_async(queue, ^{
+ // inside the async context, save the backtrace in a thread local for later consumption
+ pthread_setspecific(async_caller_key, bt);
+
+ // call through to the original block
+ block();
+
+ // and decref our current backtrace
+ pthread_setspecific(async_caller_key, NULL);
+ sentrycrash__async_backtrace_decref(bt);
+ });
+}
+
+static void (*real_dispatch_async_f)(
+ dispatch_queue_t queue, void *_Nullable context, dispatch_function_t work);
+
+void
+sentrycrash__hook_dispatch_async_f(
+ dispatch_queue_t queue, void *_Nullable context, dispatch_function_t work)
+{
+ sentrycrash__hook_dispatch_async(queue, ^{ work(context); });
+}
+
+static void (*real_dispatch_after)(
+ dispatch_time_t when, dispatch_queue_t queue, dispatch_block_t block);
+
+void
+sentrycrash__hook_dispatch_after(
+ dispatch_time_t when, dispatch_queue_t queue, dispatch_block_t block)
+{
+ // create a backtrace, capturing the async callsite
+ sentrycrash_async_backtrace_t *bt = sentrycrash__async_backtrace_capture();
+
+ return real_dispatch_after(when, queue, ^{
+ // inside the async context, save the backtrace in a thread local for later consumption
+ pthread_setspecific(async_caller_key, bt);
+
+ // call through to the original block
+ block();
+
+ // and decref our current backtrace
+ pthread_setspecific(async_caller_key, NULL);
+ sentrycrash__async_backtrace_decref(bt);
+ });
+}
+
+static void (*real_dispatch_after_f)(dispatch_time_t when, dispatch_queue_t queue,
+ void *_Nullable context, dispatch_function_t work);
+
+void
+sentrycrash__hook_dispatch_after_f(
+ dispatch_time_t when, dispatch_queue_t queue, void *_Nullable context, dispatch_function_t work)
+{
+ sentrycrash__hook_dispatch_after(when, queue, ^{ work(context); });
+}
+
+static void (*real_dispatch_barrier_async)(dispatch_queue_t queue, dispatch_block_t block);
+
+void
+sentrycrash__hook_dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block)
+{
+ // create a backtrace, capturing the async callsite
+ sentrycrash_async_backtrace_t *bt = sentrycrash__async_backtrace_capture();
+
+ return real_dispatch_barrier_async(queue, ^{
+ // inside the async context, save the backtrace in a thread local for later consumption
+ pthread_setspecific(async_caller_key, bt);
+
+ // call through to the original block
+ block();
+
+ // and decref our current backtrace
+ pthread_setspecific(async_caller_key, NULL);
+ sentrycrash__async_backtrace_decref(bt);
+ });
+}
+
+static void (*real_dispatch_barrier_async_f)(
+ dispatch_queue_t queue, void *_Nullable context, dispatch_function_t work);
+
+void
+sentrycrash__hook_dispatch_barrier_async_f(
+ dispatch_queue_t queue, void *_Nullable context, dispatch_function_t work)
+{
+ sentrycrash__hook_dispatch_barrier_async(queue, ^{ work(context); });
+}
+
+static bool hooks_installed = false;
+
+void
+sentrycrash_install_async_hooks(void)
+{
+ if (__atomic_exchange_n(&hooks_installed, true, __ATOMIC_SEQ_CST)) {
+ return;
+ }
+ if (pthread_key_create(&async_caller_key, NULL) != 0) {
+ return;
+ }
+
+ sentrycrash__hook_rebind_symbols(
+ (struct rebinding[1]) {
+ { "dispatch_async", sentrycrash__hook_dispatch_async, (void *)&real_dispatch_async },
+ },
+ 1);
+ sentrycrash__hook_rebind_symbols(
+ (struct rebinding[1]) {
+ { "dispatch_async_f", sentrycrash__hook_dispatch_async_f,
+ (void *)&real_dispatch_async_f },
+ },
+ 1);
+ sentrycrash__hook_rebind_symbols(
+ (struct rebinding[1]) {
+ { "dispatch_after", sentrycrash__hook_dispatch_after, (void *)&real_dispatch_after },
+ },
+ 1);
+ sentrycrash__hook_rebind_symbols(
+ (struct rebinding[1]) {
+ { "dispatch_after_f", sentrycrash__hook_dispatch_after_f,
+ (void *)&real_dispatch_after_f },
+ },
+ 1);
+ sentrycrash__hook_rebind_symbols(
+ (struct rebinding[1]) {
+ { "dispatch_barrier_async", sentrycrash__hook_dispatch_barrier_async,
+ (void *)&real_dispatch_barrier_async },
+ },
+ 1);
+ sentrycrash__hook_rebind_symbols(
+ (struct rebinding[1]) {
+ { "dispatch_barrier_async_f", sentrycrash__hook_dispatch_barrier_async_f,
+ (void *)&real_dispatch_barrier_async_f },
+ },
+ 1);
+
+ // NOTE: We will *not* hook the following functions:
+ //
+ // - dispatch_async_and_wait
+ // - dispatch_async_and_wait_f
+ // - dispatch_barrier_async_and_wait
+ // - dispatch_barrier_async_and_wait_f
+ //
+ // Because these functions `will use the stack of the submitting thread` in some cases
+ // and our thread tracking logic would do the wrong thing in that case.
+ //
+ // See:
+ // https://github.com/apple/swift-corelibs-libdispatch/blob/f13ea5dcc055e5d2d7c02e90d8c9907ca9dc72e1/private/workloop_private.h#L321-L326
+}
+
+// TODO: uninstall hooks
diff --git a/Sources/SentryCrash/Recording/Tools/SentryHook.h b/Sources/SentryCrash/Recording/Tools/SentryHook.h
new file mode 100644
index 00000000000..b539f55b945
--- /dev/null
+++ b/Sources/SentryCrash/Recording/Tools/SentryHook.h
@@ -0,0 +1,38 @@
+#ifndef SENRTY_HOOK_h
+#define SENRTY_HOOK_h
+
+#include "SentryCrashThread.h"
+#include
+
+#define MAX_BACKTRACE_FRAMES 128
+
+/**
+ * This represents a stacktrace that can optionally have an `async_caller` and form an async call
+ * chain.
+ */
+typedef struct sentrycrash_async_backtrace_s sentrycrash_async_backtrace_t;
+struct sentrycrash_async_backtrace_s {
+ size_t refcount;
+ sentrycrash_async_backtrace_t *async_caller;
+ size_t len;
+ void *backtrace[MAX_BACKTRACE_FRAMES];
+};
+
+/**
+ * Returns the async caller of the current calling context, if any.
+ */
+sentrycrash_async_backtrace_t *sentrycrash_get_async_caller_for_thread(SentryCrashThread);
+
+/**
+ * Installs the various async hooks that sentry offers.
+ *
+ * The hooks work like this:
+ * We overwrite the `libdispatch/dispatch_async`, etc functions with our own wrapper.
+ * Those wrappers create a stacktrace in the calling thread, and pass that stacktrace via a closure
+ * into the callee thread. In the callee, the stacktrace is saved as a thread-local before invoking
+ * the original block/function. The thread local can be accessed for inspection and is also used for
+ * chained async calls.
+ */
+void sentrycrash_install_async_hooks(void);
+
+#endif /* SENRTY_HOOK_h */
diff --git a/Sources/SentryCrash/Recording/Tools/fishhook.c b/Sources/SentryCrash/Recording/Tools/fishhook.c
new file mode 100644
index 00000000000..cb64e81d5f8
--- /dev/null
+++ b/Sources/SentryCrash/Recording/Tools/fishhook.c
@@ -0,0 +1,268 @@
+// Copyright (c) 2013, Facebook, Inc.
+// All rights reserved.
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+// * Neither the name Facebook nor the names of its contributors may be used to
+// endorse or promote products derived from this software without specific
+// prior written permission.
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "fishhook.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#ifdef __LP64__
+typedef struct mach_header_64 mach_header_t;
+typedef struct segment_command_64 segment_command_t;
+typedef struct section_64 section_t;
+typedef struct nlist_64 nlist_t;
+# define LC_SEGMENT_ARCH_DEPENDENT LC_SEGMENT_64
+#else
+typedef struct mach_header mach_header_t;
+typedef struct segment_command segment_command_t;
+typedef struct section section_t;
+typedef struct nlist nlist_t;
+# define LC_SEGMENT_ARCH_DEPENDENT LC_SEGMENT
+#endif
+
+#ifndef SEG_DATA_CONST
+# define SEG_DATA_CONST "__DATA_CONST"
+#endif
+
+#ifndef SEG_AUTH_CONST
+# define SEG_AUTH_CONST "__AUTH_CONST"
+#endif
+
+struct rebindings_entry {
+ struct rebinding *rebindings;
+ size_t rebindings_nel;
+ struct rebindings_entry *next;
+};
+
+static struct rebindings_entry *_rebindings_head;
+
+static int
+prepend_rebindings(
+ struct rebindings_entry **rebindings_head, struct rebinding rebindings[], size_t nel)
+{
+ struct rebindings_entry *new_entry
+ = (struct rebindings_entry *)malloc(sizeof(struct rebindings_entry));
+ if (!new_entry) {
+ return -1;
+ }
+ new_entry->rebindings = (struct rebinding *)malloc(sizeof(struct rebinding) * nel);
+ if (!new_entry->rebindings) {
+ free(new_entry);
+ return -1;
+ }
+ memcpy(new_entry->rebindings, rebindings, sizeof(struct rebinding) * nel);
+ new_entry->rebindings_nel = nel;
+ new_entry->next = *rebindings_head;
+ *rebindings_head = new_entry;
+ return 0;
+}
+
+static vm_prot_t
+get_protection(void *sectionStart)
+{
+ mach_port_t task = mach_task_self();
+ vm_size_t size = 0;
+ vm_address_t address = (vm_address_t)sectionStart;
+ memory_object_name_t object;
+#if __LP64__
+ mach_msg_type_number_t count = VM_REGION_BASIC_INFO_COUNT_64;
+ vm_region_basic_info_data_64_t info;
+ kern_return_t info_ret = vm_region_64(task, &address, &size, VM_REGION_BASIC_INFO_64,
+ (vm_region_info_64_t)&info, &count, &object);
+#else
+ mach_msg_type_number_t count = VM_REGION_BASIC_INFO_COUNT;
+ vm_region_basic_info_data_t info;
+ kern_return_t info_ret = vm_region(
+ task, &address, &size, VM_REGION_BASIC_INFO, (vm_region_info_t)&info, &count, &object);
+#endif
+ if (info_ret == KERN_SUCCESS) {
+ return info.protection;
+ } else {
+ return VM_PROT_READ;
+ }
+}
+
+static void
+perform_rebinding_with_section(struct rebindings_entry *rebindings, section_t *section,
+ intptr_t slide, nlist_t *symtab, char *strtab, uint32_t *indirect_symtab)
+{
+ uint32_t *indirect_symbol_indices = indirect_symtab + section->reserved1;
+ void **indirect_symbol_bindings = (void **)((uintptr_t)slide + section->addr);
+ for (uint i = 0; i < section->size / sizeof(void *); i++) {
+ uint32_t symtab_index = indirect_symbol_indices[i];
+ if (symtab_index == INDIRECT_SYMBOL_ABS || symtab_index == INDIRECT_SYMBOL_LOCAL
+ || symtab_index == (INDIRECT_SYMBOL_LOCAL | INDIRECT_SYMBOL_ABS)) {
+ continue;
+ }
+ uint32_t strtab_offset = symtab[symtab_index].n_un.n_strx;
+ char *symbol_name = strtab + strtab_offset;
+ bool symbol_name_longer_than_1 = symbol_name[0] && symbol_name[1];
+ struct rebindings_entry *cur = rebindings;
+ while (cur) {
+ for (uint j = 0; j < cur->rebindings_nel; j++) {
+ if (symbol_name_longer_than_1
+ && strcmp(&symbol_name[1], cur->rebindings[j].name) == 0) {
+ if (cur->rebindings[j].replaced != NULL
+ && indirect_symbol_bindings[i] != cur->rebindings[j].replacement) {
+ *(cur->rebindings[j].replaced) = indirect_symbol_bindings[i];
+ }
+ bool is_prot_changed = false;
+ void **address_to_write = indirect_symbol_bindings + i;
+ vm_prot_t prot = get_protection(address_to_write);
+ if ((prot & VM_PROT_WRITE) == 0) {
+ is_prot_changed = true;
+ kern_return_t success
+ = vm_protect(mach_task_self(), (vm_address_t)address_to_write,
+ sizeof(void *), false, prot | VM_PROT_WRITE);
+ if (success != KERN_SUCCESS) {
+ goto symbol_loop;
+ }
+ }
+ indirect_symbol_bindings[i] = cur->rebindings[j].replacement;
+ if (is_prot_changed) {
+ vm_protect(mach_task_self(), (vm_address_t)address_to_write, sizeof(void *),
+ false, prot);
+ }
+ goto symbol_loop;
+ }
+ }
+ cur = cur->next;
+ }
+ symbol_loop:;
+ }
+}
+
+static void
+rebind_symbols_for_image(
+ struct rebindings_entry *rebindings, const struct mach_header *header, intptr_t slide)
+{
+ Dl_info info;
+ if (dladdr(header, &info) == 0) {
+ return;
+ }
+
+ segment_command_t *cur_seg_cmd;
+ segment_command_t *linkedit_segment = NULL;
+ struct symtab_command *symtab_cmd = NULL;
+ struct dysymtab_command *dysymtab_cmd = NULL;
+
+ uintptr_t cur = (uintptr_t)header + sizeof(mach_header_t);
+ for (uint i = 0; i < header->ncmds; i++, cur += cur_seg_cmd->cmdsize) {
+ cur_seg_cmd = (segment_command_t *)cur;
+ if (cur_seg_cmd->cmd == LC_SEGMENT_ARCH_DEPENDENT) {
+ if (strcmp(cur_seg_cmd->segname, SEG_LINKEDIT) == 0) {
+ linkedit_segment = cur_seg_cmd;
+ }
+ } else if (cur_seg_cmd->cmd == LC_SYMTAB) {
+ symtab_cmd = (struct symtab_command *)cur_seg_cmd;
+ } else if (cur_seg_cmd->cmd == LC_DYSYMTAB) {
+ dysymtab_cmd = (struct dysymtab_command *)cur_seg_cmd;
+ }
+ }
+
+ if (!symtab_cmd || !dysymtab_cmd || !linkedit_segment || !dysymtab_cmd->nindirectsyms) {
+ return;
+ }
+
+ // Find base symbol/string table addresses
+ uintptr_t linkedit_base
+ = (uintptr_t)slide + linkedit_segment->vmaddr - linkedit_segment->fileoff;
+ nlist_t *symtab = (nlist_t *)(linkedit_base + symtab_cmd->symoff);
+ char *strtab = (char *)(linkedit_base + symtab_cmd->stroff);
+
+ // Get indirect symbol table (array of uint32_t indices into symbol table)
+ uint32_t *indirect_symtab = (uint32_t *)(linkedit_base + dysymtab_cmd->indirectsymoff);
+
+ cur = (uintptr_t)header + sizeof(mach_header_t);
+ for (uint i = 0; i < header->ncmds; i++, cur += cur_seg_cmd->cmdsize) {
+ cur_seg_cmd = (segment_command_t *)cur;
+ if (cur_seg_cmd->cmd == LC_SEGMENT_ARCH_DEPENDENT) {
+ if (strcmp(cur_seg_cmd->segname, SEG_DATA) != 0
+ && strcmp(cur_seg_cmd->segname, SEG_DATA_CONST) != 0
+ && strcmp(cur_seg_cmd->segname, SEG_AUTH_CONST) != 0) {
+ continue;
+ }
+ for (uint j = 0; j < cur_seg_cmd->nsects; j++) {
+ section_t *sect = (section_t *)(cur + sizeof(segment_command_t)) + j;
+ if ((sect->flags & SECTION_TYPE) == S_LAZY_SYMBOL_POINTERS) {
+ perform_rebinding_with_section(
+ rebindings, sect, slide, symtab, strtab, indirect_symtab);
+ }
+ if ((sect->flags & SECTION_TYPE) == S_NON_LAZY_SYMBOL_POINTERS) {
+ perform_rebinding_with_section(
+ rebindings, sect, slide, symtab, strtab, indirect_symtab);
+ }
+ }
+ }
+ }
+}
+
+static void
+_rebind_symbols_for_image(const struct mach_header *header, intptr_t slide)
+{
+ rebind_symbols_for_image(_rebindings_head, header, slide);
+}
+
+int
+sentrycrash__hook_rebind_symbols_image(
+ void *header, intptr_t slide, struct rebinding rebindings[], size_t rebindings_nel)
+{
+ struct rebindings_entry *rebindings_head = NULL;
+ int retval = prepend_rebindings(&rebindings_head, rebindings, rebindings_nel);
+ rebind_symbols_for_image(rebindings_head, (const struct mach_header *)header, slide);
+ if (rebindings_head) {
+ free(rebindings_head->rebindings);
+ }
+ free(rebindings_head);
+ return retval;
+}
+
+int
+sentrycrash__hook_rebind_symbols(struct rebinding rebindings[], size_t rebindings_nel)
+{
+ int retval = prepend_rebindings(&_rebindings_head, rebindings, rebindings_nel);
+ if (retval < 0) {
+ return retval;
+ }
+ // If this was the first call, register callback for image additions (which is also invoked for
+ // existing images, otherwise, just run on existing images
+ if (!_rebindings_head->next) {
+ _dyld_register_func_for_add_image(_rebind_symbols_for_image);
+ } else {
+ uint32_t c = _dyld_image_count();
+ for (uint32_t i = 0; i < c; i++) {
+ _rebind_symbols_for_image(_dyld_get_image_header(i), _dyld_get_image_vmaddr_slide(i));
+ }
+ }
+ return retval;
+}
diff --git a/Sources/SentryCrash/Recording/Tools/fishhook.h b/Sources/SentryCrash/Recording/Tools/fishhook.h
new file mode 100644
index 00000000000..eba1b877cdc
--- /dev/null
+++ b/Sources/SentryCrash/Recording/Tools/fishhook.h
@@ -0,0 +1,73 @@
+// Copyright (c) 2013, Facebook, Inc.
+// All rights reserved.
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+// * Neither the name Facebook nor the names of its contributors may be used to
+// endorse or promote products derived from this software without specific
+// prior written permission.
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef fishhook_h
+#define fishhook_h
+
+#include
+#include
+
+#if !defined(FISHHOOK_EXPORT)
+# define FISHHOOK_VISIBILITY __attribute__((visibility("hidden")))
+#else
+# define FISHHOOK_VISIBILITY __attribute__((visibility("default")))
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif //__cplusplus
+
+/*
+ * A structure representing a particular intended rebinding from a symbol
+ * name to its replacement
+ */
+struct rebinding {
+ const char *name;
+ void *replacement;
+ void **replaced;
+};
+
+/*
+ * For each rebinding in rebindings, rebinds references to external, indirect
+ * symbols with the specified name to instead point at replacement for each
+ * image in the calling process as well as for all future images that are loaded
+ * by the process. If rebind_functions is called more than once, the symbols to
+ * rebind are added to the existing list of rebindings, and if a given symbol
+ * is rebound more than once, the later rebinding will take precedence.
+ */
+FISHHOOK_VISIBILITY
+int sentrycrash__hook_rebind_symbols(struct rebinding rebindings[], size_t rebindings_nel);
+
+/*
+ * Rebinds as above, but only in the specified image. The header should point
+ * to the mach-o header, the slide should be the slide offset. Others as above.
+ */
+FISHHOOK_VISIBILITY
+int sentrycrash__hook_rebind_symbols_image(
+ void *header, intptr_t slide, struct rebinding rebindings[], size_t rebindings_nel);
+
+#ifdef __cplusplus
+}
+#endif //__cplusplus
+
+#endif // fishhook_h
diff --git a/Tests/SentryTests/SentryCrash/SentryStacktraceBuilderTests.swift b/Tests/SentryTests/SentryCrash/SentryStacktraceBuilderTests.swift
index 580b7e55a59..c1174bf941e 100644
--- a/Tests/SentryTests/SentryCrash/SentryStacktraceBuilderTests.swift
+++ b/Tests/SentryTests/SentryCrash/SentryStacktraceBuilderTests.swift
@@ -53,4 +53,44 @@ class SentryStacktraceBuilderTests: XCTestCase {
XCTAssertTrue(filteredFrames.count == 2, "The frames must be ordered from caller to callee, or oldest to youngest.")
}
+
+ func testAsyncStacktraces() throws {
+ SentrySDK.start(options: ["dsn": TestConstants.dsnAsString(username: "SentrySDKTests")])
+
+ let group = DispatchGroup()
+
+ DispatchQueue.main.async {
+ group.enter()
+ self.asyncFrame1(group: group)
+ }
+
+ group.waitWithTimeout()
+ }
+
+ func asyncFrame1(group: DispatchGroup) {
+ DispatchQueue.main.asyncAfter(deadline: DispatchTime.now()) {
+ self.asyncFrame2(group: group)
+ }
+ }
+
+ func asyncFrame2(group: DispatchGroup) {
+ DispatchQueue.main.async {
+ self.asyncAssertion(group: group)
+ }
+ }
+
+ func asyncAssertion(group: DispatchGroup) {
+ let actual = self.fixture.getSut().buildStacktraceForCurrentThread()
+
+ let filteredFrames = actual.frames.filter { frame in
+ return frame.function?.contains("testAsyncStacktraces") ?? false ||
+ frame.function?.contains("asyncFrame1") ?? false ||
+ frame.function?.contains("asyncFrame2") ?? false ||
+ frame.function?.contains("asyncAssertion") ?? false
+ }
+
+ XCTAssertTrue(filteredFrames.count >= 4, "The Stacktrace must include the async callers.")
+
+ group.leave()
+ }
}