[TOOL] c2kpe: C expression to kprobe event format converter

From: Masami Hiramatsu
Date: Thu Aug 13 2009 - 16:56:56 EST


This program converts probe point in C expression to kprobe event
format for kprobe-based event tracer. This helps to define kprobes
events by C source line number or function name, and local variable
name. Currently, this supports only x86(32/64) kernels.


Compile
--------
Before compilation, please install libelf and libdwarf development
packages.
(e.g. elfutils-libelf-devel and libdwarf-devel on Fedora)

$ gcc -Wall -lelf -ldwarf c2kpe.c -o c2kpe


Synopsis
--------
$ c2kpe [options] FUNCTION[+OFFS][@SRC] [VAR [VAR ...]]
or
$ c2kpe [options] @SRC:LINE [VAR [VAR ...]]

FUNCTION: Probing function name.
OFFS: Offset in bytes.
SRC: Source file path.
LINE: Line number
VAR: Local variable name.
options:
-r KREL Kernel release version (e.g. 2.6.31-rc5)
-m DEBUGINFO Dwarf-format binary file (vmlinux or kmodule)


Example
-------
$ c2kpe sys_read fd buf count
sys_read+0 %di %si %dx

$ c2kpe @mm/filemap.c:339 inode pos
sync_page_range+125 -48(%bp) %r14


Example with kprobe-tracer
--------------------------
Since C expression may be converted multiple results, I recommend to use
readline.

$ c2kpe sys_read fd buf count | while read i; do \
echo "p $i" > $DEBUGFS/tracing/kprobe_events ;\
done


Note
----
- This requires a kernel compiled with CONFIG_DEBUG_INFO.
- Specifying @SRC speeds up c2kpe, because we can skip CUs which don't
include specified SRC file.
- c2kpe doesn't check whether the offset byte is correctly on the
instruction boundary. I recommend you to use @SRC:LINE expression for
tracing function body.
- This tool doesn't search kmodule file. You need to specify kmodule
file if you want to probe it.


TODO
----
- Fix bugs.
- Support multiple probepoints from stdin.
- Better kmodule support.
- Use elfutils-libdw?
- Merge into trace-cmd or perf-tools?

--
Masami Hiramatsu

Software Engineer
Hitachi Computer Products (America), Inc.
Software Solutions Division

e-mail: mhiramat@xxxxxxxxxx

/*
* c2kpe : C expression to kprobe event converter
*
* Written by Masami Hiramatsu <mhiramat@xxxxxxxxxx>
*
* 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 <sys/utsname.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <stdio.h>
#include <unistd.h>
#include <getopt.h>
#include <stdlib.h>
#include <string.h>
#include <libdwarf/dwarf.h>
#include <libdwarf/libdwarf.h>

/* 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);
}
}