Skip to main content

padlock_dwarf/
btf.rs

1// padlock-dwarf/src/btf.rs
2//
3// Minimal BTF (BPF Type Format) parser for extracting struct layouts.
4// BTF is used by Linux eBPF programs and is embedded in the `.BTF` ELF section.
5//
6// Reference: https://www.kernel.org/doc/html/latest/bpf/btf.html
7
8use padlock_core::arch::ArchConfig;
9use padlock_core::ir::{AccessPattern, Field, StructLayout, TypeInfo};
10
11// ── BTF constants ─────────────────────────────────────────────────────────────
12
13const BTF_MAGIC: u16 = 0xEB9F;
14const BTF_KIND_INT: u32 = 1;
15const BTF_KIND_PTR: u32 = 2;
16const BTF_KIND_ARRAY: u32 = 3;
17const BTF_KIND_STRUCT: u32 = 4;
18const BTF_KIND_UNION: u32 = 5;
19const BTF_KIND_ENUM: u32 = 6;
20const BTF_KIND_FWD: u32 = 7;
21const BTF_KIND_TYPEDEF: u32 = 8;
22const BTF_KIND_VOLATILE: u32 = 9;
23const BTF_KIND_CONST: u32 = 10;
24const BTF_KIND_RESTRICT: u32 = 11;
25const BTF_KIND_FUNC: u32 = 12;
26const BTF_KIND_FUNC_PROTO: u32 = 13;
27const BTF_KIND_VAR: u32 = 14;
28const BTF_KIND_DATASEC: u32 = 15;
29const BTF_KIND_FLOAT: u32 = 16;
30const BTF_KIND_DECL_TAG: u32 = 17;
31const BTF_KIND_TYPE_TAG: u32 = 18;
32const BTF_KIND_ENUM64: u32 = 19;
33
34// ── BTF wire types (little-endian) ────────────────────────────────────────────
35
36#[derive(Debug, Clone)]
37struct BtfHeader {
38    hdr_len: u32,
39    type_off: u32,
40    type_len: u32,
41    str_off: u32,
42    str_len: u32,
43}
44
45#[derive(Debug, Clone)]
46struct RawBtfType {
47    info: u32,
48}
49
50impl RawBtfType {
51    fn kind(&self) -> u32 {
52        (self.info >> 24) & 0x1f
53    }
54    fn vlen(&self) -> u32 {
55        self.info & 0xffff
56    }
57    fn kind_flag(&self) -> bool {
58        (self.info >> 31) & 1 == 1
59    }
60}
61
62// ── parsed type table ─────────────────────────────────────────────────────────
63
64#[derive(Debug, Clone)]
65enum BtfType {
66    Int {
67        name: String,
68        size: u32,
69    },
70    Ptr,
71    Array {
72        elem_type: u32,
73        nelems: u32,
74    },
75    Struct {
76        name: String,
77        size: u32,
78        members: Vec<BtfMember>,
79        is_union: bool,
80    },
81    Enum {
82        size: u32,
83    },
84    Typedef {
85        type_id: u32,
86    },
87    Qualifier {
88        type_id: u32,
89    }, // volatile, const, restrict
90    Float {
91        size: u32,
92    },
93    Unknown,
94}
95
96#[derive(Debug, Clone)]
97struct BtfMember {
98    name: String,
99    type_id: u32,
100    bit_offset: u32,
101    bitfield_size: u32, // 0 = not a bitfield
102}
103
104// ── parser ────────────────────────────────────────────────────────────────────
105
106pub struct BtfParser<'a> {
107    data: &'a [u8],
108    // types is 1-indexed: types[0] is unused (type_id 0 = void)
109    types: Vec<BtfType>,
110    arch: &'static ArchConfig,
111}
112
113impl<'a> BtfParser<'a> {
114    pub fn new(data: &'a [u8], arch: &'static ArchConfig) -> anyhow::Result<Self> {
115        let mut p = BtfParser {
116            data,
117            types: Vec::new(),
118            arch,
119        };
120        p.parse()?;
121        Ok(p)
122    }
123
124    fn parse(&mut self) -> anyhow::Result<()> {
125        if self.data.len() < 24 {
126            anyhow::bail!("BTF data too short");
127        }
128
129        let magic = u16::from_le_bytes([self.data[0], self.data[1]]);
130        if magic != BTF_MAGIC {
131            anyhow::bail!("invalid BTF magic: 0x{:04x}", magic);
132        }
133
134        let hdr = BtfHeader {
135            hdr_len: u32::from_le_bytes(self.data[4..8].try_into()?),
136            type_off: u32::from_le_bytes(self.data[8..12].try_into()?),
137            type_len: u32::from_le_bytes(self.data[12..16].try_into()?),
138            str_off: u32::from_le_bytes(self.data[16..20].try_into()?),
139            str_len: u32::from_le_bytes(self.data[20..24].try_into()?),
140        };
141
142        let type_base = hdr.hdr_len as usize + hdr.type_off as usize;
143        let type_end = type_base + hdr.type_len as usize;
144        let str_base = hdr.hdr_len as usize + hdr.str_off as usize;
145        let str_end = str_base + hdr.str_len as usize;
146
147        if type_end > self.data.len() || str_end > self.data.len() {
148            anyhow::bail!("BTF sections extend beyond data");
149        }
150
151        let type_data = &self.data[type_base..type_end];
152        let str_data = &self.data[str_base..str_end];
153
154        // type_id 0 is void — reserve slot 0
155        self.types.push(BtfType::Unknown);
156
157        let mut off = 0usize;
158        while off + 12 <= type_data.len() {
159            let name_off = u32::from_le_bytes(type_data[off..off + 4].try_into()?);
160            let info = u32::from_le_bytes(type_data[off + 4..off + 8].try_into()?);
161            let size_or_type = u32::from_le_bytes(type_data[off + 8..off + 12].try_into()?);
162            off += 12;
163
164            let raw = RawBtfType { info };
165            let name = read_btf_str(str_data, name_off as usize);
166            let kind = raw.kind();
167            let vlen = raw.vlen() as usize;
168
169            let btf_type = match kind {
170                BTF_KIND_INT => {
171                    off += 4; // skip int encoding
172                    BtfType::Int {
173                        name,
174                        size: size_or_type,
175                    }
176                }
177                BTF_KIND_PTR => BtfType::Ptr,
178                BTF_KIND_ARRAY => {
179                    if off + 12 > type_data.len() {
180                        break;
181                    }
182                    let elem_type = u32::from_le_bytes(type_data[off..off + 4].try_into()?);
183                    // index_type: off+4..off+8 (skip)
184                    let nelems = u32::from_le_bytes(type_data[off + 8..off + 12].try_into()?);
185                    off += 12;
186                    BtfType::Array { elem_type, nelems }
187                }
188                BTF_KIND_STRUCT | BTF_KIND_UNION => {
189                    let mut members = Vec::with_capacity(vlen);
190                    for _ in 0..vlen {
191                        if off + 12 > type_data.len() {
192                            break;
193                        }
194                        let m_name_off = u32::from_le_bytes(type_data[off..off + 4].try_into()?);
195                        let m_type = u32::from_le_bytes(type_data[off + 4..off + 8].try_into()?);
196                        let m_offset = u32::from_le_bytes(type_data[off + 8..off + 12].try_into()?);
197                        off += 12;
198
199                        let (bit_offset, bitfield_size) = if raw.kind_flag() {
200                            (m_offset & 0xffffff, (m_offset >> 24) & 0xff)
201                        } else {
202                            (m_offset, 0)
203                        };
204
205                        members.push(BtfMember {
206                            name: read_btf_str(str_data, m_name_off as usize),
207                            type_id: m_type,
208                            bit_offset,
209                            bitfield_size,
210                        });
211                    }
212                    BtfType::Struct {
213                        name,
214                        size: size_or_type,
215                        members,
216                        is_union: kind == BTF_KIND_UNION,
217                    }
218                }
219                BTF_KIND_ENUM => {
220                    off += vlen * 8; // each enum value = name_off(4) + val(4)
221                    BtfType::Enum { size: size_or_type }
222                }
223                BTF_KIND_TYPEDEF => BtfType::Typedef {
224                    type_id: size_or_type,
225                },
226                BTF_KIND_VOLATILE | BTF_KIND_CONST | BTF_KIND_RESTRICT => BtfType::Qualifier {
227                    type_id: size_or_type,
228                },
229                BTF_KIND_FLOAT => BtfType::Float { size: size_or_type },
230                BTF_KIND_ENUM64 => {
231                    off += vlen * 12; // each enum64 value = name_off(4) + val_lo(4) + val_hi(4)
232                    BtfType::Enum { size: size_or_type }
233                }
234                // Kinds with no extra bytes beyond the 12-byte header
235                BTF_KIND_FWD | BTF_KIND_FUNC | BTF_KIND_TYPE_TAG => BtfType::Unknown,
236                // FUNC_PROTO: vlen params, each 8 bytes (name_off + type_id)
237                BTF_KIND_FUNC_PROTO => {
238                    off += vlen * 8;
239                    BtfType::Unknown
240                }
241                // VAR: 4 extra bytes (linkage u32)
242                BTF_KIND_VAR => {
243                    off += 4;
244                    BtfType::Unknown
245                }
246                // DATASEC: vlen * 12 bytes (type + offset + size per btf_var_secinfo)
247                BTF_KIND_DATASEC => {
248                    off += vlen * 12;
249                    BtfType::Unknown
250                }
251                // DECL_TAG: 4 extra bytes (component_idx i32)
252                BTF_KIND_DECL_TAG => {
253                    off += 4;
254                    BtfType::Unknown
255                }
256                _ => {
257                    // Truly unknown kind — stop parsing to avoid reading garbage
258                    break;
259                }
260            };
261
262            self.types.push(btf_type);
263        }
264
265        Ok(())
266    }
267
268    /// Resolve a type_id to its byte size.
269    fn type_size(&self, type_id: u32) -> usize {
270        if type_id == 0 {
271            return 0; // void
272        }
273        let idx = type_id as usize;
274        if idx >= self.types.len() {
275            return self.arch.pointer_size;
276        }
277        match &self.types[idx] {
278            BtfType::Int { size, .. } | BtfType::Float { size } | BtfType::Enum { size } => {
279                *size as usize
280            }
281            BtfType::Ptr => self.arch.pointer_size,
282            BtfType::Array { elem_type, nelems } => self.type_size(*elem_type) * (*nelems as usize),
283            BtfType::Struct { size, .. } => *size as usize,
284            BtfType::Typedef { type_id } | BtfType::Qualifier { type_id } => {
285                self.type_size(*type_id)
286            }
287            BtfType::Unknown => self.arch.pointer_size,
288        }
289    }
290
291    /// Infer alignment from a type_id (BTF doesn't store alignment explicitly).
292    fn type_align(&self, type_id: u32) -> usize {
293        if type_id == 0 {
294            return 1;
295        }
296        let idx = type_id as usize;
297        if idx >= self.types.len() {
298            return self.arch.pointer_size;
299        }
300        match &self.types[idx] {
301            BtfType::Int { size, .. } | BtfType::Float { size } | BtfType::Enum { size } => {
302                (*size as usize).min(self.arch.max_align)
303            }
304            BtfType::Ptr => self.arch.pointer_size,
305            BtfType::Array { elem_type, .. } => self.type_align(*elem_type),
306            BtfType::Struct { members, .. } => members
307                .iter()
308                .map(|m| self.type_align(m.type_id))
309                .max()
310                .unwrap_or(1),
311            BtfType::Typedef { type_id } | BtfType::Qualifier { type_id } => {
312                self.type_align(*type_id)
313            }
314            BtfType::Unknown => self.arch.pointer_size,
315        }
316    }
317
318    /// Resolve typedef/qualifier chains to a displayable type name.
319    fn type_name(&self, type_id: u32) -> String {
320        if type_id == 0 {
321            return "void".to_string();
322        }
323        let idx = type_id as usize;
324        if idx >= self.types.len() {
325            return format!("type_{}", type_id);
326        }
327        match &self.types[idx] {
328            BtfType::Int { name, .. } | BtfType::Struct { name, .. } => {
329                if name.is_empty() {
330                    format!("type_{}", type_id)
331                } else {
332                    name.clone()
333                }
334            }
335            BtfType::Float { size } => format!("f{}", size * 8),
336            BtfType::Ptr => "*".to_string(),
337            BtfType::Array { elem_type, nelems } => {
338                format!("[{}]{}", nelems, self.type_name(*elem_type))
339            }
340            BtfType::Enum { .. } => format!("enum_{}", type_id),
341            BtfType::Typedef { type_id } => self.type_name(*type_id),
342            BtfType::Qualifier { type_id } => self.type_name(*type_id),
343            BtfType::Unknown => format!("unknown_{}", type_id),
344        }
345    }
346
347    /// Extract all named structs and unions as `StructLayout` values.
348    pub fn extract_structs(&self) -> Vec<StructLayout> {
349        let mut layouts = Vec::new();
350
351        for (idx, ty) in self.types.iter().enumerate() {
352            if let BtfType::Struct {
353                name,
354                size,
355                members,
356                is_union,
357            } = ty
358            {
359                // Skip anonymous structs (name is empty) — they're usually embedded
360                if name.is_empty() {
361                    continue;
362                }
363
364                let mut fields: Vec<Field> = Vec::new();
365                // Track byte ranges already covered by synthetic bitfield-group fields
366                // so we don't emit overlapping entries.
367                let mut covered_until: usize = 0;
368
369                for member in members {
370                    let is_bitfield = member.bitfield_size != 0 || member.bit_offset % 8 != 0;
371
372                    if is_bitfield {
373                        // Represent the storage unit for this bitfield group.
374                        // The storage unit starts at the byte-aligned base of the bit offset
375                        // and has the size of the member's declared base type.
376                        let storage_size = self.type_size(member.type_id).max(1);
377                        // Align the bit offset down to the nearest storage-unit boundary.
378                        let storage_bits = (storage_size * 8) as u32;
379                        let unit_start_bit = (member.bit_offset / storage_bits) * storage_bits;
380                        let unit_byte_offset = (unit_start_bit / 8) as usize;
381                        let unit_end = unit_byte_offset + storage_size;
382
383                        // Only emit a synthetic field if this storage unit isn't
384                        // already covered by a previously emitted field.
385                        if unit_byte_offset >= covered_until {
386                            let fname = format!("{}__bits", member.name);
387                            let falign = storage_size.min(self.arch.max_align);
388                            fields.push(Field {
389                                name: fname.clone(),
390                                ty: TypeInfo::Primitive {
391                                    name: format!("u{}", storage_size * 8),
392                                    size: storage_size,
393                                    align: falign,
394                                },
395                                offset: unit_byte_offset,
396                                size: storage_size,
397                                align: falign,
398                                source_file: None,
399                                source_line: None,
400                                access: AccessPattern::Unknown,
401                            });
402                            covered_until = unit_end;
403                        }
404                        continue;
405                    }
406
407                    let byte_offset = (member.bit_offset / 8) as usize;
408                    let fsize = self.type_size(member.type_id);
409                    let falign = self.type_align(member.type_id);
410                    let fname = if member.name.is_empty() {
411                        format!("field_{}", fields.len())
412                    } else {
413                        member.name.clone()
414                    };
415
416                    covered_until = covered_until.max(byte_offset + fsize);
417                    fields.push(Field {
418                        name: fname.clone(),
419                        ty: TypeInfo::Primitive {
420                            name: self.type_name(member.type_id),
421                            size: fsize,
422                            align: falign,
423                        },
424                        offset: byte_offset,
425                        size: fsize,
426                        align: falign,
427                        source_file: None,
428                        source_line: None,
429                        access: AccessPattern::Unknown,
430                    });
431                }
432
433                if fields.is_empty() {
434                    continue;
435                }
436
437                let max_align = fields.iter().map(|f| f.align).max().unwrap_or(1);
438
439                // Detect packed structs: a struct is packed if its total_size is
440                // smaller than what natural alignment would produce. This catches
441                // __attribute__((packed)) structs emitted by the compiler.
442                let natural_size = {
443                    let mut off2 = 0usize;
444                    for f in &fields {
445                        if max_align > 0 {
446                            off2 = off2.next_multiple_of(f.align.max(1));
447                        }
448                        off2 += f.size;
449                    }
450                    if max_align > 0 {
451                        off2 = off2.next_multiple_of(max_align.max(1));
452                    }
453                    off2
454                };
455                let is_packed = !*is_union && (*size as usize) < natural_size;
456
457                layouts.push(StructLayout {
458                    name: name.clone(),
459                    total_size: *size as usize,
460                    align: max_align,
461                    fields,
462                    source_file: None,
463                    source_line: None,
464                    arch: self.arch,
465                    is_packed,
466                    is_union: *is_union,
467                });
468
469                let _ = idx;
470            }
471        }
472
473        layouts
474    }
475}
476
477fn read_btf_str(str_data: &[u8], off: usize) -> String {
478    if off >= str_data.len() {
479        return String::new();
480    }
481    let end = str_data[off..]
482        .iter()
483        .position(|&b| b == 0)
484        .map(|p| off + p)
485        .unwrap_or(str_data.len());
486    String::from_utf8_lossy(&str_data[off..end]).into_owned()
487}
488
489// ── public entry point ────────────────────────────────────────────────────────
490
491/// Extract struct layouts from raw BTF data (the contents of a `.BTF` ELF section).
492pub fn extract_from_btf(
493    btf_data: &[u8],
494    arch: &'static ArchConfig,
495) -> anyhow::Result<Vec<StructLayout>> {
496    let parser = BtfParser::new(btf_data, arch)?;
497    Ok(parser.extract_structs())
498}
499
500// ── tests ─────────────────────────────────────────────────────────────────────
501
502#[cfg(test)]
503mod tests {
504    use super::*;
505    use padlock_core::arch::X86_64_SYSV;
506
507    /// Build a minimal valid BTF blob containing one struct with two int fields.
508    fn build_test_btf() -> Vec<u8> {
509        // Strings: "\0point\0x\0y\0"
510        let strings: &[u8] = b"\0point\0x\0y\0";
511        let str_len = strings.len() as u32;
512
513        // Strings layout: "\0point\0x\0y\0"
514        //   offset 0: \0 (empty name)
515        //   offset 1: "point" (6 bytes including null)
516        //   offset 7: "x" (2 bytes including null)
517        //   offset 9: "y" (2 bytes including null)
518        // Types:
519        // type_id 1: INT "x"  size=4
520        // type_id 2: INT "y"  size=4
521        // type_id 3: STRUCT "point"  size=8  vlen=2
522        //   member 0: name="x"(off=7) type=1 offset=0
523        //   member 1: name="y"(off=9) type=2 offset=32 (bit offset)
524
525        let mut type_data: Vec<u8> = Vec::new();
526
527        // INT "x": name_off=7, info=(1<<24|0), size=4; extra int_encoding=4bytes
528        let x_name_off: u32 = 7;
529        type_data.extend_from_slice(&x_name_off.to_le_bytes());
530        type_data.extend_from_slice(&(BTF_KIND_INT << 24).to_le_bytes());
531        type_data.extend_from_slice(&4u32.to_le_bytes());
532        type_data.extend_from_slice(&0u32.to_le_bytes()); // int encoding
533
534        // INT "y": name_off=9
535        let y_name_off: u32 = 9;
536        type_data.extend_from_slice(&y_name_off.to_le_bytes());
537        type_data.extend_from_slice(&(BTF_KIND_INT << 24).to_le_bytes());
538        type_data.extend_from_slice(&4u32.to_le_bytes());
539        type_data.extend_from_slice(&0u32.to_le_bytes());
540
541        // STRUCT "point": name_off=1, info=(4<<24|2), size=8
542        let point_name_off: u32 = 1;
543        type_data.extend_from_slice(&point_name_off.to_le_bytes());
544        type_data.extend_from_slice(&((BTF_KIND_STRUCT << 24) | 2u32).to_le_bytes()); // vlen=2
545        type_data.extend_from_slice(&8u32.to_le_bytes()); // size=8
546        // member 0: x
547        type_data.extend_from_slice(&x_name_off.to_le_bytes()); // name_off for "x"
548        type_data.extend_from_slice(&1u32.to_le_bytes()); // type_id=1
549        type_data.extend_from_slice(&0u32.to_le_bytes()); // bit_offset=0
550        // member 1: y
551        type_data.extend_from_slice(&y_name_off.to_le_bytes()); // name_off for "y"
552        type_data.extend_from_slice(&2u32.to_le_bytes()); // type_id=2
553        type_data.extend_from_slice(&32u32.to_le_bytes()); // bit_offset=32
554
555        let type_len = type_data.len() as u32;
556
557        // BTF header (24 bytes)
558        let hdr_len: u32 = 24;
559        let mut btf = Vec::new();
560        btf.extend_from_slice(&BTF_MAGIC.to_le_bytes()); // magic
561        btf.push(1); // version
562        btf.push(0); // flags
563        btf.extend_from_slice(&hdr_len.to_le_bytes()); // hdr_len
564        btf.extend_from_slice(&0u32.to_le_bytes()); // type_off = 0
565        btf.extend_from_slice(&type_len.to_le_bytes()); // type_len
566        btf.extend_from_slice(&type_len.to_le_bytes()); // str_off = after types
567        btf.extend_from_slice(&str_len.to_le_bytes()); // str_len
568        btf.extend_from_slice(&type_data);
569        btf.extend_from_slice(strings);
570        btf
571    }
572
573    #[test]
574    fn btf_parse_simple_struct() {
575        let btf = build_test_btf();
576        let layouts = extract_from_btf(&btf, &X86_64_SYSV).unwrap();
577        assert_eq!(layouts.len(), 1);
578        assert_eq!(layouts[0].name, "point");
579        assert_eq!(layouts[0].total_size, 8);
580        assert_eq!(layouts[0].fields.len(), 2);
581    }
582
583    #[test]
584    fn btf_field_offsets_correct() {
585        let btf = build_test_btf();
586        let layouts = extract_from_btf(&btf, &X86_64_SYSV).unwrap();
587        let l = &layouts[0];
588        assert_eq!(l.fields[0].name, "x");
589        assert_eq!(l.fields[0].offset, 0);
590        assert_eq!(l.fields[1].name, "y");
591        assert_eq!(l.fields[1].offset, 4);
592    }
593
594    #[test]
595    fn btf_invalid_magic_errors() {
596        let mut btf = build_test_btf();
597        btf[0] = 0xff;
598        btf[1] = 0xff;
599        assert!(extract_from_btf(&btf, &X86_64_SYSV).is_err());
600    }
601
602    #[test]
603    fn btf_bitfield_members_become_synthetic_storage_unit_fields() {
604        // Struct with one bitfield member: `u32 flags : 3` at bit_offset = 0.
605        // Expected: a synthetic "flags__bits" field of type u32 at offset 0.
606        let strings: &[u8] = b"\0mystruct\0flags\0";
607        // "mystruct" at 1, "flags" at 10
608        let str_len = strings.len() as u32;
609        let mut type_data: Vec<u8> = Vec::new();
610
611        // INT type (u32): name_off=10 (flags), size=4
612        let flags_name_off: u32 = 10;
613        type_data.extend_from_slice(&flags_name_off.to_le_bytes());
614        type_data.extend_from_slice(&(BTF_KIND_INT << 24).to_le_bytes());
615        type_data.extend_from_slice(&4u32.to_le_bytes());
616        type_data.extend_from_slice(&0u32.to_le_bytes()); // int encoding
617
618        // STRUCT "mystruct": kind_flag=1 (bit 31 of info set), vlen=1, size=4
619        let struct_name_off: u32 = 1;
620        // info: kind_flag(1) | kind(4 << 24) | vlen(1)
621        let struct_info: u32 = (1u32 << 31) | (BTF_KIND_STRUCT << 24) | 1u32;
622        type_data.extend_from_slice(&struct_name_off.to_le_bytes());
623        type_data.extend_from_slice(&struct_info.to_le_bytes());
624        type_data.extend_from_slice(&4u32.to_le_bytes()); // size=4
625        // member: flags, type=1, offset encoding = (bitfield_size=3 << 24) | bit_offset=0
626        let m_offset: u32 = (3u32 << 24) | 0u32; // 3-bit bitfield at bit 0
627        type_data.extend_from_slice(&flags_name_off.to_le_bytes());
628        type_data.extend_from_slice(&1u32.to_le_bytes()); // type_id=1
629        type_data.extend_from_slice(&m_offset.to_le_bytes());
630
631        let type_len = type_data.len() as u32;
632        let hdr_len: u32 = 24;
633        let mut btf = Vec::new();
634        btf.extend_from_slice(&BTF_MAGIC.to_le_bytes());
635        btf.push(1);
636        btf.push(0);
637        btf.extend_from_slice(&hdr_len.to_le_bytes());
638        btf.extend_from_slice(&0u32.to_le_bytes());
639        btf.extend_from_slice(&type_len.to_le_bytes());
640        btf.extend_from_slice(&type_len.to_le_bytes());
641        btf.extend_from_slice(&str_len.to_le_bytes());
642        btf.extend_from_slice(&type_data);
643        btf.extend_from_slice(strings);
644
645        let layouts = extract_from_btf(&btf, &X86_64_SYSV).unwrap();
646        assert_eq!(layouts.len(), 1);
647        let l = &layouts[0];
648        assert_eq!(l.name, "mystruct");
649        // Should have one synthetic field representing the 4-byte storage unit
650        assert_eq!(l.fields.len(), 1);
651        assert_eq!(l.fields[0].offset, 0);
652        assert_eq!(l.fields[0].size, 4); // storage unit = u32 = 4 bytes
653        assert!(l.fields[0].name.ends_with("__bits"));
654    }
655
656    #[test]
657    fn btf_skips_unknown_kinds_gracefully() {
658        // Build a BTF blob that has a FUNC kind (12) before the struct.
659        // The parser should skip it and still extract the struct.
660        let strings: &[u8] = b"\0foo\0x\0myfunc\0";
661        let str_len = strings.len() as u32;
662        // "foo" at 1, "x" at 5, "myfunc" at 7
663        let mut type_data: Vec<u8> = Vec::new();
664
665        // INT "x": name_off=5
666        type_data.extend_from_slice(&5u32.to_le_bytes());
667        type_data.extend_from_slice(&(BTF_KIND_INT << 24).to_le_bytes());
668        type_data.extend_from_slice(&4u32.to_le_bytes());
669        type_data.extend_from_slice(&0u32.to_le_bytes());
670
671        // FUNC "myfunc": name_off=7, no extra bytes
672        type_data.extend_from_slice(&7u32.to_le_bytes());
673        type_data.extend_from_slice(&(BTF_KIND_FUNC << 24).to_le_bytes());
674        type_data.extend_from_slice(&1u32.to_le_bytes()); // points to INT
675
676        // STRUCT "foo": name_off=1, vlen=1, size=4
677        type_data.extend_from_slice(&1u32.to_le_bytes());
678        type_data.extend_from_slice(&((BTF_KIND_STRUCT << 24) | 1u32).to_le_bytes());
679        type_data.extend_from_slice(&4u32.to_le_bytes());
680        // member: x at bit_offset=0, type=1 (INT)
681        type_data.extend_from_slice(&5u32.to_le_bytes());
682        type_data.extend_from_slice(&1u32.to_le_bytes());
683        type_data.extend_from_slice(&0u32.to_le_bytes());
684
685        let type_len = type_data.len() as u32;
686        let hdr_len: u32 = 24;
687        let mut btf = Vec::new();
688        btf.extend_from_slice(&BTF_MAGIC.to_le_bytes());
689        btf.push(1);
690        btf.push(0);
691        btf.extend_from_slice(&hdr_len.to_le_bytes());
692        btf.extend_from_slice(&0u32.to_le_bytes());
693        btf.extend_from_slice(&type_len.to_le_bytes());
694        btf.extend_from_slice(&type_len.to_le_bytes());
695        btf.extend_from_slice(&str_len.to_le_bytes());
696        btf.extend_from_slice(&type_data);
697        btf.extend_from_slice(strings);
698
699        let layouts = extract_from_btf(&btf, &X86_64_SYSV).unwrap();
700        assert_eq!(layouts.len(), 1);
701        assert_eq!(layouts[0].name, "foo");
702        assert_eq!(layouts[0].fields[0].name, "x");
703    }
704}