[PATCH v3 5/8] sframe: Allow unsorted FDEs.

From: Dylan Hatch

Date: Mon Apr 06 2026 - 14:51:50 EST


The .sframe in kernel modules is built without SFRAME_F_FDE_SORTED set.
In order to allow sframe PC lookup in modules, add a code path to handle
unsorted FDE tables by doing a simple linear search.

Signed-off-by: Dylan Hatch <dylanbhatch@xxxxxxxxxx>
---
include/linux/sframe.h | 1 +
kernel/unwind/sframe.c | 44 +++++++++++++++++++++++++++++++++++++-----
2 files changed, 40 insertions(+), 5 deletions(-)

diff --git a/include/linux/sframe.h b/include/linux/sframe.h
index 905775c3fde2..593b60715cd6 100644
--- a/include/linux/sframe.h
+++ b/include/linux/sframe.h
@@ -64,6 +64,7 @@ struct sframe_section {
unsigned long text_start;
unsigned long text_end;

+ bool fdes_sorted;
unsigned long fdes_start;
unsigned long fres_start;
unsigned long fres_end;
diff --git a/kernel/unwind/sframe.c b/kernel/unwind/sframe.c
index 321d0615aec7..4dd3612f9e7a 100644
--- a/kernel/unwind/sframe.c
+++ b/kernel/unwind/sframe.c
@@ -179,9 +179,34 @@ static __always_inline int __read_fde(struct sframe_section *sec,
return -EFAULT;
}

-static __always_inline int __find_fde(struct sframe_section *sec,
- unsigned long ip,
- struct sframe_fde_internal *fde)
+static __always_inline int __find_fde_unsorted(struct sframe_section *sec,
+ unsigned long ip,
+ struct sframe_fde_internal *fde)
+{
+ struct sframe_fde_v3 *cur, *start, *end;
+
+ start = (struct sframe_fde_v3 *)sec->fdes_start;
+ end = start + sec->num_fdes;
+
+ for (cur = start; cur < end; cur++) {
+ s64 func_off;
+ u32 func_size;
+ unsigned long func_addr;
+
+ DATA_GET(sec, func_off, &cur->func_start_off, s64, Efault);
+ DATA_GET(sec, func_size, &cur->func_size, u32, Efault);
+ func_addr = (unsigned long)cur + func_off;
+
+ if (ip >= func_addr && ip < func_addr + func_size)
+ return __read_fde(sec, cur - start, fde);
+ }
+Efault:
+ return -EFAULT;
+}
+
+static __always_inline int __find_fde_sorted(struct sframe_section *sec,
+ unsigned long ip,
+ struct sframe_fde_internal *fde)
{
unsigned long func_addr_low = 0, func_addr_high = ULONG_MAX;
struct sframe_fde_v3 *first, *low, *high, *found = NULL;
@@ -236,6 +261,15 @@ static __always_inline int __find_fde(struct sframe_section *sec,
return -EFAULT;
}

+static __always_inline int __find_fde(struct sframe_section *sec,
+ unsigned long ip,
+ struct sframe_fde_internal *fde)
+{
+ if (sec->fdes_sorted)
+ return __find_fde_sorted(sec, ip, fde);
+ return __find_fde_unsorted(sec, ip, fde);
+}
+
#define ____GET_INC(sec, to, from, type, label) \
({ \
type __to; \
@@ -660,7 +694,7 @@ static int sframe_validate_section(struct sframe_section *sec)
return ret;

ip = fde.func_addr;
- if (ip <= prev_ip) {
+ if (sec->fdes_sorted && ip <= prev_ip) {
dbg_sec("fde %u not sorted\n", i);
return -EFAULT;
}
@@ -739,7 +773,6 @@ static int sframe_read_header(struct sframe_section *sec)

if (shdr.preamble.magic != SFRAME_MAGIC ||
shdr.preamble.version != SFRAME_VERSION_3 ||
- !(shdr.preamble.flags & SFRAME_F_FDE_SORTED) ||
!(shdr.preamble.flags & SFRAME_F_FDE_FUNC_START_PCREL) ||
shdr.auxhdr_len) {
dbg_sec("bad/unsupported sframe header\n");
@@ -769,6 +802,7 @@ static int sframe_read_header(struct sframe_section *sec)
return -EINVAL;
}

+ sec->fdes_sorted = shdr.preamble.flags & SFRAME_F_FDE_SORTED;
sec->num_fdes = num_fdes;
sec->fdes_start = fdes_start;
sec->fres_start = fres_start;
--
2.53.0.1213.gd9a14994de-goog