OpenSolaris_b135/lib/libtnfctl/open.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 (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 2010 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*
 * Interfaces that return a tnfctl handle back to client (except for
 * tnfctl_internal_open()) and helper functions for these interfaces.
 * Also has buffer alloc, buffer dealloc, and trace attributes retrieval
 * interfaces.
 */

#include "tnfctl_int.h"
#include "kernel_int.h"
#include "dbg.h"

#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <errno.h>

static tnfctl_errcode_t attach_pid(pid_t pid, prb_proc_ctl_t **proc_pp);
static tnfctl_errcode_t step_to_end_of_exec(tnfctl_handle_t *hndl);

/*
 * invokes the target program and executes it till the run time linker (rtld)
 * has loaded in the shared objects (but before any .init sections are
 * executed).  Returns a pointer to a tnfctl handle.
 */
tnfctl_errcode_t
tnfctl_exec_open(const char *pgm_name, char * const *args,  char * const *envp,
		const char *ld_preload,
		const char *libtnfprobe_path,
		tnfctl_handle_t **ret_val)
{
	tnfctl_handle_t	*hdl;
	prb_proc_ctl_t	*proc_p = NULL;
	prb_status_t	prbstat;
	uintptr_t	dbgaddr;
	tnfctl_errcode_t	prexstat;

	prbstat = prb_child_create(pgm_name, args, ld_preload, libtnfprobe_path,
	    envp, &proc_p);
	if (prbstat) {
		return (_tnfctl_map_to_errcode(prbstat));
	}

	/* allocate hdl and zero fill */
	hdl = calloc(1, sizeof (*hdl));
	if (hdl == NULL) {
		(void) prb_proc_close(proc_p);
		return (TNFCTL_ERR_ALLOCFAIL);
	}

	hdl->proc_p = proc_p;
	hdl->mode = DIRECT_MODE;
	hdl->called_exit = B_FALSE;

	/* use native /proc on this target */
	hdl->p_read = _tnfctl_read_targ;
	hdl->p_write = _tnfctl_write_targ;
	hdl->p_obj_iter = _tnfctl_loadobj_iter;
	hdl->p_getpid = _tnfctl_pid_get;

	/*
	 * get the address of DT_DEBUG and send it in to prb_ layer.
	 * This is needed before before prb_rtld_sync() can be called.
	 */
	prexstat = _tnfctl_elf_dbgent(hdl, &dbgaddr);
	if (prexstat)
		goto failure_ret;

	prb_dbgaddr(proc_p, dbgaddr);

	/* sync up to rtld sync point */
	prbstat = prb_rtld_sync_if_needed(proc_p);
	if (prbstat) {
		prexstat = _tnfctl_map_to_errcode(prbstat);
		goto failure_ret;
	}

	/* initialize state in handle */
	prexstat = _tnfctl_set_state(hdl);
	if (prexstat)
		goto failure_ret;

	prexstat = _tnfctl_external_getlock(hdl);
	if (prexstat)
		goto failure_ret;

	*ret_val = hdl;
	/* Successful return */
	return (TNFCTL_ERR_NONE);

failure_ret:
	(void) prb_proc_close(proc_p);
	free(hdl);
	return (prexstat);
}


/*
 * attaches to a running process.  If the process is in the beginning
 * of an exec(2) system call (which is how tnfctl_continue() returns on exec),
 * it steps the process till the end of the the exec. If the process hasn't
 * reached the rtld sync point, the process is continued until it does
 * reach it.  Returns a pointer to a tnfctl handle.
 */
tnfctl_errcode_t
tnfctl_pid_open(pid_t pid, tnfctl_handle_t **ret_val)
{
	tnfctl_handle_t	*hdl;
	prb_proc_ctl_t	*proc_p = NULL;
	uintptr_t	dbgaddr;
	prb_status_t	prbstat;
	tnfctl_errcode_t	prexstat;

	prexstat = attach_pid(pid, &proc_p);
	if (prexstat) {
		return (prexstat);
	}

	/* allocate hdl and zero fill */
	hdl = calloc(1, sizeof (*hdl));
	if (hdl == NULL) {
		(void) prb_proc_close(proc_p);
		return (TNFCTL_ERR_ALLOCFAIL);
	}

	hdl->proc_p = proc_p;
	hdl->mode = DIRECT_MODE;
	hdl->called_exit = B_FALSE;

	/* use native /proc on this target */
	hdl->p_read = _tnfctl_read_targ;
	hdl->p_write = _tnfctl_write_targ;
	hdl->p_obj_iter = _tnfctl_loadobj_iter;
	hdl->p_getpid = _tnfctl_pid_get;

	/*
	 * Since tnfctl_continue() returns when a process does an exec
	 * and leaves the process stopped at the beginning of exec, we
	 * have to be sure to catch this case.
	 */
	prexstat = step_to_end_of_exec(hdl);
	/* proc_p could be side effected by step_to_end_of_exec() */
	proc_p = hdl->proc_p;
	if (prexstat)
		goto failure_ret;

	/*
	 * get the address of DT_DEBUG and send it in to prb_ layer.
	 */
	prexstat = _tnfctl_elf_dbgent(hdl, &dbgaddr);
	if (prexstat)
		goto failure_ret;

	prb_dbgaddr(proc_p, dbgaddr);

	/* sync up to rtld sync point if target is not there yet */
	prbstat = prb_rtld_sync_if_needed(proc_p);
	if (prbstat) {
		prexstat = _tnfctl_map_to_errcode(prbstat);
		goto failure_ret;
	}

	/* initialize state in handle */
	prexstat = _tnfctl_set_state(hdl);
	if (prexstat)
		goto failure_ret;

	/* set state in target indicating we're tracing externally */
	prexstat = _tnfctl_external_getlock(hdl);
	if (prexstat)
		goto failure_ret;

	*ret_val = hdl;

	/* Sucessful return */
	return (TNFCTL_ERR_NONE);

failure_ret:
	(void) prb_proc_close(proc_p);
	free(hdl);
	return (prexstat);
}

/*
 * open a process for tracing without using native /proc on it.  The client
 * provides a set of callback functions which encapsulate the /proc
 * functionality we need.  Returns a pointer to a tnfctl handle.
 */
tnfctl_errcode_t
tnfctl_indirect_open(void *prochandle, tnfctl_ind_config_t *config,
		tnfctl_handle_t **ret_val)
{
	tnfctl_handle_t	*hdl;
	tnfctl_errcode_t	prexstat;

	/* allocate hdl and zero fill */
	hdl = calloc(1, sizeof (*hdl));
	if (hdl == NULL) {
		return (TNFCTL_ERR_ALLOCFAIL);
	}

	hdl->proc_p = prochandle;
	hdl->mode = INDIRECT_MODE;
	hdl->called_exit = B_FALSE;

	/* initialize callback functions */
	hdl->p_read = config->p_read;
	hdl->p_write = config->p_write;
	hdl->p_obj_iter = config->p_obj_iter;
	hdl->p_getpid = config->p_getpid;

	/* initialize state in handle */
	prexstat = _tnfctl_set_state(hdl);
	if (prexstat) {
		free(hdl);
		return (prexstat);
	}
	/* set state in target indicating we're tracing externally */
	prexstat = _tnfctl_external_getlock(hdl);
	if (prexstat) {
		free(hdl);
		return (prexstat);
	}
	*ret_val = hdl;
	return (TNFCTL_ERR_NONE);
}

/*
 * Returns a pointer to a tnfctl handle that can do kernel trace control
 * and kernel probe control.
 */
tnfctl_errcode_t
tnfctl_kernel_open(tnfctl_handle_t **ret_val)
{
	tnfctl_handle_t	*hdl;
	tnfctl_errcode_t	prexstat;

	/* allocate hdl and zero fill */
	hdl = calloc(1, sizeof (*hdl));
	if (hdl == NULL) {
		return (TNFCTL_ERR_ALLOCFAIL);
	}

	/* initialize kernel tracing */
	prexstat = _tnfctl_prbk_init(hdl);
	if (prexstat)
		return (prexstat);

	hdl->mode = KERNEL_MODE;
	hdl->targ_pid = 0;

	/* initialize function pointers that can be stuffed into a probe */
	_tnfctl_prbk_get_other_funcs(&hdl->allocfunc, &hdl->commitfunc,
	    &hdl->rollbackfunc, &hdl->endfunc);
	_tnfctl_prbk_test_func(&hdl->testfunc);

	/* find the probes in the kernel */
	prexstat = _tnfctl_refresh_kernel(hdl);
	if (prexstat)
		return (prexstat);

	*ret_val = hdl;
	return (TNFCTL_ERR_NONE);
}

/*
 * Returns the trace attributes to the client.  Since there can be
 * only one controlling agent on a target at a time, our cached information
 * is correct and we don't have to actually retrieve any information
 * from the target.
 */
tnfctl_errcode_t
tnfctl_trace_attrs_get(tnfctl_handle_t *hdl, tnfctl_trace_attrs_t *attrs)
{
	boolean_t		release_lock;
	tnfctl_errcode_t	prexstat;

	/*LINTED statement has no consequent: else*/
	LOCK_SYNC(hdl, prexstat, release_lock);

	attrs->targ_pid = hdl->targ_pid;
	attrs->trace_file_name = hdl->trace_file_name;
	attrs->trace_buf_size = hdl->trace_buf_size;
	attrs->trace_min_size = hdl->trace_min_size;
	attrs->trace_buf_state = hdl->trace_buf_state;
	attrs->trace_state = hdl->trace_state;
	attrs->filter_state = hdl->kpidfilter_state;

	/*LINTED statement has no consequent: else*/
	UNLOCK(hdl, release_lock);

	return (TNFCTL_ERR_NONE);
}


/*
 * Allocate a trace buffer of the specified name and size.
 */
tnfctl_errcode_t
tnfctl_buffer_alloc(tnfctl_handle_t *hdl, const char *trace_file_name,
			uint_t trace_file_size)
{
	tnfctl_errcode_t prexstat;

	if (hdl->mode == KERNEL_MODE) {
		/* trace_file_name is ignored in kernel mode */
		prexstat = _tnfctl_prbk_buffer_alloc(hdl, trace_file_size);
		if (prexstat)
			return (prexstat);
		return (TNFCTL_ERR_NONE);
	}

	/* Not KERNEL_MODE */
	if (hdl->trace_file_name != NULL) {
		/* buffer already allocated */
		return (TNFCTL_ERR_BUFEXISTS);
	}

	prexstat = _tnfctl_create_tracefile(hdl, trace_file_name,
	    trace_file_size);
	if (prexstat) {
		return (prexstat);
	}

	return (TNFCTL_ERR_NONE);
}

/*
 * Deallocate the trace buffer - only works for kernel mode
 */
tnfctl_errcode_t
tnfctl_buffer_dealloc(tnfctl_handle_t *hdl)
{
	tnfctl_errcode_t prexstat;

	if (hdl->mode != KERNEL_MODE)
		return (TNFCTL_ERR_BADARG);

	/* KERNEL_MODE */
	prexstat = _tnfctl_prbk_buffer_dealloc(hdl);
	if (prexstat)
		return (prexstat);
	return (TNFCTL_ERR_NONE);
}


/*
 * Helper function for attaching to a target process
 */
static tnfctl_errcode_t
attach_pid(pid_t pid, prb_proc_ctl_t **proc_pp)
{
	prb_status_t	prbstat;
	prb_proc_ctl_t	*proc_p;

	if (getpid() == pid)
		return (TNFCTL_ERR_BADARG);

	/* check if pid is valid */
	if ((kill(pid, 0) == -1) && errno == ESRCH) {
		return (TNFCTL_ERR_NOPROCESS);
	}
	/* open up /proc fd */
	prbstat = prb_proc_open(pid, proc_pp);
	if (prbstat)
		return (_tnfctl_map_to_errcode(prbstat));

	proc_p = *proc_pp;
	/*
	 * default is to run-on-last-close.  In case we cannot sync with
	 * target, we don't want to kill the target.
	 */
	prbstat = prb_proc_setrlc(proc_p, B_TRUE);
	if (prbstat)
		goto failure_ret;
	prbstat = prb_proc_setklc(proc_p, B_FALSE);
	if (prbstat)
		goto failure_ret;

	/* stop process */
	prbstat = prb_proc_stop(proc_p);
	if (prbstat)
		goto failure_ret;

	/* Sucessful return */
	return (TNFCTL_ERR_NONE);

failure_ret:
	(void) prb_proc_close(proc_p);
	return (_tnfctl_map_to_errcode(prbstat));
}

/*
 * Checks if target is at the beginning of an exec system call.  If so,
 * it runs it till the end of the exec system call.  It takes care of
 * the case where you're about to exec a setuid program.
 * CAUTION: could side effect hndl->proc_p
 */
static tnfctl_errcode_t
step_to_end_of_exec(tnfctl_handle_t *hndl)
{
	prb_proc_ctl_t	*proc_p, *oldproc_p;
	prb_status_t	prbstat, tempstat;
	int		pid;
	prb_proc_state_t	pstate;

	proc_p = hndl->proc_p;
	pid = hndl->p_getpid(proc_p);

	prbstat = prb_proc_state(proc_p, &pstate);
	if (prbstat)
		return (_tnfctl_map_to_errcode(prbstat));
	if (!(pstate.ps_issysentry && pstate.ps_syscallnum == SYS_execve)) {
		/* not stopped at beginning of exec system call */
		return (TNFCTL_ERR_NONE);
	}

	/* we are stopped at beginning of exec system call */

	prbstat = prb_proc_exit(proc_p, SYS_execve, PRB_SYS_ADD);
	if (prbstat)
		return (_tnfctl_map_to_errcode(prbstat));

	prbstat = prb_proc_cont(proc_p);
	if (prbstat)
		return (_tnfctl_map_to_errcode(prbstat));

	prbstat = prb_proc_wait(proc_p, B_FALSE, NULL);
	switch (prbstat) {
	case PRB_STATUS_OK:
		break;
	case EAGAIN:
		/*
		 * If we had exec'ed a setuid/setgid program PIOCWSTOP
		 * will return EAGAIN.  Reopen the 'fd' and try again.
		 * Read the last section of /proc man page - we reopen first
		 * and then close the old fd.
		 */
		oldproc_p = proc_p;
		tempstat = prb_proc_reopen(pid, &proc_p);
		if (tempstat) {
			/* here EACCES means exec'ed a setuid/setgid program */
			return (_tnfctl_map_to_errcode(tempstat));
		}

		prb_proc_close(oldproc_p);
		hndl->proc_p = proc_p;
		break;
	default:
		return (_tnfctl_map_to_errcode(prbstat));
	}

	prbstat = prb_proc_state(proc_p, &pstate);
	if (prbstat)
		return (_tnfctl_map_to_errcode(prbstat));

	if (!(pstate.ps_issysexit && pstate.ps_syscallnum == SYS_execve)) {
		/* unexpected condition */
		return (tnfctl_status_map(ENOENT));
	}

	/* clear old interest mask */
	prbstat = prb_proc_exit(proc_p, SYS_execve, PRB_SYS_DEL);
	if (prbstat)
		return (_tnfctl_map_to_errcode(prbstat));
	return (TNFCTL_ERR_NONE);
}


tnfctl_errcode_t
_tnfctl_external_getlock(tnfctl_handle_t *hdl)
{

	tnfctl_errcode_t	prexstat;
	prb_status_t		prbstat;
	uintptr_t		targ_symbol_ptr;
	int			internal_tracing_on;

	prexstat = _tnfctl_sym_find(hdl, TNFCTL_INTERNAL_TRACEFLAG,
	    &targ_symbol_ptr);
	if (prexstat) {
	/* no libtnfctl in target: success */
	return (TNFCTL_ERR_NONE);
	}
	prbstat = hdl->p_read(hdl->proc_p, targ_symbol_ptr,
	    &internal_tracing_on, sizeof (internal_tracing_on));

	if (prbstat) {
	prexstat = _tnfctl_map_to_errcode(prbstat);
	goto failure_ret;
	}
	if (internal_tracing_on) {
	/* target process being traced internally */
	prexstat = TNFCTL_ERR_BUSY;
	goto failure_ret;
	}
	prexstat = _tnfctl_sym_find(hdl, TNFCTL_EXTERNAL_TRACEDPID,
	    &targ_symbol_ptr);
	if (prexstat) {
	/* this shouldn't happen. we know we have libtnfctl */
	goto failure_ret;
	}
	prbstat = hdl->p_write(hdl->proc_p, targ_symbol_ptr,
	    &(hdl->targ_pid), sizeof (hdl->targ_pid));
	if (prbstat) {
	prexstat = _tnfctl_map_to_errcode(prbstat);
	goto failure_ret;
	}
	/* success */
	DBG((void) fprintf(stderr, "_tnfctl_external_getlock: ok to trace %d\n",
	    hdl->targ_pid));
	return (TNFCTL_ERR_NONE);

failure_ret:
	return (prexstat);
}