use std::marker::PhantomData;
use serde::{Deserialize, Serialize};
pub(crate) mod lifter;
pub(crate) fn level(ir: crate::lift::Ir) -> Option<&'static str> {
use crate::lift::Ir;
match ir {
Ir::BinaryNinjaLlil => Some("llil"),
Ir::BinaryNinjaMlil => Some("mlil"),
Ir::BinaryNinjaHlil => Some("hlil"),
Ir::GhidraPcode | Ir::IdaMicrocode => None,
}
}
pub trait Level {
const CALL_OPS: &'static [&'static str];
const CALL_TARGET: usize;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Llil;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Mlil;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Hlil;
impl Level for Llil {
const CALL_OPS: &'static [&'static str] = &[
"LLIL_CALL",
"LLIL_CALL_STACK_ADJUST",
"LLIL_TAILCALL",
"LLIL_SYSCALL",
];
const CALL_TARGET: usize = 0;
}
impl Level for Mlil {
const CALL_OPS: &'static [&'static str] = &[
"MLIL_CALL",
"MLIL_CALL_UNTYPED",
"MLIL_TAILCALL",
"MLIL_TAILCALL_UNTYPED",
"MLIL_SYSCALL",
"MLIL_SYSCALL_UNTYPED",
];
const CALL_TARGET: usize = 1;
}
impl Level for Hlil {
const CALL_OPS: &'static [&'static str] = &["HLIL_CALL", "HLIL_TAILCALL", "HLIL_SYSCALL"];
const CALL_TARGET: usize = 0;
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(bound = "")]
pub struct Op<L> {
op: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
out: Option<String>,
inputs: Vec<String>,
#[serde(skip)]
level: PhantomData<L>,
}
impl<L: Level> crate::Op for Op<L> {
fn mnemonic(&self) -> &str {
&self.op
}
fn inputs(&self) -> &[String] {
&self.inputs
}
fn ret(&self) -> Option<&str> {
self.out.as_deref()
}
fn is_call(&self) -> bool {
L::CALL_OPS.contains(&self.op.as_str())
}
fn symbol_key(&self) -> Option<String> {
if !self.is_call() {
return None;
}
self.inputs.get(L::CALL_TARGET).cloned()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Op as _;
fn op<L>(name: &str, inputs: &[&str]) -> Op<L> {
Op {
op: name.to_string(),
out: None,
inputs: inputs.iter().map(|s| s.to_string()).collect(),
level: PhantomData,
}
}
#[test]
fn test_each_level_reads_the_callee_from_its_own_position() {
let llil: Op<Llil> = op("LLIL_CALL", &["0x100000480"]);
assert_eq!(llil.symbol_key().as_deref(), Some("0x100000480"));
let mlil: Op<Mlil> = op("MLIL_CALL", &["[]", "0x100000480", "[]"]);
assert_eq!(mlil.symbol_key().as_deref(), Some("0x100000480"));
let hlil: Op<Hlil> = op("HLIL_CALL", &["_printf", "[]"]);
assert_eq!(hlil.symbol_key().as_deref(), Some("_printf"));
}
#[test]
fn test_the_operands_and_the_destination_are_what_was_read() {
let mut set: Op<Llil> = op("LLIL_SET_REG", &["x0", "0x100000000"]);
assert_eq!(set.inputs(), ["x0".to_string(), "0x100000000".to_string()]);
assert_eq!(set.ret(), None);
set.out = Some("x0".to_string());
assert_eq!(set.ret(), Some("x0"));
let empty: Op<Hlil> = op("HLIL_NOP", &[]);
assert!(empty.inputs().is_empty());
}
#[test]
fn test_only_binary_ninjas_representations_name_a_level() {
use crate::lift::Ir;
assert_eq!(level(Ir::BinaryNinjaLlil), Some("llil"));
assert_eq!(level(Ir::BinaryNinjaMlil), Some("mlil"));
assert_eq!(level(Ir::BinaryNinjaHlil), Some("hlil"));
assert_eq!(level(Ir::GhidraPcode), None);
assert_eq!(level(Ir::IdaMicrocode), None);
}
#[test]
fn test_only_a_call_names_a_symbol() {
let set: Op<Llil> = op("LLIL_SET_REG", &["x0", "0x100000480"]);
assert!(!set.is_call());
assert_eq!(set.symbol_key(), None);
}
#[test]
fn test_a_call_without_its_target_operand_yields_nothing() {
let truncated: Op<Mlil> = op("MLIL_CALL", &["[]"]);
assert!(truncated.is_call());
assert_eq!(truncated.symbol_key(), None);
}
#[test]
fn test_no_level_recognises_another_levels_call() {
let llil_call_at_mlil: Op<Mlil> = op("LLIL_CALL", &["0x1"]);
assert!(!llil_call_at_mlil.is_call());
let hlil_call_at_llil: Op<Llil> = op("HLIL_CALL", &["_printf"]);
assert!(!hlil_call_at_llil.is_call());
}
}