1#include <bootstage_condition.h>
2#include <compiler/intrinsic.h>
3#include <console/printf.h>
4#include <dbg.h>
5#include <linker/symbol_table.h>
6#include <linker/symbols.h>
7#include <log.h>
8#include <math/min_max.h>
9#include <math/range.h>
10#include <mem/alloc.h>
11#include <mem/nofail.h>
12#include <mem/vmm.h>
13#include <ndjson.h>
14#include <sch/sched.h>
15#include <smp/core.h>
16#include <stdarg.h>
17#include <stddef.h>
18#include <stdint.h>
19#include <string.h>
20#include <stringify.h>
21#include <structures/locked_list.h>
22#include <thread/thread.h>
23#include <time/time.h>
24
25NDJSON_DEFINE(log_message, NDJSON_SECTION_LOG, NDJSON_KIND_MESSAGE, 1,
26 NDJSON_STR(site), NDJSON_STR(level), NDJSON_STR(msg),
27 NDJSON_STR(file), NDJSON_U64(line), NDJSON_STR(func));
28
29#define LOG_IMPORTANT_RETRY 32
30LOG_SITE_DEFINE(global, .flags = LOG_SITE_DEFAULT,
31 .capacity = LOG_SITE_CAPACITY_DEFAULT,
32 .dump_opts = LOG_DUMP_CONSOLE, .enabled_mask = LOG_SITE_ALL);
33
34LOG_HANDLE_DEFINE(global, .flags = LOG_HANDLE_PRINT);
35
36struct log_globals {
37 bool initialized;
38 struct locked_list list;
39};
40
41struct log_globals log_global = {0};
42
43/* NULL until the blob has been stamped, which only an unpatched image should
44 * ever hit. The table cannot go stale any more: it is written into the linked
45 * kernel after the fact, so it always describes this exact image */
46static const struct kernel_syms_hdr *syms_header(void) {
47 const struct kernel_syms_hdr *hdr = (const void *) kernel_syms_blob;
48
49 if (hdr->magic != KERNEL_SYMS_MAGIC || !hdr->count)
50 return NULL;
51
52 return hdr;
53}
54
55static void syms_warn_if_missing(void) {
56 if (syms_header())
57 return;
58
59 printf(format: " <no symbol table: .kernel_syms was never stamped, "
60 "rebuild to symbolize>\n");
61}
62
63static const char *find_symbol(uint64_t addr, uint64_t *out_sym_addr) {
64 const struct kernel_syms_hdr *hdr = syms_header();
65
66 if (out_sym_addr)
67 *out_sym_addr = 0;
68
69 if (!hdr)
70 return NULL;
71
72 const struct kernel_sym *tab = (const void *) (hdr + 1);
73 const char *strtab = kernel_syms_blob + hdr->strtab_off;
74
75 /* Sorted by address, so bisect for the last entry at or below addr. Worth
76 * it here: this runs from panic paths where a scan of every symbol is the
77 * last thing we want */
78 uint32_t lo = 0, hi = hdr->count;
79 while (lo < hi) {
80 uint32_t mid = lo + (hi - lo) / 2;
81 if (tab[mid].addr <= addr)
82 lo = mid + 1;
83 else
84 hi = mid;
85 }
86
87 if (!lo)
88 return NULL;
89
90 const struct kernel_sym *best = &tab[lo - 1];
91
92 if (out_sym_addr)
93 *out_sym_addr = best->addr;
94
95 return strtab + best->name_off;
96}
97
98static const struct kernel_lines_hdr *lines_header(void) {
99 const struct kernel_syms_hdr *hdr = syms_header();
100
101 if (!hdr || !hdr->lines_off)
102 return NULL;
103
104 const struct kernel_lines_hdr *lines =
105 (const void *) (kernel_syms_blob + hdr->lines_off);
106
107 if (lines->magic != KERNEL_LINES_MAGIC || !lines->count)
108 return NULL;
109
110 return lines;
111}
112
113static uint64_t uleb_next(const uint8_t **p, const uint8_t *end) {
114 uint64_t v = 0;
115 unsigned shift = 0;
116
117 while (*p < end) {
118 uint8_t byte = *(*p)++;
119
120 v |= (uint64_t) (byte & 0x7f) << shift;
121 if (!(byte & 0x80))
122 break;
123
124 shift += 7;
125 if (shift >= 64)
126 break;
127 }
128
129 return v;
130}
131
132static int64_t unzigzag(uint64_t v) {
133 return (v & 1) ? -(int64_t) (v >> 1) - 1 : (int64_t) (v >> 1);
134}
135
136/* Name of the file'th entry in the table, or NULL if it runs off the end */
137static const char *lines_file_name(const struct kernel_lines_hdr *lines,
138 int64_t want) {
139 if (want < 0)
140 return NULL;
141
142 const char *base = (const char *) lines + lines->files_off;
143 const char *end = base + lines->files_len;
144
145 for (int64_t i = 0; i < want; i++) {
146 while (base < end && *base)
147 base++;
148
149 if (base >= end)
150 return NULL;
151
152 base++; /* past the NUL */
153 }
154
155 return (base < end) ? base : NULL;
156}
157
158static const char *find_line(uint64_t addr, uint32_t *out_line) {
159 const struct kernel_lines_hdr *lines = lines_header();
160
161 if (out_line)
162 *out_line = 0;
163
164 if (!lines)
165 return NULL;
166
167 const uint8_t *p = (const uint8_t *) lines + lines->stream_off;
168 const uint8_t *end = p + lines->stream_len;
169
170 uint64_t cur = lines->base_addr;
171 int64_t file = 0, line = 0;
172 int64_t best_file = -1, best_line = 0;
173 bool found = false;
174
175 for (uint32_t i = 0; i < lines->count && p < end; i++) {
176 cur += uleb_next(p: &p, end);
177 file += unzigzag(v: uleb_next(p: &p, end));
178 line += unzigzag(v: uleb_next(p: &p, end));
179
180 if (cur > addr)
181 break;
182
183 best_file = file;
184 best_line = line;
185 found = true;
186 }
187
188 if (!found || best_line <= 0)
189 return NULL;
190
191 const char *name = lines_file_name(lines, want: best_file);
192 if (!name)
193 return NULL;
194
195 if (out_line)
196 *out_line = (uint32_t) best_line;
197
198 return name;
199}
200
201static void k_printf_from_log(const char *fmt, const uint64_t *args,
202 uint8_t nargs, void (*print)(const char *, ...)) {
203 switch (nargs) {
204 case 0: print(fmt); break;
205 case 1: print(fmt, args[0]); break;
206 case 2: print(fmt, args[0], args[1]); break;
207 case 3: print(fmt, args[0], args[1], args[2]); break;
208 case 4: print(fmt, args[0], args[1], args[2], args[3]); break;
209 case 5: print(fmt, args[0], args[1], args[2], args[3], args[4]); break;
210 case 6:
211 print(fmt, args[0], args[1], args[2], args[3], args[4], args[5]);
212 break;
213 case 7:
214 print(fmt, args[0], args[1], args[2], args[3], args[4], args[5],
215 args[6]);
216 break;
217 case 8:
218 print(fmt, args[0], args[1], args[2], args[3], args[4], args[5],
219 args[6], args[7]);
220 break;
221 case 9:
222 print(fmt, args[0], args[1], args[2], args[3], args[4], args[5],
223 args[6], args[7], args[8]);
224 break;
225 case 10:
226 print(fmt, args[0], args[1], args[2], args[3], args[4], args[5],
227 args[6], args[7], args[8], args[9]);
228 break;
229 default: print("<invalid nargs>");
230 }
231}
232
233STRINGIFY_DECLARE_ENUM(log_level) {
234 switch (stringify_in) {
235 case LOG_TRACE: return "trace";
236 case LOG_DEBUG: return "debug";
237 case LOG_INFO: return "info";
238 case LOG_WARN: return "warn";
239 case LOG_ERROR: return "error";
240 default: cc_unreachable();
241 }
242}
243
244static void snprintf_from_log(char *buf, size_t size, const char *fmt,
245 const uint64_t *args, uint8_t nargs) {
246 if (!fmt) {
247 buf[0] = '\0';
248 return;
249 }
250 switch (nargs) {
251 case 0: snprintf(buf, size, "%s", fmt); break;
252 case 1: snprintf(buf, size, fmt, args[0]); break;
253 case 2: snprintf(buf, size, fmt, args[0], args[1]); break;
254 case 3: snprintf(buf, size, fmt, args[0], args[1], args[2]); break;
255 case 4: snprintf(buf, size, fmt, args[0], args[1], args[2], args[3]); break;
256 case 5:
257 snprintf(buf, size, fmt, args[0], args[1], args[2], args[3], args[4]);
258 break;
259 case 6:
260 snprintf(buf, size, fmt, args[0], args[1], args[2], args[3], args[4],
261 args[5]);
262 break;
263 case 7:
264 snprintf(buf, size, fmt, args[0], args[1], args[2], args[3], args[4],
265 args[5], args[6]);
266 break;
267 case 8:
268 snprintf(buf, size, fmt, args[0], args[1], args[2], args[3], args[4],
269 args[5], args[6], args[7]);
270 break;
271 case 9:
272 snprintf(buf, size, fmt, args[0], args[1], args[2], args[3], args[4],
273 args[5], args[6], args[7], args[8]);
274 break;
275 case 10:
276 snprintf(buf, size, fmt, args[0], args[1], args[2], args[3], args[4],
277 args[5], args[6], args[7], args[8], args[9]);
278 break;
279 default: snprintf(buf, size, "<invalid nargs>"); break;
280 }
281}
282
283static void log_emit_ndjson_record(const struct log_site *site,
284 const struct log_record *rec) {
285 if (!ndjson_carrier_online())
286 return;
287
288 char msg_buf[256];
289 enforce(rec->fmt);
290 snprintf_from_log(buf: msg_buf, size: sizeof(msg_buf), fmt: rec->fmt, args: rec->args,
291 nargs: rec->nargs);
292
293 ndjson_emit(log_message, .site = site->name ? site->name : "unknown",
294 .level = log_level_to_str(rec->level), .msg = msg_buf,
295 .file = rec->caller_file, .line = (uint64_t) rec->caller_line,
296 .func = rec->caller_fn);
297}
298
299static void log_dump_record(const struct log_site *site,
300 const struct log_record *rec,
301 const struct log_dump_options opts,
302 void (*print)(const char *f, ...)) {
303 size_t sec = MS_TO_SECONDS(rec->timestamp);
304 size_t msec = rec->timestamp % 1000;
305 if (sec == 0 && msec == 0) {
306 print("[X.XXX] %s%s%s: ", log_level_color(l: rec->level), site->name,
307 ANSI_RESET);
308 } else {
309 if (!rec->handle->print) {
310 print("[%llu.%03llu] %s%s%s: ", sec, msec,
311 log_level_color(l: rec->level), site->name, ANSI_RESET);
312 } else {
313 rec->handle->print(site, rec, print);
314 }
315 }
316
317 if (opts.show_cpu)
318 print("cpu=%u ", rec->cpu);
319
320 if (opts.show_tid)
321 print("tid=%u ", rec->tid);
322
323 if (opts.show_irql)
324 print("irql=%d ", rec->logged_at_irql);
325
326 /* message */
327 enforce(rec->fmt);
328 k_printf_from_log(fmt: rec->fmt, args: rec->args, nargs: rec->nargs, print);
329
330 if (opts.show_caller) {
331 print(" <+ at %s()", rec->caller_fn);
332 }
333
334 if (!(rec->handle->flags & LOG_HANDLE_NO_NEWLINE))
335 print("\n");
336}
337
338static void log_dump_record_locked(const struct log_site *site,
339 const struct log_record *rec,
340 const struct log_dump_options opts) {
341 enum irql irql = printf_lock();
342 log_dump_record(site, rec, opts, print: printf_unlocked);
343 printf_unlock(i: irql);
344}
345
346static inline bool log_ringbuf_try_enqueue(struct log_site *site,
347 struct log_ringbuf *rb,
348 const struct log_record *rec) {
349 uint64_t pos;
350 struct log_ring_slot *slot;
351
352 while (true) {
353 pos = atomic_load_rlx(&rb->head);
354 slot = &rb->slots[pos % site->capacity];
355
356 uint64_t seq = atomic_load_acq(&slot->seq);
357 int64_t diff = (int64_t) seq - (int64_t) pos;
358
359 if (diff == 0) {
360 if (atomic_cas_weak(&rb->head, &pos, pos + 1, mo_acq_rel, mo_rlx)) {
361
362 slot->rec = *rec;
363
364 atomic_store_release(&slot->seq, pos + 1);
365 return true;
366 }
367 } else if (diff < 0) {
368 return false;
369 }
370 }
371}
372
373static inline bool log_ringbuf_try_dequeue(struct log_site *site,
374 struct log_ringbuf *rb,
375 struct log_record *out) {
376 uint64_t pos;
377 struct log_ring_slot *slot;
378
379 while (true) {
380 pos = atomic_load_rlx(&rb->tail);
381 slot = &rb->slots[pos % site->capacity];
382
383 uint64_t seq = atomic_load_acq(&slot->seq);
384 int64_t diff = (int64_t) seq - (int64_t) (pos + 1);
385
386 if (diff == 0) {
387 if (atomic_cas_weak(&rb->tail, &pos, pos + 1, mo_acq_rel, mo_rlx)) {
388
389 *out = slot->rec;
390
391 atomic_store_release(&slot->seq, pos + site->capacity);
392 return true;
393 }
394 } else if (diff < 0) {
395 return false;
396 }
397 }
398}
399
400static bool log_ringbuf_force_enqueue(struct log_site *site,
401 struct log_ringbuf *rb,
402 const struct log_record *rec) {
403 struct log_record dummy;
404 log_ringbuf_try_dequeue(site, rb, out: &dummy);
405 return log_ringbuf_try_enqueue(site, rb, rec);
406}
407
408static void log_dump_site_pinned(struct log_site *site,
409 struct log_dump_options opts) {
410 struct log_record rec;
411 enum irql irql = printf_lock();
412 while (log_ringbuf_try_dequeue(site, rb: &site->rb, out: &rec)) {
413 if (rec.level < opts.min_level)
414 continue;
415
416 if (site->dropped) {
417 printf_unlocked(format: "!! dropped %u log records !!\n", site->dropped);
418 site->dropped = 0;
419 }
420
421 log_dump_record(site, rec: &rec, opts, print: printf_unlocked);
422 }
423 printf_unlock(i: irql);
424}
425
426void log_dump_site_with_opts(struct log_site *site,
427 struct log_dump_options opts) {
428 if (!site || !log_site_getref(site))
429 return;
430
431 log_dump_site_pinned(site, opts);
432 log_site_putref(site);
433}
434
435void log_dump_site_default(struct log_site *site) {
436 log_dump_site_with_opts(site, LOG_DUMP_DEFAULT);
437}
438
439void log_dump_site(struct log_site *site) {
440 log_dump_site_with_opts(site, opts: site->dump_opts);
441}
442
443void log_emit_full(struct log_site *site, struct log_handle *handle,
444 enum log_level ll, const char *func, const char *file,
445 int32_t line, uintptr_t ip, uint8_t narg, char *fmt, ...) {
446 if (!site || !log_site_getref(site))
447 return;
448
449 enum log_level level = ll;
450 struct log_record rec = {0};
451 rec.handle = handle;
452 rec.level = level;
453
454 rec.fmt = fmt;
455
456 rec.caller_pc = ip;
457 rec.caller_fn = (char *) func;
458 rec.caller_file = (char *) file;
459 rec.caller_line = line;
460
461 /* pack args */
462 va_list ap;
463 va_start(ap, fmt);
464 for (int i = 0; i < narg && i < 10; i++) {
465 rec.args[i] = va_arg(ap, uint64_t);
466 rec.nargs++;
467 }
468 va_end(ap);
469
470 struct log_dump_options dopts = site->dump_opts;
471
472 BOOTSTAGE_IF_LT(BOOTSTAGE_LATE) {
473 if (log_handle_should_print(h: handle, s: site, level))
474 return log_dump_record(site, rec: &rec, opts: dopts, print: printf);
475 }
476
477 if (site->flags & LOG_SITE_NO_IRQ && irq_in_interrupt())
478 return;
479
480 if (!log_site_enabled(ss: site, level))
481 return;
482
483 rec.timestamp = time_get_ms();
484 rec.cpu = smp_id_raw(); /* Merely a snapshot */
485 rec.tid = thread_get_current()->id;
486 rec.logged_at_irql = irql_get();
487
488 if (irq_in_interrupt())
489 rec.flags |= LOG_REC_FROM_IRQ;
490
491 if (handle->flags & LOG_HANDLE_ONCE) {
492 if (atomic_fetch_add(&handle->seen_internal, 1) != 0)
493 return;
494 }
495
496 if (handle->flags & LOG_HANDLE_RATELIMIT) {
497 uint64_t now = rec.timestamp;
498 uint64_t last = atomic_load(&handle->last_ts_internal);
499
500 if (now - last < 100)
501 return;
502
503 atomic_store(&handle->last_ts_internal, now);
504 }
505
506 bool queued = log_ringbuf_try_enqueue(site, rb: &site->rb, rec: &rec);
507
508 if (!queued) {
509 if (handle->flags & LOG_HANDLE_IMPORTANT) {
510 if (site->flags & LOG_SITE_DROP_OLD) {
511 queued = log_ringbuf_force_enqueue(site, rb: &site->rb, rec: &rec);
512 } else if (!irq_in_interrupt()) {
513 for (int i = 0; i < LOG_IMPORTANT_RETRY; i++) {
514 if (log_ringbuf_try_enqueue(site, rb: &site->rb, rec: &rec)) {
515 queued = true;
516 break;
517 }
518 cpu_pause();
519 }
520 }
521
522 if (!queued) {
523 /* last-resort visibility */
524 log_dump_record_locked(site, rec: &rec, opts: dopts);
525 }
526 } else {
527 site->dropped++;
528 }
529 }
530
531 if (log_handle_should_print(h: handle, s: site, level)) {
532 log_dump_record_locked(site, rec: &rec, opts: dopts);
533 }
534
535 if (site->flags & LOG_SITE_NDJSON) {
536 log_emit_ndjson_record(site, rec: &rec);
537 }
538
539 if ((handle->flags & LOG_HANDLE_PANIC) && level >= LOG_ERROR) {
540 log_dump_all();
541 debug_print_stack();
542 panic("fatal log event");
543 }
544
545 log_site_putref(site);
546}
547
548void log_sites_init(void) {
549 log_global.initialized = true;
550 locked_list_init(ll: &log_global.list, LOCKED_LIST_INIT_NORMAL);
551
552 for (struct log_site *s = __skernel_log_sites; s < __ekernel_log_sites;
553 s++) {
554 list_node_init(n: &s->list);
555 refcount_init(rc: &s->refcount, val: 1);
556 struct log_ringbuf *lrb = &s->rb;
557 enforce(s->capacity);
558 lrb->slots = nofail_vnews(lrb->slots, s->capacity, ALLOC_ZERO);
559
560 for (size_t i = 0; i < s->capacity; i++) {
561 atomic_store_release(&lrb->slots[i].seq, i);
562 }
563
564 locked_list_add(ll: &log_global.list, lh: &s->list);
565 }
566}
567
568void log_dump_all(void) {
569 enum irql irql = spin_lock_high(&log_global.list.lock);
570
571 struct log_site *site;
572 list_for_each_entry(site, &log_global.list.list, list) {
573 log_dump_site_pinned(site, opts: site->dump_opts);
574 }
575
576 spin_unlock(&log_global.list.lock, irql);
577}
578
579void log_dump_panic(void) TSA_NO_ANALYSIS {
580
581 struct log_site *site;
582 list_for_each_entry(site, &log_global.list.list, list) {
583 if (site->flags & LOG_SITE_PANIC_VISIBLE)
584 log_dump_site_pinned(site, opts: site->dump_opts);
585 }
586}
587
588void log_site_free(struct log_site *site) {
589 locked_list_del(ll: &log_global.list, lh: &site->list);
590 vfree(site->rb.slots);
591 vfree(site->name);
592 vfree_aligned(site, cc_alignof(struct log_site));
593}
594
595struct log_site *log_site_create(struct log_site_options opts) {
596 struct log_site *ret = valloc_aligned(
597 sizeof(struct log_site), cc_alignof(struct log_site), ALLOC_ZERO);
598 if (!ret)
599 return NULL;
600
601 ret->name = strdup(str: opts.name);
602 if (!ret->name)
603 goto err;
604
605 struct log_ring_slot *slots = vnews(slots, opts.capacity, ALLOC_ZERO);
606 if (!slots)
607 goto err;
608
609 ret->msg_max_len = opts.msg_max_len;
610 ret->dump_opts = opts.dump_opts;
611 ret->enabled_mask = opts.enabled_mask;
612 ret->capacity = opts.capacity;
613 ret->rb.slots = slots;
614 refcount_init(rc: &ret->refcount, val: 1);
615 ret->dropped = 0;
616 ret->flags = opts.flags;
617 list_node_init(n: &ret->list);
618 for (size_t i = 0; i < opts.capacity; i++) {
619 atomic_store_release(&slots[i].seq, i);
620 }
621
622 locked_list_add(ll: &log_global.list, lh: &ret->list);
623
624 return ret;
625
626err:
627
628 if (ret) {
629 vfree(ret->rb.slots);
630 vfree(ret->name);
631 }
632
633 vfree_aligned(ret, cc_alignof(struct log_site));
634 return NULL;
635}
636
637static bool stack_addr_readable(uint64_t addr) {
638 return vmm_get_phys(PAGE_ALIGN_DOWN(addr)) != (uintptr_t) -1;
639}
640
641/* Frame holds caller's saved rbp at [0] and ret addr at [1], so both
642 * qwords have to be there */
643static bool stack_frame_readable(uint64_t frame) {
644 if (!frame || (frame & (sizeof(uint64_t) - 1)))
645 return false;
646
647 return stack_addr_readable(addr: frame) &&
648 stack_addr_readable(addr: frame + sizeof(uint64_t));
649}
650
651static bool stack_addr_is_text(uint64_t addr) {
652 return addr >= (uint64_t) &__stext && addr < (uint64_t) &__etext;
653}
654
655/* Use the rbp chain that -fno-omit-frame-pointer builds, and collect
656 * them all into `entries`, returning the number of entries found */
657size_t stack_unwind(uint64_t frame, uint64_t *entries, size_t max) {
658 size_t nr = 0;
659 max = MIN(max, STACK_TRACE_MAX_DEPTH);
660
661 while (nr < max) {
662 if (!stack_frame_readable(frame))
663 break;
664
665 uint64_t next = ((const uint64_t *) frame)[0];
666 uint64_t ret = ((const uint64_t *) frame)[1];
667
668 if (!stack_addr_is_text(addr: ret))
669 break;
670
671 if (entries)
672 entries[nr] = ret;
673
674 nr++;
675 if (next <= frame)
676 break;
677
678 frame = next;
679 }
680
681 return nr;
682}
683
684const char *debug_symbolize(uint64_t addr, uint64_t *out_off) {
685 uint64_t base = 0;
686 const char *sym = find_symbol(addr, out_sym_addr: &base);
687
688 if (out_off)
689 *out_off = sym ? addr - base : 0;
690
691 return sym;
692}
693
694const char *debug_line_for(uint64_t addr, uint32_t *out_line) {
695 return find_line(addr, out_line);
696}
697
698bool debug_syms_present(void) {
699 return syms_header() != NULL;
700}
701
702void debug_print_stack_trace(const uint64_t *entries, size_t nr) {
703 if (!nr) {
704 printf(format: " <no kernel frames found>\n");
705 return;
706 }
707
708 syms_warn_if_missing();
709
710 for (size_t i = 0; i < nr; i++) {
711 uint64_t sym_addr;
712 const char *sym = find_symbol(addr: entries[i], out_sym_addr: &sym_addr);
713
714 if (sym) {
715 printf(format: " #%-2zu [0x%016lx] %s+0x%lx\n", i, entries[i], sym,
716 entries[i] - sym_addr);
717 } else {
718 printf(format: " #%-2zu [0x%016lx] <unknown>\n", i, entries[i]);
719 }
720
721 uint32_t line;
722 const char *file = find_line(addr: entries[i] - 1, out_line: &line);
723
724 if (file)
725 printf(format: " at %s:%u\n", file, line);
726 }
727}
728
729void debug_print_stack(void) {
730 uint64_t entries[STACK_TRACE_MAX_DEPTH];
731
732 size_t nr = stack_unwind(frame: (uint64_t) ci_frame_address(0), entries,
733 max: sizeof(entries) / sizeof(*entries));
734
735 debug_print_stack_trace(entries, nr);
736}
737
738void debug_print_memory(void *addr, uint64_t size) {
739 uint8_t *ptr = (uint8_t *) addr;
740 printf(format: "Memory at %p:\n", addr);
741 for (uint64_t i = 0; i < size; i++) {
742 if (i % 16 == 0) {
743 if (i != 0)
744 printf(format: "\n");
745 printf(format: "%p: ", (void *) (ptr + i));
746 }
747 printf(format: "%02x ", ptr[i]);
748 }
749 printf(format: "\n");
750}
751
752void debug_print_stack_from(uint64_t *start, size_t max_scan) {
753 int hits = 0;
754 uint8_t *last_checked_page = NULL;
755
756 if (!max_scan)
757 max_scan = 64 * 1024;
758
759 syms_warn_if_missing();
760
761 printf(format: "Stack unwind from %p:\n", (void *) start);
762
763 for (size_t offset = 0; offset < max_scan; offset += sizeof(uint64_t)) {
764 uint8_t *addr = (uint8_t *) start + offset;
765 uint8_t *page_base = (uint8_t *) PAGE_ALIGN_DOWN(addr);
766
767 if (page_base != last_checked_page) {
768 if (vmm_get_phys((vaddr_t) page_base) == (uintptr_t) -1)
769 break;
770 last_checked_page = page_base;
771 }
772
773 uint64_t val = *(uint64_t *) addr;
774
775 if (IN_RANGE(val, UINT64_C(0xffffffff80000000), UINT64_MAX)) {
776 uint64_t sym_addr;
777 const char *sym = find_symbol(addr: val, out_sym_addr: &sym_addr);
778 if (sym) {
779 printf(format: " [0x%016lx] %s+0x%lx (sp=0x%016lx)\n", val, sym,
780 val - sym_addr, (uint64_t) addr);
781 hits++;
782 }
783 }
784 }
785
786 if (hits == 0)
787 printf(format: " <no kernel symbols found>\n");
788}
789