Changeset View
Standalone View
cddl/contrib/opensolaris/lib/libdtrace/common/dt_sugar.c
Show All 32 Lines | |||||
* | * | ||||
* This infrastructure is designed to accommodate other syntactic sugar features | * This infrastructure is designed to accommodate other syntactic sugar features | ||||
* in the future. | * in the future. | ||||
*/ | */ | ||||
#include <sys/types.h> | #include <sys/types.h> | ||||
#include <sys/wait.h> | #include <sys/wait.h> | ||||
#include <sys/sysmacros.h> | #include <sys/sysmacros.h> | ||||
#include <sys/param.h> | |||||
#include <sys/queue.h> | |||||
#include <assert.h> | #include <assert.h> | ||||
#include <strings.h> | |||||
#include <stdlib.h> | |||||
#include <stdio.h> | |||||
#include <ctype.h> | #include <ctype.h> | ||||
#include <dwarf.h> | |||||
#include <err.h> | |||||
#include <fcntl.h> | |||||
#include <gelf.h> | |||||
#include <libdwarf.h> | |||||
#include <libelf.h> | |||||
#include <stdio.h> | |||||
#include <stdlib.h> | |||||
#include <string.h> | |||||
#include <strings.h> | |||||
#include <unistd.h> | |||||
#include <dt_module.h> | #include <dt_module.h> | ||||
#include <dt_program.h> | #include <dt_program.h> | ||||
#include <dt_provider.h> | #include <dt_provider.h> | ||||
#include <dt_printf.h> | #include <dt_printf.h> | ||||
#include <dt_pid.h> | #include <dt_pid.h> | ||||
#include <dt_grammar.h> | #include <dt_grammar.h> | ||||
#include <dt_ident.h> | #include <dt_ident.h> | ||||
#include <dt_string.h> | #include <dt_string.h> | ||||
#include <dt_impl.h> | #include <dt_impl.h> | ||||
#include <dis_tables.h> | |||||
/* kinst-related */ | |||||
struct elf_info { | |||||
Elf *elf; | |||||
struct section { | |||||
Elf_Scn *scn; | |||||
GElf_Shdr sh; | |||||
const char *name; | |||||
} *sl; | |||||
size_t shnum; | |||||
int fd; | |||||
}; | |||||
struct entry { | |||||
struct off { | |||||
const char *func; | |||||
uint64_t val; | |||||
int valid; | |||||
} *off; | |||||
int noff; | |||||
TAILQ_ENTRY(entry) next; | |||||
}; | |||||
typedef struct dt_sugar_parse { | typedef struct dt_sugar_parse { | ||||
dtrace_hdl_t *dtsp_dtp; /* dtrace handle */ | dtrace_hdl_t *dtsp_dtp; /* dtrace handle */ | ||||
dt_node_t *dtsp_pdescs; /* probe descriptions */ | dt_node_t *dtsp_pdescs; /* probe descriptions */ | ||||
int dtsp_num_conditions; /* number of condition variables */ | int dtsp_num_conditions; /* number of condition variables */ | ||||
int dtsp_num_ifs; /* number of "if" statements */ | int dtsp_num_ifs; /* number of "if" statements */ | ||||
int dtsp_kinst; /* specify if the provider is kinst */ | |||||
dt_node_t *dtsp_clause_list; /* list of clauses */ | dt_node_t *dtsp_clause_list; /* list of clauses */ | ||||
struct elf_info dtsp_elf_kern; /* ELF info of the kernel executable */ | |||||
struct elf_info dtsp_elf_dbg; /* ELF info of the kernel debug file */ | |||||
dtrace_probedesc_t *dtsp_desc; /* kinst pdesc to duplicate contents */ | |||||
Dwarf_Off dtsp_dieoff; /* DIE offset of kinst inline definition */ | |||||
int dtsp_inline; /* kinst probe function is inline */ | |||||
int dtsp_entry_or_return; /* kinst probe is entry or return */ | |||||
TAILQ_HEAD(, entry) dtsp_head; /* kinst inline copy entry TAILQ */ | |||||
} dt_sugar_parse_t; | } dt_sugar_parse_t; | ||||
enum { | |||||
F_SUBPROGRAM, | |||||
F_INLINE_COPY, | |||||
}; | |||||
static void dt_sugar_visit_stmts(dt_sugar_parse_t *, dt_node_t *, int); | static void dt_sugar_visit_stmts(dt_sugar_parse_t *, dt_node_t *, int); | ||||
/* | /* | ||||
* Return a node for "self->%error". | * Return a node for "self->%error". | ||||
* | * | ||||
* Note that the "%" is part of the variable name, and is included so that | * Note that the "%" is part of the variable name, and is included so that | ||||
* this variable name can not collide with any user-specified variable. | * this variable name can not collide with any user-specified variable. | ||||
* | * | ||||
Show All 12 Lines | |||||
* Append this clause to the clause list. | * Append this clause to the clause list. | ||||
*/ | */ | ||||
static void | static void | ||||
dt_sugar_append_clause(dt_sugar_parse_t *dp, dt_node_t *clause) | dt_sugar_append_clause(dt_sugar_parse_t *dp, dt_node_t *clause) | ||||
{ | { | ||||
dp->dtsp_clause_list = dt_node_link(dp->dtsp_clause_list, clause); | dp->dtsp_clause_list = dt_node_link(dp->dtsp_clause_list, clause); | ||||
} | } | ||||
/* | /* | ||||
markj: You should avoid adding global variables to libdtrace, all state should be accessed via the… | |||||
Done Inline ActionsSince this is dt_sugar-only code, do you think I could embed those globals christos: Since this is dt_sugar-only code, do you think I could embed those globals
in… | |||||
* Prepend this clause to the clause list. | * Prepend this clause to the clause list. | ||||
*/ | */ | ||||
static void | static void | ||||
dt_sugar_prepend_clause(dt_sugar_parse_t *dp, dt_node_t *clause) | dt_sugar_prepend_clause(dt_sugar_parse_t *dp, dt_node_t *clause) | ||||
{ | { | ||||
dp->dtsp_clause_list = dt_node_link(clause, dp->dtsp_clause_list); | dp->dtsp_clause_list = dt_node_link(clause, dp->dtsp_clause_list); | ||||
} | } | ||||
▲ Show 20 Lines • Show All 79 Lines • ▼ Show 20 Lines | |||||
{ | { | ||||
dp->dtsp_num_conditions++; | dp->dtsp_num_conditions++; | ||||
dt_sugar_append_clause(dp, dt_sugar_new_condition_impl(dp, | dt_sugar_append_clause(dp, dt_sugar_new_condition_impl(dp, | ||||
pred, condid, dp->dtsp_num_conditions)); | pred, condid, dp->dtsp_num_conditions)); | ||||
return (dp->dtsp_num_conditions); | return (dp->dtsp_num_conditions); | ||||
} | } | ||||
/* | /* | ||||
* Visit the specified node and all of its descendants. Currently this is only | * kinst-related | ||||
* used to count the number of "if" statements (dtsp_num_ifs). | |||||
*/ | */ | ||||
static void | static void | ||||
dt_sugar_elf_init(dtrace_hdl_t *dtp, struct elf_info *ei, const char *file) | |||||
Done Inline ActionsAny reason not to use dt_alloc()? markj: Any reason not to use `dt_alloc()`? | |||||
{ | |||||
Elf_Scn *scn; | |||||
GElf_Shdr sh; | |||||
struct section *s; | |||||
const char *name; | |||||
size_t shstrndx, ndx; | |||||
if (elf_version(EV_CURRENT) == EV_NONE) | |||||
errx(1, "dt_sugar: elf_version(): %s", elf_errmsg(-1)); | |||||
if ((ei->fd = open(file, O_RDONLY)) < 0) | |||||
err(1, "dt_sugar: open(%s)", file); | |||||
if ((ei->elf = elf_begin(ei->fd, ELF_C_READ, NULL)) == NULL) | |||||
errx(1, "dt_sugar: elf_begin(): %s", elf_errmsg(-1)); | |||||
if (elf_kind(ei->elf) == ELF_K_NONE) | |||||
errx(1, "dt_sugar: not an ELF file: %s", file); | |||||
/* Load ELF sections */ | |||||
if (!elf_getshnum(ei->elf, &ei->shnum)) | |||||
errx(1, "dt_sugar: elf_getshnum(): %s", elf_errmsg(-1)); | |||||
ei->sl = dt_alloc(dtp, ei->shnum * sizeof(struct section)); | |||||
if (ei->sl == NULL) | |||||
err(1, "dt_sugar: dt_alloc()"); | |||||
if (!elf_getshstrndx(ei->elf, &shstrndx)) | |||||
errx(1, "dt_sugar: elf_getshstrndx(): %s", elf_errmsg(-1)); | |||||
if ((scn = elf_getscn(ei->elf, 0)) == NULL) | |||||
errx(1, "dt_sugar: elf_getscn(): %s", elf_errmsg(-1)); | |||||
(void) elf_errno(); | |||||
do { | |||||
if (gelf_getshdr(scn, &sh) == NULL) { | |||||
warnx("dt_sugar: gelf_getshdr(): %s", elf_errmsg(-1)); | |||||
(void) elf_errno(); | |||||
continue; | |||||
} | |||||
if ((name = elf_strptr(ei->elf, shstrndx, sh.sh_name)) == NULL) | |||||
(void) elf_errno(); | |||||
if ((ndx = elf_ndxscn(scn)) == SHN_UNDEF && elf_errno() != 0) { | |||||
warnx("dt_sugar: elf_ndxscn(): %s", elf_errmsg(-1)); | |||||
continue; | |||||
} | |||||
if (ndx >= ei->shnum) | |||||
continue; | |||||
s = &ei->sl[ndx]; | |||||
s->scn = scn; | |||||
s->sh = sh; | |||||
s->name = name; | |||||
} while ((scn = elf_nextscn(ei->elf, scn)) != NULL); | |||||
if (elf_errno() != 0) | |||||
warnx("dt_sugar: elf_nextscn(): %s", elf_errmsg(-1)); | |||||
} | |||||
static void | |||||
dt_sugar_elf_deinit(dtrace_hdl_t *dtp, struct elf_info *ei) | |||||
{ | |||||
dt_free(dtp, ei->sl); | |||||
close(ei->fd); | |||||
elf_end(ei->elf); | |||||
} | |||||
static int | |||||
Done Inline ActionsInstead of duplicating all of these fields, why not embed the GElf_Shdr directly into the section structure? markj: Instead of duplicating all of these fields, why not embed the GElf_Shdr directly into the… | |||||
dt_sugar_dis_get_byte(void *p) | |||||
{ | |||||
int ret; | |||||
uint8_t **instr = p; | |||||
ret = **instr; | |||||
(*instr)++; | |||||
return (ret); | |||||
} | |||||
/* | |||||
* Find the caller function and offset of an inline copy. Since we know the | |||||
* inline copy's boundaries (`addr_lo` and `addr_hi` arguments), the caller | |||||
* function is going to be the ELF symbol that the inline copy's boundaries are | |||||
* inside of. | |||||
*/ | |||||
static void | |||||
dt_sugar_kinst_find_caller_func(dt_sugar_parse_t *dp, struct off *off, | |||||
uint64_t addr_lo, uint64_t addr_hi) | |||||
{ | |||||
Elf_Data *d; | |||||
GElf_Sym sym; | |||||
struct section *s; | |||||
dis86_t d86; | |||||
uint8_t *buf; | |||||
uint64_t addr, lo, hi; | |||||
uint32_t stab; | |||||
int len, i, j; | |||||
/* Find the caller function's boundaries and name. */ | |||||
off->func = NULL; | |||||
for (i = 1; i < dp->dtsp_elf_kern.shnum; i++) { | |||||
s = &dp->dtsp_elf_kern.sl[i]; | |||||
if (s->sh.sh_type != SHT_SYMTAB && s->sh.sh_type != SHT_DYNSYM) | |||||
continue; | |||||
if (s->sh.sh_link >= dp->dtsp_elf_kern.shnum) | |||||
continue; | |||||
stab = s->sh.sh_link; | |||||
(void) elf_errno(); | |||||
if ((d = elf_getdata(s->scn, NULL)) == NULL) { | |||||
if (elf_errno() != 0) | |||||
warnx("dt_sugar: elf_getdata(): %s", | |||||
elf_errmsg(-1)); | |||||
continue; | |||||
} | |||||
if (d->d_size <= 0) | |||||
continue; | |||||
if (s->sh.sh_entsize == 0) | |||||
continue; | |||||
else if (s->sh.sh_size / s->sh.sh_entsize > INT_MAX) | |||||
continue; | |||||
len = (int)(s->sh.sh_size / s->sh.sh_entsize); | |||||
for (j = 0; j < len; j++) { | |||||
if (gelf_getsym(d, j, &sym) != &sym) { | |||||
warnx("dt_sugar: gelf_getsym(): %s", | |||||
elf_errmsg(-1)); | |||||
continue; | |||||
} | |||||
lo = sym.st_value; | |||||
hi = sym.st_value + sym.st_size; | |||||
if (addr_lo < lo || addr_hi > hi) | |||||
continue; | |||||
if ((off->func = elf_strptr(dp->dtsp_elf_kern.elf, stab, | |||||
sym.st_name)) != NULL) | |||||
break; | |||||
} | |||||
} | |||||
/* Find inline copy's return offset. */ | |||||
for (i = 1; i < dp->dtsp_elf_kern.shnum; i++) { | |||||
s = &dp->dtsp_elf_kern.sl[i]; | |||||
if (strcmp(s->name, ".text") != 0 || | |||||
Done Inline ActionsWhat's the purpose of the else-if? markj: What's the purpose of the else-if? | |||||
Done Inline ActionsNo real use other than to make it obvious that I'm handling both entry christos: No real use other than to make it obvious that I'm handling both `entry`
and `return` probes. | |||||
s->sh.sh_type != SHT_PROGBITS) | |||||
continue; | |||||
(void) elf_errno(); | |||||
if ((d = elf_getdata(s->scn, NULL)) == NULL) { | |||||
if (elf_errno() != 0) | |||||
warnx("dt_sugar: elf_getdata(): %s", | |||||
elf_errmsg(-1)); | |||||
continue; | |||||
} | |||||
if (d->d_size <= 0 || d->d_buf == NULL) | |||||
continue; | |||||
buf = d->d_buf; | |||||
addr = s->sh.sh_addr + d->d_off; | |||||
/* | |||||
* Compiling without `-mno-omit-leaf-frame-pointer` might | |||||
* result in, as the name suggests, leaf functions omitting | |||||
* `push %rbp`. kinst ignores any function that doesn't start | |||||
* with this instruction, so in order to avoid having dtrace(1) | |||||
* exit because one of the probes we're creating is a leaf | |||||
* function with its 'push %rbp' ommitted, we're catching this | |||||
* error before we get to kinst. | |||||
*/ | |||||
while (addr != lo) { | |||||
addr++; | |||||
buf++; | |||||
} | |||||
if (*buf != 0x55) { | |||||
warnx("dt_sugar: ignoring '%s': function does not " | |||||
"begin with 'push %%rbp'", off->func); | |||||
off->valid = 0; | |||||
return; | |||||
} | |||||
off->valid = 1; | |||||
/* | |||||
* Get to the inline copy's start manually to avoid potential | |||||
* dtrace_disx86() failures. | |||||
*/ | |||||
while (addr != addr_lo) { | |||||
addr++; | |||||
buf++; | |||||
} | |||||
if (strcmp(dp->dtsp_desc->dtpd_name, "entry") == 0) { | |||||
off->val = addr - lo; | |||||
break; | |||||
} else if (strcmp(dp->dtsp_desc->dtpd_name, "return") == 0) | |||||
; /* nothing */ | |||||
d86.d86_data = &buf; | |||||
d86.d86_get_byte = dt_sugar_dis_get_byte; | |||||
d86.d86_check_func = NULL; | |||||
/* Get to the inline copy's end. */ | |||||
while (addr != addr_hi) { | |||||
/* | |||||
* XXX We might have to add #ifdefs when we port kinst | |||||
* to other architectures. | |||||
*/ | |||||
if (dtrace_disx86(&d86, SIZE64) != 0) { | |||||
warnx("dt_sugar: dtrace_disx86() failed"); | |||||
return; | |||||
} | |||||
addr += d86.d86_len; | |||||
} | |||||
/* | |||||
* DWARF considers addr_hi to be the instruction | |||||
* *after* the inline copy's end, so we want to go back | |||||
* one instruction. | |||||
*/ | |||||
addr -= d86.d86_len; | |||||
off->val = addr - lo; | |||||
break; | |||||
} | |||||
} | |||||
/* | |||||
* Parse DWARF info recursively and create a TAILQ of entries that correspond | |||||
* to inline copies of the probe function. | |||||
*/ | |||||
static void | |||||
dt_sugar_kinst_parse_die(dt_sugar_parse_t *dp, Dwarf_Debug dbg, Dwarf_Die die, | |||||
int level, int flag) | |||||
{ | |||||
static Dwarf_Die die_root; | |||||
Dwarf_Die die_next; | |||||
Dwarf_Ranges *ranges, *rp; | |||||
Dwarf_Attribute attp; | |||||
Dwarf_Addr base0, lowpc, highpc; | |||||
Dwarf_Off dieoff, cuoff, culen, v_off; | |||||
Dwarf_Unsigned nbytes, v_udata; | |||||
Dwarf_Signed nranges; | |||||
Dwarf_Half attr, tag; | |||||
Dwarf_Bool v_flag; | |||||
Dwarf_Error error; | |||||
struct entry *e; | |||||
struct off *off; | |||||
char *v_str; | |||||
int res, noff, i, found = 0; | |||||
if (level == 0) | |||||
die_root = die; | |||||
if (dwarf_dieoffset(die, &dieoff, &error) != DW_DLV_OK) { | |||||
warnx("dt_sugar: %s", dwarf_errmsg(error)); | |||||
goto cont; | |||||
} | |||||
if (dwarf_die_CU_offset_range(die, &cuoff, &culen, &error) != DW_DLV_OK) { | |||||
warnx("dt_sugar: %s", dwarf_errmsg(error)); | |||||
cuoff = 0; | |||||
} | |||||
if (dwarf_tag(die, &tag, &error) != DW_DLV_OK) { | |||||
warnx("dt_sugar: %s", dwarf_errmsg(error)); | |||||
goto cont; | |||||
} | |||||
if (tag != DW_TAG_subprogram && tag != DW_TAG_inlined_subroutine) | |||||
goto cont; | |||||
if (flag == F_SUBPROGRAM && tag == DW_TAG_subprogram) { | |||||
if (dwarf_hasattr(die, DW_AT_inline, &v_flag, &error) != | |||||
DW_DLV_OK) { | |||||
warnx("dt_sugar: %s", dwarf_errmsg(error)); | |||||
goto cont; | |||||
} | |||||
if (!v_flag) | |||||
goto cont; | |||||
res = dwarf_diename(die, &v_str, &error); | |||||
if (res != DW_DLV_OK) { | |||||
warnx("dt_sugar: %s", dwarf_errmsg(error)); | |||||
goto cont; | |||||
} | |||||
if (strcmp(v_str, dp->dtsp_desc->dtpd_func) != 0) | |||||
goto cont; | |||||
/* | |||||
* The function name we're searching for has an inline | |||||
* definition. | |||||
*/ | |||||
found = 1; | |||||
goto cont; | |||||
} else if (flag == F_INLINE_COPY && tag == DW_TAG_inlined_subroutine) { | |||||
res = dwarf_attr(die, DW_AT_abstract_origin, &attp, &error); | |||||
if (res != DW_DLV_OK) { | |||||
if (res == DW_DLV_ERROR) | |||||
warnx("dt_sugar: %s", dwarf_errmsg(error)); | |||||
goto cont; | |||||
} | |||||
if (dwarf_formref(attp, &v_off, &error) != DW_DLV_OK) { | |||||
warnx("dt_sugar: %s", dwarf_errmsg(error)); | |||||
goto cont; | |||||
} | |||||
v_off += cuoff; | |||||
/* Doesn't point to the definition's DIE offset. */ | |||||
if (v_off != dp->dtsp_dieoff) | |||||
goto cont; | |||||
if (dwarf_hasattr(die, DW_AT_ranges, &v_flag, &error) != | |||||
DW_DLV_OK) { | |||||
warnx("dt_sugar: %s", dwarf_errmsg(error)); | |||||
goto cont; | |||||
} | |||||
if (v_flag) { | |||||
/* DIE has ranges */ | |||||
res = dwarf_attr(die, DW_AT_ranges, &attp, &error); | |||||
if (res != DW_DLV_OK) { | |||||
if (res == DW_DLV_ERROR) | |||||
warnx("dt_sugar: %s", | |||||
dwarf_errmsg(error)); | |||||
goto cont; | |||||
} | |||||
if (dwarf_global_formref(attp, &v_off, &error) != | |||||
DW_DLV_OK) { | |||||
warnx("dt_sugar: %s", dwarf_errmsg(error)); | |||||
goto cont; | |||||
} | |||||
if (dwarf_get_ranges(dbg, v_off, &ranges, &nranges, | |||||
&nbytes, &error) != DW_DLV_OK) { | |||||
warnx("dt_sugar: %s", dwarf_errmsg(error)); | |||||
goto cont; | |||||
} | |||||
res = dwarf_lowpc(die_root, &lowpc, &error); | |||||
if (res != DW_DLV_OK) { | |||||
warnx("dt_sugar: %s", dwarf_errmsg(error)); | |||||
goto cont; | |||||
} | |||||
base0 = lowpc; | |||||
if (strcmp(dp->dtsp_desc->dtpd_name, "entry") == 0) { | |||||
/* | |||||
* Trace the first instruction of the first | |||||
* range since this is the beginning of the | |||||
* inline copy. | |||||
*/ | |||||
noff = 1; | |||||
} else if (strcmp(dp->dtsp_desc->dtpd_name, "return") == 0) { | |||||
/* | |||||
* Trace the last instruction of every range in | |||||
* case the inline copy is split into multiple | |||||
* ranges (e.g if it has early `return`s). | |||||
*/ | |||||
noff = nranges - 1; | |||||
} | |||||
off = dt_alloc(dp->dtsp_dtp, noff * sizeof(struct off)); | |||||
if (off == NULL) | |||||
err(1, "dt_sugar: dt_alloc()"); | |||||
for (i = 0; i < noff; i++) { | |||||
rp = &ranges[i]; | |||||
if (rp->dwr_type == DW_RANGES_ADDRESS_SELECTION) | |||||
base0 = rp->dwr_addr2; | |||||
dt_sugar_kinst_find_caller_func(dp, &off[i], | |||||
rp->dwr_addr1 + base0, | |||||
rp->dwr_addr2 + base0); | |||||
} | |||||
dwarf_ranges_dealloc(dbg, ranges, nranges); | |||||
} else { | |||||
/* DIE has high/low PC boundaries */ | |||||
res = dwarf_lowpc(die, &lowpc, &error); | |||||
if (res != DW_DLV_OK) { | |||||
warnx("dt_sugar: %s", dwarf_errmsg(error)); | |||||
goto cont; | |||||
} | |||||
res = dwarf_highpc(die, &highpc, &error); | |||||
if (res != DW_DLV_OK) { | |||||
warnx("dt_sugar: %s", dwarf_errmsg(error)); | |||||
goto cont; | |||||
} | |||||
noff = 1; | |||||
off = dt_alloc(dp->dtsp_dtp, noff * sizeof(struct off)); | |||||
if (off == NULL) | |||||
err(1, "dt_sugar: dt_alloc()"); | |||||
dt_sugar_kinst_find_caller_func(dp, off, lowpc, | |||||
lowpc + highpc); | |||||
} | |||||
} else | |||||
goto cont; | |||||
e = dt_alloc(dp->dtsp_dtp, sizeof(struct entry)); | |||||
if (e == NULL) | |||||
err(1, "dt_sugar: dt_alloc()"); | |||||
e->noff = noff; | |||||
e->off = off; | |||||
TAILQ_INSERT_TAIL(&dp->dtsp_head, e, next); | |||||
cont: | |||||
/* | |||||
* Inline copies might appear before the declaration, so we need to | |||||
* re-parse the CU. | |||||
* | |||||
* The rationale for choosing to re-parse the CU instead of using a | |||||
* hash table of DIEs is that, because we re-parse only when an inline | |||||
* definition of the function we want is found, statistically, we won't | |||||
* have to re-parse many times at all considering that only a handful | |||||
* of CUs will define the same function, whereas if we have used a hash | |||||
* table, we would first need to parse the whole CU at once and store | |||||
* all DW_TAG_inlined_subroutine DIEs (so that we can match them | |||||
* afterwards). In this case, we always have to "parse" twice -- first | |||||
* the CU, then the DIE table -- and also, the program would use much | |||||
* more memory since we would have allocated DIEs, which most of them | |||||
* would never be used. | |||||
*/ | |||||
if (found) { | |||||
die = die_root; | |||||
level = 0; | |||||
/* | |||||
* We'll be checking against the DIE offset of the definition | |||||
* to determine if the inline copy's DW_AT_abstract_origin | |||||
* points to it. | |||||
*/ | |||||
dp->dtsp_dieoff = dieoff; | |||||
dp->dtsp_inline = 1; | |||||
flag = F_INLINE_COPY; | |||||
} | |||||
res = dwarf_child(die, &die_next, &error); | |||||
if (res == DW_DLV_ERROR) | |||||
warnx("dt_sugar: %s", dwarf_errmsg(error)); | |||||
else if (res == DW_DLV_OK) | |||||
dt_sugar_kinst_parse_die(dp, dbg, die_next, level + 1, flag); | |||||
res = dwarf_siblingof(dbg, die, &die_next, &error); | |||||
if (res == DW_DLV_ERROR) | |||||
warnx("dt_sugar: %s", dwarf_errmsg(error)); | |||||
else if (res == DW_DLV_OK) | |||||
dt_sugar_kinst_parse_die(dp, dbg, die_next, level, flag); | |||||
/* | |||||
* Deallocating on level 0 will attempt to double-free, since die_root | |||||
* points to the first DIE. We'll deallocate the root DIE in main(). | |||||
*/ | |||||
if (level > 0) | |||||
dwarf_dealloc(dbg, die, DW_DLA_DIE); | |||||
} | |||||
/* | |||||
* Append new clauses for each inline copy to the parse tree. | |||||
* | |||||
* If foo() is an inline function, and is called from functions bar() and baz() | |||||
* at offsets 10 and 20 respectively, we'll transform the parse tree from: | |||||
* | |||||
* kinst::foo:<entry/return> | |||||
* /pred/ | |||||
* { | |||||
* acts | |||||
* } | |||||
* | |||||
* To: | |||||
* | |||||
* kinst::bar:10, | |||||
* kinst::baz:20 | |||||
* /pred/ | |||||
* { | |||||
* acts | |||||
* } | |||||
*/ | |||||
static void | |||||
dt_sugar_kinst_create_probes(dt_sugar_parse_t *dp, dt_node_t *dnp) | |||||
{ | |||||
dt_node_t *pdesc, *p; | |||||
struct entry *e; | |||||
char buf[DTRACE_FULLNAMELEN]; | |||||
int i, j = 0; | |||||
p = dp->dtsp_clause_list->dn_pdescs; | |||||
/* Clean up as well */ | |||||
while (!TAILQ_EMPTY(&dp->dtsp_head)) { | |||||
e = TAILQ_FIRST(&dp->dtsp_head); | |||||
TAILQ_REMOVE(&dp->dtsp_head, e, next); | |||||
for (i = 0; i < e->noff; i++) { | |||||
if (!e->off[i].valid) | |||||
continue; | |||||
if (j++ == 0) { | |||||
/* | |||||
* Since we're trying to trace inline copies of | |||||
* a given function by requesting a probe of | |||||
* the form | |||||
* `kinst::<inline_func_name>:<entry/return>`, | |||||
* the requested probe, by definition cannot be | |||||
* traced, and as a result DTrace will exit | |||||
* with an error because it cannot create a | |||||
* probe for this function. In order to get | |||||
* around this, we're overriding the requested | |||||
* probe's <function> and <offset> fields with | |||||
* the very first inline copy's information. | |||||
*/ | |||||
snprintf(buf, sizeof(buf), "%lu", e->off[i].val); | |||||
strlcpy(dp->dtsp_desc->dtpd_func, e->off[i].func, | |||||
sizeof(dp->dtsp_desc->dtpd_func)); | |||||
strlcpy(dp->dtsp_desc->dtpd_name, buf, | |||||
sizeof(dp->dtsp_desc->dtpd_name)); | |||||
} else { | |||||
/* | |||||
* Append the probe description of each inline | |||||
* copy to main clause. | |||||
*/ | |||||
snprintf(buf, sizeof(buf), "%s:%s:%s:%lu", | |||||
dp->dtsp_desc->dtpd_provider, | |||||
dp->dtsp_desc->dtpd_mod, | |||||
e->off[i].func, e->off[i].val); | |||||
pdesc = dt_node_pdesc_by_name(strdup(buf)); | |||||
p = dt_node_link(p, pdesc); | |||||
} | |||||
} | |||||
dt_free(dp->dtsp_dtp, e->off); | |||||
dt_free(dp->dtsp_dtp, e); | |||||
} | |||||
} | |||||
/* | |||||
* Initialize libelf and libdwarf and parse kernel.debug's DWARF info. | |||||
*/ | |||||
static void | |||||
dt_sugar_do_kinst_inline(dt_sugar_parse_t *dp, dt_node_t *dnp) | |||||
{ | |||||
Done Inline ActionsThis should use the kern.bootfile sysctl instead of hard-coding the path. Then we have three cases: debug info is embedded in the kernel executable itself (should be rare), kernel.debug is in the same directory as the kernel, or kernel.debug is in /usr/lib/debug/$(dirname $(sysctl -n kern.bootfile)). It might make sense to keep the bootfile path in the dtrace_hdl_t since there's some other code in libdtrace that uses it. (Since the current code should work fine, you could postpone fixing this until later, just be sure to add a TODO comment or so.) markj: This should use the `kern.bootfile` sysctl instead of hard-coding the path. Then we have three… | |||||
Dwarf_Debug dbg; | |||||
Dwarf_Die die; | |||||
Dwarf_Error error; | |||||
char dbgfile[MAXPATHLEN]; | |||||
int res = DW_DLV_OK; | |||||
/* We only make entry and return probes for inline functions. */ | |||||
if (strcmp(dp->dtsp_desc->dtpd_name, "entry") != 0 && | |||||
strcmp(dp->dtsp_desc->dtpd_name, "return") != 0) | |||||
return; | |||||
(void) snprintf(dbgfile, sizeof(dbgfile), "/usr/lib/debug/%s.debug", | |||||
dp->dtsp_dtp->bootfile); | |||||
dt_sugar_elf_init(dp->dtsp_dtp, &dp->dtsp_elf_kern, | |||||
dp->dtsp_dtp->bootfile); | |||||
dt_sugar_elf_init(dp->dtsp_dtp, &dp->dtsp_elf_dbg, dbgfile); | |||||
if (dwarf_elf_init(dp->dtsp_elf_dbg.elf, DW_DLC_READ, NULL, NULL, &dbg, | |||||
&error) != DW_DLV_OK) | |||||
errx(1, "dt_sugar: dwarf_elf_init(): %s", dwarf_errmsg(error)); | |||||
dp->dtsp_entry_or_return = 1; | |||||
TAILQ_INIT(&dp->dtsp_head); | |||||
/* | |||||
* Parse DWARF info for kernel.debug and create entries for the inline | |||||
* copies we'll create probes for. | |||||
*/ | |||||
while ((res = dwarf_next_cu_header(dbg, NULL, NULL, NULL, NULL, NULL, | |||||
&error)) == DW_DLV_OK) { | |||||
die = NULL; | |||||
while (dwarf_siblingof(dbg, die, &die, &error) == DW_DLV_OK) | |||||
dt_sugar_kinst_parse_die(dp, dbg, die, 0, F_SUBPROGRAM); | |||||
dwarf_dealloc(dbg, die, DW_DLA_DIE); | |||||
} | |||||
if (res == DW_DLV_ERROR) | |||||
warnx("dt_sugar: %s", dwarf_errmsg(error)); | |||||
dt_sugar_elf_deinit(dp->dtsp_dtp, &dp->dtsp_elf_kern); | |||||
dt_sugar_elf_deinit(dp->dtsp_dtp, &dp->dtsp_elf_dbg); | |||||
dwarf_finish(dbg, &error); | |||||
} | |||||
/* | |||||
* Visit the specified node and all of its descendants. | |||||
*/ | |||||
static void | |||||
dt_sugar_visit_all(dt_sugar_parse_t *dp, dt_node_t *dnp) | dt_sugar_visit_all(dt_sugar_parse_t *dp, dt_node_t *dnp) | ||||
{ | { | ||||
dt_node_t *arg; | dt_node_t *arg; | ||||
switch (dnp->dn_kind) { | switch (dnp->dn_kind) { | ||||
case DT_NODE_FREE: | case DT_NODE_FREE: | ||||
case DT_NODE_INT: | case DT_NODE_INT: | ||||
case DT_NODE_STRING: | case DT_NODE_STRING: | ||||
case DT_NODE_SYM: | case DT_NODE_SYM: | ||||
case DT_NODE_TYPE: | case DT_NODE_TYPE: | ||||
case DT_NODE_PROBE: | case DT_NODE_PROBE: | ||||
break; | |||||
case DT_NODE_PDESC: | case DT_NODE_PDESC: | ||||
if (strcmp(dnp->dn_desc->dtpd_provider, "kinst") != 0) | |||||
Done Inline ActionsDon't we want to check the probe name as well? This code shouldn't modify regular kinst::<func>:<offset> probes. markj: Don't we want to check the probe name as well? This code shouldn't modify regular kinst::<func>… | |||||
Done Inline ActionsThis doesn't affect kinst::<func>:<offset> probes at all. The christos: This doesn't affect `kinst::<func>:<offset>` probes at all. The
flag is tested in… | |||||
break; | |||||
dp->dtsp_kinst = 1; | |||||
dp->dtsp_inline = 0; | |||||
dp->dtsp_entry_or_return = 0; | |||||
dp->dtsp_desc = dnp->dn_desc; | |||||
dt_sugar_do_kinst_inline(dp, dnp); | |||||
break; | |||||
case DT_NODE_IDENT: | case DT_NODE_IDENT: | ||||
break; | break; | ||||
case DT_NODE_FUNC: | case DT_NODE_FUNC: | ||||
for (arg = dnp->dn_args; arg != NULL; arg = arg->dn_list) | for (arg = dnp->dn_args; arg != NULL; arg = arg->dn_list) | ||||
dt_sugar_visit_all(dp, arg); | dt_sugar_visit_all(dp, arg); | ||||
break; | break; | ||||
▲ Show 20 Lines • Show All 289 Lines • ▼ Show 20 Lines | if (dp.dtsp_num_ifs == 0 && dp.dtsp_num_conditions == 0) { | ||||
} | } | ||||
} | } | ||||
if (dp.dtsp_num_conditions != 0) { | if (dp.dtsp_num_conditions != 0) { | ||||
dt_sugar_prepend_clause(&dp, | dt_sugar_prepend_clause(&dp, | ||||
dt_sugar_new_clearerror_clause(&dp)); | dt_sugar_new_clearerror_clause(&dp)); | ||||
} | } | ||||
if (dp.dtsp_kinst) { | |||||
if (dp.dtsp_inline) | |||||
dt_sugar_kinst_create_probes(&dp, clause); | |||||
else if (!dp.dtsp_inline && dp.dtsp_entry_or_return) { | |||||
/* | |||||
* Delegate non-inline function probes to FBT so that | |||||
* we don't duplicate FBT code in kinst. | |||||
*/ | |||||
strlcpy(dp.dtsp_desc->dtpd_provider, "fbt", | |||||
sizeof(dp.dtsp_desc->dtpd_provider)); | |||||
} | |||||
/* Regular kinst probes are not affected. */ | |||||
} | |||||
if (dp.dtsp_clause_list != NULL && | if (dp.dtsp_clause_list != NULL && | ||||
dp.dtsp_clause_list->dn_list != NULL && !dtp->dt_has_sugar) { | dp.dtsp_clause_list->dn_list != NULL && !dtp->dt_has_sugar) { | ||||
dtp->dt_has_sugar = B_TRUE; | dtp->dt_has_sugar = B_TRUE; | ||||
dt_sugar_prepend_clause(&dp, dt_sugar_makeerrorclause()); | dt_sugar_prepend_clause(&dp, dt_sugar_makeerrorclause()); | ||||
} | } | ||||
return (dp.dtsp_clause_list); | return (dp.dtsp_clause_list); | ||||
} | } |
You should avoid adding global variables to libdtrace, all state should be accessed via the dtrace_hdl_t structure.