Source/PLCrashLogWriter.m (678 lines of code) (raw):
/*
* Author: Landon Fuller <landonf@plausiblelabs.com>
*
* Copyright (c) 2008-2013 Plausible Labs Cooperative, Inc.
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/
#import <stdlib.h>
#import <fcntl.h>
#import <errno.h>
#import <string.h>
#import <stdbool.h>
#import <dlfcn.h>
#import <sys/sysctl.h>
#import <sys/time.h>
#import <mach-o/dyld.h>
#import <stdatomic.h>
#if __has_include(<CrashReporter/PLCrashReport.h>)
#import <CrashReporter/PLCrashReport.h>
#else
#import "PLCrashReport.h"
#endif
#import "PLCrashLogWriter.h"
#import "PLCrashLogWriterEncoding.h"
#import "PLCrashAsyncSignalInfo.h"
#import "PLCrashAsyncSymbolication.h"
#import "PLCrashSysctl.h"
#import "PLCrashProcessInfo.h"
/**
* @internal
* Maximum number of frames that will be written to the crash report for a single thread. Used as a safety measure
* to avoid overrunning our output limit when writing a crash report triggered by frame recursion.
*/
#define MAX_THREAD_FRAMES 512 // matches Apple's crash reporting on Snow Leopard
/**
* @internal
* Protobuf Field IDs, as defined in crashreport.proto
*/
enum {
/** CrashReport.system_info */
PLCRASH_PROTO_SYSTEM_INFO_ID = 1,
/** CrashReport.system_info.operating_system */
PLCRASH_PROTO_SYSTEM_INFO_OS_ID = 1,
/** CrashReport.system_info.os_version */
PLCRASH_PROTO_SYSTEM_INFO_OS_VERSION_ID = 2,
/** CrashReport.system_info.architecture */
PLCRASH_PROTO_SYSTEM_INFO_ARCHITECTURE_TYPE_ID = 3,
/** CrashReport.system_info.timestamp */
PLCRASH_PROTO_SYSTEM_INFO_TIMESTAMP_ID = 4,
/** CrashReport.system_info.os_build */
PLCRASH_PROTO_SYSTEM_INFO_OS_BUILD_ID = 5,
/** CrashReport.app_info */
PLCRASH_PROTO_APP_INFO_ID = 2,
/** CrashReport.app_info.app_identifier */
PLCRASH_PROTO_APP_INFO_APP_IDENTIFIER_ID = 1,
/** CrashReport.app_info.app_version */
PLCRASH_PROTO_APP_INFO_APP_VERSION_ID = 2,
/** CrashReport.app_info.app_marketing_version */
PLCRASH_PROTO_APP_INFO_APP_MARKETING_VERSION_ID = 3,
/** CrashReport.symbol.name */
PLCRASH_PROTO_SYMBOL_NAME = 1,
/** CrashReport.symbol.start_address */
PLCRASH_PROTO_SYMBOL_START_ADDRESS = 2,
/** CrashReport.symbol.end_address */
PLCRASH_PROTO_SYMBOL_END_ADDRESS = 3,
/** CrashReport.threads */
PLCRASH_PROTO_THREADS_ID = 3,
/** CrashReports.thread.thread_number */
PLCRASH_PROTO_THREAD_THREAD_NUMBER_ID = 1,
/** CrashReports.thread.frames */
PLCRASH_PROTO_THREAD_FRAMES_ID = 2,
/** CrashReport.thread.crashed */
PLCRASH_PROTO_THREAD_CRASHED_ID = 3,
/** CrashReport.thread.frame.pc */
PLCRASH_PROTO_THREAD_FRAME_PC_ID = 3,
/** CrashReport.thread.frame.symbol */
PLCRASH_PROTO_THREAD_FRAME_SYMBOL_ID = 6,
/** CrashReport.thread.registers */
PLCRASH_PROTO_THREAD_REGISTERS_ID = 4,
/** CrashReport.thread.register.name */
PLCRASH_PROTO_THREAD_REGISTER_NAME_ID = 1,
/** CrashReport.thread.register.value */
PLCRASH_PROTO_THREAD_REGISTER_VALUE_ID = 2,
/** CrashReport.images */
PLCRASH_PROTO_BINARY_IMAGES_ID = 4,
/** CrashReport.BinaryImage.base_address */
PLCRASH_PROTO_BINARY_IMAGE_ADDR_ID = 1,
/** CrashReport.BinaryImage.size */
PLCRASH_PROTO_BINARY_IMAGE_SIZE_ID = 2,
/** CrashReport.BinaryImage.name */
PLCRASH_PROTO_BINARY_IMAGE_NAME_ID = 3,
/** CrashReport.BinaryImage.uuid */
PLCRASH_PROTO_BINARY_IMAGE_UUID_ID = 4,
/** CrashReport.BinaryImage.code_type */
PLCRASH_PROTO_BINARY_IMAGE_CODE_TYPE_ID = 5,
/** CrashReport.exception */
PLCRASH_PROTO_EXCEPTION_ID = 5,
/** CrashReport.exception.name */
PLCRASH_PROTO_EXCEPTION_NAME_ID = 1,
/** CrashReport.exception.reason */
PLCRASH_PROTO_EXCEPTION_REASON_ID = 2,
/** CrashReports.exception.frames */
PLCRASH_PROTO_EXCEPTION_FRAMES_ID = 3,
/** CrashReport.signal */
PLCRASH_PROTO_SIGNAL_ID = 6,
/** CrashReport.signal.name */
PLCRASH_PROTO_SIGNAL_NAME_ID = 1,
/** CrashReport.signal.code */
PLCRASH_PROTO_SIGNAL_CODE_ID = 2,
/** CrashReport.signal.address */
PLCRASH_PROTO_SIGNAL_ADDRESS_ID = 3,
/** CrashReport.signal.mach_exception */
PLCRASH_PROTO_SIGNAL_MACH_EXCEPTION_ID = 4,
/** CrashReport.signal.mach_exception.type */
PLCRASH_PROTO_SIGNAL_MACH_EXCEPTION_TYPE_ID = 1,
/** CrashReport.signal.mach_exception.codes */
PLCRASH_PROTO_SIGNAL_MACH_EXCEPTION_CODES_ID = 2,
/** CrashReport.process_info */
PLCRASH_PROTO_PROCESS_INFO_ID = 7,
/** CrashReport.process_info.process_name */
PLCRASH_PROTO_PROCESS_INFO_PROCESS_NAME_ID = 1,
/** CrashReport.process_info.process_id */
PLCRASH_PROTO_PROCESS_INFO_PROCESS_ID_ID = 2,
/** CrashReport.process_info.process_path */
PLCRASH_PROTO_PROCESS_INFO_PROCESS_PATH_ID = 3,
/** CrashReport.process_info.parent_process_name */
PLCRASH_PROTO_PROCESS_INFO_PARENT_PROCESS_NAME_ID = 4,
/** CrashReport.process_info.parent_process_id */
PLCRASH_PROTO_PROCESS_INFO_PARENT_PROCESS_ID_ID = 5,
/** CrashReport.process_info.native */
PLCRASH_PROTO_PROCESS_INFO_NATIVE_ID = 6,
/** CrashReport.process_info.start_time */
PLCRASH_PROTO_PROCESS_INFO_START_TIME_ID = 7,
/** CrashReport.Processor.encoding */
PLCRASH_PROTO_PROCESSOR_ENCODING_ID = 1,
/** CrashReport.Processor.encoding */
PLCRASH_PROTO_PROCESSOR_TYPE_ID = 2,
/** CrashReport.Processor.encoding */
PLCRASH_PROTO_PROCESSOR_SUBTYPE_ID = 3,
/** CrashReport.machine_info */
PLCRASH_PROTO_MACHINE_INFO_ID = 8,
/** CrashReport.machine_info.model */
PLCRASH_PROTO_MACHINE_INFO_MODEL_ID = 1,
/** CrashReport.machine_info.processor */
PLCRASH_PROTO_MACHINE_INFO_PROCESSOR_ID = 2,
/** CrashReport.machine_info.processor_count */
PLCRASH_PROTO_MACHINE_INFO_PROCESSOR_COUNT_ID = 3,
/** CrashReport.machine_info.logical_processor_count */
PLCRASH_PROTO_MACHINE_INFO_LOGICAL_PROCESSOR_COUNT_ID = 4,
/** CrashReport.report_info */
PLCRASH_PROTO_REPORT_INFO_ID = 9,
/** CrashReport.report_info.crashed */
PLCRASH_PROTO_REPORT_INFO_USER_REQUESTED_ID = 1,
/** CrashReport.report_info.uuid */
PLCRASH_PROTO_REPORT_INFO_UUID_ID = 2,
/** CrashReport.custom_data */
PLCRASH_PROTO_CUSTOM_DATA_ID = 10,
};
static void plprotobuf_cbinary_data_init (PLProtobufCBinaryData *data, const void *pointer, size_t len) {
data->data = malloc(len);
memcpy(data->data , pointer, len);
data->len = len;
}
static void plprotobuf_cbinary_data_string_init (PLProtobufCBinaryData *data, const char *value) {
data->data = (void *)value;
data->len = strlen(value);
}
static void plprotobuf_cbinary_data_nsstring_init (PLProtobufCBinaryData *data, NSString *value) {
plprotobuf_cbinary_data_string_init(data, strdup([value UTF8String]));
}
static void plprotobuf_cbinary_data_free (PLProtobufCBinaryData *data) {
if (data != NULL && data->data != NULL) {
free(data->data);
data->data = NULL;
data->len = 0;
}
}
/**
* Initialize a new crash log writer instance and issue a memory barrier upon completion. This fetches all necessary
* environment information.
*
* @param writer Writer instance to be initialized.
* @param app_identifier Unique per-application identifier. On Mac OS X, this is likely the CFBundleIdentifier.
* @param app_version Application version string.
* @param app_marketing_version Application marketing version string (may be nil).
* @param symbol_strategy The strategy to use for local symbolication.
* @param user_requested If true, the written report will be marked as a 'generated' non-crash report, rather than as
* a true crash report created upon an actual crash.
*
* @note If this function fails, plcrash_log_writer_free() should be called
* to free any partially allocated data.
*
* @warning This function is not guaranteed to be async-safe, and must be called prior to enabling the crash handler.
*/
plcrash_error_t plcrash_log_writer_init (plcrash_log_writer_t *writer,
NSString *app_identifier,
NSString *app_version,
NSString *app_marketing_version,
plcrash_async_symbol_strategy_t symbol_strategy,
BOOL user_requested)
{
/* Default to 0 */
memset(writer, 0, sizeof(*writer));
/* Initialize configuration */
writer->symbol_strategy = symbol_strategy;
/* Default to false */
writer->report_info.user_requested = user_requested;
/* Generate a UUID for this incident; CFUUID is used in favor of NSUUID as to maintain compatibility
* with (Mac OS X 10.7|iOS 5) and earlier. */
{
CFUUIDRef uuid = CFUUIDCreate(NULL);
CFUUIDBytes bytes = CFUUIDGetUUIDBytes(uuid);
PLCF_ASSERT(sizeof(bytes) == sizeof(writer->report_info.uuid_bytes));
memcpy(writer->report_info.uuid_bytes, &bytes, sizeof(writer->report_info.uuid_bytes));
CFRelease(uuid);
}
/* Fetch the application information */
{
plprotobuf_cbinary_data_nsstring_init(&writer->application_info.app_identifier, app_identifier);
plprotobuf_cbinary_data_nsstring_init(&writer->application_info.app_version, app_version);
if (app_marketing_version != nil) {
plprotobuf_cbinary_data_nsstring_init(&writer->application_info.app_marketing_version, app_marketing_version);
}
}
/* Fetch the process information */
{
/* Current process */
PLCrashProcessInfo *pinfo = [PLCrashProcessInfo currentProcessInfo];
if (pinfo == nil) {
/* Should only occur if the process is no longer valid */
PLCF_DEBUG("Could not retreive process info for target");
return PLCRASH_EINVAL;
}
{
/* Retrieve PID */
writer->process_info.process_id = pinfo.processID;
/* Retrieve name and start time. */
if (pinfo.processName != nil) {
plprotobuf_cbinary_data_nsstring_init(&writer->process_info.process_name, pinfo.processName);
}
writer->process_info.start_time = pinfo.startTime.tv_sec;
/* Retrieve path */
uint32_t process_path_len = 0;
_NSGetExecutablePath(NULL, &process_path_len);
if (process_path_len > 0) {
char *process_path = malloc(process_path_len);
_NSGetExecutablePath(process_path, &process_path_len);
writer->process_info.process_path.data = process_path;
writer->process_info.process_path.len = process_path_len;
}
}
/* Parent process */
{
/* Retrieve PID */
writer->process_info.parent_process_id = pinfo.parentProcessID;
/* Retrieve name. This will fail on iOS 9+, where EPERM is returned due to new sandbox constraints. */
PLCrashProcessInfo *parentInfo = [[PLCrashProcessInfo alloc] initWithProcessID: pinfo.parentProcessID];
if (parentInfo != nil) {
if (parentInfo.processName != nil) {
plprotobuf_cbinary_data_nsstring_init(&writer->process_info.parent_process_name, parentInfo.processName);
}
} else {
PLCF_DEBUG("Could not retreive parent process name: %s", strerror(errno));
}
}
}
/* Fetch the machine information */
{
/* Model */
#if TARGET_OS_IPHONE && !TARGET_OS_MACCATALYST
/* On iOS, we want hw.machine (e.g. hw.machine = iPad2,1; hw.model = K93AP) */
char *model = plcrash_sysctl_string("hw.machine");
#else
/* On Mac OS X, we want hw.model (e.g. hw.machine = x86_64; hw.model = Macmini5,3) */
char *model = plcrash_sysctl_string("hw.model");
#endif
if (model == NULL) {
PLCF_DEBUG("Could not retrive hw.model: %s", strerror(errno));
}
plprotobuf_cbinary_data_string_init(&writer->machine_info.model, model);
/* CPU */
{
int retval;
/* Fetch the CPU types */
if (plcrash_sysctl_int("hw.cputype", &retval)) {
writer->machine_info.cpu_type = retval;
} else {
PLCF_DEBUG("Could not retrive hw.cputype: %s", strerror(errno));
}
if (plcrash_sysctl_int("hw.cpusubtype", &retval)) {
writer->machine_info.cpu_subtype = retval;
} else {
PLCF_DEBUG("Could not retrive hw.cpusubtype: %s", strerror(errno));
}
/* Processor count */
if (plcrash_sysctl_int("hw.physicalcpu_max", &retval)) {
writer->machine_info.processor_count = retval;
} else {
PLCF_DEBUG("Could not retrive hw.physicalcpu_max: %s", strerror(errno));
}
if (plcrash_sysctl_int("hw.logicalcpu_max", &retval)) {
writer->machine_info.logical_processor_count = retval;
} else {
PLCF_DEBUG("Could not retrive hw.logicalcpu_max: %s", strerror(errno));
}
}
/*
* Check if the process is emulated. This sysctl is defined in the Universal Binary Programming Guidelines,
* Second Edition:
*
* http://developer.apple.com/legacy/mac/library/documentation/MacOSX/Conceptual/universal_binary/universal_binary.pdf
*/
{
int retval;
if (plcrash_sysctl_int("sysctl.proc_native", &retval)) {
if (retval == 0) {
writer->process_info.native = false;
} else {
writer->process_info.native = true;
}
} else {
/* If the sysctl is not available, the process can be assumed to be native. */
writer->process_info.native = true;
}
}
}
/* Fetch the OS information */
char *build = plcrash_sysctl_string("kern.osversion");
if (build == NULL) {
PLCF_DEBUG("Could not retrive kern.osversion: %s", strerror(errno));
}
plprotobuf_cbinary_data_string_init(&writer->system_info.build, build);
#if TARGET_OS_IPHONE || TARGET_OS_MAC
/* iOS, tvOS, macOS and Mac Catalyst */
{
NSProcessInfo *processInfo = [NSProcessInfo processInfo];
NSOperatingSystemVersion systemVersion = processInfo.operatingSystemVersion;
NSString *systemVersionString = [NSString stringWithFormat:@"%ld.%ld", (long)systemVersion.majorVersion, (long)systemVersion.minorVersion];
if (systemVersion.patchVersion > 0) {
systemVersionString = [systemVersionString stringByAppendingFormat:@".%ld", (long)systemVersion.patchVersion];
}
plprotobuf_cbinary_data_nsstring_init(&writer->system_info.version, systemVersionString);
}
#else
#error Unsupported Platform
#endif
/* Ensure that any signal handler has a consistent view of the above initialization. */
atomic_thread_fence(memory_order_seq_cst);
return PLCRASH_ESUCCESS;
}
/**
* Set the uncaught exception for this writer. Once set, this exception will be used to
* provide exception data for the crash log output.
*
* @warning This function is not async safe, and must be called outside of a signal handler.
*/
void plcrash_log_writer_set_exception (plcrash_log_writer_t *writer, NSException *exception) {
assert(writer->uncaught_exception.has_exception == false);
/* Save the exception data */
writer->uncaught_exception.has_exception = true;
writer->uncaught_exception.name = strdup([[exception name] UTF8String]);
writer->uncaught_exception.reason = strdup([exception reason] != nil ? [[exception reason] UTF8String] : "");
/* Save the call stack, if available */
NSArray *callStackArray = [exception callStackReturnAddresses];
if (callStackArray != nil && [callStackArray count] > 0) {
size_t count = [callStackArray count];
writer->uncaught_exception.callstack_count = count;
writer->uncaught_exception.callstack = malloc(sizeof(void *) * count);
size_t i = 0;
for (NSNumber *num in callStackArray) {
assert(i < count);
writer->uncaught_exception.callstack[i] = (void *)(uintptr_t)[num unsignedLongLongValue];
i++;
}
}
/* Ensure that any signal handler has a consistent view of the above initialization. */
atomic_thread_fence(memory_order_seq_cst);
}
/**
* Set the custom data for this writer. Once set, this information will be used to
* provide custom data for the crash log output.
*
* @warning This function is not async safe, and must be called outside of a signal handler.
*/
void plcrash_log_writer_set_custom_data (plcrash_log_writer_t *writer, NSData *custom_data) {
/* If there is already user data, delete it */
if (writer->custom_data.data) {
plprotobuf_cbinary_data_free(&writer->custom_data);
}
/* Save the user data */
if (custom_data != nil) {
plprotobuf_cbinary_data_init(&writer->custom_data, custom_data.bytes, custom_data.length);
}
}
/**
* Close the plcrash_writer_t output.
*
* @param writer Writer instance to be closed.
*/
plcrash_error_t plcrash_log_writer_close (plcrash_log_writer_t *writer) {
return PLCRASH_ESUCCESS;
}
/**
* Free any crash log writer resources.
*
* @warning This method is not async safe.
*/
void plcrash_log_writer_free (plcrash_log_writer_t *writer) {
/* Free the app info */
plprotobuf_cbinary_data_free(&writer->application_info.app_identifier);
plprotobuf_cbinary_data_free(&writer->application_info.app_version);
plprotobuf_cbinary_data_free(&writer->application_info.app_marketing_version);
/* Free the process info */
plprotobuf_cbinary_data_free(&writer->process_info.process_name);
plprotobuf_cbinary_data_free(&writer->process_info.process_path);
plprotobuf_cbinary_data_free(&writer->process_info.parent_process_name);
/* Free the system info */
plprotobuf_cbinary_data_free(&writer->system_info.version);
plprotobuf_cbinary_data_free(&writer->system_info.build);
/* Free the machine info */
plprotobuf_cbinary_data_free(&writer->machine_info.model);
/* Free the exception data */
if (writer->uncaught_exception.has_exception) {
if (writer->uncaught_exception.name != NULL)
free(writer->uncaught_exception.name);
if (writer->uncaught_exception.reason != NULL)
free(writer->uncaught_exception.reason);
if (writer->uncaught_exception.callstack != NULL)
free(writer->uncaught_exception.callstack);
}
if (writer->custom_data.data) {
plprotobuf_cbinary_data_free(&writer->custom_data);
}
}
/**
* @internal
*
* Write the system info message.
*
* @param file Output file
* @param timestamp Timestamp to use (seconds since epoch). Must be same across calls, as varint encoding.
*/
static size_t plcrash_writer_write_system_info (plcrash_async_file_t *file, plcrash_log_writer_t *writer, int64_t timestamp) {
size_t rv = 0;
uint32_t enumval;
/* OS */
enumval = PLCrashReportHostOperatingSystem;
rv += plcrash_writer_pack(file, PLCRASH_PROTO_SYSTEM_INFO_OS_ID, PLPROTOBUF_C_TYPE_ENUM, &enumval);
/* OS Version */
rv += plcrash_writer_pack(file, PLCRASH_PROTO_SYSTEM_INFO_OS_VERSION_ID, PLPROTOBUF_C_TYPE_BYTES, &writer->system_info.version);
/* OS Build */
rv += plcrash_writer_pack(file, PLCRASH_PROTO_SYSTEM_INFO_OS_BUILD_ID, PLPROTOBUF_C_TYPE_BYTES, &writer->system_info.build);
/* Machine type */
enumval = PLCrashReportHostArchitecture;
rv += plcrash_writer_pack(file, PLCRASH_PROTO_SYSTEM_INFO_ARCHITECTURE_TYPE_ID, PLPROTOBUF_C_TYPE_ENUM, &enumval);
/* Timestamp */
rv += plcrash_writer_pack(file, PLCRASH_PROTO_SYSTEM_INFO_TIMESTAMP_ID, PLPROTOBUF_C_TYPE_INT64, ×tamp);
return rv;
}
/**
* @internal
*
* Write the processor info message.
*
* @param file Output file
* @param cpu_type The Mach CPU type.
* @param cpu_subtype The Mach CPU subtype
*/
static size_t plcrash_writer_write_processor_info (plcrash_async_file_t *file, uint64_t cpu_type, uint64_t cpu_subtype) {
size_t rv = 0;
uint32_t enumval;
/* Encoding */
enumval = PLCrashReportProcessorTypeEncodingMach;
rv += plcrash_writer_pack(file, PLCRASH_PROTO_PROCESSOR_ENCODING_ID, PLPROTOBUF_C_TYPE_ENUM, &enumval);
/* Type */
rv += plcrash_writer_pack(file, PLCRASH_PROTO_PROCESSOR_TYPE_ID, PLPROTOBUF_C_TYPE_UINT64, &cpu_type);
/* Subtype */
rv += plcrash_writer_pack(file, PLCRASH_PROTO_PROCESSOR_SUBTYPE_ID, PLPROTOBUF_C_TYPE_UINT64, &cpu_subtype);
return rv;
}
/**
* @internal
*
* Write the machine info message.
*
* @param file Output file
*/
static size_t plcrash_writer_write_machine_info (plcrash_async_file_t *file, plcrash_log_writer_t *writer) {
size_t rv = 0;
/* Model */
if (writer->machine_info.model.data != NULL)
rv += plcrash_writer_pack(file, PLCRASH_PROTO_MACHINE_INFO_MODEL_ID, PLPROTOBUF_C_TYPE_BYTES, &writer->machine_info.model);
/* Processor */
{
uint32_t size;
/* Determine size */
size = (uint32_t) plcrash_writer_write_processor_info(NULL, writer->machine_info.cpu_type, writer->machine_info.cpu_subtype);
/* Write message */
rv += plcrash_writer_pack(file, PLCRASH_PROTO_MACHINE_INFO_PROCESSOR_ID, PLPROTOBUF_C_TYPE_MESSAGE, &size);
rv += plcrash_writer_write_processor_info(file, writer->machine_info.cpu_type, writer->machine_info.cpu_subtype);
}
/* Physical Processor Count */
rv += plcrash_writer_pack(file, PLCRASH_PROTO_MACHINE_INFO_PROCESSOR_COUNT_ID, PLPROTOBUF_C_TYPE_UINT32, &writer->machine_info.processor_count);
/* Logical Processor Count */
rv += plcrash_writer_pack(file, PLCRASH_PROTO_MACHINE_INFO_LOGICAL_PROCESSOR_COUNT_ID, PLPROTOBUF_C_TYPE_UINT32, &writer->machine_info.logical_processor_count);
return rv;
}
/**
* @internal
*
* Write the app info message.
*
* @param file Output file
* @param app_identifier Application identifier
* @param app_version Application version
* @param app_marketing_version Application marketing version
*/
static size_t plcrash_writer_write_app_info (plcrash_async_file_t *file,
PLProtobufCBinaryData *app_identifier,
PLProtobufCBinaryData *app_version,
PLProtobufCBinaryData *app_marketing_version) {
size_t rv = 0;
/* App identifier */
rv += plcrash_writer_pack(file, PLCRASH_PROTO_APP_INFO_APP_IDENTIFIER_ID, PLPROTOBUF_C_TYPE_BYTES, app_identifier);
/* App version */
rv += plcrash_writer_pack(file, PLCRASH_PROTO_APP_INFO_APP_VERSION_ID, PLPROTOBUF_C_TYPE_BYTES, app_version);
/* App marketing version */
if (app_marketing_version != NULL)
rv += plcrash_writer_pack(file, PLCRASH_PROTO_APP_INFO_APP_MARKETING_VERSION_ID, PLPROTOBUF_C_TYPE_BYTES, app_marketing_version);
return rv;
}
/**
* @internal
*
* Write the process info message.
*
* @param file Output file
* @param process_name Process name, or NULL if unavailable.
* @param process_id Process ID
* @param process_path Process path, or NULL if unavailable.
* @param parent_process_name Parent process name, or NULL if unavailable.
* @param parent_process_id Parent process ID
* @param native If false, process is running under emulation.
* @param start_time The start time of the process.
*/
static size_t plcrash_writer_write_process_info (plcrash_async_file_t *file, PLProtobufCBinaryData *process_name,
const pid_t process_id, PLProtobufCBinaryData *process_path,
PLProtobufCBinaryData *parent_process_name, const pid_t parent_process_id,
bool native, time_t start_time)
{
size_t rv = 0;
uint64_t tval;
/*
* In the current crash reporter serialization format, pid values are serialized as unsigned 32-bit integers. This
* conforms with the actual implementation of pid_t on both 32-bit and 64-bit Darwin systems. To conform with
* SuSV3, however, the values should be encoded as signed integers; the actual width of the type being implementation
* defined.
*
* To maintain compatibility with existing report readers the values remain encoded as unsigned 32-bit integers,
* but should be updated to int64 values in future major revision of the data format.
*/
uint32_t pidval;
/* Process name */
if (process_name != NULL)
rv += plcrash_writer_pack(file, PLCRASH_PROTO_PROCESS_INFO_PROCESS_NAME_ID, PLPROTOBUF_C_TYPE_BYTES, process_name);
/* Process ID */
pidval = process_id;
rv += plcrash_writer_pack(file, PLCRASH_PROTO_PROCESS_INFO_PROCESS_ID_ID, PLPROTOBUF_C_TYPE_UINT32, &pidval);
/* Process path */
if (process_path != NULL)
rv += plcrash_writer_pack(file, PLCRASH_PROTO_PROCESS_INFO_PROCESS_PATH_ID, PLPROTOBUF_C_TYPE_BYTES, process_path);
/* Parent process name */
if (parent_process_name != NULL)
rv += plcrash_writer_pack(file, PLCRASH_PROTO_PROCESS_INFO_PARENT_PROCESS_NAME_ID, PLPROTOBUF_C_TYPE_BYTES, parent_process_name);
/* Parent process ID */
pidval = parent_process_id;
rv += plcrash_writer_pack(file, PLCRASH_PROTO_PROCESS_INFO_PARENT_PROCESS_ID_ID, PLPROTOBUF_C_TYPE_UINT32, &pidval);
/* Native process. */
rv += plcrash_writer_pack(file, PLCRASH_PROTO_PROCESS_INFO_NATIVE_ID, PLPROTOBUF_C_TYPE_BOOL, &native);
/* Start time */
tval = start_time;
rv += plcrash_writer_pack(file, PLCRASH_PROTO_PROCESS_INFO_START_TIME_ID, PLPROTOBUF_C_TYPE_UINT64, &tval);
return rv;
}
/**
* @internal
*
* Write a single register.
*
* @param file Output file
* @param regname The register to write's name.
* @param regval The register to write's value.
*/
static size_t plcrash_writer_write_thread_register (plcrash_async_file_t *file, const char *regname, plcrash_greg_t regval) {
uint64_t uint64val;
size_t rv = 0;
/* Write the name */
rv += plcrash_writer_pack(file, PLCRASH_PROTO_THREAD_REGISTER_NAME_ID, PLPROTOBUF_C_TYPE_STRING, regname);
/* Write the value */
uint64val = regval;
rv += plcrash_writer_pack(file, PLCRASH_PROTO_THREAD_REGISTER_VALUE_ID, PLPROTOBUF_C_TYPE_UINT64, &uint64val);
return rv;
}
/**
* @internal
*
* Write all thread backtrace register messages
*
* @param file Output file
* @param task The task from which @a uap was derived. All memory accesses will be mapped from this task.
* @param cursor The cursor from which to acquire frame registers.
*/
static size_t plcrash_writer_write_thread_registers (plcrash_async_file_t *file, task_t task, plframe_cursor_t *cursor) {
plframe_error_t frame_err;
uint32_t regCount = (uint32_t) plframe_cursor_get_regcount(cursor);
size_t rv = 0;
/* Write out register messages */
for (int i = 0; i < regCount; i++) {
plcrash_greg_t regVal;
const char *regname;
uint32_t msgsize;
/* Fetch the register value */
if ((frame_err = plframe_cursor_get_reg(cursor, i, ®Val)) != PLFRAME_ESUCCESS) {
// Should never happen
PLCF_DEBUG("Could not fetch register %i value: %s", i, plframe_strerror(frame_err));
regVal = 0;
}
/* Fetch the register name */
regname = plframe_cursor_get_regname(cursor, i);
/* Get the register message size */
msgsize = (uint32_t) plcrash_writer_write_thread_register(NULL, regname, regVal);
/* Write the header and message */
rv += plcrash_writer_pack(file, PLCRASH_PROTO_THREAD_REGISTERS_ID, PLPROTOBUF_C_TYPE_MESSAGE, &msgsize);
rv += plcrash_writer_write_thread_register(file, regname, regVal);
}
return rv;
}
/**
* @internal
*
* Write a symbol
*
* @param file Output file
* @param name The symbol name
* @param start_address The symbol start address
*/
static size_t plcrash_writer_write_symbol (plcrash_async_file_t *file, const char *name, uint64_t start_address) {
size_t rv = 0;
/* name */
rv += plcrash_writer_pack(file, PLCRASH_PROTO_SYMBOL_NAME, PLPROTOBUF_C_TYPE_STRING, name);
/* start_address */
rv += plcrash_writer_pack(file, PLCRASH_PROTO_SYMBOL_START_ADDRESS, PLPROTOBUF_C_TYPE_UINT64, &start_address);
return rv;
}
/**
* @internal
* Symbol lookup callback context
*/
struct pl_symbol_cb_ctx {
/** File to use for writing out a symbol entry. May be NULL. */
plcrash_async_file_t *file;
/** Size of the symbol entry, to be written by the callback function upon writing an entry. */
uint32_t msgsize;
};
/**
* @internal
*
* pl_async_macho_found_symbol_cb callback implementation. Writes the result to the file available via @a ctx,
* which must be a valid pl_symbol_cb_ctx structure.
*/
static void plcrash_writer_write_thread_frame_symbol_cb (pl_vm_address_t address, const char *name, void *ctx) {
struct pl_symbol_cb_ctx *cb_ctx = ctx;
cb_ctx->msgsize = (uint32_t) plcrash_writer_write_symbol(cb_ctx->file, name, address);
}
/**
* @internal
*
* Write a thread backtrace frame
*
* @param file Output file
* @param pcval The frame PC value.
*/
static size_t plcrash_writer_write_thread_frame (plcrash_async_file_t *file, plcrash_log_writer_t *writer, uint64_t pcval, plcrash_async_image_list_t *image_list, plcrash_async_symbol_cache_t *findContext) {
size_t rv = 0;
rv += plcrash_writer_pack(file, PLCRASH_PROTO_THREAD_FRAME_PC_ID, PLPROTOBUF_C_TYPE_UINT64, &pcval);
plcrash_async_image_list_set_reading(image_list, true);
plcrash_async_image_t *image = plcrash_async_image_containing_address(image_list, (pl_vm_address_t) pcval);
if (image != NULL && writer->symbol_strategy != PLCRASH_ASYNC_SYMBOL_STRATEGY_NONE) {
struct pl_symbol_cb_ctx ctx;
plcrash_error_t ret;
/* Get the symbol message size. If the symbol can not be found, our callback will not be called. If the symbol is found,
* our callback is called and PLCRASH_ESUCCESS is returned. */
ctx.file = NULL;
ctx.msgsize = 0x0;
ret = plcrash_async_find_symbol(&image->macho_image, writer->symbol_strategy, findContext, (pl_vm_address_t) pcval, plcrash_writer_write_thread_frame_symbol_cb, &ctx);
if (ret == PLCRASH_ESUCCESS) {
/* Write the header and message */
rv += plcrash_writer_pack(file, PLCRASH_PROTO_THREAD_FRAME_SYMBOL_ID, PLPROTOBUF_C_TYPE_MESSAGE, &ctx.msgsize);
ctx.file = file;
ret = plcrash_async_find_symbol(&image->macho_image, writer->symbol_strategy, findContext, (pl_vm_address_t) pcval, plcrash_writer_write_thread_frame_symbol_cb, &ctx);
if (ret == PLCRASH_ESUCCESS) {
rv += ctx.msgsize;
} else {
/* This should not happen, but it would be very confusing if it did and nothing was logged. */
PLCF_DEBUG("Fetching the symbol unexpectedly failed during the second call");
}
}
}
plcrash_async_image_list_set_reading(image_list, false);
return rv;
}
/**
* @internal
*
* Write a thread message
*
* @param file Output file
* @param task The task in which @a thread is executing.
* @param thread Thread for which we'll output data.
* @param thread_number The thread's index number.
* @param thread_ctx Thread state to use for stack walking. If NULL, the thread state will be fetched from @a thread. If
* @a thread is the currently executing thread, <em>must</em> be non-NULL.
* @param image_list The Mach-O image list.
* @param findContext Symbol lookup cache.
* @param crashed If true, mark this as a crashed thread.
*/
static size_t plcrash_writer_write_thread (plcrash_async_file_t *file,
plcrash_log_writer_t *writer,
task_t task,
thread_t thread,
uint32_t thread_number,
plcrash_async_thread_state_t *thread_ctx,
plcrash_async_image_list_t *image_list,
plcrash_async_symbol_cache_t *findContext,
bool crashed)
{
size_t rv = 0;
plframe_cursor_t cursor;
plframe_error_t ferr;
/* A context must be supplied when walking the current thread */
PLCF_ASSERT(task != mach_task_self() || thread_ctx != NULL || thread != pl_mach_thread_self());
/* Write the required elements first; fatal errors may occur below, in which case we need to have
* written out required elements before returning. */
{
/* Write the thread ID */
rv += plcrash_writer_pack(file, PLCRASH_PROTO_THREAD_THREAD_NUMBER_ID, PLPROTOBUF_C_TYPE_UINT32, &thread_number);
/* Note crashed status */
rv += plcrash_writer_pack(file, PLCRASH_PROTO_THREAD_CRASHED_ID, PLPROTOBUF_C_TYPE_BOOL, &crashed);
}
/* Write out the stack frames. */
{
/* Set up the frame cursor. */
{
/* Use the provided context if available, otherwise initialize a new thread context
* from the target thread's state. */
plcrash_async_thread_state_t cursor_thr_state;
if (thread_ctx) {
cursor_thr_state = *thread_ctx;
} else {
plcrash_async_thread_state_mach_thread_init(&cursor_thr_state, thread);
}
/* Initialize the cursor */
ferr = plframe_cursor_init(&cursor, task, &cursor_thr_state, image_list);
if (ferr != PLFRAME_ESUCCESS) {
PLCF_DEBUG("An error occured initializing the frame cursor: %s", plframe_strerror(ferr));
return rv;
}
}
/* Walk the stack, limiting the total number of frames that are output. */
uint32_t frame_count = 0;
while ((ferr = plframe_cursor_next(&cursor)) == PLFRAME_ESUCCESS && frame_count < MAX_THREAD_FRAMES) {
uint32_t frame_size;
/* On the first frame, dump registers for the crashed thread */
if (frame_count == 0 && crashed) {
rv += plcrash_writer_write_thread_registers(file, task, &cursor);
}
/* Fetch the PC value */
plcrash_greg_t pc = 0;
if ((ferr = plframe_cursor_get_reg(&cursor, PLCRASH_REG_IP, &pc)) != PLFRAME_ESUCCESS) {
PLCF_DEBUG("Could not retrieve frame PC register: %s", plframe_strerror(ferr));
break;
}
/* Determine the size */
frame_size = (uint32_t) plcrash_writer_write_thread_frame(NULL, writer, pc, image_list, findContext);
rv += plcrash_writer_pack(file, PLCRASH_PROTO_THREAD_FRAMES_ID, PLPROTOBUF_C_TYPE_MESSAGE, &frame_size);
rv += plcrash_writer_write_thread_frame(file, writer, pc, image_list, findContext);
frame_count++;
}
/* Did we reach the end successfully? */
if (ferr != PLFRAME_ENOFRAME) {
/* This is non-fatal, and in some circumstances -could- be caused by reaching the end of the stack if the
* final frame pointer is not NULL. */
PLCF_DEBUG("Terminated stack walking early: %s", plframe_strerror(ferr));
}
}
plframe_cursor_free(&cursor);
return rv;
}
/**
* @internal
*
* Write a binary image frame
*
* @param file Output file
* @param image Mach-O image.
*/
static size_t plcrash_writer_write_binary_image (plcrash_async_file_t *file, plcrash_async_macho_t *image) {
size_t rv = 0;
/* Fetch the CPU types. Note that the wire format represents these as 64-bit unsigned integers.
* We explicitly cast to an equivalently sized unsigned type to prevent improper sign extension. */
uint64_t cpu_type = (uint32_t) image->byteorder->swap32(image->header.cputype);
uint64_t cpu_subtype = (uint32_t) image->byteorder->swap32(image->header.cpusubtype);
/* Text segment size */
uint64_t mach_size = image->text_size;
rv += plcrash_writer_pack(file, PLCRASH_PROTO_BINARY_IMAGE_SIZE_ID, PLPROTOBUF_C_TYPE_UINT64, &mach_size);
/* Base address */
{
uintptr_t base_addr;
uint64_t u64;
base_addr = (uintptr_t) image->header_addr;
u64 = base_addr;
rv += plcrash_writer_pack(file, PLCRASH_PROTO_BINARY_IMAGE_ADDR_ID, PLPROTOBUF_C_TYPE_UINT64, &u64);
}
/* Name */
rv += plcrash_writer_pack(file, PLCRASH_PROTO_BINARY_IMAGE_NAME_ID, PLPROTOBUF_C_TYPE_STRING, image->name);
/* UUID */
struct uuid_command *uuid;
uuid = plcrash_async_macho_find_command(image, LC_UUID);
if (uuid != NULL) {
PLProtobufCBinaryData binary;
/* Write the 128-bit UUID */
binary.len = sizeof(uuid->uuid);
binary.data = uuid->uuid;
rv += plcrash_writer_pack(file, PLCRASH_PROTO_BINARY_IMAGE_UUID_ID, PLPROTOBUF_C_TYPE_BYTES, &binary);
}
/* Get the processor message size */
uint32_t msgsize = (uint32_t) plcrash_writer_write_processor_info(NULL, cpu_type, cpu_subtype);
/* Write the header and message */
rv += plcrash_writer_pack(file, PLCRASH_PROTO_BINARY_IMAGE_CODE_TYPE_ID, PLPROTOBUF_C_TYPE_MESSAGE, &msgsize);
rv += plcrash_writer_write_processor_info(file, cpu_type, cpu_subtype);
return rv;
}
/**
* @internal
*
* Write the crash Exception message
*
* @param file Output file
* @param writer Writer containing exception data
*/
static size_t plcrash_writer_write_exception (plcrash_async_file_t *file, plcrash_log_writer_t *writer, plcrash_async_image_list_t *image_list, plcrash_async_symbol_cache_t *findContext) {
size_t rv = 0;
/* Write the name and reason */
assert(writer->uncaught_exception.has_exception);
rv += plcrash_writer_pack(file, PLCRASH_PROTO_EXCEPTION_NAME_ID, PLPROTOBUF_C_TYPE_STRING, writer->uncaught_exception.name);
rv += plcrash_writer_pack(file, PLCRASH_PROTO_EXCEPTION_REASON_ID, PLPROTOBUF_C_TYPE_STRING, writer->uncaught_exception.reason);
/* Write the stack frames, if any */
uint32_t frame_count = 0;
for (size_t i = 0; i < writer->uncaught_exception.callstack_count && frame_count < MAX_THREAD_FRAMES; i++) {
uint64_t pc = (uint64_t)(uintptr_t) writer->uncaught_exception.callstack[i];
/* Determine the size */
uint32_t frame_size = (uint32_t) plcrash_writer_write_thread_frame(NULL, writer, pc, image_list, findContext);
rv += plcrash_writer_pack(file, PLCRASH_PROTO_EXCEPTION_FRAMES_ID, PLPROTOBUF_C_TYPE_MESSAGE, &frame_size);
rv += plcrash_writer_write_thread_frame(file, writer, pc, image_list, findContext);
frame_count++;
}
return rv;
}
/**
* @internal
*
* Write the crash signal's mach exception info.
*
* @param file Output file
* @param siginfo The signal information
*/
static size_t plcrash_writer_write_mach_signal (plcrash_async_file_t *file, plcrash_log_mach_signal_info_t *siginfo) {
size_t rv = 0;
/* Type */
uint64_t type = siginfo->type;
rv += plcrash_writer_pack(file, PLCRASH_PROTO_SIGNAL_MACH_EXCEPTION_TYPE_ID, PLPROTOBUF_C_TYPE_UINT64, &type);
/* Code(s) */
for (mach_msg_type_number_t i = 0; i < siginfo->code_count; i++) {
uint64_t code = siginfo->code[i];
rv += plcrash_writer_pack(file, PLCRASH_PROTO_SIGNAL_MACH_EXCEPTION_CODES_ID, PLPROTOBUF_C_TYPE_UINT64, &code);
}
return rv;
}
/**
* @internal
*
* Write the crash signal message
*
* @param file Output file
* @param siginfo The signal information
*/
static size_t plcrash_writer_write_signal (plcrash_async_file_t *file, plcrash_log_signal_info_t *siginfo) {
size_t rv = 0;
/* BSD signal info is always required in the current report format; this restriction will be lifted
* once we switch to the 2.0 format. */
PLCF_ASSERT(siginfo->bsd_info != NULL);
/* Fetch the signal name */
char name_buf[10];
const char *name;
if ((name = plcrash_async_signal_signame(siginfo->bsd_info->signo)) == NULL) {
PLCF_DEBUG("Warning -- unhandled signal number (signo=%d). This is a bug.", siginfo->bsd_info->signo);
snprintf(name_buf, sizeof(name_buf), "#%d", siginfo->bsd_info->signo);
name = name_buf;
}
/* Fetch the signal code string */
char code_buf[10];
const char *code;
if ((code = plcrash_async_signal_sigcode(siginfo->bsd_info->signo, siginfo->bsd_info->code)) == NULL) {
PLCF_DEBUG("Warning -- unhandled signal sicode (signo=%d, code=%d). This is a bug.", siginfo->bsd_info->signo, siginfo->bsd_info->code);
snprintf(code_buf, sizeof(code_buf), "#%d", siginfo->bsd_info->code);
code = code_buf;
}
/* Address value */
uint64_t addr = (uintptr_t) siginfo->bsd_info->address;
/* Write it out */
rv += plcrash_writer_pack(file, PLCRASH_PROTO_SIGNAL_NAME_ID, PLPROTOBUF_C_TYPE_STRING, name);
rv += plcrash_writer_pack(file, PLCRASH_PROTO_SIGNAL_CODE_ID, PLPROTOBUF_C_TYPE_STRING, code);
rv += plcrash_writer_pack(file, PLCRASH_PROTO_SIGNAL_ADDRESS_ID, PLPROTOBUF_C_TYPE_UINT64, &addr);
/* Mach exception info */
if (siginfo->mach_info != NULL) {
uint32_t size;
/* Determine size */
size = (uint32_t) plcrash_writer_write_mach_signal(NULL, siginfo->mach_info);
/* Write message */
rv += plcrash_writer_pack(file, PLCRASH_PROTO_SIGNAL_MACH_EXCEPTION_ID, PLPROTOBUF_C_TYPE_MESSAGE, &size);
rv += plcrash_writer_write_mach_signal(file, siginfo->mach_info);
}
return rv;
}
/**
* @internal
*
* Write the report info message
*
* @param file Output file
* @param writer Writer containing report data
*/
static size_t plcrash_writer_write_report_info (plcrash_async_file_t *file, plcrash_log_writer_t *writer) {
size_t rv = 0;
/* Note crashed status */
rv += plcrash_writer_pack(file, PLCRASH_PROTO_REPORT_INFO_USER_REQUESTED_ID, PLPROTOBUF_C_TYPE_BOOL, &writer->report_info.user_requested);
/* Write the 128-bit UUID */
PLProtobufCBinaryData uuid_bin;
uuid_bin.len = sizeof(writer->report_info.uuid_bytes);
uuid_bin.data = &writer->report_info.uuid_bytes;
rv += plcrash_writer_pack(file, PLCRASH_PROTO_REPORT_INFO_UUID_ID, PLPROTOBUF_C_TYPE_BYTES, &uuid_bin);
return rv;
}
/**
* Write the crash report. All other running threads are suspended while the crash report is generated.
*
* @param writer The writer context.
* @param crashed_thread The crashed thread.
* @param image_list The current list of loaded binary images.
* @param file The output file.
* @param siginfo Signal information.
* @param current_state If non-NULL, the given thread state will be used when walking the current thread. The state must remain
* valid until this function returns. Generally, this state will be generated by a signal handler, or via a
* context-generating trampoline such as plcrash_log_writer_write_curthread(). If NULL, a thread dump for the current
* thread will not be written. If @a crashed_thread is the current thread (as returned by mach_thread_self()), this
* value <em>must</em> be provided.
*/
plcrash_error_t plcrash_log_writer_write (plcrash_log_writer_t *writer,
thread_t crashed_thread,
plcrash_async_image_list_t *image_list,
plcrash_async_file_t *file,
plcrash_log_signal_info_t *siginfo,
plcrash_async_thread_state_t *current_state)
{
thread_act_array_t threads;
mach_msg_type_number_t thread_count;
/* A context must be supplied if the current thread is marked as the crashed thread; otherwise,
* the thread's stack can not be safely walked. */
PLCF_ASSERT(pl_mach_thread_self() != crashed_thread || current_state != NULL);
/* Get a list of all threads */
if (task_threads(mach_task_self(), &threads, &thread_count) != KERN_SUCCESS) {
PLCF_DEBUG("Fetching thread list failed");
thread_count = 0;
}
/* Suspend all but the current thread. */
for (mach_msg_type_number_t i = 0; i < thread_count; i++) {
if (threads[i] != pl_mach_thread_self())
thread_suspend(threads[i]);
}
/* Set up a symbol-finding context. */
plcrash_async_symbol_cache_t findContext;
plcrash_error_t err = plcrash_async_symbol_cache_init(&findContext);
/* Abort if it failed, although that should never actually happen, ever. */
if (err != PLCRASH_ESUCCESS)
return err;
/* Write the file header */
{
uint8_t version = PLCRASH_REPORT_FILE_VERSION;
/* Write the magic string (with no trailing NULL) and the version number */
plcrash_async_file_write(file, PLCRASH_REPORT_FILE_MAGIC, strlen(PLCRASH_REPORT_FILE_MAGIC));
plcrash_async_file_write(file, &version, sizeof(version));
}
/* Report Info */
{
uint32_t size;
/* Determine size */
size = (uint32_t) plcrash_writer_write_report_info(NULL, writer);
/* Write message */
plcrash_writer_pack(file, PLCRASH_PROTO_REPORT_INFO_ID, PLPROTOBUF_C_TYPE_MESSAGE, &size);
plcrash_writer_write_report_info(file, writer);
}
/* System Info */
{
time_t timestamp;
uint32_t size;
/* Must stay the same across both calls, so get the timestamp here */
if (time(×tamp) == (time_t)-1) {
PLCF_DEBUG("Failed to fetch timestamp: %s", strerror(errno));
timestamp = 0;
}
/* Determine size */
size = (uint32_t) plcrash_writer_write_system_info(NULL, writer, timestamp);
/* Write message */
plcrash_writer_pack(file, PLCRASH_PROTO_SYSTEM_INFO_ID, PLPROTOBUF_C_TYPE_MESSAGE, &size);
plcrash_writer_write_system_info(file, writer, timestamp);
}
/* Machine Info */
{
uint32_t size;
/* Determine size */
size = (uint32_t) plcrash_writer_write_machine_info(NULL, writer);
/* Write message */
plcrash_writer_pack(file, PLCRASH_PROTO_MACHINE_INFO_ID, PLPROTOBUF_C_TYPE_MESSAGE, &size);
plcrash_writer_write_machine_info(file, writer);
}
/* App info */
{
uint32_t size;
/* Determine size */
size = (uint32_t) plcrash_writer_write_app_info(NULL, &writer->application_info.app_identifier, &writer->application_info.app_version, &writer->application_info.app_marketing_version);
/* Write message */
plcrash_writer_pack(file, PLCRASH_PROTO_APP_INFO_ID, PLPROTOBUF_C_TYPE_MESSAGE, &size);
plcrash_writer_write_app_info(file, &writer->application_info.app_identifier, &writer->application_info.app_version, &writer->application_info.app_marketing_version);
}
/* Process info */
{
uint32_t size;
/* Determine size */
size = (uint32_t) plcrash_writer_write_process_info(NULL, &writer->process_info.process_name, writer->process_info.process_id,
&writer->process_info.process_path, &writer->process_info.parent_process_name,
writer->process_info.parent_process_id, writer->process_info.native,
writer->process_info.start_time);
/* Write message */
plcrash_writer_pack(file, PLCRASH_PROTO_PROCESS_INFO_ID, PLPROTOBUF_C_TYPE_MESSAGE, &size);
plcrash_writer_write_process_info(file, &writer->process_info.process_name, writer->process_info.process_id,
&writer->process_info.process_path, &writer->process_info.parent_process_name,
writer->process_info.parent_process_id, writer->process_info.native,
writer->process_info.start_time);
}
/* Threads */
uint32_t thread_number = 0;
for (mach_msg_type_number_t i = 0; i < thread_count; i++) {
thread_t thread = threads[i];
plcrash_async_thread_state_t *thr_ctx = NULL;
bool crashed = false;
uint32_t size;
/* If executing on the target thread, we need to a valid context to walk */
if (pl_mach_thread_self() == thread) {
/* Can't log a report for the current thread without a valid context. */
if (current_state == NULL)
continue;
thr_ctx = current_state;
}
/* Check if this is the crashed thread */
if (crashed_thread == thread) {
crashed = true;
}
/* Determine the size */
size = (uint32_t) plcrash_writer_write_thread(NULL, writer, mach_task_self(), thread, thread_number, thr_ctx, image_list, &findContext, crashed);
/* Write message */
plcrash_writer_pack(file, PLCRASH_PROTO_THREADS_ID, PLPROTOBUF_C_TYPE_MESSAGE, &size);
plcrash_writer_write_thread(file, writer, mach_task_self(), thread, thread_number, thr_ctx, image_list, &findContext, crashed);
thread_number++;
}
/* Binary Images */
plcrash_async_image_list_set_reading(image_list, true);
plcrash_async_image_t *image = NULL;
while ((image = plcrash_async_image_list_next(image_list, image)) != NULL) {
uint32_t size;
/* Calculate the message size */
size = (uint32_t) plcrash_writer_write_binary_image(NULL, &image->macho_image);
plcrash_writer_pack(file, PLCRASH_PROTO_BINARY_IMAGES_ID, PLPROTOBUF_C_TYPE_MESSAGE, &size);
plcrash_writer_write_binary_image(file, &image->macho_image);
}
plcrash_async_image_list_set_reading(image_list, false);
/* Exception */
if (writer->uncaught_exception.has_exception) {
uint32_t size;
/* Calculate the message size */
size = (uint32_t) plcrash_writer_write_exception(NULL, writer, image_list, &findContext);
plcrash_writer_pack(file, PLCRASH_PROTO_EXCEPTION_ID, PLPROTOBUF_C_TYPE_MESSAGE, &size);
plcrash_writer_write_exception(file, writer, image_list, &findContext);
}
/* Signal */
{
uint32_t size;
/* Calculate the message size */
size = (uint32_t) plcrash_writer_write_signal(NULL, siginfo);
plcrash_writer_pack(file, PLCRASH_PROTO_SIGNAL_ID, PLPROTOBUF_C_TYPE_MESSAGE, &size);
plcrash_writer_write_signal(file, siginfo);
}
/* Custom data */
if (writer->custom_data.data) {
plcrash_writer_pack(file, PLCRASH_PROTO_CUSTOM_DATA_ID, PLPROTOBUF_C_TYPE_BYTES, &writer->custom_data);
}
plcrash_async_symbol_cache_free(&findContext);
/* Clean up the thread array */
for (mach_msg_type_number_t i = 0; i < thread_count; i++) {
if (threads[i] != pl_mach_thread_self())
thread_resume(threads[i]);
mach_port_deallocate(mach_task_self(), threads[i]);
}
vm_deallocate(mach_task_self(), (vm_address_t)threads, sizeof(thread_t) * thread_count);
return PLCRASH_ESUCCESS;
}
/*
* @} plcrash_log_writer
*/