5a0e3ad6af
percpu.h is included by sched.h and module.h and thus ends up being included when building most .c files. percpu.h includes slab.h which in turn includes gfp.h making everything defined by the two files universally available and complicating inclusion dependencies. percpu.h -> slab.h dependency is about to be removed. Prepare for this change by updating users of gfp and slab facilities include those headers directly instead of assuming availability. As this conversion needs to touch large number of source files, the following script is used as the basis of conversion. http://userweb.kernel.org/~tj/misc/slabh-sweep.py The script does the followings. * Scan files for gfp and slab usages and update includes such that only the necessary includes are there. ie. if only gfp is used, gfp.h, if slab is used, slab.h. * When the script inserts a new include, it looks at the include blocks and try to put the new include such that its order conforms to its surrounding. It's put in the include block which contains core kernel includes, in the same order that the rest are ordered - alphabetical, Christmas tree, rev-Xmas-tree or at the end if there doesn't seem to be any matching order. * If the script can't find a place to put a new include (mostly because the file doesn't have fitting include block), it prints out an error message indicating which .h file needs to be added to the file. The conversion was done in the following steps. 1. The initial automatic conversion of all .c files updated slightly over 4000 files, deleting around 700 includes and adding ~480 gfp.h and ~3000 slab.h inclusions. The script emitted errors for ~400 files. 2. Each error was manually checked. Some didn't need the inclusion, some needed manual addition while adding it to implementation .h or embedding .c file was more appropriate for others. This step added inclusions to around 150 files. 3. The script was run again and the output was compared to the edits from #2 to make sure no file was left behind. 4. Several build tests were done and a couple of problems were fixed. e.g. lib/decompress_*.c used malloc/free() wrappers around slab APIs requiring slab.h to be added manually. 5. The script was run on all .h files but without automatically editing them as sprinkling gfp.h and slab.h inclusions around .h files could easily lead to inclusion dependency hell. Most gfp.h inclusion directives were ignored as stuff from gfp.h was usually wildly available and often used in preprocessor macros. Each slab.h inclusion directive was examined and added manually as necessary. 6. percpu.h was updated not to include slab.h. 7. Build test were done on the following configurations and failures were fixed. CONFIG_GCOV_KERNEL was turned off for all tests (as my distributed build env didn't work with gcov compiles) and a few more options had to be turned off depending on archs to make things build (like ipr on powerpc/64 which failed due to missing writeq). * x86 and x86_64 UP and SMP allmodconfig and a custom test config. * powerpc and powerpc64 SMP allmodconfig * sparc and sparc64 SMP allmodconfig * ia64 SMP allmodconfig * s390 SMP allmodconfig * alpha SMP allmodconfig * um on x86_64 SMP allmodconfig 8. percpu.h modifications were reverted so that it could be applied as a separate patch and serve as bisection point. Given the fact that I had only a couple of failures from tests on step 6, I'm fairly confident about the coverage of this conversion patch. If there is a breakage, it's likely to be something in one of the arch headers which should be easily discoverable easily on most builds of the specific arch. Signed-off-by: Tejun Heo <tj@kernel.org> Guess-its-ok-by: Christoph Lameter <cl@linux-foundation.org> Cc: Ingo Molnar <mingo@redhat.com> Cc: Lee Schermerhorn <Lee.Schermerhorn@hp.com>
520 lines
12 KiB
C
520 lines
12 KiB
C
/*
|
|
* trace_ksym.c - Kernel Symbol Tracer
|
|
*
|
|
* 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.
|
|
*
|
|
* Copyright (C) IBM Corporation, 2009
|
|
*/
|
|
|
|
#include <linux/kallsyms.h>
|
|
#include <linux/uaccess.h>
|
|
#include <linux/debugfs.h>
|
|
#include <linux/ftrace.h>
|
|
#include <linux/module.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/fs.h>
|
|
|
|
#include "trace_output.h"
|
|
#include "trace.h"
|
|
|
|
#include <linux/hw_breakpoint.h>
|
|
#include <asm/hw_breakpoint.h>
|
|
|
|
#include <asm/atomic.h>
|
|
|
|
/*
|
|
* For now, let us restrict the no. of symbols traced simultaneously to number
|
|
* of available hardware breakpoint registers.
|
|
*/
|
|
#define KSYM_TRACER_MAX HBP_NUM
|
|
|
|
#define KSYM_TRACER_OP_LEN 3 /* rw- */
|
|
|
|
struct trace_ksym {
|
|
struct perf_event **ksym_hbp;
|
|
struct perf_event_attr attr;
|
|
#ifdef CONFIG_PROFILE_KSYM_TRACER
|
|
atomic64_t counter;
|
|
#endif
|
|
struct hlist_node ksym_hlist;
|
|
};
|
|
|
|
static struct trace_array *ksym_trace_array;
|
|
|
|
static unsigned int ksym_filter_entry_count;
|
|
static unsigned int ksym_tracing_enabled;
|
|
|
|
static HLIST_HEAD(ksym_filter_head);
|
|
|
|
static DEFINE_MUTEX(ksym_tracer_mutex);
|
|
|
|
#ifdef CONFIG_PROFILE_KSYM_TRACER
|
|
|
|
#define MAX_UL_INT 0xffffffff
|
|
|
|
void ksym_collect_stats(unsigned long hbp_hit_addr)
|
|
{
|
|
struct hlist_node *node;
|
|
struct trace_ksym *entry;
|
|
|
|
rcu_read_lock();
|
|
hlist_for_each_entry_rcu(entry, node, &ksym_filter_head, ksym_hlist) {
|
|
if (entry->attr.bp_addr == hbp_hit_addr) {
|
|
atomic64_inc(&entry->counter);
|
|
break;
|
|
}
|
|
}
|
|
rcu_read_unlock();
|
|
}
|
|
#endif /* CONFIG_PROFILE_KSYM_TRACER */
|
|
|
|
void ksym_hbp_handler(struct perf_event *hbp, int nmi,
|
|
struct perf_sample_data *data,
|
|
struct pt_regs *regs)
|
|
{
|
|
struct ring_buffer_event *event;
|
|
struct ksym_trace_entry *entry;
|
|
struct ring_buffer *buffer;
|
|
int pc;
|
|
|
|
if (!ksym_tracing_enabled)
|
|
return;
|
|
|
|
buffer = ksym_trace_array->buffer;
|
|
|
|
pc = preempt_count();
|
|
|
|
event = trace_buffer_lock_reserve(buffer, TRACE_KSYM,
|
|
sizeof(*entry), 0, pc);
|
|
if (!event)
|
|
return;
|
|
|
|
entry = ring_buffer_event_data(event);
|
|
entry->ip = instruction_pointer(regs);
|
|
entry->type = hw_breakpoint_type(hbp);
|
|
entry->addr = hw_breakpoint_addr(hbp);
|
|
strlcpy(entry->cmd, current->comm, TASK_COMM_LEN);
|
|
|
|
#ifdef CONFIG_PROFILE_KSYM_TRACER
|
|
ksym_collect_stats(hw_breakpoint_addr(hbp));
|
|
#endif /* CONFIG_PROFILE_KSYM_TRACER */
|
|
|
|
trace_buffer_unlock_commit(buffer, event, 0, pc);
|
|
}
|
|
|
|
/* Valid access types are represented as
|
|
*
|
|
* rw- : Set Read/Write Access Breakpoint
|
|
* -w- : Set Write Access Breakpoint
|
|
* --- : Clear Breakpoints
|
|
* --x : Set Execution Break points (Not available yet)
|
|
*
|
|
*/
|
|
static int ksym_trace_get_access_type(char *str)
|
|
{
|
|
int access = 0;
|
|
|
|
if (str[0] == 'r')
|
|
access |= HW_BREAKPOINT_R;
|
|
|
|
if (str[1] == 'w')
|
|
access |= HW_BREAKPOINT_W;
|
|
|
|
if (str[2] == 'x')
|
|
access |= HW_BREAKPOINT_X;
|
|
|
|
switch (access) {
|
|
case HW_BREAKPOINT_R:
|
|
case HW_BREAKPOINT_W:
|
|
case HW_BREAKPOINT_W | HW_BREAKPOINT_R:
|
|
return access;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* There can be several possible malformed requests and we attempt to capture
|
|
* all of them. We enumerate some of the rules
|
|
* 1. We will not allow kernel symbols with ':' since it is used as a delimiter.
|
|
* i.e. multiple ':' symbols disallowed. Possible uses are of the form
|
|
* <module>:<ksym_name>:<op>.
|
|
* 2. No delimiter symbol ':' in the input string
|
|
* 3. Spurious operator symbols or symbols not in their respective positions
|
|
* 4. <ksym_name>:--- i.e. clear breakpoint request when ksym_name not in file
|
|
* 5. Kernel symbol not a part of /proc/kallsyms
|
|
* 6. Duplicate requests
|
|
*/
|
|
static int parse_ksym_trace_str(char *input_string, char **ksymname,
|
|
unsigned long *addr)
|
|
{
|
|
int ret;
|
|
|
|
*ksymname = strsep(&input_string, ":");
|
|
*addr = kallsyms_lookup_name(*ksymname);
|
|
|
|
/* Check for malformed request: (2), (1) and (5) */
|
|
if ((!input_string) ||
|
|
(strlen(input_string) != KSYM_TRACER_OP_LEN) ||
|
|
(*addr == 0))
|
|
return -EINVAL;;
|
|
|
|
ret = ksym_trace_get_access_type(input_string);
|
|
|
|
return ret;
|
|
}
|
|
|
|
int process_new_ksym_entry(char *ksymname, int op, unsigned long addr)
|
|
{
|
|
struct trace_ksym *entry;
|
|
int ret = -ENOMEM;
|
|
|
|
if (ksym_filter_entry_count >= KSYM_TRACER_MAX) {
|
|
printk(KERN_ERR "ksym_tracer: Maximum limit:(%d) reached. No"
|
|
" new requests for tracing can be accepted now.\n",
|
|
KSYM_TRACER_MAX);
|
|
return -ENOSPC;
|
|
}
|
|
|
|
entry = kzalloc(sizeof(struct trace_ksym), GFP_KERNEL);
|
|
if (!entry)
|
|
return -ENOMEM;
|
|
|
|
hw_breakpoint_init(&entry->attr);
|
|
|
|
entry->attr.bp_type = op;
|
|
entry->attr.bp_addr = addr;
|
|
entry->attr.bp_len = HW_BREAKPOINT_LEN_4;
|
|
|
|
entry->ksym_hbp = register_wide_hw_breakpoint(&entry->attr,
|
|
ksym_hbp_handler);
|
|
|
|
if (IS_ERR(entry->ksym_hbp)) {
|
|
ret = PTR_ERR(entry->ksym_hbp);
|
|
printk(KERN_INFO "ksym_tracer request failed. Try again"
|
|
" later!!\n");
|
|
goto err;
|
|
}
|
|
|
|
hlist_add_head_rcu(&(entry->ksym_hlist), &ksym_filter_head);
|
|
ksym_filter_entry_count++;
|
|
|
|
return 0;
|
|
|
|
err:
|
|
kfree(entry);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static ssize_t ksym_trace_filter_read(struct file *filp, char __user *ubuf,
|
|
size_t count, loff_t *ppos)
|
|
{
|
|
struct trace_ksym *entry;
|
|
struct hlist_node *node;
|
|
struct trace_seq *s;
|
|
ssize_t cnt = 0;
|
|
int ret;
|
|
|
|
s = kmalloc(sizeof(*s), GFP_KERNEL);
|
|
if (!s)
|
|
return -ENOMEM;
|
|
trace_seq_init(s);
|
|
|
|
mutex_lock(&ksym_tracer_mutex);
|
|
|
|
hlist_for_each_entry(entry, node, &ksym_filter_head, ksym_hlist) {
|
|
ret = trace_seq_printf(s, "%pS:",
|
|
(void *)(unsigned long)entry->attr.bp_addr);
|
|
if (entry->attr.bp_type == HW_BREAKPOINT_R)
|
|
ret = trace_seq_puts(s, "r--\n");
|
|
else if (entry->attr.bp_type == HW_BREAKPOINT_W)
|
|
ret = trace_seq_puts(s, "-w-\n");
|
|
else if (entry->attr.bp_type == (HW_BREAKPOINT_W | HW_BREAKPOINT_R))
|
|
ret = trace_seq_puts(s, "rw-\n");
|
|
WARN_ON_ONCE(!ret);
|
|
}
|
|
|
|
cnt = simple_read_from_buffer(ubuf, count, ppos, s->buffer, s->len);
|
|
|
|
mutex_unlock(&ksym_tracer_mutex);
|
|
|
|
kfree(s);
|
|
|
|
return cnt;
|
|
}
|
|
|
|
static void __ksym_trace_reset(void)
|
|
{
|
|
struct trace_ksym *entry;
|
|
struct hlist_node *node, *node1;
|
|
|
|
mutex_lock(&ksym_tracer_mutex);
|
|
hlist_for_each_entry_safe(entry, node, node1, &ksym_filter_head,
|
|
ksym_hlist) {
|
|
unregister_wide_hw_breakpoint(entry->ksym_hbp);
|
|
ksym_filter_entry_count--;
|
|
hlist_del_rcu(&(entry->ksym_hlist));
|
|
synchronize_rcu();
|
|
kfree(entry);
|
|
}
|
|
mutex_unlock(&ksym_tracer_mutex);
|
|
}
|
|
|
|
static ssize_t ksym_trace_filter_write(struct file *file,
|
|
const char __user *buffer,
|
|
size_t count, loff_t *ppos)
|
|
{
|
|
struct trace_ksym *entry;
|
|
struct hlist_node *node;
|
|
char *buf, *input_string, *ksymname = NULL;
|
|
unsigned long ksym_addr = 0;
|
|
int ret, op, changed = 0;
|
|
|
|
buf = kzalloc(count + 1, GFP_KERNEL);
|
|
if (!buf)
|
|
return -ENOMEM;
|
|
|
|
ret = -EFAULT;
|
|
if (copy_from_user(buf, buffer, count))
|
|
goto out;
|
|
|
|
buf[count] = '\0';
|
|
input_string = strstrip(buf);
|
|
|
|
/*
|
|
* Clear all breakpoints if:
|
|
* 1: echo > ksym_trace_filter
|
|
* 2: echo 0 > ksym_trace_filter
|
|
* 3: echo "*:---" > ksym_trace_filter
|
|
*/
|
|
if (!input_string[0] || !strcmp(input_string, "0") ||
|
|
!strcmp(input_string, "*:---")) {
|
|
__ksym_trace_reset();
|
|
ret = 0;
|
|
goto out;
|
|
}
|
|
|
|
ret = op = parse_ksym_trace_str(input_string, &ksymname, &ksym_addr);
|
|
if (ret < 0)
|
|
goto out;
|
|
|
|
mutex_lock(&ksym_tracer_mutex);
|
|
|
|
ret = -EINVAL;
|
|
hlist_for_each_entry(entry, node, &ksym_filter_head, ksym_hlist) {
|
|
if (entry->attr.bp_addr == ksym_addr) {
|
|
/* Check for malformed request: (6) */
|
|
if (entry->attr.bp_type != op)
|
|
changed = 1;
|
|
else
|
|
goto out_unlock;
|
|
break;
|
|
}
|
|
}
|
|
if (changed) {
|
|
unregister_wide_hw_breakpoint(entry->ksym_hbp);
|
|
entry->attr.bp_type = op;
|
|
ret = 0;
|
|
if (op > 0) {
|
|
entry->ksym_hbp =
|
|
register_wide_hw_breakpoint(&entry->attr,
|
|
ksym_hbp_handler);
|
|
if (IS_ERR(entry->ksym_hbp))
|
|
ret = PTR_ERR(entry->ksym_hbp);
|
|
else
|
|
goto out_unlock;
|
|
}
|
|
/* Error or "symbol:---" case: drop it */
|
|
ksym_filter_entry_count--;
|
|
hlist_del_rcu(&(entry->ksym_hlist));
|
|
synchronize_rcu();
|
|
kfree(entry);
|
|
goto out_unlock;
|
|
} else {
|
|
/* Check for malformed request: (4) */
|
|
if (op)
|
|
ret = process_new_ksym_entry(ksymname, op, ksym_addr);
|
|
}
|
|
out_unlock:
|
|
mutex_unlock(&ksym_tracer_mutex);
|
|
out:
|
|
kfree(buf);
|
|
return !ret ? count : ret;
|
|
}
|
|
|
|
static const struct file_operations ksym_tracing_fops = {
|
|
.open = tracing_open_generic,
|
|
.read = ksym_trace_filter_read,
|
|
.write = ksym_trace_filter_write,
|
|
};
|
|
|
|
static void ksym_trace_reset(struct trace_array *tr)
|
|
{
|
|
ksym_tracing_enabled = 0;
|
|
__ksym_trace_reset();
|
|
}
|
|
|
|
static int ksym_trace_init(struct trace_array *tr)
|
|
{
|
|
int cpu, ret = 0;
|
|
|
|
for_each_online_cpu(cpu)
|
|
tracing_reset(tr, cpu);
|
|
ksym_tracing_enabled = 1;
|
|
ksym_trace_array = tr;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void ksym_trace_print_header(struct seq_file *m)
|
|
{
|
|
seq_puts(m,
|
|
"# TASK-PID CPU# Symbol "
|
|
"Type Function\n");
|
|
seq_puts(m,
|
|
"# | | | "
|
|
" | |\n");
|
|
}
|
|
|
|
static enum print_line_t ksym_trace_output(struct trace_iterator *iter)
|
|
{
|
|
struct trace_entry *entry = iter->ent;
|
|
struct trace_seq *s = &iter->seq;
|
|
struct ksym_trace_entry *field;
|
|
char str[KSYM_SYMBOL_LEN];
|
|
int ret;
|
|
|
|
if (entry->type != TRACE_KSYM)
|
|
return TRACE_TYPE_UNHANDLED;
|
|
|
|
trace_assign_type(field, entry);
|
|
|
|
ret = trace_seq_printf(s, "%11s-%-5d [%03d] %pS", field->cmd,
|
|
entry->pid, iter->cpu, (char *)field->addr);
|
|
if (!ret)
|
|
return TRACE_TYPE_PARTIAL_LINE;
|
|
|
|
switch (field->type) {
|
|
case HW_BREAKPOINT_R:
|
|
ret = trace_seq_printf(s, " R ");
|
|
break;
|
|
case HW_BREAKPOINT_W:
|
|
ret = trace_seq_printf(s, " W ");
|
|
break;
|
|
case HW_BREAKPOINT_R | HW_BREAKPOINT_W:
|
|
ret = trace_seq_printf(s, " RW ");
|
|
break;
|
|
default:
|
|
return TRACE_TYPE_PARTIAL_LINE;
|
|
}
|
|
|
|
if (!ret)
|
|
return TRACE_TYPE_PARTIAL_LINE;
|
|
|
|
sprint_symbol(str, field->ip);
|
|
ret = trace_seq_printf(s, "%s\n", str);
|
|
if (!ret)
|
|
return TRACE_TYPE_PARTIAL_LINE;
|
|
|
|
return TRACE_TYPE_HANDLED;
|
|
}
|
|
|
|
struct tracer ksym_tracer __read_mostly =
|
|
{
|
|
.name = "ksym_tracer",
|
|
.init = ksym_trace_init,
|
|
.reset = ksym_trace_reset,
|
|
#ifdef CONFIG_FTRACE_SELFTEST
|
|
.selftest = trace_selftest_startup_ksym,
|
|
#endif
|
|
.print_header = ksym_trace_print_header,
|
|
.print_line = ksym_trace_output
|
|
};
|
|
|
|
#ifdef CONFIG_PROFILE_KSYM_TRACER
|
|
static int ksym_profile_show(struct seq_file *m, void *v)
|
|
{
|
|
struct hlist_node *node;
|
|
struct trace_ksym *entry;
|
|
int access_type = 0;
|
|
char fn_name[KSYM_NAME_LEN];
|
|
|
|
seq_puts(m, " Access Type ");
|
|
seq_puts(m, " Symbol Counter\n");
|
|
seq_puts(m, " ----------- ");
|
|
seq_puts(m, " ------ -------\n");
|
|
|
|
rcu_read_lock();
|
|
hlist_for_each_entry_rcu(entry, node, &ksym_filter_head, ksym_hlist) {
|
|
|
|
access_type = entry->attr.bp_type;
|
|
|
|
switch (access_type) {
|
|
case HW_BREAKPOINT_R:
|
|
seq_puts(m, " R ");
|
|
break;
|
|
case HW_BREAKPOINT_W:
|
|
seq_puts(m, " W ");
|
|
break;
|
|
case HW_BREAKPOINT_R | HW_BREAKPOINT_W:
|
|
seq_puts(m, " RW ");
|
|
break;
|
|
default:
|
|
seq_puts(m, " NA ");
|
|
}
|
|
|
|
if (lookup_symbol_name(entry->attr.bp_addr, fn_name) >= 0)
|
|
seq_printf(m, " %-36s", fn_name);
|
|
else
|
|
seq_printf(m, " %-36s", "<NA>");
|
|
seq_printf(m, " %15llu\n",
|
|
(unsigned long long)atomic64_read(&entry->counter));
|
|
}
|
|
rcu_read_unlock();
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ksym_profile_open(struct inode *node, struct file *file)
|
|
{
|
|
return single_open(file, ksym_profile_show, NULL);
|
|
}
|
|
|
|
static const struct file_operations ksym_profile_fops = {
|
|
.open = ksym_profile_open,
|
|
.read = seq_read,
|
|
.llseek = seq_lseek,
|
|
.release = single_release,
|
|
};
|
|
#endif /* CONFIG_PROFILE_KSYM_TRACER */
|
|
|
|
__init static int init_ksym_trace(void)
|
|
{
|
|
struct dentry *d_tracer;
|
|
|
|
d_tracer = tracing_init_dentry();
|
|
|
|
trace_create_file("ksym_trace_filter", 0644, d_tracer,
|
|
NULL, &ksym_tracing_fops);
|
|
|
|
#ifdef CONFIG_PROFILE_KSYM_TRACER
|
|
trace_create_file("ksym_profile", 0444, d_tracer,
|
|
NULL, &ksym_profile_fops);
|
|
#endif
|
|
|
|
return register_tracer(&ksym_tracer);
|
|
}
|
|
device_initcall(init_ksym_trace);
|