tracing updates for v7.4:

- Add "within" to filter logic
 
   Add a way to trace events by what function they fall in.
 
   echo 'within == "vfs_read" > events/sched/sched_switch/filter
 
   Will only trigger sched_switch events that happen within the vfs_read
   function. It uses a stacktrace at the event and checks if any entry is
   within the specified function and if so, it prints the event.
 
 - Remove a duplicate definition of ftrace_set_clr_event() from the header
 
 - Do not have ftrace_set_clr_event() modify its input buffer
 
   Have the buffer passed into ftrace_set_clr_event() to be const char * and
   not allow modifications to it that might cause side effects.
 
 - Add kunit tests for ftrace_parse_event_string()
 
 - Add filter argument to __ftrace_set_clr_event()
 
   Make it possible to set a filter on an event at the same time it is
   enabled.
 
 - Add event filtering logic to the kernel command line
 
   To allow filtering events on boot, allow filtering to be specified on the
   kernel command line when enabling events.
 
 - Rename "ring_buffer_size()" to "ring_buffer_capacity()"
 
   "size" refers to how much is allocated whereas the actual return value is
   the amount of available data in the ring buffer minus its meta data.
 
 - Rename "trace_array_entries" to "trace_array_capacity"
 
   There's several functions that use "entries" and returns the amount of
   data that that is usable. "entries" should be only for the number of
   events, not the amount of data.
 
 - Print the histogram "bucket" size as unsigned.
 
   If a bucket size is LONG_MAX, then it shows up as a negative number.
 
 - Show bucket size in output of histograms
 
   The output of what the histogram is shown doesn't show the size of the
   buckets when buckets are used. The output should be the same as the input
   and since specifying buckets without a size is an invalid command, the
   output to display what was entered should also show the size.
 
 - Fix stacktrace modifier output
 
   Only show the stacktrace modifier when it was specified in creation.
 
 - Unregister filed variable histograms when the command fails
 
   If one histogram references a variable of another histogram it registers
   that other histogram. But if the first histogram fails in setup after it
   registered the other histogram it doesn't unregister it. Have the first
   histogram add the reference to the other histogram so the failure clean up
   will unregister the second histogram too.
 
 - Fix 64-bit signed histogram fields on 32-bit kernels
 
   A fix to convert char * to u64 with added casting also incorrectly added
   an intermediate cast to convert a long value to u64 which ended up
   truncating the upper 32 bits for 32-bit kernels. Remove the intermediate
   typecast.
 
 - Print signed histogram keys as signed
 
   Histogram keys were being printed as unsigned and with signed keys it was
   giving the wrong output.
 
 - Use copy_from_user() and not __copy_from_user() in syscall tracing
 
   The syscall tracing incorrectly was using __copy_from_user() which could
   be an issue if the system trusts perf doing the trace.
 
 - Take trace_types_lock when recording a new module in persistent buffer
 
   When a new module is loaded, the persistent ring buffer saves it in its
   persistent memory. All the trace_arrays are iterated looking for any
   persistent buffer to add the module to. But it did not hold the
   trace_types_lock meaning a trace_array could be added or removed while
   iterating the list.
 
 - Only create tracepoint guest_halt_poll_ns when it is used
 
   The tracepoint guest_halt_poll_ns is defined in the power management code
   but it is only used when CONFIG_CPU_IDLE_GOV_HALTPOLL is enabled. This
   wastes memory when that config isn't enabled so wrap the tracepoint around
   that as well.
 
 - Handle empty fields for events in trace_event_get_offsets()
 
   The user events could have empty fields and this causes
   trace_event_get_offsets() to crash as it expects events to always have
   fields. Make the function handle events with no fields.
 
 - Reject incomplete event filter expressions
 
   If a filter has an incomplete expression (common_pid == 1111 || ), it will
   cause a out-of-bounds bug. Have the predicate_parse() only process the
   expression if it is complete.
 -----BEGIN PGP SIGNATURE-----
 
 iIoEABYKADIWIQRRSw7ePDh/lE+zeZMp5XQQmuv6qgUCatwDfRQccm9zdGVkdEBn
 b29kbWlzLm9yZwAKCRAp5XQQmuv6qk8TAP9buI4fQRvXGetyth9F/j8qGgTAEv19
 sL5B48A/HDx2wgD+O6FcFAwwNKTjJHfHPArJUcQMp/D17rNDFSoF4T0kFQU=
 =F2J6
 -----END PGP SIGNATURE-----

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

Pull tracing updates from Steven Rostedt:

 - Add "within" to filter logic

   Add a way to trace events by what function they fall in.

	echo 'within == "vfs_read" > events/sched/sched_switch/filter

   Will only trigger sched_switch events that happen within the vfs_read
   function. It uses a stacktrace at the event and checks if any entry
   is within the specified function and if so, it prints the event.

 - Remove a duplicate definition of ftrace_set_clr_event() from the
   header

 - Do not have ftrace_set_clr_event() modify its input buffer

   Have the buffer passed into ftrace_set_clr_event() to be const char *
   and not allow modifications to it that might cause side effects.

 - Add kunit tests for ftrace_parse_event_string()

 - Add filter argument to __ftrace_set_clr_event()

   Make it possible to set a filter on an event at the same time it is
   enabled.

 - Add event filtering logic to the kernel command line

   To allow filtering events on boot, allow filtering to be specified on
   the kernel command line when enabling events.

 - Rename "ring_buffer_size()" to "ring_buffer_capacity()"

   "size" refers to how much is allocated whereas the actual return
   value is the amount of available data in the ring buffer minus its
   meta data.

 - Rename "trace_array_entries" to "trace_array_capacity"

   There's several functions that use "entries" and returns the amount
   of data that that is usable. "entries" should be only for the number
   of events, not the amount of data.

 - Print the histogram "bucket" size as unsigned.

   If a bucket size is LONG_MAX, then it shows up as a negative number.

 - Show bucket size in output of histograms

   The output of what the histogram is shown doesn't show the size of
   the buckets when buckets are used. The output should be the same as
   the input and since specifying buckets without a size is an invalid
   command, the output to display what was entered should also show the
   size.

 - Fix stacktrace modifier output

   Only show the stacktrace modifier when it was specified in creation.

 - Unregister filed variable histograms when the command fails

   If one histogram references a variable of another histogram it
   registers that other histogram. But if the first histogram fails in
   setup after it registered the other histogram it doesn't unregister
   it. Have the first histogram add the reference to the other histogram
   so the failure clean up will unregister the second histogram too.

 - Fix 64-bit signed histogram fields on 32-bit kernels

   A fix to convert char * to u64 with added casting also incorrectly
   added an intermediate cast to convert a long value to u64 which ended
   up truncating the upper 32 bits for 32-bit kernels. Remove the
   intermediate typecast.

 - Print signed histogram keys as signed

   Histogram keys were being printed as unsigned and with signed keys it
   was giving the wrong output.

 - Use copy_from_user() and not __copy_from_user() in syscall tracing

   The syscall tracing incorrectly was using __copy_from_user() which
   could be an issue if the system trusts perf doing the trace.

 - Take trace_types_lock when recording a new module in persistent
   buffer

   When a new module is loaded, the persistent ring buffer saves it in
   its persistent memory. All the trace_arrays are iterated looking for
   any persistent buffer to add the module to. But it did not hold the
   trace_types_lock meaning a trace_array could be added or removed
   while iterating the list.

 - Only create tracepoint guest_halt_poll_ns when it is used

   The tracepoint guest_halt_poll_ns is defined in the power management
   code but it is only used when CONFIG_CPU_IDLE_GOV_HALTPOLL is
   enabled. This wastes memory when that config isn't enabled so wrap
   the tracepoint around that as well.

 - Handle empty fields for events in trace_event_get_offsets()

   The user events could have empty fields and this causes
   trace_event_get_offsets() to crash as it expects events to always
   have fields. Make the function handle events with no fields.

 - Reject incomplete event filter expressions

   If a filter has an incomplete expression (common_pid == 1111 || ), it
   will cause a out-of-bounds bug. Have the predicate_parse() only
   process the expression if it is complete.

* tag 'trace-v7.4' of git://git.kernel.org/pub/scm/linux/kernel/git/trace/linux-trace: (27 commits)
  tracing: Reject incomplete event filter expressions
  tracing: Guard trace_event_get_offsets() against an empty fields list
  cpuidle: haltpoll: Only define guest_halt_poll_ns tracepoint when used
  tracing/osnoise: Add __printf attribute to osnoise_print()
  tracing/histograms: Verify event_call identity when updating synth_event refcount
  tracing: Save the event file instead of the compatible histogram
  tracing: Hold trace_types_lock when resetting last boot data from readers
  tracing: Hold trace_types_lock when updating modules in persistent instances
  tracing/syscalls: Use copy_from_user for the faultable syscall buffer
  tracing: Print a signed histogram key as signed
  tracing: Fix 64-bit and signed histogram fields on 32-bit kernels
  tracing: Clamp the printed end of a histogram bucket at U64_MAX
  tracing: Unregister field variable histograms when the command fails
  tracing: Only report the stacktrace modifier on a real field
  tracing: Add the bucket size to expr_field_str()
  tracing: Print the bucket size as unsigned
  tracing: Rename tracing_entries_fops to tracing_capacity_fops
  tracing: Rename trace_array::entries to capacity
  ring-buffer: Rename ring_buffer_size() to ring_buffer_capacity()
  tracing: Parse filter from boot event string
  ...
This commit is contained in:
Linus Torvalds 2026-10-27 15:01:12 -10:00
commit 59a02493f3
22 changed files with 439 additions and 122 deletions

View File

@ -7748,8 +7748,13 @@ Kernel parameters
trace_event=:mod:<module>
The value before :mod: will only enable specific events
that are part of the module. See the above mentioned
document for more information.
that are part of the module.
To apply a filter, use the :filter: keyword:
trace_event=...:filter:<filter>
See the above mentioned document for more information.
trace_instance=[instance-info]
[FTRACE] Create a ring buffer instance early in boot up.

View File

@ -118,6 +118,11 @@ In order to facilitate early boot debugging, use boot option::
event-list is a comma separated list of events. See section 2.1 for event
format.
The boot option also supports the ``:filter:`` keyword to apply a filter.
The ``:filter:`` keyword needs to be at the end of the event specification::
<system>:<event>:mod:<module>:filter:<filter>
3. Defining an event-enabled tracepoint
=======================================
@ -243,6 +248,18 @@ the function "security_prepare_creds" and less than the end of that function.
The ".function" postfix can only be attached to values of size long, and can only
be compared with "==" or "!=".
The special field "within" can be used to filter events based on whether
a specific function appears in the current call stack::
within == "function_name"
within != "function_name"
For example, to only trace events where "vfs_read" is in the call stack::
# echo 'within == "vfs_read"' > events/sched/sched_switch/filter
The within field supports only the "==" and "!=" operators.
Cpumask fields or scalar fields that encode a CPU number can be filtered using
a user-provided cpumask in cpulist format. The format is as follows::

View File

@ -1943,6 +1943,9 @@ the ".buckets" modifier and specify a size (in this case groups of 10)::
Entries: 16
Dropped: 0
Keys are 64-bit, so the group holding the largest values is truncated where
that range ends and can be smaller than the size asked for.
To save stacktraces, create a synthetic event with a field of type "unsigned long[]"
or even just "long[]". For example, to see how long a task is blocked in an
uninterruptible state::

View File

@ -173,7 +173,7 @@ void ring_buffer_iter_reset(struct ring_buffer_iter *iter);
int ring_buffer_iter_empty(struct ring_buffer_iter *iter);
bool ring_buffer_iter_dropped(struct ring_buffer_iter *iter);
unsigned long ring_buffer_size(struct trace_buffer *buffer, int cpu);
unsigned long ring_buffer_capacity(struct trace_buffer *buffer, int cpu);
unsigned long ring_buffer_max_event_size(struct trace_buffer *buffer);
void ring_buffer_reset_cpu(struct trace_buffer *buffer, int cpu);

View File

@ -872,6 +872,7 @@ enum {
FILTER_COMM,
FILTER_CPU,
FILTER_STACKTRACE,
FILTER_WITHIN,
};
extern int trace_event_raw_init(struct trace_event_call *call);
@ -882,7 +883,7 @@ extern int trace_add_event_call(struct trace_event_call *call);
extern int trace_remove_event_call(struct trace_event_call *call);
extern int trace_event_get_offsets(struct trace_event_call *call);
int ftrace_set_clr_event(struct trace_array *tr, char *buf, int set);
int ftrace_set_clr_event(struct trace_array *tr, const char *buf, int set, bool handle_filter);
int trace_set_clr_event(const char *system, const char *event, int set);
int trace_array_set_clr_event(struct trace_array *tr, const char *system,
const char *event, bool enable);

View File

@ -497,6 +497,7 @@ DEFINE_EVENT(dev_pm_qos_request, dev_pm_qos_remove_request,
TP_ARGS(name, type, new_value)
);
#ifdef CONFIG_CPU_IDLE_GOV_HALTPOLL
TRACE_EVENT(guest_halt_poll_ns,
TP_PROTO(bool grow, unsigned int new, unsigned int old),
@ -525,6 +526,8 @@ TRACE_EVENT(guest_halt_poll_ns,
trace_guest_halt_poll_ns(true, new, old)
#define trace_guest_halt_poll_ns_shrink(new, old) \
trace_guest_halt_poll_ns(false, new, old)
#endif /* CONFIG_CPU_IDLE_GOV_HALTPOLL */
#endif /* _TRACE_POWER_H */
/* This part must be outside protection */

View File

@ -151,6 +151,14 @@ config EVENT_TRACING
select GLOB
bool
config EVENT_TRACING_TEST
tristate "Test for event tracing" if !KUNIT_ALL_TESTS
depends on EVENT_TRACING
depends on KUNIT
default KUNIT_ALL_TESTS
help
KUnit test for the event tracing implementation.
config CONTEXT_SWITCH_TRACER
bool

View File

@ -96,6 +96,7 @@ obj-$(CONFIG_EVENT_TRACING) += blktrace.o
endif
obj-$(CONFIG_EVENT_TRACING) += trace_events.o
obj-$(CONFIG_EVENT_TRACING) += trace_export.o
obj-$(CONFIG_EVENT_TRACING_TEST) += trace_events_test.o
obj-$(CONFIG_FTRACE_SYSCALLS) += trace_syscalls.o
ifeq ($(CONFIG_PERF_EVENTS),y)
obj-$(CONFIG_EVENT_TRACING) += trace_event_perf.o

View File

@ -6548,18 +6548,23 @@ void ring_buffer_iter_advance(struct ring_buffer_iter *iter)
EXPORT_SYMBOL_GPL(ring_buffer_iter_advance);
/**
* ring_buffer_size - return the size of the ring buffer (in bytes)
* ring_buffer_capacity - return the capacity of the ring buffer (in bytes)
* @buffer: The ring buffer.
* @cpu: The CPU to get ring buffer size from.
* @cpu: The CPU to get ring buffer capacity from.
*
* Capacity is the total memory available for recording event data in the
* ring buffer. It excludes subbuffer headers.
*
* Returns the capacity in bytes, or 0 if the CPU is not set in the buffer mask.
*/
unsigned long ring_buffer_size(struct trace_buffer *buffer, int cpu)
unsigned long ring_buffer_capacity(struct trace_buffer *buffer, int cpu)
{
if (!cpumask_test_cpu(cpu, buffer->cpumask))
return 0;
return rb_subbuf_capacity(buffer) * buffer->buffers[cpu]->nr_pages;
}
EXPORT_SYMBOL_GPL(ring_buffer_size);
EXPORT_SYMBOL_GPL(ring_buffer_capacity);
/**
* ring_buffer_max_event_size - return the max data size of an event

View File

@ -3614,6 +3614,7 @@ static int tracing_single_release_tr(struct inode *inode, struct file *file)
return single_release(inode, file);
}
static bool __update_last_data_if_empty(struct trace_array *tr);
static bool update_last_data_if_empty(struct trace_array *tr);
static int tracing_open(struct inode *inode, struct file *file)
@ -4708,24 +4709,24 @@ tracing_set_trace_read(struct file *filp, char __user *ubuf,
int tracer_init(struct tracer *t, struct trace_array *tr)
{
tracing_reset_online_cpus(&tr->array_buffer);
update_last_data_if_empty(tr);
__update_last_data_if_empty(tr);
return t->init(tr);
}
void trace_set_buffer_entries(struct array_buffer *buf, unsigned long val)
void trace_set_buffer_capacity(struct array_buffer *buf, unsigned long val)
{
int cpu;
for_each_tracing_cpu(cpu)
per_cpu_ptr(buf->data, cpu)->entries = val;
per_cpu_ptr(buf->data, cpu)->capacity = val;
}
static void update_buffer_entries(struct array_buffer *buf, int cpu)
static void update_buffer_capacity(struct array_buffer *buf, int cpu)
{
if (cpu == RING_BUFFER_ALL_CPUS) {
trace_set_buffer_entries(buf, ring_buffer_size(buf->buffer, 0));
trace_set_buffer_capacity(buf, ring_buffer_capacity(buf->buffer, 0));
} else {
per_cpu_ptr(buf->data, cpu)->entries = ring_buffer_size(buf->buffer, cpu);
per_cpu_ptr(buf->data, cpu)->capacity = ring_buffer_capacity(buf->buffer, cpu);
}
}
@ -4781,12 +4782,12 @@ static int __tracing_resize_ring_buffer(struct trace_array *tr,
goto out_start;
}
update_buffer_entries(&tr->snapshot_buffer, cpu);
update_buffer_capacity(&tr->snapshot_buffer, cpu);
out:
#endif /* CONFIG_TRACER_SNAPSHOT */
update_buffer_entries(&tr->array_buffer, cpu);
update_buffer_capacity(&tr->array_buffer, cpu);
out_start:
tracing_start_tr(tr);
return ret;
@ -5387,8 +5388,10 @@ static int tracing_wait_pipe(struct file *filp)
return 1;
}
static bool update_last_data_if_empty(struct trace_array *tr)
static bool __update_last_data_if_empty(struct trace_array *tr)
{
lockdep_assert_held(&trace_types_lock);
if (!(tr->flags & TRACE_ARRAY_FL_LAST_BOOT))
return false;
@ -5403,6 +5406,16 @@ static bool update_last_data_if_empty(struct trace_array *tr)
return true;
}
static bool update_last_data_if_empty(struct trace_array *tr)
{
/* Avoid the lock on the read path unless this is a last boot buffer */
if (!(tr->flags & TRACE_ARRAY_FL_LAST_BOOT))
return false;
guard(mutex)(&trace_types_lock);
return __update_last_data_if_empty(tr);
}
/*
* Consumer reader.
*/
@ -5688,8 +5701,7 @@ tracing_syscall_buf_write(struct file *filp, const char __user *ubuf,
}
static ssize_t
tracing_entries_read(struct file *filp, char __user *ubuf,
size_t cnt, loff_t *ppos)
tracing_capacity_read(struct file *filp, char __user *ubuf, size_t cnt, loff_t *ppos)
{
struct inode *inode = file_inode(filp);
struct trace_array *tr = inode->i_private;
@ -5710,8 +5722,8 @@ tracing_entries_read(struct file *filp, char __user *ubuf,
for_each_tracing_cpu(cpu) {
/* fill in the size from first enabled cpu */
if (size == 0)
size = per_cpu_ptr(tr->array_buffer.data, cpu)->entries;
if (size != per_cpu_ptr(tr->array_buffer.data, cpu)->entries) {
size = per_cpu_ptr(tr->array_buffer.data, cpu)->capacity;
if (size != per_cpu_ptr(tr->array_buffer.data, cpu)->capacity) {
buf_size_same = 0;
break;
}
@ -5727,7 +5739,7 @@ tracing_entries_read(struct file *filp, char __user *ubuf,
} else
r = sprintf(buf, "X\n");
} else
r = sprintf(buf, "%lu\n", per_cpu_ptr(tr->array_buffer.data, cpu)->entries >> 10);
r = sprintf(buf, "%lu\n", per_cpu_ptr(tr->array_buffer.data, cpu)->capacity >> 10);
mutex_unlock(&trace_types_lock);
@ -5736,8 +5748,7 @@ tracing_entries_read(struct file *filp, char __user *ubuf,
}
static ssize_t
tracing_entries_write(struct file *filp, const char __user *ubuf,
size_t cnt, loff_t *ppos)
tracing_capacity_write(struct file *filp, const char __user *ubuf, size_t cnt, loff_t *ppos)
{
struct inode *inode = file_inode(filp);
struct trace_array *tr = inode->i_private;
@ -5764,8 +5775,7 @@ tracing_entries_write(struct file *filp, const char __user *ubuf,
}
static ssize_t
tracing_total_entries_read(struct file *filp, char __user *ubuf,
size_t cnt, loff_t *ppos)
tracing_total_capacity_read(struct file *filp, char __user *ubuf, size_t cnt, loff_t *ppos)
{
struct trace_array *tr = filp->private_data;
char buf[64];
@ -5774,7 +5784,7 @@ tracing_total_entries_read(struct file *filp, char __user *ubuf,
mutex_lock(&trace_types_lock);
for_each_tracing_cpu(cpu) {
size += per_cpu_ptr(tr->array_buffer.data, cpu)->entries >> 10;
size += per_cpu_ptr(tr->array_buffer.data, cpu)->capacity >> 10;
if (!tr->ring_buffer_expanded)
expanded_size += trace_buf_size >> 10;
}
@ -6495,7 +6505,7 @@ int tracing_set_clock(struct trace_array *tr, const char *clockstr)
ring_buffer_set_clock(tr->snapshot_buffer.buffer, trace_clocks[i].func);
tracing_reset_online_cpus(&tr->snapshot_buffer);
#endif
update_last_data_if_empty(tr);
__update_last_data_if_empty(tr);
if (tr->scratch && !(tr->flags & TRACE_ARRAY_FL_LAST_BOOT)) {
struct trace_scratch *tscratch = tr->scratch;
@ -6616,10 +6626,10 @@ static const struct file_operations tracing_pipe_fops = {
.release = tracing_release_pipe,
};
static const struct file_operations tracing_entries_fops = {
static const struct file_operations tracing_capacity_fops = {
.open = tracing_open_generic_tr,
.read = tracing_entries_read,
.write = tracing_entries_write,
.read = tracing_capacity_read,
.write = tracing_capacity_write,
.llseek = generic_file_llseek,
.release = tracing_release_generic_tr,
};
@ -6639,9 +6649,9 @@ static const struct file_operations tracing_buffer_meta_fops = {
.release = tracing_seq_release,
};
static const struct file_operations tracing_total_entries_fops = {
static const struct file_operations tracing_total_capacity_fops = {
.open = tracing_open_generic_tr,
.read = tracing_total_entries_read,
.read = tracing_total_capacity_read,
.llseek = generic_file_llseek,
.release = tracing_release_generic_tr,
};
@ -7669,7 +7679,7 @@ tracing_init_tracefs_percpu(struct trace_array *tr, long cpu)
tr, cpu, &tracing_stats_fops);
trace_create_cpu_file("buffer_size_kb", TRACE_MODE_WRITE, d_cpu,
tr, cpu, &tracing_entries_fops);
tr, cpu, &tracing_capacity_fops);
if (tr->range_addr_start)
trace_create_cpu_file("buffer_meta", TRACE_MODE_READ, d_cpu,
@ -8535,8 +8545,8 @@ int allocate_trace_buffer(struct trace_array *tr, struct array_buffer *buf, int
}
/* Allocate the first page for all buffers */
trace_set_buffer_entries(&tr->array_buffer,
ring_buffer_size(tr->array_buffer.buffer, 0));
trace_set_buffer_capacity(&tr->array_buffer,
ring_buffer_capacity(tr->array_buffer.buffer, 0));
return 0;
}
@ -9014,10 +9024,10 @@ init_tracer_tracefs(struct trace_array *tr, struct dentry *d_tracer)
tr, &tracing_pipe_fops);
trace_create_file("buffer_size_kb", writable_mode, d_tracer,
tr, &tracing_entries_fops);
tr, &tracing_capacity_fops);
trace_create_file("buffer_total_size_kb", TRACE_MODE_READ, d_tracer,
tr, &tracing_total_entries_fops);
tr, &tracing_total_capacity_fops);
trace_create_file("trace_clock", writable_mode, d_tracer, tr,
&trace_clock_fops);
@ -9263,6 +9273,8 @@ static void trace_module_record(struct module *mod, bool add)
struct trace_array *tr;
unsigned long flags;
guard(mutex)(&trace_types_lock);
list_for_each_entry(tr, &ftrace_trace_arrays, list) {
flags = tr->flags & (TRACE_ARRAY_FL_BOOT | TRACE_ARRAY_FL_LAST_BOOT);
/* Update any persistent trace array that has already been started */

View File

@ -191,7 +191,7 @@ struct trace_array;
struct trace_array_cpu {
local_t disabled;
unsigned long entries;
unsigned long capacity;
unsigned long saved_latency;
unsigned long critical_start;
unsigned long critical_end;
@ -731,7 +731,7 @@ ssize_t tracing_nsecs_read(unsigned long *ptr, char __user *ubuf,
ssize_t tracing_nsecs_write(unsigned long *ptr, const char __user *ubuf,
size_t cnt, loff_t *ppos);
void trace_set_buffer_entries(struct array_buffer *buf, unsigned long val);
void trace_set_buffer_capacity(struct array_buffer *buf, unsigned long val);
/*
* Should be used after trace_array_get(), trace_types_lock
@ -1823,7 +1823,8 @@ static inline bool is_string_field(struct ftrace_event_field *field)
field->filter_type == FILTER_RDYN_STRING ||
field->filter_type == FILTER_STATIC_STRING ||
field->filter_type == FILTER_PTR_STRING ||
field->filter_type == FILTER_COMM;
field->filter_type == FILTER_COMM ||
field->filter_type == FILTER_WITHIN;
}
static inline bool is_function_field(struct ftrace_event_field *field)
@ -2286,7 +2287,6 @@ static inline const char *get_syscall_name(int syscall)
void trace_event_init(void);
void trace_event_update_all(struct trace_eval_map **map, int len, struct module *mod);
/* Used from boot time tracer */
extern int ftrace_set_clr_event(struct trace_array *tr, char *buf, int set);
extern int trigger_process_regex(struct trace_event_file *file, char *buff);
#else
static inline void __init trace_event_init(void) { }
@ -2506,3 +2506,8 @@ static inline int rv_init_interface(void)
})
#endif /* _LINUX_KERNEL_TRACE_H */
#if IS_ENABLED(CONFIG_KUNIT)
void ftrace_parse_event_string(char *buf, char **match, char **sub, char **event, char **mod,
bool handle_filter, char **filter);
#endif

View File

@ -94,7 +94,7 @@ trace_boot_enable_events(struct trace_array *tr, struct xbc_node *node)
continue;
}
if (ftrace_set_clr_event(tr, buf, 1) < 0)
if (ftrace_set_clr_event(tr, buf, 1, false) < 0)
pr_err("Failed to enable event: %s\n", p);
}
}

View File

@ -25,6 +25,8 @@
#include <linux/delay.h>
#include <linux/btf.h>
#include <kunit/visibility.h>
#include <trace/events/sched.h>
#include <trace/syscall.h>
@ -201,6 +203,9 @@ static int trace_define_generic_fields(void)
__generic_field(char *, comm, FILTER_COMM);
__generic_field(char *, stacktrace, FILTER_STACKTRACE);
__generic_field(char *, STACKTRACE, FILTER_STACKTRACE);
#ifdef CONFIG_STACKTRACE
__generic_field(char *, within, FILTER_WITHIN);
#endif
return ret;
}
@ -241,6 +246,10 @@ int trace_event_get_offsets(struct trace_event_call *call)
struct list_head *head;
head = trace_get_fields(call);
if (list_empty(head))
return sizeof(struct trace_entry);
/*
* head->next points to the last field with the largest offset,
* since it was added last by trace_define_field()
@ -941,6 +950,7 @@ struct event_mod_load {
char *match;
char *system;
char *event;
char *filter;
};
static void free_event_mod(struct event_mod_load *event_mod)
@ -950,6 +960,7 @@ static void free_event_mod(struct event_mod_load *event_mod)
kfree(event_mod->match);
kfree(event_mod->system);
kfree(event_mod->event);
kfree(event_mod->filter);
kfree(event_mod);
}
@ -991,7 +1002,7 @@ static int remove_cache_mod(struct trace_array *tr, const char *mod,
}
static int cache_mod(struct trace_array *tr, const char *mod, int set,
const char *match, const char *system, const char *event)
const char *match, const char *system, const char *event, const char *filter)
{
struct event_mod_load *event_mod;
@ -1030,6 +1041,12 @@ static int cache_mod(struct trace_array *tr, const char *mod, int set,
goto out_free;
}
if (filter) {
event_mod->filter = kstrdup(filter, GFP_KERNEL);
if (!event_mod->filter)
goto out_free;
}
list_add(&event_mod->list, &tr->mod_events);
return 0;
@ -1042,7 +1059,7 @@ static int cache_mod(struct trace_array *tr, const char *mod, int set,
#else /* CONFIG_MODULES */
static inline void clear_mod_events(struct trace_array *tr) { }
static int cache_mod(struct trace_array *tr, const char *mod, int set,
const char *match, const char *system, const char *event)
const char *match, const char *system, const char *event, const char *filter)
{
return -EINVAL;
}
@ -1351,7 +1368,7 @@ static void remove_event_file_dir(struct trace_event_file *file)
static int
__ftrace_set_clr_event_nolock(struct trace_array *tr, const char *match,
const char *sub, const char *event, int set,
const char *mod)
const char *mod, char *filter)
{
struct trace_event_file *file;
struct trace_event_call *call;
@ -1401,7 +1418,11 @@ __ftrace_set_clr_event_nolock(struct trace_array *tr, const char *match,
if (event && strcmp(event, name) != 0)
continue;
ret = ftrace_event_enable_disable(file, set);
if (filter)
ret = apply_event_filter(file, filter);
if (!filter || !ret)
ret = ftrace_event_enable_disable(file, set);
/*
* Save the first error and return that. Some events
@ -1419,14 +1440,14 @@ __ftrace_set_clr_event_nolock(struct trace_array *tr, const char *match,
* check if the module was loaded. If it wasn't cache it.
*/
if (module && ret == -EINVAL && !eret)
ret = cache_mod(tr, module, set, match, sub, event);
ret = cache_mod(tr, module, set, match, sub, event, filter);
return ret;
}
static int __ftrace_set_clr_event(struct trace_array *tr, const char *match,
const char *sub, const char *event, int set,
const char *mod)
const char *mod, char *filter)
{
int ret;
@ -1434,26 +1455,35 @@ static int __ftrace_set_clr_event(struct trace_array *tr, const char *match,
return -EACCES;
mutex_lock(&event_mutex);
ret = __ftrace_set_clr_event_nolock(tr, match, sub, event, set, mod);
ret = __ftrace_set_clr_event_nolock(tr, match, sub, event, set, mod, filter);
mutex_unlock(&event_mutex);
return ret;
}
int ftrace_set_clr_event(struct trace_array *tr, char *buf, int set)
VISIBLE_IF_KUNIT void
ftrace_parse_event_string(char *buf, char **match, char **sub, char **event, char **mod,
bool handle_filter, char **filter)
{
char *event = NULL, *sub = NULL, *match, *mod;
int ret;
*event = NULL;
*sub = NULL;
*filter = NULL;
if (!tr)
return -ENOENT;
/* event filters can be appended with :filter:<filter> */
if (handle_filter) {
*filter = strstr(buf, ":filter:");
if (*filter) {
**filter = '\0';
*filter += 8;
}
}
/* Modules events can be appended with :mod:<module> */
mod = strstr(buf, ":mod:");
if (mod) {
*mod = '\0';
*mod = strstr(buf, ":mod:");
if (*mod) {
**mod = '\0';
/* move to the module name */
mod += 5;
*mod += 5;
}
/*
@ -1468,31 +1498,38 @@ int ftrace_set_clr_event(struct trace_array *tr, char *buf, int set)
* the name <name> or any event that matches <name>
*/
match = strsep(&buf, ":");
*match = strsep(&buf, ":");
if (buf) {
sub = match;
event = buf;
match = NULL;
*sub = *match;
*event = buf;
*match = NULL;
if (!strlen(sub) || strcmp(sub, "*") == 0)
sub = NULL;
if (!strlen(event) || strcmp(event, "*") == 0)
event = NULL;
} else if (mod) {
if (!strlen(*sub) || strcmp(*sub, "*") == 0)
*sub = NULL;
if (!strlen(*event) || strcmp(*event, "*") == 0)
*event = NULL;
} else if (*mod) {
/* Allow wildcard for no length or star */
if (!strlen(match) || strcmp(match, "*") == 0)
match = NULL;
if (!strlen(*match) || strcmp(*match, "*") == 0)
*match = NULL;
}
}
EXPORT_SYMBOL_IF_KUNIT(ftrace_parse_event_string);
ret = __ftrace_set_clr_event(tr, match, sub, event, set, mod);
int ftrace_set_clr_event(struct trace_array *tr, const char *arg_buf, int set, bool handle_filter)
{
char *event, *sub, *match, *mod, *filter;
/* Put back the colon to allow this to be called again */
if (buf)
*(buf - 1) = ':';
if (mod)
*(mod - 5) = ':';
if (!tr)
return -ENOENT;
return ret;
char *dupped_buf __free(kfree) = kstrdup(arg_buf, GFP_KERNEL);
if (!dupped_buf)
return -ENOMEM;
ftrace_parse_event_string(dupped_buf, &match, &sub, &event, &mod, handle_filter, &filter);
return __ftrace_set_clr_event(tr, match, sub, event, set, mod, filter);
}
/**
@ -1514,7 +1551,7 @@ int trace_set_clr_event(const char *system, const char *event, int set)
if (!tr)
return -ENODEV;
return __ftrace_set_clr_event(tr, NULL, system, event, set, NULL);
return __ftrace_set_clr_event(tr, NULL, system, event, set, NULL, NULL);
}
EXPORT_SYMBOL_GPL(trace_set_clr_event);
@ -1540,7 +1577,7 @@ int trace_array_set_clr_event(struct trace_array *tr, const char *system,
return -ENOENT;
set = (enable == true) ? 1 : 0;
return __ftrace_set_clr_event(tr, NULL, system, event, set, NULL);
return __ftrace_set_clr_event(tr, NULL, system, event, set, NULL, NULL);
}
EXPORT_SYMBOL_GPL(trace_array_set_clr_event);
@ -1574,7 +1611,7 @@ ftrace_event_write(struct file *file, const char __user *ubuf,
if (*parser.buffer == '!')
set = 0;
ret = ftrace_set_clr_event(tr, parser.buffer + !set, set);
ret = ftrace_set_clr_event(tr, parser.buffer + !set, set, false);
if (ret)
goto out_put;
}
@ -2060,7 +2097,7 @@ system_enable_write(struct file *filp, const char __user *ubuf, size_t cnt,
if (system)
name = system->name;
ret = __ftrace_set_clr_event(dir->tr, NULL, name, NULL, val, NULL);
ret = __ftrace_set_clr_event(dir->tr, NULL, name, NULL, val, NULL, NULL);
if (ret)
goto out;
@ -4035,7 +4072,8 @@ static void update_mod_cache(struct trace_array *tr, struct module *mod)
__ftrace_set_clr_event_nolock(tr, event_mod->match,
event_mod->system,
event_mod->event, 1, mod->name);
event_mod->event, 1, mod->name,
event_mod->filter);
free_event_mod(event_mod);
}
}
@ -4809,7 +4847,7 @@ int event_trace_del_tracer(struct trace_array *tr)
__ftrace_clear_event_pids(tr, TRACE_PIDS | TRACE_NO_PIDS);
/* Disable any running events */
__ftrace_set_clr_event_nolock(tr, NULL, NULL, NULL, 0, NULL);
__ftrace_set_clr_event_nolock(tr, NULL, NULL, NULL, 0, NULL, NULL);
/* Make sure no more events are being executed */
tracepoint_synchronize_unregister();
@ -4842,10 +4880,10 @@ static __init void __early_set_events(struct trace_array *tr, char *buf, bool en
while ((token = strsep(&buf, ","))) {
if (*token) {
if (enable) {
if (ftrace_set_clr_event(tr, token, 1))
if (ftrace_set_clr_event(tr, token, 1, true))
pr_warn("Failed to enable trace event: %s\n", token);
} else {
ftrace_set_clr_event(tr, token, 0);
ftrace_set_clr_event(tr, token, 0, true);
}
}
@ -5119,7 +5157,7 @@ static __init void event_trace_self_tests(void)
pr_info("Testing event system %s: ", system->name);
ret = __ftrace_set_clr_event(tr, NULL, system->name, NULL, 1, NULL);
ret = __ftrace_set_clr_event(tr, NULL, system->name, NULL, 1, NULL, NULL);
if (WARN_ON_ONCE(ret)) {
pr_warn("error enabling system %s\n",
system->name);
@ -5128,7 +5166,7 @@ static __init void event_trace_self_tests(void)
event_test_stuff();
ret = __ftrace_set_clr_event(tr, NULL, system->name, NULL, 0, NULL);
ret = __ftrace_set_clr_event(tr, NULL, system->name, NULL, 0, NULL, NULL);
if (WARN_ON_ONCE(ret)) {
pr_warn("error disabling system %s\n",
system->name);
@ -5143,7 +5181,7 @@ static __init void event_trace_self_tests(void)
pr_info("Running tests on all trace events:\n");
pr_info("Testing all events: ");
ret = __ftrace_set_clr_event(tr, NULL, NULL, NULL, 1, NULL);
ret = __ftrace_set_clr_event(tr, NULL, NULL, NULL, 1, NULL, NULL);
if (WARN_ON_ONCE(ret)) {
pr_warn("error enabling all events\n");
return;
@ -5152,7 +5190,7 @@ static __init void event_trace_self_tests(void)
event_test_stuff();
/* reset sysname */
ret = __ftrace_set_clr_event(tr, NULL, NULL, NULL, 0, NULL);
ret = __ftrace_set_clr_event(tr, NULL, NULL, NULL, 0, NULL, NULL);
if (WARN_ON_ONCE(ret)) {
pr_warn("error disabling all events\n");
return;

View File

@ -72,6 +72,7 @@ enum filter_pred_fn {
FILTER_PRED_FN_CPUMASK,
FILTER_PRED_FN_CPUMASK_CPU,
FILTER_PRED_FN_FUNCTION,
FILTER_PRED_FN_WITHIN,
FILTER_PRED_FN_,
FILTER_PRED_TEST_VISITED,
};
@ -117,6 +118,7 @@ struct filter_pred {
C(IP_FIELD_ONLY, "Only 'ip' field is supported for function trace"), \
C(INVALID_VALUE, "Invalid value (did you forget quotes)?"), \
C(NO_FUNCTION, "Function not found"), \
C(GLOB_WITHIN, "'within' filter cannot take glob expressions"), \
C(ERRNO, "Error"), \
C(NO_FILTER, "No filter found")
@ -595,6 +597,12 @@ predicate_parse(const char *str, int nr_parens, int nr_preds,
top--; /* #12 */
}
}
/* Only the inner loop can complete a non-empty expression. */
if (N) {
ret = -EINVAL;
parse_error(pe, FILT_ERR_INVALID_FILTER, ptr - str);
goto out_free;
}
out:
if (top != op_stack) {
/* Too many '(' */
@ -1008,6 +1016,22 @@ static int filter_pred_function(struct filter_pred *pred, void *event)
return pred->op == OP_EQ ? ret : !ret;
}
/* Filter predicate for within. */
static int filter_pred_within(struct filter_pred *pred, void *event)
{
#ifdef CONFIG_STACKTRACE
unsigned long entries[16];
unsigned int nr_entries;
int i;
nr_entries = stack_trace_save(entries, ARRAY_SIZE(entries), 0);
for (i = 0; i < nr_entries; i++)
if (pred->val <= entries[i] && entries[i] < pred->val2)
return !pred->not;
#endif
return pred->not;
}
/*
* regex_match_foo - Basic regex callbacks
*
@ -1617,6 +1641,8 @@ static int filter_pred_fn_call(struct filter_pred *pred, void *event)
return filter_pred_cpumask_cpu(pred, event);
case FILTER_PRED_FN_FUNCTION:
return filter_pred_function(pred, event);
case FILTER_PRED_FN_WITHIN:
return filter_pred_within(pred, event);
case FILTER_PRED_TEST_VISITED:
return test_pred_visited_fn(pred, event);
default:
@ -2002,10 +2028,30 @@ static int parse_pred(const char *str, void *data,
} else if (field->filter_type == FILTER_DYN_STRING) {
pred->fn_num = FILTER_PRED_FN_STRLOC;
} else if (field->filter_type == FILTER_RDYN_STRING)
} else if (field->filter_type == FILTER_RDYN_STRING) {
pred->fn_num = FILTER_PRED_FN_STRRELLOC;
else {
} else if (field->filter_type == FILTER_WITHIN) {
unsigned long func;
if (op == OP_GLOB) {
parse_error(pe, FILT_ERR_GLOB_WITHIN, pos + i);
goto err_free;
}
pred->fn_num = FILTER_PRED_FN_WITHIN;
func = kallsyms_lookup_name(pred->regex->pattern);
if (!func) {
parse_error(pe, FILT_ERR_NO_FUNCTION, pos + i);
goto err_free;
}
/* Now find the function start and end address */
if (!kallsyms_lookup_size_offset(func, &size, &offset)) {
parse_error(pe, FILT_ERR_NO_FUNCTION, pos + i);
goto err_free;
}
pred->val = func - offset;
pred->val2 = pred->val + size;
} else {
if (!ustring_per_cpu) {
/* Once allocated, keep it around for good */
ustring_per_cpu = alloc_percpu(struct ustring_buffer);

View File

@ -279,6 +279,14 @@ static u64 hist_field_pstring(struct hist_field *hist_field,
return (u64)(unsigned long)*addr;
}
static bool hist_field_is_signed(struct hist_field *hist_field)
{
if (hist_field->field)
return hist_field->field->is_signed;
return hist_field->is_signed;
}
static u64 hist_field_log2(struct hist_field *hist_field,
struct tracing_map_elt *elt,
struct trace_buffer *buffer,
@ -466,7 +474,7 @@ static u64 hist_field_unary_minus(struct hist_field *hist_field,
{ \
type *addr = (type *)(event + hist_field->field->offset); \
\
return (u64)(unsigned long)*addr; \
return (u64)*addr; \
}
DEFINE_HIST_FIELD_FN(s64);
@ -549,7 +557,7 @@ struct field_var {
};
struct field_var_hist {
struct hist_trigger_data *hist_data;
struct trace_event_file *file;
char *cmd;
};
@ -1725,7 +1733,7 @@ static const char *get_hist_field_flags(struct hist_field *hist_field)
flags_str = "percent";
else if (hist_field->flags & HIST_FIELD_FL_GRAPH)
flags_str = "graph";
else if (hist_field->flags & HIST_FIELD_FL_STACKTRACE)
else if (hist_field->flags & HIST_FIELD_FL_STACKTRACE && hist_field->field)
flags_str = "stacktrace";
return flags_str;
@ -1754,6 +1762,9 @@ static bool expr_field_str(struct hist_field *field, struct seq_buf *s)
seq_buf_printf(s, ".%s", flags_str);
}
if (field->buckets)
seq_buf_printf(s, "=%lu", field->buckets);
return !seq_buf_has_overflowed(s);
}
@ -3117,8 +3128,8 @@ create_field_var_hist(struct hist_trigger_data *target_hist_data,
return ERR_PTR(-ENOMEM);
}
/* Save the compatible histogram information */
var_hist->hist_data = hist_data;
/* Needed to remove the histogram when the target goes away */
var_hist->file = file;
/* Create the new histogram with our variable */
ret = event_hist_trigger_parse(&trigger_hist_cmd, file,
@ -3133,20 +3144,18 @@ create_field_var_hist(struct hist_trigger_data *target_hist_data,
kfree(cmd);
n = target_hist_data->n_field_var_hists;
target_hist_data->field_var_hists[n] = var_hist;
target_hist_data->n_field_var_hists++;
/* If we can't find the variable, something went wrong */
event_var = find_synthetic_field_var(target_hist_data, subsys_name,
event_name, field_name);
if (IS_ERR_OR_NULL(event_var)) {
kfree(var_hist->cmd);
kfree(var_hist);
hist_err(tr, HIST_ERR_SYNTH_VAR_NOT_FOUND, errpos(field_name));
return ERR_PTR(-EINVAL);
}
n = target_hist_data->n_field_var_hists;
target_hist_data->field_var_hists[n] = var_hist;
target_hist_data->n_field_var_hists++;
return event_var;
}
@ -5565,15 +5574,24 @@ static void hist_trigger_print_key(struct seq_file *m,
*(u64 *)(key + key_field->offset));
} else if (key_field->flags & HIST_FIELD_FL_BUCKET) {
unsigned long buckets = key_field->buckets;
u64 end;
uval = *(u64 *)(key + key_field->offset);
end = uval + buckets - 1;
if (end < uval)
end = U64_MAX;
seq_printf(m, "%s: ~ %llu-%llu", field_name,
uval, uval + buckets -1);
uval, end);
} else if (key_field->flags & HIST_FIELD_FL_STRING) {
seq_printf(m, "%s: %-50s", field_name,
(char *)(key + key_field->offset));
} else {
uval = *(u64 *)(key + key_field->offset);
seq_printf(m, "%s: %10llu", field_name, uval);
if (hist_field_is_signed(key_field))
seq_printf(m, "%s: %10lld", field_name,
(s64)uval);
else
seq_printf(m, "%s: %10llu", field_name, uval);
}
}
@ -6205,7 +6223,7 @@ static void hist_field_print(struct seq_file *m, struct hist_field *hist_field)
}
}
if (hist_field->buckets)
seq_printf(m, "=%ld", hist_field->buckets);
seq_printf(m, "=%lu", hist_field->buckets);
}
static int event_hist_trigger_print(struct seq_file *m,
@ -6349,7 +6367,7 @@ static void unregister_field_var_hists(struct hist_trigger_data *hist_data)
int ret;
for (i = 0; i < hist_data->n_field_var_hists; i++) {
file = hist_data->field_var_hists[i]->hist_data->event_file;
file = hist_data->field_var_hists[i]->file;
cmd = hist_data->field_var_hists[i]->cmd;
ret = event_hist_trigger_parse(&trigger_hist_cmd, file,
"!hist", "hist", cmd);
@ -6799,7 +6817,6 @@ static void hist_unreg_all(struct trace_event_file *file)
struct event_trigger_data *test, *n;
struct hist_trigger_data *hist_data;
struct synth_event *se;
const char *se_name;
lockdep_assert_held(&event_mutex);
@ -6812,8 +6829,7 @@ static void hist_unreg_all(struct trace_event_file *file)
list_del_rcu(&test->list);
trace_event_trigger_enable_disable(file, 0);
se_name = trace_event_name(file->event_call);
se = find_synth_event(se_name);
se = find_synth_event_call(file->event_call);
if (se)
se->ref--;
@ -6837,7 +6853,6 @@ static int event_hist_trigger_parse(struct event_command *cmd_ops,
struct hist_trigger_data *hist_data;
char *param, *filter, *p, *start;
struct synth_event *se;
const char *se_name;
bool remove;
int ret = 0;
@ -6931,8 +6946,7 @@ static int event_hist_trigger_parse(struct event_command *cmd_ops,
}
event_trigger_unregister(cmd_ops, file, glob+1, trigger_data);
se_name = trace_event_name(file->event_call);
se = find_synth_event(se_name);
se = find_synth_event_call(file->event_call);
if (se)
se->ref--;
ret = 0;
@ -6967,8 +6981,7 @@ static int event_hist_trigger_parse(struct event_command *cmd_ops,
if (ret)
goto out_unreg;
se_name = trace_event_name(file->event_call);
se = find_synth_event(se_name);
se = find_synth_event_call(file->event_call);
if (se)
se->ref++;
out:
@ -6985,6 +6998,8 @@ static int event_hist_trigger_parse(struct event_command *cmd_ops,
trigger_data_free(trigger_data);
unregister_field_var_hists(hist_data);
destroy_hist_data(hist_data);
goto out;
}

View File

@ -914,6 +914,16 @@ struct synth_event *find_synth_event(const char *name)
return NULL;
}
struct synth_event *find_synth_event_call(struct trace_event_call *call)
{
struct synth_event *se;
const char *name;
name = trace_event_name(call);
se = find_synth_event(name);
return se && &se->call == call ? se : NULL;
}
static struct trace_event_fields synth_event_fields_array[] = {
{ .type = TRACE_FUNCTION_TYPE,
.define_fields = synth_event_define_fields },

View File

@ -0,0 +1,122 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/module.h>
#include <kunit/test.h>
#include "trace.h"
struct parse_event_string_test_case {
const char *input;
const char *match, *sub, *event, *mod, *filter;
};
static const struct parse_event_string_test_case parse_event_string_test_cases[] = {
{
"",
.match = "",
},
{
"*:event",
.event = "event",
},
{
":event",
.event = "event",
},
{
"sub:*",
.sub = "sub",
},
{
"sub:",
.sub = "sub",
},
{
"sub:event",
.sub = "sub",
.event = "event",
},
{
"sub:event:filter:filter",
.sub = "sub",
.event = "event",
.filter = "filter",
},
{
"match",
.match = "match",
},
{
":mod:module",
.mod = "module",
},
{
"sub:event:mod:module",
.sub = "sub",
.event = "event",
.mod = "module",
},
{
"sub:event:mod:module:filter:filter",
.sub = "sub",
.event = "event",
.mod = "module",
.filter = "filter",
},
{
"sub:event:mod:module:filter:unexpected-filter",
.sub = "sub",
.event = "event",
.mod = "module:filter:unexpected-filter",
},
};
static void
parse_event_string_test_desc(const struct parse_event_string_test_case *params, char *desc)
{
snprintf(desc, KUNIT_PARAM_DESC_SIZE, "'%s'", params->input);
}
KUNIT_ARRAY_PARAM(parse_event_string, parse_event_string_test_cases, parse_event_string_test_desc);
#define EXPECT_NULL_OR_STR_EQ(test, left, right) \
({ \
if ((left) != NULL) \
KUNIT_EXPECT_STREQ(test, (left), (right)); \
else \
KUNIT_EXPECT_NULL(test, (right)); \
})
static void parse_event_string(struct kunit *test)
{
const struct parse_event_string_test_case *params = test->param_value;
char *input, *match, *sub, *event, *mod, *filter;
input = kunit_kstrdup(test, params->input, GFP_KERNEL);
if (!input)
kunit_skip(test, "ENOMEM");
ftrace_parse_event_string(input, &match, &sub, &event, &mod, params->filter, &filter);
EXPECT_NULL_OR_STR_EQ(test, match, params->match);
EXPECT_NULL_OR_STR_EQ(test, sub, params->sub);
EXPECT_NULL_OR_STR_EQ(test, event, params->event);
EXPECT_NULL_OR_STR_EQ(test, mod, params->mod);
EXPECT_NULL_OR_STR_EQ(test, filter, params->filter);
}
static struct kunit_case trace_events_test_cases[] = {
KUNIT_CASE_PARAM(parse_event_string, parse_event_string_gen_params),
{}
};
static struct kunit_suite trace_events_test_suite = {
.name = "trace-events",
.test_cases = trace_events_test_cases,
};
kunit_test_suite(trace_events_test_suite);
MODULE_DESCRIPTION("KUnit test for the event tracing implementation");
MODULE_LICENSE("GPL");
MODULE_IMPORT_NS("EXPORTED_FOR_KUNIT_TESTING");

View File

@ -83,6 +83,7 @@ struct osnoise_instance {
static struct list_head osnoise_instances;
__printf(1, 2)
static void osnoise_print(const char *fmt, ...)
{
struct osnoise_instance *inst;

View File

@ -142,18 +142,18 @@ int resize_buffer_duplicate_size(struct array_buffer *trace_buf,
if (cpu_id == RING_BUFFER_ALL_CPUS) {
for_each_tracing_cpu(cpu) {
ret = ring_buffer_resize(trace_buf->buffer,
per_cpu_ptr(size_buf->data, cpu)->entries, cpu);
per_cpu_ptr(size_buf->data, cpu)->capacity, cpu);
if (ret < 0)
break;
per_cpu_ptr(trace_buf->data, cpu)->entries =
per_cpu_ptr(size_buf->data, cpu)->entries;
per_cpu_ptr(trace_buf->data, cpu)->capacity =
per_cpu_ptr(size_buf->data, cpu)->capacity;
}
} else {
ret = ring_buffer_resize(trace_buf->buffer,
per_cpu_ptr(size_buf->data, cpu_id)->entries, cpu_id);
per_cpu_ptr(size_buf->data, cpu_id)->capacity, cpu_id);
if (ret == 0)
per_cpu_ptr(trace_buf->data, cpu_id)->entries =
per_cpu_ptr(size_buf->data, cpu_id)->entries;
per_cpu_ptr(trace_buf->data, cpu_id)->capacity =
per_cpu_ptr(size_buf->data, cpu_id)->capacity;
}
return ret;
@ -193,7 +193,7 @@ void free_snapshot(struct trace_array *tr)
*/
ring_buffer_subbuf_order_set(tr->snapshot_buffer.buffer, 0);
ring_buffer_resize(tr->snapshot_buffer.buffer, 1, RING_BUFFER_ALL_CPUS);
trace_set_buffer_entries(&tr->snapshot_buffer, 1);
trace_set_buffer_capacity(&tr->snapshot_buffer, 1);
tracing_reset_online_cpus(&tr->snapshot_buffer);
tr->allocated_snapshot = false;
}

View File

@ -37,5 +37,6 @@ struct synth_event {
};
extern struct synth_event *find_synth_event(const char *name);
extern struct synth_event *find_synth_event_call(struct trace_event_call *call);
#endif /* __TRACE_SYNTH_H */

View File

@ -683,7 +683,7 @@ static int syscall_copy_user_array(char *buf, const char __user *ptr,
for (int i = 0; i < args->uargs; i++, buf += SYSCALL_FAULT_ARG_SZ) {
ptr = (char __user *)args->ptr_array[i];
ret = __copy_from_user(buf, ptr, size);
ret = copy_from_user(buf, ptr, size);
args->read[i] = ret ? -1 : size;
}
return 0;

View File

@ -0,0 +1,24 @@
#!/bin/sh
# SPDX-License-Identifier: GPL-2.0
# description: event filter - reject incomplete logical expressions
# requires: events/sched/sched_switch/filter
# flags: instance
filter=events/sched/sched_switch/filter
for base in \
'common_pid == 1' \
'common_pid == 1 || common_pid == 2' \
'common_pid == 1 && common_pid == 2' \
'!(common_pid == 1 || common_pid == 2)'
do
printf '%s' "$base" > "$filter"
for tail in '&&' '||' '&& !' '|| !' '&& !! ' '|| !! '; do
if printf '%s' "$base $tail" > "$filter" 2>/dev/null; then
echo "Accepted incomplete filter: $base $tail"
exit_fail
fi
done
done
echo 0 > "$filter"