#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct TaskOffsets {
pub real_parent: u32,
pub tgid: u32,
}
const BTF_MAGIC: u16 = 0xEB9F;
const BTF_TYPE_HDR_LEN: usize = 12;
const BTF_MEMBER_LEN: usize = 12;
const BTF_KIND_INT: u32 = 1;
const BTF_KIND_ARRAY: u32 = 3;
const BTF_KIND_STRUCT: u32 = 4;
const BTF_KIND_UNION: u32 = 5;
const BTF_KIND_ENUM: u32 = 6;
const BTF_KIND_FUNC_PROTO: u32 = 13;
const BTF_KIND_VAR: u32 = 14;
const BTF_KIND_DATASEC: u32 = 15;
const BTF_KIND_DECL_TAG: u32 = 17;
const BTF_KIND_ENUM64: u32 = 19;
struct Reader<'a> {
bytes: &'a [u8],
little_endian: bool,
}
impl<'a> Reader<'a> {
fn u16_at(&self, off: usize) -> Option<u16> {
let s = self.bytes.get(off..off + 2)?;
let arr = [s[0], s[1]];
Some(if self.little_endian {
u16::from_le_bytes(arr)
} else {
u16::from_be_bytes(arr)
})
}
fn u32_at(&self, off: usize) -> Option<u32> {
let s = self.bytes.get(off..off + 4)?;
let arr = [s[0], s[1], s[2], s[3]];
Some(if self.little_endian {
u32::from_le_bytes(arr)
} else {
u32::from_be_bytes(arr)
})
}
}
fn trailing_len(kind: u32, vlen: usize) -> usize {
match kind {
BTF_KIND_INT => 4,
BTF_KIND_ARRAY => 12,
BTF_KIND_STRUCT | BTF_KIND_UNION => vlen * BTF_MEMBER_LEN,
BTF_KIND_ENUM => vlen * 8,
BTF_KIND_FUNC_PROTO => vlen * 8,
BTF_KIND_VAR => 4,
BTF_KIND_DATASEC => vlen * 12,
BTF_KIND_DECL_TAG => 4,
BTF_KIND_ENUM64 => vlen * 12,
_ => 0,
}
}
fn read_str(str_section: &[u8], name_off: u32) -> Option<&str> {
let start = name_off as usize;
let rest = str_section.get(start..)?;
let end = rest.iter().position(|&b| b == 0).unwrap_or(rest.len());
core::str::from_utf8(&rest[..end]).ok()
}
pub fn parse_task_offsets(btf: &[u8]) -> Option<TaskOffsets> {
let little_endian = {
let le = Reader {
bytes: btf,
little_endian: true,
};
if le.u16_at(0)? == BTF_MAGIC {
true
} else {
let be = Reader {
bytes: btf,
little_endian: false,
};
if be.u16_at(0)? == BTF_MAGIC {
false
} else {
return None;
}
}
};
let r = Reader {
bytes: btf,
little_endian,
};
let hdr_len = r.u32_at(4)? as usize;
let type_off = r.u32_at(8)? as usize;
let type_len = r.u32_at(12)? as usize;
let str_off = r.u32_at(16)? as usize;
let str_len = r.u32_at(20)? as usize;
let type_start = hdr_len.checked_add(type_off)?;
let type_end = type_start.checked_add(type_len)?;
let str_start = hdr_len.checked_add(str_off)?;
let str_end = str_start.checked_add(str_len)?;
let str_section = btf.get(str_start..str_end)?;
let mut pos = type_start;
while pos + BTF_TYPE_HDR_LEN <= type_end {
let name_off = r.u32_at(pos)?;
let info = r.u32_at(pos + 4)?;
let vlen = (info & 0xffff) as usize;
let kind = (info >> 24) & 0x1f;
let trailing = trailing_len(kind, vlen);
let members_start = pos + BTF_TYPE_HDR_LEN;
let next = members_start.checked_add(trailing)?;
if kind == BTF_KIND_STRUCT && read_str(str_section, name_off) == Some("task_struct") {
let mut real_parent: Option<u32> = None;
let mut tgid: Option<u32> = None;
for i in 0..vlen {
let m = members_start + i * BTF_MEMBER_LEN;
let m_name_off = r.u32_at(m)?;
let m_offset_bits = r.u32_at(m + 8)?;
let byte_off = (m_offset_bits & 0x00ff_ffff) / 8;
match read_str(str_section, m_name_off) {
Some("real_parent") => real_parent = Some(byte_off),
Some("tgid") => tgid = Some(byte_off),
_ => {}
}
}
if let (Some(real_parent), Some(tgid)) = (real_parent, tgid) {
return Some(TaskOffsets { real_parent, tgid });
}
}
pos = next;
}
None
}
pub fn task_offsets_from_sys() -> Option<TaskOffsets> {
let bytes = std::fs::read("/sys/kernel/btf/vmlinux").ok()?;
parse_task_offsets(&bytes)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parses_task_struct_member_offsets() {
let mut strs: Vec<u8> = vec![0];
let off_task = strs.len() as u32;
strs.extend_from_slice(b"task_struct\0");
let off_real_parent = strs.len() as u32;
strs.extend_from_slice(b"real_parent\0");
let off_tgid = strs.len() as u32;
strs.extend_from_slice(b"tgid\0");
let real_parent_byte: u32 = 1408;
let tgid_byte: u32 = 2464;
let vlen: u32 = 2;
let info = (BTF_KIND_STRUCT << 24) | (vlen & 0xffff);
let mut types: Vec<u8> = Vec::new();
types.extend_from_slice(&off_task.to_le_bytes()); types.extend_from_slice(&info.to_le_bytes()); types.extend_from_slice(&0u32.to_le_bytes()); types.extend_from_slice(&off_real_parent.to_le_bytes());
types.extend_from_slice(&0u32.to_le_bytes()); types.extend_from_slice(&(real_parent_byte * 8).to_le_bytes()); types.extend_from_slice(&off_tgid.to_le_bytes());
types.extend_from_slice(&0u32.to_le_bytes());
types.extend_from_slice(&(tgid_byte * 8).to_le_bytes());
let hdr_len: u32 = 24;
let type_off: u32 = 0;
let type_len = types.len() as u32;
let str_off = type_len;
let str_len = strs.len() as u32;
let mut blob: Vec<u8> = Vec::new();
blob.extend_from_slice(&BTF_MAGIC.to_le_bytes()); blob.push(1); blob.push(0); blob.extend_from_slice(&hdr_len.to_le_bytes());
blob.extend_from_slice(&type_off.to_le_bytes());
blob.extend_from_slice(&type_len.to_le_bytes());
blob.extend_from_slice(&str_off.to_le_bytes());
blob.extend_from_slice(&str_len.to_le_bytes());
blob.extend_from_slice(&types);
blob.extend_from_slice(&strs);
let got = parse_task_offsets(&blob).expect("should parse task_struct");
assert_eq!(got.real_parent, real_parent_byte);
assert_eq!(got.tgid, tgid_byte);
}
#[test]
fn rejects_non_btf_blob() {
assert!(parse_task_offsets(&[0, 1, 2, 3, 4, 5, 6, 7]).is_none());
assert!(parse_task_offsets(&[]).is_none());
}
#[test]
fn returns_none_when_task_struct_absent() {
let hdr_len: u32 = 24;
let mut blob: Vec<u8> = Vec::new();
blob.extend_from_slice(&BTF_MAGIC.to_le_bytes());
blob.push(1);
blob.push(0);
blob.extend_from_slice(&hdr_len.to_le_bytes());
blob.extend_from_slice(&0u32.to_le_bytes()); blob.extend_from_slice(&0u32.to_le_bytes()); blob.extend_from_slice(&0u32.to_le_bytes()); blob.extend_from_slice(&1u32.to_le_bytes()); blob.push(0); assert!(parse_task_offsets(&blob).is_none());
}
}