Skip to main content

btf_rs/
section.rs

1use std::{
2    cmp,
3    collections::HashMap,
4    ffi::CStr,
5    io::{BufRead, Cursor, Seek, SeekFrom},
6    mem,
7    sync::Arc,
8};
9
10use memmap2::Mmap;
11
12use crate::{btf::*, cbtf, Error, Result};
13
14// Main internal representation of a parsed BTF section.
15pub struct BtfSection(Box<dyn BtfBackend + Send + Sync>);
16
17impl BtfSection {
18    // Parse a BTF section from a mmaped file. This takes the `Mmap` ownership
19    // to allow reading the BTF data on-demand. This provides a faster
20    // initialization and a lower memory footprint than `Self::from_reader`.
21    pub(super) fn from_mmap(mmap: Mmap, base: Option<Arc<BtfSection>>) -> Result<Self> {
22        Ok(Self(Box::new(MmapBtfSection::new(mmap, base)?)))
23    }
24
25    // Parse a BTF section from a Reader. The BTF data is cached in memory. This
26    // provides faster API access than `Self::from_mmap`.
27    pub(super) fn from_reader<R: Seek + BufRead>(
28        reader: &mut R,
29        base: Option<Arc<BtfSection>>,
30    ) -> Result<Self> {
31        Ok(Self(Box::new(CachedBtfSection::new(reader, base)?)))
32    }
33
34    /// Find a list of BTF ids with a given name.
35    ///
36    /// Using an empty name (`""`) resolves anonymous ids.
37    pub fn resolve_ids_by_name(&self, name: &str) -> Result<Vec<u32>> {
38        self.0.resolve_ids_by_name(name)
39    }
40
41    /// Find a list of BTF ids whose names match a regex.
42    ///
43    /// If the regex matches the empty name (`""`), e.g. `"^$"`, the result will
44    /// contain anonymous ids.
45    #[cfg(feature = "regex")]
46    pub fn resolve_ids_by_regex(&self, re: &regex::Regex) -> Result<Vec<u32>> {
47        self.0.resolve_ids_by_regex(re)
48    }
49
50    /// Find a BTF type with a given id.
51    pub fn resolve_type_by_id(&self, id: u32) -> Result<Type> {
52        self.0.resolve_type_by_id(id)
53    }
54
55    /// Find a list of BTF types with a given name.
56    ///
57    /// Using an empty name (`""`) resolves anonymous types.
58    pub fn resolve_types_by_name(&self, name: &str) -> Result<Vec<Type>> {
59        let mut types = Vec::new();
60        self.resolve_ids_by_name(name)?
61            .iter()
62            .try_for_each(|id| -> Result<()> {
63                types.push(self.resolve_type_by_id(*id)?);
64                Ok(())
65            })?;
66        Ok(types)
67    }
68
69    /// Find a list of BTF types whose names match a regex.
70    ///
71    /// If the regex matches the empty name (`""`), e.g. `"^$"`, the result will
72    /// contain anonymous types.
73    #[cfg(feature = "regex")]
74    pub fn resolve_types_by_regex(&self, re: &regex::Regex) -> Result<Vec<Type>> {
75        let mut types = Vec::new();
76        self.resolve_ids_by_regex(re)?
77            .iter()
78            .try_for_each(|id| -> Result<()> {
79                types.push(self.resolve_type_by_id(*id)?);
80                Ok(())
81            })?;
82        Ok(types)
83    }
84
85    /// Return the range of the type ids contained in this section in the
86    /// (start, end) form ("start" and "end" ids are included).
87    pub fn type_id_range(&self) -> (u32, u32) {
88        let start = self.0.type_id_offset();
89        let end = start + self.0.types() as u32 - 1;
90        (start, end)
91    }
92
93    /// Return an iterator over all types defined in the current BTF section.
94    pub fn type_iter(&self) -> TypeIter<'_> {
95        TypeIter::new(self, None)
96    }
97
98    // Resolve a name referenced by a Type which is defined in the current BTF
99    // section.
100    pub(super) fn resolve_name(&self, r#type: &dyn BtfType) -> Result<String> {
101        let offset = r#type
102            .get_name_offset()
103            .ok_or(Error::OpNotSupp("No name offset in type".to_string()))?;
104        self.resolve_name_by_offset(offset)
105            .ok_or(Error::InvalidString(offset))
106    }
107
108    fn header(&self) -> &cbtf::btf_header {
109        self.0.header()
110    }
111
112    // Return the number of types in the section.
113    fn types(&self) -> usize {
114        self.0.types()
115    }
116
117    // Resolve a name using its offset.
118    fn resolve_name_by_offset(&self, offset: u32) -> Option<String> {
119        self.0.resolve_name_by_offset(offset)
120    }
121}
122
123// Helpers implemented by BTF backends to allow querying the BTF definitions
124// (types, names, etc).
125pub(super) trait BtfBackend {
126    // Access the BTF header as a reference.
127    fn header(&self) -> &cbtf::btf_header;
128    // Return the type id offset.
129    fn type_id_offset(&self) -> u32;
130    // Return the number of types in the section.
131    fn types(&self) -> usize;
132    // Find a list of BTF ids with a given name.
133    fn resolve_ids_by_name(&self, name: &str) -> Result<Vec<u32>>;
134    // Find a BTF type with a given id.
135    fn resolve_type_by_id(&self, id: u32) -> Result<Type>;
136    // Resolve a name using its offset.
137    fn resolve_name_by_offset(&self, offset: u32) -> Option<String>;
138    // Find a list of BTF ids whose names match a regex.
139    #[cfg(feature = "regex")]
140    fn resolve_ids_by_regex(&self, re: &regex::Regex) -> Result<Vec<u32>>;
141}
142
143// Backend for a parsed BTF section with all its types and strings cached in
144// memory. This provides faster API performances at the cost of slower
145// initialization and increase in memory footprint.
146struct CachedBtfSection {
147    header: cbtf::btf_header,
148    // Type id offset from the base, 0 if not.
149    type_offset: u32,
150    // Map from str offsets to the strings. For internal use (name resolution)
151    // only.
152    str_cache: HashMap<u32, String>,
153    // Map from symbol names to their type id, used for retrieving a type by its
154    // name.
155    strings: HashMap<String, Vec<u32>>,
156    // Vector of all the types parsed from the BTF info. The vector makes the
157    // retrieval by their id implicit as the id is incremental in the BTF file;
158    // but that is really the goal here.
159    types: Vec<Type>,
160}
161
162impl CachedBtfSection {
163    fn new<R: Seek + BufRead>(reader: &mut R, base: Option<Arc<BtfSection>>) -> Result<Self> {
164        // First parse the BTF header, retrieve the endianness & perform sanity
165        // checks.
166        let (header, endianness) = cbtf::btf_header::from_reader(reader)?;
167        if header.version != 1 {
168            return Err(Error::Format(format!(
169                "Unsupported BTF version: {}",
170                header.version
171            )));
172        }
173        if header.flags != 0 {
174            return Err(Error::Format(format!(
175                "Unsupported flags {:#x}",
176                header.flags
177            )));
178        }
179        let (est_str, est_ty) = estimate(&header);
180
181        // Cache the str section for later use (name resolution).
182        let offset = u64::checked_add(header.hdr_len as u64, header.str_off as u64)
183            .ok_or(Error::Format("Invalid strings section offset".to_string()))?;
184        reader.seek(SeekFrom::Start(offset))?;
185
186        let mut str_cache = HashMap::with_capacity(est_str);
187        let mut offset: u32 = 0;
188
189        // For split BTFs both ids and string offsets are logically consecutive.
190        let (mut id, start_str_off) = match base {
191            None => (1, 0),
192            Some(ref base) => (base.types() as u32, base.header().str_len),
193        };
194
195        while offset < header.str_len {
196            let mut raw = Vec::new();
197            let bytes = reader.read_until(b'\0', &mut raw)? as u32;
198
199            let s = bytes_to_str(&raw)?;
200            str_cache.insert(start_str_off + offset, String::from(s));
201
202            offset += bytes;
203        }
204
205        // Finally build our representation of the BTF types.
206        let offset = u64::checked_add(header.hdr_len as u64, header.type_off as u64)
207            .ok_or(Error::Format("Invalid types section offset".to_string()))?;
208        reader.seek(SeekFrom::Start(offset))?;
209
210        let mut strings: HashMap<String, Vec<u32>> = HashMap::with_capacity(est_str);
211        let mut types = Vec::with_capacity(est_ty);
212
213        if base.is_none() {
214            // Add special type Void with ID 0 (not described in type section)
215            // only on base BTF.
216            types.push(Type::Void);
217        }
218
219        let end_type_section = u64::checked_add(offset, header.type_len as u64)
220            .ok_or(Error::Format("Invalid types section length".to_string()))?;
221        while reader.stream_position()? < end_type_section {
222            let bt = cbtf::btf_type::from_reader(reader, &endianness)?;
223            let r#type = Type::from_reader(reader, &endianness, bt)?;
224
225            if let Some(name_off) = bt.name_offset() {
226                // Look for the name in our own cache, and if not found try
227                // looking into the base one (if any).
228                let name = str_cache.get(&name_off).cloned().or_else(|| {
229                    base.as_ref()
230                        .and_then(|base| base.resolve_name_by_offset(name_off))
231                });
232
233                match name {
234                    Some(ref name) => match strings.get_mut(name) {
235                        Some(entry) => entry.push(id),
236                        None => _ = strings.insert(name.clone(), vec![id]),
237                    },
238                    None => return Err(Error::InvalidString(name_off)),
239                }
240            }
241
242            types.push(r#type);
243            id += 1;
244        }
245
246        // Sanity check
247        if reader.stream_position()? != end_type_section {
248            return Err(Error::Format("Invalid type section".to_string()));
249        }
250
251        Ok(Self {
252            header,
253            type_offset: match base {
254                Some(base) => base.types() as u32,
255                None => 0,
256            },
257            str_cache,
258            strings,
259            types,
260        })
261    }
262}
263
264impl BtfBackend for CachedBtfSection {
265    fn header(&self) -> &cbtf::btf_header {
266        &self.header
267    }
268
269    fn type_id_offset(&self) -> u32 {
270        self.type_offset
271    }
272
273    fn types(&self) -> usize {
274        self.types.len()
275    }
276
277    fn resolve_ids_by_name(&self, name: &str) -> Result<Vec<u32>> {
278        Ok(self.strings.get(name).cloned().unwrap_or_default())
279    }
280
281    fn resolve_type_by_id(&self, id: u32) -> Result<Type> {
282        let local_id = match id.checked_sub(self.type_offset) {
283            Some(id) if (id as usize) < self.types() => id,
284            _ => return Err(Error::InvalidType(id)),
285        };
286
287        self.types
288            .get(local_id as usize)
289            .cloned()
290            .ok_or(Error::InvalidType(id))
291    }
292
293    fn resolve_name_by_offset(&self, offset: u32) -> Option<String> {
294        self.str_cache.get(&offset).cloned()
295    }
296
297    #[cfg(feature = "regex")]
298    fn resolve_ids_by_regex(&self, re: &regex::Regex) -> Result<Vec<u32>> {
299        Ok(self
300            .strings
301            .iter()
302            .filter_map(|(name, ids)| match re.is_match(name) {
303                true => Some(ids.clone()),
304                false => None,
305            })
306            .flatten()
307            .collect::<Vec<_>>())
308    }
309}
310
311// Backend for a parsed BTF section keeping the input data memory-mapped. This
312// provides a faster initialization and lower memory footprint at the cost of
313// slower API performances.
314struct MmapBtfSection {
315    endianness: cbtf::Endianness,
316    header: cbtf::btf_header,
317    // String offset from the base, 0 if not.
318    str_offset: u32,
319    // Type id offset from the base, 0 if not.
320    type_offset: u32,
321    // Number of types defined in the section.
322    types: usize,
323    // Memory-mapped reader.
324    mmap: Mmap,
325    // Map from type ids to their offsets in the mmaped BTF.
326    type_offsets: Vec<usize>,
327}
328
329impl MmapBtfSection {
330    fn new(mmap: Mmap, base: Option<Arc<BtfSection>>) -> Result<Self> {
331        let len = mmap.len();
332        let mut reader = Cursor::new(mmap);
333
334        // First parse the BTF header, retrieve the endianness & perform sanity
335        // checks.
336        let (header, endianness) = cbtf::btf_header::from_reader(&mut reader)?;
337        if header.version != 1 {
338            return Err(Error::Format(format!(
339                "Unsupported BTF version: {}",
340                header.version
341            )));
342        }
343        if header.flags != 0 {
344            return Err(Error::Format(format!(
345                "Unsupported flags {:#x}",
346                header.flags
347            )));
348        }
349        let (_, est_ty) = estimate(&header);
350
351        // Then sanity check the string section.
352        let offset = u64::checked_add(header.hdr_len as u64, header.str_off as u64)
353            .ok_or(Error::Format("Invalid strings section offset".to_string()))?;
354        let offset = u64::checked_add(offset, header.str_len as u64)
355            .ok_or(Error::Format("Invalid strings section length".to_string()))?;
356        if len < offset as usize {
357            return Err(Error::Format(
358                "String section is missing or incomplete".to_string(),
359            ));
360        }
361
362        // Finally build our representation of the BTF types.
363        let offset = u64::checked_add(header.hdr_len as u64, header.type_off as u64)
364            .ok_or(Error::Format("Invalid types section offset".to_string()))?;
365        reader.seek(SeekFrom::Start(offset))?;
366
367        let mut offsets = Vec::with_capacity(est_ty);
368        let mut types = 0;
369
370        let end_type_section = u64::checked_add(offset, header.type_len as u64)
371            .ok_or(Error::Format("Invalid types section length".to_string()))?;
372        while reader.stream_position()? < end_type_section {
373            offsets.push(reader.stream_position()? as usize);
374            cbtf::btf_skip_type(&mut reader, &endianness)?;
375            types += 1;
376        }
377
378        // Sanity check
379        if reader.stream_position()? != end_type_section {
380            return Err(Error::Format("Invalid type section".to_string()));
381        }
382
383        let (str_offset, type_offset) = match base {
384            Some(base) => (base.header().str_len, base.types() as u32),
385            None => (0, 0),
386        };
387
388        Ok(Self {
389            endianness,
390            header,
391            str_offset,
392            type_offset,
393            types,
394            mmap: reader.into_inner(),
395            type_offsets: offsets,
396        })
397    }
398
399    // Iterate over the type names, calling a function on them (providing the
400    // type id and name bytes buffer).
401    fn iter_over_names<F>(&self, mut f: F) -> Result<()>
402    where
403        F: FnMut(u32, &[u8]) -> Result<()>,
404    {
405        let mmap = &self.mmap;
406
407        for (id, offset) in self.type_offsets.iter().enumerate() {
408            let bt = cbtf::btf_type::from_bytes(&mmap[*offset..], &self.endianness)?;
409            let name_off = match bt.name_offset() {
410                Some(offset) => offset,
411                None => continue,
412            };
413
414            if name_off < self.header.str_len {
415                let start = (self.header.hdr_len + self.header.str_off + name_off) as usize;
416
417                f(id as u32 + 1 + self.type_offset, &mmap[start..])?;
418            }
419        }
420
421        Ok(())
422    }
423}
424
425impl BtfBackend for MmapBtfSection {
426    fn header(&self) -> &cbtf::btf_header {
427        &self.header
428    }
429
430    fn type_id_offset(&self) -> u32 {
431        self.type_offset
432    }
433
434    fn types(&self) -> usize {
435        // Take `Type::Void` into account for base sections.
436        (if self.type_offset != 0 { 0 } else { 1 }) + self.types
437    }
438
439    fn resolve_ids_by_name(&self, name: &str) -> Result<Vec<u32>> {
440        let len = name.len();
441        let mut ids = Vec::new();
442
443        self.iter_over_names(|id, buf| {
444            // If len == buf.len(), the NULL char isn't there.
445            if len < buf.len() && buf[len] == b'\0' && name.as_bytes() == &buf[..len] {
446                ids.push(id);
447            }
448            Ok(())
449        })?;
450
451        Ok(ids)
452    }
453
454    fn resolve_type_by_id(&self, id: u32) -> Result<Type> {
455        let local_id = match id.checked_sub(self.type_offset) {
456            Some(id) if (id as usize) < self.types() => id,
457            _ => return Err(Error::InvalidType(id)),
458        };
459
460        if id == 0 {
461            return Ok(Type::Void);
462        }
463
464        Ok(match self.type_offsets.get(local_id as usize - 1) {
465            Some(offset) => {
466                let bt = cbtf::btf_type::from_bytes(&self.mmap[*offset..], &self.endianness)?;
467                Type::from_bytes(
468                    &self.mmap[(*offset + mem::size_of::<cbtf::btf_type>())..],
469                    &self.endianness,
470                    bt,
471                )?
472            }
473            None => return Err(Error::InvalidType(id)),
474        })
475    }
476
477    fn resolve_name_by_offset(&self, offset: u32) -> Option<String> {
478        let offset = match offset.checked_sub(self.str_offset) {
479            Some(id) if id <= self.header.str_len => id,
480            _ => return None,
481        };
482
483        let start = (self.header.hdr_len + self.header.str_off + offset) as usize;
484        bytes_to_str(&self.mmap[start..])
485            .ok()
486            .map(|s| s.to_string())
487    }
488
489    #[cfg(feature = "regex")]
490    fn resolve_ids_by_regex(&self, re: &regex::Regex) -> Result<Vec<u32>> {
491        let mut ids = Vec::new();
492        self.iter_over_names(|id, buf| {
493            if let Ok(s) = bytes_to_str(buf) {
494                if re.is_match(s) {
495                    ids.push(id);
496                }
497            }
498            Ok(())
499        })?;
500        Ok(ids)
501    }
502}
503
504// Estimate the number of strings and types defined in the BTF section.
505fn estimate(header: &cbtf::btf_header) -> (usize, usize) {
506    let mut strings = header.str_len as usize / 15;
507    let mut types = header.type_len as usize / 22;
508
509    // Cap at 16MB.
510    const MAX_SIZE: usize = 16 * 1024 * 1024;
511    strings = cmp::min(strings, MAX_SIZE / mem::size_of::<String>());
512    types = cmp::min(types, MAX_SIZE / mem::size_of::<Type>());
513
514    (strings, types)
515}
516
517// Converts a bytes array to an str representation, without copy.
518fn bytes_to_str(buf: &[u8]) -> Result<&str> {
519    CStr::from_bytes_until_nul(buf)
520        .map_err(|e| Error::Format(format!("Could not parse string: {e}")))?
521        .to_str()
522        .map_err(|e| Error::Format(format!("Invalid UTF-8 string: {e}")))
523}