/* * 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" /* * Load object and probe discovery in target process. This file is * not exercised for kernel probes. */ #ifndef DEBUG #define NDEBUG 1 #endif #include #include #include #include #include #include #include #include #include #include "tnfctl_int.h" #include "kernel_int.h" #include "dbg.h" /* * Defines - Project private interfaces */ #define PROBE_SYMBOL "__tnf_probe_version_1" /* * Typedefs */ typedef struct link_args { char *la_probename; int ret_val; } link_args_t; typedef struct link_args2 { tnfctl_handle_t *la_hndl; char *la_probename; objlist_t *la_obj; ulong_t la_index; ulong_t la_base; } link_args2_t; NOTE(SCHEME_PROTECTS_DATA("always automatic", link_args link_args2)) static int per_loadobj(void *, const tnfctl_ind_obj_info_t *, void *); static objlist_t *loadobj_find(tnfctl_handle_t *, const tnfctl_ind_obj_info_t *); static tnfctl_errcode_t get_num_probes(tnfctl_handle_t *, objlist_t *, int *); static tnfctl_errcode_t read_probes_in_obj(tnfctl_handle_t *, objlist_t *, ulong_t, ulong_t); static void free_obj_fields(objlist_t *); static tnfctl_errcode_t count_probes(char *, uintptr_t, void *, tnfctl_elf_search_t *); static tnfctl_errcode_t read_a_probe(char *, uintptr_t, void *, tnfctl_elf_search_t *); static tnfctl_errcode_t link_targ_obj_probes(tnfctl_handle_t *, objlist_t *); static tnfctl_errcode_t unlink_targ_obj_probes(tnfctl_handle_t *, objlist_t *); /* * sync up our library list with that of the run time linker's * Returns an event indicating if a dlopen or dlclose happened. */ tnfctl_errcode_t _tnfctl_lmap_update(tnfctl_handle_t *hndl, boolean_t *lmap_ok, enum event_op_t *dl_evt) { int miscstat; objlist_t *cur_obj; *lmap_ok = B_TRUE; /* reset old and new of current objects */ for (cur_obj = hndl->objlist; cur_obj; cur_obj = cur_obj->next) { cur_obj->old = B_TRUE; cur_obj->new = B_FALSE; } /* read in object list */ miscstat = hndl->p_obj_iter(hndl->proc_p, per_loadobj, hndl); /* reset libs_changed global var to indicated sync up done */ _tnfctl_libs_changed = B_FALSE; if (miscstat) { /* * for INDIRECT_MODE or INTERNAL_MODE, we should never get * called when linkmaps are not consistent, so this is a real * error - return without setting lmap_ok. */ if ((hndl->mode == INDIRECT_MODE) || (hndl->mode == INTERNAL_MODE)) return (TNFCTL_ERR_INTERNAL); assert(hndl->mode == DIRECT_MODE); /* * in DIRECT_MODE: * caller needs to call tnfctl_continue on BADLMAPSTATE * XXXX - the cast from int to prb_status_t is ok as * we know we are in DIRECT_MODE and we are calling our * own loadobject iterator function. */ if ((prb_status_t) miscstat == PRB_STATUS_BADLMAPSTATE) *lmap_ok = B_FALSE; return (_tnfctl_map_to_errcode((prb_status_t) miscstat)); } /* * find out about dlopens or dlcloses - In direct mode, there * can only be one since we monitor all dl activity. The dl_evt * field is only used by tnfctl_continue(). In proc_service * mode or internal mode, the new_probe member indicates new probes * correctly. */ *dl_evt = EVT_NONE; for (cur_obj = hndl->objlist; cur_obj; cur_obj = cur_obj->next) { if (cur_obj->old == B_TRUE) { *dl_evt = EVT_CLOSE; break; } if (cur_obj->new == B_TRUE) { *dl_evt = EVT_OPEN; break; } } /* * reset new_probe field only if there was a dlopen or dlclose */ if (*dl_evt != EVT_NONE) { for (cur_obj = hndl->objlist; cur_obj; cur_obj = cur_obj->next) { cur_obj->new_probe = cur_obj->new; } } return (TNFCTL_ERR_NONE); } /* * search through all libraries and discover all probes in target * This function assumes all objects have been found and marked as * appropriate (new, old, or neither) */ tnfctl_errcode_t _tnfctl_find_all_probes(tnfctl_handle_t *hndl) { tnfctl_errcode_t prexstat; int num_probes, j; objlist_t *cur_obj, *prev_obj, *tmp_obj; boolean_t saw_new_probes = B_FALSE; prev_obj = NULL; cur_obj = hndl->objlist; while (cur_obj) { if (cur_obj->old == B_TRUE) { /* dlclosed library : stitch out probes in target */ DBG_TNF_PROBE_3(_tnfctl_find_all_probes_1, "libtnfctl", "sunw%verbosity 1; sunw%debug 'lib dlclosed'", tnf_opaque, lib_baseaddr, cur_obj->baseaddr, tnf_string, lib_name, cur_obj->objname, tnf_long, lib_fd, cur_obj->objfd); prexstat = unlink_targ_obj_probes(hndl, cur_obj); if (prexstat) return (prexstat); free_obj_fields(cur_obj); /* remove this object from linked list */ tmp_obj = cur_obj; cur_obj = cur_obj->next; if (prev_obj == NULL) hndl->objlist = cur_obj; else prev_obj->next = cur_obj; free(tmp_obj); continue; } if (cur_obj->new == B_TRUE) { /* dlopened library : read in probes */ prexstat = get_num_probes(hndl, cur_obj, &num_probes); if (prexstat) return (prexstat); if (num_probes) { saw_new_probes = B_TRUE; cur_obj->probes = malloc(num_probes * sizeof (prbctlref_t)); if (cur_obj->probes == NULL) return (TNFCTL_ERR_ALLOCFAIL); prexstat = read_probes_in_obj(hndl, cur_obj, num_probes, hndl->num_probes); if (prexstat) return (prexstat); cur_obj->min_probe_num = hndl->num_probes; /* increment num_probes */ hndl->num_probes += num_probes; cur_obj->probecnt = num_probes; prexstat = link_targ_obj_probes(hndl, cur_obj); if (prexstat) return (prexstat); } } prev_obj = cur_obj; cur_obj = cur_obj->next; } #if 0 for (cur_obj = hndl->objlist; cur_obj; cur_obj = cur_obj->next) { (void) fprintf(stderr, "%s 0x%08x %s fd=%d\n", (cur_obj->new) ? "*" : " ", cur_obj->baseaddr, cur_obj->objname, cur_obj->objfd); } #endif /* call create_func for client data if we saw new probes */ if (saw_new_probes && hndl->create_func) { for (cur_obj = hndl->objlist; cur_obj; cur_obj = cur_obj->next) { tnfctl_probe_t *probe_handle; if (cur_obj->new == B_FALSE) continue; /* new object */ for (j = 0; j < cur_obj->probecnt; j++) { probe_handle = cur_obj->probes[j].probe_handle; probe_handle->client_registered_data = hndl->create_func(hndl, probe_handle); } } } return (TNFCTL_ERR_NONE); } /* * _tnfctl_free_objs_and_probes() - cleans up objects and probes */ void _tnfctl_free_objs_and_probes(tnfctl_handle_t *hndl) { objlist_t *obj, *tmp; NOTE(NO_COMPETING_THREADS_NOW) obj = hndl->objlist; while (obj) { free_obj_fields(obj); tmp = obj; obj = obj->next; free(tmp); } hndl->objlist = NULL; NOTE(COMPETING_THREADS_NOW) } /* * Free members of objlist_t */ static void free_obj_fields(objlist_t *obj) { int i; prbctlref_t *probe_p; for (i = 0; i < obj->probecnt; i++) { probe_p = &(obj->probes[i]); if (probe_p->attr_string) free(probe_p->attr_string); if (probe_p->probe_handle) probe_p->probe_handle->valid = B_FALSE; } if (obj->probes) free(obj->probes); obj->probecnt = 0; if (obj->objname) free(obj->objname); if (obj->objfd != -1) close(obj->objfd); } /* * _tnfctl_probes_traverse() - iterate over all probes by calling the * callback function supplied. */ tnfctl_errcode_t _tnfctl_probes_traverse(tnfctl_handle_t *hndl, _tnfctl_traverse_probe_func_t func_p, void *calldata_p) { tnfctl_errcode_t prexstat; boolean_t release_lock; objlist_t *obj; int j; /*LINTED statement has no consequent: else*/ LOCK_SYNC(hndl, prexstat, release_lock); for (obj = hndl->objlist; obj; obj = obj->next) { for (j = 0; j < obj->probecnt; j++) { prexstat = (*func_p) (hndl, &(obj->probes[j]), calldata_p); if (prexstat) { /*LINTED statement has no consequent: else*/ UNLOCK(hndl, release_lock); return (prexstat); } } } /*LINTED statement has no consequent: else*/ UNLOCK(hndl, release_lock); return (TNFCTL_ERR_NONE); } /* * function that is called by loadobject iterator function for every * loadobject. If a new loadobject, add it to to our list. */ static int per_loadobj(void *proc_p, const tnfctl_ind_obj_info_t *obj, void *cd) { tnfctl_handle_t *hndl = cd; objlist_t *entry_p, *cur_p, *next_p; if (entry_p = loadobj_find(hndl, obj)) { /* loadobject already exists */ entry_p->old = B_FALSE; /* no need to close the objfd because iterator func will */ /* successful return */ return (0); } /* add new loadobject */ entry_p = calloc(1, sizeof (objlist_t)); entry_p->old = B_FALSE; entry_p->new = B_TRUE; entry_p->new_probe = B_TRUE; entry_p->objname = strdup(obj->objname); if (entry_p->objname == NULL) return (1); entry_p->baseaddr = obj->text_base; /* may have to actually open the fd */ if (obj->objfd == -1) { entry_p->objfd = open(obj->objname, O_RDONLY); if (entry_p->objfd == -1) return (1); } else { /* dup the fd because iterator function will close it */ entry_p->objfd = dup(obj->objfd); if (entry_p->objfd == -1) return (1); } entry_p->min_probe_num = 0; entry_p->probecnt = 0; entry_p->probes = NULL; entry_p->next = NULL; if (hndl->objlist == NULL) { hndl->objlist = entry_p; } else { /* add to end of list */ next_p = hndl->objlist; while (next_p) { cur_p = next_p; next_p = next_p->next; } /* cur_p now points to last element on list */ cur_p->next = entry_p; } return (0); } /* * check if this loadobject already exists in our linked list. */ static objlist_t * loadobj_find(tnfctl_handle_t *hndl, const tnfctl_ind_obj_info_t *this_obj) { objlist_t *obj; for (obj = hndl->objlist; obj; obj = obj->next) { if (obj->baseaddr == this_obj->text_base) return (obj); } return (NULL); } /* * find the number of probes in a loadobject */ static tnfctl_errcode_t get_num_probes(tnfctl_handle_t *hndl, objlist_t *obj, int *num_probes) { tnfctl_errcode_t prexstat; link_args_t largs; tnfctl_elf_search_t search_info; DBG_TNF_PROBE_0(get_num_probes_1, "libtnfctl", "sunw%verbosity 1"); largs.la_probename = PROBE_SYMBOL; largs.ret_val = 0; search_info.section_func = _tnfctl_traverse_rela; search_info.record_func = count_probes; search_info.record_data = &largs; prexstat = _tnfctl_traverse_object(obj->objfd, obj->baseaddr, &search_info); if (prexstat) return (prexstat); DBG_TNF_PROBE_2(get_num_probes_2, "libtnfctl", "sunw%verbosity 1", tnf_long, num_probes, largs.ret_val, tnf_string, obj_name, obj->objname); *num_probes = largs.ret_val; return (TNFCTL_ERR_NONE); } /* * discover all probes in a loadobject and read it into our array. */ static tnfctl_errcode_t read_probes_in_obj(tnfctl_handle_t *hndl, objlist_t *obj, ulong_t num_probes, ulong_t probe_base_num) { tnfctl_errcode_t prexstat; link_args2_t largs2; tnfctl_elf_search_t search_info; DBG_TNF_PROBE_0(read_probes_in_obj_1, "libtnfctl", "sunw%verbosity 2"); largs2.la_hndl = hndl; largs2.la_probename = PROBE_SYMBOL; largs2.la_obj = obj; largs2.la_index = 0; largs2.la_base = probe_base_num; search_info.section_func = _tnfctl_traverse_rela; search_info.record_func = read_a_probe; search_info.record_data = &largs2; prexstat = _tnfctl_traverse_object(obj->objfd, obj->baseaddr, &search_info); if (prexstat) return (prexstat); return (TNFCTL_ERR_NONE); } /* * checks if this relocation entry is a probe and if so, * increments a counter for every probe seen */ /*ARGSUSED*/ static tnfctl_errcode_t count_probes(char *name, uintptr_t addr, void *rel_entry, tnfctl_elf_search_t * search_info_p) { link_args_t *largs_p = (link_args_t *) search_info_p->record_data; if (strcmp(name, largs_p->la_probename) == 0) { largs_p->ret_val++; } return (TNFCTL_ERR_NONE); } /* * checks if this relocation entry is a probe and if so, reads in info * on this probe */ /*ARGSUSED*/ static tnfctl_errcode_t read_a_probe(char *name, uintptr_t addr, void *rel_entry, tnfctl_elf_search_t * search_info_p) { link_args2_t *largs2_p = (link_args2_t *) search_info_p->record_data; ulong_t index = largs2_p->la_index; prbctlref_t *prbctl_p; tnfctl_handle_t *hndl = largs2_p->la_hndl; tnfctl_errcode_t prexstat; int miscstat; uintptr_t attrs; assert((hndl->mode == INTERNAL_MODE) ? (MUTEX_HELD(&_tnfctl_lmap_lock)) : 1); if (strcmp(name, largs2_p->la_probename) != 0) return (TNFCTL_ERR_NONE); /* found a probe */ prbctl_p = &(largs2_p->la_obj->probes[index]); prbctl_p->addr = addr; prbctl_p->probe_id = largs2_p->la_base + index; prbctl_p->obj = largs2_p->la_obj; largs2_p->la_index++; /* read in probe structure */ miscstat = hndl->p_read(hndl->proc_p, addr, &prbctl_p->wrkprbctl, sizeof (prbctl_p->wrkprbctl)); if (miscstat) { DBG((void) fprintf(stderr, "read_a_probe: read from target failed: %d\n", miscstat)); return (TNFCTL_ERR_INTERNAL); } /* * dereference the attrs (read it into our address space only for * working copy) */ attrs = (uintptr_t) prbctl_p->wrkprbctl.attrs; prexstat = _tnfctl_readstr_targ(hndl, attrs, &prbctl_p->attr_string); if (prexstat) { DBG((void) fprintf(stderr, "read_a_probe: _tnfctl_readstr_targ (attrs) failed: %s\n", tnfctl_strerror(prexstat))); return (prexstat); } DBG_TNF_PROBE_1(read_a_probe_2, "libtnfctl", "sunw%verbosity 1; sunw%debug 'found a probe'", tnf_string, probe, prbctl_p->attr_string); /* create probe handle */ prbctl_p->probe_handle = calloc(1, sizeof (tnfctl_probe_t)); if (prbctl_p->probe_handle == NULL) return (TNFCTL_ERR_ALLOCFAIL); prbctl_p->probe_handle->valid = B_TRUE; prbctl_p->probe_handle->probe_p = prbctl_p; /* link in probe handle into chain off tnfctl_handle_t */ prbctl_p->probe_handle->next = hndl->probe_handle_list_head; hndl->probe_handle_list_head = prbctl_p->probe_handle; /* * if this is a "virgin" probe, set up probe to initial state * REMIND: Could defer this target write till we link the probes * together in target process in link_targ_obj_probes() i.e. * do the "write" only once. */ if (prbctl_p->wrkprbctl.commit_func == NULL) { prbctl_p->wrkprbctl.probe_func = (tnf_probe_func_t) hndl->endfunc; prbctl_p->wrkprbctl.commit_func = (tnf_probe_func_t) hndl->commitfunc; prbctl_p->wrkprbctl.alloc_func = (tnf_probe_alloc_func_t) hndl->allocfunc; /* * update the probe in target to its initial state * Since the probe is disabled, it is ok to write it one * write command as opposed to updating each word individually */ miscstat = hndl->p_write(hndl->proc_p, addr, &prbctl_p->wrkprbctl, sizeof (prbctl_p->wrkprbctl)); if (miscstat) return (TNFCTL_ERR_INTERNAL); } return (TNFCTL_ERR_NONE); } /* * Link all the probes in a linked list in the target image in specified * object. Also, link probes from previous object and next object into * this list. The only * reason this is needed is because internally in the process, * tnf_probe_notify() that is called from libthread walks through all * probes substituting the test function * REMIND: find a way that we don't have to walk through probes internally. */ static tnfctl_errcode_t link_targ_obj_probes(tnfctl_handle_t *hndl, objlist_t *cur) { int i; prbctlref_t *probe_p; tnf_probe_control_t *next_probe; int miscstat; objlist_t *cur_tmp, *prev_w_probes, *next_w_probes; uintptr_t next_addr; /* find previous object that has probes */ prev_w_probes = NULL; cur_tmp = hndl->objlist; while (cur_tmp != cur) { if (cur_tmp->probecnt != 0) prev_w_probes = cur_tmp; cur_tmp = cur_tmp->next; } /* find next object with probes */ next_w_probes = NULL; cur_tmp = cur->next; while (cur_tmp != NULL) { if (cur_tmp->probecnt != 0) next_w_probes = cur_tmp; cur_tmp = cur_tmp->next; } /* link probes (except for last one) in order */ for (i = 0; i < (cur->probecnt - 1); i++) { probe_p = &(cur->probes[i]); next_probe = (tnf_probe_control_t *) cur->probes[i+1].addr; probe_p->wrkprbctl.next = next_probe; miscstat = hndl->p_write(hndl->proc_p, probe_p->addr + offsetof(struct tnf_probe_control, next), &next_probe, sizeof (next_probe)); if (miscstat) return (TNFCTL_ERR_INTERNAL); } next_probe = (tnf_probe_control_t *) cur->probes[0].addr; if (prev_w_probes == NULL) { /* adding as first object in list */ next_addr = hndl->probelist_head; } else { probe_p = &(prev_w_probes->probes[prev_w_probes->probecnt - 1]); probe_p->wrkprbctl.next = next_probe; next_addr = probe_p->addr + offsetof(struct tnf_probe_control, next); } /* point next_addr to first probe in this object */ miscstat = hndl->p_write(hndl->proc_p, next_addr, &next_probe, sizeof (next_probe)); if (miscstat) return (TNFCTL_ERR_INTERNAL); /* link last probe in object */ if (next_w_probes == NULL) next_probe = NULL; else { next_probe = (tnf_probe_control_t *) next_w_probes->probes[0].addr; } probe_p = &(cur->probes[cur->probecnt - 1]); probe_p->wrkprbctl.next = next_probe; miscstat = hndl->p_write(hndl->proc_p, probe_p->addr + offsetof(struct tnf_probe_control, next), &next_probe, sizeof (next_probe)); if (miscstat) return (TNFCTL_ERR_INTERNAL); return (TNFCTL_ERR_NONE); } /* * An object has been closed. Stitch probes around this object in * target image. */ static tnfctl_errcode_t unlink_targ_obj_probes(tnfctl_handle_t *hndl, objlist_t *cur) { prbctlref_t *probe_p; tnf_probe_control_t *next_probe; int miscstat; objlist_t *cur_tmp, *prev_w_probes, *next_w_probes; uintptr_t next_addr; /* find previous object that has probes */ prev_w_probes = NULL; cur_tmp = hndl->objlist; while (cur_tmp != cur) { if (cur_tmp->probecnt != 0) prev_w_probes = cur_tmp; cur_tmp = cur_tmp->next; } /* find next object with probes */ next_w_probes = NULL; cur_tmp = cur->next; while (cur_tmp != NULL) { if (cur_tmp->probecnt != 0) next_w_probes = cur_tmp; cur_tmp = cur_tmp->next; } if (next_w_probes == NULL) next_probe = NULL; else { next_probe = (tnf_probe_control_t *) next_w_probes->probes[0].addr; } if (prev_w_probes == NULL) { /* removing first object in list */ next_addr = hndl->probelist_head; } else { probe_p = &(prev_w_probes->probes[prev_w_probes->probecnt - 1]); probe_p->wrkprbctl.next = next_probe; next_addr = probe_p->addr + offsetof(struct tnf_probe_control, next); } /* point next_addr to next_probe */ miscstat = hndl->p_write(hndl->proc_p, next_addr, &next_probe, sizeof (next_probe)); if (miscstat) return (TNFCTL_ERR_INTERNAL); return (TNFCTL_ERR_NONE); } /* * _tnfctl_flush_a_probe() - write a changed probe into the target process' * address space. */ tnfctl_errcode_t _tnfctl_flush_a_probe(tnfctl_handle_t *hndl, prbctlref_t *ref_p, size_t offset, size_t size) { tnfctl_errcode_t prexstat; int miscstat; /* * For internal control: * There is *no race* for finding the test function (between the time * we call find_test_func() and the time we assign it to a probe), * because tnfctl_internal_open() cannot be called from an init section * (look at man page of tnfctl_internal_open()). And, after the init * section of libthread has run, we will always use the MT test * function. */ if (hndl->mode == KERNEL_MODE) { prexstat = _tnfctl_prbk_flush(hndl, ref_p); if (prexstat) return (prexstat); } else { miscstat = hndl->p_write(hndl->proc_p, ref_p->addr + offset, ((char *)&(ref_p->wrkprbctl)) + offset, size); if (miscstat) return (TNFCTL_ERR_INTERNAL); } return (TNFCTL_ERR_NONE); }