static int dt_pid_has_jump_table(struct ps_prochandle *P, dtrace_hdl_t *dtp, uint8_t *text, fasttrap_probe_spec_t *ftp, const GElf_Sym *symp) { ulong_t i; int size; #if defined(sun) pid_t pid = Pstatus(P)->pr_pid; char dmodel = Pstatus(P)->pr_dmodel; #else pid_t pid = proc_getpid(P); #if __i386__ char dmodel = PR_MODEL_ILP32; #elif __amd64__ char dmodel = PR_MODEL_LP64; #endif #endif /* * Take a pass through the function looking for a register-dependant * jmp instruction. This could be a jump table so we have to be * ultra conservative. */ for (i = 0; i < ftp->ftps_size; i += size) { size = dt_instr_size(&text[i], dtp, pid, symp->st_value + i, dmodel); /* * Assume the worst if we hit an illegal instruction. */ if (size <= 0) { dt_dprintf("error at %#lx (assuming jump table)\n", i); return (1); } #ifdef notyet /* * Register-dependant jmp instructions start with a 0xff byte * and have the modrm.reg field set to 4. They can have an * optional REX prefix on the 64-bit ISA. */ if ((text[i] == 0xff && DT_MODRM_REG(text[i + 1]) == 4) || (dmodel == PR_MODEL_LP64 && (text[i] & 0xf0) == 0x40 && text[i + 1] == 0xff && DT_MODRM_REG(text[i + 2]) == 4)) { dt_dprintf("found a suspected jump table at %s:%lx\n", ftp->ftps_func, i); return (1); } #endif } return (0); }
/*ARGSUSED*/ static void prochandler(struct ps_prochandle *P, const char *msg, void *arg) { #if !defined(__APPLE__) const psinfo_t *prp = Ppsinfo(P); int pid = Pstatus(P)->pr_pid; char name[SIG2STR_MAX]; #else #define SIG2STR_MAX 32 /* Not referenced so long as prp just below is NULL. */ #define proc_signame(x,y,z) "Unknown" /* Not referenced so long as prp just below is NULL. */ typedef struct psinfo { int pr_wstat; } psinfo_t; const psinfo_t *prp = NULL; int pid = Pstatus(P)->pr_pid; #endif /* __APPLE__ */ if (msg != NULL) { notice("pid %d: %s\n", pid, msg); return; } switch (Pstate(P)) { case PS_UNDEAD: /* * Ideally we would like to always report pr_wstat here, but it * isn't possible given current /proc semantics. If we grabbed * the process, Ppsinfo() will either fail or return a zeroed * psinfo_t depending on how far the parent is in reaping it. * When /proc provides a stable pr_wstat in the status file, * this code can be improved by examining this new pr_wstat. */ if (prp != NULL && WIFSIGNALED(prp->pr_wstat)) { notice("pid %d terminated by %s\n", pid, proc_signame(WTERMSIG(prp->pr_wstat), name, sizeof (name))); } else if (prp != NULL && WEXITSTATUS(prp->pr_wstat) != 0) { notice("pid %d exited with status %d\n", pid, WEXITSTATUS(prp->pr_wstat)); } else { notice("pid %d has exited\n", pid); } g_exited = 1; break; case PS_LOST: #if !defined(__APPLE__) notice("pid %d exec'd a set-id or unobservable program\n", pid); #else notice("pid %d has exited\n", pid); #endif g_exited = 1; break; } }
/* * Wait for a stopped process to be set running again by some other debugger. * This is typically not required by /proc-based debuggers, since the usual * model is that one debugger controls one victim. But DTrace, as usual, has * its own needs: the stop() action assumes that prun(1) or some other tool * will be applied to resume the victim process. This could be solved by * adding a PCWRUN directive to /proc, but that seems like overkill unless * other debuggers end up needing this functionality, so we implement a cheap * equivalent to PCWRUN using the set of existing kernel mechanisms. * * Our intent is really not just to wait for the victim to run, but rather to * wait for it to run and then stop again for a reason other than the current * PR_REQUESTED stop. Since PCWSTOP/Pstopstatus() can be applied repeatedly * to a stopped process and will return the same result without affecting the * victim, we can just perform these operations repeatedly until Pstate() * changes, the representative LWP ID changes, or the stop timestamp advances. * dt_gproc_control() will then rediscover the new state and continue as usual. * When the process is still stopped in the same exact state, we sleep for a * brief interval before waiting again so as not to spin consuming CPU cycles. */ static void dt_proc_waitrun(dt_proc_t *dpr) { struct ps_prochandle *P = dpr->dpr_proc; const lwpstatus_t *psp = &Pstatus(P)->pr_lwp; int krflag = psp->pr_flags & (PR_KLC | PR_RLC); timestruc_t tstamp = psp->pr_tstamp; lwpid_t lwpid = psp->pr_lwpid; const long wstop = PCWSTOP; int pfd = Pctlfd(P); assert(DT_MUTEX_HELD(&dpr->dpr_lock)); assert(psp->pr_flags & PR_STOPPED); assert(Pstate(P) == PS_STOP); /* * While we are waiting for the victim to run, clear PR_KLC and PR_RLC * so that if the libdtrace client is killed, the victim stays stopped. * dt_proc_destroy() will also observe this and perform PRELEASE_HANG. */ (void) Punsetflags(P, krflag); Psync(P); (void) pthread_mutex_unlock(&dpr->dpr_lock); while (!dpr->dpr_quit) { if (write(pfd, &wstop, sizeof (wstop)) == -1 && errno == EINTR) continue; /* check dpr_quit and continue waiting */ (void) pthread_mutex_lock(&dpr->dpr_lock); (void) Pstopstatus(P, PCNULL, 0); psp = &Pstatus(P)->pr_lwp; /* * If we've reached a new state, found a new representative, or * the stop timestamp has changed, restore PR_KLC/PR_RLC to its * original setting and then return with dpr_lock held. */ if (Pstate(P) != PS_STOP || psp->pr_lwpid != lwpid || bcmp(&psp->pr_tstamp, &tstamp, sizeof (tstamp)) != 0) { (void) Psetflags(P, krflag); Psync(P); return; } (void) pthread_mutex_unlock(&dpr->dpr_lock); (void) poll(NULL, 0, MILLISEC / 2); } (void) pthread_mutex_lock(&dpr->dpr_lock); }
/* * statvfs() system call -- executed by subject process */ int pr_statvfs(struct ps_prochandle *Pr, const char *path, statvfs_t *buf) { sysret_t rval; /* return value from statvfs() */ argdes_t argd[2]; /* arg descriptors for statvfs() */ argdes_t *adp = &argd[0]; /* first argument */ int error; #ifdef _LP64 statvfs32_t statvfs32; #endif /* _LP64 */ if (Pr == NULL) /* no subject process */ return (statvfs(path, buf)); adp->arg_value = 0; adp->arg_object = (void *)path; adp->arg_type = AT_BYREF; adp->arg_inout = AI_INPUT; adp->arg_size = strlen(path)+1; adp++; /* move to buffer argument */ adp->arg_value = 0; adp->arg_type = AT_BYREF; adp->arg_inout = AI_OUTPUT; #ifdef _LP64 if (Pstatus(Pr)->pr_dmodel == PR_MODEL_ILP32) { adp->arg_object = &statvfs32; adp->arg_size = sizeof (statvfs32); } else { adp->arg_object = buf; adp->arg_size = sizeof (*buf); } #else /* _LP64 */ adp->arg_object = buf; adp->arg_size = sizeof (*buf); #endif /* _LP64 */ error = Psyscall(Pr, &rval, SYS_statvfs, 2, &argd[0]); if (error) { errno = (error > 0)? error : ENOSYS; return (-1); } #ifdef _LP64 if (Pstatus(Pr)->pr_dmodel == PR_MODEL_ILP32) statvfs_32_to_n(&statvfs32, buf); #endif /* _LP64 */ return (0); }
dt_proc_t * dt_proc_lookup(dtrace_hdl_t *dtp, struct ps_prochandle *P, int remove) { dt_proc_hash_t *dph = dtp->dt_procs; #if defined(sun) pid_t pid = Pstatus(P)->pr_pid; #else pid_t pid = proc_getpid(P); #endif dt_proc_t *dpr, **dpp = &dph->dph_hash[pid & (dph->dph_hashlen - 1)]; for (dpr = *dpp; dpr != NULL; dpr = dpr->dpr_hash) { if (dpr->dpr_pid == pid) break; else dpp = &dpr->dpr_hash; } assert(dpr != NULL); assert(dpr->dpr_proc == P); if (remove) *dpp = dpr->dpr_hash; /* remove from pid hash chain */ return (dpr); }
/* ARGSUSED */ static int lwpstop(int *lwpcount, const lwpstatus_t *status, const lwpsinfo_t *info) { struct ps_lwphandle *L; int gcode; if (proc_lwp_in_set(lwps, info->pr_lwpid)) { /* * There is a race between the callback from the iterator and * grabbing of the lwp. If the lwp has already exited, Lgrab * will return the error code G_NOPROC. It's not a real error, * only if there is no lwp matching the specification. */ if ((L = Lgrab(P, info->pr_lwpid, &gcode)) != NULL) { (void) Ldstop(L); Lfree(L); if (*lwpcount >= 0) (*lwpcount)++; } else if (gcode != G_NOPROC) { (void) fprintf(stderr, "%s: cannot control %d/%d: %s\n", command, (int)Pstatus(P)->pr_pid, (int)info->pr_lwpid, Lgrab_error(gcode)); *lwpcount = -1; } } return (0); }
struct ps_prochandle * dt_proc_create(dtrace_hdl_t *dtp, const char *file, char *const *argv) { dt_proc_hash_t *dph = dtp->dt_procs; dt_proc_t *dpr; int err; if ((dpr = dt_zalloc(dtp, sizeof (dt_proc_t))) == NULL) return (NULL); /* errno is set for us */ (void) pthread_mutex_init(&dpr->dpr_lock, NULL); (void) pthread_cond_init(&dpr->dpr_cv, NULL); if ((dpr->dpr_proc = Pcreate(file, argv, &err, NULL, 0, dtp->dt_arch)) == NULL) { return (dt_proc_error(dtp, dpr, "failed to execute %s: %s\n", file, Pcreate_error(err))); } dpr->dpr_hdl = dtp; dpr->dpr_pid = Pstatus(dpr->dpr_proc)->pr_pid; (void) Punsetflags(dpr->dpr_proc, PR_RLC); (void) Psetflags(dpr->dpr_proc, PR_KLC); if (dt_proc_create_thread(dtp, dpr, dtp->dt_prcmode) != 0) return (NULL); /* dt_proc_error() has been called for us */ dpr->dpr_hash = dph->dph_hash[dpr->dpr_pid & (dph->dph_hashlen - 1)]; dph->dph_hash[dpr->dpr_pid & (dph->dph_hashlen - 1)] = dpr; dt_list_prepend(&dph->dph_lrulist, dpr); dt_dprintf("created pid %d\n", (int)dpr->dpr_pid); dpr->dpr_refs++; return (dpr->dpr_proc); }
/*ARGSUSED*/ static int python_object_iter(void *cd, const prmap_t *pmp, const char *obj) { char path[PATH_MAX]; char *name; char *s1, *s2; struct ps_prochandle *Pr = cd; name = strstr(obj, "/libpython"); if (name) { (void) strcpy(path, obj); if (Pstatus(Pr)->pr_dmodel != PR_MODEL_NATIVE) { s1 = name; s2 = path + (s1 - obj); (void) strcpy(s2, "/64"); s2 += 3; (void) strcpy(s2, s1); } s1 = strstr(obj, ".so"); s2 = strstr(path, ".so"); (void) strcpy(s2, "_db"); s2 += 3; (void) strcpy(s2, s1); if ((pydb_dlhdl = dlopen(path, RTLD_LAZY|RTLD_GLOBAL)) != NULL) return (1); } return (0); }
static void dt_proc_bpmatch(dtrace_hdl_t *dtp, dt_proc_t *dpr) { const lwpstatus_t *psp = &Pstatus(dpr->dpr_proc)->pr_lwp; dt_bkpt_t *dbp; assert(DT_MUTEX_HELD(&dpr->dpr_lock)); for (dbp = dt_list_next(&dpr->dpr_bps); dbp != NULL; dbp = dt_list_next(dbp)) { if (psp->pr_reg[R_PC] == dbp->dbp_addr) break; } if (dbp == NULL) { dt_dprintf("pid %d: spurious breakpoint wakeup for %lx\n", (int)dpr->dpr_pid, (ulong_t)psp->pr_reg[R_PC]); return; } dt_dprintf("pid %d: hit breakpoint at %lx (%lu)\n", (int)dpr->dpr_pid, (ulong_t)dbp->dbp_addr, ++dbp->dbp_hits); dbp->dbp_func(dtp, dpr, dbp->dbp_data); (void) Pxecbkpt(dpr->dpr_proc, dbp->dbp_instr); }
/* * We cannot rely on pr_instr, because if we hit a breakpoint or the user has * artifically modified memory, it will no longer be correct. */ static uint32_t pt_read_instr(mdb_tgt_t *t) { const lwpstatus_t *psp = &Pstatus(t->t_pshandle)->pr_lwp; uint32_t ret = 0; (void) mdb_tgt_vread(t, &ret, sizeof (ret), psp->pr_reg[R_PC]); return (ret); }
static int xmapping_iter(struct ps_prochandle *Pr, proc_xmap_f *func, void *cd, int doswap) { char mapname[PATH_MAX]; int mapfd, nmap, i, rc; struct stat st; prxmap_t *prmapp, *pmp; ssize_t n; (void) snprintf(mapname, sizeof (mapname), "/proc/%d/xmap", (int)Pstatus(Pr)->pr_pid); if ((mapfd = open(mapname, O_RDONLY)) < 0 || fstat(mapfd, &st) != 0) { if (mapfd >= 0) (void) close(mapfd); return (perr(mapname)); } nmap = st.st_size / sizeof (prxmap_t); nmap *= 2; again: prmapp = malloc((nmap + 1) * sizeof (prxmap_t)); if ((n = pread(mapfd, prmapp, (nmap + 1) * sizeof (prxmap_t), 0)) < 0) { (void) close(mapfd); free(prmapp); return (perr("read xmap")); } if (nmap < n / sizeof (prxmap_t)) { free(prmapp); nmap *= 2; goto again; } (void) close(mapfd); nmap = n / sizeof (prxmap_t); for (i = 0, pmp = prmapp; i < nmap; i++, pmp++) { if ((rc = func(cd, pmp, NULL, i == nmap - 1, doswap)) != 0) { free(prmapp); return (rc); } } /* * Mark the last element. */ if (map_count > 0) maps[map_count - 1].md_last = B_TRUE; free(prmapp); return (0); }
struct ps_prochandle * dtrace_proc_create(dtrace_hdl_t *dtp, const char *file, char *const *argv) { dt_ident_t *idp = dt_idhash_lookup(dtp->dt_macros, "target"); struct ps_prochandle *P = dt_proc_create(dtp, file, argv); if (P != NULL && idp != NULL && idp->di_id == 0) idp->di_id = Pstatus(P)->pr_pid; /* $target = created pid */ return (P); }
/*ARGSUSED*/ static int look_xmap(void *data, const prxmap_t *pmp, const char *object_name, int last, int doswap) { struct totals *t = data; const pstatus_t *Psp = Pstatus(Pr); char mname[PATH_MAX]; char *lname = NULL; char *ln; /* * If the mapping is not anon or not part of the heap, make a name * for it. We don't want to report the heap as a.out's data. */ if (!(pmp->pr_mflags & MA_ANON) || pmp->pr_vaddr + pmp->pr_size <= Psp->pr_brkbase || pmp->pr_vaddr >= Psp->pr_brkbase + Psp->pr_brksize) { lname = make_name(Pr, lflag, pmp->pr_vaddr, pmp->pr_mapname, mname, sizeof (mname)); } if (lname != NULL) { if ((ln = strrchr(lname, '/')) != NULL) lname = ln + 1; } else if ((pmp->pr_mflags & MA_ANON) || Pstate(Pr) == PS_DEAD) { lname = anon_name(mname, Psp, stacks, nstacks, pmp->pr_vaddr, pmp->pr_size, pmp->pr_mflags, pmp->pr_shmid, NULL); } (void) printf("%.*lX", addr_width, (ulong_t)pmp->pr_vaddr); printK(ROUNDUP_KB(pmp->pr_size), size_width); printK(pmp->pr_rss * (pmp->pr_pagesize / KILOBYTE), size_width); printK(ANON(pmp) * (pmp->pr_pagesize / KILOBYTE), size_width); printK(pmp->pr_locked * (pmp->pr_pagesize / KILOBYTE), size_width); (void) printf(lname ? " %4s %-6s %s\n" : " %4s %s\n", pagesize(pmp), mflags(pmp->pr_mflags), lname); t->total_size += ROUNDUP_KB(pmp->pr_size); t->total_rss += pmp->pr_rss * (pmp->pr_pagesize / KILOBYTE); t->total_anon += ANON(pmp) * (pmp->pr_pagesize / KILOBYTE); t->total_locked += (pmp->pr_locked * (pmp->pr_pagesize / KILOBYTE)); return (0); }
/* * Create labels for non-anon, non-heap mappings */ char * make_name(struct ps_prochandle *Pr, int lflag, uintptr_t addr, const char *mapname, char *buf, size_t bufsz) { const pstatus_t *Psp = Pstatus(Pr); struct stat statb; char path[PATH_MAX]; int len; if (lflag || Pstate(Pr) == PS_DEAD) { if (Pobjname(Pr, addr, buf, bufsz) != NULL) return (buf); } else { if (Pobjname_resolved(Pr, addr, buf, bufsz) != NULL) { /* Verify that the path exists */ if ((len = resolvepath(buf, buf, bufsz)) > 0) { buf[len] = '\0'; return (buf); } } } if (Pstate(Pr) == PS_DEAD || *mapname == '\0') return (NULL); /* first see if we can find a path via /proc */ (void) proc_snprintf(path, sizeof (path), "/proc/%d/path/%s", (int)Psp->pr_pid, mapname); len = readlink(path, buf, bufsz - 1); if (len >= 0) { buf[len] = '\0'; return (buf); } /* fall back to object information reported by /proc */ (void) proc_snprintf(path, sizeof (path), "/proc/%d/object/%s", (int)Psp->pr_pid, mapname); if (stat(path, &statb) == 0) { dev_t dev = statb.st_dev; ino_t ino = statb.st_ino; (void) snprintf(buf, bufsz, "dev:%lu,%lu ino:%lu", (ulong_t)major(dev), (ulong_t)minor(dev), ino); return (buf); } return (NULL); }
static int dt_pid_create_usdt_probes(dtrace_probedesc_t *pdp, dtrace_hdl_t *dtp, dt_pcb_t *pcb, dt_proc_t *dpr) { struct ps_prochandle *P = dpr->dpr_proc; int ret = 0; assert(DT_MUTEX_HELD(&dpr->dpr_lock)); #if defined(sun) (void) Pupdate_maps(P); if (Pobject_iter(P, dt_pid_usdt_mapping, P) != 0) { ret = -1; (void) dt_pid_error(dtp, pcb, dpr, NULL, D_PROC_USDT, "failed to instantiate probes for pid %d: %s", #if defined(sun) (int)Pstatus(P)->pr_pid, strerror(errno)); #else (int)proc_getpid(P), strerror(errno)); #endif }
static int ptime_pid(const char *pidstr) { struct ps_prochandle *Pr; pid_t pid; int gret; if ((Pr = proc_arg_grab(pidstr, PR_ARG_PIDS, Fflag | PGRAB_RDONLY, &gret)) == NULL) { (void) fprintf(stderr, "%s: cannot examine %s: %s\n", command, pidstr, Pgrab_error(gret)); return (1); } pid = Pstatus(Pr)->pr_pid; (void) sprintf(procname, "%d", (int)pid); /* for perr() */ (void) look(pid); Prelease(Pr, 0); return (0); }
static int rmapping_iter(struct ps_prochandle *Pr, proc_map_f *func, void *cd) { char mapname[PATH_MAX]; int mapfd, nmap, i, rc; struct stat st; prmap_t *prmapp, *pmp; ssize_t n; (void) snprintf(mapname, sizeof (mapname), "/proc/%d/rmap", (int)Pstatus(Pr)->pr_pid); if ((mapfd = open(mapname, O_RDONLY)) < 0 || fstat(mapfd, &st) != 0) { if (mapfd >= 0) (void) close(mapfd); return (perr(mapname)); } nmap = st.st_size / sizeof (prmap_t); prmapp = malloc((nmap + 1) * sizeof (prmap_t)); if ((n = pread(mapfd, prmapp, (nmap + 1) * sizeof (prmap_t), 0L)) < 0) { (void) close(mapfd); free(prmapp); return (perr("read rmap")); } (void) close(mapfd); nmap = n / sizeof (prmap_t); for (i = 0, pmp = prmapp; i < nmap; i++, pmp++) { if ((rc = func(cd, pmp, NULL)) != 0) { free(prmapp); return (rc); } } free(prmapp); return (0); }
/* * Common code for enabling events associated with the run-time linker after * attaching to a process or after a victim process completes an exec(2). */ static void dt_proc_attach(dt_proc_t *dpr, int exec) { const pstatus_t *psp = Pstatus(dpr->dpr_proc); rd_err_e err; GElf_Sym sym; assert(DT_MUTEX_HELD(&dpr->dpr_lock)); if (exec) { if (psp->pr_lwp.pr_errno != 0) return; /* exec failed: nothing needs to be done */ dt_proc_bpdestroy(dpr, B_FALSE); Preset_maps(dpr->dpr_proc); } if ((dpr->dpr_rtld = Prd_agent(dpr->dpr_proc)) != NULL && (err = rd_event_enable(dpr->dpr_rtld, B_TRUE)) == RD_OK) { dt_proc_rdwatch(dpr, RD_PREINIT, "RD_PREINIT"); dt_proc_rdwatch(dpr, RD_POSTINIT, "RD_POSTINIT"); dt_proc_rdwatch(dpr, RD_DLACTIVITY, "RD_DLACTIVITY"); } else { dt_dprintf("pid %d: failed to enable rtld events: %s\n", (int)dpr->dpr_pid, dpr->dpr_rtld ? rd_errstr(err) : "rtld_db agent initialization failed"); } Pupdate_maps(dpr->dpr_proc); if (Pxlookup_by_name(dpr->dpr_proc, LM_ID_BASE, "a.out", "main", &sym, NULL) == 0) { (void) dt_proc_bpcreate(dpr, (uintptr_t)sym.st_value, (dt_bkpt_f *)dt_proc_bpmain, "a.out`main"); } else { dt_dprintf("pid %d: failed to find a.out`main: %s\n", (int)dpr->dpr_pid, strerror(errno)); } }
static void dt_proc_bpmatch(dtrace_hdl_t *dtp, dt_proc_t *dpr) { #ifdef illumos const lwpstatus_t *psp = &Pstatus(dpr->dpr_proc)->pr_lwp; #else unsigned long pc; #endif dt_bkpt_t *dbp; assert(DT_MUTEX_HELD(&dpr->dpr_lock)); #ifndef illumos proc_regget(dpr->dpr_proc, REG_PC, &pc); proc_bkptregadj(&pc); #endif for (dbp = dt_list_next(&dpr->dpr_bps); dbp != NULL; dbp = dt_list_next(dbp)) { #ifdef illumos if (psp->pr_reg[R_PC] == dbp->dbp_addr) break; #else if (pc == dbp->dbp_addr) break; #endif } if (dbp == NULL) { dt_dprintf("pid %d: spurious breakpoint wakeup for %lx\n", #ifdef illumos (int)dpr->dpr_pid, (ulong_t)psp->pr_reg[R_PC]); #else (int)dpr->dpr_pid, pc); #endif return; }
static int dt_pid_create_usdt_probes(dtrace_probedesc_t *pdp, dtrace_hdl_t *dtp, dt_pcb_t *pcb, dt_proc_t *dpr) { struct ps_prochandle *P = dpr->dpr_proc; int ret = 0; assert(MUTEX_HELD(&dpr->dpr_lock)); (void) Pupdate_maps(P); if (Pobject_iter(P, dt_pid_usdt_mapping, P) != 0) { ret = -1; (void) dt_pid_error(dtp, pcb, dpr, NULL, D_PROC_USDT, "failed to instantiate probes for pid %d: %s", (int)Pstatus(P)->pr_pid, strerror(errno)); } /* * Put the module name in its canonical form. */ (void) dt_pid_fix_mod(pdp, P); return (ret); }
int main(int argc, char **argv) { int rflag = 0, sflag = 0, xflag = 0, Fflag = 0; int errflg = 0, Sflag = 0; int rc = 0; int opt; const char *bar8 = "-------"; const char *bar16 = "----------"; const char *bar; struct rlimit rlim; struct stat64 statbuf; char buf[128]; int mapfd; int prg_gflags = PGRAB_RDONLY; int prr_flags = 0; boolean_t use_agent_lwp = B_FALSE; if ((command = strrchr(argv[0], '/')) != NULL) command++; else command = argv[0]; while ((opt = getopt(argc, argv, "arsxSlLFA:")) != EOF) { switch (opt) { case 'a': /* include shared mappings in -[xS] */ aflag = 1; break; case 'r': /* show reserved mappings */ rflag = 1; break; case 's': /* show hardware page sizes */ sflag = 1; break; case 'S': /* show swap reservations */ Sflag = 1; break; case 'x': /* show extended mappings */ xflag = 1; break; case 'l': /* show unresolved link map names */ lflag = 1; break; case 'L': /* show lgroup information */ Lflag = 1; use_agent_lwp = B_TRUE; break; case 'F': /* force grabbing (no O_EXCL) */ Fflag = PGRAB_FORCE; break; case 'A': if (parse_addr_range(optarg, &start_addr, &end_addr) != 0) errflg++; break; default: errflg = 1; break; } } argc -= optind; argv += optind; if ((Sflag && (xflag || rflag || sflag)) || (xflag && rflag) || (aflag && (!xflag && !Sflag)) || (Lflag && (xflag || Sflag))) { errflg = 1; } if (errflg || argc <= 0) { (void) fprintf(stderr, "usage:\t%s [-rslF] [-A start[,end]] { pid | core } ...\n", command); (void) fprintf(stderr, "\t\t(report process address maps)\n"); (void) fprintf(stderr, "\t%s -L [-rslF] [-A start[,end]] pid ...\n", command); (void) fprintf(stderr, "\t\t(report process address maps lgroups mappings)\n"); (void) fprintf(stderr, "\t%s -x [-aslF] [-A start[,end]] pid ...\n", command); (void) fprintf(stderr, "\t\t(show resident/anon/locked mapping details)\n"); (void) fprintf(stderr, "\t%s -S [-alF] [-A start[,end]] { pid | core } ...\n", command); (void) fprintf(stderr, "\t\t(show swap reservations)\n\n"); (void) fprintf(stderr, "\t-a: include shared mappings in -[xS] summary\n"); (void) fprintf(stderr, "\t-r: show reserved address maps\n"); (void) fprintf(stderr, "\t-s: show hardware page sizes\n"); (void) fprintf(stderr, "\t-l: show unresolved dynamic linker map names\n"); (void) fprintf(stderr, "\t-F: force grabbing of the target process\n"); (void) fprintf(stderr, "\t-L: show lgroup mappings\n"); (void) fprintf(stderr, "\t-A start,end: limit output to the specified range\n"); return (2); } /* * Make sure we'll have enough file descriptors to handle a target * that has many many mappings. */ if (getrlimit(RLIMIT_NOFILE, &rlim) == 0) { rlim.rlim_cur = rlim.rlim_max; (void) setrlimit(RLIMIT_NOFILE, &rlim); (void) enable_extended_FILE_stdio(-1, -1); } /* * The implementation of -L option creates an agent LWP in the target * process address space. The agent LWP issues meminfo(2) system calls * on behalf of the target process. If we are interrupted prematurely, * the target process remains in the stopped state with the agent still * attached to it. To prevent such situation we catch signals from * terminal and terminate gracefully. */ if (use_agent_lwp) { /* * Buffer output to stdout, stderr while process is grabbed. * Prevents infamous deadlocks due to pmap `pgrep xterm` and * other variants. */ (void) proc_initstdio(); prg_gflags = PGRAB_RETAIN | Fflag; prr_flags = PRELEASE_RETAIN; if (sigset(SIGHUP, SIG_IGN) == SIG_DFL) (void) sigset(SIGHUP, intr); if (sigset(SIGINT, SIG_IGN) == SIG_DFL) (void) sigset(SIGINT, intr); if (sigset(SIGQUIT, SIG_IGN) == SIG_DFL) (void) sigset(SIGQUIT, intr); (void) sigset(SIGPIPE, intr); (void) sigset(SIGTERM, intr); } while (argc-- > 0) { char *arg; int gcode; psinfo_t psinfo; int tries = 0; if (use_agent_lwp) (void) proc_flushstdio(); if ((Pr = proc_arg_grab(arg = *argv++, PR_ARG_ANY, prg_gflags, &gcode)) == NULL) { (void) fprintf(stderr, "%s: cannot examine %s: %s\n", command, arg, Pgrab_error(gcode)); rc++; continue; } procname = arg; /* for perr() */ addr_width = (Pstatus(Pr)->pr_dmodel == PR_MODEL_LP64) ? 16 : 8; size_width = (Pstatus(Pr)->pr_dmodel == PR_MODEL_LP64) ? 11 : 8; bar = addr_width == 8 ? bar8 : bar16; (void) memcpy(&psinfo, Ppsinfo(Pr), sizeof (psinfo_t)); proc_unctrl_psinfo(&psinfo); if (Pstate(Pr) != PS_DEAD) { (void) snprintf(buf, sizeof (buf), "/proc/%d/map", (int)psinfo.pr_pid); if ((mapfd = open(buf, O_RDONLY)) < 0) { (void) fprintf(stderr, "%s: cannot " "examine %s: lost control of " "process\n", command, arg); rc++; Prelease(Pr, prr_flags); continue; } } else { mapfd = -1; } again: map_count = 0; if (Pstate(Pr) == PS_DEAD) { (void) printf("core '%s' of %d:\t%.70s\n", arg, (int)psinfo.pr_pid, psinfo.pr_psargs); if (rflag || sflag || xflag || Sflag || Lflag) { (void) printf(" -%c option is not compatible " "with core files\n", xflag ? 'x' : sflag ? 's' : rflag ? 'r' : Lflag ? 'L' : 'S'); Prelease(Pr, prr_flags); rc++; continue; } } else { (void) printf("%d:\t%.70s\n", (int)psinfo.pr_pid, psinfo.pr_psargs); } if (!(Pstatus(Pr)->pr_flags & PR_ISSYS)) { struct totals t; /* * Since we're grabbing the process readonly, we need * to make sure the address space doesn't change during * execution. */ if (Pstate(Pr) != PS_DEAD) { if (tries++ == MAX_TRIES) { Prelease(Pr, prr_flags); (void) close(mapfd); (void) fprintf(stderr, "%s: cannot " "examine %s: address space is " "changing\n", command, arg); continue; } if (fstat64(mapfd, &statbuf) != 0) { Prelease(Pr, prr_flags); (void) close(mapfd); (void) fprintf(stderr, "%s: cannot " "examine %s: lost control of " "process\n", command, arg); continue; } } nstacks = psinfo.pr_nlwp * 2; stacks = calloc(nstacks, sizeof (stacks[0])); if (stacks != NULL) { int n = 0; (void) Plwp_iter(Pr, getstack, &n); qsort(stacks, nstacks, sizeof (stacks[0]), cmpstacks); } (void) memset(&t, 0, sizeof (t)); if (Pgetauxval(Pr, AT_BASE) != -1L && Prd_agent(Pr) == NULL) { (void) fprintf(stderr, "%s: warning: " "librtld_db failed to initialize; " "shared library information will not be " "available\n", command); } /* * Gather data */ if (xflag) rc += xmapping_iter(Pr, gather_xmap, NULL, 0); else if (Sflag) rc += xmapping_iter(Pr, gather_xmap, NULL, 1); else { if (rflag) rc += rmapping_iter(Pr, gather_map, NULL); else if (sflag) rc += xmapping_iter(Pr, gather_xmap, NULL, 0); else if (lflag) rc += Pmapping_iter(Pr, gather_map, NULL); else rc += Pmapping_iter_resolved(Pr, gather_map, NULL); } /* * Ensure mappings are consistent. */ if (Pstate(Pr) != PS_DEAD) { struct stat64 newbuf; if (fstat64(mapfd, &newbuf) != 0 || memcmp(&newbuf.st_mtim, &statbuf.st_mtim, sizeof (newbuf.st_mtim)) != 0) { if (stacks != NULL) { free(stacks); stacks = NULL; } goto again; } } /* * Display data. */ if (xflag) { (void) printf("%*s%*s%*s%*s%*s " "%sMode Mapped File\n", addr_width, "Address", size_width, "Kbytes", size_width, "RSS", size_width, "Anon", size_width, "Locked", sflag ? "Pgsz " : ""); rc += iter_xmap(sflag ? look_xmap : look_xmap_nopgsz, &t); (void) printf("%s%s %s %s %s %s\n", addr_width == 8 ? "-" : "------", bar, bar, bar, bar, bar); (void) printf("%stotal Kb", addr_width == 16 ? " " : ""); printK(t.total_size, size_width); printK(t.total_rss, size_width); printK(t.total_anon, size_width); printK(t.total_locked, size_width); (void) printf("\n"); } else if (Sflag) { (void) printf("%*s%*s%*s Mode" " Mapped File\n", addr_width, "Address", size_width, "Kbytes", size_width, "Swap"); rc += iter_xmap(look_xmap_nopgsz, &t); (void) printf("%s%s %s %s\n", addr_width == 8 ? "-" : "------", bar, bar, bar); (void) printf("%stotal Kb", addr_width == 16 ? " " : ""); printK(t.total_size, size_width); printK(t.total_swap, size_width); (void) printf("\n"); } else { if (rflag) { rc += iter_map(look_map, &t); } else if (sflag) { if (Lflag) { (void) printf("%*s %*s %4s" " %-6s %s %s\n", addr_width, "Address", size_width, "Bytes", "Pgsz", "Mode ", "Lgrp", "Mapped File"); rc += iter_xmap(look_smap, &t); } else { (void) printf("%*s %*s %4s" " %-6s %s\n", addr_width, "Address", size_width, "Bytes", "Pgsz", "Mode ", "Mapped File"); rc += iter_xmap(look_smap, &t); } } else { rc += iter_map(look_map, &t); } (void) printf(" %stotal %*luK\n", addr_width == 16 ? " " : "", size_width, t.total_size); } if (stacks != NULL) { free(stacks); stacks = NULL; } } Prelease(Pr, prr_flags); if (mapfd != -1) (void) close(mapfd); } if (use_agent_lwp) (void) proc_finistdio(); return (rc); }
/*ARGSUSED*/ static void prochandler(struct ps_prochandle *P, const char *msg, void *arg) { #if defined(sun) const psinfo_t *prp = Ppsinfo(P); int pid = Pstatus(P)->pr_pid; char name[SIG2STR_MAX]; #else int wstatus = proc_getwstat(P); int pid = proc_getpid(P); #endif if (msg != NULL) { notice("pid %d: %s\n", pid, msg); return; } #if defined(sun) switch (Pstate(P)) { #else switch (proc_state(P)) { #endif case PS_UNDEAD: #if defined(sun) /* * Ideally we would like to always report pr_wstat here, but it * isn't possible given current /proc semantics. If we grabbed * the process, Ppsinfo() will either fail or return a zeroed * psinfo_t depending on how far the parent is in reaping it. * When /proc provides a stable pr_wstat in the status file, * this code can be improved by examining this new pr_wstat. */ if (prp != NULL && WIFSIGNALED(prp->pr_wstat)) { notice("pid %d terminated by %s\n", pid, proc_signame(WTERMSIG(prp->pr_wstat), name, sizeof (name))); #else if (WIFSIGNALED(wstatus)) { notice("pid %d terminated by %d\n", pid, WTERMSIG(wstatus)); #endif #if defined(sun) } else if (prp != NULL && WEXITSTATUS(prp->pr_wstat) != 0) { notice("pid %d exited with status %d\n", pid, WEXITSTATUS(prp->pr_wstat)); #else } else if (WEXITSTATUS(wstatus) != 0) { notice("pid %d exited with status %d\n", pid, WEXITSTATUS(wstatus)); #endif } else { notice("pid %d has exited\n", pid); } g_pslive--; break; case PS_LOST: notice("pid %d exec'd a set-id or unobservable program\n", pid); g_pslive--; break; } } /*ARGSUSED*/ static int errhandler(const dtrace_errdata_t *data, void *arg) { error(data->dteda_msg); return (DTRACE_HANDLE_OK); } /*ARGSUSED*/ static int drophandler(const dtrace_dropdata_t *data, void *arg) { error(data->dtdda_msg); return (DTRACE_HANDLE_OK); }
static int look(char *arg) { struct ps_prochandle *Pr; static prcred_t *prcred = NULL; int gcode; procname = arg; /* for perr() */ if (prcred == NULL) { prcred = malloc(sizeof (prcred_t) + (ngroups_max - 1) * sizeof (gid_t)); if (prcred == NULL) { (void) perr("malloc"); exit(1); } } if ((Pr = proc_arg_grab(arg, doset ? PR_ARG_PIDS : PR_ARG_ANY, PGRAB_RETAIN | PGRAB_FORCE | (doset ? 0 : PGRAB_RDONLY) | PGRAB_NOSTOP, &gcode)) == NULL) { (void) fprintf(stderr, "%s: cannot examine %s: %s\n", command, arg, Pgrab_error(gcode)); return (1); } if (Pcred(Pr, prcred, ngroups_max) == -1) { (void) perr("getcred"); Prelease(Pr, 0); return (1); } if (doset) { credupdate(prcred); if (Psetcred(Pr, prcred) != 0) { (void) perr("setcred"); Prelease(Pr, 0); return (1); } Prelease(Pr, 0); return (0); } if (Pstate(Pr) == PS_DEAD) (void) printf("core of %d:\t", (int)Pstatus(Pr)->pr_pid); else (void) printf("%d:\t", (int)Pstatus(Pr)->pr_pid); if (!all && prcred->pr_euid == prcred->pr_ruid && prcred->pr_ruid == prcred->pr_suid) (void) printf("e/r/suid=%u ", prcred->pr_euid); else (void) printf("euid=%u ruid=%u suid=%u ", prcred->pr_euid, prcred->pr_ruid, prcred->pr_suid); if (!all && prcred->pr_egid == prcred->pr_rgid && prcred->pr_rgid == prcred->pr_sgid) (void) printf("e/r/sgid=%u\n", prcred->pr_egid); else (void) printf("egid=%u rgid=%u sgid=%u\n", prcred->pr_egid, prcred->pr_rgid, prcred->pr_sgid); if (prcred->pr_ngroups != 0 && (all || prcred->pr_ngroups != 1 || prcred->pr_groups[0] != prcred->pr_rgid)) { int i; (void) printf("\tgroups:"); for (i = 0; i < prcred->pr_ngroups; i++) (void) printf(" %u", prcred->pr_groups[i]); (void) printf("\n"); } Prelease(Pr, 0); return (0); }
static void dt_proc_destroy(dtrace_hdl_t *dtp, struct ps_prochandle *P) { dt_proc_t *dpr = dt_proc_lookup(dtp, P, B_FALSE); dt_proc_hash_t *dph = dtp->dt_procs; dt_proc_notify_t *npr, **npp; int rflag; assert(dpr != NULL); /* * If neither PR_KLC nor PR_RLC is set, then the process is stopped by * an external debugger and we were waiting in dt_proc_waitrun(). * Leave the process in this condition using PRELEASE_HANG. */ printf("dt_proc_destroy flags=%d\n", Pstatus(dpr->dpr_proc)->pr_flags); if (!(Pstatus(dpr->dpr_proc)->pr_flags & (PR_KLC | PR_RLC))) { dt_dprintf("abandoning pid %d\n", (int)dpr->dpr_pid); rflag = PRELEASE_HANG; } else { dt_dprintf("releasing pid %d\n", (int)dpr->dpr_pid); rflag = 0; /* apply kill or run-on-last-close */ } if (dpr->dpr_tid) { /* * Set the dpr_quit flag to tell the daemon thread to exit. We * send it a SIGCANCEL to poke it out of PCWSTOP or any other * long-term /proc system call. Our daemon threads have POSIX * cancellation disabled, so EINTR will be the only effect. We * then wait for dpr_done to indicate the thread has exited. * * We can't use pthread_kill() to send SIGCANCEL because the * interface forbids it and we can't use pthread_cancel() * because with cancellation disabled it won't actually * send SIGCANCEL to the target thread, so we use _lwp_kill() * to do the job. This is all built on evil knowledge of * the details of the cancellation mechanism in libc. */ (void) pthread_mutex_lock(&dpr->dpr_lock); dpr->dpr_quit = B_TRUE; #if defined(sun) (void) _lwp_kill(dpr->dpr_tid, SIGCANCEL); #else (void) pthread_kill(dpr->dpr_tid, SIGUSR1); #endif /* * If the process is currently idling in dt_proc_stop(), re- * enable breakpoints and poke it into running again. */ if (dpr->dpr_stop & DT_PROC_STOP_IDLE) { dt_proc_bpenable(dpr); dpr->dpr_stop &= ~DT_PROC_STOP_IDLE; (void) pthread_cond_broadcast(&dpr->dpr_cv); } while (!dpr->dpr_done) (void) pthread_cond_wait(&dpr->dpr_cv, &dpr->dpr_lock); (void) pthread_mutex_unlock(&dpr->dpr_lock); } /* * Before we free the process structure, remove this dt_proc_t from the * lookup hash, and then walk the dt_proc_hash_t's notification list * and remove this dt_proc_t if it is enqueued. */ (void) pthread_mutex_lock(&dph->dph_lock); (void) dt_proc_lookup(dtp, P, B_TRUE); npp = &dph->dph_notify; while ((npr = *npp) != NULL) { if (npr->dprn_dpr == dpr) { *npp = npr->dprn_next; dt_free(dtp, npr); } else { npp = &npr->dprn_next; } } (void) pthread_mutex_unlock(&dph->dph_lock); /* * Remove the dt_proc_list from the LRU list, release the underlying * libproc handle, and free our dt_proc_t data structure. */ if (dpr->dpr_cacheable) { assert(dph->dph_lrucnt != 0); dph->dph_lrucnt--; } dt_list_delete(&dph->dph_lrulist, dpr); Prelease(dpr->dpr_proc, rflag); dt_free(dtp, dpr); }
/* * Main loop for all victim process control threads. We initialize all the * appropriate /proc control mechanisms, and then enter a loop waiting for * the process to stop on an event or die. We process any events by calling * appropriate subroutines, and exit when the victim dies or we lose control. * * The control thread synchronizes the use of dpr_proc with other libdtrace * threads using dpr_lock. We hold the lock for all of our operations except * waiting while the process is running: this is accomplished by writing a * PCWSTOP directive directly to the underlying /proc/<pid>/ctl file. If the * libdtrace client wishes to exit or abort our wait, SIGCANCEL can be used. */ static void * dt_proc_control(void *arg) { dt_proc_control_data_t *datap = arg; dtrace_hdl_t *dtp = datap->dpcd_hdl; dt_proc_t *dpr = datap->dpcd_proc; dt_proc_hash_t *dph = dpr->dpr_hdl->dt_procs; struct ps_prochandle *P = dpr->dpr_proc; #if defined(sun) int pfd = Pctlfd(P); const long wstop = PCWSTOP; #endif int notify = B_FALSE; /* * We disable the POSIX thread cancellation mechanism so that the * client program using libdtrace can't accidentally cancel our thread. * dt_proc_destroy() uses SIGCANCEL explicitly to simply poke us out * of PCWSTOP with EINTR, at which point we will see dpr_quit and exit. */ (void) pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL); dpr->dpr_pid = proc_getpid(P); int pid = dpr->dpr_pid; /* * Set up the corresponding process for tracing by libdtrace. We want * to be able to catch breakpoints and efficiently single-step over * them, and we need to enable librtld_db to watch libdl activity. */ do_ptrace(__func__, PTRACE_ATTACH, dpr->dpr_pid, 0, 0); (void) pthread_mutex_lock(&dpr->dpr_lock); (void) Punsetflags(P, PR_ASYNC); /* require synchronous mode */ (void) Psetflags(P, PR_BPTADJ); /* always adjust eip on x86 */ (void) Punsetflags(P, PR_FORK); /* do not inherit on fork */ (void) Pfault(P, FLTBPT, B_TRUE); /* always trace breakpoints */ (void) Pfault(P, FLTTRACE, B_TRUE); /* always trace single-step */ /* * We must trace exit from exec() system calls so that if the exec is * successful, we can reset our breakpoints and re-initialize libproc. */ (void) Psysexit(P, SYS_exec, B_TRUE); (void) Psysexit(P, SYS_execve, B_TRUE); /* * We must trace entry and exit for fork() system calls in order to * disable our breakpoints temporarily during the fork. We do not set * the PR_FORK flag, so if fork succeeds the child begins executing and * does not inherit any other tracing behaviors or a control thread. */ (void) Psysentry(P, SYS_vfork, B_TRUE); (void) Psysexit(P, SYS_vfork, B_TRUE); (void) Psysentry(P, SYS_fork1, B_TRUE); (void) Psysexit(P, SYS_fork1, B_TRUE); (void) Psysentry(P, SYS_forkall, B_TRUE); (void) Psysexit(P, SYS_forkall, B_TRUE); (void) Psysentry(P, SYS_forksys, B_TRUE); (void) Psysexit(P, SYS_forksys, B_TRUE); Psync(P); /* enable all /proc changes */ dt_proc_attach(dpr, B_FALSE); /* enable rtld breakpoints */ /* * If PR_KLC is set, we created the process; otherwise we grabbed it. * Check for an appropriate stop request and wait for dt_proc_continue. */ dpr->dpr_stop |= DT_PROC_STOP_CREATE; if (Pstatus(P)->pr_flags & PR_KLC) dt_proc_stop(dpr, DT_PROC_STOP_CREATE); else dt_proc_stop(dpr, DT_PROC_STOP_GRAB); if (Psetrun(P, 0, 0) == -1) { dt_dprintf("pid %d: failed to set running: %s\n", (int)dpr->dpr_pid, strerror(errno)); } (void) pthread_mutex_unlock(&dpr->dpr_lock); /* * Wait for the process corresponding to this control thread to stop, * process the event, and then set it running again. We want to sleep * with dpr_lock *unheld* so that other parts of libdtrace can use the * ps_prochandle in the meantime (e.g. ustack()). To do this, we write * a PCWSTOP directive directly to the underlying /proc/<pid>/ctl file. * Once the process stops, we wake up, grab dpr_lock, and then call * Pwait() (which will return immediately) and do our processing. */ //printf("%s: waiting to quit\n", __func__); while (!dpr->dpr_quit) { const lwpstatus_t *psp; #if defined(sun) if (write(pfd, &wstop, sizeof (wstop)) == -1 && errno == EINTR) continue; /* check dpr_quit and continue waiting */ #else /* Wait for the process to report status. */ proc_wait(P); #endif (void) pthread_mutex_lock(&dpr->dpr_lock); pwait_locked: if (Pstopstatus(P, PCNULL, 0) == -1 && errno == EINTR) { //printf("%s stopstatus (loop) pr_pid pid=%d\n", __func__, Pstatus(dpr->dpr_proc)->pr_pid); (void) pthread_mutex_unlock(&dpr->dpr_lock); continue; /* check dpr_quit and continue waiting */ } switch (Pstate(P)) { case PS_STOP: psp = &Pstatus(P)->pr_lwp; dt_dprintf("pid %d: proc stopped showing %d/%d\n", pid, psp->pr_why, psp->pr_what); #if defined(sun) /* * If the process stops showing PR_REQUESTED, then the * DTrace stop() action was applied to it or another * debugging utility (e.g. pstop(1)) asked it to stop. * In either case, the user's intention is for the * process to remain stopped until another external * mechanism (e.g. prun(1)) is applied. So instead of * setting the process running ourself, we wait for * someone else to do so. Once that happens, we return * to our normal loop waiting for an event of interest. */ if (psp->pr_why == PR_REQUESTED) { dt_proc_waitrun(dpr); (void) pthread_mutex_unlock(&dpr->dpr_lock); continue; } /* * If the process stops showing one of the events that * we are tracing, perform the appropriate response. * Note that we ignore PR_SUSPENDED, PR_CHECKPOINT, and * PR_JOBCONTROL by design: if one of these conditions * occurs, we will fall through to Psetrun() but the * process will remain stopped in the kernel by the * corresponding mechanism (e.g. job control stop). */ if (psp->pr_why == PR_FAULTED && psp->pr_what == FLTBPT) dt_proc_bpmatch(dtp, dpr); else if (psp->pr_why == PR_SYSENTRY && IS_SYS_FORK(psp->pr_what)) dt_proc_bpdisable(dpr); else if (psp->pr_why == PR_SYSEXIT && IS_SYS_FORK(psp->pr_what)) dt_proc_bpenable(dpr); else if (psp->pr_why == PR_SYSEXIT && IS_SYS_EXEC(psp->pr_what)) dt_proc_attach(dpr, B_TRUE); #endif //printf("In PS_STOP dpr_stop=%x\n", dpr->dpr_stop); break; case PS_LOST: //printf("in PS_LOST\n"); if (Preopen(P) == 0) goto pwait_locked; dt_dprintf("pid %d: proc lost: %s\n", pid, strerror(errno)); dpr->dpr_quit = B_TRUE; notify = B_TRUE; break; case PS_UNDEAD: case PS_DEAD: dt_dprintf("pid %d: proc died\n", pid); dpr->dpr_quit = B_TRUE; notify = B_TRUE; break; } if (Pstate(P) != PS_UNDEAD && Psetrun(P, 0, 0) == -1) { dt_dprintf("pid %d: failed to set running: %s\n", (int)dpr->dpr_pid, strerror(errno)); } (void) pthread_mutex_unlock(&dpr->dpr_lock); } /* * If the control thread detected PS_UNDEAD or PS_LOST, then enqueue * the dt_proc_t structure on the dt_proc_hash_t notification list. */ if (notify) dt_proc_notify(dtp, dph, dpr, NULL); /* * Destroy and remove any remaining breakpoints, set dpr_done and clear * dpr_tid to indicate the control thread has exited, and notify any * waiting thread in dt_proc_destroy() that we have succesfully exited. */ (void) pthread_mutex_lock(&dpr->dpr_lock); dt_proc_bpdestroy(dpr, B_TRUE); dpr->dpr_done = B_TRUE; dpr->dpr_tid = 0; (void) pthread_cond_broadcast(&dpr->dpr_cv); (void) pthread_mutex_unlock(&dpr->dpr_lock); return (NULL); }
int main(int argc, char **argv) { int err; int opt_C = 0, opt_H = 0, opt_p = 0, opt_v = 0; char c, *p, *end; struct sigaction act; int done = 0; g_pname = basename(argv[0]); argv[0] = g_pname; /* rewrite argv[0] for getopt errors */ while ((c = getopt(argc, argv, PLOCKSTAT_OPTSTR)) != EOF) { switch (c) { case 'n': errno = 0; g_nent = strtoul(optarg, &end, 10); if (*end != '\0' || errno != 0) { (void) fprintf(stderr, "%s: invalid count " "'%s'\n", g_pname, optarg); usage(); } break; case 'p': opt_p = 1; break; case 'v': opt_v = 1; break; case 'A': opt_C = opt_H = 1; break; case 'C': opt_C = 1; break; case 'H': opt_H = 1; break; case 'V': g_opt_V = 1; break; default: if (strchr(PLOCKSTAT_OPTSTR, c) == NULL) usage(); } } /* * We need a command or at least one pid. */ if (argc == optind) usage(); if (opt_C == 0 && opt_H == 0) opt_C = 1; if ((g_dtp = dtrace_open(DTRACE_VERSION, 0, &err)) == NULL) fatal("failed to initialize dtrace: %s\n", dtrace_errmsg(NULL, err)); /* * The longest string we trace is 23 bytes long -- so 32 is plenty. */ if (dtrace_setopt(g_dtp, "strsize", "32") == -1) dfatal("failed to set 'strsize'"); /* * 1k should be more than enough for all trace() and printa() actions. */ if (dtrace_setopt(g_dtp, "bufsize", "1k") == -1) dfatal("failed to set 'bufsize'"); /* * The table we produce has the hottest locks at the top. */ if (dtrace_setopt(g_dtp, "aggsortrev", NULL) == -1) dfatal("failed to set 'aggsortrev'"); /* * These are two reasonable defaults which should suffice. */ if (dtrace_setopt(g_dtp, "aggsize", "256k") == -1) dfatal("failed to set 'aggsize'"); if (dtrace_setopt(g_dtp, "aggrate", "1sec") == -1) dfatal("failed to set 'aggrate'"); /* * Take a second pass through to look for options that set options now * that we have an open dtrace handle. */ optind = 1; while ((c = getopt(argc, argv, PLOCKSTAT_OPTSTR)) != EOF) { switch (c) { case 's': g_opt_s = 1; if (dtrace_setopt(g_dtp, "ustackframes", optarg) == -1) dfatal("failed to set 'ustackframes'"); break; case 'x': if ((p = strchr(optarg, '=')) != NULL) *p++ = '\0'; if (dtrace_setopt(g_dtp, optarg, p) != 0) dfatal("failed to set -x %s", optarg); break; case 'e': errno = 0; (void) strtoul(optarg, &end, 10); if (*optarg == '-' || *end != '\0' || errno != 0) { (void) fprintf(stderr, "%s: invalid timeout " "'%s'\n", g_pname, optarg); usage(); } /* * Construct a DTrace enabling that will exit after * the specified number of seconds. */ dprog_add("BEGIN\n{\n\tend = timestamp + "); dprog_add(optarg); dprog_add(" * 1000000000;\n}\n"); dprog_add("tick-10hz\n/timestamp >= end/\n"); dprog_add("{\n\texit(0);\n}\n"); break; } } argc -= optind; argv += optind; if (opt_H) { dprog_add(g_hold_init); if (g_opt_s == NULL) dprog_add(g_hold_times); else dprog_add(g_hold_histogram); } if (opt_C) { dprog_add(g_ctnd_init); if (g_opt_s == NULL) dprog_add(g_ctnd_times); else dprog_add(g_ctnd_histogram); } if (opt_p) { ulong_t pid; if (argc > 1) { (void) fprintf(stderr, "%s: only one pid is allowed\n", g_pname); usage(); } errno = 0; pid = strtoul(argv[0], &end, 10); if (*end != '\0' || errno != 0 || (pid_t)pid != pid) { (void) fprintf(stderr, "%s: invalid pid '%s'\n", g_pname, argv[0]); usage(); } if ((g_pr = dtrace_proc_grab(g_dtp, (pid_t)pid, 0)) == NULL) dfatal(NULL); } else { if ((g_pr = dtrace_proc_create(g_dtp, argv[0], argv)) == NULL) dfatal(NULL); } dprog_compile(); if (dtrace_handle_proc(g_dtp, &prochandler, NULL) == -1) dfatal("failed to establish proc handler"); (void) sigemptyset(&act.sa_mask); act.sa_flags = 0; act.sa_handler = intr; (void) sigaction(SIGINT, &act, NULL); (void) sigaction(SIGTERM, &act, NULL); if (dtrace_go(g_dtp) != 0) dfatal("dtrace_go()"); if (dtrace_getopt(g_dtp, "ustackframes", &g_nframes) != 0) dfatal("failed to get 'ustackframes'"); dtrace_proc_continue(g_dtp, g_pr); if (opt_v) (void) printf("%s: tracing enabled for pid %d\n", g_pname, (int)Pstatus(g_pr)->pr_pid); do { if (!g_intr && !done) dtrace_sleep(g_dtp); if (done || g_intr || g_exited) { done = 1; if (dtrace_stop(g_dtp) == -1) dfatal("couldn't stop tracing"); } switch (dtrace_work(g_dtp, stdout, NULL, chewrec, NULL)) { case DTRACE_WORKSTATUS_DONE: done = 1; break; case DTRACE_WORKSTATUS_OKAY: break; default: dfatal("processing aborted"); } } while (!done); dtrace_close(g_dtp); return (0); }
/*ARGSUSED*/ static int look_smap(void *data, const prxmap_t *pmp, const char *object_name, int last, int doswap) { struct totals *t = data; const pstatus_t *Psp = Pstatus(Pr); size_t size; char mname[PATH_MAX]; char *lname = NULL; const char *format; size_t psz = pmp->pr_pagesize; uintptr_t vaddr = pmp->pr_vaddr; uintptr_t segment_end = vaddr + pmp->pr_size; lgrp_id_t lgrp; memory_chunk_t mchunk; /* * If the mapping is not anon or not part of the heap, make a name * for it. We don't want to report the heap as a.out's data. */ if (!(pmp->pr_mflags & MA_ANON) || pmp->pr_vaddr + pmp->pr_size <= Psp->pr_brkbase || pmp->pr_vaddr >= Psp->pr_brkbase + Psp->pr_brksize) { lname = make_name(Pr, lflag, pmp->pr_vaddr, pmp->pr_mapname, mname, sizeof (mname)); } if (lname == NULL && ((pmp->pr_mflags & MA_ANON) || Pstate(Pr) == PS_DEAD)) { lname = anon_name(mname, Psp, stacks, nstacks, pmp->pr_vaddr, pmp->pr_size, pmp->pr_mflags, pmp->pr_shmid, NULL); } /* * Adjust the address range if -A is specified. */ size = adjust_addr_range(pmp->pr_vaddr, segment_end, psz, &vaddr, &segment_end); if (size == 0) return (0); if (!Lflag) { /* * Display the whole mapping */ if (lname != NULL) format = "%.*lX %*luK %4s %-6s %s\n"; else format = "%.*lX %*luK %4s %s\n"; size = ROUNDUP_KB(size); (void) printf(format, addr_width, vaddr, size_width - 1, size, pagesize(pmp), mflags(pmp->pr_mflags), lname); t->total_size += size; return (0); } if (lname != NULL) format = "%.*lX %*luK %4s %-6s%s %s\n"; else format = "%.*lX %*luK %4s%s %s\n"; /* * We need to display lgroups backing physical memory, so we break the * segment into individual pages and coalesce pages with the same lgroup * into one "segment". */ /* * Initialize address descriptions for the mapping. */ mem_chunk_init(&mchunk, segment_end, psz); size = 0; /* * Walk mapping (page by page) and display contiguous ranges of memory * allocated to same lgroup. */ do { size_t size_contig; /* * Get contiguous region of memory starting from vaddr allocated * from the same lgroup. */ size_contig = get_contiguous_region(&mchunk, vaddr, segment_end, pmp->pr_pagesize, &lgrp); (void) printf(format, addr_width, vaddr, size_width - 1, size_contig / KILOBYTE, pagesize(pmp), mflags(pmp->pr_mflags), lgrp2str(lgrp), lname); vaddr += size_contig; size += size_contig; } while (vaddr < segment_end && !interrupt); t->total_size += ROUNDUP_KB(size); return (0); }
/*ARGSUSED*/ int pt_regs(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { mdb_tgt_t *t = mdb.m_target; mdb_tgt_tid_t tid; prgregset_t grs; uint64_t xgregs[8]; uint64_t xoregs[8]; int rwidth, i; #if defined(__sparc) && defined(_ILP32) static const uint32_t zero[8] = { 0 }; prxregset_t xrs; #endif #define GETREG2(x) ((uintptr_t)grs[(x)]), ((uintptr_t)grs[(x)]) if (argc != 0) return (DCMD_USAGE); if (t->t_pshandle == NULL || Pstate(t->t_pshandle) == PS_UNDEAD) { mdb_warn("no process active\n"); return (DCMD_ERR); } if (Pstate(t->t_pshandle) == PS_LOST) { mdb_warn("debugger has lost control of process\n"); return (DCMD_ERR); } if (flags & DCMD_ADDRSPEC) tid = (mdb_tgt_tid_t)addr; else tid = PTL_TID(t); if (PTL_GETREGS(t, tid, grs) != 0) { mdb_warn("failed to get current register set"); return (DCMD_ERR); } for (i = 0; i < 8; i++) { xgregs[i] = (ulong_t)grs[R_G0 + i]; xoregs[i] = (ulong_t)grs[R_O0 + i]; } if (Pstatus(t->t_pshandle)->pr_dmodel == PR_MODEL_LP64) rwidth = 16; else rwidth = 8; #if defined(__sparc) && defined(_ILP32) /* * If we are debugging a 32-bit SPARC process on an UltraSPARC CPU, * the globals and outs can have 32 upper bits hiding in the xregs. */ if (PTL_GETXREGS(t, tid, &xrs) == 0 && xrs.pr_type == XR_TYPE_V8P) { for (i = 0; i < 8; i++) { xgregs[i] |= (uint64_t) xrs.pr_un.pr_v8p.pr_xg[XR_G0 + i] << 32; xoregs[i] |= (uint64_t) xrs.pr_un.pr_v8p.pr_xo[XR_O0 + i] << 32; } if (bcmp(xrs.pr_un.pr_v8p.pr_xg, zero, sizeof (zero)) || bcmp(xrs.pr_un.pr_v8p.pr_xo, zero, sizeof (zero))) rwidth = 16; /* one or more have upper bits set */ } #endif /* __sparc && _ILP32 */ for (i = 0; i < 8; i++) { mdb_printf("%%g%d = 0x%0*llx %15llA %%l%d = 0x%0?p %A\n", i, rwidth, xgregs[i], xgregs[i], i, GETREG2(R_L0 + i)); } for (i = 0; i < 8; i++) { mdb_printf("%%o%d = 0x%0*llx %15llA %%i%d = 0x%0?p %A\n", i, rwidth, xoregs[i], xoregs[i], i, GETREG2(R_I0 + i)); } mdb_printf("\n"); #ifdef __sparcv9 mdb_printf(" %%ccr = 0x%02x xcc=%c%c%c%c icc=%c%c%c%c\n", grs[R_CCR], (grs[R_CCR] & KREG_CCR_XCC_N_MASK) ? 'N' : 'n', (grs[R_CCR] & KREG_CCR_XCC_Z_MASK) ? 'Z' : 'z', (grs[R_CCR] & KREG_CCR_XCC_V_MASK) ? 'V' : 'v', (grs[R_CCR] & KREG_CCR_XCC_C_MASK) ? 'C' : 'c', (grs[R_CCR] & KREG_CCR_ICC_N_MASK) ? 'N' : 'n', (grs[R_CCR] & KREG_CCR_ICC_Z_MASK) ? 'Z' : 'z', (grs[R_CCR] & KREG_CCR_ICC_V_MASK) ? 'V' : 'v', (grs[R_CCR] & KREG_CCR_ICC_C_MASK) ? 'C' : 'c'); #else /* __sparcv9 */ mdb_printf(" %%psr = 0x%08x impl=0x%x ver=0x%x icc=%c%c%c%c\n" " ec=%u ef=%u pil=%u s=%u ps=%u et=%u cwp=0x%x\n", grs[R_PSR], (grs[R_PSR] & KREG_PSR_IMPL_MASK) >> KREG_PSR_IMPL_SHIFT, (grs[R_PSR] & KREG_PSR_VER_MASK) >> KREG_PSR_VER_SHIFT, (grs[R_PSR] & KREG_PSR_ICC_N_MASK) ? 'N' : 'n', (grs[R_PSR] & KREG_PSR_ICC_Z_MASK) ? 'Z' : 'z', (grs[R_PSR] & KREG_PSR_ICC_V_MASK) ? 'V' : 'v', (grs[R_PSR] & KREG_PSR_ICC_C_MASK) ? 'C' : 'c', grs[R_PSR] & KREG_PSR_EC_MASK, grs[R_PSR] & KREG_PSR_EF_MASK, (grs[R_PSR] & KREG_PSR_PIL_MASK) >> KREG_PSR_PIL_SHIFT, grs[R_PSR] & KREG_PSR_S_MASK, grs[R_PSR] & KREG_PSR_PS_MASK, grs[R_PSR] & KREG_PSR_ET_MASK, (grs[R_PSR] & KREG_PSR_CWP_MASK) >> KREG_PSR_CWP_SHIFT); #endif /* __sparcv9 */ mdb_printf(" %%y = 0x%0?p\n", grs[R_Y]); mdb_printf(" %%pc = 0x%0?p %A\n", GETREG2(R_PC)); mdb_printf(" %%npc = 0x%0?p %A\n", GETREG2(R_nPC)); mdb_printf(" %%sp = 0x%0?p\n", grs[R_SP]); mdb_printf(" %%fp = 0x%0?p\n\n", grs[R_FP]); #ifdef __sparcv9 mdb_printf(" %%asi = 0x%02lx\n", grs[R_ASI]); mdb_printf("%%fprs = 0x%02lx\n", grs[R_FPRS]); #else /* __sparcv9 */ mdb_printf(" %%wim = 0x%08x\n", grs[R_WIM]); mdb_printf(" %%tbr = 0x%08x\n", grs[R_TBR]); #endif /* __sparcv9 */ return (DCMD_OK); }
/*ARGSUSED*/ static int look_xmap_nopgsz(void *data, const prxmap_t *pmp, const char *object_name, int last, int doswap) { struct totals *t = data; const pstatus_t *Psp = Pstatus(Pr); char mname[PATH_MAX]; char *lname = NULL; char *ln; static uintptr_t prev_vaddr; static size_t prev_size; static offset_t prev_offset; static int prev_mflags; static char *prev_lname; static char prev_mname[PATH_MAX]; static ulong_t prev_rss; static ulong_t prev_anon; static ulong_t prev_locked; static ulong_t prev_swap; int merged = 0; static int first = 1; ulong_t swap = 0; int kperpage; /* * Calculate swap reservations */ if (pmp->pr_mflags & MA_SHARED) { if (aflag && (pmp->pr_mflags & MA_NORESERVE) == 0) { /* Swap reserved for entire non-ism SHM */ swap = pmp->pr_size / pmp->pr_pagesize; } } else if (pmp->pr_mflags & MA_NORESERVE) { /* Swap reserved on fault for each anon page */ swap = pmp->pr_anon; } else if (pmp->pr_mflags & MA_WRITE) { /* Swap reserve for entire writable segment */ swap = pmp->pr_size / pmp->pr_pagesize; } /* * If the mapping is not anon or not part of the heap, make a name * for it. We don't want to report the heap as a.out's data. */ if (!(pmp->pr_mflags & MA_ANON) || pmp->pr_vaddr + pmp->pr_size <= Psp->pr_brkbase || pmp->pr_vaddr >= Psp->pr_brkbase + Psp->pr_brksize) { lname = make_name(Pr, lflag, pmp->pr_vaddr, pmp->pr_mapname, mname, sizeof (mname)); } if (lname != NULL) { if ((ln = strrchr(lname, '/')) != NULL) lname = ln + 1; } else if ((pmp->pr_mflags & MA_ANON) || Pstate(Pr) == PS_DEAD) { lname = anon_name(mname, Psp, stacks, nstacks, pmp->pr_vaddr, pmp->pr_size, pmp->pr_mflags, pmp->pr_shmid, NULL); } kperpage = pmp->pr_pagesize / KILOBYTE; t->total_size += ROUNDUP_KB(pmp->pr_size); t->total_rss += pmp->pr_rss * kperpage; t->total_anon += ANON(pmp) * kperpage; t->total_locked += pmp->pr_locked * kperpage; t->total_swap += swap * kperpage; if (first == 1) { first = 0; prev_vaddr = pmp->pr_vaddr; prev_size = pmp->pr_size; prev_offset = pmp->pr_offset; prev_mflags = pmp->pr_mflags; if (lname == NULL) { prev_lname = NULL; } else { (void) strcpy(prev_mname, lname); prev_lname = prev_mname; } prev_rss = pmp->pr_rss * kperpage; prev_anon = ANON(pmp) * kperpage; prev_locked = pmp->pr_locked * kperpage; prev_swap = swap * kperpage; if (last == 0) { return (0); } merged = 1; } else if (prev_vaddr + prev_size == pmp->pr_vaddr && prev_mflags == pmp->pr_mflags && ((prev_mflags & MA_ISM) || prev_offset + prev_size == pmp->pr_offset) && ((lname == NULL && prev_lname == NULL) || (lname != NULL && prev_lname != NULL && strcmp(lname, prev_lname) == 0))) { prev_size += pmp->pr_size; prev_rss += pmp->pr_rss * kperpage; prev_anon += ANON(pmp) * kperpage; prev_locked += pmp->pr_locked * kperpage; prev_swap += swap * kperpage; if (last == 0) { return (0); } merged = 1; } (void) printf("%.*lX", addr_width, (ulong_t)prev_vaddr); printK(ROUNDUP_KB(prev_size), size_width); if (doswap) printK(prev_swap, size_width); else { printK(prev_rss, size_width); printK(prev_anon, size_width); printK(prev_locked, size_width); } (void) printf(prev_lname ? " %-6s %s\n" : "%s\n", mflags(prev_mflags), prev_lname); if (last == 0) { prev_vaddr = pmp->pr_vaddr; prev_size = pmp->pr_size; prev_offset = pmp->pr_offset; prev_mflags = pmp->pr_mflags; if (lname == NULL) { prev_lname = NULL; } else { (void) strcpy(prev_mname, lname); prev_lname = prev_mname; } prev_rss = pmp->pr_rss * kperpage; prev_anon = ANON(pmp) * kperpage; prev_locked = pmp->pr_locked * kperpage; prev_swap = swap * kperpage; } else if (merged == 0) { (void) printf("%.*lX", addr_width, (ulong_t)pmp->pr_vaddr); printK(ROUNDUP_KB(pmp->pr_size), size_width); if (doswap) printK(swap * kperpage, size_width); else { printK(pmp->pr_rss * kperpage, size_width); printK(ANON(pmp) * kperpage, size_width); printK(pmp->pr_locked * kperpage, size_width); } (void) printf(lname ? " %-6s %s\n" : " %s\n", mflags(pmp->pr_mflags), lname); } if (last != 0) first = 1; return (0); }
/*ARGSUSED*/ int pt_fpregs(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) { mdb_tgt_t *t = mdb.m_target; mdb_tgt_tid_t tid; int is_v8plus, is_v9, i; #ifdef __sparcv9 prgregset_t grs; #endif prfpregset_t fprs; prxregset_t xrs; uint32_t *regs; int ns, nd, nq; enum { FPR_MIXED = 0x0, /* show single, double, and status */ FPR_SINGLE = 0x1, /* show single-precision only */ FPR_DOUBLE = 0x2, /* show double-precision only */ FPR_QUAD = 0x4 /* show quad-precision only */ }; uint_t opts = FPR_MIXED; /* * The prfpregset structure only provides us with the FPU in the form * of 32-bit integers, doubles, or quads. We use this union of the * various types to display floats, doubles, and long doubles. */ union { struct { uint32_t i1; uint32_t i2; uint32_t i3; uint32_t i4; } ip; float f; double d; long double ld; } fpu; if (mdb_getopts(argc, argv, 's', MDB_OPT_SETBITS, FPR_SINGLE, &opts, 'd', MDB_OPT_SETBITS, FPR_DOUBLE, &opts, 'q', MDB_OPT_SETBITS, FPR_QUAD, &opts, NULL) != argc) return (DCMD_USAGE); if (t->t_pshandle == NULL || Pstate(t->t_pshandle) == PS_UNDEAD) { mdb_warn("no process active\n"); return (DCMD_ERR); } if (Pstate(t->t_pshandle) == PS_LOST) { mdb_warn("debugger has lost control of process\n"); return (DCMD_ERR); } if (flags & DCMD_ADDRSPEC) tid = (mdb_tgt_tid_t)addr; else tid = PTL_TID(t); is_v9 = Pstatus(t->t_pshandle)->pr_dmodel == PR_MODEL_LP64; is_v8plus = is_v9 == 0 && PTL_GETXREGS(t, tid, &xrs) == 0 && xrs.pr_type == XR_TYPE_V8P; #ifdef __sparcv9 if (is_v9 && opts == FPR_MIXED) { if (PTL_GETREGS(t, tid, grs) == 0) mdb_printf("fprs %lx\n", grs[R_FPRS]); else mdb_warn("failed to read fprs register"); } #endif if (is_v8plus && opts == FPR_MIXED) mdb_printf("fprs %x\n", xrs.pr_un.pr_v8p.pr_fprs); if (PTL_GETFPREGS(t, tid, &fprs) != 0) { mdb_warn("failed to get floating point registers"); return (DCMD_ERR); } if (opts == FPR_MIXED) { uint64_t fsr = fprs.pr_fsr; if (is_v8plus) fsr |= (uint64_t)xrs.pr_un.pr_v8p.pr_xfsr << 32; mdb_printf("fsr %llx\n", fsr); } /* * Set up the regs pointer to be a pointer to a contiguous chunk of * memory containing all the floating pointer register data. Set * ns, nd, and nq to indicate the number of registers of each type. */ if (is_v9) { regs = fprs.pr_fr.pr_regs; ns = 64; nd = 32; nq = 16; } else if (is_v8plus) { regs = mdb_alloc(sizeof (uint32_t) * 64, UM_SLEEP | UM_GC); bcopy(fprs.pr_fr.pr_regs, regs, sizeof (uint32_t) * 32); bcopy(xrs.pr_un.pr_v8p.pr_xfr.pr_regs, regs + 32, sizeof (uint32_t) * 32); ns = 64; nd = 32; nq = 16; } else { regs = fprs.pr_fr.pr_regs; ns = 32; nd = 16; nq = 0; } if (opts == FPR_MIXED) { for (i = 0; i < ns; i++) { fpu.ip.i1 = regs[i]; mdb_printf("f%-3d %08x %e", i, fpu.ip.i1, fpu.f); if (i & 1) { fpu.ip.i1 = regs[i - 1]; fpu.ip.i2 = regs[i]; mdb_printf(" %g", fpu.d); } mdb_printf("\n"); } } if (opts & FPR_SINGLE) { for (i = 0; i < ns; i++) { fpu.ip.i1 = regs[i]; mdb_printf("f%-3d %08x %e\n", i, fpu.ip.i1, fpu.f); } } if (opts & FPR_DOUBLE) { for (i = 0; i < nd; i++) { fpu.ip.i1 = regs[i * 2 + 0]; fpu.ip.i2 = regs[i * 2 + 1]; mdb_printf("f%-3d %08x.%08x %g\n", i * 2, fpu.ip.i1, fpu.ip.i2, fpu.d); } } if (opts & FPR_QUAD) { for (i = 0; i < nq; i++) { fpu.ip.i1 = regs[i * 4 + 0]; fpu.ip.i2 = regs[i * 4 + 1]; fpu.ip.i3 = regs[i * 4 + 2]; fpu.ip.i4 = regs[i * 4 + 3]; mdb_printf("f%-3d %08x.%08x.%08x.%08x %s\n", i * 4, fpu.ip.i1, fpu.ip.i2, fpu.ip.i3, fpu.ip.i4, longdoubletos(&fpu.ld, 16, 'e')); } } return (DCMD_OK); }