Add a KUnit test module (CONFIG_LINEINFO_KUNIT_TEST) that verifies the
kallsyms lineinfo feature produces correct source file:line annotations
in stack traces.

Export sprint_backtrace() and sprint_backtrace_build_id() as GPL symbols
so the test module can exercise the backtrace APIs.

Assisted-by: Claude:claude-opus-4-6
Signed-off-by: Sasha Levin <[email protected]>
---
 MAINTAINERS                |   1 +
 kernel/kallsyms.c          |   2 +
 lib/Kconfig.debug          |  10 +
 lib/tests/Makefile         |   3 +
 lib/tests/lineinfo_kunit.c | 772 +++++++++++++++++++++++++++++++++++++
 5 files changed, 788 insertions(+)
 create mode 100644 lib/tests/lineinfo_kunit.c

diff --git a/MAINTAINERS b/MAINTAINERS
index 535e992ca5a20..118711f72b874 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -13733,6 +13733,7 @@ M:      Sasha Levin <[email protected]>
 S:     Maintained
 F:     Documentation/admin-guide/kallsyms-lineinfo.rst
 F:     include/linux/mod_lineinfo.h
+F:     lib/tests/lineinfo_kunit.c
 F:     scripts/gen-mod-lineinfo.sh
 F:     scripts/gen_lineinfo.c
 
diff --git a/kernel/kallsyms.c b/kernel/kallsyms.c
index bbc4d59243dd4..41b94018a3345 100644
--- a/kernel/kallsyms.c
+++ b/kernel/kallsyms.c
@@ -703,6 +703,7 @@ int sprint_backtrace(char *buffer, unsigned long address)
 {
        return __sprint_symbol(buffer, address, -1, 1, 0);
 }
+EXPORT_SYMBOL_GPL(sprint_backtrace);
 
 /**
  * sprint_backtrace_build_id - Look up a backtrace symbol and return it in a 
text buffer
@@ -723,6 +724,7 @@ int sprint_backtrace_build_id(char *buffer, unsigned long 
address)
 {
        return __sprint_symbol(buffer, address, -1, 1, 1);
 }
+EXPORT_SYMBOL_GPL(sprint_backtrace_build_id);
 
 /* To avoid using get_symbol_offset for every symbol, we carry prefix along. */
 struct kallsym_iter {
diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug
index 93f356d2b3d95..688bbcb3eaa62 100644
--- a/lib/Kconfig.debug
+++ b/lib/Kconfig.debug
@@ -3048,6 +3048,16 @@ config LONGEST_SYM_KUNIT_TEST
 
          If unsure, say N.
 
+config LINEINFO_KUNIT_TEST
+       tristate "KUnit tests for kallsyms lineinfo" if !KUNIT_ALL_TESTS
+       depends on KUNIT && KALLSYMS_LINEINFO
+       default KUNIT_ALL_TESTS
+       help
+         KUnit tests for the kallsyms source line info feature.
+         Verifies that stack traces include correct (file.c:line) annotations.
+
+         If unsure, say N.
+
 config HW_BREAKPOINT_KUNIT_TEST
        bool "Test hw_breakpoint constraints accounting" if !KUNIT_ALL_TESTS
        depends on HAVE_HW_BREAKPOINT
diff --git a/lib/tests/Makefile b/lib/tests/Makefile
index 05f74edbc62bf..c0d080e7fa123 100644
--- a/lib/tests/Makefile
+++ b/lib/tests/Makefile
@@ -36,6 +36,9 @@ obj-$(CONFIG_LIVEUPDATE_TEST) += liveupdate.o
 CFLAGS_longest_symbol_kunit.o += $(call cc-disable-warning, missing-prototypes)
 obj-$(CONFIG_LONGEST_SYM_KUNIT_TEST) += longest_symbol_kunit.o
 
+CFLAGS_lineinfo_kunit.o += -fno-inline-functions-called-once
+obj-$(CONFIG_LINEINFO_KUNIT_TEST) += lineinfo_kunit.o
+
 obj-$(CONFIG_MEMCPY_KUNIT_TEST) += memcpy_kunit.o
 obj-$(CONFIG_MIN_HEAP_KUNIT_TEST) += min_heap_kunit.o
 CFLAGS_overflow_kunit.o = $(call cc-disable-warning, 
tautological-constant-out-of-range-compare)
diff --git a/lib/tests/lineinfo_kunit.c b/lib/tests/lineinfo_kunit.c
new file mode 100644
index 0000000000000..cb827cd0d6ccb
--- /dev/null
+++ b/lib/tests/lineinfo_kunit.c
@@ -0,0 +1,772 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * KUnit tests for kallsyms lineinfo (CONFIG_KALLSYMS_LINEINFO).
+ *
+ * Copyright (c) 2026 Sasha Levin <[email protected]>
+ *
+ * Verifies that sprint_symbol() and related APIs append correct
+ * " (file.c:NNN)" annotations to kernel symbol lookups.
+ *
+ * Build with: CONFIG_LINEINFO_KUNIT_TEST=m (or =y)
+ * Run with:   ./tools/testing/kunit/kunit.py run lineinfo
+ */
+
+#include <kunit/test.h>
+#include <linux/kallsyms.h>
+#include <linux/module.h>
+#include <linux/smp.h>
+#include <linux/string.h>
+#include <linux/slab.h>
+#include <linux/mod_lineinfo.h>
+
+/* --------------- helpers --------------- */
+
+static char *alloc_sym_buf(struct kunit *test)
+{
+       return kunit_kzalloc(test, KSYM_SYMBOL_LEN, GFP_KERNEL);
+}
+
+/*
+ * Return true if @buf contains a lineinfo annotation matching
+ * the pattern " (<path>:<digits>)".
+ *
+ * The path may be a full path like "lib/tests/lineinfo_kunit.c" or
+ * a shortened form from module lineinfo (e.g., just a directory name).
+ */
+static bool has_lineinfo(const char *buf)
+{
+       const char *p, *colon, *end;
+
+       p = strstr(buf, " (");
+       if (!p)
+               return false;
+       p += 2; /* skip " (" */
+
+       colon = strchr(p, ':');
+       if (!colon || colon == p)
+               return false;
+
+       /* After colon: one or more digits then ')' */
+       end = colon + 1;
+       if (*end < '0' || *end > '9')
+               return false;
+       while (*end >= '0' && *end <= '9')
+               end++;
+       return *end == ')';
+}
+
+/*
+ * Extract line number from a lineinfo annotation.
+ * Returns 0 if not found.
+ */
+static unsigned int extract_line(const char *buf)
+{
+       const char *p, *colon;
+       unsigned int line = 0;
+
+       p = strstr(buf, " (");
+       if (!p)
+               return 0;
+
+       colon = strchr(p + 2, ':');
+       if (!colon)
+               return 0;
+
+       colon++;
+       while (*colon >= '0' && *colon <= '9') {
+               line = line * 10 + (*colon - '0');
+               colon++;
+       }
+       return line;
+}
+
+/*
+ * Check if the lineinfo annotation contains the given filename substring.
+ */
+static bool lineinfo_contains_file(const char *buf, const char *name)
+{
+       const char *p, *colon;
+
+       p = strstr(buf, " (");
+       if (!p)
+               return false;
+
+       colon = strchr(p + 2, ':');
+       if (!colon)
+               return false;
+
+       /* Search for @name between '(' and ':' */
+       return strnstr(p + 1, name, colon - p - 1) != NULL;
+}
+
+/* --------------- target functions --------------- */
+
+static noinline int lineinfo_target_normal(void)
+{
+       barrier();
+       return 42;
+}
+
+static noinline int lineinfo_target_short(void)
+{
+       barrier();
+       return 1;
+}
+
+static noinline int lineinfo_target_with_arg(int x)
+{
+       barrier();
+       return x + 1;
+}
+
+static noinline int lineinfo_target_many_lines(void)
+{
+       int a = 0;
+
+       barrier();
+       a += 1;
+       a += 2;
+       a += 3;
+       a += 4;
+       a += 5;
+       a += 6;
+       a += 7;
+       a += 8;
+       a += 9;
+       a += 10;
+       barrier();
+       return a;
+}
+
+static __always_inline int lineinfo_inline_helper(void)
+{
+       return 99;
+}
+
+static noinline int lineinfo_inline_caller(void)
+{
+       barrier();
+       return lineinfo_inline_helper();
+}
+
+/* 10-deep call chain */
+static noinline int lineinfo_chain_10(void) { barrier(); return 10; }
+static noinline int lineinfo_chain_9(void)  { barrier(); return 
lineinfo_chain_10(); }
+static noinline int lineinfo_chain_8(void)  { barrier(); return 
lineinfo_chain_9(); }
+static noinline int lineinfo_chain_7(void)  { barrier(); return 
lineinfo_chain_8(); }
+static noinline int lineinfo_chain_6(void)  { barrier(); return 
lineinfo_chain_7(); }
+static noinline int lineinfo_chain_5(void)  { barrier(); return 
lineinfo_chain_6(); }
+static noinline int lineinfo_chain_4(void)  { barrier(); return 
lineinfo_chain_5(); }
+static noinline int lineinfo_chain_3(void)  { barrier(); return 
lineinfo_chain_4(); }
+static noinline int lineinfo_chain_2(void)  { barrier(); return 
lineinfo_chain_3(); }
+static noinline int lineinfo_chain_1(void)  { barrier(); return 
lineinfo_chain_2(); }
+
+/* --------------- Group A: Basic lineinfo presence --------------- */
+
+static void test_normal_function(struct kunit *test)
+{
+       char *buf = alloc_sym_buf(test);
+       unsigned long addr = (unsigned long)lineinfo_target_normal;
+
+       sprint_symbol(buf, addr);
+       KUNIT_EXPECT_TRUE_MSG(test, has_lineinfo(buf),
+                             "No lineinfo in: %s", buf);
+       KUNIT_EXPECT_TRUE_MSG(test,
+                             lineinfo_contains_file(buf, "lineinfo_kunit.c"),
+                             "Wrong file in: %s", buf);
+}
+
+static void test_static_function(struct kunit *test)
+{
+       char *buf = alloc_sym_buf(test);
+       unsigned long addr = (unsigned long)lineinfo_target_short;
+
+       sprint_symbol(buf, addr);
+       KUNIT_EXPECT_TRUE_MSG(test, has_lineinfo(buf),
+                             "No lineinfo in: %s", buf);
+}
+
+static void test_noinline_function(struct kunit *test)
+{
+       char *buf = alloc_sym_buf(test);
+       unsigned long addr = (unsigned long)lineinfo_target_with_arg;
+
+       sprint_symbol(buf, addr);
+       KUNIT_EXPECT_TRUE_MSG(test, has_lineinfo(buf),
+                             "No lineinfo in: %s", buf);
+}
+
+static void test_inline_function(struct kunit *test)
+{
+       char *buf = alloc_sym_buf(test);
+       unsigned long addr = (unsigned long)lineinfo_inline_caller;
+
+       sprint_symbol(buf, addr);
+       KUNIT_EXPECT_TRUE_MSG(test, has_lineinfo(buf),
+                             "No lineinfo for inline caller in: %s", buf);
+       KUNIT_EXPECT_TRUE_MSG(test,
+                             lineinfo_contains_file(buf, "lineinfo_kunit.c"),
+                             "Wrong file in: %s", buf);
+}
+
+static void test_short_function(struct kunit *test)
+{
+       char *buf = alloc_sym_buf(test);
+       unsigned long addr = (unsigned long)lineinfo_target_short;
+
+       sprint_symbol(buf, addr);
+       KUNIT_EXPECT_TRUE_MSG(test, has_lineinfo(buf),
+                             "No lineinfo for short function in: %s", buf);
+}
+
+static void test_many_lines_function(struct kunit *test)
+{
+       char *buf = alloc_sym_buf(test);
+       unsigned long addr = (unsigned long)lineinfo_target_many_lines;
+       unsigned int line;
+
+       sprint_symbol(buf, addr);
+       KUNIT_EXPECT_TRUE_MSG(test, has_lineinfo(buf),
+                             "No lineinfo in: %s", buf);
+       line = extract_line(buf);
+       KUNIT_EXPECT_GT_MSG(test, line, (unsigned int)0,
+                           "Line number should be > 0 in: %s", buf);
+}
+
+/* --------------- Group B: Deep call chain --------------- */
+
+typedef int (*chain_fn_t)(void);
+
+static void test_deep_call_chain(struct kunit *test)
+{
+       static const chain_fn_t chain_fns[] = {
+               lineinfo_chain_1,  lineinfo_chain_2,
+               lineinfo_chain_3,  lineinfo_chain_4,
+               lineinfo_chain_5,  lineinfo_chain_6,
+               lineinfo_chain_7,  lineinfo_chain_8,
+               lineinfo_chain_9,  lineinfo_chain_10,
+       };
+       char *buf = alloc_sym_buf(test);
+       int i, found = 0;
+
+       /* Call chain to prevent dead-code elimination */
+       KUNIT_ASSERT_EQ(test, lineinfo_chain_1(), 10);
+
+       for (i = 0; i < ARRAY_SIZE(chain_fns); i++) {
+               unsigned long addr = (unsigned long)chain_fns[i];
+
+               sprint_symbol(buf, addr);
+               if (has_lineinfo(buf))
+                       found++;
+       }
+
+       /*
+        * Not every tiny function gets DWARF line info (compiler may
+        * omit it for very small stubs), but at least some should.
+        */
+       KUNIT_EXPECT_GT_MSG(test, found, 0,
+                           "None of the 10 chain functions had lineinfo");
+}
+
+/* --------------- Group C: sprint_symbol API variants --------------- */
+
+static void test_sprint_symbol_format(struct kunit *test)
+{
+       char *buf = alloc_sym_buf(test);
+       unsigned long addr = (unsigned long)lineinfo_target_normal;
+
+       sprint_symbol(buf, addr);
+
+       /* Should contain +0x and /0x for offset/size */
+       KUNIT_EXPECT_NOT_NULL_MSG(test, strstr(buf, "+0x"),
+                                 "Missing offset in: %s", buf);
+       KUNIT_EXPECT_NOT_NULL_MSG(test, strstr(buf, "/0x"),
+                                 "Missing size in: %s", buf);
+       KUNIT_EXPECT_TRUE_MSG(test, has_lineinfo(buf),
+                             "No lineinfo in: %s", buf);
+}
+
+static void test_sprint_backtrace(struct kunit *test)
+{
+       char *buf = alloc_sym_buf(test);
+       unsigned long addr = (unsigned long)lineinfo_target_normal;
+
+       /* sprint_backtrace subtracts 1 internally to handle tail calls */
+       sprint_backtrace(buf, addr + 1);
+       KUNIT_EXPECT_TRUE_MSG(test, has_lineinfo(buf),
+                             "No lineinfo in backtrace: %s", buf);
+       KUNIT_EXPECT_TRUE_MSG(test,
+                             lineinfo_contains_file(buf, "lineinfo_kunit.c"),
+                             "Wrong file in backtrace: %s", buf);
+}
+
+static void test_sprint_backtrace_build_id(struct kunit *test)
+{
+       char *buf = alloc_sym_buf(test);
+       unsigned long addr = (unsigned long)lineinfo_target_normal;
+
+       sprint_backtrace_build_id(buf, addr + 1);
+       KUNIT_EXPECT_TRUE_MSG(test, has_lineinfo(buf),
+                             "No lineinfo in backtrace_build_id: %s", buf);
+}
+
+static void test_sprint_symbol_no_offset(struct kunit *test)
+{
+       char *buf = alloc_sym_buf(test);
+       unsigned long addr = (unsigned long)lineinfo_target_normal;
+
+       sprint_symbol_no_offset(buf, addr);
+       /* No "+0x" in output */
+       KUNIT_EXPECT_NULL_MSG(test, strstr(buf, "+0x"),
+                             "Unexpected offset in no_offset: %s", buf);
+       KUNIT_EXPECT_TRUE_MSG(test, has_lineinfo(buf),
+                             "No lineinfo in no_offset: %s", buf);
+}
+
+/* --------------- Group D: printk format specifiers --------------- */
+
+static void test_pS_format(struct kunit *test)
+{
+       char *buf = alloc_sym_buf(test);
+       void *addr = lineinfo_target_normal;
+
+       snprintf(buf, KSYM_SYMBOL_LEN, "%pS", addr);
+       KUNIT_EXPECT_TRUE_MSG(test, has_lineinfo(buf),
+                             "No lineinfo in %%pS: %s", buf);
+}
+
+static void test_pBb_format(struct kunit *test)
+{
+       char *buf = alloc_sym_buf(test);
+       /*
+        * %pBb uses sprint_backtrace_build_id which subtracts 1 from the
+        * address, so pass addr+1 to resolve back to the function.
+        */
+       void *addr = (void *)((unsigned long)lineinfo_target_normal + 1);
+
+       snprintf(buf, KSYM_SYMBOL_LEN, "%pBb", addr);
+       KUNIT_EXPECT_TRUE_MSG(test, has_lineinfo(buf),
+                             "No lineinfo in %%pBb: %s", buf);
+}
+
+static void test_pSR_format(struct kunit *test)
+{
+       char *buf = alloc_sym_buf(test);
+       void *addr = lineinfo_target_normal;
+
+       snprintf(buf, KSYM_SYMBOL_LEN, "%pSR", addr);
+       KUNIT_EXPECT_TRUE_MSG(test, has_lineinfo(buf),
+                             "No lineinfo in %%pSR: %s", buf);
+}
+
+/* --------------- Group E: Address edge cases --------------- */
+
+static void test_symbol_start_addr(struct kunit *test)
+{
+       char *buf = alloc_sym_buf(test);
+       unsigned long addr = (unsigned long)lineinfo_target_normal;
+
+       sprint_symbol(buf, addr);
+       KUNIT_EXPECT_NOT_NULL_MSG(test, strstr(buf, "+0x0/"),
+                                 "Expected +0x0/ at function start: %s", buf);
+       KUNIT_EXPECT_TRUE_MSG(test, has_lineinfo(buf),
+                             "No lineinfo at function start: %s", buf);
+}
+
+static void test_symbol_nonzero_offset(struct kunit *test)
+{
+       char *buf = alloc_sym_buf(test);
+       unsigned long addr = (unsigned long)lineinfo_target_normal;
+
+       /*
+        * sprint_backtrace subtracts 1 internally.
+        * Passing addr+2 resolves to addr+1 which is inside the function
+        * at a non-zero offset.
+        */
+       sprint_backtrace(buf, addr + 2);
+       KUNIT_EXPECT_TRUE_MSG(test,
+                             strnstr(buf, "lineinfo_target_normal",
+                                     KSYM_SYMBOL_LEN) != NULL,
+                             "Didn't resolve to expected function: %s", buf);
+       KUNIT_EXPECT_TRUE_MSG(test, has_lineinfo(buf),
+                             "No lineinfo at non-zero offset: %s", buf);
+}
+
+static void test_unknown_address(struct kunit *test)
+{
+       char *buf = alloc_sym_buf(test);
+
+       sprint_symbol(buf, 1UL);
+       /* Should be "0x1" with no lineinfo */
+       KUNIT_EXPECT_NOT_NULL_MSG(test, strstr(buf, "0x1"),
+                                 "Expected hex address for bogus addr: %s", 
buf);
+       KUNIT_EXPECT_FALSE_MSG(test, has_lineinfo(buf),
+                              "Unexpected lineinfo for bogus addr: %s", buf);
+}
+
+static void test_kernel_function_lineinfo(struct kunit *test)
+{
+       char *buf = alloc_sym_buf(test);
+       unsigned long addr = (unsigned long)sprint_symbol;
+
+       sprint_symbol(buf, addr);
+       KUNIT_EXPECT_TRUE_MSG(test, has_lineinfo(buf),
+                             "No lineinfo for sprint_symbol: %s", buf);
+       KUNIT_EXPECT_TRUE_MSG(test,
+                             lineinfo_contains_file(buf, "kallsyms.c"),
+                             "Expected kallsyms.c in: %s", buf);
+}
+
+static void test_assembly_no_lineinfo(struct kunit *test)
+{
+#if IS_BUILTIN(CONFIG_LINEINFO_KUNIT_TEST)
+       char *buf = alloc_sym_buf(test);
+       unsigned long addr = (unsigned long)_text;
+
+       sprint_symbol(buf, addr);
+       /*
+        * _text is typically an asm entry point with no DWARF line info.
+        * If it has lineinfo, it's a C-based entry — skip in that case.
+        */
+       if (has_lineinfo(buf))
+               kunit_skip(test, "_text has lineinfo (C entry?): %s", buf);
+
+       KUNIT_EXPECT_FALSE_MSG(test, has_lineinfo(buf),
+                              "Unexpected lineinfo for asm symbol: %s", buf);
+#else
+       kunit_skip(test, "_text not accessible from modules");
+#endif
+}
+
+/* --------------- Group F: Module path --------------- */
+
+static void test_module_function_lineinfo(struct kunit *test)
+{
+       char *buf = alloc_sym_buf(test);
+       unsigned long addr = (unsigned long)lineinfo_target_normal;
+
+       if (!IS_MODULE(CONFIG_LINEINFO_KUNIT_TEST)) {
+               kunit_skip(test, "Test only meaningful when built as module");
+               return;
+       }
+
+       sprint_symbol(buf, addr);
+       KUNIT_EXPECT_NOT_NULL_MSG(test,
+                                 strstr(buf, "[lineinfo_kunit"),
+                                 "Missing module name in: %s", buf);
+       KUNIT_EXPECT_TRUE_MSG(test, has_lineinfo(buf),
+                             "No lineinfo for module function: %s", buf);
+       KUNIT_EXPECT_TRUE_MSG(test,
+                             lineinfo_contains_file(buf, "lineinfo_kunit.c"),
+                             "Wrong file for module function: %s", buf);
+}
+
+/* --------------- Group G: Stress --------------- */
+
+struct lineinfo_stress_data {
+       unsigned long addr;
+       atomic_t failures;
+};
+
+static void lineinfo_stress_fn(void *info)
+{
+       struct lineinfo_stress_data *data = info;
+       char buf[KSYM_SYMBOL_LEN];
+       int i;
+
+       for (i = 0; i < 100; i++) {
+               sprint_symbol(buf, data->addr);
+               if (!has_lineinfo(buf))
+                       atomic_inc(&data->failures);
+       }
+}
+
+static void test_concurrent_sprint_symbol(struct kunit *test)
+{
+       struct lineinfo_stress_data data;
+
+       data.addr = (unsigned long)lineinfo_target_normal;
+       atomic_set(&data.failures, 0);
+
+       on_each_cpu(lineinfo_stress_fn, &data, 1);
+
+       KUNIT_EXPECT_EQ_MSG(test, atomic_read(&data.failures), 0,
+                           "Concurrent lineinfo failures detected");
+}
+
+static void test_rapid_sprint_symbol(struct kunit *test)
+{
+       char *buf = alloc_sym_buf(test);
+       unsigned long addr = (unsigned long)lineinfo_target_normal;
+       int i, failures = 0;
+
+       for (i = 0; i < 1000; i++) {
+               sprint_symbol(buf, addr);
+               if (!has_lineinfo(buf))
+                       failures++;
+       }
+
+       KUNIT_EXPECT_EQ_MSG(test, failures, 0,
+                           "Rapid sprint_symbol failures: %d/1000", failures);
+}
+
+/* --------------- Group H: Safety and plausibility --------------- */
+
+static void test_line_number_plausible(struct kunit *test)
+{
+       char *buf = alloc_sym_buf(test);
+       unsigned long addr = (unsigned long)lineinfo_target_normal;
+       unsigned int line;
+
+       sprint_symbol(buf, addr);
+       KUNIT_ASSERT_TRUE(test, has_lineinfo(buf));
+
+       line = extract_line(buf);
+       KUNIT_EXPECT_GT_MSG(test, line, (unsigned int)0,
+                           "Line number should be > 0");
+       KUNIT_EXPECT_LT_MSG(test, line, (unsigned int)10000,
+                           "Line number %u implausibly large for this file",
+                           line);
+}
+
+static void test_buffer_no_overflow(struct kunit *test)
+{
+       const size_t canary_size = 16;
+       char *buf;
+       int i;
+
+       buf = kunit_kzalloc(test, KSYM_SYMBOL_LEN + canary_size, GFP_KERNEL);
+       KUNIT_ASSERT_NOT_NULL(test, buf);
+
+       /* Fill canary area past KSYM_SYMBOL_LEN with 0xAA */
+       memset(buf + KSYM_SYMBOL_LEN, 0xAA, canary_size);
+
+       sprint_symbol(buf, (unsigned long)lineinfo_target_normal);
+
+       /* Verify canary bytes are untouched */
+       for (i = 0; i < canary_size; i++) {
+               KUNIT_EXPECT_EQ_MSG(test,
+                                   (unsigned char)buf[KSYM_SYMBOL_LEN + i],
+                                   (unsigned char)0xAA,
+                                   "Buffer overflow at offset %d past 
KSYM_SYMBOL_LEN",
+                                   i);
+       }
+}
+
+static void test_dump_stack_no_crash(struct kunit *test)
+{
+       /* Just verify dump_stack() completes without panic */
+       dump_stack();
+       KUNIT_SUCCEED(test);
+}
+
+static void test_sprint_symbol_build_id(struct kunit *test)
+{
+       char *buf = alloc_sym_buf(test);
+       unsigned long addr = (unsigned long)lineinfo_target_normal;
+
+       sprint_symbol_build_id(buf, addr);
+       KUNIT_EXPECT_TRUE_MSG(test, has_lineinfo(buf),
+                             "No lineinfo in sprint_symbol_build_id: %s", buf);
+}
+
+static void test_zigzag_roundtrip(struct kunit *test)
+{
+       static const int32_t vals[] = {
+               0, -1, 1, -2, 2, -128, 127, -32768, 32767,
+               S32_MIN, S32_MAX,
+       };
+       int i;
+
+       for (i = 0; i < ARRAY_SIZE(vals); i++) {
+               u32 encoded = zigzag_encode(vals[i]);
+               int32_t decoded = zigzag_decode(encoded);
+
+               KUNIT_EXPECT_EQ_MSG(test, decoded, vals[i],
+                                   "zigzag roundtrip failed for %d 
(encoded=%u)",
+                                   vals[i], encoded);
+       }
+
+       /* Verify specific known encodings */
+       KUNIT_EXPECT_EQ(test, zigzag_encode(0), (u32)0);
+       KUNIT_EXPECT_EQ(test, zigzag_encode(-1), (u32)1);
+       KUNIT_EXPECT_EQ(test, zigzag_encode(1), (u32)2);
+       KUNIT_EXPECT_EQ(test, zigzag_encode(S32_MIN), (u32)0xFFFFFFFF);
+       KUNIT_EXPECT_EQ(test, zigzag_encode(S32_MAX), (u32)0xFFFFFFFE);
+}
+
+static void test_uleb128_edge_cases(struct kunit *test)
+{
+       u32 pos, result;
+
+       /* Value 0: single byte 0x00 */
+       {
+               static const u8 data[] = { 0x00 };
+
+               pos = 0;
+               result = lineinfo_read_uleb128(data, &pos, sizeof(data));
+               KUNIT_EXPECT_EQ(test, result, (u32)0);
+               KUNIT_EXPECT_EQ(test, pos, (u32)1);
+       }
+
+       /* Value 127: single byte 0x7F */
+       {
+               static const u8 data[] = { 0x7F };
+
+               pos = 0;
+               result = lineinfo_read_uleb128(data, &pos, sizeof(data));
+               KUNIT_EXPECT_EQ(test, result, (u32)127);
+               KUNIT_EXPECT_EQ(test, pos, (u32)1);
+       }
+
+       /* Value 128: two bytes 0x80 0x01 */
+       {
+               static const u8 data[] = { 0x80, 0x01 };
+
+               pos = 0;
+               result = lineinfo_read_uleb128(data, &pos, sizeof(data));
+               KUNIT_EXPECT_EQ(test, result, (u32)128);
+               KUNIT_EXPECT_EQ(test, pos, (u32)2);
+       }
+
+       /* Max u32 0xFFFFFFFF: 5 bytes */
+       {
+               static const u8 data[] = { 0xFF, 0xFF, 0xFF, 0xFF, 0x0F };
+
+               pos = 0;
+               result = lineinfo_read_uleb128(data, &pos, sizeof(data));
+               KUNIT_EXPECT_EQ(test, result, (u32)0xFFFFFFFF);
+               KUNIT_EXPECT_EQ(test, pos, (u32)5);
+       }
+
+       /* Truncated input: pos >= end returns 0 */
+       {
+               static const u8 data[] = { 0x80 };
+
+               pos = 0;
+               result = lineinfo_read_uleb128(data, &pos, 0);
+               KUNIT_EXPECT_EQ_MSG(test, result, (u32)0,
+                                   "Expected 0 for empty input");
+       }
+
+       /* Truncated mid-varint: continuation byte but end reached */
+       {
+               static const u8 data[] = { 0x80 };
+
+               pos = 0;
+               result = lineinfo_read_uleb128(data, &pos, 1);
+               KUNIT_EXPECT_EQ_MSG(test, result, (u32)0,
+                                   "Expected 0 for truncated varint");
+               KUNIT_EXPECT_EQ(test, pos, (u32)1);
+       }
+}
+
+static void test_line_number_accuracy(struct kunit *test)
+{
+       char *buf = alloc_sym_buf(test);
+       unsigned long addr = (unsigned long)lineinfo_target_normal;
+       unsigned int line;
+
+       sprint_symbol(buf, addr);
+       KUNIT_ASSERT_TRUE(test, has_lineinfo(buf));
+
+       line = extract_line(buf);
+
+       /*
+        * lineinfo_target_normal is defined around line 103-107.
+        * Allow wide range: KASAN instrumentation and module lineinfo
+        * address mapping can shift the reported line significantly.
+        */
+       KUNIT_EXPECT_GE_MSG(test, line, (unsigned int)50,
+                           "Line %u too low for lineinfo_target_normal", line);
+       KUNIT_EXPECT_LE_MSG(test, line, (unsigned int)300,
+                           "Line %u too high for lineinfo_target_normal", 
line);
+}
+
+static void test_many_lines_mid_function(struct kunit *test)
+{
+       char *buf = alloc_sym_buf(test);
+       unsigned long addr = (unsigned long)lineinfo_target_many_lines;
+       unsigned int line;
+       unsigned long mid_addr;
+
+       /* Get function size from sprint_symbol output */
+       sprint_symbol(buf, addr);
+       KUNIT_ASSERT_TRUE(test, has_lineinfo(buf));
+
+       /* Try an address 8 bytes into the function (past prologue) */
+       mid_addr = addr + 8;
+       sprint_symbol(buf, mid_addr);
+
+       /*
+        * Should still resolve to lineinfo_target_many_lines.
+        * Lineinfo should be present with a plausible line number.
+        */
+       KUNIT_EXPECT_TRUE_MSG(test,
+                             strnstr(buf, "lineinfo_target_many_lines",
+                                     KSYM_SYMBOL_LEN) != NULL,
+                             "Mid-function addr resolved to wrong symbol: %s",
+                             buf);
+       if (has_lineinfo(buf)) {
+               line = extract_line(buf);
+               KUNIT_EXPECT_GE_MSG(test, line, (unsigned int)50,
+                                   "Line %u too low for mid-function", line);
+               KUNIT_EXPECT_LE_MSG(test, line, (unsigned int)700,
+                                   "Line %u too high for mid-function", line);
+       }
+}
+
+/* --------------- Suite registration --------------- */
+
+static struct kunit_case lineinfo_test_cases[] = {
+       /* Group A: Basic lineinfo presence */
+       KUNIT_CASE(test_normal_function),
+       KUNIT_CASE(test_static_function),
+       KUNIT_CASE(test_noinline_function),
+       KUNIT_CASE(test_inline_function),
+       KUNIT_CASE(test_short_function),
+       KUNIT_CASE(test_many_lines_function),
+       /* Group B: Deep call chain */
+       KUNIT_CASE(test_deep_call_chain),
+       /* Group C: sprint_symbol API variants */
+       KUNIT_CASE(test_sprint_symbol_format),
+       KUNIT_CASE(test_sprint_backtrace),
+       KUNIT_CASE(test_sprint_backtrace_build_id),
+       KUNIT_CASE(test_sprint_symbol_no_offset),
+       /* Group D: printk format specifiers */
+       KUNIT_CASE(test_pS_format),
+       KUNIT_CASE(test_pBb_format),
+       KUNIT_CASE(test_pSR_format),
+       /* Group E: Address edge cases */
+       KUNIT_CASE(test_symbol_start_addr),
+       KUNIT_CASE(test_symbol_nonzero_offset),
+       KUNIT_CASE(test_unknown_address),
+       KUNIT_CASE(test_kernel_function_lineinfo),
+       KUNIT_CASE(test_assembly_no_lineinfo),
+       /* Group F: Module path */
+       KUNIT_CASE(test_module_function_lineinfo),
+       /* Group G: Stress */
+       KUNIT_CASE_SLOW(test_concurrent_sprint_symbol),
+       KUNIT_CASE_SLOW(test_rapid_sprint_symbol),
+       /* Group H: Safety and plausibility */
+       KUNIT_CASE(test_line_number_plausible),
+       KUNIT_CASE(test_buffer_no_overflow),
+       KUNIT_CASE(test_dump_stack_no_crash),
+       KUNIT_CASE(test_sprint_symbol_build_id),
+       /* Group I: Encoding/decoding and accuracy */
+       KUNIT_CASE(test_zigzag_roundtrip),
+       KUNIT_CASE(test_uleb128_edge_cases),
+       KUNIT_CASE(test_line_number_accuracy),
+       KUNIT_CASE(test_many_lines_mid_function),
+       {}
+};
+
+static struct kunit_suite lineinfo_test_suite = {
+       .name = "lineinfo",
+       .test_cases = lineinfo_test_cases,
+};
+kunit_test_suites(&lineinfo_test_suite);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("KUnit tests for kallsyms lineinfo");
+MODULE_AUTHOR("Sasha Levin");
-- 
2.51.0


Reply via email to