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:
Ingo Molnar 2011-07-21 09:32:40 +02:00
commit 40bcea7bbe
26 changed files with 1670 additions and 1010 deletions

View File

@ -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.

View File

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

View File

@ -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.

View File

@ -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,
/*

View File

@ -76,6 +76,7 @@ struct trace_iterator {
struct trace_entry *ent;
unsigned long lost_events;
int leftover;
int ent_size;
int cpu;
u64 ts;

View File

@ -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)

View File

@ -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;

View File

@ -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 *

View File

@ -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 */

View File

@ -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"

View File

@ -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();

View File

@ -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

View File

@ -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;

View File

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

View File

@ -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::

View File

@ -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

View File

@ -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", &params.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", &params.max_probe_points,

663
tools/perf/util/dwarf-aux.c Normal file
View File

@ -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, &param, 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;
}

100
tools/perf/util/dwarf-aux.h Normal file
View File

@ -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

View File

@ -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;

View File

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

View File

@ -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, &param, 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;
}

View File

@ -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 */

View File

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

View File

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

View File

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