root/usr/src/cmd/sgs/demo_rdb/common/bpt.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 (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);
}