// SPDX-License-Identifier: GPL-2.0-or-later // // /dev/dbg -- asking the kernel a question. See include/core/dbg.h. // // The device is a conversation: write a line, read the answer. // // # echo dma > /dev/dbg && cat /dev/dbg // // That shape is chosen so the debugger is usable with the tools already on the // machine, before `schdbg` is built and on a system where it will not start. // docs/architecture/debugging.md argues the case at length; the short version is that a // protocol you cannot drive by hand is a protocol you cannot debug. // // One conversation at a time, deliberately. The answer buffer is global and // the lock is held only while it is being filled, so two programs asking at // once get one answer each, in some order, and neither gets half of the // other's. Making it per-open would mean a struct file hook for something that // has exactly one user. #include "core/types.h" #include "core/defs.h" #include "core/param.h" #include "core/spinlock.h" #include "core/sleeplock.h" #include "fs/fs.h" #include "fs/file.h" #include "uapi/errno.h" #include "core/sbuf.h" #include "arch/memlayout.h" #include "arch/mmu.h" #include "core/dbg.h" #define NVIEW 24 #define DBGBUF (16 * 1024) struct view { const char *name; dbg_view_fn fn; const char *what; int rw; // changes something; see dbg_register_rw }; static struct view views[NVIEW]; static int nview; struct claimant { const char *name; dbg_whose_fn fn; }; static struct claimant claimants[NVIEW]; static int nclaim; static int streq(const char *a, const char *b); // Static, and 16 KB of it. A view of every process on a 64-slot table is the // largest thing here and comes to a few kilobytes; the alternative -- kalloc // on every read -- would put an allocation on the path of the tool you reach // for when the allocator is what you are investigating. static char dbgbuf[DBGBUF]; static int dbglen; static struct spinlock dbglock; static int dbginit; static void dbg_register_at(const char *name, dbg_view_fn fn, const char *what, int rw) { if (nview >= NVIEW || !name || !fn) return; // Registering the same name twice is a bug in the caller -- it means an init // that runs once per CPU, which is how `watch` first appeared in the list // twice. Refusing keeps the list honest whoever makes that mistake next. for (int i = 0; i < nview; i++) if (streq(views[i].name, name)) return; views[nview].name = name; views[nview].fn = fn; views[nview].what = what ? what : ""; views[nview].rw = rw; nview++; } void dbg_register(const char *name, dbg_view_fn fn, const char *what) { dbg_register_at(name, fn, what, 0); } void dbg_register_rw(const char *name, dbg_view_fn fn, const char *what) { dbg_register_at(name, fn, what, 1); } void dbg_register_whose(const char *name, dbg_whose_fn fn) { if (nclaim >= NVIEW || !name || !fn) return; for (int i = 0; i < nclaim; i++) if (streq(claimants[i].name, name)) return; claimants[nclaim].name = name; claimants[nclaim].fn = fn; nclaim++; } static int streq(const char *a, const char *b) { while (*a && *b && *a == *b) { a++; b++; } return *a == 0 && *b == 0; } static void do_help(struct sbuf *out) { sb_puts(out, "views this kernel publishes:\n"); for (int i = 0; i < nview; i++) { sb_puts(out, " "); sb_pad(out, views[i].name, 10); sb_puts(out, views[i].what); if (views[i].rw) sb_puts(out, " [changes state]"); sb_putc(out, '\n'); } sb_puts(out, "\n whose every subsystem's claim on one page\n"); sb_puts(out, " all every view above\n"); sb_puts(out, " help this\n"); } // "0x3eceb000", or "3eceb000", or a decimal. Returns 0 if there was no number. static int parse_addr(const char *s, uint64 *out) { uint64 v = 0; int any = 0, hex = 0; while (*s == ' ') s++; if (s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) { hex = 1; s += 2; } // A bare string of hex digits is taken as hex: every address in this // kernel's own output is printed without the 0x, and retyping one and // getting a decimal reading of it would be a trap. if (!hex) { const char *p = s; while (*p) { if ((*p >= 'a' && *p <= 'f') || (*p >= 'A' && *p <= 'F')) { hex = 1; break; } if (!(*p >= '0' && *p <= '9')) break; p++; } if (!hex) { // All decimal digits, but an address that long is hex in practice. int n = 0; const char *q = s; while (*q >= '0' && *q <= '9') { n++; q++; } if (n > 10) hex = 1; } } while (*s) { int d; if (*s >= '0' && *s <= '9') d = *s - '0'; else if (*s >= 'a' && *s <= 'f') d = *s - 'a' + 10; else if (*s >= 'A' && *s <= 'F') d = *s - 'A' + 10; else break; if (!hex && d > 9) break; v = hex ? (v << 4) + d : v * 10 + d; any = 1; s++; } *out = v; return any; } static void do_whose(struct sbuf *out, const char *arg) { struct whose w; uint64 addr, kva; if (!parse_addr(arg, &addr)) { sb_puts(out, "whose -- a physical address, or a kernel virtual one\n"); return; } // Which kind of address is it? Anything at or above KERNBASE is a kernel // virtual address and is translated; anything below is taken as physical. // // A *user* virtual address cannot be resolved here and is not guessed at: // it means nothing without saying which address space, and answering for // whichever one happens to be current would be wrong in a way that looks // right. `vma ` is the view for that question. if (addr >= (uint64)KERNBASE) { kva = addr; addr = addr - (uint64)KERNBASE; } else { kva = addr + (uint64)KERNBASE; } w.phys = addr & ~(uint64)(PGSIZE - 1); w.off = addr & (PGSIZE - 1); w.owners = 0; w.free_owners = 0; w.mappings = 0; sb_puts(out, "phys 0x"); sb_puthex(out, w.phys, 16); if (w.off) { sb_puts(out, " + 0x"); sb_puthex(out, w.off, 3); } sb_puts(out, " kernel va 0x"); sb_puthex(out, (kva & ~(uint64)(PGSIZE - 1)), 16); sb_putc(out, '\n'); if (w.phys >= (uint64)PHYSTOP) sb_puts(out, " (above PHYSTOP -- this is not RAM this kernel manages)\n"); for (int i = 0; i < nclaim; i++) claimants[i].fn(out, &w); // The verdict. Two shapes of wrong are worth naming, because both are // silent everywhere else and both corrupt memory some distance away. sb_putc(out, '\n'); if (w.owners == 0 && w.mappings == 0) { sb_puts(out, " nobody claims this page\n"); } else if (w.owners > 1) { sb_puts(out, " *** "); sb_putu(out, w.owners); sb_puts(out, " owners -- this page is claimed more than once, and whichever\n" " allocator hands it out second will be handing out memory\n" " the first one is still using\n"); } else if (w.free_owners > 0 && w.mappings > 0) { sb_puts(out, " *** its allocator believes this page is free, and "); sb_putu(out, w.mappings); sb_puts(out, " page table(s)\n" " still map it -- it will be handed out from under them\n"); } else if (w.owners == 0) { // Not a finding. kalloc keeps a free list and no record of what it has // handed out, so a page in use by the kernel and a page genuinely lost // look the same from here -- and saying "nobody owns this" about an // ordinary allocated page would be the query crying wolf. sb_puts(out, " no allocator has a record of this page, which is what an\n" " allocated one looks like: kalloc tracks only what is free.\n"); if (w.mappings) { sb_puts(out, " "); sb_putu(out, w.mappings); sb_puts(out, " page table(s) map it, so it is in use.\n"); } } else { sb_puts(out, " consistent: "); sb_putu(out, w.owners); sb_puts(out, " owner, "); sb_putu(out, w.mappings); sb_puts(out, " mapping(s)\n"); } } static void do_all(struct sbuf *out) { for (int i = 0; i < nview; i++) { sb_puts(out, "=== "); sb_puts(out, views[i].name); sb_puts(out, " ===\n"); views[i].fn(out, ""); sb_putc(out, '\n'); } } int dbg_command(const char *line, struct sbuf *out) { return dbg_command_ro(line, out, 0); } // `ro` refuses any view that declared itself as changing state. // // The refusal is driven by the registration rather than by a list here, which // is the whole reason dbg_register_rw() exists: a list of dangerous views in // this file would go stale in the direction where a new one is treated as // safe. int dbg_command_ro(const char *line, struct sbuf *out, int ro) { char word[32], arg[64]; int n = 0; while (*line == ' ' || *line == '\t') line++; while (*line && *line != '\n' && *line != ' ' && n < (int)sizeof(word) - 1) word[n++] = *line++; word[n] = 0; while (*line == ' ' || *line == '\t') line++; n = 0; while (*line && *line != '\n' && n < (int)sizeof(arg) - 1) arg[n++] = *line++; while (n > 0 && (arg[n-1] == ' ' || arg[n-1] == '\r')) n--; arg[n] = 0; if (word[0] == 0 || streq(word, "help")) { do_help(out); return out->len; } if (streq(word, "all")) { do_all(out); return out->len; } if (streq(word, "whose")) { do_whose(out, arg); return out->len; } for (int i = 0; i < nview; i++) { if (streq(word, views[i].name)) { // A view that changes state, asked for through /dev/dbgro. Reading it // with no argument is still allowed, because that is the listing -- // `watch` on its own prints what is armed and arms nothing. if (ro && views[i].rw && arg[0] != 0) { sb_puts(out, "'"); sb_puts(out, word); sb_puts(out, "' changes state, and this is /dev/dbgro.\n" "use /dev/dbg from the console for that.\n"); return out->len; } views[i].fn(out, arg); return out->len; } } sb_puts(out, "no view called '"); sb_puts(out, word); sb_puts(out, "'. try: help\n"); return out->len; } // Fill dbgbuf, and say so if the answer did not fit. // // sb_* silently drops what will not fit, which is the right behaviour for a // builder that must not fail -- but a table that stops in the middle of a row // with no explanation reads as a kernel that is broken rather than as a buffer // that is full. The last 64 bytes are held back to have somewhere to say it. static int fill(const char *cmd, int ro) { struct sbuf sb; sb.p = dbgbuf; sb.len = 0; sb.cap = DBGBUF - 64; if (cmd) dbg_command_ro(cmd, &sb, ro); else do_all(&sb); if (sb.len >= sb.cap - 1) { sb.cap = DBGBUF; sb_puts(&sb, "\n-- truncated: the answer is larger than the buffer. " "ask for one view at a time.\n"); } dbgbuf[sb.len] = 0; return sb.len; } // --- the console key -------------------------------------------------------- // // Ctrl-G runs the debugger with no filesystem, no shell and no client program, // which is what makes it available *during boot* rather than once userspace is // up. docs/architecture/debugging.md calls this level 0, and it is the level that // matters when the thing you are debugging is the boot. // // It cannot do the work in the interrupt handler, though, and the reason is // worth stating because the neighbouring Ctrl-P does exactly that. procdump() // reads the process table with no lock held, deliberately: a lock is a way to // hang while reporting a hang. The views cannot follow that rule -- they read // free lists and socket tables that are meaningless torn -- so they take locks, // and taking a lock from an interrupt handler deadlocks against whatever was // interrupted while holding it. // // So the key *requests* and a kernel thread *performs*. The cost is that a // scheduler too wedged to run a thread will not answer; Ctrl-P still does, and // that division is the honest one. static int dbg_wanted; void dbg_request_dump(void) { dbg_wanted = 1; wakeup(&dbg_wanted); } static void dbg_thread(void) { for (;;) { acquire(&dbglock); while (!dbg_wanted) sleep(&dbg_wanted, &dbglock); dbg_wanted = 0; release(&dbglock); cprintf("\n--- debug views, requested from the console ---\n"); dbg_dump_all(); } } void dbg_thread_start(void) { if (!dbginit) { initlock(&dbglock, "dbg"); dbginit = 1; } if (kthread_create(dbg_thread, "dbgd") < 0) cprintf("dbg: no kernel thread; Ctrl-G will not work\n"); } void dbg_dump_all(void) { if (!dbginit) { initlock(&dbglock, "dbg"); dbginit = 1; } acquire(&dbglock); dbglen = fill(0, 0); // Printed inside the lock so a second sysrq cannot interleave with this one. // cprintf takes cons.lock, which is never held by anything that wants // dbglock, so the order is fixed and there is no cycle. cprintf("%s", dbgbuf); release(&dbglock); } // --- the device ------------------------------------------------------------- // A command. The answer replaces whatever was there. // // **The answer is read from offset zero.** That matters because writing to a // descriptor advances its offset, so a program that writes and then reads on // the same descriptor starts reading as many bytes into the answer as its // command was long -- which is exactly the bug this comment exists to stop // being written again. `cat` is unaffected because it opens the file fresh; // cmd/schdbg seeks back to zero. See lseek() there. int dbg_write(struct inode *ip, uint64 off, char *src, int n) { char line[128]; int i; (void)off; if (n <= 0) return 0; if (n > (int)sizeof(line) - 1) n = (int)sizeof(line) - 1; for (i = 0; i < n; i++) line[i] = src[i]; line[n] = 0; if (!dbginit) { initlock(&dbglock, "dbg"); dbginit = 1; } acquire(&dbglock); dbglen = fill(line, ip && ip->minor == 1); release(&dbglock); return n; // the whole line was consumed either way } int dbg_read(struct inode *ip, uint64 off, char *dst, int n) { int i, avail; (void)ip; if (!dbginit) { initlock(&dbglock, "dbg"); dbginit = 1; } acquire(&dbglock); if (dbglen == 0) { // Nothing asked yet. Answering with the help text rather than with EOF, // because `cat /dev/dbg` on its own is what someone does first and an // empty file teaches them nothing. struct sbuf sb = { dbgbuf, 0, DBGBUF }; do_help(&sb); dbglen = sb.len; } if (off >= (uint64)dbglen) { release(&dbglock); return 0; // EOF } avail = dbglen - (int)off; if (n > avail) n = avail; for (i = 0; i < n; i++) dst[i] = dbgbuf[off + i]; release(&dbglock); return n; }