db_capture.c revision 178492
1202375Srdivacky/*- 2202375Srdivacky * Copyright (c) 2007 Robert N. M. Watson 3202375Srdivacky * All rights reserved. 4202375Srdivacky * 5202375Srdivacky * Redistribution and use in source and binary forms, with or without 6202375Srdivacky * modification, are permitted provided that the following conditions 7202375Srdivacky * are met: 8202375Srdivacky * 1. Redistributions of source code must retain the above copyright 9202375Srdivacky * notice, this list of conditions and the following disclaimer. 10202375Srdivacky * 2. Redistributions in binary form must reproduce the above copyright 11202375Srdivacky * notice, this list of conditions and the following disclaimer in the 12202375Srdivacky * documentation and/or other materials provided with the distribution. 13202375Srdivacky * 14202375Srdivacky * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 15202375Srdivacky * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16202375Srdivacky * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17202375Srdivacky * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 18202375Srdivacky * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19202375Srdivacky * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 20202375Srdivacky * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 21202375Srdivacky * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 22202375Srdivacky * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 23202375Srdivacky * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 24202375Srdivacky * SUCH DAMAGE. 25202375Srdivacky */ 26202375Srdivacky 27202375Srdivacky/* 28202375Srdivacky * DDB capture support: capture kernel debugger output into a fixed-size 29202375Srdivacky * buffer for later dumping to disk or extraction from user space. 30202375Srdivacky */ 31202375Srdivacky 32202375Srdivacky#include <sys/cdefs.h> 33202375Srdivacky__FBSDID("$FreeBSD: head/sys/ddb/db_capture.c 178492 2008-04-25 13:23:36Z rwatson $"); 34202375Srdivacky 35202375Srdivacky#include "opt_ddb.h" 36202375Srdivacky 37202375Srdivacky#include <sys/param.h> 38203954Srdivacky#include <sys/conf.h> 39202375Srdivacky#include <sys/kernel.h> 40202375Srdivacky#include <sys/kerneldump.h> 41202375Srdivacky#include <sys/malloc.h> 42202375Srdivacky#include <sys/msgbuf.h> 43202375Srdivacky#include <sys/priv.h> 44202375Srdivacky#include <sys/sx.h> 45202375Srdivacky#include <sys/sysctl.h> 46202375Srdivacky#include <sys/systm.h> 47202375Srdivacky 48202375Srdivacky#include <ddb/ddb.h> 49202375Srdivacky#include <ddb/db_lex.h> 50202375Srdivacky 51202375Srdivacky/* 52202375Srdivacky * While it would be desirable to use a small block-sized buffer and dump 53202375Srdivacky * incrementally to disk in fixed-size blocks, it's not possible to enter 54202375Srdivacky * kernel dumper routines without restarting the kernel, which is undesirable 55202375Srdivacky * in the midst of debugging. Instead, we maintain a large static global 56202375Srdivacky * buffer that we fill from DDB's output routines. 57202375Srdivacky * 58202375Srdivacky * We enforce an invariant at runtime that buffer sizes are even multiples of 59202375Srdivacky * the textdump block size, which is a design choice that we might want to 60202375Srdivacky * reconsider. 61202375Srdivacky */ 62202375Srdivackystatic MALLOC_DEFINE(M_DDB_CAPTURE, "ddb_capture", "DDB capture buffer"); 63202375Srdivacky 64202375Srdivacky#ifndef DDB_CAPTURE_DEFAULTBUFSIZE 65202375Srdivacky#define DDB_CAPTURE_DEFAULTBUFSIZE 48*1024 66202375Srdivacky#endif 67202375Srdivacky#ifndef DDB_CAPTURE_MAXBUFSIZE 68202375Srdivacky#define DDB_CAPTURE_MAXBUFSIZE 5*1024*1024 69202375Srdivacky#endif 70202375Srdivacky#define DDB_CAPTURE_FILENAME "ddb.txt" /* Captured DDB output. */ 71202375Srdivacky 72202375Srdivackystatic char *db_capture_buf; 73202375Srdivackystatic u_int db_capture_bufsize = DDB_CAPTURE_DEFAULTBUFSIZE; 74202375Srdivackystatic u_int db_capture_maxbufsize = DDB_CAPTURE_MAXBUFSIZE; /* Read-only. */ 75202375Srdivackystatic u_int db_capture_bufoff; /* Next location to write in buffer. */ 76202375Srdivackystatic u_int db_capture_bufpadding; /* Amount of zero padding. */ 77202375Srdivackystatic int db_capture_inpager; /* Suspend capture in pager. */ 78202375Srdivackystatic int db_capture_inprogress; /* DDB capture currently in progress. */ 79202375Srdivacky 80202375Srdivackystruct sx db_capture_sx; /* Lock against user thread races. */ 81202375SrdivackySX_SYSINIT(db_capture_sx, &db_capture_sx, "db_capture_sx"); 82202375Srdivacky 83202375Srdivackystatic SYSCTL_NODE(_debug_ddb, OID_AUTO, capture, CTLFLAG_RW, 0, 84202375Srdivacky "DDB capture options"); 85202375Srdivacky 86202375SrdivackySYSCTL_UINT(_debug_ddb_capture, OID_AUTO, bufoff, CTLFLAG_RD, 87202375Srdivacky &db_capture_bufoff, 0, "Bytes of data in DDB capture buffer"); 88202375Srdivacky 89202375SrdivackySYSCTL_UINT(_debug_ddb_capture, OID_AUTO, maxbufsize, CTLFLAG_RD, 90202375Srdivacky &db_capture_maxbufsize, 0, 91202375Srdivacky "Maximum value for debug.ddb.capture.bufsize"); 92202375Srdivacky 93202375SrdivackySYSCTL_UINT(_debug_ddb_capture, OID_AUTO, inprogress, CTLFLAG_RD, 94202375Srdivacky &db_capture_inprogress, 0, "DDB output capture in progress"); 95202375Srdivacky 96202375Srdivacky/* 97202375Srdivacky * Boot-time allocation of the DDB capture buffer, if any. Force all buffer 98202375Srdivacky * sizes, including the maximum size, to be rounded to block sizes. 99202375Srdivacky */ 100202375Srdivackystatic void 101202375Srdivackydb_capture_sysinit(__unused void *dummy) 102202375Srdivacky{ 103202375Srdivacky 104202375Srdivacky TUNABLE_INT_FETCH("debug.ddb.capture.bufsize", &db_capture_bufsize); 105202375Srdivacky db_capture_maxbufsize = roundup(db_capture_maxbufsize, 106202375Srdivacky TEXTDUMP_BLOCKSIZE); 107202375Srdivacky db_capture_bufsize = roundup(db_capture_bufsize, TEXTDUMP_BLOCKSIZE); 108202375Srdivacky if (db_capture_bufsize > db_capture_maxbufsize) 109202375Srdivacky db_capture_bufsize = db_capture_maxbufsize; 110202375Srdivacky if (db_capture_bufsize != 0) 111202375Srdivacky db_capture_buf = malloc(db_capture_bufsize, M_DDB_CAPTURE, 112202375Srdivacky M_WAITOK); 113202375Srdivacky} 114202375SrdivackySYSINIT(db_capture, SI_SUB_DDB_SERVICES, SI_ORDER_ANY, db_capture_sysinit, 115202375Srdivacky NULL); 116202375Srdivacky 117202375Srdivacky/* 118202375Srdivacky * Run-time adjustment of the capture buffer. 119202375Srdivacky */ 120202375Srdivackystatic int 121202375Srdivackysysctl_debug_ddb_capture_bufsize(SYSCTL_HANDLER_ARGS) 122202375Srdivacky{ 123202375Srdivacky u_int len, size; 124203954Srdivacky char *buf; 125203954Srdivacky int error; 126203954Srdivacky 127203954Srdivacky size = db_capture_bufsize; 128203954Srdivacky error = sysctl_handle_int(oidp, &size, 0, req); 129203954Srdivacky if (error || req->newptr == NULL) 130203954Srdivacky return (error); 131203954Srdivacky size = roundup(size, TEXTDUMP_BLOCKSIZE); 132202375Srdivacky if (size > db_capture_maxbufsize) 133203954Srdivacky return (EINVAL); 134203954Srdivacky sx_xlock(&db_capture_sx); 135203954Srdivacky if (size != 0) { 136203954Srdivacky /* 137203954Srdivacky * Potentially the buffer is quite large, so if we can't 138202375Srdivacky * allocate it, fail rather than waiting. 139203954Srdivacky */ 140203954Srdivacky buf = malloc(size, M_DDB_CAPTURE, M_NOWAIT); 141203954Srdivacky if (buf == NULL) { 142203954Srdivacky sx_xunlock(&db_capture_sx); 143203954Srdivacky return (ENOMEM); 144203954Srdivacky } 145202375Srdivacky len = min(db_capture_bufoff, size); 146202375Srdivacky } else { 147202375Srdivacky buf = NULL; 148203954Srdivacky len = 0; 149202375Srdivacky } 150202375Srdivacky if (db_capture_buf != NULL && buf != NULL) 151203954Srdivacky bcopy(db_capture_buf, buf, len); 152202375Srdivacky if (db_capture_buf != NULL) 153202375Srdivacky free(db_capture_buf, M_DDB_CAPTURE); 154202375Srdivacky db_capture_bufoff = len; 155202375Srdivacky db_capture_buf = buf; 156202375Srdivacky db_capture_bufsize = size; 157202375Srdivacky sx_xunlock(&db_capture_sx); 158202375Srdivacky 159202375Srdivacky KASSERT(db_capture_bufoff <= db_capture_bufsize, 160202375Srdivacky ("sysctl_debug_ddb_capture_bufsize: bufoff > bufsize")); 161202375Srdivacky KASSERT(db_capture_bufsize <= db_capture_maxbufsize, 162202375Srdivacky ("sysctl_debug_ddb_capture_maxbufsize: bufsize > maxbufsize")); 163202375Srdivacky 164202375Srdivacky return (0); 165202375Srdivacky} 166202375SrdivackySYSCTL_PROC(_debug_ddb_capture, OID_AUTO, bufsize, CTLTYPE_UINT|CTLFLAG_RW, 167202375Srdivacky 0, 0, sysctl_debug_ddb_capture_bufsize, "IU", 168202375Srdivacky "Size of DDB capture buffer"); 169202375Srdivacky 170202375Srdivacky/* 171203954Srdivacky * Sysctl to read out the capture buffer from userspace. We require 172202375Srdivacky * privilege as sensitive process/memory information may be accessed. 173202375Srdivacky */ 174202375Srdivackystatic int 175202375Srdivackysysctl_debug_ddb_capture_data(SYSCTL_HANDLER_ARGS) 176202375Srdivacky{ 177202375Srdivacky int error; 178202375Srdivacky char ch; 179202375Srdivacky 180202375Srdivacky error = priv_check(req->td, PRIV_DDB_CAPTURE); 181202375Srdivacky if (error) 182202375Srdivacky return (error); 183202375Srdivacky 184202375Srdivacky sx_slock(&db_capture_sx); 185202375Srdivacky error = SYSCTL_OUT(req, db_capture_buf, db_capture_bufoff); 186202375Srdivacky sx_sunlock(&db_capture_sx); 187202375Srdivacky if (error) 188202375Srdivacky return (error); 189202375Srdivacky ch = '\0'; 190202375Srdivacky return (SYSCTL_OUT(req, &ch, sizeof(ch))); 191202375Srdivacky} 192202375SrdivackySYSCTL_PROC(_debug_ddb_capture, OID_AUTO, data, CTLTYPE_STRING | CTLFLAG_RD, 193202375Srdivacky NULL, 0, sysctl_debug_ddb_capture_data, "A", "DDB capture data"); 194202375Srdivacky 195202375Srdivacky/* 196202375Srdivacky * Routines for capturing DDB output into a fixed-size buffer. These are 197202375Srdivacky * invoked from DDB's input and output routines. If we hit the limit on the 198202375Srdivacky * buffer, we simply drop further data. 199202375Srdivacky */ 200202375Srdivackyvoid 201202375Srdivackydb_capture_write(char *buffer, u_int buflen) 202202375Srdivacky{ 203202375Srdivacky u_int len; 204202375Srdivacky 205202375Srdivacky if (db_capture_inprogress == 0 || db_capture_inpager) 206202375Srdivacky return; 207202375Srdivacky len = min(buflen, db_capture_bufsize - db_capture_bufoff); 208202375Srdivacky bcopy(buffer, db_capture_buf + db_capture_bufoff, len); 209202375Srdivacky db_capture_bufoff += len; 210202375Srdivacky 211202375Srdivacky KASSERT(db_capture_bufoff <= db_capture_bufsize, 212202375Srdivacky ("db_capture_write: bufoff > bufsize")); 213202375Srdivacky} 214202375Srdivacky 215202375Srdivackyvoid 216202375Srdivackydb_capture_writech(char ch) 217202375Srdivacky{ 218202375Srdivacky 219202375Srdivacky return (db_capture_write(&ch, sizeof(ch))); 220202375Srdivacky} 221202375Srdivacky 222202375Srdivackyvoid 223202375Srdivackydb_capture_enterpager(void) 224202375Srdivacky{ 225203954Srdivacky 226202375Srdivacky db_capture_inpager = 1; 227202375Srdivacky} 228202375Srdivacky 229202375Srdivackyvoid 230202375Srdivackydb_capture_exitpager(void) 231202375Srdivacky{ 232202375Srdivacky 233202375Srdivacky db_capture_inpager = 0; 234202375Srdivacky} 235202375Srdivacky 236202375Srdivacky/* 237202375Srdivacky * Zero out any bytes left in the last block of the DDB capture buffer. This 238202375Srdivacky * is run shortly before writing the blocks to disk, rather than when output 239202375Srdivacky * capture is stopped, in order to avoid injecting nul's into the middle of 240202375Srdivacky * output. 241202375Srdivacky */ 242202375Srdivackystatic void 243202375Srdivackydb_capture_zeropad(void) 244202375Srdivacky{ 245202375Srdivacky u_int len; 246202375Srdivacky 247202375Srdivacky len = min(TEXTDUMP_BLOCKSIZE, (db_capture_bufsize - 248202375Srdivacky db_capture_bufoff) % TEXTDUMP_BLOCKSIZE); 249202375Srdivacky bzero(db_capture_buf + db_capture_bufoff, len); 250202375Srdivacky db_capture_bufpadding = len; 251202375Srdivacky} 252202375Srdivacky 253202375Srdivacky/* 254202375Srdivacky * Reset capture state, which flushes buffers. 255202375Srdivacky */ 256202375Srdivackystatic void 257202375Srdivackydb_capture_reset(void) 258202375Srdivacky{ 259202375Srdivacky 260202375Srdivacky db_capture_inprogress = 0; 261202375Srdivacky db_capture_bufoff = 0; 262202375Srdivacky db_capture_bufpadding = 0; 263202375Srdivacky} 264202375Srdivacky 265202375Srdivacky/* 266202375Srdivacky * Start capture. Only one session is allowed at any time, but we may 267202375Srdivacky * continue a previous session, so the buffer isn't reset. 268202375Srdivacky */ 269202375Srdivackystatic void 270202375Srdivackydb_capture_start(void) 271202375Srdivacky{ 272202375Srdivacky 273202375Srdivacky if (db_capture_inprogress) { 274202375Srdivacky db_printf("Capture already started\n"); 275202375Srdivacky return; 276202375Srdivacky } 277202375Srdivacky db_capture_inprogress = 1; 278202375Srdivacky} 279202375Srdivacky 280202375Srdivacky/* 281202375Srdivacky * Terminate DDB output capture--real work is deferred to db_capture_dump, 282202375Srdivacky * which executes outside of the DDB context. We don't zero pad here because 283202375Srdivacky * capture may be started again before the dump takes place. 284202375Srdivacky */ 285202375Srdivackystatic void 286202375Srdivackydb_capture_stop(void) 287202375Srdivacky{ 288202375Srdivacky 289202375Srdivacky if (db_capture_inprogress == 0) { 290202375Srdivacky db_printf("Capture not started\n"); 291202375Srdivacky return; 292202375Srdivacky } 293202375Srdivacky db_capture_inprogress = 0; 294202375Srdivacky} 295202375Srdivacky 296202375Srdivacky/* 297202375Srdivacky * Dump DDB(4) captured output (and resets capture buffers). 298202375Srdivacky */ 299202375Srdivackyvoid 300202375Srdivackydb_capture_dump(struct dumperinfo *di) 301202375Srdivacky{ 302202375Srdivacky u_int offset; 303202375Srdivacky 304202375Srdivacky if (db_capture_bufoff == 0) 305202375Srdivacky return; 306202375Srdivacky 307202375Srdivacky db_capture_zeropad(); 308202375Srdivacky textdump_mkustar(textdump_block_buffer, DDB_CAPTURE_FILENAME, 309202375Srdivacky db_capture_bufoff); 310202375Srdivacky (void)textdump_writenextblock(di, textdump_block_buffer); 311202375Srdivacky for (offset = 0; offset < db_capture_bufoff + db_capture_bufpadding; 312202375Srdivacky offset += TEXTDUMP_BLOCKSIZE) 313202375Srdivacky (void)textdump_writenextblock(di, db_capture_buf + offset); 314202375Srdivacky db_capture_bufoff = 0; 315202375Srdivacky db_capture_bufpadding = 0; 316202375Srdivacky} 317202375Srdivacky 318202375Srdivacky/*- 319202375Srdivacky * DDB(4) command to manage capture: 320202375Srdivacky * 321202375Srdivacky * capture on - start DDB output capture 322202375Srdivacky * capture off - stop DDB output capture 323202375Srdivacky * capture reset - reset DDB capture buffer (also stops capture) 324202375Srdivacky * capture status - print DDB output capture status 325202375Srdivacky */ 326202375Srdivackystatic void 327202375Srdivackydb_capture_usage(void) 328202375Srdivacky{ 329202375Srdivacky 330202375Srdivacky db_error("capture [on|off|reset|status]\n"); 331202375Srdivacky} 332202375Srdivacky 333202375Srdivackyvoid 334202375Srdivackydb_capture_cmd(db_expr_t addr, boolean_t have_addr, db_expr_t count, 335202375Srdivacky char *modif) 336202375Srdivacky{ 337202375Srdivacky int t; 338202375Srdivacky 339202375Srdivacky t = db_read_token(); 340202375Srdivacky if (t != tIDENT) { 341202375Srdivacky db_capture_usage(); 342202375Srdivacky return; 343202375Srdivacky } 344202375Srdivacky if (db_read_token() != tEOL) 345202375Srdivacky db_error("?\n"); 346202375Srdivacky if (strcmp(db_tok_string, "on") == 0) 347202375Srdivacky db_capture_start(); 348202375Srdivacky else if (strcmp(db_tok_string, "off") == 0) 349202375Srdivacky db_capture_stop(); 350202375Srdivacky else if (strcmp(db_tok_string, "reset") == 0) 351202375Srdivacky db_capture_reset(); 352202375Srdivacky else if (strcmp(db_tok_string, "status") == 0) { 353202375Srdivacky db_printf("%u/%u bytes used\n", db_capture_bufoff, 354202375Srdivacky db_capture_bufsize); 355202375Srdivacky if (db_capture_inprogress) 356202375Srdivacky db_printf("capture is on\n"); 357202375Srdivacky else 358202375Srdivacky db_printf("capture is off\n"); 359202375Srdivacky } else 360202375Srdivacky db_capture_usage(); 361202375Srdivacky} 362202375Srdivacky