summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--sys/conf/files1
-rw-r--r--sys/ddb/db_capture.c58
-rw-r--r--sys/ddb/db_command.c1
-rw-r--r--sys/ddb/db_textdump.c555
-rw-r--r--sys/ddb/ddb.h23
-rw-r--r--sys/kern/kern_shutdown.c10
6 files changed, 644 insertions, 4 deletions
diff --git a/sys/conf/files b/sys/conf/files
index 2ceedea..a3392eb 100644
--- a/sys/conf/files
+++ b/sys/conf/files
@@ -364,6 +364,7 @@ ddb/db_run.c optional ddb
ddb/db_script.c optional ddb
ddb/db_sym.c optional ddb
ddb/db_thread.c optional ddb
+ddb/db_textdump.c optional ddb
ddb/db_variables.c optional ddb
ddb/db_watch.c optional ddb
ddb/db_write_cmd.c optional ddb
diff --git a/sys/ddb/db_capture.c b/sys/ddb/db_capture.c
index 4e6dd7c..bbc7925 100644
--- a/sys/ddb/db_capture.c
+++ b/sys/ddb/db_capture.c
@@ -57,11 +57,13 @@ static MALLOC_DEFINE(M_DB_CAPTURE, "db_capture", "DDB capture buffer");
#define DB_CAPTURE_DEFAULTBUFSIZE 48*1024
#define DB_CAPTURE_MAXBUFSIZE 512*1024
+#define DB_CAPTURE_FILENAME "ddb.txt" /* Captured DDB output. */
static char *db_capture_buf;
static u_int db_capture_bufsize = DB_CAPTURE_DEFAULTBUFSIZE;
static u_int db_capture_maxbufsize = DB_CAPTURE_MAXBUFSIZE; /* Read-only. */
static u_int db_capture_bufoff; /* Next location to write in buffer. */
+static u_int db_capture_bufpadding; /* Amount of zero padding. */
static int db_capture_inpager; /* Suspend capture in pager. */
static int db_capture_inprogress; /* DDB capture currently in progress. */
@@ -79,6 +81,14 @@ SYSCTL_UINT(_debug_ddb_capture, OID_AUTO, maxbufsize, CTLFLAG_RD,
"Maximum value for debug.ddb.capture.bufsize");
/*
+ * Various compile-time assertions: defaults must be even multiples of
+ * textdump block size. We also perform run-time checking of
+ * user-configurable values.
+ */
+CTASSERT(DB_CAPTURE_DEFAULTBUFSIZE % TEXTDUMP_BLOCKSIZE == 0);
+CTASSERT(DB_CAPTURE_MAXBUFSIZE % TEXTDUMP_BLOCKSIZE == 0);
+
+/*
* Boot-time allocation of the DDB capture buffer, if any.
*/
static void
@@ -86,9 +96,9 @@ db_capture_sysinit(__unused void *dummy)
{
TUNABLE_INT_FETCH("debug.ddb.capture.bufsize", &db_capture_bufsize);
+ db_capture_bufsize = roundup(db_capture_bufsize, TEXTDUMP_BLOCKSIZE);
if (db_capture_bufsize > DB_CAPTURE_MAXBUFSIZE)
db_capture_bufsize = DB_CAPTURE_MAXBUFSIZE;
-
if (db_capture_bufsize != 0)
db_capture_buf = malloc(db_capture_bufsize, M_DB_CAPTURE,
M_WAITOK);
@@ -110,9 +120,9 @@ sysctl_debug_ddb_capture_bufsize(SYSCTL_HANDLER_ARGS)
error = sysctl_handle_int(oidp, &size, 0, req);
if (error || req->newptr == NULL)
return (error);
+ size = roundup(size, TEXTDUMP_BLOCKSIZE);
if (size > DB_CAPTURE_MAXBUFSIZE)
return (EINVAL);
-
sx_xlock(&db_capture_sx);
if (size != 0) {
/*
@@ -216,6 +226,23 @@ db_capture_exitpager(void)
}
/*
+ * Zero out any bytes left in the last block of the DDB capture buffer. This
+ * is run shortly before writing the blocks to disk, rather than when output
+ * capture is stopped, in order to avoid injecting nul's into the middle of
+ * output.
+ */
+static void
+db_capture_zeropad(void)
+{
+ u_int len;
+
+ len = min(TEXTDUMP_BLOCKSIZE, (db_capture_bufsize -
+ db_capture_bufoff) % TEXTDUMP_BLOCKSIZE);
+ bzero(db_capture_buf + db_capture_bufoff, len);
+ db_capture_bufpadding = len;
+}
+
+/*
* Reset capture state, which flushes buffers.
*/
static void
@@ -224,6 +251,7 @@ db_capture_reset(void)
db_capture_inprogress = 0;
db_capture_bufoff = 0;
+ db_capture_bufpadding = 0;
}
/*
@@ -242,7 +270,9 @@ db_capture_start(void)
}
/*
- * Terminate DDB output capture.
+ * Terminate DDB output capture--real work is deferred to db_capture_dump,
+ * which executes outside of the DDB context. We don't zero pad here because
+ * capture may be started again before the dump takes place.
*/
static void
db_capture_stop(void)
@@ -255,6 +285,28 @@ db_capture_stop(void)
db_capture_inprogress = 0;
}
+/*
+ * Dump DDB(4) captured output (and resets capture buffers).
+ */
+void
+db_capture_dump(struct dumperinfo *di)
+{
+ u_int offset;
+
+ if (db_capture_bufoff == 0)
+ return;
+
+ db_capture_zeropad();
+ textdump_mkustar(textdump_block_buffer, DB_CAPTURE_FILENAME,
+ db_capture_bufoff);
+ (void)textdump_writenextblock(di, textdump_block_buffer);
+ for (offset = 0; offset < db_capture_bufoff + db_capture_bufpadding;
+ offset += TEXTDUMP_BLOCKSIZE)
+ (void)textdump_writenextblock(di, db_capture_buf + offset);
+ db_capture_bufoff = 0;
+ db_capture_bufpadding = 0;
+}
+
/*-
* DDB(4) command to manage capture:
*
diff --git a/sys/ddb/db_command.c b/sys/ddb/db_command.c
index c1a062b..5ebcc15 100644
--- a/sys/ddb/db_command.c
+++ b/sys/ddb/db_command.c
@@ -146,6 +146,7 @@ static struct command db_commands[] = {
{ "scripts", db_scripts_cmd, 0, 0 },
{ "unscript", db_unscript_cmd, CS_OWN, 0 },
{ "capture", db_capture_cmd, CS_OWN, 0 },
+ { "textdump", db_textdump_cmd, CS_OWN, 0 },
{ (char *)0, }
};
diff --git a/sys/ddb/db_textdump.c b/sys/ddb/db_textdump.c
new file mode 100644
index 0000000..237040c
--- /dev/null
+++ b/sys/ddb/db_textdump.c
@@ -0,0 +1,555 @@
+/*-
+ * Copyright (c) 2007 Robert N. M. Watson
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*-
+ * Kernel text-dump support: allow a series of text files to be written to
+ * the dump partition for later recovery, including captured DDB output, the
+ * kernel configuration, message buffer, panic message, etc. This allows for
+ * a more compact representation of critical debugging information than
+ * traditional binary dumps, as well as allowing dump information to be used
+ * without access to kernel symbols, source code, etc.
+ *
+ * Storage Layout
+ * --------------
+ *
+ * Crash dumps are aligned to the end of the dump or swap partition in order
+ * to minimize the chances of swap duing fsck eating into the dump. However,
+ * unlike a memory dump, we don't know the size of the textdump a priori, so
+ * can't just write it out sequentially in order from a known starting point
+ * calculated with respect to the end of the partition. In order to address
+ * this, we actually write out the textdump in reverse block order, allowing
+ * us to directly align it to the end of the partition and then write out the
+ * dump header and trailer before and after it once done. savecore(8) must
+ * know to reverse the order of the blocks in order to produce a readable
+ * file.
+ *
+ * Data is written out in the 'tar' file format, as it provides the facility
+ * to write data incrementally as a stream without reference to previous
+ * files.
+ *
+ * TODO
+ * ----
+ *
+ * - Allow subsytems to register to submit files for inclusion in the text
+ * dump in a generic way.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include "opt_config.h"
+
+#include <sys/param.h>
+#include <sys/conf.h>
+#include <sys/kernel.h>
+#include <sys/kerneldump.h>
+#include <sys/msgbuf.h>
+#include <sys/sysctl.h>
+#include <sys/systm.h>
+
+#include <ddb/ddb.h>
+#include <ddb/db_lex.h>
+
+static SYSCTL_NODE(_debug_ddb, OID_AUTO, textdump, CTLFLAG_RW, 0,
+ "DDB textdump options");
+
+/*
+ * Don't touch the first SIZEOF_METADATA bytes on the dump device. This is
+ * to protect us from metadata and metadata from us.
+ */
+#define SIZEOF_METADATA (64*1024)
+
+/*
+ * Data is written out as a series of files in the ustar tar format. ustar
+ * is a simple streamed format consiting of a series of files prefixed with
+ * headers, and all padded to 512-byte block boundaries, which maps
+ * conveniently to our requirements.
+ */
+struct ustar_header {
+ char uh_filename[100];
+ char uh_mode[8];
+ char uh_tar_owner[8];
+ char uh_tar_group[8];
+ char uh_size[12];
+ char uh_mtime[12];
+ char uh_sum[8];
+ char uh_type;
+ char uh_linkfile[100];
+ char uh_ustar[6];
+ char uh_version[2];
+ char uh_owner[32];
+ char uh_group[32];
+ char uh_major[8];
+ char uh_minor[8];
+ char uh_filenameprefix[155];
+ char uh_zeropad[12];
+} __packed;
+
+/*
+ * Various size assertions -- pretty much everything must be one block in
+ * size.
+ */
+CTASSERT(sizeof(struct kerneldumpheader) == TEXTDUMP_BLOCKSIZE);
+CTASSERT(sizeof(struct ustar_header) == TEXTDUMP_BLOCKSIZE);
+
+/*
+ * Is a textdump scheduled? If so, the shutdown code will invoke our dumpsys
+ * routine instead of the machine-dependent kernel dump routine.
+ */
+int textdump_pending;
+SYSCTL_INT(_debug_ddb_textdump, OID_AUTO, pending, CTLFLAG_RW,
+ &textdump_pending, 0,
+ "Perform textdump instead of regular kernel dump.");
+
+/*
+ * Various constants for tar headers and contents.
+ */
+#define TAR_USER "root"
+#define TAR_GROUP "wheel"
+#define TAR_UID "0"
+#define TAR_GID "0"
+#define TAR_MODE "0600"
+#define TAR_USTAR "ustar"
+
+#define TAR_CONFIG_FILENAME "config.txt" /* Kernel configuration. */
+#define TAR_MSGBUF_FILENAME "msgbuf.txt" /* Kernel messsage buffer. */
+#define TAR_PANIC_FILENAME "panic.txt" /* Panic message. */
+#define TAR_VERSION_FILENAME "version.txt" /* Kernel version. */
+
+/*
+ * Configure which files will be dumped.
+ */
+#ifdef INCLUDE_CONFIG_FILE
+static int textdump_do_config = 1;
+SYSCTL_INT(_debug_ddb_textdump, OID_AUTO, do_config, CTLFLAG_RW,
+ &textdump_do_config, 0, "Dump kernel configuration in textdump");
+#endif
+
+static int textdump_do_ddb = 1;
+SYSCTL_INT(_debug_ddb_textdump, OID_AUTO, do_ddb, CTLFLAG_RW,
+ &textdump_do_ddb, 0, "Dump DDB captured output in textdump");
+
+static int textdump_do_msgbuf = 1;
+SYSCTL_INT(_debug_ddb_textdump, OID_AUTO, do_msgbuf, CTLFLAG_RW,
+ &textdump_do_msgbuf, 0, "Dump kernel message buffer in textdump");
+
+static int textdump_do_panic = 1;
+SYSCTL_INT(_debug_ddb_textdump, OID_AUTO, do_panic, CTLFLAG_RW,
+ &textdump_do_panic, 0, "Dump kernel panic message in textdump");
+
+static int textdump_do_version = 1;
+SYSCTL_INT(_debug_ddb_textdump, OID_AUTO, do_version, CTLFLAG_RW,
+ &textdump_do_version, 0, "Dump kernel version string in textdump");
+
+/*
+ * State related to incremental writing of blocks to disk.
+ */
+static off_t textdump_offset; /* Offset of next sequential write. */
+static int textdump_error; /* Carried write error, if any. */
+
+/*
+ * Statically allocate space to prepare block-sized headers and data.
+ */
+char textdump_block_buffer[TEXTDUMP_BLOCKSIZE];
+static struct kerneldumpheader kdh;
+
+/*
+ * Text dumps are prefixed with a normal kernel dump header but with a
+ * different magic number to allow them to be uniquely identified.
+ */
+static void
+mkdumpheader(struct kerneldumpheader *kdh, uint32_t archver,
+ uint64_t dumplen, uint32_t blksz)
+{
+
+ bzero(kdh, sizeof(*kdh));
+ strncpy(kdh->magic, TEXTDUMPMAGIC, sizeof(kdh->magic));
+ strncpy(kdh->architecture, MACHINE_ARCH, sizeof(kdh->architecture));
+ kdh->version = htod32(KERNELDUMPVERSION);
+ kdh->architectureversion = htod32(archver);
+ kdh->dumplength = htod64(dumplen);
+ kdh->dumptime = htod64(time_second);
+ kdh->blocksize = htod32(blksz);
+ strncpy(kdh->hostname, hostname, sizeof(kdh->hostname));
+ strncpy(kdh->versionstring, version, sizeof(kdh->versionstring));
+ if (panicstr != NULL)
+ strncpy(kdh->panicstring, panicstr, sizeof(kdh->panicstring));
+ kdh->parity = kerneldump_parity(kdh);
+}
+
+/*
+ * Calculate and fill in the checksum for a tar header.
+ */
+static void
+ustar_checksum(struct ustar_header *uhp)
+{
+ u_int sum;
+ int i;
+
+ for (i = 0; i < sizeof(uhp->uh_sum); i++)
+ uhp->uh_sum[i] = ' ';
+ sum = 0;
+ for (i = 0; i < sizeof(*uhp); i++)
+ sum += ((u_char *)uhp)[i];
+ snprintf(uhp->uh_sum, sizeof(uhp->uh_sum), "%6o", sum);
+}
+
+/*
+ * Each file in the tarball has a block-sized header with its name and other,
+ * largely hard-coded, properties.
+ */
+void
+textdump_mkustar(char *block_buffer, const char *filename, u_int size)
+{
+ struct ustar_header *uhp;
+
+ uhp = (struct ustar_header *)block_buffer;
+ bzero(uhp, sizeof(*uhp));
+ strlcpy(uhp->uh_filename, filename, sizeof(uhp->uh_filename));
+ strlcpy(uhp->uh_mode, TAR_MODE, sizeof(uhp->uh_mode));
+ snprintf(uhp->uh_size, sizeof(uhp->uh_size), "%o", size);
+ strlcpy(uhp->uh_tar_owner, TAR_UID, sizeof(uhp->uh_tar_owner));
+ strlcpy(uhp->uh_tar_group, TAR_GID, sizeof(uhp->uh_tar_group));
+ strlcpy(uhp->uh_owner, TAR_USER, sizeof(uhp->uh_owner));
+ strlcpy(uhp->uh_group, TAR_GROUP, sizeof(uhp->uh_group));
+ snprintf(uhp->uh_mtime, sizeof(uhp->uh_mtime), "%lo",
+ (unsigned long)time_second);
+ uhp->uh_type = 0;
+ strlcpy(uhp->uh_ustar, TAR_USTAR, sizeof(uhp->uh_ustar));
+ ustar_checksum(uhp);
+}
+
+/*
+ * textdump_writeblock() writes TEXTDUMP_BLOCKSIZE-sized blocks of data to
+ * the space between di->mediaoffset and di->mediaoffset + di->mediasize. It
+ * accepts an offset relative to di->mediaoffset. If we're carrying any
+ * error from previous I/O, return that error and don't continue to try to
+ * write. Most writers ignore the error and forge ahead on the basis that
+ * there's not much you can do.
+ */
+static int
+textdump_writeblock(struct dumperinfo *di, off_t offset, char *buffer)
+{
+
+ if (textdump_error)
+ return (textdump_error);
+ if (offset + TEXTDUMP_BLOCKSIZE > di->mediasize)
+ return (EIO);
+ if (offset < SIZEOF_METADATA)
+ return (ENOSPC);
+ textdump_error = di->dumper(di->priv, buffer, 0, offset +
+ di->mediaoffset, TEXTDUMP_BLOCKSIZE);
+ return (textdump_error);
+}
+
+/*
+ * Interfaces to save and restore the dump offset, so that printers can go
+ * back to rewrite a header if required, while avoiding their knowing about
+ * the global layout of the blocks.
+ */
+void
+textdump_saveoff(off_t *offsetp)
+{
+
+ *offsetp = textdump_offset;
+}
+
+void
+textdump_restoreoff(off_t offset)
+{
+
+ textdump_offset = offset;
+}
+
+/*
+ * Interface to write the "next block" relative to the current offset; since
+ * we write backwards from the end of the partition, we subtract, but there's
+ * no reason for the caller to know this.
+ */
+int
+textdump_writenextblock(struct dumperinfo *di, char *buffer)
+{
+ int error;
+
+ error = textdump_writeblock(di, textdump_offset, buffer);
+ textdump_offset -= TEXTDUMP_BLOCKSIZE;
+ return (error);
+}
+
+#ifdef INCLUDE_CONFIG_FILE
+extern char kernconfstring[];
+
+/*
+ * Dump kernel configuration.
+ */
+static void
+textdump_dump_config(struct dumperinfo *di)
+{
+ u_int count, fullblocks, len;
+
+ len = strlen(kernconfstring);
+ textdump_mkustar(textdump_block_buffer, TAR_CONFIG_FILENAME, len);
+ (void)textdump_writenextblock(di, textdump_block_buffer);
+
+ /*
+ * Write out all full blocks directly from the string, and handle any
+ * left-over bits by copying it to out to the local buffer and
+ * zero-padding it.
+ */
+ fullblocks = len / TEXTDUMP_BLOCKSIZE;
+ for (count = 0; count < fullblocks; count++)
+ (void)textdump_writenextblock(di, kernconfstring + count *
+ TEXTDUMP_BLOCKSIZE);
+ if (len % TEXTDUMP_BLOCKSIZE != 0) {
+ bzero(textdump_block_buffer, TEXTDUMP_BLOCKSIZE);
+ bcopy(kernconfstring + count * TEXTDUMP_BLOCKSIZE,
+ textdump_block_buffer, len % TEXTDUMP_BLOCKSIZE);
+ (void)textdump_writenextblock(di, textdump_block_buffer);
+ }
+}
+#endif /* INCLUDE_CONFIG_FILE */
+
+/*
+ * Dump kernel message buffer.
+ */
+static void
+textdump_dump_msgbuf(struct dumperinfo *di)
+{
+ off_t end_offset, tarhdr_offset;
+ u_int i, len, offset, seq, total_len;
+ char buf[16];
+
+ /*
+ * Write out a dummy tar header to advance the offset; we'll rewrite
+ * it later once we know the true size.
+ */
+ textdump_saveoff(&tarhdr_offset);
+ textdump_mkustar(textdump_block_buffer, TAR_MSGBUF_FILENAME, 0);
+ (void)textdump_writenextblock(di, textdump_block_buffer);
+
+ /*
+ * Copy out the data in small chunks, but don't copy nuls that may be
+ * present if the message buffer has not yet completely filled at
+ * least once.
+ */
+ total_len = 0;
+ offset = 0;
+ msgbuf_peekbytes(msgbufp, NULL, 0, &seq);
+ while ((len = msgbuf_peekbytes(msgbufp, buf, sizeof(buf), &seq)) > 0) {
+ for (i = 0; i < len; i++) {
+ if (buf[i] == '\0')
+ continue;
+ textdump_block_buffer[offset] = buf[i];
+ offset++;
+ if (offset != sizeof(textdump_block_buffer))
+ continue;
+ (void)textdump_writenextblock(di,
+ textdump_block_buffer);
+ total_len += offset;
+ offset = 0;
+ }
+ }
+ total_len += offset; /* Without the zero-padding. */
+ if (offset != 0) {
+ bzero(textdump_block_buffer + offset,
+ sizeof(textdump_block_buffer) - offset);
+ (void)textdump_writenextblock(di, textdump_block_buffer);
+ }
+
+ /*
+ * Rewrite tar header to reflect how much was actually written.
+ */
+ textdump_saveoff(&end_offset);
+ textdump_restoreoff(tarhdr_offset);
+ textdump_mkustar(textdump_block_buffer, TAR_MSGBUF_FILENAME,
+ total_len);
+ (void)textdump_writenextblock(di, textdump_block_buffer);
+ textdump_restoreoff(end_offset);
+}
+
+static void
+textdump_dump_panic(struct dumperinfo *di)
+{
+ u_int len;
+
+ /*
+ * Write out tar header -- we store up to one block of panic message.
+ */
+ len = min(strlen(panicstr), TEXTDUMP_BLOCKSIZE);
+ textdump_mkustar(textdump_block_buffer, TAR_PANIC_FILENAME, len);
+ (void)textdump_writenextblock(di, textdump_block_buffer);
+
+ /*
+ * Zero-pad the panic string and write out block.
+ */
+ bzero(textdump_block_buffer, sizeof(textdump_block_buffer));
+ bcopy(panicstr, textdump_block_buffer, len);
+ (void)textdump_writenextblock(di, textdump_block_buffer);
+}
+
+static void
+textdump_dump_version(struct dumperinfo *di)
+{
+ u_int len;
+
+ /*
+ * Write out tar header -- at most one block of version information.
+ */
+ len = min(strlen(version), TEXTDUMP_BLOCKSIZE);
+ textdump_mkustar(textdump_block_buffer, TAR_VERSION_FILENAME, len);
+ (void)textdump_writenextblock(di, textdump_block_buffer);
+
+ /*
+ * Zero pad the version string and write out block.
+ */
+ bzero(textdump_block_buffer, sizeof(textdump_block_buffer));
+ bcopy(version, textdump_block_buffer, len);
+ (void)textdump_writenextblock(di, textdump_block_buffer);
+}
+
+/*
+ * Commit text dump to disk.
+ */
+void
+textdump_dumpsys(struct dumperinfo *di)
+{
+ off_t dumplen, trailer_offset;
+
+ if (di->blocksize != TEXTDUMP_BLOCKSIZE) {
+ printf("Dump partition block size (%ju) not textdump "
+ "block size (%ju)", (uintmax_t)di->blocksize,
+ (uintmax_t)TEXTDUMP_BLOCKSIZE);
+ return;
+ }
+
+ /*
+ * We don't know a priori how large the dump will be, but we do know
+ * that we need to reserve space for metadata and that we need two
+ * dump headers. Also leave room for one ustar header and one block
+ * of data.
+ */
+ if (di->mediasize < SIZEOF_METADATA + 2 * sizeof(kdh)) {
+ printf("Insufficient space on dump partition.\n");
+ return;
+ }
+ textdump_error = 0;
+
+ /*
+ * Position the start of the dump so that we'll write the kernel dump
+ * trailer immediately before the end of the partition, and then work
+ * our way back. We will rewrite this header later to reflect the
+ * true size if things go well.
+ */
+ textdump_offset = di->mediasize - sizeof(kdh);
+ textdump_saveoff(&trailer_offset);
+ mkdumpheader(&kdh, KERNELDUMP_TEXT_VERSION, 0, TEXTDUMP_BLOCKSIZE);
+ (void)textdump_writenextblock(di, (char *)&kdh);
+
+ /*
+ * Write a series of files in ustar format.
+ */
+ if (textdump_do_ddb)
+ db_capture_dump(di);
+#ifdef INCLUDE_CONFIG_FILE
+ if (textdump_do_config)
+ textdump_dump_config(di);
+#endif
+ if (textdump_do_msgbuf)
+ textdump_dump_msgbuf(di);
+ if (textdump_do_panic && panicstr != NULL)
+ textdump_dump_panic(di);
+ if (textdump_do_version)
+ textdump_dump_version(di);
+
+ /*
+ * Now that we know the true size, we can write out the header, then
+ * seek back to the end and rewrite the trailer with the correct
+ * size.
+ */
+ dumplen = trailer_offset - (textdump_offset + TEXTDUMP_BLOCKSIZE);
+ mkdumpheader(&kdh, KERNELDUMP_TEXT_VERSION, dumplen,
+ TEXTDUMP_BLOCKSIZE);
+ (void)textdump_writenextblock(di, (char *)&kdh);
+ textdump_restoreoff(trailer_offset);
+ (void)textdump_writenextblock(di, (char *)&kdh);
+
+ /*
+ * Terminate the dump, report any errors, and clear the pending flag.
+ */
+ if (textdump_error == 0)
+ (void)di->dumper(di->priv, NULL, 0, 0, 0);
+ if (textdump_error == ENOSPC)
+ printf("Insufficient space on dump partition\n");
+ else if (textdump_error != 0)
+ printf("Error %d writing dump\n", textdump_error);
+ else
+ printf("Textdump complete.\n");
+ textdump_pending = 0;
+}
+
+/*-
+ * DDB(4) command to manage textdumps:
+ *
+ * textdump set - request a textdump
+ * textdump status - print DDB output textdump status
+ * textdump unset - clear textdump request
+ */
+static void
+db_textdump_usage(void)
+{
+
+ db_printf("textdump [unset|set|status]\n");
+}
+
+void
+db_textdump_cmd(db_expr_t addr, boolean_t have_addr, db_expr_t count,
+ char *modif)
+{
+ int t;
+
+ t = db_read_token();
+ if (t != tIDENT) {
+ db_textdump_usage();
+ return;
+ }
+ if (db_read_token() != tEOL) {
+ db_textdump_usage();
+ return;
+ }
+ if (strcmp(db_tok_string, "set") == 0) {
+ textdump_pending = 1;
+ db_printf("textdump set\n");
+ } else if (strcmp(db_tok_string, "status") == 0) {
+ if (textdump_pending)
+ db_printf("textdump is set\n");
+ else
+ db_printf("textdump is not set\n");
+ } else if (strcmp(db_tok_string, "unset") == 0) {
+ textdump_pending = 0;
+ db_printf("textdump unset\n");
+ } else
+ db_textdump_usage();
+}
diff --git a/sys/ddb/ddb.h b/sys/ddb/ddb.h
index ebec60a..d559978 100644
--- a/sys/ddb/ddb.h
+++ b/sys/ddb/ddb.h
@@ -169,6 +169,7 @@ db_cmdfcn_t db_set_thread;
db_cmdfcn_t db_show_regs;
db_cmdfcn_t db_show_threads;
db_cmdfcn_t db_single_step_cmd;
+db_cmdfcn_t db_textdump_cmd;
db_cmdfcn_t db_trace_until_call_cmd;
db_cmdfcn_t db_trace_until_matching_cmd;
db_cmdfcn_t db_unscript_cmd;
@@ -212,4 +213,26 @@ void db_capture_writech(char ch);
*/
void db_script_kdbenter(const char *eventname); /* KDB enter event. */
+/*
+ * Interface between DDB and the textdump facility.
+ *
+ * Text dump blocks are of a fixed size; textdump_block_buffer is a
+ * statically allocated buffer that code interacting with textdumps can use
+ * to prepare and hold a pending block in when calling writenextblock().
+ */
+#define TEXTDUMP_BLOCKSIZE 512
+extern char textdump_block_buffer[TEXTDUMP_BLOCKSIZE];
+
+void textdump_mkustar(char *block_buffer, const char *filename,
+ u_int size);
+void textdump_restoreoff(off_t offset);
+void textdump_saveoff(off_t *offsetp);
+int textdump_writenextblock(struct dumperinfo *di, char *buffer);
+
+/*
+ * Interface between the kernel and textdumps.
+ */
+extern int textdump_pending; /* Call textdump_dumpsys() instead. */
+void textdump_dumpsys(struct dumperinfo *di);
+
#endif /* !_DDB_DDB_H_ */
diff --git a/sys/kern/kern_shutdown.c b/sys/kern/kern_shutdown.c
index baeed23..7bbd98f 100644
--- a/sys/kern/kern_shutdown.c
+++ b/sys/kern/kern_shutdown.c
@@ -37,6 +37,7 @@
#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");
+#include "opt_ddb.h"
#include "opt_kdb.h"
#include "opt_mac.h"
#include "opt_panic.h"
@@ -64,6 +65,8 @@ __FBSDID("$FreeBSD$");
#include <sys/sysctl.h>
#include <sys/sysproto.h>
+#include <ddb/ddb.h>
+
#include <machine/cpu.h>
#include <machine/pcb.h>
#include <machine/smp.h>
@@ -240,7 +243,12 @@ doadump(void)
savectx(&dumppcb);
dumptid = curthread->td_tid;
dumping++;
- dumpsys(&dumper);
+#ifdef DDB
+ if (textdump_pending)
+ textdump_dumpsys(&dumper);
+ else
+#endif
+ dumpsys(&dumper);
}
static int
OpenPOWER on IntegriCloud