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() + } }