OpenSolaris_b135/lib/libtnfctl/prb_rtld.c

Compare this file to the similar file:
Show the results in this format:

/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (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 2004 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

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

/*
 * Interfaces to sync up with run time linker (rtld) at process start up time
 * and at dlopen() and dlclose() time
 * In Solaris 2.6, librtld_db.so should replace this functionality.  Issues
 * to solve before libtnfctl.so can use librtld_db.so:
 * 1. Should libtnfctl.so be usable before Solaris 2.6 - If so, cannot use
 *    librtld_db.so
 * 2. libtnfctl.so will have to provide <proc_service.h> in order to use
 *    librtld_db.so.  If libtnfctl.so is now linked into a debugger that
 *    also provides <proc_service.h>, how will the two co-exist - will the
 *    linker get confused, or not ?
 */

#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/fcntl.h>
#include <sys/procfs.h>
#include <link.h>

#include "tnfctl.h"
#include "prb_proc_int.h"
#include "dbg.h"


static prb_status_t prb_rtld_setup(prb_proc_ctl_t *proc_p, boolean_t *synced);
static prb_status_t prb_rtld_wait(prb_proc_ctl_t *proc_p);
static prb_status_t bpt(prb_proc_ctl_t *proc_p, uintptr_t addr);
static prb_status_t unbpt(prb_proc_ctl_t *proc_p, uintptr_t addr);


/* ---------------------------------------------------------------- */
/* ----------------------- Public Functions ----------------------- */
/* ---------------------------------------------------------------- */


/*
 * prb_rtld_stalk() - setup for a breakpoint when rtld has opened or closed a
 * shared object.
 */
prb_status_t
prb_rtld_stalk(prb_proc_ctl_t *proc_p)
{
	prb_status_t	prbstat = PRB_STATUS_OK;

	DBG_TNF_PROBE_0(prb_rtld_stalk_1, "libtnfctl", "sunw%verbosity 2");

	if (!proc_p->bptaddr) {
		Elf3264_Dyn	   dentry;
		struct r_debug  r_dbg;

		if (proc_p->dbgaddr == 0) {
			DBG((void) fprintf(stderr,
				"prb_rtld_stalk: dbgaddr not set\n"));
			return (PRB_STATUS_BADARG);
		}

		prbstat = prb_proc_read(proc_p, proc_p->dbgaddr,
			&dentry, sizeof (dentry));
		if (prbstat || !dentry.d_un.d_ptr) {
			DBG((void) fprintf(stderr,
				"prb_rtld_stalk: error in d_un.d_ptr\n"));
			return (prbstat);
		}
		/* read in the debug struct that it points to */
		prbstat = prb_proc_read(proc_p, dentry.d_un.d_ptr,
			&r_dbg, sizeof (r_dbg));
		if (prbstat)
			return (prbstat);

		proc_p->bptaddr = r_dbg.r_brk;
	}
	/* plant a breakpoint trap in the pointed to function */
	prbstat = bpt(proc_p, proc_p->bptaddr);
	if (prbstat)
		return (prbstat);

	/* setup process to stop when breakpoint encountered */
	prbstat = prb_proc_tracebpt(proc_p, B_TRUE);

	return (prbstat);

}


/*
 * prb_rtld_unstalk() - remove rtld breakpoint
 */
prb_status_t
prb_rtld_unstalk(prb_proc_ctl_t *proc_p)
{
	prb_status_t	prbstat;

	DBG_TNF_PROBE_0(prb_rtld_unstalk_1, "libtnfctl", "sunw%verbosity 2");

	/* turn off BPT tracing while out of the water ... */
	prbstat = prb_proc_tracebpt(proc_p, B_FALSE);

	prbstat = unbpt(proc_p, proc_p->bptaddr);

	return (prbstat);
}


/*
 * prb_rtld_advance() - we've hit a breakpoint, replace the original
 * instruction, istep, put the breakpoint back ...
 */
prb_status_t
prb_rtld_advance(prb_proc_ctl_t *proc_p)
{
	prb_status_t	prbstat;

	DBG_TNF_PROBE_0(prb_rtld_advance_1, "libtnfctl", "sunw%verbosity 2");

	prbstat = prb_proc_clrbptflt(proc_p);
	if (prbstat)
		return (prbstat);
	prbstat = unbpt(proc_p, proc_p->bptaddr);
	if (prbstat)
		return (prbstat);

	prbstat = prb_proc_istepbpt(proc_p);
	if (prbstat)
		return (prbstat);

	prbstat = bpt(proc_p, proc_p->bptaddr);
	if (prbstat)
		return (prbstat);

	return (PRB_STATUS_OK);
}

/*
 * checks if process has reached rtld_sync point or not i.e. has rltld
 * loaded in libraries or not ?  If not, it lets process run until
 * rtld has mapped in all libraries (no user code would have been
 * executed, including .init sections)
 */
prb_status_t
prb_rtld_sync_if_needed(prb_proc_ctl_t *proc_p)
{
	prb_status_t	prbstat = PRB_STATUS_OK;
	boolean_t	synced = B_FALSE;

	prbstat = prb_rtld_setup(proc_p, &synced);
	if (prbstat)
		return (prbstat);

	if (synced == B_FALSE) {
		/* wait on target to sync up after rtld maps in all .so's */
		prbstat = prb_rtld_wait(proc_p);
		if (prbstat)
			return (prbstat);
	}

	return (prbstat);
}

/* ---------------------------------------------------------------- */
/* ----------------------- Private Functions ---------------------- */
/* ---------------------------------------------------------------- */

/*
 * prb_rtld_setup() - turns on the flag in the rtld structure so that rtld
 * executes a getpid() stystem call after it done mapping all shared objects
 * but before it executes any init code.
 */
static prb_status_t
prb_rtld_setup(prb_proc_ctl_t *proc_p, boolean_t *synced)
{
	prb_status_t	prbstat = PRB_STATUS_OK;
	Elf3264_Dyn	dentry;

	DBG_TNF_PROBE_0(prb_rtld_setup_1, "libtnfctl", "sunw%verbosity 2");

	if (proc_p->dbgaddr == 0) {
		DBG((void) fprintf(stderr,
			"prb_rtld_setup: dbgaddr not set\n"));
		return (PRB_STATUS_BADARG);
	}

	prbstat = prb_proc_read(proc_p, proc_p->dbgaddr, &dentry,
					sizeof (dentry));
	if (prbstat) {
		DBG((void) fprintf(stderr,
			"prb_rtld_setup: error in d_un.d_ptr\n"));
		return (prbstat);
	}

	if ((dentry.d_un.d_ptr == 0) || (dentry.d_un.d_ptr == 1)) {
		*synced = B_FALSE;
	} else {
		*synced = B_TRUE;
		return (PRB_STATUS_OK);
	}

	/* modify it  - i.e. request rtld to do getpid() */
	dentry.d_un.d_ptr = 1;
	prbstat = prb_proc_write(proc_p, proc_p->dbgaddr, &dentry,
					sizeof (dentry));

	return (prbstat);
}


/*
 * prb_rtld_wait() - waits on target to execute getpid()
 */
static prb_status_t
prb_rtld_wait(prb_proc_ctl_t *proc_p)
{
	prb_proc_state_t pstate;
	prb_status_t	prbstat;

	DBG_TNF_PROBE_0(prb_rtld_wait_1, "libtnfctl", "sunw%verbosity 2");

	/* stop on exit of getpid() */
	prbstat = prb_proc_exit(proc_p, SYS_getpid, PRB_SYS_ADD);
	if (prbstat) {
		DBG((void) fprintf(stderr,
			"prb_rtld_wait: couldn't set up child to stop on "
			"exit of getpid(): %s\n", prb_status_str(prbstat)));
		return (prbstat);
	}
	/* stop on entry of exit() - i.e. exec failed */
	prbstat = prb_proc_entry(proc_p, SYS_exit, PRB_SYS_ADD);
	if (prbstat) {
		DBG((void) fprintf(stderr,
			"prb_rtld_wait: couldn't set up child to stop on "
			"entry of exit(): %s\n", prb_status_str(prbstat)));
		return (prbstat);
	}
	/* continue target and wait for it to stop */
	prbstat = prb_proc_cont(proc_p);
	if (prbstat) {
		DBG((void) fprintf(stderr,
			"prb_rtld_wait: couldn't continue target process: %s\n",
				prb_status_str(prbstat)));
		return (prbstat);
	}
	/* wait for target to stop */
	prbstat = prb_proc_wait(proc_p, B_FALSE, NULL);
	if (prbstat) {
		DBG((void) fprintf(stderr,
			"prb_rtld_wait: couldn't wait on target process: %s\n",
			prb_status_str(prbstat)));
		return (prbstat);
	}
	/* make sure it did stop on getpid() */
	prbstat = prb_proc_state(proc_p, &pstate);
	if (prbstat) {
		DBG((void) fprintf(stderr,
			"prb_rtld_wait: couldn't get state of target: %s\n",
				prb_status_str(prbstat)));
		return (prbstat);
	}
	if (pstate.ps_issysentry && (pstate.ps_syscallnum == SYS_exit)) {
		DBG((void) fprintf(stderr, "prb_rtld_wait: target exited\n"));
		return (prb_status_map(EACCES));
	}
	/* catch any other errors */
	if (!(pstate.ps_issysexit && (pstate.ps_syscallnum == SYS_getpid))) {
		DBG((void) fprintf(stderr,
			"prb_rtld_wait: target didn't stop on getpid\n"));
		return (PRB_STATUS_BADSYNC);
	}
	/* clear wait on getpid */
	prbstat = prb_proc_exit(proc_p, SYS_getpid, PRB_SYS_DEL);
	if (prbstat) {
		DBG((void) fprintf(stderr,
			"prb_rtld_wait: couldn't clear child to stop on "
			"exit of getpid(): %s\n", prb_status_str(prbstat)));
		return (prbstat);
	}
	/* clear wait on exit */
	prbstat = prb_proc_entry(proc_p, SYS_exit, PRB_SYS_DEL);
	if (prbstat) {
		DBG((void) fprintf(stderr,
			"prb_rtld_wait: couldn't clear child to stop on "
			"entry of exit(): %s\n", prb_status_str(prbstat)));
		return (prbstat);
	}
	/* start-stop the process to clear it out of the system call */
	prbstat = prb_proc_prstop(proc_p);
	if (prbstat) {
		DBG((void) fprintf(stderr,
			"prb_rtld_wait: couldn't prstop child: %s\n",
			prb_status_str(prbstat)));
		return (prbstat);
	}
	return (PRB_STATUS_OK);
}


#if defined(__sparc)
#define	INS_BPT 0x91d02001
#elif defined(__i386) || defined(__amd64)
#define	INS_BPT 0xcc
#else
#error  What is your breakpoint instruction?
#endif

/*
 * plants a breakpoint at the specified location in
 * the target process, and saves the existing instruction.
 */
static prb_status_t
bpt(prb_proc_ctl_t *proc_p, uintptr_t addr)
{
	prb_status_t	prbstat;
	bptsave_t	instr;

	if (!proc_p->bpt_inserted) {

		DBG_TNF_PROBE_1(bpt_1, "libtnfctl", "sunw%verbosity 2",
			tnf_opaque, bpt_planted_at, addr);

		prbstat = prb_proc_read(proc_p, addr,
			&(proc_p->saveinstr), sizeof (proc_p->saveinstr));
		if (prbstat)
			return (prbstat);

		DBG_TNF_PROBE_1(bpt_2, "libtnfctl", "sunw%verbosity 2",
			tnf_opaque, saved_instr, (unsigned)proc_p->saveinstr);

		instr = INS_BPT;

		prbstat = prb_proc_write(proc_p, addr,
			&instr, sizeof (instr));
		if (prbstat)
			return (prbstat);

		proc_p->bpt_inserted = B_TRUE;
	}
	return (PRB_STATUS_OK);
}

/*
 * removes  a breakpoint at the specified location in
 * the target process, and replaces it with the original instruction.
 */
prb_status_t
unbpt(prb_proc_ctl_t *proc_p, uintptr_t addr)
{
	prb_status_t	prbstat;

	if (proc_p->bpt_inserted) {

		DBG_TNF_PROBE_2(unbpt_1, "libtnfctl", "sunw%verbosity 2",
			tnf_opaque, unplanting_at, addr,
			tnf_opaque, saved_instr, (unsigned)proc_p->saveinstr);

		prbstat = prb_proc_write(proc_p, addr, &(proc_p->saveinstr),
			sizeof (proc_p->saveinstr));
		if (prbstat)
			return (prbstat);

		proc_p->bpt_inserted = B_FALSE;
	}
	return (PRB_STATUS_OK);
}