#![allow(clippy::cast_possible_truncation)]
mod aarch64;
mod args;
mod bpf;
mod bpf_args_ssa;
mod bpf_ssa;
mod build_function;
mod build_module;
mod data_lookup;
mod expr;
mod idioms;
pub mod macho;
pub mod ne;
mod patterns;
pub mod pe;
pub mod raw6502;
mod ssa;
mod stack_slots;
pub mod wasm;
mod wasm_disasm;
pub use data_lookup::DataLookup;
pub use macho::{decompile_macho, decompile_macho_to_text};
pub use ne::{decompile_ne, decompile_ne_to_text};
pub use pe::{decompile_pe, decompile_pe_to_text};
pub use raw6502::{decompile_raw_6502, decompile_raw_6502_to_text};
pub use wasm::{decompile_wasm, decompile_wasm_to_text};
use std::collections::HashMap;
use crate::analysis::{FunctionMap, discover_functions};
use crate::arch::x86::{Bitness, decode, lift_function};
use crate::ast::{Item, UdFile};
use crate::debug::DebugFunction;
use crate::format::elf::{
EM_386, EM_AARCH64, EM_BPF, EM_SBF, EM_X86_64, Elf64File, ElfClass, SHF_EXECINSTR, Shdr64,
};
#[derive(Debug, Clone, Copy)]
enum Arch {
X86 {
bitness: Bitness,
},
Aarch64,
Bpf {
variant: crate::arch::bpf::BpfVariant,
},
}
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("only x86 ELF inputs are supported; got e_machine = {0}")]
UnsupportedMachine(u16),
#[error(transparent)]
Analysis(#[from] crate::analysis::Error),
#[error(transparent)]
Decode(#[from] crate::arch::x86::Error),
#[error(transparent)]
Lift(#[from] crate::arch::x86::LiftError),
#[error(transparent)]
Debug(#[from] crate::debug::DebugError),
#[error(transparent)]
Aarch64Decode(crate::arch::aarch64::Error),
#[error(transparent)]
BpfDecode(crate::arch::bpf::Error),
#[error(transparent)]
BpfReloc(crate::analysis::bpf_relocs::BpfRelocError),
}
pub type Result<T, E = Error> = std::result::Result<T, E>;
pub fn decompile(elf: &Elf64File) -> Result<UdFile> {
let arch = match (elf.class, elf.ehdr.e_machine) {
(ElfClass::Elf64, EM_X86_64) => Arch::X86 {
bitness: Bitness::Bits64,
},
(ElfClass::Elf32, EM_386) => Arch::X86 {
bitness: Bitness::Bits32,
},
(ElfClass::Elf64, EM_AARCH64) => Arch::Aarch64,
(ElfClass::Elf64, EM_BPF) => Arch::Bpf {
variant: crate::arch::bpf::BpfVariant::Linux,
},
(ElfClass::Elf64, EM_SBF) => Arch::Bpf {
variant: crate::arch::bpf::BpfVariant::Sbfv1,
},
_ => return Err(Error::UnsupportedMachine(elf.ehdr.e_machine)),
};
let module = build_module::build_module(elf);
let map = discover_functions(elf)?;
let debug_by_addr: HashMap<u64, DebugFunction> = crate::debug::read_debug_info(elf)?;
let name_at: HashMap<u64, String> = map.iter().map(|f| (f.addr.0, f.name.clone())).collect();
let call_site_names: HashMap<u64, String> = match arch {
Arch::Bpf { .. } => {
crate::analysis::bpf_relocs::build_call_site_names(elf).map_err(Error::BpfReloc)?
}
_ => HashMap::new(),
};
let mut items = Vec::new();
for f in map.iter() {
if f.size == 0 {
items.push(Item::Comment(format!(
"note: `{}` at 0x{:x} has no known size; not bodied",
f.name, f.addr.0
)));
} else if !function_lives_in_a_section(elf, f.addr.0, f.size) {
items.push(Item::Comment(format!(
"note: `{}` at 0x{:x} not in any on-disk section; not bodied",
f.name, f.addr.0
)));
}
}
for (idx, sh, data) in elf.sections() {
if !section_is_emittable(sh) {
continue;
}
let name = elf
.section_name(idx)
.map_or_else(|| format!("section{idx}"), str::to_string);
let mut section_items = build_section_items(
elf,
sh,
data,
&map,
&debug_by_addr,
&name_at,
&call_site_names,
arch,
)?;
drop_redundant_function_addrs(sh.sh_addr, &mut section_items);
items.push(Item::Section {
name,
addr: sh.sh_addr,
items: section_items,
});
}
let tables = collect_switch_tables(&items);
replace_raw_with_jump_tables(&mut items, &tables);
let bitness = if let Arch::X86 { bitness } = arch {
Some(bitness)
} else {
None
};
if let Some(bitness) = bitness {
drop_regenerable_asm_bytes(&mut items, bitness);
}
match arch {
Arch::Bpf { variant } => {
let codec = crate::arch::bpf::BpfCodec(variant);
drop_regenerable_bytes(&mut items, &codec);
}
Arch::X86 { bitness } => {
let codec = crate::arch::x86::X86Codec(bitness);
drop_regenerable_bytes(&mut items, &codec);
}
Arch::Aarch64 => {}
}
Ok(UdFile { module, items })
}
#[allow(clippy::too_many_lines, clippy::items_after_statements)]
fn drop_regenerable_bytes(items: &mut [Item], arch: &dyn crate::arch::codec::ArchCodec) {
let slot_size = arch.encoded_jump_size(0, 0, crate::arch::codec::EncodeHints::default()) as u64;
fn visit_stmts(
stmts: &mut [crate::ast::Stmt],
ip: &mut u64,
arch: &dyn crate::arch::codec::ArchCodec,
slot_size: u64,
) {
for stmt in stmts.iter_mut() {
match stmt {
crate::ast::Stmt::Asm { text, bytes } => {
let here = *ip;
if bytes.is_empty() {
*ip = ip.saturating_add(slot_size);
continue;
}
let mut dropped = false;
if let Ok(encoded) = arch.assemble_one(text, here)
&& encoded == *bytes
{
bytes.clear();
dropped = true;
}
if !dropped {
let desym = arch.desymbolize(text, here);
if desym != *text
&& let Ok(encoded) = arch.assemble_one(&desym, here)
&& encoded == *bytes
{
bytes.clear();
}
}
*ip = ip.saturating_add(slot_size);
}
crate::ast::Stmt::Move { dst, src, bytes } => {
if !bytes.is_empty()
&& let Ok(encoded) = arch.encode_move(dst, src)
&& encoded == *bytes
{
bytes.clear();
}
let slots = if bytes.is_empty() {
1
} else {
(bytes.len() as u64).max(slot_size) / slot_size
};
*ip = ip.saturating_add(slots * slot_size);
}
crate::ast::Stmt::RegArith {
dst,
op,
src,
bytes,
} => {
if !bytes.is_empty()
&& let Ok(encoded) = arch.encode_arith(dst, op, src)
&& encoded == *bytes
{
bytes.clear();
}
*ip = ip.saturating_add(slot_size);
}
crate::ast::Stmt::Return { value, bytes } => {
if !bytes.is_empty()
&& let Ok(encoded) = arch.encode_return(Some(*value))
&& encoded == *bytes
{
bytes.clear();
}
*ip = ip.saturating_add(slot_size);
}
crate::ast::Stmt::Call {
bytes,
direct_target,
..
} => {
if !bytes.is_empty()
&& arch.direct_call_bytes_contain_call()
&& let Some(target) = *direct_target
{
if bytes.first() == Some(&0x85)
&& let Ok(encoded) = arch.encode_call(
*ip,
target,
crate::arch::codec::EncodeHints::default(),
)
&& encoded == *bytes
{
bytes.clear();
}
}
*ip = ip.saturating_add(slot_size);
}
crate::ast::Stmt::IfBlock {
cond_text,
cond_bytes,
then_body,
then_tail_jmp,
else_body,
} => {
if !cond_bytes.is_empty() {
let cond_ip = *ip;
let then_body_size = build_function::lowered_body_size_at(
then_body,
cond_ip.saturating_add(cond_bytes.len() as u64),
);
let target = cond_ip
.saturating_add(slot_size)
.saturating_add(then_body_size)
.saturating_add(then_tail_jmp.len() as u64);
if let Ok(encoded) = arch.encode_cond_jump(
cond_text,
cond_ip,
target,
crate::arch::codec::EncodeHints::default(),
) && encoded == *cond_bytes
{
cond_bytes.clear();
}
}
*ip = ip.saturating_add(slot_size);
visit_stmts(then_body, ip, arch, slot_size);
if !then_tail_jmp.is_empty() {
let ttj_ip = *ip;
let else_size = build_function::lowered_body_size_at(
else_body,
ttj_ip.saturating_add(slot_size),
);
let target = ttj_ip.saturating_add(slot_size).saturating_add(else_size);
if let Ok(encoded) = arch.encode_jump(
ttj_ip,
target,
crate::arch::codec::EncodeHints::default(),
) && encoded == *then_tail_jmp
{
then_tail_jmp.clear();
}
*ip = ip.saturating_add(slot_size);
}
visit_stmts(else_body, ip, arch, slot_size);
}
crate::ast::Stmt::WhileBlock {
cond_text,
entry_bytes,
body: wb,
tail_bytes,
} => {
let entry_ip = *ip;
if !entry_bytes.is_empty() {
let body_size = build_function::lowered_body_size_at(
wb,
entry_ip.saturating_add(entry_bytes.len() as u64),
);
let target = entry_ip
.saturating_add(slot_size)
.saturating_add(body_size)
.saturating_add(tail_bytes.len() as u64);
if let Ok(encoded) = arch.encode_cond_jump(
cond_text,
entry_ip,
target,
crate::arch::codec::EncodeHints::default(),
) && encoded == *entry_bytes
{
entry_bytes.clear();
}
}
*ip = ip.saturating_add(slot_size);
visit_stmts(wb, ip, arch, slot_size);
if !tail_bytes.is_empty() {
let ja_ip = *ip;
if let Ok(encoded) = arch.encode_jump(
ja_ip,
entry_ip,
crate::arch::codec::EncodeHints::default(),
) && encoded == *tail_bytes
{
tail_bytes.clear();
}
*ip = ip.saturating_add(slot_size);
}
}
crate::ast::Stmt::IfBranch {
pre_body,
cond_bytes,
then_body,
else_body,
..
} => {
visit_stmts(pre_body, ip, arch, slot_size);
*ip = ip.saturating_add(cond_bytes.len() as u64);
visit_stmts(then_body, ip, arch, slot_size);
if let Some(eb) = else_body {
visit_stmts(eb, ip, arch, slot_size);
}
}
_ => {}
}
}
}
fn fn_base_addr(fd: &crate::ast::FnDecl) -> u64 {
if let Some(a) = fd.addr {
return a;
}
if let Some(rest) = fd.name.strip_prefix("sub_")
&& let Ok(a) = u64::from_str_radix(rest, 16)
{
return a;
}
0
}
fn visit_section_items(
section_addr: u64,
items: &mut [Item],
arch: &dyn crate::arch::codec::ArchCodec,
slot_size: u64,
) {
let mut cursor = section_addr;
for item in items.iter_mut() {
match item {
Item::Function(fd) => {
let start = if let Some(a) = fd.addr {
a
} else if let Some(rest) = fd.name.strip_prefix("sub_") {
u64::from_str_radix(rest, 16).unwrap_or(cursor)
} else {
cursor
};
let body_size = build_function::lowered_body_size_at(&fd.body, start);
let mut ip = start;
visit_stmts(&mut fd.body, &mut ip, arch, slot_size);
cursor = start.saturating_add(body_size);
}
Item::Raw { addr, bytes } => {
cursor = (*addr).saturating_add(bytes.len() as u64);
}
Item::Section { addr, items, .. } => {
visit_section_items(*addr, items, arch, slot_size);
}
_ => {}
}
}
}
for item in items.iter_mut() {
match item {
Item::Section { addr, items, .. } => {
visit_section_items(*addr, items, arch, slot_size);
}
Item::Function(fd) => {
let mut ip = fn_base_addr(fd);
visit_stmts(&mut fd.body, &mut ip, arch, slot_size);
}
_ => {}
}
}
}
#[allow(clippy::too_many_lines)]
fn drop_regenerable_asm_bytes(items: &mut [Item], bitness: crate::arch::x86::Bitness) {
fn asm_size(stmt: &crate::ast::Stmt, bitness: crate::arch::x86::Bitness, ip: u64) -> u64 {
if let crate::ast::Stmt::Asm { text, bytes } = stmt {
if !bytes.is_empty() {
return bytes.len() as u64;
}
if let Ok(encoded) = crate::arch::x86::assemble_intel(bitness, text, ip) {
return encoded.len() as u64;
}
}
0
}
fn visit_stmts(
stmts: &mut [crate::ast::Stmt],
bitness: crate::arch::x86::Bitness,
ip: &mut u64,
) {
for stmt in stmts.iter_mut() {
let here = *ip;
match stmt {
crate::ast::Stmt::Asm { text, bytes } => {
if bytes.is_empty() {
*ip = ip.saturating_add(asm_size(stmt, bitness, here));
continue;
}
if let Ok(encoded) = crate::arch::x86::assemble_intel(bitness, text, here)
&& encoded == *bytes
{
bytes.clear();
}
*ip = ip.saturating_add(asm_size(stmt, bitness, here));
}
crate::ast::Stmt::IfBranch {
pre_body,
cond_bytes,
then_body,
else_body,
..
} => {
visit_stmts(pre_body, bitness, ip);
*ip = ip.saturating_add(cond_bytes.len() as u64);
visit_stmts(then_body, bitness, ip);
if let Some(eb) = else_body {
visit_stmts(eb, bitness, ip);
}
}
crate::ast::Stmt::Loop {
entry_jmp_bytes,
body,
tail_bytes,
..
} => {
if let Some(jmp) = entry_jmp_bytes {
*ip = ip.saturating_add(jmp.len() as u64);
}
visit_stmts(body, bitness, ip);
*ip = ip.saturating_add(tail_bytes.len() as u64);
}
other => {
*ip = ip.saturating_add(stmt_min_size(other));
}
}
}
}
fn auto_prologue_size(f: &crate::ast::FnDecl) -> u64 {
let has_flag = f.attrs.iter().any(|a| {
(a.key == "autogen_pro" || a.key == "autogen_pro_legacy")
&& matches!(a.value, crate::ast::AttrValue::Flag)
});
if !has_flag {
return 0;
}
let profile = build_function::profile_inputs_from_fn(f);
let cb = if profile.bits == 64 {
crate::arch::x86::CodecBits::Bits64
} else {
crate::arch::x86::CodecBits::Bits32
};
let pro = crate::arch::x86::default_prologue(&profile);
crate::arch::x86::encode_prologue(&pro, cb).len() as u64
}
fn auto_epilogue_size(f: &crate::ast::FnDecl) -> u64 {
let has_flag = f.attrs.iter().any(|a| {
(a.key == "autogen_pro" || a.key == "autogen_pro_legacy")
&& matches!(a.value, crate::ast::AttrValue::Flag)
});
if !has_flag {
return 0;
}
let profile = build_function::profile_inputs_from_fn(f);
let cb = if profile.bits == 64 {
crate::arch::x86::CodecBits::Bits64
} else {
crate::arch::x86::CodecBits::Bits32
};
let epi = crate::arch::x86::default_epilogue(&profile);
crate::arch::x86::encode_epilogue(&epi, cb).len() as u64
}
#[allow(clippy::enum_glob_use)]
fn stmt_min_size(stmt: &crate::ast::Stmt) -> u64 {
use crate::ast::Stmt::*;
match stmt {
Asm { bytes, .. }
| Return { bytes, .. }
| Prologue { bytes, .. }
| Epilogue { bytes, .. }
| Save { bytes, .. }
| Restore { bytes, .. }
| ReturnExpr { bytes, .. }
| ArgSpill { bytes, .. }
| LocalSet { bytes, .. }
| LocalArith { bytes, .. }
| LocalCompound { bytes, .. }
| Move { bytes, .. }
| Inc16 { bytes, .. }
| SehInstall { bytes }
| SehRestore { bytes } => bytes.len() as u64,
Call {
bytes,
direct_target,
..
} => bytes.len() as u64 + if direct_target.is_some() { 5 } else { 0 },
Goto { wide, .. } => {
if *wide {
5
} else {
2
}
}
IfGoto {
cmp_bytes, wide, ..
}
| IfReturn {
cmp_bytes, wide, ..
} => cmp_bytes.len() as u64 + if *wide { 6 } else { 2 },
_ => 0,
}
}
fn visit_items(items: &mut [Item], bitness: crate::arch::x86::Bitness, section_cursor: u64) {
let mut cursor = section_cursor;
for item in items.iter_mut() {
match item {
Item::Function(f) => {
let fn_ip = f.addr.unwrap_or(cursor);
let mut ip = fn_ip;
ip = ip.saturating_add(auto_prologue_size(f));
visit_stmts(&mut f.body, bitness, &mut ip);
ip = ip.saturating_add(auto_epilogue_size(f));
cursor = ip;
}
Item::Section {
addr,
items: nested,
..
} => visit_items(nested, bitness, *addr),
Item::Raw { addr, bytes } => {
cursor = (*addr).saturating_add(bytes.len() as u64);
}
Item::Strings { addr, strings } => {
cursor =
(*addr).saturating_add(strings.iter().map(|s| (s.len() + 1) as u64).sum());
}
Item::Notes { addr, .. } => {
cursor = *addr;
}
Item::JumpTable { addr, entries, .. } => {
cursor = (*addr).saturating_add((entries.len() as u64) * 4);
}
Item::Comment(_) => {}
}
}
}
visit_items(items, bitness, 0);
}
#[derive(Debug, Clone)]
struct SwitchTable {
table_va: u64,
dispatch: String,
cases: Vec<u64>,
}
fn collect_switch_tables(items: &[Item]) -> Vec<SwitchTable> {
fn walk(items: &[Item], out: &mut Vec<SwitchTable>) {
for item in items {
match item {
Item::Function(f) => collect_in_stmts(&f.body, out),
Item::Section { items: nested, .. } => walk(nested, out),
_ => {}
}
}
}
fn collect_in_stmts(stmts: &[crate::ast::Stmt], out: &mut Vec<SwitchTable>) {
for s in stmts {
match s {
crate::ast::Stmt::Switch {
cases,
dispatch,
table_va,
..
} if *table_va != 0 && !cases.is_empty() => {
out.push(SwitchTable {
table_va: *table_va,
dispatch: dispatch.clone(),
cases: cases.clone(),
});
}
crate::ast::Stmt::IfBranch {
pre_body,
then_body,
else_body,
..
} => {
collect_in_stmts(pre_body, out);
collect_in_stmts(then_body, out);
if let Some(eb) = else_body {
collect_in_stmts(eb, out);
}
}
crate::ast::Stmt::Loop { body, .. } => collect_in_stmts(body, out),
_ => {}
}
}
}
let mut out = Vec::new();
walk(items, &mut out);
out
}
fn replace_raw_with_jump_tables(items: &mut [Item], tables: &[SwitchTable]) {
fn entry_size(dispatch: &str) -> Option<u64> {
match dispatch {
"gcc_pie_rel32" | "msvc_va32" => Some(4),
_ => None,
}
}
fn rewrite_section(section_items: &mut Vec<Item>, tables: &[SwitchTable]) {
let mut i = 0;
while i < section_items.len() {
let mut replacement: Option<(usize, Vec<Item>)> = None;
if let Item::Raw { addr, bytes } = §ion_items[i] {
for t in tables {
let Some(esz) = entry_size(&t.dispatch) else {
continue;
};
let table_size = esz * (t.cases.len() as u64);
let raw_start = *addr;
let raw_end = raw_start.saturating_add(bytes.len() as u64);
let table_end = t.table_va.saturating_add(table_size);
if t.table_va < raw_start || table_end > raw_end {
continue;
}
let prefix_len = (t.table_va - raw_start) as usize;
let table_len = table_size as usize;
let suffix_start = prefix_len + table_len;
let mut new_items: Vec<Item> = Vec::new();
if prefix_len > 0 {
new_items.push(Item::Raw {
addr: raw_start,
bytes: bytes[..prefix_len].to_vec(),
});
}
let entries = t
.cases
.iter()
.enumerate()
.map(|(idx, target)| crate::ast::JumpTableEntry {
case: idx as u64,
target: *target,
})
.collect();
new_items.push(Item::JumpTable {
addr: t.table_va,
dispatch: t.dispatch.clone(),
entries,
});
if suffix_start < bytes.len() {
new_items.push(Item::Raw {
addr: table_end,
bytes: bytes[suffix_start..].to_vec(),
});
}
replacement = Some((1, new_items));
break;
}
}
if let Some((remove, mut new_items)) = replacement {
section_items.remove(i);
let added = new_items.len();
for (off, it) in new_items.drain(..).enumerate() {
section_items.insert(i + off, it);
}
let _ = remove; i += added;
} else {
i += 1;
}
}
}
for item in items.iter_mut() {
if let Item::Section { items: nested, .. } = item {
rewrite_section(nested, tables);
}
}
}
fn drop_redundant_function_addrs(section_addr: u64, items: &mut [Item]) {
let mut cursor = section_addr;
for item in items.iter_mut() {
match item {
Item::Function(f) => {
let body_size = build_function::lowered_body_size_at(&f.body, cursor);
if f.addr == Some(cursor) {
f.addr = None;
}
cursor = cursor.saturating_add(body_size);
}
Item::Raw { addr, bytes } => {
cursor = (*addr).saturating_add(bytes.len() as u64);
}
Item::Strings { addr, strings } => {
cursor = (*addr).saturating_add(strings_byte_size(strings));
}
Item::Notes { addr, entries } => {
cursor = (*addr).saturating_add(notes_byte_size(entries));
}
Item::JumpTable { addr, entries, .. } => {
cursor = (*addr).saturating_add((entries.len() as u64) * 4);
}
Item::Comment(_) | Item::Section { .. } => {}
}
}
}
fn strings_byte_size(strings: &[String]) -> u64 {
strings.iter().map(|s| (s.len() + 1) as u64).sum()
}
fn notes_byte_size(entries: &[crate::ast::NoteEntry]) -> u64 {
let mut size: u64 = 0;
for e in entries {
size += 12; let name_padded = ((e.name.len() + 1) + 3) & !3; size += name_padded as u64;
let desc_padded = (e.desc.len() + 3) & !3;
size += desc_padded as u64;
}
size
}
pub fn decompile_to_text(elf: &Elf64File) -> Result<String> {
let ast = decompile(elf)?;
Ok(crate::ast::emit(&ast))
}
fn section_is_emittable(sh: &Shdr64) -> bool {
const SHT_NULL: u32 = 0;
const SHT_NOBITS: u32 = 8;
sh.sh_type != SHT_NULL && sh.sh_type != SHT_NOBITS && sh.sh_size > 0
}
fn function_lives_in_a_section(elf: &Elf64File, addr: u64, size: u64) -> bool {
if size == 0 {
return false;
}
let end = addr.saturating_add(size);
for (_, sh, _) in elf.sections() {
let sh_end = sh.sh_addr.saturating_add(sh.sh_size);
if sh.sh_addr <= addr && end <= sh_end {
return true;
}
}
false
}
fn decode_strtab(data: &[u8]) -> Option<Vec<String>> {
if data.is_empty() {
return Some(Vec::new());
}
if *data.last().unwrap() != 0 {
return None;
}
let mut out = Vec::new();
for chunk in data.split(|&b| b == 0) {
if chunk.is_empty() {
out.push(String::new());
} else {
out.push(std::str::from_utf8(chunk).ok()?.to_string());
}
}
if out.last().is_some_and(String::is_empty) {
out.pop();
}
Some(out)
}
fn decode_notes(data: &[u8]) -> Option<Vec<crate::ast::NoteEntry>> {
let mut out = Vec::new();
let mut i = 0;
while i < data.len() {
if i + 12 > data.len() {
return None;
}
let name_size = u32::from_le_bytes(data[i..i + 4].try_into().ok()?) as usize;
let desc_size = u32::from_le_bytes(data[i + 4..i + 8].try_into().ok()?) as usize;
let note_type = u32::from_le_bytes(data[i + 8..i + 12].try_into().ok()?);
i += 12;
if name_size == 0 || i + name_size > data.len() {
return None;
}
let name_bytes = &data[i..i + name_size - 1];
if data[i + name_size - 1] != 0 {
return None;
}
let name = std::str::from_utf8(name_bytes).ok()?.to_string();
i += name_size;
while i % 4 != 0 {
if i >= data.len() || data[i] != 0 {
return None;
}
i += 1;
}
if i + desc_size > data.len() {
return None;
}
let desc = data[i..i + desc_size].to_vec();
i += desc_size;
while i % 4 != 0 {
if i >= data.len() || data[i] != 0 {
return None;
}
i += 1;
}
out.push(crate::ast::NoteEntry {
note_type,
name,
desc,
});
}
Some(out)
}
#[allow(clippy::too_many_arguments)]
fn build_section_items(
elf: &Elf64File,
sh: &Shdr64,
data: &[u8],
map: &FunctionMap,
debug_by_addr: &HashMap<u64, DebugFunction>,
name_at: &HashMap<u64, String>,
call_site_names: &HashMap<u64, String>,
arch: Arch,
) -> Result<Vec<Item>> {
const SHT_STRTAB: u32 = 3;
const SHT_NOTE: u32 = 7;
let section_name = elf
.shdrs
.iter()
.position(|sh2| std::ptr::eq(sh2, sh))
.and_then(|idx| elf.section_name(idx))
.unwrap_or("");
let is_interp = section_name == ".interp";
if (sh.sh_type == SHT_STRTAB || is_interp)
&& let Some(strings) = decode_strtab(data)
{
return Ok(vec![Item::Strings {
addr: sh.sh_addr,
strings,
}]);
}
if sh.sh_type == SHT_NOTE
&& let Some(entries) = decode_notes(data)
{
return Ok(vec![Item::Notes {
addr: sh.sh_addr,
entries,
}]);
}
let section_start = sh.sh_addr;
let section_end = sh.sh_addr.saturating_add(sh.sh_size);
let is_exec = sh.sh_flags & SHF_EXECINSTR != 0;
let mut funcs: Vec<_> = if is_exec {
map.iter()
.filter(|f| {
f.size > 0
&& f.addr.0 >= section_start
&& f.addr.0.saturating_add(f.size) <= section_end
})
.collect()
} else {
Vec::new()
};
funcs.sort_by_key(|f| f.addr.0);
let mut out = Vec::new();
let mut cursor = section_start;
for f in &funcs {
if cursor < f.addr.0 {
let lo = (cursor - section_start) as usize;
let hi = (f.addr.0 - section_start) as usize;
emit_gap(
&mut out,
arch,
is_exec,
cursor,
&data[lo..hi],
name_at,
call_site_names,
elf,
)?;
}
let lo = (f.addr.0 - section_start) as usize;
let hi = lo + f.size as usize;
let slice = &data[lo..hi];
let fn_decl = match arch {
Arch::X86 { bitness } => {
let insns = decode(bitness, slice, f.addr.0)?;
let lifted = lift_function(f.name.clone(), &insns)?;
let debug = debug_by_addr.get(&f.addr.0);
build_function::build_function(&lifted, debug, name_at, elf)
}
Arch::Aarch64 => {
let insns =
crate::arch::aarch64::decode(slice, f.addr.0).map_err(Error::Aarch64Decode)?;
let lifted = crate::arch::aarch64::lift_function(f.name.clone(), &insns);
aarch64::build_function(&lifted, name_at)
}
Arch::Bpf { variant } => {
let insns =
crate::arch::bpf::decode(slice, f.addr.0, variant).map_err(Error::BpfDecode)?;
let lifted = crate::arch::bpf::lift_function(f.name.clone(), &insns);
bpf::build_function(&lifted, name_at, call_site_names, variant, Some(elf))
}
};
out.push(Item::Function(fn_decl));
cursor = f.addr.0.saturating_add(f.size);
}
if cursor < section_end {
let lo = (cursor - section_start) as usize;
emit_gap(
&mut out,
arch,
is_exec,
cursor,
&data[lo..],
name_at,
call_site_names,
elf,
)?;
}
Ok(out)
}
#[allow(clippy::too_many_arguments)]
fn emit_gap(
out: &mut Vec<Item>,
arch: Arch,
is_exec: bool,
addr: u64,
bytes: &[u8],
name_at: &HashMap<u64, String>,
call_site_names: &HashMap<u64, String>,
elf: &Elf64File,
) -> Result<()> {
if bytes.is_empty() {
return Ok(());
}
if is_exec && let Arch::Bpf { variant } = arch {
let prefix_len = bytes.len() % crate::arch::bpf::INSN_SIZE;
let (prefix, code) = bytes.split_at(prefix_len);
if !prefix.is_empty() {
out.push(Item::Raw {
addr,
bytes: prefix.to_vec(),
});
}
let code_addr = addr + prefix_len as u64;
if !code.is_empty() {
let insns =
crate::arch::bpf::decode(code, code_addr, variant).map_err(Error::BpfDecode)?;
let lifted = crate::arch::bpf::lift_function(format!("fragment_{code_addr:x}"), &insns);
out.push(Item::Function(bpf::build_function(
&lifted,
name_at,
call_site_names,
variant,
Some(elf),
)));
}
return Ok(());
}
out.push(Item::Raw {
addr,
bytes: bytes.to_vec(),
});
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ast::{FnDecl, Stmt};
#[test]
fn replace_raw_with_jump_table_splits_around_table_va() {
let switch_stmt = Stmt::Switch {
selector: "ecx".into(),
cases: vec![0x117a, 0x1183, 0x118c],
default_addr: 0x1200,
dispatch: "gcc_pie_rel32".into(),
table_va: 0x2008,
};
let raw_bytes = vec![
0x01, 0x00, 0x02, 0x00, 0x53, 0x75, 0x6e,
0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xde, 0xad, 0xbe, 0xef,
];
let mut items = vec![Item::Section {
name: ".text".into(),
addr: 0x1000,
items: vec![Item::Function(FnDecl {
addr: Some(0x1000),
name: "f".into(),
attrs: Vec::new(),
signature: None,
locals: Vec::new(),
body: vec![switch_stmt],
})],
}];
items.push(Item::Section {
name: ".rodata".into(),
addr: 0x2000,
items: vec![Item::Raw {
addr: 0x2000,
bytes: raw_bytes,
}],
});
let tables = collect_switch_tables(&items);
assert_eq!(tables.len(), 1);
replace_raw_with_jump_tables(&mut items, &tables);
let Item::Section {
items: ro_items, ..
} = &items[1]
else {
panic!("expected .rodata section");
};
assert_eq!(ro_items.len(), 3);
match &ro_items[0] {
Item::Raw { addr, bytes } => {
assert_eq!(*addr, 0x2000);
assert_eq!(bytes.len(), 8);
}
other => panic!("expected Raw prefix, got {other:?}"),
}
match &ro_items[1] {
Item::JumpTable {
addr,
dispatch,
entries,
} => {
assert_eq!(*addr, 0x2008);
assert_eq!(dispatch, "gcc_pie_rel32");
assert_eq!(entries.len(), 3);
assert_eq!(entries[0].target, 0x117a);
assert_eq!(entries[2].target, 0x118c);
}
other => panic!("expected JumpTable, got {other:?}"),
}
match &ro_items[2] {
Item::Raw { addr, bytes } => {
assert_eq!(*addr, 0x2014);
assert_eq!(bytes, &[0xde, 0xad, 0xbe, 0xef]);
}
other => panic!("expected Raw suffix, got {other:?}"),
}
}
#[test]
fn replace_raw_with_jump_table_skips_unknown_dispatch() {
let switch_stmt = Stmt::Switch {
selector: "ecx".into(),
cases: vec![0x100, 0x200],
default_addr: 0x300,
dispatch: "bogus-dispatch".into(),
table_va: 0x2000,
};
let mut items = vec![
Item::Section {
name: ".text".into(),
addr: 0x1000,
items: vec![Item::Function(FnDecl {
addr: Some(0x1000),
name: "f".into(),
attrs: Vec::new(),
signature: None,
locals: Vec::new(),
body: vec![switch_stmt],
})],
},
Item::Section {
name: ".rodata".into(),
addr: 0x2000,
items: vec![Item::Raw {
addr: 0x2000,
bytes: vec![0u8; 8],
}],
},
];
let tables = collect_switch_tables(&items);
replace_raw_with_jump_tables(&mut items, &tables);
let Item::Section {
items: ro_items, ..
} = &items[1]
else {
panic!()
};
assert!(matches!(&ro_items[0], Item::Raw { .. }));
}
}