/* Call probe_finder callback with real subprogram DIE */ static int call_probe_finder(Dwarf_Die *sp_die, struct probe_finder *pf) { Dwarf_Die die_mem; Dwarf_Attribute fb_attr; size_t nops; int ret; /* If no real subprogram, find a real one */ if (!sp_die || dwarf_tag(sp_die) != DW_TAG_subprogram) { sp_die = die_find_real_subprogram(&pf->cu_die, pf->addr, &die_mem); if (!sp_die) { pr_warning("Failed to find probe point in any " "functions.\n"); return -ENOENT; } } /* Get the frame base attribute/ops */ dwarf_attr(sp_die, DW_AT_frame_base, &fb_attr); ret = dwarf_getlocation_addr(&fb_attr, pf->addr, &pf->fb_ops, &nops, 1); if (ret <= 0 || nops == 0) { pf->fb_ops = NULL; #if _ELFUTILS_PREREQ(0, 142) } else if (nops == 1 && pf->fb_ops[0].atom == DW_OP_call_frame_cfa && pf->cfi != NULL) { Dwarf_Frame *frame; if (dwarf_cfi_addrframe(pf->cfi, pf->addr, &frame) != 0 || dwarf_frame_cfa(frame, &pf->fb_ops, &nops) != 0) { pr_warning("Failed to get call frame on 0x%jx\n", (uintmax_t)pf->addr); return -ENOENT; } #endif } /* Call finder's callback handler */ ret = pf->callback(sp_die, pf); /* *pf->fb_ops will be cached in libdw. Don't free it. */ pf->fb_ops = NULL; return ret; }
/* Search function from function name */ static int probe_point_search_cb(Dwarf_Die *sp_die, void *data) { struct dwarf_callback_param *param = data; struct probe_finder *pf = param->data; struct perf_probe_point *pp = &pf->pev->point; /* Check tag and diename */ if (dwarf_tag(sp_die) != DW_TAG_subprogram || die_compare_name(sp_die, pp->function) != 0) return DWARF_CB_OK; pf->fname = dwarf_decl_file(sp_die); if (pp->line) { /* Function relative line */ dwarf_decl_line(sp_die, &pf->lno); pf->lno += pp->line; param->retval = find_probe_point_by_line(pf); } else if (!dwarf_func_inline(sp_die)) { /* Real function */ if (pp->lazy_line) param->retval = find_probe_point_lazy(sp_die, pf); else { if (dwarf_entrypc(sp_die, &pf->addr) != 0) { pr_warning("Failed to get entry pc of %s.\n", dwarf_diename(sp_die)); param->retval = -ENOENT; return DWARF_CB_ABORT; } pf->addr += pp->offset; /* TODO: Check the address in this function */ param->retval = convert_probe_point(sp_die, pf); } } else { struct dwarf_callback_param _param = {.data = (void *)pf, .retval = 0}; /* Inlined function: search instances */ dwarf_func_inline_instances(sp_die, probe_point_inline_cb, &_param); param->retval = _param.retval; } return DWARF_CB_ABORT; /* Exit; no same symbol in this CU. */ } static int find_probe_point_by_func(struct probe_finder *pf) { struct dwarf_callback_param _param = {.data = (void *)pf, .retval = 0}; dwarf_getfuncs(&pf->cu_die, probe_point_search_cb, &_param, 0); return _param.retval; } /* Find kprobe_trace_events specified by perf_probe_event from debuginfo */ int find_kprobe_trace_events(int fd, struct perf_probe_event *pev, struct kprobe_trace_event **tevs, int max_tevs) { struct probe_finder pf = {.pev = pev, .max_tevs = max_tevs}; struct perf_probe_point *pp = &pev->point; Dwarf_Off off, noff; size_t cuhl; Dwarf_Die *diep; Dwarf *dbg; int ret = 0; pf.tevs = zalloc(sizeof(struct kprobe_trace_event) * max_tevs); if (pf.tevs == NULL) return -ENOMEM; *tevs = pf.tevs; pf.ntevs = 0; dbg = dwarf_begin(fd, DWARF_C_READ); if (!dbg) { pr_warning("No dwarf info found in the vmlinux - " "please rebuild with CONFIG_DEBUG_INFO=y.\n"); free(pf.tevs); *tevs = NULL; return -EBADF; } #if _ELFUTILS_PREREQ(0, 142) /* Get the call frame information from this dwarf */ pf.cfi = dwarf_getcfi(dbg); #endif off = 0; line_list__init(&pf.lcache); /* Loop on CUs (Compilation Unit) */ while (!dwarf_nextcu(dbg, off, &noff, &cuhl, NULL, NULL, NULL) && ret >= 0) { /* Get the DIE(Debugging Information Entry) of this CU */ diep = dwarf_offdie(dbg, off + cuhl, &pf.cu_die); if (!diep) continue; /* Check if target file is included. */ if (pp->file) pf.fname = cu_find_realpath(&pf.cu_die, pp->file); else pf.fname = NULL; if (!pp->file || pf.fname) { if (pp->function) ret = find_probe_point_by_func(&pf); else if (pp->lazy_line) ret = find_probe_point_lazy(NULL, &pf); else { pf.lno = pp->line; ret = find_probe_point_by_line(&pf); } } off = noff; } line_list__free(&pf.lcache); dwarf_end(dbg); return (ret < 0) ? ret : pf.ntevs; } /* Reverse search */ int find_perf_probe_point(int fd, unsigned long addr, struct perf_probe_point *ppt) { Dwarf_Die cudie, spdie, indie; Dwarf *dbg; Dwarf_Line *line; Dwarf_Addr laddr, eaddr; const char *tmp; int lineno, ret = 0; bool found = false; dbg = dwarf_begin(fd, DWARF_C_READ); if (!dbg) return -EBADF; /* Find cu die */ if (!dwarf_addrdie(dbg, (Dwarf_Addr)addr, &cudie)) { ret = -EINVAL; goto end; } /* Find a corresponding line */ line = dwarf_getsrc_die(&cudie, (Dwarf_Addr)addr); if (line) { if (dwarf_lineaddr(line, &laddr) == 0 && (Dwarf_Addr)addr == laddr && dwarf_lineno(line, &lineno) == 0) { tmp = dwarf_linesrc(line, NULL, NULL); if (tmp) { ppt->line = lineno; ppt->file = strdup(tmp); if (ppt->file == NULL) { ret = -ENOMEM; goto end; } found = true; } } } /* Find a corresponding function */ if (die_find_real_subprogram(&cudie, (Dwarf_Addr)addr, &spdie)) { tmp = dwarf_diename(&spdie); if (!tmp || dwarf_entrypc(&spdie, &eaddr) != 0) goto end; if (ppt->line) { if (die_find_inlinefunc(&spdie, (Dwarf_Addr)addr, &indie)) { /* addr in an inline function */ tmp = dwarf_diename(&indie); if (!tmp) goto end; ret = dwarf_decl_line(&indie, &lineno); } else { if (eaddr == addr) { /* Function entry */ lineno = ppt->line; ret = 0; } else ret = dwarf_decl_line(&spdie, &lineno); } if (ret == 0) { /* Make a relative line number */ ppt->line -= lineno; goto found; } } /* We don't have a line number, let's use offset */ ppt->offset = addr - (unsigned long)eaddr; found: ppt->function = strdup(tmp); if (ppt->function == NULL) { ret = -ENOMEM; goto end; } found = true; } end: dwarf_end(dbg); if (ret >= 0) ret = found ? 1 : 0; return ret; } /* Add a line and store the src path */ static int line_range_add_line(const char *src, unsigned int lineno, struct line_range *lr) { /* Copy real path */ if (!lr->path) { lr->path = strdup(src); if (lr->path == NULL) return -ENOMEM; } return line_list__add_line(&lr->line_list, lineno); } /* Search function declaration lines */ static int line_range_funcdecl_cb(Dwarf_Die *sp_die, void *data) { struct dwarf_callback_param *param = data; struct line_finder *lf = param->data; const char *src; int lineno; src = dwarf_decl_file(sp_die); if (src && strtailcmp(src, lf->fname) != 0) return DWARF_CB_OK; if (dwarf_decl_line(sp_die, &lineno) != 0 || (lf->lno_s > lineno || lf->lno_e < lineno)) return DWARF_CB_OK; param->retval = line_range_add_line(src, lineno, lf->lr); if (param->retval < 0) return DWARF_CB_ABORT; return DWARF_CB_OK; } static int find_line_range_func_decl_lines(struct line_finder *lf) { struct dwarf_callback_param param = {.data = (void *)lf, .retval = 0}; dwarf_getfuncs(&lf->cu_die, line_range_funcdecl_cb, ¶m, 0); return param.retval; } /* Find line range from its line number */ static int find_line_range_by_line(Dwarf_Die *sp_die, struct line_finder *lf) { Dwarf_Lines *lines; Dwarf_Line *line; size_t nlines, i; Dwarf_Addr addr; int lineno, ret = 0; const char *src; Dwarf_Die die_mem; line_list__init(&lf->lr->line_list); if (dwarf_getsrclines(&lf->cu_die, &lines, &nlines) != 0) { pr_warning("No source lines found in this CU.\n"); return -ENOENT; } /* Search probable lines on lines list */ for (i = 0; i < nlines; i++) { line = dwarf_onesrcline(lines, i); if (dwarf_lineno(line, &lineno) != 0 || (lf->lno_s > lineno || lf->lno_e < lineno)) continue; if (sp_die) { /* Address filtering 1: does sp_die include addr? */ if (dwarf_lineaddr(line, &addr) != 0 || !dwarf_haspc(sp_die, addr)) continue; /* Address filtering 2: No child include addr? */ if (die_find_inlinefunc(sp_die, addr, &die_mem)) continue; } /* TODO: Get fileno from line, but how? */ src = dwarf_linesrc(line, NULL, NULL); if (strtailcmp(src, lf->fname) != 0) continue; ret = line_range_add_line(src, lineno, lf->lr); if (ret < 0) return ret; } /* * Dwarf lines doesn't include function declarations. We have to * check functions list or given function. */ if (sp_die) { src = dwarf_decl_file(sp_die); if (src && dwarf_decl_line(sp_die, &lineno) == 0 && (lf->lno_s <= lineno && lf->lno_e >= lineno)) ret = line_range_add_line(src, lineno, lf->lr); } else ret = find_line_range_func_decl_lines(lf); /* Update status */ if (ret >= 0) if (!list_empty(&lf->lr->line_list)) ret = lf->found = 1; else ret = 0; /* Lines are not found */ else { free(lf->lr->path); lf->lr->path = NULL; } return ret; } static int line_range_inline_cb(Dwarf_Die *in_die, void *data) { struct dwarf_callback_param *param = data; param->retval = find_line_range_by_line(in_die, param->data); return DWARF_CB_ABORT; /* No need to find other instances */ } /* Search function from function name */ static int line_range_search_cb(Dwarf_Die *sp_die, void *data) { struct dwarf_callback_param *param = data; struct line_finder *lf = param->data; struct line_range *lr = lf->lr; if (dwarf_tag(sp_die) == DW_TAG_subprogram && die_compare_name(sp_die, lr->function) == 0) { lf->fname = dwarf_decl_file(sp_die); dwarf_decl_line(sp_die, &lr->offset); pr_debug("fname: %s, lineno:%d\n", lf->fname, lr->offset); lf->lno_s = lr->offset + lr->start; if (lf->lno_s < 0) /* Overflow */ lf->lno_s = INT_MAX; lf->lno_e = lr->offset + lr->end; if (lf->lno_e < 0) /* Overflow */ lf->lno_e = INT_MAX; pr_debug("New line range: %d to %d\n", lf->lno_s, lf->lno_e); lr->start = lf->lno_s; lr->end = lf->lno_e; if (dwarf_func_inline(sp_die)) { struct dwarf_callback_param _param; _param.data = (void *)lf; _param.retval = 0; dwarf_func_inline_instances(sp_die, line_range_inline_cb, &_param); param->retval = _param.retval; } else param->retval = find_line_range_by_line(sp_die, lf); return DWARF_CB_ABORT; } return DWARF_CB_OK; } static int find_line_range_by_func(struct line_finder *lf) { struct dwarf_callback_param param = {.data = (void *)lf, .retval = 0}; dwarf_getfuncs(&lf->cu_die, line_range_search_cb, ¶m, 0); return param.retval; } int find_line_range(int fd, struct line_range *lr) { struct line_finder lf = {.lr = lr, .found = 0}; int ret = 0; Dwarf_Off off = 0, noff; size_t cuhl; Dwarf_Die *diep; Dwarf *dbg; dbg = dwarf_begin(fd, DWARF_C_READ); if (!dbg) { pr_warning("No dwarf info found in the vmlinux - " "please rebuild with CONFIG_DEBUG_INFO=y.\n"); return -EBADF; } /* Loop on CUs (Compilation Unit) */ while (!lf.found && ret >= 0) { if (dwarf_nextcu(dbg, off, &noff, &cuhl, NULL, NULL, NULL) != 0) break; /* Get the DIE(Debugging Information Entry) of this CU */ diep = dwarf_offdie(dbg, off + cuhl, &lf.cu_die); if (!diep) continue; /* Check if target file is included. */ if (lr->file) lf.fname = cu_find_realpath(&lf.cu_die, lr->file); else lf.fname = 0; if (!lr->file || lf.fname) { if (lr->function) ret = find_line_range_by_func(&lf); else { lf.lno_s = lr->start; lf.lno_e = lr->end; ret = find_line_range_by_line(NULL, &lf); } } off = noff; } pr_debug("path: %lx\n", (unsigned long)lr->path); dwarf_end(dbg); return (ret < 0) ? ret : lf.found; }
/* Show a probe point to output buffer */ static int convert_probe_point(Dwarf_Die *sp_die, struct probe_finder *pf) { struct kprobe_trace_event *tev; Dwarf_Addr eaddr; Dwarf_Die die_mem; const char *name; int ret, i; Dwarf_Attribute fb_attr; size_t nops; if (pf->ntevs == pf->max_tevs) { pr_warning("Too many( > %d) probe point found.\n", pf->max_tevs); return -ERANGE; } tev = &pf->tevs[pf->ntevs++]; /* If no real subprogram, find a real one */ if (!sp_die || dwarf_tag(sp_die) != DW_TAG_subprogram) { sp_die = die_find_real_subprogram(&pf->cu_die, pf->addr, &die_mem); if (!sp_die) { pr_warning("Failed to find probe point in any " "functions.\n"); return -ENOENT; } } /* Copy the name of probe point */ name = dwarf_diename(sp_die); if (name) { if (dwarf_entrypc(sp_die, &eaddr) != 0) { pr_warning("Failed to get entry pc of %s\n", dwarf_diename(sp_die)); return -ENOENT; } tev->point.symbol = strdup(name); if (tev->point.symbol == NULL) return -ENOMEM; tev->point.offset = (unsigned long)(pf->addr - eaddr); } else /* This function has no name. */ tev->point.offset = (unsigned long)pf->addr; pr_debug("Probe point found: %s+%lu\n", tev->point.symbol, tev->point.offset); /* Get the frame base attribute/ops */ dwarf_attr(sp_die, DW_AT_frame_base, &fb_attr); ret = dwarf_getlocation_addr(&fb_attr, pf->addr, &pf->fb_ops, &nops, 1); if (ret <= 0 || nops == 0) { pf->fb_ops = NULL; #if _ELFUTILS_PREREQ(0, 142) } else if (nops == 1 && pf->fb_ops[0].atom == DW_OP_call_frame_cfa && pf->cfi != NULL) { Dwarf_Frame *frame; if (dwarf_cfi_addrframe(pf->cfi, pf->addr, &frame) != 0 || dwarf_frame_cfa(frame, &pf->fb_ops, &nops) != 0) { pr_warning("Failed to get CFA on 0x%jx\n", (uintmax_t)pf->addr); return -ENOENT; } #endif } /* Find each argument */ tev->nargs = pf->pev->nargs; tev->args = zalloc(sizeof(struct kprobe_trace_arg) * tev->nargs); if (tev->args == NULL) return -ENOMEM; for (i = 0; i < pf->pev->nargs; i++) { pf->pvar = &pf->pev->args[i]; pf->tvar = &tev->args[i]; ret = find_variable(sp_die, pf); if (ret != 0) return ret; } /* *pf->fb_ops will be cached in libdw. Don't free it. */ pf->fb_ops = NULL; return 0; }
/* Reverse search */ int find_perf_probe_point(unsigned long addr, struct perf_probe_point *ppt) { Dwarf_Die cudie, spdie, indie; Dwarf *dbg = NULL; Dwfl *dwfl = NULL; Dwarf_Line *line; Dwarf_Addr laddr, eaddr, bias = 0; const char *tmp; int lineno, ret = 0; bool found = false; /* Open the live linux kernel */ dbg = dwfl_init_live_kernel_dwarf(addr, &dwfl, &bias); if (!dbg) { pr_warning("No debug information found in the vmlinux - " "please rebuild with CONFIG_DEBUG_INFO=y.\n"); ret = -EINVAL; goto end; } /* Adjust address with bias */ addr += bias; /* Find cu die */ if (!dwarf_addrdie(dbg, (Dwarf_Addr)addr - bias, &cudie)) { pr_warning("Failed to find debug information for address %lx\n", addr); ret = -EINVAL; goto end; } /* Find a corresponding line */ line = dwarf_getsrc_die(&cudie, (Dwarf_Addr)addr); if (line) { if (dwarf_lineaddr(line, &laddr) == 0 && (Dwarf_Addr)addr == laddr && dwarf_lineno(line, &lineno) == 0) { tmp = dwarf_linesrc(line, NULL, NULL); if (tmp) { ppt->line = lineno; ppt->file = strdup(tmp); if (ppt->file == NULL) { ret = -ENOMEM; goto end; } found = true; } } } /* Find a corresponding function */ if (die_find_real_subprogram(&cudie, (Dwarf_Addr)addr, &spdie)) { tmp = dwarf_diename(&spdie); if (!tmp || dwarf_entrypc(&spdie, &eaddr) != 0) goto end; if (ppt->line) { if (die_find_inlinefunc(&spdie, (Dwarf_Addr)addr, &indie)) { /* addr in an inline function */ tmp = dwarf_diename(&indie); if (!tmp) goto end; ret = dwarf_decl_line(&indie, &lineno); } else { if (eaddr == addr) { /* Function entry */ lineno = ppt->line; ret = 0; } else ret = dwarf_decl_line(&spdie, &lineno); } if (ret == 0) { /* Make a relative line number */ ppt->line -= lineno; goto found; } } /* We don't have a line number, let's use offset */ ppt->offset = addr - (unsigned long)eaddr; found: ppt->function = strdup(tmp); if (ppt->function == NULL) { ret = -ENOMEM; goto end; } found = true; } end: if (dwfl) dwfl_end(dwfl); if (ret >= 0) ret = found ? 1 : 0; return ret; }