use std::collections::HashMap;
use gimli::{BaseAddresses, CieOrFde, EhFrame, EndianSlice, LittleEndian, UnwindSection};
#[derive(Debug, Clone)]
pub struct TryRegion {
pub start: u64,
pub end: u64,
pub landing_pad: u64,
}
pub fn parse_eh_frame(binary: &[u8]) -> HashMap<u64, Vec<TryRegion>> {
let mut out: HashMap<u64, Vec<TryRegion>> = HashMap::new();
let Ok(obj) = goblin::Object::parse(binary) else {
return out;
};
let elf = match obj {
goblin::Object::Elf(e) => e,
_ => return out,
};
let mut eh_frame_data: Option<&[u8]> = None;
let mut eh_frame_addr: u64 = 0;
let mut text_addr: u64 = 0;
for sh in &elf.section_headers {
let name = elf.shdr_strtab.get_at(sh.sh_name).unwrap_or("");
match name {
".eh_frame" => {
let off = sh.sh_offset as usize;
let sz = sh.sh_size as usize;
if off + sz <= binary.len() {
eh_frame_data = Some(&binary[off..off + sz]);
eh_frame_addr = sh.sh_addr;
}
}
".text" => {
text_addr = sh.sh_addr;
}
_ => {}
}
}
let Some(data) = eh_frame_data else {
return out;
};
let bases = BaseAddresses::default()
.set_eh_frame(eh_frame_addr)
.set_text(text_addr);
let eh_frame = EhFrame::new(data, LittleEndian);
let mut entries = eh_frame.entries(&bases);
while let Ok(Some(entry)) = entries.next() {
let fde = match entry {
CieOrFde::Cie(_) => continue,
CieOrFde::Fde(partial) => match partial.parse(EhFrame::cie_from_offset) {
Ok(fde) => fde,
Err(_) => continue,
},
};
let func_start = fde.initial_address();
let Some(lsda_ptr) = fde.lsda() else {
continue;
};
let lsda_addr = match lsda_ptr {
gimli::Pointer::Direct(a) => a,
gimli::Pointer::Indirect(a) => a,
};
let Some(lsda_off) = va_to_file_offset(&elf, lsda_addr) else {
continue;
};
if lsda_off >= binary.len() {
continue;
}
let regions = parse_lsda(&binary[lsda_off..], func_start);
if !regions.is_empty() {
out.insert(func_start, regions);
}
}
out
}
fn va_to_file_offset(elf: &goblin::elf::Elf, va: u64) -> Option<usize> {
for sh in &elf.section_headers {
if va >= sh.sh_addr && va < sh.sh_addr + sh.sh_size {
return Some((sh.sh_offset + (va - sh.sh_addr)) as usize);
}
}
None
}
fn parse_lsda(data: &[u8], func_start: u64) -> Vec<TryRegion> {
let mut out = Vec::new();
let mut p = 0usize;
if p >= data.len() {
return out;
}
let lp_enc = data[p];
p += 1;
let lp_start = if lp_enc == DW_EH_PE_OMIT {
func_start
} else {
match read_encoded(data, &mut p, lp_enc, func_start) {
Some(v) => v,
None => return out,
}
};
if p >= data.len() {
return out;
}
let ttype_enc = data[p];
p += 1;
if ttype_enc != DW_EH_PE_OMIT {
let _ttype_off = match read_uleb(data, &mut p) {
Some(v) => v,
None => return out,
};
}
if p >= data.len() {
return out;
}
let cs_enc = data[p];
p += 1;
let cs_len = match read_uleb(data, &mut p) {
Some(v) => v,
None => return out,
};
let cs_end = p + cs_len as usize;
if cs_end > data.len() {
return out;
}
while p < cs_end {
let start_off = match read_encoded(data, &mut p, cs_enc, 0) {
Some(v) => v,
None => break,
};
let length = match read_encoded(data, &mut p, cs_enc, 0) {
Some(v) => v,
None => break,
};
let lp_off = match read_encoded(data, &mut p, cs_enc, 0) {
Some(v) => v,
None => break,
};
let _action = match read_uleb(data, &mut p) {
Some(v) => v,
None => break,
};
if length == 0 {
continue;
}
if lp_off == 0 {
continue;
}
out.push(TryRegion {
start: func_start.wrapping_add(start_off),
end: func_start.wrapping_add(start_off).wrapping_add(length),
landing_pad: lp_start.wrapping_add(lp_off),
});
}
out
}
const DW_EH_PE_OMIT: u8 = 0xFF;
const DW_EH_PE_ABSPTR: u8 = 0x00;
const DW_EH_PE_ULEB128: u8 = 0x01;
const DW_EH_PE_UDATA2: u8 = 0x02;
const DW_EH_PE_UDATA4: u8 = 0x03;
const DW_EH_PE_UDATA8: u8 = 0x04;
const DW_EH_PE_SLEB128: u8 = 0x09;
const DW_EH_PE_SDATA2: u8 = 0x0A;
const DW_EH_PE_SDATA4: u8 = 0x0B;
const DW_EH_PE_SDATA8: u8 = 0x0C;
fn read_encoded(data: &[u8], p: &mut usize, enc: u8, pc_rel_base: u64) -> Option<u64> {
let format = enc & 0x0F;
let application = enc & 0x70;
let v: u64 = match format {
DW_EH_PE_ABSPTR => {
if *p + 8 > data.len() {
return None;
}
let v = u64::from_le_bytes(data[*p..*p + 8].try_into().ok()?);
*p += 8;
v
}
DW_EH_PE_ULEB128 => read_uleb(data, p)?,
DW_EH_PE_UDATA2 => {
if *p + 2 > data.len() {
return None;
}
let v = u16::from_le_bytes(data[*p..*p + 2].try_into().ok()?) as u64;
*p += 2;
v
}
DW_EH_PE_UDATA4 => {
if *p + 4 > data.len() {
return None;
}
let v = u32::from_le_bytes(data[*p..*p + 4].try_into().ok()?) as u64;
*p += 4;
v
}
DW_EH_PE_UDATA8 => {
if *p + 8 > data.len() {
return None;
}
let v = u64::from_le_bytes(data[*p..*p + 8].try_into().ok()?);
*p += 8;
v
}
DW_EH_PE_SLEB128 => {
let sv = read_sleb(data, p)?;
sv as u64
}
DW_EH_PE_SDATA2 => {
if *p + 2 > data.len() {
return None;
}
let v = i16::from_le_bytes(data[*p..*p + 2].try_into().ok()?) as i64 as u64;
*p += 2;
v
}
DW_EH_PE_SDATA4 => {
if *p + 4 > data.len() {
return None;
}
let v = i32::from_le_bytes(data[*p..*p + 4].try_into().ok()?) as i64 as u64;
*p += 4;
v
}
DW_EH_PE_SDATA8 => {
if *p + 8 > data.len() {
return None;
}
let v = i64::from_le_bytes(data[*p..*p + 8].try_into().ok()?) as u64;
*p += 8;
v
}
_ => return None,
};
const DW_EH_PE_PCREL: u8 = 0x10;
if application == DW_EH_PE_PCREL {
Some(v.wrapping_add(pc_rel_base))
} else {
Some(v)
}
}
fn read_uleb(data: &[u8], p: &mut usize) -> Option<u64> {
let mut result: u64 = 0;
let mut shift = 0;
loop {
if *p >= data.len() {
return None;
}
let b = data[*p];
*p += 1;
result |= ((b & 0x7F) as u64) << shift;
if b & 0x80 == 0 {
return Some(result);
}
shift += 7;
if shift >= 64 {
return None;
}
}
}
fn read_sleb(data: &[u8], p: &mut usize) -> Option<i64> {
let mut result: i64 = 0;
let mut shift = 0;
let mut byte = 0;
loop {
if *p >= data.len() {
return None;
}
byte = data[*p];
*p += 1;
result |= ((byte & 0x7F) as i64) << shift;
shift += 7;
if byte & 0x80 == 0 {
break;
}
if shift >= 64 {
return None;
}
}
if shift < 64 && (byte & 0x40) != 0 {
result |= !0i64 << shift;
}
Some(result)
}