#include <darwintest.h>
#include <dispatch/dispatch.h>
#include <inttypes.h>
#include <ktrace.h>
#include <ktrace_private.h>
#include <kperf/kperf.h>
#include <kperfdata/kpdecode.h>
#include <os/assumes.h>
#include <stdint.h>
#include <sys/sysctl.h>
#include "kperf_helpers.h"
#define PERF_STK_KHDR UINT32_C(0x25020014)
#define PERF_STK_UHDR UINT32_C(0x25020018)
#define KDEBUG_TRIGGER_TIMEOUT_NS (10 * NSEC_PER_SEC)
#define NON_TRIGGER_CLASS UINT8_C(0xfd)
#define NON_TRIGGER_SUBCLASS UINT8_C(0xff)
#define NON_TRIGGER_CODE UINT8_C(0xff)
#define NON_TRIGGER_EVENT \
(KDBG_EVENTID(NON_TRIGGER_CLASS, NON_TRIGGER_SUBCLASS, NON_TRIGGER_CODE))
static void
expect_kdebug_trigger(const char *filter_desc, const uint32_t *debugids,
unsigned int n_debugids)
{
__block int missing_kernel_stacks = 0;
__block int missing_user_stacks = 0;
ktrace_session_t s;
kperf_kdebug_filter_t filter;
s = ktrace_session_create();
T_QUIET; T_ASSERT_NOTNULL(s, NULL);
ktrace_events_single(s, PERF_STK_KHDR, ^(struct trace_point *tp) {
missing_kernel_stacks--;
T_LOG("saw kernel stack with %lu frames, flags = %#lx", tp->arg2,
tp->arg1);
});
ktrace_events_single(s, PERF_STK_UHDR, ^(struct trace_point *tp) {
missing_user_stacks--;
T_LOG("saw user stack with %lu frames, flags = %#lx", tp->arg2,
tp->arg1);
});
for (unsigned int i = 0; i < n_debugids; i++) {
ktrace_events_single(s, debugids[i], ^(struct trace_point *tp) {
missing_kernel_stacks++;
missing_user_stacks++;
T_LOG("saw event with debugid 0x%" PRIx32, tp->debugid);
});
}
ktrace_events_single(s, NON_TRIGGER_EVENT,
^(__unused struct trace_point *tp)
{
ktrace_end(s, 0);
});
ktrace_set_completion_handler(s, ^{
T_EXPECT_LE(missing_kernel_stacks, 0, NULL);
T_EXPECT_LE(missing_user_stacks, 0, NULL);
ktrace_session_destroy(s);
T_END;
});
kperf_reset();
(void)kperf_action_count_set(1);
T_ASSERT_POSIX_SUCCESS(kperf_action_samplers_set(1,
KPERF_SAMPLER_KSTACK | KPERF_SAMPLER_USTACK), NULL);
filter = kperf_kdebug_filter_create();
T_ASSERT_NOTNULL(filter, NULL);
T_ASSERT_POSIX_SUCCESS(kperf_kdebug_action_set(1), NULL);
T_ASSERT_POSIX_SUCCESS(kperf_kdebug_filter_add_desc(filter, filter_desc),
NULL);
T_ASSERT_POSIX_SUCCESS(kperf_kdebug_filter_set(filter), NULL);
kperf_kdebug_filter_destroy(filter);
T_ASSERT_POSIX_SUCCESS(kperf_sample_set(1), NULL);
T_ASSERT_POSIX_ZERO(ktrace_start(s, dispatch_get_main_queue()), NULL);
for (unsigned int i = 0; i < n_debugids; i++) {
T_ASSERT_POSIX_SUCCESS(kdebug_trace(debugids[i], 0, 0, 0, 0), NULL);
}
T_ASSERT_POSIX_SUCCESS(kdebug_trace(NON_TRIGGER_EVENT, 0, 0, 0, 0), NULL);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, KDEBUG_TRIGGER_TIMEOUT_NS),
dispatch_get_main_queue(), ^(void)
{
ktrace_end(s, 1);
});
}
#define TRIGGER_CLASS UINT8_C(0xfe)
#define TRIGGER_CLASS_END UINT8_C(0xfd)
#define TRIGGER_SUBCLASS UINT8_C(0xff)
#define TRIGGER_CODE UINT8_C(0)
#define TRIGGER_DEBUGID \
(KDBG_EVENTID(TRIGGER_CLASS, TRIGGER_SUBCLASS, TRIGGER_CODE))
T_DECL(kdebug_trigger_classes, "test that kdebug trigger samples on classes",
T_META_ASROOT(true))
{
const uint32_t class_debugids[] = {
KDBG_EVENTID(TRIGGER_CLASS, 1, 1),
KDBG_EVENTID(TRIGGER_CLASS, 2, 1),
KDBG_EVENTID(TRIGGER_CLASS_END, 1, 1) | DBG_FUNC_END,
KDBG_EVENTID(TRIGGER_CLASS_END, 2, 1) | DBG_FUNC_END,
};
expect_kdebug_trigger("C0xfe,C0xfdr", class_debugids,
sizeof(class_debugids) / sizeof(class_debugids[0]));
dispatch_main();
}
T_DECL(kdebug_trigger_subclasses,
"test that kdebug trigger samples on subclasses",
T_META_ASROOT(true))
{
const uint32_t subclass_debugids[] = {
KDBG_EVENTID(TRIGGER_CLASS, TRIGGER_SUBCLASS, 0),
KDBG_EVENTID(TRIGGER_CLASS, TRIGGER_SUBCLASS, 1),
KDBG_EVENTID(TRIGGER_CLASS_END, TRIGGER_SUBCLASS, 0) | DBG_FUNC_END,
KDBG_EVENTID(TRIGGER_CLASS_END, TRIGGER_SUBCLASS, 1) | DBG_FUNC_END
};
expect_kdebug_trigger("S0xfeff,S0xfdffr", subclass_debugids,
sizeof(subclass_debugids) / sizeof(subclass_debugids[0]));
dispatch_main();
}
T_DECL(kdebug_trigger_debugids, "test that kdebug trigger samples on debugids",
T_META_ASROOT(true))
{
const uint32_t debugids[] = {
TRIGGER_DEBUGID
};
expect_kdebug_trigger("D0xfeff0000", debugids,
sizeof(debugids) / sizeof(debugids[0]));
dispatch_main();
}
T_DECL(kdbg_callstacks, "test that the kdbg_callstacks samples on syscalls",
T_META_ASROOT(true))
{
ktrace_session_t s;
__block bool saw_user_stack = false;
s = ktrace_session_create();
T_ASSERT_NOTNULL(s, NULL);
ktrace_events_class(s, DBG_BSD,
^void(__unused struct trace_point *tp) {});
ktrace_events_single(s, PERF_STK_UHDR, ^(__unused struct trace_point *tp) {
saw_user_stack = true;
ktrace_end(s, 1);
});
ktrace_set_completion_handler(s, ^{
ktrace_session_destroy(s);
T_EXPECT_TRUE(saw_user_stack,
"saw user stack after configuring kdbg_callstacks");
ktrace_session_t s_after = ktrace_session_create();
T_ASSERT_NOTNULL(s_after, NULL);
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
T_ASSERT_POSIX_SUCCESS(kperf_kdbg_callstacks_set(0), NULL);
#pragma clang diagnostic pop
ktrace_events_class(s_after, DBG_BSD,
^void(__unused struct trace_point *tp) {});
__block bool saw_extra_stack = false;
ktrace_events_single(s_after, PERF_STK_UHDR,
^(__unused struct trace_point *tp)
{
saw_extra_stack = true;
ktrace_end(s_after, 1);
});
ktrace_set_completion_handler(s_after, ^(void) {
ktrace_session_destroy(s_after);
T_EXPECT_FALSE(saw_extra_stack,
"saw user stack after disabling kdbg_callstacks)");
kperf_reset();
T_END;
});
T_ASSERT_POSIX_ZERO(ktrace_start(s_after, dispatch_get_main_queue()),
NULL);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC),
dispatch_get_main_queue(), ^(void)
{
ktrace_end(s_after, 1);
});
});
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
T_ASSERT_POSIX_SUCCESS(kperf_kdbg_callstacks_set(1), NULL);
#pragma clang diagnostic pop
T_ASSERT_POSIX_ZERO(ktrace_start(s, dispatch_get_main_queue()), NULL);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 10 * NSEC_PER_SEC),
dispatch_get_main_queue(), ^(void)
{
ktrace_end(s, 1);
});
dispatch_main();
}
#define STACKS_WAIT_DURATION_NS (3 * NSEC_PER_SEC)
static void
expect_stacks_traced(void (^cb)(void))
{
ktrace_session_t s;
s = ktrace_session_create();
T_QUIET; T_ASSERT_NOTNULL(s, "ktrace_session_create");
__block unsigned int user_stacks = 0;
__block unsigned int kernel_stacks = 0;
ktrace_events_single(s, PERF_STK_UHDR, ^(__unused struct trace_point *tp) {
user_stacks++;
});
ktrace_events_single(s, PERF_STK_KHDR, ^(__unused struct trace_point *tp) {
kernel_stacks++;
});
ktrace_set_completion_handler(s, ^(void) {
ktrace_session_destroy(s);
T_EXPECT_GT(user_stacks, 0U, NULL);
T_EXPECT_GT(kernel_stacks, 0U, NULL);
cb();
});
T_QUIET; T_ASSERT_POSIX_SUCCESS(kperf_sample_set(1), NULL);
T_ASSERT_POSIX_ZERO(ktrace_start(s, dispatch_get_main_queue()), NULL);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, STACKS_WAIT_DURATION_NS),
dispatch_get_main_queue(), ^(void)
{
kperf_reset();
ktrace_end(s, 0);
});
}
T_DECL(pet, "test that PET mode samples kernel and user stacks",
T_META_ASROOT(true))
{
configure_kperf_stacks_timer(-1, 10);
T_ASSERT_POSIX_SUCCESS(kperf_timer_pet_set(0), NULL);
expect_stacks_traced(^(void) {
T_END;
});
dispatch_main();
}
T_DECL(lightweight_pet,
"test that lightweight PET mode samples kernel and user stacks",
T_META_ASROOT(true))
{
int set = 1;
configure_kperf_stacks_timer(-1, 10);
T_ASSERT_POSIX_SUCCESS(sysctlbyname("kperf.lightweight_pet", NULL, NULL,
&set, sizeof(set)), NULL);
T_ASSERT_POSIX_SUCCESS(kperf_timer_pet_set(0), NULL);
expect_stacks_traced(^(void) {
T_END;
});
dispatch_main();
}