// SPDX-License-Identifier: GPL-2.0-or-later // // Hardware watchpoints. docs/architecture/debugging.md milestone D4. // // The question this answers is the one no amount of printing gets at: // *something wrote to this address -- who?* Adding a print to every writer // only works when you already know who the writers are, and the bug is // invariably a writer you have not thought of. The processor will simply tell // you, through four debug registers that have been sitting unused in this // kernel since it was written. // // Two decisions worth reading: // // **It reports and continues.** A data watchpoint on x86 is a *trap*, not a // fault -- it is delivered after the storing instruction has completed -- so // returning from the handler carries on with the write already done. That is // what makes this usable on a running system, which docs/architecture/ // debugging.md argues is the whole point: the bugs here are timing bugs, and a // debugger that stops the world cannot see them. The cost is that you cannot // prevent the write, only learn about it. // // **Every CPU watches, without an IPI.** Debug registers are per-CPU, so // arming DR0 on the processor that ran the command would watch one quarter of // a four-way machine and silently miss the rest. There is no general IPI in // this kernel, so instead the table carries a generation number and each CPU // reloads its registers from the timer interrupt when it notices the number // has moved. Worst case a watchpoint takes one tick to cover a CPU, and the // cost when nothing has changed is one comparison per tick. #include "core/types.h" #include "core/defs.h" #include "core/param.h" #include "arch/memlayout.h" #include "arch/mmu.h" #include "arch/x86.h" #include "core/proc.h" #include "arch/traps.h" #include "core/sbuf.h" #include "core/dbg.h" #define NWATCH 4 // DR0..DR3, and there are no more // DR7. The Intel manual's names, because nothing is gained by inventing new // ones for a register that has had these since 1985. #define DR7_L(n) (1u << ((n) * 2)) // local enable #define DR7_G(n) (1u << ((n) * 2 + 1)) // global enable #define DR7_LE (1u << 8) #define DR7_GE (1u << 9) #define DR7_RW_SHIFT(n) (16 + (n) * 4) #define DR7_LEN_SHIFT(n) (18 + (n) * 4) #define RW_WRITE 0x1 // break on data write #define RW_RW 0x3 // break on data read or write struct watchpoint { uint64 addr; int len; // 1, 2, 4 or 8 int rw; // RW_WRITE or RW_RW int used; uint64 hits; }; static struct watchpoint wp[NWATCH]; static volatile uint wp_generation = 1; static uint wp_seen[NCPU]; // Recursion guard. The handler prints, and printing touches memory; a // watchpoint armed on something cprintf writes to would otherwise re-enter // here for ever. Per-CPU because two processors can be in the handler at once // and one must not silence the other. static int wp_busy[NCPU]; // --- the registers ---------------------------------------------------------- static inline uint64 read_dr6(void) { uint64 v; asm volatile("mov %%dr6, %0" : "=r"(v)); return v; } static inline void write_dr6(uint64 v) { asm volatile("mov %0, %%dr6" : : "r"(v)); } static inline void write_dr7(uint64 v) { asm volatile("mov %0, %%dr7" : : "r"(v)); } static void write_dr(int n, uint64 v) { switch (n) { case 0: asm volatile("mov %0, %%dr0" : : "r"(v)); break; case 1: asm volatile("mov %0, %%dr1" : : "r"(v)); break; case 2: asm volatile("mov %0, %%dr2" : : "r"(v)); break; case 3: asm volatile("mov %0, %%dr3" : : "r"(v)); break; } } // The LEN field is not a byte count. 1 byte is 00, 2 is 01, 8 is 10 and 4 is // 11 -- the 8 and 4 encodings are the way round nobody expects, and getting // them backwards produces a watchpoint that silently covers the wrong span. static uint64 len_bits(int len) { switch (len) { case 1: return 0x0; case 2: return 0x1; case 8: return 0x2; default: return 0x3; // 4 } } // Load this CPU's debug registers from the table. static void wp_load(void) { uint64 dr7 = DR7_LE | DR7_GE; for (int i = 0; i < NWATCH; i++) { if (!wp[i].used) { write_dr(i, 0); continue; } write_dr(i, wp[i].addr); // Global, not local. The local bits are cleared by a hardware task switch, // which this kernel never does -- but "watch this everywhere" is what is // meant, and G is the bit that says so. dr7 |= DR7_G(i); dr7 |= (uint64)wp[i].rw << DR7_RW_SHIFT(i); dr7 |= len_bits(wp[i].len) << DR7_LEN_SHIFT(i); } write_dr6(0); write_dr7(dr7); } // Called from the timer interrupt. Cheap when nothing has changed, which is // every tick except the handful after somebody arms or clears a watchpoint. void watch_sync(void) { int c = cpunum(); if (c < 0 || c >= NCPU) return; if (wp_seen[c] == wp_generation) return; wp_seen[c] = wp_generation; wp_load(); } // --- the handler ------------------------------------------------------------ // #DB. Say which watchpoint fired, where the write came from, and carry on. // // Returns 1 if this was ours, so trap() can tell a watchpoint apart from a // single-step or any other source of #DB and not swallow the latter. int watch_trap(struct trapframe *tf) { uint64 dr6 = read_dr6(); int c = cpunum(); int hit = 0; if (!(dr6 & 0xF)) return 0; // a #DB, but not one of ours write_dr6(0); // DR6 bits are sticky; nothing else clears them if (c >= 0 && c < NCPU) { if (wp_busy[c]) return 1; // already reporting; see the note on recursion wp_busy[c] = 1; } for (int i = 0; i < NWATCH; i++) { addr_t pcs[10]; char where[72]; if (!(dr6 & (1u << i))) continue; hit = 1; wp[i].hits++; ksym_fmt(where, sizeof(where), tf->rip); cprintf("watch %d: 0x%p written, %d byte(s), by %s (rip 0x%p) on cpu %d", i, wp[i].addr, wp[i].len, where, tf->rip, c); if (myproc()) cprintf(", pid %d %s", myproc()->pid, myproc()->name); cprintf("\n"); // The chain matters more than the rip. "memset wrote to it" is not an // answer; "memset, called from dma_alloc, called from do_dma" is. getstackpcs((addr_t *)tf->rbp, pcs); for (int j = 0; j < 6 && pcs[j]; j++) { ksym_fmt(where, sizeof(where), pcs[j]); cprintf(" #%d %s\n", j, where); } } if (c >= 0 && c < NCPU) wp_busy[c] = 0; return hit; } // --- arming and clearing ---------------------------------------------------- static int watch_set(uint64 addr, int len, int rw) { int i; if (len != 1 && len != 2 && len != 4 && len != 8) return -1; // The address must be naturally aligned for the length. The processor // ignores the low bits otherwise and watches a span you did not ask for, // which is worse than refusing. if (addr & (uint64)(len - 1)) return -2; for (i = 0; i < NWATCH; i++) if (!wp[i].used) break; if (i == NWATCH) return -3; wp[i].addr = addr; wp[i].len = len; wp[i].rw = rw; wp[i].hits = 0; wp[i].used = 1; wp_generation++; wp_load(); // this CPU now; the rest at their next tick return i; } static int watch_clear(int i) { if (i < 0 || i >= NWATCH || !wp[i].used) return -1; wp[i].used = 0; wp_generation++; wp_load(); return 0; } // --- the command ------------------------------------------------------------ static int parse_num(const char **sp, uint64 *out) { const char *s = *sp; 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; } else { const char *p = s; while (*p && *p != ' ') { if ((*p >= 'a' && *p <= 'f') || (*p >= 'A' && *p <= 'F')) { hex = 1; break; } p++; } } 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++; } *sp = s; *out = v; return any; } static void watch_list(struct sbuf *out) { int any = 0; sb_puts(out, " # address len on hits\n"); for (int i = 0; i < NWATCH; i++) { if (!wp[i].used) continue; any = 1; sb_putu_pad(out, i, 3); sb_puts(out, " 0x"); sb_puthex(out, wp[i].addr, 16); sb_putu_pad(out, wp[i].len, 5); sb_puts(out, wp[i].rw == RW_RW ? " rw " : " w "); sb_putu_pad(out, wp[i].hits, 6); sb_putc(out, '\n'); } if (!any) sb_puts(out, " (none armed)\n"); // Which processors have actually loaded the table. // // Reported rather than assumed, because the answer is the difference // between "this watchpoint is watching" and "this watchpoint is watching // one quarter of the machine". A CPU that is behind catches up at its next // timer tick; one that stays behind is a bug and this is where it shows. sb_puts(out, "\n table generation "); sb_putu(out, wp_generation); sb_puts(out, ", loaded by:"); for (int c = 0; c < ncpu && c < NCPU; c++) { sb_puts(out, " cpu"); sb_putu(out, c); sb_putc(out, '='); sb_putu(out, wp_seen[c]); if (wp_seen[c] != wp_generation) sb_puts(out, "(behind)"); } sb_putc(out, '\n'); sb_puts(out, "\n watch [len] [w|rw] arm one; len is 1, 2, 4 or 8\n" " watch clear disarm one\n" " watch clear all disarm every one\n" "\n Addresses are kernel virtual. `whose` prints the kernel va of a\n" " physical page, which is how you get from one to the other.\n" " Hits are reported to the console, not here -- they happen while\n" " you are not looking, which is the point.\n"); } static void watch_view(struct sbuf *out, const char *arg) { const char *p = arg; uint64 addr, len = 8; int rw = RW_WRITE, r; while (*p == ' ') p++; if (*p == 0) { watch_list(out); return; } if (p[0] == 'c' && p[1] == 'l') { // clear while (*p && *p != ' ') p++; while (*p == ' ') p++; if (p[0] == 'a') { // all for (int i = 0; i < NWATCH; i++) if (wp[i].used) watch_clear(i); sb_puts(out, "all watchpoints disarmed\n"); return; } if (!parse_num(&p, &addr)) { sb_puts(out, "watch clear , or watch clear all\n"); return; } if (watch_clear((int)addr) < 0) sb_puts(out, "no such watchpoint\n"); else { sb_puts(out, "watch "); sb_putu(out, addr); sb_puts(out, " disarmed\n"); } return; } if (!parse_num(&p, &addr)) { watch_list(out); return; } parse_num(&p, &len); // optional while (*p == ' ') p++; if (p[0] == 'r' && p[1] == 'w') rw = RW_RW; // A watchpoint on a user address would fire in whichever address space // happens to have that linear address mapped, which is every process. Say so // rather than arming something that reports nonsense. if (addr < (uint64)KERNBASE) sb_puts(out, "note: that is below KERNBASE, so it is a *user* linear address\n" " and will fire in whatever process is current. Kernel\n" " addresses are what this is for.\n"); r = watch_set(addr, (int)len, rw); if (r == -1) sb_puts(out, "length must be 1, 2, 4 or 8\n"); else if (r == -2) sb_puts(out, "address must be aligned to its length\n"); else if (r == -3) sb_puts(out, "all four debug registers are in use\n"); else { sb_puts(out, "watch "); sb_putu(out, r); sb_puts(out, " armed on 0x"); sb_puthex(out, addr, 16); sb_puts(out, ", "); sb_putu(out, len); sb_puts(out, rw == RW_RW ? " byte(s), read or write\n" : " byte(s), on write\n"); sb_puts(out, "hits are reported to the console\n"); } } void watch_init(void) { dbg_register_rw("watch", watch_view, "hardware watchpoints -- `watch ` to arm one"); }