use crate::{
assemble_intel, encode_call_rel32, encode_jcc, encode_jmp, encode_msvc_jmp_table_dispatch,
encoded_jcc_size, encoded_jmp_size, Bitness,
};
use ud_arch_codec::{ArchCodec, ArchError, EncodeHints, SwitchSpec};
#[derive(Debug, Clone, Copy)]
pub struct X86Codec(pub Bitness);
impl X86Codec {
pub const BITS64: Self = Self(Bitness::Bits64);
pub const BITS32: Self = Self(Bitness::Bits32);
}
impl ArchCodec for X86Codec {
fn name(&self) -> &'static str {
match self.0 {
Bitness::Bits16 => "x86-16",
Bitness::Bits32 => "x86-32",
Bitness::Bits64 => "x86-64",
}
}
fn assemble_one(&self, text: &str, addr: u64) -> Result<Vec<u8>, ArchError> {
assemble_intel(self.0, text, addr).map_err(|e| ArchError::Assemble(e.to_string()))
}
fn encode_jump(
&self,
source_ip: u64,
target: u64,
hints: EncodeHints,
) -> Result<Vec<u8>, ArchError> {
encode_jmp(source_ip, target, hints.wide_or(false))
.map_err(|e| ArchError::OutOfRange(e.to_string()))
}
fn encode_call(
&self,
source_ip: u64,
target: u64,
_hints: EncodeHints,
) -> Result<Vec<u8>, ArchError> {
encode_call_rel32(source_ip, target).map_err(|e| ArchError::OutOfRange(e.to_string()))
}
fn encode_cond_jump(
&self,
_cond_text: &str,
_source_ip: u64,
_target: u64,
_hints: EncodeHints,
) -> Result<Vec<u8>, ArchError> {
Err(ArchError::Unsupported {
arch: self.name(),
operation: "cond_jump (text)",
})
}
fn encode_cond_jump_with_code(
&self,
cond_code: u8,
source_ip: u64,
target: u64,
hints: EncodeHints,
) -> Result<Vec<u8>, ArchError> {
encode_jcc(source_ip, target, cond_code, hints.wide_or(false))
.map_err(|e| ArchError::OutOfRange(e.to_string()))
}
fn encode_switch_dispatch(&self, spec: &SwitchSpec) -> Result<Vec<u8>, ArchError> {
if spec.dispatch != "msvc-jmp-table" {
return Err(ArchError::Unsupported {
arch: self.name(),
operation: "switch_dispatch (non-msvc)",
});
}
encode_msvc_jmp_table_dispatch(
spec.selector,
spec.cases.len(),
spec.default_addr,
spec.table_va,
spec.cmp_ip,
)
.map_err(|e| ArchError::OutOfRange(e.to_string()))
}
fn encoded_jump_size(&self, source_ip: u64, target: u64, hints: EncodeHints) -> usize {
encoded_jmp_size(source_ip, target, hints.wide_or(false))
}
fn encoded_cond_jump_size(&self, source_ip: u64, target: u64, hints: EncodeHints) -> usize {
encoded_jcc_size(source_ip, target, hints.wide_or(false))
}
fn encoded_call_size(&self, _source_ip: u64, _target: u64, _hints: EncodeHints) -> usize {
5
}
fn encode_move(&self, dst: &str, src: &str) -> Result<Vec<u8>, ArchError> {
let (asm_dst, dst_width) =
resolve_x86_operand(dst).ok_or_else(|| ArchError::Unsupported {
arch: self.name(),
operation: "move (unresolved dst)",
})?;
let (asm_src, src_width) =
resolve_x86_operand(src).ok_or_else(|| ArchError::Unsupported {
arch: self.name(),
operation: "move (unresolved src)",
})?;
let width = dst_width.or(src_width).unwrap_or(32);
let size_prefix = match width {
8 => "byte ptr",
16 => "word ptr",
32 => "dword ptr",
_ => "qword ptr",
};
let dst_text = if asm_dst.starts_with('[') {
format!("{size_prefix} {asm_dst}")
} else {
asm_dst
};
let src_text = if asm_src.starts_with('[') {
format!("{size_prefix} {asm_src}")
} else {
asm_src
};
let text = format!("mov {dst_text}, {src_text}");
assemble_intel(self.0, &text, 0).map_err(|e| ArchError::Assemble(e.to_string()))
}
}
fn resolve_x86_operand(s: &str) -> Option<(String, Option<u32>)> {
let s = s.trim();
if let Some(hex) = s.strip_prefix("var_") {
let disp = u64::from_str_radix(hex, 16).ok()?;
return Some((format!("[rbp - 0x{disp:x}]"), None));
}
if let Some(hex) = s.strip_prefix("arg_") {
let disp = u64::from_str_radix(hex, 16).ok()?;
return Some((format!("[rbp + 0x{disp:x}]"), None));
}
if let Some(w) = x86_gpr_width(s) {
return Some((s.to_string(), Some(w)));
}
if is_integer_literal(s) {
return Some((s.to_string(), None));
}
None
}
fn x86_gpr_width(s: &str) -> Option<u32> {
let s = s.trim();
if matches!(
s,
"rax"
| "rbx"
| "rcx"
| "rdx"
| "rsi"
| "rdi"
| "rbp"
| "rsp"
| "r8"
| "r9"
| "r10"
| "r11"
| "r12"
| "r13"
| "r14"
| "r15"
) {
return Some(64);
}
if matches!(
s,
"eax" | "ebx" | "ecx" | "edx" | "esi" | "edi" | "ebp" | "esp"
) || (s.starts_with('r')
&& s.ends_with('d')
&& s.len() == 3
&& s.as_bytes()[1].is_ascii_digit())
|| (s.starts_with("r1") && s.ends_with('d') && s.len() == 4)
{
return Some(32);
}
if matches!(s, "ax" | "bx" | "cx" | "dx" | "si" | "di" | "bp" | "sp") {
return Some(16);
}
if matches!(
s,
"al" | "bl" | "cl" | "dl" | "ah" | "bh" | "ch" | "dh" | "sil" | "dil" | "bpl" | "spl"
) {
return Some(8);
}
None
}
fn is_integer_literal(s: &str) -> bool {
let s = s.trim();
let s = s.strip_prefix('-').unwrap_or(s);
if let Some(hex) = s.strip_prefix("0x") {
return !hex.is_empty() && hex.chars().all(|c| c.is_ascii_hexdigit());
}
!s.is_empty() && s.chars().all(|c| c.is_ascii_digit())
}
pub fn register() {
ud_arch_codec::register(factory);
}
fn factory(arch_name: Option<&str>, _e_machine: Option<u64>) -> Option<Box<dyn ArchCodec>> {
match arch_name {
Some("x86_64") => Some(Box::new(X86Codec(Bitness::Bits64))),
Some("i386") => Some(Box::new(X86Codec(Bitness::Bits32))),
Some("x86_16") => Some(Box::new(X86Codec(Bitness::Bits16))),
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
use ud_arch_codec::ArchCodec;
#[test]
fn encode_move_reg_reg() {
let codec = X86Codec(Bitness::Bits64);
let bytes = codec.encode_move("rax", "rbx").expect("encode");
assert_eq!(bytes, vec![0x48, 0x89, 0xd8]);
}
#[test]
fn encode_move_unsupported_forms_error() {
let codec = X86Codec(Bitness::Bits64);
assert!(codec.encode_move("eax", "0").is_err());
assert!(codec.encode_move("var_8", "0").is_err());
}
}