firebase log level

This commit is contained in:
oscarz
2024-08-29 18:25:13 +08:00
parent 8500300d18
commit 27c160beaf
1165 changed files with 122916 additions and 1 deletions

View File

@ -0,0 +1,95 @@
// Copyright 2019 Google
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import <Foundation/Foundation.h>
#if CLS_TARGET_OS_HAS_UIKIT
#import <UIKit/UIKit.h>
#endif
__BEGIN_DECLS
#define FIRCLSApplicationActivityDefault \
(NSActivitySuddenTerminationDisabled | NSActivityAutomaticTerminationDisabled)
/**
* Type to indicate application installation source
*/
typedef NS_ENUM(NSInteger, FIRCLSApplicationInstallationSourceType) {
FIRCLSApplicationInstallationSourceTypeDeveloperInstall = 1,
// 2 and 3 are reserved for legacy values.
FIRCLSApplicationInstallationSourceTypeAppStore = 4
};
/**
* Returns the application bundle identifier with occurrences of "/" replaced by "_"
*/
NSString* FIRCLSApplicationGetBundleIdentifier(void);
/**
* Returns the SDK's bundle ID
*/
NSString* FIRCLSApplicationGetSDKBundleID(void);
/**
* Returns the platform identifier, either: ios, mac, or tvos.
* Catalyst apps are treated as mac.
* This is a legacy function, for platform identificaiton please use
* FIRCLSApplicationGetFirebasePlatform.
*/
NSString* FIRCLSApplicationGetPlatform(void);
/**
* Returns the operating system for filtering. Should be kept consistent with Analytics.
*/
NSString* FIRCLSApplicationGetFirebasePlatform(void);
/**
* Returns the user-facing app name
*/
NSString* FIRCLSApplicationGetName(void);
/**
* Returns the build number
*/
NSString* FIRCLSApplicationGetBundleVersion(void);
/**
* Returns the human-readable build version
*/
NSString* FIRCLSApplicationGetShortBundleVersion(void);
/**
* Returns a number to indicate how the app has been installed: Developer / App Store
*/
FIRCLSApplicationInstallationSourceType FIRCLSApplicationInstallationSource(void);
BOOL FIRCLSApplicationIsExtension(void);
NSString* FIRCLSApplicationExtensionPointIdentifier(void);
#if CLS_TARGET_OS_HAS_UIKIT
UIApplication* FIRCLSApplicationSharedInstance(void);
#else
id FIRCLSApplicationSharedInstance(void);
#endif
void FIRCLSApplicationOpenURL(NSURL* url,
NSExtensionContext* extensionContext,
void (^completionBlock)(BOOL success));
id<NSObject> FIRCLSApplicationBeginActivity(NSActivityOptions options, NSString* reason);
void FIRCLSApplicationEndActivity(id<NSObject> activity);
void FIRCLSApplicationActivity(NSActivityOptions options, NSString* reason, void (^block)(void));
__END_DECLS

View File

@ -0,0 +1,234 @@
// Copyright 2019 Google
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import "Crashlytics/Crashlytics/Components/FIRCLSApplication.h"
#import "Crashlytics/Crashlytics/Components/FIRCLSHost.h"
#import "Crashlytics/Crashlytics/Helpers/FIRCLSUtility.h"
#import <GoogleUtilities/GULAppEnvironmentUtil.h>
#if CLS_TARGET_OS_OSX
#import <AppKit/AppKit.h>
#endif
#if CLS_TARGET_OS_HAS_UIKIT
#import <UIKit/UIKit.h>
#endif
NSString* FIRCLSApplicationGetBundleIdentifier(void) {
return [[[NSBundle mainBundle] bundleIdentifier] stringByReplacingOccurrencesOfString:@"/"
withString:@"_"];
}
NSString* FIRCLSApplicationGetSDKBundleID(void) {
return
[@"com.google.firebase.crashlytics." stringByAppendingString:FIRCLSApplicationGetPlatform()];
}
// Legacy function, we use FIRCLSApplicationGetFirebasePlatform now for platform specification.
// Can't clean the code since some endpoints setup depend on this function.
NSString* FIRCLSApplicationGetPlatform(void) {
#if defined(TARGET_OS_MACCATALYST) && TARGET_OS_MACCATALYST
return @"mac";
#elif TARGET_OS_IOS
return @"ios";
#elif TARGET_OS_OSX
return @"mac";
#elif TARGET_OS_TV
return @"tvos";
#elif TARGET_OS_WATCH
return @"ios";
#elif defined(TARGET_OS_VISION) && TARGET_OS_VISION
return @"ios";
#endif
}
NSString* FIRCLSApplicationGetFirebasePlatform(void) {
NSString* firebasePlatform = [GULAppEnvironmentUtil applePlatform];
#if TARGET_OS_IOS
// This check is necessary because iOS-only apps running on iPad
// will report UIUserInterfaceIdiomPhone via UI_USER_INTERFACE_IDIOM().
if ([firebasePlatform isEqualToString:@"ios"] &&
([[UIDevice currentDevice].model.lowercaseString containsString:@"ipad"] ||
[[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad)) {
return @"ipados";
}
#endif
return firebasePlatform;
}
// these defaults match the FIRCLSInfoPlist helper in FIRCLSIDEFoundation
NSString* FIRCLSApplicationGetBundleVersion(void) {
return [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"];
}
NSString* FIRCLSApplicationGetShortBundleVersion(void) {
return [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"];
}
NSString* FIRCLSApplicationGetName(void) {
NSString* name;
NSBundle* mainBundle;
mainBundle = [NSBundle mainBundle];
name = [mainBundle objectForInfoDictionaryKey:@"CFBundleDisplayName"];
if (name) {
return name;
}
name = [mainBundle objectForInfoDictionaryKey:@"CFBundleName"];
if (name) {
return name;
}
return FIRCLSApplicationGetBundleVersion();
}
BOOL FIRCLSApplicationHasAppStoreReceipt(void) {
NSURL* url = NSBundle.mainBundle.appStoreReceiptURL;
return [NSFileManager.defaultManager fileExistsAtPath:[url path]];
}
FIRCLSApplicationInstallationSourceType FIRCLSApplicationInstallationSource(void) {
if (FIRCLSApplicationHasAppStoreReceipt()) {
return FIRCLSApplicationInstallationSourceTypeAppStore;
}
return FIRCLSApplicationInstallationSourceTypeDeveloperInstall;
}
BOOL FIRCLSApplicationIsExtension(void) {
return FIRCLSApplicationExtensionPointIdentifier() != nil;
}
NSString* FIRCLSApplicationExtensionPointIdentifier(void) {
id extensionDict = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"NSExtension"];
if (!extensionDict) {
return nil;
}
if (![extensionDict isKindOfClass:[NSDictionary class]]) {
FIRCLSSDKLog("Error: NSExtension Info.plist entry is mal-formed\n");
return nil;
}
id typeValue = [(NSDictionary*)extensionDict objectForKey:@"NSExtensionPointIdentifier"];
if (![typeValue isKindOfClass:[NSString class]]) {
FIRCLSSDKLog("Error: NSExtensionPointIdentifier Info.plist entry is mal-formed\n");
return nil;
}
return typeValue;
}
#if CLS_TARGET_OS_HAS_UIKIT
UIApplication* FIRCLSApplicationSharedInstance(void) {
if (FIRCLSApplicationIsExtension()) {
return nil;
}
return [[UIApplication class] performSelector:@selector(sharedApplication)];
}
#elif CLS_TARGET_OS_OSX
id FIRCLSApplicationSharedInstance(void) {
return [NSClassFromString(@"NSApplication") sharedApplication];
}
#else
id FIRCLSApplicationSharedInstance(void) {
return nil; // FIXME: what do we actually return for watch?
}
#endif
void FIRCLSApplicationOpenURL(NSURL* url,
NSExtensionContext* extensionContext,
void (^completionBlock)(BOOL success)) {
if (extensionContext) {
[extensionContext openURL:url completionHandler:completionBlock];
return;
}
BOOL result = NO;
#if TARGET_OS_IOS
// What's going on here is the value returned is a scalar, but we really need an object to
// call this dynamically. Hoops must be jumped.
NSInvocationOperation* op =
[[NSInvocationOperation alloc] initWithTarget:FIRCLSApplicationSharedInstance()
selector:@selector(openURL:)
object:url];
[op start];
[op.result getValue:&result];
#elif CLS_TARGET_OS_OSX
result = [[NSClassFromString(@"NSWorkspace") sharedWorkspace] openURL:url];
#endif
completionBlock(result);
}
id<NSObject> FIRCLSApplicationBeginActivity(NSActivityOptions options, NSString* reason) {
if ([[NSProcessInfo processInfo] respondsToSelector:@selector(beginActivityWithOptions:
reason:)]) {
return [[NSProcessInfo processInfo] beginActivityWithOptions:options reason:reason];
}
#if CLS_TARGET_OS_OSX
if (options & NSActivitySuddenTerminationDisabled) {
[[NSProcessInfo processInfo] disableSuddenTermination];
}
if (options & NSActivityAutomaticTerminationDisabled) {
[[NSProcessInfo processInfo] disableAutomaticTermination:reason];
}
#endif
// encode the options, so we can undo our work later
return @{@"options" : @(options), @"reason" : reason};
}
void FIRCLSApplicationEndActivity(id<NSObject> activity) {
if (!activity) {
return;
}
if ([[NSProcessInfo processInfo] respondsToSelector:@selector(endActivity:)]) {
[[NSProcessInfo processInfo] endActivity:activity];
return;
}
#if CLS_TARGET_OS_OSX
NSInteger options = [[(NSDictionary*)activity objectForKey:@"options"] integerValue];
if (options & NSActivitySuddenTerminationDisabled) {
[[NSProcessInfo processInfo] enableSuddenTermination];
}
if (options & NSActivityAutomaticTerminationDisabled) {
[[NSProcessInfo processInfo]
enableAutomaticTermination:[(NSDictionary*)activity objectForKey:@"reason"]];
}
#endif
}
void FIRCLSApplicationActivity(NSActivityOptions options, NSString* reason, void (^block)(void)) {
id<NSObject> activity = FIRCLSApplicationBeginActivity(options, reason);
block();
FIRCLSApplicationEndActivity(activity);
}

View File

@ -0,0 +1,80 @@
// Copyright 2019 Google
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#pragma once
#include <stdbool.h>
#include <stdint.h>
#include "Crashlytics/Crashlytics/Helpers/FIRCLSFeatures.h"
#include "Crashlytics/Crashlytics/Helpers/FIRCLSFile.h"
#include "Crashlytics/Shared/FIRCLSMachO/FIRCLSMachO.h"
__BEGIN_DECLS
// Typically, apps seem to have ~700 binary images loaded
#define CLS_BINARY_IMAGE_RUNTIME_NODE_COUNT (1024)
#define CLS_BINARY_IMAGE_RUNTIME_NODE_NAME_SIZE (32)
#define CLS_BINARY_IMAGE_RUNTIME_NODE_RECORD_NAME 0
#define FIRCLSUUIDStringLength (33)
typedef struct {
_Atomic(void*) volatile baseAddress;
uint64_t size;
#if CLS_DWARF_UNWINDING_SUPPORTED
const void* ehFrame;
#endif
#if CLS_COMPACT_UNWINDING_SUPPORTED
const void* unwindInfo;
#endif
const void* crashInfo;
#if CLS_BINARY_IMAGE_RUNTIME_NODE_RECORD_NAME
char name[CLS_BINARY_IMAGE_RUNTIME_NODE_NAME_SIZE];
#endif
} FIRCLSBinaryImageRuntimeNode;
typedef struct {
char uuidString[FIRCLSUUIDStringLength];
bool encrypted;
FIRCLSMachOVersion builtSDK;
FIRCLSMachOVersion minSDK;
FIRCLSBinaryImageRuntimeNode node;
struct FIRCLSMachOSlice slice;
intptr_t vmaddr_slide;
} FIRCLSBinaryImageDetails;
typedef struct {
const char* path;
} FIRCLSBinaryImageReadOnlyContext;
typedef struct {
FIRCLSFile file;
FIRCLSBinaryImageRuntimeNode nodes[CLS_BINARY_IMAGE_RUNTIME_NODE_COUNT];
} FIRCLSBinaryImageReadWriteContext;
void FIRCLSBinaryImageInit(void);
#if CLS_COMPACT_UNWINDING_SUPPORTED
bool FIRCLSBinaryImageSafeFindImageForAddress(uintptr_t address,
FIRCLSBinaryImageRuntimeNode* image);
bool FIRCLSBinaryImageSafeHasUnwindInfo(FIRCLSBinaryImageRuntimeNode* image);
#endif
bool FIRCLSBinaryImageFindImageForUUID(const char* uuidString,
FIRCLSBinaryImageDetails* imageDetails);
bool FIRCLSBinaryImageRecordMainExecutable(FIRCLSFile* file);
__END_DECLS

View File

@ -0,0 +1,607 @@
// Copyright 2019 Google
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "Crashlytics/Crashlytics/Components/FIRCLSBinaryImage.h"
#include <libkern/OSAtomic.h>
#include <mach-o/dyld.h>
#include <mach-o/getsect.h>
#include <stdatomic.h>
#include "Crashlytics/Crashlytics/Components/FIRCLSGlobals.h"
#include "Crashlytics/Crashlytics/Components/FIRCLSHost.h"
#include "Crashlytics/Crashlytics/Helpers/FIRCLSDefines.h"
#include "Crashlytics/Crashlytics/Helpers/FIRCLSFeatures.h"
#include "Crashlytics/Crashlytics/Helpers/FIRCLSFile.h"
#include "Crashlytics/Crashlytics/Helpers/FIRCLSUtility.h"
#include "Crashlytics/Shared/FIRCLSByteUtility.h"
#include "Crashlytics/Shared/FIRCLSMachO/FIRCLSMachO.h"
#include <dispatch/dispatch.h>
// this is defined only if __OPEN_SOURCE__ is *not* defined in the TVOS SDK's mach-o/loader.h
// also, it has not yet made it back to the OSX SDKs, for example
#ifndef LC_VERSION_MIN_TVOS
#define LC_VERSION_MIN_TVOS 0x2F
#endif
#pragma mark Prototypes
static bool FIRCLSBinaryImageOpenIfNeeded(bool* needsClosing);
static void FIRCLSBinaryImageAddedCallback(const struct mach_header* mh, intptr_t vmaddr_slide);
static void FIRCLSBinaryImageRemovedCallback(const struct mach_header* mh, intptr_t vmaddr_slide);
static void FIRCLSBinaryImageChanged(bool added,
const struct mach_header* mh,
intptr_t vmaddr_slide);
static bool FIRCLSBinaryImageFillInImageDetails(FIRCLSBinaryImageDetails* details);
static void FIRCLSBinaryImageStoreNode(bool added, FIRCLSBinaryImageDetails imageDetails);
static void FIRCLSBinaryImageRecordSlice(bool added, const FIRCLSBinaryImageDetails imageDetails);
#pragma mark - Core API
void FIRCLSBinaryImageInit(void) {
// initialize our node array to all zeros
memset(&_firclsContext.writable->binaryImage, 0, sizeof(_firclsContext.writable->binaryImage));
_firclsContext.writable->binaryImage.file.fd = -1;
dispatch_async(FIRCLSGetBinaryImageQueue(), ^{
if (!FIRCLSUnlinkIfExists(_firclsContext.readonly->binaryimage.path)) {
FIRCLSSDKLog("Unable to reset the binary image log file %s\n", strerror(errno));
}
bool needsClosing; // unneeded
if (!FIRCLSBinaryImageOpenIfNeeded(&needsClosing)) {
FIRCLSSDKLog("Error: Unable to open the binary image log file during init\n");
}
});
_dyld_register_func_for_add_image(FIRCLSBinaryImageAddedCallback);
_dyld_register_func_for_remove_image(FIRCLSBinaryImageRemovedCallback);
dispatch_async(FIRCLSGetBinaryImageQueue(), ^{
FIRCLSFileClose(&_firclsContext.writable->binaryImage.file);
});
}
static bool FIRCLSBinaryImageOpenIfNeeded(bool* needsClosing) {
if (!FIRCLSIsValidPointer(_firclsContext.writable)) {
return false;
}
if (!FIRCLSIsValidPointer(_firclsContext.readonly)) {
return false;
}
if (!FIRCLSIsValidPointer(needsClosing)) {
return false;
}
*needsClosing = false;
if (FIRCLSFileIsOpen(&_firclsContext.writable->binaryImage.file)) {
return true;
}
if (!FIRCLSFileInitWithPath(&_firclsContext.writable->binaryImage.file,
_firclsContext.readonly->binaryimage.path, false)) {
FIRCLSSDKLog("Error: unable to open binary image log file\n");
return false;
}
*needsClosing = true;
return true;
}
#if CLS_COMPACT_UNWINDING_SUPPORTED
bool FIRCLSBinaryImageSafeFindImageForAddress(uintptr_t address,
FIRCLSBinaryImageRuntimeNode* image) {
if (!FIRCLSContextIsInitialized()) {
return false;
}
if (address == 0) {
return false;
}
if (!FIRCLSIsValidPointer(image)) {
return false;
}
FIRCLSBinaryImageRuntimeNode* nodes = _firclsContext.writable->binaryImage.nodes;
if (!nodes) {
FIRCLSSDKLogError("The node structure is NULL\n");
return false;
}
for (uint32_t i = 0; i < CLS_BINARY_IMAGE_RUNTIME_NODE_COUNT; ++i) {
FIRCLSBinaryImageRuntimeNode* node = &nodes[i];
if (!FIRCLSIsValidPointer(node)) {
FIRCLSSDKLog(
"Invalid node pointer encountered in context's writable binary image at index %i", i);
continue;
}
if ((address >= (uintptr_t)node->baseAddress) &&
(address < (uintptr_t)node->baseAddress + node->size)) {
*image = *node; // copy the image
return true;
}
}
return false;
}
bool FIRCLSBinaryImageSafeHasUnwindInfo(FIRCLSBinaryImageRuntimeNode* image) {
return FIRCLSIsValidPointer(image->unwindInfo);
}
#endif
bool FIRCLSBinaryImageFindImageForUUID(const char* uuidString,
FIRCLSBinaryImageDetails* imageDetails) {
if (!imageDetails || !uuidString) {
FIRCLSSDKLog("null input\n");
return false;
}
uint32_t imageCount = _dyld_image_count();
for (uint32_t i = 0; i < imageCount; ++i) {
const struct mach_header* mh = _dyld_get_image_header(i);
FIRCLSBinaryImageDetails image;
image.slice = FIRCLSMachOSliceWithHeader((void*)mh);
FIRCLSBinaryImageFillInImageDetails(&image);
if (strncmp(uuidString, image.uuidString, FIRCLSUUIDStringLength) == 0) {
*imageDetails = image;
return true;
}
}
return false;
}
#pragma mark - DYLD callback handlers
static void FIRCLSBinaryImageAddedCallback(const struct mach_header* mh, intptr_t vmaddr_slide) {
FIRCLSBinaryImageChanged(true, mh, vmaddr_slide);
}
static void FIRCLSBinaryImageRemovedCallback(const struct mach_header* mh, intptr_t vmaddr_slide) {
FIRCLSBinaryImageChanged(false, mh, vmaddr_slide);
}
#if CLS_BINARY_IMAGE_RUNTIME_NODE_RECORD_NAME
static bool FIRCLSBinaryImagePopulateRuntimeNodeName(FIRCLSBinaryImageDetails* details) {
if (!FIRCLSIsValidPointer(details)) {
return false;
}
memset(details->node.name, 0, CLS_BINARY_IMAGE_RUNTIME_NODE_NAME_SIZE);
// We have limited storage space for the name. And, we really want to store
// "CoreFoundation", not "/System/Library/Fram", so we have to play tricks
// to make sure we get the right side of the string.
const char* imageName = FIRCLSMachOSliceGetExecutablePath(&details->slice);
if (!imageName) {
return false;
}
const size_t imageNameLength = strlen(imageName);
// Remember to leave one character for null-termination.
if (imageNameLength > CLS_BINARY_IMAGE_RUNTIME_NODE_NAME_SIZE - 1) {
imageName = imageName + (imageNameLength - (CLS_BINARY_IMAGE_RUNTIME_NODE_NAME_SIZE - 1));
}
// subtract one to make sure the string is always null-terminated
strncpy(details->node.name, imageName, CLS_BINARY_IMAGE_RUNTIME_NODE_NAME_SIZE - 1);
return true;
}
#endif
// There were plans later to replace this with FIRCLSMachO
static FIRCLSMachOSegmentCommand FIRCLSBinaryImageMachOGetSegmentCommand(
const struct load_command* cmd) {
FIRCLSMachOSegmentCommand segmentCommand;
memset(&segmentCommand, 0, sizeof(FIRCLSMachOSegmentCommand));
if (!cmd) {
return segmentCommand;
}
if (cmd->cmd == LC_SEGMENT) {
struct segment_command* segCmd = (struct segment_command*)cmd;
memcpy(segmentCommand.segname, segCmd->segname, 16);
segmentCommand.vmaddr = segCmd->vmaddr;
segmentCommand.vmsize = segCmd->vmsize;
} else if (cmd->cmd == LC_SEGMENT_64) {
struct segment_command_64* segCmd = (struct segment_command_64*)cmd;
memcpy(segmentCommand.segname, segCmd->segname, 16);
segmentCommand.vmaddr = segCmd->vmaddr;
segmentCommand.vmsize = segCmd->vmsize;
}
return segmentCommand;
}
#if !CLS_TARGET_OS_VISION
static bool FIRCLSBinaryImageMachOSliceInitSectionByName(FIRCLSMachOSliceRef slice,
const char* segName,
const char* sectionName,
FIRCLSMachOSection* section) {
if (!FIRCLSIsValidPointer(slice)) {
return false;
}
if (!section) {
return false;
}
memset(section, 0, sizeof(FIRCLSMachOSection));
if (FIRCLSMachOSliceIs64Bit(slice)) {
const struct section_64* sect =
getsectbynamefromheader_64(slice->startAddress, segName, sectionName);
if (!sect) {
return false;
}
section->addr = sect->addr;
section->size = sect->size;
section->offset = sect->offset;
} else {
const struct section* sect = getsectbynamefromheader(slice->startAddress, segName, sectionName);
if (!sect) {
return false;
}
section->addr = sect->addr;
section->size = sect->size;
section->offset = sect->offset;
}
return true;
}
#endif
static void FIRCLSPopulateImageDetailWithLoadCommand(uint32_t type,
uint32_t size,
const struct load_command* cmd,
void* context) {
FIRCLSBinaryImageDetails* details = context;
switch (type) {
case LC_UUID: {
const uint8_t* uuid = FIRCLSMachOGetUUID(cmd);
FIRCLSSafeHexToString(uuid, 16, details->uuidString);
} break;
case LC_ENCRYPTION_INFO:
details->encrypted = FIRCLSMachOGetEncrypted(cmd);
break;
case LC_SEGMENT:
case LC_SEGMENT_64: {
FIRCLSMachOSegmentCommand segmentCommand = FIRCLSBinaryImageMachOGetSegmentCommand(cmd);
if (strncmp(segmentCommand.segname, SEG_TEXT, sizeof(SEG_TEXT)) == 0) {
details->node.size = segmentCommand.vmsize;
}
} break;
case LC_VERSION_MIN_MACOSX:
case LC_VERSION_MIN_IPHONEOS:
case LC_VERSION_MIN_TVOS:
case LC_VERSION_MIN_WATCHOS:
details->minSDK = FIRCLSMachOGetMinimumOSVersion(cmd);
details->builtSDK = FIRCLSMachOGetLinkedSDKVersion(cmd);
break;
}
}
static bool FIRCLSBinaryImageFillInImageDetails(FIRCLSBinaryImageDetails* details) {
if (!FIRCLSIsValidPointer(details)) {
return false;
}
if (!FIRCLSIsValidPointer(details->slice.startAddress)) {
return false;
}
#if CLS_BINARY_IMAGE_RUNTIME_NODE_RECORD_NAME
// this is done for debugging purposes, so if it fails, its ok to continue
FIRCLSBinaryImagePopulateRuntimeNodeName(details);
#endif
// This cast might look a little dubious, but its just because we're using the same
// struct types in a few different places.
details->node.baseAddress = (void* volatile)details->slice.startAddress;
FIRCLSMachOSliceEnumerateLoadCommands_f(&details->slice, details,
FIRCLSPopulateImageDetailWithLoadCommand);
// We look up the section we want, and we *should* be able to use:
//
// address of data we want = start address + section.offset
//
// However, the offset value is coming back funky in iOS 9. So, instead we look up the address
// the section should be loaded at, and compute the offset by looking up the address of the
// segment itself.
FIRCLSMachOSection section;
#if CLS_COMPACT_UNWINDING_SUPPORTED
#if !CLS_TARGET_OS_VISION
if (FIRCLSBinaryImageMachOSliceInitSectionByName(&details->slice, SEG_TEXT, "__unwind_info",
&section)) {
details->node.unwindInfo = (void*)(section.addr + details->vmaddr_slide);
}
#else
unsigned long unwindInfoSize;
details->node.unwindInfo = (void*)getsectiondata(details->slice.startAddress, "__TEXT",
"__unwind_info", &unwindInfoSize);
#endif
#endif
#if CLS_DWARF_UNWINDING_SUPPORTED
#if !CLS_TARGET_OS_VISION
if (FIRCLSBinaryImageMachOSliceInitSectionByName(&details->slice, SEG_TEXT, "__eh_frame",
&section)) {
details->node.ehFrame = (void*)(section.addr + details->vmaddr_slide);
}
#else
unsigned long ehFrameSize;
details->node.ehFrame =
(void*)getsectiondata(details->slice.startAddress, "__TEXT", "__eh_frame", &ehFrameSize);
#endif
#endif
#if !CLS_TARGET_OS_VISION
if (FIRCLSBinaryImageMachOSliceInitSectionByName(&details->slice, SEG_DATA, "__crash_info",
&section)) {
details->node.crashInfo = (void*)(section.addr + details->vmaddr_slide);
}
#else
unsigned long crashInfoSize;
details->node.crashInfo =
(void*)getsectiondata(details->slice.startAddress, "__TEXT", "__crash_info", &crashInfoSize);
#endif
return true;
}
typedef struct {
FIRCLSBinaryImageDetails details;
bool added;
} FIRCLSImageChange;
static void FIRCLSProcessBinaryImageChange(void* context) {
FIRCLSImageChange* imageChange = context;
// this is an atomic operation
FIRCLSBinaryImageStoreNode(imageChange->added, imageChange->details);
FIRCLSBinaryImageRecordSlice(imageChange->added, imageChange->details);
free(context);
}
static void FIRCLSBinaryImageChanged(bool added,
const struct mach_header* mh,
intptr_t vmaddr_slide) {
// FIRCLSSDKLog("Binary image %s %p\n", added ? "loaded" : "unloaded", mh);
FIRCLSBinaryImageDetails imageDetails;
memset(&imageDetails, 0, sizeof(FIRCLSBinaryImageDetails));
imageDetails.slice = FIRCLSMachOSliceWithHeader((void*)mh);
imageDetails.vmaddr_slide = vmaddr_slide;
// fill imageDetails fields using slice & vmaddr_slide
FIRCLSBinaryImageFillInImageDetails(&imageDetails);
FIRCLSImageChange* change = malloc(sizeof(FIRCLSImageChange));
if (!change) return;
change->added = added;
change->details = imageDetails;
dispatch_async_f(FIRCLSGetBinaryImageQueue(), change, FIRCLSProcessBinaryImageChange);
}
#pragma mark - In-Memory Storage
static void FIRCLSBinaryImageStoreNode(bool added, FIRCLSBinaryImageDetails imageDetails) {
// This function is mutating a structure that needs to be accessed at crash time. We
// need to make sure the structure is always in as valid a state as possible.
// FIRCLSSDKLog("Storing %s node %p\n", added ? "loaded" : "unloaded",
// (void*)imageDetails.node.baseAddress);
if (!_firclsContext.writable) {
FIRCLSSDKLog("Error: Writable context is NULL\n");
return;
}
// looking for an empty space if an image added
void* searchAddress = NULL;
bool success = false;
FIRCLSBinaryImageRuntimeNode* nodes = _firclsContext.writable->binaryImage.nodes;
if (!added) {
// capture the search address first
searchAddress = imageDetails.node.baseAddress;
// If we are removing a node, we need to set its entries to zero. By clearing all of
// these values, we can just copy in imageDetails.node. Using memset here is slightly
// weird, since we have to restore one field. But, this way, if/when the structure changes,
// we still do the right thing.
memset(&imageDetails.node, 0, sizeof(FIRCLSBinaryImageRuntimeNode));
// restore the baseAddress, which just got zeroed, and is used for indexing
imageDetails.node.baseAddress = searchAddress;
}
for (uint32_t i = 0; i < CLS_BINARY_IMAGE_RUNTIME_NODE_COUNT; ++i) {
FIRCLSBinaryImageRuntimeNode* node = &nodes[i];
if (!node) {
FIRCLSSDKLog("Error: Binary image storage is NULL\n");
break;
}
// navigate through the array, looking for our matching address
if (node->baseAddress != searchAddress) {
continue;
}
// Attempt to swap the base address with whatever we are searching for. Success means that
// entry has been claims/cleared. Failure means some other thread beat us to it.
if (atomic_compare_exchange_strong(&node->baseAddress, &searchAddress,
imageDetails.node.baseAddress)) {
*node = imageDetails.node;
success = true;
break;
}
// If this is an unload, getting here means two threads unloaded at the same time. I think
// that's highly unlikely, and possibly even impossible. So, I'm choosing to abort the process
// at this point.
if (!added) {
FIRCLSSDKLog("Error: Failed to swap during image unload\n");
break;
}
}
if (!success) {
FIRCLSSDKLog("Error: Unable to track a %s node %p\n", added ? "loaded" : "unloaded",
(void*)imageDetails.node.baseAddress);
}
}
#pragma mark - On-Disk Storage
static void FIRCLSBinaryImageRecordDetails(FIRCLSFile* file,
const FIRCLSBinaryImageDetails imageDetails) {
if (!file) {
FIRCLSSDKLog("Error: file is invalid\n");
return;
}
FIRCLSFileWriteHashEntryString(file, "uuid", imageDetails.uuidString);
FIRCLSFileWriteHashEntryUint64(file, "base", (uintptr_t)imageDetails.slice.startAddress);
FIRCLSFileWriteHashEntryUint64(file, "size", imageDetails.node.size);
}
static void FIRCLSBinaryImageRecordLibraryFrameworkInfo(FIRCLSFile* file, const char* path) {
if (!file) {
FIRCLSSDKLog("Error: file is invalid\n");
return;
}
if (!path) {
return;
}
// Because this function is so expensive, we've decided to omit this info for all Apple-supplied
// frameworks. This really isn't that bad, because we can know their info ahead of time (within a
// small margin of error). With this implementation, we will still record this info for any
// user-built framework, which in the end is the most important thing.
if (strncmp(path, "/System", 7) == 0) {
return;
}
// check to see if this is a potential framework bundle
if (!strstr(path, ".framework")) {
return;
}
// My.framework/Versions/A/My for OS X
// My.framework/My for iOS
NSString* frameworkPath = [NSString stringWithUTF8String:path];
#if TARGET_OS_IPHONE
frameworkPath = [frameworkPath stringByDeletingLastPathComponent];
#else
frameworkPath = [frameworkPath stringByDeletingLastPathComponent]; // My.framework/Versions/A
frameworkPath = [frameworkPath stringByDeletingLastPathComponent]; // My.framework/Versions
frameworkPath = [frameworkPath stringByDeletingLastPathComponent]; // My.framework
#endif
NSBundle* const bundle = [NSBundle bundleWithPath:frameworkPath];
if (!bundle) {
return;
}
FIRCLSFileWriteHashEntryNSStringUnlessNilOrEmpty(file, "bundle_id", [bundle bundleIdentifier]);
FIRCLSFileWriteHashEntryNSStringUnlessNilOrEmpty(
file, "build_version", [bundle objectForInfoDictionaryKey:@"CFBundleVersion"]);
FIRCLSFileWriteHashEntryNSStringUnlessNilOrEmpty(
file, "display_version", [bundle objectForInfoDictionaryKey:@"CFBundleShortVersionString"]);
}
static void FIRCLSBinaryImageRecordSlice(bool added, const FIRCLSBinaryImageDetails imageDetails) {
bool needsClosing = false;
if (!FIRCLSBinaryImageOpenIfNeeded(&needsClosing)) {
FIRCLSSDKLog("Error: unable to open binary image log file\n");
return;
}
FIRCLSFile* file = &_firclsContext.writable->binaryImage.file;
FIRCLSFileWriteSectionStart(file, added ? "load" : "unload");
FIRCLSFileWriteHashStart(file);
const char* path = FIRCLSMachOSliceGetExecutablePath((FIRCLSMachOSliceRef)&imageDetails.slice);
FIRCLSFileWriteHashEntryString(file, "path", path);
if (added) {
// this won't work if the binary has been unloaded
FIRCLSBinaryImageRecordLibraryFrameworkInfo(file, path);
}
FIRCLSBinaryImageRecordDetails(file, imageDetails);
FIRCLSFileWriteHashEnd(file);
FIRCLSFileWriteSectionEnd(file);
if (needsClosing) {
FIRCLSFileClose(file);
}
}
bool FIRCLSBinaryImageRecordMainExecutable(FIRCLSFile* file) {
FIRCLSBinaryImageDetails imageDetails;
memset(&imageDetails, 0, sizeof(FIRCLSBinaryImageDetails));
imageDetails.slice = FIRCLSMachOSliceGetCurrent();
FIRCLSBinaryImageFillInImageDetails(&imageDetails);
FIRCLSFileWriteSectionStart(file, "executable");
FIRCLSFileWriteHashStart(file);
FIRCLSFileWriteHashEntryString(file, "architecture",
FIRCLSMachOSliceGetArchitectureName(&imageDetails.slice));
FIRCLSBinaryImageRecordDetails(file, imageDetails);
FIRCLSFileWriteHashEntryBoolean(file, "encrypted", imageDetails.encrypted);
FIRCLSFileWriteHashEntryString(file, "minimum_sdk_version",
[FIRCLSMachOFormatVersion(&imageDetails.minSDK) UTF8String]);
FIRCLSFileWriteHashEntryString(file, "built_sdk_version",
[FIRCLSMachOFormatVersion(&imageDetails.builtSDK) UTF8String]);
FIRCLSFileWriteHashEnd(file);
FIRCLSFileWriteSectionEnd(file);
return true;
}

View File

@ -0,0 +1,101 @@
// Copyright 2019 Google
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#pragma once
#include "Crashlytics/Crashlytics/Components/FIRCLSBinaryImage.h"
#include "Crashlytics/Crashlytics/Components/FIRCLSHost.h"
#include "Crashlytics/Crashlytics/Components/FIRCLSUserLogging.h"
#include "Crashlytics/Crashlytics/Handlers/FIRCLSException.h"
#include "Crashlytics/Crashlytics/Handlers/FIRCLSMachException.h"
#include "Crashlytics/Crashlytics/Handlers/FIRCLSSignal.h"
#include "Crashlytics/Crashlytics/Helpers/FIRCLSAllocate.h"
#include "Crashlytics/Crashlytics/Helpers/FIRCLSFeatures.h"
#include "Crashlytics/Crashlytics/Helpers/FIRCLSInternalLogging.h"
#include <dispatch/dispatch.h>
#include <stdbool.h>
// The purpose of the crash context is to hold values that absolutely must be read and/or written at
// crash time. For robustness against memory corruption, they are protected with guard pages.
// Further, the context is separated into read-only and read-write sections.
__BEGIN_DECLS
#ifdef __OBJC__
@class FIRCLSInternalReport;
@class FIRCLSSettings;
@class FIRCLSInstallIdentifierModel;
@class FIRCLSFileManager;
@class FIRCLSContextInitData;
#endif
typedef struct {
volatile bool initialized;
volatile bool debuggerAttached;
const char* previouslyCrashedFileFullPath;
const char* logPath;
// Initial report path represents the report path used to initialized the context;
// where non-on-demand exceptions and other crashes will be written.
const char* initialReportPath;
#if CLS_USE_SIGALTSTACK
void* signalStack;
#endif
#if CLS_MACH_EXCEPTION_SUPPORTED
void* machStack;
#endif
FIRCLSBinaryImageReadOnlyContext binaryimage;
FIRCLSExceptionReadOnlyContext exception;
FIRCLSHostReadOnlyContext host;
#if CLS_SIGNAL_SUPPORTED
FIRCLSSignalReadContext signal;
#endif
#if CLS_MACH_EXCEPTION_SUPPORTED
FIRCLSMachExceptionReadContext machException;
#endif
FIRCLSUserLoggingReadOnlyContext logging;
} FIRCLSReadOnlyContext;
typedef struct {
FIRCLSInternalLoggingWritableContext internalLogging;
volatile bool crashOccurred;
FIRCLSBinaryImageReadWriteContext binaryImage;
FIRCLSUserLoggingWritableContext logging;
FIRCLSExceptionWritableContext exception;
} FIRCLSReadWriteContext;
typedef struct {
FIRCLSReadOnlyContext* readonly;
FIRCLSReadWriteContext* writable;
FIRCLSAllocatorRef allocator;
} FIRCLSContext;
#ifdef __OBJC__
bool FIRCLSContextInitialize(FIRCLSContextInitData* initData, FIRCLSFileManager* fileManager);
FIRCLSContextInitData* FIRCLSContextBuildInitData(FIRCLSInternalReport* report,
FIRCLSSettings* settings,
FIRCLSFileManager* fileManager,
NSString* appQualitySessionId);
bool FIRCLSContextRecordMetadata(NSString* rootPath, FIRCLSContextInitData* initData);
#endif
void FIRCLSContextBaseInit(void);
void FIRCLSContextBaseDeinit(void);
bool FIRCLSContextIsInitialized(void);
bool FIRCLSContextHasCrashed(void);
void FIRCLSContextMarkHasCrashed(void);
bool FIRCLSContextMarkAndCheckIfCrashed(void);
__END_DECLS

View File

@ -0,0 +1,438 @@
// Copyright 2019 Google
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "Crashlytics/Crashlytics/Components/FIRCLSContext.h"
#include <stdlib.h>
#include <string.h>
#import "Crashlytics/Shared/FIRCLSConstants.h"
#import "Crashlytics/Crashlytics/Models/FIRCLSFileManager.h"
#import "Crashlytics/Crashlytics/Models/FIRCLSInstallIdentifierModel.h"
#import "Crashlytics/Crashlytics/Models/FIRCLSInternalReport.h"
#import "Crashlytics/Crashlytics/Models/FIRCLSSettings.h"
#include "Crashlytics/Crashlytics/Components/FIRCLSApplication.h"
#include "Crashlytics/Crashlytics/Components/FIRCLSCrashedMarkerFile.h"
#include "Crashlytics/Crashlytics/Components/FIRCLSGlobals.h"
#include "Crashlytics/Crashlytics/Components/FIRCLSProcess.h"
#include "Crashlytics/Crashlytics/Helpers/FIRCLSContextInitData.h"
#include "Crashlytics/Crashlytics/Helpers/FIRCLSDefines.h"
#include "Crashlytics/Crashlytics/Helpers/FIRCLSFeatures.h"
#include "Crashlytics/Crashlytics/Helpers/FIRCLSUtility.h"
// The writable size is our handler stack plus whatever scratch we need. We have to use this space
// extremely carefully, however, because thread stacks always needs to be page-aligned. Only the
// first allocation is guaranteed to be page-aligned.
//
// CLS_SIGNAL_HANDLER_STACK_SIZE and CLS_MACH_EXCEPTION_HANDLER_STACK_SIZE are platform dependant,
// defined as 0 for tv/watch.
#define CLS_MINIMUM_READWRITE_SIZE \
(CLS_SIGNAL_HANDLER_STACK_SIZE + CLS_MACH_EXCEPTION_HANDLER_STACK_SIZE + \
sizeof(FIRCLSReadWriteContext))
// We need enough space here for the context, plus storage for strings.
#define CLS_MINIMUM_READABLE_SIZE (sizeof(FIRCLSReadOnlyContext) + 4096 * 4)
static const int64_t FIRCLSContextInitWaitTime = 5LL * NSEC_PER_SEC;
static const char* FIRCLSContextAppendToRoot(NSString* root, NSString* component);
static void FIRCLSContextAllocate(FIRCLSContext* context);
FIRCLSContextInitData* FIRCLSContextBuildInitData(FIRCLSInternalReport* report,
FIRCLSSettings* settings,
FIRCLSFileManager* fileManager,
NSString* appQualitySessionId) {
// Because we need to start the crash reporter right away,
// it starts up either with default settings, or cached settings
// from the last time they were fetched
FIRCLSContextInitData* initData = [[FIRCLSContextInitData alloc] init];
initData.customBundleId = nil;
initData.sessionId = [report identifier];
initData.appQualitySessionId = appQualitySessionId;
initData.rootPath = [report path];
initData.previouslyCrashedFileRootPath = [fileManager rootPath];
initData.errorsEnabled = [settings errorReportingEnabled];
initData.customExceptionsEnabled = [settings customExceptionsEnabled];
initData.maxCustomExceptions = [settings maxCustomExceptions];
initData.maxErrorLogSize = [settings errorLogBufferSize];
initData.maxLogSize = [settings logBufferSize];
initData.maxKeyValues = [settings maxCustomKeys];
initData.betaToken = @"";
return initData;
}
bool FIRCLSContextInitialize(FIRCLSContextInitData* initData, FIRCLSFileManager* fileManager) {
if (!initData) {
return false;
}
FIRCLSContextBaseInit();
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
if (!FIRCLSIsValidPointer(initData.rootPath)) {
return false;
}
NSString* rootPath = initData.rootPath;
// setup our SDK log file synchronously, because other calls may depend on it
_firclsContext.readonly->logPath = FIRCLSContextAppendToRoot(rootPath, @"sdk.log");
_firclsContext.readonly->initialReportPath = FIRCLSDupString([[initData rootPath] UTF8String]);
if (!FIRCLSUnlinkIfExists(_firclsContext.readonly->logPath)) {
FIRCLSErrorLog(@"Unable to write initialize SDK write paths %s", strerror(errno));
}
// some values that aren't tied to particular subsystem
_firclsContext.readonly->debuggerAttached = FIRCLSProcessDebuggerAttached();
dispatch_group_async(group, queue, ^{
FIRCLSHostInitialize(&_firclsContext.readonly->host);
});
dispatch_group_async(group, queue, ^{
_firclsContext.readonly->logging.errorStorage.maxSize = 0;
_firclsContext.readonly->logging.errorStorage.maxEntries =
initData.errorsEnabled ? initData.maxCustomExceptions : 0;
_firclsContext.readonly->logging.errorStorage.restrictBySize = false;
_firclsContext.readonly->logging.errorStorage.entryCount =
&_firclsContext.writable->logging.errorsCount;
_firclsContext.readonly->logging.errorStorage.aPath =
FIRCLSContextAppendToRoot(rootPath, FIRCLSReportErrorAFile);
_firclsContext.readonly->logging.errorStorage.bPath =
FIRCLSContextAppendToRoot(rootPath, FIRCLSReportErrorBFile);
_firclsContext.readonly->logging.logStorage.maxSize = initData.maxLogSize;
_firclsContext.readonly->logging.logStorage.maxEntries = 0;
_firclsContext.readonly->logging.logStorage.restrictBySize = true;
_firclsContext.readonly->logging.logStorage.entryCount = NULL;
_firclsContext.readonly->logging.logStorage.aPath =
FIRCLSContextAppendToRoot(rootPath, FIRCLSReportLogAFile);
_firclsContext.readonly->logging.logStorage.bPath =
FIRCLSContextAppendToRoot(rootPath, FIRCLSReportLogBFile);
_firclsContext.readonly->logging.customExceptionStorage.aPath =
FIRCLSContextAppendToRoot(rootPath, FIRCLSReportCustomExceptionAFile);
_firclsContext.readonly->logging.customExceptionStorage.bPath =
FIRCLSContextAppendToRoot(rootPath, FIRCLSReportCustomExceptionBFile);
_firclsContext.readonly->logging.customExceptionStorage.maxSize = 0;
_firclsContext.readonly->logging.customExceptionStorage.restrictBySize = false;
_firclsContext.readonly->logging.customExceptionStorage.maxEntries =
initData.maxCustomExceptions;
_firclsContext.readonly->logging.customExceptionStorage.entryCount =
&_firclsContext.writable->exception.customExceptionCount;
_firclsContext.readonly->logging.userKVStorage.maxCount = initData.maxKeyValues;
_firclsContext.readonly->logging.userKVStorage.incrementalPath =
FIRCLSContextAppendToRoot(rootPath, FIRCLSReportUserIncrementalKVFile);
_firclsContext.readonly->logging.userKVStorage.compactedPath =
FIRCLSContextAppendToRoot(rootPath, FIRCLSReportUserCompactedKVFile);
_firclsContext.readonly->logging.internalKVStorage.maxCount = 32; // Hardcode = bad
_firclsContext.readonly->logging.internalKVStorage.incrementalPath =
FIRCLSContextAppendToRoot(rootPath, FIRCLSReportInternalIncrementalKVFile);
_firclsContext.readonly->logging.internalKVStorage.compactedPath =
FIRCLSContextAppendToRoot(rootPath, FIRCLSReportInternalCompactedKVFile);
FIRCLSUserLoggingInit(&_firclsContext.readonly->logging, &_firclsContext.writable->logging);
});
dispatch_group_async(group, queue, ^{
_firclsContext.readonly->binaryimage.path =
FIRCLSContextAppendToRoot(rootPath, FIRCLSReportBinaryImageFile);
FIRCLSBinaryImageInit();
});
dispatch_group_async(group, queue, ^{
NSString* rootPath = initData.previouslyCrashedFileRootPath;
NSString* fileName = [NSString stringWithUTF8String:FIRCLSCrashedMarkerFileName];
_firclsContext.readonly->previouslyCrashedFileFullPath =
FIRCLSContextAppendToRoot(rootPath, fileName);
});
// To initialize Crashlytics handlers even if the Xcode debugger is attached, replace this check
// with YES. Note that this is only possible to do on an actual device as it will cause the
// simulator to crash.
if (!_firclsContext.readonly->debuggerAttached) {
#if CLS_SIGNAL_SUPPORTED
dispatch_group_async(group, queue, ^{
_firclsContext.readonly->signal.path =
FIRCLSContextAppendToRoot(rootPath, FIRCLSReportSignalFile);
FIRCLSSignalInitialize(&_firclsContext.readonly->signal);
});
#endif
#if CLS_MACH_EXCEPTION_SUPPORTED
dispatch_group_async(group, queue, ^{
_firclsContext.readonly->machException.path =
FIRCLSContextAppendToRoot(rootPath, FIRCLSReportMachExceptionFile);
FIRCLSMachExceptionInit(&_firclsContext.readonly->machException);
});
#endif
dispatch_group_async(group, queue, ^{
_firclsContext.readonly->exception.path =
FIRCLSContextAppendToRoot(rootPath, FIRCLSReportExceptionFile);
_firclsContext.readonly->exception.maxCustomExceptions =
initData.customExceptionsEnabled ? initData.maxCustomExceptions : 0;
FIRCLSExceptionInitialize(&_firclsContext.readonly->exception,
&_firclsContext.writable->exception);
});
} else {
FIRCLSSDKLog("Debugger present - not installing handlers\n");
}
dispatch_group_async(group, queue, ^{
if (!FIRCLSContextRecordMetadata(rootPath, initData)) {
FIRCLSSDKLog("Unable to record context metadata\n");
}
});
// At this point we need to do two things. First, we need to do our memory protection *only* after
// all of these initialization steps are really done. But, we also want to wait as long as
// possible for these to be complete. If we do not, there's a chance that we will not be able to
// correctly report a crash shortly after start.
// Note at this will retain the group, so its totally fine to release the group here.
dispatch_group_notify(group, queue, ^{
_firclsContext.readonly->initialized = true;
__sync_synchronize();
if (!FIRCLSAllocatorProtect(_firclsContext.allocator)) {
FIRCLSSDKLog("Error: Memory protection failed\n");
}
});
if (dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, FIRCLSContextInitWaitTime)) !=
0) {
FIRCLSSDKLog("Error: Delayed initialization\n");
}
return true;
}
void FIRCLSContextBaseInit(void) {
NSString* sdkBundleID = FIRCLSApplicationGetSDKBundleID();
NSString* loggingQueueName = [sdkBundleID stringByAppendingString:@".logging"];
NSString* binaryImagesQueueName = [sdkBundleID stringByAppendingString:@".binary-images"];
NSString* exceptionQueueName = [sdkBundleID stringByAppendingString:@".exception"];
_firclsLoggingQueue = dispatch_queue_create([loggingQueueName UTF8String], DISPATCH_QUEUE_SERIAL);
_firclsBinaryImageQueue =
dispatch_queue_create([binaryImagesQueueName UTF8String], DISPATCH_QUEUE_SERIAL);
_firclsExceptionQueue =
dispatch_queue_create([exceptionQueueName UTF8String], DISPATCH_QUEUE_SERIAL);
FIRCLSContextAllocate(&_firclsContext);
_firclsContext.writable->internalLogging.logFd = -1;
_firclsContext.writable->internalLogging.logLevel = FIRCLSInternalLogLevelDebug;
_firclsContext.writable->crashOccurred = false;
_firclsContext.readonly->initialized = false;
__sync_synchronize();
}
static void FIRCLSContextAllocate(FIRCLSContext* context) {
// create the allocator, and the contexts
// The ordering here is really important, because the "stack" variable must be
// page-aligned. There's no mechanism to ask the allocator to do alignment, but we
// do know the very first allocation in a region is aligned to a page boundary.
context->allocator = FIRCLSAllocatorCreate(CLS_MINIMUM_READWRITE_SIZE, CLS_MINIMUM_READABLE_SIZE);
context->readonly =
FIRCLSAllocatorSafeAllocate(context->allocator, sizeof(FIRCLSReadOnlyContext), CLS_READONLY);
memset(context->readonly, 0, sizeof(FIRCLSReadOnlyContext));
#if CLS_MEMORY_PROTECTION_ENABLED
#if CLS_MACH_EXCEPTION_SUPPORTED
context->readonly->machStack = FIRCLSAllocatorSafeAllocate(
context->allocator, CLS_MACH_EXCEPTION_HANDLER_STACK_SIZE, CLS_READWRITE);
#endif
#if CLS_USE_SIGALTSTACK
context->readonly->signalStack =
FIRCLSAllocatorSafeAllocate(context->allocator, CLS_SIGNAL_HANDLER_STACK_SIZE, CLS_READWRITE);
#endif
#else
#if CLS_MACH_EXCEPTION_SUPPORTED
context->readonly->machStack = valloc(CLS_MACH_EXCEPTION_HANDLER_STACK_SIZE);
#endif
#if CLS_USE_SIGALTSTACK
context->readonly->signalStack = valloc(CLS_SIGNAL_HANDLER_STACK_SIZE);
#endif
#endif
#if CLS_MACH_EXCEPTION_SUPPORTED
memset(_firclsContext.readonly->machStack, 0, CLS_MACH_EXCEPTION_HANDLER_STACK_SIZE);
#endif
#if CLS_USE_SIGALTSTACK
memset(_firclsContext.readonly->signalStack, 0, CLS_SIGNAL_HANDLER_STACK_SIZE);
#endif
context->writable = FIRCLSAllocatorSafeAllocate(context->allocator,
sizeof(FIRCLSReadWriteContext), CLS_READWRITE);
memset(context->writable, 0, sizeof(FIRCLSReadWriteContext));
}
void FIRCLSContextBaseDeinit(void) {
_firclsContext.readonly->initialized = false;
FIRCLSAllocatorDestroy(_firclsContext.allocator);
}
bool FIRCLSContextIsInitialized(void) {
__sync_synchronize();
if (!FIRCLSIsValidPointer(_firclsContext.readonly)) {
return false;
}
return _firclsContext.readonly->initialized;
}
bool FIRCLSContextHasCrashed(void) {
if (!FIRCLSContextIsInitialized()) {
return false;
}
// we've already run a full barrier above, so this read is ok
return _firclsContext.writable->crashOccurred;
}
void FIRCLSContextMarkHasCrashed(void) {
if (!FIRCLSContextIsInitialized()) {
return;
}
_firclsContext.writable->crashOccurred = true;
__sync_synchronize();
}
bool FIRCLSContextMarkAndCheckIfCrashed(void) {
if (!FIRCLSContextIsInitialized()) {
return false;
}
if (_firclsContext.writable->crashOccurred) {
return true;
}
_firclsContext.writable->crashOccurred = true;
__sync_synchronize();
return false;
}
static const char* FIRCLSContextAppendToRoot(NSString* root, NSString* component) {
return FIRCLSDupString(
[[root stringByAppendingPathComponent:component] fileSystemRepresentation]);
}
static bool FIRCLSContextRecordIdentity(FIRCLSFile* file,
const char* sessionId,
const char* betaToken,
const char* appQualitySessionId) {
FIRCLSFileWriteSectionStart(file, "identity");
FIRCLSFileWriteHashStart(file);
FIRCLSFileWriteHashEntryString(file, "generator", FIRCLSSDKGeneratorName().UTF8String);
FIRCLSFileWriteHashEntryString(file, "display_version", FIRCLSSDKVersion().UTF8String);
FIRCLSFileWriteHashEntryString(file, "build_version", FIRCLSSDKVersion().UTF8String);
FIRCLSFileWriteHashEntryUint64(file, "started_at", time(NULL));
FIRCLSFileWriteHashEntryString(file, "session_id", sessionId);
FIRCLSFileWriteHashEntryString(file, "app_quality_session_id", appQualitySessionId);
// install_id is written into the proto directly. This is only left here to
// support Apple Report Converter.
FIRCLSFileWriteHashEntryString(file, "install_id", "");
FIRCLSFileWriteHashEntryString(file, "beta_token", betaToken);
FIRCLSFileWriteHashEntryBoolean(file, "absolute_log_timestamps", true);
FIRCLSFileWriteHashEnd(file);
FIRCLSFileWriteSectionEnd(file);
return true;
}
static bool FIRCLSContextRecordApplication(FIRCLSFile* file, const char* customBundleId) {
FIRCLSFileWriteSectionStart(file, "application");
FIRCLSFileWriteHashStart(file);
FIRCLSFileWriteHashEntryString(file, "bundle_id",
[FIRCLSApplicationGetBundleIdentifier() UTF8String]);
FIRCLSFileWriteHashEntryString(file, "custom_bundle_id", customBundleId);
FIRCLSFileWriteHashEntryString(file, "build_version",
[FIRCLSApplicationGetBundleVersion() UTF8String]);
FIRCLSFileWriteHashEntryString(file, "display_version",
[FIRCLSApplicationGetShortBundleVersion() UTF8String]);
FIRCLSFileWriteHashEntryString(file, "extension_id",
[FIRCLSApplicationExtensionPointIdentifier() UTF8String]);
FIRCLSFileWriteHashEnd(file);
FIRCLSFileWriteSectionEnd(file);
return true;
}
bool FIRCLSContextRecordMetadata(NSString* rootPath, const FIRCLSContextInitData* initData) {
const char* sessionId = [[initData sessionId] UTF8String];
const char* betaToken = [[initData betaToken] UTF8String];
const char* customBundleId = [[initData customBundleId] UTF8String];
const char* appQualitySessionId = [[initData appQualitySessionId] UTF8String];
const char* path =
[[rootPath stringByAppendingPathComponent:FIRCLSReportMetadataFile] fileSystemRepresentation];
if (!FIRCLSUnlinkIfExists(path)) {
FIRCLSSDKLog("Unable to unlink existing metadata file %s\n", strerror(errno));
}
FIRCLSFile file;
if (!FIRCLSFileInitWithPath(&file, path, false)) {
FIRCLSSDKLog("Unable to open metadata file %s\n", strerror(errno));
return false;
}
if (!FIRCLSContextRecordIdentity(&file, sessionId, betaToken, appQualitySessionId)) {
FIRCLSSDKLog("Unable to write out identity metadata\n");
}
if (!FIRCLSHostRecord(&file)) {
FIRCLSSDKLog("Unable to write out host metadata\n");
}
if (!FIRCLSContextRecordApplication(&file, customBundleId)) {
FIRCLSSDKLog("Unable to write out application metadata\n");
}
if (!FIRCLSBinaryImageRecordMainExecutable(&file)) {
FIRCLSSDKLog("Unable to write out executable metadata\n");
}
FIRCLSFileClose(&file);
return true;
}

View File

@ -0,0 +1,31 @@
// Copyright 2019 Google
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "Crashlytics/Crashlytics/Components/FIRCLSCrashedMarkerFile.h"
#include "Crashlytics/Crashlytics/Helpers/FIRCLSFile.h"
#include "Crashlytics/Crashlytics/Helpers/FIRCLSUtility.h"
const char *FIRCLSCrashedMarkerFileName = "previously-crashed";
void FIRCLSCreateCrashedMarkerFile(void) {
FIRCLSFile file;
if (!FIRCLSFileInitWithPath(&file, _firclsContext.readonly->previouslyCrashedFileFullPath, false)) {
FIRCLSSDKLog("Unable to create the crashed marker file\n");
return;
}
FIRCLSFileClose(&file);
FIRCLSSDKLog("Created the crashed marker file\n");
}

View File

@ -0,0 +1,19 @@
// Copyright 2019 Google
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <stdio.h>
extern const char *FIRCLSCrashedMarkerFileName;
void FIRCLSCreateCrashedMarkerFile(void);

View File

@ -0,0 +1,28 @@
// Copyright 2019 Google
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "Crashlytics/Crashlytics/Components/FIRCLSContext.h"
__BEGIN_DECLS
extern FIRCLSContext _firclsContext;
extern dispatch_queue_t _firclsLoggingQueue;
extern dispatch_queue_t _firclsBinaryImageQueue;
extern dispatch_queue_t _firclsExceptionQueue;
#define FIRCLSGetLoggingQueue() (_firclsLoggingQueue)
#define FIRCLSGetBinaryImageQueue() (_firclsBinaryImageQueue)
#define FIRCLSGetExceptionQueue() (_firclsExceptionQueue)
__END_DECLS

View File

@ -0,0 +1,39 @@
// Copyright 2019 Google
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#pragma once
#include <mach/vm_types.h>
#include <sys/cdefs.h>
#include "Crashlytics/Crashlytics/Helpers/FIRCLSFile.h"
typedef struct {
const char* documentDirectoryPath;
vm_size_t pageSize;
} FIRCLSHostReadOnlyContext;
__BEGIN_DECLS
void FIRCLSHostInitialize(FIRCLSHostReadOnlyContext* roContext);
vm_size_t FIRCLSHostGetPageSize(void);
bool FIRCLSHostRecord(FIRCLSFile* file);
void FIRCLSHostWriteDiskUsage(FIRCLSFile* file);
bool FIRCLSHostIsRosettaTranslated(void);
__END_DECLS

View File

@ -0,0 +1,195 @@
// Copyright 2019 Google
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "Crashlytics/Crashlytics/Components/FIRCLSHost.h"
#include <mach/mach.h>
#include <sys/sysctl.h>
#import "Crashlytics/Crashlytics/Components/FIRCLSApplication.h"
#include "Crashlytics/Crashlytics/Components/FIRCLSGlobals.h"
#include "Crashlytics/Crashlytics/Helpers/FIRCLSDefines.h"
#include "Crashlytics/Crashlytics/Helpers/FIRCLSFile.h"
#include "Crashlytics/Crashlytics/Helpers/FIRCLSUtility.h"
#import "Crashlytics/Shared/FIRCLSFABHost.h"
#if TARGET_OS_IPHONE
#import <UIKit/UIKit.h>
#else
#import <Cocoa/Cocoa.h>
#endif
#define CLS_HOST_SYSCTL_BUFFER_SIZE (128)
#define CLS_MAX_ARM64_NATIVE_PAGE_SIZE (1024 * 16)
#if CLS_CPU_ARM64
#define CLS_MAX_NATIVE_PAGE_SIZE CLS_MAX_ARM64_NATIVE_PAGE_SIZE
#else
// return 4K, which is correct for all platforms except arm64, currently
#define CLS_MAX_NATIVE_PAGE_SIZE (1024 * 4)
#endif
#define CLS_MIN_NATIVE_PAGE_SIZE (1024 * 4)
#pragma mark Prototypes
static void FIRCLSHostWriteSysctlEntry(
FIRCLSFile* file, const char* key, const char* sysctlKey, void* buffer, size_t bufferSize);
static void FIRCLSHostWriteModelInfo(FIRCLSFile* file);
static void FIRCLSHostWriteOSVersionInfo(FIRCLSFile* file);
#pragma mark - API
void FIRCLSHostInitialize(FIRCLSHostReadOnlyContext* roContext) {
_firclsContext.readonly->host.pageSize = FIRCLSHostGetPageSize();
_firclsContext.readonly->host.documentDirectoryPath = NULL;
// determine where the document directory is mounted, so we can get file system statistics later
NSArray* paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
if ([paths count]) {
_firclsContext.readonly->host.documentDirectoryPath =
FIRCLSDupString([[paths objectAtIndex:0] fileSystemRepresentation]);
}
}
vm_size_t FIRCLSHostGetPageSize(void) {
size_t size;
int pageSize;
// hw.pagesize is defined as HW_PAGESIZE, which is an int. It's important to match
// these types. Turns out that sysctl will not init the data to zero, but it appears
// that sysctlbyname does. This API is nicer, but that's important to keep in mind.
int maxNativePageSize = CLS_MAX_NATIVE_PAGE_SIZE;
// On Apple Silicon, we need to use the arm64 page size
// even if we're in x86 land.
if (FIRCLSHostIsRosettaTranslated()) {
FIRCLSSDKLog("Running under Rosetta 2 emulation. Using the arm64 page size.\n");
maxNativePageSize = CLS_MAX_ARM64_NATIVE_PAGE_SIZE;
}
pageSize = 0;
size = sizeof(pageSize);
if (sysctlbyname("hw.pagesize", &pageSize, &size, NULL, 0) != 0) {
FIRCLSSDKLog("sysctlbyname failed while trying to get hw.pagesize\n");
return maxNativePageSize;
}
// if the returned size is not the expected value, abort
if (size != sizeof(pageSize)) {
return maxNativePageSize;
}
// put in some guards to make sure our size is reasonable
if (pageSize > maxNativePageSize) {
return maxNativePageSize;
}
if (pageSize < CLS_MIN_NATIVE_PAGE_SIZE) {
return CLS_MIN_NATIVE_PAGE_SIZE;
}
return pageSize;
}
// This comes from the Apple documentation here:
// https://developer.apple.com/documentation/apple_silicon/about_the_rosetta_translation_environment
bool FIRCLSHostIsRosettaTranslated(void) {
#if TARGET_OS_MAC
int result = 0;
size_t size = sizeof(result);
if (sysctlbyname("sysctl.proc_translated", &result, &size, NULL, 0) == -1) {
// If we get an error, or 0, we're going to treat this as x86_64 macOS native
if (errno == ENOENT) {
return false;
}
// This is the error case
FIRCLSSDKLog("sysctlbyname failed while trying to get sysctl.proc_translated for Rosetta 2 "
"translation\n");
return false;
}
return result == 1;
#else
return false;
#endif
}
static void FIRCLSHostWriteSysctlEntry(
FIRCLSFile* file, const char* key, const char* sysctlKey, void* buffer, size_t bufferSize) {
if (sysctlbyname(sysctlKey, buffer, &bufferSize, NULL, 0) != 0) {
FIRCLSFileWriteHashEntryString(file, key, "(failed)");
return;
}
FIRCLSFileWriteHashEntryString(file, key, buffer);
}
static void FIRCLSHostWriteModelInfo(FIRCLSFile* file) {
FIRCLSFileWriteHashEntryString(file, "model", [FIRCLSHostModelInfo() UTF8String]);
// allocate a static buffer for the sysctl values, which are typically
// quite short
char buffer[CLS_HOST_SYSCTL_BUFFER_SIZE];
#if TARGET_OS_EMBEDDED
FIRCLSHostWriteSysctlEntry(file, "machine", "hw.model", buffer, CLS_HOST_SYSCTL_BUFFER_SIZE);
#else
FIRCLSHostWriteSysctlEntry(file, "machine", "hw.machine", buffer, CLS_HOST_SYSCTL_BUFFER_SIZE);
FIRCLSHostWriteSysctlEntry(file, "cpu", "machdep.cpu.brand_string", buffer,
CLS_HOST_SYSCTL_BUFFER_SIZE);
#endif
}
static void FIRCLSHostWriteOSVersionInfo(FIRCLSFile* file) {
FIRCLSFileWriteHashEntryString(file, "os_build_version", [FIRCLSHostOSBuildVersion() UTF8String]);
FIRCLSFileWriteHashEntryString(file, "os_display_version",
[FIRCLSHostOSDisplayVersion() UTF8String]);
FIRCLSFileWriteHashEntryString(file, "platform", [FIRCLSApplicationGetPlatform() UTF8String]);
FIRCLSFileWriteHashEntryString(file, "firebase_platform",
[FIRCLSApplicationGetFirebasePlatform() UTF8String]);
}
bool FIRCLSHostRecord(FIRCLSFile* file) {
FIRCLSFileWriteSectionStart(file, "host");
FIRCLSFileWriteHashStart(file);
FIRCLSHostWriteModelInfo(file);
FIRCLSHostWriteOSVersionInfo(file);
FIRCLSFileWriteHashEntryString(file, "locale",
[[[NSLocale currentLocale] localeIdentifier] UTF8String]);
FIRCLSFileWriteHashEnd(file);
FIRCLSFileWriteSectionEnd(file);
return true;
}
void FIRCLSHostWriteDiskUsage(FIRCLSFile* file) {
FIRCLSFileWriteSectionStart(file, "storage");
FIRCLSFileWriteHashStart(file);
// Due to Apple Privacy Manifests, Crashlytics is not collecting
// disk space using statfs. When we find a solution, we can update
// this to actually track disk space correctly.
FIRCLSFileWriteHashEntryUint64(file, "free", 0);
FIRCLSFileWriteHashEntryUint64(file, "total", 0);
FIRCLSFileWriteHashEnd(file);
FIRCLSFileWriteSectionEnd(file);
}

View File

@ -0,0 +1,840 @@
// Copyright 2019 Google
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "Crashlytics/Crashlytics/Components/FIRCLSProcess.h"
#include "Crashlytics/Crashlytics/Helpers/FIRCLSDefines.h"
#include "Crashlytics/Crashlytics/Helpers/FIRCLSFeatures.h"
#include "Crashlytics/Crashlytics/Components/FIRCLSGlobals.h"
#include "Crashlytics/Crashlytics/Helpers/FIRCLSThreadState.h"
#include "Crashlytics/Crashlytics/Unwind/FIRCLSUnwind.h"
#include "Crashlytics/Crashlytics/Helpers/FIRCLSUtility.h"
#include <dispatch/dispatch.h>
#include <objc/message.h>
#include <pthread.h>
#include <sys/sysctl.h>
#define THREAD_NAME_BUFFER_SIZE (64)
#pragma mark Prototypes
static bool FIRCLSProcessGetThreadName(FIRCLSProcess *process,
thread_t thread,
char *buffer,
size_t length);
static const char *FIRCLSProcessGetThreadDispatchQueueName(FIRCLSProcess *process, thread_t thread);
#pragma mark - API
bool FIRCLSProcessInit(FIRCLSProcess *process, thread_t crashedThread, void *uapVoid) {
if (!process) {
return false;
}
process->task = mach_task_self();
process->thisThread = mach_thread_self();
process->crashedThread = crashedThread;
process->uapVoid = uapVoid;
if (task_threads(process->task, &process->threads, &process->threadCount) != KERN_SUCCESS) {
// failed to get all threads
process->threadCount = 0;
FIRCLSSDKLog("Error: unable to get task threads\n");
return false;
}
return true;
}
// https://developer.apple.com/library/mac/#qa/qa2004/qa1361.html
bool FIRCLSProcessDebuggerAttached(void) {
int junk;
int mib[4];
struct kinfo_proc info;
size_t size;
// Initialize the flags so that, if sysctl fails for some bizarre
// reason, we get a predictable result.
info.kp_proc.p_flag = 0;
// Initialize mib, which tells sysctl the info we want, in this case
// we're looking for information about a specific process ID.
mib[0] = CTL_KERN;
mib[1] = KERN_PROC;
mib[2] = KERN_PROC_PID;
mib[3] = getpid();
// Call sysctl.
size = sizeof(info);
junk = sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, NULL, 0);
if (junk != 0) {
FIRCLSSDKLog("sysctl failed while trying to get kinfo_proc\n");
return false;
}
// We're being debugged if the P_TRACED flag is set.
return (info.kp_proc.p_flag & P_TRACED) != 0;
}
#pragma mark - Thread Support
static bool FIRCLSProcessIsCurrentThread(FIRCLSProcess *process, thread_t thread) {
return MACH_PORT_INDEX(process->thisThread) == MACH_PORT_INDEX(thread);
}
static bool FIRCLSProcessIsCrashedThread(FIRCLSProcess *process, thread_t thread) {
return MACH_PORT_INDEX(process->crashedThread) == MACH_PORT_INDEX(thread);
}
static uint32_t FIRCLSProcessGetThreadCount(FIRCLSProcess *process) {
return process->threadCount;
}
static thread_t FIRCLSProcessGetThread(FIRCLSProcess *process, uint32_t index) {
if (index >= process->threadCount) {
return MACH_PORT_NULL;
}
return process->threads[index];
}
bool FIRCLSProcessSuspendAllOtherThreads(FIRCLSProcess *process) {
mach_msg_type_number_t i;
bool success;
success = true;
for (i = 0; i < process->threadCount; ++i) {
thread_t thread;
thread = FIRCLSProcessGetThread(process, i);
if (FIRCLSProcessIsCurrentThread(process, thread)) {
continue;
}
// FIXME: workaround to get this building on watch, but we need to suspend/resume threads!
#if CLS_CAN_SUSPEND_THREADS
success = success && (thread_suspend(thread) == KERN_SUCCESS);
#endif
}
return success;
}
bool FIRCLSProcessResumeAllOtherThreads(FIRCLSProcess *process) {
mach_msg_type_number_t i;
bool success;
success = true;
for (i = 0; i < process->threadCount; ++i) {
thread_t thread;
thread = FIRCLSProcessGetThread(process, i);
if (FIRCLSProcessIsCurrentThread(process, thread)) {
continue;
}
// FIXME: workaround to get this building on watch, but we need to suspend/resume threads!
#if CLS_CAN_SUSPEND_THREADS
success = success && (thread_resume(thread) == KERN_SUCCESS);
#endif
}
return success;
}
#pragma mark - Thread Properties
void *FIRCLSThreadGetCurrentPC(void) {
return __builtin_return_address(0);
}
static bool FIRCLSProcessGetThreadState(FIRCLSProcess *process,
thread_t thread,
FIRCLSThreadContext *context) {
if (!FIRCLSIsValidPointer(context)) {
FIRCLSSDKLogError("Invalid context supplied\n");
return false;
}
// If the thread context we should use is non-NULL, then just assign it here. Otherwise,
// query the thread state
if (FIRCLSProcessIsCrashedThread(process, thread) && FIRCLSIsValidPointer(process->uapVoid)) {
*context = *((_STRUCT_UCONTEXT *)process->uapVoid)->uc_mcontext;
return true;
}
// Here's a wild trick: emulate what thread_get_state would do. It appears that
// we cannot reliably unwind out of thread_get_state. So, instead of trying, setup
// a thread context that resembles what the real thing would look like
if (FIRCLSProcessIsCurrentThread(process, thread)) {
FIRCLSSDKLog("Faking current thread\n");
memset(context, 0, sizeof(FIRCLSThreadContext));
// Compute the frame address, and then base the stack value off of that. A frame pushes
// two pointers onto the stack, so we have to offset.
const uintptr_t frameAddress = (uintptr_t)__builtin_frame_address(0);
const uintptr_t stackAddress = FIRCLSUnwindStackPointerFromFramePointer(frameAddress);
#if CLS_CPU_X86_64
context->__ss.__rip = (uintptr_t)FIRCLSThreadGetCurrentPC();
context->__ss.__rbp = frameAddress;
context->__ss.__rsp = stackAddress;
#elif CLS_CPU_I386
context->__ss.__eip = (uintptr_t)FIRCLSThreadGetCurrentPC();
context->__ss.__ebp = frameAddress;
context->__ss.__esp = stackAddress;
#elif CLS_CPU_ARM64
FIRCLSThreadContextSetPC(context, (uintptr_t)FIRCLSThreadGetCurrentPC());
FIRCLSThreadContextSetFramePointer(context, frameAddress);
FIRCLSThreadContextSetLinkRegister(context, (uintptr_t)__builtin_return_address(0));
FIRCLSThreadContextSetStackPointer(context, stackAddress);
#elif CLS_CPU_ARM
context->__ss.__pc = (uintptr_t)FIRCLSThreadGetCurrentPC();
context->__ss.__r[7] = frameAddress;
context->__ss.__lr = (uintptr_t)__builtin_return_address(0);
context->__ss.__sp = stackAddress;
#endif
return true;
}
#if !TARGET_OS_WATCH
// try to get the value by querying the thread state
mach_msg_type_number_t stateCount = FIRCLSThreadStateCount;
// For unknown reasons, thread_get_state returns this value on Rosetta,
// but still succeeds.
const int ROSETTA_SUCCESS = 268435459;
kern_return_t status = thread_get_state(thread, FIRCLSThreadState, (thread_state_t)(&(context->__ss)),
&stateCount);
if (status != KERN_SUCCESS && status != ROSETTA_SUCCESS) {
FIRCLSSDKLogError("Failed to get thread state via thread_get_state for thread: %i\n", thread);
return false;
}
return true;
#else
return false;
#endif
}
static bool FIRCLSProcessGetThreadName(FIRCLSProcess *process,
thread_t thread,
char *buffer,
size_t length) {
pthread_t pthread;
if (!buffer || length <= 0) {
return false;
}
pthread = pthread_from_mach_thread_np(thread);
return pthread_getname_np(pthread, buffer, length) == 0;
}
static const char *FIRCLSProcessGetThreadDispatchQueueName(FIRCLSProcess *process,
thread_t thread) {
thread_identifier_info_data_t info;
mach_msg_type_number_t infoCount;
dispatch_queue_t *queueAddress;
dispatch_queue_t queue;
const char *string;
infoCount = THREAD_IDENTIFIER_INFO_COUNT;
if (thread_info(thread, THREAD_IDENTIFIER_INFO, (thread_info_t)&info, &infoCount) !=
KERN_SUCCESS) {
FIRCLSSDKLog("Unable to get thread info\n");
return NULL;
}
queueAddress = (dispatch_queue_t *)info.dispatch_qaddr;
if (queueAddress == NULL) {
return "";
}
// Sometimes a queue address is invalid. I cannot explain why this is, but
// it can cause a crash.
if (!FIRCLSReadMemory((vm_address_t)queueAddress, &queue, sizeof(void *))) {
return "";
}
// here, we know it is safe to de-reference this address, so attempt to get the queue name
if (!queue) {
return "";
}
string = dispatch_queue_get_label(queue);
// but, we still don't if the entire string is valid, so check that too
if (!FIRCLSReadString((vm_address_t)string, (char **)&string, 128)) {
return "";
}
return string;
}
#pragma mark - Data Recording
static bool FIRCLSProcessRecordThreadRegisters(FIRCLSThreadContext context, FIRCLSFile *file) {
#if CLS_CPU_ARM
FIRCLSFileWriteHashEntryUint64(file, "r0", context.__ss.__r[0]);
FIRCLSFileWriteHashEntryUint64(file, "r1", context.__ss.__r[1]);
FIRCLSFileWriteHashEntryUint64(file, "r2", context.__ss.__r[2]);
FIRCLSFileWriteHashEntryUint64(file, "r3", context.__ss.__r[3]);
FIRCLSFileWriteHashEntryUint64(file, "r4", context.__ss.__r[4]);
FIRCLSFileWriteHashEntryUint64(file, "r5", context.__ss.__r[5]);
FIRCLSFileWriteHashEntryUint64(file, "r6", context.__ss.__r[6]);
FIRCLSFileWriteHashEntryUint64(file, "r7", context.__ss.__r[7]);
FIRCLSFileWriteHashEntryUint64(file, "r8", context.__ss.__r[8]);
FIRCLSFileWriteHashEntryUint64(file, "r9", context.__ss.__r[9]);
FIRCLSFileWriteHashEntryUint64(file, "r10", context.__ss.__r[10]);
FIRCLSFileWriteHashEntryUint64(file, "r11", context.__ss.__r[11]);
FIRCLSFileWriteHashEntryUint64(file, "ip", context.__ss.__r[12]);
FIRCLSFileWriteHashEntryUint64(file, "sp", context.__ss.__sp);
FIRCLSFileWriteHashEntryUint64(file, "lr", context.__ss.__lr);
FIRCLSFileWriteHashEntryUint64(file, "pc", context.__ss.__pc);
FIRCLSFileWriteHashEntryUint64(file, "cpsr", context.__ss.__cpsr);
#elif CLS_CPU_ARM64
FIRCLSFileWriteHashEntryUint64(file, "x0", context.__ss.__x[0]);
FIRCLSFileWriteHashEntryUint64(file, "x1", context.__ss.__x[1]);
FIRCLSFileWriteHashEntryUint64(file, "x2", context.__ss.__x[2]);
FIRCLSFileWriteHashEntryUint64(file, "x3", context.__ss.__x[3]);
FIRCLSFileWriteHashEntryUint64(file, "x4", context.__ss.__x[4]);
FIRCLSFileWriteHashEntryUint64(file, "x5", context.__ss.__x[5]);
FIRCLSFileWriteHashEntryUint64(file, "x6", context.__ss.__x[6]);
FIRCLSFileWriteHashEntryUint64(file, "x7", context.__ss.__x[7]);
FIRCLSFileWriteHashEntryUint64(file, "x8", context.__ss.__x[8]);
FIRCLSFileWriteHashEntryUint64(file, "x9", context.__ss.__x[9]);
FIRCLSFileWriteHashEntryUint64(file, "x10", context.__ss.__x[10]);
FIRCLSFileWriteHashEntryUint64(file, "x11", context.__ss.__x[11]);
FIRCLSFileWriteHashEntryUint64(file, "x12", context.__ss.__x[12]);
FIRCLSFileWriteHashEntryUint64(file, "x13", context.__ss.__x[13]);
FIRCLSFileWriteHashEntryUint64(file, "x14", context.__ss.__x[14]);
FIRCLSFileWriteHashEntryUint64(file, "x15", context.__ss.__x[15]);
FIRCLSFileWriteHashEntryUint64(file, "x16", context.__ss.__x[16]);
FIRCLSFileWriteHashEntryUint64(file, "x17", context.__ss.__x[17]);
FIRCLSFileWriteHashEntryUint64(file, "x18", context.__ss.__x[18]);
FIRCLSFileWriteHashEntryUint64(file, "x19", context.__ss.__x[19]);
FIRCLSFileWriteHashEntryUint64(file, "x20", context.__ss.__x[20]);
FIRCLSFileWriteHashEntryUint64(file, "x21", context.__ss.__x[21]);
FIRCLSFileWriteHashEntryUint64(file, "x22", context.__ss.__x[22]);
FIRCLSFileWriteHashEntryUint64(file, "x23", context.__ss.__x[23]);
FIRCLSFileWriteHashEntryUint64(file, "x24", context.__ss.__x[24]);
FIRCLSFileWriteHashEntryUint64(file, "x25", context.__ss.__x[25]);
FIRCLSFileWriteHashEntryUint64(file, "x26", context.__ss.__x[26]);
FIRCLSFileWriteHashEntryUint64(file, "x27", context.__ss.__x[27]);
FIRCLSFileWriteHashEntryUint64(file, "x28", context.__ss.__x[28]);
FIRCLSFileWriteHashEntryUint64(file, "fp", FIRCLSThreadContextGetFramePointer(&context));
FIRCLSFileWriteHashEntryUint64(file, "sp", FIRCLSThreadContextGetStackPointer(&context));
FIRCLSFileWriteHashEntryUint64(file, "lr", FIRCLSThreadContextGetLinkRegister(&context));
FIRCLSFileWriteHashEntryUint64(file, "pc", FIRCLSThreadContextGetPC(&context));
FIRCLSFileWriteHashEntryUint64(file, "cpsr", context.__ss.__cpsr);
#elif CLS_CPU_I386
FIRCLSFileWriteHashEntryUint64(file, "eax", context.__ss.__eax);
FIRCLSFileWriteHashEntryUint64(file, "ebx", context.__ss.__ebx);
FIRCLSFileWriteHashEntryUint64(file, "ecx", context.__ss.__ecx);
FIRCLSFileWriteHashEntryUint64(file, "edx", context.__ss.__edx);
FIRCLSFileWriteHashEntryUint64(file, "edi", context.__ss.__edi);
FIRCLSFileWriteHashEntryUint64(file, "esi", context.__ss.__esi);
FIRCLSFileWriteHashEntryUint64(file, "ebp", context.__ss.__ebp);
FIRCLSFileWriteHashEntryUint64(file, "esp", context.__ss.__esp);
FIRCLSFileWriteHashEntryUint64(file, "ss", context.__ss.__ss);
FIRCLSFileWriteHashEntryUint64(file, "eflags", context.__ss.__eflags);
FIRCLSFileWriteHashEntryUint64(file, "eip", context.__ss.__eip);
FIRCLSFileWriteHashEntryUint64(file, "cs", context.__ss.__cs);
FIRCLSFileWriteHashEntryUint64(file, "ds", context.__ss.__ds);
FIRCLSFileWriteHashEntryUint64(file, "es", context.__ss.__es);
FIRCLSFileWriteHashEntryUint64(file, "fs", context.__ss.__fs);
FIRCLSFileWriteHashEntryUint64(file, "gs", context.__ss.__gs);
// how do we get the cr2 register?
#elif CLS_CPU_X86_64
FIRCLSFileWriteHashEntryUint64(file, "rax", context.__ss.__rax);
FIRCLSFileWriteHashEntryUint64(file, "rbx", context.__ss.__rbx);
FIRCLSFileWriteHashEntryUint64(file, "rcx", context.__ss.__rcx);
FIRCLSFileWriteHashEntryUint64(file, "rdx", context.__ss.__rdx);
FIRCLSFileWriteHashEntryUint64(file, "rdi", context.__ss.__rdi);
FIRCLSFileWriteHashEntryUint64(file, "rsi", context.__ss.__rsi);
FIRCLSFileWriteHashEntryUint64(file, "rbp", context.__ss.__rbp);
FIRCLSFileWriteHashEntryUint64(file, "rsp", context.__ss.__rsp);
FIRCLSFileWriteHashEntryUint64(file, "r8", context.__ss.__r8);
FIRCLSFileWriteHashEntryUint64(file, "r9", context.__ss.__r9);
FIRCLSFileWriteHashEntryUint64(file, "r10", context.__ss.__r10);
FIRCLSFileWriteHashEntryUint64(file, "r11", context.__ss.__r11);
FIRCLSFileWriteHashEntryUint64(file, "r12", context.__ss.__r12);
FIRCLSFileWriteHashEntryUint64(file, "r13", context.__ss.__r13);
FIRCLSFileWriteHashEntryUint64(file, "r14", context.__ss.__r14);
FIRCLSFileWriteHashEntryUint64(file, "r15", context.__ss.__r15);
FIRCLSFileWriteHashEntryUint64(file, "rip", context.__ss.__rip);
FIRCLSFileWriteHashEntryUint64(file, "rflags", context.__ss.__rflags);
FIRCLSFileWriteHashEntryUint64(file, "cs", context.__ss.__cs);
FIRCLSFileWriteHashEntryUint64(file, "fs", context.__ss.__fs);
FIRCLSFileWriteHashEntryUint64(file, "gs", context.__ss.__gs);
#endif
return true;
}
static bool FIRCLSProcessRecordThread(FIRCLSProcess *process, thread_t thread, FIRCLSFile *file) {
FIRCLSUnwindContext unwindContext;
FIRCLSThreadContext context;
if (!FIRCLSProcessGetThreadState(process, thread, &context)) {
FIRCLSSDKLogError("Unable to get thread state\n");
return false;
}
if (!FIRCLSUnwindInit(&unwindContext, context)) {
FIRCLSSDKLog("Unable to init unwind context\n");
return false;
}
FIRCLSFileWriteHashStart(file);
// registers
FIRCLSFileWriteHashKey(file, "registers");
FIRCLSFileWriteHashStart(file);
FIRCLSProcessRecordThreadRegisters(context, file);
FIRCLSFileWriteHashEnd(file);
// stacktrace
FIRCLSFileWriteHashKey(file, "stacktrace");
// stacktrace is an array of integers
FIRCLSFileWriteArrayStart(file);
uint32_t repeatedPCCount = 0;
uint64_t repeatedPC = 0;
const FIRCLSInternalLogLevel level = _firclsContext.writable->internalLogging.logLevel;
while (FIRCLSUnwindNextFrame(&unwindContext)) {
const uintptr_t pc = FIRCLSUnwindGetPC(&unwindContext);
const uint32_t frameCount = FIRCLSUnwindGetFrameRepeatCount(&unwindContext);
if (repeatedPC == pc && repeatedPC != 0) {
// actively counting a recursion
repeatedPCCount = frameCount;
continue;
}
if (frameCount >= FIRCLSUnwindInfiniteRecursionCountThreshold && repeatedPC == 0) {
repeatedPC = pc;
FIRCLSSDKLogWarn("Possible infinite recursion - suppressing logging\n");
_firclsContext.writable->internalLogging.logLevel = FIRCLSInternalLogLevelWarn;
continue;
}
if (repeatedPC != 0) {
// at this point, we've recorded a repeated PC, but it is now no longer
// repeating, so we can restore the logging
_firclsContext.writable->internalLogging.logLevel = level;
}
FIRCLSFileWriteArrayEntryUint64(file, pc);
}
FIRCLSFileWriteArrayEnd(file);
// crashed?
if (FIRCLSProcessIsCrashedThread(process, thread)) {
FIRCLSFileWriteHashEntryBoolean(file, "crashed", true);
}
if (repeatedPC != 0) {
FIRCLSFileWriteHashEntryUint64(file, "repeated_pc", repeatedPC);
FIRCLSFileWriteHashEntryUint64(file, "repeat_count", repeatedPCCount);
}
// Just for extra safety, restore the logging level again. The logic
// above is fairly tricky, this is cheap, and no logging is a real pain.
_firclsContext.writable->internalLogging.logLevel = level;
// end thread info
FIRCLSFileWriteHashEnd(file);
return true;
}
bool FIRCLSProcessRecordAllThreads(FIRCLSProcess *process, FIRCLSFile *file) {
uint32_t threadCount;
uint32_t i;
threadCount = FIRCLSProcessGetThreadCount(process);
FIRCLSFileWriteSectionStart(file, "threads");
FIRCLSFileWriteArrayStart(file);
for (i = 0; i < threadCount; ++i) {
thread_t thread;
thread = FIRCLSProcessGetThread(process, i);
FIRCLSSDKLogInfo("recording thread %d data\n", i);
if (!FIRCLSProcessRecordThread(process, thread, file)) {
FIRCLSSDKLogError("Failed to record thread state. Closing threads JSON to prevent malformed crash report.\n");
FIRCLSFileWriteArrayEnd(file);
FIRCLSFileWriteSectionEnd(file);
return false;
}
}
FIRCLSFileWriteArrayEnd(file);
FIRCLSFileWriteSectionEnd(file);
FIRCLSSDKLogInfo("Completed recording all thread data\n");
return true;
}
void FIRCLSProcessRecordThreadNames(FIRCLSProcess *process, FIRCLSFile *file) {
uint32_t threadCount;
uint32_t i;
FIRCLSFileWriteSectionStart(file, "thread_names");
FIRCLSFileWriteArrayStart(file);
threadCount = FIRCLSProcessGetThreadCount(process);
for (i = 0; i < threadCount; ++i) {
thread_t thread;
char name[THREAD_NAME_BUFFER_SIZE];
thread = FIRCLSProcessGetThread(process, i);
name[0] = 0; // null-terminate, just in case nothing is written
FIRCLSProcessGetThreadName(process, thread, name, THREAD_NAME_BUFFER_SIZE);
FIRCLSFileWriteArrayEntryString(file, name);
}
FIRCLSFileWriteArrayEnd(file);
FIRCLSFileWriteSectionEnd(file);
}
void FIRCLSProcessRecordDispatchQueueNames(FIRCLSProcess *process, FIRCLSFile *file) {
uint32_t threadCount;
uint32_t i;
FIRCLSFileWriteSectionStart(file, "dispatch_queue_names");
FIRCLSFileWriteArrayStart(file);
threadCount = FIRCLSProcessGetThreadCount(process);
for (i = 0; i < threadCount; ++i) {
thread_t thread;
const char *name;
thread = FIRCLSProcessGetThread(process, i);
name = FIRCLSProcessGetThreadDispatchQueueName(process, thread);
// Apple Report Converter will fail to parse this when "name" is null,
// so we will use an empty string instead.
if (name == NULL) {
name = "";
}
FIRCLSFileWriteArrayEntryString(file, name);
}
FIRCLSFileWriteArrayEnd(file);
FIRCLSFileWriteSectionEnd(file);
}
#pragma mark - Other Process Info
bool FIRCLSProcessGetMemoryUsage(uint64_t *active,
uint64_t *inactive,
uint64_t *wired,
uint64_t *freeMem) {
mach_port_t hostPort;
mach_msg_type_number_t hostSize;
vm_size_t pageSize;
vm_statistics_data_t vmStat;
hostPort = mach_host_self();
hostSize = sizeof(vm_statistics_data_t) / sizeof(integer_t);
pageSize = _firclsContext.readonly->host.pageSize;
if (host_statistics(hostPort, HOST_VM_INFO, (host_info_t)&vmStat, &hostSize) != KERN_SUCCESS) {
FIRCLSSDKLog("Failed to get vm statistics\n");
return false;
}
if (!(active && inactive && wired && freeMem)) {
FIRCLSSDKLog("Invalid pointers\n");
return false;
}
// compute the sizes in bytes and return the values
*active = vmStat.active_count * pageSize;
*inactive = vmStat.inactive_count * pageSize;
*wired = vmStat.wire_count * pageSize;
*freeMem = vmStat.free_count * pageSize;
return true;
}
bool FIRCLSProcessGetInfo(FIRCLSProcess *process,
uint64_t *virtualSize,
uint64_t *residentSize,
time_value_t *userTime,
time_value_t *systemTime) {
struct task_basic_info_64 taskInfo;
mach_msg_type_number_t count;
count = TASK_BASIC_INFO_64_COUNT;
if (task_info(process->task, TASK_BASIC_INFO_64, (task_info_t)&taskInfo, &count) !=
KERN_SUCCESS) {
FIRCLSSDKLog("Failed to get task info\n");
return false;
}
if (!(virtualSize && residentSize && userTime && systemTime)) {
FIRCLSSDKLog("Invalid pointers\n");
return false;
}
*virtualSize = taskInfo.virtual_size;
*residentSize = taskInfo.resident_size;
*userTime = taskInfo.user_time;
*systemTime = taskInfo.system_time;
return true;
}
void FIRCLSProcessRecordStats(FIRCLSProcess *process, FIRCLSFile *file) {
uint64_t active;
uint64_t inactive;
uint64_t virtualSize;
uint64_t residentSize;
uint64_t wired;
uint64_t freeMem;
time_value_t userTime;
time_value_t systemTime;
if (!FIRCLSProcessGetMemoryUsage(&active, &inactive, &wired, &freeMem)) {
FIRCLSSDKLog("Unable to get process memory usage\n");
return;
}
if (!FIRCLSProcessGetInfo(process, &virtualSize, &residentSize, &userTime, &systemTime)) {
FIRCLSSDKLog("Unable to get process stats\n");
return;
}
FIRCLSFileWriteSectionStart(file, "process_stats");
FIRCLSFileWriteHashStart(file);
FIRCLSFileWriteHashEntryUint64(file, "active", active);
FIRCLSFileWriteHashEntryUint64(file, "inactive", inactive);
FIRCLSFileWriteHashEntryUint64(file, "wired", wired);
FIRCLSFileWriteHashEntryUint64(file, "freeMem", freeMem); // Intentionally left in, for now. Arg.
FIRCLSFileWriteHashEntryUint64(file, "free_mem", freeMem);
FIRCLSFileWriteHashEntryUint64(file, "virtual", virtualSize);
FIRCLSFileWriteHashEntryUint64(file, "resident", active);
FIRCLSFileWriteHashEntryUint64(file, "user_time",
(userTime.seconds * 1000 * 1000) + userTime.microseconds);
FIRCLSFileWriteHashEntryUint64(file, "sys_time",
(systemTime.seconds * 1000 * 1000) + systemTime.microseconds);
FIRCLSFileWriteHashEnd(file);
FIRCLSFileWriteSectionEnd(file);
}
#pragma mark - Runtime Info
#define OBJC_MSG_SEND_START ((vm_address_t)objc_msgSend)
#define OBJC_MSG_SEND_SUPER_START ((vm_address_t)objc_msgSendSuper)
#define OBJC_MSG_SEND_END (OBJC_MSG_SEND_START + 66)
#define OBJC_MSG_SEND_SUPER_END (OBJC_MSG_SEND_SUPER_START + 66)
#if !CLS_CPU_ARM64
#define OBJC_MSG_SEND_STRET_START ((vm_address_t)objc_msgSend_stret)
#define OBJC_MSG_SEND_SUPER_STRET_START ((vm_address_t)objc_msgSendSuper_stret)
#define OBJC_MSG_SEND_STRET_END (OBJC_MSG_SEND_STRET_START + 66)
#define OBJC_MSG_SEND_SUPER_STRET_END (OBJC_MSG_SEND_SUPER_STRET_START + 66)
#endif
#if CLS_CPU_X86
#define OBJC_MSG_SEND_FPRET_START ((vm_address_t)objc_msgSend_fpret)
#define OBJC_MSG_SEND_FPRET_END (OBJC_MSG_SEND_FPRET_START + 66)
#endif
static const char *FIRCLSProcessGetObjCSelectorName(FIRCLSThreadContext registers) {
void *selectorAddress;
void *selRegister;
#if !CLS_CPU_ARM64
void *stretSelRegister;
#endif
vm_address_t pc;
// First, did we crash in objc_msgSend? The two ways I can think
// of doing this are to use dladdr, and then comparing the strings to
// objc_msg*, or looking up the symbols, and guessing if we are "close enough".
selectorAddress = NULL;
#if CLS_CPU_ARM
pc = registers.__ss.__pc;
selRegister = (void *)registers.__ss.__r[1];
stretSelRegister = (void *)registers.__ss.__r[2];
#elif CLS_CPU_ARM64
pc = FIRCLSThreadContextGetPC(&registers);
selRegister = (void *)registers.__ss.__x[1];
#elif CLS_CPU_I386
pc = registers.__ss.__eip;
selRegister = (void *)registers.__ss.__ecx;
stretSelRegister = (void *)registers.__ss.__ecx;
#elif CLS_CPU_X86_64
pc = registers.__ss.__rip;
selRegister = (void *)registers.__ss.__rsi;
stretSelRegister = (void *)registers.__ss.__rdx;
#endif
if ((pc >= OBJC_MSG_SEND_START) && (pc <= OBJC_MSG_SEND_END)) {
selectorAddress = selRegister;
}
#if !CLS_CPU_ARM64
if ((pc >= OBJC_MSG_SEND_SUPER_START) && (pc <= OBJC_MSG_SEND_SUPER_END)) {
selectorAddress = selRegister;
}
if ((pc >= OBJC_MSG_SEND_STRET_START) && (pc <= OBJC_MSG_SEND_STRET_END)) {
selectorAddress = stretSelRegister;
}
if ((pc >= OBJC_MSG_SEND_SUPER_STRET_START) && (pc <= OBJC_MSG_SEND_SUPER_STRET_END)) {
selectorAddress = stretSelRegister;
}
#if CLS_CPU_X86
if ((pc >= OBJC_MSG_SEND_FPRET_START) && (pc <= OBJC_MSG_SEND_FPRET_END)) {
selectorAddress = selRegister;
}
#endif
#endif
if (!selectorAddress) {
return "";
}
if (!FIRCLSReadString((vm_address_t)selectorAddress, (char **)&selectorAddress, 128)) {
FIRCLSSDKLog("Unable to read the selector string\n");
return "";
}
return selectorAddress;
}
#define CRASH_ALIGN __attribute__((aligned(8)))
typedef struct {
unsigned version CRASH_ALIGN;
const char *message CRASH_ALIGN;
const char *signature CRASH_ALIGN;
const char *backtrace CRASH_ALIGN;
const char *message2 CRASH_ALIGN;
void *reserved CRASH_ALIGN;
void *reserved2 CRASH_ALIGN;
} crash_info_t;
static void FIRCLSProcessRecordCrashInfo(FIRCLSFile *file) {
// TODO: this should be abstracted into binary images, if possible
FIRCLSBinaryImageRuntimeNode *nodes = _firclsContext.writable->binaryImage.nodes;
if (!nodes) {
FIRCLSSDKLogError("The node structure is NULL\n");
return;
}
for (uint32_t i = 0; i < CLS_BINARY_IMAGE_RUNTIME_NODE_COUNT; ++i) {
FIRCLSBinaryImageRuntimeNode *node = &nodes[i];
if (!node->crashInfo) {
continue;
}
crash_info_t info;
if (!FIRCLSReadMemory((vm_address_t)node->crashInfo, &info, sizeof(crash_info_t))) {
continue;
}
FIRCLSSDKLogDebug("Found crash info with version %d\n", info.version);
// Currently support versions 0 through 5.
// 4 was in use for a long time, but it appears that with iOS 9 / swift 2.0, the version has
// been bumped.
if (info.version > 5) {
continue;
}
if (!info.message) {
continue;
}
#if CLS_BINARY_IMAGE_RUNTIME_NODE_RECORD_NAME
FIRCLSSDKLogInfo("Found crash info for %s\n", node->name);
#endif
FIRCLSSDKLogDebug("attempting to read crash info string\n");
char *string = NULL;
if (!FIRCLSReadString((vm_address_t)info.message, &string, 256)) {
FIRCLSSDKLogError("Failed to copy crash info string\n");
continue;
}
// The crash_info_t's message may contain the device's UDID, in this case,
// make sure that we do our best to redact that information before writing the
// rest of the message to disk. This also has the effect of not uploading that
// information in the subsequent crash report.
FIRCLSRedactUUID(string);
FIRCLSFileWriteArrayEntryHexEncodedString(file, string);
}
}
void FIRCLSProcessRecordRuntimeInfo(FIRCLSProcess *process, FIRCLSFile *file) {
FIRCLSThreadContext mcontext;
if (!FIRCLSProcessGetThreadState(process, process->crashedThread, &mcontext)) {
FIRCLSSDKLogError("unable to get crashed thread state");
}
FIRCLSFileWriteSectionStart(file, "runtime");
FIRCLSFileWriteHashStart(file);
FIRCLSFileWriteHashEntryString(file, "objc_selector", FIRCLSProcessGetObjCSelectorName(mcontext));
FIRCLSFileWriteHashKey(file, "crash_info_entries");
FIRCLSFileWriteArrayStart(file);
FIRCLSProcessRecordCrashInfo(file);
FIRCLSFileWriteArrayEnd(file);
FIRCLSFileWriteHashEnd(file);
FIRCLSFileWriteSectionEnd(file);
}

View File

@ -0,0 +1,44 @@
// Copyright 2019 Google
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#pragma once
#include <mach/mach.h>
#include <stdbool.h>
#include "Crashlytics/Crashlytics/Helpers/FIRCLSFile.h"
typedef struct {
// task info
mach_port_t task;
// thread stuff
thread_t thisThread;
thread_t crashedThread;
thread_act_array_t threads;
mach_msg_type_number_t threadCount;
void *uapVoid; // current thread state
} FIRCLSProcess;
bool FIRCLSProcessInit(FIRCLSProcess *process, thread_t crashedThread, void *uapVoid);
bool FIRCLSProcessDebuggerAttached(void);
bool FIRCLSProcessSuspendAllOtherThreads(FIRCLSProcess *process);
bool FIRCLSProcessResumeAllOtherThreads(FIRCLSProcess *process);
void FIRCLSProcessRecordThreadNames(FIRCLSProcess *process, FIRCLSFile *file);
void FIRCLSProcessRecordDispatchQueueNames(FIRCLSProcess *process, FIRCLSFile *file);
bool FIRCLSProcessRecordAllThreads(FIRCLSProcess *process, FIRCLSFile *file);
void FIRCLSProcessRecordStats(FIRCLSProcess *process, FIRCLSFile *file);
void FIRCLSProcessRecordRuntimeInfo(FIRCLSProcess *process, FIRCLSFile *file);

View File

@ -0,0 +1,115 @@
// Copyright 2019 Google
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#pragma once
#include "Crashlytics/Crashlytics/Helpers/FIRCLSFile.h"
__BEGIN_DECLS
#ifdef __OBJC__
extern NSString* const FIRCLSStartTimeKey;
extern NSString* const FIRCLSFirstRunloopTurnTimeKey;
extern NSString* const FIRCLSInBackgroundKey;
#if TARGET_OS_IPHONE
extern NSString* const FIRCLSDeviceOrientationKey;
extern NSString* const FIRCLSUIOrientationKey;
#endif
extern NSString* const FIRCLSUserIdentifierKey;
extern NSString* const FIRCLSUserNameKey;
extern NSString* const FIRCLSUserEmailKey;
extern NSString* const FIRCLSDevelopmentPlatformNameKey;
extern NSString* const FIRCLSDevelopmentPlatformVersionKey;
extern NSString* const FIRCLSOnDemandRecordedExceptionsKey;
extern NSString* const FIRCLSOnDemandDroppedExceptionsKey;
#endif
extern const uint32_t FIRCLSUserLoggingMaxKVEntries;
typedef struct {
const char* incrementalPath;
const char* compactedPath;
uint32_t maxIncrementalCount;
uint32_t maxCount;
} FIRCLSUserLoggingKVStorage;
typedef struct {
const char* aPath;
const char* bPath;
uint32_t maxSize;
uint32_t maxEntries;
bool restrictBySize;
uint32_t* entryCount;
} FIRCLSUserLoggingABStorage;
typedef struct {
FIRCLSUserLoggingKVStorage userKVStorage;
FIRCLSUserLoggingKVStorage internalKVStorage;
FIRCLSUserLoggingABStorage logStorage;
FIRCLSUserLoggingABStorage errorStorage;
FIRCLSUserLoggingABStorage customExceptionStorage;
} FIRCLSUserLoggingReadOnlyContext;
typedef struct {
const char* activeUserLogPath;
const char* activeErrorLogPath;
const char* activeCustomExceptionPath;
uint32_t userKVCount;
uint32_t internalKVCount;
uint32_t errorsCount;
} FIRCLSUserLoggingWritableContext;
void FIRCLSUserLoggingInit(FIRCLSUserLoggingReadOnlyContext* roContext,
FIRCLSUserLoggingWritableContext* rwContext);
#ifdef __OBJC__
void FIRCLSUserLoggingRecordUserKeyValue(NSString* key, id value);
void FIRCLSUserLoggingRecordUserKeysAndValues(NSDictionary* keysAndValues);
void FIRCLSUserLoggingRecordInternalKeyValue(NSString* key, id value);
void FIRCLSUserLoggingWriteInternalKeyValue(NSString* key, NSString* value);
void FIRCLSUserLoggingRecordError(NSError* error,
NSDictionary<NSString*, id>* additionalUserInfo,
NSString* rolloutsInfoJSON);
NSDictionary* FIRCLSUserLoggingGetCompactedKVEntries(FIRCLSUserLoggingKVStorage* storage,
bool decodeHex);
void FIRCLSUserLoggingCompactKVEntries(FIRCLSUserLoggingKVStorage* storage);
void FIRCLSUserLoggingRecordKeyValue(NSString* key,
id value,
FIRCLSUserLoggingKVStorage* storage,
uint32_t* counter);
void FIRCLSUserLoggingRecordKeysAndValues(NSDictionary* keysAndValues,
FIRCLSUserLoggingKVStorage* storage,
uint32_t* counter);
void FIRCLSUserLoggingWriteAndCheckABFiles(FIRCLSUserLoggingABStorage* storage,
const char** activePath,
void (^openedFileBlock)(FIRCLSFile* file));
NSArray* FIRCLSUserLoggingStoredKeyValues(const char* path);
OBJC_EXTERN void FIRCLSLog(NSString* format, ...) NS_FORMAT_FUNCTION(1, 2);
OBJC_EXTERN void FIRCLSLogToStorage(FIRCLSUserLoggingABStorage* storage,
const char** activePath,
NSString* format,
...) NS_FORMAT_FUNCTION(3, 4);
#endif
__END_DECLS

View File

@ -0,0 +1,612 @@
// Copyright 2019 Google
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "Crashlytics/Crashlytics/Components/FIRCLSUserLogging.h"
#include <sys/time.h>
#include "Crashlytics/Crashlytics/Components/FIRCLSGlobals.h"
#include "Crashlytics/Crashlytics/Helpers/FIRCLSUtility.h"
#import "Crashlytics/Crashlytics/Controllers/FIRCLSReportManager_Private.h"
NSString *const FIRCLSStartTimeKey = @"com.crashlytics.kit-start-time";
NSString *const FIRCLSFirstRunloopTurnTimeKey = @"com.crashlytics.first-run-loop-time";
NSString *const FIRCLSInBackgroundKey = @"com.crashlytics.in-background";
#if TARGET_OS_IPHONE
NSString *const FIRCLSDeviceOrientationKey = @"com.crashlytics.device-orientation";
NSString *const FIRCLSUIOrientationKey = @"com.crashlytics.ui-orientation";
#endif
NSString *const FIRCLSUserIdentifierKey = @"com.crashlytics.user-id";
NSString *const FIRCLSDevelopmentPlatformNameKey = @"com.crashlytics.development-platform-name";
NSString *const FIRCLSDevelopmentPlatformVersionKey =
@"com.crashlytics.development-platform-version";
NSString *const FIRCLSOnDemandRecordedExceptionsKey =
@"com.crashlytics.on-demand.recorded-exceptions";
NSString *const FIRCLSOnDemandDroppedExceptionsKey =
@"com.crashlytics.on-demand.dropped-exceptions";
// Empty string object synchronized on to prevent a race condition when accessing AB file path
NSString *const FIRCLSSynchronizedPathKey = @"";
const uint32_t FIRCLSUserLoggingMaxKVEntries = 64;
#pragma mark - Prototypes
static void FIRCLSUserLoggingWriteKeysAndValues(NSDictionary *keysAndValues,
FIRCLSUserLoggingKVStorage *storage,
uint32_t *counter,
BOOL containsNullValue);
static void FIRCLSUserLoggingCheckAndSwapABFiles(FIRCLSUserLoggingABStorage *storage,
const char **activePath,
off_t fileSize);
void FIRCLSLogInternal(FIRCLSUserLoggingABStorage *storage,
const char **activePath,
NSString *message);
#pragma mark - Setup
void FIRCLSUserLoggingInit(FIRCLSUserLoggingReadOnlyContext *roContext,
FIRCLSUserLoggingWritableContext *rwContext) {
rwContext->activeUserLogPath = roContext->logStorage.aPath;
rwContext->activeErrorLogPath = roContext->errorStorage.aPath;
rwContext->activeCustomExceptionPath = roContext->customExceptionStorage.aPath;
rwContext->userKVCount = 0;
rwContext->internalKVCount = 0;
rwContext->errorsCount = 0;
roContext->userKVStorage.maxIncrementalCount = FIRCLSUserLoggingMaxKVEntries;
roContext->internalKVStorage.maxIncrementalCount = roContext->userKVStorage.maxIncrementalCount;
}
#pragma mark - KV Logging
void FIRCLSUserLoggingRecordInternalKeyValue(NSString *key, id value) {
FIRCLSUserLoggingRecordKeyValue(key, value, &_firclsContext.readonly->logging.internalKVStorage,
&_firclsContext.writable->logging.internalKVCount);
}
void FIRCLSUserLoggingWriteInternalKeyValue(NSString *key, NSString *value) {
// Unsynchronized - must be run on the correct queue
NSDictionary *keysAndValues = key ? @{key : value ?: [NSNull null]} : nil;
FIRCLSUserLoggingWriteKeysAndValues(keysAndValues,
&_firclsContext.readonly->logging.internalKVStorage,
&_firclsContext.writable->logging.internalKVCount, NO);
}
void FIRCLSUserLoggingRecordUserKeyValue(NSString *key, id value) {
FIRCLSUserLoggingRecordKeyValue(key, value, &_firclsContext.readonly->logging.userKVStorage,
&_firclsContext.writable->logging.userKVCount);
}
void FIRCLSUserLoggingRecordUserKeysAndValues(NSDictionary *keysAndValues) {
FIRCLSUserLoggingRecordKeysAndValues(keysAndValues,
&_firclsContext.readonly->logging.userKVStorage,
&_firclsContext.writable->logging.userKVCount);
}
static id FIRCLSUserLoggingGetComponent(NSDictionary *entry,
NSString *componentName,
bool decodeHex) {
id value = [entry objectForKey:componentName];
return (decodeHex && value != [NSNull null]) ? FIRCLSFileHexDecodeString([value UTF8String])
: value;
}
static NSString *FIRCLSUserLoggingGetKey(NSDictionary *entry, bool decodeHex) {
return FIRCLSUserLoggingGetComponent(entry, @"key", decodeHex);
}
static id FIRCLSUserLoggingGetValue(NSDictionary *entry, bool decodeHex) {
return FIRCLSUserLoggingGetComponent(entry, @"value", decodeHex);
}
NSDictionary *FIRCLSUserLoggingGetCompactedKVEntries(FIRCLSUserLoggingKVStorage *storage,
bool decodeHex) {
if (!FIRCLSIsValidPointer(storage)) {
FIRCLSSDKLogError("storage invalid\n");
return nil;
}
NSArray *incrementalKVs = FIRCLSUserLoggingStoredKeyValues(storage->incrementalPath);
NSArray *compactedKVs = FIRCLSUserLoggingStoredKeyValues(storage->compactedPath);
NSMutableDictionary *finalKVSet = [NSMutableDictionary new];
// These should all be unique, so there might be a more efficient way to
// do this
for (NSDictionary *entry in compactedKVs) {
NSString *key = FIRCLSUserLoggingGetKey(entry, decodeHex);
NSString *value = FIRCLSUserLoggingGetValue(entry, decodeHex);
if (!key || !value) {
FIRCLSSDKLogError("compacted key/value contains a nil and must be dropped\n");
continue;
}
[finalKVSet setObject:value forKey:key];
}
// Now, assign the incremental values, in file order, so we overwrite any older values.
for (NSDictionary *entry in incrementalKVs) {
NSString *key = FIRCLSUserLoggingGetKey(entry, decodeHex);
NSString *value = FIRCLSUserLoggingGetValue(entry, decodeHex);
if (!key || !value) {
FIRCLSSDKLogError("incremental key/value contains a nil and must be dropped\n");
continue;
}
if ([value isEqual:[NSNull null]]) {
[finalKVSet removeObjectForKey:key];
} else {
[finalKVSet setObject:value forKey:key];
}
}
return finalKVSet;
}
static void FIRCLSUserLoggingWriteKVEntriesToFile(
NSDictionary<NSString *, NSString *> *keysAndValues, BOOL shouldHexEncode, FIRCLSFile *file) {
for (NSString *key in keysAndValues) {
NSString *valueObject = [keysAndValues objectForKey:key];
// map `NSNull` into nil
const char *value = (valueObject == (NSString *)[NSNull null] ? nil : [valueObject UTF8String]);
FIRCLSFileWriteSectionStart(file, "kv");
FIRCLSFileWriteHashStart(file);
if (shouldHexEncode) {
FIRCLSFileWriteHashEntryHexEncodedString(file, "key", [key UTF8String]);
FIRCLSFileWriteHashEntryHexEncodedString(file, "value", value);
} else {
FIRCLSFileWriteHashEntryString(file, "key", [key UTF8String]);
FIRCLSFileWriteHashEntryString(file, "value", value);
}
FIRCLSFileWriteHashEnd(file);
FIRCLSFileWriteSectionEnd(file);
}
}
void FIRCLSUserLoggingCompactKVEntries(FIRCLSUserLoggingKVStorage *storage) {
if (!FIRCLSIsValidPointer(storage)) {
FIRCLSSDKLogError("Error: storage invalid\n");
return;
}
NSDictionary *finalKVs = FIRCLSUserLoggingGetCompactedKVEntries(storage, false);
if (unlink(storage->compactedPath) != 0) {
FIRCLSSDKLog("Error: Unable to remove compacted KV store before compaction %s\n",
strerror(errno));
}
FIRCLSFile file;
if (!FIRCLSFileInitWithPath(&file, storage->compactedPath, true)) {
FIRCLSSDKLog("Error: Unable to open compacted k-v file\n");
return;
}
uint32_t maxCount = storage->maxCount;
if ([finalKVs count] > maxCount) {
// We need to remove keys, to avoid going over the max.
// This is just about the worst way to go about doing this. There are lots of smarter ways,
// but it's very uncommon to go down this path.
NSArray *keys = [finalKVs allKeys];
FIRCLSSDKLogInfo("Truncating %d keys from KV set, which is above max %d\n",
(uint32_t)(finalKVs.count - maxCount), maxCount);
finalKVs =
[finalKVs dictionaryWithValuesForKeys:[keys subarrayWithRange:NSMakeRange(0, maxCount)]];
}
FIRCLSUserLoggingWriteKVEntriesToFile(finalKVs, false, &file);
FIRCLSFileClose(&file);
if (unlink(storage->incrementalPath) != 0) {
FIRCLSSDKLog("Error: Unable to remove incremental KV store after compaction %s\n",
strerror(errno));
}
}
void FIRCLSUserLoggingRecordKeyValue(NSString *key,
id value,
FIRCLSUserLoggingKVStorage *storage,
uint32_t *counter) {
if (!FIRCLSIsValidPointer(key)) {
FIRCLSSDKLogWarn("User provided bad key\n");
return;
}
NSDictionary *keysAndValues = @{key : (value ?: [NSNull null])};
FIRCLSUserLoggingRecordKeysAndValues(keysAndValues, storage, counter);
}
void FIRCLSUserLoggingRecordKeysAndValues(NSDictionary *keysAndValues,
FIRCLSUserLoggingKVStorage *storage,
uint32_t *counter) {
if (!FIRCLSContextIsInitialized()) {
return;
}
if (keysAndValues.count == 0) {
FIRCLSSDKLogWarn("User provided empty key/value dictionary\n");
return;
}
if (!FIRCLSIsValidPointer(keysAndValues)) {
FIRCLSSDKLogWarn("User provided bad key/value dictionary\n");
return;
}
NSMutableDictionary *sanitizedKeysAndValues = [keysAndValues mutableCopy];
BOOL containsNullValue = NO;
for (NSString *key in keysAndValues) {
if (!FIRCLSIsValidPointer(key)) {
FIRCLSSDKLogWarn("User provided bad key\n");
return;
}
id value = keysAndValues[key];
// ensure that any invalid pointer is actually set to nil
if (!FIRCLSIsValidPointer(value) && value != nil) {
FIRCLSSDKLogWarn("Bad value pointer being clamped to nil\n");
sanitizedKeysAndValues[key] = [NSNull null];
}
if ([value respondsToSelector:@selector(description)] && ![value isEqual:[NSNull null]]) {
sanitizedKeysAndValues[key] = [value description];
} else {
// passing nil will result in a JSON null being written, which is deserialized as [NSNull
// null], signaling to remove the key during compaction
sanitizedKeysAndValues[key] = [NSNull null];
containsNullValue = YES;
}
}
dispatch_sync(FIRCLSGetLoggingQueue(), ^{
FIRCLSUserLoggingWriteKeysAndValues(sanitizedKeysAndValues, storage, counter,
containsNullValue);
});
}
static void FIRCLSUserLoggingWriteKeysAndValues(NSDictionary *keysAndValues,
FIRCLSUserLoggingKVStorage *storage,
uint32_t *counter,
BOOL containsNullValue) {
FIRCLSFile file;
if (!FIRCLSIsValidPointer(storage) || !FIRCLSIsValidPointer(counter)) {
FIRCLSSDKLogError("Bad parameters\n");
return;
}
if (!FIRCLSFileInitWithPath(&file, storage->incrementalPath, true)) {
FIRCLSSDKLogError("Unable to open k-v file\n");
return;
}
FIRCLSUserLoggingWriteKVEntriesToFile(keysAndValues, true, &file);
FIRCLSFileClose(&file);
*counter += keysAndValues.count;
if (*counter >= storage->maxIncrementalCount || containsNullValue) {
dispatch_async(FIRCLSGetLoggingQueue(), ^{
FIRCLSUserLoggingCompactKVEntries(storage);
*counter = 0;
});
}
}
NSArray *FIRCLSUserLoggingStoredKeyValues(const char *path) {
if (!FIRCLSContextIsInitialized()) {
return nil;
}
return FIRCLSFileReadSections(path, true, ^NSObject *(id obj) {
return [obj objectForKey:@"kv"];
});
}
#pragma mark - NSError Logging
static void FIRCLSUserLoggingRecordErrorUserInfo(FIRCLSFile *file,
const char *fileKey,
NSDictionary<NSString *, id> *userInfo) {
if ([userInfo count] == 0) {
return;
}
FIRCLSFileWriteHashKey(file, fileKey);
FIRCLSFileWriteArrayStart(file);
for (id key in userInfo) {
id value = [userInfo objectForKey:key];
if (![value respondsToSelector:@selector(description)]) {
continue;
}
FIRCLSFileWriteArrayStart(file);
FIRCLSFileWriteArrayEntryHexEncodedString(file, [key UTF8String]);
FIRCLSFileWriteArrayEntryHexEncodedString(file, [[value description] UTF8String]);
FIRCLSFileWriteArrayEnd(file);
}
FIRCLSFileWriteArrayEnd(file);
}
static void FIRCLSUserLoggingWriteError(FIRCLSFile *file,
NSError *error,
NSDictionary<NSString *, id> *additionalUserInfo,
NSArray *addresses,
uint64_t timestamp,
NSString *rolloutsInfoJSON) {
FIRCLSFileWriteSectionStart(file, "error");
FIRCLSFileWriteHashStart(file);
FIRCLSFileWriteHashEntryHexEncodedString(file, "domain", [[error domain] UTF8String]);
FIRCLSFileWriteHashEntryInt64(file, "code", [error code]);
FIRCLSFileWriteHashEntryUint64(file, "time", timestamp);
// addresses
FIRCLSFileWriteHashKey(file, "stacktrace");
FIRCLSFileWriteArrayStart(file);
for (NSNumber *address in addresses) {
FIRCLSFileWriteArrayEntryUint64(file, [address unsignedLongLongValue]);
}
FIRCLSFileWriteArrayEnd(file);
// user-info
FIRCLSUserLoggingRecordErrorUserInfo(file, "info", [error userInfo]);
FIRCLSUserLoggingRecordErrorUserInfo(file, "extra_info", additionalUserInfo);
// rollouts
if (rolloutsInfoJSON) {
FIRCLSFileWriteHashKey(file, "rollouts");
FIRCLSFileWriteStringUnquoted(file, [rolloutsInfoJSON UTF8String]);
FIRCLSFileWriteHashEnd(file);
}
FIRCLSFileWriteHashEnd(file);
FIRCLSFileWriteSectionEnd(file);
}
void FIRCLSUserLoggingRecordError(NSError *error,
NSDictionary<NSString *, id> *additionalUserInfo,
NSString *rolloutsInfoJSON) {
if (!error) {
return;
}
if (!FIRCLSContextIsInitialized()) {
return;
}
// record the stacktrace and timestamp here, so we
// are as close as possible to the user's log statement
NSArray *addresses = [NSThread callStackReturnAddresses];
uint64_t timestamp = time(NULL);
FIRCLSUserLoggingWriteAndCheckABFiles(
&_firclsContext.readonly->logging.errorStorage,
&_firclsContext.writable->logging.activeErrorLogPath, ^(FIRCLSFile *file) {
FIRCLSUserLoggingWriteError(file, error, additionalUserInfo, addresses, timestamp,
rolloutsInfoJSON);
});
}
#pragma mark - CLSLog Support
void FIRCLSLog(NSString *format, ...) {
// If the format is nil do nothing just like NSLog.
if (!format) {
return;
}
va_list args;
va_start(args, format);
NSString *msg = [[NSString alloc] initWithFormat:format arguments:args];
va_end(args);
FIRCLSUserLoggingABStorage *currentStorage = &_firclsContext.readonly->logging.logStorage;
const char **activePath = &_firclsContext.writable->logging.activeUserLogPath;
FIRCLSLogInternal(currentStorage, activePath, msg);
}
void FIRCLSLogToStorage(FIRCLSUserLoggingABStorage *storage,
const char **activePath,
NSString *format,
...) {
// If the format is nil do nothing just like NSLog.
if (!format) {
return;
}
va_list args;
va_start(args, format);
NSString *msg = [[NSString alloc] initWithFormat:format arguments:args];
va_end(args);
FIRCLSLogInternal(storage, activePath, msg);
}
#pragma mark - Properties
uint32_t FIRCLSUserLoggingMaxLogSize(void) {
// don't forget that the message encoding overhead is 2x, and we
// wrap everything in a json structure with time. So, there is
// quite a penalty
uint32_t size = 1024 * 64;
return size * 2;
}
uint32_t FIRCLSUserLoggingMaxErrorSize(void) {
return FIRCLSUserLoggingMaxLogSize();
}
#pragma mark - AB Logging
void FIRCLSUserLoggingCheckAndSwapABFiles(FIRCLSUserLoggingABStorage *storage,
const char **activePath,
off_t fileSize) {
if (!activePath || !storage) {
return;
}
if (!*activePath) {
return;
}
if (storage->restrictBySize) {
if (fileSize <= storage->maxSize) {
return;
}
} else {
if (!FIRCLSIsValidPointer(storage->entryCount)) {
FIRCLSSDKLogError("Error: storage has invalid pointer, but is restricted by entry count\n");
return;
}
if (*storage->entryCount < storage->maxEntries) {
return;
}
// Here we have rolled over, so we have to reset our counter.
*storage->entryCount = 0;
}
// if it is too big:
// - reset the other log
// - make it active
const char *otherPath = NULL;
if (*activePath == storage->aPath) {
otherPath = storage->bPath;
} else {
// take this path if the pointer is invalid as well, to reset
otherPath = storage->aPath;
}
// guard here against path being nil or empty
NSString *pathString = [NSString stringWithUTF8String:otherPath];
if ([pathString length] > 0) {
// ignore the error, because there is nothing we can do to recover here, and its likely
// any failures would be intermittent
[[NSFileManager defaultManager] removeItemAtPath:pathString error:nil];
}
@synchronized(FIRCLSSynchronizedPathKey) {
*activePath = otherPath;
}
}
void FIRCLSUserLoggingWriteAndCheckABFiles(FIRCLSUserLoggingABStorage *storage,
const char **activePath,
void (^openedFileBlock)(FIRCLSFile *file)) {
if (!storage || !activePath || !openedFileBlock) {
return;
}
@synchronized(FIRCLSSynchronizedPathKey) {
if (!*activePath) {
return;
}
}
if (storage->restrictBySize) {
if (storage->maxSize == 0) {
return;
}
} else {
if (storage->maxEntries == 0) {
return;
}
}
dispatch_sync(FIRCLSGetLoggingQueue(), ^{
FIRCLSFile file;
if (!FIRCLSFileInitWithPath(&file, *activePath, true)) {
FIRCLSSDKLog("Unable to open log file\n");
return;
}
openedFileBlock(&file);
off_t fileSize = 0;
FIRCLSFileCloseWithOffset(&file, &fileSize);
// increment the count before calling FIRCLSUserLoggingCheckAndSwapABFiles, so the value
// reflects the actual amount of stuff written
if (!storage->restrictBySize && FIRCLSIsValidPointer(storage->entryCount)) {
*storage->entryCount += 1;
}
dispatch_async(FIRCLSGetLoggingQueue(), ^{
FIRCLSUserLoggingCheckAndSwapABFiles(storage, activePath, fileSize);
});
});
}
void FIRCLSLogInternalWrite(FIRCLSFile *file, NSString *message, uint64_t time) {
FIRCLSFileWriteSectionStart(file, "log");
FIRCLSFileWriteHashStart(file);
FIRCLSFileWriteHashEntryHexEncodedString(file, "msg", [message UTF8String]);
FIRCLSFileWriteHashEntryUint64(file, "time", time);
FIRCLSFileWriteHashEnd(file);
FIRCLSFileWriteSectionEnd(file);
}
void FIRCLSLogInternal(FIRCLSUserLoggingABStorage *storage,
const char **activePath,
NSString *message) {
if (!message) {
return;
}
if (!FIRCLSContextIsInitialized()) {
FIRCLSWarningLog(@"WARNING: FIRCLSLog has been used before (or concurrently with) "
@"Crashlytics initialization and cannot be recorded. The message was: \n%@",
message);
return;
}
struct timeval te;
NSUInteger messageLength = [message length];
int maxLogSize = storage->maxSize;
if (messageLength > maxLogSize) {
FIRCLSWarningLog(
@"WARNING: Attempted to write %zd bytes, but %d is the maximum size of the log. "
@"Truncating to %d bytes.\n",
messageLength, maxLogSize, maxLogSize);
message = [message substringToIndex:maxLogSize];
}
// unable to get time - abort
if (gettimeofday(&te, NULL) != 0) {
return;
}
const uint64_t time = te.tv_sec * 1000LL + te.tv_usec / 1000;
FIRCLSUserLoggingWriteAndCheckABFiles(storage, activePath, ^(FIRCLSFile *file) {
FIRCLSLogInternalWrite(file, message, time);
});
}

View File

@ -0,0 +1,53 @@
// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@class FIRCLSSettings;
@protocol FIRAnalyticsInterop;
@protocol FIRAnalyticsInteropListener;
/*
* Registers a listener for Analytics events in Crashlytics
* logs (aka. breadcrumbs), and sends events to the
* Analytics SDK for Crash Free Users.
*/
@interface FIRCLSAnalyticsManager : NSObject
- (instancetype)initWithAnalytics:(nullable id<FIRAnalyticsInterop>)analytics;
- (instancetype)init NS_UNAVAILABLE;
+ (instancetype)new NS_UNAVAILABLE;
/*
* Starts listening for Analytics events for Breadcrumbs.
*/
- (void)registerAnalyticsListener;
/*
* Logs a Crashlytics crash session to Firebase Analytics for Crash Free Users.
* @param crashTimeStamp The time stamp of the crash to be logged.
*/
+ (void)logCrashWithTimeStamp:(NSTimeInterval)crashTimeStamp
toAnalytics:(id<FIRAnalyticsInterop>)analytics;
/*
* Public for testing.
*/
NSString *FIRCLSFIRAEventDictionaryToJSON(NSDictionary *eventAsDictionary);
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,135 @@
// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import "Crashlytics/Crashlytics/Controllers/FIRCLSAnalyticsManager.h"
#import "Crashlytics/Crashlytics/Components/FIRCLSUserLogging.h"
#import "Crashlytics/Crashlytics/Helpers/FIRCLSInternalLogging.h"
#import "Interop/Analytics/Public/FIRAnalyticsInterop.h"
#import "Interop/Analytics/Public/FIRAnalyticsInteropListener.h"
static NSString *FIRCLSFirebaseAnalyticsEventLogFormat = @"$A$:%@";
// Origin for events and user properties generated by Crashlytics.
static NSString *const kFIREventOriginCrash = @"clx";
// App exception event name.
static NSString *const kFIREventAppException = @"_ae";
// Timestamp key for the event payload.
static NSString *const kFIRParameterTimestamp = @"timestamp";
// Fatal key for the event payload.
static NSString *const kFIRParameterFatal = @"fatal";
FOUNDATION_STATIC_INLINE NSNumber *timeIntervalInMillis(NSTimeInterval timeInterval) {
return @(llrint(timeInterval * 1000.0));
}
@interface FIRCLSAnalyticsManager () <FIRAnalyticsInteropListener>
@property(nonatomic, strong) id<FIRAnalyticsInterop> analytics;
@property(nonatomic, assign) BOOL registeredAnalyticsEventListener;
@end
@implementation FIRCLSAnalyticsManager
- (instancetype)initWithAnalytics:(nullable id<FIRAnalyticsInterop>)analytics {
self = [super init];
if (!self) {
return nil;
}
_analytics = analytics;
return self;
}
- (void)registerAnalyticsListener {
if (self.registeredAnalyticsEventListener) {
return;
}
if (self.analytics == nil) {
FIRCLSDeveloperLog(@"Crashlytics:Crash:Reports:Event",
"Firebase Analytics SDK not detected. Crash-free statistics and "
"breadcrumbs will not be reported");
return;
}
[self.analytics registerAnalyticsListener:self withOrigin:kFIREventOriginCrash];
FIRCLSDeveloperLog(@"Crashlytics:Crash:Reports:Event",
"Registered Firebase Analytics event listener to receive breadcrumb logs");
self.registeredAnalyticsEventListener = YES;
}
- (void)messageTriggered:(NSString *)name parameters:(NSDictionary *)parameters {
NSDictionary *event = @{
@"name" : name,
@"parameters" : parameters,
};
NSString *json = FIRCLSFIRAEventDictionaryToJSON(event);
if (json != nil) {
FIRCLSLog(FIRCLSFirebaseAnalyticsEventLogFormat, json);
}
}
+ (void)logCrashWithTimeStamp:(NSTimeInterval)crashTimeStamp
toAnalytics:(id<FIRAnalyticsInterop>)analytics {
if (analytics == nil) {
return;
}
FIRCLSDeveloperLog(@"Crashlytics:Crash:Reports:Event",
"Sending app_exception event to Firebase Analytics for crash-free statistics");
NSDictionary *params = @{
kFIRParameterTimestamp : timeIntervalInMillis(crashTimeStamp),
kFIRParameterFatal : @(INT64_C(1))
};
[analytics logEventWithOrigin:kFIREventOriginCrash name:kFIREventAppException parameters:params];
}
NSString *FIRCLSFIRAEventDictionaryToJSON(NSDictionary *eventAsDictionary) {
NSError *error = nil;
if (eventAsDictionary == nil) {
return nil;
}
if (![NSJSONSerialization isValidJSONObject:eventAsDictionary]) {
FIRCLSSDKLog("Firebase Analytics event is not valid JSON");
return nil;
}
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:eventAsDictionary
options:0
error:&error];
if (error == nil) {
NSString *json = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
return json;
} else {
FIRCLSSDKLog("Unable to convert Firebase Analytics event to json");
return nil;
}
}
@end

View File

@ -0,0 +1,43 @@
//
// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import <Foundation/Foundation.h>
#import "Crashlytics/Crashlytics/Models/FIRCLSFileManager.h"
#import "Crashlytics/Crashlytics/Models/FIRCLSInternalReport.h"
#import "Crashlytics/Crashlytics/Models/FIRCLSSettings.h"
NS_ASSUME_NONNULL_BEGIN
///
/// The ContextManager determines when to build the context object,
/// and write its metadata. It was created because the FIRCLSContext
/// is interacted with via functions, which makes it hard to include in tests.
/// In addition, we this class is responsible for re-writing the Metadata object
/// when the App Quality Session ID changes.
///
@interface FIRCLSContextManager : NSObject
/// This should be set immediately when the FirebaseSessions SDK generates
/// a new Session ID.
@property(nonatomic, copy) NSString *appQualitySessionId;
- (BOOL)setupContextWithReport:(FIRCLSInternalReport *)report
settings:(FIRCLSSettings *)settings
fileManager:(FIRCLSFileManager *)fileManager;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,78 @@
//
// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import "Crashlytics/Crashlytics/Controllers/FIRCLSContextManager.h"
#import "Crashlytics/Crashlytics/Components/FIRCLSContext.h"
@interface FIRCLSContextManager ()
@property(nonatomic, assign) BOOL hasInitializedContext;
@property(nonatomic, strong) FIRCLSInternalReport *report;
@property(nonatomic, strong) FIRCLSSettings *settings;
@property(nonatomic, strong) FIRCLSFileManager *fileManager;
@end
@implementation FIRCLSContextManager
- (instancetype)init {
self = [super init];
if (!self) {
return self;
}
_appQualitySessionId = @"";
return self;
}
- (BOOL)setupContextWithReport:(FIRCLSInternalReport *)report
settings:(FIRCLSSettings *)settings
fileManager:(FIRCLSFileManager *)fileManager {
_report = report;
_settings = settings;
_fileManager = fileManager;
_hasInitializedContext = true;
FIRCLSContextInitData *initDataObj = self.buildInitData;
return FIRCLSContextInitialize(initDataObj, self.fileManager);
}
- (void)setAppQualitySessionId:(NSString *)appQualitySessionId {
_appQualitySessionId = appQualitySessionId;
// This may be called before the context is originally initialized. In that case
// skip the write because it will be written as soon as the context is initialized.
// On future Session ID updates, this will be true and the context metadata will be
// rewritten.
if (!self.hasInitializedContext) {
return;
}
FIRCLSContextInitData *initDataObj = self.buildInitData;
if (!FIRCLSContextRecordMetadata(self.report.path, initDataObj)) {
FIRCLSErrorLog(@"Failed to write context file while updating App Quality Session ID");
}
}
- (FIRCLSContextInitData *)buildInitData {
return FIRCLSContextBuildInitData(self.report, self.settings, self.fileManager,
self.appQualitySessionId);
}
@end

View File

@ -0,0 +1,83 @@
// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@class FIRCLSManagerData;
@class FIRCLSReportUploader;
@class FIRCLSDataCollectionToken;
@class FIRCrashlyticsReport;
FOUNDATION_EXPORT NSUInteger const FIRCLSMaxUnsentReports;
@interface FIRCLSExistingReportManager : NSObject
/**
* Returns the number of unsent reports on the device, ignoring empty reports in
* the active folder, and ignoring any reports in "processing" or "prepared".
*
* In the past, this would count reports in the processed or prepared
* folders. This has been changed because reports in those paths have already
* been cleared for upload, so there isn't any point in asking for permission
* or possibly spamming end-users if a report gets stuck.
*
* The tricky part is, customers will NOT be alerted in `checkForUnsentReports`
* for reports in these paths, but when they choose `sendUnsentReports` / enable data
* collection, reports in those directories will be re-managed. This should be ok and
* just an edge case because reports should only be in processing or prepared for a split second as
* they do on-device symbolication and get converted into a GDTEvent. After a report is handed off
* to GoogleDataTransport, it is uploaded regardless of Crashlytics data collection.
*/
@property(nonatomic, readonly) NSUInteger unsentReportsCount;
/**
* This value needs to stay in sync with `numUnsentReports`, so if there is > 0 `numUnsentReports`,
* `newestUnsentReport` needs to return a value. Otherwise it needs to return nil.
*
* `FIRCLSContext` needs to be initialized before the `CrashlyticsReport` is instantiated.
*/
@property(nonatomic, readonly) FIRCrashlyticsReport *_Nullable newestUnsentReport;
- (instancetype)initWithManagerData:(FIRCLSManagerData *)managerData
reportUploader:(FIRCLSReportUploader *)reportUploader;
- (instancetype)init NS_UNAVAILABLE;
+ (instancetype)new NS_UNAVAILABLE;
/**
* This is important to call once, early in startup, before the
* new report for this run of the app has been created. Any
* reports in `ExistingReportManager` will be uploaded or deleted
* and we don't want to do that for the current run of the app.
*
* If there are over MAX_UNSENT_REPORTS valid reports, this will delete them.
*
* This methods is slow and should be called only once.
*/
- (void)collectExistingReports;
/**
* This is the side-effect of calling `deleteUnsentReports`, or collect_reports setting
* being false.
*/
- (void)deleteUnsentReports;
- (void)sendUnsentReportsWithToken:(FIRCLSDataCollectionToken *)dataCollectionToken
asUrgent:(BOOL)urgent;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,274 @@
// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import "Crashlytics/Crashlytics/Controllers/FIRCLSExistingReportManager.h"
#import "Crashlytics/Crashlytics/Controllers/FIRCLSManagerData.h"
#import "Crashlytics/Crashlytics/Controllers/FIRCLSReportUploader.h"
#import "Crashlytics/Crashlytics/DataCollection/FIRCLSDataCollectionArbiter.h"
#import "Crashlytics/Crashlytics/DataCollection/FIRCLSDataCollectionToken.h"
#import "Crashlytics/Crashlytics/Helpers/FIRCLSLogger.h"
#import "Crashlytics/Crashlytics/Models/FIRCLSFileManager.h"
#import "Crashlytics/Crashlytics/Models/FIRCLSInternalReport.h"
#import "Crashlytics/Crashlytics/Models/FIRCLSSettings.h"
#import "Crashlytics/Crashlytics/Private/FIRCLSOnDemandModel_Private.h"
#import "Crashlytics/Crashlytics/Private/FIRCrashlyticsReport_Private.h"
#import "Crashlytics/Crashlytics/Public/FirebaseCrashlytics/FIRCrashlyticsReport.h"
// This value should stay in sync with the Android SDK
NSUInteger const FIRCLSMaxUnsentReports = 4;
@interface FIRCLSExistingReportManager ()
@property(nonatomic, strong) FIRCLSFileManager *fileManager;
@property(nonatomic, strong) FIRCLSReportUploader *reportUploader;
@property(nonatomic, strong) NSOperationQueue *operationQueue;
@property(nonatomic, strong) FIRCLSSettings *settings;
@property(nonatomic, strong) FIRCLSDataCollectionArbiter *dataArbiter;
@property(nonatomic, strong) FIRCLSOnDemandModel *onDemandModel;
// This list of active reports excludes the brand new active report that will be created this run of
// the app.
@property(nonatomic, strong) NSArray *existingUnemptyActiveReportPaths;
@property(nonatomic, strong) NSArray *processingReportPaths;
@property(nonatomic, strong) NSArray *preparedReportPaths;
@property(nonatomic, strong) FIRCLSInternalReport *newestInternalReport;
@end
@implementation FIRCLSExistingReportManager
- (instancetype)initWithManagerData:(FIRCLSManagerData *)managerData
reportUploader:(FIRCLSReportUploader *)reportUploader {
self = [super init];
if (!self) {
return nil;
}
_fileManager = managerData.fileManager;
_settings = managerData.settings;
_operationQueue = managerData.operationQueue;
_dataArbiter = managerData.dataArbiter;
_reportUploader = reportUploader;
_onDemandModel = managerData.onDemandModel;
return self;
}
NSInteger compareNewer(FIRCLSInternalReport *reportA,
FIRCLSInternalReport *reportB,
void *context) {
// Compare naturally sorts with oldest first, so swap A and B
return [reportB.dateCreated compare:reportA.dateCreated];
}
- (void)collectExistingReports {
self.existingUnemptyActiveReportPaths =
[self getUnsentActiveReportsAndDeleteEmptyOrOld:self.fileManager.activePathContents];
self.processingReportPaths = self.fileManager.processingPathContents;
self.preparedReportPaths = self.fileManager.preparedPathContents;
}
- (FIRCrashlyticsReport *)newestUnsentReport {
if (self.unsentReportsCount <= 0) {
return nil;
}
return [[FIRCrashlyticsReport alloc] initWithInternalReport:self.newestInternalReport];
}
- (NSUInteger)unsentReportsCount {
// There are nuances about why we only count active reports.
// See the header comment for more information.
return self.existingUnemptyActiveReportPaths.count;
}
/*
* This has the side effect of deleting any reports over the max, starting with oldest reports.
*/
- (NSArray<NSString *> *)getUnsentActiveReportsAndDeleteEmptyOrOld:(NSArray *)reportPaths {
NSMutableArray<FIRCLSInternalReport *> *validReports = [NSMutableArray array];
NSMutableArray<FIRCLSInternalReport *> *reports = [NSMutableArray array];
for (NSString *path in reportPaths) {
FIRCLSInternalReport *_Nullable report = [FIRCLSInternalReport reportWithPath:path];
if (!report) {
continue;
}
[reports addObject:report];
}
if (reports.count == 0) {
return @[];
}
[reports sortUsingFunction:compareNewer context:nil];
NSString *newestReportPath = [reports firstObject].path;
// If there was a MetricKit event recorded on the last run of the app, add it to the newest
// report.
if (self.settings.metricKitCollectionEnabled &&
[self.fileManager metricKitDiagnosticFileExists]) {
[self.fileManager createEmptyMetricKitFile:newestReportPath];
}
for (FIRCLSInternalReport *report in reports) {
// Delete reports without any crashes or non-fatals
if (![report hasAnyEvents]) {
[self.operationQueue addOperationWithBlock:^{
[self.fileManager removeItemAtPath:report.path];
}];
continue;
}
[validReports addObject:report];
}
if (validReports.count == 0) {
return @[];
}
// Sort with the newest at the end
[validReports sortUsingFunction:compareNewer context:nil];
// Set our report for updating in checkAndUpdateUnsentReports
self.newestInternalReport = [validReports firstObject];
// Delete any reports above the limit, starting with the oldest
// which should be at the start of the array.
if (validReports.count > FIRCLSMaxUnsentReports) {
NSUInteger deletingCount = validReports.count - FIRCLSMaxUnsentReports;
FIRCLSInfoLog(
@"Automatic data collection is disabled. Deleting %lu unsent reports over the limit of %lu "
@"to prevent disk space from "
@"filling up. To take action on these reports, call send/deleteUnsentReports. To turn on "
@"automatic data collection, call setCrashlyticsCollectionEnabled with true",
deletingCount, FIRCLSMaxUnsentReports);
}
// Not that validReports is sorted, delete any reports at indices > MAX_UNSENT_REPORTS, and
// collect the rest of the reports to return.
NSMutableArray<NSString *> *validReportPaths = [NSMutableArray array];
for (int i = 0; i < validReports.count; i++) {
if (i >= FIRCLSMaxUnsentReports) {
[self.operationQueue addOperationWithBlock:^{
NSString *path = [[validReports objectAtIndex:i] path];
[self.fileManager removeItemAtPath:path];
}];
} else {
[validReportPaths addObject:[[validReports objectAtIndex:i] path]];
}
}
return validReportPaths;
}
- (void)sendUnsentReportsWithToken:(FIRCLSDataCollectionToken *)dataCollectionToken
asUrgent:(BOOL)urgent {
for (NSString *path in self.existingUnemptyActiveReportPaths) {
[self processExistingActiveReportPath:path
dataCollectionToken:dataCollectionToken
asUrgent:urgent];
}
for (NSString *path in self.onDemandModel.storedActiveReportPaths) {
[self processExistingActiveReportPath:path
dataCollectionToken:dataCollectionToken
asUrgent:urgent];
}
[self.onDemandModel.storedActiveReportPaths removeAllObjects];
// deal with stuff in processing more carefully - do not process again
[self.operationQueue addOperationWithBlock:^{
for (NSString *path in self.processingReportPaths) {
FIRCLSInternalReport *report = [FIRCLSInternalReport reportWithPath:path];
[self.reportUploader prepareAndSubmitReport:report
dataCollectionToken:dataCollectionToken
asUrgent:NO
withProcessing:NO];
}
}];
// Because this could happen quite a bit after the initial set of files was
// captured, some could be completed (deleted). So, just double-check to make sure
// the file still exists.
[self.operationQueue addOperationWithBlock:^{
for (NSString *path in self.preparedReportPaths) {
if (![self.fileManager fileExistsAtPath:path]) {
continue;
}
[self.reportUploader uploadPackagedReportAtPath:path
dataCollectionToken:dataCollectionToken
asUrgent:NO];
}
}];
}
- (void)processExistingActiveReportPath:(NSString *)path
dataCollectionToken:(FIRCLSDataCollectionToken *)dataCollectionToken
asUrgent:(BOOL)urgent {
FIRCLSInternalReport *report = [FIRCLSInternalReport reportWithPath:path];
// TODO: hasAnyEvents should really be called on the background queue.
if (![report hasAnyEvents]) {
[self.operationQueue addOperationWithBlock:^{
[self.fileManager removeItemAtPath:path];
}];
return;
}
if (urgent && [dataCollectionToken isValid]) {
// We can proceed without the delegate.
[self.reportUploader prepareAndSubmitReport:report
dataCollectionToken:dataCollectionToken
asUrgent:urgent
withProcessing:YES];
return;
}
[self.operationQueue addOperationWithBlock:^{
[self.reportUploader prepareAndSubmitReport:report
dataCollectionToken:dataCollectionToken
asUrgent:NO
withProcessing:YES];
}];
}
- (void)deleteUnsentReports {
NSArray<NSString *> *reportPaths = @[];
reportPaths = [reportPaths arrayByAddingObjectsFromArray:self.existingUnemptyActiveReportPaths];
reportPaths = [reportPaths arrayByAddingObjectsFromArray:self.processingReportPaths];
reportPaths = [reportPaths arrayByAddingObjectsFromArray:self.preparedReportPaths];
[self.operationQueue addOperationWithBlock:^{
for (NSString *path in reportPaths) {
[self.fileManager removeItemAtPath:path];
}
}];
}
- (void)handleOnDemandReportUpload:(NSString *)path
dataCollectionToken:(FIRCLSDataCollectionToken *)dataCollectionToken
asUrgent:(BOOL)urgent {
dispatch_async(self.operationQueue.underlyingQueue, ^{
[self processExistingActiveReportPath:path
dataCollectionToken:dataCollectionToken
asUrgent:YES];
});
}
@end

View File

@ -0,0 +1,93 @@
// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@class FIRCLSFileManager;
@class FIRInstallations;
@class FIRCLSDataCollectionArbiter;
@class FIRCLSApplicationIdentifierModel;
@class FIRCLSInstallIdentifierModel;
@class FIRCLSExecutionIdentifierModel;
@class FIRCLSOnDemandModel;
@class FIRCLSSettings;
@class FIRCLSLaunchMarkerModel;
@class FIRCLSContextManager;
@class GDTCORTransport;
@protocol FIRAnalyticsInterop;
/*
* FIRCLSManagerData's purpose is to simplify the adding and removing of
* dependencies from each of the Manager classes so that it's easier
* to inject mock classes during testing. A lot of the Manager classes
* share these dependencies, but don't use all of them.
*
* If you plan on adding interdependencies between Managers, do not add a pointer
* to the dependency here. Instead add them as a new value to the constructor of
* the Manager, and construct them in FirebaseCrashlytics. This data structure should
* be for Models and other SDKs / Interops Crashlytics depends on.
*/
@interface FIRCLSManagerData : NSObject
- (instancetype)initWithGoogleAppID:(NSString *)googleAppID
googleTransport:(GDTCORTransport *)googleTransport
installations:(FIRInstallations *)installations
analytics:(nullable id<FIRAnalyticsInterop>)analytics
fileManager:(FIRCLSFileManager *)fileManager
dataArbiter:(FIRCLSDataCollectionArbiter *)dataArbiter
settings:(FIRCLSSettings *)settings
onDemandModel:(FIRCLSOnDemandModel *)onDemandModel NS_DESIGNATED_INITIALIZER;
- (instancetype)init NS_UNAVAILABLE;
+ (instancetype)new NS_UNAVAILABLE;
@property(nonatomic, readonly) NSString *googleAppID;
@property(nonatomic, strong) GDTCORTransport *googleTransport;
@property(nonatomic, strong) FIRInstallations *installations;
@property(nonatomic, strong) id<FIRAnalyticsInterop> analytics;
@property(nonatomic, strong) FIRCLSFileManager *fileManager;
@property(nonatomic, strong) FIRCLSDataCollectionArbiter *dataArbiter;
// Uniquely identifies a build / binary of the app
@property(nonatomic, strong) FIRCLSApplicationIdentifierModel *appIDModel;
// Uniquely identifies an install of the app
@property(nonatomic, strong) FIRCLSInstallIdentifierModel *installIDModel;
// Uniquely identifies a run of the app
@property(nonatomic, strong) FIRCLSExecutionIdentifierModel *executionIDModel;
// Handles storing and uploading of on-demand events
@property(nonatomic, readonly) FIRCLSOnDemandModel *onDemandModel;
// Settings fetched from the server
@property(nonatomic, strong) FIRCLSSettings *settings;
// Sets up the Context and writes Metadata files to the crash report
@property(nonatomic, strong) FIRCLSContextManager *contextManager;
// These queues function together as a single startup queue
@property(nonatomic, strong) NSOperationQueue *operationQueue;
@property(nonatomic, strong) dispatch_queue_t dispatchQueue;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,65 @@
// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import "Crashlytics/Crashlytics/Controllers/FIRCLSManagerData.h"
#import "Crashlytics/Crashlytics/Components/FIRCLSApplication.h"
#import "Crashlytics/Crashlytics/Controllers/FIRCLSContextManager.h"
#import "Crashlytics/Crashlytics/Models/FIRCLSExecutionIdentifierModel.h"
#import "Crashlytics/Crashlytics/Models/FIRCLSInstallIdentifierModel.h"
#import "Crashlytics/Crashlytics/Models/FIRCLSSettings.h"
#import "Crashlytics/Crashlytics/Private/FIRCLSOnDemandModel_Private.h"
#import "Crashlytics/Crashlytics/Settings/Models/FIRCLSApplicationIdentifierModel.h"
@implementation FIRCLSManagerData
- (instancetype)initWithGoogleAppID:(NSString *)googleAppID
googleTransport:(GDTCORTransport *)googleTransport
installations:(FIRInstallations *)installations
analytics:(nullable id<FIRAnalyticsInterop>)analytics
fileManager:(FIRCLSFileManager *)fileManager
dataArbiter:(FIRCLSDataCollectionArbiter *)dataArbiter
settings:(FIRCLSSettings *)settings
onDemandModel:(FIRCLSOnDemandModel *)onDemandModel {
self = [super init];
if (!self) {
return nil;
}
_googleAppID = googleAppID;
_googleTransport = googleTransport;
_installations = installations;
_analytics = analytics;
_fileManager = fileManager;
_dataArbiter = dataArbiter;
_settings = settings;
_onDemandModel = onDemandModel;
_contextManager = [[FIRCLSContextManager alloc] init];
_appIDModel = [[FIRCLSApplicationIdentifierModel alloc] init];
_installIDModel = [[FIRCLSInstallIdentifierModel alloc] initWithInstallations:installations];
_executionIDModel = [[FIRCLSExecutionIdentifierModel alloc] init];
NSString *sdkBundleID = FIRCLSApplicationGetSDKBundleID();
_operationQueue = [NSOperationQueue new];
[_operationQueue setMaxConcurrentOperationCount:1];
[_operationQueue setName:[sdkBundleID stringByAppendingString:@".work-queue"]];
_dispatchQueue = dispatch_queue_create("com.google.firebase.crashlytics.startup", 0);
_operationQueue.underlyingQueue = _dispatchQueue;
return self;
}
@end

View File

@ -0,0 +1,47 @@
// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <Availability.h>
#import <Foundation/Foundation.h>
#import "Crashlytics/Crashlytics/Helpers/FIRCLSDefines.h"
#if CLS_METRICKIT_SUPPORTED
#import <MetricKit/MetricKit.h>
#import "Crashlytics/Crashlytics/Controllers/FIRCLSExistingReportManager.h"
#import "Crashlytics/Crashlytics/Models/FIRCLSFileManager.h"
#if __has_include(<FBLPromises/FBLPromises.h>)
#import <FBLPromises/FBLPromises.h>
#else
#import "FBLPromises.h"
#endif
NS_ASSUME_NONNULL_BEGIN
@interface FIRCLSMetricKitManager : NSObject <MXMetricManagerSubscriber>
- (instancetype)initWithManagerData:(FIRCLSManagerData *)managerData
existingReportManager:(FIRCLSExistingReportManager *)existingReportManager
fileManager:(FIRCLSFileManager *)fileManager;
- (instancetype)init NS_UNAVAILABLE;
- (void)registerMetricKitManager;
- (FBLPromise *)waitForMetricKitDataAvailable;
@end
NS_ASSUME_NONNULL_END
#endif // CLS_METRICKIT_SUPPORTED

View File

@ -0,0 +1,449 @@
// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import <Foundation/Foundation.h>
#import "Crashlytics/Crashlytics/Controllers/FIRCLSMetricKitManager.h"
#if CLS_METRICKIT_SUPPORTED
#import "Crashlytics/Crashlytics/Controllers/FIRCLSManagerData.h"
#include "Crashlytics/Crashlytics/Handlers/FIRCLSMachException.h"
#include "Crashlytics/Crashlytics/Handlers/FIRCLSSignal.h"
#import "Crashlytics/Crashlytics/Helpers/FIRCLSCallStackTree.h"
#import "Crashlytics/Crashlytics/Helpers/FIRCLSFile.h"
#import "Crashlytics/Crashlytics/Helpers/FIRCLSLogger.h"
#import "Crashlytics/Crashlytics/Models/FIRCLSExecutionIdentifierModel.h"
#import "Crashlytics/Crashlytics/Models/FIRCLSInternalReport.h"
#import "Crashlytics/Crashlytics/Public/FirebaseCrashlytics/FIRCrashlytics.h"
#import "Crashlytics/Crashlytics/Public/FirebaseCrashlytics/FIRCrashlyticsReport.h"
@interface FIRCLSMetricKitManager ()
@property FBLPromise *metricKitDataAvailable;
@property FIRCLSExistingReportManager *existingReportManager;
@property FIRCLSFileManager *fileManager;
@property FIRCLSManagerData *managerData;
@property BOOL metricKitPromiseFulfilled;
@end
@implementation FIRCLSMetricKitManager
- (instancetype)initWithManagerData:(FIRCLSManagerData *)managerData
existingReportManager:(FIRCLSExistingReportManager *)existingReportManager
fileManager:(FIRCLSFileManager *)fileManager {
_existingReportManager = existingReportManager;
_fileManager = fileManager;
_managerData = managerData;
_metricKitPromiseFulfilled = NO;
return self;
}
/*
* Registers the MetricKit manager to receive MetricKit reports by adding self to the
* MXMetricManager subscribers. Also initializes the promise that we'll use to ensure that any
* MetricKit report files are included in Crashylytics fatal reports. If no crash occurred on the
* last run of the app, this promise is immediately resolved so that the upload of any nonfatal
* events can proceed.
*/
- (void)registerMetricKitManager API_AVAILABLE(ios(14)) {
[[MXMetricManager sharedManager] addSubscriber:self];
self.metricKitDataAvailable = [FBLPromise pendingPromise];
// If there was no crash on the last run of the app or there's no diagnostic report in the
// MetricKit directory, then we aren't expecting a MetricKit diagnostic report and should resolve
// the promise immediately. If MetricKit captured a fatal event and Crashlytics did not, then
// we'll still process the MetricKit crash but won't upload it until the app restarts again.
if (![self.fileManager didCrashOnPreviousExecution] ||
![self.fileManager metricKitDiagnosticFileExists]) {
@synchronized(self) {
[self fulfillMetricKitPromise];
}
}
// If we haven't resolved this promise within three seconds, resolve it now so that we're not
// waiting indefinitely for MetricKit payloads that won't arrive.
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 3 * NSEC_PER_SEC), self.managerData.dispatchQueue,
^{
@synchronized(self) {
if (!self.metricKitPromiseFulfilled) {
FIRCLSDebugLog(@"Resolving MetricKit promise after three seconds");
[self fulfillMetricKitPromise];
}
}
});
FIRCLSDebugLog(@"Finished registering metrickit manager");
}
/*
* This method receives diagnostic payloads from MetricKit whenever a fatal or nonfatal MetricKit
* event occurs. If a fatal event, this method will be called when the app restarts. Since we're
* including a MetricKit report file in the Crashlytics report to be sent to the backend, we need
* to make sure that we process the payloads and write the included information to file before
* the report is sent up. If this method is called due to a nonfatal event, it will be called
* immediately after the event. Since we send nonfatal events on the next run of the app, we can
* write out the information but won't need to resolve the promise.
*/
- (void)didReceiveDiagnosticPayloads:(NSArray<MXDiagnosticPayload *> *)payloads
API_AVAILABLE(ios(14)) {
BOOL processedFatalPayload = NO;
for (MXDiagnosticPayload *diagnosticPayload in payloads) {
if (!diagnosticPayload) {
continue;
}
BOOL processedPayload = [self processMetricKitPayload:diagnosticPayload
skipCrashEvent:processedFatalPayload];
if (processedPayload && ([diagnosticPayload.crashDiagnostics count] > 0)) {
processedFatalPayload = YES;
}
}
// Once we've processed all the payloads, resolve the promise so that reporting uploading
// continues. If there was not a crash on the previous run of the app, the promise will already
// have been resolved.
@synchronized(self) {
[self fulfillMetricKitPromise];
}
}
// Helper method to write a MetricKit payload's data to file.
- (BOOL)processMetricKitPayload:(MXDiagnosticPayload *)diagnosticPayload
skipCrashEvent:(BOOL)skipCrashEvent API_AVAILABLE(ios(14)) {
BOOL writeFailed = NO;
// Write out each type of diagnostic if it exists in the report
BOOL hasCrash = [diagnosticPayload.crashDiagnostics count] > 0;
BOOL hasHang = [diagnosticPayload.hangDiagnostics count] > 0;
BOOL hasCPUException = [diagnosticPayload.cpuExceptionDiagnostics count] > 0;
BOOL hasDiskWriteException = [diagnosticPayload.diskWriteExceptionDiagnostics count] > 0;
// If there are no diagnostics in the report, return before writing out any files.
if (!hasCrash && !hasHang && !hasCPUException && !hasDiskWriteException) {
return false;
}
// MXDiagnosticPayload have both a timeStampBegin and timeStampEnd. Now that these events are
// real-time, both refer to the same time - record both values anyway.
NSTimeInterval beginSecondsSince1970 = [diagnosticPayload.timeStampBegin timeIntervalSince1970];
NSTimeInterval endSecondsSince1970 = [diagnosticPayload.timeStampEnd timeIntervalSince1970];
// Get file path for the active reports directory.
NSString *activePath = [[self.fileManager activePath] stringByAppendingString:@"/"];
// If there is a crash diagnostic in the payload, then this method was called for a fatal event.
// Also ensure that there is a report from the last run of the app that we can write to.
NSString *metricKitFatalReportFile;
NSString *metricKitNonfatalReportFile;
NSString *newestUnsentReportID =
self.existingReportManager.newestUnsentReport.reportID
? [self.existingReportManager.newestUnsentReport.reportID stringByAppendingString:@"/"]
: nil;
NSString *currentReportID =
[_managerData.executionIDModel.executionID stringByAppendingString:@"/"];
BOOL crashlyticsFatalReported =
([diagnosticPayload.crashDiagnostics count] > 0) && (newestUnsentReportID != nil) &&
([self.fileManager
fileExistsAtPath:[activePath stringByAppendingString:newestUnsentReportID]]);
// Set the MetricKit fatal path appropriately depending on whether we also captured a Crashlytics
// fatal event and whether the diagnostic report came from a fatal or nonfatal event.
if (crashlyticsFatalReported) {
metricKitFatalReportFile = [[activePath stringByAppendingString:newestUnsentReportID]
stringByAppendingString:FIRCLSMetricKitFatalReportFile];
} else {
metricKitFatalReportFile = [[activePath stringByAppendingString:currentReportID]
stringByAppendingString:FIRCLSMetricKitFatalReportFile];
}
metricKitNonfatalReportFile = [[activePath stringByAppendingString:currentReportID]
stringByAppendingString:FIRCLSMetricKitNonfatalReportFile];
if (!metricKitFatalReportFile || !metricKitNonfatalReportFile) {
FIRCLSDebugLog(@"Error finding MetricKit files");
return NO;
}
FIRCLSDebugLog(@"File paths for MetricKit report: %@, %@", metricKitFatalReportFile,
metricKitNonfatalReportFile);
if (hasCrash && ![_fileManager fileExistsAtPath:metricKitFatalReportFile]) {
[_fileManager createFileAtPath:metricKitFatalReportFile contents:nil attributes:nil];
}
if ((hasHang | hasCPUException | hasDiskWriteException) &&
![_fileManager fileExistsAtPath:metricKitNonfatalReportFile]) {
[_fileManager createFileAtPath:metricKitNonfatalReportFile contents:nil attributes:nil];
}
NSFileHandle *nonfatalFile =
[NSFileHandle fileHandleForUpdatingAtPath:metricKitNonfatalReportFile];
if ((hasHang | hasCPUException | hasDiskWriteException) && nonfatalFile == nil) {
FIRCLSDebugLog(@"Unable to create or open nonfatal MetricKit file.");
return false;
}
NSFileHandle *fatalFile = [NSFileHandle fileHandleForUpdatingAtPath:metricKitFatalReportFile];
if (hasCrash && fatalFile == nil) {
FIRCLSDebugLog(@"Unable to create or open fatal MetricKit file.");
return false;
}
NSData *newLineData = [@"\n" dataUsingEncoding:NSUTF8StringEncoding];
// For each diagnostic type, write out a section in the MetricKit report file. This section will
// have subsections for threads, metadata, and event specific metadata.
if (hasCrash && !skipCrashEvent) {
// Write out time information to the MetricKit report file. Time needs to be a value for
// backend serialization, so we write out end_time separately.
MXCrashDiagnostic *crashDiagnostic = [diagnosticPayload.crashDiagnostics objectAtIndex:0];
NSArray *threadArray = [self convertThreadsToArray:crashDiagnostic.callStackTree];
NSDictionary *metadataDict = [self convertMetadataToDictionary:crashDiagnostic.metaData];
NSString *nilString = @"";
// On the backend, we process name, code name, and address into the subtitle of an issue.
// Mach exception name and code should be preferred over signal name and code if available.
const char *signalName = NULL;
const char *signalCodeName = NULL;
FIRCLSSignalNameLookup([crashDiagnostic.signal intValue], 0, &signalName, &signalCodeName);
// signalName is the default name, so should never be NULL
if (signalName == NULL) {
signalName = "UNKNOWN";
}
if (signalCodeName == NULL) {
signalCodeName = "";
}
const char *machExceptionName = NULL;
const char *machExceptionCodeName = NULL;
#if CLS_MACH_EXCEPTION_SUPPORTED
FIRCLSMachExceptionNameLookup(
[crashDiagnostic.exceptionType intValue],
(mach_exception_data_type_t)[crashDiagnostic.exceptionCode intValue], &machExceptionName,
&machExceptionCodeName);
#endif
if (machExceptionCodeName == NULL) {
machExceptionCodeName = "";
}
NSString *name = machExceptionName != NULL ? [NSString stringWithUTF8String:machExceptionName]
: [NSString stringWithUTF8String:signalName];
NSString *codeName = machExceptionName != NULL
? [NSString stringWithUTF8String:machExceptionCodeName]
: [NSString stringWithUTF8String:signalCodeName];
NSDictionary *crashDictionary = @{
@"metric_kit_fatal" : @{
@"time" : [NSNumber numberWithLong:beginSecondsSince1970],
@"end_time" : [NSNumber numberWithLong:endSecondsSince1970],
@"metadata" : metadataDict,
@"termination_reason" :
(crashDiagnostic.terminationReason) ? crashDiagnostic.terminationReason : nilString,
@"virtual_memory_region_info" : (crashDiagnostic.virtualMemoryRegionInfo)
? crashDiagnostic.virtualMemoryRegionInfo
: nilString,
@"exception_type" : crashDiagnostic.exceptionType,
@"exception_code" : crashDiagnostic.exceptionCode,
@"signal" : crashDiagnostic.signal,
@"app_version" : crashDiagnostic.applicationVersion,
@"code_name" : codeName,
@"name" : name
}
};
writeFailed = ![self writeDictionaryToFile:crashDictionary
file:fatalFile
newLineData:newLineData];
writeFailed = writeFailed | ![self writeDictionaryToFile:@{@"threads" : threadArray}
file:fatalFile
newLineData:newLineData];
}
if (hasHang) {
MXHangDiagnostic *hangDiagnostic = [diagnosticPayload.hangDiagnostics objectAtIndex:0];
NSArray *threadArray = [self convertThreadsToArray:hangDiagnostic.callStackTree];
NSDictionary *metadataDict = [self convertMetadataToDictionary:hangDiagnostic.metaData];
NSDictionary *hangDictionary = @{
@"exception" : @{
@"type" : @"metrickit_nonfatal",
@"name" : @"hang_event",
@"time" : [NSNumber numberWithLong:beginSecondsSince1970],
@"end_time" : [NSNumber numberWithLong:endSecondsSince1970],
@"threads" : threadArray,
@"metadata" : metadataDict,
@"hang_duration" : [NSNumber numberWithDouble:[hangDiagnostic.hangDuration doubleValue]],
@"app_version" : hangDiagnostic.applicationVersion
}
};
writeFailed = ![self writeDictionaryToFile:hangDictionary
file:nonfatalFile
newLineData:newLineData];
}
if (hasCPUException) {
MXCPUExceptionDiagnostic *cpuExceptionDiagnostic =
[diagnosticPayload.cpuExceptionDiagnostics objectAtIndex:0];
NSArray *threadArray = [self convertThreadsToArray:cpuExceptionDiagnostic.callStackTree];
NSDictionary *metadataDict = [self convertMetadataToDictionary:cpuExceptionDiagnostic.metaData];
NSDictionary *cpuDictionary = @{
@"exception" : @{
@"type" : @"metrickit_nonfatal",
@"name" : @"cpu_exception_event",
@"time" : [NSNumber numberWithLong:beginSecondsSince1970],
@"end_time" : [NSNumber numberWithLong:endSecondsSince1970],
@"threads" : threadArray,
@"metadata" : metadataDict,
@"total_cpu_time" :
[NSNumber numberWithDouble:[cpuExceptionDiagnostic.totalCPUTime doubleValue]],
@"total_sampled_time" :
[NSNumber numberWithDouble:[cpuExceptionDiagnostic.totalSampledTime doubleValue]],
@"app_version" : cpuExceptionDiagnostic.applicationVersion
}
};
writeFailed = ![self writeDictionaryToFile:cpuDictionary
file:nonfatalFile
newLineData:newLineData];
}
if (hasDiskWriteException) {
MXDiskWriteExceptionDiagnostic *diskWriteExceptionDiagnostic =
[diagnosticPayload.diskWriteExceptionDiagnostics objectAtIndex:0];
NSArray *threadArray = [self convertThreadsToArray:diskWriteExceptionDiagnostic.callStackTree];
NSDictionary *metadataDict =
[self convertMetadataToDictionary:diskWriteExceptionDiagnostic.metaData];
NSDictionary *diskWriteDictionary = @{
@"exception" : @{
@"type" : @"metrickit_nonfatal",
@"name" : @"disk_write_exception_event",
@"time" : [NSNumber numberWithLong:beginSecondsSince1970],
@"end_time" : [NSNumber numberWithLong:endSecondsSince1970],
@"threads" : threadArray,
@"metadata" : metadataDict,
@"app_version" : diskWriteExceptionDiagnostic.applicationVersion,
@"total_writes_caused" :
[NSNumber numberWithDouble:[diskWriteExceptionDiagnostic.totalWritesCaused doubleValue]]
}
};
writeFailed = ![self writeDictionaryToFile:diskWriteDictionary
file:nonfatalFile
newLineData:newLineData];
}
return !writeFailed;
}
/*
* Required for MXMetricManager subscribers. Since we aren't currently collecting any MetricKit
* metrics, this method is left empty.
*/
- (void)didReceiveMetricPayloads:(NSArray<MXMetricPayload *> *)payloads API_AVAILABLE(ios(13)) {
}
- (FBLPromise *)waitForMetricKitDataAvailable {
FBLPromise *result = nil;
@synchronized(self) {
result = self.metricKitDataAvailable;
}
return result;
}
/*
* Helper method to convert threads for a MetricKit fatal diagnostic event to an array of threads.
*/
- (NSArray *)convertThreadsToArray:(MXCallStackTree *)mxCallStackTree API_AVAILABLE(ios(14)) {
FIRCLSCallStackTree *tree = [[FIRCLSCallStackTree alloc] initWithMXCallStackTree:mxCallStackTree];
return [tree getArrayRepresentation];
}
/*
* Helper method to convert threads for a MetricKit nonfatal diagnostic event to an array of frames.
*/
- (NSArray *)convertThreadsToArrayForNonfatal:(MXCallStackTree *)mxCallStackTree
API_AVAILABLE(ios(14)) {
FIRCLSCallStackTree *tree = [[FIRCLSCallStackTree alloc] initWithMXCallStackTree:mxCallStackTree];
return [tree getFramesOfBlamedThread];
}
/*
* Helper method to convert metadata for a MetricKit diagnostic event to a dictionary. MXMetadata
* has a dictionaryRepresentation method but it is deprecated.
*/
- (NSDictionary *)convertMetadataToDictionary:(MXMetaData *)metadata API_AVAILABLE(ios(14)) {
NSError *error = nil;
NSDictionary *metadataDictionary =
[NSJSONSerialization JSONObjectWithData:[metadata JSONRepresentation] options:0 error:&error];
return metadataDictionary;
}
/*
* Helper method to fulfill the metricKitDataAvailable promise and track that it has been fulfilled.
*/
- (void)fulfillMetricKitPromise {
if (self.metricKitPromiseFulfilled) return;
[self.metricKitDataAvailable fulfill:nil];
self.metricKitPromiseFulfilled = YES;
}
/*
* Helper method to write a dictionary of event information to file. Returns whether it succeeded.
*/
- (BOOL)writeDictionaryToFile:(NSDictionary *)dictionary
file:(NSFileHandle *)file
newLineData:(NSData *)newLineData {
NSError *dataError = nil;
NSData *data = [NSJSONSerialization dataWithJSONObject:dictionary options:0 error:&dataError];
if (dataError) {
FIRCLSDebugLog(@"Unable to write out dictionary.");
return NO;
}
[file seekToEndOfFile];
[file writeData:data];
[file writeData:newLineData];
return YES;
}
- (NSString *)getSignalName:(NSNumber *)signalCode {
int signal = [signalCode intValue];
switch (signal) {
case SIGABRT:
return @"SIGABRT";
case SIGBUS:
return @"SIGBUS";
case SIGFPE:
return @"SIGFPE";
case SIGILL:
return @"SIGILL";
case SIGSEGV:
return @"SIGSEGV";
case SIGSYS:
return @"SIGSYS";
case SIGTRAP:
return @"SIGTRAP";
default:
return @"UNKNOWN";
}
return @"UNKNOWN";
}
@end
#endif // CLS_METRICKIT_SUPPORTED

View File

@ -0,0 +1,27 @@
// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface FIRCLSNotificationManager : NSObject
+ (instancetype)new NS_UNAVAILABLE;
- (void)registerNotificationListener;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,113 @@
// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import "Crashlytics/Crashlytics/Controllers/FIRCLSNotificationManager.h"
#import "Crashlytics/Crashlytics/Components/FIRCLSApplication.h"
#import "Crashlytics/Crashlytics/Components/FIRCLSGlobals.h"
#import "Crashlytics/Crashlytics/Components/FIRCLSUserLogging.h"
#include "Crashlytics/Crashlytics/Helpers/FIRCLSDefines.h"
#if TARGET_OS_IPHONE
#import <UIKit/UIKit.h>
#else
#import <AppKit/AppKit.h>
#endif
@implementation FIRCLSNotificationManager
- (void)registerNotificationListener {
[self captureInitialNotificationStates];
#if TARGET_OS_IOS
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(willBecomeActive:)
name:UIApplicationWillEnterForegroundNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(didBecomeInactive:)
name:UIApplicationDidEnterBackgroundNotification
object:nil];
#if !CLS_TARGET_OS_VISION
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(didChangeOrientation:)
name:UIDeviceOrientationDidChangeNotification
object:nil];
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(didChangeUIOrientation:)
name:UIApplicationDidChangeStatusBarOrientationNotification
object:nil];
#pragma clang diagnostic pop
#endif // !CLS_TARGET_OS_VISION
#elif CLS_TARGET_OS_OSX
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(willBecomeActive:)
name:@"NSApplicationWillBecomeActiveNotification"
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(didBecomeInactive:)
name:@"NSApplicationDidResignActiveNotification"
object:nil];
#endif
}
- (void)captureInitialNotificationStates {
#if TARGET_OS_IOS && (!CLS_TARGET_OS_VISION)
UIDeviceOrientation orientation = [[UIDevice currentDevice] orientation];
UIInterfaceOrientation statusBarOrientation =
[FIRCLSApplicationSharedInstance() statusBarOrientation];
#endif // TARGET_OS_IOS && (!CLS_TARGET_OS_VISION)
// It's nice to do this async, so we don't hold up the main thread while doing three
// consecutive IOs here.
dispatch_async(FIRCLSGetLoggingQueue(), ^{
FIRCLSUserLoggingWriteInternalKeyValue(FIRCLSInBackgroundKey, @"0");
#if TARGET_OS_IOS && (!CLS_TARGET_OS_VISION)
FIRCLSUserLoggingWriteInternalKeyValue(FIRCLSDeviceOrientationKey,
[@(orientation) description]);
FIRCLSUserLoggingWriteInternalKeyValue(FIRCLSUIOrientationKey,
[@(statusBarOrientation) description]);
#endif // TARGET_OS_IOS && (!CLS_TARGET_OS_VISION)
});
}
- (void)willBecomeActive:(NSNotification *)notification {
FIRCLSUserLoggingRecordInternalKeyValue(FIRCLSInBackgroundKey, @NO);
}
- (void)didBecomeInactive:(NSNotification *)notification {
FIRCLSUserLoggingRecordInternalKeyValue(FIRCLSInBackgroundKey, @YES);
}
#if TARGET_OS_IOS && (!CLS_TARGET_OS_VISION)
- (void)didChangeOrientation:(NSNotification *)notification {
UIDeviceOrientation orientation = [[UIDevice currentDevice] orientation];
FIRCLSUserLoggingRecordInternalKeyValue(FIRCLSDeviceOrientationKey, @(orientation));
}
- (void)didChangeUIOrientation:(NSNotification *)notification {
UIInterfaceOrientation statusBarOrientation =
[FIRCLSApplicationSharedInstance() statusBarOrientation];
FIRCLSUserLoggingRecordInternalKeyValue(FIRCLSUIOrientationKey, @(statusBarOrientation));
}
#endif // TARGET_OS_IOS && (!CLS_TARGET_OS_VISION)
@end

View File

@ -0,0 +1,47 @@
// Copyright 2019 Google
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import <Foundation/Foundation.h>
#import "Crashlytics/Crashlytics/Public/FirebaseCrashlytics/FIRCrashlytics.h"
@class FBLPromise<T>;
@class FIRCLSExistingReportManager;
@class FIRCLSAnalyticsManager;
@class FIRCLSManagerData;
@class FIRCLSContextManager;
NS_ASSUME_NONNULL_BEGIN
@interface FIRCLSReportManager : NSObject
- (instancetype)initWithManagerData:(FIRCLSManagerData *)managerData
existingReportManager:(FIRCLSExistingReportManager *)existingReportManager
analyticsManager:(FIRCLSAnalyticsManager *)analyticsManager
NS_DESIGNATED_INITIALIZER;
- (instancetype)init NS_UNAVAILABLE;
+ (instancetype)new NS_UNAVAILABLE;
- (FBLPromise<NSNumber *> *)startWithProfiling;
- (FBLPromise<FIRCrashlyticsReport *> *)checkForUnsentReports;
- (FBLPromise *)sendUnsentReports;
- (FBLPromise *)deleteUnsentReports;
@end
extern NSString *const FIRCLSConfigSubmitReportsKey;
extern NSString *const FIRCLSConfigPackageReportsKey;
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,502 @@
// Copyright 2019 Google
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// The report manager has the ability to send to two different endpoints.
//
// The old legacy flow for a report goes through the following states/folders:
// 1. active - .clsrecords optimized for crash time persistence
// 2. processing - .clsrecords with attempted symbolication
// 3. prepared-legacy - .multipartmime of compressed .clsrecords
//
// The new flow for a report goes through the following states/folders:
// 1. active - .clsrecords optimized for crash time persistence
// 2. processing - .clsrecords with attempted symbolication
// 3. prepared - .clsrecords moved from processing with no changes
//
// The code was designed so the report processing workflows are not dramatically different from one
// another. The design will help avoid having a lot of conditional code blocks throughout the
// codebase.
//
#include <stdatomic.h>
#if __has_include(<FBLPromises/FBLPromises.h>)
#import <FBLPromises/FBLPromises.h>
#else
#import "FBLPromises.h"
#endif
#import "Crashlytics/Crashlytics/Components/FIRCLSApplication.h"
#import "Crashlytics/Crashlytics/Components/FIRCLSUserLogging.h"
#import "Crashlytics/Crashlytics/Controllers/FIRCLSAnalyticsManager.h"
#import "Crashlytics/Crashlytics/Controllers/FIRCLSContextManager.h"
#import "Crashlytics/Crashlytics/Controllers/FIRCLSExistingReportManager.h"
#import "Crashlytics/Crashlytics/Controllers/FIRCLSManagerData.h"
#import "Crashlytics/Crashlytics/Controllers/FIRCLSMetricKitManager.h"
#import "Crashlytics/Crashlytics/Controllers/FIRCLSNotificationManager.h"
#import "Crashlytics/Crashlytics/DataCollection/FIRCLSDataCollectionArbiter.h"
#import "Crashlytics/Crashlytics/DataCollection/FIRCLSDataCollectionToken.h"
#import "Crashlytics/Crashlytics/Helpers/FIRCLSDefines.h"
#import "Crashlytics/Crashlytics/Helpers/FIRCLSFeatures.h"
#import "Crashlytics/Crashlytics/Helpers/FIRCLSLogger.h"
#import "Crashlytics/Crashlytics/Models/FIRCLSFileManager.h"
#import "Crashlytics/Crashlytics/Models/FIRCLSInternalReport.h"
#import "Crashlytics/Crashlytics/Models/FIRCLSLaunchMarkerModel.h"
#import "Crashlytics/Crashlytics/Models/FIRCLSSettings.h"
#import "Crashlytics/Crashlytics/Models/FIRCLSSymbolResolver.h"
#import "Crashlytics/Crashlytics/Operations/Reports/FIRCLSProcessReportOperation.h"
#import "Crashlytics/Crashlytics/Settings/Models/FIRCLSApplicationIdentifierModel.h"
#include "Crashlytics/Crashlytics/Components/FIRCLSGlobals.h"
#include "Crashlytics/Crashlytics/Helpers/FIRCLSUtility.h"
#import "Crashlytics/Crashlytics/Models/FIRCLSExecutionIdentifierModel.h"
#import "Crashlytics/Crashlytics/Models/FIRCLSInstallIdentifierModel.h"
#import "Crashlytics/Crashlytics/Settings/FIRCLSSettingsManager.h"
#import "Crashlytics/Shared/FIRCLSConstants.h"
#import "Crashlytics/Crashlytics/Controllers/FIRCLSReportManager_Private.h"
#if TARGET_OS_IPHONE
#import <UIKit/UIKit.h>
#else
#import <AppKit/AppKit.h>
#endif
/**
* A FirebaseReportAction is used to indicate how to handle unsent reports.
*/
typedef NS_ENUM(NSInteger, FIRCLSReportAction) {
/** Upload the reports to Crashlytics. */
FIRCLSReportActionSend,
/** Delete the reports without uploading them. */
FIRCLSReportActionDelete,
};
/**
* This is just a helper to make code using FirebaseReportAction more readable.
*/
typedef NSNumber FIRCLSWrappedReportAction;
@implementation NSNumber (FIRCLSWrappedReportAction)
- (FIRCLSReportAction)reportActionValue {
return [self intValue];
}
@end
@interface FIRCLSReportManager () {
FIRCLSFileManager *_fileManager;
dispatch_queue_t _dispatchQueue;
NSOperationQueue *_operationQueue;
id<FIRAnalyticsInterop> _analytics;
// A promise that will be resolved when unsent reports are found on the device, and
// processReports: can be called to decide how to deal with them.
FBLPromise<FIRCrashlyticsReport *> *_unsentReportsAvailable;
// A promise that will be resolved when the user has provided an action that they want to perform
// for all the unsent reports.
FBLPromise<FIRCLSWrappedReportAction *> *_reportActionProvided;
// A promise that will be resolved when all unsent reports have been "handled". They won't
// necessarily have been uploaded, but we will know whether they should be sent or deleted, and
// the initial work to make that happen will have been processed on the work queue.
//
// Currently only used for testing
FBLPromise *_unsentReportsHandled;
// A token to make sure that checkForUnsentReports only gets called once.
atomic_bool _checkForUnsentReportsCalled;
BOOL _registeredAnalyticsEventListener;
}
@property(nonatomic, readonly) NSString *googleAppID;
@property(nonatomic, strong) GDTCORTransport *googleTransport;
@property(nonatomic, strong) FIRCLSDataCollectionArbiter *dataArbiter;
@property(nonatomic, strong) FIRCLSSettings *settings;
@property(nonatomic, strong) FIRCLSLaunchMarkerModel *launchMarker;
@property(nonatomic, strong) FIRCLSApplicationIdentifierModel *appIDModel;
@property(nonatomic, strong) FIRCLSInstallIdentifierModel *installIDModel;
@property(nonatomic, strong) FIRCLSExecutionIdentifierModel *executionIDModel;
@property(nonatomic, strong) FIRCLSAnalyticsManager *analyticsManager;
@property(nonatomic, strong) FIRCLSExistingReportManager *existingReportManager;
@property(nonatomic, strong) FIRCLSContextManager *contextManager;
// Internal Managers
@property(nonatomic, strong) FIRCLSSettingsManager *settingsManager;
@property(nonatomic, strong) FIRCLSNotificationManager *notificationManager;
#if CLS_METRICKIT_SUPPORTED
@property(nonatomic, strong) FIRCLSMetricKitManager *metricKitManager;
#endif
@end
@implementation FIRCLSReportManager
- (instancetype)initWithManagerData:(FIRCLSManagerData *)managerData
existingReportManager:(FIRCLSExistingReportManager *)existingReportManager
analyticsManager:(FIRCLSAnalyticsManager *)analyticsManager {
self = [super init];
if (!self) {
return nil;
}
_fileManager = managerData.fileManager;
_analytics = managerData.analytics;
_googleAppID = [managerData.googleAppID copy];
_dataArbiter = managerData.dataArbiter;
_googleTransport = managerData.googleTransport;
_operationQueue = managerData.operationQueue;
_dispatchQueue = managerData.dispatchQueue;
_appIDModel = managerData.appIDModel;
_installIDModel = managerData.installIDModel;
_settings = managerData.settings;
_executionIDModel = managerData.executionIDModel;
_contextManager = managerData.contextManager;
_existingReportManager = existingReportManager;
_analyticsManager = analyticsManager;
_unsentReportsAvailable = [FBLPromise pendingPromise];
_reportActionProvided = [FBLPromise pendingPromise];
_unsentReportsHandled = [FBLPromise pendingPromise];
_checkForUnsentReportsCalled = NO;
_settingsManager = [[FIRCLSSettingsManager alloc] initWithAppIDModel:self.appIDModel
installIDModel:self.installIDModel
settings:self.settings
fileManager:self.fileManager
googleAppID:self.googleAppID];
_notificationManager = [[FIRCLSNotificationManager alloc] init];
// This needs to be called before any values are read from settings
NSTimeInterval currentTimestamp = [NSDate timeIntervalSinceReferenceDate];
[self.settings reloadFromCacheWithGoogleAppID:self.googleAppID currentTimestamp:currentTimestamp];
#if CLS_METRICKIT_SUPPORTED
if (@available(iOS 15, *)) {
if (self.settings.metricKitCollectionEnabled) {
FIRCLSDebugLog(@"MetricKit data collection enabled.");
_metricKitManager = [[FIRCLSMetricKitManager alloc] initWithManagerData:managerData
existingReportManager:existingReportManager
fileManager:_fileManager];
}
}
#endif
_launchMarker = [[FIRCLSLaunchMarkerModel alloc] initWithFileManager:_fileManager];
return self;
}
// This method returns a promise that is resolved with a wrapped FirebaseReportAction once the user
// has indicated whether they want to upload currently cached reports. This method should only be
// called when we have determined there is at least 1 unsent report. This method waits until either:
// 1. Data collection becomes enabled, in which case, the promise will be resolved with Send.
// 2. The developer uses the processCrashReports API to indicate whether the report
// should be sent or deleted, at which point the promise will be resolved with the action.
- (FBLPromise<FIRCLSWrappedReportAction *> *)waitForReportAction {
FIRCrashlyticsReport *unsentReport = self.existingReportManager.newestUnsentReport;
[_unsentReportsAvailable fulfill:unsentReport];
// If data collection gets enabled while we are waiting for an action, go ahead and send the
// reports, and any subsequent explicit response will be ignored.
FBLPromise<FIRCLSWrappedReportAction *> *collectionEnabled =
[[self.dataArbiter waitForCrashlyticsCollectionEnabled]
then:^id _Nullable(NSNumber *_Nullable value) {
return @(FIRCLSReportActionSend);
}];
// Wait for either the processReports callback to be called, or data collection to be enabled.
return [FBLPromise race:@[ collectionEnabled, _reportActionProvided ]];
}
/*
* This method returns a promise that is resolved once
* MetricKit diagnostic reports have been received by `metricKitManager`.
*/
- (FBLPromise *)waitForMetricKitData {
// If the platform is not iOS or the iOS version is less than 15, immediately resolve the promise
// since no MetricKit diagnostics will be available.
FBLPromise *promise = [FBLPromise resolvedWith:nil];
#if CLS_METRICKIT_SUPPORTED
if (@available(iOS 15, *)) {
if (self.settings.metricKitCollectionEnabled) {
promise = [self.metricKitManager waitForMetricKitDataAvailable];
}
}
return promise;
#endif
return promise;
}
- (FBLPromise<FIRCrashlyticsReport *> *)checkForUnsentReports {
bool expectedCalled = NO;
if (!atomic_compare_exchange_strong(&_checkForUnsentReportsCalled, &expectedCalled, YES)) {
FIRCLSErrorLog(@"Either checkForUnsentReports or checkAndUpdateUnsentReports should be called "
@"once per execution.");
return [FBLPromise resolvedWith:nil];
}
return _unsentReportsAvailable;
}
- (FBLPromise *)sendUnsentReports {
[_reportActionProvided fulfill:@(FIRCLSReportActionSend)];
return _unsentReportsHandled;
}
- (FBLPromise *)deleteUnsentReports {
[_reportActionProvided fulfill:@(FIRCLSReportActionDelete)];
return _unsentReportsHandled;
}
- (FBLPromise<NSNumber *> *)startWithProfiling {
NSString *executionIdentifier = self.executionIDModel.executionID;
// This needs to be called before the new report is created for
// this run of the app.
[self.existingReportManager collectExistingReports];
if (![self validateAppIdentifiers]) {
return [FBLPromise resolvedWith:@NO];
}
#if DEBUG
FIRCLSDebugLog(@"Root: %@", [_fileManager rootPath]);
#endif
if (![_fileManager createReportDirectories]) {
return [FBLPromise resolvedWith:@NO];
}
BOOL launchFailure = [self.launchMarker checkForAndCreateLaunchMarker];
FIRCLSInternalReport *report = [self setupCurrentReport:executionIdentifier];
if (!report) {
FIRCLSErrorLog(@"Unable to setup a new report");
}
if (![self startCrashReporterWithProfilingReport:report]) {
FIRCLSErrorLog(@"Unable to start crash reporter");
report = nil;
}
#if CLS_METRICKIT_SUPPORTED
if (@available(iOS 15, *)) {
if (self.settings.metricKitCollectionEnabled) {
[self.metricKitManager registerMetricKitManager];
}
}
#endif
FBLPromise<NSNumber *> *promise;
if ([self.dataArbiter isCrashlyticsCollectionEnabled]) {
FIRCLSDebugLog(@"Automatic data collection is enabled.");
FIRCLSDebugLog(@"Unsent reports will be uploaded at startup");
FIRCLSDataCollectionToken *dataCollectionToken = [FIRCLSDataCollectionToken validToken];
[self beginSettingsWithToken:dataCollectionToken];
// Wait for MetricKit data to be available, then continue to send reports and resolve promise.
promise = [[self waitForMetricKitData]
onQueue:_dispatchQueue
then:^id _Nullable(id _Nullable metricKitValue) {
[self beginReportUploadsWithToken:dataCollectionToken blockingSend:launchFailure];
// If data collection is enabled, the SDK will not notify the user
// when unsent reports are available, or respect Send / DeleteUnsentReports
[self->_unsentReportsAvailable fulfill:nil];
return @(report != nil);
}];
} else {
FIRCLSDebugLog(@"Automatic data collection is disabled.");
FIRCLSDebugLog(@"[Crashlytics:Crash] %d unsent reports are available. Waiting for "
@"send/deleteUnsentReports to be called.",
self.existingReportManager.unsentReportsCount);
// Wait for an action to get sent, either from processReports: or automatic data collection,
// and for MetricKit data to be available.
promise = [[FBLPromise all:@[ [self waitForReportAction], [self waitForMetricKitData] ]]
onQueue:_dispatchQueue
then:^id _Nullable(NSArray *_Nullable wrappedActionAndData) {
// Process the actions for the reports on disk.
FIRCLSReportAction action = [[wrappedActionAndData firstObject] reportActionValue];
if (action == FIRCLSReportActionSend) {
FIRCLSDebugLog(@"Sending unsent reports.");
FIRCLSDataCollectionToken *dataCollectionToken =
[FIRCLSDataCollectionToken validToken];
[self beginSettingsWithToken:dataCollectionToken];
[self beginReportUploadsWithToken:dataCollectionToken blockingSend:NO];
} else if (action == FIRCLSReportActionDelete) {
FIRCLSDebugLog(@"Deleting unsent reports.");
[self.existingReportManager deleteUnsentReports];
} else {
FIRCLSErrorLog(@"Unknown report action: %d", action);
}
return @(report != nil);
}];
}
if (report != nil) {
// empty for disabled start-up time
dispatch_async(FIRCLSGetLoggingQueue(), ^{
FIRCLSUserLoggingWriteInternalKeyValue(FIRCLSStartTimeKey, @"");
});
}
// To make the code more predictable and therefore testable, don't resolve the startup promise
// until the operations that got queued up for processing reports have been processed through the
// work queue.
NSOperationQueue *__weak queue = _operationQueue;
FBLPromise *__weak unsentReportsHandled = _unsentReportsHandled;
promise = [promise then:^id _Nullable(NSNumber *_Nullable value) {
FBLPromise *allOpsFinished = [FBLPromise pendingPromise];
[queue addOperationWithBlock:^{
[allOpsFinished fulfill:nil];
}];
return [allOpsFinished onQueue:dispatch_get_main_queue()
then:^id _Nullable(id _Nullable allOpsFinishedValue) {
// Signal that to callers of processReports that everything is
// finished.
[unsentReportsHandled fulfill:nil];
return value;
}];
}];
return promise;
}
- (void)beginSettingsWithToken:(FIRCLSDataCollectionToken *)token {
if (self.settings.isCacheExpired) {
// This method can be called more than once if the user calls
// SendUnsentReports again, so don't repeat the settings fetch
static dispatch_once_t settingsFetchOnceToken;
dispatch_once(&settingsFetchOnceToken, ^{
[self.settingsManager beginSettingsWithGoogleAppId:self.googleAppID token:token];
});
}
}
- (void)beginReportUploadsWithToken:(FIRCLSDataCollectionToken *)token
blockingSend:(BOOL)blockingSend {
if (self.settings.collectReportsEnabled) {
[self.existingReportManager sendUnsentReportsWithToken:token asUrgent:blockingSend];
} else {
FIRCLSInfoLog(@"Collect crash reports is disabled");
[self.existingReportManager deleteUnsentReports];
}
}
- (BOOL)startCrashReporterWithProfilingReport:(FIRCLSInternalReport *)report {
if (!report) {
return NO;
}
if (![self.contextManager setupContextWithReport:report
settings:self.settings
fileManager:_fileManager]) {
return NO;
}
[self.notificationManager registerNotificationListener];
[self.analyticsManager registerAnalyticsListener];
[self crashReportingSetupCompleted];
return YES;
}
- (void)crashReportingSetupCompleted {
// check our handlers
FIRCLSDispatchAfter(2.0, dispatch_get_main_queue(), ^{
FIRCLSExceptionCheckHandlers((__bridge void *)(self));
#if CLS_SIGNAL_SUPPORTED
FIRCLSSignalCheckHandlers();
#endif
#if CLS_MACH_EXCEPTION_SUPPORTED
FIRCLSMachExceptionCheckHandlers();
#endif
});
// remove the launch failure marker and records and empty string since
// we're avoiding mach_absolute_time calls.
dispatch_async(dispatch_get_main_queue(), ^{
[self.launchMarker removeLaunchFailureMarker];
dispatch_async(FIRCLSGetLoggingQueue(), ^{
FIRCLSUserLoggingWriteInternalKeyValue(FIRCLSFirstRunloopTurnTimeKey, @"");
});
});
}
- (BOOL)validateAppIdentifiers {
// When the ApplicationIdentifierModel fails to initialize, it is usually due to
// failing computeExecutableInfo. This can happen if the user sets the
// Exported Symbols File in Build Settings, and leaves off the one symbol
// that Crashlytics needs, "__mh_execute_header" (which is defined in mach-o/ldsyms.h as
// _MH_EXECUTE_SYM). From https://github.com/firebase/firebase-ios-sdk/issues/5020
if (!self.appIDModel) {
FIRCLSErrorLog(@"Crashlytics could not find the symbol for the app's main function and cannot "
@"start up. This can be resolved 2 ways depending on your setup:\n 1. If you "
@"have Exported Symbols File set in your Build Settings, add "
@"\"__mh_execute_header\" as a newline in your Exported Symbols File.\n 2. If "
@"you have -exported_symbols_list in your linker flags, remove it.");
return NO;
}
if (self.appIDModel.bundleID.length == 0) {
FIRCLSErrorLog(@"An application must have a valid bundle identifier in its Info.plist");
return NO;
}
if ([self.dataArbiter isLegacyDataCollectionKeyInPlist]) {
FIRCLSErrorLog(@"Found legacy data collection key in app's Info.plist: "
@"firebase_crashlytics_collection_enabled");
FIRCLSErrorLog(@"Please update your Info.plist to use the new data collection key: "
@"FirebaseCrashlyticsCollectionEnabled");
FIRCLSErrorLog(@"The legacy data collection Info.plist value could be overridden by "
@"calling: [Fabric with:...]");
FIRCLSErrorLog(@"The new value can be overridden by calling: [[FIRCrashlytics "
@"crashlytics] setCrashlyticsCollectionEnabled:<isEnabled>]");
return NO;
}
return YES;
}
- (FIRCLSInternalReport *)setupCurrentReport:(NSString *)executionIdentifier {
[self.launchMarker createLaunchFailureMarker];
NSString *reportPath = [_fileManager setupNewPathForExecutionIdentifier:executionIdentifier];
return [[FIRCLSInternalReport alloc] initWithPath:reportPath
executionIdentifier:executionIdentifier];
}
@end

View File

@ -0,0 +1,32 @@
// Copyright 2019 Google
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import "Crashlytics/Crashlytics/Controllers/FIRCLSReportManager.h"
#import "Crashlytics/Crashlytics/Controllers/FIRCLSReportUploader.h"
#import "Crashlytics/Crashlytics/Models/FIRCLSLaunchMarkerModel.h"
@class FIRCLSInstallIdentifierModel;
@interface FIRCLSReportManager ()
@property(nonatomic, strong) NSOperationQueue *operationQueue;
@property(nonatomic, strong) FIRCLSFileManager *fileManager;
@end
@interface FIRCLSReportManager (PrivateMethods)
@property(nonatomic, strong) FIRCLSLaunchMarkerModel *launchMarker;
@end

View File

@ -0,0 +1,42 @@
// Copyright 2019 Google
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import <Foundation/Foundation.h>
@class FIRCLSDataCollectionToken;
@class FIRCLSInternalReport;
@class FIRCLSManagerData;
@class FIRCLSFileManager;
@interface FIRCLSReportUploader : NSObject
- (instancetype)init NS_UNAVAILABLE;
+ (instancetype)new NS_UNAVAILABLE;
- (instancetype)initWithManagerData:(FIRCLSManagerData *)managerData NS_DESIGNATED_INITIALIZER;
@property(nonatomic, readonly) NSOperationQueue *operationQueue;
@property(nonatomic, readonly) FIRCLSFileManager *fileManager;
@property(nonatomic, copy) NSString *fiid;
@property(nonatomic, copy) NSString *authToken;
- (void)prepareAndSubmitReport:(FIRCLSInternalReport *)report
dataCollectionToken:(FIRCLSDataCollectionToken *)dataCollectionToken
asUrgent:(BOOL)urgent
withProcessing:(BOOL)shouldProcess;
- (void)uploadPackagedReportAtPath:(NSString *)path
dataCollectionToken:(FIRCLSDataCollectionToken *)dataCollectionToken
asUrgent:(BOOL)urgent;
@end

View File

@ -0,0 +1,239 @@
// Copyright 2019 Google
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import "Interop/Analytics/Public/FIRAnalyticsInterop.h"
#import "Crashlytics/Crashlytics/Components/FIRCLSApplication.h"
#import "Crashlytics/Crashlytics/Controllers/FIRCLSAnalyticsManager.h"
#import "Crashlytics/Crashlytics/Controllers/FIRCLSManagerData.h"
#import "Crashlytics/Crashlytics/Controllers/FIRCLSReportUploader_Private.h"
#import "Crashlytics/Crashlytics/DataCollection/FIRCLSDataCollectionToken.h"
#import "Crashlytics/Crashlytics/Helpers/FIRCLSDefines.h"
#import "Crashlytics/Crashlytics/Models/FIRCLSFileManager.h"
#import "Crashlytics/Crashlytics/Models/FIRCLSInstallIdentifierModel.h"
#import "Crashlytics/Crashlytics/Models/FIRCLSInternalReport.h"
#import "Crashlytics/Crashlytics/Models/FIRCLSSettings.h"
#import "Crashlytics/Crashlytics/Models/FIRCLSSymbolResolver.h"
#import "Crashlytics/Crashlytics/Models/Record/FIRCLSReportAdapter.h"
#import "Crashlytics/Crashlytics/Operations/Reports/FIRCLSProcessReportOperation.h"
#include "Crashlytics/Crashlytics/Helpers/FIRCLSUtility.h"
#import "Crashlytics/Shared/FIRCLSConstants.h"
#import "Crashlytics/Shared/FIRCLSNetworking/FIRCLSURLBuilder.h"
#import <GoogleDataTransport/GoogleDataTransport.h>
@interface FIRCLSReportUploader () {
id<FIRAnalyticsInterop> _analytics;
}
@property(nonatomic, strong) GDTCORTransport *googleTransport;
@property(nonatomic, strong) FIRCLSInstallIdentifierModel *installIDModel;
@property(nonatomic, readonly) NSString *googleAppID;
@end
@implementation FIRCLSReportUploader
- (instancetype)initWithManagerData:(FIRCLSManagerData *)managerData {
self = [super init];
if (!self) {
return nil;
}
_operationQueue = managerData.operationQueue;
_googleAppID = managerData.googleAppID;
_googleTransport = managerData.googleTransport;
_installIDModel = managerData.installIDModel;
_fileManager = managerData.fileManager;
_analytics = managerData.analytics;
return self;
}
#pragma mark - Packaging and Submission
/*
* For a crash report, this is the initial code path for uploading. A report
* will not repeat this code path after it's happened because this code path
* will move the report from the "active" folder into "processing" and then
* "prepared". Once in prepared, the report can be re-uploaded any number of times
* with uploadPackagedReportAtPath in the case of an upload failure.
*/
- (void)prepareAndSubmitReport:(FIRCLSInternalReport *)report
dataCollectionToken:(FIRCLSDataCollectionToken *)dataCollectionToken
asUrgent:(BOOL)urgent
withProcessing:(BOOL)shouldProcess {
if (![dataCollectionToken isValid]) {
FIRCLSErrorLog(@"Data collection disabled and report will not be submitted");
return;
}
// This activity is still relevant using GoogleDataTransport because the on-device
// symbolication operation may be computationally intensive.
FIRCLSApplicationActivity(
FIRCLSApplicationActivityDefault, @"Crashlytics Crash Report Processing", ^{
// Check to see if the FID has rotated before we construct the payload
// so that the payload has an updated value.
//
// If we're in urgent mode, this will be running on the main thread. Since
// the FIID callback is run on the main thread, this call can deadlock in
// urgent mode. Since urgent mode happens when the app is in a crash loop,
// we can safely assume users aren't rotating their FIID, so this can be skipped.
if (!urgent) {
[self.installIDModel regenerateInstallIDIfNeededWithBlock:^(
NSString *_Nonnull newFIID, NSString *_Nonnull authToken) {
self.fiid = [newFIID copy];
self.authToken = [authToken copy];
}];
} else {
FIRCLSWarningLog(
@"Crashlytics skipped rotating the Install ID during urgent mode because it is run "
@"on the main thread, which can't succeed. This can happen if the app crashed the "
@"last run and Crashlytics is uploading urgently.");
}
// Run on-device symbolication before packaging if we should process
if (shouldProcess) {
if (![self.fileManager moveItemAtPath:report.path
toDirectory:self.fileManager.processingPath]) {
FIRCLSErrorLog(@"Unable to move report for processing");
return;
}
// adjust the report's path, and process it
[report setPath:[self.fileManager.processingPath
stringByAppendingPathComponent:report.directoryName]];
FIRCLSSymbolResolver *resolver = [[FIRCLSSymbolResolver alloc] init];
FIRCLSProcessReportOperation *processOperation =
[[FIRCLSProcessReportOperation alloc] initWithReport:report resolver:resolver];
[processOperation start];
}
// With the new report endpoint, the report is deleted once it is written to GDT
// Check if the report has a crash file before the report is moved or deleted
BOOL isCrash = report.isCrash;
// For the new endpoint, just move the .clsrecords from "processing" -> "prepared".
// In the old endpoint this was for packaging the report as a multipartmime file,
// so this can probably be removed for GoogleDataTransport.
if (![self.fileManager moveItemAtPath:report.path
toDirectory:self.fileManager.preparedPath]) {
FIRCLSErrorLog(@"Unable to move report to prepared");
return;
}
NSString *packagedPath = [self.fileManager.preparedPath
stringByAppendingPathComponent:report.path.lastPathComponent];
FIRCLSInfoLog(@"[Firebase/Crashlytics] Packaged report with id '%@' for submission",
report.identifier);
[self uploadPackagedReportAtPath:packagedPath
dataCollectionToken:dataCollectionToken
asUrgent:urgent];
// We don't check for success here for 2 reasons:
// 1) If we can't upload a crash for whatever reason, but we can upload analytics
// it's better for the customer to get accurate Crash Free Users.
// 2) In the past we did try to check for success, but it was a useless check because
// sendDataEvent is async (unless we're sending urgently).
if (isCrash) {
[FIRCLSAnalyticsManager logCrashWithTimeStamp:report.crashedOnDate.timeIntervalSince1970
toAnalytics:self->_analytics];
}
});
return;
}
/*
* This code path can be repeated any number of times for a prepared crash report if
* the report is failing to upload.
*
* Therefore, side effects (like logging to Analytics) should not go in this method or
* else they will re-trigger when failures happen.
*
* When a crash report fails to upload, it will stay in the "prepared" folder. Upon next
* run of the app, the ReportManager will attempt to re-upload prepared reports using this
* method.
*/
- (void)uploadPackagedReportAtPath:(NSString *)path
dataCollectionToken:(FIRCLSDataCollectionToken *)dataCollectionToken
asUrgent:(BOOL)urgent {
FIRCLSDebugLog(@"Submitting report %@", urgent ? @"urgently" : @"async");
if (![dataCollectionToken isValid]) {
FIRCLSErrorLog(@"A report upload was requested with an invalid data collection token.");
return;
}
FIRCLSReportAdapter *adapter = [[FIRCLSReportAdapter alloc] initWithPath:path
googleAppId:self.googleAppID
installIDModel:self.installIDModel
fiid:self.fiid
authToken:self.authToken];
GDTCOREvent *event = [self.googleTransport eventForTransport];
event.dataObject = adapter;
event.qosTier = GDTCOREventQoSFast; // Bypass batching, send immediately
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[self.googleTransport
sendDataEvent:event
onComplete:^(BOOL wasWritten, NSError *error) {
if (!wasWritten) {
FIRCLSErrorLog(
@"Failed to send crash report due to failure writing GoogleDataTransport event");
dispatch_semaphore_signal(semaphore);
return;
}
if (error) {
FIRCLSErrorLog(@"Failed to send crash report due to GoogleDataTransport error: %@",
error.localizedDescription);
dispatch_semaphore_signal(semaphore);
return;
}
FIRCLSInfoLog(@"Completed report submission with id: %@", path.lastPathComponent);
if (urgent) {
dispatch_semaphore_signal(semaphore);
}
[self cleanUpSubmittedReportAtPath:path];
}];
if (urgent) {
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
}
}
- (BOOL)cleanUpSubmittedReportAtPath:(NSString *)path {
if (![[self fileManager] removeItemAtPath:path]) {
FIRCLSErrorLog(@"Unable to remove packaged submission");
return NO;
}
return YES;
}
@end

View File

@ -0,0 +1,23 @@
// Copyright 2019 Google
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import "Crashlytics/Crashlytics/Controllers/FIRCLSReportUploader.h"
@interface FIRCLSReportUploader (PrivateMethods)
@property(nonatomic, readonly) NSURL *reportURL;
- (NSMutableURLRequest *)mutableRequestWithURL:(NSURL *)url timeout:(NSTimeInterval)timeout;
@end

View File

@ -0,0 +1,36 @@
// Copyright 2024 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#if SWIFT_PACKAGE
@import FirebaseCrashlyticsSwift;
#elif __has_include(<FirebaseCrashlytics/FirebaseCrashlytics-Swift.h>)
#import <FirebaseCrashlytics/FirebaseCrashlytics-Swift.h>
#elif __has_include("FirebaseCrashlytics-Swift.h")
// If frameworks are not available, fall back to importing the header as it
// should be findable from a header search path pointing to the build
// directory. See #12611 for more context.
#import "FirebaseCrashlytics-Swift.h"
#endif
@interface FIRCLSRolloutsPersistenceManager : NSObject <FIRCLSPersistenceLog>
- (instancetype _Nullable)initWithFileManager:(FIRCLSFileManager *_Nonnull)fileManager
andQueue:(dispatch_queue_t _Nonnull)queue;
- (instancetype _Nonnull)init NS_UNAVAILABLE;
+ (instancetype _Nonnull)new NS_UNAVAILABLE;
- (void)updateRolloutsStateToPersistenceWithRollouts:(NSData *_Nonnull)rollouts
reportID:(NSString *_Nonnull)reportID;
- (void)debugLogWithMessage:(NSString *_Nonnull)message;
@end

View File

@ -0,0 +1,91 @@
// Copyright 2024 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import <Foundation/Foundation.h>
#include "Crashlytics/Crashlytics/Components/FIRCLSUserLogging.h"
#import "Crashlytics/Crashlytics/Helpers/FIRCLSLogger.h"
#import "Crashlytics/Crashlytics/Models/FIRCLSFileManager.h"
#import "Crashlytics/Crashlytics/Models/FIRCLSInternalReport.h"
#if SWIFT_PACKAGE
@import FirebaseCrashlyticsSwift;
#elif __has_include(<FirebaseCrashlytics/FirebaseCrashlytics-Swift.h>)
#import <FirebaseCrashlytics/FirebaseCrashlytics-Swift.h>
#elif __has_include("FirebaseCrashlytics-Swift.h")
// If frameworks are not available, fall back to importing the header as it
// should be findable from a header search path pointing to the build
// directory. See #12611 for more context.
#import "FirebaseCrashlytics-Swift.h"
#endif
@interface FIRCLSRolloutsPersistenceManager : NSObject <FIRCLSPersistenceLog>
@property(nonatomic, readonly) FIRCLSFileManager *fileManager;
@property(nonnull, nonatomic, readonly) dispatch_queue_t rolloutsLoggingQueue;
@end
@implementation FIRCLSRolloutsPersistenceManager
- (instancetype)initWithFileManager:(FIRCLSFileManager *)fileManager
andQueue:(dispatch_queue_t)queue {
self = [super init];
if (!self) {
return nil;
}
_fileManager = fileManager;
if (!queue) {
FIRCLSDebugLog(@"Failed to initialize FIRCLSRolloutsPersistenceManager, logging queue is nil");
return nil;
}
_rolloutsLoggingQueue = queue;
return self;
}
- (void)updateRolloutsStateToPersistenceWithRollouts:(NSData *_Nonnull)rollouts
reportID:(NSString *_Nonnull)reportID {
NSString *rolloutsPath = [[[_fileManager activePath] stringByAppendingPathComponent:reportID]
stringByAppendingPathComponent:FIRCLSReportRolloutsFile];
if (![_fileManager fileExistsAtPath:rolloutsPath]) {
if (![_fileManager createFileAtPath:rolloutsPath contents:nil attributes:nil]) {
FIRCLSDebugLog(@"Could not create rollouts.clsrecord file. Error was code: %d - message: %s",
errno, strerror(errno));
return;
}
}
NSFileHandle *rolloutsFile = [NSFileHandle fileHandleForUpdatingAtPath:rolloutsPath];
if (!_rolloutsLoggingQueue) {
FIRCLSDebugLog(@"Rollouts logging queue is dealloccated");
return;
}
dispatch_async(_rolloutsLoggingQueue, ^{
@try {
[rolloutsFile seekToEndOfFile];
NSMutableData *rolloutsWithNewLineData = [rollouts mutableCopy];
[rolloutsWithNewLineData appendData:[@"\n" dataUsingEncoding:NSUTF8StringEncoding]];
[rolloutsFile writeData:rolloutsWithNewLineData];
[rolloutsFile closeFile];
} @catch (NSException *exception) {
FIRCLSDebugLog(@"Failed to write new rollouts. Exception name: %s - message: %s",
exception.name, exception.reason);
}
});
}
- (void)debugLogWithMessage:(NSString *_Nonnull)message {
FIRCLSDebugLog(message);
}
@end

View File

@ -0,0 +1,39 @@
// Copyright 2019 Google
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import <Foundation/Foundation.h>
@class FIRApp;
@class FBLPromise<T>;
NS_ASSUME_NONNULL_BEGIN
@interface FIRCLSDataCollectionArbiter : NSObject
- (instancetype)init NS_UNAVAILABLE;
- (instancetype)initWithApp:(FIRApp *)app withAppInfo:(NSDictionary *)dict;
- (BOOL)isLegacyDataCollectionKeyInPlist;
- (BOOL)isCrashlyticsCollectionEnabled;
- (void)setCrashlyticsCollectionEnabled:(BOOL)enabled;
// Returns a promise that is fulfilled once data collection is enabled.
- (FBLPromise<NSNumber *> *)waitForCrashlyticsCollectionEnabled;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,147 @@
// Copyright 2019 Google
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import "Crashlytics/Crashlytics/DataCollection/FIRCLSDataCollectionArbiter.h"
#if __has_include(<FBLPromises/FBLPromises.h>)
#import <FBLPromises/FBLPromises.h>
#else
#import "FBLPromises.h"
#endif
#import "FirebaseCore/Extension/FirebaseCoreInternal.h"
#import "Crashlytics/Crashlytics/FIRCLSUserDefaults/FIRCLSUserDefaults.h"
// The legacy data collection setting allows Fabric customers to turn off auto-
// initialization, but can be overridden by calling [Fabric with:].
//
// While we support Fabric, we must have two different versions, because
// they require these slightly different semantics.
NSString *const FIRCLSLegacyCrashlyticsCollectionKey = @"firebase_crashlytics_collection_enabled";
// The new data collection setting can be set by an API that is stored in FIRCLSUserDefaults
NSString *const FIRCLSDataCollectionEnabledKey = @"com.crashlytics.data_collection";
// The new data collection setting also allows Firebase customers to turn off data
// collection in their Info.plist, and can be overridden by setting it to true using
// the setCrashlyticsCollectionEnabled API.
NSString *const FIRCLSCrashlyticsCollectionKey = @"FirebaseCrashlyticsCollectionEnabled";
typedef NS_ENUM(NSInteger, FIRCLSDataCollectionSetting) {
FIRCLSDataCollectionSettingNotSet = 0,
FIRCLSDataCollectionSettingEnabled = 1,
FIRCLSDataCollectionSettingDisabled = 2,
};
@interface FIRCLSDataCollectionArbiter () {
NSLock *_mutex;
FBLPromise *_dataCollectionEnabled;
BOOL _promiseResolved;
FIRApp *_app;
NSDictionary *_appInfo;
}
@end
@implementation FIRCLSDataCollectionArbiter
- (instancetype)initWithApp:(FIRApp *)app withAppInfo:(NSDictionary *)dict {
self = [super init];
if (self) {
_mutex = [[NSLock alloc] init];
_appInfo = dict;
_app = app;
if ([FIRCLSDataCollectionArbiter isCrashlyticsCollectionEnabledWithApp:app withAppInfo:dict]) {
_dataCollectionEnabled = [FBLPromise resolvedWith:nil];
_promiseResolved = YES;
} else {
_dataCollectionEnabled = [FBLPromise pendingPromise];
_promiseResolved = NO;
}
}
return self;
}
/*
* Legacy collection key that we provide for customers to disable Crash reporting.
* Customers can later turn on Crashlytics using Fabric.with if they choose to do so.
*
* This flag is unsupported for the "New SDK"
*/
- (BOOL)isLegacyDataCollectionKeyInPlist {
if ([_appInfo objectForKey:FIRCLSLegacyCrashlyticsCollectionKey]) {
return true;
}
return false;
}
// This functionality is called in the initializer before self is fully initialized,
// so a class method is used. The instance method below allows for a consistent clean API.
+ (BOOL)isCrashlyticsCollectionEnabledWithApp:(FIRApp *)app withAppInfo:(NSDictionary *)dict {
FIRCLSDataCollectionSetting stickySetting = [FIRCLSDataCollectionArbiter stickySetting];
if (stickySetting != FIRCLSDataCollectionSettingNotSet) {
return stickySetting == FIRCLSDataCollectionSettingEnabled;
}
id firebaseCrashlyticsCollectionEnabled = [dict objectForKey:FIRCLSCrashlyticsCollectionKey];
if ([firebaseCrashlyticsCollectionEnabled isKindOfClass:[NSString class]] ||
[firebaseCrashlyticsCollectionEnabled isKindOfClass:[NSNumber class]]) {
return [firebaseCrashlyticsCollectionEnabled boolValue];
}
return [app isDataCollectionDefaultEnabled];
}
- (BOOL)isCrashlyticsCollectionEnabled {
return [FIRCLSDataCollectionArbiter isCrashlyticsCollectionEnabledWithApp:_app
withAppInfo:_appInfo];
}
- (void)setCrashlyticsCollectionEnabled:(BOOL)enabled {
FIRCLSUserDefaults *userDefaults = [FIRCLSUserDefaults standardUserDefaults];
FIRCLSDataCollectionSetting setting =
enabled ? FIRCLSDataCollectionSettingEnabled : FIRCLSDataCollectionSettingDisabled;
[userDefaults setInteger:setting forKey:FIRCLSDataCollectionEnabledKey];
[userDefaults synchronize];
[_mutex lock];
if (enabled) {
if (!_promiseResolved) {
[_dataCollectionEnabled fulfill:nil];
_promiseResolved = YES;
}
} else {
if (_promiseResolved) {
_dataCollectionEnabled = [FBLPromise pendingPromise];
_promiseResolved = NO;
}
}
[_mutex unlock];
}
+ (FIRCLSDataCollectionSetting)stickySetting {
FIRCLSUserDefaults *userDefaults = [FIRCLSUserDefaults standardUserDefaults];
return [userDefaults integerForKey:FIRCLSDataCollectionEnabledKey];
}
- (FBLPromise *)waitForCrashlyticsCollectionEnabled {
FBLPromise *result = nil;
[_mutex lock];
result = _dataCollectionEnabled;
[_mutex unlock];
return result;
}
@end

View File

@ -0,0 +1,46 @@
// Copyright 2019 Google
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
/**
* A FIRCLSDataCollectionToken represents having permission to upload data. A data collection token
* is either valid or nil. Every function that directly initiates a network operation that will
* result in data collection must check to make sure it has been passed a valid token. Tokens should
* only be created when either (1) automatic data collection is enabled, or (2) the user has
* explicitly given permission to collect data for a particular purpose, using the API. For all the
* functions in between, the data collection token getting passed as an argument helps to document
* and enforce the flow of data collection permission through the SDK.
*/
@interface FIRCLSDataCollectionToken : NSObject
/**
* Creates a valid token. Only call this method when either (1) automatic data collection is
* enabled, or (2) the user has explicitly given permission to collect data for a particular
* purpose, using the API.
*/
+ (instancetype)validToken;
/**
* Use this to verify that a token is valid. If this is called on a nil instance, it will return
* false.
* @return true.
*/
- (BOOL)isValid;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,27 @@
// Copyright 2019 Google
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import "Crashlytics/Crashlytics/DataCollection/FIRCLSDataCollectionToken.h"
@implementation FIRCLSDataCollectionToken
+ (instancetype)validToken {
return [[FIRCLSDataCollectionToken alloc] init];
}
- (BOOL)isValid {
return YES;
}
@end

View File

@ -0,0 +1,42 @@
// Copyright 2019 Google
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import <Foundation/Foundation.h>
extern NSString *const FIRCLSUserDefaultsPathComponent;
@interface FIRCLSUserDefaults : NSObject
+ (instancetype)standardUserDefaults;
- (id)objectForKey:(NSString *)key;
- (NSString *)stringForKey:(NSString *)key;
- (BOOL)boolForKey:(NSString *)key;
- (NSInteger)integerForKey:(NSString *)key;
- (void)setObject:(id)object forKey:(NSString *)key;
- (void)setString:(NSString *)string forKey:(NSString *)key;
- (void)setBool:(BOOL)boolean forKey:(NSString *)key;
- (void)setInteger:(NSInteger)integer forKey:(NSString *)key;
- (void)removeObjectForKey:(NSString *)key;
- (void)removeAllObjects;
- (NSDictionary *)dictionaryRepresentation;
- (void)migrateFromNSUserDefaults:(NSArray *)keysToMigrate;
- (id)objectForKeyByMigratingFromNSUserDefaults:(NSString *)keyToMigrateOrNil;
- (void)synchronize;
@end

View File

@ -0,0 +1,372 @@
// Copyright 2019 Google
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import "Crashlytics/Crashlytics/FIRCLSUserDefaults/FIRCLSUserDefaults.h"
#import "Crashlytics/Crashlytics/Components/FIRCLSApplication.h"
#import "Crashlytics/Crashlytics/Helpers/FIRCLSLogger.h"
#define CLS_USER_DEFAULTS_SERIAL_DISPATCH_QUEUE "com.crashlytics.CLSUserDefaults.access"
#define CLS_USER_DEFAULTS_SYNC_QUEUE "com.crashlytics.CLSUserDefaults.io"
#define CLS_TARGET_CAN_WRITE_TO_DISK !TARGET_OS_TV
// These values are required to stay the same between versions of the SDK so
// that when end users upgrade, their crashlytics data is still saved on disk.
#if !CLS_TARGET_CAN_WRITE_TO_DISK
static NSString *const FIRCLSNSUserDefaultsDataDictionaryKey =
@"com.crashlytics.CLSUserDefaults.user-default-key.data-dictionary";
#endif
NSString *const FIRCLSUserDefaultsPathComponent = @"CLSUserDefaults";
/**
* This class is an isolated re-implementation of UserDefaults which isolates our storage
* from that of our customers. This solves a number of issues we have seen in production, firstly
* that customers often delete or clear UserDefaults, unintentionally deleting our data.
* Further, we have seen thread safety issues in production with UserDefaults, as well as a number
* of bugs related to accessing UserDefaults before the device has been unlocked due to the
* FileProtection of UserDefaults.
*/
@interface FIRCLSUserDefaults ()
@property(nonatomic, readwrite) BOOL synchronizeWroteToDisk;
#if CLS_TARGET_CAN_WRITE_TO_DISK
@property(nonatomic, copy, readonly) NSURL *directoryURL;
@property(nonatomic, copy, readonly) NSURL *fileURL;
#endif
@property(nonatomic, copy, readonly)
NSDictionary *persistedDataDictionary; // May only be safely accessed on the DictionaryQueue
@property(nonatomic, copy, readonly)
NSMutableDictionary *dataDictionary; // May only be safely accessed on the DictionaryQueue
@property(nonatomic, readonly) dispatch_queue_t
serialDictionaryQueue; // The queue on which all access to the dataDictionary occurs.
@property(nonatomic, readonly)
dispatch_queue_t synchronizationQueue; // The queue on which all disk access occurs.
@end
@implementation FIRCLSUserDefaults
#pragma mark - singleton
+ (instancetype)standardUserDefaults {
static FIRCLSUserDefaults *standardUserDefaults = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
standardUserDefaults = [[super allocWithZone:NULL] init];
});
return standardUserDefaults;
}
- (id)copyWithZone:(NSZone *)zone {
return self;
}
- (id)init {
if (self = [super init]) {
_serialDictionaryQueue =
dispatch_queue_create(CLS_USER_DEFAULTS_SERIAL_DISPATCH_QUEUE, DISPATCH_QUEUE_SERIAL);
_synchronizationQueue =
dispatch_queue_create(CLS_USER_DEFAULTS_SYNC_QUEUE, DISPATCH_QUEUE_SERIAL);
dispatch_sync(self.serialDictionaryQueue, ^{
#if CLS_TARGET_CAN_WRITE_TO_DISK
self->_directoryURL = [self generateDirectoryURL];
self->_fileURL = [[self->_directoryURL
URLByAppendingPathComponent:FIRCLSUserDefaultsPathComponent
isDirectory:NO] URLByAppendingPathExtension:@"plist"];
#endif
self->_persistedDataDictionary = [self loadDefaults];
if (!self->_persistedDataDictionary) {
self->_persistedDataDictionary = [NSDictionary dictionary];
}
self->_dataDictionary = [self->_persistedDataDictionary mutableCopy];
});
}
return self;
}
- (NSURL *)generateDirectoryURL {
NSURL *directoryBaseURL =
[[[NSFileManager defaultManager] URLsForDirectory:NSApplicationSupportDirectory
inDomains:NSUserDomainMask] lastObject];
NSString *hostAppBundleIdentifier = [self getEscapedAppBundleIdentifier];
return [self generateDirectoryURLForBaseURL:directoryBaseURL
hostAppBundleIdentifier:hostAppBundleIdentifier];
}
- (NSURL *)generateDirectoryURLForBaseURL:(NSURL *)directoryBaseURL
hostAppBundleIdentifier:(NSString *)hostAppBundleIdentifier {
NSURL *directoryURL = directoryBaseURL;
// On iOS NSApplicationSupportDirectory is contained in the app's bundle. On OSX, it is not (it is
// ~/Library/Application Support/). On OSX we create a directory
// ~/Library/Application Support/<app-identifier>/com.crashlytics/ for storing files.
// Mac App Store review process requires files to be written to
// ~/Library/Application Support/<app-identifier>/,
// so ~/Library/Application Support/com.crashlytics/<app-identifier>/ cannot be used.
#if !TARGET_OS_SIMULATOR && !TARGET_OS_EMBEDDED
if (hostAppBundleIdentifier) {
directoryURL = [directoryURL URLByAppendingPathComponent:hostAppBundleIdentifier];
}
#endif
directoryURL = [directoryURL URLByAppendingPathComponent:@"com.crashlytics"];
return directoryURL;
}
- (NSString *)getEscapedAppBundleIdentifier {
return FIRCLSApplicationGetBundleIdentifier();
}
#pragma mark - fetch object
- (id)objectForKey:(NSString *)key {
__block id result;
dispatch_sync(self.serialDictionaryQueue, ^{
result = [self->_dataDictionary objectForKey:key];
});
return result;
}
- (NSString *)stringForKey:(NSString *)key {
id result = [self objectForKey:key];
if (result != nil && [result isKindOfClass:[NSString class]]) {
return (NSString *)result;
} else {
return nil;
}
}
- (BOOL)boolForKey:(NSString *)key {
id result = [self objectForKey:key];
if (result != nil && [result isKindOfClass:[NSNumber class]]) {
return [(NSNumber *)result boolValue];
} else {
return NO;
}
}
// Defaults to 0
- (NSInteger)integerForKey:(NSString *)key {
id result = [self objectForKey:key];
if (result && [result isKindOfClass:[NSNumber class]]) {
return [(NSNumber *)result integerValue];
} else {
return 0;
}
}
#pragma mark - set object
- (void)setObject:(id)object forKey:(NSString *)key {
dispatch_sync(self.serialDictionaryQueue, ^{
[self->_dataDictionary setValue:object forKey:key];
});
}
- (void)setString:(NSString *)string forKey:(NSString *)key {
[self setObject:string forKey:key];
}
- (void)setBool:(BOOL)boolean forKey:(NSString *)key {
[self setObject:[NSNumber numberWithBool:boolean] forKey:key];
}
- (void)setInteger:(NSInteger)integer forKey:(NSString *)key {
[self setObject:[NSNumber numberWithInteger:integer] forKey:key];
}
#pragma mark - removing objects
- (void)removeObjectForKey:(NSString *)key {
dispatch_sync(self.serialDictionaryQueue, ^{
[self->_dataDictionary removeObjectForKey:key];
});
}
- (void)removeAllObjects {
dispatch_sync(self.serialDictionaryQueue, ^{
[self->_dataDictionary removeAllObjects];
});
}
#pragma mark - dictionary representation
- (NSDictionary *)dictionaryRepresentation {
__block NSDictionary *result;
dispatch_sync(self.serialDictionaryQueue, ^{
result = [self->_dataDictionary copy];
});
return result;
}
#pragma mark - synchronization
- (void)synchronize {
__block BOOL dirty = NO;
// only write to the disk if the dictionaries have changed
dispatch_sync(self.serialDictionaryQueue, ^{
dirty = ![self->_persistedDataDictionary isEqualToDictionary:self->_dataDictionary];
});
_synchronizeWroteToDisk = dirty;
if (!dirty) {
return;
}
NSDictionary *state = [self dictionaryRepresentation];
dispatch_sync(self.synchronizationQueue, ^{
#if CLS_TARGET_CAN_WRITE_TO_DISK
BOOL isDirectory = NO;
BOOL pathExists = [[NSFileManager defaultManager] fileExistsAtPath:[self->_directoryURL path]
isDirectory:&isDirectory];
if (!pathExists) {
NSError *error;
if (![[NSFileManager defaultManager] createDirectoryAtURL:self->_directoryURL
withIntermediateDirectories:YES
attributes:nil
error:&error]) {
FIRCLSErrorLog(@"Failed to create directory with error: %@", error);
}
}
if (![state writeToURL:self->_fileURL atomically:YES]) {
FIRCLSErrorLog(@"Unable to open file for writing at path %@", [self->_fileURL path]);
} else {
#if TARGET_OS_IOS
// We disable NSFileProtection on our file in order to allow us to access it even if the
// device is locked.
NSError *error;
if (![[NSFileManager defaultManager]
setAttributes:@{NSFileProtectionKey : NSFileProtectionNone}
ofItemAtPath:[self->_fileURL path]
error:&error]) {
FIRCLSErrorLog(@"Error setting NSFileProtection: %@", error);
}
#endif
}
#else
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:state forKey:FIRCLSNSUserDefaultsDataDictionaryKey];
[defaults synchronize];
#endif
});
dispatch_sync(self.serialDictionaryQueue, ^{
self->_persistedDataDictionary = [self->_dataDictionary copy];
});
}
- (NSDictionary *)loadDefaults {
__block NSDictionary *state = nil;
dispatch_sync(self.synchronizationQueue, ^{
#if CLS_TARGET_CAN_WRITE_TO_DISK
BOOL isDirectory = NO;
BOOL fileExists = [[NSFileManager defaultManager] fileExistsAtPath:[self->_fileURL path]
isDirectory:&isDirectory];
if (fileExists && !isDirectory) {
state = [NSDictionary dictionaryWithContentsOfURL:self->_fileURL];
if (nil == state) {
FIRCLSErrorLog(@"Failed to read existing UserDefaults file");
}
} else if (!fileExists) {
// No file found. This is expected on first launch.
} else if (fileExists && isDirectory) {
FIRCLSErrorLog(@"Found directory where file expected. Removing conflicting directory");
NSError *error;
if (![[NSFileManager defaultManager] removeItemAtURL:self->_fileURL error:&error]) {
FIRCLSErrorLog(@"Error removing conflicting directory: %@", error);
}
}
#else
state = [[NSUserDefaults standardUserDefaults] dictionaryForKey:FIRCLSNSUserDefaultsDataDictionaryKey];
#endif
});
return state;
}
#pragma mark - migration
// This method migrates all keys specified from UserDefaults to FIRCLSUserDefaults
// To do so, we copy all known key-value pairs into FIRCLSUserDefaults, synchronize it, then
// remove the keys from UserDefaults and synchronize it.
- (void)migrateFromNSUserDefaults:(NSArray *)keysToMigrate {
BOOL didFindKeys = NO;
// First, copy all of the keysToMigrate which are stored UserDefaults
for (NSString *key in keysToMigrate) {
id oldValue = [[NSUserDefaults standardUserDefaults] objectForKey:(NSString *)key];
if (nil != oldValue) {
didFindKeys = YES;
[self setObject:oldValue forKey:key];
}
}
if (didFindKeys) {
// First synchronize FIRCLSUserDefaults such that all keysToMigrate in UserDefaults are stored
// in FIRCLSUserDefaults. At this point, data is duplicated.
[[FIRCLSUserDefaults standardUserDefaults] synchronize];
for (NSString *key in keysToMigrate) {
[[NSUserDefaults standardUserDefaults] removeObjectForKey:(NSString *)key];
}
// This should be our last interaction with UserDefaults. All data is migrated into
// FIRCLSUserDefaults
[[NSUserDefaults standardUserDefaults] synchronize];
}
}
// This method first queries FIRCLSUserDefaults to see if the key exist, and upon failure,
// searches for the key in UserDefaults, and migrates it if found.
- (id)objectForKeyByMigratingFromNSUserDefaults:(NSString *)keyToMigrateOrNil {
if (!keyToMigrateOrNil) {
return nil;
}
id clsUserDefaultsValue = [self objectForKey:keyToMigrateOrNil];
if (clsUserDefaultsValue != nil) {
return clsUserDefaultsValue; // if the value exists in FIRCLSUserDefaults, return it.
}
id oldNSUserDefaultsValue =
[[NSUserDefaults standardUserDefaults] objectForKey:keyToMigrateOrNil];
if (!oldNSUserDefaultsValue) {
return nil; // if the value also does not exist in UserDefaults, return nil.
}
// Otherwise, the key exists in UserDefaults. Migrate it to FIRCLSUserDefaults
// and then return the associated value.
// First store it in FIRCLSUserDefaults so in the event of a crash, data is not lost.
[self setObject:oldNSUserDefaultsValue forKey:keyToMigrateOrNil];
[[FIRCLSUserDefaults standardUserDefaults] synchronize];
[[NSUserDefaults standardUserDefaults] removeObjectForKey:keyToMigrateOrNil];
[[NSUserDefaults standardUserDefaults] synchronize];
return oldNSUserDefaultsValue;
}
@end

View File

@ -0,0 +1,23 @@
// Copyright 2019 Google
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import <Foundation/Foundation.h>
#import "Crashlytics/Crashlytics/FIRCLSUserDefaults/FIRCLSUserDefaults.h"
@interface FIRCLSUserDefaults (Private)
- (BOOL)synchronizeWroteToDisk;
- (NSDictionary *)loadDefaults;
- (NSURL *)generateDirectoryURLForBaseURL:(NSURL *)directoryBaseURL
hostAppBundleIdentifier:(NSString *)hostAppBundleIdentifier;
@end

View File

@ -0,0 +1,448 @@
// Copyright 2019 Google
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <stdatomic.h>
#if __has_include(<FBLPromises/FBLPromises.h>)
#import <FBLPromises/FBLPromises.h>
#else
#import "FBLPromises.h"
#endif
#include "Crashlytics/Crashlytics/Components/FIRCLSCrashedMarkerFile.h"
#include "Crashlytics/Crashlytics/Components/FIRCLSGlobals.h"
#import "Crashlytics/Crashlytics/Components/FIRCLSHost.h"
#include "Crashlytics/Crashlytics/Components/FIRCLSUserLogging.h"
#import "Crashlytics/Crashlytics/DataCollection/FIRCLSDataCollectionArbiter.h"
#import "Crashlytics/Crashlytics/DataCollection/FIRCLSDataCollectionToken.h"
#import "Crashlytics/Crashlytics/FIRCLSUserDefaults/FIRCLSUserDefaults.h"
#include "Crashlytics/Crashlytics/Handlers/FIRCLSException.h"
#import "Crashlytics/Crashlytics/Helpers/FIRCLSDefines.h"
#include "Crashlytics/Crashlytics/Helpers/FIRCLSUtility.h"
#import "Crashlytics/Crashlytics/Models/FIRCLSExecutionIdentifierModel.h"
#import "Crashlytics/Crashlytics/Models/FIRCLSFileManager.h"
#import "Crashlytics/Crashlytics/Models/FIRCLSSettings.h"
#import "Crashlytics/Crashlytics/Settings/Models/FIRCLSApplicationIdentifierModel.h"
#import "Crashlytics/Crashlytics/Helpers/FIRCLSLogger.h"
#import "Crashlytics/Shared/FIRCLSByteUtility.h"
#import "Crashlytics/Shared/FIRCLSConstants.h"
#import "Crashlytics/Shared/FIRCLSFABHost.h"
#import "Crashlytics/Crashlytics/Controllers/FIRCLSAnalyticsManager.h"
#import "Crashlytics/Crashlytics/Controllers/FIRCLSContextManager.h"
#import "Crashlytics/Crashlytics/Controllers/FIRCLSExistingReportManager.h"
#import "Crashlytics/Crashlytics/Controllers/FIRCLSManagerData.h"
#import "Crashlytics/Crashlytics/Controllers/FIRCLSNotificationManager.h"
#import "Crashlytics/Crashlytics/Controllers/FIRCLSReportManager.h"
#import "Crashlytics/Crashlytics/Controllers/FIRCLSReportUploader.h"
#import "Crashlytics/Crashlytics/Controllers/FIRCLSRolloutsPersistenceManager.h"
#import "Crashlytics/Crashlytics/Private/FIRCLSExistingReportManager_Private.h"
#import "Crashlytics/Crashlytics/Private/FIRCLSOnDemandModel_Private.h"
#import "Crashlytics/Crashlytics/Private/FIRExceptionModel_Private.h"
#import "FirebaseCore/Extension/FirebaseCoreInternal.h"
#import "FirebaseInstallations/Source/Library/Private/FirebaseInstallationsInternal.h"
#import "Interop/Analytics/Public/FIRAnalyticsInterop.h"
#import <GoogleDataTransport/GoogleDataTransport.h>
@import FirebaseSessions;
@import FirebaseRemoteConfigInterop;
#if SWIFT_PACKAGE
@import FirebaseCrashlyticsSwift;
#elif __has_include(<FirebaseCrashlytics/FirebaseCrashlytics-Swift.h>)
#import <FirebaseCrashlytics/FirebaseCrashlytics-Swift.h>
#elif __has_include("FirebaseCrashlytics-Swift.h")
// If frameworks are not available, fall back to importing the header as it
// should be findable from a header search path pointing to the build
// directory. See #12611 for more context.
#import "FirebaseCrashlytics-Swift.h"
#endif
#if TARGET_OS_IPHONE
#import <UIKit/UIKit.h>
#endif
FIRCLSContext _firclsContext;
dispatch_queue_t _firclsLoggingQueue;
dispatch_queue_t _firclsBinaryImageQueue;
dispatch_queue_t _firclsExceptionQueue;
static atomic_bool _hasInitializedInstance;
NSString *const FIRCLSGoogleTransportMappingID = @"1206";
/// Empty protocol to register with FirebaseCore's component system.
@protocol FIRCrashlyticsInstanceProvider <NSObject>
@end
@interface FIRCrashlytics () <FIRLibrary,
FIRCrashlyticsInstanceProvider,
FIRSessionsSubscriber,
FIRRolloutsStateSubscriber>
@property(nonatomic) BOOL didPreviouslyCrash;
@property(nonatomic, copy) NSString *googleAppID;
@property(nonatomic) FIRCLSDataCollectionArbiter *dataArbiter;
@property(nonatomic) FIRCLSFileManager *fileManager;
@property(nonatomic) FIRCLSReportManager *reportManager;
@property(nonatomic) FIRCLSReportUploader *reportUploader;
@property(nonatomic, strong) FIRCLSExistingReportManager *existingReportManager;
@property(nonatomic, strong) FIRCLSAnalyticsManager *analyticsManager;
@property(nonatomic, strong) FIRCLSRemoteConfigManager *remoteConfigManager;
// Dependencies common to each of the Controllers
@property(nonatomic, strong) FIRCLSManagerData *managerData;
@end
@implementation FIRCrashlytics
#pragma mark - Singleton Support
- (instancetype)initWithApp:(FIRApp *)app
appInfo:(NSDictionary *)appInfo
installations:(FIRInstallations *)installations
analytics:(id<FIRAnalyticsInterop>)analytics
sessions:(id<FIRSessionsProvider>)sessions
remoteConfig:(id<FIRRemoteConfigInterop>)remoteConfig {
self = [super init];
if (self) {
bool expectedCalled = NO;
if (!atomic_compare_exchange_strong(&_hasInitializedInstance, &expectedCalled, YES)) {
FIRCLSErrorLog(@"Cannot instantiate more than one instance of Crashlytics.");
return nil;
}
NSLog(@"[Firebase/Crashlytics] Version %@", FIRCLSSDKVersion());
FIRCLSDeveloperLog("Crashlytics", @"Running on %@, %@ (%@)", FIRCLSHostModelInfo(),
FIRCLSHostOSDisplayVersion(), FIRCLSHostOSBuildVersion());
GDTCORTransport *googleTransport =
[[GDTCORTransport alloc] initWithMappingID:FIRCLSGoogleTransportMappingID
transformers:nil
target:kGDTCORTargetCSH];
_fileManager = [[FIRCLSFileManager alloc] init];
_googleAppID = app.options.googleAppID;
_dataArbiter = [[FIRCLSDataCollectionArbiter alloc] initWithApp:app withAppInfo:appInfo];
FIRCLSApplicationIdentifierModel *appModel = [[FIRCLSApplicationIdentifierModel alloc] init];
FIRCLSSettings *settings = [[FIRCLSSettings alloc] initWithFileManager:_fileManager
appIDModel:appModel];
FIRCLSOnDemandModel *onDemandModel =
[[FIRCLSOnDemandModel alloc] initWithFIRCLSSettings:settings fileManager:_fileManager];
_managerData = [[FIRCLSManagerData alloc] initWithGoogleAppID:_googleAppID
googleTransport:googleTransport
installations:installations
analytics:analytics
fileManager:_fileManager
dataArbiter:_dataArbiter
settings:settings
onDemandModel:onDemandModel];
if (sessions) {
FIRCLSDebugLog(@"Registering Sessions SDK subscription for session data");
// Subscription should be made after the DataCollectionArbiter
// is initialized so that the Sessions SDK can immediately get
// the data collection state.
//
// It should also be made after managerData is initialized so
// that the ContextManager can accept data
[sessions registerWithSubscriber:self];
}
_reportUploader = [[FIRCLSReportUploader alloc] initWithManagerData:_managerData];
_existingReportManager =
[[FIRCLSExistingReportManager alloc] initWithManagerData:_managerData
reportUploader:_reportUploader];
_analyticsManager = [[FIRCLSAnalyticsManager alloc] initWithAnalytics:analytics];
_reportManager = [[FIRCLSReportManager alloc] initWithManagerData:_managerData
existingReportManager:_existingReportManager
analyticsManager:_analyticsManager];
_didPreviouslyCrash = [_fileManager didCrashOnPreviousExecution];
// Process did crash during previous execution
if (_didPreviouslyCrash) {
// Delete the crash file marker in the background ensure start up is as fast as possible
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
NSString *crashedMarkerFileFullPath = [[self.fileManager rootPath]
stringByAppendingPathComponent:[NSString
stringWithUTF8String:FIRCLSCrashedMarkerFileName]];
[self.fileManager removeItemAtPath:crashedMarkerFileFullPath];
});
}
[[[_reportManager startWithProfiling] then:^id _Nullable(NSNumber *_Nullable value) {
if (![value boolValue]) {
FIRCLSErrorLog(@"Crash reporting could not be initialized");
}
return value;
}] catch:^void(NSError *error) {
FIRCLSErrorLog(@"Crash reporting failed to initialize with error: %@", error);
}];
// RemoteConfig subscription should be made after session report directory created.
if (remoteConfig) {
FIRCLSDebugLog(@"Registering RemoteConfig SDK subscription for rollouts data");
FIRCLSRolloutsPersistenceManager *persistenceManager =
[[FIRCLSRolloutsPersistenceManager alloc]
initWithFileManager:_fileManager
andQueue:dispatch_queue_create(
"com.google.firebase.FIRCLSRolloutsPersistence",
DISPATCH_QUEUE_SERIAL)];
_remoteConfigManager =
[[FIRCLSRemoteConfigManager alloc] initWithRemoteConfig:remoteConfig
persistenceDelegate:persistenceManager];
[remoteConfig registerRolloutsStateSubscriber:self for:FIRRemoteConfigConstants.FIRNamespaceGoogleMobilePlatform];
}
}
return self;
}
+ (void)load {
[FIRApp registerInternalLibrary:(Class<FIRLibrary>)self withName:@"firebase-crashlytics"];
[FIRSessionsDependencies addDependencyWithName:FIRSessionsSubscriberNameCrashlytics];
}
+ (NSArray<FIRComponent *> *)componentsToRegister {
FIRComponentCreationBlock creationBlock =
^id _Nullable(FIRComponentContainer *container, BOOL *isCacheable) {
if (!container.app.isDefaultApp) {
FIRCLSErrorLog(@"Crashlytics must be used with the default Firebase app.");
return nil;
}
id<FIRAnalyticsInterop> analytics = FIR_COMPONENT(FIRAnalyticsInterop, container);
id<FIRSessionsProvider> sessions = FIR_COMPONENT(FIRSessionsProvider, container);
id<FIRRemoteConfigInterop> remoteConfig = FIR_COMPONENT(FIRRemoteConfigInterop, container);
FIRInstallations *installations = [FIRInstallations installationsWithApp:container.app];
*isCacheable = YES;
return [[FIRCrashlytics alloc] initWithApp:container.app
appInfo:NSBundle.mainBundle.infoDictionary
installations:installations
analytics:analytics
sessions:sessions
remoteConfig:remoteConfig];
};
FIRComponent *component =
[FIRComponent componentWithProtocol:@protocol(FIRCrashlyticsInstanceProvider)
instantiationTiming:FIRInstantiationTimingEagerInDefaultApp
creationBlock:creationBlock];
return @[ component ];
}
+ (instancetype)crashlytics {
// The container will return the same instance since isCacheable is set
FIRApp *defaultApp = [FIRApp defaultApp]; // Missing configure will be logged here.
// Get the instance from the `FIRApp`'s container. This will create a new instance the
// first time it is called, and since `isCacheable` is set in the component creation
// block, it will return the existing instance on subsequent calls.
id<FIRCrashlyticsInstanceProvider> instance =
FIR_COMPONENT(FIRCrashlyticsInstanceProvider, defaultApp.container);
// In the component creation block, we return an instance of `FIRCrashlytics`. Cast it and
// return it.
return (FIRCrashlytics *)instance;
}
- (void)setCrashlyticsCollectionEnabled:(BOOL)enabled {
[self.dataArbiter setCrashlyticsCollectionEnabled:enabled];
}
- (BOOL)isCrashlyticsCollectionEnabled {
return [self.dataArbiter isCrashlyticsCollectionEnabled];
}
#pragma mark - API: didCrashDuringPreviousExecution
- (BOOL)didCrashDuringPreviousExecution {
return self.didPreviouslyCrash;
}
- (void)processDidCrashDuringPreviousExecution {
NSString *crashedMarkerFileName = [NSString stringWithUTF8String:FIRCLSCrashedMarkerFileName];
NSString *crashedMarkerFileFullPath =
[[self.fileManager rootPath] stringByAppendingPathComponent:crashedMarkerFileName];
self.didPreviouslyCrash = [self.fileManager fileExistsAtPath:crashedMarkerFileFullPath];
if (self.didPreviouslyCrash) {
// Delete the crash file marker in the background ensure start up is as fast as possible
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
[self.fileManager removeItemAtPath:crashedMarkerFileFullPath];
});
}
}
#pragma mark - API: Logging
- (void)log:(NSString *)msg {
FIRCLSLog(@"%@", msg);
}
- (void)logWithFormat:(NSString *)format, ... {
va_list args;
va_start(args, format);
[self logWithFormat:format arguments:args];
va_end(args);
}
- (void)logWithFormat:(NSString *)format arguments:(va_list)args {
[self log:[[NSString alloc] initWithFormat:format arguments:args]];
}
#pragma mark - API: Accessors
- (void)checkForUnsentReportsWithCompletion:(void (^)(BOOL))completion {
[[self.reportManager checkForUnsentReports]
then:^id _Nullable(FIRCrashlyticsReport *_Nullable value) {
completion(value ? true : false);
return nil;
}];
}
- (void)checkAndUpdateUnsentReportsWithCompletion:
(void (^)(FIRCrashlyticsReport *_Nonnull))completion {
[[self.reportManager checkForUnsentReports]
then:^id _Nullable(FIRCrashlyticsReport *_Nullable value) {
completion(value);
return nil;
}];
}
- (void)sendUnsentReports {
[self.reportManager sendUnsentReports];
}
- (void)deleteUnsentReports {
[self.reportManager deleteUnsentReports];
}
#pragma mark - API: setUserID
- (void)setUserID:(nullable NSString *)userID {
FIRCLSUserLoggingRecordInternalKeyValue(FIRCLSUserIdentifierKey, userID);
}
#pragma mark - API: setCustomValue
- (void)setCustomValue:(nullable id)value forKey:(NSString *)key {
FIRCLSUserLoggingRecordUserKeyValue(key, value);
}
- (void)setCustomKeysAndValues:(NSDictionary *)keysAndValues {
FIRCLSUserLoggingRecordUserKeysAndValues(keysAndValues);
}
#pragma mark - API: Development Platform
// These two methods are deprecated by our own API, so
// its ok to implement them
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-implementations"
+ (void)setDevelopmentPlatformName:(NSString *)name {
[[self crashlytics] setDevelopmentPlatformName:name];
}
+ (void)setDevelopmentPlatformVersion:(NSString *)version {
[[self crashlytics] setDevelopmentPlatformVersion:version];
}
#pragma clang diagnostic pop
- (NSString *)developmentPlatformName {
FIRCLSErrorLog(@"developmentPlatformName is write-only");
return nil;
}
- (void)setDevelopmentPlatformName:(NSString *)developmentPlatformName {
FIRCLSUserLoggingRecordInternalKeyValue(FIRCLSDevelopmentPlatformNameKey,
developmentPlatformName);
}
- (NSString *)developmentPlatformVersion {
FIRCLSErrorLog(@"developmentPlatformVersion is write-only");
return nil;
}
- (void)setDevelopmentPlatformVersion:(NSString *)developmentPlatformVersion {
FIRCLSUserLoggingRecordInternalKeyValue(FIRCLSDevelopmentPlatformVersionKey,
developmentPlatformVersion);
}
#pragma mark - API: Errors and Exceptions
- (void)recordError:(NSError *)error {
[self recordError:error userInfo:nil];
}
- (void)recordError:(NSError *)error userInfo:(NSDictionary<NSString *, id> *)userInfo {
NSString *rolloutsInfoJSON = [_remoteConfigManager getRolloutAssignmentsEncodedJsonString];
FIRCLSUserLoggingRecordError(error, userInfo, rolloutsInfoJSON);
}
- (void)recordExceptionModel:(FIRExceptionModel *)exceptionModel {
NSString *rolloutsInfoJSON = [_remoteConfigManager getRolloutAssignmentsEncodedJsonString];
FIRCLSExceptionRecordModel(exceptionModel, rolloutsInfoJSON);
}
- (void)recordOnDemandExceptionModel:(FIRExceptionModel *)exceptionModel {
[self.managerData.onDemandModel
recordOnDemandExceptionIfQuota:exceptionModel
withDataCollectionEnabled:[self.dataArbiter isCrashlyticsCollectionEnabled]
usingExistingReportManager:self.existingReportManager];
}
#pragma mark - FIRSessionsSubscriber
- (void)onSessionChanged:(FIRSessionDetails *_Nonnull)session {
FIRCLSDebugLog(@"Session ID changed: %@", session.sessionId.copy);
[self.managerData.contextManager setAppQualitySessionId:session.sessionId.copy];
}
- (BOOL)isDataCollectionEnabled {
return self.dataArbiter.isCrashlyticsCollectionEnabled;
}
- (FIRSessionsSubscriberName)sessionsSubscriberName {
return FIRSessionsSubscriberNameCrashlytics;
}
#pragma mark - FIRRolloutsStateSubscriber
- (void)rolloutsStateDidChange:(FIRRolloutsState *_Nonnull)rolloutsState {
if (!_remoteConfigManager) {
FIRCLSDebugLog(@"rolloutsStateDidChange gets called without init the rc manager.");
return;
}
NSString *currentReportID = _managerData.executionIDModel.executionID;
[_remoteConfigManager updateRolloutsStateWithRolloutsState:rolloutsState
reportID:currentReportID];
}
@end

View File

@ -0,0 +1,197 @@
// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import "Crashlytics/Crashlytics/Public/FirebaseCrashlytics/FIRCrashlyticsReport.h"
#import "Crashlytics/Crashlytics/Components/FIRCLSContext.h"
#import "Crashlytics/Crashlytics/Components/FIRCLSGlobals.h"
#import "Crashlytics/Crashlytics/Helpers/FIRCLSLogger.h"
#import "Crashlytics/Crashlytics/Models/FIRCLSInternalReport.h"
@interface FIRCrashlyticsReport () {
NSString *_reportID;
NSDate *_dateCreated;
BOOL _hasCrash;
FIRCLSUserLoggingABStorage _logStorage;
const char *_activeLogPath;
uint32_t _internalKVCounter;
FIRCLSUserLoggingKVStorage _internalKVStorage;
uint32_t _userKVCounter;
FIRCLSUserLoggingKVStorage _userKVStorage;
}
@property(nonatomic, strong) FIRCLSInternalReport *internalReport;
@end
@implementation FIRCrashlyticsReport
- (instancetype)initWithInternalReport:(FIRCLSInternalReport *)internalReport {
self = [super init];
if (!self) {
return nil;
}
_internalReport = internalReport;
_reportID = [[internalReport identifier] copy];
_dateCreated = [[internalReport dateCreated] copy];
_hasCrash = [internalReport isCrash];
_logStorage.maxSize = _firclsContext.readonly->logging.logStorage.maxSize;
_logStorage.maxEntries = _firclsContext.readonly->logging.logStorage.maxEntries;
_logStorage.restrictBySize = _firclsContext.readonly->logging.logStorage.restrictBySize;
_logStorage.entryCount = _firclsContext.readonly->logging.logStorage.entryCount;
_logStorage.aPath = [FIRCrashlyticsReport filesystemPathForContentFile:FIRCLSReportLogAFile
inInternalReport:internalReport];
_logStorage.bPath = [FIRCrashlyticsReport filesystemPathForContentFile:FIRCLSReportLogBFile
inInternalReport:internalReport];
_activeLogPath = _logStorage.aPath;
// TODO: correct kv accounting
// The internal report will have non-zero compacted and incremental keys. The right thing to do
// is count them, so we can kick off compactions/pruning at the right times. By
// setting this value to zero, we're allowing more entries to be made than there really
// should be. Not the end of the world, but we should do better eventually.
_internalKVCounter = 0;
_userKVCounter = 0;
_userKVStorage.maxCount = _firclsContext.readonly->logging.userKVStorage.maxCount;
_userKVStorage.maxIncrementalCount =
_firclsContext.readonly->logging.userKVStorage.maxIncrementalCount;
_userKVStorage.compactedPath =
[FIRCrashlyticsReport filesystemPathForContentFile:FIRCLSReportUserCompactedKVFile
inInternalReport:internalReport];
_userKVStorage.incrementalPath =
[FIRCrashlyticsReport filesystemPathForContentFile:FIRCLSReportUserIncrementalKVFile
inInternalReport:internalReport];
_internalKVStorage.maxCount = _firclsContext.readonly->logging.internalKVStorage.maxCount;
_internalKVStorage.maxIncrementalCount =
_firclsContext.readonly->logging.internalKVStorage.maxIncrementalCount;
_internalKVStorage.compactedPath =
[FIRCrashlyticsReport filesystemPathForContentFile:FIRCLSReportInternalCompactedKVFile
inInternalReport:internalReport];
_internalKVStorage.incrementalPath =
[FIRCrashlyticsReport filesystemPathForContentFile:FIRCLSReportInternalIncrementalKVFile
inInternalReport:internalReport];
return self;
}
+ (const char *)filesystemPathForContentFile:(NSString *)contentFile
inInternalReport:(FIRCLSInternalReport *)internalReport {
if (!internalReport) {
return nil;
}
// We need to be defensive because strdup will crash
// if given a nil.
NSString *objCString = [internalReport pathForContentFile:contentFile];
const char *fileSystemString = [objCString fileSystemRepresentation];
if (!objCString || !fileSystemString) {
return nil;
}
// Paths need to be duplicated because fileSystemRepresentation returns C strings
// that are freed outside of this context.
return strdup(fileSystemString);
}
- (BOOL)checkContextForMethod:(NSString *)methodName {
if (!FIRCLSContextIsInitialized()) {
FIRCLSErrorLog(@"%@ failed for FIRCrashlyticsReport because Crashlytics context isn't "
@"initialized.",
methodName);
return false;
}
return true;
}
#pragma mark - API: Getters
- (NSString *)reportID {
return _reportID;
}
- (NSDate *)dateCreated {
return _dateCreated;
}
- (BOOL)hasCrash {
return _hasCrash;
}
#pragma mark - API: Logging
- (void)log:(NSString *)msg {
if (![self checkContextForMethod:@"log:"]) {
return;
}
FIRCLSLogToStorage(&_logStorage, &_activeLogPath, @"%@", msg);
}
- (void)logWithFormat:(NSString *)format, ... {
if (![self checkContextForMethod:@"logWithFormat:"]) {
return;
}
va_list args;
va_start(args, format);
[self logWithFormat:format arguments:args];
va_end(args);
}
- (void)logWithFormat:(NSString *)format arguments:(va_list)args {
if (![self checkContextForMethod:@"logWithFormat:arguments:"]) {
return;
}
[self log:[[NSString alloc] initWithFormat:format arguments:args]];
}
#pragma mark - API: setUserID
- (void)setUserID:(nullable NSString *)userID {
if (![self checkContextForMethod:@"setUserID:"]) {
return;
}
FIRCLSUserLoggingRecordKeyValue(FIRCLSUserIdentifierKey, userID, &_internalKVStorage,
&_internalKVCounter);
}
#pragma mark - API: setCustomValue
- (void)setCustomValue:(nullable id)value forKey:(NSString *)key {
if (![self checkContextForMethod:@"setCustomValue:forKey:"]) {
return;
}
FIRCLSUserLoggingRecordKeyValue(key, value, &_userKVStorage, &_userKVCounter);
}
- (void)setCustomKeysAndValues:(NSDictionary *)keysAndValues {
if (![self checkContextForMethod:@"setCustomKeysAndValues:"]) {
return;
}
FIRCLSUserLoggingRecordKeysAndValues(keysAndValues, &_userKVStorage, &_userKVCounter);
}
@end

View File

@ -0,0 +1,44 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import "Crashlytics/Crashlytics/Public/FirebaseCrashlytics/FIRExceptionModel.h"
@interface FIRExceptionModel ()
@property(nonatomic, copy) NSString *name;
@property(nonatomic, copy) NSString *reason;
@property(nonatomic) BOOL isFatal;
@property(nonatomic) BOOL onDemand;
@end
@implementation FIRExceptionModel
- (instancetype)initWithName:(NSString *)name reason:(NSString *)reason {
self = [super init];
if (!self) {
return nil;
}
_name = [name copy];
_reason = [reason copy];
return self;
}
+ (instancetype)exceptionModelWithName:(NSString *)name reason:(NSString *)reason {
return [[FIRExceptionModel alloc] initWithName:name reason:reason];
}
@end

View File

@ -0,0 +1,94 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import "Crashlytics/Crashlytics/Private/FIRStackFrame_Private.h"
@interface FIRStackFrame ()
@property(nonatomic, copy, nullable) NSString *symbol;
@property(nonatomic, copy, nullable) NSString *rawSymbol;
@property(nonatomic, copy, nullable) NSString *library;
@property(nonatomic, copy, nullable) NSString *fileName;
@property(nonatomic, assign) uint32_t lineNumber;
@property(nonatomic, assign) uint64_t offset;
@property(nonatomic, assign) uint64_t address;
@property(nonatomic, assign) BOOL isSymbolicated;
@end
@implementation FIRStackFrame
#pragma mark - Public Methods
- (instancetype)initWithSymbol:(NSString *)symbol file:(NSString *)file line:(NSInteger)line {
self = [super init];
if (!self) {
return nil;
}
_symbol = [symbol copy];
_fileName = [file copy];
_lineNumber = (uint32_t)line;
_isSymbolicated = true;
return self;
}
+ (instancetype)stackFrameWithAddress:(NSUInteger)address {
FIRStackFrame *frame = [self stackFrame];
[frame setAddress:address];
return frame;
}
+ (instancetype)stackFrameWithSymbol:(NSString *)symbol file:(NSString *)file line:(NSInteger)line {
return [[FIRStackFrame alloc] initWithSymbol:symbol file:file line:line];
}
#pragma mark - Internal Methods
+ (instancetype)stackFrame {
return [[self alloc] init];
}
+ (instancetype)stackFrameWithSymbol:(NSString *)symbol {
FIRStackFrame *frame = [self stackFrame];
frame.symbol = symbol;
frame.rawSymbol = symbol;
return frame;
}
#pragma mark - Overrides
- (NSString *)description {
if (self.isSymbolicated) {
return [NSString
stringWithFormat:@"{%@ - %@:%u}", [self fileName], [self symbol], [self lineNumber]];
}
if (self.fileName) {
return [NSString stringWithFormat:@"{[0x%llx] %@ - %@:%u}", [self address], [self fileName],
[self symbol], [self lineNumber]];
}
return [NSString
stringWithFormat:@"{[0x%llx + %u] %@}", [self address], [self lineNumber], [self symbol]];
}
@end

View File

@ -0,0 +1,82 @@
// Copyright 2019 Google
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#pragma once
#include <stdint.h>
#include <sys/cdefs.h>
#ifdef __OBJC__
#import <Foundation/Foundation.h>
@class FIRStackFrame;
@class FIRExceptionModel;
#endif
#define CLS_EXCEPTION_STRING_LENGTH_MAX (1024 * 16)
typedef enum {
FIRCLSExceptionTypeUnknown = 0,
FIRCLSExceptionTypeObjectiveC = 1,
FIRCLSExceptionTypeCpp = 2,
// 3 was FIRCLSExceptionTypeJavascript
// Keeping these numbers the same just to be safe
FIRCLSExceptionTypeCustom = 4
} FIRCLSExceptionType;
typedef struct {
const char* path;
void (*originalTerminateHandler)(void);
#if !TARGET_OS_IPHONE
void* originalNSApplicationReportException;
#endif
uint32_t maxCustomExceptions;
} FIRCLSExceptionReadOnlyContext;
typedef struct {
uint32_t customExceptionCount;
} FIRCLSExceptionWritableContext;
__BEGIN_DECLS
void FIRCLSExceptionInitialize(FIRCLSExceptionReadOnlyContext* roContext,
FIRCLSExceptionWritableContext* rwContext);
void FIRCLSExceptionCheckHandlers(void* delegate);
void FIRCLSExceptionRaiseTestObjCException(void) __attribute((noreturn));
void FIRCLSExceptionRaiseTestCppException(void) __attribute((noreturn));
#ifdef __OBJC__
void FIRCLSExceptionRecordModel(FIRExceptionModel* exceptionModel, NSString* rolloutsInfoJSON);
NSString* FIRCLSExceptionRecordOnDemandModel(FIRExceptionModel* exceptionModel,
int previousRecordedOnDemandExceptions,
int previousDroppedOnDemandExceptions);
void FIRCLSExceptionRecordNSException(NSException* exception);
void FIRCLSExceptionRecord(FIRCLSExceptionType type,
const char* name,
const char* reason,
NSArray<FIRStackFrame*>* frames,
NSString* rolloutsInfoJSON);
NSString* FIRCLSExceptionRecordOnDemand(FIRCLSExceptionType type,
const char* name,
const char* reason,
NSArray<FIRStackFrame*>* frames,
BOOL fatal,
int previousRecordedOnDemandExceptions,
int previousDroppedOnDemandExceptions);
#endif
__END_DECLS

View File

@ -0,0 +1,538 @@
// Copyright 2019 Google
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import <Foundation/Foundation.h>
#include "Crashlytics/Crashlytics/Handlers/FIRCLSException.h"
#import "Crashlytics/Crashlytics/Private/FIRExceptionModel_Private.h"
#import "Crashlytics/Crashlytics/Private/FIRStackFrame_Private.h"
#include "Crashlytics/Crashlytics/Components/FIRCLSApplication.h"
#include "Crashlytics/Crashlytics/Components/FIRCLSContext.h"
#include "Crashlytics/Crashlytics/Components/FIRCLSGlobals.h"
#include "Crashlytics/Crashlytics/Components/FIRCLSProcess.h"
#import "Crashlytics/Crashlytics/Components/FIRCLSUserLogging.h"
#include "Crashlytics/Crashlytics/Handlers/FIRCLSHandler.h"
#include "Crashlytics/Crashlytics/Helpers/FIRCLSFile.h"
#import "Crashlytics/Crashlytics/Helpers/FIRCLSLogger.h"
#import "Crashlytics/Crashlytics/Helpers/FIRCLSUtility.h"
#import "Crashlytics/Crashlytics/Controllers/FIRCLSReportManager_Private.h"
#import "Crashlytics/Crashlytics/Models/FIRCLSExecutionIdentifierModel.h"
#import "Crashlytics/Crashlytics/Models/FIRCLSFileManager.h"
#import "Crashlytics/Crashlytics/Models/FIRCLSInternalReport.h"
#include "Crashlytics/Crashlytics/Operations/Symbolication/FIRCLSDemangleOperation.h"
// C++/Objective-C exception handling
#include <cxxabi.h>
#include <exception>
#include <string>
#include <typeinfo>
#if !TARGET_OS_IPHONE
#import <AppKit/NSApplication.h>
#import <objc/runtime.h>
#endif
#pragma mark Prototypes
static void FIRCLSTerminateHandler(void);
#if !TARGET_OS_IPHONE
void FIRCLSNSApplicationReportException(id self, SEL cmd, NSException *exception);
typedef void (*NSApplicationReportExceptionFunction)(id, SEL, NSException *);
static BOOL FIRCLSIsNSApplicationCrashOnExceptionsEnabled(void);
static NSApplicationReportExceptionFunction FIRCLSOriginalNSExceptionReportExceptionFunction(void);
static Method FIRCLSGetNSApplicationReportExceptionMethod(void);
#endif
#pragma mark - API
void FIRCLSExceptionInitialize(FIRCLSExceptionReadOnlyContext *roContext,
FIRCLSExceptionWritableContext *rwContext) {
if (!FIRCLSUnlinkIfExists(roContext->path)) {
FIRCLSSDKLog("Unable to reset the exception file %s\n", strerror(errno));
}
roContext->originalTerminateHandler = std::set_terminate(FIRCLSTerminateHandler);
#if !TARGET_OS_IPHONE
// If FIRCLSApplicationSharedInstance is null, we don't need this
if (FIRCLSIsNSApplicationCrashOnExceptionsEnabled() && FIRCLSApplicationSharedInstance()) {
Method m = FIRCLSGetNSApplicationReportExceptionMethod();
roContext->originalNSApplicationReportException =
(void *)method_setImplementation(m, (IMP)FIRCLSNSApplicationReportException);
}
#endif
rwContext->customExceptionCount = 0;
}
void FIRCLSExceptionRecordModel(FIRExceptionModel *exceptionModel, NSString *rolloutsInfoJSON) {
const char *name = [[exceptionModel.name copy] UTF8String];
const char *reason = [[exceptionModel.reason copy] UTF8String] ?: "";
FIRCLSExceptionRecord(FIRCLSExceptionTypeCustom, name, reason, [exceptionModel.stackTrace copy],
rolloutsInfoJSON);
}
NSString *FIRCLSExceptionRecordOnDemandModel(FIRExceptionModel *exceptionModel,
int previousRecordedOnDemandExceptions,
int previousDroppedOnDemandExceptions) {
const char *name = [[exceptionModel.name copy] UTF8String];
const char *reason = [[exceptionModel.reason copy] UTF8String] ?: "";
return FIRCLSExceptionRecordOnDemand(FIRCLSExceptionTypeCustom, name, reason,
[exceptionModel.stackTrace copy], exceptionModel.isFatal,
previousRecordedOnDemandExceptions,
previousDroppedOnDemandExceptions);
}
void FIRCLSExceptionRecordNSException(NSException *exception) {
FIRCLSSDKLog("Recording an NSException\n");
NSArray *returnAddresses = [exception callStackReturnAddresses];
NSString *name = [exception name];
NSString *reason = [exception reason] ?: @"";
// It's tempting to try to make use of callStackSymbols here. But, the output
// of that function is not intended to be machine-readible. We could parse it,
// but that isn't really worthwhile, considering that address-based symbolication
// needs to work anyways.
// package our frames up into the appropriate format
NSMutableArray *frames = [NSMutableArray new];
for (NSNumber *address in returnAddresses) {
[frames addObject:[FIRStackFrame stackFrameWithAddress:[address unsignedIntegerValue]]];
}
FIRCLSExceptionRecord(FIRCLSExceptionTypeObjectiveC, [name UTF8String], [reason UTF8String],
frames, nil);
}
static void FIRCLSExceptionRecordFrame(FIRCLSFile *file, FIRStackFrame *frame) {
FIRCLSFileWriteHashStart(file);
FIRCLSFileWriteHashEntryUint64(file, "pc", [frame address]);
NSString *string = [frame symbol];
if (string) {
FIRCLSFileWriteHashEntryHexEncodedString(file, "symbol", [string UTF8String]);
}
FIRCLSFileWriteHashEntryUint64(file, "offset", [frame offset]);
string = [frame library];
if (string) {
FIRCLSFileWriteHashEntryHexEncodedString(file, "library", [string UTF8String]);
}
string = [frame fileName];
if (string) {
FIRCLSFileWriteHashEntryHexEncodedString(file, "file", [string UTF8String]);
}
FIRCLSFileWriteHashEntryUint64(file, "line", [frame lineNumber]);
FIRCLSFileWriteHashEnd(file);
}
static bool FIRCLSExceptionIsNative(FIRCLSExceptionType type) {
return type == FIRCLSExceptionTypeObjectiveC || type == FIRCLSExceptionTypeCpp;
}
static const char *FIRCLSExceptionNameForType(FIRCLSExceptionType type) {
switch (type) {
case FIRCLSExceptionTypeObjectiveC:
return "objective-c";
case FIRCLSExceptionTypeCpp:
return "c++";
case FIRCLSExceptionTypeCustom:
return "custom";
default:
break;
}
return "unknown";
}
void FIRCLSExceptionWrite(FIRCLSFile *file,
FIRCLSExceptionType type,
const char *name,
const char *reason,
NSArray<FIRStackFrame *> *frames,
NSString *rolloutsInfoJSON) {
FIRCLSFileWriteSectionStart(file, "exception");
FIRCLSFileWriteHashStart(file);
FIRCLSFileWriteHashEntryString(file, "type", FIRCLSExceptionNameForType(type));
FIRCLSFileWriteHashEntryHexEncodedString(file, "name", name);
FIRCLSFileWriteHashEntryHexEncodedString(file, "reason", reason);
FIRCLSFileWriteHashEntryUint64(file, "time", time(NULL));
if ([frames count]) {
FIRCLSFileWriteHashKey(file, "frames");
FIRCLSFileWriteArrayStart(file);
for (FIRStackFrame *frame in frames) {
FIRCLSExceptionRecordFrame(file, frame);
}
FIRCLSFileWriteArrayEnd(file);
}
if (rolloutsInfoJSON) {
FIRCLSFileWriteHashKey(file, "rollouts");
FIRCLSFileWriteStringUnquoted(file, [rolloutsInfoJSON UTF8String]);
FIRCLSFileWriteHashEnd(file);
}
FIRCLSFileWriteHashEnd(file);
FIRCLSFileWriteSectionEnd(file);
}
void FIRCLSExceptionRecord(FIRCLSExceptionType type,
const char *name,
const char *reason,
NSArray<FIRStackFrame *> *frames,
NSString *rolloutsInfoJSON) {
if (!FIRCLSContextIsInitialized()) {
return;
}
bool native = FIRCLSExceptionIsNative(type);
FIRCLSSDKLog("Recording an exception structure (%d)\n", native);
// exceptions can happen on multiple threads at the same time
if (native) {
dispatch_sync(_firclsExceptionQueue, ^{
const char *path = _firclsContext.readonly->exception.path;
FIRCLSFile file;
if (!FIRCLSFileInitWithPath(&file, path, false)) {
FIRCLSSDKLog("Unable to open exception file\n");
return;
}
FIRCLSExceptionWrite(&file, type, name, reason, frames, nil);
// We only want to do this work if we have the expectation that we'll actually crash
FIRCLSHandler(&file, mach_thread_self(), NULL);
FIRCLSFileClose(&file);
});
} else {
FIRCLSUserLoggingWriteAndCheckABFiles(
&_firclsContext.readonly->logging.customExceptionStorage,
&_firclsContext.writable->logging.activeCustomExceptionPath, ^(FIRCLSFile *file) {
FIRCLSExceptionWrite(file, type, name, reason, frames, rolloutsInfoJSON);
});
}
FIRCLSSDKLog("Finished recording an exception structure\n");
}
// Prepares a new active report for on-demand delivery and returns the path to the report.
// Should only be used for platforms in which exceptions do not crash the app (flutter, Unity, etc).
NSString *FIRCLSExceptionRecordOnDemand(FIRCLSExceptionType type,
const char *name,
const char *reason,
NSArray<FIRStackFrame *> *frames,
BOOL fatal,
int previousRecordedOnDemandExceptions,
int previousDroppedOnDemandExceptions) {
if (!FIRCLSContextIsInitialized()) {
return nil;
}
FIRCLSSDKLog("Recording an exception structure on demand\n");
FIRCLSFileManager *fileManager = [[FIRCLSFileManager alloc] init];
// Create paths for new report.
NSString *currentReportPath =
[NSString stringWithUTF8String:_firclsContext.readonly->initialReportPath];
NSString *newReportID = [[[FIRCLSExecutionIdentifierModel alloc] init] executionID];
NSString *newReportPath = [fileManager.activePath stringByAppendingPathComponent:newReportID];
NSString *customFatalIndicatorFilePath =
[newReportPath stringByAppendingPathComponent:FIRCLSCustomFatalIndicatorFile];
NSString *newKVPath =
[newReportPath stringByAppendingPathComponent:FIRCLSReportInternalIncrementalKVFile];
// Create new report and copy into it the current state of custom keys and log and the sdk.log,
// binary_images.clsrecord, and metadata.clsrecord files.
// Also copy rollouts.clsrecord if applicable.
NSError *error = nil;
BOOL copied = [fileManager.underlyingFileManager copyItemAtPath:currentReportPath
toPath:newReportPath
error:&error];
if (error || !copied) {
FIRCLSSDKLog("Unable to create a new report to record on-demand exeption.");
return nil;
}
// Once the report is copied, remove non-fatal events from current report.
if ([fileManager
fileExistsAtPath:[NSString stringWithUTF8String:_firclsContext.readonly->logging
.customExceptionStorage.aPath]]) {
[fileManager
removeItemAtPath:[NSString stringWithUTF8String:_firclsContext.readonly->logging
.customExceptionStorage.aPath]];
}
if ([fileManager
fileExistsAtPath:[NSString stringWithUTF8String:_firclsContext.readonly->logging
.customExceptionStorage.bPath]]) {
[fileManager
removeItemAtPath:[NSString stringWithUTF8String:_firclsContext.readonly->logging
.customExceptionStorage.bPath]];
}
*_firclsContext.readonly->logging.customExceptionStorage.entryCount = 0;
_firclsContext.writable->exception.customExceptionCount = 0;
// Record how many on-demand exceptions occurred before this one as well as how many were dropped.
FIRCLSFile kvFile;
if (!FIRCLSFileInitWithPath(&kvFile, [newKVPath UTF8String], true)) {
FIRCLSSDKLogError("Unable to open k-v file\n");
return nil;
}
FIRCLSFileWriteSectionStart(&kvFile, "kv");
FIRCLSFileWriteHashStart(&kvFile);
FIRCLSFileWriteHashEntryHexEncodedString(&kvFile, "key",
[FIRCLSOnDemandRecordedExceptionsKey UTF8String]);
FIRCLSFileWriteHashEntryHexEncodedString(
&kvFile, "value",
[[[NSNumber numberWithInt:previousRecordedOnDemandExceptions] stringValue] UTF8String]);
FIRCLSFileWriteHashEnd(&kvFile);
FIRCLSFileWriteSectionEnd(&kvFile);
FIRCLSFileWriteSectionStart(&kvFile, "kv");
FIRCLSFileWriteHashStart(&kvFile);
FIRCLSFileWriteHashEntryHexEncodedString(&kvFile, "key",
[FIRCLSOnDemandDroppedExceptionsKey UTF8String]);
FIRCLSFileWriteHashEntryHexEncodedString(
&kvFile, "value",
[[[NSNumber numberWithInt:previousDroppedOnDemandExceptions] stringValue] UTF8String]);
FIRCLSFileWriteHashEnd(&kvFile);
FIRCLSFileWriteSectionEnd(&kvFile);
FIRCLSFileClose(&kvFile);
// If the event was fatal, write out an empty file to indicate that the report contains a fatal
// event. This is used to report events to Analytics for CFU calculations.
if (fatal && ![fileManager createFileAtPath:customFatalIndicatorFilePath
contents:nil
attributes:nil]) {
FIRCLSSDKLog("Unable to create custom exception file. On demand exception will not be logged "
"with analytics.");
}
// Write out the exception in the new report.
const char *newActiveCustomExceptionPath =
fatal ? [[newReportPath stringByAppendingPathComponent:FIRCLSReportExceptionFile] UTF8String]
: [[newReportPath stringByAppendingPathComponent:FIRCLSReportCustomExceptionAFile]
UTF8String];
FIRCLSFile file;
if (!FIRCLSFileInitWithPath(&file, newActiveCustomExceptionPath, true)) {
FIRCLSSDKLog("Unable to open log file for on demand custom exception\n");
return nil;
}
FIRCLSExceptionWrite(&file, type, name, reason, frames, nil);
FIRCLSHandler(&file, mach_thread_self(), NULL);
FIRCLSFileClose(&file);
// Return the path to the new report.
FIRCLSSDKLog("Finished recording on demand exception structure\n");
return newReportPath;
}
// Ignore this message here, because we know that this call will not leak.
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Winvalid-noreturn"
void FIRCLSExceptionRaiseTestObjCException(void) {
[NSException raise:@"CrashlyticsTestException"
format:@"This is an Objective-C exception used for testing."];
}
void FIRCLSExceptionRaiseTestCppException(void) {
throw "Crashlytics C++ Test Exception";
}
#pragma clang diagnostic pop
static const char *FIRCLSExceptionDemangle(const char *symbol) {
return [[FIRCLSDemangleOperation demangleCppSymbol:symbol] UTF8String];
}
static void FIRCLSCatchAndRecordActiveException(std::type_info *typeInfo) {
if (!FIRCLSIsValidPointer(typeInfo)) {
FIRCLSSDKLog("Error: invalid parameter\n");
return;
}
const char *name = typeInfo->name();
FIRCLSSDKLog("Recording exception of type '%s'\n", name);
// This is a funny technique to get the exception object. The inner @try
// has the ability to capture NSException-derived objects. It seems that
// c++ tries can do that in some cases, but I was warned by the WWDC labs
// that there are cases where that will not work (like for NSException subclasses).
try {
@try {
// This could potentially cause a call to std::terminate() if there is actually no active
// exception.
throw;
} @catch (NSException *exception) {
#if TARGET_OS_IPHONE
FIRCLSExceptionRecordNSException(exception);
#else
// There's no need to record this here, because we're going to get
// the value forward to us by AppKit
FIRCLSSDKLog("Skipping ObjC exception at this point\n");
#endif
}
} catch (const char *exc) {
FIRCLSExceptionRecord(FIRCLSExceptionTypeCpp, "const char *", exc, nil, nil);
} catch (const std::string &exc) {
FIRCLSExceptionRecord(FIRCLSExceptionTypeCpp, "std::string", exc.c_str(), nil, nil);
} catch (const std::exception &exc) {
FIRCLSExceptionRecord(FIRCLSExceptionTypeCpp, FIRCLSExceptionDemangle(name), exc.what(), nil,
nil);
} catch (const std::exception *exc) {
FIRCLSExceptionRecord(FIRCLSExceptionTypeCpp, FIRCLSExceptionDemangle(name), exc->what(), nil,
nil);
} catch (const std::bad_alloc &exc) {
// it is especially important to avoid demangling in this case, because the expectation at this
// point is that all allocations could fail
FIRCLSExceptionRecord(FIRCLSExceptionTypeCpp, "std::bad_alloc", exc.what(), nil, nil);
} catch (...) {
FIRCLSExceptionRecord(FIRCLSExceptionTypeCpp, FIRCLSExceptionDemangle(name), "", nil, nil);
}
}
#pragma mark - Handlers
static void FIRCLSTerminateHandler(void) {
FIRCLSSDKLog("C++ terminate handler invoked\n");
void (*handler)(void) = _firclsContext.readonly->exception.originalTerminateHandler;
if (handler == FIRCLSTerminateHandler) {
FIRCLSSDKLog("Error: original handler was set recursively\n");
handler = NULL;
}
// Restore pre-existing handler, if any. Do this early, so that
// if std::terminate is called while we are executing here, we do not recurse.
if (handler) {
FIRCLSSDKLog("restoring pre-existing handler\n");
// To prevent infinite recursion in this function, check that we aren't resetting the terminate
// handler to the same function again, which would be this function in the event that we can't
// actually change the handler during a terminate.
if (std::set_terminate(handler) == handler) {
FIRCLSSDKLog("handler has already been restored, aborting\n");
abort();
}
}
// we can use typeInfo to record the type of the exception,
// but we must use a catch to get the value
std::type_info *typeInfo = __cxxabiv1::__cxa_current_exception_type();
if (typeInfo) {
FIRCLSCatchAndRecordActiveException(typeInfo);
} else {
FIRCLSSDKLog("no active exception\n");
}
// only do this if there was a pre-existing handler
if (handler) {
FIRCLSSDKLog("invoking pre-existing handler\n");
handler();
}
FIRCLSSDKLog("aborting\n");
abort();
}
void FIRCLSExceptionCheckHandlers(void *delegate) {
#if !TARGET_OS_IPHONE
// Check this on OS X all the time, even if the debugger is attached. This is a common
// source of errors, so we want to be extra verbose in this case.
if (FIRCLSApplicationSharedInstance()) {
if (!FIRCLSIsNSApplicationCrashOnExceptionsEnabled()) {
FIRCLSWarningLog(@"Warning: NSApplicationCrashOnExceptions is not set. This will "
@"result in poor top-level uncaught exception reporting.");
}
}
#endif
if (_firclsContext.readonly->debuggerAttached) {
return;
}
void *ptr = NULL;
ptr = (void *)std::get_terminate();
if (ptr != FIRCLSTerminateHandler) {
FIRCLSLookupFunctionPointer(ptr, ^(const char *name, const char *lib) {
FIRCLSWarningLog(@"Warning: std::get_terminate is '%s' in '%s'", name, lib);
});
}
#if TARGET_OS_IPHONE
ptr = (void *)NSGetUncaughtExceptionHandler();
if (ptr) {
FIRCLSLookupFunctionPointer(ptr, ^(const char *name, const char *lib) {
FIRCLSWarningLog(@"Warning: NSUncaughtExceptionHandler is '%s' in '%s'", name, lib);
});
}
#else
if (FIRCLSApplicationSharedInstance() && FIRCLSIsNSApplicationCrashOnExceptionsEnabled()) {
// In this case, we *might* be able to intercept exceptions. But, verify we've still
// swizzled the method.
Method m = FIRCLSGetNSApplicationReportExceptionMethod();
if (method_getImplementation(m) != (IMP)FIRCLSNSApplicationReportException) {
FIRCLSWarningLog(
@"Warning: top-level NSApplication-reported exceptions cannot be intercepted");
}
}
#endif
}
#pragma mark - AppKit Handling
#if !TARGET_OS_IPHONE
static BOOL FIRCLSIsNSApplicationCrashOnExceptionsEnabled(void) {
return [[NSUserDefaults standardUserDefaults] boolForKey:@"NSApplicationCrashOnExceptions"];
}
static Method FIRCLSGetNSApplicationReportExceptionMethod(void) {
return class_getInstanceMethod(NSClassFromString(@"NSApplication"), @selector(reportException:));
}
static NSApplicationReportExceptionFunction FIRCLSOriginalNSExceptionReportExceptionFunction(void) {
return (NSApplicationReportExceptionFunction)
_firclsContext.readonly->exception.originalNSApplicationReportException;
}
void FIRCLSNSApplicationReportException(id self, SEL cmd, NSException *exception) {
FIRCLSExceptionRecordNSException(exception);
// Call the original implementation
FIRCLSOriginalNSExceptionReportExceptionFunction()(self, cmd, exception);
}
#endif

View File

@ -0,0 +1,25 @@
// Copyright 2019 Google
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#pragma once
#include <mach/mach.h>
#include "Crashlytics/Crashlytics/Helpers/FIRCLSFile.h"
__BEGIN_DECLS
void FIRCLSHandler(FIRCLSFile* file, thread_t crashedThread, void* uapVoid);
__END_DECLS

View File

@ -0,0 +1,49 @@
// Copyright 2019 Google
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "Crashlytics/Crashlytics/Handlers/FIRCLSHandler.h"
#include "Crashlytics/Crashlytics/Components/FIRCLSCrashedMarkerFile.h"
#include "Crashlytics/Crashlytics/Components/FIRCLSGlobals.h"
#include "Crashlytics/Crashlytics/Components/FIRCLSHost.h"
#include "Crashlytics/Crashlytics/Components/FIRCLSProcess.h"
#include "Crashlytics/Crashlytics/Helpers/FIRCLSUtility.h"
#import "Crashlytics/Crashlytics/Controllers/FIRCLSReportManager_Private.h"
void FIRCLSHandler(FIRCLSFile* file, thread_t crashedThread, void* uapVoid) {
FIRCLSProcess process;
FIRCLSProcessInit(&process, crashedThread, uapVoid);
FIRCLSProcessSuspendAllOtherThreads(&process);
FIRCLSProcessRecordAllThreads(&process, file);
FIRCLSProcessRecordRuntimeInfo(&process, file);
// Get dispatch queue and thread names. Note that getting the thread names
// can hang, so let's do that last
FIRCLSProcessRecordDispatchQueueNames(&process, file);
FIRCLSProcessRecordThreadNames(&process, file);
// this stuff isn't super important, but we can try
FIRCLSProcessRecordStats(&process, file);
FIRCLSHostWriteDiskUsage(file);
// This is the first common point where various crash handlers call into
// Store a crash file marker to indicate that a crash has occurred
FIRCLSCreateCrashedMarkerFile();
FIRCLSProcessResumeAllOtherThreads(&process);
}

View File

@ -0,0 +1,534 @@
// Copyright 2019 Google
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "Crashlytics/Crashlytics/Helpers/FIRCLSDefines.h"
#include "Crashlytics/Crashlytics/Helpers/FIRCLSFeatures.h"
#if CLS_MACH_EXCEPTION_SUPPORTED
#include "Crashlytics/Crashlytics/Components/FIRCLSGlobals.h"
#include "Crashlytics/Crashlytics/Handlers/FIRCLSHandler.h"
#include "Crashlytics/Crashlytics/Handlers/FIRCLSMachException.h"
#include "Crashlytics/Crashlytics/Components/FIRCLSProcess.h"
#include "Crashlytics/Crashlytics/Handlers/FIRCLSSignal.h"
#include "Crashlytics/Crashlytics/Helpers/FIRCLSUtility.h"
#include <errno.h>
#include <mach/mach.h>
#include <pthread.h>
#include <unistd.h>
#pragma mark Prototypes
static void* FIRCLSMachExceptionServer(void* argument);
static bool FIRCLSMachExceptionThreadStart(FIRCLSMachExceptionReadContext* context);
static bool FIRCLSMachExceptionReadMessage(FIRCLSMachExceptionReadContext* context,
MachExceptionMessage* message);
static kern_return_t FIRCLSMachExceptionDispatchMessage(FIRCLSMachExceptionReadContext* context,
MachExceptionMessage* message);
static bool FIRCLSMachExceptionReply(FIRCLSMachExceptionReadContext* context,
MachExceptionMessage* message,
kern_return_t result);
static bool FIRCLSMachExceptionRegister(FIRCLSMachExceptionReadContext* context);
static bool FIRCLSMachExceptionUnregister(FIRCLSMachExceptionOriginalPorts* originalPorts,
exception_mask_t mask);
static bool FIRCLSMachExceptionRecord(FIRCLSMachExceptionReadContext* context,
MachExceptionMessage* message);
#pragma mark - Initialization
void FIRCLSMachExceptionInit(FIRCLSMachExceptionReadContext* context) {
if (!FIRCLSUnlinkIfExists(context->path)) {
FIRCLSSDKLog("Unable to reset the mach exception file %s\n", strerror(errno));
}
if (!FIRCLSMachExceptionRegister(context)) {
FIRCLSSDKLog("Unable to register mach exception handler\n");
return;
}
if (!FIRCLSMachExceptionThreadStart(context)) {
FIRCLSSDKLog("Unable to start thread\n");
FIRCLSMachExceptionUnregister(&context->originalPorts, context->mask);
}
}
void FIRCLSMachExceptionCheckHandlers(void) {
if (_firclsContext.readonly->debuggerAttached) {
return;
}
// It isn't really critical that this be done, as its extremely uncommon to run into
// preexisting handlers.
// Can use task_get_exception_ports for this.
}
static exception_mask_t FIRCLSMachExceptionMask(void) {
exception_mask_t mask;
// EXC_BAD_ACCESS
// EXC_BAD_INSTRUCTION
// EXC_ARITHMETIC
// EXC_EMULATION - non-failure
// EXC_SOFTWARE - non-failure
// EXC_BREAKPOINT - trap instructions, from the debugger and code. Needs special treatment.
// EXC_SYSCALL - non-failure
// EXC_MACH_SYSCALL - non-failure
// EXC_RPC_ALERT - non-failure
// EXC_CRASH - see below
// EXC_RESOURCE - non-failure, happens when a process exceeds a resource limit
// EXC_GUARD - see below
//
// EXC_CRASH is a special kind of exception. It is handled by launchd, and treated special by
// the kernel. Seems that we cannot safely catch it - our handler will never be called. This
// is a confirmed kernel bug. Lacking access to EXC_CRASH means we must use signal handlers to
// cover all types of crashes.
// EXC_GUARD is relatively new, and isn't available on all OS versions. You have to be careful,
// because you cannot successfully register handlers if there are any unrecognized masks. We've
// dropped support for old OS versions that didn't have EXC_GUARD (iOS 5 and below, macOS 10.6 and
// below) so we always add it now
mask = EXC_MASK_BAD_ACCESS | EXC_MASK_BAD_INSTRUCTION | EXC_MASK_ARITHMETIC |
EXC_MASK_BREAKPOINT | EXC_MASK_GUARD;
return mask;
}
static bool FIRCLSMachExceptionThreadStart(FIRCLSMachExceptionReadContext* context) {
pthread_attr_t attr;
if (pthread_attr_init(&attr) != 0) {
FIRCLSSDKLog("pthread_attr_init %s\n", strerror(errno));
pthread_attr_destroy(&attr);
return false;
}
if (pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED) != 0) {
FIRCLSSDKLog("pthread_attr_setdetachstate %s\n", strerror(errno));
pthread_attr_destroy(&attr);
return false;
}
// Use to pre-allocate a stack for this thread
// The stack must be page-aligned
if (pthread_attr_setstack(&attr, _firclsContext.readonly->machStack,
CLS_MACH_EXCEPTION_HANDLER_STACK_SIZE) != 0) {
FIRCLSSDKLog("pthread_attr_setstack %s\n", strerror(errno));
pthread_attr_destroy(&attr);
return false;
}
if (pthread_create(&context->thread, &attr, FIRCLSMachExceptionServer, context) != 0) {
FIRCLSSDKLog("pthread_create %s\n", strerror(errno));
pthread_attr_destroy(&attr);
return false;
}
pthread_attr_destroy(&attr);
return true;
}
exception_mask_t FIRCLSMachExceptionMaskForSignal(int signal) {
switch (signal) {
case SIGTRAP:
return EXC_MASK_BREAKPOINT;
case SIGSEGV:
return EXC_MASK_BAD_ACCESS;
case SIGBUS:
return EXC_MASK_BAD_ACCESS;
case SIGILL:
return EXC_MASK_BAD_INSTRUCTION;
case SIGABRT:
return EXC_MASK_CRASH;
case SIGSYS:
return EXC_MASK_CRASH;
case SIGFPE:
return EXC_MASK_ARITHMETIC;
}
return 0;
}
#pragma mark - Message Handling
static void* FIRCLSMachExceptionServer(void* argument) {
FIRCLSMachExceptionReadContext* context = argument;
pthread_setname_np("com.google.firebase.crashlytics.MachExceptionServer");
while (1) {
MachExceptionMessage message;
// read the exception message
if (!FIRCLSMachExceptionReadMessage(context, &message)) {
break;
}
// handle it, and possibly forward
kern_return_t result = FIRCLSMachExceptionDispatchMessage(context, &message);
// and now, reply
if (!FIRCLSMachExceptionReply(context, &message, result)) {
break;
}
}
FIRCLSSDKLog("Mach exception server thread exiting\n");
return NULL;
}
static bool FIRCLSMachExceptionReadMessage(FIRCLSMachExceptionReadContext* context,
MachExceptionMessage* message) {
mach_msg_return_t r;
memset(message, 0, sizeof(MachExceptionMessage));
r = mach_msg(&message->head, MACH_RCV_MSG | MACH_RCV_LARGE, 0, sizeof(MachExceptionMessage),
context->port, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
if (r != MACH_MSG_SUCCESS) {
FIRCLSSDKLog("Error receiving mach_msg (%d)\n", r);
return false;
}
FIRCLSSDKLog("Accepted mach exception message\n");
return true;
}
static kern_return_t FIRCLSMachExceptionDispatchMessage(FIRCLSMachExceptionReadContext* context,
MachExceptionMessage* message) {
FIRCLSSDKLog("Mach exception: 0x%x, count: %d, code: 0x%llx 0x%llx\n", message->exception,
message->codeCnt, message->codeCnt > 0 ? message->code[0] : -1,
message->codeCnt > 1 ? message->code[1] : -1);
// This will happen if a child process raises an exception, as the exception ports are
// inherited.
if (message->task.name != mach_task_self()) {
FIRCLSSDKLog("Mach exception task mis-match, returning failure\n");
return KERN_FAILURE;
}
FIRCLSSDKLog("Unregistering handler\n");
if (!FIRCLSMachExceptionUnregister(&context->originalPorts, context->mask)) {
FIRCLSSDKLog("Failed to unregister\n");
return KERN_FAILURE;
}
FIRCLSSDKLog("Restoring original signal handlers\n");
if (!FIRCLSSignalSafeInstallPreexistingHandlers(& _firclsContext.readonly->signal, -1, NULL, NULL)) {
FIRCLSSDKLog("Failed to restore signal handlers\n");
return KERN_FAILURE;
}
FIRCLSSDKLog("Recording mach exception\n");
if (!FIRCLSMachExceptionRecord(context, message)) {
FIRCLSSDKLog("Failed to record mach exception\n");
return KERN_FAILURE;
}
return KERN_SUCCESS;
}
static bool FIRCLSMachExceptionReply(FIRCLSMachExceptionReadContext* context,
MachExceptionMessage* message,
kern_return_t result) {
MachExceptionReply reply;
mach_msg_return_t r;
// prepare the reply
reply.head.msgh_bits = MACH_MSGH_BITS(MACH_MSGH_BITS_REMOTE(message->head.msgh_bits), 0);
reply.head.msgh_remote_port = message->head.msgh_remote_port;
reply.head.msgh_size = (mach_msg_size_t)sizeof(MachExceptionReply);
reply.head.msgh_local_port = MACH_PORT_NULL;
reply.head.msgh_id = message->head.msgh_id + 100;
reply.NDR = NDR_record;
reply.retCode = result;
FIRCLSSDKLog("Sending exception reply\n");
// send it
r = mach_msg(&reply.head, MACH_SEND_MSG, reply.head.msgh_size, 0, MACH_PORT_NULL,
MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
if (r != MACH_MSG_SUCCESS) {
FIRCLSSDKLog("mach_msg reply failed (%d)\n", r);
return false;
}
FIRCLSSDKLog("Exception reply delivered\n");
return true;
}
#pragma mark - Registration
static bool FIRCLSMachExceptionRegister(FIRCLSMachExceptionReadContext* context) {
mach_port_t task = mach_task_self();
kern_return_t kr = mach_port_allocate(task, MACH_PORT_RIGHT_RECEIVE, &context->port);
if (kr != KERN_SUCCESS) {
FIRCLSSDKLog("Error: mach_port_allocate failed %d\n", kr);
return false;
}
kr = mach_port_insert_right(task, context->port, context->port, MACH_MSG_TYPE_MAKE_SEND);
if (kr != KERN_SUCCESS) {
FIRCLSSDKLog("Error: mach_port_insert_right failed %d\n", kr);
mach_port_deallocate(task, context->port);
return false;
}
// Get the desired mask, which covers all the mach exceptions we are capable of handling,
// but clear out any that are in our ignore list. We do this by ANDing with the bitwise
// negation. Because we are only clearing bits, there's no way to set an incorrect mask
// using ignoreMask.
context->mask = FIRCLSMachExceptionMask();
// ORing with MACH_EXCEPTION_CODES will produce 64-bit exception data
kr = task_swap_exception_ports(task, context->mask, context->port,
EXCEPTION_DEFAULT | MACH_EXCEPTION_CODES, THREAD_STATE_NONE,
context->originalPorts.masks, &context->originalPorts.count,
context->originalPorts.ports, context->originalPorts.behaviors,
context->originalPorts.flavors);
if (kr != KERN_SUCCESS) {
FIRCLSSDKLog("Error: task_swap_exception_ports %d\n", kr);
return false;
}
for (int i = 0; i < context->originalPorts.count; ++i) {
FIRCLSSDKLog("original 0x%x 0x%x 0x%x 0x%x\n", context->originalPorts.ports[i],
context->originalPorts.masks[i], context->originalPorts.behaviors[i],
context->originalPorts.flavors[i]);
}
return true;
}
static bool FIRCLSMachExceptionUnregister(FIRCLSMachExceptionOriginalPorts* originalPorts,
exception_mask_t mask) {
kern_return_t kr;
// Re-register all the old ports.
for (mach_msg_type_number_t i = 0; i < originalPorts->count; ++i) {
// clear the bits from this original mask
mask &= ~originalPorts->masks[i];
kr =
task_set_exception_ports(mach_task_self(), originalPorts->masks[i], originalPorts->ports[i],
originalPorts->behaviors[i], originalPorts->flavors[i]);
if (kr != KERN_SUCCESS) {
FIRCLSSDKLog("unable to restore original port: %d", originalPorts->ports[i]);
}
}
// Finally, mark any masks we registered for that do not have an original port as unused.
kr = task_set_exception_ports(mach_task_self(), mask, MACH_PORT_NULL,
EXCEPTION_DEFAULT | MACH_EXCEPTION_CODES, THREAD_STATE_NONE);
if (kr != KERN_SUCCESS) {
FIRCLSSDKLog("unable to unset unregistered mask: 0x%x", mask);
return false;
}
return true;
}
#pragma mark - Recording
void FIRCLSMachExceptionNameLookup(exception_type_t number,
mach_exception_data_type_t code,
const char** name,
const char** codeName) {
if (!name || !codeName) {
return;
}
*name = NULL;
*codeName = NULL;
switch (number) {
case EXC_BAD_ACCESS:
*name = "EXC_BAD_ACCESS";
switch (code) {
case KERN_INVALID_ADDRESS:
*codeName = "KERN_INVALID_ADDRESS";
break;
case KERN_PROTECTION_FAILURE:
*codeName = "KERN_PROTECTION_FAILURE";
break;
}
break;
case EXC_BAD_INSTRUCTION:
*name = "EXC_BAD_INSTRUCTION";
#if CLS_CPU_X86
*codeName = "EXC_I386_INVOP";
#endif
break;
case EXC_ARITHMETIC:
*name = "EXC_ARITHMETIC";
#if CLS_CPU_X86
switch (code) {
case EXC_I386_DIV:
*codeName = "EXC_I386_DIV";
break;
case EXC_I386_INTO:
*codeName = "EXC_I386_INTO";
break;
case EXC_I386_NOEXT:
*codeName = "EXC_I386_NOEXT";
break;
case EXC_I386_EXTOVR:
*codeName = "EXC_I386_EXTOVR";
break;
case EXC_I386_EXTERR:
*codeName = "EXC_I386_EXTERR";
break;
case EXC_I386_EMERR:
*codeName = "EXC_I386_EMERR";
break;
case EXC_I386_BOUND:
*codeName = "EXC_I386_BOUND";
break;
case EXC_I386_SSEEXTERR:
*codeName = "EXC_I386_SSEEXTERR";
break;
}
#endif
break;
case EXC_BREAKPOINT:
*name = "EXC_BREAKPOINT";
#if CLS_CPU_X86
switch (code) {
case EXC_I386_DIVERR:
*codeName = "EXC_I386_DIVERR";
break;
case EXC_I386_SGLSTP:
*codeName = "EXC_I386_SGLSTP";
break;
case EXC_I386_NMIFLT:
*codeName = "EXC_I386_NMIFLT";
break;
case EXC_I386_BPTFLT:
*codeName = "EXC_I386_BPTFLT";
break;
case EXC_I386_INTOFLT:
*codeName = "EXC_I386_INTOFLT";
break;
case EXC_I386_BOUNDFLT:
*codeName = "EXC_I386_BOUNDFLT";
break;
case EXC_I386_INVOPFLT:
*codeName = "EXC_I386_INVOPFLT";
break;
case EXC_I386_NOEXTFLT:
*codeName = "EXC_I386_NOEXTFLT";
break;
case EXC_I386_EXTOVRFLT:
*codeName = "EXC_I386_EXTOVRFLT";
break;
case EXC_I386_INVTSSFLT:
*codeName = "EXC_I386_INVTSSFLT";
break;
case EXC_I386_SEGNPFLT:
*codeName = "EXC_I386_SEGNPFLT";
break;
case EXC_I386_STKFLT:
*codeName = "EXC_I386_STKFLT";
break;
case EXC_I386_GPFLT:
*codeName = "EXC_I386_GPFLT";
break;
case EXC_I386_PGFLT:
*codeName = "EXC_I386_PGFLT";
break;
case EXC_I386_EXTERRFLT:
*codeName = "EXC_I386_EXTERRFLT";
break;
case EXC_I386_ALIGNFLT:
*codeName = "EXC_I386_ALIGNFLT";
break;
case EXC_I386_ENDPERR:
*codeName = "EXC_I386_ENDPERR";
break;
case EXC_I386_ENOEXTFLT:
*codeName = "EXC_I386_ENOEXTFLT";
break;
}
#endif
break;
case EXC_GUARD:
*name = "EXC_GUARD";
break;
}
}
static bool FIRCLSMachExceptionRecord(FIRCLSMachExceptionReadContext* context,
MachExceptionMessage* message) {
if (!context || !message) {
return false;
}
if (FIRCLSContextMarkAndCheckIfCrashed()) {
FIRCLSSDKLog("Error: aborting mach exception handler because crash has already occurred\n");
exit(1);
return false;
}
FIRCLSFile file;
if (!FIRCLSFileInitWithPath(&file, context->path, false)) {
FIRCLSSDKLog("Unable to open mach exception file\n");
return false;
}
FIRCLSFileWriteSectionStart(&file, "mach_exception");
FIRCLSFileWriteHashStart(&file);
FIRCLSFileWriteHashEntryUint64(&file, "exception", message->exception);
// record the codes
FIRCLSFileWriteHashKey(&file, "codes");
FIRCLSFileWriteArrayStart(&file);
for (mach_msg_type_number_t i = 0; i < message->codeCnt; ++i) {
FIRCLSFileWriteArrayEntryUint64(&file, message->code[i]);
}
FIRCLSFileWriteArrayEnd(&file);
const char* name = NULL;
const char* codeName = NULL;
FIRCLSMachExceptionNameLookup(message->exception, message->codeCnt > 0 ? message->code[0] : 0,
&name, &codeName);
FIRCLSFileWriteHashEntryString(&file, "name", name);
FIRCLSFileWriteHashEntryString(&file, "code_name", codeName);
FIRCLSFileWriteHashEntryUint64(&file, "original_ports", context->originalPorts.count);
FIRCLSFileWriteHashEntryUint64(&file, "time", time(NULL));
FIRCLSFileWriteHashEnd(&file);
FIRCLSFileWriteSectionEnd(&file);
FIRCLSHandler(&file, message->thread.name, NULL);
FIRCLSFileClose(&file);
return true;
}
#else
INJECT_STRIP_SYMBOL(cls_mach_exception)
#endif

View File

@ -0,0 +1,81 @@
// Copyright 2019 Google
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "Crashlytics/Crashlytics/Helpers/FIRCLSFeatures.h"
#pragma once
#if CLS_MACH_EXCEPTION_SUPPORTED
#include <mach/mach.h>
#include <pthread.h>
#include <stdbool.h>
// must be at least PTHREAD_STACK_MIN size
#define CLS_MACH_EXCEPTION_HANDLER_STACK_SIZE (256 * 1024)
#pragma mark Structures
#pragma pack(push, 4)
typedef struct {
mach_msg_header_t head;
/* start of the kernel processed data */
mach_msg_body_t msgh_body;
mach_msg_port_descriptor_t thread;
mach_msg_port_descriptor_t task;
/* end of the kernel processed data */
NDR_record_t NDR;
exception_type_t exception;
mach_msg_type_number_t codeCnt;
mach_exception_data_type_t code[EXCEPTION_CODE_MAX];
mach_msg_trailer_t trailer;
} MachExceptionMessage;
typedef struct {
mach_msg_header_t head;
NDR_record_t NDR;
kern_return_t retCode;
} MachExceptionReply;
#pragma pack(pop)
typedef struct {
mach_msg_type_number_t count;
exception_mask_t masks[EXC_TYPES_COUNT];
exception_handler_t ports[EXC_TYPES_COUNT];
exception_behavior_t behaviors[EXC_TYPES_COUNT];
thread_state_flavor_t flavors[EXC_TYPES_COUNT];
} FIRCLSMachExceptionOriginalPorts;
typedef struct {
mach_port_t port;
pthread_t thread;
const char* path;
exception_mask_t mask;
FIRCLSMachExceptionOriginalPorts originalPorts;
} FIRCLSMachExceptionReadContext;
#pragma mark - API
void FIRCLSMachExceptionInit(FIRCLSMachExceptionReadContext* context);
void FIRCLSMachExceptionCheckHandlers(void);
void FIRCLSMachExceptionNameLookup(exception_type_t number,
mach_exception_data_type_t code,
const char** name,
const char** codeName);
#else
#define CLS_MACH_EXCEPTION_HANDLER_STACK_SIZE 0
#endif

View File

@ -0,0 +1,334 @@
// Copyright 2019 Google
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "Crashlytics/Crashlytics/Handlers/FIRCLSSignal.h"
#include "Crashlytics/Crashlytics/Components/FIRCLSGlobals.h"
#include "Crashlytics/Crashlytics/Handlers/FIRCLSHandler.h"
#include "Crashlytics/Crashlytics/Helpers/FIRCLSUtility.h"
#include <dlfcn.h>
#include <stdlib.h>
#if CLS_SIGNAL_SUPPORTED
static const int FIRCLSFatalSignals[FIRCLSSignalCount] = {SIGABRT, SIGBUS, SIGFPE, SIGILL,
SIGSEGV, SIGSYS, SIGTRAP};
#if CLS_USE_SIGALTSTACK
static void FIRCLSSignalInstallAltStack(FIRCLSSignalReadContext *roContext);
#endif
static void FIRCLSSignalInstallHandlers(FIRCLSSignalReadContext *roContext);
static void FIRCLSSignalHandler(int signal, siginfo_t *info, void *uapVoid);
void FIRCLSSignalInitialize(FIRCLSSignalReadContext *roContext) {
if (!FIRCLSUnlinkIfExists(roContext->path)) {
FIRCLSSDKLog("Unable to reset the signal log file %s\n", strerror(errno));
}
#if CLS_USE_SIGALTSTACK
FIRCLSSignalInstallAltStack(roContext);
#endif
FIRCLSSignalInstallHandlers(roContext);
#if TARGET_IPHONE_SIMULATOR
// prevent the OpenGL stack (by way of OpenGLES.framework/libLLVMContainer.dylib) from installing
// signal handlers that do not chain back
// TODO: I don't believe this is necessary as of recent iOS releases
bool *ptr = dlsym(RTLD_DEFAULT, "_ZN4llvm23DisablePrettyStackTraceE");
if (ptr) {
*ptr = true;
}
#endif
}
void FIRCLSSignalEnumerateHandledSignals(void (^block)(int idx, int signal)) {
for (int i = 0; i < FIRCLSSignalCount; ++i) {
block(i, FIRCLSFatalSignals[i]);
}
}
#if CLS_USE_SIGALTSTACK
static void FIRCLSSignalInstallAltStack(FIRCLSSignalReadContext *roContext) {
stack_t signalStack;
stack_t originalStack;
signalStack.ss_sp = _firclsContext.readonly->signalStack;
signalStack.ss_size = CLS_SIGNAL_HANDLER_STACK_SIZE;
signalStack.ss_flags = 0;
if (sigaltstack(&signalStack, &originalStack) != 0) {
FIRCLSSDKLog("Unable to setup stack %s\n", strerror(errno));
return;
}
roContext->originalStack.ss_sp = NULL;
roContext->originalStack = originalStack;
}
#endif
static void FIRCLSSignalInstallHandlers(FIRCLSSignalReadContext *roContext) {
FIRCLSSignalEnumerateHandledSignals(^(int idx, int signal) {
struct sigaction action;
struct sigaction previousAction;
action.sa_sigaction = FIRCLSSignalHandler;
// SA_RESETHAND seems like it would be great, but it doesn't appear to
// work correctly. After taking a signal, causing another identical signal in
// the handler will *not* cause the default handler to be involved (which should
// terminate the process). I've found some evidence that others have seen this
// behavior on MAC OS X.
action.sa_flags = SA_SIGINFO | SA_ONSTACK;
sigemptyset(&action.sa_mask);
previousAction.sa_sigaction = NULL;
if (sigaction(signal, &action, &previousAction) != 0) {
FIRCLSSDKLog("Unable to install handler for %d (%s)\n", signal, strerror(errno));
}
// store the last action, so it can be recalled
roContext->originalActions[idx].sa_sigaction = NULL;
if (previousAction.sa_sigaction) {
roContext->originalActions[idx] = previousAction;
}
});
}
void FIRCLSSignalCheckHandlers(void) {
if (_firclsContext.readonly->debuggerAttached) {
// Adding this log to remind user deattachs from the debugger. Besides FIRCLSSignal, this logic is on FIRCLSMachException and FIRCLSException as well. Only log once since the check is same.
FIRCLSSDKLog("[Crashlytics] App is attached to a debugger, to see the crash reports please detach from the debugger, https://firebase.google.com/docs/crashlytics/get-started?platform=ios#force-test-crash");
return;
}
FIRCLSSignalEnumerateHandledSignals(^(int idx, int signal) {
struct sigaction previousAction;
Dl_info info;
void *ptr;
if (sigaction(signal, 0, &previousAction) != 0) {
fprintf(stderr, "Unable to read signal handler\n");
return;
}
ptr = previousAction.__sigaction_u.__sa_handler;
const char *signalName = NULL;
const char *codeName = NULL;
FIRCLSSignalNameLookup(signal, 0, &signalName, &codeName);
if (ptr == FIRCLSSignalHandler) {
return;
}
const char *name = NULL;
if (dladdr(ptr, &info) != 0) {
name = info.dli_sname;
}
fprintf(stderr,
"[Crashlytics] The signal %s has a non-Crashlytics handler (%s). This will interfere "
"with reporting.\n",
signalName, name);
});
}
void FIRCLSSignalSafeRemoveHandlers(bool includingAbort) {
FIRCLSSignalEnumerateHandledSignals(^(int idx, int signal) {
struct sigaction sa;
if (!includingAbort && (signal == SIGABRT)) {
return;
}
sa.sa_handler = SIG_DFL;
sigemptyset(&sa.sa_mask);
if (sigaction(signal, &sa, NULL) != 0) {
FIRCLSSDKLog("Unable to set default handler for %d (%s)\n", signal, strerror(errno));
}
});
}
bool FIRCLSSignalSafeInstallPreexistingHandlers(FIRCLSSignalReadContext *roContext,
const int signal,
siginfo_t *info,
void *uapVoid) {
__block bool success = true;
FIRCLSSignalSafeRemoveHandlers(true);
#if CLS_USE_SIGALTSTACK
// re-install the original stack, if needed
if (roContext->originalStack.ss_sp) {
if (sigaltstack(&roContext->originalStack, 0) != 0) {
FIRCLSSDKLog("Unable to setup stack %s\n", strerror(errno));
return false;
}
}
#endif
// re-install the original handlers, if any
FIRCLSSignalEnumerateHandledSignals(^(int idx, int currentSignal) {
if (roContext->originalActions[idx].sa_sigaction == NULL) {
return;
}
if (sigaction(currentSignal, &roContext->originalActions[idx], 0) != 0) {
FIRCLSSDKLog("Unable to install handler for %d (%s)\n", currentSignal, strerror(errno));
success = false;
}
// invoke original handler for current signal
if (signal < 0) {
return;
}
if (signal == currentSignal) {
roContext->originalActions[idx].sa_sigaction(signal, info, uapVoid);
}
});
return success;
}
void FIRCLSSignalNameLookup(int number, int code, const char **name, const char **codeName) {
if (!name || !codeName) {
return;
}
*codeName = NULL;
switch (number) {
case SIGABRT:
*name = "SIGABRT";
*codeName = "ABORT";
break;
case SIGBUS:
*name = "SIGBUS";
break;
case SIGFPE:
*name = "SIGFPE";
break;
case SIGILL:
*name = "SIGILL";
break;
case SIGSEGV:
*name = "SIGSEGV";
break;
case SIGSYS:
*name = "SIGSYS";
break;
case SIGTRAP:
*name = "SIGTRAP";
break;
default:
*name = "UNKNOWN";
break;
}
}
static void FIRCLSSignalRecordSignal(int savedErrno, siginfo_t *info, void *uapVoid) {
if (!_firclsContext.readonly) {
return;
}
if (FIRCLSContextMarkAndCheckIfCrashed()) {
FIRCLSSDKLog("Error: aborting signal handler because crash has already occurred");
exit(1);
return;
}
FIRCLSFile file;
if (!FIRCLSFileInitWithPath(&file, _firclsContext.readonly->signal.path, false)) {
FIRCLSSDKLog("Unable to open signal file\n");
return;
}
FIRCLSFileWriteSectionStart(&file, "signal");
FIRCLSFileWriteHashStart(&file);
if (FIRCLSIsValidPointer(info)) {
FIRCLSFileWriteHashEntryUint64(&file, "number", info->si_signo);
FIRCLSFileWriteHashEntryUint64(&file, "code", info->si_code);
FIRCLSFileWriteHashEntryUint64(&file, "address", (uint64_t)info->si_addr);
const char *name = NULL;
const char *codeName = NULL;
FIRCLSSignalNameLookup(info->si_signo, info->si_code, &name, &codeName);
FIRCLSFileWriteHashEntryString(&file, "name", name);
FIRCLSFileWriteHashEntryString(&file, "code_name", codeName);
}
FIRCLSFileWriteHashEntryUint64(&file, "errno", savedErrno);
FIRCLSFileWriteHashEntryUint64(&file, "time", time(NULL));
FIRCLSFileWriteHashEnd(&file);
FIRCLSFileWriteSectionEnd(&file);
FIRCLSHandler(&file, mach_thread_self(), uapVoid);
FIRCLSFileClose(&file);
}
static void FIRCLSSignalHandler(int signal, siginfo_t *info, void *uapVoid) {
int savedErrno;
sigset_t set;
// save errno, both because it is interesting, and so we can restore it afterwards
savedErrno = errno;
errno = 0;
FIRCLSSDKLog("Signal: %d\n", signal);
// it is important to do this before unmasking signals, otherwise we can get
// called in a loop
FIRCLSSignalSafeRemoveHandlers(true);
sigfillset(&set);
if (sigprocmask(SIG_UNBLOCK, &set, NULL) != 0) {
FIRCLSSDKLog("Unable to unmask signals - we risk infinite recursion here\n");
}
// check info and uapVoid, and set them to appropriate values if invalid. This can happen
// if we have been called without the SA_SIGINFO flag set
if (!FIRCLSIsValidPointer(info)) {
info = NULL;
}
if (!FIRCLSIsValidPointer(uapVoid)) {
uapVoid = NULL;
}
FIRCLSSignalRecordSignal(savedErrno, info, uapVoid);
// re-install original handlers
if (_firclsContext.readonly) {
FIRCLSSignalSafeInstallPreexistingHandlers(&_firclsContext.readonly->signal, signal, info,
uapVoid);
}
// restore errno
errno = savedErrno;
}
#endif

View File

@ -0,0 +1,56 @@
// Copyright 2019 Google
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#pragma once
#include "Crashlytics/Crashlytics/Helpers/FIRCLSFeatures.h"
#include "Crashlytics/Crashlytics/Helpers/FIRCLSFile.h"
#include <signal.h>
#include <stdbool.h>
// per man sigaltstack, MINSIGSTKSZ is the minimum *overhead* needed to support
// a signal stack. The actual stack size must be larger. Let's pick the recommended
// size.
#if CLS_USE_SIGALTSTACK
#define CLS_SIGNAL_HANDLER_STACK_SIZE (SIGSTKSZ * 2)
#else
#define CLS_SIGNAL_HANDLER_STACK_SIZE 0
#endif
#if CLS_SIGNAL_SUPPORTED
#define FIRCLSSignalCount (7)
typedef struct {
const char* path;
struct sigaction originalActions[FIRCLSSignalCount];
#if CLS_USE_SIGALTSTACK
stack_t originalStack;
#endif
} FIRCLSSignalReadContext;
void FIRCLSSignalInitialize(FIRCLSSignalReadContext* roContext);
void FIRCLSSignalCheckHandlers(void);
void FIRCLSSignalSafeRemoveHandlers(bool includingAbort);
bool FIRCLSSignalSafeInstallPreexistingHandlers(FIRCLSSignalReadContext* roContext,
const int signal,
siginfo_t* info,
void* uapVoid);
void FIRCLSSignalNameLookup(int number, int code, const char** name, const char** codeName);
void FIRCLSSignalEnumerateHandledSignals(void (^block)(int idx, int signal));
#endif

View File

@ -0,0 +1,238 @@
// Copyright 2019 Google
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <stdatomic.h>
#include "Crashlytics/Crashlytics/Helpers/FIRCLSAllocate.h"
#include "Crashlytics/Crashlytics/Components/FIRCLSHost.h"
#include "Crashlytics/Crashlytics/Helpers/FIRCLSUtility.h"
#include <errno.h>
#include <libkern/OSAtomic.h>
#include <mach/vm_map.h>
#include <mach/vm_param.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
void* FIRCLSAllocatorSafeAllocateFromRegion(FIRCLSAllocationRegion* region, size_t size);
FIRCLSAllocatorRef FIRCLSAllocatorCreate(size_t writableSpace, size_t readableSpace) {
FIRCLSAllocatorRef allocator;
FIRCLSAllocationRegion writableRegion;
FIRCLSAllocationRegion readableRegion;
size_t allocationSize;
vm_size_t pageSize;
void* buffer;
// | GUARD | WRITABLE_REGION | GUARD | READABLE_REGION | GUARD |
pageSize = FIRCLSHostGetPageSize();
readableSpace += sizeof(FIRCLSAllocator); // add the space for our allocator itself
// we can only protect at the page level, so we need all of our regions to be
// exact multiples of pages. But, we don't need anything in the special-case of zero.
writableRegion.size = 0;
if (writableSpace > 0) {
writableRegion.size = ((writableSpace / pageSize) + 1) * pageSize;
}
readableRegion.size = 0;
if (readableSpace > 0) {
readableRegion.size = ((readableSpace / pageSize) + 1) * pageSize;
}
// Make one big, continuous allocation, adding additional pages for our guards. Note
// that we cannot use malloc (or valloc) in this case, because we need to assert full
// ownership over these allocations. mmap is a much better choice. We also mark these
// pages as MAP_NOCACHE.
allocationSize = writableRegion.size + readableRegion.size + pageSize * 3;
buffer =
mmap(0, allocationSize, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE | MAP_NOCACHE, -1, 0);
if (buffer == MAP_FAILED) {
FIRCLSSDKLogError("Mapping failed %s\n", strerror(errno));
return NULL;
}
// move our cursors into position
writableRegion.cursor = (void*)((uintptr_t)buffer + pageSize);
readableRegion.cursor = (void*)((uintptr_t)buffer + pageSize + writableRegion.size + pageSize);
writableRegion.start = writableRegion.cursor;
readableRegion.start = readableRegion.cursor;
FIRCLSSDKLogInfo("Mapping: %p %p %p, total: %zu K\n", buffer, writableRegion.start,
readableRegion.start, allocationSize / 1024);
// protect first guard page
if (mprotect(buffer, pageSize, PROT_NONE) != 0) {
FIRCLSSDKLogError("First guard protection failed %s\n", strerror(errno));
return NULL;
}
// middle guard
if (mprotect((void*)((uintptr_t)buffer + pageSize + writableRegion.size), pageSize, PROT_NONE) !=
0) {
FIRCLSSDKLogError("Middle guard protection failed %s\n", strerror(errno));
return NULL;
}
// end guard
if (mprotect((void*)((uintptr_t)buffer + pageSize + writableRegion.size + pageSize +
readableRegion.size),
pageSize, PROT_NONE) != 0) {
FIRCLSSDKLogError("Last guard protection failed %s\n", strerror(errno));
return NULL;
}
// now, perform our first "allocation", which is to place our allocator into the read-only region
allocator = FIRCLSAllocatorSafeAllocateFromRegion(&readableRegion, sizeof(FIRCLSAllocator));
// set up its data structure
allocator->buffer = buffer;
allocator->protectionEnabled = false;
allocator->readableRegion = readableRegion;
allocator->writeableRegion = writableRegion;
FIRCLSSDKLogDebug("Allocator successfully created %p", allocator);
return allocator;
}
void FIRCLSAllocatorDestroy(FIRCLSAllocatorRef allocator) {
if (allocator) {
}
}
bool FIRCLSAllocatorProtect(FIRCLSAllocatorRef allocator) {
void* address;
if (!FIRCLSIsValidPointer(allocator)) {
FIRCLSSDKLogError("Invalid allocator");
return false;
}
if (allocator->protectionEnabled) {
FIRCLSSDKLogWarn("Write protection already enabled");
return true;
}
// This has to be done first
allocator->protectionEnabled = true;
vm_size_t pageSize = FIRCLSHostGetPageSize();
// readable region
address =
(void*)((uintptr_t)allocator->buffer + pageSize + allocator->writeableRegion.size + pageSize);
return mprotect(address, allocator->readableRegion.size, PROT_READ) == 0;
}
bool FIRCLSAllocatorUnprotect(FIRCLSAllocatorRef allocator) {
size_t bufferSize;
if (!allocator) {
return false;
}
vm_size_t pageSize = FIRCLSHostGetPageSize();
bufferSize = (uintptr_t)allocator->buffer + pageSize + allocator->writeableRegion.size +
pageSize + allocator->readableRegion.size + pageSize;
allocator->protectionEnabled =
!(mprotect(allocator->buffer, bufferSize, PROT_READ | PROT_WRITE) == 0);
return allocator->protectionEnabled;
}
void* FIRCLSAllocatorSafeAllocateFromRegion(FIRCLSAllocationRegion* region, size_t size) {
void* newCursor;
void* originalCursor;
// Here's the idea
// - read the current cursor
// - compute what our new cursor should be
// - attempt a swap
// if the swap fails, some other thread has modified stuff, and we have to start again
// if the swap works, everything has been updated correctly and we are done
do {
originalCursor = region->cursor;
// this shouldn't happen unless we make a mistake with our size pre-computations
if ((uintptr_t)originalCursor - (uintptr_t)region->start + size > region->size) {
FIRCLSSDKLog("Unable to allocate sufficient memory, falling back to malloc\n");
void* ptr = malloc(size);
if (!ptr) {
FIRCLSSDKLog("Unable to malloc in FIRCLSAllocatorSafeAllocateFromRegion\n");
return NULL;
}
return ptr;
}
newCursor = (void*)((uintptr_t)originalCursor + size);
} while (!atomic_compare_exchange_strong(&region->cursor, &originalCursor, newCursor));
return originalCursor;
}
void* FIRCLSAllocatorSafeAllocate(FIRCLSAllocatorRef allocator,
size_t size,
FIRCLSAllocationType type) {
FIRCLSAllocationRegion* region;
if (!allocator) {
// fall back to malloc in this case
FIRCLSSDKLog("Allocator invalid, falling back to malloc\n");
void* ptr = malloc(size);
if (!ptr) {
FIRCLSSDKLog("Unable to malloc in FIRCLSAllocatorSafeAllocate\n");
return NULL;
}
return ptr;
}
if (allocator->protectionEnabled) {
FIRCLSSDKLog("Allocator already protected, falling back to malloc\n");
void* ptr = malloc(size);
if (!ptr) {
FIRCLSSDKLog("Unable to malloc in FIRCLSAllocatorSafeAllocate\n");
return NULL;
}
return ptr;
}
switch (type) {
case CLS_READONLY:
region = &allocator->readableRegion;
break;
case CLS_READWRITE:
region = &allocator->writeableRegion;
break;
default:
return NULL;
}
return FIRCLSAllocatorSafeAllocateFromRegion(region, size);
}
void FIRCLSAllocatorFree(FIRCLSAllocatorRef allocator, void* ptr) {
if (!allocator) {
free(ptr);
}
// how do we do deallocations?
}

View File

@ -0,0 +1,48 @@
// Copyright 2019 Google
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "Crashlytics/Crashlytics/Helpers/FIRCLSFeatures.h"
#pragma once
#include <stdbool.h>
#include <sys/types.h>
typedef enum { CLS_READONLY = 0, CLS_READWRITE = 1 } FIRCLSAllocationType;
typedef struct {
size_t size;
void* start;
_Atomic(void*) volatile cursor;
} FIRCLSAllocationRegion;
typedef struct {
void* buffer;
bool protectionEnabled;
FIRCLSAllocationRegion writeableRegion;
FIRCLSAllocationRegion readableRegion;
} FIRCLSAllocator;
typedef FIRCLSAllocator* FIRCLSAllocatorRef;
FIRCLSAllocatorRef FIRCLSAllocatorCreate(size_t writableSpace, size_t readableSpace);
void FIRCLSAllocatorDestroy(FIRCLSAllocatorRef allocator);
bool FIRCLSAllocatorProtect(FIRCLSAllocatorRef allocator);
bool FIRCLSAllocatorUnprotect(FIRCLSAllocatorRef allocator);
void* FIRCLSAllocatorSafeAllocate(FIRCLSAllocatorRef allocator,
size_t size,
FIRCLSAllocationType type);
const char* FIRCLSAllocatorSafeStrdup(FIRCLSAllocatorRef allocator, const char* string);
void FIRCLSAllocatorFree(FIRCLSAllocatorRef allocator, void* ptr);

View File

@ -0,0 +1,37 @@
// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import <Foundation/Foundation.h>
#if defined(__IPHONE_15_0)
#define CLS_METRICKIT_SUPPORTED (__has_include(<MetricKit/MetricKit.h>) && TARGET_OS_IOS)
#else
#define CLS_METRICKIT_SUPPORTED 0
#endif
#if CLS_METRICKIT_SUPPORTED
#import <MetricKit/MetricKit.h>
/*
* Helper class for parsing the `MXCallStackTree` that we receive from MetricKit. Flattens the
* nested structure into a structure similar to what is used in Crashlytics.
*/
@interface FIRCLSCallStackTree : NSObject
- (instancetype)initWithMXCallStackTree:(MXCallStackTree *)callStackTree API_AVAILABLE(ios(14.0));
- (NSArray *)getArrayRepresentation;
- (NSArray *)getFramesOfBlamedThread;
- (instancetype)init NS_UNAVAILABLE;
@end
#endif

View File

@ -0,0 +1,147 @@
// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import <Foundation/Foundation.h>
#import "Crashlytics/Crashlytics/Helpers/FIRCLSCallStackTree.h"
#if CLS_METRICKIT_SUPPORTED
@interface FIRCLSFrame : NSObject
@property long address;
@property long sampleCount;
@property long offsetIntoBinaryTextSegment;
@property NSString *binaryName;
@property NSUUID *binaryUUID;
@end
@implementation FIRCLSFrame
@end
@interface FIRCLSThread : NSObject
@property NSString *threadName;
@property BOOL threadBlamed;
@property NSArray<FIRCLSFrame *> *frames;
@end
@implementation FIRCLSThread
@end
@interface FIRCLSCallStackTree ()
@property NSArray<FIRCLSThread *> *threads;
@property(nonatomic) BOOL callStackPerThread;
@end
@implementation FIRCLSCallStackTree
- (instancetype)initWithMXCallStackTree:(MXCallStackTree *)callStackTree {
NSData *jsonCallStackTree = callStackTree.JSONRepresentation;
if ([jsonCallStackTree length] == 0) return nil;
NSError *error = nil;
NSDictionary *jsonDictionary = [NSJSONSerialization JSONObjectWithData:jsonCallStackTree
options:0
error:&error];
if (error) {
NSLog(@"Crashlytics: error creating json");
return nil;
}
self = [super init];
if (!self) {
return nil;
}
_callStackPerThread = [[jsonDictionary objectForKey:@"callStackPerThread"] boolValue];
// Recurse through the frames in the callStackTree and add them all to an array
NSMutableArray<FIRCLSThread *> *threads = [[NSMutableArray alloc] init];
NSArray *callStacks = jsonDictionary[@"callStacks"];
for (id object in callStacks) {
NSMutableArray<FIRCLSFrame *> *frames = [[NSMutableArray alloc] init];
[self flattenSubFrames:object[@"callStackRootFrames"] intoFrames:frames];
FIRCLSThread *thread = [[FIRCLSThread alloc] init];
thread.threadBlamed = [[object objectForKey:@"threadAttributed"] boolValue];
thread.frames = frames;
[threads addObject:thread];
}
_threads = threads;
return self;
}
// Flattens the nested structure we receive from MetricKit into an array of frames.
- (void)flattenSubFrames:(NSArray *)callStacks intoFrames:(NSMutableArray *)frames {
NSDictionary *rootFrames = [callStacks firstObject];
FIRCLSFrame *frame = [[FIRCLSFrame alloc] init];
frame.offsetIntoBinaryTextSegment =
[[rootFrames valueForKey:@"offsetIntoBinaryTextSegment"] longValue];
frame.address = [[rootFrames valueForKey:@"address"] longValue];
frame.sampleCount = [[rootFrames valueForKey:@"sampleCount"] longValue];
frame.binaryUUID = [rootFrames valueForKey:@"binaryUUID"];
frame.binaryName = [rootFrames valueForKey:@"binaryName"];
[frames addObject:frame];
// Recurse through any subframes and add them to the array.
if ([rootFrames objectForKey:@"subFrames"]) {
[self flattenSubFrames:[rootFrames objectForKey:@"subFrames"] intoFrames:frames];
}
}
- (NSArray *)getArrayRepresentation {
NSMutableArray *threadArray = [[NSMutableArray alloc] init];
for (FIRCLSThread *thread in self.threads) {
[threadArray addObject:[self getDictionaryRepresentation:thread]];
}
return threadArray;
}
- (NSDictionary *)getDictionaryRepresentation:(FIRCLSThread *)thread {
NSMutableDictionary *dictionary = [[NSMutableDictionary alloc] init];
[dictionary setObject:@{} forKey:@"registers"];
NSMutableArray *frameArray = [[NSMutableArray alloc] init];
for (FIRCLSFrame *frame in thread.frames) {
[frameArray addObject:[NSNumber numberWithLong:frame.address]];
}
[dictionary setObject:frameArray forKey:@"stacktrace"];
[dictionary setObject:[NSNumber numberWithBool:thread.threadBlamed] forKey:@"crashed"];
return dictionary;
}
- (NSArray *)getFramesOfBlamedThread {
for (FIRCLSThread *thread in self.threads) {
if (thread.threadBlamed) {
return [self convertFramesFor:thread];
}
}
if ([self.threads count] > 0) {
return [self convertFramesFor:self.threads.firstObject];
}
return [NSArray array];
}
- (NSArray *)convertFramesFor:(FIRCLSThread *)thread {
NSMutableArray *frames = [[NSMutableArray alloc] init];
for (FIRCLSFrame *frame in thread.frames) {
[frames addObject:@{
@"pc" : [NSNumber numberWithLong:frame.address],
@"offset" : [NSNumber numberWithLong:frame.offsetIntoBinaryTextSegment],
@"line" : @0
}];
}
return frames;
}
@end
#endif

View File

@ -0,0 +1,38 @@
// Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#pragma once
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface FIRCLSContextInitData : NSObject
@property(nonatomic, copy, nullable) NSString* customBundleId;
@property(nonatomic, copy) NSString* rootPath;
@property(nonatomic, copy) NSString* previouslyCrashedFileRootPath;
@property(nonatomic, copy) NSString* sessionId;
@property(nonatomic, copy) NSString* appQualitySessionId;
@property(nonatomic, copy) NSString* betaToken;
@property(nonatomic) BOOL errorsEnabled;
@property(nonatomic) BOOL customExceptionsEnabled;
@property(nonatomic) uint32_t maxCustomExceptions;
@property(nonatomic) uint32_t maxErrorLogSize;
@property(nonatomic) uint32_t maxLogSize;
@property(nonatomic) uint32_t maxKeyValues;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,18 @@
// Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import "Crashlytics/Crashlytics/Helpers/FIRCLSContextInitData.h"
@implementation FIRCLSContextInitData
@end

View File

@ -0,0 +1,89 @@
// Copyright 2019 Google
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#pragma once
#include <TargetConditionals.h>
// macro trickiness
#define CONCAT_EXPANDED(a, b) a##b
#define CONCAT(a, b) CONCAT_EXPANDED(a, b)
// These macros generate a function to force a symbol for the containing .o, to work around an issue
// where strip will not strip debug information without a symbol to strip.
#define DUMMY_FUNCTION_NAME(x) CONCAT(fircls_strip_this_, x)
#define INJECT_STRIP_SYMBOL(x) \
void DUMMY_FUNCTION_NAME(x)(void) { \
}
// These make some target os types available to previous versions of xcode that do not yet have them
// in their SDKs
#ifndef TARGET_OS_IOS
#define TARGET_OS_IOS TARGET_OS_IPHONE
#endif
#ifndef TARGET_OS_WATCH
#define TARGET_OS_WATCH 0
#endif
#ifndef TARGET_OS_TV
#define TARGET_OS_TV 0
#endif
// Whether MetricKit should be supported
#if defined(__IPHONE_15_0)
#define CLS_METRICKIT_SUPPORTED (__has_include(<MetricKit/MetricKit.h>) && TARGET_OS_IOS)
#else
#define CLS_METRICKIT_SUPPORTED 0
#endif
// These help compile based on availability of technologies/frameworks.
#define CLS_TARGET_OS_OSX (TARGET_OS_MAC && !TARGET_OS_IPHONE)
#define CLS_TARGET_OS_HAS_UIKIT (TARGET_OS_IOS || TARGET_OS_TV)
// arch definitions
#if defined(__arm__) || defined(__arm64__) || defined(__arm64e__)
#include <arm/arch.h>
#endif
// VisionOS support
#if defined(TARGET_OS_VISION) && TARGET_OS_VISION
#define CLS_TARGET_OS_VISION 1
#else
#define CLS_TARGET_OS_VISION 0
#endif
#if defined(__arm__)
#define CLS_CPU_ARM 1
#endif
#if defined(__arm64__) || defined(__arm64e__)
#define CLS_CPU_ARM64 1
#endif
#if defined(__ARM_ARCH_7S__)
#define CLS_CPU_ARMV7S 1
#endif
#if defined(_ARM_ARCH_7)
#define CLS_CPU_ARMV7 1
#endif
#if defined(_ARM_ARCH_6)
#define CLS_CPU_ARMV6 1
#endif
#if defined(__i386__)
#define CLS_CPU_I386 1
#endif
#if defined(__x86_64__)
#define CLS_CPU_X86_64 1
#endif
#define CLS_CPU_X86 (CLS_CPU_I386 || CLS_CPU_X86_64)
#define CLS_CPU_64BIT (CLS_CPU_X86_64 || CLS_CPU_ARM64)

View File

@ -0,0 +1,32 @@
// Copyright 2019 Google
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#pragma once
#include "Crashlytics/Crashlytics/Helpers/FIRCLSDefines.h"
#define CLS_MEMORY_PROTECTION_ENABLED 1
#define CLS_COMPACT_UNWINDED_ENABLED 1
#define CLS_DWARF_UNWINDING_ENABLED 1
#define CLS_USE_SIGALTSTACK (!TARGET_OS_WATCH && !TARGET_OS_TV)
#define CLS_CAN_SUSPEND_THREADS !TARGET_OS_WATCH
#define CLS_MACH_EXCEPTION_SUPPORTED (!TARGET_OS_WATCH && !TARGET_OS_TV)
#define CLS_SIGNAL_SUPPORTED !TARGET_OS_WATCH // As of WatchOS 3, Signal crashes are not supported
#define CLS_COMPACT_UNWINDING_SUPPORTED \
((CLS_CPU_I386 || CLS_CPU_X86_64 || CLS_CPU_ARM64) && CLS_COMPACT_UNWINDED_ENABLED)
#define CLS_DWARF_UNWINDING_SUPPORTED \
(CLS_COMPACT_UNWINDING_SUPPORTED && CLS_DWARF_UNWINDING_ENABLED)

View File

@ -0,0 +1,110 @@
// Copyright 2019 Google
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#pragma once
#include <stdbool.h>
#include <stdint.h>
#include <sys/cdefs.h>
// Required for 1P builds
#include <stddef.h>
#include <sys/types.h>
#include <unistd.h>
#if defined(__OBJC__)
#import <Foundation/Foundation.h>
#endif
__BEGIN_DECLS
typedef struct {
int fd;
int collectionDepth;
bool needComma;
bool bufferWrites;
char* writeBuffer;
size_t writeBufferLength;
off_t writtenLength;
} FIRCLSFile;
typedef FIRCLSFile* FIRCLSFileRef;
#define CLS_FILE_MAX_STRING_LENGTH (10240)
#define CLS_FILE_HEX_BUFFER \
(32) // must be at least 2, and should be even (to account for 2 chars per hex value)
#define CLS_FILE_MAX_WRITE_ATTEMPTS (50)
extern const size_t FIRCLSWriteBufferLength;
// make sure to stop work if either FIRCLSFileInit... method returns false, because the FIRCLSFile
// struct will contain garbage data!
bool FIRCLSFileInitWithPath(FIRCLSFile* file, const char* path, bool bufferWrites);
bool FIRCLSFileInitWithPathMode(FIRCLSFile* file,
const char* path,
bool appendMode,
bool bufferWrites);
void FIRCLSFileFlushWriteBuffer(FIRCLSFile* file);
bool FIRCLSFileClose(FIRCLSFile* file);
bool FIRCLSFileCloseWithOffset(FIRCLSFile* file, off_t* finalSize);
bool FIRCLSFileIsOpen(FIRCLSFile* file);
bool FIRCLSFileLoopWithWriteBlock(const void* buffer,
size_t length,
ssize_t (^writeBlock)(const void* partialBuffer,
size_t partialLength));
bool FIRCLSFileWriteWithRetries(int fd, const void* buffer, size_t length);
// writing
void FIRCLSFileWriteSectionStart(FIRCLSFile* file, const char* name);
void FIRCLSFileWriteSectionEnd(FIRCLSFile* file);
void FIRCLSFileWriteHashStart(FIRCLSFile* file);
void FIRCLSFileWriteHashEnd(FIRCLSFile* file);
void FIRCLSFileWriteHashKey(FIRCLSFile* file, const char* key);
void FIRCLSFileWriteHashEntryUint64(FIRCLSFile* file, const char* key, uint64_t value);
void FIRCLSFileWriteHashEntryInt64(FIRCLSFile* file, const char* key, int64_t value);
void FIRCLSFileWriteHashEntryString(FIRCLSFile* file, const char* key, const char* value);
void FIRCLSFileWriteStringUnquoted(FIRCLSFile* file, const char* string);
#if defined(__OBJC__)
void FIRCLSFileWriteHashEntryNSString(FIRCLSFile* file, const char* key, NSString* string);
void FIRCLSFileWriteHashEntryNSStringUnlessNilOrEmpty(FIRCLSFile* file,
const char* key,
NSString* string);
#endif
void FIRCLSFileWriteHashEntryHexEncodedString(FIRCLSFile* file, const char* key, const char* value);
void FIRCLSFileWriteHashEntryBoolean(FIRCLSFile* file, const char* key, bool value);
void FIRCLSFileWriteArrayStart(FIRCLSFile* file);
void FIRCLSFileWriteArrayEnd(FIRCLSFile* file);
void FIRCLSFileWriteArrayEntryUint64(FIRCLSFile* file, uint64_t value);
void FIRCLSFileWriteArrayEntryString(FIRCLSFile* file, const char* value);
void FIRCLSFileWriteArrayEntryHexEncodedString(FIRCLSFile* file, const char* value);
void FIRCLSFileFDWriteUInt64(int fd, uint64_t number, bool hex);
void FIRCLSFileFDWriteInt64(int fd, int64_t number);
void FIRCLSFileWriteUInt64(FIRCLSFile* file, uint64_t number, bool hex);
void FIRCLSFileWriteInt64(FIRCLSFile* file, int64_t number);
#if defined(__OBJC__) && TARGET_OS_MAC
NSArray* FIRCLSFileReadSections(const char* path,
bool deleteOnFailure,
NSObject* (^transformer)(id obj));
NSString* FIRCLSFileHexEncodeString(const char* string);
NSString* FIRCLSFileHexDecodeString(const char* string);
#endif
__END_DECLS

View File

@ -0,0 +1,723 @@
// Copyright 2019 Google
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "Crashlytics/Crashlytics/Helpers/FIRCLSFile.h"
#include "Crashlytics/Crashlytics/Helpers/FIRCLSUtility.h"
#include "Crashlytics/Shared/FIRCLSByteUtility.h"
#if TARGET_OS_MAC
#include <Foundation/Foundation.h>
#endif
#include <sys/stat.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
// uint64_t should only have max 19 chars in base 10, and less in base 16
static const size_t FIRCLSUInt64StringBufferLength = 21;
static const size_t FIRCLSStringBufferLength = 16;
const size_t FIRCLSWriteBufferLength = 1000;
static bool FIRCLSFileInit(
FIRCLSFile* file, const char* path, int fdm, bool appendMode, bool bufferWrites);
static void FIRCLSFileWriteToFileDescriptorOrBuffer(FIRCLSFile* file,
const char* string,
size_t length);
static void FIRCLSFileWriteToBuffer(FIRCLSFile* file, const char* string, size_t length);
static void FIRCLSFileWriteToFileDescriptor(FIRCLSFile* file, const char* string, size_t length);
short FIRCLSFilePrepareUInt64(char* buffer, uint64_t number, bool hex);
static void FIRCLSFileWriteString(FIRCLSFile* file, const char* string);
static void FIRCLSFileWriteHexEncodedString(FIRCLSFile* file, const char* string);
static void FIRCLSFileWriteBool(FIRCLSFile* file, bool value);
static void FIRCLSFileWriteCollectionStart(FIRCLSFile* file, const char openingChar);
static void FIRCLSFileWriteCollectionEnd(FIRCLSFile* file, const char closingChar);
static void FIRCLSFileWriteCollectionEntryProlog(FIRCLSFile* file);
static void FIRCLSFileWriteCollectionEntryEpilog(FIRCLSFile* file);
#define CLS_FILE_DEBUG_LOGGING 0
#pragma mark - File Structure
static bool FIRCLSFileInit(
FIRCLSFile* file, const char* path, int fd, bool appendMode, bool bufferWrites) {
if (!file) {
FIRCLSSDKLog("Error: file is null\n");
return false;
}
if (fd < 0) {
FIRCLSSDKLog("Error: file descriptor invalid\n");
return false;
}
memset(file, 0, sizeof(FIRCLSFile));
file->fd = fd;
file->bufferWrites = bufferWrites;
if (bufferWrites) {
file->writeBuffer = malloc(FIRCLSWriteBufferLength * sizeof(char));
if (!file->writeBuffer) {
FIRCLSErrorLog(@"Unable to malloc in FIRCLSFileInit");
return false;
}
file->writeBufferLength = 0;
}
file->writtenLength = 0;
if (appendMode) {
NSError* attributesError;
NSString* objCPath = [NSString stringWithCString:path encoding:NSUTF8StringEncoding];
NSDictionary* fileAttributes =
[[NSFileManager defaultManager] attributesOfItemAtPath:objCPath error:&attributesError];
if (attributesError != nil) {
FIRCLSErrorLog(@"Failed to read filesize from %@ with error %@", objCPath, attributesError);
return false;
}
NSNumber* fileSizeNumber = [fileAttributes objectForKey:NSFileSize];
long long currentFileSize = [fileSizeNumber longLongValue];
if (currentFileSize > 0) {
file->writtenLength += currentFileSize;
}
}
return true;
}
bool FIRCLSFileInitWithPath(FIRCLSFile* file, const char* path, bool bufferWrites) {
return FIRCLSFileInitWithPathMode(file, path, true, bufferWrites);
}
bool FIRCLSFileInitWithPathMode(FIRCLSFile* file,
const char* path,
bool appendMode,
bool bufferWrites) {
if (!file) {
FIRCLSSDKLog("Error: file is null\n");
return false;
}
int mask = O_WRONLY | O_CREAT;
if (appendMode) {
mask |= O_APPEND;
} else {
mask |= O_TRUNC;
}
// make sure to call FIRCLSFileInit no matter what
int fd = -1;
if (path) {
#if TARGET_OS_IPHONE
/*
* data-protected non-portable open(2) :
* int open_dprotected_np(user_addr_t path, int flags, int class, int dpflags, int mode)
*/
fd = open_dprotected_np(path, mask, 4, 0, 0644);
#else
fd = open(path, mask, 0644);
#endif
if (fd < 0) {
FIRCLSSDKLog("Error: Unable to open file %s\n", strerror(errno));
}
}
return FIRCLSFileInit(file, path, fd, appendMode, bufferWrites);
}
bool FIRCLSFileClose(FIRCLSFile* file) {
return FIRCLSFileCloseWithOffset(file, NULL);
}
bool FIRCLSFileCloseWithOffset(FIRCLSFile* file, off_t* finalSize) {
if (!FIRCLSIsValidPointer(file)) {
return false;
}
if (file->bufferWrites && FIRCLSIsValidPointer(file->writeBuffer)) {
if (file->writeBufferLength > 0) {
FIRCLSFileFlushWriteBuffer(file);
}
free(file->writeBuffer);
}
if (FIRCLSIsValidPointer(finalSize)) {
*finalSize = file->writtenLength;
}
if (close(file->fd) != 0) {
FIRCLSSDKLog("Error: Unable to close file %s\n", strerror(errno));
return false;
}
memset(file, 0, sizeof(FIRCLSFile));
file->fd = -1;
return true;
}
bool FIRCLSFileIsOpen(FIRCLSFile* file) {
if (!FIRCLSIsValidPointer(file)) {
return false;
}
return file->fd > -1;
}
#pragma mark - Core Writing API
void FIRCLSFileFlushWriteBuffer(FIRCLSFile* file) {
if (!FIRCLSIsValidPointer(file)) {
return;
}
if (!file->bufferWrites) {
return;
}
FIRCLSFileWriteToFileDescriptor(file, file->writeBuffer, file->writeBufferLength);
file->writeBufferLength = 0;
}
static void FIRCLSFileWriteToFileDescriptorOrBuffer(FIRCLSFile* file,
const char* string,
size_t length) {
if (file->bufferWrites) {
if (file->writeBufferLength + length > FIRCLSWriteBufferLength - 1) {
// fill remaining space in buffer
size_t remainingSpace = FIRCLSWriteBufferLength - file->writeBufferLength - 1;
FIRCLSFileWriteToBuffer(file, string, remainingSpace);
FIRCLSFileFlushWriteBuffer(file);
// write remainder of string to newly-emptied buffer
size_t remainingLength = length - remainingSpace;
FIRCLSFileWriteToFileDescriptorOrBuffer(file, string + remainingSpace, remainingLength);
} else {
FIRCLSFileWriteToBuffer(file, string, length);
}
} else {
FIRCLSFileWriteToFileDescriptor(file, string, length);
}
}
void FIRCLSFileWriteStringUnquoted(FIRCLSFile* file, const char* string) {
size_t length = strlen(string);
FIRCLSFileWriteToFileDescriptorOrBuffer(file, string, length);
}
static void FIRCLSFileWriteToFileDescriptor(FIRCLSFile* file, const char* string, size_t length) {
if (!FIRCLSFileWriteWithRetries(file->fd, string, length)) {
return;
}
file->writtenLength += length;
}
// Beware calling this method directly: it will truncate the input string if it's longer
// than the remaining space in the buffer. It's safer to call through
// FIRCLSFileWriteToFileDescriptorOrBuffer.
static void FIRCLSFileWriteToBuffer(FIRCLSFile* file, const char* string, size_t length) {
size_t writeLength = length;
if (file->writeBufferLength + writeLength > FIRCLSWriteBufferLength - 1) {
writeLength = FIRCLSWriteBufferLength - file->writeBufferLength - 1;
}
strncpy(file->writeBuffer + file->writeBufferLength, string, writeLength);
file->writeBufferLength += writeLength;
file->writeBuffer[file->writeBufferLength] = '\0';
}
bool FIRCLSFileLoopWithWriteBlock(const void* buffer,
size_t length,
ssize_t (^writeBlock)(const void* buf, size_t len)) {
for (size_t count = 0; length > 0 && count < CLS_FILE_MAX_WRITE_ATTEMPTS; ++count) {
// try to write all that is left
ssize_t ret = writeBlock(buffer, length);
if (length > SIZE_MAX) {
// if this happens we can't convert it to a signed version due to overflow
return false;
}
const ssize_t signedLength = (ssize_t)length;
if (ret >= 0 && ret == signedLength) {
return true;
}
// Write was unsuccessful (out of space, etc)
if (ret < 0) {
return false;
}
// We wrote more bytes than we expected, abort
if (ret > signedLength) {
return false;
}
// wrote a portion of the data, adjust and keep trying
if (ret > 0) {
length -= ret;
buffer += ret;
continue;
}
// return value is <= 0, which is an error
break;
}
return false;
}
bool FIRCLSFileWriteWithRetries(int fd, const void* buffer, size_t length) {
return FIRCLSFileLoopWithWriteBlock(buffer, length,
^ssize_t(const void* partialBuffer, size_t partialLength) {
return write(fd, partialBuffer, partialLength);
});
}
#pragma mark - Strings
static void FIRCLSFileWriteUnbufferedStringWithSuffix(FIRCLSFile* file,
const char* string,
size_t length,
char suffix) {
char suffixBuffer[2];
// collapse the quote + suffix into one single write call, for a small performance win
suffixBuffer[0] = '"';
suffixBuffer[1] = suffix;
FIRCLSFileWriteToFileDescriptorOrBuffer(file, "\"", 1);
FIRCLSFileWriteToFileDescriptorOrBuffer(file, string, length);
FIRCLSFileWriteToFileDescriptorOrBuffer(file, suffixBuffer, suffix == 0 ? 1 : 2);
}
static void FIRCLSFileWriteStringWithSuffix(FIRCLSFile* file,
const char* string,
size_t length,
char suffix) {
// 2 for quotes, 1 for suffix (if present) and 1 more for null character
const size_t maxStringSize = FIRCLSStringBufferLength - (suffix == 0 ? 3 : 4);
if (length >= maxStringSize) {
FIRCLSFileWriteUnbufferedStringWithSuffix(file, string, length, suffix);
return;
}
// we are trying to achieve this in one write call
// <"><string contents><"><suffix>
char buffer[FIRCLSStringBufferLength];
buffer[0] = '"';
strncpy(buffer + 1, string, length);
buffer[length + 1] = '"';
length += 2;
if (suffix) {
buffer[length] = suffix;
length += 1;
}
// Always add the terminator. strncpy above would copy the terminator, if we supplied length + 1,
// but since we do this suffix adjustment here, it's easier to just fix it up in both cases.
buffer[length + 1] = 0;
FIRCLSFileWriteToFileDescriptorOrBuffer(file, buffer, length);
}
void FIRCLSFileWriteString(FIRCLSFile* file, const char* string) {
if (!string) {
FIRCLSFileWriteToFileDescriptorOrBuffer(file, "null", 4);
return;
}
FIRCLSFileWriteStringWithSuffix(file, string, strlen(string), 0);
}
void FIRCLSFileWriteHexEncodedString(FIRCLSFile* file, const char* string) {
if (!file) {
return;
}
if (!string) {
FIRCLSFileWriteToFileDescriptorOrBuffer(file, "null", 4);
return;
}
char buffer[CLS_FILE_HEX_BUFFER];
memset(buffer, 0, sizeof(buffer));
size_t length = strlen(string);
FIRCLSFileWriteToFileDescriptorOrBuffer(file, "\"", 1);
int bufferIndex = 0;
for (int i = 0; i < length; ++i) {
FIRCLSHexFromByte(string[i], &buffer[bufferIndex]);
bufferIndex += 2; // 1 char => 2 hex values at a time
// we can continue only if we have enough space for two more hex
// characters *and* a terminator. So, we need three total chars
// of space
if (bufferIndex >= CLS_FILE_HEX_BUFFER) {
FIRCLSFileWriteToFileDescriptorOrBuffer(file, buffer, CLS_FILE_HEX_BUFFER);
bufferIndex = 0;
}
}
// Copy the remainder, which could even be the entire string, if it
// fit into the buffer completely. Be careful with bounds checking here.
// The string needs to be non-empty, and we have to have copied at least
// one pair of hex characters in.
if (bufferIndex > 0 && length > 0) {
FIRCLSFileWriteToFileDescriptorOrBuffer(file, buffer, bufferIndex);
}
FIRCLSFileWriteToFileDescriptorOrBuffer(file, "\"", 1);
}
#pragma mark - Integers
void FIRCLSFileWriteUInt64(FIRCLSFile* file, uint64_t number, bool hex) {
char buffer[FIRCLSUInt64StringBufferLength];
short i = FIRCLSFilePrepareUInt64(buffer, number, hex);
char* beginning = &buffer[i]; // Write from a pointer to the beginning of the string.
FIRCLSFileWriteToFileDescriptorOrBuffer(file, beginning, strlen(beginning));
}
void FIRCLSFileFDWriteUInt64(int fd, uint64_t number, bool hex) {
char buffer[FIRCLSUInt64StringBufferLength];
short i = FIRCLSFilePrepareUInt64(buffer, number, hex);
char* beginning = &buffer[i]; // Write from a pointer to the beginning of the string.
FIRCLSFileWriteWithRetries(fd, beginning, strlen(beginning));
}
void FIRCLSFileWriteInt64(FIRCLSFile* file, int64_t number) {
if (number < 0) {
FIRCLSFileWriteToFileDescriptorOrBuffer(file, "-", 1);
number *= -1; // make it positive
}
FIRCLSFileWriteUInt64(file, number, false);
}
void FIRCLSFileFDWriteInt64(int fd, int64_t number) {
if (number < 0) {
FIRCLSFileWriteWithRetries(fd, "-", 1);
number *= -1; // make it positive
}
FIRCLSFileFDWriteUInt64(fd, number, false);
}
short FIRCLSFilePrepareUInt64(char* buffer, uint64_t number, bool hex) {
uint32_t base = hex ? 16 : 10;
// zero it out, which will add a terminator
memset(buffer, 0, FIRCLSUInt64StringBufferLength);
// TODO: look at this closer
// I'm pretty sure there is a bug in this code that
// can result in numbers with leading zeros. Technically,
// those are not valid json.
// Set current index.
short i = FIRCLSUInt64StringBufferLength - 1;
// Loop through filling in the chars from the end.
do {
char value = number % base + '0';
if (value > '9') {
value += 'a' - '9' - 1;
}
buffer[--i] = value;
} while ((number /= base) > 0 && i > 0);
// returns index pointing to the beginning of the string.
return i;
}
void FIRCLSFileWriteBool(FIRCLSFile* file, bool value) {
if (value) {
FIRCLSFileWriteToFileDescriptorOrBuffer(file, "true", 4);
} else {
FIRCLSFileWriteToFileDescriptorOrBuffer(file, "false", 5);
}
}
void FIRCLSFileWriteSectionStart(FIRCLSFile* file, const char* name) {
FIRCLSFileWriteHashStart(file);
FIRCLSFileWriteHashKey(file, name);
}
void FIRCLSFileWriteSectionEnd(FIRCLSFile* file) {
FIRCLSFileWriteHashEnd(file);
FIRCLSFileWriteToFileDescriptorOrBuffer(file, "\n", 1);
}
void FIRCLSFileWriteCollectionStart(FIRCLSFile* file, const char openingChar) {
char string[2];
string[0] = ',';
string[1] = openingChar;
if (file->needComma) {
FIRCLSFileWriteToFileDescriptorOrBuffer(file, string, 2); // write the separator + opening char
} else {
FIRCLSFileWriteToFileDescriptorOrBuffer(file, &string[1], 1); // write only the opening char
}
file->collectionDepth++;
file->needComma = false;
}
void FIRCLSFileWriteCollectionEnd(FIRCLSFile* file, const char closingChar) {
FIRCLSFileWriteToFileDescriptorOrBuffer(file, &closingChar, 1);
if (file->collectionDepth <= 0) {
// FIRCLSSafeLog("Collection depth invariant violated\n");
return;
}
file->collectionDepth--;
file->needComma = file->collectionDepth > 0;
}
void FIRCLSFileWriteCollectionEntryProlog(FIRCLSFile* file) {
if (file->needComma) {
FIRCLSFileWriteToFileDescriptorOrBuffer(file, ",", 1);
}
}
void FIRCLSFileWriteCollectionEntryEpilog(FIRCLSFile* file) {
file->needComma = true;
}
void FIRCLSFileWriteHashStart(FIRCLSFile* file) {
FIRCLSFileWriteCollectionStart(file, '{');
}
void FIRCLSFileWriteHashEnd(FIRCLSFile* file) {
FIRCLSFileWriteCollectionEnd(file, '}');
}
void FIRCLSFileWriteHashKey(FIRCLSFile* file, const char* key) {
FIRCLSFileWriteCollectionEntryProlog(file);
FIRCLSFileWriteStringWithSuffix(file, key, strlen(key), ':');
file->needComma = false;
}
void FIRCLSFileWriteHashEntryUint64(FIRCLSFile* file, const char* key, uint64_t value) {
// no prolog needed because it comes from the key
FIRCLSFileWriteHashKey(file, key);
FIRCLSFileWriteUInt64(file, value, false);
FIRCLSFileWriteCollectionEntryEpilog(file);
}
void FIRCLSFileWriteHashEntryInt64(FIRCLSFile* file, const char* key, int64_t value) {
// prolog from key
FIRCLSFileWriteHashKey(file, key);
FIRCLSFileWriteInt64(file, value);
FIRCLSFileWriteCollectionEntryEpilog(file);
}
void FIRCLSFileWriteHashEntryString(FIRCLSFile* file, const char* key, const char* value) {
FIRCLSFileWriteHashKey(file, key);
FIRCLSFileWriteString(file, value);
FIRCLSFileWriteCollectionEntryEpilog(file);
}
void FIRCLSFileWriteHashEntryNSString(FIRCLSFile* file, const char* key, NSString* string) {
FIRCLSFileWriteHashEntryString(file, key, [string UTF8String]);
}
void FIRCLSFileWriteHashEntryNSStringUnlessNilOrEmpty(FIRCLSFile* file,
const char* key,
NSString* string) {
if ([string length] > 0) {
FIRCLSFileWriteHashEntryString(file, key, [string UTF8String]);
}
}
void FIRCLSFileWriteHashEntryHexEncodedString(FIRCLSFile* file,
const char* key,
const char* value) {
FIRCLSFileWriteHashKey(file, key);
FIRCLSFileWriteHexEncodedString(file, value);
FIRCLSFileWriteCollectionEntryEpilog(file);
}
void FIRCLSFileWriteHashEntryBoolean(FIRCLSFile* file, const char* key, bool value) {
FIRCLSFileWriteHashKey(file, key);
FIRCLSFileWriteBool(file, value);
FIRCLSFileWriteCollectionEntryEpilog(file);
}
void FIRCLSFileWriteArrayStart(FIRCLSFile* file) {
FIRCLSFileWriteCollectionStart(file, '[');
}
void FIRCLSFileWriteArrayEnd(FIRCLSFile* file) {
FIRCLSFileWriteCollectionEnd(file, ']');
}
void FIRCLSFileWriteArrayEntryUint64(FIRCLSFile* file, uint64_t value) {
FIRCLSFileWriteCollectionEntryProlog(file);
FIRCLSFileWriteUInt64(file, value, false);
FIRCLSFileWriteCollectionEntryEpilog(file);
}
void FIRCLSFileWriteArrayEntryString(FIRCLSFile* file, const char* value) {
FIRCLSFileWriteCollectionEntryProlog(file);
FIRCLSFileWriteString(file, value);
FIRCLSFileWriteCollectionEntryEpilog(file);
}
void FIRCLSFileWriteArrayEntryHexEncodedString(FIRCLSFile* file, const char* value) {
FIRCLSFileWriteCollectionEntryProlog(file);
FIRCLSFileWriteHexEncodedString(file, value);
FIRCLSFileWriteCollectionEntryEpilog(file);
}
NSArray* FIRCLSFileReadSections(const char* path,
bool deleteOnFailure,
NSObject* (^transformer)(id obj)) {
if (!FIRCLSIsValidPointer(path)) {
FIRCLSSDKLogError("Error: input path is invalid\n");
return nil;
}
NSString* pathString = [NSString stringWithUTF8String:path];
NSString* contents = [NSString stringWithContentsOfFile:pathString
encoding:NSUTF8StringEncoding
error:nil];
NSArray* components = [contents componentsSeparatedByString:@"\n"];
if (!components) {
if (deleteOnFailure) {
unlink(path);
}
FIRCLSSDKLog("Unable to read file %s\n", path);
return nil;
}
NSMutableArray* array = [NSMutableArray array];
// loop through all the entries, and
for (NSString* component in components) {
NSData* data = [component dataUsingEncoding:NSUTF8StringEncoding];
id obj = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
if (!obj) {
continue;
}
if (transformer) {
obj = transformer(obj);
}
if (!obj) {
continue;
}
[array addObject:obj];
}
return array;
}
NSString* FIRCLSFileHexEncodeString(const char* string) {
size_t length = strlen(string);
char* encodedBuffer = malloc(length * 2 + 1);
if (!encodedBuffer) {
FIRCLSErrorLog(@"Unable to malloc in FIRCLSFileHexEncodeString");
return nil;
}
memset(encodedBuffer, 0, length * 2 + 1);
int bufferIndex = 0;
for (int i = 0; i < length; ++i) {
FIRCLSHexFromByte(string[i], &encodedBuffer[bufferIndex]);
bufferIndex += 2; // 1 char => 2 hex values at a time
}
NSString* stringObject = [NSString stringWithUTF8String:encodedBuffer];
free(encodedBuffer);
return stringObject;
}
NSString* FIRCLSFileHexDecodeString(const char* string) {
size_t length = strlen(string);
char* decodedBuffer = malloc(length); // too long, but safe
if (!decodedBuffer) {
FIRCLSErrorLog(@"Unable to malloc in FIRCLSFileHexDecodeString");
return nil;
}
memset(decodedBuffer, 0, length);
for (int i = 0; i < length / 2; ++i) {
size_t index = i * 2;
uint8_t hiNybble = FIRCLSNybbleFromChar(string[index]);
uint8_t lowNybble = FIRCLSNybbleFromChar(string[index + 1]);
if (hiNybble == FIRCLSInvalidCharNybble || lowNybble == FIRCLSInvalidCharNybble) {
// char is invalid, abort loop
break;
}
decodedBuffer[i] = (hiNybble << 4) | lowNybble;
}
NSString* strObject = [NSString stringWithUTF8String:decodedBuffer];
free(decodedBuffer);
return strObject;
}

View File

@ -0,0 +1,101 @@
// Copyright 2019 Google
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <dispatch/dispatch.h>
#include "Crashlytics/Crashlytics/Helpers/FIRCLSInternalLogging.h"
#include "Crashlytics/Crashlytics/Components/FIRCLSContext.h"
#include "Crashlytics/Crashlytics/Components/FIRCLSGlobals.h"
#include "Crashlytics/Crashlytics/Helpers/FIRCLSUtility.h"
void FIRCLSSDKFileLog(FIRCLSInternalLogLevel level, const char* format, ...) {
if (!_firclsContext.readonly || !_firclsContext.writable) {
return;
}
const char* path = _firclsContext.readonly->logPath;
if (!FIRCLSIsValidPointer(path)) {
return;
}
if (_firclsContext.writable->internalLogging.logLevel > level) {
return;
}
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
if (_firclsContext.writable->internalLogging.logFd == -1) {
_firclsContext.writable->internalLogging.logFd = open(path, O_WRONLY | O_CREAT | O_APPEND, 0644);
}
});
const int fd = _firclsContext.writable->internalLogging.logFd;
if (fd < 0) {
return;
}
va_list args;
va_start(args, format);
#if DEBUG && 0
// It's nice to use printf here, so all the formatting works. However, its possible to hit a
// deadlock if you call vfprintf in a crash handler. So, this code is handy to keep, just in case,
// if there's a really tough thing to debug.
FILE* file = fopen(path, "a+");
vfprintf(file, format, args);
fclose(file);
#else
size_t formatLength = strlen(format);
for (size_t idx = 0; idx < formatLength; ++idx) {
if (format[idx] != '%') {
write(fd, &format[idx], 1);
continue;
}
idx++; // move to the format char
switch (format[idx]) {
case 'd': {
int value = va_arg(args, int);
FIRCLSFileFDWriteInt64(fd, value);
} break;
case 'u': {
uint32_t value = va_arg(args, uint32_t);
FIRCLSFileFDWriteUInt64(fd, value, false);
} break;
case 'p': {
uintptr_t value = va_arg(args, uintptr_t);
write(fd, "0x", 2);
FIRCLSFileFDWriteUInt64(fd, value, true);
} break;
case 's': {
const char* string = va_arg(args, const char*);
if (!string) {
string = "(null)";
}
write(fd, string, strlen(string));
} break;
case 'x': {
unsigned int value = va_arg(args, unsigned int);
FIRCLSFileFDWriteUInt64(fd, value, true);
} break;
default:
// unhandled, back up to write out the percent + the format char
write(fd, &format[idx - 1], 2);
break;
}
}
#endif
va_end(args);
}

View File

@ -0,0 +1,57 @@
// Copyright 2019 Google
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#pragma once
#include <stdio.h>
#if __OBJC__
#import "Crashlytics/Crashlytics/Helpers/FIRCLSLogger.h"
#define FIRCLSDeveloperLog(label, __FORMAT__, ...) \
FIRCLSDebugLog(@"[" label "] " __FORMAT__, ##__VA_ARGS__);
#endif
typedef enum {
FIRCLSInternalLogLevelUnknown = 0,
FIRCLSInternalLogLevelDebug = 1,
FIRCLSInternalLogLevelInfo = 2,
FIRCLSInternalLogLevelWarn = 3,
FIRCLSInternalLogLevelError = 4
} FIRCLSInternalLogLevel;
typedef struct {
int logFd;
FIRCLSInternalLogLevel logLevel;
} FIRCLSInternalLoggingWritableContext;
#define FIRCLSSDKLogDebug(__FORMAT__, ...) \
FIRCLSSDKFileLog(FIRCLSInternalLogLevelDebug, "DEBUG [%s:%d] " __FORMAT__, __FUNCTION__, \
__LINE__, ##__VA_ARGS__)
#define FIRCLSSDKLogInfo(__FORMAT__, ...) \
FIRCLSSDKFileLog(FIRCLSInternalLogLevelInfo, "INFO [%s:%d] " __FORMAT__, __FUNCTION__, \
__LINE__, ##__VA_ARGS__)
#define FIRCLSSDKLogWarn(__FORMAT__, ...) \
FIRCLSSDKFileLog(FIRCLSInternalLogLevelWarn, "WARN [%s:%d] " __FORMAT__, __FUNCTION__, \
__LINE__, ##__VA_ARGS__)
#define FIRCLSSDKLogError(__FORMAT__, ...) \
FIRCLSSDKFileLog(FIRCLSInternalLogLevelError, "ERROR [%s:%d] " __FORMAT__, __FUNCTION__, \
__LINE__, ##__VA_ARGS__)
#define FIRCLSSDKLog FIRCLSSDKLogWarn
__BEGIN_DECLS
void FIRCLSSDKFileLog(FIRCLSInternalLogLevel level, const char* format, ...) __printflike(2, 3);
__END_DECLS

View File

@ -0,0 +1,24 @@
// Copyright 2019 Google
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import <Foundation/Foundation.h>
__BEGIN_DECLS
void FIRCLSDebugLog(NSString *message, ...);
void FIRCLSInfoLog(NSString *message, ...);
void FIRCLSWarningLog(NSString *message, ...);
void FIRCLSErrorLog(NSString *message, ...);
__END_DECLS

View File

@ -0,0 +1,52 @@
// Copyright 2019 Google
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import "Crashlytics/Crashlytics/Helpers/FIRCLSLogger.h"
#import "FirebaseCore/Extension/FirebaseCoreInternal.h"
FIRLoggerService kFIRLoggerCrashlytics = @"[FirebaseCrashlytics]";
NSString *const CrashlyticsMessageCode = @"I-CLS000000";
void FIRCLSDebugLog(NSString *message, ...) {
va_list args_ptr;
va_start(args_ptr, message);
FIRLogBasic(FIRLoggerLevelDebug, kFIRLoggerCrashlytics, CrashlyticsMessageCode, message,
args_ptr);
va_end(args_ptr);
}
void FIRCLSInfoLog(NSString *message, ...) {
va_list args_ptr;
va_start(args_ptr, message);
FIRLogBasic(FIRLoggerLevelInfo, kFIRLoggerCrashlytics, CrashlyticsMessageCode, message, args_ptr);
va_end(args_ptr);
}
void FIRCLSWarningLog(NSString *message, ...) {
va_list args_ptr;
va_start(args_ptr, message);
FIRLogBasic(FIRLoggerLevelWarning, kFIRLoggerCrashlytics, CrashlyticsMessageCode, message,
args_ptr);
va_end(args_ptr);
}
void FIRCLSErrorLog(NSString *message, ...) {
va_list args_ptr;
va_start(args_ptr, message);
FIRLogBasic(FIRLoggerLevelError, kFIRLoggerCrashlytics, CrashlyticsMessageCode, message,
args_ptr);
va_end(args_ptr);
}

View File

@ -0,0 +1,147 @@
// Copyright 2019 Google
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "Crashlytics/Crashlytics/Helpers/FIRCLSThreadState.h"
#include "Crashlytics/Crashlytics/Helpers/FIRCLSDefines.h"
#include "Crashlytics/Crashlytics/Helpers/FIRCLSUtility.h"
#if defined(__arm__) || defined(__arm64__)
#include <mach/arm/thread_status.h>
#include <ptrauth.h>
#endif
#if CLS_CPU_X86_64
#define GET_IP_REGISTER(r) (r->__ss.__rip)
#define GET_FP_REGISTER(r) (r->__ss.__rbp)
#define GET_SP_REGISTER(r) (r->__ss.__rsp)
#define GET_LR_REGISTER(r) 0
#define SET_IP_REGISTER(r, v) (r->__ss.__rip = v)
#define SET_FP_REGISTER(r, v) (r->__ss.__rbp = v)
#define SET_SP_REGISTER(r, v) (r->__ss.__rsp = v)
#define SET_LR_REGISTER(r, v)
#elif CLS_CPU_I386
#define GET_IP_REGISTER(r) (r->__ss.__eip)
#define GET_FP_REGISTER(r) (r->__ss.__ebp)
#define GET_SP_REGISTER(r) (r->__ss.__esp)
#define GET_LR_REGISTER(r) 0
#define SET_IP_REGISTER(r, v) (r->__ss.__eip = v)
#define SET_FP_REGISTER(r, v) (r->__ss.__ebp = v)
#define SET_SP_REGISTER(r, v) (r->__ss.__esp = v)
#define SET_LR_REGISTER(r, v)
#elif CLS_CPU_ARM64
// The arm_thread_state64_get_* macros translate down to the AUTIA and AUTIB instructions which
// authenticate the address, but don't clear the upper bits. From the docs:
// "If the authentication passes, the upper bits of the address are restored to enable
// subsequent use of the address. the authentication fails, the upper bits are corrupted and
// any subsequent use of the address results in a Translation fault."
// Since we only want the address (with the metadata in the upper bits masked out), we used the
// ptrauth_strip macro to clear the upper bits.
//
// We found later that ptrauth_strip doesn't seem to do anything. In many cases, the upper bits were
// already stripped, so for most non-system-library code, Crashlytics would still symbolicate. But
// for system libraries, the upper bits were being left in even when we called ptrauth_strip.
// Instead, we're bit masking and only allowing the latter 36 bits.
#define CLS_PTRAUTH_STRIP(pointer) ((uintptr_t)pointer & 0x0000000FFFFFFFFF)
#define GET_IP_REGISTER(r) (CLS_PTRAUTH_STRIP(arm_thread_state64_get_pc(r->__ss)))
#define GET_FP_REGISTER(r) (CLS_PTRAUTH_STRIP(arm_thread_state64_get_fp(r->__ss)))
#define GET_SP_REGISTER(r) (CLS_PTRAUTH_STRIP(arm_thread_state64_get_sp(r->__ss)))
#define GET_LR_REGISTER(r) (CLS_PTRAUTH_STRIP(arm_thread_state64_get_lr(r->__ss)))
#define SET_IP_REGISTER(r, v) arm_thread_state64_set_pc_fptr(r->__ss, (void*)v)
#define SET_FP_REGISTER(r, v) arm_thread_state64_set_fp(r->__ss, v)
#define SET_SP_REGISTER(r, v) arm_thread_state64_set_sp(r->__ss, v)
#define SET_LR_REGISTER(r, v) arm_thread_state64_set_lr_fptr(r->__ss, (void*)v)
#elif CLS_CPU_ARM
#define GET_IP_REGISTER(r) (r->__ss.__pc)
#define GET_FP_REGISTER(r) (r->__ss.__r[7])
#define GET_SP_REGISTER(r) (r->__ss.__sp)
#define GET_LR_REGISTER(r) (r->__ss.__lr)
#define SET_IP_REGISTER(r, v) (r->__ss.__pc = v)
#define SET_FP_REGISTER(r, v) (r->__ss.__r[7] = v)
#define SET_SP_REGISTER(r, v) (r->__ss.__sp = v)
#define SET_LR_REGISTER(r, v) (r->__ss.__lr = v)
#else
#error "Architecture Unsupported"
#endif
uintptr_t FIRCLSThreadContextGetPC(FIRCLSThreadContext* registers) {
if (!registers) {
return 0;
}
return GET_IP_REGISTER(registers);
}
uintptr_t FIRCLSThreadContextGetStackPointer(const FIRCLSThreadContext* registers) {
if (!registers) {
return 0;
}
return GET_SP_REGISTER(registers);
}
bool FIRCLSThreadContextSetStackPointer(FIRCLSThreadContext* registers, uintptr_t value) {
if (!FIRCLSIsValidPointer(registers)) {
return false;
}
SET_SP_REGISTER(registers, value);
return true;
}
uintptr_t FIRCLSThreadContextGetLinkRegister(const FIRCLSThreadContext* registers) {
if (!FIRCLSIsValidPointer(registers)) {
return 0;
}
return GET_LR_REGISTER(registers);
}
bool FIRCLSThreadContextSetLinkRegister(FIRCLSThreadContext* registers, uintptr_t value) {
if (!FIRCLSIsValidPointer(registers)) {
return false;
}
SET_LR_REGISTER(registers, value);
return true;
}
bool FIRCLSThreadContextSetPC(FIRCLSThreadContext* registers, uintptr_t value) {
if (!registers) {
return false;
}
SET_IP_REGISTER(registers, value);
return true;
}
uintptr_t FIRCLSThreadContextGetFramePointer(const FIRCLSThreadContext* registers) {
if (!FIRCLSIsValidPointer(registers)) {
return 0;
}
return GET_FP_REGISTER(registers);
}
bool FIRCLSThreadContextSetFramePointer(FIRCLSThreadContext* registers, uintptr_t value) {
if (!FIRCLSIsValidPointer(registers)) {
return false;
}
SET_FP_REGISTER(registers, value);
return true;
}

View File

@ -0,0 +1,57 @@
// Copyright 2019 Google
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#pragma once
#include <stdbool.h>
#include <sys/ucontext.h>
#if CLS_CPU_ARM
#define FIRCLSThreadStateCount ARM_THREAD_STATE_COUNT
#define FIRCLSThreadState ARM_THREAD_STATE
#elif CLS_CPU_ARM64
#define FIRCLSThreadStateCount ARM_THREAD_STATE64_COUNT
#define FIRCLSThreadState ARM_THREAD_STATE64
#elif CLS_CPU_I386
#define FIRCLSThreadStateCount x86_THREAD_STATE32_COUNT
#define FIRCLSThreadState x86_THREAD_STATE32
#elif CLS_CPU_X86_64
#define FIRCLSThreadStateCount x86_THREAD_STATE64_COUNT
#define FIRCLSThreadState x86_THREAD_STATE64
#endif
// _STRUCT_MCONTEXT was fixed to point to the right thing on ARM in the iOS 7.1 SDK
typedef _STRUCT_MCONTEXT FIRCLSThreadContext;
// I'm not entirely sure what happened when, but this appears to have disappeared from
// the SDKs...
#if !defined(_STRUCT_UCONTEXT64)
typedef _STRUCT_UCONTEXT _STRUCT_UCONTEXT64;
#endif
#pragma mark Register Access
uintptr_t FIRCLSThreadContextGetPC(FIRCLSThreadContext* registers);
uintptr_t FIRCLSThreadContextGetStackPointer(const FIRCLSThreadContext* registers);
uintptr_t FIRCLSThreadContextGetFramePointer(const FIRCLSThreadContext* registers);
bool FIRCLSThreadContextSetPC(FIRCLSThreadContext* registers, uintptr_t value);
bool FIRCLSThreadContextSetStackPointer(FIRCLSThreadContext* registers, uintptr_t value);
bool FIRCLSThreadContextSetFramePointer(FIRCLSThreadContext* registers, uintptr_t value);
// The link register only exists on ARM platforms.
#if CLS_CPU_ARM || CLS_CPU_ARM64
uintptr_t FIRCLSThreadContextGetLinkRegister(const FIRCLSThreadContext* registers);
bool FIRCLSThreadContextSetLinkRegister(FIRCLSThreadContext* registers, uintptr_t value);
#endif

View File

@ -0,0 +1,55 @@
// Copyright 2019 Google
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#pragma once
#include <mach/vm_types.h>
#include <stdbool.h>
#include <stdio.h>
#include "Crashlytics/Crashlytics/Components/FIRCLSGlobals.h"
#define FIRCLSIsValidPointer(x) ((uintptr_t)x >= 4096)
#define FIRCLSInvalidCharNybble (255)
__BEGIN_DECLS
void FIRCLSLookupFunctionPointer(void* ptr, void (^block)(const char* name, const char* lib));
void FIRCLSHexFromByte(uint8_t c, char output[]);
uint8_t FIRCLSNybbleFromChar(char c);
bool FIRCLSReadMemory(vm_address_t src, void* dest, size_t len);
bool FIRCLSReadString(vm_address_t src, char** dest, size_t maxlen);
const char* FIRCLSDupString(const char* string);
bool FIRCLSUnlinkIfExists(const char* path);
void FIRCLSRedactUUID(char* value);
#if __OBJC__
void FIRCLSDispatchAfter(float timeInSeconds, dispatch_queue_t queue, dispatch_block_t block);
NSString* FIRCLSNormalizeUUID(NSString* value);
NSString* FIRCLSGenerateNormalizedUUID(void);
NSString* FIRCLSNSDataToNSString(NSData* data);
void FIRCLSAddOperationAfter(float timeInSeconds, NSOperationQueue* queue, void (^block)(void));
#endif
#if DEBUG
void FIRCLSPrintAUUID(const uint8_t* value);
#endif
__END_DECLS

View File

@ -0,0 +1,222 @@
// Copyright 2019 Google
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "Crashlytics/Crashlytics/Helpers/FIRCLSUtility.h"
#include <mach/mach.h>
#include <dlfcn.h>
#include "Crashlytics/Crashlytics/Components/FIRCLSGlobals.h"
#include "Crashlytics/Crashlytics/Helpers/FIRCLSFeatures.h"
#include "Crashlytics/Crashlytics/Helpers/FIRCLSFile.h"
#import "Crashlytics/Shared/FIRCLSByteUtility.h"
#import "Crashlytics/Shared/FIRCLSUUID.h"
#import <CommonCrypto/CommonHMAC.h>
void FIRCLSLookupFunctionPointer(void* ptr, void (^block)(const char* name, const char* lib)) {
Dl_info info;
if (dladdr(ptr, &info) == 0) {
block(NULL, NULL);
return;
}
const char* name = "unknown";
const char* lib = "unknown";
if (info.dli_sname) {
name = info.dli_sname;
}
if (info.dli_fname) {
lib = info.dli_fname;
}
block(name, lib);
}
uint8_t FIRCLSNybbleFromChar(char c) {
if (c >= '0' && c <= '9') {
return c - '0';
}
if (c >= 'a' && c <= 'f') {
return c - 'a' + 10;
}
if (c >= 'A' && c <= 'F') {
return c - 'A' + 10;
}
return FIRCLSInvalidCharNybble;
}
bool FIRCLSReadMemory(vm_address_t src, void* dest, size_t len) {
if (!FIRCLSIsValidPointer(src)) {
return false;
}
vm_size_t readSize = len;
return vm_read_overwrite(mach_task_self(), src, len, (pointer_t)dest, &readSize) == KERN_SUCCESS;
}
bool FIRCLSReadString(vm_address_t src, char** dest, size_t maxlen) {
char c;
vm_address_t address;
if (!dest) {
return false;
}
// Walk the entire string. Not certain this is perfect...
for (address = src; address < src + maxlen; ++address) {
if (!FIRCLSReadMemory(address, &c, 1)) {
return false;
}
if (c == 0) {
break;
}
}
*dest = (char*)src;
return true;
}
const char* FIRCLSDupString(const char* string) {
#if CLS_MEMORY_PROTECTION_ENABLED
char* buffer;
size_t length;
if (!string) {
return NULL;
}
length = strlen(string);
buffer = FIRCLSAllocatorSafeAllocate(_firclsContext.allocator, length + 1, CLS_READONLY);
memcpy(buffer, string, length);
buffer[length] = 0; // null-terminate
return buffer;
#else
return strdup(string);
#endif
}
void FIRCLSDispatchAfter(float timeInSeconds, dispatch_queue_t queue, dispatch_block_t block) {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(timeInSeconds * NSEC_PER_SEC)), queue,
block);
}
bool FIRCLSUnlinkIfExists(const char* path) {
if (unlink(path) != 0) {
if (errno != ENOENT) {
return false;
}
}
return true;
}
NSString* FIRCLSNormalizeUUID(NSString* value) {
return [[value stringByReplacingOccurrencesOfString:@"-" withString:@""] lowercaseString];
}
NSString* FIRCLSGenerateNormalizedUUID(void) {
return FIRCLSNormalizeUUID(FIRCLSGenerateUUID());
}
// Redacts a UUID wrapped in parenthesis from a char* using strchr, which is async safe.
// Ex.
// "foo (bar) (45D62CC2-CFB5-4E33-AB61-B0684627F1B6) baz"
// becomes
// "foo (bar) (********-****-****-****-************) baz"
void FIRCLSRedactUUID(char* value) {
if (value == NULL) {
return;
}
char* openParen = value;
// find the index of the first paren
while ((openParen = strchr(openParen, '(')) != NULL) {
// find index of the matching close paren
const char* closeParen = strchr(openParen, ')');
if (closeParen == NULL) {
break;
}
// if the distance between them is 37, traverse the characters
// and replace anything that is not a '-' with '*'
if (closeParen - openParen == 37) {
for (int i = 1; i < 37; ++i) {
if (*(openParen + i) != '-') {
*(openParen + i) = '*';
}
}
break;
}
openParen++;
}
}
NSString* FIRCLSNSDataToNSString(NSData* data) {
NSString* string;
char* buffer;
size_t size;
NSUInteger length;
// we need 2 hex char for every byte of data, plus one more spot for a
// null terminator
length = [data length];
size = (length * 2) + 1;
buffer = malloc(sizeof(char) * size);
if (!buffer) {
FIRCLSErrorLog(@"Unable to malloc in FIRCLSNSDataToNSString");
return nil;
}
FIRCLSSafeHexToString([data bytes], length, buffer);
string = [NSString stringWithUTF8String:buffer];
free(buffer);
return string;
}
void FIRCLSAddOperationAfter(float timeInSeconds, NSOperationQueue* queue, void (^block)(void)) {
dispatch_queue_t afterQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
FIRCLSDispatchAfter(timeInSeconds, afterQueue, ^{
[queue addOperationWithBlock:block];
});
}
#if DEBUG
void FIRCLSPrintAUUID(const uint8_t* value) {
CFUUIDRef uuid = CFUUIDCreateFromUUIDBytes(kCFAllocatorDefault, *(CFUUIDBytes*)value);
NSString* string = CFBridgingRelease(CFUUIDCreateString(kCFAllocatorDefault, uuid));
CFRelease(uuid);
FIRCLSDebugLog(@"%@", [[string stringByReplacingOccurrencesOfString:@"-"
withString:@""] lowercaseString]);
}
#endif

View File

@ -0,0 +1,33 @@
// Copyright 2019 Google
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
/**
* This class is a model to identify a single execution of the app
*/
@interface FIRCLSExecutionIdentifierModel : NSObject
/**
* Returns the launch identifier. This is a unique id that will remain constant until this process
* is relaunched. This value is useful for correlating events across kits and/or across reports at
* the process-lifecycle level.
*/
@property(nonatomic, readonly) NSString *executionID;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,33 @@
// Copyright 2019 Google
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import "Crashlytics/Crashlytics/Models/FIRCLSExecutionIdentifierModel.h"
#import "Crashlytics/Shared/FIRCLSUUID.h"
@implementation FIRCLSExecutionIdentifierModel
- (instancetype)init {
self = [super init];
if (!self) {
return nil;
}
_executionID = [[FIRCLSGenerateUUID() stringByReplacingOccurrencesOfString:@"-"
withString:@""] lowercaseString];
return self;
}
@end

View File

@ -0,0 +1,75 @@
// Copyright 2019 Google
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import <Foundation/Foundation.h>
@class FIRCLSInternalReport;
@interface FIRCLSFileManager : NSObject
- (instancetype)init NS_DESIGNATED_INITIALIZER;
@property(nonatomic, readonly) NSFileManager *underlyingFileManager;
/**
* Returns the folder containing the settings file
*/
@property(nonatomic, readonly) NSString *settingsDirectoryPath;
/**
* Returns the path to the settings file
*/
@property(nonatomic, readonly) NSString *settingsFilePath;
/**
* Path to the file that holds the ttl and keys that invalidate settings
*/
@property(nonatomic, readonly) NSString *settingsCacheKeyPath;
@property(nonatomic, readonly) NSString *rootPath;
@property(nonatomic, readonly) NSString *cachesPath;
@property(nonatomic, readonly) NSString *structurePath;
@property(nonatomic, readonly) NSString *activePath;
@property(nonatomic, readonly) NSString *processingPath;
@property(nonatomic, readonly) NSString *pendingPath;
@property(nonatomic, readonly) NSString *preparedPath;
@property(nonatomic, readonly) NSArray *activePathContents;
@property(nonatomic, readonly) NSArray *preparedPathContents;
@property(nonatomic, readonly) NSArray *processingPathContents;
- (BOOL)fileExistsAtPath:(NSString *)path;
- (BOOL)createFileAtPath:(NSString *)path
contents:(NSData *)data
attributes:(NSDictionary<NSFileAttributeKey, id> *)attr;
- (BOOL)createDirectoryAtPath:(NSString *)path;
- (BOOL)removeItemAtPath:(NSString *)path;
- (BOOL)removeContentsOfDirectoryAtPath:(NSString *)path;
- (BOOL)moveItemAtPath:(NSString *)path toDirectory:(NSString *)destDir;
- (BOOL)didCrashOnPreviousExecution;
- (BOOL)metricKitDiagnosticFileExists;
- (void)createEmptyMetricKitFile:(NSString *)reportPath;
- (void)enumerateFilesInDirectory:(NSString *)directory
usingBlock:(void (^)(NSString *filePath, NSString *extension))block;
- (NSNumber *)fileSizeAtPath:(NSString *)path;
- (NSArray *)contentsOfDirectory:(NSString *)path;
// logic of managing files/directories
- (BOOL)createReportDirectories;
- (NSString *)setupNewPathForExecutionIdentifier:(NSString *)identifier;
- (BOOL)moveItemAtPath:(NSString *)srcPath toPath:(NSString *)dstPath error:(NSError **)error;
- (NSData *)dataWithContentsOfFile:(NSString *)path;
@end

View File

@ -0,0 +1,296 @@
// Copyright 2019 Google
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import "Crashlytics/Crashlytics/Models/FIRCLSFileManager.h"
#import "Crashlytics/Crashlytics/Components/FIRCLSApplication.h"
#import "Crashlytics/Crashlytics/Components/FIRCLSCrashedMarkerFile.h"
#import "Crashlytics/Crashlytics/Helpers/FIRCLSLogger.h"
#import "Crashlytics/Crashlytics/Models/FIRCLSInternalReport.h"
NSString *const FIRCLSCacheDirectoryName = @"com.crashlytics.data";
NSString *const FIRCLSCacheVersion = @"v5";
NSString *const FIRCLSMetricKitDiagnosticPath = @"/MetricKit/Diagnostics/";
@interface FIRCLSFileManager () {
NSString *_rootPath;
NSString *_cachesPath;
}
@property(nonatomic) BOOL crashFileMarkerExists;
@end
@implementation FIRCLSFileManager
- (instancetype)init {
self = [super init];
if (!self) {
return nil;
}
_underlyingFileManager = [NSFileManager defaultManager];
NSString *path =
[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
_cachesPath = [path copy];
path = [path stringByAppendingPathComponent:FIRCLSCacheDirectoryName];
path = [path stringByAppendingPathComponent:[self pathNamespace]];
_rootPath = [path copy];
_crashFileMarkerExists = NO;
return self;
}
#pragma mark - Core API
- (BOOL)fileExistsAtPath:(NSString *)path {
return [_underlyingFileManager fileExistsAtPath:path];
}
- (BOOL)createFileAtPath:(NSString *)path
contents:(nullable NSData *)data
attributes:(nullable NSDictionary<NSFileAttributeKey, id> *)attr {
return [_underlyingFileManager createFileAtPath:path contents:data attributes:attr];
}
- (BOOL)createDirectoryAtPath:(NSString *)path {
NSDictionary *attributes;
NSError *error;
attributes = @{NSFilePosixPermissions : [NSNumber numberWithShort:0755]};
error = nil;
if (![[self underlyingFileManager] createDirectoryAtPath:path
withIntermediateDirectories:YES
attributes:attributes
error:&error]) {
FIRCLSErrorLog(@"Unable to create directory %@", error);
return NO;
}
return YES;
}
- (BOOL)removeItemAtPath:(NSString *)path {
NSError *error;
error = nil;
if (![[self underlyingFileManager] removeItemAtPath:path error:&error] || !path) {
FIRCLSErrorLog(@"Failed to remove file %@: %@", path, error);
return NO;
}
return YES;
}
- (BOOL)removeContentsOfDirectoryAtPath:(NSString *)path {
__block BOOL success = YES;
// only return true if we were able to remove every item in the directory (or it was empty)
[self enumerateFilesInDirectory:path
usingBlock:^(NSString *filePath, NSString *extension) {
success = [self removeItemAtPath:filePath] && success;
}];
return success;
}
- (BOOL)moveItemAtPath:(NSString *)path toDirectory:(NSString *)destDir {
NSString *destPath;
NSError *error;
destPath = [destDir stringByAppendingPathComponent:[path lastPathComponent]];
error = nil;
if (!path || !destPath) {
FIRCLSErrorLog(@"Failed to move file, inputs invalid");
return NO;
}
if (![[self underlyingFileManager] moveItemAtPath:path toPath:destPath error:&error]) {
FIRCLSErrorLog(@"Failed to move file: %@", error);
return NO;
}
return YES;
}
- (BOOL)didCrashOnPreviousExecution {
static dispatch_once_t checkCrashFileMarketExistsOnceToken;
dispatch_once(&checkCrashFileMarketExistsOnceToken, ^{
NSString *crashedMarkerFileName = [NSString stringWithUTF8String:FIRCLSCrashedMarkerFileName];
NSString *crashedMarkerFileFullPath =
[[self rootPath] stringByAppendingPathComponent:crashedMarkerFileName];
self.crashFileMarkerExists = [self fileExistsAtPath:crashedMarkerFileFullPath];
});
return self.crashFileMarkerExists;
}
- (BOOL)metricKitDiagnosticFileExists {
NSArray *contentsOfMetricKitDirectory = [self
contentsOfDirectory:[_cachesPath stringByAppendingString:FIRCLSMetricKitDiagnosticPath]];
return ([contentsOfMetricKitDirectory count] > 0);
}
- (void)createEmptyMetricKitFile:(NSString *)reportPath {
NSString *metricKitFile =
[reportPath stringByAppendingPathComponent:FIRCLSMetricKitFatalReportFile];
[self createFileAtPath:metricKitFile contents:nil attributes:nil];
}
- (void)enumerateFilesInDirectory:(NSString *)directory
usingBlock:(void (^)(NSString *filePath, NSString *extension))block {
for (NSString *path in [[self underlyingFileManager] contentsOfDirectoryAtPath:directory
error:nil]) {
NSString *extension;
NSString *fullPath;
// Skip files that start with a dot. This is important, because if you try to move a .DS_Store
// file, it will fail if the target directory also has a .DS_Store file in it. Plus, its
// wasteful, because we don't care about dot files.
if ([path hasPrefix:@"."]) {
continue;
}
extension = [path pathExtension];
fullPath = [directory stringByAppendingPathComponent:path];
if (block) {
block(fullPath, extension);
}
}
}
- (NSNumber *)fileSizeAtPath:(NSString *)path {
NSError *error = nil;
NSDictionary *attrs = [[self underlyingFileManager] attributesOfItemAtPath:path error:&error];
if (!attrs) {
FIRCLSErrorLog(@"Unable to read file size: %@", error);
return nil;
}
return [attrs objectForKey:NSFileSize];
}
- (NSArray *)contentsOfDirectory:(NSString *)path {
NSMutableArray *array = [NSMutableArray array];
[self enumerateFilesInDirectory:path
usingBlock:^(NSString *filePath, NSString *extension) {
[array addObject:filePath];
}];
return [array copy];
}
#pragma - Properties
- (NSString *)pathNamespace {
return FIRCLSApplicationGetBundleIdentifier();
}
- (NSString *)versionedPath {
return [[self rootPath] stringByAppendingPathComponent:FIRCLSCacheVersion];
}
#pragma - Settings Paths
// This path should be different than the structurePath because the
// settings download operations will delete the settings directory,
// which would delete crash reports if these were the same
- (NSString *)settingsDirectoryPath {
return [[self versionedPath] stringByAppendingPathComponent:@"settings"];
}
- (NSString *)settingsFilePath {
return [[self settingsDirectoryPath] stringByAppendingPathComponent:@"settings.json"];
}
- (NSString *)settingsCacheKeyPath {
return [[self settingsDirectoryPath] stringByAppendingPathComponent:@"cache-key.json"];
}
#pragma - Report Paths
- (NSString *)structurePath {
return [[self versionedPath] stringByAppendingPathComponent:@"reports"];
}
- (NSString *)activePath {
return [[self structurePath] stringByAppendingPathComponent:@"active"];
}
- (NSString *)pendingPath {
return [[self structurePath] stringByAppendingPathComponent:@"pending"];
}
- (NSString *)processingPath {
return [[self structurePath] stringByAppendingPathComponent:@"processing"];
}
- (NSString *)preparedPath {
return [[self structurePath] stringByAppendingPathComponent:@"prepared"];
}
- (NSArray *)activePathContents {
return [self contentsOfDirectory:[self activePath]];
}
- (NSArray *)preparedPathContents {
return [self contentsOfDirectory:[self preparedPath]];
}
- (NSArray *)processingPathContents {
return [self contentsOfDirectory:[self processingPath]];
}
#pragma mark - Logic
- (BOOL)createReportDirectories {
if (![self createDirectoryAtPath:[self activePath]]) {
return NO;
}
if (![self createDirectoryAtPath:[self processingPath]]) {
return NO;
}
if (![self createDirectoryAtPath:[self preparedPath]]) {
return NO;
}
return YES;
}
- (NSString *)setupNewPathForExecutionIdentifier:(NSString *)identifier {
NSString *path = [[self activePath] stringByAppendingPathComponent:identifier];
if (![self createDirectoryAtPath:path]) {
return nil;
}
return path;
}
- (BOOL)moveItemAtPath:(NSString *)srcPath toPath:(NSString *)dstPath error:(NSError **)error {
return [self.underlyingFileManager moveItemAtPath:srcPath toPath:dstPath error:error];
}
// Wrapper over NSData so the method can be mocked for unit tests
- (NSData *)dataWithContentsOfFile:(NSString *)path {
return [NSData dataWithContentsOfFile:path];
}
@end

View File

@ -0,0 +1,51 @@
// Copyright 2019 Google
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import <Foundation/Foundation.h>
@class FIRInstallations;
NS_ASSUME_NONNULL_BEGIN
/**
* This class is a model for identifying an installation of an app
*/
@interface FIRCLSInstallIdentifierModel : NSObject
- (instancetype)init NS_UNAVAILABLE;
- (instancetype)initWithInstallations:(FIRInstallations *)instanceID NS_DESIGNATED_INITIALIZER;
/**
* Returns the backwards compatible Crashlytics Installation UUID
*/
@property(nonatomic, readonly) NSString *installID;
/**
* To support end-users rotating Install IDs, this will check and rotate the Install ID,
* which can be a slow operation. This should be run in an Activity or
* background thread.
*
* This method has 2 concerns:
* - Concern 1: We have the old Crashlytics Install ID that needs to regenerate when the FIID
* changes. If we get a null FIID, we don't want to rotate because we don't know if it changed or
* not.
* - Concern 2: Whatever the FIID is, we should send it with the Crash report so we're in sync with
* Sessions and other Firebase SDKs
*/
- (BOOL)regenerateInstallIDIfNeededWithBlock:(void (^)(NSString *fiid, NSString *authToken))block;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,189 @@
// Copyright 2019 Google
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import "Crashlytics/Crashlytics/Models/FIRCLSInstallIdentifierModel.h"
#import "FirebaseInstallations/Source/Library/Private/FirebaseInstallationsInternal.h"
#import "Crashlytics/Crashlytics/FIRCLSUserDefaults/FIRCLSUserDefaults.h"
#import "Crashlytics/Crashlytics/Helpers/FIRCLSLogger.h"
#import "Crashlytics/Shared/FIRCLSByteUtility.h"
#import "Crashlytics/Shared/FIRCLSUUID.h"
static NSString *const FIRCLSInstallationUUIDKey = @"com.crashlytics.iuuid";
static NSString *const FIRCLSInstallationIIDHashKey = @"com.crashlytics.install.iid";
// Legacy key that is automatically removed
static NSString *const FIRCLSInstallationADIDKey = @"com.crashlytics.install.adid";
static unsigned long long FIRCLSInstallationsWaitTime = 10 * NSEC_PER_SEC;
@interface FIRCLSInstallIdentifierModel ()
@property(nonatomic, copy) NSString *installID;
@property(nonatomic, readonly) FIRInstallations *installations;
@end
@implementation FIRCLSInstallIdentifierModel
// This needs to be synthesized so we can set without using the setter in the constructor and
// overridden setters and getters
@synthesize installID = _installID;
- (instancetype)initWithInstallations:(FIRInstallations *)installations {
self = [super init];
if (!self) {
return nil;
}
// capture the install ID information
_installID = [self readInstallationUUID].copy;
_installations = installations;
if (!_installID) {
FIRCLSDebugLog(@"Generating Install ID");
_installID = [self generateInstallationUUID].copy;
FIRCLSUserDefaults *defaults = [FIRCLSUserDefaults standardUserDefaults];
[defaults synchronize];
}
return self;
}
- (NSString *)installID {
@synchronized(self) {
return _installID;
}
}
- (void)setInstallID:(NSString *)installID {
@synchronized(self) {
_installID = installID;
}
}
/**
* Reads installation UUID stored in persistent storage.
* If the installation UUID is stored in legacy key, migrates it over to the new key.
*/
- (NSString *)readInstallationUUID {
return [[FIRCLSUserDefaults standardUserDefaults] objectForKey:FIRCLSInstallationUUIDKey];
}
/**
* Generates a new UUID and saves it in persistent storage.
* Does not synchronize the user defaults (to allow optimized
* batching of user default synchronizing)
*/
- (NSString *)generateInstallationUUID {
NSString *UUID = FIRCLSGenerateUUID();
FIRCLSUserDefaults *userDefaults = [FIRCLSUserDefaults standardUserDefaults];
[userDefaults setObject:UUID forKey:FIRCLSInstallationUUIDKey];
return UUID;
}
#pragma mark Privacy Shield
- (BOOL)regenerateInstallIDIfNeededWithBlock:(void (^)(NSString *fiid, NSString *authToken))block {
BOOL __block didRotate = false;
NSString __block *authTokenComplete = @"";
NSString __block *currentIIDComplete = @"";
// Installations Completions run async, so wait a reasonable amount of time for it to finish.
dispatch_group_t workingGroup = dispatch_group_create();
dispatch_group_enter(workingGroup);
[self.installations
authTokenWithCompletion:^(FIRInstallationsAuthTokenResult *_Nullable tokenResult,
NSError *_Nullable error) {
authTokenComplete = tokenResult.authToken;
dispatch_group_leave(workingGroup);
}];
dispatch_group_enter(workingGroup);
[self.installations
installationIDWithCompletion:^(NSString *_Nullable currentIID, NSError *_Nullable error) {
currentIIDComplete = currentIID;
didRotate = [self rotateCrashlyticsInstallUUIDWithIID:currentIID error:error];
if (didRotate) {
FIRCLSInfoLog(@"Rotated Crashlytics Install UUID because Firebase Install ID changed");
}
dispatch_group_leave(workingGroup);
}];
intptr_t result = dispatch_group_wait(
workingGroup, dispatch_time(DISPATCH_TIME_NOW, FIRCLSInstallationsWaitTime));
if (result != 0) {
FIRCLSErrorLog(@"Crashlytics timed out while checking for Firebase Installation ID");
}
// Provide the IID to the callback. For this case we don't care
// if the FIID is null because it's the best we can do - we just want
// to send up the same FIID that is sent by other SDKs (eg. the Sessions SDK).
block(currentIIDComplete, authTokenComplete);
return didRotate;
}
- (BOOL)rotateCrashlyticsInstallUUIDWithIID:(NSString *_Nullable)currentIID
error:(NSError *_Nullable)error {
BOOL didRotate = NO;
FIRCLSUserDefaults *defaults = [FIRCLSUserDefaults standardUserDefaults];
// Remove the legacy ID
NSString *adID = [defaults objectForKey:FIRCLSInstallationADIDKey];
if (adID.length != 0) {
[defaults removeObjectForKey:FIRCLSInstallationADIDKey];
[defaults synchronize];
}
if (error != nil) {
FIRCLSErrorLog(@"Failed to get Firebase Instance ID: %@", error);
return didRotate;
}
if (currentIID.length == 0) {
FIRCLSErrorLog(@"Firebase Instance ID was empty when checked for changes");
return didRotate;
}
NSString *currentIIDHash =
FIRCLS256HashNSData([currentIID dataUsingEncoding:NSUTF8StringEncoding]);
NSString *lastIIDHash = [defaults objectForKey:FIRCLSInstallationIIDHashKey];
// If the IDs are the same, we never regenerate
if ([lastIIDHash isEqualToString:currentIIDHash]) {
return didRotate;
}
// If we had an FIID saved, we know it's not an upgrade scenario, so we can regenerate
if (lastIIDHash.length != 0) {
FIRCLSDebugLog(@"Regenerating Install ID");
self.installID = [self generateInstallationUUID].copy;
didRotate = YES;
}
// Write the new FIID to UserDefaults
[defaults setObject:currentIIDHash forKey:FIRCLSInstallationIIDHashKey];
[defaults synchronize];
return didRotate;
}
@end

View File

@ -0,0 +1,121 @@
// Copyright 2019 Google
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import <Foundation/Foundation.h>
#include "Crashlytics/Crashlytics/Helpers/FIRCLSFeatures.h"
extern NSString *const FIRCLSCustomFatalIndicatorFile;
extern NSString *const FIRCLSReportBinaryImageFile;
extern NSString *const FIRCLSReportExceptionFile;
extern NSString *const FIRCLSReportCustomExceptionAFile;
extern NSString *const FIRCLSReportCustomExceptionBFile;
extern NSString *const FIRCLSReportSignalFile;
extern NSString *const FIRCLSMetricKitFatalReportFile;
extern NSString *const FIRCLSMetricKitNonfatalReportFile;
#if CLS_MACH_EXCEPTION_SUPPORTED
extern NSString *const FIRCLSReportMachExceptionFile;
#endif
extern NSString *const FIRCLSReportErrorAFile;
extern NSString *const FIRCLSReportErrorBFile;
extern NSString *const FIRCLSReportLogAFile;
extern NSString *const FIRCLSReportLogBFile;
extern NSString *const FIRCLSReportMetadataFile;
extern NSString *const FIRCLSReportInternalIncrementalKVFile;
extern NSString *const FIRCLSReportInternalCompactedKVFile;
extern NSString *const FIRCLSReportUserIncrementalKVFile;
extern NSString *const FIRCLSReportUserCompactedKVFile;
extern NSString *const FIRCLSReportRolloutsFile;
@class FIRCLSFileManager;
@interface FIRCLSInternalReport : NSObject
+ (instancetype)reportWithPath:(NSString *)path;
- (instancetype)initWithPath:(NSString *)path
executionIdentifier:(NSString *)identifier NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithPath:(NSString *)path;
- (instancetype)init NS_UNAVAILABLE;
+ (instancetype)new NS_UNAVAILABLE;
+ (NSArray *)crashFileNames;
@property(nonatomic, copy, readonly) NSString *directoryName;
@property(nonatomic, copy) NSString *path;
@property(nonatomic, assign, readonly) BOOL hasAnyEvents;
// content paths
@property(nonatomic, copy, readonly) NSString *binaryImagePath;
@property(nonatomic, copy, readonly) NSString *metadataPath;
- (void)enumerateSymbolicatableFilesInContent:(void (^)(NSString *path))block;
- (NSString *)pathForContentFile:(NSString *)name;
// Metadata Helpers
/**
* Returns the org id for the report.
**/
@property(nonatomic, copy, readonly) NSString *orgID;
/**
* Returns the Install UUID for the report.
**/
@property(nonatomic, copy, readonly) NSString *installID;
/**
* Returns true if report contains a signal, mach exception or unhandled exception record, false
* otherwise.
**/
@property(nonatomic, assign, readonly) BOOL isCrash;
/**
* Returns the session identifier for the report.
**/
@property(nonatomic, copy, readonly) NSString *identifier;
/**
* Returns the custom key value data for the report.
**/
@property(nonatomic, copy, readonly) NSDictionary *customKeys;
/**
* Returns the CFBundleVersion of the application that generated the report.
**/
@property(nonatomic, copy, readonly) NSString *bundleVersion;
/**
* Returns the CFBundleShortVersionString of the application that generated the report.
**/
@property(nonatomic, copy, readonly) NSString *bundleShortVersionString;
/**
* Returns the date that the report was created.
**/
@property(nonatomic, copy, readonly) NSDate *dateCreated;
@property(nonatomic, copy, readonly) NSDate *crashedOnDate;
/**
* Returns the os version that the application crashed on.
**/
@property(nonatomic, copy, readonly) NSString *OSVersion;
/**
* Returns the os build version that the application crashed on.
**/
@property(nonatomic, copy, readonly) NSString *OSBuildVersion;
@end

View File

@ -0,0 +1,258 @@
// Copyright 2019 Google
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// TODO: Remove this class after the uploading of reports via GoogleDataTransport is no longer an
// experiment
#import "Crashlytics/Crashlytics/Models/FIRCLSInternalReport.h"
#import "Crashlytics/Crashlytics/Helpers/FIRCLSFile.h"
#import "Crashlytics/Crashlytics/Helpers/FIRCLSLogger.h"
#import "Crashlytics/Crashlytics/Models/FIRCLSFileManager.h"
NSString *const FIRCLSCustomFatalIndicatorFile = @"custom_fatal.clsrecord";
NSString *const FIRCLSReportBinaryImageFile = @"binary_images.clsrecord";
NSString *const FIRCLSReportExceptionFile = @"exception.clsrecord";
NSString *const FIRCLSReportCustomExceptionAFile = @"custom_exception_a.clsrecord";
NSString *const FIRCLSReportCustomExceptionBFile = @"custom_exception_b.clsrecord";
NSString *const FIRCLSReportSignalFile = @"signal.clsrecord";
NSString *const FIRCLSMetricKitFatalReportFile = @"metric_kit_fatal.clsrecord";
NSString *const FIRCLSMetricKitNonfatalReportFile = @"metric_kit_nonfatal.clsrecord";
#if CLS_MACH_EXCEPTION_SUPPORTED
NSString *const FIRCLSReportMachExceptionFile = @"mach_exception.clsrecord";
#endif
NSString *const FIRCLSReportMetadataFile = @"metadata.clsrecord";
NSString *const FIRCLSReportErrorAFile = @"errors_a.clsrecord";
NSString *const FIRCLSReportErrorBFile = @"errors_b.clsrecord";
NSString *const FIRCLSReportLogAFile = @"log_a.clsrecord";
NSString *const FIRCLSReportLogBFile = @"log_b.clsrecord";
NSString *const FIRCLSReportInternalIncrementalKVFile = @"internal_incremental_kv.clsrecord";
NSString *const FIRCLSReportInternalCompactedKVFile = @"internal_compacted_kv.clsrecord";
NSString *const FIRCLSReportUserIncrementalKVFile = @"user_incremental_kv.clsrecord";
NSString *const FIRCLSReportUserCompactedKVFile = @"user_compacted_kv.clsrecord";
NSString *const FIRCLSReportRolloutsFile = @"rollouts.clsrecord";
@interface FIRCLSInternalReport () {
NSString *_identifier;
NSString *_path;
NSArray *_metadataSections;
}
@end
@implementation FIRCLSInternalReport
+ (instancetype)reportWithPath:(NSString *)path {
return [[self alloc] initWithPath:path];
}
#pragma mark - Initialization
/**
* Initializes a new report, i.e. one without metadata on the file system yet.
*/
- (instancetype)initWithPath:(NSString *)path executionIdentifier:(NSString *)identifier {
self = [super init];
if (!self) {
return self;
}
if (!path || !identifier) {
return nil;
}
[self setPath:path];
_identifier = [identifier copy];
return self;
}
/**
* Initializes a pre-existing report, i.e. one with metadata on the file system.
*/
- (instancetype)initWithPath:(NSString *)path {
NSString *metadataPath = [path stringByAppendingPathComponent:FIRCLSReportMetadataFile];
NSString *identifier = [[[[self.class readFIRCLSFileAtPath:metadataPath] objectAtIndex:0]
objectForKey:@"identity"] objectForKey:@"session_id"];
if (!identifier) {
FIRCLSErrorLog(@"Unable to read identifier at path %@", path);
}
return [self initWithPath:path executionIdentifier:identifier];
}
#pragma mark - Path Helpers
- (NSString *)directoryName {
return self.path.lastPathComponent;
}
- (NSString *)pathForContentFile:(NSString *)name {
return [[self path] stringByAppendingPathComponent:name];
}
- (NSString *)metadataPath {
return [[self path] stringByAppendingPathComponent:FIRCLSReportMetadataFile];
}
- (NSString *)binaryImagePath {
return [self pathForContentFile:FIRCLSReportBinaryImageFile];
}
#pragma mark - Processing Methods
- (BOOL)hasAnyEvents {
NSArray *reportFiles = @[
FIRCLSReportExceptionFile, FIRCLSReportSignalFile, FIRCLSReportCustomExceptionAFile,
FIRCLSReportCustomExceptionBFile, FIRCLSMetricKitFatalReportFile,
FIRCLSMetricKitNonfatalReportFile,
#if CLS_MACH_EXCEPTION_SUPPORTED
FIRCLSReportMachExceptionFile,
#endif
FIRCLSReportErrorAFile, FIRCLSReportErrorBFile
];
return [self checkExistenceOfAtLeastOneFileInArray:reportFiles];
}
// These are purposefully in order of precedence. If duplicate data exists
// in any crash file, the exception file's contents take precedence over the
// rest, for example
//
// Do not change the order of this.
//
+ (NSArray *)crashFileNames {
static NSArray *files;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
files = @[
FIRCLSReportExceptionFile,
#if CLS_MACH_EXCEPTION_SUPPORTED
FIRCLSReportMachExceptionFile,
#endif
FIRCLSReportSignalFile, FIRCLSMetricKitFatalReportFile, FIRCLSCustomFatalIndicatorFile
];
});
return files;
}
- (BOOL)isCrash {
NSArray *crashFiles = [FIRCLSInternalReport crashFileNames];
return [self checkExistenceOfAtLeastOneFileInArray:crashFiles];
}
- (BOOL)checkExistenceOfAtLeastOneFileInArray:(NSArray *)files {
NSFileManager *manager = [NSFileManager defaultManager];
for (NSString *fileName in files) {
NSString *path = [self pathForContentFile:fileName];
if ([manager fileExistsAtPath:path]) {
return YES;
}
}
return NO;
}
- (void)enumerateSymbolicatableFilesInContent:(void (^)(NSString *path))block {
for (NSString *fileName in [FIRCLSInternalReport crashFileNames]) {
NSString *path = [self pathForContentFile:fileName];
block(path);
}
}
#pragma mark - Metadata helpers
+ (NSArray *)readFIRCLSFileAtPath:(NSString *)path {
NSArray *sections = FIRCLSFileReadSections([path fileSystemRepresentation], false, nil);
if ([sections count] == 0) {
return nil;
}
return sections;
}
- (NSArray *)metadataSections {
if (!_metadataSections) {
_metadataSections = [self.class readFIRCLSFileAtPath:self.metadataPath];
}
return _metadataSections;
}
- (NSString *)orgID {
return
[[[self.metadataSections objectAtIndex:0] objectForKey:@"identity"] objectForKey:@"org_id"];
}
- (NSDictionary *)customKeys {
return nil;
}
- (NSString *)bundleVersion {
return [[[self.metadataSections objectAtIndex:2] objectForKey:@"application"]
objectForKey:@"build_version"];
}
- (NSString *)bundleShortVersionString {
return [[[self.metadataSections objectAtIndex:2] objectForKey:@"application"]
objectForKey:@"display_version"];
}
- (NSDate *)dateCreated {
NSUInteger unixtime = [[[[self.metadataSections objectAtIndex:0] objectForKey:@"identity"]
objectForKey:@"started_at"] unsignedIntegerValue];
return [NSDate dateWithTimeIntervalSince1970:unixtime];
}
- (NSDate *)crashedOnDate {
if (!self.isCrash) {
return nil;
}
#if CLS_MACH_EXCEPTION_SUPPORTED
// try the mach exception first, because it is more common
NSDate *date = [self timeFromCrashContentFile:FIRCLSReportMachExceptionFile
sectionName:@"mach_exception"];
if (date) {
return date;
}
#endif
return [self timeFromCrashContentFile:FIRCLSReportSignalFile sectionName:@"signal"];
}
- (NSDate *)timeFromCrashContentFile:(NSString *)fileName sectionName:(NSString *)sectionName {
// This works because both signal and mach exception files have the same structure to extract
// the "time" component
NSString *path = [self pathForContentFile:fileName];
NSNumber *timeValue = [[[[self.class readFIRCLSFileAtPath:path] objectAtIndex:0]
objectForKey:sectionName] objectForKey:@"time"];
if (timeValue == nil) {
return nil;
}
return [NSDate dateWithTimeIntervalSince1970:[timeValue unsignedIntegerValue]];
}
- (NSString *)OSVersion {
return [[[self.metadataSections objectAtIndex:1] objectForKey:@"host"]
objectForKey:@"os_display_version"];
}
- (NSString *)OSBuildVersion {
return [[[self.metadataSections objectAtIndex:1] objectForKey:@"host"]
objectForKey:@"os_build_version"];
}
@end

View File

@ -0,0 +1,40 @@
// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import <Foundation/Foundation.h>
#import "Crashlytics/Crashlytics/Models/FIRCLSFileManager.h"
NS_ASSUME_NONNULL_BEGIN
/*
* Writes a file during startup, and deletes it at the end. Existence
* of this file on the next run means there was a crash at launch,
* because the file wasn't deleted. This is used to make Crashlytics
* block startup on uploading the crash.
*/
@interface FIRCLSLaunchMarkerModel : NSObject
- (instancetype)initWithFileManager:(FIRCLSFileManager *)fileManager;
- (instancetype)init NS_UNAVAILABLE;
+ (instancetype)new NS_UNAVAILABLE;
- (BOOL)checkForAndCreateLaunchMarker;
- (BOOL)createLaunchFailureMarker;
- (BOOL)removeLaunchFailureMarker;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,84 @@
// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import "Crashlytics/Crashlytics/Models/FIRCLSLaunchMarkerModel.h"
#import "Crashlytics/Crashlytics/Helpers/FIRCLSInternalLogging.h"
@interface FIRCLSLaunchMarkerModel ()
@property(nonatomic, strong) FIRCLSFileManager *fileManager;
@end
@implementation FIRCLSLaunchMarkerModel
- (instancetype)initWithFileManager:(FIRCLSFileManager *)fileManager {
self = [super init];
if (!self) {
return nil;
}
_fileManager = fileManager;
return self;
}
- (BOOL)checkForAndCreateLaunchMarker {
BOOL launchFailure = [self launchFailureMarkerPresent];
if (launchFailure) {
FIRCLSDeveloperLog("Crashlytics:Crash",
@"Last launch failed: this may indicate a crash shortly after app launch.");
} else {
[self createLaunchFailureMarker];
}
return launchFailure;
}
- (NSString *)launchFailureMarkerPath {
return [[_fileManager structurePath] stringByAppendingPathComponent:@"launchmarker"];
}
- (BOOL)createLaunchFailureMarker {
// It's tempting to use - [NSFileManger createFileAtPath:contents:attributes:] here. But that
// operation, even with empty/nil contents does a ton of work to write out nothing via a
// temporarly file. This is a much faster implementation.
const char *path = [[self launchFailureMarkerPath] fileSystemRepresentation];
#if TARGET_OS_IPHONE
/*
* data-protected non-portable open(2) :
* int open_dprotected_np(user_addr_t path, int flags, int class, int dpflags, int mode)
*/
int fd = open_dprotected_np(path, O_WRONLY | O_CREAT | O_TRUNC, 4, 0, 0644);
#else
int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0644);
#endif
if (fd == -1) {
return NO;
}
return close(fd) == 0;
}
- (BOOL)launchFailureMarkerPresent {
return [[_fileManager underlyingFileManager] fileExistsAtPath:[self launchFailureMarkerPath]];
}
- (BOOL)removeLaunchFailureMarker {
return [_fileManager removeItemAtPath:[self launchFailureMarkerPath]];
}
@end

View File

@ -0,0 +1,22 @@
// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import <Foundation/Foundation.h>
NS_SWIFT_NAME(OnDemandModel)
@interface FIRCLSOnDemandModel : NSObject
- (instancetype)init NS_UNAVAILABLE;
@end

View File

@ -0,0 +1,267 @@
// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import <Foundation/Foundation.h>
#import "Crashlytics/Crashlytics/Components/FIRCLSApplication.h"
#import "Crashlytics/Crashlytics/DataCollection/FIRCLSDataCollectionArbiter.h"
#import "Crashlytics/Crashlytics/DataCollection/FIRCLSDataCollectionToken.h"
#import "Crashlytics/Crashlytics/Handlers/FIRCLSException.h"
#import "Crashlytics/Crashlytics/Helpers/FIRCLSLogger.h"
#include "Crashlytics/Crashlytics/Helpers/FIRCLSUtility.h"
#import "Crashlytics/Crashlytics/Models/FIRCLSFileManager.h"
#import "Crashlytics/Crashlytics/Models/FIRCLSOnDemandModel.h"
#import "Crashlytics/Crashlytics/Private/FIRCLSOnDemandModel_Private.h"
#include <math.h>
@interface FIRCLSOnDemandModel ()
@property(nonatomic, readonly) int recordedOnDemandExceptionCount;
@property(nonatomic, readonly) int droppedOnDemandExceptionCount;
@property(nonatomic, readonly) int queuedOperationsCount;
@property(nonatomic, strong) FIRCLSSettings *settings;
@property(nonatomic, strong) NSOperationQueue *operationQueue;
@property(nonatomic, strong) dispatch_queue_t dispatchQueue;
@property(nonatomic) double lastUpdated;
@property(nonatomic) double currentStep;
@property(nonatomic, strong) FIRCLSFileManager *fileManager;
@property(nonatomic, strong) NSMutableArray *storedActiveReportPaths;
@end
@implementation FIRCLSOnDemandModel
@synthesize recordedOnDemandExceptionCount = _recordedOnDemandExceptionCount;
@synthesize droppedOnDemandExceptionCount = _droppedOnDemandExceptionCount;
@synthesize queuedOperationsCount = _queuedOperationsCount;
static const double MAX_DELAY_SEC = 3600;
static const double SEC_PER_MINUTE = 60;
- (instancetype)initWithFIRCLSSettings:(FIRCLSSettings *)settings
fileManager:(FIRCLSFileManager *)fileManager {
self = [super init];
if (!self) {
return nil;
}
_settings = settings;
_fileManager = fileManager;
NSString *sdkBundleID = FIRCLSApplicationGetSDKBundleID();
_operationQueue = [NSOperationQueue new];
[_operationQueue setMaxConcurrentOperationCount:1];
[_operationQueue setName:[sdkBundleID stringByAppendingString:@".on-demand-queue"]];
_dispatchQueue = dispatch_queue_create("com.google.firebase.crashlytics.on.demand", 0);
_operationQueue.underlyingQueue = _dispatchQueue;
_queuedOperationsCount = 0;
_recordedOnDemandExceptionCount = 0;
_droppedOnDemandExceptionCount = 0;
_lastUpdated = [NSDate timeIntervalSinceReferenceDate];
_currentStep = -1;
self.storedActiveReportPaths = [NSMutableArray array];
return self;
}
/*
* Called from FIRCrashlytics whenever the on-demand record exception method is called. Handles
* rate limiting and exponential backoff.
*/
- (BOOL)recordOnDemandExceptionIfQuota:(FIRExceptionModel *)exceptionModel
withDataCollectionEnabled:(BOOL)dataCollectionEnabled
usingExistingReportManager:(FIRCLSExistingReportManager *)existingReportManager {
// Record the exception model into a new report if there is unused on-demand quota. Otherwise,
// log the occurrence but drop the event.
@synchronized(self) {
if ([self isQueueFull]) {
FIRCLSDebugLog(@"No available on-demand quota, dropping report");
[self incrementDroppedExceptionCount];
[self incrementRecordedExceptionCount];
return NO; // Didn't record or submit the exception because no quota was available.
}
FIRCLSDataCollectionToken *dataCollectionToken = [FIRCLSDataCollectionToken validToken];
NSString *activeReportPath = [self recordOnDemandExceptionWithModel:exceptionModel];
if (!activeReportPath) {
FIRCLSErrorLog(@"Error recording on-demand exception");
return NO; // Something went wrong when recording the exception, so we don't have a valid
// path.
}
// Only submit an exception report if data collection is enabled. Otherwise, the report
// is stored until send or delete unsent reports is called.
[self incrementQueuedOperationCount];
[self incrementRecordedExceptionCount];
[self resetDroppedExceptionCount];
[self.operationQueue addOperationWithBlock:^{
double uploadDelay = [self calculateUploadDelay];
if (dataCollectionEnabled) {
[existingReportManager handleOnDemandReportUpload:activeReportPath
dataCollectionToken:dataCollectionToken
asUrgent:YES];
FIRCLSDebugLog(@"Submitted an on-demand exception, starting delay %.20f", uploadDelay);
} else {
[self.storedActiveReportPaths insertObject:activeReportPath atIndex:0];
if ([self.storedActiveReportPaths count] > FIRCLSMaxUnsentReports) {
[self.fileManager removeItemAtPath:[self.storedActiveReportPaths lastObject]];
[self.storedActiveReportPaths removeLastObject];
[self decrementRecordedExceptionCount];
[self incrementDroppedExceptionCount];
}
FIRCLSDebugLog(@"Stored an on-demand exception, starting delay %.20f", uploadDelay);
}
[self implementOnDemandUploadDelay:uploadDelay];
[self decrementQueuedOperationCount];
}];
return YES; // Recorded and submitted the exception.
}
}
- (double)calculateUploadDelay {
double calculatedStepDuration = [self calculateStepDuration];
double power = pow(self.settings.onDemandBackoffBase, calculatedStepDuration);
NSNumber *calculatedUploadDelay =
[NSNumber numberWithDouble:(SEC_PER_MINUTE / self.settings.onDemandUploadRate) * power];
NSComparisonResult result =
[[NSNumber numberWithDouble:MAX_DELAY_SEC] compare:calculatedUploadDelay];
return (result == NSOrderedAscending) ? MAX_DELAY_SEC : [calculatedUploadDelay doubleValue];
}
- (double)calculateStepDuration {
double currentTime = [NSDate timeIntervalSinceReferenceDate];
BOOL queueIsFull = [self isQueueFull];
if (self.currentStep == -1) {
self.currentStep = 0;
self.lastUpdated = currentTime;
}
double delta =
(currentTime - self.lastUpdated) / (double)self.settings.onDemandBackoffStepDuration;
double queueFullDuration = (self.currentStep + delta) > 100 ? 100 : (self.currentStep + delta);
double queueNotFullDuration = (self.currentStep - delta) < 0 ? 0 : (self.currentStep - delta);
double calculatedDuration = queueIsFull ? queueFullDuration : queueNotFullDuration;
if (self.currentStep != calculatedDuration) {
self.currentStep = calculatedDuration;
self.lastUpdated = currentTime;
}
return calculatedDuration;
}
- (void)implementOnDemandUploadDelay:(int)delay {
sleep(delay);
}
- (NSString *)recordOnDemandExceptionWithModel:(FIRExceptionModel *)exceptionModel {
return FIRCLSExceptionRecordOnDemandModel(exceptionModel, self.recordedOnDemandExceptionCount,
self.droppedOnDemandExceptionCount);
}
- (int)droppedOnDemandExceptionCount {
@synchronized(self) {
return _droppedOnDemandExceptionCount;
}
}
- (void)setDroppedOnDemandExceptionCount:(int)count {
@synchronized(self) {
_droppedOnDemandExceptionCount = count;
}
}
- (void)incrementDroppedExceptionCount {
@synchronized(self) {
[self setDroppedOnDemandExceptionCount:[self droppedOnDemandExceptionCount] + 1];
}
}
- (void)decrementDroppedExceptionCount {
@synchronized(self) {
[self setDroppedOnDemandExceptionCount:[self droppedOnDemandExceptionCount] - 1];
}
}
- (void)resetDroppedExceptionCount {
@synchronized(self) {
[self setDroppedOnDemandExceptionCount:0];
}
}
- (int)recordedOnDemandExceptionCount {
@synchronized(self) {
return _recordedOnDemandExceptionCount;
}
}
- (void)setRecordedOnDemandExceptionCount:(int)count {
@synchronized(self) {
_recordedOnDemandExceptionCount = count;
}
}
- (void)incrementRecordedExceptionCount {
@synchronized(self) {
[self setRecordedOnDemandExceptionCount:[self recordedOnDemandExceptionCount] + 1];
}
}
- (void)decrementRecordedExceptionCount {
@synchronized(self) {
[self setRecordedOnDemandExceptionCount:[self recordedOnDemandExceptionCount] - 1];
}
}
- (int)getQueuedOperationsCount {
@synchronized(self) {
return _queuedOperationsCount;
}
}
- (void)setQueuedOperationsCount:(int)count {
@synchronized(self) {
_queuedOperationsCount = count;
}
}
- (void)incrementQueuedOperationCount {
@synchronized(self) {
[self setQueuedOperationsCount:[self getQueuedOperationsCount] + 1];
}
}
- (void)decrementQueuedOperationCount {
@synchronized(self) {
[self setQueuedOperationsCount:[self getQueuedOperationsCount] - 1];
}
}
- (BOOL)isQueueFull {
return ([self getQueuedOperationsCount] >= self.settings.onDemandUploadRate);
}
@end

View File

@ -0,0 +1,126 @@
// Copyright 2019 Google
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import <Foundation/Foundation.h>
#if __has_include(<FBLPromises/FBLPromises.h>)
#import <FBLPromises/FBLPromises.h>
#else
#import "FBLPromises.h"
#endif
@class FIRCLSApplicationIdentifierModel;
@class FIRCLSFileManager;
NS_ASSUME_NONNULL_BEGIN
@interface FIRCLSSettings : NSObject
- (instancetype)init NS_UNAVAILABLE;
+ (instancetype)new NS_UNAVAILABLE;
- (instancetype)initWithFileManager:(FIRCLSFileManager *)fileManager
appIDModel:(FIRCLSApplicationIdentifierModel *)appIDModel
NS_DESIGNATED_INITIALIZER;
/**
* Recreates the settings dictionary by re-reading the settings file from persistent storage. This
* should be called before any settings values are read, as it will populate the underlying
* settingsDictionary. If the Google App ID has changed or there is an error, delete the cache file
* and settingsDictionary. If the cache has expired, set `isCacheExpired` to true so that settings
* are re-fetched, but do not delete any values.
*/
- (void)reloadFromCacheWithGoogleAppID:(NSString *)googleAppID
currentTimestamp:(NSTimeInterval)currentTimestamp;
/**
* Stores a separate file with the settings expiration and Google App ID it was saved with
* so that we can later determine that the settings have expired.
*
* This should be called in a background thread right after the settings.json file has been
* downloaded.
*/
- (void)cacheSettingsWithGoogleAppID:(NSString *)googleAppID
currentTimestamp:(NSTimeInterval)currentTimestamp;
/**
* Returns true when Settings should be fetched from the server again
*/
@property(nonatomic, readonly) BOOL isCacheExpired;
/**
* Determines how long these Settings should be respected until the SDK should fetch again
*/
@property(nonatomic, readonly) uint32_t cacheDurationSeconds;
/**
* When this is false, Crashlytics will not start up
*/
@property(nonatomic, readonly) BOOL collectReportsEnabled;
/**
* When this is false, Crashlytics will not collect non-fatal errors and errors
* from the custom exception / record error APIs
*/
@property(nonatomic, readonly) BOOL errorReportingEnabled;
/**
* When this is false, Crashlytics will not collect custom exceptions from the API
*/
@property(nonatomic, readonly) BOOL customExceptionsEnabled;
/**
* When this is true, Crashlytics will collect data from MetricKit
*/
@property(nonatomic, readonly) BOOL metricKitCollectionEnabled;
/**
* Returns the maximum number of custom exception events that will be
* recorded in a session.
*/
@property(nonatomic, readonly) uint32_t errorLogBufferSize;
/**
* Returns the maximum size of the log buffer in bytes
*/
@property(nonatomic, readonly) uint32_t logBufferSize;
/**
* Returns the maximum number of custom exceptions that will be collected
* in a session.
*/
@property(nonatomic, readonly) uint32_t maxCustomExceptions;
/**
* Returns the maximum number of custom key-value pair keys (not bytes).
*/
@property(nonatomic, readonly) uint32_t maxCustomKeys;
/**
* Returns the initial upload rate for on-demand exception reporting.
*/
@property(nonatomic, readonly) double onDemandUploadRate;
/**
* Base exponent used when exponential backoff is triggered for on-demand reporting.
*/
@property(nonatomic, readonly) double onDemandBackoffBase;
/**
* Step duration to use with exponential backoff for on-demand reporting.
*/
@property(nonatomic, readonly) uint32_t onDemandBackoffStepDuration;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,360 @@
// Copyright 2019 Google
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import "Crashlytics/Crashlytics/Models/FIRCLSSettings.h"
#if __has_include(<FBLPromises/FBLPromises.h>)
#import <FBLPromises/FBLPromises.h>
#else
#import "FBLPromises.h"
#endif
#import "Crashlytics/Crashlytics/Helpers/FIRCLSLogger.h"
#import "Crashlytics/Crashlytics/Models/FIRCLSFileManager.h"
#import "Crashlytics/Crashlytics/Settings/Models/FIRCLSApplicationIdentifierModel.h"
#import "Crashlytics/Shared/FIRCLSConstants.h"
#import "Crashlytics/Shared/FIRCLSNetworking/FIRCLSURLBuilder.h"
NSString *const CreatedAtKey = @"created_at";
NSString *const GoogleAppIDKey = @"google_app_id";
NSString *const BuildInstanceID = @"build_instance_id";
NSString *const AppVersion = @"app_version";
@interface FIRCLSSettings ()
@property(nonatomic, strong) FIRCLSFileManager *fileManager;
@property(nonatomic, strong) FIRCLSApplicationIdentifierModel *appIDModel;
@property(nonatomic, strong) NSDictionary<NSString *, id> *settingsDictionary;
@property(nonatomic) BOOL isCacheKeyExpired;
@end
@implementation FIRCLSSettings
- (instancetype)initWithFileManager:(FIRCLSFileManager *)fileManager
appIDModel:(FIRCLSApplicationIdentifierModel *)appIDModel {
self = [super init];
if (!self) {
return nil;
}
_fileManager = fileManager;
_appIDModel = appIDModel;
_settingsDictionary = nil;
_isCacheKeyExpired = NO;
return self;
}
#pragma mark - Public Methods
- (void)reloadFromCacheWithGoogleAppID:(NSString *)googleAppID
currentTimestamp:(NSTimeInterval)currentTimestamp {
NSString *settingsFilePath = self.fileManager.settingsFilePath;
NSData *data = [self.fileManager dataWithContentsOfFile:settingsFilePath];
if (!data) {
FIRCLSDebugLog(@"[Crashlytics:Settings] No settings were cached");
return;
}
NSError *error = nil;
@synchronized(self) {
_settingsDictionary = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error];
}
if (!_settingsDictionary) {
FIRCLSErrorLog(@"Could not load settings file data with error: %@", error.localizedDescription);
// Attempt to remove it, in case it's messed up
[self deleteCachedSettings];
return;
}
NSDictionary<NSString *, id> *cacheKey = [self loadCacheKey];
if (!cacheKey) {
FIRCLSErrorLog(@"Could not load settings cache key");
[self deleteCachedSettings];
return;
}
NSString *cachedGoogleAppID = cacheKey[GoogleAppIDKey];
if (![cachedGoogleAppID isEqualToString:googleAppID]) {
FIRCLSDebugLog(
@"[Crashlytics:Settings] Invalidating settings cache because Google App ID changed");
[self deleteCachedSettings];
return;
}
NSTimeInterval cacheCreatedAt = [cacheKey[CreatedAtKey] unsignedIntValue];
NSTimeInterval cacheDurationSeconds = self.cacheDurationSeconds;
if (currentTimestamp > (cacheCreatedAt + cacheDurationSeconds)) {
FIRCLSDebugLog(@"[Crashlytics:Settings] Settings TTL expired");
@synchronized(self) {
self.isCacheKeyExpired = YES;
}
}
NSString *cacheBuildInstanceID = cacheKey[BuildInstanceID];
if (![cacheBuildInstanceID isEqualToString:self.appIDModel.buildInstanceID]) {
FIRCLSDebugLog(@"[Crashlytics:Settings] Settings expired because build instance changed");
@synchronized(self) {
self.isCacheKeyExpired = YES;
}
}
NSString *cacheAppVersion = cacheKey[AppVersion];
if (![cacheAppVersion isEqualToString:self.appIDModel.synthesizedVersion]) {
FIRCLSDebugLog(@"[Crashlytics:Settings] Settings expired because app version changed");
@synchronized(self) {
self.isCacheKeyExpired = YES;
}
}
}
- (void)cacheSettingsWithGoogleAppID:(NSString *)googleAppID
currentTimestamp:(NSTimeInterval)currentTimestamp {
NSNumber *createdAtTimestamp = [NSNumber numberWithDouble:currentTimestamp];
NSDictionary *cacheKey = @{
CreatedAtKey : createdAtTimestamp,
GoogleAppIDKey : googleAppID,
BuildInstanceID : self.appIDModel.buildInstanceID,
AppVersion : self.appIDModel.synthesizedVersion,
};
NSError *error = nil;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:cacheKey
options:kNilOptions
error:&error];
if (!jsonData) {
FIRCLSErrorLog(@"Could not create settings cache key with error: %@",
error.localizedDescription);
return;
}
if ([self.fileManager fileExistsAtPath:self.fileManager.settingsCacheKeyPath]) {
[self.fileManager removeItemAtPath:self.fileManager.settingsCacheKeyPath];
}
[self.fileManager createFileAtPath:self.fileManager.settingsCacheKeyPath
contents:jsonData
attributes:nil];
// If Settings were expired before, they should no longer be expired after this.
// This may be set back to YES if reloading from the cache fails
@synchronized(self) {
self.isCacheKeyExpired = NO;
}
[self reloadFromCacheWithGoogleAppID:googleAppID currentTimestamp:currentTimestamp];
}
#pragma mark - Convenience Methods
- (NSDictionary *)loadCacheKey {
NSData *cacheKeyData =
[self.fileManager dataWithContentsOfFile:self.fileManager.settingsCacheKeyPath];
if (!cacheKeyData) {
return nil;
}
NSError *error = nil;
NSDictionary *cacheKey = [NSJSONSerialization JSONObjectWithData:cacheKeyData
options:NSJSONReadingAllowFragments
error:&error];
return cacheKey;
}
- (void)deleteCachedSettings {
__weak FIRCLSSettings *weakSelf = self;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
__strong FIRCLSSettings *strongSelf = weakSelf;
if ([strongSelf.fileManager fileExistsAtPath:strongSelf.fileManager.settingsFilePath]) {
[strongSelf.fileManager removeItemAtPath:strongSelf.fileManager.settingsFilePath];
}
if ([strongSelf.fileManager fileExistsAtPath:strongSelf.fileManager.settingsCacheKeyPath]) {
[strongSelf.fileManager removeItemAtPath:strongSelf.fileManager.settingsCacheKeyPath];
}
});
@synchronized(self) {
self.isCacheKeyExpired = YES;
_settingsDictionary = nil;
}
}
- (NSDictionary<NSString *, id> *)settingsDictionary {
@synchronized(self) {
return _settingsDictionary;
}
}
#pragma mark - Settings Groups
- (NSDictionary<NSString *, id> *)appSettings {
return self.settingsDictionary[@"app"];
}
- (NSDictionary<NSString *, id> *)sessionSettings {
return self.settingsDictionary[@"session"];
}
- (NSDictionary<NSString *, id> *)featuresSettings {
return self.settingsDictionary[@"features"];
}
- (NSDictionary<NSString *, id> *)fabricSettings {
return self.settingsDictionary[@"fabric"];
}
#pragma mark - Caching
- (BOOL)isCacheExpired {
if (!self.settingsDictionary) {
return YES;
}
@synchronized(self) {
return self.isCacheKeyExpired;
}
}
- (uint32_t)cacheDurationSeconds {
id fetchedCacheDuration = self.settingsDictionary[@"cache_duration"];
if (fetchedCacheDuration) {
return [fetchedCacheDuration unsignedIntValue];
}
return 60 * 60;
}
#pragma mark - On / Off Switches
- (BOOL)errorReportingEnabled {
NSNumber *value = [self featuresSettings][@"collect_logged_exceptions"];
if (value != nil) {
return [value boolValue];
}
return YES;
}
- (BOOL)customExceptionsEnabled {
// Right now, recording custom exceptions from the API and
// automatically capturing non-fatal errors go hand in hand
return [self errorReportingEnabled];
}
- (BOOL)collectReportsEnabled {
NSNumber *value = [self featuresSettings][@"collect_reports"];
if (value != nil) {
return value.boolValue;
}
return YES;
}
- (BOOL)metricKitCollectionEnabled {
NSNumber *value = [self featuresSettings][@"collect_metric_kit"];
if (value != nil) {
return value.boolValue;
}
return NO;
}
#pragma mark - Optional Limit Overrides
- (uint32_t)errorLogBufferSize {
return [self logBufferSize];
}
- (uint32_t)logBufferSize {
NSNumber *value = [self sessionSettings][@"log_buffer_size"];
if (value != nil) {
return value.unsignedIntValue;
}
return 64 * 1000;
}
- (uint32_t)maxCustomExceptions {
NSNumber *value = [self sessionSettings][@"max_custom_exception_events"];
if (value != nil) {
return value.unsignedIntValue;
}
return 8;
}
- (uint32_t)maxCustomKeys {
NSNumber *value = [self sessionSettings][@"max_custom_key_value_pairs"];
if (value != nil) {
return value.unsignedIntValue;
}
return 64;
}
#pragma mark - On Demand Reporting Parameters
- (double)onDemandUploadRate {
NSNumber *value = self.settingsDictionary[@"on_demand_upload_rate_per_minute"];
if (value != nil) {
return value.doubleValue;
}
return 10; // on-demand uploads allowed per minute
}
- (double)onDemandBackoffBase {
NSNumber *value = self.settingsDictionary[@"on_demand_backoff_base"];
if (value != nil) {
return [value doubleValue];
}
return 1.5; // base of exponent for exponential backoff
}
- (uint32_t)onDemandBackoffStepDuration {
NSNumber *value = self.settingsDictionary[@"on_demand_backoff_step_duration_seconds"];
if (value != nil) {
return value.unsignedIntValue;
}
return 6; // step duration for exponential backoff
}
@end

View File

@ -0,0 +1,26 @@
// Copyright 2019 Google
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import <Foundation/Foundation.h>
@class FIRStackFrame;
@interface FIRCLSSymbolResolver : NSObject
- (BOOL)loadBinaryImagesFromFile:(NSString *)path;
- (FIRStackFrame *)frameForAddress:(uint64_t)address;
- (BOOL)updateStackFrame:(FIRStackFrame *)frame;
@end

View File

@ -0,0 +1,176 @@
// Copyright 2019 Google
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import "Crashlytics/Crashlytics/Models/FIRCLSSymbolResolver.h"
#include <dlfcn.h>
#include "Crashlytics/Crashlytics/Components/FIRCLSBinaryImage.h"
#include "Crashlytics/Crashlytics/Helpers/FIRCLSFile.h"
#import "Crashlytics/Crashlytics/Helpers/FIRCLSInternalLogging.h"
#import "Crashlytics/Crashlytics/Helpers/FIRCLSLogger.h"
#import "Crashlytics/Crashlytics/Private/FIRStackFrame_Private.h"
@interface FIRCLSSymbolResolver () {
NSMutableArray* _binaryImages;
}
@end
@implementation FIRCLSSymbolResolver
- (instancetype)init {
self = [super init];
if (!self) {
return nil;
}
_binaryImages = [NSMutableArray array];
return self;
}
- (BOOL)loadBinaryImagesFromFile:(NSString*)path {
if ([path length] == 0) {
return NO;
}
NSArray* sections = FIRCLSFileReadSections([path fileSystemRepresentation], false, nil);
if ([sections count] == 0) {
FIRCLSErrorLog(@"Failed to read binary image file %@", path);
return NO;
}
// filter out unloads, as well as loads with invalid entries
for (NSDictionary* entry in sections) {
NSDictionary* details = [entry objectForKey:@"load"];
if (!details) {
continue;
}
// This does happen occasionally and causes a crash. I'm really not sure there
// is anything sane we can do in this case.
if (![details objectForKey:@"base"] || ![details objectForKey:@"size"]) {
continue;
}
if ([details objectForKey:@"base"] == (id)[NSNull null] ||
[details objectForKey:@"size"] == (id)[NSNull null]) {
continue;
}
[_binaryImages addObject:details];
}
[_binaryImages sortUsingComparator:^NSComparisonResult(id obj1, id obj2) {
NSNumber* base1 = [obj1 objectForKey:@"base"];
NSNumber* base2 = [obj2 objectForKey:@"base"];
return [base1 compare:base2];
}];
return YES;
}
- (NSDictionary*)loadedBinaryImageForPC:(uintptr_t)pc {
NSUInteger index =
[_binaryImages indexOfObjectPassingTest:^BOOL(id obj, NSUInteger idx, BOOL* stop) {
uintptr_t base = [[obj objectForKey:@"base"] unsignedIntegerValue];
uintptr_t size = [[obj objectForKey:@"size"] unsignedIntegerValue];
return pc >= base && pc < (base + size);
}];
if (index == NSNotFound) {
return nil;
}
return [_binaryImages objectAtIndex:index];
}
- (BOOL)fillInImageDetails:(FIRCLSBinaryImageDetails*)details forUUID:(NSString*)uuid {
if (!details || !uuid) {
return NO;
}
return FIRCLSBinaryImageFindImageForUUID([uuid UTF8String], details);
}
- (FIRStackFrame*)frameForAddress:(uint64_t)address {
FIRStackFrame* frame = [FIRStackFrame stackFrameWithAddress:(NSUInteger)address];
if (![self updateStackFrame:frame]) {
return nil;
}
return frame;
}
- (BOOL)updateStackFrame:(FIRStackFrame*)frame {
uint64_t address = [frame address];
if (address == 0) {
return NO;
}
NSDictionary* binaryImage = [self loadedBinaryImageForPC:(uintptr_t)address];
FIRCLSBinaryImageDetails imageDetails;
if (![self fillInImageDetails:&imageDetails forUUID:[binaryImage objectForKey:@"uuid"]]) {
#if DEBUG
FIRCLSSDKLog("Image not found\n");
#endif
return NO;
}
uintptr_t addr = (uintptr_t)address -
(uintptr_t)[[binaryImage objectForKey:@"base"] unsignedIntegerValue] +
(uintptr_t)imageDetails.node.baseAddress;
Dl_info dlInfo;
if (dladdr((void*)addr, &dlInfo) == 0) {
#if DEBUG
FIRCLSSDKLog("Could not look up address\n");
#endif
return NO;
}
if (addr - (uintptr_t)dlInfo.dli_saddr == 0) {
addr -= 2;
if (dladdr((void*)addr, &dlInfo) == 0) {
#if DEBUG
FIRCLSSDKLog("Could not look up address after move\n");
#endif
return NO;
}
}
if (dlInfo.dli_sname) {
NSString* symbol = [NSString stringWithUTF8String:dlInfo.dli_sname];
frame.symbol = symbol;
frame.rawSymbol = symbol;
}
if (addr > (uintptr_t)dlInfo.dli_saddr) {
[frame setOffset:addr - (uintptr_t)dlInfo.dli_saddr];
}
[frame setLibrary:[[binaryImage objectForKey:@"path"] lastPathComponent]];
return YES;
}
@end

View File

@ -0,0 +1,24 @@
/*
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import "Crashlytics/Crashlytics/Models/Record/FIRCLSRecordBase.h"
@interface FIRCLSRecordApplication : FIRCLSRecordBase
@property(nonatomic, copy) NSString *build_version;
@property(nonatomic, copy) NSString *display_version;
@end

View File

@ -0,0 +1,30 @@
/*
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import "Crashlytics/Crashlytics/Models/Record/FIRCLSRecordApplication.h"
@implementation FIRCLSRecordApplication
- (instancetype)initWithDict:(NSDictionary *)dict {
self = [super initWithDict:dict];
if (self) {
_display_version = dict[@"display_version"];
_build_version = dict[@"build_version"];
}
return self;
}
@end

View File

@ -0,0 +1,37 @@
/*
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import <Foundation/Foundation.h>
/**
* This is the base class to represent the data in the persisted crash (.clsrecord) files.
* The properties these subclasses are nullable on purpose. If there is an issue reading values
* from the crash files, continue as if those fields are optional so a report can still be uploaded.
* That way the issue can potentially be monitored through the backend.
**/
@interface FIRCLSRecordBase : NSObject
/**
* Mark the default initializer as unavailable so the subclasses do not have to add the same line
**/
- (instancetype)init NS_UNAVAILABLE;
/**
* All subclasses should define an initializer taking in a dictionary
**/
- (instancetype)initWithDict:(NSDictionary *)dict;
@end

View File

@ -0,0 +1,25 @@
/*
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import "Crashlytics/Crashlytics/Models/Record/FIRCLSRecordBase.h"
@implementation FIRCLSRecordBase
- (instancetype)initWithDict:(NSDictionary *)dict {
return [super init];
}
@end

View File

@ -0,0 +1,23 @@
/*
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import "Crashlytics/Crashlytics/Models/Record/FIRCLSRecordBase.h"
@interface FIRCLSRecordHost : FIRCLSRecordBase
@property(nonatomic, copy) NSString *platform;
@end

View File

@ -0,0 +1,29 @@
/*
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import "Crashlytics/Crashlytics/Models/Record/FIRCLSRecordHost.h"
@implementation FIRCLSRecordHost
- (instancetype)initWithDict:(NSDictionary *)dict {
self = [super initWithDict:dict];
if (self) {
_platform = dict[@"platform"];
}
return self;
}
@end

View File

@ -0,0 +1,24 @@
/*
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import "Crashlytics/Crashlytics/Models/Record/FIRCLSRecordBase.h"
@interface FIRCLSRecordIdentity : FIRCLSRecordBase
@property(nonatomic, copy) NSString *build_version;
@property(nonatomic, copy) NSString *app_quality_session_id;
@end

View File

@ -0,0 +1,30 @@
/*
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import "Crashlytics/Crashlytics/Models/Record/FIRCLSRecordIdentity.h"
@implementation FIRCLSRecordIdentity
- (instancetype)initWithDict:(NSDictionary *)dict {
self = [super initWithDict:dict];
if (self) {
_build_version = dict[@"build_version"];
_app_quality_session_id = dict[@"app_quality_session_id"];
}
return self;
}
@end

View File

@ -0,0 +1,40 @@
/*
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import <Foundation/Foundation.h>
#include "Crashlytics/Protogen/nanopb/crashlytics.nanopb.h"
#import "Crashlytics/Crashlytics/Models/FIRCLSInstallIdentifierModel.h"
#import <GoogleDataTransport/GoogleDataTransport.h>
/// This class is responsible for reading the persisted crash reports from disk and converting them
/// the information into the nanopb model to be used with GoogleDataTransport
@interface FIRCLSReportAdapter : NSObject <GDTCOREventDataObject>
- (instancetype)init NS_UNAVAILABLE;
/// Initializer
/// @param folderPath Path where the persisted crash files reside
/// @param googleAppID ID for the app passed in from Firebase Core
/// @param installIDModel for pulling the Crashlytics Installation UUID
- (instancetype)initWithPath:(NSString *)folderPath
googleAppId:(NSString *)googleAppID
installIDModel:(FIRCLSInstallIdentifierModel *)installIDModel
fiid:(NSString *)fiid
authToken:(NSString *)authToken;
@end

View File

@ -0,0 +1,268 @@
/*
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import "Crashlytics/Crashlytics/Models/Record/FIRCLSReportAdapter.h"
#import "Crashlytics/Crashlytics/Models/Record/FIRCLSReportAdapter_Private.h"
#import "Crashlytics/Crashlytics/Helpers/FIRCLSLogger.h"
#import "Crashlytics/Crashlytics/Models/FIRCLSInternalReport.h"
#import "Crashlytics/Crashlytics/Components/FIRCLSUserLogging.h"
#import <nanopb/pb.h>
#import <nanopb/pb_decode.h>
#import <nanopb/pb_encode.h>
@interface FIRCLSReportAdapter ()
@property(nonatomic, strong) FIRCLSInstallIdentifierModel *installIDModel;
@property(nonatomic, copy) NSString *fiid;
@property(nonatomic, copy) NSString *authToken;
@end
@implementation FIRCLSReportAdapter
- (instancetype)initWithPath:(NSString *)folderPath
googleAppId:(NSString *)googleAppID
installIDModel:(FIRCLSInstallIdentifierModel *)installIDModel
fiid:(NSString *)fiid
authToken:(NSString *)authToken {
self = [super init];
if (self) {
_folderPath = folderPath;
_googleAppID = googleAppID;
_installIDModel = installIDModel;
_fiid = [fiid copy];
_authToken = [authToken copy];
[self loadMetaDataFile];
_report = [self protoReport];
}
return self;
}
- (void)dealloc {
pb_release(google_crashlytics_Report_fields, &_report);
}
//
// MARK: Load from persisted crash files
//
/// Reads from metadata.clsrecord
- (void)loadMetaDataFile {
NSString *path = [self.folderPath stringByAppendingPathComponent:FIRCLSReportMetadataFile];
NSDictionary *dict = [FIRCLSReportAdapter combinedDictionariesFromFilePath:path];
self.identity = [[FIRCLSRecordIdentity alloc] initWithDict:dict[@"identity"]];
self.host = [[FIRCLSRecordHost alloc] initWithDict:dict[@"host"]];
self.application = [[FIRCLSRecordApplication alloc] initWithDict:dict[@"application"]];
}
/// Return the persisted crash file as a combined dictionary that way lookups can occur with a key
/// (to avoid ordering dependency)
/// @param filePath Persisted crash file path
+ (NSDictionary *)combinedDictionariesFromFilePath:(NSString *)filePath {
NSMutableDictionary *joinedDict = [[NSMutableDictionary alloc] init];
for (NSDictionary *dict in [self dictionariesFromEachLineOfFile:filePath]) {
[joinedDict addEntriesFromDictionary:dict];
}
return joinedDict;
}
/// The persisted crash files contains JSON on separate lines. Read each line and return the JSON
/// data as a dictionary.
/// @param filePath Persisted crash file path
+ (NSArray<NSDictionary *> *)dictionariesFromEachLineOfFile:(NSString *)filePath {
NSString *content = [[NSString alloc] initWithContentsOfFile:filePath
encoding:NSUTF8StringEncoding
error:nil];
NSArray *lines =
[content componentsSeparatedByCharactersInSet:NSCharacterSet.newlineCharacterSet];
NSMutableArray<NSDictionary *> *array = [[NSMutableArray<NSDictionary *> alloc] init];
int lineNum = 0;
for (NSString *line in lines) {
lineNum++;
if (line.length == 0) {
// Likely newline at the end of the file
continue;
}
NSError *error;
NSDictionary *dict =
[NSJSONSerialization JSONObjectWithData:[line dataUsingEncoding:NSUTF8StringEncoding]
options:0
error:&error];
if (error) {
FIRCLSErrorLog(@"Failed to read JSON from file (%@) line (%d) with error: %@", filePath,
lineNum, error);
} else {
[array addObject:dict];
}
}
return array;
}
//
// MARK: GDTCOREventDataObject
//
- (NSData *)transportBytes {
pb_ostream_t sizestream = PB_OSTREAM_SIZING;
// Encode 1 time to determine the size.
if (!pb_encode(&sizestream, google_crashlytics_Report_fields, &_report)) {
FIRCLSErrorLog(@"Error in nanopb encoding for size: %s", PB_GET_ERROR(&sizestream));
}
// Encode a 2nd time to actually get the bytes from it.
size_t bufferSize = sizestream.bytes_written;
CFMutableDataRef dataRef = CFDataCreateMutable(CFAllocatorGetDefault(), bufferSize);
CFDataSetLength(dataRef, bufferSize);
pb_ostream_t ostream = pb_ostream_from_buffer((void *)CFDataGetBytePtr(dataRef), bufferSize);
if (!pb_encode(&ostream, google_crashlytics_Report_fields, &_report)) {
FIRCLSErrorLog(@"Error in nanopb encoding for bytes: %s", PB_GET_ERROR(&ostream));
}
return CFBridgingRelease(dataRef);
}
//
// MARK: NanoPB conversions
//
- (google_crashlytics_Report)protoReport {
google_crashlytics_Report report = google_crashlytics_Report_init_default;
report.sdk_version = FIRCLSEncodeString(self.identity.build_version);
report.gmp_app_id = FIRCLSEncodeString(self.googleAppID);
report.platform = [self protoPlatformFromString:self.host.platform];
report.installation_uuid = FIRCLSEncodeString(self.installIDModel.installID);
report.firebase_installation_id = FIRCLSEncodeString(self.fiid);
report.app_quality_session_id = FIRCLSEncodeString(self.identity.app_quality_session_id);
report.firebase_authentication_token = FIRCLSEncodeString(self.authToken);
report.build_version = FIRCLSEncodeString(self.application.build_version);
report.display_version = FIRCLSEncodeString(self.application.display_version);
report.apple_payload = [self protoFilesPayload];
return report;
}
- (google_crashlytics_FilesPayload)protoFilesPayload {
google_crashlytics_FilesPayload apple_payload = google_crashlytics_FilesPayload_init_default;
NSArray<NSString *> *clsRecords = [self clsRecordFilePaths];
google_crashlytics_FilesPayload_File *files =
malloc(sizeof(google_crashlytics_FilesPayload_File) * clsRecords.count);
if (files == NULL) {
// files and files_count are initialized to NULL and 0 by default.
return apple_payload;
}
for (NSUInteger i = 0; i < clsRecords.count; i++) {
google_crashlytics_FilesPayload_File file = google_crashlytics_FilesPayload_File_init_default;
file.filename = FIRCLSEncodeString(clsRecords[i].lastPathComponent);
NSError *error;
file.contents = FIRCLSEncodeData([NSData dataWithContentsOfFile:clsRecords[i]
options:0
error:&error]);
if (error) {
FIRCLSErrorLog(@"Failed to read from %@ with error: %@", clsRecords[i], error);
}
files[i] = file;
}
apple_payload.files = files;
apple_payload.files_count = (pb_size_t)clsRecords.count;
return apple_payload;
}
- (NSArray<NSString *> *)clsRecordFilePaths {
NSMutableArray<NSString *> *clsRecords = [[NSMutableArray<NSString *> alloc] init];
NSError *error;
NSArray *files = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:self.folderPath
error:&error];
if (error) {
FIRCLSErrorLog(@"Failed to find .clsrecords from %@ with error: %@", self.folderPath, error);
return clsRecords;
}
[files enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
NSString *filename = (NSString *)obj;
NSString *lowerExtension = filename.pathExtension.lowercaseString;
if ([lowerExtension isEqualToString:@"clsrecord"] ||
[lowerExtension isEqualToString:@"symbolicated"]) {
[clsRecords addObject:[self.folderPath stringByAppendingPathComponent:filename]];
}
}];
return [clsRecords sortedArrayUsingSelector:@selector(localizedCaseInsensitiveCompare:)];
}
- (google_crashlytics_Platforms)protoPlatformFromString:(NSString *)str {
NSString *platform = str.lowercaseString;
if ([platform isEqualToString:@"ios"]) {
return google_crashlytics_Platforms_IOS;
} else if ([platform isEqualToString:@"mac"]) {
return google_crashlytics_Platforms_MAC_OS_X;
} else if ([platform isEqualToString:@"tvos"]) {
return google_crashlytics_Platforms_TVOS;
} else {
return google_crashlytics_Platforms_UNKNOWN_PLATFORM;
}
}
/** Mallocs a pb_bytes_array and copies the given NSString's bytes into the bytes array.
* @note Memory needs to be freed manually, through pb_free or pb_release.
* @param string The string to encode as pb_bytes.
*/
pb_bytes_array_t *FIRCLSEncodeString(NSString *string) {
if ([string isMemberOfClass:[NSNull class]]) {
FIRCLSErrorLog(@"Expected encodable string, but found NSNull instead. "
@"Set a symbolic breakpoint at FIRCLSEncodeString to debug.");
string = nil;
}
NSString *stringToEncode = string ? string : @"";
NSData *stringBytes = [stringToEncode dataUsingEncoding:NSUTF8StringEncoding];
return FIRCLSEncodeData(stringBytes);
}
/** Mallocs a pb_bytes_array and copies the given NSData bytes into the bytes array.
* @note Memory needs to be free manually, through pb_free or pb_release.
* @param data The data to copy into the new bytes array.
*/
pb_bytes_array_t *FIRCLSEncodeData(NSData *data) {
pb_bytes_array_t *pbBytes = malloc(PB_BYTES_ARRAY_T_ALLOCSIZE(data.length));
if (pbBytes == NULL) {
return NULL;
}
memcpy(pbBytes->bytes, [data bytes], data.length);
pbBytes->size = (pb_size_t)data.length;
return pbBytes;
}
@end

View File

@ -0,0 +1,43 @@
/*
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import "Crashlytics/Crashlytics/Models/Record/FIRCLSReportAdapter.h"
#import "Crashlytics/Crashlytics/Models/Record/FIRCLSRecordApplication.h"
#import "Crashlytics/Crashlytics/Models/Record/FIRCLSRecordHost.h"
#import "Crashlytics/Crashlytics/Models/Record/FIRCLSRecordIdentity.h"
pb_bytes_array_t *FIRCLSEncodeString(NSString *string);
pb_bytes_array_t *FIRCLSEncodeData(NSData *data);
@interface FIRCLSReportAdapter ()
@property(nonatomic, readonly) BOOL hasCrashed;
@property(nonatomic, strong) NSString *folderPath;
@property(nonatomic, strong) NSString *googleAppID;
// From metadata.clsrecord
@property(nonatomic, strong) FIRCLSRecordIdentity *identity;
@property(nonatomic, strong) FIRCLSRecordHost *host;
@property(nonatomic, strong) FIRCLSRecordApplication *application;
@property(nonatomic) google_crashlytics_Report report;
- (google_crashlytics_Report)protoReport;
- (NSArray<NSString *> *)clsRecordFilePaths;
@end

View File

@ -0,0 +1,23 @@
// Copyright 2019 Google
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import <Foundation/Foundation.h>
typedef void (^FIRCLSAsyncOperationCompletionBlock)(NSError* error);
@interface FIRCLSAsyncOperation : NSOperation
@property(copy, nonatomic) FIRCLSAsyncOperationCompletionBlock asyncCompletion;
@end

Some files were not shown because too many files have changed in this diff Show More