Skip to content

Commit b7002ff

Browse files
committed
fix(kfunc): resolve split BTF name_off using combined vmlinux+module string table
Module BTF (split BTF) has name_off values that index into the COMBINED string table (vmlinux strings || module strings), not just the module-local string section. The previous code checked name_off against the module string section length (303 bytes) which caused all FUNC entries to be skipped with "out of str_bytes" warnings, resulting in matched 0/2 kfuncs. Fix: load /sys/kernel/btf/vmlinux once to obtain: base_str_len - used to adjust name_off to a local module string offset base_strings - vmlinux string bytes (for names that live in vmlinux) base_nr_types - vmlinux type count (global type ID offset, unchanged) For any FUNC: if name_off >= base_str_len, resolve from module strings at (name_off - base_str_len); otherwise resolve from vmlinux strings. load_vmlinux_btf_info() replaces the former get_vmlinux_nr_types() and returns all three values in a single sysfs read. Also fixed consecutive doc list items using non-standard 2a./2b. numbering that triggered clippy::doc_missing_crate_level_docs warnings.
1 parent 6313aca commit b7002ff

2 files changed

Lines changed: 126 additions & 75 deletions

File tree

relay-xdp/src/bpf.rs

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,20 @@
22
//! Port of `relay_bpf.c`.
33
//!
44
//! Loading flow:
5-
//! 1. Read ELF bytes from disk.
6-
//! 2a. Patch kfunc call sites: src_reg 1->2 so aya-obj skips them.
7-
//! 2b. Patch BPF helper calls: src_reg 1->0, imm -> kernel helper ID.
8-
//! Needed because aya-obj filters UNDEF-symbol relocations and falls
9-
//! through to a pc-relative lookup that fails with UnknownFunction.
10-
//! 3. Ebpf::load(patched) to create all 6 maps (aya manages map FDs).
11-
//! 4. Extract raw map FDs via typed-map API.
12-
//! 5. Patch map FD values directly into ELF bytes (bypass relocate_maps).
13-
//! 6. aya_obj second parse + relocate_calls => flat instruction Vec.
14-
//! 7. Find relay_module.ko BTF, parse kfunc BTF type IDs.
15-
//! 8. Patch kfunc instructions with BTF IDs and fd_array index.
16-
//! 9. raw BPF_PROG_LOAD with fd_array -> prog_fd.
17-
//! 10. raw BPF_LINK_CREATE -> link_fd (holds XDP attachment for NIC lifetime).
5+
//!
6+
//! 1. Read ELF bytes from disk.
7+
//! 2. Patch kfunc call sites: src_reg 1->2 so aya-obj skips them.
8+
//! 3. Patch BPF helper calls: src_reg 1->0, imm -> kernel helper ID
9+
//! (aya-obj filters UNDEF-symbol relocations and falls through to a
10+
//! pc-relative lookup that fails with UnknownFunction without this).
11+
//! 4. Ebpf::load(patched) to create all 6 maps (aya manages map FDs).
12+
//! 5. Extract raw map FDs via typed-map API.
13+
//! 6. Patch map FD values directly into ELF bytes (bypass relocate_maps).
14+
//! 7. aya_obj second parse + relocate_calls => flat instruction Vec.
15+
//! 8. Find relay_module.ko BTF, parse kfunc BTF type IDs.
16+
//! 9. Patch kfunc instructions with BTF IDs and fd_array index.
17+
//! 10. raw BPF_PROG_LOAD with fd_array -> prog_fd.
18+
//! 11. raw BPF_LINK_CREATE -> link_fd (holds XDP attachment for NIC lifetime).
1819
1920
use anyhow::{Context, Result};
2021
use aya::maps::{Array, HashMap as AyaHashMap, IterableMap, MapData, PerCpuArray};

relay-xdp/src/kfunc.rs

Lines changed: 112 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -294,10 +294,7 @@ pub fn patch_elf_map_fds(elf: &[u8], map_fds: &HashMap<String, i32>) -> Result<V
294294

295295
/// BPF helper function IDs for helpers used in the relay XDP program.
296296
/// Values from Linux kernel `include/uapi/linux/bpf.h` (stable, append-only ABI).
297-
static BPF_HELPER_IDS: &[(&str, i32)] = &[
298-
("bpf_xdp_adjust_head", 44),
299-
("bpf_xdp_adjust_tail", 65),
300-
];
297+
static BPF_HELPER_IDS: &[(&str, i32)] = &[("bpf_xdp_adjust_head", 44), ("bpf_xdp_adjust_tail", 65)];
301298

302299
/// Patches standard BPF helper call instructions in the `xdp` section.
303300
///
@@ -377,10 +374,7 @@ pub fn patch_elf_bpf_helpers(elf: &[u8]) -> Result<Vec<u8>> {
377374

378375
let insn_file_pos = xdp_file_offset + r_offset;
379376
if insn_file_pos + BPF_INSN_SIZE > patched.len() {
380-
bail!(
381-
"BPF helper call at xdp+0x{:x} is out of bounds",
382-
r_offset
383-
);
377+
bail!("BPF helper call at xdp+0x{:x} is out of bounds", r_offset);
384378
}
385379

386380
// Verify BPF_CALL opcode at byte 0 of the instruction.
@@ -409,8 +403,7 @@ pub fn patch_elf_bpf_helpers(elf: &[u8]) -> Result<Vec<u8>> {
409403
patched[insn_file_pos + 1] = regs_byte & 0x0f;
410404

411405
// Patch imm: write helper ID as little-endian i32 at bytes 4-7.
412-
patched[insn_file_pos + 4..insn_file_pos + 8]
413-
.copy_from_slice(&helper_id.to_le_bytes());
406+
patched[insn_file_pos + 4..insn_file_pos + 8].copy_from_slice(&helper_id.to_le_bytes());
414407

415408
patched_count += 1;
416409
}
@@ -622,7 +615,9 @@ pub fn find_module_btf(module_name: &str) -> Result<(i32, Vec<u8>)> {
622615
// Found the module - fetch the raw BTF bytes
623616
log::info!(
624617
"find_module_btf: found '{}' BTF id={} btf_size={} bytes",
625-
found_name, next_id, btf_size
618+
found_name,
619+
next_id,
620+
btf_size
626621
);
627622
if btf_size == 0 {
628623
bail!(
@@ -745,7 +740,12 @@ pub fn parse_btf_func_ids(btf: &[u8], names: &[&str]) -> Result<HashMap<String,
745740

746741
log::info!(
747742
"parse_btf_func_ids: btf_len={} hdr_len={} type_off={} type_len={} str_off={} str_len={}",
748-
btf.len(), hdr_len, type_off, type_len, str_off, str_len
743+
btf.len(),
744+
hdr_len,
745+
type_off,
746+
type_len,
747+
str_off,
748+
str_len
749749
);
750750

751751
let type_section_start = hdr_len + type_off;
@@ -756,116 +756,166 @@ pub fn parse_btf_func_ids(btf: &[u8], names: &[&str]) -> Result<HashMap<String,
756756
if type_section_end > btf.len() || str_section_end > btf.len() {
757757
bail!(
758758
"BTF type/string section out of bounds: btf_len={} type={}..{} str={}..{}",
759-
btf.len(), type_section_start, type_section_end, str_section_start, str_section_end
759+
btf.len(),
760+
type_section_start,
761+
type_section_end,
762+
str_section_start,
763+
str_section_end
760764
);
761765
}
762766

763767
let type_bytes = &btf[type_section_start..type_section_end];
764768
let str_bytes = &btf[str_section_start..str_section_end];
765769

766-
// Compute base type ID offset from vmlinux BTF so the IDs we return are
767-
// the correct "global" IDs that the BPF verifier expects when resolving
768-
// kfunc calls via fd_array. Module BTF is split BTF: its raw bytes only
769-
// contain the module-local types, but the kernel numbers them starting at
770-
// vmlinux_nr_types + 1. Reading vmlinux from sysfs is the most reliable
771-
// way to get the base count.
772-
let base_nr_types = get_vmlinux_nr_types().unwrap_or_else(|e| {
773-
log::warn!("failed to get vmlinux type count ({}), assuming base_nr_types=0; kfunc IDs may be wrong", e);
774-
0
770+
// Module BTF is split BTF (kernel 5.13+). The raw bytes returned by
771+
// BPF_OBJ_GET_INFO_BY_FD contain only the module-local type and string
772+
// sections. HOWEVER, name_off values in module type entries are indices
773+
// into the COMBINED string table (vmlinux strings || module strings), not
774+
// into the module-local string section alone. Similarly, type IDs must be
775+
// the global IDs (vmlinux_nr_types + local_type_id) for the BPF verifier.
776+
//
777+
// We read /sys/kernel/btf/vmlinux to obtain:
778+
// base_nr_types - number of types in vmlinux (type ID offset)
779+
// base_str_len - size of vmlinux string section (name_off adjustment)
780+
// base_strings - vmlinux string bytes (for resolving names from vmlinux)
781+
let (base_nr_types, base_str_len, base_strings) = load_vmlinux_btf_info().unwrap_or_else(|e| {
782+
log::warn!(
783+
"failed to load vmlinux BTF ({}); kfunc type IDs and string offsets may be wrong",
784+
e
785+
);
786+
(0, 0, Vec::new())
775787
});
776788
log::info!(
777-
"parse_btf_func_ids: vmlinux base_nr_types={} -> module type IDs start at {}",
778-
base_nr_types, base_nr_types + 1
789+
"parse_btf_func_ids: vmlinux base_nr_types={} base_str_len={} -> module IDs start at {}",
790+
base_nr_types,
791+
base_str_len,
792+
base_nr_types + 1
779793
);
780794

781795
let mut result = HashMap::new();
782796
let mut pos: usize = 0;
783-
let mut local_type_id: u32 = 1; // 1-based within module BTF section
797+
let mut local_type_id: u32 = 1; // 1-based within this module's type section
784798
let mut func_count: u32 = 0;
785799

786800
while pos + 12 <= type_bytes.len() {
787-
let name_off = u32::from_le_bytes(type_bytes[pos..pos + 4].try_into().expect("4")) as usize;
801+
let raw_name_off =
802+
u32::from_le_bytes(type_bytes[pos..pos + 4].try_into().expect("4")) as usize;
788803
let info = u32::from_le_bytes(type_bytes[pos + 4..pos + 8].try_into().expect("4"));
789804
let kind = (info >> 24) & 0x1f;
790805
let vlen = (info & 0xffff) as usize;
791806

792-
// BTF_KIND_FUNC = 12 - function declaration (0 extra bytes)
807+
// BTF_KIND_FUNC = 12 - function declaration (no extra bytes)
793808
if kind == 12 {
794809
func_count += 1;
795-
// Global BTF type ID = vmlinux base count + local position
796810
let global_type_id = base_nr_types + local_type_id;
797-
if name_off < str_bytes.len() {
798-
let name_bytes = &str_bytes[name_off..];
799-
if let Ok(cstr) = std::ffi::CStr::from_bytes_until_nul(name_bytes) {
800-
let name = cstr.to_string_lossy();
801-
log::info!(
802-
"parse_btf_func_ids: FUNC local_id={} global_id={} name='{}'",
803-
local_type_id, global_type_id, name
804-
);
805-
if names.contains(&name.as_ref()) {
806-
result.insert(name.into_owned(), global_type_id);
807-
}
811+
812+
// Resolve the name from the appropriate string section.
813+
// name_off is a global offset into (vmlinux_strings || module_strings).
814+
let name_opt = if raw_name_off < base_str_len {
815+
// Name is in the vmlinux string section
816+
if raw_name_off < base_strings.len() {
817+
std::ffi::CStr::from_bytes_until_nul(&base_strings[raw_name_off..])
818+
.ok()
819+
.map(|c| c.to_string_lossy().into_owned())
820+
} else {
821+
None
808822
}
809823
} else {
810-
log::warn!(
811-
"parse_btf_func_ids: FUNC local_id={} name_off={} out of str_bytes (len={})",
812-
local_type_id, name_off, str_bytes.len()
824+
// Name is in the module-local string section
825+
let local_off = raw_name_off - base_str_len;
826+
if local_off < str_bytes.len() {
827+
std::ffi::CStr::from_bytes_until_nul(&str_bytes[local_off..])
828+
.ok()
829+
.map(|c| c.to_string_lossy().into_owned())
830+
} else {
831+
log::warn!(
832+
"parse_btf_func_ids: FUNC local_id={} raw_name_off={} local_off={} out of str_bytes (len={}), base_str_len={}",
833+
local_type_id, raw_name_off, local_off, str_bytes.len(), base_str_len
834+
);
835+
None
836+
}
837+
};
838+
839+
if let Some(name) = name_opt {
840+
log::info!(
841+
"parse_btf_func_ids: FUNC local_id={} global_id={} name='{}'",
842+
local_type_id,
843+
global_type_id,
844+
name
813845
);
846+
if names.contains(&name.as_str()) {
847+
result.insert(name, global_type_id);
848+
}
814849
}
815850
}
816851

817-
// Advance past this type entry: 12 base bytes + extra bytes per kind
818852
let extra = btf_kind_extra_bytes(kind, vlen);
819853
log::debug!(
820854
"parse_btf_func_ids: pos={} kind={} vlen={} extra={} local_id={}",
821-
pos, kind, vlen, extra, local_type_id
855+
pos,
856+
kind,
857+
vlen,
858+
extra,
859+
local_type_id
822860
);
823861
pos += 12 + extra;
824862
local_type_id += 1;
825863
}
826864

827865
log::info!(
828866
"parse_btf_func_ids: iterated {} types, found {} FUNC entries, matched {}/{}",
829-
local_type_id - 1, func_count, result.len(), names.len()
867+
local_type_id - 1,
868+
func_count,
869+
result.len(),
870+
names.len()
830871
);
831872

832-
// Verify all requested names were found
833873
for name in names {
834874
if !result.contains_key(*name) {
835875
bail!(
836-
"BTF func '{}' not found in module BTF (base_nr_types={}, {} types parsed, {} FUNC entries) - module may need rebuild",
837-
name, base_nr_types, local_type_id - 1, func_count
876+
"BTF func '{}' not found in module BTF (base_nr_types={} base_str_len={} {} types {} FUNCs) - module may need rebuild",
877+
name, base_nr_types, base_str_len, local_type_id - 1, func_count
838878
);
839879
}
840880
}
841881

842882
Ok(result)
843883
}
844884

845-
/// Counts the number of types in the vmlinux BTF by reading /sys/kernel/btf/vmlinux.
846-
/// Returns the total type count so module BTF type IDs can be correctly offset.
847-
fn get_vmlinux_nr_types() -> Result<u32> {
848-
let vmlinux_btf = std::fs::read("/sys/kernel/btf/vmlinux")
885+
/// Parses /sys/kernel/btf/vmlinux and returns:
886+
/// (nr_types, str_len, str_bytes)
887+
/// nr_types - total number of types (used as base offset for module type IDs)
888+
/// str_len - total length of vmlinux string section (used as base offset for module name_off)
889+
/// str_bytes - vmlinux string section bytes (for resolving names that live in vmlinux)
890+
fn load_vmlinux_btf_info() -> Result<(u32, usize, Vec<u8>)> {
891+
let btf = std::fs::read("/sys/kernel/btf/vmlinux")
849892
.context("failed to read /sys/kernel/btf/vmlinux")?;
850893

851-
if vmlinux_btf.len() < 24 {
852-
bail!("vmlinux BTF too short");
894+
if btf.len() < 24 {
895+
bail!("vmlinux BTF too short ({} bytes)", btf.len());
853896
}
854897

855-
let hdr_len = u32::from_le_bytes(vmlinux_btf[4..8].try_into().expect("4")) as usize;
856-
let type_off = u32::from_le_bytes(vmlinux_btf[8..12].try_into().expect("4")) as usize;
857-
let type_len = u32::from_le_bytes(vmlinux_btf[12..16].try_into().expect("4")) as usize;
898+
let hdr_len = u32::from_le_bytes(btf[4..8].try_into().expect("4")) as usize;
899+
let type_off = u32::from_le_bytes(btf[8..12].try_into().expect("4")) as usize;
900+
let type_len = u32::from_le_bytes(btf[12..16].try_into().expect("4")) as usize;
901+
let str_off = u32::from_le_bytes(btf[16..20].try_into().expect("4")) as usize;
902+
let str_len = u32::from_le_bytes(btf[20..24].try_into().expect("4")) as usize;
858903

859904
let type_start = hdr_len + type_off;
860905
let type_end = type_start + type_len;
861-
if type_end > vmlinux_btf.len() {
862-
bail!("vmlinux BTF type section out of bounds");
906+
let str_start = hdr_len + str_off;
907+
let str_end = str_start + str_len;
908+
909+
if type_end > btf.len() || str_end > btf.len() {
910+
bail!("vmlinux BTF sections out of bounds (btf_len={})", btf.len());
863911
}
864912

865-
let type_bytes = &vmlinux_btf[type_start..type_end];
913+
let type_bytes = &btf[type_start..type_end];
914+
let str_bytes = btf[str_start..str_end].to_vec();
915+
916+
// Count types by iterating the type section
866917
let mut pos = 0usize;
867918
let mut nr_types: u32 = 0;
868-
869919
while pos + 12 <= type_bytes.len() {
870920
let info = u32::from_le_bytes(type_bytes[pos + 4..pos + 8].try_into().expect("4"));
871921
let kind = (info >> 24) & 0x1f;
@@ -875,7 +925,7 @@ fn get_vmlinux_nr_types() -> Result<u32> {
875925
nr_types += 1;
876926
}
877927

878-
Ok(nr_types)
928+
Ok((nr_types, str_len, str_bytes))
879929
}
880930

881931
/// Returns extra bytes after the 12-byte type base depending on BTF kind.

0 commit comments

Comments
 (0)