Merge branch 'tracing-v28-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/linux-2.6-tip
* 'tracing-v28-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/linux-2.6-tip: (131 commits) tracing/fastboot: improve help text tracing/stacktrace: improve help text tracing/fastboot: fix initcalls disposition in bootgraph.pl tracing/fastboot: fix bootgraph.pl initcall name regexp tracing/fastboot: fix issues and improve output of bootgraph.pl tracepoints: synchronize unregister static inline tracepoints: tracepoint_synchronize_unregister() ftrace: make ftrace_test_p6nop disassembler-friendly markers: fix synchronize marker unregister static inline tracing/fastboot: add better resolution to initcall debug/tracing trace: add build-time check to avoid overrunning hex buffer ftrace: fix hex output mode of ftrace tracing/fastboot: fix initcalls disposition in bootgraph.pl tracing/fastboot: fix printk format typo in boot tracer ftrace: return an error when setting a nonexistent tracer ftrace: make some tracers reentrant ring-buffer: make reentrant ring-buffer: move page indexes into page headers tracing/fastboot: only trace non-module initcalls ftrace: move pc counter in irqtrace ... Manually fix conflicts: - init/main.c: initcall tracing - kernel/module.c: verbose level vs tracepoints - scripts/bootgraph.pl: fallout from cherry-picking commits.
This commit is contained in:
@@ -85,6 +85,7 @@ obj-$(CONFIG_SYSCTL) += utsname_sysctl.o
|
||||
obj-$(CONFIG_TASK_DELAY_ACCT) += delayacct.o
|
||||
obj-$(CONFIG_TASKSTATS) += taskstats.o tsacct.o
|
||||
obj-$(CONFIG_MARKERS) += marker.o
|
||||
obj-$(CONFIG_TRACEPOINTS) += tracepoint.o
|
||||
obj-$(CONFIG_LATENCYTOP) += latencytop.o
|
||||
obj-$(CONFIG_HAVE_GENERIC_DMA_COHERENT) += dma-coherent.o
|
||||
obj-$(CONFIG_FTRACE) += trace/
|
||||
|
@@ -47,6 +47,7 @@
|
||||
#include <linux/blkdev.h>
|
||||
#include <linux/task_io_accounting_ops.h>
|
||||
#include <linux/tracehook.h>
|
||||
#include <trace/sched.h>
|
||||
|
||||
#include <asm/uaccess.h>
|
||||
#include <asm/unistd.h>
|
||||
@@ -146,7 +147,10 @@ static void __exit_signal(struct task_struct *tsk)
|
||||
|
||||
static void delayed_put_task_struct(struct rcu_head *rhp)
|
||||
{
|
||||
put_task_struct(container_of(rhp, struct task_struct, rcu));
|
||||
struct task_struct *tsk = container_of(rhp, struct task_struct, rcu);
|
||||
|
||||
trace_sched_process_free(tsk);
|
||||
put_task_struct(tsk);
|
||||
}
|
||||
|
||||
|
||||
@@ -1070,6 +1074,8 @@ NORET_TYPE void do_exit(long code)
|
||||
|
||||
if (group_dead)
|
||||
acct_process();
|
||||
trace_sched_process_exit(tsk);
|
||||
|
||||
exit_sem(tsk);
|
||||
exit_files(tsk);
|
||||
exit_fs(tsk);
|
||||
@@ -1675,6 +1681,8 @@ static long do_wait(enum pid_type type, struct pid *pid, int options,
|
||||
struct task_struct *tsk;
|
||||
int retval;
|
||||
|
||||
trace_sched_process_wait(pid);
|
||||
|
||||
add_wait_queue(¤t->signal->wait_chldexit,&wait);
|
||||
repeat:
|
||||
/*
|
||||
|
@@ -58,6 +58,7 @@
|
||||
#include <linux/tty.h>
|
||||
#include <linux/proc_fs.h>
|
||||
#include <linux/blkdev.h>
|
||||
#include <trace/sched.h>
|
||||
|
||||
#include <asm/pgtable.h>
|
||||
#include <asm/pgalloc.h>
|
||||
@@ -1372,6 +1373,8 @@ long do_fork(unsigned long clone_flags,
|
||||
if (!IS_ERR(p)) {
|
||||
struct completion vfork;
|
||||
|
||||
trace_sched_process_fork(current, p);
|
||||
|
||||
nr = task_pid_vnr(p);
|
||||
|
||||
if (clone_flags & CLONE_PARENT_SETTID)
|
||||
|
@@ -13,6 +13,7 @@
|
||||
#include <linux/file.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <trace/sched.h>
|
||||
|
||||
#define KTHREAD_NICE_LEVEL (-5)
|
||||
|
||||
@@ -205,6 +206,8 @@ int kthread_stop(struct task_struct *k)
|
||||
/* It could exit after stop_info.k set, but before wake_up_process. */
|
||||
get_task_struct(k);
|
||||
|
||||
trace_sched_kthread_stop(k);
|
||||
|
||||
/* Must init completion *before* thread sees kthread_stop_info.k */
|
||||
init_completion(&kthread_stop_info.done);
|
||||
smp_wmb();
|
||||
@@ -220,6 +223,8 @@ int kthread_stop(struct task_struct *k)
|
||||
ret = kthread_stop_info.err;
|
||||
mutex_unlock(&kthread_stop_lock);
|
||||
|
||||
trace_sched_kthread_stop_ret(ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL(kthread_stop);
|
||||
|
@@ -62,7 +62,7 @@ struct marker_entry {
|
||||
int refcount; /* Number of times armed. 0 if disarmed. */
|
||||
struct rcu_head rcu;
|
||||
void *oldptr;
|
||||
unsigned char rcu_pending:1;
|
||||
int rcu_pending;
|
||||
unsigned char ptype:1;
|
||||
char name[0]; /* Contains name'\0'format'\0' */
|
||||
};
|
||||
@@ -103,11 +103,11 @@ void marker_probe_cb(const struct marker *mdata, void *call_private, ...)
|
||||
char ptype;
|
||||
|
||||
/*
|
||||
* preempt_disable does two things : disabling preemption to make sure
|
||||
* the teardown of the callbacks can be done correctly when they are in
|
||||
* modules and they insure RCU read coherency.
|
||||
* rcu_read_lock_sched does two things : disabling preemption to make
|
||||
* sure the teardown of the callbacks can be done correctly when they
|
||||
* are in modules and they insure RCU read coherency.
|
||||
*/
|
||||
preempt_disable();
|
||||
rcu_read_lock_sched();
|
||||
ptype = mdata->ptype;
|
||||
if (likely(!ptype)) {
|
||||
marker_probe_func *func;
|
||||
@@ -145,7 +145,7 @@ void marker_probe_cb(const struct marker *mdata, void *call_private, ...)
|
||||
va_end(args);
|
||||
}
|
||||
}
|
||||
preempt_enable();
|
||||
rcu_read_unlock_sched();
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(marker_probe_cb);
|
||||
|
||||
@@ -162,7 +162,7 @@ void marker_probe_cb_noarg(const struct marker *mdata, void *call_private, ...)
|
||||
va_list args; /* not initialized */
|
||||
char ptype;
|
||||
|
||||
preempt_disable();
|
||||
rcu_read_lock_sched();
|
||||
ptype = mdata->ptype;
|
||||
if (likely(!ptype)) {
|
||||
marker_probe_func *func;
|
||||
@@ -195,7 +195,7 @@ void marker_probe_cb_noarg(const struct marker *mdata, void *call_private, ...)
|
||||
multi[i].func(multi[i].probe_private, call_private,
|
||||
mdata->format, &args);
|
||||
}
|
||||
preempt_enable();
|
||||
rcu_read_unlock_sched();
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(marker_probe_cb_noarg);
|
||||
|
||||
@@ -560,7 +560,7 @@ static int set_marker(struct marker_entry **entry, struct marker *elem,
|
||||
* Disable a marker and its probe callback.
|
||||
* Note: only waiting an RCU period after setting elem->call to the empty
|
||||
* function insures that the original callback is not used anymore. This insured
|
||||
* by preempt_disable around the call site.
|
||||
* by rcu_read_lock_sched around the call site.
|
||||
*/
|
||||
static void disable_marker(struct marker *elem)
|
||||
{
|
||||
@@ -653,11 +653,17 @@ int marker_probe_register(const char *name, const char *format,
|
||||
entry = get_marker(name);
|
||||
if (!entry) {
|
||||
entry = add_marker(name, format);
|
||||
if (IS_ERR(entry)) {
|
||||
if (IS_ERR(entry))
|
||||
ret = PTR_ERR(entry);
|
||||
goto end;
|
||||
}
|
||||
} else if (format) {
|
||||
if (!entry->format)
|
||||
ret = marker_set_format(&entry, format);
|
||||
else if (strcmp(entry->format, format))
|
||||
ret = -EPERM;
|
||||
}
|
||||
if (ret)
|
||||
goto end;
|
||||
|
||||
/*
|
||||
* If we detect that a call_rcu is pending for this marker,
|
||||
* make sure it's executed now.
|
||||
@@ -674,6 +680,8 @@ int marker_probe_register(const char *name, const char *format,
|
||||
mutex_lock(&markers_mutex);
|
||||
entry = get_marker(name);
|
||||
WARN_ON(!entry);
|
||||
if (entry->rcu_pending)
|
||||
rcu_barrier_sched();
|
||||
entry->oldptr = old;
|
||||
entry->rcu_pending = 1;
|
||||
/* write rcu_pending before calling the RCU callback */
|
||||
@@ -717,6 +725,8 @@ int marker_probe_unregister(const char *name,
|
||||
entry = get_marker(name);
|
||||
if (!entry)
|
||||
goto end;
|
||||
if (entry->rcu_pending)
|
||||
rcu_barrier_sched();
|
||||
entry->oldptr = old;
|
||||
entry->rcu_pending = 1;
|
||||
/* write rcu_pending before calling the RCU callback */
|
||||
@@ -795,6 +805,8 @@ int marker_probe_unregister_private_data(marker_probe_func *probe,
|
||||
mutex_lock(&markers_mutex);
|
||||
entry = get_marker_from_private_data(probe, probe_private);
|
||||
WARN_ON(!entry);
|
||||
if (entry->rcu_pending)
|
||||
rcu_barrier_sched();
|
||||
entry->oldptr = old;
|
||||
entry->rcu_pending = 1;
|
||||
/* write rcu_pending before calling the RCU callback */
|
||||
|
@@ -46,6 +46,8 @@
|
||||
#include <asm/cacheflush.h>
|
||||
#include <linux/license.h>
|
||||
#include <asm/sections.h>
|
||||
#include <linux/tracepoint.h>
|
||||
#include <linux/ftrace.h>
|
||||
|
||||
#if 0
|
||||
#define DEBUGP printk
|
||||
@@ -1430,6 +1432,9 @@ static void free_module(struct module *mod)
|
||||
/* Module unload stuff */
|
||||
module_unload_free(mod);
|
||||
|
||||
/* release any pointers to mcount in this module */
|
||||
ftrace_release(mod->module_core, mod->core_size);
|
||||
|
||||
/* This may be NULL, but that's OK */
|
||||
module_free(mod, mod->module_init);
|
||||
kfree(mod->args);
|
||||
@@ -1861,9 +1866,13 @@ static noinline struct module *load_module(void __user *umod,
|
||||
unsigned int markersindex;
|
||||
unsigned int markersstringsindex;
|
||||
unsigned int verboseindex;
|
||||
unsigned int tracepointsindex;
|
||||
unsigned int tracepointsstringsindex;
|
||||
unsigned int mcountindex;
|
||||
struct module *mod;
|
||||
long err = 0;
|
||||
void *percpu = NULL, *ptr = NULL; /* Stops spurious gcc warning */
|
||||
void *mseg;
|
||||
struct exception_table_entry *extable;
|
||||
mm_segment_t old_fs;
|
||||
|
||||
@@ -2156,6 +2165,12 @@ static noinline struct module *load_module(void __user *umod,
|
||||
markersstringsindex = find_sec(hdr, sechdrs, secstrings,
|
||||
"__markers_strings");
|
||||
verboseindex = find_sec(hdr, sechdrs, secstrings, "__verbose");
|
||||
tracepointsindex = find_sec(hdr, sechdrs, secstrings, "__tracepoints");
|
||||
tracepointsstringsindex = find_sec(hdr, sechdrs, secstrings,
|
||||
"__tracepoints_strings");
|
||||
|
||||
mcountindex = find_sec(hdr, sechdrs, secstrings,
|
||||
"__mcount_loc");
|
||||
|
||||
/* Now do relocations. */
|
||||
for (i = 1; i < hdr->e_shnum; i++) {
|
||||
@@ -2183,6 +2198,12 @@ static noinline struct module *load_module(void __user *umod,
|
||||
mod->num_markers =
|
||||
sechdrs[markersindex].sh_size / sizeof(*mod->markers);
|
||||
#endif
|
||||
#ifdef CONFIG_TRACEPOINTS
|
||||
mod->tracepoints = (void *)sechdrs[tracepointsindex].sh_addr;
|
||||
mod->num_tracepoints =
|
||||
sechdrs[tracepointsindex].sh_size / sizeof(*mod->tracepoints);
|
||||
#endif
|
||||
|
||||
|
||||
/* Find duplicate symbols */
|
||||
err = verify_export_symbols(mod);
|
||||
@@ -2201,12 +2222,22 @@ static noinline struct module *load_module(void __user *umod,
|
||||
|
||||
add_kallsyms(mod, sechdrs, symindex, strindex, secstrings);
|
||||
|
||||
if (!mod->taints) {
|
||||
#ifdef CONFIG_MARKERS
|
||||
if (!mod->taints)
|
||||
marker_update_probe_range(mod->markers,
|
||||
mod->markers + mod->num_markers);
|
||||
#endif
|
||||
dynamic_printk_setup(sechdrs, verboseindex);
|
||||
#ifdef CONFIG_TRACEPOINTS
|
||||
tracepoint_update_probe_range(mod->tracepoints,
|
||||
mod->tracepoints + mod->num_tracepoints);
|
||||
#endif
|
||||
}
|
||||
|
||||
/* sechdrs[0].sh_size is always zero */
|
||||
mseg = (void *)sechdrs[mcountindex].sh_addr;
|
||||
ftrace_init_module(mseg, mseg + sechdrs[mcountindex].sh_size);
|
||||
|
||||
err = module_finalize(hdr, sechdrs, mod);
|
||||
if (err < 0)
|
||||
goto cleanup;
|
||||
@@ -2276,6 +2307,7 @@ static noinline struct module *load_module(void __user *umod,
|
||||
cleanup:
|
||||
kobject_del(&mod->mkobj.kobj);
|
||||
kobject_put(&mod->mkobj.kobj);
|
||||
ftrace_release(mod->module_core, mod->core_size);
|
||||
free_unload:
|
||||
module_unload_free(mod);
|
||||
module_free(mod, mod->module_init);
|
||||
@@ -2759,3 +2791,50 @@ void module_update_markers(void)
|
||||
mutex_unlock(&module_mutex);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_TRACEPOINTS
|
||||
void module_update_tracepoints(void)
|
||||
{
|
||||
struct module *mod;
|
||||
|
||||
mutex_lock(&module_mutex);
|
||||
list_for_each_entry(mod, &modules, list)
|
||||
if (!mod->taints)
|
||||
tracepoint_update_probe_range(mod->tracepoints,
|
||||
mod->tracepoints + mod->num_tracepoints);
|
||||
mutex_unlock(&module_mutex);
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns 0 if current not found.
|
||||
* Returns 1 if current found.
|
||||
*/
|
||||
int module_get_iter_tracepoints(struct tracepoint_iter *iter)
|
||||
{
|
||||
struct module *iter_mod;
|
||||
int found = 0;
|
||||
|
||||
mutex_lock(&module_mutex);
|
||||
list_for_each_entry(iter_mod, &modules, list) {
|
||||
if (!iter_mod->taints) {
|
||||
/*
|
||||
* Sorted module list
|
||||
*/
|
||||
if (iter_mod < iter->module)
|
||||
continue;
|
||||
else if (iter_mod > iter->module)
|
||||
iter->tracepoint = NULL;
|
||||
found = tracepoint_get_iter_range(&iter->tracepoint,
|
||||
iter_mod->tracepoints,
|
||||
iter_mod->tracepoints
|
||||
+ iter_mod->num_tracepoints);
|
||||
if (found) {
|
||||
iter->module = iter_mod;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
mutex_unlock(&module_mutex);
|
||||
return found;
|
||||
}
|
||||
#endif
|
||||
|
@@ -550,7 +550,7 @@ EXPORT_SYMBOL(unregister_reboot_notifier);
|
||||
|
||||
static ATOMIC_NOTIFIER_HEAD(die_chain);
|
||||
|
||||
int notify_die(enum die_val val, const char *str,
|
||||
int notrace notify_die(enum die_val val, const char *str,
|
||||
struct pt_regs *regs, long err, int trap, int sig)
|
||||
{
|
||||
struct die_args args = {
|
||||
|
@@ -71,6 +71,7 @@
|
||||
#include <linux/debugfs.h>
|
||||
#include <linux/ctype.h>
|
||||
#include <linux/ftrace.h>
|
||||
#include <trace/sched.h>
|
||||
|
||||
#include <asm/tlb.h>
|
||||
#include <asm/irq_regs.h>
|
||||
@@ -1936,6 +1937,7 @@ unsigned long wait_task_inactive(struct task_struct *p, long match_state)
|
||||
* just go back and repeat.
|
||||
*/
|
||||
rq = task_rq_lock(p, &flags);
|
||||
trace_sched_wait_task(rq, p);
|
||||
running = task_running(rq, p);
|
||||
on_rq = p->se.on_rq;
|
||||
ncsw = 0;
|
||||
@@ -2297,9 +2299,7 @@ out_activate:
|
||||
success = 1;
|
||||
|
||||
out_running:
|
||||
trace_mark(kernel_sched_wakeup,
|
||||
"pid %d state %ld ## rq %p task %p rq->curr %p",
|
||||
p->pid, p->state, rq, p, rq->curr);
|
||||
trace_sched_wakeup(rq, p);
|
||||
check_preempt_curr(rq, p, sync);
|
||||
|
||||
p->state = TASK_RUNNING;
|
||||
@@ -2432,9 +2432,7 @@ void wake_up_new_task(struct task_struct *p, unsigned long clone_flags)
|
||||
p->sched_class->task_new(rq, p);
|
||||
inc_nr_running(rq);
|
||||
}
|
||||
trace_mark(kernel_sched_wakeup_new,
|
||||
"pid %d state %ld ## rq %p task %p rq->curr %p",
|
||||
p->pid, p->state, rq, p, rq->curr);
|
||||
trace_sched_wakeup_new(rq, p);
|
||||
check_preempt_curr(rq, p, 0);
|
||||
#ifdef CONFIG_SMP
|
||||
if (p->sched_class->task_wake_up)
|
||||
@@ -2607,11 +2605,7 @@ context_switch(struct rq *rq, struct task_struct *prev,
|
||||
struct mm_struct *mm, *oldmm;
|
||||
|
||||
prepare_task_switch(rq, prev, next);
|
||||
trace_mark(kernel_sched_schedule,
|
||||
"prev_pid %d next_pid %d prev_state %ld "
|
||||
"## rq %p prev %p next %p",
|
||||
prev->pid, next->pid, prev->state,
|
||||
rq, prev, next);
|
||||
trace_sched_switch(rq, prev, next);
|
||||
mm = next->mm;
|
||||
oldmm = prev->active_mm;
|
||||
/*
|
||||
@@ -2851,6 +2845,7 @@ static void sched_migrate_task(struct task_struct *p, int dest_cpu)
|
||||
|| unlikely(!cpu_active(dest_cpu)))
|
||||
goto out;
|
||||
|
||||
trace_sched_migrate_task(rq, p, dest_cpu);
|
||||
/* force the process onto the specified CPU */
|
||||
if (migrate_task(p, dest_cpu, &req)) {
|
||||
/* Need to wait for migration thread (might exit: take ref). */
|
||||
|
@@ -27,6 +27,7 @@
|
||||
#include <linux/freezer.h>
|
||||
#include <linux/pid_namespace.h>
|
||||
#include <linux/nsproxy.h>
|
||||
#include <trace/sched.h>
|
||||
|
||||
#include <asm/param.h>
|
||||
#include <asm/uaccess.h>
|
||||
@@ -803,6 +804,8 @@ static int send_signal(int sig, struct siginfo *info, struct task_struct *t,
|
||||
struct sigpending *pending;
|
||||
struct sigqueue *q;
|
||||
|
||||
trace_sched_signal_send(sig, t);
|
||||
|
||||
assert_spin_locked(&t->sighand->siglock);
|
||||
if (!prepare_signal(sig, t))
|
||||
return 0;
|
||||
|
@@ -1,23 +1,37 @@
|
||||
#
|
||||
# Architectures that offer an FTRACE implementation should select HAVE_FTRACE:
|
||||
#
|
||||
config HAVE_FTRACE
|
||||
|
||||
config NOP_TRACER
|
||||
bool
|
||||
|
||||
config HAVE_FTRACE
|
||||
bool
|
||||
select NOP_TRACER
|
||||
|
||||
config HAVE_DYNAMIC_FTRACE
|
||||
bool
|
||||
|
||||
config HAVE_FTRACE_MCOUNT_RECORD
|
||||
bool
|
||||
|
||||
config TRACER_MAX_TRACE
|
||||
bool
|
||||
|
||||
config RING_BUFFER
|
||||
bool
|
||||
|
||||
config TRACING
|
||||
bool
|
||||
select DEBUG_FS
|
||||
select RING_BUFFER
|
||||
select STACKTRACE
|
||||
select TRACEPOINTS
|
||||
|
||||
config FTRACE
|
||||
bool "Kernel Function Tracer"
|
||||
depends on HAVE_FTRACE
|
||||
depends on DEBUG_KERNEL
|
||||
select FRAME_POINTER
|
||||
select TRACING
|
||||
select CONTEXT_SWITCH_TRACER
|
||||
@@ -36,6 +50,7 @@ config IRQSOFF_TRACER
|
||||
depends on TRACE_IRQFLAGS_SUPPORT
|
||||
depends on GENERIC_TIME
|
||||
depends on HAVE_FTRACE
|
||||
depends on DEBUG_KERNEL
|
||||
select TRACE_IRQFLAGS
|
||||
select TRACING
|
||||
select TRACER_MAX_TRACE
|
||||
@@ -59,6 +74,7 @@ config PREEMPT_TRACER
|
||||
depends on GENERIC_TIME
|
||||
depends on PREEMPT
|
||||
depends on HAVE_FTRACE
|
||||
depends on DEBUG_KERNEL
|
||||
select TRACING
|
||||
select TRACER_MAX_TRACE
|
||||
help
|
||||
@@ -86,6 +102,7 @@ config SYSPROF_TRACER
|
||||
config SCHED_TRACER
|
||||
bool "Scheduling Latency Tracer"
|
||||
depends on HAVE_FTRACE
|
||||
depends on DEBUG_KERNEL
|
||||
select TRACING
|
||||
select CONTEXT_SWITCH_TRACER
|
||||
select TRACER_MAX_TRACE
|
||||
@@ -96,16 +113,56 @@ config SCHED_TRACER
|
||||
config CONTEXT_SWITCH_TRACER
|
||||
bool "Trace process context switches"
|
||||
depends on HAVE_FTRACE
|
||||
depends on DEBUG_KERNEL
|
||||
select TRACING
|
||||
select MARKERS
|
||||
help
|
||||
This tracer gets called from the context switch and records
|
||||
all switching of tasks.
|
||||
|
||||
config BOOT_TRACER
|
||||
bool "Trace boot initcalls"
|
||||
depends on HAVE_FTRACE
|
||||
depends on DEBUG_KERNEL
|
||||
select TRACING
|
||||
help
|
||||
This tracer helps developers to optimize boot times: it records
|
||||
the timings of the initcalls and traces key events and the identity
|
||||
of tasks that can cause boot delays, such as context-switches.
|
||||
|
||||
Its aim is to be parsed by the /scripts/bootgraph.pl tool to
|
||||
produce pretty graphics about boot inefficiencies, giving a visual
|
||||
representation of the delays during initcalls - but the raw
|
||||
/debug/tracing/trace text output is readable too.
|
||||
|
||||
( Note that tracing self tests can't be enabled if this tracer is
|
||||
selected, because the self-tests are an initcall as well and that
|
||||
would invalidate the boot trace. )
|
||||
|
||||
config STACK_TRACER
|
||||
bool "Trace max stack"
|
||||
depends on HAVE_FTRACE
|
||||
depends on DEBUG_KERNEL
|
||||
select FTRACE
|
||||
select STACKTRACE
|
||||
help
|
||||
This special tracer records the maximum stack footprint of the
|
||||
kernel and displays it in debugfs/tracing/stack_trace.
|
||||
|
||||
This tracer works by hooking into every function call that the
|
||||
kernel executes, and keeping a maximum stack depth value and
|
||||
stack-trace saved. Because this logic has to execute in every
|
||||
kernel function, all the time, this option can slow down the
|
||||
kernel measurably and is generally intended for kernel
|
||||
developers only.
|
||||
|
||||
Say N if unsure.
|
||||
|
||||
config DYNAMIC_FTRACE
|
||||
bool "enable/disable ftrace tracepoints dynamically"
|
||||
depends on FTRACE
|
||||
depends on HAVE_DYNAMIC_FTRACE
|
||||
depends on DEBUG_KERNEL
|
||||
default y
|
||||
help
|
||||
This option will modify all the calls to ftrace dynamically
|
||||
@@ -121,12 +178,17 @@ config DYNAMIC_FTRACE
|
||||
were made. If so, it runs stop_machine (stops all CPUS)
|
||||
and modifies the code to jump over the call to ftrace.
|
||||
|
||||
config FTRACE_MCOUNT_RECORD
|
||||
def_bool y
|
||||
depends on DYNAMIC_FTRACE
|
||||
depends on HAVE_FTRACE_MCOUNT_RECORD
|
||||
|
||||
config FTRACE_SELFTEST
|
||||
bool
|
||||
|
||||
config FTRACE_STARTUP_TEST
|
||||
bool "Perform a startup test on ftrace"
|
||||
depends on TRACING
|
||||
depends on TRACING && DEBUG_KERNEL && !BOOT_TRACER
|
||||
select FTRACE_SELFTEST
|
||||
help
|
||||
This option performs a series of startup tests on ftrace. On bootup
|
||||
|
@@ -11,6 +11,7 @@ obj-y += trace_selftest_dynamic.o
|
||||
endif
|
||||
|
||||
obj-$(CONFIG_FTRACE) += libftrace.o
|
||||
obj-$(CONFIG_RING_BUFFER) += ring_buffer.o
|
||||
|
||||
obj-$(CONFIG_TRACING) += trace.o
|
||||
obj-$(CONFIG_CONTEXT_SWITCH_TRACER) += trace_sched_switch.o
|
||||
@@ -19,6 +20,9 @@ obj-$(CONFIG_FTRACE) += trace_functions.o
|
||||
obj-$(CONFIG_IRQSOFF_TRACER) += trace_irqsoff.o
|
||||
obj-$(CONFIG_PREEMPT_TRACER) += trace_irqsoff.o
|
||||
obj-$(CONFIG_SCHED_TRACER) += trace_sched_wakeup.o
|
||||
obj-$(CONFIG_NOP_TRACER) += trace_nop.o
|
||||
obj-$(CONFIG_STACK_TRACER) += trace_stack.o
|
||||
obj-$(CONFIG_MMIOTRACE) += trace_mmiotrace.o
|
||||
obj-$(CONFIG_BOOT_TRACER) += trace_boot.o
|
||||
|
||||
libftrace-y := ftrace.o
|
||||
|
@@ -81,7 +81,7 @@ void clear_ftrace_function(void)
|
||||
|
||||
static int __register_ftrace_function(struct ftrace_ops *ops)
|
||||
{
|
||||
/* Should never be called by interrupts */
|
||||
/* should not be called from interrupt context */
|
||||
spin_lock(&ftrace_lock);
|
||||
|
||||
ops->next = ftrace_list;
|
||||
@@ -115,6 +115,7 @@ static int __unregister_ftrace_function(struct ftrace_ops *ops)
|
||||
struct ftrace_ops **p;
|
||||
int ret = 0;
|
||||
|
||||
/* should not be called from interrupt context */
|
||||
spin_lock(&ftrace_lock);
|
||||
|
||||
/*
|
||||
@@ -153,6 +154,30 @@ static int __unregister_ftrace_function(struct ftrace_ops *ops)
|
||||
|
||||
#ifdef CONFIG_DYNAMIC_FTRACE
|
||||
|
||||
#ifndef CONFIG_FTRACE_MCOUNT_RECORD
|
||||
/*
|
||||
* The hash lock is only needed when the recording of the mcount
|
||||
* callers are dynamic. That is, by the caller themselves and
|
||||
* not recorded via the compilation.
|
||||
*/
|
||||
static DEFINE_SPINLOCK(ftrace_hash_lock);
|
||||
#define ftrace_hash_lock(flags) spin_lock_irqsave(&ftrace_hash_lock, flags)
|
||||
#define ftrace_hash_unlock(flags) \
|
||||
spin_unlock_irqrestore(&ftrace_hash_lock, flags)
|
||||
#else
|
||||
/* This is protected via the ftrace_lock with MCOUNT_RECORD. */
|
||||
#define ftrace_hash_lock(flags) do { (void)(flags); } while (0)
|
||||
#define ftrace_hash_unlock(flags) do { } while(0)
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Since MCOUNT_ADDR may point to mcount itself, we do not want
|
||||
* to get it confused by reading a reference in the code as we
|
||||
* are parsing on objcopy output of text. Use a variable for
|
||||
* it instead.
|
||||
*/
|
||||
static unsigned long mcount_addr = MCOUNT_ADDR;
|
||||
|
||||
static struct task_struct *ftraced_task;
|
||||
|
||||
enum {
|
||||
@@ -171,7 +196,6 @@ static struct hlist_head ftrace_hash[FTRACE_HASHSIZE];
|
||||
|
||||
static DEFINE_PER_CPU(int, ftrace_shutdown_disable_cpu);
|
||||
|
||||
static DEFINE_SPINLOCK(ftrace_shutdown_lock);
|
||||
static DEFINE_MUTEX(ftraced_lock);
|
||||
static DEFINE_MUTEX(ftrace_regex_lock);
|
||||
|
||||
@@ -294,13 +318,37 @@ static inline void ftrace_del_hash(struct dyn_ftrace *node)
|
||||
|
||||
static void ftrace_free_rec(struct dyn_ftrace *rec)
|
||||
{
|
||||
/* no locking, only called from kstop_machine */
|
||||
|
||||
rec->ip = (unsigned long)ftrace_free_records;
|
||||
ftrace_free_records = rec;
|
||||
rec->flags |= FTRACE_FL_FREE;
|
||||
}
|
||||
|
||||
void ftrace_release(void *start, unsigned long size)
|
||||
{
|
||||
struct dyn_ftrace *rec;
|
||||
struct ftrace_page *pg;
|
||||
unsigned long s = (unsigned long)start;
|
||||
unsigned long e = s + size;
|
||||
int i;
|
||||
|
||||
if (ftrace_disabled || !start)
|
||||
return;
|
||||
|
||||
/* should not be called from interrupt context */
|
||||
spin_lock(&ftrace_lock);
|
||||
|
||||
for (pg = ftrace_pages_start; pg; pg = pg->next) {
|
||||
for (i = 0; i < pg->index; i++) {
|
||||
rec = &pg->records[i];
|
||||
|
||||
if ((rec->ip >= s) && (rec->ip < e))
|
||||
ftrace_free_rec(rec);
|
||||
}
|
||||
}
|
||||
spin_unlock(&ftrace_lock);
|
||||
|
||||
}
|
||||
|
||||
static struct dyn_ftrace *ftrace_alloc_dyn_node(unsigned long ip)
|
||||
{
|
||||
struct dyn_ftrace *rec;
|
||||
@@ -338,7 +386,6 @@ ftrace_record_ip(unsigned long ip)
|
||||
unsigned long flags;
|
||||
unsigned long key;
|
||||
int resched;
|
||||
int atomic;
|
||||
int cpu;
|
||||
|
||||
if (!ftrace_enabled || ftrace_disabled)
|
||||
@@ -368,9 +415,7 @@ ftrace_record_ip(unsigned long ip)
|
||||
if (ftrace_ip_in_hash(ip, key))
|
||||
goto out;
|
||||
|
||||
atomic = irqs_disabled();
|
||||
|
||||
spin_lock_irqsave(&ftrace_shutdown_lock, flags);
|
||||
ftrace_hash_lock(flags);
|
||||
|
||||
/* This ip may have hit the hash before the lock */
|
||||
if (ftrace_ip_in_hash(ip, key))
|
||||
@@ -387,7 +432,7 @@ ftrace_record_ip(unsigned long ip)
|
||||
ftraced_trigger = 1;
|
||||
|
||||
out_unlock:
|
||||
spin_unlock_irqrestore(&ftrace_shutdown_lock, flags);
|
||||
ftrace_hash_unlock(flags);
|
||||
out:
|
||||
per_cpu(ftrace_shutdown_disable_cpu, cpu)--;
|
||||
|
||||
@@ -531,6 +576,16 @@ static void ftrace_shutdown_replenish(void)
|
||||
ftrace_pages->next = (void *)get_zeroed_page(GFP_KERNEL);
|
||||
}
|
||||
|
||||
static void print_ip_ins(const char *fmt, unsigned char *p)
|
||||
{
|
||||
int i;
|
||||
|
||||
printk(KERN_CONT "%s", fmt);
|
||||
|
||||
for (i = 0; i < MCOUNT_INSN_SIZE; i++)
|
||||
printk(KERN_CONT "%s%02x", i ? ":" : "", p[i]);
|
||||
}
|
||||
|
||||
static int
|
||||
ftrace_code_disable(struct dyn_ftrace *rec)
|
||||
{
|
||||
@@ -541,10 +596,27 @@ ftrace_code_disable(struct dyn_ftrace *rec)
|
||||
ip = rec->ip;
|
||||
|
||||
nop = ftrace_nop_replace();
|
||||
call = ftrace_call_replace(ip, MCOUNT_ADDR);
|
||||
call = ftrace_call_replace(ip, mcount_addr);
|
||||
|
||||
failed = ftrace_modify_code(ip, call, nop);
|
||||
if (failed) {
|
||||
switch (failed) {
|
||||
case 1:
|
||||
WARN_ON_ONCE(1);
|
||||
pr_info("ftrace faulted on modifying ");
|
||||
print_ip_sym(ip);
|
||||
break;
|
||||
case 2:
|
||||
WARN_ON_ONCE(1);
|
||||
pr_info("ftrace failed to modify ");
|
||||
print_ip_sym(ip);
|
||||
print_ip_ins(" expected: ", call);
|
||||
print_ip_ins(" actual: ", (unsigned char *)ip);
|
||||
print_ip_ins(" replace: ", nop);
|
||||
printk(KERN_CONT "\n");
|
||||
break;
|
||||
}
|
||||
|
||||
rec->flags |= FTRACE_FL_FAILED;
|
||||
return 0;
|
||||
}
|
||||
@@ -792,47 +864,7 @@ static int ftrace_update_code(void)
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int ftraced(void *ignore)
|
||||
{
|
||||
unsigned long usecs;
|
||||
|
||||
while (!kthread_should_stop()) {
|
||||
|
||||
set_current_state(TASK_INTERRUPTIBLE);
|
||||
|
||||
/* check once a second */
|
||||
schedule_timeout(HZ);
|
||||
|
||||
if (unlikely(ftrace_disabled))
|
||||
continue;
|
||||
|
||||
mutex_lock(&ftrace_sysctl_lock);
|
||||
mutex_lock(&ftraced_lock);
|
||||
if (!ftraced_suspend && !ftraced_stop &&
|
||||
ftrace_update_code()) {
|
||||
usecs = nsecs_to_usecs(ftrace_update_time);
|
||||
if (ftrace_update_tot_cnt > 100000) {
|
||||
ftrace_update_tot_cnt = 0;
|
||||
pr_info("hm, dftrace overflow: %lu change%s"
|
||||
" (%lu total) in %lu usec%s\n",
|
||||
ftrace_update_cnt,
|
||||
ftrace_update_cnt != 1 ? "s" : "",
|
||||
ftrace_update_tot_cnt,
|
||||
usecs, usecs != 1 ? "s" : "");
|
||||
ftrace_disabled = 1;
|
||||
WARN_ON_ONCE(1);
|
||||
}
|
||||
}
|
||||
mutex_unlock(&ftraced_lock);
|
||||
mutex_unlock(&ftrace_sysctl_lock);
|
||||
|
||||
ftrace_shutdown_replenish();
|
||||
}
|
||||
__set_current_state(TASK_RUNNING);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __init ftrace_dyn_table_alloc(void)
|
||||
static int __init ftrace_dyn_table_alloc(unsigned long num_to_init)
|
||||
{
|
||||
struct ftrace_page *pg;
|
||||
int cnt;
|
||||
@@ -859,7 +891,9 @@ static int __init ftrace_dyn_table_alloc(void)
|
||||
|
||||
pg = ftrace_pages = ftrace_pages_start;
|
||||
|
||||
cnt = NR_TO_INIT / ENTRIES_PER_PAGE;
|
||||
cnt = num_to_init / ENTRIES_PER_PAGE;
|
||||
pr_info("ftrace: allocating %ld hash entries in %d pages\n",
|
||||
num_to_init, cnt);
|
||||
|
||||
for (i = 0; i < cnt; i++) {
|
||||
pg->next = (void *)get_zeroed_page(GFP_KERNEL);
|
||||
@@ -901,6 +935,8 @@ t_next(struct seq_file *m, void *v, loff_t *pos)
|
||||
|
||||
(*pos)++;
|
||||
|
||||
/* should not be called from interrupt context */
|
||||
spin_lock(&ftrace_lock);
|
||||
retry:
|
||||
if (iter->idx >= iter->pg->index) {
|
||||
if (iter->pg->next) {
|
||||
@@ -910,15 +946,13 @@ t_next(struct seq_file *m, void *v, loff_t *pos)
|
||||
}
|
||||
} else {
|
||||
rec = &iter->pg->records[iter->idx++];
|
||||
if ((!(iter->flags & FTRACE_ITER_FAILURES) &&
|
||||
if ((rec->flags & FTRACE_FL_FREE) ||
|
||||
|
||||
(!(iter->flags & FTRACE_ITER_FAILURES) &&
|
||||
(rec->flags & FTRACE_FL_FAILED)) ||
|
||||
|
||||
((iter->flags & FTRACE_ITER_FAILURES) &&
|
||||
(!(rec->flags & FTRACE_FL_FAILED) ||
|
||||
(rec->flags & FTRACE_FL_FREE))) ||
|
||||
|
||||
((iter->flags & FTRACE_ITER_FILTER) &&
|
||||
!(rec->flags & FTRACE_FL_FILTER)) ||
|
||||
!(rec->flags & FTRACE_FL_FAILED)) ||
|
||||
|
||||
((iter->flags & FTRACE_ITER_NOTRACE) &&
|
||||
!(rec->flags & FTRACE_FL_NOTRACE))) {
|
||||
@@ -926,6 +960,7 @@ t_next(struct seq_file *m, void *v, loff_t *pos)
|
||||
goto retry;
|
||||
}
|
||||
}
|
||||
spin_unlock(&ftrace_lock);
|
||||
|
||||
iter->pos = *pos;
|
||||
|
||||
@@ -1039,8 +1074,8 @@ static void ftrace_filter_reset(int enable)
|
||||
unsigned long type = enable ? FTRACE_FL_FILTER : FTRACE_FL_NOTRACE;
|
||||
unsigned i;
|
||||
|
||||
/* keep kstop machine from running */
|
||||
preempt_disable();
|
||||
/* should not be called from interrupt context */
|
||||
spin_lock(&ftrace_lock);
|
||||
if (enable)
|
||||
ftrace_filtered = 0;
|
||||
pg = ftrace_pages_start;
|
||||
@@ -1053,7 +1088,7 @@ static void ftrace_filter_reset(int enable)
|
||||
}
|
||||
pg = pg->next;
|
||||
}
|
||||
preempt_enable();
|
||||
spin_unlock(&ftrace_lock);
|
||||
}
|
||||
|
||||
static int
|
||||
@@ -1165,8 +1200,8 @@ ftrace_match(unsigned char *buff, int len, int enable)
|
||||
}
|
||||
}
|
||||
|
||||
/* keep kstop machine from running */
|
||||
preempt_disable();
|
||||
/* should not be called from interrupt context */
|
||||
spin_lock(&ftrace_lock);
|
||||
if (enable)
|
||||
ftrace_filtered = 1;
|
||||
pg = ftrace_pages_start;
|
||||
@@ -1203,7 +1238,7 @@ ftrace_match(unsigned char *buff, int len, int enable)
|
||||
}
|
||||
pg = pg->next;
|
||||
}
|
||||
preempt_enable();
|
||||
spin_unlock(&ftrace_lock);
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
@@ -1556,6 +1591,114 @@ static __init int ftrace_init_debugfs(void)
|
||||
|
||||
fs_initcall(ftrace_init_debugfs);
|
||||
|
||||
#ifdef CONFIG_FTRACE_MCOUNT_RECORD
|
||||
static int ftrace_convert_nops(unsigned long *start,
|
||||
unsigned long *end)
|
||||
{
|
||||
unsigned long *p;
|
||||
unsigned long addr;
|
||||
unsigned long flags;
|
||||
|
||||
p = start;
|
||||
while (p < end) {
|
||||
addr = ftrace_call_adjust(*p++);
|
||||
/* should not be called from interrupt context */
|
||||
spin_lock(&ftrace_lock);
|
||||
ftrace_record_ip(addr);
|
||||
spin_unlock(&ftrace_lock);
|
||||
ftrace_shutdown_replenish();
|
||||
}
|
||||
|
||||
/* p is ignored */
|
||||
local_irq_save(flags);
|
||||
__ftrace_update_code(p);
|
||||
local_irq_restore(flags);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void ftrace_init_module(unsigned long *start, unsigned long *end)
|
||||
{
|
||||
if (ftrace_disabled || start == end)
|
||||
return;
|
||||
ftrace_convert_nops(start, end);
|
||||
}
|
||||
|
||||
extern unsigned long __start_mcount_loc[];
|
||||
extern unsigned long __stop_mcount_loc[];
|
||||
|
||||
void __init ftrace_init(void)
|
||||
{
|
||||
unsigned long count, addr, flags;
|
||||
int ret;
|
||||
|
||||
/* Keep the ftrace pointer to the stub */
|
||||
addr = (unsigned long)ftrace_stub;
|
||||
|
||||
local_irq_save(flags);
|
||||
ftrace_dyn_arch_init(&addr);
|
||||
local_irq_restore(flags);
|
||||
|
||||
/* ftrace_dyn_arch_init places the return code in addr */
|
||||
if (addr)
|
||||
goto failed;
|
||||
|
||||
count = __stop_mcount_loc - __start_mcount_loc;
|
||||
|
||||
ret = ftrace_dyn_table_alloc(count);
|
||||
if (ret)
|
||||
goto failed;
|
||||
|
||||
last_ftrace_enabled = ftrace_enabled = 1;
|
||||
|
||||
ret = ftrace_convert_nops(__start_mcount_loc,
|
||||
__stop_mcount_loc);
|
||||
|
||||
return;
|
||||
failed:
|
||||
ftrace_disabled = 1;
|
||||
}
|
||||
#else /* CONFIG_FTRACE_MCOUNT_RECORD */
|
||||
static int ftraced(void *ignore)
|
||||
{
|
||||
unsigned long usecs;
|
||||
|
||||
while (!kthread_should_stop()) {
|
||||
|
||||
set_current_state(TASK_INTERRUPTIBLE);
|
||||
|
||||
/* check once a second */
|
||||
schedule_timeout(HZ);
|
||||
|
||||
if (unlikely(ftrace_disabled))
|
||||
continue;
|
||||
|
||||
mutex_lock(&ftrace_sysctl_lock);
|
||||
mutex_lock(&ftraced_lock);
|
||||
if (!ftraced_suspend && !ftraced_stop &&
|
||||
ftrace_update_code()) {
|
||||
usecs = nsecs_to_usecs(ftrace_update_time);
|
||||
if (ftrace_update_tot_cnt > 100000) {
|
||||
ftrace_update_tot_cnt = 0;
|
||||
pr_info("hm, dftrace overflow: %lu change%s"
|
||||
" (%lu total) in %lu usec%s\n",
|
||||
ftrace_update_cnt,
|
||||
ftrace_update_cnt != 1 ? "s" : "",
|
||||
ftrace_update_tot_cnt,
|
||||
usecs, usecs != 1 ? "s" : "");
|
||||
ftrace_disabled = 1;
|
||||
WARN_ON_ONCE(1);
|
||||
}
|
||||
}
|
||||
mutex_unlock(&ftraced_lock);
|
||||
mutex_unlock(&ftrace_sysctl_lock);
|
||||
|
||||
ftrace_shutdown_replenish();
|
||||
}
|
||||
__set_current_state(TASK_RUNNING);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __init ftrace_dynamic_init(void)
|
||||
{
|
||||
struct task_struct *p;
|
||||
@@ -1572,7 +1715,7 @@ static int __init ftrace_dynamic_init(void)
|
||||
goto failed;
|
||||
}
|
||||
|
||||
ret = ftrace_dyn_table_alloc();
|
||||
ret = ftrace_dyn_table_alloc(NR_TO_INIT);
|
||||
if (ret)
|
||||
goto failed;
|
||||
|
||||
@@ -1593,6 +1736,8 @@ static int __init ftrace_dynamic_init(void)
|
||||
}
|
||||
|
||||
core_initcall(ftrace_dynamic_init);
|
||||
#endif /* CONFIG_FTRACE_MCOUNT_RECORD */
|
||||
|
||||
#else
|
||||
# define ftrace_startup() do { } while (0)
|
||||
# define ftrace_shutdown() do { } while (0)
|
||||
|
2014
kernel/trace/ring_buffer.c
Normal file
2014
kernel/trace/ring_buffer.c
Normal file
File diff suppressed because it is too large
Load Diff
1903
kernel/trace/trace.c
1903
kernel/trace/trace.c
File diff suppressed because it is too large
Load Diff
@@ -5,7 +5,9 @@
|
||||
#include <asm/atomic.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/clocksource.h>
|
||||
#include <linux/ring_buffer.h>
|
||||
#include <linux/mmiotrace.h>
|
||||
#include <linux/ftrace.h>
|
||||
|
||||
enum trace_type {
|
||||
__TRACE_FIRST_TYPE = 0,
|
||||
@@ -13,38 +15,60 @@ enum trace_type {
|
||||
TRACE_FN,
|
||||
TRACE_CTX,
|
||||
TRACE_WAKE,
|
||||
TRACE_CONT,
|
||||
TRACE_STACK,
|
||||
TRACE_PRINT,
|
||||
TRACE_SPECIAL,
|
||||
TRACE_MMIO_RW,
|
||||
TRACE_MMIO_MAP,
|
||||
TRACE_BOOT,
|
||||
|
||||
__TRACE_LAST_TYPE
|
||||
};
|
||||
|
||||
/*
|
||||
* The trace entry - the most basic unit of tracing. This is what
|
||||
* is printed in the end as a single line in the trace output, such as:
|
||||
*
|
||||
* bash-15816 [01] 235.197585: idle_cpu <- irq_enter
|
||||
*/
|
||||
struct trace_entry {
|
||||
unsigned char type;
|
||||
unsigned char cpu;
|
||||
unsigned char flags;
|
||||
unsigned char preempt_count;
|
||||
int pid;
|
||||
};
|
||||
|
||||
/*
|
||||
* Function trace entry - function address and parent function addres:
|
||||
*/
|
||||
struct ftrace_entry {
|
||||
struct trace_entry ent;
|
||||
unsigned long ip;
|
||||
unsigned long parent_ip;
|
||||
};
|
||||
extern struct tracer boot_tracer;
|
||||
|
||||
/*
|
||||
* Context switch trace entry - which task (and prio) we switched from/to:
|
||||
*/
|
||||
struct ctx_switch_entry {
|
||||
struct trace_entry ent;
|
||||
unsigned int prev_pid;
|
||||
unsigned char prev_prio;
|
||||
unsigned char prev_state;
|
||||
unsigned int next_pid;
|
||||
unsigned char next_prio;
|
||||
unsigned char next_state;
|
||||
unsigned int next_cpu;
|
||||
};
|
||||
|
||||
/*
|
||||
* Special (free-form) trace entry:
|
||||
*/
|
||||
struct special_entry {
|
||||
struct trace_entry ent;
|
||||
unsigned long arg1;
|
||||
unsigned long arg2;
|
||||
unsigned long arg3;
|
||||
@@ -57,33 +81,60 @@ struct special_entry {
|
||||
#define FTRACE_STACK_ENTRIES 8
|
||||
|
||||
struct stack_entry {
|
||||
struct trace_entry ent;
|
||||
unsigned long caller[FTRACE_STACK_ENTRIES];
|
||||
};
|
||||
|
||||
/*
|
||||
* The trace entry - the most basic unit of tracing. This is what
|
||||
* is printed in the end as a single line in the trace output, such as:
|
||||
*
|
||||
* bash-15816 [01] 235.197585: idle_cpu <- irq_enter
|
||||
* ftrace_printk entry:
|
||||
*/
|
||||
struct trace_entry {
|
||||
char type;
|
||||
char cpu;
|
||||
char flags;
|
||||
char preempt_count;
|
||||
int pid;
|
||||
cycle_t t;
|
||||
union {
|
||||
struct ftrace_entry fn;
|
||||
struct ctx_switch_entry ctx;
|
||||
struct special_entry special;
|
||||
struct stack_entry stack;
|
||||
struct mmiotrace_rw mmiorw;
|
||||
struct mmiotrace_map mmiomap;
|
||||
};
|
||||
struct print_entry {
|
||||
struct trace_entry ent;
|
||||
unsigned long ip;
|
||||
char buf[];
|
||||
};
|
||||
|
||||
#define TRACE_ENTRY_SIZE sizeof(struct trace_entry)
|
||||
#define TRACE_OLD_SIZE 88
|
||||
|
||||
struct trace_field_cont {
|
||||
unsigned char type;
|
||||
/* Temporary till we get rid of this completely */
|
||||
char buf[TRACE_OLD_SIZE - 1];
|
||||
};
|
||||
|
||||
struct trace_mmiotrace_rw {
|
||||
struct trace_entry ent;
|
||||
struct mmiotrace_rw rw;
|
||||
};
|
||||
|
||||
struct trace_mmiotrace_map {
|
||||
struct trace_entry ent;
|
||||
struct mmiotrace_map map;
|
||||
};
|
||||
|
||||
struct trace_boot {
|
||||
struct trace_entry ent;
|
||||
struct boot_trace initcall;
|
||||
};
|
||||
|
||||
/*
|
||||
* trace_flag_type is an enumeration that holds different
|
||||
* states when a trace occurs. These are:
|
||||
* IRQS_OFF - interrupts were disabled
|
||||
* NEED_RESCED - reschedule is requested
|
||||
* HARDIRQ - inside an interrupt handler
|
||||
* SOFTIRQ - inside a softirq handler
|
||||
* CONT - multiple entries hold the trace item
|
||||
*/
|
||||
enum trace_flag_type {
|
||||
TRACE_FLAG_IRQS_OFF = 0x01,
|
||||
TRACE_FLAG_NEED_RESCHED = 0x02,
|
||||
TRACE_FLAG_HARDIRQ = 0x04,
|
||||
TRACE_FLAG_SOFTIRQ = 0x08,
|
||||
TRACE_FLAG_CONT = 0x10,
|
||||
};
|
||||
|
||||
#define TRACE_BUF_SIZE 1024
|
||||
|
||||
/*
|
||||
* The CPU trace array - it consists of thousands of trace entries
|
||||
@@ -91,16 +142,9 @@ struct trace_entry {
|
||||
* the trace, etc.)
|
||||
*/
|
||||
struct trace_array_cpu {
|
||||
struct list_head trace_pages;
|
||||
atomic_t disabled;
|
||||
raw_spinlock_t lock;
|
||||
struct lock_class_key lock_key;
|
||||
|
||||
/* these fields get copied into max-trace: */
|
||||
unsigned trace_head_idx;
|
||||
unsigned trace_tail_idx;
|
||||
void *trace_head; /* producer */
|
||||
void *trace_tail; /* consumer */
|
||||
unsigned long trace_idx;
|
||||
unsigned long overrun;
|
||||
unsigned long saved_latency;
|
||||
@@ -124,6 +168,7 @@ struct trace_iterator;
|
||||
* They have on/off state as well:
|
||||
*/
|
||||
struct trace_array {
|
||||
struct ring_buffer *buffer;
|
||||
unsigned long entries;
|
||||
long ctrl;
|
||||
int cpu;
|
||||
@@ -132,6 +177,56 @@ struct trace_array {
|
||||
struct trace_array_cpu *data[NR_CPUS];
|
||||
};
|
||||
|
||||
#define FTRACE_CMP_TYPE(var, type) \
|
||||
__builtin_types_compatible_p(typeof(var), type *)
|
||||
|
||||
#undef IF_ASSIGN
|
||||
#define IF_ASSIGN(var, entry, etype, id) \
|
||||
if (FTRACE_CMP_TYPE(var, etype)) { \
|
||||
var = (typeof(var))(entry); \
|
||||
WARN_ON(id && (entry)->type != id); \
|
||||
break; \
|
||||
}
|
||||
|
||||
/* Will cause compile errors if type is not found. */
|
||||
extern void __ftrace_bad_type(void);
|
||||
|
||||
/*
|
||||
* The trace_assign_type is a verifier that the entry type is
|
||||
* the same as the type being assigned. To add new types simply
|
||||
* add a line with the following format:
|
||||
*
|
||||
* IF_ASSIGN(var, ent, type, id);
|
||||
*
|
||||
* Where "type" is the trace type that includes the trace_entry
|
||||
* as the "ent" item. And "id" is the trace identifier that is
|
||||
* used in the trace_type enum.
|
||||
*
|
||||
* If the type can have more than one id, then use zero.
|
||||
*/
|
||||
#define trace_assign_type(var, ent) \
|
||||
do { \
|
||||
IF_ASSIGN(var, ent, struct ftrace_entry, TRACE_FN); \
|
||||
IF_ASSIGN(var, ent, struct ctx_switch_entry, 0); \
|
||||
IF_ASSIGN(var, ent, struct trace_field_cont, TRACE_CONT); \
|
||||
IF_ASSIGN(var, ent, struct stack_entry, TRACE_STACK); \
|
||||
IF_ASSIGN(var, ent, struct print_entry, TRACE_PRINT); \
|
||||
IF_ASSIGN(var, ent, struct special_entry, 0); \
|
||||
IF_ASSIGN(var, ent, struct trace_mmiotrace_rw, \
|
||||
TRACE_MMIO_RW); \
|
||||
IF_ASSIGN(var, ent, struct trace_mmiotrace_map, \
|
||||
TRACE_MMIO_MAP); \
|
||||
IF_ASSIGN(var, ent, struct trace_boot, TRACE_BOOT); \
|
||||
__ftrace_bad_type(); \
|
||||
} while (0)
|
||||
|
||||
/* Return values for print_line callback */
|
||||
enum print_line_t {
|
||||
TRACE_TYPE_PARTIAL_LINE = 0, /* Retry after flushing the seq */
|
||||
TRACE_TYPE_HANDLED = 1,
|
||||
TRACE_TYPE_UNHANDLED = 2 /* Relay to other output functions */
|
||||
};
|
||||
|
||||
/*
|
||||
* A specific tracer, represented by methods that operate on a trace array:
|
||||
*/
|
||||
@@ -152,7 +247,7 @@ struct tracer {
|
||||
int (*selftest)(struct tracer *trace,
|
||||
struct trace_array *tr);
|
||||
#endif
|
||||
int (*print_line)(struct trace_iterator *iter);
|
||||
enum print_line_t (*print_line)(struct trace_iterator *iter);
|
||||
struct tracer *next;
|
||||
int print_max;
|
||||
};
|
||||
@@ -171,57 +266,58 @@ struct trace_iterator {
|
||||
struct trace_array *tr;
|
||||
struct tracer *trace;
|
||||
void *private;
|
||||
long last_overrun[NR_CPUS];
|
||||
long overrun[NR_CPUS];
|
||||
struct ring_buffer_iter *buffer_iter[NR_CPUS];
|
||||
|
||||
/* The below is zeroed out in pipe_read */
|
||||
struct trace_seq seq;
|
||||
struct trace_entry *ent;
|
||||
int cpu;
|
||||
|
||||
struct trace_entry *prev_ent;
|
||||
int prev_cpu;
|
||||
u64 ts;
|
||||
|
||||
unsigned long iter_flags;
|
||||
loff_t pos;
|
||||
unsigned long next_idx[NR_CPUS];
|
||||
struct list_head *next_page[NR_CPUS];
|
||||
unsigned next_page_idx[NR_CPUS];
|
||||
long idx;
|
||||
};
|
||||
|
||||
void tracing_reset(struct trace_array_cpu *data);
|
||||
void trace_wake_up(void);
|
||||
void tracing_reset(struct trace_array *tr, int cpu);
|
||||
int tracing_open_generic(struct inode *inode, struct file *filp);
|
||||
struct dentry *tracing_init_dentry(void);
|
||||
void init_tracer_sysprof_debugfs(struct dentry *d_tracer);
|
||||
|
||||
struct trace_entry *tracing_get_trace_entry(struct trace_array *tr,
|
||||
struct trace_array_cpu *data);
|
||||
void tracing_generic_entry_update(struct trace_entry *entry,
|
||||
unsigned long flags,
|
||||
int pc);
|
||||
|
||||
void ftrace(struct trace_array *tr,
|
||||
struct trace_array_cpu *data,
|
||||
unsigned long ip,
|
||||
unsigned long parent_ip,
|
||||
unsigned long flags);
|
||||
unsigned long flags, int pc);
|
||||
void tracing_sched_switch_trace(struct trace_array *tr,
|
||||
struct trace_array_cpu *data,
|
||||
struct task_struct *prev,
|
||||
struct task_struct *next,
|
||||
unsigned long flags);
|
||||
unsigned long flags, int pc);
|
||||
void tracing_record_cmdline(struct task_struct *tsk);
|
||||
|
||||
void tracing_sched_wakeup_trace(struct trace_array *tr,
|
||||
struct trace_array_cpu *data,
|
||||
struct task_struct *wakee,
|
||||
struct task_struct *cur,
|
||||
unsigned long flags);
|
||||
unsigned long flags, int pc);
|
||||
void trace_special(struct trace_array *tr,
|
||||
struct trace_array_cpu *data,
|
||||
unsigned long arg1,
|
||||
unsigned long arg2,
|
||||
unsigned long arg3);
|
||||
unsigned long arg3, int pc);
|
||||
void trace_function(struct trace_array *tr,
|
||||
struct trace_array_cpu *data,
|
||||
unsigned long ip,
|
||||
unsigned long parent_ip,
|
||||
unsigned long flags);
|
||||
unsigned long flags, int pc);
|
||||
|
||||
void tracing_start_cmdline_record(void);
|
||||
void tracing_stop_cmdline_record(void);
|
||||
@@ -268,51 +364,33 @@ extern unsigned long ftrace_update_tot_cnt;
|
||||
extern int DYN_FTRACE_TEST_NAME(void);
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_MMIOTRACE
|
||||
extern void __trace_mmiotrace_rw(struct trace_array *tr,
|
||||
struct trace_array_cpu *data,
|
||||
struct mmiotrace_rw *rw);
|
||||
extern void __trace_mmiotrace_map(struct trace_array *tr,
|
||||
struct trace_array_cpu *data,
|
||||
struct mmiotrace_map *map);
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_FTRACE_STARTUP_TEST
|
||||
#ifdef CONFIG_FTRACE
|
||||
extern int trace_selftest_startup_function(struct tracer *trace,
|
||||
struct trace_array *tr);
|
||||
#endif
|
||||
#ifdef CONFIG_IRQSOFF_TRACER
|
||||
extern int trace_selftest_startup_irqsoff(struct tracer *trace,
|
||||
struct trace_array *tr);
|
||||
#endif
|
||||
#ifdef CONFIG_PREEMPT_TRACER
|
||||
extern int trace_selftest_startup_preemptoff(struct tracer *trace,
|
||||
struct trace_array *tr);
|
||||
#endif
|
||||
#if defined(CONFIG_IRQSOFF_TRACER) && defined(CONFIG_PREEMPT_TRACER)
|
||||
extern int trace_selftest_startup_preemptirqsoff(struct tracer *trace,
|
||||
struct trace_array *tr);
|
||||
#endif
|
||||
#ifdef CONFIG_SCHED_TRACER
|
||||
extern int trace_selftest_startup_wakeup(struct tracer *trace,
|
||||
struct trace_array *tr);
|
||||
#endif
|
||||
#ifdef CONFIG_CONTEXT_SWITCH_TRACER
|
||||
extern int trace_selftest_startup_nop(struct tracer *trace,
|
||||
struct trace_array *tr);
|
||||
extern int trace_selftest_startup_sched_switch(struct tracer *trace,
|
||||
struct trace_array *tr);
|
||||
#endif
|
||||
#ifdef CONFIG_SYSPROF_TRACER
|
||||
extern int trace_selftest_startup_sysprof(struct tracer *trace,
|
||||
struct trace_array *tr);
|
||||
#endif
|
||||
#endif /* CONFIG_FTRACE_STARTUP_TEST */
|
||||
|
||||
extern void *head_page(struct trace_array_cpu *data);
|
||||
extern int trace_seq_printf(struct trace_seq *s, const char *fmt, ...);
|
||||
extern void trace_seq_print_cont(struct trace_seq *s,
|
||||
struct trace_iterator *iter);
|
||||
extern ssize_t trace_seq_to_user(struct trace_seq *s, char __user *ubuf,
|
||||
size_t cnt);
|
||||
extern long ns2usecs(cycle_t nsec);
|
||||
extern int trace_vprintk(unsigned long ip, const char *fmt, va_list args);
|
||||
|
||||
extern unsigned long trace_flags;
|
||||
|
||||
@@ -334,6 +412,9 @@ enum trace_iterator_flags {
|
||||
TRACE_ITER_BLOCK = 0x80,
|
||||
TRACE_ITER_STACKTRACE = 0x100,
|
||||
TRACE_ITER_SCHED_TREE = 0x200,
|
||||
TRACE_ITER_PRINTK = 0x400,
|
||||
};
|
||||
|
||||
extern struct tracer nop_trace;
|
||||
|
||||
#endif /* _LINUX_KERNEL_TRACE_H */
|
||||
|
126
kernel/trace/trace_boot.c
Normal file
126
kernel/trace/trace_boot.c
Normal file
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
* ring buffer based initcalls tracer
|
||||
*
|
||||
* Copyright (C) 2008 Frederic Weisbecker <fweisbec@gmail.com>
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/init.h>
|
||||
#include <linux/debugfs.h>
|
||||
#include <linux/ftrace.h>
|
||||
#include <linux/kallsyms.h>
|
||||
|
||||
#include "trace.h"
|
||||
|
||||
static struct trace_array *boot_trace;
|
||||
static int trace_boot_enabled;
|
||||
|
||||
|
||||
/* Should be started after do_pre_smp_initcalls() in init/main.c */
|
||||
void start_boot_trace(void)
|
||||
{
|
||||
trace_boot_enabled = 1;
|
||||
}
|
||||
|
||||
void stop_boot_trace(void)
|
||||
{
|
||||
trace_boot_enabled = 0;
|
||||
}
|
||||
|
||||
void reset_boot_trace(struct trace_array *tr)
|
||||
{
|
||||
stop_boot_trace();
|
||||
}
|
||||
|
||||
static void boot_trace_init(struct trace_array *tr)
|
||||
{
|
||||
int cpu;
|
||||
boot_trace = tr;
|
||||
|
||||
trace_boot_enabled = 0;
|
||||
|
||||
for_each_cpu_mask(cpu, cpu_possible_map)
|
||||
tracing_reset(tr, cpu);
|
||||
}
|
||||
|
||||
static void boot_trace_ctrl_update(struct trace_array *tr)
|
||||
{
|
||||
if (tr->ctrl)
|
||||
start_boot_trace();
|
||||
else
|
||||
stop_boot_trace();
|
||||
}
|
||||
|
||||
static enum print_line_t initcall_print_line(struct trace_iterator *iter)
|
||||
{
|
||||
int ret;
|
||||
struct trace_entry *entry = iter->ent;
|
||||
struct trace_boot *field = (struct trace_boot *)entry;
|
||||
struct boot_trace *it = &field->initcall;
|
||||
struct trace_seq *s = &iter->seq;
|
||||
struct timespec calltime = ktime_to_timespec(it->calltime);
|
||||
struct timespec rettime = ktime_to_timespec(it->rettime);
|
||||
|
||||
if (entry->type == TRACE_BOOT) {
|
||||
ret = trace_seq_printf(s, "[%5ld.%09ld] calling %s @ %i\n",
|
||||
calltime.tv_sec,
|
||||
calltime.tv_nsec,
|
||||
it->func, it->caller);
|
||||
if (!ret)
|
||||
return TRACE_TYPE_PARTIAL_LINE;
|
||||
|
||||
ret = trace_seq_printf(s, "[%5ld.%09ld] initcall %s "
|
||||
"returned %d after %lld msecs\n",
|
||||
rettime.tv_sec,
|
||||
rettime.tv_nsec,
|
||||
it->func, it->result, it->duration);
|
||||
|
||||
if (!ret)
|
||||
return TRACE_TYPE_PARTIAL_LINE;
|
||||
return TRACE_TYPE_HANDLED;
|
||||
}
|
||||
return TRACE_TYPE_UNHANDLED;
|
||||
}
|
||||
|
||||
struct tracer boot_tracer __read_mostly =
|
||||
{
|
||||
.name = "initcall",
|
||||
.init = boot_trace_init,
|
||||
.reset = reset_boot_trace,
|
||||
.ctrl_update = boot_trace_ctrl_update,
|
||||
.print_line = initcall_print_line,
|
||||
};
|
||||
|
||||
void trace_boot(struct boot_trace *it, initcall_t fn)
|
||||
{
|
||||
struct ring_buffer_event *event;
|
||||
struct trace_boot *entry;
|
||||
struct trace_array_cpu *data;
|
||||
unsigned long irq_flags;
|
||||
struct trace_array *tr = boot_trace;
|
||||
|
||||
if (!trace_boot_enabled)
|
||||
return;
|
||||
|
||||
/* Get its name now since this function could
|
||||
* disappear because it is in the .init section.
|
||||
*/
|
||||
sprint_symbol(it->func, (unsigned long)fn);
|
||||
preempt_disable();
|
||||
data = tr->data[smp_processor_id()];
|
||||
|
||||
event = ring_buffer_lock_reserve(tr->buffer, sizeof(*entry),
|
||||
&irq_flags);
|
||||
if (!event)
|
||||
goto out;
|
||||
entry = ring_buffer_event_data(event);
|
||||
tracing_generic_entry_update(&entry->ent, 0, 0);
|
||||
entry->ent.type = TRACE_BOOT;
|
||||
entry->initcall = *it;
|
||||
ring_buffer_unlock_commit(tr->buffer, event, irq_flags);
|
||||
|
||||
trace_wake_up();
|
||||
|
||||
out:
|
||||
preempt_enable();
|
||||
}
|
@@ -23,7 +23,7 @@ static void function_reset(struct trace_array *tr)
|
||||
tr->time_start = ftrace_now(tr->cpu);
|
||||
|
||||
for_each_online_cpu(cpu)
|
||||
tracing_reset(tr->data[cpu]);
|
||||
tracing_reset(tr, cpu);
|
||||
}
|
||||
|
||||
static void start_function_trace(struct trace_array *tr)
|
||||
|
@@ -95,7 +95,7 @@ irqsoff_tracer_call(unsigned long ip, unsigned long parent_ip)
|
||||
disabled = atomic_inc_return(&data->disabled);
|
||||
|
||||
if (likely(disabled == 1))
|
||||
trace_function(tr, data, ip, parent_ip, flags);
|
||||
trace_function(tr, data, ip, parent_ip, flags, preempt_count());
|
||||
|
||||
atomic_dec(&data->disabled);
|
||||
}
|
||||
@@ -130,6 +130,7 @@ check_critical_timing(struct trace_array *tr,
|
||||
unsigned long latency, t0, t1;
|
||||
cycle_t T0, T1, delta;
|
||||
unsigned long flags;
|
||||
int pc;
|
||||
|
||||
/*
|
||||
* usecs conversion is slow so we try to delay the conversion
|
||||
@@ -141,6 +142,8 @@ check_critical_timing(struct trace_array *tr,
|
||||
|
||||
local_save_flags(flags);
|
||||
|
||||
pc = preempt_count();
|
||||
|
||||
if (!report_latency(delta))
|
||||
goto out;
|
||||
|
||||
@@ -150,7 +153,7 @@ check_critical_timing(struct trace_array *tr,
|
||||
if (!report_latency(delta))
|
||||
goto out_unlock;
|
||||
|
||||
trace_function(tr, data, CALLER_ADDR0, parent_ip, flags);
|
||||
trace_function(tr, data, CALLER_ADDR0, parent_ip, flags, pc);
|
||||
|
||||
latency = nsecs_to_usecs(delta);
|
||||
|
||||
@@ -173,8 +176,8 @@ out_unlock:
|
||||
out:
|
||||
data->critical_sequence = max_sequence;
|
||||
data->preempt_timestamp = ftrace_now(cpu);
|
||||
tracing_reset(data);
|
||||
trace_function(tr, data, CALLER_ADDR0, parent_ip, flags);
|
||||
tracing_reset(tr, cpu);
|
||||
trace_function(tr, data, CALLER_ADDR0, parent_ip, flags, pc);
|
||||
}
|
||||
|
||||
static inline void
|
||||
@@ -203,11 +206,11 @@ start_critical_timing(unsigned long ip, unsigned long parent_ip)
|
||||
data->critical_sequence = max_sequence;
|
||||
data->preempt_timestamp = ftrace_now(cpu);
|
||||
data->critical_start = parent_ip ? : ip;
|
||||
tracing_reset(data);
|
||||
tracing_reset(tr, cpu);
|
||||
|
||||
local_save_flags(flags);
|
||||
|
||||
trace_function(tr, data, ip, parent_ip, flags);
|
||||
trace_function(tr, data, ip, parent_ip, flags, preempt_count());
|
||||
|
||||
per_cpu(tracing_cpu, cpu) = 1;
|
||||
|
||||
@@ -234,14 +237,14 @@ stop_critical_timing(unsigned long ip, unsigned long parent_ip)
|
||||
|
||||
data = tr->data[cpu];
|
||||
|
||||
if (unlikely(!data) || unlikely(!head_page(data)) ||
|
||||
if (unlikely(!data) ||
|
||||
!data->critical_start || atomic_read(&data->disabled))
|
||||
return;
|
||||
|
||||
atomic_inc(&data->disabled);
|
||||
|
||||
local_save_flags(flags);
|
||||
trace_function(tr, data, ip, parent_ip, flags);
|
||||
trace_function(tr, data, ip, parent_ip, flags, preempt_count());
|
||||
check_critical_timing(tr, data, parent_ip ? : ip, cpu);
|
||||
data->critical_start = 0;
|
||||
atomic_dec(&data->disabled);
|
||||
|
@@ -27,7 +27,7 @@ static void mmio_reset_data(struct trace_array *tr)
|
||||
tr->time_start = ftrace_now(tr->cpu);
|
||||
|
||||
for_each_online_cpu(cpu)
|
||||
tracing_reset(tr->data[cpu]);
|
||||
tracing_reset(tr, cpu);
|
||||
}
|
||||
|
||||
static void mmio_trace_init(struct trace_array *tr)
|
||||
@@ -130,10 +130,14 @@ static unsigned long count_overruns(struct trace_iterator *iter)
|
||||
{
|
||||
int cpu;
|
||||
unsigned long cnt = 0;
|
||||
/* FIXME: */
|
||||
#if 0
|
||||
for_each_online_cpu(cpu) {
|
||||
cnt += iter->overrun[cpu];
|
||||
iter->overrun[cpu] = 0;
|
||||
}
|
||||
#endif
|
||||
(void)cpu;
|
||||
return cnt;
|
||||
}
|
||||
|
||||
@@ -171,17 +175,21 @@ print_out:
|
||||
return (ret == -EBUSY) ? 0 : ret;
|
||||
}
|
||||
|
||||
static int mmio_print_rw(struct trace_iterator *iter)
|
||||
static enum print_line_t mmio_print_rw(struct trace_iterator *iter)
|
||||
{
|
||||
struct trace_entry *entry = iter->ent;
|
||||
struct mmiotrace_rw *rw = &entry->mmiorw;
|
||||
struct trace_mmiotrace_rw *field;
|
||||
struct mmiotrace_rw *rw;
|
||||
struct trace_seq *s = &iter->seq;
|
||||
unsigned long long t = ns2usecs(entry->t);
|
||||
unsigned long long t = ns2usecs(iter->ts);
|
||||
unsigned long usec_rem = do_div(t, 1000000ULL);
|
||||
unsigned secs = (unsigned long)t;
|
||||
int ret = 1;
|
||||
|
||||
switch (entry->mmiorw.opcode) {
|
||||
trace_assign_type(field, entry);
|
||||
rw = &field->rw;
|
||||
|
||||
switch (rw->opcode) {
|
||||
case MMIO_READ:
|
||||
ret = trace_seq_printf(s,
|
||||
"R %d %lu.%06lu %d 0x%llx 0x%lx 0x%lx %d\n",
|
||||
@@ -209,21 +217,25 @@ static int mmio_print_rw(struct trace_iterator *iter)
|
||||
break;
|
||||
}
|
||||
if (ret)
|
||||
return 1;
|
||||
return 0;
|
||||
return TRACE_TYPE_HANDLED;
|
||||
return TRACE_TYPE_PARTIAL_LINE;
|
||||
}
|
||||
|
||||
static int mmio_print_map(struct trace_iterator *iter)
|
||||
static enum print_line_t mmio_print_map(struct trace_iterator *iter)
|
||||
{
|
||||
struct trace_entry *entry = iter->ent;
|
||||
struct mmiotrace_map *m = &entry->mmiomap;
|
||||
struct trace_mmiotrace_map *field;
|
||||
struct mmiotrace_map *m;
|
||||
struct trace_seq *s = &iter->seq;
|
||||
unsigned long long t = ns2usecs(entry->t);
|
||||
unsigned long long t = ns2usecs(iter->ts);
|
||||
unsigned long usec_rem = do_div(t, 1000000ULL);
|
||||
unsigned secs = (unsigned long)t;
|
||||
int ret = 1;
|
||||
int ret;
|
||||
|
||||
switch (entry->mmiorw.opcode) {
|
||||
trace_assign_type(field, entry);
|
||||
m = &field->map;
|
||||
|
||||
switch (m->opcode) {
|
||||
case MMIO_PROBE:
|
||||
ret = trace_seq_printf(s,
|
||||
"MAP %lu.%06lu %d 0x%llx 0x%lx 0x%lx 0x%lx %d\n",
|
||||
@@ -241,20 +253,43 @@ static int mmio_print_map(struct trace_iterator *iter)
|
||||
break;
|
||||
}
|
||||
if (ret)
|
||||
return 1;
|
||||
return 0;
|
||||
return TRACE_TYPE_HANDLED;
|
||||
return TRACE_TYPE_PARTIAL_LINE;
|
||||
}
|
||||
|
||||
/* return 0 to abort printing without consuming current entry in pipe mode */
|
||||
static int mmio_print_line(struct trace_iterator *iter)
|
||||
static enum print_line_t mmio_print_mark(struct trace_iterator *iter)
|
||||
{
|
||||
struct trace_entry *entry = iter->ent;
|
||||
struct print_entry *print = (struct print_entry *)entry;
|
||||
const char *msg = print->buf;
|
||||
struct trace_seq *s = &iter->seq;
|
||||
unsigned long long t = ns2usecs(iter->ts);
|
||||
unsigned long usec_rem = do_div(t, 1000000ULL);
|
||||
unsigned secs = (unsigned long)t;
|
||||
int ret;
|
||||
|
||||
/* The trailing newline must be in the message. */
|
||||
ret = trace_seq_printf(s, "MARK %lu.%06lu %s", secs, usec_rem, msg);
|
||||
if (!ret)
|
||||
return TRACE_TYPE_PARTIAL_LINE;
|
||||
|
||||
if (entry->flags & TRACE_FLAG_CONT)
|
||||
trace_seq_print_cont(s, iter);
|
||||
|
||||
return TRACE_TYPE_HANDLED;
|
||||
}
|
||||
|
||||
static enum print_line_t mmio_print_line(struct trace_iterator *iter)
|
||||
{
|
||||
switch (iter->ent->type) {
|
||||
case TRACE_MMIO_RW:
|
||||
return mmio_print_rw(iter);
|
||||
case TRACE_MMIO_MAP:
|
||||
return mmio_print_map(iter);
|
||||
case TRACE_PRINT:
|
||||
return mmio_print_mark(iter);
|
||||
default:
|
||||
return 1; /* ignore unknown entries */
|
||||
return TRACE_TYPE_HANDLED; /* ignore unknown entries */
|
||||
}
|
||||
}
|
||||
|
||||
@@ -276,6 +311,27 @@ __init static int init_mmio_trace(void)
|
||||
}
|
||||
device_initcall(init_mmio_trace);
|
||||
|
||||
static void __trace_mmiotrace_rw(struct trace_array *tr,
|
||||
struct trace_array_cpu *data,
|
||||
struct mmiotrace_rw *rw)
|
||||
{
|
||||
struct ring_buffer_event *event;
|
||||
struct trace_mmiotrace_rw *entry;
|
||||
unsigned long irq_flags;
|
||||
|
||||
event = ring_buffer_lock_reserve(tr->buffer, sizeof(*entry),
|
||||
&irq_flags);
|
||||
if (!event)
|
||||
return;
|
||||
entry = ring_buffer_event_data(event);
|
||||
tracing_generic_entry_update(&entry->ent, 0, preempt_count());
|
||||
entry->ent.type = TRACE_MMIO_RW;
|
||||
entry->rw = *rw;
|
||||
ring_buffer_unlock_commit(tr->buffer, event, irq_flags);
|
||||
|
||||
trace_wake_up();
|
||||
}
|
||||
|
||||
void mmio_trace_rw(struct mmiotrace_rw *rw)
|
||||
{
|
||||
struct trace_array *tr = mmio_trace_array;
|
||||
@@ -283,6 +339,27 @@ void mmio_trace_rw(struct mmiotrace_rw *rw)
|
||||
__trace_mmiotrace_rw(tr, data, rw);
|
||||
}
|
||||
|
||||
static void __trace_mmiotrace_map(struct trace_array *tr,
|
||||
struct trace_array_cpu *data,
|
||||
struct mmiotrace_map *map)
|
||||
{
|
||||
struct ring_buffer_event *event;
|
||||
struct trace_mmiotrace_map *entry;
|
||||
unsigned long irq_flags;
|
||||
|
||||
event = ring_buffer_lock_reserve(tr->buffer, sizeof(*entry),
|
||||
&irq_flags);
|
||||
if (!event)
|
||||
return;
|
||||
entry = ring_buffer_event_data(event);
|
||||
tracing_generic_entry_update(&entry->ent, 0, preempt_count());
|
||||
entry->ent.type = TRACE_MMIO_MAP;
|
||||
entry->map = *map;
|
||||
ring_buffer_unlock_commit(tr->buffer, event, irq_flags);
|
||||
|
||||
trace_wake_up();
|
||||
}
|
||||
|
||||
void mmio_trace_mapping(struct mmiotrace_map *map)
|
||||
{
|
||||
struct trace_array *tr = mmio_trace_array;
|
||||
@@ -293,3 +370,8 @@ void mmio_trace_mapping(struct mmiotrace_map *map)
|
||||
__trace_mmiotrace_map(tr, data, map);
|
||||
preempt_enable();
|
||||
}
|
||||
|
||||
int mmio_trace_printk(const char *fmt, va_list args)
|
||||
{
|
||||
return trace_vprintk(0, fmt, args);
|
||||
}
|
||||
|
64
kernel/trace/trace_nop.c
Normal file
64
kernel/trace/trace_nop.c
Normal file
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* nop tracer
|
||||
*
|
||||
* Copyright (C) 2008 Steven Noonan <steven@uplinklabs.net>
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/debugfs.h>
|
||||
#include <linux/ftrace.h>
|
||||
|
||||
#include "trace.h"
|
||||
|
||||
static struct trace_array *ctx_trace;
|
||||
|
||||
static void start_nop_trace(struct trace_array *tr)
|
||||
{
|
||||
/* Nothing to do! */
|
||||
}
|
||||
|
||||
static void stop_nop_trace(struct trace_array *tr)
|
||||
{
|
||||
/* Nothing to do! */
|
||||
}
|
||||
|
||||
static void nop_trace_init(struct trace_array *tr)
|
||||
{
|
||||
int cpu;
|
||||
ctx_trace = tr;
|
||||
|
||||
for_each_online_cpu(cpu)
|
||||
tracing_reset(tr, cpu);
|
||||
|
||||
if (tr->ctrl)
|
||||
start_nop_trace(tr);
|
||||
}
|
||||
|
||||
static void nop_trace_reset(struct trace_array *tr)
|
||||
{
|
||||
if (tr->ctrl)
|
||||
stop_nop_trace(tr);
|
||||
}
|
||||
|
||||
static void nop_trace_ctrl_update(struct trace_array *tr)
|
||||
{
|
||||
/* When starting a new trace, reset the buffers */
|
||||
if (tr->ctrl)
|
||||
start_nop_trace(tr);
|
||||
else
|
||||
stop_nop_trace(tr);
|
||||
}
|
||||
|
||||
struct tracer nop_trace __read_mostly =
|
||||
{
|
||||
.name = "nop",
|
||||
.init = nop_trace_init,
|
||||
.reset = nop_trace_reset,
|
||||
.ctrl_update = nop_trace_ctrl_update,
|
||||
#ifdef CONFIG_FTRACE_SELFTEST
|
||||
.selftest = trace_selftest_startup_nop,
|
||||
#endif
|
||||
};
|
||||
|
@@ -9,8 +9,8 @@
|
||||
#include <linux/debugfs.h>
|
||||
#include <linux/kallsyms.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <linux/marker.h>
|
||||
#include <linux/ftrace.h>
|
||||
#include <trace/sched.h>
|
||||
|
||||
#include "trace.h"
|
||||
|
||||
@@ -19,15 +19,16 @@ static int __read_mostly tracer_enabled;
|
||||
static atomic_t sched_ref;
|
||||
|
||||
static void
|
||||
sched_switch_func(void *private, void *__rq, struct task_struct *prev,
|
||||
probe_sched_switch(struct rq *__rq, struct task_struct *prev,
|
||||
struct task_struct *next)
|
||||
{
|
||||
struct trace_array **ptr = private;
|
||||
struct trace_array *tr = *ptr;
|
||||
struct trace_array_cpu *data;
|
||||
unsigned long flags;
|
||||
long disabled;
|
||||
int cpu;
|
||||
int pc;
|
||||
|
||||
if (!atomic_read(&sched_ref))
|
||||
return;
|
||||
|
||||
tracing_record_cmdline(prev);
|
||||
tracing_record_cmdline(next);
|
||||
@@ -35,97 +36,41 @@ sched_switch_func(void *private, void *__rq, struct task_struct *prev,
|
||||
if (!tracer_enabled)
|
||||
return;
|
||||
|
||||
pc = preempt_count();
|
||||
local_irq_save(flags);
|
||||
cpu = raw_smp_processor_id();
|
||||
data = tr->data[cpu];
|
||||
disabled = atomic_inc_return(&data->disabled);
|
||||
data = ctx_trace->data[cpu];
|
||||
|
||||
if (likely(disabled == 1))
|
||||
tracing_sched_switch_trace(tr, data, prev, next, flags);
|
||||
if (likely(!atomic_read(&data->disabled)))
|
||||
tracing_sched_switch_trace(ctx_trace, data, prev, next, flags, pc);
|
||||
|
||||
atomic_dec(&data->disabled);
|
||||
local_irq_restore(flags);
|
||||
}
|
||||
|
||||
static notrace void
|
||||
sched_switch_callback(void *probe_data, void *call_data,
|
||||
const char *format, va_list *args)
|
||||
{
|
||||
struct task_struct *prev;
|
||||
struct task_struct *next;
|
||||
struct rq *__rq;
|
||||
|
||||
if (!atomic_read(&sched_ref))
|
||||
return;
|
||||
|
||||
/* skip prev_pid %d next_pid %d prev_state %ld */
|
||||
(void)va_arg(*args, int);
|
||||
(void)va_arg(*args, int);
|
||||
(void)va_arg(*args, long);
|
||||
__rq = va_arg(*args, typeof(__rq));
|
||||
prev = va_arg(*args, typeof(prev));
|
||||
next = va_arg(*args, typeof(next));
|
||||
|
||||
/*
|
||||
* If tracer_switch_func only points to the local
|
||||
* switch func, it still needs the ptr passed to it.
|
||||
*/
|
||||
sched_switch_func(probe_data, __rq, prev, next);
|
||||
}
|
||||
|
||||
static void
|
||||
wakeup_func(void *private, void *__rq, struct task_struct *wakee, struct
|
||||
task_struct *curr)
|
||||
probe_sched_wakeup(struct rq *__rq, struct task_struct *wakee)
|
||||
{
|
||||
struct trace_array **ptr = private;
|
||||
struct trace_array *tr = *ptr;
|
||||
struct trace_array_cpu *data;
|
||||
unsigned long flags;
|
||||
long disabled;
|
||||
int cpu;
|
||||
int cpu, pc;
|
||||
|
||||
if (!tracer_enabled)
|
||||
if (!likely(tracer_enabled))
|
||||
return;
|
||||
|
||||
tracing_record_cmdline(curr);
|
||||
pc = preempt_count();
|
||||
tracing_record_cmdline(current);
|
||||
|
||||
local_irq_save(flags);
|
||||
cpu = raw_smp_processor_id();
|
||||
data = tr->data[cpu];
|
||||
disabled = atomic_inc_return(&data->disabled);
|
||||
data = ctx_trace->data[cpu];
|
||||
|
||||
if (likely(disabled == 1))
|
||||
tracing_sched_wakeup_trace(tr, data, wakee, curr, flags);
|
||||
if (likely(!atomic_read(&data->disabled)))
|
||||
tracing_sched_wakeup_trace(ctx_trace, data, wakee, current,
|
||||
flags, pc);
|
||||
|
||||
atomic_dec(&data->disabled);
|
||||
local_irq_restore(flags);
|
||||
}
|
||||
|
||||
static notrace void
|
||||
wake_up_callback(void *probe_data, void *call_data,
|
||||
const char *format, va_list *args)
|
||||
{
|
||||
struct task_struct *curr;
|
||||
struct task_struct *task;
|
||||
struct rq *__rq;
|
||||
|
||||
if (likely(!tracer_enabled))
|
||||
return;
|
||||
|
||||
/* Skip pid %d state %ld */
|
||||
(void)va_arg(*args, int);
|
||||
(void)va_arg(*args, long);
|
||||
/* now get the meat: "rq %p task %p rq->curr %p" */
|
||||
__rq = va_arg(*args, typeof(__rq));
|
||||
task = va_arg(*args, typeof(task));
|
||||
curr = va_arg(*args, typeof(curr));
|
||||
|
||||
tracing_record_cmdline(task);
|
||||
tracing_record_cmdline(curr);
|
||||
|
||||
wakeup_func(probe_data, __rq, task, curr);
|
||||
}
|
||||
|
||||
static void sched_switch_reset(struct trace_array *tr)
|
||||
{
|
||||
int cpu;
|
||||
@@ -133,67 +78,47 @@ static void sched_switch_reset(struct trace_array *tr)
|
||||
tr->time_start = ftrace_now(tr->cpu);
|
||||
|
||||
for_each_online_cpu(cpu)
|
||||
tracing_reset(tr->data[cpu]);
|
||||
tracing_reset(tr, cpu);
|
||||
}
|
||||
|
||||
static int tracing_sched_register(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = marker_probe_register("kernel_sched_wakeup",
|
||||
"pid %d state %ld ## rq %p task %p rq->curr %p",
|
||||
wake_up_callback,
|
||||
&ctx_trace);
|
||||
ret = register_trace_sched_wakeup(probe_sched_wakeup);
|
||||
if (ret) {
|
||||
pr_info("wakeup trace: Couldn't add marker"
|
||||
pr_info("wakeup trace: Couldn't activate tracepoint"
|
||||
" probe to kernel_sched_wakeup\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = marker_probe_register("kernel_sched_wakeup_new",
|
||||
"pid %d state %ld ## rq %p task %p rq->curr %p",
|
||||
wake_up_callback,
|
||||
&ctx_trace);
|
||||
ret = register_trace_sched_wakeup_new(probe_sched_wakeup);
|
||||
if (ret) {
|
||||
pr_info("wakeup trace: Couldn't add marker"
|
||||
pr_info("wakeup trace: Couldn't activate tracepoint"
|
||||
" probe to kernel_sched_wakeup_new\n");
|
||||
goto fail_deprobe;
|
||||
}
|
||||
|
||||
ret = marker_probe_register("kernel_sched_schedule",
|
||||
"prev_pid %d next_pid %d prev_state %ld "
|
||||
"## rq %p prev %p next %p",
|
||||
sched_switch_callback,
|
||||
&ctx_trace);
|
||||
ret = register_trace_sched_switch(probe_sched_switch);
|
||||
if (ret) {
|
||||
pr_info("sched trace: Couldn't add marker"
|
||||
pr_info("sched trace: Couldn't activate tracepoint"
|
||||
" probe to kernel_sched_schedule\n");
|
||||
goto fail_deprobe_wake_new;
|
||||
}
|
||||
|
||||
return ret;
|
||||
fail_deprobe_wake_new:
|
||||
marker_probe_unregister("kernel_sched_wakeup_new",
|
||||
wake_up_callback,
|
||||
&ctx_trace);
|
||||
unregister_trace_sched_wakeup_new(probe_sched_wakeup);
|
||||
fail_deprobe:
|
||||
marker_probe_unregister("kernel_sched_wakeup",
|
||||
wake_up_callback,
|
||||
&ctx_trace);
|
||||
unregister_trace_sched_wakeup(probe_sched_wakeup);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void tracing_sched_unregister(void)
|
||||
{
|
||||
marker_probe_unregister("kernel_sched_schedule",
|
||||
sched_switch_callback,
|
||||
&ctx_trace);
|
||||
marker_probe_unregister("kernel_sched_wakeup_new",
|
||||
wake_up_callback,
|
||||
&ctx_trace);
|
||||
marker_probe_unregister("kernel_sched_wakeup",
|
||||
wake_up_callback,
|
||||
&ctx_trace);
|
||||
unregister_trace_sched_switch(probe_sched_switch);
|
||||
unregister_trace_sched_wakeup_new(probe_sched_wakeup);
|
||||
unregister_trace_sched_wakeup(probe_sched_wakeup);
|
||||
}
|
||||
|
||||
static void tracing_start_sched_switch(void)
|
||||
|
@@ -15,7 +15,7 @@
|
||||
#include <linux/kallsyms.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <linux/ftrace.h>
|
||||
#include <linux/marker.h>
|
||||
#include <trace/sched.h>
|
||||
|
||||
#include "trace.h"
|
||||
|
||||
@@ -44,10 +44,12 @@ wakeup_tracer_call(unsigned long ip, unsigned long parent_ip)
|
||||
long disabled;
|
||||
int resched;
|
||||
int cpu;
|
||||
int pc;
|
||||
|
||||
if (likely(!wakeup_task))
|
||||
return;
|
||||
|
||||
pc = preempt_count();
|
||||
resched = need_resched();
|
||||
preempt_disable_notrace();
|
||||
|
||||
@@ -70,7 +72,7 @@ wakeup_tracer_call(unsigned long ip, unsigned long parent_ip)
|
||||
if (task_cpu(wakeup_task) != cpu)
|
||||
goto unlock;
|
||||
|
||||
trace_function(tr, data, ip, parent_ip, flags);
|
||||
trace_function(tr, data, ip, parent_ip, flags, pc);
|
||||
|
||||
unlock:
|
||||
__raw_spin_unlock(&wakeup_lock);
|
||||
@@ -112,17 +114,18 @@ static int report_latency(cycle_t delta)
|
||||
}
|
||||
|
||||
static void notrace
|
||||
wakeup_sched_switch(void *private, void *rq, struct task_struct *prev,
|
||||
probe_wakeup_sched_switch(struct rq *rq, struct task_struct *prev,
|
||||
struct task_struct *next)
|
||||
{
|
||||
unsigned long latency = 0, t0 = 0, t1 = 0;
|
||||
struct trace_array **ptr = private;
|
||||
struct trace_array *tr = *ptr;
|
||||
struct trace_array_cpu *data;
|
||||
cycle_t T0, T1, delta;
|
||||
unsigned long flags;
|
||||
long disabled;
|
||||
int cpu;
|
||||
int pc;
|
||||
|
||||
tracing_record_cmdline(prev);
|
||||
|
||||
if (unlikely(!tracer_enabled))
|
||||
return;
|
||||
@@ -139,12 +142,14 @@ wakeup_sched_switch(void *private, void *rq, struct task_struct *prev,
|
||||
if (next != wakeup_task)
|
||||
return;
|
||||
|
||||
pc = preempt_count();
|
||||
|
||||
/* The task we are waiting for is waking up */
|
||||
data = tr->data[wakeup_cpu];
|
||||
data = wakeup_trace->data[wakeup_cpu];
|
||||
|
||||
/* disable local data, not wakeup_cpu data */
|
||||
cpu = raw_smp_processor_id();
|
||||
disabled = atomic_inc_return(&tr->data[cpu]->disabled);
|
||||
disabled = atomic_inc_return(&wakeup_trace->data[cpu]->disabled);
|
||||
if (likely(disabled != 1))
|
||||
goto out;
|
||||
|
||||
@@ -155,7 +160,7 @@ wakeup_sched_switch(void *private, void *rq, struct task_struct *prev,
|
||||
if (unlikely(!tracer_enabled || next != wakeup_task))
|
||||
goto out_unlock;
|
||||
|
||||
trace_function(tr, data, CALLER_ADDR1, CALLER_ADDR2, flags);
|
||||
trace_function(wakeup_trace, data, CALLER_ADDR1, CALLER_ADDR2, flags, pc);
|
||||
|
||||
/*
|
||||
* usecs conversion is slow so we try to delay the conversion
|
||||
@@ -174,39 +179,14 @@ wakeup_sched_switch(void *private, void *rq, struct task_struct *prev,
|
||||
t0 = nsecs_to_usecs(T0);
|
||||
t1 = nsecs_to_usecs(T1);
|
||||
|
||||
update_max_tr(tr, wakeup_task, wakeup_cpu);
|
||||
update_max_tr(wakeup_trace, wakeup_task, wakeup_cpu);
|
||||
|
||||
out_unlock:
|
||||
__wakeup_reset(tr);
|
||||
__wakeup_reset(wakeup_trace);
|
||||
__raw_spin_unlock(&wakeup_lock);
|
||||
local_irq_restore(flags);
|
||||
out:
|
||||
atomic_dec(&tr->data[cpu]->disabled);
|
||||
}
|
||||
|
||||
static notrace void
|
||||
sched_switch_callback(void *probe_data, void *call_data,
|
||||
const char *format, va_list *args)
|
||||
{
|
||||
struct task_struct *prev;
|
||||
struct task_struct *next;
|
||||
struct rq *__rq;
|
||||
|
||||
/* skip prev_pid %d next_pid %d prev_state %ld */
|
||||
(void)va_arg(*args, int);
|
||||
(void)va_arg(*args, int);
|
||||
(void)va_arg(*args, long);
|
||||
__rq = va_arg(*args, typeof(__rq));
|
||||
prev = va_arg(*args, typeof(prev));
|
||||
next = va_arg(*args, typeof(next));
|
||||
|
||||
tracing_record_cmdline(prev);
|
||||
|
||||
/*
|
||||
* If tracer_switch_func only points to the local
|
||||
* switch func, it still needs the ptr passed to it.
|
||||
*/
|
||||
wakeup_sched_switch(probe_data, __rq, prev, next);
|
||||
atomic_dec(&wakeup_trace->data[cpu]->disabled);
|
||||
}
|
||||
|
||||
static void __wakeup_reset(struct trace_array *tr)
|
||||
@@ -216,7 +196,7 @@ static void __wakeup_reset(struct trace_array *tr)
|
||||
|
||||
for_each_possible_cpu(cpu) {
|
||||
data = tr->data[cpu];
|
||||
tracing_reset(data);
|
||||
tracing_reset(tr, cpu);
|
||||
}
|
||||
|
||||
wakeup_cpu = -1;
|
||||
@@ -240,19 +220,26 @@ static void wakeup_reset(struct trace_array *tr)
|
||||
}
|
||||
|
||||
static void
|
||||
wakeup_check_start(struct trace_array *tr, struct task_struct *p,
|
||||
struct task_struct *curr)
|
||||
probe_wakeup(struct rq *rq, struct task_struct *p)
|
||||
{
|
||||
int cpu = smp_processor_id();
|
||||
unsigned long flags;
|
||||
long disabled;
|
||||
int pc;
|
||||
|
||||
if (likely(!tracer_enabled))
|
||||
return;
|
||||
|
||||
tracing_record_cmdline(p);
|
||||
tracing_record_cmdline(current);
|
||||
|
||||
if (likely(!rt_task(p)) ||
|
||||
p->prio >= wakeup_prio ||
|
||||
p->prio >= curr->prio)
|
||||
p->prio >= current->prio)
|
||||
return;
|
||||
|
||||
disabled = atomic_inc_return(&tr->data[cpu]->disabled);
|
||||
pc = preempt_count();
|
||||
disabled = atomic_inc_return(&wakeup_trace->data[cpu]->disabled);
|
||||
if (unlikely(disabled != 1))
|
||||
goto out;
|
||||
|
||||
@@ -264,7 +251,7 @@ wakeup_check_start(struct trace_array *tr, struct task_struct *p,
|
||||
goto out_locked;
|
||||
|
||||
/* reset the trace */
|
||||
__wakeup_reset(tr);
|
||||
__wakeup_reset(wakeup_trace);
|
||||
|
||||
wakeup_cpu = task_cpu(p);
|
||||
wakeup_prio = p->prio;
|
||||
@@ -274,74 +261,37 @@ wakeup_check_start(struct trace_array *tr, struct task_struct *p,
|
||||
|
||||
local_save_flags(flags);
|
||||
|
||||
tr->data[wakeup_cpu]->preempt_timestamp = ftrace_now(cpu);
|
||||
trace_function(tr, tr->data[wakeup_cpu],
|
||||
CALLER_ADDR1, CALLER_ADDR2, flags);
|
||||
wakeup_trace->data[wakeup_cpu]->preempt_timestamp = ftrace_now(cpu);
|
||||
trace_function(wakeup_trace, wakeup_trace->data[wakeup_cpu],
|
||||
CALLER_ADDR1, CALLER_ADDR2, flags, pc);
|
||||
|
||||
out_locked:
|
||||
__raw_spin_unlock(&wakeup_lock);
|
||||
out:
|
||||
atomic_dec(&tr->data[cpu]->disabled);
|
||||
}
|
||||
|
||||
static notrace void
|
||||
wake_up_callback(void *probe_data, void *call_data,
|
||||
const char *format, va_list *args)
|
||||
{
|
||||
struct trace_array **ptr = probe_data;
|
||||
struct trace_array *tr = *ptr;
|
||||
struct task_struct *curr;
|
||||
struct task_struct *task;
|
||||
struct rq *__rq;
|
||||
|
||||
if (likely(!tracer_enabled))
|
||||
return;
|
||||
|
||||
/* Skip pid %d state %ld */
|
||||
(void)va_arg(*args, int);
|
||||
(void)va_arg(*args, long);
|
||||
/* now get the meat: "rq %p task %p rq->curr %p" */
|
||||
__rq = va_arg(*args, typeof(__rq));
|
||||
task = va_arg(*args, typeof(task));
|
||||
curr = va_arg(*args, typeof(curr));
|
||||
|
||||
tracing_record_cmdline(task);
|
||||
tracing_record_cmdline(curr);
|
||||
|
||||
wakeup_check_start(tr, task, curr);
|
||||
atomic_dec(&wakeup_trace->data[cpu]->disabled);
|
||||
}
|
||||
|
||||
static void start_wakeup_tracer(struct trace_array *tr)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = marker_probe_register("kernel_sched_wakeup",
|
||||
"pid %d state %ld ## rq %p task %p rq->curr %p",
|
||||
wake_up_callback,
|
||||
&wakeup_trace);
|
||||
ret = register_trace_sched_wakeup(probe_wakeup);
|
||||
if (ret) {
|
||||
pr_info("wakeup trace: Couldn't add marker"
|
||||
pr_info("wakeup trace: Couldn't activate tracepoint"
|
||||
" probe to kernel_sched_wakeup\n");
|
||||
return;
|
||||
}
|
||||
|
||||
ret = marker_probe_register("kernel_sched_wakeup_new",
|
||||
"pid %d state %ld ## rq %p task %p rq->curr %p",
|
||||
wake_up_callback,
|
||||
&wakeup_trace);
|
||||
ret = register_trace_sched_wakeup_new(probe_wakeup);
|
||||
if (ret) {
|
||||
pr_info("wakeup trace: Couldn't add marker"
|
||||
pr_info("wakeup trace: Couldn't activate tracepoint"
|
||||
" probe to kernel_sched_wakeup_new\n");
|
||||
goto fail_deprobe;
|
||||
}
|
||||
|
||||
ret = marker_probe_register("kernel_sched_schedule",
|
||||
"prev_pid %d next_pid %d prev_state %ld "
|
||||
"## rq %p prev %p next %p",
|
||||
sched_switch_callback,
|
||||
&wakeup_trace);
|
||||
ret = register_trace_sched_switch(probe_wakeup_sched_switch);
|
||||
if (ret) {
|
||||
pr_info("sched trace: Couldn't add marker"
|
||||
pr_info("sched trace: Couldn't activate tracepoint"
|
||||
" probe to kernel_sched_schedule\n");
|
||||
goto fail_deprobe_wake_new;
|
||||
}
|
||||
@@ -363,28 +313,18 @@ static void start_wakeup_tracer(struct trace_array *tr)
|
||||
|
||||
return;
|
||||
fail_deprobe_wake_new:
|
||||
marker_probe_unregister("kernel_sched_wakeup_new",
|
||||
wake_up_callback,
|
||||
&wakeup_trace);
|
||||
unregister_trace_sched_wakeup_new(probe_wakeup);
|
||||
fail_deprobe:
|
||||
marker_probe_unregister("kernel_sched_wakeup",
|
||||
wake_up_callback,
|
||||
&wakeup_trace);
|
||||
unregister_trace_sched_wakeup(probe_wakeup);
|
||||
}
|
||||
|
||||
static void stop_wakeup_tracer(struct trace_array *tr)
|
||||
{
|
||||
tracer_enabled = 0;
|
||||
unregister_ftrace_function(&trace_ops);
|
||||
marker_probe_unregister("kernel_sched_schedule",
|
||||
sched_switch_callback,
|
||||
&wakeup_trace);
|
||||
marker_probe_unregister("kernel_sched_wakeup_new",
|
||||
wake_up_callback,
|
||||
&wakeup_trace);
|
||||
marker_probe_unregister("kernel_sched_wakeup",
|
||||
wake_up_callback,
|
||||
&wakeup_trace);
|
||||
unregister_trace_sched_switch(probe_wakeup_sched_switch);
|
||||
unregister_trace_sched_wakeup_new(probe_wakeup);
|
||||
unregister_trace_sched_wakeup(probe_wakeup);
|
||||
}
|
||||
|
||||
static void wakeup_tracer_init(struct trace_array *tr)
|
||||
|
@@ -9,65 +9,29 @@ static inline int trace_valid_entry(struct trace_entry *entry)
|
||||
case TRACE_FN:
|
||||
case TRACE_CTX:
|
||||
case TRACE_WAKE:
|
||||
case TRACE_CONT:
|
||||
case TRACE_STACK:
|
||||
case TRACE_PRINT:
|
||||
case TRACE_SPECIAL:
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
trace_test_buffer_cpu(struct trace_array *tr, struct trace_array_cpu *data)
|
||||
static int trace_test_buffer_cpu(struct trace_array *tr, int cpu)
|
||||
{
|
||||
struct trace_entry *entries;
|
||||
struct page *page;
|
||||
int idx = 0;
|
||||
int i;
|
||||
struct ring_buffer_event *event;
|
||||
struct trace_entry *entry;
|
||||
|
||||
BUG_ON(list_empty(&data->trace_pages));
|
||||
page = list_entry(data->trace_pages.next, struct page, lru);
|
||||
entries = page_address(page);
|
||||
while ((event = ring_buffer_consume(tr->buffer, cpu, NULL))) {
|
||||
entry = ring_buffer_event_data(event);
|
||||
|
||||
check_pages(data);
|
||||
if (head_page(data) != entries)
|
||||
goto failed;
|
||||
|
||||
/*
|
||||
* The starting trace buffer always has valid elements,
|
||||
* if any element exists.
|
||||
*/
|
||||
entries = head_page(data);
|
||||
|
||||
for (i = 0; i < tr->entries; i++) {
|
||||
|
||||
if (i < data->trace_idx && !trace_valid_entry(&entries[idx])) {
|
||||
if (!trace_valid_entry(entry)) {
|
||||
printk(KERN_CONT ".. invalid entry %d ",
|
||||
entries[idx].type);
|
||||
entry->type);
|
||||
goto failed;
|
||||
}
|
||||
|
||||
idx++;
|
||||
if (idx >= ENTRIES_PER_PAGE) {
|
||||
page = virt_to_page(entries);
|
||||
if (page->lru.next == &data->trace_pages) {
|
||||
if (i != tr->entries - 1) {
|
||||
printk(KERN_CONT ".. entries buffer mismatch");
|
||||
goto failed;
|
||||
}
|
||||
} else {
|
||||
page = list_entry(page->lru.next, struct page, lru);
|
||||
entries = page_address(page);
|
||||
}
|
||||
idx = 0;
|
||||
}
|
||||
}
|
||||
|
||||
page = virt_to_page(entries);
|
||||
if (page->lru.next != &data->trace_pages) {
|
||||
printk(KERN_CONT ".. too many entries");
|
||||
goto failed;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
failed:
|
||||
@@ -89,13 +53,11 @@ static int trace_test_buffer(struct trace_array *tr, unsigned long *count)
|
||||
/* Don't allow flipping of max traces now */
|
||||
raw_local_irq_save(flags);
|
||||
__raw_spin_lock(&ftrace_max_lock);
|
||||
|
||||
cnt = ring_buffer_entries(tr->buffer);
|
||||
|
||||
for_each_possible_cpu(cpu) {
|
||||
if (!head_page(tr->data[cpu]))
|
||||
continue;
|
||||
|
||||
cnt += tr->data[cpu]->trace_idx;
|
||||
|
||||
ret = trace_test_buffer_cpu(tr, tr->data[cpu]);
|
||||
ret = trace_test_buffer_cpu(tr, cpu);
|
||||
if (ret)
|
||||
break;
|
||||
}
|
||||
@@ -120,11 +82,11 @@ int trace_selftest_startup_dynamic_tracing(struct tracer *trace,
|
||||
struct trace_array *tr,
|
||||
int (*func)(void))
|
||||
{
|
||||
unsigned long count;
|
||||
int ret;
|
||||
int save_ftrace_enabled = ftrace_enabled;
|
||||
int save_tracer_enabled = tracer_enabled;
|
||||
unsigned long count;
|
||||
char *func_name;
|
||||
int ret;
|
||||
|
||||
/* The ftrace test PASSED */
|
||||
printk(KERN_CONT "PASSED\n");
|
||||
@@ -157,6 +119,7 @@ int trace_selftest_startup_dynamic_tracing(struct tracer *trace,
|
||||
/* enable tracing */
|
||||
tr->ctrl = 1;
|
||||
trace->init(tr);
|
||||
|
||||
/* Sleep for a 1/10 of a second */
|
||||
msleep(100);
|
||||
|
||||
@@ -212,10 +175,10 @@ int trace_selftest_startup_dynamic_tracing(struct tracer *trace,
|
||||
int
|
||||
trace_selftest_startup_function(struct tracer *trace, struct trace_array *tr)
|
||||
{
|
||||
unsigned long count;
|
||||
int ret;
|
||||
int save_ftrace_enabled = ftrace_enabled;
|
||||
int save_tracer_enabled = tracer_enabled;
|
||||
unsigned long count;
|
||||
int ret;
|
||||
|
||||
/* make sure msleep has been recorded */
|
||||
msleep(1);
|
||||
@@ -415,6 +378,15 @@ trace_selftest_startup_preemptirqsoff(struct tracer *trace, struct trace_array *
|
||||
}
|
||||
#endif /* CONFIG_IRQSOFF_TRACER && CONFIG_PREEMPT_TRACER */
|
||||
|
||||
#ifdef CONFIG_NOP_TRACER
|
||||
int
|
||||
trace_selftest_startup_nop(struct tracer *trace, struct trace_array *tr)
|
||||
{
|
||||
/* What could possibly go wrong? */
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_SCHED_TRACER
|
||||
static int trace_wakeup_test_thread(void *data)
|
||||
{
|
||||
@@ -486,6 +458,9 @@ trace_selftest_startup_wakeup(struct tracer *trace, struct trace_array *tr)
|
||||
|
||||
wake_up_process(p);
|
||||
|
||||
/* give a little time to let the thread wake up */
|
||||
msleep(100);
|
||||
|
||||
/* stop the tracing. */
|
||||
tr->ctrl = 0;
|
||||
trace->ctrl_update(tr);
|
||||
|
310
kernel/trace/trace_stack.c
Normal file
310
kernel/trace/trace_stack.c
Normal file
@@ -0,0 +1,310 @@
|
||||
/*
|
||||
* Copyright (C) 2008 Steven Rostedt <srostedt@redhat.com>
|
||||
*
|
||||
*/
|
||||
#include <linux/stacktrace.h>
|
||||
#include <linux/kallsyms.h>
|
||||
#include <linux/seq_file.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <linux/debugfs.h>
|
||||
#include <linux/ftrace.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/fs.h>
|
||||
#include "trace.h"
|
||||
|
||||
#define STACK_TRACE_ENTRIES 500
|
||||
|
||||
static unsigned long stack_dump_trace[STACK_TRACE_ENTRIES+1] =
|
||||
{ [0 ... (STACK_TRACE_ENTRIES)] = ULONG_MAX };
|
||||
static unsigned stack_dump_index[STACK_TRACE_ENTRIES];
|
||||
|
||||
static struct stack_trace max_stack_trace = {
|
||||
.max_entries = STACK_TRACE_ENTRIES,
|
||||
.entries = stack_dump_trace,
|
||||
};
|
||||
|
||||
static unsigned long max_stack_size;
|
||||
static raw_spinlock_t max_stack_lock =
|
||||
(raw_spinlock_t)__RAW_SPIN_LOCK_UNLOCKED;
|
||||
|
||||
static int stack_trace_disabled __read_mostly;
|
||||
static DEFINE_PER_CPU(int, trace_active);
|
||||
|
||||
static inline void check_stack(void)
|
||||
{
|
||||
unsigned long this_size, flags;
|
||||
unsigned long *p, *top, *start;
|
||||
int i;
|
||||
|
||||
this_size = ((unsigned long)&this_size) & (THREAD_SIZE-1);
|
||||
this_size = THREAD_SIZE - this_size;
|
||||
|
||||
if (this_size <= max_stack_size)
|
||||
return;
|
||||
|
||||
raw_local_irq_save(flags);
|
||||
__raw_spin_lock(&max_stack_lock);
|
||||
|
||||
/* a race could have already updated it */
|
||||
if (this_size <= max_stack_size)
|
||||
goto out;
|
||||
|
||||
max_stack_size = this_size;
|
||||
|
||||
max_stack_trace.nr_entries = 0;
|
||||
max_stack_trace.skip = 3;
|
||||
|
||||
save_stack_trace(&max_stack_trace);
|
||||
|
||||
/*
|
||||
* Now find where in the stack these are.
|
||||
*/
|
||||
i = 0;
|
||||
start = &this_size;
|
||||
top = (unsigned long *)
|
||||
(((unsigned long)start & ~(THREAD_SIZE-1)) + THREAD_SIZE);
|
||||
|
||||
/*
|
||||
* Loop through all the entries. One of the entries may
|
||||
* for some reason be missed on the stack, so we may
|
||||
* have to account for them. If they are all there, this
|
||||
* loop will only happen once. This code only takes place
|
||||
* on a new max, so it is far from a fast path.
|
||||
*/
|
||||
while (i < max_stack_trace.nr_entries) {
|
||||
|
||||
stack_dump_index[i] = this_size;
|
||||
p = start;
|
||||
|
||||
for (; p < top && i < max_stack_trace.nr_entries; p++) {
|
||||
if (*p == stack_dump_trace[i]) {
|
||||
this_size = stack_dump_index[i++] =
|
||||
(top - p) * sizeof(unsigned long);
|
||||
/* Start the search from here */
|
||||
start = p + 1;
|
||||
}
|
||||
}
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
out:
|
||||
__raw_spin_unlock(&max_stack_lock);
|
||||
raw_local_irq_restore(flags);
|
||||
}
|
||||
|
||||
static void
|
||||
stack_trace_call(unsigned long ip, unsigned long parent_ip)
|
||||
{
|
||||
int cpu, resched;
|
||||
|
||||
if (unlikely(!ftrace_enabled || stack_trace_disabled))
|
||||
return;
|
||||
|
||||
resched = need_resched();
|
||||
preempt_disable_notrace();
|
||||
|
||||
cpu = raw_smp_processor_id();
|
||||
/* no atomic needed, we only modify this variable by this cpu */
|
||||
if (per_cpu(trace_active, cpu)++ != 0)
|
||||
goto out;
|
||||
|
||||
check_stack();
|
||||
|
||||
out:
|
||||
per_cpu(trace_active, cpu)--;
|
||||
/* prevent recursion in schedule */
|
||||
if (resched)
|
||||
preempt_enable_no_resched_notrace();
|
||||
else
|
||||
preempt_enable_notrace();
|
||||
}
|
||||
|
||||
static struct ftrace_ops trace_ops __read_mostly =
|
||||
{
|
||||
.func = stack_trace_call,
|
||||
};
|
||||
|
||||
static ssize_t
|
||||
stack_max_size_read(struct file *filp, char __user *ubuf,
|
||||
size_t count, loff_t *ppos)
|
||||
{
|
||||
unsigned long *ptr = filp->private_data;
|
||||
char buf[64];
|
||||
int r;
|
||||
|
||||
r = snprintf(buf, sizeof(buf), "%ld\n", *ptr);
|
||||
if (r > sizeof(buf))
|
||||
r = sizeof(buf);
|
||||
return simple_read_from_buffer(ubuf, count, ppos, buf, r);
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
stack_max_size_write(struct file *filp, const char __user *ubuf,
|
||||
size_t count, loff_t *ppos)
|
||||
{
|
||||
long *ptr = filp->private_data;
|
||||
unsigned long val, flags;
|
||||
char buf[64];
|
||||
int ret;
|
||||
|
||||
if (count >= sizeof(buf))
|
||||
return -EINVAL;
|
||||
|
||||
if (copy_from_user(&buf, ubuf, count))
|
||||
return -EFAULT;
|
||||
|
||||
buf[count] = 0;
|
||||
|
||||
ret = strict_strtoul(buf, 10, &val);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
raw_local_irq_save(flags);
|
||||
__raw_spin_lock(&max_stack_lock);
|
||||
*ptr = val;
|
||||
__raw_spin_unlock(&max_stack_lock);
|
||||
raw_local_irq_restore(flags);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static struct file_operations stack_max_size_fops = {
|
||||
.open = tracing_open_generic,
|
||||
.read = stack_max_size_read,
|
||||
.write = stack_max_size_write,
|
||||
};
|
||||
|
||||
static void *
|
||||
t_next(struct seq_file *m, void *v, loff_t *pos)
|
||||
{
|
||||
long i = (long)m->private;
|
||||
|
||||
(*pos)++;
|
||||
|
||||
i++;
|
||||
|
||||
if (i >= max_stack_trace.nr_entries ||
|
||||
stack_dump_trace[i] == ULONG_MAX)
|
||||
return NULL;
|
||||
|
||||
m->private = (void *)i;
|
||||
|
||||
return &m->private;
|
||||
}
|
||||
|
||||
static void *t_start(struct seq_file *m, loff_t *pos)
|
||||
{
|
||||
void *t = &m->private;
|
||||
loff_t l = 0;
|
||||
|
||||
local_irq_disable();
|
||||
__raw_spin_lock(&max_stack_lock);
|
||||
|
||||
for (; t && l < *pos; t = t_next(m, t, &l))
|
||||
;
|
||||
|
||||
return t;
|
||||
}
|
||||
|
||||
static void t_stop(struct seq_file *m, void *p)
|
||||
{
|
||||
__raw_spin_unlock(&max_stack_lock);
|
||||
local_irq_enable();
|
||||
}
|
||||
|
||||
static int trace_lookup_stack(struct seq_file *m, long i)
|
||||
{
|
||||
unsigned long addr = stack_dump_trace[i];
|
||||
#ifdef CONFIG_KALLSYMS
|
||||
char str[KSYM_SYMBOL_LEN];
|
||||
|
||||
sprint_symbol(str, addr);
|
||||
|
||||
return seq_printf(m, "%s\n", str);
|
||||
#else
|
||||
return seq_printf(m, "%p\n", (void*)addr);
|
||||
#endif
|
||||
}
|
||||
|
||||
static int t_show(struct seq_file *m, void *v)
|
||||
{
|
||||
long i = *(long *)v;
|
||||
int size;
|
||||
|
||||
if (i < 0) {
|
||||
seq_printf(m, " Depth Size Location"
|
||||
" (%d entries)\n"
|
||||
" ----- ---- --------\n",
|
||||
max_stack_trace.nr_entries);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (i >= max_stack_trace.nr_entries ||
|
||||
stack_dump_trace[i] == ULONG_MAX)
|
||||
return 0;
|
||||
|
||||
if (i+1 == max_stack_trace.nr_entries ||
|
||||
stack_dump_trace[i+1] == ULONG_MAX)
|
||||
size = stack_dump_index[i];
|
||||
else
|
||||
size = stack_dump_index[i] - stack_dump_index[i+1];
|
||||
|
||||
seq_printf(m, "%3ld) %8d %5d ", i, stack_dump_index[i], size);
|
||||
|
||||
trace_lookup_stack(m, i);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct seq_operations stack_trace_seq_ops = {
|
||||
.start = t_start,
|
||||
.next = t_next,
|
||||
.stop = t_stop,
|
||||
.show = t_show,
|
||||
};
|
||||
|
||||
static int stack_trace_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = seq_open(file, &stack_trace_seq_ops);
|
||||
if (!ret) {
|
||||
struct seq_file *m = file->private_data;
|
||||
m->private = (void *)-1;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static struct file_operations stack_trace_fops = {
|
||||
.open = stack_trace_open,
|
||||
.read = seq_read,
|
||||
.llseek = seq_lseek,
|
||||
};
|
||||
|
||||
static __init int stack_trace_init(void)
|
||||
{
|
||||
struct dentry *d_tracer;
|
||||
struct dentry *entry;
|
||||
|
||||
d_tracer = tracing_init_dentry();
|
||||
|
||||
entry = debugfs_create_file("stack_max_size", 0644, d_tracer,
|
||||
&max_stack_size, &stack_max_size_fops);
|
||||
if (!entry)
|
||||
pr_warning("Could not create debugfs 'stack_max_size' entry\n");
|
||||
|
||||
entry = debugfs_create_file("stack_trace", 0444, d_tracer,
|
||||
NULL, &stack_trace_fops);
|
||||
if (!entry)
|
||||
pr_warning("Could not create debugfs 'stack_trace' entry\n");
|
||||
|
||||
register_ftrace_function(&trace_ops);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
device_initcall(stack_trace_init);
|
@@ -241,7 +241,7 @@ static void stack_reset(struct trace_array *tr)
|
||||
tr->time_start = ftrace_now(tr->cpu);
|
||||
|
||||
for_each_online_cpu(cpu)
|
||||
tracing_reset(tr->data[cpu]);
|
||||
tracing_reset(tr, cpu);
|
||||
}
|
||||
|
||||
static void start_stack_trace(struct trace_array *tr)
|
||||
|
477
kernel/tracepoint.c
Normal file
477
kernel/tracepoint.c
Normal file
@@ -0,0 +1,477 @@
|
||||
/*
|
||||
* Copyright (C) 2008 Mathieu Desnoyers
|
||||
*
|
||||
* 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 <linux/module.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/jhash.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/rcupdate.h>
|
||||
#include <linux/tracepoint.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
extern struct tracepoint __start___tracepoints[];
|
||||
extern struct tracepoint __stop___tracepoints[];
|
||||
|
||||
/* Set to 1 to enable tracepoint debug output */
|
||||
static const int tracepoint_debug;
|
||||
|
||||
/*
|
||||
* tracepoints_mutex nests inside module_mutex. Tracepoints mutex protects the
|
||||
* builtin and module tracepoints and the hash table.
|
||||
*/
|
||||
static DEFINE_MUTEX(tracepoints_mutex);
|
||||
|
||||
/*
|
||||
* Tracepoint hash table, containing the active tracepoints.
|
||||
* Protected by tracepoints_mutex.
|
||||
*/
|
||||
#define TRACEPOINT_HASH_BITS 6
|
||||
#define TRACEPOINT_TABLE_SIZE (1 << TRACEPOINT_HASH_BITS)
|
||||
|
||||
/*
|
||||
* Note about RCU :
|
||||
* It is used to to delay the free of multiple probes array until a quiescent
|
||||
* state is reached.
|
||||
* Tracepoint entries modifications are protected by the tracepoints_mutex.
|
||||
*/
|
||||
struct tracepoint_entry {
|
||||
struct hlist_node hlist;
|
||||
void **funcs;
|
||||
int refcount; /* Number of times armed. 0 if disarmed. */
|
||||
struct rcu_head rcu;
|
||||
void *oldptr;
|
||||
unsigned char rcu_pending:1;
|
||||
char name[0];
|
||||
};
|
||||
|
||||
static struct hlist_head tracepoint_table[TRACEPOINT_TABLE_SIZE];
|
||||
|
||||
static void free_old_closure(struct rcu_head *head)
|
||||
{
|
||||
struct tracepoint_entry *entry = container_of(head,
|
||||
struct tracepoint_entry, rcu);
|
||||
kfree(entry->oldptr);
|
||||
/* Make sure we free the data before setting the pending flag to 0 */
|
||||
smp_wmb();
|
||||
entry->rcu_pending = 0;
|
||||
}
|
||||
|
||||
static void tracepoint_entry_free_old(struct tracepoint_entry *entry, void *old)
|
||||
{
|
||||
if (!old)
|
||||
return;
|
||||
entry->oldptr = old;
|
||||
entry->rcu_pending = 1;
|
||||
/* write rcu_pending before calling the RCU callback */
|
||||
smp_wmb();
|
||||
call_rcu_sched(&entry->rcu, free_old_closure);
|
||||
}
|
||||
|
||||
static void debug_print_probes(struct tracepoint_entry *entry)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (!tracepoint_debug)
|
||||
return;
|
||||
|
||||
for (i = 0; entry->funcs[i]; i++)
|
||||
printk(KERN_DEBUG "Probe %d : %p\n", i, entry->funcs[i]);
|
||||
}
|
||||
|
||||
static void *
|
||||
tracepoint_entry_add_probe(struct tracepoint_entry *entry, void *probe)
|
||||
{
|
||||
int nr_probes = 0;
|
||||
void **old, **new;
|
||||
|
||||
WARN_ON(!probe);
|
||||
|
||||
debug_print_probes(entry);
|
||||
old = entry->funcs;
|
||||
if (old) {
|
||||
/* (N -> N+1), (N != 0, 1) probes */
|
||||
for (nr_probes = 0; old[nr_probes]; nr_probes++)
|
||||
if (old[nr_probes] == probe)
|
||||
return ERR_PTR(-EEXIST);
|
||||
}
|
||||
/* + 2 : one for new probe, one for NULL func */
|
||||
new = kzalloc((nr_probes + 2) * sizeof(void *), GFP_KERNEL);
|
||||
if (new == NULL)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
if (old)
|
||||
memcpy(new, old, nr_probes * sizeof(void *));
|
||||
new[nr_probes] = probe;
|
||||
entry->refcount = nr_probes + 1;
|
||||
entry->funcs = new;
|
||||
debug_print_probes(entry);
|
||||
return old;
|
||||
}
|
||||
|
||||
static void *
|
||||
tracepoint_entry_remove_probe(struct tracepoint_entry *entry, void *probe)
|
||||
{
|
||||
int nr_probes = 0, nr_del = 0, i;
|
||||
void **old, **new;
|
||||
|
||||
old = entry->funcs;
|
||||
|
||||
debug_print_probes(entry);
|
||||
/* (N -> M), (N > 1, M >= 0) probes */
|
||||
for (nr_probes = 0; old[nr_probes]; nr_probes++) {
|
||||
if ((!probe || old[nr_probes] == probe))
|
||||
nr_del++;
|
||||
}
|
||||
|
||||
if (nr_probes - nr_del == 0) {
|
||||
/* N -> 0, (N > 1) */
|
||||
entry->funcs = NULL;
|
||||
entry->refcount = 0;
|
||||
debug_print_probes(entry);
|
||||
return old;
|
||||
} else {
|
||||
int j = 0;
|
||||
/* N -> M, (N > 1, M > 0) */
|
||||
/* + 1 for NULL */
|
||||
new = kzalloc((nr_probes - nr_del + 1)
|
||||
* sizeof(void *), GFP_KERNEL);
|
||||
if (new == NULL)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
for (i = 0; old[i]; i++)
|
||||
if ((probe && old[i] != probe))
|
||||
new[j++] = old[i];
|
||||
entry->refcount = nr_probes - nr_del;
|
||||
entry->funcs = new;
|
||||
}
|
||||
debug_print_probes(entry);
|
||||
return old;
|
||||
}
|
||||
|
||||
/*
|
||||
* Get tracepoint if the tracepoint is present in the tracepoint hash table.
|
||||
* Must be called with tracepoints_mutex held.
|
||||
* Returns NULL if not present.
|
||||
*/
|
||||
static struct tracepoint_entry *get_tracepoint(const char *name)
|
||||
{
|
||||
struct hlist_head *head;
|
||||
struct hlist_node *node;
|
||||
struct tracepoint_entry *e;
|
||||
u32 hash = jhash(name, strlen(name), 0);
|
||||
|
||||
head = &tracepoint_table[hash & (TRACEPOINT_TABLE_SIZE - 1)];
|
||||
hlist_for_each_entry(e, node, head, hlist) {
|
||||
if (!strcmp(name, e->name))
|
||||
return e;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Add the tracepoint to the tracepoint hash table. Must be called with
|
||||
* tracepoints_mutex held.
|
||||
*/
|
||||
static struct tracepoint_entry *add_tracepoint(const char *name)
|
||||
{
|
||||
struct hlist_head *head;
|
||||
struct hlist_node *node;
|
||||
struct tracepoint_entry *e;
|
||||
size_t name_len = strlen(name) + 1;
|
||||
u32 hash = jhash(name, name_len-1, 0);
|
||||
|
||||
head = &tracepoint_table[hash & (TRACEPOINT_TABLE_SIZE - 1)];
|
||||
hlist_for_each_entry(e, node, head, hlist) {
|
||||
if (!strcmp(name, e->name)) {
|
||||
printk(KERN_NOTICE
|
||||
"tracepoint %s busy\n", name);
|
||||
return ERR_PTR(-EEXIST); /* Already there */
|
||||
}
|
||||
}
|
||||
/*
|
||||
* Using kmalloc here to allocate a variable length element. Could
|
||||
* cause some memory fragmentation if overused.
|
||||
*/
|
||||
e = kmalloc(sizeof(struct tracepoint_entry) + name_len, GFP_KERNEL);
|
||||
if (!e)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
memcpy(&e->name[0], name, name_len);
|
||||
e->funcs = NULL;
|
||||
e->refcount = 0;
|
||||
e->rcu_pending = 0;
|
||||
hlist_add_head(&e->hlist, head);
|
||||
return e;
|
||||
}
|
||||
|
||||
/*
|
||||
* Remove the tracepoint from the tracepoint hash table. Must be called with
|
||||
* mutex_lock held.
|
||||
*/
|
||||
static int remove_tracepoint(const char *name)
|
||||
{
|
||||
struct hlist_head *head;
|
||||
struct hlist_node *node;
|
||||
struct tracepoint_entry *e;
|
||||
int found = 0;
|
||||
size_t len = strlen(name) + 1;
|
||||
u32 hash = jhash(name, len-1, 0);
|
||||
|
||||
head = &tracepoint_table[hash & (TRACEPOINT_TABLE_SIZE - 1)];
|
||||
hlist_for_each_entry(e, node, head, hlist) {
|
||||
if (!strcmp(name, e->name)) {
|
||||
found = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found)
|
||||
return -ENOENT;
|
||||
if (e->refcount)
|
||||
return -EBUSY;
|
||||
hlist_del(&e->hlist);
|
||||
/* Make sure the call_rcu_sched has been executed */
|
||||
if (e->rcu_pending)
|
||||
rcu_barrier_sched();
|
||||
kfree(e);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Sets the probe callback corresponding to one tracepoint.
|
||||
*/
|
||||
static void set_tracepoint(struct tracepoint_entry **entry,
|
||||
struct tracepoint *elem, int active)
|
||||
{
|
||||
WARN_ON(strcmp((*entry)->name, elem->name) != 0);
|
||||
|
||||
/*
|
||||
* rcu_assign_pointer has a smp_wmb() which makes sure that the new
|
||||
* probe callbacks array is consistent before setting a pointer to it.
|
||||
* This array is referenced by __DO_TRACE from
|
||||
* include/linux/tracepoints.h. A matching smp_read_barrier_depends()
|
||||
* is used.
|
||||
*/
|
||||
rcu_assign_pointer(elem->funcs, (*entry)->funcs);
|
||||
elem->state = active;
|
||||
}
|
||||
|
||||
/*
|
||||
* Disable a tracepoint and its probe callback.
|
||||
* Note: only waiting an RCU period after setting elem->call to the empty
|
||||
* function insures that the original callback is not used anymore. This insured
|
||||
* by preempt_disable around the call site.
|
||||
*/
|
||||
static void disable_tracepoint(struct tracepoint *elem)
|
||||
{
|
||||
elem->state = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* tracepoint_update_probe_range - Update a probe range
|
||||
* @begin: beginning of the range
|
||||
* @end: end of the range
|
||||
*
|
||||
* Updates the probe callback corresponding to a range of tracepoints.
|
||||
*/
|
||||
void tracepoint_update_probe_range(struct tracepoint *begin,
|
||||
struct tracepoint *end)
|
||||
{
|
||||
struct tracepoint *iter;
|
||||
struct tracepoint_entry *mark_entry;
|
||||
|
||||
mutex_lock(&tracepoints_mutex);
|
||||
for (iter = begin; iter < end; iter++) {
|
||||
mark_entry = get_tracepoint(iter->name);
|
||||
if (mark_entry) {
|
||||
set_tracepoint(&mark_entry, iter,
|
||||
!!mark_entry->refcount);
|
||||
} else {
|
||||
disable_tracepoint(iter);
|
||||
}
|
||||
}
|
||||
mutex_unlock(&tracepoints_mutex);
|
||||
}
|
||||
|
||||
/*
|
||||
* Update probes, removing the faulty probes.
|
||||
*/
|
||||
static void tracepoint_update_probes(void)
|
||||
{
|
||||
/* Core kernel tracepoints */
|
||||
tracepoint_update_probe_range(__start___tracepoints,
|
||||
__stop___tracepoints);
|
||||
/* tracepoints in modules. */
|
||||
module_update_tracepoints();
|
||||
}
|
||||
|
||||
/**
|
||||
* tracepoint_probe_register - Connect a probe to a tracepoint
|
||||
* @name: tracepoint name
|
||||
* @probe: probe handler
|
||||
*
|
||||
* Returns 0 if ok, error value on error.
|
||||
* The probe address must at least be aligned on the architecture pointer size.
|
||||
*/
|
||||
int tracepoint_probe_register(const char *name, void *probe)
|
||||
{
|
||||
struct tracepoint_entry *entry;
|
||||
int ret = 0;
|
||||
void *old;
|
||||
|
||||
mutex_lock(&tracepoints_mutex);
|
||||
entry = get_tracepoint(name);
|
||||
if (!entry) {
|
||||
entry = add_tracepoint(name);
|
||||
if (IS_ERR(entry)) {
|
||||
ret = PTR_ERR(entry);
|
||||
goto end;
|
||||
}
|
||||
}
|
||||
/*
|
||||
* If we detect that a call_rcu_sched is pending for this tracepoint,
|
||||
* make sure it's executed now.
|
||||
*/
|
||||
if (entry->rcu_pending)
|
||||
rcu_barrier_sched();
|
||||
old = tracepoint_entry_add_probe(entry, probe);
|
||||
if (IS_ERR(old)) {
|
||||
ret = PTR_ERR(old);
|
||||
goto end;
|
||||
}
|
||||
mutex_unlock(&tracepoints_mutex);
|
||||
tracepoint_update_probes(); /* may update entry */
|
||||
mutex_lock(&tracepoints_mutex);
|
||||
entry = get_tracepoint(name);
|
||||
WARN_ON(!entry);
|
||||
if (entry->rcu_pending)
|
||||
rcu_barrier_sched();
|
||||
tracepoint_entry_free_old(entry, old);
|
||||
end:
|
||||
mutex_unlock(&tracepoints_mutex);
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(tracepoint_probe_register);
|
||||
|
||||
/**
|
||||
* tracepoint_probe_unregister - Disconnect a probe from a tracepoint
|
||||
* @name: tracepoint name
|
||||
* @probe: probe function pointer
|
||||
*
|
||||
* We do not need to call a synchronize_sched to make sure the probes have
|
||||
* finished running before doing a module unload, because the module unload
|
||||
* itself uses stop_machine(), which insures that every preempt disabled section
|
||||
* have finished.
|
||||
*/
|
||||
int tracepoint_probe_unregister(const char *name, void *probe)
|
||||
{
|
||||
struct tracepoint_entry *entry;
|
||||
void *old;
|
||||
int ret = -ENOENT;
|
||||
|
||||
mutex_lock(&tracepoints_mutex);
|
||||
entry = get_tracepoint(name);
|
||||
if (!entry)
|
||||
goto end;
|
||||
if (entry->rcu_pending)
|
||||
rcu_barrier_sched();
|
||||
old = tracepoint_entry_remove_probe(entry, probe);
|
||||
mutex_unlock(&tracepoints_mutex);
|
||||
tracepoint_update_probes(); /* may update entry */
|
||||
mutex_lock(&tracepoints_mutex);
|
||||
entry = get_tracepoint(name);
|
||||
if (!entry)
|
||||
goto end;
|
||||
if (entry->rcu_pending)
|
||||
rcu_barrier_sched();
|
||||
tracepoint_entry_free_old(entry, old);
|
||||
remove_tracepoint(name); /* Ignore busy error message */
|
||||
ret = 0;
|
||||
end:
|
||||
mutex_unlock(&tracepoints_mutex);
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(tracepoint_probe_unregister);
|
||||
|
||||
/**
|
||||
* tracepoint_get_iter_range - Get a next tracepoint iterator given a range.
|
||||
* @tracepoint: current tracepoints (in), next tracepoint (out)
|
||||
* @begin: beginning of the range
|
||||
* @end: end of the range
|
||||
*
|
||||
* Returns whether a next tracepoint has been found (1) or not (0).
|
||||
* Will return the first tracepoint in the range if the input tracepoint is
|
||||
* NULL.
|
||||
*/
|
||||
int tracepoint_get_iter_range(struct tracepoint **tracepoint,
|
||||
struct tracepoint *begin, struct tracepoint *end)
|
||||
{
|
||||
if (!*tracepoint && begin != end) {
|
||||
*tracepoint = begin;
|
||||
return 1;
|
||||
}
|
||||
if (*tracepoint >= begin && *tracepoint < end)
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(tracepoint_get_iter_range);
|
||||
|
||||
static void tracepoint_get_iter(struct tracepoint_iter *iter)
|
||||
{
|
||||
int found = 0;
|
||||
|
||||
/* Core kernel tracepoints */
|
||||
if (!iter->module) {
|
||||
found = tracepoint_get_iter_range(&iter->tracepoint,
|
||||
__start___tracepoints, __stop___tracepoints);
|
||||
if (found)
|
||||
goto end;
|
||||
}
|
||||
/* tracepoints in modules. */
|
||||
found = module_get_iter_tracepoints(iter);
|
||||
end:
|
||||
if (!found)
|
||||
tracepoint_iter_reset(iter);
|
||||
}
|
||||
|
||||
void tracepoint_iter_start(struct tracepoint_iter *iter)
|
||||
{
|
||||
tracepoint_get_iter(iter);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(tracepoint_iter_start);
|
||||
|
||||
void tracepoint_iter_next(struct tracepoint_iter *iter)
|
||||
{
|
||||
iter->tracepoint++;
|
||||
/*
|
||||
* iter->tracepoint may be invalid because we blindly incremented it.
|
||||
* Make sure it is valid by marshalling on the tracepoints, getting the
|
||||
* tracepoints from following modules if necessary.
|
||||
*/
|
||||
tracepoint_get_iter(iter);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(tracepoint_iter_next);
|
||||
|
||||
void tracepoint_iter_stop(struct tracepoint_iter *iter)
|
||||
{
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(tracepoint_iter_stop);
|
||||
|
||||
void tracepoint_iter_reset(struct tracepoint_iter *iter)
|
||||
{
|
||||
iter->module = NULL;
|
||||
iter->tracepoint = NULL;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(tracepoint_iter_reset);
|
Reference in New Issue
Block a user