user_events: Enable user processes to create and write to trace events
Subsystems (3)
v8 cover user_events: Enable user processes to create and write to trace events
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.
V2:
Fixed kmalloc vs kzalloc for register_page.
Renamed user_event_mmap to user_event_status.
Renamed user_event prefix from ue to u.
Added seq_* operations to user_event_status to enable cat output. Aligned field parsing to synth_events format (+ size specifier for custom/user types).
Added uapi header user_events.h to align kernel and user ABI definitions.
V3:
Updated ABI to handle single FD into many events via an int header. Added iovec/writev support to enable int header without payload changes. Updated bpf context to describe if data is coming from user, kernel or raw iovec.
Added flag support for registering event, allows forcing BPF to always recieve the direct iovecs for sensitive code paths that do not want copies.
V4:
Moved to struct user_reg for registering events via ioctl.
Added unit tests for ftrace, dyn_events and perf integration. Added print_fmt generation and proper dyn_events matching statements. Reduced time in preemption disabled paths.
Added documentation file.
Pre-fault in data when preemption is enabled and use no-fault copy in probes. Fixed MIPs missing PAGE_READONLY define.
V5:
Rebase to linux-trace for-next branch.
Added sample code into samples/user_events.
Switched to str_has_prefix in various locations.
Allow hex in array sizes and ensure reasonable sizes are used. Moved lifetime of name buffer when parsing to the caller for failure paths. Fixed documentation nits and index.
Ensure event isn't busy before freeing through dyn_events. Properly handle failure case for ftrace and perf in fault cases for buffers. Ensure write data is over min size and null terminated for dynamic arrays.
V6:
Fixed endian issue with dyn loc decoding (use u32).
Fixed size_t conversion warning on hexagon arch (min vs min_t). Handle cases for __get_str vs __get_rel_str in print_fmt generation. Add additional comments around various event member lifetimes. Reduced max field array size to 1K.
V7:
Acquire reg_mutex during release, ensure refs cannot change under any situation. Remove default n from Kconfig.
Move from static 0644 mode to TRACE_MODE_WRITE.
V8:
Squashed UABI header into ftrace minimal patch thread.
Moved pagefault_disable/enable into copy_nofault.
Moved to strscpy vs custom copy when getting array size from type. Made patch bisect friendly by ensuring tests are split from kernel code.
Beau Belgrave (12):
user_events: Add minimal support for trace_event into ftrace
user_events: Add print_fmt generation support for basic types
user_events: Handle matching arguments from dyn_events
user_events: Add basic perf and eBPF support
user_events: Add self-test for ftrace integration
user_events: Add self-test for dynamic_events integration
user_events: Add self-test for perf_event integration
user_events: Optimize writing events by only copying data once
user_events: Add documentation file
user_events: Add sample code for typical usage
user_events: Validate user payloads for size and null termination
user_events: Add self-test for validator boundaries
Documentation/trace/index.rst | 1 + Documentation/trace/user_events.rst | 195 ++ include/uapi/linux/user_events.h | 71 + kernel/trace/Kconfig | 14 + kernel/trace/Makefile | 1 + kernel/trace/trace_events_user.c | 1618 +++++++++++++++++ samples/user_events/Makefile | 5 + samples/user_events/example.c | 91 + tools/testing/selftests/user_events/Makefile | 9 + .../testing/selftests/user_events/dyn_test.c | 130 ++ .../selftests/user_events/ftrace_test.c | 452 +++++ .../testing/selftests/user_events/perf_test.c | 168 ++ tools/testing/selftests/user_events/settings | 1 + 13 files changed, 2756 insertions(+) create mode 100644 Documentation/trace/user_events.rst create mode 100644 include/uapi/linux/user_events.h create mode 100644 kernel/trace/trace_events_user.c create mode 100644 samples/user_events/Makefile create mode 100644 samples/user_events/example.c create mode 100644 tools/testing/selftests/user_events/Makefile create mode 100644 tools/testing/selftests/user_events/dyn_test.c create mode 100644 tools/testing/selftests/user_events/ftrace_test.c create mode 100644 tools/testing/selftests/user_events/perf_test.c create mode 100644 tools/testing/selftests/user_events/settings
base-commit: 67d4f6e3bf5dddced226fbf19704cdbbb0c98847
Signature · 1 line
2.17.1
PATCH v8 1/12 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.
--- include/uapi/linux/user_events.h | 71 ++ kernel/trace/Kconfig | 14 + kernel/trace/Makefile | 1 + kernel/trace/trace_events_user.c | 1188 ++++++++++++++++++++++++++++++ 4 files changed, 1274 insertions(+) create mode 100644 include/uapi/linux/user_events.h create mode 100644 kernel/trace/trace_events_user.c
include/uapi/linux/user_events.h new file +71 -0
@@ -0,0 +1,71 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +/* +* Copyright (c) 2021, Microsoft Corporation. +* +* Authors: +* Beau Belgrave <[email protected]> +*/ +#ifndef _UAPI_LINUX_USER_EVENTS_H +#define _UAPI_LINUX_USER_EVENTS_H + +#include <linux/types.h> +#include <linux/ioctl.h> + +#ifdef __KERNEL__ +#include <linux/uio.h> +#else +#include <sys/uio.h> +#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) + +struct user_reg { +__u32 size; +__u64 name_args; +__u32 status_index; +__u32 write_index; +}; + +#define DIAG_IOC_MAGIC '*' +#define DIAG_IOCSREG _IOWR(DIAG_IOC_MAGIC, 0, struct user_reg*) +#define DIAG_IOCSDEL _IOW(DIAG_IOC_MAGIC, 1, char*) + +enum { +USER_BPF_DATA_KERNEL , +USER_BPF_DATA_USER , +USER_BPF_DATA_ITER , +}; + +struct user_bpf_iter { +__u32 iov_offset; +__u32 nr_segs; +const struct iovec *iov; +}; + +struct user_bpf_context { +__u32 data_type; +__u32 data_len; +union { +void *kdata; +void *udata; +struct user_bpf_iter *iter; + }; +}; + +#endif /* _UAPI_LINUX_USER_EVENTS_H */
kernel/trace/Kconfig +14 -0
@@ -724,6 +724,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
kernel/trace/Makefile +1 -0
@@ -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
kernel/trace/trace_events_user.c new file +1188 -0
1,188 lines not on this page · raw message
Signature · 1 line
2.17.1
Masami Hiramatsu
maintainerRe: [PATCH v8 01/12] user_events: Add minimal support for trace_event into ftrace
Hi Beau,
Quoted 2 lines · On Thu, 16 Dec 2021 09:35:00 -0800 Beau Belgrave <[email protected]>…
Minimal support for interacting with dynamic events,
trace_eventand ftrace.
Since the cover mail is merged, could you describe what is the user_events here? :)
I have some comments below, but not so much.
Quoted 46 lines
Core outline of flow between user process, ioctl and
trace_eventAPIs.Signed-off-by Beau Belgrave <[email protected]>--- include/uapi/linux/user_events.h | 71 ++ kernel/trace/Kconfig | 14 + kernel/trace/Makefile | 1 + kernel/trace/trace_events_user.c | 1188 ++++++++++++++++++++++++++++++ 4 files changed, 1274 insertions(+) create mode 100644 include/uapi/linux/user_events.h create mode 100644 kernel/trace/trace_events_user.cinclude/uapi/linux/user_events.h new file +27
-0@@ -0,0 +1,71 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +/* +* Copyright (c) 2021, Microsoft Corporation. +* +* Authors: +* Beau Belgrave <[email protected]> +*/ +#ifndef _UAPI_LINUX_USER_EVENTS_H +#define _UAPI_LINUX_USER_EVENTS_H + +#include <linux/types.h> +#include <linux/ioctl.h> + +#ifdef __KERNEL__ +#include <linux/uio.h> +#else +#include <sys/uio.h> +#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 +
include/uapi/linux/user_events.h line 37
Quoted 10 lines
include/uapi/linux/user_events.h new file +10
-0+#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) +
Can you add a description of the user_reg (and each field) here?
include/uapi/linux/user_events.h line 40
Quoted 3 lines
include/uapi/linux/user_events.h new file +3
-0+struct user_reg { +__u32 size; +__u64 name_args;
BTW, this field name is a bit strange. It is indeed "name and arguments", but actually, it is the definition of the event, isn't it?
Quoted 19 lines
include/uapi/linux/user_events.h new file +19
-0+__u32 status_index; +__u32 write_index; +}; + +#define DIAG_IOC_MAGIC '*' +#define DIAG_IOCSREG _IOWR(DIAG_IOC_MAGIC, 0, struct user_reg*) +#define DIAG_IOCSDEL _IOW(DIAG_IOC_MAGIC, 1, char*) + +enum { +USER_BPF_DATA_KERNEL , +USER_BPF_DATA_USER , +USER_BPF_DATA_ITER , +}; + +struct user_bpf_iter { +__u32 iov_offset; +__u32 nr_segs; +const struct iovec *iov; +};
include/uapi/linux/user_events.h line 69
Quoted 10 lines
include/uapi/linux/user_events.h new file +10
-0+ +struct user_bpf_context { +__u32 data_type; +__u32 data_len; +union { +void *kdata; +void *udata; +struct user_bpf_iter *iter; + }; +};
Are those bpf related data structures passed from/to user?
[...]
include/uapi/linux/user_events.h
Quoted 5 lines
include/uapi/linux/user_events.h new file +5
-0+/* +* 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 a unsigned int after:
Please quote the words in the example, like
Example event named 'test' with a 20 char 'msg' field with an 'unsigned int id' after:
(is that correct?)
Quoted 10 lines
+ * 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.
+ */include/uapi/linux/user_events.h new file +4
-0+static int user_event_parse_cmd (char *raw_command,struct user_event **newuser) +{ +char *name = raw_command; +char *args =strpbrk (name," " );
include/uapi/linux/user_events.h
Quoted 10 lines
include/uapi/linux/user_events.h new file +10
-0+char *flags; + +if (args) + *args++ =0 ; + + flags =strpbrk (name,":" ); + +if (flags) + *flags++ =0 ; +
Just a nitpick. What about using strsep()?
args = raw_command;
flags = strsep(&args, " ");
name = strsep(&flags, ":");
Quoted 3 lines
include/uapi/linux/user_events.h new file +3
-0+return user_event_parse (name, args, flags, newuser); +} +
[...]
include/uapi/linux/user_events.h
Quoted 8 lines
include/uapi/linux/user_events.h new file +8
-0+ +static ssize_t user_status_read (struct file *file,char __user *ubuf, +size_t count,loff_t *ppos) +{ +/* +* Delay allocation of seq data until requested, most callers +* will never read the status file. They will only mmap. +*/
I think you don't need to do this optimization since this is not a hot path. And it causes strange behaviors. See below;
include/uapi/linux/user_events.h
Quoted 10 lines
include/uapi/linux/user_events.h new file +10
-0+if (file->private_data ==NULL ) { +int ret; + +if (*ppos !=0 ) +return -EINVAL ; + + ret =single_open (file, user_status_show,NULL ); + +if (ret) +return ret;
This seems strange returning failure of open(2) from read(2).
include/uapi/linux/user_events.h
Quoted 8 lines
include/uapi/linux/user_events.h new file +8
-0+ } + +return seq_read (file, ubuf, count, ppos); +} + +static loff_t user_status_seek (struct file *file,loff_t offset,int whence) +{ +if (file->private_data ==NULL )
For example, this means unless start reading we can not do seek. So, please make the code as usually that is, unless any special reason.
Quoted 9 lines
include/uapi/linux/user_events.h new file +9
-0+return 0 ; + +return seq_lseek (file, offset, whence); +} + +static int user_status_release (struct inode *node,struct file *file) +{ +if (file->private_data ==NULL ) +return 0 ;
include/uapi/linux/user_events.h
Quoted 10 lines
include/uapi/linux/user_events.h new file +10
-0+ +return single_release (node, file); +} + +static const struct file_operations user_status_fops = { + .mmap = user_status_mmap, + .read = user_status_read, + .llseek = user_status_seek, + .release = user_status_release, +};
Thank you,
Signature · 1 line
Masami Hiramatsu <[email protected]>
Re: [PATCH v8 01/12] user_events: Add minimal support for trace_event into ftrace
Quoted 11 lines · Masami Hiramatsu
Hi Beau,
Quoted 2 lines · On Thu, 16 Dec 2021 09:35:00 -0800 Beau Belgrave <[email protected]>…
Minimal support for interacting with dynamic events,
trace_eventand ftrace.Since the cover mail is merged, could you describe what is the
user_eventshere? :)
Yeah, would this basically be what I have in the cover letter without the history tracking?
Quoted 52 lines
I have some comments below, but not so much.
Quoted 50 lines
Core outline of flow between user process, ioctl and
trace_eventAPIs.Signed-off-by Beau Belgrave <[email protected]>--- include/uapi/linux/user_events.h | 71 ++ kernel/trace/Kconfig | 14 + kernel/trace/Makefile | 1 + kernel/trace/trace_events_user.c | 1188 ++++++++++++++++++++++++++++++ 4 files changed, 1274 insertions(+) create mode 100644 include/uapi/linux/user_events.h create mode 100644 kernel/trace/trace_events_user.cinclude/uapi/linux/user_events.h new file +31
-0@@ -0,0 +1,71 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +/* +* Copyright (c) 2021, Microsoft Corporation. +* +* Authors: +* Beau Belgrave <[email protected]> +*/ +#ifndef _UAPI_LINUX_USER_EVENTS_H +#define _UAPI_LINUX_USER_EVENTS_H + +#include <linux/types.h> +#include <linux/ioctl.h> + +#ifdef __KERNEL__ +#include <linux/uio.h> +#else +#include <sys/uio.h> +#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) +
Quoted 9 lines
include/uapi/linux/user_events.h line 37
Quoted 6 lines
include/uapi/linux/user_events.h new file +6
-0+/* 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) +Can you add a description of the
user_reg(and each field) here?
Sure thing.
Quoted 7 lines
include/uapi/linux/user_events.h line 40
Quoted 3 lines
include/uapi/linux/user_events.h new file +3
-0+struct user_reg { +__u32 size; +__u64 name_args;BTW, this field name is a bit strange. It is indeed "name and arguments", but actually, it is the definition of the event, isn't it?
It also includes flags, which are not part of the definition. I expect the number of flags to increase in the future. For example, enabling auto-deregistering of the event upon close if it's the last ref, etc.
Quoted 23 lines
Quoted 23 lines
include/uapi/linux/user_events.h new file +23
-0+__u32 status_index; +__u32 write_index; +}; + +#define DIAG_IOC_MAGIC '*' +#define DIAG_IOCSREG _IOWR(DIAG_IOC_MAGIC, 0, struct user_reg*) +#define DIAG_IOCSDEL _IOW(DIAG_IOC_MAGIC, 1, char*) + +enum { +USER_BPF_DATA_KERNEL , +USER_BPF_DATA_USER , +USER_BPF_DATA_ITER , +}; + +struct user_bpf_iter { +__u32 iov_offset; +__u32 nr_segs; +const struct iovec *iov; +}; + +struct user_bpf_context { +__u32 data_type; +__u32 data_len;
Quoted 9 lines
include/uapi/linux/user_events.h line 69
Quoted 6 lines
include/uapi/linux/user_events.h new file +6
-0+union { +void *kdata; +void *udata; +struct user_bpf_iter *iter; + }; +};Are those bpf related data structures passed from/to user?
They are passed to the BPF program, which requires to know their definitions when compiling in places such as libbpf. The owner of the structure in memory is always the kernel. The data the context points to can either be from the kernel or user. The data_type tells you which is the case so your BPF program can probe correctly.
Quoted 7 lines
[...]
Quoted 5 lines
+/*
+ * Parses a register command foruser_events
+ * Format: event_name[:FLAG1[,FLAG2...]] [field1[;field2...]]
+ *
+ * Example event named test with a 20 char msg field with a unsigned int after:
Quoted 6 lines
Please quote the words in the example, like
Example event named 'test' with a 20 char 'msg' field with an 'unsigned int id' after:
(is that correct?)
Think so, seems like unsigned int 'id' would be more appropriate here, the rest look accurate. I'll change it.
Quoted 14 lines
Quoted 14 lines
+ * 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.
+ */include/uapi/linux/user_events.h new file +8
-0+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 ;
Quoted 9 lines
include/uapi/linux/user_events.h
Quoted 6 lines
include/uapi/linux/user_events.h new file +6
-0+ + flags =strpbrk (name,":" ); + +if (flags) + *flags++ =0 ; +Just a nitpick. What about using
strsep()?
strsep() has a side effect of setting the first parameter to NULL if the delimiter is not found. This can lead to unintentional errors.
Quoted 4 lines
args =
raw_command;
flags =strsep(&args, " ");
name =strsep(&flags, ":");
For example, if there wasn't a space then args would now be null. Where as before only args would be null and name would be valid.
Quoted 6 lines
Quoted 3 lines
include/uapi/linux/user_events.h new file +3
-0+return user_event_parse (name, args, flags, newuser); +} +[...]
Quoted 12 lines
include/uapi/linux/user_events.h
Quoted 8 lines
include/uapi/linux/user_events.h new file +8
-0+ +static ssize_t user_status_read (struct file *file,char __user *ubuf, +size_t count,loff_t *ppos) +{ +/* +* Delay allocation of seq data until requested, most callers +* will never read the status file. They will only mmap. +*/ I think you don't need to do this optimization since this is not a hot path. And it causes strange behaviors. See below;
In all of our usage we won't read the file, we simply mmap in. It felt wasteful for the 99% case to allocate a seq_buf to just never be used. If the file is closed after mmap from the user side then the seq_buf is allocated but then freed, so not as big of a deal.
I'll try to see if the normal mmap pattern we use leaves allocations around. If not, then I'm fine having this optimization removed.
Quoted 4 lines
Quoted 4 lines
include/uapi/linux/user_events.h new file +4
-0+if (file->private_data ==NULL ) { +int ret; + +if (*ppos !=0 )
Quoted 9 lines
include/uapi/linux/user_events.h
Quoted 6 lines
include/uapi/linux/user_events.h new file +6
-0+return -EINVAL ; + + ret =single_open (file, user_status_show,NULL ); + +if (ret) +return ret;This seems strange returning failure of open(2) from read(2).
Yeah, too bad there wasn't a better place for it if we don't want extra memory per-status file.
Quoted 12 lines
include/uapi/linux/user_events.h
Quoted 8 lines
include/uapi/linux/user_events.h new file +8
-0+ } + +return seq_read (file, ubuf, count, ppos); +} + +static loff_t user_status_seek (struct file *file,loff_t offset,int whence) +{ +if (file->private_data ==NULL )For example, this means unless start reading we can not do seek. So, please make the code as usually that is, unless any special reason.
Sure, see above, I'll see if I can get this cleaned up.
Quoted 14 lines
Quoted 14 lines
include/uapi/linux/user_events.h new file +13
-0+return 0 ; + +return seq_lseek (file, offset, whence); +} + +static int user_status_release (struct inode *node,struct file *file) +{ +if (file->private_data ==NULL ) +return 0 ; + +return single_release (node, file); +} +
Quoted 13 lines
include/uapi/linux/user_events.h
Quoted 6 lines
include/uapi/linux/user_events.h new file +6
-0+static const struct file_operations user_status_fops = { + .mmap = user_status_mmap, + .read = user_status_read, + .llseek = user_status_seek, + .release = user_status_release, +};Thank you,
Signature · 1 line
Masami Hiramatsu <[email protected]>
Thanks,
-Beau
PATCH v8 2/12 user_events: Add print_fmt generation support for basic types
In reply to Beau BelgraveAddes 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.
--- kernel/trace/trace_events_user.c | 115 ++++++++++++++++++++++++++++++- 1 file changed, 113 insertions(+), 2 deletions(-)
kernel/trace/trace_events_user.c +113 -2
@@ -358,6 +358,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)
@@ -391,6 +499,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);
@@ -663,8 +772,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;
Signature · 1 line
2.17.1
Re: [PATCH v8 02/12] user_events: Add print_fmt generation support for basic types
Quoted 2 lines · On Thu, 16 Dec 2021 09:35:01 -0800 Beau Belgrave <[email protected]>…
Addes
print_fmtformat generation for basic types that are supported for user processes. Only supports sizes that are the same on 32 and 64 bit.
Is the last sentence for user_events itself, or only limiting the print_fmt but user_events supports it?
The code looks good to me.
Thank you,
Quoted 149 lines
Signed-off-by Beau Belgrave <[email protected]>--- kernel/trace/trace_events_user.c | 115 ++++++++++++++++++++++++++++++- 1 file changed, 113 insertions(+), 2 deletions(-)kernel/trace/trace_events_user.c +113
-2@@ -358,6 +358,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)@@ -391,6 +499,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);@@ -663,8 +772,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;Signature · 1 line
2.17.1
Signature · 1 line
Masami Hiramatsu <[email protected]>
Re: [PATCH v8 02/12] user_events: Add print_fmt generation support for basic types
Quoted 9 lines · Masami Hiramatsu
Quoted 2 lines · On Thu, 16 Dec 2021 09:35:01 -0800 Beau Belgrave <[email protected]>…
Addes
print_fmtformat generation for basic types that are supported for user processes. Only supports sizes that are the same on 32 and 64 bit.Is the last sentence for
user_eventsitself, or only limiting theprint_fmtbutuser_eventssupports it?
It's only for print_fmt to prevent incorrect decoding at that level, as we discussed previously.
Quoted 6 lines
The code looks good to me.
Acked-by Masami Hiramatsu <[email protected]>Thank you,
Thanks,
-Beau
PATCH v8 3/12 user_events: Handle matching arguments from dyn_events
In reply to Beau BelgraveEnsures that when dynamic events requests a match with arguments that they match what is in the user_event.
--- kernel/trace/trace_events_user.c | 77 +++++++++++++++++++++++++++++++- 1 file changed, 76 insertions(+), 1 deletion(-)
kernel/trace/trace_events_user.c +76 -1
@@ -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 256static char *register_page_data;
@@ -694,13 +695,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 = {
Signature · 1 line
2.17.1
Re: [PATCH v8 03/12] user_events: Handle matching arguments from dyn_events
Quoted 3 lines · On Thu, 16 Dec 2021 09:35:02 -0800 Beau Belgrave <[email protected]>…
Ensures that when dynamic events requests a match with arguments that they match what is in the
user_event.
Looks good to me.
Thank you,
Quoted 109 lines
Signed-off-by Beau Belgrave <[email protected]>--- kernel/trace/trace_events_user.c | 77 +++++++++++++++++++++++++++++++- 1 file changed, 76 insertions(+), 1 deletion(-)kernel/trace/trace_events_user.c +76
-1@@ -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 256static char *register_page_data;@@ -694,13 +695,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 = {Signature · 1 line
2.17.1
Signature · 1 line
Masami Hiramatsu <[email protected]>
PATCH v8 4/12 user_events: Add basic perf and eBPF support
In reply to Beau BelgraveAdds support to write out user_event data to perf_probe/perf files as well as to any attached eBPF program.
--- kernel/trace/trace_events_user.c | 72 +++++++++++++++++++++++++++++++- 1 file changed, 71 insertions(+), 1 deletion(-)
kernel/trace/trace_events_user.c +71 -1
@@ -550,6 +550,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. */
@@ -572,6 +616,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);
@@ -611,8 +659,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;
@@ -864,6 +931,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);
Signature · 1 line
2.17.1
Re: [PATCH v8 04/12] user_events: Add basic perf and eBPF support
Quoted 3 lines · On Thu, 16 Dec 2021 09:35:03 -0800 Beau Belgrave <[email protected]>…
Adds support to write out
user_eventdata to perf_probe/perf files as well as to any attached eBPF program.
Looks good to me.
Thanks!
Quoted 113 lines
Signed-off-by Beau Belgrave <[email protected]>--- kernel/trace/trace_events_user.c | 72 +++++++++++++++++++++++++++++++- 1 file changed, 71 insertions(+), 1 deletion(-)kernel/trace/trace_events_user.c +71
-1@@ -550,6 +550,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. */ @@ -572,6 +616,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);@@ -611,8 +659,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;@@ -864,6 +931,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);Signature · 1 line
2.17.1
Signature · 1 line
Masami Hiramatsu <[email protected]>
PATCH v8 5/12 user_events: Add self-test for ftrace integration
In reply to Beau BelgraveTests basic functionality of registering/deregistering, status and writing data out via ftrace mechanisms within user_events.
--- 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
tools/testing/selftests/user_events/Makefile new file +9 -0
@@ -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
tools/testing/selftests/user_events/ftrace_test.c new file +387 -0
387 lines not on this page · raw message
tools/testing/selftests/user_events/settings new file +1 -0
@@ -0,0 +1 @@ +timeout=90
Signature · 1 line
2.17.1
PATCH v8 6/12 user_events: Add self-test for dynamic_events integration
In reply to Beau BelgraveTests matching deletes, creation of basic and complex types. Ensures common patterns work correctly when interacting with dynamic_events file.
--- 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
tools/testing/selftests/user_events/Makefile +1 -1
@@ -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
tools/testing/selftests/user_events/dyn_test.c new file +130 -0
130 lines not on this page · raw message
Signature · 1 line
2.17.1
PATCH v8 7/12 user_events: Add self-test for perf_event integration
In reply to Beau BelgraveTests perf can be attached to and written out correctly. Ensures attach updates status bits in user programs.
--- 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
tools/testing/selftests/user_events/Makefile +1 -1
@@ -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
tools/testing/selftests/user_events/perf_test.c new file +168 -0
168 lines not on this page · raw message
Signature · 1 line
2.17.1
PATCH v8 8/12 user_events: Optimize writing events by only copying data once
In reply to Beau BelgravePass 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.
--- kernel/trace/trace_events_user.c | 113 +++++++++++++++++++++++-------- 1 file changed, 85 insertions(+), 28 deletions(-)
kernel/trace/trace_events_user.c +85 -28
@@ -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;
@@ -524,7 +540,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;
@@ -540,41 +556,85 @@ 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); +if (unlikely (!copy_nofault (entry +1 , i->count, i))) { +__trace_event_discard_commit (event_buffer.buffer, + event_buffer.event); +return ; + }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),
@@ -585,7 +645,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,
@@ -1018,16 +1081,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 (iov_iter_fault_in_readable (i, i->count))) +return -EFAULT ;rcu_read_lock_sched ();
@@ -1035,15 +1093,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;
Signature · 1 line
2.17.1
Re: [PATCH v8 08/12] user_events: Optimize writing events by only copying data once
Quoted 3 lines · On Thu, 16 Dec 2021 09:35:07 -0800 Beau Belgrave <[email protected]>…
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.
This looks good to me, except for 1 nitpick. See below.
Quoted 68 lines
Signed-off-by Beau Belgrave <[email protected]>--- kernel/trace/trace_events_user.c | 113 +++++++++++++++++++++++-------- 1 file changed, 85 insertions(+), 28 deletions(-)kernel/trace/trace_events_user.c +20
-4@@ -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;@@ -524,7 +540,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;@@ -540,41 +556,85 @@ 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))
kernel/trace/trace_events_user.c static void user_event_ftrace(struct user_event *user, void *data, u32 datalen, · line 570
Quoted 10 lines
kernel/trace/trace_events_user.c +5
-1return ; -memcpy (entry +1 , data, datalen); +if (unlikely (!copy_nofault (entry +1 , i->count, i))) { +__trace_event_discard_commit (event_buffer.buffer, + event_buffer.event); +return ; + }trace_event_buffer_commit (&event_buffer);
Will this be
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);
?
Thank you,
Quoted 134 lines
kernel/trace/trace_events_user.c +60
-23}#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),@@ -585,7 +645,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,@@ -1018,16 +1081,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 (iov_iter_fault_in_readable (i, i->count))) +return -EFAULT ;rcu_read_lock_sched ();@@ -1035,15 +1093,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;Signature · 1 line
2.17.1
Signature · 1 line
Masami Hiramatsu <[email protected]>
Re: [PATCH v8 08/12] user_events: Optimize writing events by only copying data once
Quoted 25 lines · Masami Hiramatsu
Quoted 3 lines · On Thu, 16 Dec 2021 09:35:07 -0800 Beau Belgrave <[email protected]>…
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.
This looks good to me, except for 1 nitpick. See below.
Reviewed-by Masami Hiramatsu <[email protected]>Quoted 14 lines
Signed-off-by Beau Belgrave <[email protected]>--- kernel/trace/trace_events_user.c | 113 +++++++++++++++++++++++-------- 1 file changed, 85 insertions(+), 28 deletions(-)kernel/trace/trace_events_user.c +0
-0@@ -41,6 +41,9 @@#define MAX_FIELD_ARRAY_SIZE 1024#define MAX_FIELD_ARG_NAME 256
[..]
Quoted 26 lines
Quoted 13 lines
kernel/trace/trace_events_user.c +2
-2-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;@@ -540,41 +556,85 @@ 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))kernel/trace/trace_events_user.c static void user_event_ftrace(struct user_event *user, void *data, u32 datalen, · line 570
Quoted 10 lines
kernel/trace/trace_events_user.c +5
-1return ; -memcpy (entry +1 , data, datalen); +if (unlikely (!copy_nofault (entry +1 , i->count, i))) { +__trace_event_discard_commit (event_buffer.buffer, + event_buffer.event); +return ; + }trace_event_buffer_commit (&event_buffer);Will this be
Quoted 7 lines
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);?
Sure, why not.
Thanks,
-Beau
Steven Rostedt
maintainerRe: [PATCH v8 08/12] user_events: Optimize writing events by only copying data once
In reply to Beau BelgraveQuoted 6 lines · On Thu, 16 Dec 2021 09:35:07 -0800 Beau Belgrave <[email protected]>…
kernel/trace/trace_events_user.c +1
-0@@ -1018,16 +1081,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;
kernel/trace/trace_events_user.c static ssize_t user_events_write_core(struct file *file, struct iov_iter *i) · line 1087
Quoted 10 lines
kernel/trace/trace_events_user.c +1
-8-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 (iov_iter_fault_in_readable (i, i->count)))
Can you rebase on my tree: ftrace/core ?
That's because upstream now has:
a6294593e8a12 ("iov_iter: Turn iov_iter_fault_in_readable into fault_in_iov_iter_readable")
And it looks like you need to call fault_in_iov_iter_readable().
-- Steve
Quoted 4 lines
kernel/trace/trace_events_user.c +1
-0+return -EFAULT ;rcu_read_lock_sched ();
Re: [PATCH v8 08/12] user_events: Optimize writing events by only copying data once
Quoted 22 lines · Steven Rostedt
Quoted 6 lines · On Thu, 16 Dec 2021 09:35:07 -0800 Beau Belgrave <[email protected]>…
kernel/trace/trace_events_user.c +1
-0@@ -1018,16 +1081,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;kernel/trace/trace_events_user.c static ssize_t user_events_write_core(struct file *file, struct iov_iter *i) · line 1087
Quoted 10 lines
kernel/trace/trace_events_user.c +1
-8-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 (iov_iter_fault_in_readable (i, i->count)))Can you rebase on my tree: ftrace/core ?
Quoted 8 lines
That's because upstream now has:
a6294593e8a12("iov_iter: Turniov_iter_fault_in_readableintofault_in_iov_iter_readable")And it looks like you need to call
fault_in_iov_iter_readable().-- Steve
Yep, will do.
Thanks,
-Beau
PATCH v8 9/12 user_events: Add documentation file
In reply to Beau BelgraveAdd a documentation file about user_events with example code, etc. explaining how it may be used.
--- Documentation/trace/index.rst | 1 + Documentation/trace/user_events.rst | 195 ++++++++++++++++++++++++++++ 2 files changed, 196 insertions(+) create mode 100644 Documentation/trace/user_events.rst
Documentation/trace/index.rst +1 -0
@@ -30,3 +30,4 @@ Linux Tracing Technologies stm sys-t coresight/index + user_events
Documentation/trace/user_events.rst new file +195 -0
195 lines not on this page · raw message
Signature · 1 line
2.17.1
Masami Hiramatsu
maintainerRe: [PATCH v8 09/12] user_events: Add documentation file
Hi Beau,
Quoted 55 lines · On Thu, 16 Dec 2021 09:35:08 -0800 Beau Belgrave <[email protected]>…
Add a documentation file about
user_eventswith example code, etc. explaining how it may be used.Signed-off-by Beau Belgrave <[email protected]>--- Documentation/trace/index.rst | 1 + Documentation/trace/user_events.rst | 195 ++++++++++++++++++++++++++++ 2 files changed, 196 insertions(+) create mode 100644 Documentation/trace/user_events.rstDocumentation/trace/index.rst +1
-0@@ -30,3 +30,4 @@ Linux Tracing Technologies stm sys-t coresight/index + user_eventsDocumentation/trace/user_events.rst new file +30
-0@@ -0,0 +1,195 @@ +========================================= +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.
Documentation/trace/user_events.rst line 40
Quoted 10 lines
Documentation/trace/user_events.rst new file +10
-0+ +**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. +
Could you add the user_reg data structure here?
Documentation/trace/user_events.rst line 43
Quoted 3 lines
Documentation/trace/user_events.rst new file +3
-0+The struct user_reg requires two values, the first is the size of the structure +to ensure forward and backward compatibility. The second is the command string +to issue for registering.
This explanation may be a bit out of date?user_reg has 4 fields. 2 for input, 2 for output.
And could you add a section for DIAG_IOCSDEL?
Quoted 142 lines
Documentation/trace/user_events.rst new file +142
-0+ +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 + +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.* +
Documentation/trace/user_events.rst line 195
Quoted 10 lines
Documentation/trace/user_events.rst new file +10
-0+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.
Maybe tools/testing/selftests/user_events ?
Thank you,
Quoted 3 lines
Signature · 1 line
2.17.1
Signature · 1 line
Masami Hiramatsu <[email protected]>
Re: [PATCH v8 09/12] user_events: Add documentation file
Quoted 33 lines · Masami Hiramatsu
Hi Beau,
Quoted 28 lines · On Thu, 16 Dec 2021 09:35:08 -0800 Beau Belgrave <[email protected]>…
Add a documentation file about
user_eventswith example code, etc. explaining how it may be used.Signed-off-by Beau Belgrave <[email protected]>--- Documentation/trace/index.rst | 1 + Documentation/trace/user_events.rst | 195 ++++++++++++++++++++++++++++ 2 files changed, 196 insertions(+) create mode 100644 Documentation/trace/user_events.rstDocumentation/trace/index.rst +1
-0@@ -30,3 +30,4 @@ Linux Tracing Technologies stm sys-t coresight/index + user_eventsDocumentation/trace/user_events.rst new file +3
-0@@ -0,0 +1,195 @@ +========================================= +user_events: User-based Event Tracing +=========================================
[..]
Quoted 9 lines
Documentation/trace/user_events.rst line 40
Quoted 6 lines
Documentation/trace/user_events.rst new file +6
-0+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. +Could you add the
user_regdata structure here?
Sure thing.
Quoted 7 lines
Documentation/trace/user_events.rst line 43
Quoted 3 lines
Documentation/trace/user_events.rst new file +3
-0+The struct user_reg requires two values, the first is the size of the structure +to ensure forward and backward compatibility. The second is the command string +to issue for registering.This explanation may be a bit out of date?
user_reghas 4 fields. 2 for input, 2 for output.
Yeah, it only requires 2 inputs to work. I'll try to make this clearer.
Quoted 2 lines
And could you add a section for
DIAG_IOCSDEL?
Sure thing.
Quoted 8 lines
Quoted 8 lines
Documentation/trace/user_events.rst new file +8
-0+ +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* +
[..]
Quoted 6 lines
Documentation/trace/user_events.rst line 195
Quoted 3 lines
Documentation/trace/user_events.rst new file +3
-0+Example Code +------------ +See sample code in samples/user_events.Maybe
tools/testing/selftests/user_events?
Previously I was asked to put the sample code in samples/user_events, so it lives there.
Thanks,
-Beau
Steven Rostedt
maintainerRe: [PATCH v8 09/12] user_events: Add documentation file
Quoted 9 lines · On Mon, 3 Jan 2022 15:01:39 -0800 Beau Belgrave <[email protected]>…
Quoted 6 lines
Documentation/trace/user_events.rst line 195
Quoted 3 lines
Documentation/trace/user_events.rst new file +3
-0+Example Code +------------ +See sample code in samples/user_events.Maybe
tools/testing/selftests/user_events?Previously I was asked to put the sample code in
samples/user_events, so it lives there.
Yes, please keep the sample code in samples. Even if we have duplicate code in selftests, that should not be used for sample code.
-- Steve
PATCH v8 10/12 user_events: Add sample code for typical usage
In reply to Beau BelgraveAdd sample code for user_events typical usage to show how to register and monitor status, as well as to write out data.
--- 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
samples/user_events/Makefile new file +5 -0
@@ -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
samples/user_events/example.c new file +91 -0
91 lines not on this page · raw message
Signature · 1 line
2.17.1
Re: [PATCH v8 10/12] user_events: Add sample code for typical usage
Quoted 2 lines · On Thu, 16 Dec 2021 09:35:09 -0800 Beau Belgrave <[email protected]>…
Add sample code for
user_eventstypical usage to show how to register and monitor status, as well as to write out data.
Ah, here is the sample code. OK.
Thanks!
Quoted 120 lines
Signed-off-by Beau Belgrave <[email protected]>--- 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.csamples/user_events/Makefile new file +5
-0@@ -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.csamples/user_events/example.c new file +91
-0@@ -0,0 +1,91 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* +* Copyright (c) 2021, Microsoft Corporation. +* +* Authors: +* Beau Belgrave <[email protected]> +*/ + +#include <errno.h> +#include <sys/ioctl.h> +#include <sys/mman.h> +#include <fcntl.h> +#include <stdio.h> +#include <unistd.h> +#include <linux/user_events.h> + +/* 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 ; +}Signature · 1 line
2.17.1
Signature · 1 line
Masami Hiramatsu <[email protected]>
Steven Rostedt
maintainerRe: [PATCH v8 10/12] user_events: Add sample code for typical usage
Quoted 9 lines · On Thu, 23 Dec 2021 08:18:14 +0900 Masami Hiramatsu <[email protected]> wrote:
Quoted 2 lines · On Thu, 16 Dec 2021 09:35:09 -0800 Beau Belgrave <[email protected]>…
Add sample code for
user_eventstypical usage to show how to register and monitor status, as well as to write out data.Ah, here is the sample code. OK.
Reviewed-by Masami Hiramatsu <[email protected]>
Ah, probably should swap the patches around (makes it easier on the reviewers ;-)
That is, 10 should come before 9, so that the reference to samples exists when the document is added.
-- Steve
Re: [PATCH v8 10/12] user_events: Add sample code for typical usage
Quoted 13 lines · Steven Rostedt
Quoted 9 lines · On Thu, 23 Dec 2021 08:18:14 +0900 Masami Hiramatsu <[email protected]> wrote:
Quoted 2 lines · On Thu, 16 Dec 2021 09:35:09 -0800 Beau Belgrave <[email protected]>…
Add sample code for
user_eventstypical usage to show how to register and monitor status, as well as to write out data.Ah, here is the sample code. OK.
Reviewed-by Masami Hiramatsu <[email protected]>
Quoted 7 lines
Ah, probably should swap the patches around (makes it easier on the reviewers ;-)
That is, 10 should come before 9, so that the reference to samples exists when the document is added.
-- Steve
Sure thing.
Thanks,
-Beau
PATCH v8 11/12 user_events: Validate user payloads for size and null termination
In reply to Beau BelgraveAdd 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.
--- kernel/trace/trace_events_user.c | 147 +++++++++++++++++++++++++++---- 1 file changed, 132 insertions(+), 15 deletions(-)
kernel/trace/trace_events_user.c +132 -15
@@ -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);
@@ -214,6 +225,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;
@@ -229,13 +251,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;
@@ -245,6 +297,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 ; }
@@ -516,6 +574,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);
@@ -537,15 +596,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;
@@ -555,19 +648,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); -return ; - } +if (unlikely (!copy_nofault (entry +1 , i->count, i))) + 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
@@ -622,7 +721,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;
@@ -645,14 +744,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
@@ -967,6 +1073,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;
@@ -1015,6 +1122,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; }
@@ -1072,6 +1180,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;/*
@@ -1083,10 +1194,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 (iov_iter_fault_in_readable (i, i->count)))return -EFAULT ; + faulted = false; +rcu_read_lock_sched (); probe_func_ptr =rcu_dereference_sched (tp->funcs);
@@ -1096,11 +1210,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;
Signature · 1 line
2.17.1
Masami Hiramatsu
maintainerRe: [PATCH v8 11/12] user_events: Validate user payloads for size and null termination
Quoted 3 lines · On Thu, 16 Dec 2021 09:35:10 -0800 Beau Belgrave <[email protected]>…
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.
OK, looks good to me except a few nitpicks.
I added some comments below.
Quoted 28 lines
Signed-off-by Beau Belgrave <[email protected]>--- kernel/trace/trace_events_user.c | 147 +++++++++++++++++++++++++++---- 1 file changed, 132 insertions(+), 15 deletions(-)kernel/trace/trace_events_user.c +4
-0@@ -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)
kernel/trace/trace_events_user.c struct user_event_refs { · line 96
Quoted 10 lines
kernel/trace/trace_events_user.c +8
-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);
Why don't you just return "int" value? ;-)
Quoted 107 lines
kernel/trace/trace_events_user.c +65
-0static int user_event_parse (char *name,char *args,char *flags,struct user_event **newuser);@@ -214,6 +225,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;@@ -229,13 +251,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;@@ -245,6 +297,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 ; }@@ -516,6 +574,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);@@ -537,15 +596,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);
kernel/trace/trace_events_user.c static struct user_event *find_user_event(char *name, u32 *outkey) · line 625
Quoted 10 lines
kernel/trace/trace_events_user.c +10
-0+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 ))
As we discussed in the previous version, isn't it '\0' ? (just a style comment)
Quoted 28 lines
kernel/trace/trace_events_user.c +9
-3+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;@@ -555,19 +648,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);
kernel/trace/trace_events_user.c static void user_event_ftrace(struct user_event *user, struct iov_iter *i, · line 657
Quoted 10 lines
kernel/trace/trace_events_user.c +2
-5if (unlikely (!entry))return ; -if (unlikely (!copy_nofault (entry +1 , i->count, i))) { -__trace_event_discard_commit (event_buffer.buffer, - event_buffer.event); -return ; - } +if (unlikely (!copy_nofault (entry +1 , i->count, i))) + goto discard;
OK, this is a fault error.
kernel/trace/trace_events_user.c static void user_event_ftrace(struct user_event *user, struct iov_iter *i, · line 661
Quoted 4 lines
kernel/trace/trace_events_user.c +4
-0+ +if (!list_empty (&user->validators) && +unlikely (user_event_validate (user, entry, size))) + goto discard;
But this maybe an invalid parameter error.
Thank you,
Quoted 105 lines
kernel/trace/trace_events_user.c +30
-6trace_event_buffer_commit (&event_buffer); + +return ; +discard: + *faulted = true; +__trace_event_discard_commit (event_buffer.buffer, + event_buffer.event); }#ifdef CONFIG_PERF_EVENTS @@ -622,7 +721,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;@@ -645,14 +744,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 @@ -967,6 +1073,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;@@ -1015,6 +1122,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; }@@ -1072,6 +1180,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;/* @@ -1083,10 +1194,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 (iov_iter_fault_in_readable (i, i->count)))return -EFAULT ; + faulted = false; +rcu_read_lock_sched (); probe_func_ptr =rcu_dereference_sched (tp->funcs);@@ -1096,11 +1210,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;Signature · 1 line
2.17.1
Signature · 1 line
Masami Hiramatsu <[email protected]>
Re: [PATCH v8 11/12] user_events: Validate user payloads for size and null termination
Quoted 39 lines · Masami Hiramatsu
Quoted 3 lines · On Thu, 16 Dec 2021 09:35:10 -0800 Beau Belgrave <[email protected]>…
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.
OK, looks good to me except a few nitpicks.
Reveiewed-by Masami Hiramatsu <[email protected]>I added some comments below.
Quoted 26 lines
Signed-off-by Beau Belgrave <[email protected]>--- kernel/trace/trace_events_user.c | 147 +++++++++++++++++++++++++++---- 1 file changed, 132 insertions(+), 15 deletions(-)kernel/trace/trace_events_user.c +2
-0@@ -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[]; };
Quoted 15 lines
kernel/trace/trace_events_user.c struct user_event_refs { · line 96
Quoted 12 lines
kernel/trace/trace_events_user.c +10
-1+#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);Why don't you just return "int" value? ;-)
There can be more than one callback attached per-probe, and in all cases where a return value is needed is for a faulted (or would have faulted) case. This allows less branches when data is being traced/logged as the return value does not need to be checked (nor should it short circuit other probes that are attached).
Quoted 7 lines
Quoted 7 lines
kernel/trace/trace_events_user.c +0
-0static int user_event_parse (char *name,char *args,char *flags,struct user_event **newuser);@@ -214,6 +225,17 @@ static int user_field_size(const char *type)return -EINVAL ; }
[..]
Quoted 22 lines
Quoted 22 lines
kernel/trace/trace_events_user.c +22
-0+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; +
Quoted 9 lines
kernel/trace/trace_events_user.c static struct user_event *find_user_event(char *name, u32 *outkey) · line 625
Quoted 5 lines
kernel/trace/trace_events_user.c +5
-0+if (unlikely (pos > end)) +return -EFAULT ; + +if (likely (validator->flags &VALIDATOR_ENSURE_NULL )) +if (unlikely (*(char *)(pos -1 ) !=0 ))As we discussed in the previous version, isn't it '\0' ? (just a style comment)
Sure, there are a few dangling around that I missed. I'll fix them.
Quoted 41 lines
Quoted 28 lines
kernel/trace/trace_events_user.c +9
-3+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;@@ -555,19 +648,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);kernel/trace/trace_events_user.c static void user_event_ftrace(struct user_event *user, struct iov_iter *i, · line 657
Quoted 10 lines
kernel/trace/trace_events_user.c +2
-5if (unlikely (!entry))return ; -if (unlikely (!copy_nofault (entry +1 , i->count, i))) { -__trace_event_discard_commit (event_buffer.buffer, - event_buffer.event); -return ; - } +if (unlikely (!copy_nofault (entry +1 , i->count, i))) + goto discard;OK, this is a fault error.
Quoted 7 lines
kernel/trace/trace_events_user.c static void user_event_ftrace(struct user_event *user, struct iov_iter *i, · line 661
Quoted 4 lines
kernel/trace/trace_events_user.c +4
-0+ +if (!list_empty (&user->validators) && +unlikely (user_event_validate (user, entry, size))) + goto discard;But this maybe an invalid parameter error.
Yes, but it has to be an invalid parameter that would have caused a possible fault in a worse place. In my mind, I still treat it as a fault case whether the user did it intentionally or not :)
Thanks,
-Beau
Masami Hiramatsu
maintainerRe: [PATCH v8 11/12] user_events: Validate user payloads for size and null termination
Hi Beau,
On Mon, 3 Jan 2022 10:53:08 -0800
Beau Belgrave <[email protected]> wrote:
[...]
Quoted 7 lines
Quoted 6 lines
kernel/trace/trace_events_user.c struct user_event_refs { · line 96
Quoted 3 lines
typedef void (
*user_event_func_t) (structuser_event*user, structiov_iter*i,kernel/trace/trace_events_user.c +1
-1- void *tpdata); + void *tpdata, bool *faulted);Why don't you just return "int" value? ;-)
Quoted 5 lines
There can be more than one callback attached per-probe, and in all cases where a return value is needed is for a faulted (or would have faulted) case. This allows less branches when data is being traced/logged as the return value does not need to be checked (nor should it short circuit other probes that are attached).
Would you mean overwriting the 'faulted' ? If so, you can do something like
faulted = 0;
for_each_user_event_func(user_event_func) {
faulted |= user_event_func();
}if (faulted)
...
But I think if one user_event_func() fails to access the user data, other funcs also fail. In this case, it is faster to skip others than repeating faults.
[...]
Quoted 29 lines
Quoted 21 lines
Quoted 8 lines
kernel/trace/trace_events_user.c +1
-2@@ -555,19 +648,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);kernel/trace/trace_events_user.c static void user_event_ftrace(struct user_event *user, struct iov_iter *i, · line 657
Quoted 10 lines
kernel/trace/trace_events_user.c +2
-5if (unlikely (!entry))return ; -if (unlikely (!copy_nofault (entry +1 , i->count, i))) { -__trace_event_discard_commit (event_buffer.buffer, - event_buffer.event); -return ; - } +if (unlikely (!copy_nofault (entry +1 , i->count, i))) + goto discard;OK, this is a fault error.
Quoted 7 lines
kernel/trace/trace_events_user.c static void user_event_ftrace(struct user_event *user, struct iov_iter *i, · line 661
Quoted 4 lines
kernel/trace/trace_events_user.c +4
-0+ +if (!list_empty (&user->validators) && +unlikely (user_event_validate (user, entry, size))) + goto discard;But this maybe an invalid parameter error.
Quoted 3 lines
Yes, but it has to be an invalid parameter that would have caused a possible fault in a worse place. In my mind, I still treat it as a fault case whether the user did it intentionally or not :)
OK, I got it.
Thank you,
Quoted 3 lines
Thanks,
-Beau
Signature · 1 line
Masami Hiramatsu <[email protected]>
Re: [PATCH v8 11/12] user_events: Validate user payloads for size and null termination
Quoted 19 lines · Masami Hiramatsu
Hi Beau,
On Mon, 3 Jan 2022 10:53:08 -0800
Beau Belgrave <[email protected]> wrote:[...]
Quoted 7 lines
Quoted 6 lines
kernel/trace/trace_events_user.c struct user_event_refs { · line 96
Quoted 3 lines
typedef void (
*user_event_func_t) (structuser_event*user, structiov_iter*i,kernel/trace/trace_events_user.c +1
-1- void *tpdata); + void *tpdata, bool *faulted);Why don't you just return "int" value? ;-)
Quoted 5 lines
There can be more than one callback attached per-probe, and in all cases where a return value is needed is for a faulted (or would have faulted) case. This allows less branches when data is being traced/logged as the return value does not need to be checked (nor should it short circuit other probes that are attached).
Quoted 9 lines
Would you mean overwriting the 'faulted' ? If so, you can do something like
faulted = 0;
for_each_user_event_func(user_event_func) { faulted |= user_event_func(); }if (faulted)
...
Yeah, could OR it in, I don't see a big difference though to be honest :)
Quoted 3 lines
But I think if one
user_event_func()fails to access the user data, other funcs also fail. In this case, it is faster to skip others than repeating faults.
eBPF will not fault when perf/ftrace could, at the very least we want to ensure that callbacks get a chance to see data even if it faulted elsewhere. This ensures that we are not blind to the fact it is happening at least when eBPF is being used. We cannot guarantee probe/callback order.
This has been a problem in the past for us, we've seen data disappear later to find out it was possibly due to a page fault occurring.
In later versions I would like to add internal tracepoints for these conditions (and some others) so we can further track when they occur.
Thanks,
-Beau
PATCH v8 12/12 user_events: Add self-test for validator boundaries
In reply to Beau BelgraveTests 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.
--- .../selftests/user_events/ftrace_test.c | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+)
tools/testing/selftests/user_events/ftrace_test.c +65 -0
@@ -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;
Signature · 1 line
2.17.1
Quoted 8 lines · * Beau Belgrave | 2021-12-16 09:34:59 [-0800]:
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 inuser_regstruct. Thestatus_indexof the struct is used to know the byte in the status page to check for that event. Thewrite_indexof 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 bywrite()or bywritev().
Hey Beau, a little bit late to the party. A few questions from my side: What are the exact weak points of USDT compared to User Events that stand in the way of further extend USDT (in a non-compatible way, sure, just as an different approach!)? The nice thing about USDT is that I can search for all possible probes of the system via "find / | readelf | ". Since they are listed in a dedicated ELF section (.note.stapsdt) - they are visible & transparent. I can also map a hierarchy/structure in Executable/DSO via clever choice of names. The big disadvantage of USDT is the lack of type information, but from a registration, explicit point of view, they are nice.
Or in other words: why not extends the USDT approach? Why not
u32 val = 23;
const char *garbage = "tracestring";
DYNAMIC_TRACE_PROBE2("foo:bar", val, u32, garbage, cstring);
Sure, the argument names, here "val" and "garbage" should also be saved. I also like the "just one additional header to the project to get things running" (#include "sdt.h"). Sure, a DYNAMIC_TRACE_IS_ACTIVE("foo:bar") would be great. But in fact we have never needed that in the past.
hgn
Quoted 21 lines · Hagen Paul Pfeifer
Quoted 8 lines · * Beau Belgrave | 2021-12-16 09:34:59 [-0800]:
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 inuser_regstruct. Thestatus_indexof the struct is used to know the byte in the status page to check for that event. Thewrite_indexof 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 bywrite()or bywritev().Hey Beau, a little bit late to the party. A few questions from my side: What are the exact weak points of USDT compared to User Events that stand in the way of further extend USDT (in a non-compatible way, sure, just as an different approach!)? The nice thing about USDT is that I can search for all possible probes of the system via "find / | readelf | ". Since they are listed in a dedicated ELF section (.note.stapsdt) - they are visible & transparent. I can also map a hierarchy/structure in Executable/DSO via clever choice of names. The big disadvantage of USDT is the lack of type information, but from a registration, explicit point of view, they are nice.
Quoted 7 lines
Or in other words: why not extends the USDT approach? Why not
u32 val = 23;
const char *garbage = "tracestring";
DYNAMIC_TRACE_PROBE2("foo:bar", val, u32, garbage, cstring);
We actually tried some USDT extension methods early on, by extending the .note.stapsdt sections and seeing how far we could get our definitions into that form.
There are a few problems when running in a highly container/CGROUP environment even if you can get our formats into stapsdt.
It costs a lot to transverse every ELF file on the machine to find all the notes. When profiling or tracing many containers, each cgroup's mount space must be entered and then tracked. Since these files are in different locations, they each need a separate probe definition, since the definitions/patches are tied to the location of the binary to patch.
As new cgroups come online, we would have to keep track of each new binary location and find probes that match their location. This becomes really hard to manage if for example we just want to always enable a specific event regardless of where it is on the filesystem. Events are limited to a max of 2^16 having many duplicate events in the system might start to approach that limit for high-core machines with many small cgroup isolations.
We run programs that are built on interpreted or JIT'd code (C#, javascript, etc.). These don't have great places to put a stap definition, since they aren't ELF files. I've seen approaches where temporary ELF files are generated, however, this costs a lot. Now we have even more temporarily files to go patch, meaning more events and more probe definitions (many of them in our case would be duplicates of the others).
In production environments we have them locked down heavily with both SELINUX and IPE enabled. This prevents us from patching user mode code on the fly, the typical perf probe calls fail here.
We typically want to know what events are available to us with very little overhead. Having programs register to a well known location already (trace_events, tracefs) I can easily see all the user events on the system by just doing ls on /sys/kernel/tracing/events/user_events. I can also see all their data formats and easily enable hist and filtering since these formats are known to the kernel.
In our testing uprobes are much more costly to the running program than the write syscall.
For managed code, as in java, code is moving around and are not always in static locations. The probe locations can change, etc. Calling from a managed location into a native one has performance implications as well when using a dynamic/temp elf stub approach.
We are actively using user_events to solve these problems in our environments that have previously seen high overheads to achieve the same results. Many times we cannot afford to miss any events, so live scanning for new ELF files doesn't work for us as the programs and cgroups are short lived.
Quoted 8 lines
Sure, the argument names, here "val" and "garbage" should also be saved. I also like the "just one additional header to the project to get things running" (#include "
sdt.h"). Sure, aDYNAMIC_TRACE_IS_ACTIVE("foo:bar")would be great. But in fact we have never needed that in the past.hgn
Thanks,
-Beau