root/usr/src/cmd/mdb/common/mdb/mdb_kproc.c
/*
 * 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 2008 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 *
 * Copyright 2018 Joyent, Inc.
 * Copyright 2024 Oxide Computer Company
 */

/*
 * Kernel Process View Target
 *
 * The kproc target is activated when the user is debugging a kernel using the
 * kvm target and executes a ::context dcmd to change the debugger view to one
 * of the running processes.  The kvm target's t_setcontext operation will
 * create and activate a kproc target in response to this call.  The kproc
 * target itself is built upon the kvm target's libkvm cookie and the ability
 * to read information from the kernel itself and the ability to read the
 * address space of a particular user process with kvm_aread().  It also relies
 * on a special set of functions provided by the kvm target's mdb_ks support
 * module in order to bootstrap: specifically, given the initial proc pointer,
 * mdb_ks provides functions to return the set of address space mappings, the
 * address space pointer itself, the aux vector vector saved in the u-area,
 * and the process data model.  The kproc target maintains a list of address
 * space mappings (kp_map_t) and load objects (kp_file_t), and for each load
 * object will attempt to read the corresponding dynamic symbol table.  In
 * order to bootstrap, the target uses the AT_BASE and AT_ENTRY aux vector
 * elements to locate the dynamic linker and executable mappings.  With these
 * mappings in place, we initialize a librtld_db agent on the target (see
 * mdb_pservice.c for how this is done), and then process each load object
 * found in the link-map chain.  In order to simplify the construction of
 * symbol tables for each load object, we would like make use of our existing
 * library of GElf processing code.  Since the MDB GElf code uses mdb_io
 * objects to read in an ELF file, we simply define a new type of mdb_io object
 * where each read operation is translated into a call to kproc's t_vread
 * function to read from the range of the address space defined by the mapping
 * as if it were a file.
 */

#include <sys/types.h>
#include <sys/proc.h>
#include <sys/auxv.h>

#include <strings.h>
#include <limits.h>
#include <rtld_db.h>
#include <procfs.h>
#include <dlfcn.h>
#include <kvm.h>

#include <mdb/mdb_target_impl.h>
#include <mdb/mdb_debug.h>
#include <mdb/mdb_string.h>
#include <mdb/mdb_err.h>
#include <mdb/mdb_ks.h>
#include <mdb/mdb_gelf.h>
#include <mdb/mdb_io_impl.h>
#include <mdb/mdb.h>

typedef struct kp_symarg {
        mdb_tgt_sym_f *sym_cb;          /* Caller's callback function */
        void *sym_data;                 /* Callback function argument */
        uint_t sym_type;                /* Symbol type/binding filter */
        uintptr_t sym_adjust;           /* Symbol value adjustment */
        mdb_syminfo_t sym_info;         /* Symbol id and table id */
        const char *sym_obj;            /* Containing object */
} kp_symarg_t;

typedef struct kp_file {
        mdb_gelf_file_t *kpf_file;      /* ELF file object */
        mdb_io_t *kpf_fio;              /* ELF file back-end */
        mdb_gelf_symtab_t *kpf_dynsym;  /* Dynamic symbol table */
        struct kp_map *kpf_map;         /* Primary (text) mapping */
        const char *kpf_basename;       /* Mapping basename */
        uintptr_t kpf_dyn_base;         /* Load address for ET_DYN files */
        uintptr_t kpf_text_base;        /* Base address of text mapping */
        uintptr_t kpf_data_base;        /* Base address of data mapping */
        struct kp_file *kpf_next;       /* Pointer to next file */
} kp_file_t;

typedef struct kp_map {
        mdb_map_t kpm_map;              /* Mapping information */
        kp_file_t *kpm_file;            /* Pointer to load object */
        struct kp_map *kpm_next;        /* Pointer to next mapping */
} kp_map_t;

typedef struct kp_io {
        mdb_tgt_t *kpi_tgt;             /* Backpointer to kproc target */
        kp_map_t *kpi_map;              /* Mapping for this i/o */
        uintptr_t kpi_ptr;              /* Virtual address pointer */
        uintptr_t kpi_lim;              /* Virtual address limit */
} kp_io_t;

typedef struct kp_data {
        mdb_tgt_t *kp_parent;           /* Parent kvm target */
        kvm_t *kp_cookie;               /* Cookie for libkvm routines */
        rd_agent_t *kp_rap;             /* Cookie for librtld_db routines */
        proc_t *kp_proc;                /* Proc address in dump */
        struct as *kp_as;               /* Proc as address in dump */
        pid_t kp_pid;                   /* Process ID */
        auxv_t *kp_auxv;                /* Auxv array from u-area */
        int kp_nauxv;                   /* Length of kp_auxv */
        const char *kp_platform;        /* Platform string from kvm target */
        uint_t kp_model;                /* Process data model */
        kp_file_t *kp_file_head;        /* Head of load object list */
        kp_file_t *kp_file_tail;        /* Tail of load object list */
        kp_map_t *kp_map_head;          /* Head of mapping list */
        kp_map_t *kp_map_tail;          /* Tail of mapping list */
        int kp_num_files;               /* Length of load object list */
        int kp_num_maps;                /* Length of mapping list */
        kp_map_t *kp_map_exec;          /* Executable mapping */
        kp_map_t *kp_map_ldso;          /* Interpreter mapping */
        kp_file_t kp_prfile;            /* Fake file for mdb.m_prsym */
} kp_data_t;

static mdb_io_t *kp_io_create(mdb_tgt_t *, kp_map_t *);

static kp_map_t *
kp_addr_to_kpmap(kp_data_t *kp, uintptr_t addr)
{
        kp_map_t *kpm;

        for (kpm = kp->kp_map_head; kpm != NULL; kpm = kpm->kpm_next) {
                if (addr >= kpm->kpm_map.map_base &&
                    addr < kpm->kpm_map.map_base + kpm->kpm_map.map_size)
                        return (kpm);
        }

        return (NULL);
}

static long
kp_getauxval(kp_data_t *kp, int type)
{
        auxv_t *auxp;

        for (auxp = kp->kp_auxv; auxp->a_type != AT_NULL; auxp++) {
                if (auxp->a_type == type)
                        return (auxp->a_un.a_val);
        }

        return (-1L);
}

static void
kp_add_mapping(const mdb_map_t *pmp, void *data)
{
        kp_map_t *kpm = mdb_zalloc(sizeof (kp_map_t), UM_SLEEP);
        kp_data_t *kp = data;

        bcopy(pmp, &kpm->kpm_map, sizeof (mdb_map_t));

        if (kp->kp_map_tail != NULL)
                kp->kp_map_tail->kpm_next = kpm;
        else
                kp->kp_map_head = kpm;

        kp->kp_map_tail = kpm;
        kp->kp_num_maps++;
}

static kp_file_t *
kp_file_create(mdb_tgt_t *t, kp_map_t *kpm, GElf_Half etype)
{
        kp_file_t *kpf = mdb_zalloc(sizeof (kp_file_t), UM_SLEEP);
        kp_data_t *kp = t->t_data;
        size_t dyns_sz;
        void *dyns;

        kpf->kpf_fio = kp_io_create(t, kpm);
        kpf->kpf_map = kpm;
        kpf->kpf_basename = strbasename(kpm->kpm_map.map_name);
        kpf->kpf_file = mdb_gelf_create(kpf->kpf_fio, etype, GF_PROGRAM);
        kpf->kpf_text_base = kpm->kpm_map.map_base;

        if (kpm != kp->kp_map_exec)
                kpf->kpf_dyn_base = kpf->kpf_text_base;

        if (kpf->kpf_file == NULL)
                goto err; /* Failed to create ELF file */

        mdb_dprintf(MDB_DBG_TGT, "loading symbols for %s\n",
            kpm->kpm_map.map_name);

        if ((kp->kp_rap != NULL) && (rd_get_dyns(kp->kp_rap,
            kpf->kpf_text_base, &dyns, &dyns_sz) == RD_OK))
                mdb_gelf_dyns_set(kpf->kpf_file, dyns, dyns_sz);

        kpf->kpf_dynsym = mdb_gelf_symtab_create_dynamic(kpf->kpf_file,
            MDB_TGT_DYNSYM);

        if (kpf->kpf_dynsym == NULL)
                goto err; /* Failed to create symbol table */

        kpm->kpm_file = kpf;

        if (kp->kp_file_tail != NULL)
                kp->kp_file_tail->kpf_next = kpf;
        else
                kp->kp_file_head = kpf;

        kp->kp_file_tail = kpf;
        kp->kp_num_files++;

        return (kpf);

err:
        if (kpf->kpf_file != NULL)
                mdb_gelf_destroy(kpf->kpf_file);
        else
                mdb_io_destroy(kpf->kpf_fio);
        mdb_free(kpf, sizeof (kp_file_t));
        return (NULL);
}

static void
kp_file_destroy(kp_file_t *kpf)
{
        if (kpf->kpf_dynsym != NULL)
                mdb_gelf_symtab_destroy(kpf->kpf_dynsym);

        mdb_gelf_destroy(kpf->kpf_file);
        mdb_free(kpf, sizeof (kp_file_t));
}

static int
kp_setcontext(mdb_tgt_t *t, void *context)
{
        kp_data_t *kp = t->t_data;

        if (kp->kp_proc != context) {
                mdb_tgt_destroy(t);
                return (mdb_tgt_setcontext(mdb.m_target, context));
        }

        mdb_warn("debugger context is already set to proc %p\n", context);
        return (0);
}

static kp_map_t *
kp_find_data(kp_data_t *kp, kp_file_t *kpf, const rd_loadobj_t *rlp)
{
        GElf_Phdr *gpp = kpf->kpf_file->gf_phdrs;
        size_t i, n = kpf->kpf_file->gf_npload;

        /*
         * Find the first loadable, writeable Phdr and compute kpf_data_base
         * as the virtual address at which is was loaded.
         */
        for (i = 0; i < n; i++, gpp++) {
                if (gpp->p_type == PT_LOAD && (gpp->p_flags & PF_W)) {
                        kpf->kpf_data_base = gpp->p_vaddr;
                        if (kpf->kpf_map != kp->kp_map_exec)
                                kpf->kpf_data_base += rlp->rl_base;
                        break;
                }
        }

        /*
         * If we found a suitable Phdr and set kpf_data_base, return
         * the mapping information for this address; otherwise fail.
         */
        if (kpf->kpf_data_base != 0)
                return (kp_addr_to_kpmap(kp, kpf->kpf_data_base));

        return (NULL);
}

static int
kp_iter_mapping(const rd_loadobj_t *rlp, mdb_tgt_t *t)
{
        kp_data_t *kp = t->t_data;
        kp_file_t *kpf;
        kp_map_t *kpm;

        char name[MDB_TGT_MAPSZ];

        if (mdb_tgt_readstr(t, MDB_TGT_AS_VIRT, name,
            sizeof (name), (mdb_tgt_addr_t)rlp->rl_nameaddr) <= 0) {
                mdb_dprintf(MDB_DBG_TGT, "failed to read name %p",
                    (void *)rlp->rl_nameaddr);
                return (1); /* Keep going; forget this if we can't read name */
        }

        mdb_dprintf(MDB_DBG_TGT, "rd_loadobj name = \"%s\" rl_base = %p\n",
            name, (void *)rlp->rl_base);

        if ((kpm = kp_addr_to_kpmap(kp, rlp->rl_base)) == NULL)
                return (1); /* Keep going; no mapping at this address */

        (void) strncpy(kpm->kpm_map.map_name, name, MDB_TGT_MAPSZ);
        kpm->kpm_map.map_name[MDB_TGT_MAPSZ - 1] = '\0';

        if ((kpf = kpm->kpm_file) == NULL) {
                if (kpm == kp->kp_map_exec)
                        kpf = kp_file_create(t, kpm, ET_EXEC);
                else
                        kpf = kp_file_create(t, kpm, ET_DYN);

                if (kpf == NULL)
                        return (1); /* Keep going; failed to build ELF file */
        } else
                kpf->kpf_basename = strbasename(kpm->kpm_map.map_name);

        if ((kpm = kp_find_data(kp, kpf, rlp)) != NULL) {
                mdb_dprintf(MDB_DBG_TGT, "found data for %s at %p\n",
                    kpf->kpf_basename, (void *)kpm->kpm_map.map_base);
                kpm->kpm_file = kpf;
        }

        return (1);
}

/*ARGSUSED*/
static int
kp_status_dcmd(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv)
{
        kp_data_t *kp = mdb.m_target->t_data;

        mdb_printf("debugging PID %d (%d-bit) in kernel crash dump\n",
            kp->kp_pid, kp->kp_model == PR_MODEL_ILP32 ? 32 : 64);

        if (kp->kp_map_exec != NULL) {
                mdb_printf("executable file: %s\n",
                    kp->kp_map_exec->kpm_map.map_name);
        }

        return (DCMD_OK);
}

static const mdb_dcmd_t kp_dcmds[] = {
        { "status", NULL, "print summary of current target", kp_status_dcmd },
        { NULL }
};

static void
kp_activate(mdb_tgt_t *t)
{
        kp_data_t *kp = t->t_data;

        mdb_prop_postmortem = TRUE;
        mdb_prop_kernel = FALSE;

        if (kp->kp_model == PR_MODEL_ILP32)
                mdb_prop_datamodel = MDB_TGT_MODEL_ILP32;
        else
                mdb_prop_datamodel = MDB_TGT_MODEL_LP64;

        /*
         * Initialize our rtld_db agent and then iterate over the link map,
         * instantiating kp_file objects as we go.
         */
        if ((kp->kp_rap = rd_new((struct ps_prochandle *)t)) != NULL) {
                (void) rd_loadobj_iter(kp->kp_rap, (rl_iter_f *)
                    kp_iter_mapping, t);
        } else {
                mdb_warn("unable to initialize rtld_db agent for proc %p\n",
                    (void *)kp->kp_proc);
        }

        (void) mdb_tgt_register_dcmds(t, &kp_dcmds[0], MDB_MOD_FORCE);

        if (kp->kp_map_exec != NULL && kp->kp_map_exec->kpm_file != NULL)
                mdb_tgt_elf_export(kp->kp_map_exec->kpm_file->kpf_file);
        else
                mdb_tgt_elf_export(NULL);
}

static void
kp_deactivate(mdb_tgt_t *t)
{
        const mdb_dcmd_t *dcp;

        for (dcp = &kp_dcmds[0]; dcp->dc_name != NULL; dcp++) {
                if (mdb_module_remove_dcmd(t->t_module, dcp->dc_name) == -1)
                        warn("failed to remove dcmd %s", dcp->dc_name);
        }

        mdb_prop_postmortem = FALSE;
        mdb_prop_kernel = FALSE;
        mdb_prop_datamodel = MDB_TGT_MODEL_UNKNOWN;
}

static void
kp_destroy(mdb_tgt_t *t)
{
        kp_data_t *kp = t->t_data;
        kp_map_t *kpm, *nkpm;
        kp_file_t *kpf, *nkpf;

        if (kp->kp_rap != NULL)
                rd_delete(kp->kp_rap);

        for (kpm = kp->kp_map_head; kpm != NULL; kpm = nkpm) {
                nkpm = kpm->kpm_next;
                mdb_free(kpm, sizeof (kp_map_t));
        }

        for (kpf = kp->kp_file_head; kpf != NULL; kpf = nkpf) {
                nkpf = kpf->kpf_next;
                kp_file_destroy(kpf);
        }

        mdb_free(kp->kp_auxv, kp->kp_nauxv * sizeof (auxv_t));
        mdb_free(kp, sizeof (kp_data_t));
}

/*ARGSUSED*/
static const char *
kp_name(mdb_tgt_t *t)
{
        return ("kproc");
}

static const char *
kp_isa(mdb_tgt_t *t)
{
        kp_data_t *kp = t->t_data;
#ifdef __sparc
        return (kp->kp_model == PR_MODEL_ILP32 ? "sparc" : "sparcv9");
#else
        return (kp->kp_model == PR_MODEL_ILP32 ? "i386" : "amd64");
#endif
}

static const char *
kp_platform(mdb_tgt_t *t)
{
        return (((kp_data_t *)t->t_data)->kp_platform);
}

static int
kp_uname(mdb_tgt_t *t, struct utsname *utsp)
{
        kp_data_t *kp = t->t_data;
        return (mdb_tgt_uname(kp->kp_parent, utsp));
}

static int
kp_dmodel(mdb_tgt_t *t)
{
        kp_data_t *kp = t->t_data;

        switch (kp->kp_model) {
        case PR_MODEL_ILP32:
                return (MDB_TGT_MODEL_ILP32);
        case PR_MODEL_LP64:
                return (MDB_TGT_MODEL_LP64);
        }

        return (MDB_TGT_MODEL_UNKNOWN);
}

static kp_map_t *
kp_name_to_kpmap(kp_data_t *kp, const char *name)
{
        size_t namelen;
        kp_file_t *kpf;
        kp_map_t *kpm;

        /*
         * Handle special reserved names (except for MDB_TGT_OBJ_EVERY):
         */
        if (name == MDB_TGT_OBJ_EXEC)
                return (kp->kp_map_exec);

        if (name == MDB_TGT_OBJ_RTLD)
                return (kp->kp_map_ldso);

        /*
         * First pass: look for exact matches on the entire pathname
         * associated with the mapping or its basename.
         */
        for (kpm = kp->kp_map_head; kpm != NULL; kpm = kpm->kpm_next) {
                if ((kpf = kpm->kpm_file) != NULL) {
                        if (strcmp(kpm->kpm_map.map_name, name) == 0 ||
                            strcmp(kpf->kpf_basename, name) == 0)
                                return (kpf->kpf_map);
                }
        }

        namelen = strlen(name);

        /*
         * Second pass: look for partial matches (initial basename match
         * up to a '.' suffix); allows "libc.so" or "libc" to match "libc.so.1"
         */
        for (kpm = kp->kp_map_head; kpm != NULL; kpm = kpm->kpm_next) {
                if ((kpf = kpm->kpm_file) != NULL) {
                        if (strncmp(kpf->kpf_basename, name, namelen) == 0 &&
                            kpf->kpf_basename[namelen] == '.')
                                return (kpf->kpf_map);
                }
        }

        /*
         * One last check: we allow "a.out" to always alias the executable,
         * assuming this name was not in use for something else.
         */
        if (strcmp(name, "a.out") == 0)
                return (kp->kp_map_exec);

        return (NULL);
}


static ssize_t
kp_vread(mdb_tgt_t *t, void *buf, size_t nbytes, uintptr_t addr)
{
        kp_data_t *kp = t->t_data;
        ssize_t n = kvm_aread(kp->kp_cookie, addr, buf, nbytes, kp->kp_as);

        if (n == -1)
                return (set_errno(EMDB_NOMAP));

        return (n);
}

static ssize_t
kp_vwrite(mdb_tgt_t *t, const void *buf, size_t nbytes, uintptr_t addr)
{
        kp_data_t *kp = t->t_data;
        ssize_t n = kvm_awrite(kp->kp_cookie, addr, buf, nbytes, kp->kp_as);

        if (n == -1)
                return (set_errno(EMDB_NOMAP));

        return (n);
}


int
kp_vtop(mdb_tgt_t *t, mdb_tgt_as_t as, uintptr_t va, physaddr_t *pap)
{
        kp_data_t *kp = t->t_data;
        physaddr_t pa;

        if (as != MDB_TGT_AS_VIRT && as != MDB_TGT_AS_VIRT_I &&
            as != MDB_TGT_AS_VIRT_S)
                return (set_errno(EINVAL));

        if ((pa = kvm_physaddr(kp->kp_cookie, kp->kp_as, va)) != -1ULL) {
                *pap = pa;
                return (0);
        }

        return (set_errno(EMDB_NOMAP));
}

static int
kp_lookup_by_name(mdb_tgt_t *t, const char *object,
    const char *name, GElf_Sym *symp, mdb_syminfo_t *sip)
{
        kp_data_t *kp = t->t_data;
        kp_file_t *kpf;
        int n;

        GElf_Sym sym;
        uint_t symid;
        int rv = -1;

        /*
         * Simplify our task: if object is EVERY, then we need to search
         * kp_num_files files beginning at kp_file_head; otherwise we are
         * searching 1 file whose file pointer is obtained via object_to_map.
         */
        if (object != MDB_TGT_OBJ_EVERY) {
                kp_map_t *kpm = kp_name_to_kpmap(kp, object);
                if (kpm == NULL || kpm->kpm_file == NULL)
                        return (set_errno(EMDB_NOOBJ));
                kpf = kpm->kpm_file;
                n = 1;
        } else {
                kpf = kp->kp_file_head;
                n = kp->kp_num_files;
        }

        /*
         * Iterate through the load object files and look for the symbol name
         * in the .dynsym of each.  If we encounter a match with SHN_UNDEF,
         * keep looking in hopes of finding a better match.  This means that
         * a name such as "puts" will match the puts function in libc instead
         * of matching the puts PLT entry in the a.out file.
         */
        for (; n > 0; n--, kpf = kpf->kpf_next) {
                if (kpf->kpf_dynsym == NULL)
                        continue; /* No symbols for this file */

                if (mdb_gelf_symtab_lookup_by_name(kpf->kpf_dynsym,
                    name, symp, &sip->sym_id) != 0)
                        continue; /* Symbol name not found */

                symp->st_value += kpf->kpf_dyn_base;

                if (symp->st_shndx != SHN_UNDEF) {
                        sip->sym_table = MDB_TGT_DYNSYM;
                        return (0);
                }

                if (rv != 0) {
                        sym = *symp;
                        symid = sip->sym_id;
                        rv = 0;
                }
        }

        if (rv != 0)
                return (set_errno(EMDB_NOSYM));

        sip->sym_table = MDB_TGT_DYNSYM;
        sip->sym_id = symid;
        *symp = sym;

        return (0);
}

static int
kp_lookup_by_addr(mdb_tgt_t *t, uintptr_t addr, uint_t flags,
    char *buf, size_t nbytes, GElf_Sym *symp, mdb_syminfo_t *sip)
{
        kp_data_t *kp = t->t_data;
        kp_map_t *kpm = kp_addr_to_kpmap(kp, addr);

        kp_file_t *sym_kpf = NULL;
        GElf_Sym sym;
        uint_t symid;

        const char *name;
        kp_file_t *kpf;
        int n;

        /*
         * Check the user's private symbol table first; if a match is
         * found there, we're done or we have a first guess.
         */
        if (mdb_gelf_symtab_lookup_by_addr(mdb.m_prsym,
            addr, flags, buf, nbytes, symp, &sip->sym_id) == 0) {
                sym_kpf = &kp->kp_prfile;
                if (flags & MDB_TGT_SYM_EXACT)
                        goto found;
                sym = *symp;
                symid = sip->sym_id;
        }

        /*
         * If no mapping contains the address and EXACT mode is set, we're done.
         * Otherwise we need to search all the symbol tables in fuzzy mode.
         * If we find a mapping, then we only need to search that symtab.
         */
        if (kpm == NULL || kpm->kpm_file == NULL) {
                if (flags & MDB_TGT_SYM_EXACT)
                        return (set_errno(EMDB_NOSYMADDR));
                kpf = kp->kp_file_head;
                n = kp->kp_num_files;
        } else {
                kpf = kpm->kpm_file;
                n = 1;
        }

        /*
         * Iterate through our list of load objects, scanning each one which
         * has a symbol table.  In fuzzy mode, we continue looking and
         * improve our choice if we find a closer symbol.
         */
        for (; n > 0; n--, kpf = kpf->kpf_next) {
                if (kpf->kpf_dynsym == NULL)
                        continue; /* No symbols for this file */

                if (mdb_gelf_symtab_lookup_by_addr(kpf->kpf_dynsym,
                    addr - kpf->kpf_dyn_base, flags, buf, nbytes,
                    symp, &sip->sym_id) != 0)
                        continue; /* No symbol for this address */

                symp->st_value += kpf->kpf_dyn_base;

                if (flags & MDB_TGT_SYM_EXACT) {
                        sym_kpf = kpf;
                        goto found;
                }

                if (sym_kpf == NULL || mdb_gelf_sym_closer(symp, &sym, addr)) {
                        sym_kpf = kpf;
                        sym = *symp;
                        symid = sip->sym_id;
                }
        }

        if (sym_kpf == NULL)
                return (set_errno(EMDB_NOSYMADDR));

        *symp = sym;    /* Copy our best symbol into the caller's symbol */
        sip->sym_id = symid;
found:
        /*
         * Once we've found something, copy the final name into the caller's
         * buffer and prefix it with the load object name if appropriate.
         */
        name = mdb_gelf_sym_name(sym_kpf->kpf_dynsym, symp);

        if (sym_kpf != kp->kp_map_exec->kpm_file && sym_kpf != &kp->kp_prfile) {
                (void) mdb_snprintf(buf, nbytes, "%s`%s",
                    sym_kpf->kpf_basename, name);
        } else if (nbytes > 0) {
                (void) strncpy(buf, name, nbytes);
                buf[nbytes - 1] = '\0';
        }

        if (sym_kpf == &kp->kp_prfile)
                sip->sym_table = MDB_TGT_PRVSYM;
        else
                sip->sym_table = MDB_TGT_DYNSYM;

        return (0);
}

static int
kp_symtab_func(void *data, const GElf_Sym *symp, const char *name, uint_t id)
{
        kp_symarg_t *argp = data;
        if (mdb_tgt_sym_match(symp, argp->sym_type)) {
                GElf_Sym sym = *symp;

                sym.st_value += argp->sym_adjust;

                argp->sym_info.sym_id = id;

                return (argp->sym_cb(argp->sym_data, &sym, name,
                    &argp->sym_info, argp->sym_obj));
        }

        return (0);
}

static void
kp_symtab_iter(kp_file_t *kpf, uint_t type, const char *obj,
    mdb_tgt_sym_f *cb, void *data)
{
        if (kpf->kpf_dynsym != NULL) {
                kp_symarg_t arg;

                arg.sym_cb = cb;
                arg.sym_data = data;
                arg.sym_type = type;
                arg.sym_adjust = kpf->kpf_dyn_base;
                arg.sym_info.sym_table = kpf->kpf_dynsym->gst_tabid;
                arg.sym_obj = obj;

                mdb_gelf_symtab_iter(kpf->kpf_dynsym, kp_symtab_func, &arg);
        }
}

/*ARGSUSED*/
static int
kp_symbol_iter(mdb_tgt_t *t, const char *object, uint_t which,
    uint_t type, mdb_tgt_sym_f *func, void *private)
{
        kp_data_t *kp = t->t_data;
        kp_file_t *kpf = NULL;
        kp_map_t *kpm;

        switch ((uintptr_t)object) {
        case (uintptr_t)MDB_TGT_OBJ_EVERY:
                if (kp->kp_map_exec && kp->kp_map_exec->kpm_file) {
                        kpf = kp->kp_map_exec->kpm_file;
                        kp_symtab_iter(kpf, type, MDB_TGT_OBJ_EXEC, func,
                            private);
                }
                if (kp->kp_map_ldso && kp->kp_map_ldso->kpm_file) {
                        kpf = kp->kp_map_ldso->kpm_file;
                        kp_symtab_iter(kpf, type, MDB_TGT_OBJ_RTLD, func,
                            private);
                }
                return (0);

        case (uintptr_t)MDB_TGT_OBJ_EXEC:
                if (kp->kp_map_exec && kp->kp_map_exec->kpm_file)
                        kpf = kp->kp_map_exec->kpm_file;
                break;

        case (uintptr_t)MDB_TGT_OBJ_RTLD:
                if (kp->kp_map_ldso && kp->kp_map_ldso->kpm_file)
                        kpf = kp->kp_map_ldso->kpm_file;
                break;

        default:
                if ((kpm = kp_name_to_kpmap(kp, object)) != NULL) {
                        kpf = kpm->kpm_file;
                        break;
                } else
                        return (set_errno(EMDB_NOOBJ));
        }

        if (kpf != NULL)
                kp_symtab_iter(kpf, type, object, func, private);

        return (0);
}

static int
kp_mapping_iter(mdb_tgt_t *t, mdb_tgt_map_f *func, void *private)
{
        kp_data_t *kp = t->t_data;
        kp_map_t *kpm;

        for (kpm = kp->kp_map_head; kpm != NULL; kpm = kpm->kpm_next) {
                if (func(private, &kpm->kpm_map, kpm->kpm_map.map_name) != 0)
                        break;
        }

        return (0);
}

static int
kp_object_iter(mdb_tgt_t *t, mdb_tgt_map_f *func, void *private)
{
        kp_data_t *kp = t->t_data;
        kp_file_t *kpf;

        for (kpf = kp->kp_file_head; kpf != NULL; kpf = kpf->kpf_next) {
                if (func(private, &kpf->kpf_map->kpm_map,
                    kpf->kpf_map->kpm_map.map_name) != 0)
                        break;
        }

        return (0);
}

static const mdb_map_t *
kp_addr_to_map(mdb_tgt_t *t, uintptr_t addr)
{
        kp_map_t *kpm = kp_addr_to_kpmap(t->t_data, addr);

        if (kpm != NULL)
                return (&kpm->kpm_map);

        (void) set_errno(EMDB_NOMAP);
        return (NULL);
}

static const mdb_map_t *
kp_name_to_map(mdb_tgt_t *t, const char *name)
{
        kp_map_t *kpm = kp_name_to_kpmap(t->t_data, name);

        if (kpm != NULL)
                return (&kpm->kpm_map);

        (void) set_errno(EMDB_NOOBJ);
        return (NULL);
}

/*ARGSUSED*/
static int
kp_status(mdb_tgt_t *t, mdb_tgt_status_t *tsp)
{
        bzero(tsp, sizeof (mdb_tgt_status_t));
        tsp->st_state = MDB_TGT_DEAD;
        return (0);
}

static int
kp_auxv(mdb_tgt_t *t, const auxv_t **auxvp)
{
        kp_data_t *kp = t->t_data;
        *auxvp = kp->kp_auxv;
        return (0);
}

static const mdb_tgt_ops_t kproc_ops = {
        .t_setflags = (int (*)())(uintptr_t)mdb_tgt_notsup,
        .t_setcontext = kp_setcontext,
        .t_activate = kp_activate,
        .t_deactivate = kp_deactivate,
        .t_periodic = (void (*)())(uintptr_t)mdb_tgt_nop,
        .t_destroy = kp_destroy,
        .t_name = kp_name,
        .t_isa = kp_isa,
        .t_platform = kp_platform,
        .t_uname = kp_uname,
        .t_dmodel = kp_dmodel,
        .t_aread = (ssize_t (*)())mdb_tgt_notsup,
        .t_awrite = (ssize_t (*)())mdb_tgt_notsup,
        .t_vread = kp_vread,
        .t_vwrite = kp_vwrite,
        .t_pread = (ssize_t (*)())mdb_tgt_notsup,
        .t_pwrite = (ssize_t (*)())mdb_tgt_notsup,
        .t_fread = (ssize_t (*)())mdb_tgt_notsup,
        .t_fwrite = (ssize_t (*)())mdb_tgt_notsup,
        .t_ioread = (ssize_t (*)())mdb_tgt_notsup,
        .t_iowrite = (ssize_t (*)())mdb_tgt_notsup,
        .t_vtop = kp_vtop,
        .t_lookup_by_name = kp_lookup_by_name,
        .t_lookup_by_addr = kp_lookup_by_addr,
        .t_symbol_iter = kp_symbol_iter,
        .t_mapping_iter = kp_mapping_iter,
        .t_object_iter = kp_object_iter,
        .t_addr_to_map = kp_addr_to_map,
        .t_name_to_map = kp_name_to_map,
        .t_addr_to_ctf = (struct ctf_file *(*)())mdb_tgt_null,
        .t_name_to_ctf = (struct ctf_file *(*)())mdb_tgt_null,
        .t_status = kp_status,
        .t_run = (int (*)())(uintptr_t)mdb_tgt_notsup,
        .t_step = (int (*)())(uintptr_t)mdb_tgt_notsup,
        .t_step_out = (int (*)())(uintptr_t)mdb_tgt_notsup,
        .t_next = (int (*)())(uintptr_t)mdb_tgt_notsup,
        .t_cont = (int (*)())(uintptr_t)mdb_tgt_notsup,
        .t_signal = (int (*)())(uintptr_t)mdb_tgt_notsup,
        .t_add_sbrkpt = (int (*)())(uintptr_t)mdb_tgt_null,
        .t_add_vbrkpt = (int (*)())(uintptr_t)mdb_tgt_null,
        .t_add_pwapt = (int (*)())(uintptr_t)mdb_tgt_null,
        .t_add_vwapt = (int (*)())(uintptr_t)mdb_tgt_null,
        .t_add_iowapt = (int (*)())(uintptr_t)mdb_tgt_null,
        .t_add_sysenter = (int (*)())(uintptr_t)mdb_tgt_null,
        .t_add_sysexit = (int (*)())(uintptr_t)mdb_tgt_null,
        .t_add_signal = (int (*)())(uintptr_t)mdb_tgt_null,
        .t_add_fault = (int (*)())(uintptr_t)mdb_tgt_null,
        .t_getareg = (int (*)())(uintptr_t)mdb_tgt_notsup,      /* XXX */
        .t_putareg = (int (*)())(uintptr_t)mdb_tgt_notsup,      /* XXX */
        .t_stack_iter = (int (*)())(uintptr_t)mdb_tgt_notsup,   /* XXX */
        .t_auxv = kp_auxv,
        .t_thread_name = (int (*)())(uintptr_t)mdb_tgt_notsup,
};

int
mdb_kproc_tgt_create(mdb_tgt_t *t, int argc, const char *argv[])
{
        kp_data_t *kp = mdb_zalloc(sizeof (kp_data_t), UM_SLEEP);
        void *proc = (void *)argv[0];
        long at_entry, at_base;
        GElf_Sym sym;

        int (*f_asiter)(uintptr_t, void (*)(const mdb_map_t *, void *), void *);
        int (*f_auxv)(uintptr_t, auxv_t *);
        uintptr_t (*f_as)(uintptr_t);
        uint_t (*f_model)(uintptr_t);
        pid_t (*f_pid)(uintptr_t);

        if (argc != 1)
                return (set_errno(EINVAL));

        t->t_flags &= ~MDB_TGT_F_RDWR;
        t->t_data = kp;
        t->t_ops = &kproc_ops;

        f_asiter = (int (*)()) dlsym(RTLD_NEXT, "mdb_kproc_asiter");
        f_auxv = (int (*)()) dlsym(RTLD_NEXT, "mdb_kproc_auxv");
        f_as = (uintptr_t (*)()) dlsym(RTLD_NEXT, "mdb_kproc_as");
        f_model = (model_t (*)()) dlsym(RTLD_NEXT, "mdb_kproc_model");
        f_pid = (pid_t (*)()) dlsym(RTLD_NEXT, "mdb_kproc_pid");

        if (f_asiter == NULL || f_auxv == NULL ||
            f_as == NULL || f_model == NULL || f_pid == NULL) {
                warn("required kernel support module is not loaded\n");
                goto err;
        }

        /*
         * Here the kproc target relies on the fact that at the time of its
         * instantiation, mdb.m_target is pointing at a kvm target, and
         * that the kvm target has stored its libkvm handle in t_pshandle.
         */
        kp->kp_parent = mdb.m_target;
        kp->kp_cookie = mdb.m_target->t_pshandle;
        kp->kp_platform = mdb_tgt_platform(mdb.m_target);
        kp->kp_proc = proc;
        kp->kp_as = (struct as *)f_as((uintptr_t)proc);
        kp->kp_pid = f_pid((uintptr_t)proc);

        if (kp->kp_as == NULL) {
                warn("failed to obtain address space for proc %p\n", proc);
                goto err;
        }

        if (kp->kp_pid == -1) {
                warn("failed to obtain PID for proc %p\n", proc);
                goto err;
        }

        if (mdb_tgt_lookup_by_name(kp->kp_parent, MDB_TGT_OBJ_EXEC, "kas",
            &sym, NULL) == 0 && kp->kp_as ==
            (struct as *)(uintptr_t)sym.st_value) {
                warn("specified process is a system process (no context)\n");
                goto err;
        }

        if ((kp->kp_model = f_model((uintptr_t)proc)) == PR_MODEL_UNKNOWN) {
                warn("failed to obtain data model for proc %p\n", proc);
                goto err;
        }

        if (f_asiter((uintptr_t)kp->kp_as, kp_add_mapping, kp) == -1) {
                warn("failed to load mappings for proc %p", proc);
                goto err;
        }

        kp->kp_nauxv = f_auxv((uintptr_t)proc, NULL) + 1;
        kp->kp_auxv = mdb_alloc(sizeof (auxv_t) * kp->kp_nauxv, UM_SLEEP);

        if (f_auxv((uintptr_t)proc, kp->kp_auxv) == -1) {
                warn("failed to load auxv for proc %p", proc);
                goto err;
        }

        kp->kp_auxv[kp->kp_nauxv - 1].a_type = AT_NULL;
        kp->kp_auxv[kp->kp_nauxv - 1].a_un.a_val = 0;

        if ((at_entry = kp_getauxval(kp, AT_ENTRY)) == -1L) {
                warn("auxv for proc %p is missing AT_ENTRY\n", proc);
                goto err;
        }

        if ((at_base = kp_getauxval(kp, AT_BASE)) == -1L) {
                warn("auxv for proc %p is missing AT_BASE\n", proc);
                goto err;
        }

        /*
         * If we're applying kproc to a live kernel, we need to force libkvm
         * to set the current process to the process in question so we can
         * read from its address space.  If kvm_getproc returns NULL, the
         * process may have gone away since our previous calls to mdb_ks.
         */
        if (mdb_prop_postmortem == FALSE &&
            kvm_getproc(kp->kp_cookie, kp->kp_pid) == NULL)
                warn("failed to attach to PID %d\n", (int)kp->kp_pid);

        kp->kp_map_exec = kp_addr_to_kpmap(kp, at_entry);
        kp->kp_map_ldso = kp_addr_to_kpmap(kp, at_base);

        (void) kp_file_create(t, kp->kp_map_exec, ET_EXEC);
        (void) kp_file_create(t, kp->kp_map_ldso, ET_DYN);

        kp->kp_prfile.kpf_dynsym = mdb.m_prsym;

        return (0);

err:
        kp_destroy(t);
        return (-1);
}

static ssize_t
kp_io_read(mdb_io_t *io, void *buf, size_t nbytes)
{
        kp_io_t *kpi = io->io_data;
        kp_data_t *kp = kpi->kpi_tgt->t_data;

        kp_map_t *kpm = kp_addr_to_kpmap(kp, kpi->kpi_ptr);
        size_t left;

        if (kpm != NULL) {
                const mdb_map_t *mp = &kpm->kpm_map;
                left = mp->map_base + mp->map_size - kpi->kpi_ptr;
        } else
                left = 0;

        if (left != 0) {
                ssize_t rbytes = kp_vread(kpi->kpi_tgt,
                    buf, MIN(nbytes, left), kpi->kpi_ptr);

                if (rbytes >= 0)
                        kpi->kpi_ptr += rbytes;

                return (rbytes);
        }

        return (0); /* At end of segment or in hole; return EOF */
}

static off64_t
kp_io_seek(mdb_io_t *io, off64_t offset, int whence)
{
        kp_io_t *kpi = io->io_data;
        const mdb_map_t *mp = &kpi->kpi_map->kpm_map;
        uintptr_t nptr;

        if (io->io_next != NULL)
                return (IOP_SEEK(io->io_next, offset, whence));

        switch (whence) {
        case SEEK_SET:
                nptr = mp->map_base + offset;
                break;
        case SEEK_CUR:
                nptr = kpi->kpi_ptr + offset;
                break;
        case SEEK_END:
                nptr = kpi->kpi_lim + offset;
                break;
        default:
                return (set_errno(EINVAL));
        }

        if (nptr < mp->map_base || nptr >= kpi->kpi_lim)
                return (set_errno(EINVAL));

        kpi->kpi_ptr = nptr;
        return ((off64_t)(nptr - mp->map_base));
}

static void
kp_io_close(mdb_io_t *io)
{
        mdb_free(io->io_data, sizeof (kp_io_t));
}

static const char *
kp_io_name(mdb_io_t *io)
{
        kp_io_t *kpi = io->io_data;

        if (io->io_next != NULL)
                return (IOP_NAME(io->io_next));

        return (kpi->kpi_map->kpm_map.map_name);
}

static const mdb_io_ops_t kp_io_ops = {
        .io_read = kp_io_read,
        .io_write = no_io_write,
        .io_seek = kp_io_seek,
        .io_ctl = no_io_ctl,
        .io_close = kp_io_close,
        .io_name = kp_io_name,
        .io_link = no_io_link,
        .io_unlink = no_io_unlink,
        .io_setattr = no_io_setattr,
        .io_suspend = no_io_suspend,
        .io_resume = no_io_resume,
};

static mdb_io_t *
kp_io_create(mdb_tgt_t *t, kp_map_t *kpm)
{
        kp_data_t *kp = t->t_data;
        mdb_map_t *mp = &kp->kp_map_tail->kpm_map;

        mdb_io_t *io = mdb_alloc(sizeof (mdb_io_t), UM_SLEEP);
        kp_io_t *kpi = mdb_alloc(sizeof (kp_io_t), UM_SLEEP);

        kpi->kpi_tgt = t;
        kpi->kpi_map = kpm;
        kpi->kpi_ptr = kpm->kpm_map.map_base;
        kpi->kpi_lim = mp->map_base + mp->map_size;

        io->io_ops = &kp_io_ops;
        io->io_data = kpi;
        io->io_next = NULL;
        io->io_refcnt = 0;

        return (io);
}