/*
* 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);
}