/* * probe-finder.c : C expression to kprobe event converter * * Written by Masami Hiramatsu * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * */ #include #include #include #include #include #include #include #include #include #include #include #include #include "event.h" #include "debug.h" #include "util.h" #include "probe-finder.h" /* Dwarf_Die Linkage to parent Die */ struct die_link { struct die_link *parent; /* Parent die */ Dwarf_Die die; /* Current die */ }; static Dwarf_Debug __dw_debug; static Dwarf_Error __dw_error; /* * Generic dwarf analysis helpers */ #define X86_32_MAX_REGS 8 const char *x86_32_regs_table[X86_32_MAX_REGS] = { "%ax", "%cx", "%dx", "%bx", "$stack", /* Stack address instead of %sp */ "%bp", "%si", "%di", }; #define X86_64_MAX_REGS 16 const char *x86_64_regs_table[X86_64_MAX_REGS] = { "%ax", "%dx", "%cx", "%bx", "%si", "%di", "%bp", "%sp", "%r8", "%r9", "%r10", "%r11", "%r12", "%r13", "%r14", "%r15", }; /* TODO: switching by dwarf address size */ #ifdef __x86_64__ #define ARCH_MAX_REGS X86_64_MAX_REGS #define arch_regs_table x86_64_regs_table #else #define ARCH_MAX_REGS X86_32_MAX_REGS #define arch_regs_table x86_32_regs_table #endif /* Return architecture dependent register string (for kprobe-tracer) */ static const char *get_arch_regstr(unsigned int n) { return (n <= ARCH_MAX_REGS) ? arch_regs_table[n] : NULL; } /* * Compare the tail of two strings. * Return 0 if whole of either string is same as another's tail part. */ static int strtailcmp(const char *s1, const char *s2) { int i1 = strlen(s1); int i2 = strlen(s2); while (--i1 > 0 && --i2 > 0) { if (s1[i1] != s2[i2]) return s1[i1] - s2[i2]; } return 0; } /* Find the fileno of the target file. */ static Dwarf_Unsigned cu_find_fileno(Dwarf_Die cu_die, const char *fname) { Dwarf_Signed cnt, i; Dwarf_Unsigned found = 0; char **srcs; int ret; if (!fname) return 0; ret = dwarf_srcfiles(cu_die, &srcs, &cnt, &__dw_error); if (ret == DW_DLV_OK) { for (i = 0; i < cnt && !found; i++) { if (strtailcmp(srcs[i], fname) == 0) found = i + 1; dwarf_dealloc(__dw_debug, srcs[i], DW_DLA_STRING); } for (; i < cnt; i++) dwarf_dealloc(__dw_debug, srcs[i], DW_DLA_STRING); dwarf_dealloc(__dw_debug, srcs, DW_DLA_LIST); } if (found) pr_debug("found fno: %d\n", (int)found); return found; } /* Compare diename and tname */ static int die_compare_name(Dwarf_Die dw_die, const char *tname) { char *name; int ret; ret = dwarf_diename(dw_die, &name, &__dw_error); DIE_IF(ret == DW_DLV_ERROR); if (ret == DW_DLV_OK) { ret = strcmp(tname, name); dwarf_dealloc(__dw_debug, name, DW_DLA_STRING); } else ret = -1; return ret; } /* Check the address is in the subprogram(function). */ static int die_within_subprogram(Dwarf_Die sp_die, Dwarf_Addr addr, Dwarf_Signed *offs) { Dwarf_Addr lopc, hipc; int ret; /* TODO: check ranges */ ret = dwarf_lowpc(sp_die, &lopc, &__dw_error); DIE_IF(ret == DW_DLV_ERROR); if (ret == DW_DLV_NO_ENTRY) return 0; ret = dwarf_highpc(sp_die, &hipc, &__dw_error); DIE_IF(ret != DW_DLV_OK); if (lopc <= addr && addr < hipc) { *offs = addr - lopc; return 1; } else return 0; } /* Check the die is inlined function */ static Dwarf_Bool die_inlined_subprogram(Dwarf_Die dw_die) { /* TODO: check strictly */ Dwarf_Bool inl; int ret; ret = dwarf_hasattr(dw_die, DW_AT_inline, &inl, &__dw_error); DIE_IF(ret == DW_DLV_ERROR); return inl; } /* Get the offset of abstruct_origin */ static Dwarf_Off die_get_abstract_origin(Dwarf_Die dw_die) { Dwarf_Attribute attr; Dwarf_Off cu_offs; int ret; ret = dwarf_attr(dw_die, DW_AT_abstract_origin, &attr, &__dw_error); DIE_IF(ret != DW_DLV_OK); ret = dwarf_formref(attr, &cu_offs, &__dw_error); DIE_IF(ret != DW_DLV_OK); dwarf_dealloc(__dw_debug, attr, DW_DLA_ATTR); return cu_offs; } /* Get entry pc(or low pc, 1st entry of ranges) of the die */ static Dwarf_Addr die_get_entrypc(Dwarf_Die dw_die) { Dwarf_Attribute attr; Dwarf_Addr addr; Dwarf_Off offs; Dwarf_Ranges *ranges; Dwarf_Signed cnt; int ret; /* Try to get entry pc */ ret = dwarf_attr(dw_die, DW_AT_entry_pc, &attr, &__dw_error); DIE_IF(ret == DW_DLV_ERROR); if (ret == DW_DLV_OK) { ret = dwarf_formaddr(attr, &addr, &__dw_error); DIE_IF(ret != DW_DLV_OK); dwarf_dealloc(__dw_debug, attr, DW_DLA_ATTR); return addr; } /* Try to get low pc */ ret = dwarf_lowpc(dw_die, &addr, &__dw_error); DIE_IF(ret == DW_DLV_ERROR); if (ret == DW_DLV_OK) return addr; /* Try to get ranges */ ret = dwarf_attr(dw_die, DW_AT_ranges, &attr, &__dw_error); DIE_IF(ret != DW_DLV_OK); ret = dwarf_formref(attr, &offs, &__dw_error); DIE_IF(ret != DW_DLV_OK); ret = dwarf_get_ranges(__dw_debug, offs, &ranges, &cnt, NULL, &__dw_error); DIE_IF(ret != DW_DLV_OK); addr = ranges[0].dwr_addr1; dwarf_ranges_dealloc(__dw_debug, ranges, cnt); return addr; } /* * Search a Die from Die tree. * Note: cur_link->die should be deallocated in this function. */ static int __search_die_tree(struct die_link *cur_link, int (*die_cb)(struct die_link *, void *), void *data) { Dwarf_Die new_die; struct die_link new_link; int ret; if (!die_cb) return 0; /* Check current die */ while (!(ret = die_cb(cur_link, data))) { /* Check child die */ ret = dwarf_child(cur_link->die, &new_die, &__dw_error); DIE_IF(ret == DW_DLV_ERROR); if (ret == DW_DLV_OK) { new_link.parent = cur_link; new_link.die = new_die; ret = __search_die_tree(&new_link, die_cb, data); if (ret) break; } /* Move to next sibling */ ret = dwarf_siblingof(__dw_debug, cur_link->die, &new_die, &__dw_error); DIE_IF(ret == DW_DLV_ERROR); dwarf_dealloc(__dw_debug, cur_link->die, DW_DLA_DIE); cur_link->die = new_die; if (ret == DW_DLV_NO_ENTRY) return 0; } dwarf_dealloc(__dw_debug, cur_link->die, DW_DLA_DIE); return ret; } /* Search a die in its children's die tree */ static int search_die_from_children(Dwarf_Die parent_die, int (*die_cb)(struct die_link *, void *), void *data) { struct die_link new_link; int ret; new_link.parent = NULL; ret = dwarf_child(parent_die, &new_link.die, &__dw_error); DIE_IF(ret == DW_DLV_ERROR); if (ret == DW_DLV_OK) return __search_die_tree(&new_link, die_cb, data); else return 0; } /* Find a locdesc corresponding to the address */ static int attr_get_locdesc(Dwarf_Attribute attr, Dwarf_Locdesc *desc, Dwarf_Addr addr) { Dwarf_Signed lcnt; Dwarf_Locdesc **llbuf; int ret, i; ret = dwarf_loclist_n(attr, &llbuf, &lcnt, &__dw_error); DIE_IF(ret != DW_DLV_OK); ret = DW_DLV_NO_ENTRY; for (i = 0; i < lcnt; ++i) { if (llbuf[i]->ld_lopc <= addr && llbuf[i]->ld_hipc > addr) { memcpy(desc, llbuf[i], sizeof(Dwarf_Locdesc)); desc->ld_s = malloc(sizeof(Dwarf_Loc) * llbuf[i]->ld_cents); DIE_IF(desc->ld_s == NULL); memcpy(desc->ld_s, llbuf[i]->ld_s, sizeof(Dwarf_Loc) * llbuf[i]->ld_cents); ret = DW_DLV_OK; break; } dwarf_dealloc(__dw_debug, llbuf[i]->ld_s, DW_DLA_LOC_BLOCK); dwarf_dealloc(__dw_debug, llbuf[i], DW_DLA_LOCDESC); } /* Releasing loop */ for (; i < lcnt; ++i) { dwarf_dealloc(__dw_debug, llbuf[i]->ld_s, DW_DLA_LOC_BLOCK); dwarf_dealloc(__dw_debug, llbuf[i], DW_DLA_LOCDESC); } dwarf_dealloc(__dw_debug, llbuf, DW_DLA_LIST); return ret; } /* Get decl_file attribute value (file number) */ static Dwarf_Unsigned die_get_decl_file(Dwarf_Die sp_die) { Dwarf_Attribute attr; Dwarf_Unsigned fno; int ret; ret = dwarf_attr(sp_die, DW_AT_decl_file, &attr, &__dw_error); DIE_IF(ret != DW_DLV_OK); dwarf_formudata(attr, &fno, &__dw_error); DIE_IF(ret != DW_DLV_OK); dwarf_dealloc(__dw_debug, attr, DW_DLA_ATTR); return fno; } /* Get decl_line attribute value (line number) */ static Dwarf_Unsigned die_get_decl_line(Dwarf_Die sp_die) { Dwarf_Attribute attr; Dwarf_Unsigned lno; int ret; ret = dwarf_attr(sp_die, DW_AT_decl_line, &attr, &__dw_error); DIE_IF(ret != DW_DLV_OK); dwarf_formudata(attr, &lno, &__dw_error); DIE_IF(ret != DW_DLV_OK); dwarf_dealloc(__dw_debug, attr, DW_DLA_ATTR); return lno; } /* * Probe finder related functions */ /* Show a location */ static void show_location(Dwarf_Loc *loc, struct probe_finder *pf) { Dwarf_Small op; Dwarf_Unsigned regn; Dwarf_Signed offs; int deref = 0, ret; const char *regs; op = loc->lr_atom; /* If this is based on frame buffer, set the offset */ if (op == DW_OP_fbreg) { deref = 1; offs = (Dwarf_Signed)loc->lr_number; op = pf->fbloc.ld_s[0].lr_atom; loc = &pf->fbloc.ld_s[0]; } else offs = 0; if (op >= DW_OP_breg0 && op <= DW_OP_breg31) { regn = op - DW_OP_breg0; offs += (Dwarf_Signed)loc->lr_number; deref = 1; } else if (op >= DW_OP_reg0 && op <= DW_OP_reg31) { regn = op - DW_OP_reg0; } else if (op == DW_OP_bregx) { regn = loc->lr_number; offs += (Dwarf_Signed)loc->lr_number2; deref = 1; } else if (op == DW_OP_regx) { regn = loc->lr_number; } else die("Dwarf_OP %d is not supported.\n", op); regs = get_arch_regstr(regn); if (!regs) die("%lld exceeds max register number.\n", regn); if (deref) ret = snprintf(pf->buf, pf->len, " %s=%+lld(%s)", pf->var, offs, regs); else ret = snprintf(pf->buf, pf->len, " %s=%s", pf->var, regs); DIE_IF(ret < 0); DIE_IF(ret >= pf->len); } /* Show a variables in kprobe event format */ static void show_variable(Dwarf_Die vr_die, struct probe_finder *pf) { Dwarf_Attribute attr; Dwarf_Locdesc ld; int ret; ret = dwarf_attr(vr_die, DW_AT_location, &attr, &__dw_error); if (ret != DW_DLV_OK) goto error; ret = attr_get_locdesc(attr, &ld, (pf->addr - pf->cu_base)); if (ret != DW_DLV_OK) goto error; /* TODO? */ DIE_IF(ld.ld_cents != 1); show_location(&ld.ld_s[0], pf); free(ld.ld_s); dwarf_dealloc(__dw_debug, attr, DW_DLA_ATTR); return ; error: die("Failed to find the location of %s at this address.\n" " Perhaps, it has been optimized out.\n", pf->var); } static int variable_callback(struct die_link *dlink, void *data) { struct probe_finder *pf = (struct probe_finder *)data; Dwarf_Half tag; int ret; ret = dwarf_tag(dlink->die, &tag, &__dw_error); DIE_IF(ret == DW_DLV_ERROR); if ((tag == DW_TAG_formal_parameter || tag == DW_TAG_variable) && (die_compare_name(dlink->die, pf->var) == 0)) { show_variable(dlink->die, pf); return 1; } /* TODO: Support struct members and arrays */ return 0; } /* Find a variable in a subprogram die */ static void find_variable(Dwarf_Die sp_die, struct probe_finder *pf) { int ret; if (!is_c_varname(pf->var)) { /* Output raw parameters */ ret = snprintf(pf->buf, pf->len, " %s", pf->var); DIE_IF(ret < 0); DIE_IF(ret >= pf->len); return ; } pr_debug("Searching '%s' variable in context.\n", pf->var); /* Search child die for local variables and parameters. */ ret = search_die_from_children(sp_die, variable_callback, pf); if (!ret) die("Failed to find '%s' in this function.\n", pf->var); } /* Get a frame base on the address */ static void get_current_frame_base(Dwarf_Die sp_die, struct probe_finder *pf) { Dwarf_Attribute attr; int ret; ret = dwarf_attr(sp_die, DW_AT_frame_base, &attr, &__dw_error); DIE_IF(ret != DW_DLV_OK); ret = attr_get_locdesc(attr, &pf->fbloc, (pf->addr - pf->cu_base)); DIE_IF(ret != DW_DLV_OK); dwarf_dealloc(__dw_debug, attr, DW_DLA_ATTR); } static void free_current_frame_base(struct probe_finder *pf) { free(pf->fbloc.ld_s); memset(&pf->fbloc, 0, sizeof(Dwarf_Locdesc)); } /* Show a probe point to output buffer */ static void show_probepoint(Dwarf_Die sp_die, Dwarf_Signed offs, struct probe_finder *pf) { struct probe_point *pp = pf->pp; char *name; char tmp[MAX_PROBE_BUFFER]; int ret, i, len; /* Output name of probe point */ ret = dwarf_diename(sp_die, &name, &__dw_error); DIE_IF(ret == DW_DLV_ERROR); if (ret == DW_DLV_OK) { ret = snprintf(tmp, MAX_PROBE_BUFFER, "%s+%u", name, (unsigned int)offs); /* Copy the function name if possible */ if (!pp->function) { pp->function = strdup(name); pp->offset = offs; } dwarf_dealloc(__dw_debug, name, DW_DLA_STRING); } else { /* This function has no name. */ ret = snprintf(tmp, MAX_PROBE_BUFFER, "0x%llx", pf->addr); if (!pp->function) { /* TODO: Use _stext */ pp->function = strdup(""); pp->offset = (int)pf->addr; } } DIE_IF(ret < 0); DIE_IF(ret >= MAX_PROBE_BUFFER); len = ret; pr_debug("Probe point found: %s\n", tmp); /* Find each argument */ get_current_frame_base(sp_die, pf); for (i = 0; i < pp->nr_args; i++) { pf->var = pp->args[i]; pf->buf = &tmp[len]; pf->len = MAX_PROBE_BUFFER - len; find_variable(sp_die, pf); len += strlen(pf->buf); } free_current_frame_base(pf); pp->probes[pp->found] = strdup(tmp); pp->found++; } static int probeaddr_callback(struct die_link *dlink, void *data) { struct probe_finder *pf = (struct probe_finder *)data; Dwarf_Half tag; Dwarf_Signed offs; int ret; ret = dwarf_tag(dlink->die, &tag, &__dw_error); DIE_IF(ret == DW_DLV_ERROR); /* Check the address is in this subprogram */ if (tag == DW_TAG_subprogram && die_within_subprogram(dlink->die, pf->addr, &offs)) { show_probepoint(dlink->die, offs, pf); return 1; } return 0; } /* Find probe point from its line number */ static void find_by_line(struct probe_finder *pf) { Dwarf_Signed cnt, i, clm; Dwarf_Line *lines; Dwarf_Unsigned lineno = 0; Dwarf_Addr addr; Dwarf_Unsigned fno; int ret; ret = dwarf_srclines(pf->cu_die, &lines, &cnt, &__dw_error); DIE_IF(ret != DW_DLV_OK); for (i = 0; i < cnt; i++) { ret = dwarf_line_srcfileno(lines[i], &fno, &__dw_error); DIE_IF(ret != DW_DLV_OK); if (fno != pf->fno) continue; ret = dwarf_lineno(lines[i], &lineno, &__dw_error); DIE_IF(ret != DW_DLV_OK); if (lineno != pf->lno) continue; ret = dwarf_lineoff(lines[i], &clm, &__dw_error); DIE_IF(ret != DW_DLV_OK); ret = dwarf_lineaddr(lines[i], &addr, &__dw_error); DIE_IF(ret != DW_DLV_OK); pr_debug("Probe line found: line[%d]:%u,%d addr:0x%llx\n", (int)i, (unsigned)lineno, (int)clm, addr); pf->addr = addr; /* Search a real subprogram including this line, */ ret = search_die_from_children(pf->cu_die, probeaddr_callback, pf); if (ret == 0) die("Probe point is not found in subprograms.\n"); /* Continuing, because target line might be inlined. */ } dwarf_srclines_dealloc(__dw_debug, lines, cnt); } /* Search function from function name */ static int probefunc_callback(struct die_link *dlink, void *data) { struct probe_finder *pf = (struct probe_finder *)data; struct probe_point *pp = pf->pp; struct die_link *lk; Dwarf_Signed offs; Dwarf_Half tag; int ret; ret = dwarf_tag(dlink->die, &tag, &__dw_error); DIE_IF(ret == DW_DLV_ERROR); if (tag == DW_TAG_subprogram) { if (die_compare_name(dlink->die, pp->function) == 0) { if (pp->line) { /* Function relative line */ pf->fno = die_get_decl_file(dlink->die); pf->lno = die_get_decl_line(dlink->die) + pp->line; find_by_line(pf); return 1; } if (die_inlined_subprogram(dlink->die)) { /* Inlined function, save it. */ ret = dwarf_die_CU_offset(dlink->die, &pf->inl_offs, &__dw_error); DIE_IF(ret != DW_DLV_OK); pr_debug("inline definition offset %lld\n", pf->inl_offs); return 0; /* Continue to search */ } /* Get probe address */ pf->addr = die_get_entrypc(dlink->die); pf->addr += pp->offset; /* TODO: Check the address in this function */ show_probepoint(dlink->die, pp->offset, pf); return 1; /* Exit; no same symbol in this CU. */ } } else if (tag == DW_TAG_inlined_subroutine && pf->inl_offs) { if (die_get_abstract_origin(dlink->die) == pf->inl_offs) { /* Get probe address */ pf->addr = die_get_entrypc(dlink->die); pf->addr += pp->offset; pr_debug("found inline addr: 0x%llx\n", pf->addr); /* Inlined function. Get a real subprogram */ for (lk = dlink->parent; lk != NULL; lk = lk->parent) { tag = 0; dwarf_tag(lk->die, &tag, &__dw_error); DIE_IF(ret == DW_DLV_ERROR); if (tag == DW_TAG_subprogram && !die_inlined_subprogram(lk->die)) goto found; } die("Failed to find real subprogram.\n"); found: /* Get offset from subprogram */ ret = die_within_subprogram(lk->die, pf->addr, &offs); DIE_IF(!ret); show_probepoint(lk->die, offs, pf); /* Continue to search */ } } return 0; } static void find_by_func(struct probe_finder *pf) { search_die_from_children(pf->cu_die, probefunc_callback, pf); } /* Find a probe point */ int find_probepoint(int fd, struct probe_point *pp) { Dwarf_Half addr_size = 0; Dwarf_Unsigned next_cuh = 0; int cu_number = 0, ret; struct probe_finder pf = {.pp = pp}; ret = dwarf_init(fd, DW_DLC_READ, 0, 0, &__dw_debug, &__dw_error); if (ret != DW_DLV_OK) { pr_warning("No dwarf info found in the vmlinux - please rebuild with CONFIG_DEBUG_INFO.\n"); return -ENOENT; } pp->found = 0; while (++cu_number) { /* Search CU (Compilation Unit) */ ret = dwarf_next_cu_header(__dw_debug, NULL, NULL, NULL, &addr_size, &next_cuh, &__dw_error); DIE_IF(ret == DW_DLV_ERROR); if (ret == DW_DLV_NO_ENTRY) break; /* Get the DIE(Debugging Information Entry) of this CU */ ret = dwarf_siblingof(__dw_debug, 0, &pf.cu_die, &__dw_error); DIE_IF(ret != DW_DLV_OK); /* Check if target file is included. */ if (pp->file) pf.fno = cu_find_fileno(pf.cu_die, pp->file); if (!pp->file || pf.fno) { /* Save CU base address (for frame_base) */ ret = dwarf_lowpc(pf.cu_die, &pf.cu_base, &__dw_error); DIE_IF(ret == DW_DLV_ERROR); if (ret == DW_DLV_NO_ENTRY) pf.cu_base = 0; if (pp->function) find_by_func(&pf); else { pf.lno = pp->line; find_by_line(&pf); } } dwarf_dealloc(__dw_debug, pf.cu_die, DW_DLA_DIE); } ret = dwarf_finish(__dw_debug, &__dw_error); DIE_IF(ret != DW_DLV_OK); return pp->found; }