/* * c2kpe : 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 /* Default vmlinux search paths */ #define NR_SEARCH_PATH 2 const char *default_search_path[NR_SEARCH_PATH] = { "/lib/modules/%s/build/vmlinux", /* Custom build kernel */ "/usr/lib/debug/lib/modules/%s/vmlinux", /* Red Hat debuginfo */ }; #define _stringify(n) #n #define stringify(n) _stringify(n) #ifdef DEBUG #define debug(fmt ...) \ fprintf(stderr, "DBG(" __FILE__ ":" stringify(__LINE__) "): " fmt) #else #define debug(fmt ...) do {} while (0) #endif #define ERR_IF(cnd) \ do { if (cnd) { \ fprintf(stderr, "Error (" __FILE__ ":" stringify(__LINE__) \ "): " stringify(cnd) "\n"); \ exit(1); \ }} while (0) #define MAX_PATH_LEN 256 /* Dwarf_Die Linkage to parent Die */ struct die_link { struct die_link *parent; /* Parent die */ Dwarf_Die die; /* Current die */ }; #define X86_32_MAX_REGS 8 const char *x86_32_regs_table[X86_32_MAX_REGS] = { "%ax", "%cx", "%dx", "%bx", "sa", /* Stack address */ "%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 */ static inline const char *get_arch_regstr(unsigned int n) { return (n <= ARCH_MAX_REGS) ? arch_regs_table[n] : NULL; } struct probe_finder { /* Inputs */ char *file; /* File name */ int line; /* Line number */ char *function; /* Function name */ int offset; /* Offset bytes */ Dwarf_Addr addr; /* Address */ int nr_args; /* Number of arguments */ char **args; /* Arguments */ /* Working area */ Dwarf_Addr cu_base; /* Current CU base address */ Dwarf_Locdesc fbloc; /* Location of Current Frame Base */ Dwarf_Unsigned fno; /* File number */ Dwarf_Off inl_offs; /* Inline offset */ const char *var; /* Current variable name */ /* Output */ int found; /* Number of found probe points */ }; /* Find a probe point */ static void find_probepoint(struct probe_finder *pf); /* Session management structure */ static struct { char *kver; char *modpath; int maxprobe; struct probe_finder finder; Dwarf_Debug dbg; Dwarf_Error err; int nfound; } session; static void usage(const char *msg) { if (msg) printf("%s\n\n", msg); printf("c2kpe: C expression to kprobe event converter\n"); printf("Usage: c2kpe [-r KREL] [-m mod|vmlinux] FUNC|SRC [ARG...]\n"); printf(" FUNC:\tFUNCNAME[+OFFS_BYTE][@SRCPATH]\n"); printf(" SRC:\t@SRCPATH:LINE\n"); printf(" ARG:\tLocal variable name\n"); /* TODO: @global, $vars, $params */ exit(0); } static void semantic_error(const char *msg) { fprintf(stderr, "Semantic error: %s\n", msg); exit(1); } static void parse_probe_point(int argc, char *argv[], struct probe_finder *pf) { char *arg; char *ptr; arg = argv[0]; if (arg[0] == '@') { /* Source Line */ arg++; ptr = strchr(arg, ':'); if (!ptr || ptr[1] == '\0') semantic_error("Line number is required."); *ptr++ = '\0'; pf->file = arg; if (strlen(arg) == 0) semantic_error("No file name."); pf->line = atoi(ptr); debug("file:%s line:%d\n", pf->file, pf->line); } else { /* Function name */ pf->function = arg; ptr = strchr(arg, '+'); if (ptr) { if (ptr[1] == '\0' || ptr[1] == '@') semantic_error("Offset is required."); *ptr++ = '\0'; pf->offset = atoi(ptr); arg = ptr; } ptr = strchr(arg, '@'); if (ptr) { *ptr++ = '\0'; pf->file = ptr; } debug("fname:%s file:%s offset:%d\n", pf->function, pf->file, pf->offset); } pf->nr_args = argc - 1; if (pf->nr_args > 0) pf->args = &argv[1]; debug("%d arguments\n", pf->nr_args); } static void parse_args(int argc, char *argv[]) { int opt; if (argc < 1) usage("Need a probe point."); while ((opt = getopt(argc, argv, "r:m:n:")) != -1) { switch (opt) { case 'r': session.kver = optarg; break; case 'm': session.modpath = optarg; break; case 'n': session.maxprobe = atoi(optarg); break; default: usage("Unexpected option found."); } } if (optind >= argc) usage("Need a probe point."); parse_probe_point(argc - optind, &argv[optind], &session.finder); } static int open_default_vmlinux(const char *kver) { struct utsname uts; char fname[MAX_PATH_LEN]; int fd, ret, i; if (!kver) { ret = uname(&uts); if (ret) { debug("uname() failed.\n"); return -errno; } kver = uts.release; } for (i = 0; i < NR_SEARCH_PATH; i++) { ret = snprintf(fname, MAX_PATH_LEN, default_search_path[i], kver); if (ret >= MAX_PATH_LEN || ret < 0) { debug("Filename(%d,%s) is too long.\n", i, uts.release); errno = E2BIG; return -E2BIG; } debug("try to open %s\n", fname); fd = open(fname, O_RDONLY); if (fd >= 0) break; } return fd; } int main(int argc, char *argv[]) { int fd, ret; parse_args(argc, argv); if (session.modpath) fd = open(session.modpath, O_RDONLY); else fd = open_default_vmlinux(session.kver); if (fd < 0) { perror("file open"); exit(1); } /* TODO: handle errors */ ret = dwarf_init(fd, DW_DLC_READ, 0, 0, &session.dbg, &session.err); if (ret != DW_DLV_OK) { fprintf(stderr, "Failed to call dwarf_init(). " "Maybe, not a dwarf file?\n"); exit(1); } find_probepoint(&session.finder); ret = dwarf_finish(session.dbg, &session.err); ERR_IF(ret != DW_DLV_OK); close(fd); return 0; } /*--------------------- * Dwarf Analysys Part *---------------------*/ /* * 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 die_get_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, &session.err); if (ret == DW_DLV_OK) { for (i = 0; i < cnt && !found; i++) { if (strtailcmp(srcs[i], fname) == 0) found = i + 1; dwarf_dealloc(session.dbg, srcs[i], DW_DLA_STRING); } for (;i < cnt; i++) dwarf_dealloc(session.dbg, srcs[i], DW_DLA_STRING); dwarf_dealloc(session.dbg, srcs, DW_DLA_LIST); } if (found) debug("found fno: %d\n", (int)found); return found; } /* Compare diename and tname */ static int die_compare_name(Dwarf_Die die, const char *tname) { char *name; int ret; ret = dwarf_diename(die, &name, &session.err); ERR_IF(ret == DW_DLV_ERROR); if (ret == DW_DLV_OK) { //debug("diename: %s\n", name); ret = strcmp(tname, name); dwarf_dealloc(session.dbg, 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; ret = dwarf_lowpc(sp_die, &lopc, &session.err); ERR_IF(ret == DW_DLV_ERROR); if (ret == DW_DLV_NO_ENTRY) return 0; ret = dwarf_highpc(sp_die, &hipc, &session.err); ERR_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 die) { /* TODO: check strictly */ Dwarf_Bool inl; int ret; ret = dwarf_hasattr(die, DW_AT_inline, &inl, &session.err); ERR_IF(ret == DW_DLV_ERROR); return inl; } /* Get the offset of abstruct_origin */ static Dwarf_Off die_get_abstract_origin(Dwarf_Die die) { Dwarf_Attribute attr; Dwarf_Off cu_offs; int ret; ret = dwarf_attr(die, DW_AT_abstract_origin, &attr, &session.err); ERR_IF(ret != DW_DLV_OK); ret = dwarf_formref(attr, &cu_offs, &session.err); ERR_IF(ret != DW_DLV_OK); dwarf_dealloc(session.dbg, 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 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(die, DW_AT_entry_pc, &attr, &session.err); ERR_IF(ret == DW_DLV_ERROR); if (ret == DW_DLV_OK) { ret = dwarf_formaddr(attr, &addr, &session.err); ERR_IF(ret != DW_DLV_OK); dwarf_dealloc(session.dbg, attr, DW_DLA_ATTR); return addr; } /* Try to get low pc */ ret = dwarf_lowpc(die, &addr, &session.err); ERR_IF(ret == DW_DLV_ERROR); if (ret == DW_DLV_OK) return addr; /* Try to get ranges */ ret = dwarf_attr(die, DW_AT_ranges, &attr, &session.err); ERR_IF(ret != DW_DLV_OK); ret = dwarf_formref(attr, &offs, &session.err); ERR_IF(ret != DW_DLV_OK); ret = dwarf_get_ranges(session.dbg, offs, &ranges, &cnt, NULL, &session.err); ERR_IF(ret != DW_DLV_OK); addr = ranges[0].dwr_addr1; dwarf_ranges_dealloc(session.dbg, 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 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, &session.err); ERR_IF(ret == DW_DLV_ERROR); if (ret == DW_DLV_OK) { link.parent = cur_link; link.die = new_die; ret = __search_die_tree(&link, die_cb, data); if (ret) break; } /* Move to next sibling */ ret = dwarf_siblingof(session.dbg, cur_link->die, &new_die, &session.err); ERR_IF(ret == DW_DLV_ERROR); dwarf_dealloc(session.dbg, cur_link->die, DW_DLA_DIE); cur_link->die = new_die; if (ret == DW_DLV_NO_ENTRY) return 0; } dwarf_dealloc(session.dbg, 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 link; int ret; link.parent = NULL; ret = dwarf_child(parent_die, &link.die, &session.err); ERR_IF(ret == DW_DLV_ERROR); if (ret == DW_DLV_OK) return __search_die_tree(&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, &session.err); ERR_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); ERR_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(session.dbg, llbuf[i]->ld_s, DW_DLA_LOC_BLOCK); dwarf_dealloc(session.dbg, llbuf[i], DW_DLA_LOCDESC); } /* Releasing loop */ for (; i < lcnt; ++i) { dwarf_dealloc(session.dbg, llbuf[i]->ld_s, DW_DLA_LOC_BLOCK); dwarf_dealloc(session.dbg, llbuf[i], DW_DLA_LOCDESC); } dwarf_dealloc(session.dbg, llbuf, DW_DLA_LIST); return ret; } /*--------------------------------- * 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; 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 { fprintf(stderr, "Error: Dwarf_OP %d is not supported.\n", op); exit(1); } regs = get_arch_regstr(regn); if (!regs) { fprintf(stderr, "Error: %lld exceeds max register number.\n", regn); exit(1); } if (deref) printf(" %+lld(%s)", offs, regs); else printf(" %s", regs); } /* 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, &session.err); ERR_IF(ret != DW_DLV_OK); ret = attr_get_locdesc(attr, &ld, (pf->addr - pf->cu_base)); ERR_IF(ret != DW_DLV_OK); /* TODO? */ if (ld.ld_cents != 1) { fprintf(stderr, "This variable type is not supported.\n"); exit(1); } show_location(&ld.ld_s[0], pf); free(ld.ld_s); dwarf_dealloc(session.dbg, attr, DW_DLA_ATTR); } static int variable_callback(struct die_link *link, void *data) { struct probe_finder *pf = (struct probe_finder *)data; Dwarf_Half tag; int ret; ret = dwarf_tag(link->die, &tag, &session.err); ERR_IF(ret == DW_DLV_ERROR); if ((tag == DW_TAG_formal_parameter || tag == DW_TAG_variable) && (die_compare_name(link->die, pf->var) == 0)) { show_variable(link->die, pf); return 1; } /* TODO: Support struct members */ return 0; } /* Find a variable in a subprogram die */ static void find_variable(const char *var, Dwarf_Die sp_die, struct probe_finder *pf) { int ret; debug("Searching %s variable in context.\n", var); pf->var = var; /* Search child die for local variables and parameters. */ ret = search_die_from_children(sp_die, variable_callback, pf); if (!ret) { fprintf(stderr, "\nFailed to find %s in this function.\n", var); exit(1); } } /* 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, &session.err); ERR_IF(ret != DW_DLV_OK); ret = attr_get_locdesc(attr, &pf->fbloc, (pf->addr - pf->cu_base)); ERR_IF(ret != DW_DLV_OK); dwarf_dealloc(session.dbg, 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 stdout */ static void show_probepoint(Dwarf_Die sp_die, Dwarf_Signed offs, struct probe_finder *pf) { char *name; int ret, i; ret = dwarf_diename(sp_die, &name, &session.err); ERR_IF(ret == DW_DLV_ERROR); if (ret == DW_DLV_OK) { printf("%s%+d", name, (int)offs); dwarf_dealloc(session.dbg, name, DW_DLA_STRING); } else { /* This function has no name. */ printf("0x%llx", pf->addr); } get_current_frame_base(sp_die, pf); for (i = 0; i < pf->nr_args; i++) find_variable(pf->args[i], sp_die, pf); free_current_frame_base(pf); printf("\n"); pf->found++; } static int probeaddr_callback(struct die_link *link, void *data) { struct probe_finder *pf = (struct probe_finder *)data; Dwarf_Half tag; Dwarf_Signed offs; int ret; ret = dwarf_tag(link->die, &tag, &session.err); ERR_IF(ret == DW_DLV_ERROR); if (tag == DW_TAG_subprogram && die_within_subprogram(link->die, pf->addr, &offs)) { show_probepoint(link->die, offs, pf); return 1; } return 0; } /* Find probe point from its line number */ static void find_by_line(Dwarf_Die cu_die, struct probe_finder *pf) { Dwarf_Signed cnt, i; Dwarf_Line *lines; Dwarf_Unsigned lineno = 0; Dwarf_Addr addr; Dwarf_Unsigned fno; int ret; ret = dwarf_srclines(cu_die, &lines, &cnt, &session.err); ERR_IF(ret != DW_DLV_OK); for (i = 0; i < cnt; i++) { ret = dwarf_line_srcfileno(lines[i], &fno, &session.err); ERR_IF(ret != DW_DLV_OK); if (fno != pf->fno) continue; ret = dwarf_lineno(lines[i], &lineno, &session.err); ERR_IF(ret != DW_DLV_OK); if (lineno != pf->line) continue; ret = dwarf_lineaddr(lines[i], &addr, &session.err); ERR_IF(ret != DW_DLV_OK); debug("Probe point found: 0x%llx\n", addr); pf->addr = addr; /* Search a real subprogram including this line, */ ret = search_die_from_children(cu_die, probeaddr_callback, pf); if (ret == 0) { fprintf(stderr, "Probe point is not found in subprograms.\n"); exit(1); } /* Continuing, because target line might be inlined. */ } dwarf_srclines_dealloc(session.dbg, lines, cnt); } /* Search function from function name */ static int probefunc_callback(struct die_link *link, void *data) { struct probe_finder *pf = (struct probe_finder *)data; struct die_link *lk; Dwarf_Signed offs; Dwarf_Half tag; int ret; ret = dwarf_tag(link->die, &tag, &session.err); ERR_IF(ret == DW_DLV_ERROR); if (tag == DW_TAG_subprogram) { if (die_compare_name(link->die, pf->function) == 0) { if (die_inlined_subprogram(link->die)) { /* Inlined function, save it. */ ret = dwarf_die_CU_offset(link->die, &pf->inl_offs, &session.err); ERR_IF(ret != DW_DLV_OK); debug("inline definition offset %lld\n", pf->inl_offs); return 0; } dwarf_lowpc(link->die, &pf->addr, &session.err); pf->addr += pf->offset; /* TODO: Check the address in this function */ show_probepoint(link->die, pf->offset, pf); /* Continue to search */ } } else if (tag == DW_TAG_inlined_subroutine && pf->inl_offs) { if (die_get_abstract_origin(link->die) == pf->inl_offs) { pf->addr = die_get_entrypc(link->die); pf->addr += pf->offset; debug("found inline addr: 0x%llx\n", pf->addr); /* Inlined function. Get a real subprogram */ for (lk = link->parent; lk != NULL; lk = lk->parent) { tag = 0; dwarf_tag(lk->die, &tag, &session.err); ERR_IF(ret == DW_DLV_ERROR); if (tag == DW_TAG_subprogram && !die_inlined_subprogram(lk->die)) goto found; } fprintf(stderr, "Failed to find real subprogram.\n"); exit(1); found: ret = die_within_subprogram(lk->die, pf->addr, &offs); ERR_IF(!ret); show_probepoint(lk->die, offs, pf); /* Continue to search */ } } return 0; } static void find_by_func(Dwarf_Die cu_die, struct probe_finder *pf) { search_die_from_children(cu_die, probefunc_callback, pf); } static void find_probepoint(struct probe_finder *pf) { Dwarf_Unsigned cuh_len = 0; Dwarf_Half vstamp = 0; Dwarf_Unsigned abbrev = 0; Dwarf_Half addr_size = 0; Dwarf_Unsigned next_cuh = 0; Dwarf_Die cu_die = 0; int cu_number = 0, ret; pf->found = 0; while (++cu_number) { /* Search CU (Compilation Unit) */ ret = dwarf_next_cu_header(session.dbg, &cuh_len, &vstamp, &abbrev, &addr_size, &next_cuh, &session.err); ERR_IF(ret == DW_DLV_ERROR); if (ret == DW_DLV_NO_ENTRY) break; /* Get the DIE(Debugging Information Entry) of this CU */ ret = dwarf_siblingof(session.dbg, 0, &cu_die, &session.err); ERR_IF(ret != DW_DLV_OK); /* Check if target file is included. */ if (pf->file) pf->fno = die_get_fileno(cu_die, pf->file); if (!pf->file || pf->fno) { /* Save CU base address (for frame_base) */ ret = dwarf_lowpc(cu_die, &pf->cu_base, &session.err); ERR_IF(ret == DW_DLV_ERROR); if (ret == DW_DLV_NO_ENTRY) pf->cu_base = 0; if (pf->line) find_by_line(cu_die, pf); if (pf->function) find_by_func(cu_die, pf); } dwarf_dealloc(session.dbg, cu_die, DW_DLA_DIE); } if (pf->found == 0) { fprintf(stderr, "Probe point is not found.\n"); exit(1); } }