diff options
Diffstat (limited to 'sys/ddb')
-rw-r--r-- | sys/ddb/db_access.c | 113 | ||||
-rw-r--r-- | sys/ddb/db_access.h | 42 | ||||
-rw-r--r-- | sys/ddb/db_break.c | 367 | ||||
-rw-r--r-- | sys/ddb/db_break.h | 67 | ||||
-rw-r--r-- | sys/ddb/db_capture.c | 361 | ||||
-rw-r--r-- | sys/ddb/db_command.c | 872 | ||||
-rw-r--r-- | sys/ddb/db_command.h | 57 | ||||
-rw-r--r-- | sys/ddb/db_examine.c | 341 | ||||
-rw-r--r-- | sys/ddb/db_expr.c | 228 | ||||
-rw-r--r-- | sys/ddb/db_input.c | 374 | ||||
-rw-r--r-- | sys/ddb/db_lex.c | 314 | ||||
-rw-r--r-- | sys/ddb/db_lex.h | 73 | ||||
-rw-r--r-- | sys/ddb/db_main.c | 262 | ||||
-rw-r--r-- | sys/ddb/db_output.c | 395 | ||||
-rw-r--r-- | sys/ddb/db_output.h | 47 | ||||
-rw-r--r-- | sys/ddb/db_print.c | 70 | ||||
-rw-r--r-- | sys/ddb/db_ps.c | 468 | ||||
-rw-r--r-- | sys/ddb/db_run.c | 392 | ||||
-rw-r--r-- | sys/ddb/db_script.c | 562 | ||||
-rw-r--r-- | sys/ddb/db_sym.c | 505 | ||||
-rw-r--r-- | sys/ddb/db_sym.h | 106 | ||||
-rw-r--r-- | sys/ddb/db_textdump.c | 550 | ||||
-rw-r--r-- | sys/ddb/db_thread.c | 172 | ||||
-rw-r--r-- | sys/ddb/db_variables.c | 159 | ||||
-rw-r--r-- | sys/ddb/db_variables.h | 63 | ||||
-rw-r--r-- | sys/ddb/db_watch.c | 331 | ||||
-rw-r--r-- | sys/ddb/db_watch.h | 48 | ||||
-rw-r--r-- | sys/ddb/db_write_cmd.c | 96 | ||||
-rw-r--r-- | sys/ddb/ddb.h | 287 |
29 files changed, 7722 insertions, 0 deletions
diff --git a/sys/ddb/db_access.c b/sys/ddb/db_access.c new file mode 100644 index 0000000..2f4d049 --- /dev/null +++ b/sys/ddb/db_access.c @@ -0,0 +1,113 @@ +/*- + * Mach Operating System + * Copyright (c) 1991,1990 Carnegie Mellon University + * All Rights Reserved. + * + * Permission to use, copy, modify and distribute this software and its + * documentation is hereby granted, provided that both the copyright + * notice and this permission notice appear in all copies of the + * software, derivative works or modified versions, and any portions + * thereof, and that both notices appear in supporting documentation. + * + * CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS + * CONDITION. CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND FOR + * ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE. + * + * Carnegie Mellon requests users of this software to return to + * + * Software Distribution Coordinator or Software.Distribution@CS.CMU.EDU + * School of Computer Science + * Carnegie Mellon University + * Pittsburgh PA 15213-3890 + * + * any improvements or extensions that they make and grant Carnegie the + * rights to redistribute these changes. + */ +/* + * Author: David B. Golub, Carnegie Mellon University + * Date: 7/90 + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/param.h> +#include <sys/kdb.h> + +#include <ddb/ddb.h> +#include <ddb/db_access.h> + +/* + * Access unaligned data items on aligned (longword) + * boundaries. + */ + +static unsigned db_extend[] = { /* table for sign-extending */ + 0, + 0xFFFFFF80U, + 0xFFFF8000U, + 0xFF800000U +}; + +#ifndef BYTE_MSF +#define BYTE_MSF 0 +#endif + +db_expr_t +db_get_value(addr, size, is_signed) + db_addr_t addr; + register int size; + boolean_t is_signed; +{ + char data[sizeof(u_int64_t)]; + register db_expr_t value; + register int i; + + if (db_read_bytes(addr, size, data) != 0) { + db_printf("*** error reading from address %llx ***\n", + (long long)addr); + kdb_reenter(); + } + + value = 0; +#if BYTE_MSF + for (i = 0; i < size; i++) +#else /* BYTE_LSF */ + for (i = size - 1; i >= 0; i--) +#endif + { + value = (value << 8) + (data[i] & 0xFF); + } + + if (size < 4) { + if (is_signed && (value & db_extend[size]) != 0) + value |= db_extend[size]; + } + return (value); +} + +void +db_put_value(addr, size, value) + db_addr_t addr; + register int size; + register db_expr_t value; +{ + char data[sizeof(int)]; + register int i; + +#if BYTE_MSF + for (i = size - 1; i >= 0; i--) +#else /* BYTE_LSF */ + for (i = 0; i < size; i++) +#endif + { + data[i] = value & 0xFF; + value >>= 8; + } + + if (db_write_bytes(addr, size, data) != 0) { + db_printf("*** error writing to address %llx ***\n", + (long long)addr); + kdb_reenter(); + } +} diff --git a/sys/ddb/db_access.h b/sys/ddb/db_access.h new file mode 100644 index 0000000..44915e3 --- /dev/null +++ b/sys/ddb/db_access.h @@ -0,0 +1,42 @@ +/*- + * Mach Operating System + * Copyright (c) 1991,1990 Carnegie Mellon University + * All Rights Reserved. + * + * Permission to use, copy, modify and distribute this software and its + * documentation is hereby granted, provided that both the copyright + * notice and this permission notice appear in all copies of the + * software, derivative works or modified versions, and any portions + * thereof, and that both notices appear in supporting documentation. + * + * CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS + * CONDITION. CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND FOR + * ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE. + * + * Carnegie Mellon requests users of this software to return to + * + * Software Distribution Coordinator or Software.Distribution@CS.CMU.EDU + * School of Computer Science + * Carnegie Mellon University + * Pittsburgh PA 15213-3890 + * + * any improvements or extensions that they make and grant Carnegie the + * rights to redistribute these changes. + * + * $FreeBSD$ + */ + +#ifndef _DDB_DB_ACCESS_H_ +#define _DDB_DB_ACCESS_H_ + +/* + * Author: David B. Golub, Carnegie Mellon University + * Date: 7/90 + */ +/* + * Data access functions for debugger. + */ +db_expr_t db_get_value(db_addr_t addr, int size, boolean_t is_signed); +void db_put_value(db_addr_t addr, int size, db_expr_t value); + +#endif /* !_DDB_DB_ACCESS_H_ */ diff --git a/sys/ddb/db_break.c b/sys/ddb/db_break.c new file mode 100644 index 0000000..02833ec --- /dev/null +++ b/sys/ddb/db_break.c @@ -0,0 +1,367 @@ +/*- + * Mach Operating System + * Copyright (c) 1991,1990 Carnegie Mellon University + * All Rights Reserved. + * + * Permission to use, copy, modify and distribute this software and its + * documentation is hereby granted, provided that both the copyright + * notice and this permission notice appear in all copies of the + * software, derivative works or modified versions, and any portions + * thereof, and that both notices appear in supporting documentation. + * + * CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS + * CONDITION. CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND FOR + * ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE. + * + * Carnegie Mellon requests users of this software to return to + * + * Software Distribution Coordinator or Software.Distribution@CS.CMU.EDU + * School of Computer Science + * Carnegie Mellon University + * Pittsburgh PA 15213-3890 + * + * any improvements or extensions that they make and grant Carnegie the + * rights to redistribute these changes. + * + */ +/* + * Author: David B. Golub, Carnegie Mellon University + * Date: 7/90 + */ +/* + * Breakpoints. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/param.h> + +#include <vm/vm.h> +#include <vm/vm_kern.h> + +#include <ddb/ddb.h> +#include <ddb/db_break.h> +#include <ddb/db_access.h> +#include <ddb/db_sym.h> + +#define NBREAKPOINTS 100 +static struct db_breakpoint db_break_table[NBREAKPOINTS]; +static db_breakpoint_t db_next_free_breakpoint = &db_break_table[0]; +static db_breakpoint_t db_free_breakpoints = 0; +static db_breakpoint_t db_breakpoint_list = 0; + +static db_breakpoint_t db_breakpoint_alloc(void); +static void db_breakpoint_free(db_breakpoint_t bkpt); +static void db_delete_breakpoint(vm_map_t map, db_addr_t addr); +static db_breakpoint_t db_find_breakpoint(vm_map_t map, db_addr_t addr); +static void db_list_breakpoints(void); +static void db_set_breakpoint(vm_map_t map, db_addr_t addr, int count); + +static db_breakpoint_t +db_breakpoint_alloc() +{ + register db_breakpoint_t bkpt; + + if ((bkpt = db_free_breakpoints) != 0) { + db_free_breakpoints = bkpt->link; + return (bkpt); + } + if (db_next_free_breakpoint == &db_break_table[NBREAKPOINTS]) { + db_printf("All breakpoints used.\n"); + return (0); + } + bkpt = db_next_free_breakpoint; + db_next_free_breakpoint++; + + return (bkpt); +} + +static void +db_breakpoint_free(bkpt) + register db_breakpoint_t bkpt; +{ + bkpt->link = db_free_breakpoints; + db_free_breakpoints = bkpt; +} + +static void +db_set_breakpoint(map, addr, count) + vm_map_t map; + db_addr_t addr; + int count; +{ + register db_breakpoint_t bkpt; + + if (db_find_breakpoint(map, addr)) { + db_printf("Already set.\n"); + return; + } + + bkpt = db_breakpoint_alloc(); + if (bkpt == 0) { + db_printf("Too many breakpoints.\n"); + return; + } + + bkpt->map = map; + bkpt->address = addr; + bkpt->flags = 0; + bkpt->init_count = count; + bkpt->count = count; + + bkpt->link = db_breakpoint_list; + db_breakpoint_list = bkpt; +} + +static void +db_delete_breakpoint(map, addr) + vm_map_t map; + db_addr_t addr; +{ + register db_breakpoint_t bkpt; + register db_breakpoint_t *prev; + + for (prev = &db_breakpoint_list; + (bkpt = *prev) != 0; + prev = &bkpt->link) { + if (db_map_equal(bkpt->map, map) && + (bkpt->address == addr)) { + *prev = bkpt->link; + break; + } + } + if (bkpt == 0) { + db_printf("Not set.\n"); + return; + } + + db_breakpoint_free(bkpt); +} + +static db_breakpoint_t +db_find_breakpoint(map, addr) + vm_map_t map; + db_addr_t addr; +{ + register db_breakpoint_t bkpt; + + for (bkpt = db_breakpoint_list; + bkpt != 0; + bkpt = bkpt->link) + { + if (db_map_equal(bkpt->map, map) && + (bkpt->address == addr)) + return (bkpt); + } + return (0); +} + +db_breakpoint_t +db_find_breakpoint_here(addr) + db_addr_t addr; +{ + return db_find_breakpoint(db_map_addr(addr), addr); +} + +static boolean_t db_breakpoints_inserted = TRUE; + +#ifndef BKPT_WRITE +#define BKPT_WRITE(addr, storage) \ +do { \ + *storage = db_get_value(addr, BKPT_SIZE, FALSE); \ + db_put_value(addr, BKPT_SIZE, BKPT_SET(*storage)); \ +} while (0) +#endif + +#ifndef BKPT_CLEAR +#define BKPT_CLEAR(addr, storage) \ + db_put_value(addr, BKPT_SIZE, *storage) +#endif + +void +db_set_breakpoints() +{ + register db_breakpoint_t bkpt; + + if (!db_breakpoints_inserted) { + + for (bkpt = db_breakpoint_list; + bkpt != 0; + bkpt = bkpt->link) + if (db_map_current(bkpt->map)) { + BKPT_WRITE(bkpt->address, &bkpt->bkpt_inst); + } + db_breakpoints_inserted = TRUE; + } +} + +void +db_clear_breakpoints() +{ + register db_breakpoint_t bkpt; + + if (db_breakpoints_inserted) { + + for (bkpt = db_breakpoint_list; + bkpt != 0; + bkpt = bkpt->link) + if (db_map_current(bkpt->map)) { + BKPT_CLEAR(bkpt->address, &bkpt->bkpt_inst); + } + db_breakpoints_inserted = FALSE; + } +} + +#ifdef SOFTWARE_SSTEP +/* + * Set a temporary breakpoint. + * The instruction is changed immediately, + * so the breakpoint does not have to be on the breakpoint list. + */ +db_breakpoint_t +db_set_temp_breakpoint(addr) + db_addr_t addr; +{ + register db_breakpoint_t bkpt; + + bkpt = db_breakpoint_alloc(); + if (bkpt == 0) { + db_printf("Too many breakpoints.\n"); + return 0; + } + + bkpt->map = NULL; + bkpt->address = addr; + bkpt->flags = BKPT_TEMP; + bkpt->init_count = 1; + bkpt->count = 1; + + BKPT_WRITE(bkpt->address, &bkpt->bkpt_inst); + return bkpt; +} + +void +db_delete_temp_breakpoint(bkpt) + db_breakpoint_t bkpt; +{ + BKPT_CLEAR(bkpt->address, &bkpt->bkpt_inst); + db_breakpoint_free(bkpt); +} +#endif /* SOFTWARE_SSTEP */ + +/* + * List breakpoints. + */ +static void +db_list_breakpoints() +{ + register db_breakpoint_t bkpt; + + if (db_breakpoint_list == 0) { + db_printf("No breakpoints set\n"); + return; + } + + db_printf(" Map Count Address\n"); + for (bkpt = db_breakpoint_list; + bkpt != 0; + bkpt = bkpt->link) { + db_printf("%s%8p %5d ", + db_map_current(bkpt->map) ? "*" : " ", + (void *)bkpt->map, bkpt->init_count); + db_printsym(bkpt->address, DB_STGY_PROC); + db_printf("\n"); + } +} + +/* Delete breakpoint */ +/*ARGSUSED*/ +void +db_delete_cmd(addr, have_addr, count, modif) + db_expr_t addr; + boolean_t have_addr; + db_expr_t count; + char * modif; +{ + db_delete_breakpoint(db_map_addr(addr), (db_addr_t)addr); +} + +/* Set breakpoint with skip count */ +/*ARGSUSED*/ +void +db_breakpoint_cmd(addr, have_addr, count, modif) + db_expr_t addr; + boolean_t have_addr; + db_expr_t count; + char * modif; +{ + if (count == -1) + count = 1; + + db_set_breakpoint(db_map_addr(addr), (db_addr_t)addr, count); +} + +/* list breakpoints */ +void +db_listbreak_cmd(dummy1, dummy2, dummy3, dummy4) + db_expr_t dummy1; + boolean_t dummy2; + db_expr_t dummy3; + char * dummy4; +{ + db_list_breakpoints(); +} + +/* + * We want ddb to be usable before most of the kernel has been + * initialized. In particular, current_thread() or kernel_map + * (or both) may be null. + */ + +boolean_t +db_map_equal(map1, map2) + vm_map_t map1, map2; +{ + return ((map1 == map2) || + ((map1 == NULL) && (map2 == kernel_map)) || + ((map1 == kernel_map) && (map2 == NULL))); +} + +boolean_t +db_map_current(map) + vm_map_t map; +{ +#if 0 + thread_t thread; + + return ((map == NULL) || + (map == kernel_map) || + (((thread = current_thread()) != NULL) && + (map == thread->task->map))); +#else + return (1); +#endif +} + +vm_map_t +db_map_addr(addr) + vm_offset_t addr; +{ +#if 0 + thread_t thread; + + /* + * We want to return kernel_map for all + * non-user addresses, even when debugging + * kernel tasks with their own maps. + */ + + if ((VM_MIN_ADDRESS <= addr) && + (addr < VM_MAX_ADDRESS) && + ((thread = current_thread()) != NULL)) + return thread->task->map; + else +#endif + return kernel_map; +} diff --git a/sys/ddb/db_break.h b/sys/ddb/db_break.h new file mode 100644 index 0000000..f30b933 --- /dev/null +++ b/sys/ddb/db_break.h @@ -0,0 +1,67 @@ +/*- + * Mach Operating System + * Copyright (c) 1991,1990 Carnegie Mellon University + * All Rights Reserved. + * + * Permission to use, copy, modify and distribute this software and its + * documentation is hereby granted, provided that both the copyright + * notice and this permission notice appear in all copies of the + * software, derivative works or modified versions, and any portions + * thereof, and that both notices appear in supporting documentation. + * + * CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS + * CONDITION. CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND FOR + * ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE. + * + * Carnegie Mellon requests users of this software to return to + * + * Software Distribution Coordinator or Software.Distribution@CS.CMU.EDU + * School of Computer Science + * Carnegie Mellon University + * Pittsburgh PA 15213-3890 + * + * any improvements or extensions that they make and grant Carnegie the + * rights to redistribute these changes. + * + * $FreeBSD$ + */ + +/* + * Author: David B. Golub, Carnegie Mellon University + * Date: 7/90 + */ +#ifndef _DDB_DB_BREAK_H_ +#define _DDB_DB_BREAK_H_ + +/* + * Breakpoint. + */ + +#ifndef BKPT_INST_TYPE +#define BKPT_INST_TYPE int +#endif + +struct db_breakpoint { + vm_map_t map; /* in this map */ + db_addr_t address; /* set here */ + int init_count; /* number of times to skip bkpt */ + int count; /* current count */ + int flags; /* flags: */ +#define BKPT_SINGLE_STEP 0x2 /* to simulate single step */ +#define BKPT_TEMP 0x4 /* temporary */ + BKPT_INST_TYPE bkpt_inst; /* saved instruction at bkpt */ + struct db_breakpoint *link; /* link in in-use or free chain */ +}; +typedef struct db_breakpoint *db_breakpoint_t; + +void db_clear_breakpoints(void); +#ifdef SOFTWARE_SSTEP +void db_delete_temp_breakpoint(db_breakpoint_t); +#endif +db_breakpoint_t db_find_breakpoint_here(db_addr_t addr); +void db_set_breakpoints(void); +#ifdef SOFTWARE_SSTEP +db_breakpoint_t db_set_temp_breakpoint(db_addr_t); +#endif + +#endif /* !_DDB_DB_BREAK_H_ */ diff --git a/sys/ddb/db_capture.c b/sys/ddb/db_capture.c new file mode 100644 index 0000000..50ae2cf --- /dev/null +++ b/sys/ddb/db_capture.c @@ -0,0 +1,361 @@ +/*- + * 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. + */ + +/* + * DDB capture support: capture kernel debugger output into a fixed-size + * buffer for later dumping to disk or extraction from user space. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include "opt_ddb.h" + +#include <sys/param.h> +#include <sys/conf.h> +#include <sys/kernel.h> +#include <sys/kerneldump.h> +#include <sys/malloc.h> +#include <sys/msgbuf.h> +#include <sys/priv.h> +#include <sys/sx.h> +#include <sys/sysctl.h> +#include <sys/systm.h> + +#include <ddb/ddb.h> +#include <ddb/db_lex.h> + +/* + * While it would be desirable to use a small block-sized buffer and dump + * incrementally to disk in fixed-size blocks, it's not possible to enter + * kernel dumper routines without restarting the kernel, which is undesirable + * in the midst of debugging. Instead, we maintain a large static global + * buffer that we fill from DDB's output routines. + * + * We enforce an invariant at runtime that buffer sizes are even multiples of + * the textdump block size, which is a design choice that we might want to + * reconsider. + */ +static MALLOC_DEFINE(M_DDB_CAPTURE, "ddb_capture", "DDB capture buffer"); + +#ifndef DDB_CAPTURE_DEFAULTBUFSIZE +#define DDB_CAPTURE_DEFAULTBUFSIZE 48*1024 +#endif +#ifndef DDB_CAPTURE_MAXBUFSIZE +#define DDB_CAPTURE_MAXBUFSIZE 5*1024*1024 +#endif +#define DDB_CAPTURE_FILENAME "ddb.txt" /* Captured DDB output. */ + +static char *db_capture_buf; +static u_int db_capture_bufsize = DDB_CAPTURE_DEFAULTBUFSIZE; +static u_int db_capture_maxbufsize = DDB_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. */ + +struct sx db_capture_sx; /* Lock against user thread races. */ +SX_SYSINIT(db_capture_sx, &db_capture_sx, "db_capture_sx"); + +static SYSCTL_NODE(_debug_ddb, OID_AUTO, capture, CTLFLAG_RW, 0, + "DDB capture options"); + +SYSCTL_UINT(_debug_ddb_capture, OID_AUTO, bufoff, CTLFLAG_RD, + &db_capture_bufoff, 0, "Bytes of data in DDB capture buffer"); + +SYSCTL_UINT(_debug_ddb_capture, OID_AUTO, maxbufsize, CTLFLAG_RD, + &db_capture_maxbufsize, 0, + "Maximum value for debug.ddb.capture.bufsize"); + +SYSCTL_INT(_debug_ddb_capture, OID_AUTO, inprogress, CTLFLAG_RD, + &db_capture_inprogress, 0, "DDB output capture in progress"); + +/* + * Boot-time allocation of the DDB capture buffer, if any. Force all buffer + * sizes, including the maximum size, to be rounded to block sizes. + */ +static void +db_capture_sysinit(__unused void *dummy) +{ + + TUNABLE_INT_FETCH("debug.ddb.capture.bufsize", &db_capture_bufsize); + db_capture_maxbufsize = roundup(db_capture_maxbufsize, + TEXTDUMP_BLOCKSIZE); + 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_DDB_CAPTURE, + M_WAITOK); +} +SYSINIT(db_capture, SI_SUB_DDB_SERVICES, SI_ORDER_ANY, db_capture_sysinit, + NULL); + +/* + * Run-time adjustment of the capture buffer. + */ +static int +sysctl_debug_ddb_capture_bufsize(SYSCTL_HANDLER_ARGS) +{ + u_int len, size; + char *buf; + int error; + + size = db_capture_bufsize; + 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) { + /* + * Potentially the buffer is quite large, so if we can't + * allocate it, fail rather than waiting. + */ + buf = malloc(size, M_DDB_CAPTURE, M_NOWAIT); + if (buf == NULL) { + sx_xunlock(&db_capture_sx); + return (ENOMEM); + } + len = min(db_capture_bufoff, size); + } else { + buf = NULL; + len = 0; + } + if (db_capture_buf != NULL && buf != NULL) + bcopy(db_capture_buf, buf, len); + if (db_capture_buf != NULL) + free(db_capture_buf, M_DDB_CAPTURE); + db_capture_bufoff = len; + db_capture_buf = buf; + db_capture_bufsize = size; + sx_xunlock(&db_capture_sx); + + KASSERT(db_capture_bufoff <= db_capture_bufsize, + ("sysctl_debug_ddb_capture_bufsize: bufoff > bufsize")); + KASSERT(db_capture_bufsize <= db_capture_maxbufsize, + ("sysctl_debug_ddb_capture_maxbufsize: bufsize > maxbufsize")); + + return (0); +} +SYSCTL_PROC(_debug_ddb_capture, OID_AUTO, bufsize, CTLTYPE_UINT|CTLFLAG_RW, + 0, 0, sysctl_debug_ddb_capture_bufsize, "IU", + "Size of DDB capture buffer"); + +/* + * Sysctl to read out the capture buffer from userspace. We require + * privilege as sensitive process/memory information may be accessed. + */ +static int +sysctl_debug_ddb_capture_data(SYSCTL_HANDLER_ARGS) +{ + int error; + char ch; + + error = priv_check(req->td, PRIV_DDB_CAPTURE); + if (error) + return (error); + + sx_slock(&db_capture_sx); + error = SYSCTL_OUT(req, db_capture_buf, db_capture_bufoff); + sx_sunlock(&db_capture_sx); + if (error) + return (error); + ch = '\0'; + return (SYSCTL_OUT(req, &ch, sizeof(ch))); +} +SYSCTL_PROC(_debug_ddb_capture, OID_AUTO, data, CTLTYPE_STRING | CTLFLAG_RD, + NULL, 0, sysctl_debug_ddb_capture_data, "A", "DDB capture data"); + +/* + * Routines for capturing DDB output into a fixed-size buffer. These are + * invoked from DDB's input and output routines. If we hit the limit on the + * buffer, we simply drop further data. + */ +void +db_capture_write(char *buffer, u_int buflen) +{ + u_int len; + + if (db_capture_inprogress == 0 || db_capture_inpager) + return; + len = min(buflen, db_capture_bufsize - db_capture_bufoff); + bcopy(buffer, db_capture_buf + db_capture_bufoff, len); + db_capture_bufoff += len; + + KASSERT(db_capture_bufoff <= db_capture_bufsize, + ("db_capture_write: bufoff > bufsize")); +} + +void +db_capture_writech(char ch) +{ + + return (db_capture_write(&ch, sizeof(ch))); +} + +void +db_capture_enterpager(void) +{ + + db_capture_inpager = 1; +} + +void +db_capture_exitpager(void) +{ + + db_capture_inpager = 0; +} + +/* + * 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 +db_capture_reset(void) +{ + + db_capture_inprogress = 0; + db_capture_bufoff = 0; + db_capture_bufpadding = 0; +} + +/* + * Start capture. Only one session is allowed at any time, but we may + * continue a previous session, so the buffer isn't reset. + */ +static void +db_capture_start(void) +{ + + if (db_capture_inprogress) { + db_printf("Capture already started\n"); + return; + } + db_capture_inprogress = 1; +} + +/* + * 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) +{ + + if (db_capture_inprogress == 0) { + db_printf("Capture not started\n"); + return; + } + 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, DDB_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: + * + * capture on - start DDB output capture + * capture off - stop DDB output capture + * capture reset - reset DDB capture buffer (also stops capture) + * capture status - print DDB output capture status + */ +static void +db_capture_usage(void) +{ + + db_error("capture [on|off|reset|status]\n"); +} + +void +db_capture_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_capture_usage(); + return; + } + if (db_read_token() != tEOL) + db_error("?\n"); + if (strcmp(db_tok_string, "on") == 0) + db_capture_start(); + else if (strcmp(db_tok_string, "off") == 0) + db_capture_stop(); + else if (strcmp(db_tok_string, "reset") == 0) + db_capture_reset(); + else if (strcmp(db_tok_string, "status") == 0) { + db_printf("%u/%u bytes used\n", db_capture_bufoff, + db_capture_bufsize); + if (db_capture_inprogress) + db_printf("capture is on\n"); + else + db_printf("capture is off\n"); + } else + db_capture_usage(); +} diff --git a/sys/ddb/db_command.c b/sys/ddb/db_command.c new file mode 100644 index 0000000..cc4bde9 --- /dev/null +++ b/sys/ddb/db_command.c @@ -0,0 +1,872 @@ +/*- + * Mach Operating System + * Copyright (c) 1991,1990 Carnegie Mellon University + * All Rights Reserved. + * + * Permission to use, copy, modify and distribute this software and its + * documentation is hereby granted, provided that both the copyright + * notice and this permission notice appear in all copies of the + * software, derivative works or modified versions, and any portions + * thereof, and that both notices appear in supporting documentation. + * + * CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS + * CONDITION. CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND FOR + * ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE. + * + * Carnegie Mellon requests users of this software to return to + * + * Software Distribution Coordinator or Software.Distribution@CS.CMU.EDU + * School of Computer Science + * Carnegie Mellon University + * Pittsburgh PA 15213-3890 + * + * any improvements or extensions that they make and grant Carnegie the + * rights to redistribute these changes. + */ +/* + * Author: David B. Golub, Carnegie Mellon University + * Date: 7/90 + */ +/* + * Command dispatcher. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/param.h> +#include <sys/linker_set.h> +#include <sys/lock.h> +#include <sys/kdb.h> +#include <sys/mutex.h> +#include <sys/proc.h> +#include <sys/reboot.h> +#include <sys/signalvar.h> +#include <sys/systm.h> +#include <sys/cons.h> +#include <sys/conf.h> +#include <sys/watchdog.h> +#include <sys/kernel.h> + +#include <ddb/ddb.h> +#include <ddb/db_command.h> +#include <ddb/db_lex.h> +#include <ddb/db_output.h> + +#include <machine/cpu.h> +#include <machine/setjmp.h> + +/* + * Exported global variables + */ +boolean_t db_cmd_loop_done; +db_addr_t db_dot; +db_addr_t db_last_addr; +db_addr_t db_prev; +db_addr_t db_next; + +static db_cmdfcn_t db_dump; +static db_cmdfcn_t db_fncall; +static db_cmdfcn_t db_gdb; +static db_cmdfcn_t db_halt; +static db_cmdfcn_t db_kill; +static db_cmdfcn_t db_reset; +static db_cmdfcn_t db_stack_trace; +static db_cmdfcn_t db_stack_trace_all; +static db_cmdfcn_t db_watchdog; + +/* + * 'show' commands + */ + +static struct command db_show_all_cmds[] = { + { "trace", db_stack_trace_all, 0, 0 }, +}; +struct command_table db_show_all_table = + LIST_HEAD_INITIALIZER(db_show_all_table); + +static struct command db_show_cmds[] = { + { "all", 0, 0, &db_show_all_table }, + { "registers", db_show_regs, 0, 0 }, + { "breaks", db_listbreak_cmd, 0, 0 }, + { "threads", db_show_threads, 0, 0 }, +}; +struct command_table db_show_table = LIST_HEAD_INITIALIZER(db_show_table); + +static struct command db_cmds[] = { + { "print", db_print_cmd, 0, 0 }, + { "p", db_print_cmd, 0, 0 }, + { "examine", db_examine_cmd, CS_SET_DOT, 0 }, + { "x", db_examine_cmd, CS_SET_DOT, 0 }, + { "search", db_search_cmd, CS_OWN|CS_SET_DOT, 0 }, + { "set", db_set_cmd, CS_OWN, 0 }, + { "write", db_write_cmd, CS_MORE|CS_SET_DOT, 0 }, + { "w", db_write_cmd, CS_MORE|CS_SET_DOT, 0 }, + { "delete", db_delete_cmd, 0, 0 }, + { "d", db_delete_cmd, 0, 0 }, + { "dump", db_dump, 0, 0 }, + { "break", db_breakpoint_cmd, 0, 0 }, + { "b", db_breakpoint_cmd, 0, 0 }, + { "dwatch", db_deletewatch_cmd, 0, 0 }, + { "watch", db_watchpoint_cmd, CS_MORE,0 }, + { "dhwatch", db_deletehwatch_cmd, 0, 0 }, + { "hwatch", db_hwatchpoint_cmd, 0, 0 }, + { "step", db_single_step_cmd, 0, 0 }, + { "s", db_single_step_cmd, 0, 0 }, + { "continue", db_continue_cmd, 0, 0 }, + { "c", db_continue_cmd, 0, 0 }, + { "until", db_trace_until_call_cmd,0, 0 }, + { "next", db_trace_until_matching_cmd,0, 0 }, + { "match", db_trace_until_matching_cmd,0, 0 }, + { "trace", db_stack_trace, CS_OWN, 0 }, + { "t", db_stack_trace, CS_OWN, 0 }, + /* XXX alias for all trace */ + { "alltrace", db_stack_trace_all, 0, 0 }, + { "where", db_stack_trace, CS_OWN, 0 }, + { "bt", db_stack_trace, CS_OWN, 0 }, + { "call", db_fncall, CS_OWN, 0 }, + { "show", 0, 0, &db_show_table }, + { "ps", db_ps, 0, 0 }, + { "gdb", db_gdb, 0, 0 }, + { "halt", db_halt, 0, 0 }, + { "reboot", db_reset, 0, 0 }, + { "reset", db_reset, 0, 0 }, + { "kill", db_kill, CS_OWN, 0 }, + { "watchdog", db_watchdog, CS_OWN, 0 }, + { "thread", db_set_thread, CS_OWN, 0 }, + { "run", db_run_cmd, CS_OWN, 0 }, + { "script", db_script_cmd, CS_OWN, 0 }, + { "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 }, + { "findstack", db_findstack_cmd, 0, 0 }, +}; +struct command_table db_cmd_table = LIST_HEAD_INITIALIZER(db_cmd_table); + +static struct command *db_last_command = 0; + +/* + * if 'ed' style: 'dot' is set at start of last item printed, + * and '+' points to next line. + * Otherwise: 'dot' points to next item, '..' points to last. + */ +static boolean_t db_ed_style = TRUE; + +/* + * Utility routine - discard tokens through end-of-line. + */ +void +db_skip_to_eol() +{ + int t; + do { + t = db_read_token(); + } while (t != tEOL); +} + +/* + * Results of command search. + */ +#define CMD_UNIQUE 0 +#define CMD_FOUND 1 +#define CMD_NONE 2 +#define CMD_AMBIGUOUS 3 +#define CMD_HELP 4 + +static void db_cmd_match(char *name, struct command *cmd, + struct command **cmdp, int *resultp); +static void db_cmd_list(struct command_table *table); +static int db_cmd_search(char *name, struct command_table *table, + struct command **cmdp); +static void db_command(struct command **last_cmdp, + struct command_table *cmd_table, int dopager); + +/* + * Initialize the command lists from the static tables. + */ +void +db_command_init(void) +{ +#define N(a) (sizeof(a) / sizeof(a[0])) + int i; + + for (i = 0; i < N(db_cmds); i++) + db_command_register(&db_cmd_table, &db_cmds[i]); + for (i = 0; i < N(db_show_cmds); i++) + db_command_register(&db_show_table, &db_show_cmds[i]); + for (i = 0; i < N(db_show_all_cmds); i++) + db_command_register(&db_show_all_table, &db_show_all_cmds[i]); +#undef N +} + +/* + * Register a command. + */ +void +db_command_register(struct command_table *list, struct command *cmd) +{ + struct command *c, *last; + + last = NULL; + LIST_FOREACH(c, list, next) { + int n = strcmp(cmd->name, c->name); + + /* Check that the command is not already present. */ + if (n == 0) { + printf("%s: Warning, the command \"%s\" already exists;" + " ignoring request\n", __func__, cmd->name); + return; + } + if (n < 0) { + /* NB: keep list sorted lexicographically */ + LIST_INSERT_BEFORE(c, cmd, next); + return; + } + last = c; + } + if (last == NULL) + LIST_INSERT_HEAD(list, cmd, next); + else + LIST_INSERT_AFTER(last, cmd, next); +} + +/* + * Remove a command previously registered with db_command_register. + */ +void +db_command_unregister(struct command_table *list, struct command *cmd) +{ + struct command *c; + + LIST_FOREACH(c, list, next) { + if (cmd == c) { + LIST_REMOVE(cmd, next); + return; + } + } + /* NB: intentionally quiet */ +} + +/* + * Helper function to match a single command. + */ +static void +db_cmd_match(name, cmd, cmdp, resultp) + char * name; + struct command *cmd; + struct command **cmdp; /* out */ + int * resultp; +{ + char *lp, *rp; + int c; + + lp = name; + rp = cmd->name; + while ((c = *lp) == *rp) { + if (c == 0) { + /* complete match */ + *cmdp = cmd; + *resultp = CMD_UNIQUE; + return; + } + lp++; + rp++; + } + if (c == 0) { + /* end of name, not end of command - + partial match */ + if (*resultp == CMD_FOUND) { + *resultp = CMD_AMBIGUOUS; + /* but keep looking for a full match - + this lets us match single letters */ + } else { + *cmdp = cmd; + *resultp = CMD_FOUND; + } + } +} + +/* + * Search for command prefix. + */ +static int +db_cmd_search(name, table, cmdp) + char * name; + struct command_table *table; + struct command **cmdp; /* out */ +{ + struct command *cmd; + int result = CMD_NONE; + + LIST_FOREACH(cmd, table, next) { + db_cmd_match(name,cmd,cmdp,&result); + if (result == CMD_UNIQUE) + break; + } + + if (result == CMD_NONE) { + /* check for 'help' */ + if (name[0] == 'h' && name[1] == 'e' + && name[2] == 'l' && name[3] == 'p') + result = CMD_HELP; + } + return (result); +} + +static void +db_cmd_list(table) + struct command_table *table; +{ + register struct command *cmd; + + LIST_FOREACH(cmd, table, next) { + db_printf("%-12s", cmd->name); + db_end_line(12); + } +} + +static void +db_command(last_cmdp, cmd_table, dopager) + struct command **last_cmdp; /* IN_OUT */ + struct command_table *cmd_table; + int dopager; +{ + struct command *cmd = NULL; + int t; + char modif[TOK_STRING_SIZE]; + db_expr_t addr, count; + boolean_t have_addr = FALSE; + int result; + + t = db_read_token(); + if (t == tEOL) { + /* empty line repeats last command, at 'next' */ + cmd = *last_cmdp; + addr = (db_expr_t)db_next; + have_addr = FALSE; + count = 1; + modif[0] = '\0'; + } + else if (t == tEXCL) { + db_fncall((db_expr_t)0, (boolean_t)0, (db_expr_t)0, (char *)0); + return; + } + else if (t != tIDENT) { + db_printf("?\n"); + db_flush_lex(); + return; + } + else { + /* + * Search for command + */ + while (cmd_table) { + result = db_cmd_search(db_tok_string, + cmd_table, + &cmd); + switch (result) { + case CMD_NONE: + db_printf("No such command\n"); + db_flush_lex(); + return; + case CMD_AMBIGUOUS: + db_printf("Ambiguous\n"); + db_flush_lex(); + return; + case CMD_HELP: + db_cmd_list(cmd_table); + db_flush_lex(); + return; + default: + break; + } + if ((cmd_table = cmd->more) != NULL) { + t = db_read_token(); + if (t != tIDENT) { + db_cmd_list(cmd_table); + db_flush_lex(); + return; + } + } + } + + if ((cmd->flag & CS_OWN) == 0) { + /* + * Standard syntax: + * command [/modifier] [addr] [,count] + */ + t = db_read_token(); + if (t == tSLASH) { + t = db_read_token(); + if (t != tIDENT) { + db_printf("Bad modifier\n"); + db_flush_lex(); + return; + } + db_strcpy(modif, db_tok_string); + } + else { + db_unread_token(t); + modif[0] = '\0'; + } + + if (db_expression(&addr)) { + db_dot = (db_addr_t) addr; + db_last_addr = db_dot; + have_addr = TRUE; + } + else { + addr = (db_expr_t) db_dot; + have_addr = FALSE; + } + t = db_read_token(); + if (t == tCOMMA) { + if (!db_expression(&count)) { + db_printf("Count missing\n"); + db_flush_lex(); + return; + } + } + else { + db_unread_token(t); + count = -1; + } + if ((cmd->flag & CS_MORE) == 0) { + db_skip_to_eol(); + } + } + } + *last_cmdp = cmd; + if (cmd != 0) { + /* + * Execute the command. + */ + if (dopager) + db_enable_pager(); + else + db_disable_pager(); + (*cmd->fcn)(addr, have_addr, count, modif); + if (dopager) + db_disable_pager(); + + if (cmd->flag & CS_SET_DOT) { + /* + * If command changes dot, set dot to + * previous address displayed (if 'ed' style). + */ + if (db_ed_style) { + db_dot = db_prev; + } + else { + db_dot = db_next; + } + } + else { + /* + * If command does not change dot, + * set 'next' location to be the same. + */ + db_next = db_dot; + } + } +} + +/* + * At least one non-optional command must be implemented using + * DB_COMMAND() so that db_cmd_set gets created. Here is one. + */ +DB_COMMAND(panic, db_panic) +{ + db_disable_pager(); + panic("from debugger"); +} + +void +db_command_loop() +{ + /* + * Initialize 'prev' and 'next' to dot. + */ + db_prev = db_dot; + db_next = db_dot; + + db_cmd_loop_done = 0; + while (!db_cmd_loop_done) { + if (db_print_position() != 0) + db_printf("\n"); + + db_printf("db> "); + (void) db_read_line(); + + db_command(&db_last_command, &db_cmd_table, /* dopager */ 1); + } +} + +/* + * Execute a command on behalf of a script. The caller is responsible for + * making sure that the command string is < DB_MAXLINE or it will be + * truncated. + * + * XXXRW: Runs by injecting faked input into DDB input stream; it would be + * nicer to use an alternative approach that didn't mess with the previous + * command buffer. + */ +void +db_command_script(const char *command) +{ + db_prev = db_next = db_dot; + db_inject_line(command); + db_command(&db_last_command, &db_cmd_table, /* dopager */ 0); +} + +void +db_error(s) + const char *s; +{ + if (s) + db_printf("%s", s); + db_flush_lex(); + kdb_reenter(); +} + +static void +db_dump(db_expr_t dummy, boolean_t dummy2, db_expr_t dummy3, char *dummy4) +{ + int error; + + if (textdump_pending) { + db_printf("textdump_pending set.\n" + "run \"textdump unset\" first or \"textdump dump\" for a textdump.\n"); + return; + } + error = doadump(FALSE); + if (error) { + db_printf("Cannot dump: "); + switch (error) { + case EBUSY: + db_printf("debugger got invoked while dumping.\n"); + break; + case ENXIO: + db_printf("no dump device specified.\n"); + break; + default: + db_printf("unknown error (error=%d).\n", error); + break; + } + } +} + +/* + * Call random function: + * !expr(arg,arg,arg) + */ + +/* The generic implementation supports a maximum of 10 arguments. */ +typedef db_expr_t __db_f(db_expr_t, db_expr_t, db_expr_t, db_expr_t, + db_expr_t, db_expr_t, db_expr_t, db_expr_t, db_expr_t, db_expr_t); + +static __inline int +db_fncall_generic(db_expr_t addr, db_expr_t *rv, int nargs, db_expr_t args[]) +{ + __db_f *f = (__db_f *)addr; + + if (nargs > 10) { + db_printf("Too many arguments (max 10)\n"); + return (0); + } + *rv = (*f)(args[0], args[1], args[2], args[3], args[4], args[5], + args[6], args[7], args[8], args[9]); + return (1); +} + +static void +db_fncall(dummy1, dummy2, dummy3, dummy4) + db_expr_t dummy1; + boolean_t dummy2; + db_expr_t dummy3; + char * dummy4; +{ + db_expr_t fn_addr; + db_expr_t args[DB_MAXARGS]; + int nargs = 0; + db_expr_t retval; + int t; + + if (!db_expression(&fn_addr)) { + db_printf("Bad function\n"); + db_flush_lex(); + return; + } + + t = db_read_token(); + if (t == tLPAREN) { + if (db_expression(&args[0])) { + nargs++; + while ((t = db_read_token()) == tCOMMA) { + if (nargs == DB_MAXARGS) { + db_printf("Too many arguments (max %d)\n", DB_MAXARGS); + db_flush_lex(); + return; + } + if (!db_expression(&args[nargs])) { + db_printf("Argument missing\n"); + db_flush_lex(); + return; + } + nargs++; + } + db_unread_token(t); + } + if (db_read_token() != tRPAREN) { + db_printf("?\n"); + db_flush_lex(); + return; + } + } + db_skip_to_eol(); + db_disable_pager(); + + if (DB_CALL(fn_addr, &retval, nargs, args)) + db_printf("= %#lr\n", (long)retval); +} + +static void +db_halt(db_expr_t dummy, boolean_t dummy2, db_expr_t dummy3, char *dummy4) +{ + + cpu_halt(); +} + +static void +db_kill(dummy1, dummy2, dummy3, dummy4) + db_expr_t dummy1; + boolean_t dummy2; + db_expr_t dummy3; + char * dummy4; +{ + db_expr_t old_radix, pid, sig; + struct proc *p; + +#define DB_ERROR(f) do { db_printf f; db_flush_lex(); goto out; } while (0) + + /* + * PIDs and signal numbers are typically represented in base + * 10, so make that the default here. It can, of course, be + * overridden by specifying a prefix. + */ + old_radix = db_radix; + db_radix = 10; + /* Retrieve arguments. */ + if (!db_expression(&sig)) + DB_ERROR(("Missing signal number\n")); + if (!db_expression(&pid)) + DB_ERROR(("Missing process ID\n")); + db_skip_to_eol(); + if (!_SIG_VALID(sig)) + DB_ERROR(("Signal number out of range\n")); + + /* + * Find the process in question. allproc_lock is not needed + * since we're in DDB. + */ + /* sx_slock(&allproc_lock); */ + FOREACH_PROC_IN_SYSTEM(p) + if (p->p_pid == pid) + break; + /* sx_sunlock(&allproc_lock); */ + if (p == NULL) + DB_ERROR(("Can't find process with pid %ld\n", (long) pid)); + + /* If it's already locked, bail; otherwise, do the deed. */ + if (PROC_TRYLOCK(p) == 0) + DB_ERROR(("Can't lock process with pid %ld\n", (long) pid)); + else { + pksignal(p, sig, NULL); + PROC_UNLOCK(p); + } + +out: + db_radix = old_radix; +#undef DB_ERROR +} + +/* + * Reboot. In case there is an additional argument, take it as delay in + * seconds. Default to 15s if we cannot parse it and make sure we will + * never wait longer than 1 week. Some code is similar to + * kern_shutdown.c:shutdown_panic(). + */ +#ifndef DB_RESET_MAXDELAY +#define DB_RESET_MAXDELAY (3600 * 24 * 7) +#endif + +static void +db_reset(db_expr_t addr, boolean_t have_addr, db_expr_t count __unused, + char *modif __unused) +{ + int delay, loop; + + if (have_addr) { + delay = (int)db_hex2dec(addr); + + /* If we parse to fail, use 15s. */ + if (delay == -1) + delay = 15; + + /* Cap at one week. */ + if ((uintmax_t)delay > (uintmax_t)DB_RESET_MAXDELAY) + delay = DB_RESET_MAXDELAY; + + db_printf("Automatic reboot in %d seconds - " + "press a key on the console to abort\n", delay); + for (loop = delay * 10; loop > 0; --loop) { + DELAY(1000 * 100); /* 1/10th second */ + /* Did user type a key? */ + if (cncheckc() != -1) + return; + } + } + + cpu_reset(); +} + +static void +db_watchdog(dummy1, dummy2, dummy3, dummy4) + db_expr_t dummy1; + boolean_t dummy2; + db_expr_t dummy3; + char * dummy4; +{ + db_expr_t old_radix, tout; + int err, i; + + old_radix = db_radix; + db_radix = 10; + err = db_expression(&tout); + db_skip_to_eol(); + db_radix = old_radix; + + /* If no argument is provided the watchdog will just be disabled. */ + if (err == 0) { + db_printf("No argument provided, disabling watchdog\n"); + tout = 0; + } else if ((tout & WD_INTERVAL) == WD_TO_NEVER) { + db_error("Out of range watchdog interval\n"); + return; + } + EVENTHANDLER_INVOKE(watchdog_list, tout, &i); +} + +static void +db_gdb(db_expr_t dummy1, boolean_t dummy2, db_expr_t dummy3, char *dummy4) +{ + + if (kdb_dbbe_select("gdb") != 0) { + db_printf("The remote GDB backend could not be selected.\n"); + return; + } + /* + * Mark that we are done in the debugger. kdb_trap() + * should re-enter with the new backend. + */ + db_cmd_loop_done = 1; + db_printf("(ctrl-c will return control to ddb)\n"); +} + +static void +db_stack_trace(db_expr_t tid, boolean_t hastid, db_expr_t count, char *modif) +{ + struct thread *td; + db_expr_t radix; + pid_t pid; + int t; + + /* + * We parse our own arguments. We don't like the default radix. + */ + radix = db_radix; + db_radix = 10; + hastid = db_expression(&tid); + t = db_read_token(); + if (t == tCOMMA) { + if (!db_expression(&count)) { + db_printf("Count missing\n"); + db_flush_lex(); + return; + } + } else { + db_unread_token(t); + count = -1; + } + db_skip_to_eol(); + db_radix = radix; + + if (hastid) { + td = kdb_thr_lookup((lwpid_t)tid); + if (td == NULL) + td = kdb_thr_from_pid((pid_t)tid); + if (td == NULL) { + db_printf("Thread %d not found\n", (int)tid); + return; + } + } else + td = kdb_thread; + if (td->td_proc != NULL) + pid = td->td_proc->p_pid; + else + pid = -1; + db_printf("Tracing pid %d tid %ld td %p\n", pid, (long)td->td_tid, td); + db_trace_thread(td, count); +} + +static void +db_stack_trace_all(db_expr_t dummy, boolean_t dummy2, db_expr_t dummy3, + char *dummy4) +{ + struct proc *p; + struct thread *td; + jmp_buf jb; + void *prev_jb; + + FOREACH_PROC_IN_SYSTEM(p) { + prev_jb = kdb_jmpbuf(jb); + if (setjmp(jb) == 0) { + FOREACH_THREAD_IN_PROC(p, td) { + db_printf("\nTracing command %s pid %d tid %ld td %p\n", + p->p_comm, p->p_pid, (long)td->td_tid, td); + db_trace_thread(td, -1); + if (db_pager_quit) { + kdb_jmpbuf(prev_jb); + return; + } + } + } + kdb_jmpbuf(prev_jb); + } +} + +/* + * Take the parsed expression value from the command line that was parsed + * as a hexadecimal value and convert it as if the expression was parsed + * as a decimal value. Returns -1 if the expression was not a valid + * decimal value. + */ +db_expr_t +db_hex2dec(db_expr_t expr) +{ + uintptr_t x, y; + db_expr_t val; + + y = 1; + val = 0; + x = expr; + while (x != 0) { + if (x % 16 > 9) + return (-1); + val += (x % 16) * (y); + x >>= 4; + y *= 10; + } + return (val); +} diff --git a/sys/ddb/db_command.h b/sys/ddb/db_command.h new file mode 100644 index 0000000..2103a02 --- /dev/null +++ b/sys/ddb/db_command.h @@ -0,0 +1,57 @@ +/*- + * Mach Operating System + * Copyright (c) 1991,1990 Carnegie Mellon University + * All Rights Reserved. + * + * Permission to use, copy, modify and distribute this software and its + * documentation is hereby granted, provided that both the copyright + * notice and this permission notice appear in all copies of the + * software, derivative works or modified versions, and any portions + * thereof, and that both notices appear in supporting documentation. + * + * CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS + * CONDITION. CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND FOR + * ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE. + * + * Carnegie Mellon requests users of this software to return to + * + * Software Distribution Coordinator or Software.Distribution@CS.CMU.EDU + * School of Computer Science + * Carnegie Mellon University + * Pittsburgh PA 15213-3890 + * + * any improvements or extensions that they make and grant Carnegie the + * rights to redistribute these changes. + * + * $FreeBSD$ + */ + +#ifndef _DDB_DB_COMMAND_H_ +#define _DDB_DB_COMMAND_H_ + +/* + * Author: David B. Golub, Carnegie Mellon University + * Date: 7/90 + */ + +/* + * Helper functions. + */ +db_expr_t db_hex2dec(db_expr_t expr); + +/* + * Command loop declarations. + */ + +void db_command_init(void); +void db_command_loop(void); +void db_command_script(const char *command); + +extern db_addr_t db_dot; /* current location */ +extern db_addr_t db_last_addr; /* last explicit address typed */ +extern db_addr_t db_prev; /* last address examined + or written */ +extern db_addr_t db_next; /* next address to be examined + or written */ + +#endif /* !_DDB_DB_COMMAND_H_ */ diff --git a/sys/ddb/db_examine.c b/sys/ddb/db_examine.c new file mode 100644 index 0000000..fc4f472 --- /dev/null +++ b/sys/ddb/db_examine.c @@ -0,0 +1,341 @@ +/*- + * Mach Operating System + * Copyright (c) 1991,1990 Carnegie Mellon University + * All Rights Reserved. + * + * Permission to use, copy, modify and distribute this software and its + * documentation is hereby granted, provided that both the copyright + * notice and this permission notice appear in all copies of the + * software, derivative works or modified versions, and any portions + * thereof, and that both notices appear in supporting documentation. + * + * CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS + * CONDITION. CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND FOR + * ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE. + * + * Carnegie Mellon requests users of this software to return to + * + * Software Distribution Coordinator or Software.Distribution@CS.CMU.EDU + * School of Computer Science + * Carnegie Mellon University + * Pittsburgh PA 15213-3890 + * + * any improvements or extensions that they make and grant Carnegie the + * rights to redistribute these changes. + */ +/* + * Author: David B. Golub, Carnegie Mellon University + * Date: 7/90 + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/param.h> +#include <sys/systm.h> + +#include <ddb/ddb.h> + +#include <ddb/db_lex.h> +#include <ddb/db_output.h> +#include <ddb/db_command.h> +#include <ddb/db_sym.h> +#include <ddb/db_access.h> + +static char db_examine_format[TOK_STRING_SIZE] = "x"; + +static void db_examine(db_addr_t, char *, int); +static void db_search(db_addr_t, int, db_expr_t, db_expr_t, u_int); + +/* + * Examine (print) data. + */ +/*ARGSUSED*/ +void +db_examine_cmd(addr, have_addr, count, modif) + db_expr_t addr; + boolean_t have_addr; + db_expr_t count; + char * modif; +{ + if (modif[0] != '\0') + db_strcpy(db_examine_format, modif); + + if (count == -1) + count = 1; + + db_examine((db_addr_t) addr, db_examine_format, count); +} + +static void +db_examine(addr, fmt, count) + register + db_addr_t addr; + char * fmt; /* format string */ + int count; /* repeat count */ +{ + int c; + db_expr_t value; + int size; + int width; + char * fp; + + while (--count >= 0 && !db_pager_quit) { + fp = fmt; + size = 4; + while ((c = *fp++) != 0) { + switch (c) { + case 'b': + size = 1; + break; + case 'h': + size = 2; + break; + case 'l': + size = 4; + break; + case 'g': + size = 8; + break; + case 'a': /* address */ + size = sizeof(void *); + /* always forces a new line */ + if (db_print_position() != 0) + db_printf("\n"); + db_prev = addr; + db_printsym(addr, DB_STGY_ANY); + db_printf(":\t"); + break; + default: + if (db_print_position() == 0) { + /* Print the address. */ + db_printsym(addr, DB_STGY_ANY); + db_printf(":\t"); + db_prev = addr; + } + + width = size * 4; + switch (c) { + case 'r': /* signed, current radix */ + value = db_get_value(addr, size, TRUE); + addr += size; + db_printf("%+-*lr", width, (long)value); + break; + case 'x': /* unsigned hex */ + value = db_get_value(addr, size, FALSE); + addr += size; + db_printf("%-*lx", width, (long)value); + break; + case 'z': /* signed hex */ + value = db_get_value(addr, size, TRUE); + addr += size; + db_printf("%-*ly", width, (long)value); + break; + case 'd': /* signed decimal */ + value = db_get_value(addr, size, TRUE); + addr += size; + db_printf("%-*ld", width, (long)value); + break; + case 'u': /* unsigned decimal */ + value = db_get_value(addr, size, FALSE); + addr += size; + db_printf("%-*lu", width, (long)value); + break; + case 'o': /* unsigned octal */ + value = db_get_value(addr, size, FALSE); + addr += size; + db_printf("%-*lo", width, (long)value); + break; + case 'c': /* character */ + value = db_get_value(addr, 1, FALSE); + addr += 1; + if (value >= ' ' && value <= '~') + db_printf("%c", (int)value); + else + db_printf("\\%03o", (int)value); + break; + case 's': /* null-terminated string */ + for (;;) { + value = db_get_value(addr, 1, FALSE); + addr += 1; + if (value == 0) + break; + if (value >= ' ' && value <= '~') + db_printf("%c", (int)value); + else + db_printf("\\%03o", (int)value); + } + break; + case 'S': /* symbol */ + value = db_get_value(addr, sizeof(void *), + FALSE); + addr += sizeof(void *); + db_printsym(value, DB_STGY_ANY); + break; + case 'i': /* instruction */ + addr = db_disasm(addr, FALSE); + break; + case 'I': /* instruction, alternate form */ + addr = db_disasm(addr, TRUE); + break; + default: + break; + } + if (db_print_position() != 0) + db_end_line(1); + break; + } + } + } + db_next = addr; +} + +/* + * Print value. + */ +static char db_print_format = 'x'; + +/*ARGSUSED*/ +void +db_print_cmd(addr, have_addr, count, modif) + db_expr_t addr; + boolean_t have_addr; + db_expr_t count; + char * modif; +{ + db_expr_t value; + + if (modif[0] != '\0') + db_print_format = modif[0]; + + switch (db_print_format) { + case 'a': + db_printsym((db_addr_t)addr, DB_STGY_ANY); + break; + case 'r': + db_printf("%+11lr", (long)addr); + break; + case 'x': + db_printf("%8lx", (unsigned long)addr); + break; + case 'z': + db_printf("%8ly", (long)addr); + break; + case 'd': + db_printf("%11ld", (long)addr); + break; + case 'u': + db_printf("%11lu", (unsigned long)addr); + break; + case 'o': + db_printf("%16lo", (unsigned long)addr); + break; + case 'c': + value = addr & 0xFF; + if (value >= ' ' && value <= '~') + db_printf("%c", (int)value); + else + db_printf("\\%03o", (int)value); + break; + } + db_printf("\n"); +} + +void +db_print_loc_and_inst(loc) + db_addr_t loc; +{ + db_printsym(loc, DB_STGY_PROC); + db_printf(":\t"); + (void) db_disasm(loc, TRUE); +} + +/* + * Search for a value in memory. + * Syntax: search [/bhl] addr value [mask] [,count] + */ +void +db_search_cmd(dummy1, dummy2, dummy3, dummy4) + db_expr_t dummy1; + boolean_t dummy2; + db_expr_t dummy3; + char * dummy4; +{ + int t; + db_addr_t addr; + int size; + db_expr_t value; + db_expr_t mask; + db_expr_t count; + + t = db_read_token(); + if (t == tSLASH) { + t = db_read_token(); + if (t != tIDENT) { + bad_modifier: + db_printf("Bad modifier\n"); + db_flush_lex(); + return; + } + + if (!strcmp(db_tok_string, "b")) + size = 1; + else if (!strcmp(db_tok_string, "h")) + size = 2; + else if (!strcmp(db_tok_string, "l")) + size = 4; + else + goto bad_modifier; + } else { + db_unread_token(t); + size = 4; + } + + if (!db_expression((db_expr_t *)&addr)) { + db_printf("Address missing\n"); + db_flush_lex(); + return; + } + + if (!db_expression(&value)) { + db_printf("Value missing\n"); + db_flush_lex(); + return; + } + + if (!db_expression(&mask)) + mask = 0xffffffffUL; + + t = db_read_token(); + if (t == tCOMMA) { + if (!db_expression(&count)) { + db_printf("Count missing\n"); + db_flush_lex(); + return; + } + } else { + db_unread_token(t); + count = -1; /* effectively forever */ + } + db_skip_to_eol(); + + db_search(addr, size, value, mask, count); +} + +static void +db_search(addr, size, value, mask, count) + register + db_addr_t addr; + int size; + db_expr_t value; + db_expr_t mask; + unsigned int count; +{ + while (count-- != 0) { + db_prev = addr; + if ((db_get_value(addr, size, FALSE) & mask) == value) + break; + addr += size; + } + db_next = addr; +} diff --git a/sys/ddb/db_expr.c b/sys/ddb/db_expr.c new file mode 100644 index 0000000..424384c --- /dev/null +++ b/sys/ddb/db_expr.c @@ -0,0 +1,228 @@ +/*- + * Mach Operating System + * Copyright (c) 1991,1990 Carnegie Mellon University + * All Rights Reserved. + * + * Permission to use, copy, modify and distribute this software and its + * documentation is hereby granted, provided that both the copyright + * notice and this permission notice appear in all copies of the + * software, derivative works or modified versions, and any portions + * thereof, and that both notices appear in supporting documentation. + * + * CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS + * CONDITION. CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND FOR + * ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE. + * + * Carnegie Mellon requests users of this software to return to + * + * Software Distribution Coordinator or Software.Distribution@CS.CMU.EDU + * School of Computer Science + * Carnegie Mellon University + * Pittsburgh PA 15213-3890 + * + * any improvements or extensions that they make and grant Carnegie the + * rights to redistribute these changes. + */ +/* + * Author: David B. Golub, Carnegie Mellon University + * Date: 7/90 + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/param.h> + +#include <ddb/ddb.h> +#include <ddb/db_lex.h> +#include <ddb/db_access.h> +#include <ddb/db_command.h> + +static boolean_t db_add_expr(db_expr_t *valuep); +static boolean_t db_mult_expr(db_expr_t *valuep); +static boolean_t db_shift_expr(db_expr_t *valuep); +static boolean_t db_term(db_expr_t *valuep); +static boolean_t db_unary(db_expr_t *valuep); + +static boolean_t +db_term(db_expr_t *valuep) +{ + int t; + + t = db_read_token(); + if (t == tIDENT) { + if (!db_value_of_name(db_tok_string, valuep) && + !db_value_of_name_pcpu(db_tok_string, valuep) && + !db_value_of_name_vnet(db_tok_string, valuep)) { + db_error("Symbol not found\n"); + /*NOTREACHED*/ + } + return (TRUE); + } + if (t == tNUMBER) { + *valuep = (db_expr_t)db_tok_number; + return (TRUE); + } + if (t == tDOT) { + *valuep = (db_expr_t)db_dot; + return (TRUE); + } + if (t == tDOTDOT) { + *valuep = (db_expr_t)db_prev; + return (TRUE); + } + if (t == tPLUS) { + *valuep = (db_expr_t) db_next; + return (TRUE); + } + if (t == tDITTO) { + *valuep = (db_expr_t)db_last_addr; + return (TRUE); + } + if (t == tDOLLAR) { + if (!db_get_variable(valuep)) + return (FALSE); + return (TRUE); + } + if (t == tLPAREN) { + if (!db_expression(valuep)) { + db_error("Syntax error\n"); + /*NOTREACHED*/ + } + t = db_read_token(); + if (t != tRPAREN) { + db_error("Syntax error\n"); + /*NOTREACHED*/ + } + return (TRUE); + } + db_unread_token(t); + return (FALSE); +} + +static boolean_t +db_unary(db_expr_t *valuep) +{ + int t; + + t = db_read_token(); + if (t == tMINUS) { + if (!db_unary(valuep)) { + db_error("Syntax error\n"); + /*NOTREACHED*/ + } + *valuep = -*valuep; + return (TRUE); + } + if (t == tSTAR) { + /* indirection */ + if (!db_unary(valuep)) { + db_error("Syntax error\n"); + /*NOTREACHED*/ + } + *valuep = db_get_value((db_addr_t)*valuep, sizeof(void *), FALSE); + return (TRUE); + } + db_unread_token(t); + return (db_term(valuep)); +} + +static boolean_t +db_mult_expr(db_expr_t *valuep) +{ + db_expr_t lhs, rhs; + int t; + + if (!db_unary(&lhs)) + return (FALSE); + + t = db_read_token(); + while (t == tSTAR || t == tSLASH || t == tPCT || t == tHASH) { + if (!db_term(&rhs)) { + db_error("Syntax error\n"); + /*NOTREACHED*/ + } + if (t == tSTAR) + lhs *= rhs; + else { + if (rhs == 0) { + db_error("Divide by 0\n"); + /*NOTREACHED*/ + } + if (t == tSLASH) + lhs /= rhs; + else if (t == tPCT) + lhs %= rhs; + else + lhs = ((lhs+rhs-1)/rhs)*rhs; + } + t = db_read_token(); + } + db_unread_token(t); + *valuep = lhs; + return (TRUE); +} + +static boolean_t +db_add_expr(db_expr_t *valuep) +{ + db_expr_t lhs, rhs; + int t; + + if (!db_mult_expr(&lhs)) + return (FALSE); + + t = db_read_token(); + while (t == tPLUS || t == tMINUS) { + if (!db_mult_expr(&rhs)) { + db_error("Syntax error\n"); + /*NOTREACHED*/ + } + if (t == tPLUS) + lhs += rhs; + else + lhs -= rhs; + t = db_read_token(); + } + db_unread_token(t); + *valuep = lhs; + return (TRUE); +} + +static boolean_t +db_shift_expr(db_expr_t *valuep) +{ + db_expr_t lhs, rhs; + int t; + + if (!db_add_expr(&lhs)) + return (FALSE); + + t = db_read_token(); + while (t == tSHIFT_L || t == tSHIFT_R) { + if (!db_add_expr(&rhs)) { + db_error("Syntax error\n"); + /*NOTREACHED*/ + } + if (rhs < 0) { + db_error("Negative shift amount\n"); + /*NOTREACHED*/ + } + if (t == tSHIFT_L) + lhs <<= rhs; + else { + /* Shift right is unsigned */ + lhs = (unsigned) lhs >> rhs; + } + t = db_read_token(); + } + db_unread_token(t); + *valuep = lhs; + return (TRUE); +} + +int +db_expression(db_expr_t *valuep) +{ + return (db_shift_expr(valuep)); +} diff --git a/sys/ddb/db_input.c b/sys/ddb/db_input.c new file mode 100644 index 0000000..171b3da --- /dev/null +++ b/sys/ddb/db_input.c @@ -0,0 +1,374 @@ +/*- + * Mach Operating System + * Copyright (c) 1991,1990 Carnegie Mellon University + * All Rights Reserved. + * + * Permission to use, copy, modify and distribute this software and its + * documentation is hereby granted, provided that both the copyright + * notice and this permission notice appear in all copies of the + * software, derivative works or modified versions, and any portions + * thereof, and that both notices appear in supporting documentation. + * + * CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS + * CONDITION. CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND FOR + * ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE. + * + * Carnegie Mellon requests users of this software to return to + * + * Software Distribution Coordinator or Software.Distribution@CS.CMU.EDU + * School of Computer Science + * Carnegie Mellon University + * Pittsburgh PA 15213-3890 + * + * any improvements or extensions that they make and grant Carnegie the + * rights to redistribute these changes. + */ +/* + * Author: David B. Golub, Carnegie Mellon University + * Date: 7/90 + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/cons.h> + +#include <ddb/ddb.h> +#include <ddb/db_output.h> + +/* + * Character input and editing. + */ + +/* + * We don't track output position while editing input, + * since input always ends with a new-line. We just + * reset the line position at the end. + */ +static char * db_lbuf_start; /* start of input line buffer */ +static char * db_lbuf_end; /* end of input line buffer */ +static char * db_lc; /* current character */ +static char * db_le; /* one past last character */ + +/* + * Simple input line history support. + */ +static char db_lhistory[2048]; +static int db_lhistlsize, db_lhistidx, db_lhistcur; +static int db_lhist_nlines; + +#define CTRL(c) ((c) & 0x1f) +#define BLANK ' ' +#define BACKUP '\b' + +static int cnmaygetc(void); +static void db_delete(int n, int bwd); +static int db_inputchar(int c); +static void db_putnchars(int c, int count); +static void db_putstring(char *s, int count); + +static void +db_putstring(s, count) + char *s; + int count; +{ + while (--count >= 0) + cnputc(*s++); +} + +static void +db_putnchars(c, count) + int c; + int count; +{ + while (--count >= 0) + cnputc(c); +} + +/* + * Delete N characters, forward or backward + */ +#define DEL_FWD 0 +#define DEL_BWD 1 +static void +db_delete(n, bwd) + int n; + int bwd; +{ + register char *p; + + if (bwd) { + db_lc -= n; + db_putnchars(BACKUP, n); + } + for (p = db_lc; p < db_le-n; p++) { + *p = *(p+n); + cnputc(*p); + } + db_putnchars(BLANK, n); + db_putnchars(BACKUP, db_le - db_lc); + db_le -= n; +} + +/* returns TRUE at end-of-line */ +static int +db_inputchar(c) + int c; +{ + static int escstate; + + if (escstate == 1) { + /* ESC seen, look for [ or O */ + if (c == '[' || c == 'O') + escstate++; + else + escstate = 0; /* re-init state machine */ + return (0); + } else if (escstate == 2) { + escstate = 0; + /* + * If a valid cursor key has been found, translate + * into an emacs-style control key, and fall through. + * Otherwise, drop off. + */ + switch (c) { + case 'A': /* up */ + c = CTRL('p'); + break; + case 'B': /* down */ + c = CTRL('n'); + break; + case 'C': /* right */ + c = CTRL('f'); + break; + case 'D': /* left */ + c = CTRL('b'); + break; + default: + return (0); + } + } + + switch (c) { + case CTRL('['): + escstate = 1; + break; + case CTRL('b'): + /* back up one character */ + if (db_lc > db_lbuf_start) { + cnputc(BACKUP); + db_lc--; + } + break; + case CTRL('f'): + /* forward one character */ + if (db_lc < db_le) { + cnputc(*db_lc); + db_lc++; + } + break; + case CTRL('a'): + /* beginning of line */ + while (db_lc > db_lbuf_start) { + cnputc(BACKUP); + db_lc--; + } + break; + case CTRL('e'): + /* end of line */ + while (db_lc < db_le) { + cnputc(*db_lc); + db_lc++; + } + break; + case CTRL('h'): + case 0177: + /* erase previous character */ + if (db_lc > db_lbuf_start) + db_delete(1, DEL_BWD); + break; + case CTRL('d'): + /* erase next character */ + if (db_lc < db_le) + db_delete(1, DEL_FWD); + break; + case CTRL('u'): + /* kill entire line: */ + /* at first, delete to beginning of line */ + if (db_lc > db_lbuf_start) + db_delete(db_lc - db_lbuf_start, DEL_BWD); + /* FALLTHROUGH */ + case CTRL('k'): + /* delete to end of line */ + if (db_lc < db_le) + db_delete(db_le - db_lc, DEL_FWD); + break; + case CTRL('t'): + /* twiddle last 2 characters */ + if (db_lc >= db_lbuf_start + 2) { + c = db_lc[-2]; + db_lc[-2] = db_lc[-1]; + db_lc[-1] = c; + cnputc(BACKUP); + cnputc(BACKUP); + cnputc(db_lc[-2]); + cnputc(db_lc[-1]); + } + break; + case CTRL('r'): + db_putstring("^R\n", 3); + redraw: + if (db_le > db_lbuf_start) { + db_putstring(db_lbuf_start, db_le - db_lbuf_start); + db_putnchars(BACKUP, db_le - db_lc); + } + break; + case CTRL('p'): + /* Make previous history line the active one. */ + if (db_lhistcur >= 0) { + bcopy(db_lhistory + db_lhistcur * db_lhistlsize, + db_lbuf_start, db_lhistlsize); + db_lhistcur--; + goto hist_redraw; + } + break; + case CTRL('n'): + /* Make next history line the active one. */ + if (db_lhistcur < db_lhistidx - 1) { + db_lhistcur += 2; + bcopy(db_lhistory + db_lhistcur * db_lhistlsize, + db_lbuf_start, db_lhistlsize); + } else { + /* + * ^N through tail of history, reset the + * buffer to zero length. + */ + *db_lbuf_start = '\0'; + db_lhistcur = db_lhistidx; + } + + hist_redraw: + db_putnchars(BACKUP, db_lc - db_lbuf_start); + db_putnchars(BLANK, db_le - db_lbuf_start); + db_putnchars(BACKUP, db_le - db_lbuf_start); + db_le = strchr(db_lbuf_start, '\0'); + if (db_le[-1] == '\r' || db_le[-1] == '\n') + *--db_le = '\0'; + db_lc = db_le; + goto redraw; + + case -1: + /* + * eek! the console returned eof. + * probably that means we HAVE no console.. we should try bail + * XXX + */ + c = '\r'; + case '\n': + /* FALLTHROUGH */ + case '\r': + *db_le++ = c; + return (1); + default: + if (db_le == db_lbuf_end) { + cnputc('\007'); + } + else if (c >= ' ' && c <= '~') { + register char *p; + + for (p = db_le; p > db_lc; p--) + *p = *(p-1); + *db_lc++ = c; + db_le++; + cnputc(c); + db_putstring(db_lc, db_le - db_lc); + db_putnchars(BACKUP, db_le - db_lc); + } + break; + } + return (0); +} + +static int +cnmaygetc() +{ + return (-1); +} + +int +db_readline(lstart, lsize) + char * lstart; + int lsize; +{ + + if (lsize < 2) + return (0); + if (lsize != db_lhistlsize) { + /* + * (Re)initialize input line history. Throw away any + * existing history. + */ + db_lhist_nlines = sizeof(db_lhistory) / lsize; + db_lhistlsize = lsize; + db_lhistidx = -1; + } + db_lhistcur = db_lhistidx; + + db_force_whitespace(); /* synch output position */ + + db_lbuf_start = lstart; + db_lbuf_end = lstart + lsize - 2; /* Will append NL and NUL. */ + db_lc = lstart; + db_le = lstart; + + while (!db_inputchar(cngetc())) + continue; + + db_capture_write(lstart, db_le - db_lbuf_start); + db_printf("\n"); /* synch output position */ + *db_le = 0; + + if (db_le - db_lbuf_start > 1) { + /* Maintain input line history for non-empty lines. */ + if (++db_lhistidx == db_lhist_nlines) { + /* Rotate history. */ + bcopy(db_lhistory + db_lhistlsize, db_lhistory, + db_lhistlsize * (db_lhist_nlines - 1)); + db_lhistidx--; + } + bcopy(lstart, db_lhistory + db_lhistidx * db_lhistlsize, + db_lhistlsize); + } + + return (db_le - db_lbuf_start); +} + +void +db_check_interrupt() +{ + register int c; + + c = cnmaygetc(); + switch (c) { + case -1: /* no character */ + return; + + case CTRL('c'): + db_error((char *)0); + /*NOTREACHED*/ + + case CTRL('s'): + do { + c = cnmaygetc(); + if (c == CTRL('c')) + db_error((char *)0); + } while (c != CTRL('q')); + break; + + default: + /* drop on floor */ + break; + } +} diff --git a/sys/ddb/db_lex.c b/sys/ddb/db_lex.c new file mode 100644 index 0000000..1c8151e --- /dev/null +++ b/sys/ddb/db_lex.c @@ -0,0 +1,314 @@ +/*- + * Mach Operating System + * Copyright (c) 1991,1990 Carnegie Mellon University + * All Rights Reserved. + * + * Permission to use, copy, modify and distribute this software and its + * documentation is hereby granted, provided that both the copyright + * notice and this permission notice appear in all copies of the + * software, derivative works or modified versions, and any portions + * thereof, and that both notices appear in supporting documentation. + * + * CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS + * CONDITION. CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND FOR + * ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE. + * + * Carnegie Mellon requests users of this software to return to + * + * Software Distribution Coordinator or Software.Distribution@CS.CMU.EDU + * School of Computer Science + * Carnegie Mellon University + * Pittsburgh PA 15213-3890 + * + * any improvements or extensions that they make and grant Carnegie the + * rights to redistribute these changes. + */ +/* + * Author: David B. Golub, Carnegie Mellon University + * Date: 7/90 + */ +/* + * Lexical analyzer. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/param.h> +#include <sys/libkern.h> + +#include <ddb/ddb.h> +#include <ddb/db_lex.h> + +static char db_line[DB_MAXLINE]; +static char * db_lp, *db_endlp; + +static int db_lex(void); +static void db_flush_line(void); +static int db_read_char(void); +static void db_unread_char(int); + +int +db_read_line() +{ + int i; + + i = db_readline(db_line, sizeof(db_line)); + if (i == 0) + return (0); /* EOI */ + db_lp = db_line; + db_endlp = db_lp + i; + return (i); +} + +/* + * Simulate a line of input into DDB. + */ +void +db_inject_line(const char *command) +{ + + strlcpy(db_line, command, sizeof(db_line)); + db_lp = db_line; + db_endlp = db_lp + strlen(command); +} + +/* + * In rare cases, we may want to pull the remainder of the line input + * verbatim, rather than lexing it. For example, when assigning literal + * values associated with scripts. In that case, return a static pointer to + * the current location in the input buffer. The caller must be aware that + * the contents are not stable if other lex/input calls are made. + */ +char * +db_get_line(void) +{ + + return (db_lp); +} + +static void +db_flush_line() +{ + db_lp = db_line; + db_endlp = db_line; +} + +static int db_look_char = 0; + +static int +db_read_char() +{ + int c; + + if (db_look_char != 0) { + c = db_look_char; + db_look_char = 0; + } + else if (db_lp >= db_endlp) + c = -1; + else + c = *db_lp++; + return (c); +} + +static void +db_unread_char(c) + int c; +{ + db_look_char = c; +} + +static int db_look_token = 0; + +void +db_unread_token(t) + int t; +{ + db_look_token = t; +} + +int +db_read_token() +{ + int t; + + if (db_look_token) { + t = db_look_token; + db_look_token = 0; + } + else + t = db_lex(); + return (t); +} + +db_expr_t db_tok_number; +char db_tok_string[TOK_STRING_SIZE]; + +db_expr_t db_radix = 16; + +void +db_flush_lex() +{ + db_flush_line(); + db_look_char = 0; + db_look_token = 0; +} + +static int +db_lex() +{ + int c; + + c = db_read_char(); + while (c <= ' ' || c > '~') { + if (c == '\n' || c == -1) + return (tEOL); + c = db_read_char(); + } + + if (c >= '0' && c <= '9') { + /* number */ + int r, digit = 0; + + if (c > '0') + r = db_radix; + else { + c = db_read_char(); + if (c == 'O' || c == 'o') + r = 8; + else if (c == 'T' || c == 't') + r = 10; + else if (c == 'X' || c == 'x') + r = 16; + else { + r = db_radix; + db_unread_char(c); + } + c = db_read_char(); + } + db_tok_number = 0; + for (;;) { + if (c >= '0' && c <= ((r == 8) ? '7' : '9')) + digit = c - '0'; + else if (r == 16 && ((c >= 'A' && c <= 'F') || + (c >= 'a' && c <= 'f'))) { + if (c >= 'a') + digit = c - 'a' + 10; + else if (c >= 'A') + digit = c - 'A' + 10; + } + else + break; + db_tok_number = db_tok_number * r + digit; + c = db_read_char(); + } + if ((c >= '0' && c <= '9') || + (c >= 'A' && c <= 'Z') || + (c >= 'a' && c <= 'z') || + (c == '_')) + { + db_error("Bad character in number\n"); + db_flush_lex(); + return (tEOF); + } + db_unread_char(c); + return (tNUMBER); + } + if ((c >= 'A' && c <= 'Z') || + (c >= 'a' && c <= 'z') || + c == '_' || c == '\\') + { + /* string */ + char *cp; + + cp = db_tok_string; + if (c == '\\') { + c = db_read_char(); + if (c == '\n' || c == -1) + db_error("Bad escape\n"); + } + *cp++ = c; + while (1) { + c = db_read_char(); + if ((c >= 'A' && c <= 'Z') || + (c >= 'a' && c <= 'z') || + (c >= '0' && c <= '9') || + c == '_' || c == '\\' || c == ':' || c == '.') + { + if (c == '\\') { + c = db_read_char(); + if (c == '\n' || c == -1) + db_error("Bad escape\n"); + } + *cp++ = c; + if (cp == db_tok_string+sizeof(db_tok_string)) { + db_error("String too long\n"); + db_flush_lex(); + return (tEOF); + } + continue; + } + else { + *cp = '\0'; + break; + } + } + db_unread_char(c); + return (tIDENT); + } + + switch (c) { + case '+': + return (tPLUS); + case '-': + return (tMINUS); + case '.': + c = db_read_char(); + if (c == '.') + return (tDOTDOT); + db_unread_char(c); + return (tDOT); + case '*': + return (tSTAR); + case '/': + return (tSLASH); + case '=': + return (tEQ); + case '%': + return (tPCT); + case '#': + return (tHASH); + case '(': + return (tLPAREN); + case ')': + return (tRPAREN); + case ',': + return (tCOMMA); + case '"': + return (tDITTO); + case '$': + return (tDOLLAR); + case '!': + return (tEXCL); + case ';': + return (tSEMI); + case '<': + c = db_read_char(); + if (c == '<') + return (tSHIFT_L); + db_unread_char(c); + break; + case '>': + c = db_read_char(); + if (c == '>') + return (tSHIFT_R); + db_unread_char(c); + break; + case -1: + return (tEOF); + } + db_printf("Bad character\n"); + db_flush_lex(); + return (tEOF); +} diff --git a/sys/ddb/db_lex.h b/sys/ddb/db_lex.h new file mode 100644 index 0000000..8713a27 --- /dev/null +++ b/sys/ddb/db_lex.h @@ -0,0 +1,73 @@ +/*- + * Mach Operating System + * Copyright (c) 1991,1990 Carnegie Mellon University + * All Rights Reserved. + * + * Permission to use, copy, modify and distribute this software and its + * documentation is hereby granted, provided that both the copyright + * notice and this permission notice appear in all copies of the + * software, derivative works or modified versions, and any portions + * thereof, and that both notices appear in supporting documentation. + * + * CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS + * CONDITION. CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND FOR + * ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE. + * + * Carnegie Mellon requests users of this software to return to + * + * Software Distribution Coordinator or Software.Distribution@CS.CMU.EDU + * School of Computer Science + * Carnegie Mellon University + * Pittsburgh PA 15213-3890 + * + * any improvements or extensions that they make and grant Carnegie the + * rights to redistribute these changes. + * + * $FreeBSD$ + */ + +#ifndef _DDB_DB_LEX_H_ +#define _DDB_DB_LEX_H_ + +/* + * Author: David B. Golub, Carnegie Mellon University + * Date: 7/90 + */ +/* + * Lexical analyzer. + */ +void db_flush_lex(void); +char *db_get_line(void); +void db_inject_line(const char *command); +int db_read_line(void); +int db_read_token(void); +void db_unread_token(int t); + +extern db_expr_t db_tok_number; +#define TOK_STRING_SIZE 120 +extern char db_tok_string[TOK_STRING_SIZE]; + +#define tEOF (-1) +#define tEOL 1 +#define tNUMBER 2 +#define tIDENT 3 +#define tPLUS 4 +#define tMINUS 5 +#define tDOT 6 +#define tSTAR 7 +#define tSLASH 8 +#define tEQ 9 +#define tLPAREN 10 +#define tRPAREN 11 +#define tPCT 12 +#define tHASH 13 +#define tCOMMA 14 +#define tDITTO 15 +#define tDOLLAR 16 +#define tEXCL 17 +#define tSHIFT_L 18 +#define tSHIFT_R 19 +#define tDOTDOT 20 +#define tSEMI 21 + +#endif /* !_DDB_DB_LEX_H_ */ diff --git a/sys/ddb/db_main.c b/sys/ddb/db_main.c new file mode 100644 index 0000000..6e9286c --- /dev/null +++ b/sys/ddb/db_main.c @@ -0,0 +1,262 @@ +/*- + * Mach Operating System + * Copyright (c) 1991,1990 Carnegie Mellon University + * All Rights Reserved. + * + * Permission to use, copy, modify and distribute this software and its + * documentation is hereby granted, provided that both the copyright + * notice and this permission notice appear in all copies of the + * software, derivative works or modified versions, and any portions + * thereof, and that both notices appear in supporting documentation. + * + * CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS + * CONDITION. CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND FOR + * ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE. + * + * Carnegie Mellon requests users of this software to return to + * + * Software Distribution Coordinator or Software.Distribution@CS.CMU.EDU + * School of Computer Science + * Carnegie Mellon University + * Pittsburgh PA 15213-3890 + * + * any improvements or extensions that they make and grant Carnegie the + * rights to redistribute these changes. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/cons.h> +#include <sys/linker.h> +#include <sys/kdb.h> +#include <sys/kernel.h> +#include <sys/pcpu.h> +#include <sys/proc.h> +#include <sys/reboot.h> +#include <sys/sysctl.h> + +#include <machine/kdb.h> +#include <machine/pcb.h> +#include <machine/setjmp.h> + +#include <ddb/ddb.h> +#include <ddb/db_command.h> +#include <ddb/db_sym.h> + +SYSCTL_NODE(_debug, OID_AUTO, ddb, CTLFLAG_RW, 0, "DDB settings"); + +static dbbe_init_f db_init; +static dbbe_trap_f db_trap; +static dbbe_trace_f db_trace_self_wrapper; +static dbbe_trace_thread_f db_trace_thread_wrapper; + +KDB_BACKEND(ddb, db_init, db_trace_self_wrapper, db_trace_thread_wrapper, + db_trap); + +vm_offset_t ksym_start, ksym_end; + +boolean_t +X_db_line_at_pc(db_symtab_t *symtab, c_db_sym_t sym, char **file, int *line, + db_expr_t off) +{ + return (FALSE); +} + +c_db_sym_t +X_db_lookup(db_symtab_t *symtab, const char *symbol) +{ + c_linker_sym_t lsym; + Elf_Sym *sym; + + if (symtab->private == NULL) { + return ((c_db_sym_t)((!linker_ddb_lookup(symbol, &lsym)) + ? lsym : NULL)); + } else { + sym = (Elf_Sym *)symtab->start; + while ((char *)sym < symtab->end) { + if (sym->st_name != 0 && + !strcmp(symtab->private + sym->st_name, symbol)) + return ((c_db_sym_t)sym); + sym++; + } + } + return (NULL); +} + +c_db_sym_t +X_db_search_symbol(db_symtab_t *symtab, db_addr_t off, db_strategy_t strat, + db_expr_t *diffp) +{ + c_linker_sym_t lsym; + Elf_Sym *sym, *match; + unsigned long diff; + + if (symtab->private == NULL) { + if (!linker_ddb_search_symbol((caddr_t)off, &lsym, &diff)) { + *diffp = (db_expr_t)diff; + return ((c_db_sym_t)lsym); + } + return (NULL); + } + + diff = ~0UL; + match = NULL; + for (sym = (Elf_Sym*)symtab->start; (char*)sym < symtab->end; sym++) { + if (sym->st_name == 0) + continue; + if (off < sym->st_value) + continue; + if (ELF_ST_TYPE(sym->st_info) != STT_OBJECT && + ELF_ST_TYPE(sym->st_info) != STT_FUNC && + ELF_ST_TYPE(sym->st_info) != STT_NOTYPE) + continue; + if ((off - sym->st_value) > diff) + continue; + if ((off - sym->st_value) < diff) { + diff = off - sym->st_value; + match = sym; + } else { + if (match == NULL) + match = sym; + else if (ELF_ST_BIND(match->st_info) == STB_LOCAL && + ELF_ST_BIND(sym->st_info) != STB_LOCAL) + match = sym; + } + if (diff == 0) { + if (strat == DB_STGY_PROC && + ELF_ST_TYPE(sym->st_info) == STT_FUNC && + ELF_ST_BIND(sym->st_info) != STB_LOCAL) + break; + if (strat == DB_STGY_ANY && + ELF_ST_BIND(sym->st_info) != STB_LOCAL) + break; + } + } + + *diffp = (match == NULL) ? off : diff; + return ((c_db_sym_t)match); +} + +boolean_t +X_db_sym_numargs(db_symtab_t *symtab, c_db_sym_t sym, int *nargp, + char **argp) +{ + return (FALSE); +} + +void +X_db_symbol_values(db_symtab_t *symtab, c_db_sym_t sym, const char **namep, + db_expr_t *valp) +{ + linker_symval_t lval; + + if (symtab->private == NULL) { + linker_ddb_symbol_values((c_linker_sym_t)sym, &lval); + if (namep != NULL) + *namep = (const char*)lval.name; + if (valp != NULL) + *valp = (db_expr_t)lval.value; + } else { + if (namep != NULL) + *namep = (const char *)symtab->private + + ((const Elf_Sym *)sym)->st_name; + if (valp != NULL) + *valp = (db_expr_t)((const Elf_Sym *)sym)->st_value; + } +} + +static int +db_init(void) +{ + uintptr_t symtab, strtab; + Elf_Size tabsz, strsz; + + db_command_init(); + if (ksym_end > ksym_start && ksym_start != 0) { + symtab = ksym_start; + tabsz = *((Elf_Size*)symtab); + symtab += sizeof(Elf_Size); + strtab = symtab + tabsz; + strsz = *((Elf_Size*)strtab); + strtab += sizeof(Elf_Size); + if (strtab + strsz <= ksym_end) { + db_add_symbol_table((char *)symtab, + (char *)(symtab + tabsz), "elf", (char *)strtab); + } + } + db_add_symbol_table(NULL, NULL, "kld", NULL); + return (1); /* We're the default debugger. */ +} + +static int +db_trap(int type, int code) +{ + jmp_buf jb; + void *prev_jb; + boolean_t bkpt, watchpt; + const char *why; + + /* + * Don't handle the trap if the console is unavailable (i.e. it + * is in graphics mode). + */ + if (cnunavailable()) + return (0); + + bkpt = IS_BREAKPOINT_TRAP(type, code); + watchpt = IS_WATCHPOINT_TRAP(type, code); + + if (db_stop_at_pc(&bkpt)) { + if (db_inst_count) { + db_printf("After %d instructions (%d loads, %d stores),\n", + db_inst_count, db_load_count, db_store_count); + } + prev_jb = kdb_jmpbuf(jb); + if (setjmp(jb) == 0) { + db_dot = PC_REGS(); + db_print_thread(); + if (bkpt) + db_printf("Breakpoint at\t"); + else if (watchpt) + db_printf("Watchpoint at\t"); + else + db_printf("Stopped at\t"); + db_print_loc_and_inst(db_dot); + } + why = kdb_why; + db_script_kdbenter(why != KDB_WHY_UNSET ? why : "unknown"); + db_command_loop(); + (void)kdb_jmpbuf(prev_jb); + } + + db_restart_at_pc(watchpt); + + return (1); +} + +static void +db_trace_self_wrapper(void) +{ + jmp_buf jb; + void *prev_jb; + + prev_jb = kdb_jmpbuf(jb); + if (setjmp(jb) == 0) + db_trace_self(); + (void)kdb_jmpbuf(prev_jb); +} + +static void +db_trace_thread_wrapper(struct thread *td) +{ + jmp_buf jb; + void *prev_jb; + + prev_jb = kdb_jmpbuf(jb); + if (setjmp(jb) == 0) + db_trace_thread(td, -1); + (void)kdb_jmpbuf(prev_jb); +} diff --git a/sys/ddb/db_output.c b/sys/ddb/db_output.c new file mode 100644 index 0000000..8390a86 --- /dev/null +++ b/sys/ddb/db_output.c @@ -0,0 +1,395 @@ +/*- + * Mach Operating System + * Copyright (c) 1991,1990 Carnegie Mellon University + * All Rights Reserved. + * + * Permission to use, copy, modify and distribute this software and its + * documentation is hereby granted, provided that both the copyright + * notice and this permission notice appear in all copies of the + * software, derivative works or modified versions, and any portions + * thereof, and that both notices appear in supporting documentation. + * + * CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS + * CONDITION. CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND FOR + * ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE. + * + * Carnegie Mellon requests users of this software to return to + * + * Software Distribution Coordinator or Software.Distribution@CS.CMU.EDU + * School of Computer Science + * Carnegie Mellon University + * Pittsburgh PA 15213-3890 + * + * any improvements or extensions that they make and grant Carnegie the + * rights to redistribute these changes. + */ +/* + * Author: David B. Golub, Carnegie Mellon University + * Date: 7/90 + */ + +/* + * Printf and character output for debugger. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include "opt_ddb.h" + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/cons.h> +#include <sys/kdb.h> +#include <sys/kernel.h> +#include <sys/sysctl.h> + +#include <machine/stdarg.h> + +#include <ddb/ddb.h> +#include <ddb/db_output.h> + +struct dbputchar_arg { + size_t da_nbufr; + size_t da_remain; + char *da_pbufr; + char *da_pnext; +}; + +/* + * Character output - tracks position in line. + * To do this correctly, we should know how wide + * the output device is - then we could zero + * the line position when the output device wraps + * around to the start of the next line. + * + * Instead, we count the number of spaces printed + * since the last printing character so that we + * don't print trailing spaces. This avoids most + * of the wraparounds. + */ +static int db_output_position = 0; /* output column */ +static int db_last_non_space = 0; /* last non-space character */ +db_expr_t db_tab_stop_width = 8; /* how wide are tab stops? */ +#define NEXT_TAB(i) \ + ((((i) + db_tab_stop_width) / db_tab_stop_width) * db_tab_stop_width) +db_expr_t db_max_width = 79; /* output line width */ +db_expr_t db_lines_per_page = 20; /* lines per page */ +volatile int db_pager_quit; /* user requested quit */ +static int db_newlines; /* # lines this page */ +static int db_maxlines; /* max lines/page when paging */ +static int ddb_use_printf = 0; +SYSCTL_INT(_debug, OID_AUTO, ddb_use_printf, CTLFLAG_RW, &ddb_use_printf, 0, + "use printf for all ddb output"); + +static void db_putc(int c); +static void db_puts(const char *str); +static void db_putchar(int c, void *arg); +static void db_pager(void); + +/* + * Force pending whitespace. + */ +void +db_force_whitespace() +{ + register int last_print, next_tab; + + last_print = db_last_non_space; + while (last_print < db_output_position) { + next_tab = NEXT_TAB(last_print); + if (next_tab <= db_output_position) { + while (last_print < next_tab) { /* DON'T send a tab!!! */ + cnputc(' '); + db_capture_writech(' '); + last_print++; + } + } + else { + cnputc(' '); + db_capture_writech(' '); + last_print++; + } + } + db_last_non_space = db_output_position; +} + +/* + * Output character. Buffer whitespace. + */ +static void +db_putchar(int c, void *arg) +{ + struct dbputchar_arg *dap = arg; + + if (dap->da_pbufr == NULL) { + + /* No bufferized output is provided. */ + db_putc(c); + } else { + + *dap->da_pnext++ = c; + dap->da_remain--; + + /* Leave always the buffer 0 terminated. */ + *dap->da_pnext = '\0'; + + /* Check if the buffer needs to be flushed. */ + if (dap->da_remain < 2 || c == '\n') { + db_puts(dap->da_pbufr); + dap->da_pnext = dap->da_pbufr; + dap->da_remain = dap->da_nbufr; + *dap->da_pnext = '\0'; + } + } +} + +static void +db_putc(int c) +{ + + /* + * If not in the debugger or the user requests it, output data to + * both the console and the message buffer. + */ + if (!kdb_active || ddb_use_printf) { + printf("%c", c); + if (!kdb_active) + return; + if (c == '\r' || c == '\n') + db_check_interrupt(); + if (c == '\n' && db_maxlines > 0) { + db_newlines++; + if (db_newlines >= db_maxlines) + db_pager(); + } + return; + } + + /* Otherwise, output data directly to the console. */ + if (c > ' ' && c <= '~') { + /* + * Printing character. + * If we have spaces to print, print them first. + * Use tabs if possible. + */ + db_force_whitespace(); + cnputc(c); + db_capture_writech(c); + db_output_position++; + db_last_non_space = db_output_position; + } + else if (c == '\n') { + /* Newline */ + cnputc(c); + db_capture_writech(c); + db_output_position = 0; + db_last_non_space = 0; + db_check_interrupt(); + if (db_maxlines > 0) { + db_newlines++; + if (db_newlines >= db_maxlines) + db_pager(); + } + } + else if (c == '\r') { + /* Return */ + cnputc(c); + db_capture_writech(c); + db_output_position = 0; + db_last_non_space = 0; + db_check_interrupt(); + } + else if (c == '\t') { + /* assume tabs every 8 positions */ + db_output_position = NEXT_TAB(db_output_position); + } + else if (c == ' ') { + /* space */ + db_output_position++; + } + else if (c == '\007') { + /* bell */ + cnputc(c); + /* No need to beep in a log: db_capture_writech(c); */ + } + /* other characters are assumed non-printing */ +} + +static void +db_puts(const char *str) +{ + int i; + + for (i = 0; str[i] != '\0'; i++) + db_putc(str[i]); +} + +/* + * Turn on the pager. + */ +void +db_enable_pager(void) +{ + if (db_maxlines == 0) { + db_maxlines = db_lines_per_page; + db_newlines = 0; + db_pager_quit = 0; + } +} + +/* + * Turn off the pager. + */ +void +db_disable_pager(void) +{ + db_maxlines = 0; +} + +/* + * A simple paging callout function. It supports several simple more(1)-like + * commands as well as a quit command that sets db_pager_quit which db + * commands can poll to see if they should terminate early. + */ +void +db_pager(void) +{ + int c, done; + + db_capture_enterpager(); + db_printf("--More--\r"); + done = 0; + while (!done) { + c = cngetc(); + switch (c) { + case 'e': + case 'j': + case '\n': + /* Just one more line. */ + db_maxlines = 1; + done++; + break; + case 'd': + /* Half a page. */ + db_maxlines = db_lines_per_page / 2; + done++; + break; + case 'f': + case ' ': + /* Another page. */ + db_maxlines = db_lines_per_page; + done++; + break; + case 'q': + case 'Q': + case 'x': + case 'X': + /* Quit */ + db_maxlines = 0; + db_pager_quit = 1; + done++; + break; +#if 0 + /* FALLTHROUGH */ + default: + cnputc('\007'); +#endif + } + } + db_printf(" "); + db_force_whitespace(); + db_printf("\r"); + db_newlines = 0; + db_capture_exitpager(); +} + +/* + * Return output position + */ +int +db_print_position() +{ + return (db_output_position); +} + +/* + * Printing + */ +int +db_printf(const char *fmt, ...) +{ +#ifdef DDB_BUFR_SIZE + char bufr[DDB_BUFR_SIZE]; +#endif + struct dbputchar_arg dca; + va_list listp; + int retval; + +#ifdef DDB_BUFR_SIZE + dca.da_pbufr = bufr; + dca.da_pnext = dca.da_pbufr; + dca.da_nbufr = sizeof(bufr); + dca.da_remain = sizeof(bufr); + *dca.da_pnext = '\0'; +#else + dca.da_pbufr = NULL; +#endif + + va_start(listp, fmt); + retval = kvprintf (fmt, db_putchar, &dca, db_radix, listp); + va_end(listp); + +#ifdef DDB_BUFR_SIZE + if (*dca.da_pbufr != '\0') + db_puts(dca.da_pbufr); +#endif + return (retval); +} + +int db_indent; + +void +db_iprintf(const char *fmt,...) +{ +#ifdef DDB_BUFR_SIZE + char bufr[DDB_BUFR_SIZE]; +#endif + struct dbputchar_arg dca; + register int i; + va_list listp; + + for (i = db_indent; i >= 8; i -= 8) + db_printf("\t"); + while (--i >= 0) + db_printf(" "); + +#ifdef DDB_BUFR_SIZE + dca.da_pbufr = bufr; + dca.da_pnext = dca.da_pbufr; + dca.da_nbufr = sizeof(bufr); + dca.da_remain = sizeof(bufr); + *dca.da_pnext = '\0'; +#else + dca.da_pbufr = NULL; +#endif + + va_start(listp, fmt); + kvprintf (fmt, db_putchar, &dca, db_radix, listp); + va_end(listp); + +#ifdef DDB_BUFR_SIZE + if (*dca.da_pbufr != '\0') + db_puts(dca.da_pbufr); +#endif +} + +/* + * End line if too long. + */ +void +db_end_line(int field_width) +{ + if (db_output_position + field_width > db_max_width) + db_printf("\n"); +} diff --git a/sys/ddb/db_output.h b/sys/ddb/db_output.h new file mode 100644 index 0000000..cbf9f0e --- /dev/null +++ b/sys/ddb/db_output.h @@ -0,0 +1,47 @@ +/*- + * Mach Operating System + * Copyright (c) 1991,1990 Carnegie Mellon University + * All Rights Reserved. + * + * Permission to use, copy, modify and distribute this software and its + * documentation is hereby granted, provided that both the copyright + * notice and this permission notice appear in all copies of the + * software, derivative works or modified versions, and any portions + * thereof, and that both notices appear in supporting documentation. + * + * CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS + * CONDITION. CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND FOR + * ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE. + * + * Carnegie Mellon requests users of this software to return to + * + * Software Distribution Coordinator or Software.Distribution@CS.CMU.EDU + * School of Computer Science + * Carnegie Mellon University + * Pittsburgh PA 15213-3890 + * + * any improvements or extensions that they make and grant Carnegie the + * rights to redistribute these changes. + * + * $FreeBSD$ + */ + +#ifndef _DDB_DB_OUTPUT_H_ +#define _DDB_DB_OUTPUT_H_ + +/* + * Author: David B. Golub, Carnegie Mellon University + * Date: 8/90 + */ + +/* + * Printing routines for kernel debugger. + */ + +void db_disable_pager(void); +void db_enable_pager(void); +void db_end_line(int); +void db_force_whitespace(void); +int db_print_position(void); + +#endif /* !_DDB_DB_OUTPUT_H_ */ diff --git a/sys/ddb/db_print.c b/sys/ddb/db_print.c new file mode 100644 index 0000000..6cffd6d --- /dev/null +++ b/sys/ddb/db_print.c @@ -0,0 +1,70 @@ +/*- + * Mach Operating System + * Copyright (c) 1991,1990 Carnegie Mellon University + * All Rights Reserved. + * + * Permission to use, copy, modify and distribute this software and its + * documentation is hereby granted, provided that both the copyright + * notice and this permission notice appear in all copies of the + * software, derivative works or modified versions, and any portions + * thereof, and that both notices appear in supporting documentation. + * + * CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS + * CONDITION. CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND FOR + * ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE. + * + * Carnegie Mellon requests users of this software to return to + * + * Software Distribution Coordinator or Software.Distribution@CS.CMU.EDU + * School of Computer Science + * Carnegie Mellon University + * Pittsburgh PA 15213-3890 + * + * any improvements or extensions that they make and grant Carnegie the + * rights to redistribute these changes. + * + */ +/* + * Author: David B. Golub, Carnegie Mellon University + * Date: 7/90 + */ + +/* + * Miscellaneous printing. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/param.h> +#include <sys/kdb.h> +#include <sys/proc.h> + +#include <machine/pcb.h> + +#include <ddb/ddb.h> +#include <ddb/db_variables.h> +#include <ddb/db_sym.h> + +void +db_show_regs(db_expr_t _1, boolean_t _2, db_expr_t _3, char *_4) +{ + struct db_variable *regp; + db_expr_t value, offset; + const char *name; + + for (regp = db_regs; regp < db_eregs; regp++) { + if (!db_read_variable(regp, &value)) + continue; + db_printf("%-12s%#10lr", regp->name, (unsigned long)value); + db_find_xtrn_sym_and_offset((db_addr_t)value, &name, &offset); + if (name != NULL && offset <= (unsigned long)db_maxoff && + offset != value) { + db_printf("\t%s", name); + if (offset != 0) + db_printf("+%+#lr", (long)offset); + } + db_printf("\n"); + } + db_print_loc_and_inst(PC_REGS()); +} diff --git a/sys/ddb/db_ps.c b/sys/ddb/db_ps.c new file mode 100644 index 0000000..81e141e --- /dev/null +++ b/sys/ddb/db_ps.c @@ -0,0 +1,468 @@ +/*- + * Copyright (c) 1993 The Regents of the University of California. + * 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. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/param.h> +#include <sys/cons.h> +#include <sys/jail.h> +#include <sys/kdb.h> +#include <sys/proc.h> +#include <sys/sysent.h> +#include <sys/systm.h> +#include <sys/_kstack_cache.h> +#include <vm/vm.h> +#include <vm/vm_param.h> +#include <vm/pmap.h> + +#include <ddb/ddb.h> + +static void dumpthread(volatile struct proc *p, volatile struct thread *td, + int all); +/* + * At least one non-optional show-command must be implemented using + * DB_SHOW_ALL_COMMAND() so that db_show_all_cmd_set gets created. + * Here is one. + */ +DB_SHOW_ALL_COMMAND(procs, db_procs_cmd) +{ + db_ps(addr, have_addr, count, modif); +} + +/* + * Layout: + * - column counts + * - header + * - single-threaded process + * - multi-threaded process + * - thread in a MT process + * + * 1 2 3 4 5 6 7 + * 1234567890123456789012345678901234567890123456789012345678901234567890 + * pid ppid pgrp uid state wmesg wchan cmd + * <pid> <ppi> <pgi> <uid> <stat> < wmesg > < wchan > <name> + * <pid> <ppi> <pgi> <uid> <stat> (threaded) <command> + * <tid > <stat> < wmesg > < wchan > <name> + * + * For machines with 64-bit pointers, we expand the wchan field 8 more + * characters. + */ +void +db_ps(db_expr_t addr, boolean_t hasaddr, db_expr_t count, char *modif) +{ + volatile struct proc *p, *pp; + volatile struct thread *td; + struct ucred *cred; + struct pgrp *pgrp; + char state[9]; + int np, rflag, sflag, dflag, lflag, wflag; + + np = nprocs; + + if (!LIST_EMPTY(&allproc)) + p = LIST_FIRST(&allproc); + else + p = &proc0; + +#ifdef __LP64__ + db_printf(" pid ppid pgrp uid state wmesg wchan cmd\n"); +#else + db_printf(" pid ppid pgrp uid state wmesg wchan cmd\n"); +#endif + while (--np >= 0 && !db_pager_quit) { + if (p == NULL) { + db_printf("oops, ran out of processes early!\n"); + break; + } + pp = p->p_pptr; + if (pp == NULL) + pp = p; + + cred = p->p_ucred; + pgrp = p->p_pgrp; + db_printf("%5d %5d %5d %5d ", p->p_pid, pp->p_pid, + pgrp != NULL ? pgrp->pg_id : 0, + cred != NULL ? cred->cr_ruid : 0); + + /* Determine our primary process state. */ + switch (p->p_state) { + case PRS_NORMAL: + if (P_SHOULDSTOP(p)) + state[0] = 'T'; + else { + /* + * One of D, L, R, S, W. For a + * multithreaded process we will use + * the state of the thread with the + * highest precedence. The + * precendence order from high to low + * is R, L, D, S, W. If no thread is + * in a sane state we use '?' for our + * primary state. + */ + rflag = sflag = dflag = lflag = wflag = 0; + FOREACH_THREAD_IN_PROC(p, td) { + if (td->td_state == TDS_RUNNING || + td->td_state == TDS_RUNQ || + td->td_state == TDS_CAN_RUN) + rflag++; + if (TD_ON_LOCK(td)) + lflag++; + if (TD_IS_SLEEPING(td)) { + if (!(td->td_flags & TDF_SINTR)) + dflag++; + else + sflag++; + } + if (TD_AWAITING_INTR(td)) + wflag++; + } + if (rflag) + state[0] = 'R'; + else if (lflag) + state[0] = 'L'; + else if (dflag) + state[0] = 'D'; + else if (sflag) + state[0] = 'S'; + else if (wflag) + state[0] = 'W'; + else + state[0] = '?'; + } + break; + case PRS_NEW: + state[0] = 'N'; + break; + case PRS_ZOMBIE: + state[0] = 'Z'; + break; + default: + state[0] = 'U'; + break; + } + state[1] = '\0'; + + /* Additional process state flags. */ + if (!(p->p_flag & P_INMEM)) + strlcat(state, "W", sizeof(state)); + if (p->p_flag & P_TRACED) + strlcat(state, "X", sizeof(state)); + if (p->p_flag & P_WEXIT && p->p_state != PRS_ZOMBIE) + strlcat(state, "E", sizeof(state)); + if (p->p_flag & P_PPWAIT) + strlcat(state, "V", sizeof(state)); + if (p->p_flag & P_SYSTEM || p->p_lock > 0) + strlcat(state, "L", sizeof(state)); + if (p->p_session != NULL && SESS_LEADER(p)) + strlcat(state, "s", sizeof(state)); + /* Cheated here and didn't compare pgid's. */ + if (p->p_flag & P_CONTROLT) + strlcat(state, "+", sizeof(state)); + if (cred != NULL && jailed(cred)) + strlcat(state, "J", sizeof(state)); + db_printf(" %-6.6s ", state); + if (p->p_flag & P_HADTHREADS) { +#ifdef __LP64__ + db_printf(" (threaded) "); +#else + db_printf(" (threaded) "); +#endif + if (p->p_flag & P_SYSTEM) + db_printf("["); + db_printf("%s", p->p_comm); + if (p->p_flag & P_SYSTEM) + db_printf("]"); + db_printf("\n"); + } + FOREACH_THREAD_IN_PROC(p, td) { + dumpthread(p, td, p->p_flag & P_HADTHREADS); + if (db_pager_quit) + break; + } + + p = LIST_NEXT(p, p_list); + if (p == NULL && np > 0) + p = LIST_FIRST(&zombproc); + } +} + +static void +dumpthread(volatile struct proc *p, volatile struct thread *td, int all) +{ + char state[9], wprefix; + const char *wmesg; + void *wchan; + + if (all) { + db_printf("%6d ", td->td_tid); + switch (td->td_state) { + case TDS_RUNNING: + snprintf(state, sizeof(state), "Run"); + break; + case TDS_RUNQ: + snprintf(state, sizeof(state), "RunQ"); + break; + case TDS_CAN_RUN: + snprintf(state, sizeof(state), "CanRun"); + break; + case TDS_INACTIVE: + snprintf(state, sizeof(state), "Inactv"); + break; + case TDS_INHIBITED: + state[0] = '\0'; + if (TD_ON_LOCK(td)) + strlcat(state, "L", sizeof(state)); + if (TD_IS_SLEEPING(td)) { + if (td->td_flags & TDF_SINTR) + strlcat(state, "S", sizeof(state)); + else + strlcat(state, "D", sizeof(state)); + } + if (TD_IS_SWAPPED(td)) + strlcat(state, "W", sizeof(state)); + if (TD_AWAITING_INTR(td)) + strlcat(state, "I", sizeof(state)); + if (TD_IS_SUSPENDED(td)) + strlcat(state, "s", sizeof(state)); + if (state[0] != '\0') + break; + default: + snprintf(state, sizeof(state), "???"); + } + db_printf(" %-6.6s ", state); + } + wprefix = ' '; + if (TD_ON_LOCK(td)) { + wprefix = '*'; + wmesg = td->td_lockname; + wchan = td->td_blocked; + } else if (TD_ON_SLEEPQ(td)) { + wmesg = td->td_wmesg; + wchan = td->td_wchan; + } else if (TD_IS_RUNNING(td)) { + snprintf(state, sizeof(state), "CPU %d", td->td_oncpu); + wmesg = state; + wchan = NULL; + } else { + wmesg = ""; + wchan = NULL; + } + db_printf("%c%-8.8s ", wprefix, wmesg); + if (wchan == NULL) +#ifdef __LP64__ + db_printf("%18s ", ""); +#else + db_printf("%10s ", ""); +#endif + else + db_printf("%p ", wchan); + if (p->p_flag & P_SYSTEM) + db_printf("["); + if (td->td_name[0] != '\0') + db_printf("%s", td->td_name); + else + db_printf("%s", td->td_proc->p_comm); + if (p->p_flag & P_SYSTEM) + db_printf("]"); + db_printf("\n"); +} + +DB_SHOW_COMMAND(thread, db_show_thread) +{ + struct thread *td; + struct lock_object *lock; + boolean_t comma; + + /* Determine which thread to examine. */ + if (have_addr) + td = db_lookup_thread(addr, FALSE); + else + td = kdb_thread; + lock = (struct lock_object *)td->td_lock; + + db_printf("Thread %d at %p:\n", td->td_tid, td); + db_printf(" proc (pid %d): %p\n", td->td_proc->p_pid, td->td_proc); + if (td->td_name[0] != '\0') + db_printf(" name: %s\n", td->td_name); + db_printf(" stack: %p-%p\n", (void *)td->td_kstack, + (void *)(td->td_kstack + td->td_kstack_pages * PAGE_SIZE - 1)); + db_printf(" flags: %#x ", td->td_flags); + db_printf(" pflags: %#x\n", td->td_pflags); + db_printf(" state: "); + switch (td->td_state) { + case TDS_INACTIVE: + db_printf("INACTIVE\n"); + break; + case TDS_CAN_RUN: + db_printf("CAN RUN\n"); + break; + case TDS_RUNQ: + db_printf("RUNQ\n"); + break; + case TDS_RUNNING: + db_printf("RUNNING (CPU %d)\n", td->td_oncpu); + break; + case TDS_INHIBITED: + db_printf("INHIBITED: {"); + comma = FALSE; + if (TD_IS_SLEEPING(td)) { + db_printf("SLEEPING"); + comma = TRUE; + } + if (TD_IS_SUSPENDED(td)) { + if (comma) + db_printf(", "); + db_printf("SUSPENDED"); + comma = TRUE; + } + if (TD_IS_SWAPPED(td)) { + if (comma) + db_printf(", "); + db_printf("SWAPPED"); + comma = TRUE; + } + if (TD_ON_LOCK(td)) { + if (comma) + db_printf(", "); + db_printf("LOCK"); + comma = TRUE; + } + if (TD_AWAITING_INTR(td)) { + if (comma) + db_printf(", "); + db_printf("IWAIT"); + } + db_printf("}\n"); + break; + default: + db_printf("??? (%#x)\n", td->td_state); + break; + } + if (TD_ON_LOCK(td)) + db_printf(" lock: %s turnstile: %p\n", td->td_lockname, + td->td_blocked); + if (TD_ON_SLEEPQ(td)) + db_printf(" wmesg: %s wchan: %p\n", td->td_wmesg, + td->td_wchan); + db_printf(" priority: %d\n", td->td_priority); + db_printf(" container lock: %s (%p)\n", lock->lo_name, lock); +} + +DB_SHOW_COMMAND(proc, db_show_proc) +{ + struct thread *td; + struct proc *p; + int i; + + /* Determine which process to examine. */ + if (have_addr) + p = db_lookup_proc(addr); + else + p = kdb_thread->td_proc; + + db_printf("Process %d (%s) at %p:\n", p->p_pid, p->p_comm, p); + db_printf(" state: "); + switch (p->p_state) { + case PRS_NEW: + db_printf("NEW\n"); + break; + case PRS_NORMAL: + db_printf("NORMAL\n"); + break; + case PRS_ZOMBIE: + db_printf("ZOMBIE\n"); + break; + default: + db_printf("??? (%#x)\n", p->p_state); + } + if (p->p_ucred != NULL) { + db_printf(" uid: %d gids: ", p->p_ucred->cr_uid); + for (i = 0; i < p->p_ucred->cr_ngroups; i++) { + db_printf("%d", p->p_ucred->cr_groups[i]); + if (i < (p->p_ucred->cr_ngroups - 1)) + db_printf(", "); + } + db_printf("\n"); + } + if (p->p_pptr != NULL) + db_printf(" parent: pid %d at %p\n", p->p_pptr->p_pid, + p->p_pptr); + if (p->p_leader != NULL && p->p_leader != p) + db_printf(" leader: pid %d at %p\n", p->p_leader->p_pid, + p->p_leader); + if (p->p_sysent != NULL) + db_printf(" ABI: %s\n", p->p_sysent->sv_name); + if (p->p_args != NULL) + db_printf(" arguments: %.*s\n", (int)p->p_args->ar_length, + p->p_args->ar_args); + db_printf(" threads: %d\n", p->p_numthreads); + FOREACH_THREAD_IN_PROC(p, td) { + dumpthread(p, td, 1); + if (db_pager_quit) + break; + } +} + +void +db_findstack_cmd(db_expr_t addr, boolean_t have_addr, + db_expr_t dummy3 __unused, char *dummy4 __unused) +{ + struct proc *p; + struct thread *td; + struct kstack_cache_entry *ks_ce; + vm_offset_t saddr; + + if (have_addr) + saddr = addr; + else { + db_printf("Usage: findstack <address>\n"); + return; + } + + FOREACH_PROC_IN_SYSTEM(p) { + FOREACH_THREAD_IN_PROC(p, td) { + if (td->td_kstack <= saddr && saddr < td->td_kstack + + PAGE_SIZE * td->td_kstack_pages) { + db_printf("Thread %p\n", td); + return; + } + } + } + + for (ks_ce = kstack_cache; ks_ce != NULL; + ks_ce = ks_ce->next_ks_entry) { + if ((vm_offset_t)ks_ce <= saddr && saddr < (vm_offset_t)ks_ce + + PAGE_SIZE * KSTACK_PAGES) { + db_printf("Cached stack %p\n", ks_ce); + return; + } + } +} diff --git a/sys/ddb/db_run.c b/sys/ddb/db_run.c new file mode 100644 index 0000000..f0b31bf --- /dev/null +++ b/sys/ddb/db_run.c @@ -0,0 +1,392 @@ +/*- + * Mach Operating System + * Copyright (c) 1991,1990 Carnegie Mellon University + * All Rights Reserved. + * + * Permission to use, copy, modify and distribute this software and its + * documentation is hereby granted, provided that both the copyright + * notice and this permission notice appear in all copies of the + * software, derivative works or modified versions, and any portions + * thereof, and that both notices appear in supporting documentation. + * + * CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS + * CONDITION. CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND FOR + * ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE. + * + * Carnegie Mellon requests users of this software to return to + * + * Software Distribution Coordinator or Software.Distribution@CS.CMU.EDU + * School of Computer Science + * Carnegie Mellon University + * Pittsburgh PA 15213-3890 + * + * any improvements or extensions that they make and grant Carnegie the + * rights to redistribute these changes. + */ +/* + * Author: David B. Golub, Carnegie Mellon University + * Date: 7/90 + */ + +/* + * Commands to run process. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/param.h> +#include <sys/kdb.h> +#include <sys/proc.h> + +#include <machine/kdb.h> +#include <machine/pcb.h> + +#include <vm/vm.h> + +#include <ddb/ddb.h> +#include <ddb/db_break.h> +#include <ddb/db_access.h> + +static int db_run_mode; +#define STEP_NONE 0 +#define STEP_ONCE 1 +#define STEP_RETURN 2 +#define STEP_CALLT 3 +#define STEP_CONTINUE 4 +#define STEP_INVISIBLE 5 +#define STEP_COUNT 6 + +static boolean_t db_sstep_print; +static int db_loop_count; +static int db_call_depth; + +int db_inst_count; +int db_load_count; +int db_store_count; + +#ifndef db_set_single_step +void db_set_single_step(void); +#endif +#ifndef db_clear_single_step +void db_clear_single_step(void); +#endif + +#ifdef SOFTWARE_SSTEP +db_breakpoint_t db_not_taken_bkpt = 0; +db_breakpoint_t db_taken_bkpt = 0; +#endif + +boolean_t +db_stop_at_pc(is_breakpoint) + boolean_t *is_breakpoint; +{ + register db_addr_t pc; + register db_breakpoint_t bkpt; + + pc = PC_REGS(); +#ifdef SOFTWARE_SSTEP + if ((db_not_taken_bkpt != 0 && pc == db_not_taken_bkpt->address) + || (db_taken_bkpt != 0 && pc == db_taken_bkpt->address)) + *is_breakpoint = FALSE; +#endif + + db_clear_single_step(); + db_clear_breakpoints(); + db_clear_watchpoints(); + +#ifdef FIXUP_PC_AFTER_BREAK + if (*is_breakpoint) { + /* + * Breakpoint trap. Fix up the PC if the + * machine requires it. + */ + FIXUP_PC_AFTER_BREAK + pc = PC_REGS(); + } +#endif + + /* + * Now check for a breakpoint at this address. + */ + bkpt = db_find_breakpoint_here(pc); + if (bkpt) { + if (--bkpt->count == 0) { + bkpt->count = bkpt->init_count; + *is_breakpoint = TRUE; + return (TRUE); /* stop here */ + } + } else if (*is_breakpoint) { +#ifdef BKPT_SKIP + BKPT_SKIP; +#endif + } + + *is_breakpoint = FALSE; + + if (db_run_mode == STEP_INVISIBLE) { + db_run_mode = STEP_CONTINUE; + return (FALSE); /* continue */ + } + if (db_run_mode == STEP_COUNT) { + return (FALSE); /* continue */ + } + if (db_run_mode == STEP_ONCE) { + if (--db_loop_count > 0) { + if (db_sstep_print) { + db_printf("\t\t"); + db_print_loc_and_inst(pc); + db_printf("\n"); + } + return (FALSE); /* continue */ + } + } + if (db_run_mode == STEP_RETURN) { + /* continue until matching return */ + db_expr_t ins; + + ins = db_get_value(pc, sizeof(int), FALSE); + if (!inst_trap_return(ins) && + (!inst_return(ins) || --db_call_depth != 0)) { + if (db_sstep_print) { + if (inst_call(ins) || inst_return(ins)) { + register int i; + + db_printf("[after %6d] ", db_inst_count); + for (i = db_call_depth; --i > 0; ) + db_printf(" "); + db_print_loc_and_inst(pc); + db_printf("\n"); + } + } + if (inst_call(ins)) + db_call_depth++; + return (FALSE); /* continue */ + } + } + if (db_run_mode == STEP_CALLT) { + /* continue until call or return */ + db_expr_t ins; + + ins = db_get_value(pc, sizeof(int), FALSE); + if (!inst_call(ins) && + !inst_return(ins) && + !inst_trap_return(ins)) { + return (FALSE); /* continue */ + } + } + db_run_mode = STEP_NONE; + return (TRUE); +} + +void +db_restart_at_pc(watchpt) + boolean_t watchpt; +{ + register db_addr_t pc = PC_REGS(); + + if ((db_run_mode == STEP_COUNT) || + (db_run_mode == STEP_RETURN) || + (db_run_mode == STEP_CALLT)) { + db_expr_t ins; + + /* + * We are about to execute this instruction, + * so count it now. + */ + + ins = db_get_value(pc, sizeof(int), FALSE); + db_inst_count++; + db_load_count += inst_load(ins); + db_store_count += inst_store(ins); +#ifdef SOFTWARE_SSTEP + /* XXX works on mips, but... */ + if (inst_branch(ins) || inst_call(ins)) { + ins = db_get_value(next_instr_address(pc,1), + sizeof(int), FALSE); + db_inst_count++; + db_load_count += inst_load(ins); + db_store_count += inst_store(ins); + } +#endif /* SOFTWARE_SSTEP */ + } + + if (db_run_mode == STEP_CONTINUE) { + if (watchpt || db_find_breakpoint_here(pc)) { + /* + * Step over breakpoint/watchpoint. + */ + db_run_mode = STEP_INVISIBLE; + db_set_single_step(); + } else { + db_set_breakpoints(); + db_set_watchpoints(); + } + } else { + db_set_single_step(); + } +} + +#ifdef SOFTWARE_SSTEP +/* + * Software implementation of single-stepping. + * If your machine does not have a trace mode + * similar to the vax or sun ones you can use + * this implementation, done for the mips. + * Just define the above conditional and provide + * the functions/macros defined below. + * + * extern boolean_t + * inst_branch(), returns true if the instruction might branch + * extern unsigned + * branch_taken(), return the address the instruction might + * branch to + * db_getreg_val(); return the value of a user register, + * as indicated in the hardware instruction + * encoding, e.g. 8 for r8 + * + * next_instr_address(pc,bd) returns the address of the first + * instruction following the one at "pc", + * which is either in the taken path of + * the branch (bd==1) or not. This is + * for machines (mips) with branch delays. + * + * A single-step may involve at most 2 breakpoints - + * one for branch-not-taken and one for branch taken. + * If one of these addresses does not already have a breakpoint, + * we allocate a breakpoint and save it here. + * These breakpoints are deleted on return. + */ + +void +db_set_single_step(void) +{ + db_addr_t pc = PC_REGS(), brpc; + unsigned inst; + + /* + * User was stopped at pc, e.g. the instruction + * at pc was not executed. + */ + inst = db_get_value(pc, sizeof(int), FALSE); + if (inst_branch(inst) || inst_call(inst) || inst_return(inst)) { + brpc = branch_taken(inst, pc); + if (brpc != pc) { /* self-branches are hopeless */ + db_taken_bkpt = db_set_temp_breakpoint(brpc); + } + pc = next_instr_address(pc, 1); + } + pc = next_instr_address(pc, 0); + db_not_taken_bkpt = db_set_temp_breakpoint(pc); +} + +void +db_clear_single_step(void) +{ + + if (db_not_taken_bkpt != 0) { + db_delete_temp_breakpoint(db_not_taken_bkpt); + db_not_taken_bkpt = 0; + } + if (db_taken_bkpt != 0) { + db_delete_temp_breakpoint(db_taken_bkpt); + db_taken_bkpt = 0; + } +} + +#endif /* SOFTWARE_SSTEP */ + +extern int db_cmd_loop_done; + +/* single-step */ +/*ARGSUSED*/ +void +db_single_step_cmd(addr, have_addr, count, modif) + db_expr_t addr; + boolean_t have_addr; + db_expr_t count; + char * modif; +{ + boolean_t print = FALSE; + + if (count == -1) + count = 1; + + if (modif[0] == 'p') + print = TRUE; + + db_run_mode = STEP_ONCE; + db_loop_count = count; + db_sstep_print = print; + db_inst_count = 0; + db_load_count = 0; + db_store_count = 0; + + db_cmd_loop_done = 1; +} + +/* trace and print until call/return */ +/*ARGSUSED*/ +void +db_trace_until_call_cmd(addr, have_addr, count, modif) + db_expr_t addr; + boolean_t have_addr; + db_expr_t count; + char * modif; +{ + boolean_t print = FALSE; + + if (modif[0] == 'p') + print = TRUE; + + db_run_mode = STEP_CALLT; + db_sstep_print = print; + db_inst_count = 0; + db_load_count = 0; + db_store_count = 0; + + db_cmd_loop_done = 1; +} + +/*ARGSUSED*/ +void +db_trace_until_matching_cmd(addr, have_addr, count, modif) + db_expr_t addr; + boolean_t have_addr; + db_expr_t count; + char * modif; +{ + boolean_t print = FALSE; + + if (modif[0] == 'p') + print = TRUE; + + db_run_mode = STEP_RETURN; + db_call_depth = 1; + db_sstep_print = print; + db_inst_count = 0; + db_load_count = 0; + db_store_count = 0; + + db_cmd_loop_done = 1; +} + +/* continue */ +/*ARGSUSED*/ +void +db_continue_cmd(addr, have_addr, count, modif) + db_expr_t addr; + boolean_t have_addr; + db_expr_t count; + char * modif; +{ + if (modif[0] == 'c') + db_run_mode = STEP_COUNT; + else + db_run_mode = STEP_CONTINUE; + db_inst_count = 0; + db_load_count = 0; + db_store_count = 0; + + db_cmd_loop_done = 1; +} diff --git a/sys/ddb/db_script.c b/sys/ddb/db_script.c new file mode 100644 index 0000000..34215f8 --- /dev/null +++ b/sys/ddb/db_script.c @@ -0,0 +1,562 @@ +/*- + * 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. + */ + +/*- + * Simple DDB scripting mechanism. Each script consists of a named list of + * DDB commands to execute sequentially. A more sophisticated scripting + * language might be desirable, but would be significantly more complex to + * implement. A more interesting syntax might allow general use of variables + * and extracting of useful values, such as a thread's process identifier, + * for passing into further DDB commands. Certain scripts are run + * automatically at kdb_enter(), if defined, based on how the debugger is + * entered, allowing scripted responses to panics, break signals, etc. + * + * Scripts may be managed from within DDB using the script, scripts, and + * unscript commands. They may also be managed from userspace using ddb(8), + * which operates using a set of sysctls. + * + * TODO: + * - Allow scripts to be defined using tunables so that they can be defined + * before boot and be present in single-user mode without boot scripts + * running. + * - Memory allocation is not possible from within DDB, so we use a set of + * statically allocated buffers to hold defined scripts. However, when + * scripts are being defined from userspace via sysctl, we could in fact be + * using malloc(9) and therefore not impose a static limit, giving greater + * flexibility and avoiding hard-defined buffer limits. + * - When scripts run automatically on entrance to DDB, placing "continue" at + * the end still results in being in the debugger, as we unconditionally + * run db_command_loop() after the script. There should be a way to avoid + * this. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/param.h> +#include <sys/kdb.h> +#include <sys/kernel.h> +#include <sys/libkern.h> +#include <sys/lock.h> +#include <sys/malloc.h> +#include <sys/mutex.h> +#include <sys/sbuf.h> +#include <sys/sysctl.h> +#include <sys/systm.h> + +#include <ddb/ddb.h> +#include <ddb/db_command.h> +#include <ddb/db_lex.h> + +#include <machine/setjmp.h> + +/* + * struct ddb_script describes an individual script. + */ +struct ddb_script { + char ds_scriptname[DB_MAXSCRIPTNAME]; + char ds_script[DB_MAXSCRIPTLEN]; +}; + +/* + * Global list of scripts -- defined scripts have non-empty name fields. + */ +static struct ddb_script db_script_table[DB_MAXSCRIPTS]; + +/* + * While executing a script, we parse it using strsep(), so require a + * temporary buffer that may be used destructively. Since we support weak + * recursion of scripts (one may reference another), we need one buffer for + * each concurrently executing script. + */ +static struct db_recursion_data { + char drd_buffer[DB_MAXSCRIPTLEN]; +} db_recursion_data[DB_MAXSCRIPTRECURSION]; +static int db_recursion = -1; + +/* + * We use a separate static buffer for script validation so that it is safe + * to validate scripts from within a script. This is used only in + * db_script_valid(), which should never be called reentrantly. + */ +static char db_static_buffer[DB_MAXSCRIPTLEN]; + +/* + * Synchronization is not required from within the debugger, as it is + * singe-threaded (although reentrance must be carefully considered). + * However, it is required when interacting with scripts from user space + * processes. Sysctl procedures acquire db_script_mtx before accessing the + * global script data structures. + */ +static struct mtx db_script_mtx; +MTX_SYSINIT(db_script_mtx, &db_script_mtx, "db_script_mtx", MTX_DEF); + +/* + * Some script names have special meaning, such as those executed + * automatically when KDB is entered. + */ +#define DB_SCRIPT_KDBENTER_PREFIX "kdb.enter" /* KDB has entered. */ +#define DB_SCRIPT_KDBENTER_DEFAULT "kdb.enter.default" + +/* + * Find the existing script slot for a named script, if any. + */ +static struct ddb_script * +db_script_lookup(const char *scriptname) +{ + int i; + + for (i = 0; i < DB_MAXSCRIPTS; i++) { + if (strcmp(db_script_table[i].ds_scriptname, scriptname) == + 0) + return (&db_script_table[i]); + } + return (NULL); +} + +/* + * Find a new slot for a script, if available. Does not mark as allocated in + * any way--this must be done by the caller. + */ +static struct ddb_script * +db_script_new(void) +{ + int i; + + for (i = 0; i < DB_MAXSCRIPTS; i++) { + if (strlen(db_script_table[i].ds_scriptname) == 0) + return (&db_script_table[i]); + } + return (NULL); +} + +/* + * Perform very rudimentary validation of a proposed script. It would be + * easy to imagine something more comprehensive. The script string is + * validated in a static buffer. + */ +static int +db_script_valid(const char *scriptname, const char *script) +{ + char *buffer, *command; + + if (strlen(scriptname) == 0) + return (EINVAL); + if (strlen(scriptname) >= DB_MAXSCRIPTNAME) + return (EINVAL); + if (strlen(script) >= DB_MAXSCRIPTLEN) + return (EINVAL); + buffer = db_static_buffer; + strcpy(buffer, script); + while ((command = strsep(&buffer, ";")) != NULL) { + if (strlen(command) >= DB_MAXLINE) + return (EINVAL); + } + return (0); +} + +/* + * Modify an existing script or add a new script with the specified script + * name and contents. If there are no script slots available, an error will + * be returned. + */ +static int +db_script_set(const char *scriptname, const char *script) +{ + struct ddb_script *dsp; + int error; + + error = db_script_valid(scriptname, script); + if (error) + return (error); + dsp = db_script_lookup(scriptname); + if (dsp == NULL) { + dsp = db_script_new(); + if (dsp == NULL) + return (ENOSPC); + strlcpy(dsp->ds_scriptname, scriptname, + sizeof(dsp->ds_scriptname)); + } + strlcpy(dsp->ds_script, script, sizeof(dsp->ds_script)); + return (0); +} + +/* + * Delete an existing script by name, if found. + */ +static int +db_script_unset(const char *scriptname) +{ + struct ddb_script *dsp; + + dsp = db_script_lookup(scriptname); + if (dsp == NULL) + return (ENOENT); + strcpy(dsp->ds_scriptname, ""); + strcpy(dsp->ds_script, ""); + return (0); +} + +/* + * Trim leading/trailing white space in a command so that we don't pass + * carriage returns, etc, into DDB command parser. + */ +static int +db_command_trimmable(char ch) +{ + + switch (ch) { + case ' ': + case '\t': + case '\n': + case '\r': + return (1); + + default: + return (0); + } +} + +static void +db_command_trim(char **commandp) +{ + char *command; + + command = *commandp; + while (db_command_trimmable(*command)) + command++; + while ((strlen(command) > 0) && + db_command_trimmable(command[strlen(command) - 1])) + command[strlen(command) - 1] = 0; + *commandp = command; +} + +/* + * Execute a script, breaking it up into individual commands and passing them + * sequentially into DDB's input processing. Use the KDB jump buffer to + * restore control to the main script loop if things get too wonky when + * processing a command -- i.e., traps, etc. Also, make sure we don't exceed + * practical limits on recursion. + * + * XXXRW: If any individual command is too long, it will be truncated when + * injected into the input at a lower layer. We should validate the script + * before configuring it to avoid this scenario. + */ +static int +db_script_exec(const char *scriptname, int warnifnotfound) +{ + struct db_recursion_data *drd; + struct ddb_script *dsp; + char *buffer, *command; + void *prev_jb; + jmp_buf jb; + + dsp = db_script_lookup(scriptname); + if (dsp == NULL) { + if (warnifnotfound) + db_printf("script '%s' not found\n", scriptname); + return (ENOENT); + } + + if (db_recursion >= DB_MAXSCRIPTRECURSION) { + db_printf("Script stack too deep\n"); + return (E2BIG); + } + db_recursion++; + drd = &db_recursion_data[db_recursion]; + + /* + * Parse script in temporary buffer, since strsep() is destructive. + */ + buffer = drd->drd_buffer; + strcpy(buffer, dsp->ds_script); + while ((command = strsep(&buffer, ";")) != NULL) { + db_printf("db:%d:%s> %s\n", db_recursion, scriptname, + command); + db_command_trim(&command); + prev_jb = kdb_jmpbuf(jb); + if (setjmp(jb) == 0) + db_command_script(command); + else + db_printf("Script command '%s' returned error\n", + command); + kdb_jmpbuf(prev_jb); + } + db_recursion--; + return (0); +} + +/* + * Wrapper for exec path that is called on KDB enter. Map reason for KDB + * enter to a script name, and don't whine if the script doesn't exist. If + * there is no matching script, try the catch-all script. + */ +void +db_script_kdbenter(const char *eventname) +{ + char scriptname[DB_MAXSCRIPTNAME]; + + snprintf(scriptname, sizeof(scriptname), "%s.%s", + DB_SCRIPT_KDBENTER_PREFIX, eventname); + if (db_script_exec(scriptname, 0) == ENOENT) + (void)db_script_exec(DB_SCRIPT_KDBENTER_DEFAULT, 0); +} + +/*- + * DDB commands for scripting, as reached via the DDB user interface: + * + * scripts - lists scripts + * run <scriptname> - run a script + * script <scriptname> - prints script + * script <scriptname> <script> - set a script + * unscript <scriptname> - remove a script + */ + +/* + * List scripts and their contents. + */ +void +db_scripts_cmd(db_expr_t addr, boolean_t have_addr, db_expr_t count, + char *modif) +{ + int i; + + for (i = 0; i < DB_MAXSCRIPTS; i++) { + if (strlen(db_script_table[i].ds_scriptname) != 0) { + db_printf("%s=%s\n", + db_script_table[i].ds_scriptname, + db_script_table[i].ds_script); + } + } +} + +/* + * Execute a script. + */ +void +db_run_cmd(db_expr_t addr, boolean_t have_addr, db_expr_t count, char *modif) +{ + int t; + + /* + * Right now, we accept exactly one argument. In the future, we + * might want to accept flags and arguments to the script itself. + */ + t = db_read_token(); + if (t != tIDENT) + db_error("?\n"); + + if (db_read_token() != tEOL) + db_error("?\n"); + + db_script_exec(db_tok_string, 1); +} + +/* + * Print or set a named script, with the set portion broken out into its own + * function. We must directly access the remainder of the DDB line input as + * we do not wish to use db_lex's token processing. + */ +void +db_script_cmd(db_expr_t addr, boolean_t have_addr, db_expr_t count, + char *modif) +{ + char *buf, scriptname[DB_MAXSCRIPTNAME]; + struct ddb_script *dsp; + int error, t; + + t = db_read_token(); + if (t != tIDENT) { + db_printf("usage: script scriptname=script\n"); + db_skip_to_eol(); + return; + } + + if (strlcpy(scriptname, db_tok_string, sizeof(scriptname)) >= + sizeof(scriptname)) { + db_printf("scriptname too long\n"); + db_skip_to_eol(); + return; + } + + t = db_read_token(); + if (t == tEOL) { + dsp = db_script_lookup(scriptname); + if (dsp == NULL) { + db_printf("script '%s' not found\n", scriptname); + db_skip_to_eol(); + return; + } + db_printf("%s=%s\n", scriptname, dsp->ds_script); + } else if (t == tEQ) { + buf = db_get_line(); + if (buf[strlen(buf)-1] == '\n') + buf[strlen(buf)-1] = '\0'; + error = db_script_set(scriptname, buf); + if (error != 0) + db_printf("Error: %d\n", error); + } else + db_printf("?\n"); + db_skip_to_eol(); +} + +/* + * Remove a named script. + */ +void +db_unscript_cmd(db_expr_t addr, boolean_t have_addr, db_expr_t count, + char *modif) +{ + int error, t; + + t = db_read_token(); + if (t != tIDENT) { + db_printf("?\n"); + db_skip_to_eol(); + return; + } + + error = db_script_unset(db_tok_string); + if (error == ENOENT) { + db_printf("script '%s' not found\n", db_tok_string); + db_skip_to_eol(); + return; + } + db_skip_to_eol(); +} + +/* + * Sysctls for managing DDB scripting: + * + * debug.ddb.scripting.script - Define a new script + * debug.ddb.scripting.scripts - List of names *and* scripts + * debug.ddb.scripting.unscript - Remove an existing script + * + * Since we don't want to try to manage arbitrary extensions to the sysctl + * name space from the debugger, the script/unscript sysctls are a bit more + * like RPCs and a bit less like normal get/set requests. The ddb(8) command + * line tool wraps them to make things a bit more user-friendly. + */ +static SYSCTL_NODE(_debug_ddb, OID_AUTO, scripting, CTLFLAG_RW, 0, + "DDB script settings"); + +static int +sysctl_debug_ddb_scripting_scripts(SYSCTL_HANDLER_ARGS) +{ + struct sbuf sb; + int error, i, len; + char *buffer; + + /* + * Make space to include a maximum-length name, = symbol, + * maximum-length script, and carriage return for every script that + * may be defined. + */ + len = DB_MAXSCRIPTS * (DB_MAXSCRIPTNAME + 1 + DB_MAXSCRIPTLEN + 1); + buffer = malloc(len, M_TEMP, M_WAITOK); + (void)sbuf_new(&sb, buffer, len, SBUF_FIXEDLEN); + mtx_lock(&db_script_mtx); + for (i = 0; i < DB_MAXSCRIPTS; i++) { + if (strlen(db_script_table[i].ds_scriptname) == 0) + continue; + (void)sbuf_printf(&sb, "%s=%s\n", + db_script_table[i].ds_scriptname, + db_script_table[i].ds_script); + } + mtx_unlock(&db_script_mtx); + sbuf_finish(&sb); + error = SYSCTL_OUT(req, sbuf_data(&sb), sbuf_len(&sb) + 1); + sbuf_delete(&sb); + free(buffer, M_TEMP); + return (error); +} +SYSCTL_PROC(_debug_ddb_scripting, OID_AUTO, scripts, CTLTYPE_STRING | + CTLFLAG_RD, 0, 0, sysctl_debug_ddb_scripting_scripts, "A", + "List of defined scripts"); + +static int +sysctl_debug_ddb_scripting_script(SYSCTL_HANDLER_ARGS) +{ + char *buffer, *script, *scriptname; + int error, len; + + /* + * Maximum length for an input string is DB_MAXSCRIPTNAME + '=' + * symbol + DB_MAXSCRIPT. + */ + len = DB_MAXSCRIPTNAME + DB_MAXSCRIPTLEN + 1; + buffer = malloc(len, M_TEMP, M_WAITOK | M_ZERO); + error = sysctl_handle_string(oidp, buffer, len, req); + if (error) + goto out; + + /* + * Argument will be in form scriptname=script, so split into the + * scriptname and script. + */ + script = buffer; + scriptname = strsep(&script, "="); + if (script == NULL) { + error = EINVAL; + goto out; + } + mtx_lock(&db_script_mtx); + error = db_script_set(scriptname, script); + mtx_unlock(&db_script_mtx); +out: + free(buffer, M_TEMP); + return (error); +} +SYSCTL_PROC(_debug_ddb_scripting, OID_AUTO, script, CTLTYPE_STRING | + CTLFLAG_RW, 0, 0, sysctl_debug_ddb_scripting_script, "A", + "Set a script"); + +/* + * debug.ddb.scripting.unscript has somewhat unusual sysctl semantics -- set + * the name of the script that you want to delete. + */ +static int +sysctl_debug_ddb_scripting_unscript(SYSCTL_HANDLER_ARGS) +{ + char name[DB_MAXSCRIPTNAME]; + int error; + + bzero(name, sizeof(name)); + error = sysctl_handle_string(oidp, name, sizeof(name), req); + if (error) + return (error); + if (req->newptr == NULL) + return (0); + mtx_lock(&db_script_mtx); + error = db_script_unset(name); + mtx_unlock(&db_script_mtx); + if (error == ENOENT) + return (EINVAL); /* Don't confuse sysctl consumers. */ + return (0); +} +SYSCTL_PROC(_debug_ddb_scripting, OID_AUTO, unscript, CTLTYPE_STRING | + CTLFLAG_RW, 0, 0, sysctl_debug_ddb_scripting_unscript, "A", + "Unset a script"); diff --git a/sys/ddb/db_sym.c b/sys/ddb/db_sym.c new file mode 100644 index 0000000..04af1eb --- /dev/null +++ b/sys/ddb/db_sym.c @@ -0,0 +1,505 @@ +/*- + * Mach Operating System + * Copyright (c) 1991,1990 Carnegie Mellon University + * All Rights Reserved. + * + * Permission to use, copy, modify and distribute this software and its + * documentation is hereby granted, provided that both the copyright + * notice and this permission notice appear in all copies of the + * software, derivative works or modified versions, and any portions + * thereof, and that both notices appear in supporting documentation. + * + * CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS + * CONDITION. CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND FOR + * ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE. + * + * Carnegie Mellon requests users of this software to return to + * + * Software Distribution Coordinator or Software.Distribution@CS.CMU.EDU + * School of Computer Science + * Carnegie Mellon University + * Pittsburgh PA 15213-3890 + * + * any improvements or extensions that they make and grant Carnegie the + * rights to redistribute these changes. + */ +/* + * Author: David B. Golub, Carnegie Mellon University + * Date: 7/90 + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/param.h> +#include <sys/pcpu.h> +#include <sys/smp.h> +#include <sys/systm.h> + +#include <net/vnet.h> + +#include <ddb/ddb.h> +#include <ddb/db_sym.h> +#include <ddb/db_variables.h> + +#include <opt_ddb.h> + +/* + * Multiple symbol tables + */ +#ifndef MAXNOSYMTABS +#define MAXNOSYMTABS 3 /* mach, ux, emulator */ +#endif + +static db_symtab_t db_symtabs[MAXNOSYMTABS] = {{0,},}; +static int db_nsymtab = 0; + +static db_symtab_t *db_last_symtab; /* where last symbol was found */ + +static c_db_sym_t db_lookup( const char *symstr); +static char *db_qualify(c_db_sym_t sym, char *symtabname); +static boolean_t db_symbol_is_ambiguous(c_db_sym_t sym); +static boolean_t db_line_at_pc(c_db_sym_t, char **, int *, db_expr_t); + +static int db_cpu = -1; + +#ifdef VIMAGE +static void *db_vnet = NULL; +#endif + +/* + * Validate the CPU number used to interpret per-CPU variables so we can + * avoid later confusion if an invalid CPU is requested. + */ +int +db_var_db_cpu(struct db_variable *vp, db_expr_t *valuep, int op) +{ + + switch (op) { + case DB_VAR_GET: + *valuep = db_cpu; + return (1); + + case DB_VAR_SET: + if (*(int *)valuep < -1 && *(int *)valuep > mp_maxid) { + db_printf("Invalid value: %d", *(int*)valuep); + return (0); + } + db_cpu = *(int *)valuep; + return (1); + + default: + db_printf("db_var_db_cpu: unknown operation\n"); + return (0); + } +} + +/* + * Read-only variable reporting the current CPU, which is what we use when + * db_cpu is set to -1. + */ +int +db_var_curcpu(struct db_variable *vp, db_expr_t *valuep, int op) +{ + + switch (op) { + case DB_VAR_GET: + *valuep = curcpu; + return (1); + + case DB_VAR_SET: + db_printf("Read-only variable.\n"); + return (0); + + default: + db_printf("db_var_curcpu: unknown operation\n"); + return (0); + } +} + +#ifdef VIMAGE +/* + * Validate the virtual network pointer used to interpret per-vnet global + * variable expansion. Right now we don't do much here, really we should + * walk the global vnet list to check it's an OK pointer. + */ +int +db_var_db_vnet(struct db_variable *vp, db_expr_t *valuep, int op) +{ + + switch (op) { + case DB_VAR_GET: + *valuep = (db_expr_t)db_vnet; + return (1); + + case DB_VAR_SET: + db_vnet = *(void **)valuep; + return (1); + + default: + db_printf("db_var_db_vnet: unknown operation\n"); + return (0); + } +} + +/* + * Read-only variable reporting the current vnet, which is what we use when + * db_vnet is set to NULL. + */ +int +db_var_curvnet(struct db_variable *vp, db_expr_t *valuep, int op) +{ + + switch (op) { + case DB_VAR_GET: + *valuep = (db_expr_t)curvnet; + return (1); + + case DB_VAR_SET: + db_printf("Read-only variable.\n"); + return (0); + + default: + db_printf("db_var_curcpu: unknown operation\n"); + return (0); + } +} +#endif + +/* + * Add symbol table, with given name, to list of symbol tables. + */ +void +db_add_symbol_table(start, end, name, ref) + char *start; + char *end; + char *name; + char *ref; +{ + if (db_nsymtab >= MAXNOSYMTABS) { + printf ("No slots left for %s symbol table", name); + panic ("db_sym.c: db_add_symbol_table"); + } + + db_symtabs[db_nsymtab].start = start; + db_symtabs[db_nsymtab].end = end; + db_symtabs[db_nsymtab].name = name; + db_symtabs[db_nsymtab].private = ref; + db_nsymtab++; +} + +/* + * db_qualify("vm_map", "ux") returns "unix:vm_map". + * + * Note: return value points to static data whose content is + * overwritten by each call... but in practice this seems okay. + */ +static char * +db_qualify(sym, symtabname) + c_db_sym_t sym; + register char *symtabname; +{ + const char *symname; + static char tmp[256]; + + db_symbol_values(sym, &symname, 0); + snprintf(tmp, sizeof(tmp), "%s:%s", symtabname, symname); + return tmp; +} + + +boolean_t +db_eqname(src, dst, c) + const char *src; + const char *dst; + int c; +{ + if (!strcmp(src, dst)) + return (TRUE); + if (src[0] == c) + return (!strcmp(src+1,dst)); + return (FALSE); +} + +boolean_t +db_value_of_name(name, valuep) + const char *name; + db_expr_t *valuep; +{ + c_db_sym_t sym; + + sym = db_lookup(name); + if (sym == C_DB_SYM_NULL) + return (FALSE); + db_symbol_values(sym, &name, valuep); + return (TRUE); +} + +boolean_t +db_value_of_name_pcpu(name, valuep) + const char *name; + db_expr_t *valuep; +{ + static char tmp[256]; + db_expr_t value; + c_db_sym_t sym; + int cpu; + + if (db_cpu != -1) + cpu = db_cpu; + else + cpu = curcpu; + snprintf(tmp, sizeof(tmp), "pcpu_entry_%s", name); + sym = db_lookup(tmp); + if (sym == C_DB_SYM_NULL) + return (FALSE); + db_symbol_values(sym, &name, &value); + if (value < DPCPU_START || value >= DPCPU_STOP) + return (FALSE); + *valuep = (db_expr_t)((uintptr_t)value + dpcpu_off[cpu]); + return (TRUE); +} + +boolean_t +db_value_of_name_vnet(name, valuep) + const char *name; + db_expr_t *valuep; +{ +#ifdef VIMAGE + static char tmp[256]; + db_expr_t value; + c_db_sym_t sym; + struct vnet *vnet; + + if (db_vnet != NULL) + vnet = db_vnet; + else + vnet = curvnet; + snprintf(tmp, sizeof(tmp), "vnet_entry_%s", name); + sym = db_lookup(tmp); + if (sym == C_DB_SYM_NULL) + return (FALSE); + db_symbol_values(sym, &name, &value); + if (value < VNET_START || value >= VNET_STOP) + return (FALSE); + *valuep = (db_expr_t)((uintptr_t)value + vnet->vnet_data_base); + return (TRUE); +#else + return (FALSE); +#endif +} + +/* + * Lookup a symbol. + * If the symbol has a qualifier (e.g., ux:vm_map), + * then only the specified symbol table will be searched; + * otherwise, all symbol tables will be searched. + */ +static c_db_sym_t +db_lookup(symstr) + const char *symstr; +{ + c_db_sym_t sp; + register int i; + int symtab_start = 0; + int symtab_end = db_nsymtab; + register const char *cp; + + /* + * Look for, remove, and remember any symbol table specifier. + */ + for (cp = symstr; *cp; cp++) { + if (*cp == ':') { + for (i = 0; i < db_nsymtab; i++) { + int n = strlen(db_symtabs[i].name); + + if ( + n == (cp - symstr) && + strncmp(symstr, db_symtabs[i].name, n) == 0 + ) { + symtab_start = i; + symtab_end = i + 1; + break; + } + } + if (i == db_nsymtab) { + db_error("invalid symbol table name"); + } + symstr = cp+1; + } + } + + /* + * Look in the specified set of symbol tables. + * Return on first match. + */ + for (i = symtab_start; i < symtab_end; i++) { + sp = X_db_lookup(&db_symtabs[i], symstr); + if (sp) { + db_last_symtab = &db_symtabs[i]; + return sp; + } + } + return 0; +} + +/* + * If TRUE, check across symbol tables for multiple occurrences + * of a name. Might slow things down quite a bit. + */ +static volatile boolean_t db_qualify_ambiguous_names = FALSE; + +/* + * Does this symbol name appear in more than one symbol table? + * Used by db_symbol_values to decide whether to qualify a symbol. + */ +static boolean_t +db_symbol_is_ambiguous(sym) + c_db_sym_t sym; +{ + const char *sym_name; + register int i; + register + boolean_t found_once = FALSE; + + if (!db_qualify_ambiguous_names) + return FALSE; + + db_symbol_values(sym, &sym_name, 0); + for (i = 0; i < db_nsymtab; i++) { + if (X_db_lookup(&db_symtabs[i], sym_name)) { + if (found_once) + return TRUE; + found_once = TRUE; + } + } + return FALSE; +} + +/* + * Find the closest symbol to val, and return its name + * and the difference between val and the symbol found. + */ +c_db_sym_t +db_search_symbol( val, strategy, offp) + register db_addr_t val; + db_strategy_t strategy; + db_expr_t *offp; +{ + register + unsigned int diff; + size_t newdiff; + register int i; + c_db_sym_t ret = C_DB_SYM_NULL, sym; + + newdiff = diff = ~0; + for (i = 0; i < db_nsymtab; i++) { + sym = X_db_search_symbol(&db_symtabs[i], val, strategy, &newdiff); + if (newdiff < diff) { + db_last_symtab = &db_symtabs[i]; + diff = newdiff; + ret = sym; + } + } + *offp = diff; + return ret; +} + +/* + * Return name and value of a symbol + */ +void +db_symbol_values(sym, namep, valuep) + c_db_sym_t sym; + const char **namep; + db_expr_t *valuep; +{ + db_expr_t value; + + if (sym == DB_SYM_NULL) { + *namep = 0; + return; + } + + X_db_symbol_values(db_last_symtab, sym, namep, &value); + + if (db_symbol_is_ambiguous(sym)) + *namep = db_qualify(sym, db_last_symtab->name); + if (valuep) + *valuep = value; +} + + +/* + * Print a the closest symbol to value + * + * After matching the symbol according to the given strategy + * we print it in the name+offset format, provided the symbol's + * value is close enough (eg smaller than db_maxoff). + * We also attempt to print [filename:linenum] when applicable + * (eg for procedure names). + * + * If we could not find a reasonable name+offset representation, + * then we just print the value in hex. Small values might get + * bogus symbol associations, e.g. 3 might get some absolute + * value like _INCLUDE_VERSION or something, therefore we do + * not accept symbols whose value is "small" (and use plain hex). + */ + +db_expr_t db_maxoff = 0x10000; + +void +db_printsym(off, strategy) + db_expr_t off; + db_strategy_t strategy; +{ + db_expr_t d; + char *filename; + const char *name; + db_expr_t value; + int linenum; + c_db_sym_t cursym; + + cursym = db_search_symbol(off, strategy, &d); + db_symbol_values(cursym, &name, &value); + if (name == 0) + value = off; + if (value >= DB_SMALL_VALUE_MIN && value <= DB_SMALL_VALUE_MAX) { + db_printf("%+#lr", (long)off); + return; + } + if (name == 0 || d >= (unsigned long)db_maxoff) { + db_printf("%#lr", (unsigned long)off); + return; + } +#ifdef DDB_NUMSYM + db_printf("%#lr = %s", (unsigned long)off, name); +#else + db_printf("%s", name); +#endif + if (d) + db_printf("+%+#lr", (long)d); + if (strategy == DB_STGY_PROC) { + if (db_line_at_pc(cursym, &filename, &linenum, off)) + db_printf(" [%s:%d]", filename, linenum); + } +} + +static boolean_t +db_line_at_pc( sym, filename, linenum, pc) + c_db_sym_t sym; + char **filename; + int *linenum; + db_expr_t pc; +{ + return X_db_line_at_pc( db_last_symtab, sym, filename, linenum, pc); +} + +int +db_sym_numargs(sym, nargp, argnames) + c_db_sym_t sym; + int *nargp; + char **argnames; +{ + return X_db_sym_numargs(db_last_symtab, sym, nargp, argnames); +} diff --git a/sys/ddb/db_sym.h b/sys/ddb/db_sym.h new file mode 100644 index 0000000..f68ccbc --- /dev/null +++ b/sys/ddb/db_sym.h @@ -0,0 +1,106 @@ +/*- + * Mach Operating System + * Copyright (c) 1991,1990 Carnegie Mellon University + * All Rights Reserved. + * + * Permission to use, copy, modify and distribute this software and its + * documentation is hereby granted, provided that both the copyright + * notice and this permission notice appear in all copies of the + * software, derivative works or modified versions, and any portions + * thereof, and that both notices appear in supporting documentation. + * + * CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS + * CONDITION. CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND FOR + * ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE. + * + * Carnegie Mellon requests users of this software to return to + * + * Software Distribution Coordinator or Software.Distribution@CS.CMU.EDU + * School of Computer Science + * Carnegie Mellon University + * Pittsburgh PA 15213-3890 + * + * any improvements or extensions that they make and grant Carnegie the + * rights to redistribute these changes. + * + * $FreeBSD$ + */ + +#ifndef _DDB_DB_SYM_H_ +#define _DDB_DB_SYM_H_ + +/* + * Author: Alessandro Forin, Carnegie Mellon University + * Date: 8/90 + */ + +/* + * This module can handle multiple symbol tables + */ +typedef struct { + char *name; /* symtab name */ + char *start; /* symtab location */ + char *end; + char *private; /* optional machdep pointer */ +} db_symtab_t; + +/* + * Symbol representation is specific to the symtab style: + * BSD compilers use dbx' nlist, other compilers might use + * a different one + */ +typedef char * db_sym_t; /* opaque handle on symbols */ +typedef const char * c_db_sym_t; /* const opaque handle on symbols */ +#define DB_SYM_NULL ((db_sym_t)0) +#define C_DB_SYM_NULL ((c_db_sym_t)0) + +/* + * Non-stripped symbol tables will have duplicates, for instance + * the same string could match a parameter name, a local var, a + * global var, etc. + * We are most concern with the following matches. + */ +typedef int db_strategy_t; /* search strategy */ + +#define DB_STGY_ANY 0 /* anything goes */ +#define DB_STGY_XTRN 1 /* only external symbols */ +#define DB_STGY_PROC 2 /* only procedures */ + +/* + * Functions exported by the symtable module + */ +void db_add_symbol_table(char *, char *, char *, char *); + /* extend the list of symbol tables */ + +c_db_sym_t db_search_symbol(db_addr_t, db_strategy_t, db_expr_t *); + /* find symbol given value */ + +void db_symbol_values(c_db_sym_t, const char **, db_expr_t *); + /* return name and value of symbol */ + +#define db_find_sym_and_offset(val,namep,offp) \ + db_symbol_values(db_search_symbol(val,DB_STGY_ANY,offp),namep,0) + /* find name&value given approx val */ + +#define db_find_xtrn_sym_and_offset(val,namep,offp) \ + db_symbol_values(db_search_symbol(val,DB_STGY_XTRN,offp),namep,0) + /* ditto, but no locals */ + +int db_eqname(const char *, const char *, int); + /* strcmp, modulo leading char */ + +void db_printsym(db_expr_t, db_strategy_t); + /* print closest symbol to a value */ + +int db_sym_numargs(c_db_sym_t, int *, char **); + +boolean_t X_db_line_at_pc(db_symtab_t *symtab, c_db_sym_t cursym, + char **filename, int *linenum, db_expr_t off); +c_db_sym_t X_db_lookup(db_symtab_t *stab, const char *symstr); +c_db_sym_t X_db_search_symbol(db_symtab_t *symtab, db_addr_t off, + db_strategy_t strategy, db_expr_t *diffp); +int X_db_sym_numargs(db_symtab_t *, c_db_sym_t, int *, char **); +void X_db_symbol_values(db_symtab_t *symtab, c_db_sym_t sym, + const char **namep, db_expr_t *valuep); + +#endif /* !_DDB_DB_SYM_H_ */ diff --git a/sys/ddb/db_textdump.c b/sys/ddb/db_textdump.c new file mode 100644 index 0000000..e664870 --- /dev/null +++ b/sys/ddb/db_textdump.c @@ -0,0 +1,550 @@ +/*- + * 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: write a series of text files to the dump + * partition for later recovery, including captured DDB output, kernel + * configuration, message buffer, and panic message. 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 ustar file format so that we can 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 "opt_ddb.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. + */ +#ifdef TEXTDUMP_PREFERRED +int textdump_pending = 1; +#else +int textdump_pending = 0; +#endif +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; + +/* + * Calculate and fill in the checksum for a ustar 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; + +#ifdef TEXTDUMP_VERBOSE + if (textdump_error == 0) + printf("textdump: creating '%s'.\n", filename); +#endif + 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 = dump_write(di, buffer, 0, offset + di->mediaoffset, + TEXTDUMP_BLOCKSIZE); + if (textdump_error) + printf("textdump_writeblock: offset %jd, error %d\n", (intmax_t)offset, + textdump_error); + 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. + * + * If we ever want to support writing textdumps to tape or other + * stream-oriented target, we'll need to remove this. + */ +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 for minimal textdump.\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, TEXTDUMPMAGIC, 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, TEXTDUMPMAGIC, 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)dump_write(di, NULL, 0, 0, 0); + if (textdump_error == ENOSPC) + printf("Textdump: Insufficient space on dump partition\n"); + else if (textdump_error != 0) + printf("Textdump: 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|dump]\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 if (strcmp(db_tok_string, "dump") == 0) { + textdump_pending = 1; + doadump(TRUE); + } else { + db_textdump_usage(); + } +} diff --git a/sys/ddb/db_thread.c b/sys/ddb/db_thread.c new file mode 100644 index 0000000..f6712a0 --- /dev/null +++ b/sys/ddb/db_thread.c @@ -0,0 +1,172 @@ +/*- + * Copyright (c) 2004 Marcel Moolenaar + * 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 ``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 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. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/kdb.h> +#include <sys/proc.h> + +#include <machine/pcb.h> + +#include <ddb/ddb.h> +#include <ddb/db_command.h> +#include <ddb/db_sym.h> + +void +db_print_thread(void) +{ + pid_t pid; + + pid = -1; + if (kdb_thread->td_proc != NULL) + pid = kdb_thread->td_proc->p_pid; + db_printf("[ thread pid %d tid %ld ]\n", pid, (long)kdb_thread->td_tid); +} + +void +db_set_thread(db_expr_t tid, boolean_t hastid, db_expr_t cnt, char *mod) +{ + struct thread *thr; + db_expr_t radix; + int err; + + /* + * We parse our own arguments. We don't like the default radix. + */ + radix = db_radix; + db_radix = 10; + hastid = db_expression(&tid); + db_radix = radix; + db_skip_to_eol(); + + if (hastid) { + thr = kdb_thr_lookup(tid); + if (thr != NULL) { + err = kdb_thr_select(thr); + if (err != 0) { + db_printf("unable to switch to thread %ld\n", + (long)thr->td_tid); + return; + } + db_dot = PC_REGS(); + } else { + db_printf("%d: invalid thread\n", (int)tid); + return; + } + } + + db_print_thread(); + db_print_loc_and_inst(PC_REGS()); +} + +void +db_show_threads(db_expr_t addr, boolean_t hasaddr, db_expr_t cnt, char *mod) +{ + jmp_buf jb; + void *prev_jb; + struct thread *thr; + + thr = kdb_thr_first(); + while (!db_pager_quit && thr != NULL) { + db_printf(" %6ld (%p) (stack %p) ", (long)thr->td_tid, thr, + (void *)thr->td_kstack); + prev_jb = kdb_jmpbuf(jb); + if (setjmp(jb) == 0) { + if (db_trace_thread(thr, 1) != 0) + db_printf("***\n"); + } + kdb_jmpbuf(prev_jb); + thr = kdb_thr_next(thr); + } +} + +/* + * Lookup a thread based on a db expression address. We assume that the + * address was parsed in hexadecimal. We reparse the address in decimal + * first and try to treat it as a thread ID to find an associated thread. + * If that fails and check_pid is true, we treat the decimal value as a + * PID. If that matches a process, we return the first thread in that + * process. Otherwise, we treat the addr as a pointer to a thread. + */ +struct thread * +db_lookup_thread(db_expr_t addr, boolean_t check_pid) +{ + struct thread *td; + db_expr_t decaddr; + struct proc *p; + + /* + * If the parsed address was not a valid decimal expression, + * assume it is a thread pointer. + */ + decaddr = db_hex2dec(addr); + if (decaddr == -1) + return ((struct thread *)addr); + + td = kdb_thr_lookup(decaddr); + if (td != NULL) + return (td); + if (check_pid) { + FOREACH_PROC_IN_SYSTEM(p) { + if (p->p_pid == decaddr) + return (FIRST_THREAD_IN_PROC(p)); + } + LIST_FOREACH(p, &zombproc, p_list) { + if (p->p_pid == decaddr) + return (FIRST_THREAD_IN_PROC(p)); + } + } + return ((struct thread *)addr); +} + +/* + * Lookup a process based on a db expression address. We assume that the + * address was parsed in hexadecimal. We reparse the address in decimal + * first and try to treat it as a PID to find an associated process. + * If that fails we treat the addr as a pointer to a process. + */ +struct proc * +db_lookup_proc(db_expr_t addr) +{ + db_expr_t decaddr; + struct proc *p; + + decaddr = db_hex2dec(addr); + if (decaddr != -1) { + FOREACH_PROC_IN_SYSTEM(p) { + if (p->p_pid == decaddr) + return (p); + } + LIST_FOREACH(p, &zombproc, p_list) { + if (p->p_pid == decaddr) + return (p); + } + } + return ((struct proc *)addr); +} diff --git a/sys/ddb/db_variables.c b/sys/ddb/db_variables.c new file mode 100644 index 0000000..69c11ae --- /dev/null +++ b/sys/ddb/db_variables.c @@ -0,0 +1,159 @@ +/*- + * Mach Operating System + * Copyright (c) 1991,1990 Carnegie Mellon University + * All Rights Reserved. + * + * Permission to use, copy, modify and distribute this software and its + * documentation is hereby granted, provided that both the copyright + * notice and this permission notice appear in all copies of the + * software, derivative works or modified versions, and any portions + * thereof, and that both notices appear in supporting documentation. + * + * CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS + * CONDITION. CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND FOR + * ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE. + * + * Carnegie Mellon requests users of this software to return to + * + * Software Distribution Coordinator or Software.Distribution@CS.CMU.EDU + * School of Computer Science + * Carnegie Mellon University + * Pittsburgh PA 15213-3890 + * + * any improvements or extensions that they make and grant Carnegie the + * rights to redistribute these changes. + */ +/* + * Author: David B. Golub, Carnegie Mellon University + * Date: 7/90 + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/param.h> +#include <sys/systm.h> + +#include <ddb/ddb.h> +#include <ddb/db_lex.h> +#include <ddb/db_variables.h> + +static int db_find_variable(struct db_variable **varp); + +static struct db_variable db_vars[] = { + { "radix", &db_radix, FCN_NULL }, + { "maxoff", &db_maxoff, FCN_NULL }, + { "maxwidth", &db_max_width, FCN_NULL }, + { "tabstops", &db_tab_stop_width, FCN_NULL }, + { "lines", &db_lines_per_page, FCN_NULL }, + { "curcpu", NULL, db_var_curcpu }, + { "db_cpu", NULL, db_var_db_cpu }, +#ifdef VIMAGE + { "curvnet", NULL, db_var_curvnet }, + { "db_vnet", NULL, db_var_db_vnet }, +#endif +}; +static struct db_variable *db_evars = + db_vars + sizeof(db_vars)/sizeof(db_vars[0]); + +static int +db_find_variable(struct db_variable **varp) +{ + struct db_variable *vp; + int t; + + t = db_read_token(); + if (t == tIDENT) { + for (vp = db_vars; vp < db_evars; vp++) { + if (!strcmp(db_tok_string, vp->name)) { + *varp = vp; + return (1); + } + } + for (vp = db_regs; vp < db_eregs; vp++) { + if (!strcmp(db_tok_string, vp->name)) { + *varp = vp; + return (1); + } + } + } + db_error("Unknown variable\n"); + return (0); +} + +int +db_get_variable(db_expr_t *valuep) +{ + struct db_variable *vp; + + if (!db_find_variable(&vp)) + return (0); + + return (db_read_variable(vp, valuep)); +} + +int +db_set_variable(db_expr_t value) +{ + struct db_variable *vp; + + if (!db_find_variable(&vp)) + return (0); + + return (db_write_variable(vp, value)); +} + +int +db_read_variable(struct db_variable *vp, db_expr_t *valuep) +{ + db_varfcn_t *func = vp->fcn; + + if (func == FCN_NULL) { + *valuep = *(vp->valuep); + return (1); + } + return ((*func)(vp, valuep, DB_VAR_GET)); +} + +int +db_write_variable(struct db_variable *vp, db_expr_t value) +{ + db_varfcn_t *func = vp->fcn; + + if (func == FCN_NULL) { + *(vp->valuep) = value; + return (1); + } + return ((*func)(vp, &value, DB_VAR_SET)); +} + +void +db_set_cmd(db_expr_t dummy1, boolean_t dummy2, db_expr_t dummy3, char *dummy4) +{ + struct db_variable *vp; + db_expr_t value; + int t; + + t = db_read_token(); + if (t != tDOLLAR) { + db_error("Unknown variable\n"); + return; + } + if (!db_find_variable(&vp)) { + db_error("Unknown variable\n"); + return; + } + + t = db_read_token(); + if (t != tEQ) + db_unread_token(t); + + if (!db_expression(&value)) { + db_error("No value\n"); + return; + } + if (db_read_token() != tEOL) + db_error("?\n"); + + db_write_variable(vp, value); +} diff --git a/sys/ddb/db_variables.h b/sys/ddb/db_variables.h new file mode 100644 index 0000000..42a3bf9 --- /dev/null +++ b/sys/ddb/db_variables.h @@ -0,0 +1,63 @@ +/*- + * Mach Operating System + * Copyright (c) 1991,1990 Carnegie Mellon University + * All Rights Reserved. + * + * Permission to use, copy, modify and distribute this software and its + * documentation is hereby granted, provided that both the copyright + * notice and this permission notice appear in all copies of the + * software, derivative works or modified versions, and any portions + * thereof, and that both notices appear in supporting documentation. + * + * CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS + * CONDITION. CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND FOR + * ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE. + * + * Carnegie Mellon requests users of this software to return to + * + * Software Distribution Coordinator or Software.Distribution@CS.CMU.EDU + * School of Computer Science + * Carnegie Mellon University + * Pittsburgh PA 15213-3890 + * + * any improvements or extensions that they make and grant Carnegie the + * rights to redistribute these changes. + * + * $FreeBSD$ + */ + +/* + * Author: David B. Golub, Carnegie Mellon University + * Date: 7/90 + */ + +#ifndef _DDB_DB_VARIABLES_H_ +#define _DDB_DB_VARIABLES_H_ + +/* + * Debugger variables. + */ +struct db_variable; +typedef int db_varfcn_t(struct db_variable *vp, db_expr_t *valuep, int op); +struct db_variable { + char *name; /* Name of variable */ + db_expr_t *valuep; /* value of variable */ + /* function to call when reading/writing */ + db_varfcn_t *fcn; +#define DB_VAR_GET 0 +#define DB_VAR_SET 1 +}; +#define FCN_NULL ((db_varfcn_t *)0) + +extern struct db_variable db_regs[]; /* machine registers */ +extern struct db_variable *db_eregs; + +extern db_varfcn_t db_var_curcpu; /* DPCPU default CPU */ +extern db_varfcn_t db_var_curvnet; /* Default vnet */ +extern db_varfcn_t db_var_db_cpu; /* DPCPU active CPU */ +extern db_varfcn_t db_var_db_vnet; /* Active vnet */ + +int db_read_variable(struct db_variable *, db_expr_t *); +int db_write_variable(struct db_variable *, db_expr_t); + +#endif /* _!DDB_DB_VARIABLES_H_ */ diff --git a/sys/ddb/db_watch.c b/sys/ddb/db_watch.c new file mode 100644 index 0000000..b2be970 --- /dev/null +++ b/sys/ddb/db_watch.c @@ -0,0 +1,331 @@ +/*- + * Mach Operating System + * Copyright (c) 1991,1990 Carnegie Mellon University + * All Rights Reserved. + * + * Permission to use, copy, modify and distribute this software and its + * documentation is hereby granted, provided that both the copyright + * notice and this permission notice appear in all copies of the + * software, derivative works or modified versions, and any portions + * thereof, and that both notices appear in supporting documentation. + * + * CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS + * CONDITION. CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND FOR + * ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE. + * + * Carnegie Mellon requests users of this software to return to + * + * Software Distribution Coordinator or Software.Distribution@CS.CMU.EDU + * School of Computer Science + * Carnegie Mellon University + * Pittsburgh PA 15213-3890 + * + * any improvements or extensions that they make and grant Carnegie the + * rights to redistribute these changes. + */ +/* + * Author: Richard P. Draves, Carnegie Mellon University + * Date: 10/90 + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/param.h> +#include <sys/kernel.h> +#include <sys/lock.h> +#include <sys/proc.h> + +#include <vm/vm.h> +#include <vm/pmap.h> +#include <vm/vm_map.h> + +#include <ddb/ddb.h> +#include <ddb/db_watch.h> + +/* + * Watchpoints. + */ + +static boolean_t db_watchpoints_inserted = TRUE; + +#define NWATCHPOINTS 100 +static struct db_watchpoint db_watch_table[NWATCHPOINTS]; +static db_watchpoint_t db_next_free_watchpoint = &db_watch_table[0]; +static db_watchpoint_t db_free_watchpoints = 0; +static db_watchpoint_t db_watchpoint_list = 0; + +static db_watchpoint_t db_watchpoint_alloc(void); +static void db_watchpoint_free(db_watchpoint_t watch); +static void db_delete_watchpoint(vm_map_t map, db_addr_t addr); +#ifdef notused +static boolean_t db_find_watchpoint(vm_map_t map, db_addr_t addr, + db_regs_t *regs); +#endif +static void db_list_watchpoints(void); +static void db_set_watchpoint(vm_map_t map, db_addr_t addr, + vm_size_t size); + +static db_watchpoint_t +db_watchpoint_alloc() +{ + register db_watchpoint_t watch; + + if ((watch = db_free_watchpoints) != 0) { + db_free_watchpoints = watch->link; + return (watch); + } + if (db_next_free_watchpoint == &db_watch_table[NWATCHPOINTS]) { + db_printf("All watchpoints used.\n"); + return (0); + } + watch = db_next_free_watchpoint; + db_next_free_watchpoint++; + + return (watch); +} + +static void +db_watchpoint_free(watch) + register db_watchpoint_t watch; +{ + watch->link = db_free_watchpoints; + db_free_watchpoints = watch; +} + +static void +db_set_watchpoint(map, addr, size) + vm_map_t map; + db_addr_t addr; + vm_size_t size; +{ + register db_watchpoint_t watch; + + if (map == NULL) { + db_printf("No map.\n"); + return; + } + + /* + * Should we do anything fancy with overlapping regions? + */ + + for (watch = db_watchpoint_list; + watch != 0; + watch = watch->link) + if (db_map_equal(watch->map, map) && + (watch->loaddr == addr) && + (watch->hiaddr == addr+size)) { + db_printf("Already set.\n"); + return; + } + + watch = db_watchpoint_alloc(); + if (watch == 0) { + db_printf("Too many watchpoints.\n"); + return; + } + + watch->map = map; + watch->loaddr = addr; + watch->hiaddr = addr+size; + + watch->link = db_watchpoint_list; + db_watchpoint_list = watch; + + db_watchpoints_inserted = FALSE; +} + +static void +db_delete_watchpoint(map, addr) + vm_map_t map; + db_addr_t addr; +{ + register db_watchpoint_t watch; + register db_watchpoint_t *prev; + + for (prev = &db_watchpoint_list; + (watch = *prev) != 0; + prev = &watch->link) + if (db_map_equal(watch->map, map) && + (watch->loaddr <= addr) && + (addr < watch->hiaddr)) { + *prev = watch->link; + db_watchpoint_free(watch); + return; + } + + db_printf("Not set.\n"); +} + +static void +db_list_watchpoints() +{ + register db_watchpoint_t watch; + + if (db_watchpoint_list == 0) { + db_printf("No watchpoints set\n"); + return; + } + +#ifdef __LP64__ + db_printf(" Map Address Size\n"); +#else + db_printf(" Map Address Size\n"); +#endif + for (watch = db_watchpoint_list; + watch != 0; + watch = watch->link) +#ifdef __LP64__ + db_printf("%s%16p %16lx %lx\n", +#else + db_printf("%s%8p %8lx %lx\n", +#endif + db_map_current(watch->map) ? "*" : " ", + (void *)watch->map, (long)watch->loaddr, + (long)watch->hiaddr - (long)watch->loaddr); +} + +/* Delete watchpoint */ +/*ARGSUSED*/ +void +db_deletewatch_cmd(addr, have_addr, count, modif) + db_expr_t addr; + boolean_t have_addr; + db_expr_t count; + char * modif; +{ + db_delete_watchpoint(db_map_addr(addr), addr); +} + +/* Set watchpoint */ +/*ARGSUSED*/ +void +db_watchpoint_cmd(addr, have_addr, count, modif) + db_expr_t addr; + boolean_t have_addr; + db_expr_t count; + char * modif; +{ + vm_size_t size; + db_expr_t value; + + if (db_expression(&value)) + size = (vm_size_t) value; + else + size = 4; + db_skip_to_eol(); + + db_set_watchpoint(db_map_addr(addr), addr, size); +} + +/* + * At least one non-optional show-command must be implemented using + * DB_SHOW_COMMAND() so that db_show_cmd_set gets created. Here is one. + */ +DB_SHOW_COMMAND(watches, db_listwatch_cmd) +{ + db_list_watchpoints(); + db_md_list_watchpoints(); +} + +void +db_set_watchpoints() +{ + register db_watchpoint_t watch; + + if (!db_watchpoints_inserted) { + for (watch = db_watchpoint_list; + watch != 0; + watch = watch->link) + pmap_protect(watch->map->pmap, + trunc_page(watch->loaddr), + round_page(watch->hiaddr), + VM_PROT_READ); + + db_watchpoints_inserted = TRUE; + } +} + +void +db_clear_watchpoints() +{ + db_watchpoints_inserted = FALSE; +} + +#ifdef notused +static boolean_t +db_find_watchpoint(map, addr, regs) + vm_map_t map; + db_addr_t addr; + db_regs_t *regs; +{ + register db_watchpoint_t watch; + db_watchpoint_t found = 0; + + for (watch = db_watchpoint_list; + watch != 0; + watch = watch->link) + if (db_map_equal(watch->map, map)) { + if ((watch->loaddr <= addr) && + (addr < watch->hiaddr)) + return (TRUE); + else if ((trunc_page(watch->loaddr) <= addr) && + (addr < round_page(watch->hiaddr))) + found = watch; + } + + /* + * We didn't hit exactly on a watchpoint, but we are + * in a protected region. We want to single-step + * and then re-protect. + */ + + if (found) { + db_watchpoints_inserted = FALSE; + db_single_step(regs); + } + + return (FALSE); +} +#endif + + + +/* Delete hardware watchpoint */ +/*ARGSUSED*/ +void +db_deletehwatch_cmd(addr, have_addr, count, modif) + db_expr_t addr; + boolean_t have_addr; + db_expr_t count; + char * modif; +{ + int rc; + + if (count < 0) + count = 4; + + rc = db_md_clr_watchpoint(addr, count); + if (rc < 0) + db_printf("hardware watchpoint could not be deleted\n"); +} + +/* Set hardware watchpoint */ +/*ARGSUSED*/ +void +db_hwatchpoint_cmd(addr, have_addr, count, modif) + db_expr_t addr; + boolean_t have_addr; + db_expr_t count; + char * modif; +{ + int rc; + + if (count < 0) + count = 4; + + rc = db_md_set_watchpoint(addr, count); + if (rc < 0) + db_printf("hardware watchpoint could not be set\n"); +} diff --git a/sys/ddb/db_watch.h b/sys/ddb/db_watch.h new file mode 100644 index 0000000..7c48ce8 --- /dev/null +++ b/sys/ddb/db_watch.h @@ -0,0 +1,48 @@ +/*- + * Mach Operating System + * Copyright (c) 1991,1990 Carnegie Mellon University + * All Rights Reserved. + * + * Permission to use, copy, modify and distribute this software and its + * documentation is hereby granted, provided that both the copyright + * notice and this permission notice appear in all copies of the + * software, derivative works or modified versions, and any portions + * thereof, and that both notices appear in supporting documentation. + * + * CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS + * CONDITION. CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND FOR + * ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE. + * + * Carnegie Mellon requests users of this software to return to + * + * Software Distribution Coordinator or Software.Distribution@CS.CMU.EDU + * School of Computer Science + * Carnegie Mellon University + * Pittsburgh PA 15213-3890 + * + * any improvements or extensions that they make and grant Carnegie the + * rights to redistribute these changes. + * + * $FreeBSD$ + */ + +/* + * Author: David B. Golub, Carnegie Mellon University + * Date: 10/90 + */ + +#ifndef _DDB_DB_WATCH_H_ +#define _DDB_DB_WATCH_H_ + +/* + * Watchpoint. + */ + +typedef struct db_watchpoint { + vm_map_t map; /* in this map */ + db_addr_t loaddr; /* from this address */ + db_addr_t hiaddr; /* to this address */ + struct db_watchpoint *link; /* link in in-use or free chain */ +} *db_watchpoint_t; + +#endif /* !_DDB_DB_WATCH_H_ */ diff --git a/sys/ddb/db_write_cmd.c b/sys/ddb/db_write_cmd.c new file mode 100644 index 0000000..64d3473 --- /dev/null +++ b/sys/ddb/db_write_cmd.c @@ -0,0 +1,96 @@ +/*- + * Mach Operating System + * Copyright (c) 1991,1990 Carnegie Mellon University + * All Rights Reserved. + * + * Permission to use, copy, modify and distribute this software and its + * documentation is hereby granted, provided that both the copyright + * notice and this permission notice appear in all copies of the + * software, derivative works or modified versions, and any portions + * thereof, and that both notices appear in supporting documentation. + * + * CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS + * CONDITION. CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND FOR + * ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE. + * + * Carnegie Mellon requests users of this software to return to + * + * Software Distribution Coordinator or Software.Distribution@CS.CMU.EDU + * School of Computer Science + * Carnegie Mellon University + * Pittsburgh PA 15213-3890 + * + * any improvements or extensions that they make and grant Carnegie the + * rights to redistribute these changes. + */ +/* + * Author: David B. Golub, Carnegie Mellon University + * Date: 7/90 + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/param.h> + +#include <ddb/ddb.h> +#include <ddb/db_access.h> +#include <ddb/db_command.h> +#include <ddb/db_sym.h> + +/* + * Write to file. + */ +/*ARGSUSED*/ +void +db_write_cmd(address, have_addr, count, modif) + db_expr_t address; + boolean_t have_addr; + db_expr_t count; + char * modif; +{ + register + db_addr_t addr; + register + db_expr_t old_value; + db_expr_t new_value; + register int size; + boolean_t wrote_one = FALSE; + + addr = (db_addr_t) address; + + switch (modif[0]) { + case 'b': + size = 1; + break; + case 'h': + size = 2; + break; + case 'l': + case '\0': + size = 4; + break; + default: + db_error("Unknown size\n"); + return; + } + + while (db_expression(&new_value)) { + old_value = db_get_value(addr, size, FALSE); + db_printsym(addr, DB_STGY_ANY); + db_printf("\t\t%#8lr\t=\t%#8lr\n", (long)old_value,(long)new_value); + db_put_value(addr, size, new_value); + addr += size; + + wrote_one = TRUE; + } + + if (!wrote_one) + db_error("Nothing written.\n"); + + db_next = addr; + db_prev = addr - size; + + db_skip_to_eol(); +} + diff --git a/sys/ddb/ddb.h b/sys/ddb/ddb.h new file mode 100644 index 0000000..93bf713 --- /dev/null +++ b/sys/ddb/ddb.h @@ -0,0 +1,287 @@ +/*- + * Copyright (c) 1993, Garrett A. Wollman. + * Copyright (c) 1993, University of Vermont and State Agricultural College. + * 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. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * 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. + * + * $FreeBSD$ + */ + +/* + * Necessary declarations for the `ddb' kernel debugger. + */ + +#ifndef _DDB_DDB_H_ +#define _DDB_DDB_H_ + +#ifdef SYSCTL_DECL +SYSCTL_DECL(_debug_ddb); +#endif + +#include <machine/db_machdep.h> /* type definitions */ + +#include <sys/queue.h> /* LIST_* */ +#include <sys/kernel.h> /* SYSINIT */ + +#ifndef DB_MAXARGS +#define DB_MAXARGS 10 +#endif + +#ifndef DB_MAXLINE +#define DB_MAXLINE 120 +#endif + +#ifndef DB_MAXSCRIPTS +#define DB_MAXSCRIPTS 8 +#endif + +#ifndef DB_MAXSCRIPTNAME +#define DB_MAXSCRIPTNAME 32 +#endif + +#ifndef DB_MAXSCRIPTLEN +#define DB_MAXSCRIPTLEN 128 +#endif + +#ifndef DB_MAXSCRIPTRECURSION +#define DB_MAXSCRIPTRECURSION 3 +#endif + +#ifndef DB_CALL +#define DB_CALL db_fncall_generic +#else +int DB_CALL(db_expr_t, db_expr_t *, int, db_expr_t[]); +#endif + +/* + * There are three "command tables": + * - One for simple commands; a list of these is displayed + * by typing 'help' at the debugger prompt. + * - One for sub-commands of 'show'; to see this type 'show' + * without any arguments. + * - The last one for sub-commands of 'show all'; type 'show all' + * without any argument to get a list. + */ +struct command; +LIST_HEAD(command_table, command); +extern struct command_table db_cmd_table; +extern struct command_table db_show_table; +extern struct command_table db_show_all_table; + +/* + * Type signature for a function implementing a ddb command. + */ +typedef void db_cmdfcn_t(db_expr_t addr, boolean_t have_addr, db_expr_t count, + char *modif); + +/* + * Command table entry. + */ +struct command { + char * name; /* command name */ + db_cmdfcn_t *fcn; /* function to call */ + int flag; /* extra info: */ +#define CS_OWN 0x1 /* non-standard syntax */ +#define CS_MORE 0x2 /* standard syntax, but may have other words + * at end */ +#define CS_SET_DOT 0x100 /* set dot after command */ + struct command_table *more; /* another level of command */ + LIST_ENTRY(command) next; /* next entry in the command table */ +}; + +/* + * Arrange for the specified ddb command to be defined and + * bound to the specified function. Commands can be defined + * in modules in which case they will be available only when + * the module is loaded. + */ +#define _DB_SET(_suffix, _name, _func, list, _flag, _more) \ +static struct command __CONCAT(_name,_suffix) = { \ + .name = __STRING(_name), \ + .fcn = _func, \ + .flag = _flag, \ + .more = _more \ +}; \ +static void __CONCAT(__CONCAT(_name,_suffix),_add)(void *arg __unused) \ + { db_command_register(&list, &__CONCAT(_name,_suffix)); } \ +SYSINIT(__CONCAT(_name,_suffix), SI_SUB_KLD, SI_ORDER_ANY, \ + __CONCAT(__CONCAT(_name,_suffix),_add), NULL); \ +static void __CONCAT(__CONCAT(_name,_suffix),_del)(void *arg __unused) \ + { db_command_unregister(&list, &__CONCAT(_name,_suffix)); } \ +SYSUNINIT(__CONCAT(_name,_suffix), SI_SUB_KLD, SI_ORDER_ANY, \ + __CONCAT(__CONCAT(_name,_suffix),_del), NULL); + +/* + * Like _DB_SET but also create the function declaration which + * must be followed immediately by the body; e.g. + * _DB_FUNC(_cmd, panic, db_panic, db_cmd_table, 0, NULL) + * { + * ...panic implementation... + * } + * + * This macro is mostly used to define commands placed in one of + * the ddb command tables; see DB_COMMAND, etc. below. + */ +#define _DB_FUNC(_suffix, _name, _func, list, _flag, _more) \ +static db_cmdfcn_t _func; \ +_DB_SET(_suffix, _name, _func, list, _flag, _more); \ +static void \ +_func(db_expr_t addr, boolean_t have_addr, db_expr_t count, char *modif) + +/* common idom provided for backwards compatibility */ +#define DB_FUNC(_name, _func, list, _flag, _more) \ + _DB_FUNC(_cmd, _name, _func, list, _flag, _more) + +#define DB_COMMAND(cmd_name, func_name) \ + _DB_FUNC(_cmd, cmd_name, func_name, db_cmd_table, 0, NULL) +#define DB_ALIAS(alias_name, func_name) \ + _DB_SET(_cmd, alias_name, func_name, db_cmd_table, 0, NULL) +#define DB_SHOW_COMMAND(cmd_name, func_name) \ + _DB_FUNC(_show, cmd_name, func_name, db_show_table, 0, NULL) +#define DB_SHOW_ALIAS(alias_name, func_name) \ + _DB_SET(_show, alias_name, func_name, db_show_table, 0, NULL) +#define DB_SHOW_ALL_COMMAND(cmd_name, func_name) \ + _DB_FUNC(_show_all, cmd_name, func_name, db_show_all_table, 0, NULL) +#define DB_SHOW_ALL_ALIAS(alias_name, func_name) \ + _DB_SET(_show_all, alias_name, func_name, db_show_all_table, 0, NULL) + +extern db_expr_t db_maxoff; +extern int db_indent; +extern int db_inst_count; +extern int db_load_count; +extern int db_store_count; +extern volatile int db_pager_quit; +extern db_expr_t db_radix; +extern db_expr_t db_max_width; +extern db_expr_t db_tab_stop_width; +extern db_expr_t db_lines_per_page; + +struct thread; +struct vm_map; + +void db_check_interrupt(void); +void db_clear_watchpoints(void); +db_addr_t db_disasm(db_addr_t loc, boolean_t altfmt); + /* instruction disassembler */ +void db_error(const char *s); +int db_expression(db_expr_t *valuep); +int db_get_variable(db_expr_t *valuep); +void db_iprintf(const char *,...) __printflike(1, 2); +struct proc *db_lookup_proc(db_expr_t addr); +struct thread *db_lookup_thread(db_expr_t addr, boolean_t check_pid); +struct vm_map *db_map_addr(vm_offset_t); +boolean_t db_map_current(struct vm_map *); +boolean_t db_map_equal(struct vm_map *, struct vm_map *); +int db_md_set_watchpoint(db_expr_t addr, db_expr_t size); +int db_md_clr_watchpoint(db_expr_t addr, db_expr_t size); +void db_md_list_watchpoints(void); +void db_print_loc_and_inst(db_addr_t loc); +void db_print_thread(void); +int db_printf(const char *fmt, ...) __printflike(1, 2); +int db_read_bytes(vm_offset_t addr, size_t size, char *data); + /* machine-dependent */ +int db_readline(char *lstart, int lsize); +void db_restart_at_pc(boolean_t watchpt); +int db_set_variable(db_expr_t value); +void db_set_watchpoints(void); +void db_skip_to_eol(void); +boolean_t db_stop_at_pc(boolean_t *is_breakpoint); +#define db_strcpy strcpy +void db_trace_self(void); +int db_trace_thread(struct thread *, int); +int db_value_of_name(const char *name, db_expr_t *valuep); +int db_value_of_name_pcpu(const char *name, db_expr_t *valuep); +int db_value_of_name_vnet(const char *name, db_expr_t *valuep); +int db_write_bytes(vm_offset_t addr, size_t size, char *data); +void db_command_register(struct command_table *, struct command *); +void db_command_unregister(struct command_table *, struct command *); + +db_cmdfcn_t db_breakpoint_cmd; +db_cmdfcn_t db_capture_cmd; +db_cmdfcn_t db_continue_cmd; +db_cmdfcn_t db_delete_cmd; +db_cmdfcn_t db_deletehwatch_cmd; +db_cmdfcn_t db_deletewatch_cmd; +db_cmdfcn_t db_examine_cmd; +db_cmdfcn_t db_findstack_cmd; +db_cmdfcn_t db_hwatchpoint_cmd; +db_cmdfcn_t db_listbreak_cmd; +db_cmdfcn_t db_scripts_cmd; +db_cmdfcn_t db_print_cmd; +db_cmdfcn_t db_ps; +db_cmdfcn_t db_run_cmd; +db_cmdfcn_t db_script_cmd; +db_cmdfcn_t db_search_cmd; +db_cmdfcn_t db_set_cmd; +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; +db_cmdfcn_t db_watchpoint_cmd; +db_cmdfcn_t db_write_cmd; + +/* + * Interface between DDB and the DDB output capture facility. + */ +struct dumperinfo; +void db_capture_dump(struct dumperinfo *di); +void db_capture_enterpager(void); +void db_capture_exitpager(void); +void db_capture_write(char *buffer, u_int buflen); +void db_capture_writech(char ch); + +/* + * Interface between DDB and the script facility. + */ +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_ */ |