diff --git a/Samples/macOS-Swift/macOS-Swift.xcodeproj/xcshareddata/xcschemes/macOS-Swift.xcscheme b/Samples/macOS-Swift/macOS-Swift.xcodeproj/xcshareddata/xcschemes/macOS-Swift.xcscheme
index d6f7f2ad178..2e1233123de 100644
--- a/Samples/macOS-Swift/macOS-Swift.xcodeproj/xcshareddata/xcschemes/macOS-Swift.xcscheme
+++ b/Samples/macOS-Swift/macOS-Swift.xcodeproj/xcshareddata/xcschemes/macOS-Swift.xcscheme
@@ -36,8 +36,8 @@
-
+
+
+
diff --git a/Samples/macOS-Swift/macOS-Swift/CppSample.cpp b/Samples/macOS-Swift/macOS-Swift/CppSample.cpp
index db2e123981f..6ad378d5b1c 100644
--- a/Samples/macOS-Swift/macOS-Swift/CppSample.cpp
+++ b/Samples/macOS-Swift/macOS-Swift/CppSample.cpp
@@ -13,6 +13,12 @@ Sentry::CppSample::throwCPPException(void)
internalFunction();
}
+void
+Sentry::CppSample::noExceptCppException() noexcept
+{
+ throw std::invalid_argument("Invalid Argument.");
+}
+
void
Sentry::CppSample::rethrowNoActiveCPPException(void)
{
diff --git a/Samples/macOS-Swift/macOS-Swift/CppSample.hpp b/Samples/macOS-Swift/macOS-Swift/CppSample.hpp
index 3158df63c3d..f53049b092a 100644
--- a/Samples/macOS-Swift/macOS-Swift/CppSample.hpp
+++ b/Samples/macOS-Swift/macOS-Swift/CppSample.hpp
@@ -8,6 +8,7 @@ namespace Sentry {
class CppSample {
public:
void throwCPPException();
+ void noExceptCppException() noexcept;
void rethrowNoActiveCPPException();
};
}
diff --git a/Samples/macOS-Swift/macOS-Swift/CppWrapper.h b/Samples/macOS-Swift/macOS-Swift/CppWrapper.h
index 9b0ce052e97..b891c576c1f 100644
--- a/Samples/macOS-Swift/macOS-Swift/CppWrapper.h
+++ b/Samples/macOS-Swift/macOS-Swift/CppWrapper.h
@@ -2,6 +2,7 @@
@interface CppWrapper : NSObject
- (void)throwCPPException;
+- (void)noExceptCppException;
- (void)rethrowNoActiveCPPException;
- (void)throwNSRangeException;
@end
diff --git a/Samples/macOS-Swift/macOS-Swift/CppWrapper.m b/Samples/macOS-Swift/macOS-Swift/CppWrapper.m
index 0e738fb0b72..c45294e97fa 100644
--- a/Samples/macOS-Swift/macOS-Swift/CppWrapper.m
+++ b/Samples/macOS-Swift/macOS-Swift/CppWrapper.m
@@ -10,6 +10,12 @@ - (void)throwCPPException
cppTool.throwCPPException();
}
+- (void)noExceptCppException
+{
+ Sentry::CppSample cppTool;
+ cppTool.noExceptCppException();
+}
+
- (void)rethrowNoActiveCPPException
{
Sentry::CppSample cppTool;
diff --git a/Samples/macOS-Swift/macOS-Swift/ViewController.swift b/Samples/macOS-Swift/macOS-Swift/ViewController.swift
index 9692b9032ad..5db44dba079 100644
--- a/Samples/macOS-Swift/macOS-Swift/ViewController.swift
+++ b/Samples/macOS-Swift/macOS-Swift/ViewController.swift
@@ -79,7 +79,19 @@ class ViewController: NSViewController {
let wrapper = CppWrapper()
wrapper.throwCPPException()
}
-
+
+ @IBAction func cppExceptionBGThread(_ sender: Any) {
+ DispatchQueue.global().async {
+ let wrapper = CppWrapper()
+ wrapper.throwCPPException()
+ }
+ }
+
+ @IBAction func noExceptCppException(_ sender: Any) {
+ let wrapper = CppWrapper()
+ wrapper.noExceptCppException()
+ }
+
@IBAction func rethrowNoActiveCppException(_ sender: Any) {
let wrapper = CppWrapper()
wrapper.rethrowNoActiveCPPException()
diff --git a/Sentry.xcodeproj/project.pbxproj b/Sentry.xcodeproj/project.pbxproj
index 276c5c89d2f..3f0f090e5e5 100644
--- a/Sentry.xcodeproj/project.pbxproj
+++ b/Sentry.xcodeproj/project.pbxproj
@@ -87,6 +87,7 @@
620467AC2D3FFD230025F06C /* SentryNSErrorCodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 620467AB2D3FFD1C0025F06C /* SentryNSErrorCodable.swift */; };
6205B4A42CE73AA100744684 /* TestDebugImageProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = D85790282976A69F00C6AC1F /* TestDebugImageProvider.swift */; };
6205CF262D549D8A001E6049 /* SentryDateCodableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6205CF252D549D8A001E6049 /* SentryDateCodableTests.swift */; };
+ 621655662DB12A8900810504 /* SentryCrashMach-OTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 621655652DB12A8900810504 /* SentryCrashMach-OTests.m */; };
621AE74B2C626C230012E730 /* SentryANRTrackerV2.h in Headers */ = {isa = PBXBuildFile; fileRef = 621AE74A2C626C230012E730 /* SentryANRTrackerV2.h */; };
621AE74D2C626C510012E730 /* SentryANRTrackerV2.m in Sources */ = {isa = PBXBuildFile; fileRef = 621AE74C2C626C510012E730 /* SentryANRTrackerV2.m */; };
621D9F2F2B9B0320003D94DE /* SentryCurrentDateProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 621D9F2E2B9B0320003D94DE /* SentryCurrentDateProvider.swift */; };
@@ -118,9 +119,14 @@
62872B5F2BA1B7F300A4FA7D /* NSLock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62872B5E2BA1B7F300A4FA7D /* NSLock.swift */; };
62872B632BA1B86100A4FA7D /* NSLockTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62872B622BA1B86100A4FA7D /* NSLockTests.swift */; };
62885DA729E946B100554F38 /* TestConncurrentModifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62885DA629E946B100554F38 /* TestConncurrentModifications.swift */; };
+ 628B45342DB8D5E700934391 /* SentryCrashCxaThrowSwapper_Tests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 628B45332DB8D5E700934391 /* SentryCrashCxaThrowSwapper_Tests.mm */; };
628B89022D841D7F004B6F2A /* SentryDateUtilsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 628B89012D841D7F004B6F2A /* SentryDateUtilsTests.swift */; };
629194A92D51F976000F7C6B /* SentryDebugMetaCodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 629194A82D51F976000F7C6B /* SentryDebugMetaCodable.swift */; };
629258552DAF91940049388F /* SentryCrashStackCursorSelfThreadTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 629258542DAF91940049388F /* SentryCrashStackCursorSelfThreadTests.swift */; };
+ 629258592DAFA57F0049388F /* SentryCrashCxaThrowSwapper.h in Headers */ = {isa = PBXBuildFile; fileRef = 629258582DAFA57F0049388F /* SentryCrashCxaThrowSwapper.h */; };
+ 6292585B2DAFA5F70049388F /* SentryCrashCxaThrowSwapper.c in Sources */ = {isa = PBXBuildFile; fileRef = 6292585A2DAFA5F70049388F /* SentryCrashCxaThrowSwapper.c */; };
+ 6292585E2DAFA8290049388F /* SentryCrashMach-O.h in Headers */ = {isa = PBXBuildFile; fileRef = 6292585C2DAFA8290049388F /* SentryCrashMach-O.h */; };
+ 6292585F2DAFA8290049388F /* SentryCrashMach-O.c in Sources */ = {isa = PBXBuildFile; fileRef = 6292585D2DAFA8290049388F /* SentryCrashMach-O.c */; };
6293F5752D422A95002BC3BD /* SentryStacktraceCodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6293F5742D422A8A002BC3BD /* SentryStacktraceCodable.swift */; };
629428802CB3BF69002C454C /* SwizzleClassNameExclude.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6294287F2CB3BF4E002C454C /* SwizzleClassNameExclude.swift */; };
6294774C2C6F255F00846CBC /* SentryANRTrackerV2Delegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6294774B2C6F255F00846CBC /* SentryANRTrackerV2Delegate.swift */; };
@@ -1170,6 +1176,7 @@
620379DC2AFE1432005AC0C1 /* SentryBuildAppStartSpans.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentryBuildAppStartSpans.m; sourceTree = ""; };
620467AB2D3FFD1C0025F06C /* SentryNSErrorCodable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryNSErrorCodable.swift; sourceTree = ""; };
6205CF252D549D8A001E6049 /* SentryDateCodableTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryDateCodableTests.swift; sourceTree = ""; };
+ 621655652DB12A8900810504 /* SentryCrashMach-OTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "SentryCrashMach-OTests.m"; sourceTree = ""; };
621AE74A2C626C230012E730 /* SentryANRTrackerV2.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentryANRTrackerV2.h; path = include/SentryANRTrackerV2.h; sourceTree = ""; };
621AE74C2C626C510012E730 /* SentryANRTrackerV2.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentryANRTrackerV2.m; sourceTree = ""; };
621AE74E2C626CF70012E730 /* SentryANRTrackerV2Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryANRTrackerV2Tests.swift; sourceTree = ""; };
@@ -1203,9 +1210,14 @@
62872B5E2BA1B7F300A4FA7D /* NSLock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSLock.swift; sourceTree = ""; };
62872B622BA1B86100A4FA7D /* NSLockTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSLockTests.swift; sourceTree = ""; };
62885DA629E946B100554F38 /* TestConncurrentModifications.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestConncurrentModifications.swift; sourceTree = ""; };
+ 628B45332DB8D5E700934391 /* SentryCrashCxaThrowSwapper_Tests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = SentryCrashCxaThrowSwapper_Tests.mm; sourceTree = ""; };
628B89012D841D7F004B6F2A /* SentryDateUtilsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryDateUtilsTests.swift; sourceTree = ""; };
629194A82D51F976000F7C6B /* SentryDebugMetaCodable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryDebugMetaCodable.swift; sourceTree = ""; };
629258542DAF91940049388F /* SentryCrashStackCursorSelfThreadTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryCrashStackCursorSelfThreadTests.swift; sourceTree = ""; };
+ 629258582DAFA57F0049388F /* SentryCrashCxaThrowSwapper.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SentryCrashCxaThrowSwapper.h; sourceTree = ""; };
+ 6292585A2DAFA5F70049388F /* SentryCrashCxaThrowSwapper.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = SentryCrashCxaThrowSwapper.c; sourceTree = ""; };
+ 6292585C2DAFA8290049388F /* SentryCrashMach-O.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SentryCrashMach-O.h"; sourceTree = ""; };
+ 6292585D2DAFA8290049388F /* SentryCrashMach-O.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = "SentryCrashMach-O.c"; sourceTree = ""; };
6293F5742D422A8A002BC3BD /* SentryStacktraceCodable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryStacktraceCodable.swift; sourceTree = ""; };
6294287F2CB3BF4E002C454C /* SwizzleClassNameExclude.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwizzleClassNameExclude.swift; sourceTree = ""; };
6294774B2C6F255F00846CBC /* SentryANRTrackerV2Delegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryANRTrackerV2Delegate.swift; sourceTree = ""; };
@@ -2996,6 +3008,10 @@
7B883F48253D714C00879E62 /* SentryCrashUUIDConversion.c */,
71F11CDEF5952DF5CC69AC74 /* SentryCrashUUIDConversion.h */,
7B31C290277B04A000337126 /* SentryCrashPlatformSpecificDefines.h */,
+ 629258582DAFA57F0049388F /* SentryCrashCxaThrowSwapper.h */,
+ 6292585A2DAFA5F70049388F /* SentryCrashCxaThrowSwapper.c */,
+ 6292585C2DAFA8290049388F /* SentryCrashMach-O.h */,
+ 6292585D2DAFA8290049388F /* SentryCrashMach-O.c */,
);
path = Tools;
sourceTree = "";
@@ -3064,6 +3080,8 @@
0ADC33EF28D9BE690078D980 /* TestSentryUIDeviceWrapper.swift */,
7B984A9E28E572AF001F4BEE /* CrashReport.swift */,
7BF69E062987D1FE002EBCA4 /* SentryCrashDoctorTests.swift */,
+ 621655652DB12A8900810504 /* SentryCrashMach-OTests.m */,
+ 628B45332DB8D5E700934391 /* SentryCrashCxaThrowSwapper_Tests.mm */,
629258542DAF91940049388F /* SentryCrashStackCursorSelfThreadTests.swift */,
);
path = SentryCrash;
@@ -4435,6 +4453,7 @@
7BFC169B2524995700FF6266 /* SentryMessage.h in Headers */,
7B18DE4028D9F748004845C6 /* SentryNSNotificationCenterWrapper.h in Headers */,
03F84D1E27DD414C008FE43F /* SentryBacktrace.hpp in Headers */,
+ 629258592DAFA57F0049388F /* SentryCrashCxaThrowSwapper.h in Headers */,
63AA76991EB9C1C200D153DE /* SentryDefines.h in Headers */,
D86B6835294348A400B8B1FC /* SentryAttachment+Private.h in Headers */,
84AF45A629A7FFA500FBB177 /* SentryProfiledTracerConcurrency.h in Headers */,
@@ -4625,6 +4644,7 @@
62A456E32B0370AA003F19A1 /* SentryUIEventTrackerTransactionMode.h in Headers */,
63FE714B20DA4C1100CDBAE8 /* SentryCrashString.h in Headers */,
7BCFBD6D2681D0A900BC27D8 /* SentryCrashScopeObserver.h in Headers */,
+ 6292585E2DAFA8290049388F /* SentryCrashMach-O.h in Headers */,
63FE715320DA4C1100CDBAE8 /* SentryCrashObjCApple.h in Headers */,
63FE710120DA4C1000CDBAE8 /* SentryCrashDate.h in Headers */,
7B4E24FC251C97B500060D68 /* SentrySession.h in Headers */,
@@ -4983,6 +5003,7 @@
62C97D3A2CC64E6B00DDA204 /* SentryUncaughtNSExceptions.m in Sources */,
03F84D3727DD4191008FE43F /* SentrySamplingProfiler.cpp in Sources */,
8453421628BE8A9500C22EEC /* SentrySpanStatus.m in Sources */,
+ 6292585B2DAFA5F70049388F /* SentryCrashCxaThrowSwapper.c in Sources */,
7B08A3472924CF9C0059603A /* SentryMetricKitIntegration.m in Sources */,
623FD9022D3FA5E000803EDA /* SentryFrameCodable.swift in Sources */,
62BDDD122D51FD540024CCD1 /* SentryThreadCodable.swift in Sources */,
@@ -5274,6 +5295,7 @@
D820CDB72BB1895F00BA339D /* SentrySessionReplayIntegration.m in Sources */,
632F43521F581D5400A18A36 /* SentryCrashExceptionApplication.m in Sources */,
620379DD2AFE1432005AC0C1 /* SentryBuildAppStartSpans.m in Sources */,
+ 6292585F2DAFA8290049388F /* SentryCrashMach-O.c in Sources */,
7B77BE3727EC8460003C9020 /* SentryDiscardReasonMapper.m in Sources */,
63FE712520DA4C1000CDBAE8 /* SentryCrashSignalInfo.c in Sources */,
63FE70F320DA4C1000CDBAE8 /* SentryCrashMonitor_Signal.c in Sources */,
@@ -5288,6 +5310,7 @@
8ED3D306264DFE700049393B /* SwiftDescriptorTests.swift in Sources */,
7BFC16AD2524BCE700FF6266 /* SentryMessageTests.swift in Sources */,
7BF6505F292B77EC00BBA5A8 /* SentryMetricKitIntegrationTests.swift in Sources */,
+ 628B45342DB8D5E700934391 /* SentryCrashCxaThrowSwapper_Tests.mm in Sources */,
629690532AD3E060000185FA /* SentryReachabilitySwiftTests.swift in Sources */,
7BA61CC6247CFC5F00C130A8 /* SentryCrashDefaultBinaryImageProviderTests.swift in Sources */,
7BBC827925DFD7D7005F1ED8 /* SentryInAppLogicTests.swift in Sources */,
@@ -5341,6 +5364,7 @@
D8FFE50C2703DBB400607131 /* SwizzlingCallTests.swift in Sources */,
7BFAA6E7297AA16A00E7E02E /* SentryCrashMonitor_CppException_Tests.mm in Sources */,
9286059929A50BAB00F96038 /* SentryGeoTests.swift in Sources */,
+ 621655662DB12A8900810504 /* SentryCrashMach-OTests.m in Sources */,
D8B76B0828081461000A58C4 /* TestSentryScreenShot.swift in Sources */,
A8AFFCD22907DA7600967CD7 /* SentryHttpStatusCodeRangeTests.swift in Sources */,
7BE2C7F8257000A4003B66C7 /* SentryTestIntegration.m in Sources */,
diff --git a/Sources/Sentry/SentryAsyncSafeLog.h b/Sources/Sentry/SentryAsyncSafeLog.h
index 40738e34fa6..81e89f05a48 100644
--- a/Sources/Sentry/SentryAsyncSafeLog.h
+++ b/Sources/Sentry/SentryAsyncSafeLog.h
@@ -58,7 +58,7 @@ void sentry_asyncLogC(const char *level, const char *file, int line, const char
#define SENTRY_ASYNC_SAFE_LOG_LEVEL_DEBUG 40
#define SENTRY_ASYNC_SAFE_LOG_LEVEL_TRACE 50
-#define SENTRY_ASYNC_SAFE_LOG_LEVEL SENTRY_ASYNC_SAFE_LOG_LEVEL_ERROR
+#define SENTRY_ASYNC_SAFE_LOG_LEVEL SENTRY_ASYNC_SAFE_LOG_LEVEL_TRACE
#define a_SENTRY_ASYNC_SAFE_LOG(LEVEL, FMT, ...) \
i_SENTRY_ASYNC_SAFE_LOG(LEVEL, __FILE__, __LINE__, FMT, ##__VA_ARGS__)
diff --git a/Sources/Sentry/SentryCrashIntegration.m b/Sources/Sentry/SentryCrashIntegration.m
index 7ab4776bf38..7bdb8759ae7 100644
--- a/Sources/Sentry/SentryCrashIntegration.m
+++ b/Sources/Sentry/SentryCrashIntegration.m
@@ -3,6 +3,7 @@
#import "SentryCrashC.h"
#import "SentryCrashIntegrationSessionHandler.h"
+#import "SentryCrashMonitor_CPPException.h"
#include "SentryCrashMonitor_Signal.h"
#import "SentryCrashWrapper.h"
#import "SentryDispatchQueueWrapper.h"
@@ -120,7 +121,8 @@ - (BOOL)installWithOptions:(nonnull SentryOptions *)options
[self startCrashHandler:options.cacheDirectoryPath
enableSigtermReporting:enableSigtermReporting
- enableReportingUncaughtExceptions:enableUncaughtNSExceptionReporting];
+ enableReportingUncaughtExceptions:enableUncaughtNSExceptionReporting
+ enableCppExceptionsV2:options.experimental.enableUnhandledCPPExceptionsV2];
[self configureScope];
@@ -139,6 +141,7 @@ - (SentryIntegrationOption)integrationOptions
- (void)startCrashHandler:(NSString *)cacheDirectory
enableSigtermReporting:(BOOL)enableSigtermReporting
enableReportingUncaughtExceptions:(BOOL)enableReportingUncaughtExceptions
+ enableCppExceptionsV2:(BOOL)enableCppExceptionsV2
{
void (^block)(void) = ^{
BOOL canSendReports = NO;
@@ -166,6 +169,10 @@ - (void)startCrashHandler:(NSString *)cacheDirectory
}
#endif // TARGET_OS_OSX
+ if (enableCppExceptionsV2) {
+ sentrycrashcm_cppexception_enable_V2();
+ }
+
// We need to send the crashed event together with the crashed session in the same envelope
// to have proper statistics in release health. To achieve this we need both synchronously
// in the hub. The crashed event is converted from a SentryCrashReport to an event in
diff --git a/Sources/Sentry/SentryCrashReportConverter.m b/Sources/Sentry/SentryCrashReportConverter.m
index efcfbf1f32c..59eb25994e2 100644
--- a/Sources/Sentry/SentryCrashReportConverter.m
+++ b/Sources/Sentry/SentryCrashReportConverter.m
@@ -384,9 +384,13 @@ - (SentryDebugMeta *)debugMetaFromBinaryImageDictionary:(NSDictionary *)sourceIm
if ([exceptionType isEqualToString:@"nsexception"]) {
exception = [self parseNSException];
} else if ([exceptionType isEqualToString:@"cpp_exception"]) {
- exception =
- [[SentryException alloc] initWithValue:self.exceptionContext[@"cpp_exception"][@"name"]
- type:@"C++ Exception"];
+ NSString *cppExceptionName = self.exceptionContext[@"cpp_exception"][@"name"];
+ NSString *cppExceptionReason = self.exceptionContext[@"reason"];
+
+ NSString *exceptionValue =
+ [NSString stringWithFormat:@"%@: %@", cppExceptionName, cppExceptionReason];
+
+ exception = [[SentryException alloc] initWithValue:exceptionValue type:@"C++ Exception"];
} else if ([exceptionType isEqualToString:@"mach"]) {
exception = [[SentryException alloc]
initWithValue:[NSString stringWithFormat:@"Exception %@, Code %@, Subcode %@",
diff --git a/Sources/Sentry/include/SentryCompiler.h b/Sources/Sentry/include/SentryCompiler.h
index e00fc1e43b5..9785cd59b1d 100644
--- a/Sources/Sentry/include/SentryCompiler.h
+++ b/Sources/Sentry/include/SentryCompiler.h
@@ -237,6 +237,11 @@
#define NEVER_INLINE
#endif
+/* KEEP_FUNCTION_IN_STACKTRACE */
+#define KEEP_FUNCTION_IN_STACKTRACE __attribute__((disable_tail_calls))
+
+#define THWART_TAIL_CALL_OPTIMISATION __asm__ __volatile__("");
+
/* NO_RETURN */
#if !defined(NO_RETURN) && COMPILER(GCC_COMPATIBLE)
diff --git a/Sources/SentryCrash/Recording/Monitors/SentryCrashMonitor_CPPException.cpp b/Sources/SentryCrash/Recording/Monitors/SentryCrashMonitor_CPPException.cpp
index f0b9688e390..ec0b37eaf6e 100644
--- a/Sources/SentryCrash/Recording/Monitors/SentryCrashMonitor_CPPException.cpp
+++ b/Sources/SentryCrash/Recording/Monitors/SentryCrashMonitor_CPPException.cpp
@@ -24,6 +24,8 @@
//
#include "SentryCrashMonitor_CPPException.h"
+#include "SentryCompiler.h"
+#include "SentryCrashCxaThrowSwapper.h"
#include "SentryCrashID.h"
#include "SentryCrashMachineContext.h"
#include "SentryCrashMonitorContext.h"
@@ -57,6 +59,8 @@ static volatile bool g_isEnabled = false;
/** True if the handler should capture the next stack trace. */
static bool g_captureNextStackTrace = false;
+static bool g_cxaSwapEnabled = false;
+
static std::terminate_handler g_originalTerminateHandler;
static char g_eventID[37];
@@ -71,6 +75,20 @@ static SentryCrashStackCursor g_stackCursor;
#pragma mark - Callbacks -
// ============================================================================
+static NEVER_INLINE void
+captureStackTrace(void *, std::type_info *tinfo, void (*)(void *)) KEEP_FUNCTION_IN_STACKTRACE
+{
+ SENTRY_ASYNC_SAFE_LOG_TRACE("Entering captureStackTrace");
+
+ if (tinfo != nullptr && strcmp(tinfo->name(), "NSException") == 0) {
+ return;
+ }
+ if (g_captureNextStackTrace) {
+ sentrycrashsc_initSelfThread(&g_stackCursor, 2);
+ }
+ THWART_TAIL_CALL_OPTIMISATION
+}
+
typedef void (*cxa_throw_type)(void *, std::type_info *, void (*)(void *));
typedef void (*cxa_rethrow_type)(void);
@@ -79,30 +97,37 @@ void __cxa_throw(void *thrown_exception, std::type_info *tinfo, void (*dest)(voi
__attribute__((weak));
void
-__cxa_throw(void *thrown_exception, std::type_info *tinfo, void (*dest)(void *))
+__cxa_throw(
+ void *thrown_exception, std::type_info *tinfo, void (*dest)(void *)) KEEP_FUNCTION_IN_STACKTRACE
{
- if (g_captureNextStackTrace) {
- sentrycrashsc_initSelfThread(&g_stackCursor, 1);
- }
+ SENTRY_ASYNC_SAFE_LOG_TRACE("Entering __cxa_throw");
static cxa_throw_type orig_cxa_throw = NULL;
+ if (g_cxaSwapEnabled == false) {
+ captureStackTrace(thrown_exception, tinfo, dest);
+ }
unlikely_if(orig_cxa_throw == NULL)
{
orig_cxa_throw = (cxa_throw_type)dlsym(RTLD_NEXT, "__cxa_throw");
}
orig_cxa_throw(thrown_exception, tinfo, dest);
+ THWART_TAIL_CALL_OPTIMISATION
__builtin_unreachable();
}
void
__sentry_cxa_throw(void *thrown_exception, std::type_info *tinfo, void (*dest)(void *))
{
+ SENTRY_ASYNC_SAFE_LOG_DEBUG("Entering __sentry_cxa_throw");
+
__cxa_throw(thrown_exception, tinfo, dest);
}
void
__sentry_cxa_rethrow()
{
+ SENTRY_ASYNC_SAFE_LOG_DEBUG("Entering __sentry_cxa_rethrow");
+
if (g_captureNextStackTrace) {
sentrycrashsc_initSelfThread(&g_stackCursor, 1);
}
@@ -120,6 +145,9 @@ __sentry_cxa_rethrow()
void
sentrycrashcm_cppexception_callOriginalTerminationHandler(void)
{
+ SENTRY_ASYNC_SAFE_LOG_DEBUG(
+ "Entering sentrycrashcm_cppexception_callOriginalTerminationHandler");
+
// Can be NULL as the return value of set_terminate can be a NULL pointer; see:
// https://en.cppreference.com/w/cpp/error/set_terminate
if (g_originalTerminateHandler != NULL) {
@@ -155,6 +183,22 @@ CPPExceptionTerminate(void)
SENTRY_ASYNC_SAFE_LOG_DEBUG("Terminate without exception.");
sentrycrashsc_initSelfThread(&g_stackCursor, 0);
} else {
+
+ // When we reach this point, the stack has already been unwound and the original stack
+ // frame where the exception was thrown is lost. This is because __cxa_rethrow is called
+ // after the exception has propagated up the call stack and the stack frames have been
+ // cleaned up. Therefore, any attempt to capture the stacktrace here would only show the
+ // current location in the exception handling code, not where the exception originated.
+ //
+ // This is why we use a fishhook via sentrycrashct_swap to intercept __cxa_throw
+ // instead. When an exception is first thrown, __cxa_throw is called before any stack
+ // unwinding occurs, allowing us to capture the complete stacktrace at the exact point
+ // where the exception originated. This gives us much more useful debugging information
+ // about where and why the exception was thrown with a slight overhead of getting the
+ // stacktrace for every C++ exception. Sadly, there is no reliable way to know if an
+ // exception is going to be handled or not in __cxa_throw, so we can't avoid the
+ // overhead.
+
SENTRY_ASYNC_SAFE_LOG_DEBUG("Discovering what kind of exception was thrown.");
g_captureNextStackTrace = false;
try {
@@ -187,6 +231,8 @@ CPPExceptionTerminate(void)
// TODO: Should this be done here? Maybe better in the exception
// handler?
+ SENTRY_ASYNC_SAFE_LOG_DEBUG("New machine context.");
+
SentryCrashMC_NEW_CONTEXT(machineContext);
sentrycrashmc_getContextForThread(sentrycrashthread_self(), machineContext, true);
@@ -237,11 +283,18 @@ setEnabled(bool isEnabled)
} else {
std::set_terminate(g_originalTerminateHandler);
g_originalTerminateHandler = NULL;
+ sentrycrashct_unswap_cxa_throw();
}
g_captureNextStackTrace = isEnabled;
}
}
+void
+sentrycrashcm_cppexception_enable_V2(void)
+{
+ sentrycrashct_swap_cxa_throw(captureStackTrace);
+}
+
static bool
isEnabled(void)
{
diff --git a/Sources/SentryCrash/Recording/Monitors/SentryCrashMonitor_CPPException.h b/Sources/SentryCrash/Recording/Monitors/SentryCrashMonitor_CPPException.h
index 3b684fbe23a..43e2d86a475 100644
--- a/Sources/SentryCrash/Recording/Monitors/SentryCrashMonitor_CPPException.h
+++ b/Sources/SentryCrash/Recording/Monitors/SentryCrashMonitor_CPPException.h
@@ -36,6 +36,8 @@ extern "C" {
*/
SentryCrashMonitorAPI *sentrycrashcm_cppexception_getAPI(void);
+void sentrycrashcm_cppexception_enable_V2(void);
+
/** For testing.
*/
void sentrycrashcm_cppexception_callOriginalTerminationHandler(void);
diff --git a/Sources/SentryCrash/Recording/Tools/SentryCrashCxaThrowSwapper.c b/Sources/SentryCrash/Recording/Tools/SentryCrashCxaThrowSwapper.c
new file mode 100644
index 00000000000..44b3589a2b4
--- /dev/null
+++ b/Sources/SentryCrash/Recording/Tools/SentryCrashCxaThrowSwapper.c
@@ -0,0 +1,370 @@
+// Adapted from: https://github.com/kstenerud/KSCrash
+//
+// SentryCxaThrowSwapper.cpp
+//
+// Copyright (c) 2019 YANDEX LLC. All rights reserved.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall remain in place
+// in this source code.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+//
+// Inspired by facebook/fishhook
+// https://github.com/facebook/fishhook
+//
+// 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 "SentryCrashCxaThrowSwapper.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "SentryAsyncSafeLog.h"
+#include "SentryCrashMach-O.h"
+#include "SentryCrashPlatformSpecificDefines.h"
+
+typedef struct {
+ uintptr_t image;
+ uintptr_t function;
+} SentryCrashAddressPair;
+
+static cxa_throw_type g_cxa_throw_handler = NULL;
+static const char *const g_cxa_throw_name = "__cxa_throw";
+
+static SentryCrashAddressPair *g_cxa_originals = NULL;
+static size_t g_cxa_originals_capacity = 0;
+static size_t g_cxa_originals_count = 0;
+
+static void
+addPair(SentryCrashAddressPair pair)
+{
+ SENTRY_ASYNC_SAFE_LOG_DEBUG(
+ "Adding address pair: image=%p, function=%p", (void *)pair.image, (void *)pair.function);
+
+ if (g_cxa_originals_count == g_cxa_originals_capacity) {
+ g_cxa_originals_capacity *= 2;
+ g_cxa_originals = (SentryCrashAddressPair *)realloc(
+ g_cxa_originals, sizeof(SentryCrashAddressPair) * g_cxa_originals_capacity);
+ if (g_cxa_originals == NULL) {
+ SENTRY_ASYNC_SAFE_LOG_ERROR(
+ "Failed to realloc memory for g_cxa_originals: %s", strerror(errno));
+ return;
+ }
+ }
+ memcpy(&g_cxa_originals[g_cxa_originals_count++], &pair, sizeof(SentryCrashAddressPair));
+}
+
+static void
+sentrycrashct_clear_pairs(void)
+{
+ SENTRY_ASYNC_SAFE_LOG_TRACE("Clearing all stored pairs");
+
+ if (g_cxa_originals == NULL) {
+ SENTRY_ASYNC_SAFE_LOG_WARN("g_cxa_originals is NULL, nothing to clear");
+ return;
+ }
+
+ // Free the allocated memory
+ free(g_cxa_originals);
+ g_cxa_originals = NULL;
+ g_cxa_originals_count = 0;
+ g_cxa_originals_capacity = 0;
+
+ SENTRY_ASYNC_SAFE_LOG_TRACE("Freed memory for stored pairs");
+}
+
+static uintptr_t
+findAddress(void *address)
+{
+ SENTRY_ASYNC_SAFE_LOG_TRACE("Finding address for %p", address);
+
+ for (size_t i = 0; i < g_cxa_originals_count; i++) {
+ if (g_cxa_originals[i].image == (uintptr_t)address) {
+ return g_cxa_originals[i].function;
+ }
+ }
+ SENTRY_ASYNC_SAFE_LOG_WARN("Address %p not found", address);
+ return (uintptr_t)NULL;
+}
+
+static void
+__cxa_throw_decorator(void *thrown_exception, void *tinfo, void (*dest)(void *))
+{
+ const int k_requiredFrames = 2;
+
+ SENTRY_ASYNC_SAFE_LOG_TRACE("Decorating __cxa_throw");
+
+ g_cxa_throw_handler(thrown_exception, tinfo, dest);
+
+ void *backtraceArr[k_requiredFrames];
+ int count = backtrace(backtraceArr, k_requiredFrames);
+
+ Dl_info info;
+ if (count >= k_requiredFrames) {
+ if (dladdr(backtraceArr[k_requiredFrames - 1], &info) != 0) {
+ uintptr_t function = findAddress(info.dli_fbase);
+ if (function != (uintptr_t)NULL) {
+ SENTRY_ASYNC_SAFE_LOG_TRACE(
+ "Calling original __cxa_throw function at %p", (void *)function);
+ cxa_throw_type original = (cxa_throw_type)function;
+ original(thrown_exception, tinfo, dest);
+ }
+ }
+ }
+}
+
+static void
+perform_rebinding_with_section(const section_t *dataSection, intptr_t slide, nlist_t *symtab,
+ char *strtab, uint32_t *indirect_symtab, bool is_swapping_cxa_throw)
+{
+ SENTRY_ASYNC_SAFE_LOG_TRACE(
+ "Processing section %s,%s", dataSection->segname, dataSection->sectname);
+
+ uint32_t *indirect_symbol_indices = indirect_symtab + dataSection->reserved1;
+ void **indirect_symbol_bindings = (void **)((uintptr_t)slide + dataSection->addr);
+
+ // The SEG_DATA_CONST is read-only by default, so we need to make it writable
+ // before we can modify the indirect symbol bindings.
+ const bool isDataConst = strcmp(dataSection->segname, SEG_DATA_CONST) == 0;
+
+ // As the default protection for the SEG_DATA_CONST is read-only we set the default
+ // oldProtection to VM_PROT_READ.
+ vm_prot_t oldProtection = VM_PROT_READ;
+ if (isDataConst) {
+ oldProtection = sentrycrash_macho_getSectionProtection(indirect_symbol_bindings);
+ if (mprotect(indirect_symbol_bindings, dataSection->size, PROT_READ | PROT_WRITE) != 0) {
+ SENTRY_ASYNC_SAFE_LOG_DEBUG(
+ "mprotect failed to set PROT_READ | PROT_WRITE for section %s,%s: %s",
+ dataSection->segname, dataSection->sectname, strerror(errno));
+ return;
+ }
+ }
+
+ for (uint i = 0; i < dataSection->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];
+ if (symbol_name_longer_than_1 && strcmp(&symbol_name[1], g_cxa_throw_name) == 0) {
+ Dl_info info;
+ if (dladdr(dataSection, &info) != 0) {
+ if (is_swapping_cxa_throw) {
+ // Swapping: Store original and set new handler
+ SentryCrashAddressPair pair
+ = { (uintptr_t)info.dli_fbase, (uintptr_t)indirect_symbol_bindings[i] };
+ addPair(pair);
+ indirect_symbol_bindings[i] = (void *)__cxa_throw_decorator;
+ SENTRY_ASYNC_SAFE_LOG_TRACE("Swapped __cxa_throw function at %p with decorator",
+ (void *)indirect_symbol_bindings[i]);
+ } else {
+ // Unswapping: Restore original handler
+ uintptr_t original_function = findAddress(info.dli_fbase);
+ if (original_function != (uintptr_t)NULL) {
+ indirect_symbol_bindings[i] = (void *)original_function;
+ SENTRY_ASYNC_SAFE_LOG_TRACE("Restored original __cxa_throw function at %p",
+ (void *)original_function);
+ }
+ }
+ }
+ }
+ }
+
+ if (isDataConst) {
+ int protection = 0;
+ if (oldProtection & VM_PROT_READ) {
+ protection |= PROT_READ;
+ }
+ if (oldProtection & VM_PROT_WRITE) {
+ protection |= PROT_WRITE;
+ }
+ if (oldProtection & VM_PROT_EXECUTE) {
+ protection |= PROT_EXEC;
+ }
+ if (mprotect(indirect_symbol_bindings, dataSection->size, protection) != 0) {
+ SENTRY_ASYNC_SAFE_LOG_ERROR(
+ "mprotect failed to restore protection for section %s,%s: %s", dataSection->segname,
+ dataSection->sectname, strerror(errno));
+ }
+ }
+}
+
+static void
+process_segment(const struct mach_header *header, intptr_t slide, const char *segname,
+ nlist_t *symtab, char *strtab, uint32_t *indirect_symtab, bool is_swapping_cxa_throw)
+{
+ SENTRY_ASYNC_SAFE_LOG_DEBUG("Processing segment %s", segname);
+
+ const segment_command_t *segment
+ = sentrycrash_macho_getSegmentByNameFromHeader((mach_header_t *)header, segname);
+ if (segment != NULL) {
+ const section_t *lazy_sym_sect
+ = sentrycrash_macho_getSectionByTypeFlagFromSegment(segment, S_LAZY_SYMBOL_POINTERS);
+ const section_t *non_lazy_sym_sect = sentrycrash_macho_getSectionByTypeFlagFromSegment(
+ segment, S_NON_LAZY_SYMBOL_POINTERS);
+
+ if (lazy_sym_sect != NULL) {
+ perform_rebinding_with_section(
+ lazy_sym_sect, slide, symtab, strtab, indirect_symtab, is_swapping_cxa_throw);
+ }
+ if (non_lazy_sym_sect != NULL) {
+ perform_rebinding_with_section(
+ non_lazy_sym_sect, slide, symtab, strtab, indirect_symtab, is_swapping_cxa_throw);
+ }
+ } else {
+ SENTRY_ASYNC_SAFE_LOG_WARN("Segment %s not found", segname);
+ }
+}
+
+static void
+rebind_symbols_for_image(
+ const struct mach_header *header, intptr_t slide, bool is_swapping_cxa_throw)
+{
+ SENTRY_ASYNC_SAFE_LOG_TRACE("Rebinding symbols for image with slide %p", (void *)slide);
+
+ Dl_info info;
+ if (dladdr(header, &info) == 0) {
+ SENTRY_ASYNC_SAFE_LOG_WARN("dladdr failed");
+ return;
+ }
+ SENTRY_ASYNC_SAFE_LOG_DEBUG("Image name: %s", info.dli_fname);
+ if (slide == 0) {
+ SENTRY_ASYNC_SAFE_LOG_DEBUG("Zero slide, can't do anything with it");
+ return;
+ }
+
+ const struct symtab_command *symtab_cmd
+ = (struct symtab_command *)sentrycrash_macho_getCommandByTypeFromHeader(
+ (const mach_header_t *)header, LC_SYMTAB);
+ const struct dysymtab_command *dysymtab_cmd
+ = (struct dysymtab_command *)sentrycrash_macho_getCommandByTypeFromHeader(
+ (const mach_header_t *)header, LC_DYSYMTAB);
+ const segment_command_t *linkedit_segment
+ = sentrycrash_macho_getSegmentByNameFromHeader((mach_header_t *)header, SEG_LINKEDIT);
+
+ if (symtab_cmd == NULL || dysymtab_cmd == NULL || linkedit_segment == NULL) {
+ SENTRY_ASYNC_SAFE_LOG_WARN("Required commands or segments not found");
+ 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);
+
+ process_segment(
+ header, slide, SEG_DATA, symtab, strtab, indirect_symtab, is_swapping_cxa_throw);
+ process_segment(
+ header, slide, SEG_DATA_CONST, symtab, strtab, indirect_symtab, is_swapping_cxa_throw);
+}
+
+typedef void (*dyld_image_callback)(const struct mach_header *mh, intptr_t vmaddr_slide);
+static void
+rebind_symbols_for_image_wrapper(const struct mach_header *mh, intptr_t vmaddr_slide)
+{
+ rebind_symbols_for_image(mh, vmaddr_slide, true);
+}
+
+int
+sentrycrashct_swap_cxa_throw(const cxa_throw_type handler)
+{
+ SENTRY_ASYNC_SAFE_LOG_TRACE("Swapping __cxa_throw handler");
+
+ if (g_cxa_originals == NULL) {
+ g_cxa_originals_capacity = 25;
+ g_cxa_originals = (SentryCrashAddressPair *)malloc(
+ sizeof(SentryCrashAddressPair) * g_cxa_originals_capacity);
+ if (g_cxa_originals == NULL) {
+ SENTRY_ASYNC_SAFE_LOG_ERROR(
+ "Failed to allocate memory for g_cxa_originals: %s", strerror(errno));
+ return -1;
+ }
+ }
+ g_cxa_originals_count = 0;
+
+ if (g_cxa_throw_handler == NULL) {
+ g_cxa_throw_handler = handler;
+ _dyld_register_func_for_add_image(rebind_symbols_for_image_wrapper);
+ } else {
+ g_cxa_throw_handler = handler;
+ 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), true);
+ }
+ }
+ return 0;
+}
+
+int
+sentrycrashct_unswap_cxa_throw(void)
+{
+ SENTRY_ASYNC_SAFE_LOG_TRACE("Unswapping __cxa_throw handler");
+
+ if (g_cxa_throw_handler == NULL || g_cxa_originals == NULL || g_cxa_originals_count == 0) {
+ SENTRY_ASYNC_SAFE_LOG_INFO("No original __cxa_throw handlers to restore");
+ return -1;
+ }
+
+ // Iterate through all loaded images
+ uint32_t image_count = _dyld_image_count();
+ for (uint32_t i = 0; i < image_count; i++) {
+ rebind_symbols_for_image(_dyld_get_image_header(i), _dyld_get_image_vmaddr_slide(i), false);
+ }
+
+ sentrycrashct_clear_pairs();
+ g_cxa_throw_handler = NULL;
+
+ return 0;
+}
diff --git a/Sources/SentryCrash/Recording/Tools/SentryCrashCxaThrowSwapper.h b/Sources/SentryCrash/Recording/Tools/SentryCrashCxaThrowSwapper.h
new file mode 100644
index 00000000000..70bc9b98b84
--- /dev/null
+++ b/Sources/SentryCrash/Recording/Tools/SentryCrashCxaThrowSwapper.h
@@ -0,0 +1,73 @@
+// Adapted from: https://github.com/kstenerud/KSCrash
+//
+// SentryCxaThrowSwapper.h
+//
+// Copyright (c) 2019 YANDEX LLC. All rights reserved.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall remain in place
+// in this source code.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#ifndef SentryCrashCxaThrowSwapper_h
+#define SentryCrashCxaThrowSwapper_h
+
+#ifdef __cplusplus
+
+# include
+
+extern "C" {
+
+typedef void (*cxa_throw_type)(void *, std::type_info *, void (*)(void *));
+#else
+typedef void (*cxa_throw_type)(void *, void *, void (*)(void *));
+#endif
+
+/**
+ * Swaps the current C++ exception throw handler with a custom one.
+ * This allows intercepting C++ exceptions when they are thrown.
+ *
+ * When a C++ exception is thrown, the compiler generates a call to __cxa_throw.
+ * This function replaces the default __cxa_throw implementation by modifying
+ * the dynamic linker, allowing us to intercept all C++ exceptions before they
+ * are actually thrown. After processing the exception, it still calls the
+ * original __cxa_throw to ensure normal exception handling continues.
+ *
+ * Internally, it iterates through all loaded dynamic library images and updates
+ * the __cxa_throw symbol to point to the new handler. This iteration is necessary
+ * because each dynamic library has its own instance of __cxa_throw that needs to
+ * be modified to ensure we catch exceptions thrown from any library. The implementation
+ * is based on the approach used by Meta's fishhook library, but uses its own
+ * implementation to rebind dynamic symbols on iOS and macOS.
+ *
+ * @param handler The new exception throw handler to install
+ * @return 0 if successful
+ */
+int sentrycrashct_swap_cxa_throw(const cxa_throw_type handler);
+
+/**
+ * Unswaps the C++ exception throw handler.
+ *
+ * @return 0 if successful
+ */
+int sentrycrashct_unswap_cxa_throw(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* SentryCrashCxaThrowSwapper_h */
diff --git a/Sources/SentryCrash/Recording/Tools/SentryCrashMach-O.c b/Sources/SentryCrash/Recording/Tools/SentryCrashMach-O.c
new file mode 100644
index 00000000000..5e7116ab3a9
--- /dev/null
+++ b/Sources/SentryCrash/Recording/Tools/SentryCrashMach-O.c
@@ -0,0 +1,171 @@
+// Adapted from: https://github.com/kstenerud/KSCrash
+//
+// SentryCrashMach-O.c
+//
+// Copyright (c) 2019 YANDEX LLC. All rights reserved.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall remain in place
+// in this source code.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+// Contains code of getsegbyname.c
+// https://opensource.apple.com/source/cctools/cctools-921/libmacho/getsegbyname.c.auto.html
+// Copyright (c) 1999 Apple Computer, Inc. All rights reserved.
+//
+// @APPLE_LICENSE_HEADER_START@
+//
+// This file contains Original Code and/or Modifications of Original Code
+// as defined in and that are subject to the Apple Public Source License
+// Version 2.0 (the 'License'). You may not use this file except in
+// compliance with the License. Please obtain a copy of the License at
+// http://www.opensource.apple.com/apsl/ and read it before using this
+// file.
+//
+// The Original Code and all software distributed under the License are
+// distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
+// EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
+// INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
+// Please see the License for the specific language governing rights and
+// limitations under the License.
+//
+// @APPLE_LICENSE_HEADER_END@
+//
+
+#include "SentryCrashMach-O.h"
+
+#include
+#include
+#include
+#include
+
+#include "SentryAsyncSafeLog.h"
+
+const struct load_command *
+sentrycrash_macho_getCommandByTypeFromHeader(const mach_header_t *header, uint32_t commandType)
+{
+ if (header == NULL) {
+ SENTRY_ASYNC_SAFE_LOG_ERROR("Header is NULL");
+ return NULL;
+ }
+
+ SENTRY_ASYNC_SAFE_LOG_TRACE(
+ "Getting command by type %u in Mach header at %p", commandType, header);
+
+ uintptr_t current = (uintptr_t)header + sizeof(mach_header_t);
+ struct load_command *loadCommand = NULL;
+
+ for (uint commandIndex = 0; commandIndex < header->ncmds; commandIndex++) {
+ loadCommand = (struct load_command *)current;
+ if (loadCommand->cmd == commandType) {
+ return loadCommand;
+ }
+ current += loadCommand->cmdsize;
+ }
+ SENTRY_ASYNC_SAFE_LOG_WARN("Command type %u not found", commandType);
+ return NULL;
+}
+
+const segment_command_t *
+sentrycrash_macho_getSegmentByNameFromHeader(const mach_header_t *header, const char *segmentName)
+{
+ if (header == NULL) {
+ SENTRY_ASYNC_SAFE_LOG_ERROR("Header is NULL");
+ return NULL;
+ }
+
+ if (segmentName == NULL) {
+ SENTRY_ASYNC_SAFE_LOG_ERROR("Segment name is NULL");
+ return NULL;
+ }
+
+ SENTRY_ASYNC_SAFE_LOG_TRACE(
+ "Searching for segment %s in Mach header at %p", segmentName, header);
+
+ const segment_command_t *segmentCommand;
+
+ segmentCommand = (segment_command_t *)((uintptr_t)header + sizeof(mach_header_t));
+ for (uint commandIndex = 0; commandIndex < header->ncmds; commandIndex++) {
+ if (segmentCommand->cmd == LC_SEGMENT_ARCH_DEPENDENT
+ && strncmp(segmentCommand->segname, segmentName, sizeof(segmentCommand->segname))
+ == 0) {
+ SENTRY_ASYNC_SAFE_LOG_DEBUG("Segment %s found at %p", segmentName, segmentCommand);
+ return segmentCommand;
+ }
+ segmentCommand = (segment_command_t *)((uintptr_t)segmentCommand + segmentCommand->cmdsize);
+ }
+
+ SENTRY_ASYNC_SAFE_LOG_WARN("Segment %s not found in Mach header at %p", segmentName, header);
+ return NULL;
+}
+
+const section_t *
+sentrycrash_macho_getSectionByTypeFlagFromSegment(
+ const segment_command_t *segmentCommand, uint32_t flag)
+{
+ if (segmentCommand == NULL) {
+ SENTRY_ASYNC_SAFE_LOG_ERROR("Segment is NULL");
+ return NULL;
+ }
+
+ SENTRY_ASYNC_SAFE_LOG_TRACE(
+ "Getting section by flag %u in segment %s", flag, segmentCommand->segname);
+
+ uintptr_t current = (uintptr_t)segmentCommand + sizeof(segment_command_t);
+ const section_t *section = NULL;
+
+ for (uint sectionIndex = 0; sectionIndex < segmentCommand->nsects; sectionIndex++) {
+ section = (const section_t *)(current + sectionIndex * sizeof(section_t));
+ if ((section->flags & SECTION_TYPE) == flag) {
+ return section;
+ }
+ }
+
+ SENTRY_ASYNC_SAFE_LOG_DEBUG(
+ "Section with flag %u not found in segment %s", flag, segmentCommand->segname);
+ return NULL;
+}
+
+vm_prot_t
+sentrycrash_macho_getSectionProtection(void *sectionStart)
+{
+ SENTRY_ASYNC_SAFE_LOG_TRACE("Getting protection for section starting at %p", 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) {
+ SENTRY_ASYNC_SAFE_LOG_DEBUG("Protection obtained: %d", info.protection);
+ return info.protection;
+ } else {
+ SENTRY_ASYNC_SAFE_LOG_ERROR(
+ "Failed to get protection for section: %s", mach_error_string(info_ret));
+ return VM_PROT_READ;
+ }
+}
diff --git a/Sources/SentryCrash/Recording/Tools/SentryCrashMach-O.h b/Sources/SentryCrash/Recording/Tools/SentryCrashMach-O.h
new file mode 100644
index 00000000000..e09f291f448
--- /dev/null
+++ b/Sources/SentryCrash/Recording/Tools/SentryCrashMach-O.h
@@ -0,0 +1,84 @@
+// Adapted from: https://github.com/kstenerud/KSCrash
+//
+// SentryCrashMach-O.h
+//
+// Copyright (c) 2019 YANDEX LLC. All rights reserved.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall remain in place
+// in this source code.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#ifndef SentryCrashMachO_h
+#define SentryCrashMachO_h
+
+#include
+
+#include "SentryCrashPlatformSpecificDefines.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/**
+ * This routine returns the `load_command` structure for the specified command type
+ * if it exists in the passed mach header. Otherwise, it returns `NULL`.
+ *
+ * @param header Pointer to the mach_header structure.
+ * @param command_type The type of the command to search for.
+ * @return Pointer to the `load_command` structure if found, otherwise `NULL`.
+ */
+const struct load_command *sentrycrash_macho_getCommandByTypeFromHeader(
+ const mach_header_t *header, uint32_t command_type);
+
+/**
+ * This routine returns the `segment_command` structure for the named segment
+ * if it exists in the passed mach header. Otherwise, it returns `NULL`.
+ * It just looks through the load commands. Since these are mapped into the text
+ * segment, they are read-only and thus const.
+ *
+ * @param header Pointer to the mach_header structure.
+ * @param seg_name The name of the segment to search for.
+ * @return Pointer to the `segment_command` structure if found, otherwise `NULL`.
+ */
+const segment_command_t *sentrycrash_macho_getSegmentByNameFromHeader(
+ const mach_header_t *header, const char *seg_name);
+
+/**
+ * This routine returns the section structure for the specified `SECTION_TYPE` flag
+ * from mach-o/loader.h if it exists in the passed segment command. Otherwise, it returns `NULL`.
+ *
+ * @param dataSegment Pointer to the segment_command structure.
+ * @param flag The `SECTION_TYPE` flag of the section to search for.
+ * @return Pointer to the section structure if found, otherwise `NULL`.
+ */
+const section_t *sentrycrash_macho_getSectionByTypeFlagFromSegment(
+ const segment_command_t *dataSegment, uint32_t flag);
+
+/**
+ * This routine returns the protection attributes for a given memory section.
+ *
+ * @param sectionStart Pointer to the start of the memory section.
+ * @return Protection attributes of the section.
+ */
+vm_prot_t sentrycrash_macho_getSectionProtection(void *sectionStart);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SentryCrash_h */
diff --git a/Sources/SentryCrash/Recording/Tools/SentryCrashPlatformSpecificDefines.h b/Sources/SentryCrash/Recording/Tools/SentryCrashPlatformSpecificDefines.h
index 4642182c3da..03ea3cfff9d 100644
--- a/Sources/SentryCrash/Recording/Tools/SentryCrashPlatformSpecificDefines.h
+++ b/Sources/SentryCrash/Recording/Tools/SentryCrashPlatformSpecificDefines.h
@@ -42,4 +42,8 @@ typedef struct nlist nlist_t;
# define LC_SEGMENT_ARCH_DEPENDENT LC_SEGMENT
#endif /* __LP64__ */
+#ifndef SEG_DATA_CONST
+# define SEG_DATA_CONST "__DATA_CONST"
+#endif /* SEG_DATA_CONST */
+
#endif /* SentryCrashPlatformSpecificDefines_h */
diff --git a/Sources/Swift/Helper/SentryEnabledFeaturesBuilder.swift b/Sources/Swift/Helper/SentryEnabledFeaturesBuilder.swift
index 3408ac30358..3dcdfda0758 100644
--- a/Sources/Swift/Helper/SentryEnabledFeaturesBuilder.swift
+++ b/Sources/Swift/Helper/SentryEnabledFeaturesBuilder.swift
@@ -64,6 +64,10 @@ import Foundation
if options.experimental.enableFileManagerSwizzling {
features.append("fileManagerSwizzling")
}
+ if options.experimental.enableUnhandledCPPExceptionsV2 {
+ features.append("unhandledCPPExceptionsV2")
+ }
+
return features
}
// swiftlint:enable cyclomatic_complexity function_body_length
diff --git a/Sources/Swift/SentryExperimentalOptions.swift b/Sources/Swift/SentryExperimentalOptions.swift
index 8468f7ab3c2..86ae64c5f38 100644
--- a/Sources/Swift/SentryExperimentalOptions.swift
+++ b/Sources/Swift/SentryExperimentalOptions.swift
@@ -19,6 +19,12 @@ public class SentryExperimentalOptions: NSObject {
*/
public var enableFileManagerSwizzling = false
+ /// A more reliable way to report unhandled C++ exceptions.
+ ///
+ /// This approach hooks into all intances of the `__cxa_throw` function, which provides a more comprehensive and consistent exception handling across an app’s runtime, regardless of the number of C++ modules or how they’re linked. It helps in obtaining accurate stack traces..
+ ///
+ public var enableUnhandledCPPExceptionsV2 = false
+
func validateOptions(_ options: [String: Any]?) {
}
}
diff --git a/Tests/SentryCrashTests/SentryCrashCxaThrowSwapperTests.m b/Tests/SentryCrashTests/SentryCrashCxaThrowSwapperTests.m
new file mode 100644
index 00000000000..703ec8ad3b5
--- /dev/null
+++ b/Tests/SentryCrashTests/SentryCrashCxaThrowSwapperTests.m
@@ -0,0 +1,115 @@
+#import "SentryCrashCxaThrowSwapper.h"
+#import
+
+@interface SentryCrashCxaThrowSwapperTests : XCTestCase
+
+@end
+
+@implementation SentryCrashCxaThrowSwapperTests
+
+- (void)setUp
+{
+ [super setUp];
+ // Reset any global state before each test
+}
+
+- (void)tearDown
+{
+ // Clean up after each test
+ [super tearDown];
+}
+
+- (void)testInitialSwap
+{
+ // Test initial swap with a mock handler
+ void (^mockHandler)(void *, void *, void (*)(void *))
+ = ^(void *thrown_exception, void *tinfo, void (*dest)(void *)) {
+ // Mock implementation
+ };
+
+ int result = ksct_swap((cxa_throw_type)mockHandler);
+ XCTAssertEqual(result, 0, @"Initial swap should succeed");
+}
+
+- (void)testMultipleSwaps
+{
+ // Test swapping handlers multiple times
+ void (^firstHandler)(void *, void *, void (*)(void *))
+ = ^(void *thrown_exception, void *tinfo, void (*dest)(void *)) {
+ // First mock implementation
+ };
+
+ void (^secondHandler)(void *, void *, void (*)(void *))
+ = ^(void *thrown_exception, void *tinfo, void (*dest)(void *)) {
+ // Second mock implementation
+ };
+
+ int firstResult = ksct_swap((cxa_throw_type)firstHandler);
+ XCTAssertEqual(firstResult, 0, @"First swap should succeed");
+
+ int secondResult = ksct_swap((cxa_throw_type)secondHandler);
+ XCTAssertEqual(secondResult, 0, @"Second swap should succeed");
+}
+
+- (void)testNullHandler
+{
+ // Test swapping with a NULL handler
+ int result = ksct_swap(NULL);
+ XCTAssertEqual(result, 0, @"Swapping with NULL handler should still succeed");
+}
+
+- (void)testMemoryAllocation
+{
+ // Test that memory allocation for g_cxa_originals works correctly
+ void (^mockHandler)(void *, void *, void (*)(void *))
+ = ^(void *thrown_exception, void *tinfo, void (*dest)(void *)) {
+ // Mock implementation
+ };
+
+ // First swap to initialize the memory
+ int result = ksct_swap((cxa_throw_type)mockHandler);
+ XCTAssertEqual(result, 0, @"Initial swap should succeed");
+
+ // Second swap to test memory reallocation
+ result = ksct_swap((cxa_throw_type)mockHandler);
+ XCTAssertEqual(result, 0, @"Second swap should succeed");
+}
+
+- (void)testSymbolRebinding
+{
+ // Test that symbols are properly rebound
+ void (^mockHandler)(void *, void *, void (*)(void *))
+ = ^(void *thrown_exception, void *tinfo, void (*dest)(void *)) {
+ // Mock implementation
+ };
+
+ int result = ksct_swap((cxa_throw_type)mockHandler);
+ XCTAssertEqual(result, 0, @"Symbol rebinding should succeed");
+
+ // Note: We can't directly test the rebinding results as they depend on the runtime environment
+ // and loaded libraries. In a real environment, you might want to add more specific tests
+ // that verify the actual function addresses and symbol tables.
+}
+
+- (void)testConcurrentAccess
+{
+ // Test concurrent access to the swapper
+ dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
+ dispatch_group_t group = dispatch_group_create();
+
+ void (^mockHandler)(void *, void *, void (*)(void *))
+ = ^(void *thrown_exception, void *tinfo, void (*dest)(void *)) {
+ // Mock implementation
+ };
+
+ for (int i = 0; i < 10; i++) {
+ dispatch_group_async(group, queue, ^{
+ int result = ksct_swap((cxa_throw_type)mockHandler);
+ XCTAssertEqual(result, 0, @"Concurrent swap should succeed");
+ });
+ }
+
+ dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
+}
+
+@end
diff --git a/Tests/SentryTests/Helper/SentryEnabledFeaturesBuilderTests.swift b/Tests/SentryTests/Helper/SentryEnabledFeaturesBuilderTests.swift
index 203cb29f6ce..3c3d684f6ac 100644
--- a/Tests/SentryTests/Helper/SentryEnabledFeaturesBuilderTests.swift
+++ b/Tests/SentryTests/Helper/SentryEnabledFeaturesBuilderTests.swift
@@ -165,4 +165,17 @@ final class SentryEnabledFeaturesBuilderTests: XCTestCase {
// -- Assert --
XCTAssertFalse(features.contains("fileManagerSwizzling"))
}
+
+ func testEnableUnhandledCPPExceptionsV2_shouldAddFeature() throws {
+ // -- Arrange --
+ let options = Options()
+
+ options.experimental.enableUnhandledCPPExceptionsV2 = true
+
+ // -- Act --
+ let features = SentryEnabledFeaturesBuilder.getEnabledFeatures(options: options)
+
+ // -- Assert --
+ XCTAssert(features.contains("unhandledCPPExceptionsV2"))
+ }
}
diff --git a/Tests/SentryTests/SentryCrash/SentryCrashCxaThrowSwapper_Tests.mm b/Tests/SentryTests/SentryCrash/SentryCrashCxaThrowSwapper_Tests.mm
new file mode 100644
index 00000000000..e29cfb3cdfc
--- /dev/null
+++ b/Tests/SentryTests/SentryCrash/SentryCrashCxaThrowSwapper_Tests.mm
@@ -0,0 +1,246 @@
+#include "SentryCompiler.h"
+#import "SentryCrashCxaThrowSwapper.h"
+#import
+#import
+#import
+#import
+#import
+#import
+
+@interface SentryCrashCxaThrowSwapper_Tests : XCTestCase
+
+@end
+
+// Define a block type for the exception handler
+typedef void (^ExceptionHandlerBlock)(NSString *exceptionWhat, NSString *typeInfoName);
+
+static ExceptionHandlerBlock g_exceptionHandlerBlock = nil;
+static int g_exceptionHandlerInvocations = 0;
+
+static NEVER_INLINE void
+testExceptionHandler(
+ void *thrown_exception, std::type_info *tinfo, void (*)(void *)) KEEP_FUNCTION_IN_STACKTRACE
+{
+ g_exceptionHandlerInvocations++;
+
+ if (tinfo != nullptr && thrown_exception != nullptr && g_exceptionHandlerBlock != nil) {
+ std::exception *exception = static_cast(thrown_exception);
+ NSString *errorMessage = [NSString stringWithUTF8String:exception->what()];
+
+ NSString *typeName = [NSString stringWithCString:tinfo->name()
+ encoding:NSUTF8StringEncoding];
+
+ g_exceptionHandlerBlock(errorMessage, typeName);
+ }
+
+ THWART_TAIL_CALL_OPTIMISATION
+}
+
+@implementation SentryCrashCxaThrowSwapper_Tests
+
+- (void)tearDown
+{
+ [super tearDown];
+ g_exceptionHandlerBlock = nil;
+ g_exceptionHandlerInvocations = 0;
+ sentrycrashct_unswap_cxa_throw();
+}
+
+- (void)testSwapCxaThrowHandler_RuntimeError
+{
+ // Arrange
+ XCTestExpectation *expectation = [self expectationWithDescription:@"Exception handler called"];
+ expectation.expectedFulfillmentCount = 2;
+
+ g_exceptionHandlerBlock = ^(NSString *exceptionWhat, NSString *typeInfoName) {
+ // The type name should be "St13runtime_error" or similar (mangled C++ name)
+ XCTAssertTrue([typeInfoName containsString:@"runtime_error"]);
+ XCTAssertEqualObjects(exceptionWhat, @"Runtime errrrrrorrrr!");
+
+ [expectation fulfill];
+ };
+
+ sentrycrashct_swap_cxa_throw(testExceptionHandler);
+
+ // Act
+ try {
+ throw std::runtime_error("Runtime errrrrrorrrr!");
+ } catch (...) {
+ [expectation fulfill];
+ }
+
+ // Assert
+ [self waitForExpectations:@[ expectation ] timeout:1.0];
+}
+
+- (void)testSwapCxaThrowHandler_ObjCTryCatch_InvalidArgument
+{
+ // Arrange
+ XCTestExpectation *expectation = [self expectationWithDescription:@"Exception handler called"];
+ expectation.expectedFulfillmentCount = 2;
+
+ g_exceptionHandlerBlock = ^(NSString *exceptionWhat, NSString *typeInfoName) {
+ // The type name should be "St16invalid_argument" or similar (mangled C++ name)
+ XCTAssertTrue([typeInfoName containsString:@"invalid_argument"]);
+ XCTAssertEqualObjects(exceptionWhat, @"Passed to many arguments");
+
+ [expectation fulfill];
+ };
+
+ sentrycrashct_swap_cxa_throw(testExceptionHandler);
+
+ // Act
+ @try {
+ throw std::invalid_argument("Passed to many arguments");
+ } @catch (...) {
+ [expectation fulfill];
+ }
+
+ // Assert
+ [self waitForExpectations:@[ expectation ] timeout:1.0];
+}
+
+- (void)testSwapCxaThrowHandler_NestedTryCatchHandlers
+{
+ // Arrange
+ XCTestExpectation *expectation = [self expectationWithDescription:@"Exception handler called"];
+ expectation.expectedFulfillmentCount = 2;
+
+ g_exceptionHandlerBlock = ^(NSString *exceptionWhat, NSString *typeInfoName) {
+ // The type name should be "St16invalid_argument" or similar (mangled C++ name)
+ XCTAssertTrue([typeInfoName containsString:@"invalid_argument"]);
+ XCTAssertEqualObjects(exceptionWhat, @"Passed to many arguments");
+
+ [expectation fulfill];
+ };
+
+ sentrycrashct_swap_cxa_throw(testExceptionHandler);
+
+ // Act
+ try {
+ try {
+ throw std::invalid_argument("Passed to many arguments");
+ } catch (const std::invalid_argument &e) {
+ [expectation fulfill];
+ }
+ } catch (...) {
+ // This catch block must not be called
+ [expectation fulfill];
+ }
+
+ // Assert
+ [self waitForExpectations:@[ expectation ] timeout:1.0];
+}
+
+- (void)testSwapCxaThrowHandler_NestedTryCatchHandlers_Rethrow
+{
+ // Arrange
+ XCTestExpectation *expectation = [self expectationWithDescription:@"Exception handler called"];
+ expectation.expectedFulfillmentCount = 3;
+
+ g_exceptionHandlerBlock = ^(NSString *exceptionWhat, NSString *typeInfoName) {
+ // The type name should be "St16invalid_argument" or similar (mangled C++ name)
+ XCTAssertTrue([typeInfoName containsString:@"invalid_argument"]);
+ XCTAssertEqualObjects(exceptionWhat, @"Passed to many arguments");
+
+ [expectation fulfill];
+ };
+
+ sentrycrashct_swap_cxa_throw(testExceptionHandler);
+
+ // Act
+ try {
+ try {
+ throw std::invalid_argument("Passed to many arguments");
+ } catch (const std::invalid_argument &e) {
+ [expectation fulfill];
+ throw; // Rethrow the exception
+ }
+ } catch (...) {
+ [expectation fulfill];
+ }
+
+ // Assert
+ [self waitForExpectations:@[ expectation ] timeout:1.0];
+
+ XCTAssertEqual(g_exceptionHandlerInvocations, 1);
+}
+
+- (void)testSwapCxaThrowHandler_NestedTryCatchHandlers_ThrowDifferentExceptions
+{
+ // Arrange
+ XCTestExpectation *expectation = [self expectationWithDescription:@"Exception handler called"];
+ expectation.expectedFulfillmentCount = 4;
+
+ __block int invocations = 0;
+ g_exceptionHandlerBlock = ^(NSString *exceptionWhat, NSString *typeInfoName) {
+ // First we get the runtime error, then the invalid argument exception
+ if (invocations == 0) {
+ // The type name should be "St13runtime_error" or similar (mangled C++ name)
+ XCTAssertTrue([typeInfoName containsString:@"runtime_error"]);
+ XCTAssertEqualObjects(exceptionWhat, @"Runtime errrrrrorrrr!");
+ } else {
+ // The type name should be "St16invalid_argument" or similar (mangled C++ name)
+ XCTAssertTrue([typeInfoName containsString:@"invalid_argument"]);
+ XCTAssertEqualObjects(exceptionWhat, @"Passed to many arguments");
+ }
+
+ invocations++;
+ [expectation fulfill];
+ };
+
+ sentrycrashct_swap_cxa_throw(testExceptionHandler);
+
+ // Act
+ try {
+ try {
+ throw std::runtime_error("Runtime errrrrrorrrr!");
+ } catch (const std::runtime_error &e) {
+ [expectation fulfill];
+ throw std::invalid_argument("Passed to many arguments");
+ }
+ } catch (...) {
+ [expectation fulfill];
+ }
+
+ // Assert
+ [self waitForExpectations:@[ expectation ] timeout:1.0];
+
+ XCTAssertEqual(g_exceptionHandlerInvocations, 2);
+}
+
+- (void)testSwapCxaThrowHandler_RuntimeErrorFromBGThread
+{
+ // Arrange
+ XCTestExpectation *expectation = [self expectationWithDescription:@"Exception handler called"];
+ expectation.expectedFulfillmentCount = 2;
+
+ g_exceptionHandlerBlock = ^(NSString *exceptionWhat, NSString *typeInfoName) {
+ // The type name should be "St13runtime_error" or similar (mangled C++ name)
+ XCTAssertTrue([typeInfoName containsString:@"runtime_error"]);
+ XCTAssertEqualObjects(exceptionWhat, @"Runtime errrrrrorrrr!");
+
+ [expectation fulfill];
+ };
+
+ sentrycrashct_swap_cxa_throw(testExceptionHandler);
+
+ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
+ // Act
+ try {
+ throw std::runtime_error("Runtime errrrrrorrrr!");
+ } catch (...) {
+ [expectation fulfill];
+ }
+ });
+
+ // Assert
+ [self waitForExpectations:@[ expectation ] timeout:1.0];
+}
+
+- (void)testUnswapWithoutSwappingBefore
+{
+ XCTAssertEqual(sentrycrashct_unswap_cxa_throw(), -1);
+}
+
+@end
diff --git a/Tests/SentryTests/SentryCrash/SentryCrashMach-OTests.m b/Tests/SentryTests/SentryCrash/SentryCrashMach-OTests.m
new file mode 100644
index 00000000000..f87706bead6
--- /dev/null
+++ b/Tests/SentryTests/SentryCrash/SentryCrashMach-OTests.m
@@ -0,0 +1,549 @@
+// Adapted from: https://github.com/kstenerud/KSCrash
+//
+// KSMach-O_Tests.m
+//
+// Copyright (c) 2019 YANDEX LLC. All rights reserved.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall remain in place
+// in this source code.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#import "SentryCrashMach-O.h"
+#import
+#import
+#import
+
+@interface SentryCrashMach_O_Tests : XCTestCase
+@end
+
+@implementation SentryCrashMach_O_Tests
+
+- (void)testGetCommandByTypeFromHeader_SegmentArchDependent
+{
+ // Arrange
+
+ // Create a test Mach-O header
+ mach_header_t header;
+ header.ncmds = 1;
+
+ // Create a command
+ struct load_command cmd1;
+ cmd1.cmd = LC_SEGMENT_ARCH_DEPENDENT;
+ cmd1.cmdsize = sizeof(struct load_command);
+
+ // Copy the command into the header memory
+ uint8_t buffer[sizeof(header) + sizeof(cmd1)];
+ memcpy(buffer, &header, sizeof(header));
+ memcpy(buffer + sizeof(header), &cmd1, sizeof(cmd1));
+
+ const mach_header_t *testHeader = (mach_header_t *)buffer;
+
+ // Act
+ const struct load_command *result
+ = sentrycrash_macho_getCommandByTypeFromHeader(testHeader, LC_SEGMENT_ARCH_DEPENDENT);
+
+ // Assert
+ XCTAssertNotEqual(result, NULL);
+ XCTAssertEqual(result->cmd, LC_SEGMENT_ARCH_DEPENDENT);
+}
+
+- (void)testGetCommandByTypeFromHeader_Symtab
+{
+ // Arrange
+
+ // Create a test Mach-O header
+ mach_header_t header;
+ header.ncmds = 1;
+
+ // Create a command
+ struct load_command cmd2;
+ cmd2.cmd = LC_SYMTAB;
+ cmd2.cmdsize = sizeof(struct load_command);
+
+ // Copy the command into the header memory
+ uint8_t buffer[sizeof(header) + sizeof(cmd2)];
+ memcpy(buffer, &header, sizeof(header));
+ memcpy(buffer + sizeof(header), &cmd2, sizeof(cmd2));
+
+ const mach_header_t *testHeader = (mach_header_t *)buffer;
+
+ // Act
+ const struct load_command *result
+ = sentrycrash_macho_getCommandByTypeFromHeader(testHeader, LC_SYMTAB);
+
+ // Assert
+ XCTAssertNotEqual(result, NULL);
+ XCTAssertEqual(result->cmd, LC_SYMTAB);
+}
+
+- (void)testGetCommandByTypeFromHeader_NotFound
+{
+ // Arrange
+
+ // Create a test Mach-O header
+ mach_header_t header;
+ header.ncmds = 1;
+
+ // Create a command
+ struct load_command cmd1;
+ cmd1.cmd = LC_SEGMENT_ARCH_DEPENDENT;
+ cmd1.cmdsize = sizeof(struct load_command);
+
+ // Copy the command into the header memory
+ uint8_t buffer[sizeof(header) + sizeof(cmd1)];
+ memcpy(buffer, &header, sizeof(header));
+ memcpy(buffer + sizeof(header), &cmd1, sizeof(cmd1));
+
+ const mach_header_t *testHeader = (mach_header_t *)buffer;
+
+ // Act
+ const struct load_command *result
+ = sentrycrash_macho_getCommandByTypeFromHeader(testHeader, LC_DYSYMTAB);
+
+ // Assert
+ XCTAssertEqual(result, NULL);
+}
+
+- (void)testGetCommandByTypeFromHeader_InvalidHeader
+{
+ // Arrange & Act
+ const struct load_command *result
+ = sentrycrash_macho_getCommandByTypeFromHeader(NULL, LC_SEGMENT_ARCH_DEPENDENT);
+
+ // Assert
+ XCTAssertEqual(result, NULL);
+}
+
+- (void)testGetCommandByTypeFromHeader_InvalidCommand
+{
+ // Arrange
+
+ // Create a test Mach-O header
+ mach_header_t header;
+ header.ncmds = 1;
+
+ // Create a command
+ struct load_command cmd2;
+ cmd2.cmd = LC_SYMTAB;
+ cmd2.cmdsize = sizeof(struct load_command);
+
+ // Copy the command into the header memory
+ uint8_t buffer[sizeof(header) + sizeof(cmd2)];
+ memcpy(buffer, &header, sizeof(header));
+ memcpy(buffer + sizeof(header), &cmd2, sizeof(cmd2));
+
+ const mach_header_t *testHeader = (mach_header_t *)buffer;
+
+ // Act
+ const struct load_command *result
+ = sentrycrash_macho_getCommandByTypeFromHeader(testHeader, UINT32_MAX);
+
+ // Assert
+ XCTAssertEqual(result, NULL);
+}
+
+- (void)testGetSegmentByNameFromHeader_TextSegment
+{
+ // Arrange
+
+ // Create a test Mach-O header
+ mach_header_t header;
+ header.ncmds = 1;
+
+ // Create a segment
+ segment_command_t seg1;
+ seg1.cmd = LC_SEGMENT_ARCH_DEPENDENT;
+ seg1.cmdsize = sizeof(segment_command_t);
+ strcpy(seg1.segname, "__TEXT");
+
+ // Copy the segment into the header memory
+ uint8_t buffer[sizeof(header) + sizeof(seg1)];
+ memcpy(buffer, &header, sizeof(header));
+ memcpy(buffer + sizeof(header), &seg1, sizeof(seg1));
+
+ const mach_header_t *testHeader = (mach_header_t *)buffer;
+
+ // Act
+ const segment_command_t *result
+ = sentrycrash_macho_getSegmentByNameFromHeader(testHeader, "__TEXT");
+
+ // Assert
+ XCTAssertNotEqual(result, NULL);
+ XCTAssertEqual(strcmp(result->segname, "__TEXT"), 0);
+}
+
+- (void)testGetSegmentByNameFromHeader_DataSegment
+{
+ // Arrange
+
+ // Create a test Mach-O header
+ mach_header_t header;
+ header.ncmds = 1;
+
+ // Create a segment
+ segment_command_t seg2;
+ seg2.cmd = LC_SEGMENT_ARCH_DEPENDENT;
+ seg2.cmdsize = sizeof(segment_command_t);
+ strcpy(seg2.segname, "__DATA");
+
+ // Copy the segment into the header memory
+ uint8_t buffer[sizeof(header) + sizeof(seg2)];
+ memcpy(buffer, &header, sizeof(header));
+ memcpy(buffer + sizeof(header), &seg2, sizeof(seg2));
+
+ const mach_header_t *testHeader = (mach_header_t *)buffer;
+
+ // Act
+ const segment_command_t *result
+ = sentrycrash_macho_getSegmentByNameFromHeader(testHeader, "__DATA");
+
+ // Assert
+ XCTAssertNotEqual(result, NULL);
+ XCTAssertEqual(strcmp(result->segname, "__DATA"), 0);
+}
+
+- (void)testGetSegmentByNameFromHeader_NotFound
+{
+ // Arrange
+
+ // Create a test Mach-O header
+ mach_header_t header;
+ header.ncmds = 1;
+
+ // Create a segment
+ segment_command_t seg1;
+ seg1.cmd = LC_SEGMENT_ARCH_DEPENDENT;
+ seg1.cmdsize = sizeof(segment_command_t);
+ strcpy(seg1.segname, "__TEXT");
+
+ // Copy the segment into the header memory
+ uint8_t buffer[sizeof(header) + sizeof(seg1)];
+ memcpy(buffer, &header, sizeof(header));
+ memcpy(buffer + sizeof(header), &seg1, sizeof(seg1));
+
+ const mach_header_t *testHeader = (mach_header_t *)buffer;
+
+ // Act
+ const segment_command_t *result
+ = sentrycrash_macho_getSegmentByNameFromHeader(testHeader, "__INVALID");
+
+ // Assert
+ XCTAssertEqual(result, NULL);
+}
+
+- (void)testGetSegmentByNameFromHeader_InvalidHeader
+{
+ // Arrange & Act
+ const segment_command_t *result = sentrycrash_macho_getSegmentByNameFromHeader(NULL, "__TEXT");
+
+ // Assert
+ XCTAssertEqual(result, NULL);
+}
+
+- (void)testGetSegmentByNameFromHeader_InvalidSegment
+{
+ // Arrange
+ // Create a test Mach-O header
+ mach_header_t header;
+ header.ncmds = 1;
+
+ // Create a segment
+ segment_command_t seg1;
+ seg1.cmd = LC_SEGMENT_ARCH_DEPENDENT;
+ seg1.cmdsize = sizeof(segment_command_t);
+ strcpy(seg1.segname, "__TEXT");
+
+ // Copy the segment into the header memory
+ uint8_t buffer[sizeof(header) + sizeof(seg1)];
+ memcpy(buffer, &header, sizeof(header));
+ memcpy(buffer + sizeof(header), &seg1, sizeof(seg1));
+
+ const mach_header_t *testHeader = (mach_header_t *)buffer;
+
+ // Act
+ const segment_command_t *result
+ = sentrycrash_macho_getSegmentByNameFromHeader(testHeader, NULL);
+
+ // Assert
+ XCTAssertEqual(result, NULL);
+}
+
+- (void)testGetSectionByTypeFlagFromSegment_NonLazySymbolPointers
+{
+ // Arrange
+
+ // Create a test segment
+ segment_command_t segment;
+ strcpy(segment.segname, "__DATA");
+ segment.nsects = 1;
+
+ // Create a section
+ section_t sect1;
+ strcpy(sect1.sectname, "__nl_symbol_ptr");
+ sect1.flags = S_ATTR_PURE_INSTRUCTIONS | S_NON_LAZY_SYMBOL_POINTERS;
+
+ // Copy the section into the segment memory
+ uint8_t buffer[sizeof(segment) + sizeof(sect1)];
+ memcpy(buffer, &segment, sizeof(segment));
+ memcpy(buffer + sizeof(segment), §1, sizeof(sect1));
+
+ const segment_command_t *testSegment = (segment_command_t *)buffer;
+
+ // Act
+ const section_t *result = sentrycrash_macho_getSectionByTypeFlagFromSegment(
+ testSegment, S_NON_LAZY_SYMBOL_POINTERS);
+
+ // Assert
+ XCTAssertNotEqual(result, NULL);
+ XCTAssertEqual(result->flags & SECTION_TYPE, S_NON_LAZY_SYMBOL_POINTERS);
+ XCTAssertEqual(strcmp(result->sectname, "__nl_symbol_ptr"), 0);
+}
+
+- (void)testGetSectionByTypeFlagFromSegment_LazySymbolPointers
+{
+ // Arrange
+
+ // Create a test segment
+ segment_command_t segment;
+ strcpy(segment.segname, "__DATA");
+ segment.nsects = 1;
+
+ // Create a section
+ section_t sect2;
+ strcpy(sect2.sectname, "__la_symbol_ptr");
+ sect2.flags = S_ATTR_SOME_INSTRUCTIONS | S_LAZY_SYMBOL_POINTERS;
+
+ // Copy the section into the segment memory
+ uint8_t buffer[sizeof(segment) + sizeof(sect2)];
+ memcpy(buffer, &segment, sizeof(segment));
+ memcpy(buffer + sizeof(segment), §2, sizeof(sect2));
+
+ const segment_command_t *testSegment = (segment_command_t *)buffer;
+
+ // Act
+ const section_t *result
+ = sentrycrash_macho_getSectionByTypeFlagFromSegment(testSegment, S_LAZY_SYMBOL_POINTERS);
+
+ // Assert
+ XCTAssertNotEqual(result, NULL);
+ XCTAssertEqual(result->flags & SECTION_TYPE, S_LAZY_SYMBOL_POINTERS);
+ XCTAssertEqual(strcmp(result->sectname, "__la_symbol_ptr"), 0);
+}
+
+- (void)testGetSectionByTypeFlagFromSegment_Regular
+{
+ // Arrange
+ // Create a test segment
+ segment_command_t segment;
+ strcpy(segment.segname, "__DATA");
+ segment.nsects = 1;
+
+ // Create a section
+ section_t sect3;
+ strcpy(sect3.sectname, "__const");
+ sect3.flags = S_REGULAR;
+
+ // Copy the section into the segment memory
+ uint8_t buffer[sizeof(segment) + sizeof(sect3)];
+ memcpy(buffer, &segment, sizeof(segment));
+ memcpy(buffer + sizeof(segment), §3, sizeof(sect3));
+
+ const segment_command_t *testSegment = (segment_command_t *)buffer;
+
+ // Act
+ const section_t *result
+ = sentrycrash_macho_getSectionByTypeFlagFromSegment(testSegment, S_REGULAR);
+
+ // Assert
+ XCTAssertNotEqual(result, NULL);
+ XCTAssertEqual(result->flags & SECTION_TYPE, S_REGULAR);
+ XCTAssertEqual(strcmp(result->sectname, "__const"), 0);
+}
+
+- (void)testGetSectionByTypeFlagFromSegment_NotFound
+{
+ // Arrange
+
+ // Create a test segment
+ segment_command_t segment;
+ strcpy(segment.segname, "__DATA");
+ segment.nsects = 1;
+
+ // Create a section
+ section_t sect1;
+ strcpy(sect1.sectname, "__nl_symbol_ptr");
+ sect1.flags = S_ATTR_PURE_INSTRUCTIONS | S_NON_LAZY_SYMBOL_POINTERS;
+
+ // Copy the section into the segment memory
+ uint8_t buffer[sizeof(segment) + sizeof(sect1)];
+ memcpy(buffer, &segment, sizeof(segment));
+ memcpy(buffer + sizeof(segment), §1, sizeof(sect1));
+
+ const segment_command_t *testSegment = (segment_command_t *)buffer;
+
+ // Act
+ const section_t *result
+ = sentrycrash_macho_getSectionByTypeFlagFromSegment(testSegment, S_ATTR_DEBUG);
+
+ // Assert
+ // Verify that the section is not found for a different type flag
+ XCTAssertEqual(result, NULL);
+}
+
+- (void)testGetSectionByTypeFlagFromSegment_InvalidSegment
+{
+ // Arrange & Act
+ const section_t *result
+ = sentrycrash_macho_getSectionByTypeFlagFromSegment(NULL, S_NON_LAZY_SYMBOL_POINTERS);
+
+ // Assert
+ XCTAssertEqual(result, NULL);
+}
+
+- (void)testGetSectionByTypeFlagFromSegment_InvalidFlag
+{
+ // Arrange
+
+ // Create a test segment
+ segment_command_t segment;
+ strcpy(segment.segname, "__DATA");
+ segment.nsects = 1;
+
+ // Create a section
+ section_t sect1;
+ strcpy(sect1.sectname, "__nl_symbol_ptr");
+ sect1.flags = S_ATTR_PURE_INSTRUCTIONS | S_NON_LAZY_SYMBOL_POINTERS;
+
+ // Copy the section into the segment memory
+ uint8_t buffer[sizeof(segment) + sizeof(sect1)];
+ memcpy(buffer, &segment, sizeof(segment));
+ memcpy(buffer + sizeof(segment), §1, sizeof(sect1));
+
+ const segment_command_t *testSegment = (segment_command_t *)buffer;
+
+ // Act
+ // Use UINT32_MAX as the flag and ensure we're not crashing
+ const section_t *result
+ = sentrycrash_macho_getSectionByTypeFlagFromSegment(testSegment, UINT32_MAX);
+
+ // Assert
+ XCTAssertEqual(result, NULL);
+}
+
+- (void)testGetSectionProtection_ReadOnlyProtection
+{
+ // Arrange
+
+ // Create a memory region with read-only protection
+ vm_address_t address;
+ vm_size_t size = getpagesize();
+ vm_prot_t expectedProtection = VM_PROT_READ;
+ kern_return_t result = vm_allocate(mach_task_self(), &address, size, VM_FLAGS_ANYWHERE);
+ XCTAssertEqual(result, KERN_SUCCESS);
+
+ result = vm_protect(mach_task_self(), address, size, FALSE, expectedProtection);
+ XCTAssertEqual(result, KERN_SUCCESS);
+
+ // Act
+ vm_prot_t actualProtection = sentrycrash_macho_getSectionProtection((void *)address);
+
+ // Assert
+ XCTAssertEqual(actualProtection, expectedProtection);
+
+ // Deallocate the memory region
+ result = vm_deallocate(mach_task_self(), address, size);
+ XCTAssertEqual(result, KERN_SUCCESS);
+}
+
+- (void)testGetSectionProtection_ExecutableProtection
+{
+ // Arrange
+
+ // Create a memory region with executable protection
+ vm_address_t address;
+ vm_size_t size = getpagesize();
+ vm_prot_t expectedProtection = VM_PROT_READ | VM_PROT_EXECUTE;
+ kern_return_t result = vm_allocate(mach_task_self(), &address, size, VM_FLAGS_ANYWHERE);
+ XCTAssertEqual(result, KERN_SUCCESS);
+
+ result = vm_protect(mach_task_self(), address, size, FALSE, expectedProtection);
+ XCTAssertEqual(result, KERN_SUCCESS);
+
+ // Act
+ vm_prot_t actualProtection = sentrycrash_macho_getSectionProtection((void *)address);
+
+ // Assert
+ XCTAssertEqual(actualProtection, expectedProtection);
+
+ // Deallocate the memory region
+ result = vm_deallocate(mach_task_self(), address, size);
+ XCTAssertEqual(result, KERN_SUCCESS);
+}
+
+- (void)testGetSectionProtection_NoAccessProtection
+{
+ // Arrange
+
+ // Create a memory region with no access protection
+ vm_address_t address;
+ vm_size_t size = getpagesize();
+ vm_prot_t expectedProtection = VM_PROT_NONE;
+ kern_return_t result = vm_allocate(mach_task_self(), &address, size, VM_FLAGS_ANYWHERE);
+ XCTAssertEqual(result, KERN_SUCCESS);
+
+ result = vm_protect(mach_task_self(), address, size, FALSE, expectedProtection);
+ XCTAssertEqual(result, KERN_SUCCESS);
+
+ // Act
+ vm_prot_t actualProtection = sentrycrash_macho_getSectionProtection((void *)address);
+
+ // Assert
+ XCTAssertEqual(actualProtection, expectedProtection);
+
+ // Deallocate the memory region
+ result = vm_deallocate(mach_task_self(), address, size);
+ XCTAssertEqual(result, KERN_SUCCESS);
+}
+
+- (void)testGetSectionProtection_WithInvalidMemoryAddress_ReturnsDefaultProtection
+{
+ // Arrange
+ vm_address_t invalidAddress = 0xFFFFFFFFFFFFFFFFULL;
+
+ // Act
+ vm_prot_t actualProtection = sentrycrash_macho_getSectionProtection((void *)invalidAddress);
+
+ // Assert
+ XCTAssertEqual(
+ actualProtection, VM_PROT_READ, @"Expected default protection value of VM_PROT_READ");
+}
+
+- (void)testGetSectionProtection_PassingNULL_ReturnsDefaultProtection
+{
+ // Arrange
+ vm_prot_t expectedProtection = VM_PROT_READ | VM_PROT_EXECUTE;
+
+ // Act
+ vm_prot_t actualProtection = sentrycrash_macho_getSectionProtection((void *)NULL);
+
+ // Assert
+ XCTAssertEqual(actualProtection, expectedProtection,
+ @"Expected default protection value of VM_PROT_READ | VM_PROT_EXECUTE");
+}
+
+@end
diff --git a/Tests/SentryTests/SentryKSCrashReportConverterTests.m b/Tests/SentryTests/SentryKSCrashReportConverterTests.m
index e822314aa09..ad26edc2667 100644
--- a/Tests/SentryTests/SentryKSCrashReportConverterTests.m
+++ b/Tests/SentryTests/SentryKSCrashReportConverterTests.m
@@ -235,6 +235,15 @@ - (void)testStackoverflow
- (void)testCPPException
{
[self isValidReport:@"Resources/CPPException"];
+
+ NSDictionary *rawCrash = [self getCrashReport:@"Resources/CPPException"];
+ SentryCrashReportConverter *reportConverter =
+ [[SentryCrashReportConverter alloc] initWithReport:rawCrash inAppLogic:self.inAppLogic];
+ SentryEvent *event = [reportConverter convertReportToEvent];
+
+ SentryException *exception = event.exceptions.firstObject;
+
+ XCTAssertEqualObjects(exception.value, @"MyException: Something bad happened...");
}
- (void)testNXPage