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                    is_repr_rust: false,
468                    suppressed_findings: Vec::new(),
469                    uncertain_fields: Vec::new(),
470                });
471
472                let _ = idx;
473            }
474        }
475
476        layouts
477    }
478}
479
480fn read_btf_str(str_data: &[u8], off: usize) -> String {
481    if off >= str_data.len() {
482        return String::new();
483    }
484    let end = str_data[off..]
485        .iter()
486        .position(|&b| b == 0)
487        .map(|p| off + p)
488        .unwrap_or(str_data.len());
489    String::from_utf8_lossy(&str_data[off..end]).into_owned()
490}
491
492// ── public entry point ────────────────────────────────────────────────────────
493
494/// Extract struct layouts from raw BTF data (the contents of a `.BTF` ELF section).
495pub fn extract_from_btf(
496    btf_data: &[u8],
497    arch: &'static ArchConfig,
498) -> anyhow::Result<Vec<StructLayout>> {
499    let parser = BtfParser::new(btf_data, arch)?;
500    Ok(parser.extract_structs())
501}
502
503// ── tests ─────────────────────────────────────────────────────────────────────
504
505#[cfg(test)]
506mod tests {
507    use super::*;
508    use padlock_core::arch::X86_64_SYSV;
509
510    /// Build a minimal valid BTF blob containing one struct with two int fields.
511    fn build_test_btf() -> Vec<u8> {
512        // Strings: "\0point\0x\0y\0"
513        let strings: &[u8] = b"\0point\0x\0y\0";
514        let str_len = strings.len() as u32;
515
516        // Strings layout: "\0point\0x\0y\0"
517        //   offset 0: \0 (empty name)
518        //   offset 1: "point" (6 bytes including null)
519        //   offset 7: "x" (2 bytes including null)
520        //   offset 9: "y" (2 bytes including null)
521        // Types:
522        // type_id 1: INT "x"  size=4
523        // type_id 2: INT "y"  size=4
524        // type_id 3: STRUCT "point"  size=8  vlen=2
525        //   member 0: name="x"(off=7) type=1 offset=0
526        //   member 1: name="y"(off=9) type=2 offset=32 (bit offset)
527
528        let mut type_data: Vec<u8> = Vec::new();
529
530        // INT "x": name_off=7, info=(1<<24|0), size=4; extra int_encoding=4bytes
531        let x_name_off: u32 = 7;
532        type_data.extend_from_slice(&x_name_off.to_le_bytes());
533        type_data.extend_from_slice(&(BTF_KIND_INT << 24).to_le_bytes());
534        type_data.extend_from_slice(&4u32.to_le_bytes());
535        type_data.extend_from_slice(&0u32.to_le_bytes()); // int encoding
536
537        // INT "y": name_off=9
538        let y_name_off: u32 = 9;
539        type_data.extend_from_slice(&y_name_off.to_le_bytes());
540        type_data.extend_from_slice(&(BTF_KIND_INT << 24).to_le_bytes());
541        type_data.extend_from_slice(&4u32.to_le_bytes());
542        type_data.extend_from_slice(&0u32.to_le_bytes());
543
544        // STRUCT "point": name_off=1, info=(4<<24|2), size=8
545        let point_name_off: u32 = 1;
546        type_data.extend_from_slice(&point_name_off.to_le_bytes());
547        type_data.extend_from_slice(&((BTF_KIND_STRUCT << 24) | 2u32).to_le_bytes()); // vlen=2
548        type_data.extend_from_slice(&8u32.to_le_bytes()); // size=8
549        // member 0: x
550        type_data.extend_from_slice(&x_name_off.to_le_bytes()); // name_off for "x"
551        type_data.extend_from_slice(&1u32.to_le_bytes()); // type_id=1
552        type_data.extend_from_slice(&0u32.to_le_bytes()); // bit_offset=0
553        // member 1: y
554        type_data.extend_from_slice(&y_name_off.to_le_bytes()); // name_off for "y"
555        type_data.extend_from_slice(&2u32.to_le_bytes()); // type_id=2
556        type_data.extend_from_slice(&32u32.to_le_bytes()); // bit_offset=32
557
558        let type_len = type_data.len() as u32;
559
560        // BTF header (24 bytes)
561        let hdr_len: u32 = 24;
562        let mut btf = Vec::new();
563        btf.extend_from_slice(&BTF_MAGIC.to_le_bytes()); // magic
564        btf.push(1); // version
565        btf.push(0); // flags
566        btf.extend_from_slice(&hdr_len.to_le_bytes()); // hdr_len
567        btf.extend_from_slice(&0u32.to_le_bytes()); // type_off = 0
568        btf.extend_from_slice(&type_len.to_le_bytes()); // type_len
569        btf.extend_from_slice(&type_len.to_le_bytes()); // str_off = after types
570        btf.extend_from_slice(&str_len.to_le_bytes()); // str_len
571        btf.extend_from_slice(&type_data);
572        btf.extend_from_slice(strings);
573        btf
574    }
575
576    #[test]
577    fn btf_parse_simple_struct() {
578        let btf = build_test_btf();
579        let layouts = extract_from_btf(&btf, &X86_64_SYSV).unwrap();
580        assert_eq!(layouts.len(), 1);
581        assert_eq!(layouts[0].name, "point");
582        assert_eq!(layouts[0].total_size, 8);
583        assert_eq!(layouts[0].fields.len(), 2);
584    }
585
586    #[test]
587    fn btf_field_offsets_correct() {
588        let btf = build_test_btf();
589        let layouts = extract_from_btf(&btf, &X86_64_SYSV).unwrap();
590        let l = &layouts[0];
591        assert_eq!(l.fields[0].name, "x");
592        assert_eq!(l.fields[0].offset, 0);
593        assert_eq!(l.fields[1].name, "y");
594        assert_eq!(l.fields[1].offset, 4);
595    }
596
597    #[test]
598    fn btf_invalid_magic_errors() {
599        let mut btf = build_test_btf();
600        btf[0] = 0xff;
601        btf[1] = 0xff;
602        assert!(extract_from_btf(&btf, &X86_64_SYSV).is_err());
603    }
604
605    #[test]
606    fn btf_bitfield_members_become_synthetic_storage_unit_fields() {
607        // Struct with one bitfield member: `u32 flags : 3` at bit_offset = 0.
608        // Expected: a synthetic "flags__bits" field of type u32 at offset 0.
609        let strings: &[u8] = b"\0mystruct\0flags\0";
610        // "mystruct" at 1, "flags" at 10
611        let str_len = strings.len() as u32;
612        let mut type_data: Vec<u8> = Vec::new();
613
614        // INT type (u32): name_off=10 (flags), size=4
615        let flags_name_off: u32 = 10;
616        type_data.extend_from_slice(&flags_name_off.to_le_bytes());
617        type_data.extend_from_slice(&(BTF_KIND_INT << 24).to_le_bytes());
618        type_data.extend_from_slice(&4u32.to_le_bytes());
619        type_data.extend_from_slice(&0u32.to_le_bytes()); // int encoding
620
621        // STRUCT "mystruct": kind_flag=1 (bit 31 of info set), vlen=1, size=4
622        let struct_name_off: u32 = 1;
623        // info: kind_flag(1) | kind(4 << 24) | vlen(1)
624        let struct_info: u32 = (1u32 << 31) | (BTF_KIND_STRUCT << 24) | 1u32;
625        type_data.extend_from_slice(&struct_name_off.to_le_bytes());
626        type_data.extend_from_slice(&struct_info.to_le_bytes());
627        type_data.extend_from_slice(&4u32.to_le_bytes()); // size=4
628        // member: flags, type=1, offset encoding = (bitfield_size=3 << 24) | bit_offset=0
629        let m_offset: u32 = (3u32 << 24) | 0u32; // 3-bit bitfield at bit 0
630        type_data.extend_from_slice(&flags_name_off.to_le_bytes());
631        type_data.extend_from_slice(&1u32.to_le_bytes()); // type_id=1
632        type_data.extend_from_slice(&m_offset.to_le_bytes());
633
634        let type_len = type_data.len() as u32;
635        let hdr_len: u32 = 24;
636        let mut btf = Vec::new();
637        btf.extend_from_slice(&BTF_MAGIC.to_le_bytes());
638        btf.push(1);
639        btf.push(0);
640        btf.extend_from_slice(&hdr_len.to_le_bytes());
641        btf.extend_from_slice(&0u32.to_le_bytes());
642        btf.extend_from_slice(&type_len.to_le_bytes());
643        btf.extend_from_slice(&type_len.to_le_bytes());
644        btf.extend_from_slice(&str_len.to_le_bytes());
645        btf.extend_from_slice(&type_data);
646        btf.extend_from_slice(strings);
647
648        let layouts = extract_from_btf(&btf, &X86_64_SYSV).unwrap();
649        assert_eq!(layouts.len(), 1);
650        let l = &layouts[0];
651        assert_eq!(l.name, "mystruct");
652        // Should have one synthetic field representing the 4-byte storage unit
653        assert_eq!(l.fields.len(), 1);
654        assert_eq!(l.fields[0].offset, 0);
655        assert_eq!(l.fields[0].size, 4); // storage unit = u32 = 4 bytes
656        assert!(l.fields[0].name.ends_with("__bits"));
657    }
658
659    #[test]
660    fn btf_skips_unknown_kinds_gracefully() {
661        // Build a BTF blob that has a FUNC kind (12) before the struct.
662        // The parser should skip it and still extract the struct.
663        let strings: &[u8] = b"\0foo\0x\0myfunc\0";
664        let str_len = strings.len() as u32;
665        // "foo" at 1, "x" at 5, "myfunc" at 7
666        let mut type_data: Vec<u8> = Vec::new();
667
668        // INT "x": name_off=5
669        type_data.extend_from_slice(&5u32.to_le_bytes());
670        type_data.extend_from_slice(&(BTF_KIND_INT << 24).to_le_bytes());
671        type_data.extend_from_slice(&4u32.to_le_bytes());
672        type_data.extend_from_slice(&0u32.to_le_bytes());
673
674        // FUNC "myfunc": name_off=7, no extra bytes
675        type_data.extend_from_slice(&7u32.to_le_bytes());
676        type_data.extend_from_slice(&(BTF_KIND_FUNC << 24).to_le_bytes());
677        type_data.extend_from_slice(&1u32.to_le_bytes()); // points to INT
678
679        // STRUCT "foo": name_off=1, vlen=1, size=4
680        type_data.extend_from_slice(&1u32.to_le_bytes());
681        type_data.extend_from_slice(&((BTF_KIND_STRUCT << 24) | 1u32).to_le_bytes());
682        type_data.extend_from_slice(&4u32.to_le_bytes());
683        // member: x at bit_offset=0, type=1 (INT)
684        type_data.extend_from_slice(&5u32.to_le_bytes());
685        type_data.extend_from_slice(&1u32.to_le_bytes());
686        type_data.extend_from_slice(&0u32.to_le_bytes());
687
688        let type_len = type_data.len() as u32;
689        let hdr_len: u32 = 24;
690        let mut btf = Vec::new();
691        btf.extend_from_slice(&BTF_MAGIC.to_le_bytes());
692        btf.push(1);
693        btf.push(0);
694        btf.extend_from_slice(&hdr_len.to_le_bytes());
695        btf.extend_from_slice(&0u32.to_le_bytes());
696        btf.extend_from_slice(&type_len.to_le_bytes());
697        btf.extend_from_slice(&type_len.to_le_bytes());
698        btf.extend_from_slice(&str_len.to_le_bytes());
699        btf.extend_from_slice(&type_data);
700        btf.extend_from_slice(strings);
701
702        let layouts = extract_from_btf(&btf, &X86_64_SYSV).unwrap();
703        assert_eq!(layouts.len(), 1);
704        assert_eq!(layouts[0].name, "foo");
705        assert_eq!(layouts[0].fields[0].name, "x");
706    }
707}