/* * Copyright (c) 2019-2023 Apple Inc. All rights reserved. */ #undef offset #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "log_encode.h" #include "log_internal.h" #include "log_mem.h" #include "log_queue.h" #include "trace_internal.h" #define OS_LOGMEM_BUF_ORDER 14 #define OS_LOGMEM_MIN_LOG_ORDER 9 #define OS_LOGMEM_MAX_LOG_ORDER (OS_LOG_MAX_SIZE_ORDER) #define OS_LOG_SUBSYSTEM_MAX_CNT 1024 #define OS_LOG_SUBSYSTEM_NONE 0xffff #define OS_LOG_SUBSYSTEM_BASE 0x0001 // OS_LOG_DEFAULT takes 0 by definition #define OS_LOG_SUBSYSTEM_LAST (OS_LOG_SUBSYSTEM_BASE + OS_LOG_SUBSYSTEM_MAX_CNT) #define OS_LOG_SUBSYSTEM_NAME_MAX_LEN 128 /* * OSLog subsystem type. The struct layout matches its libtrace counterpart * and also matches what libtrace (logd) expects from subsystem registration * metadata payload. */ struct os_log_subsystem_s { uint16_t ols_id; union { struct { uint8_t ols_sub_size; uint8_t ols_cat_size; }; uint16_t ols_sizes; }; char ols_name[2 * OS_LOG_SUBSYSTEM_NAME_MAX_LEN]; }; struct os_log_s { struct os_log_subsystem_s ol_subsystem; struct smrq_slink ol_hash_link; }; struct os_log_s _os_log_default; struct os_log_s _os_log_replay; /* Counters for persistence mode */ SCALABLE_COUNTER_DEFINE(oslog_p_total_msgcount); SCALABLE_COUNTER_DEFINE(oslog_p_metadata_saved_msgcount); SCALABLE_COUNTER_DEFINE(oslog_p_metadata_dropped_msgcount); SCALABLE_COUNTER_DEFINE(oslog_p_signpost_saved_msgcount); SCALABLE_COUNTER_DEFINE(oslog_p_signpost_dropped_msgcount); SCALABLE_COUNTER_DEFINE(oslog_p_error_count); SCALABLE_COUNTER_DEFINE(oslog_p_saved_msgcount); SCALABLE_COUNTER_DEFINE(oslog_p_dropped_msgcount); SCALABLE_COUNTER_DEFINE(oslog_p_boot_dropped_msgcount); SCALABLE_COUNTER_DEFINE(oslog_p_coprocessor_total_msgcount); SCALABLE_COUNTER_DEFINE(oslog_p_coprocessor_dropped_msgcount); SCALABLE_COUNTER_DEFINE(oslog_p_unresolved_kc_msgcount); /* Counters for msgbuf logging */ SCALABLE_COUNTER_DEFINE(oslog_msgbuf_msgcount) SCALABLE_COUNTER_DEFINE(oslog_msgbuf_dropped_msgcount) /* Log subsystem counters */ SCALABLE_COUNTER_DEFINE(oslog_subsystem_count); SCALABLE_COUNTER_DEFINE(oslog_subsystem_found); SCALABLE_COUNTER_DEFINE(oslog_subsystem_dropped); LCK_GRP_DECLARE(oslog_cache_lock_grp, "oslog_cache_lock_grp"); LCK_TICKET_DECLARE(oslog_cache_lock, &oslog_cache_lock_grp); static bool oslog_boot_done = false; static SECURITY_READ_ONLY_LATE(bool) oslog_disabled = false; static uint16_t os_log_subsystem_id = OS_LOG_SUBSYSTEM_BASE; static struct logmem_s os_log_mem; static struct smr_hash os_log_cache; static struct firehose_chunk_s firehose_boot_chunk = { .fc_pos = { .fcp_next_entry_offs = offsetof(struct firehose_chunk_s, fc_data), .fcp_private_offs = FIREHOSE_CHUNK_SIZE, .fcp_refcnt = 1, // Indicate that there is a writer to this chunk .fcp_stream = firehose_stream_persist, .fcp_flag_io = 1, // Lets assume this is coming from the io bank }, }; bool os_log_disabled(void); extern vm_offset_t kernel_firehose_addr; extern bool bsd_log_lock(bool); extern void bsd_log_unlock(void); extern void logwakeup(void); extern void oslog_stream(bool, firehose_tracepoint_id_u, uint64_t, const void *, size_t); extern void *OSKextKextForAddress(const void *); static bool os_log_safe(void) { return oslog_is_safe() || startup_phase < STARTUP_SUB_EARLY_BOOT; } static bool os_log_turned_off(void) { return oslog_disabled || (atm_get_diagnostic_config() & ATM_TRACE_OFF); } bool os_log_info_enabled(os_log_t log __unused) { return !os_log_turned_off(); } bool os_log_debug_enabled(os_log_t log __unused) { return !os_log_turned_off(); } bool os_log_disabled(void) { return oslog_disabled; } static inline size_t log_payload_priv_data_size(const log_payload_s *lp) { assert3u(lp->lp_pub_data_size, <=, lp->lp_data_size); return lp->lp_data_size - lp->lp_pub_data_size; } static inline const uint8_t * log_payload_priv_data(const log_payload_s *lp, const uint8_t *lp_data) { if (log_payload_priv_data_size(lp) == 0) { return NULL; } return &lp_data[lp->lp_pub_data_size]; } static void log_payload_init(log_payload_t lp, firehose_stream_t stream, firehose_tracepoint_id_u ftid, uint64_t timestamp, size_t data_size, size_t pub_data_size) { assert3u(pub_data_size, <=, data_size); assert3u(data_size, <, UINT16_MAX); lp->lp_stream = stream; lp->lp_ftid = ftid; lp->lp_timestamp = timestamp; lp->lp_pub_data_size = (uint16_t)pub_data_size; lp->lp_data_size = (uint16_t)data_size; } static firehose_stream_t firehose_stream(os_log_type_t type) { return (type == OS_LOG_TYPE_INFO || type == OS_LOG_TYPE_DEBUG) ? firehose_stream_memory : firehose_stream_persist; } static firehose_tracepoint_id_t firehose_ftid(os_log_type_t type, const char *fmt, firehose_tracepoint_flags_t flags, void *dso, void *addr, bool driverKit) { uint32_t off; if (driverKit) { /* * Set FIREHOSE_TRACEPOINT_PC_DYNAMIC_BIT so logd will not try * to find the format string in the executable text. */ off = (uint32_t)((uintptr_t)addr | FIREHOSE_TRACEPOINT_PC_DYNAMIC_BIT); } else { off = _os_trace_offset(dso, fmt, (_firehose_tracepoint_flags_activity_t)flags); } return FIREHOSE_TRACE_ID_MAKE(firehose_tracepoint_namespace_log, type, flags, off); } static firehose_tracepoint_flags_t firehose_ftid_flags(const void *dso, bool driverKit) { kc_format_t kcformat = KCFormatUnknown; __assert_only bool result = PE_get_primary_kc_format(&kcformat); assert(result); if (kcformat == KCFormatStatic || kcformat == KCFormatKCGEN) { return _firehose_tracepoint_flags_pc_style_shared_cache; } /* * driverKit will have the dso set as MH_EXECUTE (it is logging from a * syscall in the kernel) but needs logd to parse the address as an * absolute pc. */ const kernel_mach_header_t *mh = dso; if (mh->filetype == MH_EXECUTE && !driverKit) { return _firehose_tracepoint_flags_pc_style_main_exe; } return _firehose_tracepoint_flags_pc_style_absolute; } static void * resolve_dso(const char *fmt, void *dso, void *addr, bool driverKit) { kc_format_t kcformat = KCFormatUnknown; if (!addr || !PE_get_primary_kc_format(&kcformat)) { return NULL; } switch (kcformat) { case KCFormatStatic: case KCFormatKCGEN: dso = PE_get_kc_baseaddress(KCKindPrimary); break; case KCFormatDynamic: case KCFormatFileset: if (!dso && (dso = (void *)OSKextKextForAddress(fmt)) == NULL) { return NULL; } if (!_os_trace_addr_in_text_segment(dso, fmt)) { return NULL; } if (!driverKit && (dso != (void *)OSKextKextForAddress(addr))) { return NULL; } break; default: panic("unknown KC format type"); } return dso; } static inline uintptr_t resolve_location(firehose_tracepoint_flags_t flags, uintptr_t dso, uintptr_t addr, bool driverKit, size_t *loc_size) { switch (flags & _firehose_tracepoint_flags_pc_style_mask) { case _firehose_tracepoint_flags_pc_style_shared_cache: case _firehose_tracepoint_flags_pc_style_main_exe: *loc_size = sizeof(uint32_t); return addr - dso; case _firehose_tracepoint_flags_pc_style_absolute: *loc_size = sizeof(uintptr_t); return driverKit ? addr : VM_KERNEL_UNSLIDE(addr); default: panic("Unknown firehose tracepoint flags %x", flags); } } __startup_func static void oslog_init(void) { /* * Disable kernel logging if ATM_TRACE_DISABLE set. ATM_TRACE_DISABLE * bit is not supposed to change during a system run but nothing really * prevents userspace from unintentionally doing so => we stash initial * value in a dedicated variable for a later reference, just in case. */ oslog_disabled = atm_get_diagnostic_config() & ATM_TRACE_DISABLE; if (!oslog_disabled) { smr_hash_init(&os_log_cache, OS_LOG_SUBSYSTEM_MAX_CNT / 4); } } STARTUP(OSLOG, STARTUP_RANK_FIRST, oslog_init); __startup_func static void oslog_init_logmem(void) { if (os_log_disabled()) { printf("Long logs support disabled: Logging disabled by ATM\n"); return; } const size_t logmem_size = logmem_required_size(OS_LOGMEM_BUF_ORDER, OS_LOGMEM_MIN_LOG_ORDER); vm_offset_t addr; if (kmem_alloc(kernel_map, &addr, logmem_size + ptoa(2), KMA_KOBJECT | KMA_PERMANENT | KMA_ZERO | KMA_GUARD_FIRST | KMA_GUARD_LAST, VM_KERN_MEMORY_LOG) == KERN_SUCCESS) { logmem_init(&os_log_mem, (void *)(addr + PAGE_SIZE), logmem_size, OS_LOGMEM_BUF_ORDER, OS_LOGMEM_MIN_LOG_ORDER, OS_LOGMEM_MAX_LOG_ORDER); printf("Long logs support configured: size: %u\n", os_log_mem.lm_cnt_free); } else { printf("Long logs support disabled: Not enough memory\n"); } } STARTUP(OSLOG, STARTUP_RANK_SECOND, oslog_init_logmem); bool os_log_encoded_metadata(firehose_tracepoint_id_u ftid, uint64_t ts, const void *msg, size_t msg_size) { assert(ftid.ftid._namespace == firehose_tracepoint_namespace_metadata); counter_inc(&oslog_p_total_msgcount); if (!os_log_safe() || os_log_disabled()) { counter_inc(&oslog_p_metadata_dropped_msgcount); return false; } log_payload_s log; log_payload_init(&log, firehose_stream_metadata, ftid, ts, msg_size, msg_size); if (log_queue_log(&log, msg, true)) { return true; } counter_inc(&oslog_p_metadata_dropped_msgcount); return false; } bool os_log_encoded_log(firehose_tracepoint_id_u ftid, uint64_t ts, const void *msg, size_t msg_size, size_t pub_data_size) { assert(ftid.ftid._namespace == firehose_tracepoint_namespace_log); counter_inc(&oslog_p_total_msgcount); if (!os_log_safe() || os_log_disabled()) { counter_inc(&oslog_p_dropped_msgcount); return false; } firehose_stream_t stream = firehose_stream(ftid.ftid._type); log_payload_s log; log_payload_init(&log, stream, ftid, ts, msg_size, pub_data_size); if (log_queue_log(&log, msg, true)) { return true; } counter_inc(&oslog_p_dropped_msgcount); return false; } bool os_log_encoded_signpost(firehose_tracepoint_id_u ftid, uint64_t ts, const void *msg, size_t msg_size, size_t pub_data_size) { assert(ftid.ftid._namespace == firehose_tracepoint_namespace_signpost); counter_inc(&oslog_p_total_msgcount); if (!os_log_safe() || os_log_disabled()) { counter_inc(&oslog_p_signpost_dropped_msgcount); return false; } log_payload_s log; log_payload_init(&log, firehose_stream_signpost, ftid, ts, msg_size, pub_data_size); if (log_queue_log(&log, msg, true)) { return true; } counter_inc(&oslog_p_signpost_dropped_msgcount); return false; } static inline size_t os_log_subsystem_name_size(const char *name) { return MIN(strlen(name) + 1, OS_LOG_SUBSYSTEM_NAME_MAX_LEN); } bool os_log_subsystem_id_valid(uint16_t sid) { return sid >= OS_LOG_SUBSYSTEM_BASE && sid <= os_log_subsystem_id; } static inline size_t os_log_subsystem_id_length(const struct os_log_subsystem_s *sub) { return sub->ols_sub_size + sub->ols_cat_size; } static inline size_t os_log_subsystem_size(const struct os_log_subsystem_s *tbs) { assert(tbs->ols_sub_size <= OS_LOG_SUBSYSTEM_NAME_MAX_LEN); assert(tbs->ols_cat_size <= OS_LOG_SUBSYSTEM_NAME_MAX_LEN); return sizeof(*tbs) - sizeof(tbs->ols_name) + os_log_subsystem_id_length(tbs); } static void os_log_subsystem_init(struct os_log_subsystem_s *ols, uint16_t sid, const char *sub, const char *cat) { ols->ols_sub_size = os_log_subsystem_name_size(sub); ols->ols_cat_size = os_log_subsystem_name_size(cat); strlcpy(&ols->ols_name[0], sub, OS_LOG_SUBSYSTEM_NAME_MAX_LEN); strlcpy(&ols->ols_name[ols->ols_sub_size], cat, OS_LOG_SUBSYSTEM_NAME_MAX_LEN); ols->ols_id = sid; } static void os_log_subsystem_register(const struct os_log_subsystem_s *ols, uint64_t stamp) { assert(os_log_subsystem_id_valid(ols->ols_id)); firehose_tracepoint_id_u trace_id; trace_id.ftid_value = FIREHOSE_TRACE_ID_MAKE(firehose_tracepoint_namespace_metadata, _firehose_tracepoint_type_metadata_subsystem, 0, ols->ols_id); os_log_encoded_metadata(trace_id, stamp, ols, os_log_subsystem_size(ols)); } #if DEVELOPMENT || DEBUG static bool os_log_valid(const struct os_log_s *log) { return os_log_subsystem_id_valid(log->ol_subsystem.ols_id); } #endif // DEVELOPMENT || DEBUG static smrh_key_t os_log_key(const struct os_log_subsystem_s *sub) { return (smrh_key_t) { .smrk_opaque = sub->ols_name, .smrk_len = os_log_subsystem_id_length(sub) }; } static uint32_t os_log_key_hash(smrh_key_t key, uint32_t seed) { return smrh_key_hash_mem(key, seed); } static bool os_log_key_equ(smrh_key_t k1, smrh_key_t k2) { return k1.smrk_len == k2.smrk_len ? smrh_key_equ_mem(k1, k2) : false; } static uint32_t os_log_hash(const struct smrq_slink *link, uint32_t seed) { const os_log_t log = __container_of(link, struct os_log_s, ol_hash_link); return os_log_key_hash(os_log_key(&log->ol_subsystem), seed); } static bool os_log_equ(const struct smrq_slink *link, smrh_key_t key) { const os_log_t h = __container_of(link, struct os_log_s, ol_hash_link); return os_log_key_equ(os_log_key(&h->ol_subsystem), key); } static bool oslog_try_get(void *oslog __unused) { return true; } SMRH_TRAITS_DEFINE(os_log_cache_traits, struct os_log_s, ol_hash_link, .domain = &smr_oslog, .key_hash = os_log_key_hash, .key_equ = os_log_key_equ, .obj_hash = os_log_hash, .obj_equ = os_log_equ, .obj_try_get = oslog_try_get ); static os_log_t os_log_cache_find_by_sub(const struct os_log_subsystem_s *sub) { os_log_t log = smr_hash_get(&os_log_cache, os_log_key(sub), &os_log_cache_traits); assert(!log || os_log_valid(log)); return log; } static os_log_t os_log_cache_insert(os_log_t log) { assert(os_log_subsystem_id_valid(log->ol_subsystem.ols_id)); os_log_t found = smr_hash_serialized_get_or_insert(&os_log_cache, os_log_key(&log->ol_subsystem), &log->ol_hash_link, &os_log_cache_traits); assert(!found || os_log_valid(found)); return found; } static os_log_t os_log_allocate(const struct os_log_subsystem_s *subsystem) { os_log_t new = kalloc_type(struct os_log_s, Z_WAITOK_ZERO); if (new) { new->ol_subsystem = *subsystem; } return new; } static uint16_t os_log_subsystem_id_next(void) { assert(os_log_subsystem_id_valid(os_log_subsystem_id)); if (__improbable(os_log_subsystem_id == OS_LOG_SUBSYSTEM_LAST)) { return OS_LOG_SUBSYSTEM_NONE; } return os_log_subsystem_id++; } static void os_log_subsystem_id_revert(uint16_t __assert_only sid) { assert(os_log_subsystem_id_valid(os_log_subsystem_id)); assert3u(os_log_subsystem_id, ==, sid + 1); os_log_subsystem_id--; } static os_log_t os_log_create_impl(const char *subsystem, const char *category, uint64_t stamp) { if (os_log_disabled()) { return OS_LOG_DISABLED; } struct os_log_subsystem_s new_sub; os_log_subsystem_init(&new_sub, OS_LOG_SUBSYSTEM_NONE, subsystem, category); os_log_t log = os_log_cache_find_by_sub(&new_sub); if (log) { counter_inc(&oslog_subsystem_found); return log; } if (!(log = os_log_allocate(&new_sub))) { counter_inc(&oslog_subsystem_dropped); return OS_LOG_DEFAULT; } lck_ticket_lock(&oslog_cache_lock, &oslog_cache_lock_grp); log->ol_subsystem.ols_id = os_log_subsystem_id_next(); if (__improbable(log->ol_subsystem.ols_id == OS_LOG_SUBSYSTEM_NONE)) { lck_ticket_unlock(&oslog_cache_lock); kfree_type(struct os_log_s, log); counter_inc(&oslog_subsystem_dropped); return OS_LOG_DEFAULT; } os_log_t found = os_log_cache_insert(log); if (__improbable(found)) { os_log_subsystem_id_revert(log->ol_subsystem.ols_id); lck_ticket_unlock(&oslog_cache_lock); kfree_type(struct os_log_s, log); counter_inc(&oslog_subsystem_found); return found; } lck_ticket_unlock(&oslog_cache_lock); os_log_subsystem_register(&log->ol_subsystem, stamp); counter_inc(&oslog_subsystem_count); return log; } os_log_t os_log_create(const char *subsystem, const char *category) { uint64_t stamp = firehose_tracepoint_time(firehose_activity_flags_default); return os_log_create_impl(subsystem, category, stamp); } static void _os_log_to_msgbuf_internal(const char *format, va_list args, uint64_t timestamp, bool safe, bool logging, bool addcr) { /* * The following threshold was determined empirically as the point where * it would be more advantageous to be able to fit in more log lines than * to know exactly when a log line was printed out. We don't want to use up * a large percentage of the log buffer on timestamps in a memory-constricted * environment. */ const int MSGBUF_TIMESTAMP_THRESHOLD = 4096; static int msgbufreplay = -1; static bool newlogline = true; va_list args_copy; if (!bsd_log_lock(safe)) { counter_inc(&oslog_msgbuf_dropped_msgcount); return; } if (!safe) { if (-1 == msgbufreplay) { msgbufreplay = msgbufp->msg_bufx; } } else if (logging && (-1 != msgbufreplay)) { uint32_t i; uint32_t localbuff_size; int newl, position; char *localbuff, *p, *s, *next, ch; position = msgbufreplay; msgbufreplay = -1; localbuff_size = (msgbufp->msg_size + 2); /* + '\n' + '\0' */ /* Size for non-blocking */ if (localbuff_size > 4096) { localbuff_size = 4096; } bsd_log_unlock(); /* Allocate a temporary non-circular buffer */ localbuff = kalloc_data(localbuff_size, Z_NOWAIT); if (localbuff != NULL) { /* in between here, the log could become bigger, but that's fine */ bsd_log_lock(true); /* * The message buffer is circular; start at the replay pointer, and * make one loop up to write pointer - 1. */ p = msgbufp->msg_bufc + position; for (i = newl = 0; p != msgbufp->msg_bufc + msgbufp->msg_bufx - 1; ++p) { if (p >= msgbufp->msg_bufc + msgbufp->msg_size) { p = msgbufp->msg_bufc; } ch = *p; if (ch == '\0') { continue; } newl = (ch == '\n'); localbuff[i++] = ch; if (i >= (localbuff_size - 2)) { break; } } bsd_log_unlock(); if (!newl) { localbuff[i++] = '\n'; } localbuff[i++] = 0; s = localbuff; while ((next = strchr(s, '\n'))) { next++; ch = next[0]; next[0] = 0; os_log(&_os_log_replay, "%s", s); next[0] = ch; s = next; } kfree_data(localbuff, localbuff_size); } bsd_log_lock(true); } /* Do not prepend timestamps when we are memory-constricted */ if (newlogline && (msgbufp->msg_size > MSGBUF_TIMESTAMP_THRESHOLD)) { clock_sec_t secs; clock_usec_t microsecs; absolutetime_to_microtime(timestamp, &secs, µsecs); printf_log_locked(FALSE, "[%5lu.%06u]: ", (unsigned long)secs, microsecs); } #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wformat-nonliteral" va_copy(args_copy, args); newlogline = vprintf_log_locked(format, args_copy, addcr); va_end(args_copy); #pragma clang diagnostic pop bsd_log_unlock(); logwakeup(); counter_inc(&oslog_msgbuf_msgcount); } static void _os_log_to_log_internal(uint16_t sid, os_log_type_t type, const char *fmt, va_list args, uint64_t ts, void *addr, void *dso, bool driverKit) { dso = resolve_dso(fmt, dso, addr, driverKit); if (__improbable(!dso)) { counter_inc(&oslog_p_unresolved_kc_msgcount); return; } firehose_tracepoint_flags_t flags = firehose_ftid_flags(dso, driverKit); if (sid != 0) { flags |= _firehose_tracepoint_flags_log_has_subsystem; } size_t loc_sz = 0; uintptr_t loc = resolve_location(flags, (uintptr_t)dso, (uintptr_t)addr, driverKit, &loc_sz); firehose_tracepoint_id_u ftid = { .ftid_value = firehose_ftid(type, fmt, flags, dso, addr, driverKit) }; __attribute__((uninitialized, aligned(8))) uint8_t buffer[OS_LOG_BUFFER_MAX_SIZE]; struct os_log_context_s ctx; os_log_context_init(&ctx, &os_log_mem, buffer, sizeof(buffer)); if (!os_log_context_encode(&ctx, fmt, args, loc, loc_sz, sid)) { counter_inc(&oslog_p_error_count); os_log_context_free(&ctx); return; } log_payload_s log; log_payload_init(&log, firehose_stream(type), ftid, ts, ctx.ctx_content_sz, ctx.ctx_content_sz); if (!log_queue_log(&log, ctx.ctx_buffer, true)) { counter_inc(&oslog_p_dropped_msgcount); } os_log_context_free(&ctx); } static void _os_log_with_args_internal(os_log_t oslog, os_log_type_t type, const char *fmt, va_list args, uint64_t ts, void *addr, void *dso, bool driverKit, bool addcr) { counter_inc(&oslog_p_total_msgcount); if (oslog == OS_LOG_DISABLED) { counter_inc(&oslog_p_dropped_msgcount); return; } if (__improbable(fmt[0] == '\0')) { counter_inc(&oslog_p_error_count); return; } /* early boot can log to dmesg for later replay (27307943) */ bool safe = os_log_safe(); bool logging = !os_log_turned_off(); if (oslog != &_os_log_replay) { _os_log_to_msgbuf_internal(fmt, args, ts, safe, logging, addcr); } if (safe && logging) { const uint16_t sid = oslog->ol_subsystem.ols_id; _os_log_to_log_internal(sid, type, fmt, args, ts, addr, dso, driverKit); } } __attribute__((noinline, not_tail_called)) void _os_log_internal(void *dso, os_log_t log, uint8_t type, const char *fmt, ...) { uint64_t ts = firehose_tracepoint_time(firehose_activity_flags_default); void *addr = __builtin_return_address(0); va_list args; va_start(args, fmt); _os_log_with_args_internal(log, type, fmt, args, ts, addr, dso, FALSE, FALSE); va_end(args); } __attribute__((noinline, not_tail_called)) void _os_log_at_time(void *dso, os_log_t log, uint8_t type, uint64_t ts, const char *fmt, ...) { void *addr = __builtin_return_address(0); va_list args; va_start(args, fmt); _os_log_with_args_internal(log, type, fmt, args, ts, addr, dso, FALSE, FALSE); va_end(args); } __attribute__((noinline, not_tail_called)) int _os_log_internal_driverKit(void *dso, os_log_t log, uint8_t type, const char *fmt, ...) { uint64_t ts = firehose_tracepoint_time(firehose_activity_flags_default); void *addr = __builtin_return_address(0); bool driverKitLog = FALSE; va_list args; /* * We want to be able to identify dexts from the logs. * * Usually the addr is used to understand if the log line * was generated by a kext or the kernel main executable. * Logd uses copyKextUUIDForAddress with the addr specified * in the log line to retrieve the kext UUID of the sender. * * Dext however are not loaded in kernel space so they do not * have a kernel range of addresses. * * To make the same mechanism work, OSKext fakes a kernel * address range for dexts using the loadTag, * so we just need to use the loadTag as addr here * to allow logd to retrieve the correct UUID. * * NOTE: loadTag is populated in the task when the dext is matching, * so if log lines are generated before the matching they will be * identified as kernel main executable. */ task_t self_task = current_task(); /* * Only dexts are supposed to use this log path. Verified in log_data() * but worth of another check here in case this function gets called * directly. */ if (!task_is_driver(self_task)) { return EPERM; } uint64_t loadTag = get_task_loadTag(self_task); if (loadTag != 0) { driverKitLog = TRUE; addr = (void *)loadTag; } va_start(args, fmt); _os_log_with_args_internal(log, type, fmt, args, ts, addr, dso, driverKitLog, true); va_end(args); return 0; } __attribute__((noinline, not_tail_called)) void os_log_with_args(os_log_t oslog, os_log_type_t type, const char *fmt, va_list args, void *addr) { uint64_t ts = firehose_tracepoint_time(firehose_activity_flags_default); // if no address passed, look it up if (addr == NULL) { addr = __builtin_return_address(0); } _os_log_with_args_internal(oslog, type, fmt, args, ts, addr, NULL, FALSE, FALSE); } bool os_log_coprocessor(void *buff, uint64_t buff_len, os_log_type_t type, const char *uuid, uint64_t timestamp, uint32_t offset, bool stream_log) { counter_inc(&oslog_p_coprocessor_total_msgcount); if (os_log_turned_off() || !os_log_safe()) { counter_inc(&oslog_p_coprocessor_dropped_msgcount); return false; } if (buff_len + 16 + sizeof(uint32_t) > OS_LOG_BUFFER_MAX_SIZE) { counter_inc(&oslog_p_coprocessor_dropped_msgcount); return false; } __attribute__((uninitialized)) uint8_t pubdata[OS_LOG_BUFFER_MAX_SIZE]; size_t wr_pos = 0; memcpy(pubdata, &offset, sizeof(uint32_t)); wr_pos += sizeof(uint32_t); memcpy(pubdata + wr_pos, uuid, 16); wr_pos += 16; memcpy(pubdata + wr_pos, buff, buff_len); wr_pos += buff_len; /* * Unlike KEXTs, where PC is used to find UUID, in coprocessor logs the UUID * is passed as part of the tracepoint. */ firehose_tracepoint_namespace_t ns = firehose_tracepoint_namespace_log; firehose_tracepoint_flags_t flags = _firehose_tracepoint_flags_pc_style_uuid_relative; firehose_tracepoint_id_u trace_id = { .ftid_value = FIREHOSE_TRACE_ID_MAKE(ns, type, flags, offset) }; log_payload_s log; log_payload_init(&log, firehose_stream(type), trace_id, timestamp, wr_pos, wr_pos); if (!log_queue_log(&log, pubdata, stream_log)) { counter_inc(&oslog_p_coprocessor_dropped_msgcount); return false; } return true; } static inline firehose_tracepoint_t firehose_trace_start(const log_payload_s *lp, uint8_t **ft_priv_data) { uint16_t priv_data_size = (uint16_t)log_payload_priv_data_size(lp); uint16_t pub_data_size = (uint16_t)lp->lp_pub_data_size; if (priv_data_size > 0) { pub_data_size += sizeof(struct firehose_buffer_range_s); } assert3u(offsetof(struct firehose_tracepoint_s, ft_data) + pub_data_size + priv_data_size, <=, sizeof(((firehose_chunk_t)0)->fc_data)); firehose_tracepoint_t ft = __firehose_buffer_tracepoint_reserve(lp->lp_timestamp, lp->lp_stream, pub_data_size, priv_data_size, ft_priv_data); if (fastpath(ft)) { oslog_boot_done = true; return ft; } if (fastpath(oslog_boot_done)) { return 0; } /* * Early boot logging when kernel logging is up and running but logd is * not yet ready. Only the persist firehose stream is available. */ long offset = firehose_chunk_tracepoint_try_reserve(&firehose_boot_chunk, lp->lp_timestamp, firehose_stream_persist, 0, pub_data_size, priv_data_size, ft_priv_data); if (offset <= 0) { counter_inc(&oslog_p_boot_dropped_msgcount); return 0; } return firehose_chunk_tracepoint_begin(&firehose_boot_chunk, lp->lp_timestamp, pub_data_size, thread_tid(current_thread()), offset); } static inline void firehose_trace_finish(firehose_tracepoint_t ft, firehose_tracepoint_id_u ftid) { if (__probable(oslog_boot_done)) { __firehose_buffer_tracepoint_flush(ft, ftid); } else { firehose_chunk_tracepoint_end(&firehose_boot_chunk, ft, ftid); } } static inline void firehose_trace_save_payload(const log_payload_s *lp, const uint8_t *lp_data, firehose_tracepoint_t ft, uint8_t *ft_priv_data) { const size_t priv_data_size = log_payload_priv_data_size(lp); uint8_t *ft_data = ft->ft_data; if (priv_data_size > 0) { assert(FIREHOSE_TRACE_ID_HAS_FLAG(lp->lp_ftid, log, has_private_data)); memcpy(ft_priv_data, log_payload_priv_data(lp, lp_data), priv_data_size); firehose_chunk_t fc = firehose_chunk_for_address(ft); struct firehose_buffer_range_s range = { .fbr_offset = (uint16_t)(ft_priv_data - fc->fc_start), .fbr_length = (uint16_t)priv_data_size }; memcpy(ft_data, &range, sizeof(range)); ft_data += sizeof(range); } memcpy(ft_data, lp_data, lp->lp_pub_data_size); } static inline void log_stats_update_saved(firehose_stream_t stream) { if (__improbable(!oslog_boot_done)) { stream = firehose_stream_persist; } switch (stream) { case firehose_stream_metadata: counter_inc(&oslog_p_metadata_saved_msgcount); break; case firehose_stream_persist: case firehose_stream_memory: counter_inc(&oslog_p_saved_msgcount); break; case firehose_stream_signpost: counter_inc(&oslog_p_signpost_saved_msgcount); break; default: panic("Unexpected firehose stream type %u", stream); } } static inline firehose_tracepoint_id_t firehose_trace(const log_payload_s *lp, const uint8_t *lp_data) { uint8_t *ft_priv_data = NULL; firehose_tracepoint_t ft = firehose_trace_start(lp, &ft_priv_data); if (fastpath(ft)) { firehose_trace_save_payload(lp, lp_data, ft, ft_priv_data); firehose_trace_finish(ft, lp->lp_ftid); log_stats_update_saved(lp->lp_stream); return lp->lp_ftid.ftid_value; } return 0; } static firehose_tracepoint_code_t coproc_reg_type_to_firehost_code(os_log_coproc_reg_t reg_type) { switch (reg_type) { case os_log_coproc_register_memory: return firehose_tracepoint_code_load_memory; case os_log_coproc_register_harvest_fs_ftab: return firehose_tracepoint_code_load_filesystem_ftab; default: return firehose_tracepoint_code_invalid; } } void os_log_coprocessor_register_with_type(const char *uuid, const char *file_path, os_log_coproc_reg_t reg_type) { size_t path_size = strlen(file_path) + 1; size_t uuid_info_len = sizeof(struct firehose_trace_uuid_info_s) + path_size; if (os_log_disabled() || path_size > PATH_MAX) { return; } __attribute__((uninitialized)) union { struct firehose_trace_uuid_info_s uuid_info; char path[PATH_MAX + sizeof(struct firehose_trace_uuid_info_s)]; } buf; // write metadata to uuid_info memcpy(buf.uuid_info.ftui_uuid, uuid, sizeof(uuid_t)); buf.uuid_info.ftui_size = 1; buf.uuid_info.ftui_address = 1; uint64_t stamp = firehose_tracepoint_time(firehose_activity_flags_default); // create tracepoint id firehose_tracepoint_id_u trace_id; trace_id.ftid_value = FIREHOSE_TRACE_ID_MAKE(firehose_tracepoint_namespace_metadata, _firehose_tracepoint_type_metadata_coprocessor, (firehose_tracepoint_flags_t)0, coproc_reg_type_to_firehost_code(reg_type)); // write path to buffer memcpy(buf.uuid_info.ftui_path, file_path, path_size); // send metadata tracepoint to firehose for coprocessor registration in logd os_log_encoded_metadata(trace_id, stamp, (void *)&buf, uuid_info_len); } bool log_payload_send(log_payload_t lp, const void *lp_data, bool use_stream) { if (use_stream) { bool is_metadata = (lp->lp_stream == firehose_stream_metadata); oslog_stream(is_metadata, lp->lp_ftid, lp->lp_timestamp, lp_data, lp->lp_data_size); } return firehose_trace(lp, lp_data) != 0; } void __firehose_buffer_push_to_logd(firehose_buffer_t fb __unused, bool for_io __unused) { oslogwakeup(); return; } void __firehose_allocate(vm_offset_t *addr, vm_size_t size __unused) { firehose_chunk_t kernel_buffer = (firehose_chunk_t)kernel_firehose_addr; if (kernel_firehose_addr) { *addr = kernel_firehose_addr; } else { *addr = 0; return; } // Now that we are done adding logs to this chunk, set the number of writers to 0 // Without this, logd won't flush when the page is full firehose_boot_chunk.fc_pos.fcp_refcnt = 0; memcpy(&kernel_buffer[FIREHOSE_BUFFER_KERNEL_CHUNK_COUNT - 1], (const void *)&firehose_boot_chunk, FIREHOSE_CHUNK_SIZE); return; } // There isnt a lock held in this case. void __firehose_critical_region_enter(void) { disable_preemption(); return; } void __firehose_critical_region_leave(void) { enable_preemption(); return; } #ifdef CONFIG_XNUPOST static_assert(&_os_log_default == OS_LOG_DEFAULT, "OS_LOG_DEFAULT is an alias to _os_log_default"); static_assert(OS_LOG_DISABLED == NULL, "OS_LOG_DISABLED handle is defined as NULL"); #include #include typedef struct { size_t total; size_t saved; size_t dropped; size_t truncated; size_t errors; size_t errors_kc; size_t errors_fmt; size_t errors_max_args; } log_stats_t; #define TESTBUFLEN 256 #define TESTOSLOGFMT(fn_name) "%u^%llu/%llu^kernel^0^test^" fn_name #define TESTOSLOGPFX "TESTLOG:%u#" #define TESTOSLOG(fn_name) TESTOSLOGPFX TESTOSLOGFMT(fn_name "#") #define LOG_STAT_CMP(cmp, b, a, e, stat, msg, test) \ { \ size_t n##stat = (a)->stat - (b)->stat; \ size_t n##expected = (e)->stat; \ cmp(n##stat, n##expected, (msg), (test), n##expected); \ } #define LOG_STAT_EQ(b, a, e, stat, msg, test) \ LOG_STAT_CMP(T_EXPECT_EQ_UINT, b, a, e, stat, msg, test) #define LOG_STAT_GE(b, a, e, stat, msg, test) \ LOG_STAT_CMP(T_EXPECT_GE_UINT, b, a, e, stat, msg, test) #define GENOSLOGHELPER(fname, ident, callout_f) \ static void \ fname(uint32_t uniqid, uint64_t count, log_stats_t *before, log_stats_t *after) \ { \ uint32_t checksum = 0; \ char pattern[TESTBUFLEN]; \ T_LOG("Testing " ident "() with %d logs", count); \ log_stats_get(before); \ for (uint64_t i = 0; i < count; i++) { \ (void) save_pattern(pattern, &checksum, TESTOSLOGFMT(ident), uniqid, i + 1, count); \ callout_f(OS_LOG_DEFAULT, TESTOSLOG(ident), checksum, uniqid, i + 1, count); \ } \ log_stats_get(after); \ } extern size_t find_pattern_in_buffer(const char *, size_t, size_t); void test_oslog_handleOSLogCtl(int32_t *, int32_t *, int32_t); kern_return_t test_printf(void); kern_return_t test_os_log(void); kern_return_t test_os_log_handles(void); kern_return_t test_os_log_parallel(void); SCALABLE_COUNTER_DECLARE(oslog_p_fmt_invalid_msgcount); SCALABLE_COUNTER_DECLARE(oslog_p_fmt_max_args_msgcount); SCALABLE_COUNTER_DECLARE(oslog_p_truncated_msgcount); SCALABLE_COUNTER_DECLARE(log_queue_cnt_received); static void log_stats_get(log_stats_t *stats) { if (!stats) { return; } stats->total = counter_load(&oslog_p_total_msgcount); stats->saved = counter_load(&log_queue_cnt_received); stats->dropped = counter_load(&oslog_p_dropped_msgcount); stats->truncated = counter_load(&oslog_p_truncated_msgcount); stats->errors = counter_load(&oslog_p_error_count); stats->errors_kc = counter_load(&oslog_p_unresolved_kc_msgcount); stats->errors_fmt = counter_load(&oslog_p_fmt_invalid_msgcount); stats->errors_max_args = counter_load(&oslog_p_fmt_max_args_msgcount); } static void log_stats_diff(const log_stats_t *before, const log_stats_t *after, log_stats_t *diff) { log_stats_t d = {}; if (before) { d = *before; } if (after) { d.total = after->total - d.total; d.saved = after->saved - d.saved; d.dropped = after->dropped - d.dropped; d.truncated = after->truncated - d.truncated; d.errors = after->errors - d.errors; d.errors_kc = after->errors_kc - d.errors_kc; d.errors_fmt = after->errors_fmt - d.errors_fmt; d.errors_max_args = after->errors_max_args - d.errors_max_args; } *diff = d; } static void log_stats_check_errors(const log_stats_t *before, const log_stats_t *after, const log_stats_t *expected, const char *test) { LOG_STAT_EQ(before, after, expected, errors, "%s: Expected %lu encoding errors", test); LOG_STAT_EQ(before, after, expected, errors_kc, "%s: Expected %lu DSO errors", test); LOG_STAT_EQ(before, after, expected, errors_fmt, "%s: Expected %lu bad format errors", test); LOG_STAT_EQ(before, after, expected, errors_max_args, "%s: Expected %lu max arguments errors", test); } static void log_stats_check(const log_stats_t *before, const log_stats_t *after, const log_stats_t *expected, const char *test) { /* * The comparison is >= (_GE) for total and saved counters because tests * are running while the system is up and potentially logging. Test and * regular logs interfere rather rarely but can make the test flaky * which is not desired. */ LOG_STAT_GE(before, after, expected, total, "%s: Expected %lu logs total", test); LOG_STAT_GE(before, after, expected, saved, "%s: Expected %lu saved logs", test); LOG_STAT_EQ(before, after, expected, dropped, "%s: Expected %lu logs dropped", test); log_stats_check_errors(before, after, expected, test); } static void log_stats_report(const log_stats_t *before, const log_stats_t *after, const char *test) { log_stats_t diff = {}; log_stats_diff(before, after, &diff); T_LOG("\n%s: Logging stats:\n\ttotal: %u\n\tsaved: %u\n\tdropped: %u\n" "\terrors: %u\n\terrors_kc: %u\n\terrors_fmt: %u\n\terrors_max_args: %u", test, diff.total, diff.saved, diff.dropped, diff.errors, diff.errors_kc, diff.errors_fmt, diff.errors_max_args); } static int save_pattern(char buf[static TESTBUFLEN], uint32_t *crc, const char *fmt, ...) { va_list va; va_start(va, fmt); #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wformat-nonliteral" int n = vscnprintf(buf, TESTBUFLEN, fmt, va); #pragma clang diagnostic pop va_end(va); if (crc) { *crc = crc32(0, buf, n); } return n; } /* * Actual GENOSLOGHELPER() 2nd argument values below are expected by libtrace * test_kern_oslog test suite and shall not be changed without updating given * test suite. */ GENOSLOGHELPER(test_oslog_info, "oslog_info_helper", os_log_info); GENOSLOGHELPER(test_oslog_fault, "oslog_fault_helper", os_log_fault); GENOSLOGHELPER(test_oslog_debug, "oslog_debug_helper", os_log_debug); GENOSLOGHELPER(test_oslog_error, "oslog_error_helper", os_log_error); GENOSLOGHELPER(test_oslog_default, "oslog_default_helper", os_log); kern_return_t test_printf(void) { const uint32_t uniqid = RandomULong(); T_ASSERT_NE_UINT(0, uniqid, "Random number should not be zero"); uint64_t stamp = mach_absolute_time(); T_ASSERT_NE_ULLONG(0, stamp, "Absolute time should not be zero"); T_LOG("Validating with atm_diagnostic_config=%#x uniqid=%u stamp=%llu", atm_get_diagnostic_config(), uniqid, stamp); __attribute__((uninitialized)) char pattern[TESTBUFLEN]; log_stats_t before = {}, after = {}; uint32_t checksum = 0; log_stats_get(&before); (void) save_pattern(pattern, &checksum, TESTOSLOGFMT("printf_only"), uniqid, 1LL, 2LL); printf(TESTOSLOG("printf_only") "mat%llu\n", checksum, uniqid, 1LL, 2LL, stamp); (void) save_pattern(pattern, &checksum, TESTOSLOGFMT("printf_only"), uniqid, 2LL, 2LL); printf(TESTOSLOG("printf_only") "mat%llu\n", checksum, uniqid, 2LL, 2LL, stamp); size_t saved = save_pattern(pattern, NULL, "kernel^0^test^printf_only#mat%llu", stamp); size_t match_count = find_pattern_in_buffer(pattern, saved, 2LL); T_EXPECT_EQ_ULONG(match_count, 2LL, "printf() logs to msgbuf"); log_stats_get(&after); if (!os_log_turned_off()) { // printf() should log to OSLog with OSLog enabled. log_stats_t expected = { .total = 2, .saved = 2 }; log_stats_check(&before, &after, &expected, "printf() logs to oslog"); } log_stats_report(&before, &after, __FUNCTION__); return KERN_SUCCESS; } static void verify_os_log(struct os_log_s *h, char *sub, char *cat) { const size_t sub_len = os_log_subsystem_name_size(sub); const size_t cat_len = os_log_subsystem_name_size(cat); struct os_log_subsystem_s *s = &h->ol_subsystem; T_ASSERT_EQ_STR(s->ols_name, sub, "Log subsystem name is %s", sub); T_ASSERT_EQ_ULONG(s->ols_sub_size, sub_len, "Log subsystem size is %lu", sub_len); T_ASSERT_EQ_ULONG(strlen(s->ols_name), sub_len - 1, "Log subsystem length is %lu", sub_len - 1); T_ASSERT_EQ_STR(&s->ols_name[sub_len], cat, "Log category name is %s", cat); T_ASSERT_EQ_ULONG(s->ols_cat_size, cat_len, "Log category length is %lu", cat_len); T_ASSERT_EQ_ULONG(strlen(&s->ols_name[sub_len]), cat_len - 1, "Log category size is %lu", cat_len - 1); } static os_log_t create_verified_os_log(char *sub, char *cat) { os_log_t h = os_log_create(sub, cat); verify_os_log(h, sub, cat); return h; } kern_return_t test_os_log_handles(void) { os_log_t h1 = create_verified_os_log("xnu_post_subsystem1", "xnu_post_category1"); if (os_log_disabled()) { T_ASSERT_EQ_PTR(h1, OS_LOG_DISABLED, "Disabled logging uses OS_LOG_DISABLED"); return KERN_SUCCESS; } T_ASSERT_NE_PTR(h1, OS_LOG_DEFAULT, "Custom log handle is not OS_LOG_DEFAULT"); os_log_t h2 = create_verified_os_log("xnu_post_subsystem1", "xnu_post_category1"); T_ASSERT_EQ_PTR(h1, h2, "os_log_create() finds an existing log handle in the cache"); h2 = create_verified_os_log("xnu_post_subsystem1", "xnu_post_category2"); T_ASSERT_NE_PTR(h1, h2, "Subsystem is not enough to identify a log handle"); h2 = create_verified_os_log("xnu_post_subsystem2", "xnu_post_category1"); T_ASSERT_NE_PTR(h1, h2, "Category is not enough to identify a log handle"); h2 = create_verified_os_log("xnu_post_subsystem3", "xnu_post_category3"); T_ASSERT_NE_PTR(h1, h2, "Different subsystem and category yield a different handle"); h1 = h2; h2 = create_verified_os_log("xnu_post", "_subsystem3xnu_post_category3"); T_ASSERT_NE_PTR(h1, h2, "Subsystem and category cannot be mixed"); const size_t name_size = sizeof(h1->ol_subsystem.ols_name) / 2; char super_long_sub[name_size] = { [0 ... sizeof(super_long_sub) - 1] = 'X' }; char super_long_cat[name_size] = { [0 ... sizeof(super_long_sub) - 1] = 'Y' }; h1 = os_log_create(super_long_sub, super_long_cat); T_ASSERT_NE_PTR(h1, OS_LOG_DEFAULT, "Custom log handle is not OS_LOG_DEFAULT"); super_long_sub[name_size - 1] = '\0'; super_long_cat[name_size - 1] = '\0'; verify_os_log(h1, super_long_sub, super_long_cat); return KERN_SUCCESS; } kern_return_t test_os_log(void) { const uint32_t uniqid = RandomULong(); T_ASSERT_NE_UINT(0, uniqid, "Random number should not be zero"); uint64_t stamp = mach_absolute_time(); T_ASSERT_NE_ULLONG(0, stamp, "Absolute time should not be zero"); T_LOG("Validating with atm_diagnostic_config=%#x uniqid=%u stamp=%llu", atm_get_diagnostic_config(), uniqid, stamp); os_log_t log_handle = os_log_create("com.apple.xnu.test.t1", "kpost"); T_ASSERT_NE_PTR(OS_LOG_DEFAULT, log_handle, "Log handle is not OS_LOG_DEFAULT"); const bool enabled = !os_log_turned_off(); T_ASSERT_EQ_INT(enabled, os_log_info_enabled(log_handle), "Info log level is enabled"); T_ASSERT_EQ_INT(enabled, os_log_debug_enabled(log_handle), "Debug log level is enabled"); __attribute__((uninitialized)) char pattern[TESTBUFLEN]; uint32_t checksum = 0; (void) save_pattern(pattern, &checksum, TESTOSLOGFMT("oslog_info"), uniqid, 1LL, 1LL); os_log_info(log_handle, TESTOSLOG("oslog_info") "mat%llu", checksum, uniqid, 1LL, 1LL, stamp); size_t saved = save_pattern(pattern, NULL, "kernel^0^test^oslog_info#mat%llu", stamp); size_t match_count = find_pattern_in_buffer(pattern, saved, 1LL); T_EXPECT_EQ_ULONG(match_count, 1LL, "oslog_info() logs to system message buffer"); const size_t n = 10; log_stats_t expected = { .total = enabled ? n : 0, .saved = enabled ? n : 0 }; log_stats_t before = {}, after = {}; test_oslog_info(uniqid, n, &before, &after); log_stats_check(&before, &after, &expected, "test_oslog_info"); test_oslog_debug(uniqid, n, &before, &after); log_stats_check(&before, &after, &expected, "test_oslog_debug"); test_oslog_error(uniqid, n, &before, &after); log_stats_check(&before, &after, &expected, "test_oslog_error"); test_oslog_default(uniqid, n, &before, &after); log_stats_check(&before, &after, &expected, "test_oslog_default"); test_oslog_fault(uniqid, n, &before, &after); log_stats_check(&before, &after, &expected, "test_oslog_fault"); log_stats_report(&before, &after, __FUNCTION__); return KERN_SUCCESS; } static size_t _test_log_loop_count = 0; static void _test_log_loop(void *arg __unused, wait_result_t wres __unused) { test_oslog_debug(RandomULong(), 100, NULL, NULL); os_atomic_add(&_test_log_loop_count, 100, relaxed); } kern_return_t test_os_log_parallel(void) { if (os_log_turned_off()) { T_LOG("Logging disabled, skipping tests."); return KERN_SUCCESS; } thread_t thread[2]; kern_return_t kr; log_stats_t after, before, expected = {}; log_stats_get(&before); kr = kernel_thread_start(_test_log_loop, NULL, &thread[0]); T_ASSERT_EQ_INT(kr, KERN_SUCCESS, "kernel_thread_start returned successfully"); kr = kernel_thread_start(_test_log_loop, NULL, &thread[1]); T_ASSERT_EQ_INT(kr, KERN_SUCCESS, "kernel_thread_start returned successfully"); test_oslog_info(RandomULong(), 100, NULL, NULL); /* wait until other thread has also finished */ while (_test_log_loop_count < 200) { delay(1000); } thread_deallocate(thread[0]); thread_deallocate(thread[1]); log_stats_get(&after); log_stats_check_errors(&before, &after, &expected, __FUNCTION__); log_stats_report(&before, &after, __FUNCTION__); return KERN_SUCCESS; } static kern_return_t test_stresslog_dropmsg(const uint32_t uniqid) { if (os_log_turned_off()) { T_LOG("Logging disabled, skipping tests."); return KERN_SUCCESS; } log_stats_t after, before, expected = {}; test_oslog_debug(uniqid, 100, &before, &after); while ((after.dropped - before.dropped) == 0) { test_oslog_debug(uniqid, 100, NULL, &after); } log_stats_check_errors(&before, &after, &expected, __FUNCTION__); log_stats_report(&before, &after, __FUNCTION__); return KERN_SUCCESS; } void test_oslog_handleOSLogCtl(int32_t *in, int32_t *out, int32_t len) { if (!in || !out || len != 4) { return; } switch (in[0]) { case 1: { /* send out counters */ out[1] = counter_load(&oslog_p_total_msgcount); out[2] = counter_load(&oslog_p_saved_msgcount); out[3] = counter_load(&oslog_p_dropped_msgcount); out[0] = KERN_SUCCESS; break; } case 2: { /* mini stress run */ out[0] = test_os_log_parallel(); break; } case 3: { /* drop msg tests */ out[1] = RandomULong(); out[0] = test_stresslog_dropmsg(out[1]); break; } case 4: { /* invoke log helpers */ uint32_t uniqid = in[3]; int32_t msgcount = in[2]; if (uniqid == 0 || msgcount == 0) { out[0] = KERN_INVALID_VALUE; return; } switch (in[1]) { case OS_LOG_TYPE_INFO: test_oslog_info(uniqid, msgcount, NULL, NULL); break; case OS_LOG_TYPE_DEBUG: test_oslog_debug(uniqid, msgcount, NULL, NULL); break; case OS_LOG_TYPE_ERROR: test_oslog_error(uniqid, msgcount, NULL, NULL); break; case OS_LOG_TYPE_FAULT: test_oslog_fault(uniqid, msgcount, NULL, NULL); break; case OS_LOG_TYPE_DEFAULT: default: test_oslog_default(uniqid, msgcount, NULL, NULL); break; } out[0] = KERN_SUCCESS; break; /* end of case 4 */ } default: { out[0] = KERN_INVALID_VALUE; break; } } return; } #endif /* CONFIG_XNUPOST */