Skip to content

Commit 5c21d2c

Browse files
committed
Make kscrash as main crash detection source
1 parent be164d1 commit 5c21d2c

File tree

9 files changed

+752
-14
lines changed

9 files changed

+752
-14
lines changed

CHANGELOG.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,11 @@
3939

4040
**Changed**
4141

42-
- Nothing yet!
42+
- Changed previous-run fatal issue reporting to use the cached KSCrash report as the canonical iOS crash source, with MetricKit used only as optional enrichment for non-crash diagnostics.
4343

4444
**Fixed**
4545

46-
- Nothing yet!
46+
- Fixed inaccurate iOS previous-run crash timestamps and duplicate previous-run crash materialization caused by relying on MetricKit crash diagnostics as the primary report source.
4747

4848
## [0.22.13]
4949

platform/swift/source/Logger.swift

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -264,11 +264,19 @@ public final class Logger {
264264
// hasFatallyTerminatedOnPreviousRun may already be set if configure() succeeded
265265
}
266266

267+
if Logger.hasFatallyTerminatedOnPreviousRun == true {
268+
_ = try? BitdriftKSCrashWrapper.createPreviousRunIssueReport(
269+
withOutputDir: Logger.reportCollectionDirectory(base: directoryURL),
270+
sdkVersion: capture_get_sdk_version()
271+
)
272+
self.underlyingLogger.processIssueReports(reportProcessingSession: .previousRun)
273+
}
274+
267275
let hangDuration = self.underlyingLogger.runtimeValue(.applicationANRReporterThresholdMs)
268276
let reporter = DiagnosticEventReporter(
269277
outputDir: Logger.reportCollectionDirectory(base: directoryURL),
270278
sdkVersion: capture_get_sdk_version(),
271-
eventTypes: .crash,
279+
eventTypes: .hang,
272280
minimumHangSeconds: Double(hangDuration) / Double(MSEC_PER_SEC)) { [weak self] in
273281
self?.underlyingLogger.processIssueReports(reportProcessingSession: .previousRun)
274282
}

platform/swift/source/crash_handler/BitdriftKSCrashHandler.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,10 @@ NS_ASSUME_NONNULL_BEGIN
5252
*/
5353
+ (NSNumber *_Nullable)didCrashLastLaunch;
5454

55+
+ (BOOL)createPreviousRunIssueReportWithOutputDir:(NSURL *)outputDir
56+
sdkVersion:(NSString *)sdkVersion
57+
error:(NSError **)error;
58+
5559
+ (void)stopCrashReporter;
5660

5761
@end

platform/swift/source/crash_handler/BitdriftKSCrashHandler.m

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#import "ReportContext.h"
1919

2020
#import <stdatomic.h>
21+
#import <time.h>
2122
#import <string.h>
2223
#import <sys/sysctl.h>
2324
#import <sys/utsname.h>
@@ -39,12 +40,21 @@ void ksmemory_notifyUnhandledFatalSignal(void) {}
3940
CacheResultSuccess = 3
4041
} CacheResult;
4142

43+
typedef enum {
44+
ReportCreationResultFailure = 0,
45+
ReportCreationResultReportDoesNotExist = 1,
46+
ReportCreationResultSuccess = 2,
47+
} ReportCreationResult;
48+
4249
/** Cache a KSCrash report, which will be used later for report enhancement. */
4350
CacheResult capture_cache_kscrash_report(NSString *reportPath);
4451

4552
/** Enhance a MetricKit report using the cached KSCrash report. */
4653
NSDictionary *_Nullable capture_enhance_metrickit_diagnostic_report(const NSDictionary *_Nullable report);
4754

55+
/** Create a capture report directly from the cached KSCrash report. */
56+
ReportCreationResult capture_create_report_from_cached_kscrash_report(NSString *reportPath, NSString *sdkVersion);
57+
4858
#pragma mark Crash Handling
4959

5060
static ReportContext g_crashHandlerReportContext;
@@ -57,7 +67,10 @@ static void onCrash(struct KSCrash_MonitorContext *monitorContext) {
5767
return;
5868
}
5969

60-
g_crashHandlerReportContext.metadata.time = time(NULL);
70+
struct timespec crashTime;
71+
clock_gettime(CLOCK_REALTIME, &crashTime);
72+
g_crashHandlerReportContext.metadata.time = crashTime.tv_sec;
73+
g_crashHandlerReportContext.metadata.timeNanos = (uint32_t)crashTime.tv_nsec;
6174
g_crashHandlerReportContext.monitorContext = monitorContext;
6275
bitdrift_writeKSCrashReport(&g_crashHandlerReportContext);
6376
}
@@ -66,6 +79,7 @@ static void onCrash(struct KSCrash_MonitorContext *monitorContext) {
6679

6780
@interface BitdriftKSCrashHandler ()
6881
@property(nonatomic, strong) NSString *kscrashReportFilePath;
82+
@property(nonatomic, strong) NSString *reportCollectionDirectoryPath;
6983
@property(nullable, nonatomic, strong) NSNumber *didCrashLastLaunch;
7084
@property(class, nonatomic, readonly, strong) BitdriftKSCrashHandler *sharedInstance;
7185
@end
@@ -108,6 +122,7 @@ - (BOOL)configureWithCrashReportDirectory:(NSURL *)crashReportDir error:(NSError
108122
}
109123

110124
self.kscrashReportFilePath = crashReportFile;
125+
self.reportCollectionDirectoryPath = [crashReportDir.absoluteURL.path stringByDeletingLastPathComponent];
111126

112127
BOOL result = YES;
113128

@@ -215,4 +230,33 @@ - (void)stopCrashReporter {
215230
return capture_enhance_metrickit_diagnostic_report(metricKitReport);
216231
}
217232

233+
+ (BOOL)createPreviousRunIssueReportWithOutputDir:(NSURL *)outputDir sdkVersion:(NSString *)sdkVersion error:(NSError **)error {
234+
return [self.sharedInstance createPreviousRunIssueReportWithOutputDir:outputDir sdkVersion:sdkVersion error:error];
235+
}
236+
237+
- (BOOL)createPreviousRunIssueReportWithOutputDir:(NSURL *)outputDir sdkVersion:(NSString *)sdkVersion error:(NSError **)error {
238+
if (self.kscrashReportFilePath == nil || self.didCrashLastLaunch.boolValue != YES) {
239+
return NO;
240+
}
241+
242+
NSString *identifier = [[NSUUID UUID] UUIDString];
243+
NSString *filename = [NSString stringWithFormat:@"kscrash_previous_run_crash_%@.cap", identifier];
244+
NSString *path = [[outputDir URLByAppendingPathComponent:filename] path];
245+
246+
switch (capture_create_report_from_cached_kscrash_report(path, sdkVersion)) {
247+
case ReportCreationResultSuccess:
248+
return YES;
249+
case ReportCreationResultReportDoesNotExist:
250+
return NO;
251+
case ReportCreationResultFailure:
252+
if (error != nil) {
253+
*error = [NSError errorWithDomain:@"BitdriftKSCrashHandler" code:0 userInfo:@{
254+
NSLocalizedDescriptionKey: @"Failed to create previous run issue report",
255+
NSLocalizedFailureReasonErrorKey: @"Unable to serialize cached KSCrash report",
256+
}];
257+
}
258+
return NO;
259+
}
260+
}
261+
218262
@end

platform/swift/source/crash_handler/ReportContext.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ extern "C" {
2121
typedef struct {
2222
pid_t pid;
2323
time_t time;
24+
uint32_t timeNanos;
2425
} ReportMetadata;
2526

2627
typedef struct {

platform/swift/source/crash_handler/ReportWriter.c

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,10 +159,67 @@ static bool writeAllThreads(BDCrashWriterHandle writer, const ReportContext* ctx
159159

160160
static bool writeMetadata(BDCrashWriterHandle writer, const ReportContext* ctx) {
161161
RETURN_ON_FAIL(writeKVUnsigned(writer, "crashedAt", ctx->metadata.time));
162+
RETURN_ON_FAIL(writeKVUnsigned(writer, "crashedAtNanos", ctx->metadata.timeNanos));
162163
RETURN_ON_FAIL(writeKVUnsigned(writer, "pid", ctx->metadata.pid));
163164
RETURN_ON_FAIL(writeKVUnsigned(writer, "exceptionType", ctx->monitorContext->mach.type));
164165
RETURN_ON_FAIL(writeKVUnsigned(writer, "exceptionCode", ctx->monitorContext->mach.code));
165166
RETURN_ON_FAIL(writeKVUnsigned(writer, "signal", ctx->monitorContext->signal.signum));
167+
if (ctx->monitorContext->eventID != NULL) {
168+
RETURN_ON_FAIL(writeKVString(writer, "eventID", ctx->monitorContext->eventID));
169+
}
170+
return true;
171+
}
172+
173+
static bool writeErrorInfo(BDCrashWriterHandle writer, const ReportContext* ctx) {
174+
RETURN_ON_FAIL(writeKVObjectBegin(writer, "error"));
175+
{
176+
if (ctx->monitorContext->exceptionName != NULL) {
177+
RETURN_ON_FAIL(writeKVString(writer, "exceptionName", ctx->monitorContext->exceptionName));
178+
}
179+
if (ctx->monitorContext->crashReason != NULL) {
180+
RETURN_ON_FAIL(writeKVString(writer, "crashReason", ctx->monitorContext->crashReason));
181+
}
182+
if (ctx->monitorContext->monitorId != NULL) {
183+
RETURN_ON_FAIL(writeKVString(writer, "monitorId", ctx->monitorContext->monitorId));
184+
}
185+
RETURN_ON_FAIL(writeKVBoolean(writer, "stackOverflow", ctx->monitorContext->isStackOverflow));
186+
}
187+
RETURN_ON_FAIL(bdcrw_write_container_end(writer));
188+
return true;
189+
}
190+
191+
static bool writeSystemInfo(BDCrashWriterHandle writer, const ReportContext* ctx) {
192+
RETURN_ON_FAIL(writeKVObjectBegin(writer, "system"));
193+
{
194+
if (ctx->monitorContext->System.bundleID != NULL) {
195+
RETURN_ON_FAIL(writeKVString(writer, "bundleID", ctx->monitorContext->System.bundleID));
196+
}
197+
if (ctx->monitorContext->System.bundleShortVersion != NULL) {
198+
RETURN_ON_FAIL(writeKVString(writer, "bundleShortVersion", ctx->monitorContext->System.bundleShortVersion));
199+
}
200+
if (ctx->monitorContext->System.bundleVersion != NULL) {
201+
RETURN_ON_FAIL(writeKVString(writer, "bundleVersion", ctx->monitorContext->System.bundleVersion));
202+
}
203+
if (ctx->monitorContext->System.osVersion != NULL) {
204+
RETURN_ON_FAIL(writeKVString(writer, "osVersion", ctx->monitorContext->System.osVersion));
205+
}
206+
if (ctx->monitorContext->System.systemName != NULL) {
207+
RETURN_ON_FAIL(writeKVString(writer, "systemName", ctx->monitorContext->System.systemName));
208+
}
209+
if (ctx->monitorContext->System.kernelVersion != NULL) {
210+
RETURN_ON_FAIL(writeKVString(writer, "kernelVersion", ctx->monitorContext->System.kernelVersion));
211+
}
212+
if (ctx->monitorContext->System.model != NULL) {
213+
RETURN_ON_FAIL(writeKVString(writer, "model", ctx->monitorContext->System.model));
214+
}
215+
if (ctx->monitorContext->System.binaryArchitecture != NULL) {
216+
RETURN_ON_FAIL(writeKVString(writer, "binaryArchitecture", ctx->monitorContext->System.binaryArchitecture));
217+
}
218+
if (ctx->monitorContext->System.timezone != NULL) {
219+
RETURN_ON_FAIL(writeKVString(writer, "timezone", ctx->monitorContext->System.timezone));
220+
}
221+
}
222+
RETURN_ON_FAIL(bdcrw_write_container_end(writer));
166223
return true;
167224
}
168225

@@ -175,6 +232,10 @@ static bool writeReport(BDCrashWriterHandle writer, ReportContext* ctx) {
175232
}
176233
RETURN_ON_FAIL(bdcrw_write_container_end(writer));
177234

235+
RETURN_ON_FAIL(writeErrorInfo(writer, ctx));
236+
237+
RETURN_ON_FAIL(writeSystemInfo(writer, ctx));
238+
178239
RETURN_ON_FAIL(writeKVArrayBegin(writer, "threads"));
179240
{
180241
RETURN_ON_FAIL(writeAllThreads(writer, ctx));

platform/swift/source/reports/BitdriftKSCrashWrapper.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,10 @@ NS_ASSUME_NONNULL_BEGIN
5252
*/
5353
+ (NSNumber *_Nullable)didCrashLastLaunch;
5454

55+
+ (BOOL)createPreviousRunIssueReportWithOutputDir:(NSURL *)outputDir
56+
sdkVersion:(NSString *)sdkVersion
57+
error:(NSError **)error;
58+
5559
+ (void)stopCrashReporter;
5660

5761
@end

platform/swift/source/reports/BitdriftKSCrashWrapper.m

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,16 @@ + (NSNumber *_Nullable)didCrashLastLaunch {
4747
#endif
4848
}
4949

50+
+ (BOOL)createPreviousRunIssueReportWithOutputDir:(NSURL *)outputDir
51+
sdkVersion:(NSString *)sdkVersion
52+
error:(NSError **)error {
53+
#ifndef BITDRIFT_OMIT_KSCRASH
54+
return [BitdriftKSCrashHandler createPreviousRunIssueReportWithOutputDir:outputDir sdkVersion:sdkVersion error:error];
55+
#else
56+
return false;
57+
#endif
58+
}
59+
5060
+ (void)stopCrashReporter {
5161
#ifndef BITDRIFT_OMIT_KSCRASH
5262
[BitdriftKSCrashHandler stopCrashReporter];

0 commit comments

Comments
 (0)