// SPDX-License-Identifier: Apache-2.0 // // /dev/dbg -- asking the kernel a question. See include/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/plan/debugger.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 "types.h" #include "defs.h" #include "param.h" #include "spinlock.h" #include "sleeplock.h" #include "fs.h" #include "file.h" #include "errno.h" #include "sbuf.h" #include "dbg.h" #define NVIEW 24 #define DBGBUF (16 * 1024) struct view { const char *name; dbg_view_fn fn; const char *what; }; static struct view views[NVIEW]; static int nview; // 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; void dbg_register(const char *name, dbg_view_fn fn, const char *what) { if (nview >= NVIEW || !name || !fn) return; views[nview].name = name; views[nview].fn = fn; views[nview].what = what ? what : ""; nview++; } 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); sb_putc(out, '\n'); } sb_puts(out, "\n all every view above\n"); sb_puts(out, " help this\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) { 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; } for (int i = 0; i < nview; i++) { if (streq(word, views[i].name)) { 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) { struct sbuf sb; sb.p = dbgbuf; sb.len = 0; sb.cap = DBGBUF - 64; if (cmd) dbg_command(cmd, &sb); 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; } void dbg_dump_all(void) { if (!dbginit) { initlock(&dbglock, "dbg"); dbginit = 1; } acquire(&dbglock); dbglen = fill(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)ip; (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); 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; }