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> | ||||
#ifdef __amd64__ | |||||
#include <dis_tables.h> | |||||
#endif /* __amd64__ */ | |||||
#include <zlib.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; | |||||
} *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 */ | ||||
dt_node_t *dtsp_clause_list; /* list of clauses */ | dt_node_t *dtsp_clause_list; /* list of clauses */ | ||||
struct elf_info dtsp_elf_mod; /* 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 */ | |||||
#define DF_EXISTS 0x01 /* function exists in module */ | |||||
#define DF_REGULAR 0x02 /* function has regular definition */ | |||||
#define DF_INLINE 0x04 /* function has inline definition */ | |||||
#define DF_ENTRY 0x08 /* probe is entry */ | |||||
#define DF_RETURN 0x10 /* probe is return */ | |||||
#define DF_NEWPDESC 0x20 /* we're searching a new pdesc */ | |||||
int dtsp_flags; /* kinst-related flags */ | |||||
char dtsp_func[DTRACE_FUNCNAMELEN]; /* current function */ | |||||
char dtsp_name[DTRACE_NAMELEN]; /* current probe name */ | |||||
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 int | |||||
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) { | |||||
warnx("dt_sugar: elf_version(): %s", elf_errmsg(-1)); | |||||
return (-1); | |||||
} | |||||
if ((ei->fd = open(file, O_RDONLY)) < 0) { | |||||
dt_dprintf("dt_sugar: open(%s)", file); | |||||
return (-1); | |||||
} | |||||
if ((ei->elf = elf_begin(ei->fd, ELF_C_READ, NULL)) == NULL) { | |||||
warnx("dt_sugar: elf_begin(): %s", elf_errmsg(-1)); | |||||
return (-1); | |||||
} | |||||
if (elf_kind(ei->elf) == ELF_K_NONE) { | |||||
warnx("dt_sugar: not an ELF file: %s", file); | |||||
return (-1); | |||||
} | |||||
/* Load ELF sections */ | |||||
if (!elf_getshnum(ei->elf, &ei->shnum)) { | |||||
warnx("dt_sugar: elf_getshnum(): %s", elf_errmsg(-1)); | |||||
return (-1); | |||||
} | |||||
ei->sl = dt_alloc(dtp, ei->shnum * sizeof(struct section)); | |||||
if (ei->sl == NULL) | |||||
longjmp(yypcb->pcb_jmpbuf, EDT_NOMEM); | |||||
if (!elf_getshstrndx(ei->elf, &shstrndx)) { | |||||
warnx("dt_sugar: elf_getshstrndx(): %s", elf_errmsg(-1)); | |||||
return (-1); | |||||
} | |||||
if ((scn = elf_getscn(ei->elf, 0)) == NULL) { | |||||
warnx("dt_sugar: elf_getscn(): %s", elf_errmsg(-1)); | |||||
return (-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; | |||||
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… | |||||
} while ((scn = elf_nextscn(ei->elf, scn)) != NULL); | |||||
if (elf_errno() != 0) | |||||
warnx("dt_sugar: elf_nextscn(): %s", elf_errmsg(-1)); | |||||
return (0); | |||||
} | |||||
static void | 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); | |||||
} | |||||
/* | |||||
* Verify the checksum in the .gnu_debuglink section to make sure the debug | |||||
* file is up to date. | |||||
*/ | |||||
static int | |||||
dt_sugar_elf_verify_debuglink(struct elf_info *ei, int dbgfd) | |||||
{ | |||||
Elf_Data *d; | |||||
struct section *s; | |||||
char buf[MAXPHYS]; | |||||
ssize_t nr; | |||||
uint32_t crc, compcrc; | |||||
int i; | |||||
for (i = 1; i < ei->shnum; i++) { | |||||
s = &ei->sl[i]; | |||||
if (strcmp(s->name, ".gnu_debuglink") == 0) | |||||
break; | |||||
} | |||||
if ((d = elf_getdata(s->scn, NULL)) == NULL) { | |||||
if (elf_errno() != 0) | |||||
warnx("dt_sugar: elf_getdata(): %s", elf_errmsg(-1)); | |||||
return (-1); | |||||
} | |||||
if (d->d_size < sizeof(crc) + 1 || d->d_buf == NULL) | |||||
return (-1); | |||||
if (strnlen(d->d_buf, d->d_size) >= d->d_size - sizeof(crc)) | |||||
return (-1); | |||||
memcpy(&crc, (uint8_t *)d->d_buf + d->d_size - sizeof(crc), | |||||
sizeof(crc)); | |||||
compcrc = crc32(0L, Z_NULL, 0); | |||||
while ((nr = read(dbgfd, buf, sizeof(buf))) > 0) | |||||
compcrc = crc32(compcrc, (char *)buf, nr); | |||||
if (nr != 0 || crc != compcrc) | |||||
return (-1); | |||||
return (0); | |||||
} | |||||
#ifdef __amd64__ | |||||
static int | |||||
dt_sugar_dis_get_byte(void *p) | |||||
{ | |||||
int ret; | |||||
uint8_t **instr = p; | |||||
ret = **instr; | |||||
(*instr)++; | |||||
return (ret); | |||||
} | |||||
#endif /* __amd64__ */ | |||||
/* | |||||
* 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 | |||||
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. | |||||
* 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, int last) | |||||
{ | |||||
Elf_Data *d; | |||||
GElf_Sym sym; | |||||
struct section *s; | |||||
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_mod.shnum; i++) { | |||||
s = &dp->dtsp_elf_mod.sl[i]; | |||||
if (s->sh.sh_type != SHT_SYMTAB && s->sh.sh_type != SHT_DYNSYM) | |||||
continue; | |||||
if (s->sh.sh_link >= dp->dtsp_elf_mod.shnum) | |||||
continue; | |||||
stab = s->sh.sh_link; | |||||
len = (int)(s->sh.sh_size / s->sh.sh_entsize); | |||||
(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 (len > INT_MAX) | |||||
continue; | |||||
for (j = 0; j < len; j++) { | |||||
if (gelf_getsym(d, j, &sym) != &sym) { | |||||
warnx("dt_sugar: gelf_getsym(): %s", | |||||
elf_errmsg(-1)); | |||||
continue; | |||||
} | |||||
if (GELF_ST_TYPE(sym.st_info) != STT_FUNC) | |||||
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_mod.elf, stab, | |||||
sym.st_name)) != NULL) | |||||
break; | |||||
} | |||||
} | |||||
/* Find inline copy's return offset. */ | |||||
for (i = 1; i < dp->dtsp_elf_mod.shnum; i++) { | |||||
s = &dp->dtsp_elf_mod.sl[i]; | |||||
if (strcmp(s->name, ".text") != 0 || | |||||
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; | |||||
while (addr != lo) { | |||||
addr++; | |||||
buf++; | |||||
} | |||||
if (dp->dtsp_flags & DF_ENTRY) { | |||||
off->val = addr_lo - lo; | |||||
} else if (dp->dtsp_flags & DF_RETURN) { | |||||
if (addr_hi == hi || last) { | |||||
/* | |||||
* In this case the offset is one instruction | |||||
* *outside* the inline or the caller function, | |||||
* so we have to go back one instruction to | |||||
* stay within bounds. | |||||
*/ | |||||
/* | |||||
* Get to the inline copy's start manually to | |||||
* avoid potential dtrace_disx86() failures. | |||||
*/ | |||||
while (addr != addr_lo) { | |||||
addr++; | |||||
buf++; | |||||
} | |||||
#ifdef __amd64__ | |||||
dis86_t d86; | |||||
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; | |||||
} | |||||
addr -= d86.d86_len; | |||||
#endif /* __amd64__ */ | |||||
off->val = addr - lo; | |||||
} else | |||||
off->val = addr_hi - 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) { | |||||
/* Find if the function exists in the current module. */ | |||||
if (dwarf_hasattr(die, DW_AT_name, &v_flag, &error) != | |||||
DW_DLV_OK) { | |||||
warnx("dt_sugar: %s", dwarf_errmsg(error)); | |||||
goto cont; | |||||
} | |||||
if (!v_flag) | |||||
goto cont; | |||||
if (dwarf_diename(die, &v_str, &error) != DW_DLV_OK) { | |||||
warnx("dt_sugar: %s", dwarf_errmsg(error)); | |||||
goto cont; | |||||
} | |||||
if (strcmp(v_str, dp->dtsp_func) == 0) | |||||
dp->dtsp_flags |= DF_EXISTS; | |||||
else | |||||
goto cont; | |||||
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) { | |||||
dp->dtsp_flags |= DF_INLINE; | |||||
found = 1; | |||||
} else | |||||
dp->dtsp_flags |= DF_REGULAR; | |||||
goto cont; | |||||
} else if (flag == F_INLINE_COPY) { | |||||
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 (dp->dtsp_flags & DF_ENTRY) { | |||||
/* | |||||
* Trace the first instruction of the first | |||||
* range since this is the beginning of the | |||||
* inline copy. | |||||
*/ | |||||
noff = 1; | |||||
} else if (dp->dtsp_flags & DF_RETURN) { | |||||
/* | |||||
* 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) | |||||
longjmp(yypcb->pcb_jmpbuf, EDT_NOMEM); | |||||
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, | |||||
(dp->dtsp_flags & DF_RETURN) && | |||||
i == noff - 1); | |||||
} | |||||
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) | |||||
longjmp(yypcb->pcb_jmpbuf, EDT_NOMEM); | |||||
dt_sugar_kinst_find_caller_func(dp, off, lowpc, | |||||
lowpc + highpc, | |||||
dp->dtsp_flags & DF_RETURN); | |||||
} | |||||
} else | |||||
goto cont; | |||||
e = dt_alloc(dp->dtsp_dtp, sizeof(struct entry)); | |||||
if (e == NULL) | |||||
longjmp(yypcb->pcb_jmpbuf, EDT_NOMEM); | |||||
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; | |||||
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> | |||||
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… | |||||
* } | |||||
*/ | |||||
static void | |||||
dt_sugar_kinst_create_probes(dt_sugar_parse_t *dp) | |||||
{ | |||||
dt_node_t *dnp, *pdesc; | |||||
struct entry *e; | |||||
char buf[DTRACE_FULLNAMELEN]; | |||||
int i, n; | |||||
dnp = dp->dtsp_clause_list->dn_pdescs; | |||||
n = dp->dtsp_flags & DF_NEWPDESC; | |||||
/* We have found inline copies. 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 (dp->dtsp_flags & DF_NEWPDESC) { | |||||
/* We want to get here only once per-pdesc. */ | |||||
dp->dtsp_flags &= ~DF_NEWPDESC; | |||||
/* | |||||
* 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 { | |||||
/* | |||||
* Create a new probe description for each | |||||
* inline copy and append it to the main pdesc | |||||
* list. | |||||
*/ | |||||
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)); | |||||
dnp = dt_node_link(dnp, pdesc); | |||||
} | |||||
} | |||||
dt_free(dp->dtsp_dtp, e->off); | |||||
dt_free(dp->dtsp_dtp, e); | |||||
} | |||||
if (!(dp->dtsp_flags & DF_INLINE)) { | |||||
/* | |||||
* Delegate non-inline function probes to FBT so that we don't | |||||
* duplicate FBT code in kinst. Regular kinst probes are not | |||||
* affected by this. | |||||
*/ | |||||
strlcpy(dp->dtsp_desc->dtpd_provider, "fbt", | |||||
sizeof(dp->dtsp_desc->dtpd_provider)); | |||||
} else if ((~dp->dtsp_flags & (DF_REGULAR | DF_INLINE)) == 0 && n) { | |||||
/* | |||||
* If we have found a regular definition along with an inline | |||||
* one, create an FBT probe for the non-inline definition as | |||||
* well. | |||||
*/ | |||||
snprintf(buf, sizeof(buf), "fbt:%s:%s:%s", | |||||
dp->dtsp_desc->dtpd_mod, dp->dtsp_func, dp->dtsp_name); | |||||
pdesc = dt_node_pdesc_by_name(strdup(buf)); | |||||
dnp = dt_node_link(dnp, pdesc); | |||||
} | |||||
} | |||||
/* | |||||
* Initialize libelf and libdwarf and parse kernel.debug's DWARF info. | |||||
*/ | |||||
static void | |||||
dt_sugar_do_kinst_inline(dt_sugar_parse_t *dp) | |||||
{ | |||||
dt_module_t *dmp; | |||||
Dwarf_Debug dbg; | |||||
Dwarf_Die die; | |||||
Dwarf_Error error; | |||||
char dbgfile[MAXPATHLEN]; | |||||
int res = DW_DLV_OK; | |||||
strlcpy(dp->dtsp_func, dp->dtsp_desc->dtpd_func, sizeof(dp->dtsp_func)); | |||||
strlcpy(dp->dtsp_name, dp->dtsp_desc->dtpd_name, sizeof(dp->dtsp_func)); | |||||
dp->dtsp_flags = 0; | |||||
/* We only make entry and return probes for inline functions. */ | |||||
if (strcmp(dp->dtsp_name, "entry") == 0) | |||||
dp->dtsp_flags |= DF_ENTRY; | |||||
else if (strcmp(dp->dtsp_name, "return") == 0) | |||||
dp->dtsp_flags |= DF_RETURN; | |||||
else | |||||
return; | |||||
dp->dtsp_flags |= DF_NEWPDESC; | |||||
for (dmp = dt_list_next(&dp->dtsp_dtp->dt_modlist); dmp != NULL; | |||||
dmp = dt_list_next(dmp)) { | |||||
if (strcmp(dmp->dm_name, "C") == 0 || | |||||
strcmp(dmp->dm_name, "D") == 0) | |||||
continue; | |||||
(void) snprintf(dbgfile, sizeof(dbgfile), | |||||
"/usr/lib/debug/%s.debug", dmp->dm_file); | |||||
if (dt_sugar_elf_init(dp->dtsp_dtp, &dp->dtsp_elf_mod, | |||||
dmp->dm_file) < 0) | |||||
continue; | |||||
if (dt_sugar_elf_init(dp->dtsp_dtp, &dp->dtsp_elf_dbg, | |||||
dbgfile) < 0) { | |||||
dt_sugar_elf_deinit(dp->dtsp_dtp, &dp->dtsp_elf_mod); | |||||
continue; | |||||
} | |||||
if (dt_sugar_elf_verify_debuglink(&dp->dtsp_elf_mod, | |||||
dp->dtsp_elf_dbg.fd) < 0) { | |||||
warnx("dt_sugar: debug link mismatch: " | |||||
"make sure '%s' is up to date", dbgfile); | |||||
dt_sugar_elf_deinit(dp->dtsp_dtp, &dp->dtsp_elf_mod); | |||||
dt_sugar_elf_deinit(dp->dtsp_dtp, &dp->dtsp_elf_dbg); | |||||
continue; | |||||
} | |||||
if (dwarf_elf_init(dp->dtsp_elf_dbg.elf, DW_DLC_READ, NULL, | |||||
NULL, &dbg, &error) != DW_DLV_OK) { | |||||
warnx("dt_sugar: dwarf_elf_init(): %s", | |||||
dwarf_errmsg(error)); | |||||
continue; | |||||
} | |||||
TAILQ_INIT(&dp->dtsp_head); | |||||
dp->dtsp_flags &= ~(DF_INLINE | DF_REGULAR | DF_EXISTS); | |||||
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)); | |||||
if (!(dp->dtsp_flags & DF_EXISTS)) | |||||
goto cont; | |||||
dt_sugar_kinst_create_probes(dp); | |||||
cont: | |||||
dwarf_finish(dbg, &error); | |||||
dt_sugar_elf_deinit(dp->dtsp_dtp, &dp->dtsp_elf_mod); | |||||
dt_sugar_elf_deinit(dp->dtsp_dtp, &dp->dtsp_elf_dbg); | |||||
} | |||||
} | |||||
/* | |||||
* 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: | ||||
case DT_NODE_PDESC: | case DT_NODE_PDESC: | ||||
case DT_NODE_IDENT: | case DT_NODE_IDENT: | ||||
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; | 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; | ||||
case DT_NODE_OP1: | case DT_NODE_OP1: | ||||
▲ Show 20 Lines • Show All 242 Lines • ▼ Show 20 Lines | |||||
/* | /* | ||||
* Transform the super-clause into straight-D, returning the new list of | * Transform the super-clause into straight-D, returning the new list of | ||||
* sub-clauses. | * sub-clauses. | ||||
*/ | */ | ||||
dt_node_t * | dt_node_t * | ||||
dt_compile_sugar(dtrace_hdl_t *dtp, dt_node_t *clause) | dt_compile_sugar(dtrace_hdl_t *dtp, dt_node_t *clause) | ||||
{ | { | ||||
dt_sugar_parse_t dp = { 0 }; | dt_sugar_parse_t dp = { 0 }; | ||||
dt_node_t *dnp; | |||||
int condid = 0; | int condid = 0; | ||||
dp.dtsp_dtp = dtp; | dp.dtsp_dtp = dtp; | ||||
dp.dtsp_pdescs = clause->dn_pdescs; | dp.dtsp_pdescs = clause->dn_pdescs; | ||||
/* make dt_node_int() generate an "int"-typed integer */ | /* make dt_node_int() generate an "int"-typed integer */ | ||||
yyintdecimal = B_TRUE; | yyintdecimal = B_TRUE; | ||||
yyintsuffix[0] = '\0'; | yyintsuffix[0] = '\0'; | ||||
Show All 30 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)); | ||||
} | } | ||||
/* | |||||
* This loop is a bit of a hack. What it does is iterate through all | |||||
* probe descriptions and handle kinst entry/return probes, but you | |||||
* will notice that in case we handle inline function probes, | |||||
* dt_sugar_kinst_create_probes() appends new elements to the list | |||||
* we're looping through, yet it works just fine! | |||||
* | |||||
* Consider the following initial `dn_pdescs` list: | |||||
* | |||||
* dn_pdescs = kinst::inlinefunc1:entry | |||||
* dn_pdescs->dn_list = kinst::inlinefunc2:return | |||||
* dn_pdescs->dn_list->dn_list = kinst::normalfunc1:0 | |||||
* dn_pdescs->dn_list->dn_list->dn_list = kinst::normalfunc2:entry | |||||
* | |||||
* The final list will look like this (read the comments in | |||||
* dt_sugar_kinst_create_probes()): | |||||
* | |||||
* dn_pdescs = kinst::callerfunc1:<x> | |||||
* dn_pdescs->dn_list = kinst::callerfunc2:<y> | |||||
* dn_pdescs->dn_list->dn_list = kinst::normalfunc1:0 | |||||
* dn_pdescs->dn_list->dn_list->dn_list = fbt::normalfunc2:entry | |||||
* ... = new probes are appended here | |||||
* | |||||
* Because it is guaranteed that any new probes appended to the list by | |||||
* dt_sugar_kinst_create_probes() will be regular kinst probes, the | |||||
* loop below *does* loop through them as well, but does nothing since | |||||
* regular kinst probes are skipped. | |||||
*/ | |||||
for (dnp = dp.dtsp_clause_list->dn_pdescs; dnp != NULL; | |||||
dnp = dnp->dn_list) { | |||||
if (strcmp(dnp->dn_desc->dtpd_provider, "kinst") != 0) | |||||
continue; | |||||
dp.dtsp_desc = dnp->dn_desc; | |||||
dt_sugar_do_kinst_inline(&dp); | |||||
} | |||||
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.