use std::{
cmp,
collections::HashMap,
ffi::CStr,
io::{BufRead, Cursor, Seek, SeekFrom},
mem,
sync::Arc,
};
use memmap2::Mmap;
use crate::{btf::*, cbtf, Error, Result};
pub struct BtfSection(Box<dyn BtfBackend + Send + Sync>);
impl BtfSection {
pub(super) fn from_mmap(mmap: Mmap, base: Option<Arc<BtfSection>>) -> Result<Self> {
Ok(Self(Box::new(MmapBtfSection::new(mmap, base)?)))
}
pub(super) fn from_reader<R: Seek + BufRead>(
reader: &mut R,
base: Option<Arc<BtfSection>>,
) -> Result<Self> {
Ok(Self(Box::new(CachedBtfSection::new(reader, base)?)))
}
pub fn resolve_ids_by_name(&self, name: &str) -> Result<Vec<u32>> {
self.0.resolve_ids_by_name(name)
}
#[cfg(feature = "regex")]
pub fn resolve_ids_by_regex(&self, re: ®ex::Regex) -> Result<Vec<u32>> {
self.0.resolve_ids_by_regex(re)
}
pub fn resolve_type_by_id(&self, id: u32) -> Result<Type> {
self.0.resolve_type_by_id(id)
}
pub fn resolve_types_by_name(&self, name: &str) -> Result<Vec<Type>> {
let mut types = Vec::new();
self.resolve_ids_by_name(name)?
.iter()
.try_for_each(|id| -> Result<()> {
types.push(self.resolve_type_by_id(*id)?);
Ok(())
})?;
Ok(types)
}
#[cfg(feature = "regex")]
pub fn resolve_types_by_regex(&self, re: ®ex::Regex) -> Result<Vec<Type>> {
let mut types = Vec::new();
self.resolve_ids_by_regex(re)?
.iter()
.try_for_each(|id| -> Result<()> {
types.push(self.resolve_type_by_id(*id)?);
Ok(())
})?;
Ok(types)
}
pub fn type_id_range(&self) -> (u32, u32) {
let start = self.0.type_id_offset();
let end = start + self.0.types() as u32 - 1;
(start, end)
}
pub fn type_iter(&self) -> TypeIter<'_> {
TypeIter::new(self, None)
}
pub(super) fn resolve_name(&self, r#type: &dyn BtfType) -> Result<String> {
let offset = r#type
.get_name_offset()
.ok_or(Error::OpNotSupp("No name offset in type".to_string()))?;
self.resolve_name_by_offset(offset)
.ok_or(Error::InvalidString(offset))
}
fn header(&self) -> &cbtf::btf_header {
self.0.header()
}
fn types(&self) -> usize {
self.0.types()
}
fn resolve_name_by_offset(&self, offset: u32) -> Option<String> {
self.0.resolve_name_by_offset(offset)
}
}
pub(super) trait BtfBackend {
fn header(&self) -> &cbtf::btf_header;
fn type_id_offset(&self) -> u32;
fn types(&self) -> usize;
fn resolve_ids_by_name(&self, name: &str) -> Result<Vec<u32>>;
fn resolve_type_by_id(&self, id: u32) -> Result<Type>;
fn resolve_name_by_offset(&self, offset: u32) -> Option<String>;
#[cfg(feature = "regex")]
fn resolve_ids_by_regex(&self, re: ®ex::Regex) -> Result<Vec<u32>>;
}
struct CachedBtfSection {
header: cbtf::btf_header,
type_offset: u32,
str_cache: HashMap<u32, String>,
strings: HashMap<String, Vec<u32>>,
types: Vec<Type>,
}
impl CachedBtfSection {
fn new<R: Seek + BufRead>(reader: &mut R, base: Option<Arc<BtfSection>>) -> Result<Self> {
let (header, endianness) = cbtf::btf_header::from_reader(reader)?;
if header.version != 1 {
return Err(Error::Format(format!(
"Unsupported BTF version: {}",
header.version
)));
}
if header.flags != 0 {
return Err(Error::Format(format!(
"Unsupported flags {:#x}",
header.flags
)));
}
let (est_str, est_ty) = estimate(&header);
let offset = u64::checked_add(header.hdr_len as u64, header.str_off as u64)
.ok_or(Error::Format("Invalid strings section offset".to_string()))?;
reader.seek(SeekFrom::Start(offset))?;
let mut str_cache = HashMap::with_capacity(est_str);
let mut offset: u32 = 0;
let (mut id, start_str_off) = match base {
None => (1, 0),
Some(ref base) => (base.types() as u32, base.header().str_len),
};
while offset < header.str_len {
let mut raw = Vec::new();
let bytes = reader.read_until(b'\0', &mut raw)? as u32;
let s = bytes_to_str(&raw)?;
str_cache.insert(start_str_off + offset, String::from(s));
offset += bytes;
}
let offset = u64::checked_add(header.hdr_len as u64, header.type_off as u64)
.ok_or(Error::Format("Invalid types section offset".to_string()))?;
reader.seek(SeekFrom::Start(offset))?;
let mut strings: HashMap<String, Vec<u32>> = HashMap::with_capacity(est_str);
let mut types = Vec::with_capacity(est_ty);
if base.is_none() {
types.push(Type::Void);
}
let end_type_section = u64::checked_add(offset, header.type_len as u64)
.ok_or(Error::Format("Invalid types section length".to_string()))?;
while reader.stream_position()? < end_type_section {
let bt = cbtf::btf_type::from_reader(reader, &endianness)?;
let r#type = Type::from_reader(reader, &endianness, bt)?;
if let Some(name_off) = bt.name_offset() {
let name = str_cache.get(&name_off).cloned().or_else(|| {
base.as_ref()
.and_then(|base| base.resolve_name_by_offset(name_off))
});
match name {
Some(ref name) => match strings.get_mut(name) {
Some(entry) => entry.push(id),
None => _ = strings.insert(name.clone(), vec![id]),
},
None => return Err(Error::InvalidString(name_off)),
}
}
types.push(r#type);
id += 1;
}
if reader.stream_position()? != end_type_section {
return Err(Error::Format("Invalid type section".to_string()));
}
Ok(Self {
header,
type_offset: match base {
Some(base) => base.types() as u32,
None => 0,
},
str_cache,
strings,
types,
})
}
}
impl BtfBackend for CachedBtfSection {
fn header(&self) -> &cbtf::btf_header {
&self.header
}
fn type_id_offset(&self) -> u32 {
self.type_offset
}
fn types(&self) -> usize {
self.types.len()
}
fn resolve_ids_by_name(&self, name: &str) -> Result<Vec<u32>> {
Ok(self.strings.get(name).cloned().unwrap_or_default())
}
fn resolve_type_by_id(&self, id: u32) -> Result<Type> {
let local_id = match id.checked_sub(self.type_offset) {
Some(id) if (id as usize) < self.types() => id,
_ => return Err(Error::InvalidType(id)),
};
self.types
.get(local_id as usize)
.cloned()
.ok_or(Error::InvalidType(id))
}
fn resolve_name_by_offset(&self, offset: u32) -> Option<String> {
self.str_cache.get(&offset).cloned()
}
#[cfg(feature = "regex")]
fn resolve_ids_by_regex(&self, re: ®ex::Regex) -> Result<Vec<u32>> {
Ok(self
.strings
.iter()
.filter_map(|(name, ids)| match re.is_match(name) {
true => Some(ids.clone()),
false => None,
})
.flatten()
.collect::<Vec<_>>())
}
}
struct MmapBtfSection {
endianness: cbtf::Endianness,
header: cbtf::btf_header,
str_offset: u32,
type_offset: u32,
types: usize,
mmap: Mmap,
type_offsets: Vec<usize>,
}
impl MmapBtfSection {
fn new(mmap: Mmap, base: Option<Arc<BtfSection>>) -> Result<Self> {
let len = mmap.len();
let mut reader = Cursor::new(mmap);
let (header, endianness) = cbtf::btf_header::from_reader(&mut reader)?;
if header.version != 1 {
return Err(Error::Format(format!(
"Unsupported BTF version: {}",
header.version
)));
}
if header.flags != 0 {
return Err(Error::Format(format!(
"Unsupported flags {:#x}",
header.flags
)));
}
let (_, est_ty) = estimate(&header);
let offset = u64::checked_add(header.hdr_len as u64, header.str_off as u64)
.ok_or(Error::Format("Invalid strings section offset".to_string()))?;
let offset = u64::checked_add(offset, header.str_len as u64)
.ok_or(Error::Format("Invalid strings section length".to_string()))?;
if len < offset as usize {
return Err(Error::Format(
"String section is missing or incomplete".to_string(),
));
}
let offset = u64::checked_add(header.hdr_len as u64, header.type_off as u64)
.ok_or(Error::Format("Invalid types section offset".to_string()))?;
reader.seek(SeekFrom::Start(offset))?;
let mut offsets = Vec::with_capacity(est_ty);
let mut types = 0;
let end_type_section = u64::checked_add(offset, header.type_len as u64)
.ok_or(Error::Format("Invalid types section length".to_string()))?;
while reader.stream_position()? < end_type_section {
offsets.push(reader.stream_position()? as usize);
cbtf::btf_skip_type(&mut reader, &endianness)?;
types += 1;
}
if reader.stream_position()? != end_type_section {
return Err(Error::Format("Invalid type section".to_string()));
}
let (str_offset, type_offset) = match base {
Some(base) => (base.header().str_len, base.types() as u32),
None => (0, 0),
};
Ok(Self {
endianness,
header,
str_offset,
type_offset,
types,
mmap: reader.into_inner(),
type_offsets: offsets,
})
}
fn iter_over_names<F>(&self, mut f: F) -> Result<()>
where
F: FnMut(u32, &[u8]) -> Result<()>,
{
let mmap = &self.mmap;
for (id, offset) in self.type_offsets.iter().enumerate() {
let bt = cbtf::btf_type::from_bytes(&mmap[*offset..], &self.endianness)?;
let name_off = match bt.name_offset() {
Some(offset) => offset,
None => continue,
};
if name_off < self.header.str_len {
let start = (self.header.hdr_len + self.header.str_off + name_off) as usize;
f(id as u32 + 1 + self.type_offset, &mmap[start..])?;
}
}
Ok(())
}
}
impl BtfBackend for MmapBtfSection {
fn header(&self) -> &cbtf::btf_header {
&self.header
}
fn type_id_offset(&self) -> u32 {
self.type_offset
}
fn types(&self) -> usize {
(if self.type_offset != 0 { 0 } else { 1 }) + self.types
}
fn resolve_ids_by_name(&self, name: &str) -> Result<Vec<u32>> {
let len = name.len();
let mut ids = Vec::new();
self.iter_over_names(|id, buf| {
if len < buf.len() && buf[len] == b'\0' && name.as_bytes() == &buf[..len] {
ids.push(id);
}
Ok(())
})?;
Ok(ids)
}
fn resolve_type_by_id(&self, id: u32) -> Result<Type> {
let local_id = match id.checked_sub(self.type_offset) {
Some(id) if (id as usize) < self.types() => id,
_ => return Err(Error::InvalidType(id)),
};
if id == 0 {
return Ok(Type::Void);
}
Ok(match self.type_offsets.get(local_id as usize - 1) {
Some(offset) => {
let bt = cbtf::btf_type::from_bytes(&self.mmap[*offset..], &self.endianness)?;
Type::from_bytes(
&self.mmap[(*offset + mem::size_of::<cbtf::btf_type>())..],
&self.endianness,
bt,
)?
}
None => return Err(Error::InvalidType(id)),
})
}
fn resolve_name_by_offset(&self, offset: u32) -> Option<String> {
let offset = match offset.checked_sub(self.str_offset) {
Some(id) if id <= self.header.str_len => id,
_ => return None,
};
let start = (self.header.hdr_len + self.header.str_off + offset) as usize;
bytes_to_str(&self.mmap[start..])
.ok()
.map(|s| s.to_string())
}
#[cfg(feature = "regex")]
fn resolve_ids_by_regex(&self, re: ®ex::Regex) -> Result<Vec<u32>> {
let mut ids = Vec::new();
self.iter_over_names(|id, buf| {
if let Ok(s) = bytes_to_str(buf) {
if re.is_match(s) {
ids.push(id);
}
}
Ok(())
})?;
Ok(ids)
}
}
fn estimate(header: &cbtf::btf_header) -> (usize, usize) {
let mut strings = header.str_len as usize / 15;
let mut types = header.type_len as usize / 22;
const MAX_SIZE: usize = 16 * 1024 * 1024;
strings = cmp::min(strings, MAX_SIZE / mem::size_of::<String>());
types = cmp::min(types, MAX_SIZE / mem::size_of::<Type>());
(strings, types)
}
fn bytes_to_str(buf: &[u8]) -> Result<&str> {
CStr::from_bytes_until_nul(buf)
.map_err(|e| Error::Format(format!("Could not parse string: {e}")))?
.to_str()
.map_err(|e| Error::Format(format!("Invalid UTF-8 string: {e}")))
}