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[+off...@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: [email protected]

/*
 * c2kpe : C expression to kprobe event converter
 *
 * Written by Masami Hiramatsu <[email protected]>
 *
 * 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_byt...@srcpath]\n");
        printf(" SRC:\...@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);
        }
}

Reply via email to