#![allow(clippy::cast_possible_truncation)]
use ud_core::VAddr;
pub trait ArchInsn {
fn addr(&self) -> VAddr;
fn original_bytes(&self) -> &[u8];
fn len_bytes(&self) -> usize {
self.original_bytes().len()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Terminator {
Fallthrough,
UnconditionalBranch { target: VAddr },
ConditionalBranch { taken: VAddr, fallthrough: VAddr },
Return,
IndirectBranch,
InvalidOrUnreachable,
}
#[derive(Debug, Clone)]
pub struct BasicBlock<I> {
pub addr: VAddr,
pub insns: Vec<I>,
pub terminator: Terminator,
}
impl<I: ArchInsn> BasicBlock<I> {
#[must_use]
pub fn size(&self) -> usize {
self.insns.iter().map(ArchInsn::len_bytes).sum()
}
#[must_use]
pub fn end_addr(&self) -> VAddr {
VAddr(self.addr.0 + self.size() as u64)
}
}
#[derive(Debug, Clone)]
pub struct Function<I> {
pub addr: VAddr,
pub name: String,
pub blocks: Vec<BasicBlock<I>>,
}
impl<I: ArchInsn> Function<I> {
#[must_use]
pub fn size(&self) -> usize {
self.blocks.iter().map(BasicBlock::size).sum()
}
#[must_use]
pub fn emit_bytes(&self) -> Vec<u8> {
let mut out = Vec::with_capacity(self.size());
for block in &self.blocks {
for insn in &block.insns {
out.extend_from_slice(insn.original_bytes());
}
}
out
}
#[must_use]
pub fn block_at(&self, addr: VAddr) -> Option<&BasicBlock<I>> {
self.blocks.iter().find(|b| b.addr == addr)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Debug, Clone)]
struct DummyInsn {
addr: u64,
bytes: Vec<u8>,
}
impl ArchInsn for DummyInsn {
fn addr(&self) -> VAddr {
VAddr(self.addr)
}
fn original_bytes(&self) -> &[u8] {
&self.bytes
}
}
fn insn(addr: u64, bytes: &[u8]) -> DummyInsn {
DummyInsn {
addr,
bytes: bytes.to_vec(),
}
}
fn function_one_block() -> Function<DummyInsn> {
Function {
addr: VAddr(0x1000),
name: "single".into(),
blocks: vec![BasicBlock {
addr: VAddr(0x1000),
insns: vec![insn(0x1000, &[0x90, 0x90]), insn(0x1002, &[0xc3])],
terminator: Terminator::Return,
}],
}
}
#[test]
fn emit_bytes_concatenates_in_block_order() {
let f = function_one_block();
assert_eq!(f.emit_bytes(), vec![0x90, 0x90, 0xc3]);
}
#[test]
fn function_size_sums_block_sizes() {
let f = function_one_block();
assert_eq!(f.size(), 3);
}
#[test]
fn block_end_addr_is_addr_plus_size() {
let f = function_one_block();
assert_eq!(f.blocks[0].end_addr(), VAddr(0x1003));
}
#[test]
fn block_at_finds_blocks_by_address() {
let f = Function::<DummyInsn> {
addr: VAddr(0x1000),
name: "two_blocks".into(),
blocks: vec![
BasicBlock {
addr: VAddr(0x1000),
insns: vec![insn(0x1000, &[0xeb, 0x02])],
terminator: Terminator::UnconditionalBranch {
target: VAddr(0x1004),
},
},
BasicBlock {
addr: VAddr(0x1004),
insns: vec![insn(0x1004, &[0xc3])],
terminator: Terminator::Return,
},
],
};
assert!(f.block_at(VAddr(0x1004)).is_some());
assert!(f.block_at(VAddr(0x1002)).is_none());
}
}