* [PATCH bpf-next v3 1/2] bpf: support bpf_get_func_arg() for BPF_TRACE_RAW_TP
From: Menglong Dong @ 2026-01-19 2:37 UTC (permalink / raw)
To: andrii, ast
Cc: daniel, john.fastabend, martin.lau, eddyz87, song, yonghong.song,
kpsingh, sdf, haoluo, jolsa, mattbobrowski, rostedt, mhiramat,
mathieu.desnoyers, bpf, linux-kernel, linux-trace-kernel
In-Reply-To: <20260119023732.130642-1-dongml2@chinatelecom.cn>
For now, bpf_get_func_arg() and bpf_get_func_arg_cnt() is not supported by
the BPF_TRACE_RAW_TP, which is not convenient to get the argument of the
tracepoint, especially for the case that the position of the arguments in
a tracepoint can change.
The target tracepoint BTF type id is specified during loading time,
therefore we can get the function argument count from the function
prototype instead of the stack.
Signed-off-by: Menglong Dong <dongml2@chinatelecom.cn>
---
v3:
- remove unnecessary NULL checking for prog->aux->attach_func_proto
v2:
- for nr_args, skip first 'void *__data' argument in btf_trace_##name
typedef
---
kernel/bpf/verifier.c | 32 ++++++++++++++++++++++++++++----
kernel/trace/bpf_trace.c | 4 ++--
2 files changed, 30 insertions(+), 6 deletions(-)
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index faa1ecc1fe9d..4f52342573f0 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -23316,8 +23316,20 @@ static int do_misc_fixups(struct bpf_verifier_env *env)
/* Implement bpf_get_func_arg inline. */
if (prog_type == BPF_PROG_TYPE_TRACING &&
insn->imm == BPF_FUNC_get_func_arg) {
- /* Load nr_args from ctx - 8 */
- insn_buf[0] = BPF_LDX_MEM(BPF_DW, BPF_REG_0, BPF_REG_1, -8);
+ if (eatype == BPF_TRACE_RAW_TP) {
+ int nr_args = btf_type_vlen(prog->aux->attach_func_proto);
+
+ /*
+ * skip first 'void *__data' argument in btf_trace_##name
+ * typedef
+ */
+ nr_args--;
+ /* Save nr_args to reg0 */
+ insn_buf[0] = BPF_MOV64_IMM(BPF_REG_0, nr_args);
+ } else {
+ /* Load nr_args from ctx - 8 */
+ insn_buf[0] = BPF_LDX_MEM(BPF_DW, BPF_REG_0, BPF_REG_1, -8);
+ }
insn_buf[1] = BPF_JMP32_REG(BPF_JGE, BPF_REG_2, BPF_REG_0, 6);
insn_buf[2] = BPF_ALU64_IMM(BPF_LSH, BPF_REG_2, 3);
insn_buf[3] = BPF_ALU64_REG(BPF_ADD, BPF_REG_2, BPF_REG_1);
@@ -23369,8 +23381,20 @@ static int do_misc_fixups(struct bpf_verifier_env *env)
/* Implement get_func_arg_cnt inline. */
if (prog_type == BPF_PROG_TYPE_TRACING &&
insn->imm == BPF_FUNC_get_func_arg_cnt) {
- /* Load nr_args from ctx - 8 */
- insn_buf[0] = BPF_LDX_MEM(BPF_DW, BPF_REG_0, BPF_REG_1, -8);
+ if (eatype == BPF_TRACE_RAW_TP) {
+ int nr_args = btf_type_vlen(prog->aux->attach_func_proto);
+
+ /*
+ * skip first 'void *__data' argument in btf_trace_##name
+ * typedef
+ */
+ nr_args--;
+ /* Save nr_args to reg0 */
+ insn_buf[0] = BPF_MOV64_IMM(BPF_REG_0, nr_args);
+ } else {
+ /* Load nr_args from ctx - 8 */
+ insn_buf[0] = BPF_LDX_MEM(BPF_DW, BPF_REG_0, BPF_REG_1, -8);
+ }
new_prog = bpf_patch_insn_data(env, i + delta, insn_buf, 1);
if (!new_prog)
diff --git a/kernel/trace/bpf_trace.c b/kernel/trace/bpf_trace.c
index 6e076485bf70..9b1b56851d26 100644
--- a/kernel/trace/bpf_trace.c
+++ b/kernel/trace/bpf_trace.c
@@ -1734,11 +1734,11 @@ tracing_prog_func_proto(enum bpf_func_id func_id, const struct bpf_prog *prog)
case BPF_FUNC_d_path:
return &bpf_d_path_proto;
case BPF_FUNC_get_func_arg:
- return bpf_prog_has_trampoline(prog) ? &bpf_get_func_arg_proto : NULL;
+ return &bpf_get_func_arg_proto;
case BPF_FUNC_get_func_ret:
return bpf_prog_has_trampoline(prog) ? &bpf_get_func_ret_proto : NULL;
case BPF_FUNC_get_func_arg_cnt:
- return bpf_prog_has_trampoline(prog) ? &bpf_get_func_arg_cnt_proto : NULL;
+ return &bpf_get_func_arg_cnt_proto;
case BPF_FUNC_get_attach_cookie:
if (prog->type == BPF_PROG_TYPE_TRACING &&
prog->expected_attach_type == BPF_TRACE_RAW_TP)
--
2.52.0
^ permalink raw reply related
* [PATCH bpf-next v3 0/2] bpf: support bpf_get_func_arg() for BPF_TRACE_RAW_TP
From: Menglong Dong @ 2026-01-19 2:37 UTC (permalink / raw)
To: andrii, ast
Cc: daniel, john.fastabend, martin.lau, eddyz87, song, yonghong.song,
kpsingh, sdf, haoluo, jolsa, mattbobrowski, rostedt, mhiramat,
mathieu.desnoyers, bpf, linux-kernel, linux-trace-kernel
Support bpf_get_func_arg() for BPF_TRACE_RAW_TP by getting the function
argument count from "prog->aux->attach_func_proto" during verifier inline.
Changes v3 -> v2:
* remove unnecessary NULL checking for prog->aux->attach_func_proto
* v2: https://lore.kernel.org/bpf/20260116071739.121182-1-dongml2@chinatelecom.cn/
Changes v2 -> v1:
* for nr_args, skip first 'void *__data' argument in btf_trace_##name
typedef
* check the result4 and result5 in the selftests
* v1: https://lore.kernel.org/bpf/20260116035024.98214-1-dongml2@chinatelecom.cn/
Menglong Dong (2):
bpf: support bpf_get_func_arg() for BPF_TRACE_RAW_TP
selftests/bpf: test bpf_get_func_arg() for tp_btf
kernel/bpf/verifier.c | 32 +++++++++++--
kernel/trace/bpf_trace.c | 4 +-
.../bpf/prog_tests/get_func_args_test.c | 3 ++
.../selftests/bpf/progs/get_func_args_test.c | 45 +++++++++++++++++++
.../bpf/test_kmods/bpf_testmod-events.h | 10 +++++
.../selftests/bpf/test_kmods/bpf_testmod.c | 4 ++
6 files changed, 92 insertions(+), 6 deletions(-)
--
2.52.0
^ permalink raw reply
* Re: [PATCH V5 0/2] mm/khugepaged: fix dirty page handling for MADV_COLLAPSE
From: Andrew Morton @ 2026-01-18 20:22 UTC (permalink / raw)
To: Shivank Garg
Cc: David Hildenbrand, Lorenzo Stoakes, Zi Yan, Baolin Wang,
Liam R . Howlett, Nico Pache, Ryan Roberts, Dev Jain, Barry Song,
Lance Yang, Masami Hiramatsu, Steven Rostedt, linux-trace-kernel,
Mathieu Desnoyers, Zach O'Keefe, linux-mm, linux-kernel,
Stephen Rothwell
In-Reply-To: <20260118190939.8986-2-shivankg@amd.com>
On Sun, 18 Jan 2026 19:09:38 +0000 Shivank Garg <shivankg@amd.com> wrote:
> MADV_COLLAPSE on file-backed mappings fails with -EINVAL when TEXT pages
> are dirty. This affects scenarios like package/container updates or
> executing binaries immediately after writing them, etc.
>
> The issue is that collapse_file() triggers async writeback and returns
> SCAN_FAIL (maps to -EINVAL), expecting khugepaged to revisit later. But
> MADV_COLLAPSE is synchronous and userspace expects immediate success or
> a clear retry signal.
>
> Reproduction:
> - Compile or copy 2MB-aligned executable to XFS/ext4 FS
> - Call MADV_COLLAPSE on .text section
> - First call fails with -EINVAL (text pages dirty from copy)
> - Second call succeeds (async writeback completed)
>
> Issue Report:
> https://lore.kernel.org/all/4e26fe5e-7374-467c-a333-9dd48f85d7cc@amd.com
Updated, thanks.
Please tolerate a little whining about the timeliess here. We're at
-rc6, v4 was added to mm.git over a month ago, had quite a lot of
review, this is very close to being moved into the mm-stable branch and now
we get v5. Argh.
> V5:
> - In patch 2/2, Simplify dirty writeback retry logic (David)
Are you sure this is the only change? It looks like a lot for a
simplification and I'm wondering if we should retain the v4 series and
defer a simplification for separate consideration during the next
cycle.
Below is how this updated altered mm.git. Could reviewers please check
this fairly soon?
--- a/mm/khugepaged.c~b
+++ a/mm/khugepaged.c
@@ -2788,11 +2788,11 @@ int madvise_collapse(struct vm_area_stru
hend = end & HPAGE_PMD_MASK;
for (addr = hstart; addr < hend; addr += HPAGE_PMD_SIZE) {
- bool retried = false;
int result = SCAN_FAIL;
+ bool triggered_wb = false;
- if (!mmap_locked) {
retry:
+ if (!mmap_locked) {
cond_resched();
mmap_read_lock(mm);
mmap_locked = true;
@@ -2812,52 +2812,27 @@ retry:
mmap_read_unlock(mm);
mmap_locked = false;
+ *lock_dropped = true;
result = hpage_collapse_scan_file(mm, addr, file, pgoff,
cc);
- fput(file);
- } else {
- result = hpage_collapse_scan_pmd(mm, vma, addr,
- &mmap_locked, cc);
- }
- if (!mmap_locked)
- *lock_dropped = true;
-
- /*
- * If the file-backed VMA has dirty pages, the scan triggers
- * async writeback and returns SCAN_PAGE_DIRTY_OR_WRITEBACK.
- * Since MADV_COLLAPSE is sync, we force sync writeback and
- * retry once.
- */
- if (result == SCAN_PAGE_DIRTY_OR_WRITEBACK && !retried) {
- /*
- * File scan drops the lock. We must re-acquire it to
- * safely inspect the VMA and hold the file reference.
- */
- if (!mmap_locked) {
- cond_resched();
- mmap_read_lock(mm);
- mmap_locked = true;
- result = hugepage_vma_revalidate(mm, addr, false, &vma, cc);
- if (result != SCAN_SUCCEED)
- goto handle_result;
- }
- if (!vma_is_anonymous(vma) && vma->vm_file &&
- mapping_can_writeback(vma->vm_file->f_mapping)) {
- struct file *file = get_file(vma->vm_file);
- pgoff_t pgoff = linear_page_index(vma, addr);
+ if (result == SCAN_PAGE_DIRTY_OR_WRITEBACK && !triggered_wb &&
+ mapping_can_writeback(file->f_mapping)) {
loff_t lstart = (loff_t)pgoff << PAGE_SHIFT;
loff_t lend = lstart + HPAGE_PMD_SIZE - 1;
- mmap_read_unlock(mm);
- mmap_locked = false;
- *lock_dropped = true;
filemap_write_and_wait_range(file->f_mapping, lstart, lend);
+ triggered_wb = true;
fput(file);
- retried = true;
goto retry;
}
+ fput(file);
+ } else {
+ result = hpage_collapse_scan_pmd(mm, vma, addr,
+ &mmap_locked, cc);
}
+ if (!mmap_locked)
+ *lock_dropped = true;
handle_result:
switch (result) {
_
^ permalink raw reply
* Re: [PATCH V4 2/2] mm/khugepaged: retry with sync writeback for MADV_COLLAPSE
From: Garg, Shivank @ 2026-01-18 19:51 UTC (permalink / raw)
To: David Hildenbrand (Red Hat), Andrew Morton, Lorenzo Stoakes
Cc: Zi Yan, Baolin Wang, Liam R . Howlett, Nico Pache, Ryan Roberts,
Dev Jain, Barry Song, Lance Yang, Steven Rostedt,
Masami Hiramatsu, Mathieu Desnoyers, Zach O'Keefe, linux-mm,
linux-kernel, linux-trace-kernel, Branden Moore
In-Reply-To: <976561d0-0edb-4f09-8ed1-ab8f85c9aa87@kernel.org>
On 1/15/2026 1:44 AM, David Hildenbrand (Red Hat) wrote:
> On 1/14/26 20:47, Garg, Shivank wrote:
>>
>>
>> On 1/11/2026 4:59 PM, David Hildenbrand (Red Hat) wrote:
>>> On 1/10/26 19:20, Garg, Shivank wrote:
>>>>
>>>>
>>>> On 1/9/2026 8:16 PM, David Hildenbrand (Red Hat) wrote:
>>>>> On 12/15/25 09:46, Shivank Garg wrote:
>>
>>>>>
>>>>> This looks a bit complicated. Can't we move that handing up, where we have most of that
>>>>> information already? Or am I missing something important?
>>>>>
>>>>> diff --git a/mm/khugepaged.c b/mm/khugepaged.c
>>>>> index 97d1b2824386f..c7271877c5220 100644
>>>>> --- a/mm/khugepaged.c
>>>>> +++ b/mm/khugepaged.c
>>>>> @@ -22,6 +22,7 @@
>>>>> #include <linux/dax.h>
>>>>> #include <linux/ksm.h>
>>>>> #include <linux/pgalloc.h>
>>>>> +#include <linux/backing-dev.h>
>>>>> #include <asm/tlb.h>
>>>>> #include "internal.h"
>>>>> @@ -2786,7 +2787,9 @@ int madvise_collapse(struct vm_area_struct *vma, unsigned long start,
>>>>> for (addr = hstart; addr < hend; addr += HPAGE_PMD_SIZE) {
>>>>> int result = SCAN_FAIL;
>>>>> + bool triggered_wb = false;
>>>>> +retry:
>>>>> if (!mmap_locked) {
>>>>> cond_resched();
>>>>> mmap_read_lock(mm);
>>>>> @@ -2809,6 +2812,16 @@ int madvise_collapse(struct vm_area_struct *vma, unsigned long start,
>>>>> mmap_locked = false;
>>>> *lock_dropped = true;
>>>>> result = hpage_collapse_scan_file(mm, addr, file, pgoff,
>>>>> cc);
>>>>> +
>>>>> + if (result == SCAN_PAGE_DIRTY_OR_WRITEBACK && !triggered_wb &&
>>>>> + mapping_can_writeback(file->f_mapping)) {
>>>>> + loff_t lstart = (loff_t)pgoff << PAGE_SHIFT;
>>>>> + loff_t lend = lstart + HPAGE_PMD_SIZE - 1;
>>>>> +
>>>>> + filemap_write_and_wait_range(file->f_mapping, lstart, lend);
>>>>> + triggered_wb = true;
>>>>
>>>> fput(file);
>>>>
>>>>> + goto retry;
>>>>> + }
>>>>> fput(file);
>>>>> } else {
>>>>> result = hpage_collapse_scan_pmd(mm, vma, addr,
>>>>>
>>>>>
>>>>
>>>> Thank you for the suggestion, this approach looks much simpler.
>>>>
>>>> There are two small nits I observed:
>>>
>>> Yeah, was a quick untested hack to see if this can be simplified :)
>>>
>>>>
>>>> 1. In the retry loop, it is possible that we reacquire the mmap_lock and set
>>>> mmap_locked to true. This can cause issues later when we do:
>>>>
>>>> if (!mmap_locked)
>>>> *lock_dropped = true;
>>>
>>> That whole logic of having two variables that express whether locks have been taken/dropped is just absolutely confusing. Any way we can clean that up?
>>>
>>>>
>>>> because the caller would no longer see that the lock was dropped earlier.
>>>>
>>>> 2. We need an fput() to balance the file reference taken at line 2795.
>>>
>>> Ah, yes, makes sense. Having a single fput() would be nicer, but that would require yet another temporary variable.
>>>
>>
>> I agree, that this interaction for lock taken/droped is confusing.
>> However, a proper clean-up would require refactoring the locking logic across multiple functions in the collapse call-flow path.
>> This seems significantly more invasive and risky.
>>
>> I would like to handle this refactoring but in a separate TODO for later.
>> Could we please proceed with these minimal changes for now?
>
> Sure, fine with me.
>
>>
>> Since, V4 has been in the linux-next/mm-unstable for a while, should I send a v5 or an incremental clean-up on top for this?
>
> Just send a v4, unless Andrew tells you otherwise :)
Thanks! I have send V5:
https://lore.kernel.org/linux-mm/20260118192253.9263-14-shivankg@amd.com
Best Regards,
Shivank
^ permalink raw reply
* [PATCH V5 2/2] mm/khugepaged: retry with sync writeback for MADV_COLLAPSE
From: Shivank Garg @ 2026-01-18 19:09 UTC (permalink / raw)
To: Andrew Morton, David Hildenbrand, Lorenzo Stoakes
Cc: Zi Yan, Baolin Wang, Liam R . Howlett, Nico Pache, Ryan Roberts,
Dev Jain, Barry Song, Lance Yang, Masami Hiramatsu,
Steven Rostedt, linux-trace-kernel, Mathieu Desnoyers,
Zach O'Keefe, linux-mm, linux-kernel, Stephen Rothwell,
shivankg, Branden Moore
In-Reply-To: <20260118190939.8986-2-shivankg@amd.com>
When MADV_COLLAPSE is called on file-backed mappings (e.g., executable
text sections), the pages may still be dirty from recent writes.
collapse_file() will trigger async writeback and fail with
SCAN_PAGE_DIRTY_OR_WRITEBACK (-EAGAIN).
MADV_COLLAPSE is a synchronous operation where userspace expects
immediate results. If the collapse fails due to dirty pages, perform
synchronous writeback on the specific range and retry once.
This avoids spurious failures for freshly written executables while
avoiding unnecessary synchronous I/O for mappings that are already clean.
Reported-by: Branden Moore <Branden.Moore@amd.com>
Closes: https://lore.kernel.org/all/4e26fe5e-7374-467c-a333-9dd48f85d7cc@amd.com
Fixes: 34488399fa08 ("mm/madvise: add file and shmem support to MADV_COLLAPSE")
Suggested-by: David Hildenbrand <david@kernel.org>
Tested-by: Lance Yang <lance.yang@linux.dev>
Signed-off-by: Shivank Garg <shivankg@amd.com>
---
mm/khugepaged.c | 15 +++++++++++++++
1 file changed, 15 insertions(+)
diff --git a/mm/khugepaged.c b/mm/khugepaged.c
index 219dfa2e523c..16582bdcb6ff 100644
--- a/mm/khugepaged.c
+++ b/mm/khugepaged.c
@@ -22,6 +22,7 @@
#include <linux/dax.h>
#include <linux/ksm.h>
#include <linux/pgalloc.h>
+#include <linux/backing-dev.h>
#include <asm/tlb.h>
#include "internal.h"
@@ -2788,7 +2789,9 @@ int madvise_collapse(struct vm_area_struct *vma, unsigned long start,
for (addr = hstart; addr < hend; addr += HPAGE_PMD_SIZE) {
int result = SCAN_FAIL;
+ bool triggered_wb = false;
+retry:
if (!mmap_locked) {
cond_resched();
mmap_read_lock(mm);
@@ -2809,8 +2812,20 @@ int madvise_collapse(struct vm_area_struct *vma, unsigned long start,
mmap_read_unlock(mm);
mmap_locked = false;
+ *lock_dropped = true;
result = hpage_collapse_scan_file(mm, addr, file, pgoff,
cc);
+
+ if (result == SCAN_PAGE_DIRTY_OR_WRITEBACK && !triggered_wb &&
+ mapping_can_writeback(file->f_mapping)) {
+ loff_t lstart = (loff_t)pgoff << PAGE_SHIFT;
+ loff_t lend = lstart + HPAGE_PMD_SIZE - 1;
+
+ filemap_write_and_wait_range(file->f_mapping, lstart, lend);
+ triggered_wb = true;
+ fput(file);
+ goto retry;
+ }
fput(file);
} else {
result = hpage_collapse_scan_pmd(mm, vma, addr,
--
2.43.0
^ permalink raw reply related
* [PATCH V5 1/2] mm/khugepaged: map dirty/writeback pages failures to EAGAIN
From: Shivank Garg @ 2026-01-18 19:09 UTC (permalink / raw)
To: Andrew Morton, David Hildenbrand, Lorenzo Stoakes
Cc: Zi Yan, Baolin Wang, Liam R . Howlett, Nico Pache, Ryan Roberts,
Dev Jain, Barry Song, Lance Yang, Masami Hiramatsu,
Steven Rostedt, linux-trace-kernel, Mathieu Desnoyers,
Zach O'Keefe, linux-mm, linux-kernel, Stephen Rothwell,
shivankg, Branden Moore, wang lian
In-Reply-To: <20260118190939.8986-2-shivankg@amd.com>
When collapse_file encounters dirty or writeback pages in file-backed
mappings, it currently returns SCAN_FAIL which maps to -EINVAL. This is
misleading as EINVAL suggests invalid arguments, whereas dirty/writeback
pages represent transient conditions that may resolve on retry.
Introduce SCAN_PAGE_DIRTY_OR_WRITEBACK to cover both dirty and writeback
states, mapping it to -EAGAIN. For MADV_COLLAPSE, this provides userspace
with a clear signal that retry may succeed after writeback completes. For
khugepaged, this is harmless as it will naturally revisit the range during
periodic scans after async writeback completes.
Fixes: 34488399fa08 ("mm/madvise: add file and shmem support to MADV_COLLAPSE")
Reported-by: Branden Moore <Branden.Moore@amd.com>
Closes: https://lore.kernel.org/all/4e26fe5e-7374-467c-a333-9dd48f85d7cc@amd.com
Reviewed-by: Dev Jain <dev.jain@arm.com>
Reviewed-by: Lance Yang <lance.yang@linux.dev>
Reviewed-by: Baolin Wang <baolin.wang@linux.alibaba.com>
Reviewed-by: wang lian <lianux.mm@gmail.com>
Acked-by: David Hildenbrand (Red Hat) <david@kernel.org>
Signed-off-by: Shivank Garg <shivankg@amd.com>
---
include/trace/events/huge_memory.h | 3 ++-
mm/khugepaged.c | 8 +++++---
2 files changed, 7 insertions(+), 4 deletions(-)
diff --git a/include/trace/events/huge_memory.h b/include/trace/events/huge_memory.h
index 4cde53b45a85..4e41bff31888 100644
--- a/include/trace/events/huge_memory.h
+++ b/include/trace/events/huge_memory.h
@@ -37,7 +37,8 @@
EM( SCAN_PAGE_HAS_PRIVATE, "page_has_private") \
EM( SCAN_STORE_FAILED, "store_failed") \
EM( SCAN_COPY_MC, "copy_poisoned_page") \
- EMe(SCAN_PAGE_FILLED, "page_filled")
+ EM( SCAN_PAGE_FILLED, "page_filled") \
+ EMe(SCAN_PAGE_DIRTY_OR_WRITEBACK, "page_dirty_or_writeback")
#undef EM
#undef EMe
diff --git a/mm/khugepaged.c b/mm/khugepaged.c
index 97d1b2824386..219dfa2e523c 100644
--- a/mm/khugepaged.c
+++ b/mm/khugepaged.c
@@ -58,6 +58,7 @@ enum scan_result {
SCAN_STORE_FAILED,
SCAN_COPY_MC,
SCAN_PAGE_FILLED,
+ SCAN_PAGE_DIRTY_OR_WRITEBACK,
};
#define CREATE_TRACE_POINTS
@@ -1967,11 +1968,11 @@ static int collapse_file(struct mm_struct *mm, unsigned long addr,
*/
xas_unlock_irq(&xas);
filemap_flush(mapping);
- result = SCAN_FAIL;
+ result = SCAN_PAGE_DIRTY_OR_WRITEBACK;
goto xa_unlocked;
} else if (folio_test_writeback(folio)) {
xas_unlock_irq(&xas);
- result = SCAN_FAIL;
+ result = SCAN_PAGE_DIRTY_OR_WRITEBACK;
goto xa_unlocked;
} else if (folio_trylock(folio)) {
folio_get(folio);
@@ -2018,7 +2019,7 @@ static int collapse_file(struct mm_struct *mm, unsigned long addr,
* folio is dirty because it hasn't been flushed
* since first write.
*/
- result = SCAN_FAIL;
+ result = SCAN_PAGE_DIRTY_OR_WRITEBACK;
goto out_unlock;
}
@@ -2747,6 +2748,7 @@ static int madvise_collapse_errno(enum scan_result r)
case SCAN_PAGE_LRU:
case SCAN_DEL_PAGE_LRU:
case SCAN_PAGE_FILLED:
+ case SCAN_PAGE_DIRTY_OR_WRITEBACK:
return -EAGAIN;
/*
* Other: Trying again likely not to succeed / error intrinsic to
--
2.43.0
^ permalink raw reply related
* [PATCH V5 0/2] mm/khugepaged: fix dirty page handling for MADV_COLLAPSE
From: Shivank Garg @ 2026-01-18 19:09 UTC (permalink / raw)
To: Andrew Morton, David Hildenbrand, Lorenzo Stoakes
Cc: Zi Yan, Baolin Wang, Liam R . Howlett, Nico Pache, Ryan Roberts,
Dev Jain, Barry Song, Lance Yang, Masami Hiramatsu,
Steven Rostedt, linux-trace-kernel, Mathieu Desnoyers,
Zach O'Keefe, linux-mm, linux-kernel, Stephen Rothwell,
shivankg
MADV_COLLAPSE on file-backed mappings fails with -EINVAL when TEXT pages
are dirty. This affects scenarios like package/container updates or
executing binaries immediately after writing them, etc.
The issue is that collapse_file() triggers async writeback and returns
SCAN_FAIL (maps to -EINVAL), expecting khugepaged to revisit later. But
MADV_COLLAPSE is synchronous and userspace expects immediate success or
a clear retry signal.
Reproduction:
- Compile or copy 2MB-aligned executable to XFS/ext4 FS
- Call MADV_COLLAPSE on .text section
- First call fails with -EINVAL (text pages dirty from copy)
- Second call succeeds (async writeback completed)
Issue Report:
https://lore.kernel.org/all/4e26fe5e-7374-467c-a333-9dd48f85d7cc@amd.com
Hi Andrew,
This V5 series incorporates David's feedback for simplifying the retry
logic. To apply on mm-new, please drop:
- 20251215084615.5283-3-shivankg@amd.com:
[PATCH V4 0/2] mm/khugepaged: fix dirty page handling for MADV_COLLAPSE
- 20251224111351.41042-4-shivankg@amd.com:
[PATCH V2 0/5] mm/khugepaged: cleanups and scan limit fix
(merge conflicts with this series; V3 with review fixes posting soon)
Thank you :)
Changelog:
V5:
- In patch 2/2, Simplify dirty writeback retry logic (David)
V4:
- https://lore.kernel.org/all/20251215084615.5283-3-shivankg@amd.com
- Rebase on mm-new
- Fix spurious blank line (Lance)
V3:
- https://lore.kernel.org/all/20251201185604.210634-6-shivankg@amd.com
- Reordered patches: Enum definition comes first as the retry logic depends on it
- Renamed SCAN_PAGE_NOT_CLEAN to SCAN_PAGE_DIRTY_OR_WRITEBACK (Dev, Lance, David)
- Changed writeback logic: Only trigger synchronous writeback and retry if the
initial collapse attempt failed specifically due to dirty/writeback pages,
rather than blindly flushing all file-backed VMAs (David)
- Added proper file reference counting (get_file/fput) around the unlock window
to prevent UAF (Lance)
V2:
- https://lore.kernel.org/all/20251120065043.41738-6-shivankg@amd.com
- Move writeback to madvise_collapse() (better abstraction, proper
mmap_lock handling and does VMA revalidation after I/O) (Lorenzo)
- Rename to SCAN_PAGE_DIRTY to SCAN_PAGE_NOT_CLEAN and extend its use
for all dirty/writeback folio cases that previously returned incorrect
results (Dev)
V1: https://lore.kernel.org/all/20251110113254.77822-1-shivankg@amd.com
Thanks,
Shivank Garg (2):
mm/khugepaged: map dirty/writeback pages failures to EAGAIN
mm/khugepaged: retry with sync writeback for MADV_COLLAPSE
include/trace/events/huge_memory.h | 3 ++-
mm/khugepaged.c | 23 ++++++++++++++++++++---
2 files changed, 22 insertions(+), 4 deletions(-)
--
2.43.0
^ permalink raw reply
* Re: [PATCH] scripts/tracepoint-update: fix memory leak in make_trace_array()
From: Steven Rostedt @ 2026-01-18 15:54 UTC (permalink / raw)
To: Weigang He
Cc: Masami Hiramatsu, Mathieu Desnoyers, linux-kernel,
linux-trace-kernel, Tuo Li
In-Reply-To: <20260118130247.1003369-1-geoffreyhe2@gmail.com>
On Sun, 18 Jan 2026 13:02:47 +0000
Weigang He <geoffreyhe2@gmail.com> wrote:
> In make_trace_array(), if add_string() fails after some successful
> iterations, the function returns without freeing the 'vals' array that
> was allocated by previous add_string() calls.
>
> The add_string() function uses realloc() internally with a local
> temporary variable, which means the original pointer is preserved on
> allocation failure. When make_trace_array() returns early on error,
> the previously allocated memory is leaked.
>
> Fix this by freeing 'vals' before returning on the error path.
>
> This bug is found by my static analysis tool and my code review.
>
> Signed-off-by: Tuo Li <islituo@gmail.com>
> ---
> scripts/tracepoint-update.c | 4 +++-
> 1 file changed, 3 insertions(+), 1 deletion(-)
>
> diff --git a/scripts/tracepoint-update.c b/scripts/tracepoint-update.c
> index 90046aedc97b9..7bc9d66229ddf 100644
> --- a/scripts/tracepoint-update.c
> +++ b/scripts/tracepoint-update.c
> @@ -93,8 +93,10 @@ static void make_trace_array(struct elf_tracepoint *etrace)
> for_each_shdr_str(len, ehdr, check_data_sec) {
> if (!len)
> continue;
> - if (add_string(str, &vals, &count) < 0)
> + if (add_string(str, &vals, &count) < 0) {
> + free(vals);
> return;
> + }
> }
It would make much more sense to have add_string() free vals, and set
vals to NULL on error.
-- Steve
>
> /* If CONFIG_TRACEPOINT_VERIFY_USED is not set, there's nothing to do */
^ permalink raw reply
* [PATCH] scripts/tracepoint-update: fix memory leak in make_trace_array()
From: Weigang He @ 2026-01-18 13:02 UTC (permalink / raw)
To: Steven Rostedt, Masami Hiramatsu
Cc: Mathieu Desnoyers, linux-kernel, linux-trace-kernel, Weigang He,
Tuo Li
In make_trace_array(), if add_string() fails after some successful
iterations, the function returns without freeing the 'vals' array that
was allocated by previous add_string() calls.
The add_string() function uses realloc() internally with a local
temporary variable, which means the original pointer is preserved on
allocation failure. When make_trace_array() returns early on error,
the previously allocated memory is leaked.
Fix this by freeing 'vals' before returning on the error path.
This bug is found by my static analysis tool and my code review.
Signed-off-by: Tuo Li <islituo@gmail.com>
---
scripts/tracepoint-update.c | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/scripts/tracepoint-update.c b/scripts/tracepoint-update.c
index 90046aedc97b9..7bc9d66229ddf 100644
--- a/scripts/tracepoint-update.c
+++ b/scripts/tracepoint-update.c
@@ -93,8 +93,10 @@ static void make_trace_array(struct elf_tracepoint *etrace)
for_each_shdr_str(len, ehdr, check_data_sec) {
if (!len)
continue;
- if (add_string(str, &vals, &count) < 0)
+ if (add_string(str, &vals, &count) < 0) {
+ free(vals);
return;
+ }
}
/* If CONFIG_TRACEPOINT_VERIFY_USED is not set, there's nothing to do */
--
2.34.1
^ permalink raw reply related
* [PATCH bpf RESEND v2 2/2] bpf: Require ARG_PTR_TO_MEM with memory flag
From: Zesen Liu @ 2026-01-18 8:16 UTC (permalink / raw)
To: Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Martin KaFai Lau, Eduard Zingerman, Song Liu, Yonghong Song,
John Fastabend, KP Singh, Stanislav Fomichev, Hao Luo, Jiri Olsa,
Matt Bobrowski, Steven Rostedt, Masami Hiramatsu,
Mathieu Desnoyers, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Simon Horman, Daniel Xu
Cc: bpf, linux-kernel, linux-trace-kernel, netdev, Shuran Liu,
Peili Gao, Haoran Ni, Zesen Liu
In-Reply-To: <20260118-helper_proto-v2-0-ab3a1337e755@gmail.com>
Add check to ensure that ARG_PTR_TO_MEM is used with either MEM_WRITE or
MEM_RDONLY.
Using ARG_PTR_TO_MEM alone without tags does not make sense because:
- If the helper does not change the argument, missing MEM_RDONLY causes the
verifier to incorrectly reject a read-only buffer.
- If the helper does change the argument, missing MEM_WRITE causes the
verifier to incorrectly assume the memory is unchanged, leading to errors
in code optimization.
Co-developed-by: Shuran Liu <electronlsr@gmail.com>
Signed-off-by: Shuran Liu <electronlsr@gmail.com>
Co-developed-by: Peili Gao <gplhust955@gmail.com>
Signed-off-by: Peili Gao <gplhust955@gmail.com>
Co-developed-by: Haoran Ni <haoran.ni.cs@gmail.com>
Signed-off-by: Haoran Ni <haoran.ni.cs@gmail.com>
Signed-off-by: Zesen Liu <ftyghome@gmail.com>
---
kernel/bpf/verifier.c | 17 +++++++++++++++++
1 file changed, 17 insertions(+)
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index f0ca69f888fa..c7ebddb66385 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -10349,10 +10349,27 @@ static bool check_btf_id_ok(const struct bpf_func_proto *fn)
return true;
}
+static bool check_mem_arg_rw_flag_ok(const struct bpf_func_proto *fn)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(fn->arg_type); i++) {
+ enum bpf_arg_type arg_type = fn->arg_type[i];
+
+ if (base_type(arg_type) != ARG_PTR_TO_MEM)
+ continue;
+ if (!(arg_type & (MEM_WRITE | MEM_RDONLY)))
+ return false;
+ }
+
+ return true;
+}
+
static int check_func_proto(const struct bpf_func_proto *fn, int func_id)
{
return check_raw_mode_ok(fn) &&
check_arg_pair_ok(fn) &&
+ check_mem_arg_rw_flag_ok(fn) &&
check_btf_id_ok(fn) ? 0 : -EINVAL;
}
--
2.43.0
^ permalink raw reply related
* [PATCH bpf RESEND v2 1/2] bpf: Fix memory access flags in helper prototypes
From: Zesen Liu @ 2026-01-18 8:16 UTC (permalink / raw)
To: Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Martin KaFai Lau, Eduard Zingerman, Song Liu, Yonghong Song,
John Fastabend, KP Singh, Stanislav Fomichev, Hao Luo, Jiri Olsa,
Matt Bobrowski, Steven Rostedt, Masami Hiramatsu,
Mathieu Desnoyers, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Simon Horman, Daniel Xu
Cc: bpf, linux-kernel, linux-trace-kernel, netdev, Shuran Liu,
Peili Gao, Haoran Ni, Zesen Liu
In-Reply-To: <20260118-helper_proto-v2-0-ab3a1337e755@gmail.com>
After commit 37cce22dbd51 ("bpf: verifier: Refactor helper access type tracking"),
the verifier started relying on the access type flags in helper
function prototypes to perform memory access optimizations.
Currently, several helper functions utilizing ARG_PTR_TO_MEM lack the
corresponding MEM_RDONLY or MEM_WRITE flags. This omission causes the
verifier to incorrectly assume that the buffer contents are unchanged
across the helper call. Consequently, the verifier may optimize away
subsequent reads based on this wrong assumption, leading to correctness
issues.
For bpf_get_stack_proto_raw_tp, the original MEM_RDONLY was incorrect
since the helper writes to the buffer. Change it to ARG_PTR_TO_UNINIT_MEM
which correctly indicates write access to potentially uninitialized memory.
Similar issues were recently addressed for specific helpers in commit
ac44dcc788b9 ("bpf: Fix verifier assumptions of bpf_d_path's output buffer")
and commit 2eb7648558a7 ("bpf: Specify access type of bpf_sysctl_get_name args").
Fix these prototypes by adding the correct memory access flags.
Fixes: 37cce22dbd51 ("bpf: verifier: Refactor helper access type tracking")
Co-developed-by: Shuran Liu <electronlsr@gmail.com>
Signed-off-by: Shuran Liu <electronlsr@gmail.com>
Co-developed-by: Peili Gao <gplhust955@gmail.com>
Signed-off-by: Peili Gao <gplhust955@gmail.com>
Co-developed-by: Haoran Ni <haoran.ni.cs@gmail.com>
Signed-off-by: Haoran Ni <haoran.ni.cs@gmail.com>
Signed-off-by: Zesen Liu <ftyghome@gmail.com>
---
kernel/bpf/helpers.c | 2 +-
kernel/bpf/syscall.c | 2 +-
kernel/trace/bpf_trace.c | 6 +++---
net/core/filter.c | 20 ++++++++++----------
4 files changed, 15 insertions(+), 15 deletions(-)
diff --git a/kernel/bpf/helpers.c b/kernel/bpf/helpers.c
index db72b96f9c8c..f66284f8ec2c 100644
--- a/kernel/bpf/helpers.c
+++ b/kernel/bpf/helpers.c
@@ -1077,7 +1077,7 @@ const struct bpf_func_proto bpf_snprintf_proto = {
.func = bpf_snprintf,
.gpl_only = true,
.ret_type = RET_INTEGER,
- .arg1_type = ARG_PTR_TO_MEM_OR_NULL,
+ .arg1_type = ARG_PTR_TO_MEM_OR_NULL | MEM_WRITE,
.arg2_type = ARG_CONST_SIZE_OR_ZERO,
.arg3_type = ARG_PTR_TO_CONST_STR,
.arg4_type = ARG_PTR_TO_MEM | PTR_MAYBE_NULL | MEM_RDONLY,
diff --git a/kernel/bpf/syscall.c b/kernel/bpf/syscall.c
index 4ff82144f885..ee116a3b7baf 100644
--- a/kernel/bpf/syscall.c
+++ b/kernel/bpf/syscall.c
@@ -6407,7 +6407,7 @@ static const struct bpf_func_proto bpf_kallsyms_lookup_name_proto = {
.func = bpf_kallsyms_lookup_name,
.gpl_only = false,
.ret_type = RET_INTEGER,
- .arg1_type = ARG_PTR_TO_MEM,
+ .arg1_type = ARG_PTR_TO_MEM | MEM_RDONLY,
.arg2_type = ARG_CONST_SIZE_OR_ZERO,
.arg3_type = ARG_ANYTHING,
.arg4_type = ARG_PTR_TO_FIXED_SIZE_MEM | MEM_UNINIT | MEM_WRITE | MEM_ALIGNED,
diff --git a/kernel/trace/bpf_trace.c b/kernel/trace/bpf_trace.c
index fe28d86f7c35..59c2394981c7 100644
--- a/kernel/trace/bpf_trace.c
+++ b/kernel/trace/bpf_trace.c
@@ -1022,7 +1022,7 @@ const struct bpf_func_proto bpf_snprintf_btf_proto = {
.func = bpf_snprintf_btf,
.gpl_only = false,
.ret_type = RET_INTEGER,
- .arg1_type = ARG_PTR_TO_MEM,
+ .arg1_type = ARG_PTR_TO_MEM | MEM_WRITE,
.arg2_type = ARG_CONST_SIZE,
.arg3_type = ARG_PTR_TO_MEM | MEM_RDONLY,
.arg4_type = ARG_CONST_SIZE,
@@ -1526,7 +1526,7 @@ static const struct bpf_func_proto bpf_read_branch_records_proto = {
.gpl_only = true,
.ret_type = RET_INTEGER,
.arg1_type = ARG_PTR_TO_CTX,
- .arg2_type = ARG_PTR_TO_MEM_OR_NULL,
+ .arg2_type = ARG_PTR_TO_MEM_OR_NULL | MEM_WRITE,
.arg3_type = ARG_CONST_SIZE_OR_ZERO,
.arg4_type = ARG_ANYTHING,
};
@@ -1661,7 +1661,7 @@ static const struct bpf_func_proto bpf_get_stack_proto_raw_tp = {
.gpl_only = true,
.ret_type = RET_INTEGER,
.arg1_type = ARG_PTR_TO_CTX,
- .arg2_type = ARG_PTR_TO_MEM | MEM_RDONLY,
+ .arg2_type = ARG_PTR_TO_UNINIT_MEM,
.arg3_type = ARG_CONST_SIZE_OR_ZERO,
.arg4_type = ARG_ANYTHING,
};
diff --git a/net/core/filter.c b/net/core/filter.c
index 616e0520a0bb..18174e0d3fcf 100644
--- a/net/core/filter.c
+++ b/net/core/filter.c
@@ -6399,7 +6399,7 @@ static const struct bpf_func_proto bpf_xdp_fib_lookup_proto = {
.gpl_only = true,
.ret_type = RET_INTEGER,
.arg1_type = ARG_PTR_TO_CTX,
- .arg2_type = ARG_PTR_TO_MEM,
+ .arg2_type = ARG_PTR_TO_MEM | MEM_WRITE,
.arg3_type = ARG_CONST_SIZE,
.arg4_type = ARG_ANYTHING,
};
@@ -6454,7 +6454,7 @@ static const struct bpf_func_proto bpf_skb_fib_lookup_proto = {
.gpl_only = true,
.ret_type = RET_INTEGER,
.arg1_type = ARG_PTR_TO_CTX,
- .arg2_type = ARG_PTR_TO_MEM,
+ .arg2_type = ARG_PTR_TO_MEM | MEM_WRITE,
.arg3_type = ARG_CONST_SIZE,
.arg4_type = ARG_ANYTHING,
};
@@ -8008,9 +8008,9 @@ static const struct bpf_func_proto bpf_tcp_raw_gen_syncookie_ipv4_proto = {
.gpl_only = true, /* __cookie_v4_init_sequence() is GPL */
.pkt_access = true,
.ret_type = RET_INTEGER,
- .arg1_type = ARG_PTR_TO_FIXED_SIZE_MEM,
+ .arg1_type = ARG_PTR_TO_FIXED_SIZE_MEM | MEM_RDONLY,
.arg1_size = sizeof(struct iphdr),
- .arg2_type = ARG_PTR_TO_MEM,
+ .arg2_type = ARG_PTR_TO_MEM | MEM_RDONLY,
.arg3_type = ARG_CONST_SIZE_OR_ZERO,
};
@@ -8040,9 +8040,9 @@ static const struct bpf_func_proto bpf_tcp_raw_gen_syncookie_ipv6_proto = {
.gpl_only = true, /* __cookie_v6_init_sequence() is GPL */
.pkt_access = true,
.ret_type = RET_INTEGER,
- .arg1_type = ARG_PTR_TO_FIXED_SIZE_MEM,
+ .arg1_type = ARG_PTR_TO_FIXED_SIZE_MEM | MEM_RDONLY,
.arg1_size = sizeof(struct ipv6hdr),
- .arg2_type = ARG_PTR_TO_MEM,
+ .arg2_type = ARG_PTR_TO_MEM | MEM_RDONLY,
.arg3_type = ARG_CONST_SIZE_OR_ZERO,
};
@@ -8060,9 +8060,9 @@ static const struct bpf_func_proto bpf_tcp_raw_check_syncookie_ipv4_proto = {
.gpl_only = true, /* __cookie_v4_check is GPL */
.pkt_access = true,
.ret_type = RET_INTEGER,
- .arg1_type = ARG_PTR_TO_FIXED_SIZE_MEM,
+ .arg1_type = ARG_PTR_TO_FIXED_SIZE_MEM | MEM_RDONLY,
.arg1_size = sizeof(struct iphdr),
- .arg2_type = ARG_PTR_TO_FIXED_SIZE_MEM,
+ .arg2_type = ARG_PTR_TO_FIXED_SIZE_MEM | MEM_RDONLY,
.arg2_size = sizeof(struct tcphdr),
};
@@ -8084,9 +8084,9 @@ static const struct bpf_func_proto bpf_tcp_raw_check_syncookie_ipv6_proto = {
.gpl_only = true, /* __cookie_v6_check is GPL */
.pkt_access = true,
.ret_type = RET_INTEGER,
- .arg1_type = ARG_PTR_TO_FIXED_SIZE_MEM,
+ .arg1_type = ARG_PTR_TO_FIXED_SIZE_MEM | MEM_RDONLY,
.arg1_size = sizeof(struct ipv6hdr),
- .arg2_type = ARG_PTR_TO_FIXED_SIZE_MEM,
+ .arg2_type = ARG_PTR_TO_FIXED_SIZE_MEM | MEM_RDONLY,
.arg2_size = sizeof(struct tcphdr),
};
#endif /* CONFIG_SYN_COOKIES */
--
2.43.0
^ permalink raw reply related
* [PATCH bpf RESEND v2 0/2] bpf: Fix memory access flags in helper prototypes
From: Zesen Liu @ 2026-01-18 8:16 UTC (permalink / raw)
To: Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Martin KaFai Lau, Eduard Zingerman, Song Liu, Yonghong Song,
John Fastabend, KP Singh, Stanislav Fomichev, Hao Luo, Jiri Olsa,
Matt Bobrowski, Steven Rostedt, Masami Hiramatsu,
Mathieu Desnoyers, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Simon Horman, Daniel Xu
Cc: bpf, linux-kernel, linux-trace-kernel, netdev, Shuran Liu,
Peili Gao, Haoran Ni, Zesen Liu
Hi,
This series adds missing memory access flags (MEM_RDONLY or MEM_WRITE) to
several bpf helper function prototypes that use ARG_PTR_TO_MEM but lack the
correct flag. It also adds a new check in verifier to ensure the flag is
specified.
Missing memory access flags in helper prototypes can lead to critical
correctness issues when the verifier tries to perform code optimization.
After commit 37cce22dbd51 ("bpf: verifier: Refactor helper access type
tracking"), the verifier relies on the memory access flags, rather than
treating all arguments in helper functions as potentially modifying the
pointed-to memory.
Using ARG_PTR_TO_MEM alone without flags does not make sense because:
- If the helper does not change the argument, missing MEM_RDONLY causes the
verifier to incorrectly reject a read-only buffer.
- If the helper does change the argument, missing MEM_WRITE causes the
verifier to incorrectly assume the memory is unchanged, leading to
errors in code optimization.
We have already seen several reports regarding this:
- commit ac44dcc788b9 ("bpf: Fix verifier assumptions of bpf_d_path's
output buffer") adds MEM_WRITE to bpf_d_path;
- commit 2eb7648558a7 ("bpf: Specify access type of bpf_sysctl_get_name
args") adds MEM_WRITE to bpf_sysctl_get_name.
This series looks through all prototypes in the kernel and completes the
flags. It also adds a new check (check_func_proto) in
verifier.c to statically restrict ARG_PTR_TO_MEM from appearing without
memory access flags.
Changelog
=========
v2:
- Add missing MEM_RDONLY flags to protos with ARG_PTR_TO_FIXED_SIZE_MEM.
Thanks,
Zesen Liu
---
Zesen Liu (2):
bpf: Fix memory access flags in helper prototypes
bpf: Require ARG_PTR_TO_MEM with memory flag
kernel/bpf/helpers.c | 2 +-
kernel/bpf/syscall.c | 2 +-
kernel/bpf/verifier.c | 17 +++++++++++++++++
kernel/trace/bpf_trace.c | 6 +++---
net/core/filter.c | 20 ++++++++++----------
5 files changed, 32 insertions(+), 15 deletions(-)
---
base-commit: ab86d0bf01f6d0e37fd67761bb62918321b64efc
change-id: 20251220-helper_proto-fb6e64182467
Best regards,
--
Zesen Liu <ftyghome@gmail.com>
^ permalink raw reply
* Re: [PATCH v5 v5 2/3] tracing/fprobe: Support comma-separated symbols and :entry/:exit
From: kernel test robot @ 2026-01-18 6:36 UTC (permalink / raw)
To: Seokwoo Chung (Ryan), rostedt, mhiramat, corbet, shuah
Cc: oe-kbuild-all, mathieu.desnoyers, linux-kernel,
linux-trace-kernel, linux-doc, linux-kselftest,
Seokwoo Chung (Ryan)
In-Reply-To: <20260118011815.56516-3-seokwoo.chung130@gmail.com>
Hi Seokwoo,
kernel test robot noticed the following build errors:
[auto build test ERROR on trace/for-next]
[also build test ERROR on linus/master v6.19-rc5 next-20260116]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]
url: https://github.com/intel-lab-lkp/linux/commits/Seokwoo-Chung-Ryan/docs-tracing-fprobe-Document-list-filters-and-entry-exit/20260118-092055
base: https://git.kernel.org/pub/scm/linux/kernel/git/trace/linux-trace for-next
patch link: https://lore.kernel.org/r/20260118011815.56516-3-seokwoo.chung130%40gmail.com
patch subject: [PATCH v5 v5 2/3] tracing/fprobe: Support comma-separated symbols and :entry/:exit
config: x86_64-randconfig-012-20260118 (https://download.01.org/0day-ci/archive/20260118/202601181443.vv7qeHyx-lkp@intel.com/config)
compiler: gcc-14 (Debian 14.2.0-19) 14.2.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20260118/202601181443.vv7qeHyx-lkp@intel.com/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202601181443.vv7qeHyx-lkp@intel.com/
All errors (new ones prefixed by >>):
kernel/trace/trace_fprobe.c: In function 'parse_fprobe_spec':
kernel/trace/trace_fprobe.c:1282:12: error: invalid storage class for function 'trace_fprobe_create_internal'
1282 | static int trace_fprobe_create_internal(int argc, const char *argv[],
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~
kernel/trace/trace_fprobe.c:1513:12: error: invalid storage class for function 'trace_fprobe_create_cb'
1513 | static int trace_fprobe_create_cb(int argc, const char *argv[])
| ^~~~~~~~~~~~~~~~~~~~~~
kernel/trace/trace_fprobe.c:1530:12: error: invalid storage class for function 'trace_fprobe_create'
1530 | static int trace_fprobe_create(const char *raw_command)
| ^~~~~~~~~~~~~~~~~~~
kernel/trace/trace_fprobe.c:1535:12: error: invalid storage class for function 'trace_fprobe_release'
1535 | static int trace_fprobe_release(struct dyn_event *ev)
| ^~~~~~~~~~~~~~~~~~~~
kernel/trace/trace_fprobe.c:1545:12: error: invalid storage class for function 'trace_fprobe_show'
1545 | static int trace_fprobe_show(struct seq_file *m, struct dyn_event *ev)
| ^~~~~~~~~~~~~~~~~
kernel/trace/trace_fprobe.c:1572:12: error: invalid storage class for function 'enable_trace_fprobe'
1572 | static int enable_trace_fprobe(struct trace_event_call *call,
| ^~~~~~~~~~~~~~~~~~~
kernel/trace/trace_fprobe.c:1608:12: error: invalid storage class for function 'disable_trace_fprobe'
1608 | static int disable_trace_fprobe(struct trace_event_call *call,
| ^~~~~~~~~~~~~~~~~~~~
kernel/trace/trace_fprobe.c:1653:12: error: invalid storage class for function 'fprobe_register'
1653 | static int fprobe_register(struct trace_event_call *event,
| ^~~~~~~~~~~~~~~
kernel/trace/trace_fprobe.c:1683:19: error: invalid storage class for function 'init_fprobe_trace_early'
1683 | static __init int init_fprobe_trace_early(void)
| ^~~~~~~~~~~~~~~~~~~~~~~
In file included from include/linux/fprobe.h:6,
from kernel/trace/trace_fprobe.c:8:
>> include/linux/compiler.h:284:51: error: initializer element is not constant
284 | __UNIQUE_ID(__PASTE(addressable_, sym)) = (void *)(uintptr_t)&sym;
| ^
include/linux/compiler.h:287:9: note: in expansion of macro '___ADDRESSABLE'
287 | ___ADDRESSABLE(sym, __section(".discard.addressable"))
| ^~~~~~~~~~~~~~
include/linux/init.h:251:9: note: in expansion of macro '__ADDRESSABLE'
251 | __ADDRESSABLE(fn)
| ^~~~~~~~~~~~~
include/linux/init.h:256:9: note: in expansion of macro '__define_initcall_stub'
256 | __define_initcall_stub(__stub, fn) \
| ^~~~~~~~~~~~~~~~~~~~~~
include/linux/init.h:269:9: note: in expansion of macro '____define_initcall'
269 | ____define_initcall(fn, \
| ^~~~~~~~~~~~~~~~~~~
include/linux/init.h:275:9: note: in expansion of macro '__unique_initcall'
275 | __unique_initcall(fn, id, __sec, __initcall_id(fn))
| ^~~~~~~~~~~~~~~~~
include/linux/init.h:277:35: note: in expansion of macro '___define_initcall'
277 | #define __define_initcall(fn, id) ___define_initcall(fn, id, .initcall##id)
| ^~~~~~~~~~~~~~~~~~
include/linux/init.h:295:41: note: in expansion of macro '__define_initcall'
295 | #define core_initcall(fn) __define_initcall(fn, 1)
| ^~~~~~~~~~~~~~~~~
kernel/trace/trace_fprobe.c:1702:1: note: in expansion of macro 'core_initcall'
1702 | core_initcall(init_fprobe_trace_early);
| ^~~~~~~~~~~~~
kernel/trace/trace_fprobe.c:1702:1: error: expected declaration or statement at end of input
kernel/trace/trace_fprobe.c: At top level:
kernel/trace/trace_fprobe.c:28:12: warning: 'trace_fprobe_create' used but never defined
28 | static int trace_fprobe_create(const char *raw_command);
| ^~~~~~~~~~~~~~~~~~~
kernel/trace/trace_fprobe.c:29:12: warning: 'trace_fprobe_show' used but never defined
29 | static int trace_fprobe_show(struct seq_file *m, struct dyn_event *ev);
| ^~~~~~~~~~~~~~~~~
kernel/trace/trace_fprobe.c:30:12: warning: 'trace_fprobe_release' used but never defined
30 | static int trace_fprobe_release(struct dyn_event *ev);
| ^~~~~~~~~~~~~~~~~~~~
kernel/trace/trace_fprobe.c:741:12: warning: 'fprobe_register' used but never defined
741 | static int fprobe_register(struct trace_event_call *event,
| ^~~~~~~~~~~~~~~
kernel/trace/trace_fprobe.c:1653:12: warning: 'fprobe_register' defined but not used [-Wunused-function]
1653 | static int fprobe_register(struct trace_event_call *event,
| ^~~~~~~~~~~~~~~
kernel/trace/trace_fprobe.c:1545:12: warning: 'trace_fprobe_show' defined but not used [-Wunused-function]
1545 | static int trace_fprobe_show(struct seq_file *m, struct dyn_event *ev)
| ^~~~~~~~~~~~~~~~~
kernel/trace/trace_fprobe.c:1535:12: warning: 'trace_fprobe_release' defined but not used [-Wunused-function]
1535 | static int trace_fprobe_release(struct dyn_event *ev)
| ^~~~~~~~~~~~~~~~~~~~
kernel/trace/trace_fprobe.c:1530:12: warning: 'trace_fprobe_create' defined but not used [-Wunused-function]
1530 | static int trace_fprobe_create(const char *raw_command)
| ^~~~~~~~~~~~~~~~~~~
vim +284 include/linux/compiler.h
0ef8047b737d74 Juergen Gross 2024-11-29 275
7290d58095712a Ard Biesheuvel 2018-08-21 276 /*
7290d58095712a Ard Biesheuvel 2018-08-21 277 * Force the compiler to emit 'sym' as a symbol, so that we can reference
7290d58095712a Ard Biesheuvel 2018-08-21 278 * it from inline assembler. Necessary in case 'sym' could be inlined
7290d58095712a Ard Biesheuvel 2018-08-21 279 * otherwise, or eliminated entirely due to lack of references that are
7290d58095712a Ard Biesheuvel 2018-08-21 280 * visible to the compiler.
7290d58095712a Ard Biesheuvel 2018-08-21 281 */
92efda8eb15295 Sami Tolvanen 2022-09-08 282 #define ___ADDRESSABLE(sym, __attrs) \
92efda8eb15295 Sami Tolvanen 2022-09-08 283 static void * __used __attrs \
9f14f1f91883aa Josh Poimboeuf 2025-09-17 @284 __UNIQUE_ID(__PASTE(addressable_, sym)) = (void *)(uintptr_t)&sym;
0ef8047b737d74 Juergen Gross 2024-11-29 285
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply
* Re: [PATCH v3 2/2] mm/vmscan: add tracepoint and reason for kswapd_failures reset
From: Shakeel Butt @ 2026-01-18 5:08 UTC (permalink / raw)
To: Jiayuan Chen
Cc: linux-mm, Jiayuan Chen, Andrew Morton, Axel Rasmussen,
Yuanchu Xie, Wei Xu, David Hildenbrand, Lorenzo Stoakes,
Liam R. Howlett, Vlastimil Babka, Mike Rapoport,
Suren Baghdasaryan, Michal Hocko, Steven Rostedt,
Masami Hiramatsu, Mathieu Desnoyers, Brendan Jackman,
Johannes Weiner, Zi Yan, Qi Zheng, linux-kernel,
linux-trace-kernel
In-Reply-To: <20260114074049.229935-3-jiayuan.chen@linux.dev>
On Wed, Jan 14, 2026 at 03:40:36PM +0800, Jiayuan Chen wrote:
> From: Jiayuan Chen <jiayuan.chen@shopee.com>
>
> Currently, kswapd_failures is reset in multiple places (kswapd,
> direct reclaim, PCP freeing, memory-tiers), but there's no way to
> trace when and why it was reset, making it difficult to debug
> memory reclaim issues.
>
> This patch:
>
> 1. Introduce pgdat_reset_kswapd_failures() as a wrapper function to
> centralize kswapd_failures reset logic.
>
> 2. Add reset_kswapd_failures_reason enum to distinguish reset sources:
> - RESET_KSWAPD_FAILURES_KSWAPD: reset from kswapd context
> - RESET_KSWAPD_FAILURES_DIRECT: reset from direct reclaim
> - RESET_KSWAPD_FAILURES_PCP: reset from PCP page freeing
> - RESET_KSWAPD_FAILURES_OTHER: reset from other paths
>
> 3. Add tracepoints for better observability:
> - mm_vmscan_reset_kswapd_failures: traces each reset with reason
> - mm_vmscan_kswapd_reclaim_fail: traces each kswapd reclaim failure
>
> ---
> Test results:
>
> $ trace-cmd record -e vmscan:mm_vmscan_reset_kswapd_failures -e vmscan:mm_vmscan_kswapd_reclaim_fail
> $ # generate memory pressure
> $ trace-cmd report
> cpus=4
> kswapd1-73 [002] 24.863112: mm_vmscan_kswapd_reclaim_fail: nid=1 failures=1
> kswapd1-73 [002] 24.863472: mm_vmscan_kswapd_reclaim_fail: nid=1 failures=2
> kswapd1-73 [002] 24.863813: mm_vmscan_kswapd_reclaim_fail: nid=1 failures=3
> kswapd1-73 [002] 24.864141: mm_vmscan_kswapd_reclaim_fail: nid=1 failures=4
> kswapd1-73 [002] 24.864462: mm_vmscan_kswapd_reclaim_fail: nid=1 failures=5
> kswapd1-73 [002] 24.864779: mm_vmscan_kswapd_reclaim_fail: nid=1 failures=6
> kswapd1-73 [002] 24.865103: mm_vmscan_kswapd_reclaim_fail: nid=1 failures=7
> kswapd1-73 [002] 24.865421: mm_vmscan_kswapd_reclaim_fail: nid=1 failures=8
> kswapd1-73 [002] 24.865737: mm_vmscan_kswapd_reclaim_fail: nid=1 failures=9
> kswapd1-73 [002] 24.866070: mm_vmscan_kswapd_reclaim_fail: nid=1 failures=10
> kswapd1-73 [002] 24.866385: mm_vmscan_kswapd_reclaim_fail: nid=1 failures=11
> kswapd1-73 [002] 24.866701: mm_vmscan_kswapd_reclaim_fail: nid=1 failures=12
> kswapd1-73 [002] 24.867016: mm_vmscan_kswapd_reclaim_fail: nid=1 failures=13
> kswapd1-73 [002] 24.867333: mm_vmscan_kswapd_reclaim_fail: nid=1 failures=14
> kswapd1-73 [002] 24.867649: mm_vmscan_kswapd_reclaim_fail: nid=1 failures=15
> kswapd1-73 [002] 24.867965: mm_vmscan_kswapd_reclaim_fail: nid=1 failures=16
> kswapd0-72 [001] 25.020464: mm_vmscan_kswapd_reclaim_fail: nid=0 failures=1
> kswapd0-72 [001] 25.021054: mm_vmscan_kswapd_reclaim_fail: nid=0 failures=2
> kswapd0-72 [001] 25.021628: mm_vmscan_kswapd_reclaim_fail: nid=0 failures=3
> kswapd0-72 [001] 25.022217: mm_vmscan_kswapd_reclaim_fail: nid=0 failures=4
> kswapd0-72 [001] 25.022790: mm_vmscan_kswapd_reclaim_fail: nid=0 failures=5
> kswapd0-72 [001] 25.023366: mm_vmscan_kswapd_reclaim_fail: nid=0 failures=6
> kswapd0-72 [001] 25.023937: mm_vmscan_kswapd_reclaim_fail: nid=0 failures=7
> kswapd0-72 [001] 25.024511: mm_vmscan_kswapd_reclaim_fail: nid=0 failures=8
> kswapd0-72 [001] 25.025092: mm_vmscan_kswapd_reclaim_fail: nid=0 failures=9
> kswapd0-72 [001] 25.025665: mm_vmscan_kswapd_reclaim_fail: nid=0 failures=10
> kswapd0-72 [001] 25.026249: mm_vmscan_kswapd_reclaim_fail: nid=0 failures=11
> kswapd0-72 [001] 25.026824: mm_vmscan_kswapd_reclaim_fail: nid=0 failures=12
> kswapd0-72 [001] 25.027398: mm_vmscan_kswapd_reclaim_fail: nid=0 failures=13
> kswapd0-72 [001] 25.027976: mm_vmscan_kswapd_reclaim_fail: nid=0 failures=14
> kswapd0-72 [001] 25.028554: mm_vmscan_kswapd_reclaim_fail: nid=0 failures=15
> kswapd0-72 [001] 25.029140: mm_vmscan_kswapd_reclaim_fail: nid=0 failures=16
> ann-416 [002] 25.577925: mm_vmscan_reset_kswapd_failures: nid=0 reason=PCP
> dd-417 [002] 35.111721: mm_vmscan_reset_kswapd_failures: nid=1 reason=DIRECT
>
> Signed-off-by: Jiayuan Chen <jiayuan.chen@shopee.com>
> Signed-off-by: Jiayuan Chen <jiayuan.chen@linux.dev>
Thanks for adding this.
Acked-by: Shakeel Butt <shakeel.butt@linux.dev>
^ permalink raw reply
* Re: [PATCH v3 1/2] mm/vmscan: mitigate spurious kswapd_failures reset from direct reclaim
From: Shakeel Butt @ 2026-01-18 5:04 UTC (permalink / raw)
To: Jiayuan Chen
Cc: linux-mm, Jiayuan Chen, Andrew Morton, David Hildenbrand,
Lorenzo Stoakes, Liam R. Howlett, Vlastimil Babka, Mike Rapoport,
Suren Baghdasaryan, Michal Hocko, Axel Rasmussen, Yuanchu Xie,
Wei Xu, Steven Rostedt, Masami Hiramatsu, Mathieu Desnoyers,
Brendan Jackman, Johannes Weiner, Zi Yan, Qi Zheng, linux-kernel,
linux-trace-kernel
In-Reply-To: <20260114074049.229935-2-jiayuan.chen@linux.dev>
On Wed, Jan 14, 2026 at 03:40:35PM +0800, Jiayuan Chen wrote:
> From: Jiayuan Chen <jiayuan.chen@shopee.com>
>
> When kswapd fails to reclaim memory, kswapd_failures is incremented.
> Once it reaches MAX_RECLAIM_RETRIES, kswapd stops running to avoid
> futile reclaim attempts. However, any successful direct reclaim
> unconditionally resets kswapd_failures to 0, which can cause problems.
>
> We observed an issue in production on a multi-NUMA system where a
> process allocated large amounts of anonymous pages on a single NUMA
> node, causing its watermark to drop below high and evicting most file
> pages:
>
> $ numastat -m
> Per-node system memory usage (in MBs):
> Node 0 Node 1 Total
> --------------- --------------- ---------------
> MemTotal 128222.19 127983.91 256206.11
> MemFree 1414.48 1432.80 2847.29
> MemUsed 126807.71 126551.11 252358.82
> SwapCached 0.00 0.00 0.00
> Active 29017.91 25554.57 54572.48
> Inactive 92749.06 95377.00 188126.06
> Active(anon) 28998.96 23356.47 52355.43
> Inactive(anon) 92685.27 87466.11 180151.39
> Active(file) 18.95 2198.10 2217.05
> Inactive(file) 63.79 7910.89 7974.68
>
> With swap disabled, only file pages can be reclaimed. When kswapd is
> woken (e.g., via wake_all_kswapds()), it runs continuously but cannot
> raise free memory above the high watermark since reclaimable file pages
> are insufficient. Normally, kswapd would eventually stop after
> kswapd_failures reaches MAX_RECLAIM_RETRIES.
>
> However, containers on this machine have memory.high set in their
> cgroup. Business processes continuously trigger the high limit, causing
> frequent direct reclaim that keeps resetting kswapd_failures to 0. This
> prevents kswapd from ever stopping.
>
> The key insight is that direct reclaim triggered by cgroup memory.high
> performs aggressive scanning to throttle the allocating process. With
> sufficiently aggressive scanning, even hot pages will eventually be
> reclaimed, making direct reclaim "successful" at freeing some memory.
> However, this success does not mean the node has reached a balanced
> state - the freed memory may still be insufficient to bring free pages
> above the high watermark. Unconditionally resetting kswapd_failures in
> this case keeps kswapd alive indefinitely.
>
> The result is that kswapd runs endlessly. Unlike direct reclaim which
> only reclaims from the allocating cgroup, kswapd scans the entire node's
> memory. This causes hot file pages from all workloads on the node to be
> evicted, not just those from the cgroup triggering memory.high. These
> pages constantly refault, generating sustained heavy IO READ pressure
> across the entire system.
>
> Fix this by only resetting kswapd_failures when the node is actually
> balanced. This allows both kswapd and direct reclaim to clear
> kswapd_failures upon successful reclaim, but only when the reclaim
> actually resolves the memory pressure (i.e., the node becomes balanced).
>
> Signed-off-by: Jiayuan Chen <jiayuan.chen@shopee.com>
> Signed-off-by: Jiayuan Chen <jiayuan.chen@linux.dev>
After incorporating suggestions from Johannes, you can add:
Acked-by: Shakeel Butt <shakeel.butt@linux.dev>
^ permalink raw reply
* [PATCH v5 v5 3/3] selftests/ftrace: Add accept cases for fprobe list syntax
From: Seokwoo Chung (Ryan) @ 2026-01-18 1:18 UTC (permalink / raw)
To: rostedt, mhiramat, corbet, shuah
Cc: mathieu.desnoyers, linux-kernel, linux-trace-kernel, linux-doc,
linux-kselftest, Seokwoo Chung (Ryan)
In-Reply-To: <20260118011815.56516-1-seokwoo.chung130@gmail.com>
Add selftest for comma-separated symbol lists, exclusion (!), and explicit
:entry/:exit suffixes. Verify that excluded symbols are not attached and
that enabled_functions shows the correct probes.
Signed-off-by: Seokwoo Chung (Ryan) <seokwoo.chung130@gmail.com>
---
.../ftrace/test.d/dynevent/fprobe_list.tc | 92 +++++++++++++++++++
1 file changed, 92 insertions(+)
create mode 100644 tools/testing/selftests/ftrace/test.d/dynevent/fprobe_list.tc
diff --git a/tools/testing/selftests/ftrace/test.d/dynevent/fprobe_list.tc b/tools/testing/selftests/ftrace/test.d/dynevent/fprobe_list.tc
new file mode 100644
index 000000000000..45e57c6f487d
--- /dev/null
+++ b/tools/testing/selftests/ftrace/test.d/dynevent/fprobe_list.tc
@@ -0,0 +1,92 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0
+# description: Fprobe event list syntax and :entry/:exit suffixes
+# requires: dynamic_events "f[:[<group>/][<event>]] <func-name>[:entry|:exit] [<args>]":README
+
+# Setup symbols to test. These are common kernel functions.
+PLACE=vfs_read
+PLACE2=vfs_write
+PLACE3=vfs_open
+
+echo 0 > events/enable
+echo > dynamic_events
+
+# Get baseline count of enabled functions (should be 0 if clean, but be safe)
+if [ -f enabled_functions ]; then
+ ocnt=`cat enabled_functions | wc -l`
+else
+ ocnt=0
+fi
+
+# Test 1: List default (entry) with exclusion
+# Target: Trace vfs_read and vfs_open, but EXCLUDE vfs_write
+echo "f:test/list_entry $PLACE,!$PLACE2,$PLACE3" >> dynamic_events
+grep -q "test/list_entry" dynamic_events
+test -d events/test/list_entry
+
+echo 1 > events/test/list_entry/enable
+
+grep -q "$PLACE" enabled_functions
+grep -q "$PLACE3" enabled_functions
+! grep -q "$PLACE2" enabled_functions
+
+# Check count (Baseline + 2 new functions)
+cnt=`cat enabled_functions | wc -l`
+if [ $cnt -ne $((ocnt + 2)) ]; then
+ exit_fail
+fi
+
+# Cleanup Test 1
+echo 0 > events/test/list_entry/enable
+echo "-:test/list_entry" >> dynamic_events
+! grep -q "test/list_entry" dynamic_events
+
+# Count should return to baseline
+cnt=`cat enabled_functions | wc -l`
+if [ $cnt -ne $ocnt ]; then
+ exit_fail
+fi
+
+# Test 2: List with explicit :entry suffix
+# (Should behave exactly like Test 1)
+echo "f:test/list_entry_exp $PLACE,!$PLACE2,$PLACE3:entry" >> dynamic_events
+grep -q "test/list_entry_exp" dynamic_events
+test -d events/test/list_entry_exp
+
+echo 1 > events/test/list_entry_exp/enable
+
+grep -q "$PLACE" enabled_functions
+grep -q "$PLACE3" enabled_functions
+! grep -q "$PLACE2" enabled_functions
+
+cnt=`cat enabled_functions | wc -l`
+if [ $cnt -ne $((ocnt + 2)) ]; then
+ exit_fail
+fi
+
+# Cleanup Test 2
+echo 0 > events/test/list_entry_exp/enable
+echo "-:test/list_entry_exp" >> dynamic_events
+
+# Test 3: List with :exit suffix
+echo "f:test/list_exit $PLACE,!$PLACE2,$PLACE3:exit" >> dynamic_events
+grep -q "test/list_exit" dynamic_events
+test -d events/test/list_exit
+
+echo 1 > events/test/list_exit/enable
+
+# Even for return probes, enabled_functions lists the attached symbols
+grep -q "$PLACE" enabled_functions
+grep -q "$PLACE3" enabled_functions
+! grep -q "$PLACE2" enabled_functions
+
+cnt=`cat enabled_functions | wc -l`
+if [ $cnt -ne $((ocnt + 2)) ]; then
+ exit_fail
+fi
+
+# Cleanup Test 3
+echo 0 > events/test/list_exit/enable
+echo "-:test/list_exit" >> dynamic_events
+
+clear_trace
--
2.43.0
^ permalink raw reply related
* [PATCH v5 v5 2/3] tracing/fprobe: Support comma-separated symbols and :entry/:exit
From: Seokwoo Chung (Ryan) @ 2026-01-18 1:18 UTC (permalink / raw)
To: rostedt, mhiramat, corbet, shuah
Cc: mathieu.desnoyers, linux-kernel, linux-trace-kernel, linux-doc,
linux-kselftest, Seokwoo Chung (Ryan)
In-Reply-To: <20260118011815.56516-1-seokwoo.chung130@gmail.com>
- Update DEFINE_FREE to use standard __free()
- Extend fprobe to support multiple symbols per event. Add parsing logic for
lists, ! exclusions, and explicit suffixes. Update tracefs/README to reflect
the new syntax
Signed-off-by: Seokwoo Chung (Ryan) <seokwoo.chung130@gmail.com>
---
kernel/trace/trace.c | 3 +-
kernel/trace/trace_fprobe.c | 209 +++++++++++++++++++++++++++---------
2 files changed, 163 insertions(+), 49 deletions(-)
diff --git a/kernel/trace/trace.c b/kernel/trace/trace.c
index baec63134ab6..10cdcc7b194e 100644
--- a/kernel/trace/trace.c
+++ b/kernel/trace/trace.c
@@ -5578,7 +5578,8 @@ static const char readme_msg[] =
"\t r[maxactive][:[<group>/][<event>]] <place> [<args>]\n"
#endif
#ifdef CONFIG_FPROBE_EVENTS
- "\t f[:[<group>/][<event>]] <func-name>[%return] [<args>]\n"
+ "\t f[:[<group>/][<event>]] <func-name>[:entry|:exit] [<args>]\n"
+ "\t (single symbols still accept %return)\n"
"\t t[:[<group>/][<event>]] <tracepoint> [<args>]\n"
#endif
#ifdef CONFIG_HIST_TRIGGERS
diff --git a/kernel/trace/trace_fprobe.c b/kernel/trace/trace_fprobe.c
index 262c0556e4af..5a2a41eea603 100644
--- a/kernel/trace/trace_fprobe.c
+++ b/kernel/trace/trace_fprobe.c
@@ -187,11 +187,14 @@ DEFINE_FREE(tuser_put, struct tracepoint_user *,
*/
struct trace_fprobe {
struct dyn_event devent;
+ char *filter;
struct fprobe fp;
+ bool list_mode;
+ char *nofilter;
const char *symbol;
+ struct trace_probe tp;
bool tprobe;
struct tracepoint_user *tuser;
- struct trace_probe tp;
};
static bool is_trace_fprobe(struct dyn_event *ev)
@@ -559,6 +562,8 @@ static void free_trace_fprobe(struct trace_fprobe *tf)
trace_probe_cleanup(&tf->tp);
if (tf->tuser)
tracepoint_user_put(tf->tuser);
+ kfree(tf->filter);
+ kfree(tf->nofilter);
kfree(tf->symbol);
kfree(tf);
}
@@ -838,7 +843,12 @@ static int __register_trace_fprobe(struct trace_fprobe *tf)
if (trace_fprobe_is_tracepoint(tf))
return __regsiter_tracepoint_fprobe(tf);
- /* TODO: handle filter, nofilter or symbol list */
+ /* Registration path:
+ * - list_mode: pass filter/nofilter
+ * - single: pass symbol only (legacy)
+ */
+ if (tf->list_mode)
+ return register_fprobe(&tf->fp, tf->filter, tf->nofilter);
return register_fprobe(&tf->fp, tf->symbol, NULL);
}
@@ -1154,60 +1164,119 @@ static struct notifier_block tprobe_event_module_nb = {
};
#endif /* CONFIG_MODULES */
-static int parse_symbol_and_return(int argc, const char *argv[],
- char **symbol, bool *is_return,
- bool is_tracepoint)
+static bool has_wildcard(const char *s)
{
- char *tmp = strchr(argv[1], '%');
- int i;
+ return s && (strchr(s, '*') || strchr(s, '?'));
+}
- if (tmp) {
- int len = tmp - argv[1];
+static int parse_fprobe_spec(const char *in, bool is_tracepoint,
+ char **base, bool *is_return, bool *list_mode,
+ char **filter, char **nofilter)
+{
+ char *work __free(kfree) = NULL;
+ char *b __free(kfree) = NULL;
+ char *f __free(kfree) = NULL;
+ char *nf __free(kfree) = NULL;
+ bool legacy_ret = false;
+ bool list = false;
+ const char *p;
+ int ret = 0;
- if (!is_tracepoint && !strcmp(tmp, "%return")) {
- *is_return = true;
- } else {
- trace_probe_log_err(len, BAD_ADDR_SUFFIX);
- return -EINVAL;
- }
- *symbol = kmemdup_nul(argv[1], len, GFP_KERNEL);
- } else
- *symbol = kstrdup(argv[1], GFP_KERNEL);
- if (!*symbol)
- return -ENOMEM;
+ if (!in || !base || !is_return || !list_mode || !filter || !nofilter)
+ return -EINVAL;
- if (*is_return)
- return 0;
+ *base = NULL; *filter = NULL; *nofilter = NULL;
+ *is_return = false; *list_mode = false;
if (is_tracepoint) {
- tmp = *symbol;
- while (*tmp && (isalnum(*tmp) || *tmp == '_'))
- tmp++;
- if (*tmp) {
- /* find a wrong character. */
- trace_probe_log_err(tmp - *symbol, BAD_TP_NAME);
- kfree(*symbol);
- *symbol = NULL;
+ if (strchr(in, ',') || strchr(in, ':'))
return -EINVAL;
- }
+ if (strstr(in, "%return"))
+ return -EINVAL;
+ for (p = in; *p; p++)
+ if (!isalnum(*p) && *p != '_')
+ return -EINVAL;
+ b = kstrdup(in, GFP_KERNEL);
+ if (!b)
+ return -ENOMEM;
+ *base = no_free_ptr(b);
+ return 0;
}
- /* If there is $retval, this should be a return fprobe. */
- for (i = 2; i < argc; i++) {
- tmp = strstr(argv[i], "$retval");
- if (tmp && !isalnum(tmp[7]) && tmp[7] != '_') {
- if (is_tracepoint) {
- trace_probe_log_set_index(i);
- trace_probe_log_err(tmp - argv[i], RETVAL_ON_PROBE);
- kfree(*symbol);
- *symbol = NULL;
+ work = kstrdup(in, GFP_KERNEL);
+ if (!work)
+ return -ENOMEM;
+
+ p = strstr(work, "%return");
+ if (p && p[7] == '\0') {
+ *is_return = true;
+ legacy_ret = true;
+ *(char *)p = '\0';
+ } else {
+ /*
+ * If "symbol:entry" or "symbol:exit" is given, it is new
+ * style probe.
+ */
+ p = strrchr(work, ':');
+ if (p) {
+ if (!strcmp(p, ":exit")) {
+ *is_return = true;
+ *(char *)p = '\0';
+ } else if (!strcmp(p, ":entry")) {
+ *(char *)p = '\0';
+ } else {
return -EINVAL;
}
- *is_return = true;
- break;
}
}
- return 0;
+
+ list = !!strchr(work, ',');
+
+ if (list && legacy_ret) {
+ return -EINVAL;
+ }
+
+ if (legacy_ret)
+ *is_return = true;
+
+ b = kstrdup(work, GFP_KERNEL);
+ if (!b)
+ return -ENOMEM;
+
+ if (list) {
+ char *tmp = b, *tok;
+ size_t fsz, nfsz;
+
+ fsz = nfsz = strlen(b) + 1;
+
+ f = kzalloc(fsz, GFP_KERNEL);
+ nf = kzalloc(nfsz, GFP_KERNEL);
+ if (!f || !nf)
+ return -ENOMEM;
+
+ while ((tok = strsep(&tmp, ",")) != NULL) {
+ char *dst;
+ bool neg = (*tok == '!');
+
+ if (*tok == '\0') {
+ trace_probe_log_err(tmp - b - 1, BAD_TP_NAME);
+ return -EINVAL;
+
+ if (neg)
+ tok++;
+ dst = neg ? nf : f;
+ if (dst[0] != '\0')
+ strcat(dst, ",");
+ strcat(dst, tok);
+ }
+ *list_mode = true;
+ }
+
+ *base = no_free_ptr(b);
+ *filter = no_free_ptr(f);
+ *nofilter = no_free_ptr(nf);
+
+ return ret;
}
static int trace_fprobe_create_internal(int argc, const char *argv[],
@@ -1241,6 +1310,8 @@ static int trace_fprobe_create_internal(int argc, const char *argv[],
const char *event = NULL, *group = FPROBE_EVENT_SYSTEM;
struct module *mod __free(module_put) = NULL;
const char **new_argv __free(kfree) = NULL;
+ char *parsed_nofilter __free(kfree) = NULL;
+ char *parsed_filter __free(kfree) = NULL;
char *symbol __free(kfree) = NULL;
char *ebuf __free(kfree) = NULL;
char *gbuf __free(kfree) = NULL;
@@ -1249,6 +1320,7 @@ static int trace_fprobe_create_internal(int argc, const char *argv[],
char *dbuf __free(kfree) = NULL;
int i, new_argc = 0, ret = 0;
bool is_tracepoint = false;
+ bool list_mode = false;
bool is_return = false;
if ((argv[0][0] != 'f' && argv[0][0] != 't') || argc < 2)
@@ -1270,11 +1342,26 @@ static int trace_fprobe_create_internal(int argc, const char *argv[],
trace_probe_log_set_index(1);
- /* a symbol(or tracepoint) must be specified */
- ret = parse_symbol_and_return(argc, argv, &symbol, &is_return, is_tracepoint);
+ /* Parse spec early (single vs list, suffix, base symbol) */
+ ret = parse_fprobe_spec(argv[1], is_tracepoint, &symbol, &is_return,
+ &list_mode, &parsed_filter, &parsed_nofilter);
if (ret < 0)
return -EINVAL;
+ for (i = 2; i < argc; i++) {
+ char *tmp = strstr(argv[i], "$retval");
+
+ if (tmp && !isalnum(tmp[7]) && tmp[7] != '_') {
+ if (is_tracepoint) {
+ trace_probe_log_set_index(i);
+ trace_probe_log_err(tmp - argv[i], RETVAL_ON_PROBE);
+ return -EINVAL;
+ }
+ is_return = true;
+ break;
+ }
+ }
+
trace_probe_log_set_index(0);
if (event) {
gbuf = kmalloc(MAX_EVENT_NAME_LEN, GFP_KERNEL);
@@ -1287,6 +1374,15 @@ static int trace_fprobe_create_internal(int argc, const char *argv[],
}
if (!event) {
+ /*
+ * Event name rules:
+ * - For list/wildcard: require explicit [GROUP/]EVENT
+ * - For single literal: autogenerate symbol__entry/symbol__exit
+ */
+ if (list_mode || has_wildcard(symbol)) {
+ trace_probe_log_err(0, NO_GROUP_NAME);
+ return -EINVAL;
+ }
ebuf = kmalloc(MAX_EVENT_NAME_LEN, GFP_KERNEL);
if (!ebuf)
return -ENOMEM;
@@ -1322,7 +1418,8 @@ static int trace_fprobe_create_internal(int argc, const char *argv[],
NULL, NULL, NULL, sbuf);
}
}
- if (!ctx->funcname)
+
+ if (!list_mode && !has_wildcard(symbol) && !is_tracepoint)
ctx->funcname = symbol;
abuf = kmalloc(MAX_BTF_ARGS_LEN, GFP_KERNEL);
@@ -1356,6 +1453,21 @@ static int trace_fprobe_create_internal(int argc, const char *argv[],
return ret;
}
+ /* carry list parsing result into tf */
+ if (!is_tracepoint) {
+ tf->list_mode = list_mode;
+ if (parsed_filter) {
+ tf->filter = kstrdup(parsed_filter, GFP_KERNEL);
+ if (!tf->filter)
+ return -ENOMEM;
+ }
+ if (parsed_nofilter) {
+ tf->nofilter = kstrdup(parsed_nofilter, GFP_KERNEL);
+ if (!tf->nofilter)
+ return -ENOMEM;
+ }
+ }
+
/* parse arguments */
for (i = 0; i < argc; i++) {
trace_probe_log_set_index(i + 2);
@@ -1442,8 +1554,9 @@ static int trace_fprobe_show(struct seq_file *m, struct dyn_event *ev)
seq_printf(m, ":%s/%s", trace_probe_group_name(&tf->tp),
trace_probe_name(&tf->tp));
- seq_printf(m, " %s%s", trace_fprobe_symbol(tf),
- trace_fprobe_is_return(tf) ? "%return" : "");
+ seq_printf(m, " %s", trace_fprobe_symbol(tf));
+ if (!trace_fprobe_is_tracepoint(tf) && trace_fprobe_is_return(tf))
+ seq_puts(m, ":exit");
for (i = 0; i < tf->tp.nr_args; i++)
seq_printf(m, " %s=%s", tf->tp.args[i].name, tf->tp.args[i].comm);
--
2.43.0
^ permalink raw reply related
* [PATCH v5 v5 1/3] docs: tracing/fprobe: Document list filters and :entry/:exit
From: Seokwoo Chung (Ryan) @ 2026-01-18 1:18 UTC (permalink / raw)
To: rostedt, mhiramat, corbet, shuah
Cc: mathieu.desnoyers, linux-kernel, linux-trace-kernel, linux-doc,
linux-kselftest, Seokwoo Chung (Ryan)
In-Reply-To: <20260118011815.56516-1-seokwoo.chung130@gmail.com>
Update fprobe event documentation to describe comma-separated symbol lists,
exclusions, and explicit suffixes.
Signed-off-by: Seokwoo Chung (Ryan) <seokwoo.chung130@gmail.com>
---
Documentation/trace/fprobetrace.rst | 17 ++++++++++++++---
1 file changed, 14 insertions(+), 3 deletions(-)
diff --git a/Documentation/trace/fprobetrace.rst b/Documentation/trace/fprobetrace.rst
index b4c2ca3d02c1..bbcfd57f0005 100644
--- a/Documentation/trace/fprobetrace.rst
+++ b/Documentation/trace/fprobetrace.rst
@@ -25,14 +25,18 @@ Synopsis of fprobe-events
-------------------------
::
- f[:[GRP1/][EVENT1]] SYM [FETCHARGS] : Probe on function entry
- f[MAXACTIVE][:[GRP1/][EVENT1]] SYM%return [FETCHARGS] : Probe on function exit
+ f[:[GRP1/][EVENT1]] SYM[%return] [FETCHARGS] : Single function
+ f[:[GRP1/][EVENT1]] SYM[,[!]SYM[,...]][:entry|:exit] [FETCHARGS] :Multiple
+ function
t[:[GRP2/][EVENT2]] TRACEPOINT [FETCHARGS] : Probe on tracepoint
GRP1 : Group name for fprobe. If omitted, use "fprobes" for it.
GRP2 : Group name for tprobe. If omitted, use "tracepoints" for it.
EVENT1 : Event name for fprobe. If omitted, the event name is
- "SYM__entry" or "SYM__exit".
+ - For a single literal symbol, the event name is
+ "SYM__entry" or "SYM__exit".
+ - For a *list or any wildcard*, an explicit [GRP1/][EVENT1] is
+ required; otherwise the parser rejects it.
EVENT2 : Event name for tprobe. If omitted, the event name is
the same as "TRACEPOINT", but if the "TRACEPOINT" starts
with a digit character, "_TRACEPOINT" is used.
@@ -40,6 +44,13 @@ Synopsis of fprobe-events
can be probed simultaneously, or 0 for the default value
as defined in Documentation/trace/fprobe.rst
+ SYM : Function name or comma-separated list of symbols.
+ - SYM prefixed with "!" are exclusions.
+ - ":entry" suffix means it probes entry of given symbols
+ (default)
+ - ":exit" suffix means it probes exit of given symbols.
+ - "%return" suffix means it probes exit of SYM (single
+ symbol).
FETCHARGS : Arguments. Each probe can have up to 128 args.
ARG : Fetch "ARG" function argument using BTF (only for function
entry or tracepoint.) (\*1)
--
2.43.0
^ permalink raw reply related
* [PATCH v5 v5 0/3] tracing/fprobe: Support comma-separated symbols and :entry/:exit
From: Seokwoo Chung (Ryan) @ 2026-01-18 1:18 UTC (permalink / raw)
To: rostedt, mhiramat, corbet, shuah
Cc: mathieu.desnoyers, linux-kernel, linux-trace-kernel, linux-doc,
linux-kselftest, Seokwoo Chung (Ryan)
This series extends fprobe dynamic events to accept a comma-separated list of
symbols and explicit/suffixes.
Currently, fprobe only supports a single symbol (or wildcard) per event. This
series allow users to specify a comma-separated list of symbols, including
exclusions, and to select entry/exit explicitly using /
Examples:
- f:[GRP/][EVENT] func1,func2,func3
- f:[GRP/][EVENT] func1,!func2,func3 (exclude with '!')
Logic changes:
- Refactor parsing logic into parse_fprobe_spec()
- Support comma-separated lists and '!' exclusions
- Add / suffixes for explicit entry/exit selection
- Preserve legacy single-symbol behavior (single symbols still accept %return)
- Disable BTF-based signature lookup when list/wildcard is used, since one
function signature cannot apply to multiple functions
- Reject mixed legacy/new syntax where applicable (e.g. list + %return)
- Update tracefs/README and fprobe documentation
- Add ftrace selftests covering accepted list syntax cases
*Patch order is adjusted: code first, then docs, then selftest
Changes in v5:
- Reordered patches (code->docs->selftests) as suggested
- Addressed review feedback on README help text to show both legacy and list
syntaxes
- Added missing traceprobe error IDs used by the new validation and fixed
parsing/bracing issues found by automated builds
- Removed the dedicated list_mode field and infer list behavior from presence of
filter/nofilter and keep struct trace_probe as the last member
- Link to v4: https://lore.kernel.org/linux-trace-kernel/20251127151218.4763b25c751bb2aac4b1ee36@kernel.org/
I am not fully confident the runtime testing coverage that I did is sufficient
across configs/architectures, so additional verification would be appreciated.
Best regards,
Ryan Chung
Seokwoo Chung (Ryan) (3):
docs: tracing/fprobe: Document list filters and :entry/:exit
tracing/fprobe: Support comma-separated symbols and :entry/:exit
selftests/ftrace: Add accept cases for fprobe list syntax
Documentation/trace/fprobetrace.rst | 17 +-
kernel/trace/trace.c | 3 +-
kernel/trace/trace_fprobe.c | 209 ++++++++++++++----
.../ftrace/test.d/dynevent/fprobe_list.tc | 92 ++++++++
4 files changed, 269 insertions(+), 52 deletions(-)
create mode 100644 tools/testing/selftests/ftrace/test.d/dynevent/fprobe_list.tc
--
2.43.0
^ permalink raw reply
* Re: [PATCH bpf-next v2 1/2] bpf: support bpf_get_func_arg() for BPF_TRACE_RAW_TP
From: Menglong Dong @ 2026-01-17 4:43 UTC (permalink / raw)
To: Menglong Dong, Andrii Nakryiko
Cc: ast, daniel, john.fastabend, andrii, martin.lau, eddyz87, song,
yonghong.song, kpsingh, sdf, haoluo, jolsa, mattbobrowski,
rostedt, mhiramat, mathieu.desnoyers, bpf, linux-kernel,
linux-trace-kernel
In-Reply-To: <CAEf4BzazwvaLVy+4SByCt0cvkOm6eNSmDmGBfUM8u9scFseGCw@mail.gmail.com>
On 2026/1/17 07:32, Andrii Nakryiko wrote:
> On Thu, Jan 15, 2026 at 11:18 PM Menglong Dong <menglong8.dong@gmail.com> wrote:
> >
> > For now, bpf_get_func_arg() and bpf_get_func_arg_cnt() is not supported by
> > the BPF_TRACE_RAW_TP, which is not convenient to get the argument of the
> > tracepoint, especially for the case that the position of the arguments in
> > a tracepoint can change.
> >
> > The target tracepoint BTF type id is specified during loading time,
> > therefore we can get the function argument count from the function
> > prototype instead of the stack.
> >
> > Signed-off-by: Menglong Dong <dongml2@chinatelecom.cn>
> > ---
> > v2:
> > - for nr_args, skip first 'void *__data' argument in btf_trace_##name
> > typedef
> > ---
> > kernel/bpf/verifier.c | 36 ++++++++++++++++++++++++++++++++----
> > kernel/trace/bpf_trace.c | 4 ++--
> > 2 files changed, 34 insertions(+), 6 deletions(-)
> >
> > diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
> > index faa1ecc1fe9d..422d35c100ff 100644
> > --- a/kernel/bpf/verifier.c
> > +++ b/kernel/bpf/verifier.c
> > @@ -23316,8 +23316,22 @@ static int do_misc_fixups(struct bpf_verifier_env *env)
> > /* Implement bpf_get_func_arg inline. */
> > if (prog_type == BPF_PROG_TYPE_TRACING &&
> > insn->imm == BPF_FUNC_get_func_arg) {
> > - /* Load nr_args from ctx - 8 */
> > - insn_buf[0] = BPF_LDX_MEM(BPF_DW, BPF_REG_0, BPF_REG_1, -8);
> > + if (eatype == BPF_TRACE_RAW_TP) {
> > + int nr_args;
> > +
> > + if (!prog->aux->attach_func_proto)
> > + return -EINVAL;
>
> can this happen? can we have tp_btf program without attach_func_proto
> properly set?
I saw it can be NULL in some case, such as bpf2bpf. Maybe it can't
happen for tp_btf, and I'll do further analysis on this point.
Thanks!
Menglong Dong
>
> > + /*
> > + * skip first 'void *__data' argument in btf_trace_##name
> > + * typedef
> > + */
> > + nr_args = btf_type_vlen(prog->aux->attach_func_proto) - 1;
> > + /* Save nr_args to reg0 */
> > + insn_buf[0] = BPF_MOV64_IMM(BPF_REG_0, nr_args);
> > + } else {
> > + /* Load nr_args from ctx - 8 */
> > + insn_buf[0] = BPF_LDX_MEM(BPF_DW, BPF_REG_0, BPF_REG_1, -8);
> > + }
> > insn_buf[1] = BPF_JMP32_REG(BPF_JGE, BPF_REG_2, BPF_REG_0, 6);
> > insn_buf[2] = BPF_ALU64_IMM(BPF_LSH, BPF_REG_2, 3);
> > insn_buf[3] = BPF_ALU64_REG(BPF_ADD, BPF_REG_2, BPF_REG_1);
> > @@ -23369,8 +23383,22 @@ static int do_misc_fixups(struct bpf_verifier_env *env)
> > /* Implement get_func_arg_cnt inline. */
> > if (prog_type == BPF_PROG_TYPE_TRACING &&
> > insn->imm == BPF_FUNC_get_func_arg_cnt) {
> > - /* Load nr_args from ctx - 8 */
> > - insn_buf[0] = BPF_LDX_MEM(BPF_DW, BPF_REG_0, BPF_REG_1, -8);
> > + if (eatype == BPF_TRACE_RAW_TP) {
> > + int nr_args;
> > +
> > + if (!prog->aux->attach_func_proto)
> > + return -EINVAL;
> > + /*
> > + * skip first 'void *__data' argument in btf_trace_##name
> > + * typedef
> > + */
> > + nr_args = btf_type_vlen(prog->aux->attach_func_proto) - 1;
> > + /* Save nr_args to reg0 */
> > + insn_buf[0] = BPF_MOV64_IMM(BPF_REG_0, nr_args);
> > + } else {
> > + /* Load nr_args from ctx - 8 */
> > + insn_buf[0] = BPF_LDX_MEM(BPF_DW, BPF_REG_0, BPF_REG_1, -8);
> > + }
> >
> > new_prog = bpf_patch_insn_data(env, i + delta, insn_buf, 1);
> > if (!new_prog)
> > diff --git a/kernel/trace/bpf_trace.c b/kernel/trace/bpf_trace.c
> > index 6e076485bf70..9b1b56851d26 100644
> > --- a/kernel/trace/bpf_trace.c
> > +++ b/kernel/trace/bpf_trace.c
> > @@ -1734,11 +1734,11 @@ tracing_prog_func_proto(enum bpf_func_id func_id, const struct bpf_prog *prog)
> > case BPF_FUNC_d_path:
> > return &bpf_d_path_proto;
> > case BPF_FUNC_get_func_arg:
> > - return bpf_prog_has_trampoline(prog) ? &bpf_get_func_arg_proto : NULL;
> > + return &bpf_get_func_arg_proto;
> > case BPF_FUNC_get_func_ret:
> > return bpf_prog_has_trampoline(prog) ? &bpf_get_func_ret_proto : NULL;
> > case BPF_FUNC_get_func_arg_cnt:
> > - return bpf_prog_has_trampoline(prog) ? &bpf_get_func_arg_cnt_proto : NULL;
> > + return &bpf_get_func_arg_cnt_proto;
> > case BPF_FUNC_get_attach_cookie:
> > if (prog->type == BPF_PROG_TYPE_TRACING &&
> > prog->expected_attach_type == BPF_TRACE_RAW_TP)
> > --
> > 2.52.0
> >
>
>
^ permalink raw reply
* Re: [PATCH bpf-next v2 1/2] bpf: support bpf_get_func_arg() for BPF_TRACE_RAW_TP
From: Andrii Nakryiko @ 2026-01-16 23:32 UTC (permalink / raw)
To: Menglong Dong
Cc: ast, daniel, john.fastabend, andrii, martin.lau, eddyz87, song,
yonghong.song, kpsingh, sdf, haoluo, jolsa, mattbobrowski,
rostedt, mhiramat, mathieu.desnoyers, bpf, linux-kernel,
linux-trace-kernel
In-Reply-To: <20260116071739.121182-2-dongml2@chinatelecom.cn>
On Thu, Jan 15, 2026 at 11:18 PM Menglong Dong <menglong8.dong@gmail.com> wrote:
>
> For now, bpf_get_func_arg() and bpf_get_func_arg_cnt() is not supported by
> the BPF_TRACE_RAW_TP, which is not convenient to get the argument of the
> tracepoint, especially for the case that the position of the arguments in
> a tracepoint can change.
>
> The target tracepoint BTF type id is specified during loading time,
> therefore we can get the function argument count from the function
> prototype instead of the stack.
>
> Signed-off-by: Menglong Dong <dongml2@chinatelecom.cn>
> ---
> v2:
> - for nr_args, skip first 'void *__data' argument in btf_trace_##name
> typedef
> ---
> kernel/bpf/verifier.c | 36 ++++++++++++++++++++++++++++++++----
> kernel/trace/bpf_trace.c | 4 ++--
> 2 files changed, 34 insertions(+), 6 deletions(-)
>
> diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
> index faa1ecc1fe9d..422d35c100ff 100644
> --- a/kernel/bpf/verifier.c
> +++ b/kernel/bpf/verifier.c
> @@ -23316,8 +23316,22 @@ static int do_misc_fixups(struct bpf_verifier_env *env)
> /* Implement bpf_get_func_arg inline. */
> if (prog_type == BPF_PROG_TYPE_TRACING &&
> insn->imm == BPF_FUNC_get_func_arg) {
> - /* Load nr_args from ctx - 8 */
> - insn_buf[0] = BPF_LDX_MEM(BPF_DW, BPF_REG_0, BPF_REG_1, -8);
> + if (eatype == BPF_TRACE_RAW_TP) {
> + int nr_args;
> +
> + if (!prog->aux->attach_func_proto)
> + return -EINVAL;
can this happen? can we have tp_btf program without attach_func_proto
properly set?
> + /*
> + * skip first 'void *__data' argument in btf_trace_##name
> + * typedef
> + */
> + nr_args = btf_type_vlen(prog->aux->attach_func_proto) - 1;
> + /* Save nr_args to reg0 */
> + insn_buf[0] = BPF_MOV64_IMM(BPF_REG_0, nr_args);
> + } else {
> + /* Load nr_args from ctx - 8 */
> + insn_buf[0] = BPF_LDX_MEM(BPF_DW, BPF_REG_0, BPF_REG_1, -8);
> + }
> insn_buf[1] = BPF_JMP32_REG(BPF_JGE, BPF_REG_2, BPF_REG_0, 6);
> insn_buf[2] = BPF_ALU64_IMM(BPF_LSH, BPF_REG_2, 3);
> insn_buf[3] = BPF_ALU64_REG(BPF_ADD, BPF_REG_2, BPF_REG_1);
> @@ -23369,8 +23383,22 @@ static int do_misc_fixups(struct bpf_verifier_env *env)
> /* Implement get_func_arg_cnt inline. */
> if (prog_type == BPF_PROG_TYPE_TRACING &&
> insn->imm == BPF_FUNC_get_func_arg_cnt) {
> - /* Load nr_args from ctx - 8 */
> - insn_buf[0] = BPF_LDX_MEM(BPF_DW, BPF_REG_0, BPF_REG_1, -8);
> + if (eatype == BPF_TRACE_RAW_TP) {
> + int nr_args;
> +
> + if (!prog->aux->attach_func_proto)
> + return -EINVAL;
> + /*
> + * skip first 'void *__data' argument in btf_trace_##name
> + * typedef
> + */
> + nr_args = btf_type_vlen(prog->aux->attach_func_proto) - 1;
> + /* Save nr_args to reg0 */
> + insn_buf[0] = BPF_MOV64_IMM(BPF_REG_0, nr_args);
> + } else {
> + /* Load nr_args from ctx - 8 */
> + insn_buf[0] = BPF_LDX_MEM(BPF_DW, BPF_REG_0, BPF_REG_1, -8);
> + }
>
> new_prog = bpf_patch_insn_data(env, i + delta, insn_buf, 1);
> if (!new_prog)
> diff --git a/kernel/trace/bpf_trace.c b/kernel/trace/bpf_trace.c
> index 6e076485bf70..9b1b56851d26 100644
> --- a/kernel/trace/bpf_trace.c
> +++ b/kernel/trace/bpf_trace.c
> @@ -1734,11 +1734,11 @@ tracing_prog_func_proto(enum bpf_func_id func_id, const struct bpf_prog *prog)
> case BPF_FUNC_d_path:
> return &bpf_d_path_proto;
> case BPF_FUNC_get_func_arg:
> - return bpf_prog_has_trampoline(prog) ? &bpf_get_func_arg_proto : NULL;
> + return &bpf_get_func_arg_proto;
> case BPF_FUNC_get_func_ret:
> return bpf_prog_has_trampoline(prog) ? &bpf_get_func_ret_proto : NULL;
> case BPF_FUNC_get_func_arg_cnt:
> - return bpf_prog_has_trampoline(prog) ? &bpf_get_func_arg_cnt_proto : NULL;
> + return &bpf_get_func_arg_cnt_proto;
> case BPF_FUNC_get_attach_cookie:
> if (prog->type == BPF_PROG_TYPE_TRACING &&
> prog->expected_attach_type == BPF_TRACE_RAW_TP)
> --
> 2.52.0
>
^ permalink raw reply
* Re: [PATCH bpf-next 2/4] x86/fgraph,bpf: Switch kprobe_multi program stack unwind to hw_regs path
From: Andrii Nakryiko @ 2026-01-16 22:24 UTC (permalink / raw)
To: Jiri Olsa
Cc: Steven Rostedt, Masami Hiramatsu, Josh Poimboeuf, Mahe Tardy,
Peter Zijlstra, bpf, linux-trace-kernel, x86, Yonghong Song,
Song Liu, Andrii Nakryiko
In-Reply-To: <aWpme7kBw9xyzRFP@krava>
On Fri, Jan 16, 2026 at 8:25 AM Jiri Olsa <olsajiri@gmail.com> wrote:
>
> On Thu, Jan 15, 2026 at 10:52:04AM -0800, Andrii Nakryiko wrote:
> > On Tue, Jan 13, 2026 at 3:43 AM Jiri Olsa <olsajiri@gmail.com> wrote:
> > >
> > > On Mon, Jan 12, 2026 at 05:07:57PM -0500, Steven Rostedt wrote:
> > > > On Mon, 12 Jan 2026 22:49:38 +0100
> > > > Jiri Olsa <jolsa@kernel.org> wrote:
> > > >
> > > > > To recreate same stack setup for return probe as we have for entry
> > > > > probe, we set the instruction pointer to the attached function address,
> > > > > which gets us the same unwind setup and same stack trace.
> > > > >
> > > > > With the fix, entry probe:
> > > > >
> > > > > # bpftrace -e 'kprobe:__x64_sys_newuname* { print(kstack)}'
> > > > > Attaching 1 probe...
> > > > >
> > > > > __x64_sys_newuname+9
> > > > > do_syscall_64+134
> > > > > entry_SYSCALL_64_after_hwframe+118
> > > > >
> > > > > return probe:
> > > > >
> > > > > # bpftrace -e 'kretprobe:__x64_sys_newuname* { print(kstack)}'
> > > > > Attaching 1 probe...
> > > > >
> > > > > __x64_sys_newuname+4
> > > > > do_syscall_64+134
> > > > > entry_SYSCALL_64_after_hwframe+118
> > > >
> > > > But is this really correct?
> > > >
> > > > The stack trace of the return from __x86_sys_newuname is from offset "+4".
> > > >
> > > > The stack trace from entry is offset "+9". Isn't it confusing that the
> > > > offset is likely not from the return portion of that function?
> > >
> > > right, makes sense.. so standard kprobe actualy skips attached function
> > > (__x86_sys_newuname) on return probe stacktrace.. perhaps we should do
> > > the same for kprobe_multi
> >
> > but it is quite nice to see what function we were kretprobing,
> > actually...
>
> IIUC Steven doesn't like the wrong +offset that comes from entry probe,
> maybe we could have func+(ADDRESS_OF_RET+1) ..but not sure how hard that
> would be
>
> still.. you always have the attached function ip when you get the stacktrace,
> so I'm not sure how usefull it's to have it in stacktrace as well.. you can
> always add it yourself
You can, but that's a custom thing that every single tool has to
implement. Having traced function right there in the stack would be so
nice and convenient.
I don't insist, but I'm just saying that practically speaking this
would make sense. Even conceptually, kretprobe is (logically) called
from traced function right before exit. In reality it's not exactly
like that and we don't know where ret happened, but having traced
function in kretprobe's stack trace is more useful than confusing,
IMO.
But again, I just found it interesting that we could have it, if we wanted to.
>
>
> > How hard would it be to support that for singular kprobe
> > as well? And what does fexit's stack trace show for such case?
>
> I think we will get the bpf_program address, so we see the attached
> function in stacktrace, will check
>
> thanks,
> jirka
^ permalink raw reply
* Re: [PATCH v5 0/6] Unload linux/kernel.h
From: Joel Fernandes @ 2026-01-16 17:32 UTC (permalink / raw)
To: Yury Norov, Steven Rostedt, Andrew Morton, Masami Hiramatsu,
Mathieu Desnoyers, Andy Shevchenko, Christophe Leroy,
Randy Dunlap, Ingo Molnar, Jani Nikula, Joonas Lahtinen,
David Laight, Petr Pavlu, Andi Shyti, Rodrigo Vivi,
Tvrtko Ursulin, Daniel Gomez, Greg Kroah-Hartman,
Rafael J. Wysocki, Danilo Krummrich, linux-kernel, intel-gfx,
dri-devel, linux-modules, linux-trace-kernel
Cc: Yury Norov (NVIDIA)
In-Reply-To: <20260116042510.241009-1-ynorov@nvidia.com>
On 1/15/2026 11:25 PM, Yury Norov wrote:
> kernel.h hosts declarations that can be placed better. This series
> decouples kernel.h with some explicit and implicit dependencies; also,
> moves tracing functionality to a new independent header.
>
> For testing, see v4.
>
> v1: https://lore.kernel.org/all/20251129195304.204082-1-yury.norov@gmail.com/
> v2: https://lore.kernel.org/all/20251203162329.280182-1-yury.norov@gmail.com/
> v3: https://lore.kernel.org/all/20251205175237.242022-1-yury.norov@gmail.com/
> v4: https://lore.kernel.org/all/20251225170930.1151781-1-yury.norov@gmail.com/
> v5:
> - drop v4#7, i.e. keep trace_printk.h included in kernel.h
Reviewed-by: Joel Fernandes <joelagnelf@nvidia.com>
thanks,
- Joel
>
> Steven Rostedt (1):
> tracing: Remove size parameter in __trace_puts()
>
> Yury Norov (5):
> kernel.h: drop STACK_MAGIC macro
> moduleparam: include required headers explicitly
> kernel.h: move VERIFY_OCTAL_PERMISSIONS() to sysfs.h
> kernel.h: include linux/instruction_pointer.h explicitly
> tracing: move tracing declarations from kernel.h to a dedicated header
>
> Documentation/filesystems/sysfs.rst | 2 +-
> arch/s390/include/asm/processor.h | 1 +
> .../drm/i915/gt/selftest_ring_submission.c | 1 +
> drivers/gpu/drm/i915/i915_selftest.h | 2 +
> include/linux/kernel.h | 210 +-----------------
> include/linux/moduleparam.h | 7 +-
> include/linux/sysfs.h | 13 ++
> include/linux/trace_printk.h | 204 +++++++++++++++++
> include/linux/ww_mutex.h | 1 +
> kernel/trace/trace.c | 7 +-
> kernel/trace/trace.h | 2 +-
> 11 files changed, 234 insertions(+), 216 deletions(-)
> create mode 100644 include/linux/trace_printk.h
>
^ permalink raw reply
* Re: [PATCH v5 0/6] Unload linux/kernel.h
From: Yury Norov @ 2026-01-16 17:06 UTC (permalink / raw)
To: Andy Shevchenko
Cc: Steven Rostedt, Andrew Morton, Masami Hiramatsu,
Mathieu Desnoyers, Christophe Leroy, Randy Dunlap, Ingo Molnar,
Jani Nikula, Joonas Lahtinen, David Laight, Petr Pavlu,
Andi Shyti, Rodrigo Vivi, Tvrtko Ursulin, Daniel Gomez,
Greg Kroah-Hartman, Rafael J. Wysocki, Danilo Krummrich,
Joel Fernandes, linux-kernel, intel-gfx, dri-devel, linux-modules,
linux-trace-kernel, Yury Norov (NVIDIA)
In-Reply-To: <aWoVRlm1GFux-Z-9@smile.fi.intel.com>
On Fri, Jan 16, 2026 at 12:39:02PM +0200, Andy Shevchenko wrote:
> On Thu, Jan 15, 2026 at 11:25:03PM -0500, Yury Norov wrote:
> > kernel.h hosts declarations that can be placed better. This series
> > decouples kernel.h with some explicit and implicit dependencies; also,
> > moves tracing functionality to a new independent header.
>
> Thanks! Which tree should it go through?
Andrew or Steven maybe? As a last resort, I can move it myself.
Thanks to you and everyone for review!
^ permalink raw reply
* Re: [PATCH v3 1/2] mm/vmscan: mitigate spurious kswapd_failures reset from direct reclaim
From: Johannes Weiner @ 2026-01-16 17:00 UTC (permalink / raw)
To: Jiayuan Chen
Cc: linux-mm, shakeel.butt, Jiayuan Chen, Andrew Morton,
David Hildenbrand, Lorenzo Stoakes, Liam R. Howlett,
Vlastimil Babka, Mike Rapoport, Suren Baghdasaryan, Michal Hocko,
Axel Rasmussen, Yuanchu Xie, Wei Xu, Steven Rostedt,
Masami Hiramatsu, Mathieu Desnoyers, Brendan Jackman, Zi Yan,
Qi Zheng, linux-kernel, linux-trace-kernel
In-Reply-To: <20260114074049.229935-2-jiayuan.chen@linux.dev>
On Wed, Jan 14, 2026 at 03:40:35PM +0800, Jiayuan Chen wrote:
> From: Jiayuan Chen <jiayuan.chen@shopee.com>
>
> When kswapd fails to reclaim memory, kswapd_failures is incremented.
> Once it reaches MAX_RECLAIM_RETRIES, kswapd stops running to avoid
> futile reclaim attempts. However, any successful direct reclaim
> unconditionally resets kswapd_failures to 0, which can cause problems.
>
> We observed an issue in production on a multi-NUMA system where a
> process allocated large amounts of anonymous pages on a single NUMA
> node, causing its watermark to drop below high and evicting most file
> pages:
>
> $ numastat -m
> Per-node system memory usage (in MBs):
> Node 0 Node 1 Total
> --------------- --------------- ---------------
> MemTotal 128222.19 127983.91 256206.11
> MemFree 1414.48 1432.80 2847.29
> MemUsed 126807.71 126551.11 252358.82
> SwapCached 0.00 0.00 0.00
> Active 29017.91 25554.57 54572.48
> Inactive 92749.06 95377.00 188126.06
> Active(anon) 28998.96 23356.47 52355.43
> Inactive(anon) 92685.27 87466.11 180151.39
> Active(file) 18.95 2198.10 2217.05
> Inactive(file) 63.79 7910.89 7974.68
>
> With swap disabled, only file pages can be reclaimed. When kswapd is
> woken (e.g., via wake_all_kswapds()), it runs continuously but cannot
> raise free memory above the high watermark since reclaimable file pages
> are insufficient. Normally, kswapd would eventually stop after
> kswapd_failures reaches MAX_RECLAIM_RETRIES.
>
> However, containers on this machine have memory.high set in their
> cgroup. Business processes continuously trigger the high limit, causing
> frequent direct reclaim that keeps resetting kswapd_failures to 0. This
> prevents kswapd from ever stopping.
>
> The key insight is that direct reclaim triggered by cgroup memory.high
> performs aggressive scanning to throttle the allocating process. With
> sufficiently aggressive scanning, even hot pages will eventually be
> reclaimed, making direct reclaim "successful" at freeing some memory.
> However, this success does not mean the node has reached a balanced
> state - the freed memory may still be insufficient to bring free pages
> above the high watermark. Unconditionally resetting kswapd_failures in
> this case keeps kswapd alive indefinitely.
>
> The result is that kswapd runs endlessly. Unlike direct reclaim which
> only reclaims from the allocating cgroup, kswapd scans the entire node's
> memory. This causes hot file pages from all workloads on the node to be
> evicted, not just those from the cgroup triggering memory.high. These
> pages constantly refault, generating sustained heavy IO READ pressure
> across the entire system.
>
> Fix this by only resetting kswapd_failures when the node is actually
> balanced. This allows both kswapd and direct reclaim to clear
> kswapd_failures upon successful reclaim, but only when the reclaim
> actually resolves the memory pressure (i.e., the node becomes balanced).
>
> Signed-off-by: Jiayuan Chen <jiayuan.chen@shopee.com>
> Signed-off-by: Jiayuan Chen <jiayuan.chen@linux.dev>
Great analysis, and I agree with both the fix and adding tracepoints.
Two minor nits:
> @@ -2650,6 +2650,25 @@ static bool can_age_anon_pages(struct lruvec *lruvec,
> lruvec_memcg(lruvec));
> }
>
> +static void pgdat_reset_kswapd_failures(pg_data_t *pgdat)
> +{
> + atomic_set(&pgdat->kswapd_failures, 0);
> +/*
> + * Reset kswapd_failures only when the node is balanced. Without this
> + * check, successful direct reclaim (e.g., from cgroup memory.high
> + * throttling) can keep resetting kswapd_failures even when the node
> + * cannot be balanced, causing kswapd to run endlessly.
> + */
> +static bool pgdat_balanced(pg_data_t *pgdat, int order, int highest_zoneidx);
> +static inline void pgdat_try_reset_kswapd_failures(struct pglist_data *pgdat,
Please remove the inline, the compiler will figure it out.
> + struct scan_control *sc)
> +{
> + if (pgdat_balanced(pgdat, sc->order, sc->reclaim_idx))
> + pgdat_reset_kswapd_failures(pgdat);
> +}
As this is kswapd API, please move these down to after wakeup_kswapd().
I think we can streamline the names a bit. We already use "hopeless"
for that state in the comments; can you please rename the functions
kswapd_clear_hopeless() and kswapd_try_clear_hopeless()?
We should then also replace the open-coded kswapd_failure checks with
kswapd_test_hopeless(). But I can send a follow-up patch if you don't
want to, just let me know.
^ 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