use bitflags::bitflags;
use core::fmt::Write;
use fallible_iterator::FallibleIterator;
use std::cmp::Ordering;
use thiserror::Error;
macro_rules! read_binary {
($data: expr, $le: expr, $ty: ident, $offset: expr) => {{
let data_offset = $offset;
let mut data_bytes: [u8; core::mem::size_of::<$ty>()] = [0; core::mem::size_of::<$ty>()];
data_bytes.copy_from_slice(&$data[data_offset..data_offset + core::mem::size_of::<$ty>()]);
if $le {
$ty::from_le_bytes(data_bytes)
} else {
$ty::from_be_bytes(data_bytes)
}
}};
}
macro_rules! read_struct {
($struct: ident, $data: expr, $le: expr, $x: ident, $ty: ident) => {{ read_binary!($data, $le, $ty, core::mem::offset_of!($struct, $x)) }};
}
pub type SFrameResult<T> = core::result::Result<T, SFrameError>;
#[derive(Debug, Clone, Copy)]
pub enum SFrameVersion {
V1,
V2,
}
#[derive(Debug, Clone, Copy)]
pub enum SFrameABI {
AArch64BigEndian,
AArch64LittleEndian,
AMD64LittleEndian,
S390XBigEndian,
}
bitflags! {
#[derive(Debug, Clone, Copy)]
pub struct SFrameFlags: u8 {
const SFRAME_F_FDE_SORTED = 0x1;
const SFRAME_F_FRAME_POINTER = 0x2;
const SFRAME_F_FDE_FUNC_START_PCREL = 0x4;
}
}
#[derive(Debug, Clone, Copy)]
#[allow(dead_code)]
pub struct SFrameSection<'a> {
data: &'a [u8],
section_base: u64,
little_endian: bool,
version: SFrameVersion,
flags: SFrameFlags,
abi: SFrameABI,
cfa_fixed_fp_offset: i8,
cfa_fixed_ra_offset: i8,
auxhdr_len: u8,
num_fdes: u32,
num_fres: u32,
fre_len: u32,
fdeoff: u32,
freoff: u32,
}
const SFRAME_MAGIC: u16 = 0xdee2;
impl<'a> SFrameSection<'a> {
pub fn from(data: &'a [u8], section_base: u64) -> SFrameResult<SFrameSection<'a>> {
if data.len() < core::mem::size_of::<RawSFrameHeader>() {
return Err(SFrameError::UnexpectedEndOfData);
}
let magic_offset = core::mem::offset_of!(RawSFrameHeader, magic);
let mut magic_bytes: [u8; 2] = [0; 2];
magic_bytes.copy_from_slice(&data[magic_offset..magic_offset + 2]);
let magic_le = u16::from_le_bytes(magic_bytes);
let little_endian;
if magic_le == SFRAME_MAGIC {
little_endian = true;
} else {
let magic_be = u16::from_be_bytes(magic_bytes);
if magic_be == SFRAME_MAGIC {
little_endian = false;
} else {
return Err(SFrameError::InvalidMagic);
}
}
let version_offset = core::mem::offset_of!(RawSFrameHeader, version);
let version = data[version_offset];
let version = match version {
1 => SFrameVersion::V1,
2 => SFrameVersion::V2,
_ => return Err(SFrameError::UnsupportedVersion),
};
let flags_offset = core::mem::offset_of!(RawSFrameHeader, flags);
let flags = data[flags_offset];
let flags = match SFrameFlags::from_bits(flags) {
Some(flags) => flags,
None => return Err(SFrameError::UnsupportedFlags),
};
let abi_offset = core::mem::offset_of!(RawSFrameHeader, abi_arch);
let abi = data[abi_offset];
let abi = match abi {
1 => SFrameABI::AArch64BigEndian,
2 => SFrameABI::AArch64LittleEndian,
3 => SFrameABI::AMD64LittleEndian,
4 => SFrameABI::S390XBigEndian,
_ => return Err(SFrameError::UnsupportedABI),
};
let cfa_fixed_fp_offset =
data[core::mem::offset_of!(RawSFrameHeader, cfa_fixed_fp_offset)] as i8;
let cfa_fixed_ra_offset =
data[core::mem::offset_of!(RawSFrameHeader, cfa_fixed_ra_offset)] as i8;
let auxhdr_len = data[core::mem::offset_of!(RawSFrameHeader, auxhdr_len)];
Ok(SFrameSection {
data,
section_base,
little_endian,
version,
flags,
abi,
cfa_fixed_fp_offset,
cfa_fixed_ra_offset,
auxhdr_len,
num_fdes: read_struct!(RawSFrameHeader, data, little_endian, num_fdes, u32),
num_fres: read_struct!(RawSFrameHeader, data, little_endian, num_fres, u32),
fre_len: read_struct!(RawSFrameHeader, data, little_endian, fre_len, u32),
fdeoff: read_struct!(RawSFrameHeader, data, little_endian, fdeoff, u32),
freoff: read_struct!(RawSFrameHeader, data, little_endian, freoff, u32),
})
}
pub fn get_fde_count(&self) -> u32 {
self.num_fdes
}
pub fn get_fde(&self, index: u32) -> SFrameResult<Option<SFrameFDE>> {
if index >= self.num_fdes {
return Ok(None);
}
let offset = self.fdeoff as usize
+ index as usize * core::mem::size_of::<RawSFrameFDE>()
+ core::mem::size_of::<RawSFrameHeader>();
if offset + core::mem::size_of::<RawSFrameFDE>() > self.data.len() {
return Err(SFrameError::UnexpectedEndOfData);
}
Ok(Some(SFrameFDE {
offset,
func_start_address: read_struct!(
RawSFrameFDE,
&self.data[offset..],
self.little_endian,
func_start_address,
i32
),
func_size: read_struct!(
RawSFrameFDE,
&self.data[offset..],
self.little_endian,
func_size,
u32
),
func_start_fre_off: read_struct!(
RawSFrameFDE,
&self.data[offset..],
self.little_endian,
func_start_fre_off,
u32
),
func_num_fres: read_struct!(
RawSFrameFDE,
&self.data[offset..],
self.little_endian,
func_num_fres,
u32
),
func_info: SFrameFDEInfo(
self.data[offset + core::mem::offset_of!(RawSFrameFDE, func_info)],
),
func_rep_size: self.data[offset + core::mem::offset_of!(RawSFrameFDE, func_rep_size)],
}))
}
pub fn to_string(&self) -> SFrameResult<String> {
let mut s = String::new();
writeln!(&mut s, "Header :")?;
writeln!(&mut s)?;
writeln!(
&mut s,
" Version: {}",
match self.version {
SFrameVersion::V1 => "SFRAME_VERSION_1",
SFrameVersion::V2 => "SFRAME_VERSION_2",
}
)?;
writeln!(
&mut s,
" Flags: {}",
self.flags
.iter_names()
.map(|(name, _flag)| name)
.collect::<Vec<_>>()
.join(" | ")
)?;
if self.cfa_fixed_fp_offset != 0 {
writeln!(
&mut s,
" CFA fixed FP offset: {:?}",
self.cfa_fixed_fp_offset
)?;
}
if self.cfa_fixed_ra_offset != 0 {
writeln!(
&mut s,
" CFA fixed RA offset: {:?}",
self.cfa_fixed_ra_offset
)?;
}
writeln!(&mut s, " Num FDEs: {:?}", self.num_fdes)?;
writeln!(&mut s, " Num FREs: {:?}", self.num_fres)?;
writeln!(&mut s)?;
writeln!(&mut s, "Function Index :")?;
writeln!(&mut s)?;
for i in 0..self.num_fdes {
let fde = self.get_fde(i)?.unwrap();
let pc = fde.get_pc(self);
writeln!(
&mut s,
" func idx [{i}]: pc = 0x{:x}, size = {} bytes",
pc, fde.func_size,
)?;
match fde.func_info.get_fde_type()? {
SFrameFDEType::PCInc => {
writeln!(&mut s, " STARTPC CFA FP RA")?;
}
SFrameFDEType::PCMask => {
writeln!(&mut s, " STARTPC[m] CFA FP RA")?;
}
}
let mut iter = fde.iter_fre(self);
while let Some(fre) = iter.next()? {
let start_pc = match fde.func_info.get_fde_type()? {
SFrameFDEType::PCInc => pc + fre.start_address.get() as u64,
SFrameFDEType::PCMask => fre.start_address.get() as u64,
};
let rest = match self.abi {
SFrameABI::AMD64LittleEndian => {
let base_reg = if fre.info.get_cfa_base_reg_id() == 0 {
"fp"
} else {
"sp"
};
let cfa = format!("{}+{}", base_reg, fre.stack_offsets[0].get());
let fp = match fre.stack_offsets.get(1) {
Some(offset) => format!("c{:+}", offset.get()),
None => "u".to_string(), };
let ra = "f"; format!("{cfa:8} {fp:6} {ra}")
}
SFrameABI::AArch64LittleEndian => {
let base_reg = if fre.info.get_cfa_base_reg_id() == 0 {
"fp"
} else {
"sp"
};
let cfa = format!("{}+{}", base_reg, fre.stack_offsets[0].get());
let fp = match fre.stack_offsets.get(1) {
Some(offset) => format!("c{:+}", offset.get()),
None => "u".to_string(), };
let ra = match fre.stack_offsets.get(2) {
Some(offset) => format!("c{:+}", offset.get()),
None => "u".to_string(), };
format!("{cfa:8} {fp:6} {ra}")
}
_ => todo!(),
};
writeln!(&mut s, " {:016x} {}", start_pc, rest)?;
}
writeln!(&mut s,)?;
}
Ok(s)
}
pub fn iter_fde(&self) -> SFrameFDEIterator<'_> {
SFrameFDEIterator {
section: self,
index: 0,
}
}
pub fn find_fde(&self, pc: u64) -> SFrameResult<Option<SFrameFDE>> {
if self.flags.contains(SFrameFlags::SFRAME_F_FDE_SORTED) {
let mut size = self.num_fdes;
if size == 0 {
return Ok(None);
}
let mut base = 0;
while size > 1 {
let half = size / 2;
let mid = base + half;
let cmp = self.get_fde(mid)?.unwrap().get_pc(self).cmp(&pc);
if cmp != Ordering::Greater {
base = mid;
}
size -= half;
}
let base_fde = self.get_fde(base)?.unwrap();
let base_pc = base_fde.get_pc(self);
let cmp = base_pc.cmp(&pc);
match cmp {
Ordering::Equal | Ordering::Less if pc < base_pc + base_fde.func_size as u64 => {
Ok(Some(base_fde))
}
_ => Ok(None),
}
} else {
let mut iter = self.iter_fde();
while let Some(fde) = iter.next()? {
let start = fde.get_pc(self);
let end = start + fde.func_size as u64;
if start <= pc && pc < end {
return Ok(Some(fde));
}
}
Ok(None)
}
}
pub fn get_version(&self) -> SFrameVersion {
self.version
}
pub fn get_flags(&self) -> SFrameFlags {
self.flags
}
pub fn get_abi(&self) -> SFrameABI {
self.abi
}
pub fn get_cfa_fixed_fp_offset(&self) -> i8 {
self.cfa_fixed_fp_offset
}
pub fn get_cfa_fixed_ra_offset(&self) -> i8 {
self.cfa_fixed_ra_offset
}
}
#[repr(C, packed)]
struct RawSFrameHeader {
magic: u16,
version: u8,
flags: u8,
abi_arch: u8,
cfa_fixed_fp_offset: i8,
cfa_fixed_ra_offset: i8,
auxhdr_len: u8,
num_fdes: u32,
num_fres: u32,
fre_len: u32,
fdeoff: u32,
freoff: u32,
}
#[repr(C, packed)]
#[allow(dead_code)]
struct RawSFrameFDE {
func_start_address: i32,
func_size: u32,
func_start_fre_off: u32,
func_num_fres: u32,
func_info: u8,
func_rep_size: u8,
func_padding2: u16,
}
#[derive(Debug, Clone, Copy)]
#[repr(transparent)]
pub struct SFrameFDEInfo(u8);
impl SFrameFDEInfo {
pub fn get_fre_type(&self) -> SFrameResult<SFrameFREType> {
let fretype = self.0 & 0b1111;
match fretype {
0 => Ok(SFrameFREType::Addr0),
1 => Ok(SFrameFREType::Addr1),
2 => Ok(SFrameFREType::Addr2),
_ => Err(SFrameError::UnsupportedFREType),
}
}
pub fn get_fde_type(&self) -> SFrameResult<SFrameFDEType> {
let fretype = (self.0 >> 4) & 0b1;
match fretype {
0 => Ok(SFrameFDEType::PCInc),
1 => Ok(SFrameFDEType::PCMask),
_ => unreachable!(),
}
}
pub fn get_aarch64_pauth_key(&self) -> SFrameResult<SFrameAArch64PAuthKey> {
let fretype = (self.0 >> 5) & 0b1;
match fretype {
0 => Ok(SFrameAArch64PAuthKey::KeyA),
1 => Ok(SFrameAArch64PAuthKey::KeyB),
_ => unreachable!(),
}
}
}
#[derive(Debug, Clone, Copy)]
pub enum SFrameFREType {
Addr0,
Addr1,
Addr2,
}
#[derive(Debug, Clone, Copy)]
pub enum SFrameFDEType {
PCInc,
PCMask,
}
#[derive(Debug, Clone, Copy)]
pub enum SFrameAArch64PAuthKey {
KeyA,
KeyB,
}
#[derive(Debug, Clone, Copy)]
pub struct SFrameFDE {
offset: usize,
pub func_start_address: i32,
pub func_size: u32,
pub func_start_fre_off: u32,
pub func_num_fres: u32,
pub func_info: SFrameFDEInfo,
pub func_rep_size: u8,
}
impl SFrameFDE {
pub fn get_pc(&self, section: &SFrameSection<'_>) -> u64 {
if section
.flags
.contains(SFrameFlags::SFRAME_F_FDE_FUNC_START_PCREL)
{
(self.func_start_address as i64 + self.offset as i64 + section.section_base as i64)
as u64
} else {
(self.func_start_address as i64 + section.section_base as i64) as u64
}
}
pub fn iter_fre<'a>(&'a self, section: &'a SFrameSection<'a>) -> SFrameFREIterator<'a> {
let offset = section.freoff as usize
+ core::mem::size_of::<RawSFrameHeader>()
+ self.func_start_fre_off as usize;
SFrameFREIterator {
fde: self,
section,
offset,
index: 0,
}
}
pub fn find_fre(
&self,
section: &SFrameSection<'_>,
pc: u64,
) -> SFrameResult<Option<SFrameFRE>> {
let fde_pc = self.get_pc(section);
match self.func_info.get_fde_type()? {
SFrameFDEType::PCInc => {
let mut last: Option<SFrameFRE> = None;
let mut iter = self.iter_fre(§ion);
while let Some(fre) = iter.next()? {
if fre.start_address.get() as u64 + fde_pc > pc {
break;
}
last = Some(fre);
}
if let Some(fre) = last {
if fre.start_address.get() as u64 + fde_pc <= pc {
return Ok(Some(fre));
}
}
return Ok(None);
}
SFrameFDEType::PCMask => {
let mut iter = self.iter_fre(§ion);
while let Some(fre) = iter.next()? {
if pc % self.func_rep_size as u64 >= fre.start_address.get() as u64 {
return Ok(Some(fre));
}
}
return Ok(None);
}
}
}
}
#[derive(Debug, Clone, Copy)]
pub enum SFrameFREStartAddress {
U8(u8),
U16(u16),
U32(u32),
}
impl SFrameFREStartAddress {
pub fn get(&self) -> u32 {
match self {
SFrameFREStartAddress::U8(i) => *i as u32,
SFrameFREStartAddress::U16(i) => *i as u32,
SFrameFREStartAddress::U32(i) => *i,
}
}
}
#[derive(Debug, Clone, Copy)]
pub enum SFrameFREStackOffset {
I8(i8),
I16(i16),
I32(i32),
}
impl SFrameFREStackOffset {
pub fn get(&self) -> i32 {
match self {
SFrameFREStackOffset::I8(i) => *i as i32,
SFrameFREStackOffset::I16(i) => *i as i32,
SFrameFREStackOffset::I32(i) => *i,
}
}
}
#[derive(Debug, Clone)]
pub struct SFrameFRE {
pub start_address: SFrameFREStartAddress,
pub info: SFrameFREInfo,
pub stack_offsets: Vec<SFrameFREStackOffset>,
}
impl SFrameFRE {
pub fn get_cfa_offset(&self) -> Option<i32> {
self.stack_offsets.get(0).map(|offset| offset.get())
}
pub fn get_ra_offset(&self, section: &SFrameSection<'_>) -> Option<i32> {
match section.abi {
SFrameABI::AArch64BigEndian | SFrameABI::AArch64LittleEndian => {
self.stack_offsets.get(1).map(|offset| offset.get())
}
SFrameABI::AMD64LittleEndian => Some(section.cfa_fixed_ra_offset as i32),
SFrameABI::S390XBigEndian => todo!(),
}
}
pub fn get_fp_offset(&self, section: &SFrameSection<'_>) -> Option<i32> {
match section.abi {
SFrameABI::AArch64BigEndian | SFrameABI::AArch64LittleEndian => {
self.stack_offsets.get(2).map(|offset| offset.get())
}
SFrameABI::AMD64LittleEndian => self.stack_offsets.get(1).map(|offset| offset.get()),
SFrameABI::S390XBigEndian => todo!(),
}
}
}
#[derive(Debug, Clone, Copy)]
#[repr(transparent)]
pub struct SFrameFREInfo(u8);
impl SFrameFREInfo {
pub fn get_mangled_ra_p(&self) -> bool {
(self.0 >> 7) & 0b1 == 1
}
pub fn get_offset_size(&self) -> SFrameResult<usize> {
match (self.0 >> 5) & 0b11 {
0x0 => Ok(1),
0x1 => Ok(2),
0x2 => Ok(4),
_ => Err(SFrameError::UnsupportedFREStackOffsetSize),
}
}
pub fn get_offset_count(&self) -> u8 {
(self.0 >> 1) & 0b111
}
pub fn get_cfa_base_reg_id(&self) -> u8 {
self.0 & 0b1
}
}
pub struct SFrameFREIterator<'a> {
fde: &'a SFrameFDE,
section: &'a SFrameSection<'a>,
index: u32,
offset: usize,
}
impl<'a> FallibleIterator for SFrameFREIterator<'a> {
type Item = SFrameFRE;
type Error = SFrameError;
fn next(&mut self) -> SFrameResult<Option<SFrameFRE>> {
if self.index >= self.fde.func_num_fres {
return Ok(None);
}
let fre_type = self.fde.func_info.get_fre_type()?;
let entry_size = match fre_type {
SFrameFREType::Addr0 => 1 + 1,
SFrameFREType::Addr1 => 2 + 1,
SFrameFREType::Addr2 => 4 + 1,
} as usize;
let offset = self.offset;
if offset + entry_size > self.section.data.len() {
return Err(SFrameError::UnexpectedEndOfData);
}
let (start_address, info) = match self.fde.func_info.get_fre_type()? {
SFrameFREType::Addr0 => (
SFrameFREStartAddress::U8(self.section.data[offset]),
SFrameFREInfo(self.section.data[offset + 1]),
),
SFrameFREType::Addr1 => (
SFrameFREStartAddress::U16(read_binary!(
self.section.data,
self.section.little_endian,
u16,
offset
)),
SFrameFREInfo(self.section.data[offset + 2]),
),
SFrameFREType::Addr2 => (
SFrameFREStartAddress::U32(read_binary!(
self.section.data,
self.section.little_endian,
u32,
offset
)),
SFrameFREInfo(self.section.data[offset + 4]),
),
};
let offset_size = info.get_offset_size()?;
let offset_count = info.get_offset_count() as usize;
let offset_total_size = offset_size * offset_count;
if offset + entry_size + offset_total_size > self.section.data.len() {
return Err(SFrameError::UnexpectedEndOfData);
}
let mut stack_offsets = vec![];
for i in 0..offset_count {
match offset_size {
1 => stack_offsets.push(SFrameFREStackOffset::I8(
self.section.data[offset + entry_size + i * offset_size] as i8,
)),
2 => stack_offsets.push(SFrameFREStackOffset::I16(read_binary!(
self.section.data,
self.section.little_endian,
i16,
offset + entry_size + i * offset_size
))),
4 => stack_offsets.push(SFrameFREStackOffset::I32(read_binary!(
self.section.data,
self.section.little_endian,
i32,
offset + entry_size + i * offset_size
))),
_ => unreachable!(),
}
}
self.offset += entry_size + offset_total_size;
self.index += 1;
Ok(Some(SFrameFRE {
start_address,
info,
stack_offsets,
}))
}
}
pub struct SFrameFDEIterator<'a> {
section: &'a SFrameSection<'a>,
index: u32,
}
impl<'a> FallibleIterator for SFrameFDEIterator<'a> {
type Item = SFrameFDE;
type Error = SFrameError;
fn next(&mut self) -> Result<Option<Self::Item>, Self::Error> {
let res = self.section.get_fde(self.index);
if let Ok(Some(_)) = res {
self.index += 1;
}
res
}
}
#[derive(Error, Debug)]
pub enum SFrameError {
#[error("format error")]
Fmt(#[from] core::fmt::Error),
#[error("unexpected end of data")]
UnexpectedEndOfData,
#[error("invalid magic number")]
InvalidMagic,
#[error("unsupported version")]
UnsupportedVersion,
#[error("unsupported flags")]
UnsupportedFlags,
#[error("unsupported abi")]
UnsupportedABI,
#[error("unsupported fre type")]
UnsupportedFREType,
#[error("unsupported fre stack offset size")]
UnsupportedFREStackOffsetSize,
}
#[cfg(test)]
mod tests {
use std::iter::zip;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize)]
struct Testcase {
section_base: u64,
content: Vec<u8>,
groundtruth: String,
}
#[test]
fn test() {
for entry in std::fs::read_dir("testcases").unwrap() {
let entry = entry.unwrap();
let testcase: Testcase =
serde_json::from_reader(std::fs::File::open(entry.path()).unwrap()).unwrap();
let section =
crate::SFrameSection::from(&testcase.content, testcase.section_base).unwrap();
let s = section.to_string().unwrap();
let mut lines_expected: Vec<&str> = testcase.groundtruth.trim().split("\n").collect();
while let Some(line) = lines_expected.first() {
if line.contains("Header :") {
break;
}
lines_expected.remove(0);
}
let lines_actual: Vec<&str> = s.trim().split("\n").collect();
assert_eq!(lines_expected.len(), lines_actual.len());
for (expected, actual) in zip(lines_expected, lines_actual) {
let parts_expected: Vec<&str> =
expected.trim().split(" ").filter(|s| s.len() > 0).collect();
let parts_actual: Vec<&str> =
actual.trim().split(" ").filter(|s| s.len() > 0).collect();
assert_eq!(
parts_expected, parts_actual,
"\"{}\"({:?}) != \"{}\"({:?})",
expected, parts_expected, actual, parts_actual,
);
}
}
}
}