comb.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"
/*
* Functions that know how to create and decode combinations that are
* used for connecting probe functions.
*/
#ifndef DEBUG
#define NDEBUG 1
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <search.h>
#include <assert.h>
#include <sys/types.h>
#include "tnfctl_int.h"
#include "dbg.h"
/*
* Typedefs
*/
typedef struct comb_callinfo {
unsigned offset;
unsigned shift; /* shift right <n> bits */
unsigned mask;
} comb_callinfo_t;
typedef struct comb_calltmpl {
uintptr_t entry;
uintptr_t down;
uintptr_t next;
uintptr_t end;
} comb_calltmpl_t;
typedef struct comb_key {
comb_op_t op;
uintptr_t down;
uintptr_t next;
uintptr_t comb;
} comb_key_t;
typedef struct decode_key {
uintptr_t addr;
char **name_ptrs;
uintptr_t *func_addrs;
} decode_key_t;
/*
* Global - defined in assembler file
*/
extern comb_callinfo_t prb_callinfo;
extern void prb_chain_entry(void);
extern void prb_chain_down(void);
extern void prb_chain_next(void);
extern void prb_chain_end(void);
static comb_calltmpl_t calltmpl[PRB_COMB_COUNT] = {
{
(uintptr_t) prb_chain_entry,
(uintptr_t) prb_chain_down,
(uintptr_t) prb_chain_next,
(uintptr_t) prb_chain_end}
};
/*
* Declarations
*/
static tnfctl_errcode_t decode(tnfctl_handle_t *hndl, uintptr_t addr,
char ***func_names, uintptr_t **func_addrs);
static boolean_t find(tnfctl_handle_t *hndl, comb_op_t op, uintptr_t down,
uintptr_t next, uintptr_t * comb_p);
static tnfctl_errcode_t build(tnfctl_handle_t *hndl, comb_op_t op,
uintptr_t down, uintptr_t next, uintptr_t * comb_p);
static tnfctl_errcode_t add(tnfctl_handle_t *hndl, comb_op_t op, uintptr_t down,
uintptr_t next, uintptr_t comb);
static int comb_compare(const void *a, const void *b);
static int decode_compare(const void *v0p, const void *v1p);
static tnfctl_errcode_t iscomb(tnfctl_handle_t *hndl, uintptr_t addr,
uintptr_t *down_p, uintptr_t *next_p, boolean_t *ret_val);
static tnfctl_errcode_t findname(tnfctl_handle_t *hndl, uintptr_t addr,
char **ret_name);
/* ---------------------------------------------------------------- */
/* ----------------------- Public Functions ----------------------- */
/* ---------------------------------------------------------------- */
/*
* _tnfctl_comb_build() - finds (or builds) a combination satisfing the op,
* down and next constraints of the caller.
*/
tnfctl_errcode_t
_tnfctl_comb_build(tnfctl_handle_t *hndl, comb_op_t op,
uintptr_t down, uintptr_t next, uintptr_t *comb_p)
{
tnfctl_errcode_t prexstat;
*comb_p = NULL;
DBG_TNF_PROBE_0(_tnfctl_comb_build_start, "libtnfctl",
"start _tnfctl_comb_build; sunw%verbosity 1");
if (find(hndl, op, down, next, comb_p)) {
DBG_TNF_PROBE_1(_tnfctl_comb_build_end, "libtnfctl",
"end _tnfctl_comb_build; sunw%verbosity 1",
tnf_opaque, found_comb_at, *comb_p);
return (TNFCTL_ERR_NONE);
}
prexstat = build(hndl, op, down, next, comb_p);
DBG_TNF_PROBE_1(_tnfctl_comb_build_end, "libtnfctl",
"end _tnfctl_comb_build; sunw%verbosity 1",
tnf_opaque, built_comb_at, *comb_p);
return (prexstat);
}
/*
* _tnfctl_comb_decode() - returns a string describing the probe functions
* NOTE - the string is for reference purposes ONLY, it should not be freed
* by the client.
*/
tnfctl_errcode_t
_tnfctl_comb_decode(tnfctl_handle_t *hndl, uintptr_t addr, char ***func_names,
uintptr_t **func_addrs)
{
tnfctl_errcode_t prexstat;
DBG_TNF_PROBE_0(_tnfctl_comb_decode_start, "libtnfctl",
"start _tnfctl_comb_decode; sunw%verbosity 2");
prexstat = decode(hndl, addr, func_names, func_addrs);
DBG_TNF_PROBE_0(_tnfctl_comb_decode_end, "libtnfctl",
"end _tnfctl_comb_decode; sunw%verbosity 2");
return (prexstat);
}
/* ---------------------------------------------------------------- */
/* ----------------------- Private Functions ---------------------- */
/* ---------------------------------------------------------------- */
/*
* if combination has been decoded, return decoded info., else
* decode combination and cache information
*/
static tnfctl_errcode_t
decode(tnfctl_handle_t *hndl, uintptr_t addr, char ***func_names,
uintptr_t **func_addrs)
{
tnfctl_errcode_t prexstat = TNFCTL_ERR_NONE;
decode_key_t key;
decode_key_t *new_p = NULL;
decode_key_t **find_pp;
uintptr_t down;
uintptr_t next;
char *thisname = NULL;
boolean_t is_combination;
/* see if we can find the previously decoded answer */
key.addr = addr;
find_pp = (decode_key_t **) tfind(&key, &hndl->decoderoot,
decode_compare);
if (find_pp) {
DBG_TNF_PROBE_0(decode_1, "libtnfctl",
"sunw%verbosity 2; sunw%debug 'found existing'");
*func_names = (*find_pp)->name_ptrs;
*func_addrs = (*find_pp)->func_addrs;
return (TNFCTL_ERR_NONE);
}
new_p = (decode_key_t *) calloc(1, sizeof (decode_key_t));
if (!new_p)
return (TNFCTL_ERR_ALLOCFAIL);
new_p->addr = addr;
prexstat = iscomb(hndl, addr, &down, &next, &is_combination);
if (prexstat)
goto Error;
if (is_combination) {
char **nextnames;
uintptr_t *nextaddrs;
char **name_pp;
uintptr_t *addr_p;
int count, j;
DBG_TNF_PROBE_2(decode_2, "libtnfctl", "sunw%verbosity 2;",
tnf_opaque, down, down,
tnf_opaque, next, next);
prexstat = findname(hndl, down, &thisname);
if (prexstat == TNFCTL_ERR_USR1) {
/*
* should never happen - combination should not
* point at the end function
*/
prexstat = TNFCTL_ERR_INTERNAL;
goto Error;
} else if (prexstat)
goto Error;
prexstat = decode(hndl, next, &nextnames, &nextaddrs);
if (prexstat)
goto Error;
/* count number of elements - caution: empty 'for' loop */
for (count = 0; nextnames[count]; count++);
count++; /* since it was 0 based */
/* allocate one more for new function name */
new_p->name_ptrs = malloc((count + 1) *
sizeof (new_p->name_ptrs[0]));
if (new_p->name_ptrs == NULL) {
prexstat = TNFCTL_ERR_ALLOCFAIL;
goto Error;
}
new_p->func_addrs = malloc((count + 1) *
sizeof (new_p->func_addrs[0]));
if (new_p->func_addrs == NULL) {
prexstat = TNFCTL_ERR_ALLOCFAIL;
goto Error;
}
name_pp = new_p->name_ptrs;
addr_p = new_p->func_addrs;
addr_p[0] = down;
name_pp[0] = thisname;
for (j = 0; j < count; j++) {
name_pp[j + 1] = nextnames[j];
addr_p[j + 1] = nextaddrs[j];
}
} else {
prexstat = findname(hndl, addr, &thisname);
if (prexstat != TNFCTL_ERR_USR1) {
/*
* base case - end function is the only function
* that can be pointed at directly
*/
if (prexstat == TNFCTL_ERR_NONE)
prexstat = TNFCTL_ERR_NONE;
goto Error;
}
new_p->name_ptrs = malloc(sizeof (new_p->name_ptrs[0]));
if (new_p->name_ptrs == NULL) {
prexstat = TNFCTL_ERR_ALLOCFAIL;
goto Error;
}
new_p->func_addrs = malloc(sizeof (new_p->func_addrs[0]));
if (new_p->func_addrs == NULL) {
prexstat = TNFCTL_ERR_ALLOCFAIL;
goto Error;
}
new_p->name_ptrs[0] = NULL;
new_p->func_addrs[0] = NULL;
}
DBG_TNF_PROBE_1(decode_3, "libtnfctl",
"sunw%verbosity 2; sunw%debug 'decode built'",
tnf_string, func_name,
(thisname) ? (thisname) : "end_func");
find_pp = (decode_key_t **) tsearch(new_p,
&hndl->decoderoot, decode_compare);
assert(*find_pp == new_p);
*func_names = new_p->name_ptrs;
*func_addrs = new_p->func_addrs;
return (TNFCTL_ERR_NONE);
Error:
if (new_p) {
if (new_p->name_ptrs)
free(new_p->name_ptrs);
if (new_p->func_addrs)
free(new_p->func_addrs);
free(new_p);
}
return (prexstat);
}
/*
* iscomb() - determine whether the pointed to function is a combination. If
* it is, return the down and next pointers
*/
static tnfctl_errcode_t
iscomb(tnfctl_handle_t *hndl,
uintptr_t addr, uintptr_t *down_p, uintptr_t *next_p,
boolean_t *ret_val)
{
int type;
boolean_t matched = B_FALSE;
for (type = 0; type < PRB_COMB_COUNT; type++) {
size_t size;
int miscstat;
char *targ_p;
char *ptr;
char *tptr;
uintptr_t downaddr;
uintptr_t nextaddr;
int num_bits = 0;
int tmp_bits = prb_callinfo.mask;
/* allocate room to copy the target code */
size = (size_t) (calltmpl[type].end - calltmpl[type].entry);
targ_p = (char *) malloc(size);
if (!targ_p)
return (TNFCTL_ERR_ALLOCFAIL);
/* copy code from target */
miscstat = hndl->p_read(hndl->proc_p, addr, targ_p, size);
if (miscstat) {
free(targ_p);
return (TNFCTL_ERR_INTERNAL);
}
/* find the number of bits before the highest bit in mask */
while (tmp_bits > 0) {
num_bits++;
tmp_bits <<= 1;
}
/* loop over all the words */
tptr = (char *) calltmpl[type].entry;
for (ptr = targ_p; ptr < (targ_p + size); ptr++, tptr++) {
int downbits;
int nextbits;
/* LINTED pointer cast may result in improper alignment */
int *uptr = (int *) ptr;
/*
* If we are pointing at one of the words that we
* patch, * (down or next displ) then read that value
* in. * Otherwise make sure the words match.
*/
if ((uintptr_t) tptr == calltmpl[type].down +
prb_callinfo.offset) {
downbits = *uptr;
downbits &= prb_callinfo.mask;
/* sign extend */
downbits = (downbits << num_bits) >> num_bits;
downbits <<= prb_callinfo.shift;
downaddr = addr + (ptr - targ_p) + downbits;
#if defined(i386)
downaddr += 4;
/* intel is relative to *next* instruction */
#endif
ptr += 3;
tptr += 3;
} else if ((uintptr_t) tptr == calltmpl[type].next +
prb_callinfo.offset) {
nextbits = *uptr;
nextbits &= prb_callinfo.mask;
/* sign extend */
nextbits = (nextbits << num_bits) >> num_bits;
nextbits <<= prb_callinfo.shift;
nextaddr = addr + (ptr - targ_p) + nextbits;
#if defined(i386)
nextaddr += 4;
/* intel is relative to *next* instruction */
#endif
ptr += 3;
tptr += 3;
} else {
/* the byte better match or we bail */
if (*ptr != *tptr)
goto NextComb;
}
}
/* YOWSA! - its a match */
matched = B_TRUE;
NextComb:
/* free allocated memory */
if (targ_p)
free(targ_p);
if (matched) {
*down_p = downaddr;
*next_p = nextaddr;
*ret_val = B_TRUE;
return (TNFCTL_ERR_NONE);
}
}
*ret_val = B_FALSE;
return (TNFCTL_ERR_NONE);
}
#define FUNC_BUF_SIZE 32
/*
* findname() - find a name for a function given its address.
*/
static tnfctl_errcode_t
findname(tnfctl_handle_t *hndl, uintptr_t addr, char **ret_name)
{
char *symname;
tnfctl_errcode_t prexstat;
symname = NULL;
prexstat = _tnfctl_sym_findname(hndl, addr, &symname);
if ((prexstat == TNFCTL_ERR_NONE) && (symname != NULL)) {
/* found a name */
/*
* SPECIAL CASE
* If we find "tnf_trace_end" then we should not report it
* as this is the "end-cap" function and should be hidden
* from the user. Return a null string instead ...
*/
if (strcmp(symname, TRACE_END_FUNC) == 0) {
return (TNFCTL_ERR_USR1);
} else {
*ret_name = symname;
return (TNFCTL_ERR_NONE);
}
} else {
char *buffer;
buffer = malloc(FUNC_BUF_SIZE);
if (buffer == NULL)
return (TNFCTL_ERR_ALLOCFAIL);
/* no name found, use the address */
(void) sprintf(buffer, "func@0x%p", addr);
*ret_name = buffer;
return (TNFCTL_ERR_NONE);
}
}
/*
* find() - try to find an existing combination that satisfies ...
*/
static boolean_t
find(tnfctl_handle_t *hndl, comb_op_t op, uintptr_t down, uintptr_t next,
uintptr_t * comb_p)
{
comb_key_t key;
comb_key_t **find_pp;
key.op = op;
key.down = down;
key.next = next;
key.comb = NULL;
find_pp = (comb_key_t **) tfind(&key, &hndl->buildroot, comb_compare);
if (find_pp) {
*comb_p = (*find_pp)->comb;
return (B_TRUE);
} else
return (B_FALSE);
}
/*
* add() - adds a combination to combination cache
*/
static tnfctl_errcode_t
add(tnfctl_handle_t *hndl, comb_op_t op, uintptr_t down, uintptr_t next,
uintptr_t comb)
{
comb_key_t *new_p;
/* LINTED set but not used in function */
comb_key_t **ret_pp;
new_p = (comb_key_t *) malloc(sizeof (comb_key_t));
if (!new_p)
return (TNFCTL_ERR_ALLOCFAIL);
new_p->op = op;
new_p->down = down;
new_p->next = next;
new_p->comb = comb;
ret_pp = (comb_key_t **) tsearch(new_p, &hndl->buildroot,
comb_compare);
assert(*ret_pp == new_p);
return (TNFCTL_ERR_NONE);
}
/*
* decode_compare() - comparison function used for tree search for
* combinations
*/
static int
decode_compare(const void *v0p, const void *v1p)
{
decode_key_t *k0p = (decode_key_t *) v0p;
decode_key_t *k1p = (decode_key_t *) v1p;
return (int) ((uintptr_t) k1p->addr - (uintptr_t) k0p->addr);
} /* end decode_compare */
/*
* comb_compare() - comparison function used for tree search for combinations
*/
static int
comb_compare(const void *v0p, const void *v1p)
{
comb_key_t *k0p = (comb_key_t *) v0p;
comb_key_t *k1p = (comb_key_t *) v1p;
if (k0p->op != k1p->op)
return ((k0p->op < k1p->op) ? -1 : 1);
if (k0p->down != k1p->down)
return ((k0p->down < k1p->down) ? -1 : 1);
if (k0p->next != k1p->next)
return ((k0p->next < k1p->next) ? -1 : 1);
return (0);
} /* end comb_compare */
/*
* build() - build a composition
*/
static tnfctl_errcode_t
build(tnfctl_handle_t *hndl, comb_op_t op, uintptr_t down, uintptr_t next,
uintptr_t *comb_p)
{
size_t size;
uintptr_t addr;
char *buffer_p = NULL;
uintptr_t offset;
uintptr_t contents;
unsigned *word_p;
int miscstat;
tnfctl_errcode_t prexstat;
#if 0
(void) fprintf(stderr, "off=0x%x shift=0x%x mask=0x%x size=%d\n",
prb_callinfo.offset,
prb_callinfo.shift,
prb_callinfo.mask,
calltmpl[op].end - calltmpl[op].entry);
#endif
*comb_p = NULL;
size = calltmpl[op].end - calltmpl[op].entry;
/* allocate memory in the target process */
prexstat = _tnfctl_targmem_alloc(hndl, size, &addr);
if (prexstat) {
DBG((void) fprintf(stderr,
"build: trouble allocating target memory:\n"));
goto Error;
}
/* allocate a scratch buffer, copy the template into it */
buffer_p = malloc(size);
if (!buffer_p) {
DBG((void) fprintf(stderr, "build: alloc failed\n"));
prexstat = TNFCTL_ERR_ALLOCFAIL;
goto Error;
}
(void) memcpy(buffer_p, (void *) calltmpl[op].entry, size);
/* poke the down address */
offset = calltmpl[op].down - calltmpl[op].entry;
/*LINTED pointer cast may result in improper alignment*/
word_p = (unsigned *) (buffer_p + offset + prb_callinfo.offset);
contents = down - (addr + offset);
#if defined(i386)
contents -= 5; /* intel offset is relative to *next* instr */
#endif
DBG_TNF_PROBE_4(build_1, "libtnfctl", "sunw%verbosity 3",
tnf_opaque, down, down,
tnf_opaque, contents, contents,
tnf_opaque, word_p, word_p,
tnf_long, offset, offset);
*word_p &= ~prb_callinfo.mask; /* clear the relevant field */
*word_p |= ((contents >> prb_callinfo.shift) & prb_callinfo.mask);
/* poke the next address */
offset = calltmpl[op].next - calltmpl[op].entry;
/*LINTED pointer cast may result in improper alignment*/
word_p = (unsigned *) (buffer_p + offset + prb_callinfo.offset);
contents = next - (addr + offset);
#if defined(i386)
contents -= 5; /* intel offset is relative to *next* instr */
#endif
DBG_TNF_PROBE_4(build_2, "libtnfctl", "sunw%verbosity 3",
tnf_opaque, next, next,
tnf_opaque, contents, contents,
tnf_opaque, word_p, word_p,
tnf_long, offset, offset);
*word_p &= ~prb_callinfo.mask; /* clear the relevant field */
*word_p |= ((contents >> prb_callinfo.shift) & prb_callinfo.mask);
/* copy the combination template into target memory */
miscstat = hndl->p_write(hndl->proc_p, addr, buffer_p, size);
if (miscstat) {
DBG((void) fprintf(stderr,
"build: trouble writing combination: \n"));
prexstat = TNFCTL_ERR_INTERNAL;
goto Error;
}
*comb_p = addr;
prexstat = add(hndl, op, down, next, addr);
Error:
if (buffer_p)
free(buffer_p);
return (prexstat);
}