Commit 40bcea7b authored by Ingo Molnar's avatar Ingo Molnar

Merge branch 'tip/perf/core' of...

Merge branch 'tip/perf/core' of git://git.kernel.org/pub/scm/linux/kernel/git/rostedt/linux-2.6-trace into perf/core
parents 492f73a3 14a8fd7c
...@@ -22,14 +22,15 @@ current_tracer. Instead of that, add probe points via ...@@ -22,14 +22,15 @@ current_tracer. Instead of that, add probe points via
Synopsis of kprobe_events Synopsis of kprobe_events
------------------------- -------------------------
p[:[GRP/]EVENT] SYMBOL[+offs]|MEMADDR [FETCHARGS] : Set a probe p[:[GRP/]EVENT] [MOD:]SYM[+offs]|MEMADDR [FETCHARGS] : Set a probe
r[:[GRP/]EVENT] SYMBOL[+0] [FETCHARGS] : Set a return probe r[:[GRP/]EVENT] [MOD:]SYM[+0] [FETCHARGS] : Set a return probe
-:[GRP/]EVENT : Clear a probe -:[GRP/]EVENT : Clear a probe
GRP : Group name. If omitted, use "kprobes" for it. GRP : Group name. If omitted, use "kprobes" for it.
EVENT : Event name. If omitted, the event name is generated EVENT : Event name. If omitted, the event name is generated
based on SYMBOL+offs or MEMADDR. based on SYM+offs or MEMADDR.
SYMBOL[+offs] : Symbol+offset where the probe is inserted. MOD : Module name which has given SYM.
SYM[+offs] : Symbol+offset where the probe is inserted.
MEMADDR : Address where the probe is inserted. MEMADDR : Address where the probe is inserted.
FETCHARGS : Arguments. Each probe can have up to 128 args. FETCHARGS : Arguments. Each probe can have up to 128 args.
......
...@@ -101,6 +101,14 @@ ...@@ -101,6 +101,14 @@
#define P4_CONFIG_HT_SHIFT 63 #define P4_CONFIG_HT_SHIFT 63
#define P4_CONFIG_HT (1ULL << P4_CONFIG_HT_SHIFT) #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 * The bits we allow to pass for RAW events
*/ */
...@@ -123,6 +131,31 @@ ...@@ -123,6 +131,31 @@
(p4_config_pack_escr(P4_CONFIG_MASK_ESCR)) | \ (p4_config_pack_escr(P4_CONFIG_MASK_ESCR)) | \
(p4_config_pack_cccr(P4_CONFIG_MASK_CCCR)) (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) static inline bool p4_is_event_cascaded(u64 config)
{ {
u32 cccr = p4_config_unpack_cccr(config); u32 cccr = p4_config_unpack_cccr(config);
......
...@@ -274,7 +274,6 @@ struct x86_pmu { ...@@ -274,7 +274,6 @@ struct x86_pmu {
void (*enable_all)(int added); void (*enable_all)(int added);
void (*enable)(struct perf_event *); void (*enable)(struct perf_event *);
void (*disable)(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 (*hw_config)(struct perf_event *event);
int (*schedule_events)(struct cpu_hw_events *cpuc, int n, int *assign); int (*schedule_events)(struct cpu_hw_events *cpuc, int n, int *assign);
unsigned eventsel; unsigned eventsel;
...@@ -360,12 +359,6 @@ static u64 __read_mostly hw_cache_extra_regs ...@@ -360,12 +359,6 @@ static u64 __read_mostly hw_cache_extra_regs
[PERF_COUNT_HW_CACHE_OP_MAX] [PERF_COUNT_HW_CACHE_OP_MAX]
[PERF_COUNT_HW_CACHE_RESULT_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. * Propagate event elapsed time into the generic event.
* Can only be executed on the CPU where the event is active. * Can only be executed on the CPU where the event is active.
......
...@@ -570,11 +570,92 @@ static __initconst const u64 p4_hw_cache_event_ids ...@@ -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] = { static u64 p4_general_events[PERF_COUNT_HW_MAX] = {
/* non-halted CPU clocks */ /* non-halted CPU clocks */
[PERF_COUNT_HW_CPU_CYCLES] = [PERF_COUNT_HW_CPU_CYCLES] =
p4_config_pack_escr(P4_ESCR_EVENT(P4_EVENT_GLOBAL_POWER_EVENTS) | 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 * retired instructions
...@@ -719,31 +800,6 @@ static int p4_validate_raw_event(struct perf_event *event) ...@@ -719,31 +800,6 @@ static int p4_validate_raw_event(struct perf_event *event)
return 0; 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) static int p4_hw_config(struct perf_event *event)
{ {
int cpu = get_cpu(); int cpu = get_cpu();
...@@ -1159,6 +1215,8 @@ static int p4_pmu_schedule_events(struct cpu_hw_events *cpuc, int n, int *assign ...@@ -1159,6 +1215,8 @@ static int p4_pmu_schedule_events(struct cpu_hw_events *cpuc, int n, int *assign
struct p4_event_bind *bind; struct p4_event_bind *bind;
unsigned int i, thread, num; unsigned int i, thread, num;
int cntr_idx, escr_idx; int cntr_idx, escr_idx;
u64 config_alias;
int pass;
bitmap_zero(used_mask, X86_PMC_IDX_MAX); bitmap_zero(used_mask, X86_PMC_IDX_MAX);
bitmap_zero(escr_mask, P4_ESCR_MSR_TABLE_SIZE); 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 ...@@ -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; hwc = &cpuc->event_list[i]->hw;
thread = p4_ht_thread(cpu); 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); bind = p4_config_get_bind(hwc->config);
escr_idx = p4_get_escr_idx(bind->escr_msr[thread]); escr_idx = p4_get_escr_idx(bind->escr_msr[thread]);
if (unlikely(escr_idx == -1)) if (unlikely(escr_idx == -1))
...@@ -1180,8 +1249,17 @@ static int p4_pmu_schedule_events(struct cpu_hw_events *cpuc, int n, int *assign ...@@ -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); cntr_idx = p4_next_cntr(thread, used_mask, bind);
if (cntr_idx == -1 || test_bit(escr_idx, escr_mask)) if (cntr_idx == -1 || test_bit(escr_idx, escr_mask)) {
goto done; /*
* 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); p4_pmu_swap_config_ts(hwc, cpu);
if (assign) if (assign)
...@@ -1218,7 +1296,6 @@ static __initconst const struct x86_pmu p4_pmu = { ...@@ -1218,7 +1296,6 @@ static __initconst const struct x86_pmu p4_pmu = {
.cntval_bits = ARCH_P4_CNTRVAL_BITS, .cntval_bits = ARCH_P4_CNTRVAL_BITS,
.cntval_mask = ARCH_P4_CNTRVAL_MASK, .cntval_mask = ARCH_P4_CNTRVAL_MASK,
.max_period = (1ULL << (ARCH_P4_CNTRVAL_BITS - 1)) - 1, .max_period = (1ULL << (ARCH_P4_CNTRVAL_BITS - 1)) - 1,
.hw_watchdog_set_attr = p4_hw_watchdog_set_attr,
.hw_config = p4_hw_config, .hw_config = p4_hw_config,
.schedule_events = p4_pmu_schedule_events, .schedule_events = p4_pmu_schedule_events,
/* /*
......
...@@ -76,6 +76,7 @@ struct trace_iterator { ...@@ -76,6 +76,7 @@ struct trace_iterator {
struct trace_entry *ent; struct trace_entry *ent;
unsigned long lost_events; unsigned long lost_events;
int leftover; int leftover;
int ent_size;
int cpu; int cpu;
u64 ts; u64 ts;
......
...@@ -1255,19 +1255,29 @@ static int __kprobes in_kprobes_functions(unsigned long addr) ...@@ -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 * 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. * 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) static kprobe_opcode_t __kprobes *kprobe_addr(struct kprobe *p)
{ {
kprobe_opcode_t *addr = p->addr; kprobe_opcode_t *addr = p->addr;
if ((p->symbol_name && p->addr) ||
(!p->symbol_name && !p->addr))
goto invalid;
if (p->symbol_name) { if (p->symbol_name) {
if (addr)
return NULL;
kprobe_lookup_name(p->symbol_name, addr); kprobe_lookup_name(p->symbol_name, addr);
if (!addr)
return ERR_PTR(-ENOENT);
} }
if (!addr) addr = (kprobe_opcode_t *)(((char *)addr) + p->offset);
return NULL; if (addr)
return (kprobe_opcode_t *)(((char *)addr) + p->offset); return addr;
invalid:
return ERR_PTR(-EINVAL);
} }
/* Check passed kprobe is valid and return kprobe in kprobe_table. */ /* Check passed kprobe is valid and return kprobe in kprobe_table. */
...@@ -1311,8 +1321,8 @@ int __kprobes register_kprobe(struct kprobe *p) ...@@ -1311,8 +1321,8 @@ int __kprobes register_kprobe(struct kprobe *p)
kprobe_opcode_t *addr; kprobe_opcode_t *addr;
addr = kprobe_addr(p); addr = kprobe_addr(p);
if (!addr) if (IS_ERR(addr))
return -EINVAL; return PTR_ERR(addr);
p->addr = addr; p->addr = addr;
ret = check_kprobe_rereg(p); ret = check_kprobe_rereg(p);
...@@ -1335,6 +1345,8 @@ int __kprobes register_kprobe(struct kprobe *p) ...@@ -1335,6 +1345,8 @@ int __kprobes register_kprobe(struct kprobe *p)
*/ */
probed_mod = __module_text_address((unsigned long) p->addr); probed_mod = __module_text_address((unsigned long) p->addr);
if (probed_mod) { if (probed_mod) {
/* Return -ENOENT if fail. */
ret = -ENOENT;
/* /*
* We must hold a refcount of the probed module while updating * We must hold a refcount of the probed module while updating
* its code to prohibit unexpected unloading. * its code to prohibit unexpected unloading.
...@@ -1351,6 +1363,7 @@ int __kprobes register_kprobe(struct kprobe *p) ...@@ -1351,6 +1363,7 @@ int __kprobes register_kprobe(struct kprobe *p)
module_put(probed_mod); module_put(probed_mod);
goto fail_with_jump_label; goto fail_with_jump_label;
} }
/* ret will be updated by following code */
} }
preempt_enable(); preempt_enable();
jump_label_unlock(); jump_label_unlock();
...@@ -1399,7 +1412,7 @@ int __kprobes register_kprobe(struct kprobe *p) ...@@ -1399,7 +1412,7 @@ int __kprobes register_kprobe(struct kprobe *p)
fail_with_jump_label: fail_with_jump_label:
preempt_enable(); preempt_enable();
jump_label_unlock(); jump_label_unlock();
return -EINVAL; return ret;
} }
EXPORT_SYMBOL_GPL(register_kprobe); EXPORT_SYMBOL_GPL(register_kprobe);
...@@ -1686,8 +1699,8 @@ int __kprobes register_kretprobe(struct kretprobe *rp) ...@@ -1686,8 +1699,8 @@ int __kprobes register_kretprobe(struct kretprobe *rp)
if (kretprobe_blacklist_size) { if (kretprobe_blacklist_size) {
addr = kprobe_addr(&rp->kp); addr = kprobe_addr(&rp->kp);
if (!addr) if (IS_ERR(addr))
return -EINVAL; return PTR_ERR(addr);
for (i = 0; kretprobe_blacklist[i].name != NULL; i++) { for (i = 0; kretprobe_blacklist[i].name != NULL; i++) {
if (kretprobe_blacklist[i].addr == addr) if (kretprobe_blacklist[i].addr == addr)
......
...@@ -88,6 +88,7 @@ static struct ftrace_ops ftrace_list_end __read_mostly = { ...@@ -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_global_list __read_mostly = &ftrace_list_end;
static struct ftrace_ops *ftrace_ops_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; 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_trace_function __read_mostly = ftrace_stub;
ftrace_func_t ftrace_pid_function __read_mostly = ftrace_stub; ftrace_func_t ftrace_pid_function __read_mostly = ftrace_stub;
static struct ftrace_ops global_ops; static struct ftrace_ops global_ops;
...@@ -146,9 +147,11 @@ void clear_ftrace_function(void) ...@@ -146,9 +147,11 @@ void clear_ftrace_function(void)
{ {
ftrace_trace_function = ftrace_stub; ftrace_trace_function = ftrace_stub;
__ftrace_trace_function = ftrace_stub; __ftrace_trace_function = ftrace_stub;
__ftrace_trace_function_delay = ftrace_stub;
ftrace_pid_function = ftrace_stub; ftrace_pid_function = ftrace_stub;
} }
#undef CONFIG_HAVE_FUNCTION_TRACE_MCOUNT_TEST
#ifndef 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 * For those archs that do not test ftrace_trace_stop in their
...@@ -207,8 +210,13 @@ static void update_ftrace_function(void) ...@@ -207,8 +210,13 @@ static void update_ftrace_function(void)
#ifdef CONFIG_HAVE_FUNCTION_TRACE_MCOUNT_TEST #ifdef CONFIG_HAVE_FUNCTION_TRACE_MCOUNT_TEST
ftrace_trace_function = func; ftrace_trace_function = func;
#else
#ifdef CONFIG_DYNAMIC_FTRACE
/* do not update till all functions have been modified */
__ftrace_trace_function_delay = func;
#else #else
__ftrace_trace_function = func; __ftrace_trace_function = func;
#endif
ftrace_trace_function = ftrace_test_stop_func; ftrace_trace_function = ftrace_test_stop_func;
#endif #endif
} }
...@@ -1170,8 +1178,14 @@ alloc_and_copy_ftrace_hash(int size_bits, struct ftrace_hash *hash) ...@@ -1170,8 +1178,14 @@ alloc_and_copy_ftrace_hash(int size_bits, struct ftrace_hash *hash)
return NULL; 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 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 ftrace_func_entry *entry;
struct hlist_node *tp, *tn; struct hlist_node *tp, *tn;
...@@ -1181,8 +1195,15 @@ ftrace_hash_move(struct ftrace_hash **dst, struct ftrace_hash *src) ...@@ -1181,8 +1195,15 @@ ftrace_hash_move(struct ftrace_hash **dst, struct ftrace_hash *src)
unsigned long key; unsigned long key;
int size = src->count; int size = src->count;
int bits = 0; int bits = 0;
int ret;
int i; 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 * If the new source is empty, just free dst and assign it
* the empty_hash. * the empty_hash.
...@@ -1203,9 +1224,10 @@ ftrace_hash_move(struct ftrace_hash **dst, struct ftrace_hash *src) ...@@ -1203,9 +1224,10 @@ ftrace_hash_move(struct ftrace_hash **dst, struct ftrace_hash *src)
if (bits > FTRACE_HASH_MAX_BITS) if (bits > FTRACE_HASH_MAX_BITS)
bits = FTRACE_HASH_MAX_BITS; bits = FTRACE_HASH_MAX_BITS;
ret = -ENOMEM;
new_hash = alloc_ftrace_hash(bits); new_hash = alloc_ftrace_hash(bits);
if (!new_hash) if (!new_hash)
return -ENOMEM; goto out;
size = 1 << src->size_bits; size = 1 << src->size_bits;
for (i = 0; i < size; i++) { for (i = 0; i < size; i++) {
...@@ -1224,7 +1246,16 @@ ftrace_hash_move(struct ftrace_hash **dst, struct ftrace_hash *src) ...@@ -1224,7 +1246,16 @@ ftrace_hash_move(struct ftrace_hash **dst, struct ftrace_hash *src)
rcu_assign_pointer(*dst, new_hash); rcu_assign_pointer(*dst, new_hash);
free_ftrace_hash_rcu(old_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) ...@@ -1584,6 +1615,12 @@ static int __ftrace_modify_code(void *data)
{ {
int *command = 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) if (*command & FTRACE_ENABLE_CALLS)
ftrace_replace_code(1); ftrace_replace_code(1);
else if (*command & FTRACE_DISABLE_CALLS) else if (*command & FTRACE_DISABLE_CALLS)
...@@ -1597,6 +1634,18 @@ static int __ftrace_modify_code(void *data) ...@@ -1597,6 +1634,18 @@ static int __ftrace_modify_code(void *data)
else if (*command & FTRACE_STOP_FUNC_RET) else if (*command & FTRACE_STOP_FUNC_RET)
ftrace_disable_ftrace_graph_caller(); 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; return 0;
} }
...@@ -2865,7 +2914,11 @@ ftrace_set_regex(struct ftrace_ops *ops, unsigned char *buf, int len, ...@@ -2865,7 +2914,11 @@ ftrace_set_regex(struct ftrace_ops *ops, unsigned char *buf, int len,
ftrace_match_records(hash, buf, len); ftrace_match_records(hash, buf, len);
mutex_lock(&ftrace_lock); 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_lock);
mutex_unlock(&ftrace_regex_lock); mutex_unlock(&ftrace_regex_lock);
...@@ -3048,18 +3101,12 @@ ftrace_regex_release(struct inode *inode, struct file *file) ...@@ -3048,18 +3101,12 @@ ftrace_regex_release(struct inode *inode, struct file *file)
orig_hash = &iter->ops->notrace_hash; orig_hash = &iter->ops->notrace_hash;
mutex_lock(&ftrace_lock); mutex_lock(&ftrace_lock);
/* ret = ftrace_hash_move(iter->ops, filter_hash,
* Remove the current set, update the hash and add orig_hash, iter->hash);
* them back. if (!ret && (iter->ops->flags & FTRACE_OPS_FL_ENABLED)
*/ && ftrace_enabled)
ftrace_hash_rec_disable(iter->ops, filter_hash); ftrace_run_update_code(FTRACE_ENABLE_CALLS);
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);
}
mutex_unlock(&ftrace_lock); mutex_unlock(&ftrace_lock);
} }
free_ftrace_hash(iter->hash); free_ftrace_hash(iter->hash);
...@@ -3338,7 +3385,7 @@ static int ftrace_process_locs(struct module *mod, ...@@ -3338,7 +3385,7 @@ static int ftrace_process_locs(struct module *mod,
{ {
unsigned long *p; unsigned long *p;
unsigned long addr; unsigned long addr;
unsigned long flags; unsigned long flags = 0; /* Shut up gcc */
mutex_lock(&ftrace_lock); mutex_lock(&ftrace_lock);
p = start; p = start;
...@@ -3356,12 +3403,18 @@ static int ftrace_process_locs(struct module *mod, ...@@ -3356,12 +3403,18 @@ static int ftrace_process_locs(struct module *mod,
} }
/* /*
* Disable interrupts to prevent interrupts from executing * We only need to disable interrupts on start up
* code that is being modified. * 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); ftrace_update_code(mod);
local_irq_restore(flags); if (!mod)
local_irq_restore(flags);
mutex_unlock(&ftrace_lock); mutex_unlock(&ftrace_lock);
return 0; return 0;
......
...@@ -1248,6 +1248,15 @@ ftrace(struct trace_array *tr, struct trace_array_cpu *data, ...@@ -1248,6 +1248,15 @@ ftrace(struct trace_array *tr, struct trace_array_cpu *data,
} }
#ifdef CONFIG_STACKTRACE #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, static void __ftrace_trace_stack(struct ring_buffer *buffer,
unsigned long flags, unsigned long flags,
int skip, int pc, struct pt_regs *regs) int skip, int pc, struct pt_regs *regs)
...@@ -1256,25 +1265,77 @@ static void __ftrace_trace_stack(struct ring_buffer *buffer, ...@@ -1256,25 +1265,77 @@ static void __ftrace_trace_stack(struct ring_buffer *buffer,
struct ring_buffer_event *event; struct ring_buffer_event *event;
struct stack_entry *entry; struct stack_entry *entry;
struct stack_trace trace; struct stack_trace trace;
int use_stack;
int size = FTRACE_STACK_ENTRIES;
trace.nr_entries = 0;
trace.skip = skip;
/*
* 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, event = trace_buffer_lock_reserve(buffer, TRACE_STACK,
sizeof(*entry), flags, pc); sizeof(*entry) + size, flags, pc);
if (!event) if (!event)
return; goto out;
entry = ring_buffer_event_data(event); entry = ring_buffer_event_data(event);
memset(&entry->caller, 0, sizeof(entry->caller));
trace.nr_entries = 0; memset(&entry->caller, 0, size);
trace.max_entries = FTRACE_STACK_ENTRIES;
trace.skip = skip; if (use_stack)
trace.entries = entry->caller; 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 (regs)
save_stack_trace_regs(regs, &trace);
else
save_stack_trace(&trace);
if (!filter_check_discard(call, entry, buffer, event)) if (!filter_check_discard(call, entry, buffer, event))
ring_buffer_unlock_commit(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, 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, ...@@ -1562,7 +1623,12 @@ peek_next_entry(struct trace_iterator *iter, int cpu, u64 *ts,
ftrace_enable_cpu(); 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 * static struct trace_entry *
......
...@@ -278,6 +278,29 @@ struct tracer { ...@@ -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 #define TRACE_PIPE_ALL_CPU -1
int tracer_init(struct tracer *t, struct trace_array *tr); int tracer_init(struct tracer *t, struct trace_array *tr);
...@@ -516,8 +539,18 @@ static inline int ftrace_graph_addr(unsigned long addr) ...@@ -516,8 +539,18 @@ static inline int ftrace_graph_addr(unsigned long addr)
return 1; return 1;
for (i = 0; i < ftrace_graph_count; i++) { 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 1;
}
} }
return 0; return 0;
...@@ -795,19 +828,4 @@ extern const char *__stop___trace_bprintk_fmt[]; ...@@ -795,19 +828,4 @@ extern const char *__stop___trace_bprintk_fmt[];
FTRACE_ENTRY(call, struct_name, id, PARAMS(tstruct), PARAMS(print)) FTRACE_ENTRY(call, struct_name, id, PARAMS(tstruct), PARAMS(print))
#include "trace_entries.h" #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 */ #endif /* _LINUX_KERNEL_TRACE_H */
...@@ -161,7 +161,8 @@ FTRACE_ENTRY(kernel_stack, stack_entry, ...@@ -161,7 +161,8 @@ FTRACE_ENTRY(kernel_stack, stack_entry,
TRACE_STACK, TRACE_STACK,
F_STRUCT( 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" F_printk("\t=> (%08lx)\n\t=> (%08lx)\n\t=> (%08lx)\n\t=> (%08lx)\n"
......
...@@ -227,7 +227,7 @@ int __trace_graph_entry(struct trace_array *tr, ...@@ -227,7 +227,7 @@ int __trace_graph_entry(struct trace_array *tr,
static inline int ftrace_graph_ignore_irqs(void) 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 0;
return in_irq(); return in_irq();
......
This diff is collapsed.
...@@ -1107,19 +1107,20 @@ static enum print_line_t trace_stack_print(struct trace_iterator *iter, ...@@ -1107,19 +1107,20 @@ static enum print_line_t trace_stack_print(struct trace_iterator *iter,
{ {
struct stack_entry *field; struct stack_entry *field;
struct trace_seq *s = &iter->seq; struct trace_seq *s = &iter->seq;
int i; unsigned long *p;
unsigned long *end;
trace_assign_type(field, iter->ent); trace_assign_type(field, iter->ent);
end = (unsigned long *)((long)iter->ent + iter->ent_size);
if (!trace_seq_puts(s, "<stack trace>\n")) if (!trace_seq_puts(s, "<stack trace>\n"))
goto partial; goto partial;
for (i = 0; i < FTRACE_STACK_ENTRIES; i++) {
if (!field->caller[i] || (field->caller[i] == ULONG_MAX)) for (p = field->caller; p && *p != ULONG_MAX && p < end; p++) {
break;
if (!trace_seq_puts(s, " => ")) if (!trace_seq_puts(s, " => "))
goto partial; goto partial;
if (!seq_print_ip_sym(s, field->caller[i], flags)) if (!seq_print_ip_sym(s, *p, flags))
goto partial; goto partial;
if (!trace_seq_puts(s, "\n")) if (!trace_seq_puts(s, "\n"))
goto partial; goto partial;
......
...@@ -200,7 +200,6 @@ static int is_softlockup(unsigned long touch_ts) ...@@ -200,7 +200,6 @@ static int is_softlockup(unsigned long touch_ts)
} }
#ifdef CONFIG_HARDLOCKUP_DETECTOR #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 = { static struct perf_event_attr wd_hw_attr = {
.type = PERF_TYPE_HARDWARE, .type = PERF_TYPE_HARDWARE,
...@@ -372,7 +371,6 @@ static int watchdog_nmi_enable(int cpu) ...@@ -372,7 +371,6 @@ static int watchdog_nmi_enable(int cpu)
wd_attr = &wd_hw_attr; wd_attr = &wd_hw_attr;
wd_attr->sample_period = hw_nmi_get_sample_period(watchdog_thresh); 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 */ /* Try to register using hardware perf events */
event = perf_event_create_kernel_counter(wd_attr, cpu, NULL, watchdog_overflow_callback, NULL); event = perf_event_create_kernel_counter(wd_attr, cpu, NULL, watchdog_overflow_callback, NULL);
......
...@@ -34,9 +34,11 @@ OPTIONS ...@@ -34,9 +34,11 @@ OPTIONS
Specify vmlinux path which has debuginfo (Dwarf binary). Specify vmlinux path which has debuginfo (Dwarf binary).
-m:: -m::
--module=MODNAME:: --module=MODNAME|PATH::
Specify module name in which perf-probe searches probe points 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:: -s::
--source=PATH:: --source=PATH::
......
...@@ -279,6 +279,7 @@ LIB_H += util/thread.h ...@@ -279,6 +279,7 @@ LIB_H += util/thread.h
LIB_H += util/thread_map.h LIB_H += util/thread_map.h
LIB_H += util/trace-event.h LIB_H += util/trace-event.h
LIB_H += util/probe-finder.h LIB_H += util/probe-finder.h
LIB_H += util/dwarf-aux.h
LIB_H += util/probe-event.h LIB_H += util/probe-event.h
LIB_H += util/pstack.h LIB_H += util/pstack.h
LIB_H += util/cpumap.h LIB_H += util/cpumap.h
...@@ -435,6 +436,7 @@ else ...@@ -435,6 +436,7 @@ else
BASIC_CFLAGS += -DDWARF_SUPPORT BASIC_CFLAGS += -DDWARF_SUPPORT
EXTLIBS += -lelf -ldw EXTLIBS += -lelf -ldw
LIB_OBJS += $(OUTPUT)util/probe-finder.o LIB_OBJS += $(OUTPUT)util/probe-finder.o
LIB_OBJS += $(OUTPUT)util/dwarf-aux.o
endif # PERF_HAVE_DWARF_REGS endif # PERF_HAVE_DWARF_REGS
endif # NO_DWARF endif # NO_DWARF
......
...@@ -242,7 +242,8 @@ static const struct option options[] = { ...@@ -242,7 +242,8 @@ static const struct option options[] = {
OPT_STRING('s', "source", &symbol_conf.source_prefix, OPT_STRING('s', "source", &symbol_conf.source_prefix,
"directory", "path to kernel source"), "directory", "path to kernel source"),
OPT_STRING('m', "module", &params.target_module, OPT_STRING('m', "module", &params.target_module,
"modname", "target module name"), "modname|path",
"target module name (for online) or path (for offline)"),
#endif #endif
OPT__DRY_RUN(&probe_event_dry_run), OPT__DRY_RUN(&probe_event_dry_run),
OPT_INTEGER('\0', "max-probes", &params.max_probe_points, OPT_INTEGER('\0', "max-probes", &params.max_probe_points,
......
This diff is collapsed.
#ifndef _DWARF_AUX_H
#define _DWARF_AUX_H
/*
* dwarf-aux.h : libdw auxiliary interfaces
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
*/
#include <dwarf.h>
#include <elfutils/libdw.h>
#include <elfutils/libdwfl.h>
#include <elfutils/version.h>
/* Find the realpath of the target file */
extern const char *cu_find_realpath(Dwarf_Die *cu_die, const char *fname);
/* Get DW_AT_comp_dir (should be NULL with older gcc) */
extern const char *cu_get_comp_dir(Dwarf_Die *cu_die);
/* Get a line number and file name for given address */
extern int cu_find_lineinfo(Dwarf_Die *cudie, unsigned long addr,
const char **fname, int *lineno);
/* Compare diename and tname */
extern bool die_compare_name(Dwarf_Die *dw_die, const char *tname);
/* Get callsite line number of inline-function instance */
extern int die_get_call_lineno(Dwarf_Die *in_die);
/* Get type die */
extern Dwarf_Die *die_get_type(Dwarf_Die *vr_die, Dwarf_Die *die_mem);
/* Get a type die, but skip qualifiers and typedef */
extern Dwarf_Die *die_get_real_type(Dwarf_Die *vr_die, Dwarf_Die *die_mem);
/* Check whether the DIE is signed or not */
extern bool die_is_signed_type(Dwarf_Die *tp_die);
/* Get data_member_location offset */
extern int die_get_data_member_location(Dwarf_Die *mb_die, Dwarf_Word *offs);
/* Return values for die_find_child() callbacks */
enum {
DIE_FIND_CB_END = 0, /* End of Search */
DIE_FIND_CB_CHILD = 1, /* Search only children */
DIE_FIND_CB_SIBLING = 2, /* Search only siblings */
DIE_FIND_CB_CONTINUE = 3, /* Search children and siblings */
};
/* Search child DIEs */
extern Dwarf_Die *die_find_child(Dwarf_Die *rt_die,
int (*callback)(Dwarf_Die *, void *),
void *data, Dwarf_Die *die_mem);
/* Search a non-inlined function including given address */
extern Dwarf_Die *die_find_realfunc(Dwarf_Die *cu_die, Dwarf_Addr addr,
Dwarf_Die *die_mem);
/* Search an inlined function including given address */
extern Dwarf_Die *die_find_inlinefunc(Dwarf_Die *sp_die, Dwarf_Addr addr,
Dwarf_Die *die_mem);
/* Walker on lines (Note: line number will not be sorted) */
typedef int (* line_walk_callback_t) (const char *fname, int lineno,
Dwarf_Addr addr, void *data);
/*
* Walk on lines inside given DIE. If the DIE is a subprogram, walk only on
* the lines inside the subprogram, otherwise the DIE must be a CU DIE.
*/
extern int die_walk_lines(Dwarf_Die *rt_die, line_walk_callback_t callback,
void *data);
/* Find a variable called 'name' at given address */
extern Dwarf_Die *die_find_variable_at(Dwarf_Die *sp_die, const char *name,
Dwarf_Addr addr, Dwarf_Die *die_mem);
/* Find a member called 'name' */
extern Dwarf_Die *die_find_member(Dwarf_Die *st_die, const char *name,
Dwarf_Die *die_mem);
/* Get the name of given variable DIE */
extern int die_get_typename(Dwarf_Die *vr_die, char *buf, int len);
/* Get the name and type of given variable DIE, stored as "type\tname" */
extern int die_get_varname(Dwarf_Die *vr_die, char *buf, int len);
#endif
...@@ -117,6 +117,10 @@ static struct map *kernel_get_module_map(const char *module) ...@@ -117,6 +117,10 @@ static struct map *kernel_get_module_map(const char *module)
struct rb_node *nd; struct rb_node *nd;
struct map_groups *grp = &machine.kmaps; 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) if (!module)
module = "kernel"; module = "kernel";
...@@ -170,16 +174,24 @@ const char *kernel_get_module_path(const char *module) ...@@ -170,16 +174,24 @@ const char *kernel_get_module_path(const char *module)
} }
#ifdef DWARF_SUPPORT #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); const char *path;
if (!path) {
pr_err("Failed to find path of %s module.\n", /* A file path -- this is an offline module */
module ?: "kernel"); if (module && strchr(module, '/'))
return -ENOENT; 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 debuginfo__new(path);
return open(path, O_RDONLY);
} }
/* /*
...@@ -193,13 +205,24 @@ static int kprobe_convert_to_perf_probe(struct probe_trace_point *tp, ...@@ -193,13 +205,24 @@ static int kprobe_convert_to_perf_probe(struct probe_trace_point *tp,
struct map *map; struct map *map;
u64 addr; u64 addr;
int ret = -ENOENT; int ret = -ENOENT;
struct debuginfo *dinfo;
sym = __find_kernel_function_by_name(tp->symbol, &map); sym = __find_kernel_function_by_name(tp->symbol, &map);
if (sym) { if (sym) {
addr = map->unmap_ip(map, sym->start + tp->offset); addr = map->unmap_ip(map, sym->start + tp->offset);
pr_debug("try to find %s+%ld@%" PRIx64 "\n", tp->symbol, pr_debug("try to find %s+%ld@%" PRIx64 "\n", tp->symbol,
tp->offset, addr); 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) { if (ret <= 0) {
pr_debug("Failed to find corresponding probes from " pr_debug("Failed to find corresponding probes from "
...@@ -214,30 +237,70 @@ static int kprobe_convert_to_perf_probe(struct probe_trace_point *tp, ...@@ -214,30 +237,70 @@ static int kprobe_convert_to_perf_probe(struct probe_trace_point *tp,
return 0; 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 */ /* Try to find perf_probe_event with debuginfo */
static int try_to_find_probe_trace_events(struct perf_probe_event *pev, static int try_to_find_probe_trace_events(struct perf_probe_event *pev,
struct probe_trace_event **tevs, struct probe_trace_event **tevs,
int max_tevs, const char *module) int max_tevs, const char *module)
{ {
bool need_dwarf = perf_probe_event_need_dwarf(pev); 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 (!dinfo) {
if (fd < 0) {
if (need_dwarf) { if (need_dwarf) {
pr_warning("Failed to open debuginfo file.\n"); 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; return 0;
} }
/* Searching trace events corresponding to probe event */ /* Searching trace events corresponding to a probe event */
ntevs = find_probe_trace_events(fd, pev, tevs, max_tevs); ntevs = debuginfo__find_trace_events(dinfo, pev, tevs, max_tevs);
debuginfo__delete(dinfo);
if (ntevs > 0) { /* Succeeded to find trace events */ if (ntevs > 0) { /* Succeeded to find trace events */
pr_debug("find %d probe_trace_events.\n", ntevs); 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. */ 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) ...@@ -371,8 +434,9 @@ int show_line_range(struct line_range *lr, const char *module)
{ {
int l = 1; int l = 1;
struct line_node *ln; struct line_node *ln;
struct debuginfo *dinfo;
FILE *fp; FILE *fp;
int fd, ret; int ret;
char *tmp; char *tmp;
/* Search a line range */ /* Search a line range */
...@@ -380,13 +444,14 @@ int show_line_range(struct line_range *lr, const char *module) ...@@ -380,13 +444,14 @@ int show_line_range(struct line_range *lr, const char *module)
if (ret < 0) if (ret < 0)
return ret; return ret;
fd = open_vmlinux(module); dinfo = open_debuginfo(module);
if (fd < 0) { if (!dinfo) {
pr_warning("Failed to open debuginfo file.\n"); 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) { if (ret == 0) {
pr_warning("Specified source line is not found.\n"); pr_warning("Specified source line is not found.\n");
return -ENOENT; return -ENOENT;
...@@ -448,7 +513,8 @@ int show_line_range(struct line_range *lr, const char *module) ...@@ -448,7 +513,8 @@ int show_line_range(struct line_range *lr, const char *module)
return ret; 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, int max_vls, struct strfilter *_filter,
bool externs) bool externs)
{ {
...@@ -463,7 +529,8 @@ static int show_available_vars_at(int fd, struct perf_probe_event *pev, ...@@ -463,7 +529,8 @@ static int show_available_vars_at(int fd, struct perf_probe_event *pev,
return -EINVAL; return -EINVAL;
pr_debug("Searching variables at %s\n", buf); 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) { if (ret <= 0) {
pr_err("Failed to find variables at %s (%d)\n", buf, ret); pr_err("Failed to find variables at %s (%d)\n", buf, ret);
goto end; goto end;
...@@ -504,24 +571,26 @@ int show_available_vars(struct perf_probe_event *pevs, int npevs, ...@@ -504,24 +571,26 @@ int show_available_vars(struct perf_probe_event *pevs, int npevs,
int max_vls, const char *module, int max_vls, const char *module,
struct strfilter *_filter, bool externs) struct strfilter *_filter, bool externs)
{ {
int i, fd, ret = 0; int i, ret = 0;
struct debuginfo *dinfo;
ret = init_vmlinux(); ret = init_vmlinux();
if (ret < 0) if (ret < 0)
return ret; return ret;
dinfo = open_debuginfo(module);
if (!dinfo) {
pr_warning("Failed to open debuginfo file.\n");
return -ENOENT;
}
setup_pager(); setup_pager();
for (i = 0; i < npevs && ret >= 0; i++) { for (i = 0; i < npevs && ret >= 0; i++)
fd = open_vmlinux(module); ret = show_available_vars_at(dinfo, &pevs[i], max_vls, _filter,
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,
externs); externs);
}
debuginfo__delete(dinfo);
return ret; return ret;
} }
...@@ -990,7 +1059,7 @@ bool perf_probe_event_need_dwarf(struct perf_probe_event *pev) ...@@ -990,7 +1059,7 @@ bool perf_probe_event_need_dwarf(struct perf_probe_event *pev)
/* Parse probe_events event into struct probe_point */ /* Parse probe_events event into struct probe_point */
static int parse_probe_trace_command(const char *cmd, 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; struct probe_trace_point *tp = &tev->point;
char pr; char pr;
...@@ -1023,8 +1092,14 @@ static int parse_probe_trace_command(const char *cmd, ...@@ -1023,8 +1092,14 @@ static int parse_probe_trace_command(const char *cmd,
tp->retprobe = (pr == 'r'); tp->retprobe = (pr == 'r');
/* Scan function name and offset */ /* Scan module name(if there), function name and offset */
ret = sscanf(argv[1], "%a[^+]+%lu", (float *)(void *)&tp->symbol, 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); &tp->offset);
if (ret == 1) if (ret == 1)
tp->offset = 0; tp->offset = 0;
...@@ -1269,9 +1344,10 @@ char *synthesize_probe_trace_command(struct probe_trace_event *tev) ...@@ -1269,9 +1344,10 @@ char *synthesize_probe_trace_command(struct probe_trace_event *tev)
if (buf == NULL) if (buf == NULL)
return 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', tp->retprobe ? 'r' : 'p',
tev->group, tev->event, tev->group, tev->event,
tp->module ?: "", tp->module ? ":" : "",
tp->symbol, tp->offset); tp->symbol, tp->offset);
if (len <= 0) if (len <= 0)
goto error; goto error;
...@@ -1378,6 +1454,8 @@ static void clear_probe_trace_event(struct probe_trace_event *tev) ...@@ -1378,6 +1454,8 @@ static void clear_probe_trace_event(struct probe_trace_event *tev)
free(tev->group); free(tev->group);
if (tev->point.symbol) if (tev->point.symbol)
free(tev->point.symbol); free(tev->point.symbol);
if (tev->point.module)
free(tev->point.module);
for (i = 0; i < tev->nargs; i++) { for (i = 0; i < tev->nargs; i++) {
if (tev->args[i].name) if (tev->args[i].name)
free(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, ...@@ -1729,7 +1807,7 @@ static int convert_to_probe_trace_events(struct perf_probe_event *pev,
/* Convert perf_probe_event with debuginfo */ /* Convert perf_probe_event with debuginfo */
ret = try_to_find_probe_trace_events(pev, tevs, max_tevs, module); ret = try_to_find_probe_trace_events(pev, tevs, max_tevs, module);
if (ret != 0) if (ret != 0)
return ret; return ret; /* Found in debuginfo or got an error */
/* Allocate trace event buffer */ /* Allocate trace event buffer */
tev = *tevs = zalloc(sizeof(struct probe_trace_event)); tev = *tevs = zalloc(sizeof(struct probe_trace_event));
...@@ -1742,6 +1820,11 @@ static int convert_to_probe_trace_events(struct perf_probe_event *pev, ...@@ -1742,6 +1820,11 @@ static int convert_to_probe_trace_events(struct perf_probe_event *pev,
ret = -ENOMEM; ret = -ENOMEM;
goto error; goto error;
} }
tev->point.module = strdup(module);
if (tev->point.module == NULL) {
ret = -ENOMEM;
goto error;
}
tev->point.offset = pev->point.offset; tev->point.offset = pev->point.offset;
tev->point.retprobe = pev->point.retprobe; tev->point.retprobe = pev->point.retprobe;
tev->nargs = pev->nargs; tev->nargs = pev->nargs;
......
...@@ -10,6 +10,7 @@ extern bool probe_event_dry_run; ...@@ -10,6 +10,7 @@ extern bool probe_event_dry_run;
/* kprobe-tracer tracing point */ /* kprobe-tracer tracing point */
struct probe_trace_point { struct probe_trace_point {
char *symbol; /* Base symbol */ char *symbol; /* Base symbol */
char *module; /* Module name */
unsigned long offset; /* Offset from symbol */ unsigned long offset; /* Offset from symbol */
bool retprobe; /* Return probe flag */ bool retprobe; /* Return probe flag */
}; };
......
This diff is collapsed.
...@@ -16,27 +16,42 @@ static inline int is_c_varname(const char *name) ...@@ -16,27 +16,42 @@ static inline int is_c_varname(const char *name)
} }
#ifdef DWARF_SUPPORT #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 */ /* Find probe_trace_events specified by perf_probe_event from debuginfo */
extern int find_probe_trace_events(int fd, struct perf_probe_event *pev, extern int debuginfo__find_trace_events(struct debuginfo *self,
struct probe_trace_event **tevs, struct perf_probe_event *pev,
int max_tevs); struct probe_trace_event **tevs,
int max_tevs);
/* Find a perf_probe_point from debuginfo */ /* Find a perf_probe_point from debuginfo */
extern int find_perf_probe_point(unsigned long addr, extern int debuginfo__find_probe_point(struct debuginfo *self,
struct perf_probe_point *ppt); unsigned long addr,
struct perf_probe_point *ppt);
/* Find a line range */ /* 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 */ /* Find available variables */
extern int find_available_vars_at(int fd, struct perf_probe_event *pev, extern int debuginfo__find_available_vars_at(struct debuginfo *self,
struct variable_list **vls, int max_points, struct perf_probe_event *pev,
bool externs); struct variable_list **vls,
int max_points, bool externs);
#include <dwarf.h>
#include <elfutils/libdw.h>
#include <elfutils/libdwfl.h>
#include <elfutils/version.h>
struct probe_finder { struct probe_finder {
struct perf_probe_event *pev; /* Target probe event */ struct perf_probe_event *pev; /* Target probe event */
......
...@@ -294,3 +294,22 @@ bool strlazymatch(const char *str, const char *pat) ...@@ -294,3 +294,22 @@ bool strlazymatch(const char *str, const char *pat)
{ {
return __match_glob(str, pat, true); return __match_glob(str, pat, true);
} }
/**
* strtailcmp - Compare the tail of two strings
* @s1: 1st string to be compared
* @s2: 2nd string to be compared
*
* Return 0 if whole of either string is same as another's tail part.
*/
int strtailcmp(const char *s1, const char *s2)
{
int i1 = strlen(s1);
int i2 = strlen(s2);
while (--i1 >= 0 && --i2 >= 0) {
if (s1[i1] != s2[i2])
return s1[i1] - s2[i2];
}
return 0;
}
...@@ -183,106 +183,59 @@ int bigendian(void) ...@@ -183,106 +183,59 @@ int bigendian(void)
return *ptr == 0x01020304; 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; unsigned long long size = 0;
char buf[BUFSIZ]; char buf[BUFSIZ], *sizep;
int r; off_t hdr_pos = lseek(output_fd, 0, SEEK_CUR);
int r, fd;
do {
r = read(fd, buf, BUFSIZ);
if (r > 0) {
size += r;
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); fd = open(file, O_RDONLY);
if (fd < 0) if (fd < 0)
die("Can't read '%s'", file); die("Can't read '%s'", file);
size = copy_file_fd(fd);
close(fd);
return size; /* put in zeros for file size, then fill true size later */
} write_or_die(&size, hdr_sz);
static unsigned long get_size_fd(int fd)
{
unsigned long long size = 0;
char buf[BUFSIZ];
int r;
do { do {
r = read(fd, buf, BUFSIZ); r = read(fd, buf, BUFSIZ);
if (r > 0) if (r > 0) {
size += r; size += r;
write_or_die(buf, r);
}
} while (r > 0); } 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); close(fd);
return size; /* ugh, handle big-endian hdr_size == 4 */
sizep = (char*)&size;
if (bigendian())
sizep += sizeof(u64) - hdr_sz;
if (pwrite(output_fd, sizep, hdr_sz, hdr_pos) < 0)
die("writing to %s", output_file);
} }
static void read_header_files(void) static void read_header_files(void)
{ {
unsigned long long size, check_size;
char *path; char *path;
int fd; struct stat st;
path = get_tracing_file("events/header_page"); path = get_tracing_file("events/header_page");
fd = open(path, O_RDONLY); if (stat(path, &st) < 0)
if (fd < 0)
die("can't read '%s'", path); 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("header_page", 12);
write_or_die(&size, 8); record_file(path, 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);
put_tracing_file(path); put_tracing_file(path);
path = get_tracing_file("events/header_event"); path = get_tracing_file("events/header_event");
fd = open(path, O_RDONLY); if (stat(path, &st) < 0)
if (fd < 0)
die("can't read '%s'", path); die("can't read '%s'", path);
size = get_size_fd(fd);
write_or_die("header_event", 13); write_or_die("header_event", 13);
write_or_die(&size, 8); record_file(path, 8);
check_size = copy_file_fd(fd);
if (size != check_size)
die("wrong size for '%s'", path);
put_tracing_file(path); put_tracing_file(path);
close(fd);
} }
static bool name_in_tp_list(char *sys, struct tracepoint_path *tps) 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) ...@@ -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) static void copy_event_system(const char *sys, struct tracepoint_path *tps)
{ {
unsigned long long size, check_size;
struct dirent *dent; struct dirent *dent;
struct stat st; struct stat st;
char *format; char *format;
...@@ -338,14 +290,8 @@ static void copy_event_system(const char *sys, struct tracepoint_path *tps) ...@@ -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); sprintf(format, "%s/%s/format", sys, dent->d_name);
ret = stat(format, &st); ret = stat(format, &st);
if (ret >= 0) { if (ret >= 0)
/* unfortunately, you can not stat debugfs files for size */ record_file(format, 8);
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);
}
free(format); free(format);
} }
...@@ -426,7 +372,7 @@ static void read_event_files(struct tracepoint_path *tps) ...@@ -426,7 +372,7 @@ static void read_event_files(struct tracepoint_path *tps)
static void read_proc_kallsyms(void) static void read_proc_kallsyms(void)
{ {
unsigned int size, check_size; unsigned int size;
const char *path = "/proc/kallsyms"; const char *path = "/proc/kallsyms";
struct stat st; struct stat st;
int ret; int ret;
...@@ -438,17 +384,12 @@ static void read_proc_kallsyms(void) ...@@ -438,17 +384,12 @@ static void read_proc_kallsyms(void)
write_or_die(&size, 4); write_or_die(&size, 4);
return; return;
} }
size = get_size(path); record_file(path, 4);
write_or_die(&size, 4);
check_size = copy_file(path);
if (size != check_size)
die("error in size of file '%s'", path);
} }
static void read_ftrace_printk(void) static void read_ftrace_printk(void)
{ {
unsigned int size, check_size; unsigned int size;
char *path; char *path;
struct stat st; struct stat st;
int ret; int ret;
...@@ -461,11 +402,8 @@ static void read_ftrace_printk(void) ...@@ -461,11 +402,8 @@ static void read_ftrace_printk(void)
write_or_die(&size, 4); write_or_die(&size, 4);
goto out; goto out;
} }
size = get_size(path); record_file(path, 4);
write_or_die(&size, 4);
check_size = copy_file(path);
if (size != check_size)
die("error in size of file '%s'", path);
out: out:
put_tracing_file(path); put_tracing_file(path);
} }
......
...@@ -238,6 +238,7 @@ char **argv_split(const char *str, int *argcp); ...@@ -238,6 +238,7 @@ char **argv_split(const char *str, int *argcp);
void argv_free(char **argv); void argv_free(char **argv);
bool strglobmatch(const char *str, const char *pat); bool strglobmatch(const char *str, const char *pat);
bool strlazymatch(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); unsigned long convert_unit(unsigned long value, char *unit);
int readn(int fd, void *buf, size_t size); int readn(int fd, void *buf, size_t size);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment