From f244b4dc53e520d4570b2610436aba0593ce6f55 Mon Sep 17 00:00:00 2001 From: Petr Mladek Date: Fri, 21 Jan 2022 18:36:28 +0530 Subject: [PATCH 01/67] printk: ringbuffer: Improve prb_next_seq() performance prb_next_seq() always iterates from the first known sequence number. In the worst case, it might loop 8k times for 256kB buffer, 15k times for 512kB buffer, and 64k times for 2MB buffer. It was reported that polling and reading using syslog interface might occupy 50% of CPU. Speedup the search by storing @id of the last finalized descriptor. The loop is still needed because the @id is stored and read in the best effort way. An atomic variable is used to keep the @id consistent. But the stores and reads are not serialized against each other. The descriptor could get reused in the meantime. The related sequence number will be used only when it is still valid. An invalid value should be read _only_ when there is a flood of messages and the ringbuffer is rapidly reused. The performance is the least problem in this case. Reported-by: Chunlei Wang Signed-off-by: Mukesh Ojha Reviewed-by: John Ogness Signed-off-by: Petr Mladek Link: https://lore.kernel.org/r/1642770388-17327-1-git-send-email-quic_mojha@quicinc.com Link: https://lore.kernel.org/lkml/YXlddJxLh77DKfIO@alley/T/#m43062e8b2a17f8dbc8c6ccdb8851fb0dbaabbb14 --- kernel/printk/printk_ringbuffer.c | 52 ++++++++++++++++++++++++++++--- kernel/printk/printk_ringbuffer.h | 2 ++ 2 files changed, 49 insertions(+), 5 deletions(-) diff --git a/kernel/printk/printk_ringbuffer.c b/kernel/printk/printk_ringbuffer.c index 8a7b7362c0dd..2b7b6ddab4f7 100644 --- a/kernel/printk/printk_ringbuffer.c +++ b/kernel/printk/printk_ringbuffer.c @@ -474,8 +474,10 @@ static enum desc_state desc_read(struct prb_desc_ring *desc_ring, * state has been re-checked. A memcpy() for all of @desc * cannot be used because of the atomic_t @state_var field. */ - memcpy(&desc_out->text_blk_lpos, &desc->text_blk_lpos, - sizeof(desc_out->text_blk_lpos)); /* LMM(desc_read:C) */ + if (desc_out) { + memcpy(&desc_out->text_blk_lpos, &desc->text_blk_lpos, + sizeof(desc_out->text_blk_lpos)); /* LMM(desc_read:C) */ + } if (seq_out) *seq_out = info->seq; /* also part of desc_read:C */ if (caller_id_out) @@ -528,7 +530,8 @@ static enum desc_state desc_read(struct prb_desc_ring *desc_ring, state_val = atomic_long_read(state_var); /* LMM(desc_read:E) */ d_state = get_desc_state(id, state_val); out: - atomic_long_set(&desc_out->state_var, state_val); + if (desc_out) + atomic_long_set(&desc_out->state_var, state_val); return d_state; } @@ -1449,6 +1452,9 @@ static void desc_make_final(struct prb_desc_ring *desc_ring, unsigned long id) atomic_long_cmpxchg_relaxed(&d->state_var, prev_state_val, DESC_SV(id, desc_finalized)); /* LMM(desc_make_final:A) */ + + /* Best effort to remember the last finalized @id. */ + atomic_long_set(&desc_ring->last_finalized_id, id); } /** @@ -1657,7 +1663,12 @@ void prb_commit(struct prb_reserved_entry *e) */ void prb_final_commit(struct prb_reserved_entry *e) { + struct prb_desc_ring *desc_ring = &e->rb->desc_ring; + _prb_commit(e, desc_finalized); + + /* Best effort to remember the last finalized @id. */ + atomic_long_set(&desc_ring->last_finalized_id, e->id); } /* @@ -2005,9 +2016,39 @@ u64 prb_first_valid_seq(struct printk_ringbuffer *rb) */ u64 prb_next_seq(struct printk_ringbuffer *rb) { - u64 seq = 0; + struct prb_desc_ring *desc_ring = &rb->desc_ring; + enum desc_state d_state; + unsigned long id; + u64 seq; - /* Search forward from the oldest descriptor. */ + /* Check if the cached @id still points to a valid @seq. */ + id = atomic_long_read(&desc_ring->last_finalized_id); + d_state = desc_read(desc_ring, id, NULL, &seq, NULL); + + if (d_state == desc_finalized || d_state == desc_reusable) { + /* + * Begin searching after the last finalized record. + * + * On 0, the search must begin at 0 because of hack#2 + * of the bootstrapping phase it is not known if a + * record at index 0 exists. + */ + if (seq != 0) + seq++; + } else { + /* + * The information about the last finalized sequence number + * has gone. It should happen only when there is a flood of + * new messages and the ringbuffer is rapidly recycled. + * Give up and start from the beginning. + */ + seq = 0; + } + + /* + * The information about the last finalized @seq might be inaccurate. + * Search forward to find the current one. + */ while (_prb_read_valid(rb, &seq, NULL, NULL)) seq++; @@ -2044,6 +2085,7 @@ void prb_init(struct printk_ringbuffer *rb, rb->desc_ring.infos = infos; atomic_long_set(&rb->desc_ring.head_id, DESC0_ID(descbits)); atomic_long_set(&rb->desc_ring.tail_id, DESC0_ID(descbits)); + atomic_long_set(&rb->desc_ring.last_finalized_id, DESC0_ID(descbits)); rb->text_data_ring.size_bits = textbits; rb->text_data_ring.data = text_buf; diff --git a/kernel/printk/printk_ringbuffer.h b/kernel/printk/printk_ringbuffer.h index 73cc80e01cef..18cd25e489b8 100644 --- a/kernel/printk/printk_ringbuffer.h +++ b/kernel/printk/printk_ringbuffer.h @@ -75,6 +75,7 @@ struct prb_desc_ring { struct printk_info *infos; atomic_long_t head_id; atomic_long_t tail_id; + atomic_long_t last_finalized_id; }; /* @@ -258,6 +259,7 @@ static struct printk_ringbuffer name = { \ .infos = &_##name##_infos[0], \ .head_id = ATOMIC_INIT(DESC0_ID(descbits)), \ .tail_id = ATOMIC_INIT(DESC0_ID(descbits)), \ + .last_finalized_id = ATOMIC_INIT(DESC0_ID(descbits)), \ }, \ .text_data_ring = { \ .size_bits = (avgtextbits) + (descbits), \ From d75b26f880f60ead301e79ba0f4a635c5a60767f Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Thu, 27 Jan 2022 20:12:32 +0200 Subject: [PATCH 02/67] vsprintf: Fix potential unaligned access The %p4cc specifier in some cases might get an unaligned pointer. Due to this we need to make copy to local variable once to avoid potential crashes on some architectures due to improper access. Fixes: af612e43de6d ("lib/vsprintf: Add support for printing V4L2 and DRM fourccs") Cc: Sakari Ailus Signed-off-by: Andy Shevchenko Reviewed-by: Petr Mladek Signed-off-by: Petr Mladek Link: https://lore.kernel.org/r/20220127181233.72910-1-andriy.shevchenko@linux.intel.com --- lib/vsprintf.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/lib/vsprintf.c b/lib/vsprintf.c index 53d6081f9e8b..3e416a3cb66d 100644 --- a/lib/vsprintf.c +++ b/lib/vsprintf.c @@ -49,6 +49,7 @@ #include /* for PAGE_SIZE */ #include /* cpu_to_le16 */ +#include #include #include "kstrtox.h" @@ -1771,7 +1772,7 @@ char *fourcc_string(char *buf, char *end, const u32 *fourcc, char output[sizeof("0123 little-endian (0x01234567)")]; char *p = output; unsigned int i; - u32 val; + u32 orig, val; if (fmt[1] != 'c' || fmt[2] != 'c') return error_string(buf, end, "(%p4?)", spec); @@ -1779,21 +1780,22 @@ char *fourcc_string(char *buf, char *end, const u32 *fourcc, if (check_pointer(&buf, end, fourcc, spec)) return buf; - val = *fourcc & ~BIT(31); + orig = get_unaligned(fourcc); + val = orig & ~BIT(31); - for (i = 0; i < sizeof(*fourcc); i++) { + for (i = 0; i < sizeof(u32); i++) { unsigned char c = val >> (i * 8); /* Print non-control ASCII characters as-is, dot otherwise */ *p++ = isascii(c) && isprint(c) ? c : '.'; } - strcpy(p, *fourcc & BIT(31) ? " big-endian" : " little-endian"); + strcpy(p, orig & BIT(31) ? " big-endian" : " little-endian"); p += strlen(p); *p++ = ' '; *p++ = '('; - p = special_hex_number(p, output + sizeof(output) - 2, *fourcc, sizeof(u32)); + p = special_hex_number(p, output + sizeof(output) - 2, orig, sizeof(u32)); *p++ = ')'; *p = '\0'; From f74a08fc61073cc5b5f4e24eb513f0b79f4f6ce7 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Thu, 27 Jan 2022 20:12:33 +0200 Subject: [PATCH 03/67] vsprintf: Move space out of string literals in fourcc_string() The literals "big-endian" and "little-endian" may be potentially occurred in other places. Dropping space allows linker to merge them by using only a single copy. Signed-off-by: Andy Shevchenko Acked-by: Sakari Ailus Reviewed-by: Petr Mladek Signed-off-by: Petr Mladek Link: https://lore.kernel.org/r/20220127181233.72910-2-andriy.shevchenko@linux.intel.com --- lib/vsprintf.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/vsprintf.c b/lib/vsprintf.c index 3e416a3cb66d..f96c66899df6 100644 --- a/lib/vsprintf.c +++ b/lib/vsprintf.c @@ -1790,7 +1790,8 @@ char *fourcc_string(char *buf, char *end, const u32 *fourcc, *p++ = isascii(c) && isprint(c) ? c : '.'; } - strcpy(p, orig & BIT(31) ? " big-endian" : " little-endian"); + *p++ = ' '; + strcpy(p, orig & BIT(31) ? "big-endian" : "little-endian"); p += strlen(p); *p++ = ' '; From 1581a884b7ca5592270caa010a910f2ed4f7b5f5 Mon Sep 17 00:00:00 2001 From: Tom Zanussi Date: Thu, 27 Jan 2022 15:44:19 -0600 Subject: [PATCH 04/67] tracing: Remove size restriction on tracing_log_err cmd strings Currently, tracing_log_err.cmd strings are restricted to a length of MAX_FILTER_STR_VAL (256), which is too short for some commands already seen in the wild (with cmd strings longer than that showing up truncated). Remove the restriction so that no command string is ever truncated. Link: https://lkml.kernel.org/r/ca965f23256b350ebd94b3dc1a319f28e8267f5f.1643319703.git.zanussi@kernel.org Signed-off-by: Tom Zanussi Signed-off-by: Steven Rostedt (Google) --- kernel/trace/trace.c | 55 +++++++++++++++++++++++++++++++++----------- kernel/trace/trace.h | 2 +- 2 files changed, 43 insertions(+), 14 deletions(-) diff --git a/kernel/trace/trace.c b/kernel/trace/trace.c index 7c2578efde26..7c85ce9ffdc3 100644 --- a/kernel/trace/trace.c +++ b/kernel/trace/trace.c @@ -7723,7 +7723,7 @@ const struct file_operations trace_min_max_fops = { struct err_info { const char **errs; /* ptr to loc-specific array of err strings */ u8 type; /* index into errs -> specific err string */ - u8 pos; /* MAX_FILTER_STR_VAL = 256 */ + u16 pos; /* caret position */ u64 ts; }; @@ -7731,26 +7731,52 @@ struct tracing_log_err { struct list_head list; struct err_info info; char loc[TRACING_LOG_LOC_MAX]; /* err location */ - char cmd[MAX_FILTER_STR_VAL]; /* what caused err */ + char *cmd; /* what caused err */ }; static DEFINE_MUTEX(tracing_err_log_lock); -static struct tracing_log_err *get_tracing_log_err(struct trace_array *tr) +static struct tracing_log_err *alloc_tracing_log_err(int len) +{ + struct tracing_log_err *err; + + err = kzalloc(sizeof(*err), GFP_KERNEL); + if (!err) + return ERR_PTR(-ENOMEM); + + err->cmd = kzalloc(len, GFP_KERNEL); + if (!err->cmd) { + kfree(err); + return ERR_PTR(-ENOMEM); + } + + return err; +} + +static void free_tracing_log_err(struct tracing_log_err *err) +{ + kfree(err->cmd); + kfree(err); +} + +static struct tracing_log_err *get_tracing_log_err(struct trace_array *tr, + int len) { struct tracing_log_err *err; if (tr->n_err_log_entries < TRACING_LOG_ERRS_MAX) { - err = kzalloc(sizeof(*err), GFP_KERNEL); - if (!err) - err = ERR_PTR(-ENOMEM); - else + err = alloc_tracing_log_err(len); + if (PTR_ERR(err) != -ENOMEM) tr->n_err_log_entries++; return err; } err = list_first_entry(&tr->err_log, struct tracing_log_err, list); + kfree(err->cmd); + err->cmd = kzalloc(len, GFP_KERNEL); + if (!err->cmd) + return ERR_PTR(-ENOMEM); list_del(&err->list); return err; @@ -7811,22 +7837,25 @@ unsigned int err_pos(char *cmd, const char *str) */ void tracing_log_err(struct trace_array *tr, const char *loc, const char *cmd, - const char **errs, u8 type, u8 pos) + const char **errs, u8 type, u16 pos) { struct tracing_log_err *err; + int len = 0; if (!tr) tr = &global_trace; + len += sizeof(CMD_PREFIX) + 2 * sizeof("\n") + strlen(cmd) + 1; + mutex_lock(&tracing_err_log_lock); - err = get_tracing_log_err(tr); + err = get_tracing_log_err(tr, len); if (PTR_ERR(err) == -ENOMEM) { mutex_unlock(&tracing_err_log_lock); return; } snprintf(err->loc, TRACING_LOG_LOC_MAX, "%s: error: ", loc); - snprintf(err->cmd, MAX_FILTER_STR_VAL,"\n" CMD_PREFIX "%s\n", cmd); + snprintf(err->cmd, len, "\n" CMD_PREFIX "%s\n", cmd); err->info.errs = errs; err->info.type = type; @@ -7844,7 +7873,7 @@ static void clear_tracing_err_log(struct trace_array *tr) mutex_lock(&tracing_err_log_lock); list_for_each_entry_safe(err, next, &tr->err_log, list) { list_del(&err->list); - kfree(err); + free_tracing_log_err(err); } tr->n_err_log_entries = 0; @@ -7872,9 +7901,9 @@ static void tracing_err_log_seq_stop(struct seq_file *m, void *v) mutex_unlock(&tracing_err_log_lock); } -static void tracing_err_log_show_pos(struct seq_file *m, u8 pos) +static void tracing_err_log_show_pos(struct seq_file *m, u16 pos) { - u8 i; + u16 i; for (i = 0; i < sizeof(CMD_PREFIX) - 1; i++) seq_putc(m, ' '); diff --git a/kernel/trace/trace.h b/kernel/trace/trace.h index d038ddbf1bea..0f5e22238cd2 100644 --- a/kernel/trace/trace.h +++ b/kernel/trace/trace.h @@ -1878,7 +1878,7 @@ extern ssize_t trace_parse_run_command(struct file *file, extern unsigned int err_pos(char *cmd, const char *str); extern void tracing_log_err(struct trace_array *tr, const char *loc, const char *cmd, - const char **errs, u8 type, u8 pos); + const char **errs, u8 type, u16 pos); /* * Normal trace_printk() and friends allocates special buffers From edfeed318d59ff242e895bf906223fc0b915117c Mon Sep 17 00:00:00 2001 From: Tom Zanussi Date: Fri, 28 Jan 2022 14:08:26 -0600 Subject: [PATCH 05/67] tracing: Remove size restriction on hist trigger cmd error logging Currently, hist trigger command error strings are restricted to a length of MAX_FILTER_STR_VAL (256), which is too short for some commands already seen in the wild (with cmd strings longer than that showing up truncated in err_log). Remove the restriction so that no hist trigger command error string is ever truncated. Link: https://lkml.kernel.org/r/0f9d46407222eaf6632cd3b417bc50a11f401b71.1643399022.git.zanussi@kernel.org Signed-off-by: Tom Zanussi Signed-off-by: Steven Rostedt (Google) --- kernel/trace/trace_events_hist.c | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/kernel/trace/trace_events_hist.c b/kernel/trace/trace_events_hist.c index ada87bfb5bb8..5e8970624bce 100644 --- a/kernel/trace/trace_events_hist.c +++ b/kernel/trace/trace_events_hist.c @@ -727,11 +727,16 @@ static struct track_data *track_data_alloc(unsigned int key_len, return data; } -static char last_cmd[MAX_FILTER_STR_VAL]; +#define HIST_PREFIX "hist:" + +static char *last_cmd; static char last_cmd_loc[MAX_FILTER_STR_VAL]; static int errpos(char *str) { + if (!str || !last_cmd) + return 0; + return err_pos(last_cmd, str); } @@ -739,12 +744,19 @@ static void last_cmd_set(struct trace_event_file *file, char *str) { const char *system = NULL, *name = NULL; struct trace_event_call *call; + int len = 0; if (!str) return; - strcpy(last_cmd, "hist:"); - strncat(last_cmd, str, MAX_FILTER_STR_VAL - 1 - sizeof("hist:")); + len += sizeof(HIST_PREFIX) + strlen(str) + 1; + kfree(last_cmd); + last_cmd = kzalloc(len, GFP_KERNEL); + if (!last_cmd) + return; + + strcpy(last_cmd, HIST_PREFIX); + strncat(last_cmd, str, len - sizeof(HIST_PREFIX)); if (file) { call = file->event_call; @@ -757,18 +769,22 @@ static void last_cmd_set(struct trace_event_file *file, char *str) } if (system) - snprintf(last_cmd_loc, MAX_FILTER_STR_VAL, "hist:%s:%s", system, name); + snprintf(last_cmd_loc, MAX_FILTER_STR_VAL, HIST_PREFIX "%s:%s", system, name); } -static void hist_err(struct trace_array *tr, u8 err_type, u8 err_pos) +static void hist_err(struct trace_array *tr, u8 err_type, u16 err_pos) { + if (!last_cmd) + return; + tracing_log_err(tr, last_cmd_loc, last_cmd, err_text, err_type, err_pos); } static void hist_err_clear(void) { - last_cmd[0] = '\0'; + if (last_cmd) + last_cmd[0] = '\0'; last_cmd_loc[0] = '\0'; } @@ -5610,7 +5626,7 @@ static int event_hist_trigger_print(struct seq_file *m, bool have_var = false; unsigned int i; - seq_puts(m, "hist:"); + seq_puts(m, HIST_PREFIX); if (data->name) seq_printf(m, "%s:", data->name); From 27c888da9867725784bad3d6455d6e53b425fa2b Mon Sep 17 00:00:00 2001 From: Tom Zanussi Date: Fri, 28 Jan 2022 14:08:27 -0600 Subject: [PATCH 06/67] tracing: Remove size restriction on synthetic event cmd error logging Currently, synthetic event command error strings are restricted to a length of MAX_FILTER_STR_VAL (256), which is too short for some commands already seen in the wild (with cmd strings longer than that showing up truncated in err_log). Remove the restriction so that no synthetic event command error string is ever truncated. Link: https://lkml.kernel.org/r/0376692396a81d0b795127c66ea92ca5bf60f481.1643399022.git.zanussi@kernel.org Signed-off-by: Tom Zanussi Signed-off-by: Steven Rostedt (Google) --- kernel/trace/trace_events_synth.c | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/kernel/trace/trace_events_synth.c b/kernel/trace/trace_events_synth.c index 154db74dadbc..fdd79e07e2fc 100644 --- a/kernel/trace/trace_events_synth.c +++ b/kernel/trace/trace_events_synth.c @@ -42,10 +42,13 @@ enum { ERRORS }; static const char *err_text[] = { ERRORS }; -static char last_cmd[MAX_FILTER_STR_VAL]; +static char *last_cmd; static int errpos(const char *str) { + if (!str || !last_cmd) + return 0; + return err_pos(last_cmd, str); } @@ -54,11 +57,19 @@ static void last_cmd_set(const char *str) if (!str) return; - strncpy(last_cmd, str, MAX_FILTER_STR_VAL - 1); + kfree(last_cmd); + last_cmd = kzalloc(strlen(str) + 1, GFP_KERNEL); + if (!last_cmd) + return; + + strncpy(last_cmd, str, strlen(str) + 1); } -static void synth_err(u8 err_type, u8 err_pos) +static void synth_err(u8 err_type, u16 err_pos) { + if (!last_cmd) + return; + tracing_log_err(NULL, "synthetic_events", last_cmd, err_text, err_type, err_pos); } From 55bc8384d3deadce48923d8329c1434494c52273 Mon Sep 17 00:00:00 2001 From: "Steven Rostedt (Google)" Date: Thu, 10 Feb 2022 19:36:05 -0500 Subject: [PATCH 07/67] tracing: Save both wakee and current on wakeup events Use the sched_switch function to save both the wakee and the waker comms in the saved cmdlines list when sched_wakeup is done. Signed-off-by: Steven Rostedt (Google) --- kernel/trace/trace_sched_switch.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/trace/trace_sched_switch.c b/kernel/trace/trace_sched_switch.c index e304196d7c28..5cd33be2031b 100644 --- a/kernel/trace/trace_sched_switch.c +++ b/kernel/trace/trace_sched_switch.c @@ -44,7 +44,7 @@ probe_sched_wakeup(void *ignore, struct task_struct *wakee) if (!flags) return; - tracing_record_taskinfo(current, flags); + tracing_record_taskinfo_sched_switch(current, wakee, flags); } static int tracing_sched_register(void) From 7f5a08c79df35e68f1a43033450c5050f12bc155 Mon Sep 17 00:00:00 2001 From: Beau Belgrave Date: Tue, 18 Jan 2022 12:43:15 -0800 Subject: [PATCH 08/67] user_events: Add minimal support for trace_event into ftrace Minimal support for interacting with dynamic events, trace_event and ftrace. Core outline of flow between user process, ioctl and trace_event APIs. User mode processes that wish to use trace events to get data into ftrace, perf, eBPF, etc are limited to uprobes today. The user events features enables an ABI for user mode processes to create and write to trace events that are isolated from kernel level trace events. This enables a faster path for tracing from user mode data as well as opens managed code to participate in trace events, where stub locations are dynamic. User processes often want to trace only when it's useful. To enable this a set of pages are mapped into the user process space that indicate the current state of the user events that have been registered. User processes can check if their event is hooked to a trace/probe, and if it is, emit the event data out via the write() syscall. Two new files are introduced into tracefs to accomplish this: user_events_status - This file is mmap'd into participating user mode processes to indicate event status. user_events_data - This file is opened and register/delete ioctl's are issued to create/open/delete trace events that can be used for tracing. The typical scenario is on process start to mmap user_events_status. Processes then register the events they plan to use via the REG ioctl. The ioctl reads and updates the passed in user_reg struct. The status_index of the struct is used to know the byte in the status page to check for that event. The write_index of the struct is used to describe that event when writing out to the fd that was used for the ioctl call. The data must always include this index first when writing out data for an event. Data can be written either by write() or by writev(). For example, in memory: int index; char data[]; Psuedo code example of typical usage: struct user_reg reg; int page_fd = open("user_events_status", O_RDWR); char *page_data = mmap(NULL, PAGE_SIZE, PROT_READ, MAP_SHARED, page_fd, 0); close(page_fd); int data_fd = open("user_events_data", O_RDWR); reg.size = sizeof(reg); reg.name_args = (__u64)"test"; ioctl(data_fd, DIAG_IOCSREG, ®); int status_id = reg.status_index; int write_id = reg.write_index; struct iovec io[2]; io[0].iov_base = &write_id; io[0].iov_len = sizeof(write_id); io[1].iov_base = payload; io[1].iov_len = sizeof(payload); if (page_data[status_id]) writev(data_fd, io, 2); User events are also exposed via the dynamic_events tracefs file for both create and delete. Current status is exposed via the user_events_status tracefs file. Simple example to register a user event via dynamic_events: echo u:test >> dynamic_events cat dynamic_events u:test If an event is hooked to a probe, the probe hooked shows up: echo 1 > events/user_events/test/enable cat user_events_status 1:test # Used by ftrace Active: 1 Busy: 1 Max: 4096 If an event is not hooked to a probe, no probe status shows up: echo 0 > events/user_events/test/enable cat user_events_status 1:test Active: 1 Busy: 0 Max: 4096 Users can describe the trace event format via the following format: name[:FLAG1[,FLAG2...] [field1[;field2...]] Each field has the following format: type name Example for char array with a size of 20 named msg: echo 'u:detailed char[20] msg' >> dynamic_events cat dynamic_events u:detailed char[20] msg Data offsets are based on the data written out via write() and will be updated to reflect the correct offset in the trace_event fields. For dynamic data it is recommended to use the new __rel_loc data type. This type will be the same as __data_loc, but the offset is relative to this entry. This allows user_events to not worry about what common fields are being inserted before the data. The above format is valid for both the ioctl and the dynamic_events file. Link: https://lkml.kernel.org/r/20220118204326.2169-2-beaub@linux.microsoft.com Acked-by: Masami Hiramatsu Signed-off-by: Beau Belgrave Signed-off-by: Steven Rostedt (Google) --- include/uapi/linux/user_events.h | 116 +++ kernel/trace/Kconfig | 14 + kernel/trace/Makefile | 1 + kernel/trace/trace_events_user.c | 1187 ++++++++++++++++++++++++++++++ 4 files changed, 1318 insertions(+) create mode 100644 include/uapi/linux/user_events.h create mode 100644 kernel/trace/trace_events_user.c diff --git a/include/uapi/linux/user_events.h b/include/uapi/linux/user_events.h new file mode 100644 index 000000000000..e570840571e1 --- /dev/null +++ b/include/uapi/linux/user_events.h @@ -0,0 +1,116 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +/* + * Copyright (c) 2021, Microsoft Corporation. + * + * Authors: + * Beau Belgrave + */ +#ifndef _UAPI_LINUX_USER_EVENTS_H +#define _UAPI_LINUX_USER_EVENTS_H + +#include +#include + +#ifdef __KERNEL__ +#include +#else +#include +#endif + +#define USER_EVENTS_SYSTEM "user_events" +#define USER_EVENTS_PREFIX "u:" + +/* Bits 0-6 are for known probe types, Bit 7 is for unknown probes */ +#define EVENT_BIT_FTRACE 0 +#define EVENT_BIT_PERF 1 +#define EVENT_BIT_OTHER 7 + +#define EVENT_STATUS_FTRACE (1 << EVENT_BIT_FTRACE) +#define EVENT_STATUS_PERF (1 << EVENT_BIT_PERF) +#define EVENT_STATUS_OTHER (1 << EVENT_BIT_OTHER) + +/* Create dynamic location entry within a 32-bit value */ +#define DYN_LOC(offset, size) ((size) << 16 | (offset)) + +/* Use raw iterator for attached BPF program(s), no affect on ftrace/perf */ +#define FLAG_BPF_ITER (1 << 0) + +/* + * Describes an event registration and stores the results of the registration. + * This structure is passed to the DIAG_IOCSREG ioctl, callers at a minimum + * must set the size and name_args before invocation. + */ +struct user_reg { + + /* Input: Size of the user_reg structure being used */ + __u32 size; + + /* Input: Pointer to string with event name, description and flags */ + __u64 name_args; + + /* Output: Byte index of the event within the status page */ + __u32 status_index; + + /* Output: Index of the event to use when writing data */ + __u32 write_index; +}; + +#define DIAG_IOC_MAGIC '*' + +/* Requests to register a user_event */ +#define DIAG_IOCSREG _IOWR(DIAG_IOC_MAGIC, 0, struct user_reg*) + +/* Requests to delete a user_event */ +#define DIAG_IOCSDEL _IOW(DIAG_IOC_MAGIC, 1, char*) + +/* Data type that was passed to the BPF program */ +enum { + /* Data resides in kernel space */ + USER_BPF_DATA_KERNEL, + + /* Data resides in user space */ + USER_BPF_DATA_USER, + + /* Data is a pointer to a user_bpf_iter structure */ + USER_BPF_DATA_ITER, +}; + +/* + * Describes an iovec iterator that BPF programs can use to access data for + * a given user_event write() / writev() call. + */ +struct user_bpf_iter { + + /* Offset of the data within the first iovec */ + __u32 iov_offset; + + /* Number of iovec structures */ + __u32 nr_segs; + + /* Pointer to iovec structures */ + const struct iovec *iov; +}; + +/* Context that BPF programs receive when attached to a user_event */ +struct user_bpf_context { + + /* Data type being passed (see union below) */ + __u32 data_type; + + /* Length of the data */ + __u32 data_len; + + /* Pointer to data, varies by data type */ + union { + /* Kernel data (data_type == USER_BPF_DATA_KERNEL) */ + void *kdata; + + /* User data (data_type == USER_BPF_DATA_USER) */ + void *udata; + + /* Direct iovec (data_type == USER_BPF_DATA_ITER) */ + struct user_bpf_iter *iter; + }; +}; + +#endif /* _UAPI_LINUX_USER_EVENTS_H */ diff --git a/kernel/trace/Kconfig b/kernel/trace/Kconfig index a5eb5e7fd624..16a52a71732d 100644 --- a/kernel/trace/Kconfig +++ b/kernel/trace/Kconfig @@ -737,6 +737,20 @@ config SYNTH_EVENTS If in doubt, say N. +config USER_EVENTS + bool "User trace events" + select TRACING + select DYNAMIC_EVENTS + help + User trace events are user-defined trace events that + can be used like an existing kernel trace event. User trace + events are generated by writing to a tracefs file. User + processes can determine if their tracing events should be + generated by memory mapping a tracefs file and checking for + an associated byte being non-zero. + + If in doubt, say N. + config HIST_TRIGGERS bool "Histogram triggers" depends on ARCH_HAVE_NMI_SAFE_CMPXCHG diff --git a/kernel/trace/Makefile b/kernel/trace/Makefile index bedc5caceec7..19ef3758da95 100644 --- a/kernel/trace/Makefile +++ b/kernel/trace/Makefile @@ -82,6 +82,7 @@ obj-$(CONFIG_PROBE_EVENTS) += trace_eprobe.o obj-$(CONFIG_TRACE_EVENT_INJECT) += trace_events_inject.o obj-$(CONFIG_SYNTH_EVENTS) += trace_events_synth.o obj-$(CONFIG_HIST_TRIGGERS) += trace_events_hist.o +obj-$(CONFIG_USER_EVENTS) += trace_events_user.o obj-$(CONFIG_BPF_EVENTS) += bpf_trace.o obj-$(CONFIG_KPROBE_EVENTS) += trace_kprobe.o obj-$(CONFIG_TRACEPOINTS) += error_report-traces.o diff --git a/kernel/trace/trace_events_user.c b/kernel/trace/trace_events_user.c new file mode 100644 index 000000000000..77105233115e --- /dev/null +++ b/kernel/trace/trace_events_user.c @@ -0,0 +1,1187 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2021, Microsoft Corporation. + * + * Authors: + * Beau Belgrave + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "trace.h" +#include "trace_dynevent.h" + +#define USER_EVENTS_PREFIX_LEN (sizeof(USER_EVENTS_PREFIX)-1) + +#define FIELD_DEPTH_TYPE 0 +#define FIELD_DEPTH_NAME 1 +#define FIELD_DEPTH_SIZE 2 + +/* + * Limits how many trace_event calls user processes can create: + * Must be multiple of PAGE_SIZE. + */ +#define MAX_PAGES 1 +#define MAX_EVENTS (MAX_PAGES * PAGE_SIZE) + +/* Limit how long of an event name plus args within the subsystem. */ +#define MAX_EVENT_DESC 512 +#define EVENT_NAME(user_event) ((user_event)->tracepoint.name) +#define MAX_FIELD_ARRAY_SIZE 1024 + +static char *register_page_data; + +static DEFINE_MUTEX(reg_mutex); +static DEFINE_HASHTABLE(register_table, 4); +static DECLARE_BITMAP(page_bitmap, MAX_EVENTS); + +/* + * Stores per-event properties, as users register events + * within a file a user_event might be created if it does not + * already exist. These are globally used and their lifetime + * is tied to the refcnt member. These cannot go away until the + * refcnt reaches zero. + */ +struct user_event { + struct tracepoint tracepoint; + struct trace_event_call call; + struct trace_event_class class; + struct dyn_event devent; + struct hlist_node node; + struct list_head fields; + atomic_t refcnt; + int index; + int flags; +}; + +/* + * Stores per-file events references, as users register events + * within a file this structure is modified and freed via RCU. + * The lifetime of this struct is tied to the lifetime of the file. + * These are not shared and only accessible by the file that created it. + */ +struct user_event_refs { + struct rcu_head rcu; + int count; + struct user_event *events[]; +}; + +typedef void (*user_event_func_t) (struct user_event *user, + void *data, u32 datalen, + void *tpdata); + +static int user_event_parse(char *name, char *args, char *flags, + struct user_event **newuser); + +static u32 user_event_key(char *name) +{ + return jhash(name, strlen(name), 0); +} + +static struct list_head *user_event_get_fields(struct trace_event_call *call) +{ + struct user_event *user = (struct user_event *)call->data; + + return &user->fields; +} + +/* + * Parses a register command for user_events + * Format: event_name[:FLAG1[,FLAG2...]] [field1[;field2...]] + * + * Example event named 'test' with a 20 char 'msg' field with an unsigned int + * 'id' field after: + * test char[20] msg;unsigned int id + * + * NOTE: Offsets are from the user data perspective, they are not from the + * trace_entry/buffer perspective. We automatically add the common properties + * sizes to the offset for the user. + */ +static int user_event_parse_cmd(char *raw_command, struct user_event **newuser) +{ + char *name = raw_command; + char *args = strpbrk(name, " "); + char *flags; + + if (args) + *args++ = '\0'; + + flags = strpbrk(name, ":"); + + if (flags) + *flags++ = '\0'; + + return user_event_parse(name, args, flags, newuser); +} + +static int user_field_array_size(const char *type) +{ + const char *start = strchr(type, '['); + char val[8]; + char *bracket; + int size = 0; + + if (start == NULL) + return -EINVAL; + + if (strscpy(val, start + 1, sizeof(val)) <= 0) + return -EINVAL; + + bracket = strchr(val, ']'); + + if (!bracket) + return -EINVAL; + + *bracket = '\0'; + + if (kstrtouint(val, 0, &size)) + return -EINVAL; + + if (size > MAX_FIELD_ARRAY_SIZE) + return -EINVAL; + + return size; +} + +static int user_field_size(const char *type) +{ + /* long is not allowed from a user, since it's ambigious in size */ + if (strcmp(type, "s64") == 0) + return sizeof(s64); + if (strcmp(type, "u64") == 0) + return sizeof(u64); + if (strcmp(type, "s32") == 0) + return sizeof(s32); + if (strcmp(type, "u32") == 0) + return sizeof(u32); + if (strcmp(type, "int") == 0) + return sizeof(int); + if (strcmp(type, "unsigned int") == 0) + return sizeof(unsigned int); + if (strcmp(type, "s16") == 0) + return sizeof(s16); + if (strcmp(type, "u16") == 0) + return sizeof(u16); + if (strcmp(type, "short") == 0) + return sizeof(short); + if (strcmp(type, "unsigned short") == 0) + return sizeof(unsigned short); + if (strcmp(type, "s8") == 0) + return sizeof(s8); + if (strcmp(type, "u8") == 0) + return sizeof(u8); + if (strcmp(type, "char") == 0) + return sizeof(char); + if (strcmp(type, "unsigned char") == 0) + return sizeof(unsigned char); + if (str_has_prefix(type, "char[")) + return user_field_array_size(type); + if (str_has_prefix(type, "unsigned char[")) + return user_field_array_size(type); + if (str_has_prefix(type, "__data_loc ")) + return sizeof(u32); + if (str_has_prefix(type, "__rel_loc ")) + return sizeof(u32); + + /* Uknown basic type, error */ + return -EINVAL; +} + +static void user_event_destroy_fields(struct user_event *user) +{ + struct ftrace_event_field *field, *next; + struct list_head *head = &user->fields; + + list_for_each_entry_safe(field, next, head, link) { + list_del(&field->link); + kfree(field); + } +} + +static int user_event_add_field(struct user_event *user, const char *type, + const char *name, int offset, int size, + int is_signed, int filter_type) +{ + struct ftrace_event_field *field; + + field = kmalloc(sizeof(*field), GFP_KERNEL); + + if (!field) + return -ENOMEM; + + field->type = type; + field->name = name; + field->offset = offset; + field->size = size; + field->is_signed = is_signed; + field->filter_type = filter_type; + + list_add(&field->link, &user->fields); + + return 0; +} + +/* + * Parses the values of a field within the description + * Format: type name [size] + */ +static int user_event_parse_field(char *field, struct user_event *user, + u32 *offset) +{ + char *part, *type, *name; + u32 depth = 0, saved_offset = *offset; + int len, size = -EINVAL; + bool is_struct = false; + + field = skip_spaces(field); + + if (*field == '\0') + return 0; + + /* Handle types that have a space within */ + len = str_has_prefix(field, "unsigned "); + if (len) + goto skip_next; + + len = str_has_prefix(field, "struct "); + if (len) { + is_struct = true; + goto skip_next; + } + + len = str_has_prefix(field, "__data_loc unsigned "); + if (len) + goto skip_next; + + len = str_has_prefix(field, "__data_loc "); + if (len) + goto skip_next; + + len = str_has_prefix(field, "__rel_loc unsigned "); + if (len) + goto skip_next; + + len = str_has_prefix(field, "__rel_loc "); + if (len) + goto skip_next; + + goto parse; +skip_next: + type = field; + field = strpbrk(field + len, " "); + + if (field == NULL) + return -EINVAL; + + *field++ = '\0'; + depth++; +parse: + while ((part = strsep(&field, " ")) != NULL) { + switch (depth++) { + case FIELD_DEPTH_TYPE: + type = part; + break; + case FIELD_DEPTH_NAME: + name = part; + break; + case FIELD_DEPTH_SIZE: + if (!is_struct) + return -EINVAL; + + if (kstrtou32(part, 10, &size)) + return -EINVAL; + break; + default: + return -EINVAL; + } + } + + if (depth < FIELD_DEPTH_SIZE) + return -EINVAL; + + if (depth == FIELD_DEPTH_SIZE) + size = user_field_size(type); + + if (size == 0) + return -EINVAL; + + if (size < 0) + return size; + + *offset = saved_offset + size; + + return user_event_add_field(user, type, name, saved_offset, size, + type[0] != 'u', FILTER_OTHER); +} + +static void user_event_parse_flags(struct user_event *user, char *flags) +{ + char *flag; + + if (flags == NULL) + return; + + while ((flag = strsep(&flags, ",")) != NULL) { + if (strcmp(flag, "BPF_ITER") == 0) + user->flags |= FLAG_BPF_ITER; + } +} + +static int user_event_parse_fields(struct user_event *user, char *args) +{ + char *field; + u32 offset = sizeof(struct trace_entry); + int ret = -EINVAL; + + if (args == NULL) + return 0; + + while ((field = strsep(&args, ";")) != NULL) { + ret = user_event_parse_field(field, user, &offset); + + if (ret) + break; + } + + return ret; +} + +static struct trace_event_fields user_event_fields_array[1]; + +static enum print_line_t user_event_print_trace(struct trace_iterator *iter, + int flags, + struct trace_event *event) +{ + /* Unsafe to try to decode user provided print_fmt, use hex */ + trace_print_hex_dump_seq(&iter->seq, "", DUMP_PREFIX_OFFSET, 16, + 1, iter->ent, iter->ent_size, true); + + return trace_handle_return(&iter->seq); +} + +static struct trace_event_functions user_event_funcs = { + .trace = user_event_print_trace, +}; + +static int destroy_user_event(struct user_event *user) +{ + int ret = 0; + + /* Must destroy fields before call removal */ + user_event_destroy_fields(user); + + ret = trace_remove_event_call(&user->call); + + if (ret) + return ret; + + dyn_event_remove(&user->devent); + + register_page_data[user->index] = 0; + clear_bit(user->index, page_bitmap); + hash_del(&user->node); + + kfree(EVENT_NAME(user)); + kfree(user); + + return ret; +} + +static struct user_event *find_user_event(char *name, u32 *outkey) +{ + struct user_event *user; + u32 key = user_event_key(name); + + *outkey = key; + + hash_for_each_possible(register_table, user, node, key) + if (!strcmp(EVENT_NAME(user), name)) + return user; + + return NULL; +} + +/* + * Writes the user supplied payload out to a trace file. + */ +static void user_event_ftrace(struct user_event *user, void *data, u32 datalen, + void *tpdata) +{ + struct trace_event_file *file; + struct trace_entry *entry; + struct trace_event_buffer event_buffer; + + file = (struct trace_event_file *)tpdata; + + if (!file || + !(file->flags & EVENT_FILE_FL_ENABLED) || + trace_trigger_soft_disabled(file)) + return; + + /* Allocates and fills trace_entry, + 1 of this is data payload */ + entry = trace_event_buffer_reserve(&event_buffer, file, + sizeof(*entry) + datalen); + + if (unlikely(!entry)) + return; + + memcpy(entry + 1, data, datalen); + + trace_event_buffer_commit(&event_buffer); +} + +/* + * Update the register page that is shared between user processes. + */ +static void update_reg_page_for(struct user_event *user) +{ + struct tracepoint *tp = &user->tracepoint; + char status = 0; + + if (atomic_read(&tp->key.enabled) > 0) { + struct tracepoint_func *probe_func_ptr; + user_event_func_t probe_func; + + rcu_read_lock_sched(); + + probe_func_ptr = rcu_dereference_sched(tp->funcs); + + if (probe_func_ptr) { + do { + probe_func = probe_func_ptr->func; + + if (probe_func == user_event_ftrace) + status |= EVENT_STATUS_FTRACE; + else + status |= EVENT_STATUS_OTHER; + } while ((++probe_func_ptr)->func); + } + + rcu_read_unlock_sched(); + } + + register_page_data[user->index] = status; +} + +/* + * Register callback for our events from tracing sub-systems. + */ +static int user_event_reg(struct trace_event_call *call, + enum trace_reg type, + void *data) +{ + struct user_event *user = (struct user_event *)call->data; + int ret = 0; + + if (!user) + return -ENOENT; + + switch (type) { + case TRACE_REG_REGISTER: + ret = tracepoint_probe_register(call->tp, + call->class->probe, + data); + if (!ret) + goto inc; + break; + + case TRACE_REG_UNREGISTER: + tracepoint_probe_unregister(call->tp, + call->class->probe, + data); + goto dec; + + default: + break; + } + + return ret; +inc: + atomic_inc(&user->refcnt); + update_reg_page_for(user); + return 0; +dec: + update_reg_page_for(user); + atomic_dec(&user->refcnt); + return 0; +} + +static int user_event_create(const char *raw_command) +{ + struct user_event *user; + char *name; + int ret; + + if (!str_has_prefix(raw_command, USER_EVENTS_PREFIX)) + return -ECANCELED; + + raw_command += USER_EVENTS_PREFIX_LEN; + raw_command = skip_spaces(raw_command); + + name = kstrdup(raw_command, GFP_KERNEL); + + if (!name) + return -ENOMEM; + + mutex_lock(®_mutex); + ret = user_event_parse_cmd(name, &user); + mutex_unlock(®_mutex); + + if (ret) + kfree(name); + + return ret; +} + +static int user_event_show(struct seq_file *m, struct dyn_event *ev) +{ + struct user_event *user = container_of(ev, struct user_event, devent); + struct ftrace_event_field *field, *next; + struct list_head *head; + int depth = 0; + + seq_printf(m, "%s%s", USER_EVENTS_PREFIX, EVENT_NAME(user)); + + head = trace_get_fields(&user->call); + + list_for_each_entry_safe_reverse(field, next, head, link) { + if (depth == 0) + seq_puts(m, " "); + else + seq_puts(m, "; "); + + seq_printf(m, "%s %s", field->type, field->name); + + if (str_has_prefix(field->type, "struct ")) + seq_printf(m, " %d", field->size); + + depth++; + } + + seq_puts(m, "\n"); + + return 0; +} + +static bool user_event_is_busy(struct dyn_event *ev) +{ + struct user_event *user = container_of(ev, struct user_event, devent); + + return atomic_read(&user->refcnt) != 0; +} + +static int user_event_free(struct dyn_event *ev) +{ + struct user_event *user = container_of(ev, struct user_event, devent); + + if (atomic_read(&user->refcnt) != 0) + return -EBUSY; + + return destroy_user_event(user); +} + +static bool user_event_match(const char *system, const char *event, + int argc, const char **argv, struct dyn_event *ev) +{ + struct user_event *user = container_of(ev, struct user_event, devent); + + return strcmp(EVENT_NAME(user), event) == 0 && + (!system || strcmp(system, USER_EVENTS_SYSTEM) == 0); +} + +static struct dyn_event_operations user_event_dops = { + .create = user_event_create, + .show = user_event_show, + .is_busy = user_event_is_busy, + .free = user_event_free, + .match = user_event_match, +}; + +static int user_event_trace_register(struct user_event *user) +{ + int ret; + + ret = register_trace_event(&user->call.event); + + if (!ret) + return -ENODEV; + + ret = trace_add_event_call(&user->call); + + if (ret) + unregister_trace_event(&user->call.event); + + return ret; +} + +/* + * Parses the event name, arguments and flags then registers if successful. + * The name buffer lifetime is owned by this method for success cases only. + */ +static int user_event_parse(char *name, char *args, char *flags, + struct user_event **newuser) +{ + int ret; + int index; + u32 key; + struct user_event *user = find_user_event(name, &key); + + if (user) { + *newuser = user; + /* + * Name is allocated by caller, free it since it already exists. + * Caller only worries about failure cases for freeing. + */ + kfree(name); + return 0; + } + + index = find_first_zero_bit(page_bitmap, MAX_EVENTS); + + if (index == MAX_EVENTS) + return -EMFILE; + + user = kzalloc(sizeof(*user), GFP_KERNEL); + + if (!user) + return -ENOMEM; + + INIT_LIST_HEAD(&user->class.fields); + INIT_LIST_HEAD(&user->fields); + + user->tracepoint.name = name; + + user_event_parse_flags(user, flags); + + ret = user_event_parse_fields(user, args); + + if (ret) + goto put_user; + + /* Minimal print format */ + user->call.print_fmt = "\"\""; + + user->call.data = user; + user->call.class = &user->class; + user->call.name = name; + user->call.flags = TRACE_EVENT_FL_TRACEPOINT; + user->call.tp = &user->tracepoint; + user->call.event.funcs = &user_event_funcs; + + user->class.system = USER_EVENTS_SYSTEM; + user->class.fields_array = user_event_fields_array; + user->class.get_fields = user_event_get_fields; + user->class.reg = user_event_reg; + user->class.probe = user_event_ftrace; + + mutex_lock(&event_mutex); + ret = user_event_trace_register(user); + mutex_unlock(&event_mutex); + + if (ret) + goto put_user; + + user->index = index; + dyn_event_init(&user->devent, &user_event_dops); + dyn_event_add(&user->devent, &user->call); + set_bit(user->index, page_bitmap); + hash_add(register_table, &user->node, key); + + *newuser = user; + return 0; +put_user: + user_event_destroy_fields(user); + kfree(user); + return ret; +} + +/* + * Deletes a previously created event if it is no longer being used. + */ +static int delete_user_event(char *name) +{ + u32 key; + int ret; + struct user_event *user = find_user_event(name, &key); + + if (!user) + return -ENOENT; + + if (atomic_read(&user->refcnt) != 0) + return -EBUSY; + + mutex_lock(&event_mutex); + ret = destroy_user_event(user); + mutex_unlock(&event_mutex); + + return ret; +} + +/* + * Validates the user payload and writes via iterator. + */ +static ssize_t user_events_write_core(struct file *file, struct iov_iter *i) +{ + struct user_event_refs *refs; + struct user_event *user = NULL; + struct tracepoint *tp; + ssize_t ret = i->count; + int idx; + + if (unlikely(copy_from_iter(&idx, sizeof(idx), i) != sizeof(idx))) + return -EFAULT; + + rcu_read_lock_sched(); + + refs = rcu_dereference_sched(file->private_data); + + /* + * The refs->events array is protected by RCU, and new items may be + * added. But the user retrieved from indexing into the events array + * shall be immutable while the file is opened. + */ + if (likely(refs && idx < refs->count)) + user = refs->events[idx]; + + rcu_read_unlock_sched(); + + if (unlikely(user == NULL)) + return -ENOENT; + + tp = &user->tracepoint; + + /* + * It's possible key.enabled disables after this check, however + * we don't mind if a few events are included in this condition. + */ + if (likely(atomic_read(&tp->key.enabled) > 0)) { + struct tracepoint_func *probe_func_ptr; + user_event_func_t probe_func; + void *tpdata; + void *kdata; + u32 datalen; + + kdata = kmalloc(i->count, GFP_KERNEL); + + if (unlikely(!kdata)) + return -ENOMEM; + + datalen = copy_from_iter(kdata, i->count, i); + + rcu_read_lock_sched(); + + probe_func_ptr = rcu_dereference_sched(tp->funcs); + + if (probe_func_ptr) { + do { + probe_func = probe_func_ptr->func; + tpdata = probe_func_ptr->data; + probe_func(user, kdata, datalen, tpdata); + } while ((++probe_func_ptr)->func); + } + + rcu_read_unlock_sched(); + + kfree(kdata); + } + + return ret; +} + +static ssize_t user_events_write(struct file *file, const char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct iovec iov; + struct iov_iter i; + + if (unlikely(*ppos != 0)) + return -EFAULT; + + if (unlikely(import_single_range(READ, (char *)ubuf, count, &iov, &i))) + return -EFAULT; + + return user_events_write_core(file, &i); +} + +static ssize_t user_events_write_iter(struct kiocb *kp, struct iov_iter *i) +{ + return user_events_write_core(kp->ki_filp, i); +} + +static int user_events_ref_add(struct file *file, struct user_event *user) +{ + struct user_event_refs *refs, *new_refs; + int i, size, count = 0; + + refs = rcu_dereference_protected(file->private_data, + lockdep_is_held(®_mutex)); + + if (refs) { + count = refs->count; + + for (i = 0; i < count; ++i) + if (refs->events[i] == user) + return i; + } + + size = struct_size(refs, events, count + 1); + + new_refs = kzalloc(size, GFP_KERNEL); + + if (!new_refs) + return -ENOMEM; + + new_refs->count = count + 1; + + for (i = 0; i < count; ++i) + new_refs->events[i] = refs->events[i]; + + new_refs->events[i] = user; + + atomic_inc(&user->refcnt); + + rcu_assign_pointer(file->private_data, new_refs); + + if (refs) + kfree_rcu(refs, rcu); + + return i; +} + +static long user_reg_get(struct user_reg __user *ureg, struct user_reg *kreg) +{ + u32 size; + long ret; + + ret = get_user(size, &ureg->size); + + if (ret) + return ret; + + if (size > PAGE_SIZE) + return -E2BIG; + + return copy_struct_from_user(kreg, sizeof(*kreg), ureg, size); +} + +/* + * Registers a user_event on behalf of a user process. + */ +static long user_events_ioctl_reg(struct file *file, unsigned long uarg) +{ + struct user_reg __user *ureg = (struct user_reg __user *)uarg; + struct user_reg reg; + struct user_event *user; + char *name; + long ret; + + ret = user_reg_get(ureg, ®); + + if (ret) + return ret; + + name = strndup_user((const char __user *)(uintptr_t)reg.name_args, + MAX_EVENT_DESC); + + if (IS_ERR(name)) { + ret = PTR_ERR(name); + return ret; + } + + ret = user_event_parse_cmd(name, &user); + + if (ret) { + kfree(name); + return ret; + } + + ret = user_events_ref_add(file, user); + + /* Positive number is index and valid */ + if (ret < 0) + return ret; + + put_user((u32)ret, &ureg->write_index); + put_user(user->index, &ureg->status_index); + + return 0; +} + +/* + * Deletes a user_event on behalf of a user process. + */ +static long user_events_ioctl_del(struct file *file, unsigned long uarg) +{ + void __user *ubuf = (void __user *)uarg; + char *name; + long ret; + + name = strndup_user(ubuf, MAX_EVENT_DESC); + + if (IS_ERR(name)) + return PTR_ERR(name); + + ret = delete_user_event(name); + + kfree(name); + + return ret; +} + +/* + * Handles the ioctl from user mode to register or alter operations. + */ +static long user_events_ioctl(struct file *file, unsigned int cmd, + unsigned long uarg) +{ + long ret = -ENOTTY; + + switch (cmd) { + case DIAG_IOCSREG: + mutex_lock(®_mutex); + ret = user_events_ioctl_reg(file, uarg); + mutex_unlock(®_mutex); + break; + + case DIAG_IOCSDEL: + mutex_lock(®_mutex); + ret = user_events_ioctl_del(file, uarg); + mutex_unlock(®_mutex); + break; + } + + return ret; +} + +/* + * Handles the final close of the file from user mode. + */ +static int user_events_release(struct inode *node, struct file *file) +{ + struct user_event_refs *refs; + struct user_event *user; + int i; + + /* + * Ensure refs cannot change under any situation by taking the + * register mutex during the final freeing of the references. + */ + mutex_lock(®_mutex); + + refs = file->private_data; + + if (!refs) + goto out; + + /* + * The lifetime of refs has reached an end, it's tied to this file. + * The underlying user_events are ref counted, and cannot be freed. + * After this decrement, the user_events may be freed elsewhere. + */ + for (i = 0; i < refs->count; ++i) { + user = refs->events[i]; + + if (user) + atomic_dec(&user->refcnt); + } +out: + file->private_data = NULL; + + mutex_unlock(®_mutex); + + kfree(refs); + + return 0; +} + +static const struct file_operations user_data_fops = { + .write = user_events_write, + .write_iter = user_events_write_iter, + .unlocked_ioctl = user_events_ioctl, + .release = user_events_release, +}; + +/* + * Maps the shared page into the user process for checking if event is enabled. + */ +static int user_status_mmap(struct file *file, struct vm_area_struct *vma) +{ + unsigned long size = vma->vm_end - vma->vm_start; + + if (size != MAX_EVENTS) + return -EINVAL; + + return remap_pfn_range(vma, vma->vm_start, + virt_to_phys(register_page_data) >> PAGE_SHIFT, + size, vm_get_page_prot(VM_READ)); +} + +static void *user_seq_start(struct seq_file *m, loff_t *pos) +{ + if (*pos) + return NULL; + + return (void *)1; +} + +static void *user_seq_next(struct seq_file *m, void *p, loff_t *pos) +{ + ++*pos; + return NULL; +} + +static void user_seq_stop(struct seq_file *m, void *p) +{ +} + +static int user_seq_show(struct seq_file *m, void *p) +{ + struct user_event *user; + char status; + int i, active = 0, busy = 0, flags; + + mutex_lock(®_mutex); + + hash_for_each(register_table, i, user, node) { + status = register_page_data[user->index]; + flags = user->flags; + + seq_printf(m, "%d:%s", user->index, EVENT_NAME(user)); + + if (flags != 0 || status != 0) + seq_puts(m, " #"); + + if (status != 0) { + seq_puts(m, " Used by"); + if (status & EVENT_STATUS_FTRACE) + seq_puts(m, " ftrace"); + if (status & EVENT_STATUS_PERF) + seq_puts(m, " perf"); + if (status & EVENT_STATUS_OTHER) + seq_puts(m, " other"); + busy++; + } + + if (flags & FLAG_BPF_ITER) + seq_puts(m, " FLAG:BPF_ITER"); + + seq_puts(m, "\n"); + active++; + } + + mutex_unlock(®_mutex); + + seq_puts(m, "\n"); + seq_printf(m, "Active: %d\n", active); + seq_printf(m, "Busy: %d\n", busy); + seq_printf(m, "Max: %ld\n", MAX_EVENTS); + + return 0; +} + +static const struct seq_operations user_seq_ops = { + .start = user_seq_start, + .next = user_seq_next, + .stop = user_seq_stop, + .show = user_seq_show, +}; + +static int user_status_open(struct inode *node, struct file *file) +{ + return seq_open(file, &user_seq_ops); +} + +static const struct file_operations user_status_fops = { + .open = user_status_open, + .mmap = user_status_mmap, + .read = seq_read, + .llseek = seq_lseek, + .release = seq_release, +}; + +/* + * Creates a set of tracefs files to allow user mode interactions. + */ +static int create_user_tracefs(void) +{ + struct dentry *edata, *emmap; + + edata = tracefs_create_file("user_events_data", TRACE_MODE_WRITE, + NULL, NULL, &user_data_fops); + + if (!edata) { + pr_warn("Could not create tracefs 'user_events_data' entry\n"); + goto err; + } + + /* mmap with MAP_SHARED requires writable fd */ + emmap = tracefs_create_file("user_events_status", TRACE_MODE_WRITE, + NULL, NULL, &user_status_fops); + + if (!emmap) { + tracefs_remove(edata); + pr_warn("Could not create tracefs 'user_events_mmap' entry\n"); + goto err; + } + + return 0; +err: + return -ENODEV; +} + +static void set_page_reservations(bool set) +{ + int page; + + for (page = 0; page < MAX_PAGES; ++page) { + void *addr = register_page_data + (PAGE_SIZE * page); + + if (set) + SetPageReserved(virt_to_page(addr)); + else + ClearPageReserved(virt_to_page(addr)); + } +} + +static int __init trace_events_user_init(void) +{ + int ret; + + /* Zero all bits beside 0 (which is reserved for failures) */ + bitmap_zero(page_bitmap, MAX_EVENTS); + set_bit(0, page_bitmap); + + register_page_data = kzalloc(MAX_EVENTS, GFP_KERNEL); + + if (!register_page_data) + return -ENOMEM; + + set_page_reservations(true); + + ret = create_user_tracefs(); + + if (ret) { + pr_warn("user_events could not register with tracefs\n"); + set_page_reservations(false); + kfree(register_page_data); + return ret; + } + + if (dyn_event_register(&user_event_dops)) + pr_warn("user_events could not register with dyn_events\n"); + + return 0; +} + +fs_initcall(trace_events_user_init); From aa3b2b4c669205200615dd8a2cc4af4f81fd0335 Mon Sep 17 00:00:00 2001 From: Beau Belgrave Date: Tue, 18 Jan 2022 12:43:16 -0800 Subject: [PATCH 09/67] user_events: Add print_fmt generation support for basic types Addes print_fmt format generation for basic types that are supported for user processes. Only supports sizes that are the same on 32 and 64 bit. Link: https://lkml.kernel.org/r/20220118204326.2169-3-beaub@linux.microsoft.com Acked-by: Masami Hiramatsu Signed-off-by: Beau Belgrave Signed-off-by: Steven Rostedt (Google) --- kernel/trace/trace_events_user.c | 115 ++++++++++++++++++++++++++++++- 1 file changed, 113 insertions(+), 2 deletions(-) diff --git a/kernel/trace/trace_events_user.c b/kernel/trace/trace_events_user.c index 77105233115e..ddc5c3cf1bf8 100644 --- a/kernel/trace/trace_events_user.c +++ b/kernel/trace/trace_events_user.c @@ -359,6 +359,114 @@ static int user_event_parse_fields(struct user_event *user, char *args) static struct trace_event_fields user_event_fields_array[1]; +static const char *user_field_format(const char *type) +{ + if (strcmp(type, "s64") == 0) + return "%lld"; + if (strcmp(type, "u64") == 0) + return "%llu"; + if (strcmp(type, "s32") == 0) + return "%d"; + if (strcmp(type, "u32") == 0) + return "%u"; + if (strcmp(type, "int") == 0) + return "%d"; + if (strcmp(type, "unsigned int") == 0) + return "%u"; + if (strcmp(type, "s16") == 0) + return "%d"; + if (strcmp(type, "u16") == 0) + return "%u"; + if (strcmp(type, "short") == 0) + return "%d"; + if (strcmp(type, "unsigned short") == 0) + return "%u"; + if (strcmp(type, "s8") == 0) + return "%d"; + if (strcmp(type, "u8") == 0) + return "%u"; + if (strcmp(type, "char") == 0) + return "%d"; + if (strcmp(type, "unsigned char") == 0) + return "%u"; + if (strstr(type, "char[") != 0) + return "%s"; + + /* Unknown, likely struct, allowed treat as 64-bit */ + return "%llu"; +} + +static bool user_field_is_dyn_string(const char *type, const char **str_func) +{ + if (str_has_prefix(type, "__data_loc ")) { + *str_func = "__get_str"; + goto check; + } + + if (str_has_prefix(type, "__rel_loc ")) { + *str_func = "__get_rel_str"; + goto check; + } + + return false; +check: + return strstr(type, "char") != 0; +} + +#define LEN_OR_ZERO (len ? len - pos : 0) +static int user_event_set_print_fmt(struct user_event *user, char *buf, int len) +{ + struct ftrace_event_field *field, *next; + struct list_head *head = &user->fields; + int pos = 0, depth = 0; + const char *str_func; + + pos += snprintf(buf + pos, LEN_OR_ZERO, "\""); + + list_for_each_entry_safe_reverse(field, next, head, link) { + if (depth != 0) + pos += snprintf(buf + pos, LEN_OR_ZERO, " "); + + pos += snprintf(buf + pos, LEN_OR_ZERO, "%s=%s", + field->name, user_field_format(field->type)); + + depth++; + } + + pos += snprintf(buf + pos, LEN_OR_ZERO, "\""); + + list_for_each_entry_safe_reverse(field, next, head, link) { + if (user_field_is_dyn_string(field->type, &str_func)) + pos += snprintf(buf + pos, LEN_OR_ZERO, + ", %s(%s)", str_func, field->name); + else + pos += snprintf(buf + pos, LEN_OR_ZERO, + ", REC->%s", field->name); + } + + return pos + 1; +} +#undef LEN_OR_ZERO + +static int user_event_create_print_fmt(struct user_event *user) +{ + char *print_fmt; + int len; + + len = user_event_set_print_fmt(user, NULL, 0); + + print_fmt = kmalloc(len, GFP_KERNEL); + + if (!print_fmt) + return -ENOMEM; + + user_event_set_print_fmt(user, print_fmt, len); + + user->call.print_fmt = print_fmt; + + return 0; +} + static enum print_line_t user_event_print_trace(struct trace_iterator *iter, int flags, struct trace_event *event) @@ -392,6 +500,7 @@ static int destroy_user_event(struct user_event *user) clear_bit(user->index, page_bitmap); hash_del(&user->node); + kfree(user->call.print_fmt); kfree(EVENT_NAME(user)); kfree(user); @@ -669,8 +778,10 @@ static int user_event_parse(char *name, char *args, char *flags, if (ret) goto put_user; - /* Minimal print format */ - user->call.print_fmt = "\"\""; + ret = user_event_create_print_fmt(user); + + if (ret) + goto put_user; user->call.data = user; user->call.class = &user->class; From 9aed4e157d1ffe4aeebc005b4eceede1ed5a403a Mon Sep 17 00:00:00 2001 From: Beau Belgrave Date: Tue, 18 Jan 2022 12:43:17 -0800 Subject: [PATCH 10/67] user_events: Handle matching arguments from dyn_events Ensures that when dynamic events requests a match with arguments that they match what is in the user_event. Link: https://lkml.kernel.org/r/20220118204326.2169-4-beaub@linux.microsoft.com Acked-by: Masami Hiramatsu Signed-off-by: Beau Belgrave Signed-off-by: Steven Rostedt (Google) --- kernel/trace/trace_events_user.c | 77 +++++++++++++++++++++++++++++++- 1 file changed, 76 insertions(+), 1 deletion(-) diff --git a/kernel/trace/trace_events_user.c b/kernel/trace/trace_events_user.c index ddc5c3cf1bf8..a6794cb1f586 100644 --- a/kernel/trace/trace_events_user.c +++ b/kernel/trace/trace_events_user.c @@ -39,6 +39,7 @@ #define MAX_EVENT_DESC 512 #define EVENT_NAME(user_event) ((user_event)->tracepoint.name) #define MAX_FIELD_ARRAY_SIZE 1024 +#define MAX_FIELD_ARG_NAME 256 static char *register_page_data; @@ -700,13 +701,87 @@ static int user_event_free(struct dyn_event *ev) return destroy_user_event(user); } +static bool user_field_match(struct ftrace_event_field *field, int argc, + const char **argv, int *iout) +{ + char *field_name, *arg_name; + int len, pos, i = *iout; + bool colon = false, match = false; + + if (i >= argc) + return false; + + len = MAX_FIELD_ARG_NAME; + field_name = kmalloc(len, GFP_KERNEL); + arg_name = kmalloc(len, GFP_KERNEL); + + if (!arg_name || !field_name) + goto out; + + pos = 0; + + for (; i < argc; ++i) { + if (i != *iout) + pos += snprintf(arg_name + pos, len - pos, " "); + + pos += snprintf(arg_name + pos, len - pos, argv[i]); + + if (strchr(argv[i], ';')) { + ++i; + colon = true; + break; + } + } + + pos = 0; + + pos += snprintf(field_name + pos, len - pos, field->type); + pos += snprintf(field_name + pos, len - pos, " "); + pos += snprintf(field_name + pos, len - pos, field->name); + + if (colon) + pos += snprintf(field_name + pos, len - pos, ";"); + + *iout = i; + + match = strcmp(arg_name, field_name) == 0; +out: + kfree(arg_name); + kfree(field_name); + + return match; +} + +static bool user_fields_match(struct user_event *user, int argc, + const char **argv) +{ + struct ftrace_event_field *field, *next; + struct list_head *head = &user->fields; + int i = 0; + + list_for_each_entry_safe_reverse(field, next, head, link) + if (!user_field_match(field, argc, argv, &i)) + return false; + + if (i != argc) + return false; + + return true; +} + static bool user_event_match(const char *system, const char *event, int argc, const char **argv, struct dyn_event *ev) { struct user_event *user = container_of(ev, struct user_event, devent); + bool match; - return strcmp(EVENT_NAME(user), event) == 0 && + match = strcmp(EVENT_NAME(user), event) == 0 && (!system || strcmp(system, USER_EVENTS_SYSTEM) == 0); + + if (match && argc > 0) + match = user_fields_match(user, argc, argv); + + return match; } static struct dyn_event_operations user_event_dops = { From 3207d0459ef3789c7efa801b57123c8a79d05694 Mon Sep 17 00:00:00 2001 From: Beau Belgrave Date: Tue, 18 Jan 2022 12:43:18 -0800 Subject: [PATCH 11/67] user_events: Add basic perf and eBPF support Adds support to write out user_event data to perf_probe/perf files as well as to any attached eBPF program. Link: https://lkml.kernel.org/r/20220118204326.2169-5-beaub@linux.microsoft.com Acked-by: Masami Hiramatsu Signed-off-by: Beau Belgrave Signed-off-by: Steven Rostedt (Google) --- kernel/trace/trace_events_user.c | 72 +++++++++++++++++++++++++++++++- 1 file changed, 71 insertions(+), 1 deletion(-) diff --git a/kernel/trace/trace_events_user.c b/kernel/trace/trace_events_user.c index a6794cb1f586..371f31472156 100644 --- a/kernel/trace/trace_events_user.c +++ b/kernel/trace/trace_events_user.c @@ -551,6 +551,50 @@ static void user_event_ftrace(struct user_event *user, void *data, u32 datalen, trace_event_buffer_commit(&event_buffer); } +#ifdef CONFIG_PERF_EVENTS +/* + * Writes the user supplied payload out to perf ring buffer or eBPF program. + */ +static void user_event_perf(struct user_event *user, void *data, u32 datalen, + void *tpdata) +{ + struct hlist_head *perf_head; + + if (bpf_prog_array_valid(&user->call)) { + struct user_bpf_context context = {0}; + + context.data_len = datalen; + context.data_type = USER_BPF_DATA_KERNEL; + context.kdata = data; + + trace_call_bpf(&user->call, &context); + } + + perf_head = this_cpu_ptr(user->call.perf_events); + + if (perf_head && !hlist_empty(perf_head)) { + struct trace_entry *perf_entry; + struct pt_regs *regs; + size_t size = sizeof(*perf_entry) + datalen; + int context; + + perf_entry = perf_trace_buf_alloc(ALIGN(size, 8), + ®s, &context); + + if (unlikely(!perf_entry)) + return; + + perf_fetch_caller_regs(regs); + + memcpy(perf_entry + 1, data, datalen); + + perf_trace_buf_submit(perf_entry, size, context, + user->call.event.type, 1, regs, + perf_head, NULL); + } +} +#endif + /* * Update the register page that is shared between user processes. */ @@ -573,6 +617,10 @@ static void update_reg_page_for(struct user_event *user) if (probe_func == user_event_ftrace) status |= EVENT_STATUS_FTRACE; +#ifdef CONFIG_PERF_EVENTS + else if (probe_func == user_event_perf) + status |= EVENT_STATUS_PERF; +#endif else status |= EVENT_STATUS_OTHER; } while ((++probe_func_ptr)->func); @@ -612,8 +660,27 @@ static int user_event_reg(struct trace_event_call *call, data); goto dec; - default: +#ifdef CONFIG_PERF_EVENTS + case TRACE_REG_PERF_REGISTER: + ret = tracepoint_probe_register(call->tp, + call->class->perf_probe, + data); + if (!ret) + goto inc; break; + + case TRACE_REG_PERF_UNREGISTER: + tracepoint_probe_unregister(call->tp, + call->class->perf_probe, + data); + goto dec; + + case TRACE_REG_PERF_OPEN: + case TRACE_REG_PERF_CLOSE: + case TRACE_REG_PERF_ADD: + case TRACE_REG_PERF_DEL: + break; +#endif } return ret; @@ -870,6 +937,9 @@ static int user_event_parse(char *name, char *args, char *flags, user->class.get_fields = user_event_get_fields; user->class.reg = user_event_reg; user->class.probe = user_event_ftrace; +#ifdef CONFIG_PERF_EVENTS + user->class.perf_probe = user_event_perf; +#endif mutex_lock(&event_mutex); ret = user_event_trace_register(user); From 0279400ad38d858ed68f5d787385f6122d4170b2 Mon Sep 17 00:00:00 2001 From: Beau Belgrave Date: Tue, 18 Jan 2022 12:43:19 -0800 Subject: [PATCH 12/67] user_events: Optimize writing events by only copying data once Pass iterator through to probes to allow copying data directly to the probe buffers instead of taking multiple copies. Enables eBPF user and raw iterator types out to programs for no-copy scenarios. Link: https://lkml.kernel.org/r/20220118204326.2169-6-beaub@linux.microsoft.com Acked-by: Masami Hiramatsu Signed-off-by: Beau Belgrave Signed-off-by: Steven Rostedt (Google) --- kernel/trace/trace_events_user.c | 115 +++++++++++++++++++++++-------- 1 file changed, 85 insertions(+), 30 deletions(-) diff --git a/kernel/trace/trace_events_user.c b/kernel/trace/trace_events_user.c index 371f31472156..78b6b96c4cfa 100644 --- a/kernel/trace/trace_events_user.c +++ b/kernel/trace/trace_events_user.c @@ -41,6 +41,9 @@ #define MAX_FIELD_ARRAY_SIZE 1024 #define MAX_FIELD_ARG_NAME 256 +#define MAX_BPF_COPY_SIZE PAGE_SIZE +#define MAX_STACK_BPF_DATA 512 + static char *register_page_data; static DEFINE_MUTEX(reg_mutex); @@ -78,8 +81,7 @@ struct user_event_refs { struct user_event *events[]; }; -typedef void (*user_event_func_t) (struct user_event *user, - void *data, u32 datalen, +typedef void (*user_event_func_t) (struct user_event *user, struct iov_iter *i, void *tpdata); static int user_event_parse(char *name, char *args, char *flags, @@ -90,6 +92,20 @@ static u32 user_event_key(char *name) return jhash(name, strlen(name), 0); } +static __always_inline __must_check +size_t copy_nofault(void *addr, size_t bytes, struct iov_iter *i) +{ + size_t ret; + + pagefault_disable(); + + ret = copy_from_iter_nocache(addr, bytes, i); + + pagefault_enable(); + + return ret; +} + static struct list_head *user_event_get_fields(struct trace_event_call *call) { struct user_event *user = (struct user_event *)call->data; @@ -525,7 +541,7 @@ static struct user_event *find_user_event(char *name, u32 *outkey) /* * Writes the user supplied payload out to a trace file. */ -static void user_event_ftrace(struct user_event *user, void *data, u32 datalen, +static void user_event_ftrace(struct user_event *user, struct iov_iter *i, void *tpdata) { struct trace_event_file *file; @@ -541,41 +557,83 @@ static void user_event_ftrace(struct user_event *user, void *data, u32 datalen, /* Allocates and fills trace_entry, + 1 of this is data payload */ entry = trace_event_buffer_reserve(&event_buffer, file, - sizeof(*entry) + datalen); + sizeof(*entry) + i->count); if (unlikely(!entry)) return; - memcpy(entry + 1, data, datalen); - - trace_event_buffer_commit(&event_buffer); + if (unlikely(!copy_nofault(entry + 1, i->count, i))) + __trace_event_discard_commit(event_buffer.buffer, + event_buffer.event); + else + trace_event_buffer_commit(&event_buffer); } #ifdef CONFIG_PERF_EVENTS +static void user_event_bpf(struct user_event *user, struct iov_iter *i) +{ + struct user_bpf_context context; + struct user_bpf_iter bpf_i; + char fast_data[MAX_STACK_BPF_DATA]; + void *temp = NULL; + + if ((user->flags & FLAG_BPF_ITER) && iter_is_iovec(i)) { + /* Raw iterator */ + context.data_type = USER_BPF_DATA_ITER; + context.data_len = i->count; + context.iter = &bpf_i; + + bpf_i.iov_offset = i->iov_offset; + bpf_i.iov = i->iov; + bpf_i.nr_segs = i->nr_segs; + } else if (i->nr_segs == 1 && iter_is_iovec(i)) { + /* Single buffer from user */ + context.data_type = USER_BPF_DATA_USER; + context.data_len = i->count; + context.udata = i->iov->iov_base + i->iov_offset; + } else { + /* Multi buffer from user */ + struct iov_iter copy = *i; + size_t copy_size = min_t(size_t, i->count, MAX_BPF_COPY_SIZE); + + context.data_type = USER_BPF_DATA_KERNEL; + context.kdata = fast_data; + + if (unlikely(copy_size > sizeof(fast_data))) { + temp = kmalloc(copy_size, GFP_NOWAIT); + + if (temp) + context.kdata = temp; + else + copy_size = sizeof(fast_data); + } + + context.data_len = copy_nofault(context.kdata, + copy_size, ©); + } + + trace_call_bpf(&user->call, &context); + + kfree(temp); +} + /* * Writes the user supplied payload out to perf ring buffer or eBPF program. */ -static void user_event_perf(struct user_event *user, void *data, u32 datalen, +static void user_event_perf(struct user_event *user, struct iov_iter *i, void *tpdata) { struct hlist_head *perf_head; - if (bpf_prog_array_valid(&user->call)) { - struct user_bpf_context context = {0}; - - context.data_len = datalen; - context.data_type = USER_BPF_DATA_KERNEL; - context.kdata = data; - - trace_call_bpf(&user->call, &context); - } + if (bpf_prog_array_valid(&user->call)) + user_event_bpf(user, i); perf_head = this_cpu_ptr(user->call.perf_events); if (perf_head && !hlist_empty(perf_head)) { struct trace_entry *perf_entry; struct pt_regs *regs; - size_t size = sizeof(*perf_entry) + datalen; + size_t size = sizeof(*perf_entry) + i->count; int context; perf_entry = perf_trace_buf_alloc(ALIGN(size, 8), @@ -586,7 +644,10 @@ static void user_event_perf(struct user_event *user, void *data, u32 datalen, perf_fetch_caller_regs(regs); - memcpy(perf_entry + 1, data, datalen); + if (unlikely(!copy_nofault(perf_entry + 1, i->count, i))) { + perf_swevent_put_recursion_context(context); + return; + } perf_trace_buf_submit(perf_entry, size, context, user->call.event.type, 1, regs, @@ -1024,16 +1085,11 @@ static ssize_t user_events_write_core(struct file *file, struct iov_iter *i) if (likely(atomic_read(&tp->key.enabled) > 0)) { struct tracepoint_func *probe_func_ptr; user_event_func_t probe_func; + struct iov_iter copy; void *tpdata; - void *kdata; - u32 datalen; - kdata = kmalloc(i->count, GFP_KERNEL); - - if (unlikely(!kdata)) - return -ENOMEM; - - datalen = copy_from_iter(kdata, i->count, i); + if (unlikely(fault_in_iov_iter_readable(i, i->count))) + return -EFAULT; rcu_read_lock_sched(); @@ -1041,15 +1097,14 @@ static ssize_t user_events_write_core(struct file *file, struct iov_iter *i) if (probe_func_ptr) { do { + copy = *i; probe_func = probe_func_ptr->func; tpdata = probe_func_ptr->data; - probe_func(user, kdata, datalen, tpdata); + probe_func(user, ©, tpdata); } while ((++probe_func_ptr)->func); } rcu_read_unlock_sched(); - - kfree(kdata); } return ret; From 2467cda1b5c97a58776a8aebfa5d76543e47479d Mon Sep 17 00:00:00 2001 From: Beau Belgrave Date: Tue, 18 Jan 2022 12:43:20 -0800 Subject: [PATCH 13/67] user_events: Validate user payloads for size and null termination Add validation to ensure data is at or greater than the min size for the fields of the event. If a dynamic array is used and is a type of char, ensure null termination of the array exists. Link: https://lkml.kernel.org/r/20220118204326.2169-7-beaub@linux.microsoft.com Acked-by: Masami Hiramatsu Signed-off-by: Beau Belgrave Signed-off-by: Steven Rostedt (Google) --- kernel/trace/trace_events_user.c | 147 ++++++++++++++++++++++++++++--- 1 file changed, 133 insertions(+), 14 deletions(-) diff --git a/kernel/trace/trace_events_user.c b/kernel/trace/trace_events_user.c index 78b6b96c4cfa..2b5e9fdb63a0 100644 --- a/kernel/trace/trace_events_user.c +++ b/kernel/trace/trace_events_user.c @@ -64,9 +64,11 @@ struct user_event { struct dyn_event devent; struct hlist_node node; struct list_head fields; + struct list_head validators; atomic_t refcnt; int index; int flags; + int min_size; }; /* @@ -81,8 +83,17 @@ struct user_event_refs { struct user_event *events[]; }; +#define VALIDATOR_ENSURE_NULL (1 << 0) +#define VALIDATOR_REL (1 << 1) + +struct user_event_validator { + struct list_head link; + int offset; + int flags; +}; + typedef void (*user_event_func_t) (struct user_event *user, struct iov_iter *i, - void *tpdata); + void *tpdata, bool *faulted); static int user_event_parse(char *name, char *args, char *flags, struct user_event **newuser); @@ -215,6 +226,17 @@ static int user_field_size(const char *type) return -EINVAL; } +static void user_event_destroy_validators(struct user_event *user) +{ + struct user_event_validator *validator, *next; + struct list_head *head = &user->validators; + + list_for_each_entry_safe(validator, next, head, link) { + list_del(&validator->link); + kfree(validator); + } +} + static void user_event_destroy_fields(struct user_event *user) { struct ftrace_event_field *field, *next; @@ -230,13 +252,43 @@ static int user_event_add_field(struct user_event *user, const char *type, const char *name, int offset, int size, int is_signed, int filter_type) { + struct user_event_validator *validator; struct ftrace_event_field *field; + int validator_flags = 0; field = kmalloc(sizeof(*field), GFP_KERNEL); if (!field) return -ENOMEM; + if (str_has_prefix(type, "__data_loc ")) + goto add_validator; + + if (str_has_prefix(type, "__rel_loc ")) { + validator_flags |= VALIDATOR_REL; + goto add_validator; + } + + goto add_field; + +add_validator: + if (strstr(type, "char") != 0) + validator_flags |= VALIDATOR_ENSURE_NULL; + + validator = kmalloc(sizeof(*validator), GFP_KERNEL); + + if (!validator) { + kfree(field); + return -ENOMEM; + } + + validator->flags = validator_flags; + validator->offset = offset; + + /* Want sequential access when validating */ + list_add_tail(&validator->link, &user->validators); + +add_field: field->type = type; field->name = name; field->offset = offset; @@ -246,6 +298,12 @@ static int user_event_add_field(struct user_event *user, const char *type, list_add(&field->link, &user->fields); + /* + * Min size from user writes that are required, this does not include + * the size of trace_entry (common fields). + */ + user->min_size = (offset + size) - sizeof(struct trace_entry); + return 0; } @@ -517,6 +575,7 @@ static int destroy_user_event(struct user_event *user) clear_bit(user->index, page_bitmap); hash_del(&user->node); + user_event_destroy_validators(user); kfree(user->call.print_fmt); kfree(EVENT_NAME(user)); kfree(user); @@ -538,15 +597,49 @@ static struct user_event *find_user_event(char *name, u32 *outkey) return NULL; } +static int user_event_validate(struct user_event *user, void *data, int len) +{ + struct list_head *head = &user->validators; + struct user_event_validator *validator; + void *pos, *end = data + len; + u32 loc, offset, size; + + list_for_each_entry(validator, head, link) { + pos = data + validator->offset; + + /* Already done min_size check, no bounds check here */ + loc = *(u32 *)pos; + offset = loc & 0xffff; + size = loc >> 16; + + if (likely(validator->flags & VALIDATOR_REL)) + pos += offset + sizeof(loc); + else + pos = data + offset; + + pos += size; + + if (unlikely(pos > end)) + return -EFAULT; + + if (likely(validator->flags & VALIDATOR_ENSURE_NULL)) + if (unlikely(*(char *)(pos - 1) != '\0')) + return -EFAULT; + } + + return 0; +} + /* * Writes the user supplied payload out to a trace file. */ static void user_event_ftrace(struct user_event *user, struct iov_iter *i, - void *tpdata) + void *tpdata, bool *faulted) { struct trace_event_file *file; struct trace_entry *entry; struct trace_event_buffer event_buffer; + size_t size = sizeof(*entry) + i->count; file = (struct trace_event_file *)tpdata; @@ -556,17 +649,25 @@ static void user_event_ftrace(struct user_event *user, struct iov_iter *i, return; /* Allocates and fills trace_entry, + 1 of this is data payload */ - entry = trace_event_buffer_reserve(&event_buffer, file, - sizeof(*entry) + i->count); + entry = trace_event_buffer_reserve(&event_buffer, file, size); if (unlikely(!entry)) return; if (unlikely(!copy_nofault(entry + 1, i->count, i))) - __trace_event_discard_commit(event_buffer.buffer, - event_buffer.event); - else - trace_event_buffer_commit(&event_buffer); + goto discard; + + if (!list_empty(&user->validators) && + unlikely(user_event_validate(user, entry, size))) + goto discard; + + trace_event_buffer_commit(&event_buffer); + + return; +discard: + *faulted = true; + __trace_event_discard_commit(event_buffer.buffer, + event_buffer.event); } #ifdef CONFIG_PERF_EVENTS @@ -621,7 +722,7 @@ static void user_event_bpf(struct user_event *user, struct iov_iter *i) * Writes the user supplied payload out to perf ring buffer or eBPF program. */ static void user_event_perf(struct user_event *user, struct iov_iter *i, - void *tpdata) + void *tpdata, bool *faulted) { struct hlist_head *perf_head; @@ -644,14 +745,21 @@ static void user_event_perf(struct user_event *user, struct iov_iter *i, perf_fetch_caller_regs(regs); - if (unlikely(!copy_nofault(perf_entry + 1, i->count, i))) { - perf_swevent_put_recursion_context(context); - return; - } + if (unlikely(!copy_nofault(perf_entry + 1, i->count, i))) + goto discard; + + if (!list_empty(&user->validators) && + unlikely(user_event_validate(user, perf_entry, size))) + goto discard; perf_trace_buf_submit(perf_entry, size, context, user->call.event.type, 1, regs, perf_head, NULL); + + return; +discard: + *faulted = true; + perf_swevent_put_recursion_context(context); } } #endif @@ -971,6 +1079,7 @@ static int user_event_parse(char *name, char *args, char *flags, INIT_LIST_HEAD(&user->class.fields); INIT_LIST_HEAD(&user->fields); + INIT_LIST_HEAD(&user->validators); user->tracepoint.name = name; @@ -1019,6 +1128,7 @@ static int user_event_parse(char *name, char *args, char *flags, return 0; put_user: user_event_destroy_fields(user); + user_event_destroy_validators(user); kfree(user); return ret; } @@ -1076,6 +1186,9 @@ static ssize_t user_events_write_core(struct file *file, struct iov_iter *i) if (unlikely(user == NULL)) return -ENOENT; + if (unlikely(i->count < user->min_size)) + return -EINVAL; + tp = &user->tracepoint; /* @@ -1087,10 +1200,13 @@ static ssize_t user_events_write_core(struct file *file, struct iov_iter *i) user_event_func_t probe_func; struct iov_iter copy; void *tpdata; + bool faulted; if (unlikely(fault_in_iov_iter_readable(i, i->count))) return -EFAULT; + faulted = false; + rcu_read_lock_sched(); probe_func_ptr = rcu_dereference_sched(tp->funcs); @@ -1100,11 +1216,14 @@ static ssize_t user_events_write_core(struct file *file, struct iov_iter *i) copy = *i; probe_func = probe_func_ptr->func; tpdata = probe_func_ptr->data; - probe_func(user, ©, tpdata); + probe_func(user, ©, tpdata, &faulted); } while ((++probe_func_ptr)->func); } rcu_read_unlock_sched(); + + if (unlikely(faulted)) + return -EFAULT; } return ret; From 446640e49ec00458655e29c31e55aabd6702985d Mon Sep 17 00:00:00 2001 From: Beau Belgrave Date: Tue, 18 Jan 2022 12:43:21 -0800 Subject: [PATCH 14/67] user_events: Add self-test for ftrace integration Tests basic functionality of registering/deregistering, status and writing data out via ftrace mechanisms within user_events. Link: https://lkml.kernel.org/r/20220118204326.2169-8-beaub@linux.microsoft.com Acked-by: Masami Hiramatsu Signed-off-by: Beau Belgrave Signed-off-by: Steven Rostedt (Google) --- tools/testing/selftests/user_events/Makefile | 9 + .../selftests/user_events/ftrace_test.c | 387 ++++++++++++++++++ tools/testing/selftests/user_events/settings | 1 + 3 files changed, 397 insertions(+) create mode 100644 tools/testing/selftests/user_events/Makefile create mode 100644 tools/testing/selftests/user_events/ftrace_test.c create mode 100644 tools/testing/selftests/user_events/settings diff --git a/tools/testing/selftests/user_events/Makefile b/tools/testing/selftests/user_events/Makefile new file mode 100644 index 000000000000..d66c551a6fe3 --- /dev/null +++ b/tools/testing/selftests/user_events/Makefile @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: GPL-2.0 +CFLAGS += -Wl,-no-as-needed -Wall -I../../../../usr/include +LDLIBS += -lrt -lpthread -lm + +TEST_GEN_PROGS = ftrace_test + +TEST_FILES := settings + +include ../lib.mk diff --git a/tools/testing/selftests/user_events/ftrace_test.c b/tools/testing/selftests/user_events/ftrace_test.c new file mode 100644 index 000000000000..68010fd7b719 --- /dev/null +++ b/tools/testing/selftests/user_events/ftrace_test.c @@ -0,0 +1,387 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * User Events FTrace Test Program + * + * Copyright (c) 2021 Beau Belgrave + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../kselftest_harness.h" + +const char *data_file = "/sys/kernel/debug/tracing/user_events_data"; +const char *status_file = "/sys/kernel/debug/tracing/user_events_status"; +const char *enable_file = "/sys/kernel/debug/tracing/events/user_events/__test_event/enable"; +const char *trace_file = "/sys/kernel/debug/tracing/trace"; +const char *fmt_file = "/sys/kernel/debug/tracing/events/user_events/__test_event/format"; + +static int trace_bytes(void) +{ + int fd = open(trace_file, O_RDONLY); + char buf[256]; + int bytes = 0, got; + + if (fd == -1) + return -1; + + while (true) { + got = read(fd, buf, sizeof(buf)); + + if (got == -1) + return -1; + + if (got == 0) + break; + + bytes += got; + } + + close(fd); + + return bytes; +} + +static int skip_until_empty_line(FILE *fp) +{ + int c, last = 0; + + while (true) { + c = getc(fp); + + if (c == EOF) + break; + + if (last == '\n' && c == '\n') + return 0; + + last = c; + } + + return -1; +} + +static int get_print_fmt(char *buffer, int len) +{ + FILE *fp = fopen(fmt_file, "r"); + char *newline; + + if (!fp) + return -1; + + /* Read until empty line (Skip Common) */ + if (skip_until_empty_line(fp) < 0) + goto err; + + /* Read until empty line (Skip Properties) */ + if (skip_until_empty_line(fp) < 0) + goto err; + + /* Read in print_fmt: */ + if (fgets(buffer, len, fp) == NULL) + goto err; + + newline = strchr(buffer, '\n'); + + if (newline) + *newline = '\0'; + + fclose(fp); + + return 0; +err: + fclose(fp); + + return -1; +} + +static int clear(void) +{ + int fd = open(data_file, O_RDWR); + + if (fd == -1) + return -1; + + if (ioctl(fd, DIAG_IOCSDEL, "__test_event") == -1) + if (errno != ENOENT) + return -1; + + close(fd); + + return 0; +} + +static int check_print_fmt(const char *event, const char *expected) +{ + struct user_reg reg = {0}; + char print_fmt[256]; + int ret; + int fd; + + /* Ensure cleared */ + ret = clear(); + + if (ret != 0) + return ret; + + fd = open(data_file, O_RDWR); + + if (fd == -1) + return fd; + + reg.size = sizeof(reg); + reg.name_args = (__u64)event; + + /* Register should work */ + ret = ioctl(fd, DIAG_IOCSREG, ®); + + close(fd); + + if (ret != 0) + return ret; + + /* Ensure correct print_fmt */ + ret = get_print_fmt(print_fmt, sizeof(print_fmt)); + + if (ret != 0) + return ret; + + return strcmp(print_fmt, expected); +} + +FIXTURE(user) { + int status_fd; + int data_fd; + int enable_fd; +}; + +FIXTURE_SETUP(user) { + self->status_fd = open(status_file, O_RDONLY); + ASSERT_NE(-1, self->status_fd); + + self->data_fd = open(data_file, O_RDWR); + ASSERT_NE(-1, self->data_fd); + + self->enable_fd = -1; +} + +FIXTURE_TEARDOWN(user) { + close(self->status_fd); + close(self->data_fd); + + if (self->enable_fd != -1) { + write(self->enable_fd, "0", sizeof("0")); + close(self->enable_fd); + } + + ASSERT_EQ(0, clear()); +} + +TEST_F(user, register_events) { + struct user_reg reg = {0}; + int page_size = sysconf(_SC_PAGESIZE); + char *status_page; + + reg.size = sizeof(reg); + reg.name_args = (__u64)"__test_event u32 field1; u32 field2"; + + status_page = mmap(NULL, page_size, PROT_READ, MAP_SHARED, + self->status_fd, 0); + + /* Register should work */ + ASSERT_EQ(0, ioctl(self->data_fd, DIAG_IOCSREG, ®)); + ASSERT_EQ(0, reg.write_index); + ASSERT_NE(0, reg.status_index); + + /* Multiple registers should result in same index */ + ASSERT_EQ(0, ioctl(self->data_fd, DIAG_IOCSREG, ®)); + ASSERT_EQ(0, reg.write_index); + ASSERT_NE(0, reg.status_index); + + /* Ensure disabled */ + self->enable_fd = open(enable_file, O_RDWR); + ASSERT_NE(-1, self->enable_fd); + ASSERT_NE(-1, write(self->enable_fd, "0", sizeof("0"))) + + /* MMAP should work and be zero'd */ + ASSERT_NE(MAP_FAILED, status_page); + ASSERT_NE(NULL, status_page); + ASSERT_EQ(0, status_page[reg.status_index]); + + /* Enable event and ensure bits updated in status */ + ASSERT_NE(-1, write(self->enable_fd, "1", sizeof("1"))) + ASSERT_EQ(EVENT_STATUS_FTRACE, status_page[reg.status_index]); + + /* Disable event and ensure bits updated in status */ + ASSERT_NE(-1, write(self->enable_fd, "0", sizeof("0"))) + ASSERT_EQ(0, status_page[reg.status_index]); + + /* File still open should return -EBUSY for delete */ + ASSERT_EQ(-1, ioctl(self->data_fd, DIAG_IOCSDEL, "__test_event")); + ASSERT_EQ(EBUSY, errno); + + /* Delete should work only after close */ + close(self->data_fd); + self->data_fd = open(data_file, O_RDWR); + ASSERT_EQ(0, ioctl(self->data_fd, DIAG_IOCSDEL, "__test_event")); + + /* Unmap should work */ + ASSERT_EQ(0, munmap(status_page, page_size)); +} + +TEST_F(user, write_events) { + struct user_reg reg = {0}; + struct iovec io[3]; + __u32 field1, field2; + int before = 0, after = 0; + + reg.size = sizeof(reg); + reg.name_args = (__u64)"__test_event u32 field1; u32 field2"; + + field1 = 1; + field2 = 2; + + io[0].iov_base = ®.write_index; + io[0].iov_len = sizeof(reg.write_index); + io[1].iov_base = &field1; + io[1].iov_len = sizeof(field1); + io[2].iov_base = &field2; + io[2].iov_len = sizeof(field2); + + /* Register should work */ + ASSERT_EQ(0, ioctl(self->data_fd, DIAG_IOCSREG, ®)); + ASSERT_EQ(0, reg.write_index); + ASSERT_NE(0, reg.status_index); + + /* Write should fail on invalid slot with ENOENT */ + io[0].iov_base = &field2; + io[0].iov_len = sizeof(field2); + ASSERT_EQ(-1, writev(self->data_fd, (const struct iovec *)io, 3)); + ASSERT_EQ(ENOENT, errno); + io[0].iov_base = ®.write_index; + io[0].iov_len = sizeof(reg.write_index); + + /* Enable event */ + self->enable_fd = open(enable_file, O_RDWR); + ASSERT_NE(-1, write(self->enable_fd, "1", sizeof("1"))) + + /* Write should make it out to ftrace buffers */ + before = trace_bytes(); + ASSERT_NE(-1, writev(self->data_fd, (const struct iovec *)io, 3)); + after = trace_bytes(); + ASSERT_GT(after, before); +} + +TEST_F(user, write_fault) { + struct user_reg reg = {0}; + struct iovec io[2]; + int l = sizeof(__u64); + void *anon; + + reg.size = sizeof(reg); + reg.name_args = (__u64)"__test_event u64 anon"; + + anon = mmap(NULL, l, PROT_READ, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + ASSERT_NE(MAP_FAILED, anon); + + io[0].iov_base = ®.write_index; + io[0].iov_len = sizeof(reg.write_index); + io[1].iov_base = anon; + io[1].iov_len = l; + + /* Register should work */ + ASSERT_EQ(0, ioctl(self->data_fd, DIAG_IOCSREG, ®)); + ASSERT_EQ(0, reg.write_index); + ASSERT_NE(0, reg.status_index); + + /* Write should work normally */ + ASSERT_NE(-1, writev(self->data_fd, (const struct iovec *)io, 2)); + + /* Faulted data should zero fill and work */ + ASSERT_EQ(0, madvise(anon, l, MADV_DONTNEED)); + ASSERT_NE(-1, writev(self->data_fd, (const struct iovec *)io, 2)); + ASSERT_EQ(0, munmap(anon, l)); +} + +TEST_F(user, print_fmt) { + int ret; + + ret = check_print_fmt("__test_event __rel_loc char[] data", + "print fmt: \"data=%s\", __get_rel_str(data)"); + ASSERT_EQ(0, ret); + + ret = check_print_fmt("__test_event __data_loc char[] data", + "print fmt: \"data=%s\", __get_str(data)"); + ASSERT_EQ(0, ret); + + ret = check_print_fmt("__test_event s64 data", + "print fmt: \"data=%lld\", REC->data"); + ASSERT_EQ(0, ret); + + ret = check_print_fmt("__test_event u64 data", + "print fmt: \"data=%llu\", REC->data"); + ASSERT_EQ(0, ret); + + ret = check_print_fmt("__test_event s32 data", + "print fmt: \"data=%d\", REC->data"); + ASSERT_EQ(0, ret); + + ret = check_print_fmt("__test_event u32 data", + "print fmt: \"data=%u\", REC->data"); + ASSERT_EQ(0, ret); + + ret = check_print_fmt("__test_event int data", + "print fmt: \"data=%d\", REC->data"); + ASSERT_EQ(0, ret); + + ret = check_print_fmt("__test_event unsigned int data", + "print fmt: \"data=%u\", REC->data"); + ASSERT_EQ(0, ret); + + ret = check_print_fmt("__test_event s16 data", + "print fmt: \"data=%d\", REC->data"); + ASSERT_EQ(0, ret); + + ret = check_print_fmt("__test_event u16 data", + "print fmt: \"data=%u\", REC->data"); + ASSERT_EQ(0, ret); + + ret = check_print_fmt("__test_event short data", + "print fmt: \"data=%d\", REC->data"); + ASSERT_EQ(0, ret); + + ret = check_print_fmt("__test_event unsigned short data", + "print fmt: \"data=%u\", REC->data"); + ASSERT_EQ(0, ret); + + ret = check_print_fmt("__test_event s8 data", + "print fmt: \"data=%d\", REC->data"); + ASSERT_EQ(0, ret); + + ret = check_print_fmt("__test_event u8 data", + "print fmt: \"data=%u\", REC->data"); + ASSERT_EQ(0, ret); + + ret = check_print_fmt("__test_event char data", + "print fmt: \"data=%d\", REC->data"); + ASSERT_EQ(0, ret); + + ret = check_print_fmt("__test_event unsigned char data", + "print fmt: \"data=%u\", REC->data"); + ASSERT_EQ(0, ret); + + ret = check_print_fmt("__test_event char[4] data", + "print fmt: \"data=%s\", REC->data"); + ASSERT_EQ(0, ret); +} + +int main(int argc, char **argv) +{ + return test_harness_run(argc, argv); +} diff --git a/tools/testing/selftests/user_events/settings b/tools/testing/selftests/user_events/settings new file mode 100644 index 000000000000..ba4d85f74cd6 --- /dev/null +++ b/tools/testing/selftests/user_events/settings @@ -0,0 +1 @@ +timeout=90 From 745bb7e683ff00e7b52e54b422fd05c58ae94e1a Mon Sep 17 00:00:00 2001 From: Beau Belgrave Date: Tue, 18 Jan 2022 12:43:22 -0800 Subject: [PATCH 15/67] user_events: Add self-test for dynamic_events integration Tests matching deletes, creation of basic and complex types. Ensures common patterns work correctly when interacting with dynamic_events file. Link: https://lkml.kernel.org/r/20220118204326.2169-9-beaub@linux.microsoft.com Acked-by: Masami Hiramatsu Signed-off-by: Beau Belgrave Signed-off-by: Steven Rostedt (Google) --- tools/testing/selftests/user_events/Makefile | 2 +- .../testing/selftests/user_events/dyn_test.c | 130 ++++++++++++++++++ 2 files changed, 131 insertions(+), 1 deletion(-) create mode 100644 tools/testing/selftests/user_events/dyn_test.c diff --git a/tools/testing/selftests/user_events/Makefile b/tools/testing/selftests/user_events/Makefile index d66c551a6fe3..e824b9c2cae7 100644 --- a/tools/testing/selftests/user_events/Makefile +++ b/tools/testing/selftests/user_events/Makefile @@ -2,7 +2,7 @@ CFLAGS += -Wl,-no-as-needed -Wall -I../../../../usr/include LDLIBS += -lrt -lpthread -lm -TEST_GEN_PROGS = ftrace_test +TEST_GEN_PROGS = ftrace_test dyn_test TEST_FILES := settings diff --git a/tools/testing/selftests/user_events/dyn_test.c b/tools/testing/selftests/user_events/dyn_test.c new file mode 100644 index 000000000000..d6265d14cd51 --- /dev/null +++ b/tools/testing/selftests/user_events/dyn_test.c @@ -0,0 +1,130 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * User Events Dyn Events Test Program + * + * Copyright (c) 2021 Beau Belgrave + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../kselftest_harness.h" + +const char *dyn_file = "/sys/kernel/debug/tracing/dynamic_events"; +const char *clear = "!u:__test_event"; + +static int Append(const char *value) +{ + int fd = open(dyn_file, O_RDWR | O_APPEND); + int ret = write(fd, value, strlen(value)); + + close(fd); + return ret; +} + +#define CLEAR() \ +do { \ + int ret = Append(clear); \ + if (ret == -1) \ + ASSERT_EQ(ENOENT, errno); \ +} while (0) + +#define TEST_PARSE(x) \ +do { \ + ASSERT_NE(-1, Append(x)); \ + CLEAR(); \ +} while (0) + +#define TEST_NPARSE(x) ASSERT_EQ(-1, Append(x)) + +FIXTURE(user) { +}; + +FIXTURE_SETUP(user) { + CLEAR(); +} + +FIXTURE_TEARDOWN(user) { + CLEAR(); +} + +TEST_F(user, basic_types) { + /* All should work */ + TEST_PARSE("u:__test_event u64 a"); + TEST_PARSE("u:__test_event u32 a"); + TEST_PARSE("u:__test_event u16 a"); + TEST_PARSE("u:__test_event u8 a"); + TEST_PARSE("u:__test_event char a"); + TEST_PARSE("u:__test_event unsigned char a"); + TEST_PARSE("u:__test_event int a"); + TEST_PARSE("u:__test_event unsigned int a"); + TEST_PARSE("u:__test_event short a"); + TEST_PARSE("u:__test_event unsigned short a"); + TEST_PARSE("u:__test_event char[20] a"); + TEST_PARSE("u:__test_event unsigned char[20] a"); + TEST_PARSE("u:__test_event char[0x14] a"); + TEST_PARSE("u:__test_event unsigned char[0x14] a"); + /* Bad size format should fail */ + TEST_NPARSE("u:__test_event char[aa] a"); + /* Large size should fail */ + TEST_NPARSE("u:__test_event char[9999] a"); + /* Long size string should fail */ + TEST_NPARSE("u:__test_event char[0x0000000000001] a"); +} + +TEST_F(user, loc_types) { + /* All should work */ + TEST_PARSE("u:__test_event __data_loc char[] a"); + TEST_PARSE("u:__test_event __data_loc unsigned char[] a"); + TEST_PARSE("u:__test_event __rel_loc char[] a"); + TEST_PARSE("u:__test_event __rel_loc unsigned char[] a"); +} + +TEST_F(user, size_types) { + /* Should work */ + TEST_PARSE("u:__test_event struct custom a 20"); + /* Size not specified on struct should fail */ + TEST_NPARSE("u:__test_event struct custom a"); + /* Size specified on non-struct should fail */ + TEST_NPARSE("u:__test_event char a 20"); +} + +TEST_F(user, flags) { + /* Should work */ + TEST_PARSE("u:__test_event:BPF_ITER u32 a"); + /* Forward compat */ + TEST_PARSE("u:__test_event:BPF_ITER,FLAG_FUTURE u32 a"); +} + +TEST_F(user, matching) { + /* Register */ + ASSERT_NE(-1, Append("u:__test_event struct custom a 20")); + /* Should not match */ + TEST_NPARSE("!u:__test_event struct custom b"); + /* Should match */ + TEST_PARSE("!u:__test_event struct custom a"); + /* Multi field reg */ + ASSERT_NE(-1, Append("u:__test_event u32 a; u32 b")); + /* Non matching cases */ + TEST_NPARSE("!u:__test_event u32 a"); + TEST_NPARSE("!u:__test_event u32 b"); + TEST_NPARSE("!u:__test_event u32 a; u32 "); + TEST_NPARSE("!u:__test_event u32 a; u32 a"); + /* Matching case */ + TEST_PARSE("!u:__test_event u32 a; u32 b"); + /* Register */ + ASSERT_NE(-1, Append("u:__test_event u32 a; u32 b")); + /* Ensure trailing semi-colon case */ + TEST_PARSE("!u:__test_event u32 a; u32 b;"); +} + +int main(int argc, char **argv) +{ + return test_harness_run(argc, argv); +} From 3a6163893a9a6a90e086234ee205c1f74eeb5f84 Mon Sep 17 00:00:00 2001 From: Beau Belgrave Date: Tue, 18 Jan 2022 12:43:23 -0800 Subject: [PATCH 16/67] user_events: Add self-test for perf_event integration Tests perf can be attached to and written out correctly. Ensures attach updates status bits in user programs. Link: https://lkml.kernel.org/r/20220118204326.2169-10-beaub@linux.microsoft.com Acked-by: Masami Hiramatsu Signed-off-by: Beau Belgrave Signed-off-by: Steven Rostedt (Google) --- tools/testing/selftests/user_events/Makefile | 2 +- .../testing/selftests/user_events/perf_test.c | 168 ++++++++++++++++++ 2 files changed, 169 insertions(+), 1 deletion(-) create mode 100644 tools/testing/selftests/user_events/perf_test.c diff --git a/tools/testing/selftests/user_events/Makefile b/tools/testing/selftests/user_events/Makefile index e824b9c2cae7..c765d8635d9a 100644 --- a/tools/testing/selftests/user_events/Makefile +++ b/tools/testing/selftests/user_events/Makefile @@ -2,7 +2,7 @@ CFLAGS += -Wl,-no-as-needed -Wall -I../../../../usr/include LDLIBS += -lrt -lpthread -lm -TEST_GEN_PROGS = ftrace_test dyn_test +TEST_GEN_PROGS = ftrace_test dyn_test perf_test TEST_FILES := settings diff --git a/tools/testing/selftests/user_events/perf_test.c b/tools/testing/selftests/user_events/perf_test.c new file mode 100644 index 000000000000..26851d51d6bb --- /dev/null +++ b/tools/testing/selftests/user_events/perf_test.c @@ -0,0 +1,168 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * User Events Perf Events Test Program + * + * Copyright (c) 2021 Beau Belgrave + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../kselftest_harness.h" + +const char *data_file = "/sys/kernel/debug/tracing/user_events_data"; +const char *status_file = "/sys/kernel/debug/tracing/user_events_status"; +const char *id_file = "/sys/kernel/debug/tracing/events/user_events/__test_event/id"; +const char *fmt_file = "/sys/kernel/debug/tracing/events/user_events/__test_event/format"; + +struct event { + __u32 index; + __u32 field1; + __u32 field2; +}; + +static long perf_event_open(struct perf_event_attr *pe, pid_t pid, + int cpu, int group_fd, unsigned long flags) +{ + return syscall(__NR_perf_event_open, pe, pid, cpu, group_fd, flags); +} + +static int get_id(void) +{ + FILE *fp = fopen(id_file, "r"); + int ret, id = 0; + + if (!fp) + return -1; + + ret = fscanf(fp, "%d", &id); + fclose(fp); + + if (ret != 1) + return -1; + + return id; +} + +static int get_offset(void) +{ + FILE *fp = fopen(fmt_file, "r"); + int ret, c, last = 0, offset = 0; + + if (!fp) + return -1; + + /* Read until empty line */ + while (true) { + c = getc(fp); + + if (c == EOF) + break; + + if (last == '\n' && c == '\n') + break; + + last = c; + } + + ret = fscanf(fp, "\tfield:u32 field1;\toffset:%d;", &offset); + fclose(fp); + + if (ret != 1) + return -1; + + return offset; +} + +FIXTURE(user) { + int status_fd; + int data_fd; +}; + +FIXTURE_SETUP(user) { + self->status_fd = open(status_file, O_RDONLY); + ASSERT_NE(-1, self->status_fd); + + self->data_fd = open(data_file, O_RDWR); + ASSERT_NE(-1, self->data_fd); +} + +FIXTURE_TEARDOWN(user) { + close(self->status_fd); + close(self->data_fd); +} + +TEST_F(user, perf_write) { + struct perf_event_attr pe = {0}; + struct user_reg reg = {0}; + int page_size = sysconf(_SC_PAGESIZE); + char *status_page; + struct event event; + struct perf_event_mmap_page *perf_page; + int id, fd, offset; + __u32 *val; + + reg.size = sizeof(reg); + reg.name_args = (__u64)"__test_event u32 field1; u32 field2"; + + status_page = mmap(NULL, page_size, PROT_READ, MAP_SHARED, + self->status_fd, 0); + ASSERT_NE(MAP_FAILED, status_page); + + /* Register should work */ + ASSERT_EQ(0, ioctl(self->data_fd, DIAG_IOCSREG, ®)); + ASSERT_EQ(0, reg.write_index); + ASSERT_NE(0, reg.status_index); + ASSERT_EQ(0, status_page[reg.status_index]); + + /* Id should be there */ + id = get_id(); + ASSERT_NE(-1, id); + offset = get_offset(); + ASSERT_NE(-1, offset); + + pe.type = PERF_TYPE_TRACEPOINT; + pe.size = sizeof(pe); + pe.config = id; + pe.sample_type = PERF_SAMPLE_RAW; + pe.sample_period = 1; + pe.wakeup_events = 1; + + /* Tracepoint attach should work */ + fd = perf_event_open(&pe, 0, -1, -1, 0); + ASSERT_NE(-1, fd); + + perf_page = mmap(NULL, page_size * 2, PROT_READ, MAP_SHARED, fd, 0); + ASSERT_NE(MAP_FAILED, perf_page); + + /* Status should be updated */ + ASSERT_EQ(EVENT_STATUS_PERF, status_page[reg.status_index]); + + event.index = reg.write_index; + event.field1 = 0xc001; + event.field2 = 0xc01a; + + /* Ensure write shows up at correct offset */ + ASSERT_NE(-1, write(self->data_fd, &event, sizeof(event))); + val = (void *)(((char *)perf_page) + perf_page->data_offset); + ASSERT_EQ(PERF_RECORD_SAMPLE, *val); + /* Skip over header and size, move to offset */ + val += 3; + val = (void *)((char *)val) + offset; + /* Ensure correct */ + ASSERT_EQ(event.field1, *val++); + ASSERT_EQ(event.field2, *val++); +} + +int main(int argc, char **argv) +{ + return test_harness_run(argc, argv); +} From 7640e77035aadcd7d50f9c7583ca25a4e1aa2874 Mon Sep 17 00:00:00 2001 From: Beau Belgrave Date: Tue, 18 Jan 2022 12:43:24 -0800 Subject: [PATCH 17/67] user_events: Add self-test for validator boundaries Tests to ensure validator boundary cases are working correctly within close and far bounds. Ensures __data_loc and __rel_loc strings are null terminated and within range. Ensures min size checks work as expected. Link: https://lkml.kernel.org/r/20220118204326.2169-11-beaub@linux.microsoft.com Acked-by: Masami Hiramatsu Signed-off-by: Beau Belgrave Signed-off-by: Steven Rostedt (Google) --- .../selftests/user_events/ftrace_test.c | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/tools/testing/selftests/user_events/ftrace_test.c b/tools/testing/selftests/user_events/ftrace_test.c index 68010fd7b719..a80fb5ef61d5 100644 --- a/tools/testing/selftests/user_events/ftrace_test.c +++ b/tools/testing/selftests/user_events/ftrace_test.c @@ -309,6 +309,71 @@ TEST_F(user, write_fault) { ASSERT_EQ(0, munmap(anon, l)); } +TEST_F(user, write_validator) { + struct user_reg reg = {0}; + struct iovec io[3]; + int loc, bytes; + char data[8]; + int before = 0, after = 0; + + reg.size = sizeof(reg); + reg.name_args = (__u64)"__test_event __rel_loc char[] data"; + + /* Register should work */ + ASSERT_EQ(0, ioctl(self->data_fd, DIAG_IOCSREG, ®)); + ASSERT_EQ(0, reg.write_index); + ASSERT_NE(0, reg.status_index); + + io[0].iov_base = ®.write_index; + io[0].iov_len = sizeof(reg.write_index); + io[1].iov_base = &loc; + io[1].iov_len = sizeof(loc); + io[2].iov_base = data; + bytes = snprintf(data, sizeof(data), "Test") + 1; + io[2].iov_len = bytes; + + /* Undersized write should fail */ + ASSERT_EQ(-1, writev(self->data_fd, (const struct iovec *)io, 1)); + ASSERT_EQ(EINVAL, errno); + + /* Enable event */ + self->enable_fd = open(enable_file, O_RDWR); + ASSERT_NE(-1, write(self->enable_fd, "1", sizeof("1"))) + + /* Full in-bounds write should work */ + before = trace_bytes(); + loc = DYN_LOC(0, bytes); + ASSERT_NE(-1, writev(self->data_fd, (const struct iovec *)io, 3)); + after = trace_bytes(); + ASSERT_GT(after, before); + + /* Out of bounds write should fault (offset way out) */ + loc = DYN_LOC(1024, bytes); + ASSERT_EQ(-1, writev(self->data_fd, (const struct iovec *)io, 3)); + ASSERT_EQ(EFAULT, errno); + + /* Out of bounds write should fault (offset 1 byte out) */ + loc = DYN_LOC(1, bytes); + ASSERT_EQ(-1, writev(self->data_fd, (const struct iovec *)io, 3)); + ASSERT_EQ(EFAULT, errno); + + /* Out of bounds write should fault (size way out) */ + loc = DYN_LOC(0, bytes + 1024); + ASSERT_EQ(-1, writev(self->data_fd, (const struct iovec *)io, 3)); + ASSERT_EQ(EFAULT, errno); + + /* Out of bounds write should fault (size 1 byte out) */ + loc = DYN_LOC(0, bytes + 1); + ASSERT_EQ(-1, writev(self->data_fd, (const struct iovec *)io, 3)); + ASSERT_EQ(EFAULT, errno); + + /* Non-Null should fault */ + memset(data, 'A', sizeof(data)); + loc = DYN_LOC(0, bytes); + ASSERT_EQ(-1, writev(self->data_fd, (const struct iovec *)io, 3)); + ASSERT_EQ(EFAULT, errno); +} + TEST_F(user, print_fmt) { int ret; From c57eb47815097d879e1fa8c81e313aec917d8f4d Mon Sep 17 00:00:00 2001 From: Beau Belgrave Date: Tue, 18 Jan 2022 12:43:25 -0800 Subject: [PATCH 18/67] user_events: Add sample code for typical usage Add sample code for user_events typical usage to show how to register and monitor status, as well as to write out data. Link: https://lkml.kernel.org/r/20220118204326.2169-12-beaub@linux.microsoft.com Acked-by: Masami Hiramatsu Signed-off-by: Beau Belgrave Signed-off-by: Steven Rostedt (Google) --- samples/user_events/Makefile | 5 ++ samples/user_events/example.c | 91 +++++++++++++++++++++++++++++++++++ 2 files changed, 96 insertions(+) create mode 100644 samples/user_events/Makefile create mode 100644 samples/user_events/example.c diff --git a/samples/user_events/Makefile b/samples/user_events/Makefile new file mode 100644 index 000000000000..7252b589db57 --- /dev/null +++ b/samples/user_events/Makefile @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: GPL-2.0 +CFLAGS += -Wl,-no-as-needed -Wall -I../../usr/include + +example: example.o +example.o: example.c diff --git a/samples/user_events/example.c b/samples/user_events/example.c new file mode 100644 index 000000000000..4f5778e441c0 --- /dev/null +++ b/samples/user_events/example.c @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2021, Microsoft Corporation. + * + * Authors: + * Beau Belgrave + */ + +#include +#include +#include +#include +#include +#include +#include + +/* Assumes debugfs is mounted */ +const char *data_file = "/sys/kernel/debug/tracing/user_events_data"; +const char *status_file = "/sys/kernel/debug/tracing/user_events_status"; + +static int event_status(char **status) +{ + int fd = open(status_file, O_RDONLY); + + *status = mmap(NULL, sysconf(_SC_PAGESIZE), PROT_READ, + MAP_SHARED, fd, 0); + + close(fd); + + if (*status == MAP_FAILED) + return -1; + + return 0; +} + +static int event_reg(int fd, const char *command, int *status, int *write) +{ + struct user_reg reg = {0}; + + reg.size = sizeof(reg); + reg.name_args = (__u64)command; + + if (ioctl(fd, DIAG_IOCSREG, ®) == -1) + return -1; + + *status = reg.status_index; + *write = reg.write_index; + + return 0; +} + +int main(int argc, char **argv) +{ + int data_fd, status, write; + char *status_page; + struct iovec io[2]; + __u32 count = 0; + + if (event_status(&status_page) == -1) + return errno; + + data_fd = open(data_file, O_RDWR); + + if (event_reg(data_fd, "test u32 count", &status, &write) == -1) + return errno; + + /* Setup iovec */ + io[0].iov_base = &write; + io[0].iov_len = sizeof(write); + io[1].iov_base = &count; + io[1].iov_len = sizeof(count); + +ask: + printf("Press enter to check status...\n"); + getchar(); + + /* Check if anyone is listening */ + if (status_page[status]) { + /* Yep, trace out our data */ + writev(data_fd, (const struct iovec *)io, 2); + + /* Increase the count */ + count++; + + printf("Something was attached, wrote data\n"); + } + + goto ask; + + return 0; +} From 864ea0e10cc90416a01b46f0d47a6f26dc020820 Mon Sep 17 00:00:00 2001 From: Beau Belgrave Date: Tue, 18 Jan 2022 12:43:26 -0800 Subject: [PATCH 19/67] user_events: Add documentation file Add a documentation file about user_events with example code, etc. explaining how it may be used. Link: https://lkml.kernel.org/r/20220118204326.2169-13-beaub@linux.microsoft.com Acked-by: Masami Hiramatsu Signed-off-by: Beau Belgrave Signed-off-by: Steven Rostedt (Google) --- Documentation/trace/index.rst | 1 + Documentation/trace/user_events.rst | 216 ++++++++++++++++++++++++++++ 2 files changed, 217 insertions(+) create mode 100644 Documentation/trace/user_events.rst diff --git a/Documentation/trace/index.rst b/Documentation/trace/index.rst index 3769b9b7aed8..3a47aa8341c6 100644 --- a/Documentation/trace/index.rst +++ b/Documentation/trace/index.rst @@ -30,3 +30,4 @@ Linux Tracing Technologies stm sys-t coresight/index + user_events diff --git a/Documentation/trace/user_events.rst b/Documentation/trace/user_events.rst new file mode 100644 index 000000000000..bddedabaca80 --- /dev/null +++ b/Documentation/trace/user_events.rst @@ -0,0 +1,216 @@ +========================================= +user_events: User-based Event Tracing +========================================= + +:Author: Beau Belgrave + +Overview +-------- +User based trace events allow user processes to create events and trace data +that can be viewed via existing tools, such as ftrace, perf and eBPF. +To enable this feature, build your kernel with CONFIG_USER_EVENTS=y. + +Programs can view status of the events via +/sys/kernel/debug/tracing/user_events_status and can both register and write +data out via /sys/kernel/debug/tracing/user_events_data. + +Programs can also use /sys/kernel/debug/tracing/dynamic_events to register and +delete user based events via the u: prefix. The format of the command to +dynamic_events is the same as the ioctl with the u: prefix applied. + +Typically programs will register a set of events that they wish to expose to +tools that can read trace_events (such as ftrace and perf). The registration +process gives back two ints to the program for each event. The first int is the +status index. This index describes which byte in the +/sys/kernel/debug/tracing/user_events_status file represents this event. The +second int is the write index. This index describes the data when a write() or +writev() is called on the /sys/kernel/debug/tracing/user_events_data file. + +The structures referenced in this document are contained with the +/include/uap/linux/user_events.h file in the source tree. + +**NOTE:** *Both user_events_status and user_events_data are under the tracefs +filesystem and may be mounted at different paths than above.* + +Registering +----------- +Registering within a user process is done via ioctl() out to the +/sys/kernel/debug/tracing/user_events_data file. The command to issue is +DIAG_IOCSREG. + +This command takes a struct user_reg as an argument:: + + struct user_reg { + u32 size; + u64 name_args; + u32 status_index; + u32 write_index; + }; + +The struct user_reg requires two inputs, the first is the size of the structure +to ensure forward and backward compatibility. The second is the command string +to issue for registering. Upon success two outputs are set, the status index +and the write index. + +User based events show up under tracefs like any other event under the +subsystem named "user_events". This means tools that wish to attach to the +events need to use /sys/kernel/debug/tracing/events/user_events/[name]/enable +or perf record -e user_events:[name] when attaching/recording. + +**NOTE:** *The write_index returned is only valid for the FD that was used* + +Command Format +^^^^^^^^^^^^^^ +The command string format is as follows:: + + name[:FLAG1[,FLAG2...]] [Field1[;Field2...]] + +Supported Flags +^^^^^^^^^^^^^^^ +**BPF_ITER** - EBPF programs attached to this event will get the raw iovec +struct instead of any data copies for max performance. + +Field Format +^^^^^^^^^^^^ +:: + + type name [size] + +Basic types are supported (__data_loc, u32, u64, int, char, char[20], etc). +User programs are encouraged to use clearly sized types like u32. + +**NOTE:** *Long is not supported since size can vary between user and kernel.* + +The size is only valid for types that start with a struct prefix. +This allows user programs to describe custom structs out to tools, if required. + +For example, a struct in C that looks like this:: + + struct mytype { + char data[20]; + }; + +Would be represented by the following field:: + + struct mytype myname 20 + +Deleting +----------- +Deleting an event from within a user process is done via ioctl() out to the +/sys/kernel/debug/tracing/user_events_data file. The command to issue is +DIAG_IOCSDEL. + +This command only requires a single string specifying the event to delete by +its name. Delete will only succeed if there are no references left to the +event (in both user and kernel space). User programs should use a separate file +to request deletes than the one used for registration due to this. + +Status +------ +When tools attach/record user based events the status of the event is updated +in realtime. This allows user programs to only incur the cost of the write() or +writev() calls when something is actively attached to the event. + +User programs call mmap() on /sys/kernel/debug/tracing/user_events_status to +check the status for each event that is registered. The byte to check in the +file is given back after the register ioctl() via user_reg.status_index. +Currently the size of user_events_status is a single page, however, custom +kernel configurations can change this size to allow more user based events. In +all cases the size of the file is a multiple of a page size. + +For example, if the register ioctl() gives back a status_index of 3 you would +check byte 3 of the returned mmap data to see if anything is attached to that +event. + +Administrators can easily check the status of all registered events by reading +the user_events_status file directly via a terminal. The output is as follows:: + + Byte:Name [# Comments] + ... + + Active: ActiveCount + Busy: BusyCount + Max: MaxCount + +For example, on a system that has a single event the output looks like this:: + + 1:test + + Active: 1 + Busy: 0 + Max: 4096 + +If a user enables the user event via ftrace, the output would change to this:: + + 1:test # Used by ftrace + + Active: 1 + Busy: 1 + Max: 4096 + +**NOTE:** *A status index of 0 will never be returned. This allows user +programs to have an index that can be used on error cases.* + +Status Bits +^^^^^^^^^^^ +The byte being checked will be non-zero if anything is attached. Programs can +check specific bits in the byte to see what mechanism has been attached. + +The following values are defined to aid in checking what has been attached: + +**EVENT_STATUS_FTRACE** - Bit set if ftrace has been attached (Bit 0). + +**EVENT_STATUS_PERF** - Bit set if perf/eBPF has been attached (Bit 1). + +Writing Data +------------ +After registering an event the same fd that was used to register can be used +to write an entry for that event. The write_index returned must be at the start +of the data, then the remaining data is treated as the payload of the event. + +For example, if write_index returned was 1 and I wanted to write out an int +payload of the event. Then the data would have to be 8 bytes (2 ints) in size, +with the first 4 bytes being equal to 1 and the last 4 bytes being equal to the +value I want as the payload. + +In memory this would look like this:: + + int index; + int payload; + +User programs might have well known structs that they wish to use to emit out +as payloads. In those cases writev() can be used, with the first vector being +the index and the following vector(s) being the actual event payload. + +For example, if I have a struct like this:: + + struct payload { + int src; + int dst; + int flags; + }; + +It's advised for user programs to do the following:: + + struct iovec io[2]; + struct payload e; + + io[0].iov_base = &write_index; + io[0].iov_len = sizeof(write_index); + io[1].iov_base = &e; + io[1].iov_len = sizeof(e); + + writev(fd, (const struct iovec*)io, 2); + +**NOTE:** *The write_index is not emitted out into the trace being recorded.* + +EBPF +---- +EBPF programs that attach to a user-based event tracepoint are given a pointer +to a struct user_bpf_context. The bpf context contains the data type (which can +be a user or kernel buffer, or can be a pointer to the iovec) and the data +length that was emitted (minus the write_index). + +Example Code +------------ +See sample code in samples/user_events. From 77498617857f68496b360081dde1a492d40c28b2 Mon Sep 17 00:00:00 2001 From: Stephen Brennan Date: Wed, 2 Feb 2022 09:18:18 -0800 Subject: [PATCH 20/67] printk: Add panic_in_progress helper This will be used help avoid deadlocks during panics. Although it would be better to include this in linux/panic.h, it would require that header to include linux/atomic.h as well. On some architectures, this results in a circular dependency as well. So instead add the helper directly to printk.c. Suggested-by: Petr Mladek Signed-off-by: Stephen Brennan Reviewed-by: Petr Mladek Reviewed-by: Sergey Senozhatsky Signed-off-by: Petr Mladek Link: https://lore.kernel.org/r/20220202171821.179394-2-stephen.s.brennan@oracle.com --- kernel/printk/printk.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/kernel/printk/printk.c b/kernel/printk/printk.c index 155229f0cf0f..f04bbed0aa79 100644 --- a/kernel/printk/printk.c +++ b/kernel/printk/printk.c @@ -256,6 +256,11 @@ static void __up_console_sem(unsigned long ip) } #define up_console_sem() __up_console_sem(_RET_IP_) +static bool panic_in_progress(void) +{ + return unlikely(atomic_read(&panic_cpu) != PANIC_CPU_INVALID); +} + /* * This is used for debugging the mess that is the VT code by * keeping track if we have the console semaphore held. It's From d51507098ff91e863b6e0a8047507741d59b8175 Mon Sep 17 00:00:00 2001 From: Stephen Brennan Date: Wed, 2 Feb 2022 09:18:19 -0800 Subject: [PATCH 21/67] printk: disable optimistic spin during panic A CPU executing with console lock spinning enabled might be halted during a panic. Before the panicking CPU calls console_flush_on_panic(), it may call console_trylock(), which attempts to optimistically spin, deadlocking the panic CPU: CPU 0 (panic CPU) CPU 1 ----------------- ------ printk() { vprintk_func() { vprintk_default() { vprintk_emit() { console_unlock() { console_lock_spinning_enable(); ... printing to console ... panic() { crash_smp_send_stop() { NMI -------------------> HALT } atomic_notifier_call_chain() { printk() { ... console_trylock_spinnning() { // optimistic spin infinitely This hang during panic can be induced when a kdump kernel is loaded, and crash_kexec_post_notifiers=1 is present on the kernel command line. The following script which concurrently writes to /dev/kmsg, and triggers a panic, can result in this hang: #!/bin/bash date # 991 chars (based on log buffer size): chars="$(printf 'a%.0s' {1..991})" while :; do echo $chars > /dev/kmsg done & echo c > /proc/sysrq-trigger & date exit To avoid this deadlock, ensure that console_trylock_spinning() does not allow spinning once a panic has begun. Fixes: dbdda842fe96 ("printk: Add console owner and waiter logic to load balance console writes") Suggested-by: Petr Mladek Signed-off-by: Stephen Brennan Reviewed-by: Petr Mladek Reviewed-by: Sergey Senozhatsky Signed-off-by: Petr Mladek Link: https://lore.kernel.org/r/20220202171821.179394-3-stephen.s.brennan@oracle.com --- kernel/printk/printk.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/kernel/printk/printk.c b/kernel/printk/printk.c index f04bbed0aa79..e83c12770104 100644 --- a/kernel/printk/printk.c +++ b/kernel/printk/printk.c @@ -1847,6 +1847,16 @@ static int console_trylock_spinning(void) if (console_trylock()) return 1; + /* + * It's unsafe to spin once a panic has begun. If we are the + * panic CPU, we may have already halted the owner of the + * console_sem. If we are not the panic CPU, then we should + * avoid taking console_sem, so the panic CPU has a better + * chance of cleanly acquiring it later. + */ + if (panic_in_progress()) + return 0; + printk_safe_enter_irqsave(flags); raw_spin_lock(&console_owner_lock); From 13fb0f74d7029df3b8137f11ef955e578a4a4a60 Mon Sep 17 00:00:00 2001 From: Stephen Brennan Date: Wed, 2 Feb 2022 09:18:20 -0800 Subject: [PATCH 22/67] printk: Avoid livelock with heavy printk during panic During panic(), if another CPU is writing heavily the kernel log (e.g. via /dev/kmsg), then the panic CPU may livelock writing out its messages to the console. Note when too many messages are dropped during panic and suppress further printk, except from the panic CPU. This could result in some important messages being dropped. However, messages are already being dropped, so this approach at least prevents a livelock. Reviewed-by: Petr Mladek Signed-off-by: Stephen Brennan Reviewed-by: Sergey Senozhatsky Signed-off-by: Petr Mladek Link: https://lore.kernel.org/r/20220202171821.179394-4-stephen.s.brennan@oracle.com --- kernel/printk/printk.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/kernel/printk/printk.c b/kernel/printk/printk.c index e83c12770104..2ec6b547cda6 100644 --- a/kernel/printk/printk.c +++ b/kernel/printk/printk.c @@ -93,6 +93,12 @@ EXPORT_SYMBOL_GPL(console_drivers); */ int __read_mostly suppress_printk; +/* + * During panic, heavy printk by other CPUs can delay the + * panic and risk deadlock on console resources. + */ +int __read_mostly suppress_panic_printk; + #ifdef CONFIG_LOCKDEP static struct lockdep_map console_lock_dep_map = { .name = "console_lock" @@ -2232,6 +2238,10 @@ asmlinkage int vprintk_emit(int facility, int level, if (unlikely(suppress_printk)) return 0; + if (unlikely(suppress_panic_printk) && + atomic_read(&panic_cpu) != raw_smp_processor_id()) + return 0; + if (level == LOGLEVEL_SCHED) { level = LOGLEVEL_DEFAULT; in_sched = true; @@ -2617,6 +2627,7 @@ void console_unlock(void) { static char ext_text[CONSOLE_EXT_LOG_MAX]; static char text[CONSOLE_LOG_MAX]; + static int panic_console_dropped; unsigned long flags; bool do_cond_resched, retry; struct printk_info info; @@ -2671,6 +2682,10 @@ skip: if (console_seq != r.info->seq) { console_dropped += r.info->seq - console_seq; console_seq = r.info->seq; + if (panic_in_progress() && panic_console_dropped++ > 10) { + suppress_panic_printk = 1; + pr_warn_once("Too many dropped messages. Suppress messages on non-panic CPUs to prevent livelock.\n"); + } } if (suppress_message_printing(r.info->level)) { From 8ebc476fd51e6c0fd3174ec1959a20ba99d4c5e5 Mon Sep 17 00:00:00 2001 From: Stephen Brennan Date: Wed, 2 Feb 2022 09:18:21 -0800 Subject: [PATCH 23/67] printk: Drop console_sem during panic If another CPU is in panic, we are about to be halted. Try to gracefully abandon the console_sem, leaving it free for the panic CPU to grab. Suggested-by: Petr Mladek Signed-off-by: Stephen Brennan Reviewed-by: Petr Mladek Reviewed-by: Sergey Senozhatsky Signed-off-by: Petr Mladek Link: https://lore.kernel.org/r/20220202171821.179394-5-stephen.s.brennan@oracle.com --- kernel/printk/printk.c | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/kernel/printk/printk.c b/kernel/printk/printk.c index 2ec6b547cda6..6a51907a33b9 100644 --- a/kernel/printk/printk.c +++ b/kernel/printk/printk.c @@ -2597,6 +2597,25 @@ static int have_callable_console(void) return 0; } +/* + * Return true when this CPU should unlock console_sem without pushing all + * messages to the console. This reduces the chance that the console is + * locked when the panic CPU tries to use it. + */ +static bool abandon_console_lock_in_panic(void) +{ + if (!panic_in_progress()) + return false; + + /* + * We can use raw_smp_processor_id() here because it is impossible for + * the task to be migrated to the panic_cpu, or away from it. If + * panic_cpu has already been set, and we're not currently executing on + * that CPU, then we never will be. + */ + return atomic_read(&panic_cpu) != raw_smp_processor_id(); +} + /* * Can we actually use the console at this time on this cpu? * @@ -2745,6 +2764,10 @@ skip: if (handover) return; + /* Allow panic_cpu to take over the consoles safely */ + if (abandon_console_lock_in_panic()) + break; + if (do_cond_resched) cond_resched(); } @@ -2762,7 +2785,7 @@ skip: * flush, no worries. */ retry = prb_read_valid(prb, next_seq, NULL); - if (retry && console_trylock()) + if (retry && !abandon_console_lock_in_panic() && console_trylock()) goto again; } EXPORT_SYMBOL(console_unlock); From 2ba3673d70178bf07fb75ff25c54bc478add4021 Mon Sep 17 00:00:00 2001 From: John Ogness Date: Fri, 11 Feb 2022 12:29:37 +0106 Subject: [PATCH 24/67] printk: use atomic updates for klogd work The per-cpu @printk_pending variable can be updated from sleepable contexts, such as: get_random_bytes() warn_unseeded_randomness() printk_deferred() defer_console_output() and can be updated from interrupt contexts, such as: handle_irq_event_percpu() __irq_wake_thread() wake_up_process() try_to_wake_up() select_task_rq() select_fallback_rq() printk_deferred() defer_console_output() and can be updated from NMI contexts, such as: vprintk() if (in_nmi()) defer_console_output() Therefore the atomic variant of the updating functions must be used. Replace __this_cpu_xchg() with this_cpu_xchg(). Replace __this_cpu_or() with this_cpu_or(). Reported-by: Sebastian Andrzej Siewior Signed-off-by: John Ogness Signed-off-by: Petr Mladek Link: https://lore.kernel.org/r/87iltld4ue.fsf@jogness.linutronix.de --- kernel/printk/printk.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kernel/printk/printk.c b/kernel/printk/printk.c index 155229f0cf0f..25dce8b74791 100644 --- a/kernel/printk/printk.c +++ b/kernel/printk/printk.c @@ -3226,7 +3226,7 @@ static DEFINE_PER_CPU(int, printk_pending); static void wake_up_klogd_work_func(struct irq_work *irq_work) { - int pending = __this_cpu_xchg(printk_pending, 0); + int pending = this_cpu_xchg(printk_pending, 0); if (pending & PRINTK_PENDING_OUTPUT) { /* If trylock fails, someone else is doing the printing */ @@ -3260,7 +3260,7 @@ void defer_console_output(void) return; preempt_disable(); - __this_cpu_or(printk_pending, PRINTK_PENDING_OUTPUT); + this_cpu_or(printk_pending, PRINTK_PENDING_OUTPUT); irq_work_queue(this_cpu_ptr(&wake_up_klogd_work)); preempt_enable(); } From bd53ce4da252ddb1ae4257c164f80aea3d8ab90c Mon Sep 17 00:00:00 2001 From: Miaohe Lin Date: Thu, 17 Feb 2022 16:58:42 +0800 Subject: [PATCH 25/67] mm/slob: make kmem_cache_boot static kmem_cache_boot is never accessed outside slob.c. Make it static. Signed-off-by: Miaohe Lin Acked-by: David Rientjes Signed-off-by: Vlastimil Babka Link: https://lore.kernel.org/r/20220217085842.29032-1-linmiaohe@huawei.com --- mm/slob.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mm/slob.c b/mm/slob.c index 60c5842215f1..1179bcad2df8 100644 --- a/mm/slob.c +++ b/mm/slob.c @@ -708,7 +708,7 @@ int __kmem_cache_shrink(struct kmem_cache *d) return 0; } -struct kmem_cache kmem_cache_boot = { +static struct kmem_cache kmem_cache_boot = { .name = "kmem_cache", .size = sizeof(struct kmem_cache), .flags = SLAB_PANIC, From 7d6b6cc355378a66044cbd25ccee4153bf60dea9 Mon Sep 17 00:00:00 2001 From: Miaohe Lin Date: Thu, 17 Feb 2022 17:16:09 +0800 Subject: [PATCH 26/67] mm/slab_common: use helper function is_power_of_2() Use helper function is_power_of_2() to check if KMALLOC_MIN_SIZE is power of two. Minor readability improvement. Signed-off-by: Miaohe Lin Signed-off-by: Vlastimil Babka Link: https://lore.kernel.org/r/20220217091609.8214-1-linmiaohe@huawei.com --- mm/slab_common.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mm/slab_common.c b/mm/slab_common.c index 23f2ab0713b7..6ee64d6208b3 100644 --- a/mm/slab_common.c +++ b/mm/slab_common.c @@ -807,7 +807,7 @@ void __init setup_kmalloc_cache_index_table(void) unsigned int i; BUILD_BUG_ON(KMALLOC_MIN_SIZE > 256 || - (KMALLOC_MIN_SIZE & (KMALLOC_MIN_SIZE - 1))); + !is_power_of_2(KMALLOC_MIN_SIZE)); for (i = 8; i < KMALLOC_MIN_SIZE; i += 8) { unsigned int elem = size_index_elem(i); From 96b02f2fbde29a08ac7239fd9ba87ff870cf8a94 Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Wed, 16 Feb 2022 12:37:45 -0800 Subject: [PATCH 27/67] Docs: printk: add 'console=null|""' to admin/kernel-parameters Tell about 'console=null|""' and how to use it. It can be helpful to set (enable) CONFIG_NULL_TTY so that the ttynull driver is available. This avoids problems with stdin/stdout/stderr of the init process. Howevere, CONFIG_NULL_TTY cannot be enabled by default because it can be used by mistake, see the commit a91bd6223ecd ("Revert "init/console: Use ttynull as a fallback when there is no console""). Signed-off-by: Randy Dunlap Cc: Petr Mladek Cc: Sergey Senozhatsky Cc: Steven Rostedt Cc: John Ogness Cc: Jonathan Corbet Cc: linux-doc@vger.kernel.org [pmladek@suse.com: Slightly update wording.] Signed-off-by: Petr Mladek Link: https://lore.kernel.org/r/20220216203745.980-1-rdunlap@infradead.org --- Documentation/admin-guide/kernel-parameters.txt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt index 49f495c9a7f8..46bfadf02bc7 100644 --- a/Documentation/admin-guide/kernel-parameters.txt +++ b/Documentation/admin-guide/kernel-parameters.txt @@ -724,6 +724,12 @@ hvc Use the hypervisor console device . This is for both Xen and PowerPC hypervisors. + { null | "" } + Use to disable console output, i.e., to have kernel + console messages discarded. + This must be the only console= parameter used on the + kernel command line. + If the device connected to the port is not a TTY but a braille device, prepend "brl," before the device type, for instance console=brl,ttyS0 From a5a763b2b26678f1e01b2d031819b175d8f14555 Mon Sep 17 00:00:00 2001 From: Andre Kalb Date: Wed, 16 Feb 2022 11:41:38 +0100 Subject: [PATCH 28/67] printk: Set console_set_on_cmdline=1 when __add_preferred_console() is called with user_specified == true In case of using console="" or console=null set console_set_on_cmdline=1 to disable "stdout-path" node from DT. We basically need to set it every time when __add_preferred_console() is called with parameter 'user_specified' set. Therefore we can move setting it into a helper function that is called from __add_preferred_console(). Suggested-by: Petr Mladek Signed-off-by: Andre Kalb Reviewed-by: Petr Mladek Reviewed-by: Sergey Senozhatsky Signed-off-by: Petr Mladek Link: https://lore.kernel.org/r/YgzU4ho8l6XapyG2@pc6682 --- kernel/printk/printk.c | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/kernel/printk/printk.c b/kernel/printk/printk.c index 25dce8b74791..266cc974b0e3 100644 --- a/kernel/printk/printk.c +++ b/kernel/printk/printk.c @@ -2323,6 +2323,20 @@ asmlinkage __visible void early_printk(const char *fmt, ...) } #endif +static void set_user_specified(struct console_cmdline *c, bool user_specified) +{ + if (!user_specified) + return; + + /* + * @c console was defined by the user on the command line. + * Do not clear when added twice also by SPCR or the device tree. + */ + c->user_specified = true; + /* At least one console defined by the user on the command line. */ + console_set_on_cmdline = 1; +} + static int __add_preferred_console(char *name, int idx, char *options, char *brl_options, bool user_specified) { @@ -2339,8 +2353,7 @@ static int __add_preferred_console(char *name, int idx, char *options, if (strcmp(c->name, name) == 0 && c->index == idx) { if (!brl_options) preferred_console = i; - if (user_specified) - c->user_specified = true; + set_user_specified(c, user_specified); return 0; } } @@ -2350,7 +2363,7 @@ static int __add_preferred_console(char *name, int idx, char *options, preferred_console = i; strlcpy(c->name, name, sizeof(c->name)); c->options = options; - c->user_specified = user_specified; + set_user_specified(c, user_specified); braille_set_options(c, brl_options); c->index = idx; @@ -2416,7 +2429,6 @@ static int __init console_setup(char *str) *s = 0; __add_preferred_console(buf, idx, options, brl_options, true); - console_set_on_cmdline = 1; return 1; } __setup("console=", console_setup); From ce06e863f36f16cdc3b84c7206cd13d5f597d623 Mon Sep 17 00:00:00 2001 From: Jiapeng Chong Date: Wed, 16 Feb 2022 11:19:57 +0800 Subject: [PATCH 29/67] printk: make suppress_panic_printk static This symbol is not used outside of printk.c, so marks it static. Fix the following sparse warning: kernel/printk/printk.c:100:19: warning: symbol 'suppress_panic_printk' was not declared. Should it be static? Reported-by: Abaci Robot Signed-off-by: Jiapeng Chong Reviewed-by: Sergey Senozhatsky Reviewed-by: Miguel Ojeda Signed-off-by: Petr Mladek Link: https://lore.kernel.org/r/20220216031957.9761-1-jiapeng.chong@linux.alibaba.com --- kernel/printk/printk.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/printk/printk.c b/kernel/printk/printk.c index 6a51907a33b9..f9430ac4caca 100644 --- a/kernel/printk/printk.c +++ b/kernel/printk/printk.c @@ -97,7 +97,7 @@ int __read_mostly suppress_printk; * During panic, heavy printk by other CPUs can delay the * panic and risk deadlock on console resources. */ -int __read_mostly suppress_panic_printk; +static int __read_mostly suppress_panic_printk; #ifdef CONFIG_LOCKDEP static struct lockdep_map console_lock_dep_map = { From 9f8e5aee93ed2482638d577a56806b455084b595 Mon Sep 17 00:00:00 2001 From: "Steven Rostedt (Google)" Date: Mon, 14 Feb 2022 12:00:59 -0500 Subject: [PATCH 30/67] tracing: Fix allocation of last_cmd in last_cmd_set() The strncat() used in last_cmd_set() includes the nul byte of length of the string being copied in, when it should only hold the size of the string being copied (not the nul byte). Change it to subtract the length of the allocated space and the nul byte to pass that into the strncat(). Also, assign "len" instead of initializing it to zero and its first update is to do a "+=". Link: https://lore.kernel.org/all/202202140628.fj6e4w4v-lkp@intel.com/ Reported-by: kernel test robot Signed-off-by: Steven Rostedt (Google) --- kernel/trace/trace_events_hist.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/kernel/trace/trace_events_hist.c b/kernel/trace/trace_events_hist.c index 5e8970624bce..78788049f3d3 100644 --- a/kernel/trace/trace_events_hist.c +++ b/kernel/trace/trace_events_hist.c @@ -744,19 +744,20 @@ static void last_cmd_set(struct trace_event_file *file, char *str) { const char *system = NULL, *name = NULL; struct trace_event_call *call; - int len = 0; + int len; if (!str) return; - len += sizeof(HIST_PREFIX) + strlen(str) + 1; + len = sizeof(HIST_PREFIX) + strlen(str) + 1; kfree(last_cmd); last_cmd = kzalloc(len, GFP_KERNEL); if (!last_cmd) return; strcpy(last_cmd, HIST_PREFIX); - strncat(last_cmd, str, len - sizeof(HIST_PREFIX)); + len -= sizeof(HIST_PREFIX) + 1; + strncat(last_cmd, str, len); if (file) { call = file->event_call; From 84842911322fc6a02a03ab9e728a48c691fe3efd Mon Sep 17 00:00:00 2001 From: Christophe Leroy Date: Thu, 17 Feb 2022 09:49:59 +0100 Subject: [PATCH 31/67] vsprintf: Fix %pK with kptr_restrict == 0 Although kptr_restrict is set to 0 and the kernel is booted with no_hash_pointers parameter, the content of /proc/vmallocinfo is lacking the real addresses. / # cat /proc/vmallocinfo 0x(ptrval)-0x(ptrval) 8192 load_module+0xc0c/0x2c0c pages=1 vmalloc 0x(ptrval)-0x(ptrval) 12288 start_kernel+0x4e0/0x690 pages=2 vmalloc 0x(ptrval)-0x(ptrval) 12288 start_kernel+0x4e0/0x690 pages=2 vmalloc 0x(ptrval)-0x(ptrval) 8192 _mpic_map_mmio.constprop.0+0x20/0x44 phys=0x80041000 ioremap 0x(ptrval)-0x(ptrval) 12288 _mpic_map_mmio.constprop.0+0x20/0x44 phys=0x80041000 ioremap ... According to the documentation for /proc/sys/kernel/, %pK is equivalent to %p when kptr_restrict is set to 0. Fixes: 5ead723a20e0 ("lib/vsprintf: no_hash_pointers prints all addresses as unhashed") Signed-off-by: Christophe Leroy Reviewed-by: Petr Mladek Signed-off-by: Petr Mladek Link: https://lore.kernel.org/r/107476128e59bff11a309b5bf7579a1753a41aca.1645087605.git.christophe.leroy@csgroup.eu --- .../admin-guide/kernel-parameters.txt | 3 +- lib/vsprintf.c | 36 +++++++++++-------- 2 files changed, 22 insertions(+), 17 deletions(-) diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt index 46bfadf02bc7..86e2b586c171 100644 --- a/Documentation/admin-guide/kernel-parameters.txt +++ b/Documentation/admin-guide/kernel-parameters.txt @@ -3491,8 +3491,7 @@ difficult since unequal pointers can no longer be compared. However, if this command-line option is specified, then all normal pointers will have their true - value printed. Pointers printed via %pK may still be - hashed. This option should only be specified when + value printed. This option should only be specified when debugging the kernel. Please do not use on production kernels. diff --git a/lib/vsprintf.c b/lib/vsprintf.c index 53d6081f9e8b..8fb4a21c0b60 100644 --- a/lib/vsprintf.c +++ b/lib/vsprintf.c @@ -53,6 +53,10 @@ #include #include "kstrtox.h" +/* Disable pointer hashing if requested */ +bool no_hash_pointers __ro_after_init; +EXPORT_SYMBOL_GPL(no_hash_pointers); + static noinline unsigned long long simple_strntoull(const char *startp, size_t max_chars, char **endp, unsigned int base) { const char *cp; @@ -848,6 +852,19 @@ static char *ptr_to_id(char *buf, char *end, const void *ptr, return pointer_string(buf, end, (const void *)hashval, spec); } +static char *default_pointer(char *buf, char *end, const void *ptr, + struct printf_spec spec) +{ + /* + * default is to _not_ leak addresses, so hash before printing, + * unless no_hash_pointers is specified on the command line. + */ + if (unlikely(no_hash_pointers)) + return pointer_string(buf, end, ptr, spec); + + return ptr_to_id(buf, end, ptr, spec); +} + int kptr_restrict __read_mostly; static noinline_for_stack @@ -857,7 +874,7 @@ char *restricted_pointer(char *buf, char *end, const void *ptr, switch (kptr_restrict) { case 0: /* Handle as %p, hash and do _not_ leak addresses. */ - return ptr_to_id(buf, end, ptr, spec); + return default_pointer(buf, end, ptr, spec); case 1: { const struct cred *cred; @@ -2233,10 +2250,6 @@ char *fwnode_string(char *buf, char *end, struct fwnode_handle *fwnode, return widen_string(buf, buf - buf_start, end, spec); } -/* Disable pointer hashing if requested */ -bool no_hash_pointers __ro_after_init; -EXPORT_SYMBOL_GPL(no_hash_pointers); - int __init no_hash_pointers_enable(char *str) { if (no_hash_pointers) @@ -2465,7 +2478,7 @@ char *pointer(const char *fmt, char *buf, char *end, void *ptr, case 'e': /* %pe with a non-ERR_PTR gets treated as plain %p */ if (!IS_ERR(ptr)) - break; + return default_pointer(buf, end, ptr, spec); return err_ptr(buf, end, ptr, spec); case 'u': case 'k': @@ -2475,16 +2488,9 @@ char *pointer(const char *fmt, char *buf, char *end, void *ptr, default: return error_string(buf, end, "(einval)", spec); } + default: + return default_pointer(buf, end, ptr, spec); } - - /* - * default is to _not_ leak addresses, so hash before printing, - * unless no_hash_pointers is specified on the command line. - */ - if (unlikely(no_hash_pointers)) - return pointer_string(buf, end, ptr, spec); - else - return ptr_to_id(buf, end, ptr, spec); } /* From b665eae7a788c5e2bc10f9ac3c0137aa0ad1fc97 Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Mon, 28 Feb 2022 14:05:56 -0800 Subject: [PATCH 32/67] printk: fix return value of printk.devkmsg __setup handler If an invalid option value is used with "printk.devkmsg=", it is silently ignored. If a valid option value is used, it is honored but the wrong return value (0) is used, indicating that the command line option had an error and was not handled. This string is not added to init's environment strings due to init/main.c::unknown_bootoption() checking for a '.' in the boot option string and then considering that string to be an "Unused module parameter". Print a warning message if a bad option string is used. Always return 1 from the __setup handler to indicate that the command line option has been handled. Fixes: 750afe7babd1 ("printk: add kernel parameter to control writes to /dev/kmsg") Signed-off-by: Randy Dunlap Reported-by: Igor Zhbanov Link: lore.kernel.org/r/64644a2f-4a20-bab3-1e15-3b2cdd0defe3@omprussia.ru Cc: Borislav Petkov Cc: Andrew Morton Cc: Petr Mladek Cc: Sergey Senozhatsky Cc: Steven Rostedt Cc: John Ogness Reviewed-by: John Ogness Reviewed-by: Sergey Senozhatsky Reviewed-by: Petr Mladek Signed-off-by: Petr Mladek Link: https://lore.kernel.org/r/20220228220556.23484-1-rdunlap@infradead.org --- kernel/printk/printk.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/kernel/printk/printk.c b/kernel/printk/printk.c index 266cc974b0e3..21fb4e1cd7db 100644 --- a/kernel/printk/printk.c +++ b/kernel/printk/printk.c @@ -146,8 +146,10 @@ static int __control_devkmsg(char *str) static int __init control_devkmsg(char *str) { - if (__control_devkmsg(str) < 0) + if (__control_devkmsg(str) < 0) { + pr_warn("printk.devkmsg: bad option string '%s'\n", str); return 1; + } /* * Set sysctl string accordingly: @@ -166,7 +168,7 @@ static int __init control_devkmsg(char *str) */ devkmsg_log |= DEVKMSG_LOG_MASK_LOCK; - return 0; + return 1; } __setup("printk.devkmsg=", control_devkmsg); From d1d28bd9a0f896c7d2f7147ca631f86c49b16d7f Mon Sep 17 00:00:00 2001 From: Lianjie Zhang Date: Sun, 6 Mar 2022 15:38:18 +0800 Subject: [PATCH 33/67] mm/slub: use helper macro __ATTR_XX_MODE for SLAB_ATTR(_RO) This allows more concise code, and VERIFY_OCTAL_PERMISSIONS() can help validate any future change. Signed-off-by: Lianjie Zhang Acked-by: David Rientjes Signed-off-by: Vlastimil Babka Link: https://lore.kernel.org/r/20220306073818.15089-1-zhanglianjie@uniontech.com --- mm/slub.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/mm/slub.c b/mm/slub.c index 261474092e43..d33aada17985 100644 --- a/mm/slub.c +++ b/mm/slub.c @@ -5344,12 +5344,10 @@ struct slab_attribute { }; #define SLAB_ATTR_RO(_name) \ - static struct slab_attribute _name##_attr = \ - __ATTR(_name, 0400, _name##_show, NULL) + static struct slab_attribute _name##_attr = __ATTR_RO_MODE(_name, 0400) #define SLAB_ATTR(_name) \ - static struct slab_attribute _name##_attr = \ - __ATTR(_name, 0600, _name##_show, _name##_store) + static struct slab_attribute _name##_attr = __ATTR_RW_MODE(_name, 0600) static ssize_t slab_size_show(struct kmem_cache *s, char *buf) { From 5182f3c9180397b16d15981b385ecfad9249e527 Mon Sep 17 00:00:00 2001 From: Hyeonggon Yoo <42.hyeyoo@gmail.com> Date: Mon, 7 Mar 2022 07:40:55 +0000 Subject: [PATCH 34/67] mm/slub: limit number of node partial slabs only in cache creation SLUB sets number of minimum partial slabs for node (min_partial) using set_min_partial(). SLUB holds at least min_partial slabs even if they're empty to avoid excessive use of page allocator. set_min_partial() limits value of min_partial limits value of min_partial MIN_PARTIAL and MAX_PARTIAL. As set_min_partial() can be called by min_partial_store() too, Only limit value of min_partial in kmem_cache_open() so that it can be changed to value that a user wants. [ rientjes@google.com: Fold set_min_partial() into its callers ] Signed-off-by: Hyeonggon Yoo <42.hyeyoo@gmail.com> Reviewed-by: Vlastimil Babka Reviewed-by: Roman Gushchin Signed-off-by: Vlastimil Babka Link: https://lore.kernel.org/r/20220307074057.902222-2-42.hyeyoo@gmail.com --- mm/slub.c | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/mm/slub.c b/mm/slub.c index 261474092e43..1ce09b0347ad 100644 --- a/mm/slub.c +++ b/mm/slub.c @@ -4000,15 +4000,6 @@ static int init_kmem_cache_nodes(struct kmem_cache *s) return 1; } -static void set_min_partial(struct kmem_cache *s, unsigned long min) -{ - if (min < MIN_PARTIAL) - min = MIN_PARTIAL; - else if (min > MAX_PARTIAL) - min = MAX_PARTIAL; - s->min_partial = min; -} - static void set_cpu_partial(struct kmem_cache *s) { #ifdef CONFIG_SLUB_CPU_PARTIAL @@ -4215,7 +4206,8 @@ static int kmem_cache_open(struct kmem_cache *s, slab_flags_t flags) * The larger the object size is, the more slabs we want on the partial * list to avoid pounding the page allocator excessively. */ - set_min_partial(s, ilog2(s->size) / 2); + s->min_partial = min_t(unsigned long, MAX_PARTIAL, ilog2(s->size) / 2); + s->min_partial = max_t(unsigned long, MIN_PARTIAL, s->min_partial); set_cpu_partial(s); @@ -5396,7 +5388,7 @@ static ssize_t min_partial_store(struct kmem_cache *s, const char *buf, if (err) return err; - set_min_partial(s, min); + s->min_partial = min; return length; } SLAB_ATTR(min_partial); From 6d3a16d09bfac2883b8ea12a83d4420a4062d8c0 Mon Sep 17 00:00:00 2001 From: Hyeonggon Yoo <42.hyeyoo@gmail.com> Date: Mon, 7 Mar 2022 07:40:56 +0000 Subject: [PATCH 35/67] mm/slub: refactor deactivate_slab() Simplify deactivate_slab() by unlocking n->list_lock and retrying cmpxchg_double() when cmpxchg_double() fails, and perform add_{partial,full} only when it succeed. Releasing and taking n->list_lock again here is not harmful as SLUB avoids deactivating slabs as much as possible. [ vbabka@suse.cz: perform add_{partial,full} when cmpxchg_double() succeed. count deactivating full slabs even if debugging flag is not set. ] Signed-off-by: Hyeonggon Yoo <42.hyeyoo@gmail.com> Reviewed-by: Vlastimil Babka Reviewed-by: Roman Gushchin Signed-off-by: Vlastimil Babka Link: https://lore.kernel.org/r/20220307074057.902222-3-42.hyeyoo@gmail.com --- mm/slub.c | 91 ++++++++++++++++++++++++------------------------------- 1 file changed, 39 insertions(+), 52 deletions(-) diff --git a/mm/slub.c b/mm/slub.c index 1ce09b0347ad..e6df45e12b86 100644 --- a/mm/slub.c +++ b/mm/slub.c @@ -2348,10 +2348,10 @@ static void init_kmem_cache_cpus(struct kmem_cache *s) static void deactivate_slab(struct kmem_cache *s, struct slab *slab, void *freelist) { - enum slab_modes { M_NONE, M_PARTIAL, M_FULL, M_FREE }; + enum slab_modes { M_NONE, M_PARTIAL, M_FULL, M_FREE, M_FULL_NOLIST }; struct kmem_cache_node *n = get_node(s, slab_nid(slab)); - int lock = 0, free_delta = 0; - enum slab_modes l = M_NONE, m = M_NONE; + int free_delta = 0; + enum slab_modes mode = M_NONE; void *nextfree, *freelist_iter, *freelist_tail; int tail = DEACTIVATE_TO_HEAD; unsigned long flags = 0; @@ -2393,14 +2393,10 @@ static void deactivate_slab(struct kmem_cache *s, struct slab *slab, * Ensure that the slab is unfrozen while the list presence * reflects the actual number of objects during unfreeze. * - * We setup the list membership and then perform a cmpxchg - * with the count. If there is a mismatch then the slab - * is not unfrozen but the slab is on the wrong list. - * - * Then we restart the process which may have to remove - * the slab from the list that we just put it on again - * because the number of objects in the slab may have - * changed. + * We first perform cmpxchg holding lock and insert to list + * when it succeed. If there is mismatch then the slab is not + * unfrozen and number of objects in the slab may have changed. + * Then release lock and retry cmpxchg again. */ redo: @@ -2419,61 +2415,52 @@ redo: new.frozen = 0; - if (!new.inuse && n->nr_partial >= s->min_partial) - m = M_FREE; - else if (new.freelist) { - m = M_PARTIAL; - if (!lock) { - lock = 1; - /* - * Taking the spinlock removes the possibility that - * acquire_slab() will see a slab that is frozen - */ - spin_lock_irqsave(&n->list_lock, flags); - } + if (!new.inuse && n->nr_partial >= s->min_partial) { + mode = M_FREE; + } else if (new.freelist) { + mode = M_PARTIAL; + /* + * Taking the spinlock removes the possibility that + * acquire_slab() will see a slab that is frozen + */ + spin_lock_irqsave(&n->list_lock, flags); + } else if (kmem_cache_debug_flags(s, SLAB_STORE_USER)) { + mode = M_FULL; + /* + * This also ensures that the scanning of full + * slabs from diagnostic functions will not see + * any frozen slabs. + */ + spin_lock_irqsave(&n->list_lock, flags); } else { - m = M_FULL; - if (kmem_cache_debug_flags(s, SLAB_STORE_USER) && !lock) { - lock = 1; - /* - * This also ensures that the scanning of full - * slabs from diagnostic functions will not see - * any frozen slabs. - */ - spin_lock_irqsave(&n->list_lock, flags); - } + mode = M_FULL_NOLIST; } - if (l != m) { - if (l == M_PARTIAL) - remove_partial(n, slab); - else if (l == M_FULL) - remove_full(s, n, slab); - if (m == M_PARTIAL) - add_partial(n, slab, tail); - else if (m == M_FULL) - add_full(s, n, slab); - } - - l = m; if (!cmpxchg_double_slab(s, slab, old.freelist, old.counters, new.freelist, new.counters, - "unfreezing slab")) + "unfreezing slab")) { + if (mode == M_PARTIAL || mode == M_FULL) + spin_unlock_irqrestore(&n->list_lock, flags); goto redo; + } - if (lock) + + if (mode == M_PARTIAL) { + add_partial(n, slab, tail); spin_unlock_irqrestore(&n->list_lock, flags); - - if (m == M_PARTIAL) stat(s, tail); - else if (m == M_FULL) - stat(s, DEACTIVATE_FULL); - else if (m == M_FREE) { + } else if (mode == M_FREE) { stat(s, DEACTIVATE_EMPTY); discard_slab(s, slab); stat(s, FREE_SLAB); + } else if (mode == M_FULL) { + add_full(s, n, slab); + spin_unlock_irqrestore(&n->list_lock, flags); + stat(s, DEACTIVATE_FULL); + } else if (mode == M_FULL_NOLIST) { + stat(s, DEACTIVATE_FULL); } } From ae44d81d502756cb97e7b698207083967d6f20a3 Mon Sep 17 00:00:00 2001 From: Miaohe Lin Date: Wed, 9 Mar 2022 17:20:36 +0800 Subject: [PATCH 36/67] mm/slub: remove forced_order parameter in calculate_sizes Since commit 32a6f409b693 ("mm, slub: remove runtime allocation order changes"), forced_order is always -1. Remove this unneeded parameter to simplify the code. Signed-off-by: Miaohe Lin Reviewed-by: Hyeonggon Yoo <42.hyeyoo@gmail.com> Signed-off-by: Vlastimil Babka Link: https://lore.kernel.org/r/20220309092036.50844-1-linmiaohe@huawei.com --- mm/slub.c | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/mm/slub.c b/mm/slub.c index d33aada17985..498b56f66f5e 100644 --- a/mm/slub.c +++ b/mm/slub.c @@ -4046,7 +4046,7 @@ static void set_cpu_partial(struct kmem_cache *s) * calculate_sizes() determines the order and the distribution of data within * a slab object. */ -static int calculate_sizes(struct kmem_cache *s, int forced_order) +static int calculate_sizes(struct kmem_cache *s) { slab_flags_t flags = s->flags; unsigned int size = s->object_size; @@ -4150,10 +4150,7 @@ static int calculate_sizes(struct kmem_cache *s, int forced_order) size = ALIGN(size, s->align); s->size = size; s->reciprocal_size = reciprocal_value(size); - if (forced_order >= 0) - order = forced_order; - else - order = calculate_order(size); + order = calculate_order(size); if ((int)order < 0) return 0; @@ -4189,7 +4186,7 @@ static int kmem_cache_open(struct kmem_cache *s, slab_flags_t flags) s->random = get_random_long(); #endif - if (!calculate_sizes(s, -1)) + if (!calculate_sizes(s)) goto error; if (disable_higher_order_debug) { /* @@ -4199,7 +4196,7 @@ static int kmem_cache_open(struct kmem_cache *s, slab_flags_t flags) if (get_order(s->size) > get_order(s->object_size)) { s->flags &= ~DEBUG_METADATA_FLAGS; s->offset = 0; - if (!calculate_sizes(s, -1)) + if (!calculate_sizes(s)) goto error; } } From 382627824afb09cd86192f4b649fca8c5bfe5f40 Mon Sep 17 00:00:00 2001 From: Xiongwei Song Date: Thu, 10 Mar 2022 22:07:00 +0800 Subject: [PATCH 37/67] mm: slab: Delete unused SLAB_DEACTIVATED flag Since commit 9855609bde03 ("mm: memcg/slab: use a single set of kmem_caches for all accounted allocations") deleted all SLAB_DEACTIVATED users, therefore this flag is not needed any more, let's delete it. Signed-off-by: Xiongwei Song Acked-by: David Rientjes Reviewed-by: Roman Gushchin Acked-by: Hyeonggon Yoo <42.hyeyoo@gmail.com> Signed-off-by: Vlastimil Babka Link: https://lore.kernel.org/r/20220310140701.87908-2-sxwjean@me.com --- include/linux/slab.h | 3 --- 1 file changed, 3 deletions(-) diff --git a/include/linux/slab.h b/include/linux/slab.h index 37bde99b74af..b6b3eed6c7c4 100644 --- a/include/linux/slab.h +++ b/include/linux/slab.h @@ -117,9 +117,6 @@ #define SLAB_RECLAIM_ACCOUNT ((slab_flags_t __force)0x00020000U) #define SLAB_TEMPORARY SLAB_RECLAIM_ACCOUNT /* Objects are short-lived */ -/* Slab deactivation flag */ -#define SLAB_DEACTIVATED ((slab_flags_t __force)0x10000000U) - /* * ZERO_SIZE_PTR will be returned for zero sized kmalloc requests. * From a485e1dacdb09802ad0ba1126bc4d029773ae1b2 Mon Sep 17 00:00:00 2001 From: Xiongwei Song Date: Thu, 10 Mar 2022 22:07:01 +0800 Subject: [PATCH 38/67] mm: slub: Delete useless parameter of alloc_slab_page() The parameter @s is useless for alloc_slab_page(). It was added in 2014 by commit 5dfb41750992 ("sl[au]b: charge slabs to kmemcg explicitly"). The need for it was removed in 2020 by commit 1f3147b49d75 ("mm: slub: call account_slab_page() after slab page initialization"). Let's delete it. [willy@infradead.org: Added detailed history of @s] Signed-off-by: Xiongwei Song Reviewed-by: Matthew Wilcox (Oracle) Acked-by: David Rientjes Reviewed-by: Roman Gushchin Reviewed-by: Hyeonggon Yoo <42.hyeyoo@gmail.com> Signed-off-by: Vlastimil Babka Link: https://lore.kernel.org/r/20220310140701.87908-3-sxwjean@me.com --- mm/slub.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/mm/slub.c b/mm/slub.c index 498b56f66f5e..c2d3fc51377d 100644 --- a/mm/slub.c +++ b/mm/slub.c @@ -1788,8 +1788,8 @@ static void *setup_object(struct kmem_cache *s, struct slab *slab, /* * Slab allocation and freeing */ -static inline struct slab *alloc_slab_page(struct kmem_cache *s, - gfp_t flags, int node, struct kmem_cache_order_objects oo) +static inline struct slab *alloc_slab_page(gfp_t flags, int node, + struct kmem_cache_order_objects oo) { struct folio *folio; struct slab *slab; @@ -1941,7 +1941,7 @@ static struct slab *allocate_slab(struct kmem_cache *s, gfp_t flags, int node) if ((alloc_gfp & __GFP_DIRECT_RECLAIM) && oo_order(oo) > oo_order(s->min)) alloc_gfp = (alloc_gfp | __GFP_NOMEMALLOC) & ~(__GFP_RECLAIM|__GFP_NOFAIL); - slab = alloc_slab_page(s, alloc_gfp, node, oo); + slab = alloc_slab_page(alloc_gfp, node, oo); if (unlikely(!slab)) { oo = s->min; alloc_gfp = flags; @@ -1949,7 +1949,7 @@ static struct slab *allocate_slab(struct kmem_cache *s, gfp_t flags, int node) * Allocation may have failed due to fragmentation. * Try a lower order alloc if possible */ - slab = alloc_slab_page(s, alloc_gfp, node, oo); + slab = alloc_slab_page(alloc_gfp, node, oo); if (unlikely(!slab)) goto out; stat(s, ORDER_FALLBACK); From 173c2049d12b441b498d6423276f5dd76b1e637b Mon Sep 17 00:00:00 2001 From: Beau Belgrave Date: Thu, 24 Feb 2022 10:16:37 -0800 Subject: [PATCH 39/67] user_events: Fix potential uninitialized pointer while parsing field Ensure name is initialized by default to NULL to prevent possible edge cases that could lead to it being left uninitialized. Add an explicit check for NULL name to ensure edge boundaries. Link: https://lore.kernel.org/bpf/20220224105334.GA2248@kili/ Link: https://lore.kernel.org/linux-trace-devel/20220224181637.2129-1-beaub@linux.microsoft.com Signed-off-by: Beau Belgrave Reported-by: Dan Carpenter Signed-off-by: Steven Rostedt (Google) --- kernel/trace/trace_events_user.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/kernel/trace/trace_events_user.c b/kernel/trace/trace_events_user.c index 2b5e9fdb63a0..9a6191a6a786 100644 --- a/kernel/trace/trace_events_user.c +++ b/kernel/trace/trace_events_user.c @@ -362,6 +362,8 @@ skip_next: *field++ = '\0'; depth++; parse: + name = NULL; + while ((part = strsep(&field, " ")) != NULL) { switch (depth++) { case FIELD_DEPTH_TYPE: @@ -382,7 +384,7 @@ parse: } } - if (depth < FIELD_DEPTH_SIZE) + if (depth < FIELD_DEPTH_SIZE || !name) return -EINVAL; if (depth == FIELD_DEPTH_SIZE) From 5677a3d713ad795e3ce32d631941e982777b43db Mon Sep 17 00:00:00 2001 From: "Steven Rostedt (Google)" Date: Tue, 8 Mar 2022 10:39:45 -0500 Subject: [PATCH 40/67] tracing: Fix last_cmd_set() string management in histogram code Using strnlen(dest, str, n) is confusing, as the size of dest must be strlen(dest) + n + 1. Even more confusing, using sizeof(string constant) gives you strlen(string constant) + 1 and not just strlen(). These two together made using strncat() with a constant string a bit off in the calculations as we have: len = sizeof(HIST_PREFIX) + strlen(str) + 1; kfree(last_cmd); last_cmd = kzalloc(len, GFP_KERNEL); strcpy(last_cmd, HIST_PREFIX); len -= sizeof(HIST_PREFIX) + 1; strncat(last_cmd, str, len); The above works if we s/sizeof/strlen/ with HIST_PREFIX (which is defined as "hist:", but because sizeof(HIST_PREFIX) is equal to strlen(HIST_PREFIX) + 1, we can drop the +1 in the code. But at least comment that we are doing so. Link: https://lore.kernel.org/all/202203082112.Iu7tvFl4-lkp@intel.com/ Fixes: 9f8e5aee93ed2 ("tracing: Fix allocation of last_cmd in last_cmd_set()") Reported-by: kernel test robot Signed-off-by: Steven Rostedt (Google) --- kernel/trace/trace_events_hist.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/kernel/trace/trace_events_hist.c b/kernel/trace/trace_events_hist.c index 78788049f3d3..954b19e2f196 100644 --- a/kernel/trace/trace_events_hist.c +++ b/kernel/trace/trace_events_hist.c @@ -749,14 +749,16 @@ static void last_cmd_set(struct trace_event_file *file, char *str) if (!str) return; - len = sizeof(HIST_PREFIX) + strlen(str) + 1; + /* sizeof() contains the nul byte */ + len = sizeof(HIST_PREFIX) + strlen(str); kfree(last_cmd); last_cmd = kzalloc(len, GFP_KERNEL); if (!last_cmd) return; strcpy(last_cmd, HIST_PREFIX); - len -= sizeof(HIST_PREFIX) + 1; + /* Again, sizeof() contains the nul byte */ + len -= sizeof(HIST_PREFIX); strncat(last_cmd, str, len); if (file) { From 8bcd06632b73291e8734f46084ed04f4106b840a Mon Sep 17 00:00:00 2001 From: "Steven Rostedt (Google)" Date: Thu, 3 Mar 2022 17:05:31 -0500 Subject: [PATCH 41/67] tracing: Allow custom events to be added to the tracefs directory Allow custom events to be added to the events directory in the tracefs file system. For example, a module could be installed that attaches to an event and wants to be enabled and disabled via the tracefs file system. It would use trace_add_event_call() to add the event to the tracefs directory, and trace_remove_event_call() to remove it. Make both those functions EXPORT_SYMBOL_GPL(). Link: https://lkml.kernel.org/r/20220303220625.186988045@goodmis.org Cc: Ingo Molnar Cc: Andrew Morton Cc: Joel Fernandes Cc: Peter Zijlstra Cc: Masami Hiramatsu Cc: Tom Zanussi Signed-off-by: Steven Rostedt (Google) --- kernel/trace/trace_events.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/kernel/trace/trace_events.c b/kernel/trace/trace_events.c index 3147614c1812..38afd66d80e3 100644 --- a/kernel/trace/trace_events.c +++ b/kernel/trace/trace_events.c @@ -2758,6 +2758,7 @@ int trace_add_event_call(struct trace_event_call *call) mutex_unlock(&trace_types_lock); return ret; } +EXPORT_SYMBOL_GPL(trace_add_event_call); /* * Must be called under locking of trace_types_lock, event_mutex and @@ -2819,6 +2820,7 @@ int trace_remove_event_call(struct trace_event_call *call) return ret; } +EXPORT_SYMBOL_GPL(trace_remove_event_call); #define for_each_event(event, start, end) \ for (event = start; \ From 953c2f052112a857c00058a641dc0c58ec7551d4 Mon Sep 17 00:00:00 2001 From: "Steven Rostedt (Google)" Date: Thu, 3 Mar 2022 17:05:32 -0500 Subject: [PATCH 42/67] tracing: Add sample code for custom trace events Add sample code to show how to create custom trace events in the tracefs directory that can be enabled and modified like any event in tracefs (including triggers, histograms, synthetic events and event probes). The example is creating a custom sched_switch and a sched_waking to limit what is recorded: If the custom sched switch only records the prev_prio, next_prio and next_pid, it can bring the size from 64 bytes per event, down to just 16 bytes! If sched_waking only records the prio and pid of the woken event, it will bring the size down from 36 bytes to 12 bytes per event. This will allow for a much smaller footprint into the ring buffer and keep more events from dropping. Link: https://lkml.kernel.org/r/20220303220625.369226746@goodmis.org Cc: Ingo Molnar Cc: Andrew Morton Cc: Peter Zijlstra Cc: Masami Hiramatsu Cc: Tom Zanussi Suggested-by: Joel Fernandes Signed-off-by: Steven Rostedt (Google) --- samples/Kconfig | 8 +- samples/Makefile | 1 + samples/trace_events/Makefile | 2 + samples/trace_events/trace_custom_sched.c | 271 ++++++++++++++++++++++ 4 files changed, 281 insertions(+), 1 deletion(-) create mode 100644 samples/trace_events/trace_custom_sched.c diff --git a/samples/Kconfig b/samples/Kconfig index 22cc921ae291..10e021c72282 100644 --- a/samples/Kconfig +++ b/samples/Kconfig @@ -14,7 +14,13 @@ config SAMPLE_TRACE_EVENTS tristate "Build trace_events examples -- loadable modules only" depends on EVENT_TRACING && m help - This build trace event example modules. + This builds the trace event example module. + +config SAMPLE_TRACE_CUSTOM_EVENTS + tristate "Build custom trace event example -- loadable modules only" + depends on EVENT_TRACING && m + help + This builds the custom trace event example module. config SAMPLE_TRACE_PRINTK tristate "Build trace_printk module - tests various trace_printk formats" diff --git a/samples/Makefile b/samples/Makefile index 1ae4de99c983..448343e8faeb 100644 --- a/samples/Makefile +++ b/samples/Makefile @@ -20,6 +20,7 @@ obj-$(CONFIG_SAMPLE_RPMSG_CLIENT) += rpmsg/ subdir-$(CONFIG_SAMPLE_SECCOMP) += seccomp subdir-$(CONFIG_SAMPLE_TIMER) += timers obj-$(CONFIG_SAMPLE_TRACE_EVENTS) += trace_events/ +obj-$(CONFIG_SAMPLE_TRACE_CUSTOM_EVENTS) += trace_events/ obj-$(CONFIG_SAMPLE_TRACE_PRINTK) += trace_printk/ obj-$(CONFIG_SAMPLE_FTRACE_DIRECT) += ftrace/ obj-$(CONFIG_SAMPLE_FTRACE_DIRECT_MULTI) += ftrace/ diff --git a/samples/trace_events/Makefile b/samples/trace_events/Makefile index b78344e7bbed..e98afc447fe1 100644 --- a/samples/trace_events/Makefile +++ b/samples/trace_events/Makefile @@ -13,3 +13,5 @@ CFLAGS_trace-events-sample.o := -I$(src) obj-$(CONFIG_SAMPLE_TRACE_EVENTS) += trace-events-sample.o + +obj-$(CONFIG_SAMPLE_TRACE_CUSTOM_EVENTS) += trace_custom_sched.o diff --git a/samples/trace_events/trace_custom_sched.c b/samples/trace_events/trace_custom_sched.c new file mode 100644 index 000000000000..70a12c32ff99 --- /dev/null +++ b/samples/trace_events/trace_custom_sched.c @@ -0,0 +1,271 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * event tracer + * + * Copyright (C) 2022 Google Inc, Steven Rostedt + */ + +#define pr_fmt(fmt) fmt + +#include +#include +#include +#include +#include + +#define THIS_SYSTEM "custom_sched" + +#define SCHED_PRINT_FMT \ + C("prev_prio=%d next_pid=%d next_prio=%d", REC->prev_prio, REC->next_pid, \ + REC->next_prio) + +#define SCHED_WAKING_FMT \ + C("pid=%d prio=%d", REC->pid, REC->prio) + +#undef C +#define C(a, b...) a, b + +static struct trace_event_fields sched_switch_fields[] = { + { + .type = "unsigned short", + .name = "prev_prio", + .size = sizeof(short), + .align = __alignof__(short), + .is_signed = 0, + .filter_type = FILTER_OTHER, + }, + { + .type = "unsigned short", + .name = "next_prio", + .size = sizeof(short), + .align = __alignof__(short), + .is_signed = 0, + .filter_type = FILTER_OTHER, + }, + { + .type = "unsigned int", + .name = "next_prio", + .size = sizeof(int), + .align = __alignof__(int), + .is_signed = 0, + .filter_type = FILTER_OTHER, + }, + {} +}; + +struct sched_event { + struct trace_entry ent; + unsigned short prev_prio; + unsigned short next_prio; + unsigned int next_pid; +}; + +static struct trace_event_fields sched_waking_fields[] = { + { + .type = "unsigned int", + .name = "pid", + .size = sizeof(int), + .align = __alignof__(int), + .is_signed = 0, + .filter_type = FILTER_OTHER, + }, + { + .type = "unsigned short", + .name = "prio", + .size = sizeof(short), + .align = __alignof__(short), + .is_signed = 0, + .filter_type = FILTER_OTHER, + }, + {} +}; + +struct wake_event { + struct trace_entry ent; + unsigned int pid; + unsigned short prio; +}; + +static void sched_switch_probe(void *data, bool preempt, struct task_struct *prev, + struct task_struct *next) +{ + struct trace_event_file *trace_file = data; + struct trace_event_buffer fbuffer; + struct sched_event *entry; + + if (trace_trigger_soft_disabled(trace_file)) + return; + + entry = trace_event_buffer_reserve(&fbuffer, trace_file, + sizeof(*entry)); + + if (!entry) + return; + + entry->prev_prio = prev->prio; + entry->next_prio = next->prio; + entry->next_pid = next->pid; + + trace_event_buffer_commit(&fbuffer); +} + +static struct trace_event_class sched_switch_class = { + .system = THIS_SYSTEM, + .reg = trace_event_reg, + .fields_array = sched_switch_fields, + .fields = LIST_HEAD_INIT(sched_switch_class.fields), + .probe = sched_switch_probe, +}; + +static void sched_waking_probe(void *data, struct task_struct *t) +{ + struct trace_event_file *trace_file = data; + struct trace_event_buffer fbuffer; + struct wake_event *entry; + + if (trace_trigger_soft_disabled(trace_file)) + return; + + entry = trace_event_buffer_reserve(&fbuffer, trace_file, + sizeof(*entry)); + + if (!entry) + return; + + entry->prio = t->prio; + entry->pid = t->pid; + + trace_event_buffer_commit(&fbuffer); +} + +static struct trace_event_class sched_waking_class = { + .system = THIS_SYSTEM, + .reg = trace_event_reg, + .fields_array = sched_waking_fields, + .fields = LIST_HEAD_INIT(sched_waking_class.fields), + .probe = sched_waking_probe, +}; + +static enum print_line_t sched_switch_output(struct trace_iterator *iter, int flags, + struct trace_event *trace_event) +{ + struct trace_seq *s = &iter->seq; + struct sched_event *REC = (struct sched_event *)iter->ent; + int ret; + + ret = trace_raw_output_prep(iter, trace_event); + if (ret != TRACE_TYPE_HANDLED) + return ret; + + trace_seq_printf(s, SCHED_PRINT_FMT); + trace_seq_putc(s, '\n'); + + return trace_handle_return(s); +} + +static struct trace_event_functions sched_switch_funcs = { + .trace = sched_switch_output, +}; + +static enum print_line_t sched_waking_output(struct trace_iterator *iter, int flags, + struct trace_event *trace_event) +{ + struct trace_seq *s = &iter->seq; + struct wake_event *REC = (struct wake_event *)iter->ent; + int ret; + + ret = trace_raw_output_prep(iter, trace_event); + if (ret != TRACE_TYPE_HANDLED) + return ret; + + trace_seq_printf(s, SCHED_WAKING_FMT); + trace_seq_putc(s, '\n'); + + return trace_handle_return(s); +} + +static struct trace_event_functions sched_waking_funcs = { + .trace = sched_waking_output, +}; + +#undef C +#define C(a, b...) #a "," __stringify(b) + +static struct trace_event_call sched_switch_call = { + .class = &sched_switch_class, + .event = { + .funcs = &sched_switch_funcs, + }, + .print_fmt = SCHED_PRINT_FMT, + .module = THIS_MODULE, + .flags = TRACE_EVENT_FL_TRACEPOINT, +}; + +static struct trace_event_call sched_waking_call = { + .class = &sched_waking_class, + .event = { + .funcs = &sched_waking_funcs, + }, + .print_fmt = SCHED_WAKING_FMT, + .module = THIS_MODULE, + .flags = TRACE_EVENT_FL_TRACEPOINT, +}; + +static void fct(struct tracepoint *tp, void *priv) +{ + if (tp->name && strcmp(tp->name, "sched_switch") == 0) + sched_switch_call.tp = tp; + else if (tp->name && strcmp(tp->name, "sched_waking") == 0) + sched_waking_call.tp = tp; +} + +static int add_event(struct trace_event_call *call) +{ + int ret; + + ret = register_trace_event(&call->event); + if (WARN_ON(!ret)) + return -ENODEV; + + ret = trace_add_event_call(call); + if (WARN_ON(ret)) + unregister_trace_event(&call->event); + + return ret; +} + +static int __init trace_sched_init(void) +{ + int ret; + + check_trace_callback_type_sched_switch(sched_switch_probe); + check_trace_callback_type_sched_waking(sched_waking_probe); + + for_each_kernel_tracepoint(fct, NULL); + + ret = add_event(&sched_switch_call); + if (ret) + return ret; + + ret = add_event(&sched_waking_call); + if (ret) + trace_remove_event_call(&sched_switch_call); + + return ret; +} + +static void __exit trace_sched_exit(void) +{ + trace_set_clr_event(THIS_SYSTEM, "sched_switch", 0); + trace_set_clr_event(THIS_SYSTEM, "sched_waking", 0); + + trace_remove_event_call(&sched_switch_call); + trace_remove_event_call(&sched_waking_call); +} + +module_init(trace_sched_init); +module_exit(trace_sched_exit); + +MODULE_AUTHOR("Steven Rostedt"); +MODULE_DESCRIPTION("Custom scheduling events"); +MODULE_LICENSE("GPL"); From af6b9668e85ffd1502aada8036ccbf4dbd481708 Mon Sep 17 00:00:00 2001 From: "Steven Rostedt (Google)" Date: Thu, 3 Mar 2022 17:05:33 -0500 Subject: [PATCH 43/67] tracing: Move the defines to create TRACE_EVENTS into their own files In an effort to add custom event macros that can be used to create your own custom events based on existing tracepoints, move the defines of the special macros used in TRACE_EVENT() into their own files such that they can be reused for TRACE_CUSTOM_EVENT(). Link: https://lkml.kernel.org/r/20220303220625.553406495@goodmis.org Cc: Ingo Molnar Cc: Andrew Morton Cc: Joel Fernandes Cc: Peter Zijlstra Cc: Masami Hiramatsu Cc: Tom Zanussi Signed-off-by: Steven Rostedt (Google) --- include/trace/stages/init.h | 37 ++ include/trace/stages/stage1_defines.h | 45 +++ include/trace/stages/stage2_defines.h | 48 +++ include/trace/stages/stage3_defines.h | 129 +++++++ include/trace/stages/stage4_defines.h | 57 +++ include/trace/stages/stage5_defines.h | 83 +++++ include/trace/stages/stage6_defines.h | 86 +++++ include/trace/stages/stage7_defines.h | 34 ++ include/trace/trace_events.h | 499 +------------------------- 9 files changed, 527 insertions(+), 491 deletions(-) create mode 100644 include/trace/stages/init.h create mode 100644 include/trace/stages/stage1_defines.h create mode 100644 include/trace/stages/stage2_defines.h create mode 100644 include/trace/stages/stage3_defines.h create mode 100644 include/trace/stages/stage4_defines.h create mode 100644 include/trace/stages/stage5_defines.h create mode 100644 include/trace/stages/stage6_defines.h create mode 100644 include/trace/stages/stage7_defines.h diff --git a/include/trace/stages/init.h b/include/trace/stages/init.h new file mode 100644 index 000000000000..000bcfc8dd2e --- /dev/null +++ b/include/trace/stages/init.h @@ -0,0 +1,37 @@ + +#define __app__(x, y) str__##x##y +#define __app(x, y) __app__(x, y) + +#define TRACE_SYSTEM_STRING __app(TRACE_SYSTEM_VAR,__trace_system_name) + +#define TRACE_MAKE_SYSTEM_STR() \ + static const char TRACE_SYSTEM_STRING[] = \ + __stringify(TRACE_SYSTEM) + +TRACE_MAKE_SYSTEM_STR(); + +#undef TRACE_DEFINE_ENUM +#define TRACE_DEFINE_ENUM(a) \ + static struct trace_eval_map __used __initdata \ + __##TRACE_SYSTEM##_##a = \ + { \ + .system = TRACE_SYSTEM_STRING, \ + .eval_string = #a, \ + .eval_value = a \ + }; \ + static struct trace_eval_map __used \ + __section("_ftrace_eval_map") \ + *TRACE_SYSTEM##_##a = &__##TRACE_SYSTEM##_##a + +#undef TRACE_DEFINE_SIZEOF +#define TRACE_DEFINE_SIZEOF(a) \ + static struct trace_eval_map __used __initdata \ + __##TRACE_SYSTEM##_##a = \ + { \ + .system = TRACE_SYSTEM_STRING, \ + .eval_string = "sizeof(" #a ")", \ + .eval_value = sizeof(a) \ + }; \ + static struct trace_eval_map __used \ + __section("_ftrace_eval_map") \ + *TRACE_SYSTEM##_##a = &__##TRACE_SYSTEM##_##a diff --git a/include/trace/stages/stage1_defines.h b/include/trace/stages/stage1_defines.h new file mode 100644 index 000000000000..8ab88c766d2b --- /dev/null +++ b/include/trace/stages/stage1_defines.h @@ -0,0 +1,45 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +/* Stage 1 definitions for creating trace events */ + +#undef __field +#define __field(type, item) type item; + +#undef __field_ext +#define __field_ext(type, item, filter_type) type item; + +#undef __field_struct +#define __field_struct(type, item) type item; + +#undef __field_struct_ext +#define __field_struct_ext(type, item, filter_type) type item; + +#undef __array +#define __array(type, item, len) type item[len]; + +#undef __dynamic_array +#define __dynamic_array(type, item, len) u32 __data_loc_##item; + +#undef __string +#define __string(item, src) __dynamic_array(char, item, -1) + +#undef __string_len +#define __string_len(item, src, len) __dynamic_array(char, item, -1) + +#undef __bitmask +#define __bitmask(item, nr_bits) __dynamic_array(char, item, -1) + +#undef __rel_dynamic_array +#define __rel_dynamic_array(type, item, len) u32 __rel_loc_##item; + +#undef __rel_string +#define __rel_string(item, src) __rel_dynamic_array(char, item, -1) + +#undef __rel_string_len +#define __rel_string_len(item, src, len) __rel_dynamic_array(char, item, -1) + +#undef __rel_bitmask +#define __rel_bitmask(item, nr_bits) __rel_dynamic_array(char, item, -1) + +#undef TP_STRUCT__entry +#define TP_STRUCT__entry(args...) args diff --git a/include/trace/stages/stage2_defines.h b/include/trace/stages/stage2_defines.h new file mode 100644 index 000000000000..9f2341df40da --- /dev/null +++ b/include/trace/stages/stage2_defines.h @@ -0,0 +1,48 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +/* Stage 2 definitions for creating trace events */ + +#undef TRACE_DEFINE_ENUM +#define TRACE_DEFINE_ENUM(a) + +#undef TRACE_DEFINE_SIZEOF +#define TRACE_DEFINE_SIZEOF(a) + +#undef __field +#define __field(type, item) + +#undef __field_ext +#define __field_ext(type, item, filter_type) + +#undef __field_struct +#define __field_struct(type, item) + +#undef __field_struct_ext +#define __field_struct_ext(type, item, filter_type) + +#undef __array +#define __array(type, item, len) + +#undef __dynamic_array +#define __dynamic_array(type, item, len) u32 item; + +#undef __string +#define __string(item, src) __dynamic_array(char, item, -1) + +#undef __bitmask +#define __bitmask(item, nr_bits) __dynamic_array(unsigned long, item, -1) + +#undef __string_len +#define __string_len(item, src, len) __dynamic_array(char, item, -1) + +#undef __rel_dynamic_array +#define __rel_dynamic_array(type, item, len) u32 item; + +#undef __rel_string +#define __rel_string(item, src) __rel_dynamic_array(char, item, -1) + +#undef __rel_string_len +#define __rel_string_len(item, src, len) __rel_dynamic_array(char, item, -1) + +#undef __rel_bitmask +#define __rel_bitmask(item, nr_bits) __rel_dynamic_array(unsigned long, item, -1) diff --git a/include/trace/stages/stage3_defines.h b/include/trace/stages/stage3_defines.h new file mode 100644 index 000000000000..0bc131993b7a --- /dev/null +++ b/include/trace/stages/stage3_defines.h @@ -0,0 +1,129 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +/* Stage 3 definitions for creating trace events */ + +#undef __entry +#define __entry field + +#undef TP_printk +#define TP_printk(fmt, args...) fmt "\n", args + +#undef __get_dynamic_array +#define __get_dynamic_array(field) \ + ((void *)__entry + (__entry->__data_loc_##field & 0xffff)) + +#undef __get_dynamic_array_len +#define __get_dynamic_array_len(field) \ + ((__entry->__data_loc_##field >> 16) & 0xffff) + +#undef __get_str +#define __get_str(field) ((char *)__get_dynamic_array(field)) + +#undef __get_rel_dynamic_array +#define __get_rel_dynamic_array(field) \ + ((void *)__entry + \ + offsetof(typeof(*__entry), __rel_loc_##field) + \ + sizeof(__entry->__rel_loc_##field) + \ + (__entry->__rel_loc_##field & 0xffff)) + +#undef __get_rel_dynamic_array_len +#define __get_rel_dynamic_array_len(field) \ + ((__entry->__rel_loc_##field >> 16) & 0xffff) + +#undef __get_rel_str +#define __get_rel_str(field) ((char *)__get_rel_dynamic_array(field)) + +#undef __get_bitmask +#define __get_bitmask(field) \ + ({ \ + void *__bitmask = __get_dynamic_array(field); \ + unsigned int __bitmask_size; \ + __bitmask_size = __get_dynamic_array_len(field); \ + trace_print_bitmask_seq(p, __bitmask, __bitmask_size); \ + }) + +#undef __get_rel_bitmask +#define __get_rel_bitmask(field) \ + ({ \ + void *__bitmask = __get_rel_dynamic_array(field); \ + unsigned int __bitmask_size; \ + __bitmask_size = __get_rel_dynamic_array_len(field); \ + trace_print_bitmask_seq(p, __bitmask, __bitmask_size); \ + }) + +#undef __print_flags +#define __print_flags(flag, delim, flag_array...) \ + ({ \ + static const struct trace_print_flags __flags[] = \ + { flag_array, { -1, NULL }}; \ + trace_print_flags_seq(p, delim, flag, __flags); \ + }) + +#undef __print_symbolic +#define __print_symbolic(value, symbol_array...) \ + ({ \ + static const struct trace_print_flags symbols[] = \ + { symbol_array, { -1, NULL }}; \ + trace_print_symbols_seq(p, value, symbols); \ + }) + +#undef __print_flags_u64 +#undef __print_symbolic_u64 +#if BITS_PER_LONG == 32 +#define __print_flags_u64(flag, delim, flag_array...) \ + ({ \ + static const struct trace_print_flags_u64 __flags[] = \ + { flag_array, { -1, NULL } }; \ + trace_print_flags_seq_u64(p, delim, flag, __flags); \ + }) + +#define __print_symbolic_u64(value, symbol_array...) \ + ({ \ + static const struct trace_print_flags_u64 symbols[] = \ + { symbol_array, { -1, NULL } }; \ + trace_print_symbols_seq_u64(p, value, symbols); \ + }) +#else +#define __print_flags_u64(flag, delim, flag_array...) \ + __print_flags(flag, delim, flag_array) + +#define __print_symbolic_u64(value, symbol_array...) \ + __print_symbolic(value, symbol_array) +#endif + +#undef __print_hex +#define __print_hex(buf, buf_len) \ + trace_print_hex_seq(p, buf, buf_len, false) + +#undef __print_hex_str +#define __print_hex_str(buf, buf_len) \ + trace_print_hex_seq(p, buf, buf_len, true) + +#undef __print_array +#define __print_array(array, count, el_size) \ + ({ \ + BUILD_BUG_ON(el_size != 1 && el_size != 2 && \ + el_size != 4 && el_size != 8); \ + trace_print_array_seq(p, array, count, el_size); \ + }) + +#undef __print_hex_dump +#define __print_hex_dump(prefix_str, prefix_type, \ + rowsize, groupsize, buf, len, ascii) \ + trace_print_hex_dump_seq(p, prefix_str, prefix_type, \ + rowsize, groupsize, buf, len, ascii) + +#undef __print_ns_to_secs +#define __print_ns_to_secs(value) \ + ({ \ + u64 ____val = (u64)(value); \ + do_div(____val, NSEC_PER_SEC); \ + ____val; \ + }) + +#undef __print_ns_without_secs +#define __print_ns_without_secs(value) \ + ({ \ + u64 ____val = (u64)(value); \ + (u32) do_div(____val, NSEC_PER_SEC); \ + }) diff --git a/include/trace/stages/stage4_defines.h b/include/trace/stages/stage4_defines.h new file mode 100644 index 000000000000..780a10fa5279 --- /dev/null +++ b/include/trace/stages/stage4_defines.h @@ -0,0 +1,57 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +/* Stage 4 definitions for creating trace events */ + +#undef __field_ext +#define __field_ext(_type, _item, _filter_type) { \ + .type = #_type, .name = #_item, \ + .size = sizeof(_type), .align = __alignof__(_type), \ + .is_signed = is_signed_type(_type), .filter_type = _filter_type }, + +#undef __field_struct_ext +#define __field_struct_ext(_type, _item, _filter_type) { \ + .type = #_type, .name = #_item, \ + .size = sizeof(_type), .align = __alignof__(_type), \ + 0, .filter_type = _filter_type }, + +#undef __field +#define __field(type, item) __field_ext(type, item, FILTER_OTHER) + +#undef __field_struct +#define __field_struct(type, item) __field_struct_ext(type, item, FILTER_OTHER) + +#undef __array +#define __array(_type, _item, _len) { \ + .type = #_type"["__stringify(_len)"]", .name = #_item, \ + .size = sizeof(_type[_len]), .align = __alignof__(_type), \ + .is_signed = is_signed_type(_type), .filter_type = FILTER_OTHER }, + +#undef __dynamic_array +#define __dynamic_array(_type, _item, _len) { \ + .type = "__data_loc " #_type "[]", .name = #_item, \ + .size = 4, .align = 4, \ + .is_signed = is_signed_type(_type), .filter_type = FILTER_OTHER }, + +#undef __string +#define __string(item, src) __dynamic_array(char, item, -1) + +#undef __string_len +#define __string_len(item, src, len) __dynamic_array(char, item, -1) + +#undef __bitmask +#define __bitmask(item, nr_bits) __dynamic_array(unsigned long, item, -1) + +#undef __rel_dynamic_array +#define __rel_dynamic_array(_type, _item, _len) { \ + .type = "__rel_loc " #_type "[]", .name = #_item, \ + .size = 4, .align = 4, \ + .is_signed = is_signed_type(_type), .filter_type = FILTER_OTHER }, + +#undef __rel_string +#define __rel_string(item, src) __rel_dynamic_array(char, item, -1) + +#undef __rel_string_len +#define __rel_string_len(item, src, len) __rel_dynamic_array(char, item, -1) + +#undef __rel_bitmask +#define __rel_bitmask(item, nr_bits) __rel_dynamic_array(unsigned long, item, -1) diff --git a/include/trace/stages/stage5_defines.h b/include/trace/stages/stage5_defines.h new file mode 100644 index 000000000000..fb15394aae31 --- /dev/null +++ b/include/trace/stages/stage5_defines.h @@ -0,0 +1,83 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +/* Stage 5 definitions for creating trace events */ + +/* + * remember the offset of each array from the beginning of the event. + */ + +#undef __entry +#define __entry entry + +#undef __field +#define __field(type, item) + +#undef __field_ext +#define __field_ext(type, item, filter_type) + +#undef __field_struct +#define __field_struct(type, item) + +#undef __field_struct_ext +#define __field_struct_ext(type, item, filter_type) + +#undef __array +#define __array(type, item, len) + +#undef __dynamic_array +#define __dynamic_array(type, item, len) \ + __item_length = (len) * sizeof(type); \ + __data_offsets->item = __data_size + \ + offsetof(typeof(*entry), __data); \ + __data_offsets->item |= __item_length << 16; \ + __data_size += __item_length; + +#undef __string +#define __string(item, src) __dynamic_array(char, item, \ + strlen((src) ? (const char *)(src) : "(null)") + 1) + +#undef __string_len +#define __string_len(item, src, len) __dynamic_array(char, item, (len) + 1) + +#undef __rel_dynamic_array +#define __rel_dynamic_array(type, item, len) \ + __item_length = (len) * sizeof(type); \ + __data_offsets->item = __data_size + \ + offsetof(typeof(*entry), __data) - \ + offsetof(typeof(*entry), __rel_loc_##item) - \ + sizeof(u32); \ + __data_offsets->item |= __item_length << 16; \ + __data_size += __item_length; + +#undef __rel_string +#define __rel_string(item, src) __rel_dynamic_array(char, item, \ + strlen((src) ? (const char *)(src) : "(null)") + 1) + +#undef __rel_string_len +#define __rel_string_len(item, src, len) __rel_dynamic_array(char, item, (len) + 1) +/* + * __bitmask_size_in_bytes_raw is the number of bytes needed to hold + * num_possible_cpus(). + */ +#define __bitmask_size_in_bytes_raw(nr_bits) \ + (((nr_bits) + 7) / 8) + +#define __bitmask_size_in_longs(nr_bits) \ + ((__bitmask_size_in_bytes_raw(nr_bits) + \ + ((BITS_PER_LONG / 8) - 1)) / (BITS_PER_LONG / 8)) + +/* + * __bitmask_size_in_bytes is the number of bytes needed to hold + * num_possible_cpus() padded out to the nearest long. This is what + * is saved in the buffer, just to be consistent. + */ +#define __bitmask_size_in_bytes(nr_bits) \ + (__bitmask_size_in_longs(nr_bits) * (BITS_PER_LONG / 8)) + +#undef __bitmask +#define __bitmask(item, nr_bits) __dynamic_array(unsigned long, item, \ + __bitmask_size_in_longs(nr_bits)) + +#undef __rel_bitmask +#define __rel_bitmask(item, nr_bits) __rel_dynamic_array(unsigned long, item, \ + __bitmask_size_in_longs(nr_bits)) diff --git a/include/trace/stages/stage6_defines.h b/include/trace/stages/stage6_defines.h new file mode 100644 index 000000000000..b3a1f26026be --- /dev/null +++ b/include/trace/stages/stage6_defines.h @@ -0,0 +1,86 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +/* Stage 6 definitions for creating trace events */ + +#undef __entry +#define __entry entry + +#undef __field +#define __field(type, item) + +#undef __field_struct +#define __field_struct(type, item) + +#undef __array +#define __array(type, item, len) + +#undef __dynamic_array +#define __dynamic_array(type, item, len) \ + __entry->__data_loc_##item = __data_offsets.item; + +#undef __string +#define __string(item, src) __dynamic_array(char, item, -1) + +#undef __string_len +#define __string_len(item, src, len) __dynamic_array(char, item, -1) + +#undef __assign_str +#define __assign_str(dst, src) \ + strcpy(__get_str(dst), (src) ? (const char *)(src) : "(null)"); + +#undef __assign_str_len +#define __assign_str_len(dst, src, len) \ + do { \ + memcpy(__get_str(dst), (src), (len)); \ + __get_str(dst)[len] = '\0'; \ + } while(0) + +#undef __bitmask +#define __bitmask(item, nr_bits) __dynamic_array(unsigned long, item, -1) + +#undef __get_bitmask +#define __get_bitmask(field) (char *)__get_dynamic_array(field) + +#undef __assign_bitmask +#define __assign_bitmask(dst, src, nr_bits) \ + memcpy(__get_bitmask(dst), (src), __bitmask_size_in_bytes(nr_bits)) + +#undef __rel_dynamic_array +#define __rel_dynamic_array(type, item, len) \ + __entry->__rel_loc_##item = __data_offsets.item; + +#undef __rel_string +#define __rel_string(item, src) __rel_dynamic_array(char, item, -1) + +#undef __rel_string_len +#define __rel_string_len(item, src, len) __rel_dynamic_array(char, item, -1) + +#undef __assign_rel_str +#define __assign_rel_str(dst, src) \ + strcpy(__get_rel_str(dst), (src) ? (const char *)(src) : "(null)"); + +#undef __assign_rel_str_len +#define __assign_rel_str_len(dst, src, len) \ + do { \ + memcpy(__get_rel_str(dst), (src), (len)); \ + __get_rel_str(dst)[len] = '\0'; \ + } while (0) + +#undef __rel_bitmask +#define __rel_bitmask(item, nr_bits) __rel_dynamic_array(unsigned long, item, -1) + +#undef __get_rel_bitmask +#define __get_rel_bitmask(field) (char *)__get_rel_dynamic_array(field) + +#undef __assign_rel_bitmask +#define __assign_rel_bitmask(dst, src, nr_bits) \ + memcpy(__get_rel_bitmask(dst), (src), __bitmask_size_in_bytes(nr_bits)) + +#undef TP_fast_assign +#define TP_fast_assign(args...) args + +#undef __perf_count +#define __perf_count(c) (c) + +#undef __perf_task +#define __perf_task(t) (t) diff --git a/include/trace/stages/stage7_defines.h b/include/trace/stages/stage7_defines.h new file mode 100644 index 000000000000..d65445328f18 --- /dev/null +++ b/include/trace/stages/stage7_defines.h @@ -0,0 +1,34 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +/* Stage 7 definitions for creating trace events */ + +#undef __entry +#define __entry REC + +#undef __print_flags +#undef __print_symbolic +#undef __print_hex +#undef __print_hex_str +#undef __get_dynamic_array +#undef __get_dynamic_array_len +#undef __get_str +#undef __get_bitmask +#undef __get_rel_dynamic_array +#undef __get_rel_dynamic_array_len +#undef __get_rel_str +#undef __get_rel_bitmask +#undef __print_array +#undef __print_hex_dump + +/* + * The below is not executed in the kernel. It is only what is + * displayed in the print format for userspace to parse. + */ +#undef __print_ns_to_secs +#define __print_ns_to_secs(val) (val) / 1000000000UL + +#undef __print_ns_without_secs +#define __print_ns_without_secs(val) (val) % 1000000000UL + +#undef TP_printk +#define TP_printk(fmt, args...) "\"" fmt "\", " __stringify(args) diff --git a/include/trace/trace_events.h b/include/trace/trace_events.h index 3d29919045af..8a8cd66cc6d5 100644 --- a/include/trace/trace_events.h +++ b/include/trace/trace_events.h @@ -24,42 +24,7 @@ #define TRACE_SYSTEM_VAR TRACE_SYSTEM #endif -#define __app__(x, y) str__##x##y -#define __app(x, y) __app__(x, y) - -#define TRACE_SYSTEM_STRING __app(TRACE_SYSTEM_VAR,__trace_system_name) - -#define TRACE_MAKE_SYSTEM_STR() \ - static const char TRACE_SYSTEM_STRING[] = \ - __stringify(TRACE_SYSTEM) - -TRACE_MAKE_SYSTEM_STR(); - -#undef TRACE_DEFINE_ENUM -#define TRACE_DEFINE_ENUM(a) \ - static struct trace_eval_map __used __initdata \ - __##TRACE_SYSTEM##_##a = \ - { \ - .system = TRACE_SYSTEM_STRING, \ - .eval_string = #a, \ - .eval_value = a \ - }; \ - static struct trace_eval_map __used \ - __section("_ftrace_eval_map") \ - *TRACE_SYSTEM##_##a = &__##TRACE_SYSTEM##_##a - -#undef TRACE_DEFINE_SIZEOF -#define TRACE_DEFINE_SIZEOF(a) \ - static struct trace_eval_map __used __initdata \ - __##TRACE_SYSTEM##_##a = \ - { \ - .system = TRACE_SYSTEM_STRING, \ - .eval_string = "sizeof(" #a ")", \ - .eval_value = sizeof(a) \ - }; \ - static struct trace_eval_map __used \ - __section("_ftrace_eval_map") \ - *TRACE_SYSTEM##_##a = &__##TRACE_SYSTEM##_##a +#include "stages/init.h" /* * DECLARE_EVENT_CLASS can be used to add a generic function @@ -80,48 +45,7 @@ TRACE_MAKE_SYSTEM_STR(); PARAMS(print)); \ DEFINE_EVENT(name, name, PARAMS(proto), PARAMS(args)); - -#undef __field -#define __field(type, item) type item; - -#undef __field_ext -#define __field_ext(type, item, filter_type) type item; - -#undef __field_struct -#define __field_struct(type, item) type item; - -#undef __field_struct_ext -#define __field_struct_ext(type, item, filter_type) type item; - -#undef __array -#define __array(type, item, len) type item[len]; - -#undef __dynamic_array -#define __dynamic_array(type, item, len) u32 __data_loc_##item; - -#undef __string -#define __string(item, src) __dynamic_array(char, item, -1) - -#undef __string_len -#define __string_len(item, src, len) __dynamic_array(char, item, -1) - -#undef __bitmask -#define __bitmask(item, nr_bits) __dynamic_array(char, item, -1) - -#undef __rel_dynamic_array -#define __rel_dynamic_array(type, item, len) u32 __rel_loc_##item; - -#undef __rel_string -#define __rel_string(item, src) __rel_dynamic_array(char, item, -1) - -#undef __rel_string_len -#define __rel_string_len(item, src, len) __rel_dynamic_array(char, item, -1) - -#undef __rel_bitmask -#define __rel_bitmask(item, nr_bits) __rel_dynamic_array(char, item, -1) - -#undef TP_STRUCT__entry -#define TP_STRUCT__entry(args...) args +#include "stages/stage1_defines.h" #undef DECLARE_EVENT_CLASS #define DECLARE_EVENT_CLASS(name, proto, args, tstruct, assign, print) \ @@ -185,50 +109,7 @@ TRACE_MAKE_SYSTEM_STR(); * The size of an array is also encoded, in the higher 16 bits of . */ -#undef TRACE_DEFINE_ENUM -#define TRACE_DEFINE_ENUM(a) - -#undef TRACE_DEFINE_SIZEOF -#define TRACE_DEFINE_SIZEOF(a) - -#undef __field -#define __field(type, item) - -#undef __field_ext -#define __field_ext(type, item, filter_type) - -#undef __field_struct -#define __field_struct(type, item) - -#undef __field_struct_ext -#define __field_struct_ext(type, item, filter_type) - -#undef __array -#define __array(type, item, len) - -#undef __dynamic_array -#define __dynamic_array(type, item, len) u32 item; - -#undef __string -#define __string(item, src) __dynamic_array(char, item, -1) - -#undef __bitmask -#define __bitmask(item, nr_bits) __dynamic_array(unsigned long, item, -1) - -#undef __string_len -#define __string_len(item, src, len) __dynamic_array(char, item, -1) - -#undef __rel_dynamic_array -#define __rel_dynamic_array(type, item, len) u32 item; - -#undef __rel_string -#define __rel_string(item, src) __rel_dynamic_array(char, item, -1) - -#undef __rel_string_len -#define __rel_string_len(item, src, len) __rel_dynamic_array(char, item, -1) - -#undef __rel_bitmask -#define __rel_bitmask(item, nr_bits) __rel_dynamic_array(unsigned long, item, -1) +#include "stages/stage2_defines.h" #undef DECLARE_EVENT_CLASS #define DECLARE_EVENT_CLASS(call, proto, args, tstruct, assign, print) \ @@ -300,131 +181,7 @@ TRACE_MAKE_SYSTEM_STR(); * in binary. */ -#undef __entry -#define __entry field - -#undef TP_printk -#define TP_printk(fmt, args...) fmt "\n", args - -#undef __get_dynamic_array -#define __get_dynamic_array(field) \ - ((void *)__entry + (__entry->__data_loc_##field & 0xffff)) - -#undef __get_dynamic_array_len -#define __get_dynamic_array_len(field) \ - ((__entry->__data_loc_##field >> 16) & 0xffff) - -#undef __get_str -#define __get_str(field) ((char *)__get_dynamic_array(field)) - -#undef __get_rel_dynamic_array -#define __get_rel_dynamic_array(field) \ - ((void *)__entry + \ - offsetof(typeof(*__entry), __rel_loc_##field) + \ - sizeof(__entry->__rel_loc_##field) + \ - (__entry->__rel_loc_##field & 0xffff)) - -#undef __get_rel_dynamic_array_len -#define __get_rel_dynamic_array_len(field) \ - ((__entry->__rel_loc_##field >> 16) & 0xffff) - -#undef __get_rel_str -#define __get_rel_str(field) ((char *)__get_rel_dynamic_array(field)) - -#undef __get_bitmask -#define __get_bitmask(field) \ - ({ \ - void *__bitmask = __get_dynamic_array(field); \ - unsigned int __bitmask_size; \ - __bitmask_size = __get_dynamic_array_len(field); \ - trace_print_bitmask_seq(p, __bitmask, __bitmask_size); \ - }) - -#undef __get_rel_bitmask -#define __get_rel_bitmask(field) \ - ({ \ - void *__bitmask = __get_rel_dynamic_array(field); \ - unsigned int __bitmask_size; \ - __bitmask_size = __get_rel_dynamic_array_len(field); \ - trace_print_bitmask_seq(p, __bitmask, __bitmask_size); \ - }) - -#undef __print_flags -#define __print_flags(flag, delim, flag_array...) \ - ({ \ - static const struct trace_print_flags __flags[] = \ - { flag_array, { -1, NULL }}; \ - trace_print_flags_seq(p, delim, flag, __flags); \ - }) - -#undef __print_symbolic -#define __print_symbolic(value, symbol_array...) \ - ({ \ - static const struct trace_print_flags symbols[] = \ - { symbol_array, { -1, NULL }}; \ - trace_print_symbols_seq(p, value, symbols); \ - }) - -#undef __print_flags_u64 -#undef __print_symbolic_u64 -#if BITS_PER_LONG == 32 -#define __print_flags_u64(flag, delim, flag_array...) \ - ({ \ - static const struct trace_print_flags_u64 __flags[] = \ - { flag_array, { -1, NULL } }; \ - trace_print_flags_seq_u64(p, delim, flag, __flags); \ - }) - -#define __print_symbolic_u64(value, symbol_array...) \ - ({ \ - static const struct trace_print_flags_u64 symbols[] = \ - { symbol_array, { -1, NULL } }; \ - trace_print_symbols_seq_u64(p, value, symbols); \ - }) -#else -#define __print_flags_u64(flag, delim, flag_array...) \ - __print_flags(flag, delim, flag_array) - -#define __print_symbolic_u64(value, symbol_array...) \ - __print_symbolic(value, symbol_array) -#endif - -#undef __print_hex -#define __print_hex(buf, buf_len) \ - trace_print_hex_seq(p, buf, buf_len, false) - -#undef __print_hex_str -#define __print_hex_str(buf, buf_len) \ - trace_print_hex_seq(p, buf, buf_len, true) - -#undef __print_array -#define __print_array(array, count, el_size) \ - ({ \ - BUILD_BUG_ON(el_size != 1 && el_size != 2 && \ - el_size != 4 && el_size != 8); \ - trace_print_array_seq(p, array, count, el_size); \ - }) - -#undef __print_hex_dump -#define __print_hex_dump(prefix_str, prefix_type, \ - rowsize, groupsize, buf, len, ascii) \ - trace_print_hex_dump_seq(p, prefix_str, prefix_type, \ - rowsize, groupsize, buf, len, ascii) - -#undef __print_ns_to_secs -#define __print_ns_to_secs(value) \ - ({ \ - u64 ____val = (u64)(value); \ - do_div(____val, NSEC_PER_SEC); \ - ____val; \ - }) - -#undef __print_ns_without_secs -#define __print_ns_without_secs(value) \ - ({ \ - u64 ____val = (u64)(value); \ - (u32) do_div(____val, NSEC_PER_SEC); \ - }) +#include "stages/stage3_defines.h" #undef DECLARE_EVENT_CLASS #define DECLARE_EVENT_CLASS(call, proto, args, tstruct, assign, print) \ @@ -479,59 +236,7 @@ static struct trace_event_functions trace_event_type_funcs_##call = { \ #include TRACE_INCLUDE(TRACE_INCLUDE_FILE) -#undef __field_ext -#define __field_ext(_type, _item, _filter_type) { \ - .type = #_type, .name = #_item, \ - .size = sizeof(_type), .align = __alignof__(_type), \ - .is_signed = is_signed_type(_type), .filter_type = _filter_type }, - -#undef __field_struct_ext -#define __field_struct_ext(_type, _item, _filter_type) { \ - .type = #_type, .name = #_item, \ - .size = sizeof(_type), .align = __alignof__(_type), \ - 0, .filter_type = _filter_type }, - -#undef __field -#define __field(type, item) __field_ext(type, item, FILTER_OTHER) - -#undef __field_struct -#define __field_struct(type, item) __field_struct_ext(type, item, FILTER_OTHER) - -#undef __array -#define __array(_type, _item, _len) { \ - .type = #_type"["__stringify(_len)"]", .name = #_item, \ - .size = sizeof(_type[_len]), .align = __alignof__(_type), \ - .is_signed = is_signed_type(_type), .filter_type = FILTER_OTHER }, - -#undef __dynamic_array -#define __dynamic_array(_type, _item, _len) { \ - .type = "__data_loc " #_type "[]", .name = #_item, \ - .size = 4, .align = 4, \ - .is_signed = is_signed_type(_type), .filter_type = FILTER_OTHER }, - -#undef __string -#define __string(item, src) __dynamic_array(char, item, -1) - -#undef __string_len -#define __string_len(item, src, len) __dynamic_array(char, item, -1) - -#undef __bitmask -#define __bitmask(item, nr_bits) __dynamic_array(unsigned long, item, -1) - -#undef __rel_dynamic_array -#define __rel_dynamic_array(_type, _item, _len) { \ - .type = "__rel_loc " #_type "[]", .name = #_item, \ - .size = 4, .align = 4, \ - .is_signed = is_signed_type(_type), .filter_type = FILTER_OTHER }, - -#undef __rel_string -#define __rel_string(item, src) __rel_dynamic_array(char, item, -1) - -#undef __rel_string_len -#define __rel_string_len(item, src, len) __rel_dynamic_array(char, item, -1) - -#undef __rel_bitmask -#define __rel_bitmask(item, nr_bits) __rel_dynamic_array(unsigned long, item, -1) +#include "stages/stage4_defines.h" #undef DECLARE_EVENT_CLASS #define DECLARE_EVENT_CLASS(call, proto, args, tstruct, func, print) \ @@ -544,85 +249,7 @@ static struct trace_event_fields trace_event_fields_##call[] = { \ #include TRACE_INCLUDE(TRACE_INCLUDE_FILE) -/* - * remember the offset of each array from the beginning of the event. - */ - -#undef __entry -#define __entry entry - -#undef __field -#define __field(type, item) - -#undef __field_ext -#define __field_ext(type, item, filter_type) - -#undef __field_struct -#define __field_struct(type, item) - -#undef __field_struct_ext -#define __field_struct_ext(type, item, filter_type) - -#undef __array -#define __array(type, item, len) - -#undef __dynamic_array -#define __dynamic_array(type, item, len) \ - __item_length = (len) * sizeof(type); \ - __data_offsets->item = __data_size + \ - offsetof(typeof(*entry), __data); \ - __data_offsets->item |= __item_length << 16; \ - __data_size += __item_length; - -#undef __string -#define __string(item, src) __dynamic_array(char, item, \ - strlen((src) ? (const char *)(src) : "(null)") + 1) - -#undef __string_len -#define __string_len(item, src, len) __dynamic_array(char, item, (len) + 1) - -#undef __rel_dynamic_array -#define __rel_dynamic_array(type, item, len) \ - __item_length = (len) * sizeof(type); \ - __data_offsets->item = __data_size + \ - offsetof(typeof(*entry), __data) - \ - offsetof(typeof(*entry), __rel_loc_##item) - \ - sizeof(u32); \ - __data_offsets->item |= __item_length << 16; \ - __data_size += __item_length; - -#undef __rel_string -#define __rel_string(item, src) __rel_dynamic_array(char, item, \ - strlen((src) ? (const char *)(src) : "(null)") + 1) - -#undef __rel_string_len -#define __rel_string_len(item, src, len) __rel_dynamic_array(char, item, (len) + 1) -/* - * __bitmask_size_in_bytes_raw is the number of bytes needed to hold - * num_possible_cpus(). - */ -#define __bitmask_size_in_bytes_raw(nr_bits) \ - (((nr_bits) + 7) / 8) - -#define __bitmask_size_in_longs(nr_bits) \ - ((__bitmask_size_in_bytes_raw(nr_bits) + \ - ((BITS_PER_LONG / 8) - 1)) / (BITS_PER_LONG / 8)) - -/* - * __bitmask_size_in_bytes is the number of bytes needed to hold - * num_possible_cpus() padded out to the nearest long. This is what - * is saved in the buffer, just to be consistent. - */ -#define __bitmask_size_in_bytes(nr_bits) \ - (__bitmask_size_in_longs(nr_bits) * (BITS_PER_LONG / 8)) - -#undef __bitmask -#define __bitmask(item, nr_bits) __dynamic_array(unsigned long, item, \ - __bitmask_size_in_longs(nr_bits)) - -#undef __rel_bitmask -#define __rel_bitmask(item, nr_bits) __rel_dynamic_array(unsigned long, item, \ - __bitmask_size_in_longs(nr_bits)) +#include "stages/stage5_defines.h" #undef DECLARE_EVENT_CLASS #define DECLARE_EVENT_CLASS(call, proto, args, tstruct, assign, print) \ @@ -745,88 +372,7 @@ static inline notrace int trace_event_get_offsets_##call( \ #define _TRACE_PERF_INIT(call) #endif /* CONFIG_PERF_EVENTS */ -#undef __entry -#define __entry entry - -#undef __field -#define __field(type, item) - -#undef __field_struct -#define __field_struct(type, item) - -#undef __array -#define __array(type, item, len) - -#undef __dynamic_array -#define __dynamic_array(type, item, len) \ - __entry->__data_loc_##item = __data_offsets.item; - -#undef __string -#define __string(item, src) __dynamic_array(char, item, -1) - -#undef __string_len -#define __string_len(item, src, len) __dynamic_array(char, item, -1) - -#undef __assign_str -#define __assign_str(dst, src) \ - strcpy(__get_str(dst), (src) ? (const char *)(src) : "(null)"); - -#undef __assign_str_len -#define __assign_str_len(dst, src, len) \ - do { \ - memcpy(__get_str(dst), (src), (len)); \ - __get_str(dst)[len] = '\0'; \ - } while(0) - -#undef __bitmask -#define __bitmask(item, nr_bits) __dynamic_array(unsigned long, item, -1) - -#undef __get_bitmask -#define __get_bitmask(field) (char *)__get_dynamic_array(field) - -#undef __assign_bitmask -#define __assign_bitmask(dst, src, nr_bits) \ - memcpy(__get_bitmask(dst), (src), __bitmask_size_in_bytes(nr_bits)) - -#undef __rel_dynamic_array -#define __rel_dynamic_array(type, item, len) \ - __entry->__rel_loc_##item = __data_offsets.item; - -#undef __rel_string -#define __rel_string(item, src) __rel_dynamic_array(char, item, -1) - -#undef __rel_string_len -#define __rel_string_len(item, src, len) __rel_dynamic_array(char, item, -1) - -#undef __assign_rel_str -#define __assign_rel_str(dst, src) \ - strcpy(__get_rel_str(dst), (src) ? (const char *)(src) : "(null)"); - -#undef __assign_rel_str_len -#define __assign_rel_str_len(dst, src, len) \ - do { \ - memcpy(__get_rel_str(dst), (src), (len)); \ - __get_rel_str(dst)[len] = '\0'; \ - } while (0) - -#undef __rel_bitmask -#define __rel_bitmask(item, nr_bits) __rel_dynamic_array(unsigned long, item, -1) - -#undef __get_rel_bitmask -#define __get_rel_bitmask(field) (char *)__get_rel_dynamic_array(field) - -#undef __assign_rel_bitmask -#define __assign_rel_bitmask(dst, src, nr_bits) \ - memcpy(__get_rel_bitmask(dst), (src), __bitmask_size_in_bytes(nr_bits)) - -#undef TP_fast_assign -#define TP_fast_assign(args...) args - -#undef __perf_count -#define __perf_count(c) (c) - -#undef __perf_task -#define __perf_task(t) (t) +#include "stages/stage6_defines.h" #undef DECLARE_EVENT_CLASS #define DECLARE_EVENT_CLASS(call, proto, args, tstruct, assign, print) \ @@ -872,36 +418,7 @@ static inline void ftrace_test_probe_##call(void) \ #include TRACE_INCLUDE(TRACE_INCLUDE_FILE) -#undef __entry -#define __entry REC - -#undef __print_flags -#undef __print_symbolic -#undef __print_hex -#undef __print_hex_str -#undef __get_dynamic_array -#undef __get_dynamic_array_len -#undef __get_str -#undef __get_bitmask -#undef __get_rel_dynamic_array -#undef __get_rel_dynamic_array_len -#undef __get_rel_str -#undef __get_rel_bitmask -#undef __print_array -#undef __print_hex_dump - -/* - * The below is not executed in the kernel. It is only what is - * displayed in the print format for userspace to parse. - */ -#undef __print_ns_to_secs -#define __print_ns_to_secs(val) (val) / 1000000000UL - -#undef __print_ns_without_secs -#define __print_ns_without_secs(val) (val) % 1000000000UL - -#undef TP_printk -#define TP_printk(fmt, args...) "\"" fmt "\", " __stringify(args) +#include "stages/stage7_defines.h" #undef DECLARE_EVENT_CLASS #define DECLARE_EVENT_CLASS(call, proto, args, tstruct, assign, print) \ From 3a73333fb370f7b65de9d94c53df503642bda789 Mon Sep 17 00:00:00 2001 From: "Steven Rostedt (Google)" Date: Thu, 3 Mar 2022 17:05:34 -0500 Subject: [PATCH 44/67] tracing: Add TRACE_CUSTOM_EVENT() macro To make it really easy to add custom events from modules, add a TRACE_CUSTOM_EVENT() macro that acts just like the TRACE_EVENT() macro, but creates a custom event to an already existing tracepoint. The trace_custom_sched.[ch] has been updated to use this new macro to show how simple it is. Link: https://lkml.kernel.org/r/20220303220625.738622494@goodmis.org Cc: Ingo Molnar Cc: Andrew Morton Cc: Joel Fernandes Cc: Peter Zijlstra Cc: Masami Hiramatsu Cc: Tom Zanussi Signed-off-by: Steven Rostedt (Google) --- include/linux/trace_events.h | 24 +- include/trace/define_custom_trace.h | 77 +++++++ include/trace/trace_custom_events.h | 221 ++++++++++++++++++ samples/trace_events/Makefile | 2 +- samples/trace_events/trace_custom_sched.c | 259 ++-------------------- samples/trace_events/trace_custom_sched.h | 95 ++++++++ 6 files changed, 441 insertions(+), 237 deletions(-) create mode 100644 include/trace/define_custom_trace.h create mode 100644 include/trace/trace_custom_events.h create mode 100644 samples/trace_events/trace_custom_sched.h diff --git a/include/linux/trace_events.h b/include/linux/trace_events.h index 70c069aef02c..9b09fd633d48 100644 --- a/include/linux/trace_events.h +++ b/include/linux/trace_events.h @@ -315,6 +315,7 @@ enum { TRACE_EVENT_FL_KPROBE_BIT, TRACE_EVENT_FL_UPROBE_BIT, TRACE_EVENT_FL_EPROBE_BIT, + TRACE_EVENT_FL_CUSTOM_BIT, }; /* @@ -328,6 +329,9 @@ enum { * KPROBE - Event is a kprobe * UPROBE - Event is a uprobe * EPROBE - Event is an event probe + * CUSTOM - Event is a custom event (to be attached to an exsiting tracepoint) + * This is set when the custom event has not been attached + * to a tracepoint yet, then it is cleared when it is. */ enum { TRACE_EVENT_FL_FILTERED = (1 << TRACE_EVENT_FL_FILTERED_BIT), @@ -339,6 +343,7 @@ enum { TRACE_EVENT_FL_KPROBE = (1 << TRACE_EVENT_FL_KPROBE_BIT), TRACE_EVENT_FL_UPROBE = (1 << TRACE_EVENT_FL_UPROBE_BIT), TRACE_EVENT_FL_EPROBE = (1 << TRACE_EVENT_FL_EPROBE_BIT), + TRACE_EVENT_FL_CUSTOM = (1 << TRACE_EVENT_FL_CUSTOM_BIT), }; #define TRACE_EVENT_FL_UKPROBE (TRACE_EVENT_FL_KPROBE | TRACE_EVENT_FL_UPROBE) @@ -440,7 +445,9 @@ static inline bool bpf_prog_array_valid(struct trace_event_call *call) static inline const char * trace_event_name(struct trace_event_call *call) { - if (call->flags & TRACE_EVENT_FL_TRACEPOINT) + if (call->flags & TRACE_EVENT_FL_CUSTOM) + return call->name; + else if (call->flags & TRACE_EVENT_FL_TRACEPOINT) return call->tp ? call->tp->name : NULL; else return call->name; @@ -901,3 +908,18 @@ perf_trace_buf_submit(void *raw_data, int size, int rctx, u16 type, #endif #endif /* _LINUX_TRACE_EVENT_H */ + +/* + * Note: we keep the TRACE_CUSTOM_EVENT outside the include file ifdef protection. + * This is due to the way trace custom events work. If a file includes two + * trace event headers under one "CREATE_CUSTOM_TRACE_EVENTS" the first include + * will override the TRACE_CUSTOM_EVENT and break the second include. + */ + +#ifndef TRACE_CUSTOM_EVENT + +#define DECLARE_CUSTOM_EVENT_CLASS(name, proto, args, tstruct, assign, print) +#define DEFINE_CUSTOM_EVENT(template, name, proto, args) +#define TRACE_CUSTOM_EVENT(name, proto, args, struct, assign, print) + +#endif /* ifdef TRACE_CUSTOM_EVENT (see note above) */ diff --git a/include/trace/define_custom_trace.h b/include/trace/define_custom_trace.h new file mode 100644 index 000000000000..5827a4c92c74 --- /dev/null +++ b/include/trace/define_custom_trace.h @@ -0,0 +1,77 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Trace files that want to automate creation of all tracepoints defined + * in their file should include this file. The following are macros that the + * trace file may define: + * + * TRACE_SYSTEM defines the system the tracepoint is for + * + * TRACE_INCLUDE_FILE if the file name is something other than TRACE_SYSTEM.h + * This macro may be defined to tell define_trace.h what file to include. + * Note, leave off the ".h". + * + * TRACE_INCLUDE_PATH if the path is something other than core kernel include/trace + * then this macro can define the path to use. Note, the path is relative to + * define_trace.h, not the file including it. Full path names for out of tree + * modules must be used. + */ + +#ifdef CREATE_CUSTOM_TRACE_EVENTS + +/* Prevent recursion */ +#undef CREATE_CUSTOM_TRACE_EVENTS + +#include + +#undef TRACE_CUSTOM_EVENT +#define TRACE_CUSTOM_EVENT(name, proto, args, tstruct, assign, print) + +#undef DEFINE_CUSTOM_EVENT +#define DEFINE_CUSTOM_EVENT(template, name, proto, args) + +#undef TRACE_INCLUDE +#undef __TRACE_INCLUDE + +#ifndef TRACE_INCLUDE_FILE +# define TRACE_INCLUDE_FILE TRACE_SYSTEM +# define UNDEF_TRACE_INCLUDE_FILE +#endif + +#ifndef TRACE_INCLUDE_PATH +# define __TRACE_INCLUDE(system) +# define UNDEF_TRACE_INCLUDE_PATH +#else +# define __TRACE_INCLUDE(system) __stringify(TRACE_INCLUDE_PATH/system.h) +#endif + +# define TRACE_INCLUDE(system) __TRACE_INCLUDE(system) + +/* Let the trace headers be reread */ +#define TRACE_CUSTOM_MULTI_READ + +#include TRACE_INCLUDE(TRACE_INCLUDE_FILE) + +#ifdef TRACEPOINTS_ENABLED +#include +#endif + +#undef TRACE_CUSTOM_EVENT +#undef DECLARE_CUSTOM_EVENT_CLASS +#undef DEFINE_CUSTOM_EVENT +#undef TRACE_CUSTOM_MULTI_READ + +/* Only undef what we defined in this file */ +#ifdef UNDEF_TRACE_INCLUDE_FILE +# undef TRACE_INCLUDE_FILE +# undef UNDEF_TRACE_INCLUDE_FILE +#endif + +#ifdef UNDEF_TRACE_INCLUDE_PATH +# undef TRACE_INCLUDE_PATH +# undef UNDEF_TRACE_INCLUDE_PATH +#endif + +/* We may be processing more files */ +#define CREATE_CUSTOM_TRACE_POINTS + +#endif /* CREATE_CUSTOM_TRACE_POINTS */ diff --git a/include/trace/trace_custom_events.h b/include/trace/trace_custom_events.h new file mode 100644 index 000000000000..b567c7202339 --- /dev/null +++ b/include/trace/trace_custom_events.h @@ -0,0 +1,221 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * This is similar to the trace_events.h file, but is to only + * create custom trace events to be attached to existing tracepoints. + * Where as the TRACE_EVENT() macro (from trace_events.h) will create + * both the trace event and the tracepoint it will attach the event to, + * TRACE_CUSTOM_EVENT() is to create only a custom version of an existing + * trace event (created by TRACE_EVENT() or DEFINE_EVENT()), and will + * be placed in the "custom" system. + */ + +#include + +/* All custom events are placed in the custom group */ +#undef TRACE_SYSTEM +#define TRACE_SYSTEM custom + +#ifndef TRACE_SYSTEM_VAR +#define TRACE_SYSTEM_VAR TRACE_SYSTEM +#endif + +/* The init stage creates the system string and enum mappings */ + +#include "stages/init.h" + +#undef TRACE_CUSTOM_EVENT +#define TRACE_CUSTOM_EVENT(name, proto, args, tstruct, assign, print) \ + DECLARE_CUSTOM_EVENT_CLASS(name, \ + PARAMS(proto), \ + PARAMS(args), \ + PARAMS(tstruct), \ + PARAMS(assign), \ + PARAMS(print)); \ + DEFINE_CUSTOM_EVENT(name, name, PARAMS(proto), PARAMS(args)); + +/* Stage 1 creates the structure of the recorded event layout */ + +#include "stages/stage1_defines.h" + +#undef DECLARE_CUSTOM_EVENT_CLASS +#define DECLARE_CUSTOM_EVENT_CLASS(name, proto, args, tstruct, assign, print) \ + struct trace_custom_event_raw_##name { \ + struct trace_entry ent; \ + tstruct \ + char __data[]; \ + }; \ + \ + static struct trace_event_class custom_event_class_##name; + +#undef DEFINE_CUSTOM_EVENT +#define DEFINE_CUSTOM_EVENT(template, name, proto, args) \ + static struct trace_event_call __used \ + __attribute__((__aligned__(4))) custom_event_##name + +#include TRACE_INCLUDE(TRACE_INCLUDE_FILE) + +/* Stage 2 creates the custom class */ + +#include "stages/stage2_defines.h" + +#undef DECLARE_CUSTOM_EVENT_CLASS +#define DECLARE_CUSTOM_EVENT_CLASS(call, proto, args, tstruct, assign, print) \ + struct trace_custom_event_data_offsets_##call { \ + tstruct; \ + }; + +#undef DEFINE_CUSTOM_EVENT +#define DEFINE_CUSTOM_EVENT(template, name, proto, args) + +#include TRACE_INCLUDE(TRACE_INCLUDE_FILE) + +/* Stage 3 create the way to print the custom event */ + +#include "stages/stage3_defines.h" + +#undef DECLARE_CUSTOM_EVENT_CLASS +#define DECLARE_CUSTOM_EVENT_CLASS(call, proto, args, tstruct, assign, print) \ +static notrace enum print_line_t \ +trace_custom_raw_output_##call(struct trace_iterator *iter, int flags, \ + struct trace_event *trace_event) \ +{ \ + struct trace_seq *s = &iter->seq; \ + struct trace_seq __maybe_unused *p = &iter->tmp_seq; \ + struct trace_custom_event_raw_##call *field; \ + int ret; \ + \ + field = (typeof(field))iter->ent; \ + \ + ret = trace_raw_output_prep(iter, trace_event); \ + if (ret != TRACE_TYPE_HANDLED) \ + return ret; \ + \ + trace_event_printf(iter, print); \ + \ + return trace_handle_return(s); \ +} \ +static struct trace_event_functions trace_custom_event_type_funcs_##call = { \ + .trace = trace_custom_raw_output_##call, \ +}; + +#include TRACE_INCLUDE(TRACE_INCLUDE_FILE) + +/* Stage 4 creates the offset layout for the fields */ + +#include "stages/stage4_defines.h" + +#undef DECLARE_CUSTOM_EVENT_CLASS +#define DECLARE_CUSTOM_EVENT_CLASS(call, proto, args, tstruct, func, print) \ +static struct trace_event_fields trace_custom_event_fields_##call[] = { \ + tstruct \ + {} }; + +#include TRACE_INCLUDE(TRACE_INCLUDE_FILE) + +/* Stage 5 creates the helper function for dynamic fields */ + +#include "stages/stage5_defines.h" + +#undef DECLARE_CUSTOM_EVENT_CLASS +#define DECLARE_CUSTOM_EVENT_CLASS(call, proto, args, tstruct, assign, print) \ +static inline notrace int trace_custom_event_get_offsets_##call( \ + struct trace_custom_event_data_offsets_##call *__data_offsets, proto) \ +{ \ + int __data_size = 0; \ + int __maybe_unused __item_length; \ + struct trace_custom_event_raw_##call __maybe_unused *entry; \ + \ + tstruct; \ + \ + return __data_size; \ +} + +#include TRACE_INCLUDE(TRACE_INCLUDE_FILE) + +/* Stage 6 creates the probe function that records the event */ + +#include "stages/stage6_defines.h" + +#undef DECLARE_CUSTOM_EVENT_CLASS +#define DECLARE_CUSTOM_EVENT_CLASS(call, proto, args, tstruct, assign, print) \ + \ +static notrace void \ +trace_custom_event_raw_event_##call(void *__data, proto) \ +{ \ + struct trace_event_file *trace_file = __data; \ + struct trace_custom_event_data_offsets_##call __maybe_unused __data_offsets; \ + struct trace_event_buffer fbuffer; \ + struct trace_custom_event_raw_##call *entry; \ + int __data_size; \ + \ + if (trace_trigger_soft_disabled(trace_file)) \ + return; \ + \ + __data_size = trace_custom_event_get_offsets_##call(&__data_offsets, args); \ + \ + entry = trace_event_buffer_reserve(&fbuffer, trace_file, \ + sizeof(*entry) + __data_size); \ + \ + if (!entry) \ + return; \ + \ + tstruct \ + \ + { assign; } \ + \ + trace_event_buffer_commit(&fbuffer); \ +} +/* + * The ftrace_test_custom_probe is compiled out, it is only here as a build time check + * to make sure that if the tracepoint handling changes, the ftrace probe will + * fail to compile unless it too is updated. + */ + +#undef DEFINE_CUSTOM_EVENT +#define DEFINE_CUSTOM_EVENT(template, call, proto, args) \ +static inline void ftrace_test_custom_probe_##call(void) \ +{ \ + check_trace_callback_type_##call(trace_custom_event_raw_event_##template); \ +} + +#include TRACE_INCLUDE(TRACE_INCLUDE_FILE) + +/* Stage 7 creates the actual class and event structure for the custom event */ + +#include "stages/stage7_defines.h" + +#undef DECLARE_CUSTOM_EVENT_CLASS +#define DECLARE_CUSTOM_EVENT_CLASS(call, proto, args, tstruct, assign, print) \ +static char custom_print_fmt_##call[] = print; \ +static struct trace_event_class __used __refdata custom_event_class_##call = { \ + .system = TRACE_SYSTEM_STRING, \ + .fields_array = trace_custom_event_fields_##call, \ + .fields = LIST_HEAD_INIT(custom_event_class_##call.fields),\ + .raw_init = trace_event_raw_init, \ + .probe = trace_custom_event_raw_event_##call, \ + .reg = trace_event_reg, \ +}; + +#undef DEFINE_CUSTOM_EVENT +#define DEFINE_CUSTOM_EVENT(template, call, proto, args) \ + \ +static struct trace_event_call __used custom_event_##call = { \ + .name = #call, \ + .class = &custom_event_class_##template, \ + .event.funcs = &trace_custom_event_type_funcs_##template, \ + .print_fmt = custom_print_fmt_##template, \ + .flags = TRACE_EVENT_FL_CUSTOM, \ +}; \ +static inline int trace_custom_event_##call##_update(struct tracepoint *tp) \ +{ \ + if (tp->name && strcmp(tp->name, #call) == 0) { \ + custom_event_##call.tp = tp; \ + custom_event_##call.flags = TRACE_EVENT_FL_TRACEPOINT; \ + return 1; \ + } \ + return 0; \ +} \ +static struct trace_event_call __used \ +__section("_ftrace_events") *__custom_event_##call = &custom_event_##call + +#include TRACE_INCLUDE(TRACE_INCLUDE_FILE) diff --git a/samples/trace_events/Makefile b/samples/trace_events/Makefile index e98afc447fe1..b3808bb4cf8b 100644 --- a/samples/trace_events/Makefile +++ b/samples/trace_events/Makefile @@ -11,7 +11,7 @@ # Here trace-events-sample.c does the CREATE_TRACE_POINTS. # CFLAGS_trace-events-sample.o := -I$(src) +CFLAGS_trace_custom_sched.o := -I$(src) obj-$(CONFIG_SAMPLE_TRACE_EVENTS) += trace-events-sample.o - obj-$(CONFIG_SAMPLE_TRACE_CUSTOM_EVENTS) += trace_custom_sched.o diff --git a/samples/trace_events/trace_custom_sched.c b/samples/trace_events/trace_custom_sched.c index 70a12c32ff99..b99d9ab7db85 100644 --- a/samples/trace_events/trace_custom_sched.c +++ b/samples/trace_events/trace_custom_sched.c @@ -11,256 +11,45 @@ #include #include #include + +/* + * Must include the event header that the custom event will attach to, + * from the C file, and not in the custom header file. + */ #include -#define THIS_SYSTEM "custom_sched" +/* Declare CREATE_CUSTOM_TRACE_EVENTS before including custom header */ +#define CREATE_CUSTOM_TRACE_EVENTS -#define SCHED_PRINT_FMT \ - C("prev_prio=%d next_pid=%d next_prio=%d", REC->prev_prio, REC->next_pid, \ - REC->next_prio) - -#define SCHED_WAKING_FMT \ - C("pid=%d prio=%d", REC->pid, REC->prio) - -#undef C -#define C(a, b...) a, b - -static struct trace_event_fields sched_switch_fields[] = { - { - .type = "unsigned short", - .name = "prev_prio", - .size = sizeof(short), - .align = __alignof__(short), - .is_signed = 0, - .filter_type = FILTER_OTHER, - }, - { - .type = "unsigned short", - .name = "next_prio", - .size = sizeof(short), - .align = __alignof__(short), - .is_signed = 0, - .filter_type = FILTER_OTHER, - }, - { - .type = "unsigned int", - .name = "next_prio", - .size = sizeof(int), - .align = __alignof__(int), - .is_signed = 0, - .filter_type = FILTER_OTHER, - }, - {} -}; - -struct sched_event { - struct trace_entry ent; - unsigned short prev_prio; - unsigned short next_prio; - unsigned int next_pid; -}; - -static struct trace_event_fields sched_waking_fields[] = { - { - .type = "unsigned int", - .name = "pid", - .size = sizeof(int), - .align = __alignof__(int), - .is_signed = 0, - .filter_type = FILTER_OTHER, - }, - { - .type = "unsigned short", - .name = "prio", - .size = sizeof(short), - .align = __alignof__(short), - .is_signed = 0, - .filter_type = FILTER_OTHER, - }, - {} -}; - -struct wake_event { - struct trace_entry ent; - unsigned int pid; - unsigned short prio; -}; - -static void sched_switch_probe(void *data, bool preempt, struct task_struct *prev, - struct task_struct *next) -{ - struct trace_event_file *trace_file = data; - struct trace_event_buffer fbuffer; - struct sched_event *entry; - - if (trace_trigger_soft_disabled(trace_file)) - return; - - entry = trace_event_buffer_reserve(&fbuffer, trace_file, - sizeof(*entry)); - - if (!entry) - return; - - entry->prev_prio = prev->prio; - entry->next_prio = next->prio; - entry->next_pid = next->pid; - - trace_event_buffer_commit(&fbuffer); -} - -static struct trace_event_class sched_switch_class = { - .system = THIS_SYSTEM, - .reg = trace_event_reg, - .fields_array = sched_switch_fields, - .fields = LIST_HEAD_INIT(sched_switch_class.fields), - .probe = sched_switch_probe, -}; - -static void sched_waking_probe(void *data, struct task_struct *t) -{ - struct trace_event_file *trace_file = data; - struct trace_event_buffer fbuffer; - struct wake_event *entry; - - if (trace_trigger_soft_disabled(trace_file)) - return; - - entry = trace_event_buffer_reserve(&fbuffer, trace_file, - sizeof(*entry)); - - if (!entry) - return; - - entry->prio = t->prio; - entry->pid = t->pid; - - trace_event_buffer_commit(&fbuffer); -} - -static struct trace_event_class sched_waking_class = { - .system = THIS_SYSTEM, - .reg = trace_event_reg, - .fields_array = sched_waking_fields, - .fields = LIST_HEAD_INIT(sched_waking_class.fields), - .probe = sched_waking_probe, -}; - -static enum print_line_t sched_switch_output(struct trace_iterator *iter, int flags, - struct trace_event *trace_event) -{ - struct trace_seq *s = &iter->seq; - struct sched_event *REC = (struct sched_event *)iter->ent; - int ret; - - ret = trace_raw_output_prep(iter, trace_event); - if (ret != TRACE_TYPE_HANDLED) - return ret; - - trace_seq_printf(s, SCHED_PRINT_FMT); - trace_seq_putc(s, '\n'); - - return trace_handle_return(s); -} - -static struct trace_event_functions sched_switch_funcs = { - .trace = sched_switch_output, -}; - -static enum print_line_t sched_waking_output(struct trace_iterator *iter, int flags, - struct trace_event *trace_event) -{ - struct trace_seq *s = &iter->seq; - struct wake_event *REC = (struct wake_event *)iter->ent; - int ret; - - ret = trace_raw_output_prep(iter, trace_event); - if (ret != TRACE_TYPE_HANDLED) - return ret; - - trace_seq_printf(s, SCHED_WAKING_FMT); - trace_seq_putc(s, '\n'); - - return trace_handle_return(s); -} - -static struct trace_event_functions sched_waking_funcs = { - .trace = sched_waking_output, -}; - -#undef C -#define C(a, b...) #a "," __stringify(b) - -static struct trace_event_call sched_switch_call = { - .class = &sched_switch_class, - .event = { - .funcs = &sched_switch_funcs, - }, - .print_fmt = SCHED_PRINT_FMT, - .module = THIS_MODULE, - .flags = TRACE_EVENT_FL_TRACEPOINT, -}; - -static struct trace_event_call sched_waking_call = { - .class = &sched_waking_class, - .event = { - .funcs = &sched_waking_funcs, - }, - .print_fmt = SCHED_WAKING_FMT, - .module = THIS_MODULE, - .flags = TRACE_EVENT_FL_TRACEPOINT, -}; +#include "trace_custom_sched.h" +/* + * As the trace events are not exported to modules, the use of + * for_each_kernel_tracepoint() is needed to find the trace event + * to attach to. The fct() function below, is a callback that + * will be called for every event. + * + * Helper functions are created by the TRACE_CUSTOM_EVENT() macro + * update the event. Those are of the form: + * + * trace_custom_event__update() + * + * Where is the event to attach. + */ static void fct(struct tracepoint *tp, void *priv) { - if (tp->name && strcmp(tp->name, "sched_switch") == 0) - sched_switch_call.tp = tp; - else if (tp->name && strcmp(tp->name, "sched_waking") == 0) - sched_waking_call.tp = tp; -} - -static int add_event(struct trace_event_call *call) -{ - int ret; - - ret = register_trace_event(&call->event); - if (WARN_ON(!ret)) - return -ENODEV; - - ret = trace_add_event_call(call); - if (WARN_ON(ret)) - unregister_trace_event(&call->event); - - return ret; + trace_custom_event_sched_switch_update(tp); + trace_custom_event_sched_waking_update(tp); } static int __init trace_sched_init(void) { - int ret; - - check_trace_callback_type_sched_switch(sched_switch_probe); - check_trace_callback_type_sched_waking(sched_waking_probe); - for_each_kernel_tracepoint(fct, NULL); - - ret = add_event(&sched_switch_call); - if (ret) - return ret; - - ret = add_event(&sched_waking_call); - if (ret) - trace_remove_event_call(&sched_switch_call); - - return ret; + return 0; } static void __exit trace_sched_exit(void) { - trace_set_clr_event(THIS_SYSTEM, "sched_switch", 0); - trace_set_clr_event(THIS_SYSTEM, "sched_waking", 0); - - trace_remove_event_call(&sched_switch_call); - trace_remove_event_call(&sched_waking_call); } module_init(trace_sched_init); diff --git a/samples/trace_events/trace_custom_sched.h b/samples/trace_events/trace_custom_sched.h new file mode 100644 index 000000000000..a3d14de6a2e5 --- /dev/null +++ b/samples/trace_events/trace_custom_sched.h @@ -0,0 +1,95 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +/* + * Like the headers that use TRACE_EVENT(), the TRACE_CUSTOM_EVENT() + * needs a header that allows for multiple inclusions. + * + * Test for a unique name (here we have _TRACE_CUSTOM_SCHED_H), + * also allowing to continue if TRACE_CUSTOM_MULTI_READ is defined. + */ +#if !defined(_TRACE_CUSTOM_SCHED_H) || defined(TRACE_CUSTOM_MULTI_READ) +#define _TRACE_CUSTOM_SCHED_H + +/* Include linux/trace_events.h for initial defines of TRACE_CUSTOM_EVENT() */ +#include + +/* + * TRACE_CUSTOM_EVENT() is just like TRACE_EVENT(). The first parameter + * is the event name of an existing event where the TRACE_EVENT has been included + * in the C file before including this file. + */ +TRACE_CUSTOM_EVENT(sched_switch, + + /* + * The TP_PROTO() and TP_ARGS must match the trace event + * that the custom event is using. + */ + TP_PROTO(bool preempt, + struct task_struct *prev, + struct task_struct *next), + + TP_ARGS(preempt, prev, next), + + /* + * The next fields are where the customization happens. + * The TP_STRUCT__entry() defines what will be recorded + * in the ring buffer when the custom event triggers. + * + * The rest is just like the TRACE_EVENT() macro except that + * it uses the custom entry. + */ + TP_STRUCT__entry( + __field( unsigned short, prev_prio ) + __field( unsigned short, next_prio ) + __field( pid_t, next_pid ) + ), + + TP_fast_assign( + __entry->prev_prio = prev->prio; + __entry->next_pid = next->pid; + __entry->next_prio = next->prio; + ), + + TP_printk("prev_prio=%d next_pid=%d next_prio=%d", + __entry->prev_prio, __entry->next_pid, __entry->next_prio) +) + + +TRACE_CUSTOM_EVENT(sched_waking, + + TP_PROTO(struct task_struct *p), + + TP_ARGS(p), + + TP_STRUCT__entry( + __field( pid_t, pid ) + __field( unsigned short, prio ) + ), + + TP_fast_assign( + __entry->pid = p->pid; + __entry->prio = p->prio; + ), + + TP_printk("pid=%d prio=%d", __entry->pid, __entry->prio) +) +#endif +/* + * Just like the headers that create TRACE_EVENTs, the below must + * be outside the protection of the above #if block. + */ + +/* + * It is required that the Makefile includes: + * CFLAGS_.o := -I$(src) + */ +#undef TRACE_INCLUDE_PATH +#undef TRACE_INCLUDE_FILE +#define TRACE_INCLUDE_PATH . + +/* + * It is requred that the TRACE_INCLUDE_FILE be the same + * as this file without the ".h". + */ +#define TRACE_INCLUDE_FILE trace_custom_sched +#include From 7e348b325bc40eb52aead4d57a1f90d33ea834fc Mon Sep 17 00:00:00 2001 From: Beau Belgrave Date: Wed, 9 Mar 2022 16:11:41 -0800 Subject: [PATCH 45/67] user_events: Prevent dyn_event delete racing with ioctl add/delete Find user_events always while under the event_mutex and before leaving the lock, add a ref count to the user_event. This ensures that all paths under the event_mutex that check the ref counts will be synchronized. The ioctl add/delete paths are protected by the reg_mutex. However, dyn_event is only protected by the event_mutex. The dyn_event delete path cannot acquire reg_mutex, since that could cause a deadlock between the ioctl delete case acquiring event_mutex after acquiring the reg_mutex. Link: https://lkml.kernel.org/r/20220310001141.1660-1-beaub@linux.microsoft.com Signed-off-by: Beau Belgrave Signed-off-by: Steven Rostedt (Google) --- kernel/trace/trace_events_user.c | 46 +++++++++++++++++++++++++++----- 1 file changed, 40 insertions(+), 6 deletions(-) diff --git a/kernel/trace/trace_events_user.c b/kernel/trace/trace_events_user.c index 9a6191a6a786..4febc1d6ae72 100644 --- a/kernel/trace/trace_events_user.c +++ b/kernel/trace/trace_events_user.c @@ -135,6 +135,8 @@ static struct list_head *user_event_get_fields(struct trace_event_call *call) * NOTE: Offsets are from the user data perspective, they are not from the * trace_entry/buffer perspective. We automatically add the common properties * sizes to the offset for the user. + * + * Upon success user_event has its ref count increased by 1. */ static int user_event_parse_cmd(char *raw_command, struct user_event **newuser) { @@ -593,8 +595,10 @@ static struct user_event *find_user_event(char *name, u32 *outkey) *outkey = key; hash_for_each_possible(register_table, user, node, key) - if (!strcmp(EVENT_NAME(user), name)) + if (!strcmp(EVENT_NAME(user), name)) { + atomic_inc(&user->refcnt); return user; + } return NULL; } @@ -883,7 +887,12 @@ static int user_event_create(const char *raw_command) return -ENOMEM; mutex_lock(®_mutex); + ret = user_event_parse_cmd(name, &user); + + if (!ret) + atomic_dec(&user->refcnt); + mutex_unlock(®_mutex); if (ret) @@ -1050,6 +1059,7 @@ static int user_event_trace_register(struct user_event *user) /* * Parses the event name, arguments and flags then registers if successful. * The name buffer lifetime is owned by this method for success cases only. + * Upon success the returned user_event has its ref count increased by 1. */ static int user_event_parse(char *name, char *args, char *flags, struct user_event **newuser) @@ -1057,7 +1067,12 @@ static int user_event_parse(char *name, char *args, char *flags, int ret; int index; u32 key; - struct user_event *user = find_user_event(name, &key); + struct user_event *user; + + /* Prevent dyn_event from racing */ + mutex_lock(&event_mutex); + user = find_user_event(name, &key); + mutex_unlock(&event_mutex); if (user) { *newuser = user; @@ -1121,6 +1136,10 @@ static int user_event_parse(char *name, char *args, char *flags, goto put_user; user->index = index; + + /* Ensure we track ref */ + atomic_inc(&user->refcnt); + dyn_event_init(&user->devent, &user_event_dops); dyn_event_add(&user->devent, &user->call); set_bit(user->index, page_bitmap); @@ -1147,12 +1166,21 @@ static int delete_user_event(char *name) if (!user) return -ENOENT; - if (atomic_read(&user->refcnt) != 0) - return -EBUSY; + /* Ensure we are the last ref */ + if (atomic_read(&user->refcnt) != 1) { + ret = -EBUSY; + goto put_ref; + } - mutex_lock(&event_mutex); ret = destroy_user_event(user); - mutex_unlock(&event_mutex); + + if (ret) + goto put_ref; + + return ret; +put_ref: + /* No longer have this ref */ + atomic_dec(&user->refcnt); return ret; } @@ -1340,6 +1368,9 @@ static long user_events_ioctl_reg(struct file *file, unsigned long uarg) ret = user_events_ref_add(file, user); + /* No longer need parse ref, ref_add either worked or not */ + atomic_dec(&user->refcnt); + /* Positive number is index and valid */ if (ret < 0) return ret; @@ -1364,7 +1395,10 @@ static long user_events_ioctl_del(struct file *file, unsigned long uarg) if (IS_ERR(name)) return PTR_ERR(name); + /* event_mutex prevents dyn_event from racing */ + mutex_lock(&event_mutex); ret = delete_user_event(name); + mutex_unlock(&event_mutex); kfree(name); From 9f438d4d7fa2f59570cd58bca24ac82e7172d63b Mon Sep 17 00:00:00 2001 From: Tom Zanussi Date: Thu, 10 Mar 2022 11:17:07 -0600 Subject: [PATCH 46/67] tracing: Fix strncpy warning in trace_events_synth.c 0-day reported the strncpy error below: ../kernel/trace/trace_events_synth.c: In function 'last_cmd_set': ../kernel/trace/trace_events_synth.c:65:9: warning: 'strncpy' specified bound depends on the length o\ f the source argument [-Wstringop-truncation] 65 | strncpy(last_cmd, str, strlen(str) + 1); | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ../kernel/trace/trace_events_synth.c:65:32: note: length computed here 65 | strncpy(last_cmd, str, strlen(str) + 1); | ^~~~~~~~~~~ There's no reason to use strncpy here, in fact there's no reason to do anything but a simple kstrdup() (note we don't even need to check for failure since last_cmod is expected to be either the last cmd string or NULL, and the containing function is a void return). Link: https://lkml.kernel.org/r/77deca8cbfd226981b3f1eab203967381e9b5bd9.camel@kernel.org Fixes: 27c888da9867 ("tracing: Remove size restriction on synthetic event cmd error logging") Reported-by: kernel test robot Signed-off-by: Tom Zanussi Signed-off-by: Steven Rostedt (Google) --- kernel/trace/trace_events_synth.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/kernel/trace/trace_events_synth.c b/kernel/trace/trace_events_synth.c index fdd79e07e2fc..5e8c07aef071 100644 --- a/kernel/trace/trace_events_synth.c +++ b/kernel/trace/trace_events_synth.c @@ -58,11 +58,8 @@ static void last_cmd_set(const char *str) return; kfree(last_cmd); - last_cmd = kzalloc(strlen(str) + 1, GFP_KERNEL); - if (!last_cmd) - return; - strncpy(last_cmd, str, strlen(str) + 1); + last_cmd = kstrdup(str, GFP_KERNEL); } static void synth_err(u8 err_type, u16 err_pos) From b3bc8547d3be60898818885f5bf22d0a62e2eb48 Mon Sep 17 00:00:00 2001 From: "Steven Rostedt (Google)" Date: Thu, 10 Mar 2022 23:27:38 -0500 Subject: [PATCH 47/67] tracing: Have TRACE_DEFINE_ENUM affect trace event types as well The macro TRACE_DEFINE_ENUM is used to convert enums in the kernel to their actual value when they are exported to user space via the trace event format file. Currently only the enums in the "print fmt" (TP_printk in the TRACE_EVENT macro) have the enums converted. But the enums can be used to denote array size: field:unsigned int fc_ineligible_rc[EXT4_FC_REASON_MAX]; offset:12; size:36; signed:0; The EXT4_FC_REASON_MAX has no meaning to userspace but it needs to know that information to know how to parse the array. Have the array indexes also be parsed as well. Link: https://lore.kernel.org/all/cover.1646922487.git.riteshh@linux.ibm.com/ Reported-by: Ritesh Harjani Tested-by: Ritesh Harjani Signed-off-by: Steven Rostedt (Google) --- kernel/trace/trace_events.c | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/kernel/trace/trace_events.c b/kernel/trace/trace_events.c index 38afd66d80e3..ae9a3b8481f5 100644 --- a/kernel/trace/trace_events.c +++ b/kernel/trace/trace_events.c @@ -2633,6 +2633,33 @@ static void update_event_printk(struct trace_event_call *call, } } +static void update_event_fields(struct trace_event_call *call, + struct trace_eval_map *map) +{ + struct ftrace_event_field *field; + struct list_head *head; + char *ptr; + int len = strlen(map->eval_string); + + head = trace_get_fields(call); + list_for_each_entry(field, head, link) { + ptr = strchr(field->type, '['); + if (!ptr) + continue; + ptr++; + + if (!isalpha(*ptr) && *ptr != '_') + continue; + + if (strncmp(map->eval_string, ptr, len) != 0) + continue; + + ptr = eval_replace(ptr, map, len); + /* enum/sizeof string smaller than value */ + WARN_ON_ONCE(!ptr); + } +} + void trace_event_eval_update(struct trace_eval_map **map, int len) { struct trace_event_call *call, *p; @@ -2668,6 +2695,7 @@ void trace_event_eval_update(struct trace_eval_map **map, int len) first = false; } update_event_printk(call, map[i]); + update_event_fields(call, map[i]); } } } From 380af29b8d7670c445965bd573ab219aff0c4c11 Mon Sep 17 00:00:00 2001 From: "Steven Rostedt (Google)" Date: Thu, 10 Mar 2022 21:37:09 -0500 Subject: [PATCH 48/67] tracing: Add snapshot at end of kernel boot up Add ftrace_boot_snapshot kernel parameter that will take a snapshot at the end of boot up just before switching over to user space (it happens during the kernel freeing of init memory). This is useful when there's interesting data that can be collected from kernel start up, but gets overridden by user space start up code. With this option, the ring buffer content from the boot up traces gets saved in the snapshot at the end of boot up. This trace can be read from: /sys/kernel/tracing/snapshot Signed-off-by: Steven Rostedt (Google) --- .../admin-guide/kernel-parameters.txt | 8 ++++++++ include/linux/ftrace.h | 11 ++++++++++- kernel/trace/ftrace.c | 2 ++ kernel/trace/trace.c | 18 ++++++++++++++++++ 4 files changed, 38 insertions(+), 1 deletion(-) diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt index f5a27f067db9..f6b7ee64ace8 100644 --- a/Documentation/admin-guide/kernel-parameters.txt +++ b/Documentation/admin-guide/kernel-parameters.txt @@ -1435,6 +1435,14 @@ as early as possible in order to facilitate early boot debugging. + ftrace_boot_snapshot + [FTRACE] On boot up, a snapshot will be taken of the + ftrace ring buffer that can be read at: + /sys/kernel/tracing/snapshot. + This is useful if you need tracing information from kernel + boot up that is likely to be overridden by user space + start up functionality. + ftrace_dump_on_oops[=orig_cpu] [FTRACE] will dump the trace buffers on oops. If no parameter is passed, ftrace will dump diff --git a/include/linux/ftrace.h b/include/linux/ftrace.h index 9999e29187de..37b619185ec9 100644 --- a/include/linux/ftrace.h +++ b/include/linux/ftrace.h @@ -30,6 +30,12 @@ #define ARCH_SUPPORTS_FTRACE_OPS 0 #endif +#ifdef CONFIG_TRACING +extern void ftrace_boot_snapshot(void); +#else +static inline void ftrace_boot_snapshot(void) { } +#endif + #ifdef CONFIG_FUNCTION_TRACER struct ftrace_ops; struct ftrace_regs; @@ -215,7 +221,10 @@ struct ftrace_ops_hash { void ftrace_free_init_mem(void); void ftrace_free_mem(struct module *mod, void *start, void *end); #else -static inline void ftrace_free_init_mem(void) { } +static inline void ftrace_free_init_mem(void) +{ + ftrace_boot_snapshot(); +} static inline void ftrace_free_mem(struct module *mod, void *start, void *end) { } #endif diff --git a/kernel/trace/ftrace.c b/kernel/trace/ftrace.c index f9feb197b2da..4e29bd1cf151 100644 --- a/kernel/trace/ftrace.c +++ b/kernel/trace/ftrace.c @@ -7096,6 +7096,8 @@ void __init ftrace_free_init_mem(void) void *start = (void *)(&__init_begin); void *end = (void *)(&__init_end); + ftrace_boot_snapshot(); + ftrace_free_mem(NULL, start, end); } diff --git a/kernel/trace/trace.c b/kernel/trace/trace.c index 7c85ce9ffdc3..eaf7d30ca6f1 100644 --- a/kernel/trace/trace.c +++ b/kernel/trace/trace.c @@ -185,6 +185,7 @@ static char bootup_tracer_buf[MAX_TRACER_SIZE] __initdata; static char *default_bootup_tracer; static bool allocate_snapshot; +static bool snapshot_at_boot; static int __init set_cmdline_ftrace(char *str) { @@ -230,6 +231,15 @@ static int __init boot_alloc_snapshot(char *str) __setup("alloc_snapshot", boot_alloc_snapshot); +static int __init boot_snapshot(char *str) +{ + snapshot_at_boot = true; + boot_alloc_snapshot(str); + return 1; +} +__setup("ftrace_boot_snapshot", boot_snapshot); + + static char trace_boot_options_buf[MAX_TRACER_SIZE] __initdata; static int __init set_trace_boot_options(char *str) @@ -10149,6 +10159,14 @@ out: return ret; } +void __init ftrace_boot_snapshot(void) +{ + if (snapshot_at_boot) { + tracing_snapshot(); + internal_trace_puts("** Boot snapshot taken **\n"); + } +} + void __init early_trace_init(void) { if (tracepoint_printk) { From bc47ee4844d6b7d7351536cd99d35848c4449689 Mon Sep 17 00:00:00 2001 From: "Steven Rostedt (Google)" Date: Fri, 11 Mar 2022 18:59:57 -0500 Subject: [PATCH 49/67] tracing/user_events: Use alloc_pages instead of kzalloc() for register pages kzalloc virtual addresses do not work with SetPageReserved, use the actual page virtual addresses instead via alloc_pages. The issue is reported when booting with user_events and DEBUG_VM_PGFLAGS=y. Also make the number of events based on the ORDER. Link: https://lore.kernel.org/all/CADYN=9+xY5Vku3Ws5E9S60SM5dCFfeGeRBkmDFbcxX0ZMoFing@mail.gmail.com/ Link: https://lore.kernel.org/all/20220311223028.1865-1-beaub@linux.microsoft.com/ Cc: Beau Belgrave Reported-by: Anders Roxell Signed-off-by: Steven Rostedt (Google) --- kernel/trace/trace_events_user.c | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/kernel/trace/trace_events_user.c b/kernel/trace/trace_events_user.c index 4febc1d6ae72..e10ad057e797 100644 --- a/kernel/trace/trace_events_user.c +++ b/kernel/trace/trace_events_user.c @@ -30,9 +30,10 @@ /* * Limits how many trace_event calls user processes can create: - * Must be multiple of PAGE_SIZE. + * Must be a power of two of PAGE_SIZE. */ -#define MAX_PAGES 1 +#define MAX_PAGE_ORDER 0 +#define MAX_PAGES (1 << MAX_PAGE_ORDER) #define MAX_EVENTS (MAX_PAGES * PAGE_SIZE) /* Limit how long of an event name plus args within the subsystem. */ @@ -1622,16 +1623,17 @@ static void set_page_reservations(bool set) static int __init trace_events_user_init(void) { + struct page *pages; int ret; /* Zero all bits beside 0 (which is reserved for failures) */ bitmap_zero(page_bitmap, MAX_EVENTS); set_bit(0, page_bitmap); - register_page_data = kzalloc(MAX_EVENTS, GFP_KERNEL); - - if (!register_page_data) + pages = alloc_pages(GFP_KERNEL | __GFP_ZERO, MAX_PAGE_ORDER); + if (!pages) return -ENOMEM; + register_page_data = page_address(pages); set_page_reservations(true); @@ -1640,7 +1642,7 @@ static int __init trace_events_user_init(void) if (ret) { pr_warn("user_events could not register with tracefs\n"); set_page_reservations(false); - kfree(register_page_data); + __free_pages(pages, MAX_PAGE_ORDER); return ret; } From 089331d47325d3f55016ed3d1f79ffd2754fb19e Mon Sep 17 00:00:00 2001 From: Beau Belgrave Date: Tue, 8 Mar 2022 14:28:07 -0800 Subject: [PATCH 50/67] user_events: Add trace event call as root for low permission cases Tracefs by default is locked down heavily. System operators can open up some files, such as user_events to a broader set of users. These users do not have access within tracefs beyond just the user_event files. Due to this restriction the trace_add_event_call/remove calls will silently fail since the caller does not have permissions to create directories. To fix this trace_add_event_call/remove calls will be issued with override creds of the global root UID. Creds are reverted immediately afterward. Link: https://lkml.kernel.org/r/20220308222807.2040-1-beaub@linux.microsoft.com Signed-off-by: Beau Belgrave Signed-off-by: Steven Rostedt (Google) --- kernel/trace/trace_events_user.c | 39 ++++++++++++++++++++++++++++++-- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/kernel/trace/trace_events_user.c b/kernel/trace/trace_events_user.c index e10ad057e797..8b3d241a31c2 100644 --- a/kernel/trace/trace_events_user.c +++ b/kernel/trace/trace_events_user.c @@ -562,6 +562,41 @@ static struct trace_event_functions user_event_funcs = { .trace = user_event_print_trace, }; +static int user_event_set_call_visible(struct user_event *user, bool visible) +{ + int ret; + const struct cred *old_cred; + struct cred *cred; + + cred = prepare_creds(); + + if (!cred) + return -ENOMEM; + + /* + * While by default tracefs is locked down, systems can be configured + * to allow user_event files to be less locked down. The extreme case + * being "other" has read/write access to user_events_data/status. + * + * When not locked down, processes may not have have permissions to + * add/remove calls themselves to tracefs. We need to temporarily + * switch to root file permission to allow for this scenario. + */ + cred->fsuid = GLOBAL_ROOT_UID; + + old_cred = override_creds(cred); + + if (visible) + ret = trace_add_event_call(&user->call); + else + ret = trace_remove_event_call(&user->call); + + revert_creds(old_cred); + put_cred(cred); + + return ret; +} + static int destroy_user_event(struct user_event *user) { int ret = 0; @@ -569,7 +604,7 @@ static int destroy_user_event(struct user_event *user) /* Must destroy fields before call removal */ user_event_destroy_fields(user); - ret = trace_remove_event_call(&user->call); + ret = user_event_set_call_visible(user, false); if (ret) return ret; @@ -1049,7 +1084,7 @@ static int user_event_trace_register(struct user_event *user) if (!ret) return -ENODEV; - ret = trace_add_event_call(&user->call); + ret = user_event_set_call_visible(user, true); if (ret) unregister_trace_event(&user->call.event); From 61c57d578bd7ca2aff3652ed62d95e3f8fc6d16e Mon Sep 17 00:00:00 2001 From: Daniel Bristot de Oliveira Date: Wed, 2 Mar 2022 20:01:26 +0100 Subject: [PATCH 51/67] rtla/osnoise: Add support to adjust the tracing_thresh osnoise uses the tracing_thresh parameter to define the delta between two reads of the time to be considered a noise. Add support to get and set the tracing_thresh from osnoise tools. Link: https://lkml.kernel.org/r/715ad2a53fd40e41bab8c3f1214c1a94e12fb595.1646247211.git.bristot@kernel.org Cc: Daniel Bristot de Oliveira Cc: Clark Williams Cc: Juri Lelli Cc: Jonathan Corbet Signed-off-by: Daniel Bristot de Oliveira Signed-off-by: Steven Rostedt (Google) --- tools/tracing/rtla/src/osnoise.c | 83 ++++++++++++++++++++++++++++++++ tools/tracing/rtla/src/osnoise.h | 8 +++ 2 files changed, 91 insertions(+) diff --git a/tools/tracing/rtla/src/osnoise.c b/tools/tracing/rtla/src/osnoise.c index e60f1862bad0..b8ec6c15bccb 100644 --- a/tools/tracing/rtla/src/osnoise.c +++ b/tools/tracing/rtla/src/osnoise.c @@ -655,6 +655,85 @@ void osnoise_put_print_stack(struct osnoise_context *context) context->orig_print_stack = OSNOISE_OPTION_INIT_VAL; } +/* + * osnoise_get_tracing_thresh - read and save the original "tracing_thresh" + */ +static long long +osnoise_get_tracing_thresh(struct osnoise_context *context) +{ + long long tracing_thresh; + + if (context->tracing_thresh != OSNOISE_OPTION_INIT_VAL) + return context->tracing_thresh; + + if (context->orig_tracing_thresh != OSNOISE_OPTION_INIT_VAL) + return context->orig_tracing_thresh; + + tracing_thresh = osnoise_read_ll_config("tracing_thresh"); + if (tracing_thresh < 0) + goto out_err; + + context->orig_tracing_thresh = tracing_thresh; + return tracing_thresh; + +out_err: + return OSNOISE_OPTION_INIT_VAL; +} + +/* + * osnoise_set_tracing_thresh - set "tracing_thresh" + */ +int osnoise_set_tracing_thresh(struct osnoise_context *context, long long tracing_thresh) +{ + long long curr_tracing_thresh = osnoise_get_tracing_thresh(context); + int retval; + + if (curr_tracing_thresh == OSNOISE_OPTION_INIT_VAL) + return -1; + + retval = osnoise_write_ll_config("tracing_thresh", tracing_thresh); + if (retval < 0) + return -1; + + context->tracing_thresh = tracing_thresh; + + return 0; +} + +/* + * osnoise_restore_tracing_thresh - restore the original "tracing_thresh" + */ +void osnoise_restore_tracing_thresh(struct osnoise_context *context) +{ + int retval; + + if (context->orig_tracing_thresh == OSNOISE_OPTION_INIT_VAL) + return; + + if (context->orig_tracing_thresh == context->tracing_thresh) + goto out_done; + + retval = osnoise_write_ll_config("tracing_thresh", context->orig_tracing_thresh); + if (retval < 0) + err_msg("Could not restore original tracing_thresh\n"); + +out_done: + context->tracing_thresh = OSNOISE_OPTION_INIT_VAL; +} + +/* + * osnoise_put_tracing_thresh - restore original values and cleanup data + */ +void osnoise_put_tracing_thresh(struct osnoise_context *context) +{ + osnoise_restore_tracing_thresh(context); + + if (context->orig_tracing_thresh == OSNOISE_OPTION_INIT_VAL) + return; + + context->orig_tracing_thresh = OSNOISE_OPTION_INIT_VAL; +} + /* * enable_osnoise - enable osnoise tracer in the trace_instance */ @@ -716,6 +795,9 @@ struct osnoise_context *osnoise_context_alloc(void) context->orig_print_stack = OSNOISE_OPTION_INIT_VAL; context->print_stack = OSNOISE_OPTION_INIT_VAL; + context->orig_tracing_thresh = OSNOISE_OPTION_INIT_VAL; + context->tracing_thresh = OSNOISE_OPTION_INIT_VAL; + osnoise_get_context(context); return context; @@ -741,6 +823,7 @@ void osnoise_put_context(struct osnoise_context *context) osnoise_put_stop_total_us(context); osnoise_put_timerlat_period_us(context); osnoise_put_print_stack(context); + osnoise_put_tracing_thresh(context); free(context); } diff --git a/tools/tracing/rtla/src/osnoise.h b/tools/tracing/rtla/src/osnoise.h index 9e4b2e2a4559..04a4384cc544 100644 --- a/tools/tracing/rtla/src/osnoise.h +++ b/tools/tracing/rtla/src/osnoise.h @@ -23,6 +23,10 @@ struct osnoise_context { long long orig_timerlat_period_us; long long timerlat_period_us; + /* 0 as init value */ + long long orig_tracing_thresh; + long long tracing_thresh; + /* -1 as init value because 0 is disabled */ long long orig_stop_us; long long stop_us; @@ -67,6 +71,10 @@ int osnoise_set_timerlat_period_us(struct osnoise_context *context, long long timerlat_period_us); void osnoise_restore_timerlat_period_us(struct osnoise_context *context); +int osnoise_set_tracing_thresh(struct osnoise_context *context, + long long tracing_thresh); +void osnoise_restore_tracing_thresh(struct osnoise_context *context); + void osnoise_restore_print_stack(struct osnoise_context *context); int osnoise_set_print_stack(struct osnoise_context *context, long long print_stack); From d635316ae92291083ae7a36014e29ed7b306cb04 Mon Sep 17 00:00:00 2001 From: Daniel Bristot de Oliveira Date: Wed, 2 Mar 2022 20:01:27 +0100 Subject: [PATCH 52/67] rtla/osnoise: Add an option to set the threshold Add the -T/--threshold option to set the minimum threshold to be considered a noise to osnoise top and hist commands. Also update the man pages. Link: https://lkml.kernel.org/r/031861200ffdb24a1df4aa72c458706889a20d5d.1646247211.git.bristot@kernel.org Cc: Daniel Bristot de Oliveira Cc: Clark Williams Cc: Juri Lelli Cc: Jonathan Corbet Signed-off-by: Daniel Bristot de Oliveira Signed-off-by: Steven Rostedt (Google) --- .../tools/rtla/common_osnoise_options.rst | 5 +++++ tools/tracing/rtla/src/osnoise_hist.c | 22 +++++++++++++++---- tools/tracing/rtla/src/osnoise_top.c | 20 ++++++++++++++--- 3 files changed, 40 insertions(+), 7 deletions(-) diff --git a/Documentation/tools/rtla/common_osnoise_options.rst b/Documentation/tools/rtla/common_osnoise_options.rst index d556883e4e26..27f1493f7bc0 100644 --- a/Documentation/tools/rtla/common_osnoise_options.rst +++ b/Documentation/tools/rtla/common_osnoise_options.rst @@ -15,3 +15,8 @@ Stop the trace if the total sample is higher than the argument in microseconds. If **-T** is set, it will also save the trace to the output. + +**-T**, **--threshold** *us* + + Specify the minimum delta between two time reads to be considered noise. + The default threshold is *5 us*. diff --git a/tools/tracing/rtla/src/osnoise_hist.c b/tools/tracing/rtla/src/osnoise_hist.c index 52c053cc1789..ab02219de528 100644 --- a/tools/tracing/rtla/src/osnoise_hist.c +++ b/tools/tracing/rtla/src/osnoise_hist.c @@ -21,6 +21,7 @@ struct osnoise_hist_params { char *trace_output; unsigned long long runtime; unsigned long long period; + long long threshold; long long stop_us; long long stop_total_us; int sleep_time; @@ -425,15 +426,16 @@ static void osnoise_hist_usage(char *usage) static const char * const msg[] = { "", - " usage: rtla osnoise hist [-h] [-D] [-d s] [-p us] [-r us] [-s us] [-S us] [-t[=file]] \\", - " [-c cpu-list] [-P priority] [-b N] [-E N] [--no-header] [--no-summary] \\", - " [--no-index] [--with-zeros]", + " usage: rtla osnoise hist [-h] [-D] [-d s] [-p us] [-r us] [-s us] [-S us] [-T us] \\", + " [-t[=file]] [-c cpu-list] [-P priority] [-b N] [-E N] [--no-header] \\", + " [--no-summary] [--no-index] [--with-zeros]", "", " -h/--help: print this menu", " -p/--period us: osnoise period in us", " -r/--runtime us: osnoise runtime in us", " -s/--stop us: stop trace if a single sample is higher than the argument in us", " -S/--stop-total us: stop trace if the total sample is higher than the argument in us", + " -T/--threshold us: the minimum delta to be considered a noise", " -c/--cpus cpu-list: list of cpus to run osnoise threads", " -d/--duration time[s|m|h|d]: duration of the session", " -D/--debug: print debug info", @@ -497,6 +499,7 @@ static struct osnoise_hist_params {"stop", required_argument, 0, 's'}, {"stop-total", required_argument, 0, 'S'}, {"trace", optional_argument, 0, 't'}, + {"threshold", required_argument, 0, 'T'}, {"no-header", no_argument, 0, '0'}, {"no-summary", no_argument, 0, '1'}, {"no-index", no_argument, 0, '2'}, @@ -507,7 +510,7 @@ static struct osnoise_hist_params /* getopt_long stores the option index here. */ int option_index = 0; - c = getopt_long(argc, argv, "c:b:d:E:Dhp:P:r:s:S:t::0123", + c = getopt_long(argc, argv, "c:b:d:E:Dhp:P:r:s:S:t::T:0123", long_options, &option_index); /* detect the end of the options. */ @@ -565,6 +568,9 @@ static struct osnoise_hist_params case 'S': params->stop_total_us = get_llong_from_str(optarg); break; + case 'T': + params->threshold = get_llong_from_str(optarg); + break; case 't': if (optarg) /* skip = */ @@ -645,6 +651,14 @@ osnoise_hist_apply_config(struct osnoise_tool *tool, struct osnoise_hist_params } } + if (params->threshold) { + retval = osnoise_set_tracing_thresh(tool->context, params->threshold); + if (retval) { + err_msg("Failed to set tracing_thresh\n"); + goto out_err; + } + } + return 0; out_err: diff --git a/tools/tracing/rtla/src/osnoise_top.c b/tools/tracing/rtla/src/osnoise_top.c index 7af769b9c0de..07fb1b8314d3 100644 --- a/tools/tracing/rtla/src/osnoise_top.c +++ b/tools/tracing/rtla/src/osnoise_top.c @@ -23,6 +23,7 @@ struct osnoise_top_params { char *trace_output; unsigned long long runtime; unsigned long long period; + long long threshold; long long stop_us; long long stop_total_us; int sleep_time; @@ -244,14 +245,15 @@ void osnoise_top_usage(char *usage) int i; static const char * const msg[] = { - " usage: rtla osnoise [top] [-h] [-q] [-D] [-d s] [-p us] [-r us] [-s us] [-S us] [-t[=file]] \\", - " [-c cpu-list] [-P priority]", + " usage: rtla osnoise [top] [-h] [-q] [-D] [-d s] [-p us] [-r us] [-s us] [-S us] [-T us] \\", + " [-t[=file]] [-c cpu-list] [-P priority]", "", " -h/--help: print this menu", " -p/--period us: osnoise period in us", " -r/--runtime us: osnoise runtime in us", " -s/--stop us: stop trace if a single sample is higher than the argument in us", " -S/--stop-total us: stop trace if the total sample is higher than the argument in us", + " -T/--threshold us: the minimum delta to be considered a noise", " -c/--cpus cpu-list: list of cpus to run osnoise threads", " -d/--duration time[s|m|h|d]: duration of the session", " -D/--debug: print debug info", @@ -302,6 +304,7 @@ struct osnoise_top_params *osnoise_top_parse_args(int argc, char **argv) {"runtime", required_argument, 0, 'r'}, {"stop", required_argument, 0, 's'}, {"stop-total", required_argument, 0, 'S'}, + {"threshold", required_argument, 0, 'T'}, {"trace", optional_argument, 0, 't'}, {0, 0, 0, 0} }; @@ -309,7 +312,7 @@ struct osnoise_top_params *osnoise_top_parse_args(int argc, char **argv) /* getopt_long stores the option index here. */ int option_index = 0; - c = getopt_long(argc, argv, "c:d:Dhp:P:qr:s:S:t::", + c = getopt_long(argc, argv, "c:d:Dhp:P:qr:s:S:t::T:", long_options, &option_index); /* Detect the end of the options. */ @@ -367,6 +370,9 @@ struct osnoise_top_params *osnoise_top_parse_args(int argc, char **argv) else params->trace_output = "osnoise_trace.txt"; break; + case 'T': + params->threshold = get_llong_from_str(optarg); + break; default: osnoise_top_usage("Invalid option"); } @@ -425,6 +431,14 @@ osnoise_top_apply_config(struct osnoise_tool *tool, struct osnoise_top_params *p } } + if (params->threshold) { + retval = osnoise_set_tracing_thresh(tool->context, params->threshold); + if (retval) { + err_msg("Failed to set tracing_thresh\n"); + goto out_err; + } + } + return 0; out_err: From 2b622edd5eb5a12c1203fdb353c2ce0681672571 Mon Sep 17 00:00:00 2001 From: Daniel Bristot de Oliveira Date: Wed, 2 Mar 2022 20:01:28 +0100 Subject: [PATCH 53/67] rtla/osnoise: Add the automatic trace option Add the -a/--auto option. This option sets some commonly used options while debugging the system. It aims to help users produce reports in the field, reducing the number of arguments passed to the tool in the first approach to a problem. It is equivalent to setting osnoise/stop_tracing_us with the argument, setting tracing_thresh to 1 us, and saving the trace to osnoise_trace.txt file if the trace is stopped automatically. Link: https://lkml.kernel.org/r/ef04c961b227eb93a83cd0b54bfca45e1a381b77.1646247211.git.bristot@kernel.org Cc: Daniel Bristot de Oliveira Cc: Clark Williams Cc: Juri Lelli Cc: Jonathan Corbet Signed-off-by: Daniel Bristot de Oliveira Signed-off-by: Steven Rostedt (Google) --- .../tools/rtla/common_osnoise_options.rst | 5 +++++ tools/tracing/rtla/src/osnoise_hist.c | 19 ++++++++++++++++--- tools/tracing/rtla/src/osnoise_top.c | 19 ++++++++++++++++--- 3 files changed, 37 insertions(+), 6 deletions(-) diff --git a/Documentation/tools/rtla/common_osnoise_options.rst b/Documentation/tools/rtla/common_osnoise_options.rst index 27f1493f7bc0..f792ca58c211 100644 --- a/Documentation/tools/rtla/common_osnoise_options.rst +++ b/Documentation/tools/rtla/common_osnoise_options.rst @@ -1,3 +1,8 @@ +**-a**, **--auto** *us* + + Set the automatic trace mode. This mode sets some commonly used options + while debugging the system. It is equivalent to use **-s** *us* **-T 1 -t**. + **-p**, **--period** *us* Set the *osnoise* tracer period in microseconds. diff --git a/tools/tracing/rtla/src/osnoise_hist.c b/tools/tracing/rtla/src/osnoise_hist.c index ab02219de528..5698da2fe3dd 100644 --- a/tools/tracing/rtla/src/osnoise_hist.c +++ b/tools/tracing/rtla/src/osnoise_hist.c @@ -426,11 +426,12 @@ static void osnoise_hist_usage(char *usage) static const char * const msg[] = { "", - " usage: rtla osnoise hist [-h] [-D] [-d s] [-p us] [-r us] [-s us] [-S us] [-T us] \\", - " [-t[=file]] [-c cpu-list] [-P priority] [-b N] [-E N] [--no-header] \\", + " usage: rtla osnoise hist [-h] [-D] [-d s] [-a us] [-p us] [-r us] [-s us] [-S us] \\", + " [-T us] [-t[=file]] [-c cpu-list] [-P priority] [-b N] [-E N] [--no-header] \\", " [--no-summary] [--no-index] [--with-zeros]", "", " -h/--help: print this menu", + " -a/--auto: set automatic trace mode, stopping the session if argument in us sample is hit", " -p/--period us: osnoise period in us", " -r/--runtime us: osnoise runtime in us", " -s/--stop us: stop trace if a single sample is higher than the argument in us", @@ -487,6 +488,7 @@ static struct osnoise_hist_params while (1) { static struct option long_options[] = { + {"auto", required_argument, 0, 'a'}, {"bucket-size", required_argument, 0, 'b'}, {"entries", required_argument, 0, 'E'}, {"cpus", required_argument, 0, 'c'}, @@ -510,7 +512,7 @@ static struct osnoise_hist_params /* getopt_long stores the option index here. */ int option_index = 0; - c = getopt_long(argc, argv, "c:b:d:E:Dhp:P:r:s:S:t::T:0123", + c = getopt_long(argc, argv, "a:c:b:d:E:Dhp:P:r:s:S:t::T:0123", long_options, &option_index); /* detect the end of the options. */ @@ -518,6 +520,17 @@ static struct osnoise_hist_params break; switch (c) { + case 'a': + /* set sample stop to auto_thresh */ + params->stop_us = get_llong_from_str(optarg); + + /* set sample threshold to 1 */ + params->threshold = 1; + + /* set trace */ + params->trace_output = "osnoise_trace.txt"; + + break; case 'b': params->bucket_size = get_llong_from_str(optarg); if ((params->bucket_size == 0) || (params->bucket_size >= 1000000)) diff --git a/tools/tracing/rtla/src/osnoise_top.c b/tools/tracing/rtla/src/osnoise_top.c index 07fb1b8314d3..a6f434f85738 100644 --- a/tools/tracing/rtla/src/osnoise_top.c +++ b/tools/tracing/rtla/src/osnoise_top.c @@ -245,10 +245,11 @@ void osnoise_top_usage(char *usage) int i; static const char * const msg[] = { - " usage: rtla osnoise [top] [-h] [-q] [-D] [-d s] [-p us] [-r us] [-s us] [-S us] [-T us] \\", - " [-t[=file]] [-c cpu-list] [-P priority]", + " usage: rtla osnoise [top] [-h] [-q] [-D] [-d s] [-a us] [-p us] [-r us] [-s us] [-S us] \\", + " [-T us] [-t[=file]] [-c cpu-list] [-P priority]", "", " -h/--help: print this menu", + " -a/--auto: set automatic trace mode, stopping the session if argument in us sample is hit", " -p/--period us: osnoise period in us", " -r/--runtime us: osnoise runtime in us", " -s/--stop us: stop trace if a single sample is higher than the argument in us", @@ -294,6 +295,7 @@ struct osnoise_top_params *osnoise_top_parse_args(int argc, char **argv) while (1) { static struct option long_options[] = { + {"auto", required_argument, 0, 'a'}, {"cpus", required_argument, 0, 'c'}, {"debug", no_argument, 0, 'D'}, {"duration", required_argument, 0, 'd'}, @@ -312,7 +314,7 @@ struct osnoise_top_params *osnoise_top_parse_args(int argc, char **argv) /* getopt_long stores the option index here. */ int option_index = 0; - c = getopt_long(argc, argv, "c:d:Dhp:P:qr:s:S:t::T:", + c = getopt_long(argc, argv, "a:c:d:Dhp:P:qr:s:S:t::T:", long_options, &option_index); /* Detect the end of the options. */ @@ -320,6 +322,17 @@ struct osnoise_top_params *osnoise_top_parse_args(int argc, char **argv) break; switch (c) { + case 'a': + /* set sample stop to auto_thresh */ + params->stop_us = get_llong_from_str(optarg); + + /* set sample threshold to 1 */ + params->threshold = 1; + + /* set trace */ + params->trace_output = "osnoise_trace.txt"; + + break; case 'c': retval = parse_cpu_list(optarg, ¶ms->monitored_cpus); if (retval) From 173a3b014827955cefdf972ae673d94b60e31cf8 Mon Sep 17 00:00:00 2001 From: Daniel Bristot de Oliveira Date: Wed, 2 Mar 2022 20:01:29 +0100 Subject: [PATCH 54/67] rtla/timerlat: Add the automatic trace option Add the -a/--auto option. This option sets some commonly used options while debugging the system. It aims to help users produce reports in the field, reducing the number of arguments passed to the tool in the first approach to a problem. It is equivalent to setting osnoise/stop_tracing_total_us and print_stack with the argument, and saving the trace to timerlat_trace.txt file if the trace is stopped automatically. Link: https://lkml.kernel.org/r/92438f7ef132c731f538cebdf77850300afe04a5.1646247211.git.bristot@kernel.org Cc: Daniel Bristot de Oliveira Cc: Clark Williams Cc: Juri Lelli Cc: Jonathan Corbet Signed-off-by: Daniel Bristot de Oliveira Signed-off-by: Steven Rostedt (Google) --- .../tools/rtla/common_timerlat_options.rst | 7 ++++++ tools/tracing/rtla/src/timerlat_hist.c | 24 +++++++++++++++---- tools/tracing/rtla/src/timerlat_top.c | 22 ++++++++++++++--- 3 files changed, 46 insertions(+), 7 deletions(-) diff --git a/Documentation/tools/rtla/common_timerlat_options.rst b/Documentation/tools/rtla/common_timerlat_options.rst index e9c1bfd55d48..14a24a121f5d 100644 --- a/Documentation/tools/rtla/common_timerlat_options.rst +++ b/Documentation/tools/rtla/common_timerlat_options.rst @@ -1,3 +1,10 @@ +**-a**, **--auto** *us* + + Set the automatic trace mode. This mode sets some commonly used options + while debugging the system. It is equivalent to use **-T** *us* **-s** *us* + **-t**. By default, *timerlat* tracer uses FIFO:95 for *timerlat* threads, + thus equilavent to **-P** *f:95*. + **-p**, **--period** *us* Set the *timerlat* tracer period in microseconds. diff --git a/tools/tracing/rtla/src/timerlat_hist.c b/tools/tracing/rtla/src/timerlat_hist.c index 237e1735afa7..9cd97095b04a 100644 --- a/tools/tracing/rtla/src/timerlat_hist.c +++ b/tools/tracing/rtla/src/timerlat_hist.c @@ -428,11 +428,12 @@ static void timerlat_hist_usage(char *usage) char *msg[] = { "", - " usage: [rtla] timerlat hist [-h] [-q] [-d s] [-D] [-n] [-p us] [-i us] [-T us] [-s us] [-t[=file]] \\", - " [-c cpu-list] [-P priority] [-E N] [-b N] [--no-irq] [--no-thread] [--no-header] [--no-summary] \\", - " [--no-index] [--with-zeros]", + " usage: [rtla] timerlat hist [-h] [-q] [-d s] [-D] [-n] [-a us] [-p us] [-i us] [-T us] [-s us] \\", + " [-t[=file]] [-c cpu-list] [-P priority] [-E N] [-b N] [--no-irq] [--no-thread] [--no-header] \\", + " [--no-summary] [--no-index] [--with-zeros]", "", " -h/--help: print this menu", + " -a/--auto: set automatic trace mode, stopping the session if argument in us latency is hit", " -p/--period us: timerlat period in us", " -i/--irq us: stop trace if the irq latency is higher than the argument in us", " -T/--thread us: stop trace if the thread latency is higher than the argument in us", @@ -477,6 +478,7 @@ static struct timerlat_hist_params *timerlat_hist_parse_args(int argc, char *argv[]) { struct timerlat_hist_params *params; + int auto_thresh; int retval; int c; @@ -491,6 +493,7 @@ static struct timerlat_hist_params while (1) { static struct option long_options[] = { + {"auto", required_argument, 0, 'a'}, {"cpus", required_argument, 0, 'c'}, {"bucket-size", required_argument, 0, 'b'}, {"debug", no_argument, 0, 'D'}, @@ -516,7 +519,7 @@ static struct timerlat_hist_params /* getopt_long stores the option index here. */ int option_index = 0; - c = getopt_long(argc, argv, "c:b:d:E:Dhi:np:P:s:t::T:012345", + c = getopt_long(argc, argv, "a:c:b:d:E:Dhi:np:P:s:t::T:012345", long_options, &option_index); /* detect the end of the options. */ @@ -524,6 +527,19 @@ static struct timerlat_hist_params break; switch (c) { + case 'a': + auto_thresh = get_llong_from_str(optarg); + + /* set thread stop to auto_thresh */ + params->stop_total_us = auto_thresh; + + /* get stack trace */ + params->print_stack = auto_thresh; + + /* set trace */ + params->trace_output = "timerlat_trace.txt"; + + break; case 'c': retval = parse_cpu_list(optarg, ¶ms->monitored_cpus); if (retval) diff --git a/tools/tracing/rtla/src/timerlat_top.c b/tools/tracing/rtla/src/timerlat_top.c index d4187f6534ed..aef044832964 100644 --- a/tools/tracing/rtla/src/timerlat_top.c +++ b/tools/tracing/rtla/src/timerlat_top.c @@ -266,10 +266,11 @@ static void timerlat_top_usage(char *usage) static const char *const msg[] = { "", - " usage: rtla timerlat [top] [-h] [-q] [-d s] [-D] [-n] [-p us] [-i us] [-T us] [-s us] [-t[=file]] \\", - " [-c cpu-list] [-P priority]", + " usage: rtla timerlat [top] [-h] [-q] [-a us] [-d s] [-D] [-n] [-p us] [-i us] [-T us] [-s us] \\", + " [[-t[=file]] -c cpu-list] [-P priority]", "", " -h/--help: print this menu", + " -a/--auto: set automatic trace mode, stopping the session if argument in us latency is hit", " -p/--period us: timerlat period in us", " -i/--irq us: stop trace if the irq latency is higher than the argument in us", " -T/--thread us: stop trace if the thread latency is higher than the argument in us", @@ -307,6 +308,7 @@ static struct timerlat_top_params *timerlat_top_parse_args(int argc, char **argv) { struct timerlat_top_params *params; + long long auto_thresh; int retval; int c; @@ -319,6 +321,7 @@ static struct timerlat_top_params while (1) { static struct option long_options[] = { + {"auto", required_argument, 0, 'a'}, {"cpus", required_argument, 0, 'c'}, {"debug", no_argument, 0, 'D'}, {"duration", required_argument, 0, 'd'}, @@ -337,7 +340,7 @@ static struct timerlat_top_params /* getopt_long stores the option index here. */ int option_index = 0; - c = getopt_long(argc, argv, "c:d:Dhi:np:P:qs:t::T:", + c = getopt_long(argc, argv, "a:c:d:Dhi:np:P:qs:t::T:", long_options, &option_index); /* detect the end of the options. */ @@ -345,6 +348,19 @@ static struct timerlat_top_params break; switch (c) { + case 'a': + auto_thresh = get_llong_from_str(optarg); + + /* set thread stop to auto_thresh */ + params->stop_total_us = auto_thresh; + + /* get stack trace */ + params->print_stack = auto_thresh; + + /* set trace */ + params->trace_output = "timerlat_trace.txt"; + + break; case 'c': retval = parse_cpu_list(optarg, ¶ms->monitored_cpus); if (retval) From b5aa0be25c27a7f21d9a28f0e0057915552d3c1b Mon Sep 17 00:00:00 2001 From: Daniel Bristot de Oliveira Date: Wed, 2 Mar 2022 20:01:30 +0100 Subject: [PATCH 55/67] rtla/trace: Add trace events helpers Add a set of helper functions to allow the rtla tools to enable additional tracepoints in the trace instance. Link: https://lkml.kernel.org/r/932398b36c1bbaa22c7810d7a40ca9b8c5595b94.1646247211.git.bristot@kernel.org Cc: Daniel Bristot de Oliveira Cc: Clark Williams Cc: Juri Lelli Cc: Jonathan Corbet Signed-off-by: Daniel Bristot de Oliveira Signed-off-by: Steven Rostedt (Google) --- tools/tracing/rtla/src/trace.c | 104 +++++++++++++++++++++++++++++++++ tools/tracing/rtla/src/trace.h | 15 +++++ 2 files changed, 119 insertions(+) diff --git a/tools/tracing/rtla/src/trace.c b/tools/tracing/rtla/src/trace.c index 83de259abcc1..9ad3d8890ec5 100644 --- a/tools/tracing/rtla/src/trace.c +++ b/tools/tracing/rtla/src/trace.c @@ -190,3 +190,107 @@ int trace_instance_start(struct trace_instance *trace) { return tracefs_trace_on(trace->inst); } + +/* + * trace_events_free - free a list of trace events + */ +static void trace_events_free(struct trace_events *events) +{ + struct trace_events *tevent = events; + struct trace_events *free_event; + + while (tevent) { + free_event = tevent; + + tevent = tevent->next; + + free(free_event->system); + free(free_event); + } +} + +/* + * trace_event_alloc - alloc and parse a single trace event + */ +struct trace_events *trace_event_alloc(const char *event_string) +{ + struct trace_events *tevent; + + tevent = calloc(1, sizeof(*tevent)); + if (!tevent) + return NULL; + + tevent->system = strdup(event_string); + if (!tevent->system) { + free(tevent); + return NULL; + } + + tevent->event = strstr(tevent->system, ":"); + if (tevent->event) { + *tevent->event = '\0'; + tevent->event = &tevent->event[1]; + } + + return tevent; +} + +/* + * trace_events_disable - disable all trace events + */ +void trace_events_disable(struct trace_instance *instance, + struct trace_events *events) +{ + struct trace_events *tevent = events; + + if (!events) + return; + + while (tevent) { + debug_msg("Disabling event %s:%s\n", tevent->system, tevent->event ? : "*"); + if (tevent->enabled) + tracefs_event_disable(instance->inst, tevent->system, tevent->event); + + tevent->enabled = 0; + tevent = tevent->next; + } +} + +/* + * trace_events_enable - enable all events + */ +int trace_events_enable(struct trace_instance *instance, + struct trace_events *events) +{ + struct trace_events *tevent = events; + int retval; + + while (tevent) { + debug_msg("Enabling event %s:%s\n", tevent->system, tevent->event ? : "*"); + retval = tracefs_event_enable(instance->inst, tevent->system, tevent->event); + if (retval < 0) { + err_msg("Error enabling event %s:%s\n", tevent->system, + tevent->event ? : "*"); + return 1; + } + + + tevent->enabled = 1; + tevent = tevent->next; + } + + return 0; +} + +/* + * trace_events_destroy - disable and free all trace events + */ +void trace_events_destroy(struct trace_instance *instance, + struct trace_events *events) +{ + if (!events) + return; + + trace_events_disable(instance, events); + trace_events_free(events); +} diff --git a/tools/tracing/rtla/src/trace.h b/tools/tracing/rtla/src/trace.h index 0ea1df0ad9a7..9f9751f7ee36 100644 --- a/tools/tracing/rtla/src/trace.h +++ b/tools/tracing/rtla/src/trace.h @@ -2,6 +2,13 @@ #include #include +struct trace_events { + struct trace_events *next; + char *system; + char *event; + char enabled; +}; + struct trace_instance { struct tracefs_instance *inst; struct tep_handle *tep; @@ -25,3 +32,11 @@ void destroy_instance(struct tracefs_instance *inst); int save_trace_to_file(struct tracefs_instance *inst, const char *filename); int collect_registered_events(struct tep_event *tep, struct tep_record *record, int cpu, void *context); + +struct trace_events *trace_event_alloc(const char *event_string); +void trace_events_disable(struct trace_instance *instance, + struct trace_events *events); +void trace_events_destroy(struct trace_instance *instance, + struct trace_events *events); +int trace_events_enable(struct trace_instance *instance, + struct trace_events *events); From 51d64c3a181938da8fb56404524e15776e9c6bf8 Mon Sep 17 00:00:00 2001 From: Daniel Bristot de Oliveira Date: Wed, 2 Mar 2022 20:01:31 +0100 Subject: [PATCH 56/67] rtla: Add -e/--event support Add -e/--event option. This option enables an event in the trace (-t) session. The argument can be a specific event, e.g., -e sched:sched_switch, or all events of a system group, e.g., -e sched. Multiple -e are allowed. It is only active when -t or -a are set. This option is available for all current tools. Link: https://lkml.kernel.org/r/6a3b753be9b1e811953995f7f21a86918ad13390.1646247211.git.bristot@kernel.org Cc: Daniel Bristot de Oliveira Cc: Clark Williams Cc: Juri Lelli Cc: Jonathan Corbet Signed-off-by: Daniel Bristot de Oliveira Signed-off-by: Steven Rostedt (Google) --- Documentation/tools/rtla/common_options.rst | 4 +++ tools/tracing/rtla/src/osnoise_hist.c | 31 +++++++++++++++++-- tools/tracing/rtla/src/osnoise_top.c | 29 ++++++++++++++++-- tools/tracing/rtla/src/timerlat_hist.c | 33 ++++++++++++++++++--- tools/tracing/rtla/src/timerlat_top.c | 28 +++++++++++++++-- 5 files changed, 114 insertions(+), 11 deletions(-) diff --git a/Documentation/tools/rtla/common_options.rst b/Documentation/tools/rtla/common_options.rst index 721790ad984e..89d783dc3304 100644 --- a/Documentation/tools/rtla/common_options.rst +++ b/Documentation/tools/rtla/common_options.rst @@ -14,6 +14,10 @@ Save the stopped trace to [*file|osnoise_trace.txt*]. +**-e**, **--event** *sys:event* + + Enable an event in the trace (**-t**) session. The argument can be a specific event, e.g., **-e** *sched:sched_switch*, or all events of a system group, e.g., **-e** *sched*. Multiple **-e** are allowed. It is only active when **-t** or **-a** are set. + **-P**, **--priority** *o:prio|r:prio|f:prio|d:runtime:period* Set scheduling parameters to the osnoise tracer threads, the format to set the priority are: diff --git a/tools/tracing/rtla/src/osnoise_hist.c b/tools/tracing/rtla/src/osnoise_hist.c index 5698da2fe3dd..10d683a98087 100644 --- a/tools/tracing/rtla/src/osnoise_hist.c +++ b/tools/tracing/rtla/src/osnoise_hist.c @@ -29,6 +29,7 @@ struct osnoise_hist_params { int set_sched; int output_divisor; struct sched_attr sched_param; + struct trace_events *events; char no_header; char no_summary; @@ -427,8 +428,8 @@ static void osnoise_hist_usage(char *usage) static const char * const msg[] = { "", " usage: rtla osnoise hist [-h] [-D] [-d s] [-a us] [-p us] [-r us] [-s us] [-S us] \\", - " [-T us] [-t[=file]] [-c cpu-list] [-P priority] [-b N] [-E N] [--no-header] \\", - " [--no-summary] [--no-index] [--with-zeros]", + " [-T us] [-t[=file]] [-e sys[:event]] [-c cpu-list] [-P priority] [-b N] [-E N] \\", + " [--no-header] [--no-summary] [--no-index] [--with-zeros]", "", " -h/--help: print this menu", " -a/--auto: set automatic trace mode, stopping the session if argument in us sample is hit", @@ -441,6 +442,7 @@ static void osnoise_hist_usage(char *usage) " -d/--duration time[s|m|h|d]: duration of the session", " -D/--debug: print debug info", " -t/--trace[=file]: save the stopped trace to [file|osnoise_trace.txt]", + " -e/--event : enable the in the trace instance, multiple -e are allowed", " -b/--bucket-size N: set the histogram bucket size (default 1)", " -E/--entries N: set the number of entries of the histogram (default 256)", " --no-header: do not print header", @@ -474,6 +476,7 @@ static struct osnoise_hist_params *osnoise_hist_parse_args(int argc, char *argv[]) { struct osnoise_hist_params *params; + struct trace_events *tevent; int retval; int c; @@ -501,6 +504,7 @@ static struct osnoise_hist_params {"stop", required_argument, 0, 's'}, {"stop-total", required_argument, 0, 'S'}, {"trace", optional_argument, 0, 't'}, + {"event", required_argument, 0, 'e'}, {"threshold", required_argument, 0, 'T'}, {"no-header", no_argument, 0, '0'}, {"no-summary", no_argument, 0, '1'}, @@ -512,7 +516,7 @@ static struct osnoise_hist_params /* getopt_long stores the option index here. */ int option_index = 0; - c = getopt_long(argc, argv, "a:c:b:d:E:Dhp:P:r:s:S:t::T:0123", + c = getopt_long(argc, argv, "a:c:b:d:e:E:Dhp:P:r:s:S:t::T:0123", long_options, &option_index); /* detect the end of the options. */ @@ -550,6 +554,18 @@ static struct osnoise_hist_params if (!params->duration) osnoise_hist_usage("Invalid -D duration\n"); break; + case 'e': + tevent = trace_event_alloc(optarg); + if (!tevent) { + err_msg("Error alloc trace event"); + exit(EXIT_FAILURE); + } + + if (params->events) + tevent->next = params->events; + + params->events = tevent; + break; case 'E': params->entries = get_llong_from_str(optarg); if ((params->entries < 10) || (params->entries > 9999999)) @@ -778,6 +794,13 @@ int osnoise_hist_main(int argc, char *argv[]) err_msg("Failed to enable the trace instance\n"); goto out_hist; } + + if (params->events) { + retval = trace_events_enable(&record->trace, params->events); + if (retval) + goto out_hist; + } + trace_instance_start(&record->trace); } @@ -817,6 +840,8 @@ int osnoise_hist_main(int argc, char *argv[]) } out_hist: + trace_events_destroy(&record->trace, params->events); + params->events = NULL; osnoise_free_histogram(tool->data); out_destroy: osnoise_destroy_tool(record); diff --git a/tools/tracing/rtla/src/osnoise_top.c b/tools/tracing/rtla/src/osnoise_top.c index a6f434f85738..218dc1114139 100644 --- a/tools/tracing/rtla/src/osnoise_top.c +++ b/tools/tracing/rtla/src/osnoise_top.c @@ -31,6 +31,7 @@ struct osnoise_top_params { int quiet; int set_sched; struct sched_attr sched_param; + struct trace_events *events; }; struct osnoise_top_cpu { @@ -246,7 +247,7 @@ void osnoise_top_usage(char *usage) static const char * const msg[] = { " usage: rtla osnoise [top] [-h] [-q] [-D] [-d s] [-a us] [-p us] [-r us] [-s us] [-S us] \\", - " [-T us] [-t[=file]] [-c cpu-list] [-P priority]", + " [-T us] [-t[=file]] [-e sys[:event]] [-c cpu-list] [-P priority]", "", " -h/--help: print this menu", " -a/--auto: set automatic trace mode, stopping the session if argument in us sample is hit", @@ -259,6 +260,7 @@ void osnoise_top_usage(char *usage) " -d/--duration time[s|m|h|d]: duration of the session", " -D/--debug: print debug info", " -t/--trace[=file]: save the stopped trace to [file|osnoise_trace.txt]", + " -e/--event : enable the in the trace instance, multiple -e are allowed", " -q/--quiet print only a summary at the end", " -P/--priority o:prio|r:prio|f:prio|d:runtime:period : set scheduling parameters", " o:prio - use SCHED_OTHER with prio", @@ -286,6 +288,7 @@ void osnoise_top_usage(char *usage) struct osnoise_top_params *osnoise_top_parse_args(int argc, char **argv) { struct osnoise_top_params *params; + struct trace_events *tevent; int retval; int c; @@ -299,6 +302,7 @@ struct osnoise_top_params *osnoise_top_parse_args(int argc, char **argv) {"cpus", required_argument, 0, 'c'}, {"debug", no_argument, 0, 'D'}, {"duration", required_argument, 0, 'd'}, + {"event", required_argument, 0, 'e'}, {"help", no_argument, 0, 'h'}, {"period", required_argument, 0, 'p'}, {"priority", required_argument, 0, 'P'}, @@ -314,7 +318,7 @@ struct osnoise_top_params *osnoise_top_parse_args(int argc, char **argv) /* getopt_long stores the option index here. */ int option_index = 0; - c = getopt_long(argc, argv, "a:c:d:Dhp:P:qr:s:S:t::T:", + c = getopt_long(argc, argv, "a:c:d:De:hp:P:qr:s:S:t::T:", long_options, &option_index); /* Detect the end of the options. */ @@ -347,6 +351,18 @@ struct osnoise_top_params *osnoise_top_parse_args(int argc, char **argv) if (!params->duration) osnoise_top_usage("Invalid -D duration\n"); break; + case 'e': + tevent = trace_event_alloc(optarg); + if (!tevent) { + err_msg("Error alloc trace event"); + exit(EXIT_FAILURE); + } + + if (params->events) + tevent->next = params->events; + params->events = tevent; + + break; case 'h': case '?': osnoise_top_usage(NULL); @@ -556,6 +572,13 @@ int osnoise_top_main(int argc, char **argv) err_msg("Failed to enable the trace instance\n"); goto out_top; } + + if (params->events) { + retval = trace_events_enable(&record->trace, params->events); + if (retval) + goto out_top; + } + trace_instance_start(&record->trace); } @@ -597,6 +620,8 @@ int osnoise_top_main(int argc, char **argv) } out_top: + trace_events_destroy(&record->trace, params->events); + params->events = NULL; osnoise_free_top(tool->data); osnoise_destroy_tool(record); osnoise_destroy_tool(tool); diff --git a/tools/tracing/rtla/src/timerlat_hist.c b/tools/tracing/rtla/src/timerlat_hist.c index 9cd97095b04a..2bd668fd36f5 100644 --- a/tools/tracing/rtla/src/timerlat_hist.c +++ b/tools/tracing/rtla/src/timerlat_hist.c @@ -29,6 +29,7 @@ struct timerlat_hist_params { int duration; int set_sched; struct sched_attr sched_param; + struct trace_events *events; char no_irq; char no_thread; @@ -429,8 +430,8 @@ static void timerlat_hist_usage(char *usage) char *msg[] = { "", " usage: [rtla] timerlat hist [-h] [-q] [-d s] [-D] [-n] [-a us] [-p us] [-i us] [-T us] [-s us] \\", - " [-t[=file]] [-c cpu-list] [-P priority] [-E N] [-b N] [--no-irq] [--no-thread] [--no-header] \\", - " [--no-summary] [--no-index] [--with-zeros]", + " [-t[=file]] [-e sys[:event]] [-c cpu-list] [-P priority] [-E N] [-b N] [--no-irq] \\", + " [--no-thread] [--no-header] [--no-summary] [--no-index] [--with-zeros]", "", " -h/--help: print this menu", " -a/--auto: set automatic trace mode, stopping the session if argument in us latency is hit", @@ -441,7 +442,8 @@ static void timerlat_hist_usage(char *usage) " -c/--cpus cpus: run the tracer only on the given cpus", " -d/--duration time[m|h|d]: duration of the session in seconds", " -D/--debug: print debug info", - " -T/--trace[=file]: save the stopped trace to [file|timerlat_trace.txt]", + " -t/--trace[=file]: save the stopped trace to [file|timerlat_trace.txt]", + " -e/--event : enable the in the trace instance, multiple -e are allowed", " -n/--nano: display data in nanoseconds", " -b/--bucket-size N: set the histogram bucket size (default 1)", " -E/--entries N: set the number of entries of the histogram (default 256)", @@ -478,6 +480,7 @@ static struct timerlat_hist_params *timerlat_hist_parse_args(int argc, char *argv[]) { struct timerlat_hist_params *params; + struct trace_events *tevent; int auto_thresh; int retval; int c; @@ -507,6 +510,7 @@ static struct timerlat_hist_params {"stack", required_argument, 0, 's'}, {"thread", required_argument, 0, 'T'}, {"trace", optional_argument, 0, 't'}, + {"event", required_argument, 0, 'e'}, {"no-irq", no_argument, 0, '0'}, {"no-thread", no_argument, 0, '1'}, {"no-header", no_argument, 0, '2'}, @@ -519,7 +523,7 @@ static struct timerlat_hist_params /* getopt_long stores the option index here. */ int option_index = 0; - c = getopt_long(argc, argv, "a:c:b:d:E:Dhi:np:P:s:t::T:012345", + c = getopt_long(argc, argv, "a:c:b:d:e:E:Dhi:np:P:s:t::T:012345", long_options, &option_index); /* detect the end of the options. */ @@ -559,6 +563,18 @@ static struct timerlat_hist_params if (!params->duration) timerlat_hist_usage("Invalid -D duration\n"); break; + case 'e': + tevent = trace_event_alloc(optarg); + if (!tevent) { + err_msg("Error alloc trace event"); + exit(EXIT_FAILURE); + } + + if (params->events) + tevent->next = params->events; + + params->events = tevent; + break; case 'E': params->entries = get_llong_from_str(optarg); if ((params->entries < 10) || (params->entries > 9999999)) @@ -791,6 +807,13 @@ int timerlat_hist_main(int argc, char *argv[]) err_msg("Failed to enable the trace instance\n"); goto out_hist; } + + if (params->events) { + retval = trace_events_enable(&record->trace, params->events); + if (retval) + goto out_hist; + } + trace_instance_start(&record->trace); } @@ -828,6 +851,8 @@ int timerlat_hist_main(int argc, char *argv[]) } out_hist: + trace_events_destroy(&record->trace, params->events); + params->events = NULL; timerlat_free_histogram(tool->data); osnoise_destroy_tool(record); osnoise_destroy_tool(tool); diff --git a/tools/tracing/rtla/src/timerlat_top.c b/tools/tracing/rtla/src/timerlat_top.c index aef044832964..13bd922ab147 100644 --- a/tools/tracing/rtla/src/timerlat_top.c +++ b/tools/tracing/rtla/src/timerlat_top.c @@ -30,6 +30,7 @@ struct timerlat_top_params { int quiet; int set_sched; struct sched_attr sched_param; + struct trace_events *events; }; struct timerlat_top_cpu { @@ -267,7 +268,7 @@ static void timerlat_top_usage(char *usage) static const char *const msg[] = { "", " usage: rtla timerlat [top] [-h] [-q] [-a us] [-d s] [-D] [-n] [-p us] [-i us] [-T us] [-s us] \\", - " [[-t[=file]] -c cpu-list] [-P priority]", + " [[-t[=file]] [-e sys[:event]] [-c cpu-list] [-P priority]", "", " -h/--help: print this menu", " -a/--auto: set automatic trace mode, stopping the session if argument in us latency is hit", @@ -279,6 +280,7 @@ static void timerlat_top_usage(char *usage) " -d/--duration time[m|h|d]: duration of the session in seconds", " -D/--debug: print debug info", " -t/--trace[=file]: save the stopped trace to [file|timerlat_trace.txt]", + " -e/--event : enable the in the trace instance, multiple -e are allowed", " -n/--nano: display data in nanoseconds", " -q/--quiet print only a summary at the end", " -P/--priority o:prio|r:prio|f:prio|d:runtime:period : set scheduling parameters", @@ -308,6 +310,7 @@ static struct timerlat_top_params *timerlat_top_parse_args(int argc, char **argv) { struct timerlat_top_params *params; + struct trace_events *tevent; long long auto_thresh; int retval; int c; @@ -325,6 +328,7 @@ static struct timerlat_top_params {"cpus", required_argument, 0, 'c'}, {"debug", no_argument, 0, 'D'}, {"duration", required_argument, 0, 'd'}, + {"event", required_argument, 0, 'e'}, {"help", no_argument, 0, 'h'}, {"irq", required_argument, 0, 'i'}, {"nano", no_argument, 0, 'n'}, @@ -340,7 +344,7 @@ static struct timerlat_top_params /* getopt_long stores the option index here. */ int option_index = 0; - c = getopt_long(argc, argv, "a:c:d:Dhi:np:P:qs:t::T:", + c = getopt_long(argc, argv, "a:c:d:De:hi:np:P:qs:t::T:", long_options, &option_index); /* detect the end of the options. */ @@ -375,6 +379,17 @@ static struct timerlat_top_params if (!params->duration) timerlat_top_usage("Invalid -D duration\n"); break; + case 'e': + tevent = trace_event_alloc(optarg); + if (!tevent) { + err_msg("Error alloc trace event"); + exit(EXIT_FAILURE); + } + + if (params->events) + tevent->next = params->events; + params->events = tevent; + break; case 'h': case '?': timerlat_top_usage(NULL); @@ -583,6 +598,13 @@ int timerlat_top_main(int argc, char *argv[]) err_msg("Failed to enable the trace instance\n"); goto out_top; } + + if (params->events) { + retval = trace_events_enable(&record->trace, params->events); + if (retval) + goto out_top; + } + trace_instance_start(&record->trace); } @@ -624,6 +646,8 @@ int timerlat_top_main(int argc, char *argv[]) } out_top: + trace_events_destroy(&record->trace, params->events); + params->events = NULL; timerlat_free_top(top->data); osnoise_destroy_tool(record); osnoise_destroy_tool(top); From 336c92b26cf9aee6c5d5907ef49b90d2665e9d70 Mon Sep 17 00:00:00 2001 From: Daniel Bristot de Oliveira Date: Wed, 2 Mar 2022 20:01:32 +0100 Subject: [PATCH 57/67] rtla/trace: Add trace event trigger helpers Add a set of helper functions to allow rtla tools to trigger event actions in the trace instance. Link: https://lkml.kernel.org/r/e0d31abe879a78a5600b64f904d0dfa8f76e4fbb.1646247211.git.bristot@kernel.org Cc: Daniel Bristot de Oliveira Cc: Clark Williams Cc: Juri Lelli Cc: Jonathan Corbet Signed-off-by: Daniel Bristot de Oliveira Signed-off-by: Steven Rostedt (Google) --- tools/tracing/rtla/src/trace.c | 88 +++++++++++++++++++++++++++++++++- tools/tracing/rtla/src/trace.h | 4 ++ 2 files changed, 91 insertions(+), 1 deletion(-) diff --git a/tools/tracing/rtla/src/trace.c b/tools/tracing/rtla/src/trace.c index 9ad3d8890ec5..7f8661b2724d 100644 --- a/tools/tracing/rtla/src/trace.c +++ b/tools/tracing/rtla/src/trace.c @@ -204,6 +204,8 @@ static void trace_events_free(struct trace_events *events) tevent = tevent->next; + if (free_event->trigger) + free(free_event->trigger); free(free_event->system); free(free_event); } @@ -235,6 +237,48 @@ struct trace_events *trace_event_alloc(const char *event_string) return tevent; } +/* + * trace_event_add_trigger - record an event trigger action + */ +int trace_event_add_trigger(struct trace_events *event, char *trigger) +{ + if (event->trigger) + free(event->trigger); + + event->trigger = strdup(trigger); + if (!event->trigger) + return 1; + + return 0; +} + +/* + * trace_event_disable_trigger - disable an event trigger + */ +static void trace_event_disable_trigger(struct trace_instance *instance, + struct trace_events *tevent) +{ + char trigger[1024]; + int retval; + + if (!tevent->trigger) + return; + + if (!tevent->trigger_enabled) + return; + + debug_msg("Disabling %s:%s trigger %s\n", tevent->system, + tevent->event ? : "*", tevent->trigger); + + snprintf(trigger, 1024, "!%s\n", tevent->trigger); + + retval = tracefs_event_file_write(instance->inst, tevent->system, + tevent->event, "trigger", trigger); + if (retval < 0) + err_msg("Error disabling %s:%s trigger %s\n", tevent->system, + tevent->event ? : "*", tevent->trigger); +} + /* * trace_events_disable - disable all trace events */ @@ -248,14 +292,52 @@ void trace_events_disable(struct trace_instance *instance, while (tevent) { debug_msg("Disabling event %s:%s\n", tevent->system, tevent->event ? : "*"); - if (tevent->enabled) + if (tevent->enabled) { + trace_event_disable_trigger(instance, tevent); tracefs_event_disable(instance->inst, tevent->system, tevent->event); + } tevent->enabled = 0; tevent = tevent->next; } } +/* + * trace_event_enable_trigger - enable an event trigger associated with an event + */ +static int trace_event_enable_trigger(struct trace_instance *instance, + struct trace_events *tevent) +{ + char trigger[1024]; + int retval; + + if (!tevent->trigger) + return 0; + + if (!tevent->event) { + err_msg("Trigger %s applies only for single events, not for all %s:* events\n", + tevent->trigger, tevent->system); + return 1; + } + + snprintf(trigger, 1024, "%s\n", tevent->trigger); + + debug_msg("Enabling %s:%s trigger %s\n", tevent->system, + tevent->event ? : "*", tevent->trigger); + + retval = tracefs_event_file_write(instance->inst, tevent->system, + tevent->event, "trigger", trigger); + if (retval < 0) { + err_msg("Error enabling %s:%s trigger %s\n", tevent->system, + tevent->event ? : "*", tevent->trigger); + return 1; + } + + tevent->trigger_enabled = 1; + + return 0; +} + /* * trace_events_enable - enable all events */ @@ -275,6 +357,10 @@ int trace_events_enable(struct trace_instance *instance, } + retval = trace_event_enable_trigger(instance, tevent); + if (retval) + return 1; + tevent->enabled = 1; tevent = tevent->next; } diff --git a/tools/tracing/rtla/src/trace.h b/tools/tracing/rtla/src/trace.h index 9f9751f7ee36..856b26d93064 100644 --- a/tools/tracing/rtla/src/trace.h +++ b/tools/tracing/rtla/src/trace.h @@ -6,7 +6,9 @@ struct trace_events { struct trace_events *next; char *system; char *event; + char *trigger; char enabled; + char trigger_enabled; }; struct trace_instance { @@ -40,3 +42,5 @@ void trace_events_destroy(struct trace_instance *instance, struct trace_events *events); int trace_events_enable(struct trace_instance *instance, struct trace_events *events); + +int trace_event_add_trigger(struct trace_events *event, char *trigger); From 1a754893653f73724d007c2cf95cf6c47d5114c4 Mon Sep 17 00:00:00 2001 From: Daniel Bristot de Oliveira Date: Wed, 2 Mar 2022 20:01:33 +0100 Subject: [PATCH 58/67] rtla: Add --trigger support Add --trigger option. This option enables a trace event trigger to the previous -e sys:event argument, allowing some advanced tracing options. For instance, in a system with CPUs 2:23 isolated, it is possible to get a stack trace of thread wakeup targeting those CPUs while running osnoise with the following command line: # osnoise top -c 2-23 -a 50 -e sched:sched_wakeup --trigger="stacktrace if target_cpu >= 2" This option is available for all current tools. Link: https://lkml.kernel.org/r/07d2983d5f71261d4da89dbaf02efcad100ab8ee.1646247211.git.bristot@kernel.org Cc: Daniel Bristot de Oliveira Cc: Clark Williams Cc: Juri Lelli Cc: Jonathan Corbet Signed-off-by: Daniel Bristot de Oliveira Signed-off-by: Steven Rostedt (Google) --- Documentation/tools/rtla/common_options.rst | 3 +++ tools/tracing/rtla/src/osnoise_hist.c | 19 ++++++++++++++++--- tools/tracing/rtla/src/osnoise_top.c | 17 +++++++++++++++-- tools/tracing/rtla/src/timerlat_hist.c | 19 ++++++++++++++++--- tools/tracing/rtla/src/timerlat_top.c | 17 +++++++++++++++-- 5 files changed, 65 insertions(+), 10 deletions(-) diff --git a/Documentation/tools/rtla/common_options.rst b/Documentation/tools/rtla/common_options.rst index 89d783dc3304..e5870b944334 100644 --- a/Documentation/tools/rtla/common_options.rst +++ b/Documentation/tools/rtla/common_options.rst @@ -18,6 +18,9 @@ Enable an event in the trace (**-t**) session. The argument can be a specific event, e.g., **-e** *sched:sched_switch*, or all events of a system group, e.g., **-e** *sched*. Multiple **-e** are allowed. It is only active when **-t** or **-a** are set. +**--trigger** ** + Enable a trace event trigger to the previous **-e** *sys:event*. For further information about event trigger see https://www.kernel.org/doc/html/latest/trace/events.html#event-triggers. + **-P**, **--priority** *o:prio|r:prio|f:prio|d:runtime:period* Set scheduling parameters to the osnoise tracer threads, the format to set the priority are: diff --git a/tools/tracing/rtla/src/osnoise_hist.c b/tools/tracing/rtla/src/osnoise_hist.c index 10d683a98087..3e8f89b4f306 100644 --- a/tools/tracing/rtla/src/osnoise_hist.c +++ b/tools/tracing/rtla/src/osnoise_hist.c @@ -428,8 +428,8 @@ static void osnoise_hist_usage(char *usage) static const char * const msg[] = { "", " usage: rtla osnoise hist [-h] [-D] [-d s] [-a us] [-p us] [-r us] [-s us] [-S us] \\", - " [-T us] [-t[=file]] [-e sys[:event]] [-c cpu-list] [-P priority] [-b N] [-E N] \\", - " [--no-header] [--no-summary] [--no-index] [--with-zeros]", + " [-T us] [-t[=file]] [-e sys[:event]] [--trigger ] [-c cpu-list] [-P priority] \\", + " [-b N] [-E N] [--no-header] [--no-summary] [--no-index] [--with-zeros]", "", " -h/--help: print this menu", " -a/--auto: set automatic trace mode, stopping the session if argument in us sample is hit", @@ -443,6 +443,7 @@ static void osnoise_hist_usage(char *usage) " -D/--debug: print debug info", " -t/--trace[=file]: save the stopped trace to [file|osnoise_trace.txt]", " -e/--event : enable the in the trace instance, multiple -e are allowed", + " --trigger : enable a trace event trigger to the previous -e event", " -b/--bucket-size N: set the histogram bucket size (default 1)", " -E/--entries N: set the number of entries of the histogram (default 256)", " --no-header: do not print header", @@ -510,13 +511,14 @@ static struct osnoise_hist_params {"no-summary", no_argument, 0, '1'}, {"no-index", no_argument, 0, '2'}, {"with-zeros", no_argument, 0, '3'}, + {"trigger", required_argument, 0, '4'}, {0, 0, 0, 0} }; /* getopt_long stores the option index here. */ int option_index = 0; - c = getopt_long(argc, argv, "a:c:b:d:e:E:Dhp:P:r:s:S:t::T:0123", + c = getopt_long(argc, argv, "a:c:b:d:e:E:Dhp:P:r:s:S:t::T:01234:", long_options, &option_index); /* detect the end of the options. */ @@ -619,6 +621,17 @@ static struct osnoise_hist_params case '3': /* with zeros */ params->with_zeros = 1; break; + case '4': /* trigger */ + if (params->events) { + retval = trace_event_add_trigger(params->events, optarg); + if (retval) { + err_msg("Error adding trigger %s\n", optarg); + exit(EXIT_FAILURE); + } + } else { + osnoise_hist_usage("--trigger requires a previous -e\n"); + } + break; default: osnoise_hist_usage("Invalid option"); } diff --git a/tools/tracing/rtla/src/osnoise_top.c b/tools/tracing/rtla/src/osnoise_top.c index 218dc1114139..d16c7ce3e9fa 100644 --- a/tools/tracing/rtla/src/osnoise_top.c +++ b/tools/tracing/rtla/src/osnoise_top.c @@ -247,7 +247,7 @@ void osnoise_top_usage(char *usage) static const char * const msg[] = { " usage: rtla osnoise [top] [-h] [-q] [-D] [-d s] [-a us] [-p us] [-r us] [-s us] [-S us] \\", - " [-T us] [-t[=file]] [-e sys[:event]] [-c cpu-list] [-P priority]", + " [-T us] [-t[=file]] [-e sys[:event]] [--trigger ] [-c cpu-list] [-P priority]", "", " -h/--help: print this menu", " -a/--auto: set automatic trace mode, stopping the session if argument in us sample is hit", @@ -261,6 +261,7 @@ void osnoise_top_usage(char *usage) " -D/--debug: print debug info", " -t/--trace[=file]: save the stopped trace to [file|osnoise_trace.txt]", " -e/--event : enable the in the trace instance, multiple -e are allowed", + " --trigger : enable a trace event trigger to the previous -e event", " -q/--quiet print only a summary at the end", " -P/--priority o:prio|r:prio|f:prio|d:runtime:period : set scheduling parameters", " o:prio - use SCHED_OTHER with prio", @@ -312,13 +313,14 @@ struct osnoise_top_params *osnoise_top_parse_args(int argc, char **argv) {"stop-total", required_argument, 0, 'S'}, {"threshold", required_argument, 0, 'T'}, {"trace", optional_argument, 0, 't'}, + {"trigger", required_argument, 0, '0'}, {0, 0, 0, 0} }; /* getopt_long stores the option index here. */ int option_index = 0; - c = getopt_long(argc, argv, "a:c:d:De:hp:P:qr:s:S:t::T:", + c = getopt_long(argc, argv, "a:c:d:De:hp:P:qr:s:S:t::T:0:", long_options, &option_index); /* Detect the end of the options. */ @@ -402,6 +404,17 @@ struct osnoise_top_params *osnoise_top_parse_args(int argc, char **argv) case 'T': params->threshold = get_llong_from_str(optarg); break; + case '0': /* trigger */ + if (params->events) { + retval = trace_event_add_trigger(params->events, optarg); + if (retval) { + err_msg("Error adding trigger %s\n", optarg); + exit(EXIT_FAILURE); + } + } else { + osnoise_top_usage("--trigger requires a previous -e\n"); + } + break; default: osnoise_top_usage("Invalid option"); } diff --git a/tools/tracing/rtla/src/timerlat_hist.c b/tools/tracing/rtla/src/timerlat_hist.c index 2bd668fd36f5..765b5a313bd2 100644 --- a/tools/tracing/rtla/src/timerlat_hist.c +++ b/tools/tracing/rtla/src/timerlat_hist.c @@ -430,8 +430,8 @@ static void timerlat_hist_usage(char *usage) char *msg[] = { "", " usage: [rtla] timerlat hist [-h] [-q] [-d s] [-D] [-n] [-a us] [-p us] [-i us] [-T us] [-s us] \\", - " [-t[=file]] [-e sys[:event]] [-c cpu-list] [-P priority] [-E N] [-b N] [--no-irq] \\", - " [--no-thread] [--no-header] [--no-summary] [--no-index] [--with-zeros]", + " [-t[=file]] [-e sys[:event]] [--trigger ] [-c cpu-list] [-P priority] [-E N] \\", + " [-b N] [--no-irq] [--no-thread] [--no-header] [--no-summary] [--no-index] [--with-zeros]", "", " -h/--help: print this menu", " -a/--auto: set automatic trace mode, stopping the session if argument in us latency is hit", @@ -444,6 +444,7 @@ static void timerlat_hist_usage(char *usage) " -D/--debug: print debug info", " -t/--trace[=file]: save the stopped trace to [file|timerlat_trace.txt]", " -e/--event : enable the in the trace instance, multiple -e are allowed", + " --trigger : enable a trace event trigger to the previous -e event", " -n/--nano: display data in nanoseconds", " -b/--bucket-size N: set the histogram bucket size (default 1)", " -E/--entries N: set the number of entries of the histogram (default 256)", @@ -517,13 +518,14 @@ static struct timerlat_hist_params {"no-summary", no_argument, 0, '3'}, {"no-index", no_argument, 0, '4'}, {"with-zeros", no_argument, 0, '5'}, + {"trigger", required_argument, 0, '6'}, {0, 0, 0, 0} }; /* getopt_long stores the option index here. */ int option_index = 0; - c = getopt_long(argc, argv, "a:c:b:d:e:E:Dhi:np:P:s:t::T:012345", + c = getopt_long(argc, argv, "a:c:b:d:e:E:Dhi:np:P:s:t::T:0123456:", long_options, &option_index); /* detect the end of the options. */ @@ -632,6 +634,17 @@ static struct timerlat_hist_params case '5': /* with zeros */ params->with_zeros = 1; break; + case '6': /* trigger */ + if (params->events) { + retval = trace_event_add_trigger(params->events, optarg); + if (retval) { + err_msg("Error adding trigger %s\n", optarg); + exit(EXIT_FAILURE); + } + } else { + timerlat_hist_usage("--trigger requires a previous -e\n"); + } + break; default: timerlat_hist_usage("Invalid option"); } diff --git a/tools/tracing/rtla/src/timerlat_top.c b/tools/tracing/rtla/src/timerlat_top.c index 13bd922ab147..76927d4e0dac 100644 --- a/tools/tracing/rtla/src/timerlat_top.c +++ b/tools/tracing/rtla/src/timerlat_top.c @@ -268,7 +268,7 @@ static void timerlat_top_usage(char *usage) static const char *const msg[] = { "", " usage: rtla timerlat [top] [-h] [-q] [-a us] [-d s] [-D] [-n] [-p us] [-i us] [-T us] [-s us] \\", - " [[-t[=file]] [-e sys[:event]] [-c cpu-list] [-P priority]", + " [[-t[=file]] [-e sys[:event]] [--trigger ] [-c cpu-list] [-P priority]", "", " -h/--help: print this menu", " -a/--auto: set automatic trace mode, stopping the session if argument in us latency is hit", @@ -281,6 +281,7 @@ static void timerlat_top_usage(char *usage) " -D/--debug: print debug info", " -t/--trace[=file]: save the stopped trace to [file|timerlat_trace.txt]", " -e/--event : enable the in the trace instance, multiple -e are allowed", + " --trigger : enable a trace event trigger to the previous -e event", " -n/--nano: display data in nanoseconds", " -q/--quiet print only a summary at the end", " -P/--priority o:prio|r:prio|f:prio|d:runtime:period : set scheduling parameters", @@ -338,13 +339,14 @@ static struct timerlat_top_params {"stack", required_argument, 0, 's'}, {"thread", required_argument, 0, 'T'}, {"trace", optional_argument, 0, 't'}, + {"trigger", required_argument, 0, '0'}, {0, 0, 0, 0} }; /* getopt_long stores the option index here. */ int option_index = 0; - c = getopt_long(argc, argv, "a:c:d:De:hi:np:P:qs:t::T:", + c = getopt_long(argc, argv, "a:c:d:De:hi:np:P:qs:t::T:0:", long_options, &option_index); /* detect the end of the options. */ @@ -427,6 +429,17 @@ static struct timerlat_top_params else params->trace_output = "timerlat_trace.txt"; break; + case '0': /* trigger */ + if (params->events) { + retval = trace_event_add_trigger(params->events, optarg); + if (retval) { + err_msg("Error adding trigger %s\n", optarg); + exit(EXIT_FAILURE); + } + } else { + timerlat_top_usage("--trigger requires a previous -e\n"); + } + break; default: timerlat_top_usage("Invalid option"); } From 5487b6ce267bbafd399f3642062d974832d3eddc Mon Sep 17 00:00:00 2001 From: Daniel Bristot de Oliveira Date: Wed, 2 Mar 2022 20:01:34 +0100 Subject: [PATCH 59/67] rtla/trace: Add trace event filter helpers Add a set of helper functions to allow rtla tools to filter events in the trace instance. Link: https://lkml.kernel.org/r/12623b1684684549d53b90f4bf66fae44584fd14.1646247211.git.bristot@kernel.org Cc: Daniel Bristot de Oliveira Cc: Clark Williams Cc: Juri Lelli Cc: Jonathan Corbet Signed-off-by: Daniel Bristot de Oliveira Signed-off-by: Steven Rostedt (Google) --- tools/tracing/rtla/src/trace.c | 83 ++++++++++++++++++++++++++++++++++ tools/tracing/rtla/src/trace.h | 3 ++ 2 files changed, 86 insertions(+) diff --git a/tools/tracing/rtla/src/trace.c b/tools/tracing/rtla/src/trace.c index 7f8661b2724d..ef44bab0c404 100644 --- a/tools/tracing/rtla/src/trace.c +++ b/tools/tracing/rtla/src/trace.c @@ -204,6 +204,8 @@ static void trace_events_free(struct trace_events *events) tevent = tevent->next; + if (free_event->filter) + free(free_event->filter); if (free_event->trigger) free(free_event->trigger); free(free_event->system); @@ -237,6 +239,21 @@ struct trace_events *trace_event_alloc(const char *event_string) return tevent; } +/* + * trace_event_add_filter - record an event filter + */ +int trace_event_add_filter(struct trace_events *event, char *filter) +{ + if (event->filter) + free(event->filter); + + event->filter = strdup(filter); + if (!event->filter) + return 1; + + return 0; +} + /* * trace_event_add_trigger - record an event trigger action */ @@ -252,6 +269,33 @@ int trace_event_add_trigger(struct trace_events *event, char *trigger) return 0; } +/* + * trace_event_disable_filter - disable an event filter + */ +static void trace_event_disable_filter(struct trace_instance *instance, + struct trace_events *tevent) +{ + char filter[1024]; + int retval; + + if (!tevent->filter) + return; + + if (!tevent->filter_enabled) + return; + + debug_msg("Disabling %s:%s filter %s\n", tevent->system, + tevent->event ? : "*", tevent->filter); + + snprintf(filter, 1024, "!%s\n", tevent->filter); + + retval = tracefs_event_file_write(instance->inst, tevent->system, + tevent->event, "filter", filter); + if (retval < 0) + err_msg("Error disabling %s:%s filter %s\n", tevent->system, + tevent->event ? : "*", tevent->filter); +} + /* * trace_event_disable_trigger - disable an event trigger */ @@ -293,6 +337,7 @@ void trace_events_disable(struct trace_instance *instance, while (tevent) { debug_msg("Disabling event %s:%s\n", tevent->system, tevent->event ? : "*"); if (tevent->enabled) { + trace_event_disable_filter(instance, tevent); trace_event_disable_trigger(instance, tevent); tracefs_event_disable(instance->inst, tevent->system, tevent->event); } @@ -302,6 +347,41 @@ void trace_events_disable(struct trace_instance *instance, } } +/* + * trace_event_enable_filter - enable an event filter associated with an event + */ +static int trace_event_enable_filter(struct trace_instance *instance, + struct trace_events *tevent) +{ + char filter[1024]; + int retval; + + if (!tevent->filter) + return 0; + + if (!tevent->event) { + err_msg("Filter %s applies only for single events, not for all %s:* events\n", + tevent->filter, tevent->system); + return 1; + } + + snprintf(filter, 1024, "%s\n", tevent->filter); + + debug_msg("Enabling %s:%s filter %s\n", tevent->system, + tevent->event ? : "*", tevent->filter); + + retval = tracefs_event_file_write(instance->inst, tevent->system, + tevent->event, "filter", filter); + if (retval < 0) { + err_msg("Error enabling %s:%s filter %s\n", tevent->system, + tevent->event ? : "*", tevent->filter); + return 1; + } + + tevent->filter_enabled = 1; + return 0; +} + /* * trace_event_enable_trigger - enable an event trigger associated with an event */ @@ -356,6 +436,9 @@ int trace_events_enable(struct trace_instance *instance, return 1; } + retval = trace_event_enable_filter(instance, tevent); + if (retval) + return 1; retval = trace_event_enable_trigger(instance, tevent); if (retval) diff --git a/tools/tracing/rtla/src/trace.h b/tools/tracing/rtla/src/trace.h index 856b26d93064..51ad344c600b 100644 --- a/tools/tracing/rtla/src/trace.h +++ b/tools/tracing/rtla/src/trace.h @@ -6,8 +6,10 @@ struct trace_events { struct trace_events *next; char *system; char *event; + char *filter; char *trigger; char enabled; + char filter_enabled; char trigger_enabled; }; @@ -43,4 +45,5 @@ void trace_events_destroy(struct trace_instance *instance, int trace_events_enable(struct trace_instance *instance, struct trace_events *events); +int trace_event_add_filter(struct trace_events *event, char *filter); int trace_event_add_trigger(struct trace_events *event, char *trigger); From 44f3a37d1d3eb10770c7fec4eb89495d37957a26 Mon Sep 17 00:00:00 2001 From: Daniel Bristot de Oliveira Date: Wed, 2 Mar 2022 20:01:35 +0100 Subject: [PATCH 60/67] rtla: Add --filter support Add --filter option. This option enables a trace event filtering of the previous -e sys:event argument. This option is available for all current tools. Link: https://lkml.kernel.org/r/509d70b6348d3e5bcbf1f07ab725ce08d063149a.1646247211.git.bristot@kernel.org Cc: Daniel Bristot de Oliveira Cc: Clark Williams Cc: Juri Lelli Cc: Jonathan Corbet Signed-off-by: Daniel Bristot de Oliveira Signed-off-by: Steven Rostedt (Google) --- Documentation/tools/rtla/common_options.rst | 4 ++++ tools/tracing/rtla/src/osnoise_hist.c | 20 +++++++++++++++++--- tools/tracing/rtla/src/osnoise_top.c | 18 ++++++++++++++++-- tools/tracing/rtla/src/timerlat_hist.c | 20 +++++++++++++++++--- tools/tracing/rtla/src/timerlat_top.c | 18 ++++++++++++++++-- 5 files changed, 70 insertions(+), 10 deletions(-) diff --git a/Documentation/tools/rtla/common_options.rst b/Documentation/tools/rtla/common_options.rst index e5870b944334..afd45bae821f 100644 --- a/Documentation/tools/rtla/common_options.rst +++ b/Documentation/tools/rtla/common_options.rst @@ -18,6 +18,10 @@ Enable an event in the trace (**-t**) session. The argument can be a specific event, e.g., **-e** *sched:sched_switch*, or all events of a system group, e.g., **-e** *sched*. Multiple **-e** are allowed. It is only active when **-t** or **-a** are set. +**--filter** ** + + Filter the previous **-e** *sys:event* event with **. For further information about event filtering see https://www.kernel.org/doc/html/latest/trace/events.html#event-filtering. + **--trigger** ** Enable a trace event trigger to the previous **-e** *sys:event*. For further information about event trigger see https://www.kernel.org/doc/html/latest/trace/events.html#event-triggers. diff --git a/tools/tracing/rtla/src/osnoise_hist.c b/tools/tracing/rtla/src/osnoise_hist.c index 3e8f89b4f306..f86b5fb94efd 100644 --- a/tools/tracing/rtla/src/osnoise_hist.c +++ b/tools/tracing/rtla/src/osnoise_hist.c @@ -428,8 +428,9 @@ static void osnoise_hist_usage(char *usage) static const char * const msg[] = { "", " usage: rtla osnoise hist [-h] [-D] [-d s] [-a us] [-p us] [-r us] [-s us] [-S us] \\", - " [-T us] [-t[=file]] [-e sys[:event]] [--trigger ] [-c cpu-list] [-P priority] \\", - " [-b N] [-E N] [--no-header] [--no-summary] [--no-index] [--with-zeros]", + " [-T us] [-t[=file]] [-e sys[:event]] [--filter ] [--trigger ] \\", + " [-c cpu-list] [-P priority] [-b N] [-E N] [--no-header] [--no-summary] [--no-index] \\", + " [--with-zeros]", "", " -h/--help: print this menu", " -a/--auto: set automatic trace mode, stopping the session if argument in us sample is hit", @@ -443,6 +444,7 @@ static void osnoise_hist_usage(char *usage) " -D/--debug: print debug info", " -t/--trace[=file]: save the stopped trace to [file|osnoise_trace.txt]", " -e/--event : enable the in the trace instance, multiple -e are allowed", + " --filter : enable a trace event filter to the previous -e event", " --trigger : enable a trace event trigger to the previous -e event", " -b/--bucket-size N: set the histogram bucket size (default 1)", " -E/--entries N: set the number of entries of the histogram (default 256)", @@ -512,13 +514,14 @@ static struct osnoise_hist_params {"no-index", no_argument, 0, '2'}, {"with-zeros", no_argument, 0, '3'}, {"trigger", required_argument, 0, '4'}, + {"filter", required_argument, 0, '5'}, {0, 0, 0, 0} }; /* getopt_long stores the option index here. */ int option_index = 0; - c = getopt_long(argc, argv, "a:c:b:d:e:E:Dhp:P:r:s:S:t::T:01234:", + c = getopt_long(argc, argv, "a:c:b:d:e:E:Dhp:P:r:s:S:t::T:01234:5:", long_options, &option_index); /* detect the end of the options. */ @@ -632,6 +635,17 @@ static struct osnoise_hist_params osnoise_hist_usage("--trigger requires a previous -e\n"); } break; + case '5': /* filter */ + if (params->events) { + retval = trace_event_add_filter(params->events, optarg); + if (retval) { + err_msg("Error adding filter %s\n", optarg); + exit(EXIT_FAILURE); + } + } else { + osnoise_hist_usage("--filter requires a previous -e\n"); + } + break; default: osnoise_hist_usage("Invalid option"); } diff --git a/tools/tracing/rtla/src/osnoise_top.c b/tools/tracing/rtla/src/osnoise_top.c index d16c7ce3e9fa..c3d75ee456a5 100644 --- a/tools/tracing/rtla/src/osnoise_top.c +++ b/tools/tracing/rtla/src/osnoise_top.c @@ -247,7 +247,8 @@ void osnoise_top_usage(char *usage) static const char * const msg[] = { " usage: rtla osnoise [top] [-h] [-q] [-D] [-d s] [-a us] [-p us] [-r us] [-s us] [-S us] \\", - " [-T us] [-t[=file]] [-e sys[:event]] [--trigger ] [-c cpu-list] [-P priority]", + " [-T us] [-t[=file]] [-e sys[:event]] [--filter ] [--trigger ] \\", + " [-c cpu-list] [-P priority]", "", " -h/--help: print this menu", " -a/--auto: set automatic trace mode, stopping the session if argument in us sample is hit", @@ -261,6 +262,7 @@ void osnoise_top_usage(char *usage) " -D/--debug: print debug info", " -t/--trace[=file]: save the stopped trace to [file|osnoise_trace.txt]", " -e/--event : enable the in the trace instance, multiple -e are allowed", + " --filter : enable a trace event filter to the previous -e event", " --trigger : enable a trace event trigger to the previous -e event", " -q/--quiet print only a summary at the end", " -P/--priority o:prio|r:prio|f:prio|d:runtime:period : set scheduling parameters", @@ -314,13 +316,14 @@ struct osnoise_top_params *osnoise_top_parse_args(int argc, char **argv) {"threshold", required_argument, 0, 'T'}, {"trace", optional_argument, 0, 't'}, {"trigger", required_argument, 0, '0'}, + {"filter", required_argument, 0, '1'}, {0, 0, 0, 0} }; /* getopt_long stores the option index here. */ int option_index = 0; - c = getopt_long(argc, argv, "a:c:d:De:hp:P:qr:s:S:t::T:0:", + c = getopt_long(argc, argv, "a:c:d:De:hp:P:qr:s:S:t::T:0:1:", long_options, &option_index); /* Detect the end of the options. */ @@ -415,6 +418,17 @@ struct osnoise_top_params *osnoise_top_parse_args(int argc, char **argv) osnoise_top_usage("--trigger requires a previous -e\n"); } break; + case '1': /* filter */ + if (params->events) { + retval = trace_event_add_filter(params->events, optarg); + if (retval) { + err_msg("Error adding filter %s\n", optarg); + exit(EXIT_FAILURE); + } + } else { + osnoise_top_usage("--filter requires a previous -e\n"); + } + break; default: osnoise_top_usage("Invalid option"); } diff --git a/tools/tracing/rtla/src/timerlat_hist.c b/tools/tracing/rtla/src/timerlat_hist.c index 765b5a313bd2..8341f38fd0b1 100644 --- a/tools/tracing/rtla/src/timerlat_hist.c +++ b/tools/tracing/rtla/src/timerlat_hist.c @@ -430,8 +430,9 @@ static void timerlat_hist_usage(char *usage) char *msg[] = { "", " usage: [rtla] timerlat hist [-h] [-q] [-d s] [-D] [-n] [-a us] [-p us] [-i us] [-T us] [-s us] \\", - " [-t[=file]] [-e sys[:event]] [--trigger ] [-c cpu-list] [-P priority] [-E N] \\", - " [-b N] [--no-irq] [--no-thread] [--no-header] [--no-summary] [--no-index] [--with-zeros]", + " [-t[=file]] [-e sys[:event]] [--filter ] [--trigger ] [-c cpu-list] \\", + " [-P priority] [-E N] [-b N] [--no-irq] [--no-thread] [--no-header] [--no-summary] \\", + " [--no-index] [--with-zeros]", "", " -h/--help: print this menu", " -a/--auto: set automatic trace mode, stopping the session if argument in us latency is hit", @@ -444,6 +445,7 @@ static void timerlat_hist_usage(char *usage) " -D/--debug: print debug info", " -t/--trace[=file]: save the stopped trace to [file|timerlat_trace.txt]", " -e/--event : enable the in the trace instance, multiple -e are allowed", + " --filter : enable a trace event filter to the previous -e event", " --trigger : enable a trace event trigger to the previous -e event", " -n/--nano: display data in nanoseconds", " -b/--bucket-size N: set the histogram bucket size (default 1)", @@ -519,13 +521,14 @@ static struct timerlat_hist_params {"no-index", no_argument, 0, '4'}, {"with-zeros", no_argument, 0, '5'}, {"trigger", required_argument, 0, '6'}, + {"filter", required_argument, 0, '7'}, {0, 0, 0, 0} }; /* getopt_long stores the option index here. */ int option_index = 0; - c = getopt_long(argc, argv, "a:c:b:d:e:E:Dhi:np:P:s:t::T:0123456:", + c = getopt_long(argc, argv, "a:c:b:d:e:E:Dhi:np:P:s:t::T:0123456:7:", long_options, &option_index); /* detect the end of the options. */ @@ -645,6 +648,17 @@ static struct timerlat_hist_params timerlat_hist_usage("--trigger requires a previous -e\n"); } break; + case '7': /* filter */ + if (params->events) { + retval = trace_event_add_filter(params->events, optarg); + if (retval) { + err_msg("Error adding filter %s\n", optarg); + exit(EXIT_FAILURE); + } + } else { + timerlat_hist_usage("--filter requires a previous -e\n"); + } + break; default: timerlat_hist_usage("Invalid option"); } diff --git a/tools/tracing/rtla/src/timerlat_top.c b/tools/tracing/rtla/src/timerlat_top.c index 76927d4e0dac..9ce5a09664bc 100644 --- a/tools/tracing/rtla/src/timerlat_top.c +++ b/tools/tracing/rtla/src/timerlat_top.c @@ -268,7 +268,8 @@ static void timerlat_top_usage(char *usage) static const char *const msg[] = { "", " usage: rtla timerlat [top] [-h] [-q] [-a us] [-d s] [-D] [-n] [-p us] [-i us] [-T us] [-s us] \\", - " [[-t[=file]] [-e sys[:event]] [--trigger ] [-c cpu-list] [-P priority]", + " [[-t[=file]] [-e sys[:event]] [--filter ] [--trigger ] [-c cpu-list] \\", + " [-P priority]", "", " -h/--help: print this menu", " -a/--auto: set automatic trace mode, stopping the session if argument in us latency is hit", @@ -281,6 +282,7 @@ static void timerlat_top_usage(char *usage) " -D/--debug: print debug info", " -t/--trace[=file]: save the stopped trace to [file|timerlat_trace.txt]", " -e/--event : enable the in the trace instance, multiple -e are allowed", + " --filter : enable a trace event filter to the previous -e event", " --trigger : enable a trace event trigger to the previous -e event", " -n/--nano: display data in nanoseconds", " -q/--quiet print only a summary at the end", @@ -340,13 +342,14 @@ static struct timerlat_top_params {"thread", required_argument, 0, 'T'}, {"trace", optional_argument, 0, 't'}, {"trigger", required_argument, 0, '0'}, + {"filter", required_argument, 0, '1'}, {0, 0, 0, 0} }; /* getopt_long stores the option index here. */ int option_index = 0; - c = getopt_long(argc, argv, "a:c:d:De:hi:np:P:qs:t::T:0:", + c = getopt_long(argc, argv, "a:c:d:De:hi:np:P:qs:t::T:0:1:", long_options, &option_index); /* detect the end of the options. */ @@ -440,6 +443,17 @@ static struct timerlat_top_params timerlat_top_usage("--trigger requires a previous -e\n"); } break; + case '1': /* filter */ + if (params->events) { + retval = trace_event_add_filter(params->events, optarg); + if (retval) { + err_msg("Error adding filter %s\n", optarg); + exit(EXIT_FAILURE); + } + } else { + timerlat_top_usage("--filter requires a previous -e\n"); + } + break; default: timerlat_top_usage("Invalid option"); } From 761916fd02c2525139aca957b8a53fda1d8b3616 Mon Sep 17 00:00:00 2001 From: Daniel Bristot de Oliveira Date: Wed, 2 Mar 2022 20:01:36 +0100 Subject: [PATCH 61/67] rtla/trace: Save event histogram output to a file The hist: trigger generates a histogram in the file sys/event/hist. If the hist: trigger is used, automatically save the histogram output of the event sys:event in the sys_event_hist.txt file. Link: https://lkml.kernel.org/r/b5c906af31d4e022ffe87fb0848fac5c089087c8.1646247211.git.bristot@kernel.org Cc: Daniel Bristot de Oliveira Cc: Clark Williams Cc: Juri Lelli Cc: Jonathan Corbet Signed-off-by: Daniel Bristot de Oliveira Signed-off-by: Steven Rostedt (Google) --- Documentation/tools/rtla/common_options.rst | 10 +++- tools/tracing/rtla/src/trace.c | 53 +++++++++++++++++++++ 2 files changed, 62 insertions(+), 1 deletion(-) diff --git a/Documentation/tools/rtla/common_options.rst b/Documentation/tools/rtla/common_options.rst index afd45bae821f..af76df6205d4 100644 --- a/Documentation/tools/rtla/common_options.rst +++ b/Documentation/tools/rtla/common_options.rst @@ -23,7 +23,15 @@ Filter the previous **-e** *sys:event* event with **. For further information about event filtering see https://www.kernel.org/doc/html/latest/trace/events.html#event-filtering. **--trigger** ** - Enable a trace event trigger to the previous **-e** *sys:event*. For further information about event trigger see https://www.kernel.org/doc/html/latest/trace/events.html#event-triggers. + Enable a trace event trigger to the previous **-e** *sys:event*. + If the *hist:* trigger is activated, the output histogram will be automatically saved to a file named *system_event_hist.txt*. + For example, the command: + + rtla -t -e osnoise:irq_noise --trigger="hist:key=desc,duration/1000:sort=desc,duration/1000:vals=hitcount" + + Will automatically save the content of the histogram associated to *osnoise:irq_noise* event in *osnoise_irq_noise_hist.txt*. + + For further information about event trigger see https://www.kernel.org/doc/html/latest/trace/events.html#event-triggers. **-P**, **--priority** *o:prio|r:prio|f:prio|d:runtime:period* diff --git a/tools/tracing/rtla/src/trace.c b/tools/tracing/rtla/src/trace.c index ef44bab0c404..8249ec4d77cc 100644 --- a/tools/tracing/rtla/src/trace.c +++ b/tools/tracing/rtla/src/trace.c @@ -296,6 +296,57 @@ static void trace_event_disable_filter(struct trace_instance *instance, tevent->event ? : "*", tevent->filter); } +/* + * trace_event_save_hist - save the content of an event hist + * + * If the trigger is a hist: one, save the content of the hist file. + */ +static void trace_event_save_hist(struct trace_instance *instance, + struct trace_events *tevent) +{ + int retval, index, out_fd; + mode_t mode = 0644; + char path[1024]; + char *hist; + + if (!tevent) + return; + + /* trigger enables hist */ + if (!tevent->trigger) + return; + + /* is this a hist: trigger? */ + retval = strncmp(tevent->trigger, "hist:", strlen("hist:")); + if (retval) + return; + + snprintf(path, 1024, "%s_%s_hist.txt", tevent->system, tevent->event); + + printf(" Saving event %s:%s hist to %s\n", tevent->system, tevent->event, path); + + out_fd = creat(path, mode); + if (out_fd < 0) { + err_msg(" Failed to create %s output file\n", path); + return; + } + + hist = tracefs_event_file_read(instance->inst, tevent->system, tevent->event, "hist", 0); + if (!hist) { + err_msg(" Failed to read %s:%s hist file\n", tevent->system, tevent->event); + goto out_close; + } + + index = 0; + do { + index += write(out_fd, &hist[index], strlen(hist) - index); + } while (index < strlen(hist)); + + free(hist); +out_close: + close(out_fd); +} + /* * trace_event_disable_trigger - disable an event trigger */ @@ -314,6 +365,8 @@ static void trace_event_disable_trigger(struct trace_instance *instance, debug_msg("Disabling %s:%s trigger %s\n", tevent->system, tevent->event ? : "*", tevent->trigger); + trace_event_save_hist(instance, tevent); + snprintf(trigger, 1024, "!%s\n", tevent->trigger); retval = tracefs_event_file_write(instance->inst, tevent->system, From 28d2160cb1a18cca87a51345e7df47499447f5a4 Mon Sep 17 00:00:00 2001 From: Daniel Bristot de Oliveira Date: Wed, 2 Mar 2022 20:01:37 +0100 Subject: [PATCH 62/67] rtla: Check for trace off also in the trace instance With the addition of --trigger option, it is also possible to stop the trace from the -t tracing instance using the traceoff trigger. Make rtla tools to check if the trace is stopped also in the trace instance, stopping the execution of the tool. Link: https://lkml.kernel.org/r/59fc7c6f23dddd5c8b7ef1782cf3da51ea2ce0f5.1646247211.git.bristot@kernel.org Cc: Daniel Bristot de Oliveira Cc: Clark Williams Cc: Juri Lelli Cc: Jonathan Corbet Signed-off-by: Daniel Bristot de Oliveira Signed-off-by: Steven Rostedt (Google) --- tools/tracing/rtla/src/osnoise_hist.c | 4 ++-- tools/tracing/rtla/src/osnoise_top.c | 4 ++-- tools/tracing/rtla/src/timerlat_hist.c | 4 ++-- tools/tracing/rtla/src/timerlat_top.c | 4 ++-- tools/tracing/rtla/src/trace.c | 19 +++++++++++++++++++ tools/tracing/rtla/src/trace.h | 1 + 6 files changed, 28 insertions(+), 8 deletions(-) diff --git a/tools/tracing/rtla/src/osnoise_hist.c b/tools/tracing/rtla/src/osnoise_hist.c index f86b5fb94efd..b73b919bd6b4 100644 --- a/tools/tracing/rtla/src/osnoise_hist.c +++ b/tools/tracing/rtla/src/osnoise_hist.c @@ -848,7 +848,7 @@ int osnoise_hist_main(int argc, char *argv[]) goto out_hist; } - if (!tracefs_trace_is_on(trace->inst)) + if (trace_is_off(&tool->trace, &record->trace)) break; }; @@ -858,7 +858,7 @@ int osnoise_hist_main(int argc, char *argv[]) return_value = 0; - if (!tracefs_trace_is_on(trace->inst)) { + if (trace_is_off(&tool->trace, &record->trace)) { printf("rtla timelat hit stop tracing\n"); if (params->trace_output) { printf(" Saving trace to %s\n", params->trace_output); diff --git a/tools/tracing/rtla/src/osnoise_top.c b/tools/tracing/rtla/src/osnoise_top.c index c3d75ee456a5..fd29a4049322 100644 --- a/tools/tracing/rtla/src/osnoise_top.c +++ b/tools/tracing/rtla/src/osnoise_top.c @@ -629,7 +629,7 @@ int osnoise_top_main(int argc, char **argv) if (!params->quiet) osnoise_print_stats(params, tool); - if (!tracefs_trace_is_on(trace->inst)) + if (trace_is_off(&tool->trace, &record->trace)) break; } while (!stop_tracing); @@ -638,7 +638,7 @@ int osnoise_top_main(int argc, char **argv) return_value = 0; - if (!tracefs_trace_is_on(trace->inst)) { + if (trace_is_off(&tool->trace, &record->trace)) { printf("osnoise hit stop tracing\n"); if (params->trace_output) { printf(" Saving trace to %s\n", params->trace_output); diff --git a/tools/tracing/rtla/src/timerlat_hist.c b/tools/tracing/rtla/src/timerlat_hist.c index 8341f38fd0b1..17188ccb6e8b 100644 --- a/tools/tracing/rtla/src/timerlat_hist.c +++ b/tools/tracing/rtla/src/timerlat_hist.c @@ -861,7 +861,7 @@ int timerlat_hist_main(int argc, char *argv[]) goto out_hist; } - if (!tracefs_trace_is_on(trace->inst)) + if (trace_is_off(&tool->trace, &record->trace)) break; }; @@ -869,7 +869,7 @@ int timerlat_hist_main(int argc, char *argv[]) return_value = 0; - if (!tracefs_trace_is_on(trace->inst)) { + if (trace_is_off(&tool->trace, &record->trace)) { printf("rtla timelat hit stop tracing\n"); if (params->trace_output) { printf(" Saving trace to %s\n", params->trace_output); diff --git a/tools/tracing/rtla/src/timerlat_top.c b/tools/tracing/rtla/src/timerlat_top.c index 9ce5a09664bc..bf2a50350e61 100644 --- a/tools/tracing/rtla/src/timerlat_top.c +++ b/tools/tracing/rtla/src/timerlat_top.c @@ -655,7 +655,7 @@ int timerlat_top_main(int argc, char *argv[]) if (!params->quiet) timerlat_print_stats(params, top); - if (!tracefs_trace_is_on(trace->inst)) + if (trace_is_off(&top->trace, &record->trace)) break; }; @@ -664,7 +664,7 @@ int timerlat_top_main(int argc, char *argv[]) return_value = 0; - if (!tracefs_trace_is_on(trace->inst)) { + if (trace_is_off(&top->trace, &record->trace)) { printf("rtla timelat hit stop tracing\n"); if (params->trace_output) { printf(" Saving trace to %s\n", params->trace_output); diff --git a/tools/tracing/rtla/src/trace.c b/tools/tracing/rtla/src/trace.c index 8249ec4d77cc..5784c9f9e570 100644 --- a/tools/tracing/rtla/src/trace.c +++ b/tools/tracing/rtla/src/trace.c @@ -516,3 +516,22 @@ void trace_events_destroy(struct trace_instance *instance, trace_events_disable(instance, events); trace_events_free(events); } + +int trace_is_off(struct trace_instance *tool, struct trace_instance *trace) +{ + /* + * The tool instance is always present, it is the one used to collect + * data. + */ + if (!tracefs_trace_is_on(tool->inst)) + return 1; + + /* + * The trace instance is only enabled when -t is set. IOW, when the system + * is tracing. + */ + if (trace && !tracefs_trace_is_on(trace->inst)) + return 1; + + return 0; +} diff --git a/tools/tracing/rtla/src/trace.h b/tools/tracing/rtla/src/trace.h index 51ad344c600b..2e9a89a25615 100644 --- a/tools/tracing/rtla/src/trace.h +++ b/tools/tracing/rtla/src/trace.h @@ -47,3 +47,4 @@ int trace_events_enable(struct trace_instance *instance, int trace_event_add_filter(struct trace_events *event, char *filter); int trace_event_add_trigger(struct trace_events *event, char *trigger); +int trace_is_off(struct trace_instance *tool, struct trace_instance *trace); From 7d38c35167c58153e8b5bea839616d00e90564b9 Mon Sep 17 00:00:00 2001 From: Daniel Bristot de Oliveira Date: Wed, 2 Mar 2022 20:01:38 +0100 Subject: [PATCH 63/67] rtla/osnoise: Fix osnoise hist stop tracing message rtla osnoise hist is printing the following message when hitting stop tracing: printf("rtla timelat hit stop tracing\n"); which is obviosly wrong. s/timerlat/osnoise/ fixing the printf. Link: https://lkml.kernel.org/r/2b8f090556fe37b81d183b74ce271421f131c77b.1646247211.git.bristot@kernel.org Fixes: 829a6c0b5698 ("rtla/osnoise: Add the hist mode") Cc: Daniel Bristot de Oliveira Cc: Clark Williams Cc: Juri Lelli Cc: Jonathan Corbet Signed-off-by: Daniel Bristot de Oliveira Signed-off-by: Steven Rostedt (Google) --- tools/tracing/rtla/src/osnoise_hist.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/tracing/rtla/src/osnoise_hist.c b/tools/tracing/rtla/src/osnoise_hist.c index b73b919bd6b4..c47780fedbaf 100644 --- a/tools/tracing/rtla/src/osnoise_hist.c +++ b/tools/tracing/rtla/src/osnoise_hist.c @@ -859,7 +859,7 @@ int osnoise_hist_main(int argc, char *argv[]) return_value = 0; if (trace_is_off(&tool->trace, &record->trace)) { - printf("rtla timelat hit stop tracing\n"); + printf("rtla osnoise hit stop tracing\n"); if (params->trace_output) { printf(" Saving trace to %s\n", params->trace_output); save_trace_to_file(record->trace.inst, params->trace_output); From 7d0dc9576dc3817c483b408715e506c3e9f37bed Mon Sep 17 00:00:00 2001 From: Daniel Bristot de Oliveira Date: Wed, 2 Mar 2022 20:01:39 +0100 Subject: [PATCH 64/67] rtla/timerlat: Add --dma-latency option Add the --dma-latency to set /dev/cpu_dma_latency to the specified value, this aims to avoid having exit from idle states latencies that could be influencing the analysis. Link: https://lkml.kernel.org/r/72ddb0d913459f13217086dadafad88a7c46dd28.1646247211.git.bristot@kernel.org Cc: Daniel Bristot de Oliveira Cc: Clark Williams Cc: Juri Lelli Cc: Jonathan Corbet Signed-off-by: Daniel Bristot de Oliveira Signed-off-by: Steven Rostedt (Google) --- .../tools/rtla/common_timerlat_options.rst | 5 +++ tools/tracing/rtla/src/timerlat_hist.c | 28 ++++++++++++++-- tools/tracing/rtla/src/timerlat_top.c | 28 ++++++++++++++-- tools/tracing/rtla/src/utils.c | 33 +++++++++++++++++++ tools/tracing/rtla/src/utils.h | 1 + 5 files changed, 91 insertions(+), 4 deletions(-) diff --git a/Documentation/tools/rtla/common_timerlat_options.rst b/Documentation/tools/rtla/common_timerlat_options.rst index 14a24a121f5d..bacdea6de7a3 100644 --- a/Documentation/tools/rtla/common_timerlat_options.rst +++ b/Documentation/tools/rtla/common_timerlat_options.rst @@ -21,3 +21,8 @@ Save the stack trace at the *IRQ* if a *Thread* latency is higher than the argument in us. + +**--dma-latency** *us* + Set the /dev/cpu_dma_latency to *us*, aiming to bound exit from idle latencies. + *cyclictest* sets this value to *0* by default, use **--dma-latency** *0* to have + similar results. diff --git a/tools/tracing/rtla/src/timerlat_hist.c b/tools/tracing/rtla/src/timerlat_hist.c index 17188ccb6e8b..0f6ce80a198a 100644 --- a/tools/tracing/rtla/src/timerlat_hist.c +++ b/tools/tracing/rtla/src/timerlat_hist.c @@ -28,6 +28,7 @@ struct timerlat_hist_params { int output_divisor; int duration; int set_sched; + int dma_latency; struct sched_attr sched_param; struct trace_events *events; @@ -432,7 +433,7 @@ static void timerlat_hist_usage(char *usage) " usage: [rtla] timerlat hist [-h] [-q] [-d s] [-D] [-n] [-a us] [-p us] [-i us] [-T us] [-s us] \\", " [-t[=file]] [-e sys[:event]] [--filter ] [--trigger ] [-c cpu-list] \\", " [-P priority] [-E N] [-b N] [--no-irq] [--no-thread] [--no-header] [--no-summary] \\", - " [--no-index] [--with-zeros]", + " [--no-index] [--with-zeros] [--dma-latency us]", "", " -h/--help: print this menu", " -a/--auto: set automatic trace mode, stopping the session if argument in us latency is hit", @@ -456,6 +457,7 @@ static void timerlat_hist_usage(char *usage) " --no-summary: do not print summary", " --no-index: do not print index", " --with-zeros: print zero only entries", + " --dma-latency us: set /dev/cpu_dma_latency latency to reduce exit from idle latency", " -P/--priority o:prio|r:prio|f:prio|d:runtime:period : set scheduling parameters", " o:prio - use SCHED_OTHER with prio", " r:prio - use SCHED_RR with prio", @@ -492,6 +494,9 @@ static struct timerlat_hist_params if (!params) exit(1); + /* disabled by default */ + params->dma_latency = -1; + /* display data in microseconds */ params->output_divisor = 1000; params->bucket_size = 1; @@ -522,13 +527,14 @@ static struct timerlat_hist_params {"with-zeros", no_argument, 0, '5'}, {"trigger", required_argument, 0, '6'}, {"filter", required_argument, 0, '7'}, + {"dma-latency", required_argument, 0, '8'}, {0, 0, 0, 0} }; /* getopt_long stores the option index here. */ int option_index = 0; - c = getopt_long(argc, argv, "a:c:b:d:e:E:Dhi:np:P:s:t::T:0123456:7:", + c = getopt_long(argc, argv, "a:c:b:d:e:E:Dhi:np:P:s:t::T:0123456:7:8:", long_options, &option_index); /* detect the end of the options. */ @@ -659,6 +665,13 @@ static struct timerlat_hist_params timerlat_hist_usage("--filter requires a previous -e\n"); } break; + case '8': + params->dma_latency = get_llong_from_str(optarg); + if (params->dma_latency < 0 || params->dma_latency > 10000) { + err_msg("--dma-latency needs to be >= 0 and < 10000"); + exit(EXIT_FAILURE); + } + break; default: timerlat_hist_usage("Invalid option"); } @@ -791,6 +804,7 @@ int timerlat_hist_main(int argc, char *argv[]) struct osnoise_tool *record = NULL; struct osnoise_tool *tool = NULL; struct trace_instance *trace; + int dma_latency_fd = -1; int return_value = 1; int retval; @@ -826,6 +840,14 @@ int timerlat_hist_main(int argc, char *argv[]) } } + if (params->dma_latency >= 0) { + dma_latency_fd = set_cpu_dma_latency(params->dma_latency); + if (dma_latency_fd < 0) { + err_msg("Could not set /dev/cpu_dma_latency.\n"); + goto out_hist; + } + } + trace_instance_start(trace); if (params->trace_output) { @@ -878,6 +900,8 @@ int timerlat_hist_main(int argc, char *argv[]) } out_hist: + if (dma_latency_fd >= 0) + close(dma_latency_fd); trace_events_destroy(&record->trace, params->events); params->events = NULL; timerlat_free_histogram(tool->data); diff --git a/tools/tracing/rtla/src/timerlat_top.c b/tools/tracing/rtla/src/timerlat_top.c index bf2a50350e61..53f4cdfd395e 100644 --- a/tools/tracing/rtla/src/timerlat_top.c +++ b/tools/tracing/rtla/src/timerlat_top.c @@ -29,6 +29,7 @@ struct timerlat_top_params { int duration; int quiet; int set_sched; + int dma_latency; struct sched_attr sched_param; struct trace_events *events; }; @@ -269,7 +270,7 @@ static void timerlat_top_usage(char *usage) "", " usage: rtla timerlat [top] [-h] [-q] [-a us] [-d s] [-D] [-n] [-p us] [-i us] [-T us] [-s us] \\", " [[-t[=file]] [-e sys[:event]] [--filter ] [--trigger ] [-c cpu-list] \\", - " [-P priority]", + " [-P priority] [--dma-latency us]", "", " -h/--help: print this menu", " -a/--auto: set automatic trace mode, stopping the session if argument in us latency is hit", @@ -286,6 +287,7 @@ static void timerlat_top_usage(char *usage) " --trigger : enable a trace event trigger to the previous -e event", " -n/--nano: display data in nanoseconds", " -q/--quiet print only a summary at the end", + " --dma-latency us: set /dev/cpu_dma_latency latency to reduce exit from idle latency", " -P/--priority o:prio|r:prio|f:prio|d:runtime:period : set scheduling parameters", " o:prio - use SCHED_OTHER with prio", " r:prio - use SCHED_RR with prio", @@ -322,6 +324,9 @@ static struct timerlat_top_params if (!params) exit(1); + /* disabled by default */ + params->dma_latency = -1; + /* display data in microseconds */ params->output_divisor = 1000; @@ -343,13 +348,14 @@ static struct timerlat_top_params {"trace", optional_argument, 0, 't'}, {"trigger", required_argument, 0, '0'}, {"filter", required_argument, 0, '1'}, + {"dma-latency", required_argument, 0, '2'}, {0, 0, 0, 0} }; /* getopt_long stores the option index here. */ int option_index = 0; - c = getopt_long(argc, argv, "a:c:d:De:hi:np:P:qs:t::T:0:1:", + c = getopt_long(argc, argv, "a:c:d:De:hi:np:P:qs:t::T:0:1:2:", long_options, &option_index); /* detect the end of the options. */ @@ -454,6 +460,13 @@ static struct timerlat_top_params timerlat_top_usage("--filter requires a previous -e\n"); } break; + case '2': /* dma-latency */ + params->dma_latency = get_llong_from_str(optarg); + if (params->dma_latency < 0 || params->dma_latency > 10000) { + err_msg("--dma-latency needs to be >= 0 and < 10000"); + exit(EXIT_FAILURE); + } + break; default: timerlat_top_usage("Invalid option"); } @@ -582,6 +595,7 @@ int timerlat_top_main(int argc, char *argv[]) struct osnoise_tool *record = NULL; struct osnoise_tool *top = NULL; struct trace_instance *trace; + int dma_latency_fd = -1; int return_value = 1; int retval; @@ -617,6 +631,14 @@ int timerlat_top_main(int argc, char *argv[]) } } + if (params->dma_latency >= 0) { + dma_latency_fd = set_cpu_dma_latency(params->dma_latency); + if (dma_latency_fd < 0) { + err_msg("Could not set /dev/cpu_dma_latency.\n"); + goto out_top; + } + } + trace_instance_start(trace); if (params->trace_output) { @@ -673,6 +695,8 @@ int timerlat_top_main(int argc, char *argv[]) } out_top: + if (dma_latency_fd >= 0) + close(dma_latency_fd); trace_events_destroy(&record->trace, params->events); params->events = NULL; timerlat_free_top(top->data); diff --git a/tools/tracing/rtla/src/utils.c b/tools/tracing/rtla/src/utils.c index ffaf8ec84001..da2b590edaed 100644 --- a/tools/tracing/rtla/src/utils.c +++ b/tools/tracing/rtla/src/utils.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -431,3 +432,35 @@ int parse_prio(char *arg, struct sched_attr *sched_param) } return 0; } + +/* + * set_cpu_dma_latency - set the /dev/cpu_dma_latecy + * + * This is used to reduce the exit from idle latency. The value + * will be reset once the file descriptor of /dev/cpu_dma_latecy + * is closed. + * + * Return: the /dev/cpu_dma_latecy file descriptor + */ +int set_cpu_dma_latency(int32_t latency) +{ + int retval; + int fd; + + fd = open("/dev/cpu_dma_latency", O_RDWR); + if (fd < 0) { + err_msg("Error opening /dev/cpu_dma_latency\n"); + return -1; + } + + retval = write(fd, &latency, 4); + if (retval < 1) { + err_msg("Error setting /dev/cpu_dma_latency\n"); + close(fd); + return -1; + } + + debug_msg("Set /dev/cpu_dma_latency to %d\n", latency); + + return fd; +} diff --git a/tools/tracing/rtla/src/utils.h b/tools/tracing/rtla/src/utils.h index 9aa962319ca2..fa08e374870a 100644 --- a/tools/tracing/rtla/src/utils.h +++ b/tools/tracing/rtla/src/utils.h @@ -54,3 +54,4 @@ struct sched_attr { int parse_prio(char *arg, struct sched_attr *sched_param); int set_comm_sched_attr(const char *comm, struct sched_attr *attr); +int set_cpu_dma_latency(int32_t latency); From 75016ca3acd0de79868ef5b0694195fe05288ade Mon Sep 17 00:00:00 2001 From: Daniel Bristot de Oliveira Date: Wed, 2 Mar 2022 20:01:40 +0100 Subject: [PATCH 65/67] rtla: Tools main loop cleanup I probably started using "do {} while();", but changed all but osnoise_top to "while(){};" leaving the ; behind. Cleanup the main loop code, making all tools use "while() {}" Changcheng Deng reported this problem, as reported by coccicheck: Fix the following coccicheck review: ./tools/tracing/rtla/src/timerlat_hist.c: 800: 2-3: Unneeded semicolon ./tools/tracing/rtla/src/osnoise_hist.c: 776: 2-3: Unneeded semicolon ./tools/tracing/rtla/src/timerlat_top.c: 596: 2-3: Unneeded semicolon Link: https://lkml.kernel.org/r/3c1642110aa87c396f5da4a037dabc72dbb9c601.1646247211.git.bristot@kernel.org Cc: Daniel Bristot de Oliveira Cc: Clark Williams Cc: Juri Lelli Cc: Jonathan Corbet Reported-by: Changcheng Deng Reported-by: Zeal Robot Signed-off-by: Daniel Bristot de Oliveira Signed-off-by: Steven Rostedt (Google) --- tools/tracing/rtla/src/osnoise_hist.c | 2 +- tools/tracing/rtla/src/osnoise_top.c | 4 ++-- tools/tracing/rtla/src/timerlat_hist.c | 2 +- tools/tracing/rtla/src/timerlat_top.c | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tools/tracing/rtla/src/osnoise_hist.c b/tools/tracing/rtla/src/osnoise_hist.c index c47780fedbaf..b4380d45cacd 100644 --- a/tools/tracing/rtla/src/osnoise_hist.c +++ b/tools/tracing/rtla/src/osnoise_hist.c @@ -850,7 +850,7 @@ int osnoise_hist_main(int argc, char *argv[]) if (trace_is_off(&tool->trace, &record->trace)) break; - }; + } osnoise_read_trace_hist(tool); diff --git a/tools/tracing/rtla/src/osnoise_top.c b/tools/tracing/rtla/src/osnoise_top.c index fd29a4049322..72c2fd6ce005 100644 --- a/tools/tracing/rtla/src/osnoise_top.c +++ b/tools/tracing/rtla/src/osnoise_top.c @@ -612,7 +612,7 @@ int osnoise_top_main(int argc, char **argv) tool->start_time = time(NULL); osnoise_top_set_signals(params); - do { + while (!stop_tracing) { sleep(params->sleep_time); retval = tracefs_iterate_raw_events(trace->tep, @@ -632,7 +632,7 @@ int osnoise_top_main(int argc, char **argv) if (trace_is_off(&tool->trace, &record->trace)) break; - } while (!stop_tracing); + } osnoise_print_stats(params, tool); diff --git a/tools/tracing/rtla/src/timerlat_hist.c b/tools/tracing/rtla/src/timerlat_hist.c index 0f6ce80a198a..dc908126c610 100644 --- a/tools/tracing/rtla/src/timerlat_hist.c +++ b/tools/tracing/rtla/src/timerlat_hist.c @@ -885,7 +885,7 @@ int timerlat_hist_main(int argc, char *argv[]) if (trace_is_off(&tool->trace, &record->trace)) break; - }; + } timerlat_print_stats(params, tool); diff --git a/tools/tracing/rtla/src/timerlat_top.c b/tools/tracing/rtla/src/timerlat_top.c index 53f4cdfd395e..1f754c3df53f 100644 --- a/tools/tracing/rtla/src/timerlat_top.c +++ b/tools/tracing/rtla/src/timerlat_top.c @@ -680,7 +680,7 @@ int timerlat_top_main(int argc, char *argv[]) if (trace_is_off(&top->trace, &record->trace)) break; - }; + } timerlat_print_stats(params, top); From 795301d3c28996219d555023ac6863401b6076bc Mon Sep 17 00:00:00 2001 From: "Steven Rostedt (Google)" Date: Fri, 18 Mar 2022 15:34:32 -0400 Subject: [PATCH 66/67] tracing: Have type enum modifications copy the strings When an enum is used in the visible parts of a trace event that is exported to user space, the user space applications like perf and trace-cmd do not have a way to know what the value of the enum is. To solve this, at boot up (or module load) the printk formats are modified to replace the enum with their numeric value in the string output. Array fields of the event are defined by [] in the type portion of the format file so that the user space parsers can correctly parse the array into the appropriate size chunks. But in some trace events, an enum is used in defining the size of the array, which once again breaks the parsing of user space tooling. This was solved the same way as the print formats were, but it modified the type strings of the trace event. This caused crashes in some architectures because, as supposed to the print string, is a const string value. This was not detected on x86, as it appears that const strings are still writable (at least in boot up), but other architectures this is not the case, and writing to a const string will cause a kernel fault. To fix this, use kstrdup() to copy the type before modifying it. If the trace event is for the core kernel there's no need to free it because the string will be in use for the life of the machine being on line. For modules, create a link list to store all the strings being allocated for modules and when the module is removed, free them. Link: https://lore.kernel.org/all/yt9dr1706b4i.fsf@linux.ibm.com/ Link: https://lkml.kernel.org/r/20220318153432.3984b871@gandalf.local.home Tested-by: Marc Zyngier Tested-by: Sven Schnelle Reported-by: Sven Schnelle Fixes: b3bc8547d3be ("tracing: Have TRACE_DEFINE_ENUM affect trace event types as well") Signed-off-by: Steven Rostedt (Google) --- kernel/trace/trace_events.c | 62 ++++++++++++++++++++++++++++++++++++- 1 file changed, 61 insertions(+), 1 deletion(-) diff --git a/kernel/trace/trace_events.c b/kernel/trace/trace_events.c index ae9a3b8481f5..0d91152172c9 100644 --- a/kernel/trace/trace_events.c +++ b/kernel/trace/trace_events.c @@ -40,6 +40,14 @@ static LIST_HEAD(ftrace_generic_fields); static LIST_HEAD(ftrace_common_fields); static bool eventdir_initialized; +static LIST_HEAD(module_strings); + +struct module_string { + struct list_head next; + struct module *module; + char *str; +}; + #define GFP_TRACE (GFP_KERNEL | __GFP_ZERO) static struct kmem_cache *field_cachep; @@ -2633,14 +2641,40 @@ static void update_event_printk(struct trace_event_call *call, } } +static void add_str_to_module(struct module *module, char *str) +{ + struct module_string *modstr; + + modstr = kmalloc(sizeof(*modstr), GFP_KERNEL); + + /* + * If we failed to allocate memory here, then we'll just + * let the str memory leak when the module is removed. + * If this fails to allocate, there's worse problems than + * a leaked string on module removal. + */ + if (WARN_ON_ONCE(!modstr)) + return; + + modstr->module = module; + modstr->str = str; + + list_add(&modstr->next, &module_strings); +} + static void update_event_fields(struct trace_event_call *call, struct trace_eval_map *map) { struct ftrace_event_field *field; struct list_head *head; char *ptr; + char *str; int len = strlen(map->eval_string); + /* Dynamic events should never have field maps */ + if (WARN_ON_ONCE(call->flags & TRACE_EVENT_FL_DYNAMIC)) + return; + head = trace_get_fields(call); list_for_each_entry(field, head, link) { ptr = strchr(field->type, '['); @@ -2654,9 +2688,26 @@ static void update_event_fields(struct trace_event_call *call, if (strncmp(map->eval_string, ptr, len) != 0) continue; + str = kstrdup(field->type, GFP_KERNEL); + if (WARN_ON_ONCE(!str)) + return; + ptr = str + (ptr - field->type); ptr = eval_replace(ptr, map, len); /* enum/sizeof string smaller than value */ - WARN_ON_ONCE(!ptr); + if (WARN_ON_ONCE(!ptr)) { + kfree(str); + continue; + } + + /* + * If the event is part of a module, then we need to free the string + * when the module is removed. Otherwise, it will stay allocated + * until a reboot. + */ + if (call->module) + add_str_to_module(call->module, str); + + field->type = str; } } @@ -2883,6 +2934,7 @@ static void trace_module_add_events(struct module *mod) static void trace_module_remove_events(struct module *mod) { struct trace_event_call *call, *p; + struct module_string *modstr, *m; down_write(&trace_event_sem); list_for_each_entry_safe(call, p, &ftrace_events, list) { @@ -2891,6 +2943,14 @@ static void trace_module_remove_events(struct module *mod) if (call->module == mod) __trace_remove_event_call(call); } + /* Check for any strings allocade for this module */ + list_for_each_entry_safe(modstr, m, &module_strings, next) { + if (modstr->module != mod) + continue; + list_del(&modstr->next); + kfree(modstr->str); + kfree(modstr); + } up_write(&trace_event_sem); /* From 30d024b5058e0433914022f87d917a97a9527632 Mon Sep 17 00:00:00 2001 From: Herbert Xu Date: Wed, 23 Mar 2022 15:35:10 +1200 Subject: [PATCH 67/67] cacheflush.h: Add forward declaration for struct folio The struct folio is not declared in cacheflush.h so we need to provide a forward declaration as otherwise users of this header file may get warnings. Reported-by: Guenter Roeck Fixes: 522a0032af00 ("Add linux/cacheflush.h") Signed-off-by: Herbert Xu Reviewed-by: Matthew Wilcox (Oracle) Signed-off-by: Linus Torvalds --- include/linux/cacheflush.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/include/linux/cacheflush.h b/include/linux/cacheflush.h index fef8b607f97e..a6189d21f2ba 100644 --- a/include/linux/cacheflush.h +++ b/include/linux/cacheflush.h @@ -4,6 +4,8 @@ #include +struct folio; + #if ARCH_IMPLEMENTS_FLUSH_DCACHE_PAGE #ifndef ARCH_IMPLEMENTS_FLUSH_DCACHE_FOLIO void flush_dcache_folio(struct folio *folio);