Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[PLAT-2016] Fix Swift fatal error message reporting #948

Merged
merged 9 commits into from
Jan 5, 2021
48 changes: 10 additions & 38 deletions Bugsnag.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion Bugsnag/BugsnagCrashSentry.m
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ - (void)install:(BugsnagConfiguration *)config
BugsnagErrorReportSink *sink = [[BugsnagErrorReportSink alloc] initWithApiClient:apiClient configuration:config notifier:notifier];
BSG_KSCrash *ksCrash = [BSG_KSCrash sharedInstance];
ksCrash.sink = sink;
ksCrash.introspectMemory = YES;
ksCrash.introspectMemory = NO;
kstenerud marked this conversation as resolved.
Show resolved Hide resolved
ksCrash.onCrash = onCrash;
ksCrash.maxStoredReports = (int)config.maxPersistedEvents;

Expand Down
25 changes: 25 additions & 0 deletions Bugsnag/KSCrash/Source/KSCrash/Recording/BSG_KSCrashReport.c
Original file line number Diff line number Diff line change
Expand Up @@ -965,6 +965,27 @@ void bsg_kscrw_i_writeNotableAddresses(
writer->endContainer(writer);
}

/** Write the message from the `__crash_info` Mach section into the report.
*
* @param writer The writer.
*
* @param key The object key.
*
* @param address The address of the first frame in the backtrace.
*/
void bsg_kscrw_i_writeCrashInfoMessage(const BSG_KSCrashReportWriter *const writer,
const char *key, uintptr_t address) {
BSG_Mach_Header_Info *image = bsg_mach_headers_image_at_address(address);
if (!image) {
BSG_KSLOG_ERROR("Could not locate mach header info");
return;
}
const char *message = bsg_mach_headers_get_crash_info_message(image);
if (message) {
writer->addStringElement(writer, key, message);
}
}

/** Write information about a thread to the report.
*
* @param writer The writer.
Expand Down Expand Up @@ -1021,6 +1042,10 @@ void bsg_kscrw_i_writeThread(const BSG_KSCrashReportWriter *const writer,
writer, BSG_KSCrashField_NotableAddresses, machineContext);
}
}
if (isCrashedThread && backtrace && backtraceLength) {
bsg_kscrw_i_writeCrashInfoMessage(writer, BSG_KSCrashField_CrashInfoMessage,
backtrace[0]);
}
}
writer->endContainer(writer);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@
#define BSG_KSCrashField_Backtrace "backtrace"
#define BSG_KSCrashField_Basic "basic"
#define BSG_KSCrashField_Crashed "crashed"
#define BSG_KSCrashField_CrashInfoMessage "crash_info_message"
#define BSG_KSCrashField_CurrentThread "current_thread"
#define BSG_KSCrashField_DispatchQueue "dispatch_queue"
#define BSG_KSCrashField_NotableAddresses "notable_addresses"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,36 @@
//
// BSG_KSMachHeaders.m
// BSG_KSMachHeaders.c
// Bugsnag
//
// Created by Robin Macharg on 04/05/2020.
// Copyright © 2020 Bugsnag. All rights reserved.
//

#import <mach-o/dyld.h>
#import <dlfcn.h>
#import <Foundation/Foundation.h>
#import "BSG_KSDynamicLinker.h"
#import "BSG_KSMachHeaders.h"
#include "BSG_KSMachHeaders.h"

#include "BSG_KSDynamicLinker.h"
#include "BSG_KSMach.h"

#include <dispatch/dispatch.h>
#include <dlfcn.h>
#include <mach-o/dyld.h>
#include <stdlib.h>

// Copied from https://github.com/apple/swift/blob/swift-5.0-RELEASE/include/swift/Runtime/Debug.h#L28-L40

#define CRASHREPORTER_ANNOTATIONS_VERSION 5
#define CRASHREPORTER_ANNOTATIONS_SECTION "__crash_info"

struct crashreporter_annotations_t {
uint64_t version; // unsigned long
uint64_t message; // char *
uint64_t signature_string; // char *
uint64_t backtrace; // char *
uint64_t message2; // char *
uint64_t thread; // uint64_t
uint64_t dialog_mode; // unsigned int
uint64_t abort_cause; // unsigned int
};

// MARK: - Mach Header Linked List

Expand Down Expand Up @@ -255,3 +275,66 @@ uintptr_t bsg_mach_headers_image_at_base_of_image_index(const struct mach_header

return 0;
}
static uintptr_t bsg_mach_header_info_get_section_addr_named(const BSG_Mach_Header_Info *header, const char *name) {
uintptr_t cmdPtr = bsg_mach_headers_first_cmd_after_header(header->header);
if (!cmdPtr) {
return 0;
}
for (uint32_t i = 0; i < header->header->ncmds; i++) {
const struct load_command *loadCmd = (struct load_command *)cmdPtr;
if (loadCmd->cmd == LC_SEGMENT) {
const struct segment_command *segment = (void *)cmdPtr;
char *sectionPtr = (void *)(cmdPtr + sizeof(*segment));
for (uint32_t i = 0; i < segment->nsects; i++) {
struct section *section = (void *)sectionPtr;
if (strcmp(name, section->sectname) == 0) {
return section->addr + header->slide;
}
sectionPtr += sizeof(*section);
}
} else if (loadCmd->cmd == LC_SEGMENT_64) {
const struct segment_command_64 *segment = (void *)cmdPtr;
char *sectionPtr = (void *)(cmdPtr + sizeof(*segment));
for (uint32_t i = 0; i < segment->nsects; i++) {
struct section_64 *section = (void *)sectionPtr;
if (strcmp(name, section->sectname) == 0) {
return (uintptr_t)section->addr + header->slide;
}
sectionPtr += sizeof(*section);
}
}
cmdPtr += loadCmd->cmdsize;
}
return 0;
}

const char *bsg_mach_headers_get_crash_info_message(const BSG_Mach_Header_Info *header) {
struct crashreporter_annotations_t info;
uintptr_t sectionAddress = bsg_mach_header_info_get_section_addr_named(header, CRASHREPORTER_ANNOTATIONS_SECTION);
if (!sectionAddress) {
return NULL;
}
if (bsg_ksmachcopyMem((void *)sectionAddress, &info, sizeof(info)) != KERN_SUCCESS) {
return NULL;
}
// Version 4 was in use until iOS 9 / Swift 2.0 when the version was bumped to 5.
if (info.version > CRASHREPORTER_ANNOTATIONS_VERSION) {
return NULL;
}
if (!info.message) {
return NULL;
}
// Probe the string to ensure it's safe to read.
for (uintptr_t i = 0; i < 500; i++) {
char c;
if (bsg_ksmachcopyMem((void *)(info.message + i), &c, sizeof(c)) != KERN_SUCCESS) {
// String is not readable.
return NULL;
}
if (c == '\0') {
// Found end of string.
return (const char *)info.message;
}
}
return NULL;
}
Original file line number Diff line number Diff line change
Expand Up @@ -99,4 +99,11 @@ uintptr_t bsg_mach_headers_first_cmd_after_header(const struct mach_header *head
*/
uintptr_t bsg_mach_headers_image_at_base_of_image_index(const struct mach_header *header);

/** Get the __crash_info message of the specified image.
*
* @param header The header to get commands for.
* @return The __crash_info message, or NULL if no readable message could be found.
*/
const char *bsg_mach_headers_get_crash_info_message(const BSG_Mach_Header_Info *header);

#endif /* BSG_KSMachHeaders_h */
3 changes: 3 additions & 0 deletions Bugsnag/Payload/BugsnagError+Private.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ NS_ASSUME_NONNULL_BEGIN

+ (BugsnagError *)errorFromJson:(NSDictionary *)json;

/// Parses the `__crash_info` message and updates the `errorClass` and `errorMessage` as appropriate.
- (void)updateWithCrashInfoMessage:(NSString *)crashInfoMessage;

- (NSDictionary *)toDictionary;

@end
Expand Down
46 changes: 36 additions & 10 deletions Bugsnag/Payload/BugsnagError.m
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,16 @@
// Copyright © 2020 Bugsnag. All rights reserved.
//

#import "BugsnagError.h"
#import "BugsnagError+Private.h"

#import "BSG_KSCrashReportFields.h"
#import "BugsnagCollections.h"
#import "BugsnagKeys.h"
#import "BugsnagLogger.h"
#import "BugsnagStackframe+Private.h"
#import "BugsnagStacktrace.h"
#import "BugsnagCollections.h"
#import "RegisterErrorData.h"
#import "BugsnagThread.h"
#import "BugsnagThread+Private.h"


NSString *_Nonnull BSGSerializeErrorType(BSGErrorType errorType) {
switch (errorType) {
Expand Down Expand Up @@ -87,12 +89,6 @@ - (instancetype)initWithEvent:(NSDictionary *)event errorReportingThread:(Bugsna
_type = BSGErrorTypeCocoa;

if (![[event valueForKeyPath:@"user.state.didOOM"] boolValue]) {
NSArray *threadDict = [event valueForKeyPath:@"crash.threads"];
RegisterErrorData *data = [RegisterErrorData errorDataFromThreads:threadDict];
if (data) {
_errorClass = data.errorClass;
_errorMessage = data.errorMessage;
}
_stacktrace = thread.stacktrace;
}
}
Expand Down Expand Up @@ -132,6 +128,36 @@ + (BugsnagError *)errorFromJson:(NSDictionary *)json {
return error;
}

- (void)updateWithCrashInfoMessage:(NSString *)crashInfoMessage {
@try {
// Messages that match this pattern should override the errorClass (and errorMessage if there is enough information.)
NSString *pattern = @"^(Assertion failed|Fatal error|Precondition failed): ((.+): )?file .+, line \\d+\n$";
NSRegularExpression *regex = [[NSRegularExpression alloc] initWithPattern:pattern options:NSRegularExpressionCaseInsensitive error:nil];
NSArray<NSTextCheckingResult *> *matches = [regex matchesInString:crashInfoMessage options:0 range:NSMakeRange(0, crashInfoMessage.length)];
if (matches.count != 1 || matches[0].numberOfRanges != 4) {
if (!self.errorMessage.length) {
kstenerud marked this conversation as resolved.
Show resolved Hide resolved
// It's better to fall back to the raw string than have an empty errorMessage.
self.errorMessage = crashInfoMessage;
}
return;
}
NSRange errorClassRange = [matches[0] rangeAtIndex:1];
if (errorClassRange.location != NSNotFound) {
self.errorClass = [crashInfoMessage substringWithRange:errorClassRange];
}
NSRange errorMessageRange = [matches[0] rangeAtIndex:3];
if (errorMessageRange.location != NSNotFound) {
self.errorMessage = [crashInfoMessage substringWithRange:errorMessageRange];
}
} @catch (NSException *exception) {
bsg_log_err(@"Exception thrown while parsing crash info message: %@", exception);
if (!self.errorMessage.length) {
// It's better to fall back to the raw string than have an empty errorMessage.
self.errorMessage = crashInfoMessage;
}
}
}

- (NSDictionary *)findErrorReportingThread:(NSDictionary *)event {
NSArray *threads = [event valueForKeyPath:@"crash.threads"];

Expand Down
12 changes: 9 additions & 3 deletions Bugsnag/Payload/BugsnagEvent.m
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

#import "BugsnagPlatformConditional.h"

#import "BugsnagEvent+Private.h"

#if BSG_PLATFORM_IOS
#import "BSGUIKit.h"
#include <sys/utsname.h>
Expand All @@ -27,7 +29,6 @@
#import "BugsnagConfiguration+Private.h"
#import "BugsnagDeviceWithState+Private.h"
#import "BugsnagError+Private.h"
#import "BugsnagEvent+Private.h"
#import "BugsnagHandledState.h"
#import "BugsnagKeys.h"
#import "BugsnagMetadata+Private.h"
Expand All @@ -36,7 +37,7 @@
#import "BugsnagStacktrace+Private.h"
#import "BugsnagThread+Private.h"
#import "BugsnagUser+Private.h"
#import "RegisterErrorData.h"


static NSString *const DEFAULT_EXCEPTION_TYPE = @"cocoa";

Expand Down Expand Up @@ -267,7 +268,7 @@ - (instancetype)initWithOOMData:(NSDictionary *)event {
* @return a BugsnagEvent containing the parsed information
*/
- (instancetype)initWithKSCrashData:(NSDictionary *)event {
NSDictionary *error = [event valueForKeyPath:@"crash.error"];
NSMutableDictionary *error = [[event valueForKeyPath:@"crash.error"] mutableCopy];
NSString *errorType = error[BSGKeyType];

// Always assume that a report coming from KSCrash is by default an unhandled error.
Expand Down Expand Up @@ -333,6 +334,11 @@ - (instancetype)initWithKSCrashData:(NSDictionary *)event {

NSArray<BugsnagError *> *errors = @[[[BugsnagError alloc] initWithEvent:event errorReportingThread:errorReportingThread]];

if (errorReportingThread.crashInfoMessage) {
[errors[0] updateWithCrashInfoMessage:errorReportingThread.crashInfoMessage];
error[@"crashInfo"] = errorReportingThread.crashInfoMessage;
}

BugsnagHandledState *handledState;
if (recordedState) {
handledState = [[BugsnagHandledState alloc] initWithDictionary:recordedState];
Expand Down
2 changes: 2 additions & 0 deletions Bugsnag/Payload/BugsnagThread+Private.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ NS_ASSUME_NONNULL_BEGIN

+ (instancetype)threadFromJson:(NSDictionary *)json;

@property (readonly) NSString *crashInfoMessage;

+ (NSDictionary *)enhanceThreadInfo:(NSDictionary *)thread
depth:(NSUInteger)depth
errorType:(nullable NSString *)errorType;
Expand Down
15 changes: 8 additions & 7 deletions Bugsnag/Payload/BugsnagThread.m
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@
// Copyright © 2020 Bugsnag. All rights reserved.
//

#import "BugsnagThread.h"
#import "BugsnagThread+Private.h"

#import "BSG_KSCrashReportFields.h"
#import "BugsnagCollections.h"
#import "BugsnagStackframe+Private.h"
#import "BugsnagStacktrace+Private.h"
Expand Down Expand Up @@ -56,13 +57,13 @@ - (instancetype)initWithId:(NSString *)id

- (instancetype)initWithThread:(NSDictionary *)thread binaryImages:(NSArray *)binaryImages {
if (self = [super init]) {
_errorReportingThread = [thread[@"crashed"] boolValue];
self.id = [thread[@"index"] stringValue];
self.type = BSGThreadTypeCocoa;

NSArray *backtrace = thread[@"backtrace"][@"contents"];
_errorReportingThread = [thread[@(BSG_KSCrashField_Crashed)] boolValue];
_id = [thread[@(BSG_KSCrashField_Index)] stringValue];
_type = BSGThreadTypeCocoa;
_crashInfoMessage = [thread[@(BSG_KSCrashField_CrashInfoMessage)] copy];
NSArray *backtrace = thread[@(BSG_KSCrashField_Backtrace)][@(BSG_KSCrashField_Contents)];
BugsnagStacktrace *frames = [[BugsnagStacktrace alloc] initWithTrace:backtrace binaryImages:binaryImages];
self.stacktrace = frames.trace;
_stacktrace = [frames.trace copy];
}
return self;
}
Expand Down
23 changes: 0 additions & 23 deletions Bugsnag/RegisterErrorData.h

This file was deleted.

Loading