1086fec57SRobert Watson /*-
24d846d26SWarner Losh * SPDX-License-Identifier: BSD-2-Clause
3753c4e83SPedro F. Giffuni *
4086fec57SRobert Watson * Copyright (c) 2007 Robert N. M. Watson
5086fec57SRobert Watson * All rights reserved.
6086fec57SRobert Watson *
7086fec57SRobert Watson * Redistribution and use in source and binary forms, with or without
8086fec57SRobert Watson * modification, are permitted provided that the following conditions
9086fec57SRobert Watson * are met:
10086fec57SRobert Watson * 1. Redistributions of source code must retain the above copyright
11086fec57SRobert Watson * notice, this list of conditions and the following disclaimer.
12086fec57SRobert Watson * 2. Redistributions in binary form must reproduce the above copyright
13086fec57SRobert Watson * notice, this list of conditions and the following disclaimer in the
14086fec57SRobert Watson * documentation and/or other materials provided with the distribution.
15086fec57SRobert Watson *
16086fec57SRobert Watson * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17086fec57SRobert Watson * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18086fec57SRobert Watson * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19086fec57SRobert Watson * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20086fec57SRobert Watson * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21086fec57SRobert Watson * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22086fec57SRobert Watson * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23086fec57SRobert Watson * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24086fec57SRobert Watson * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25086fec57SRobert Watson * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26086fec57SRobert Watson * SUCH DAMAGE.
27086fec57SRobert Watson */
28086fec57SRobert Watson
29086fec57SRobert Watson /*
30086fec57SRobert Watson * DDB capture support: capture kernel debugger output into a fixed-size
31086fec57SRobert Watson * buffer for later dumping to disk or extraction from user space.
32086fec57SRobert Watson */
33086fec57SRobert Watson
34086fec57SRobert Watson #include <sys/cdefs.h>
35f33dc69dSRobert Watson #include "opt_ddb.h"
36f33dc69dSRobert Watson
37086fec57SRobert Watson #include <sys/param.h>
38086fec57SRobert Watson #include <sys/conf.h>
39086fec57SRobert Watson #include <sys/kernel.h>
40086fec57SRobert Watson #include <sys/kerneldump.h>
41086fec57SRobert Watson #include <sys/malloc.h>
42086fec57SRobert Watson #include <sys/msgbuf.h>
43086fec57SRobert Watson #include <sys/priv.h>
44086fec57SRobert Watson #include <sys/sx.h>
45086fec57SRobert Watson #include <sys/sysctl.h>
46086fec57SRobert Watson #include <sys/systm.h>
47086fec57SRobert Watson
48086fec57SRobert Watson #include <ddb/ddb.h>
49086fec57SRobert Watson #include <ddb/db_lex.h>
50086fec57SRobert Watson
51086fec57SRobert Watson /*
52086fec57SRobert Watson * While it would be desirable to use a small block-sized buffer and dump
53086fec57SRobert Watson * incrementally to disk in fixed-size blocks, it's not possible to enter
54086fec57SRobert Watson * kernel dumper routines without restarting the kernel, which is undesirable
55086fec57SRobert Watson * in the midst of debugging. Instead, we maintain a large static global
56086fec57SRobert Watson * buffer that we fill from DDB's output routines.
57f33dc69dSRobert Watson *
58f33dc69dSRobert Watson * We enforce an invariant at runtime that buffer sizes are even multiples of
59f33dc69dSRobert Watson * the textdump block size, which is a design choice that we might want to
60f33dc69dSRobert Watson * reconsider.
61086fec57SRobert Watson */
628a4d372eSRobert Watson static MALLOC_DEFINE(M_DDB_CAPTURE, "ddb_capture", "DDB capture buffer");
63086fec57SRobert Watson
64f33dc69dSRobert Watson #ifndef DDB_CAPTURE_DEFAULTBUFSIZE
658a4d372eSRobert Watson #define DDB_CAPTURE_DEFAULTBUFSIZE 48*1024
66f33dc69dSRobert Watson #endif
67f33dc69dSRobert Watson #ifndef DDB_CAPTURE_MAXBUFSIZE
68a384163cSRobert Watson #define DDB_CAPTURE_MAXBUFSIZE 5*1024*1024
69f33dc69dSRobert Watson #endif
708a4d372eSRobert Watson #define DDB_CAPTURE_FILENAME "ddb.txt" /* Captured DDB output. */
71086fec57SRobert Watson
72086fec57SRobert Watson static char *db_capture_buf;
738a4d372eSRobert Watson static u_int db_capture_bufsize = DDB_CAPTURE_DEFAULTBUFSIZE;
748a4d372eSRobert Watson static u_int db_capture_maxbufsize = DDB_CAPTURE_MAXBUFSIZE; /* Read-only. */
75086fec57SRobert Watson static u_int db_capture_bufoff; /* Next location to write in buffer. */
76618c7db3SRobert Watson static u_int db_capture_bufpadding; /* Amount of zero padding. */
77086fec57SRobert Watson static int db_capture_inpager; /* Suspend capture in pager. */
78086fec57SRobert Watson static int db_capture_inprogress; /* DDB capture currently in progress. */
79086fec57SRobert Watson
80086fec57SRobert Watson struct sx db_capture_sx; /* Lock against user thread races. */
81086fec57SRobert Watson SX_SYSINIT(db_capture_sx, &db_capture_sx, "db_capture_sx");
82086fec57SRobert Watson
837029da5cSPawel Biernacki static SYSCTL_NODE(_debug_ddb, OID_AUTO, capture,
847029da5cSPawel Biernacki CTLFLAG_RW | CTLFLAG_MPSAFE, 0,
85086fec57SRobert Watson "DDB capture options");
86086fec57SRobert Watson
8792e6c2fdSRobert Watson SYSCTL_UINT(_debug_ddb_capture, OID_AUTO, bufoff, CTLFLAG_RD,
88086fec57SRobert Watson &db_capture_bufoff, 0, "Bytes of data in DDB capture buffer");
89086fec57SRobert Watson
90086fec57SRobert Watson SYSCTL_UINT(_debug_ddb_capture, OID_AUTO, maxbufsize, CTLFLAG_RD,
91086fec57SRobert Watson &db_capture_maxbufsize, 0,
92086fec57SRobert Watson "Maximum value for debug.ddb.capture.bufsize");
93086fec57SRobert Watson
94fbbb13f9SMatthew D Fleming SYSCTL_INT(_debug_ddb_capture, OID_AUTO, inprogress, CTLFLAG_RD,
9592e6c2fdSRobert Watson &db_capture_inprogress, 0, "DDB output capture in progress");
9692e6c2fdSRobert Watson
97086fec57SRobert Watson /*
98f33dc69dSRobert Watson * Boot-time allocation of the DDB capture buffer, if any. Force all buffer
99f33dc69dSRobert Watson * sizes, including the maximum size, to be rounded to block sizes.
100086fec57SRobert Watson */
101086fec57SRobert Watson static void
db_capture_sysinit(__unused void * dummy)102086fec57SRobert Watson db_capture_sysinit(__unused void *dummy)
103086fec57SRobert Watson {
104086fec57SRobert Watson
105086fec57SRobert Watson TUNABLE_INT_FETCH("debug.ddb.capture.bufsize", &db_capture_bufsize);
106f33dc69dSRobert Watson db_capture_maxbufsize = roundup(db_capture_maxbufsize,
107f33dc69dSRobert Watson TEXTDUMP_BLOCKSIZE);
108618c7db3SRobert Watson db_capture_bufsize = roundup(db_capture_bufsize, TEXTDUMP_BLOCKSIZE);
109f33dc69dSRobert Watson if (db_capture_bufsize > db_capture_maxbufsize)
110f33dc69dSRobert Watson db_capture_bufsize = db_capture_maxbufsize;
111086fec57SRobert Watson if (db_capture_bufsize != 0)
1128a4d372eSRobert Watson db_capture_buf = malloc(db_capture_bufsize, M_DDB_CAPTURE,
113086fec57SRobert Watson M_WAITOK);
114086fec57SRobert Watson }
115086fec57SRobert Watson SYSINIT(db_capture, SI_SUB_DDB_SERVICES, SI_ORDER_ANY, db_capture_sysinit,
116086fec57SRobert Watson NULL);
117086fec57SRobert Watson
118086fec57SRobert Watson /*
119086fec57SRobert Watson * Run-time adjustment of the capture buffer.
120086fec57SRobert Watson */
121086fec57SRobert Watson static int
sysctl_debug_ddb_capture_bufsize(SYSCTL_HANDLER_ARGS)122086fec57SRobert Watson sysctl_debug_ddb_capture_bufsize(SYSCTL_HANDLER_ARGS)
123086fec57SRobert Watson {
124086fec57SRobert Watson u_int len, size;
125086fec57SRobert Watson char *buf;
126086fec57SRobert Watson int error;
127086fec57SRobert Watson
128086fec57SRobert Watson size = db_capture_bufsize;
129086fec57SRobert Watson error = sysctl_handle_int(oidp, &size, 0, req);
130086fec57SRobert Watson if (error || req->newptr == NULL)
131086fec57SRobert Watson return (error);
132618c7db3SRobert Watson size = roundup(size, TEXTDUMP_BLOCKSIZE);
133f33dc69dSRobert Watson if (size > db_capture_maxbufsize)
134086fec57SRobert Watson return (EINVAL);
135086fec57SRobert Watson sx_xlock(&db_capture_sx);
136086fec57SRobert Watson if (size != 0) {
137086fec57SRobert Watson /*
138086fec57SRobert Watson * Potentially the buffer is quite large, so if we can't
139086fec57SRobert Watson * allocate it, fail rather than waiting.
140086fec57SRobert Watson */
1418a4d372eSRobert Watson buf = malloc(size, M_DDB_CAPTURE, M_NOWAIT);
142086fec57SRobert Watson if (buf == NULL) {
143086fec57SRobert Watson sx_xunlock(&db_capture_sx);
144086fec57SRobert Watson return (ENOMEM);
145086fec57SRobert Watson }
146086fec57SRobert Watson len = min(db_capture_bufoff, size);
147086fec57SRobert Watson } else {
148086fec57SRobert Watson buf = NULL;
149086fec57SRobert Watson len = 0;
150086fec57SRobert Watson }
151086fec57SRobert Watson if (db_capture_buf != NULL && buf != NULL)
152086fec57SRobert Watson bcopy(db_capture_buf, buf, len);
153086fec57SRobert Watson if (db_capture_buf != NULL)
1548a4d372eSRobert Watson free(db_capture_buf, M_DDB_CAPTURE);
155086fec57SRobert Watson db_capture_bufoff = len;
156086fec57SRobert Watson db_capture_buf = buf;
157086fec57SRobert Watson db_capture_bufsize = size;
158086fec57SRobert Watson sx_xunlock(&db_capture_sx);
159086fec57SRobert Watson
160086fec57SRobert Watson KASSERT(db_capture_bufoff <= db_capture_bufsize,
161086fec57SRobert Watson ("sysctl_debug_ddb_capture_bufsize: bufoff > bufsize"));
162f33dc69dSRobert Watson KASSERT(db_capture_bufsize <= db_capture_maxbufsize,
163086fec57SRobert Watson ("sysctl_debug_ddb_capture_maxbufsize: bufsize > maxbufsize"));
164086fec57SRobert Watson
165086fec57SRobert Watson return (0);
166086fec57SRobert Watson }
1677029da5cSPawel Biernacki SYSCTL_PROC(_debug_ddb_capture, OID_AUTO, bufsize,
168*0eb2e197SZhenlei Huang CTLTYPE_UINT | CTLFLAG_RWTUN | CTLFLAG_NOFETCH | CTLFLAG_MPSAFE,
169*0eb2e197SZhenlei Huang 0, 0, sysctl_debug_ddb_capture_bufsize, "IU",
170086fec57SRobert Watson "Size of DDB capture buffer");
171086fec57SRobert Watson
172086fec57SRobert Watson /*
173086fec57SRobert Watson * Sysctl to read out the capture buffer from userspace. We require
174086fec57SRobert Watson * privilege as sensitive process/memory information may be accessed.
175086fec57SRobert Watson */
176086fec57SRobert Watson static int
sysctl_debug_ddb_capture_data(SYSCTL_HANDLER_ARGS)177086fec57SRobert Watson sysctl_debug_ddb_capture_data(SYSCTL_HANDLER_ARGS)
178086fec57SRobert Watson {
179086fec57SRobert Watson int error;
180086fec57SRobert Watson char ch;
181086fec57SRobert Watson
182086fec57SRobert Watson error = priv_check(req->td, PRIV_DDB_CAPTURE);
183086fec57SRobert Watson if (error)
184086fec57SRobert Watson return (error);
185086fec57SRobert Watson
186086fec57SRobert Watson sx_slock(&db_capture_sx);
187086fec57SRobert Watson error = SYSCTL_OUT(req, db_capture_buf, db_capture_bufoff);
188086fec57SRobert Watson sx_sunlock(&db_capture_sx);
189086fec57SRobert Watson if (error)
190086fec57SRobert Watson return (error);
191086fec57SRobert Watson ch = '\0';
192086fec57SRobert Watson return (SYSCTL_OUT(req, &ch, sizeof(ch)));
193086fec57SRobert Watson }
1947029da5cSPawel Biernacki SYSCTL_PROC(_debug_ddb_capture, OID_AUTO, data,
1957029da5cSPawel Biernacki CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE, NULL, 0,
1967029da5cSPawel Biernacki sysctl_debug_ddb_capture_data, "A",
1977029da5cSPawel Biernacki "DDB capture data");
198086fec57SRobert Watson
199086fec57SRobert Watson /*
200086fec57SRobert Watson * Routines for capturing DDB output into a fixed-size buffer. These are
201086fec57SRobert Watson * invoked from DDB's input and output routines. If we hit the limit on the
202086fec57SRobert Watson * buffer, we simply drop further data.
203086fec57SRobert Watson */
204086fec57SRobert Watson void
db_capture_write(char * buffer,u_int buflen)205086fec57SRobert Watson db_capture_write(char *buffer, u_int buflen)
206086fec57SRobert Watson {
207086fec57SRobert Watson u_int len;
208086fec57SRobert Watson
209086fec57SRobert Watson if (db_capture_inprogress == 0 || db_capture_inpager)
210086fec57SRobert Watson return;
211086fec57SRobert Watson len = min(buflen, db_capture_bufsize - db_capture_bufoff);
212086fec57SRobert Watson bcopy(buffer, db_capture_buf + db_capture_bufoff, len);
213086fec57SRobert Watson db_capture_bufoff += len;
214086fec57SRobert Watson
215086fec57SRobert Watson KASSERT(db_capture_bufoff <= db_capture_bufsize,
216086fec57SRobert Watson ("db_capture_write: bufoff > bufsize"));
217086fec57SRobert Watson }
218086fec57SRobert Watson
219086fec57SRobert Watson void
db_capture_writech(char ch)220086fec57SRobert Watson db_capture_writech(char ch)
221086fec57SRobert Watson {
222086fec57SRobert Watson
223086fec57SRobert Watson return (db_capture_write(&ch, sizeof(ch)));
224086fec57SRobert Watson }
225086fec57SRobert Watson
226086fec57SRobert Watson void
db_capture_enterpager(void)227086fec57SRobert Watson db_capture_enterpager(void)
228086fec57SRobert Watson {
229086fec57SRobert Watson
230086fec57SRobert Watson db_capture_inpager = 1;
231086fec57SRobert Watson }
232086fec57SRobert Watson
233086fec57SRobert Watson void
db_capture_exitpager(void)234086fec57SRobert Watson db_capture_exitpager(void)
235086fec57SRobert Watson {
236086fec57SRobert Watson
237086fec57SRobert Watson db_capture_inpager = 0;
238086fec57SRobert Watson }
239086fec57SRobert Watson
240086fec57SRobert Watson /*
241618c7db3SRobert Watson * Zero out any bytes left in the last block of the DDB capture buffer. This
242618c7db3SRobert Watson * is run shortly before writing the blocks to disk, rather than when output
243618c7db3SRobert Watson * capture is stopped, in order to avoid injecting nul's into the middle of
244618c7db3SRobert Watson * output.
245618c7db3SRobert Watson */
246618c7db3SRobert Watson static void
db_capture_zeropad(void)247618c7db3SRobert Watson db_capture_zeropad(void)
248618c7db3SRobert Watson {
249618c7db3SRobert Watson u_int len;
250618c7db3SRobert Watson
251618c7db3SRobert Watson len = min(TEXTDUMP_BLOCKSIZE, (db_capture_bufsize -
252618c7db3SRobert Watson db_capture_bufoff) % TEXTDUMP_BLOCKSIZE);
253618c7db3SRobert Watson bzero(db_capture_buf + db_capture_bufoff, len);
254618c7db3SRobert Watson db_capture_bufpadding = len;
255618c7db3SRobert Watson }
256618c7db3SRobert Watson
257618c7db3SRobert Watson /*
258086fec57SRobert Watson * Reset capture state, which flushes buffers.
259086fec57SRobert Watson */
260086fec57SRobert Watson static void
db_capture_reset(void)261086fec57SRobert Watson db_capture_reset(void)
262086fec57SRobert Watson {
263086fec57SRobert Watson
264086fec57SRobert Watson db_capture_inprogress = 0;
265086fec57SRobert Watson db_capture_bufoff = 0;
266618c7db3SRobert Watson db_capture_bufpadding = 0;
267086fec57SRobert Watson }
268086fec57SRobert Watson
269086fec57SRobert Watson /*
270086fec57SRobert Watson * Start capture. Only one session is allowed at any time, but we may
271086fec57SRobert Watson * continue a previous session, so the buffer isn't reset.
272086fec57SRobert Watson */
273086fec57SRobert Watson static void
db_capture_start(void)274086fec57SRobert Watson db_capture_start(void)
275086fec57SRobert Watson {
276086fec57SRobert Watson
277086fec57SRobert Watson if (db_capture_inprogress) {
278086fec57SRobert Watson db_printf("Capture already started\n");
279086fec57SRobert Watson return;
280086fec57SRobert Watson }
281086fec57SRobert Watson db_capture_inprogress = 1;
282086fec57SRobert Watson }
283086fec57SRobert Watson
284086fec57SRobert Watson /*
285618c7db3SRobert Watson * Terminate DDB output capture--real work is deferred to db_capture_dump,
286618c7db3SRobert Watson * which executes outside of the DDB context. We don't zero pad here because
287618c7db3SRobert Watson * capture may be started again before the dump takes place.
288086fec57SRobert Watson */
289086fec57SRobert Watson static void
db_capture_stop(void)290086fec57SRobert Watson db_capture_stop(void)
291086fec57SRobert Watson {
292086fec57SRobert Watson
293086fec57SRobert Watson if (db_capture_inprogress == 0) {
294086fec57SRobert Watson db_printf("Capture not started\n");
295086fec57SRobert Watson return;
296086fec57SRobert Watson }
297086fec57SRobert Watson db_capture_inprogress = 0;
298086fec57SRobert Watson }
299086fec57SRobert Watson
300618c7db3SRobert Watson /*
301618c7db3SRobert Watson * Dump DDB(4) captured output (and resets capture buffers).
302618c7db3SRobert Watson */
303618c7db3SRobert Watson void
db_capture_dump(struct dumperinfo * di)304618c7db3SRobert Watson db_capture_dump(struct dumperinfo *di)
305618c7db3SRobert Watson {
306618c7db3SRobert Watson u_int offset;
307618c7db3SRobert Watson
308618c7db3SRobert Watson if (db_capture_bufoff == 0)
309618c7db3SRobert Watson return;
310618c7db3SRobert Watson
311618c7db3SRobert Watson db_capture_zeropad();
3128a4d372eSRobert Watson textdump_mkustar(textdump_block_buffer, DDB_CAPTURE_FILENAME,
313618c7db3SRobert Watson db_capture_bufoff);
314618c7db3SRobert Watson (void)textdump_writenextblock(di, textdump_block_buffer);
315618c7db3SRobert Watson for (offset = 0; offset < db_capture_bufoff + db_capture_bufpadding;
316618c7db3SRobert Watson offset += TEXTDUMP_BLOCKSIZE)
317618c7db3SRobert Watson (void)textdump_writenextblock(di, db_capture_buf + offset);
318618c7db3SRobert Watson db_capture_bufoff = 0;
319618c7db3SRobert Watson db_capture_bufpadding = 0;
320618c7db3SRobert Watson }
321618c7db3SRobert Watson
322086fec57SRobert Watson /*-
323086fec57SRobert Watson * DDB(4) command to manage capture:
324086fec57SRobert Watson *
325086fec57SRobert Watson * capture on - start DDB output capture
326086fec57SRobert Watson * capture off - stop DDB output capture
327086fec57SRobert Watson * capture reset - reset DDB capture buffer (also stops capture)
328086fec57SRobert Watson * capture status - print DDB output capture status
329086fec57SRobert Watson */
330086fec57SRobert Watson static void
db_capture_usage(void)331086fec57SRobert Watson db_capture_usage(void)
332086fec57SRobert Watson {
333086fec57SRobert Watson
334086fec57SRobert Watson db_error("capture [on|off|reset|status]\n");
335086fec57SRobert Watson }
336086fec57SRobert Watson
337086fec57SRobert Watson void
db_capture_cmd(db_expr_t addr,bool have_addr,db_expr_t count,char * modif)338cd508278SPedro F. Giffuni db_capture_cmd(db_expr_t addr, bool have_addr, db_expr_t count, char *modif)
339086fec57SRobert Watson {
340086fec57SRobert Watson int t;
341086fec57SRobert Watson
342086fec57SRobert Watson t = db_read_token();
343086fec57SRobert Watson if (t != tIDENT) {
344086fec57SRobert Watson db_capture_usage();
345086fec57SRobert Watson return;
346086fec57SRobert Watson }
347086fec57SRobert Watson if (db_read_token() != tEOL)
348086fec57SRobert Watson db_error("?\n");
349086fec57SRobert Watson if (strcmp(db_tok_string, "on") == 0)
350086fec57SRobert Watson db_capture_start();
351086fec57SRobert Watson else if (strcmp(db_tok_string, "off") == 0)
352086fec57SRobert Watson db_capture_stop();
353086fec57SRobert Watson else if (strcmp(db_tok_string, "reset") == 0)
354086fec57SRobert Watson db_capture_reset();
355086fec57SRobert Watson else if (strcmp(db_tok_string, "status") == 0) {
356086fec57SRobert Watson db_printf("%u/%u bytes used\n", db_capture_bufoff,
357086fec57SRobert Watson db_capture_bufsize);
358086fec57SRobert Watson if (db_capture_inprogress)
359086fec57SRobert Watson db_printf("capture is on\n");
360086fec57SRobert Watson else
361086fec57SRobert Watson db_printf("capture is off\n");
362086fec57SRobert Watson } else
363086fec57SRobert Watson db_capture_usage();
364086fec57SRobert Watson }
365