1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303
//! Routines for parsing the gcc-style LSDA (Language-Specific Data Area) in an ELF object file,
//! which corresponds to an area within a `.gcc_except_table` section.
#![allow(nonstandard_style)]
use core::ops::Range;
use gimli::{Reader, DwEhPe, Endianity, EndianSlice, constants::*, };
use FallibleIterator;
/// `GccExceptTableArea` contains the contents of the Language-Specific Data Area (LSDA)
/// that is used to locate cleanup (run destructors for) a given function during stack unwinding.
///
/// Though an object file typically only includes a single `.gcc_except_table` section, it may include multiple.
/// This struct represents only *one area* of that section, the area that is for cleaning up a single function.
/// There may exist multiple instances of this struct created as overlays
/// for different, non-overlapping areas of that one `.gcc_except_table` section.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct GccExceptTableArea<R: Reader> {
reader: R,
function_start_address: u64,
}
impl<'input, Endian: Endianity> GccExceptTableArea<EndianSlice<'input, Endian>> {
/// Construct a new `GccExceptTableArea` instance from the given input data,
/// which is a slice that typically begins at an LSDA pointer that was parsed
/// from a `FrameDescriptionEntry` in the `EhFrame` section.
///
/// The starting address of the function that it corresponds to must aso be provided,
/// because this is often used as the default base address for the landing pad
/// from which all offsets are calculated.
pub fn new(data: &'input [u8], endian: Endian, function_start_address: u64) -> Self {
GccExceptTableArea {
reader: EndianSlice::new(data, endian),
function_start_address,
}
}
}
// impl<R: Reader> Section<R> for GccExceptTableArea<R> {
// // TODO FIXME: this is wrong (temporarily),
// // once we incoporate it into Gimli itself, we can add a GccExceptFrame sectionid.
// fn id() -> SectionId { SectionId::EhFrameHdr }
// fn reader(&self) -> &R { &self.reader }
// fn section_name() -> &'static str {
// ".gcc_except_table"
// }
// }
impl<R: Reader> GccExceptTableArea<R> {
/// Parses the .gcc_except_table entries from the very top of the LSDA area.
/// This only parses the two headers that are guaranteed to exist,
/// the other dynamically-sized entries should be parsed elsewhere using the result of this function.
///
/// The flow of this code was partially inspired by rust's stdlib `libpanic_unwind/dwarf/eh.rs` file.
/// <https://github.com/rust-lang/rust/blob/master/src/libpanic_unwind/dwarf/eh.rs>
fn parse_from_beginning(&self) -> gimli::Result<(LsdaHeader, CallSiteTableHeader, R)> {
// Clone the internal `Reader` to avoid modifying the offset position of the original provided reader.
let mut reader = self.reader.clone();
// First, parse the top-level header, which comes at the very beginning
let lsda_header = LsdaHeader::parse(&mut reader)?;
// debug!("{:#X?}", lsda_header);
// Second, parse the call site table header, which comes right after the top-level LSDA header
let call_site_table_header = CallSiteTableHeader::parse(&mut reader)?;
// debug!("{:#X?}", call_site_table_header);
Ok((lsda_header, call_site_table_header, reader))
}
/// Returns an iterator over all of the call site entries
/// found in this area of this .gcc_except_table section.
///
/// Can be used with the `FallibleIterator` trait.
pub fn call_site_table_entries(&self) -> gimli::Result<CallSiteTableIterator<R>> {
let (lsda_header, call_site_table_header, reader) = self.parse_from_beginning()?;
// set up the call site table iterator so it knows when to stop parsing entries.
let end_of_call_site_table = reader.offset_id().0 + call_site_table_header.length;
Ok(CallSiteTableIterator {
call_site_table_encoding: call_site_table_header.encoding,
end_of_call_site_table,
landing_pad_base: lsda_header.landing_pad_base.unwrap_or(self.function_start_address),
reader,
})
}
/// Iterates over the call site table and finds the entry that matches the given instruction pointer (IP),
/// i.e., the entry that covers the range of addresses that the `ip` falls within.
pub fn call_site_table_entry_for_address(&self, address: u64) -> gimli::Result<CallSiteTableEntry> {
let mut iter = self.call_site_table_entries()?;
while let Some(entry) = iter.next()? {
if entry.range_of_covered_addresses().contains(&address) {
return Ok(entry);
}
}
Err(gimli::Error::NoUnwindInfoForAddress)
}
}
/// The header of an LSDA section, which is at the very beginning of the area
/// in the .gcc_except_table section that was pointed to by a stack frame's LSDA pointer.
#[derive(Debug)]
#[allow(dead_code)]
struct LsdaHeader {
/// The encoding used to read the next value `landing_pad_base`.
landing_pad_base_encoding: DwEhPe,
/// If the above `landing_pad_base_encoding` is not `DW_EH_PE_omit`,
/// then this is the value that should be used as the base address of the landing pad,
/// which is used by all the offsets specified in the LSDA call site tables and action tables.
/// It is decoded using the above `landing_pad_base_encoding`,
/// which is typically the uleb128 encoding, but not always guaranteed to be.
/// Otherwise, if `DW_EH_PE_omit`, the default value is the starting function address
/// specified in the FDE (FrameDescriptionEntry) corresponding to this LSDA.
///
/// Typically, this will be the virtual address of the function that this cleanup routine is for;
/// such cleanup routines are usually at the end of the function's text section.
landing_pad_base: Option<u64>,
/// The encoding used to read pointer values in the type table.
type_table_encoding: DwEhPe,
/// If the above `type_table_encoding` is not `DW_EH_PE_omit`,
/// this is the offset to the type table, starting from the end of this header.
/// This is always encoded as a uleb128 value.
/// If it was `DW_EH_PE_omit` above, then there is no type table,
/// which is quite common in Rust-compiled object files.
type_table_offset: Option<u64>,
}
impl LsdaHeader {
fn parse<R: gimli::Reader>(reader: &mut R) -> gimli::Result<LsdaHeader> {
let lp_encoding = DwEhPe(reader.read_u8()?);
let lp = if lp_encoding == DW_EH_PE_omit {
None
} else {
Some(read_encoded_pointer(reader, lp_encoding)?)
};
let tt_encoding = DwEhPe(reader.read_u8()?);
let tt_offset = if tt_encoding == DW_EH_PE_omit {
None
} else {
Some(read_encoded_pointer(reader, DW_EH_PE_uleb128)?)
};
Ok(LsdaHeader{
landing_pad_base_encoding: lp_encoding,
landing_pad_base: lp,
type_table_encoding: tt_encoding,
type_table_offset: tt_offset,
})
}
}
/// The header of the call site table, which defines the length of the table
/// and the encoding format used to parse address values in the table.
/// The call site table comes immediately after the `LsdaHeader`.
#[derive(Debug)]
struct CallSiteTableHeader {
/// The encoding of items in the call site table.
encoding: DwEhPe,
/// The total length of the entire call site table, in bytes.
/// This is always encoded in uleb128.
length: u64,
}
impl CallSiteTableHeader {
fn parse<R: gimli::Reader>(reader: &mut R) -> gimli::Result<CallSiteTableHeader> {
let encoding = DwEhPe(reader.read_u8()?);
let length = read_encoded_pointer(reader, DW_EH_PE_uleb128)?;
Ok(CallSiteTableHeader {
encoding,
length
})
}
}
/// An entry in the call site table, which defines landing pad functions and additional actions
/// that should be executed when unwinding a given a stack frame.
/// The relevant entry for a particular stack frame can be found based on the range of addresses it covers.
#[derive(Debug)]
pub struct CallSiteTableEntry {
/// An offset from the landing pad base address (top of function section)
/// that specifies the first (starting) address that is covered by this entry.
starting_offset: u64,
/// The length (from the above `starting_offset`) that specifies
/// the range of addresses covered by this entry.
length: u64,
/// The offset from the `landing_pad_base` at which the landing pad entry function exists.
/// If `0`, then there is no landing pad function that should be run.
landing_pad_offset: u64,
/// The offset into the action table that specifies what additional action to undertake.
/// If `0`, there is no action;
/// otherwise, this value plus 1 (`action_offset + 1`) should be used to locate the action table entry.
action_offset: u64,
/// The starting address of the function that this GccExceptTableArea pertains to.
/// This is not actually part of the table entry as defined in the gcc LSDA spec,
/// it comes from the top-level LSDA header and is replicated here for convenience.
landing_pad_base: u64
}
impl CallSiteTableEntry {
fn parse<R: gimli::Reader>(
reader: &mut R,
call_site_encoding: DwEhPe,
landing_pad_base: u64,
) -> gimli::Result<CallSiteTableEntry> {
let cs_start = read_encoded_pointer(reader, call_site_encoding)?;
let cs_length = read_encoded_pointer(reader, call_site_encoding)?;
let cs_lp = read_encoded_pointer(reader, call_site_encoding)?;
let cs_action = read_encoded_pointer(reader, DW_EH_PE_uleb128)?;
Ok(CallSiteTableEntry {
starting_offset: cs_start,
length: cs_length,
landing_pad_offset: cs_lp,
action_offset: cs_action,
landing_pad_base,
})
}
/// The range of addresses (instruction pointers) that are covered by this entry.
pub fn range_of_covered_addresses(&self) -> Range<u64> {
(self.landing_pad_base + self.starting_offset) .. (self.landing_pad_base + self.starting_offset + self.length)
}
/// The address of the actual landing pad, i.e., the cleanup routine that should run, if one exists.
pub fn landing_pad_address(&self) -> Option<u64> {
if self.landing_pad_offset == 0 {
None
} else {
Some(self.landing_pad_base + self.landing_pad_offset)
}
}
/// The offset into the action table that specifies which additional action should be undertaken
/// when invoking this landing pad cleanup routine, if one exists.
pub fn action_offset(&self) -> Option<u64> {
if self.action_offset == 0 {
// no action to perform
None
} else {
// the gcc_except_table docs specify that 1 must be added to the action offset if it is not zero.
Some(self.action_offset + 1)
}
}
}
/// An iterator over all of the entries in a GccExceptTableArea's call site table.
///
/// Can be used with the `FallibleIterator` trait.
pub struct CallSiteTableIterator<R: Reader> {
/// The encoding of pointers in the call site table.
call_site_table_encoding: DwEhPe,
/// This is the ending bound for the following `reader` to parse,
/// i.e., the reader offset right after the final call site table entry.
end_of_call_site_table: u64,
/// The starting address of the function that this GccExceptTableArea pertains to.
landing_pad_base: u64,
/// This reader must be queued up to the beginning of the first call site table entry,
/// i.e., right after the end of the call site table header.
reader: R,
}
impl<R: Reader> FallibleIterator for CallSiteTableIterator<R> {
type Item = CallSiteTableEntry;
type Error = gimli::Error;
fn next(&mut self) -> Result<Option<Self::Item>, Self::Error> {
if self.reader.offset_id().0 < self.end_of_call_site_table {
let entry = CallSiteTableEntry::parse(&mut self.reader, self.call_site_table_encoding, self.landing_pad_base)?;
if let Some(action_offset) = entry.action_offset() {
warn!("unsupported/unhandled call site action, offset (with 1 added): {:#X}", action_offset);
}
Ok(Some(entry))
} else {
Ok(None)
}
}
}
/// Decodes the next pointer from the given `reader` (a stream of bytes) using the given `encoding` format.
fn read_encoded_pointer<R: gimli::Reader>(reader: &mut R, encoding: DwEhPe) -> gimli::Result<u64> {
match encoding {
DW_EH_PE_omit => Err(gimli::Error::CannotParseOmitPointerEncoding),
DW_EH_PE_absptr => reader.read_u64(),
DW_EH_PE_uleb128 => reader.read_uleb128(),
DW_EH_PE_udata2 => reader.read_u16().map(|v| v as u64),
DW_EH_PE_udata4 => reader.read_u32().map(|v| v as u64),
DW_EH_PE_udata8 => reader.read_u64(),
DW_EH_PE_sleb128 => reader.read_sleb128().map(|v| v as u64),
DW_EH_PE_sdata2 => reader.read_i16().map(|v| v as u64),
DW_EH_PE_sdata4 => reader.read_i32().map(|v| v as u64),
DW_EH_PE_sdata8 => reader.read_i64().map(|v| v as u64),
_ => {
error!("read_encoded_pointer(): unsupported pointer encoding: {:#X}: {:?}",
encoding.0,
encoding.static_string()
);
Err(gimli::Error::UnknownPointerEncoding)
}
}
}