* [PATCH bpf-next v3 1/9] Documentation: ioctl-number: Add PROCFS_IOCTL_MAGIC range 0x10-0x11
2026-11-13 22:12 [PATCH bpf-next v3 0/9] kallsyms: Add KALLSYMS_QUERY_BY_NAME and KALLSYMS_QUERY_BY_ADDR ioctls Samuel Wu
@ 2026-11-13 22:12 ` Samuel Wu
2026-11-14 2:22 ` Randy Dunlap
2026-11-13 22:12 ` [PATCH bpf-next v3 2/9] module: Add module_kallsyms_find_name() Samuel Wu
` (7 subsequent siblings)
8 siblings, 1 reply; 12+ messages in thread
From: Samuel Wu @ 2026-11-13 22:12 UTC (permalink / raw)
To: Jonathan Corbet, Shuah Khan, Randy Dunlap, Luis Chamberlain,
Petr Pavlu, Daniel Gomez, Sami Tolvanen, Aaron Tomlin,
Alexander Viro, Christian Brauner, Jan Kara, Alexei Starovoitov,
Daniel Borkmann, Andrii Nakryiko, Eduard Zingerman,
Kumar Kartikeya Dwivedi, Martin KaFai Lau, Song Liu,
Yonghong Song, Jiri Olsa, Emil Tsalapatis, Ihor Solodrai,
Nathan Chancellor, Nick Desaulniers, Bill Wendling, Justin Stitt
Cc: Andrew Morton, Petr Mladek, David Laight, Samuel Wu, kernel-team,
linux-kernel, linux-fsdevel, linux-doc, linux-modules, bpf,
linux-kselftest, llvm
PAGEMAP_SCAN (0x10) and PROCMAP_QUERY (0x11) use PROCFS_IOCTL_MAGIC.
ext4 uses the same numbers on other files, so mark the row as a
conflict.
Suggested-by: Randy Dunlap <rdunlap@infradead.org>
Signed-off-by: Samuel Wu <wusamuel@google.com>
---
Documentation/userspace-api/ioctl/ioctl-number.rst | 1 +
1 file changed, 1 insertion(+)
diff --git a/Documentation/userspace-api/ioctl/ioctl-number.rst b/Documentation/userspace-api/ioctl/ioctl-number.rst
index dbe462040999..fbc3537fc9ee 100644
--- a/Documentation/userspace-api/ioctl/ioctl-number.rst
+++ b/Documentation/userspace-api/ioctl/ioctl-number.rst
@@ -248,6 +248,7 @@ Code Seq# Include File Comments
'f' 00-0F fs/ext4/ext4.h conflict!
'f' 00-0F linux/fs.h conflict!
'f' 00-0F fs/ocfs2/ocfs2_fs.h conflict!
+'f' 10-11 linux/fs.h conflict! procfs ioctls (PROCFS_IOCTL_MAGIC)
'f' 13-27 linux/fscrypt.h
'f' 81-8F linux/fsverity.h
'g' 00-0F linux/usb/gadgetfs.h
--
2.56.0.679.gc527c65306-goog
^ permalink raw reply [flat|nested] 12+ messages in thread* Re: [PATCH bpf-next v3 1/9] Documentation: ioctl-number: Add PROCFS_IOCTL_MAGIC range 0x10-0x11
2026-11-13 22:12 ` [PATCH bpf-next v3 1/9] Documentation: ioctl-number: Add PROCFS_IOCTL_MAGIC range 0x10-0x11 Samuel Wu
@ 2026-11-14 2:22 ` Randy Dunlap
0 siblings, 0 replies; 12+ messages in thread
From: Randy Dunlap @ 2026-11-14 2:22 UTC (permalink / raw)
To: Samuel Wu, Jonathan Corbet, Shuah Khan, Luis Chamberlain,
Petr Pavlu, Daniel Gomez, Sami Tolvanen, Aaron Tomlin,
Alexander Viro, Christian Brauner, Jan Kara, Alexei Starovoitov,
Daniel Borkmann, Andrii Nakryiko, Eduard Zingerman,
Kumar Kartikeya Dwivedi, Martin KaFai Lau, Song Liu,
Yonghong Song, Jiri Olsa, Emil Tsalapatis, Ihor Solodrai,
Nathan Chancellor, Nick Desaulniers, Bill Wendling, Justin Stitt
Cc: Andrew Morton, Petr Mladek, David Laight, kernel-team,
linux-kernel, linux-fsdevel, linux-doc, linux-modules, bpf,
linux-kselftest, llvm
On 11/13/26 2:12 PM, Samuel Wu wrote:
> PAGEMAP_SCAN (0x10) and PROCMAP_QUERY (0x11) use PROCFS_IOCTL_MAGIC.
> ext4 uses the same numbers on other files, so mark the row as a
> conflict.
>
> Suggested-by: Randy Dunlap <rdunlap@infradead.org>
> Signed-off-by: Samuel Wu <wusamuel@google.com>
Reviewed-by: Randy Dunlap <rdunlap@infradead.org>
Thanks.
> ---
> Documentation/userspace-api/ioctl/ioctl-number.rst | 1 +
> 1 file changed, 1 insertion(+)
>
> diff --git a/Documentation/userspace-api/ioctl/ioctl-number.rst b/Documentation/userspace-api/ioctl/ioctl-number.rst
> index dbe462040999..fbc3537fc9ee 100644
> --- a/Documentation/userspace-api/ioctl/ioctl-number.rst
> +++ b/Documentation/userspace-api/ioctl/ioctl-number.rst
> @@ -248,6 +248,7 @@ Code Seq# Include File Comments
> 'f' 00-0F fs/ext4/ext4.h conflict!
> 'f' 00-0F linux/fs.h conflict!
> 'f' 00-0F fs/ocfs2/ocfs2_fs.h conflict!
> +'f' 10-11 linux/fs.h conflict! procfs ioctls (PROCFS_IOCTL_MAGIC)
> 'f' 13-27 linux/fscrypt.h
> 'f' 81-8F linux/fsverity.h
> 'g' 00-0F linux/usb/gadgetfs.h
--
~Randy
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH bpf-next v3 2/9] module: Add module_kallsyms_find_name()
2026-11-13 22:12 [PATCH bpf-next v3 0/9] kallsyms: Add KALLSYMS_QUERY_BY_NAME and KALLSYMS_QUERY_BY_ADDR ioctls Samuel Wu
2026-11-13 22:12 ` [PATCH bpf-next v3 1/9] Documentation: ioctl-number: Add PROCFS_IOCTL_MAGIC range 0x10-0x11 Samuel Wu
@ 2026-11-13 22:12 ` Samuel Wu
2026-11-13 22:12 ` [PATCH bpf-next v3 3/9] kallsyms: Add KALLSYMS_QUERY_BY_NAME ioctl to /proc/kallsyms Samuel Wu
` (6 subsequent siblings)
8 siblings, 0 replies; 12+ messages in thread
From: Samuel Wu @ 2026-11-13 22:12 UTC (permalink / raw)
To: Jonathan Corbet, Shuah Khan, Randy Dunlap, Luis Chamberlain,
Petr Pavlu, Daniel Gomez, Sami Tolvanen, Aaron Tomlin,
Alexander Viro, Christian Brauner, Jan Kara, Alexei Starovoitov,
Daniel Borkmann, Andrii Nakryiko, Eduard Zingerman,
Kumar Kartikeya Dwivedi, Martin KaFai Lau, Song Liu,
Yonghong Song, Jiri Olsa, Emil Tsalapatis, Ihor Solodrai,
Nathan Chancellor, Nick Desaulniers, Bill Wendling, Justin Stitt
Cc: Andrew Morton, Petr Mladek, David Laight, Samuel Wu, kernel-team,
linux-kernel, linux-fsdevel, linux-doc, linux-modules, bpf,
linux-kselftest, llvm
KALLSYMS_QUERY_BY_NAME, added later in this series, needs to find a
symbol name or prefix across module symbols in one pass.
module_get_kallsym() walks the module list from the head and runs an
export bsearch() for every symbol index.
Add module_kallsyms_find_name(), and put the shared query struct in
linux/kallsyms.h, so we have a more efficient linear walk.
Signed-off-by: Samuel Wu <wusamuel@google.com>
---
include/linux/kallsyms.h | 7 +++++
include/linux/module.h | 13 +++++++++
kernel/module/kallsyms.c | 57 ++++++++++++++++++++++++++++++++++++++++
3 files changed, 77 insertions(+)
diff --git a/include/linux/kallsyms.h b/include/linux/kallsyms.h
index d5dd54c53ace..d31f2f436159 100644
--- a/include/linux/kallsyms.h
+++ b/include/linux/kallsyms.h
@@ -24,6 +24,13 @@
struct cred;
struct module;
+/* Exact or prefix name query, optionally in one module. */
+struct kallsyms_name_query {
+ const char *name;
+ size_t cmp_len; /* bytes to compare; includes NUL if exact */
+ const char *modname; /* NULL matches any module */
+};
+
static inline int is_kernel_text(unsigned long addr)
{
if (__is_kernel_text(addr))
diff --git a/include/linux/module.h b/include/linux/module.h
index 76f4f3596477..07d5a74ecfcc 100644
--- a/include/linux/module.h
+++ b/include/linux/module.h
@@ -42,6 +42,7 @@ struct modversion_info {
struct module;
struct exception_table_entry;
+struct kallsyms_name_query;
struct module_kobject {
struct kobject kobj;
@@ -978,6 +979,10 @@ int lookup_module_symbol_attrs(unsigned long addr,
*/
int module_get_kallsym(unsigned int symnum, unsigned long *value, char *type,
char *name, char *module_name, int *exported);
+int module_kallsyms_find_name(unsigned int *symnum,
+ const struct kallsyms_name_query *q,
+ unsigned long *value, char *name,
+ char *module_name);
/* Look for this name: can be of form module:name. */
unsigned long module_kallsyms_lookup_name(const char *name);
@@ -1016,6 +1021,14 @@ static inline int module_get_kallsym(unsigned int symnum, unsigned long *value,
return -ERANGE;
}
+static inline int module_kallsyms_find_name(unsigned int *symnum,
+ const struct kallsyms_name_query *q,
+ unsigned long *value, char *name,
+ char *module_name)
+{
+ return -ENOENT;
+}
+
static inline unsigned long module_kallsyms_lookup_name(const char *name)
{
return 0;
diff --git a/kernel/module/kallsyms.c b/kernel/module/kallsyms.c
index 1ff8ce857d92..72006c9bad42 100644
--- a/kernel/module/kallsyms.c
+++ b/kernel/module/kallsyms.c
@@ -406,6 +406,63 @@ int module_get_kallsym(unsigned int symnum, unsigned long *value, char *type,
return -ERANGE;
}
+/**
+ * module_kallsyms_find_name - Find a module symbol by name or prefix
+ * @symnum: in: index to start at; out: index of the match. The index
+ * counts the symbols of the searched modules in list order.
+ * @q: name query; if @q->modname is set, only that module is searched
+ * @value: set to the symbol address
+ * @name: set to the symbol name (KSYM_NAME_LEN bytes)
+ * @module_name: set to the module name (MODULE_NAME_LEN bytes)
+ *
+ * Undefined (SHN_UNDEF) symbols are skipped.
+ *
+ * Return: 0 on a match, or -ENOENT.
+ */
+int module_kallsyms_find_name(unsigned int *symnum,
+ const struct kallsyms_name_query *q,
+ unsigned long *value, char *name,
+ char *module_name)
+{
+ unsigned int start = *symnum;
+ unsigned int sym_base = 0;
+ struct module *mod;
+
+ guard(rcu)();
+ list_for_each_entry_rcu(mod, &modules, list) {
+ struct mod_kallsyms *kallsyms;
+ unsigned int i = 0;
+
+ if (mod->state == MODULE_STATE_UNFORMED)
+ continue;
+ if (q->modname && strcmp(q->modname, mod->name))
+ continue;
+
+ kallsyms = rcu_dereference(mod->kallsyms);
+ if (start > sym_base)
+ i = start - sym_base;
+ for (; i < kallsyms->num_symtab; i++) {
+ const Elf_Sym *sym = &kallsyms->symtab[i];
+ const char *symname = kallsyms_symbol_name(kallsyms, i);
+
+ if (sym->st_shndx == SHN_UNDEF)
+ continue;
+ if (strncmp(symname, q->name, q->cmp_len))
+ continue;
+
+ *value = kallsyms_symbol_value(sym);
+ strscpy(name, symname, KSYM_NAME_LEN);
+ strscpy(module_name, mod->name, MODULE_NAME_LEN);
+ *symnum = sym_base + i;
+ return 0;
+ }
+ if (q->modname)
+ break;
+ sym_base += kallsyms->num_symtab;
+ }
+ return -ENOENT;
+}
+
/* Given a module and name of symbol, find and return the symbol's value */
static unsigned long __find_kallsyms_symbol_value(struct module *mod, const char *name)
{
--
2.56.0.679.gc527c65306-goog
^ permalink raw reply [flat|nested] 12+ messages in thread* [PATCH bpf-next v3 3/9] kallsyms: Add KALLSYMS_QUERY_BY_NAME ioctl to /proc/kallsyms
2026-11-13 22:12 [PATCH bpf-next v3 0/9] kallsyms: Add KALLSYMS_QUERY_BY_NAME and KALLSYMS_QUERY_BY_ADDR ioctls Samuel Wu
2026-11-13 22:12 ` [PATCH bpf-next v3 1/9] Documentation: ioctl-number: Add PROCFS_IOCTL_MAGIC range 0x10-0x11 Samuel Wu
2026-11-13 22:12 ` [PATCH bpf-next v3 2/9] module: Add module_kallsyms_find_name() Samuel Wu
@ 2026-11-13 22:12 ` Samuel Wu
2026-11-13 22:55 ` bot+bpf-ci
2026-11-13 22:12 ` [PATCH bpf-next v3 4/9] kallsyms: Add KALLSYMS_QUERY_BY_ADDR " Samuel Wu
` (5 subsequent siblings)
8 siblings, 1 reply; 12+ messages in thread
From: Samuel Wu @ 2026-11-13 22:12 UTC (permalink / raw)
To: Jonathan Corbet, Shuah Khan, Randy Dunlap, Luis Chamberlain,
Petr Pavlu, Daniel Gomez, Sami Tolvanen, Aaron Tomlin,
Alexander Viro, Christian Brauner, Jan Kara, Alexei Starovoitov,
Daniel Borkmann, Andrii Nakryiko, Eduard Zingerman,
Kumar Kartikeya Dwivedi, Martin KaFai Lau, Song Liu,
Yonghong Song, Jiri Olsa, Emil Tsalapatis, Ihor Solodrai,
Nathan Chancellor, Nick Desaulniers, Bill Wendling, Justin Stitt
Cc: Andrew Morton, Petr Mladek, David Laight, Samuel Wu, kernel-team,
linux-kernel, linux-fsdevel, linux-doc, linux-modules, bpf,
linux-kselftest, llvm
To find a few kernel symbols, users such as libbpf parse all of
/proc/kallsyms, which is several MB and text based.
Add the KALLSYMS_QUERY_BY_NAME ioctl. It finds a symbol by exact name or
by prefix in the entries of /proc/kallsyms, with vmlinux matches first,
and optionally in a single module with module_filter, or all modules by
default. Each call returns one match and its position; pos + 1 continues
the search, until -ENOENT. The output returns the address, the size, and
optional name and module buffers. A buffer that is too small gives
-ENAMETOOLONG and the required size.
vmlinux symbols are found with a lower-bound binary search on the sorted
names, module symbols in one pass, and ftrace, BPF, and kprobe symbols
with the per-index getters that read() uses.
For versioning, the struct carries their sizes, so the command does not
change when the struct grows.
The command uses 'f' 0x30; register 0x30 in ioctl-number.rst.
Suggested-by: Andrii Nakryiko <andrii@kernel.org>
Signed-off-by: Samuel Wu <wusamuel@google.com>
---
.../userspace-api/ioctl/ioctl-number.rst | 1 +
include/uapi/linux/fs.h | 43 +++
kernel/kallsyms.c | 319 ++++++++++++++++++
3 files changed, 363 insertions(+)
diff --git a/Documentation/userspace-api/ioctl/ioctl-number.rst b/Documentation/userspace-api/ioctl/ioctl-number.rst
index fbc3537fc9ee..f5dbb597257a 100644
--- a/Documentation/userspace-api/ioctl/ioctl-number.rst
+++ b/Documentation/userspace-api/ioctl/ioctl-number.rst
@@ -250,6 +250,7 @@ Code Seq# Include File Comments
'f' 00-0F fs/ocfs2/ocfs2_fs.h conflict!
'f' 10-11 linux/fs.h conflict! procfs ioctls (PROCFS_IOCTL_MAGIC)
'f' 13-27 linux/fscrypt.h
+'f' 30 linux/fs.h procfs kallsyms ioctls (PROCFS_IOCTL_MAGIC)
'f' 81-8F linux/fsverity.h
'g' 00-0F linux/usb/gadgetfs.h
'g' 20-2F linux/usb/g_printer.h
diff --git a/include/uapi/linux/fs.h b/include/uapi/linux/fs.h
index a46c33692aa2..987569dcdbea 100644
--- a/include/uapi/linux/fs.h
+++ b/include/uapi/linux/fs.h
@@ -674,6 +674,49 @@ struct procmap_query {
__u64 build_id_addr; /* in */
};
+/* /proc/kallsyms ioctls. -EPERM if the opener cannot see kernel addresses. */
+#define KALLSYMS_QUERY_BY_NAME _IOC(_IOC_READ | _IOC_WRITE, PROCFS_IOCTL_MAGIC, 48, 0)
+
+enum kallsyms_query_flags {
+ /* Match query_name as a prefix. */
+ KALLSYMS_QUERY_NAME_PREFIX = 0x01,
+};
+
+/*
+ * Symbol returned by a query. Each buffer pointer and its *_len must both be
+ * set or both be 0. When set, the kernel sets *_len to the string size
+ * including the NUL, even if the buffer is too small and the ioctl fails with
+ * -ENAMETOOLONG. The module name of a vmlinux symbol is "".
+ */
+struct kallsyms_query_sym {
+ __u64 sym_addr; /* out - start address */
+ __u64 sym_size; /* out - bytes to next symbol, 0 if unknown */
+ __u64 sym_name; /* in - user ptr to name buffer, or 0 */
+ __u64 sym_name_len; /* in/out - buffer size / string size */
+ __u64 sym_module; /* in - user ptr to module name buffer, or 0 */
+ __u64 sym_module_len; /* in/out - buffer size / string size */
+};
+
+/*
+ * Find a symbol by exact name, or by prefix with KALLSYMS_QUERY_NAME_PREFIX,
+ * in the entries of /proc/kallsyms, except undefined module symbols. vmlinux
+ * matches come first. A module_filter of "vmlinux" selects the core kernel.
+ *
+ * Each call returns one match and sets pos to its position, even on
+ * -ENAMETOOLONG. Start with pos 0 and use pos + 1 for the next match, until
+ * -ENOENT. Use pos only with the same query. Matches can be skipped or
+ * repeated if symbols change between calls.
+ */
+struct kallsyms_query_by_name {
+ __u64 size; /* in - sizeof(struct kallsyms_query_by_name) */
+ __u64 query_flags; /* in - enum kallsyms_query_flags */
+ __u64 query_name; /* in - user ptr to name or prefix */
+ __u64 module_filter; /* in - user ptr to loaded module name, or 0 */
+ __u64 pos; /* in/out - start position / match position */
+ __u64 sym; /* in - user ptr to struct kallsyms_query_sym */
+ __u64 sym_struct_size; /* in - sizeof(struct kallsyms_query_sym) */
+};
+
/*
* Shutdown the filesystem.
*/
diff --git a/kernel/kallsyms.c b/kernel/kallsyms.c
index aec2f06858af..29a9da04d8a3 100644
--- a/kernel/kallsyms.c
+++ b/kernel/kallsyms.c
@@ -31,6 +31,8 @@
#include <linux/kernel.h>
#include <linux/bsearch.h>
#include <linux/btf_ids.h>
+#include <linux/uaccess.h>
+#include <linux/cleanup.h>
#include "kallsyms_internal.h"
@@ -922,11 +924,328 @@ const char *kdb_walk_kallsyms(loff_t *pos)
}
#endif /* CONFIG_KGDB_KDB */
+struct kallsyms_sym_match {
+ unsigned long addr;
+ unsigned long size;
+ char name[KSYM_NAME_LEN];
+ char modname[MODULE_NAME_LEN];
+};
+
+static long kallsyms_copy_str_from_user(char *dst, size_t size, u64 usrc)
+{
+ long len = strncpy_from_user(dst, u64_to_user_ptr(usrc), size);
+
+ if (len < 0)
+ return len;
+ if (len == 0)
+ return -EINVAL;
+ if (len == size)
+ return -ENOENT;
+ return len;
+}
+
+static int kallsyms_copy_struct_from_user(void *kdst, size_t ksize,
+ size_t min_size,
+ const void __user *usrc, u64 usize)
+{
+ if (usize > PAGE_SIZE)
+ return -E2BIG;
+ if (usize < min_size)
+ return -EINVAL;
+ return copy_struct_from_user(kdst, ksize, usrc, usize);
+}
+
+static int kallsyms_copy_query_from_user(void *karg, size_t ksize,
+ size_t min_size,
+ const void __user *uarg)
+{
+ u64 usize;
+
+ if (copy_from_user(&usize, uarg, sizeof(usize)))
+ return -EFAULT;
+ return kallsyms_copy_struct_from_user(karg, ksize, min_size, uarg,
+ usize);
+}
+
+static int kallsyms_copy_sym_from_user(struct kallsyms_query_sym *ksym,
+ u64 usym, u64 usym_size)
+{
+ size_t min_size = offsetofend(struct kallsyms_query_sym,
+ sym_module_len);
+ int err = kallsyms_copy_struct_from_user(ksym, sizeof(*ksym), min_size,
+ u64_to_user_ptr(usym),
+ usym_size);
+ if (err)
+ return err;
+ if (!!ksym->sym_name != !!ksym->sym_name_len ||
+ !!ksym->sym_module != !!ksym->sym_module_len)
+ return -EINVAL;
+ return 0;
+}
+
+static int kallsyms_copy_str_to_user(u64 ubuf, u64 *ulen, const char *str)
+{
+ u64 buf_len = *ulen;
+ size_t len;
+
+ if (!ubuf)
+ return 0;
+
+ len = strlen(str) + 1;
+ *ulen = len;
+ if (buf_len < len)
+ return -ENAMETOOLONG;
+ return copy_to_user(u64_to_user_ptr(ubuf), str, len) ? -EFAULT : 0;
+}
+
+/* On -ENAMETOOLONG, *ksym is still copied, to return the required sizes. */
+static int kallsyms_copy_sym_to_user(u64 usym, u64 usym_size,
+ struct kallsyms_query_sym *ksym,
+ const struct kallsyms_sym_match *m)
+{
+ size_t copy_sz = min_t(size_t, usym_size, sizeof(*ksym));
+ bool too_long = false;
+ int err;
+
+ ksym->sym_addr = m->addr;
+ ksym->sym_size = m->size;
+ err = kallsyms_copy_str_to_user(ksym->sym_name, &ksym->sym_name_len,
+ m->name);
+ if (err == -EFAULT)
+ return -EFAULT;
+ too_long |= (err == -ENAMETOOLONG);
+
+ err = kallsyms_copy_str_to_user(ksym->sym_module, &ksym->sym_module_len,
+ m->modname);
+ if (err == -EFAULT)
+ return -EFAULT;
+ too_long |= (err == -ENAMETOOLONG);
+
+ if (copy_to_user(u64_to_user_ptr(usym), ksym, copy_sz))
+ return -EFAULT;
+
+ return too_long ? -ENAMETOOLONG : 0;
+}
+
+/* pos = (sec << 32) | idx. Sections start at 1, so pos 0 means start. */
+enum kallsyms_query_sec {
+ KSYM_SEC_VMLINUX = 1,
+ KSYM_SEC_MODULE,
+ KSYM_SEC_FTRACE,
+ KSYM_SEC_BPF,
+ KSYM_SEC_KPROBE,
+ KSYM_SEC_MAX,
+};
+
+static int kallsyms_find_name_vmlinux(unsigned int *idx,
+ const struct kallsyms_name_query *q,
+ struct kallsyms_sym_match *m)
+{
+ unsigned int high = kallsyms_num_syms;
+ unsigned int low = *idx;
+ unsigned int seq;
+
+ /* pos + 1 usually points at the next match, so try it first. */
+ if (low > 0 && low < high) {
+ int cmp;
+
+ seq = get_symbol_seq(low);
+ kallsyms_expand_symbol(get_symbol_offset(seq), m->name,
+ KSYM_NAME_LEN);
+ cmp = strncmp(m->name, q->name, q->cmp_len);
+ if (!cmp)
+ goto found;
+ if (cmp > 0)
+ return -ENOENT;
+ low++;
+ }
+
+ while (low < high) {
+ unsigned int mid = low + (high - low) / 2;
+
+ seq = get_symbol_seq(mid);
+ kallsyms_expand_symbol(get_symbol_offset(seq), m->name,
+ KSYM_NAME_LEN);
+ if (strncmp(m->name, q->name, q->cmp_len) < 0)
+ low = mid + 1;
+ else
+ high = mid;
+ }
+ if (low >= kallsyms_num_syms)
+ return -ENOENT;
+
+ seq = get_symbol_seq(low);
+ kallsyms_expand_symbol(get_symbol_offset(seq), m->name, KSYM_NAME_LEN);
+ if (strncmp(m->name, q->name, q->cmp_len))
+ return -ENOENT;
+
+found:
+ m->addr = kallsyms_sym_address(seq);
+ m->modname[0] = '\0';
+ *idx = low;
+ return 0;
+}
+
+static int kallsyms_find_name_other(unsigned int sec, unsigned int *idx,
+ const struct kallsyms_name_query *q,
+ struct kallsyms_sym_match *m)
+{
+ int exported;
+ char type;
+ int err;
+
+ if (sec == KSYM_SEC_BPF)
+ strscpy(m->modname, "bpf", MODULE_NAME_LEN);
+ else if (sec == KSYM_SEC_KPROBE)
+ strscpy(m->modname, "__builtin__kprobes", MODULE_NAME_LEN);
+
+ do {
+ if (sec == KSYM_SEC_BPF)
+ err = bpf_get_kallsym(*idx, &m->addr, &type, m->name);
+ else if (sec == KSYM_SEC_KPROBE)
+ err = kprobe_get_kallsym(*idx, &m->addr, &type, m->name);
+ else
+ err = ftrace_mod_get_kallsym(*idx, &m->addr, &type,
+ m->name, m->modname,
+ &exported);
+ if (!err && !strncmp(m->name, q->name, q->cmp_len))
+ return 0;
+ (*idx)++;
+ } while (!err);
+
+ return -ENOENT;
+}
+
+/* Find the first match at or after *@pos, and set *@pos to its position. */
+static int kallsyms_find_name(const struct kallsyms_name_query *q,
+ unsigned int first_sec, unsigned int end_sec,
+ struct kallsyms_sym_match *m, u64 *pos)
+{
+ unsigned int sec = *pos >> 32;
+ unsigned int idx = (u32)*pos;
+ unsigned long size;
+ unsigned long offset;
+ int err;
+
+ if (sec < first_sec) {
+ sec = first_sec;
+ idx = 0;
+ }
+
+ for (; sec < end_sec; sec++, idx = 0) {
+ if (sec == KSYM_SEC_VMLINUX)
+ err = kallsyms_find_name_vmlinux(&idx, q, m);
+ else if (sec == KSYM_SEC_MODULE)
+ err = module_kallsyms_find_name(&idx, q, &m->addr,
+ m->name, m->modname);
+ else
+ err = kallsyms_find_name_other(sec, &idx, q, m);
+ if (!err) {
+ /* A lookup that lands inside another symbol gives no size */
+ m->size = 0;
+ if (kallsyms_lookup_size_offset(m->addr, &size, &offset) &&
+ !offset)
+ m->size = size;
+ *pos = ((u64)sec << 32) | idx;
+ return 0;
+ }
+ }
+ return -ENOENT;
+}
+
+struct kallsyms_query_bufs {
+ char query_name[KSYM_NAME_LEN];
+ char module_filter[MODULE_NAME_LEN];
+ struct kallsyms_sym_match m;
+};
+
+static int do_kallsyms_query_by_name(struct kallsyms_query_by_name __user *uarg)
+{
+ unsigned int first_sec = KSYM_SEC_VMLINUX;
+ unsigned int end_sec = KSYM_SEC_MAX;
+ struct kallsyms_query_by_name karg;
+ struct kallsyms_name_query q = {};
+ struct kallsyms_query_sym ksym;
+ long len;
+ int err;
+
+ err = kallsyms_copy_query_from_user(&karg, sizeof(karg),
+ offsetofend(struct kallsyms_query_by_name,
+ sym_struct_size),
+ uarg);
+ if (err)
+ return err;
+
+ if (karg.query_flags & ~KALLSYMS_QUERY_NAME_PREFIX)
+ return -EINVAL;
+
+ err = kallsyms_copy_sym_from_user(&ksym, karg.sym, karg.sym_struct_size);
+ if (err)
+ return err;
+
+ struct kallsyms_query_bufs *bufs __free(kfree) = kmalloc_obj(*bufs);
+ if (!bufs)
+ return -ENOMEM;
+
+ len = kallsyms_copy_str_from_user(bufs->query_name,
+ sizeof(bufs->query_name),
+ karg.query_name);
+ if (len < 0)
+ return len;
+
+ q.name = bufs->query_name;
+ q.cmp_len = len;
+ if (!(karg.query_flags & KALLSYMS_QUERY_NAME_PREFIX))
+ q.cmp_len++;
+
+ if (karg.module_filter) {
+ len = kallsyms_copy_str_from_user(bufs->module_filter,
+ sizeof(bufs->module_filter),
+ karg.module_filter);
+ if (len < 0)
+ return len;
+ if (!strcmp(bufs->module_filter, "vmlinux")) {
+ end_sec = KSYM_SEC_VMLINUX + 1;
+ } else {
+ first_sec = KSYM_SEC_MODULE;
+ end_sec = KSYM_SEC_MODULE + 1;
+ q.modname = bufs->module_filter;
+ }
+ }
+
+ err = kallsyms_find_name(&q, first_sec, end_sec, &bufs->m, &karg.pos);
+ if (err)
+ return err;
+
+ if (copy_to_user(&uarg->pos, &karg.pos, sizeof(karg.pos)))
+ return -EFAULT;
+ return kallsyms_copy_sym_to_user(karg.sym, karg.sym_struct_size, &ksym,
+ &bufs->m);
+}
+
+static long kallsyms_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+ struct seq_file *m = file->private_data;
+ struct kallsym_iter *iter = m->private;
+ void __user *uarg = (void __user *)arg;
+
+ switch (cmd) {
+ case KALLSYMS_QUERY_BY_NAME:
+ return iter->show_value ? do_kallsyms_query_by_name(uarg) : -EPERM;
+ 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.679.gc527c65306-goog
^ permalink raw reply [flat|nested] 12+ messages in thread* Re: [PATCH bpf-next v3 3/9] kallsyms: Add KALLSYMS_QUERY_BY_NAME ioctl to /proc/kallsyms
2026-11-13 22:12 ` [PATCH bpf-next v3 3/9] kallsyms: Add KALLSYMS_QUERY_BY_NAME ioctl to /proc/kallsyms Samuel Wu
@ 2026-11-13 22:55 ` bot+bpf-ci
0 siblings, 0 replies; 12+ messages in thread
From: bot+bpf-ci @ 2026-11-13 22:55 UTC (permalink / raw)
To: wusamuel, corbet, skhan, rdunlap, mcgrof, petr.pavlu, da.gomez,
samitolvanen, atomlin, viro, brauner, jack, ast, daniel, andrii,
eddyz87, memxor, martin.lau, song, yonghong.song, jolsa, emil,
ihor.solodrai, nathan, ndesaulniers, morbo, justinstitt
Cc: akpm, pmladek, david.laight.linux, wusamuel, kernel-team,
linux-kernel, linux-fsdevel, linux-doc, linux-modules, bpf,
linux-kselftest, llvm, ast, andrii, daniel, martin.lau, eddyz87,
yonghong.song, mason, ihor.solodrai
[-- Attachment #1: Type: text/plain, Size: 4770 bytes --]
> kallsyms: Add KALLSYMS_QUERY_BY_NAME ioctl to /proc/kallsyms
TL;DR:
- KALLSYMS_QUERY_BY_NAME may scan BPF ksyms quadratically in one syscall with
no cond_resched(), possibly cancelling the speedup
- module_filter may return -ENOENT for init symbols that the unfiltered query
finds via the ftrace section
> diff --git a/kernel/kallsyms.c b/kernel/kallsyms.c
> --- a/kernel/kallsyms.c
> +++ b/kernel/kallsyms.c
[ ... ]
> +static int kallsyms_find_name_other(unsigned int sec, unsigned int *idx,
> + const struct kallsyms_name_query *q,
> + struct kallsyms_sym_match *m)
> +{
[ ... ]
> + do {
> + if (sec == KSYM_SEC_BPF)
> + err = bpf_get_kallsym(*idx, &m->addr, &type, m->name);
> + else if (sec == KSYM_SEC_KPROBE)
> + err = kprobe_get_kallsym(*idx, &m->addr, &type, m->name);
> + else
> + err = ftrace_mod_get_kallsym(*idx, &m->addr, &type,
> + m->name, m->modname,
> + &exported);
> + if (!err && !strncmp(m->name, q->name, q->cmp_len))
> + return 0;
> + (*idx)++;
> + } while (!err);
Is this loop quadratic in the number of BPF ksyms, with no reschedule point?
bpf_get_kallsym() restarts from the list head on every call and walks
forward to the index:
list_for_each_entry_rcu(ksym, &bpf_kallsyms, lnode) {
if (it++ != symnum)
continue;
So one ioctl that scans the BPF section without a match does n(n+1)/2 list
steps inside a single syscall. Every exhaustive query gets there, since
even a query whose matches are all in vmlinux or modules still scans the
ftrace, BPF and kprobe sections on the call that returns -ENOENT.
The uapi comment tells users to keep calling with pos + 1 until -ENOENT,
and the libbpf user later in this series (822e9da9a9b7 "libbpf: Resolve
typeless ksyms via KALLSYMS_QUERY_BY_NAME ioctl") does this twice per
typeless extern, once for the exact name and once for the "<name>.llvm."
prefix.
With read() of /proc/kallsyms the same getter cost is spread across
syscalls, because seq_read fills at most one seq_file buffer per call. Here
all of it lands in each call that reaches the BPF section. On hosts with
thousands of BPF programs and trampolines, does this cancel the speedup the
commit message describes?
On kernels without ARCH_HAS_PREEMPT_LAZY, or with ARCH_NO_PREEMPT, it is
also a long run with no reschedule, whereas kallsyms_on_each_symbol() and
kallsyms_on_each_match_symbol() in this file call cond_resched().
Would a helper that walks bpf_kallsyms once from a start index be better,
or at least a cond_resched() in this loop? ftrace_mod_get_kallsym() has the
same restart-from-the-beginning pattern, although its lists are usually
small.
[ ... ]
> + if (karg.module_filter) {
> + len = kallsyms_copy_str_from_user(bufs->module_filter,
> + sizeof(bufs->module_filter),
> + karg.module_filter);
> + if (len < 0)
> + return len;
> + if (!strcmp(bufs->module_filter, "vmlinux")) {
> + end_sec = KSYM_SEC_VMLINUX + 1;
> + } else {
> + first_sec = KSYM_SEC_MODULE;
> + end_sec = KSYM_SEC_MODULE + 1;
> + q.modname = bufs->module_filter;
> + }
Can module_filter return -ENOENT for a symbol that the unfiltered query
finds in the same module?
When a module name is given, only KSYM_SEC_MODULE is searched. But
/proc/kallsyms also lists entries for a loaded module from the ftrace
section. ftrace_free_mem(), called from do_init_module() for the module's
init text, runs allocate_ftrace_mod_map() when any ftrace_ops is registered,
and ftrace_mod_get_kallsym() returns those entries with
strscpy(module_name, mod_map->mod->name, ...). They appear as, for example:
ffffffffc0a01000 T foo_init [foo]
and remain until the module is unloaded, because ftrace_release_mod() frees
the map then. After init, mod->kallsyms has switched to core_kallsyms, so
these init symbols are no longer in the module section. That gives:
BY_NAME("foo_init", module_filter = NULL) -> match, sym_module = "foo"
BY_NAME("foo_init", module_filter = "foo") -> -ENOENT
The uapi comment says the ioctl finds a symbol "in the entries of
/proc/kallsyms", so the filtered result does not look like the unfiltered
result limited to [foo]. kallsyms_find_name_other() does not check
q->modname for the ftrace section, so extending end_sec alone would not be
enough.
Should the filtered case also search KSYM_SEC_FTRACE and skip entries whose
m->modname differs from q->modname, or should the uapi comment document that
module_filter only covers the module's own symbol table?
---
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/43520044354
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH bpf-next v3 4/9] kallsyms: Add KALLSYMS_QUERY_BY_ADDR ioctl to /proc/kallsyms
2026-11-13 22:12 [PATCH bpf-next v3 0/9] kallsyms: Add KALLSYMS_QUERY_BY_NAME and KALLSYMS_QUERY_BY_ADDR ioctls Samuel Wu
` (2 preceding siblings ...)
2026-11-13 22:12 ` [PATCH bpf-next v3 3/9] kallsyms: Add KALLSYMS_QUERY_BY_NAME ioctl to /proc/kallsyms Samuel Wu
@ 2026-11-13 22:12 ` Samuel Wu
2026-11-13 22:12 ` [PATCH bpf-next v3 5/9] docs/procfs: Document KALLSYMS_QUERY_* ioctls on /proc/kallsyms Samuel Wu
` (4 subsequent siblings)
8 siblings, 0 replies; 12+ messages in thread
From: Samuel Wu @ 2026-11-13 22:12 UTC (permalink / raw)
To: Jonathan Corbet, Shuah Khan, Randy Dunlap, Luis Chamberlain,
Petr Pavlu, Daniel Gomez, Sami Tolvanen, Aaron Tomlin,
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: Andrew Morton, Petr Mladek, David Laight, Samuel Wu, kernel-team,
linux-kernel, linux-fsdevel, linux-doc, linux-modules, bpf,
linux-kselftest, llvm
Symbolizers such as Perfetto read all of /proc/kallsyms and keep a
sorted copy in memory to resolve addresses.
Add the KALLSYMS_QUERY_BY_ADDR ioctl. It returns the symbol that covers
an address, from the same sources and in the same order as %pS. BPF JIT
symbols are hidden when /proc/kallsyms hides them. query_flags must be
0; it is reserved for other modes.
The command uses 'f' 0x31; register 0x31 in ioctl-number.rst.
Suggested-by: Andrii Nakryiko <andrii@kernel.org>
Signed-off-by: Samuel Wu <wusamuel@google.com>
---
.../userspace-api/ioctl/ioctl-number.rst | 2 +-
include/uapi/linux/fs.h | 13 ++++
kernel/kallsyms.c | 60 +++++++++++++++++++
3 files changed, 74 insertions(+), 1 deletion(-)
diff --git a/Documentation/userspace-api/ioctl/ioctl-number.rst b/Documentation/userspace-api/ioctl/ioctl-number.rst
index f5dbb597257a..d9b66e7a29b7 100644
--- a/Documentation/userspace-api/ioctl/ioctl-number.rst
+++ b/Documentation/userspace-api/ioctl/ioctl-number.rst
@@ -250,7 +250,7 @@ Code Seq# Include File Comments
'f' 00-0F fs/ocfs2/ocfs2_fs.h conflict!
'f' 10-11 linux/fs.h conflict! procfs ioctls (PROCFS_IOCTL_MAGIC)
'f' 13-27 linux/fscrypt.h
-'f' 30 linux/fs.h procfs kallsyms ioctls (PROCFS_IOCTL_MAGIC)
+'f' 30-31 linux/fs.h procfs kallsyms ioctls (PROCFS_IOCTL_MAGIC)
'f' 81-8F linux/fsverity.h
'g' 00-0F linux/usb/gadgetfs.h
'g' 20-2F linux/usb/g_printer.h
diff --git a/include/uapi/linux/fs.h b/include/uapi/linux/fs.h
index 987569dcdbea..c1a63c8560e2 100644
--- a/include/uapi/linux/fs.h
+++ b/include/uapi/linux/fs.h
@@ -676,6 +676,7 @@ struct procmap_query {
/* /proc/kallsyms ioctls. -EPERM if the opener cannot see kernel addresses. */
#define KALLSYMS_QUERY_BY_NAME _IOC(_IOC_READ | _IOC_WRITE, PROCFS_IOCTL_MAGIC, 48, 0)
+#define KALLSYMS_QUERY_BY_ADDR _IOC(_IOC_READ | _IOC_WRITE, PROCFS_IOCTL_MAGIC, 49, 0)
enum kallsyms_query_flags {
/* Match query_name as a prefix. */
@@ -717,6 +718,18 @@ struct kallsyms_query_by_name {
__u64 sym_struct_size; /* in - sizeof(struct kallsyms_query_sym) */
};
+/*
+ * Find the /proc/kallsyms entry that covers query_addr, or return -ENOENT.
+ * ftrace trampolines and kprobe pages are not resolved yet.
+ */
+struct kallsyms_query_by_addr {
+ __u64 size; /* in - sizeof(struct kallsyms_query_by_addr) */
+ __u64 query_flags; /* in - must be 0 */
+ __u64 query_addr; /* in - kernel address */
+ __u64 sym; /* in - user ptr to struct kallsyms_query_sym */
+ __u64 sym_struct_size; /* in - sizeof(struct kallsyms_query_sym) */
+};
+
/*
* Shutdown the filesystem.
*/
diff --git a/kernel/kallsyms.c b/kernel/kallsyms.c
index 29a9da04d8a3..6d7032cd3fd6 100644
--- a/kernel/kallsyms.c
+++ b/kernel/kallsyms.c
@@ -1223,6 +1223,64 @@ static int do_kallsyms_query_by_name(struct kallsyms_query_by_name __user *uarg)
&bufs->m);
}
+/*
+ * Look up symbol from addr. Copy the module name under RCU, and hide BPF JIT
+ * symbols as /proc/kallsyms hides them.
+ */
+static int kallsyms_find_addr(unsigned long addr, struct kallsyms_sym_match *m)
+{
+ unsigned long offset;
+ char *modname;
+
+ guard(rcu)();
+ if (!kallsyms_lookup_buildid(addr, &m->size, &offset, &modname, NULL,
+ m->name))
+ return -ENOENT;
+
+ /* Outside vmlinux, only BPF JIT symbols have no module name. */
+ if (!modname && !is_ksym_addr(addr)) {
+ if (!bpf_jit_kallsyms_enabled())
+ return -ENOENT;
+ modname = "bpf";
+ }
+
+ m->addr = addr - offset;
+ strscpy(m->modname, modname ?: "", MODULE_NAME_LEN);
+ return 0;
+}
+
+static int do_kallsyms_query_by_addr(struct kallsyms_query_by_addr __user *uarg)
+{
+ struct kallsyms_query_by_addr karg;
+ struct kallsyms_query_sym ksym;
+ struct kallsyms_sym_match m;
+ int err;
+
+ err = kallsyms_copy_query_from_user(&karg, sizeof(karg),
+ offsetofend(struct kallsyms_query_by_addr,
+ sym_struct_size),
+ uarg);
+ if (err)
+ return err;
+
+ if (karg.query_flags)
+ return -EINVAL;
+
+ err = kallsyms_copy_sym_from_user(&ksym, karg.sym, karg.sym_struct_size);
+ if (err)
+ return err;
+
+ if (karg.query_addr > ULONG_MAX)
+ return -ENOENT;
+
+ err = kallsyms_find_addr(karg.query_addr, &m);
+ if (err)
+ return err;
+
+ return kallsyms_copy_sym_to_user(karg.sym, karg.sym_struct_size, &ksym,
+ &m);
+}
+
static long kallsyms_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
struct seq_file *m = file->private_data;
@@ -1232,6 +1290,8 @@ static long kallsyms_ioctl(struct file *file, unsigned int cmd, unsigned long ar
switch (cmd) {
case KALLSYMS_QUERY_BY_NAME:
return iter->show_value ? do_kallsyms_query_by_name(uarg) : -EPERM;
+ case KALLSYMS_QUERY_BY_ADDR:
+ return iter->show_value ? do_kallsyms_query_by_addr(uarg) : -EPERM;
default:
return -ENOIOCTLCMD;
}
--
2.56.0.679.gc527c65306-goog
^ permalink raw reply [flat|nested] 12+ messages in thread* [PATCH bpf-next v3 5/9] docs/procfs: Document KALLSYMS_QUERY_* ioctls on /proc/kallsyms
2026-11-13 22:12 [PATCH bpf-next v3 0/9] kallsyms: Add KALLSYMS_QUERY_BY_NAME and KALLSYMS_QUERY_BY_ADDR ioctls Samuel Wu
` (3 preceding siblings ...)
2026-11-13 22:12 ` [PATCH bpf-next v3 4/9] kallsyms: Add KALLSYMS_QUERY_BY_ADDR " Samuel Wu
@ 2026-11-13 22:12 ` Samuel Wu
2026-11-13 22:12 ` [PATCH bpf-next v3 6/9] tools: Copy KALLSYMS_QUERY_* definitions to linux/fs.h Samuel Wu
` (3 subsequent siblings)
8 siblings, 0 replies; 12+ messages in thread
From: Samuel Wu @ 2026-11-13 22:12 UTC (permalink / raw)
To: Jonathan Corbet, Shuah Khan, Randy Dunlap, Luis Chamberlain,
Petr Pavlu, Daniel Gomez, Sami Tolvanen, Aaron Tomlin,
Alexander Viro, Christian Brauner, Jan Kara, Alexei Starovoitov,
Daniel Borkmann, Andrii Nakryiko, Eduard Zingerman,
Kumar Kartikeya Dwivedi, Martin KaFai Lau, Song Liu,
Yonghong Song, Jiri Olsa, Emil Tsalapatis, Ihor Solodrai,
Nathan Chancellor, Nick Desaulniers, Bill Wendling, Justin Stitt
Cc: Andrew Morton, Petr Mladek, David Laight, Samuel Wu, kernel-team,
linux-kernel, linux-fsdevel, linux-doc, linux-modules, bpf,
linux-kselftest, llvm
Add kallsyms to the /proc file table, and a short section that refers to
the KALLSYMS_QUERY_* definitions in include/uapi/linux/fs.h.
Signed-off-by: Samuel Wu <wusamuel@google.com>
---
Documentation/filesystems/proc.rst | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/Documentation/filesystems/proc.rst b/Documentation/filesystems/proc.rst
index fa7a468e8edf..442aae9ebd9e 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 All kernel symbols (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,14 @@ Provides counts of softirq handlers serviced since boot time, for each CPU.
HRTIMER: 0 0 0 0
RCU: 1678 1769 2178 2250
+kallsyms
+~~~~~~~~
+
+Lists the kernel symbols of vmlinux, loaded modules, and dynamic sources such
+as BPF JIT images. The ``KALLSYMS_QUERY_BY_NAME`` and ``KALLSYMS_QUERY_BY_ADDR``
+ioctls find one symbol by name or by address without parsing the text. See
+``include/uapi/linux/fs.h`` for their structs and errors.
+
1.3 Networking info in /proc/net
--------------------------------
--
2.56.0.679.gc527c65306-goog
^ permalink raw reply [flat|nested] 12+ messages in thread* [PATCH bpf-next v3 6/9] tools: Copy KALLSYMS_QUERY_* definitions to linux/fs.h
2026-11-13 22:12 [PATCH bpf-next v3 0/9] kallsyms: Add KALLSYMS_QUERY_BY_NAME and KALLSYMS_QUERY_BY_ADDR ioctls Samuel Wu
` (4 preceding siblings ...)
2026-11-13 22:12 ` [PATCH bpf-next v3 5/9] docs/procfs: Document KALLSYMS_QUERY_* ioctls on /proc/kallsyms Samuel Wu
@ 2026-11-13 22:12 ` Samuel Wu
2026-11-13 22:12 ` [PATCH bpf-next v3 7/9] selftests/proc: Add KALLSYMS_QUERY_* ioctl tests Samuel Wu
` (2 subsequent siblings)
8 siblings, 0 replies; 12+ messages in thread
From: Samuel Wu @ 2026-11-13 22:12 UTC (permalink / raw)
To: Jonathan Corbet, Shuah Khan, Randy Dunlap, Luis Chamberlain,
Petr Pavlu, Daniel Gomez, Sami Tolvanen, Aaron Tomlin,
Alexander Viro, Christian Brauner, Jan Kara, Alexei Starovoitov,
Daniel Borkmann, Andrii Nakryiko, Eduard Zingerman,
Kumar Kartikeya Dwivedi, Martin KaFai Lau, Song Liu,
Yonghong Song, Jiri Olsa, Emil Tsalapatis, Ihor Solodrai,
Nathan Chancellor, Nick Desaulniers, Bill Wendling, Justin Stitt
Cc: Andrew Morton, Petr Mladek, David Laight, Samuel Wu, kernel-team,
linux-kernel, linux-fsdevel, linux-doc, linux-modules, bpf,
linux-kselftest, llvm
Copy the KALLSYMS_QUERY_* definitions from include/uapi/linux/fs.h for
the proc selftests.
Signed-off-by: Samuel Wu <wusamuel@google.com>
---
tools/include/uapi/linux/fs.h | 56 +++++++++++++++++++++++++++++++++++
1 file changed, 56 insertions(+)
diff --git a/tools/include/uapi/linux/fs.h b/tools/include/uapi/linux/fs.h
index f0a26309b6d5..286e82c61189 100644
--- a/tools/include/uapi/linux/fs.h
+++ b/tools/include/uapi/linux/fs.h
@@ -567,4 +567,60 @@ struct procmap_query {
__u64 build_id_addr; /* in */
};
+/* /proc/kallsyms ioctls. -EPERM if the opener cannot see kernel addresses. */
+#define KALLSYMS_QUERY_BY_NAME _IOC(_IOC_READ | _IOC_WRITE, PROCFS_IOCTL_MAGIC, 48, 0)
+#define KALLSYMS_QUERY_BY_ADDR _IOC(_IOC_READ | _IOC_WRITE, PROCFS_IOCTL_MAGIC, 49, 0)
+
+enum kallsyms_query_flags {
+ /* Match query_name as a prefix. */
+ KALLSYMS_QUERY_NAME_PREFIX = 0x01,
+};
+
+/*
+ * Symbol returned by a query. Each buffer pointer and its *_len must both be
+ * set or both be 0. When set, the kernel sets *_len to the string size
+ * including the NUL, even if the buffer is too small and the ioctl fails with
+ * -ENAMETOOLONG. The module name of a vmlinux symbol is "".
+ */
+struct kallsyms_query_sym {
+ __u64 sym_addr; /* out - start address */
+ __u64 sym_size; /* out - bytes to next symbol, 0 if unknown */
+ __u64 sym_name; /* in - user ptr to name buffer, or 0 */
+ __u64 sym_name_len; /* in/out - buffer size / string size */
+ __u64 sym_module; /* in - user ptr to module name buffer, or 0 */
+ __u64 sym_module_len; /* in/out - buffer size / string size */
+};
+
+/*
+ * Find a symbol by exact name, or by prefix with KALLSYMS_QUERY_NAME_PREFIX,
+ * in the entries of /proc/kallsyms, except undefined module symbols. vmlinux
+ * matches come first. A module_filter of "vmlinux" selects the core kernel.
+ *
+ * Each call returns one match and sets pos to its position, even on
+ * -ENAMETOOLONG. Start with pos 0 and use pos + 1 for the next match, until
+ * -ENOENT. Use pos only with the same query. Matches can be skipped or
+ * repeated if symbols change between calls.
+ */
+struct kallsyms_query_by_name {
+ __u64 size; /* in - sizeof(struct kallsyms_query_by_name) */
+ __u64 query_flags; /* in - enum kallsyms_query_flags */
+ __u64 query_name; /* in - user ptr to name or prefix */
+ __u64 module_filter; /* in - user ptr to loaded module name, or 0 */
+ __u64 pos; /* in/out - start position / match position */
+ __u64 sym; /* in - user ptr to struct kallsyms_query_sym */
+ __u64 sym_struct_size; /* in - sizeof(struct kallsyms_query_sym) */
+};
+
+/*
+ * Find the /proc/kallsyms entry that covers query_addr, or return -ENOENT.
+ * ftrace trampolines and kprobe pages are not resolved yet.
+ */
+struct kallsyms_query_by_addr {
+ __u64 size; /* in - sizeof(struct kallsyms_query_by_addr) */
+ __u64 query_flags; /* in - must be 0 */
+ __u64 query_addr; /* in - kernel address */
+ __u64 sym; /* in - user ptr to struct kallsyms_query_sym */
+ __u64 sym_struct_size; /* in - sizeof(struct kallsyms_query_sym) */
+};
+
#endif /* _UAPI_LINUX_FS_H */
--
2.56.0.679.gc527c65306-goog
^ permalink raw reply [flat|nested] 12+ messages in thread* [PATCH bpf-next v3 7/9] selftests/proc: Add KALLSYMS_QUERY_* ioctl tests
2026-11-13 22:12 [PATCH bpf-next v3 0/9] kallsyms: Add KALLSYMS_QUERY_BY_NAME and KALLSYMS_QUERY_BY_ADDR ioctls Samuel Wu
` (5 preceding siblings ...)
2026-11-13 22:12 ` [PATCH bpf-next v3 6/9] tools: Copy KALLSYMS_QUERY_* definitions to linux/fs.h Samuel Wu
@ 2026-11-13 22:12 ` Samuel Wu
2026-11-13 22:12 ` [PATCH bpf-next v3 8/9] libbpf: Resolve typeless ksyms via KALLSYMS_QUERY_BY_NAME ioctl Samuel Wu
2026-11-13 22:12 ` [PATCH bpf-next v3 9/9] selftests/bpf: Test typeless module ksym resolution Samuel Wu
8 siblings, 0 replies; 12+ messages in thread
From: Samuel Wu @ 2026-11-13 22:12 UTC (permalink / raw)
To: Jonathan Corbet, Shuah Khan, Randy Dunlap, Luis Chamberlain,
Petr Pavlu, Daniel Gomez, Sami Tolvanen, Aaron Tomlin,
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: Andrew Morton, Petr Mladek, David Laight, Samuel Wu, kernel-team,
linux-kernel, linux-fsdevel, linux-doc, linux-modules, bpf,
linux-kselftest, llvm
Test KALLSYMS_QUERY_BY_NAME and KALLSYMS_QUERY_BY_ADDR against the
/proc/kallsyms text: exact and prefix names, iteration with pos and
retry after -ENAMETOOLONG, module_filter, vmlinux and module symbols,
struct size extension, and invalid input. Skip if the ioctls are
missing or addresses are hidden.
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 | 248 ++++++++++++++++++
3 files changed, 250 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..7788cb0f30f8
--- /dev/null
+++ b/tools/testing/selftests/proc/proc-kallsyms-query.c
@@ -0,0 +1,248 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Tests for the KALLSYMS_QUERY_* ioctls on /proc/kallsyms */
+#undef NDEBUG
+#include <assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
+#include <linux/fs.h>
+
+#include "kselftest.h"
+
+#define PTR(p) ((__u64)(uintptr_t)(p))
+#define PREFIX "vfs_"
+
+static char name_buf[512];
+static char mod_buf[64];
+
+/* Expected values, from the /proc/kallsyms text */
+static struct {
+ unsigned long long vfs_read_addr;
+ bool vfs_read_found;
+ unsigned int prefix_cnt; /* vmlinux names with PREFIX */
+ unsigned long long mod_addr; /* module text symbol, or 0 */
+ char mod[64];
+} ref;
+
+static void scan_text(void)
+{
+ FILE *f = fopen("/proc/kallsyms", "r");
+ unsigned long long addr;
+ char line[1024];
+ bool plain_name;
+ char name[513];
+ char mod[64];
+ bool vmlinux;
+ bool module;
+ bool text;
+ char type;
+ int n;
+
+ assert(f);
+ while (fgets(line, sizeof(line), f)) {
+ /* 3 fields for vmlinux, 4 if the line ends with [module] */
+ n = sscanf(line, "%llx %c %512s [%63[^]]]",
+ &addr, &type, name, mod);
+ /* A longer name would be cut and parsed as vmlinux */
+ assert(n >= 3 && strlen(name) < sizeof(name) - 1);
+ vmlinux = n == 3;
+ module = n == 4 && strcmp(mod, "bpf") &&
+ strncmp(mod, "__builtin__", strlen("__builtin__"));
+ text = type == 't' || type == 'T';
+ plain_name = name[0] != '.' && name[0] != '$';
+
+ if (vmlinux) {
+ if (!strncmp(name, PREFIX, strlen(PREFIX)))
+ ref.prefix_cnt++;
+ if (!ref.vfs_read_found && !strcmp(name, "vfs_read")) {
+ ref.vfs_read_addr = addr;
+ ref.vfs_read_found = true;
+ }
+ } else if (module && text && plain_name && !ref.mod_addr) {
+ ref.mod_addr = addr;
+ strcpy(ref.mod, mod);
+ }
+ }
+ fclose(f);
+}
+
+static struct kallsyms_query_sym bufs(size_t name_len, size_t mod_len)
+{
+ return (struct kallsyms_query_sym){
+ .sym_name = name_len ? PTR(name_buf) : 0,
+ .sym_name_len = name_len,
+ .sym_module = mod_len ? PTR(mod_buf) : 0,
+ .sym_module_len = mod_len,
+ };
+}
+
+static struct kallsyms_query_by_name name_q(const char *name, const char *mod,
+ struct kallsyms_query_sym *sym)
+{
+ return (struct kallsyms_query_by_name){
+ .size = sizeof(struct kallsyms_query_by_name),
+ .query_name = PTR(name),
+ .module_filter = PTR(mod),
+ .sym = PTR(sym),
+ .sym_struct_size = sizeof(*sym),
+ };
+}
+
+static struct kallsyms_query_by_addr addr_q(unsigned long long addr,
+ struct kallsyms_query_sym *sym)
+{
+ return (struct kallsyms_query_by_addr){
+ .size = sizeof(struct kallsyms_query_by_addr),
+ .query_addr = addr,
+ .sym = PTR(sym),
+ .sym_struct_size = sizeof(*sym),
+ };
+}
+
+static int by_name(int fd, void *q)
+{
+ return ioctl(fd, KALLSYMS_QUERY_BY_NAME, q) ? -errno : 0;
+}
+
+static int by_addr(int fd, void *q)
+{
+ return ioctl(fd, KALLSYMS_QUERY_BY_ADDR, q) ? -errno : 0;
+}
+
+int main(void)
+{
+ struct {
+ struct kallsyms_query_by_name q;
+ __u64 extra;
+ } qx = {};
+ struct {
+ struct kallsyms_query_sym s;
+ __u64 extra;
+ } sx = {};
+ struct kallsyms_query_by_addr qa;
+ struct kallsyms_query_by_name q;
+ struct kallsyms_query_sym sym;
+ unsigned int cnt = 0;
+ __u64 first_pos = 0;
+ char name[512];
+ __u64 size;
+ __u64 len;
+ int err;
+ int fd;
+
+ fd = open("/proc/kallsyms", O_RDONLY | O_CLOEXEC);
+ if (fd < 0)
+ ksft_exit_skip("open(/proc/kallsyms): %s\n", strerror(errno));
+ scan_text();
+ if (!ref.vfs_read_found)
+ ksft_exit_skip("vfs_read is not in /proc/kallsyms\n");
+
+ /* Exact name in vmlinux, with name and module buffers */
+ sym = bufs(sizeof(name_buf), sizeof(mod_buf));
+ q = name_q("vfs_read", "vmlinux", &sym);
+ err = by_name(fd, &q);
+ if (err == -ENOTTY)
+ ksft_exit_skip("KALLSYMS_QUERY_BY_NAME is not supported\n");
+ if (!ref.vfs_read_addr) {
+ qa = addr_q(0, &sym);
+ assert(err == -EPERM && by_addr(fd, &qa) == -EPERM);
+ ksft_exit_skip("kallsyms addresses are hidden\n");
+ }
+ assert(!err);
+ assert(sym.sym_addr == ref.vfs_read_addr && sym.sym_size > 0);
+ assert(!strcmp(name_buf, "vfs_read"));
+ assert(sym.sym_name_len == sizeof("vfs_read"));
+ assert(!mod_buf[0] && sym.sym_module_len == 1);
+ size = sym.sym_size;
+
+ /* Prefix: one match per call with pos + 1, until -ENOENT */
+ q = name_q(PREFIX, "vmlinux", &sym);
+ q.query_flags = KALLSYMS_QUERY_NAME_PREFIX;
+ sym = bufs(sizeof(name_buf), 0);
+ while (!(err = by_name(fd, &q))) {
+ assert(!strncmp(name_buf, PREFIX, strlen(PREFIX)));
+ if (!cnt++)
+ first_pos = q.pos;
+ sym = bufs(sizeof(name_buf), 0);
+ q.pos++;
+ }
+ assert(err == -ENOENT && cnt == ref.prefix_cnt);
+
+ /* -ENAMETOOLONG sets pos and both sizes; a retry succeeds */
+ sym = bufs(1, 2);
+ q.pos = 0;
+ assert(by_name(fd, &q) == -ENAMETOOLONG && q.pos == first_pos);
+ assert(sym.sym_module_len == 1);
+ len = sym.sym_name_len;
+ sym.sym_name_len = sizeof(name_buf);
+ assert(by_name(fd, &q) == 0 && q.pos == first_pos);
+ assert(sym.sym_name_len == len && strlen(name_buf) + 1 == len);
+
+ /* No match */
+ sym = bufs(0, 0);
+ q = name_q("__no_such_symbol__", NULL, &sym);
+ assert(by_name(fd, &q) == -ENOENT);
+
+ /* BY_ADDR inside vfs_read */
+ sym = bufs(sizeof(name_buf), 0);
+ qa = addr_q(ref.vfs_read_addr + 1, &sym);
+ assert(by_addr(fd, &qa) == 0 && !strcmp(name_buf, "vfs_read"));
+ assert(sym.sym_addr == ref.vfs_read_addr && sym.sym_size == size);
+
+ sym = bufs(0, 0);
+ qa = addr_q(~0ULL, &sym);
+ assert(by_addr(fd, &qa) == -ENOENT);
+
+ /* Module symbol: BY_ADDR and BY_NAME with module_filter agree */
+ if (ref.mod_addr) {
+ bool found = false;
+
+ sym = bufs(sizeof(name_buf), sizeof(mod_buf));
+ qa = addr_q(ref.mod_addr, &sym);
+ assert(by_addr(fd, &qa) == 0 && sym.sym_addr == ref.mod_addr);
+ assert(!strcmp(mod_buf, ref.mod));
+ strcpy(name, name_buf);
+ size = sym.sym_size;
+
+ q = name_q(name, ref.mod, &sym);
+ sym = bufs(0, sizeof(mod_buf));
+ while (!(err = by_name(fd, &q))) {
+ assert(!strcmp(mod_buf, ref.mod));
+ if (sym.sym_addr == ref.mod_addr) {
+ assert(sym.sym_size == size);
+ found = true;
+ }
+ sym = bufs(0, sizeof(mod_buf));
+ q.pos++;
+ }
+ assert(err == -ENOENT && found);
+ }
+
+ /* Larger structs with a zero tail are accepted */
+ qx.q = name_q("vfs_read", NULL, &sx.s);
+ qx.q.size = sizeof(qx);
+ qx.q.sym_struct_size = sizeof(sx);
+ assert(by_name(fd, &qx) == 0 && sx.s.sym_addr == ref.vfs_read_addr);
+
+ /* Unknown flags and mismatched buffer pointer/length */
+ sym = bufs(0, 0);
+ q = name_q("vfs_read", NULL, &sym);
+ q.query_flags = 1ULL << 63;
+ assert(by_name(fd, &q) == -EINVAL);
+ qa = addr_q(ref.vfs_read_addr, &sym);
+ qa.query_flags = 1;
+ assert(by_addr(fd, &qa) == -EINVAL);
+ sym = (struct kallsyms_query_sym){ .sym_name = PTR(name_buf) };
+ q = name_q("vfs_read", NULL, &sym);
+ assert(by_name(fd, &q) == -EINVAL);
+ sym = (struct kallsyms_query_sym){ .sym_module_len = sizeof(mod_buf) };
+ assert(by_name(fd, &q) == -EINVAL);
+
+ close(fd);
+ return 0;
+}
--
2.56.0.679.gc527c65306-goog
^ permalink raw reply [flat|nested] 12+ messages in thread* [PATCH bpf-next v3 8/9] libbpf: Resolve typeless ksyms via KALLSYMS_QUERY_BY_NAME ioctl
2026-11-13 22:12 [PATCH bpf-next v3 0/9] kallsyms: Add KALLSYMS_QUERY_BY_NAME and KALLSYMS_QUERY_BY_ADDR ioctls Samuel Wu
` (6 preceding siblings ...)
2026-11-13 22:12 ` [PATCH bpf-next v3 7/9] selftests/proc: Add KALLSYMS_QUERY_* ioctl tests Samuel Wu
@ 2026-11-13 22:12 ` Samuel Wu
2026-11-13 22:12 ` [PATCH bpf-next v3 9/9] selftests/bpf: Test typeless module ksym resolution Samuel Wu
8 siblings, 0 replies; 12+ messages in thread
From: Samuel Wu @ 2026-11-13 22:12 UTC (permalink / raw)
To: Jonathan Corbet, Shuah Khan, Randy Dunlap, Luis Chamberlain,
Petr Pavlu, Daniel Gomez, Sami Tolvanen, Aaron Tomlin,
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: Andrew Morton, Petr Mladek, David Laight, Samuel Wu, kernel-team,
linux-kernel, linux-fsdevel, linux-doc, linux-modules, bpf,
linux-kselftest, llvm
libbpf parses all of /proc/kallsyms to resolve typeless ksyms.
Use KALLSYMS_QUERY_BY_NAME instead: exact names, plus "<name>.llvm."
prefix matches. Ambiguous matches at different addresses fail the load.
A missing strong extern still fails and a missing weak extern is 0,
without a text scan.
On any other error (e.g. -ENOTTY on older kernels, or -EPERM), fall back
to the text parser.
Signed-off-by: Samuel Wu <wusamuel@google.com>
---
tools/lib/bpf/libbpf.c | 99 +++++++++++++++++++++++++++++++++++++++++-
1 file changed, 97 insertions(+), 2 deletions(-)
diff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c
index 44134421bd7d..f3f3475a64d3 100644
--- a/tools/lib/bpf/libbpf.c
+++ b/tools/lib/bpf/libbpf.c
@@ -59,6 +59,34 @@
#define BPF_FS_MAGIC 0xcafe4a11
#endif
+#ifndef KALLSYMS_QUERY_BY_NAME
+/* 'f' is PROCFS_IOCTL_MAGIC */
+#define KALLSYMS_QUERY_BY_NAME _IOC(_IOC_READ | _IOC_WRITE, 'f', 48, 0)
+
+enum kallsyms_query_flags {
+ KALLSYMS_QUERY_NAME_PREFIX = 0x01,
+};
+
+struct kallsyms_query_sym {
+ __u64 sym_addr;
+ __u64 sym_size;
+ __u64 sym_name;
+ __u64 sym_name_len;
+ __u64 sym_module;
+ __u64 sym_module_len;
+};
+
+struct kallsyms_query_by_name {
+ __u64 size;
+ __u64 query_flags;
+ __u64 query_name;
+ __u64 module_filter;
+ __u64 pos;
+ __u64 sym;
+ __u64 sym_struct_size;
+};
+#endif
+
#define MAX_EVENT_NAME_LEN 64
#define BPF_FS_DEFAULT_PATH "/sys/fs/bpf"
@@ -9421,8 +9449,75 @@ 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 query_ksym_by_name(int fd, struct extern_desc *ext, bool lto_prefix)
+{
+ struct kallsyms_query_sym sym = {};
+ struct kallsyms_query_by_name q = {
+ .size = sizeof(q),
+ .query_flags = lto_prefix ? KALLSYMS_QUERY_NAME_PREFIX : 0,
+ .query_name = ptr_to_u64(ext->name),
+ .sym = ptr_to_u64(&sym),
+ .sym_struct_size = sizeof(sym),
+ };
+ char prefix_buf[512];
+ int n;
+
+ if (lto_prefix) {
+ n = snprintf(prefix_buf, sizeof(prefix_buf), "%s.llvm.", ext->name);
+ if (n < 0 || n >= sizeof(prefix_buf))
+ return 0;
+ q.query_name = ptr_to_u64(prefix_buf);
+ }
+
+ while (!ioctl(fd, KALLSYMS_QUERY_BY_NAME, &q)) {
+ if (!ext->is_set) {
+ ext->is_set = true;
+ ext->ksym.addr = sym.sym_addr;
+ pr_debug("extern (ksym) '%s': set to 0x%llx\n",
+ ext->name, ext->ksym.addr);
+ } else if (sym.sym_addr != ext->ksym.addr) {
+ pr_warn("extern (ksym) '%s': resolution is ambiguous: 0x%llx or 0x%llx\n",
+ ext->name, ext->ksym.addr,
+ (unsigned long long)sym.sym_addr);
+ return -EADDRNOTAVAIL;
+ }
+ q.pos++;
+ }
+
+ return errno == ENOENT ? 0 : -errno;
+}
+
+static int bpf_object__resolve_typeless_ksyms(struct bpf_object *obj)
{
+ struct extern_desc *ext;
+ int err = 0;
+ int fd;
+ int i;
+
+ fd = open("/proc/kallsyms", O_RDONLY | O_CLOEXEC);
+ if (fd < 0) {
+ err = -errno;
+ pr_warn("failed to open /proc/kallsyms: %s\n", errstr(err));
+ return err;
+ }
+
+ for (i = 0; i < obj->nr_extern && !err; i++) {
+ ext = &obj->externs[i];
+ if (ext->type != EXT_KSYM || ext->ksym.type_id)
+ continue;
+
+ /* Check exact name and then LTO .llvm.<hash> prefix */
+ err = query_ksym_by_name(fd, ext, false);
+ if (!err)
+ err = query_ksym_by_name(fd, ext, true);
+ }
+ close(fd);
+ if (!err || err == -EADDRNOTAVAIL)
+ return err;
+
+ /* Other errors (e.g. -ENOTTY, -EPERM): parse the text file */
+ pr_debug("KALLSYMS_QUERY_BY_NAME failed (%s), falling back to /proc/kallsyms\n",
+ errstr(err));
return libbpf_kallsyms_parse(kallsyms_cb, obj);
}
@@ -9699,7 +9794,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.679.gc527c65306-goog
^ permalink raw reply [flat|nested] 12+ messages in thread* [PATCH bpf-next v3 9/9] selftests/bpf: Test typeless module ksym resolution
2026-11-13 22:12 [PATCH bpf-next v3 0/9] kallsyms: Add KALLSYMS_QUERY_BY_NAME and KALLSYMS_QUERY_BY_ADDR ioctls Samuel Wu
` (7 preceding siblings ...)
2026-11-13 22:12 ` [PATCH bpf-next v3 8/9] libbpf: Resolve typeless ksyms via KALLSYMS_QUERY_BY_NAME ioctl Samuel Wu
@ 2026-11-13 22:12 ` Samuel Wu
8 siblings, 0 replies; 12+ messages in thread
From: Samuel Wu @ 2026-11-13 22:12 UTC (permalink / raw)
To: Jonathan Corbet, Shuah Khan, Randy Dunlap, Luis Chamberlain,
Petr Pavlu, Daniel Gomez, Sami Tolvanen, Aaron Tomlin,
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: Andrew Morton, Petr Mladek, David Laight, Samuel Wu, kernel-team,
linux-kernel, linux-fsdevel, linux-doc, linux-modules, bpf,
linux-kselftest, llvm
The module ksym tests use typed ksyms, which libbpf resolves through
BTF. Add a weak typeless module ksym to the ksyms test, to cover the
module path of KALLSYMS_QUERY_BY_NAME.
Signed-off-by: Samuel Wu <wusamuel@google.com>
---
tools/testing/selftests/bpf/prog_tests/ksyms.c | 9 +++++++++
tools/testing/selftests/bpf/progs/test_ksyms.c | 4 ++++
2 files changed, 13 insertions(+)
diff --git a/tools/testing/selftests/bpf/prog_tests/ksyms.c b/tools/testing/selftests/bpf/prog_tests/ksyms.c
index dc7aab532fb1..801d38560473 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 = 0;
struct stat st;
__u64 btf_size;
int err;
@@ -27,6 +28,12 @@ void test_ksyms(void)
if (!ASSERT_NEQ(err, -ENOENT, "__per_cpu_start: ksym_find"))
return;
+ if (env.has_testmod &&
+ !ASSERT_OK(kallsyms_find("bpf_testmod_ksym_percpu",
+ &testmod_ksym_addr),
+ "bpf_testmod_ksym_percpu: kallsyms_find"))
+ return;
+
if (!ASSERT_OK(stat(btf_path, &st), "stat_btf"))
return;
btf_size = st.st_size;
@@ -47,6 +54,8 @@ void test_ksyms(void)
ASSERT_EQ(data->out__bpf_link_fops1, 0, "bpf_link_fops1");
ASSERT_EQ(data->out__btf_size, btf_size, "btf_size");
ASSERT_EQ(data->out__per_cpu_start, per_cpu_start_addr, "__per_cpu_start");
+ ASSERT_EQ(data->out__bpf_testmod_ksym_percpu, 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..0506bb574c4a 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_percpu = -1;
extern const void bpf_link_fops __ksym;
extern const void __start_BTF __ksym;
@@ -16,6 +17,8 @@ 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;
+/* module symbol, weak, zero if bpf_testmod is not loaded */
+extern const void bpf_testmod_ksym_percpu __ksym __weak;
SEC("raw_tp/sys_enter")
int handler(const void *ctx)
@@ -25,6 +28,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_percpu = (__u64)&bpf_testmod_ksym_percpu;
return 0;
}
--
2.56.0.679.gc527c65306-goog
^ permalink raw reply [flat|nested] 12+ messages in thread