| /* |
| * 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 |
| * or http://www.opensolaris.org/os/licensing. |
| * 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 2011 Nexenta Systems, Inc. All rights reserved. |
| * Copyright (c) 1999, 2010, Oracle and/or its affiliates. All rights reserved. |
| * Copyright (c) 2018, Joyent, Inc. |
| * Copyright (c) 2013 by Delphix. All rights reserved. |
| */ |
| |
| #include <mdb/mdb_param.h> |
| #include <mdb/mdb_modapi.h> |
| #include <mdb/mdb_ks.h> |
| #include <mdb/mdb_ctf.h> |
| |
| #include <sys/types.h> |
| #include <sys/thread.h> |
| #include <sys/session.h> |
| #include <sys/user.h> |
| #include <sys/proc.h> |
| #include <sys/var.h> |
| #include <sys/t_lock.h> |
| #include <sys/callo.h> |
| #include <sys/priocntl.h> |
| #include <sys/class.h> |
| #include <sys/regset.h> |
| #include <sys/stack.h> |
| #include <sys/cpuvar.h> |
| #include <sys/vnode.h> |
| #include <sys/vfs.h> |
| #include <sys/flock_impl.h> |
| #include <sys/kmem_impl.h> |
| #include <sys/vmem_impl.h> |
| #include <sys/kstat.h> |
| #include <sys/dditypes.h> |
| #include <sys/ddi_impldefs.h> |
| #include <sys/sysmacros.h> |
| #include <sys/sysconf.h> |
| #include <sys/task.h> |
| #include <sys/project.h> |
| #include <sys/errorq_impl.h> |
| #include <sys/cred_impl.h> |
| #include <sys/zone.h> |
| #include <sys/panic.h> |
| #include <regex.h> |
| #include <sys/port_impl.h> |
| |
| #include "avl.h" |
| #include "bio.h" |
| #include "bitset.h" |
| #include "combined.h" |
| #include "contract.h" |
| #include "cpupart_mdb.h" |
| #include "cred.h" |
| #include "ctxop.h" |
| #include "cyclic.h" |
| #include "damap.h" |
| #include "ddi_periodic.h" |
| #include "devinfo.h" |
| #include "dnlc.h" |
| #include "findstack.h" |
| #include "fm.h" |
| #include "gcore.h" |
| #include "group.h" |
| #include "irm.h" |
| #include "kgrep.h" |
| #include "kmem.h" |
| #include "ldi.h" |
| #include "leaky.h" |
| #include "lgrp.h" |
| #include "list.h" |
| #include "log.h" |
| #include "mdi.h" |
| #include "memory.h" |
| #include "mmd.h" |
| #include "modhash.h" |
| #include "ndievents.h" |
| #include "net.h" |
| #include "netstack.h" |
| #include "nvpair.h" |
| #include "pg.h" |
| #include "rctl.h" |
| #include "sobj.h" |
| #include "streams.h" |
| #include "sysevent.h" |
| #include "taskq.h" |
| #include "thread.h" |
| #include "tsd.h" |
| #include "tsol.h" |
| #include "typegraph.h" |
| #include "vfs.h" |
| #include "zone.h" |
| #include "hotplug.h" |
| |
| /* |
| * Surely this is defined somewhere... |
| */ |
| #define NINTR 16 |
| |
| #define KILOS 10 |
| #define MEGS 20 |
| #define GIGS 30 |
| |
| #ifndef STACK_BIAS |
| #define STACK_BIAS 0 |
| #endif |
| |
| static char |
| pstat2ch(uchar_t state) |
| { |
| switch (state) { |
| case SSLEEP: return ('S'); |
| case SRUN: return ('R'); |
| case SZOMB: return ('Z'); |
| case SIDL: return ('I'); |
| case SONPROC: return ('O'); |
| case SSTOP: return ('T'); |
| case SWAIT: return ('W'); |
| default: return ('?'); |
| } |
| } |
| |
| #define PS_PRTTHREADS 0x1 |
| #define PS_PRTLWPS 0x2 |
| #define PS_PSARGS 0x4 |
| #define PS_TASKS 0x8 |
| #define PS_PROJECTS 0x10 |
| #define PS_ZONES 0x20 |
| |
| static int |
| ps_threadprint(uintptr_t addr, const void *data, void *private) |
| { |
| const kthread_t *t = (const kthread_t *)data; |
| uint_t prt_flags = *((uint_t *)private); |
| |
| static const mdb_bitmask_t t_state_bits[] = { |
| { "TS_FREE", UINT_MAX, TS_FREE }, |
| { "TS_SLEEP", TS_SLEEP, TS_SLEEP }, |
| { "TS_RUN", TS_RUN, TS_RUN }, |
| { "TS_ONPROC", TS_ONPROC, TS_ONPROC }, |
| { "TS_ZOMB", TS_ZOMB, TS_ZOMB }, |
| { "TS_STOPPED", TS_STOPPED, TS_STOPPED }, |
| { "TS_WAIT", TS_WAIT, TS_WAIT }, |
| { NULL, 0, 0 } |
| }; |
| |
| if (prt_flags & PS_PRTTHREADS) |
| mdb_printf("\tT %?a <%b>\n", addr, t->t_state, t_state_bits); |
| |
| if (prt_flags & PS_PRTLWPS) { |
| char desc[128] = ""; |
| |
| (void) thread_getdesc(addr, B_FALSE, desc, sizeof (desc)); |
| |
| mdb_printf("\tL %?a ID: %s\n", t->t_lwp, desc); |
| } |
| |
| return (WALK_NEXT); |
| } |
| |
| typedef struct mdb_pflags_proc { |
| struct pid *p_pidp; |
| ushort_t p_pidflag; |
| uint_t p_proc_flag; |
| uint_t p_flag; |
| } mdb_pflags_proc_t; |
| |
| static int |
| pflags(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) |
| { |
| mdb_pflags_proc_t pr; |
| struct pid pid; |
| |
| static const mdb_bitmask_t p_flag_bits[] = { |
| { "SSYS", SSYS, SSYS }, |
| { "SEXITING", SEXITING, SEXITING }, |
| { "SITBUSY", SITBUSY, SITBUSY }, |
| { "SFORKING", SFORKING, SFORKING }, |
| { "SWATCHOK", SWATCHOK, SWATCHOK }, |
| { "SKILLED", SKILLED, SKILLED }, |
| { "SSCONT", SSCONT, SSCONT }, |
| { "SZONETOP", SZONETOP, SZONETOP }, |
| { "SEXTKILLED", SEXTKILLED, SEXTKILLED }, |
| { "SUGID", SUGID, SUGID }, |
| { "SEXECED", SEXECED, SEXECED }, |
| { "SJCTL", SJCTL, SJCTL }, |
| { "SNOWAIT", SNOWAIT, SNOWAIT }, |
| { "SVFORK", SVFORK, SVFORK }, |
| { "SVFWAIT", SVFWAIT, SVFWAIT }, |
| { "SEXITLWPS", SEXITLWPS, SEXITLWPS }, |
| { "SHOLDFORK", SHOLDFORK, SHOLDFORK }, |
| { "SHOLDFORK1", SHOLDFORK1, SHOLDFORK1 }, |
| { "SCOREDUMP", SCOREDUMP, SCOREDUMP }, |
| { "SMSACCT", SMSACCT, SMSACCT }, |
| { "SLWPWRAP", SLWPWRAP, SLWPWRAP }, |
| { "SAUTOLPG", SAUTOLPG, SAUTOLPG }, |
| { "SNOCD", SNOCD, SNOCD }, |
| { "SHOLDWATCH", SHOLDWATCH, SHOLDWATCH }, |
| { "SMSFORK", SMSFORK, SMSFORK }, |
| { "SDOCORE", SDOCORE, SDOCORE }, |
| { NULL, 0, 0 } |
| }; |
| |
| static const mdb_bitmask_t p_pidflag_bits[] = { |
| { "CLDPEND", CLDPEND, CLDPEND }, |
| { "CLDCONT", CLDCONT, CLDCONT }, |
| { "CLDNOSIGCHLD", CLDNOSIGCHLD, CLDNOSIGCHLD }, |
| { "CLDWAITPID", CLDWAITPID, CLDWAITPID }, |
| { NULL, 0, 0 } |
| }; |
| |
| static const mdb_bitmask_t p_proc_flag_bits[] = { |
| { "P_PR_TRACE", P_PR_TRACE, P_PR_TRACE }, |
| { "P_PR_PTRACE", P_PR_PTRACE, P_PR_PTRACE }, |
| { "P_PR_FORK", P_PR_FORK, P_PR_FORK }, |
| { "P_PR_LOCK", P_PR_LOCK, P_PR_LOCK }, |
| { "P_PR_ASYNC", P_PR_ASYNC, P_PR_ASYNC }, |
| { "P_PR_EXEC", P_PR_EXEC, P_PR_EXEC }, |
| { "P_PR_BPTADJ", P_PR_BPTADJ, P_PR_BPTADJ }, |
| { "P_PR_RUNLCL", P_PR_RUNLCL, P_PR_RUNLCL }, |
| { "P_PR_KILLCL", P_PR_KILLCL, P_PR_KILLCL }, |
| { NULL, 0, 0 } |
| }; |
| |
| if (!(flags & DCMD_ADDRSPEC)) { |
| if (mdb_walk_dcmd("proc", "pflags", argc, argv) == -1) { |
| mdb_warn("can't walk 'proc'"); |
| return (DCMD_ERR); |
| } |
| return (DCMD_OK); |
| } |
| |
| if (mdb_ctf_vread(&pr, "proc_t", "mdb_pflags_proc_t", addr, 0) == -1 || |
| mdb_vread(&pid, sizeof (pid), (uintptr_t)pr.p_pidp) == -1) { |
| mdb_warn("cannot read proc_t or pid"); |
| return (DCMD_ERR); |
| } |
| |
| mdb_printf("%p [pid %d]:\n", addr, pid.pid_id); |
| mdb_printf("\tp_flag: %08x <%b>\n", pr.p_flag, pr.p_flag, |
| p_flag_bits); |
| mdb_printf("\tp_pidflag: %08x <%b>\n", pr.p_pidflag, pr.p_pidflag, |
| p_pidflag_bits); |
| mdb_printf("\tp_proc_flag: %08x <%b>\n", pr.p_proc_flag, pr.p_proc_flag, |
| p_proc_flag_bits); |
| |
| return (DCMD_OK); |
| } |
| |
| typedef struct mdb_ps_proc { |
| char p_stat; |
| struct pid *p_pidp; |
| struct pid *p_pgidp; |
| struct cred *p_cred; |
| struct sess *p_sessp; |
| struct task *p_task; |
| struct zone *p_zone; |
| pid_t p_ppid; |
| uint_t p_flag; |
| struct { |
| char u_comm[MAXCOMLEN + 1]; |
| char u_psargs[PSARGSZ]; |
| } p_user; |
| } mdb_ps_proc_t; |
| |
| int |
| ps(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) |
| { |
| uint_t prt_flags = 0; |
| mdb_ps_proc_t pr; |
| struct pid pid, pgid, sid; |
| sess_t session; |
| cred_t cred; |
| task_t tk; |
| kproject_t pj; |
| zone_t zn; |
| |
| if (!(flags & DCMD_ADDRSPEC)) { |
| if (mdb_walk_dcmd("proc", "ps", argc, argv) == -1) { |
| mdb_warn("can't walk 'proc'"); |
| return (DCMD_ERR); |
| } |
| return (DCMD_OK); |
| } |
| |
| if (mdb_getopts(argc, argv, |
| 'f', MDB_OPT_SETBITS, PS_PSARGS, &prt_flags, |
| 'l', MDB_OPT_SETBITS, PS_PRTLWPS, &prt_flags, |
| 'T', MDB_OPT_SETBITS, PS_TASKS, &prt_flags, |
| 'P', MDB_OPT_SETBITS, PS_PROJECTS, &prt_flags, |
| 'z', MDB_OPT_SETBITS, PS_ZONES, &prt_flags, |
| 't', MDB_OPT_SETBITS, PS_PRTTHREADS, &prt_flags, NULL) != argc) |
| return (DCMD_USAGE); |
| |
| if (DCMD_HDRSPEC(flags)) { |
| mdb_printf("%<u>%1s %6s %6s %6s %6s ", |
| "S", "PID", "PPID", "PGID", "SID"); |
| if (prt_flags & PS_TASKS) |
| mdb_printf("%5s ", "TASK"); |
| if (prt_flags & PS_PROJECTS) |
| mdb_printf("%5s ", "PROJ"); |
| if (prt_flags & PS_ZONES) |
| mdb_printf("%5s ", "ZONE"); |
| mdb_printf("%6s %10s %?s %s%</u>\n", |
| "UID", "FLAGS", "ADDR", "NAME"); |
| } |
| |
| if (mdb_ctf_vread(&pr, "proc_t", "mdb_ps_proc_t", addr, 0) == -1) |
| return (DCMD_ERR); |
| |
| mdb_vread(&pid, sizeof (pid), (uintptr_t)pr.p_pidp); |
| mdb_vread(&pgid, sizeof (pgid), (uintptr_t)pr.p_pgidp); |
| mdb_vread(&cred, sizeof (cred), (uintptr_t)pr.p_cred); |
| mdb_vread(&session, sizeof (session), (uintptr_t)pr.p_sessp); |
| mdb_vread(&sid, sizeof (sid), (uintptr_t)session.s_sidp); |
| if (prt_flags & (PS_TASKS | PS_PROJECTS)) |
| mdb_vread(&tk, sizeof (tk), (uintptr_t)pr.p_task); |
| if (prt_flags & PS_PROJECTS) |
| mdb_vread(&pj, sizeof (pj), (uintptr_t)tk.tk_proj); |
| if (prt_flags & PS_ZONES) |
| mdb_vread(&zn, sizeof (zn), (uintptr_t)pr.p_zone); |
| |
| mdb_printf("%c %6d %6d %6d %6d ", |
| pstat2ch(pr.p_stat), pid.pid_id, pr.p_ppid, pgid.pid_id, |
| sid.pid_id); |
| if (prt_flags & PS_TASKS) |
| mdb_printf("%5d ", tk.tk_tkid); |
| if (prt_flags & PS_PROJECTS) |
| mdb_printf("%5d ", pj.kpj_id); |
| if (prt_flags & PS_ZONES) |
| mdb_printf("%5d ", zn.zone_id); |
| mdb_printf("%6d 0x%08x %0?p %s\n", |
| cred.cr_uid, pr.p_flag, addr, |
| (prt_flags & PS_PSARGS) ? pr.p_user.u_psargs : pr.p_user.u_comm); |
| |
| if (prt_flags & ~PS_PSARGS) |
| (void) mdb_pwalk("thread", ps_threadprint, &prt_flags, addr); |
| |
| return (DCMD_OK); |
| } |
| |
| #define PG_NEWEST 0x0001 |
| #define PG_OLDEST 0x0002 |
| #define PG_PIPE_OUT 0x0004 |
| #define PG_EXACT_MATCH 0x0008 |
| |
| typedef struct pgrep_data { |
| uint_t pg_flags; |
| uint_t pg_psflags; |
| uintptr_t pg_xaddr; |
| hrtime_t pg_xstart; |
| const char *pg_pat; |
| #ifndef _KMDB |
| regex_t pg_reg; |
| #endif |
| } pgrep_data_t; |
| |
| typedef struct mdb_pgrep_proc { |
| struct { |
| timestruc_t u_start; |
| char u_comm[MAXCOMLEN + 1]; |
| } p_user; |
| } mdb_pgrep_proc_t; |
| |
| /*ARGSUSED*/ |
| static int |
| pgrep_cb(uintptr_t addr, const void *ignored, void *data) |
| { |
| mdb_pgrep_proc_t p; |
| pgrep_data_t *pgp = data; |
| #ifndef _KMDB |
| regmatch_t pmatch; |
| #endif |
| |
| if (mdb_ctf_vread(&p, "proc_t", "mdb_pgrep_proc_t", addr, 0) == -1) |
| return (WALK_ERR); |
| |
| /* |
| * kmdb doesn't have access to the reg* functions, so we fall back |
| * to strstr/strcmp. |
| */ |
| #ifdef _KMDB |
| if ((pgp->pg_flags & PG_EXACT_MATCH) ? |
| (strcmp(p.p_user.u_comm, pgp->pg_pat) != 0) : |
| (strstr(p.p_user.u_comm, pgp->pg_pat) == NULL)) |
| return (WALK_NEXT); |
| #else |
| if (regexec(&pgp->pg_reg, p.p_user.u_comm, 1, &pmatch, 0) != 0) |
| return (WALK_NEXT); |
| |
| if ((pgp->pg_flags & PG_EXACT_MATCH) && |
| (pmatch.rm_so != 0 || p.p_user.u_comm[pmatch.rm_eo] != '\0')) |
| return (WALK_NEXT); |
| #endif |
| |
| if (pgp->pg_flags & (PG_NEWEST | PG_OLDEST)) { |
| hrtime_t start; |
| |
| start = (hrtime_t)p.p_user.u_start.tv_sec * NANOSEC + |
| p.p_user.u_start.tv_nsec; |
| |
| if (pgp->pg_flags & PG_NEWEST) { |
| if (pgp->pg_xaddr == NULL || start > pgp->pg_xstart) { |
| pgp->pg_xaddr = addr; |
| pgp->pg_xstart = start; |
| } |
| } else { |
| if (pgp->pg_xaddr == NULL || start < pgp->pg_xstart) { |
| pgp->pg_xaddr = addr; |
| pgp->pg_xstart = start; |
| } |
| } |
| |
| } else if (pgp->pg_flags & PG_PIPE_OUT) { |
| mdb_printf("%p\n", addr); |
| |
| } else { |
| if (mdb_call_dcmd("ps", addr, pgp->pg_psflags, 0, NULL) != 0) { |
| mdb_warn("can't invoke 'ps'"); |
| return (WALK_DONE); |
| } |
| pgp->pg_psflags &= ~DCMD_LOOPFIRST; |
| } |
| |
| return (WALK_NEXT); |
| } |
| |
| /*ARGSUSED*/ |
| int |
| pgrep(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) |
| { |
| pgrep_data_t pg; |
| int i; |
| #ifndef _KMDB |
| int err; |
| #endif |
| |
| if (flags & DCMD_ADDRSPEC) |
| return (DCMD_USAGE); |
| |
| pg.pg_flags = 0; |
| pg.pg_xaddr = 0; |
| |
| i = mdb_getopts(argc, argv, |
| 'n', MDB_OPT_SETBITS, PG_NEWEST, &pg.pg_flags, |
| 'o', MDB_OPT_SETBITS, PG_OLDEST, &pg.pg_flags, |
| 'x', MDB_OPT_SETBITS, PG_EXACT_MATCH, &pg.pg_flags, |
| NULL); |
| |
| argc -= i; |
| argv += i; |
| |
| if (argc != 1) |
| return (DCMD_USAGE); |
| |
| /* |
| * -n and -o are mutually exclusive. |
| */ |
| if ((pg.pg_flags & PG_NEWEST) && (pg.pg_flags & PG_OLDEST)) |
| return (DCMD_USAGE); |
| |
| if (argv->a_type != MDB_TYPE_STRING) |
| return (DCMD_USAGE); |
| |
| if (flags & DCMD_PIPE_OUT) |
| pg.pg_flags |= PG_PIPE_OUT; |
| |
| pg.pg_pat = argv->a_un.a_str; |
| if (DCMD_HDRSPEC(flags)) |
| pg.pg_psflags = DCMD_ADDRSPEC | DCMD_LOOP | DCMD_LOOPFIRST; |
| else |
| pg.pg_psflags = DCMD_ADDRSPEC | DCMD_LOOP; |
| |
| #ifndef _KMDB |
| if ((err = regcomp(&pg.pg_reg, pg.pg_pat, REG_EXTENDED)) != 0) { |
| size_t nbytes; |
| char *buf; |
| |
| nbytes = regerror(err, &pg.pg_reg, NULL, 0); |
| buf = mdb_alloc(nbytes + 1, UM_SLEEP | UM_GC); |
| (void) regerror(err, &pg.pg_reg, buf, nbytes); |
| mdb_warn("%s\n", buf); |
| |
| return (DCMD_ERR); |
| } |
| #endif |
| |
| if (mdb_walk("proc", pgrep_cb, &pg) != 0) { |
| mdb_warn("can't walk 'proc'"); |
| return (DCMD_ERR); |
| } |
| |
| if (pg.pg_xaddr != 0 && (pg.pg_flags & (PG_NEWEST | PG_OLDEST))) { |
| if (pg.pg_flags & PG_PIPE_OUT) { |
| mdb_printf("%p\n", pg.pg_xaddr); |
| } else { |
| if (mdb_call_dcmd("ps", pg.pg_xaddr, pg.pg_psflags, |
| 0, NULL) != 0) { |
| mdb_warn("can't invoke 'ps'"); |
| return (DCMD_ERR); |
| } |
| } |
| } |
| |
| return (DCMD_OK); |
| } |
| |
| int |
| task(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) |
| { |
| task_t tk; |
| kproject_t pj; |
| |
| if (!(flags & DCMD_ADDRSPEC)) { |
| if (mdb_walk_dcmd("task_cache", "task", argc, argv) == -1) { |
| mdb_warn("can't walk task_cache"); |
| return (DCMD_ERR); |
| } |
| return (DCMD_OK); |
| } |
| if (DCMD_HDRSPEC(flags)) { |
| mdb_printf("%<u>%?s %6s %6s %6s %6s %10s%</u>\n", |
| "ADDR", "TASKID", "PROJID", "ZONEID", "REFCNT", "FLAGS"); |
| } |
| if (mdb_vread(&tk, sizeof (task_t), addr) == -1) { |
| mdb_warn("can't read task_t structure at %p", addr); |
| return (DCMD_ERR); |
| } |
| if (mdb_vread(&pj, sizeof (kproject_t), (uintptr_t)tk.tk_proj) == -1) { |
| mdb_warn("can't read project_t structure at %p", addr); |
| return (DCMD_ERR); |
| } |
| mdb_printf("%0?p %6d %6d %6d %6u 0x%08x\n", |
| addr, tk.tk_tkid, pj.kpj_id, pj.kpj_zoneid, tk.tk_hold_count, |
| tk.tk_flags); |
| return (DCMD_OK); |
| } |
| |
| int |
| project(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) |
| { |
| kproject_t pj; |
| |
| if (!(flags & DCMD_ADDRSPEC)) { |
| if (mdb_walk_dcmd("projects", "project", argc, argv) == -1) { |
| mdb_warn("can't walk projects"); |
| return (DCMD_ERR); |
| } |
| return (DCMD_OK); |
| } |
| if (DCMD_HDRSPEC(flags)) { |
| mdb_printf("%<u>%?s %6s %6s %6s%</u>\n", |
| "ADDR", "PROJID", "ZONEID", "REFCNT"); |
| } |
| if (mdb_vread(&pj, sizeof (kproject_t), addr) == -1) { |
| mdb_warn("can't read kproject_t structure at %p", addr); |
| return (DCMD_ERR); |
| } |
| mdb_printf("%0?p %6d %6d %6u\n", addr, pj.kpj_id, pj.kpj_zoneid, |
| pj.kpj_count); |
| return (DCMD_OK); |
| } |
| |
| /* walk callouts themselves, either by list or id hash. */ |
| int |
| callout_walk_init(mdb_walk_state_t *wsp) |
| { |
| if (wsp->walk_addr == NULL) { |
| mdb_warn("callout doesn't support global walk"); |
| return (WALK_ERR); |
| } |
| wsp->walk_data = mdb_alloc(sizeof (callout_t), UM_SLEEP); |
| return (WALK_NEXT); |
| } |
| |
| #define CALLOUT_WALK_BYLIST 0 |
| #define CALLOUT_WALK_BYID 1 |
| |
| /* the walker arg switches between walking by list (0) and walking by id (1). */ |
| int |
| callout_walk_step(mdb_walk_state_t *wsp) |
| { |
| int retval; |
| |
| if (wsp->walk_addr == NULL) { |
| return (WALK_DONE); |
| } |
| if (mdb_vread(wsp->walk_data, sizeof (callout_t), |
| wsp->walk_addr) == -1) { |
| mdb_warn("failed to read callout at %p", wsp->walk_addr); |
| return (WALK_DONE); |
| } |
| retval = wsp->walk_callback(wsp->walk_addr, wsp->walk_data, |
| wsp->walk_cbdata); |
| |
| if ((ulong_t)wsp->walk_arg == CALLOUT_WALK_BYID) { |
| wsp->walk_addr = |
| (uintptr_t)(((callout_t *)wsp->walk_data)->c_idnext); |
| } else { |
| wsp->walk_addr = |
| (uintptr_t)(((callout_t *)wsp->walk_data)->c_clnext); |
| } |
| |
| return (retval); |
| } |
| |
| void |
| callout_walk_fini(mdb_walk_state_t *wsp) |
| { |
| mdb_free(wsp->walk_data, sizeof (callout_t)); |
| } |
| |
| /* |
| * walker for callout lists. This is different from hashes and callouts. |
| * Thankfully, it's also simpler. |
| */ |
| int |
| callout_list_walk_init(mdb_walk_state_t *wsp) |
| { |
| if (wsp->walk_addr == NULL) { |
| mdb_warn("callout list doesn't support global walk"); |
| return (WALK_ERR); |
| } |
| wsp->walk_data = mdb_alloc(sizeof (callout_list_t), UM_SLEEP); |
| return (WALK_NEXT); |
| } |
| |
| int |
| callout_list_walk_step(mdb_walk_state_t *wsp) |
| { |
| int retval; |
| |
| if (wsp->walk_addr == NULL) { |
| return (WALK_DONE); |
| } |
| if (mdb_vread(wsp->walk_data, sizeof (callout_list_t), |
| wsp->walk_addr) != sizeof (callout_list_t)) { |
| mdb_warn("failed to read callout_list at %p", wsp->walk_addr); |
| return (WALK_ERR); |
| } |
| retval = wsp->walk_callback(wsp->walk_addr, wsp->walk_data, |
| wsp->walk_cbdata); |
| |
| wsp->walk_addr = (uintptr_t) |
| (((callout_list_t *)wsp->walk_data)->cl_next); |
| |
| return (retval); |
| } |
| |
| void |
| callout_list_walk_fini(mdb_walk_state_t *wsp) |
| { |
| mdb_free(wsp->walk_data, sizeof (callout_list_t)); |
| } |
| |
| /* routines/structs to walk callout table(s) */ |
| typedef struct cot_data { |
| callout_table_t *ct0; |
| callout_table_t ct; |
| callout_hash_t cot_idhash[CALLOUT_BUCKETS]; |
| callout_hash_t cot_clhash[CALLOUT_BUCKETS]; |
| kstat_named_t ct_kstat_data[CALLOUT_NUM_STATS]; |
| int cotndx; |
| int cotsize; |
| } cot_data_t; |
| |
| int |
| callout_table_walk_init(mdb_walk_state_t *wsp) |
| { |
| int max_ncpus; |
| cot_data_t *cot_walk_data; |
| |
| cot_walk_data = mdb_alloc(sizeof (cot_data_t), UM_SLEEP); |
| |
| if (wsp->walk_addr == NULL) { |
| if (mdb_readvar(&cot_walk_data->ct0, "callout_table") == -1) { |
| mdb_warn("failed to read 'callout_table'"); |
| return (WALK_ERR); |
| } |
| if (mdb_readvar(&max_ncpus, "max_ncpus") == -1) { |
| mdb_warn("failed to get callout_table array size"); |
| return (WALK_ERR); |
| } |
| cot_walk_data->cotsize = CALLOUT_NTYPES * max_ncpus; |
| wsp->walk_addr = (uintptr_t)cot_walk_data->ct0; |
| } else { |
| /* not a global walk */ |
| cot_walk_data->cotsize = 1; |
| } |
| |
| cot_walk_data->cotndx = 0; |
| wsp->walk_data = cot_walk_data; |
| |
| return (WALK_NEXT); |
| } |
| |
| int |
| callout_table_walk_step(mdb_walk_state_t *wsp) |
| { |
| int retval; |
| cot_data_t *cotwd = (cot_data_t *)wsp->walk_data; |
| size_t size; |
| |
| if (cotwd->cotndx >= cotwd->cotsize) { |
| return (WALK_DONE); |
| } |
| if (mdb_vread(&(cotwd->ct), sizeof (callout_table_t), |
| wsp->walk_addr) != sizeof (callout_table_t)) { |
| mdb_warn("failed to read callout_table at %p", wsp->walk_addr); |
| return (WALK_ERR); |
| } |
| |
| size = sizeof (callout_hash_t) * CALLOUT_BUCKETS; |
| if (cotwd->ct.ct_idhash != NULL) { |
| if (mdb_vread(cotwd->cot_idhash, size, |
| (uintptr_t)(cotwd->ct.ct_idhash)) != size) { |
| mdb_warn("failed to read id_hash at %p", |
| cotwd->ct.ct_idhash); |
| return (WALK_ERR); |
| } |
| } |
| if (cotwd->ct.ct_clhash != NULL) { |
| if (mdb_vread(&(cotwd->cot_clhash), size, |
| (uintptr_t)cotwd->ct.ct_clhash) == -1) { |
| mdb_warn("failed to read cl_hash at %p", |
| cotwd->ct.ct_clhash); |
| return (WALK_ERR); |
| } |
| } |
| size = sizeof (kstat_named_t) * CALLOUT_NUM_STATS; |
| if (cotwd->ct.ct_kstat_data != NULL) { |
| if (mdb_vread(&(cotwd->ct_kstat_data), size, |
| (uintptr_t)cotwd->ct.ct_kstat_data) == -1) { |
| mdb_warn("failed to read kstats at %p", |
| cotwd->ct.ct_kstat_data); |
| return (WALK_ERR); |
| } |
| } |
| retval = wsp->walk_callback(wsp->walk_addr, (void *)cotwd, |
| wsp->walk_cbdata); |
| |
| cotwd->cotndx++; |
| if (cotwd->cotndx >= cotwd->cotsize) { |
| return (WALK_DONE); |
| } |
| wsp->walk_addr = (uintptr_t)((char *)wsp->walk_addr + |
| sizeof (callout_table_t)); |
| |
| return (retval); |
| } |
| |
| void |
| callout_table_walk_fini(mdb_walk_state_t *wsp) |
| { |
| mdb_free(wsp->walk_data, sizeof (cot_data_t)); |
| } |
| |
| static const char *co_typenames[] = { "R", "N" }; |
| |
| #define CO_PLAIN_ID(xid) ((xid) & CALLOUT_ID_MASK) |
| |
| #define TABLE_TO_SEQID(x) ((x) >> CALLOUT_TYPE_BITS) |
| |
| /* callout flags, in no particular order */ |
| #define COF_REAL 0x00000001 |
| #define COF_NORM 0x00000002 |
| #define COF_LONG 0x00000004 |
| #define COF_SHORT 0x00000008 |
| #define COF_EMPTY 0x00000010 |
| #define COF_TIME 0x00000020 |
| #define COF_BEFORE 0x00000040 |
| #define COF_AFTER 0x00000080 |
| #define COF_SEQID 0x00000100 |
| #define COF_FUNC 0x00000200 |
| #define COF_ADDR 0x00000400 |
| #define COF_EXEC 0x00000800 |
| #define COF_HIRES 0x00001000 |
| #define COF_ABS 0x00002000 |
| #define COF_TABLE 0x00004000 |
| #define COF_BYIDH 0x00008000 |
| #define COF_FREE 0x00010000 |
| #define COF_LIST 0x00020000 |
| #define COF_EXPREL 0x00040000 |
| #define COF_HDR 0x00080000 |
| #define COF_VERBOSE 0x00100000 |
| #define COF_LONGLIST 0x00200000 |
| #define COF_THDR 0x00400000 |
| #define COF_LHDR 0x00800000 |
| #define COF_CHDR 0x01000000 |
| #define COF_PARAM 0x02000000 |
| #define COF_DECODE 0x04000000 |
| #define COF_HEAP 0x08000000 |
| #define COF_QUEUE 0x10000000 |
| |
| /* show real and normal, short and long, expired and unexpired. */ |
| #define COF_DEFAULT (COF_REAL | COF_NORM | COF_LONG | COF_SHORT) |
| |
| #define COF_LIST_FLAGS \ |
| (CALLOUT_LIST_FLAG_HRESTIME | CALLOUT_LIST_FLAG_ABSOLUTE) |
| |
| /* private callout data for callback functions */ |
| typedef struct callout_data { |
| uint_t flags; /* COF_* */ |
| cpu_t *cpu; /* cpu pointer if given */ |
| int seqid; /* cpu seqid, or -1 */ |
| hrtime_t time; /* expiration time value */ |
| hrtime_t atime; /* expiration before value */ |
| hrtime_t btime; /* expiration after value */ |
| uintptr_t funcaddr; /* function address or NULL */ |
| uintptr_t param; /* parameter to function or NULL */ |
| hrtime_t now; /* current system time */ |
| int nsec_per_tick; /* for conversions */ |
| ulong_t ctbits; /* for decoding xid */ |
| callout_table_t *co_table; /* top of callout table array */ |
| int ndx; /* table index. */ |
| int bucket; /* which list/id bucket are we in */ |
| hrtime_t exp; /* expire time */ |
| int list_flags; /* copy of cl_flags */ |
| } callout_data_t; |
| |
| /* this callback does the actual callback itself (finally). */ |
| /*ARGSUSED*/ |
| static int |
| callouts_cb(uintptr_t addr, const void *data, void *priv) |
| { |
| callout_data_t *coargs = (callout_data_t *)priv; |
| callout_t *co = (callout_t *)data; |
| int tableid, list_flags; |
| callout_id_t coid; |
| |
| if ((coargs == NULL) || (co == NULL)) { |
| return (WALK_ERR); |
| } |
| |
| if ((coargs->flags & COF_FREE) && !(co->c_xid & CALLOUT_ID_FREE)) { |
| /* |
| * The callout must have been reallocated. No point in |
| * walking any more. |
| */ |
| return (WALK_DONE); |
| } |
| if (!(coargs->flags & COF_FREE) && (co->c_xid & CALLOUT_ID_FREE)) { |
| /* |
| * The callout must have been freed. No point in |
| * walking any more. |
| */ |
| return (WALK_DONE); |
| } |
| if ((coargs->flags & COF_FUNC) && |
| (coargs->funcaddr != (uintptr_t)co->c_func)) { |
| return (WALK_NEXT); |
| } |
| if ((coargs->flags & COF_PARAM) && |
| (coargs->param != (uintptr_t)co->c_arg)) { |
| return (WALK_NEXT); |
| } |
| if (!(coargs->flags & COF_LONG) && (co->c_xid & CALLOUT_LONGTERM)) { |
| return (WALK_NEXT); |
| } |
| if (!(coargs->flags & COF_SHORT) && !(co->c_xid & CALLOUT_LONGTERM)) { |
| return (WALK_NEXT); |
| } |
| if ((coargs->flags & COF_EXEC) && !(co->c_xid & CALLOUT_EXECUTING)) { |
| return (WALK_NEXT); |
| } |
| /* it is possible we don't have the exp time or flags */ |
| if (coargs->flags & COF_BYIDH) { |
| if (!(coargs->flags & COF_FREE)) { |
| /* we have to fetch the expire time ourselves. */ |
| if (mdb_vread(&coargs->exp, sizeof (hrtime_t), |
| (uintptr_t)co->c_list + offsetof(callout_list_t, |
| cl_expiration)) == -1) { |
| mdb_warn("failed to read expiration " |
| "time from %p", co->c_list); |
| coargs->exp = 0; |
| } |
| /* and flags. */ |
| if (mdb_vread(&coargs->list_flags, sizeof (int), |
| (uintptr_t)co->c_list + offsetof(callout_list_t, |
| cl_flags)) == -1) { |
| mdb_warn("failed to read list flags" |
| "from %p", co->c_list); |
| coargs->list_flags = 0; |
| } |
| } else { |
| /* free callouts can't use list pointer. */ |
| coargs->exp = 0; |
| coargs->list_flags = 0; |
| } |
| if (coargs->exp != 0) { |
| if ((coargs->flags & COF_TIME) && |
| (coargs->exp != coargs->time)) { |
| return (WALK_NEXT); |
| } |
| if ((coargs->flags & COF_BEFORE) && |
| (coargs->exp > coargs->btime)) { |
| return (WALK_NEXT); |
| } |
| if ((coargs->flags & COF_AFTER) && |
| (coargs->exp < coargs->atime)) { |
| return (WALK_NEXT); |
| } |
| } |
| /* tricky part, since both HIRES and ABS can be set */ |
| list_flags = coargs->list_flags; |
| if ((coargs->flags & COF_HIRES) && (coargs->flags & COF_ABS)) { |
| /* both flags are set, only skip "regular" ones */ |
| if (! (list_flags & COF_LIST_FLAGS)) { |
| return (WALK_NEXT); |
| } |
| } else { |
| /* individual flags, or no flags */ |
| if ((coargs->flags & COF_HIRES) && |
| !(list_flags & CALLOUT_LIST_FLAG_HRESTIME)) { |
| return (WALK_NEXT); |
| } |
| if ((coargs->flags & COF_ABS) && |
| !(list_flags & CALLOUT_LIST_FLAG_ABSOLUTE)) { |
| return (WALK_NEXT); |
| } |
| } |
| /* |
| * We do the checks for COF_HEAP and COF_QUEUE here only if we |
| * are traversing BYIDH. If the traversal is by callout list, |
| * we do this check in callout_list_cb() to be more |
| * efficient. |
| */ |
| if ((coargs->flags & COF_HEAP) && |
| !(list_flags & CALLOUT_LIST_FLAG_HEAPED)) { |
| return (WALK_NEXT); |
| } |
| |
| if ((coargs->flags & COF_QUEUE) && |
| !(list_flags & CALLOUT_LIST_FLAG_QUEUED)) { |
| return (WALK_NEXT); |
| } |
| } |
| |
| #define callout_table_mask ((1 << coargs->ctbits) - 1) |
| tableid = CALLOUT_ID_TO_TABLE(co->c_xid); |
| #undef callout_table_mask |
| coid = CO_PLAIN_ID(co->c_xid); |
| |
| if ((coargs->flags & COF_CHDR) && !(coargs->flags & COF_ADDR)) { |
| /* |
| * We need to print the headers. If walking by id, then |
| * the list header isn't printed, so we must include |
| * that info here. |
| */ |
| if (!(coargs->flags & COF_VERBOSE)) { |
| mdb_printf("%<u>%3s %-1s %-14s %</u>", |
| "SEQ", "T", "EXP"); |
| } else if (coargs->flags & COF_BYIDH) { |
| mdb_printf("%<u>%-14s %</u>", "EXP"); |
| } |
| mdb_printf("%<u>%-4s %-?s %-20s%</u>", |
| "XHAL", "XID", "FUNC(ARG)"); |
| if (coargs->flags & COF_LONGLIST) { |
| mdb_printf("%<u> %-?s %-?s %-?s %-?s%</u>", |
| "PREVID", "NEXTID", "PREVL", "NEXTL"); |
| mdb_printf("%<u> %-?s %-4s %-?s%</u>", |
| "DONE", "UTOS", "THREAD"); |
| } |
| mdb_printf("\n"); |
| coargs->flags &= ~COF_CHDR; |
| coargs->flags |= (COF_THDR | COF_LHDR); |
| } |
| |
| if (!(coargs->flags & COF_ADDR)) { |
| if (!(coargs->flags & COF_VERBOSE)) { |
| mdb_printf("%-3d %1s %-14llx ", |
| TABLE_TO_SEQID(tableid), |
| co_typenames[tableid & CALLOUT_TYPE_MASK], |
| (coargs->flags & COF_EXPREL) ? |
| coargs->exp - coargs->now : coargs->exp); |
| } else if (coargs->flags & COF_BYIDH) { |
| mdb_printf("%-14x ", |
| (coargs->flags & COF_EXPREL) ? |
| coargs->exp - coargs->now : coargs->exp); |
| } |
| list_flags = coargs->list_flags; |
| mdb_printf("%1s%1s%1s%1s %-?llx %a(%p)", |
| (co->c_xid & CALLOUT_EXECUTING) ? "X" : " ", |
| (list_flags & CALLOUT_LIST_FLAG_HRESTIME) ? "H" : " ", |
| (list_flags & CALLOUT_LIST_FLAG_ABSOLUTE) ? "A" : " ", |
| (co->c_xid & CALLOUT_LONGTERM) ? "L" : " ", |
| (long long)coid, co->c_func, co->c_arg); |
| if (coargs->flags & COF_LONGLIST) { |
| mdb_printf(" %-?p %-?p %-?p %-?p", |
| co->c_idprev, co->c_idnext, co->c_clprev, |
| co->c_clnext); |
| mdb_printf(" %-?p %-4d %-0?p", |
| co->c_done, co->c_waiting, co->c_executor); |
| } |
| } else { |
| /* address only */ |
| mdb_printf("%-0p", addr); |
| } |
| mdb_printf("\n"); |
| return (WALK_NEXT); |
| } |
| |
| /* this callback is for callout list handling. idhash is done by callout_t_cb */ |
| /*ARGSUSED*/ |
| static int |
| callout_list_cb(uintptr_t addr, const void *data, void *priv) |
| { |
| callout_data_t *coargs = (callout_data_t *)priv; |
| callout_list_t *cl = (callout_list_t *)data; |
| callout_t *coptr; |
| int list_flags; |
| |
| if ((coargs == NULL) || (cl == NULL)) { |
| return (WALK_ERR); |
| } |
| |
| coargs->exp = cl->cl_expiration; |
| coargs->list_flags = cl->cl_flags; |
| if ((coargs->flags & COF_FREE) && |
| !(cl->cl_flags & CALLOUT_LIST_FLAG_FREE)) { |
| /* |
| * The callout list must have been reallocated. No point in |
| * walking any more. |
| */ |
| return (WALK_DONE); |
| } |
| if (!(coargs->flags & COF_FREE) && |
| (cl->cl_flags & CALLOUT_LIST_FLAG_FREE)) { |
| /* |
| * The callout list must have been freed. No point in |
| * walking any more. |
| */ |
| return (WALK_DONE); |
| } |
| if ((coargs->flags & COF_TIME) && |
| (cl->cl_expiration != coargs->time)) { |
| return (WALK_NEXT); |
| } |
| if ((coargs->flags & COF_BEFORE) && |
| (cl->cl_expiration > coargs->btime)) { |
| return (WALK_NEXT); |
| } |
| if ((coargs->flags & COF_AFTER) && |
| (cl->cl_expiration < coargs->atime)) { |
| return (WALK_NEXT); |
| } |
| if (!(coargs->flags & COF_EMPTY) && |
| (cl->cl_callouts.ch_head == NULL)) { |
| return (WALK_NEXT); |
| } |
| /* FOUR cases, each different, !A!B, !AB, A!B, AB */ |
| if ((coargs->flags & COF_HIRES) && (coargs->flags & COF_ABS)) { |
| /* both flags are set, only skip "regular" ones */ |
| if (! (cl->cl_flags & COF_LIST_FLAGS)) { |
| return (WALK_NEXT); |
| } |
| } else { |
| if ((coargs->flags & COF_HIRES) && |
| !(cl->cl_flags & CALLOUT_LIST_FLAG_HRESTIME)) { |
| return (WALK_NEXT); |
| } |
| if ((coargs->flags & COF_ABS) && |
| !(cl->cl_flags & CALLOUT_LIST_FLAG_ABSOLUTE)) { |
| return (WALK_NEXT); |
| } |
| } |
| |
| if ((coargs->flags & COF_HEAP) && |
| !(coargs->list_flags & CALLOUT_LIST_FLAG_HEAPED)) { |
| return (WALK_NEXT); |
| } |
| |
| if ((coargs->flags & COF_QUEUE) && |
| !(coargs->list_flags & CALLOUT_LIST_FLAG_QUEUED)) { |
| return (WALK_NEXT); |
| } |
| |
| if ((coargs->flags & COF_LHDR) && !(coargs->flags & COF_ADDR) && |
| (coargs->flags & (COF_LIST | COF_VERBOSE))) { |
| if (!(coargs->flags & COF_VERBOSE)) { |
| /* don't be redundant again */ |
| mdb_printf("%<u>SEQ T %</u>"); |
| } |
| mdb_printf("%<u>EXP HA BUCKET " |
| "CALLOUTS %</u>"); |
| |
| if (coargs->flags & COF_LONGLIST) { |
| mdb_printf("%<u> %-?s %-?s%</u>", |
| "PREV", "NEXT"); |
| } |
| mdb_printf("\n"); |
| coargs->flags &= ~COF_LHDR; |
| coargs->flags |= (COF_THDR | COF_CHDR); |
| } |
| if (coargs->flags & (COF_LIST | COF_VERBOSE)) { |
| if (!(coargs->flags & COF_ADDR)) { |
| if (!(coargs->flags & COF_VERBOSE)) { |
| mdb_printf("%3d %1s ", |
| TABLE_TO_SEQID(coargs->ndx), |
| co_typenames[coargs->ndx & |
| CALLOUT_TYPE_MASK]); |
| } |
| |
| list_flags = coargs->list_flags; |
| mdb_printf("%-14llx %1s%1s %-6d %-0?p ", |
| (coargs->flags & COF_EXPREL) ? |
| coargs->exp - coargs->now : coargs->exp, |
| (list_flags & CALLOUT_LIST_FLAG_HRESTIME) ? |
| "H" : " ", |
| (list_flags & CALLOUT_LIST_FLAG_ABSOLUTE) ? |
| "A" : " ", |
| coargs->bucket, cl->cl_callouts.ch_head); |
| |
| if (coargs->flags & COF_LONGLIST) { |
| mdb_printf(" %-?p %-?p", |
| cl->cl_prev, cl->cl_next); |
| } |
| } else { |
| /* address only */ |
| mdb_printf("%-0p", addr); |
| } |
| mdb_printf("\n"); |
| if (coargs->flags & COF_LIST) { |
| return (WALK_NEXT); |
| } |
| } |
| /* yet another layer as we walk the actual callouts via list. */ |
| if (cl->cl_callouts.ch_head == NULL) { |
| return (WALK_NEXT); |
| } |
| /* free list structures do not have valid callouts off of them. */ |
| if (coargs->flags & COF_FREE) { |
| return (WALK_NEXT); |
| } |
| coptr = (callout_t *)cl->cl_callouts.ch_head; |
| |
| if (coargs->flags & COF_VERBOSE) { |
| mdb_inc_indent(4); |
| } |
| /* |
| * walk callouts using yet another callback routine. |
| * we use callouts_bytime because id hash is handled via |
| * the callout_t_cb callback. |
| */ |
| if (mdb_pwalk("callouts_bytime", callouts_cb, coargs, |
| (uintptr_t)coptr) == -1) { |
| mdb_warn("cannot walk callouts at %p", coptr); |
| return (WALK_ERR); |
| } |
| if (coargs->flags & COF_VERBOSE) { |
| mdb_dec_indent(4); |
| } |
| |
| return (WALK_NEXT); |
| } |
| |
| /* this callback handles the details of callout table walking. */ |
| static int |
| callout_t_cb(uintptr_t addr, const void *data, void *priv) |
| { |
| callout_data_t *coargs = (callout_data_t *)priv; |
| cot_data_t *cotwd = (cot_data_t *)data; |
| callout_table_t *ct = &(cotwd->ct); |
| int index, seqid, cotype; |
| int i; |
| callout_list_t *clptr; |
| callout_t *coptr; |
| |
| if ((coargs == NULL) || (ct == NULL) || (coargs->co_table == NULL)) { |
| return (WALK_ERR); |
| } |
| |
| index = ((char *)addr - (char *)coargs->co_table) / |
| sizeof (callout_table_t); |
| cotype = index & CALLOUT_TYPE_MASK; |
| seqid = TABLE_TO_SEQID(index); |
| |
| if ((coargs->flags & COF_SEQID) && (coargs->seqid != seqid)) { |
| return (WALK_NEXT); |
| } |
| |
| if (!(coargs->flags & COF_REAL) && (cotype == CALLOUT_REALTIME)) { |
| return (WALK_NEXT); |
| } |
| |
| if (!(coargs->flags & COF_NORM) && (cotype == CALLOUT_NORMAL)) { |
| return (WALK_NEXT); |
| } |
| |
| if (!(coargs->flags & COF_EMPTY) && ( |
| (ct->ct_heap == NULL) || (ct->ct_cyclic == NULL))) { |
| return (WALK_NEXT); |
| } |
| |
| if ((coargs->flags & COF_THDR) && !(coargs->flags & COF_ADDR) && |
| (coargs->flags & (COF_TABLE | COF_VERBOSE))) { |
| /* print table hdr */ |
| mdb_printf("%<u>%-3s %-1s %-?s %-?s %-?s %-?s%</u>", |
| "SEQ", "T", "FREE", "LFREE", "CYCLIC", "HEAP"); |
| coargs->flags &= ~COF_THDR; |
| coargs->flags |= (COF_LHDR | COF_CHDR); |
| if (coargs->flags & COF_LONGLIST) { |
| /* more info! */ |
| mdb_printf("%<u> %-T%-7s %-7s %-?s %-?s %-?s" |
| " %-?s %-?s %-?s%</u>", |
| "HEAPNUM", "HEAPMAX", "TASKQ", "EXPQ", "QUE", |
| "PEND", "FREE", "LOCK"); |
| } |
| mdb_printf("\n"); |
| } |
| if (coargs->flags & (COF_TABLE | COF_VERBOSE)) { |
| if (!(coargs->flags & COF_ADDR)) { |
| mdb_printf("%-3d %-1s %-0?p %-0?p %-0?p %-?p", |
| seqid, co_typenames[cotype], |
| ct->ct_free, ct->ct_lfree, ct->ct_cyclic, |
| ct->ct_heap); |
| if (coargs->flags & COF_LONGLIST) { |
| /* more info! */ |
| mdb_printf(" %-7d %-7d %-?p %-?p %-?p" |
| " %-?lld %-?lld %-?p", |
| ct->ct_heap_num, ct->ct_heap_max, |
| ct->ct_taskq, ct->ct_expired.ch_head, |
| ct->ct_queue.ch_head, |
| cotwd->ct_timeouts_pending, |
| cotwd->ct_allocations - |
| cotwd->ct_timeouts_pending, |
| ct->ct_mutex); |
| } |
| } else { |
| /* address only */ |
| mdb_printf("%-0?p", addr); |
| } |
| mdb_printf("\n"); |
| if (coargs->flags & COF_TABLE) { |
| return (WALK_NEXT); |
| } |
| } |
| |
| coargs->ndx = index; |
| if (coargs->flags & COF_VERBOSE) { |
| mdb_inc_indent(4); |
| } |
| /* keep digging. */ |
| if (!(coargs->flags & COF_BYIDH)) { |
| /* walk the list hash table */ |
| if (coargs->flags & COF_FREE) { |
| clptr = ct->ct_lfree; |
| coargs->bucket = 0; |
| if (clptr == NULL) { |
| return (WALK_NEXT); |
| } |
| if (mdb_pwalk("callout_list", callout_list_cb, coargs, |
| (uintptr_t)clptr) == -1) { |
| mdb_warn("cannot walk callout free list at %p", |
| clptr); |
| return (WALK_ERR); |
| } |
| } else { |
| /* first print the expired list. */ |
| clptr = (callout_list_t *)ct->ct_expired.ch_head; |
| if (clptr != NULL) { |
| coargs->bucket = -1; |
| if (mdb_pwalk("callout_list", callout_list_cb, |
| coargs, (uintptr_t)clptr) == -1) { |
| mdb_warn("cannot walk callout_list" |
| " at %p", clptr); |
| return (WALK_ERR); |
| } |
| } |
| /* then, print the callout queue */ |
| clptr = (callout_list_t *)ct->ct_queue.ch_head; |
| if (clptr != NULL) { |
| coargs->bucket = -1; |
| if (mdb_pwalk("callout_list", callout_list_cb, |
| coargs, (uintptr_t)clptr) == -1) { |
| mdb_warn("cannot walk callout_list" |
| " at %p", clptr); |
| return (WALK_ERR); |
| } |
| } |
| for (i = 0; i < CALLOUT_BUCKETS; i++) { |
| if (ct->ct_clhash == NULL) { |
| /* nothing to do */ |
| break; |
| } |
| if (cotwd->cot_clhash[i].ch_head == NULL) { |
| continue; |
| } |
| clptr = (callout_list_t *) |
| cotwd->cot_clhash[i].ch_head; |
| coargs->bucket = i; |
| /* walk list with callback routine. */ |
| if (mdb_pwalk("callout_list", callout_list_cb, |
| coargs, (uintptr_t)clptr) == -1) { |
| mdb_warn("cannot walk callout_list" |
| " at %p", clptr); |
| return (WALK_ERR); |
| } |
| } |
| } |
| } else { |
| /* walk the id hash table. */ |
| if (coargs->flags & COF_FREE) { |
| coptr = ct->ct_free; |
| coargs->bucket = 0; |
| if (coptr == NULL) { |
| return (WALK_NEXT); |
| } |
| if (mdb_pwalk("callouts_byid", callouts_cb, coargs, |
| (uintptr_t)coptr) == -1) { |
| mdb_warn("cannot walk callout id free list" |
| " at %p", coptr); |
| return (WALK_ERR); |
| } |
| } else { |
| for (i = 0; i < CALLOUT_BUCKETS; i++) { |
| if (ct->ct_idhash == NULL) { |
| break; |
| } |
| coptr = (callout_t *) |
| cotwd->cot_idhash[i].ch_head; |
| if (coptr == NULL) { |
| continue; |
| } |
| coargs->bucket = i; |
| |
| /* |
| * walk callouts directly by id. For id |
| * chain, the callout list is just a header, |
| * so there's no need to walk it. |
| */ |
| if (mdb_pwalk("callouts_byid", callouts_cb, |
| coargs, (uintptr_t)coptr) == -1) { |
| mdb_warn("cannot walk callouts at %p", |
| coptr); |
| return (WALK_ERR); |
| } |
| } |
| } |
| } |
| if (coargs->flags & COF_VERBOSE) { |
| mdb_dec_indent(4); |
| } |
| return (WALK_NEXT); |
| } |
| |
| /* |
| * initialize some common info for both callout dcmds. |
| */ |
| int |
| callout_common_init(callout_data_t *coargs) |
| { |
| /* we need a couple of things */ |
| if (mdb_readvar(&(coargs->co_table), "callout_table") == -1) { |
| mdb_warn("failed to read 'callout_table'"); |
| return (DCMD_ERR); |
| } |
| /* need to get now in nsecs. Approximate with hrtime vars */ |
| if (mdb_readsym(&(coargs->now), sizeof (hrtime_t), "hrtime_last") != |
| sizeof (hrtime_t)) { |
| if (mdb_readsym(&(coargs->now), sizeof (hrtime_t), |
| "hrtime_base") != sizeof (hrtime_t)) { |
| mdb_warn("Could not determine current system time"); |
| return (DCMD_ERR); |
| } |
| } |
| |
| if (mdb_readvar(&(coargs->ctbits), "callout_table_bits") == -1) { |
| mdb_warn("failed to read 'callout_table_bits'"); |
| return (DCMD_ERR); |
| } |
| if (mdb_readvar(&(coargs->nsec_per_tick), "nsec_per_tick") == -1) { |
| mdb_warn("failed to read 'nsec_per_tick'"); |
| return (DCMD_ERR); |
| } |
| return (DCMD_OK); |
| } |
| |
| /* |
| * dcmd to print callouts. Optional addr limits to specific table. |
| * Parses lots of options that get passed to callbacks for walkers. |
| * Has it's own help function. |
| */ |
| /*ARGSUSED*/ |
| int |
| callout(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) |
| { |
| callout_data_t coargs; |
| /* getopts doesn't help much with stuff like this */ |
| boolean_t Sflag, Cflag, tflag, aflag, bflag, dflag, kflag; |
| char *funcname = NULL; |
| char *paramstr = NULL; |
| uintptr_t Stmp, Ctmp; /* for getopt. */ |
| int retval; |
| |
| coargs.flags = COF_DEFAULT; |
| Sflag = Cflag = tflag = bflag = aflag = dflag = kflag = FALSE; |
| coargs.seqid = -1; |
| |
| if (mdb_getopts(argc, argv, |
| 'r', MDB_OPT_CLRBITS, COF_NORM, &coargs.flags, |
| 'n', MDB_OPT_CLRBITS, COF_REAL, &coargs.flags, |
| 'l', MDB_OPT_CLRBITS, COF_SHORT, &coargs.flags, |
| 's', MDB_OPT_CLRBITS, COF_LONG, &coargs.flags, |
| 'x', MDB_OPT_SETBITS, COF_EXEC, &coargs.flags, |
| 'h', MDB_OPT_SETBITS, COF_HIRES, &coargs.flags, |
| 'B', MDB_OPT_SETBITS, COF_ABS, &coargs.flags, |
| 'E', MDB_OPT_SETBITS, COF_EMPTY, &coargs.flags, |
| 'd', MDB_OPT_SETBITS, 1, &dflag, |
| 'C', MDB_OPT_UINTPTR_SET, &Cflag, &Ctmp, |
| 'S', MDB_OPT_UINTPTR_SET, &Sflag, &Stmp, |
| 't', MDB_OPT_UINTPTR_SET, &tflag, (uintptr_t *)&coargs.time, |
| 'a', MDB_OPT_UINTPTR_SET, &aflag, (uintptr_t *)&coargs.atime, |
| 'b', MDB_OPT_UINTPTR_SET, &bflag, (uintptr_t *)&coargs.btime, |
| 'k', MDB_OPT_SETBITS, 1, &kflag, |
| 'f', MDB_OPT_STR, &funcname, |
| 'p', MDB_OPT_STR, ¶mstr, |
| 'T', MDB_OPT_SETBITS, COF_TABLE, &coargs.flags, |
| 'D', MDB_OPT_SETBITS, COF_EXPREL, &coargs.flags, |
| 'L', MDB_OPT_SETBITS, COF_LIST, &coargs.flags, |
| 'V', MDB_OPT_SETBITS, COF_VERBOSE, &coargs.flags, |
| 'v', MDB_OPT_SETBITS, COF_LONGLIST, &coargs.flags, |
| 'i', MDB_OPT_SETBITS, COF_BYIDH, &coargs.flags, |
| 'F', MDB_OPT_SETBITS, COF_FREE, &coargs.flags, |
| 'H', MDB_OPT_SETBITS, COF_HEAP, &coargs.flags, |
| 'Q', MDB_OPT_SETBITS, COF_QUEUE, &coargs.flags, |
| 'A', MDB_OPT_SETBITS, COF_ADDR, &coargs.flags, |
| NULL) != argc) { |
| return (DCMD_USAGE); |
| } |
| |
| /* initialize from kernel variables */ |
| if ((retval = callout_common_init(&coargs)) != DCMD_OK) { |
| return (retval); |
| } |
| |
| /* do some option post-processing */ |
| if (kflag) { |
| coargs.time *= coargs.nsec_per_tick; |
| coargs.atime *= coargs.nsec_per_tick; |
| coargs.btime *= coargs.nsec_per_tick; |
| } |
| |
| if (dflag) { |
| coargs.time += coargs.now; |
| coargs.atime += coargs.now; |
| coargs.btime += coargs.now; |
| } |
| if (Sflag) { |
| if (flags & DCMD_ADDRSPEC) { |
| mdb_printf("-S option conflicts with explicit" |
| " address\n"); |
| return (DCMD_USAGE); |
| } |
| coargs.flags |= COF_SEQID; |
| coargs.seqid = (int)Stmp; |
| } |
| if (Cflag) { |
| if (flags & DCMD_ADDRSPEC) { |
| mdb_printf("-C option conflicts with explicit" |
| " address\n"); |
| return (DCMD_USAGE); |
| } |
| if (coargs.flags & COF_SEQID) { |
| mdb_printf("-C and -S are mutually exclusive\n"); |
| return (DCMD_USAGE); |
| } |
| coargs.cpu = (cpu_t *)Ctmp; |
| if (mdb_vread(&coargs.seqid, sizeof (processorid_t), |
| (uintptr_t)&(coargs.cpu->cpu_seqid)) == -1) { |
| mdb_warn("failed to read cpu_t at %p", Ctmp); |
| return (DCMD_ERR); |
| } |
| coargs.flags |= COF_SEQID; |
| } |
| /* avoid null outputs. */ |
| if (!(coargs.flags & (COF_REAL | COF_NORM))) { |
| coargs.flags |= COF_REAL | COF_NORM; |
| } |
| if (!(coargs.flags & (COF_LONG | COF_SHORT))) { |
| coargs.flags |= COF_LONG | COF_SHORT; |
| } |
| if (tflag) { |
| if (aflag || bflag) { |
| mdb_printf("-t and -a|b are mutually exclusive\n"); |
| return (DCMD_USAGE); |
| } |
| coargs.flags |= COF_TIME; |
| } |
| if (aflag) { |
| coargs.flags |= COF_AFTER; |
| } |
| if (bflag) { |
| coargs.flags |= COF_BEFORE; |
| } |
| if ((aflag && bflag) && (coargs.btime <= coargs.atime)) { |
| mdb_printf("value for -a must be earlier than the value" |
| " for -b.\n"); |
| return (DCMD_USAGE); |
| } |
| |
| if ((coargs.flags & COF_HEAP) && (coargs.flags & COF_QUEUE)) { |
| mdb_printf("-H and -Q are mutually exclusive\n"); |
| return (DCMD_USAGE); |
| } |
| |
| if (funcname != NULL) { |
| GElf_Sym sym; |
| |
| if (mdb_lookup_by_name(funcname, &sym) != 0) { |
| coargs.funcaddr = mdb_strtoull(funcname); |
| } else { |
| coargs.funcaddr = sym.st_value; |
| } |
| coargs.flags |= COF_FUNC; |
| } |
| |
| if (paramstr != NULL) { |
| GElf_Sym sym; |
| |
| if (mdb_lookup_by_name(paramstr, &sym) != 0) { |
| coargs.param = mdb_strtoull(paramstr); |
| } else { |
| coargs.param = sym.st_value; |
| } |
| coargs.flags |= COF_PARAM; |
| } |
| |
| if (!(flags & DCMD_ADDRSPEC)) { |
| /* don't pass "dot" if no addr. */ |
| addr = NULL; |
| } |
| if (addr != NULL) { |
| /* |
| * a callout table was specified. Ignore -r|n option |
| * to avoid null output. |
| */ |
| coargs.flags |= (COF_REAL | COF_NORM); |
| } |
| |
| if (DCMD_HDRSPEC(flags) || (coargs.flags & COF_VERBOSE)) { |
| coargs.flags |= COF_THDR | COF_LHDR | COF_CHDR; |
| } |
| if (coargs.flags & COF_FREE) { |
| coargs.flags |= COF_EMPTY; |
| /* -F = free callouts, -FL = free lists */ |
| if (!(coargs.flags & COF_LIST)) { |
| coargs.flags |= COF_BYIDH; |
| } |
| } |
| |
| /* walk table, using specialized callback routine. */ |
| if (mdb_pwalk("callout_table", callout_t_cb, &coargs, addr) == -1) { |
| mdb_warn("cannot walk callout_table"); |
| return (DCMD_ERR); |
| } |
| return (DCMD_OK); |
| } |
| |
| |
| /* |
| * Given an extended callout id, dump its information. |
| */ |
| /*ARGSUSED*/ |
| int |
| calloutid(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) |
| { |
| callout_data_t coargs; |
| callout_table_t *ctptr; |
| callout_table_t ct; |
| callout_id_t coid; |
| callout_t *coptr; |
| int tableid; |
| callout_id_t xid; |
| ulong_t idhash; |
| int i, retval; |
| const mdb_arg_t *arg; |
| size_t size; |
| callout_hash_t cot_idhash[CALLOUT_BUCKETS]; |
| |
| coargs.flags = COF_DEFAULT | COF_BYIDH; |
| i = mdb_getopts(argc, argv, |
| 'd', MDB_OPT_SETBITS, COF_DECODE, &coargs.flags, |
| 'v', MDB_OPT_SETBITS, COF_LONGLIST, &coargs.flags, |
| NULL); |
| argc -= i; |
| argv += i; |
| |
| if (argc != 1) { |
| return (DCMD_USAGE); |
| } |
| arg = &argv[0]; |
| |
| if (arg->a_type == MDB_TYPE_IMMEDIATE) { |
| xid = arg->a_un.a_val; |
| } else { |
| xid = (callout_id_t)mdb_strtoull(arg->a_un.a_str); |
| } |
| |
| if (DCMD_HDRSPEC(flags)) { |
| coargs.flags |= COF_CHDR; |
| } |
| |
| |
| /* initialize from kernel variables */ |
| if ((retval = callout_common_init(&coargs)) != DCMD_OK) { |
| return (retval); |
| } |
| |
| /* we must massage the environment so that the macros will play nice */ |
| #define callout_table_mask ((1 << coargs.ctbits) - 1) |
| #define callout_table_bits coargs.ctbits |
| #define nsec_per_tick coargs.nsec_per_tick |
| tableid = CALLOUT_ID_TO_TABLE(xid); |
| idhash = CALLOUT_IDHASH(xid); |
| #undef callouts_table_bits |
| #undef callout_table_mask |
| #undef nsec_per_tick |
| coid = CO_PLAIN_ID(xid); |
| |
| if (flags & DCMD_ADDRSPEC) { |
| mdb_printf("calloutid does not accept explicit address.\n"); |
| return (DCMD_USAGE); |
| } |
| |
| if (coargs.flags & COF_DECODE) { |
| if (DCMD_HDRSPEC(flags)) { |
| mdb_printf("%<u>%3s %1s %2s %-?s %-6s %</u>\n", |
| "SEQ", "T", "XL", "XID", "IDHASH"); |
| } |
| mdb_printf("%-3d %1s %1s%1s %-?llx %-6d\n", |
| TABLE_TO_SEQID(tableid), |
| co_typenames[tableid & CALLOUT_TYPE_MASK], |
| (xid & CALLOUT_EXECUTING) ? "X" : " ", |
| (xid & CALLOUT_LONGTERM) ? "L" : " ", |
| (long long)coid, idhash); |
| return (DCMD_OK); |
| } |
| |
| /* get our table. Note this relies on the types being correct */ |
| ctptr = coargs.co_table + tableid; |
| if (mdb_vread(&ct, sizeof (callout_table_t), (uintptr_t)ctptr) == -1) { |
| mdb_warn("failed to read callout_table at %p", ctptr); |
| return (DCMD_ERR); |
| } |
| size = sizeof (callout_hash_t) * CALLOUT_BUCKETS; |
| if (ct.ct_idhash != NULL) { |
| if (mdb_vread(&(cot_idhash), size, |
| (uintptr_t)ct.ct_idhash) == -1) { |
| mdb_warn("failed to read id_hash at %p", |
| ct.ct_idhash); |
| return (WALK_ERR); |
| } |
| } |
| |
| /* callout at beginning of hash chain */ |
| if (ct.ct_idhash == NULL) { |
| mdb_printf("id hash chain for this xid is empty\n"); |
| return (DCMD_ERR); |
| } |
| coptr = (callout_t *)cot_idhash[idhash].ch_head; |
| if (coptr == NULL) { |
| mdb_printf("id hash chain for this xid is empty\n"); |
| return (DCMD_ERR); |
| } |
| |
| coargs.ndx = tableid; |
| coargs.bucket = idhash; |
| |
| /* use the walker, luke */ |
| if (mdb_pwalk("callouts_byid", callouts_cb, &coargs, |
| (uintptr_t)coptr) == -1) { |
| mdb_warn("cannot walk callouts at %p", coptr); |
| return (WALK_ERR); |
| } |
| |
| return (DCMD_OK); |
| } |
| |
| void |
| callout_help(void) |
| { |
| mdb_printf("callout: display callouts.\n" |
| "Given a callout table address, display callouts from table.\n" |
| "Without an address, display callouts from all tables.\n" |
| "options:\n" |
| " -r|n : limit display to (r)ealtime or (n)ormal type callouts\n" |
| " -s|l : limit display to (s)hort-term ids or (l)ong-term ids\n" |
| " -x : limit display to callouts which are executing\n" |
| " -h : limit display to callouts based on hrestime\n" |
| " -B : limit display to callouts based on absolute time\n" |
| " -t|a|b nsec: limit display to callouts that expire a(t) time," |
| " (a)fter time,\n or (b)efore time. Use -a and -b together " |
| " to specify a range.\n For \"now\", use -d[t|a|b] 0.\n" |
| " -d : interpret time option to -t|a|b as delta from current time\n" |
| " -k : use ticks instead of nanoseconds as arguments to" |
| " -t|a|b. Note that\n ticks are less accurate and may not" |
| " match other tick times (ie: lbolt).\n" |
| " -D : display exiration time as delta from current time\n" |
| " -S seqid : limit display to callouts for this cpu sequence id\n" |
| " -C addr : limit display to callouts for this cpu pointer\n" |
| " -f name|addr : limit display to callouts with this function\n" |
| " -p name|addr : limit display to callouts functions with this" |
| " parameter\n" |
| " -T : display the callout table itself, instead of callouts\n" |
| " -L : display callout lists instead of callouts\n" |
| " -E : with -T or L, display empty data structures.\n" |
| " -i : traverse callouts by id hash instead of list hash\n" |
| " -F : walk free callout list (free list with -i) instead\n" |
| " -v : display more info for each item\n" |
| " -V : show details of each level of info as it is traversed\n" |
| " -H : limit display to callouts in the callout heap\n" |
| " -Q : limit display to callouts in the callout queue\n" |
| " -A : show only addresses. Useful for pipelines.\n"); |
| } |
| |
| void |
| calloutid_help(void) |
| { |
| mdb_printf("calloutid: display callout by id.\n" |
| "Given an extended callout id, display the callout infomation.\n" |
| "options:\n" |
| " -d : do not dereference callout, just decode the id.\n" |
| " -v : verbose display more info about the callout\n"); |
| } |
| |
| /*ARGSUSED*/ |
| int |
| class(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) |
| { |
| long num_classes, i; |
| sclass_t *class_tbl; |
| GElf_Sym g_sclass; |
| char class_name[PC_CLNMSZ]; |
| size_t tbl_size; |
| |
| if (mdb_lookup_by_name("sclass", &g_sclass) == -1) { |
| mdb_warn("failed to find symbol sclass\n"); |
| return (DCMD_ERR); |
| } |
| |
| tbl_size = (size_t)g_sclass.st_size; |
| num_classes = tbl_size / (sizeof (sclass_t)); |
| class_tbl = mdb_alloc(tbl_size, UM_SLEEP | UM_GC); |
| |
| if (mdb_readsym(class_tbl, tbl_size, "sclass") == -1) { |
| mdb_warn("failed to read sclass"); |
| return (DCMD_ERR); |
| } |
| |
| mdb_printf("%<u>%4s %-10s %-24s %-24s%</u>\n", "SLOT", "NAME", |
| "INIT FCN", "CLASS FCN"); |
| |
| for (i = 0; i < num_classes; i++) { |
| if (mdb_vread(class_name, sizeof (class_name), |
| (uintptr_t)class_tbl[i].cl_name) == -1) |
| (void) strcpy(class_name, "???"); |
| |
| mdb_printf("%4ld %-10s %-24a %-24a\n", i, class_name, |
| class_tbl[i].cl_init, class_tbl[i].cl_funcs); |
| } |
| |
| return (DCMD_OK); |
| } |
| |
| #define FSNAMELEN 32 /* Max len of FS name we read from vnodeops */ |
| |
| int |
| vnode2path(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) |
| { |
| uintptr_t rootdir; |
| vnode_t vn; |
| char buf[MAXPATHLEN]; |
| |
| uint_t opt_F = FALSE; |
| |
| if (mdb_getopts(argc, argv, |
| 'F', MDB_OPT_SETBITS, TRUE, &opt_F, NULL) != argc) |
| return (DCMD_USAGE); |
| |
| if (!(flags & DCMD_ADDRSPEC)) { |
| mdb_warn("expected explicit vnode_t address before ::\n"); |
| return (DCMD_USAGE); |
| } |
| |
| if (mdb_readvar(&rootdir, "rootdir") == -1) { |
| mdb_warn("failed to read rootdir"); |
| return (DCMD_ERR); |
| } |
| |
| if (mdb_vnode2path(addr, buf, sizeof (buf)) == -1) |
| return (DCMD_ERR); |
| |
| if (*buf == '\0') { |
| mdb_printf("??\n"); |
| return (DCMD_OK); |
| } |
| |
| mdb_printf("%s", buf); |
| if (opt_F && buf[strlen(buf)-1] != '/' && |
| mdb_vread(&vn, sizeof (vn), addr) == sizeof (vn)) |
| mdb_printf("%c", mdb_vtype2chr(vn.v_type, 0)); |
| mdb_printf("\n"); |
| |
| return (DCMD_OK); |
| } |
| |
| int |
| ld_walk_init(mdb_walk_state_t *wsp) |
| { |
| wsp->walk_data = (void *)wsp->walk_addr; |
| return (WALK_NEXT); |
| } |
| |
| int |
| ld_walk_step(mdb_walk_state_t *wsp) |
| { |
| int status; |
| lock_descriptor_t ld; |
| |
| if (mdb_vread(&ld, sizeof (lock_descriptor_t), wsp->walk_addr) == -1) { |
| mdb_warn("couldn't read lock_descriptor_t at %p\n", |
| wsp->walk_addr); |
| return (WALK_ERR); |
| } |
| |
| status = wsp->walk_callback(wsp->walk_addr, &ld, wsp->walk_cbdata); |
| if (status == WALK_ERR) |
| return (WALK_ERR); |
| |
| wsp->walk_addr = (uintptr_t)ld.l_next; |
| if (wsp->walk_addr == (uintptr_t)wsp->walk_data) |
| return (WALK_DONE); |
| |
| return (status); |
| } |
| |
| int |
| lg_walk_init(mdb_walk_state_t *wsp) |
| { |
| GElf_Sym sym; |
| |
| if (mdb_lookup_by_name("lock_graph", &sym) == -1) { |
| mdb_warn("failed to find symbol 'lock_graph'\n"); |
| return (WALK_ERR); |
| } |
| |
| wsp->walk_addr = (uintptr_t)sym.st_value; |
| wsp->walk_data = (void *)(uintptr_t)(sym.st_value + sym.st_size); |
| |
| return (WALK_NEXT); |
| } |
| |
| typedef struct lg_walk_data { |
| uintptr_t startaddr; |
| mdb_walk_cb_t callback; |
| void *data; |
| } lg_walk_data_t; |
| |
| /* |
| * We can't use ::walk lock_descriptor directly, because the head of each graph |
| * is really a dummy lock. Rather than trying to dynamically determine if this |
| * is a dummy node or not, we just filter out the initial element of the |
| * list. |
| */ |
| static int |
| lg_walk_cb(uintptr_t addr, const void *data, void *priv) |
| { |
| lg_walk_data_t *lw = priv; |
| |
| if (addr != lw->startaddr) |
| return (lw->callback(addr, data, lw->data)); |
| |
| return (WALK_NEXT); |
| } |
| |
| int |
| lg_walk_step(mdb_walk_state_t *wsp) |
| { |
| graph_t *graph; |
| lg_walk_data_t lw; |
| |
| if (wsp->walk_addr >= (uintptr_t)wsp->walk_data) |
| return (WALK_DONE); |
| |
| if (mdb_vread(&graph, sizeof (graph), wsp->walk_addr) == -1) { |
| mdb_warn("failed to read graph_t at %p", wsp->walk_addr); |
| return (WALK_ERR); |
| } |
| |
| wsp->walk_addr += sizeof (graph); |
| |
| if (graph == NULL) |
| return (WALK_NEXT); |
| |
| lw.callback = wsp->walk_callback; |
| lw.data = wsp->walk_cbdata; |
| |
| lw.startaddr = (uintptr_t)&(graph->active_locks); |
| if (mdb_pwalk("lock_descriptor", lg_walk_cb, &lw, lw.startaddr)) { |
| mdb_warn("couldn't walk lock_descriptor at %p\n", lw.startaddr); |
| return (WALK_ERR); |
| } |
| |
| lw.startaddr = (uintptr_t)&(graph->sleeping_locks); |
| if (mdb_pwalk("lock_descriptor", lg_walk_cb, &lw, lw.startaddr)) { |
| mdb_warn("couldn't walk lock_descriptor at %p\n", lw.startaddr); |
| return (WALK_ERR); |
| } |
| |
| return (WALK_NEXT); |
| } |
| |
| /* |
| * The space available for the path corresponding to the locked vnode depends |
| * on whether we are printing 32- or 64-bit addresses. |
| */ |
| #ifdef _LP64 |
| #define LM_VNPATHLEN 20 |
| #else |
| #define LM_VNPATHLEN 30 |
| #endif |
| |
| typedef struct mdb_lminfo_proc { |
| struct { |
| char u_comm[MAXCOMLEN + 1]; |
| } p_user; |
| } mdb_lminfo_proc_t; |
| |
| /*ARGSUSED*/ |
| static int |
| lminfo_cb(uintptr_t addr, const void *data, void *priv) |
| { |
| const lock_descriptor_t *ld = data; |
| char buf[LM_VNPATHLEN]; |
| mdb_lminfo_proc_t p; |
| uintptr_t paddr = 0; |
| |
| if (ld->l_flock.l_pid != 0) |
| paddr = mdb_pid2proc(ld->l_flock.l_pid, NULL); |
| |
| if (paddr != 0) |
| mdb_ctf_vread(&p, "proc_t", "mdb_lminfo_proc_t", paddr, 0); |
| |
| mdb_printf("%-?p %2s %04x %6d %-16s %-?p ", |
| addr, ld->l_type == F_RDLCK ? "RD" : |
| ld->l_type == F_WRLCK ? "WR" : "??", |
| ld->l_state, ld->l_flock.l_pid, |
| ld->l_flock.l_pid == 0 ? "<kernel>" : |
| paddr == 0 ? "<defunct>" : p.p_user.u_comm, ld->l_vnode); |
| |
| mdb_vnode2path((uintptr_t)ld->l_vnode, buf, |
| sizeof (buf)); |
| mdb_printf("%s\n", buf); |
| |
| return (WALK_NEXT); |
| } |
| |
| /*ARGSUSED*/ |
| int |
| lminfo(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) |
| { |
| if (DCMD_HDRSPEC(flags)) |
| mdb_printf("%<u>%-?s %2s %4s %6s %-16s %-?s %s%</u>\n", |
| "ADDR", "TP", "FLAG", "PID", "COMM", "VNODE", "PATH"); |
| |
| return (mdb_pwalk("lock_graph", lminfo_cb, NULL, NULL)); |
| } |
| |
| /*ARGSUSED*/ |
| int |
| whereopen_fwalk(uintptr_t addr, struct file *f, uintptr_t *target) |
| { |
| if ((uintptr_t)f->f_vnode == *target) { |
| mdb_printf("file %p\n", addr); |
| *target = NULL; |
| } |
| |
| return (WALK_NEXT); |
| } |
| |
| /*ARGSUSED*/ |
| int |
| whereopen_pwalk(uintptr_t addr, void *ignored, uintptr_t *target) |
| { |
| uintptr_t t = *target; |
| |
| if (mdb_pwalk("file", (mdb_walk_cb_t)whereopen_fwalk, &t, addr) == -1) { |
| mdb_warn("couldn't file walk proc %p", addr); |
| return (WALK_ERR); |
| } |
| |
| if (t == NULL) |
| mdb_printf("%p\n", addr); |
| |
| return (WALK_NEXT); |
| } |
| |
| /*ARGSUSED*/ |
| int |
| whereopen(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) |
| { |
| uintptr_t target = addr; |
| |
| if (!(flags & DCMD_ADDRSPEC) || addr == NULL) |
| return (DCMD_USAGE); |
| |
| if (mdb_walk("proc", (mdb_walk_cb_t)whereopen_pwalk, &target) == -1) { |
| mdb_warn("can't proc walk"); |
| return (DCMD_ERR); |
| } |
| |
| return (DCMD_OK); |
| } |
| |
| typedef struct datafmt { |
| char *hdr1; |
| char *hdr2; |
| char *dashes; |
| char *fmt; |
| } datafmt_t; |
| |
| static datafmt_t kmemfmt[] = { |
| { "cache ", "name ", |
| "-------------------------", "%-25s " }, |
| { " buf", " size", "------", "%6u " }, |
| { " buf", "in use", "------", "%6u " }, |
| { " buf", " total", "------", "%6u " }, |
| { " memory", " in use", "----------", "%10lu%c " }, |
| { " alloc", " succeed", "---------", "%9u " }, |
| { "alloc", " fail", "-----", "%5u " }, |
| { NULL, NULL, NULL, NULL } |
| }; |
| |
| static datafmt_t vmemfmt[] = { |
| { "vmem ", "name ", |
| "-------------------------", "%-*s " }, |
| { " memory", " in use", "----------", "%9llu%c " }, |
| { " memory", " total", "-----------", "%10llu%c " }, |
| { " memory", " import", "----------", "%9llu%c " }, |
| { " alloc", " succeed", "---------", "%9llu " }, |
| { "alloc", " fail", "-----", "%5llu " }, |
| { NULL, NULL, NULL, NULL } |
| }; |
| |
| /*ARGSUSED*/ |
| static int |
| kmastat_cpu_avail(uintptr_t addr, const kmem_cpu_cache_t *ccp, int *avail) |
| { |
| short rounds, prounds; |
| |
| if (KMEM_DUMPCC(ccp)) { |
| rounds = ccp->cc_dump_rounds; |
| prounds = ccp->cc_dump_prounds; |
| } else { |
| rounds = ccp->cc_rounds; |
| prounds = ccp->cc_prounds; |
| } |
| if (rounds > 0) |
| *avail += rounds; |
| if (prounds > 0) |
| *avail += prounds; |
| |
| return (WALK_NEXT); |
| } |
| |
| /*ARGSUSED*/ |
| static int |
| kmastat_cpu_alloc(uintptr_t addr, const kmem_cpu_cache_t *ccp, int *alloc) |
| { |
| *alloc += ccp->cc_alloc; |
| |
| return (WALK_NEXT); |
| } |
| |
| /*ARGSUSED*/ |
| static int |
| kmastat_slab_avail(uintptr_t addr, const kmem_slab_t *sp, int *avail) |
| { |
| *avail += sp->slab_chunks - sp->slab_refcnt; |
| |
| return (WALK_NEXT); |
| } |
| |
| typedef struct kmastat_vmem { |
| uintptr_t kv_addr; |
| struct kmastat_vmem *kv_next; |
| size_t kv_meminuse; |
| int kv_alloc; |
| int kv_fail; |
| } kmastat_vmem_t; |
| |
| typedef struct kmastat_args { |
| kmastat_vmem_t **ka_kvpp; |
| uint_t ka_shift; |
| } kmastat_args_t; |
| |
| static int |
| kmastat_cache(uintptr_t addr, const kmem_cache_t *cp, kmastat_args_t *kap) |
| { |
| kmastat_vmem_t **kvpp = kap->ka_kvpp; |
| kmastat_vmem_t *kv; |
| datafmt_t *dfp = kmemfmt; |
| int magsize; |
| |
| int avail, alloc, total; |
| size_t meminuse = (cp->cache_slab_create - cp->cache_slab_destroy) * |
| cp->cache_slabsize; |
| |
| mdb_walk_cb_t cpu_avail = (mdb_walk_cb_t)kmastat_cpu_avail; |
| mdb_walk_cb_t cpu_alloc = (mdb_walk_cb_t)kmastat_cpu_alloc; |
| mdb_walk_cb_t slab_avail = (mdb_walk_cb_t)kmastat_slab_avail; |
| |
| magsize = kmem_get_magsize(cp); |
| |
| alloc = cp->cache_slab_alloc + cp->cache_full.ml_alloc; |
| avail = cp->cache_full.ml_total * magsize; |
| total = cp->cache_buftotal; |
| |
| (void) mdb_pwalk("kmem_cpu_cache", cpu_alloc, &alloc, addr); |
| (void) mdb_pwalk("kmem_cpu_cache", cpu_avail, &avail, addr); |
| (void) mdb_pwalk("kmem_slab_partial", slab_avail, &avail, addr); |
| |
| for (kv = *kvpp; kv != NULL; kv = kv->kv_next) { |
| if (kv->kv_addr == (uintptr_t)cp->cache_arena) |
| goto out; |
| } |
| |
| kv = mdb_zalloc(sizeof (kmastat_vmem_t), UM_SLEEP | UM_GC); |
| kv->kv_next = *kvpp; |
| kv->kv_addr = (uintptr_t)cp->cache_arena; |
| *kvpp = kv; |
| out: |
| kv->kv_meminuse += meminuse; |
| kv->kv_alloc += alloc; |
| kv->kv_fail += cp->cache_alloc_fail; |
| |
| mdb_printf((dfp++)->fmt, cp->cache_name); |
| mdb_printf((dfp++)->fmt, cp->cache_bufsize); |
| mdb_printf((dfp++)->fmt, total - avail); |
| mdb_printf((dfp++)->fmt, total); |
| mdb_printf((dfp++)->fmt, meminuse >> kap->ka_shift, |
| kap->ka_shift == GIGS ? 'G' : kap->ka_shift == MEGS ? 'M' : |
| kap->ka_shift == KILOS ? 'K' : 'B'); |
| mdb_printf((dfp++)->fmt, alloc); |
| mdb_printf((dfp++)->fmt, cp->cache_alloc_fail); |
| mdb_printf("\n"); |
| |
| return (WALK_NEXT); |
| } |
| |
| static int |
| kmastat_vmem_totals(uintptr_t addr, const vmem_t *v, kmastat_args_t *kap) |
| { |
| kmastat_vmem_t *kv = *kap->ka_kvpp; |
| size_t len; |
| |
| while (kv != NULL && kv->kv_addr != addr) |
| kv = kv->kv_next; |
| |
| if (kv == NULL || kv->kv_alloc == 0) |
| return (WALK_NEXT); |
| |
| len = MIN(17, strlen(v->vm_name)); |
| |
| mdb_printf("Total [%s]%*s %6s %6s %6s %10lu%c %9u %5u\n", v->vm_name, |
| 17 - len, "", "", "", "", |
| kv->kv_meminuse >> kap->ka_shift, |
| kap->ka_shift == GIGS ? 'G' : kap->ka_shift == MEGS ? 'M' : |
| kap->ka_shift == KILOS ? 'K' : 'B', kv->kv_alloc, kv->kv_fail); |
| |
| return (WALK_NEXT); |
| } |
| |
| /*ARGSUSED*/ |
| static int |
| kmastat_vmem(uintptr_t addr, const vmem_t *v, const uint_t *shiftp) |
| { |
| datafmt_t *dfp = vmemfmt; |
| const vmem_kstat_t *vkp = &v->vm_kstat; |
| uintptr_t paddr; |
| vmem_t parent; |
| int ident = 0; |
| |
| for (paddr = (uintptr_t)v->vm_source; paddr != NULL; ident += 4) { |
| if (mdb_vread(&parent, sizeof (parent), paddr) == -1) { |
| mdb_warn("couldn't trace %p's ancestry", addr); |
| ident = 0; |
| break; |
| } |
| paddr = (uintptr_t)parent.vm_source; |
| } |
| |
| mdb_printf("%*s", ident, ""); |
| mdb_printf((dfp++)->fmt, 25 - ident, v->vm_name); |
| mdb_printf((dfp++)->fmt, vkp->vk_mem_inuse.value.ui64 >> *shiftp, |
| *shiftp == GIGS ? 'G' : *shiftp == MEGS ? 'M' : |
| *shiftp == KILOS ? 'K' : 'B'); |
| mdb_printf((dfp++)->fmt, vkp->vk_mem_total.value.ui64 >> *shiftp, |
| *shiftp == GIGS ? 'G' : *shiftp == MEGS ? 'M' : |
| *shiftp == KILOS ? 'K' : 'B'); |
| mdb_printf((dfp++)->fmt, vkp->vk_mem_import.value.ui64 >> *shiftp, |
| *shiftp == GIGS ? 'G' : *shiftp == MEGS ? 'M' : |
| *shiftp == KILOS ? 'K' : 'B'); |
| mdb_printf((dfp++)->fmt, vkp->vk_alloc.value.ui64); |
| mdb_printf((dfp++)->fmt, vkp->vk_fail.value.ui64); |
| |
| mdb_printf("\n"); |
| |
| return (WALK_NEXT); |
| } |
| |
| /*ARGSUSED*/ |
| int |
| kmastat(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv) |
| { |
| kmastat_vmem_t *kv = NULL; |
| datafmt_t *dfp; |
| kmastat_args_t ka; |
| |
| ka.ka_shift = 0; |
| if (mdb_getopts(argc, argv, |
| 'k', MDB_OPT_SETBITS, KILOS, &ka.ka_shift, |
| 'm', MDB_OPT_SETBITS, MEGS, &ka.ka_shift, |
| 'g', MDB_OPT_SETBITS, GIGS, &ka.ka_shift, NULL) != argc) |
| return (DCMD_USAGE); |
| |
| for (dfp = kmemfmt; dfp->hdr1 != NULL; dfp++) |
| mdb_printf("%s ", dfp->hdr1); |
| mdb_printf("\n"); |
| |
| for (dfp = kmemfmt; dfp->hdr1 != NULL; dfp++) |
| mdb_printf("%s ", dfp->hdr2); |
| mdb_printf("\n"); |
| |
| for (dfp = kmemfmt; dfp->hdr1 != NULL; dfp++) |
| mdb_printf("%s ", dfp->dashes); |
| mdb_printf("\n"); |
| |
| ka.ka_kvpp = &kv; |
| if (mdb_walk("kmem_cache", (mdb_walk_cb_t)kmastat_cache, &ka) == -1) { |
| mdb_warn("can't walk 'kmem_cache'"); |
| return (DCMD_ERR); |
| } |
| |
| for (dfp = kmemfmt; dfp->hdr1 != NULL; dfp++) |
| mdb_printf("%s ", dfp->dashes); |
| mdb_printf("\n"); |
| |
| if (mdb_walk("vmem", (mdb_walk_cb_t)kmastat_vmem_totals, &ka) == -1) { |
| mdb_warn("can't walk 'vmem'"); |
| return (DCMD_ERR); |
| } |
| |
| for (dfp = kmemfmt; dfp->hdr1 != NULL; dfp++) |
| mdb_printf("%s ", dfp->dashes); |
| mdb_printf("\n"); |
| |
| mdb_printf("\n"); |
| |
| for (dfp = vmemfmt; dfp->hdr1 != NULL; dfp++) |
| mdb_printf("%s ", dfp->hdr1); |
| mdb_printf("\n"); |
| |
| for (dfp = vmemfmt; dfp->hdr1 != NULL; dfp++) |
| mdb_printf("%s ", dfp->hdr2); |
| mdb_printf("\n"); |
| |
| for (dfp = vmemfmt; dfp->hdr1 != NULL; dfp++) |
| mdb_printf("%s ", dfp->dashes); |
| mdb_printf("\n"); |
| |
| if (mdb_walk("vmem", (mdb_walk_cb_t)kmastat_vmem, &ka.ka_shift) == -1) { |
| mdb_warn("can't walk 'vmem'"); |
| return (DCMD_ERR); |
| } |
| |
| for (dfp = vmemfmt; dfp->hdr1 != NULL; dfp++) |
| mdb_printf("%s ", dfp->dashes); |
| mdb_printf("\n"); |
| return (DCMD_OK); |
| } |
| |
| /* |
| * Our ::kgrep callback scans the entire kernel VA space (kas). kas is made |
| * up of a set of 'struct seg's. We could just scan each seg en masse, but |
| * unfortunately, a few of the segs are both large and sparse, so we could |
| * spend quite a bit of time scanning VAs which have no backing pages. |
| * |
| * So for the few very sparse segs, we skip the segment itself, and scan |
| * the allocated vmem_segs in the vmem arena which manages that part of kas. |
| * Currently, we do this for: |
| * |
| * SEG VMEM ARENA |
| * kvseg heap_arena |
| * kvseg32 heap32_arena |
| * kvseg_core heap_core_arena |
| * |
| * In addition, we skip the segkpm segment in its entirety, since it is very |
| * sparse, and contains no new kernel data. |
| */ |
| typedef struct kgrep_walk_data { |
| kgrep_cb_func *kg_cb; |
| void *kg_cbdata; |
| uintptr_t kg_kvseg; |
| uintptr_t kg_kvseg32; |
| uintptr_t kg_kvseg_core; |
| uintptr_t kg_segkpm; |
| uintptr_t kg_heap_lp_base; |
| uintptr_t kg_heap_lp_end; |
| } kgrep_walk_data_t; |
| |
| static int |
| kgrep_walk_seg(uintptr_t addr, const struct seg *seg, kgrep_walk_data_t *kg) |
| { |
| uintptr_t base = (uintptr_t)seg->s_base; |
| |
| if (addr == kg->kg_kvseg || addr == kg->kg_kvseg32 || |
| addr == kg->kg_kvseg_core) |
| return (WALK_NEXT); |
| |
| if ((uintptr_t)seg->s_ops == kg->kg_segkpm) |
| return (WALK_NEXT); |
| |
| return (kg->kg_cb(base, base + seg->s_size, kg->kg_cbdata)); |
| } |
| |
| /*ARGSUSED*/ |
| static int |
| kgrep_walk_vseg(uintptr_t addr, const vmem_seg_t *seg, kgrep_walk_data_t *kg) |
| { |
| /* |
| * skip large page heap address range - it is scanned by walking |
| * allocated vmem_segs in the heap_lp_arena |
| */ |
| if (seg->vs_start == kg->kg_heap_lp_base && |
| seg->vs_end == kg->kg_heap_lp_end) |
| return (WALK_NEXT); |
| |
| return (kg->kg_cb(seg->vs_start, seg->vs_end, kg->kg_cbdata)); |
| } |
| |
| /*ARGSUSED*/ |
| static int |
| kgrep_xwalk_vseg(uintptr_t addr, const vmem_seg_t *seg, kgrep_walk_data_t *kg) |
| { |
| return (kg->kg_cb(seg->vs_start, seg->vs_end, kg->kg_cbdata)); |
| } |
| |
| static int |
| kgrep_walk_vmem(uintptr_t addr, const vmem_t *vmem, kgrep_walk_data_t *kg) |
| { |
| mdb_walk_cb_t walk_vseg = (mdb_walk_cb_t)kgrep_walk_vseg; |
| |
| if (strcmp(vmem->vm_name, "heap") != 0 && |
| strcmp(vmem->vm_name, "heap32") != 0 && |
| strcmp(vmem->vm_name, "heap_core") != 0 && |
| strcmp(vmem->vm_name, "heap_lp") != 0) |
| return (WALK_NEXT); |
| |
| if (strcmp(vmem->vm_name, "heap_lp") == 0) |
| walk_vseg = (mdb_walk_cb_t)kgrep_xwalk_vseg; |
| |
| if (mdb_pwalk("vmem_alloc", walk_vseg, kg, addr) == -1) { |
| mdb_warn("couldn't walk vmem_alloc for vmem %p", addr); |
| return (WALK_ERR); |
| } |
| |
| return (WALK_NEXT); |
| } |
| |
| int |
| kgrep_subr(kgrep_cb_func *cb, void *cbdata) |
| { |
| GElf_Sym kas, kvseg, kvseg32, kvseg_core, segkpm; |
| kgrep_walk_data_t kg; |
| |
| if (mdb_get_state() == MDB_STATE_RUNNING) { |
| mdb_warn("kgrep can only be run on a system " |
| "dump or under kmdb; see dumpadm(1M)\n"); |
| return (DCMD_ERR); |
| } |
| |
| if (mdb_lookup_by_name("kas", &kas) == -1) { |
| mdb_warn("failed to locate 'kas' symbol\n"); |
| return (DCMD_ERR); |
| } |
| |
| if (mdb_lookup_by_name("kvseg", &kvseg) == -1) { |
| mdb_warn("failed to locate 'kvseg' symbol\n"); |
| return (DCMD_ERR); |
| } |
| |
| if (mdb_lookup_by_name("kvseg32", &kvseg32) == -1) { |
| mdb_warn("failed to locate 'kvseg32' symbol\n"); |
| return (DCMD_ERR); |
| } |
| |
| if (mdb_lookup_by_name("kvseg_core", &kvseg_core) == -1) { |
| mdb_warn("failed to locate 'kvseg_core' symbol\n"); |
| return (DCMD_ERR); |
| } |
| |
| if (mdb_lookup_by_name("segkpm_ops", &segkpm) == -1) { |
| mdb_warn("failed to locate 'segkpm_ops' symbol\n"); |
| return (DCMD_ERR); |
| } |
| |
| if (mdb_readvar(&kg.kg_heap_lp_base, "heap_lp_base") == -1) { |
| mdb_warn("failed to read 'heap_lp_base'\n"); |
| return (DCMD_ERR); |
| } |
| |
| if (mdb_readvar(&kg.kg_heap_lp_end, "heap_lp_end") == -1) { |
| mdb_warn("failed to read 'heap_lp_end'\n"); |
| return (DCMD_ERR); |
| } |
| |
| kg.kg_cb = cb; |
| kg.kg_cbdata = cbdata; |
| kg.kg_kvseg = (uintptr_t)kvseg.st_value; |
| kg.kg_kvseg32 = (uintptr_t)kvseg32.st_value; |
| kg.kg_kvseg_core = (uintptr_t)kvseg_core.st_value; |
| kg.kg_segkpm = (uintptr_t)segkpm.st_value; |
| |
| if (mdb_pwalk("seg", (mdb_walk_cb_t)kgrep_walk_seg, |
| &kg, kas.st_value) == -1) { |
| mdb_warn("failed to walk kas segments"); |
| return (DCMD_ERR); |
| } |
| |
| if (mdb_walk("vmem", (mdb_walk_cb_t)kgrep_walk_vmem, &kg) == -1) { |
| mdb_warn("failed to walk heap/heap32 vmem arenas"); |
| return (DCMD_ERR); |
| } |
| |
| return (DCMD_OK); |
| } |
| |
| size_t |
| kgrep_subr_pagesize(void) |
| { |
| return (PAGESIZE); |
| } |
| |
| typedef struct file_walk_data { |
| struct uf_entry *fw_flist; |
| int fw_flistsz; |
| int fw_ndx; |
| int fw_nofiles; |
| } file_walk_data_t; |
| |
| typedef struct mdb_file_proc { |
| struct { |
| struct { |
| int fi_nfiles; |
| uf_entry_t *volatile fi_list; |
| } u_finfo; |
| } p_user; |
| } mdb_file_proc_t; |
| |
| int |
| file_walk_init(mdb_walk_state_t *wsp) |
| { |
| file_walk_data_t *fw; |
| mdb_file_proc_t p; |
| |
| if (wsp->walk_addr == NULL) { |
| mdb_warn("file walk doesn't support global walks\n"); |
| return (WALK_ERR); |
| } |
| |
| fw = mdb_alloc(sizeof (file_walk_data_t), UM_SLEEP); |
| |
| if (mdb_ctf_vread(&p, "proc_t", "mdb_file_proc_t", |
| wsp->walk_addr, 0) == -1) { |
| mdb_free(fw, sizeof (file_walk_data_t)); |
| mdb_warn("failed to read proc structure at %p", wsp->walk_addr); |
| return (WALK_ERR); |
| } |
| |
| if (p.p_user.u_finfo.fi_nfiles == 0) { |
| mdb_free(fw, sizeof (file_walk_data_t)); |
| return (WALK_DONE); |
| } |
| |
| fw->fw_nofiles = p.p_user.u_finfo.fi_nfiles; |
| fw->fw_flistsz = sizeof (struct uf_entry) * fw->fw_nofiles; |
| fw->fw_flist = mdb_alloc(fw->fw_flistsz, UM_SLEEP); |
| |
| if (mdb_vread(fw->fw_flist, fw->fw_flistsz, |
| (uintptr_t)p.p_user.u_finfo.fi_list) == -1) { |
| mdb_warn("failed to read file array at %p", |
| p.p_user.u_finfo.fi_list); |
| mdb_free(fw->fw_flist, fw->fw_flistsz); |
| mdb_free(fw, sizeof (file_walk_data_t)); |
| return (WALK_ERR); |
| } |
| |
| fw->fw_ndx = 0; |
| wsp->walk_data = fw; |
| |
| return (WALK_NEXT); |
| } |
| |
| int |
| file_walk_step(mdb_walk_state_t *wsp) |
| { |
| file_walk_data_t *fw = (file_walk_data_t *)wsp->walk_data; |
| struct file file; |
| uintptr_t fp; |
| |
| again: |
| if (fw->fw_ndx == fw->fw_nofiles) |
| return (WALK_DONE); |
| |
| if ((fp = (uintptr_t)fw->fw_flist[fw->fw_ndx++].uf_file) == NULL) |
| goto again; |
| |
| (void) mdb_vread(&file, sizeof (file), (uintptr_t)fp); |
| return (wsp->walk_callback(fp, &file, wsp->walk_cbdata)); |
| } |
| |
| int |
| allfile_walk_step(mdb_walk_state_t *wsp) |
| { |
| file_walk_data_t *fw = (file_walk_data_t *)wsp->walk_data; |
| struct file file; |
| uintptr_t fp; |
| |
| if (fw->fw_ndx == fw->fw_nofiles) |
| return (WALK_DONE); |
| |
| if ((fp = (uintptr_t)fw->fw_flist[fw->fw_ndx++].uf_file) != NULL) |
| (void) mdb_vread(&file, sizeof (file), (uintptr_t)fp); |
| else |
| bzero(&file, sizeof (file)); |
| |
| return (wsp->walk_callback(fp, &file, wsp->walk_cbdata)); |
| } |
| |
| void |
| file_walk_fini(mdb_walk_state_t *wsp) |
| { |
| file_walk_data_t *fw = (file_walk_data_t *)wsp->walk_data; |
| |
| mdb_free(fw->fw_flist, fw->fw_flistsz); |
| mdb_free(fw, sizeof (file_walk_data_t)); |
| } |
| |
| int |
| port_walk_init(mdb_walk_state_t *wsp) |
| { |
| if (wsp->walk_addr == NULL) { |
| mdb_warn("port walk doesn't support global walks\n"); |
| return (WALK_ERR); |
| |