/*
 * 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 (c) 1995, 2010, Oracle and/or its affiliates. All rights reserved.
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/uio.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/signal.h>
#include <sys/fault.h>
#include <sys/syscall.h>
#include <procfs.h>
#include <sys/auxv.h>
#include <libelf.h>
#include <sys/param.h>
#include <sys/machelf.h>
#include <stdarg.h>

#include "rdb.h"

static const char *fault_strings[] = {
	"<null string>",
	"illegal instruction",
	"privileged instruction",
	"breakpoint instruction",
	"trace trap (single-step)",
	"Memory access (e.g., alignment)",
	"Memory bounds (invalid address)",
	"Integer overflow",
	"Integer zero divide"
	"Floating-point exception",
	"Irrecoverable stack faul",
	"Recoverable page fault (no associated sig)"
};

#define	MAXFAULT	FLTPAGE

retc_t
set_breakpoint(struct ps_prochandle *ph, ulong_t addr, unsigned flags)
{
	bptlist_t	*new, *cur, *prev;

	for (cur = ph->pp_breakpoints, prev = NULL;
	    (cur && (cur->bl_addr < addr));
	    prev = cur, cur = cur->bl_next)
		;
	if (cur && (cur->bl_addr == addr)) {
		/*
		 * already have break point set here.
		 */
		cur->bl_flags |= flags;
		return (RET_OK);
	}

	new = malloc(sizeof (bptlist_t));
	new->bl_addr = addr;
	new->bl_flags = flags;
	if (prev == NULL) {
		/*
		 * insert at head
		 */
		new->bl_next = ph->pp_breakpoints;
		ph->pp_breakpoints = new;
		return (RET_OK);
	}

	prev->bl_next = new;
	new->bl_next = cur;
	return (RET_OK);
}

static bptlist_t *
find_bp(struct ps_prochandle *ph, ulong_t addr)
{
	bptlist_t	*cur;

	for (cur = ph->pp_breakpoints;
	    (cur && (cur->bl_addr != addr));
	    cur = cur->bl_next)
		;

	if ((cur == NULL) || (cur->bl_addr != addr))
		return ((bptlist_t *)-1);
	return (cur);
}

static retc_t
delete_bp(struct ps_prochandle *ph, ulong_t addr)
{
	bptlist_t	*cur, *prev;

	for (cur = ph->pp_breakpoints, prev = NULL;
	    (cur && (cur->bl_addr < addr));
	    prev = cur, cur = cur->bl_next)
		;
	if ((cur == NULL) || (cur->bl_addr != addr))
		return (RET_FAILED);

	if (prev == NULL)
		ph->pp_breakpoints = cur->bl_next;
	else
		prev->bl_next = cur->bl_next;

	free(cur);
	return (RET_OK);
}

void
list_breakpoints(struct ps_prochandle *ph)
{
	bptlist_t	*cur;

	if (ph->pp_breakpoints == NULL) {
		(void) printf("no active breakpoints.\n");
		return;
	}

	(void) printf("active breakpoints:\n");
	for (cur = ph->pp_breakpoints; cur; cur = cur->bl_next) {
		(void) printf("\t0x%08lx:0x%04x - %s\n", cur->bl_addr,
		    cur->bl_flags, print_address_ps(ph, cur->bl_addr,
		    FLG_PAP_SONAME));
	}
}

static void
set_breaks(struct ps_prochandle *ph)
{
	bptlist_t	*cur;
	bptinstr_t	bpt_instr = BPINSTR;

	for (cur = ph->pp_breakpoints; cur; cur = cur->bl_next) {
		bptinstr_t	old_inst = 0;

		if (ps_pread(ph, cur->bl_addr, (char *)&old_inst,
		    sizeof (bptinstr_t)) != PS_OK)
			perr("sb: error setting breakpoint");

		cur->bl_instr = old_inst;

		if (ps_pwrite(ph, cur->bl_addr, (char *)&bpt_instr,
		    sizeof (bptinstr_t)) != PS_OK)
			perr("sb1: error setting breakpoint\n");
	}

}

static void
clear_breaks(struct ps_prochandle *ph)
{
	bptlist_t	*cur;

	/*
	 * Restore all the original instructions
	 */
	for (cur = ph->pp_breakpoints; cur; cur = cur->bl_next)
		if (ps_pwrite(ph, cur->bl_addr, (char *)&(cur->bl_instr),
		    sizeof (bptinstr_t)) != PS_OK)
			perr("cb: error clearing breakpoint");
}

retc_t
delete_all_breakpoints(struct ps_prochandle *ph)
{
	bptlist_t	*cur, *prev;

	if (ph->pp_breakpoints == NULL)
		return (RET_OK);

	for (prev = NULL, cur = ph->pp_breakpoints;
	    cur; prev = cur, cur = cur->bl_next)
		if (prev)
			free(prev);
	if (prev)
		free(prev);

	ph->pp_breakpoints = NULL;
	return (RET_OK);
}

retc_t
delete_breakpoint(struct ps_prochandle *ph, ulong_t addr, unsigned flags)
{
	bptlist_t	*bpt;

	if (((bpt = find_bp(ph, addr)) == (bptlist_t *)-1) ||
	    ((bpt->bl_flags & flags) == 0))
		return (RET_FAILED);

	bpt->bl_flags &= ~flags;
	if (bpt->bl_flags)
		return (RET_OK);

	return (delete_bp(ph, addr));
}

static void
handle_sp_break(struct ps_prochandle *ph)
{
	rd_event_msg_t	emt;

	if (rd_event_getmsg(ph->pp_rap, &emt) != RD_OK) {
		(void) fprintf(stderr, "hsb: failed rd_event_getmsg()\n");
		return;
	}

	if (emt.type == RD_DLACTIVITY) {
		if (emt.u.state == RD_CONSISTENT)
			ph->pp_flags |= FLG_PP_LMAPS;
		else
			ph->pp_flags &= ~FLG_PP_LMAPS;
		if ((rdb_flags & RDB_FL_EVENTS) == 0)
			return;

		(void) printf("dlactivity: state changed to: ");
		switch (emt.u.state) {
		case RD_CONSISTENT:
			(void) printf("RD_CONSISTENT\n");
			break;
		case RD_ADD:
			(void) printf("RD_ADD\n");
			break;
		case RD_DELETE:
			(void) printf("RD_DELETE\n");
			break;
		default:
			(void) printf("unknown: 0x%x\n", emt.u.state);
		}
		return;
	}

	if ((rdb_flags & RDB_FL_EVENTS) == 0)
		return;

	if (emt.type == RD_PREINIT) {
		(void) printf("preinit reached\n");
		return;
	}

	if (emt.type == RD_POSTINIT)
		(void) printf("postinit reached\n");
}

unsigned
continue_to_break(struct ps_prochandle *ph)
{
	bptlist_t	*bpt;
	pstatus_t	pstatus;
	struct iovec	piov[5];
	long		oper1, oper2, oper3, pflags = 0;
	fltset_t	faults;

	/*
	 * We step by the first instruction incase their was
	 * a break-point there.
	 */
	(void) step_n(ph, 1, FLG_SN_NONE);

	premptyset(&faults);
	praddset(&faults, FLTBPT);
	praddset(&faults, FLTILL);
	praddset(&faults, FLTPRIV);
	praddset(&faults, FLTACCESS);
	praddset(&faults, FLTBOUNDS);
	praddset(&faults, FLTIZDIV);
	praddset(&faults, FLTSTACK);
	praddset(&faults, FLTTRACE);


	/* LINTED CONSTANT */
	while (1) {
		set_breaks(ph);
		oper1 = PCSFAULT;
		piov[0].iov_base = (caddr_t)(&oper1);
		piov[0].iov_len = sizeof (oper1);

		piov[1].iov_base = (caddr_t)(&faults);
		piov[1].iov_len = sizeof (faults);

		oper2 = PCRUN;
		piov[2].iov_base = (caddr_t)(&oper2);
		piov[2].iov_len = sizeof (oper2);
		pflags = PRCFAULT;
		piov[3].iov_base = (caddr_t)(&pflags);
		piov[3].iov_len = sizeof (pflags);

		oper3 = PCWSTOP;
		piov[4].iov_base = (caddr_t)(&oper3);
		piov[4].iov_len = sizeof (oper3);

		if (writev(ph->pp_ctlfd, piov, 5) == -1) {
			if (errno == ENOENT) {
				ph->pp_flags &= ~FLG_PP_PACT;

				(void) ps_close(ph);
				(void) printf("process terminated.\n");
				return (0);
			}
			perr("ctb: PCWSTOP");
		}

		if (pread(ph->pp_statusfd, &pstatus, sizeof (pstatus), 0) == -1)
			perr("ctb: reading status");


		if ((pstatus.pr_lwp.pr_why != PR_FAULTED) ||
		    (pstatus.pr_lwp.pr_what != FLTBPT)) {
			const char	*fltmsg;

			if ((pstatus.pr_lwp.pr_what <= MAXFAULT) &&
			    (pstatus.pr_lwp.pr_why == PR_FAULTED))
				fltmsg = fault_strings[pstatus.pr_lwp.pr_what];
			else
				fltmsg = "<unknown error>";

			(void) fprintf(stderr, "ctb: bad stop - stopped "
			    "on why: 0x%x what: %s(0x%x)\n",
			    pstatus.pr_lwp.pr_why, fltmsg,
			    pstatus.pr_lwp.pr_what);
			return (0);
		}

		oper1 = PCCFAULT;
		if (writev(ph->pp_ctlfd, piov, 1) == -1)
			perr("ctb: PCCFAULT");

		if ((bpt = find_bp(ph, pstatus.pr_lwp.pr_reg[R_PC])) ==
		    (bptlist_t *)-1) {
			(void) fprintf(stderr,
			    "stopped at unregistered breakpoint! "
			    "addr: 0x%x\n",
			    EC_WORD(pstatus.pr_lwp.pr_reg[R_PC]));
			break;
		}
		clear_breaks(ph);

		/*
		 * If this was a BP at which we should stop
		 */
		if (bpt->bl_flags & MASK_BP_STOP)
			break;

		(void) step_n(ph, 1, FLG_SN_NONE);
	}

	if (bpt->bl_flags & FLG_BP_USERDEF)
		(void) printf("break point reached at addr: 0x%x\n",
		    EC_WORD(pstatus.pr_lwp.pr_reg[R_PC]));

	if (bpt->bl_flags & MASK_BP_SPECIAL)
		handle_sp_break(ph);

	if (ph->pp_flags & FLG_PP_LMAPS) {
		if (get_linkmaps(ph) != RET_OK)
			(void) fprintf(stderr, "problem loading linkmaps\n");
	}

	return (bpt->bl_flags);
}

ulong_t
is_plt(struct ps_prochandle *ph, ulong_t pc)
{
	map_info_t	*mip;
	ulong_t		pltbase;

	if ((mip = addr_to_map(ph, pc)) == (map_info_t *)0)
		return ((ulong_t)0);

	pltbase = mip->mi_pltbase;
	if ((mip->mi_flags & FLG_MI_EXEC) == 0)
		pltbase += mip->mi_addr;

	if ((pc >= pltbase) && (pc <= (pltbase + mip->mi_pltsize)))
		return (pltbase);

	return ((ulong_t)0);
}

retc_t
step_n(struct ps_prochandle *ph, size_t count, sn_flags_e flgs)
{
	pstatus_t	pstatus;
	fltset_t	faults;
	int		i;
	long		oper;
	long		flags;
	struct iovec	piov[2];

	if (pread(ph->pp_statusfd, &pstatus, sizeof (pstatus), 0) == -1)
		perr("stn: reading status");

	piov[0].iov_base = (caddr_t)(&oper);
	piov[0].iov_len = sizeof (oper);

	premptyset(&faults);
	praddset(&faults, FLTTRACE);

	flags = PRSTEP | PRCFAULT;

	for (i = 0; i < count; i++) {
		bptlist_t	*bpt;
		uintptr_t	pc, pltbase;

		pc = pstatus.pr_lwp.pr_reg[R_PC];

		if ((bpt = find_bp(ph, pc)) != (bptlist_t *)-1) {
			if (bpt->bl_flags & MASK_BP_SPECIAL)
				handle_sp_break(ph);
		}

		if (flgs & FLG_SN_VERBOSE)
			disasm(ph, 1);

		oper = PCSFAULT;
		piov[1].iov_base = (caddr_t)(&faults);
		piov[1].iov_len = sizeof (faults);

		if (writev(ph->pp_ctlfd, piov, 2) == -1)
			perr("stn: PCSFAULT");

		oper = PCRUN;
		piov[1].iov_base = (caddr_t)(&flags);
		piov[1].iov_len = sizeof (flags);
		if (writev(ph->pp_ctlfd, piov, 2) == -1)
			perr("stn: PCRUN(PRSETP)");

		oper = PCWSTOP;
		if (writev(ph->pp_ctlfd, piov, 1) == -1)
			perr("stn: PCWSTOP stepping");

		if (pread(ph->pp_statusfd, &pstatus, sizeof (pstatus), 0) == -1)
			perr("stn1: reading status");
		pc = pstatus.pr_lwp.pr_reg[R_PC];


		if ((pstatus.pr_lwp.pr_why != PR_FAULTED) ||
		    (pstatus.pr_lwp.pr_what != FLTTRACE)) {
			(void) fprintf(stderr, "sn: bad stop - stopped on "
			    "why: 0x%x what: 0x%x\n", pstatus.pr_lwp.pr_why,
			    pstatus.pr_lwp.pr_what);
			return (RET_FAILED);
		}

		if ((flgs & FLG_SN_PLTSKIP) &&
		    ((pltbase = is_plt(ph, pc)) != (ulong_t)0)) {
			rd_plt_info_t	rp;
			if (rd_plt_resolution(ph->pp_rap, pc,
			    pstatus.pr_lwp.pr_lwpid, pltbase, &rp) != RD_OK) {
				(void) fprintf(stderr,
				    "sn: rd_plt_resolution failed\n");
				return (RET_FAILED);
			}
			if (rp.pi_skip_method == RD_RESOLVE_TARGET_STEP) {
				unsigned	bpflags;

				(void) set_breakpoint(ph, rp.pi_target,
				    FLG_BP_PLTRES);
				bpflags = continue_to_break(ph);

				(void) delete_breakpoint(ph, rp.pi_target,
				    FLG_BP_PLTRES);

				if (bpflags & FLG_BP_PLTRES)
					(void) step_n(ph, rp.pi_nstep,
					    FLG_SN_NONE);
			} else if (rp.pi_skip_method == RD_RESOLVE_STEP)
				(void) step_n(ph, rp.pi_nstep, FLG_SN_NONE);
		}
	}

	oper = PRCFAULT;
	if (writev(ph->pp_ctlfd, piov, 1) == -1)
		perr("stn: PRCFAULT");

	if ((flgs & FLG_SN_VERBOSE) && (ph->pp_flags & FLG_PP_LMAPS)) {
		if (get_linkmaps(ph) != RET_OK)
			(void) fprintf(stderr, "problem loading linkmaps\n");
	}

	return (RET_OK);
}

void
step_to_addr(struct ps_prochandle *ph, ulong_t addr)
{
	pstatus_t	pstat;
	int		count = 0;
	ulong_t		caddr;

	if (read(ph->pp_statusfd, &pstat, sizeof (pstat)) == -1)
		perr("sta: reading status");

	caddr = pstat.pr_lwp.pr_reg[R_PC];

	while ((caddr > addr) || ((caddr + 0xff) < addr)) {
		(void) step_n(ph, 1, FLG_SN_NONE);
		if (read(ph->pp_statusfd, &pstat, sizeof (pstat)) == -1)
			perr("sta1: reading status");
		caddr = pstat.pr_lwp.pr_reg[R_PC];
		if ((count % 10000) == 0) {
			(void) printf("%d: ", count);
			disasm(ph, 1);
		}

		count++;
	}

	(void) printf("address found %d instructions in: pc: 0x%lx addr: "
	    "0x%lx\n", count, caddr, addr);
}