Commit 45979a95 authored by Linus Torvalds's avatar Linus Torvalds

Merge tag 'trace-v5.4' of git://git.kernel.org/pub/scm/linux/kernel/git/rostedt/linux-trace

Pull tracing updates from Steven Rostedt:

 - Addition of multiprobes to kprobe and uprobe events (allows for more
   than one probe attached to the same location)

 - Addition of adding immediates to probe parameters

 - Clean up of the recordmcount.c code. This brings us closer to merging
   recordmcount into objtool, and reuse code.

 - Other small clean ups

* tag 'trace-v5.4' of git://git.kernel.org/pub/scm/linux/kernel/git/rostedt/linux-trace: (33 commits)
  selftests/ftrace: Update kprobe event error testcase
  tracing/probe: Reject exactly same probe event
  tracing/probe: Fix to allow user to enable events on unloaded modules
  selftests/ftrace: Select an existing function in kprobe_eventname test
  tracing/kprobe: Fix NULL pointer access in trace_porbe_unlink()
  tracing: Make sure variable reference alias has correct var_ref_idx
  tracing: Be more clever when dumping hex in __print_hex()
  ftrace: Simplify ftrace hash lookup code in clear_func_from_hash()
  tracing: Add "gfp_t" support in synthetic_events
  tracing: Rename tracing_reset() to tracing_reset_cpu()
  tracing: Document the stack trace algorithm in the comments
  tracing/arm64: Have max stack tracer handle the case of return address after data
  recordmcount: Clarify what cleanup() does
  recordmcount: Remove redundant cleanup() calls
  recordmcount: Kernel style formatting
  recordmcount: Kernel style function signature formatting
  recordmcount: Rewrite error/success handling
  selftests/ftrace: Add syntax error test for multiprobe
  selftests/ftrace: Add syntax error test for immediates
  selftests/ftrace: Add a testcase for kprobe multiprobe event
  ...
parents 3207598a b78b94b8
...@@ -52,6 +52,7 @@ Synopsis of kprobe_events ...@@ -52,6 +52,7 @@ Synopsis of kprobe_events
$retval : Fetch return value.(\*2) $retval : Fetch return value.(\*2)
$comm : Fetch current task comm. $comm : Fetch current task comm.
+|-[u]OFFS(FETCHARG) : Fetch memory at FETCHARG +|- OFFS address.(\*3)(\*4) +|-[u]OFFS(FETCHARG) : Fetch memory at FETCHARG +|- OFFS address.(\*3)(\*4)
\IMM : Store an immediate value to the argument.
NAME=FETCHARG : Set NAME as the argument name of FETCHARG. NAME=FETCHARG : Set NAME as the argument name of FETCHARG.
FETCHARG:TYPE : Set TYPE as the type of FETCHARG. Currently, basic types FETCHARG:TYPE : Set TYPE as the type of FETCHARG. Currently, basic types
(u8/u16/u32/u64/s8/s16/s32/s64), hexadecimal types (u8/u16/u32/u64/s8/s16/s32/s64), hexadecimal types
......
...@@ -45,6 +45,7 @@ Synopsis of uprobe_tracer ...@@ -45,6 +45,7 @@ Synopsis of uprobe_tracer
$retval : Fetch return value.(\*1) $retval : Fetch return value.(\*1)
$comm : Fetch current task comm. $comm : Fetch current task comm.
+|-[u]OFFS(FETCHARG) : Fetch memory at FETCHARG +|- OFFS address.(\*2)(\*3) +|-[u]OFFS(FETCHARG) : Fetch memory at FETCHARG +|- OFFS address.(\*2)(\*3)
\IMM : Store an immediate value to the argument.
NAME=FETCHARG : Set NAME as the argument name of FETCHARG. NAME=FETCHARG : Set NAME as the argument name of FETCHARG.
FETCHARG:TYPE : Set TYPE as the type of FETCHARG. Currently, basic types FETCHARG:TYPE : Set TYPE as the type of FETCHARG. Currently, basic types
(u8/u16/u32/u64/s8/s16/s32/s64), hexadecimal types (u8/u16/u32/u64/s8/s16/s32/s64), hexadecimal types
......
...@@ -14,6 +14,19 @@ ...@@ -14,6 +14,19 @@
#define MCOUNT_ADDR ((unsigned long)_mcount) #define MCOUNT_ADDR ((unsigned long)_mcount)
#define MCOUNT_INSN_SIZE AARCH64_INSN_SIZE #define MCOUNT_INSN_SIZE AARCH64_INSN_SIZE
/*
* Currently, gcc tends to save the link register after the local variables
* on the stack. This causes the max stack tracer to report the function
* frame sizes for the wrong functions. By defining
* ARCH_FTRACE_SHIFT_STACK_TRACER, it will tell the stack tracer to expect
* to find the return address on the stack after the local variables have
* been set up.
*
* Note, this may change in the future, and we will need to deal with that
* if it were to happen.
*/
#define ARCH_FTRACE_SHIFT_STACK_TRACER 1
#ifndef __ASSEMBLY__ #ifndef __ASSEMBLY__
#include <linux/compat.h> #include <linux/compat.h>
......
...@@ -961,9 +961,16 @@ static struct kprobe *alloc_aggr_kprobe(struct kprobe *p) ...@@ -961,9 +961,16 @@ static struct kprobe *alloc_aggr_kprobe(struct kprobe *p)
#ifdef CONFIG_KPROBES_ON_FTRACE #ifdef CONFIG_KPROBES_ON_FTRACE
static struct ftrace_ops kprobe_ftrace_ops __read_mostly = { static struct ftrace_ops kprobe_ftrace_ops __read_mostly = {
.func = kprobe_ftrace_handler,
.flags = FTRACE_OPS_FL_SAVE_REGS,
};
static struct ftrace_ops kprobe_ipmodify_ops __read_mostly = {
.func = kprobe_ftrace_handler, .func = kprobe_ftrace_handler,
.flags = FTRACE_OPS_FL_SAVE_REGS | FTRACE_OPS_FL_IPMODIFY, .flags = FTRACE_OPS_FL_SAVE_REGS | FTRACE_OPS_FL_IPMODIFY,
}; };
static int kprobe_ipmodify_enabled;
static int kprobe_ftrace_enabled; static int kprobe_ftrace_enabled;
/* Must ensure p->addr is really on ftrace */ /* Must ensure p->addr is really on ftrace */
...@@ -976,58 +983,75 @@ static int prepare_kprobe(struct kprobe *p) ...@@ -976,58 +983,75 @@ static int prepare_kprobe(struct kprobe *p)
} }
/* Caller must lock kprobe_mutex */ /* Caller must lock kprobe_mutex */
static int arm_kprobe_ftrace(struct kprobe *p) static int __arm_kprobe_ftrace(struct kprobe *p, struct ftrace_ops *ops,
int *cnt)
{ {
int ret = 0; int ret = 0;
ret = ftrace_set_filter_ip(&kprobe_ftrace_ops, ret = ftrace_set_filter_ip(ops, (unsigned long)p->addr, 0, 0);
(unsigned long)p->addr, 0, 0);
if (ret) { if (ret) {
pr_debug("Failed to arm kprobe-ftrace at %pS (%d)\n", pr_debug("Failed to arm kprobe-ftrace at %pS (%d)\n",
p->addr, ret); p->addr, ret);
return ret; return ret;
} }
if (kprobe_ftrace_enabled == 0) { if (*cnt == 0) {
ret = register_ftrace_function(&kprobe_ftrace_ops); ret = register_ftrace_function(ops);
if (ret) { if (ret) {
pr_debug("Failed to init kprobe-ftrace (%d)\n", ret); pr_debug("Failed to init kprobe-ftrace (%d)\n", ret);
goto err_ftrace; goto err_ftrace;
} }
} }
kprobe_ftrace_enabled++; (*cnt)++;
return ret; return ret;
err_ftrace: err_ftrace:
/* /*
* Note: Since kprobe_ftrace_ops has IPMODIFY set, and ftrace requires a * At this point, sinec ops is not registered, we should be sefe from
* non-empty filter_hash for IPMODIFY ops, we're safe from an accidental * registering empty filter.
* empty filter_hash which would undesirably trace all functions.
*/ */
ftrace_set_filter_ip(&kprobe_ftrace_ops, (unsigned long)p->addr, 1, 0); ftrace_set_filter_ip(ops, (unsigned long)p->addr, 1, 0);
return ret; return ret;
} }
static int arm_kprobe_ftrace(struct kprobe *p)
{
bool ipmodify = (p->post_handler != NULL);
return __arm_kprobe_ftrace(p,
ipmodify ? &kprobe_ipmodify_ops : &kprobe_ftrace_ops,
ipmodify ? &kprobe_ipmodify_enabled : &kprobe_ftrace_enabled);
}
/* Caller must lock kprobe_mutex */ /* Caller must lock kprobe_mutex */
static int disarm_kprobe_ftrace(struct kprobe *p) static int __disarm_kprobe_ftrace(struct kprobe *p, struct ftrace_ops *ops,
int *cnt)
{ {
int ret = 0; int ret = 0;
if (kprobe_ftrace_enabled == 1) { if (*cnt == 1) {
ret = unregister_ftrace_function(&kprobe_ftrace_ops); ret = unregister_ftrace_function(ops);
if (WARN(ret < 0, "Failed to unregister kprobe-ftrace (%d)\n", ret)) if (WARN(ret < 0, "Failed to unregister kprobe-ftrace (%d)\n", ret))
return ret; return ret;
} }
kprobe_ftrace_enabled--; (*cnt)--;
ret = ftrace_set_filter_ip(&kprobe_ftrace_ops, ret = ftrace_set_filter_ip(ops, (unsigned long)p->addr, 1, 0);
(unsigned long)p->addr, 1, 0);
WARN_ONCE(ret < 0, "Failed to disarm kprobe-ftrace at %pS (%d)\n", WARN_ONCE(ret < 0, "Failed to disarm kprobe-ftrace at %pS (%d)\n",
p->addr, ret); p->addr, ret);
return ret; return ret;
} }
static int disarm_kprobe_ftrace(struct kprobe *p)
{
bool ipmodify = (p->post_handler != NULL);
return __disarm_kprobe_ftrace(p,
ipmodify ? &kprobe_ipmodify_ops : &kprobe_ftrace_ops,
ipmodify ? &kprobe_ipmodify_enabled : &kprobe_ftrace_enabled);
}
#else /* !CONFIG_KPROBES_ON_FTRACE */ #else /* !CONFIG_KPROBES_ON_FTRACE */
#define prepare_kprobe(p) arch_prepare_kprobe(p) #define prepare_kprobe(p) arch_prepare_kprobe(p)
#define arm_kprobe_ftrace(p) (-ENODEV) #define arm_kprobe_ftrace(p) (-ENODEV)
......
...@@ -6036,11 +6036,7 @@ clear_func_from_hash(struct ftrace_init_func *func, struct ftrace_hash *hash) ...@@ -6036,11 +6036,7 @@ clear_func_from_hash(struct ftrace_init_func *func, struct ftrace_hash *hash)
{ {
struct ftrace_func_entry *entry; struct ftrace_func_entry *entry;
if (ftrace_hash_empty(hash)) entry = ftrace_lookup_ip(hash, func->ip);
return;
entry = __ftrace_lookup_ip(hash, func->ip);
/* /*
* Do not allow this rec to match again. * Do not allow this rec to match again.
* Yeah, it may waste some memory, but will be removed * Yeah, it may waste some memory, but will be removed
......
...@@ -1854,7 +1854,7 @@ int __init register_tracer(struct tracer *type) ...@@ -1854,7 +1854,7 @@ int __init register_tracer(struct tracer *type)
return ret; return ret;
} }
void tracing_reset(struct trace_buffer *buf, int cpu) static void tracing_reset_cpu(struct trace_buffer *buf, int cpu)
{ {
struct ring_buffer *buffer = buf->buffer; struct ring_buffer *buffer = buf->buffer;
...@@ -4251,7 +4251,7 @@ static int tracing_open(struct inode *inode, struct file *file) ...@@ -4251,7 +4251,7 @@ static int tracing_open(struct inode *inode, struct file *file)
if (cpu == RING_BUFFER_ALL_CPUS) if (cpu == RING_BUFFER_ALL_CPUS)
tracing_reset_online_cpus(trace_buf); tracing_reset_online_cpus(trace_buf);
else else
tracing_reset(trace_buf, cpu); tracing_reset_cpu(trace_buf, cpu);
} }
if (file->f_mode & FMODE_READ) { if (file->f_mode & FMODE_READ) {
...@@ -4815,15 +4815,15 @@ static const char readme_msg[] = ...@@ -4815,15 +4815,15 @@ static const char readme_msg[] =
#endif #endif
#endif /* CONFIG_STACK_TRACER */ #endif /* CONFIG_STACK_TRACER */
#ifdef CONFIG_DYNAMIC_EVENTS #ifdef CONFIG_DYNAMIC_EVENTS
" dynamic_events\t\t- Add/remove/show the generic dynamic events\n" " dynamic_events\t\t- Create/append/remove/show the generic dynamic events\n"
"\t\t\t Write into this file to define/undefine new trace events.\n" "\t\t\t Write into this file to define/undefine new trace events.\n"
#endif #endif
#ifdef CONFIG_KPROBE_EVENTS #ifdef CONFIG_KPROBE_EVENTS
" kprobe_events\t\t- Add/remove/show the kernel dynamic events\n" " kprobe_events\t\t- Create/append/remove/show the kernel dynamic events\n"
"\t\t\t Write into this file to define/undefine new trace events.\n" "\t\t\t Write into this file to define/undefine new trace events.\n"
#endif #endif
#ifdef CONFIG_UPROBE_EVENTS #ifdef CONFIG_UPROBE_EVENTS
" uprobe_events\t\t- Add/remove/show the userspace dynamic events\n" " uprobe_events\t\t- Create/append/remove/show the userspace dynamic events\n"
"\t\t\t Write into this file to define/undefine new trace events.\n" "\t\t\t Write into this file to define/undefine new trace events.\n"
#endif #endif
#if defined(CONFIG_KPROBE_EVENTS) || defined(CONFIG_UPROBE_EVENTS) #if defined(CONFIG_KPROBE_EVENTS) || defined(CONFIG_UPROBE_EVENTS)
...@@ -4848,7 +4848,7 @@ static const char readme_msg[] = ...@@ -4848,7 +4848,7 @@ static const char readme_msg[] =
#else #else
"\t $stack<index>, $stack, $retval, $comm,\n" "\t $stack<index>, $stack, $retval, $comm,\n"
#endif #endif
"\t +|-[u]<offset>(<fetcharg>)\n" "\t +|-[u]<offset>(<fetcharg>), \\imm-value, \\\"imm-string\"\n"
"\t type: s8/16/32/64, u8/16/32/64, x8/16/32/64, string, symbol,\n" "\t type: s8/16/32/64, u8/16/32/64, x8/16/32/64, string, symbol,\n"
"\t b<bit-width>@<bit-offset>/<container-size>, ustring,\n" "\t b<bit-width>@<bit-offset>/<container-size>, ustring,\n"
"\t <type>\\[<array-size>\\]\n" "\t <type>\\[<array-size>\\]\n"
...@@ -6742,7 +6742,7 @@ tracing_snapshot_write(struct file *filp, const char __user *ubuf, size_t cnt, ...@@ -6742,7 +6742,7 @@ tracing_snapshot_write(struct file *filp, const char __user *ubuf, size_t cnt,
if (iter->cpu_file == RING_BUFFER_ALL_CPUS) if (iter->cpu_file == RING_BUFFER_ALL_CPUS)
tracing_reset_online_cpus(&tr->max_buffer); tracing_reset_online_cpus(&tr->max_buffer);
else else
tracing_reset(&tr->max_buffer, iter->cpu_file); tracing_reset_cpu(&tr->max_buffer, iter->cpu_file);
} }
break; break;
} }
......
...@@ -677,7 +677,6 @@ trace_buffer_iter(struct trace_iterator *iter, int cpu) ...@@ -677,7 +677,6 @@ trace_buffer_iter(struct trace_iterator *iter, int cpu)
int tracer_init(struct tracer *t, struct trace_array *tr); int tracer_init(struct tracer *t, struct trace_array *tr);
int tracing_is_enabled(void); int tracing_is_enabled(void);
void tracing_reset(struct trace_buffer *buf, int cpu);
void tracing_reset_online_cpus(struct trace_buffer *buf); void tracing_reset_online_cpus(struct trace_buffer *buf);
void tracing_reset_current(int cpu); void tracing_reset_current(int cpu);
void tracing_reset_all_online_cpus(void); void tracing_reset_all_online_cpus(void);
......
...@@ -47,6 +47,7 @@ int dyn_event_release(int argc, char **argv, struct dyn_event_operations *type) ...@@ -47,6 +47,7 @@ int dyn_event_release(int argc, char **argv, struct dyn_event_operations *type)
return -EINVAL; return -EINVAL;
event++; event++;
} }
argc--; argv++;
p = strchr(event, '/'); p = strchr(event, '/');
if (p) { if (p) {
...@@ -61,11 +62,14 @@ int dyn_event_release(int argc, char **argv, struct dyn_event_operations *type) ...@@ -61,11 +62,14 @@ int dyn_event_release(int argc, char **argv, struct dyn_event_operations *type)
for_each_dyn_event_safe(pos, n) { for_each_dyn_event_safe(pos, n) {
if (type && type != pos->ops) if (type && type != pos->ops)
continue; continue;
if (pos->ops->match(system, event, pos)) { if (!pos->ops->match(system, event,
argc, (const char **)argv, pos))
continue;
ret = pos->ops->free(pos); ret = pos->ops->free(pos);
if (ret)
break; break;
} }
}
mutex_unlock(&event_mutex); mutex_unlock(&event_mutex);
return ret; return ret;
......
...@@ -31,8 +31,9 @@ struct dyn_event; ...@@ -31,8 +31,9 @@ struct dyn_event;
* @is_busy: Check whether given event is busy so that it can not be deleted. * @is_busy: Check whether given event is busy so that it can not be deleted.
* Return true if it is busy, otherwides false. * Return true if it is busy, otherwides false.
* @free: Delete the given event. Return 0 if success, otherwides error. * @free: Delete the given event. Return 0 if success, otherwides error.
* @match: Check whether given event and system name match this event. * @match: Check whether given event and system name match this event. The argc
* Return true if it matches, otherwides false. * and argv is used for exact match. Return true if it matches, otherwides
* false.
* *
* Except for @create, these methods are called under holding event_mutex. * Except for @create, these methods are called under holding event_mutex.
*/ */
...@@ -43,7 +44,7 @@ struct dyn_event_operations { ...@@ -43,7 +44,7 @@ struct dyn_event_operations {
bool (*is_busy)(struct dyn_event *ev); bool (*is_busy)(struct dyn_event *ev);
int (*free)(struct dyn_event *ev); int (*free)(struct dyn_event *ev);
bool (*match)(const char *system, const char *event, bool (*match)(const char *system, const char *event,
struct dyn_event *ev); int argc, const char **argv, struct dyn_event *ev);
}; };
/* Register new dyn_event type -- must be called at first */ /* Register new dyn_event type -- must be called at first */
......
...@@ -13,6 +13,10 @@ ...@@ -13,6 +13,10 @@
#include <linux/rculist.h> #include <linux/rculist.h>
#include <linux/tracefs.h> #include <linux/tracefs.h>
/* for gfp flag names */
#include <linux/trace_events.h>
#include <trace/events/mmflags.h>
#include "tracing_map.h" #include "tracing_map.h"
#include "trace.h" #include "trace.h"
#include "trace_dynevent.h" #include "trace_dynevent.h"
...@@ -374,7 +378,7 @@ static int synth_event_show(struct seq_file *m, struct dyn_event *ev); ...@@ -374,7 +378,7 @@ static int synth_event_show(struct seq_file *m, struct dyn_event *ev);
static int synth_event_release(struct dyn_event *ev); static int synth_event_release(struct dyn_event *ev);
static bool synth_event_is_busy(struct dyn_event *ev); static bool synth_event_is_busy(struct dyn_event *ev);
static bool synth_event_match(const char *system, const char *event, static bool synth_event_match(const char *system, const char *event,
struct dyn_event *ev); int argc, const char **argv, struct dyn_event *ev);
static struct dyn_event_operations synth_event_ops = { static struct dyn_event_operations synth_event_ops = {
.create = synth_event_create, .create = synth_event_create,
...@@ -422,7 +426,7 @@ static bool synth_event_is_busy(struct dyn_event *ev) ...@@ -422,7 +426,7 @@ static bool synth_event_is_busy(struct dyn_event *ev)
} }
static bool synth_event_match(const char *system, const char *event, static bool synth_event_match(const char *system, const char *event,
struct dyn_event *ev) int argc, const char **argv, struct dyn_event *ev)
{ {
struct synth_event *sev = to_synth_event(ev); struct synth_event *sev = to_synth_event(ev);
...@@ -752,6 +756,8 @@ static int synth_field_size(char *type) ...@@ -752,6 +756,8 @@ static int synth_field_size(char *type)
size = sizeof(unsigned long); size = sizeof(unsigned long);
else if (strcmp(type, "pid_t") == 0) else if (strcmp(type, "pid_t") == 0)
size = sizeof(pid_t); size = sizeof(pid_t);
else if (strcmp(type, "gfp_t") == 0)
size = sizeof(gfp_t);
else if (synth_field_is_string(type)) else if (synth_field_is_string(type))
size = synth_field_string_size(type); size = synth_field_string_size(type);
...@@ -792,6 +798,8 @@ static const char *synth_field_fmt(char *type) ...@@ -792,6 +798,8 @@ static const char *synth_field_fmt(char *type)
fmt = "%lu"; fmt = "%lu";
else if (strcmp(type, "pid_t") == 0) else if (strcmp(type, "pid_t") == 0)
fmt = "%d"; fmt = "%d";
else if (strcmp(type, "gfp_t") == 0)
fmt = "%x";
else if (synth_field_is_string(type)) else if (synth_field_is_string(type))
fmt = "%s"; fmt = "%s";
...@@ -834,9 +842,20 @@ static enum print_line_t print_synth_event(struct trace_iterator *iter, ...@@ -834,9 +842,20 @@ static enum print_line_t print_synth_event(struct trace_iterator *iter,
i == se->n_fields - 1 ? "" : " "); i == se->n_fields - 1 ? "" : " ");
n_u64 += STR_VAR_LEN_MAX / sizeof(u64); n_u64 += STR_VAR_LEN_MAX / sizeof(u64);
} else { } else {
struct trace_print_flags __flags[] = {
__def_gfpflag_names, {-1, NULL} };
trace_seq_printf(s, print_fmt, se->fields[i]->name, trace_seq_printf(s, print_fmt, se->fields[i]->name,
entry->fields[n_u64], entry->fields[n_u64],
i == se->n_fields - 1 ? "" : " "); i == se->n_fields - 1 ? "" : " ");
if (strcmp(se->fields[i]->type, "gfp_t") == 0) {
trace_seq_puts(s, " (");
trace_print_flags_seq(s, "|",
entry->fields[n_u64],
__flags);
trace_seq_putc(s, ')');
}
n_u64++; n_u64++;
} }
} }
...@@ -2785,6 +2804,8 @@ static struct hist_field *create_alias(struct hist_trigger_data *hist_data, ...@@ -2785,6 +2804,8 @@ static struct hist_field *create_alias(struct hist_trigger_data *hist_data,
return NULL; return NULL;
} }
alias->var_ref_idx = var_ref->var_ref_idx;
return alias; return alias;
} }
......
This diff is collapsed.
...@@ -219,10 +219,10 @@ trace_print_hex_seq(struct trace_seq *p, const unsigned char *buf, int buf_len, ...@@ -219,10 +219,10 @@ trace_print_hex_seq(struct trace_seq *p, const unsigned char *buf, int buf_len,
{ {
int i; int i;
const char *ret = trace_seq_buffer_ptr(p); const char *ret = trace_seq_buffer_ptr(p);
const char *fmt = concatenate ? "%*phN" : "%*ph";
for (i = 0; i < buf_len; i++) for (i = 0; i < buf_len; i += 16)
trace_seq_printf(p, "%s%2.2x", concatenate || i == 0 ? "" : " ", trace_seq_printf(p, fmt, min(buf_len - i, 16), &buf[i]);
buf[i]);
trace_seq_putc(p, 0); trace_seq_putc(p, 0);
return ret; return ret;
......
...@@ -316,6 +316,29 @@ static int parse_probe_vars(char *arg, const struct fetch_type *t, ...@@ -316,6 +316,29 @@ static int parse_probe_vars(char *arg, const struct fetch_type *t,
return -EINVAL; return -EINVAL;
} }
static int str_to_immediate(char *str, unsigned long *imm)
{
if (isdigit(str[0]))
return kstrtoul(str, 0, imm);
else if (str[0] == '-')
return kstrtol(str, 0, (long *)imm);
else if (str[0] == '+')
return kstrtol(str + 1, 0, (long *)imm);
return -EINVAL;
}
static int __parse_imm_string(char *str, char **pbuf, int offs)
{
size_t len = strlen(str);
if (str[len - 1] != '"') {
trace_probe_log_err(offs + len, IMMSTR_NO_CLOSE);
return -EINVAL;
}
*pbuf = kstrndup(str, len - 1, GFP_KERNEL);
return 0;
}
/* Recursive argument parser */ /* Recursive argument parser */
static int static int
parse_probe_arg(char *arg, const struct fetch_type *type, parse_probe_arg(char *arg, const struct fetch_type *type,
...@@ -430,7 +453,8 @@ parse_probe_arg(char *arg, const struct fetch_type *type, ...@@ -430,7 +453,8 @@ parse_probe_arg(char *arg, const struct fetch_type *type,
ret = parse_probe_arg(arg, t2, &code, end, flags, offs); ret = parse_probe_arg(arg, t2, &code, end, flags, offs);
if (ret) if (ret)
break; break;
if (code->op == FETCH_OP_COMM) { if (code->op == FETCH_OP_COMM ||
code->op == FETCH_OP_DATA) {
trace_probe_log_err(offs, COMM_CANT_DEREF); trace_probe_log_err(offs, COMM_CANT_DEREF);
return -EINVAL; return -EINVAL;
} }
...@@ -444,6 +468,21 @@ parse_probe_arg(char *arg, const struct fetch_type *type, ...@@ -444,6 +468,21 @@ parse_probe_arg(char *arg, const struct fetch_type *type,
code->offset = offset; code->offset = offset;
} }
break; break;
case '\\': /* Immediate value */
if (arg[1] == '"') { /* Immediate string */
ret = __parse_imm_string(arg + 2, &tmp, offs + 2);
if (ret)
break;
code->op = FETCH_OP_DATA;
code->data = tmp;
} else {
ret = str_to_immediate(arg + 1, &code->immediate);
if (ret)
trace_probe_log_err(offs + 1, BAD_IMM);
else
code->op = FETCH_OP_IMM;
}
break;
} }
if (!ret && code->op == FETCH_OP_NOP) { if (!ret && code->op == FETCH_OP_NOP) {
/* Parsed, but do not find fetch method */ /* Parsed, but do not find fetch method */
...@@ -542,8 +581,11 @@ static int traceprobe_parse_probe_arg_body(char *arg, ssize_t *size, ...@@ -542,8 +581,11 @@ static int traceprobe_parse_probe_arg_body(char *arg, ssize_t *size,
} }
} }
/* Since $comm can not be dereferred, we can find $comm by strcmp */ /*
if (strcmp(arg, "$comm") == 0) { * Since $comm and immediate string can not be dereferred,
* we can find those by strcmp.
*/
if (strcmp(arg, "$comm") == 0 || strncmp(arg, "\\\"", 2) == 0) {
/* The type of $comm must be "string", and not an array. */ /* The type of $comm must be "string", and not an array. */
if (parg->count || (t && strcmp(t, "string"))) if (parg->count || (t && strcmp(t, "string")))
return -EINVAL; return -EINVAL;
...@@ -580,7 +622,8 @@ static int traceprobe_parse_probe_arg_body(char *arg, ssize_t *size, ...@@ -580,7 +622,8 @@ static int traceprobe_parse_probe_arg_body(char *arg, ssize_t *size,
if (!strcmp(parg->type->name, "string") || if (!strcmp(parg->type->name, "string") ||
!strcmp(parg->type->name, "ustring")) { !strcmp(parg->type->name, "ustring")) {
if (code->op != FETCH_OP_DEREF && code->op != FETCH_OP_UDEREF && if (code->op != FETCH_OP_DEREF && code->op != FETCH_OP_UDEREF &&
code->op != FETCH_OP_IMM && code->op != FETCH_OP_COMM) { code->op != FETCH_OP_IMM && code->op != FETCH_OP_COMM &&
code->op != FETCH_OP_DATA) {
trace_probe_log_err(offset + (t ? (t - arg) : 0), trace_probe_log_err(offset + (t ? (t - arg) : 0),
BAD_STRING); BAD_STRING);
ret = -EINVAL; ret = -EINVAL;
...@@ -589,9 +632,10 @@ static int traceprobe_parse_probe_arg_body(char *arg, ssize_t *size, ...@@ -589,9 +632,10 @@ static int traceprobe_parse_probe_arg_body(char *arg, ssize_t *size,
if ((code->op == FETCH_OP_IMM || code->op == FETCH_OP_COMM) || if ((code->op == FETCH_OP_IMM || code->op == FETCH_OP_COMM) ||
parg->count) { parg->count) {
/* /*
* IMM and COMM is pointing actual address, those must * IMM, DATA and COMM is pointing actual address, those
* be kept, and if parg->count != 0, this is an array * must be kept, and if parg->count != 0, this is an
* of string pointers instead of string address itself. * array of string pointers instead of string address
* itself.
*/ */
code++; code++;
if (code->op != FETCH_OP_NOP) { if (code->op != FETCH_OP_NOP) {
...@@ -665,7 +709,8 @@ static int traceprobe_parse_probe_arg_body(char *arg, ssize_t *size, ...@@ -665,7 +709,8 @@ static int traceprobe_parse_probe_arg_body(char *arg, ssize_t *size,
fail: fail:
if (ret) { if (ret) {
for (code = tmp; code < tmp + FETCH_INSN_MAX; code++) for (code = tmp; code < tmp + FETCH_INSN_MAX; code++)
if (code->op == FETCH_NOP_SYMBOL) if (code->op == FETCH_NOP_SYMBOL ||
code->op == FETCH_OP_DATA)
kfree(code->data); kfree(code->data);
} }
kfree(tmp); kfree(tmp);
...@@ -736,7 +781,8 @@ void traceprobe_free_probe_arg(struct probe_arg *arg) ...@@ -736,7 +781,8 @@ void traceprobe_free_probe_arg(struct probe_arg *arg)
struct fetch_insn *code = arg->code; struct fetch_insn *code = arg->code;
while (code && code->op != FETCH_OP_END) { while (code && code->op != FETCH_OP_END) {
if (code->op == FETCH_NOP_SYMBOL) if (code->op == FETCH_NOP_SYMBOL ||
code->op == FETCH_OP_DATA)
kfree(code->data); kfree(code->data);
code++; code++;
} }
...@@ -886,44 +932,85 @@ int traceprobe_define_arg_fields(struct trace_event_call *event_call, ...@@ -886,44 +932,85 @@ int traceprobe_define_arg_fields(struct trace_event_call *event_call,
return 0; return 0;
} }
static void trace_probe_event_free(struct trace_probe_event *tpe)
{
kfree(tpe->class.system);
kfree(tpe->call.name);
kfree(tpe->call.print_fmt);
kfree(tpe);
}
int trace_probe_append(struct trace_probe *tp, struct trace_probe *to)
{
if (trace_probe_has_sibling(tp))
return -EBUSY;
list_del_init(&tp->list);
trace_probe_event_free(tp->event);
tp->event = to->event;
list_add_tail(&tp->list, trace_probe_probe_list(to));
return 0;
}
void trace_probe_unlink(struct trace_probe *tp)
{
list_del_init(&tp->list);
if (list_empty(trace_probe_probe_list(tp)))
trace_probe_event_free(tp->event);
tp->event = NULL;
}
void trace_probe_cleanup(struct trace_probe *tp) void trace_probe_cleanup(struct trace_probe *tp)
{ {
struct trace_event_call *call = trace_probe_event_call(tp);
int i; int i;
for (i = 0; i < tp->nr_args; i++) for (i = 0; i < tp->nr_args; i++)
traceprobe_free_probe_arg(&tp->args[i]); traceprobe_free_probe_arg(&tp->args[i]);
if (call->class) if (tp->event)
kfree(call->class->system); trace_probe_unlink(tp);
kfree(call->name);
kfree(call->print_fmt);
} }
int trace_probe_init(struct trace_probe *tp, const char *event, int trace_probe_init(struct trace_probe *tp, const char *event,
const char *group) const char *group)
{ {
struct trace_event_call *call = trace_probe_event_call(tp); struct trace_event_call *call;
int ret = 0;
if (!event || !group) if (!event || !group)
return -EINVAL; return -EINVAL;
call->class = &tp->class; tp->event = kzalloc(sizeof(struct trace_probe_event), GFP_KERNEL);
call->name = kstrdup(event, GFP_KERNEL); if (!tp->event)
if (!call->name)
return -ENOMEM; return -ENOMEM;
tp->class.system = kstrdup(group, GFP_KERNEL); INIT_LIST_HEAD(&tp->event->files);
if (!tp->class.system) { INIT_LIST_HEAD(&tp->event->class.fields);
kfree(call->name); INIT_LIST_HEAD(&tp->event->probes);
call->name = NULL; INIT_LIST_HEAD(&tp->list);
return -ENOMEM; list_add(&tp->event->probes, &tp->list);
call = trace_probe_event_call(tp);
call->class = &tp->event->class;
call->name = kstrdup(event, GFP_KERNEL);
if (!call->name) {
ret = -ENOMEM;
goto error;
}
tp->event->class.system = kstrdup(group, GFP_KERNEL);
if (!tp->event->class.system) {
ret = -ENOMEM;
goto error;
} }
INIT_LIST_HEAD(&tp->files);
INIT_LIST_HEAD(&tp->class.fields);
return 0; return 0;
error:
trace_probe_cleanup(tp);
return ret;
} }
int trace_probe_register_event_call(struct trace_probe *tp) int trace_probe_register_event_call(struct trace_probe *tp)
...@@ -952,7 +1039,7 @@ int trace_probe_add_file(struct trace_probe *tp, struct trace_event_file *file) ...@@ -952,7 +1039,7 @@ int trace_probe_add_file(struct trace_probe *tp, struct trace_event_file *file)
link->file = file; link->file = file;
INIT_LIST_HEAD(&link->list); INIT_LIST_HEAD(&link->list);
list_add_tail_rcu(&link->list, &tp->files); list_add_tail_rcu(&link->list, &tp->event->files);
trace_probe_set_flag(tp, TP_FLAG_TRACE); trace_probe_set_flag(tp, TP_FLAG_TRACE);
return 0; return 0;
} }
...@@ -983,8 +1070,45 @@ int trace_probe_remove_file(struct trace_probe *tp, ...@@ -983,8 +1070,45 @@ int trace_probe_remove_file(struct trace_probe *tp,
synchronize_rcu(); synchronize_rcu();
kfree(link); kfree(link);
if (list_empty(&tp->files)) if (list_empty(&tp->event->files))
trace_probe_clear_flag(tp, TP_FLAG_TRACE); trace_probe_clear_flag(tp, TP_FLAG_TRACE);
return 0; return 0;
} }
/*
* Return the smallest index of different type argument (start from 1).
* If all argument types and name are same, return 0.
*/
int trace_probe_compare_arg_type(struct trace_probe *a, struct trace_probe *b)
{
int i;
for (i = 0; i < a->nr_args; i++) {
if ((b->nr_args <= i) ||
((a->args[i].type != b->args[i].type) ||
(a->args[i].count != b->args[i].count) ||
strcmp(a->args[i].name, b->args[i].name)))
return i + 1;
}
return 0;
}
bool trace_probe_match_command_args(struct trace_probe *tp,
int argc, const char **argv)
{
char buf[MAX_ARGSTR_LEN + 1];
int i;
if (tp->nr_args < argc)
return false;
for (i = 0; i < argc; i++) {
snprintf(buf, sizeof(buf), "%s=%s",
tp->args[i].name, tp->args[i].comm);
if (strcmp(buf, argv[i]))
return false;
}
return true;
}
...@@ -89,6 +89,7 @@ enum fetch_op { ...@@ -89,6 +89,7 @@ enum fetch_op {
FETCH_OP_COMM, /* Current comm */ FETCH_OP_COMM, /* Current comm */
FETCH_OP_ARG, /* Function argument : .param */ FETCH_OP_ARG, /* Function argument : .param */
FETCH_OP_FOFFS, /* File offset: .immediate */ FETCH_OP_FOFFS, /* File offset: .immediate */
FETCH_OP_DATA, /* Allocated data: .data */
// Stage 2 (dereference) op // Stage 2 (dereference) op
FETCH_OP_DEREF, /* Dereference: .offset */ FETCH_OP_DEREF, /* Dereference: .offset */
FETCH_OP_UDEREF, /* User-space Dereference: .offset */ FETCH_OP_UDEREF, /* User-space Dereference: .offset */
...@@ -222,11 +223,18 @@ struct probe_arg { ...@@ -222,11 +223,18 @@ struct probe_arg {
const struct fetch_type *type; /* Type of this argument */ const struct fetch_type *type; /* Type of this argument */
}; };
struct trace_probe { /* Event call and class holder */
struct trace_probe_event {
unsigned int flags; /* For TP_FLAG_* */ unsigned int flags; /* For TP_FLAG_* */
struct trace_event_class class; struct trace_event_class class;
struct trace_event_call call; struct trace_event_call call;
struct list_head files; struct list_head files;
struct list_head probes;
};
struct trace_probe {
struct list_head list;
struct trace_probe_event *event;
ssize_t size; /* trace entry size */ ssize_t size; /* trace entry size */
unsigned int nr_args; unsigned int nr_args;
struct probe_arg args[]; struct probe_arg args[];
...@@ -240,19 +248,19 @@ struct event_file_link { ...@@ -240,19 +248,19 @@ struct event_file_link {
static inline bool trace_probe_test_flag(struct trace_probe *tp, static inline bool trace_probe_test_flag(struct trace_probe *tp,
unsigned int flag) unsigned int flag)
{ {
return !!(tp->flags & flag); return !!(tp->event->flags & flag);
} }
static inline void trace_probe_set_flag(struct trace_probe *tp, static inline void trace_probe_set_flag(struct trace_probe *tp,
unsigned int flag) unsigned int flag)
{ {
tp->flags |= flag; tp->event->flags |= flag;
} }
static inline void trace_probe_clear_flag(struct trace_probe *tp, static inline void trace_probe_clear_flag(struct trace_probe *tp,
unsigned int flag) unsigned int flag)
{ {
tp->flags &= ~flag; tp->event->flags &= ~flag;
} }
static inline bool trace_probe_is_enabled(struct trace_probe *tp) static inline bool trace_probe_is_enabled(struct trace_probe *tp)
...@@ -262,45 +270,76 @@ static inline bool trace_probe_is_enabled(struct trace_probe *tp) ...@@ -262,45 +270,76 @@ static inline bool trace_probe_is_enabled(struct trace_probe *tp)
static inline const char *trace_probe_name(struct trace_probe *tp) static inline const char *trace_probe_name(struct trace_probe *tp)
{ {
return trace_event_name(&tp->call); return trace_event_name(&tp->event->call);
} }
static inline const char *trace_probe_group_name(struct trace_probe *tp) static inline const char *trace_probe_group_name(struct trace_probe *tp)
{ {
return tp->call.class->system; return tp->event->call.class->system;
} }
static inline struct trace_event_call * static inline struct trace_event_call *
trace_probe_event_call(struct trace_probe *tp) trace_probe_event_call(struct trace_probe *tp)
{ {
return &tp->call; return &tp->event->call;
}
static inline struct trace_probe_event *
trace_probe_event_from_call(struct trace_event_call *event_call)
{
return container_of(event_call, struct trace_probe_event, call);
}
static inline struct trace_probe *
trace_probe_primary_from_call(struct trace_event_call *call)
{
struct trace_probe_event *tpe = trace_probe_event_from_call(call);
return list_first_entry(&tpe->probes, struct trace_probe, list);
}
static inline struct list_head *trace_probe_probe_list(struct trace_probe *tp)
{
return &tp->event->probes;
}
static inline bool trace_probe_has_sibling(struct trace_probe *tp)
{
struct list_head *list = trace_probe_probe_list(tp);
return !list_empty(list) && !list_is_singular(list);
} }
static inline int trace_probe_unregister_event_call(struct trace_probe *tp) static inline int trace_probe_unregister_event_call(struct trace_probe *tp)
{ {
/* tp->event is unregistered in trace_remove_event_call() */ /* tp->event is unregistered in trace_remove_event_call() */
return trace_remove_event_call(&tp->call); return trace_remove_event_call(&tp->event->call);
} }
static inline bool trace_probe_has_single_file(struct trace_probe *tp) static inline bool trace_probe_has_single_file(struct trace_probe *tp)
{ {
return !!list_is_singular(&tp->files); return !!list_is_singular(&tp->event->files);
} }
int trace_probe_init(struct trace_probe *tp, const char *event, int trace_probe_init(struct trace_probe *tp, const char *event,
const char *group); const char *group);
void trace_probe_cleanup(struct trace_probe *tp); void trace_probe_cleanup(struct trace_probe *tp);
int trace_probe_append(struct trace_probe *tp, struct trace_probe *to);
void trace_probe_unlink(struct trace_probe *tp);
int trace_probe_register_event_call(struct trace_probe *tp); int trace_probe_register_event_call(struct trace_probe *tp);
int trace_probe_add_file(struct trace_probe *tp, struct trace_event_file *file); int trace_probe_add_file(struct trace_probe *tp, struct trace_event_file *file);
int trace_probe_remove_file(struct trace_probe *tp, int trace_probe_remove_file(struct trace_probe *tp,
struct trace_event_file *file); struct trace_event_file *file);
struct event_file_link *trace_probe_get_file_link(struct trace_probe *tp, struct event_file_link *trace_probe_get_file_link(struct trace_probe *tp,
struct trace_event_file *file); struct trace_event_file *file);
int trace_probe_compare_arg_type(struct trace_probe *a, struct trace_probe *b);
bool trace_probe_match_command_args(struct trace_probe *tp,
int argc, const char **argv);
#define trace_probe_for_each_link(pos, tp) \ #define trace_probe_for_each_link(pos, tp) \
list_for_each_entry(pos, &(tp)->files, list) list_for_each_entry(pos, &(tp)->event->files, list)
#define trace_probe_for_each_link_rcu(pos, tp) \ #define trace_probe_for_each_link_rcu(pos, tp) \
list_for_each_entry_rcu(pos, &(tp)->files, list) list_for_each_entry_rcu(pos, &(tp)->event->files, list)
/* Check the name is good for event/group/fields */ /* Check the name is good for event/group/fields */
static inline bool is_good_name(const char *name) static inline bool is_good_name(const char *name)
...@@ -370,6 +409,8 @@ extern int traceprobe_define_arg_fields(struct trace_event_call *event_call, ...@@ -370,6 +409,8 @@ extern int traceprobe_define_arg_fields(struct trace_event_call *event_call,
C(BAD_VAR, "Invalid $-valiable specified"), \ C(BAD_VAR, "Invalid $-valiable specified"), \
C(BAD_REG_NAME, "Invalid register name"), \ C(BAD_REG_NAME, "Invalid register name"), \
C(BAD_MEM_ADDR, "Invalid memory address"), \ C(BAD_MEM_ADDR, "Invalid memory address"), \
C(BAD_IMM, "Invalid immediate value"), \
C(IMMSTR_NO_CLOSE, "String is not closed with '\"'"), \
C(FILE_ON_KPROBE, "File offset is not available with kprobe"), \ C(FILE_ON_KPROBE, "File offset is not available with kprobe"), \
C(BAD_FILE_OFFS, "Invalid file offset value"), \ C(BAD_FILE_OFFS, "Invalid file offset value"), \
C(SYM_ON_UPROBE, "Symbol is not available with uprobe"), \ C(SYM_ON_UPROBE, "Symbol is not available with uprobe"), \
...@@ -393,7 +434,10 @@ extern int traceprobe_define_arg_fields(struct trace_event_call *event_call, ...@@ -393,7 +434,10 @@ extern int traceprobe_define_arg_fields(struct trace_event_call *event_call,
C(ARG_TOO_LONG, "Argument expression is too long"), \ C(ARG_TOO_LONG, "Argument expression is too long"), \
C(NO_ARG_BODY, "No argument expression"), \ C(NO_ARG_BODY, "No argument expression"), \
C(BAD_INSN_BNDRY, "Probe point is not an instruction boundary"),\ C(BAD_INSN_BNDRY, "Probe point is not an instruction boundary"),\
C(FAIL_REG_PROBE, "Failed to register probe event"), C(FAIL_REG_PROBE, "Failed to register probe event"),\
C(DIFF_PROBE_TYPE, "Probe type is different from existing probe"),\
C(DIFF_ARG_TYPE, "Argument type or name is different from existing probe"),\
C(SAME_PROBE, "There is already the exact same probe event"),
#undef C #undef C
#define C(a, b) TP_ERR_##a #define C(a, b) TP_ERR_##a
......
...@@ -53,6 +53,104 @@ static void print_max_stack(void) ...@@ -53,6 +53,104 @@ static void print_max_stack(void)
} }
} }
/*
* The stack tracer looks for a maximum stack at each call from a function. It
* registers a callback from ftrace, and in that callback it examines the stack
* size. It determines the stack size from the variable passed in, which is the
* address of a local variable in the stack_trace_call() callback function.
* The stack size is calculated by the address of the local variable to the top
* of the current stack. If that size is smaller than the currently saved max
* stack size, nothing more is done.
*
* If the size of the stack is greater than the maximum recorded size, then the
* following algorithm takes place.
*
* For architectures (like x86) that store the function's return address before
* saving the function's local variables, the stack will look something like
* this:
*
* [ top of stack ]
* 0: sys call entry frame
* 10: return addr to entry code
* 11: start of sys_foo frame
* 20: return addr to sys_foo
* 21: start of kernel_func_bar frame
* 30: return addr to kernel_func_bar
* 31: [ do trace stack here ]
*
* The save_stack_trace() is called returning all the functions it finds in the
* current stack. Which would be (from the bottom of the stack to the top):
*
* return addr to kernel_func_bar
* return addr to sys_foo
* return addr to entry code
*
* Now to figure out how much each of these functions' local variable size is,
* a search of the stack is made to find these values. When a match is made, it
* is added to the stack_dump_trace[] array. The offset into the stack is saved
* in the stack_trace_index[] array. The above example would show:
*
* stack_dump_trace[] | stack_trace_index[]
* ------------------ + -------------------
* return addr to kernel_func_bar | 30
* return addr to sys_foo | 20
* return addr to entry | 10
*
* The print_max_stack() function above, uses these values to print the size of
* each function's portion of the stack.
*
* for (i = 0; i < nr_entries; i++) {
* size = i == nr_entries - 1 ? stack_trace_index[i] :
* stack_trace_index[i] - stack_trace_index[i+1]
* print "%d %d %d %s\n", i, stack_trace_index[i], size, stack_dump_trace[i]);
* }
*
* The above shows
*
* depth size location
* ----- ---- --------
* 0 30 10 kernel_func_bar
* 1 20 10 sys_foo
* 2 10 10 entry code
*
* Now for architectures that might save the return address after the functions
* local variables (saving the link register before calling nested functions),
* this will cause the stack to look a little different:
*
* [ top of stack ]
* 0: sys call entry frame
* 10: start of sys_foo_frame
* 19: return addr to entry code << lr saved before calling kernel_func_bar
* 20: start of kernel_func_bar frame
* 29: return addr to sys_foo_frame << lr saved before calling next function
* 30: [ do trace stack here ]
*
* Although the functions returned by save_stack_trace() may be the same, the
* placement in the stack will be different. Using the same algorithm as above
* would yield:
*
* stack_dump_trace[] | stack_trace_index[]
* ------------------ + -------------------
* return addr to kernel_func_bar | 30
* return addr to sys_foo | 29
* return addr to entry | 19
*
* Where the mapping is off by one:
*
* kernel_func_bar stack frame size is 29 - 19 not 30 - 29!
*
* To fix this, if the architecture sets ARCH_RET_ADDR_AFTER_LOCAL_VARS the
* values in stack_trace_index[] are shifted by one to and the number of
* stack trace entries is decremented by one.
*
* stack_dump_trace[] | stack_trace_index[]
* ------------------ + -------------------
* return addr to kernel_func_bar | 29
* return addr to sys_foo | 19
*
* Although the entry function is not displayed, the first function (sys_foo)
* will still include the stack size of it.
*/
static void check_stack(unsigned long ip, unsigned long *stack) static void check_stack(unsigned long ip, unsigned long *stack)
{ {
unsigned long this_size, flags; unsigned long *p, *top, *start; unsigned long this_size, flags; unsigned long *p, *top, *start;
...@@ -158,6 +256,20 @@ static void check_stack(unsigned long ip, unsigned long *stack) ...@@ -158,6 +256,20 @@ static void check_stack(unsigned long ip, unsigned long *stack)
i++; i++;
} }
#ifdef ARCH_FTRACE_SHIFT_STACK_TRACER
/*
* Some archs will store the link register before calling
* nested functions. This means the saved return address
* comes after the local storage, and we need to shift
* for that.
*/
if (x > 1) {
memmove(&stack_trace_index[0], &stack_trace_index[1],
sizeof(stack_trace_index[0]) * (x - 1));
x--;
}
#endif
stack_trace_nr_entries = x; stack_trace_nr_entries = x;
if (task_stack_end_corrupted(current)) { if (task_stack_end_corrupted(current)) {
......
This diff is collapsed.
This diff is collapsed.
...@@ -174,7 +174,7 @@ static int MIPS_is_fake_mcount(Elf_Rel const *rp) ...@@ -174,7 +174,7 @@ static int MIPS_is_fake_mcount(Elf_Rel const *rp)
} }
/* Append the new shstrtab, Elf_Shdr[], __mcount_loc and its relocations. */ /* Append the new shstrtab, Elf_Shdr[], __mcount_loc and its relocations. */
static void append_func(Elf_Ehdr *const ehdr, static int append_func(Elf_Ehdr *const ehdr,
Elf_Shdr *const shstr, Elf_Shdr *const shstr,
uint_t const *const mloc0, uint_t const *const mloc0,
uint_t const *const mlocp, uint_t const *const mlocp,
...@@ -202,15 +202,20 @@ static void append_func(Elf_Ehdr *const ehdr, ...@@ -202,15 +202,20 @@ static void append_func(Elf_Ehdr *const ehdr,
new_e_shoff = t; new_e_shoff = t;
/* body for new shstrtab */ /* body for new shstrtab */
ulseek(fd_map, sb.st_size, SEEK_SET); if (ulseek(sb.st_size, SEEK_SET) < 0)
uwrite(fd_map, old_shstr_sh_offset + (void *)ehdr, old_shstr_sh_size); return -1;
uwrite(fd_map, mc_name, 1 + strlen(mc_name)); if (uwrite(old_shstr_sh_offset + (void *)ehdr, old_shstr_sh_size) < 0)
return -1;
if (uwrite(mc_name, 1 + strlen(mc_name)) < 0)
return -1;
/* old(modified) Elf_Shdr table, word-byte aligned */ /* old(modified) Elf_Shdr table, word-byte aligned */
ulseek(fd_map, t, SEEK_SET); if (ulseek(t, SEEK_SET) < 0)
return -1;
t += sizeof(Elf_Shdr) * old_shnum; t += sizeof(Elf_Shdr) * old_shnum;
uwrite(fd_map, old_shoff + (void *)ehdr, if (uwrite(old_shoff + (void *)ehdr,
sizeof(Elf_Shdr) * old_shnum); sizeof(Elf_Shdr) * old_shnum) < 0)
return -1;
/* new sections __mcount_loc and .rel__mcount_loc */ /* new sections __mcount_loc and .rel__mcount_loc */
t += 2*sizeof(mcsec); t += 2*sizeof(mcsec);
...@@ -225,7 +230,8 @@ static void append_func(Elf_Ehdr *const ehdr, ...@@ -225,7 +230,8 @@ static void append_func(Elf_Ehdr *const ehdr,
mcsec.sh_info = 0; mcsec.sh_info = 0;
mcsec.sh_addralign = _w(_size); mcsec.sh_addralign = _w(_size);
mcsec.sh_entsize = _w(_size); mcsec.sh_entsize = _w(_size);
uwrite(fd_map, &mcsec, sizeof(mcsec)); if (uwrite(&mcsec, sizeof(mcsec)) < 0)
return -1;
mcsec.sh_name = w(old_shstr_sh_size); mcsec.sh_name = w(old_shstr_sh_size);
mcsec.sh_type = (sizeof(Elf_Rela) == rel_entsize) mcsec.sh_type = (sizeof(Elf_Rela) == rel_entsize)
...@@ -239,15 +245,22 @@ static void append_func(Elf_Ehdr *const ehdr, ...@@ -239,15 +245,22 @@ static void append_func(Elf_Ehdr *const ehdr,
mcsec.sh_info = w(old_shnum); mcsec.sh_info = w(old_shnum);
mcsec.sh_addralign = _w(_size); mcsec.sh_addralign = _w(_size);
mcsec.sh_entsize = _w(rel_entsize); mcsec.sh_entsize = _w(rel_entsize);
uwrite(fd_map, &mcsec, sizeof(mcsec));
uwrite(fd_map, mloc0, (void *)mlocp - (void *)mloc0); if (uwrite(&mcsec, sizeof(mcsec)) < 0)
uwrite(fd_map, mrel0, (void *)mrelp - (void *)mrel0); return -1;
if (uwrite(mloc0, (void *)mlocp - (void *)mloc0) < 0)
return -1;
if (uwrite(mrel0, (void *)mrelp - (void *)mrel0) < 0)
return -1;
ehdr->e_shoff = _w(new_e_shoff); ehdr->e_shoff = _w(new_e_shoff);
ehdr->e_shnum = w2(2 + w2(ehdr->e_shnum)); /* {.rel,}__mcount_loc */ ehdr->e_shnum = w2(2 + w2(ehdr->e_shnum)); /* {.rel,}__mcount_loc */
ulseek(fd_map, 0, SEEK_SET); if (ulseek(0, SEEK_SET) < 0)
uwrite(fd_map, ehdr, sizeof(*ehdr)); return -1;
if (uwrite(ehdr, sizeof(*ehdr)) < 0)
return -1;
return 0;
} }
static unsigned get_mcountsym(Elf_Sym const *const sym0, static unsigned get_mcountsym(Elf_Sym const *const sym0,
...@@ -351,7 +364,7 @@ static uint_t *sift_rel_mcount(uint_t *mlocp, ...@@ -351,7 +364,7 @@ static uint_t *sift_rel_mcount(uint_t *mlocp,
* that are not going to be traced. The mcount calls here will be converted * that are not going to be traced. The mcount calls here will be converted
* into nops. * into nops.
*/ */
static void nop_mcount(Elf_Shdr const *const relhdr, static int nop_mcount(Elf_Shdr const *const relhdr,
Elf_Ehdr const *const ehdr, Elf_Ehdr const *const ehdr,
const char *const txtname) const char *const txtname)
{ {
...@@ -376,15 +389,18 @@ static void nop_mcount(Elf_Shdr const *const relhdr, ...@@ -376,15 +389,18 @@ static void nop_mcount(Elf_Shdr const *const relhdr,
mcountsym = get_mcountsym(sym0, relp, str0); mcountsym = get_mcountsym(sym0, relp, str0);
if (mcountsym == Elf_r_sym(relp) && !is_fake_mcount(relp)) { if (mcountsym == Elf_r_sym(relp) && !is_fake_mcount(relp)) {
if (make_nop) if (make_nop) {
ret = make_nop((void *)ehdr, _w(shdr->sh_offset) + _w(relp->r_offset)); ret = make_nop((void *)ehdr, _w(shdr->sh_offset) + _w(relp->r_offset));
if (ret < 0)
return -1;
}
if (warn_on_notrace_sect && !once) { if (warn_on_notrace_sect && !once) {
printf("Section %s has mcount callers being ignored\n", printf("Section %s has mcount callers being ignored\n",
txtname); txtname);
once = 1; once = 1;
/* just warn? */ /* just warn? */
if (!make_nop) if (!make_nop)
return; return 0;
} }
} }
...@@ -396,14 +412,16 @@ static void nop_mcount(Elf_Shdr const *const relhdr, ...@@ -396,14 +412,16 @@ static void nop_mcount(Elf_Shdr const *const relhdr,
Elf_Rel rel; Elf_Rel rel;
rel = *(Elf_Rel *)relp; rel = *(Elf_Rel *)relp;
Elf_r_info(&rel, Elf_r_sym(relp), rel_type_nop); Elf_r_info(&rel, Elf_r_sym(relp), rel_type_nop);
ulseek(fd_map, (void *)relp - (void *)ehdr, SEEK_SET); if (ulseek((void *)relp - (void *)ehdr, SEEK_SET) < 0)
uwrite(fd_map, &rel, sizeof(rel)); return -1;
if (uwrite(&rel, sizeof(rel)) < 0)
return -1;
} }
relp = (Elf_Rel const *)(rel_entsize + (void *)relp); relp = (Elf_Rel const *)(rel_entsize + (void *)relp);
} }
return 0;
} }
/* /*
* Find a symbol in the given section, to be used as the base for relocating * Find a symbol in the given section, to be used as the base for relocating
* the table of offsets of calls to mcount. A local or global symbol suffices, * the table of offsets of calls to mcount. A local or global symbol suffices,
...@@ -414,9 +432,10 @@ static void nop_mcount(Elf_Shdr const *const relhdr, ...@@ -414,9 +432,10 @@ static void nop_mcount(Elf_Shdr const *const relhdr,
* Num: Value Size Type Bind Vis Ndx Name * Num: Value Size Type Bind Vis Ndx Name
* 2: 00000000 0 SECTION LOCAL DEFAULT 1 * 2: 00000000 0 SECTION LOCAL DEFAULT 1
*/ */
static unsigned find_secsym_ndx(unsigned const txtndx, static int find_secsym_ndx(unsigned const txtndx,
char const *const txtname, char const *const txtname,
uint_t *const recvalp, uint_t *const recvalp,
unsigned int *sym_index,
Elf_Shdr const *const symhdr, Elf_Shdr const *const symhdr,
Elf_Ehdr const *const ehdr) Elf_Ehdr const *const ehdr)
{ {
...@@ -438,18 +457,17 @@ static unsigned find_secsym_ndx(unsigned const txtndx, ...@@ -438,18 +457,17 @@ static unsigned find_secsym_ndx(unsigned const txtndx,
continue; continue;
*recvalp = _w(symp->st_value); *recvalp = _w(symp->st_value);
return symp - sym0; *sym_index = symp - sym0;
return 0;
} }
} }
fprintf(stderr, "Cannot find symbol for section %u: %s.\n", fprintf(stderr, "Cannot find symbol for section %u: %s.\n",
txtndx, txtname); txtndx, txtname);
fail_file(); return -1;
} }
/* Evade ISO C restriction: no declaration after statement in has_rel_mcount. */ /* Evade ISO C restriction: no declaration after statement in has_rel_mcount. */
static char const * static char const * __has_rel_mcount(Elf_Shdr const *const relhdr, /* reltype */
__has_rel_mcount(Elf_Shdr const *const relhdr, /* is SHT_REL or SHT_RELA */
Elf_Shdr const *const shdr0, Elf_Shdr const *const shdr0,
char const *const shstrtab, char const *const shstrtab,
char const *const fname) char const *const fname)
...@@ -461,7 +479,7 @@ __has_rel_mcount(Elf_Shdr const *const relhdr, /* is SHT_REL or SHT_RELA */ ...@@ -461,7 +479,7 @@ __has_rel_mcount(Elf_Shdr const *const relhdr, /* is SHT_REL or SHT_RELA */
if (strcmp("__mcount_loc", txtname) == 0) { if (strcmp("__mcount_loc", txtname) == 0) {
fprintf(stderr, "warning: __mcount_loc already exists: %s\n", fprintf(stderr, "warning: __mcount_loc already exists: %s\n",
fname); fname);
succeed_file(); return already_has_rel_mcount;
} }
if (w(txthdr->sh_type) != SHT_PROGBITS || if (w(txthdr->sh_type) != SHT_PROGBITS ||
!(_w(txthdr->sh_flags) & SHF_EXECINSTR)) !(_w(txthdr->sh_flags) & SHF_EXECINSTR))
...@@ -491,6 +509,10 @@ static unsigned tot_relsize(Elf_Shdr const *const shdr0, ...@@ -491,6 +509,10 @@ static unsigned tot_relsize(Elf_Shdr const *const shdr0,
for (; nhdr; --nhdr, ++shdrp) { for (; nhdr; --nhdr, ++shdrp) {
txtname = has_rel_mcount(shdrp, shdr0, shstrtab, fname); txtname = has_rel_mcount(shdrp, shdr0, shstrtab, fname);
if (txtname == already_has_rel_mcount) {
totrelsz = 0;
break;
}
if (txtname && is_mcounted_section_name(txtname)) if (txtname && is_mcounted_section_name(txtname))
totrelsz += _w(shdrp->sh_size); totrelsz += _w(shdrp->sh_size);
} }
...@@ -499,8 +521,8 @@ static unsigned tot_relsize(Elf_Shdr const *const shdr0, ...@@ -499,8 +521,8 @@ static unsigned tot_relsize(Elf_Shdr const *const shdr0,
/* Overall supervision for Elf32 ET_REL file. */ /* Overall supervision for Elf32 ET_REL file. */
static void static int do_func(Elf_Ehdr *const ehdr, char const *const fname,
do_func(Elf_Ehdr *const ehdr, char const *const fname, unsigned const reltype) unsigned const reltype)
{ {
Elf_Shdr *const shdr0 = (Elf_Shdr *)(_w(ehdr->e_shoff) Elf_Shdr *const shdr0 = (Elf_Shdr *)(_w(ehdr->e_shoff)
+ (void *)ehdr); + (void *)ehdr);
...@@ -513,26 +535,54 @@ do_func(Elf_Ehdr *const ehdr, char const *const fname, unsigned const reltype) ...@@ -513,26 +535,54 @@ do_func(Elf_Ehdr *const ehdr, char const *const fname, unsigned const reltype)
unsigned k; unsigned k;
/* Upper bound on space: assume all relevant relocs are for mcount. */ /* Upper bound on space: assume all relevant relocs are for mcount. */
unsigned const totrelsz = tot_relsize(shdr0, nhdr, shstrtab, fname); unsigned totrelsz;
Elf_Rel *const mrel0 = umalloc(totrelsz);
Elf_Rel * mrelp = mrel0;
/* 2*sizeof(address) <= sizeof(Elf_Rel) */ Elf_Rel * mrel0;
uint_t *const mloc0 = umalloc(totrelsz>>1); Elf_Rel * mrelp;
uint_t * mlocp = mloc0;
uint_t * mloc0;
uint_t * mlocp;
unsigned rel_entsize = 0; unsigned rel_entsize = 0;
unsigned symsec_sh_link = 0; unsigned symsec_sh_link = 0;
int result = 0;
totrelsz = tot_relsize(shdr0, nhdr, shstrtab, fname);
if (totrelsz == 0)
return 0;
mrel0 = umalloc(totrelsz);
mrelp = mrel0;
if (!mrel0)
return -1;
/* 2*sizeof(address) <= sizeof(Elf_Rel) */
mloc0 = umalloc(totrelsz>>1);
mlocp = mloc0;
if (!mloc0) {
free(mrel0);
return -1;
}
for (relhdr = shdr0, k = nhdr; k; --k, ++relhdr) { for (relhdr = shdr0, k = nhdr; k; --k, ++relhdr) {
char const *const txtname = has_rel_mcount(relhdr, shdr0, char const *const txtname = has_rel_mcount(relhdr, shdr0,
shstrtab, fname); shstrtab, fname);
if (txtname == already_has_rel_mcount) {
result = 0;
file_updated = 0;
goto out; /* Nothing to be done; don't append! */
}
if (txtname && is_mcounted_section_name(txtname)) { if (txtname && is_mcounted_section_name(txtname)) {
unsigned int recsym;
uint_t recval = 0; uint_t recval = 0;
unsigned const recsym = find_secsym_ndx(
w(relhdr->sh_info), txtname, &recval, symsec_sh_link = w(relhdr->sh_link);
&shdr0[symsec_sh_link = w(relhdr->sh_link)], result = find_secsym_ndx(w(relhdr->sh_info), txtname,
&recval, &recsym,
&shdr0[symsec_sh_link],
ehdr); ehdr);
if (result)
goto out;
rel_entsize = _w(relhdr->sh_entsize); rel_entsize = _w(relhdr->sh_entsize);
mlocp = sift_rel_mcount(mlocp, mlocp = sift_rel_mcount(mlocp,
...@@ -543,13 +593,17 @@ do_func(Elf_Ehdr *const ehdr, char const *const fname, unsigned const reltype) ...@@ -543,13 +593,17 @@ do_func(Elf_Ehdr *const ehdr, char const *const fname, unsigned const reltype)
* This section is ignored by ftrace, but still * This section is ignored by ftrace, but still
* has mcount calls. Convert them to nops now. * has mcount calls. Convert them to nops now.
*/ */
nop_mcount(relhdr, ehdr, txtname); if (nop_mcount(relhdr, ehdr, txtname) < 0) {
result = -1;
goto out;
} }
} }
if (mloc0 != mlocp) {
append_func(ehdr, shstr, mloc0, mlocp, mrel0, mrelp,
rel_entsize, symsec_sh_link);
} }
if (!result && mloc0 != mlocp)
result = append_func(ehdr, shstr, mloc0, mlocp, mrel0, mrelp,
rel_entsize, symsec_sh_link);
out:
free(mrel0); free(mrel0);
free(mloc0); free(mloc0);
return result;
} }
...@@ -115,7 +115,7 @@ ftrace_errlog_check() { # err-prefix command-with-error-pos-by-^ command-file ...@@ -115,7 +115,7 @@ ftrace_errlog_check() { # err-prefix command-with-error-pos-by-^ command-file
command=$(echo "$2" | tr -d ^) command=$(echo "$2" | tr -d ^)
echo "Test command: $command" echo "Test command: $command"
echo > error_log echo > error_log
(! echo "$command" > "$3" ) 2> /dev/null (! echo "$command" >> "$3" ) 2> /dev/null
grep "$1: error:" -A 3 error_log grep "$1: error:" -A 3 error_log
N=$(tail -n 1 error_log | wc -c) N=$(tail -n 1 error_log | wc -c)
# " Command: " and "^\n" => 13 # " Command: " and "^\n" => 13
......
...@@ -24,7 +24,21 @@ test -d events/kprobes2/event2 || exit_failure ...@@ -24,7 +24,21 @@ test -d events/kprobes2/event2 || exit_failure
:;: "Add an event on dot function without name" ;: :;: "Add an event on dot function without name" ;:
FUNC=`grep -m 10 " [tT] .*\.isra\..*$" /proc/kallsyms | tail -n 1 | cut -f 3 -d " "` find_dot_func() {
if [ ! -f available_filter_functions ]; then
grep -m 10 " [tT] .*\.isra\..*$" /proc/kallsyms | tail -n 1 | cut -f 3 -d " "
return;
fi
grep " [tT] .*\.isra\..*" /proc/kallsyms | cut -f 3 -d " " | while read f; do
if grep -s $f available_filter_functions; then
echo $f
break
fi
done
}
FUNC=`find_dot_func | tail -n 1`
[ "x" != "x$FUNC" ] || exit_unresolved [ "x" != "x$FUNC" ] || exit_unresolved
echo "p $FUNC" > kprobe_events echo "p $FUNC" > kprobe_events
EVENT=`grep $FUNC kprobe_events | cut -f 1 -d " " | cut -f 2 -d:` EVENT=`grep $FUNC kprobe_events | cut -f 1 -d " " | cut -f 2 -d:`
......
#!/bin/sh
# SPDX-License-Identifier: GPL-2.0
# description: Create/delete multiprobe on kprobe event
[ -f kprobe_events ] || exit_unsupported
grep -q "Create/append/" README || exit_unsupported
# Choose 2 symbols for target
SYM1=_do_fork
SYM2=do_exit
EVENT_NAME=kprobes/testevent
DEF1="p:$EVENT_NAME $SYM1"
DEF2="p:$EVENT_NAME $SYM2"
:;: "Define an event which has 2 probes" ;:
echo $DEF1 >> kprobe_events
echo $DEF2 >> kprobe_events
cat kprobe_events | grep "$DEF1"
cat kprobe_events | grep "$DEF2"
:;: "Remove the event by name (should remove both)" ;:
echo "-:$EVENT_NAME" >> kprobe_events
test `cat kprobe_events | wc -l` -eq 0
:;: "Remove just 1 event" ;:
echo $DEF1 >> kprobe_events
echo $DEF2 >> kprobe_events
echo "-:$EVENT_NAME $SYM1" >> kprobe_events
! cat kprobe_events | grep "$DEF1"
cat kprobe_events | grep "$DEF2"
:;: "Appending different type must fail" ;:
! echo "$DEF1 \$stack" >> kprobe_events
...@@ -41,6 +41,11 @@ check_error 'p vfs_read ^%none_reg' # BAD_REG_NAME ...@@ -41,6 +41,11 @@ check_error 'p vfs_read ^%none_reg' # BAD_REG_NAME
check_error 'p vfs_read ^@12345678abcde' # BAD_MEM_ADDR check_error 'p vfs_read ^@12345678abcde' # BAD_MEM_ADDR
check_error 'p vfs_read ^@+10' # FILE_ON_KPROBE check_error 'p vfs_read ^@+10' # FILE_ON_KPROBE
grep -q "imm-value" README && \
check_error 'p vfs_read arg1=\^x' # BAD_IMM
grep -q "imm-string" README && \
check_error 'p vfs_read arg1=\"abcd^' # IMMSTR_NO_CLOSE
check_error 'p vfs_read ^+0@0)' # DEREF_NEED_BRACE check_error 'p vfs_read ^+0@0)' # DEREF_NEED_BRACE
check_error 'p vfs_read ^+0ab1(@0)' # BAD_DEREF_OFFS check_error 'p vfs_read ^+0ab1(@0)' # BAD_DEREF_OFFS
check_error 'p vfs_read +0(+0(@0^)' # DEREF_OPEN_BRACE check_error 'p vfs_read +0(+0(@0^)' # DEREF_OPEN_BRACE
...@@ -82,4 +87,15 @@ case $(uname -m) in ...@@ -82,4 +87,15 @@ case $(uname -m) in
;; ;;
esac esac
# multiprobe errors
if grep -q "Create/append/" README && grep -q "imm-value" README; then
echo 'p:kprobes/testevent _do_fork' > kprobe_events
check_error '^r:kprobes/testevent do_exit' # DIFF_PROBE_TYPE
echo 'p:kprobes/testevent _do_fork abcd=\1' > kprobe_events
check_error 'p:kprobes/testevent _do_fork ^bcd=\1' # DIFF_ARG_TYPE
check_error 'p:kprobes/testevent _do_fork ^abcd=\1:u8' # DIFF_ARG_TYPE
check_error 'p:kprobes/testevent _do_fork ^abcd=\"foo"' # DIFF_ARG_TYPE
check_error '^p:kprobes/testevent _do_fork' # SAME_PROBE
fi
exit 0 exit 0
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment