user_events: Enable user processes to create and write to trace events
Subsystems (3)
v6 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.
Beau Belgrave (13):
user_events: Add UABI header for user access to user_events
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: Use __get_rel_str for relative string fields
Documentation/trace/index.rst | 1 + Documentation/trace/user_events.rst | 195 ++ include/uapi/linux/user_events.h | 71 + kernel/trace/Kconfig | 15 + kernel/trace/Makefile | 1 + kernel/trace/trace_events_user.c | 1604 +++++++++++++++++ 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 | 454 +++++ .../testing/selftests/user_events/perf_test.c | 168 ++ tools/testing/selftests/user_events/settings | 1 + 13 files changed, 2745 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 v6 1/13 user_events: Add UABI header for user access to user_events
Define the basic structs and ioctl commands that allow user processes to interact with user_events.
--- include/uapi/linux/user_events.h | 68 ++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 include/uapi/linux/user_events.h
include/uapi/linux/user_events.h new file +68 -0
@@ -0,0 +1,68 @@ +/* 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) + +/* 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 */
Signature · 1 line
2.17.1
PATCH v6 2/13 user_events: Add minimal support for trace_event into ftrace
In reply to Beau BelgraveMinimal support for interacting with dynamic events, trace_event and ftrace. Core outline of flow between user process, ioctl and trace_event APIs.
--- kernel/trace/Kconfig | 15 + kernel/trace/Makefile | 1 + kernel/trace/trace_events_user.c | 1192 ++++++++++++++++++++++++++++++ 3 files changed, 1208 insertions(+) create mode 100644 kernel/trace/trace_events_user.c
kernel/trace/Kconfig +15 -0
@@ -724,6 +724,21 @@ config SYNTH_EVENTS If in doubt, say N. +config USER_EVENTS + bool "User trace events" + select TRACING + select DYNAMIC_EVENTS + default n + 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 +1192 -0
1,192 lines not on this page · raw message
Signature · 1 line
2.17.1
Steven Rostedt
maintainerRe: [PATCH v6 02/13] user_events: Add minimal support for trace_event into ftrace
Quoted 17 lines · On Wed, 1 Dec 2021 10:25:04 -0800 Beau Belgrave <[email protected]>…
Minimal support for interacting with dynamic events,
trace_eventand ftrace. Core outline of flow between user process, ioctl andtrace_eventAPIs.Signed-off-by Beau Belgrave <[email protected]>--- kernel/trace/Kconfig | 15 + kernel/trace/Makefile | 1 + kernel/trace/trace_events_user.c | 1192 ++++++++++++++++++++++++++++++ 3 files changed, 1208 insertions(+) create mode 100644 kernel/trace/trace_events_user.ckernel/trace/Kconfig +0
-0
kernel/trace/Kconfig config SYNTH_EVENTS · line 731
Quoted 8 lines
kernel/trace/Kconfig +5
-0@@ -724,6 +724,21 @@ config SYNTH_EVENTS If in doubt, say N. +config USER_EVENTS + bool "User trace events" + select TRACING + select DYNAMIC_EVENTS + default n
default n is default, so you do not need to explicitly state that.
In other words, the above line is a nop.
Quoted 28 lines
kernel/trace/Kconfig +10
-0+ 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_CMPXCHGkernel/trace/Makefile +7
-0+/* + * Handles the final close of the file from user mode. + */ +static int user_events_release(struct inode *node, struct file *file) +{ + struct user_event_refs *refs; + struct user_event *user;
kernel/trace/Makefile
Quoted 10 lines
kernel/trace/Makefile +10
-0+ int i; + + /* + * refs is protected by RCU and could in theory change immediately + * before this call on another core. To ensure we read the latest + * version of refs we acquire the RCU read lock again. + */ + rcu_read_lock_sched(); + refs = rcu_dereference_sched(file->private_data); + rcu_read_unlock_sched();
This still bothers me. Can another CPU call an ioctl here?
user_events_ioctl_reg() {
user_events_ref_add() {
refs = rcu_dereference_protected(file->private_data, ..);
new_refs = kzalloc(size, GFP_KERNEL);
rcu_assign_pointer(file->private_data, new_refs);
if (refs)
kfree_rcu(refs, rcu);refs now freed.
kernel/trace/Makefile
Quoted 11 lines
kernel/trace/Makefile +11
-0+ + if (!refs) + goto out; + + /* + * Do not need RCU while enumerating the events that were used. + * The lifetime of refs has reached an end, it's tied to this file. + * The underlying user_events are ref counted, and cannot be freed. + * After this decrement, the user_events may be freed elsewhere. + */ + for (i = 0; i < refs->count; ++i) {
Fault on refs->count
??
Quoted 122 lines
kernel/trace/Makefile +122
-0+ user = refs->events[i]; + + if (user) + atomic_dec(&user->refcnt); + } + + kfree_rcu(refs, rcu); +out: + return 0; +} + +static const struct file_operations user_data_fops = { + .write = user_events_write, + .write_iter = user_events_write_iter, + .unlocked_ioctl = user_events_ioctl, + .release = user_events_release, +}; + +/* + * Maps the shared page into the user process for checking if event is enabled. + */ +static int user_status_mmap(struct file *file, struct vm_area_struct *vma) +{ + unsigned long size = vma->vm_end - vma->vm_start; + + if (size != MAX_EVENTS) + return -EINVAL; + + return remap_pfn_range(vma, vma->vm_start, + virt_to_phys(register_page_data) >> PAGE_SHIFT, + size, vm_get_page_prot(VM_READ)); +} + +static int user_status_show(struct seq_file *m, void *p) +{ + struct user_event *user; + char status; + int i, active = 0, busy = 0, flags; + + mutex_lock(®_mutex); + + hash_for_each(register_table, i, user, node) { + status = register_page_data[user->index]; + flags = user->flags; + + seq_printf(m, "%d:%s", user->index, EVENT_NAME(user)); + + if (flags != 0 || status != 0) + seq_puts(m, " #"); + + if (status != 0) { + seq_puts(m, " Used by"); + if (status & EVENT_STATUS_FTRACE) + seq_puts(m, " ftrace"); + if (status & EVENT_STATUS_PERF) + seq_puts(m, " perf"); + if (status & EVENT_STATUS_OTHER) + seq_puts(m, " other"); + busy++; + } + + if (flags & FLAG_BPF_ITER) + seq_puts(m, " FLAG:BPF_ITER"); + + seq_puts(m, "\n"); + active++; + } + + mutex_unlock(®_mutex); + + seq_puts(m, "\n"); + seq_printf(m, "Active: %d\n", active); + seq_printf(m, "Busy: %d\n", busy); + seq_printf(m, "Max: %ld\n", MAX_EVENTS); + + return 0; +} + +static 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. + */ + if (file->private_data == NULL) { + int ret; + + if (*ppos != 0) + return -EINVAL; + + ret = single_open(file, user_status_show, NULL); + + if (ret) + return ret; + } + + 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) + 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); +} + +static const struct file_operations user_status_fops = { + .mmap = user_status_mmap, + .read = user_status_read, + .llseek = user_status_seek, + .release = user_status_release, +};
kernel/trace/Makefile
Quoted 10 lines
kernel/trace/Makefile +10
-0+ +/* + * Creates a set of tracefs files to allow user mode interactions. + */ +static int create_user_tracefs(void) +{ + struct dentry *edata, *emmap; + + edata = tracefs_create_file("user_events_data", 0644, NULL, + NULL, &user_data_fops);
BTW, I now define:
TRACE_MODE_WRITE for files to be written to, and TRACE_MODE_READ for files that are read only.
And soon tracefs will honor the gid mount option to define what group all the tracefs files should belong to on mount.
-- Steve
Quoted 66 lines
kernel/trace/Makefile +66
-0+ + if (!edata) { + pr_warn("Could not create tracefs 'user_events_data' entry\n"); + goto err; + } + + /* mmap with MAP_SHARED requires writable fd */ + emmap = tracefs_create_file("user_events_status", 0644, NULL, + NULL, &user_status_fops); + + if (!emmap) { + tracefs_remove(edata); + pr_warn("Could not create tracefs 'user_events_mmap' entry\n"); + goto err; + } + + return 0; +err: + return -ENODEV; +} + +static void set_page_reservations(bool set) +{ + int page; + + for (page = 0; page < MAX_PAGES; ++page) { + void *addr = register_page_data + (PAGE_SIZE * page); + + if (set) + SetPageReserved(virt_to_page(addr)); + else + ClearPageReserved(virt_to_page(addr)); + } +} + +static int __init trace_events_user_init(void) +{ + int ret; + + /* Zero all bits beside 0 (which is reserved for failures) */ + bitmap_zero(page_bitmap, MAX_EVENTS); + set_bit(0, page_bitmap); + + register_page_data = kzalloc(MAX_EVENTS, GFP_KERNEL); + + if (!register_page_data) + return -ENOMEM; + + set_page_reservations(true); + + ret = create_user_tracefs(); + + if (ret) { + pr_warn("user_events could not register with tracefs\n"); + set_page_reservations(false); + kfree(register_page_data); + return ret; + } + + if (dyn_event_register(&user_event_dops)) + pr_warn("user_events could not register with dyn_events\n"); + + return 0; +} + +fs_initcall(trace_events_user_init);
Re: [PATCH v6 02/13] user_events: Add minimal support for trace_event into ftrace
Quoted 31 lines · Steven Rostedt
Quoted 17 lines · On Wed, 1 Dec 2021 10:25:04 -0800 Beau Belgrave <[email protected]>…
Minimal support for interacting with dynamic events,
trace_eventand ftrace. Core outline of flow between user process, ioctl andtrace_eventAPIs.Signed-off-by Beau Belgrave <[email protected]>--- kernel/trace/Kconfig | 15 + kernel/trace/Makefile | 1 + kernel/trace/trace_events_user.c | 1192 ++++++++++++++++++++++++++++++ 3 files changed, 1208 insertions(+) create mode 100644 kernel/trace/trace_events_user.ckernel/trace/Kconfig +0
-0kernel/trace/Kconfig config SYNTH_EVENTS · line 731
Quoted 8 lines
kernel/trace/Kconfig +5
-0@@ -724,6 +724,21 @@ config SYNTH_EVENTS If in doubt, say N. +config USER_EVENTS + bool "User trace events" + select TRACING + select DYNAMIC_EVENTS + default ndefault n is default, so you do not need to explicitly state that.
Quoted 2 lines
In other words, the above line is a nop.
Got it.
Quoted 32 lines
Quoted 32 lines
kernel/trace/Kconfig +10
-0+ 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_CMPXCHGkernel/trace/Makefile +11
-0+/* + * Handles the final close of the file from user mode. + */ +static int user_events_release(struct inode *node, struct file *file) +{ + struct user_event_refs *refs; + struct user_event *user; + int i; + + /* + * refs is protected by RCU and could in theory change immediately
Quoted 8 lines
kernel/trace/Makefile
Quoted 6 lines
kernel/trace/Makefile +6
-0+ * before this call on another core. To ensure we read the latest + * version of refs we acquire the RCU read lock again. + */ + rcu_read_lock_sched(); + refs = rcu_dereference_sched(file->private_data); + rcu_read_unlock_sched();This still bothers me. Can another CPU call an ioctl here?
Sorry :)
No, another CPU cannot call the ioctl on the file, since if another CPU had a reference to this file release couldn't be called.
user_events_release is only called when the final reference to the file has been closed, so there cannot be another ioctl pending, starting or finishing for this file at the time it is called.
The last user mode program to call close() on the file will end up invoking user_events_release.
The user_event_refs is only accessible via the file's private_data, which now has zero references when release is executing. This means the private_data can no longer change and the rcu deref ensures we have the latest version.
refs is per-file, so while there can be other ioctl's occurring for other files, they are completely different ref objects than the one being cleaned up in the release of the file, it's not shared outside of this file lifetime, which has now ended.
Quoted 11 lines
user_events_ioctl_reg() { user_events_ref_add() { refs = rcu_dereference_protected(file->private_data, ..); new_refs = kzalloc(size, GFP_KERNEL); rcu_assign_pointer(file->private_data, new_refs); if (refs) kfree_rcu(refs, rcu);refs now freed.
If user_events_ioctl is executing for that same file, user_events_release could not have been called due to the file being in use to issue the ioctl.
Quoted 14 lines
kernel/trace/Makefile
Quoted 11 lines
kernel/trace/Makefile +11
-0+ + if (!refs) + goto out; + + /* + * Do not need RCU while enumerating the events that were used. + * The lifetime of refs has reached an end, it's tied to this file. + * The underlying user_events are ref counted, and cannot be freed. + * After this decrement, the user_events may be freed elsewhere. + */ + for (i = 0; i < refs->count; ++i) {Fault on
refs->count
Quoted 1 line
??
refs after rcu_dereference is checked for null before accessing.
refs cannot be changed when release is being executed, since that would mean the ioctl ran without a file reference (not sure how that could happen).
This is why it's important that release get the latest version of refs, an ioctl could have JUST happened before the final close() in user mode, and if it jumped CPUs we could (in theory) get an old value. If we got an old value, then yes, the fault could occur.
This code uses the file ops release method as a final sync point to clean up everything for that file only after there are no more references to it at all, so nobody can do this kind of thing.
Is there some case I am missing where an ioctl on a file can be performed without a reference to that file?
Are you worried about a malicious user calling close on the file and then immediately issuing an ioctl on the now closed file?
If so, wouldn't ioctl just reject that file reference being used as not in the processes file table / invalid and not let the ioctl go through?
Quoted 12 lines
Quoted 12 lines
kernel/trace/Makefile +11
-0+ user = refs->events[i]; + + if (user) + atomic_dec(&user->refcnt); + } + + kfree_rcu(refs, rcu); +out: + return 0; +} +
[..]
Quoted 19 lines
Quoted 6 lines
kernel/trace/Makefile +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, +};kernel/trace/Makefile
Quoted 10 lines
kernel/trace/Makefile +10
-0+ +/* + * Creates a set of tracefs files to allow user mode interactions. + */ +static int create_user_tracefs(void) +{ + struct dentry *edata, *emmap; + + edata = tracefs_create_file("user_events_data", 0644, NULL, + NULL, &user_data_fops);BTW, I now define:
Quoted 5 lines
TRACE_MODE_WRITEfor files to be written to, andTRACE_MODE_READfor files that are read only.And soon tracefs will honor the gid mount option to define what group all the tracefs files should belong to on mount.
Perfect, thank you.
Quoted 3 lines
-- Steve
Thanks,
-Beau
Steven Rostedt
maintainerRe: [PATCH v6 02/13] user_events: Add minimal support for trace_event into ftrace
Quoted 23 lines · On Wed, 8 Dec 2021 16:58:23 -0800 Beau Belgrave <[email protected]>…
Quoted 14 lines
Quoted 14 lines
kernel/trace/trace_events_user.c new file +4
-0+/* +* Handles the final close of the file from user mode. +*/ +static int user_events_release(struct inode *node, struct file*file) +{
kernel/trace/trace_events_user.c new file +6
-0+struct user_event_refs *refs; +struct user_event *user; +int i; + +/* +* refs is protected by RCU and could in theory change immediately
(quoted diff) +1
-0+ * before this call on another core. To ensure we readQuoted 8 lines
kernel/trace/trace_events_user.c line 983
Quoted 6 lines
the latest
kernel/trace/trace_events_user.c new file +5
-0+* version of refs we acquire the RCU read lock again. +*/ +rcu_read_lock_sched (); + refs =rcu_dereference_sched (file->private_data); +rcu_read_unlock_sched ();This still bothers me. Can another CPU call an ioctl here?
Quoted 4 lines
Sorry :)
No, another CPU cannot call the ioctl on the file, since if another CPU had a reference to this file release couldn't be called.
OK, so it should be good, as the last fdput() will call this (and the ioctl should keep that from happening until its done). But this could also be done with less confusion and less paranoia if we simply take the reg_mutex, as that should keep everything from changing, and we wouldn't need to do any rcu_read_lock*() from the release function.
Quoted 7 lines
user_events_releaseis only called when the final reference to the file has been closed, so there cannot be another ioctl pending, starting or finishing for this file at the time it is called.The last user mode program to call
close()on the file will end up invokinguser_events_release.
It doesn't work like that. There's only one close(). But you are correct that it is protected, and that's by the fdget() and fdput() that is done within the ioctl (and other) system call.
Quoted 10 lines
The
user_event_refsis only accessible via the file'sprivate_data, which now has zero references when release is executing. This means theprivate_datacan no longer change and the rcu deref ensures we have the latest version.refs is per-file, so while there can be other ioctl's occurring for other files, they are completely different ref objects than the one being cleaned up in the release of the file, it's not shared outside of this file lifetime, which has now ended.
Right, but I'm still paranoid ;-)
Quoted 10 lines
Quoted 10 lines
user_events_ioctl_reg() { user_events_ref_add() { refs = rcu_dereference_protected(file->private_data, ..); new_refs = kzalloc(size, GFP_KERNEL); rcu_assign_pointer(file->private_data, new_refs); if (refs) kfree_rcu(refs, rcu);
Quoted 6 lines
Quoted 2 lines
refs now freed.
If
user_events_ioctlis executing for that same file,user_events_releasecould not have been called due to the file being in use to issue the ioctl.
The only thing protecting against this is the fdget/put logic in the system calls.
Quoted 32 lines
Quoted 19 lines
Quoted 14 lines
kernel/trace/trace_events_user.c new file +6
-0+ +if (!refs) + goto out; + +/* +* Do not need RCU while enumerating the events that were used.
(quoted diff) +1
-0+ * The lifetime of refs has reached an end, it's tied tothis file.
(quoted diff) +1
-0+ * The underlying user_events are ref counted, andcannot be freed.
(quoted diff) +1
-0+ * After this decrement, the user_events may be freedelsewhere.
Review
Quoted 2 lines
(quoted diff) +2
-0+ */ + for (i = 0; i < refs->count; ++i) {Fault on
refs->countQuoted 1 line
??
refs after
rcu_dereferenceis checked for null before accessing.refs cannot be changed when release is being executed, since that would mean the ioctl ran without a file reference (not sure how that could happen).
This is why it's important that release get the latest version of refs, an ioctl could have JUST happened before the final
close()in user mode, and if it jumped CPUs we could (in theory) get an old value. If we got an old value, then yes, the fault could occur.
Quoted 6 lines
This code uses the file ops release method as a final sync point to clean up everything for that file only after there are no more references to it at all, so nobody can do this kind of thing.
Is there some case I am missing where an ioctl on a file can be performed without a reference to that file?
Well, the ioctl can be called as the close happens, but it's the internal working of fdget/put that protects it. If the ioctl is called at the same time as the close, the fdget in the ioctl will keep the close from calling the release. And as soon as the ioctl is finished, it will call the fdput() which then calls the release logic.
Quoted 8 lines
Are you worried about a malicious user calling close on the file and then immediately issuing an ioctl on the now closed file?
If so, wouldn't ioctl just reject that file reference being used as not in the processes file table / invalid and not let the ioctl go through?
I think it seems less confusing and saner to just use the mutex. It's not a fast path is it?
-- Steve
Re: [PATCH v6 02/13] user_events: Add minimal support for trace_event into ftrace
Quoted 27 lines · Steven Rostedt
Quoted 25 lines · On Wed, 8 Dec 2021 16:58:23 -0800 Beau Belgrave <[email protected]>…
Quoted 14 lines
Quoted 14 lines
kernel/trace/trace_events_user.c new file +4
-0+/* +* Handles the final close of the file from user mode. +*/ +static int user_events_release(struct inode *node, struct file*file) +{
kernel/trace/trace_events_user.c new file +6
-0+struct user_event_refs *refs; +struct user_event *user; +int i; + +/* +* refs is protected by RCU and could in theory change immediately
(quoted diff) +1
-0+ * before this call on another core. To ensure we readQuoted 8 lines
kernel/trace/trace_events_user.c line 983
Quoted 6 lines
the latest
kernel/trace/trace_events_user.c new file +5
-0+* version of refs we acquire the RCU read lock again. +*/ +rcu_read_lock_sched (); + refs =rcu_dereference_sched (file->private_data); +rcu_read_unlock_sched ();This still bothers me. Can another CPU call an ioctl here?
Sorry :)
Quoted 9 lines
Quoted 2 lines
No, another CPU cannot call the ioctl on the file, since if another CPU had a reference to this file release couldn't be called.
OK, so it should be good, as the last
fdput()will call this (and the ioctl should keep that from happening until its done). But this could also be done with less confusion and less paranoia if we simply take thereg_mutex, as that should keep everything from changing, and we wouldn't need to do any rcu_read_lock*() from the release function.
Sure, could do that. This shouldn't be a fast path scenario, however I dislike taking global locks when not required. Happy to change this though.
I'm guessing for less confusion and paranoia you'd want the lock held for the entire method call?
Quoted 5 lines
Quoted 5 lines
user_events_releaseis only called when the final reference to the file has been closed, so there cannot be another ioctl pending, starting or finishing for this file at the time it is called.
Quoted 7 lines
Quoted 2 lines
The last user mode program to call
close()on the file will end up invokinguser_events_release.It doesn't work like that. There's only one
close(). But you are correct that it is protected, and that's by thefdget()andfdput()that is done within the ioctl (and other) system call.
Yeah, I simplified to user space. IE: fork() then close() in each process / task. We both understand each other now though ;)
Quoted 26 lines
Quoted 10 lines
The
user_event_refsis only accessible via the file'sprivate_data, which now has zero references when release is executing. This means theprivate_datacan no longer change and the rcu deref ensures we have the latest version.refs is per-file, so while there can be other ioctl's occurring for other files, they are completely different ref objects than the one being cleaned up in the release of the file, it's not shared outside of this file lifetime, which has now ended.
Right, but I'm still paranoid ;-)
Quoted 13 lines
Quoted 12 lines
user_events_ioctl_reg() { user_events_ref_add() { refs = rcu_dereference_protected(file->private_data, ..); new_refs = kzalloc(size, GFP_KERNEL); rcu_assign_pointer(file->private_data, new_refs); if (refs) kfree_rcu(refs, rcu);refs now freed.
Quoted 8 lines
Quoted 3 lines
If
user_events_ioctlis executing for that same file,user_events_releasecould not have been called due to the file being in use to issue the ioctl.The only thing protecting against this is the fdget/put logic in the system calls.
Yes, however, it is protected.
Quoted 55 lines
Quoted 32 lines
Quoted 19 lines
Quoted 14 lines
kernel/trace/trace_events_user.c new file +6
-0+ +if (!refs) + goto out; + +/* +* Do not need RCU while enumerating the events that were used.
(quoted diff) +1
-0+ * The lifetime of refs has reached an end, it's tied tothis file.
(quoted diff) +1
-0+ * The underlying user_events are ref counted, andcannot be freed.
(quoted diff) +1
-0+ * After this decrement, the user_events may be freedelsewhere.
Review
Quoted 2 lines
(quoted diff) +2
-0+ */ + for (i = 0; i < refs->count; ++i) {Fault on
refs->countQuoted 1 line
??
refs after
rcu_dereferenceis checked for null before accessing.refs cannot be changed when release is being executed, since that would mean the ioctl ran without a file reference (not sure how that could happen).
This is why it's important that release get the latest version of refs, an ioctl could have JUST happened before the final
close()in user mode, and if it jumped CPUs we could (in theory) get an old value. If we got an old value, then yes, the fault could occur.Quoted 6 lines
This code uses the file ops release method as a final sync point to clean up everything for that file only after there are no more references to it at all, so nobody can do this kind of thing.
Is there some case I am missing where an ioctl on a file can be performed without a reference to that file?
Well, the ioctl can be called as the close happens, but it's the internal working of fdget/put that protects it. If the ioctl is called at the same time as the close, the fdget in the ioctl will keep the close from calling the release. And as soon as the ioctl is finished, it will call the
fdput()which then calls the release logic.Quoted 8 lines
Are you worried about a malicious user calling close on the file and then immediately issuing an ioctl on the now closed file?
If so, wouldn't ioctl just reject that file reference being used as not in the processes file table / invalid and not let the ioctl go through?
Quoted 4 lines
I think it seems less confusing and saner to just use the mutex. It's not a fast path is it?
-- Steve
No, this is not a fast path, and I don't have a problem moving to a mutex if you feel that is better. I've likely become too close to this code to know when things are confusing for others.
Thanks,
-Beau
Steven Rostedt
maintainerRe: [PATCH v6 02/13] user_events: Add minimal support for trace_event into ftrace
Quoted 3 lines · On Thu, 9 Dec 2021 09:40:50 -0800 Beau Belgrave <[email protected]>…
No, this is not a fast path, and I don't have a problem moving to a mutex if you feel that is better. I've likely become too close to this code to know when things are confusing for others.
Yeah. I really dislike the "protection by algorithms" then protection by locking unless it is a fast path.
If this was a fast path then I'd be more concerned. I dislike global locks as well, but unless contention becomes a concern, I don't think we should worry about it.
Also, for this comment:
+static int user_events_release(struct inode *node, struct file *file) +{
+ struct user_event_refs *refs;
+ struct user_event *user;
+ int i;
+
+ /*
+ * refs is protected by RCU and could in theory change immediately
+ * before this call on another core. To ensure we read the latest
+ * version of refs we acquire the RCU read lock again.
+ */
+ rcu_read_lock_sched();
+ refs = rcu_dereference_sched(file->private_data);
+ rcu_read_unlock_sched();
How do you see refs changing on another core if this can only be called when nothing has a reference to it?
I think this comment and grabbing the rcu locks is what is causing me concern.
-- Steve
Re: [PATCH v6 02/13] user_events: Add minimal support for trace_event into ftrace
Quoted 7 lines · Steven Rostedt
Quoted 3 lines · On Thu, 9 Dec 2021 09:40:50 -0800 Beau Belgrave <[email protected]>…
No, this is not a fast path, and I don't have a problem moving to a mutex if you feel that is better. I've likely become too close to this code to know when things are confusing for others.
Quoted 6 lines
Yeah. I really dislike the "protection by algorithms" then protection by locking unless it is a fast path.
If this was a fast path then I'd be more concerned. I dislike global locks as well, but unless contention becomes a concern, I don't think we should worry about it.
Sure thing.
Quoted 18 lines
Also, for this comment:
kernel/trace/trace_events_user.c new file +14
-0+static int user_events_release (struct inode *node,struct file *file) +{ +struct user_event_refs *refs; +struct user_event *user; +int i; + +/* +* refs is protected by RCU and could in theory change immediately +* before this call on another core. To ensure we read the latest +* version of refs we acquire the RCU read lock again. +*/ +rcu_read_lock_sched (); + refs =rcu_dereference_sched (file->private_data); +rcu_read_unlock_sched ();
Quoted 7 lines
How do you see refs changing on another core if this can only be called when nothing has a reference to it?
I think this comment and grabbing the rcu locks is what is causing me concern.
-- Steve
User program task:
CPU0: ioctl(fd, REG)
CPU1: close(fd)
IE: Some program registers and then immediately calls close on the file. If the CPU migrates right between the 2 and the close swaps, it is possible this could occur.
This could be attempted in tight loops maliciously as well.
I assume with a mutex there that some barrier is imposed to ensure correct reads in this condition? (By virtue of the mutex acquire/check)
Thanks,
-Beau
Steven Rostedt
maintainerRe: [PATCH v6 02/13] user_events: Add minimal support for trace_event into ftrace
Quoted 4 lines · On Thu, 9 Dec 2021 11:42:35 -0800 Beau Belgrave <[email protected]>…
User program task:
CPU0:ioctl(fd, REG)
CPU1: close(fd)
Quoted 8 lines
IE: Some program registers and then immediately calls close on the file. If the CPU migrates right between the 2 and the close swaps, it is possible this could occur.
This could be attempted in tight loops maliciously as well.
I assume with a mutex there that some barrier is imposed to ensure correct reads in this condition? (By virtue of the mutex acquire/check)
But as I stated before, the ioctl() uses fdget() which will prevent he close from calling the release. Even if they get swapped. If close goes first and starts down the path of the release, then the ioctl is guaranteed to return -EBADF. If it gets the fd, then close will be a nop, and the exit of the ioctl will call the release.
If this wasn't the case, then the race I was concerned about would be an issue.
Because we are both confused by this, add the mutex! :-)
-- Steve
Re: [PATCH v6 02/13] user_events: Add minimal support for trace_event into ftrace
Quoted 22 lines · Steven Rostedt
Quoted 4 lines · On Thu, 9 Dec 2021 11:42:35 -0800 Beau Belgrave <[email protected]>…
User program task:
CPU0:ioctl(fd, REG)
CPU1: close(fd)Quoted 8 lines
IE: Some program registers and then immediately calls close on the file. If the CPU migrates right between the 2 and the close swaps, it is possible this could occur.
This could be attempted in tight loops maliciously as well.
I assume with a mutex there that some barrier is imposed to ensure correct reads in this condition? (By virtue of the mutex acquire/check)
But as I stated before, the
ioctl()usesfdget()which will prevent he close from calling the release. Even if they get swapped. If close goes first and starts down the path of the release, then the ioctl is guaranteed to return-EBADF. If it gets the fd, then close will be a nop, and the exit of the ioctl will call the release.
Quoted 6 lines
If this wasn't the case, then the race I was concerned about would be an issue.
Because we are both confused by this, add the mutex! :-)
-- Steve
Agreed, I will add the mutex. :)
I guess I am being paranoid about an architecture that does not have automatic cache consistency and while the write / read don't happen at the exact time, they happen close together. Close enough that one CPU reads the old value from a cache line and gets it wrong.
I don't believe that is possible on Intel, but I don't know if it's possible on other architectures (especially older ones).
Thanks,
-Beau
Steven Rostedt
maintainerRe: [PATCH v6 02/13] user_events: Add minimal support for trace_event into ftrace
Quoted 7 lines · On Thu, 9 Dec 2021 12:11:12 -0800 Beau Belgrave <[email protected]>…
I guess I am being paranoid about an architecture that does not have automatic cache consistency and while the write / read don't happen at the exact time, they happen close together. Close enough that one CPU reads the old value from a cache line and gets it wrong.
I don't believe that is possible on Intel, but I don't know if it's possible on other architectures (especially older ones).
If this was possible, then there would be a lot more bugs out there than this one. For one thing, the file descriptor itself would be freed while accessed.
-- Steve
PATCH v6 3/13 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 | 107 ++++++++++++++++++++++++++++++- 1 file changed, 105 insertions(+), 2 deletions(-)
kernel/trace/trace_events_user.c +105 -2
@@ -357,6 +357,106 @@ 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) +{ +if (str_has_prefix (type,"__data_loc " ) || +str_has_prefix (type,"__rel_loc " )) +if (strstr (type,"char[" ) !=0 ) +return true; + +return false; +} + +#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 ; + + 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)) + pos +=snprintf (buf + pos,LEN_OR_ZERO , +", __get_str(%s)" , 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)
@@ -390,6 +490,7 @@ static int destroy_user_event(struct user_event *user)clear_bit (user->index, page_bitmap);hash_del (&user->node); +kfree (user->call.print_fmt);kfree (EVENT_NAME (user));kfree (user);
@@ -669,8 +770,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
PATCH v6 4/13 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 (2 * PAGE_SIZE) +#define MAX_FIELD_ARG_NAME 256static char *register_page_data;
@@ -692,13 +693,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
PATCH v6 5/13 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 | 63 ++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+)
kernel/trace/trace_events_user.c +63 -0
@@ -541,6 +541,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. */
@@ -563,6 +607,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);
@@ -604,7 +652,19 @@ static int user_event_reg(struct trace_event_call *call,#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 :
@@ -862,6 +922,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
PATCH v6 6/13 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 | 205 ++++++++++++++++++ tools/testing/selftests/user_events/settings | 1 + 3 files changed, 215 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 +205 -0
205 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 v6 7/13 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 v6 8/13 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 v6 9/13 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 | 102 ++++++++++++++++++++++--------- 1 file changed, 74 insertions(+), 28 deletions(-)
kernel/trace/trace_events_user.c +74 -28
@@ -41,6 +41,10 @@#define MAX_FIELD_ARRAY_SIZE (2 * PAGE_SIZE)#define MAX_FIELD_ARG_NAME 256 +#define MAX_BPF_COPY_SIZE PAGE_SIZE +#define MAX_STACK_BPF_DATA 512 +#define copy_nofault copy_from_iter_nocache +static char *register_page_data;static DEFINE_MUTEX (reg_mutex);
@@ -78,8 +82,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,
@@ -515,7 +518,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;
@@ -531,41 +534,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 (i->count, (size_t )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),
@@ -576,7 +623,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,
@@ -1009,32 +1059,28 @@ 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 (); +pagefault_disable (); probe_func_ptr =rcu_dereference_sched (tp->funcs);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); } +pagefault_enable ();rcu_read_unlock_sched (); - -kfree (kdata); }return ret;
Signature · 1 line
2.17.1
PATCH v6 10/13 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
PATCH v6 11/13 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
PATCH v6 12/13 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. Add unit test cases for the above scenarios.
--- include/uapi/linux/user_events.h | 3 + kernel/trace/trace_events_user.c | 151 ++++++++++++++++-- .../selftests/user_events/ftrace_test.c | 83 ++++++++++ 3 files changed, 220 insertions(+), 17 deletions(-)
include/uapi/linux/user_events.h +3 -0
@@ -29,6 +29,9 @@#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)
kernel/trace/trace_events_user.c +134 -17
291 lines not on this page · raw message
tools/testing/selftests/user_events/ftrace_test.c +83 -0
101 lines not on this page · raw message
Signature · 1 line
2.17.1
PATCH v6 13/13 user_events: Use __get_rel_str for relative string fields
In reply to Beau BelgraveSwitch between __get_str and __get_rel_str within the print_fmt of user_events. Add unit test to ensure print_fmt is correct on known types.
--- kernel/trace/trace_events_user.c | 24 ++- .../selftests/user_events/ftrace_test.c | 166 ++++++++++++++++++ 2 files changed, 182 insertions(+), 8 deletions(-)
kernel/trace/trace_events_user.c +16 -8
@@ -257,7 +257,7 @@ static int user_event_add_field(struct user_event *user, const char *type, goto add_field; add_validator: -if (strstr (type,"char[" ) !=0 ) +if (strstr (type,"char" ) !=0 ) validator_flags |=VALIDATOR_ENSURE_NULL ; validator =kmalloc (sizeof (*validator),GFP_KERNEL );
@@ -456,14 +456,21 @@ static const char *user_field_format(const char *type)return "%llu" ; } -static bool user_field_is_dyn_string (const char *type) +static bool user_field_is_dyn_string (const char *type,const char **str_func) { -if (str_has_prefix (type,"__data_loc " ) || -str_has_prefix (type,"__rel_loc " )) -if (strstr (type,"char[" ) !=0 ) -return true; +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)
@@ -472,6 +479,7 @@ 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 ,"\"" );
@@ -488,9 +496,9 @@ static int user_event_set_print_fmt(struct user_event *user, char *buf, int len) 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)) +if (user_field_is_dyn_string (field->type, &str_func)) pos +=snprintf (buf + pos,LEN_OR_ZERO , -", __get_str(%s)" , field->name); +", %s(%s)" , str_func, field->name);else pos +=snprintf (buf + pos,LEN_OR_ZERO ,", REC->%s" , field->name);
tools/testing/selftests/user_events/ftrace_test.c +166 -0
190 lines not on this page · raw message
Signature · 1 line
2.17.1