]> git.saurik.com Git - apple/xnu.git/blob - tests/kperf_backtracing.c
2c133a1a9f50da0d0caa52651c41fcb4ea619680
[apple/xnu.git] / tests / kperf_backtracing.c
1 /* Copyright (c) 2018-2019 Apple Inc. All rights reserved. */
2
3 #include <CoreSymbolication/CoreSymbolication.h>
4 #include <darwintest.h>
5 #include <dispatch/dispatch.h>
6 #include <kperf/kperf.h>
7 #include <ktrace/session.h>
8 #include <ktrace/private.h>
9 #include <System/sys/kdebug.h>
10 #include <pthread.h>
11
12 #include "kperf_helpers.h"
13 #include "ktrace_helpers.h"
14
15 #define PERF_STK_KHDR UINT32_C(0x25020014)
16 #define PERF_STK_UHDR UINT32_C(0x25020018)
17 #define PERF_STK_KDATA UINT32_C(0x2502000c)
18 #define PERF_STK_UDATA UINT32_C(0x25020010)
19
20 #define CALLSTACK_VALID 0x1
21 #define CALLSTACK_TRUNCATED 0x10
22
23 T_GLOBAL_META(
24 T_META_NAMESPACE("xnu.ktrace"),
25 T_META_CHECK_LEAKS(false));
26
27 static void
28 expect_frame(const char **bt, unsigned int bt_len, CSSymbolRef symbol,
29 uint64_t addr, unsigned int bt_idx, unsigned int max_frames)
30 {
31 const char *name;
32 unsigned int frame_idx = max_frames - bt_idx - 1;
33
34 if (!bt[frame_idx]) {
35 T_LOG("frame %2u: skipping system frame '%s'", frame_idx,
36 CSSymbolGetName(symbol));
37 return;
38 }
39
40 T_LOG("checking frame %d: %llx", bt_idx, addr);
41 T_ASSERT_FALSE(CSIsNull(symbol), "invalid symbol for return address");
42
43 if (frame_idx >= bt_len) {
44 T_FAIL("unexpected frame '%s' (%#" PRIx64 ") at index %u",
45 CSSymbolGetName(symbol), addr, frame_idx);
46 return;
47 }
48
49 name = CSSymbolGetName(symbol);
50 T_QUIET; T_ASSERT_NOTNULL(name, NULL);
51 T_EXPECT_EQ_STR(name, bt[frame_idx],
52 "frame %2u: saw '%s', expected '%s'",
53 frame_idx, name, bt[frame_idx]);
54 }
55
56 /*
57 * Expect to see either user or kernel stacks on thread with ID `tid` with a
58 * signature of `bt` of length `bt_len`. Updates `stacks_seen` when stack
59 * is found.
60 *
61 * Can also allow stacks to be larger than the signature -- additional frames
62 * near the current PC will be ignored. This allows stacks to potentially be
63 * in the middle of a signalling system call (which signals that it is safe to
64 * start sampling).
65 */
66 static void
67 expect_backtrace(ktrace_session_t s, uint64_t tid, unsigned int *stacks_seen,
68 bool kern, const char **bt, unsigned int bt_len, unsigned int allow_larger_by)
69 {
70 CSSymbolicatorRef symb;
71 uint32_t hdr_debugid;
72 uint32_t data_debugid;
73 __block unsigned int stacks = 0;
74 __block unsigned int frames = 0;
75 __block unsigned int hdr_frames = 0;
76 __block unsigned int allow_larger = allow_larger_by;
77
78 T_SETUPBEGIN;
79
80 if (kern) {
81 static CSSymbolicatorRef kern_symb;
82 static dispatch_once_t kern_symb_once;
83
84 hdr_debugid = PERF_STK_KHDR;
85 data_debugid = PERF_STK_KDATA;
86
87 dispatch_once(&kern_symb_once, ^(void) {
88 kern_symb = CSSymbolicatorCreateWithMachKernel();
89 T_QUIET; T_ASSERT_FALSE(CSIsNull(kern_symb), NULL);
90 });
91 symb = kern_symb;
92 } else {
93 static CSSymbolicatorRef user_symb;
94 static dispatch_once_t user_symb_once;
95
96 hdr_debugid = PERF_STK_UHDR;
97 data_debugid = PERF_STK_UDATA;
98
99 dispatch_once(&user_symb_once, ^(void) {
100 user_symb = CSSymbolicatorCreateWithTask(mach_task_self());
101 T_QUIET; T_ASSERT_FALSE(CSIsNull(user_symb), NULL);
102 T_QUIET; T_ASSERT_TRUE(CSSymbolicatorIsTaskValid(user_symb), NULL);
103 });
104 symb = user_symb;
105 }
106
107 ktrace_events_single(s, hdr_debugid, ^(struct trace_point *tp) {
108 if (tid != 0 && tid != tp->threadid) {
109 return;
110 }
111
112 T_LOG("found %s stack from thread %#" PRIx64, kern ? "kernel" : "user",
113 tp->threadid);
114 stacks++;
115 if (!(tp->arg1 & 1)) {
116 T_FAIL("invalid %s stack on thread %#" PRIx64,
117 kern ? "kernel" : "user", tp->threadid);
118 return;
119 }
120
121 hdr_frames = (unsigned int)tp->arg2;
122 /* ignore extra link register or value pointed to by stack pointer */
123 hdr_frames -= 1;
124
125 T_QUIET; T_EXPECT_GE(hdr_frames, bt_len,
126 "number of frames in header");
127 T_QUIET; T_EXPECT_LE(hdr_frames, bt_len + allow_larger,
128 "number of frames in header");
129 if (hdr_frames > bt_len && allow_larger > 0) {
130 allow_larger = hdr_frames - bt_len;
131 hdr_frames = bt_len;
132 }
133
134 T_LOG("%s stack seen", kern ? "kernel" : "user");
135 frames = 0;
136 });
137
138 ktrace_events_single(s, data_debugid, ^(struct trace_point *tp) {
139 if (tid != 0 && tid != tp->threadid) {
140 return;
141 }
142
143 int i = 0;
144
145 if (frames == 0 && hdr_frames > bt_len) {
146 /* skip frames near the PC */
147 i = (int)allow_larger;
148 allow_larger -= 4;
149 }
150
151 for (; i < 4 && frames < hdr_frames; i++, frames++) {
152 uint64_t addr = (&tp->arg1)[i];
153 CSSymbolRef symbol = CSSymbolicatorGetSymbolWithAddressAtTime(
154 symb, addr, kCSNow);
155
156 expect_frame(bt, bt_len, symbol, addr, frames, hdr_frames);
157 }
158
159 /* saw the end of the user stack */
160 if (hdr_frames == frames) {
161 *stacks_seen += 1;
162 if (!kern) {
163 ktrace_end(s, 1);
164 }
165 }
166 });
167
168 T_SETUPEND;
169 }
170
171 #define TRIGGERING_DEBUGID (0xfeff0f00)
172
173 /*
174 * These functions must return an int to avoid the function prologue being
175 * hoisted out of the path to the spin (breaking being able to get a good
176 * backtrace).
177 */
178 static int __attribute__((noinline, not_tail_called))
179 recurse_a(dispatch_semaphore_t spinning, unsigned int frames);
180 static int __attribute__((noinline, not_tail_called))
181 recurse_b(dispatch_semaphore_t spinning, unsigned int frames);
182
183 static int __attribute__((noinline, not_tail_called))
184 recurse_a(dispatch_semaphore_t spinning, unsigned int frames)
185 {
186 if (frames == 0) {
187 if (spinning) {
188 dispatch_semaphore_signal(spinning);
189 for (;;) {
190 ;
191 }
192 } else {
193 kdebug_trace(TRIGGERING_DEBUGID, 0, 0, 0, 0);
194 return 0;
195 }
196 }
197
198 return recurse_b(spinning, frames - 1) + 1;
199 }
200
201 static int __attribute__((noinline, not_tail_called))
202 recurse_b(dispatch_semaphore_t spinning, unsigned int frames)
203 {
204 if (frames == 0) {
205 if (spinning) {
206 dispatch_semaphore_signal(spinning);
207 for (;;) {
208 ;
209 }
210 } else {
211 kdebug_trace(TRIGGERING_DEBUGID, 0, 0, 0, 0);
212 return 0;
213 }
214 }
215
216 return recurse_a(spinning, frames - 1) + 1;
217 }
218
219 #define USER_FRAMES (12)
220
221 #if defined(__x86_64__)
222
223 #define RECURSE_START_OFFSET (3)
224
225 #else /* defined(__x86_64__) */
226
227 #define RECURSE_START_OFFSET (2)
228
229 #endif /* !defined(__x86_64__) */
230
231 static const char *user_bt[USER_FRAMES] = {
232 #if defined(__x86_64__)
233 /*
234 * x86_64 has an extra "thread_start" frame here.
235 */
236 NULL,
237 #endif /* defined(__x86_64__) */
238 NULL,
239 "backtrace_thread",
240 "recurse_a", "recurse_b", "recurse_a", "recurse_b",
241 "recurse_a", "recurse_b", "recurse_a", "recurse_b",
242 #if !defined(__x86_64__)
243 /*
244 * Pick up the slack to make the number of frames constant.
245 */
246 "recurse_a",
247 #endif /* !defined(__x86_64__) */
248 NULL,
249 };
250
251 #if defined(__arm__)
252
253 #define KERNEL_FRAMES (2)
254 static const char *kernel_bt[KERNEL_FRAMES] = {
255 "unix_syscall", "kdebug_trace64"
256 };
257
258 #elif defined(__arm64__)
259
260 #define KERNEL_FRAMES (4)
261 static const char *kernel_bt[KERNEL_FRAMES] = {
262 "fleh_synchronous", "sleh_synchronous", "unix_syscall", "kdebug_trace64"
263 };
264
265 #elif defined(__x86_64__)
266
267 #define KERNEL_FRAMES (2)
268 static const char *kernel_bt[KERNEL_FRAMES] = {
269 "unix_syscall64", "kdebug_trace64"
270 };
271
272 #else
273 #error "architecture unsupported"
274 #endif /* defined(__arm__) */
275
276 static dispatch_once_t backtrace_once;
277 static dispatch_semaphore_t backtrace_started;
278 static dispatch_semaphore_t backtrace_go;
279
280 /*
281 * Another thread to run with a known backtrace.
282 *
283 * Take a semaphore that will be signalled when the thread is spinning at the
284 * correct frame. If the semaphore is NULL, don't spin and instead make a
285 * kdebug_trace system call, which can trigger a deterministic backtrace itself.
286 */
287 static void *
288 backtrace_thread(void *arg)
289 {
290 dispatch_semaphore_t notify_spinning;
291 unsigned int calls;
292
293 notify_spinning = (dispatch_semaphore_t)arg;
294
295 dispatch_semaphore_signal(backtrace_started);
296 if (!notify_spinning) {
297 dispatch_semaphore_wait(backtrace_go, DISPATCH_TIME_FOREVER);
298 }
299
300 /*
301 * backtrace_thread, recurse_a, recurse_b, ...[, __kdebug_trace64]
302 *
303 * Always make one less call for this frame (backtrace_thread).
304 */
305 calls = USER_FRAMES - RECURSE_START_OFFSET - 1 /* backtrace_thread */;
306 if (notify_spinning) {
307 /*
308 * Spinning doesn't end up calling __kdebug_trace64.
309 */
310 calls -= 1;
311 }
312
313 T_LOG("backtrace thread calling into %d frames (already at %d frames)",
314 calls, RECURSE_START_OFFSET);
315 (void)recurse_a(notify_spinning, calls);
316 return NULL;
317 }
318
319 static uint64_t
320 create_backtrace_thread(void *(*thread_fn)(void *),
321 dispatch_semaphore_t notify_spinning)
322 {
323 pthread_t thread = NULL;
324 uint64_t tid;
325
326 dispatch_once(&backtrace_once, ^{
327 backtrace_started = dispatch_semaphore_create(0);
328 T_QUIET; T_ASSERT_NOTNULL(backtrace_started, NULL);
329
330 if (!notify_spinning) {
331 backtrace_go = dispatch_semaphore_create(0);
332 T_QUIET; T_ASSERT_NOTNULL(backtrace_go, NULL);
333 }
334 });
335
336 T_QUIET; T_ASSERT_POSIX_ZERO(pthread_create(&thread, NULL, thread_fn,
337 (void *)notify_spinning), NULL);
338 T_QUIET; T_ASSERT_NOTNULL(thread, "backtrace thread created");
339 dispatch_semaphore_wait(backtrace_started, DISPATCH_TIME_FOREVER);
340
341 T_QUIET; T_ASSERT_POSIX_ZERO(pthread_threadid_np(thread, &tid), NULL);
342 T_QUIET; T_ASSERT_NE(tid, UINT64_C(0),
343 "backtrace thread created does not have ID 0");
344
345 T_LOG("starting thread with ID 0x%" PRIx64, tid);
346
347 return tid;
348 }
349
350 static void
351 start_backtrace_thread(void)
352 {
353 T_QUIET; T_ASSERT_NOTNULL(backtrace_go,
354 "thread to backtrace created before starting it");
355 dispatch_semaphore_signal(backtrace_go);
356 }
357
358 #if TARGET_OS_WATCH
359 #define TEST_TIMEOUT_NS (30 * NSEC_PER_SEC)
360 #else /* TARGET_OS_WATCH */
361 #define TEST_TIMEOUT_NS (5 * NSEC_PER_SEC)
362 #endif /* !TARGET_OS_WATCH */
363
364 T_DECL(kperf_stacks_kdebug_trig,
365 "test that backtraces from kdebug trigger are correct",
366 T_META_ASROOT(true))
367 {
368 static unsigned int stacks_seen = 0;
369 ktrace_session_t s;
370 kperf_kdebug_filter_t filter;
371 uint64_t tid;
372
373 start_controlling_ktrace();
374
375 s = ktrace_session_create();
376 T_ASSERT_NOTNULL(s, "ktrace session was created");
377
378 ktrace_set_collection_interval(s, 100);
379
380 T_ASSERT_POSIX_ZERO(ktrace_filter_pid(s, getpid()), NULL);
381
382 tid = create_backtrace_thread(backtrace_thread, NULL);
383 expect_backtrace(s, tid, &stacks_seen, false, user_bt, USER_FRAMES, 0);
384 expect_backtrace(s, tid, &stacks_seen, true, kernel_bt, KERNEL_FRAMES, 0);
385
386 /*
387 * The triggering event must be traced (and thus registered with libktrace)
388 * to get backtraces.
389 */
390 ktrace_events_single(s, TRIGGERING_DEBUGID,
391 ^(__unused struct trace_point *tp){ });
392
393 ktrace_set_completion_handler(s, ^(void) {
394 T_EXPECT_GE(stacks_seen, 2U, "saw both kernel and user stacks");
395 ktrace_session_destroy(s);
396 kperf_reset();
397 T_END;
398 });
399
400 filter = kperf_kdebug_filter_create();
401 T_ASSERT_NOTNULL(filter, "kperf kdebug filter was created");
402
403 T_QUIET; T_ASSERT_POSIX_SUCCESS(kperf_kdebug_filter_add_debugid(filter,
404 TRIGGERING_DEBUGID), NULL);
405 T_QUIET; T_ASSERT_POSIX_SUCCESS(kperf_kdebug_filter_set(filter), NULL);
406 (void)kperf_action_count_set(1);
407 T_QUIET; T_ASSERT_POSIX_SUCCESS(kperf_action_samplers_set(1,
408 KPERF_SAMPLER_USTACK | KPERF_SAMPLER_KSTACK), NULL);
409 T_QUIET; T_ASSERT_POSIX_SUCCESS(kperf_kdebug_action_set(1), NULL);
410 kperf_kdebug_filter_destroy(filter);
411
412 T_ASSERT_POSIX_SUCCESS(kperf_sample_set(1), NULL);
413
414 T_ASSERT_POSIX_ZERO(ktrace_start(s, dispatch_get_main_queue()), NULL);
415
416 start_backtrace_thread();
417
418 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, TEST_TIMEOUT_NS),
419 dispatch_get_main_queue(), ^(void)
420 {
421 T_LOG("ending test after timeout");
422 ktrace_end(s, 0);
423 });
424
425 dispatch_main();
426 }
427
428 T_DECL(kperf_ustack_timer,
429 "test that user backtraces on a timer are correct",
430 T_META_ASROOT(true))
431 {
432 static unsigned int stacks_seen = 0;
433 ktrace_session_t s;
434 uint64_t tid;
435 dispatch_semaphore_t wait_for_spinning = dispatch_semaphore_create(0);
436
437 start_controlling_ktrace();
438
439 s = ktrace_session_create();
440 T_QUIET; T_ASSERT_NOTNULL(s, "ktrace_session_create");
441
442 ktrace_set_collection_interval(s, 100);
443
444 ktrace_filter_pid(s, getpid());
445
446 configure_kperf_stacks_timer(getpid(), 10, false);
447
448 tid = create_backtrace_thread(backtrace_thread, wait_for_spinning);
449 /* potentially calling dispatch function and system call */
450 expect_backtrace(s, tid, &stacks_seen, false, user_bt, USER_FRAMES - 1, 2);
451
452 ktrace_set_completion_handler(s, ^(void) {
453 T_EXPECT_GE(stacks_seen, 1U, "saw at least one stack");
454 ktrace_session_destroy(s);
455 kperf_reset();
456 T_END;
457 });
458
459 T_QUIET; T_ASSERT_POSIX_SUCCESS(kperf_sample_set(1), NULL);
460
461 /* wait until the thread that will be backtraced is spinning */
462 dispatch_semaphore_wait(wait_for_spinning, DISPATCH_TIME_FOREVER);
463
464 T_ASSERT_POSIX_ZERO(ktrace_start(s, dispatch_get_main_queue()), NULL);
465
466 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, TEST_TIMEOUT_NS),
467 dispatch_get_main_queue(), ^(void)
468 {
469 T_LOG("ending test after timeout");
470 ktrace_end(s, 0);
471 });
472
473 dispatch_main();
474 }
475
476 static volatile bool spin = true;
477
478 __attribute__((noinline, not_tail_called))
479 static void
480 recurse_spin(dispatch_semaphore_t notify_sema, int depth)
481 {
482 if (depth > 0) {
483 recurse_spin(notify_sema, depth - 1);
484 } else {
485 dispatch_semaphore_signal(notify_sema);
486 while (spin);
487 }
488 }
489
490 static void *
491 spin_thread(void *arg)
492 {
493 dispatch_semaphore_t notify_sema = arg;
494 dispatch_semaphore_signal(backtrace_started);
495 recurse_spin(notify_sema, 257);
496 return NULL;
497 }
498
499 T_DECL(kperf_ustack_trunc, "ensure stacks are marked as truncated")
500 {
501 start_controlling_ktrace();
502
503 ktrace_session_t s = ktrace_session_create();
504 T_ASSERT_NOTNULL(s, "ktrace session was created");
505
506 ktrace_set_collection_interval(s, 100);
507
508 T_QUIET;
509 T_ASSERT_POSIX_ZERO(ktrace_filter_pid(s, getpid()), NULL);
510
511 configure_kperf_stacks_timer(getpid(), 10, false);
512
513 __block bool saw_stack = false;
514 ktrace_set_completion_handler(s, ^{
515 T_EXPECT_TRUE(saw_stack, "saw the user stack");
516 T_END;
517 });
518
519 dispatch_semaphore_t notify_sema = dispatch_semaphore_create(0);
520 uint64_t tid = create_backtrace_thread(spin_thread, notify_sema);
521
522 ktrace_events_single(s, PERF_STK_UHDR, ^(struct trace_point *tp) {
523 if (tp->threadid != tid) {
524 return;
525 }
526 T_LOG("found %llu frame stack", tp->arg2);
527 T_EXPECT_BITS_SET(tp->arg1, CALLSTACK_VALID,
528 "found valid callstack");
529 T_EXPECT_BITS_SET(tp->arg1, CALLSTACK_TRUNCATED,
530 "found truncated callstack");
531 saw_stack = true;
532 ktrace_end(s, 1);
533 });
534
535 T_QUIET; T_ASSERT_POSIX_SUCCESS(kperf_sample_set(1), NULL);
536
537 T_ASSERT_POSIX_ZERO(ktrace_start(s, dispatch_get_main_queue()),
538 "start tracing");
539
540 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, TEST_TIMEOUT_NS),
541 dispatch_get_main_queue(), ^(void)
542 {
543 T_LOG("ending test after timeout");
544 ktrace_end(s, 0);
545 });
546
547 dispatch_main();
548 }
549
550 T_DECL(kperf_ustack_maxlen, "ensure stacks up to 256 frames can be captured")
551 {
552 start_controlling_ktrace();
553
554 ktrace_session_t s = ktrace_session_create();
555 T_ASSERT_NOTNULL(s, "ktrace session was created");
556
557 ktrace_set_collection_interval(s, 100);
558
559 T_QUIET;
560 T_ASSERT_POSIX_ZERO(ktrace_filter_pid(s, getpid()), NULL);
561
562 configure_kperf_stacks_timer(getpid(), 10, false);
563
564 __block bool saw_stack = false;
565 __block bool saw_stack_data = false;
566 __block uint64_t nevents = 0;
567 ktrace_set_completion_handler(s, ^{
568 T_EXPECT_TRUE(saw_stack, "saw the user stack");
569 T_LOG("saw %" PRIu64 " stack data events", nevents);
570 T_EXPECT_TRUE(saw_stack_data, "saw all frames of the user stack");
571 T_END;
572 });
573
574 dispatch_semaphore_t notify_sema = dispatch_semaphore_create(0);
575 uint64_t tid = create_backtrace_thread(spin_thread, notify_sema);
576
577 ktrace_events_single(s, PERF_STK_UHDR, ^(struct trace_point *tp) {
578 if (tp->threadid != tid) {
579 return;
580 }
581 T_LOG("found %llu frame stack", tp->arg2);
582 T_EXPECT_BITS_SET(tp->arg1, CALLSTACK_VALID,
583 "found valid callstack");
584 T_EXPECT_EQ(tp->arg2, UINT64_C(256),
585 "found the correct number of frames");
586 saw_stack = true;
587 });
588
589 ktrace_events_single(s, PERF_STK_UDATA, ^(struct trace_point *tp) {
590 if (tp->threadid != tid && !saw_stack) {
591 return;
592 }
593 nevents++;
594 if (nevents == 256 / 4) {
595 ktrace_end(s, 1);
596 }
597 saw_stack_data = true;
598 });
599
600 T_QUIET; T_ASSERT_POSIX_SUCCESS(kperf_sample_set(1), NULL);
601
602 T_ASSERT_POSIX_ZERO(ktrace_start(s, dispatch_get_main_queue()),
603 "start tracing");
604
605 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, TEST_TIMEOUT_NS),
606 dispatch_get_main_queue(), ^(void)
607 {
608 T_LOG("ending test after timeout");
609 ktrace_end(s, 0);
610 });
611
612 dispatch_main();
613 }
614
615 /* TODO test kernel stacks in all modes */
616 /* TODO legacy PET mode backtracing */