Merge branch 'tip/perf/core' of git://git.kernel.org/pub/scm/linux/kernel/git/rostedt/linux-2.6-trace into perf/core
This commit is contained in:
commit
40bcea7bbe
|
@ -22,14 +22,15 @@ current_tracer. Instead of that, add probe points via
|
|||
|
||||
Synopsis of kprobe_events
|
||||
-------------------------
|
||||
p[:[GRP/]EVENT] SYMBOL[+offs]|MEMADDR [FETCHARGS] : Set a probe
|
||||
r[:[GRP/]EVENT] SYMBOL[+0] [FETCHARGS] : Set a return probe
|
||||
p[:[GRP/]EVENT] [MOD:]SYM[+offs]|MEMADDR [FETCHARGS] : Set a probe
|
||||
r[:[GRP/]EVENT] [MOD:]SYM[+0] [FETCHARGS] : Set a return probe
|
||||
-:[GRP/]EVENT : Clear a probe
|
||||
|
||||
GRP : Group name. If omitted, use "kprobes" for it.
|
||||
EVENT : Event name. If omitted, the event name is generated
|
||||
based on SYMBOL+offs or MEMADDR.
|
||||
SYMBOL[+offs] : Symbol+offset where the probe is inserted.
|
||||
based on SYM+offs or MEMADDR.
|
||||
MOD : Module name which has given SYM.
|
||||
SYM[+offs] : Symbol+offset where the probe is inserted.
|
||||
MEMADDR : Address where the probe is inserted.
|
||||
|
||||
FETCHARGS : Arguments. Each probe can have up to 128 args.
|
||||
|
|
|
@ -101,6 +101,14 @@
|
|||
#define P4_CONFIG_HT_SHIFT 63
|
||||
#define P4_CONFIG_HT (1ULL << P4_CONFIG_HT_SHIFT)
|
||||
|
||||
/*
|
||||
* If an event has alias it should be marked
|
||||
* with a special bit. (Don't forget to check
|
||||
* P4_PEBS_CONFIG_MASK and related bits on
|
||||
* modification.)
|
||||
*/
|
||||
#define P4_CONFIG_ALIASABLE (1 << 9)
|
||||
|
||||
/*
|
||||
* The bits we allow to pass for RAW events
|
||||
*/
|
||||
|
@ -123,6 +131,31 @@
|
|||
(p4_config_pack_escr(P4_CONFIG_MASK_ESCR)) | \
|
||||
(p4_config_pack_cccr(P4_CONFIG_MASK_CCCR))
|
||||
|
||||
/*
|
||||
* In case of event aliasing we need to preserve some
|
||||
* caller bits otherwise the mapping won't be complete.
|
||||
*/
|
||||
#define P4_CONFIG_EVENT_ALIAS_MASK \
|
||||
(p4_config_pack_escr(P4_CONFIG_MASK_ESCR) | \
|
||||
p4_config_pack_cccr(P4_CCCR_EDGE | \
|
||||
P4_CCCR_THRESHOLD_MASK | \
|
||||
P4_CCCR_COMPLEMENT | \
|
||||
P4_CCCR_COMPARE))
|
||||
|
||||
#define P4_CONFIG_EVENT_ALIAS_IMMUTABLE_BITS \
|
||||
((P4_CONFIG_HT) | \
|
||||
p4_config_pack_escr(P4_ESCR_T0_OS | \
|
||||
P4_ESCR_T0_USR | \
|
||||
P4_ESCR_T1_OS | \
|
||||
P4_ESCR_T1_USR) | \
|
||||
p4_config_pack_cccr(P4_CCCR_OVF | \
|
||||
P4_CCCR_CASCADE | \
|
||||
P4_CCCR_FORCE_OVF | \
|
||||
P4_CCCR_THREAD_ANY | \
|
||||
P4_CCCR_OVF_PMI_T0 | \
|
||||
P4_CCCR_OVF_PMI_T1 | \
|
||||
P4_CONFIG_ALIASABLE))
|
||||
|
||||
static inline bool p4_is_event_cascaded(u64 config)
|
||||
{
|
||||
u32 cccr = p4_config_unpack_cccr(config);
|
||||
|
|
|
@ -274,7 +274,6 @@ struct x86_pmu {
|
|||
void (*enable_all)(int added);
|
||||
void (*enable)(struct perf_event *);
|
||||
void (*disable)(struct perf_event *);
|
||||
void (*hw_watchdog_set_attr)(struct perf_event_attr *attr);
|
||||
int (*hw_config)(struct perf_event *event);
|
||||
int (*schedule_events)(struct cpu_hw_events *cpuc, int n, int *assign);
|
||||
unsigned eventsel;
|
||||
|
@ -360,12 +359,6 @@ static u64 __read_mostly hw_cache_extra_regs
|
|||
[PERF_COUNT_HW_CACHE_OP_MAX]
|
||||
[PERF_COUNT_HW_CACHE_RESULT_MAX];
|
||||
|
||||
void hw_nmi_watchdog_set_attr(struct perf_event_attr *wd_attr)
|
||||
{
|
||||
if (x86_pmu.hw_watchdog_set_attr)
|
||||
x86_pmu.hw_watchdog_set_attr(wd_attr);
|
||||
}
|
||||
|
||||
/*
|
||||
* Propagate event elapsed time into the generic event.
|
||||
* Can only be executed on the CPU where the event is active.
|
||||
|
|
|
@ -570,11 +570,92 @@ static __initconst const u64 p4_hw_cache_event_ids
|
|||
},
|
||||
};
|
||||
|
||||
/*
|
||||
* Because of Netburst being quite restricted in now
|
||||
* many same events can run simultaneously, we use
|
||||
* event aliases, ie different events which have the
|
||||
* same functionallity but use non-intersected resources
|
||||
* (ESCR/CCCR/couter registers). This allow us to run
|
||||
* two or more semi-same events together. It is done
|
||||
* transparently to a user space.
|
||||
*
|
||||
* Never set any cusom internal bits such as P4_CONFIG_HT,
|
||||
* P4_CONFIG_ALIASABLE or bits for P4_PEBS_METRIC, they are
|
||||
* either up-to-dated automatically either not appliable
|
||||
* at all.
|
||||
*
|
||||
* And be really carefull choosing aliases!
|
||||
*/
|
||||
struct p4_event_alias {
|
||||
u64 orig;
|
||||
u64 alter;
|
||||
} p4_event_aliases[] = {
|
||||
{
|
||||
/*
|
||||
* Non-halted cycles can be substituted with
|
||||
* non-sleeping cycles (see Intel SDM Vol3b for
|
||||
* details).
|
||||
*/
|
||||
.orig =
|
||||
p4_config_pack_escr(P4_ESCR_EVENT(P4_EVENT_GLOBAL_POWER_EVENTS) |
|
||||
P4_ESCR_EMASK_BIT(P4_EVENT_GLOBAL_POWER_EVENTS, RUNNING)),
|
||||
.alter =
|
||||
p4_config_pack_escr(P4_ESCR_EVENT(P4_EVENT_EXECUTION_EVENT) |
|
||||
P4_ESCR_EMASK_BIT(P4_EVENT_EXECUTION_EVENT, NBOGUS0)|
|
||||
P4_ESCR_EMASK_BIT(P4_EVENT_EXECUTION_EVENT, NBOGUS1)|
|
||||
P4_ESCR_EMASK_BIT(P4_EVENT_EXECUTION_EVENT, NBOGUS2)|
|
||||
P4_ESCR_EMASK_BIT(P4_EVENT_EXECUTION_EVENT, NBOGUS3)|
|
||||
P4_ESCR_EMASK_BIT(P4_EVENT_EXECUTION_EVENT, BOGUS0) |
|
||||
P4_ESCR_EMASK_BIT(P4_EVENT_EXECUTION_EVENT, BOGUS1) |
|
||||
P4_ESCR_EMASK_BIT(P4_EVENT_EXECUTION_EVENT, BOGUS2) |
|
||||
P4_ESCR_EMASK_BIT(P4_EVENT_EXECUTION_EVENT, BOGUS3))|
|
||||
p4_config_pack_cccr(P4_CCCR_THRESHOLD(15) | P4_CCCR_COMPLEMENT |
|
||||
P4_CCCR_COMPARE),
|
||||
},
|
||||
};
|
||||
|
||||
static u64 p4_get_alias_event(u64 config)
|
||||
{
|
||||
u64 config_match;
|
||||
int i;
|
||||
|
||||
/*
|
||||
* Probably we're lucky and don't have to do
|
||||
* matching over all config bits.
|
||||
*/
|
||||
if (!(config & P4_CONFIG_ALIASABLE))
|
||||
return 0;
|
||||
|
||||
config_match = config & P4_CONFIG_EVENT_ALIAS_MASK;
|
||||
|
||||
/*
|
||||
* If an event was previously swapped to the alter config
|
||||
* we should swap it back otherwise contnention on registers
|
||||
* will return back.
|
||||
*/
|
||||
for (i = 0; i < ARRAY_SIZE(p4_event_aliases); i++) {
|
||||
if (config_match == p4_event_aliases[i].orig) {
|
||||
config_match = p4_event_aliases[i].alter;
|
||||
break;
|
||||
} else if (config_match == p4_event_aliases[i].alter) {
|
||||
config_match = p4_event_aliases[i].orig;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (i >= ARRAY_SIZE(p4_event_aliases))
|
||||
return 0;
|
||||
|
||||
return config_match |
|
||||
(config & P4_CONFIG_EVENT_ALIAS_IMMUTABLE_BITS);
|
||||
}
|
||||
|
||||
static u64 p4_general_events[PERF_COUNT_HW_MAX] = {
|
||||
/* non-halted CPU clocks */
|
||||
[PERF_COUNT_HW_CPU_CYCLES] =
|
||||
p4_config_pack_escr(P4_ESCR_EVENT(P4_EVENT_GLOBAL_POWER_EVENTS) |
|
||||
P4_ESCR_EMASK_BIT(P4_EVENT_GLOBAL_POWER_EVENTS, RUNNING)),
|
||||
P4_ESCR_EMASK_BIT(P4_EVENT_GLOBAL_POWER_EVENTS, RUNNING)) |
|
||||
P4_CONFIG_ALIASABLE,
|
||||
|
||||
/*
|
||||
* retired instructions
|
||||
|
@ -719,31 +800,6 @@ static int p4_validate_raw_event(struct perf_event *event)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static void p4_hw_watchdog_set_attr(struct perf_event_attr *wd_attr)
|
||||
{
|
||||
/*
|
||||
* Watchdog ticks are special on Netburst, we use
|
||||
* that named "non-sleeping" ticks as recommended
|
||||
* by Intel SDM Vol3b.
|
||||
*/
|
||||
WARN_ON_ONCE(wd_attr->type != PERF_TYPE_HARDWARE ||
|
||||
wd_attr->config != PERF_COUNT_HW_CPU_CYCLES);
|
||||
|
||||
wd_attr->type = PERF_TYPE_RAW;
|
||||
wd_attr->config =
|
||||
p4_config_pack_escr(P4_ESCR_EVENT(P4_EVENT_EXECUTION_EVENT) |
|
||||
P4_ESCR_EMASK_BIT(P4_EVENT_EXECUTION_EVENT, NBOGUS0) |
|
||||
P4_ESCR_EMASK_BIT(P4_EVENT_EXECUTION_EVENT, NBOGUS1) |
|
||||
P4_ESCR_EMASK_BIT(P4_EVENT_EXECUTION_EVENT, NBOGUS2) |
|
||||
P4_ESCR_EMASK_BIT(P4_EVENT_EXECUTION_EVENT, NBOGUS3) |
|
||||
P4_ESCR_EMASK_BIT(P4_EVENT_EXECUTION_EVENT, BOGUS0) |
|
||||
P4_ESCR_EMASK_BIT(P4_EVENT_EXECUTION_EVENT, BOGUS1) |
|
||||
P4_ESCR_EMASK_BIT(P4_EVENT_EXECUTION_EVENT, BOGUS2) |
|
||||
P4_ESCR_EMASK_BIT(P4_EVENT_EXECUTION_EVENT, BOGUS3)) |
|
||||
p4_config_pack_cccr(P4_CCCR_THRESHOLD(15) | P4_CCCR_COMPLEMENT |
|
||||
P4_CCCR_COMPARE);
|
||||
}
|
||||
|
||||
static int p4_hw_config(struct perf_event *event)
|
||||
{
|
||||
int cpu = get_cpu();
|
||||
|
@ -1159,6 +1215,8 @@ static int p4_pmu_schedule_events(struct cpu_hw_events *cpuc, int n, int *assign
|
|||
struct p4_event_bind *bind;
|
||||
unsigned int i, thread, num;
|
||||
int cntr_idx, escr_idx;
|
||||
u64 config_alias;
|
||||
int pass;
|
||||
|
||||
bitmap_zero(used_mask, X86_PMC_IDX_MAX);
|
||||
bitmap_zero(escr_mask, P4_ESCR_MSR_TABLE_SIZE);
|
||||
|
@ -1167,6 +1225,17 @@ static int p4_pmu_schedule_events(struct cpu_hw_events *cpuc, int n, int *assign
|
|||
|
||||
hwc = &cpuc->event_list[i]->hw;
|
||||
thread = p4_ht_thread(cpu);
|
||||
pass = 0;
|
||||
|
||||
again:
|
||||
/*
|
||||
* Aliases are swappable so we may hit circular
|
||||
* lock if both original config and alias need
|
||||
* resources (MSR registers) which already busy.
|
||||
*/
|
||||
if (pass > 2)
|
||||
goto done;
|
||||
|
||||
bind = p4_config_get_bind(hwc->config);
|
||||
escr_idx = p4_get_escr_idx(bind->escr_msr[thread]);
|
||||
if (unlikely(escr_idx == -1))
|
||||
|
@ -1180,8 +1249,17 @@ static int p4_pmu_schedule_events(struct cpu_hw_events *cpuc, int n, int *assign
|
|||
}
|
||||
|
||||
cntr_idx = p4_next_cntr(thread, used_mask, bind);
|
||||
if (cntr_idx == -1 || test_bit(escr_idx, escr_mask))
|
||||
goto done;
|
||||
if (cntr_idx == -1 || test_bit(escr_idx, escr_mask)) {
|
||||
/*
|
||||
* Probably an event alias is still available.
|
||||
*/
|
||||
config_alias = p4_get_alias_event(hwc->config);
|
||||
if (!config_alias)
|
||||
goto done;
|
||||
hwc->config = config_alias;
|
||||
pass++;
|
||||
goto again;
|
||||
}
|
||||
|
||||
p4_pmu_swap_config_ts(hwc, cpu);
|
||||
if (assign)
|
||||
|
@ -1218,7 +1296,6 @@ static __initconst const struct x86_pmu p4_pmu = {
|
|||
.cntval_bits = ARCH_P4_CNTRVAL_BITS,
|
||||
.cntval_mask = ARCH_P4_CNTRVAL_MASK,
|
||||
.max_period = (1ULL << (ARCH_P4_CNTRVAL_BITS - 1)) - 1,
|
||||
.hw_watchdog_set_attr = p4_hw_watchdog_set_attr,
|
||||
.hw_config = p4_hw_config,
|
||||
.schedule_events = p4_pmu_schedule_events,
|
||||
/*
|
||||
|
|
|
@ -76,6 +76,7 @@ struct trace_iterator {
|
|||
struct trace_entry *ent;
|
||||
unsigned long lost_events;
|
||||
int leftover;
|
||||
int ent_size;
|
||||
int cpu;
|
||||
u64 ts;
|
||||
|
||||
|
|
|
@ -1255,19 +1255,29 @@ static int __kprobes in_kprobes_functions(unsigned long addr)
|
|||
/*
|
||||
* If we have a symbol_name argument, look it up and add the offset field
|
||||
* to it. This way, we can specify a relative address to a symbol.
|
||||
* This returns encoded errors if it fails to look up symbol or invalid
|
||||
* combination of parameters.
|
||||
*/
|
||||
static kprobe_opcode_t __kprobes *kprobe_addr(struct kprobe *p)
|
||||
{
|
||||
kprobe_opcode_t *addr = p->addr;
|
||||
|
||||
if ((p->symbol_name && p->addr) ||
|
||||
(!p->symbol_name && !p->addr))
|
||||
goto invalid;
|
||||
|
||||
if (p->symbol_name) {
|
||||
if (addr)
|
||||
return NULL;
|
||||
kprobe_lookup_name(p->symbol_name, addr);
|
||||
if (!addr)
|
||||
return ERR_PTR(-ENOENT);
|
||||
}
|
||||
|
||||
if (!addr)
|
||||
return NULL;
|
||||
return (kprobe_opcode_t *)(((char *)addr) + p->offset);
|
||||
addr = (kprobe_opcode_t *)(((char *)addr) + p->offset);
|
||||
if (addr)
|
||||
return addr;
|
||||
|
||||
invalid:
|
||||
return ERR_PTR(-EINVAL);
|
||||
}
|
||||
|
||||
/* Check passed kprobe is valid and return kprobe in kprobe_table. */
|
||||
|
@ -1311,8 +1321,8 @@ int __kprobes register_kprobe(struct kprobe *p)
|
|||
kprobe_opcode_t *addr;
|
||||
|
||||
addr = kprobe_addr(p);
|
||||
if (!addr)
|
||||
return -EINVAL;
|
||||
if (IS_ERR(addr))
|
||||
return PTR_ERR(addr);
|
||||
p->addr = addr;
|
||||
|
||||
ret = check_kprobe_rereg(p);
|
||||
|
@ -1335,6 +1345,8 @@ int __kprobes register_kprobe(struct kprobe *p)
|
|||
*/
|
||||
probed_mod = __module_text_address((unsigned long) p->addr);
|
||||
if (probed_mod) {
|
||||
/* Return -ENOENT if fail. */
|
||||
ret = -ENOENT;
|
||||
/*
|
||||
* We must hold a refcount of the probed module while updating
|
||||
* its code to prohibit unexpected unloading.
|
||||
|
@ -1351,6 +1363,7 @@ int __kprobes register_kprobe(struct kprobe *p)
|
|||
module_put(probed_mod);
|
||||
goto fail_with_jump_label;
|
||||
}
|
||||
/* ret will be updated by following code */
|
||||
}
|
||||
preempt_enable();
|
||||
jump_label_unlock();
|
||||
|
@ -1399,7 +1412,7 @@ out:
|
|||
fail_with_jump_label:
|
||||
preempt_enable();
|
||||
jump_label_unlock();
|
||||
return -EINVAL;
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(register_kprobe);
|
||||
|
||||
|
@ -1686,8 +1699,8 @@ int __kprobes register_kretprobe(struct kretprobe *rp)
|
|||
|
||||
if (kretprobe_blacklist_size) {
|
||||
addr = kprobe_addr(&rp->kp);
|
||||
if (!addr)
|
||||
return -EINVAL;
|
||||
if (IS_ERR(addr))
|
||||
return PTR_ERR(addr);
|
||||
|
||||
for (i = 0; kretprobe_blacklist[i].name != NULL; i++) {
|
||||
if (kretprobe_blacklist[i].addr == addr)
|
||||
|
|
|
@ -88,6 +88,7 @@ static struct ftrace_ops ftrace_list_end __read_mostly = {
|
|||
static struct ftrace_ops *ftrace_global_list __read_mostly = &ftrace_list_end;
|
||||
static struct ftrace_ops *ftrace_ops_list __read_mostly = &ftrace_list_end;
|
||||
ftrace_func_t ftrace_trace_function __read_mostly = ftrace_stub;
|
||||
static ftrace_func_t __ftrace_trace_function_delay __read_mostly = ftrace_stub;
|
||||
ftrace_func_t __ftrace_trace_function __read_mostly = ftrace_stub;
|
||||
ftrace_func_t ftrace_pid_function __read_mostly = ftrace_stub;
|
||||
static struct ftrace_ops global_ops;
|
||||
|
@ -146,9 +147,11 @@ void clear_ftrace_function(void)
|
|||
{
|
||||
ftrace_trace_function = ftrace_stub;
|
||||
__ftrace_trace_function = ftrace_stub;
|
||||
__ftrace_trace_function_delay = ftrace_stub;
|
||||
ftrace_pid_function = ftrace_stub;
|
||||
}
|
||||
|
||||
#undef CONFIG_HAVE_FUNCTION_TRACE_MCOUNT_TEST
|
||||
#ifndef CONFIG_HAVE_FUNCTION_TRACE_MCOUNT_TEST
|
||||
/*
|
||||
* For those archs that do not test ftrace_trace_stop in their
|
||||
|
@ -207,8 +210,13 @@ static void update_ftrace_function(void)
|
|||
|
||||
#ifdef CONFIG_HAVE_FUNCTION_TRACE_MCOUNT_TEST
|
||||
ftrace_trace_function = func;
|
||||
#else
|
||||
#ifdef CONFIG_DYNAMIC_FTRACE
|
||||
/* do not update till all functions have been modified */
|
||||
__ftrace_trace_function_delay = func;
|
||||
#else
|
||||
__ftrace_trace_function = func;
|
||||
#endif
|
||||
ftrace_trace_function = ftrace_test_stop_func;
|
||||
#endif
|
||||
}
|
||||
|
@ -1170,8 +1178,14 @@ alloc_and_copy_ftrace_hash(int size_bits, struct ftrace_hash *hash)
|
|||
return NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
ftrace_hash_rec_disable(struct ftrace_ops *ops, int filter_hash);
|
||||
static void
|
||||
ftrace_hash_rec_enable(struct ftrace_ops *ops, int filter_hash);
|
||||
|
||||
static int
|
||||
ftrace_hash_move(struct ftrace_hash **dst, struct ftrace_hash *src)
|
||||
ftrace_hash_move(struct ftrace_ops *ops, int enable,
|
||||
struct ftrace_hash **dst, struct ftrace_hash *src)
|
||||
{
|
||||
struct ftrace_func_entry *entry;
|
||||
struct hlist_node *tp, *tn;
|
||||
|
@ -1181,8 +1195,15 @@ ftrace_hash_move(struct ftrace_hash **dst, struct ftrace_hash *src)
|
|||
unsigned long key;
|
||||
int size = src->count;
|
||||
int bits = 0;
|
||||
int ret;
|
||||
int i;
|
||||
|
||||
/*
|
||||
* Remove the current set, update the hash and add
|
||||
* them back.
|
||||
*/
|
||||
ftrace_hash_rec_disable(ops, enable);
|
||||
|
||||
/*
|
||||
* If the new source is empty, just free dst and assign it
|
||||
* the empty_hash.
|
||||
|
@ -1203,9 +1224,10 @@ ftrace_hash_move(struct ftrace_hash **dst, struct ftrace_hash *src)
|
|||
if (bits > FTRACE_HASH_MAX_BITS)
|
||||
bits = FTRACE_HASH_MAX_BITS;
|
||||
|
||||
ret = -ENOMEM;
|
||||
new_hash = alloc_ftrace_hash(bits);
|
||||
if (!new_hash)
|
||||
return -ENOMEM;
|
||||
goto out;
|
||||
|
||||
size = 1 << src->size_bits;
|
||||
for (i = 0; i < size; i++) {
|
||||
|
@ -1224,7 +1246,16 @@ ftrace_hash_move(struct ftrace_hash **dst, struct ftrace_hash *src)
|
|||
rcu_assign_pointer(*dst, new_hash);
|
||||
free_ftrace_hash_rcu(old_hash);
|
||||
|
||||
return 0;
|
||||
ret = 0;
|
||||
out:
|
||||
/*
|
||||
* Enable regardless of ret:
|
||||
* On success, we enable the new hash.
|
||||
* On failure, we re-enable the original hash.
|
||||
*/
|
||||
ftrace_hash_rec_enable(ops, enable);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -1584,6 +1615,12 @@ static int __ftrace_modify_code(void *data)
|
|||
{
|
||||
int *command = data;
|
||||
|
||||
/*
|
||||
* Do not call function tracer while we update the code.
|
||||
* We are in stop machine, no worrying about races.
|
||||
*/
|
||||
function_trace_stop++;
|
||||
|
||||
if (*command & FTRACE_ENABLE_CALLS)
|
||||
ftrace_replace_code(1);
|
||||
else if (*command & FTRACE_DISABLE_CALLS)
|
||||
|
@ -1597,6 +1634,18 @@ static int __ftrace_modify_code(void *data)
|
|||
else if (*command & FTRACE_STOP_FUNC_RET)
|
||||
ftrace_disable_ftrace_graph_caller();
|
||||
|
||||
#ifndef CONFIG_HAVE_FUNCTION_TRACE_MCOUNT_TEST
|
||||
/*
|
||||
* For archs that call ftrace_test_stop_func(), we must
|
||||
* wait till after we update all the function callers
|
||||
* before we update the callback. This keeps different
|
||||
* ops that record different functions from corrupting
|
||||
* each other.
|
||||
*/
|
||||
__ftrace_trace_function = __ftrace_trace_function_delay;
|
||||
#endif
|
||||
function_trace_stop--;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -2865,7 +2914,11 @@ ftrace_set_regex(struct ftrace_ops *ops, unsigned char *buf, int len,
|
|||
ftrace_match_records(hash, buf, len);
|
||||
|
||||
mutex_lock(&ftrace_lock);
|
||||
ret = ftrace_hash_move(orig_hash, hash);
|
||||
ret = ftrace_hash_move(ops, enable, orig_hash, hash);
|
||||
if (!ret && ops->flags & FTRACE_OPS_FL_ENABLED
|
||||
&& ftrace_enabled)
|
||||
ftrace_run_update_code(FTRACE_ENABLE_CALLS);
|
||||
|
||||
mutex_unlock(&ftrace_lock);
|
||||
|
||||
mutex_unlock(&ftrace_regex_lock);
|
||||
|
@ -3048,18 +3101,12 @@ ftrace_regex_release(struct inode *inode, struct file *file)
|
|||
orig_hash = &iter->ops->notrace_hash;
|
||||
|
||||
mutex_lock(&ftrace_lock);
|
||||
/*
|
||||
* Remove the current set, update the hash and add
|
||||
* them back.
|
||||
*/
|
||||
ftrace_hash_rec_disable(iter->ops, filter_hash);
|
||||
ret = ftrace_hash_move(orig_hash, iter->hash);
|
||||
if (!ret) {
|
||||
ftrace_hash_rec_enable(iter->ops, filter_hash);
|
||||
if (iter->ops->flags & FTRACE_OPS_FL_ENABLED
|
||||
&& ftrace_enabled)
|
||||
ftrace_run_update_code(FTRACE_ENABLE_CALLS);
|
||||
}
|
||||
ret = ftrace_hash_move(iter->ops, filter_hash,
|
||||
orig_hash, iter->hash);
|
||||
if (!ret && (iter->ops->flags & FTRACE_OPS_FL_ENABLED)
|
||||
&& ftrace_enabled)
|
||||
ftrace_run_update_code(FTRACE_ENABLE_CALLS);
|
||||
|
||||
mutex_unlock(&ftrace_lock);
|
||||
}
|
||||
free_ftrace_hash(iter->hash);
|
||||
|
@ -3338,7 +3385,7 @@ static int ftrace_process_locs(struct module *mod,
|
|||
{
|
||||
unsigned long *p;
|
||||
unsigned long addr;
|
||||
unsigned long flags;
|
||||
unsigned long flags = 0; /* Shut up gcc */
|
||||
|
||||
mutex_lock(&ftrace_lock);
|
||||
p = start;
|
||||
|
@ -3356,12 +3403,18 @@ static int ftrace_process_locs(struct module *mod,
|
|||
}
|
||||
|
||||
/*
|
||||
* Disable interrupts to prevent interrupts from executing
|
||||
* code that is being modified.
|
||||
* We only need to disable interrupts on start up
|
||||
* because we are modifying code that an interrupt
|
||||
* may execute, and the modification is not atomic.
|
||||
* But for modules, nothing runs the code we modify
|
||||
* until we are finished with it, and there's no
|
||||
* reason to cause large interrupt latencies while we do it.
|
||||
*/
|
||||
local_irq_save(flags);
|
||||
if (!mod)
|
||||
local_irq_save(flags);
|
||||
ftrace_update_code(mod);
|
||||
local_irq_restore(flags);
|
||||
if (!mod)
|
||||
local_irq_restore(flags);
|
||||
mutex_unlock(&ftrace_lock);
|
||||
|
||||
return 0;
|
||||
|
|
|
@ -1248,6 +1248,15 @@ ftrace(struct trace_array *tr, struct trace_array_cpu *data,
|
|||
}
|
||||
|
||||
#ifdef CONFIG_STACKTRACE
|
||||
|
||||
#define FTRACE_STACK_MAX_ENTRIES (PAGE_SIZE / sizeof(unsigned long))
|
||||
struct ftrace_stack {
|
||||
unsigned long calls[FTRACE_STACK_MAX_ENTRIES];
|
||||
};
|
||||
|
||||
static DEFINE_PER_CPU(struct ftrace_stack, ftrace_stack);
|
||||
static DEFINE_PER_CPU(int, ftrace_stack_reserve);
|
||||
|
||||
static void __ftrace_trace_stack(struct ring_buffer *buffer,
|
||||
unsigned long flags,
|
||||
int skip, int pc, struct pt_regs *regs)
|
||||
|
@ -1256,25 +1265,77 @@ static void __ftrace_trace_stack(struct ring_buffer *buffer,
|
|||
struct ring_buffer_event *event;
|
||||
struct stack_entry *entry;
|
||||
struct stack_trace trace;
|
||||
|
||||
event = trace_buffer_lock_reserve(buffer, TRACE_STACK,
|
||||
sizeof(*entry), flags, pc);
|
||||
if (!event)
|
||||
return;
|
||||
entry = ring_buffer_event_data(event);
|
||||
memset(&entry->caller, 0, sizeof(entry->caller));
|
||||
int use_stack;
|
||||
int size = FTRACE_STACK_ENTRIES;
|
||||
|
||||
trace.nr_entries = 0;
|
||||
trace.max_entries = FTRACE_STACK_ENTRIES;
|
||||
trace.skip = skip;
|
||||
trace.entries = entry->caller;
|
||||
|
||||
if (regs)
|
||||
save_stack_trace_regs(regs, &trace);
|
||||
else
|
||||
save_stack_trace(&trace);
|
||||
/*
|
||||
* Since events can happen in NMIs there's no safe way to
|
||||
* use the per cpu ftrace_stacks. We reserve it and if an interrupt
|
||||
* or NMI comes in, it will just have to use the default
|
||||
* FTRACE_STACK_SIZE.
|
||||
*/
|
||||
preempt_disable_notrace();
|
||||
|
||||
use_stack = ++__get_cpu_var(ftrace_stack_reserve);
|
||||
/*
|
||||
* We don't need any atomic variables, just a barrier.
|
||||
* If an interrupt comes in, we don't care, because it would
|
||||
* have exited and put the counter back to what we want.
|
||||
* We just need a barrier to keep gcc from moving things
|
||||
* around.
|
||||
*/
|
||||
barrier();
|
||||
if (use_stack == 1) {
|
||||
trace.entries = &__get_cpu_var(ftrace_stack).calls[0];
|
||||
trace.max_entries = FTRACE_STACK_MAX_ENTRIES;
|
||||
|
||||
if (regs)
|
||||
save_stack_trace_regs(regs, &trace);
|
||||
else
|
||||
save_stack_trace(&trace);
|
||||
|
||||
if (trace.nr_entries > size)
|
||||
size = trace.nr_entries;
|
||||
} else
|
||||
/* From now on, use_stack is a boolean */
|
||||
use_stack = 0;
|
||||
|
||||
size *= sizeof(unsigned long);
|
||||
|
||||
event = trace_buffer_lock_reserve(buffer, TRACE_STACK,
|
||||
sizeof(*entry) + size, flags, pc);
|
||||
if (!event)
|
||||
goto out;
|
||||
entry = ring_buffer_event_data(event);
|
||||
|
||||
memset(&entry->caller, 0, size);
|
||||
|
||||
if (use_stack)
|
||||
memcpy(&entry->caller, trace.entries,
|
||||
trace.nr_entries * sizeof(unsigned long));
|
||||
else {
|
||||
trace.max_entries = FTRACE_STACK_ENTRIES;
|
||||
trace.entries = entry->caller;
|
||||
if (regs)
|
||||
save_stack_trace_regs(regs, &trace);
|
||||
else
|
||||
save_stack_trace(&trace);
|
||||
}
|
||||
|
||||
entry->size = trace.nr_entries;
|
||||
|
||||
if (!filter_check_discard(call, entry, buffer, event))
|
||||
ring_buffer_unlock_commit(buffer, event);
|
||||
|
||||
out:
|
||||
/* Again, don't let gcc optimize things here */
|
||||
barrier();
|
||||
__get_cpu_var(ftrace_stack_reserve)--;
|
||||
preempt_enable_notrace();
|
||||
|
||||
}
|
||||
|
||||
void ftrace_trace_stack_regs(struct ring_buffer *buffer, unsigned long flags,
|
||||
|
@ -1562,7 +1623,12 @@ peek_next_entry(struct trace_iterator *iter, int cpu, u64 *ts,
|
|||
|
||||
ftrace_enable_cpu();
|
||||
|
||||
return event ? ring_buffer_event_data(event) : NULL;
|
||||
if (event) {
|
||||
iter->ent_size = ring_buffer_event_length(event);
|
||||
return ring_buffer_event_data(event);
|
||||
}
|
||||
iter->ent_size = 0;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static struct trace_entry *
|
||||
|
|
|
@ -278,6 +278,29 @@ struct tracer {
|
|||
};
|
||||
|
||||
|
||||
/* Only current can touch trace_recursion */
|
||||
#define trace_recursion_inc() do { (current)->trace_recursion++; } while (0)
|
||||
#define trace_recursion_dec() do { (current)->trace_recursion--; } while (0)
|
||||
|
||||
/* Ring buffer has the 10 LSB bits to count */
|
||||
#define trace_recursion_buffer() ((current)->trace_recursion & 0x3ff)
|
||||
|
||||
/* for function tracing recursion */
|
||||
#define TRACE_INTERNAL_BIT (1<<11)
|
||||
#define TRACE_GLOBAL_BIT (1<<12)
|
||||
/*
|
||||
* Abuse of the trace_recursion.
|
||||
* As we need a way to maintain state if we are tracing the function
|
||||
* graph in irq because we want to trace a particular function that
|
||||
* was called in irq context but we have irq tracing off. Since this
|
||||
* can only be modified by current, we can reuse trace_recursion.
|
||||
*/
|
||||
#define TRACE_IRQ_BIT (1<<13)
|
||||
|
||||
#define trace_recursion_set(bit) do { (current)->trace_recursion |= (bit); } while (0)
|
||||
#define trace_recursion_clear(bit) do { (current)->trace_recursion &= ~(bit); } while (0)
|
||||
#define trace_recursion_test(bit) ((current)->trace_recursion & (bit))
|
||||
|
||||
#define TRACE_PIPE_ALL_CPU -1
|
||||
|
||||
int tracer_init(struct tracer *t, struct trace_array *tr);
|
||||
|
@ -516,8 +539,18 @@ static inline int ftrace_graph_addr(unsigned long addr)
|
|||
return 1;
|
||||
|
||||
for (i = 0; i < ftrace_graph_count; i++) {
|
||||
if (addr == ftrace_graph_funcs[i])
|
||||
if (addr == ftrace_graph_funcs[i]) {
|
||||
/*
|
||||
* If no irqs are to be traced, but a set_graph_function
|
||||
* is set, and called by an interrupt handler, we still
|
||||
* want to trace it.
|
||||
*/
|
||||
if (in_irq())
|
||||
trace_recursion_set(TRACE_IRQ_BIT);
|
||||
else
|
||||
trace_recursion_clear(TRACE_IRQ_BIT);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
@ -795,19 +828,4 @@ extern const char *__stop___trace_bprintk_fmt[];
|
|||
FTRACE_ENTRY(call, struct_name, id, PARAMS(tstruct), PARAMS(print))
|
||||
#include "trace_entries.h"
|
||||
|
||||
/* Only current can touch trace_recursion */
|
||||
#define trace_recursion_inc() do { (current)->trace_recursion++; } while (0)
|
||||
#define trace_recursion_dec() do { (current)->trace_recursion--; } while (0)
|
||||
|
||||
/* Ring buffer has the 10 LSB bits to count */
|
||||
#define trace_recursion_buffer() ((current)->trace_recursion & 0x3ff)
|
||||
|
||||
/* for function tracing recursion */
|
||||
#define TRACE_INTERNAL_BIT (1<<11)
|
||||
#define TRACE_GLOBAL_BIT (1<<12)
|
||||
|
||||
#define trace_recursion_set(bit) do { (current)->trace_recursion |= (bit); } while (0)
|
||||
#define trace_recursion_clear(bit) do { (current)->trace_recursion &= ~(bit); } while (0)
|
||||
#define trace_recursion_test(bit) ((current)->trace_recursion & (bit))
|
||||
|
||||
#endif /* _LINUX_KERNEL_TRACE_H */
|
||||
|
|
|
@ -161,7 +161,8 @@ FTRACE_ENTRY(kernel_stack, stack_entry,
|
|||
TRACE_STACK,
|
||||
|
||||
F_STRUCT(
|
||||
__array( unsigned long, caller, FTRACE_STACK_ENTRIES )
|
||||
__field( int, size )
|
||||
__dynamic_array(unsigned long, caller )
|
||||
),
|
||||
|
||||
F_printk("\t=> (%08lx)\n\t=> (%08lx)\n\t=> (%08lx)\n\t=> (%08lx)\n"
|
||||
|
|
|
@ -227,7 +227,7 @@ int __trace_graph_entry(struct trace_array *tr,
|
|||
|
||||
static inline int ftrace_graph_ignore_irqs(void)
|
||||
{
|
||||
if (!ftrace_graph_skip_irqs)
|
||||
if (!ftrace_graph_skip_irqs || trace_recursion_test(TRACE_IRQ_BIT))
|
||||
return 0;
|
||||
|
||||
return in_irq();
|
||||
|
|
|
@ -343,6 +343,14 @@ DEFINE_BASIC_FETCH_FUNCS(deref)
|
|||
DEFINE_FETCH_deref(string)
|
||||
DEFINE_FETCH_deref(string_size)
|
||||
|
||||
static __kprobes void update_deref_fetch_param(struct deref_fetch_param *data)
|
||||
{
|
||||
if (CHECK_FETCH_FUNCS(deref, data->orig.fn))
|
||||
update_deref_fetch_param(data->orig.data);
|
||||
else if (CHECK_FETCH_FUNCS(symbol, data->orig.fn))
|
||||
update_symbol_cache(data->orig.data);
|
||||
}
|
||||
|
||||
static __kprobes void free_deref_fetch_param(struct deref_fetch_param *data)
|
||||
{
|
||||
if (CHECK_FETCH_FUNCS(deref, data->orig.fn))
|
||||
|
@ -376,6 +384,19 @@ DEFINE_BASIC_FETCH_FUNCS(bitfield)
|
|||
#define fetch_bitfield_string NULL
|
||||
#define fetch_bitfield_string_size NULL
|
||||
|
||||
static __kprobes void
|
||||
update_bitfield_fetch_param(struct bitfield_fetch_param *data)
|
||||
{
|
||||
/*
|
||||
* Don't check the bitfield itself, because this must be the
|
||||
* last fetch function.
|
||||
*/
|
||||
if (CHECK_FETCH_FUNCS(deref, data->orig.fn))
|
||||
update_deref_fetch_param(data->orig.data);
|
||||
else if (CHECK_FETCH_FUNCS(symbol, data->orig.fn))
|
||||
update_symbol_cache(data->orig.data);
|
||||
}
|
||||
|
||||
static __kprobes void
|
||||
free_bitfield_fetch_param(struct bitfield_fetch_param *data)
|
||||
{
|
||||
|
@ -389,6 +410,7 @@ free_bitfield_fetch_param(struct bitfield_fetch_param *data)
|
|||
free_symbol_cache(data->orig.data);
|
||||
kfree(data);
|
||||
}
|
||||
|
||||
/* Default (unsigned long) fetch type */
|
||||
#define __DEFAULT_FETCH_TYPE(t) u##t
|
||||
#define _DEFAULT_FETCH_TYPE(t) __DEFAULT_FETCH_TYPE(t)
|
||||
|
@ -536,6 +558,7 @@ struct probe_arg {
|
|||
/* Flags for trace_probe */
|
||||
#define TP_FLAG_TRACE 1
|
||||
#define TP_FLAG_PROFILE 2
|
||||
#define TP_FLAG_REGISTERED 4
|
||||
|
||||
struct trace_probe {
|
||||
struct list_head list;
|
||||
|
@ -555,16 +578,49 @@ struct trace_probe {
|
|||
(sizeof(struct probe_arg) * (n)))
|
||||
|
||||
|
||||
static __kprobes int probe_is_return(struct trace_probe *tp)
|
||||
static __kprobes int trace_probe_is_return(struct trace_probe *tp)
|
||||
{
|
||||
return tp->rp.handler != NULL;
|
||||
}
|
||||
|
||||
static __kprobes const char *probe_symbol(struct trace_probe *tp)
|
||||
static __kprobes const char *trace_probe_symbol(struct trace_probe *tp)
|
||||
{
|
||||
return tp->symbol ? tp->symbol : "unknown";
|
||||
}
|
||||
|
||||
static __kprobes unsigned long trace_probe_offset(struct trace_probe *tp)
|
||||
{
|
||||
return tp->rp.kp.offset;
|
||||
}
|
||||
|
||||
static __kprobes bool trace_probe_is_enabled(struct trace_probe *tp)
|
||||
{
|
||||
return !!(tp->flags & (TP_FLAG_TRACE | TP_FLAG_PROFILE));
|
||||
}
|
||||
|
||||
static __kprobes bool trace_probe_is_registered(struct trace_probe *tp)
|
||||
{
|
||||
return !!(tp->flags & TP_FLAG_REGISTERED);
|
||||
}
|
||||
|
||||
static __kprobes bool trace_probe_has_gone(struct trace_probe *tp)
|
||||
{
|
||||
return !!(kprobe_gone(&tp->rp.kp));
|
||||
}
|
||||
|
||||
static __kprobes bool trace_probe_within_module(struct trace_probe *tp,
|
||||
struct module *mod)
|
||||
{
|
||||
int len = strlen(mod->name);
|
||||
const char *name = trace_probe_symbol(tp);
|
||||
return strncmp(mod->name, name, len) == 0 && name[len] == ':';
|
||||
}
|
||||
|
||||
static __kprobes bool trace_probe_is_on_module(struct trace_probe *tp)
|
||||
{
|
||||
return !!strchr(trace_probe_symbol(tp), ':');
|
||||
}
|
||||
|
||||
static int register_probe_event(struct trace_probe *tp);
|
||||
static void unregister_probe_event(struct trace_probe *tp);
|
||||
|
||||
|
@ -646,6 +702,16 @@ error:
|
|||
return ERR_PTR(ret);
|
||||
}
|
||||
|
||||
static void update_probe_arg(struct probe_arg *arg)
|
||||
{
|
||||
if (CHECK_FETCH_FUNCS(bitfield, arg->fetch.fn))
|
||||
update_bitfield_fetch_param(arg->fetch.data);
|
||||
else if (CHECK_FETCH_FUNCS(deref, arg->fetch.fn))
|
||||
update_deref_fetch_param(arg->fetch.data);
|
||||
else if (CHECK_FETCH_FUNCS(symbol, arg->fetch.fn))
|
||||
update_symbol_cache(arg->fetch.data);
|
||||
}
|
||||
|
||||
static void free_probe_arg(struct probe_arg *arg)
|
||||
{
|
||||
if (CHECK_FETCH_FUNCS(bitfield, arg->fetch.fn))
|
||||
|
@ -671,7 +737,7 @@ static void free_trace_probe(struct trace_probe *tp)
|
|||
kfree(tp);
|
||||
}
|
||||
|
||||
static struct trace_probe *find_probe_event(const char *event,
|
||||
static struct trace_probe *find_trace_probe(const char *event,
|
||||
const char *group)
|
||||
{
|
||||
struct trace_probe *tp;
|
||||
|
@ -683,13 +749,96 @@ static struct trace_probe *find_probe_event(const char *event,
|
|||
return NULL;
|
||||
}
|
||||
|
||||
/* Enable trace_probe - @flag must be TP_FLAG_TRACE or TP_FLAG_PROFILE */
|
||||
static int enable_trace_probe(struct trace_probe *tp, int flag)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
tp->flags |= flag;
|
||||
if (trace_probe_is_enabled(tp) && trace_probe_is_registered(tp) &&
|
||||
!trace_probe_has_gone(tp)) {
|
||||
if (trace_probe_is_return(tp))
|
||||
ret = enable_kretprobe(&tp->rp);
|
||||
else
|
||||
ret = enable_kprobe(&tp->rp.kp);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Disable trace_probe - @flag must be TP_FLAG_TRACE or TP_FLAG_PROFILE */
|
||||
static void disable_trace_probe(struct trace_probe *tp, int flag)
|
||||
{
|
||||
tp->flags &= ~flag;
|
||||
if (!trace_probe_is_enabled(tp) && trace_probe_is_registered(tp)) {
|
||||
if (trace_probe_is_return(tp))
|
||||
disable_kretprobe(&tp->rp);
|
||||
else
|
||||
disable_kprobe(&tp->rp.kp);
|
||||
}
|
||||
}
|
||||
|
||||
/* Internal register function - just handle k*probes and flags */
|
||||
static int __register_trace_probe(struct trace_probe *tp)
|
||||
{
|
||||
int i, ret;
|
||||
|
||||
if (trace_probe_is_registered(tp))
|
||||
return -EINVAL;
|
||||
|
||||
for (i = 0; i < tp->nr_args; i++)
|
||||
update_probe_arg(&tp->args[i]);
|
||||
|
||||
/* Set/clear disabled flag according to tp->flag */
|
||||
if (trace_probe_is_enabled(tp))
|
||||
tp->rp.kp.flags &= ~KPROBE_FLAG_DISABLED;
|
||||
else
|
||||
tp->rp.kp.flags |= KPROBE_FLAG_DISABLED;
|
||||
|
||||
if (trace_probe_is_return(tp))
|
||||
ret = register_kretprobe(&tp->rp);
|
||||
else
|
||||
ret = register_kprobe(&tp->rp.kp);
|
||||
|
||||
if (ret == 0)
|
||||
tp->flags |= TP_FLAG_REGISTERED;
|
||||
else {
|
||||
pr_warning("Could not insert probe at %s+%lu: %d\n",
|
||||
trace_probe_symbol(tp), trace_probe_offset(tp), ret);
|
||||
if (ret == -ENOENT && trace_probe_is_on_module(tp)) {
|
||||
pr_warning("This probe might be able to register after"
|
||||
"target module is loaded. Continue.\n");
|
||||
ret = 0;
|
||||
} else if (ret == -EILSEQ) {
|
||||
pr_warning("Probing address(0x%p) is not an "
|
||||
"instruction boundary.\n",
|
||||
tp->rp.kp.addr);
|
||||
ret = -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Internal unregister function - just handle k*probes and flags */
|
||||
static void __unregister_trace_probe(struct trace_probe *tp)
|
||||
{
|
||||
if (trace_probe_is_registered(tp)) {
|
||||
if (trace_probe_is_return(tp))
|
||||
unregister_kretprobe(&tp->rp);
|
||||
else
|
||||
unregister_kprobe(&tp->rp.kp);
|
||||
tp->flags &= ~TP_FLAG_REGISTERED;
|
||||
/* Cleanup kprobe for reuse */
|
||||
if (tp->rp.kp.symbol_name)
|
||||
tp->rp.kp.addr = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/* Unregister a trace_probe and probe_event: call with locking probe_lock */
|
||||
static void unregister_trace_probe(struct trace_probe *tp)
|
||||
{
|
||||
if (probe_is_return(tp))
|
||||
unregister_kretprobe(&tp->rp);
|
||||
else
|
||||
unregister_kprobe(&tp->rp.kp);
|
||||
__unregister_trace_probe(tp);
|
||||
list_del(&tp->list);
|
||||
unregister_probe_event(tp);
|
||||
}
|
||||
|
@ -702,41 +851,65 @@ static int register_trace_probe(struct trace_probe *tp)
|
|||
|
||||
mutex_lock(&probe_lock);
|
||||
|
||||
/* register as an event */
|
||||
old_tp = find_probe_event(tp->call.name, tp->call.class->system);
|
||||
/* Delete old (same name) event if exist */
|
||||
old_tp = find_trace_probe(tp->call.name, tp->call.class->system);
|
||||
if (old_tp) {
|
||||
/* delete old event */
|
||||
unregister_trace_probe(old_tp);
|
||||
free_trace_probe(old_tp);
|
||||
}
|
||||
|
||||
/* Register new event */
|
||||
ret = register_probe_event(tp);
|
||||
if (ret) {
|
||||
pr_warning("Failed to register probe event(%d)\n", ret);
|
||||
goto end;
|
||||
}
|
||||
|
||||
tp->rp.kp.flags |= KPROBE_FLAG_DISABLED;
|
||||
if (probe_is_return(tp))
|
||||
ret = register_kretprobe(&tp->rp);
|
||||
else
|
||||
ret = register_kprobe(&tp->rp.kp);
|
||||
|
||||
if (ret) {
|
||||
pr_warning("Could not insert probe(%d)\n", ret);
|
||||
if (ret == -EILSEQ) {
|
||||
pr_warning("Probing address(0x%p) is not an "
|
||||
"instruction boundary.\n",
|
||||
tp->rp.kp.addr);
|
||||
ret = -EINVAL;
|
||||
}
|
||||
/* Register k*probe */
|
||||
ret = __register_trace_probe(tp);
|
||||
if (ret < 0)
|
||||
unregister_probe_event(tp);
|
||||
} else
|
||||
else
|
||||
list_add_tail(&tp->list, &probe_list);
|
||||
|
||||
end:
|
||||
mutex_unlock(&probe_lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Module notifier call back, checking event on the module */
|
||||
static int trace_probe_module_callback(struct notifier_block *nb,
|
||||
unsigned long val, void *data)
|
||||
{
|
||||
struct module *mod = data;
|
||||
struct trace_probe *tp;
|
||||
int ret;
|
||||
|
||||
if (val != MODULE_STATE_COMING)
|
||||
return NOTIFY_DONE;
|
||||
|
||||
/* Update probes on coming module */
|
||||
mutex_lock(&probe_lock);
|
||||
list_for_each_entry(tp, &probe_list, list) {
|
||||
if (trace_probe_within_module(tp, mod)) {
|
||||
__unregister_trace_probe(tp);
|
||||
ret = __register_trace_probe(tp);
|
||||
if (ret)
|
||||
pr_warning("Failed to re-register probe %s on"
|
||||
"%s: %d\n",
|
||||
tp->call.name, mod->name, ret);
|
||||
}
|
||||
}
|
||||
mutex_unlock(&probe_lock);
|
||||
|
||||
return NOTIFY_DONE;
|
||||
}
|
||||
|
||||
static struct notifier_block trace_probe_module_nb = {
|
||||
.notifier_call = trace_probe_module_callback,
|
||||
.priority = 1 /* Invoked after kprobe module callback */
|
||||
};
|
||||
|
||||
/* Split symbol and offset. */
|
||||
static int split_symbol_offset(char *symbol, unsigned long *offset)
|
||||
{
|
||||
|
@ -962,8 +1135,8 @@ static int create_trace_probe(int argc, char **argv)
|
|||
{
|
||||
/*
|
||||
* Argument syntax:
|
||||
* - Add kprobe: p[:[GRP/]EVENT] KSYM[+OFFS]|KADDR [FETCHARGS]
|
||||
* - Add kretprobe: r[:[GRP/]EVENT] KSYM[+0] [FETCHARGS]
|
||||
* - Add kprobe: p[:[GRP/]EVENT] [MOD:]KSYM[+OFFS]|KADDR [FETCHARGS]
|
||||
* - Add kretprobe: r[:[GRP/]EVENT] [MOD:]KSYM[+0] [FETCHARGS]
|
||||
* Fetch args:
|
||||
* $retval : fetch return value
|
||||
* $stack : fetch stack address
|
||||
|
@ -1025,7 +1198,7 @@ static int create_trace_probe(int argc, char **argv)
|
|||
return -EINVAL;
|
||||
}
|
||||
mutex_lock(&probe_lock);
|
||||
tp = find_probe_event(event, group);
|
||||
tp = find_trace_probe(event, group);
|
||||
if (!tp) {
|
||||
mutex_unlock(&probe_lock);
|
||||
pr_info("Event %s/%s doesn't exist.\n", group, event);
|
||||
|
@ -1144,7 +1317,7 @@ error:
|
|||
return ret;
|
||||
}
|
||||
|
||||
static void cleanup_all_probes(void)
|
||||
static void release_all_trace_probes(void)
|
||||
{
|
||||
struct trace_probe *tp;
|
||||
|
||||
|
@ -1158,7 +1331,6 @@ static void cleanup_all_probes(void)
|
|||
mutex_unlock(&probe_lock);
|
||||
}
|
||||
|
||||
|
||||
/* Probes listing interfaces */
|
||||
static void *probes_seq_start(struct seq_file *m, loff_t *pos)
|
||||
{
|
||||
|
@ -1181,15 +1353,16 @@ static int probes_seq_show(struct seq_file *m, void *v)
|
|||
struct trace_probe *tp = v;
|
||||
int i;
|
||||
|
||||
seq_printf(m, "%c", probe_is_return(tp) ? 'r' : 'p');
|
||||
seq_printf(m, "%c", trace_probe_is_return(tp) ? 'r' : 'p');
|
||||
seq_printf(m, ":%s/%s", tp->call.class->system, tp->call.name);
|
||||
|
||||
if (!tp->symbol)
|
||||
seq_printf(m, " 0x%p", tp->rp.kp.addr);
|
||||
else if (tp->rp.kp.offset)
|
||||
seq_printf(m, " %s+%u", probe_symbol(tp), tp->rp.kp.offset);
|
||||
seq_printf(m, " %s+%u", trace_probe_symbol(tp),
|
||||
tp->rp.kp.offset);
|
||||
else
|
||||
seq_printf(m, " %s", probe_symbol(tp));
|
||||
seq_printf(m, " %s", trace_probe_symbol(tp));
|
||||
|
||||
for (i = 0; i < tp->nr_args; i++)
|
||||
seq_printf(m, " %s=%s", tp->args[i].name, tp->args[i].comm);
|
||||
|
@ -1209,7 +1382,7 @@ static int probes_open(struct inode *inode, struct file *file)
|
|||
{
|
||||
if ((file->f_mode & FMODE_WRITE) &&
|
||||
(file->f_flags & O_TRUNC))
|
||||
cleanup_all_probes();
|
||||
release_all_trace_probes();
|
||||
|
||||
return seq_open(file, &probes_seq_op);
|
||||
}
|
||||
|
@ -1513,30 +1686,6 @@ partial:
|
|||
return TRACE_TYPE_PARTIAL_LINE;
|
||||
}
|
||||
|
||||
static int probe_event_enable(struct ftrace_event_call *call)
|
||||
{
|
||||
struct trace_probe *tp = (struct trace_probe *)call->data;
|
||||
|
||||
tp->flags |= TP_FLAG_TRACE;
|
||||
if (probe_is_return(tp))
|
||||
return enable_kretprobe(&tp->rp);
|
||||
else
|
||||
return enable_kprobe(&tp->rp.kp);
|
||||
}
|
||||
|
||||
static void probe_event_disable(struct ftrace_event_call *call)
|
||||
{
|
||||
struct trace_probe *tp = (struct trace_probe *)call->data;
|
||||
|
||||
tp->flags &= ~TP_FLAG_TRACE;
|
||||
if (!(tp->flags & (TP_FLAG_TRACE | TP_FLAG_PROFILE))) {
|
||||
if (probe_is_return(tp))
|
||||
disable_kretprobe(&tp->rp);
|
||||
else
|
||||
disable_kprobe(&tp->rp.kp);
|
||||
}
|
||||
}
|
||||
|
||||
#undef DEFINE_FIELD
|
||||
#define DEFINE_FIELD(type, item, name, is_signed) \
|
||||
do { \
|
||||
|
@ -1598,7 +1747,7 @@ static int __set_print_fmt(struct trace_probe *tp, char *buf, int len)
|
|||
|
||||
const char *fmt, *arg;
|
||||
|
||||
if (!probe_is_return(tp)) {
|
||||
if (!trace_probe_is_return(tp)) {
|
||||
fmt = "(%lx)";
|
||||
arg = "REC->" FIELD_STRING_IP;
|
||||
} else {
|
||||
|
@ -1715,49 +1864,25 @@ static __kprobes void kretprobe_perf_func(struct kretprobe_instance *ri,
|
|||
head = this_cpu_ptr(call->perf_events);
|
||||
perf_trace_buf_submit(entry, size, rctx, entry->ret_ip, 1, regs, head);
|
||||
}
|
||||
|
||||
static int probe_perf_enable(struct ftrace_event_call *call)
|
||||
{
|
||||
struct trace_probe *tp = (struct trace_probe *)call->data;
|
||||
|
||||
tp->flags |= TP_FLAG_PROFILE;
|
||||
|
||||
if (probe_is_return(tp))
|
||||
return enable_kretprobe(&tp->rp);
|
||||
else
|
||||
return enable_kprobe(&tp->rp.kp);
|
||||
}
|
||||
|
||||
static void probe_perf_disable(struct ftrace_event_call *call)
|
||||
{
|
||||
struct trace_probe *tp = (struct trace_probe *)call->data;
|
||||
|
||||
tp->flags &= ~TP_FLAG_PROFILE;
|
||||
|
||||
if (!(tp->flags & TP_FLAG_TRACE)) {
|
||||
if (probe_is_return(tp))
|
||||
disable_kretprobe(&tp->rp);
|
||||
else
|
||||
disable_kprobe(&tp->rp.kp);
|
||||
}
|
||||
}
|
||||
#endif /* CONFIG_PERF_EVENTS */
|
||||
|
||||
static __kprobes
|
||||
int kprobe_register(struct ftrace_event_call *event, enum trace_reg type)
|
||||
{
|
||||
struct trace_probe *tp = (struct trace_probe *)event->data;
|
||||
|
||||
switch (type) {
|
||||
case TRACE_REG_REGISTER:
|
||||
return probe_event_enable(event);
|
||||
return enable_trace_probe(tp, TP_FLAG_TRACE);
|
||||
case TRACE_REG_UNREGISTER:
|
||||
probe_event_disable(event);
|
||||
disable_trace_probe(tp, TP_FLAG_TRACE);
|
||||
return 0;
|
||||
|
||||
#ifdef CONFIG_PERF_EVENTS
|
||||
case TRACE_REG_PERF_REGISTER:
|
||||
return probe_perf_enable(event);
|
||||
return enable_trace_probe(tp, TP_FLAG_PROFILE);
|
||||
case TRACE_REG_PERF_UNREGISTER:
|
||||
probe_perf_disable(event);
|
||||
disable_trace_probe(tp, TP_FLAG_PROFILE);
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
@ -1807,7 +1932,7 @@ static int register_probe_event(struct trace_probe *tp)
|
|||
|
||||
/* Initialize ftrace_event_call */
|
||||
INIT_LIST_HEAD(&call->class->fields);
|
||||
if (probe_is_return(tp)) {
|
||||
if (trace_probe_is_return(tp)) {
|
||||
call->event.funcs = &kretprobe_funcs;
|
||||
call->class->define_fields = kretprobe_event_define_fields;
|
||||
} else {
|
||||
|
@ -1846,6 +1971,9 @@ static __init int init_kprobe_trace(void)
|
|||
struct dentry *d_tracer;
|
||||
struct dentry *entry;
|
||||
|
||||
if (register_module_notifier(&trace_probe_module_nb))
|
||||
return -EINVAL;
|
||||
|
||||
d_tracer = tracing_init_dentry();
|
||||
if (!d_tracer)
|
||||
return 0;
|
||||
|
@ -1899,12 +2027,12 @@ static __init int kprobe_trace_self_tests_init(void)
|
|||
warn++;
|
||||
} else {
|
||||
/* Enable trace point */
|
||||
tp = find_probe_event("testprobe", KPROBE_EVENT_SYSTEM);
|
||||
tp = find_trace_probe("testprobe", KPROBE_EVENT_SYSTEM);
|
||||
if (WARN_ON_ONCE(tp == NULL)) {
|
||||
pr_warning("error on getting new probe.\n");
|
||||
warn++;
|
||||
} else
|
||||
probe_event_enable(&tp->call);
|
||||
enable_trace_probe(tp, TP_FLAG_TRACE);
|
||||
}
|
||||
|
||||
ret = command_trace_probe("r:testprobe2 kprobe_trace_selftest_target "
|
||||
|
@ -1914,12 +2042,12 @@ static __init int kprobe_trace_self_tests_init(void)
|
|||
warn++;
|
||||
} else {
|
||||
/* Enable trace point */
|
||||
tp = find_probe_event("testprobe2", KPROBE_EVENT_SYSTEM);
|
||||
tp = find_trace_probe("testprobe2", KPROBE_EVENT_SYSTEM);
|
||||
if (WARN_ON_ONCE(tp == NULL)) {
|
||||
pr_warning("error on getting new probe.\n");
|
||||
warn++;
|
||||
} else
|
||||
probe_event_enable(&tp->call);
|
||||
enable_trace_probe(tp, TP_FLAG_TRACE);
|
||||
}
|
||||
|
||||
if (warn)
|
||||
|
@ -1940,7 +2068,7 @@ static __init int kprobe_trace_self_tests_init(void)
|
|||
}
|
||||
|
||||
end:
|
||||
cleanup_all_probes();
|
||||
release_all_trace_probes();
|
||||
if (warn)
|
||||
pr_cont("NG: Some tests are failed. Please check them.\n");
|
||||
else
|
||||
|
|
|
@ -1107,19 +1107,20 @@ static enum print_line_t trace_stack_print(struct trace_iterator *iter,
|
|||
{
|
||||
struct stack_entry *field;
|
||||
struct trace_seq *s = &iter->seq;
|
||||
int i;
|
||||
unsigned long *p;
|
||||
unsigned long *end;
|
||||
|
||||
trace_assign_type(field, iter->ent);
|
||||
end = (unsigned long *)((long)iter->ent + iter->ent_size);
|
||||
|
||||
if (!trace_seq_puts(s, "<stack trace>\n"))
|
||||
goto partial;
|
||||
for (i = 0; i < FTRACE_STACK_ENTRIES; i++) {
|
||||
if (!field->caller[i] || (field->caller[i] == ULONG_MAX))
|
||||
break;
|
||||
|
||||
for (p = field->caller; p && *p != ULONG_MAX && p < end; p++) {
|
||||
if (!trace_seq_puts(s, " => "))
|
||||
goto partial;
|
||||
|
||||
if (!seq_print_ip_sym(s, field->caller[i], flags))
|
||||
if (!seq_print_ip_sym(s, *p, flags))
|
||||
goto partial;
|
||||
if (!trace_seq_puts(s, "\n"))
|
||||
goto partial;
|
||||
|
|
|
@ -200,7 +200,6 @@ static int is_softlockup(unsigned long touch_ts)
|
|||
}
|
||||
|
||||
#ifdef CONFIG_HARDLOCKUP_DETECTOR
|
||||
void __weak hw_nmi_watchdog_set_attr(struct perf_event_attr *wd_attr) { }
|
||||
|
||||
static struct perf_event_attr wd_hw_attr = {
|
||||
.type = PERF_TYPE_HARDWARE,
|
||||
|
@ -372,7 +371,6 @@ static int watchdog_nmi_enable(int cpu)
|
|||
|
||||
wd_attr = &wd_hw_attr;
|
||||
wd_attr->sample_period = hw_nmi_get_sample_period(watchdog_thresh);
|
||||
hw_nmi_watchdog_set_attr(wd_attr);
|
||||
|
||||
/* Try to register using hardware perf events */
|
||||
event = perf_event_create_kernel_counter(wd_attr, cpu, NULL, watchdog_overflow_callback, NULL);
|
||||
|
|
|
@ -34,9 +34,11 @@ OPTIONS
|
|||
Specify vmlinux path which has debuginfo (Dwarf binary).
|
||||
|
||||
-m::
|
||||
--module=MODNAME::
|
||||
--module=MODNAME|PATH::
|
||||
Specify module name in which perf-probe searches probe points
|
||||
or lines.
|
||||
or lines. If a path of module file is passed, perf-probe
|
||||
treat it as an offline module (this means you can add a probe on
|
||||
a module which has not been loaded yet).
|
||||
|
||||
-s::
|
||||
--source=PATH::
|
||||
|
|
|
@ -279,6 +279,7 @@ LIB_H += util/thread.h
|
|||
LIB_H += util/thread_map.h
|
||||
LIB_H += util/trace-event.h
|
||||
LIB_H += util/probe-finder.h
|
||||
LIB_H += util/dwarf-aux.h
|
||||
LIB_H += util/probe-event.h
|
||||
LIB_H += util/pstack.h
|
||||
LIB_H += util/cpumap.h
|
||||
|
@ -435,6 +436,7 @@ else
|
|||
BASIC_CFLAGS += -DDWARF_SUPPORT
|
||||
EXTLIBS += -lelf -ldw
|
||||
LIB_OBJS += $(OUTPUT)util/probe-finder.o
|
||||
LIB_OBJS += $(OUTPUT)util/dwarf-aux.o
|
||||
endif # PERF_HAVE_DWARF_REGS
|
||||
endif # NO_DWARF
|
||||
|
||||
|
|
|
@ -242,7 +242,8 @@ static const struct option options[] = {
|
|||
OPT_STRING('s', "source", &symbol_conf.source_prefix,
|
||||
"directory", "path to kernel source"),
|
||||
OPT_STRING('m', "module", ¶ms.target_module,
|
||||
"modname", "target module name"),
|
||||
"modname|path",
|
||||
"target module name (for online) or path (for offline)"),
|
||||
#endif
|
||||
OPT__DRY_RUN(&probe_event_dry_run),
|
||||
OPT_INTEGER('\0', "max-probes", ¶ms.max_probe_points,
|
||||
|
|
|
@ -0,0 +1,663 @@
|
|||
/*
|
||||
* dwarf-aux.c : libdw auxiliary interfaces
|
||||
*
|
||||
* 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 <stdbool.h>
|
||||
#include "util.h"
|
||||
#include "debug.h"
|
||||
#include "dwarf-aux.h"
|
||||
|
||||
/**
|
||||
* cu_find_realpath - Find the realpath of the target file
|
||||
* @cu_die: A DIE(dwarf information entry) of CU(compilation Unit)
|
||||
* @fname: The tail filename of the target file
|
||||
*
|
||||
* Find the real(long) path of @fname in @cu_die.
|
||||
*/
|
||||
const char *cu_find_realpath(Dwarf_Die *cu_die, const char *fname)
|
||||
{
|
||||
Dwarf_Files *files;
|
||||
size_t nfiles, i;
|
||||
const char *src = NULL;
|
||||
int ret;
|
||||
|
||||
if (!fname)
|
||||
return NULL;
|
||||
|
||||
ret = dwarf_getsrcfiles(cu_die, &files, &nfiles);
|
||||
if (ret != 0)
|
||||
return NULL;
|
||||
|
||||
for (i = 0; i < nfiles; i++) {
|
||||
src = dwarf_filesrc(files, i, NULL, NULL);
|
||||
if (strtailcmp(src, fname) == 0)
|
||||
break;
|
||||
}
|
||||
if (i == nfiles)
|
||||
return NULL;
|
||||
return src;
|
||||
}
|
||||
|
||||
/**
|
||||
* cu_get_comp_dir - Get the path of compilation directory
|
||||
* @cu_die: a CU DIE
|
||||
*
|
||||
* Get the path of compilation directory of given @cu_die.
|
||||
* Since this depends on DW_AT_comp_dir, older gcc will not
|
||||
* embedded it. In that case, this returns NULL.
|
||||
*/
|
||||
const char *cu_get_comp_dir(Dwarf_Die *cu_die)
|
||||
{
|
||||
Dwarf_Attribute attr;
|
||||
if (dwarf_attr(cu_die, DW_AT_comp_dir, &attr) == NULL)
|
||||
return NULL;
|
||||
return dwarf_formstring(&attr);
|
||||
}
|
||||
|
||||
/**
|
||||
* cu_find_lineinfo - Get a line number and file name for given address
|
||||
* @cu_die: a CU DIE
|
||||
* @addr: An address
|
||||
* @fname: a pointer which returns the file name string
|
||||
* @lineno: a pointer which returns the line number
|
||||
*
|
||||
* Find a line number and file name for @addr in @cu_die.
|
||||
*/
|
||||
int cu_find_lineinfo(Dwarf_Die *cu_die, unsigned long addr,
|
||||
const char **fname, int *lineno)
|
||||
{
|
||||
Dwarf_Line *line;
|
||||
Dwarf_Addr laddr;
|
||||
|
||||
line = dwarf_getsrc_die(cu_die, (Dwarf_Addr)addr);
|
||||
if (line && dwarf_lineaddr(line, &laddr) == 0 &&
|
||||
addr == (unsigned long)laddr && dwarf_lineno(line, lineno) == 0) {
|
||||
*fname = dwarf_linesrc(line, NULL, NULL);
|
||||
if (!*fname)
|
||||
/* line number is useless without filename */
|
||||
*lineno = 0;
|
||||
}
|
||||
|
||||
return *lineno ?: -ENOENT;
|
||||
}
|
||||
|
||||
/**
|
||||
* die_compare_name - Compare diename and tname
|
||||
* @dw_die: a DIE
|
||||
* @tname: a string of target name
|
||||
*
|
||||
* Compare the name of @dw_die and @tname. Return false if @dw_die has no name.
|
||||
*/
|
||||
bool die_compare_name(Dwarf_Die *dw_die, const char *tname)
|
||||
{
|
||||
const char *name;
|
||||
name = dwarf_diename(dw_die);
|
||||
return name ? (strcmp(tname, name) == 0) : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* die_get_call_lineno - Get callsite line number of inline-function instance
|
||||
* @in_die: a DIE of an inlined function instance
|
||||
*
|
||||
* Get call-site line number of @in_die. This means from where the inline
|
||||
* function is called.
|
||||
*/
|
||||
int die_get_call_lineno(Dwarf_Die *in_die)
|
||||
{
|
||||
Dwarf_Attribute attr;
|
||||
Dwarf_Word ret;
|
||||
|
||||
if (!dwarf_attr(in_die, DW_AT_call_line, &attr))
|
||||
return -ENOENT;
|
||||
|
||||
dwarf_formudata(&attr, &ret);
|
||||
return (int)ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* die_get_type - Get type DIE
|
||||
* @vr_die: a DIE of a variable
|
||||
* @die_mem: where to store a type DIE
|
||||
*
|
||||
* Get a DIE of the type of given variable (@vr_die), and store
|
||||
* it to die_mem. Return NULL if fails to get a type DIE.
|
||||
*/
|
||||
Dwarf_Die *die_get_type(Dwarf_Die *vr_die, Dwarf_Die *die_mem)
|
||||
{
|
||||
Dwarf_Attribute attr;
|
||||
|
||||
if (dwarf_attr_integrate(vr_die, DW_AT_type, &attr) &&
|
||||
dwarf_formref_die(&attr, die_mem))
|
||||
return die_mem;
|
||||
else
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Get a type die, but skip qualifiers */
|
||||
static Dwarf_Die *__die_get_real_type(Dwarf_Die *vr_die, Dwarf_Die *die_mem)
|
||||
{
|
||||
int tag;
|
||||
|
||||
do {
|
||||
vr_die = die_get_type(vr_die, die_mem);
|
||||
if (!vr_die)
|
||||
break;
|
||||
tag = dwarf_tag(vr_die);
|
||||
} while (tag == DW_TAG_const_type ||
|
||||
tag == DW_TAG_restrict_type ||
|
||||
tag == DW_TAG_volatile_type ||
|
||||
tag == DW_TAG_shared_type);
|
||||
|
||||
return vr_die;
|
||||
}
|
||||
|
||||
/**
|
||||
* die_get_real_type - Get a type die, but skip qualifiers and typedef
|
||||
* @vr_die: a DIE of a variable
|
||||
* @die_mem: where to store a type DIE
|
||||
*
|
||||
* Get a DIE of the type of given variable (@vr_die), and store
|
||||
* it to die_mem. Return NULL if fails to get a type DIE.
|
||||
* If the type is qualifiers (e.g. const) or typedef, this skips it
|
||||
* and tries to find real type (structure or basic types, e.g. int).
|
||||
*/
|
||||
Dwarf_Die *die_get_real_type(Dwarf_Die *vr_die, Dwarf_Die *die_mem)
|
||||
{
|
||||
do {
|
||||
vr_die = __die_get_real_type(vr_die, die_mem);
|
||||
} while (vr_die && dwarf_tag(vr_die) == DW_TAG_typedef);
|
||||
|
||||
return vr_die;
|
||||
}
|
||||
|
||||
/* Get attribute and translate it as a udata */
|
||||
static int die_get_attr_udata(Dwarf_Die *tp_die, unsigned int attr_name,
|
||||
Dwarf_Word *result)
|
||||
{
|
||||
Dwarf_Attribute attr;
|
||||
|
||||
if (dwarf_attr(tp_die, attr_name, &attr) == NULL ||
|
||||
dwarf_formudata(&attr, result) != 0)
|
||||
return -ENOENT;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* die_is_signed_type - Check whether a type DIE is signed or not
|
||||
* @tp_die: a DIE of a type
|
||||
*
|
||||
* Get the encoding of @tp_die and return true if the encoding
|
||||
* is signed.
|
||||
*/
|
||||
bool die_is_signed_type(Dwarf_Die *tp_die)
|
||||
{
|
||||
Dwarf_Word ret;
|
||||
|
||||
if (die_get_attr_udata(tp_die, DW_AT_encoding, &ret))
|
||||
return false;
|
||||
|
||||
return (ret == DW_ATE_signed_char || ret == DW_ATE_signed ||
|
||||
ret == DW_ATE_signed_fixed);
|
||||
}
|
||||
|
||||
/**
|
||||
* die_get_data_member_location - Get the data-member offset
|
||||
* @mb_die: a DIE of a member of a data structure
|
||||
* @offs: The offset of the member in the data structure
|
||||
*
|
||||
* Get the offset of @mb_die in the data structure including @mb_die, and
|
||||
* stores result offset to @offs. If any error occurs this returns errno.
|
||||
*/
|
||||
int die_get_data_member_location(Dwarf_Die *mb_die, Dwarf_Word *offs)
|
||||
{
|
||||
Dwarf_Attribute attr;
|
||||
Dwarf_Op *expr;
|
||||
size_t nexpr;
|
||||
int ret;
|
||||
|
||||
if (dwarf_attr(mb_die, DW_AT_data_member_location, &attr) == NULL)
|
||||
return -ENOENT;
|
||||
|
||||
if (dwarf_formudata(&attr, offs) != 0) {
|
||||
/* DW_AT_data_member_location should be DW_OP_plus_uconst */
|
||||
ret = dwarf_getlocation(&attr, &expr, &nexpr);
|
||||
if (ret < 0 || nexpr == 0)
|
||||
return -ENOENT;
|
||||
|
||||
if (expr[0].atom != DW_OP_plus_uconst || nexpr != 1) {
|
||||
pr_debug("Unable to get offset:Unexpected OP %x (%zd)\n",
|
||||
expr[0].atom, nexpr);
|
||||
return -ENOTSUP;
|
||||
}
|
||||
*offs = (Dwarf_Word)expr[0].number;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* die_find_child - Generic DIE search function in DIE tree
|
||||
* @rt_die: a root DIE
|
||||
* @callback: a callback function
|
||||
* @data: a user data passed to the callback function
|
||||
* @die_mem: a buffer for result DIE
|
||||
*
|
||||
* Trace DIE tree from @rt_die and call @callback for each child DIE.
|
||||
* If @callback returns DIE_FIND_CB_END, this stores the DIE into
|
||||
* @die_mem and returns it. If @callback returns DIE_FIND_CB_CONTINUE,
|
||||
* this continues to trace the tree. Optionally, @callback can return
|
||||
* DIE_FIND_CB_CHILD and DIE_FIND_CB_SIBLING, those means trace only
|
||||
* the children and trace only the siblings respectively.
|
||||
* Returns NULL if @callback can't find any appropriate DIE.
|
||||
*/
|
||||
Dwarf_Die *die_find_child(Dwarf_Die *rt_die,
|
||||
int (*callback)(Dwarf_Die *, void *),
|
||||
void *data, Dwarf_Die *die_mem)
|
||||
{
|
||||
Dwarf_Die child_die;
|
||||
int ret;
|
||||
|
||||
ret = dwarf_child(rt_die, die_mem);
|
||||
if (ret != 0)
|
||||
return NULL;
|
||||
|
||||
do {
|
||||
ret = callback(die_mem, data);
|
||||
if (ret == DIE_FIND_CB_END)
|
||||
return die_mem;
|
||||
|
||||
if ((ret & DIE_FIND_CB_CHILD) &&
|
||||
die_find_child(die_mem, callback, data, &child_die)) {
|
||||
memcpy(die_mem, &child_die, sizeof(Dwarf_Die));
|
||||
return die_mem;
|
||||
}
|
||||
} while ((ret & DIE_FIND_CB_SIBLING) &&
|
||||
dwarf_siblingof(die_mem, die_mem) == 0);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct __addr_die_search_param {
|
||||
Dwarf_Addr addr;
|
||||
Dwarf_Die *die_mem;
|
||||
};
|
||||
|
||||
/* die_find callback for non-inlined function search */
|
||||
static int __die_search_func_cb(Dwarf_Die *fn_die, void *data)
|
||||
{
|
||||
struct __addr_die_search_param *ad = data;
|
||||
|
||||
if (dwarf_tag(fn_die) == DW_TAG_subprogram &&
|
||||
dwarf_haspc(fn_die, ad->addr)) {
|
||||
memcpy(ad->die_mem, fn_die, sizeof(Dwarf_Die));
|
||||
return DWARF_CB_ABORT;
|
||||
}
|
||||
return DWARF_CB_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* die_find_realfunc - Search a non-inlined function at given address
|
||||
* @cu_die: a CU DIE which including @addr
|
||||
* @addr: target address
|
||||
* @die_mem: a buffer for result DIE
|
||||
*
|
||||
* Search a non-inlined function DIE which includes @addr. Stores the
|
||||
* DIE to @die_mem and returns it if found. Returns NULl if failed.
|
||||
*/
|
||||
Dwarf_Die *die_find_realfunc(Dwarf_Die *cu_die, Dwarf_Addr addr,
|
||||
Dwarf_Die *die_mem)
|
||||
{
|
||||
struct __addr_die_search_param ad;
|
||||
ad.addr = addr;
|
||||
ad.die_mem = die_mem;
|
||||
/* dwarf_getscopes can't find subprogram. */
|
||||
if (!dwarf_getfuncs(cu_die, __die_search_func_cb, &ad, 0))
|
||||
return NULL;
|
||||
else
|
||||
return die_mem;
|
||||
}
|
||||
|
||||
/* die_find callback for inline function search */
|
||||
static int __die_find_inline_cb(Dwarf_Die *die_mem, void *data)
|
||||
{
|
||||
Dwarf_Addr *addr = data;
|
||||
|
||||
if (dwarf_tag(die_mem) == DW_TAG_inlined_subroutine &&
|
||||
dwarf_haspc(die_mem, *addr))
|
||||
return DIE_FIND_CB_END;
|
||||
|
||||
return DIE_FIND_CB_CONTINUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* die_find_inlinefunc - Search an inlined function at given address
|
||||
* @cu_die: a CU DIE which including @addr
|
||||
* @addr: target address
|
||||
* @die_mem: a buffer for result DIE
|
||||
*
|
||||
* Search an inlined function DIE which includes @addr. Stores the
|
||||
* DIE to @die_mem and returns it if found. Returns NULl if failed.
|
||||
* If several inlined functions are expanded recursively, this trace
|
||||
* it and returns deepest one.
|
||||
*/
|
||||
Dwarf_Die *die_find_inlinefunc(Dwarf_Die *sp_die, Dwarf_Addr addr,
|
||||
Dwarf_Die *die_mem)
|
||||
{
|
||||
Dwarf_Die tmp_die;
|
||||
|
||||
sp_die = die_find_child(sp_die, __die_find_inline_cb, &addr, &tmp_die);
|
||||
if (!sp_die)
|
||||
return NULL;
|
||||
|
||||
/* Inlined function could be recursive. Trace it until fail */
|
||||
while (sp_die) {
|
||||
memcpy(die_mem, sp_die, sizeof(Dwarf_Die));
|
||||
sp_die = die_find_child(sp_die, __die_find_inline_cb, &addr,
|
||||
&tmp_die);
|
||||
}
|
||||
|
||||
return die_mem;
|
||||
}
|
||||
|
||||
/* Line walker internal parameters */
|
||||
struct __line_walk_param {
|
||||
const char *fname;
|
||||
line_walk_callback_t callback;
|
||||
void *data;
|
||||
int retval;
|
||||
};
|
||||
|
||||
static int __die_walk_funclines_cb(Dwarf_Die *in_die, void *data)
|
||||
{
|
||||
struct __line_walk_param *lw = data;
|
||||
Dwarf_Addr addr;
|
||||
int lineno;
|
||||
|
||||
if (dwarf_tag(in_die) == DW_TAG_inlined_subroutine) {
|
||||
lineno = die_get_call_lineno(in_die);
|
||||
if (lineno > 0 && dwarf_entrypc(in_die, &addr) == 0) {
|
||||
lw->retval = lw->callback(lw->fname, lineno, addr,
|
||||
lw->data);
|
||||
if (lw->retval != 0)
|
||||
return DIE_FIND_CB_END;
|
||||
}
|
||||
}
|
||||
return DIE_FIND_CB_SIBLING;
|
||||
}
|
||||
|
||||
/* Walk on lines of blocks included in given DIE */
|
||||
static int __die_walk_funclines(Dwarf_Die *sp_die,
|
||||
line_walk_callback_t callback, void *data)
|
||||
{
|
||||
struct __line_walk_param lw = {
|
||||
.callback = callback,
|
||||
.data = data,
|
||||
.retval = 0,
|
||||
};
|
||||
Dwarf_Die die_mem;
|
||||
Dwarf_Addr addr;
|
||||
int lineno;
|
||||
|
||||
/* Handle function declaration line */
|
||||
lw.fname = dwarf_decl_file(sp_die);
|
||||
if (lw.fname && dwarf_decl_line(sp_die, &lineno) == 0 &&
|
||||
dwarf_entrypc(sp_die, &addr) == 0) {
|
||||
lw.retval = callback(lw.fname, lineno, addr, data);
|
||||
if (lw.retval != 0)
|
||||
goto done;
|
||||
}
|
||||
die_find_child(sp_die, __die_walk_funclines_cb, &lw, &die_mem);
|
||||
done:
|
||||
return lw.retval;
|
||||
}
|
||||
|
||||
static int __die_walk_culines_cb(Dwarf_Die *sp_die, void *data)
|
||||
{
|
||||
struct __line_walk_param *lw = data;
|
||||
|
||||
lw->retval = __die_walk_funclines(sp_die, lw->callback, lw->data);
|
||||
if (lw->retval != 0)
|
||||
return DWARF_CB_ABORT;
|
||||
|
||||
return DWARF_CB_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* die_walk_lines - Walk on lines inside given DIE
|
||||
* @rt_die: a root DIE (CU or subprogram)
|
||||
* @callback: callback routine
|
||||
* @data: user data
|
||||
*
|
||||
* Walk on all lines inside given @rt_die and call @callback on each line.
|
||||
* If the @rt_die is a function, walk only on the lines inside the function,
|
||||
* otherwise @rt_die must be a CU DIE.
|
||||
* Note that this walks not only dwarf line list, but also function entries
|
||||
* and inline call-site.
|
||||
*/
|
||||
int die_walk_lines(Dwarf_Die *rt_die, line_walk_callback_t callback, void *data)
|
||||
{
|
||||
Dwarf_Lines *lines;
|
||||
Dwarf_Line *line;
|
||||
Dwarf_Addr addr;
|
||||
const char *fname;
|
||||
int lineno, ret = 0;
|
||||
Dwarf_Die die_mem, *cu_die;
|
||||
size_t nlines, i;
|
||||
|
||||
/* Get the CU die */
|
||||
if (dwarf_tag(rt_die) == DW_TAG_subprogram)
|
||||
cu_die = dwarf_diecu(rt_die, &die_mem, NULL, NULL);
|
||||
else
|
||||
cu_die = rt_die;
|
||||
if (!cu_die) {
|
||||
pr_debug2("Failed to get CU from subprogram\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Get lines list in the CU */
|
||||
if (dwarf_getsrclines(cu_die, &lines, &nlines) != 0) {
|
||||
pr_debug2("Failed to get source lines on this CU.\n");
|
||||
return -ENOENT;
|
||||
}
|
||||
pr_debug2("Get %zd lines from this CU\n", nlines);
|
||||
|
||||
/* Walk on the lines on lines list */
|
||||
for (i = 0; i < nlines; i++) {
|
||||
line = dwarf_onesrcline(lines, i);
|
||||
if (line == NULL ||
|
||||
dwarf_lineno(line, &lineno) != 0 ||
|
||||
dwarf_lineaddr(line, &addr) != 0) {
|
||||
pr_debug2("Failed to get line info. "
|
||||
"Possible error in debuginfo.\n");
|
||||
continue;
|
||||
}
|
||||
/* Filter lines based on address */
|
||||
if (rt_die != cu_die)
|
||||
/*
|
||||
* Address filtering
|
||||
* The line is included in given function, and
|
||||
* no inline block includes it.
|
||||
*/
|
||||
if (!dwarf_haspc(rt_die, addr) ||
|
||||
die_find_inlinefunc(rt_die, addr, &die_mem))
|
||||
continue;
|
||||
/* Get source line */
|
||||
fname = dwarf_linesrc(line, NULL, NULL);
|
||||
|
||||
ret = callback(fname, lineno, addr, data);
|
||||
if (ret != 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Dwarf lines doesn't include function declarations and inlined
|
||||
* subroutines. We have to check functions list or given function.
|
||||
*/
|
||||
if (rt_die != cu_die)
|
||||
ret = __die_walk_funclines(rt_die, callback, data);
|
||||
else {
|
||||
struct __line_walk_param param = {
|
||||
.callback = callback,
|
||||
.data = data,
|
||||
.retval = 0,
|
||||
};
|
||||
dwarf_getfuncs(cu_die, __die_walk_culines_cb, ¶m, 0);
|
||||
ret = param.retval;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
struct __find_variable_param {
|
||||
const char *name;
|
||||
Dwarf_Addr addr;
|
||||
};
|
||||
|
||||
static int __die_find_variable_cb(Dwarf_Die *die_mem, void *data)
|
||||
{
|
||||
struct __find_variable_param *fvp = data;
|
||||
int tag;
|
||||
|
||||
tag = dwarf_tag(die_mem);
|
||||
if ((tag == DW_TAG_formal_parameter ||
|
||||
tag == DW_TAG_variable) &&
|
||||
die_compare_name(die_mem, fvp->name))
|
||||
return DIE_FIND_CB_END;
|
||||
|
||||
if (dwarf_haspc(die_mem, fvp->addr))
|
||||
return DIE_FIND_CB_CONTINUE;
|
||||
else
|
||||
return DIE_FIND_CB_SIBLING;
|
||||
}
|
||||
|
||||
/**
|
||||
* die_find_variable_at - Find a given name variable at given address
|
||||
* @sp_die: a function DIE
|
||||
* @name: variable name
|
||||
* @addr: address
|
||||
* @die_mem: a buffer for result DIE
|
||||
*
|
||||
* Find a variable DIE called @name at @addr in @sp_die.
|
||||
*/
|
||||
Dwarf_Die *die_find_variable_at(Dwarf_Die *sp_die, const char *name,
|
||||
Dwarf_Addr addr, Dwarf_Die *die_mem)
|
||||
{
|
||||
struct __find_variable_param fvp = { .name = name, .addr = addr};
|
||||
|
||||
return die_find_child(sp_die, __die_find_variable_cb, (void *)&fvp,
|
||||
die_mem);
|
||||
}
|
||||
|
||||
static int __die_find_member_cb(Dwarf_Die *die_mem, void *data)
|
||||
{
|
||||
const char *name = data;
|
||||
|
||||
if ((dwarf_tag(die_mem) == DW_TAG_member) &&
|
||||
die_compare_name(die_mem, name))
|
||||
return DIE_FIND_CB_END;
|
||||
|
||||
return DIE_FIND_CB_SIBLING;
|
||||
}
|
||||
|
||||
/**
|
||||
* die_find_member - Find a given name member in a data structure
|
||||
* @st_die: a data structure type DIE
|
||||
* @name: member name
|
||||
* @die_mem: a buffer for result DIE
|
||||
*
|
||||
* Find a member DIE called @name in @st_die.
|
||||
*/
|
||||
Dwarf_Die *die_find_member(Dwarf_Die *st_die, const char *name,
|
||||
Dwarf_Die *die_mem)
|
||||
{
|
||||
return die_find_child(st_die, __die_find_member_cb, (void *)name,
|
||||
die_mem);
|
||||
}
|
||||
|
||||
/**
|
||||
* die_get_typename - Get the name of given variable DIE
|
||||
* @vr_die: a variable DIE
|
||||
* @buf: a buffer for result type name
|
||||
* @len: a max-length of @buf
|
||||
*
|
||||
* Get the name of @vr_die and stores it to @buf. Return the actual length
|
||||
* of type name if succeeded. Return -E2BIG if @len is not enough long, and
|
||||
* Return -ENOENT if failed to find type name.
|
||||
* Note that the result will stores typedef name if possible, and stores
|
||||
* "*(function_type)" if the type is a function pointer.
|
||||
*/
|
||||
int die_get_typename(Dwarf_Die *vr_die, char *buf, int len)
|
||||
{
|
||||
Dwarf_Die type;
|
||||
int tag, ret, ret2;
|
||||
const char *tmp = "";
|
||||
|
||||
if (__die_get_real_type(vr_die, &type) == NULL)
|
||||
return -ENOENT;
|
||||
|
||||
tag = dwarf_tag(&type);
|
||||
if (tag == DW_TAG_array_type || tag == DW_TAG_pointer_type)
|
||||
tmp = "*";
|
||||
else if (tag == DW_TAG_subroutine_type) {
|
||||
/* Function pointer */
|
||||
ret = snprintf(buf, len, "(function_type)");
|
||||
return (ret >= len) ? -E2BIG : ret;
|
||||
} else {
|
||||
if (!dwarf_diename(&type))
|
||||
return -ENOENT;
|
||||
if (tag == DW_TAG_union_type)
|
||||
tmp = "union ";
|
||||
else if (tag == DW_TAG_structure_type)
|
||||
tmp = "struct ";
|
||||
/* Write a base name */
|
||||
ret = snprintf(buf, len, "%s%s", tmp, dwarf_diename(&type));
|
||||
return (ret >= len) ? -E2BIG : ret;
|
||||
}
|
||||
ret = die_get_typename(&type, buf, len);
|
||||
if (ret > 0) {
|
||||
ret2 = snprintf(buf + ret, len - ret, "%s", tmp);
|
||||
ret = (ret2 >= len - ret) ? -E2BIG : ret2 + ret;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* die_get_varname - Get the name and type of given variable DIE
|
||||
* @vr_die: a variable DIE
|
||||
* @buf: a buffer for type and variable name
|
||||
* @len: the max-length of @buf
|
||||
*
|
||||
* Get the name and type of @vr_die and stores it in @buf as "type\tname".
|
||||
*/
|
||||
int die_get_varname(Dwarf_Die *vr_die, char *buf, int len)
|
||||
{
|
||||
int ret, ret2;
|
||||
|
||||
ret = die_get_typename(vr_die, buf, len);
|
||||
if (ret < 0) {
|
||||
pr_debug("Failed to get type, make it unknown.\n");
|
||||
ret = snprintf(buf, len, "(unknown_type)");
|
||||
}
|
||||
if (ret > 0) {
|
||||
ret2 = snprintf(buf + ret, len - ret, "\t%s",
|
||||
dwarf_diename(vr_die));
|
||||
ret = (ret2 >= len - ret) ? -E2BIG : ret2 + ret;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
|
@ -0,0 +1,100 @@
|
|||
#ifndef _DWARF_AUX_H
|
||||
#define _DWARF_AUX_H
|
||||
/*
|
||||
* dwarf-aux.h : libdw auxiliary interfaces
|
||||
*
|
||||
* 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 <dwarf.h>
|
||||
#include <elfutils/libdw.h>
|
||||
#include <elfutils/libdwfl.h>
|
||||
#include <elfutils/version.h>
|
||||
|
||||
/* Find the realpath of the target file */
|
||||
extern const char *cu_find_realpath(Dwarf_Die *cu_die, const char *fname);
|
||||
|
||||
/* Get DW_AT_comp_dir (should be NULL with older gcc) */
|
||||
extern const char *cu_get_comp_dir(Dwarf_Die *cu_die);
|
||||
|
||||
/* Get a line number and file name for given address */
|
||||
extern int cu_find_lineinfo(Dwarf_Die *cudie, unsigned long addr,
|
||||
const char **fname, int *lineno);
|
||||
|
||||
/* Compare diename and tname */
|
||||
extern bool die_compare_name(Dwarf_Die *dw_die, const char *tname);
|
||||
|
||||
/* Get callsite line number of inline-function instance */
|
||||
extern int die_get_call_lineno(Dwarf_Die *in_die);
|
||||
|
||||
/* Get type die */
|
||||
extern Dwarf_Die *die_get_type(Dwarf_Die *vr_die, Dwarf_Die *die_mem);
|
||||
|
||||
/* Get a type die, but skip qualifiers and typedef */
|
||||
extern Dwarf_Die *die_get_real_type(Dwarf_Die *vr_die, Dwarf_Die *die_mem);
|
||||
|
||||
/* Check whether the DIE is signed or not */
|
||||
extern bool die_is_signed_type(Dwarf_Die *tp_die);
|
||||
|
||||
/* Get data_member_location offset */
|
||||
extern int die_get_data_member_location(Dwarf_Die *mb_die, Dwarf_Word *offs);
|
||||
|
||||
/* Return values for die_find_child() callbacks */
|
||||
enum {
|
||||
DIE_FIND_CB_END = 0, /* End of Search */
|
||||
DIE_FIND_CB_CHILD = 1, /* Search only children */
|
||||
DIE_FIND_CB_SIBLING = 2, /* Search only siblings */
|
||||
DIE_FIND_CB_CONTINUE = 3, /* Search children and siblings */
|
||||
};
|
||||
|
||||
/* Search child DIEs */
|
||||
extern Dwarf_Die *die_find_child(Dwarf_Die *rt_die,
|
||||
int (*callback)(Dwarf_Die *, void *),
|
||||
void *data, Dwarf_Die *die_mem);
|
||||
|
||||
/* Search a non-inlined function including given address */
|
||||
extern Dwarf_Die *die_find_realfunc(Dwarf_Die *cu_die, Dwarf_Addr addr,
|
||||
Dwarf_Die *die_mem);
|
||||
|
||||
/* Search an inlined function including given address */
|
||||
extern Dwarf_Die *die_find_inlinefunc(Dwarf_Die *sp_die, Dwarf_Addr addr,
|
||||
Dwarf_Die *die_mem);
|
||||
|
||||
/* Walker on lines (Note: line number will not be sorted) */
|
||||
typedef int (* line_walk_callback_t) (const char *fname, int lineno,
|
||||
Dwarf_Addr addr, void *data);
|
||||
|
||||
/*
|
||||
* Walk on lines inside given DIE. If the DIE is a subprogram, walk only on
|
||||
* the lines inside the subprogram, otherwise the DIE must be a CU DIE.
|
||||
*/
|
||||
extern int die_walk_lines(Dwarf_Die *rt_die, line_walk_callback_t callback,
|
||||
void *data);
|
||||
|
||||
/* Find a variable called 'name' at given address */
|
||||
extern Dwarf_Die *die_find_variable_at(Dwarf_Die *sp_die, const char *name,
|
||||
Dwarf_Addr addr, Dwarf_Die *die_mem);
|
||||
|
||||
/* Find a member called 'name' */
|
||||
extern Dwarf_Die *die_find_member(Dwarf_Die *st_die, const char *name,
|
||||
Dwarf_Die *die_mem);
|
||||
|
||||
/* Get the name of given variable DIE */
|
||||
extern int die_get_typename(Dwarf_Die *vr_die, char *buf, int len);
|
||||
|
||||
/* Get the name and type of given variable DIE, stored as "type\tname" */
|
||||
extern int die_get_varname(Dwarf_Die *vr_die, char *buf, int len);
|
||||
#endif
|
|
@ -117,6 +117,10 @@ static struct map *kernel_get_module_map(const char *module)
|
|||
struct rb_node *nd;
|
||||
struct map_groups *grp = &machine.kmaps;
|
||||
|
||||
/* A file path -- this is an offline module */
|
||||
if (module && strchr(module, '/'))
|
||||
return machine__new_module(&machine, 0, module);
|
||||
|
||||
if (!module)
|
||||
module = "kernel";
|
||||
|
||||
|
@ -170,16 +174,24 @@ const char *kernel_get_module_path(const char *module)
|
|||
}
|
||||
|
||||
#ifdef DWARF_SUPPORT
|
||||
static int open_vmlinux(const char *module)
|
||||
/* Open new debuginfo of given module */
|
||||
static struct debuginfo *open_debuginfo(const char *module)
|
||||
{
|
||||
const char *path = kernel_get_module_path(module);
|
||||
if (!path) {
|
||||
pr_err("Failed to find path of %s module.\n",
|
||||
module ?: "kernel");
|
||||
return -ENOENT;
|
||||
const char *path;
|
||||
|
||||
/* A file path -- this is an offline module */
|
||||
if (module && strchr(module, '/'))
|
||||
path = module;
|
||||
else {
|
||||
path = kernel_get_module_path(module);
|
||||
|
||||
if (!path) {
|
||||
pr_err("Failed to find path of %s module.\n",
|
||||
module ?: "kernel");
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
pr_debug("Try to open %s\n", path);
|
||||
return open(path, O_RDONLY);
|
||||
return debuginfo__new(path);
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -193,13 +205,24 @@ static int kprobe_convert_to_perf_probe(struct probe_trace_point *tp,
|
|||
struct map *map;
|
||||
u64 addr;
|
||||
int ret = -ENOENT;
|
||||
struct debuginfo *dinfo;
|
||||
|
||||
sym = __find_kernel_function_by_name(tp->symbol, &map);
|
||||
if (sym) {
|
||||
addr = map->unmap_ip(map, sym->start + tp->offset);
|
||||
pr_debug("try to find %s+%ld@%" PRIx64 "\n", tp->symbol,
|
||||
tp->offset, addr);
|
||||
ret = find_perf_probe_point((unsigned long)addr, pp);
|
||||
|
||||
dinfo = debuginfo__new_online_kernel(addr);
|
||||
if (dinfo) {
|
||||
ret = debuginfo__find_probe_point(dinfo,
|
||||
(unsigned long)addr, pp);
|
||||
debuginfo__delete(dinfo);
|
||||
} else {
|
||||
pr_debug("Failed to open debuginfo at 0x%" PRIx64 "\n",
|
||||
addr);
|
||||
ret = -ENOENT;
|
||||
}
|
||||
}
|
||||
if (ret <= 0) {
|
||||
pr_debug("Failed to find corresponding probes from "
|
||||
|
@ -214,30 +237,70 @@ static int kprobe_convert_to_perf_probe(struct probe_trace_point *tp,
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int add_module_to_probe_trace_events(struct probe_trace_event *tevs,
|
||||
int ntevs, const char *module)
|
||||
{
|
||||
int i, ret = 0;
|
||||
char *tmp;
|
||||
|
||||
if (!module)
|
||||
return 0;
|
||||
|
||||
tmp = strrchr(module, '/');
|
||||
if (tmp) {
|
||||
/* This is a module path -- get the module name */
|
||||
module = strdup(tmp + 1);
|
||||
if (!module)
|
||||
return -ENOMEM;
|
||||
tmp = strchr(module, '.');
|
||||
if (tmp)
|
||||
*tmp = '\0';
|
||||
tmp = (char *)module; /* For free() */
|
||||
}
|
||||
|
||||
for (i = 0; i < ntevs; i++) {
|
||||
tevs[i].point.module = strdup(module);
|
||||
if (!tevs[i].point.module) {
|
||||
ret = -ENOMEM;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (tmp)
|
||||
free(tmp);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Try to find perf_probe_event with debuginfo */
|
||||
static int try_to_find_probe_trace_events(struct perf_probe_event *pev,
|
||||
struct probe_trace_event **tevs,
|
||||
int max_tevs, const char *module)
|
||||
struct probe_trace_event **tevs,
|
||||
int max_tevs, const char *module)
|
||||
{
|
||||
bool need_dwarf = perf_probe_event_need_dwarf(pev);
|
||||
int fd, ntevs;
|
||||
struct debuginfo *dinfo = open_debuginfo(module);
|
||||
int ntevs, ret = 0;
|
||||
|
||||
fd = open_vmlinux(module);
|
||||
if (fd < 0) {
|
||||
if (!dinfo) {
|
||||
if (need_dwarf) {
|
||||
pr_warning("Failed to open debuginfo file.\n");
|
||||
return fd;
|
||||
return -ENOENT;
|
||||
}
|
||||
pr_debug("Could not open vmlinux. Try to use symbols.\n");
|
||||
pr_debug("Could not open debuginfo. Try to use symbols.\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Searching trace events corresponding to probe event */
|
||||
ntevs = find_probe_trace_events(fd, pev, tevs, max_tevs);
|
||||
/* Searching trace events corresponding to a probe event */
|
||||
ntevs = debuginfo__find_trace_events(dinfo, pev, tevs, max_tevs);
|
||||
|
||||
debuginfo__delete(dinfo);
|
||||
|
||||
if (ntevs > 0) { /* Succeeded to find trace events */
|
||||
pr_debug("find %d probe_trace_events.\n", ntevs);
|
||||
return ntevs;
|
||||
if (module)
|
||||
ret = add_module_to_probe_trace_events(*tevs, ntevs,
|
||||
module);
|
||||
return ret < 0 ? ret : ntevs;
|
||||
}
|
||||
|
||||
if (ntevs == 0) { /* No error but failed to find probe point. */
|
||||
|
@ -371,8 +434,9 @@ int show_line_range(struct line_range *lr, const char *module)
|
|||
{
|
||||
int l = 1;
|
||||
struct line_node *ln;
|
||||
struct debuginfo *dinfo;
|
||||
FILE *fp;
|
||||
int fd, ret;
|
||||
int ret;
|
||||
char *tmp;
|
||||
|
||||
/* Search a line range */
|
||||
|
@ -380,13 +444,14 @@ int show_line_range(struct line_range *lr, const char *module)
|
|||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
fd = open_vmlinux(module);
|
||||
if (fd < 0) {
|
||||
dinfo = open_debuginfo(module);
|
||||
if (!dinfo) {
|
||||
pr_warning("Failed to open debuginfo file.\n");
|
||||
return fd;
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
ret = find_line_range(fd, lr);
|
||||
ret = debuginfo__find_line_range(dinfo, lr);
|
||||
debuginfo__delete(dinfo);
|
||||
if (ret == 0) {
|
||||
pr_warning("Specified source line is not found.\n");
|
||||
return -ENOENT;
|
||||
|
@ -448,7 +513,8 @@ end:
|
|||
return ret;
|
||||
}
|
||||
|
||||
static int show_available_vars_at(int fd, struct perf_probe_event *pev,
|
||||
static int show_available_vars_at(struct debuginfo *dinfo,
|
||||
struct perf_probe_event *pev,
|
||||
int max_vls, struct strfilter *_filter,
|
||||
bool externs)
|
||||
{
|
||||
|
@ -463,7 +529,8 @@ static int show_available_vars_at(int fd, struct perf_probe_event *pev,
|
|||
return -EINVAL;
|
||||
pr_debug("Searching variables at %s\n", buf);
|
||||
|
||||
ret = find_available_vars_at(fd, pev, &vls, max_vls, externs);
|
||||
ret = debuginfo__find_available_vars_at(dinfo, pev, &vls,
|
||||
max_vls, externs);
|
||||
if (ret <= 0) {
|
||||
pr_err("Failed to find variables at %s (%d)\n", buf, ret);
|
||||
goto end;
|
||||
|
@ -504,24 +571,26 @@ int show_available_vars(struct perf_probe_event *pevs, int npevs,
|
|||
int max_vls, const char *module,
|
||||
struct strfilter *_filter, bool externs)
|
||||
{
|
||||
int i, fd, ret = 0;
|
||||
int i, ret = 0;
|
||||
struct debuginfo *dinfo;
|
||||
|
||||
ret = init_vmlinux();
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
dinfo = open_debuginfo(module);
|
||||
if (!dinfo) {
|
||||
pr_warning("Failed to open debuginfo file.\n");
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
setup_pager();
|
||||
|
||||
for (i = 0; i < npevs && ret >= 0; i++) {
|
||||
fd = open_vmlinux(module);
|
||||
if (fd < 0) {
|
||||
pr_warning("Failed to open debug information file.\n");
|
||||
ret = fd;
|
||||
break;
|
||||
}
|
||||
ret = show_available_vars_at(fd, &pevs[i], max_vls, _filter,
|
||||
for (i = 0; i < npevs && ret >= 0; i++)
|
||||
ret = show_available_vars_at(dinfo, &pevs[i], max_vls, _filter,
|
||||
externs);
|
||||
}
|
||||
|
||||
debuginfo__delete(dinfo);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
@ -990,7 +1059,7 @@ bool perf_probe_event_need_dwarf(struct perf_probe_event *pev)
|
|||
|
||||
/* Parse probe_events event into struct probe_point */
|
||||
static int parse_probe_trace_command(const char *cmd,
|
||||
struct probe_trace_event *tev)
|
||||
struct probe_trace_event *tev)
|
||||
{
|
||||
struct probe_trace_point *tp = &tev->point;
|
||||
char pr;
|
||||
|
@ -1023,8 +1092,14 @@ static int parse_probe_trace_command(const char *cmd,
|
|||
|
||||
tp->retprobe = (pr == 'r');
|
||||
|
||||
/* Scan function name and offset */
|
||||
ret = sscanf(argv[1], "%a[^+]+%lu", (float *)(void *)&tp->symbol,
|
||||
/* Scan module name(if there), function name and offset */
|
||||
p = strchr(argv[1], ':');
|
||||
if (p) {
|
||||
tp->module = strndup(argv[1], p - argv[1]);
|
||||
p++;
|
||||
} else
|
||||
p = argv[1];
|
||||
ret = sscanf(p, "%a[^+]+%lu", (float *)(void *)&tp->symbol,
|
||||
&tp->offset);
|
||||
if (ret == 1)
|
||||
tp->offset = 0;
|
||||
|
@ -1269,9 +1344,10 @@ char *synthesize_probe_trace_command(struct probe_trace_event *tev)
|
|||
if (buf == NULL)
|
||||
return NULL;
|
||||
|
||||
len = e_snprintf(buf, MAX_CMDLEN, "%c:%s/%s %s+%lu",
|
||||
len = e_snprintf(buf, MAX_CMDLEN, "%c:%s/%s %s%s%s+%lu",
|
||||
tp->retprobe ? 'r' : 'p',
|
||||
tev->group, tev->event,
|
||||
tp->module ?: "", tp->module ? ":" : "",
|
||||
tp->symbol, tp->offset);
|
||||
if (len <= 0)
|
||||
goto error;
|
||||
|
@ -1378,6 +1454,8 @@ static void clear_probe_trace_event(struct probe_trace_event *tev)
|
|||
free(tev->group);
|
||||
if (tev->point.symbol)
|
||||
free(tev->point.symbol);
|
||||
if (tev->point.module)
|
||||
free(tev->point.module);
|
||||
for (i = 0; i < tev->nargs; i++) {
|
||||
if (tev->args[i].name)
|
||||
free(tev->args[i].name);
|
||||
|
@ -1729,7 +1807,7 @@ static int convert_to_probe_trace_events(struct perf_probe_event *pev,
|
|||
/* Convert perf_probe_event with debuginfo */
|
||||
ret = try_to_find_probe_trace_events(pev, tevs, max_tevs, module);
|
||||
if (ret != 0)
|
||||
return ret;
|
||||
return ret; /* Found in debuginfo or got an error */
|
||||
|
||||
/* Allocate trace event buffer */
|
||||
tev = *tevs = zalloc(sizeof(struct probe_trace_event));
|
||||
|
@ -1742,6 +1820,11 @@ static int convert_to_probe_trace_events(struct perf_probe_event *pev,
|
|||
ret = -ENOMEM;
|
||||
goto error;
|
||||
}
|
||||
tev->point.module = strdup(module);
|
||||
if (tev->point.module == NULL) {
|
||||
ret = -ENOMEM;
|
||||
goto error;
|
||||
}
|
||||
tev->point.offset = pev->point.offset;
|
||||
tev->point.retprobe = pev->point.retprobe;
|
||||
tev->nargs = pev->nargs;
|
||||
|
|
|
@ -10,6 +10,7 @@ extern bool probe_event_dry_run;
|
|||
/* kprobe-tracer tracing point */
|
||||
struct probe_trace_point {
|
||||
char *symbol; /* Base symbol */
|
||||
char *module; /* Module name */
|
||||
unsigned long offset; /* Offset from symbol */
|
||||
bool retprobe; /* Return probe flag */
|
||||
};
|
||||
|
|
|
@ -43,21 +43,6 @@
|
|||
/* Kprobe tracer basic type is up to u64 */
|
||||
#define MAX_BASIC_TYPE_BITS 64
|
||||
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
|
||||
/* Line number list operations */
|
||||
|
||||
/* Add a line to line number list */
|
||||
|
@ -131,29 +116,37 @@ static const Dwfl_Callbacks offline_callbacks = {
|
|||
};
|
||||
|
||||
/* Get a Dwarf from offline image */
|
||||
static Dwarf *dwfl_init_offline_dwarf(int fd, Dwfl **dwflp, Dwarf_Addr *bias)
|
||||
static int debuginfo__init_offline_dwarf(struct debuginfo *self,
|
||||
const char *path)
|
||||
{
|
||||
Dwfl_Module *mod;
|
||||
Dwarf *dbg = NULL;
|
||||
int fd;
|
||||
|
||||
if (!dwflp)
|
||||
return NULL;
|
||||
fd = open(path, O_RDONLY);
|
||||
if (fd < 0)
|
||||
return fd;
|
||||
|
||||
*dwflp = dwfl_begin(&offline_callbacks);
|
||||
if (!*dwflp)
|
||||
return NULL;
|
||||
self->dwfl = dwfl_begin(&offline_callbacks);
|
||||
if (!self->dwfl)
|
||||
goto error;
|
||||
|
||||
mod = dwfl_report_offline(*dwflp, "", "", fd);
|
||||
mod = dwfl_report_offline(self->dwfl, "", "", fd);
|
||||
if (!mod)
|
||||
goto error;
|
||||
|
||||
dbg = dwfl_module_getdwarf(mod, bias);
|
||||
if (!dbg) {
|
||||
self->dbg = dwfl_module_getdwarf(mod, &self->bias);
|
||||
if (!self->dbg)
|
||||
goto error;
|
||||
|
||||
return 0;
|
||||
error:
|
||||
dwfl_end(*dwflp);
|
||||
*dwflp = NULL;
|
||||
}
|
||||
return dbg;
|
||||
if (self->dwfl)
|
||||
dwfl_end(self->dwfl);
|
||||
else
|
||||
close(fd);
|
||||
memset(self, 0, sizeof(*self));
|
||||
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
#if _ELFUTILS_PREREQ(0, 148)
|
||||
|
@ -189,597 +182,81 @@ static const Dwfl_Callbacks kernel_callbacks = {
|
|||
};
|
||||
|
||||
/* Get a Dwarf from live kernel image */
|
||||
static Dwarf *dwfl_init_live_kernel_dwarf(Dwarf_Addr addr, Dwfl **dwflp,
|
||||
Dwarf_Addr *bias)
|
||||
static int debuginfo__init_online_kernel_dwarf(struct debuginfo *self,
|
||||
Dwarf_Addr addr)
|
||||
{
|
||||
Dwarf *dbg;
|
||||
|
||||
if (!dwflp)
|
||||
return NULL;
|
||||
|
||||
*dwflp = dwfl_begin(&kernel_callbacks);
|
||||
if (!*dwflp)
|
||||
return NULL;
|
||||
self->dwfl = dwfl_begin(&kernel_callbacks);
|
||||
if (!self->dwfl)
|
||||
return -EINVAL;
|
||||
|
||||
/* Load the kernel dwarves: Don't care the result here */
|
||||
dwfl_linux_kernel_report_kernel(*dwflp);
|
||||
dwfl_linux_kernel_report_modules(*dwflp);
|
||||
dwfl_linux_kernel_report_kernel(self->dwfl);
|
||||
dwfl_linux_kernel_report_modules(self->dwfl);
|
||||
|
||||
dbg = dwfl_addrdwarf(*dwflp, addr, bias);
|
||||
self->dbg = dwfl_addrdwarf(self->dwfl, addr, &self->bias);
|
||||
/* Here, check whether we could get a real dwarf */
|
||||
if (!dbg) {
|
||||
if (!self->dbg) {
|
||||
pr_debug("Failed to find kernel dwarf at %lx\n",
|
||||
(unsigned long)addr);
|
||||
dwfl_end(*dwflp);
|
||||
*dwflp = NULL;
|
||||
dwfl_end(self->dwfl);
|
||||
memset(self, 0, sizeof(*self));
|
||||
return -ENOENT;
|
||||
}
|
||||
return dbg;
|
||||
|
||||
return 0;
|
||||
}
|
||||
#else
|
||||
/* With older elfutils, this just support kernel module... */
|
||||
static Dwarf *dwfl_init_live_kernel_dwarf(Dwarf_Addr addr __used, Dwfl **dwflp,
|
||||
Dwarf_Addr *bias)
|
||||
static int debuginfo__init_online_kernel_dwarf(struct debuginfo *self,
|
||||
Dwarf_Addr addr __used)
|
||||
{
|
||||
int fd;
|
||||
const char *path = kernel_get_module_path("kernel");
|
||||
|
||||
if (!path) {
|
||||
pr_err("Failed to find vmlinux path\n");
|
||||
return NULL;
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
pr_debug2("Use file %s for debuginfo\n", path);
|
||||
fd = open(path, O_RDONLY);
|
||||
if (fd < 0)
|
||||
return NULL;
|
||||
|
||||
return dwfl_init_offline_dwarf(fd, dwflp, bias);
|
||||
return debuginfo__init_offline_dwarf(self, path);
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Dwarf wrappers */
|
||||
|
||||
/* Find the realpath of the target file. */
|
||||
static const char *cu_find_realpath(Dwarf_Die *cu_die, const char *fname)
|
||||
struct debuginfo *debuginfo__new(const char *path)
|
||||
{
|
||||
Dwarf_Files *files;
|
||||
size_t nfiles, i;
|
||||
const char *src = NULL;
|
||||
int ret;
|
||||
|
||||
if (!fname)
|
||||
struct debuginfo *self = zalloc(sizeof(struct debuginfo));
|
||||
if (!self)
|
||||
return NULL;
|
||||
|
||||
ret = dwarf_getsrcfiles(cu_die, &files, &nfiles);
|
||||
if (ret != 0)
|
||||
if (debuginfo__init_offline_dwarf(self, path) < 0) {
|
||||
free(self);
|
||||
self = NULL;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
struct debuginfo *debuginfo__new_online_kernel(unsigned long addr)
|
||||
{
|
||||
struct debuginfo *self = zalloc(sizeof(struct debuginfo));
|
||||
if (!self)
|
||||
return NULL;
|
||||
|
||||
for (i = 0; i < nfiles; i++) {
|
||||
src = dwarf_filesrc(files, i, NULL, NULL);
|
||||
if (strtailcmp(src, fname) == 0)
|
||||
break;
|
||||
}
|
||||
if (i == nfiles)
|
||||
return NULL;
|
||||
return src;
|
||||
}
|
||||
|
||||
/* Get DW_AT_comp_dir (should be NULL with older gcc) */
|
||||
static const char *cu_get_comp_dir(Dwarf_Die *cu_die)
|
||||
{
|
||||
Dwarf_Attribute attr;
|
||||
if (dwarf_attr(cu_die, DW_AT_comp_dir, &attr) == NULL)
|
||||
return NULL;
|
||||
return dwarf_formstring(&attr);
|
||||
}
|
||||
|
||||
/* Get a line number and file name for given address */
|
||||
static int cu_find_lineinfo(Dwarf_Die *cudie, unsigned long addr,
|
||||
const char **fname, int *lineno)
|
||||
{
|
||||
Dwarf_Line *line;
|
||||
Dwarf_Addr laddr;
|
||||
|
||||
line = dwarf_getsrc_die(cudie, (Dwarf_Addr)addr);
|
||||
if (line && dwarf_lineaddr(line, &laddr) == 0 &&
|
||||
addr == (unsigned long)laddr && dwarf_lineno(line, lineno) == 0) {
|
||||
*fname = dwarf_linesrc(line, NULL, NULL);
|
||||
if (!*fname)
|
||||
/* line number is useless without filename */
|
||||
*lineno = 0;
|
||||
if (debuginfo__init_online_kernel_dwarf(self, (Dwarf_Addr)addr) < 0) {
|
||||
free(self);
|
||||
self = NULL;
|
||||
}
|
||||
|
||||
return *lineno ?: -ENOENT;
|
||||
return self;
|
||||
}
|
||||
|
||||
/* Compare diename and tname */
|
||||
static bool die_compare_name(Dwarf_Die *dw_die, const char *tname)
|
||||
void debuginfo__delete(struct debuginfo *self)
|
||||
{
|
||||
const char *name;
|
||||
name = dwarf_diename(dw_die);
|
||||
return name ? (strcmp(tname, name) == 0) : false;
|
||||
}
|
||||
|
||||
/* Get callsite line number of inline-function instance */
|
||||
static int die_get_call_lineno(Dwarf_Die *in_die)
|
||||
{
|
||||
Dwarf_Attribute attr;
|
||||
Dwarf_Word ret;
|
||||
|
||||
if (!dwarf_attr(in_die, DW_AT_call_line, &attr))
|
||||
return -ENOENT;
|
||||
|
||||
dwarf_formudata(&attr, &ret);
|
||||
return (int)ret;
|
||||
}
|
||||
|
||||
/* Get type die */
|
||||
static Dwarf_Die *die_get_type(Dwarf_Die *vr_die, Dwarf_Die *die_mem)
|
||||
{
|
||||
Dwarf_Attribute attr;
|
||||
|
||||
if (dwarf_attr_integrate(vr_die, DW_AT_type, &attr) &&
|
||||
dwarf_formref_die(&attr, die_mem))
|
||||
return die_mem;
|
||||
else
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Get a type die, but skip qualifiers */
|
||||
static Dwarf_Die *__die_get_real_type(Dwarf_Die *vr_die, Dwarf_Die *die_mem)
|
||||
{
|
||||
int tag;
|
||||
|
||||
do {
|
||||
vr_die = die_get_type(vr_die, die_mem);
|
||||
if (!vr_die)
|
||||
break;
|
||||
tag = dwarf_tag(vr_die);
|
||||
} while (tag == DW_TAG_const_type ||
|
||||
tag == DW_TAG_restrict_type ||
|
||||
tag == DW_TAG_volatile_type ||
|
||||
tag == DW_TAG_shared_type);
|
||||
|
||||
return vr_die;
|
||||
}
|
||||
|
||||
/* Get a type die, but skip qualifiers and typedef */
|
||||
static Dwarf_Die *die_get_real_type(Dwarf_Die *vr_die, Dwarf_Die *die_mem)
|
||||
{
|
||||
do {
|
||||
vr_die = __die_get_real_type(vr_die, die_mem);
|
||||
} while (vr_die && dwarf_tag(vr_die) == DW_TAG_typedef);
|
||||
|
||||
return vr_die;
|
||||
}
|
||||
|
||||
static int die_get_attr_udata(Dwarf_Die *tp_die, unsigned int attr_name,
|
||||
Dwarf_Word *result)
|
||||
{
|
||||
Dwarf_Attribute attr;
|
||||
|
||||
if (dwarf_attr(tp_die, attr_name, &attr) == NULL ||
|
||||
dwarf_formudata(&attr, result) != 0)
|
||||
return -ENOENT;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool die_is_signed_type(Dwarf_Die *tp_die)
|
||||
{
|
||||
Dwarf_Word ret;
|
||||
|
||||
if (die_get_attr_udata(tp_die, DW_AT_encoding, &ret))
|
||||
return false;
|
||||
|
||||
return (ret == DW_ATE_signed_char || ret == DW_ATE_signed ||
|
||||
ret == DW_ATE_signed_fixed);
|
||||
}
|
||||
|
||||
static int die_get_byte_size(Dwarf_Die *tp_die)
|
||||
{
|
||||
Dwarf_Word ret;
|
||||
|
||||
if (die_get_attr_udata(tp_die, DW_AT_byte_size, &ret))
|
||||
return 0;
|
||||
|
||||
return (int)ret;
|
||||
}
|
||||
|
||||
static int die_get_bit_size(Dwarf_Die *tp_die)
|
||||
{
|
||||
Dwarf_Word ret;
|
||||
|
||||
if (die_get_attr_udata(tp_die, DW_AT_bit_size, &ret))
|
||||
return 0;
|
||||
|
||||
return (int)ret;
|
||||
}
|
||||
|
||||
static int die_get_bit_offset(Dwarf_Die *tp_die)
|
||||
{
|
||||
Dwarf_Word ret;
|
||||
|
||||
if (die_get_attr_udata(tp_die, DW_AT_bit_offset, &ret))
|
||||
return 0;
|
||||
|
||||
return (int)ret;
|
||||
}
|
||||
|
||||
/* Get data_member_location offset */
|
||||
static int die_get_data_member_location(Dwarf_Die *mb_die, Dwarf_Word *offs)
|
||||
{
|
||||
Dwarf_Attribute attr;
|
||||
Dwarf_Op *expr;
|
||||
size_t nexpr;
|
||||
int ret;
|
||||
|
||||
if (dwarf_attr(mb_die, DW_AT_data_member_location, &attr) == NULL)
|
||||
return -ENOENT;
|
||||
|
||||
if (dwarf_formudata(&attr, offs) != 0) {
|
||||
/* DW_AT_data_member_location should be DW_OP_plus_uconst */
|
||||
ret = dwarf_getlocation(&attr, &expr, &nexpr);
|
||||
if (ret < 0 || nexpr == 0)
|
||||
return -ENOENT;
|
||||
|
||||
if (expr[0].atom != DW_OP_plus_uconst || nexpr != 1) {
|
||||
pr_debug("Unable to get offset:Unexpected OP %x (%zd)\n",
|
||||
expr[0].atom, nexpr);
|
||||
return -ENOTSUP;
|
||||
}
|
||||
*offs = (Dwarf_Word)expr[0].number;
|
||||
if (self) {
|
||||
if (self->dwfl)
|
||||
dwfl_end(self->dwfl);
|
||||
free(self);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Return values for die_find callbacks */
|
||||
enum {
|
||||
DIE_FIND_CB_FOUND = 0, /* End of Search */
|
||||
DIE_FIND_CB_CHILD = 1, /* Search only children */
|
||||
DIE_FIND_CB_SIBLING = 2, /* Search only siblings */
|
||||
DIE_FIND_CB_CONTINUE = 3, /* Search children and siblings */
|
||||
};
|
||||
|
||||
/* Search a child die */
|
||||
static Dwarf_Die *die_find_child(Dwarf_Die *rt_die,
|
||||
int (*callback)(Dwarf_Die *, void *),
|
||||
void *data, Dwarf_Die *die_mem)
|
||||
{
|
||||
Dwarf_Die child_die;
|
||||
int ret;
|
||||
|
||||
ret = dwarf_child(rt_die, die_mem);
|
||||
if (ret != 0)
|
||||
return NULL;
|
||||
|
||||
do {
|
||||
ret = callback(die_mem, data);
|
||||
if (ret == DIE_FIND_CB_FOUND)
|
||||
return die_mem;
|
||||
|
||||
if ((ret & DIE_FIND_CB_CHILD) &&
|
||||
die_find_child(die_mem, callback, data, &child_die)) {
|
||||
memcpy(die_mem, &child_die, sizeof(Dwarf_Die));
|
||||
return die_mem;
|
||||
}
|
||||
} while ((ret & DIE_FIND_CB_SIBLING) &&
|
||||
dwarf_siblingof(die_mem, die_mem) == 0);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct __addr_die_search_param {
|
||||
Dwarf_Addr addr;
|
||||
Dwarf_Die *die_mem;
|
||||
};
|
||||
|
||||
static int __die_search_func_cb(Dwarf_Die *fn_die, void *data)
|
||||
{
|
||||
struct __addr_die_search_param *ad = data;
|
||||
|
||||
if (dwarf_tag(fn_die) == DW_TAG_subprogram &&
|
||||
dwarf_haspc(fn_die, ad->addr)) {
|
||||
memcpy(ad->die_mem, fn_die, sizeof(Dwarf_Die));
|
||||
return DWARF_CB_ABORT;
|
||||
}
|
||||
return DWARF_CB_OK;
|
||||
}
|
||||
|
||||
/* Search a real subprogram including this line, */
|
||||
static Dwarf_Die *die_find_real_subprogram(Dwarf_Die *cu_die, Dwarf_Addr addr,
|
||||
Dwarf_Die *die_mem)
|
||||
{
|
||||
struct __addr_die_search_param ad;
|
||||
ad.addr = addr;
|
||||
ad.die_mem = die_mem;
|
||||
/* dwarf_getscopes can't find subprogram. */
|
||||
if (!dwarf_getfuncs(cu_die, __die_search_func_cb, &ad, 0))
|
||||
return NULL;
|
||||
else
|
||||
return die_mem;
|
||||
}
|
||||
|
||||
/* die_find callback for inline function search */
|
||||
static int __die_find_inline_cb(Dwarf_Die *die_mem, void *data)
|
||||
{
|
||||
Dwarf_Addr *addr = data;
|
||||
|
||||
if (dwarf_tag(die_mem) == DW_TAG_inlined_subroutine &&
|
||||
dwarf_haspc(die_mem, *addr))
|
||||
return DIE_FIND_CB_FOUND;
|
||||
|
||||
return DIE_FIND_CB_CONTINUE;
|
||||
}
|
||||
|
||||
/* Similar to dwarf_getfuncs, but returns inlined_subroutine if exists. */
|
||||
static Dwarf_Die *die_find_inlinefunc(Dwarf_Die *sp_die, Dwarf_Addr addr,
|
||||
Dwarf_Die *die_mem)
|
||||
{
|
||||
Dwarf_Die tmp_die;
|
||||
|
||||
sp_die = die_find_child(sp_die, __die_find_inline_cb, &addr, &tmp_die);
|
||||
if (!sp_die)
|
||||
return NULL;
|
||||
|
||||
/* Inlined function could be recursive. Trace it until fail */
|
||||
while (sp_die) {
|
||||
memcpy(die_mem, sp_die, sizeof(Dwarf_Die));
|
||||
sp_die = die_find_child(sp_die, __die_find_inline_cb, &addr,
|
||||
&tmp_die);
|
||||
}
|
||||
|
||||
return die_mem;
|
||||
}
|
||||
|
||||
/* Walker on lines (Note: line number will not be sorted) */
|
||||
typedef int (* line_walk_handler_t) (const char *fname, int lineno,
|
||||
Dwarf_Addr addr, void *data);
|
||||
|
||||
struct __line_walk_param {
|
||||
const char *fname;
|
||||
line_walk_handler_t handler;
|
||||
void *data;
|
||||
int retval;
|
||||
};
|
||||
|
||||
static int __die_walk_funclines_cb(Dwarf_Die *in_die, void *data)
|
||||
{
|
||||
struct __line_walk_param *lw = data;
|
||||
Dwarf_Addr addr;
|
||||
int lineno;
|
||||
|
||||
if (dwarf_tag(in_die) == DW_TAG_inlined_subroutine) {
|
||||
lineno = die_get_call_lineno(in_die);
|
||||
if (lineno > 0 && dwarf_entrypc(in_die, &addr) == 0) {
|
||||
lw->retval = lw->handler(lw->fname, lineno, addr,
|
||||
lw->data);
|
||||
if (lw->retval != 0)
|
||||
return DIE_FIND_CB_FOUND;
|
||||
}
|
||||
}
|
||||
return DIE_FIND_CB_SIBLING;
|
||||
}
|
||||
|
||||
/* Walk on lines of blocks included in given DIE */
|
||||
static int __die_walk_funclines(Dwarf_Die *sp_die,
|
||||
line_walk_handler_t handler, void *data)
|
||||
{
|
||||
struct __line_walk_param lw = {
|
||||
.handler = handler,
|
||||
.data = data,
|
||||
.retval = 0,
|
||||
};
|
||||
Dwarf_Die die_mem;
|
||||
Dwarf_Addr addr;
|
||||
int lineno;
|
||||
|
||||
/* Handle function declaration line */
|
||||
lw.fname = dwarf_decl_file(sp_die);
|
||||
if (lw.fname && dwarf_decl_line(sp_die, &lineno) == 0 &&
|
||||
dwarf_entrypc(sp_die, &addr) == 0) {
|
||||
lw.retval = handler(lw.fname, lineno, addr, data);
|
||||
if (lw.retval != 0)
|
||||
goto done;
|
||||
}
|
||||
die_find_child(sp_die, __die_walk_funclines_cb, &lw, &die_mem);
|
||||
done:
|
||||
return lw.retval;
|
||||
}
|
||||
|
||||
static int __die_walk_culines_cb(Dwarf_Die *sp_die, void *data)
|
||||
{
|
||||
struct __line_walk_param *lw = data;
|
||||
|
||||
lw->retval = __die_walk_funclines(sp_die, lw->handler, lw->data);
|
||||
if (lw->retval != 0)
|
||||
return DWARF_CB_ABORT;
|
||||
|
||||
return DWARF_CB_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
* Walk on lines inside given PDIE. If the PDIE is subprogram, walk only on
|
||||
* the lines inside the subprogram, otherwise PDIE must be a CU DIE.
|
||||
*/
|
||||
static int die_walk_lines(Dwarf_Die *pdie, line_walk_handler_t handler,
|
||||
void *data)
|
||||
{
|
||||
Dwarf_Lines *lines;
|
||||
Dwarf_Line *line;
|
||||
Dwarf_Addr addr;
|
||||
const char *fname;
|
||||
int lineno, ret = 0;
|
||||
Dwarf_Die die_mem, *cu_die;
|
||||
size_t nlines, i;
|
||||
|
||||
/* Get the CU die */
|
||||
if (dwarf_tag(pdie) == DW_TAG_subprogram)
|
||||
cu_die = dwarf_diecu(pdie, &die_mem, NULL, NULL);
|
||||
else
|
||||
cu_die = pdie;
|
||||
if (!cu_die) {
|
||||
pr_debug2("Failed to get CU from subprogram\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Get lines list in the CU */
|
||||
if (dwarf_getsrclines(cu_die, &lines, &nlines) != 0) {
|
||||
pr_debug2("Failed to get source lines on this CU.\n");
|
||||
return -ENOENT;
|
||||
}
|
||||
pr_debug2("Get %zd lines from this CU\n", nlines);
|
||||
|
||||
/* Walk on the lines on lines list */
|
||||
for (i = 0; i < nlines; i++) {
|
||||
line = dwarf_onesrcline(lines, i);
|
||||
if (line == NULL ||
|
||||
dwarf_lineno(line, &lineno) != 0 ||
|
||||
dwarf_lineaddr(line, &addr) != 0) {
|
||||
pr_debug2("Failed to get line info. "
|
||||
"Possible error in debuginfo.\n");
|
||||
continue;
|
||||
}
|
||||
/* Filter lines based on address */
|
||||
if (pdie != cu_die)
|
||||
/*
|
||||
* Address filtering
|
||||
* The line is included in given function, and
|
||||
* no inline block includes it.
|
||||
*/
|
||||
if (!dwarf_haspc(pdie, addr) ||
|
||||
die_find_inlinefunc(pdie, addr, &die_mem))
|
||||
continue;
|
||||
/* Get source line */
|
||||
fname = dwarf_linesrc(line, NULL, NULL);
|
||||
|
||||
ret = handler(fname, lineno, addr, data);
|
||||
if (ret != 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Dwarf lines doesn't include function declarations and inlined
|
||||
* subroutines. We have to check functions list or given function.
|
||||
*/
|
||||
if (pdie != cu_die)
|
||||
ret = __die_walk_funclines(pdie, handler, data);
|
||||
else {
|
||||
struct __line_walk_param param = {
|
||||
.handler = handler,
|
||||
.data = data,
|
||||
.retval = 0,
|
||||
};
|
||||
dwarf_getfuncs(cu_die, __die_walk_culines_cb, ¶m, 0);
|
||||
ret = param.retval;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
struct __find_variable_param {
|
||||
const char *name;
|
||||
Dwarf_Addr addr;
|
||||
};
|
||||
|
||||
static int __die_find_variable_cb(Dwarf_Die *die_mem, void *data)
|
||||
{
|
||||
struct __find_variable_param *fvp = data;
|
||||
int tag;
|
||||
|
||||
tag = dwarf_tag(die_mem);
|
||||
if ((tag == DW_TAG_formal_parameter ||
|
||||
tag == DW_TAG_variable) &&
|
||||
die_compare_name(die_mem, fvp->name))
|
||||
return DIE_FIND_CB_FOUND;
|
||||
|
||||
if (dwarf_haspc(die_mem, fvp->addr))
|
||||
return DIE_FIND_CB_CONTINUE;
|
||||
else
|
||||
return DIE_FIND_CB_SIBLING;
|
||||
}
|
||||
|
||||
/* Find a variable called 'name' at given address */
|
||||
static Dwarf_Die *die_find_variable_at(Dwarf_Die *sp_die, const char *name,
|
||||
Dwarf_Addr addr, Dwarf_Die *die_mem)
|
||||
{
|
||||
struct __find_variable_param fvp = { .name = name, .addr = addr};
|
||||
|
||||
return die_find_child(sp_die, __die_find_variable_cb, (void *)&fvp,
|
||||
die_mem);
|
||||
}
|
||||
|
||||
static int __die_find_member_cb(Dwarf_Die *die_mem, void *data)
|
||||
{
|
||||
const char *name = data;
|
||||
|
||||
if ((dwarf_tag(die_mem) == DW_TAG_member) &&
|
||||
die_compare_name(die_mem, name))
|
||||
return DIE_FIND_CB_FOUND;
|
||||
|
||||
return DIE_FIND_CB_SIBLING;
|
||||
}
|
||||
|
||||
/* Find a member called 'name' */
|
||||
static Dwarf_Die *die_find_member(Dwarf_Die *st_die, const char *name,
|
||||
Dwarf_Die *die_mem)
|
||||
{
|
||||
return die_find_child(st_die, __die_find_member_cb, (void *)name,
|
||||
die_mem);
|
||||
}
|
||||
|
||||
/* Get the name of given variable DIE */
|
||||
static int die_get_typename(Dwarf_Die *vr_die, char *buf, int len)
|
||||
{
|
||||
Dwarf_Die type;
|
||||
int tag, ret, ret2;
|
||||
const char *tmp = "";
|
||||
|
||||
if (__die_get_real_type(vr_die, &type) == NULL)
|
||||
return -ENOENT;
|
||||
|
||||
tag = dwarf_tag(&type);
|
||||
if (tag == DW_TAG_array_type || tag == DW_TAG_pointer_type)
|
||||
tmp = "*";
|
||||
else if (tag == DW_TAG_subroutine_type) {
|
||||
/* Function pointer */
|
||||
ret = snprintf(buf, len, "(function_type)");
|
||||
return (ret >= len) ? -E2BIG : ret;
|
||||
} else {
|
||||
if (!dwarf_diename(&type))
|
||||
return -ENOENT;
|
||||
if (tag == DW_TAG_union_type)
|
||||
tmp = "union ";
|
||||
else if (tag == DW_TAG_structure_type)
|
||||
tmp = "struct ";
|
||||
/* Write a base name */
|
||||
ret = snprintf(buf, len, "%s%s", tmp, dwarf_diename(&type));
|
||||
return (ret >= len) ? -E2BIG : ret;
|
||||
}
|
||||
ret = die_get_typename(&type, buf, len);
|
||||
if (ret > 0) {
|
||||
ret2 = snprintf(buf + ret, len - ret, "%s", tmp);
|
||||
ret = (ret2 >= len - ret) ? -E2BIG : ret2 + ret;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Get the name and type of given variable DIE, stored as "type\tname" */
|
||||
static int die_get_varname(Dwarf_Die *vr_die, char *buf, int len)
|
||||
{
|
||||
int ret, ret2;
|
||||
|
||||
ret = die_get_typename(vr_die, buf, len);
|
||||
if (ret < 0) {
|
||||
pr_debug("Failed to get type, make it unknown.\n");
|
||||
ret = snprintf(buf, len, "(unknown_type)");
|
||||
}
|
||||
if (ret > 0) {
|
||||
ret2 = snprintf(buf + ret, len - ret, "\t%s",
|
||||
dwarf_diename(vr_die));
|
||||
ret = (ret2 >= len - ret) ? -E2BIG : ret2 + ret;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -897,6 +374,7 @@ static int convert_variable_type(Dwarf_Die *vr_die,
|
|||
struct probe_trace_arg_ref **ref_ptr = &tvar->ref;
|
||||
Dwarf_Die type;
|
||||
char buf[16];
|
||||
int bsize, boffs, total;
|
||||
int ret;
|
||||
|
||||
/* TODO: check all types */
|
||||
|
@ -906,11 +384,15 @@ static int convert_variable_type(Dwarf_Die *vr_die,
|
|||
return (tvar->type == NULL) ? -ENOMEM : 0;
|
||||
}
|
||||
|
||||
if (die_get_bit_size(vr_die) != 0) {
|
||||
bsize = dwarf_bitsize(vr_die);
|
||||
if (bsize > 0) {
|
||||
/* This is a bitfield */
|
||||
ret = snprintf(buf, 16, "b%d@%d/%zd", die_get_bit_size(vr_die),
|
||||
die_get_bit_offset(vr_die),
|
||||
BYTES_TO_BITS(die_get_byte_size(vr_die)));
|
||||
boffs = dwarf_bitoffset(vr_die);
|
||||
total = dwarf_bytesize(vr_die);
|
||||
if (boffs < 0 || total < 0)
|
||||
return -ENOENT;
|
||||
ret = snprintf(buf, 16, "b%d@%d/%zd", bsize, boffs,
|
||||
BYTES_TO_BITS(total));
|
||||
goto formatted;
|
||||
}
|
||||
|
||||
|
@ -958,10 +440,11 @@ static int convert_variable_type(Dwarf_Die *vr_die,
|
|||
return (tvar->type == NULL) ? -ENOMEM : 0;
|
||||
}
|
||||
|
||||
ret = BYTES_TO_BITS(die_get_byte_size(&type));
|
||||
if (!ret)
|
||||
ret = dwarf_bytesize(&type);
|
||||
if (ret <= 0)
|
||||
/* No size ... try to use default type */
|
||||
return 0;
|
||||
ret = BYTES_TO_BITS(ret);
|
||||
|
||||
/* Check the bitwidth */
|
||||
if (ret > MAX_BASIC_TYPE_BITS) {
|
||||
|
@ -1025,7 +508,7 @@ static int convert_variable_fields(Dwarf_Die *vr_die, const char *varname,
|
|||
else
|
||||
*ref_ptr = ref;
|
||||
}
|
||||
ref->offset += die_get_byte_size(&type) * field->index;
|
||||
ref->offset += dwarf_bytesize(&type) * field->index;
|
||||
if (!field->next)
|
||||
/* Save vr_die for converting types */
|
||||
memcpy(die_mem, vr_die, sizeof(*die_mem));
|
||||
|
@ -1245,8 +728,7 @@ static int call_probe_finder(Dwarf_Die *sp_die, struct probe_finder *pf)
|
|||
|
||||
/* If no real subprogram, find a real one */
|
||||
if (!sp_die || dwarf_tag(sp_die) != DW_TAG_subprogram) {
|
||||
sp_die = die_find_real_subprogram(&pf->cu_die,
|
||||
pf->addr, &die_mem);
|
||||
sp_die = die_find_realfunc(&pf->cu_die, pf->addr, &die_mem);
|
||||
if (!sp_die) {
|
||||
pr_warning("Failed to find probe point in any "
|
||||
"functions.\n");
|
||||
|
@ -1504,28 +986,18 @@ static int pubname_search_cb(Dwarf *dbg, Dwarf_Global *gl, void *data)
|
|||
}
|
||||
|
||||
/* Find probe points from debuginfo */
|
||||
static int find_probes(int fd, struct probe_finder *pf)
|
||||
static int debuginfo__find_probes(struct debuginfo *self,
|
||||
struct probe_finder *pf)
|
||||
{
|
||||
struct perf_probe_point *pp = &pf->pev->point;
|
||||
Dwarf_Off off, noff;
|
||||
size_t cuhl;
|
||||
Dwarf_Die *diep;
|
||||
Dwarf *dbg = NULL;
|
||||
Dwfl *dwfl;
|
||||
Dwarf_Addr bias; /* Currently ignored */
|
||||
int ret = 0;
|
||||
|
||||
dbg = dwfl_init_offline_dwarf(fd, &dwfl, &bias);
|
||||
if (!dbg) {
|
||||
pr_warning("No debug information found in the vmlinux - "
|
||||
"please rebuild with CONFIG_DEBUG_INFO=y.\n");
|
||||
close(fd); /* Without dwfl_end(), fd isn't closed. */
|
||||
return -EBADF;
|
||||
}
|
||||
|
||||
#if _ELFUTILS_PREREQ(0, 142)
|
||||
/* Get the call frame information from this dwarf */
|
||||
pf->cfi = dwarf_getcfi(dbg);
|
||||
pf->cfi = dwarf_getcfi(self->dbg);
|
||||
#endif
|
||||
|
||||
off = 0;
|
||||
|
@ -1544,7 +1016,8 @@ static int find_probes(int fd, struct probe_finder *pf)
|
|||
.data = pf,
|
||||
};
|
||||
|
||||
dwarf_getpubnames(dbg, pubname_search_cb, &pubname_param, 0);
|
||||
dwarf_getpubnames(self->dbg, pubname_search_cb,
|
||||
&pubname_param, 0);
|
||||
if (pubname_param.found) {
|
||||
ret = probe_point_search_cb(&pf->sp_die, &probe_param);
|
||||
if (ret)
|
||||
|
@ -1553,9 +1026,9 @@ static int find_probes(int fd, struct probe_finder *pf)
|
|||
}
|
||||
|
||||
/* Loop on CUs (Compilation Unit) */
|
||||
while (!dwarf_nextcu(dbg, off, &noff, &cuhl, NULL, NULL, NULL)) {
|
||||
while (!dwarf_nextcu(self->dbg, off, &noff, &cuhl, NULL, NULL, NULL)) {
|
||||
/* Get the DIE(Debugging Information Entry) of this CU */
|
||||
diep = dwarf_offdie(dbg, off + cuhl, &pf->cu_die);
|
||||
diep = dwarf_offdie(self->dbg, off + cuhl, &pf->cu_die);
|
||||
if (!diep)
|
||||
continue;
|
||||
|
||||
|
@ -1582,8 +1055,6 @@ static int find_probes(int fd, struct probe_finder *pf)
|
|||
|
||||
found:
|
||||
line_list__free(&pf->lcache);
|
||||
if (dwfl)
|
||||
dwfl_end(dwfl);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
@ -1629,8 +1100,9 @@ static int add_probe_trace_event(Dwarf_Die *sp_die, struct probe_finder *pf)
|
|||
}
|
||||
|
||||
/* Find probe_trace_events specified by perf_probe_event from debuginfo */
|
||||
int find_probe_trace_events(int fd, struct perf_probe_event *pev,
|
||||
struct probe_trace_event **tevs, int max_tevs)
|
||||
int debuginfo__find_trace_events(struct debuginfo *self,
|
||||
struct perf_probe_event *pev,
|
||||
struct probe_trace_event **tevs, int max_tevs)
|
||||
{
|
||||
struct trace_event_finder tf = {
|
||||
.pf = {.pev = pev, .callback = add_probe_trace_event},
|
||||
|
@ -1645,7 +1117,7 @@ int find_probe_trace_events(int fd, struct perf_probe_event *pev,
|
|||
tf.tevs = *tevs;
|
||||
tf.ntevs = 0;
|
||||
|
||||
ret = find_probes(fd, &tf.pf);
|
||||
ret = debuginfo__find_probes(self, &tf.pf);
|
||||
if (ret < 0) {
|
||||
free(*tevs);
|
||||
*tevs = NULL;
|
||||
|
@ -1739,9 +1211,10 @@ out:
|
|||
}
|
||||
|
||||
/* Find available variables at given probe point */
|
||||
int find_available_vars_at(int fd, struct perf_probe_event *pev,
|
||||
struct variable_list **vls, int max_vls,
|
||||
bool externs)
|
||||
int debuginfo__find_available_vars_at(struct debuginfo *self,
|
||||
struct perf_probe_event *pev,
|
||||
struct variable_list **vls,
|
||||
int max_vls, bool externs)
|
||||
{
|
||||
struct available_var_finder af = {
|
||||
.pf = {.pev = pev, .callback = add_available_vars},
|
||||
|
@ -1756,7 +1229,7 @@ int find_available_vars_at(int fd, struct perf_probe_event *pev,
|
|||
af.vls = *vls;
|
||||
af.nvls = 0;
|
||||
|
||||
ret = find_probes(fd, &af.pf);
|
||||
ret = debuginfo__find_probes(self, &af.pf);
|
||||
if (ret < 0) {
|
||||
/* Free vlist for error */
|
||||
while (af.nvls--) {
|
||||
|
@ -1774,28 +1247,19 @@ int find_available_vars_at(int fd, struct perf_probe_event *pev,
|
|||
}
|
||||
|
||||
/* Reverse search */
|
||||
int find_perf_probe_point(unsigned long addr, struct perf_probe_point *ppt)
|
||||
int debuginfo__find_probe_point(struct debuginfo *self, unsigned long addr,
|
||||
struct perf_probe_point *ppt)
|
||||
{
|
||||
Dwarf_Die cudie, spdie, indie;
|
||||
Dwarf *dbg = NULL;
|
||||
Dwfl *dwfl = NULL;
|
||||
Dwarf_Addr _addr, baseaddr, bias = 0;
|
||||
Dwarf_Addr _addr, baseaddr;
|
||||
const char *fname = NULL, *func = NULL, *tmp;
|
||||
int baseline = 0, lineno = 0, ret = 0;
|
||||
|
||||
/* Open the live linux kernel */
|
||||
dbg = dwfl_init_live_kernel_dwarf(addr, &dwfl, &bias);
|
||||
if (!dbg) {
|
||||
pr_warning("No debug information found in the vmlinux - "
|
||||
"please rebuild with CONFIG_DEBUG_INFO=y.\n");
|
||||
ret = -EINVAL;
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* Adjust address with bias */
|
||||
addr += bias;
|
||||
addr += self->bias;
|
||||
|
||||
/* Find cu die */
|
||||
if (!dwarf_addrdie(dbg, (Dwarf_Addr)addr - bias, &cudie)) {
|
||||
if (!dwarf_addrdie(self->dbg, (Dwarf_Addr)addr - self->bias, &cudie)) {
|
||||
pr_warning("Failed to find debug information for address %lx\n",
|
||||
addr);
|
||||
ret = -EINVAL;
|
||||
|
@ -1807,7 +1271,7 @@ int find_perf_probe_point(unsigned long addr, struct perf_probe_point *ppt)
|
|||
/* Don't care whether it failed or not */
|
||||
|
||||
/* Find a corresponding function (name, baseline and baseaddr) */
|
||||
if (die_find_real_subprogram(&cudie, (Dwarf_Addr)addr, &spdie)) {
|
||||
if (die_find_realfunc(&cudie, (Dwarf_Addr)addr, &spdie)) {
|
||||
/* Get function entry information */
|
||||
tmp = dwarf_diename(&spdie);
|
||||
if (!tmp ||
|
||||
|
@ -1871,8 +1335,6 @@ post:
|
|||
}
|
||||
}
|
||||
end:
|
||||
if (dwfl)
|
||||
dwfl_end(dwfl);
|
||||
if (ret == 0 && (fname || func))
|
||||
ret = 1; /* Found a point */
|
||||
return ret;
|
||||
|
@ -1982,26 +1444,15 @@ static int find_line_range_by_func(struct line_finder *lf)
|
|||
return param.retval;
|
||||
}
|
||||
|
||||
int find_line_range(int fd, struct line_range *lr)
|
||||
int debuginfo__find_line_range(struct debuginfo *self, struct line_range *lr)
|
||||
{
|
||||
struct line_finder lf = {.lr = lr, .found = 0};
|
||||
int ret = 0;
|
||||
Dwarf_Off off = 0, noff;
|
||||
size_t cuhl;
|
||||
Dwarf_Die *diep;
|
||||
Dwarf *dbg = NULL;
|
||||
Dwfl *dwfl;
|
||||
Dwarf_Addr bias; /* Currently ignored */
|
||||
const char *comp_dir;
|
||||
|
||||
dbg = dwfl_init_offline_dwarf(fd, &dwfl, &bias);
|
||||
if (!dbg) {
|
||||
pr_warning("No debug information found in the vmlinux - "
|
||||
"please rebuild with CONFIG_DEBUG_INFO=y.\n");
|
||||
close(fd); /* Without dwfl_end(), fd isn't closed. */
|
||||
return -EBADF;
|
||||
}
|
||||
|
||||
/* Fastpath: lookup by function name from .debug_pubnames section */
|
||||
if (lr->function) {
|
||||
struct pubname_callback_param pubname_param = {
|
||||
|
@ -2010,7 +1461,8 @@ int find_line_range(int fd, struct line_range *lr)
|
|||
struct dwarf_callback_param line_range_param = {
|
||||
.data = (void *)&lf, .retval = 0};
|
||||
|
||||
dwarf_getpubnames(dbg, pubname_search_cb, &pubname_param, 0);
|
||||
dwarf_getpubnames(self->dbg, pubname_search_cb,
|
||||
&pubname_param, 0);
|
||||
if (pubname_param.found) {
|
||||
line_range_search_cb(&lf.sp_die, &line_range_param);
|
||||
if (lf.found)
|
||||
|
@ -2020,11 +1472,12 @@ int find_line_range(int fd, struct line_range *lr)
|
|||
|
||||
/* Loop on CUs (Compilation Unit) */
|
||||
while (!lf.found && ret >= 0) {
|
||||
if (dwarf_nextcu(dbg, off, &noff, &cuhl, NULL, NULL, NULL) != 0)
|
||||
if (dwarf_nextcu(self->dbg, off, &noff, &cuhl,
|
||||
NULL, NULL, NULL) != 0)
|
||||
break;
|
||||
|
||||
/* Get the DIE(Debugging Information Entry) of this CU */
|
||||
diep = dwarf_offdie(dbg, off + cuhl, &lf.cu_die);
|
||||
diep = dwarf_offdie(self->dbg, off + cuhl, &lf.cu_die);
|
||||
if (!diep)
|
||||
continue;
|
||||
|
||||
|
@ -2058,7 +1511,6 @@ found:
|
|||
}
|
||||
|
||||
pr_debug("path: %s\n", lr->path);
|
||||
dwfl_end(dwfl);
|
||||
return (ret < 0) ? ret : lf.found;
|
||||
}
|
||||
|
||||
|
|
|
@ -16,27 +16,42 @@ static inline int is_c_varname(const char *name)
|
|||
}
|
||||
|
||||
#ifdef DWARF_SUPPORT
|
||||
|
||||
#include "dwarf-aux.h"
|
||||
|
||||
/* TODO: export debuginfo data structure even if no dwarf support */
|
||||
|
||||
/* debug information structure */
|
||||
struct debuginfo {
|
||||
Dwarf *dbg;
|
||||
Dwfl *dwfl;
|
||||
Dwarf_Addr bias;
|
||||
};
|
||||
|
||||
extern struct debuginfo *debuginfo__new(const char *path);
|
||||
extern struct debuginfo *debuginfo__new_online_kernel(unsigned long addr);
|
||||
extern void debuginfo__delete(struct debuginfo *self);
|
||||
|
||||
/* Find probe_trace_events specified by perf_probe_event from debuginfo */
|
||||
extern int find_probe_trace_events(int fd, struct perf_probe_event *pev,
|
||||
struct probe_trace_event **tevs,
|
||||
int max_tevs);
|
||||
extern int debuginfo__find_trace_events(struct debuginfo *self,
|
||||
struct perf_probe_event *pev,
|
||||
struct probe_trace_event **tevs,
|
||||
int max_tevs);
|
||||
|
||||
/* Find a perf_probe_point from debuginfo */
|
||||
extern int find_perf_probe_point(unsigned long addr,
|
||||
struct perf_probe_point *ppt);
|
||||
extern int debuginfo__find_probe_point(struct debuginfo *self,
|
||||
unsigned long addr,
|
||||
struct perf_probe_point *ppt);
|
||||
|
||||
/* Find a line range */
|
||||
extern int find_line_range(int fd, struct line_range *lr);
|
||||
extern int debuginfo__find_line_range(struct debuginfo *self,
|
||||
struct line_range *lr);
|
||||
|
||||
/* Find available variables */
|
||||
extern int find_available_vars_at(int fd, struct perf_probe_event *pev,
|
||||
struct variable_list **vls, int max_points,
|
||||
bool externs);
|
||||
|
||||
#include <dwarf.h>
|
||||
#include <elfutils/libdw.h>
|
||||
#include <elfutils/libdwfl.h>
|
||||
#include <elfutils/version.h>
|
||||
extern int debuginfo__find_available_vars_at(struct debuginfo *self,
|
||||
struct perf_probe_event *pev,
|
||||
struct variable_list **vls,
|
||||
int max_points, bool externs);
|
||||
|
||||
struct probe_finder {
|
||||
struct perf_probe_event *pev; /* Target probe event */
|
||||
|
|
|
@ -294,3 +294,22 @@ bool strlazymatch(const char *str, const char *pat)
|
|||
{
|
||||
return __match_glob(str, pat, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* strtailcmp - Compare the tail of two strings
|
||||
* @s1: 1st string to be compared
|
||||
* @s2: 2nd string to be compared
|
||||
*
|
||||
* Return 0 if whole of either string is same as another's tail part.
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
|
@ -183,11 +183,20 @@ int bigendian(void)
|
|||
return *ptr == 0x01020304;
|
||||
}
|
||||
|
||||
static unsigned long long copy_file_fd(int fd)
|
||||
/* unfortunately, you can not stat debugfs or proc files for size */
|
||||
static void record_file(const char *file, size_t hdr_sz)
|
||||
{
|
||||
unsigned long long size = 0;
|
||||
char buf[BUFSIZ];
|
||||
int r;
|
||||
char buf[BUFSIZ], *sizep;
|
||||
off_t hdr_pos = lseek(output_fd, 0, SEEK_CUR);
|
||||
int r, fd;
|
||||
|
||||
fd = open(file, O_RDONLY);
|
||||
if (fd < 0)
|
||||
die("Can't read '%s'", file);
|
||||
|
||||
/* put in zeros for file size, then fill true size later */
|
||||
write_or_die(&size, hdr_sz);
|
||||
|
||||
do {
|
||||
r = read(fd, buf, BUFSIZ);
|
||||
|
@ -196,93 +205,37 @@ static unsigned long long copy_file_fd(int fd)
|
|||
write_or_die(buf, r);
|
||||
}
|
||||
} while (r > 0);
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
static unsigned long long copy_file(const char *file)
|
||||
{
|
||||
unsigned long long size = 0;
|
||||
int fd;
|
||||
|
||||
fd = open(file, O_RDONLY);
|
||||
if (fd < 0)
|
||||
die("Can't read '%s'", file);
|
||||
size = copy_file_fd(fd);
|
||||
close(fd);
|
||||
|
||||
return size;
|
||||
}
|
||||
/* ugh, handle big-endian hdr_size == 4 */
|
||||
sizep = (char*)&size;
|
||||
if (bigendian())
|
||||
sizep += sizeof(u64) - hdr_sz;
|
||||
|
||||
static unsigned long get_size_fd(int fd)
|
||||
{
|
||||
unsigned long long size = 0;
|
||||
char buf[BUFSIZ];
|
||||
int r;
|
||||
|
||||
do {
|
||||
r = read(fd, buf, BUFSIZ);
|
||||
if (r > 0)
|
||||
size += r;
|
||||
} while (r > 0);
|
||||
|
||||
lseek(fd, 0, SEEK_SET);
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
static unsigned long get_size(const char *file)
|
||||
{
|
||||
unsigned long long size = 0;
|
||||
int fd;
|
||||
|
||||
fd = open(file, O_RDONLY);
|
||||
if (fd < 0)
|
||||
die("Can't read '%s'", file);
|
||||
size = get_size_fd(fd);
|
||||
close(fd);
|
||||
|
||||
return size;
|
||||
if (pwrite(output_fd, sizep, hdr_sz, hdr_pos) < 0)
|
||||
die("writing to %s", output_file);
|
||||
}
|
||||
|
||||
static void read_header_files(void)
|
||||
{
|
||||
unsigned long long size, check_size;
|
||||
char *path;
|
||||
int fd;
|
||||
struct stat st;
|
||||
|
||||
path = get_tracing_file("events/header_page");
|
||||
fd = open(path, O_RDONLY);
|
||||
if (fd < 0)
|
||||
if (stat(path, &st) < 0)
|
||||
die("can't read '%s'", path);
|
||||
|
||||
/* unfortunately, you can not stat debugfs files for size */
|
||||
size = get_size_fd(fd);
|
||||
|
||||
write_or_die("header_page", 12);
|
||||
write_or_die(&size, 8);
|
||||
check_size = copy_file_fd(fd);
|
||||
close(fd);
|
||||
|
||||
if (size != check_size)
|
||||
die("wrong size for '%s' size=%lld read=%lld",
|
||||
path, size, check_size);
|
||||
record_file(path, 8);
|
||||
put_tracing_file(path);
|
||||
|
||||
path = get_tracing_file("events/header_event");
|
||||
fd = open(path, O_RDONLY);
|
||||
if (fd < 0)
|
||||
if (stat(path, &st) < 0)
|
||||
die("can't read '%s'", path);
|
||||
|
||||
size = get_size_fd(fd);
|
||||
|
||||
write_or_die("header_event", 13);
|
||||
write_or_die(&size, 8);
|
||||
check_size = copy_file_fd(fd);
|
||||
if (size != check_size)
|
||||
die("wrong size for '%s'", path);
|
||||
record_file(path, 8);
|
||||
put_tracing_file(path);
|
||||
close(fd);
|
||||
}
|
||||
|
||||
static bool name_in_tp_list(char *sys, struct tracepoint_path *tps)
|
||||
|
@ -298,7 +251,6 @@ static bool name_in_tp_list(char *sys, struct tracepoint_path *tps)
|
|||
|
||||
static void copy_event_system(const char *sys, struct tracepoint_path *tps)
|
||||
{
|
||||
unsigned long long size, check_size;
|
||||
struct dirent *dent;
|
||||
struct stat st;
|
||||
char *format;
|
||||
|
@ -338,14 +290,8 @@ static void copy_event_system(const char *sys, struct tracepoint_path *tps)
|
|||
sprintf(format, "%s/%s/format", sys, dent->d_name);
|
||||
ret = stat(format, &st);
|
||||
|
||||
if (ret >= 0) {
|
||||
/* unfortunately, you can not stat debugfs files for size */
|
||||
size = get_size(format);
|
||||
write_or_die(&size, 8);
|
||||
check_size = copy_file(format);
|
||||
if (size != check_size)
|
||||
die("error in size of file '%s'", format);
|
||||
}
|
||||
if (ret >= 0)
|
||||
record_file(format, 8);
|
||||
|
||||
free(format);
|
||||
}
|
||||
|
@ -426,7 +372,7 @@ static void read_event_files(struct tracepoint_path *tps)
|
|||
|
||||
static void read_proc_kallsyms(void)
|
||||
{
|
||||
unsigned int size, check_size;
|
||||
unsigned int size;
|
||||
const char *path = "/proc/kallsyms";
|
||||
struct stat st;
|
||||
int ret;
|
||||
|
@ -438,17 +384,12 @@ static void read_proc_kallsyms(void)
|
|||
write_or_die(&size, 4);
|
||||
return;
|
||||
}
|
||||
size = get_size(path);
|
||||
write_or_die(&size, 4);
|
||||
check_size = copy_file(path);
|
||||
if (size != check_size)
|
||||
die("error in size of file '%s'", path);
|
||||
|
||||
record_file(path, 4);
|
||||
}
|
||||
|
||||
static void read_ftrace_printk(void)
|
||||
{
|
||||
unsigned int size, check_size;
|
||||
unsigned int size;
|
||||
char *path;
|
||||
struct stat st;
|
||||
int ret;
|
||||
|
@ -461,11 +402,8 @@ static void read_ftrace_printk(void)
|
|||
write_or_die(&size, 4);
|
||||
goto out;
|
||||
}
|
||||
size = get_size(path);
|
||||
write_or_die(&size, 4);
|
||||
check_size = copy_file(path);
|
||||
if (size != check_size)
|
||||
die("error in size of file '%s'", path);
|
||||
record_file(path, 4);
|
||||
|
||||
out:
|
||||
put_tracing_file(path);
|
||||
}
|
||||
|
|
|
@ -238,6 +238,7 @@ char **argv_split(const char *str, int *argcp);
|
|||
void argv_free(char **argv);
|
||||
bool strglobmatch(const char *str, const char *pat);
|
||||
bool strlazymatch(const char *str, const char *pat);
|
||||
int strtailcmp(const char *s1, const char *s2);
|
||||
unsigned long convert_unit(unsigned long value, char *unit);
|
||||
int readn(int fd, void *buf, size_t size);
|
||||
|
||||
|
|
Loading…
Reference in New Issue