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