* [PATCHv6 09/13] selftests/bpf: Change uprobe syscall tests to use nop10
From: Jiri Olsa @ 2026-07-03 11:49 UTC (permalink / raw)
To: Oleg Nesterov, Peter Zijlstra, Ingo Molnar, Masami Hiramatsu,
Andrii Nakryiko
Cc: Jakub Sitnicki, bpf, linux-trace-kernel
In-Reply-To: <20260703114917.238144-1-jolsa@kernel.org>
Optimized uprobes are now on top of 10-bytes nop instructions,
reflect that in existing tests.
Reviewed-by: Jakub Sitnicki <jakub@cloudflare.com>
Signed-off-by: Jiri Olsa <jolsa@kernel.org>
---
.../selftests/bpf/benchs/bench_trigger.c | 2 +-
.../selftests/bpf/prog_tests/uprobe_syscall.c | 30 +++++++++++--------
tools/testing/selftests/bpf/prog_tests/usdt.c | 25 +++++++++-------
tools/testing/selftests/bpf/usdt_2.c | 2 +-
4 files changed, 34 insertions(+), 25 deletions(-)
diff --git a/tools/testing/selftests/bpf/benchs/bench_trigger.c b/tools/testing/selftests/bpf/benchs/bench_trigger.c
index 2f22ec61667b..a60b8173cdc4 100644
--- a/tools/testing/selftests/bpf/benchs/bench_trigger.c
+++ b/tools/testing/selftests/bpf/benchs/bench_trigger.c
@@ -398,7 +398,7 @@ static void *uprobe_producer_ret(void *input)
#ifdef __x86_64__
__nocf_check __weak void uprobe_target_nop5(void)
{
- asm volatile (".byte 0x0f, 0x1f, 0x44, 0x00, 0x00");
+ asm volatile (".byte 0x66, 0x2e, 0x0f, 0x1f, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00");
}
static void *uprobe_producer_nop5(void *input)
diff --git a/tools/testing/selftests/bpf/prog_tests/uprobe_syscall.c b/tools/testing/selftests/bpf/prog_tests/uprobe_syscall.c
index c944136252c6..ba50071ace40 100644
--- a/tools/testing/selftests/bpf/prog_tests/uprobe_syscall.c
+++ b/tools/testing/selftests/bpf/prog_tests/uprobe_syscall.c
@@ -17,7 +17,7 @@
#include "uprobe_syscall_executed.skel.h"
#include "bpf/libbpf_internal.h"
-#define USDT_NOP .byte 0x0f, 0x1f, 0x44, 0x00, 0x00
+#define USDT_NOP .byte 0x66, 0x2e, 0x0f, 0x1f, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00
#include "usdt.h"
#pragma GCC diagnostic ignored "-Wattributes"
@@ -26,7 +26,7 @@ __attribute__((aligned(16)))
__nocf_check __weak __naked unsigned long uprobe_regs_trigger(void)
{
asm volatile (
- ".byte 0x0f, 0x1f, 0x44, 0x00, 0x00\n" /* nop5 */
+ ".byte 0x66, 0x2e, 0x0f, 0x1f, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00\n" /* nop10 */
"movq $0xdeadbeef, %rax\n"
"ret\n"
);
@@ -345,9 +345,9 @@ static void test_uretprobe_syscall_call(void)
__attribute__((aligned(16)))
__nocf_check __weak __naked void uprobe_test(void)
{
- asm volatile (" \n"
- ".byte 0x0f, 0x1f, 0x44, 0x00, 0x00 \n"
- "ret \n"
+ asm volatile (
+ ".byte 0x66, 0x2e, 0x0f, 0x1f, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00\n" /* nop10 */
+ "ret\n"
);
}
@@ -388,14 +388,15 @@ static int find_uprobes_trampoline(void *tramp_addr)
return ret;
}
-static unsigned char nop5[5] = { 0x0f, 0x1f, 0x44, 0x00, 0x00 };
+static unsigned char nop10[10] = { 0x66, 0x2e, 0x0f, 0x1f, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00 };
+static unsigned char lea_rsp[5] = { 0x48, 0x8d, 0x64, 0x24, 0x80 };
-static void *find_nop5(void *fn)
+static void *find_nop10(void *fn)
{
int i;
- for (i = 0; i < 10; i++) {
- if (!memcmp(nop5, fn + i, 5))
+ for (i = 0; i < 128; i++) {
+ if (!memcmp(nop10, fn + i, 10))
return fn + i;
}
return NULL;
@@ -420,7 +421,8 @@ static void *check_attach(struct uprobe_syscall_executed *skel, trigger_t trigge
ASSERT_EQ(skel->bss->executed, executed, "executed");
/* .. and check the trampoline is as expected. */
- call = (struct __arch_relative_insn *) addr;
+ ASSERT_OK(memcmp(addr, lea_rsp, 5), "lea_rsp");
+ call = (struct __arch_relative_insn *)(addr + 5);
tramp = (void *) (call + 1) + call->raddr;
ASSERT_EQ(call->op, 0xe8, "call");
ASSERT_OK(find_uprobes_trampoline(tramp), "uprobes_trampoline");
@@ -430,9 +432,11 @@ static void *check_attach(struct uprobe_syscall_executed *skel, trigger_t trigge
static void check_detach(void *addr, void *tramp)
{
+ static const unsigned char nop10_prefix[] = { 0x66, 0x2e, 0x0f, 0x1f, 0x84 };
+
/* [uprobes_trampoline] stays after detach */
ASSERT_OK(find_uprobes_trampoline(tramp), "uprobes_trampoline");
- ASSERT_OK(memcmp(addr, nop5, 5), "nop5");
+ ASSERT_OK(memcmp(addr, nop10_prefix, 5), "nop10_prefix");
}
static void check(struct uprobe_syscall_executed *skel, struct bpf_link *link,
@@ -568,8 +572,8 @@ static void test_uprobe_usdt(void)
void *addr;
errno = 0;
- addr = find_nop5(usdt_test);
- if (!ASSERT_OK_PTR(addr, "find_nop5"))
+ addr = find_nop10(usdt_test);
+ if (!ASSERT_OK_PTR(addr, "find_nop10"))
return;
skel = uprobe_syscall_executed__open_and_load();
diff --git a/tools/testing/selftests/bpf/prog_tests/usdt.c b/tools/testing/selftests/bpf/prog_tests/usdt.c
index 69759b27794d..fda3a298ccfc 100644
--- a/tools/testing/selftests/bpf/prog_tests/usdt.c
+++ b/tools/testing/selftests/bpf/prog_tests/usdt.c
@@ -252,7 +252,7 @@ extern void usdt_1(void);
extern void usdt_2(void);
static unsigned char nop1[1] = { 0x90 };
-static unsigned char nop1_nop5_combo[6] = { 0x90, 0x0f, 0x1f, 0x44, 0x00, 0x00 };
+static unsigned char nop1_nop10_combo[11] = { 0x90, 0x66, 0x2e, 0x0f, 0x1f, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00 };
static void *find_instr(void *fn, unsigned char *instr, size_t cnt)
{
@@ -271,17 +271,17 @@ static void subtest_optimized_attach(void)
__u8 *addr_1, *addr_2;
/* usdt_1 USDT probe has single nop instruction */
- addr_1 = find_instr(usdt_1, nop1_nop5_combo, 6);
- if (!ASSERT_NULL(addr_1, "usdt_1_find_nop1_nop5_combo"))
+ addr_1 = find_instr(usdt_1, nop1_nop10_combo, 11);
+ if (!ASSERT_NULL(addr_1, "usdt_1_find_nop1_nop10_combo"))
return;
addr_1 = find_instr(usdt_1, nop1, 1);
if (!ASSERT_OK_PTR(addr_1, "usdt_1_find_nop1"))
return;
- /* usdt_2 USDT probe has nop,nop5 instructions combo */
- addr_2 = find_instr(usdt_2, nop1_nop5_combo, 6);
- if (!ASSERT_OK_PTR(addr_2, "usdt_2_find_nop1_nop5_combo"))
+ /* usdt_2 USDT probe has nop,nop10 instructions combo */
+ addr_2 = find_instr(usdt_2, nop1_nop10_combo, 11);
+ if (!ASSERT_OK_PTR(addr_2, "usdt_2_find_nop1_nop10_combo"))
return;
skel = test_usdt__open_and_load();
@@ -309,12 +309,12 @@ static void subtest_optimized_attach(void)
bpf_link__destroy(skel->links.usdt_executed);
- /* we expect the nop5 ip */
+ /* we expect the nop10 ip */
skel->bss->expected_ip = (unsigned long) addr_2 + 1;
/*
* Attach program on top of usdt_2 which is probe defined on top
- * of nop1,nop5 combo, so the probe gets optimized on top of nop5.
+ * of nop1,nop10 combo, so the probe gets optimized on top of nop10.
*/
skel->links.usdt_executed = bpf_program__attach_usdt(skel->progs.usdt_executed,
0 /*self*/, "/proc/self/exe",
@@ -328,8 +328,13 @@ static void subtest_optimized_attach(void)
/* nop stays on addr_2 address */
ASSERT_EQ(*addr_2, 0x90, "nop");
- /* call is on addr_2 + 1 address */
- ASSERT_EQ(*(addr_2 + 1), 0xe8, "call");
+ /*
+ * lea -0x80(%rsp), %rsp
+ * call ...
+ */
+ static unsigned char expected[] = { 0x48, 0x8d, 0x64, 0x24, 0x80, 0xe8 };
+
+ ASSERT_MEMEQ(addr_2 + 1, expected, sizeof(expected), "lea_and_call");
ASSERT_EQ(skel->bss->executed, 4, "executed");
cleanup:
diff --git a/tools/testing/selftests/bpf/usdt_2.c b/tools/testing/selftests/bpf/usdt_2.c
index 789883aaca4c..b359b389f6c0 100644
--- a/tools/testing/selftests/bpf/usdt_2.c
+++ b/tools/testing/selftests/bpf/usdt_2.c
@@ -3,7 +3,7 @@
#if defined(__x86_64__)
/*
- * Include usdt.h with default nop,nop5 instructions combo.
+ * Include usdt.h with default nop,nop10 instructions combo.
*/
#include "usdt.h"
--
2.54.0
^ permalink raw reply related
* [PATCHv6 08/13] selftests/bpf: Emit nop,nop10 instructions combo for x86_64 arch
From: Jiri Olsa @ 2026-07-03 11:49 UTC (permalink / raw)
To: Oleg Nesterov, Peter Zijlstra, Ingo Molnar, Masami Hiramatsu,
Andrii Nakryiko
Cc: Jakub Sitnicki, bpf, linux-trace-kernel
In-Reply-To: <20260703114917.238144-1-jolsa@kernel.org>
Syncing latest usdt.h change [1].
Now that we have nop10 optimization support in kernel, let's emit
nop,nop10 for usdt probe. We leave it up to the library to use
desirable nop instruction.
[1] https://github.com/libbpf/usdt/commit/9018f82577d1dad7ed628d9efdaffc09f8f2241b
Reviewed-by: Jakub Sitnicki <jakub@cloudflare.com>
Signed-off-by: Jiri Olsa <jolsa@kernel.org>
---
tools/testing/selftests/bpf/usdt.h | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/tools/testing/selftests/bpf/usdt.h b/tools/testing/selftests/bpf/usdt.h
index c71e21df38b3..75687f50f4e2 100644
--- a/tools/testing/selftests/bpf/usdt.h
+++ b/tools/testing/selftests/bpf/usdt.h
@@ -313,7 +313,7 @@ struct usdt_sema { volatile unsigned short active; };
#if defined(__ia64__) || defined(__s390__) || defined(__s390x__)
#define USDT_NOP nop 0
#elif defined(__x86_64__)
-#define USDT_NOP .byte 0x90, 0x0f, 0x1f, 0x44, 0x00, 0x0 /* nop, nop5 */
+#define USDT_NOP .byte 0x90, 0x66, 0x2e, 0x0f, 0x1f, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00 /* nop, nop10 */
#else
#define USDT_NOP nop
#endif
--
2.54.0
^ permalink raw reply related
* [PATCHv6 07/13] libbpf: Detect uprobe syscall with new error
From: Jiri Olsa @ 2026-07-03 11:49 UTC (permalink / raw)
To: Oleg Nesterov, Peter Zijlstra, Ingo Molnar, Masami Hiramatsu,
Andrii Nakryiko
Cc: bpf, linux-trace-kernel
In-Reply-To: <20260703114917.238144-1-jolsa@kernel.org>
In the previous optimized uprobe fix we changed the syscall
error used for its detection from ENXIO to EPROTO.
Changing related probe_uprobe_syscall detection check.
Acked-by: Andrii Nakryiko <andrii@kernel.org>
Fixes: 05738da0efa1 ("libbpf: Add uprobe syscall feature detection")
Signed-off-by: Jiri Olsa <jolsa@kernel.org>
---
tools/lib/bpf/features.c | 4 ++--
tools/testing/selftests/bpf/prog_tests/uprobe_syscall.c | 2 +-
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/tools/lib/bpf/features.c b/tools/lib/bpf/features.c
index b7e388f99d0b..e5641fa60163 100644
--- a/tools/lib/bpf/features.c
+++ b/tools/lib/bpf/features.c
@@ -577,10 +577,10 @@ static int probe_ldimm64_full_range_off(int token_fd)
static int probe_uprobe_syscall(int token_fd)
{
/*
- * If kernel supports uprobe() syscall, it will return -ENXIO when called
+ * If kernel supports uprobe() syscall, it will return -EPROTO when called
* from the outside of a kernel-generated uprobe trampoline.
*/
- return syscall(__NR_uprobe) < 0 && errno == ENXIO;
+ return syscall(__NR_uprobe) < 0 && errno == EPROTO;
}
#else
static int probe_uprobe_syscall(int token_fd)
diff --git a/tools/testing/selftests/bpf/prog_tests/uprobe_syscall.c b/tools/testing/selftests/bpf/prog_tests/uprobe_syscall.c
index 955a37751b52..c944136252c6 100644
--- a/tools/testing/selftests/bpf/prog_tests/uprobe_syscall.c
+++ b/tools/testing/selftests/bpf/prog_tests/uprobe_syscall.c
@@ -762,7 +762,7 @@ static void test_uprobe_error(void)
long err = syscall(__NR_uprobe);
ASSERT_EQ(err, -1, "error");
- ASSERT_EQ(errno, ENXIO, "errno");
+ ASSERT_EQ(errno, EPROTO, "errno");
}
static void __test_uprobe_syscall(void)
--
2.54.0
^ permalink raw reply related
* [PATCHv6 06/13] libbpf: Change has_nop_combo to work on top of nop10
From: Jiri Olsa @ 2026-07-03 11:49 UTC (permalink / raw)
To: Oleg Nesterov, Peter Zijlstra, Ingo Molnar, Masami Hiramatsu,
Andrii Nakryiko
Cc: Jakub Sitnicki, bpf, linux-trace-kernel
In-Reply-To: <20260703114917.238144-1-jolsa@kernel.org>
We now expect nop combo with 10 bytes nop instead of 5 bytes nop,
fixing has_nop_combo to reflect that.
Fixes: 41a5c7df4466 ("libbpf: Add support to detect nop,nop5 instructions combo for usdt probe")
Reviewed-by: Jakub Sitnicki <jakub@cloudflare.com>
Acked-by: Andrii Nakryiko <andrii@kernel.org>
Signed-off-by: Jiri Olsa <jolsa@kernel.org>
---
tools/lib/bpf/usdt.c | 16 ++++++++--------
1 file changed, 8 insertions(+), 8 deletions(-)
diff --git a/tools/lib/bpf/usdt.c b/tools/lib/bpf/usdt.c
index db9432adb967..2e56e3ab5b6c 100644
--- a/tools/lib/bpf/usdt.c
+++ b/tools/lib/bpf/usdt.c
@@ -305,7 +305,7 @@ struct usdt_manager *usdt_manager_new(struct bpf_object *obj)
/*
* Detect kernel support for uprobe() syscall, it's presence means we can
- * take advantage of faster nop5 uprobe handling.
+ * take advantage of faster nop10 uprobe handling.
* Added in: 56101b69c919 ("uprobes/x86: Add uprobe syscall to speed up uprobe")
*/
man->has_uprobe_syscall = kernel_supports(obj, FEAT_UPROBE_SYSCALL);
@@ -605,14 +605,14 @@ static int parse_usdt_spec(struct usdt_spec *spec, const struct usdt_note *note,
#if defined(__x86_64__)
static bool has_nop_combo(int fd, long off)
{
- unsigned char nop_combo[6] = {
- 0x90, 0x0f, 0x1f, 0x44, 0x00, 0x00 /* nop,nop5 */
+ unsigned char nop_combo[11] = {
+ 0x90, 0x66, 0x2e, 0x0f, 0x1f, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00,
};
- unsigned char buf[6];
+ unsigned char buf[11];
- if (pread(fd, buf, 6, off) != 6)
+ if (pread(fd, buf, 11, off) != 11)
return false;
- return memcmp(buf, nop_combo, 6) == 0;
+ return memcmp(buf, nop_combo, 11) == 0;
}
#else
static bool has_nop_combo(int fd, long off)
@@ -825,8 +825,8 @@ static int collect_usdt_targets(struct usdt_manager *man, struct elf_fd *elf_fd,
memset(target, 0, sizeof(*target));
/*
- * We have uprobe syscall and usdt with nop,nop5 instructions combo,
- * so we can place the uprobe directly on nop5 (+1) and get this probe
+ * We have uprobe syscall and usdt with nop,nop10 instructions combo,
+ * so we can place the uprobe directly on nop10 (+1) and get this probe
* optimized.
*/
if (man->has_uprobe_syscall && has_nop_combo(elf_fd->fd, usdt_rel_ip)) {
--
2.54.0
^ permalink raw reply related
* [PATCHv6 05/13] uprobes/x86: Move optimized uprobe from nop5 to nop10
From: Jiri Olsa @ 2026-07-03 11:49 UTC (permalink / raw)
To: Oleg Nesterov, Peter Zijlstra, Ingo Molnar, Masami Hiramatsu,
Andrii Nakryiko
Cc: bpf, linux-trace-kernel
In-Reply-To: <20260703114917.238144-1-jolsa@kernel.org>
Andrii reported an issue with optimized uprobes [1] that can clobber
redzone area with call instruction storing return address on stack
where user code may keep temporary data without adjusting rsp.
Fixing this by moving the optimized uprobes on top of 10-bytes nop
instruction, so we can squeeze another instruction to escape the
redzone area before doing the call, like:
lea -0x80(%rsp), %rsp
call tramp
Note the lea instruction is used to adjust the rsp register without
changing the flags.
We use nop10 and following transformation to optimized instructions
above and back as suggested by Peterz [2].
Optimize path (int3_update_optimize):
1) Initial state after set_swbp() installed the uprobe:
cc 2e 0f 1f 84 00 00 00 00 00
From offset 0 this is INT3 followed by the tail of the original
10-byte NOP.
After a previous unoptimization bytes 5..9 may still contain the
old call instruction, which remains valid for threads already there.
2) Rewrite the LEA tail and call displacement:
cc [8d 64 24 80 e8 d0 d1 d2 d3]
From offset 0 this traps on the uprobe INT3. Bytes 1..9 are not
executable entry points while byte 0 is trapped.
3) Publish the first LEA byte:
[48] 8d 64 24 80 e8 d0 d1 d2 d3
From offset 0 this is:
lea -0x80(%rsp), %rsp
call <uprobe-trampoline>
Unoptimize path (int3_update_unoptimize):
1) Initial optimized state:
48 8d 64 24 80 e8 d0 d1 d2 d3
Same as 3) above.
2) Trap new entries before restoring the NOP bytes:
[cc] 8d 64 24 80 e8 d0 d1 d2 d3
From offset 0 this traps. A thread that had already executed the
LEA can still reach the intact CALL at offset 5.
3) Restore bytes 1..4 of the original NOP while keeping byte 0 trapped
and byte 5 as CALL.
cc [2e 0f 1f 84] e8 d0 d1 d2 d3
From offset 0 this still traps. Offset 5 is still the CALL for any
thread that was already past the first LEA byte.
4) Publish the first byte of the original NOP:
[66] 2e 0f 1f 84 e8 d0 d1 d2 d3
From offset 0 this is the restored 10-byte NOP; the CALL opcode and
displacement are now only NOP operands. Offset 5 still decodes as
CALL for a thread that was already there.
Tthere is only a single target uprobe-trampoline for the given nop10
instruction address, so the CALL instruction will not be changed across
unoptimization/optimization cycles.
Therefore, any task that is preempted at the CALL instruction is guaranteed
to observe that CALL and not anything else.
Note as explained in [2] we need to use following nop10:
PF1 PF2 ESC NOPL MOD SIB DISP32
NOP10: 0x66, 0x2e, 0x0f, 0x1f, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00 -- cs nopw 0x00000000(%rax,%rax,1)
which means we need to allow 0x2e prefix which maps to INAT_PFX_CS
attribute in is_prefix_bad function.
Also changing the uprobe syscall error when called out of uprobe
trampoline to -EPROTO, so we are able to detect the fixed kernel.
The optimized uprobe performance stays the same:
uprobe-nop : 3.129 ± 0.013M/s
uprobe-push : 3.045 ± 0.006M/s
uprobe-ret : 1.095 ± 0.004M/s
--> uprobe-nop10 : 7.170 ± 0.020M/s
uretprobe-nop : 2.143 ± 0.021M/s
uretprobe-push : 2.090 ± 0.000M/s
uretprobe-ret : 0.942 ± 0.000M/s
--> uretprobe-nop10: 3.381 ± 0.003M/s
usdt-nop : 3.245 ± 0.004M/s
--> usdt-nop10 : 7.256 ± 0.023M/s
[1] https://lore.kernel.org/bpf/20260509003146.976844-1-andrii@kernel.org/
[2] https://lore.kernel.org/bpf/20260518104306.GU3102624@noisy.programming.kicks-ass.net/#t
Reported-by: Andrii Nakryiko <andrii@kernel.org>
Closes: https://lore.kernel.org/bpf/20260509003146.976844-1-andrii@kernel.org/
Fixes: ba2bfc97b462 ("uprobes/x86: Add support to optimize uprobes")
Assisted-by: Codex:GPT-5.5
Reviewed-by: Oleg Nesterov <oleg@redhat.com>
Signed-off-by: Jiri Olsa <jolsa@kernel.org>
---
arch/x86/kernel/uprobes.c | 292 ++++++++++++++++++++++++++++----------
1 file changed, 216 insertions(+), 76 deletions(-)
diff --git a/arch/x86/kernel/uprobes.c b/arch/x86/kernel/uprobes.c
index af5af7d67999..521a120a0c78 100644
--- a/arch/x86/kernel/uprobes.c
+++ b/arch/x86/kernel/uprobes.c
@@ -276,15 +276,9 @@ static bool is_prefix_bad(struct insn *insn)
return false;
}
-static int uprobe_init_insn(struct arch_uprobe *auprobe, struct insn *insn, bool x86_64)
+static int uprobe_init_insn(struct arch_uprobe *auprobe, struct insn *insn)
{
- enum insn_mode m = x86_64 ? INSN_MODE_64 : INSN_MODE_32;
u32 volatile *good_insns;
- int ret;
-
- ret = insn_decode(insn, auprobe->insn, sizeof(auprobe->insn), m);
- if (ret < 0)
- return -ENOEXEC;
if (is_prefix_bad(insn))
return -ENOTSUPP;
@@ -293,7 +287,7 @@ static int uprobe_init_insn(struct arch_uprobe *auprobe, struct insn *insn, bool
if (insn_masking_exception(insn))
return -ENOTSUPP;
- if (x86_64)
+ if (insn->x86_64)
good_insns = good_insns_64;
else
good_insns = good_insns_32;
@@ -631,9 +625,29 @@ static struct vm_special_mapping tramp_mapping = {
.pages = tramp_mapping_pages,
};
+
+#define LEA_INSN_SIZE 5
+#define OPT_INSN_SIZE (LEA_INSN_SIZE + CALL_INSN_SIZE)
+#define REDZONE_SIZE 0x80
+
+static const u8 lea_rsp[] = { 0x48, 0x8d, 0x64, 0x24, 0x80 };
+
+static bool is_opt_insns(const uprobe_opcode_t *insn)
+{
+ return !memcmp(insn, lea_rsp, LEA_INSN_SIZE) &&
+ insn[LEA_INSN_SIZE] == CALL_INSN_OPCODE;
+}
+
+static bool is_swbp_opt_insns(uprobe_opcode_t *insn)
+{
+ return is_swbp_insn(&insn[0]) &&
+ !memcmp(&insn[1], &lea_rsp[1], LEA_INSN_SIZE - 1) &&
+ insn[LEA_INSN_SIZE] == CALL_INSN_OPCODE;
+}
+
static bool is_reachable_by_call(unsigned long vtramp, unsigned long vaddr)
{
- long delta = (long)(vaddr + 5 - vtramp);
+ long delta = (long)(vaddr + OPT_INSN_SIZE - vtramp);
return delta >= INT_MIN && delta <= INT_MAX;
}
@@ -646,7 +660,7 @@ static unsigned long find_nearest_trampoline(unsigned long vaddr)
};
unsigned long low_limit, high_limit;
unsigned long low_tramp, high_tramp;
- unsigned long call_end = vaddr + 5;
+ unsigned long call_end = vaddr + OPT_INSN_SIZE;
if (check_add_overflow(call_end, INT_MIN, &low_limit))
low_limit = PAGE_SIZE;
@@ -754,7 +768,7 @@ SYSCALL_DEFINE0(uprobe)
/* Allow execution only from uprobe trampolines. */
if (!in_uprobe_trampoline(regs->ip))
- return -ENXIO;
+ return -EPROTO;
err = copy_from_user(&args, (void __user *)regs->sp, sizeof(args));
if (err)
@@ -770,8 +784,8 @@ SYSCALL_DEFINE0(uprobe)
regs->ax = args.ax;
regs->r11 = args.r11;
regs->cx = args.cx;
- regs->ip = args.retaddr - 5;
- regs->sp += sizeof(args);
+ regs->ip = args.retaddr - OPT_INSN_SIZE;
+ regs->sp += sizeof(args) + REDZONE_SIZE;
regs->orig_ax = -1;
sp = regs->sp;
@@ -788,12 +802,12 @@ SYSCALL_DEFINE0(uprobe)
*/
if (regs->sp != sp) {
/* skip the trampoline call */
- if (args.retaddr - 5 == regs->ip)
- regs->ip += 5;
+ if (args.retaddr - OPT_INSN_SIZE == regs->ip)
+ regs->ip += OPT_INSN_SIZE;
return regs->ax;
}
- regs->sp -= sizeof(args);
+ regs->sp -= sizeof(args) + REDZONE_SIZE;
/* for the case uprobe_consumer has changed ax/r11/cx */
args.ax = regs->ax;
@@ -801,7 +815,7 @@ SYSCALL_DEFINE0(uprobe)
args.cx = regs->cx;
/* keep return address unless we are instructed otherwise */
- if (args.retaddr - 5 != regs->ip)
+ if (args.retaddr - OPT_INSN_SIZE != regs->ip)
args.retaddr = regs->ip;
if (shstk_push(args.retaddr) == -EFAULT)
@@ -835,7 +849,7 @@ asm (
"pop %rax\n"
"pop %r11\n"
"pop %rcx\n"
- "ret\n"
+ "ret $" __stringify(REDZONE_SIZE) "\n"
"int3\n"
".balign " __stringify(PAGE_SIZE) "\n"
".popsection\n"
@@ -853,7 +867,8 @@ late_initcall(arch_uprobes_init);
enum {
EXPECT_SWBP,
- EXPECT_CALL,
+ EXPECT_OPTIMIZED,
+ EXPECT_SWBP_OPTIMIZED,
};
struct write_opcode_ctx {
@@ -861,30 +876,29 @@ struct write_opcode_ctx {
int expect;
};
-static int is_call_insn(uprobe_opcode_t *insn)
-{
- return *insn == CALL_INSN_OPCODE;
-}
-
/*
- * Verification callback used by int3_update uprobe_write calls to make sure
- * the underlying instruction is as expected - either int3 or call.
+ * Verification callback used by uprobe_write calls to make sure the underlying
+ * instruction is in the expected stage of the INT3 update sequence.
*/
static int verify_insn(struct page *page, unsigned long vaddr, uprobe_opcode_t *new_opcode,
int nbytes, void *data)
{
struct write_opcode_ctx *ctx = data;
- uprobe_opcode_t old_opcode[5];
+ uprobe_opcode_t old_opcode[OPT_INSN_SIZE];
- uprobe_copy_from_page(page, ctx->base, (uprobe_opcode_t *) &old_opcode, 5);
+ uprobe_copy_from_page(page, ctx->base, old_opcode, OPT_INSN_SIZE);
switch (ctx->expect) {
case EXPECT_SWBP:
if (is_swbp_insn(&old_opcode[0]))
return 1;
break;
- case EXPECT_CALL:
- if (is_call_insn(&old_opcode[0]))
+ case EXPECT_OPTIMIZED:
+ if (is_opt_insns(&old_opcode[0]))
+ return 1;
+ break;
+ case EXPECT_SWBP_OPTIMIZED:
+ if (is_swbp_opt_insns(&old_opcode[0]))
return 1;
break;
}
@@ -893,48 +907,122 @@ static int verify_insn(struct page *page, unsigned long vaddr, uprobe_opcode_t *
}
/*
- * Modify multi-byte instructions by using INT3 breakpoints on SMP.
+ * Modify the optimized instruction by using INT3 breakpoints on SMP.
* We completely avoid using stop_machine() here, and achieve the
* synchronization using INT3 breakpoints and SMP cross-calls.
* (borrowed comment from smp_text_poke_batch_finish)
*
- * The way it is done:
- * - Add an INT3 trap to the address that will be patched
- * - SMP sync all CPUs
- * - Update all but the first byte of the patched range
- * - SMP sync all CPUs
- * - Replace the first byte (INT3) by the first byte of the replacing opcode
- * - SMP sync all CPUs
+ * For optimization (int3_update_optimize):
+ * 1) Start with the uprobe INT3 trap already installed
+ * 2) Update everything but the first byte
+ * 3) Replace the first INT3 by the first byte of the LEA instruction
+ *
+ * For unoptimization (int3_update_unoptimize):
+ * 1) Start with the optimized uprobe lea/call instructions
+ * 2) Add an INT3 trap to the address that will be patched
+ * 3) Restore the NOP bytes before the call opcode
+ * 4) Replace the first INT3 by the first byte of the NOP instruction
+ *
+ * Note that unoptimization deliberately keeps the call opcode and displacement
+ * in bytes 5..9. Those bytes become operands of the restored 10-byte NOP.
+ *
+ * Since there is only a single target uprobe-trampoline for the given nop10
+ * instruction address, the CALL instruction will not be changed across
+ * unoptimization/optimization cycles.
+ * Therefore, any task that is preempted at the CALL instruction is guaranteed
+ * to observe that CALL and not anything else.
*/
-static int int3_update(struct arch_uprobe *auprobe, struct vm_area_struct *vma,
- unsigned long vaddr, char *insn, bool optimize)
+static int int3_update_optimize(struct arch_uprobe *auprobe, struct vm_area_struct *vma,
+ unsigned long vaddr, uprobe_opcode_t *insn)
{
- uprobe_opcode_t int3 = UPROBE_SWBP_INSN;
struct write_opcode_ctx ctx = {
.base = vaddr,
};
int err;
/*
- * Write int3 trap.
+ * 1) Initial state after set_swbp() installed the uprobe:
+ * cc 2e 0f 1f 84 00 00 00 00 00
*
- * The swbp_optimize path comes with breakpoint already installed,
- * so we can skip this step for optimize == true.
+ * After a previous unoptimization bytes 5..9 may still contain the
+ * old call instruction, which remains valid for threads already there.
*/
- if (!optimize) {
- ctx.expect = EXPECT_CALL;
- err = uprobe_write(auprobe, vma, vaddr, &int3, 1, verify_insn,
- true /* is_register */, false /* do_update_ref_ctr */,
- &ctx);
- if (err)
- return err;
- }
+ smp_text_poke_sync_each_cpu();
+
+ /*
+ * 2) Rewrite the LEA tail and call displacement:
+ * cc [8d 64 24 80 e8 d0 d1 d2 d3]
+ */
+ ctx.expect = EXPECT_SWBP;
+ err = uprobe_write(auprobe, vma, vaddr + 1, insn + 1,
+ OPT_INSN_SIZE - 1, verify_insn,
+ true /* is_register */, false /* do_update_ref_ctr */,
+ &ctx);
+ if (err)
+ return err;
+
+ smp_text_poke_sync_each_cpu();
+
+ /*
+ * 3) Publish the first LEA byte:
+ * [48] 8d 64 24 80 e8 d0 d1 d2 d3
+ *
+ * From offset 0 this is:
+ * lea -0x80(%rsp), %rsp
+ * call <uprobe-trampoline>
+ */
+ ctx.expect = EXPECT_SWBP_OPTIMIZED;
+ err = uprobe_write(auprobe, vma, vaddr, insn, 1, verify_insn,
+ true /* is_register */, false /* do_update_ref_ctr */,
+ &ctx);
+ if (err)
+ goto error;
smp_text_poke_sync_each_cpu();
+ return 0;
- /* Write all but the first byte of the patched range. */
+error:
+ /*
+ * In all intermediate states byte 0 is INT3, so EXPECT_SWBP covers every
+ * case. Restore NOP bytes 1..4, but keep the valid CALL at bytes 5..9
+ * for a thread that had already executed the LEA before a previous
+ * unoptimization.
+ */
ctx.expect = EXPECT_SWBP;
- err = uprobe_write(auprobe, vma, vaddr + 1, insn + 1, 4, verify_insn,
+ uprobe_write(auprobe, vma, vaddr + 1, auprobe->insn + 1,
+ LEA_INSN_SIZE - 1, verify_insn, true, false, &ctx);
+ smp_text_poke_sync_each_cpu();
+ return err;
+}
+
+static int int3_update_unoptimize(struct arch_uprobe *auprobe, struct vm_area_struct *vma,
+ unsigned long vaddr, uprobe_opcode_t *insn)
+{
+ uprobe_opcode_t int3 = UPROBE_SWBP_INSN;
+ struct write_opcode_ctx ctx = {
+ .base = vaddr,
+ .expect = EXPECT_OPTIMIZED,
+ };
+ int err;
+
+ /*
+ * Note the first two uprobe_write calls use is_register=true, because they
+ * are intermediate patching states while the probe is still active, so
+ * we force the exclusive anonymous page for the update.
+ * Also we use do_update_ref_ctr=false because refctr was already updated by
+ * the initial int3 install.
+ *
+ * The last uprobe_write to nop10 instruction is called with is_register=false
+ * and do_update_ref_ctr=true to trigger the refctr update and to instruct
+ * uprobe_write to zap the anonymous page if it now matches the file page.
+ *
+ * 1) Initial optimized state:
+ * 48 8d 64 24 80 e8 d0 d1 d2 d3
+ *
+ * 2) Trap new entries before restoring the NOP bytes:
+ * [cc] 8d 64 24 80 e8 d0 d1 d2 d3
+ */
+ err = uprobe_write(auprobe, vma, vaddr, &int3, 1, verify_insn,
true /* is_register */, false /* do_update_ref_ctr */,
&ctx);
if (err)
@@ -943,13 +1031,31 @@ static int int3_update(struct arch_uprobe *auprobe, struct vm_area_struct *vma,
smp_text_poke_sync_each_cpu();
/*
- * Write first byte.
+ * 3) Restore bytes 1..4 of the original NOP while keeping byte 0 trapped
+ * and byte 5 as CALL:
+ * cc [2e 0f 1f 84] e8 d0 d1 d2 d3
+ */
+ ctx.expect = EXPECT_SWBP_OPTIMIZED;
+ err = uprobe_write(auprobe, vma, vaddr + 1, insn + 1,
+ LEA_INSN_SIZE - 1, verify_insn,
+ true /* is_register */, false /* do_update_ref_ctr */,
+ &ctx);
+ if (err)
+ return err;
+
+ smp_text_poke_sync_each_cpu();
+
+ /*
+ * 4) Publish the first byte of the original NOP:
+ * [66] 2e 0f 1f 84 e8 d0 d1 d2 d3
*
- * The swbp_unoptimize needs to finish uprobe removal together
- * with ref_ctr update, using uprobe_write with proper flags.
+ * From offset 0 this is the restored 10-byte NOP; the CALL opcode and
+ * displacement are now only NOP operands. Offset 5 still decodes as
+ * CALL for a thread that was already there.
*/
+ ctx.expect = EXPECT_SWBP;
err = uprobe_write(auprobe, vma, vaddr, insn, 1, verify_insn,
- optimize /* is_register */, !optimize /* do_update_ref_ctr */,
+ false /* is_register */, true /* do_update_ref_ctr */,
&ctx);
if (err)
return err;
@@ -961,17 +1067,25 @@ static int int3_update(struct arch_uprobe *auprobe, struct vm_area_struct *vma,
static int swbp_optimize(struct arch_uprobe *auprobe, struct vm_area_struct *vma,
unsigned long vaddr, unsigned long tramp)
{
- u8 call[5];
+ u8 insn[OPT_INSN_SIZE], *call = &insn[LEA_INSN_SIZE];
- __text_gen_insn(call, CALL_INSN_OPCODE, (const void *) vaddr,
+ /*
+ * We have nop10 instruction (with first byte overwritten to int3),
+ * changing it to:
+ * lea -0x80(%rsp), %rsp
+ * call tramp
+ */
+ memcpy(insn, lea_rsp, LEA_INSN_SIZE);
+ __text_gen_insn(call, CALL_INSN_OPCODE,
+ (const void *) (vaddr + LEA_INSN_SIZE),
(const void *) tramp, CALL_INSN_SIZE);
- return int3_update(auprobe, vma, vaddr, call, true /* optimize */);
+ return int3_update_optimize(auprobe, vma, vaddr, insn);
}
static int swbp_unoptimize(struct arch_uprobe *auprobe, struct vm_area_struct *vma,
unsigned long vaddr)
{
- return int3_update(auprobe, vma, vaddr, auprobe->insn, false /* optimize */);
+ return int3_update_unoptimize(auprobe, vma, vaddr, auprobe->insn);
}
static int copy_from_vaddr(struct mm_struct *mm, unsigned long vaddr, void *dst, int len)
@@ -993,19 +1107,19 @@ static bool __is_optimized(struct mm_struct *mm, uprobe_opcode_t *insn, unsigned
struct __packed __arch_relative_insn {
u8 op;
s32 raddr;
- } *call = (struct __arch_relative_insn *) insn;
+ } *call = (struct __arch_relative_insn *)(insn + LEA_INSN_SIZE);
- if (!is_call_insn(insn))
+ if (!is_opt_insns(insn))
return false;
- return __in_uprobe_trampoline(mm, vaddr + 5 + call->raddr);
+ return __in_uprobe_trampoline(mm, vaddr + OPT_INSN_SIZE + call->raddr);
}
static int is_optimized(struct mm_struct *mm, unsigned long vaddr)
{
- uprobe_opcode_t insn[5];
+ uprobe_opcode_t insn[OPT_INSN_SIZE];
int err;
- err = copy_from_vaddr(mm, vaddr, &insn, 5);
+ err = copy_from_vaddr(mm, vaddr, &insn, OPT_INSN_SIZE);
if (err)
return err;
return __is_optimized(mm, (uprobe_opcode_t *)&insn, vaddr);
@@ -1077,7 +1191,7 @@ static int __arch_uprobe_optimize(struct arch_uprobe *auprobe, struct mm_struct
void arch_uprobe_optimize(struct arch_uprobe *auprobe, unsigned long vaddr)
{
struct mm_struct *mm = current->mm;
- uprobe_opcode_t insn[5];
+ uprobe_opcode_t insn[OPT_INSN_SIZE];
if (!should_optimize(auprobe))
return;
@@ -1088,7 +1202,7 @@ void arch_uprobe_optimize(struct arch_uprobe *auprobe, unsigned long vaddr)
* Check if some other thread already optimized the uprobe for us,
* if it's the case just go away silently.
*/
- if (copy_from_vaddr(mm, vaddr, &insn, 5))
+ if (copy_from_vaddr(mm, vaddr, &insn, OPT_INSN_SIZE))
goto unlock;
if (!is_swbp_insn((uprobe_opcode_t*) &insn))
goto unlock;
@@ -1104,16 +1218,32 @@ void arch_uprobe_optimize(struct arch_uprobe *auprobe, unsigned long vaddr)
mmap_write_unlock(mm);
}
+static bool is_optimizable_nop10(struct insn *insn)
+{
+ static const u8 nop10_prefix[] = {
+ 0x66, 0x2e, 0x0f, 0x1f, 0x84
+ };
+
+ /*
+ * Restrict this to the 10-byte NOP form whose last 5 bytes are
+ * SIB/displacement operands. Unoptimization keeps the call opcode and
+ * displacement in those bytes, so other NOP encodings are not safe.
+ */
+ return insn->length == OPT_INSN_SIZE &&
+ insn_is_nop(insn) &&
+ !memcmp(insn->kaddr, nop10_prefix, ARRAY_SIZE(nop10_prefix));
+}
+
static bool can_optimize(struct insn *insn, unsigned long vaddr)
{
- if (!insn->x86_64 || insn->length != 5)
+ if (!insn->x86_64)
return false;
- if (!insn_is_nop(insn))
+ if (!is_optimizable_nop10(insn))
return false;
/* We can't do cross page atomic writes yet. */
- return PAGE_SIZE - (vaddr & ~PAGE_MASK) >= 5;
+ return PAGE_SIZE - (vaddr & ~PAGE_MASK) >= OPT_INSN_SIZE;
}
#else /* 32-bit: */
/*
@@ -1485,16 +1615,26 @@ static int push_setup_xol_ops(struct arch_uprobe *auprobe, struct insn *insn)
*/
int arch_uprobe_analyze_insn(struct arch_uprobe *auprobe, struct mm_struct *mm, unsigned long addr)
{
+ enum insn_mode m = is_64bit_mm(mm) ? INSN_MODE_64 : INSN_MODE_32;
u8 fix_ip_or_call = UPROBE_FIX_IP;
struct insn insn;
int ret;
- ret = uprobe_init_insn(auprobe, &insn, is_64bit_mm(mm));
- if (ret)
- return ret;
+ ret = insn_decode(&insn, auprobe->insn, sizeof(auprobe->insn), m);
+ if (ret < 0)
+ return -ENOEXEC;
- if (can_optimize(&insn, addr))
+ /*
+ * No need to check instruction in uprobe_init_insn in case we
+ * are on top of optimizable nop10.
+ */
+ if (can_optimize(&insn, addr)) {
set_bit(ARCH_UPROBE_FLAG_CAN_OPTIMIZE, &auprobe->flags);
+ } else {
+ ret = uprobe_init_insn(auprobe, &insn);
+ if (ret)
+ return ret;
+ }
ret = branch_setup_xol_ops(auprobe, &insn);
if (ret != -ENOSYS)
--
2.54.0
^ permalink raw reply related
* [PATCHv6 04/13] uprobes/x86: Allow to copy uprobe trampolines on fork
From: Jiri Olsa @ 2026-07-03 11:49 UTC (permalink / raw)
To: Oleg Nesterov, Peter Zijlstra, Ingo Molnar, Masami Hiramatsu,
Andrii Nakryiko
Cc: bpf, linux-trace-kernel
In-Reply-To: <20260703114917.238144-1-jolsa@kernel.org>
When we do fork or clone without CLONE_VM the new process won't
have uprobe trampoline vma objects and at the same time it will
have optimized code calling that trampoline and crash.
Fixing this by allowing vma uprobe trampoline objects to be copied
on fork to the new process.
Fixes: ba2bfc97b462 ("uprobes/x86: Add support to optimize uprobes")
Reviewed-by: Oleg Nesterov <oleg@redhat.com>
Signed-off-by: Jiri Olsa <jolsa@kernel.org>
---
arch/x86/kernel/uprobes.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/arch/x86/kernel/uprobes.c b/arch/x86/kernel/uprobes.c
index 5730d41eb5f2..af5af7d67999 100644
--- a/arch/x86/kernel/uprobes.c
+++ b/arch/x86/kernel/uprobes.c
@@ -701,7 +701,7 @@ static struct vm_area_struct *get_uprobe_trampoline(struct mm_struct *mm, unsign
*new_mapping = true;
return _install_special_mapping(mm, vaddr, PAGE_SIZE,
- VM_READ|VM_EXEC|VM_MAYEXEC|VM_MAYREAD|VM_DONTCOPY|VM_IO,
+ VM_READ|VM_EXEC|VM_MAYEXEC|VM_MAYREAD|VM_IO,
&tramp_mapping);
}
--
2.54.0
^ permalink raw reply related
* [PATCHv6 03/13] uprobes/x86: Do not leak trampoline vma mapping on optimization failure
From: Jiri Olsa @ 2026-07-03 11:49 UTC (permalink / raw)
To: Oleg Nesterov, Peter Zijlstra, Ingo Molnar, Masami Hiramatsu,
Andrii Nakryiko
Cc: bpf, linux-trace-kernel
In-Reply-To: <20260703114917.238144-1-jolsa@kernel.org>
In case the optimization fails, we leak new-ly created trampoline
vma mapping (in case we just created it), let's unmap it.
Fixes: ba2bfc97b462 ("uprobes/x86: Add support to optimize uprobes")
Reviewed-by: Oleg Nesterov <oleg@redhat.com>
Signed-off-by: Jiri Olsa <jolsa@kernel.org>
---
arch/x86/kernel/uprobes.c | 14 +++++++++++---
1 file changed, 11 insertions(+), 3 deletions(-)
diff --git a/arch/x86/kernel/uprobes.c b/arch/x86/kernel/uprobes.c
index d2933cf77cd3..5730d41eb5f2 100644
--- a/arch/x86/kernel/uprobes.c
+++ b/arch/x86/kernel/uprobes.c
@@ -677,11 +677,14 @@ static unsigned long find_nearest_trampoline(unsigned long vaddr)
return high_tramp;
}
-static struct vm_area_struct *get_uprobe_trampoline(struct mm_struct *mm, unsigned long vaddr)
+static struct vm_area_struct *get_uprobe_trampoline(struct mm_struct *mm, unsigned long vaddr,
+ bool *new_mapping)
{
VMA_ITERATOR(vmi, mm, 0);
struct vm_area_struct *vma;
+ *new_mapping = false;
+
if (vaddr > TASK_SIZE || vaddr < PAGE_SIZE)
return ERR_PTR(-EINVAL);
@@ -696,6 +699,7 @@ static struct vm_area_struct *get_uprobe_trampoline(struct mm_struct *mm, unsign
if (IS_ERR_VALUE(vaddr))
return ERR_PTR(vaddr);
+ *new_mapping = true;
return _install_special_mapping(mm, vaddr, PAGE_SIZE,
VM_READ|VM_EXEC|VM_MAYEXEC|VM_MAYREAD|VM_DONTCOPY|VM_IO,
&tramp_mapping);
@@ -1053,6 +1057,7 @@ static int __arch_uprobe_optimize(struct arch_uprobe *auprobe, struct mm_struct
{
struct pt_regs *regs = task_pt_regs(current);
struct vm_area_struct *vma, *tramp;
+ bool new_mapping;
int ret;
if (!user_64bit_mode(regs))
@@ -1060,10 +1065,13 @@ static int __arch_uprobe_optimize(struct arch_uprobe *auprobe, struct mm_struct
vma = find_vma(mm, vaddr);
if (!vma)
return -EINVAL;
- tramp = get_uprobe_trampoline(mm, vaddr);
+ tramp = get_uprobe_trampoline(mm, vaddr, &new_mapping);
if (IS_ERR(tramp))
return PTR_ERR(tramp);
- return WARN_ON_ONCE(swbp_optimize(auprobe, vma, vaddr, tramp->vm_start));
+ ret = swbp_optimize(auprobe, vma, vaddr, tramp->vm_start);
+ if (WARN_ON_ONCE(ret) && new_mapping)
+ WARN_ON_ONCE(do_munmap(mm, tramp->vm_start, PAGE_SIZE, NULL));
+ return ret;
}
void arch_uprobe_optimize(struct arch_uprobe *auprobe, unsigned long vaddr)
--
2.54.0
^ permalink raw reply related
* [PATCHv6 02/13] uprobes/x86: Remove struct uprobe_trampoline object
From: Jiri Olsa @ 2026-07-03 11:49 UTC (permalink / raw)
To: Oleg Nesterov, Peter Zijlstra, Ingo Molnar, Masami Hiramatsu,
Andrii Nakryiko
Cc: bpf, linux-trace-kernel
In-Reply-To: <20260703114917.238144-1-jolsa@kernel.org>
Removing struct uprobe_trampoline object and it's tracking code,
because it's not needed. We can do same thing directly on top of
struct vm_area_struct objects.
This makes the code simpler and allows easy propagation of the
trampoline vma object into child process in following change.
Note the original code called destroy_uprobe_trampoline if the
optimiation failed, but it only freed the struct uprobe_trampoline
object, not the vma. The new vma leak is fixed in following change.
Acked-by: Andrii Nakryiko <andrii@kernel.org>
Reviewed-by: Oleg Nesterov <oleg@redhat.com>
Signed-off-by: Jiri Olsa <jolsa@kernel.org>
---
arch/x86/kernel/uprobes.c | 106 ++++++++------------------------------
include/linux/uprobes.h | 5 --
kernel/events/uprobes.c | 10 ----
kernel/fork.c | 1 -
4 files changed, 22 insertions(+), 100 deletions(-)
diff --git a/arch/x86/kernel/uprobes.c b/arch/x86/kernel/uprobes.c
index 2be6707e3320..d2933cf77cd3 100644
--- a/arch/x86/kernel/uprobes.c
+++ b/arch/x86/kernel/uprobes.c
@@ -631,11 +631,6 @@ static struct vm_special_mapping tramp_mapping = {
.pages = tramp_mapping_pages,
};
-struct uprobe_trampoline {
- struct hlist_node node;
- unsigned long vaddr;
-};
-
static bool is_reachable_by_call(unsigned long vtramp, unsigned long vaddr)
{
long delta = (long)(vaddr + 5 - vtramp);
@@ -682,83 +677,28 @@ static unsigned long find_nearest_trampoline(unsigned long vaddr)
return high_tramp;
}
-static struct uprobe_trampoline *create_uprobe_trampoline(unsigned long vaddr)
+static struct vm_area_struct *get_uprobe_trampoline(struct mm_struct *mm, unsigned long vaddr)
{
- struct pt_regs *regs = task_pt_regs(current);
- struct mm_struct *mm = current->mm;
- struct uprobe_trampoline *tramp;
+ VMA_ITERATOR(vmi, mm, 0);
struct vm_area_struct *vma;
- if (!user_64bit_mode(regs))
- return NULL;
+ if (vaddr > TASK_SIZE || vaddr < PAGE_SIZE)
+ return ERR_PTR(-EINVAL);
+
+ for_each_vma(vmi, vma) {
+ if (!vma_is_special_mapping(vma, &tramp_mapping))
+ continue;
+ if (is_reachable_by_call(vma->vm_start, vaddr))
+ return vma;
+ }
vaddr = find_nearest_trampoline(vaddr);
if (IS_ERR_VALUE(vaddr))
- return NULL;
+ return ERR_PTR(vaddr);
- tramp = kzalloc_obj(*tramp);
- if (unlikely(!tramp))
- return NULL;
-
- tramp->vaddr = vaddr;
- vma = _install_special_mapping(mm, tramp->vaddr, PAGE_SIZE,
+ return _install_special_mapping(mm, vaddr, PAGE_SIZE,
VM_READ|VM_EXEC|VM_MAYEXEC|VM_MAYREAD|VM_DONTCOPY|VM_IO,
&tramp_mapping);
- if (IS_ERR(vma)) {
- kfree(tramp);
- return NULL;
- }
- return tramp;
-}
-
-static struct uprobe_trampoline *get_uprobe_trampoline(unsigned long vaddr, bool *new)
-{
- struct uprobes_state *state = ¤t->mm->uprobes_state;
- struct uprobe_trampoline *tramp = NULL;
-
- if (vaddr > TASK_SIZE || vaddr < PAGE_SIZE)
- return NULL;
-
- hlist_for_each_entry(tramp, &state->head_tramps, node) {
- if (is_reachable_by_call(tramp->vaddr, vaddr)) {
- *new = false;
- return tramp;
- }
- }
-
- tramp = create_uprobe_trampoline(vaddr);
- if (!tramp)
- return NULL;
-
- *new = true;
- hlist_add_head(&tramp->node, &state->head_tramps);
- return tramp;
-}
-
-static void destroy_uprobe_trampoline(struct uprobe_trampoline *tramp)
-{
- /*
- * We do not unmap and release uprobe trampoline page itself,
- * because there's no easy way to make sure none of the threads
- * is still inside the trampoline.
- */
- hlist_del(&tramp->node);
- kfree(tramp);
-}
-
-void arch_uprobe_init_state(struct mm_struct *mm)
-{
- INIT_HLIST_HEAD(&mm->uprobes_state.head_tramps);
-}
-
-void arch_uprobe_clear_state(struct mm_struct *mm)
-{
- struct uprobes_state *state = &mm->uprobes_state;
- struct uprobe_trampoline *tramp;
- struct hlist_node *n;
-
- hlist_for_each_entry_safe(tramp, n, &state->head_tramps, node)
- destroy_uprobe_trampoline(tramp);
}
static bool __in_uprobe_trampoline(struct mm_struct *mm, unsigned long ip)
@@ -1111,21 +1051,19 @@ int set_orig_insn(struct arch_uprobe *auprobe, struct vm_area_struct *vma,
static int __arch_uprobe_optimize(struct arch_uprobe *auprobe, struct mm_struct *mm,
unsigned long vaddr)
{
- struct uprobe_trampoline *tramp;
- struct vm_area_struct *vma;
- bool new = false;
- int err = 0;
+ struct pt_regs *regs = task_pt_regs(current);
+ struct vm_area_struct *vma, *tramp;
+ int ret;
+ if (!user_64bit_mode(regs))
+ return -EINVAL;
vma = find_vma(mm, vaddr);
if (!vma)
return -EINVAL;
- tramp = get_uprobe_trampoline(vaddr, &new);
- if (!tramp)
- return -EINVAL;
- err = swbp_optimize(auprobe, vma, vaddr, tramp->vaddr);
- if (WARN_ON_ONCE(err) && new)
- destroy_uprobe_trampoline(tramp);
- return err;
+ tramp = get_uprobe_trampoline(mm, vaddr);
+ if (IS_ERR(tramp))
+ return PTR_ERR(tramp);
+ return WARN_ON_ONCE(swbp_optimize(auprobe, vma, vaddr, tramp->vm_start));
}
void arch_uprobe_optimize(struct arch_uprobe *auprobe, unsigned long vaddr)
diff --git a/include/linux/uprobes.h b/include/linux/uprobes.h
index f548fea2adec..18be159bbc34 100644
--- a/include/linux/uprobes.h
+++ b/include/linux/uprobes.h
@@ -186,9 +186,6 @@ struct xol_area;
struct uprobes_state {
struct xol_area *xol_area;
-#ifdef CONFIG_X86_64
- struct hlist_head head_tramps;
-#endif
};
typedef int (*uprobe_write_verify_t)(struct page *page, unsigned long vaddr,
@@ -238,8 +235,6 @@ extern void uprobe_handle_trampoline(struct pt_regs *regs);
extern void *arch_uretprobe_trampoline(unsigned long *psize);
extern unsigned long uprobe_get_trampoline_vaddr(void);
extern void uprobe_copy_from_page(struct page *page, unsigned long vaddr, void *dst, int len);
-extern void arch_uprobe_clear_state(struct mm_struct *mm);
-extern void arch_uprobe_init_state(struct mm_struct *mm);
extern void handle_syscall_uprobe(struct pt_regs *regs, unsigned long bp_vaddr);
extern void arch_uprobe_optimize(struct arch_uprobe *auprobe, unsigned long vaddr);
extern unsigned long arch_uprobe_get_xol_area(void);
diff --git a/kernel/events/uprobes.c b/kernel/events/uprobes.c
index 4084e926e284..b5c516168f84 100644
--- a/kernel/events/uprobes.c
+++ b/kernel/events/uprobes.c
@@ -1806,14 +1806,6 @@ static struct xol_area *get_xol_area(void)
return area;
}
-void __weak arch_uprobe_clear_state(struct mm_struct *mm)
-{
-}
-
-void __weak arch_uprobe_init_state(struct mm_struct *mm)
-{
-}
-
/*
* uprobe_clear_state - Free the area allocated for slots.
*/
@@ -1825,8 +1817,6 @@ void uprobe_clear_state(struct mm_struct *mm)
delayed_uprobe_remove(NULL, mm);
mutex_unlock(&delayed_uprobe_lock);
- arch_uprobe_clear_state(mm);
-
if (!area)
return;
diff --git a/kernel/fork.c b/kernel/fork.c
index 13e38e89a1f3..00b52c7314d1 100644
--- a/kernel/fork.c
+++ b/kernel/fork.c
@@ -1064,7 +1064,6 @@ static void mm_init_uprobes_state(struct mm_struct *mm)
{
#ifdef CONFIG_UPROBES
mm->uprobes_state.xol_area = NULL;
- arch_uprobe_init_state(mm);
#endif
}
--
2.54.0
^ permalink raw reply related
* [PATCHv6 01/13] uprobes/x86: Use proper mm_struct in __in_uprobe_trampoline
From: Jiri Olsa @ 2026-07-03 11:49 UTC (permalink / raw)
To: Oleg Nesterov, Peter Zijlstra, Ingo Molnar, Masami Hiramatsu,
Andrii Nakryiko
Cc: bpf, linux-trace-kernel
In-Reply-To: <20260703114917.238144-1-jolsa@kernel.org>
In the unregister path we use __in_uprobe_trampoline check with
current->mm for the VMA lookup, which is wrong, because we are
in the tracer context, not the traced process.
Add mm_struct pointer argument to __in_uprobe_trampoline and
changing related callers to pass proper mm_struct pointer.
Fixes: ba2bfc97b462 ("uprobes/x86: Add support to optimize uprobes")
Acked-by: Andrii Nakryiko <andrii@kernel.org>
Reviewed-by: Oleg Nesterov <oleg@redhat.com>
Signed-off-by: Jiri Olsa <jolsa@kernel.org>
---
arch/x86/kernel/uprobes.c | 14 +++++++-------
1 file changed, 7 insertions(+), 7 deletions(-)
diff --git a/arch/x86/kernel/uprobes.c b/arch/x86/kernel/uprobes.c
index ebb1baf1eb1d..2be6707e3320 100644
--- a/arch/x86/kernel/uprobes.c
+++ b/arch/x86/kernel/uprobes.c
@@ -761,9 +761,9 @@ void arch_uprobe_clear_state(struct mm_struct *mm)
destroy_uprobe_trampoline(tramp);
}
-static bool __in_uprobe_trampoline(unsigned long ip)
+static bool __in_uprobe_trampoline(struct mm_struct *mm, unsigned long ip)
{
- struct vm_area_struct *vma = vma_lookup(current->mm, ip);
+ struct vm_area_struct *vma = vma_lookup(mm, ip);
return vma && vma_is_special_mapping(vma, &tramp_mapping);
}
@@ -776,14 +776,14 @@ static bool in_uprobe_trampoline(unsigned long ip)
rcu_read_lock();
if (mmap_lock_speculate_try_begin(mm, &seq)) {
- found = __in_uprobe_trampoline(ip);
+ found = __in_uprobe_trampoline(mm, ip);
retry = mmap_lock_speculate_retry(mm, seq);
}
rcu_read_unlock();
if (retry) {
mmap_read_lock(mm);
- found = __in_uprobe_trampoline(ip);
+ found = __in_uprobe_trampoline(mm, ip);
mmap_read_unlock(mm);
}
return found;
@@ -1044,7 +1044,7 @@ static int copy_from_vaddr(struct mm_struct *mm, unsigned long vaddr, void *dst,
return 0;
}
-static bool __is_optimized(uprobe_opcode_t *insn, unsigned long vaddr)
+static bool __is_optimized(struct mm_struct *mm, uprobe_opcode_t *insn, unsigned long vaddr)
{
struct __packed __arch_relative_insn {
u8 op;
@@ -1053,7 +1053,7 @@ static bool __is_optimized(uprobe_opcode_t *insn, unsigned long vaddr)
if (!is_call_insn(insn))
return false;
- return __in_uprobe_trampoline(vaddr + 5 + call->raddr);
+ return __in_uprobe_trampoline(mm, vaddr + 5 + call->raddr);
}
static int is_optimized(struct mm_struct *mm, unsigned long vaddr)
@@ -1064,7 +1064,7 @@ static int is_optimized(struct mm_struct *mm, unsigned long vaddr)
err = copy_from_vaddr(mm, vaddr, &insn, 5);
if (err)
return err;
- return __is_optimized((uprobe_opcode_t *)&insn, vaddr);
+ return __is_optimized(mm, (uprobe_opcode_t *)&insn, vaddr);
}
static bool should_optimize(struct arch_uprobe *auprobe)
--
2.54.0
^ permalink raw reply related
* [PATCHv6 00/13] uprobes/x86: Fix red zone issue for optimized uprobes
From: Jiri Olsa @ 2026-07-03 11:49 UTC (permalink / raw)
To: Oleg Nesterov, Peter Zijlstra, Ingo Molnar, Masami Hiramatsu,
Andrii Nakryiko
Cc: bpf, linux-trace-kernel
hi,
Andrii reported an issue with optimized uprobes [1] that can clobber
redzone area with call instruction storing return address on stack
where user code may keep temporary data without adjusting rsp.
Fixing this by moving the optimized uprobes on top of 10-bytes nop
instruction, so we can squeeze another instruction to escape the
redzone area before doing the call.
Note we need upstream update first for patch 3 (github.com/libbpf/usdt),
if we decide to take this change.
thanks,
jirka
v1: https://lore.kernel.org/bpf/20260514135342.22130-1-jolsa@kernel.org/
v2: https://lore.kernel.org/bpf/20260518105957.123445-1-jolsa@kernel.org/
v3: https://lore.kernel.org/bpf/20260521124411.31133-1-jolsa@kernel.org/
v4: https://lore.kernel.org/bpf/20260526205840.173790-1-jolsa@kernel.org/
v5: https://lore.kernel.org/bpf/20260701111337.53943-1-jolsa@kernel.org/
v6 changes:
- added upstream commit ref for patch#8
- release link in test_uprobe_fork_optimized [ci/asan]
v5 changes:
- several selftests changes and reviewed-by tags [Jakub]
- add more comments in int3_update_unoptimize [Andrii]
- several other minor changes and acks [Oleg]
- move insn_decode out of uprobe_init_insn to simplify the code
- align uprobe_red_zone_test to 64 to make sure nop10 is not on page boundary
v4 changes:
- do not use 2nd int3 (ont +5 offset) because the call instruction
is allways the same for the given nop10 address [Andrii/Peter]
- unmap unused trampoline vma after unsuccesfull optimization [sashiko]
- small change to patch#2 moved user_64bit_mode earlier in the path
and pass/use mm_struct pointer directly from arch_uprobe_optimize
instead of gettting current->mm
Andrii, keeping your ack, please shout otherwise
v3 changes:
- use nop10 update suggested by Peter in [2]
- remove struct uprobe_trampoline object, use vma objects directly instead
- selftests fixes [sashiko]
- ack from Andrii
v2 changes:
- several selftest fixes [sashiko]
- consolidate is_lea_insn and is_call_insn insto single check [Jakub Sitnicki]
- use proper mm_struct object in __in_uprobe_trampoline check [sashiko]
- allow to copy uprobe trampolines vma objects on fork [sashiko]
- change uprobe syscall detection error from -ENXIO to -EPROTO [Andrii]
- added fork/clone tests
- I kept the selftest changes and nop5->nop10 changes in separate
commits for easier review, we can squash them later if we want to keep
bisect working properly
[1] https://lore.kernel.org/bpf/20260509003146.976844-1-andrii@kernel.org/
[2] https://lore.kernel.org/bpf/20260518104306.GU3102624@noisy.programming.kicks-ass.net/#t
---
Andrii Nakryiko (1):
selftests/bpf: Add tests for uprobe nop10 red zone clobbering
Jiri Olsa (12):
uprobes/x86: Use proper mm_struct in __in_uprobe_trampoline
uprobes/x86: Remove struct uprobe_trampoline object
uprobes/x86: Do not leak trampoline vma mapping on optimization failure
uprobes/x86: Allow to copy uprobe trampolines on fork
uprobes/x86: Move optimized uprobe from nop5 to nop10
libbpf: Change has_nop_combo to work on top of nop10
libbpf: Detect uprobe syscall with new error
selftests/bpf: Emit nop,nop10 instructions combo for x86_64 arch
selftests/bpf: Change uprobe syscall tests to use nop10
selftests/bpf: Change uprobe/usdt trigger bench code to use nop10
selftests/bpf: Add reattach tests for uprobe syscall
selftests/bpf: Add tests for forked/cloned optimized uprobes
arch/x86/kernel/uprobes.c | 416 +++++++++++++++++++++++++++++++++++++++++++-----------------------------
include/linux/uprobes.h | 5 -
kernel/events/uprobes.c | 10 --
kernel/fork.c | 1 -
tools/lib/bpf/features.c | 4 +-
tools/lib/bpf/usdt.c | 16 +--
tools/testing/selftests/bpf/bench.c | 20 ++--
tools/testing/selftests/bpf/benchs/bench_trigger.c | 38 +++----
tools/testing/selftests/bpf/benchs/run_bench_uprobes.sh | 2 +-
tools/testing/selftests/bpf/prog_tests/uprobe_syscall.c | 325 ++++++++++++++++++++++++++++++++++++++++++++++++++++----
tools/testing/selftests/bpf/prog_tests/usdt.c | 74 +++++++++++--
tools/testing/selftests/bpf/progs/test_usdt.c | 25 +++++
tools/testing/selftests/bpf/usdt.h | 2 +-
tools/testing/selftests/bpf/usdt_2.c | 15 ++-
14 files changed, 697 insertions(+), 256 deletions(-)
^ permalink raw reply
* Re: [PATCHv5 00/13] uprobes/x86: Fix red zone issue for optimized uprobes
From: Jiri Olsa @ 2026-07-03 11:07 UTC (permalink / raw)
To: Peter Zijlstra
Cc: Andrii Nakryiko, Jiri Olsa, Oleg Nesterov, Ingo Molnar,
Masami Hiramatsu, Andrii Nakryiko, bpf, linux-trace-kernel
In-Reply-To: <20260703105859.GD651302@noisy.programming.kicks-ass.net>
On Fri, Jul 03, 2026 at 12:58:59PM +0200, Peter Zijlstra wrote:
> On Thu, Jul 02, 2026 at 09:20:25AM -0700, Andrii Nakryiko wrote:
>
> > > I failed to release link in test_uprobe_fork_optimized, fix is below
> > > I can send new version or separate fix
> >
> > yeah, please fix the test, adjust comments as pointed out by AI and
> > send v6. Seems like Peter wants to pick it up through tip, I don't
> > mind.
>
> So I should wait for v6, and not munge the below delta in?
yep, I'll send it out later today
thanks,
jirka
^ permalink raw reply
* Re: [PATCHv5 00/13] uprobes/x86: Fix red zone issue for optimized uprobes
From: Peter Zijlstra @ 2026-07-03 10:58 UTC (permalink / raw)
To: Andrii Nakryiko
Cc: Jiri Olsa, Oleg Nesterov, Ingo Molnar, Masami Hiramatsu,
Andrii Nakryiko, bpf, linux-trace-kernel
In-Reply-To: <CAEf4BzaDwwW1ebPO+4r5rhmdyXk+XddWTDBzN4iOaCU8qEPZRw@mail.gmail.com>
On Thu, Jul 02, 2026 at 09:20:25AM -0700, Andrii Nakryiko wrote:
> > I failed to release link in test_uprobe_fork_optimized, fix is below
> > I can send new version or separate fix
>
> yeah, please fix the test, adjust comments as pointed out by AI and
> send v6. Seems like Peter wants to pick it up through tip, I don't
> mind.
So I should wait for v6, and not munge the below delta in?
^ permalink raw reply
* Re: [PATCH] tracing: Cleanup event_enable_trigger_parse() by using __free()
From: Markus Elfring @ 2026-07-03 5:00 UTC (permalink / raw)
To: Steven Rostedt, linux-trace-kernel, kernel-janitors
Cc: Masami Hiramatsu, Mathieu Desnoyers, LKML
In-Reply-To: <20260702165346.035f8eef@gandalf.local.home>
>> …
>>> +++ b/kernel/trace/trace_events_trigger.c
>>> @@ -1739,7 +1739,7 @@ int event_enable_trigger_parse(struct event_command *cmd_ops,
>>> char *glob, char *cmd, char *param_and_filter)
>>> {
>>> struct trace_event_file *event_enable_file;
>>> - struct enable_trigger_data *enable_data;
>>> + struct enable_trigger_data *enable_data __free(kfree) = NULL;
>> …
>>
>> How do you think about to reduce the scope for this local variable?
>>
>
> No, it's fine as is.
Does this feedback mean that you disagree to an essential development requirement
according to such a programming interface?
https://elixir.bootlin.com/linux/v7.1.2/source/include/linux/cleanup.h#L142-L153
Regards,
Markus
^ permalink raw reply
* Re: [PATCH 1/2] i2c: qcom-geni: trace: Add trace events for Qualcomm GENI I2C
From: Steven Rostedt @ 2026-07-02 23:09 UTC (permalink / raw)
To: Praveen Talari
Cc: Masami Hiramatsu, Mathieu Desnoyers, Mukesh Kumar Savaliya,
Viken Dadhaniya, Andi Shyti, linux-kernel, linux-trace-kernel,
linux-arm-msm, linux-i2c, aniket.randive, chandana.chiluveru
In-Reply-To: <20260630-add-tracepoints-for-qcom-geni-i2c-v1-1-474cd6cdbe27@oss.qualcomm.com>
On Tue, 30 Jun 2026 11:32:45 +0530
Praveen Talari <praveen.talari@oss.qualcomm.com> wrote:
> Add trace event support to the Qualcomm GENI I2C driver to enable
> detailed runtime debugging and analysis.
>
> The trace events capture I2C clock configuration, interrupt status
> and error code and message.
>
> Signed-off-by: Praveen Talari <praveen.talari@oss.qualcomm.com>
> ---
> include/trace/events/qcom_geni_i2c.h | 82 ++++++++++++++++++++++++++++++++++++
> 1 file changed, 82 insertions(+)
>
> diff --git a/include/trace/events/qcom_geni_i2c.h b/include/trace/events/qcom_geni_i2c.h
> new file mode 100644
> index 000000000000..c7e7984f3620
> --- /dev/null
Reviewed-by: Steven Rostedt <rostedt@goodmis.org>
-- Steve
^ permalink raw reply
* Re: [PATCH] tracing: make tracepoint_printk static as not exported
From: Steven Rostedt @ 2026-07-02 22:46 UTC (permalink / raw)
To: Ben Dooks
Cc: Masami Hiramatsu, Mathieu Desnoyers, linux-kernel,
linux-trace-kernel
In-Reply-To: <20260617105822.904164-1-ben.dooks@codethink.co.uk>
On Wed, 17 Jun 2026 11:58:22 +0100
Ben Dooks <ben.dooks@codethink.co.uk> wrote:
> The tracepoint_printk symbol is not exported, so make it
> static to remove the following sparse warning:
>
> kernel/trace/trace.c:90:5: warning: symbol 'tracepoint_printk' was not declared. Should it be static?
>
> Signed-off-by: Ben Dooks <ben.dooks@codethink.co.uk>
Thanks, this became only used by this file via commit dd293df6395a2
("tracing: Move trace sysctls into trace.c")
I may add a Fixes tag to that commit.
-- Steve
> ---
> kernel/trace/trace.c | 2 +-
> 1 file changed, 1 insertion(+), 1 deletion(-)
>
> diff --git a/kernel/trace/trace.c b/kernel/trace/trace.c
> index 6eb4d3097a4d..4c3729c8d5e2 100644
> --- a/kernel/trace/trace.c
> +++ b/kernel/trace/trace.c
> @@ -87,7 +87,7 @@ void __init disable_tracing_selftest(const char *reason)
>
> /* Pipe tracepoints to printk */
> static struct trace_iterator *tracepoint_print_iter;
> -int tracepoint_printk;
> +static int tracepoint_printk;
> static bool tracepoint_printk_stop_on_boot __initdata;
> static bool traceoff_after_boot __initdata;
> static DEFINE_STATIC_KEY_FALSE(tracepoint_printk_key);
^ permalink raw reply
* Re: [PATCH v3 2/2] tracing: Remove trace_printk.h from kernel.h
From: Steven Rostedt @ 2026-07-02 20:57 UTC (permalink / raw)
To: Uwe Kleine-König
Cc: linux-kernel, linux-trace-kernel, Masami Hiramatsu, Mark Rutland,
Mathieu Desnoyers, Andrew Morton, Linus Torvalds,
Sebastian Andrzej Siewior, John Ogness, Thomas Gleixner,
Peter Zijlstra, Julia Lawall, Yury Norov, regressions
In-Reply-To: <akZpICZ0pGqspV2u@monoceros>
On Thu, 2 Jul 2026 15:43:14 +0200
Uwe Kleine-König <u.kleine-koenig@baylibre.com> wrote:
> This patch became commit 9cbc3d9806d3 ("tracing: Remove trace_printk.h
> from kernel.h") that currently is in next via
>
> https://git.kernel.org/pub/scm/linux/kernel/git/trace/linux-trace.git trace/fixes
Thanks for letting me know. I'll remove it and push it up.
I forgot I had it in there :-p
-- Steve
^ permalink raw reply
* Re: [PATCH] tracing: Cleanup event_enable_trigger_parse() by using __free()
From: Steven Rostedt @ 2026-07-02 20:53 UTC (permalink / raw)
To: Markus Elfring
Cc: linux-trace-kernel, Masami Hiramatsu, Mathieu Desnoyers, LKML
In-Reply-To: <79106214-b341-46ab-8810-a206ce8fad29@web.de>
On Thu, 2 Jul 2026 11:58:02 +0200
Markus Elfring <Markus.Elfring@web.de> wrote:
> …
> > +++ b/kernel/trace/trace_events_trigger.c
> > @@ -1739,7 +1739,7 @@ int event_enable_trigger_parse(struct event_command *cmd_ops,
> > char *glob, char *cmd, char *param_and_filter)
> > {
> > struct trace_event_file *event_enable_file;
> > - struct enable_trigger_data *enable_data;
> > + struct enable_trigger_data *enable_data __free(kfree) = NULL;
> …
>
> How do you think about to reduce the scope for this local variable?
>
No, it's fine as is.
-- Steve
^ permalink raw reply
* Re: [RFC PATCH 1/1] psi: Introduce in-kernel PSI auto monitor feature
From: K Prateek Nayak @ 2026-07-02 19:51 UTC (permalink / raw)
To: Pintu Kumar Agarwal, linux-kernel, linux-trace-kernel, hannes,
surenb, rostedt, mhiramat, peterz, mathieu.desnoyers, mingo,
juri.lelli, vincent.guittot, dietmar.eggemann, bsegall, mgorman,
vschneid, pintu.ping, nathan, ojeda, nsc, gary, tglx,
thomas.weissschuh, aliceryhl, dianders, linux.amoon, rdunlap,
akpm, shuah
In-Reply-To: <20260702171606.527077-2-pintu.agarwal@oss.qualcomm.com>
Hello Pintu,
On 7/2/2026 10:46 PM, Pintu Kumar Agarwal wrote:
> diff --git a/kernel/sched/build_utility.c b/kernel/sched/build_utility.c
> index e2cf3b08d4e9..30e9800ce947 100644
> --- a/kernel/sched/build_utility.c
> +++ b/kernel/sched/build_utility.c
> @@ -104,3 +104,7 @@
> #ifdef CONFIG_SCHED_AUTOGROUP
> # include "autogroup.c"
> #endif
> +
> +#ifdef CONFIG_PSI_AUTO_MONITOR
> +# include "psi_monitor.c"
> +#endif
Isn't this a module? Why is this being included as a scheduler file?
Based on a quick glance, nothing in this module needs scheduler internal
APIs (and nor it should) so tools/sched/ would probabaly be a better
place to put it in if there is interest for this feature.
> diff --git a/kernel/sched/psi_monitor.c b/kernel/sched/psi_monitor.c
> new file mode 100644
> index 000000000000..e929a0c05494
> --- /dev/null
> +++ b/kernel/sched/psi_monitor.c
> @@ -0,0 +1,307 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * PSI Automatic Monitor with Weighted Task Ranking + Tracepoints
> + *
> + * Periodically samples system PSI (CPU, memory, IO) and, when any
> + * configured threshold is exceeded, ranks tasks using a composite
> + * score based on RSS, I/O activity and CPU time, then logs the
> + * top-N tasks via printk and a tracepoint.
> + *
> + * Sysfs interface:
> + * /sys/kernel/psi_monitor/cpu_thresh (percentage)
> + * /sys/kernel/psi_monitor/mem_thresh (percentage)
> + * /sys/kernel/psi_monitor/io_thresh (percentage)
> + * /sys/kernel/psi_monitor/monitor_interval_ms (milliseconds)
> + * /sys/kernel/psi_monitor/rss_weight
> + * /sys/kernel/psi_monitor/io_weight
> + * /sys/kernel/psi_monitor/cpu_weight
> + *
> + * Author: Pintu Kumar Agarwal
> + */
> +
> +#include <linux/init.h>
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/sched.h>
> +#include <linux/sched/signal.h>
> +#include <linux/sched/loadavg.h>
> +#include <linux/mm.h>
> +#include <linux/delay.h>
> +#include <linux/workqueue.h>
> +#include <linux/psi_types.h>
> +#include <linux/kobject.h>
> +#include <linux/sort.h>
> +#include <linux/jiffies.h>
> +#include <linux/time64.h>
> +#include <linux/sched/cputime.h>
> +
> +/* Create tracepoints defined in include/trace/events/psi_monitor.h */
> +#define CREATE_TRACE_POINTS
> +#include <linux/psi.h>
> +#include <trace/events/psi_monitor.h>
> +
> +
> +/* Sysfs tunables */
> +static unsigned int cpu_thresh = 80; /* in percent */
> +static unsigned int mem_thresh = 80; /* in percent */
> +static unsigned int io_thresh = 80; /* in percent */
> +static unsigned int monitor_interval_ms = 10000;
> +
> +/* scoring weights */
> +static unsigned int rss_weight = 2;
> +static unsigned int io_weight = 1;
> +static unsigned int cpu_weight = 5;
Insanely configurable but what makes it easy for developers to know
the right configurations under severe pressure as you put it?
> +
> +static struct delayed_work psi_work;
> +static struct kobject *psi_kobj;
> +
> +#define TOP_N 20
> +
> +struct task_info {
> + struct task_struct *task;
> + unsigned long rss; /* pages */
> + unsigned long io_kb; /* kB */
> + unsigned long cpu_ms; /* ms */
Isn't the suffix selfexplanatory? Do you really need the comments?
> + u64 score;
> +};
> +
> +/*
> + * psi_avg10_percent() - derive a rough integer percentage from avg10
> + * for a given PSI state (e.g. PSI_CPU_SOME, PSI_MEM_SOME, PSI_IO_SOME).
> + *
> + * psi_group.avg[state][0] is the avg10 window in fixed-point notation.
> + * The conversion here is approximate but monotonic, which is sufficient
> + * for thresholding and ranking in this internal monitor.
> + */
> +static unsigned long psi_avg10_percent(int state)
> +{
> + u64 avg10;
> +
> + if (state < 0 || state >= NR_PSI_STATES)
> + return 0;
> +
> + avg10 = READ_ONCE(psi_system.avg[state][0]);
> + if (!avg10)
> + return 0;
> +
> + /* Convert back from loadavg-style fixed-point to an approximate % */
> + /* Just consider the integer value and ignore fraction */
Why two single line comments?
> + return LOAD_INT(avg10);
> +}
> +
> +static int compare_score_desc(const void *a, const void *b)
> +{
> + const struct task_info *ta = a;
> + const struct task_info *tb = b;
> +
> + if (tb->score > ta->score)
> + return 1;
> + if (tb->score < ta->score)
> + return -1;
> + return 0;
> +}
> +
> +static void log_top_tasks(void)
> +{
> + struct task_info tasks[TOP_N];
> + struct task_struct *p, *t;
> + int count = 0;
> + int i;
> +
> + rcu_read_lock();
> + for_each_process_thread(p, t) {
Thats a ton of work every 10s.
> + struct mm_struct *mm;
> + unsigned long rss = 0;
> + unsigned long io_kb = 0;
> + unsigned long cpu_ms = 0;
> + u64 score;
> +
> + /* Ignore tasks that are not on run queue or idle */
> + if (!t->on_rq && !is_idle_task(t))
Condition doesn't match the comment. Tasks off rq that aren't idle will
still go through.
> + continue;
> +
> + mm = get_task_mm(t);
> +
> + /* mm could be NULL for kernel threads */
> + if (mm) {
> + rss = mm ? get_mm_rss(mm) : 0;
> + mmput_async(mm);
> + }
> +
> + /*
> + * Approximate I/O activity: sum of read + write bytes.
> + * This uses the task_io_accounting fields in task_struct.
> + * Values are best-effort and need not be perfectly accurate
> + * for our ranking purpose.
> + */
> + io_kb = (t->ioac.read_bytes + t->ioac.write_bytes) >> 10;
> +
> + /*
> + * Approximate CPU usage via task_sched_runtime(), converted
> + * to milliseconds. This is cumulative since task start, but
> + * is still useful for comparing hotspots at a given point.
> + */
> + cpu_ms = (unsigned long)(task_sched_runtime(t) / NSEC_PER_MSEC);
> +
> + score = (u64)rss_weight * (u64)rss +
> + (u64)io_weight * (u64)io_kb +
> + (u64)cpu_weight * (u64)cpu_ms;
> +
> + if (count < TOP_N) {
> + tasks[count].task = t;
> + tasks[count].rss = rss;
> + tasks[count].io_kb = io_kb;
> + tasks[count].cpu_ms = cpu_ms;
> + tasks[count].score = score;
> + count++;
> + } else {
> + /* Maintain a simple streaming top-N: replace smallest */
> + int min_idx = 0;
> + int j;
> +
> + for (j = 1; j < TOP_N; j++) {
> + if (tasks[j].score < tasks[min_idx].score)
> + min_idx = j;
> + }
Can't you just cache the min_idx and re-compute it when it changes
instead of taking a O(20) iteration for every task?
> +
> + if (score > tasks[min_idx].score) {
> + tasks[min_idx].task = t;
> + tasks[min_idx].rss = rss;
> + tasks[min_idx].io_kb = io_kb;
> + tasks[min_idx].cpu_ms = cpu_ms;
> + tasks[min_idx].score = score;
> + }
> + }
> + }
> + rcu_read_unlock();
> +
> + sort(tasks, count, sizeof(struct task_info), compare_score_desc, NULL);
> +
> + pr_info("psi_monitor: logging top %d tasks under pressure:\n", count);
> +
> + for (i = 0; i < count; i++) {
> + struct task_struct *ts = tasks[i].task;
> + unsigned long rss_kb = tasks[i].rss << (PAGE_SHIFT - 10);
> + char name[128] = {0,};
> +
> + if (ts->flags & PF_WQ_WORKER)
> + wq_worker_comm(name, sizeof(name), ts);
> + else
> + scnprintf(name, sizeof(name) - 1, ts->comm);
> +
> + trace_psi_monitor_top_task(ts->pid, name,
> + tasks[i].cpu_ms,
> + rss_kb,
> + tasks[i].io_kb,
> + tasks[i].score);
> +
> + pr_info("psi_monitor: pid=%d comm=%s psi_flag=%d oncpu=%d cputime(ms)=%lu rss(kB)=%lu io(kB)=%lu score=%llu\n",
> + ts->pid, name, ts->psi_flags, task_cpu(ts),
> + tasks[i].cpu_ms, rss_kb, tasks[i].io_kb,
> + (unsigned long long)tasks[i].score);
This will unnecessarily dump to dmesg even if you have tracevent
enabled. Why?
> + }
> +}
> +
> +static void psi_monitor_fn(struct work_struct *work)
> +{
> + unsigned long cpu_pct, mem_pct, io_pct;
> + bool trigger = false;
> +
> + cpu_pct = psi_avg10_percent(PSI_CPU_SOME);
> + mem_pct = psi_avg10_percent(PSI_MEM_SOME);
> + io_pct = psi_avg10_percent(PSI_IO_SOME);
> +
> + if (cpu_pct >= cpu_thresh || mem_pct >= mem_thresh ||
> + io_pct >= io_thresh)
> + trigger = true;
> +
> + if (trigger) {
> + pr_info("psi_monitor: pressure high: cpu=%lu%% mem=%lu%% io=%lu%% (thresh cpu=%u mem=%u io=%u)\n",
> + cpu_pct, mem_pct, io_pct,
> + cpu_thresh, mem_thresh, io_thresh);
> + log_top_tasks();
> + }
> +
> + queue_delayed_work(system_wq, &psi_work,
> + msecs_to_jiffies(monitor_interval_ms));
If I set monitor_interval_ms to 6 hours, and then change it back to 10s,
it'll only take effect after this callback has fired 6 hours later.
> +}
> +
> +/* Sysfs helpers */
> +#define PSI_ATTR_RW(_name) \
> +static ssize_t _name##_show(struct kobject *kobj, \
> + struct kobj_attribute *attr, char *buf) \
> +{ \
> + return sysfs_emit(buf, "%u\n", _name); \
> +} \
> +static ssize_t _name##_store(struct kobject *kobj, \
> + struct kobj_attribute *attr, \
> + const char *buf, size_t count) \
> +{ \
> + unsigned int val; \
> + if (kstrtouint(buf, 10, &val)) \
> + return -EINVAL; \
> + _name = val; \
> + return count; \
> +} \
> +static struct kobj_attribute _name##_attr = __ATTR_RW(_name)
> +
> +PSI_ATTR_RW(cpu_thresh);
> +PSI_ATTR_RW(mem_thresh);
> +PSI_ATTR_RW(io_thresh);
> +PSI_ATTR_RW(monitor_interval_ms);
> +PSI_ATTR_RW(rss_weight);
> +PSI_ATTR_RW(io_weight);
> +PSI_ATTR_RW(cpu_weight);
> +
> +static struct attribute *psi_attrs[] = {
> + &cpu_thresh_attr.attr,
> + &mem_thresh_attr.attr,
> + &io_thresh_attr.attr,
> + &monitor_interval_ms_attr.attr,
> + &rss_weight_attr.attr,
> + &io_weight_attr.attr,
> + &cpu_weight_attr.attr,
> + NULL,
> +};
> +
> +static const struct attribute_group psi_attr_group = {
> + .attrs = psi_attrs,
> +};
> +
> +static int __init psi_monitor_init(void)
> +{
> + int ret;
> +
> + INIT_DELAYED_WORK(&psi_work, psi_monitor_fn);
> + queue_delayed_work(system_wq, &psi_work,
> + msecs_to_jiffies(monitor_interval_ms));
> +
> + psi_kobj = kobject_create_and_add("psi_monitor", kernel_kobj);
> + if (!psi_kobj)
> + return -ENOMEM;
> +
> + ret = sysfs_create_group(psi_kobj, &psi_attr_group);
> + if (ret) {
> + kobject_put(psi_kobj);
> + cancel_delayed_work_sync(&psi_work);
> + return ret;
> + }
> +
> + pr_info("psi_monitor: in-kernel PSI auto monitor (weighted + tracepoints) loaded\n");
> + return 0;
> +}
> +
> +static void __exit psi_monitor_exit(void)
> +{
> + cancel_delayed_work_sync(&psi_work);
> + if (psi_kobj)
> + kobject_put(psi_kobj);
> + pr_info("psi_monitor: unloaded\n");
> +}
> +
> +module_init(psi_monitor_init);
> +module_exit(psi_monitor_exit);
There is nothing here that warrants putting this in kernel/sched.
Also this gets included by default when config is enabled and starts
dumping a bunch of stats to dmesg without anyone asking. No?
Afaict, almost all of the detail used here is also available from
procfs and people can easily put together a userspace tool if they
need it. Why do we need an in-kernel module?
> +
> +MODULE_LICENSE("GPL");
> +MODULE_AUTHOR("Pintu Kumar Agarwal");
> +MODULE_DESCRIPTION("In-kernel PSI automatic monitor with sysfs, weighted scoring and tracepoints");
> --
> 2.34.1
>
--
Thanks and Regards,
Prateek
^ permalink raw reply
* [RFC PATCH 1/1] psi: Introduce in-kernel PSI auto monitor feature
From: Pintu Kumar Agarwal @ 2026-07-02 17:16 UTC (permalink / raw)
To: linux-kernel, linux-trace-kernel, hannes, surenb, rostedt,
mhiramat, peterz, mathieu.desnoyers, mingo, juri.lelli,
vincent.guittot, dietmar.eggemann, bsegall, mgorman, vschneid,
kprateek.nayak, pintu.agarwal, pintu.ping, nathan, ojeda, nsc,
gary, tglx, thomas.weissschuh, aliceryhl, dianders, linux.amoon,
rdunlap, akpm, shuah
In-Reply-To: <20260702171606.527077-1-pintu.agarwal@oss.qualcomm.com>
Pressure Stall Information (PSI) provides accurate detection of CPU,
memory and I/O contention and supports event notifications via trigger
windows and poll-based interfaces. However, PSI intentionally does not
attribute pressure to individual tasks. As a result, developers must
reconstruct root cause in user space by correlating multiple tools,
logs, or tracing data after the fact.
In practice, this becomes difficult under severe pressure conditions,
where systems are already degraded and user-space observers may be
delayed or miss the critical window entirely.
Moreover, we need to gather information before the situation occurs
and not after the problem arises.
This patch introduces an optional in-kernel PSI auto monitor that
captures contributing tasks at the exact moment configured PSI
thresholds are breached. The monitor periodically samples PSI state and,
upon sustained pressure, records the top contending tasks based on a
lightweight composite score derived from CPU runtime, RSS and I/O
activity.
Key design points:
- No modifications to PSI fast paths
- No dependency on user-space daemons or continuous polling
- Uses existing kernel accounting and tracepoints
- Provides structured trace events for integration with tracing tools
- Runtime configurable thresholds and sampling interval
The goal is not to replace existing PSI mechanisms or user-space
components such as oomd, but to complement them by providing
low-latency, in-context attribution data at the point of pressure.
Experimental results across multiple platforms and workloads,
including real time scenarios, show improved accuracy and reduced time
to root-cause identification, especially in transient and high-pressure
conditions such as system boot and stress workloads.
This patch is submitted as RFC to gather feedback on:
- suitability of in-kernel attribution vs user-space approaches
- interface choice (sysfs vs trace-based control)
- dmesg logging when threshold hit, just like OOM messages
- some avg10 monitoring as default choice
- default threshold values and tasks count
- scoring methodology and configurability
- potential integration with existing PSI infrastructure
Signed-off-by: Pintu Kumar Agarwal <pintu.agarwal@oss.qualcomm.com>
Assisted-by: Copilot:Auto
Assisted-by: ChatGPT:GPT-5.5
---
include/trace/events/psi_monitor.h | 53 +++++
init/Kconfig | 16 ++
kernel/sched/build_utility.c | 4 +
kernel/sched/psi_monitor.c | 307 +++++++++++++++++++++++++++++
4 files changed, 380 insertions(+)
create mode 100644 include/trace/events/psi_monitor.h
create mode 100644 kernel/sched/psi_monitor.c
diff --git a/include/trace/events/psi_monitor.h b/include/trace/events/psi_monitor.h
new file mode 100644
index 000000000000..cf99f5994472
--- /dev/null
+++ b/include/trace/events/psi_monitor.h
@@ -0,0 +1,53 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Tracepoints for PSI automatic monitor
+ */
+
+#undef TRACE_SYSTEM
+#define TRACE_SYSTEM psi_monitor
+
+#if !defined(_TRACE_PSI_MONITOR_H) || defined(TRACE_HEADER_MULTI_READ)
+#define _TRACE_PSI_MONITOR_H
+
+#include <linux/types.h>
+#include <linux/tracepoint.h>
+
+TRACE_EVENT(psi_monitor_top_task,
+
+ TP_PROTO(pid_t pid, const char *comm,
+ unsigned long cpu_ms,
+ unsigned long rss_kb,
+ unsigned long io_kb,
+ u64 score),
+
+ TP_ARGS(pid, comm, cpu_ms, rss_kb, io_kb, score),
+
+ TP_STRUCT__entry(
+ __field(pid_t, pid)
+ __string(comm, comm)
+ __field(unsigned long, cpu_ms)
+ __field(unsigned long, rss_kb)
+ __field(unsigned long, io_kb)
+ __field(u64, score)
+ ),
+
+ TP_fast_assign(
+ __entry->pid = pid;
+ __assign_str(comm);
+ __entry->cpu_ms = cpu_ms;
+ __entry->rss_kb = rss_kb;
+ __entry->io_kb = io_kb;
+ __entry->score = score;
+ ),
+
+ TP_printk("pid=%d comm=%s cpu_ms=%lu rss_kb=%lu io_kb=%lu score=%llu",
+ __entry->pid, __get_str(comm),
+ __entry->cpu_ms, __entry->rss_kb,
+ __entry->io_kb,
+ (unsigned long long)__entry->score)
+);
+
+#endif /* _TRACE_PSI_MONITOR_H */
+
+/* This must be outside the header guard */
+#include <trace/define_trace.h>
diff --git a/init/Kconfig b/init/Kconfig
index 5230d4879b1c..074693f76b17 100644
--- a/init/Kconfig
+++ b/init/Kconfig
@@ -757,6 +757,22 @@ config PSI_DEFAULT_DISABLED
Say N if unsure.
+config PSI_AUTO_MONITOR
+ bool "In-kernel automatic PSI monitor with sysfs + weighted scoring"
+ depends on PSI && TASK_XACCT && TASK_IO_ACCOUNTING && TRACEPOINTS
+ default n
+ help
+ Enables a kernel-internal PSI observer that periodically checks CPU,
+ memory, and I/O pressure via a delayed workqueue. When thresholds
+ are breached, it ranks tasks by weighted RSS, I/O, and CPU usage,
+ then logs top-N tasks via printk and emits trace events.
+
+ Thresholds, poll interval and weights are tunable at runtime via:
+ /sys/kernel/psi_monitor/
+
+ Say N if unsure.
+
+
endmenu # "CPU/Task time and stats accounting"
config CPU_ISOLATION
diff --git a/kernel/sched/build_utility.c b/kernel/sched/build_utility.c
index e2cf3b08d4e9..30e9800ce947 100644
--- a/kernel/sched/build_utility.c
+++ b/kernel/sched/build_utility.c
@@ -104,3 +104,7 @@
#ifdef CONFIG_SCHED_AUTOGROUP
# include "autogroup.c"
#endif
+
+#ifdef CONFIG_PSI_AUTO_MONITOR
+# include "psi_monitor.c"
+#endif
diff --git a/kernel/sched/psi_monitor.c b/kernel/sched/psi_monitor.c
new file mode 100644
index 000000000000..e929a0c05494
--- /dev/null
+++ b/kernel/sched/psi_monitor.c
@@ -0,0 +1,307 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * PSI Automatic Monitor with Weighted Task Ranking + Tracepoints
+ *
+ * Periodically samples system PSI (CPU, memory, IO) and, when any
+ * configured threshold is exceeded, ranks tasks using a composite
+ * score based on RSS, I/O activity and CPU time, then logs the
+ * top-N tasks via printk and a tracepoint.
+ *
+ * Sysfs interface:
+ * /sys/kernel/psi_monitor/cpu_thresh (percentage)
+ * /sys/kernel/psi_monitor/mem_thresh (percentage)
+ * /sys/kernel/psi_monitor/io_thresh (percentage)
+ * /sys/kernel/psi_monitor/monitor_interval_ms (milliseconds)
+ * /sys/kernel/psi_monitor/rss_weight
+ * /sys/kernel/psi_monitor/io_weight
+ * /sys/kernel/psi_monitor/cpu_weight
+ *
+ * Author: Pintu Kumar Agarwal
+ */
+
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/sched.h>
+#include <linux/sched/signal.h>
+#include <linux/sched/loadavg.h>
+#include <linux/mm.h>
+#include <linux/delay.h>
+#include <linux/workqueue.h>
+#include <linux/psi_types.h>
+#include <linux/kobject.h>
+#include <linux/sort.h>
+#include <linux/jiffies.h>
+#include <linux/time64.h>
+#include <linux/sched/cputime.h>
+
+/* Create tracepoints defined in include/trace/events/psi_monitor.h */
+#define CREATE_TRACE_POINTS
+#include <linux/psi.h>
+#include <trace/events/psi_monitor.h>
+
+
+/* Sysfs tunables */
+static unsigned int cpu_thresh = 80; /* in percent */
+static unsigned int mem_thresh = 80; /* in percent */
+static unsigned int io_thresh = 80; /* in percent */
+static unsigned int monitor_interval_ms = 10000;
+
+/* scoring weights */
+static unsigned int rss_weight = 2;
+static unsigned int io_weight = 1;
+static unsigned int cpu_weight = 5;
+
+static struct delayed_work psi_work;
+static struct kobject *psi_kobj;
+
+#define TOP_N 20
+
+struct task_info {
+ struct task_struct *task;
+ unsigned long rss; /* pages */
+ unsigned long io_kb; /* kB */
+ unsigned long cpu_ms; /* ms */
+ u64 score;
+};
+
+/*
+ * psi_avg10_percent() - derive a rough integer percentage from avg10
+ * for a given PSI state (e.g. PSI_CPU_SOME, PSI_MEM_SOME, PSI_IO_SOME).
+ *
+ * psi_group.avg[state][0] is the avg10 window in fixed-point notation.
+ * The conversion here is approximate but monotonic, which is sufficient
+ * for thresholding and ranking in this internal monitor.
+ */
+static unsigned long psi_avg10_percent(int state)
+{
+ u64 avg10;
+
+ if (state < 0 || state >= NR_PSI_STATES)
+ return 0;
+
+ avg10 = READ_ONCE(psi_system.avg[state][0]);
+ if (!avg10)
+ return 0;
+
+ /* Convert back from loadavg-style fixed-point to an approximate % */
+ /* Just consider the integer value and ignore fraction */
+ return LOAD_INT(avg10);
+}
+
+static int compare_score_desc(const void *a, const void *b)
+{
+ const struct task_info *ta = a;
+ const struct task_info *tb = b;
+
+ if (tb->score > ta->score)
+ return 1;
+ if (tb->score < ta->score)
+ return -1;
+ return 0;
+}
+
+static void log_top_tasks(void)
+{
+ struct task_info tasks[TOP_N];
+ struct task_struct *p, *t;
+ int count = 0;
+ int i;
+
+ rcu_read_lock();
+ for_each_process_thread(p, t) {
+ struct mm_struct *mm;
+ unsigned long rss = 0;
+ unsigned long io_kb = 0;
+ unsigned long cpu_ms = 0;
+ u64 score;
+
+ /* Ignore tasks that are not on run queue or idle */
+ if (!t->on_rq && !is_idle_task(t))
+ continue;
+
+ mm = get_task_mm(t);
+
+ /* mm could be NULL for kernel threads */
+ if (mm) {
+ rss = mm ? get_mm_rss(mm) : 0;
+ mmput_async(mm);
+ }
+
+ /*
+ * Approximate I/O activity: sum of read + write bytes.
+ * This uses the task_io_accounting fields in task_struct.
+ * Values are best-effort and need not be perfectly accurate
+ * for our ranking purpose.
+ */
+ io_kb = (t->ioac.read_bytes + t->ioac.write_bytes) >> 10;
+
+ /*
+ * Approximate CPU usage via task_sched_runtime(), converted
+ * to milliseconds. This is cumulative since task start, but
+ * is still useful for comparing hotspots at a given point.
+ */
+ cpu_ms = (unsigned long)(task_sched_runtime(t) / NSEC_PER_MSEC);
+
+ score = (u64)rss_weight * (u64)rss +
+ (u64)io_weight * (u64)io_kb +
+ (u64)cpu_weight * (u64)cpu_ms;
+
+ if (count < TOP_N) {
+ tasks[count].task = t;
+ tasks[count].rss = rss;
+ tasks[count].io_kb = io_kb;
+ tasks[count].cpu_ms = cpu_ms;
+ tasks[count].score = score;
+ count++;
+ } else {
+ /* Maintain a simple streaming top-N: replace smallest */
+ int min_idx = 0;
+ int j;
+
+ for (j = 1; j < TOP_N; j++) {
+ if (tasks[j].score < tasks[min_idx].score)
+ min_idx = j;
+ }
+
+ if (score > tasks[min_idx].score) {
+ tasks[min_idx].task = t;
+ tasks[min_idx].rss = rss;
+ tasks[min_idx].io_kb = io_kb;
+ tasks[min_idx].cpu_ms = cpu_ms;
+ tasks[min_idx].score = score;
+ }
+ }
+ }
+ rcu_read_unlock();
+
+ sort(tasks, count, sizeof(struct task_info), compare_score_desc, NULL);
+
+ pr_info("psi_monitor: logging top %d tasks under pressure:\n", count);
+
+ for (i = 0; i < count; i++) {
+ struct task_struct *ts = tasks[i].task;
+ unsigned long rss_kb = tasks[i].rss << (PAGE_SHIFT - 10);
+ char name[128] = {0,};
+
+ if (ts->flags & PF_WQ_WORKER)
+ wq_worker_comm(name, sizeof(name), ts);
+ else
+ scnprintf(name, sizeof(name) - 1, ts->comm);
+
+ trace_psi_monitor_top_task(ts->pid, name,
+ tasks[i].cpu_ms,
+ rss_kb,
+ tasks[i].io_kb,
+ tasks[i].score);
+
+ pr_info("psi_monitor: pid=%d comm=%s psi_flag=%d oncpu=%d cputime(ms)=%lu rss(kB)=%lu io(kB)=%lu score=%llu\n",
+ ts->pid, name, ts->psi_flags, task_cpu(ts),
+ tasks[i].cpu_ms, rss_kb, tasks[i].io_kb,
+ (unsigned long long)tasks[i].score);
+ }
+}
+
+static void psi_monitor_fn(struct work_struct *work)
+{
+ unsigned long cpu_pct, mem_pct, io_pct;
+ bool trigger = false;
+
+ cpu_pct = psi_avg10_percent(PSI_CPU_SOME);
+ mem_pct = psi_avg10_percent(PSI_MEM_SOME);
+ io_pct = psi_avg10_percent(PSI_IO_SOME);
+
+ if (cpu_pct >= cpu_thresh || mem_pct >= mem_thresh ||
+ io_pct >= io_thresh)
+ trigger = true;
+
+ if (trigger) {
+ pr_info("psi_monitor: pressure high: cpu=%lu%% mem=%lu%% io=%lu%% (thresh cpu=%u mem=%u io=%u)\n",
+ cpu_pct, mem_pct, io_pct,
+ cpu_thresh, mem_thresh, io_thresh);
+ log_top_tasks();
+ }
+
+ queue_delayed_work(system_wq, &psi_work,
+ msecs_to_jiffies(monitor_interval_ms));
+}
+
+/* Sysfs helpers */
+#define PSI_ATTR_RW(_name) \
+static ssize_t _name##_show(struct kobject *kobj, \
+ struct kobj_attribute *attr, char *buf) \
+{ \
+ return sysfs_emit(buf, "%u\n", _name); \
+} \
+static ssize_t _name##_store(struct kobject *kobj, \
+ struct kobj_attribute *attr, \
+ const char *buf, size_t count) \
+{ \
+ unsigned int val; \
+ if (kstrtouint(buf, 10, &val)) \
+ return -EINVAL; \
+ _name = val; \
+ return count; \
+} \
+static struct kobj_attribute _name##_attr = __ATTR_RW(_name)
+
+PSI_ATTR_RW(cpu_thresh);
+PSI_ATTR_RW(mem_thresh);
+PSI_ATTR_RW(io_thresh);
+PSI_ATTR_RW(monitor_interval_ms);
+PSI_ATTR_RW(rss_weight);
+PSI_ATTR_RW(io_weight);
+PSI_ATTR_RW(cpu_weight);
+
+static struct attribute *psi_attrs[] = {
+ &cpu_thresh_attr.attr,
+ &mem_thresh_attr.attr,
+ &io_thresh_attr.attr,
+ &monitor_interval_ms_attr.attr,
+ &rss_weight_attr.attr,
+ &io_weight_attr.attr,
+ &cpu_weight_attr.attr,
+ NULL,
+};
+
+static const struct attribute_group psi_attr_group = {
+ .attrs = psi_attrs,
+};
+
+static int __init psi_monitor_init(void)
+{
+ int ret;
+
+ INIT_DELAYED_WORK(&psi_work, psi_monitor_fn);
+ queue_delayed_work(system_wq, &psi_work,
+ msecs_to_jiffies(monitor_interval_ms));
+
+ psi_kobj = kobject_create_and_add("psi_monitor", kernel_kobj);
+ if (!psi_kobj)
+ return -ENOMEM;
+
+ ret = sysfs_create_group(psi_kobj, &psi_attr_group);
+ if (ret) {
+ kobject_put(psi_kobj);
+ cancel_delayed_work_sync(&psi_work);
+ return ret;
+ }
+
+ pr_info("psi_monitor: in-kernel PSI auto monitor (weighted + tracepoints) loaded\n");
+ return 0;
+}
+
+static void __exit psi_monitor_exit(void)
+{
+ cancel_delayed_work_sync(&psi_work);
+ if (psi_kobj)
+ kobject_put(psi_kobj);
+ pr_info("psi_monitor: unloaded\n");
+}
+
+module_init(psi_monitor_init);
+module_exit(psi_monitor_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Pintu Kumar Agarwal");
+MODULE_DESCRIPTION("In-kernel PSI automatic monitor with sysfs, weighted scoring and tracepoints");
--
2.34.1
^ permalink raw reply related
* [RFC PATCH 0/1] psi: Introduce in-kernel PSI auto monitor feature
From: Pintu Kumar Agarwal @ 2026-07-02 17:16 UTC (permalink / raw)
To: linux-kernel, linux-trace-kernel, hannes, surenb, rostedt,
mhiramat, peterz, mathieu.desnoyers, mingo, juri.lelli,
vincent.guittot, dietmar.eggemann, bsegall, mgorman, vschneid,
kprateek.nayak, pintu.agarwal, pintu.ping, nathan, ojeda, nsc,
gary, tglx, thomas.weissschuh, aliceryhl, dianders, linux.amoon,
rdunlap, akpm, shuah
Hi all,
This RFC introduces an in-kernel PSI auto monitor aimed at improving
root-cause visibility for resource pressure events in Linux systems.
Motivation:
PSI already provides an excellent mechanism to detect CPU, memory and
I/O pressure and includes trigger-based notifications via pollable
interfaces. However, it deliberately avoids attributing pressure to
individual tasks.
In real-world systems, this creates a gap: when a PSI trigger fires,
users still need to determine *which tasks caused the stall* by combining
multiple tools (top, meminfo, vmstat, perf, tracing, etc.), often after
the event has already passed.
This process becomes particularly difficult during:
- transient bursts of pressure
- system boot or early initialization before user space
- PREEMPT_RT or latency-sensitive workloads
- heavily loaded embedded systems where user space is delayed
- small resource-constraints minimal system
- production system where most debugging interface are disabled
Proposal:
This patch introduces an optional in-kernel PSI auto monitor that:
- periodically samples PSI signals
- detects threshold breaches
- captures top contributing tasks at that moment
- emits trace events and kernel logs for analysis
The design goal is **low-latency attribution at the source of truth**,
without relying on user-space daemons or polling loops.
Why in-kernel?
While similar logic can be implemented in user space, there are inherent
limitations:
- scheduling delays under high pressure
- risk of missing short-lived spikes
- dependency on continuous polling or daemons
- difficulty deploying in early boot or minimal environments
In contrast, the in-kernel approach:
- observes PSI signals without scheduling latency
- captures contributors exactly at threshold breach
- works during early boot and degraded system states
- avoids duplicating logic across multiple user-space tools
- easy configurable even in runtime
- captures all sorts of information during same timestamp
Design Highlights:
- Does not modify PSI fast paths
- Optional (CONFIG_PSI_AUTO_MONITOR)
- Runtime configurable thresholds and interval
- Uses existing kernel accounting (task runtime, RSS, I/O stats)
- Provides structured tracepoints for post-processing
- Lightweight and intended for diagnostic use
- Idea is similar to, when OOM occurs dump contending tasks
Reviews and Assistance:
The core idea is mine.
However, I have taken few assistance from AI for review and enhancement.
I have done extensive review and suggestion using ChatGPT and Copilot.
The commit message and this cover letter were also prepared by Copilot.
I have done self-review and corrective actions accordingly.
Experimental Validation:
The feature has been evaluated on multiple ARM64 platforms (Cortex-A53,
A55) across different kernels and storage setups.
Extensive experiments has been carried out with multiple workloads.
Some tools and logs are shared here:
https://github.com/pintuk/KERNEL/tree/master/PSI_WORK
Test scenarios include:
- CPU/memory/IO stress workloads both on eMMC and NAND
- system boot tracing (no external tools)
- mixed workloads (stress-ng, workqueues, user/kernel threads, processes)
- PREEMPT_RT cyclictest correlation with real workloads
Results show:
- consistent identification of top resource contributors
- improved root-cause visibility compared to user-space-only methods
- ability to capture transient hotspots during boot and runtime
- correlation of latency spikes with system pressure
Papers and Reference:
The paper is presented in Open Source Summit India - 2026:
https://ossindia2026.sched.com/event/2KNI4/introducing-in-kernel-psi-auto-monitor-feature-pintu-kumar-agarwal-qualcomm?iframe=yes&w=100%&sidebar=yes&bg=no
https://hosted-files.sched.co/ossindia2026/19/OSS-IND-26-PSI-Auto-Monitor.pdf
The initial idea was also presented in LPC-2024:
https://lpc.events/event/18/contributions/1884/attachments/1439/3069/LPC2024_PIntu_PSI.pdf
Open Questions (RFC):
Feedback is especially appreciated on:
- whether this functionality belongs in-kernel vs user-space
- interface choice (sysfs vs tracefs/debugfs alternatives)
- scoring heuristic (CPU/RSS/IO weighting)
- potential reuse or extension of existing PSI interfaces
- cgroup-aware extensions for future work
Future Work:
- finer-grained PSI window integration
- IRQ pressure support
- cgroup-based attribution
- improved tracing/export interfaces
- optional integration with user-space analysis tools
Thanks for your time, and I’d really appreciate feedback.
Regards,
Pintu Kumar Agarwal
Pintu Kumar Agarwal (1):
psi: Introduce in-kernel PSI auto monitor feature
include/trace/events/psi_monitor.h | 53 +++++
init/Kconfig | 16 ++
kernel/sched/build_utility.c | 4 +
kernel/sched/psi_monitor.c | 307 +++++++++++++++++++++++++++++
4 files changed, 380 insertions(+)
create mode 100644 include/trace/events/psi_monitor.h
create mode 100644 kernel/sched/psi_monitor.c
--
2.34.1
^ permalink raw reply
* Re: [PATCH v3 04/11] arm64/mm: Add set_memory_device() and set_memory_normal()
From: Thierry Reding @ 2026-07-02 16:41 UTC (permalink / raw)
To: Will Deacon
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Jonathan Hunter,
David Airlie, Simona Vetter, Maarten Lankhorst, Maxime Ripard,
Thomas Zimmermann, Sowjanya Komatineni, Luca Ceresoli,
Mikko Perttunen, Yury Norov, Rasmus Villemoes, Russell King,
Alexander Gordeev, Gerald Schaefer, Heiko Carstens, Vasily Gorbik,
Christian Borntraeger, Sven Schnelle, Andrew Morton,
David Hildenbrand, Lorenzo Stoakes, Liam R. Howlett,
Vlastimil Babka, Mike Rapoport, Suren Baghdasaryan, Michal Hocko,
Marek Szyprowski, Robin Murphy, Sumit Semwal, Benjamin Gaignard,
Brian Starkey, John Stultz, T.J. Mercier, Christian König,
Steven Rostedt, Masami Hiramatsu, Mathieu Desnoyers,
Catalin Marinas, Thierry Reding, devicetree, linux-tegra,
linux-kernel, dri-devel, linux-media, linux-arm-kernel,
linux-s390, linux-mm, iommu, linaro-mm-sig, linux-trace-kernel,
Thierry Reding, Chun Ng
In-Reply-To: <akZkuwktaXFTrASP@orome>
[-- Attachment #1: Type: text/plain, Size: 2127 bytes --]
On Thu, Jul 02, 2026 at 03:46:44PM +0200, Thierry Reding wrote:
> On Thu, Jul 02, 2026 at 10:18:47AM +0100, Will Deacon wrote:
> > On Wed, Jul 01, 2026 at 06:08:15PM +0200, Thierry Reding wrote:
> > > From: Chun Ng <chunn@nvidia.com>
> > >
> > > Add helpers to swap PROT_NORMAL and PROT_DEVICE_nGnRnE protection bits
> > > on a kernel-linear-map range.
> >
> > That sounds like a really terrible idea. Why is this necessary and how
> > does it interact with things like load_unaligned_zeropad()?
>
> This is necessary because once the memory controller has walled off the
> new memory region the CPU must not access it under any circumstances or
> it'll cause the CPU to lock up (I think technically it'll hit an SError
> but in practice that just means it'll freeze, as far as I can tell).
>
> Probably doesn't interact well at all with load_unaligned_zeropad().
>
> > I think you should unmap the memory from the linear map and memremap()
> > it instead.
>
> Given that the memory can never be accessed by the CPU after the memory
> controller locks it down, I don't think we'll even need memremap(). The
> only thing we really need is the sg_table we hand out via the DMA BUFs
> so that they can be used by device drivers to program their DMA engines
> internally.
>
> Looking through some of the architecture code around this, shouldn't we
> simply be using set_memory_encrypted() and set_memory_decrypted() for
> this? While they might've been created for slightly other use-cases,
> they seem to be doing exactly what we want (i.e. remove the page range
> from the linear mapping and flushing it, or restoring the valid bit and
> standard permissions, respectively).
Ah... I guess we can't do it because we're not in a realm world and so
the early checks in __set_memory_enc_dec() would return early and turn
it into a no-op.
How about if I extract a common helper and provide set_memory_p() and
set_memory_np() in terms of those. Those are available on x86 and
PowerPC as well, so fairly standard. I suppose at that point we're
closer to set_memory_valid().
Thierry
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
^ permalink raw reply
* Re: [PATCHv5 00/13] uprobes/x86: Fix red zone issue for optimized uprobes
From: Andrii Nakryiko @ 2026-07-02 16:20 UTC (permalink / raw)
To: Jiri Olsa
Cc: Oleg Nesterov, Peter Zijlstra, Ingo Molnar, Masami Hiramatsu,
Andrii Nakryiko, bpf, linux-trace-kernel
In-Reply-To: <akZJZth3-fVUhADl@krava>
On Thu, Jul 2, 2026 at 4:20 AM Jiri Olsa <olsajiri@gmail.com> wrote:
>
> On Wed, Jul 01, 2026 at 04:13:26PM -0700, Andrii Nakryiko wrote:
> > On Wed, Jul 1, 2026 at 4:13 AM Jiri Olsa <jolsa@kernel.org> wrote:
> > >
> > > hi,
> > > Andrii reported an issue with optimized uprobes [1] that can clobber
> > > redzone area with call instruction storing return address on stack
> > > where user code may keep temporary data without adjusting rsp.
> > >
> > > Fixing this by moving the optimized uprobes on top of 10-bytes nop
> > > instruction, so we can squeeze another instruction to escape the
> > > redzone area before doing the call.
> > >
> > > Note we need upstream update first for patch 3 (github.com/libbpf/usdt),
> > > if we decide to take this change.
> > >
> > > thanks,
> > > jirka
> > >
> > >
> > > v1: https://lore.kernel.org/bpf/20260514135342.22130-1-jolsa@kernel.org/
> > > v2: https://lore.kernel.org/bpf/20260518105957.123445-1-jolsa@kernel.org/
> > > v3: https://lore.kernel.org/bpf/20260521124411.31133-1-jolsa@kernel.org/
> > > v4: https://lore.kernel.org/bpf/20260526205840.173790-1-jolsa@kernel.org/
> > >
> > > v5 changes:
> > > - several selftests changes and reviewed-by tags [Jakub]
> > > - add more comments in int3_update_unoptimize [Andrii]
> > > - several other minor changes and acks [Oleg]
> > > - move insn_decode out of uprobe_init_insn to simplify the code
> > > - align uprobe_red_zone_test to 64 to make sure nop10 is not on page boundary
> > >
> > > v4 changes:
> > > - do not use 2nd int3 (ont +5 offset) because the call instruction
> > > is allways the same for the given nop10 address [Andrii/Peter]
> > > - unmap unused trampoline vma after unsuccesfull optimization [sashiko]
> > > - small change to patch#2 moved user_64bit_mode earlier in the path
> > > and pass/use mm_struct pointer directly from arch_uprobe_optimize
> > > instead of gettting current->mm
> > > Andrii, keeping your ack, please shout otherwise
> > >
> > > v3 changes:
> > > - use nop10 update suggested by Peter in [2]
> > > - remove struct uprobe_trampoline object, use vma objects directly instead
> > > - selftests fixes [sashiko]
> > > - ack from Andrii
> > >
> > > v2 changes:
> > > - several selftest fixes [sashiko]
> > > - consolidate is_lea_insn and is_call_insn insto single check [Jakub Sitnicki]
> > > - use proper mm_struct object in __in_uprobe_trampoline check [sashiko]
> > > - allow to copy uprobe trampolines vma objects on fork [sashiko]
> > > - change uprobe syscall detection error from -ENXIO to -EPROTO [Andrii]
> > > - added fork/clone tests
> > > - I kept the selftest changes and nop5->nop10 changes in separate
> > > commits for easier review, we can squash them later if we want to keep
> > > bisect working properly
> > >
> > >
> > > [1] https://lore.kernel.org/bpf/20260509003146.976844-1-andrii@kernel.org/
> > > [2] https://lore.kernel.org/bpf/20260518104306.GU3102624@noisy.programming.kicks-ass.net/#t
> > > ---
> >
> > ASAN-enabled test_progs runs are not happy in CI, can you please check?
>
> I failed to release link in test_uprobe_fork_optimized, fix is below
> I can send new version or separate fix
yeah, please fix the test, adjust comments as pointed out by AI and
send v6. Seems like Peter wants to pick it up through tip, I don't
mind.
>
>
> also there's 2 things to solve/discuss once kernel changes are acked:
> - selftest changes depend on:
> selftests/bpf: Emit nop,nop10 instructions combo for x86_64 arch
> that is taken from libbpf/usdt, I pushed the PR in here [1]
>
merged that one, we are good
> - as bots complained the patchset breaks bisection, because kernel
> changes break selftests.. not sure what's prefered solution, as for
> me I'd keep it that way rather than mixing kernel/user space changes
I think it's fine to keep them separate
>
> thanks,
> jirka
>
>
> [1] https://github.com/libbpf/usdt/pull/16
> ---
> diff --git a/tools/testing/selftests/bpf/prog_tests/uprobe_syscall.c b/tools/testing/selftests/bpf/prog_tests/uprobe_syscall.c
> index eb067f029a9f..e193206fc5d2 100644
> --- a/tools/testing/selftests/bpf/prog_tests/uprobe_syscall.c
> +++ b/tools/testing/selftests/bpf/prog_tests/uprobe_syscall.c
> @@ -988,7 +988,6 @@ static noreturn int child_func(void *arg)
> static void test_uprobe_fork_optimized(bool clone_vm)
> {
> struct uprobe_syscall_executed *skel = NULL;
> - struct bpf_link *link = NULL;
> unsigned long offset;
> int pid, status, err;
> char stack[65535];
> @@ -1001,9 +1000,9 @@ static void test_uprobe_fork_optimized(bool clone_vm)
> if (!ASSERT_OK_PTR(skel, "open_and_load"))
> goto cleanup;
>
> - link = bpf_program__attach_uprobe_opts(skel->progs.test_uprobe,
> - -1, "/proc/self/exe", offset, NULL);
> - if (!ASSERT_OK_PTR(link, "attach_uprobe"))
> + skel->links.test_uprobe = bpf_program__attach_uprobe_opts(skel->progs.test_uprobe,
> + -1, "/proc/self/exe", offset, NULL);
> + if (!ASSERT_OK_PTR(skel->links.test_uprobe, "attach_uprobe"))
> goto cleanup;
>
> skel->bss->pid = getpid();
^ permalink raw reply
* Re: [PATCH] tracing: Warn when an event dereferences a pointer in TP_printk()
From: Vinod Koul @ 2026-07-02 15:37 UTC (permalink / raw)
To: Steven Rostedt
Cc: LKML, Linux Trace Kernel, Masami Hiramatsu, Mathieu Desnoyers,
Martin Kaiser, Frank Li
In-Reply-To: <20260630184836.74d477b6@gandalf.local.home>
On 30-06-26, 18:48, Steven Rostedt wrote:
> From: Steven Rostedt <rostedt@goodmis.org>
>
> Currently on boot up and when modules are loaded, the trace event
> infrastructure will examine the TP_printk's of every event looking to see
> if it dereferences pointers on the ring buffer via printk formats like
> "%pB" and such. What it doesn't do is check if the arguments themselves
> do a dereference from a pointer.
>
> This was brought with a fix[1] to the fsl_edma event that had in the
> arguments of the TP_printk(): "__entry->edma->membase"
>
> The __entry->edma is a pointer saved in the ring buffer. The dereference
> from TP_printk() happens when the user reads the "trace" file which can be
> seconds, minutes, hours, days, weeks, or even months later! There is no
> guarantee that the __entry->edma pointer will still be pointing to what it
> was when it was recorded, and could crash the kernel when a user reads the
> event.
>
> Add logic to the test_event_printk() that also checks for this case and
> warn if the event dereferences a pointer from the ring buffer.
Reviewed-by: Vinod Koul <vkoul@kernel.org>
--
~Vinod
^ permalink raw reply
* Re: [PATCH v3 03/17] verification/rvgen: Improve rv_dir discovery in RVGenerator
From: Nam Cao @ 2026-07-02 14:16 UTC (permalink / raw)
To: Gabriele Monaco, linux-trace-kernel, linux-kernel, Steven Rostedt,
Gabriele Monaco
Cc: Thomas Weissschuh, Tomas Glozar, John Kacur, Wen Yang
In-Reply-To: <20260625121440.116317-4-gmonaco@redhat.com>
Gabriele Monaco <gmonaco@redhat.com> writes:
> The RVGenerator class can find the RV directory (kernel/trace/rv) in the
> kernel tree to do some auto patching. This works by assuming PWD is
> either the kernel tree or tools/verification, which isn't always the
> case (e.g. when running from selftests).
>
> Make discovery more robust by relying on the absolute path of the
> current script and traversing backwards the right number of times.
> This should work from any location if rvgen is in the kernel tree.
>
> Signed-off-by: Gabriele Monaco <gmonaco@redhat.com>
With or without Thomas's suggestion:
Reviewed-by: Nam Cao <namcao@linutronix.de>
^ permalink raw reply
* Re: [PATCH v3 03/17] verification/rvgen: Improve rv_dir discovery in RVGenerator
From: Thomas Weissschuh @ 2026-07-02 14:09 UTC (permalink / raw)
To: Gabriele Monaco
Cc: Nam Cao, linux-trace-kernel, linux-kernel, Steven Rostedt,
Tomas Glozar, John Kacur, Wen Yang
In-Reply-To: <4dc81b777bb8daaf4687c0d9db7ce740abd0a825.camel@redhat.com>
On Thu, Jul 02, 2026 at 04:01:21PM +0200, Gabriele Monaco wrote:
> On Thu, 2026-07-02 at 15:53 +0200, Nam Cao wrote:
> > Gabriele Monaco <gmonaco@redhat.com> writes:
> > > The RVGenerator class can find the RV directory (kernel/trace/rv) in the
> > > kernel tree to do some auto patching. This works by assuming PWD is
> > > either the kernel tree or tools/verification, which isn't always the
> > > case (e.g. when running from selftests).
> > >
> > > Make discovery more robust by relying on the absolute path of the
> > > current script and traversing backwards the right number of times.
> > > This should work from any location if rvgen is in the kernel tree.
> >
> > Agree.
> >
> > > + # find the kernel tree root relative to this file's location
> > > + current_dir = os.path.dirname(os.path.abspath(__file__))
> > > + kernel_root = os.path.abspath(os.path.join(current_dir,
> > > "../../../.."))
> >
> >
> > The "../../../.." makes me sad.
> >
> > We can find the git project root instead. For example:
> >
> > def getGitRoot():
> > return subprocess.Popen(['git', 'rev-parse', '--show-toplevel'],
> > stdout=subprocess.PIPE).communicate()[0].rstrip().decode('utf-8')
> >
> > (stolen from
> > https://stackoverflow.com/questions/22081209/find-the-root-of-the-git-repository-where-the-file-lives
> > )
> >
> > But that's not important, up to you.
>
> Mmh good point, but what if we're running from a tarball?
>
> I could still fall back to something that uses directory parents (maybe
> something arguably nicer like
>
> os.path.join(current_dir, *([".."] * 4))
>
> But that may not be more readable..
current_dir = pathlib.Path(__file__).abspath()
kernel_root = current_dir.parents[4]
pathlib is generally nicer than os.path.
Thomas
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox