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