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

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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


[Index of Archives]     [KVM ARM]     [KVM ia64]     [KVM ppc]     [Virtualization Tools]     [Spice Development]     [Libvirt]     [Libvirt Users]     [Linux USB Devel]     [Linux Audio Users]     [Yosemite Questions]     [Linux Kernel]     [Linux SCSI]     [XFree86]
  Powered by Linux