Skip to content

Commit 4ff2f1d

Browse files
committed
ios/tvos: kscrash crash reporting
1 parent 989417d commit 4ff2f1d

1 file changed

Lines changed: 140 additions & 0 deletions

File tree

ui/drivers/ui_cocoatouch.m

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,14 @@
6060
#import <GameController/GCMouse.h>
6161
#endif
6262

63+
#ifdef HAVE_KSCRASH
64+
#import <KSCrash.h>
65+
#import <KSCrashConfiguration.h>
66+
#import <KSCrashReportStore.h>
67+
#import <KSCrashInstallation.h>
68+
#import <KSCrashReport.h>
69+
#endif
70+
6371
#ifdef HAVE_SDL2
6472
#define SDL_MAIN_HANDLED
6573
#include "SDL.h"
@@ -629,8 +637,136 @@ - (void)handleAudioSessionInterruption:(NSNotification *)notification
629637
}
630638
}
631639

640+
#ifdef HAVE_KSCRASH
641+
- (NSString *)crashReportsPath
642+
{
643+
/* Store crash reports in Documents directory for user access */
644+
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
645+
NSString *documentsPath = [paths firstObject];
646+
return [documentsPath stringByAppendingPathComponent:@"CrashReports"];
647+
}
648+
649+
- (void)initKSCrash
650+
{
651+
NSString *crashReportsPath = [self crashReportsPath];
652+
653+
/* Create the crash reports directory if it doesn't exist */
654+
NSFileManager *fileManager = [NSFileManager defaultManager];
655+
NSError *createError = nil;
656+
if (![fileManager fileExistsAtPath:crashReportsPath])
657+
{
658+
[fileManager createDirectoryAtPath:crashReportsPath
659+
withIntermediateDirectories:YES
660+
attributes:nil
661+
error:&createError];
662+
if (createError)
663+
{
664+
NSLog(@"[KSCrash] Failed to create crash reports directory: %@\n", createError);
665+
return;
666+
}
667+
}
668+
669+
/* Configure KSCrash for local storage only */
670+
KSCrashConfiguration *config = [KSCrashConfiguration new];
671+
config.installPath = crashReportsPath;
672+
KSCrashReportStoreConfiguration *storeConfig = [KSCrashReportStoreConfiguration new];
673+
storeConfig.reportsPath = crashReportsPath;
674+
storeConfig.appName = @"RetroArch";
675+
storeConfig.maxReportCount = 10; /* Keep last 10 crash reports */
676+
config.reportStoreConfiguration = storeConfig;
677+
678+
/* Set appropriate monitors */
679+
if (jit_available())
680+
config.monitors = KSCrashMonitorTypeDebuggerSafe;
681+
else
682+
config.monitors = KSCrashMonitorTypeProductionSafe;
683+
/* Enable useful debugging features */
684+
config.enableMemoryIntrospection = YES;
685+
config.enableQueueNameSearch = YES;
686+
config.addConsoleLogToReport = YES;
687+
688+
/* Install KSCrash without any network sink */
689+
NSError *installError = nil;
690+
if (![[KSCrash sharedInstance] installWithConfiguration:config error:&installError])
691+
{
692+
NSLog(@"[KSCrash] Failed to install crash reporter: %@\n", installError);
693+
return;
694+
}
695+
696+
NSLog(@"[KSCrash] reports will be stored in: %@\n", crashReportsPath);
697+
}
698+
699+
- (void)processKSCrashReports
700+
{
701+
/* Check if we crashed last launch */
702+
if (![[KSCrash sharedInstance] crashedLastLaunch])
703+
return;
704+
705+
if ([[[KSCrash sharedInstance] reportStore] reportCount] <= 0)
706+
return;
707+
708+
RARCH_LOG("[KSCrash] crash report available in Documents/CrashReports\n");
709+
710+
/* Process crash reports to strip binary_images section */
711+
KSCrashReportStore *store = [[KSCrash sharedInstance] reportStore];
712+
NSArray<NSNumber *> *reportIDs = [store reportIDs];
713+
for (NSNumber *reportIDNum in reportIDs)
714+
{
715+
int64_t reportID = [reportIDNum longLongValue];
716+
KSCrashReportDictionary *report = [store reportForID:reportID];
717+
if (!report)
718+
continue;
719+
720+
NSMutableDictionary *mutableReport = [report.value mutableCopy];
721+
722+
/* Remove binary_images to reduce file size */
723+
if ([mutableReport objectForKey:@"binary_images"])
724+
[mutableReport removeObjectForKey:@"binary_images"];
725+
726+
/* Save pretty-printed version as standalone file */
727+
NSData *prettyData = [NSJSONSerialization dataWithJSONObject:mutableReport
728+
options:NSJSONWritingPrettyPrinted
729+
error:nil];
730+
if (prettyData)
731+
{
732+
NSString *reportPath = [NSString stringWithFormat:@"%@/report-%lld.json",
733+
[self crashReportsPath], reportID];
734+
[prettyData writeToFile:reportPath options:NSDataWritingAtomic error:nil];
735+
RARCH_LOG("[KSCrash] Saved stripped report %lld to: %s\n",
736+
reportID, [reportPath UTF8String]);
737+
}
738+
739+
/* Log minified JSON on a single line for easy extraction */
740+
NSData *minifiedData = [NSJSONSerialization dataWithJSONObject:mutableReport
741+
options:0 /* no pretty printing */
742+
error:nil];
743+
if (minifiedData)
744+
{
745+
NSString *jsonString = [[NSString alloc] initWithData:minifiedData
746+
encoding:NSUTF8StringEncoding];
747+
if (jsonString)
748+
{
749+
/* Log with a unique marker that can be extracted with grep/sed */
750+
RARCH_LOG("[KSCrash] Report %lld follows on next line\n", reportID);
751+
RARCH_LOG("%s\n", [jsonString UTF8String]);
752+
}
753+
}
754+
755+
/* Delete the report from KSCrash store to prevent re-logging on next launch */
756+
[store deleteReportWithID:reportID];
757+
}
758+
759+
if (reportIDs.count > 0)
760+
RARCH_LOG("[KSCrash] Processed and removed %lu report(s) from store\n", (unsigned long)reportIDs.count);
761+
}
762+
#endif
763+
632764
- (void)applicationDidFinishLaunching:(UIApplication *)application
633765
{
766+
#ifdef HAVE_KSCRASH
767+
[self initKSCrash];
768+
#endif
769+
634770
char arguments[] = "retroarch";
635771
char *argv[] = {arguments, NULL};
636772
int argc = 1;
@@ -680,6 +816,10 @@ - (void)applicationDidFinishLaunching:(UIApplication *)application
680816

681817
rarch_main(argc, argv, NULL);
682818

819+
#ifdef HAVE_KSCRASH
820+
[self processKSCrashReports];
821+
#endif
822+
683823
uico_driver_state_t *uico_st = uico_state_get_ptr();
684824
rarch_setting_t *appicon_setting = menu_setting_find_enum(MENU_ENUM_LABEL_APPICON_SETTINGS);
685825
struct string_list *icons;

0 commit comments

Comments
 (0)