mdb_proc.c revision 657b1f3d64bcf8eaa2385dba72a6047f089433b2
/*
* 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 2006 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
/*
* User Process Target
*
* The user process target is invoked when the -u or -p command-line options
* are used, or when an ELF executable file or ELF core file is specified on
* the command-line. This target is also selected by default when no target
* options are present. In this case, it defaults the executable name to
* "a.out". If no process or core file is currently attached, the target
* semantics); reads from the virtual address space return zeroes and writes
* fail silently. The proc target itself is designed as a wrapper around the
* services provided by libproc.so: t->t_pshandle is set to the struct
* ps_prochandle pointer returned as a handle by libproc. The target also
* opens the executable file itself using the MDB GElf services, for
* interpreting the .symtab and .dynsym if no libproc handle has been
* initialized, and for handling i/o to and from the object file. Currently,
* the only ISA-dependent portions of the proc target are the $r and ::fpregs
* dcmds, the callbacks for t_next() and t_step_out(), and the list of named
* registers; these are linked in from the proc_isadep.c file for each ISA and
* called from the common code in this file.
*
* The user process target implements complete user process control using the
* facilities provided by libproc.so. The MDB execution control model and
* an overview of software event management is described in mdb_target.c. The
* proc target implements breakpoints by replacing the instruction of interest
* with a trap instruction, and then restoring the original instruction to step
* over the breakpoint. The idea of replacing program text with instructions
* that transfer control to the debugger dates back as far as 1951 [1]. When
* the target stops, we replace each breakpoint with the original instruction
* as part of the disarm operation. This means that no special processing is
* required for t_vread() because the instrumented instructions will never be
* seen by the debugger once the target stops. Some debuggers have improved
* handling a read from a breakpoint address as a special case. Although this
* improves efficiency for a source-level debugger, it runs somewhat contrary
* to the philosophy of the low-level debugger. Since we remove the
* instructions, users can apply other external debugging tools to the process
* once it has stopped (e.g. the proc(1) tools) and not be misled by MDB
* instrumentation. The tracing of faults, signals, system calls, and
* watchpoints and general process inspection is implemented directly using
* the mechanisms provided by /proc, as described originally in [2] and [3].
*
* References
*
* [1] S. Gill, "The Diagnosis Of Mistakes In Programmes on the EDSAC",
* Proceedings of the Royal Society Series A Mathematical and Physical
* Sciences, Cambridge University Press, 206(1087), May 1951, pp. 538-554.
*
* [2] T.J. Killian, "Processes as Files", Proceedings of the USENIX Association
* Summer Conference, Salt Lake City, June 1984, pp. 203-207.
*
* [3] Roger Faulkner and Ron Gomes, "The Process File System and Process
* Model in UNIX System V", Proceedings of the USENIX Association
* Winter Conference, Dallas, January 1991, pp. 243-252.
*/
#include <mdb/mdb_proc.h>
#include <mdb/mdb_disasm.h>
#include <mdb/mdb_signal.h>
#include <mdb/mdb_string.h>
#include <mdb/mdb_module.h>
#include <mdb/mdb_debug.h>
#include <mdb/mdb_conf.h>
#include <mdb/mdb_types.h>
#include <termio.h>
#include <signal.h>
#include <stdio_ext.h>
#include <stdlib.h>
#include <string.h>
static const pt_ptl_ops_t proc_lwp_ops;
static const pt_ptl_ops_t proc_tdb_ops;
static const mdb_se_ops_t proc_brkpt_ops;
static const mdb_se_ops_t proc_wapt_ops;
static void pt_activate_common(mdb_tgt_t *);
static mdb_tgt_vespec_f pt_ignore_sig;
static mdb_tgt_se_f pt_fork;
static mdb_tgt_se_f pt_exec;
static int pt_lookup_by_name_thr(mdb_tgt_t *, const char *,
psaddr_t *);
/*
* The Perror_printf() function interposes on the default, empty libproc
* definition. It will be called to report additional information on complex
* errors, such as a corrupt core file. We just pass the args to vwarn.
*/
/*ARGSUSED*/
void
{
}
/*
* Open the specified i/o backend as the a.out executable file, and attempt to
* load its standard and dynamic symbol tables. Note that if mdb_gelf_create
* succeeds, io is assigned to p_fio and is automatically held by gelf_create.
*/
static mdb_gelf_file_t *
{
return (NULL);
/*
* If we've got an _start symbol with a zero size, prime the private
* symbol table with a copy of _start with its size set to the distance
* between _mcount and _start. We do this because DevPro has shipped
* the Intel crt1.o without proper .size directives for years, which
* precludes proper identification of _start in stack traces.
*/
}
}
}
/*
* Destroy the symbol tables and GElf file object associated with p_fio. Note
* that we do not need to explicitly free p_fio: its reference count is
* automatically decremented by mdb_gelf_destroy, which will free it if needed.
*/
static void
{
}
}
}
}
/*
* Pobject_iter callback that we use to search for the presence of libthread in
* order to load the corresponding libthread_db support. We derive the
* libthread_db path dynamically based on the libthread path. If libthread is
* found, this function returns 1 (and thus Pobject_iter aborts and returns 1)
* regardless of whether it was successful in loading the libthread_db support.
* If we iterate over all objects and no libthread is found, 0 is returned.
* Since libthread_db support was then merged into libc_db, we load either
* libc_db or libthread_db, depending on which library we see first.
*/
/*ARGSUSED*/
static int
{
const mdb_tdb_ops_t *ops;
char *p, *q;
int libn;
return (0); /* no rtld_db object name; keep going */
break;
}
if (p == NULL)
return (0); /* no match; keep going */
/*
* If the 64-bit debugger is looking at a 32-bit victim, append /64 to
* the library directory name so we load the 64-bit version.
*/
(void) strcpy(q, "/64");
q += 3;
(void) strcpy(q, p);
}
p = strchr(p, '.');
q = strchr(q, '.');
(void) strcpy(q, "_db");
q += 3;
(void) strcpy(q, p);
goto err;
}
return (1); /* no changes needed */
PTL_DTOR(t);
if (PTL_CTOR(t) == -1) {
goto err;
}
(void) mdb_tgt_status(t, &t->t_status);
return (1);
err:
PTL_DTOR(t);
warn("warning: debugger will only be able to "
"examine raw LWPs\n");
}
(void) mdb_tgt_status(t, &t->t_status);
return (1);
}
/*
* Whenever the link map is consistent following an add or delete event, we ask
* libproc to update its mappings, check to see if we need to load libthread_db,
* and then update breakpoints which have been mapped or unmapped.
*/
/*ARGSUSED*/
static void
{
struct ps_prochandle *P = t->t_pshandle;
int docontinue = 1;
Pupdate_maps(P);
"support after dlclose\n");
PTL_DTOR(t);
(void) mdb_tgt_status(t, &t->t_status);
}
}
if (!mdb_tgt_sespec_activate_all(t) &&
pt->p_rtld_finished) {
/*
* We weren't able to activate the breakpoints.
* If so requested, we'll return without
* calling continue, thus throwing the user into
* the debugger.
*/
docontinue = 0;
}
}
(void) mdb_tgt_sespec_activate_all(t);
if (!mdb_tgt_sespec_activate_all(t) &&
/*
* Now that rtld has been initialized, we
* should be able to initialize all deferred
* breakpoints. If we can't, don't let the
* target continue.
*/
docontinue = 0;
}
}
}
if (docontinue)
(void) mdb_tgt_continue(t, NULL);
}
static void
{
struct ps_prochandle *P = t->t_pshandle;
int hflag = MDB_TGT_SPEC_HIDDEN;
/*
* When we grab a process, the initial setting of p_rtld_finished
* should be false if the process was just created by exec; otherwise
* we permit unscoped references to resolve because we do not know how
* far the process has proceeded through linker initialization.
*/
warn("target performed exec of %s\n",
}
} else
/*
* When we grab a process, if it is stopped by job control and part of
* the same session (i.e. same controlling tty), set MDB_FL_JOBCTL so
* we will know to bring it to the foreground when we continue it.
*/
/*
* When we grab control of a live process, set F_RDWR so that the
* target layer permits writes to the target's address space.
*/
t->t_flags |= MDB_TGT_F_RDWR;
/*
* Install event specifiers to track fork and exec activities:
*/
/*
* Attempt to instantiate the librtld_db agent and set breakpoints
* to track rtld activity. We will legitimately fail to instantiate
* the rtld_db agent if the target is statically linked.
*/
warn("failed to enable rtld_db event tracing: %s\n",
goto out;
}
} else {
warn("failed to install rtld_db preinit tracing: %s\n",
}
} else {
warn("failed to install rtld_db postinit tracing: %s\n",
}
} else {
warn("failed to install rtld_db activity tracing: %s\n",
}
}
out:
Pupdate_maps(P);
Psync(P);
/*
* If librtld_db failed to initialize due to an error or because we are
* debugging a statically linked executable, allow unscoped references.
*/
(void) mdb_tgt_sespec_activate_all(t);
}
/*ARGSUSED*/
static int
{
if (id < 0) {
(void) mdb_tgt_vespec_delete(t, id);
}
return (0);
}
static void
{
long cmd = 0;
/*
* If we are about to release the process and it is stopped on a traced
* SIGINT, breakpoint fault, single-step fault, or watchpoint, make
* sure to clear this event prior to releasing the process so that it
* does not subsequently reissue the fault and die from SIGTRAP.
*/
if (cmd != 0)
}
pt_close_aout(t);
}
PTL_DTOR(t);
}
static void
{
struct ps_prochandle *P = t->t_pshandle;
/*
* To release vfork parents, we must also wipe out any armed
* events in the parent by switching t_pshandle and calling
* se_disarm(). Do not change states or lose the matched list.
*/
}
t->t_pshandle = P;
}
}
/*ARGSUSED*/
static void
{
struct ps_prochandle *P = t->t_pshandle;
struct ps_prochandle *C;
const lwpstatus_t *csp;
char sysname[32];
int gcode;
char c;
(void) mdb_tgt_continue(t, NULL);
return; /* fork failed */
}
/*
* If forkmode is ASK and stdout is a terminal, then ask the user to
* explicitly set the fork behavior for this particular fork.
*/
if (c == 'P' || c == 'p') {
break;
} else if (c == 'C' || c == 'c') {
break;
}
}
}
/*
* The parent is now stopped on exit from its fork call. We must now
* grab the child on its return from fork in order to manipulate it.
*/
warn("failed to grab forked child process %ld: %s\n",
return; /* just stop if we failed to grab the child */
}
/*
* We may have grabbed the child and stopped it prematurely before it
* stopped on exit from fork. If so, wait up to 1 sec for it to settle.
*/
warn("forked child process %ld did not stop on exit from "
}
warn("target forked child process %ld (debugger following %s)\n",
(void) Prd_agent(C); /* initialize librtld_db */
/*
* At the time pt_fork() is called, the target event engine has already
* disarmed the specifiers on the active list, clearing out events in
* the parent process. However, this means that events that change
* the address space (e.g. breakpoints) have not been effectively
* disarmed in the child since its address space reflects the state of
* the process at the time of fork when events were armed. We must
* therefore handle this as a special case and re-invoke the disarm
* callback of each active specifier to clean out the child process.
*/
if (!is_vfork) {
}
t->t_pshandle = P; /* restore pshandle to parent */
}
/*
* If we're following the parent process, we need to temporarily change
* t_pshandle to refer to the child handle C so that we can clear out
* all the events in the child prior to releasing it below. If we are
* tracing a vfork, we also need to explicitly wait for the child to
* exec, exit, or die before we can reset and continue the parent. We
* avoid having to deal with the vfork child forking again by clearing
* PR_FORK and setting PR_RLC; if it does fork it will effectively be
* released from our control and we will continue following the parent.
*/
if (follow_parent) {
if (is_vfork) {
t->t_pshandle = C;
do {
break; /* failure or process died */
} else
t->t_pshandle = C;
}
/*
* If we are following the child, destroy any active libthread_db
* handle before we release the parent process.
*/
if (!follow_parent) {
PTL_DTOR(t);
}
/*
* Idle all events to make sure the address space and tracing flags are
* restored, and then release the process we are not tracing. If we
* are following the child of a vfork, we push the parent's pshandle
* on to a list of vfork parents to be released when we exec or exit.
*/
if (is_vfork && !follow_parent) {
vfp->p_pshandle = P;
} else {
if (!follow_parent)
}
/*
* Now that all the hard stuff is done, switch t_pshandle back to the
* process we are following and reset our events to the ACTIVE state.
* If we are following the child, reset the libthread_db handle as well
* as the rtld agent.
*/
if (follow_parent)
t->t_pshandle = P;
else {
t->t_pshandle = C;
}
(void) mdb_tgt_sespec_activate_all(t);
(void) mdb_tgt_continue(t, NULL);
}
/*ARGSUSED*/
static void
{
struct ps_prochandle *P = t->t_pshandle;
char execname[MAXPATHLEN];
char c;
(void) mdb_tgt_continue(t, NULL);
return; /* exec failed */
}
/*
* If execmode is ASK and stdout is a terminal, then ask the user to
* explicitly set the exec behavior for this particular exec. If
* Pstate() still shows PS_LOST, we are being called from pt_setrun()
* directly and therefore we must resume the terminal since it is still
* in the suspended state as far as tgt_continue() is concerned.
*/
if (c == 'F' || c == 'f') {
follow_exec = TRUE;
break;
} else if (c == 'S' || c == 's') {
follow_exec = FALSE;
break;
}
}
}
pt_release_parents(t); /* release any waiting vfork parents */
Preset_maps(P); /* libproc must delete mappings and symtabs */
pt_close_aout(t); /* free pt symbol tables and GElf file data */
/*
* If we lost control of the process across the exec and are not able
* to reopen it, we have no choice but to clear the matched event list
* and wait for the user to quit or otherwise release the process.
*/
warn("lost control of PID %d due to exec of %s executable\n",
mdb_tgt_sespec_rele(t, sep);
}
return; /* just stop if we exec'd a set-id executable */
}
}
warn("a.out symbol tables will not be available\n");
} else
}
/*
* We reset our libthread_db state here, but deliberately do NOT call
* PTL_DTOR because we do not want to call libthread_db's td_ta_delete.
* This interface is hopelessly broken in that it writes to the process
* address space (which we do not want it to do after an exec) and it
* doesn't bother deallocating any of its storage anyway.
*/
const char *argv[3];
char pidarg[16];
warn("cannot follow PID %d -- failed to resolve "
"debugger pathname for re-exec", (int)pid);
return;
}
state = mdb_get_config();
warn("failed to re-exec debugger");
return;
}
pt_post_attach(t); /* install tracing flags and activate events */
pt_activate_common(t); /* initialize librtld_db and libthread_db */
warn("loadable dcmds will not operate on non-native %d-bit "
warn("use ::release -a and then run mdb -p %d to restart "
"debugger\n", (int)pid);
}
if (follow_exec)
(void) mdb_tgt_continue(t, NULL);
}
static int
{
return (set_errno(EMDB_NOEXEC));
return (-1); /* errno is set for us */
(flags & MDB_TGT_F_RDWR);
}
if (flags & MDB_TGT_F_FORCE) {
t->t_flags |= MDB_TGT_F_FORCE;
}
return (0);
}
/*ARGSUSED*/
static int
const mdb_tgt_gregset_t *gregs)
{
if (argc != 0) {
}
mdb_printf(")\n");
return (0);
}
static int
const mdb_tgt_gregset_t *gregs)
{
#else
#endif
if (argc != 0) {
}
mdb_printf(")\n");
return (0);
}
static int
const mdb_tgt_gregset_t *gregs)
{
/*
* Use verbose format if register format is not supported.
*/
}
return (0);
}
/*ARGSUSED*/
static int
{
if (argc != 0) {
return (DCMD_USAGE);
else
}
mdb_warn("no process active\n");
return (DCMD_ERR);
}
/*
* In the universe of sparcv7, sparcv9, ia32, and amd64 this code can be
* common: <sys/procfs_isa.h> conveniently #defines R_FP to be the
* appropriate register we need to set in order to perform a stack
* traceback from a given frame address.
*/
if (flags & DCMD_ADDRSPEC) {
#ifdef __sparc
#endif /* __sparc */
mdb_warn("failed to get current register set");
return (DCMD_ERR);
}
return (DCMD_OK);
}
static int
{
}
static int
{
}
static int
{
/*
* Force printing of first register window, by setting the
* saved pc (%i7) to PC_FAKE.
*/
}
/*ARGSUSED*/
static int
{
char buf[PRSIGBUFSZ];
return (DCMD_USAGE);
if (P == NULL) {
mdb_warn("no process is currently active\n");
return (DCMD_ERR);
}
return (DCMD_OK);
}
/*ARGSUSED*/
static int
{
return (DCMD_USAGE);
if (P == NULL) {
mdb_warn("no process is currently active\n");
return (DCMD_ERR);
}
return (DCMD_OK);
}
static int
{
if (*n == nlwp - 2)
else if (*n == nlwp - 1)
else
(*n)++;
return (0);
}
/*ARGSUSED*/
static int
{
int n = 0;
if (P == NULL) {
mdb_warn("no process is currently active\n");
return (DCMD_ERR);
}
case 0:
mdb_printf("no lwps are");
break;
case 1:
mdb_printf("lwpid %d is the only lwp",
break;
default:
mdb_printf("lwpids ");
}
switch (Pstate(P)) {
case PS_DEAD:
break;
case PS_IDLE:
mdb_printf(" in idle target.\n");
break;
default:
break;
}
return (DCMD_OK);
}
/*ARGSUSED*/
static int
{
return (DCMD_USAGE);
return (DCMD_ERR);
}
return (DCMD_OK);
}
/*ARGSUSED*/
static int
{
return (DCMD_USAGE);
return (DCMD_USAGE);
mdb_warn("debugger is already attached to a %s\n",
return (DCMD_ERR);
}
mdb_warn("attach requires executable to be specified on "
"command-line (or use -p)\n");
return (DCMD_ERR);
}
if (flags & DCMD_ADDRSPEC)
else
if (t->t_pshandle == NULL) {
return (DCMD_ERR);
}
pt_post_attach(t);
}
(void) mdb_tgt_status(t, &t->t_status);
return (DCMD_OK);
}
static int
{
if (t->t_pshandle != NULL) {
char signame[SIG2STR_MAX];
else
mdb_printf("no process\n");
}
}
static int
{
int showargs = 0;
int count;
if (!(flags & DCMD_ADDRSPEC))
return (DCMD_USAGE);
NULL);
return (DCMD_USAGE);
return (DCMD_ERR);
}
#else
#endif
if (pc != 0)
(void) mdb_inc_indent(2);
if (argc == 1)
else if (showargs)
(void) mdb_eval("<.$C");
else
(void) mdb_eval("<.$C0");
(void) mdb_dec_indent(2);
return (DCMD_OK);
}
/*ARGSUSED*/
static int
{
char *prefix = "core";
char *content_str = NULL;
char *fname;
if (flags & DCMD_ADDRSPEC)
return (DCMD_USAGE);
return (DCMD_USAGE);
if (content_str != NULL &&
content == CC_CONTENT_INVALID)) {
return (DCMD_ERR);
}
if (t->t_pshandle == NULL) {
mdb_warn("no process active\n");
return (DCMD_ERR);
}
mdb_warn("couldn't dump core");
return (DCMD_ERR);
}
return (DCMD_OK);
}
/*ARGSUSED*/
static int
{
int state;
return (DCMD_USAGE);
if (t->t_pshandle != NULL &&
mdb_warn("victim process PID %d forcibly terminated\n",
pt_pre_detach(t, TRUE);
(void) mdb_tgt_status(t, &t->t_status);
} else
mdb_warn("no victim process is currently under control\n");
return (DCMD_OK);
}
/*ARGSUSED*/
static int
{
argv++;
argc--;
}
return (DCMD_USAGE);
mdb_warn("debugger is not currently attached to a process "
"or core file\n");
return (DCMD_ERR);
}
pt_pre_detach(t, TRUE);
(void) mdb_tgt_status(t, &t->t_status);
return (DCMD_OK);
}
static uintmax_t
reg_disc_get(const mdb_var_t *v)
{
mdb_tgt_t *t = MDB_NV_COOKIE(v);
mdb_tgt_reg_t r = 0;
return (r);
}
static void
{
mdb_tgt_t *t = MDB_NV_COOKIE(v);
mdb_nv_get_name(v), r) == -1)
}
static void
{
const char *desc;
case PR_REQUESTED:
mdb_printf("stopped by debugger");
break;
case PR_SIGNALLED:
break;
case PR_SYSENTRY:
mdb_printf("stopped on entry to %s system call",
break;
case PR_SYSEXIT:
mdb_printf("stopped on exit from %s system call",
break;
case PR_JOBCONTROL:
mdb_printf("stopped by job control");
break;
case PR_FAULTED:
mdb_printf("stopped on a breakpoint");
case TRAP_RWATCH:
desc = "read";
break;
case TRAP_WWATCH:
desc = "write";
break;
case TRAP_XWATCH:
desc = "execute";
break;
default:
desc = "unknown";
}
mdb_printf("stopped %s a watchpoint (%s access to %p)",
mdb_printf("stopped after a single-step");
} else {
mdb_printf("stopped on a %s fault",
}
break;
case PR_SUSPENDED:
case PR_CHECKPOINT:
mdb_printf("suspended by the kernel");
break;
default:
mdb_printf("stopped for unknown reason (%d/%d)",
}
}
/*ARGSUSED*/
static int
{
struct ps_prochandle *P = t->t_pshandle;
if (P != NULL) {
int state;
char panicbuf[128];
} else
mdb_printf("debugging core file of %s (%d-bit) "
mdb_printf("debugging core file\n");
mdb_printf("debugging %s file (%d-bit)\n",
mdb_printf("debugging defunct process\n");
} else {
mdb_printf("debugging PID %d (%d-bit)\n",
}
mdb_printf("threading model: ");
mdb_printf("raw lwps\n");
else
mdb_printf("native threads\n");
}
mdb_printf("status: ");
switch (state) {
case PS_RUN:
mdb_printf("process is running");
mdb_printf(", debugger stop directive pending");
mdb_printf("\n");
break;
case PS_STOP:
mdb_printf(", debugger stop directive pending");
mdb_printf(", sleeping in %s system call",
mdb_printf("\n");
}
break;
case PS_LOST:
mdb_printf("debugger lost control of process\n");
break;
case PS_UNDEAD:
/*FALLTHRU*/
case PS_DEAD:
/*
* We can only use pr_wstat == 0 as a test for gcore if
* an NT_PRCRED note is present; these features were
* added at the same time in Solaris 8.
*/
mdb_printf("process core file generated "
"with gcore(1)\n");
} else if (cursig != 0) {
mdb_printf("process terminated by %s (%s)",
if (coredump)
mdb_printf(" - core file dumped");
mdb_printf("\n");
} else {
mdb_printf("process terminated with exit "
}
"panicstr", &sym) == 0 &&
mdb_printf("panic message: %s",
panicbuf);
}
break;
case PS_IDLE:
mdb_printf("idle\n");
break;
default:
}
mdb_printf("debugging %s file (%d-bit)\n",
mdb_printf("status: idle\n");
}
return (DCMD_OK);
}
static int
{
const char *name;
const char *object;
return (DCMD_USAGE);
if (argc == 0) {
psaddr_t b;
return (DCMD_ERR);
}
mdb_printf("%lr\n", b);
mdb_set_dot(b);
return (DCMD_OK);
}
return (DCMD_ABORT); /* avoid repeated failure */
}
return (DCMD_OK);
}
/*ARGSUSED*/
static int
{
const pt_ptl_ops_t *ptl_ops;
return (DCMD_USAGE);
ptl_ops = &proc_tdb_ops;
ptl_ops = &proc_lwp_ops;
else
return (DCMD_USAGE);
PTL_DTOR(t);
if (ptl_ops == &proc_tdb_ops) {
thr_check, t);
}
}
(void) mdb_tgt_status(t, &t->t_status);
return (DCMD_OK);
}
static const char *
{
const char *loc;
return (loc + 1);
}
return (NULL);
}
/*ARGSUSED*/
static int
const char *nameval)
{
const char *value;
} else {
}
return (0);
}
/*ARGSUSED*/
static int
{
int i;
mdb_var_t *v;
argc -= i;
argv += i;
return (DCMD_USAGE);
return (DCMD_USAGE);
mdb_warn("no process active\n");
return (DCMD_ERR);
}
mdb_warn("-t option requires target to be running\n");
return (DCMD_ERR);
}
if (opt_t != 0) {
return (DCMD_ERR);
} else if (argc == 1) {
return (DCMD_ERR);
} else {
}
return (DCMD_OK);
}
/*
* Function to set a variable in the internal environment, which is used when
* creating new processes. Note that it is possible that 'nameval' can refer to
* read-only memory, if mdb calls putenv() on an existing value before calling
* this function. While we should avoid this situation, this function is
* designed to be robust in the face of such changes.
*/
static void
{
mdb_var_t *v;
const char *name;
} else {
/*
* nameval doesn't contain an equals character. Convert this to
* be 'nameval='.
*/
}
*equals = '\0';
if (v != NULL) {
char *old = mdb_nv_get_cookie(v);
name = mdb_nv_get_name(v);
} else {
/*
* The environment is created using MDB_NV_EXTNAME, so we must
* provide external storage for the variable names.
*/
}
*equals = '=';
if (equals)
*equals = '=';
}
/*
* Clears the internal environment.
*/
static void
{
mdb_var_t *v;
name = (char *)mdb_nv_get_name(v);
val = mdb_nv_get_cookie(v);
}
}
/*ARGSUSED*/
static int
{
char *nameval;
int alloc;
return (DCMD_USAGE);
return (DCMD_USAGE);
if (t->t_pshandle == NULL) {
mdb_warn("no process active\n");
return (DCMD_ERR);
}
/*
* If the process is in some sort of running state, warn the user that
* changes won't immediately take effect.
*/
mdb_warn("warning: changes will not take effect until process"
" is restarted\n");
}
/*
* We allow two forms of operation. The first is the usual "name=value"
* parameter. We also allow the user to specify two arguments, where
* the first is the name of the variable, and the second is the value.
*/
alloc = 0;
if (argc == 1) {
} else {
alloc = 1;
}
if (alloc)
return (DCMD_OK);
}
/*ARGSUSED*/
static int
{
mdb_var_t *v;
return (DCMD_USAGE);
return (DCMD_USAGE);
if (t->t_pshandle == NULL) {
mdb_warn("no process active\n");
return (DCMD_ERR);
}
/*
* If the process is in some sort of running state, warn the user that
* changes won't immediately take effect.
*/
mdb_warn("warning: changes will not take effect until process"
" is restarted\n");
}
if (argc == 0) {
} else {
name = (char *)mdb_nv_get_name(v);
value = mdb_nv_get_cookie(v);
}
}
return (DCMD_OK);
}
static const mdb_dcmd_t pt_dcmds[] = {
{ "attach", "?[core|pid]",
"attach to process or core file", pt_attach },
{ "gcore", "[-o prefix] [-c content]",
"produce a core file for the attached process", pt_gcore },
{ "getenv", "[-t] [name]", "display an environment variable",
{ "release", "[-a]",
"release the previously attached process", pt_detach },
{ "tls", ":symbol",
"lookup TLS data in the context of a given thread", pt_tls },
{ NULL }
};
static void
{
}
static int
{
mdb_warn("failed to iterate over threads");
return (WALK_ERR);
}
return (WALK_NEXT);
}
static int
{
}
return (WALK_DONE);
}
static const mdb_walker_t pt_walkers[] = {
{ "thread", "walk list of valid thread identifiers",
{ NULL }
};
static void
{
/*
* If we have a libproc handle and AT_BASE is set, the process or core
* is dynamically linked. We call Prd_agent() to force libproc to
* try to initialize librtld_db, and issue a warning if that fails.
*/
mdb_warn("warning: librtld_db failed to initialize; shared "
"library information will not be available\n");
}
/*
* If we have a libproc handle and libthread is loaded, attempt to load
* and initialize the corresponding libthread_db. If this fails, fall
* back to our native LWP implementation and issue a warning.
*/
/*
* If there's a global object named '_mdb_abort_info', assuming we're
* debugging mdb itself and load the developer support module.
*/
mdb_warn("warning: failed to load developer support\n");
}
}
static void
pt_activate(mdb_tgt_t *t)
{
mdb_var_t *v;
if (t->t_pshandle) {
} else
/*
* If we're examining a core file that doesn't contain program text,
* and uname(2) doesn't match the NT_UTSNAME note recorded in the
* core file, issue a warning.
*/
if (mdb_prop_postmortem == TRUE &&
!(content & CC_CONTENT_TEXT)) &&
mdb_warn("warning: core file is from %s %s %s; shared text "
"mappings may not match installed libraries\n",
}
/*
* Perform the common initialization tasks -- these are shared with
* the pt_exec() and pt_run() subroutines.
*/
/*
* Iterate through our register description list and export
* each register as a named variable.
*/
if (!(rd_flags & MDB_TGT_R_EXPORT))
continue; /* Don't export register as a variable */
(uintptr_t)t, MDB_NV_PERSIST);
}
}
static void
{
const mdb_dcmd_t *dcp;
const mdb_walker_t *wp;
mdb_var_t *v, *w;
if (!(rd_flags & MDB_TGT_R_EXPORT))
continue; /* Didn't export register as a variable */
w->v_flags &= ~MDB_NV_PERSIST;
}
}
}
}
}
static void
pt_periodic(mdb_tgt_t *t)
{
}
}
}
static void
pt_destroy(mdb_tgt_t *t)
{
if (t->t_pshandle != NULL) {
PTL_DTOR(t);
pt_pre_detach(t, TRUE);
}
pt_close_aout(t);
}
/*ARGSUSED*/
static const char *
{
return ("proc");
}
static const char *
pt_platform(mdb_tgt_t *t)
{
if (t->t_pshandle != NULL &&
return (pt->p_platform);
return (mdb_conf_platform());
}
static int
{
if (t->t_pshandle != NULL)
}
static int
{
if (t->t_pshandle == NULL)
return (MDB_TGT_MODEL_NATIVE);
case PR_MODEL_ILP32:
return (MDB_TGT_MODEL_ILP32);
case PR_MODEL_LP64:
return (MDB_TGT_MODEL_LP64);
}
return (MDB_TGT_MODEL_UNKNOWN);
}
static ssize_t
{
ssize_t n;
/*
* If no handle is open yet, reads from virtual addresses are
* allowed to succeed but return zero-filled memory.
*/
if (t->t_pshandle == NULL) {
return (nbytes);
}
return (set_errno(EMDB_NOMAP));
return (n);
}
static ssize_t
{
ssize_t n;
/*
* If no handle is open yet, writes to virtual addresses are
* allowed to succeed but do not actually modify anything.
*/
if (t->t_pshandle == NULL)
return (nbytes);
return (set_errno(EMDB_NOMAP));
return (n);
}
static ssize_t
{
}
return (nbytes);
}
static ssize_t
{
}
return (nbytes);
}
static const char *
{
const char *p;
}
return (object);
}
static int
{
const rd_loadobj_t *loadobjp;
if (object == MDB_TGT_OBJ_EVERY)
return (set_errno(EMDB_NOPROC));
/*
* If this fails, rtld_db has failed to initialize properly.
*/
return (set_errno(EMDB_NORTLD));
/*
* This will fail if the TLS block has not been allocated for the
* object that contains the TLS symbol in question.
*/
return (0);
}
typedef struct {
const char *pl_name;
} pt_lookup_t;
/*ARGSUSED*/
static int
{
&si) != 0)
return (0);
/*
* If we encounter a match with SHN_UNDEF, keep looking for a
* better match. Return the first match with SHN_UNDEF set if no
* better match is found.
*/
}
return (0);
}
/*
* Note that if the symbol's st_shndx is SHN_UNDEF we don't have the
* TLS offset anyway, so adding in the tlsbase would be worthless.
*/
&base) != 0)
return (-1); /* errno is set for us */
}
return (1);
}
/*
* Lookup the symbol with a thread context so that we can adjust TLS symbols
* to get the values as they would appear in the context of the given thread.
*/
static int
{
struct ps_prochandle *P = t->t_pshandle;
uint_t i;
const rd_loadobj_t *aout_lop;
if (P != NULL) {
if (object == MDB_TGT_OBJ_EVERY) {
return (-1); /* errno is set for us */
} else {
/*
* This can fail either due to an invalid lmid or
* an invalid object. To determine which is
* faulty, we test the lmid against known valid
* lmids and then see if using a wild-card lmid
* improves ths situation.
*/
if (lmid != PR_LMID_EVERY &&
lmid != LM_ID_BASE &&
lmid != LM_ID_LDSO &&
!= NULL)
return (set_errno(EMDB_NOLMID));
else
return (set_errno(EMDB_NOOBJ));
}
return (-1); /* errno is set for us */
}
return (0);
}
/*
* If libproc doesn't have the symbols for rtld, we're cooked --
* mdb doesn't have those symbols either.
*/
if (object == MDB_TGT_OBJ_RTLD)
return (set_errno(EMDB_NOSYM));
if (status != 0) {
if (P != NULL &&
return (set_errno(EMDB_NOSYM));
else
return (-1); /* errno set from lookup_by_file */
}
goto found;
}
goto local_found;
}
goto local_found;
}
return (set_errno(EMDB_NOSYM));
P != NULL &&
/*
* If the symbol has type TLS, libproc should have found the symbol
* if it exists and has been allocated.
*/
return (0);
}
static int
{
}
static int
{
struct ps_prochandle *P = t->t_pshandle;
rd_plt_info_t rpi = { 0 };
const char *pltsym;
int match, i;
int gstc = 0; /* number of valid gsts[] entries */
/*
* Fill in our array of symbol table pointers with the private symbol
* table, static symbol table, and dynamic symbol table if applicable.
* These are done in order of precedence so that if we match and
* MDB_TGT_SYM_EXACT is set, we need not look any further.
*/
/*
* Loop through our array attempting to match the address. If we match
* and we're in exact mode, we're done. Otherwise save the symbol in
* the local sym variable if it is closer than our previous match.
* We explicitly watch for zero-valued symbols since DevPro insists
* on storing __fsr_init_value's value as the symbol value instead
* of storing it in a constant integer.
*/
for (i = 0; i < gstc; i++) {
continue;
if (flags & MDB_TGT_SYM_EXACT) {
goto found;
}
}
}
/*
* If we have no libproc handle active, we're done: fail if gst is
* NULL; otherwise copy out our best symbol and skip to the end.
* We also skip to found if gst is the private symbol table: we
* want this to always take precedence over PLT re-vectoring.
*/
return (set_errno(EMDB_NOSYMADDR));
goto found;
}
/*
* Check to see if the address is in a PLT: if it is, use librtld_db to
* attempt to resolve the PLT entry. If the entry is bound, reset addr
* to the bound address, add a special prefix to the caller's buf,
* forget our previous guess, and then continue using the new addr.
* If the entry is not bound, copy the corresponding symbol name into
* buf and return a fake symbol for the given address.
*/
const rd_loadobj_t *rlp;
size_t n;
if (n > nbytes) {
nbytes = 0;
} else {
buf += n;
nbytes -= n;
}
} else {
return (0);
}
}
/*
* Ask libproc to convert the address to the closest symbol for us.
* Once we get the closest symbol, we perform the EXACT match or
* smart-mode or absolute distance check ourself:
*/
if (flags & MDB_TGT_SYM_EXACT)
else
if (match) {
goto found;
}
}
/*
* If we get here, Plookup_by_addr has failed us. If we have no
* previous best symbol (gst == NULL), we've failed completely.
* Otherwise we copy out that symbol and continue on to 'found'.
*/
return (set_errno(EMDB_NOSYMADDR));
/*
* Once we've found something, copy the final name into the caller's
* buffer and prefix it with the mapping name if appropriate.
*/
} else {
}
} else {
}
}
return (0);
}
static int
const prsyminfo_t *sip)
{
}
static int
{
return (0);
}
static int
{
}
return (0);
}
static int
{
if (t->t_pshandle != NULL) {
if (object != MDB_TGT_OBJ_EVERY) {
return (set_errno(EMDB_NOOBJ));
return (0);
return (0);
}
}
return (set_errno(EMDB_NOLMID));
return (set_errno(EMDB_NOOBJ));
if (which == MDB_TGT_SYMTAB)
else
}
return (0);
}
static const mdb_map_t *
{
struct ps_prochandle *P = t->t_pshandle;
char name[MAXPATHLEN];
} else {
}
} else {
MDB_TGT_MAPSZ - 1);
}
return (mp);
}
/*ARGSUSED*/
static int
{
}
static int
{
if (t->t_pshandle != NULL) {
return (0);
}
return (set_errno(EMDB_NOPROC));
}
static int
{
/*
* If we have a libproc handle, we can just call Pobject_iter to
* iterate over its list of load object information.
*/
if (t->t_pshandle != NULL) {
return (0);
}
/*
* If we're examining an executable or other ELF file but we have no
* libproc handle, fake up some information based on DT_NEEDED entries.
*/
size_t i;
return (0);
return (0);
}
}
return (0);
}
return (set_errno(EMDB_NOPROC));
}
static const mdb_map_t *
{
if (t->t_pshandle == NULL) {
(void) set_errno(EMDB_NOPROC);
return (NULL);
}
(void) set_errno(EMDB_NOMAP);
return (NULL);
}
}
static const mdb_map_t *
{
if (t->t_pshandle == NULL) {
(void) set_errno(EMDB_NOPROC);
return (NULL);
}
(void) set_errno(EMDB_NOOBJ);
return (NULL);
}
}
static ctf_file_t *
{
if (t->t_pshandle == NULL) {
(void) set_errno(EMDB_NOPROC);
return (NULL);
}
(void) set_errno(EMDB_NOOBJ);
return (NULL);
}
return (ret);
}
static ctf_file_t *
{
if (t->t_pshandle == NULL) {
(void) set_errno(EMDB_NOPROC);
return (NULL);
}
(void) set_errno(EMDB_NOOBJ);
return (NULL);
}
return (ret);
}
static int
{
int state;
if (t->t_pshandle == NULL) {
return (0);
}
case PS_RUN:
break;
case PS_STOP:
break;
case PS_LOST:
break;
case PS_UNDEAD:
break;
case PS_DEAD:
break;
case PS_IDLE:
break;
default:
}
if (t->t_flags & MDB_TGT_F_BUSY)
return (0);
}
static void
{
int fd;
} else
}
/*
* The Pcreate_callback() function interposes on the default, empty libproc
* definition. It will be called following a fork of a new child process by
* Pcreate() below, but before the exec of the new process image. We use this
* callback to optionally redirect stdin and stdout and reset the dispositions
* of SIGPIPE and SIGQUIT from SIG_IGN back to SIG_DFL.
*/
/*ARGSUSED*/
void
Pcreate_callback(struct ps_prochandle *P)
{
}
static int
{
struct ps_prochandle *P;
char execname[MAXPATHLEN];
const char **pargv;
int pargc = 0;
int i, perr;
char **penv;
mdb_var_t *v;
warn("run requires executable to be specified on "
"command-line\n");
}
for (i = 0; i < argc; i++) {
}
else
}
/*
* Since Pcreate() uses execvp() and "." may not be present in $PATH,
* we must manually prepend "./" when the executable is a simple name.
*/
} else {
}
UM_SLEEP);
penv[i] = mdb_nv_get_cookie(v);
if (P == NULL) {
}
if (t->t_pshandle != NULL) {
pt_pre_detach(t, TRUE);
}
t->t_pshandle = P;
pt_post_attach(t);
(void) mdb_tgt_status(t, &t->t_status);
return (0);
}
/*
* Forward a signal to the victim process in order to force it to stop or die.
* Refer to the comments above pt_setrun(), below, for more info.
*/
/*ARGSUSED*/
static void
{
struct ps_prochandle *P = t->t_pshandle;
long ctl[2];
}
/*
* If we're job control stopped and our DSTOP is pending, the
* victim will never see our signal, so undo the kill() and
* then send SIGCONT the victim to kick it out of the job
* control stop and force our DSTOP to take effect.
*/
}
}
}
/*
* Common code for step and continue: if no victim process has been created,
* call pt_run() to create one. Then set the victim running, clearing any
* pending fault. One special case is that if the victim was previously
* stopped on reception of SIGINT, we know that SIGINT was traced and the user
* requested the victim to stop, so clear this signal before continuing.
* For all other traced signals, the signal will be delivered on continue.
*
* Once the victim process is running, we wait for it to stop on an event of
* interest. Although libproc provides the basic primitive to wait for the
* victim, we must be careful in our handling of signals. We want to allow the
* user to issue a SIGINT or SIGQUIT using the designated terminal control
* character (typically ^C and ^\), and have these signals stop the target and
* return control to the debugger if the signals are traced. There are three
* cases to be considered in our implementation:
*
* (1) If the debugger and victim are in the same process group, both receive
* the signal from the terminal driver. The debugger returns from Pwait() with
* errno = EINTR, so we want to loop back and continue waiting until the victim
* stops on receipt of its SIGINT or SIGQUIT.
*
* (2) If the debugger and victim are in different process groups, and the
* victim is a member of the foreground process group, it will receive the
* signal from the terminal driver and the debugger will not. As such, we
* will remain blocked in Pwait() until the victim stops on its signal.
*
* (3) If the debugger and victim are in different process groups, and the
* debugger is a member of the foreground process group, it will receive the
* signal from the terminal driver, and the victim will not. The debugger
* returns from Pwait() with errno = EINTR, so we need to forward the signal
* to the victim process directly and then Pwait() again for it to stop.
*
* We can observe that all three cases are handled by simply calling Pwait()
* repeatedly if it fails with EINTR, and forwarding SIGINT and SIGQUIT to
* the victim if it is in a different process group, using pt_sigfwd() above.
*
* An additional complication is that the process may not be able to field
* the signal if it is currently stopped by job control. In this case, we
* also DSTOP the process, and then send it a SIGCONT to wake it up from
* job control and force it to re-enter stop() under the control of /proc.
*
* Finally, we would like to allow the user to suspend the process using the
* terminal suspend character (typically ^Z) if both are in the same session.
* We again employ pt_sigfwd() to forward SIGTSTP to the victim, wait for it to
* stop from job control, and then capture it using /proc. Once the process
* has stopped, normal SIGTSTP processing is restored and the user can issue
* another ^Z in order to suspend the debugger and return to the parent shell.
*/
static int
{
struct ps_prochandle *P = t->t_pshandle;
const lwpstatus_t *psp;
int error = 0;
int pgid = -1;
return (-1); /* errno is set for us */
P = t->t_pshandle;
else
goto out;
}
/*
* If we attached to a job stopped background process in the same
* session, make its pgid the foreground process group before running
* it. Ignore SIGTTOU while doing this to avoid being suspended.
*/
}
goto out;
}
/*
* If the process is stopped on job control, resume its process group
* by sending it a SIGCONT if we are in the same session. Otherwise
* we have no choice but to wait for someone else to foreground it.
*/
warn("process is still suspended by job control ...\n");
}
/*
* Wait for the process to stop. As described above, we loop around if
* we are interrupted (EINTR). If we lose control, attempt to re-open
* the process, or call pt_exec() if that fails to handle a re-exec.
* If the process dies (ENOENT) or Pwait() fails, break out of the loop.
*/
while (Pwait(P, 0) == -1) {
if (Preopen(P) == 0)
continue; /* Pwait() again */
else
warn("failed to wait for event");
break;
}
}
/*
* If we changed the foreground process group, restore the old pgid
* while ignoring SIGTTOU so we are not accidentally suspended.
*/
if (old_pgid != -1) {
}
/*
* If we're now stopped on exit from a successful exec, release any
* vfork parents and clean out their address space before returning
* to tgt_continue() and perturbing the list of armed event specs.
* If we're stopped for any other reason, just update the mappings.
*/
switch (Pstate(P)) {
case PS_STOP:
else
Pupdate_maps(P);
break;
case PS_UNDEAD:
case PS_LOST:
break;
}
out:
}
static int
{
}
static int
{
}
static int
{
return (0);
}
return (set_errno(EMDB_BADSIGNUM));
}
static int
{
struct ps_prochandle *P = t->t_pshandle;
}
return (set_errno(EMDB_NOPROC));
}
static void
{
}
/*ARGSUSED*/
static char *
{
char name[32];
int sysnum;
else
return (buf);
}
/*ARGSUSED*/
static int
{
}
static const mdb_se_ops_t proc_sysenter_ops = {
pt_sysenter_ctor, /* se_ctor */
pt_sysenter_dtor, /* se_dtor */
pt_sysenter_info, /* se_info */
no_se_secmp, /* se_secmp */
no_se_vecmp, /* se_vecmp */
no_se_arm, /* se_arm */
no_se_disarm, /* se_disarm */
no_se_cont, /* se_cont */
pt_sysenter_match /* se_match */
};
static int
{
struct ps_prochandle *P = t->t_pshandle;
}
return (set_errno(EMDB_NOPROC));
}
static void
{
}
/*ARGSUSED*/
static char *
{
char name[32];
int sysnum;
else
return (buf);
}
/*ARGSUSED*/
static int
{
}
static const mdb_se_ops_t proc_sysexit_ops = {
pt_sysexit_ctor, /* se_ctor */
pt_sysexit_dtor, /* se_dtor */
pt_sysexit_info, /* se_info */
no_se_secmp, /* se_secmp */
no_se_vecmp, /* se_vecmp */
no_se_arm, /* se_arm */
no_se_disarm, /* se_disarm */
no_se_cont, /* se_cont */
pt_sysexit_match /* se_match */
};
static int
{
struct ps_prochandle *P = t->t_pshandle;
}
return (set_errno(EMDB_NOPROC));
}
static void
{
}
/*ARGSUSED*/
static char *
{
char name[SIG2STR_MAX];
int signum;
else
return (buf);
}
/*ARGSUSED*/
static int
{
}
static const mdb_se_ops_t proc_signal_ops = {
pt_signal_ctor, /* se_ctor */
pt_signal_dtor, /* se_dtor */
pt_signal_info, /* se_info */
no_se_secmp, /* se_secmp */
no_se_vecmp, /* se_vecmp */
no_se_arm, /* se_arm */
no_se_disarm, /* se_disarm */
no_se_cont, /* se_cont */
pt_signal_match /* se_match */
};
static int
{
struct ps_prochandle *P = t->t_pshandle;
}
return (set_errno(EMDB_NOPROC));
}
static void
{
}
/*ARGSUSED*/
static char *
{
char name[32];
int fltnum;
else
return (buf);
}
/*ARGSUSED*/
static int
{
}
static const mdb_se_ops_t proc_fault_ops = {
pt_fault_ctor, /* se_ctor */
pt_fault_dtor, /* se_dtor */
pt_fault_info, /* se_info */
no_se_secmp, /* se_secmp */
no_se_vecmp, /* se_vecmp */
no_se_arm, /* se_arm */
no_se_disarm, /* se_disarm */
no_se_cont, /* se_cont */
pt_fault_match /* se_match */
};
/*
* Callback for pt_ignore() dcmd above: for each VID, determine if it
* corresponds to a vespec that traces the specified signal, and delete it.
*/
/*ARGSUSED*/
static int
{
(void) mdb_tgt_vespec_delete(t, vid);
return (0);
}
static int
{
GElf_Sym s;
return (set_errno(EMDB_NOPROC));
if (!pt->p_rtld_finished &&
return (set_errno(EMDB_NOSYM));
NULL) == -1) {
!pt->p_rtld_finished))) {
warn("breakpoint %s activation failed",
pta->pta_symbol);
}
return (-1); /* errno is set for us */
}
}
#ifdef __sparc
return (set_errno(EMDB_BPALIGN));
#endif
return (set_errno(EMDB_NOMAP));
return (0);
}
/*ARGSUSED*/
static void
{
}
/*ARGSUSED*/
static char *
{
pta->pta_symbol);
} else {
}
} else {
}
return (buf);
}
static int
{
}
}
/*ARGSUSED*/
static int
{
return (0); /* fail if one is symbolic, other is an explicit address */
}
static int
{
}
/*
* In order to disarm a breakpoint, we replace the trap instruction at ptb_addr
* with the saved instruction. However, if we have stopped after a successful
* exec(2), we do not want to restore ptb_instr because the address space has
* now been replaced with the text of a different executable, and so restoring
* the saved instruction would be incorrect. The exec itself has effectively
* removed all breakpoint trap instructions for us, so we can just return.
*/
static int
{
return (0); /* do not restore saved instruction */
}
/*
* Determine whether the specified sespec is an armed watchpoint that overlaps
* with the given breakpoint and has the given flags set. We use this to find
* conflicts with breakpoints, below.
*/
static int
{
}
/*
* We step over breakpoints using Pxecbkpt() in libproc. If a conflicting
* watchpoint is present, we must temporarily remove it before stepping over
* the breakpoint so we do not immediately re-trigger the watchpoint. We know
* the watchpoint has already triggered on our trap instruction as part of
* fetching it. Before we return, we must re-install any disabled watchpoints.
*/
static int
{
int status = -1;
int error;
/*
* If the PC no longer matches our original address, then the user has
* changed it while we have been stopped. In this case, it no longer
* makes any sense to continue over this breakpoint. We return as if we
* continued normally.
*/
}
}
}
return (status);
}
/*ARGSUSED*/
static int
{
}
static const mdb_se_ops_t proc_brkpt_ops = {
pt_brkpt_ctor, /* se_ctor */
pt_brkpt_dtor, /* se_dtor */
pt_brkpt_info, /* se_info */
pt_brkpt_secmp, /* se_secmp */
pt_brkpt_vecmp, /* se_vecmp */
pt_brkpt_arm, /* se_arm */
pt_brkpt_disarm, /* se_disarm */
pt_brkpt_cont, /* se_cont */
pt_brkpt_match /* se_match */
};
static int
{
return (set_errno(EMDB_NOPROC));
return (0);
}
/*ARGSUSED*/
static void
{
}
/*ARGSUSED*/
static char *
{
char desc[24];
desc[0] = '\0';
case WA_READ:
break;
case WA_WRITE:
break;
case WA_EXEC:
break;
default:
}
return (buf);
}
/*ARGSUSED*/
static int
{
}
/*ARGSUSED*/
static int
{
}
static int
{
}
static int
{
}
/*
* Determine whether the specified sespec is an armed breakpoint at the
* given %pc. We use this to find conflicts with watchpoints below.
*/
static int
{
}
/*
* We step over watchpoints using Pxecwapt() in libproc. If a conflicting
* breakpoint is present, we must temporarily disarm it before stepping
* over the watchpoint so we do not immediately re-trigger the breakpoint.
* This is similar to the case handled in pt_brkpt_cont(), above.
*/
static int
{
int status = -1;
int error;
/*
* If the PC no longer matches our original address, then the user has
* changed it while we have been stopped. In this case, it no longer
* makes any sense to continue over this instruction. We return as if
* we continued normally.
*/
break;
}
}
}
return (status);
}
/*ARGSUSED*/
static int
{
}
static const mdb_se_ops_t proc_wapt_ops = {
pt_wapt_ctor, /* se_ctor */
pt_wapt_dtor, /* se_dtor */
pt_wapt_info, /* se_info */
pt_wapt_secmp, /* se_secmp */
pt_wapt_vecmp, /* se_vecmp */
pt_wapt_arm, /* se_arm */
pt_wapt_disarm, /* se_disarm */
pt_wapt_cont, /* se_cont */
pt_wapt_match /* se_match */
};
static void
{
}
static int
{
}
static int
{
if (sym[0] == '`') {
(void) set_errno(EMDB_NOOBJ);
return (0);
}
(void) set_errno(EMDB_NOSYM);
return (0);
}
}
static int
{
return (0); /* no range overlap */
return (0); /* no range overlap */
}
static void
{
}
static int
{
if (wflags & MDB_TGT_WA_R)
if (wflags & MDB_TGT_WA_W)
if (wflags & MDB_TGT_WA_X)
goto dup;
}
goto dup;
}
dup:
(void) set_errno(EMDB_WPDUP);
return (0);
}
static int
{
(void) set_errno(EMDB_BADSYSNUM);
return (0);
}
}
static int
{
(void) set_errno(EMDB_BADSYSNUM);
return (0);
}
}
static int
{
(void) set_errno(EMDB_BADSIGNUM);
return (0);
}
}
static int
{
(void) set_errno(EMDB_BADFLTNUM);
return (0);
}
}
static int
{
mdb_var_t *v;
if (t->t_pshandle == NULL)
return (set_errno(EMDB_NOPROC));
if (!MDB_TGT_R_IS_FP(rd_flags)) {
mdb_tgt_reg_t r = 0;
/*
* If we are debugging on 32-bit SPARC, the globals and
* outs can have 32 upper bits hiding in the xregs.
*/
/* gcc doesn't like >= R_G0 because R_G0 == 0 */
}
}
#endif /* __sparc && _ILP32 */
/*
* Avoid sign-extension by casting: recall that procfs
* defines prgreg_t as a long or int and our native
* register handling uses uint64_t's.
*/
return (0);
}
return (-1);
} else
}
return (set_errno(EMDB_BADREG));
}
static int
{
mdb_var_t *v;
if (t->t_pshandle == NULL)
return (set_errno(EMDB_NOPROC));
if (!MDB_TGT_R_IS_FP(rd_flags)) {
/*
* If we are debugging on 32-bit SPARC, the globals and
* outs can have 32 upper bits stored in the xregs.
*/
if (is_g) {
} else if (is_o) {
}
return (-1);
}
#endif /* __sparc && _ILP32 */
}
return (-1);
} else
}
return (set_errno(EMDB_BADREG));
}
static int
{
if (!psp->pstk_gotpc)
return (0); /* skip initial zeroed frames */
}
static int
{
if (t->t_pshandle != NULL) {
return (0);
}
return (set_errno(EMDB_NOPROC));
}
static int
{
if (t->t_pshandle != NULL) {
return (0);
}
return (set_errno(EMDB_NOPROC));
}
static const mdb_tgt_ops_t proc_ops = {
pt_setflags, /* t_setflags */
(int (*)()) mdb_tgt_notsup, /* t_setcontext */
pt_activate, /* t_activate */
pt_deactivate, /* t_deactivate */
pt_periodic, /* t_periodic */
pt_destroy, /* t_destroy */
pt_name, /* t_name */
(const char *(*)()) mdb_conf_isa, /* t_isa */
pt_platform, /* t_platform */
pt_uname, /* t_uname */
pt_dmodel, /* t_dmodel */
pt_vread, /* t_vread */
pt_vwrite, /* t_vwrite */
pt_fread, /* t_fread */
pt_fwrite, /* t_fwrite */
(int (*)()) mdb_tgt_notsup, /* t_vtop */
pt_lookup_by_name, /* t_lookup_by_name */
pt_lookup_by_addr, /* t_lookup_by_addr */
pt_symbol_iter, /* t_symbol_iter */
pt_mapping_iter, /* t_mapping_iter */
pt_object_iter, /* t_object_iter */
pt_addr_to_map, /* t_addr_to_map */
pt_name_to_map, /* t_name_to_map */
pt_addr_to_ctf, /* t_addr_to_ctf */
pt_name_to_ctf, /* t_name_to_ctf */
pt_status, /* t_status */
pt_run, /* t_run */
pt_step, /* t_step */
pt_step_out, /* t_step_out */
(int (*)()) mdb_tgt_notsup, /* t_step_branch */
pt_next, /* t_next */
pt_continue, /* t_cont */
pt_signal, /* t_signal */
pt_add_vbrkpt, /* t_add_vbrkpt */
pt_add_sbrkpt, /* t_add_sbrkpt */
(int (*)()) mdb_tgt_null, /* t_add_pwapt */
pt_add_vwapt, /* t_add_vwapt */
(int (*)()) mdb_tgt_null, /* t_add_iowapt */
pt_add_sysenter, /* t_add_sysenter */
pt_add_sysexit, /* t_add_sysexit */
pt_add_signal, /* t_add_signal */
pt_add_fault, /* t_add_fault */
pt_getareg, /* t_getareg */
pt_putareg, /* t_putareg */
pt_stack_iter, /* t_stack_iter */
pt_auxv /* t_auxv */
};
/*
* Utility function for converting libproc errno values to mdb error values
* for the ptl calls below. Currently, we only need to convert ENOENT to
* EMDB_NOTHREAD to produce a more useful error message for the user.
*/
static int
{
return (set_errno(EMDB_NOTHREAD));
return (error);
}
/*ARGSUSED*/
static mdb_tgt_tid_t
{
if (t->t_pshandle != NULL)
return (set_errno(EMDB_NOPROC));
}
static int
{
return (0);
}
/*ARGSUSED*/
static int
{
if (t->t_pshandle != NULL)
return (set_errno(EMDB_NOPROC));
}
/*ARGSUSED*/
static int
{
if (t->t_pshandle != NULL) {
}
return (set_errno(EMDB_NOPROC));
}
/*ARGSUSED*/
static int
{
if (t->t_pshandle != NULL) {
}
return (set_errno(EMDB_NOPROC));
}
#ifdef __sparc
/*ARGSUSED*/
static int
{
if (t->t_pshandle != NULL) {
}
return (set_errno(EMDB_NOPROC));
}
/*ARGSUSED*/
static int
const prxregset_t *xregs)
{
if (t->t_pshandle != NULL) {
}
return (set_errno(EMDB_NOPROC));
}
#endif /* __sparc */
/*ARGSUSED*/
static int
{
if (t->t_pshandle != NULL) {
}
return (set_errno(EMDB_NOPROC));
}
/*ARGSUSED*/
static int
const prfpregset_t *fpregs)
{
if (t->t_pshandle != NULL) {
}
return (set_errno(EMDB_NOPROC));
}
static const pt_ptl_ops_t proc_lwp_ops = {
(int (*)()) mdb_tgt_nop,
(void (*)()) mdb_tgt_nop,
#ifdef __sparc
#endif
};
static int
pt_tdb_ctor(mdb_tgt_t *t)
{
return (0);
}
static void
{
}
static mdb_tgt_tid_t
{
if (t->t_pshandle == NULL)
return (set_errno(EMDB_NOPROC));
}
static int
{
return (0);
}
static int
{
int err;
if (t->t_pshandle == NULL)
return (set_errno(EMDB_NOPROC));
return (0);
}
static int
{
if (t->t_pshandle == NULL)
return (set_errno(EMDB_NOPROC));
return (0);
}
static int
{
if (t->t_pshandle == NULL)
return (set_errno(EMDB_NOPROC));
return (0);
}
#ifdef __sparc
static int
{
if (t->t_pshandle == NULL)
return (set_errno(EMDB_NOPROC));
return (0);
}
static int
const prxregset_t *xregs)
{
if (t->t_pshandle == NULL)
return (set_errno(EMDB_NOPROC));
return (0);
}
#endif /* __sparc */
static int
{
if (t->t_pshandle == NULL)
return (set_errno(EMDB_NOPROC));
return (0);
}
static int
const prfpregset_t *fpregs)
{
if (t->t_pshandle == NULL)
return (set_errno(EMDB_NOPROC));
return (0);
}
static const pt_ptl_ops_t proc_tdb_ops = {
#ifdef __sparc
#endif
};
static ssize_t
{
struct ps_prochandle *P = t->t_pshandle;
int auxn = 0;
auxn++;
}
if (auxn == 0)
return (nbytes);
}
static ssize_t
{
}
return (cbytes);
if (cbytes == 0)
return (nbytes);
}
static ssize_t
{
return (sizeof (GElf_Ehdr));
return (nbytes);
}
static int
{
(*lspp)++;
return (0);
}
static ssize_t
{
int nlwp = 0;
return (sizeof (lwpstatus_t) * nlwp);
if (nlwp == 0)
return (nbytes);
}
static ssize_t
{
return (sizeof (struct ps_prochandle *));
return (nbytes);
}
static ssize_t
{
return (sizeof (psinfo_t));
return (nbytes);
}
static ssize_t
{
return (sizeof (pstatus_t));
return (nbytes);
}
static ssize_t
{
return (sizeof (struct utsname));
return (nbytes);
}
int
{
const mdb_tgt_regdesc_t *rdp;
char execname[MAXPATHLEN];
int perr;
int state;
int i;
if (argc > 2) {
}
if (t->t_flags & MDB_TGT_F_RDWR)
else
if (t->t_flags & MDB_TGT_F_FORCE)
if (t->t_flags & MDB_TGT_F_NOSTOP)
/*
* If no core file name was specified, but the file ./core is present,
* infer that we want to debug it. I find this behavior confusing,
* so we only do this when precise adb(1) compatibility is required.
*/
/*
* For compatibility with adb(1), the special name "-" may be used
* to suppress the loading of the executable or core file.
*/
/*
* If a core file or pid was specified, attempt to grab it now using
* proc_arg_grab(); otherwise we'll create a fresh process later.
*/
goto err;
}
t->t_pshandle == NULL)
if (t->t_pshandle != NULL)
/*
* Make sure we'll have enough file descriptors to handle a target
* has many many mappings.
*/
}
/*
* If we don't have an executable path or the executable path is the
* attempt to derive the executable path using Pexecname(). We need
* to do this in the /proc case in order to open the executable for
*/
GElf_Sym s;
}
mdb_warn("warning: failed to infer pathname to "
"executable; symbol table will not be available\n");
}
/*
* Attempt to open the executable file. We only want this operation
* to actually cause the constructor to abort if the executable file
* name was given explicitly. If we defaulted to PT_EXEC_PATH or
* derived the executable using Pexecname, then we want to continue
* along with p_fio and p_file set to NULL.
*/
goto err;
}
/*
* Now create an ELF file from the input file, if we have one. Again,
* only abort the constructor if the name was given explicitly.
*/
goto err;
/*
* If we've successfully opened an ELF file, select the appropriate
* disassembler based on the ELF header.
*/
else
/*
* Add each register described in the target ISA register description
* list to our hash table of register descriptions and then add any
* appropriate ISA-specific floating-point register descriptions.
*/
}
pt_addfpregs(t);
/*
* Certain important /proc structures may be of interest to mdb
* modules and their dcmds. Export these using the xdata interface:
*/
(void) mdb_tgt_xdata_insert(t, "auxv",
"procfs auxv_t array", pt_xd_auxv);
(void) mdb_tgt_xdata_insert(t, "cred",
"procfs prcred_t structure", pt_xd_cred);
(void) mdb_tgt_xdata_insert(t, "ehdr",
"executable file GElf_Ehdr structure", pt_xd_ehdr);
(void) mdb_tgt_xdata_insert(t, "lwpstatus",
"procfs lwpstatus_t array", pt_xd_lwpstatus);
(void) mdb_tgt_xdata_insert(t, "pshandle",
"libproc proc service API handle", pt_xd_pshandle);
(void) mdb_tgt_xdata_insert(t, "psinfo",
"procfs psinfo_t structure", pt_xd_psinfo);
(void) mdb_tgt_xdata_insert(t, "pstatus",
"procfs pstatus_t structure", pt_xd_pstatus);
(void) mdb_tgt_xdata_insert(t, "utsname",
"utsname structure", pt_xd_utsname);
/*
* Force a status update now so that we fill in t_status with the
* latest information based on any successful grab.
*/
(void) mdb_tgt_status(t, &t->t_status);
/*
* If we're not examining a core file, trace SIGINT and all signals
* that cause the process to dump core as part of our initialization.
*/
}
/*
* If we've grabbed a live process, establish our initial breakpoints
* and librtld_db agent so we can track rtld activity. If FL_VCREATE
* is set, this process was created by a previous instantiation of
* the debugger, so reset pr_flags to kill it; otherwise we attached
* to an already running process. Pgrab() has already set the PR_RLC
* flag appropriately based on whether the process was stopped when we
* attached.
*/
} else {
}
pt_post_attach(t);
}
/*
* Initialize a local copy of the environment, which can be modified
* before running the program.
*/
/*
* If adb(1) compatibility mode is on, then print the appropriate
* greeting message if we have grabbed a core file.
*/
char signame[SIG2STR_MAX];
mdb_printf("core file = %s -- program ``%s'' on platform %s\n",
}
return (0);
err:
pt_destroy(t);
return (-1);
}