* [PATCH bpf-next v2 1/6] kallsyms: Add KALLSYMS_QUERY ioctl to /proc/kallsyms
2026-10-23 0:16 [PATCH bpf-next v2 0/6] kallsyms: Add KALLSYMS_QUERY ioctl to /proc/kallsyms Samuel Wu
@ 2026-10-23 0:16 ` Samuel Wu
2026-10-23 0:51 ` bot+bpf-ci
` (3 more replies)
2026-10-23 0:16 ` [PATCH bpf-next v2 2/6] tools: Copy KALLSYMS_QUERY definitions to linux/fs.h Samuel Wu
` (4 subsequent siblings)
5 siblings, 4 replies; 20+ messages in thread
From: Samuel Wu @ 2026-10-23 0:16 UTC (permalink / raw)
To: Jonathan Corbet, Shuah Khan, Randy Dunlap, Alexander Viro,
Christian Brauner, Jan Kara, Andrii Nakryiko, Eduard Zingerman,
Ihor Solodrai, Alexei Starovoitov, Daniel Borkmann,
Kumar Kartikeya Dwivedi, Martin KaFai Lau, Song Liu,
Yonghong Song, Jiri Olsa, Emil Tsalapatis, Nathan Chancellor,
Nick Desaulniers, Bill Wendling, Justin Stitt
Cc: Samuel Wu, kernel-team, linux-kernel, linux-fsdevel, linux-doc,
bpf, linux-kselftest, llvm
Add an extensible KALLSYMS_QUERY ioctl() interface to /proc/kallsyms
to support fast point queries without scanning the entire text file.
Exactly one query mode must be set in query_flags.
When KALLSYMS_QUERY_BY_SYMBOL is set in query_flags, KALLSYMS_QUERY
resolves a NUL-terminated symbol name in vmlinux or loaded modules and
then populates sym_addr:
- Searches vmlinux first via kallsyms_lookup_names() (binary search),
which takes precedence over loaded modules, and falls back to
module_kallsyms_on_each_symbol() under module_mutex for module
symbols
- Matches names exactly, like kallsyms_lookup_name(), disregarding the
.llvm.<hash> matching, following precedent set by commit fb6a421fb615
("kallsyms: Match symbols exactly with CONFIG_LTO_CLANG")
- Resolves 0x0 symbol addresses in both vmlinux and modules
- Rejects ambiguous symbols (multiple entries with the same name at
different addresses within vmlinux, or across loaded modules) with
-EADDRNOTAVAIL, while allowing same-address aliases
Userspace symbolizers resolve kernel addresses to symbol names with
/proc/kallsyms. This is typically done by reading the entire
/proc/kallsyms text file, and keeping a compressed sorted copy of it in
order to resolve addresses at runtime.
When KALLSYMS_QUERY_BY_ADDR is set in query_flags, KALLSYMS_QUERY
resolves one address to the symbol that contains it:
- sym_addr holds the address to resolve on input.
- sym_name_addr must point to a user buffer of sym_name_size bytes
that receives the NUL-terminated symbol name. On output,
sym_name_size holds the number of bytes written (including the
terminating NUL). -ENAMETOOLONG is returned if the buffer is too
small; a buffer of KSYM_NAME_LEN (512 at the time of writing) bytes
is always large enough.
- Symbols are resolved via kallsyms_lookup(), the same lookup as %pS,
covering vmlinux, loaded modules, BPF JIT images, and freed module
init functions still tracked by ftrace.
On an 8 CPU Cuttlefish (x86-64 VM), using this patchseries to load a BPF
program with 11 typeless ksyms has ~220x speedup (157ms -> 0.7ms).
On an 8 CPU x86-64 VM with a 175k line kallsyms, there are broad
performance and memory savings for the symbolization and callstack
sampling tool, Perfetto:
- Init time: ~350x speedup (155.7ms -> 0.44ms)
- 50k symbolizations: ~12x speedup (25.2ms -> 2.2ms)
- VmRSS: -68% (15.1MB -> 4.8MB)
Suggested-by: Andrii Nakryiko <andrii@kernel.org>
Signed-off-by: Samuel Wu <wusamuel@google.com>
---
include/uapi/linux/fs.h | 41 +++++++++++++
kernel/kallsyms.c | 132 ++++++++++++++++++++++++++++++++++++++++
2 files changed, 173 insertions(+)
diff --git a/include/uapi/linux/fs.h b/include/uapi/linux/fs.h
index 34c6f219462a..b43f227dad0e 100644
--- a/include/uapi/linux/fs.h
+++ b/include/uapi/linux/fs.h
@@ -674,6 +674,47 @@ struct procmap_query {
__u64 build_id_addr; /* in */
};
+/* /proc/kallsyms ioctl */
+#define KALLSYMS_QUERY _IOWR(PROCFS_IOCTL_MAGIC, 18, struct kallsyms_query)
+
+/* Exactly one query mode must be set in query_flags. */
+enum kallsyms_query_flags {
+ /*
+ * Look up a symbol in vmlinux or loaded modules (vmlinux first; BPF
+ * and ftrace symbols are not searched) by exact NUL-terminated name
+ * at sym_name_addr and return sym_addr. sym_addr and sym_name_size
+ * must be 0 on input.
+ */
+ KALLSYMS_QUERY_BY_SYMBOL = 0x01,
+ /*
+ * Look up the symbol covering sym_addr and write its NUL-terminated
+ * name to the sym_name_addr buffer of sym_name_size bytes. The same
+ * symbols as %pS are searched: vmlinux, loaded modules, BPF JIT
+ * images, and freed module init functions still tracked by ftrace.
+ * A buffer of KSYM_NAME_LEN (512 at the time of writing) bytes is
+ * always enough. On success, sym_name_size is set to the number of
+ * bytes written (including the terminating NUL).
+ */
+ KALLSYMS_QUERY_BY_ADDR = 0x02,
+};
+
+/*
+ * Returns -ENOENT if no symbol matches, -EADDRNOTAVAIL if the name maps to
+ * more than one address, -ENAMETOOLONG if the name does not fit in
+ * KSYM_NAME_LEN (512 at the time of writing) or sym_name_size bytes, and
+ * -EPERM if kallsyms addresses are hidden from the opener of /proc/kallsyms
+ * (e.g. kptr_restrict).
+ */
+struct kallsyms_query {
+ __u64 size; /* in - struct size for compatibility */
+ __u64 query_flags; /* in - query mode (enum kallsyms_query_flags) */
+ __u64 sym_name_addr; /* in - address of symbol name buffer */
+ __u64 sym_addr; /* out (BY_SYMBOL) / in (BY_ADDR) */
+ __u64 sym_name_size; /* in/out - name buffer size / bytes written */
+};
+
+#define KALLSYMS_QUERY_SIZE_VER0 40 /* sizeof first published struct */
+
/*
* Shutdown the filesystem.
*/
diff --git a/kernel/kallsyms.c b/kernel/kallsyms.c
index aec2f06858af..7caaf3e421c7 100644
--- a/kernel/kallsyms.c
+++ b/kernel/kallsyms.c
@@ -31,6 +31,7 @@
#include <linux/kernel.h>
#include <linux/bsearch.h>
#include <linux/btf_ids.h>
+#include <linux/uaccess.h>
#include "kallsyms_internal.h"
@@ -922,11 +923,142 @@ const char *kdb_walk_kallsyms(loff_t *pos)
}
#endif /* CONFIG_KGDB_KDB */
+struct kallsyms_mod_find_ctx {
+ const char *name;
+ unsigned long addr;
+ bool found;
+};
+
+static int kallsyms_mod_find_cb(void *data, const char *name, unsigned long addr)
+{
+ struct kallsyms_mod_find_ctx *ctx = data;
+
+ if (strcmp(name, ctx->name) != 0)
+ return 0;
+ if (ctx->found && ctx->addr != addr)
+ return -EADDRNOTAVAIL;
+ ctx->found = true;
+ ctx->addr = addr;
+ return 0;
+}
+
+static int do_kallsyms_query(struct kallsym_iter *iter, void __user *uarg)
+{
+ char namebuf[KSYM_NAME_LEN];
+ struct kallsyms_query karg;
+ unsigned long addr;
+ unsigned int start;
+ unsigned int end;
+ unsigned int i;
+ __u64 usize;
+ long len;
+ int err;
+
+ BUILD_BUG_ON(sizeof(struct kallsyms_query) != KALLSYMS_QUERY_SIZE_VER0);
+
+ /*
+ * The text interface shows zero addresses to openers without permission.
+ * A zeroed point query result is useless (and an address-to-symbol query
+ * would leak KASLR), so fail instead.
+ */
+ if (!iter->show_value)
+ return -EPERM;
+ if (copy_from_user(&usize, uarg, sizeof(usize)))
+ return -EFAULT;
+ if (usize > PAGE_SIZE)
+ return -E2BIG;
+ if (usize < KALLSYMS_QUERY_SIZE_VER0)
+ return -EINVAL;
+
+ err = copy_struct_from_user(&karg, sizeof(karg), uarg, usize);
+ if (err)
+ return err;
+
+ switch (karg.query_flags) {
+ case KALLSYMS_QUERY_BY_SYMBOL:
+ if (karg.sym_addr || karg.sym_name_size)
+ return -EINVAL;
+
+ len = strncpy_from_user(namebuf, u64_to_user_ptr(karg.sym_name_addr),
+ sizeof(namebuf));
+ if (len < 0)
+ return len;
+ if (len == 0)
+ return -EINVAL;
+ if (len == sizeof(namebuf))
+ return -ENAMETOOLONG;
+
+ if (kallsyms_lookup_names(namebuf, &start, &end)) {
+ struct kallsyms_mod_find_ctx ctx = { .name = namebuf };
+
+ err = module_kallsyms_on_each_symbol(NULL, kallsyms_mod_find_cb, &ctx);
+ /* Check first: maps -EOPNOTSUPP (CONFIG_MODULES=n) to -ENOENT. */
+ if (!ctx.found)
+ return -ENOENT;
+ if (err)
+ return err;
+ addr = ctx.addr;
+ } else {
+ addr = kallsyms_sym_address(get_symbol_seq(start));
+ for (i = start + 1; i <= end; i++) {
+ if (kallsyms_sym_address(get_symbol_seq(i)) != addr)
+ return -EADDRNOTAVAIL;
+ }
+ }
+
+ karg.sym_addr = addr;
+ break;
+ case KALLSYMS_QUERY_BY_ADDR:
+ if (!karg.sym_name_size)
+ return -EINVAL;
+ addr = karg.sym_addr;
+ /* Reject addresses that do not fit in unsigned long (32-bit). */
+ if (addr != karg.sym_addr)
+ return -ENOENT;
+ if (!kallsyms_lookup(addr, NULL, NULL, NULL, namebuf))
+ return -ENOENT;
+
+ len = strlen(namebuf) + 1;
+ if (karg.sym_name_size < len)
+ return -ENAMETOOLONG;
+ if (copy_to_user(u64_to_user_ptr(karg.sym_name_addr), namebuf, len))
+ return -EFAULT;
+
+ karg.sym_name_size = len;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (copy_to_user(uarg, &karg, min_t(size_t, sizeof(karg), usize)))
+ return -EFAULT;
+
+ return 0;
+}
+
+static long kallsyms_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+ struct seq_file *m = file->private_data;
+
+ switch (_IOC_NR(cmd)) {
+ case _IOC_NR(KALLSYMS_QUERY):
+ if (!extensible_ioctl_valid(cmd, KALLSYMS_QUERY, KALLSYMS_QUERY_SIZE_VER0))
+ return -ENOIOCTLCMD;
+ return do_kallsyms_query(m->private, (void __user *)arg);
+ default:
+ return -ENOIOCTLCMD;
+ }
+}
+
static const struct proc_ops kallsyms_proc_ops = {
.proc_open = kallsyms_open,
.proc_read = seq_read,
.proc_lseek = seq_lseek,
.proc_release = seq_release_private,
+ .proc_ioctl = kallsyms_ioctl,
+#ifdef CONFIG_COMPAT
+ .proc_compat_ioctl = compat_ptr_ioctl,
+#endif
};
static int __init kallsyms_init(void)
--
2.56.0.257.gf7497c7d27-goog
^ permalink raw reply related [flat|nested] 20+ messages in thread* Re: [PATCH bpf-next v2 1/6] kallsyms: Add KALLSYMS_QUERY ioctl to /proc/kallsyms
2026-10-23 0:16 ` [PATCH bpf-next v2 1/6] " Samuel Wu
@ 2026-10-23 0:51 ` bot+bpf-ci
2026-10-27 21:31 ` Samuel Wu
2026-10-25 23:29 ` Randy Dunlap
` (2 subsequent siblings)
3 siblings, 1 reply; 20+ messages in thread
From: bot+bpf-ci @ 2026-10-23 0:51 UTC (permalink / raw)
To: wusamuel, corbet, skhan, rdunlap, viro, brauner, jack, andrii,
eddyz87, ihor.solodrai, ast, daniel, memxor, martin.lau, song,
yonghong.song, jolsa, emil, nathan, ndesaulniers, morbo,
justinstitt
Cc: wusamuel, kernel-team, linux-kernel, linux-fsdevel, linux-doc,
bpf, linux-kselftest, llvm, ast, andrii, daniel, martin.lau,
eddyz87, yonghong.song, mason, ihor.solodrai
[-- Attachment #1: Type: text/plain, Size: 3742 bytes --]
> kallsyms: Add KALLSYMS_QUERY ioctl to /proc/kallsyms
TL;DR: KALLSYMS_QUERY_BY_ADDR may return BPF JIT symbol names that
bpf_jit_kallsyms=0 or bpf_jit_harden hide in /proc/kallsyms, possibly leaking
JIT image locations to openers with show_value.
> diff --git a/kernel/kallsyms.c b/kernel/kallsyms.c
> index aec2f06858af..7caaf3e421c7 100644
> --- a/kernel/kallsyms.c
> +++ b/kernel/kallsyms.c
[ ... ]
> @@ -922,11 +923,142 @@ const char *kdb_walk_kallsyms(loff_t *pos)
[ ... ]
> +static int do_kallsyms_query(struct kallsym_iter *iter, void __user *uarg)
> +{
[ ... ]
> +
> + /*
> + * The text interface shows zero addresses to openers without permission.
> + * A zeroed point query result is useless (and an address-to-symbol query
> + * would leak KASLR), so fail instead.
> + */
> + if (!iter->show_value)
[ ... ]
> + case KALLSYMS_QUERY_BY_ADDR:
> + if (!karg.sym_name_size)
> + return -EINVAL;
> + addr = karg.sym_addr;
> + /* Reject addresses that do not fit in unsigned long (32-bit). */
> + if (addr != karg.sym_addr)
> + return -ENOENT;
> + if (!kallsyms_lookup(addr, NULL, NULL, NULL, namebuf))
> + return -ENOENT;
Can KALLSYMS_QUERY_BY_ADDR hand out BPF JIT symbol names that /proc/kallsyms
is configured to hide?
kallsyms_lookup() reaches kallsyms_lookup_buildid() and falls through to
bpf_address_lookup() -> bpf_ksym_find(), which searches bpf_tree without any
policy check. bpf_ksym_add() always inserts into bpf_tree
(bpf_prog_kallsyms_add() only requires bpf_prog_kallsyms_candidate() and
CAP_BPF).
The text listing of the same file goes through get_ksymbol_bpf() ->
bpf_get_kallsym(), which starts with:
if (!bpf_jit_kallsyms_enabled())
return ret;
bpf_jit_kallsyms_enabled() returns false when net.core.bpf_jit_harden is
non-zero or net.core.bpf_jit_kallsyms is 0. bpf_get_kallsym() is the only
caller of bpf_jit_kallsyms_enabled() in the tree, so hiding the names in
/proc/kallsyms is now the only thing these sysctls do. Commit af91acbc6299
("bpf: Fix bpf jit kallsym access") says so directly:
It should only skip addr->name resolution for 'cat /proc/kallsyms'.
That's what bpf_jit_kallsyms and bpf_jit_harden protect.
With this patch, a process whose open of /proc/kallsyms has show_value set
can call ioctl(KALLSYMS_QUERY) with query_flags=KALLSYMS_QUERY_BY_ADDR and
get back names like bpf_prog_<tag>_<name> (plus trampolines, dispatchers and
__cfi_ prefixes) for addresses inside JIT images. That gives it an
address->name oracle for finding JIT image locations, which these sysctls
are meant to withhold from /proc/kallsyms.
The comment above the show_value check argues for matching the text
interface ("The text interface shows zero addresses to openers without
permission ... an address-to-symbol query would leak KASLR, so fail
instead"), but the code applies only the show_value gate and skips the
BPF-specific gate. The uapi comment and the commit message both say
BY_ADDR covers "BPF JIT images", but neither mentions bpf_jit_kallsyms or
bpf_jit_harden.
This is reachable whenever CONFIG_KALLSYMS and CONFIG_BPF_JIT are enabled, a
JITed program loaded with CAP_BPF exists, and either sysctl hides BPF
symbols. No later commit in the series changes kernel/ code to address
this.
Should the BY_ADDR path return -ENOENT when kallsyms_lookup() resolves to a
BPF address (for example, is_bpf_text_address(addr) is true and the address
is not in vmlinux or a module) unless bpf_jit_kallsyms_enabled() is true?
---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md
CI run summary: https://github.com/kernel-patches/bpf/actions/runs/39878338337
^ permalink raw reply [flat|nested] 20+ messages in thread* Re: [PATCH bpf-next v2 1/6] kallsyms: Add KALLSYMS_QUERY ioctl to /proc/kallsyms
2026-10-23 0:51 ` bot+bpf-ci
@ 2026-10-27 21:31 ` Samuel Wu
0 siblings, 0 replies; 20+ messages in thread
From: Samuel Wu @ 2026-10-27 21:31 UTC (permalink / raw)
To: bot+bpf-ci
Cc: corbet, skhan, rdunlap, viro, brauner, jack, andrii, eddyz87,
ihor.solodrai, ast, daniel, memxor, martin.lau, song,
yonghong.song, jolsa, emil, nathan, ndesaulniers, morbo,
justinstitt, kernel-team, linux-kernel, linux-fsdevel, linux-doc,
bpf, linux-kselftest, llvm, martin.lau, mason
On Thu, Oct 22, 2026 at 5:51 PM <bot+bpf-ci@kernel.org> wrote:
>
> [ ... ]
>
> Should the BY_ADDR path return -ENOENT when kallsyms_lookup() resolves to a
> BPF address (for example, is_bpf_text_address(addr) is true and the address
> is not in vmlinux or a module) unless bpf_jit_kallsyms_enabled() is true?
>
Thanks for the catch. In v3, I'll update BY_ADDR to gate on
bpf_jit_kallsyms_enabled() so hidden BPF JIT symbols return -ENOENT.
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH bpf-next v2 1/6] kallsyms: Add KALLSYMS_QUERY ioctl to /proc/kallsyms
2026-10-23 0:16 ` [PATCH bpf-next v2 1/6] " Samuel Wu
2026-10-23 0:51 ` bot+bpf-ci
@ 2026-10-25 23:29 ` Randy Dunlap
2026-10-27 21:33 ` Samuel Wu
2026-10-27 22:24 ` Andrii Nakryiko
2026-10-28 13:40 ` David Laight
3 siblings, 1 reply; 20+ messages in thread
From: Randy Dunlap @ 2026-10-25 23:29 UTC (permalink / raw)
To: Samuel Wu, Jonathan Corbet, Shuah Khan, Alexander Viro,
Christian Brauner, Jan Kara, Andrii Nakryiko, Eduard Zingerman,
Ihor Solodrai, Alexei Starovoitov, Daniel Borkmann,
Kumar Kartikeya Dwivedi, Martin KaFai Lau, Song Liu,
Yonghong Song, Jiri Olsa, Emil Tsalapatis, Nathan Chancellor,
Nick Desaulniers, Bill Wendling, Justin Stitt
Cc: kernel-team, linux-kernel, linux-fsdevel, linux-doc, bpf,
linux-kselftest, llvm
On 10/22/26 5:16 PM, Samuel Wu wrote:
...
> ---
> include/uapi/linux/fs.h | 41 +++++++++++++
> kernel/kallsyms.c | 132 ++++++++++++++++++++++++++++++++++++++++
> 2 files changed, 173 insertions(+)
>
> diff --git a/include/uapi/linux/fs.h b/include/uapi/linux/fs.h
> index 34c6f219462a..b43f227dad0e 100644
> --- a/include/uapi/linux/fs.h
> +++ b/include/uapi/linux/fs.h
> @@ -674,6 +674,47 @@ struct procmap_query {
> __u64 build_id_addr; /* in */
> };
>
> +/* /proc/kallsyms ioctl */
> +#define KALLSYMS_QUERY _IOWR(PROCFS_IOCTL_MAGIC, 18, struct kallsyms_query)
...
Documentation/userspace/ioctl/ioctl-number.rst doesn't seem to know
about PROCFS_IOCTL_MAGIC and what range of @nr values that it uses.
Please update it.
--
~Randy
^ permalink raw reply [flat|nested] 20+ messages in thread* Re: [PATCH bpf-next v2 1/6] kallsyms: Add KALLSYMS_QUERY ioctl to /proc/kallsyms
2026-10-25 23:29 ` Randy Dunlap
@ 2026-10-27 21:33 ` Samuel Wu
0 siblings, 0 replies; 20+ messages in thread
From: Samuel Wu @ 2026-10-27 21:33 UTC (permalink / raw)
To: Randy Dunlap
Cc: Jonathan Corbet, Shuah Khan, Alexander Viro, Christian Brauner,
Jan Kara, Andrii Nakryiko, Eduard Zingerman, Ihor Solodrai,
Alexei Starovoitov, Daniel Borkmann, Kumar Kartikeya Dwivedi,
Martin KaFai Lau, Song Liu, Yonghong Song, Jiri Olsa,
Emil Tsalapatis, Nathan Chancellor, Nick Desaulniers,
Bill Wendling, Justin Stitt, kernel-team, linux-kernel,
linux-fsdevel, linux-doc, bpf, linux-kselftest, llvm
On Sun, Oct 25, 2026 at 4:29 PM Randy Dunlap <rdunlap@infradead.org> wrote:
>
>
>
> On 10/22/26 5:16 PM, Samuel Wu wrote:
>
> ...
> > ---
> > include/uapi/linux/fs.h | 41 +++++++++++++
> > kernel/kallsyms.c | 132 ++++++++++++++++++++++++++++++++++++++++
> > 2 files changed, 173 insertions(+)
> >
> > diff --git a/include/uapi/linux/fs.h b/include/uapi/linux/fs.h
> > index 34c6f219462a..b43f227dad0e 100644
> > --- a/include/uapi/linux/fs.h
> > +++ b/include/uapi/linux/fs.h
> > @@ -674,6 +674,47 @@ struct procmap_query {
> > __u64 build_id_addr; /* in */
> > };
> >
> > +/* /proc/kallsyms ioctl */
> > +#define KALLSYMS_QUERY _IOWR(PROCFS_IOCTL_MAGIC, 18, struct kallsyms_query)
> ...
>
>
> Documentation/userspace/ioctl/ioctl-number.rst doesn't seem to know
> about PROCFS_IOCTL_MAGIC and what range of @nr values that it uses.
>
> Please update it.
>
Thanks for the catch Randy! In v3, I'll add a 'f' 10-12 entry for
PROCFS_IOCTL_MAGIC (PAGEMAP_SCAN, PROCMAP_QUERY, KALSYMS_QUERY) to
Documentation/userspace/ioctl/ioctl-number.rst.
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH bpf-next v2 1/6] kallsyms: Add KALLSYMS_QUERY ioctl to /proc/kallsyms
2026-10-23 0:16 ` [PATCH bpf-next v2 1/6] " Samuel Wu
2026-10-23 0:51 ` bot+bpf-ci
2026-10-25 23:29 ` Randy Dunlap
@ 2026-10-27 22:24 ` Andrii Nakryiko
2026-10-28 13:40 ` David Laight
3 siblings, 0 replies; 20+ messages in thread
From: Andrii Nakryiko @ 2026-10-27 22:24 UTC (permalink / raw)
To: Samuel Wu
Cc: Jonathan Corbet, Shuah Khan, Randy Dunlap, Alexander Viro,
Christian Brauner, Jan Kara, Andrii Nakryiko, Eduard Zingerman,
Ihor Solodrai, Alexei Starovoitov, Daniel Borkmann,
Kumar Kartikeya Dwivedi, Martin KaFai Lau, Song Liu,
Yonghong Song, Jiri Olsa, Emil Tsalapatis, Nathan Chancellor,
Nick Desaulniers, Bill Wendling, Justin Stitt, kernel-team,
linux-kernel, linux-fsdevel, linux-doc, bpf, linux-kselftest,
llvm
On Thu, Oct 22, 2026 at 5:17 PM Samuel Wu <wusamuel@google.com> wrote:
>
> Add an extensible KALLSYMS_QUERY ioctl() interface to /proc/kallsyms
> to support fast point queries without scanning the entire text file.
> Exactly one query mode must be set in query_flags.
>
> When KALLSYMS_QUERY_BY_SYMBOL is set in query_flags, KALLSYMS_QUERY
> resolves a NUL-terminated symbol name in vmlinux or loaded modules and
> then populates sym_addr:
> - Searches vmlinux first via kallsyms_lookup_names() (binary search),
> which takes precedence over loaded modules, and falls back to
> module_kallsyms_on_each_symbol() under module_mutex for module
> symbols
> - Matches names exactly, like kallsyms_lookup_name(), disregarding the
> .llvm.<hash> matching, following precedent set by commit fb6a421fb615
> ("kallsyms: Match symbols exactly with CONFIG_LTO_CLANG")
> - Resolves 0x0 symbol addresses in both vmlinux and modules
> - Rejects ambiguous symbols (multiple entries with the same name at
> different addresses within vmlinux, or across loaded modules) with
> -EADDRNOTAVAIL, while allowing same-address aliases
>
> Userspace symbolizers resolve kernel addresses to symbol names with
> /proc/kallsyms. This is typically done by reading the entire
> /proc/kallsyms text file, and keeping a compressed sorted copy of it in
> order to resolve addresses at runtime.
>
> When KALLSYMS_QUERY_BY_ADDR is set in query_flags, KALLSYMS_QUERY
> resolves one address to the symbol that contains it:
> - sym_addr holds the address to resolve on input.
> - sym_name_addr must point to a user buffer of sym_name_size bytes
> that receives the NUL-terminated symbol name. On output,
> sym_name_size holds the number of bytes written (including the
> terminating NUL). -ENAMETOOLONG is returned if the buffer is too
> small; a buffer of KSYM_NAME_LEN (512 at the time of writing) bytes
> is always large enough.
> - Symbols are resolved via kallsyms_lookup(), the same lookup as %pS,
> covering vmlinux, loaded modules, BPF JIT images, and freed module
> init functions still tracked by ftrace.
>
> On an 8 CPU Cuttlefish (x86-64 VM), using this patchseries to load a BPF
> program with 11 typeless ksyms has ~220x speedup (157ms -> 0.7ms).
>
> On an 8 CPU x86-64 VM with a 175k line kallsyms, there are broad
> performance and memory savings for the symbolization and callstack
> sampling tool, Perfetto:
> - Init time: ~350x speedup (155.7ms -> 0.44ms)
> - 50k symbolizations: ~12x speedup (25.2ms -> 2.2ms)
> - VmRSS: -68% (15.1MB -> 4.8MB)
>
> Suggested-by: Andrii Nakryiko <andrii@kernel.org>
> Signed-off-by: Samuel Wu <wusamuel@google.com>
> ---
> include/uapi/linux/fs.h | 41 +++++++++++++
> kernel/kallsyms.c | 132 ++++++++++++++++++++++++++++++++++++++++
> 2 files changed, 173 insertions(+)
>
> diff --git a/include/uapi/linux/fs.h b/include/uapi/linux/fs.h
> index 34c6f219462a..b43f227dad0e 100644
> --- a/include/uapi/linux/fs.h
> +++ b/include/uapi/linux/fs.h
> @@ -674,6 +674,47 @@ struct procmap_query {
> __u64 build_id_addr; /* in */
> };
>
> +/* /proc/kallsyms ioctl */
> +#define KALLSYMS_QUERY _IOWR(PROCFS_IOCTL_MAGIC, 18, struct kallsyms_query)
> +
> +/* Exactly one query mode must be set in query_flags. */
> +enum kallsyms_query_flags {
> + /*
> + * Look up a symbol in vmlinux or loaded modules (vmlinux first; BPF
> + * and ftrace symbols are not searched) by exact NUL-terminated name
> + * at sym_name_addr and return sym_addr. sym_addr and sym_name_size
> + * must be 0 on input.
> + */
> + KALLSYMS_QUERY_BY_SYMBOL = 0x01,
> + /*
> + * Look up the symbol covering sym_addr and write its NUL-terminated
> + * name to the sym_name_addr buffer of sym_name_size bytes. The same
> + * symbols as %pS are searched: vmlinux, loaded modules, BPF JIT
> + * images, and freed module init functions still tracked by ftrace.
> + * A buffer of KSYM_NAME_LEN (512 at the time of writing) bytes is
> + * always enough. On success, sym_name_size is set to the number of
> + * bytes written (including the terminating NUL).
> + */
> + KALLSYMS_QUERY_BY_ADDR = 0x02,
> +};
> +
> +/*
> + * Returns -ENOENT if no symbol matches, -EADDRNOTAVAIL if the name maps to
> + * more than one address, -ENAMETOOLONG if the name does not fit in
> + * KSYM_NAME_LEN (512 at the time of writing) or sym_name_size bytes, and
> + * -EPERM if kallsyms addresses are hidden from the opener of /proc/kallsyms
> + * (e.g. kptr_restrict).
> + */
> +struct kallsyms_query {
> + __u64 size; /* in - struct size for compatibility */
> + __u64 query_flags; /* in - query mode (enum kallsyms_query_flags) */
> + __u64 sym_name_addr; /* in - address of symbol name buffer */
> + __u64 sym_addr; /* out (BY_SYMBOL) / in (BY_ADDR) */
> + __u64 sym_name_size; /* in/out - name buffer size / bytes written */
> +};
it might be smart to have QUERY_BY_ADDR and QUERY_BY_NAME to be two
separate ioctl commands with its own query in/out struct. This would
remove addr vs name ambiguity in sym_name_addr field, and will allow
us to evolve APIs independently, if necessary. They should still
probably reuse the same type to represent symbol information, and have
kallsyms_query struct provide a pointer to that struct (and its
maximum size), which the kernel will fill out and set the size it
actually knows about.
struct kallsyms_query_by_addr {
__u64 size;
__u64 query_flags;
__u64 sym_addr; /* in */
struct kallsyms_sym *sym; /* out */
__u64 sym_len;
};
struct kallsyms_sym {
__u64 sym_addr;
__u64 sym_name;
__u64 sym_name_len;
__u64 sym_module;
__u64 sym_module_len; /* if zero, no module requested/available */
};
e.g. for symbol you'd want to return its name, address, but also
module name. and we should leave a way to extend it further with more
optional information (that's the kallsyms extensibility bit I was
talking about).
aslo, we should think about adding ways to paginate (or at least leave
a possibility of that in the future) if there are multiple matches
(e.g., for static functions with the same name resolving to multiple
addresses); not sure how it should look like API wise, but it would be
good to still return "any" or "first" match, but let user know there
are multiple, so user can decide how to proceed...
on input I'd also add module name as optional input argument, with
"vmlinux" limiting to just vmlinux, set module name to just that name,
and NULL/zero meaning "we don't care"
for QUERY_BY_ADDR we should have a flag saying whether an exact match
by address is requested or we should find a symbol that covers the
address (that's most useful for symbolization). maybe even "at or
after" address mode, so you can paginate (though probably explicitly
designed batch API would be even more useful later)
tl;dr, this is the right direction, but I think we should polish API a
bit more to be more universally useful and extensible
> +
> +#define KALLSYMS_QUERY_SIZE_VER0 40 /* sizeof first published struct */
> +
> /*
> * Shutdown the filesystem.
> */
> diff --git a/kernel/kallsyms.c b/kernel/kallsyms.c
> index aec2f06858af..7caaf3e421c7 100644
> --- a/kernel/kallsyms.c
> +++ b/kernel/kallsyms.c
> @@ -31,6 +31,7 @@
> #include <linux/kernel.h>
> #include <linux/bsearch.h>
> #include <linux/btf_ids.h>
> +#include <linux/uaccess.h>
>
> #include "kallsyms_internal.h"
>
> @@ -922,11 +923,142 @@ const char *kdb_walk_kallsyms(loff_t *pos)
> }
> #endif /* CONFIG_KGDB_KDB */
>
> +struct kallsyms_mod_find_ctx {
> + const char *name;
> + unsigned long addr;
> + bool found;
> +};
> +
> +static int kallsyms_mod_find_cb(void *data, const char *name, unsigned long addr)
> +{
> + struct kallsyms_mod_find_ctx *ctx = data;
> +
> + if (strcmp(name, ctx->name) != 0)
> + return 0;
> + if (ctx->found && ctx->addr != addr)
> + return -EADDRNOTAVAIL;
> + ctx->found = true;
> + ctx->addr = addr;
> + return 0;
> +}
> +
> +static int do_kallsyms_query(struct kallsym_iter *iter, void __user *uarg)
> +{
> + char namebuf[KSYM_NAME_LEN];
> + struct kallsyms_query karg;
> + unsigned long addr;
> + unsigned int start;
> + unsigned int end;
> + unsigned int i;
> + __u64 usize;
> + long len;
> + int err;
> +
> + BUILD_BUG_ON(sizeof(struct kallsyms_query) != KALLSYMS_QUERY_SIZE_VER0);
> +
> + /*
> + * The text interface shows zero addresses to openers without permission.
> + * A zeroed point query result is useless (and an address-to-symbol query
> + * would leak KASLR), so fail instead.
> + */
> + if (!iter->show_value)
> + return -EPERM;
> + if (copy_from_user(&usize, uarg, sizeof(usize)))
> + return -EFAULT;
> + if (usize > PAGE_SIZE)
> + return -E2BIG;
> + if (usize < KALLSYMS_QUERY_SIZE_VER0)
PROCMAP_QUERY uses offsetofend() for the last defined field, any
reason to have this #define and BUILD_BUG_ON here?
> + return -EINVAL;
> +
> + err = copy_struct_from_user(&karg, sizeof(karg), uarg, usize);
> + if (err)
> + return err;
> +
[...]
^ permalink raw reply [flat|nested] 20+ messages in thread* Re: [PATCH bpf-next v2 1/6] kallsyms: Add KALLSYMS_QUERY ioctl to /proc/kallsyms
2026-10-23 0:16 ` [PATCH bpf-next v2 1/6] " Samuel Wu
` (2 preceding siblings ...)
2026-10-27 22:24 ` Andrii Nakryiko
@ 2026-10-28 13:40 ` David Laight
3 siblings, 0 replies; 20+ messages in thread
From: David Laight @ 2026-10-28 13:40 UTC (permalink / raw)
To: Samuel Wu
Cc: Jonathan Corbet, Shuah Khan, Randy Dunlap, Alexander Viro,
Christian Brauner, Jan Kara, Andrii Nakryiko, Eduard Zingerman,
Ihor Solodrai, Alexei Starovoitov, Daniel Borkmann,
Kumar Kartikeya Dwivedi, Martin KaFai Lau, Song Liu,
Yonghong Song, Jiri Olsa, Emil Tsalapatis, Nathan Chancellor,
Nick Desaulniers, Bill Wendling, Justin Stitt, kernel-team,
linux-kernel, linux-fsdevel, linux-doc, bpf, linux-kselftest,
llvm
On Thu, 22 Oct 2026 17:16:02 -0700
Samuel Wu <wusamuel@google.com> wrote:
> Add an extensible KALLSYMS_QUERY ioctl() interface to /proc/kallsyms
> to support fast point queries without scanning the entire text file.
> Exactly one query mode must be set in query_flags.
>
> When KALLSYMS_QUERY_BY_SYMBOL is set in query_flags, KALLSYMS_QUERY
> resolves a NUL-terminated symbol name in vmlinux or loaded modules and
> then populates sym_addr:
> - Searches vmlinux first via kallsyms_lookup_names() (binary search),
> which takes precedence over loaded modules, and falls back to
> module_kallsyms_on_each_symbol() under module_mutex for module
> symbols
> - Matches names exactly, like kallsyms_lookup_name(), disregarding the
> .llvm.<hash> matching, following precedent set by commit fb6a421fb615
> ("kallsyms: Match symbols exactly with CONFIG_LTO_CLANG")
Hmmmm....
What happens when you have functions foo and foo.constprop0
In some sense they are duplicates.
> - Resolves 0x0 symbol addresses in both vmlinux and modules
> - Rejects ambiguous symbols (multiple entries with the same name at
> different addresses within vmlinux, or across loaded modules) with
> -EADDRNOTAVAIL, while allowing same-address aliases
>
> Userspace symbolizers resolve kernel addresses to symbol names with
> /proc/kallsyms. This is typically done by reading the entire
> /proc/kallsyms text file, and keeping a compressed sorted copy of it in
> order to resolve addresses at runtime.
You could mmap the tables into userspace and have a ioctl that reported
the offsets of the tables (etc).
Then user lookup would faster then kernel lookup!
Especially since you could throw a bit more memory at it to remove the
linear scans.
>
> When KALLSYMS_QUERY_BY_ADDR is set in query_flags, KALLSYMS_QUERY
> resolves one address to the symbol that contains it:
> - sym_addr holds the address to resolve on input.
> - sym_name_addr must point to a user buffer of sym_name_size bytes
> that receives the NUL-terminated symbol name. On output,
> sym_name_size holds the number of bytes written (including the
> terminating NUL). -ENAMETOOLONG is returned if the buffer is too
> small; a buffer of KSYM_NAME_LEN (512 at the time of writing) bytes
> is always large enough.
> - Symbols are resolved via kallsyms_lookup(), the same lookup as %pS,
> covering vmlinux, loaded modules, BPF JIT images, and freed module
> init functions still tracked by ftrace.
>
> On an 8 CPU Cuttlefish (x86-64 VM), using this patchseries to load a BPF
> program with 11 typeless ksyms has ~220x speedup (157ms -> 0.7ms).
Have you tried measuring it with my patches that speed up the kernel
'search by name' code?
That speeds the lookup by a factor of ~18 (testing the code in userspace).
You also should allow for 'find first' and 'find next'.
As well as letting the code iterate though functions with the same name
it is absolutely trivial to enhance the 'lookup by name' code to match
a prefix.
(Basically it needs to 'return -index' instead of 'return -1' when the
binary scan fails.)
>
> On an 8 CPU x86-64 VM with a 175k line kallsyms, there are broad
> performance and memory savings for the symbolization and callstack
> sampling tool, Perfetto:
> - Init time: ~350x speedup (155.7ms -> 0.44ms)
> - 50k symbolizations: ~12x speedup (25.2ms -> 2.2ms)
> - VmRSS: -68% (15.1MB -> 4.8MB)
>
> Suggested-by: Andrii Nakryiko <andrii@kernel.org>
> Signed-off-by: Samuel Wu <wusamuel@google.com>
> ---
> include/uapi/linux/fs.h | 41 +++++++++++++
> kernel/kallsyms.c | 132 ++++++++++++++++++++++++++++++++++++++++
> 2 files changed, 173 insertions(+)
>
> diff --git a/include/uapi/linux/fs.h b/include/uapi/linux/fs.h
> index 34c6f219462a..b43f227dad0e 100644
> --- a/include/uapi/linux/fs.h
> +++ b/include/uapi/linux/fs.h
> @@ -674,6 +674,47 @@ struct procmap_query {
> __u64 build_id_addr; /* in */
> };
>
> +/* /proc/kallsyms ioctl */
> +#define KALLSYMS_QUERY _IOWR(PROCFS_IOCTL_MAGIC, 18, struct kallsyms_query)
> +
> +/* Exactly one query mode must be set in query_flags. */
> +enum kallsyms_query_flags {
> + /*
> + * Look up a symbol in vmlinux or loaded modules (vmlinux first; BPF
> + * and ftrace symbols are not searched) by exact NUL-terminated name
> + * at sym_name_addr and return sym_addr. sym_addr and sym_name_size
> + * must be 0 on input.
> + */
> + KALLSYMS_QUERY_BY_SYMBOL = 0x01,
> + /*
> + * Look up the symbol covering sym_addr and write its NUL-terminated
> + * name to the sym_name_addr buffer of sym_name_size bytes. The same
> + * symbols as %pS are searched: vmlinux, loaded modules, BPF JIT
> + * images, and freed module init functions still tracked by ftrace.
> + * A buffer of KSYM_NAME_LEN (512 at the time of writing) bytes is
> + * always enough. On success, sym_name_size is set to the number of
> + * bytes written (including the terminating NUL).
> + */
> + KALLSYMS_QUERY_BY_ADDR = 0x02,
> +};
> +
> +/*
> + * Returns -ENOENT if no symbol matches, -EADDRNOTAVAIL if the name maps to
> + * more than one address, -ENAMETOOLONG if the name does not fit in
> + * KSYM_NAME_LEN (512 at the time of writing) or sym_name_size bytes, and
> + * -EPERM if kallsyms addresses are hidden from the opener of /proc/kallsyms
> + * (e.g. kptr_restrict).
> + */
> +struct kallsyms_query {
> + __u64 size; /* in - struct size for compatibility */
I'm not sure you need a size here as well as the one in the ioctl command.
It only complicates things when they differ.
If you need a new version they you add an extra case to the switch statement.
Simple and easy,
> + __u64 query_flags; /* in - query mode (enum kallsyms_query_flags) */
> + __u64 sym_name_addr; /* in - address of symbol name buffer */
> + __u64 sym_addr; /* out (BY_SYMBOL) / in (BY_ADDR) */
> + __u64 sym_name_size; /* in/out - name buffer size / bytes written */
Maybe separate the buffer size from the length of the symbol written?
The in/out parameter will get annoying in code doing multiple lookups.
Or just have the buffer size and require it to be '\0' terminated.
(There is a strncpy_from_user() can that be used.)
The '\0' terminated string handling is likely to be noise against
all the rest of the lookup code.
David
> +};
> +
> +#define KALLSYMS_QUERY_SIZE_VER0 40 /* sizeof first published struct */
> +
> /*
> * Shutdown the filesystem.
> */
> diff --git a/kernel/kallsyms.c b/kernel/kallsyms.c
> index aec2f06858af..7caaf3e421c7 100644
> --- a/kernel/kallsyms.c
> +++ b/kernel/kallsyms.c
> @@ -31,6 +31,7 @@
> #include <linux/kernel.h>
> #include <linux/bsearch.h>
> #include <linux/btf_ids.h>
> +#include <linux/uaccess.h>
>
> #include "kallsyms_internal.h"
>
> @@ -922,11 +923,142 @@ const char *kdb_walk_kallsyms(loff_t *pos)
> }
> #endif /* CONFIG_KGDB_KDB */
>
> +struct kallsyms_mod_find_ctx {
> + const char *name;
> + unsigned long addr;
> + bool found;
> +};
> +
> +static int kallsyms_mod_find_cb(void *data, const char *name, unsigned long addr)
> +{
> + struct kallsyms_mod_find_ctx *ctx = data;
> +
> + if (strcmp(name, ctx->name) != 0)
> + return 0;
> + if (ctx->found && ctx->addr != addr)
> + return -EADDRNOTAVAIL;
> + ctx->found = true;
> + ctx->addr = addr;
> + return 0;
> +}
> +
> +static int do_kallsyms_query(struct kallsym_iter *iter, void __user *uarg)
> +{
> + char namebuf[KSYM_NAME_LEN];
> + struct kallsyms_query karg;
> + unsigned long addr;
> + unsigned int start;
> + unsigned int end;
> + unsigned int i;
> + __u64 usize;
> + long len;
> + int err;
> +
> + BUILD_BUG_ON(sizeof(struct kallsyms_query) != KALLSYMS_QUERY_SIZE_VER0);
> +
> + /*
> + * The text interface shows zero addresses to openers without permission.
> + * A zeroed point query result is useless (and an address-to-symbol query
> + * would leak KASLR), so fail instead.
> + */
> + if (!iter->show_value)
> + return -EPERM;
> + if (copy_from_user(&usize, uarg, sizeof(usize)))
> + return -EFAULT;
> + if (usize > PAGE_SIZE)
> + return -E2BIG;
> + if (usize < KALLSYMS_QUERY_SIZE_VER0)
> + return -EINVAL;
> +
> + err = copy_struct_from_user(&karg, sizeof(karg), uarg, usize);
> + if (err)
> + return err;
> +
> + switch (karg.query_flags) {
> + case KALLSYMS_QUERY_BY_SYMBOL:
> + if (karg.sym_addr || karg.sym_name_size)
> + return -EINVAL;
> +
> + len = strncpy_from_user(namebuf, u64_to_user_ptr(karg.sym_name_addr),
> + sizeof(namebuf));
> + if (len < 0)
> + return len;
> + if (len == 0)
> + return -EINVAL;
> + if (len == sizeof(namebuf))
> + return -ENAMETOOLONG;
> +
> + if (kallsyms_lookup_names(namebuf, &start, &end)) {
> + struct kallsyms_mod_find_ctx ctx = { .name = namebuf };
> +
> + err = module_kallsyms_on_each_symbol(NULL, kallsyms_mod_find_cb, &ctx);
> + /* Check first: maps -EOPNOTSUPP (CONFIG_MODULES=n) to -ENOENT. */
> + if (!ctx.found)
> + return -ENOENT;
> + if (err)
> + return err;
> + addr = ctx.addr;
> + } else {
> + addr = kallsyms_sym_address(get_symbol_seq(start));
> + for (i = start + 1; i <= end; i++) {
> + if (kallsyms_sym_address(get_symbol_seq(i)) != addr)
> + return -EADDRNOTAVAIL;
> + }
> + }
> +
> + karg.sym_addr = addr;
> + break;
> + case KALLSYMS_QUERY_BY_ADDR:
> + if (!karg.sym_name_size)
> + return -EINVAL;
> + addr = karg.sym_addr;
> + /* Reject addresses that do not fit in unsigned long (32-bit). */
> + if (addr != karg.sym_addr)
> + return -ENOENT;
> + if (!kallsyms_lookup(addr, NULL, NULL, NULL, namebuf))
> + return -ENOENT;
> +
> + len = strlen(namebuf) + 1;
> + if (karg.sym_name_size < len)
> + return -ENAMETOOLONG;
> + if (copy_to_user(u64_to_user_ptr(karg.sym_name_addr), namebuf, len))
> + return -EFAULT;
> +
> + karg.sym_name_size = len;
> + break;
> + default:
> + return -EINVAL;
> + }
> +
> + if (copy_to_user(uarg, &karg, min_t(size_t, sizeof(karg), usize)))
> + return -EFAULT;
> +
> + return 0;
> +}
> +
> +static long kallsyms_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
> +{
> + struct seq_file *m = file->private_data;
> +
> + switch (_IOC_NR(cmd)) {
> + case _IOC_NR(KALLSYMS_QUERY):
> + if (!extensible_ioctl_valid(cmd, KALLSYMS_QUERY, KALLSYMS_QUERY_SIZE_VER0))
> + return -ENOIOCTLCMD;
> + return do_kallsyms_query(m->private, (void __user *)arg);
> + default:
> + return -ENOIOCTLCMD;
> + }
> +}
> +
> static const struct proc_ops kallsyms_proc_ops = {
> .proc_open = kallsyms_open,
> .proc_read = seq_read,
> .proc_lseek = seq_lseek,
> .proc_release = seq_release_private,
> + .proc_ioctl = kallsyms_ioctl,
> +#ifdef CONFIG_COMPAT
> + .proc_compat_ioctl = compat_ptr_ioctl,
> +#endif
> };
>
> static int __init kallsyms_init(void)
^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH bpf-next v2 2/6] tools: Copy KALLSYMS_QUERY definitions to linux/fs.h
2026-10-23 0:16 [PATCH bpf-next v2 0/6] kallsyms: Add KALLSYMS_QUERY ioctl to /proc/kallsyms Samuel Wu
2026-10-23 0:16 ` [PATCH bpf-next v2 1/6] " Samuel Wu
@ 2026-10-23 0:16 ` Samuel Wu
2026-10-23 0:16 ` [PATCH bpf-next v2 3/6] docs/procfs: Document KALLSYMS_QUERY ioctl on /proc/kallsyms Samuel Wu
` (3 subsequent siblings)
5 siblings, 0 replies; 20+ messages in thread
From: Samuel Wu @ 2026-10-23 0:16 UTC (permalink / raw)
To: Jonathan Corbet, Shuah Khan, Randy Dunlap, Alexander Viro,
Christian Brauner, Jan Kara, Andrii Nakryiko, Eduard Zingerman,
Ihor Solodrai, Alexei Starovoitov, Daniel Borkmann,
Kumar Kartikeya Dwivedi, Martin KaFai Lau, Song Liu,
Yonghong Song, Jiri Olsa, Emil Tsalapatis, Nathan Chancellor,
Nick Desaulniers, Bill Wendling, Justin Stitt
Cc: Samuel Wu, kernel-team, linux-kernel, linux-fsdevel, linux-doc,
bpf, linux-kselftest, llvm
Copy the KALLSYMS_QUERY ioctl definitions from include/uapi/linux/fs.h
to tools/include/uapi/linux/fs.h for libbpf and the proc and bpf
selftests. Only the new definitions are copied; other differences
between the two headers are left alone.
Signed-off-by: Samuel Wu <wusamuel@google.com>
---
tools/include/uapi/linux/fs.h | 41 +++++++++++++++++++++++++++++++++++
1 file changed, 41 insertions(+)
diff --git a/tools/include/uapi/linux/fs.h b/tools/include/uapi/linux/fs.h
index f0a26309b6d5..d224516e6d6e 100644
--- a/tools/include/uapi/linux/fs.h
+++ b/tools/include/uapi/linux/fs.h
@@ -567,4 +567,45 @@ struct procmap_query {
__u64 build_id_addr; /* in */
};
+/* /proc/kallsyms ioctl */
+#define KALLSYMS_QUERY _IOWR(PROCFS_IOCTL_MAGIC, 18, struct kallsyms_query)
+
+/* Exactly one query mode must be set in query_flags. */
+enum kallsyms_query_flags {
+ /*
+ * Look up a symbol in vmlinux or loaded modules (vmlinux first; BPF
+ * and ftrace symbols are not searched) by exact NUL-terminated name
+ * at sym_name_addr and return sym_addr. sym_addr and sym_name_size
+ * must be 0 on input.
+ */
+ KALLSYMS_QUERY_BY_SYMBOL = 0x01,
+ /*
+ * Look up the symbol covering sym_addr and write its NUL-terminated
+ * name to the sym_name_addr buffer of sym_name_size bytes. The same
+ * symbols as %pS are searched: vmlinux, loaded modules, BPF JIT
+ * images, and freed module init functions still tracked by ftrace.
+ * A buffer of KSYM_NAME_LEN (512 at the time of writing) bytes is
+ * always enough. On success, sym_name_size is set to the number of
+ * bytes written (including the terminating NUL).
+ */
+ KALLSYMS_QUERY_BY_ADDR = 0x02,
+};
+
+/*
+ * Returns -ENOENT if no symbol matches, -EADDRNOTAVAIL if the name maps to
+ * more than one address, -ENAMETOOLONG if the name does not fit in
+ * KSYM_NAME_LEN (512 at the time of writing) or sym_name_size bytes, and
+ * -EPERM if kallsyms addresses are hidden from the opener of /proc/kallsyms
+ * (e.g. kptr_restrict).
+ */
+struct kallsyms_query {
+ __u64 size; /* in - struct size for compatibility */
+ __u64 query_flags; /* in - query mode (enum kallsyms_query_flags) */
+ __u64 sym_name_addr; /* in - address of symbol name buffer */
+ __u64 sym_addr; /* out (BY_SYMBOL) / in (BY_ADDR) */
+ __u64 sym_name_size; /* in/out - name buffer size / bytes written */
+};
+
+#define KALLSYMS_QUERY_SIZE_VER0 40 /* sizeof first published struct */
+
#endif /* _UAPI_LINUX_FS_H */
--
2.56.0.257.gf7497c7d27-goog
^ permalink raw reply related [flat|nested] 20+ messages in thread* [PATCH bpf-next v2 3/6] docs/procfs: Document KALLSYMS_QUERY ioctl on /proc/kallsyms
2026-10-23 0:16 [PATCH bpf-next v2 0/6] kallsyms: Add KALLSYMS_QUERY ioctl to /proc/kallsyms Samuel Wu
2026-10-23 0:16 ` [PATCH bpf-next v2 1/6] " Samuel Wu
2026-10-23 0:16 ` [PATCH bpf-next v2 2/6] tools: Copy KALLSYMS_QUERY definitions to linux/fs.h Samuel Wu
@ 2026-10-23 0:16 ` Samuel Wu
2026-10-23 0:16 ` [PATCH bpf-next v2 4/6] libbpf: Resolve typeless ksyms via KALLSYMS_QUERY ioctl Samuel Wu
` (2 subsequent siblings)
5 siblings, 0 replies; 20+ messages in thread
From: Samuel Wu @ 2026-10-23 0:16 UTC (permalink / raw)
To: Jonathan Corbet, Shuah Khan, Randy Dunlap, Alexander Viro,
Christian Brauner, Jan Kara, Andrii Nakryiko, Eduard Zingerman,
Ihor Solodrai, Alexei Starovoitov, Daniel Borkmann,
Kumar Kartikeya Dwivedi, Martin KaFai Lau, Song Liu,
Yonghong Song, Jiri Olsa, Emil Tsalapatis, Nathan Chancellor,
Nick Desaulniers, Bill Wendling, Justin Stitt
Cc: Samuel Wu, kernel-team, linux-kernel, linux-fsdevel, linux-doc,
bpf, linux-kselftest, llvm
Document the existence of the KALLSYMS_QUERY ioctl() in the section
describing the /proc/kallsyms file. Refer the user to the UAPI header
for low-level details of this programmatic interface. Add a "kallsyms"
row to the /proc file table.
Signed-off-by: Samuel Wu <wusamuel@google.com>
---
Documentation/filesystems/proc.rst | 15 +++++++++++++++
1 file changed, 15 insertions(+)
diff --git a/Documentation/filesystems/proc.rst b/Documentation/filesystems/proc.rst
index c102b62023cd..a0e2fe14a01a 100644
--- a/Documentation/filesystems/proc.rst
+++ b/Documentation/filesystems/proc.rst
@@ -761,6 +761,7 @@ files are there, and which are missing.
ioports I/O port usage
irq Masks for irq to cpu affinity (2.4)(smp?)
isapnp ISA PnP (Plug&Play) Info (2.4)
+ kallsyms Kernel symbol table with dynamic queries (see text)
kcore Kernel core image (can be ELF or A.OUT(deprecated in 2.4))
kmsg Kernel messages
ksyms Kernel symbol table
@@ -1368,6 +1369,20 @@ Provides counts of softirq handlers serviced since boot time, for each CPU.
HRTIMER: 0 0 0 0
RCU: 1678 1769 2178 2250
+
+kallsyms
+~~~~~~~~
+
+The /proc/kallsyms file lists all symbols in vmlinux and loaded modules.
+
+In addition to sequential text reading, /proc/kallsyms provides an ioctl()-based
+API (`KALLSYMS_QUERY`) for querying specific symbols and addresses. This
+interface is binary and is meant for efficient extraction of data without
+parsing the entire text file. `struct kallsyms_query`, defined in the
+linux/fs.h UAPI header, serves as the input/output argument to the
+`KALLSYMS_QUERY` ioctl() command. See comments in the linux/fs.h UAPI header
+for details on query semantics, supported flags, and return values.
+
1.3 Networking info in /proc/net
--------------------------------
--
2.56.0.257.gf7497c7d27-goog
^ permalink raw reply related [flat|nested] 20+ messages in thread* [PATCH bpf-next v2 4/6] libbpf: Resolve typeless ksyms via KALLSYMS_QUERY ioctl
2026-10-23 0:16 [PATCH bpf-next v2 0/6] kallsyms: Add KALLSYMS_QUERY ioctl to /proc/kallsyms Samuel Wu
` (2 preceding siblings ...)
2026-10-23 0:16 ` [PATCH bpf-next v2 3/6] docs/procfs: Document KALLSYMS_QUERY ioctl on /proc/kallsyms Samuel Wu
@ 2026-10-23 0:16 ` Samuel Wu
2026-10-23 0:51 ` bot+bpf-ci
2026-10-26 9:28 ` Jiri Olsa
2026-10-23 0:16 ` [PATCH bpf-next v2 5/6] selftests/bpf: Test typeless module ksym resolution Samuel Wu
2026-10-23 0:16 ` [PATCH bpf-next v2 6/6] selftests/proc: Add KALLSYMS_QUERY tests Samuel Wu
5 siblings, 2 replies; 20+ messages in thread
From: Samuel Wu @ 2026-10-23 0:16 UTC (permalink / raw)
To: Jonathan Corbet, Shuah Khan, Randy Dunlap, Alexander Viro,
Christian Brauner, Jan Kara, Andrii Nakryiko, Eduard Zingerman,
Ihor Solodrai, Alexei Starovoitov, Daniel Borkmann,
Kumar Kartikeya Dwivedi, Martin KaFai Lau, Song Liu,
Yonghong Song, Jiri Olsa, Emil Tsalapatis, Nathan Chancellor,
Nick Desaulniers, Bill Wendling, Justin Stitt
Cc: Samuel Wu, kernel-team, linux-kernel, linux-fsdevel, linux-doc,
bpf, linux-kselftest, llvm
When resolving typeless extern ksyms (!ext->ksym.type_id), libbpf
currently reads and parses the entire /proc/kallsyms text file via
libbpf_kallsyms_parse(). The kallsyms text file is commonly and quite
easily several MB, which means with 4kB page sizes, parsing the file
requires a few thousand read() syscalls. This is inefficient, especially
in the common case when only a few symbols are needed.
Add bpf_object__query_ksyms() to resolve typeless extern ksyms via the
KALLSYMS_QUERY ioctl on /proc/kallsyms. Modules are still searched if a
symbol is not found in vmlinux, and ambiguous symbols are still an
error. The lookup follows kallsyms_lookup_name() semantics, so there
are two differences from parsing the text file:
- A name that exists in both vmlinux and a module resolves to the
vmlinux symbol, instead of failing as ambiguous.
- Names are matched exactly, so an extern does not match a
<name>.llvm.<hash> symbol on CONFIG_LTO_CLANG kernels.
Fall back to reading and parsing the full text kallsyms only if the
ioctl is not accessible: -ENOTTY on older kernels without the ioctl, or
-EPERM if kallsyms addresses are hidden from the opener (e.g.
kptr_restrict). A symbol that is not found is left unresolved, so a
missing strong extern still fails with -ESRCH and a missing weak extern
still defaults to zero, without a full text scan.
Signed-off-by: Samuel Wu <wusamuel@google.com>
---
tools/lib/bpf/libbpf.c | 78 ++++++++++++++++++++++++++++++++++++++++--
1 file changed, 76 insertions(+), 2 deletions(-)
diff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c
index 8fcb8cf39cc0..0e94d2335bd7 100644
--- a/tools/lib/bpf/libbpf.c
+++ b/tools/lib/bpf/libbpf.c
@@ -31,6 +31,7 @@
#include <linux/bpf.h>
#include <linux/btf.h>
#include <linux/filter.h>
+#include <linux/fs.h>
#include <linux/limits.h>
#include <linux/perf_event.h>
#include <linux/bpf_perf_event.h>
@@ -59,6 +60,23 @@
#define BPF_FS_MAGIC 0xcafe4a11
#endif
+#ifndef KALLSYMS_QUERY
+/* 'f' corresponds to PROCFS_IOCTL_MAGIC (include/uapi/linux/fs.h) */
+#define KALLSYMS_QUERY _IOWR('f', 18, struct kallsyms_query)
+
+enum kallsyms_query_flags {
+ KALLSYMS_QUERY_BY_SYMBOL = 0x01,
+};
+
+struct kallsyms_query {
+ __u64 size;
+ __u64 query_flags;
+ __u64 sym_name_addr;
+ __u64 sym_addr;
+ __u64 sym_name_size;
+};
+#endif
+
#define MAX_EVENT_NAME_LEN 64
#define BPF_FS_DEFAULT_PATH "/sys/fs/bpf"
@@ -9644,8 +9662,64 @@ static int kallsyms_cb(unsigned long long sym_addr, char sym_type,
return 0;
}
-static int bpf_object__read_kallsyms_file(struct bpf_object *obj)
+static int bpf_object__query_ksyms(struct bpf_object *obj)
+{
+ struct kallsyms_query q;
+ struct extern_desc *ext;
+ int err = 0;
+ int fd;
+ int i;
+
+ fd = open("/proc/kallsyms", O_RDONLY | O_CLOEXEC);
+ if (fd < 0)
+ return -errno;
+
+ for (i = 0; i < obj->nr_extern; i++) {
+ ext = &obj->externs[i];
+ if (ext->type != EXT_KSYM || ext->ksym.type_id)
+ continue;
+
+ q = (struct kallsyms_query){
+ .size = sizeof(q),
+ .query_flags = KALLSYMS_QUERY_BY_SYMBOL,
+ .sym_name_addr = ptr_to_u64(ext->name),
+ };
+
+ if (ioctl(fd, KALLSYMS_QUERY, &q) < 0) {
+ err = -errno;
+ if (err == -ENOENT) {
+ err = 0;
+ continue;
+ }
+ if (err == -EADDRNOTAVAIL)
+ pr_warn("extern (ksym) '%s': resolution is ambiguous\n",
+ ext->name);
+ break;
+ }
+ ext->is_set = true;
+ ext->ksym.addr = q.sym_addr;
+ pr_debug("extern (ksym) '%s': resolved in kernel to 0x%llx\n",
+ ext->name, ext->ksym.addr);
+ }
+ close(fd);
+ return err;
+}
+
+static int bpf_object__resolve_typeless_ksyms(struct bpf_object *obj)
{
+ int err;
+
+ err = bpf_object__query_ksyms(obj);
+ /*
+ * Fall back to parsing /proc/kallsyms only if the ioctl is not
+ * accessible: not present (-ENOTTY on older kernels) or kallsyms
+ * addresses hidden from this opener (-EPERM, e.g. kptr_restrict).
+ */
+ if (err != -ENOTTY && err != -EPERM)
+ return err;
+
+ pr_debug("KALLSYMS_QUERY unavailable (%s), falling back to /proc/kallsyms\n",
+ errstr(err));
return libbpf_kallsyms_parse(kallsyms_cb, obj);
}
@@ -9922,7 +9996,7 @@ static int bpf_object__resolve_externs(struct bpf_object *obj,
return -EINVAL;
}
if (need_kallsyms) {
- err = bpf_object__read_kallsyms_file(obj);
+ err = bpf_object__resolve_typeless_ksyms(obj);
if (err)
return -EINVAL;
}
--
2.56.0.257.gf7497c7d27-goog
^ permalink raw reply related [flat|nested] 20+ messages in thread* Re: [PATCH bpf-next v2 4/6] libbpf: Resolve typeless ksyms via KALLSYMS_QUERY ioctl
2026-10-23 0:16 ` [PATCH bpf-next v2 4/6] libbpf: Resolve typeless ksyms via KALLSYMS_QUERY ioctl Samuel Wu
@ 2026-10-23 0:51 ` bot+bpf-ci
2026-10-27 21:45 ` Samuel Wu
2026-10-26 9:28 ` Jiri Olsa
1 sibling, 1 reply; 20+ messages in thread
From: bot+bpf-ci @ 2026-10-23 0:51 UTC (permalink / raw)
To: wusamuel, corbet, skhan, rdunlap, viro, brauner, jack, andrii,
eddyz87, ihor.solodrai, ast, daniel, memxor, martin.lau, song,
yonghong.song, jolsa, emil, nathan, ndesaulniers, morbo,
justinstitt
Cc: wusamuel, kernel-team, linux-kernel, linux-fsdevel, linux-doc,
bpf, linux-kselftest, llvm, ast, andrii, daniel, martin.lau,
eddyz87, yonghong.song, mason, ihor.solodrai
[-- Attachment #1: Type: text/plain, Size: 6001 bytes --]
> libbpf: Resolve typeless ksyms via KALLSYMS_QUERY ioctl
TL;DR:
- Possible regression: ioctl lookup ignores .llvm. suffixed symbols, so
typeless ksyms may fail to load or resolve to 0 on LTO kernels
- Fallback to kallsyms parsing may miss -EACCES (SELinux/seccomp), failing
loads that worked before
> diff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c
> index 8fcb8cf39cc0..0e94d2335bd7 100644
> --- a/tools/lib/bpf/libbpf.c
> +++ b/tools/lib/bpf/libbpf.c
[ ... ]
> @@ -9644,8 +9662,64 @@ static int kallsyms_cb(unsigned long long sym_addr, char sym_type,
> return 0;
> }
>
> -static int bpf_object__read_kallsyms_file(struct bpf_object *obj)
> +static int bpf_object__query_ksyms(struct bpf_object *obj)
> +{
> + struct kallsyms_query q;
> + struct extern_desc *ext;
> + int err = 0;
> + int fd;
> + int i;
> +
> + fd = open("/proc/kallsyms", O_RDONLY | O_CLOEXEC);
> + if (fd < 0)
> + return -errno;
> +
> + for (i = 0; i < obj->nr_extern; i++) {
> + ext = &obj->externs[i];
> + if (ext->type != EXT_KSYM || ext->ksym.type_id)
> + continue;
> +
> + q = (struct kallsyms_query){
> + .size = sizeof(q),
> + .query_flags = KALLSYMS_QUERY_BY_SYMBOL,
> + .sym_name_addr = ptr_to_u64(ext->name),
> + };
> +
> + if (ioctl(fd, KALLSYMS_QUERY, &q) < 0) {
> + err = -errno;
> + if (err == -ENOENT) {
> + err = 0;
> + continue;
> + }
> + if (err == -EADDRNOTAVAIL)
> + pr_warn("extern (ksym) '%s': resolution is ambiguous\n",
> + ext->name);
> + break;
> + }
Can this regress typeless ksyms on CONFIG_LTO_CLANG kernels? The old
kallsyms_cb() deliberately matched data symbols that carry a suffix:
res = strstr(sym_name, ".llvm.");
if (sym_type == 'd' && res)
ext = find_extern_by_name_with_len(obj, sym_name, res - sym_name);
That was added by commit c56e59776f46 ("libbpf: Handle <orig_name>.llvm.<hash>
symbol properly") after test_ksyms failed on a CONFIG_LTO_CLANG_THIN kernel,
where bpf_link_fops had been renamed to bpf_link_fops.llvm.<hash>. That
commit also lists tk_core, fake_dst_ops and __fs_reclaim_map as real
renamed symbols.
On the kernel side, KALLSYMS_QUERY only matches names exactly:
kallsyms_lookup_names() does a strcmp() on the expanded name, and
kallsyms_mod_find_cb() also uses strcmp(). So a lookup for "bpf_link_fops"
returns -ENOENT when only "bpf_link_fops.llvm.<hash>" exists.
Here -ENOENT is treated as "not found", err is cleared and the loop
continues. bpf_object__resolve_typeless_ksyms() then falls back only on
-ENOTTY or -EPERM, so libbpf_kallsyms_parse() is never reached. On a
kernel that has this ioctl:
- a strong "extern const void foo __ksym;" now makes bpf_object__load()
fail with "extern 'foo' (strong): not resolved" (-ESRCH), where it
loaded before
- a weak one silently gets address 0 instead of the real address
The selftests helper kallsyms_find() in trace_helpers.c still strips
".llvm." from 'd' symbols, so it and libbpf now disagree about what
test_ksyms (bpf_link_fops) should resolve to.
The commit message notes this difference, but gives no reason for dropping
behavior that libbpf added on purpose. libbpf kept the .llvm. handling
elsewhere to avoid regressions, and commit f8a05692de06 says "we'd like to
not regress".
Would it work to fall back to libbpf_kallsyms_parse() for typeless externs
that are still unresolved after the ioctl pass? That only costs extra time
when a symbol is actually missing. Alternatively, the kernel query could
match the .llvm. suffix for data symbols. I did not find a fix for this in
the rest of the series.
> + ext->is_set = true;
> + ext->ksym.addr = q.sym_addr;
> + pr_debug("extern (ksym) '%s': resolved in kernel to 0x%llx\n",
> + ext->name, ext->ksym.addr);
> + }
> + close(fd);
> + return err;
> +}
> +
> +static int bpf_object__resolve_typeless_ksyms(struct bpf_object *obj)
> {
> + int err;
> +
> + err = bpf_object__query_ksyms(obj);
> + /*
> + * Fall back to parsing /proc/kallsyms only if the ioctl is not
> + * accessible: not present (-ENOTTY on older kernels) or kallsyms
> + * addresses hidden from this opener (-EPERM, e.g. kptr_restrict).
> + */
> + if (err != -ENOTTY && err != -EPERM)
> + return err;
Does this fallback miss other ways the ioctl can be blocked while reading
/proc/kallsyms is still allowed?
The old code never issued an ioctl on this file. Now every KALLSYMS_QUERY
goes through security_file_ioctl(). For this command selinux_file_ioctl()
takes the default case and calls ioctl_has_perm(cred, file, FILE__IOCTL,
(u16)cmd), which returns -EACCES when the policy grants read on the
kallsyms label but not ioctl, or limits ioctl commands with allowxperm. A
seccomp filter that makes ioctl() return an errno other than EPERM has the
same effect.
In those setups bpf_object__query_ksyms() returns -EACCES (or similar),
bpf_object__resolve_typeless_ksyms() returns it as-is, and
bpf_object__resolve_externs() turns it into -EINVAL:
err = bpf_object__resolve_typeless_ksyms(obj);
if (err)
return -EINVAL;
So an object with any typeless ksym now fails to load, even though parsing
the text file would have worked as it did before this patch.
The failure is also hard to diagnose. Apart from -EADDRNOTAVAIL, nothing
from the ioctl or from open() is reported. The open() failure path is a
bare:
fd = open("/proc/kallsyms", O_RDONLY | O_CLOEXEC);
if (fd < 0)
return -errno;
without the "failed to open /proc/kallsyms" pr_warn() that
libbpf_kallsyms_parse() prints, so the user only sees "failed to load
object".
Should the fallback cover any error other than -EADDRNOTAVAIL (and other
real lookup errors), or at least -EACCES as well, with a pr_warn() for the
errors that are not handled?
---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md
CI run summary: https://github.com/kernel-patches/bpf/actions/runs/39878338337
^ permalink raw reply [flat|nested] 20+ messages in thread* Re: [PATCH bpf-next v2 4/6] libbpf: Resolve typeless ksyms via KALLSYMS_QUERY ioctl
2026-10-23 0:51 ` bot+bpf-ci
@ 2026-10-27 21:45 ` Samuel Wu
0 siblings, 0 replies; 20+ messages in thread
From: Samuel Wu @ 2026-10-27 21:45 UTC (permalink / raw)
To: bot+bpf-ci
Cc: corbet, skhan, rdunlap, viro, brauner, jack, andrii, eddyz87,
ihor.solodrai, ast, daniel, memxor, martin.lau, song,
yonghong.song, jolsa, emil, nathan, ndesaulniers, morbo,
justinstitt, kernel-team, linux-kernel, linux-fsdevel, linux-doc,
bpf, linux-kselftest, llvm, martin.lau, mason
On Thu, Oct 22, 2026 at 5:51 PM <bot+bpf-ci@kernel.org> wrote:
[...]
> Would it work to fall back to libbpf_kallsyms_parse() for typeless externs
> that are still unresolved after the ioctl pass? That only costs extra time
> when a symbol is actually missing. Alternatively, the kernel query could
> match the .llvm. suffix for data symbols. I did not find a fix for this in
> the rest of the series.
Yes, for strong externs. In v3, if a strong extern returns -ENOENT,
I'll fallback to /proc/kallsyms to scanning .llvm.<hash> suffix,
keeping the same semantics as before.
Weak misses must default to zero without falling back to a scan.
Otherwise, all objects marked __weak would pay the full /proc/kallsyms
scanning cost. This is a slight semantic change, as a weak symbol with
a ThinLTO-renamed static now resolves to zero. However, this is the
behavior of the light skeleton (gen_loader.c), so perhaps it's
acceptable. I'll note this in the v3 commit message.
[...]
> Should the fallback cover any error other than -EADDRNOTAVAIL (and other
> real lookup errors), or at least -EACCES as well, with a pr_warn() for the
> errors that are not handled?
I'll update v3 to log pr_warn() and return on open() failure or lookup
errors. And then I think I should reset partial state and fall back to
/proc/kallsyms on ENOTTY, EPERM, EACCES, and EOPNOTSUPP.
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH bpf-next v2 4/6] libbpf: Resolve typeless ksyms via KALLSYMS_QUERY ioctl
2026-10-23 0:16 ` [PATCH bpf-next v2 4/6] libbpf: Resolve typeless ksyms via KALLSYMS_QUERY ioctl Samuel Wu
2026-10-23 0:51 ` bot+bpf-ci
@ 2026-10-26 9:28 ` Jiri Olsa
2026-10-27 22:07 ` Samuel Wu
1 sibling, 1 reply; 20+ messages in thread
From: Jiri Olsa @ 2026-10-26 9:28 UTC (permalink / raw)
To: Samuel Wu
Cc: Jonathan Corbet, Shuah Khan, Randy Dunlap, Alexander Viro,
Christian Brauner, Jan Kara, Andrii Nakryiko, Eduard Zingerman,
Ihor Solodrai, Alexei Starovoitov, Daniel Borkmann,
Kumar Kartikeya Dwivedi, Martin KaFai Lau, Song Liu,
Yonghong Song, Emil Tsalapatis, Nathan Chancellor,
Nick Desaulniers, Bill Wendling, Justin Stitt, kernel-team,
linux-kernel, linux-fsdevel, linux-doc, bpf, linux-kselftest,
llvm
On Thu, Oct 22, 2026 at 05:16:05PM -0700, Samuel Wu wrote:
SNIP
> +static int bpf_object__query_ksyms(struct bpf_object *obj)
> +{
> + struct kallsyms_query q;
> + struct extern_desc *ext;
> + int err = 0;
> + int fd;
> + int i;
> +
> + fd = open("/proc/kallsyms", O_RDONLY | O_CLOEXEC);
> + if (fd < 0)
> + return -errno;
> +
> + for (i = 0; i < obj->nr_extern; i++) {
> + ext = &obj->externs[i];
> + if (ext->type != EXT_KSYM || ext->ksym.type_id)
> + continue;
> +
> + q = (struct kallsyms_query){
> + .size = sizeof(q),
> + .query_flags = KALLSYMS_QUERY_BY_SYMBOL,
> + .sym_name_addr = ptr_to_u64(ext->name),
> + };
> +
> + err = -errno;
> + if (err == -ENOENT) {
> + err = 0;
> + continue;
> + }
> + if (err == -EADDRNOTAVAIL)
> + pr_warn("extern (ksym) '%s': resolution is ambiguous\n",
> + ext->name);
> + break;
> + }
> + ext->is_set = true;
> + ext->ksym.addr = q.sym_addr;
> + pr_debug("extern (ksym) '%s': resolved in kernel to 0x%llx\n",
> + ext->name, ext->ksym.addr);
> + }
> + close(fd);
> + return err;
> +}
> +
> +static int bpf_object__resolve_typeless_ksyms(struct bpf_object *obj)
> {
> + int err;
> +
> + err = bpf_object__query_ksyms(obj);
hi,
we could also add similar support for libbpf_available_kallsyms_parse and
probably in some other places like bpf_get_ksyms.. which makes me think it
could be useful to have KALLSYMS_QUERY support for multiple symbols
thanks,
jirka
> + /*
> + * Fall back to parsing /proc/kallsyms only if the ioctl is not
> + * accessible: not present (-ENOTTY on older kernels) or kallsyms
> + * addresses hidden from this opener (-EPERM, e.g. kptr_restrict).
> + */
> + if (err != -ENOTTY && err != -EPERM)
> + return err;
> +
> + pr_debug("KALLSYMS_QUERY unavailable (%s), falling back to /proc/kallsyms\n",
> + errstr(err));
> return libbpf_kallsyms_parse(kallsyms_cb, obj);
> }
>
> @@ -9922,7 +9996,7 @@ static int bpf_object__resolve_externs(struct bpf_object *obj,
> return -EINVAL;
> }
> if (need_kallsyms) {
> - err = bpf_object__read_kallsyms_file(obj);
> + err = bpf_object__resolve_typeless_ksyms(obj);
> if (err)
> return -EINVAL;
> }
> --
> 2.56.0.257.gf7497c7d27-goog
>
^ permalink raw reply [flat|nested] 20+ messages in thread* Re: [PATCH bpf-next v2 4/6] libbpf: Resolve typeless ksyms via KALLSYMS_QUERY ioctl
2026-10-26 9:28 ` Jiri Olsa
@ 2026-10-27 22:07 ` Samuel Wu
2026-10-28 14:01 ` David Laight
0 siblings, 1 reply; 20+ messages in thread
From: Samuel Wu @ 2026-10-27 22:07 UTC (permalink / raw)
To: Jiri Olsa
Cc: Jonathan Corbet, Shuah Khan, Randy Dunlap, Alexander Viro,
Christian Brauner, Jan Kara, Andrii Nakryiko, Eduard Zingerman,
Ihor Solodrai, Alexei Starovoitov, Daniel Borkmann,
Kumar Kartikeya Dwivedi, Martin KaFai Lau, Song Liu,
Yonghong Song, Emil Tsalapatis, Nathan Chancellor,
Nick Desaulniers, Bill Wendling, Justin Stitt, kernel-team,
linux-kernel, linux-fsdevel, linux-doc, bpf, linux-kselftest,
llvm
On Mon, Oct 26, 2026 at 2:28 AM Jiri Olsa <olsajiri@gmail.com> wrote:
>
> On Thu, Oct 22, 2026 at 05:16:05PM -0700, Samuel Wu wrote:
>
> SNIP
>
> > +static int bpf_object__query_ksyms(struct bpf_object *obj)
> > +{
> > + struct kallsyms_query q;
> > + struct extern_desc *ext;
> > + int err = 0;
> > + int fd;
> > + int i;
> > +
> > + fd = open("/proc/kallsyms", O_RDONLY | O_CLOEXEC);
> > + if (fd < 0)
> > + return -errno;
> > +
> > + for (i = 0; i < obj->nr_extern; i++) {
> > + ext = &obj->externs[i];
> > + if (ext->type != EXT_KSYM || ext->ksym.type_id)
> > + continue;
> > +
> > + q = (struct kallsyms_query){
> > + .size = sizeof(q),
> > + .query_flags = KALLSYMS_QUERY_BY_SYMBOL,
> > + .sym_name_addr = ptr_to_u64(ext->name),
> > + };
> > +
> > + err = -errno;
> > + if (err == -ENOENT) {
> > + err = 0;
> > + continue;
> > + }
> > + if (err == -EADDRNOTAVAIL)
> > + pr_warn("extern (ksym) '%s': resolution is ambiguous\n",
> > + ext->name);
> > + break;
> > + }
> > + ext->is_set = true;
> > + ext->ksym.addr = q.sym_addr;
> > + pr_debug("extern (ksym) '%s': resolved in kernel to 0x%llx\n",
> > + ext->name, ext->ksym.addr);
> > + }
> > + close(fd);
> > + return err;
> > +}
> > +
> > +static int bpf_object__resolve_typeless_ksyms(struct bpf_object *obj)
> > {
> > + int err;
> > +
> > + err = bpf_object__query_ksyms(obj);
>
> hi,
> we could also add similar support for libbpf_available_kallsyms_parse and
> probably in some other places like bpf_get_ksyms.. which makes me think it
> could be useful to have KALLSYMS_QUERY support for multiple symbols
>
> thanks,
> jirka
>
Thanks for lookiing over this Jiri!
Using KALLSYMS_QUERY for libbpf_available_kallsyms_parse() would
theoretically by O(m*log(n)) instead of O(n*log(m)), where m is the
number of tracefs symbols, and n is lines in kallsyms, with n >> m.
But AFAICT, libbpf_available_kallsyms_parse() would only be called in
pre-6.5 kernels, for kernels without commit 83f74441 (ftrace: Show all
functions with addresses in available_filter_functions_addrs); so this
ioctl could never be called.
And regarding bpf_get_ksyms(), it loads all traceable functions and
needs the full syms table, so a /proc/kallsyms should fits better.
I did profile a slightly different form of querying multiple symbols
at once [1], but didn't see much benefit. struct kallsyms_query is
extensible via size and query_flags, so we can update in the future as
needed.
[1]: https://lore.kernel.org/all/CAG2KctozPSev+EXSpHitM3JWfjufQaEjsPJHS_1HJ1wSm7sJ1w@mail.gmail.com/
[...]
^ permalink raw reply [flat|nested] 20+ messages in thread* Re: [PATCH bpf-next v2 4/6] libbpf: Resolve typeless ksyms via KALLSYMS_QUERY ioctl
2026-10-27 22:07 ` Samuel Wu
@ 2026-10-28 14:01 ` David Laight
0 siblings, 0 replies; 20+ messages in thread
From: David Laight @ 2026-10-28 14:01 UTC (permalink / raw)
To: Samuel Wu
Cc: Jiri Olsa, Jonathan Corbet, Shuah Khan, Randy Dunlap,
Alexander Viro, Christian Brauner, Jan Kara, Andrii Nakryiko,
Eduard Zingerman, Ihor Solodrai, Alexei Starovoitov,
Daniel Borkmann, Kumar Kartikeya Dwivedi, Martin KaFai Lau,
Song Liu, Yonghong Song, Emil Tsalapatis, Nathan Chancellor,
Nick Desaulniers, Bill Wendling, Justin Stitt, kernel-team,
linux-kernel, linux-fsdevel, linux-doc, bpf, linux-kselftest,
llvm
On Tue, 27 Oct 2026 15:07:44 -0700
Samuel Wu <wusamuel@google.com> wrote:
> On Mon, Oct 26, 2026 at 2:28 AM Jiri Olsa <olsajiri@gmail.com> wrote:
> >
> > On Thu, Oct 22, 2026 at 05:16:05PM -0700, Samuel Wu wrote:
> >
> > SNIP
> >
> > > +static int bpf_object__query_ksyms(struct bpf_object *obj)
> > > +{
> > > + struct kallsyms_query q;
> > > + struct extern_desc *ext;
> > > + int err = 0;
> > > + int fd;
> > > + int i;
> > > +
> > > + fd = open("/proc/kallsyms", O_RDONLY | O_CLOEXEC);
> > > + if (fd < 0)
> > > + return -errno;
> > > +
> > > + for (i = 0; i < obj->nr_extern; i++) {
> > > + ext = &obj->externs[i];
> > > + if (ext->type != EXT_KSYM || ext->ksym.type_id)
> > > + continue;
> > > +
> > > + q = (struct kallsyms_query){
> > > + .size = sizeof(q),
> > > + .query_flags = KALLSYMS_QUERY_BY_SYMBOL,
> > > + .sym_name_addr = ptr_to_u64(ext->name),
> > > + };
> > > +
> > > + err = -errno;
> > > + if (err == -ENOENT) {
> > > + err = 0;
> > > + continue;
> > > + }
> > > + if (err == -EADDRNOTAVAIL)
> > > + pr_warn("extern (ksym) '%s': resolution is ambiguous\n",
> > > + ext->name);
> > > + break;
> > > + }
> > > + ext->is_set = true;
> > > + ext->ksym.addr = q.sym_addr;
> > > + pr_debug("extern (ksym) '%s': resolved in kernel to 0x%llx\n",
> > > + ext->name, ext->ksym.addr);
> > > + }
> > > + close(fd);
> > > + return err;
> > > +}
> > > +
> > > +static int bpf_object__resolve_typeless_ksyms(struct bpf_object *obj)
> > > {
> > > + int err;
> > > +
> > > + err = bpf_object__query_ksyms(obj);
> >
> > hi,
> > we could also add similar support for libbpf_available_kallsyms_parse and
> > probably in some other places like bpf_get_ksyms.. which makes me think it
> > could be useful to have KALLSYMS_QUERY support for multiple symbols
> >
> > thanks,
> > jirka
> >
>
> Thanks for lookiing over this Jiri!
>
> Using KALLSYMS_QUERY for libbpf_available_kallsyms_parse() would
> theoretically by O(m*log(n)) instead of O(n*log(m)), where m is the
> number of tracefs symbols, and n is lines in kallsyms, with n >> m.
> But AFAICT, libbpf_available_kallsyms_parse() would only be called in
> pre-6.5 kernels, for kernels without commit 83f74441 (ftrace: Show all
> functions with addresses in available_filter_functions_addrs); so this
> ioctl could never be called.
Isn't that code also O(n*log(m)) ?
I've got a patch (I might send it after the merge window) that changes
one of the ftrace functions to look up every symbol by name, not just
when only asked to look up one.
It can also use the 'callback' function to detect duplicates.
That should be massively faster - especially with my ~18 fold improvement
(also waiting for the merge window to end before posting a new
(not much changed) version).
Does look the the code could do with getting the 'symbol name table index'
for the symbol so that it can read the entries sequentially from that
point onwards.
David
>
> And regarding bpf_get_ksyms(), it loads all traceable functions and
> needs the full syms table, so a /proc/kallsyms should fits better.
>
> I did profile a slightly different form of querying multiple symbols
> at once [1], but didn't see much benefit. struct kallsyms_query is
> extensible via size and query_flags, so we can update in the future as
> needed.
>
> [1]: https://lore.kernel.org/all/CAG2KctozPSev+EXSpHitM3JWfjufQaEjsPJHS_1HJ1wSm7sJ1w@mail.gmail.com/
>
> [...]
>
^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH bpf-next v2 5/6] selftests/bpf: Test typeless module ksym resolution
2026-10-23 0:16 [PATCH bpf-next v2 0/6] kallsyms: Add KALLSYMS_QUERY ioctl to /proc/kallsyms Samuel Wu
` (3 preceding siblings ...)
2026-10-23 0:16 ` [PATCH bpf-next v2 4/6] libbpf: Resolve typeless ksyms via KALLSYMS_QUERY ioctl Samuel Wu
@ 2026-10-23 0:16 ` Samuel Wu
2026-10-23 0:16 ` [PATCH bpf-next v2 6/6] selftests/proc: Add KALLSYMS_QUERY tests Samuel Wu
5 siblings, 0 replies; 20+ messages in thread
From: Samuel Wu @ 2026-10-23 0:16 UTC (permalink / raw)
To: Jonathan Corbet, Shuah Khan, Randy Dunlap, Alexander Viro,
Christian Brauner, Jan Kara, Andrii Nakryiko, Eduard Zingerman,
Ihor Solodrai, Alexei Starovoitov, Daniel Borkmann,
Kumar Kartikeya Dwivedi, Martin KaFai Lau, Song Liu,
Yonghong Song, Jiri Olsa, Emil Tsalapatis, Nathan Chancellor,
Nick Desaulniers, Bill Wendling, Justin Stitt
Cc: Samuel Wu, kernel-team, linux-kernel, linux-fsdevel, linux-doc,
bpf, linux-kselftest, llvm
Typeless module ksyms are resolved through kallsyms, but the existing
module ksym tests (ksyms_module, ksym_race) only use typed ksyms, which
are resolved through BTF. With KALLSYMS_QUERY, libbpf resolves module
symbols through the module lookup path of the ioctl.
Add a weak typeless extern for bpf_testmod_ksym_percpu to the ksyms test
and check that its address matches /proc/kallsyms when bpf_testmod is
loaded.
Signed-off-by: Samuel Wu <wusamuel@google.com>
---
tools/testing/selftests/bpf/prog_tests/ksyms.c | 7 +++++++
tools/testing/selftests/bpf/progs/test_ksyms.c | 3 +++
2 files changed, 10 insertions(+)
diff --git a/tools/testing/selftests/bpf/prog_tests/ksyms.c b/tools/testing/selftests/bpf/prog_tests/ksyms.c
index dc7aab532fb1..62760284f746 100644
--- a/tools/testing/selftests/bpf/prog_tests/ksyms.c
+++ b/tools/testing/selftests/bpf/prog_tests/ksyms.c
@@ -11,6 +11,7 @@ void test_ksyms(void)
struct test_ksyms *skel;
struct test_ksyms__data *data;
__u64 link_fops_addr, per_cpu_start_addr;
+ __u64 testmod_ksym_addr;
struct stat st;
__u64 btf_size;
int err;
@@ -48,6 +49,12 @@ void test_ksyms(void)
ASSERT_EQ(data->out__btf_size, btf_size, "btf_size");
ASSERT_EQ(data->out__per_cpu_start, per_cpu_start_addr, "__per_cpu_start");
+ if (env.has_testmod &&
+ ASSERT_OK(kallsyms_find("bpf_testmod_ksym_percpu", &testmod_ksym_addr),
+ "bpf_testmod_ksym_percpu: kallsyms_find"))
+ ASSERT_EQ(data->out__bpf_testmod_ksym, testmod_ksym_addr,
+ "bpf_testmod_ksym_percpu");
+
cleanup:
test_ksyms__destroy(skel);
}
diff --git a/tools/testing/selftests/bpf/progs/test_ksyms.c b/tools/testing/selftests/bpf/progs/test_ksyms.c
index 6c9cbb5a3bdf..69309f2a2d0a 100644
--- a/tools/testing/selftests/bpf/progs/test_ksyms.c
+++ b/tools/testing/selftests/bpf/progs/test_ksyms.c
@@ -9,6 +9,7 @@ __u64 out__bpf_link_fops = -1;
__u64 out__bpf_link_fops1 = -1;
__u64 out__btf_size = -1;
__u64 out__per_cpu_start = -1;
+__u64 out__bpf_testmod_ksym = -1;
extern const void bpf_link_fops __ksym;
extern const void __start_BTF __ksym;
@@ -16,6 +17,7 @@ extern const void __stop_BTF __ksym;
extern const void __per_cpu_start __ksym;
/* non-existing symbol, weak, default to zero */
extern const void bpf_link_fops1 __ksym __weak;
+extern const void bpf_testmod_ksym_percpu __ksym __weak;
SEC("raw_tp/sys_enter")
int handler(const void *ctx)
@@ -25,6 +27,7 @@ int handler(const void *ctx)
out__per_cpu_start = (__u64)&__per_cpu_start;
out__bpf_link_fops1 = (__u64)&bpf_link_fops1;
+ out__bpf_testmod_ksym = (__u64)&bpf_testmod_ksym_percpu;
return 0;
}
--
2.56.0.257.gf7497c7d27-goog
^ permalink raw reply related [flat|nested] 20+ messages in thread* [PATCH bpf-next v2 6/6] selftests/proc: Add KALLSYMS_QUERY tests
2026-10-23 0:16 [PATCH bpf-next v2 0/6] kallsyms: Add KALLSYMS_QUERY ioctl to /proc/kallsyms Samuel Wu
` (4 preceding siblings ...)
2026-10-23 0:16 ` [PATCH bpf-next v2 5/6] selftests/bpf: Test typeless module ksym resolution Samuel Wu
@ 2026-10-23 0:16 ` Samuel Wu
2026-10-23 0:25 ` sashiko-bot
5 siblings, 1 reply; 20+ messages in thread
From: Samuel Wu @ 2026-10-23 0:16 UTC (permalink / raw)
To: Jonathan Corbet, Shuah Khan, Randy Dunlap, Alexander Viro,
Christian Brauner, Jan Kara, Andrii Nakryiko, Eduard Zingerman,
Ihor Solodrai, Alexei Starovoitov, Daniel Borkmann,
Kumar Kartikeya Dwivedi, Martin KaFai Lau, Song Liu,
Yonghong Song, Jiri Olsa, Emil Tsalapatis, Nathan Chancellor,
Nick Desaulniers, Bill Wendling, Justin Stitt
Cc: Samuel Wu, kernel-team, linux-kernel, linux-fsdevel, linux-doc,
bpf, linux-kselftest, llvm
Add a selftest for the KALLSYMS_QUERY ioctl() on /proc/kallsyms.
For symbol-to-address lookup (KALLSYMS_QUERY_BY_SYMBOL), cover valid
lookups, missing symbols (-ENOENT), input validation (-EINVAL, -EFAULT,
-ENAMETOOLONG, -E2BIG), and extensible struct size handling.
For address-to-symbol lookup (KALLSYMS_QUERY_BY_ADDR), cover lookups at
exact symbol start and interior offsets, buffer truncation
(-ENAMETOOLONG), unmapped addresses (-ENOENT), and input validation
(-EINVAL, -EFAULT).
Signed-off-by: Samuel Wu <wusamuel@google.com>
---
tools/testing/selftests/proc/.gitignore | 1 +
tools/testing/selftests/proc/Makefile | 1 +
.../selftests/proc/proc-kallsyms-query.c | 213 ++++++++++++++++++
3 files changed, 215 insertions(+)
create mode 100644 tools/testing/selftests/proc/proc-kallsyms-query.c
diff --git a/tools/testing/selftests/proc/.gitignore b/tools/testing/selftests/proc/.gitignore
index 9c9735570abf..1762c28f524c 100644
--- a/tools/testing/selftests/proc/.gitignore
+++ b/tools/testing/selftests/proc/.gitignore
@@ -4,6 +4,7 @@
/fd-003-kthread
/proc-2-is-kthread
/proc-fsconfig-hidepid
+/proc-kallsyms-query
/proc-loadavg-001
/proc-maps-race
/proc-multiple-procfs
diff --git a/tools/testing/selftests/proc/Makefile b/tools/testing/selftests/proc/Makefile
index a7de2bb6d8be..dc3c28e6b83b 100644
--- a/tools/testing/selftests/proc/Makefile
+++ b/tools/testing/selftests/proc/Makefile
@@ -29,6 +29,7 @@ TEST_GEN_PROGS += setns-sysvipc
TEST_GEN_PROGS += thread-self
TEST_GEN_PROGS += proc-multiple-procfs
TEST_GEN_PROGS += proc-fsconfig-hidepid
+TEST_GEN_PROGS += proc-kallsyms-query
TEST_GEN_PROGS += proc-pidns
include ../lib.mk
diff --git a/tools/testing/selftests/proc/proc-kallsyms-query.c b/tools/testing/selftests/proc/proc-kallsyms-query.c
new file mode 100644
index 000000000000..229cde432d6a
--- /dev/null
+++ b/tools/testing/selftests/proc/proc-kallsyms-query.c
@@ -0,0 +1,213 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Tests for KALLSYMS_QUERY ioctl() on /proc/kallsyms.
+ */
+#undef NDEBUG
+#include <assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
+#include <linux/fs.h>
+
+#include "kselftest.h"
+
+#define KSYM_NAME_LEN 512
+
+struct kallsyms_query_ext {
+ struct kallsyms_query v0;
+ __u64 extra;
+};
+
+static int do_query(int fd, void *q)
+{
+ return ioctl(fd, KALLSYMS_QUERY, q) < 0 ? -errno : 0;
+}
+
+static struct kallsyms_query sym_query(const char *name)
+{
+ return (struct kallsyms_query){
+ .size = sizeof(struct kallsyms_query),
+ .query_flags = KALLSYMS_QUERY_BY_SYMBOL,
+ .sym_name_addr = (__u64)(uintptr_t)name,
+ };
+}
+
+static int query_symbol(int fd, const char *name, struct kallsyms_query *q)
+{
+ *q = sym_query(name);
+ return do_query(fd, q);
+}
+
+static int query_addr(int fd, unsigned long long addr, char *buf,
+ size_t buf_sz, struct kallsyms_query *q)
+{
+ *q = (struct kallsyms_query){
+ .size = sizeof(*q),
+ .query_flags = KALLSYMS_QUERY_BY_ADDR,
+ .sym_name_addr = (__u64)(uintptr_t)buf,
+ .sym_addr = addr,
+ .sym_name_size = buf_sz,
+ };
+ return do_query(fd, q);
+}
+
+static bool find_vmlinux_symbol(const char *target, unsigned long long *addr_out)
+{
+ char name[KSYM_NAME_LEN];
+ char line[1024];
+ char type;
+ char mod;
+ FILE *f;
+
+ f = fopen("/proc/kallsyms", "r");
+ if (!f)
+ return false;
+
+ while (fgets(line, sizeof(line), f)) {
+ if (sscanf(line, "%llx %c %511s %c", addr_out, &type, name, &mod) == 3 &&
+ strcmp(name, target) == 0) {
+ fclose(f);
+ return true;
+ }
+ }
+ fclose(f);
+ return false;
+}
+
+int main(void)
+{
+ const char *sym_name = "vfs_read";
+ char resolved_name[KSYM_NAME_LEN];
+ char long_name[KSYM_NAME_LEN + 1];
+ size_t page_size = sysconf(_SC_PAGESIZE);
+ unsigned long long exp_addr;
+ struct kallsyms_query_ext q1;
+ struct kallsyms_query q;
+ char *page_buf;
+ int err;
+ int fd;
+
+ fd = open("/proc/kallsyms", O_RDONLY | O_CLOEXEC);
+ if (fd < 0) {
+ if (errno == ENOENT)
+ return KSFT_SKIP;
+ ksft_exit_fail_msg("open(/proc/kallsyms) failed: %s\n",
+ strerror(errno));
+ }
+
+ if (!find_vmlinux_symbol(sym_name, &exp_addr)) {
+ close(fd);
+ return KSFT_SKIP;
+ }
+
+ /* CASE 1: Valid vmlinux symbol query (or -EPERM when addresses are hidden) */
+ err = query_symbol(fd, sym_name, &q);
+ if (err == -ENOTTY) {
+ close(fd);
+ return KSFT_SKIP;
+ }
+ if (!exp_addr) {
+ assert(err == -EPERM);
+ close(fd);
+ ksft_exit_skip("kallsyms addresses are hidden, only -EPERM checked\n");
+ }
+ assert(err == 0);
+ assert(q.sym_addr == exp_addr);
+
+ /* CASE 2: Missing symbol returns -ENOENT */
+ assert(query_symbol(fd, "__nonexistent_kallsyms_symbol__", &q) == -ENOENT);
+
+ /* CASE 3: Empty symbol name returns -EINVAL */
+ assert(query_symbol(fd, "", &q) == -EINVAL);
+
+ /* CASE 4: NULL sym_name_addr returns -EFAULT */
+ assert(query_symbol(fd, NULL, &q) == -EFAULT);
+
+ /* CASE 5: Symbol name >= KSYM_NAME_LEN returns -ENAMETOOLONG */
+ memset(long_name, 'a', KSYM_NAME_LEN);
+ long_name[KSYM_NAME_LEN] = '\0';
+ assert(query_symbol(fd, long_name, &q) == -ENAMETOOLONG);
+
+ /* CASE 6: Symbol name == KSYM_NAME_LEN - 1 returns -ENOENT */
+ long_name[KSYM_NAME_LEN - 1] = '\0';
+ assert(query_symbol(fd, long_name, &q) == -ENOENT);
+
+ /* CASE 7: Zero, multiple, or unknown query_flags return -EINVAL */
+ q = sym_query(sym_name);
+ q.query_flags = 0;
+ assert(do_query(fd, &q) == -EINVAL);
+ q.query_flags = KALLSYMS_QUERY_BY_SYMBOL | KALLSYMS_QUERY_BY_ADDR;
+ assert(do_query(fd, &q) == -EINVAL);
+ q.query_flags = 1ULL << 63;
+ assert(do_query(fd, &q) == -EINVAL);
+
+ /* CASE 8: Non-zero output fields on input in symbol mode return -EINVAL */
+ q = sym_query(sym_name);
+ q.sym_addr = 1;
+ assert(do_query(fd, &q) == -EINVAL);
+ q = sym_query(sym_name);
+ q.sym_name_size = 1;
+ assert(do_query(fd, &q) == -EINVAL);
+
+ /* CASE 9: size < minimum size returns -EINVAL */
+ q = sym_query(sym_name);
+ q.size = KALLSYMS_QUERY_SIZE_VER0 - 8;
+ assert(do_query(fd, &q) == -EINVAL);
+
+ /* CASE 10: size > PAGE_SIZE returns -E2BIG */
+ q = sym_query(sym_name);
+ q.size = page_size + 8;
+ assert(do_query(fd, &q) == -E2BIG);
+
+ /* CASE 11: Larger extensible struct with zeroed trailing bytes succeeds */
+ q1 = (struct kallsyms_query_ext){ .v0 = sym_query(sym_name) };
+ q1.v0.size = sizeof(q1);
+ assert(do_query(fd, &q1) == 0);
+ assert(q1.v0.sym_addr == exp_addr);
+
+ /*
+ * CASE 12: Larger struct with non-zero trailing bytes returns -E2BIG.
+ * Set the last byte of a page-sized struct so that future fields do
+ * not change the result.
+ */
+ page_buf = calloc(1, page_size);
+ assert(page_buf);
+ q = sym_query(sym_name);
+ q.size = page_size;
+ memcpy(page_buf, &q, sizeof(q));
+ page_buf[page_size - 1] = 1;
+ assert(do_query(fd, page_buf) == -E2BIG);
+ free(page_buf);
+
+ /* CASE 13: Valid address-to-symbol query (exact start + interior offset) */
+ memset(resolved_name, 0, sizeof(resolved_name));
+ assert(query_addr(fd, exp_addr, resolved_name, sizeof(resolved_name), &q) == 0);
+ assert(strcmp(resolved_name, sym_name) == 0);
+ assert(q.sym_addr == exp_addr);
+ assert(q.sym_name_size == strlen(sym_name) + 1);
+
+ memset(resolved_name, 0, sizeof(resolved_name));
+ assert(query_addr(fd, exp_addr + 1, resolved_name, sizeof(resolved_name), &q) == 0);
+ assert(strcmp(resolved_name, sym_name) == 0);
+ assert(q.sym_addr == exp_addr + 1);
+ assert(q.sym_name_size == strlen(sym_name) + 1);
+
+ /* CASE 14: KALLSYMS_QUERY_BY_ADDR with buffer too small returns -ENAMETOOLONG */
+ assert(query_addr(fd, exp_addr, resolved_name, strlen(sym_name), &q) == -ENAMETOOLONG);
+
+ /* CASE 15: KALLSYMS_QUERY_BY_ADDR on unmapped address returns -ENOENT */
+ assert(query_addr(fd, ~0ULL, resolved_name, sizeof(resolved_name), &q) == -ENOENT);
+
+ /* CASE 16: KALLSYMS_QUERY_BY_ADDR invalid inputs return -EINVAL or -EFAULT */
+ assert(query_addr(fd, exp_addr, resolved_name, 0, &q) == -EINVAL);
+ assert(query_addr(fd, exp_addr, NULL, sizeof(resolved_name), &q) == -EFAULT);
+
+ close(fd);
+ return 0;
+}
--
2.56.0.257.gf7497c7d27-goog
^ permalink raw reply related [flat|nested] 20+ messages in thread* Re: [PATCH bpf-next v2 6/6] selftests/proc: Add KALLSYMS_QUERY tests
2026-10-23 0:16 ` [PATCH bpf-next v2 6/6] selftests/proc: Add KALLSYMS_QUERY tests Samuel Wu
@ 2026-10-23 0:25 ` sashiko-bot
2026-10-27 21:36 ` Samuel Wu
0 siblings, 1 reply; 20+ messages in thread
From: sashiko-bot @ 2026-10-23 0:25 UTC (permalink / raw)
To: Samuel Wu; +Cc: bpf
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Medium] selftests/proc: main() bypasses kselftest reporting and ksft_exit_*() wrappers
- [Medium] selftests/proc: main() skips silently with bare KSFT_SKIP and no reason message
--
commit 309c42ade27ce014cdfb65d22ab97d40bf7dcd32
Author: Samuel Wu <wusamuel@google.com>
selftests/proc: Add KALLSYMS_QUERY tests
This commit adds a selftest in tools/testing/selftests/proc/proc-kallsyms-query.c
to exercise symbol-to-address and address-to-symbol lookups via the
kallsyms_query ioctl on /proc/kallsyms.
> diff --git a/tools/testing/selftests/proc/proc-kallsyms-query.c b/tools/testing/selftests/proc/proc-kallsyms-query.c
> new file mode 100644
> index 0000000000000..229cde432d6aa
> --- /dev/null
> +++ b/tools/testing/selftests/proc/proc-kallsyms-query.c
[ ... ]
> +int main(void)
> +{
> + const char *sym_name = "vfs_read";
> + char resolved_name[KSYM_NAME_LEN];
> + char long_name[KSYM_NAME_LEN + 1];
> + size_t page_size = sysconf(_SC_PAGESIZE);
> + unsigned long long exp_addr;
> + struct kallsyms_query_ext q1;
> + struct kallsyms_query q;
> + char *page_buf;
> + int err;
> + int fd;
> +
> + fd = open("/proc/kallsyms", O_RDONLY | O_CLOEXEC);
> + if (fd < 0) {
> + if (errno == ENOENT)
> + return KSFT_SKIP;
> + ksft_exit_fail_msg("open(/proc/kallsyms) failed: %s\n",
> + strerror(errno));
> + }
> +
> + if (!find_vmlinux_symbol(sym_name, &exp_addr)) {
> + close(fd);
> + return KSFT_SKIP;
> + }
> +
> + /* CASE 1: Valid vmlinux symbol query (or -EPERM when addresses are hidden) */
> + err = query_symbol(fd, sym_name, &q);
> + if (err == -ENOTTY) {
> + close(fd);
> + return KSFT_SKIP;
> + }
[Severity: Medium]
In main() in tools/testing/selftests/proc/proc-kallsyms-query.c, when
opening /proc/kallsyms fails because the file does not exist, when
find_vmlinux_symbol() does not find vfs_read, or when query_symbol() fails
because the ioctl is not supported by the running kernel, the function
returns directly without printing a skip message.
Unlike the hidden-address check right below that calls ksft_exit_skip() with
a reason string, returning silently here leaves test logs with no indication
of which prerequisite was missing.
Should these three skip paths call ksft_exit_skip() with a descriptive
reason message instead?
> + if (!exp_addr) {
> + assert(err == -EPERM);
> + close(fd);
> + ksft_exit_skip("kallsyms addresses are hidden, only -EPERM checked\n");
> + }
> + assert(err == 0);
> + assert(q.sym_addr == exp_addr);
[ ... ]
> + /* CASE 16: KALLSYMS_QUERY_BY_ADDR invalid inputs return -EINVAL or -EFAULT */
> + assert(query_addr(fd, exp_addr, resolved_name, 0, &q) == -EINVAL);
> + assert(query_addr(fd, exp_addr, NULL, sizeof(resolved_name), &q) == -EFAULT);
> +
> + close(fd);
> + return 0;
> +}
[Severity: Medium]
Although tools/testing/selftests/proc/proc-kallsyms-query.c includes
kselftest.h and uses ksft_exit_fail_msg() and ksft_exit_skip() in main(),
it never calls ksft_print_header() or ksft_set_plan(), evaluates all 16 test
cases with libc assert(), and exits main() with a bare return 0.
When run under kselftest runners or automated test parsers, this produces no
plan, per-test result lines, or summary output on completion, and aborts the
process via assert() on failure instead of reporting structured test failure
results.
Could main() use the kselftest.h reporting helpers (such as
ksft_print_header(), ksft_set_plan(), ksft_test_result(), and
ksft_exit_pass() or ksft_finished()) or the kselftest_harness.h macros
instead of raw assert() and return 0?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261023001603.560210-8-wusamuel@google.com?part=6
^ permalink raw reply [flat|nested] 20+ messages in thread* Re: [PATCH bpf-next v2 6/6] selftests/proc: Add KALLSYMS_QUERY tests
2026-10-23 0:25 ` sashiko-bot
@ 2026-10-27 21:36 ` Samuel Wu
0 siblings, 0 replies; 20+ messages in thread
From: Samuel Wu @ 2026-10-27 21:36 UTC (permalink / raw)
To: sashiko-reviews; +Cc: bpf
On Thu, Oct 22, 2026 at 5:25 PM <sashiko-bot@kernel.org> wrote:
[ ... ]
> Should these three skip paths call ksft_exit_skip() with a descriptive
> reason message instead?
Yes, I'll update v3 to use ksft_exit_skip() in the three skip paths.
> Could main() use the kselftest.h reporting helpers (such as
> ksft_print_header(), ksft_set_plan(), ksft_test_result(), and
> ksft_exit_pass() or ksft_finished()) or the kselftest_harness.h macros
> instead of raw assert() and return 0?
I kept assert() to match existing tests in selftests/proc/, but I have
no problem switching to these helpers if maintainers prefer that.
^ permalink raw reply [flat|nested] 20+ messages in thread