mdb.c revision 0a47c91c895e274dd0990009919e30e984364a8b
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2009 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
/*
* Copyright (c) 2012 by Delphix. All rights reserved.
* Copyright (c) 2012 Joyent, Inc. All rights reserved.
*/
/*
* Modular Debugger (MDB)
*
* Refer to the white paper "A Modular Debugger for Solaris" for information
* for copies of the paper and related documentation.
*
* This file provides the basic construction and destruction of the debugger's
* global state, as well as the main execution loop, mdb_run(). MDB maintains
* a stack of execution frames (mdb_frame_t's) that keep track of its current
* state, including a stack of input and output buffers, walk and memory
* garbage collect lists, and a list of commands (mdb_cmd_t's). As the
* parser consumes input, it fills in a list of commands to execute, and then
* invokes mdb_call(), below. A command consists of a dcmd, telling us
* what function to execute, and a list of arguments and other invocation-
* specific data. Each frame may have more than one command, kept on a list,
* when multiple commands are separated by | operators. New frames may be
* stacked on old ones by nested calls to mdb_run: this occurs when, for
* example, in the middle of processing one input source (such as a file
* or the terminal), we invoke a dcmd that in turn calls mdb_eval(). mdb_eval
* will construct a new frame whose input source is the string passed to
* the eval function, and then execute this frame to completion.
*/
#include <stropts.h>
#define _MDB_PRIVATE
#include <mdb/mdb_context.h>
#include <mdb/mdb_argvec.h>
#include <mdb/mdb_signal.h>
#include <mdb/mdb_macalias.h>
#include <mdb/mdb_module.h>
#include <mdb/mdb_modapi.h>
#include <mdb/mdb_string.h>
#include <mdb/mdb_callb.h>
#include <mdb/mdb_debug.h>
#include <mdb/mdb_frame.h>
#include <mdb/mdb_conf.h>
#ifdef _KMDB
#include <kmdb/kmdb_module.h>
#endif
/*
* Macro for testing if a dcmd's return status (x) indicates that we should
* abort the current loop or pipeline.
*/
extern const mdb_dcmd_t mdb_dcmd_builtins[];
extern mdb_dis_ctor_f *const mdb_dis_builtins[];
/*
* Variable discipline for toggling MDB_FL_PSYM based on the value of the
* undocumented '_' variable. Once adb(1) has been removed from the system,
* we should just remove this functionality and always disable PSYM for macros.
*/
static uintmax_t
psym_disc_get(const mdb_var_t *v)
{
int j = (MDB_NV_VALUE(v) != 0) ? 1 : 0;
if ((i ^ j) == 0)
return (MDB_NV_VALUE(v));
}
static void
{
if (value == 0)
else
MDB_NV_VALUE(v) = value;
}
/*
* Variable discipline for making <1 (most recent offset) behave properly.
*/
static uintmax_t
roff_disc_get(const mdb_var_t *v)
{
return (MDB_NV_VALUE(v));
}
static void
{
MDB_NV_VALUE(v) = value;
}
/*
* Variable discipline for exporting the representative thread.
*/
static uintmax_t
thr_disc_get(const mdb_var_t *v)
{
return (s.st_tid);
return (MDB_NV_VALUE(v));
}
const char **
{
const char **path;
char *p, *q;
int i;
static const char *empty_path[] = { NULL };
goto nomem;
while (*s == ':')
s++; /* strip leading delimiters */
if (*s == '\0') {
*newlen = 0;
return (empty_path);
}
/*
* %i embedded in path string expands to ISA.
*/
else
/*
* %p embedded in path string expands to the platform name.
*/
else
/*
* %r embedded in path string expands to root directory, or
* to the empty string if root is "/" (to avoid // in paths).
*/
/*
* %t embedded in path string expands to the target name, defaulting to
* kvm; this is so we can find mdb_kb, which is used during bootstrap.
*/
/*
* %R and %V expand to uname -r (release) and uname -v (version).
*/
else
/*
* In order to expand the buffer, we examine the format string for
* our % tokens and construct an argvec, replacing each % token
* with %s along the way. If we encounter an unknown token, we
* shift over the remaining format buffer and stick in %%.
*/
switch (q[1]) {
case 'i':
*++q = 's';
break;
case 'm':
*++q = 's';
break;
case 'p':
*++q = 's';
break;
case 'r':
*++q = 's';
break;
case 't':
*++q = 's';
break;
case 'R':
*++q = 's';
break;
case 'V':
*++q = 's';
break;
default:
*++q = '%';
}
}
/*
* We're now ready to use our printf engine to format the final string.
* Take one lap with a NULL buffer to determine how long the final
* string will be, allocate it, and format it.
*/
else
goto nomem;
/*
* Compress the string to exclude any leading delimiters.
*/
for (q = p; *q == ':'; q++)
continue;
if (q != p)
/*
* Count up the number of delimited elements. A sequence of
* consecutive delimiters is only counted once.
*/
while (*q == ':')
q++;
}
goto nomem;
}
path[i++] = q;
return (path);
warn("failed to allocate memory for path");
*newlen = 0;
return (empty_path);
}
const char **
{
char **npath;
int i, j;
continue; /* count the path elements */
if (pathlen > 0) {
}
for (j = 1; j < i; j++)
return ((const char **)npath);
}
void
{
int i;
continue; /* count the path elements */
if (i > 0) {
}
}
/*
* Convert path string "s" to canonical form, expanding any %o tokens that are
* found within the path. The old path string is specified by "path", a buffer
* of size MAXPATHLEN which is then overwritten with the new path string.
*/
static const char *
path_canon(char *path, const char *s)
{
char *p = path;
char *q = p + MAXPATHLEN - 1;
char old[MAXPATHLEN];
char c;
*q = '\0';
while (p < q && (c = *s++) != '\0') {
if (c == '%') {
if ((c = *s++) == 'o') {
p += strlen(p);
} else {
*p++ = '%';
if (p < q && c != '\0')
*p++ = c;
else
break;
}
} else
*p++ = c;
}
*p = '\0';
return (path);
}
void
mdb_set_ipath(const char *path)
{
}
void
mdb_set_lpath(const char *path)
{
#ifdef _KMDB
#endif
}
static void
prompt_update(void)
{
}
const char *
mdb_get_prompt(void)
{
if (mdb.m_promptlen == 0)
return (NULL);
else
}
int
mdb_set_prompt(const char *p)
{
if (len > MDB_PROMPTLEN) {
return (0);
}
return (1);
}
static mdb_frame_t frame0;
void
{
static char rootdir[MAXPATHLEN];
const mdb_dcmd_t *dcp;
int i;
}
(void) strdirname(rootdir);
(void) strdirname(rootdir);
(void) strdirname(rootdir);
(void) strdirname(rootdir);
} else
for (i = 0; mdb_dis_builtins[i] != NULL; i++)
(void) mdb_dis_create(mdb_dis_builtins[i]);
NULL);
/*
* The call to ctf_create that this does can in fact fail, but that's
* okay. All of the ctf functions that might use the synthetic types
* make sure that this is safe.
*/
(void) mdb_ctf_synthetics_init();
#ifdef _KMDB
#endif
}
void
mdb_destroy(void)
{
const mdb_dcmd_t *dcp;
mdb_var_t *v;
int unload_mode = MDB_MOD_SILENT;
#ifdef _KMDB
#endif
/*
* Some targets use modules during ->t_destroy, so do it first.
*/
/*
* Unload modules _before_ destroying the disassemblers since a
* module that installs a disassembler should try to clean up after
* itself.
*/
}
/*
* The real main loop of the debugger: create a new execution frame on the
* debugger stack, and while we have input available, call into the parser.
*/
int
mdb_run(void)
{
volatile int err;
mdb_frame_t f;
mdb_frame_push(&f);
/*
* This is a fresh mdb context, so ignore any pipe command we may have
* inherited from the previous frame.
*/
/*
* If a syntax error or other failure has occurred, pop all
* input buffers pushed by commands executed in this frame.
*/
while (mdb_iob_stack_size(&f.f_istk) != 0) {
}
/*
* Reset standard output and the current frame to a known,
* clean state, so we can continue execution.
*/
mdb_frame_reset(&f);
/*
* If there was an error writing to output, display a warning
* message if this is the topmost frame.
*/
mdb_warn("write failed");
/*
* If an interrupt or quit signal is reported, we may have been
* in the middle of typing or processing the command line:
* print a newline and discard everything in the parser's iob.
* Note that we do this after m_out has been reset, otherwise
* we could trigger a pipe context switch or cause a write
* to a broken pipe (in the case of a shell command) when
* writing the newline.
*/
yydiscard();
}
/*
* If we quit or abort using the output pager, reset the
* line count on standard output back to zero.
*/
/*
* If the user requested the debugger quit or abort back to
* the top, or if standard input is a pipe or mdb_eval("..."),
* then propagate the error up the debugger stack.
*/
mdb_frame_pop(&f, err);
return (err);
}
/*
* If we've returned here from a context where signals were
* blocked (e.g. a signal handler), we can now unblock them.
*/
if (err == MDB_ERR_SIGINT)
(void) mdb_signal_unblock(SIGINT);
} else
for (;;) {
(MDB_IOB_ERR | MDB_IOB_EOF)) == 0) {
mdb_iob_stack_size(&f.f_istk) == 0) {
}
(void) yyparse();
}
warn("error reading input stream %s\n",
}
}
if (mdb_iob_stack_size(&f.f_istk) == 0)
break; /* return when we're out of input */
}
mdb_frame_pop(&f, 0);
/*
* The value of '.' is a per-frame attribute, to preserve it properly
* when switching frames. But in the case of calling mdb_run()
* explicitly (such as through mdb_eval), we want to propagate the value
* of '.' to the parent.
*/
return (0);
}
/*
* The read-side of the pipe executes this service routine. We simply call
* mdb_run to create a new frame on the execution stack and run the MDB parser,
* and then propagate any error code back to the previous frame.
*/
static int
runsvc(void)
{
if (err != 0) {
mdb_err2str(err));
}
return (err);
}
/*
* Read-side pipe service routine: if we longjmp here, just return to the read
* routine because now we have more data to consume. Otherwise:
* (1) if ctx_data is non-NULL, longjmp to the write-side to produce more data;
* (2) if wriob is NULL, there is no writer but this is the first read, so we
* can just execute mdb_run() to completion on the current stack;
* (3) if (1) and (2) are false, then there is a writer and this is the first
* read, so create a co-routine context to execute mdb_run().
*/
/*ARGSUSED*/
static void
{
/*
* Save the current standard input into the pipe context, and
* reset m_in to point to the pipe. We will restore it on
* the way back in wrsvc() below.
*/
(void) runsvc();
else
mdb_warn("failed to create pipe context");
}
}
/*
* Write-side pipe service routine: if we longjmp here, just return to the
* write routine because now we have free space in the pipe buffer for writing;
* otherwise longjmp to the read-side to consume data and create space for us.
*/
/*ARGSUSED*/
static void
{
}
}
/*
* Call the current frame's mdb command. This entry point is used by the
* MDB parser to actually execute a command once it has successfully parsed
* a line of input. The command is waiting for us in the current frame.
* We loop through each command on the list, executing its dcmd with the
* appropriate argument. If the command has a successor, we know it had
* a | operator after it, and so we need to create a pipe and replace
* stdout with the pipe's output buffer.
*/
int
{
yyerror("syntax error");
} else {
mdb_err2str(err));
}
} else {
(void *)FLUSHW);
}
if (MDB_ERR_IS_FATAL(err))
break;
count = 1;
flags = 0;
} else {
}
}
/*
* If our last-command list is non-empty, destroy it. Then copy the
* current frame's cmd list to the m_lastc list and reset the frame.
*/
}
return (err == 0);
}
mdb_dot_incr(const char *op)
{
return (ndot);
}
mdb_dot_decr(const char *op)
{
return (ndot);
}
mdb_walker_lookup(const char *s)
{
const char *p = strchr(s, '`');
mdb_var_t *v;
if (p != NULL) {
char mname[MDB_NV_NAMELEN];
(void) set_errno(EMDB_NOMOD);
return (NULL);
}
mod = mdb_nv_get_cookie(v);
return (mdb_nv_get_cookie(v));
return (mdb_nv_get_cookie(mdb_nv_get_cookie(v)));
(void) set_errno(EMDB_NOWALK);
return (NULL);
}
mdb_dcmd_lookup(const char *s)
{
const char *p = strchr(s, '`');
mdb_var_t *v;
if (p != NULL) {
char mname[MDB_NV_NAMELEN];
(void) set_errno(EMDB_NOMOD);
return (NULL);
}
mod = mdb_nv_get_cookie(v);
return (mdb_nv_get_cookie(v));
return (mdb_nv_get_cookie(mdb_nv_get_cookie(v)));
(void) set_errno(EMDB_NODCMD);
return (NULL);
}
void
{
prefix = "address::";
else
prefix = "address";
prefix = "[address]::";
else
prefix = "[address]";
} else
}
}
}
static mdb_idcmd_t *
{
if (v != NULL)
return (mdb_nv_get_cookie(mdb_nv_get_cookie(v)));
return (NULL);
}
static int
{
int status;
goto done;
}
if (status == DCMD_USAGE)
done:
/*
* If standard output is a pipe and there are vcbs active, we need to
* flush standard out and the write-side of the pipe. The reasons for
* this are explained in more detail in mdb_vcb.c.
*/
}
return (status);
}
void
{
return;
}
/*
* Call an internal dcmd directly: this code is used by module API functions
* that need to execute dcmds, and by mdb_call() above.
*/
int
{
int argc;
uintmax_t i;
int status;
/*
* Update the values of dot and the most recent address and count
* to the values of our input parameters.
*/
/*
* Here the adb(1) man page lies: '9' is only set to count
* when the command is $<, not when it's $<<.
*/
if (is_exec)
/*
* We can now return if the repeat count is zero.
*/
if (count == 0)
return (DCMD_OK);
/*
* To guard against bad dcmds, we avoid passing the actual argv that
* we will use to free argument strings directly to the dcmd. Instead,
* we pass a copy that will be garbage collected automatically.
*/
if (mdb_addrvec_length(adp) != 0) {
count = 1;
}
if (DCMD_ABORTED(status))
goto done;
/*
* If the command is $< and we're not receiving input from a pipe, we
* ignore the repeat count and just return since the macro file is now
* pushed on to the input stack.
*/
goto done;
/*
* If we're going to loop, we've already executed the dcmd once,
* so clear the LOOPFIRST flag before proceeding.
*/
flags &= ~DCMD_LOOPFIRST;
for (i = 1; i < count; i++) {
if (DCMD_ABORTED(status))
goto done;
}
while (mdb_addrvec_length(adp) != 0) {
if (DCMD_ABORTED(status))
goto done;
}
done:
return (status);
}
void
mdb_intr_enable(void)
{
(void) mdb_signal_block(SIGINT);
} else
}
void
mdb_intr_disable(void)
{
}
/*
* Create an encoded string representing the internal user-modifiable
* configuration of the debugger and return a pointer to it. The string can be
* used to initialize another instance of the debugger with the same
* configuration as this one.
*/
char *
mdb_get_config(void)
{
size_t r, n = 0;
char *s = NULL;
while ((r = mdb_snprintf(s, n,
"%x;%x;%x;%x;%x;%x;%lx;%x;%x;%s;%s;%s;%s;%s",
mdb_free(s, n);
n = r + 1;
}
return (s);
}
/*
* Decode a configuration string created with mdb_get_config() and reset the
* appropriate parts of the global mdb_t accordingly.
*/
void
mdb_set_config(const char *s)
{
const char *p;
s = p + 1;
}
s = p + 1;
}
s = p + 1;
}
s = p + 1;
}
s = p + 1;
}
s = p + 1;
}
s = p + 1;
}
s = p + 1;
}
s = p + 1;
}
s = p + 1;
}
s = p + 1;
}
s = p + 1;
}
s = p + 1;
}
p = s + strlen(s);
}
mdb_get_module(void)
{
return (NULL);
return (NULL);
}