summaryrefslogtreecommitdiff
path: root/kernel/trace
diff options
context:
space:
mode:
Diffstat (limited to 'kernel/trace')
-rw-r--r--kernel/trace/fgraph.c3
-rw-r--r--kernel/trace/ftrace.c2
-rw-r--r--kernel/trace/rethook.c2
-rw-r--r--kernel/trace/ring_buffer.c23
-rw-r--r--kernel/trace/trace.c46
-rw-r--r--kernel/trace/trace.h2
-rw-r--r--kernel/trace/trace_events.c2
-rw-r--r--kernel/trace/trace_events_hist.c86
-rw-r--r--kernel/trace/trace_events_trigger.c4
-rw-r--r--kernel/trace/trace_events_user.c26
-rw-r--r--kernel/trace/trace_functions_graph.c3
-rw-r--r--kernel/trace/trace_remote.c6
12 files changed, 142 insertions, 63 deletions
diff --git a/kernel/trace/fgraph.c b/kernel/trace/fgraph.c
index 40d373d65f9b..ed455b53513b 100644
--- a/kernel/trace/fgraph.c
+++ b/kernel/trace/fgraph.c
@@ -143,9 +143,6 @@ enum {
#define FGRAPH_DATA_INDEX_MASK GENMASK(FGRAPH_DATA_INDEX_BITS - 1, 0)
#define FGRAPH_DATA_INDEX_SHIFT (FGRAPH_DATA_SHIFT + FGRAPH_DATA_BITS)
-#define FGRAPH_MAX_INDEX \
- ((FGRAPH_INDEX_SIZE << FGRAPH_DATA_BITS) + FGRAPH_RET_INDEX)
-
#define FGRAPH_ARRAY_SIZE FGRAPH_INDEX_BITS
/*
diff --git a/kernel/trace/ftrace.c b/kernel/trace/ftrace.c
index 53d5db60bfa5..673a54fdf392 100644
--- a/kernel/trace/ftrace.c
+++ b/kernel/trace/ftrace.c
@@ -6675,7 +6675,7 @@ int update_ftrace_direct_mod(struct ftrace_ops *ops, struct ftrace_hash *hash, b
/* Enable the tmp_ops to have the same functions as the hash object. */
ftrace_ops_init(&tmp_ops);
- tmp_ops.func_hash->filter_hash = hash;
+ rcu_assign_pointer(tmp_ops.func_hash->filter_hash, hash);
err = register_ftrace_function_nolock(&tmp_ops);
if (err)
diff --git a/kernel/trace/rethook.c b/kernel/trace/rethook.c
index 5a8bdf88999a..87a27f3aa4a6 100644
--- a/kernel/trace/rethook.c
+++ b/kernel/trace/rethook.c
@@ -171,7 +171,7 @@ struct rethook_node *rethook_try_get(struct rethook *rh)
* This expects the caller will set up a rethook on a function entry.
* When the function returns, the rethook will eventually be reclaimed
* or released in the rethook_recycle() with call_rcu().
- * This means the caller must be run in the RCU-availabe context.
+ * This means the caller must be run in the RCU-available context.
*/
if (unlikely(!rcu_is_watching()))
return NULL;
diff --git a/kernel/trace/ring_buffer.c b/kernel/trace/ring_buffer.c
index 9c03a555a6ba..04bb94c29f58 100644
--- a/kernel/trace/ring_buffer.c
+++ b/kernel/trace/ring_buffer.c
@@ -904,14 +904,13 @@ static void rb_wake_up_waiters(struct irq_work *work)
struct ring_buffer_per_cpu *cpu_buffer =
container_of(rbwork, struct ring_buffer_per_cpu, irq_work);
- /* Called from interrupt context */
- raw_spin_lock(&cpu_buffer->reader_lock);
- rbwork->wakeup_full = false;
- rbwork->full_waiters_pending = false;
+ scoped_guard(raw_spinlock_irqsave, &cpu_buffer->reader_lock) {
+ rbwork->wakeup_full = false;
+ rbwork->full_waiters_pending = false;
- /* Waking up all waiters, they will reset the shortest full */
- cpu_buffer->shortest_full = 0;
- raw_spin_unlock(&cpu_buffer->reader_lock);
+ /* Waking up all waiters, they will reset the shortest full */
+ cpu_buffer->shortest_full = 0;
+ }
wake_up_all(&rbwork->full_waiters);
}
@@ -7384,7 +7383,7 @@ EXPORT_SYMBOL_GPL(ring_buffer_read_page_data);
/**
* ring_buffer_read_page_size - get size of the read page.
- * @page: the page to get the size from
+ * @rpage: the page to get the size from
*
* Returns size of the page in bytes.
*/
@@ -7474,6 +7473,14 @@ int ring_buffer_subbuf_order_set(struct trace_buffer *buffer, int order)
old_capacity = rb_subbuf_capacity(buffer);
+ /* The mmap fast path reads subbuf_order without buffer->mutex. */
+ for_each_buffer_cpu(buffer, cpu) {
+ if (!cpumask_test_cpu(cpu, buffer->cpumask))
+ continue;
+ if (atomic_read(&buffer->buffers[cpu]->resize_disabled))
+ return -EBUSY;
+ }
+
atomic_inc(&buffer->record_disabled);
/* Make sure all commits have finished */
diff --git a/kernel/trace/trace.c b/kernel/trace/trace.c
index 8658cad53cb5..e4a490d3d08c 100644
--- a/kernel/trace/trace.c
+++ b/kernel/trace/trace.c
@@ -7717,12 +7717,55 @@ trace_options_write(struct file *filp, const char __user *ubuf, size_t cnt,
return cnt;
}
+static bool tr_option_match(struct trace_array *tr, void *topt)
+{
+ for (int i = 0; i < tr->nr_topts; i++) {
+ struct trace_options *tr_topts = &tr->topts[i];
+
+ if (topt >= (void *)&tr_topts->topts[0] &&
+ topt < (void *)&tr_topts->topts[tr_topts->nr_topts])
+ return true;
+ }
+ return false;
+}
+
+/*
+ * The topt is the address of a trace_array->topts[] element that holds the
+ * the tracer options descriptor. But since the trace_array reference has not
+ * been taken yet, it cannot be dereferenced as it could have been freed by
+ * a rmdir of the instance the trace_array represents.
+ *
+ * Search the list of trace_arrays and compare the topt to the address of
+ * the entire trace_array topts array for each trace_array in the list.
+ * If one is matched, then take the reference and return it. If not, the
+ * trace_array no longer exits.
+ */
+static int trace_array_tracer_options_get(void *topt)
+{
+ struct trace_array *tr;
+ int ret;
+
+ ret = security_locked_down(LOCKDOWN_TRACEFS);
+ if (ret)
+ return ret;
+
+ if (tracing_disabled)
+ return -ENODEV;
+
+ guard(mutex)(&trace_types_lock);
+ list_for_each_entry(tr, &ftrace_trace_arrays, list) {
+ if (tr_option_match(tr, topt))
+ return __trace_array_get(tr);
+ }
+ return -ENODEV;
+}
+
static int tracing_open_options(struct inode *inode, struct file *filp)
{
struct trace_option_dentry *topt = inode->i_private;
int ret;
- ret = tracing_check_open_get_tr(topt->tr);
+ ret = trace_array_tracer_options_get(topt);
if (ret)
return ret;
@@ -7984,6 +8027,7 @@ create_trace_option_files(struct trace_array *tr, struct tracer *tracer,
tr->topts = tr_topts;
tr->topts[tr->nr_topts].tracer = tracer;
tr->topts[tr->nr_topts].topts = topts;
+ tr->topts[tr->nr_topts].nr_topts = cnt;
tr->nr_topts++;
for (cnt = 0; opts[cnt].name; cnt++) {
diff --git a/kernel/trace/trace.h b/kernel/trace/trace.h
index 5e76f94e7a80..3749485a7d85 100644
--- a/kernel/trace/trace.h
+++ b/kernel/trace/trace.h
@@ -227,6 +227,7 @@ struct array_buffer {
struct trace_options {
struct tracer *tracer;
struct trace_option_dentry *topts;
+ int nr_topts;
};
struct trace_pid_list *trace_pid_list_alloc(void);
@@ -1952,6 +1953,7 @@ struct event_trigger_data {
struct enable_trigger_data {
struct trace_event_file *file;
+ struct trace_event_call *call;
bool enable;
bool hist;
};
diff --git a/kernel/trace/trace_events.c b/kernel/trace/trace_events.c
index 9dbc2441763b..30c0ddf90887 100644
--- a/kernel/trace/trace_events.c
+++ b/kernel/trace/trace_events.c
@@ -1489,6 +1489,8 @@ int ftrace_set_clr_event(struct trace_array *tr, char *buf, int set)
/* Put back the colon to allow this to be called again */
if (buf)
*(buf - 1) = ':';
+ if (mod)
+ *(mod - 5) = ':';
return ret;
}
diff --git a/kernel/trace/trace_events_hist.c b/kernel/trace/trace_events_hist.c
index 963e0d6b61fd..8af97fd4ee2d 100644
--- a/kernel/trace/trace_events_hist.c
+++ b/kernel/trace/trace_events_hist.c
@@ -169,7 +169,6 @@ struct hist_field {
struct hist_field *operands[HIST_FIELD_OPERANDS_MAX];
struct hist_trigger_data *hist_data;
enum hist_field_fn fn_num;
- unsigned int ref;
unsigned int size;
unsigned int offset;
unsigned int is_signed;
@@ -1913,16 +1912,8 @@ out:
return field_op;
}
-static void get_hist_field(struct hist_field *hist_field)
-{
- hist_field->ref++;
-}
-
static void __destroy_hist_field(struct hist_field *hist_field)
{
- if (--hist_field->ref > 1)
- return;
-
kfree(hist_field->var.name);
kfree(hist_field->name);
@@ -1969,8 +1960,6 @@ static struct hist_field *create_hist_field(struct hist_trigger_data *hist_data,
if (!hist_field)
return NULL;
- hist_field->ref = 1;
-
hist_field->hist_data = hist_data;
if (flags & HIST_FIELD_FL_EXPR || flags & HIST_FIELD_FL_ALIAS)
@@ -2223,10 +2212,8 @@ static struct hist_field *create_var_ref(struct hist_trigger_data *hist_data,
for (i = 0; i < hist_data->n_var_refs; i++) {
ref_field = hist_data->var_refs[i];
if (ref_field->var.idx == var_field->var.idx &&
- ref_field->var.hist_data == var_field->hist_data) {
- get_hist_field(ref_field);
+ ref_field->var.hist_data == var_field->hist_data)
return ref_field;
- }
}
/* Sanity check to avoid out-of-bound write on 'hist_data->var_refs' */
if (hist_data->n_var_refs >= TRACING_MAP_VARS_MAX)
@@ -2234,7 +2221,7 @@ static struct hist_field *create_var_ref(struct hist_trigger_data *hist_data,
ref_field = create_hist_field(var_field->hist_data, NULL, flags, NULL);
if (ref_field) {
if (init_var_ref(ref_field, var_field, system, event_name)) {
- destroy_hist_field(ref_field, 0);
+ __destroy_hist_field(ref_field);
return NULL;
}
@@ -2330,6 +2317,7 @@ parse_field(struct hist_trigger_data *hist_data, struct trace_event_file *file,
struct ftrace_event_field *field = NULL;
char *field_name, *modifier, *str;
struct trace_array *tr = file->tr;
+ bool stack_modifier = false;
modifier = str = kstrdup(field_str, GFP_KERNEL);
if (!modifier)
@@ -2352,9 +2340,10 @@ parse_field(struct hist_trigger_data *hist_data, struct trace_event_file *file,
*flags |= HIST_FIELD_FL_EXECNAME;
else if (strcmp(modifier, "syscall") == 0)
*flags |= HIST_FIELD_FL_SYSCALL;
- else if (strcmp(modifier, "stacktrace") == 0)
+ else if (strcmp(modifier, "stacktrace") == 0) {
*flags |= HIST_FIELD_FL_STACKTRACE;
- else if (strcmp(modifier, "log2") == 0)
+ stack_modifier = true;
+ } else if (strcmp(modifier, "log2") == 0)
*flags |= HIST_FIELD_FL_LOG2;
else if (strcmp(modifier, "usecs") == 0)
*flags |= HIST_FIELD_FL_TIMESTAMP_USECS;
@@ -2415,6 +2404,7 @@ parse_field(struct hist_trigger_data *hist_data, struct trace_event_file *file,
*flags |= HIST_FIELD_FL_CPU;
} else if (field && field->filter_type == FILTER_STACKTRACE) {
*flags |= HIST_FIELD_FL_STACKTRACE;
+ field = NULL;
} else if (field && field->filter_type == FILTER_COMM) {
*flags |= HIST_FIELD_FL_COMM | HIST_FIELD_FL_STRING;
} else {
@@ -2425,6 +2415,12 @@ parse_field(struct hist_trigger_data *hist_data, struct trace_event_file *file,
}
}
}
+
+ if (stack_modifier &&
+ (!field || field->filter_type != FILTER_STACKTRACE)) {
+ hist_err(tr, HIST_ERR_BAD_FIELD_MODIFIER, errpos(field_str));
+ field = ERR_PTR(-EINVAL);
+ }
out:
kfree(str);
@@ -3276,7 +3272,6 @@ static struct hist_field *create_var(struct hist_trigger_data *hist_data,
goto out;
}
- var->ref = 1;
var->flags = HIST_FIELD_FL_VAR;
var->var.idx = idx;
var->var.hist_data = var->hist_data = hist_data;
@@ -4313,8 +4308,7 @@ static int __create_val_field(struct hist_trigger_data *hist_data,
goto err;
} else {
/* Value */
- if (hist_field->flags & (HIST_FIELD_FL_GRAPH | HIST_FIELD_FL_PERCENT |
- HIST_FIELD_FL_BUCKET | HIST_FIELD_FL_LOG2 |
+ if (hist_field->flags & (HIST_FIELD_FL_BUCKET | HIST_FIELD_FL_LOG2 |
HIST_FIELD_FL_SYM | HIST_FIELD_FL_SYM_OFFSET |
HIST_FIELD_FL_SYSCALL | HIST_FIELD_FL_STACKTRACE))
goto err;
@@ -4331,6 +4325,7 @@ static int __create_val_field(struct hist_trigger_data *hist_data,
return ret;
err:
hist_err(file->tr, HIST_ERR_BAD_FIELD_MODIFIER, errpos(field_str));
+ destroy_hist_field(hist_field, 0);
return -EINVAL;
}
@@ -5690,7 +5685,7 @@ static int print_entries(struct seq_file *m,
{
struct tracing_map_sort_entry **sort_entries = NULL;
struct tracing_map *map = hist_data->map;
- int i, j, n_entries;
+ int i, j, n_entries, ret;
struct hist_val_stat *stats = NULL;
u64 val;
@@ -5700,6 +5695,8 @@ static int print_entries(struct seq_file *m,
if (n_entries < 0)
return n_entries;
+ ret = n_entries;
+
/* Calculate the max and the total for each field if needed. */
for (j = 0; j < hist_data->n_vals; j++) {
if (!(hist_data->fields[j]->flags &
@@ -5708,7 +5705,7 @@ static int print_entries(struct seq_file *m,
if (!stats) {
stats = kzalloc_objs(*stats, hist_data->n_vals);
if (!stats) {
- n_entries = -ENOMEM;
+ ret = -ENOMEM;
goto out;
}
}
@@ -5729,7 +5726,7 @@ static int print_entries(struct seq_file *m,
out:
tracing_map_destroy_sort_entries(sort_entries, n_entries);
- return n_entries;
+ return ret;
}
static void hist_trigger_show(struct seq_file *m,
@@ -6383,17 +6380,18 @@ static int event_hist_trigger_named_init(struct event_trigger_data *data)
{
int ret;
- data->ref++;
-
- save_named_trigger(data->named_data->name, data);
-
ret = event_hist_trigger_init(data->named_data);
if (ret < 0) {
kfree(data->cmd_ops);
data->cmd_ops = &trigger_hist_cmd;
+ return ret;
}
- return ret;
+ data->ref++;
+
+ save_named_trigger(data->named_data->name, data);
+
+ return 0;
}
static void event_hist_trigger_named_free(struct event_trigger_data *data)
@@ -6643,12 +6641,6 @@ static int hist_register_trigger(char *glob,
data->cmd_ops = cmd_ops;
}
- if (data->cmd_ops->init) {
- ret = data->cmd_ops->init(data);
- if (ret < 0)
- goto out;
- }
-
if (hist_data->enable_timestamps) {
char *clock = hist_data->attrs->clock;
@@ -6661,6 +6653,15 @@ static int hist_register_trigger(char *glob,
tracing_set_filter_buffering(file->tr, true);
}
+ if (data->cmd_ops->init) {
+ ret = data->cmd_ops->init(data);
+ if (ret < 0) {
+ if (hist_data->enable_timestamps)
+ tracing_set_filter_buffering(file->tr, false);
+ goto out;
+ }
+ }
+
if (named_data) {
remove_hist_vars(hist_data);
destroy_hist_data(hist_data);
@@ -6678,11 +6679,12 @@ static int hist_trigger_enable(struct event_trigger_data *data,
update_cond_flag(file);
- if (trace_event_trigger_enable_disable(file, 1) < 0) {
- list_del_rcu(&data->list);
- update_cond_flag(file);
+ /*
+ * On failure the caller undoes the registration, and
+ * hist_unregister_trigger() can only find the trigger here.
+ */
+ if (trace_event_trigger_enable_disable(file, 1) < 0)
ret--;
- }
return ret;
}
@@ -6760,13 +6762,13 @@ static void hist_unregister_trigger(char *glob,
}
}
- if (test && test->cmd_ops->free)
- test->cmd_ops->free(test);
-
if (hist_data->enable_timestamps) {
if (!hist_data->remove || test)
tracing_set_filter_buffering(file->tr, false);
}
+
+ if (test && test->cmd_ops->free)
+ test->cmd_ops->free(test);
}
static bool hist_file_check_refs(struct trace_event_file *file)
@@ -6971,6 +6973,8 @@ static int event_hist_trigger_parse(struct event_command *cmd_ops,
return ret;
out_unreg:
event_trigger_unregister(cmd_ops, file, glob+1, trigger_data);
+ /* The unregister frees trigger_data, skip out_free */
+ goto out;
out_free:
remove_hist_vars(hist_data);
diff --git a/kernel/trace/trace_events_trigger.c b/kernel/trace/trace_events_trigger.c
index 149300cc5e8a..4d2fde996c0f 100644
--- a/kernel/trace/trace_events_trigger.c
+++ b/kernel/trace/trace_events_trigger.c
@@ -1728,7 +1728,8 @@ static void enable_trigger_private_data_free(struct event_trigger_data *data)
{
struct enable_trigger_data *enable_data = data->private_data;
- trace_event_put_ref(enable_data->file->event_call);
+ /* The file may already be freed here, only the call is kept alive */
+ trace_event_put_ref(enable_data->call);
kfree(enable_data);
}
@@ -1801,6 +1802,7 @@ int event_enable_trigger_parse(struct event_command *cmd_ops,
enable_data->hist = hist;
enable_data->enable = enable;
enable_data->file = event_enable_file;
+ enable_data->call = event_enable_file->event_call;
trigger_data = trigger_data_alloc(cmd_ops, cmd, param, enable_data);
if (!trigger_data)
diff --git a/kernel/trace/trace_events_user.c b/kernel/trace/trace_events_user.c
index 93cda2f6f269..f658c3a77aa7 100644
--- a/kernel/trace/trace_events_user.c
+++ b/kernel/trace/trace_events_user.c
@@ -1122,10 +1122,9 @@ static void user_event_destroy_validators(struct user_event *user)
}
}
-static void user_event_destroy_fields(struct user_event *user)
+static void user_event_destroy_fields(struct list_head *head)
{
struct ftrace_event_field *field, *next;
- struct list_head *head = &user->fields;
list_for_each_entry_safe(field, next, head, link) {
list_del(&field->link);
@@ -1502,17 +1501,32 @@ static int user_event_set_call_visible(struct user_event *user, bool visible)
static int destroy_user_event(struct user_event *user)
{
+ LIST_HEAD(fields);
int ret = 0;
lockdep_assert_held(&event_mutex);
- /* Must destroy fields before call removal */
- user_event_destroy_fields(user);
+ /*
+ * Detach the fields before removing the call. Removing the event
+ * frees the field list memory (trace_destroy_fields() is run on
+ * successful removal and kmem_cache_free()s the fields), but the
+ * fields here are allocated and owned by user_events. Destroy
+ * them separately once removal has succeeded.
+ */
+ list_splice_init(&user->fields, &fields);
ret = user_event_set_call_visible(user, false);
- if (ret)
+ if (ret) {
+ /*
+ * Removal failed and the event stays registered, recover
+ * the fields so it is left in a consistent state.
+ */
+ list_splice(&fields, &user->fields);
return ret;
+ }
+
+ user_event_destroy_fields(&fields);
dyn_event_remove(&user->devent);
hash_del(&user->node);
@@ -2212,7 +2226,7 @@ static int user_event_parse(struct user_event_group *group, char *name,
put_user_lock:
mutex_unlock(&event_mutex);
put_user:
- user_event_destroy_fields(user);
+ user_event_destroy_fields(&user->fields);
user_event_destroy_validators(user);
kfree(user->call.print_fmt);
diff --git a/kernel/trace/trace_functions_graph.c b/kernel/trace/trace_functions_graph.c
index ff7cb1a76b95..c5befd0c5b9a 100644
--- a/kernel/trace/trace_functions_graph.c
+++ b/kernel/trace/trace_functions_graph.c
@@ -52,6 +52,7 @@ struct fgraph_data {
};
struct ftrace_graph_ret_entry ret;
int failed;
+ int ent_size;
int cpu;
};
@@ -1274,6 +1275,7 @@ print_graph_entry(struct ftrace_graph_ent_entry *field, struct trace_seq *s,
if (s->full) {
data->failed = 1;
data->cpu = cpu;
+ data->ent_size = iter->ent_size;
} else
data->failed = 0;
}
@@ -1457,6 +1459,7 @@ print_graph_function_flags(struct trace_iterator *iter, u32 flags)
if (data && data->failed) {
field = &data->ent.ent;
iter->cpu = data->cpu;
+ iter->ent_size = data->ent_size;
ret = print_graph_entry(field, s, iter, flags);
if (ret == TRACE_TYPE_HANDLED && iter->cpu != cpu) {
per_cpu_ptr(data->cpu_data, iter->cpu)->ignore = 1;
diff --git a/kernel/trace/trace_remote.c b/kernel/trace/trace_remote.c
index 75fa1ffc4c96..2d5bc423efca 100644
--- a/kernel/trace/trace_remote.c
+++ b/kernel/trace/trace_remote.c
@@ -980,9 +980,12 @@ int trace_remote_alloc_buffer(struct trace_buffer_desc *desc, size_t desc_size,
const struct cpumask *cpumask)
{
size_t min_desc_size = trace_buffer_desc_size(buffer_size, cpumask_weight(cpumask));
- unsigned int nr_pages = max(DIV_ROUND_UP(buffer_size, PAGE_SIZE), 2UL) + 1;
struct ring_buffer_desc *rb_desc;
int cpu, ret = -ENOMEM;
+ unsigned int nr_pages;
+
+ if (min_desc_size == SIZE_MAX)
+ return -E2BIG;
if (desc_size < min_desc_size)
return -EINVAL;
@@ -991,6 +994,7 @@ int trace_remote_alloc_buffer(struct trace_buffer_desc *desc, size_t desc_size,
desc->struct_len = min_desc_size;
rb_desc = __first_ring_buffer_desc(desc);
+ nr_pages = __calc_nr_pages_ring_buffer_desc(buffer_size);
for_each_cpu(cpu, cpumask) {
unsigned int id;