util.c revision 7c478bd95313f5f23a4c958a745db2134aa03244
/*
* 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"
/*
* Utility functions to initialize tnfctl handle, find functions that
* can be plugged into probes, find trace file information, and create
* a trace file for process tracing.
*/
#ifndef DEBUG
#define NDEBUG 1
#endif
#include "tnfctl_int.h"
#include "dbg.h"
#include <assert.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/param.h>
#include "tnf_buf.h"
/*
* Defines - Project private interfaces in libtnfprobe.so
*/
#define TRACEFILE_NAME "tnf_trace_file_name"
#define TRACEFILE_SIZE "tnf_trace_file_size"
#define TRACEFILE_MIN "tnf_trace_file_min"
#define TRACE_ERROR "_tnfw_b_control"
#define TRACE_ALLOC "tnf_trace_alloc"
#define TRACE_COMMIT "tnf_trace_commit"
#define TRACE_ROLLBACK "tnf_trace_rollback"
#define DEBUG_ENTRY "tnf_probe_debug"
#define PROBE_LIST_HEAD "__tnf_probe_list_head"
#define PROBE_LIST_VALID "__tnf_probe_list_valid"
#define NONTHREAD_TEST "tnf_non_threaded_test_addr"
#define THREAD_TEST "tnf_threaded_test_addr"
#define PROBE_THR_SYNC "__tnf_probe_thr_sync"
#define MEMSEG_PTR "__tnf_probe_memseg_p"
/* Project private interfaces in libthread.so */
#define LIBTHREAD_PRESENT "thr_probe_getfunc_addr"
/*
* Local declarations
*/
static tnfctl_errcode_t find_test_func(tnfctl_handle_t *hndl);
static tnfctl_errcode_t find_target_syms(tnfctl_handle_t *hndl);
static tnfctl_errcode_t find_trace_file_info(tnfctl_handle_t *hndl);
static tnfctl_errcode_t check_trace_error(tnfctl_handle_t *hndl);
/*
* _tnfctl_refresh_process() - search for new shared objects. If any
* found, discover probes in new shared objects.
* NOT to be called in kernel mode.
*/
tnfctl_errcode_t
_tnfctl_refresh_process(tnfctl_handle_t *hndl, boolean_t *lmap_ok,
enum event_op_t *dl_evt)
{
tnfctl_errcode_t prexstat = TNFCTL_ERR_NONE;
boolean_t release_lock;
assert(hndl->mode != KERNEL_MODE);
/*LINTED statement has no consequent: else*/
LOCK(hndl, prexstat, release_lock);
prexstat = check_trace_error(hndl);
if (prexstat)
goto finish_func;
/*
* update the link map. caller decides what to do on
* inconsistent link map
*/
prexstat = _tnfctl_lmap_update(hndl, lmap_ok, dl_evt);
if (prexstat)
goto finish_func;
/* link map is ok now */
prexstat = find_test_func(hndl);
if (prexstat)
goto finish_func;
if (*dl_evt != EVT_NONE) {
prexstat = _tnfctl_find_all_probes(hndl);
if (prexstat)
goto finish_func;
}
finish_func:
/*LINTED statement has no consequent: else*/
UNLOCK(hndl, release_lock);
return (prexstat);
}
/*
* initialize tnfctl handle for a new target
*/
tnfctl_errcode_t
_tnfctl_set_state(tnfctl_handle_t *hndl)
{
tnfctl_errcode_t prexstat = TNFCTL_ERR_NONE;
boolean_t lmap_ok;
enum event_op_t dl_evt;
boolean_t release_lock;
hndl->targ_pid = hndl->p_getpid(hndl->proc_p);
/*LINTED statement has no consequent: else*/
LOCK(hndl, prexstat, release_lock);
/*
* initialize the link map table. If link map is not ok, it is an
* error.
*/
prexstat = _tnfctl_lmap_update(hndl, &lmap_ok, &dl_evt);
if (prexstat)
goto end_func;
/* find the needed target symbols */
prexstat = find_target_syms(hndl);
if (prexstat) {
/* is libtnfprobe.so loaded in target ? */
goto end_func;
}
prexstat = find_trace_file_info(hndl);
if (prexstat)
goto end_func;
prexstat = find_test_func(hndl);
if (prexstat)
goto end_func;
prexstat = _tnfctl_find_all_probes(hndl);
if (prexstat)
goto end_func;
prexstat = check_trace_error(hndl);
/* fall into end_func */
end_func:
/*LINTED statement has no consequent: else*/
UNLOCK(hndl, release_lock);
return (prexstat);
}
/*
* find the test function for a probe. The test function could change
* with time, so we have to repeatedly check for the test function to use
*/
static tnfctl_errcode_t
find_test_func(tnfctl_handle_t *hndl)
{
long thr_sync;
int miscstat;
if (hndl->mt_target == B_FALSE) {
/* no libthread linked in */
hndl->testfunc = hndl->nonthread_test;
} else {
/*
* check whether libthread/libtnfw have synced up.
* If not yet synced up, use non-threaded test function
*/
/* assume we are going to use threaded test */
hndl->testfunc = hndl->thread_test;
miscstat = hndl->p_read(hndl->proc_p, hndl->thread_sync,
&thr_sync, sizeof (thr_sync));
if (miscstat != 0)
return (TNFCTL_ERR_INTERNAL);
/* if not yet synced up, change test func to non-threaded one */
if (thr_sync == 0) {
hndl->testfunc = hndl->nonthread_test;
}
}
/*
* Note: the testfunc in the target can change underneath us because
* in an MT program the init section of libthread changes all the
* test functions from the non-threaded one to the threaded one.
* So, every time we write out a probe, we have to make sure that
* we are using the correct test function by not trusting the test
* function in our copy of the probe. A more fool-proof solution
* which will allow other fields in the probe to change internally
* is to refresh every probe on a _tnfctl_refresh_process()
*/
return (TNFCTL_ERR_NONE);
}
/*
* check_trace_error() - checks whether there was an error in tracing
* side effects trace_buf_state and trace_state in hndl
* note: call this function only after trace_file_name is set up
* in hndl
*/
tnfctl_errcode_t
check_trace_error(tnfctl_handle_t *hndl)
{
int miscstat;
uintptr_t trace_error_ptr;
TNFW_B_CONTROL trace_error_rec;
/* read in the value of the control structure pointer */
miscstat = hndl->p_read(hndl->proc_p, hndl->trace_error,
&trace_error_ptr, sizeof (trace_error_ptr));
if (miscstat != 0)
return (TNFCTL_ERR_INTERNAL);
/* read in the value of the control structure */
miscstat = hndl->p_read(hndl->proc_p, trace_error_ptr, &trace_error_rec,
sizeof (trace_error_rec));
if (miscstat != 0)
return (TNFCTL_ERR_INTERNAL);
if (trace_error_rec.tnf_state == TNFW_B_NOBUFFER) {
/*
* massage into correct state for caller - the target might
* not have hit the first probe and hence we got "no buffer".
* So, if the user had given a file name, return BUF_OK.
*/
if (hndl->trace_file_name == NULL)
hndl->trace_buf_state = TNFCTL_BUF_NONE;
else
hndl->trace_buf_state = TNFCTL_BUF_OK;
} else if (trace_error_rec.tnf_state == TNFW_B_BROKEN) {
hndl->trace_buf_state = TNFCTL_BUF_BROKEN;
} else {
hndl->trace_buf_state = TNFCTL_BUF_OK;
}
if (TNFW_B_IS_STOPPED(trace_error_rec.tnf_state))
hndl->trace_state = B_FALSE;
else
hndl->trace_state = B_TRUE;
return (TNFCTL_ERR_NONE);
} /* end find_alloc_func */
/*
* find_target_syms() - finds needed target functions
* sideffects allocfunc, commitfunc, endfunc, rollbackfunc in hndl
*/
static tnfctl_errcode_t
find_target_syms(tnfctl_handle_t *hndl)
{
tnfctl_errcode_t prexstat;
uintptr_t temp_addr;
int miscstat;
prexstat = _tnfctl_sym_find(hndl, TRACE_ALLOC, &hndl->allocfunc);
if (prexstat)
goto end_of_func;
prexstat = _tnfctl_sym_find(hndl, TRACE_COMMIT, &hndl->commitfunc);
if (prexstat)
goto end_of_func;
prexstat = _tnfctl_sym_find(hndl, TRACE_END_FUNC, &hndl->endfunc);
if (prexstat)
goto end_of_func;
prexstat = _tnfctl_sym_find(hndl, TRACE_ROLLBACK, &hndl->rollbackfunc);
if (prexstat)
goto end_of_func;
prexstat = _tnfctl_sym_find(hndl, PROBE_LIST_HEAD,
&hndl->probelist_head);
if (prexstat)
goto end_of_func;
prexstat = _tnfctl_sym_find(hndl, TRACE_ERROR, &hndl->trace_error);
if (prexstat)
goto end_of_func;
prexstat = _tnfctl_sym_find(hndl, MEMSEG_PTR, &temp_addr);
if (prexstat)
goto end_of_func;
/* dereference to get the actual address of structure */
miscstat = hndl->p_read(hndl->proc_p, temp_addr, &hndl->memseg_p,
sizeof (hndl->memseg_p));
if (miscstat != 0)
return (TNFCTL_ERR_INTERNAL);
prexstat = _tnfctl_sym_find(hndl, PROBE_LIST_VALID,
&hndl->probelist_valid);
if (prexstat)
goto end_of_func;
prexstat = _tnfctl_sym_find(hndl, NONTHREAD_TEST, &temp_addr);
if (prexstat)
goto end_of_func;
/* dereference to get the actual function address */
miscstat = hndl->p_read(hndl->proc_p, temp_addr, &hndl->nonthread_test,
sizeof (hndl->nonthread_test));
if (miscstat != 0)
return (TNFCTL_ERR_INTERNAL);
prexstat = _tnfctl_sym_find(hndl, THREAD_TEST, &temp_addr);
if (prexstat)
goto end_of_func;
/* dereference to get the actual function address */
miscstat = hndl->p_read(hndl->proc_p, temp_addr, &hndl->thread_test,
sizeof (hndl->thread_test));
if (miscstat != 0)
return (TNFCTL_ERR_INTERNAL);
prexstat = _tnfctl_sym_find(hndl, PROBE_THR_SYNC, &hndl->thread_sync);
if (prexstat)
goto end_of_func;
prexstat = _tnfctl_sym_find(hndl, LIBTHREAD_PRESENT, &temp_addr);
if (prexstat) {
if (prexstat == TNFCTL_ERR_BADARG) {
/* no libthread linked in */
hndl->mt_target = B_FALSE;
/* this is not an error condition */
prexstat = TNFCTL_ERR_NONE;
} else {
return (prexstat);
}
} else {
hndl->mt_target = B_TRUE;
}
end_of_func:
if (prexstat == TNFCTL_ERR_BADARG)
prexstat = TNFCTL_ERR_NOLIBTNFPROBE;
return (prexstat);
}
/*
* _tnfctl_create_tracefile() - initializes tracefile, sets the tracefile name
* and size
* side effects trace_file_name and trace_buf_size in hndl
*/
#define ZBUFSZ (64 * 1024)
tnfctl_errcode_t
_tnfctl_create_tracefile(tnfctl_handle_t *hndl, const char *trace_file_name,
uint_t trace_file_size)
{
char *preexisting;
tnfctl_errcode_t prexstat;
int miscstat;
char path[MAXPATHLEN];
uintptr_t name_addr, size_addr;
uint_t outsize;
char zerobuf[ZBUFSZ];
int fd, sz, i;
/* find the neccessary symbols in the target */
prexstat = _tnfctl_sym_find(hndl, TRACEFILE_NAME, &name_addr);
if (prexstat) {
if (prexstat == TNFCTL_ERR_BADARG)
prexstat = TNFCTL_ERR_INTERNAL;
return (prexstat);
}
prexstat = _tnfctl_sym_find(hndl, TRACEFILE_SIZE, &size_addr);
if (prexstat) {
if (prexstat == TNFCTL_ERR_BADARG)
prexstat = TNFCTL_ERR_INTERNAL;
return (prexstat);
}
/* Double check that a file name doesn't already exist */
preexisting = NULL;
prexstat = _tnfctl_readstr_targ(hndl, name_addr, &preexisting);
if (prexstat) {
if (preexisting)
free(preexisting);
return (prexstat);
}
/* There better not be a file name there yet */
assert(preexisting[0] == '\0');
/* paranoia - for optimized compilation */
if (preexisting[0] != '\0')
return (TNFCTL_ERR_BUFEXISTS);
/* free memory in preexisting string */
if (preexisting)
free(preexisting);
if (trace_file_size < hndl->trace_min_size) {
return (TNFCTL_ERR_SIZETOOSMALL);
}
/* do we have an absolute, relative or no pathname specified? */
if (trace_file_name == NULL) {
return (TNFCTL_ERR_BADARG);
}
if (trace_file_name[0] == '/') {
/* absolute path to tracefile specified */
if ((strlen(trace_file_name) + 1) > (size_t) MAXPATHLEN) {
/* directory specification too long */
return (TNFCTL_ERR_BADARG);
}
(void) strcpy(path, trace_file_name);
} else {
char *cwd;
/* relative path to tracefile specified */
cwd = getcwd(NULL, MAXPATHLEN);
if (!cwd) {
return (tnfctl_status_map(errno));
}
if ((strlen(cwd) + 1 + strlen(trace_file_name) + 1) >
(size_t) MAXPATHLEN) {
/* path name too long */
return (TNFCTL_ERR_BADARG);
}
(void) sprintf(path, "%s/%s", cwd, trace_file_name);
free(cwd);
}
outsize = trace_file_size;
DBG_TNF_PROBE_2(_tnfctl_create_tracefile_1, "libtnfctl",
"sunw%verbosity 1; sunw%debug 'setting trace file name'",
tnf_string, tracefile_name, path,
tnf_long, tracefile_size, outsize);
/* unlink a previous tracefile (if one exists) */
(void) unlink(path);
/* create the new tracefile */
fd = open(path, O_CREAT | O_RDWR | O_TRUNC, 0644);
if (fd < 0) {
return (tnfctl_status_map(errno));
}
/* zero fill the file */
(void) memset(zerobuf, 0, ZBUFSZ);
sz = ZBUFSZ;
for (i = 0; i < outsize; i += sz) {
ulong_t retval;
sz = ((outsize - i) > ZBUFSZ) ? ZBUFSZ : (outsize - i);
retval = write(fd, zerobuf, sz);
if (retval != sz) {
/* trouble zeroing tracefile */
return (tnfctl_status_map(errno));
}
}
/* close the file */
(void) close(fd);
/* write the tracefile name and size into the target process */
miscstat = hndl->p_write(hndl->proc_p, name_addr, path,
strlen(path) + 1);
if (miscstat != 0)
return (TNFCTL_ERR_INTERNAL);
miscstat = hndl->p_write(hndl->proc_p, size_addr, &outsize,
sizeof (outsize));
if (miscstat != 0)
return (TNFCTL_ERR_INTERNAL);
hndl->trace_file_name = strdup(path);
if (hndl->trace_file_name == NULL)
return (TNFCTL_ERR_ALLOCFAIL);
hndl->trace_buf_size = outsize;
hndl->trace_buf_state = TNFCTL_BUF_OK;
return (TNFCTL_ERR_NONE);
} /* end _tnfctl_create_tracefile */
/*
* find_trace_file_info()
* finds out information about the trace file.
* side effects trace_buf_size, trace_min_size, trace_file_name in hndl
*/
static tnfctl_errcode_t
find_trace_file_info(tnfctl_handle_t *hndl)
{
tnfctl_errcode_t prexstat;
int miscstat;
char *preexisting;
uintptr_t name_addr, size_addr, min_addr;
uint_t outsize, minoutsize;
/* find the neccessary symbols in the target */
prexstat = _tnfctl_sym_find(hndl, TRACEFILE_NAME, &name_addr);
if (prexstat) {
if (prexstat == TNFCTL_ERR_BADARG)
prexstat = TNFCTL_ERR_INTERNAL;
return (prexstat);
}
prexstat = _tnfctl_sym_find(hndl, TRACEFILE_SIZE, &size_addr);
if (prexstat) {
if (prexstat == TNFCTL_ERR_BADARG)
prexstat = TNFCTL_ERR_INTERNAL;
return (prexstat);
}
prexstat = _tnfctl_sym_find(hndl, TRACEFILE_MIN, &min_addr);
if (prexstat) {
if (prexstat == TNFCTL_ERR_BADARG)
prexstat = TNFCTL_ERR_INTERNAL;
return (prexstat);
}
/* read file name */
preexisting = NULL;
prexstat = _tnfctl_readstr_targ(hndl, name_addr, &preexisting);
if (prexstat) {
if (preexisting)
free(preexisting);
return (prexstat);
}
/* read the minimum file size from the target */
miscstat = hndl->p_read(hndl->proc_p, min_addr, &minoutsize,
sizeof (minoutsize));
if (miscstat != 0)
return (TNFCTL_ERR_INTERNAL);
hndl->trace_min_size = minoutsize;
/* if there is no filename, we are done */
if (preexisting[0] == '\0') {
hndl->trace_file_name = NULL;
hndl->trace_buf_size = 0;
} else {
hndl->trace_file_name = preexisting;
/* read size of file */
miscstat = hndl->p_read(hndl->proc_p, size_addr,
&outsize, sizeof (outsize));
if (miscstat != 0)
return (TNFCTL_ERR_INTERNAL);
hndl->trace_buf_size = outsize;
}
return (TNFCTL_ERR_NONE);
} /* end find_trace_file_info */
/*
* wrapper functions over native /proc functions implemented by proc
* layer
*/
int
_tnfctl_read_targ(void *proc_p, uintptr_t addr, void *buf, size_t size)
{
return (prb_proc_read(proc_p, addr, buf, size));
}
int
_tnfctl_write_targ(void *proc_p, uintptr_t addr, void *buf, size_t size)
{
return (prb_proc_write(proc_p, addr, buf, size));
}
int
_tnfctl_loadobj_iter(void *proc_p, tnfctl_ind_obj_f *func, void *client_data)
{
prb_loadobj_f *same_func = (prb_loadobj_f *) func;
return (prb_loadobj_iter(proc_p, same_func, client_data));
}
pid_t
_tnfctl_pid_get(void *proc_p)
{
return (prb_proc_pid_get(proc_p));
}
/*
* _tnfctl_readstr_targ() - dereferences a string in the target
* NOTE: There is a similar routine called prb_proc_readstr()
* used by proc layer. It would be better if there was only
* one of these functions defined.
*/
#define BUFSZ 256
tnfctl_errcode_t
_tnfctl_readstr_targ(tnfctl_handle_t *hndl, uintptr_t addr, char **outstr_pp)
{
int retstat;
int bufsz = BUFSZ;
char buffer[BUFSZ + 1];
offset_t offset;
char *ptr, *orig_ptr;
*outstr_pp = NULL;
offset = 0;
/* allocate an inital return buffer */
ptr = (char *) malloc(BUFSZ);
if (!ptr) {
DBG((void) fprintf(stderr,
"_tnfctl_readstr_targ: malloc failed\n"));
return (TNFCTL_ERR_ALLOCFAIL);
}
/*LINTED constant in conditional context*/
while (1) {
int i;
/* read a chunk into our buffer */
retstat = hndl->p_read(hndl->proc_p, addr + offset, buffer,
bufsz);
if (retstat != 0) {
/*
* if we get into trouble with a large read, try again
* with a single byte. Subsequent failiure is real ...
*/
if (bufsz > 1) {
bufsz = 1;
continue;
}
DBG((void) fprintf(stderr,
"_tnfctl_readstr_targ: target read failed: \n"));
free(ptr);
return (TNFCTL_ERR_INTERNAL);
}
/* copy the chracters into the return buffer */
for (i = 0; i < bufsz; i++) {
char c = buffer[i];
ptr[offset + i] = c;
if (c == '\0') {
/* hooray! we saw the end of the string */
*outstr_pp = ptr;
return (TNFCTL_ERR_NONE);
}
}
/* bummer, need to grab another bufsz characters */
offset += bufsz;
orig_ptr = ptr;
ptr = (char *) realloc(ptr, offset + bufsz);
if (!ptr) {
free(orig_ptr);
DBG((void) fprintf(stderr,
"_tnfctl_readstr_targ: realloc failed\n"));
return (TNFCTL_ERR_ALLOCFAIL);
}
}
#if defined(lint)
return (TNFCTL_ERR_NONE);
#endif
}