/*
 * 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 2007 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#pragma ident	"%Z%%M%	%I%	%E% SMI"

#include <libproc.h>
#include <Pcontrol.h>
#include <stddef.h>

#include <mdb/mdb_modapi.h>

typedef struct ps_prochandle ps_prochandle_t;

/*
 * addr::pr_symtab [-a | n]
 *
 * 	-a	Sort symbols by address
 * 	-n	Sort symbols by name
 *
 * Given a sym_tbl_t, dump its contents in tabular form.  When given '-a' or
 * '-n', we use the sorted tables 'sym_byaddr' or 'sym_byname', respectively.
 */
static int
pr_symtab(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv)
{
	sym_tbl_t symtab;
	Elf_Data data_pri;
	Elf_Data data_aux;
	Elf_Data *data;
#ifdef _LP64
	Elf64_Sym sym;
	int width = 16;
#else
	Elf32_Sym sym;
	int width = 8;
#endif
	int i, idx, count;
	char name[128];
	int byaddr = FALSE;
	int byname = FALSE;
	uint_t *symlist;
	size_t symlistsz;

	if (mdb_getopts(argc, argv,
	    'a', MDB_OPT_SETBITS, TRUE, &byaddr,
	    'n', MDB_OPT_SETBITS, TRUE, &byname,
	    NULL) != argc)
		return (DCMD_USAGE);

	if (byaddr && byname) {
		mdb_warn("only one of '-a' or '-n' can be specified\n");
		return (DCMD_USAGE);
	}

	if (!(flags & DCMD_ADDRSPEC))
		return (DCMD_USAGE);

	if (mdb_vread(&symtab, sizeof (sym_tbl_t), addr) == -1) {
		mdb_warn("failed to read sym_tbl_t at %p", addr);
		return (DCMD_ERR);
	}

	if (symtab.sym_count == 0) {
		mdb_warn("no symbols present\n");
		return (DCMD_ERR);
	}

	/*
	 * As described in the libproc header Pcontrol.h, a sym_tbl_t
	 * contains a primary and an optional auxiliary symbol table.
	 * We treat the combination as a single table, with the auxiliary
	 * values coming before the primary ones.
	 *
	 * Read the primary and auxiliary Elf_Data structs.
	 */
	if (mdb_vread(&data_pri, sizeof (Elf_Data),
	    (uintptr_t)symtab.sym_data_pri) == -1) {
		mdb_warn("failed to read primary Elf_Data at %p",
		    symtab.sym_data_pri);
		return (DCMD_ERR);
	}
	if ((symtab.sym_symn_aux > 0) &&
	    (mdb_vread(&data_aux, sizeof (Elf_Data),
	    (uintptr_t)symtab.sym_data_aux) == -1)) {
		mdb_warn("failed to read auxiliary Elf_Data at %p",
		    symtab.sym_data_aux);
		return (DCMD_ERR);
	}

	symlist = NULL;
	if (byaddr || byname) {
		uintptr_t src = byaddr ? (uintptr_t)symtab.sym_byaddr :
		    (uintptr_t)symtab.sym_byname;

		symlistsz = symtab.sym_count * sizeof (uint_t);
		symlist = mdb_alloc(symlistsz, UM_SLEEP);
		if (mdb_vread(symlist, symlistsz, src) == -1) {
			mdb_warn("failed to read sorted symbols at %p", src);
			return (DCMD_ERR);
		}
		count = symtab.sym_count;
	} else {
		count = symtab.sym_symn;
	}

	mdb_printf("%<u>%*s  %*s  %s%</u>\n", width, "ADDRESS", width,
	    "SIZE", "NAME");

	for (i = 0; i < count; i++) {
		if (byaddr | byname)
			idx = symlist[i];
		else
			idx = i;

		/* If index is in range of primary symtab, look it up there */
		if (idx >= symtab.sym_symn_aux) {
			data = &data_pri;
			idx -= symtab.sym_symn_aux;
		} else {	/* Look it up in the auxiliary symtab */
			data = &data_aux;
		}

		if (mdb_vread(&sym, sizeof (sym), (uintptr_t)data->d_buf +
		    idx * sizeof (sym)) == -1) {
			mdb_warn("failed to read symbol at %p",
			    (uintptr_t)data->d_buf + idx * sizeof (sym));
			if (symlist)
				mdb_free(symlist, symlistsz);
			return (DCMD_ERR);
		}

		if (mdb_readstr(name, sizeof (name),
		    (uintptr_t)symtab.sym_strs + sym.st_name) == -1) {
			mdb_warn("failed to read symbol name at %p",
			    symtab.sym_strs + sym.st_name);
			name[0] = '\0';
		}

		mdb_printf("%0?p  %0?p  %s\n", sym.st_value, sym.st_size,
		    name);
	}

	if (symlist)
		mdb_free(symlist, symlistsz);

	return (DCMD_OK);
}

/*
 * addr::pr_addr2map search
 *
 * Given a ps_prochandle_t, convert the given address to the corresponding
 * map_info_t.  Functionally equivalent to Paddr2mptr().
 */
static int
pr_addr2map(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv)
{
	uintptr_t search;
	ps_prochandle_t psp;
	map_info_t *mp;
	int lo, hi, mid;

	if (!(flags & DCMD_ADDRSPEC) || argc != 1)
		return (DCMD_USAGE);

	if (argv[0].a_type == MDB_TYPE_IMMEDIATE)
		search = argv[0].a_un.a_val;
	else
		search = mdb_strtoull(argv[0].a_un.a_str);

	if (mdb_vread(&psp, sizeof (ps_prochandle_t), addr) == -1) {
		mdb_warn("failed to read ps_prochandle at %p", addr);
		return (DCMD_ERR);
	}

	lo = 0;
	hi = psp.map_count;
	while (lo <= hi) {
		mid = (lo + hi) / 2;
		mp = &psp.mappings[mid];

		if ((addr - mp->map_pmap.pr_vaddr) < mp->map_pmap.pr_size) {
			mdb_printf("%#lr\n", addr + offsetof(ps_prochandle_t,
			    mappings) + (mp - psp.mappings) *
			    sizeof (map_info_t));
			return (DCMD_OK);
		}

		if (addr < mp->map_pmap.pr_vaddr)
			hi = mid - 1;
		else
			lo = mid + 1;
	}

	mdb_warn("no corresponding map for %p\n", search);
	return (DCMD_ERR);
}

/*
 * ::walk pr_file_info
 *
 * Given a ps_prochandle_t, walk all its file_info_t structures.
 */
typedef struct {
	uintptr_t	fiw_next;
	int		fiw_count;
} file_info_walk_t;

static int
pr_file_info_walk_init(mdb_walk_state_t *wsp)
{
	ps_prochandle_t psp;
	file_info_walk_t *fiw;

	if (wsp->walk_addr == NULL) {
		mdb_warn("pr_file_info doesn't support global walks\n");
		return (WALK_ERR);
	}

	if (mdb_vread(&psp, sizeof (ps_prochandle_t), wsp->walk_addr) == -1) {
		mdb_warn("failed to read ps_prochandle at %p", wsp->walk_addr);
		return (WALK_ERR);
	}

	fiw = mdb_alloc(sizeof (file_info_walk_t), UM_SLEEP);

	fiw->fiw_next = (uintptr_t)psp.file_head.list_forw;
	fiw->fiw_count = psp.num_files;
	wsp->walk_data = fiw;

	return (WALK_NEXT);
}

static int
pr_file_info_walk_step(mdb_walk_state_t *wsp)
{
	file_info_walk_t *fiw = wsp->walk_data;
	file_info_t f;
	int status;

	if (fiw->fiw_count == 0)
		return (WALK_DONE);

	if (mdb_vread(&f, sizeof (file_info_t), fiw->fiw_next) == -1) {
		mdb_warn("failed to read file_info_t at %p", fiw->fiw_next);
		return (WALK_ERR);
	}

	status = wsp->walk_callback(fiw->fiw_next, &f, wsp->walk_cbdata);

	fiw->fiw_next = (uintptr_t)f.file_list.list_forw;
	fiw->fiw_count--;

	return (status);
}

static void
pr_file_info_walk_fini(mdb_walk_state_t *wsp)
{
	file_info_walk_t *fiw = wsp->walk_data;
	mdb_free(fiw, sizeof (file_info_walk_t));
}

/*
 * ::walk pr_map_info
 *
 * Given a ps_prochandle_t, walk all its map_info_t structures.
 */
typedef struct {
	uintptr_t	miw_next;
	int		miw_count;
	int		miw_current;
} map_info_walk_t;

static int
pr_map_info_walk_init(mdb_walk_state_t *wsp)
{
	ps_prochandle_t psp;
	map_info_walk_t *miw;

	if (wsp->walk_addr == NULL) {
		mdb_warn("pr_map_info doesn't support global walks\n");
		return (WALK_ERR);
	}

	if (mdb_vread(&psp, sizeof (ps_prochandle_t), wsp->walk_addr) == -1) {
		mdb_warn("failed to read ps_prochandle at %p", wsp->walk_addr);
		return (WALK_ERR);
	}

	miw = mdb_alloc(sizeof (map_info_walk_t), UM_SLEEP);

	miw->miw_next = (uintptr_t)psp.mappings;
	miw->miw_count = psp.map_count;
	miw->miw_current = 0;
	wsp->walk_data = miw;

	return (WALK_NEXT);
}

static int
pr_map_info_walk_step(mdb_walk_state_t *wsp)
{
	map_info_walk_t *miw = wsp->walk_data;
	map_info_t m;
	int status;

	if (miw->miw_current == miw->miw_count)
		return (WALK_DONE);

	if (mdb_vread(&m, sizeof (map_info_t), miw->miw_next) == -1) {
		mdb_warn("failed to read map_info_t at %p", miw->miw_next);
		return (WALK_DONE);
	}

	status = wsp->walk_callback(miw->miw_next, &m, wsp->walk_cbdata);

	miw->miw_current++;
	miw->miw_next += sizeof (map_info_t);

	return (status);
}

static void
pr_map_info_walk_fini(mdb_walk_state_t *wsp)
{
	map_info_walk_t *miw = wsp->walk_data;
	mdb_free(miw, sizeof (map_info_walk_t));
}

static const mdb_dcmd_t dcmds[] = {
	{ "pr_addr2map",  ":addr", "convert an adress into a map_info_t",
	    pr_addr2map },
	{ "pr_symtab",	":[-a | -n]", "print the contents of a sym_tbl_t",
	    pr_symtab },
	{ NULL }
};

static const mdb_walker_t walkers[] = {
	{ "pr_file_info", "given a ps_prochandle, walk its file_info "
	    "structures", pr_file_info_walk_init, pr_file_info_walk_step,
	    pr_file_info_walk_fini },
	{ "pr_map_info", "given a ps_prochandle, walk its map_info structures",
	    pr_map_info_walk_init, pr_map_info_walk_step,
	    pr_map_info_walk_fini },
	{ NULL }
};

static const mdb_modinfo_t modinfo = {
	MDB_API_VERSION, dcmds, walkers
};

const mdb_modinfo_t *
_mdb_init(void)
{
	return (&modinfo);
}