v3 discovered kallsyms data by scanning for token_index (256 x u16,
ti[0]=0), then reading kallsyms_offsets 512 bytes after it. works
on GKI 6.6 and 6.12. fails on 5.10, 5.15, and 6.1.
the reason is simple. the binary output order of write_src() in
scripts/kallsyms.c changed between kernel versions.
the layout shift happened somewhere between 6.1 and 6.6. android14-6.1 still uses the old order, android15-6.6 uses the new one.
in the old layout (i call it v1), offsets come first. the entire block
looks like: offsets, then kallsyms_relative_base (8 bytes, value is
_text), then kallsyms_num_syms (4 bytes, equals the symbol count N),
then names, markers, token table, and finally token_index at the end.
offsets are nowhere near token_index.
in the new layout (v2, 6.6+), token_index sits right before offsets.
token_index + 512 lands exactly on offsets. the v3 approach works
because of this.
so for older kernels we need a different way to find offsets. without
token_index as a shortcut, we look for a sorted u32 array that ends
with the pattern: 8 bytes of _text followed by 4 bytes matching the
array length. kind of like finding the signature of a packed struct.
the scan works in two phases. first, we check if the current page is entirely sorted:
int run = 0, prev = -1;
int max_i = (4096 - off) / 4;
for (int i = 0; i < max_i; i++) {
unsigned int v = buf[(off + i * 4) / 4];
if ((int)v < prev) break;
prev = (int)v; run++;
}
if (run < max_i) continue;
only if the sorted run reaches the page boundary do we cross into safe_read territory. this filters out almost everything instantly.
then we verify the pattern:
if ((rb & ~0x1FFFFFULL) != kernel_base) continue;
if (ns != (unsigned int)run) continue;
the mask comparison on _text saves us from false negatives when the
value is not exactly 2MB aligned across different builds.
kallsyms_seqs_of_nameswas added in kernel 6.1. 5.10 and 5.15 don’t have it. when it is missing,get_sym_seq()falls back to using the symbol index directly, which works because symbols are emitted in order.
took me a moment to realize why the scan kept failing on real devices.
i had capped the sorted u32 run at the 4096 byte page boundary. one
page is 1024 u32 entries, the threshold was 10000. the fix was cross
page safe_read for any candidate that survived the first phase.
simple.
kloffs @ ..., sorted=103903
layout v1, 103904 symbols
verify: addr->name MATCH
verify: name->addr MATCH
looks like it works on all three. 6.12 through v2, 6.6 through v2, 6.1 through v1. 5.10 and 5.15 builds pass CI.
Thanks to 汐の月 for providing the device for GKI 6.6 testing, and to 阿尔托莉雅·潘德拉贡 for providing the device for GKI 6.1 testing.