/** * Lookup the source location for a given address within a CU, * assuming that it is contained within an inlined function. * * A source location can be found regardless of inlining status for * this method, but in the case of an inlined function, the returned * source location will point not to the callsite but rather to the * definition site of the inline function. * * @param cu bt_dwarf_cu instance in which to look for the address * @param addr The address for which to look for * @param src_loc Out parameter, the source location (filename and * line number) for the address * @returns 0 on success, -1 on failure */ static int bin_info_lookup_cu_src_loc_no_inl(struct bt_dwarf_cu *cu, uint64_t addr, struct source_location **src_loc) { struct source_location *_src_loc = NULL; struct bt_dwarf_die *die = NULL; const char *filename = NULL; Dwarf_Line *line = NULL; Dwarf_Addr line_addr; int ret, line_no; if (!cu || !src_loc) { goto error; } die = bt_dwarf_die_create(cu); if (!die) { goto error; } line = dwarf_getsrc_die(die->dwarf_die, addr); if (!line) { goto error; } ret = dwarf_lineaddr(line, &line_addr); if (ret) { goto error; } filename = dwarf_linesrc(line, NULL, NULL); if (!filename) { goto error; } if (addr == line_addr) { _src_loc = g_new0(struct source_location, 1); if (!_src_loc) { goto error; } ret = dwarf_lineno(line, &line_no); if (ret) { goto error; } _src_loc->line_no = line_no; _src_loc->filename = strdup(filename); }
/** * cu_find_lineinfo - Get a line number and file name for given address * @cu_die: a CU DIE * @addr: An address * @fname: a pointer which returns the file name string * @lineno: a pointer which returns the line number * * Find a line number and file name for @addr in @cu_die. */ int cu_find_lineinfo(Dwarf_Die *cu_die, unsigned long addr, const char **fname, int *lineno) { Dwarf_Line *line; Dwarf_Addr laddr; line = dwarf_getsrc_die(cu_die, (Dwarf_Addr)addr); if (line && dwarf_lineaddr(line, &laddr) == 0 && addr == (unsigned long)laddr && dwarf_lineno(line, lineno) == 0) { *fname = dwarf_linesrc(line, NULL, NULL); if (!*fname) /* line number is useless without filename */ *lineno = 0; } return *lineno ?: -ENOENT; }
/* 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; }
/* 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; }