gcore.c revision 2a12f85ad140e332791b4bad1208a734c3f26bf3
/*
* This file and its contents are supplied under the terms of the
* Common Development and Distribution License ("CDDL"), version 1.0.
* You may only use this file in accordance with the terms of version
* 1.0 of the CDDL.
*
* A full copy of the text of the CDDL should have accompanied this
* source. A copy of the CDDL is also available via the Internet at
*/
/*
* Copyright (c) 2013 by Delphix. All rights reserved.
*/
/*
* This file implements the mdb ::gcore command. The command relies on the
* libproc Pgcore function to actually generate the core file but we provide
* our own ops vector to populate data required by Pgcore. The ops vector
* function implementations simulate the functionality implemented by procfs.
* The data provided by some of the ops vector functions is not complete
* (missing data is documented in function headers) but there is enough
* information to generate a core file that can be loaded into mdb.
*
* Currently only x86 is supported!
*/
#ifndef _KMDB
/*
* The kernel has its own definition of exit which has a different signature
* than the user space definition. This seems to be the standard way to deal
* with this.
*/
#include <mdb/mdb_modapi.h>
#include <mdb/mdb_param.h>
#include <mdb/mdb_debug.h>
#include <sys/cred_impl.h>
#include <sys/schedctl.h>
#include <sys/privregs.h>
#include <sys/sysmacros.h>
#include <stdio.h>
#include <stdbool.h>
#include <string.h>
#include <libproc.h>
#include "avl.h"
#ifdef _LP64
#else
#endif
/* Macros to invoke gcore seg operations */
#define GSOP_NORESERVE(_gs) \
#ifdef GCORE_DEBUG
#else
#define dprintf(...)
#endif
/* mdb versions of kernel structures used for ctf read calls */
typedef struct mdb_proc {
int p_lwpcnt;
int p_zombcnt;
char p_wcode;
int p_wdata;
} mdb_proc_t;
typedef struct mdb_kthread {
short t_sysnum;
int t_bind_pset;
short t_bind_cpu;
typedef struct mdb_seg {
} mdb_seg_t;
typedef struct mdb_as {
} mdb_as_t;
typedef struct mdb_segvn_data {
typedef struct mdb_vnode {
} mdb_vnode_t;
typedef struct mdb_znode {
} mdb_znode_t;
typedef struct mdb_tmpnode {
typedef struct mdb_vnodeops {
typedef struct mdb_shm_data {
typedef struct mdb_watched_page {
typedef struct mdb_pid {
} mdb_pid_t;
typedef struct mdb_sess {
} mdb_sess_t;
typedef struct mdb_task {
} mdb_task_t;
typedef struct mdb_kproject {
typedef struct mdb_zone {
} mdb_zone_t;
typedef struct mdb_sc_shared {
char sc_sigblock;
typedef struct mdb_klwp {
short lwp_badpriv;
char lwp_eosys;
} mdb_klwp_t;
typedef struct mdb_cpu {
} mdb_cpu_t;
typedef struct mdb_lpl {
} mdb_lpl_t;
typedef struct mdb_sigqueue {
typedef struct mdb_pool {
} mdb_pool_t;
typedef struct mdb_amp {
} mdb_amp_t;
typedef struct mdb_anon_hdr {
int flags;
typedef struct mdb_anon {
} mdb_anon_t;
/* Used to construct a linked list of prmap_ts */
typedef struct prmap_node {
struct prmap_node *next;
prmap_t m;
} prmap_node_t;
/* Fields common to psinfo_t and pstatus_t */
typedef struct pcommon {
int pc_nlwp;
int pc_nzomb;
char pc_dmodel;
} pcommon_t;
/* AVL walk callback structures */
typedef struct read_maps_cbarg {
mdb_proc_t *p;
int map_len;
typedef struct as_segat_cbarg {
typedef struct getwatchprot_cbarg {
struct gcore_segops;
typedef struct gcore_seg {
void *gs_data;
struct gcore_segops *gs_ops;
} gcore_seg_t;
/* Callback function type for processing lwp entries */
/* Private data */
static uintptr_t gcore_segvn_ops;
static priv_impl_info_t prinfo;
static sclass_t *gcore_sclass;
typedef int (*gsop_init_t)(gcore_seg_t *);
typedef void (*gsop_fini_t)(gcore_seg_t *);
typedef struct gcore_segops {
static void map_list_free(prmap_node_t *);
/*
* Segvn ops
*/
static int gsvn_init(gcore_seg_t *);
static void gsvn_fini(gcore_seg_t *);
static gcore_segops_t gsvn_ops = {
};
static int
{
goto error;
}
mdb_warn("Failed to read vpages from %p\n",
goto error;
}
} else {
}
} else {
}
return (0);
return (-1);
}
/*ARGSUSED*/
static int
{
}
static void
{
name[0] = '\0';
mdb_proc_t p;
== -1) {
return;
}
== -1) {
return;
}
== -1) {
return;
}
}
/*
* procfs has more logic here to construct a name using
* here.
*/
}
}
/*ARGSUSED*/
static int
{
return (0);
}
static void
{
}
}
}
static boolean_t
{
return (B_FALSE);
}
return (B_TRUE);
}
return (B_FALSE);
}
return (B_TRUE);
}
}
return (B_FALSE);
}
static uintptr_t
{
0) == -1) {
return (0);
}
/*
* Single level case.
*/
sizeof (anon_ptr)) {
mdb_warn("Failed to read anon_ptr from %p (1 level)\n",
return (0);
}
return (anon_ptr & ANON_PTRMASK);
}
/*
* 2 level case.
*/
(an_idx >> ANON_CHUNK_SHIFT));
sizeof (anon_ptr)) {
mdb_warn("Failed to read anon_ptr from %p (2a level)\n",
return (0);
}
if (anon_ptr == 0) {
return (0);
}
(an_idx & ANON_CHUNK_OFF));
sizeof (anon_ptr)) {
mdb_warn("Failed to read anon_ptr from %p (2b level)\n",
return (0);
}
return (anon_ptr & ANON_PTRMASK);
}
static void
{
if (ap != 0) {
-1) {
return;
}
} else {
*vp = 0;
*off = 0;
}
}
static u_offset_t
{
0) == -1) {
return (eaddr);
}
/* First check the anon map */
&offset);
break;
}
}
/* Now check the segment's vnode */
break;
}
dprintf("amp: %p vp: %p addr: %p offset: %p not in core!\n",
}
return (addr);
}
static uint_t
{
}
}
/*
* Helper functions for constructing the process address space maps.
*/
/*ARGSUSED*/
static int
{
return (WALK_ERR);
}
return (WALK_NEXT);
}
return (WALK_NEXT);
}
return (WALK_DONE);
}
/*
* Find a segment containing addr.
*/
static uintptr_t
{
as_segat_arg.res = 0;
"a_segtree");
return (as_segat_arg.res);
}
static uintptr_t
{
if (p->p_brkbase != 0)
}
/* ISA dependent function. */
static uintptr_t
{
return (p->p_usrstack - p->p_stksize);
}
static u_offset_t
{
char vops_name[128];
-1) {
return (-1);
}
return (-1);
}
-1) {
mdb_warn("Failed to read vnop_name from %p\n",
return (-1);
}
return (-1);
}
}
return (-1);
}
}
/* Unknown file system type. */
return (-1);
}
static uint64_t
{
return (-1);
}
if (fsize == -1) {
return (-1);
}
fsize = 0;
} else {
}
}
return (size);
}
return (size);
}
/*ARGSUSED*/
static int
{
return (WALK_ERR);
}
return (WALK_DONE);
}
return (WALK_NEXT);
}
static void
{
}
}
static u_offset_t
{
int noreserve = 0;
if (noreserve) {
prot = 0;
goto out;
}
}
/* Discontinuity */
goto out;
}
break;
}
}
out:
return (addr);
}
/*
* Get the page protection for the given start address.
* - saddrp: in - start address
* out - contains address of first in core page
* - naddrp: out - address of next in core page that has different protection
* - eaddr: in - end address
*/
static uint_t
{
dprintf("seg: %p saddr: %p eaddr: %p\n",
dprintf("seg: %p saddr: %p naddr: %p eaddr: %p\n",
return (prot);
}
static gcore_seg_t *
{
} else {
goto error;
}
goto error;
}
return (gs);
return (NULL);
}
static void
{
}
/*ARGSUSED*/
static int
{
mdb_seg_t s;
return (WALK_ERR);
}
seg = &s;
mdb_warn("gcore_seg_create failed!\n");
return (WALK_ERR);
}
/*
* Iterate from the base of the segment to its end, allocating a new
* prmap_node at each address boundary (baddr) between ranges that
* have different virtual memory protections.
*/
break;
}
} else {
}
if (prot & PROT_WRITE)
}
}
/*
* Manufacture a filename for the "object" dir.
*/
}
return (0);
}
/*
* Helper functions for retrieving process and lwp state.
*/
static int
{
-1) {
return (-1);
}
-1) {
return (-1);
}
-1) {
return (-1);
}
-1) {
return (-1);
}
return (-1);
}
0) == -1) {
return (-1);
}
return (-1);
}
switch (p->p_model) {
case DATAMODEL_ILP32:
break;
case DATAMODEL_LP64:
break;
}
return (0);
}
static uintptr_t
{
mdb_kthread_t *t = &kthr;
ushort_t t_istop_whystop = 0;
ushort_t t_istop_whatstop = 0;
/*
* If the agent lwp exists, it takes precedence over all others.
*/
return (t_addr);
}
return (t_addr);
do { /* for each lwp in the process */
t_addr, 0) == -1) {
return (0);
}
if (VSTOPPED(t)) { /* virtually stopped */
continue;
}
switch (t->t_state) {
default:
return (0);
case TS_SLEEP:
break;
case TS_RUN:
case TS_WAIT:
break;
case TS_ONPROC:
break;
/*
* Threads in the zombie state have the lowest
* priority when selecting a representative lwp.
*/
case TS_ZOMB:
break;
case TS_STOPPED:
switch (t->t_whystop) {
case PR_SUSPENDED:
break;
case PR_JOBCONTROL:
if (t->t_proc_flag & TP_PRSTOP) {
} else {
}
break;
case PR_REQUESTED:
break;
case PR_SYSENTRY:
case PR_SYSEXIT:
case PR_SIGNALLED:
case PR_FAULTED:
/*
* Make an lwp calling exit() be the
* last lwp seen in the process.
*/
(t_istop_whystop == PR_SYSENTRY &&
t_istop_whatstop == SYS_exit)) {
t_istop_whystop = t->t_whystop;
t_istop_whatstop = t->t_whatstop;
}
break;
case PR_CHECKPOINT: /* can't happen? */
break;
default:
return (0);
}
break;
}
if (t_onproc)
else if (t_run)
else if (t_sleep)
else if (t_jstop)
else if (t_jdstop)
else if (t_istop)
else if (t_dtrace)
else if (t_req)
else if (t_susp)
else /* TS_ZOMB */
return (t_addr);
}
/*
* Fields not populated:
* - pr_stype
* - pr_oldpri
* - pr_nice
* - pr_time
* - pr_pctcpu
* - pr_cpu
*/
static int
{
char c, state;
/* map the thread state enum into a process state enum */
switch (state) {
default: state = 0; c = '?'; break;
}
== -1) {
return (-1);
}
return (-1);
}
return (-1);
}
return (0);
}
/*ARGSUSED*/
static int
{
if (t_addr != 0) {
0) == -1) {
return (-1);
}
}
return (0);
}
static void
{
if (t->t_schedctl == NULL) {
return;
}
0) == -1) {
return;
}
if (tdp->sc_sigblock) {
tdp->sc_sigblock = 0;
}
}
static void
{
}
}
}
/* ISA dependent function. */
static int
{
}
/* ISA dependent function. */
static int
{
}
/* ISA dependent function. */
static void
{
return;
}
#if defined(__amd64)
} else {
}
#else
#endif
}
/* ISA dependent functions. */
static int
{
return (r->r_r0);
*rval1 = 0;
*rval2 = 0;
} else {
}
return (0);
}
static void
{
}
/*
* Field not populated:
* - pr_tstamp
* - pr_utime
* - pr_stime
* - pr_syscall
* - pr_syarg
* - pr_nsysarg
* - pr_fpreg
*/
/*ARGSUSED*/
static int
{
int flags;
return (-1);
}
flags = 0L;
if (t->t_state == TS_STOPPED) {
flags |= PR_STOPPED;
if ((t->t_schedflag & TS_PSTART) == 0)
} else if (VSTOPPED(t)) {
}
if (lwp->lwp_asleep)
if (!(t->t_proc_flag & TP_TWAIT))
if (t->t_proc_flag & TP_DAEMON)
if (p->p_proc_flag & P_PR_FORK)
if (p->p_proc_flag & P_PR_RUNLCL)
if (p->p_proc_flag & P_PR_KILLCL)
if (p->p_proc_flag & P_PR_ASYNC)
if (p->p_proc_flag & P_PR_BPTADJ)
if (p->p_proc_flag & P_PR_PTRACE)
return (-1);
}
if (pid.pid_pgorphaned)
if (p->p_pidflag & CLDNOSIGCHLD)
flags |= PR_NOSIGCHLD;
if (p->p_pidflag & CLDWAITPID)
flags |= PR_WAITPID;
if (VSTOPPED(t)) {
} else {
}
if (t->t_whystop == PR_FAULTED) {
} else if (lwp->lwp_curinfo) {
return (-1);
}
}
-1) {
return (-1);
}
/*
* Fetch the current instruction, if not a system process.
* We don't attempt this unless the lwp is stopped.
*/
else if (!(flags & PR_STOPPED))
else
if (gcore_prisstep(lwp))
int i;
else
if (t->t_sysnum == SYS_execve) {
i++, auxp++) {
break;
}
}
}
}
return (0);
}
static int
{
return (1);
}
== -1) {
return (-1);
}
}
static prheader_t *
{
void *ent;
int status;
int i;
return (NULL);
}
ldp++) {
sizeof (ld)) {
goto error;
}
continue;
}
sizeof (lwent)) {
mdb_warn("Failed to read lwpent_t from %p\n",
goto error;
}
if (status == -1) {
goto error;
}
if (status == 1) {
continue;
}
}
return (php);
return (NULL);
}
/*
* Misc helper functions.
*/
/*
*/
static int
{
switch (code) {
case CLD_EXITED:
stat <<= 8;
break;
case CLD_DUMPED:
break;
case CLD_KILLED:
break;
case CLD_TRAPPED:
case CLD_STOPPED:
stat <<= 8;
break;
case CLD_CONTINUED:
break;
default:
}
return (stat);
}
static void
{
/*
* set type, dpl and present bits.
*/
/*
* set avl, DB and granularity bits.
*/
#if defined(__amd64)
#else
#endif
}
#endif
static priv_set_t *
{
switch (set) {
case PRIV_EFFECTIVE:
case PRIV_PERMITTED:
}
}
}
static void
{
struct priv_info_uint *ii;
}
static void
{
while (n != NULL) {
mdb_free(n, sizeof (*n));
n = next;
}
}
/*
* Ops vector functions for ::gcore.
*/
/*ARGSUSED*/
static ssize_t
void *data)
{
mdb_proc_t *p = data;
if (ret != n) {
return (n);
}
return (ret);
}
/*ARGSUSED*/
static ssize_t
void *data)
{
return (-1);
}
/*ARGSUSED*/
static int
void *data)
{
mdb_proc_t *p = data;
prmap_node_t *n;
int error;
int i;
cbarg.p = p;
"a_segtree");
return (-1);
}
/* Conver the linked list into an array */
return (-1);
}
}
dprintf("pr_vaddr: %p pr_size: %llx, pr_name: %s "
"pr_offset: %p pr_mflags: 0x%x\n",
}
return (0);
}
/*ARGSUSED*/
static void
{
mdb_proc_t *p = data;
int naux;
*nauxp = 0;
return;
}
}
/*ARGSUSED*/
static int
{
mdb_proc_t *p = data;
int i;
return (-1);
}
prcp->pr_ngroups = 0;
return (0);
}
sizeof (crgrp)) {
return (-1);
}
for (i = 0; i < prcp->pr_ngroups; i++) {
}
return (0);
}
/*ARGSUSED*/
static int
{
mdb_proc_t *p = data;
int i;
return (-1);
}
return (-1);
}
for (i = 0; i < PRIV_NSET; i++) {
}
return (0);
}
/*
* Fields not filled populated:
* - pr_utime
* - pr_stkbase
* - pr_cutime
* - pr_cstime
* - pr_agentid
*/
/*ARGSUSED*/
static void
{
mdb_proc_t *p = data;
mdb_kthread_t *t;
t_addr = gcore_prchoose(p);
0) == -1) {
return;
}
t = &kthr;
}
/* just bzero the process part, prgetlwpstatus() does the rest */
return;
}
/* get the chosen lwp's status */
/* replicate the flags */
}
/*
* Fields not populated:
* - pr_contract
* - pr_addr
* - pr_rtime
* - pr_ctime
* - pr_ttydev
* - pr_pctcpu
* - pr_size
* - pr_rsize
* - pr_pctmem
*/
/*ARGSUSED*/
static const psinfo_t *
{
mdb_proc_t *p = data;
mdb_kthread_t *t;
} else {
}
return (NULL);
}
/*
* only export SSYS and SMSACCT; everything else is off-limits to
* userland apps.
*/
return (NULL);
}
return (NULL);
}
if (t_addr == 0) {
if (wcode)
} else {
/* get the chosen lwp's lwpsinfo */
0) == -1) {
return (NULL);
}
t = &kthr;
}
return (NULL);
}
/*ARGSUSED*/
static prheader_t *
{
mdb_proc_t *p = data;
}
/*ARGSUSED*/
static prheader_t *
{
mdb_proc_t *p = data;
}
/*ARGSUSED*/
static char *
{
mdb_warn("failed to read platform!\n");
return (NULL);
}
return (s);
}
/*ARGSUSED*/
static int
{
if (mdb_readvar(u, "utsname") != sizeof (*u)) {
return (-1);
}
return (0);
}
/*ARGSUSED*/
static char *
{
mdb_proc_t *p = data;
return (NULL);
}
return (NULL);
}
return (s);
}
/*ARGSUSED*/
static char *
{
mdb_proc_t *p = data;
return (NULL);
}
return (NULL);
}
return (buf);
}
/*ARGSUSED*/
static int
{
mdb_proc_t *p = data;
int i, limit;
return (0);
}
limit = p->p_ldtlimit;
/* Is this call just to query the size ? */
return (limit);
}
return (-1);
}
return (-1);
}
}
}
return (limit);
}
#endif
static const ps_ops_t Pgcore_ops = {
.pop_pread = Pread_gcore,
.pop_cred = Pcred_gcore,
.pop_priv = Ppriv_gcore,
#endif
};
/*ARGSUSED*/
int
{
struct ps_prochandle *P;
char core_name[MAXNAMELEN];
mdb_proc_t p;
int error;
if (!gcore_initialized) {
mdb_warn("gcore unavailable\n");
return (DCMD_ERR);
}
return (DCMD_ERR);
}
return (DCMD_ERR);
}
== -1) {
return (DCMD_ERR);
}
NULL) {
mdb_warn("Failed to initialize proc handle");
return (DCMD_ERR);
}
Pfree(P);
return (DCMD_ERR);
}
Pfree(P);
return (0);
}
void
gcore_init(void)
{
mdb_warn("Failed to lookup symbol 'segvn_ops'\n");
return;
}
mdb_warn("Failed to read variable 'priv_info'\n");
return;
}
return;
}
mdb_warn("Failed to lookup symbol 'segvn_ops'\n");
return;
}
return;
}
mdb_warn("Failed to lookup symbol 'kas'\n");
return;
}
}
#endif /* _KMDB */