Improve relocation errors

Use BpfError::RelocationError for both maps and BTF relocations. The
error includes the name of the program that failed, and the source error
stored as Box<dyn Error>.

This hides the implementation details of the source errors - which are
unrecoverable anyway - while still allowing fine grained error messages.
pull/1/head
Alessandro Decina 4 years ago
parent 37c3a198c4
commit 54637eab04

@ -2,6 +2,7 @@ use std::{
cell::{Ref, RefCell, RefMut}, cell::{Ref, RefCell, RefMut},
collections::HashMap, collections::HashMap,
convert::TryFrom, convert::TryFrom,
error::Error,
io, io,
}; };
@ -9,8 +10,7 @@ use thiserror::Error;
use crate::{ use crate::{
maps::{Map, MapError}, maps::{Map, MapError},
obj::btf::RelocationError as BtfRelocationError, obj::{btf::BtfError, Object, ParseError},
obj::{btf::BtfError, Object, ParseError, RelocationError},
programs::{KProbe, Program, ProgramData, ProgramError, SocketFilter, TracePoint, UProbe, Xdp}, programs::{KProbe, Program, ProgramData, ProgramError, SocketFilter, TracePoint, UProbe, Xdp},
syscalls::bpf_map_update_elem_ptr, syscalls::bpf_map_update_elem_ptr,
}; };
@ -152,11 +152,11 @@ pub enum BpfError {
#[error("BTF error: {0}")] #[error("BTF error: {0}")]
BtfError(#[from] BtfError), BtfError(#[from] BtfError),
#[error("error relocating BPF object: {0}")] #[error("error relocating BPF program `{program_name}`: {error}")]
RelocationError(#[from] RelocationError), RelocationError {
program_name: String,
#[error(transparent)] error: Box<dyn Error>,
BtfRelocationError(#[from] BtfRelocationError), },
#[error("map error: {0}")] #[error("map error: {0}")]
MapError(#[from] MapError), MapError(#[from] MapError),

@ -22,10 +22,10 @@ pub enum BtfError {
#[error("error parsing BTF header")] #[error("error parsing BTF header")]
InvalidHeader, InvalidHeader,
#[error("invalid type info segment")] #[error("invalid BTF type info segment")]
InvalidTypeInfo, InvalidTypeInfo,
#[error("invalid relocation info segment")] #[error("invalid BTF relocation info segment")]
InvalidRelocationInfo, InvalidRelocationInfo,
#[error("invalid BTF type kind `{kind}`")] #[error("invalid BTF type kind `{kind}`")]

@ -27,17 +27,16 @@ pub enum RelocationError {
#[error(transparent)] #[error(transparent)]
IOError(#[from] io::Error), IOError(#[from] io::Error),
#[error("section `{name}` not found")] #[error("program not found")]
SectionNotFound { name: String }, ProgramNotFound,
#[error("invalid relocation access string {access_str}")] #[error("invalid relocation access string {access_str}")]
InvalidAccessString { access_str: String }, InvalidAccessString { access_str: String },
#[error("invalid instruction index #{index} referenced by relocation #{relocation_number} in section `{section_name}`")] #[error("invalid instruction index #{index} referenced by relocation #{relocation_number}, the program contains {num_instructions} instructions")]
InvalidInstructionIndex { InvalidInstructionIndex {
index: usize, index: usize,
num_instructions: usize, num_instructions: usize,
section_name: String,
relocation_number: usize, relocation_number: usize,
}, },
@ -170,7 +169,6 @@ impl Object {
})?; })?;
let mut candidates_cache = HashMap::<u32, Vec<Candidate>>::new(); let mut candidates_cache = HashMap::<u32, Vec<Candidate>>::new();
for (sec_name_off, relos) in btf_ext.relocations() { for (sec_name_off, relos) in btf_ext.relocations() {
let section_name = local_btf.string_at(*sec_name_off)?; let section_name = local_btf.string_at(*sec_name_off)?;
@ -180,96 +178,120 @@ impl Object {
continue; continue;
} }
let section_name = parts[1]; let section_name = parts[1];
let program = self.programs.get_mut(section_name).ok_or_else(|| { let program = self
RelocationError::SectionNotFound { .programs
name: section_name.to_string(), .get_mut(section_name)
.ok_or(BpfError::RelocationError {
program_name: section_name.to_owned(),
error: Box::new(RelocationError::ProgramNotFound),
})?;
match relocate_btf_program(
program,
relos,
local_btf,
&target_btf,
&mut candidates_cache,
) {
Ok(_) => {}
Err(ErrorWrapper::BtfError(e)) => return Err(e)?,
Err(ErrorWrapper::RelocationError(error)) => {
return Err(BpfError::RelocationError {
program_name: section_name.to_owned(),
error: Box::new(error),
})
} }
})?; }
}
for rel in relos { Ok(())
let instructions = &mut program.instructions; }
let ins_index = rel.ins_offset as usize / std::mem::size_of::<bpf_insn>(); }
if ins_index >= instructions.len() {
return Err(RelocationError::InvalidInstructionIndex {
index: ins_index,
num_instructions: instructions.len(),
section_name: section_name.to_string(),
relocation_number: rel.number,
})?;
}
let local_ty = local_btf.type_by_id(rel.type_id)?; fn relocate_btf_program<'target>(
let local_name = &*local_btf.type_name(local_ty)?.unwrap(); program: &mut Program,
let access_str = &*local_btf.string_at(rel.access_str_offset)?; relos: &[Relocation],
let local_spec = AccessSpec::new(local_btf, rel.type_id, access_str, *rel)?; local_btf: &Btf,
target_btf: &'target Btf,
let mut matches = match rel.kind { candidates_cache: &mut HashMap<u32, Vec<Candidate<'target>>>,
RelocationKind::TypeIdLocal => Vec::new(), // we don't need to look at target types to relocate this value ) -> Result<(), ErrorWrapper> {
_ => { for rel in relos {
let candidates = match candidates_cache.get(&rel.type_id) { let instructions = &mut program.instructions;
Some(cands) => cands, let ins_index = rel.ins_offset as usize / std::mem::size_of::<bpf_insn>();
None => { if ins_index >= instructions.len() {
candidates_cache.insert( return Err(RelocationError::InvalidInstructionIndex {
rel.type_id, index: ins_index,
find_candidates(local_ty, local_name, &target_btf)?, num_instructions: instructions.len(),
); relocation_number: rel.number,
candidates_cache.get(&rel.type_id).unwrap() })?;
} }
};
let mut matches = Vec::new(); let local_ty = local_btf.type_by_id(rel.type_id)?;
for candidate in candidates { let local_name = &*local_btf.type_name(local_ty)?.unwrap();
if let Some(candidate_spec) = match_candidate(&local_spec, candidate)? { let access_str = &*local_btf.string_at(rel.access_str_offset)?;
let comp_rel = ComputedRelocation::new( let local_spec = AccessSpec::new(local_btf, rel.type_id, access_str, *rel)?;
rel,
&local_spec,
Some(&candidate_spec),
)?;
matches.push((candidate.name.clone(), candidate_spec, comp_rel));
}
}
matches let mut matches = match rel.kind {
RelocationKind::TypeIdLocal => Vec::new(), // we don't need to look at target types to relocate this value
_ => {
let candidates = match candidates_cache.get(&rel.type_id) {
Some(cands) => cands,
None => {
candidates_cache.insert(
rel.type_id,
find_candidates(local_ty, local_name, target_btf)?,
);
candidates_cache.get(&rel.type_id).unwrap()
} }
}; };
let comp_rel = if !matches.is_empty() { let mut matches = Vec::new();
let mut matches = matches.drain(..); for candidate in candidates {
let (_, target_spec, target_comp_rel) = matches.next().unwrap(); if let Some(candidate_spec) = match_candidate(&local_spec, candidate)? {
let comp_rel =
// if there's more than one candidate, make sure that they all resolve to the ComputedRelocation::new(rel, &local_spec, Some(&candidate_spec))?;
// same value, else the relocation is ambiguous and can't be applied matches.push((candidate.name.clone(), candidate_spec, comp_rel));
let conflicts = matches
.filter_map(|(cand_name, cand_spec, cand_comp_rel)| {
if cand_spec.bit_offset != target_spec.bit_offset
|| cand_comp_rel.target.value != target_comp_rel.target.value
{
Some(cand_name.clone())
} else {
None
}
})
.collect::<Vec<_>>();
if !conflicts.is_empty() {
return Err(RelocationError::ConflictingCandidates {
type_name: local_name.to_string(),
candidates: conflicts,
})?;
} }
target_comp_rel }
} else {
// there are no candidate matches and therefore no target_spec. This might mean
// that matching failed, or that the relocation can be applied looking at local
// types only
ComputedRelocation::new(rel, &local_spec, None)?
};
comp_rel.apply(program, rel, section_name, local_btf, &target_btf)?; matches
} }
} };
Ok(()) let comp_rel = if !matches.is_empty() {
let mut matches = matches.drain(..);
let (_, target_spec, target_comp_rel) = matches.next().unwrap();
// if there's more than one candidate, make sure that they all resolve to the
// same value, else the relocation is ambiguous and can't be applied
let conflicts = matches
.filter_map(|(cand_name, cand_spec, cand_comp_rel)| {
if cand_spec.bit_offset != target_spec.bit_offset
|| cand_comp_rel.target.value != target_comp_rel.target.value
{
Some(cand_name.clone())
} else {
None
}
})
.collect::<Vec<_>>();
if !conflicts.is_empty() {
return Err(RelocationError::ConflictingCandidates {
type_name: local_name.to_string(),
candidates: conflicts,
})?;
}
target_comp_rel
} else {
// there are no candidate matches and therefore no target_spec. This might mean
// that matching failed, or that the relocation can be applied looking at local
// types only
ComputedRelocation::new(rel, &local_spec, None)?
};
comp_rel.apply(program, rel, local_btf, &target_btf)?;
} }
Ok(())
} }
fn flavorless_name(name: &str) -> &str { fn flavorless_name(name: &str) -> &str {
@ -306,7 +328,7 @@ fn find_candidates<'target>(
fn match_candidate<'target>( fn match_candidate<'target>(
local_spec: &AccessSpec, local_spec: &AccessSpec,
candidate: &'target Candidate, candidate: &'target Candidate,
) -> Result<Option<AccessSpec<'target>>, BpfError> { ) -> Result<Option<AccessSpec<'target>>, ErrorWrapper> {
let mut target_spec = AccessSpec { let mut target_spec = AccessSpec {
btf: candidate.btf, btf: candidate.btf,
root_type_id: candidate.type_id, root_type_id: candidate.type_id,
@ -434,7 +456,7 @@ fn match_member<'local, 'target>(
target_btf: &'target Btf, target_btf: &'target Btf,
target_id: u32, target_id: u32,
target_spec: &mut AccessSpec<'target>, target_spec: &mut AccessSpec<'target>,
) -> Result<Option<u32>, BpfError> { ) -> Result<Option<u32>, ErrorWrapper> {
let local_ty = local_btf.type_by_id(local_accessor.type_id)?; let local_ty = local_btf.type_by_id(local_accessor.type_id)?;
let local_member = match local_ty { let local_member = match local_ty {
BtfType::Struct(_, members) | BtfType::Union(_, members) => { BtfType::Struct(_, members) | BtfType::Union(_, members) => {
@ -519,7 +541,7 @@ impl<'a> AccessSpec<'a> {
root_type_id: u32, root_type_id: u32,
spec: &str, spec: &str,
relocation: Relocation, relocation: Relocation,
) -> Result<AccessSpec<'a>, BpfError> { ) -> Result<AccessSpec<'a>, ErrorWrapper> {
let parts = spec let parts = spec
.split(":") .split(":")
.map(|s| s.parse::<usize>()) .map(|s| s.parse::<usize>())
@ -724,7 +746,7 @@ impl ComputedRelocation {
rel: &Relocation, rel: &Relocation,
local_spec: &AccessSpec, local_spec: &AccessSpec,
target_spec: Option<&AccessSpec>, target_spec: Option<&AccessSpec>,
) -> Result<ComputedRelocation, BpfError> { ) -> Result<ComputedRelocation, ErrorWrapper> {
use RelocationKind::*; use RelocationKind::*;
let ret = match rel.kind { let ret = match rel.kind {
FieldByteOffset | FieldByteSize | FieldExists | FieldSigned | FieldLShift64 FieldByteOffset | FieldByteSize | FieldExists | FieldSigned | FieldLShift64
@ -749,10 +771,9 @@ impl ComputedRelocation {
&self, &self,
program: &mut Program, program: &mut Program,
rel: &Relocation, rel: &Relocation,
section_name: &str,
local_btf: &Btf, local_btf: &Btf,
target_btf: &Btf, target_btf: &Btf,
) -> Result<(), BpfError> { ) -> Result<(), ErrorWrapper> {
let instructions = &mut program.instructions; let instructions = &mut program.instructions;
let num_instructions = instructions.len(); let num_instructions = instructions.len();
let ins_index = rel.ins_offset as usize / std::mem::size_of::<bpf_insn>(); let ins_index = rel.ins_offset as usize / std::mem::size_of::<bpf_insn>();
@ -762,7 +783,6 @@ impl ComputedRelocation {
.ok_or(RelocationError::InvalidInstructionIndex { .ok_or(RelocationError::InvalidInstructionIndex {
index: rel.ins_offset as usize, index: rel.ins_offset as usize,
num_instructions, num_instructions,
section_name: section_name.to_string(),
relocation_number: rel.number, relocation_number: rel.number,
})?; })?;
@ -840,7 +860,6 @@ impl ComputedRelocation {
RelocationError::InvalidInstructionIndex { RelocationError::InvalidInstructionIndex {
index: ins_index + 1, index: ins_index + 1,
num_instructions, num_instructions,
section_name: section_name.to_string(),
relocation_number: rel.number, relocation_number: rel.number,
}, },
)?; )?;
@ -862,7 +881,7 @@ impl ComputedRelocation {
fn compute_enum_relocation( fn compute_enum_relocation(
rel: &Relocation, rel: &Relocation,
spec: Option<&AccessSpec>, spec: Option<&AccessSpec>,
) -> Result<ComputedRelocationValue, BpfError> { ) -> Result<ComputedRelocationValue, ErrorWrapper> {
use RelocationKind::*; use RelocationKind::*;
let value = match rel.kind { let value = match rel.kind {
EnumVariantExists => spec.is_some() as u32, EnumVariantExists => spec.is_some() as u32,
@ -888,7 +907,7 @@ impl ComputedRelocation {
fn compute_field_relocation( fn compute_field_relocation(
rel: &Relocation, rel: &Relocation,
spec: Option<&AccessSpec>, spec: Option<&AccessSpec>,
) -> Result<ComputedRelocationValue, BpfError> { ) -> Result<ComputedRelocationValue, ErrorWrapper> {
use RelocationKind::*; use RelocationKind::*;
if let FieldExists = rel.kind { if let FieldExists = rel.kind {
@ -1013,7 +1032,7 @@ impl ComputedRelocation {
rel: &Relocation, rel: &Relocation,
local_spec: &AccessSpec, local_spec: &AccessSpec,
target_spec: Option<&AccessSpec>, target_spec: Option<&AccessSpec>,
) -> Result<ComputedRelocationValue, BpfError> { ) -> Result<ComputedRelocationValue, ErrorWrapper> {
use RelocationKind::*; use RelocationKind::*;
let value = match rel.kind { let value = match rel.kind {
TypeIdLocal => local_spec.root_type_id, TypeIdLocal => local_spec.root_type_id,
@ -1037,3 +1056,14 @@ impl ComputedRelocation {
}) })
} }
} }
// this exists only to simplify propagating errors from relocate_btf() and to associate
// RelocationError(s) with their respective program name
#[derive(Error, Debug)]
enum ErrorWrapper {
#[error(transparent)]
BtfError(#[from] BtfError),
#[error(transparent)]
RelocationError(#[from] RelocationError),
}

@ -1,14 +1,17 @@
use std::{collections::HashMap, io}; use std::collections::HashMap;
use object::{RelocationKind, RelocationTarget, SectionIndex}; use object::{RelocationKind, RelocationTarget, SectionIndex, SymbolIndex};
use thiserror::Error; use thiserror::Error;
use crate::{ use crate::{
generated::{bpf_insn, BPF_PSEUDO_MAP_FD, BPF_PSEUDO_MAP_VALUE}, generated::{bpf_insn, BPF_PSEUDO_MAP_FD, BPF_PSEUDO_MAP_VALUE},
maps::Map, maps::Map,
obj::Object, obj::Object,
BpfError,
}; };
use super::Program;
#[derive(Debug, Error)] #[derive(Debug, Error)]
pub enum RelocationError { pub enum RelocationError {
#[error("unknown symbol, index `{index}`")] #[error("unknown symbol, index `{index}`")]
@ -52,65 +55,78 @@ pub(crate) struct Symbol {
} }
impl Object { impl Object {
pub fn relocate_maps(&mut self, maps: &[Map]) -> Result<(), RelocationError> { pub fn relocate_maps(&mut self, maps: &[Map]) -> Result<(), BpfError> {
let maps_by_section = maps let maps_by_section = maps
.iter() .iter()
.map(|map| (map.obj.section_index, map)) .map(|map| (map.obj.section_index, map))
.collect::<HashMap<_, _>>(); .collect::<HashMap<_, _>>();
for program in self.programs.values_mut() { let symbol_table = &self.symbol_table;
for (program_name, program) in self.programs.iter_mut() {
if let Some(relocations) = self.relocations.get(&program.section_index) { if let Some(relocations) = self.relocations.get(&program.section_index) {
for (rel_n, rel) in relocations.iter().enumerate() { relocate_program(program, relocations, &maps_by_section, symbol_table).map_err(
match rel.target { |error| BpfError::RelocationError {
RelocationTarget::Symbol(index) => { program_name: program_name.clone(),
let sym = self error: Box::new(error),
.symbol_table },
.get(&index) )?;
.ok_or(RelocationError::UnknownSymbol { index: index.0 })?;
let section_index = sym
.section_index
.ok_or(RelocationError::UnknownSymbolSection { index: index.0 })?;
let map = maps_by_section.get(&section_index.0).ok_or(
RelocationError::SectionNotFound {
symbol_index: index.0,
symbol_name: sym.name.clone(),
section_index: section_index.0,
},
)?;
let map_fd = map.fd.ok_or_else(|| RelocationError::MapNotCreated {
name: map.obj.name.clone(),
section_index: section_index.0,
})?;
let instructions = &mut program.instructions;
let ins_index =
(rel.offset / std::mem::size_of::<bpf_insn>() as u64) as usize;
if ins_index >= instructions.len() {
return Err(RelocationError::InvalidInstructionIndex {
index: ins_index,
num_instructions: instructions.len(),
relocation_number: rel_n,
});
}
if !map.obj.data.is_empty() {
instructions[ins_index].set_src_reg(BPF_PSEUDO_MAP_VALUE as u8);
instructions[ins_index + 1].imm =
instructions[ins_index].imm + sym.address as i32;
} else {
instructions[ins_index].set_src_reg(BPF_PSEUDO_MAP_FD as u8);
}
instructions[ins_index].imm = map_fd;
}
RelocationTarget::Section(_index) => {}
RelocationTarget::Absolute => todo!(),
}
}
} }
} }
Ok(()) Ok(())
} }
} }
fn relocate_program(
program: &mut Program,
relocations: &[Relocation],
maps_by_section: &HashMap<usize, &Map>,
symbol_table: &HashMap<SymbolIndex, Symbol>,
) -> Result<(), RelocationError> {
for (rel_n, rel) in relocations.iter().enumerate() {
match rel.target {
RelocationTarget::Symbol(index) => {
let sym = symbol_table
.get(&index)
.ok_or(RelocationError::UnknownSymbol { index: index.0 })?;
let section_index = sym
.section_index
.ok_or(RelocationError::UnknownSymbolSection { index: index.0 })?;
let map = maps_by_section.get(&section_index.0).ok_or(
RelocationError::SectionNotFound {
symbol_index: index.0,
symbol_name: sym.name.clone(),
section_index: section_index.0,
},
)?;
let map_fd = map.fd.ok_or_else(|| RelocationError::MapNotCreated {
name: map.obj.name.clone(),
section_index: section_index.0,
})?;
let instructions = &mut program.instructions;
let ins_index = (rel.offset / std::mem::size_of::<bpf_insn>() as u64) as usize;
if ins_index >= instructions.len() {
return Err(RelocationError::InvalidInstructionIndex {
index: ins_index,
num_instructions: instructions.len(),
relocation_number: rel_n,
});
}
if !map.obj.data.is_empty() {
instructions[ins_index].set_src_reg(BPF_PSEUDO_MAP_VALUE as u8);
instructions[ins_index + 1].imm =
instructions[ins_index].imm + sym.address as i32;
} else {
instructions[ins_index].set_src_reg(BPF_PSEUDO_MAP_FD as u8);
}
instructions[ins_index].imm = map_fd;
}
RelocationTarget::Section(_index) => {}
RelocationTarget::Absolute => todo!(),
}
}
Ok(())
}

Loading…
Cancel
Save