#include <dispatch/dispatch.h>
#include <uuid/uuid.h>
#include <sys/types.h>
#include <sys/sysctl.h>
#include <mach-o/loader.h>
#include <mach-o/fat.h>
#include <mach-o/arch.h>
#include <mach-o/getsect.h>
#include <pthread.h>
#include <sys/types.h>
#include <execinfo.h>
#include <stdio.h>
#include <dlfcn.h>
#include <asl.h>
#include <errno.h>
#include <pthread.h>
#include "assumes.h"
#define OSX_ASSUME_LOG_REDIRECT_SECT_NAME "__osx_log_func"
#define osx_atomic_cmpxchg(p, o, n) __sync_bool_compare_and_swap((p), (o), (n))
static bool _osx_should_abort_on_assumes = false;
static const char *
_osx_basename(const char *p)
{
return ((strrchr(p, '/') ? : p - 1) + 1);
}
static void
_osx_get_build(char *build, size_t sz)
{
int mib[] = { CTL_KERN, KERN_OSVERSION };
size_t oldsz = sz;
int r = sysctl(mib, 2, build, &sz, NULL, 0);
if (r == 0 && sz == 1) {
(void)strlcpy(build, "99Z999", oldsz);
}
}
static void
_osx_get_image_uuid(void *hdr, uuid_t uuid)
{
#if __LP64__
struct mach_header_64 *hdr32or64 = (struct mach_header_64 *)hdr;
#else
struct mach_header *hdr32or64 = (struct mach_header *)hdr;
#endif
size_t i = 0;
size_t next = sizeof(*hdr32or64);
struct load_command *cur = NULL;
for (i = 0; i < hdr32or64->ncmds; i++) {
cur = (struct load_command *)((uintptr_t)hdr32or64 + next);
if (cur->cmd == LC_UUID) {
struct uuid_command *cmd = (struct uuid_command *)cur;
uuid_copy(uuid, cmd->uuid);
break;
}
next += cur->cmdsize;
}
if (i == hdr32or64->ncmds) {
uuid_clear(uuid);
}
}
static void
_osx_abort_on_assumes_once(void)
{
char bootargs[2048];
size_t len = sizeof(bootargs) - 1;
if (sysctlbyname("kern.bootargs", bootargs, &len, NULL, 0) == 0) {
if (strnstr(bootargs, "-osx_assumes_fatal", len)) {
_osx_should_abort_on_assumes = true;
}
}
}
static bool
_osx_abort_on_assumes(void)
{
static pthread_once_t once = PTHREAD_ONCE_INIT;
bool result = false;
if (getpid() != 1) {
if (getenv("OSX_ASSUMES_FATAL")) {
result = true;
} else {
pthread_once(&once, _osx_abort_on_assumes_once);
result = _osx_should_abort_on_assumes;
}
} else {
if (getenv("OSX_ASSUMES_FATAL_PID1")) {
result = true;
}
}
return result;
}
#if __LP64__
static osx_redirect_t
_osx_find_log_redirect_func(struct mach_header_64 *hdr)
{
osx_redirect_t result = NULL;
char name[128];
unsigned long size = 0;
uint8_t *data = getsectiondata(hdr, "__TEXT", OSX_ASSUME_LOG_REDIRECT_SECT_NAME, &size);
if (data && size < sizeof(name) - 2) {
(void)strlcpy(name, (const char *)data, size + 1);
result = dlsym(RTLD_DEFAULT, name);
}
return result;
}
#else
static osx_redirect_t
_osx_find_log_redirect_func(struct mach_header *hdr)
{
osx_redirect_t result = NULL;
char name[128];
unsigned long size = 0;
uint8_t *data = getsectiondata(hdr, "__TEXT", OSX_ASSUME_LOG_REDIRECT_SECT_NAME, &size);
if (data && size < sizeof(name) - 2) {
(void)strlcpy(name, (const char *)data, size + 1);
result = dlsym(RTLD_DEFAULT, name);
}
return result;
}
#endif
static bool
_osx_log_redirect(void *hdr, const char *msg)
{
bool result = false;
osx_redirect_t redirect_func = _osx_find_log_redirect_func(hdr);
if (redirect_func) {
result = redirect_func(msg);
}
return result;
}
__attribute__((always_inline))
static void
_osx_construct_message(const char *prefix, uint64_t code, aslmsg asl_message, Dl_info *info, char *buff, size_t sz)
{
const char *image_name = NULL;
uintptr_t offset = 0;
uuid_string_t uuid_str;
void *ret = __builtin_return_address(0);
if (dladdr(ret, info)) {
uuid_t uuid;
_osx_get_image_uuid(info->dli_fbase, uuid);
uuid_unparse(uuid, uuid_str);
image_name = _osx_basename(info->dli_fname);
offset = ret - info->dli_fbase;
}
char name[256];
(void)snprintf(name, sizeof(name), "com.apple.assumes.%s", image_name);
char sig[64];
(void)snprintf(sig, sizeof(sig), "%s:%lu", uuid_str, offset);
char result[24];
(void)snprintf(result, sizeof(result), "0x%llx", code);
char build[16];
size_t bsz = sizeof(build);
_osx_get_build(build, bsz);
(void)snprintf(buff, sz, "%s: %s: %s + %lu [%s]: %s", prefix, build, image_name, offset, uuid_str, result);
(void)asl_set(asl_message, "com.apple.message.domain", name);
(void)asl_set(asl_message, "com.apple.message.signature", sig);
(void)asl_set(asl_message, "com.apple.message.value", result);
}
void
_osx_assumes_log(uint64_t code)
{
aslmsg asl_message = asl_new(ASL_TYPE_MSG);
if (asl_message) {
Dl_info info;
char message[256];
_osx_construct_message("Bug", code, asl_message, &info, message, sizeof(message));
if (!_osx_log_redirect(info.dli_fbase, message)) {
(void)asl_log(NULL, NULL, ASL_LEVEL_ERR, "%s", message);
(void)asl_log(NULL, asl_message, ASL_LEVEL_ERR, "%s", message);
}
asl_free(asl_message);
}
if (_osx_abort_on_assumes()) {
__builtin_trap();
}
}
char *
_osx_assert_log(uint64_t code)
{
char *result = NULL;
aslmsg asl_message = asl_new(ASL_TYPE_MSG);
if (asl_message) {
Dl_info info;
char message[256];
_osx_construct_message("Fatal bug", code, asl_message, &info, message, sizeof(message));
if (!_osx_log_redirect(info.dli_fbase, message)) {
(void)asl_log(NULL, NULL, ASL_LEVEL_ERR, "%s", message);
(void)asl_log(NULL, asl_message, ASL_LEVEL_ERR, "%s", message);
}
asl_free(asl_message);
result = strdup(message);
}
#if LIBC_NO_LIBCRASHREPORTERCLIENT
char name[64];
(void)pthread_getname_np(pthread_self(), name, sizeof(name));
char newname[64];
if (strlen(name) == 0) {
(void)snprintf(newname, sizeof(newname), "[Fatal bug: 0x%llx]", code);
} else {
(void)snprintf(newname, sizeof(newname), "%s [Fatal bug: 0x%llx]", name, code);
}
(void)pthread_setname_np(newname);
#endif
return result;
}
void
_osx_assumes_log_ctx(osx_log_callout_t callout, void *ctx, uint64_t code)
{
aslmsg asl_message = asl_new(ASL_TYPE_MSG);
if (asl_message) {
Dl_info info;
char message[256];
_osx_construct_message("Bug", code, asl_message, &info, message, sizeof(message));
(void)callout(asl_message, ctx, message);
asl_free(asl_message);
}
if (_osx_abort_on_assumes()) {
__builtin_trap();
}
}
char *
_osx_assert_log_ctx(osx_log_callout_t callout, void *ctx, uint64_t code)
{
char *result = NULL;
aslmsg asl_message = asl_new(ASL_TYPE_MSG);
if (asl_message) {
Dl_info info;
char message[256];
_osx_construct_message("Fatal bug", code, asl_message, &info, message, sizeof(message));
(void)callout(asl_message, ctx, message);
asl_free(asl_message);
result = strdup(message);
}
}
extern void
_osx_avoid_tail_call(void)
{
}