diff options
| author | stevel@tonic-gate <none@none> | 2005-06-14 00:00:00 -0700 |
|---|---|---|
| committer | stevel@tonic-gate <none@none> | 2005-06-14 00:00:00 -0700 |
| commit | 7c478bd95313f5f23a4c958a745db2134aa03244 (patch) | |
| tree | c871e58545497667cbb4b0a4f2daf204743e1fe7 /usr/src/lib/libtnfctl/open.c | |
| download | illumos-joyent-7c478bd95313f5f23a4c958a745db2134aa03244.tar.gz | |
OpenSolaris Launch
Diffstat (limited to 'usr/src/lib/libtnfctl/open.c')
| -rw-r--r-- | usr/src/lib/libtnfctl/open.c | 544 |
1 files changed, 544 insertions, 0 deletions
diff --git a/usr/src/lib/libtnfctl/open.c b/usr/src/lib/libtnfctl/open.c new file mode 100644 index 0000000000..641ef808b1 --- /dev/null +++ b/usr/src/lib/libtnfctl/open.c @@ -0,0 +1,544 @@ +/* + * 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 (c) 1994, by Sun Microsytems, Inc. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +/* + * 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_exec || + 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 */ + + /* REMIND: do we have to wait on SYS_exec also ? */ + 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); +} |
