From 7884b0bf098c5f3d23418e94751502f25ebae309 Mon Sep 17 00:00:00 2001 From: Chen Jun Date: Fri, 21 Aug 2026 19:28:21 -0400 Subject: [PATCH 01/27] tracing: Add "within" filter for call-stack-based event filtering Low-level kernel functions are called from many different paths. When debugging, it is often useful to filter trace events to only those occurring within a specific call chain. Add a "within" filter predicate that tests whether a given function appears in the current call stack at event time. The function name is resolved to its address range via kallsyms during filter setup; at runtime, stack_trace_save() captures the call stack and compares each return address against the stored range. Example: echo 'within == "vfs_read"' > events/sched/sched_switch/filter Only "==" and "!=" operators are supported. The filter depends on CONFIG_STACKTRACE. Link: https://patch.msgid.link/20260821233050.335986786@kernel.org Link: https://patch.msgid.link/20260608145556.94931-1-chenjun102@huawei.com Signed-off-by: Chen Jun Signed-off-by: Steven Rostedt --- Documentation/trace/events.rst | 12 +++++++++ include/linux/trace_events.h | 1 + kernel/trace/trace.h | 3 ++- kernel/trace/trace_events.c | 3 +++ kernel/trace/trace_events_filter.c | 41 ++++++++++++++++++++++++++++-- 5 files changed, 57 insertions(+), 3 deletions(-) diff --git a/Documentation/trace/events.rst b/Documentation/trace/events.rst index 581f2260614b..a1a219208ecc 100644 --- a/Documentation/trace/events.rst +++ b/Documentation/trace/events.rst @@ -243,6 +243,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:: diff --git a/include/linux/trace_events.h b/include/linux/trace_events.h index 5cbd09c8be8d..d0259a515a83 100644 --- a/include/linux/trace_events.h +++ b/include/linux/trace_events.h @@ -872,6 +872,7 @@ enum { FILTER_COMM, FILTER_CPU, FILTER_STACKTRACE, + FILTER_WITHIN, }; extern int trace_event_raw_init(struct trace_event_call *call); diff --git a/kernel/trace/trace.h b/kernel/trace/trace.h index 3749485a7d85..0af2b32619d5 100644 --- a/kernel/trace/trace.h +++ b/kernel/trace/trace.h @@ -1826,7 +1826,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) diff --git a/kernel/trace/trace_events.c b/kernel/trace/trace_events.c index 30c0ddf90887..1bd227282729 100644 --- a/kernel/trace/trace_events.c +++ b/kernel/trace/trace_events.c @@ -201,6 +201,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; } diff --git a/kernel/trace/trace_events_filter.c b/kernel/trace/trace_events_filter.c index 2b46ca536045..89f606643388 100644 --- a/kernel/trace/trace_events_filter.c +++ b/kernel/trace/trace_events_filter.c @@ -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, }; @@ -1009,6 +1010,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 * @@ -1618,6 +1635,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: @@ -2003,10 +2022,28 @@ 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) + 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); From b16cb6d8040fca3ec38801f6867b830414d9cb68 Mon Sep 17 00:00:00 2001 From: Steven Rostedt Date: Fri, 21 Aug 2026 19:28:22 -0400 Subject: [PATCH 02/27] tracnig: Add proper error message for glob use with "within" filter If a user were to use a glob expression with the "within" filter, it currently just fails with an errno return but does not update the error_log to tell the user why it failed. Update it to use the parsing error log to tell the user why it failed. # echo 'within ~ "*_read"' > events/kmem/kmem_cache_alloc/filter -bash: echo: write error: Invalid argument # cat error_log [ 4676.606481] event filter parse error: error: 'within' filter cannot take glob expressions Command: within ~ "*_read" ^ Link: https://patch.msgid.link/20260821233050.488131029@kernel.org Signed-off-by: Steven Rostedt --- kernel/trace/trace_events_filter.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/kernel/trace/trace_events_filter.c b/kernel/trace/trace_events_filter.c index 89f606643388..b3b13700abce 100644 --- a/kernel/trace/trace_events_filter.c +++ b/kernel/trace/trace_events_filter.c @@ -118,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") @@ -2027,8 +2028,10 @@ static int parse_pred(const char *str, void *data, } else if (field->filter_type == FILTER_WITHIN) { unsigned long func; - if (op == OP_GLOB) + 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); From 37a39701f69341ec4b730325a17eddf310cd805d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Tue, 8 Sep 2026 08:22:16 +0200 Subject: [PATCH 03/27] tracing: Remove duplicate declaration of ftrace_set_clr_event() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The function is also declared in include/linux/trace_events.h which is even included from kernel/trace/trace.h. Having two declarations creates churn when changing the signature. Remove the unnecessary, duplicate declaration. Link: https://patch.msgid.link/20260908-tracing-cli-event-filter-v2-2-05396a3fb663@linutronix.de Signed-off-by: Thomas Weißschuh Signed-off-by: Steven Rostedt --- kernel/trace/trace.h | 1 - 1 file changed, 1 deletion(-) diff --git a/kernel/trace/trace.h b/kernel/trace/trace.h index 0af2b32619d5..e05216479913 100644 --- a/kernel/trace/trace.h +++ b/kernel/trace/trace.h @@ -2290,7 +2290,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) { } From 82c9b6a0581f6304fca15cad264a26196e1d40cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Tue, 8 Sep 2026 08:22:17 +0200 Subject: [PATCH 04/27] tracing: Stop modifying the input buffer in ftrace_set_clr_event() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ftrace_set_clr_event() modifies its input buffer during parsing. Before returning to the caller these modifications are undone. This is restoration is error prone and was forgotten before. Upcoming changes will also make it more complicated. Copy the input argument and modify that copy and drop the restoration logic. Also mark the input argument as const. Link: https://patch.msgid.link/20260908-tracing-cli-event-filter-v2-3-05396a3fb663@linutronix.de Signed-off-by: Thomas Weißschuh Signed-off-by: Steven Rostedt --- include/linux/trace_events.h | 2 +- kernel/trace/trace_events.c | 19 ++++++++----------- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/include/linux/trace_events.h b/include/linux/trace_events.h index d0259a515a83..8a396ae51fef 100644 --- a/include/linux/trace_events.h +++ b/include/linux/trace_events.h @@ -883,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); 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); diff --git a/kernel/trace/trace_events.c b/kernel/trace/trace_events.c index 1bd227282729..de964dd2878b 100644 --- a/kernel/trace/trace_events.c +++ b/kernel/trace/trace_events.c @@ -1443,14 +1443,19 @@ static int __ftrace_set_clr_event(struct trace_array *tr, const char *match, return ret; } -int ftrace_set_clr_event(struct trace_array *tr, char *buf, int set) +int ftrace_set_clr_event(struct trace_array *tr, const char *arg_buf, int set) { char *event = NULL, *sub = NULL, *match, *mod; - int ret; + char *buf; if (!tr) return -ENOENT; + char *dupped_buf __free(kfree) = kstrdup(arg_buf, GFP_KERNEL); + if (!dupped_buf) + return -ENOMEM; + buf = dupped_buf; + /* Modules events can be appended with :mod: */ mod = strstr(buf, ":mod:"); if (mod) { @@ -1487,15 +1492,7 @@ int ftrace_set_clr_event(struct trace_array *tr, char *buf, int set) match = NULL; } - ret = __ftrace_set_clr_event(tr, match, sub, event, set, mod); - - /* Put back the colon to allow this to be called again */ - if (buf) - *(buf - 1) = ':'; - if (mod) - *(mod - 5) = ':'; - - return ret; + return __ftrace_set_clr_event(tr, match, sub, event, set, mod); } /** From f572b5d9142774891a98eae21b63b1286dc6e331 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Tue, 8 Sep 2026 08:22:18 +0200 Subject: [PATCH 05/27] tracing: Split the event string parsing logic into a dedicated function MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This new function can be tested with a KUnit test to prevent regressions when adding new fields to the format. Link: https://patch.msgid.link/20260908-tracing-cli-event-filter-v2-4-05396a3fb663@linutronix.de Signed-off-by: Thomas Weißschuh Signed-off-by: Steven Rostedt --- kernel/trace/trace_events.c | 59 +++++++++++++++++++++---------------- 1 file changed, 33 insertions(+), 26 deletions(-) diff --git a/kernel/trace/trace_events.c b/kernel/trace/trace_events.c index de964dd2878b..288b90b207ca 100644 --- a/kernel/trace/trace_events.c +++ b/kernel/trace/trace_events.c @@ -1443,25 +1443,18 @@ static int __ftrace_set_clr_event(struct trace_array *tr, const char *match, return ret; } -int ftrace_set_clr_event(struct trace_array *tr, const char *arg_buf, int set) +static void +ftrace_parse_event_string(char *buf, char **match, char **sub, char **event, char **mod) { - char *event = NULL, *sub = NULL, *match, *mod; - char *buf; - - if (!tr) - return -ENOENT; - - char *dupped_buf __free(kfree) = kstrdup(arg_buf, GFP_KERNEL); - if (!dupped_buf) - return -ENOMEM; - buf = dupped_buf; + *event = NULL; + *sub = NULL; /* Modules events can be appended with :mod: */ - 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; } /* @@ -1476,21 +1469,35 @@ int ftrace_set_clr_event(struct trace_array *tr, const char *arg_buf, int set) * the name or any event that matches */ - 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; } +} + +int ftrace_set_clr_event(struct trace_array *tr, const char *arg_buf, int set) +{ + char *event, *sub, *match, *mod; + + if (!tr) + return -ENOENT; + + 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); return __ftrace_set_clr_event(tr, match, sub, event, set, mod); } From cc879c2b6cd8a1114e7d9c2e3df0d4e4cd8bb998 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Tue, 8 Sep 2026 08:22:19 +0200 Subject: [PATCH 06/27] tracing: Add a test for ftrace_parse_event_string() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The parsing logic is a bit complicated and about to become more so. Add a unit test to avoid regressions. Link: https://patch.msgid.link/20260908-tracing-cli-event-filter-v2-5-05396a3fb663@linutronix.de Signed-off-by: Thomas Weißschuh Signed-off-by: Steven Rostedt --- kernel/trace/Kconfig | 8 +++ kernel/trace/Makefile | 1 + kernel/trace/trace.h | 4 ++ kernel/trace/trace_events.c | 5 +- kernel/trace/trace_events_test.c | 102 +++++++++++++++++++++++++++++++ 5 files changed, 119 insertions(+), 1 deletion(-) create mode 100644 kernel/trace/trace_events_test.c diff --git a/kernel/trace/Kconfig b/kernel/trace/Kconfig index 0ab5916575a9..430a4152095e 100644 --- a/kernel/trace/Kconfig +++ b/kernel/trace/Kconfig @@ -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 diff --git a/kernel/trace/Makefile b/kernel/trace/Makefile index f934ff586bd4..7bef5a84ce47 100644 --- a/kernel/trace/Makefile +++ b/kernel/trace/Makefile @@ -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 diff --git a/kernel/trace/trace.h b/kernel/trace/trace.h index e05216479913..31718ef337dc 100644 --- a/kernel/trace/trace.h +++ b/kernel/trace/trace.h @@ -2509,3 +2509,7 @@ 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); +#endif diff --git a/kernel/trace/trace_events.c b/kernel/trace/trace_events.c index 288b90b207ca..f6da4eea5ca2 100644 --- a/kernel/trace/trace_events.c +++ b/kernel/trace/trace_events.c @@ -25,6 +25,8 @@ #include #include +#include + #include #include @@ -1443,7 +1445,7 @@ static int __ftrace_set_clr_event(struct trace_array *tr, const char *match, return ret; } -static void +VISIBLE_IF_KUNIT void ftrace_parse_event_string(char *buf, char **match, char **sub, char **event, char **mod) { *event = NULL; @@ -1485,6 +1487,7 @@ ftrace_parse_event_string(char *buf, char **match, char **sub, char **event, cha *match = NULL; } } +EXPORT_SYMBOL_IF_KUNIT(ftrace_parse_event_string); int ftrace_set_clr_event(struct trace_array *tr, const char *arg_buf, int set) { diff --git a/kernel/trace/trace_events_test.c b/kernel/trace/trace_events_test.c new file mode 100644 index 000000000000..515d2e668c31 --- /dev/null +++ b/kernel/trace/trace_events_test.c @@ -0,0 +1,102 @@ +// SPDX-License-Identifier: GPL-2.0 +#include + +#include + +#include "trace.h" + +struct parse_event_string_test_case { + const char *input; + + const char *match, *sub, *event, *mod; +}; + +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", + }, + { + "match", + .match = "match", + }, + { + ":mod:module", + .mod = "module", + }, + { + "sub:event:mod:module", + .sub = "sub", + .event = "event", + .mod = "module", + }, +}; + +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; + + input = kunit_kstrdup(test, params->input, GFP_KERNEL); + if (!input) + kunit_skip(test, "ENOMEM"); + + ftrace_parse_event_string(input, &match, &sub, &event, &mod); + + 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); +} + +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"); From d30778dee467f2a54759f90b29019f5aec69abd3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Tue, 8 Sep 2026 08:22:20 +0200 Subject: [PATCH 07/27] tracing: Add a filter argument to __ftrace_set_clr_event() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Make it possible to directly enable a filter when enabling an event. For now this is unused, an upcoming patch will wire it up. Link: https://patch.msgid.link/20260908-tracing-cli-event-filter-v2-6-05396a3fb663@linutronix.de Signed-off-by: Thomas Weißschuh Signed-off-by: Steven Rostedt --- kernel/trace/trace_events.c | 47 +++++++++++++++++++++++-------------- 1 file changed, 30 insertions(+), 17 deletions(-) diff --git a/kernel/trace/trace_events.c b/kernel/trace/trace_events.c index f6da4eea5ca2..b27599c7d55d 100644 --- a/kernel/trace/trace_events.c +++ b/kernel/trace/trace_events.c @@ -946,6 +946,7 @@ struct event_mod_load { char *match; char *system; char *event; + char *filter; }; static void free_event_mod(struct event_mod_load *event_mod) @@ -955,6 +956,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); } @@ -996,7 +998,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; @@ -1035,6 +1037,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; @@ -1047,7 +1055,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; } @@ -1356,7 +1364,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; @@ -1406,7 +1414,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 @@ -1424,14 +1436,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; @@ -1439,7 +1451,7 @@ 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; @@ -1502,7 +1514,7 @@ int ftrace_set_clr_event(struct trace_array *tr, const char *arg_buf, int set) ftrace_parse_event_string(dupped_buf, &match, &sub, &event, &mod); - return __ftrace_set_clr_event(tr, match, sub, event, set, mod); + return __ftrace_set_clr_event(tr, match, sub, event, set, mod, NULL); } /** @@ -1524,7 +1536,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); @@ -1550,7 +1562,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); @@ -2070,7 +2082,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; @@ -4045,7 +4057,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); } } @@ -4819,7 +4832,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(); @@ -5129,7 +5142,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); @@ -5138,7 +5151,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); @@ -5153,7 +5166,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; @@ -5162,7 +5175,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; From 084100a1b579e6e08085dfc33caa4df31bb25696 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Tue, 8 Sep 2026 08:22:21 +0200 Subject: [PATCH 08/27] tracing: Parse filter from boot event string MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Make it possible to specify a trace event filter from the kernel command line. Example: trace_event=syscalls:sys_enter_clock_nanosleep:filter:which_clock==0 Link: https://patch.msgid.link/20260908-tracing-cli-event-filter-v2-7-05396a3fb663@linutronix.de Signed-off-by: Thomas Weißschuh Signed-off-by: Steven Rostedt --- .../admin-guide/kernel-parameters.txt | 9 +++++-- Documentation/trace/events.rst | 5 ++++ include/linux/trace_events.h | 2 +- kernel/trace/trace.h | 3 ++- kernel/trace/trace_boot.c | 2 +- kernel/trace/trace_events.c | 27 +++++++++++++------ kernel/trace/trace_events_test.c | 26 +++++++++++++++--- 7 files changed, 58 insertions(+), 16 deletions(-) diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt index e75344f4e0cd..524cad368767 100644 --- a/Documentation/admin-guide/kernel-parameters.txt +++ b/Documentation/admin-guide/kernel-parameters.txt @@ -7733,8 +7733,13 @@ Kernel parameters trace_event=:mod: 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: + + See the above mentioned document for more information. trace_instance=[instance-info] [FTRACE] Create a ring buffer instance early in boot up. diff --git a/Documentation/trace/events.rst b/Documentation/trace/events.rst index a1a219208ecc..88a9099f1688 100644 --- a/Documentation/trace/events.rst +++ b/Documentation/trace/events.rst @@ -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:: + + ::mod::filter: + 3. Defining an event-enabled tracepoint ======================================= diff --git a/include/linux/trace_events.h b/include/linux/trace_events.h index 8a396ae51fef..321f2f3a6c8e 100644 --- a/include/linux/trace_events.h +++ b/include/linux/trace_events.h @@ -883,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, const 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); diff --git a/kernel/trace/trace.h b/kernel/trace/trace.h index 31718ef337dc..26390e06969e 100644 --- a/kernel/trace/trace.h +++ b/kernel/trace/trace.h @@ -2511,5 +2511,6 @@ 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); +void ftrace_parse_event_string(char *buf, char **match, char **sub, char **event, char **mod, + bool handle_filter, char **filter); #endif diff --git a/kernel/trace/trace_boot.c b/kernel/trace/trace_boot.c index 5da0a8bbb110..6ba70dac1f06 100644 --- a/kernel/trace/trace_boot.c +++ b/kernel/trace/trace_boot.c @@ -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); } } diff --git a/kernel/trace/trace_events.c b/kernel/trace/trace_events.c index b27599c7d55d..a1ac2a02f4e2 100644 --- a/kernel/trace/trace_events.c +++ b/kernel/trace/trace_events.c @@ -1458,10 +1458,21 @@ static int __ftrace_set_clr_event(struct trace_array *tr, const char *match, } VISIBLE_IF_KUNIT void -ftrace_parse_event_string(char *buf, char **match, char **sub, char **event, char **mod) +ftrace_parse_event_string(char *buf, char **match, char **sub, char **event, char **mod, + bool handle_filter, char **filter) { *event = NULL; *sub = NULL; + *filter = NULL; + + /* event filters can be appended with :filter: */ + if (handle_filter) { + *filter = strstr(buf, ":filter:"); + if (*filter) { + **filter = '\0'; + *filter += 8; + } + } /* Modules events can be appended with :mod: */ *mod = strstr(buf, ":mod:"); @@ -1501,9 +1512,9 @@ ftrace_parse_event_string(char *buf, char **match, char **sub, char **event, cha } EXPORT_SYMBOL_IF_KUNIT(ftrace_parse_event_string); -int ftrace_set_clr_event(struct trace_array *tr, const char *arg_buf, int set) +int ftrace_set_clr_event(struct trace_array *tr, const char *arg_buf, int set, bool handle_filter) { - char *event, *sub, *match, *mod; + char *event, *sub, *match, *mod, *filter; if (!tr) return -ENOENT; @@ -1512,9 +1523,9 @@ int ftrace_set_clr_event(struct trace_array *tr, const char *arg_buf, int set) if (!dupped_buf) return -ENOMEM; - ftrace_parse_event_string(dupped_buf, &match, &sub, &event, &mod); + ftrace_parse_event_string(dupped_buf, &match, &sub, &event, &mod, handle_filter, &filter); - return __ftrace_set_clr_event(tr, match, sub, event, set, mod, NULL); + return __ftrace_set_clr_event(tr, match, sub, event, set, mod, filter); } /** @@ -1596,7 +1607,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; } @@ -4865,10 +4876,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); } } diff --git a/kernel/trace/trace_events_test.c b/kernel/trace/trace_events_test.c index 515d2e668c31..4a2d77fa0f58 100644 --- a/kernel/trace/trace_events_test.c +++ b/kernel/trace/trace_events_test.c @@ -8,7 +8,7 @@ struct parse_event_string_test_case { const char *input; - const char *match, *sub, *event, *mod; + const char *match, *sub, *event, *mod, *filter; }; static const struct parse_event_string_test_case parse_event_string_test_cases[] = { @@ -37,6 +37,12 @@ static const struct parse_event_string_test_case parse_event_string_test_cases[] .sub = "sub", .event = "event", }, + { + "sub:event:filter:filter", + .sub = "sub", + .event = "event", + .filter = "filter", + }, { "match", .match = "match", @@ -51,6 +57,19 @@ static const struct parse_event_string_test_case parse_event_string_test_cases[] .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 @@ -72,18 +91,19 @@ KUNIT_ARRAY_PARAM(parse_event_string, parse_event_string_test_cases, parse_event 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; + 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); + 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[] = { From 4b0c4fb9bc51282e2cf895b838c533f364c2e7a1 Mon Sep 17 00:00:00 2001 From: Vincent Donnefort Date: Tue, 8 Sep 2026 10:09:35 +0100 Subject: [PATCH 09/27] ring-buffer: Rename ring_buffer_size() to ring_buffer_capacity() Since commit 8a5f63637890 ("ring-buffer: Fix subbuf resize race with ring buffer readers"), "capacity" refers to the memory available for events (excluding subbuffer headers) while "size" refers to the raw allocation. ring_buffer_size() actually calculates this event capacity. Rename it to ring_buffer_capacity() to align with this convention. Link: https://patch.msgid.link/20260908090937.838544-2-vdonnefort@google.com Signed-off-by: Vincent Donnefort Signed-off-by: Steven Rostedt --- include/linux/ring_buffer.h | 2 +- kernel/trace/ring_buffer.c | 13 +++++++++---- kernel/trace/trace.c | 6 +++--- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/include/linux/ring_buffer.h b/include/linux/ring_buffer.h index eac3e9080c3c..9855c7365f67 100644 --- a/include/linux/ring_buffer.h +++ b/include/linux/ring_buffer.h @@ -166,7 +166,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); diff --git a/kernel/trace/ring_buffer.c b/kernel/trace/ring_buffer.c index 04bb94c29f58..289bae003d7f 100644 --- a/kernel/trace/ring_buffer.c +++ b/kernel/trace/ring_buffer.c @@ -6546,18 +6546,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 diff --git a/kernel/trace/trace.c b/kernel/trace/trace.c index e4a490d3d08c..1054c9b29aed 100644 --- a/kernel/trace/trace.c +++ b/kernel/trace/trace.c @@ -4712,9 +4712,9 @@ void trace_set_buffer_entries(struct array_buffer *buf, unsigned long val) static void update_buffer_entries(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_entries(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)->entries = ring_buffer_capacity(buf->buffer, cpu); } } @@ -8520,7 +8520,7 @@ 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)); + ring_buffer_capacity(tr->array_buffer.buffer, 0)); return 0; } From e41338909f32d48aceaa3058f6b56578e1d0ecf7 Mon Sep 17 00:00:00 2001 From: Vincent Donnefort Date: Tue, 8 Sep 2026 10:09:36 +0100 Subject: [PATCH 10/27] tracing: Rename trace_array::entries to capacity Since commit 8a5f63637890 ("ring-buffer: Fix subbuf resize race with ring buffer readers"), "capacity" refers to the memory available for events, while "entries" refers to the number of events in a ring buffer. As a consequence, rename trace_array::entries to capacity to align with this convention and avoid any confusion. Rename also the helper functions trace_set_buffer_entries and update_buffer_entries. Link: https://patch.msgid.link/20260908090937.838544-3-vdonnefort@google.com Signed-off-by: Vincent Donnefort Signed-off-by: Steven Rostedt --- kernel/trace/trace.c | 26 +++++++++++++------------- kernel/trace/trace.h | 4 ++-- kernel/trace/trace_snapshot.c | 14 +++++++------- 3 files changed, 22 insertions(+), 22 deletions(-) diff --git a/kernel/trace/trace.c b/kernel/trace/trace.c index 1054c9b29aed..9e1b5c35f6a9 100644 --- a/kernel/trace/trace.c +++ b/kernel/trace/trace.c @@ -4701,20 +4701,20 @@ int tracer_init(struct tracer *t, struct trace_array *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_capacity(buf->buffer, 0)); + trace_set_buffer_capacity(buf, ring_buffer_capacity(buf->buffer, 0)); } else { - per_cpu_ptr(buf->data, cpu)->entries = ring_buffer_capacity(buf->buffer, cpu); + per_cpu_ptr(buf->data, cpu)->capacity = ring_buffer_capacity(buf->buffer, cpu); } } @@ -4770,12 +4770,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; @@ -5694,8 +5694,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; } @@ -5711,7 +5711,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); @@ -5758,7 +5758,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; } @@ -8519,8 +8519,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_capacity(tr->array_buffer.buffer, 0)); + trace_set_buffer_capacity(&tr->array_buffer, + ring_buffer_capacity(tr->array_buffer.buffer, 0)); return 0; } diff --git a/kernel/trace/trace.h b/kernel/trace/trace.h index 26390e06969e..0f0786700f99 100644 --- a/kernel/trace/trace.h +++ b/kernel/trace/trace.h @@ -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 diff --git a/kernel/trace/trace_snapshot.c b/kernel/trace/trace_snapshot.c index 07b43c9863a2..d7308f3b505f 100644 --- a/kernel/trace/trace_snapshot.c +++ b/kernel/trace/trace_snapshot.c @@ -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; } From c28bf729dd9c73408fca2645b75c3fda187b11a3 Mon Sep 17 00:00:00 2001 From: Vincent Donnefort Date: Tue, 8 Sep 2026 10:09:37 +0100 Subject: [PATCH 11/27] tracing: Rename tracing_entries_fops to tracing_capacity_fops Since commit 8a5f63637890 ("ring-buffer: Fix subbuf resize race with ring buffer readers"), "capacity" refers to the memory available for events, while "entries" refers to the number of events in a ring buffer. As a consequence, rename trace_array::entries to capacity to align with this convention. While at it, rename also tracing_total_entries_fops. It is the aggragate of all ring buffer capacities. Link: https://patch.msgid.link/20260908090937.838544-4-vdonnefort@google.com Signed-off-by: Vincent Donnefort Signed-off-by: Steven Rostedt --- kernel/trace/trace.c | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/kernel/trace/trace.c b/kernel/trace/trace.c index 9e1b5c35f6a9..48ecc4bb55e4 100644 --- a/kernel/trace/trace.c +++ b/kernel/trace/trace.c @@ -5672,8 +5672,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; @@ -5720,8 +5719,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; @@ -5748,8 +5746,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]; @@ -6600,10 +6597,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, }; @@ -6623,9 +6620,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, }; @@ -7653,7 +7650,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, @@ -8998,10 +8995,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); From 387945e8d2f29fa8a28d219af5416e6d3bedc314 Mon Sep 17 00:00:00 2001 From: Donggeun Yoo Date: Mon, 14 Sep 2026 14:34:40 +0900 Subject: [PATCH 12/27] tracing: Print the bucket size as unsigned A key's bucket size reads back negative once it exceeds LONG_MAX: # echo 'hist:keys=next_pid.buckets=18446744073709551615' > trigger # cat trigger hist:keys=next_pid.buckets=-1:vals=hitcount:... parse_field() takes the size with kstrtoul() and rejects only zero, so the whole unsigned long range is accepted and stored, but hist_field_print() renders it with %ld. Print it with %lu. Link: https://patch.msgid.link/20260914053443.981201-2-donggeunyoo.kernel@gmail.com Fixes: de9a48a360b7 ("tracing: Add linear buckets to histogram logic") Signed-off-by: Donggeun Yoo Assisted-by: Claude:claude-fable-5 Signed-off-by: Steven Rostedt --- kernel/trace/trace_events_hist.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/trace/trace_events_hist.c b/kernel/trace/trace_events_hist.c index 8af97fd4ee2d..a3eae5c1344a 100644 --- a/kernel/trace/trace_events_hist.c +++ b/kernel/trace/trace_events_hist.c @@ -6200,7 +6200,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, From c027ed03bc03a0e82fc368eaad657584a4480c7a Mon Sep 17 00:00:00 2001 From: Donggeun Yoo Date: Mon, 14 Sep 2026 14:34:41 +0900 Subject: [PATCH 13/27] tracing: Add the bucket size to expr_field_str() expr_field_str() renders a HIST_FIELD_FL_BUCKET field as "pid.buckets", dropping the count, and parse_field() rejects that spelling. get_hist_field_flags() returns the bare string "buckets" and keeps the count in hist_field->buckets. hist_field_print() appends it, expr_field_str() never did. Nothing reaches it today, since .buckets is refused on a value and no expression can carry one, but the next patch renders histogram keys with expr_field_str(), where a .buckets key is legal. Append the count, as hist_field_print() does. Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260914053443.981201-3-donggeunyoo.kernel@gmail.com Fixes: de9a48a360b7 ("tracing: Add linear buckets to histogram logic") Signed-off-by: Donggeun Yoo Assisted-by: Claude:claude-fable-5 Signed-off-by: Steven Rostedt --- kernel/trace/trace_events_hist.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/kernel/trace/trace_events_hist.c b/kernel/trace/trace_events_hist.c index a3eae5c1344a..91a550411e8a 100644 --- a/kernel/trace/trace_events_hist.c +++ b/kernel/trace/trace_events_hist.c @@ -1754,6 +1754,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); } From 18bb005845b78e0c0eb44b7f231c29d138552438 Mon Sep 17 00:00:00 2001 From: Donggeun Yoo Date: Mon, 14 Sep 2026 14:34:42 +0900 Subject: [PATCH 14/27] tracing: Only report the stacktrace modifier on a real field get_hist_field_flags() returns "stacktrace" for any field carrying HIST_FIELD_FL_STACKTRACE, including the common_stacktrace pseudo-field, which has no ftrace_event_field behind it. parse_field() no longer takes the modifier there: if (stack_modifier && (!field || field->filter_type != FILTER_STACKTRACE)) { hist_err(tr, HIST_ERR_BAD_FIELD_MODIFIER, errpos(field_str)); so expr_field_str() renders "common_stacktrace.stacktrace", a spelling that cannot be parsed back. Report the modifier only when there is a field to report it for. hist_field_print(), the other caller of get_hist_field_flags(), excludes HIST_FIELD_FL_STACKTRACE before it calls, so this is confined to expr_field_str(), whose only key renderer arrives in the next patch. Link: https://patch.msgid.link/20260914053443.981201-4-donggeunyoo.kernel@gmail.com Fixes: a5e70ba87ca8 ("tracing: Fix memory corruption from the histogram stacktrace modifier") Signed-off-by: Donggeun Yoo Assisted-by: Claude:claude-fable-5 Signed-off-by: Steven Rostedt --- kernel/trace/trace_events_hist.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/trace/trace_events_hist.c b/kernel/trace/trace_events_hist.c index 91a550411e8a..fdd097abb0d6 100644 --- a/kernel/trace/trace_events_hist.c +++ b/kernel/trace/trace_events_hist.c @@ -1725,7 +1725,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; From 91c14d6c13096091c56304120aaa26e479130b8b Mon Sep 17 00:00:00 2001 From: Donggeun Yoo Date: Mon, 14 Sep 2026 19:40:36 +0900 Subject: [PATCH 15/27] tracing: Unregister field variable histograms when the command fails An action that names a field on another event needs that field as a variable, so create_field_var_hist() registers a second hist trigger on that event to provide it, and records it in target_hist_data->field_var_hists[]. If a later part of the same command fails, create_actions() returns an error and event_hist_trigger_parse() jumps to out_free. The write fails with -EINVAL and sched_switch gets no trigger, but: # echo 'hist:keys=pid:ts0=common_timestamp.usecs' >> \ events/sched/sched_waking/trigger # echo 'my_synth u64 lat; int a; int b' >> synthetic_events # echo 'hist:keys=next_pid:wakeup_lat=common_timestamp.usecs-$ts0:\ onmatch(sched.sched_waking).my_synth($wakeup_lat,prio,nosuchfld)'\ >> events/sched/sched_switch/trigger # cat events/sched/sched_waking/trigger hist:keys=pid:vals=hitcount:ts0=common_timestamp.usecs:sort=hitcount:size=2048:clock=global [active] hist:keys=pid:vals=hitcount:synthetic_prio=prio:sort=hitcount:size=2048 [active] The second histogram is the one created for 'prio'. It is still active and still counting. out_free reaches destroy_field_var_hists(), which frees the field_var_hists[] entries, but never unregister_field_var_hists(), which is what removes the triggers and runs only from event_hist_trigger_free(). The entry is freed and the trigger is left with nothing tracking it. Unregister the field variable histograms in out_free as well, and record each one as soon as its trigger is registered rather than after the lookup that follows it, so no exit can leave a registered trigger untracked. The entry then belongs to field_var_hists[] and is released by destroy_field_var_hists(), so freeing it on that exit would be a double free. Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260914104036.1768529-1-donggeunyoo.kernel@gmail.com Fixes: 02205a6752f2 ("tracing: Add support for 'field variables'") Reported-by: sashiko-bot@kernel.org Closes: https://lore.kernel.org/all/20260913204302.DB2551F000FF@smtp.kernel.org/ Signed-off-by: Donggeun Yoo Assisted-by: Claude:claude-fable-5 Signed-off-by: Steven Rostedt --- kernel/trace/trace_events_hist.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/kernel/trace/trace_events_hist.c b/kernel/trace/trace_events_hist.c index fdd097abb0d6..972804709b14 100644 --- a/kernel/trace/trace_events_hist.c +++ b/kernel/trace/trace_events_hist.c @@ -3137,20 +3137,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; } @@ -6983,6 +6981,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; } From 289e2a775de434ccab2d107076bc78362ce288bd Mon Sep 17 00:00:00 2001 From: Donggeun Yoo Date: Mon, 14 Sep 2026 22:23:46 +0900 Subject: [PATCH 16/27] tracing: Clamp the printed end of a histogram bucket at U64_MAX A key with the ".buckets=N" modifier is stored as the start of its bucket, and hist_trigger_print_key() prints the range it covers as start to start + N - 1. That addition overflows once the start is within N of U64_MAX: # echo 'hist:keys=offset.buckets=10' >> \ events/syscalls/sys_enter_lseek/trigger # ./seeker 5 -1 # lseek(0, 5) and lseek(0, -1) # cat events/syscalls/sys_enter_lseek/hist ... { offset: ~ 18446744073709551610-3 } hitcount: 1 { offset: ~ 0-9 } hitcount: 1 ... 18446744073709551610 + 10 - 1 does not fit in 64 bits, so the range ends below where it starts. The bucket does end at U64_MAX: it holds every value hist_field_bucket() maps onto its start, and there is no value above U64_MAX. Clamp the printed end there. Printed honestly the group is shorter than the size asked for, which histogram.rst does not mention, so say it there. Link: https://patch.msgid.link/20260914132346.2234731-1-donggeunyoo.kernel@gmail.com Fixes: de9a48a360b7 ("tracing: Add linear buckets to histogram logic") Signed-off-by: Donggeun Yoo Assisted-by: Claude:claude-fable-5 Signed-off-by: Steven Rostedt --- Documentation/trace/histogram.rst | 3 +++ kernel/trace/trace_events_hist.c | 7 ++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/Documentation/trace/histogram.rst b/Documentation/trace/histogram.rst index 340bcb5099e7..97b67f05f5ea 100644 --- a/Documentation/trace/histogram.rst +++ b/Documentation/trace/histogram.rst @@ -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:: diff --git a/kernel/trace/trace_events_hist.c b/kernel/trace/trace_events_hist.c index 972804709b14..c150e1130a5f 100644 --- a/kernel/trace/trace_events_hist.c +++ b/kernel/trace/trace_events_hist.c @@ -5561,9 +5561,14 @@ 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)); From 443d23176812803d0fa182b8d05ef066a7b8271f Mon Sep 17 00:00:00 2001 From: Donggeun Yoo Date: Thu, 17 Sep 2026 10:55:32 +0900 Subject: [PATCH 17/27] tracing: Fix 64-bit and signed histogram fields on 32-bit kernels DEFINE_HIST_FIELD_FN() reads a trace event field and widens it to the u64 the histogram uses as a key or a value: type *addr = (type *)(event + hist_field->field->offset); return (u64)(unsigned long)*addr; unsigned long is 32 bits on a 32-bit kernel, so the intermediate cast discards the upper half of a 64-bit field, and widens a signed field by zero extension from 32 bits rather than sign extension from its own width. Five of the eight instantiations are wrong there: s64, u64, s32, s16 and s8. The cast came in with commit 79e577cbce4c ("tracing: Support string type key properly"), which also added hist_field_dynstring() and hist_field_pstring(). Those convert a char * to u64 and do warn without an unsigned long in between on 32-bit. DEFINE_HIST_FIELD_FN() converts an integer and never a pointer, so it gains nothing from the cast and only loses information. Drop the intermediate cast and let the usual arithmetic conversions widen *addr according to its own type. unsigned long is 64 bits on LP64, so the two forms are identical there and 64-bit kernels are unaffected. Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260917015532.103081-1-donggeunyoo.kernel@gmail.com Fixes: 79e577cbce4c ("tracing: Support string type key properly") Reported-by: sashiko-bot@kernel.org Closes: https://lore.kernel.org/all/20260914054614.82A9B1F000FF@smtp.kernel.org/ Signed-off-by: Donggeun Yoo Signed-off-by: Steven Rostedt --- kernel/trace/trace_events_hist.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/trace/trace_events_hist.c b/kernel/trace/trace_events_hist.c index c150e1130a5f..7d3bb7da4dd1 100644 --- a/kernel/trace/trace_events_hist.c +++ b/kernel/trace/trace_events_hist.c @@ -466,7 +466,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); From d3e0dfc1ba49a0ab6aeddbb4bcde4bfc0e171a02 Mon Sep 17 00:00:00 2001 From: Donggeun Yoo Date: Thu, 17 Sep 2026 13:59:18 +0900 Subject: [PATCH 18/27] tracing: Print a signed histogram key as signed A histogram sorts its rows by the key named in sort=, and for a key over a signed event field it sorts them signed: create_tracing_map_fields() picks the comparison function with else cmp_fn = tracing_map_cmp_num(field->size, field->is_signed); hist_trigger_print_key() does not look at is_signed at all. Every numeric key is printed with %llu, so a sorted histogram over a signed field comes out looking unsorted. syscalls/sys_exit_lseek carries "long ret", which SYSCALL_FIELD() marks signed and which is negative on every error: # echo 'hist:keys=ret:sort=ret' > events/syscalls/sys_exit_lseek/trigger # echo 1 > events/syscalls/sys_exit_lseek/enable # (five lseek(2) calls returning -ESPIPE, -EINVAL, -EBADF, 0 and 100) # cat events/syscalls/sys_exit_lseek/hist { ret: 18446744073709551587 } hitcount: 1 { ret: 18446744073709551594 } hitcount: 1 { ret: 18446744073709551607 } hitcount: 1 { ret: 0 } hitcount: 1 { ret: 100 } hitcount: 1 The rows are in ascending order and the numbers are not, because the three leading ones are -29, -22 and -9 rendered as their two's complement. Take the rendering from the same place the sort order comes from: the event field when the key has one, and the hist_field itself when it does not. The same histogram then reads { ret: -29 } hitcount: 1 { ret: -22 } hitcount: 1 { ret: -9 } hitcount: 1 { ret: 0 } hitcount: 1 { ret: 100 } hitcount: 1 A ".buckets" key stays unsigned here, and not because it is right. hist_field_bucket() divides as unsigned, so the group holding S64_MAX also holds S64_MIN, reported at https://lore.kernel.org/all/20260914054614.82A9B1F000FF@smtp.kernel.org/ Printing that group signed would name a range that does not contain S64_MIN, so the grouping has to be fixed with it, in a patch of its own. Link: https://patch.msgid.link/20260917045918.370993-1-donggeunyoo.kernel@gmail.com Fixes: 7ef224d1d0e3 ("tracing: Add 'hist' event trigger command") Signed-off-by: Donggeun Yoo Signed-off-by: Steven Rostedt --- kernel/trace/trace_events_hist.c | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/kernel/trace/trace_events_hist.c b/kernel/trace/trace_events_hist.c index 7d3bb7da4dd1..5527605758cb 100644 --- a/kernel/trace/trace_events_hist.c +++ b/kernel/trace/trace_events_hist.c @@ -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, @@ -5574,7 +5582,11 @@ static void hist_trigger_print_key(struct seq_file *m, (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); } } From 097fd0050089df17b032f2b852b9b65e548e342a Mon Sep 17 00:00:00 2001 From: Haoran Wang Date: Fri, 16 Oct 2026 11:33:13 +0800 Subject: [PATCH 19/27] tracing/syscalls: Use copy_from_user for the faultable syscall buffer syscall_copy_user_array() copies the buffer argument of faultable syscalls (write, pwrite64, ...) with __copy_from_user(), which skips access_ok(). Use copy_from_user() instead so a non-user address is rejected and the entry is recorded as faulted. It could be a security concern for machines that trust perf a bit too much. Cc: stable@vger.kernel.org Fixes: baa031b7bd2ce ("tracing: Allow syscall trace events to read more than one user parameter") Link: https://patch.msgid.link/6ad19afd.a938ce27.16851e.3440@mx.google.com Signed-off-by: Haoran Wang Signed-off-by: Steven Rostedt --- kernel/trace/trace_syscalls.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/trace/trace_syscalls.c b/kernel/trace/trace_syscalls.c index e35744049e3f..d9190fdc030d 100644 --- a/kernel/trace/trace_syscalls.c +++ b/kernel/trace/trace_syscalls.c @@ -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; From 66877a312ae3c2c3d849e82e882f642ffd4ec3e2 Mon Sep 17 00:00:00 2001 From: David Carlier Date: Fri, 16 Oct 2026 07:16:34 +0100 Subject: [PATCH 20/27] tracing: Hold trace_types_lock when updating modules in persistent instances trace_module_record() walks ftrace_trace_arrays from the module notifier without trace_types_lock. A concurrent rmdir of an instance removes it from the list and frees it under that lock, so a module load or unload can walk into a freed trace_array. Cc: stable@vger.kernel.org Fixes: 5f3719f697c3 ("tracing: Update modules to persistent instances when loaded") Link: https://patch.msgid.link/20261016061638.214847-1-devnexen@gmail.com Signed-off-by: David Carlier Signed-off-by: Steven Rostedt --- kernel/trace/trace.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/kernel/trace/trace.c b/kernel/trace/trace.c index 48ecc4bb55e4..96005a930b25 100644 --- a/kernel/trace/trace.c +++ b/kernel/trace/trace.c @@ -9244,6 +9244,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 */ From 5a6bd62f09147b49b31fd19d4eba122cb5f941a1 Mon Sep 17 00:00:00 2001 From: David Carlier Date: Fri, 16 Oct 2026 07:16:35 +0100 Subject: [PATCH 21/27] tracing: Hold trace_types_lock when resetting last boot data from readers update_last_data_if_empty() is called from tracing_open() with O_TRUNC, tracing_read_pipe() and tracing_buffers_read() without trace_types_lock. Two readers can both pass the LAST_BOOT test and free module_delta twice, and a module notifier can update module_delta while it is being freed. Take trace_types_lock in those paths and keep the callers that already hold it on an unlocked variant. Cc: stable@vger.kernel.org Fixes: 32dc0042528d ("tracing: Reset last-boot buffers when reading out all cpu buffers") Link: https://patch.msgid.link/20261016061638.214847-2-devnexen@gmail.com Signed-off-by: David Carlier Signed-off-by: Steven Rostedt --- kernel/trace/trace.c | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/kernel/trace/trace.c b/kernel/trace/trace.c index 96005a930b25..08614af8fad1 100644 --- a/kernel/trace/trace.c +++ b/kernel/trace/trace.c @@ -3603,6 +3603,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) @@ -4697,7 +4698,7 @@ 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); } @@ -5371,8 +5372,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; @@ -5387,6 +5390,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. */ @@ -6476,7 +6489,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; From d5c42664fc094899af9aae5300b27fe8fdb658ef Mon Sep 17 00:00:00 2001 From: Donggeun Yoo Date: Tue, 15 Sep 2026 08:47:54 +0900 Subject: [PATCH 22/27] tracing: Save the event file instead of the compatible histogram create_field_var_hist() creates a histogram on the matched event to supply a field variable, and records it on the target so it can be torn down later: hist_data = find_compatible_hist(target_hist_data, file); ... /* Save the compatible histogram information */ var_hist->hist_data = hist_data; hist_data is not the histogram it creates. It is one that was already there, whose keys the new one copies. The saved pointer has a single user, unregister_field_var_hists(), which runs when the target is removed: file = hist_data->field_var_hists[i]->hist_data->event_file; find_compatible_hist() matches on keys alone, so it can pick one with no variables of its own. check_var_refs() then returns false and the user can remove it while the target still points at it: BUG: KASAN: slab-use-after-free in event_hist_trigger_free+0x2b2/0x320 Read of size 8 at addr ffff8880093b90e0 by task init/1 Freed by task 1: kfree+0x154/0x420 event_hist_trigger_free+0x1d5/0x320 event_hist_trigger_parse+0x35f3/0x69e0 trigger_process_regex+0x1a6/0x250 Save the event file instead. It is all the dereference ever wanted, and it does not go away when the user removes a trigger. Cc: stable@vger.kernel.org Reported-by: Sashiko Link: https://lore.kernel.org/all/20260914110753.221E61F000FF@smtp.kernel.org/ Link: https://patch.msgid.link/20260914234754.3676905-1-donggeunyoo.kernel@gmail.com Fixes: 02205a6752f2 ("tracing: Add support for 'field variables'") Signed-off-by: Donggeun Yoo Assisted-by: Claude:claude-fable-5 Acked-by: Tom Zanussi Signed-off-by: Steven Rostedt --- kernel/trace/trace_events_hist.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/kernel/trace/trace_events_hist.c b/kernel/trace/trace_events_hist.c index 5527605758cb..42b89171134d 100644 --- a/kernel/trace/trace_events_hist.c +++ b/kernel/trace/trace_events_hist.c @@ -557,7 +557,7 @@ struct field_var { }; struct field_var_hist { - struct hist_trigger_data *hist_data; + struct trace_event_file *file; char *cmd; }; @@ -3129,8 +3129,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, @@ -6362,7 +6362,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); From 3403d9bb27ebc3572bd048620746b4414e073bf9 Mon Sep 17 00:00:00 2001 From: Lee Jones Date: Mon, 19 Oct 2026 12:07:29 +0000 Subject: [PATCH 23/27] tracing/histograms: Verify event_call identity when updating synth_event refcount In event_hist_trigger_parse() and hist_unreg_all(), the histogram registration and unregistration paths look up find_synth_event() using trace_event_name(file->event_call) and adjust se->ref if a match is found. Because find_synth_event() only compares the event basename, registering or removing a histogram on a non-synthetic trace event whose basename matches a synthetic event erroneously modifies the synthetic event's reference count. If a histogram is registered on the non-synthetic event before the synthetic event is created and removed afterwards, se->ref is decremented without a matching increment, allowing the synthetic event to be freed while an active histogram action still holds a pointer to it. Introduce find_synth_event_call() to verify that &se->call matches file->event_call before adjusting se->ref. Fixes: 4b147936fa50 ("tracing: Add support for 'synthetic' events") Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20261019120730.2342739-1-lee@kernel.org Signed-off-by: Lee Jones Signed-off-by: Steven Rostedt --- kernel/trace/trace_events_hist.c | 11 +++-------- kernel/trace/trace_events_synth.c | 10 ++++++++++ kernel/trace/trace_synth.h | 1 + 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/kernel/trace/trace_events_hist.c b/kernel/trace/trace_events_hist.c index 42b89171134d..f39fa8942673 100644 --- a/kernel/trace/trace_events_hist.c +++ b/kernel/trace/trace_events_hist.c @@ -6812,7 +6812,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); @@ -6825,8 +6824,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--; @@ -6850,7 +6848,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; @@ -6944,8 +6941,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; @@ -6980,8 +6976,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: diff --git a/kernel/trace/trace_events_synth.c b/kernel/trace/trace_events_synth.c index dc15658a887c..e2870212e58d 100644 --- a/kernel/trace/trace_events_synth.c +++ b/kernel/trace/trace_events_synth.c @@ -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 }, diff --git a/kernel/trace/trace_synth.h b/kernel/trace/trace_synth.h index 43f6fb6078db..d4ebf01b7049 100644 --- a/kernel/trace/trace_synth.h +++ b/kernel/trace/trace_synth.h @@ -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 */ From f86641ffea95ee6f62d829929a69be7ab1496ce5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A1n=20Garc=C3=ADa=20Casado?= Date: Mon, 19 Oct 2026 22:53:44 +0200 Subject: [PATCH 24/27] tracing/osnoise: Add __printf attribute to osnoise_print() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Building kernel/trace/trace_osnoise.o with W=1 triggers the following compiler warning: kernel/trace/trace_osnoise.c:96:17: warning: function 'osnoise_print' might be a candidate for 'gnu_printf' format attribute [-Wsuggest-attribute=format] osnoise_print() forwards its format string and va_list arguments to trace_array_vprintk(), which is annotated with __printf(3, 0). Add the __printf(1, 2) attribute to osnoise_print() to silence the W=1 warning and enable compile-time format string checking for its callers. Link: https://patch.msgid.link/20261019205344.190022-1-adriangarciacasado42@gmail.com Signed-off-by: Adrián García Casado Signed-off-by: Steven Rostedt --- kernel/trace/trace_osnoise.c | 1 + 1 file changed, 1 insertion(+) diff --git a/kernel/trace/trace_osnoise.c b/kernel/trace/trace_osnoise.c index 0e1265acd1cc..995da9e0b548 100644 --- a/kernel/trace/trace_osnoise.c +++ b/kernel/trace/trace_osnoise.c @@ -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; From 107b87efc8fcd6a597f1b534f116385620770e56 Mon Sep 17 00:00:00 2001 From: Steven Rostedt Date: Thu, 22 Oct 2026 15:33:59 -0400 Subject: [PATCH 25/27] cpuidle: haltpoll: Only define guest_halt_poll_ns tracepoint when used Tracepoints that are defined take up around 5K each, even if they are not used. When a tracepoint is used only in code when a specific config is enabled, their definition must also be only defined when the same config is enabled otherwise it wastes memory. The tracepoint guest_halt_poll_ns is only used when CONFIG_CPU_IDLE_GOV_HALTPOLL is enabled. Guard the definition of that tracepoint with the same config. Link: https://patch.msgid.link/20261022153359.2433474b@gandalf.local.home Fixes: 0da11bf0cab90 ("cpuidle: haltpoll: Add trace points for guest_halt_poll_ns grow/shrink") Acked-by: Rafael J. Wysocki Signed-off-by: Steven Rostedt --- include/trace/events/power.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/include/trace/events/power.h b/include/trace/events/power.h index 370f8df2fdb4..4d17eb307b6a 100644 --- a/include/trace/events/power.h +++ b/include/trace/events/power.h @@ -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 */ From c0c7c3f9d2e7d9ecc44e536d6f39f44b1faf6044 Mon Sep 17 00:00:00 2001 From: Haoran Wang Date: Fri, 16 Oct 2026 16:30:00 +0800 Subject: [PATCH 26/27] tracing: Guard trace_event_get_offsets() against an empty fields list trace_event_get_offsets() dereferences the first entry of the fields list returned by trace_get_fields() via list_first_entry(). list_first_entry() then returns the list head itself, and tail->offset + tail->size reads past it. The corrupted result is used as the BPF context-size bound in __perf_event_set_bpf_prog(): int off = trace_event_get_offsets(event->tp_event); if (prog->aux->max_ctx_offset > off) return -EACCES; so a context-reading BPF program that ought to be rejected can instead be attached to a zero-field tracepoint (which still carries TRACE_EVENT_FL_TRACEPOINT) and read adjacent kernel memory when the tracepoint fires, bypassing the verifier's context-size confinement. This is reachable through user_events: a user can register an event with no fields (e.g. `echo u:my_event > dynamic_events`), which leaves user->fields empty while the event remains a BPF-attachable tracepoint, trace_get_fields() then routes through user_event_get_fields() to that empty list. The corrupted result after list_first_entry is then used as the BPF context-size bound in __perf_event_set_bpf_prog(), which lets context-reading BPF programs attach to a zero-field tracepoint when they should be rejected with -EACCES. Add list empty check to fix this bug. Link: https://patch.msgid.link/6ad1f2ca.6048f038.32d739.a530@mx.google.com Signed-off-by: Haoran Wang Signed-off-by: Steven Rostedt --- kernel/trace/trace_events.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/kernel/trace/trace_events.c b/kernel/trace/trace_events.c index a1ac2a02f4e2..6afed2091e19 100644 --- a/kernel/trace/trace_events.c +++ b/kernel/trace/trace_events.c @@ -246,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() From 1fdc4164e488916402badddbb1f51e9380dabb91 Mon Sep 17 00:00:00 2001 From: Kyle Zeng Date: Fri, 23 Oct 2026 10:46:20 -0700 Subject: [PATCH 27/27] tracing: Reject incomplete event filter expressions A non-empty expression normally finishes in predicate_parse()'s inner loop, which resolves the pending logical operators before jumping to finalization. If the input ends after an operator, possibly followed by unary '!' tokens, the outer loop instead falls through with unresolved operator state. For example, "common_pid == 1 || common_pid == 2 &&" leaves prog[0].target equal to -1. The second pass then reads prog[-1] before the forward-jump check can reject it. Depending on adjacent slab contents, it can also copy an out-of-bounds word into a branch target that passes the forward-only check. A self-targeted perf tracepoint counting event can reach this path through PERF_EVENT_IOC_SET_FILTER. Reject the outer-loop fallthrough when a predicate has already been parsed. This ensures that non-empty expressions only reach finalization after the inner loop has resolved their operators. Keep the existing no-filter error for input that has no predicates. Add a regression test for trailing logical operators and unary negations. Fixes: 80765597bc587 ("tracing: Rewrite filter logic to be simpler and faster") Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20261023174620.91969-1-kylebot@openai.com Assisted-by: Codex:gpt-6-astra Acked-by: Masami Hiramatsu (Google) Tested-by: JongHyuk Kim(MalHyuk) Signed-off-by: Kyle Zeng Signed-off-by: Steven Rostedt --- kernel/trace/trace_events_filter.c | 6 +++++ .../test.d/filter/event-filter-incomplete.tc | 24 +++++++++++++++++++ 2 files changed, 30 insertions(+) create mode 100644 tools/testing/selftests/ftrace/test.d/filter/event-filter-incomplete.tc diff --git a/kernel/trace/trace_events_filter.c b/kernel/trace/trace_events_filter.c index b3b13700abce..5e74d2dae40a 100644 --- a/kernel/trace/trace_events_filter.c +++ b/kernel/trace/trace_events_filter.c @@ -598,6 +598,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 '(' */ diff --git a/tools/testing/selftests/ftrace/test.d/filter/event-filter-incomplete.tc b/tools/testing/selftests/ftrace/test.d/filter/event-filter-incomplete.tc new file mode 100644 index 000000000000..cc6df762845f --- /dev/null +++ b/tools/testing/selftests/ftrace/test.d/filter/event-filter-incomplete.tc @@ -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"