use core::fmt;
use super::inst::{Inst, Mnemonic};
use super::operand::{Mem, Operand};
pub(crate) fn hex(f: &mut fmt::Formatter<'_>, value: i64) -> fmt::Result {
if value < 0 {
write!(f, "-0x{:x}", value.unsigned_abs())
} else {
write!(f, "0x{value:x}")
}
}
pub(crate) fn signed_hex(f: &mut fmt::Formatter<'_>, value: i64) -> fmt::Result {
if value >= 0 {
f.write_str("+")?;
}
hex(f, value)
}
fn address(f: &mut fmt::Formatter<'_>, m: &Mem) -> fmt::Result {
f.write_str("[")?;
let disp = i64::from(m.disp());
let mut any = false;
if m.is_rip_relative() {
f.write_str("rip")?;
any = true;
}
if let Some(base) = m.base() {
f.write_str(base.name())?;
any = true;
}
if let Some(index) = m.index() {
if any {
f.write_str("+")?;
}
write!(f, "{}*{}", index.name(), m.scale().factor())?;
any = true;
}
if !any {
hex(f, disp)?;
} else if disp != 0 {
signed_hex(f, disp)?;
}
f.write_str("]")
}
fn operand(f: &mut fmt::Formatter<'_>, op: &Operand, sized: bool, ip: Option<u64>) -> fmt::Result {
match op {
Operand::Reg(r) => f.write_str(r.name()),
Operand::Mem(m) => {
if sized {
write!(f, "{} ptr ", m.size().keyword())?;
}
address(f, m)
}
Operand::Imm(v) => hex(f, *v),
Operand::Rel(d) => match ip {
Some(ip) => write!(f, "0x{:x}", ip.wrapping_add(*d as u64)),
None => {
f.write_str("$")?;
signed_hex(f, *d)
}
},
}
}
fn inst(f: &mut fmt::Formatter<'_>, i: &Inst, ip: Option<u64>) -> fmt::Result {
write!(f, "{}", i.mnemonic())?;
let sized = i.mnemonic() != Mnemonic::Lea;
for (n, op) in i.operands().iter().enumerate() {
f.write_str(if n == 0 { " " } else { ", " })?;
operand(f, op, sized, ip)?;
}
Ok(())
}
impl fmt::Display for Inst {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
padded(f, self, None)
}
}
fn padded(f: &mut fmt::Formatter<'_>, i: &Inst, ip: Option<u64>) -> fmt::Result {
if f.width().is_none() {
return inst(f, i, ip);
}
f.pad(&alloc::format!("{}", Unpadded(i, ip)))
}
struct Unpadded<'a>(&'a Inst, Option<u64>);
impl fmt::Display for Unpadded<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
inst(f, self.0, self.1)
}
}
impl fmt::Display for Operand {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
operand(f, self, true, None)
}
}
impl fmt::Display for Mem {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} ptr ", self.size().keyword())?;
address(f, self)
}
}
impl fmt::Debug for Mem {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Mem({self})")
}
}
#[derive(Clone, Copy, Debug)]
pub struct DisplayAt<'a> {
inst: &'a Inst,
ip: u64,
}
impl Inst {
#[must_use]
pub fn display_at(&self, ip: u64) -> DisplayAt<'_> {
DisplayAt { inst: self, ip }
}
}
impl fmt::Display for DisplayAt<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
padded(f, self.inst, Some(self.ip))
}
}
#[cfg(test)]
mod tests {
use alloc::string::ToString;
use super::*;
use crate::x86_64::{Reg, Scale, Size};
#[test]
fn test_hex_extremes() {
assert_eq!(Operand::Imm(i64::MIN).to_string(), "-0x8000000000000000");
assert_eq!(Operand::Imm(i64::MAX).to_string(), "0x7fffffffffffffff");
assert_eq!(Operand::Imm(0).to_string(), "0x0");
assert_eq!(Operand::Rel(0).to_string(), "$+0x0");
assert_eq!(Operand::Rel(-1).to_string(), "$-0x1");
}
#[test]
fn test_memory_text() {
let q = Size::Qword;
assert_eq!(Mem::new(q, Reg::RAX).to_string(), "qword ptr [rax]");
assert_eq!(Mem::rip(q, 0).to_string(), "qword ptr [rip]");
assert_eq!(Mem::absolute(q, -16).to_string(), "qword ptr [-0x10]");
assert_eq!(Mem::absolute(q, 0).to_string(), "qword ptr [0x0]");
let idx = Mem::absolute(q, 0).with_index(Reg::R12, Scale::X1);
assert_eq!(idx.to_string(), "qword ptr [r12*1]");
let full = Mem::new(Size::Byte, Reg::R13)
.with_index(Reg::RBP, Scale::X2)
.with_disp(i32::MIN);
assert_eq!(full.to_string(), "byte ptr [r13+rbp*2-0x80000000]");
}
#[test]
fn test_lea_has_no_size() {
let lea = Inst::new(
Mnemonic::Lea,
[
Reg::RAX.into(),
Mem::new(Size::Qword, Reg::RBX).with_disp(8).into(),
],
);
assert_eq!(lea.to_string(), "lea rax, [rbx+0x8]");
}
#[test]
fn test_padding_is_honoured() {
let i = Inst::new(Mnemonic::Ret, []);
assert_eq!(alloc::format!("[{i:>5}]"), "[ ret]");
assert_eq!(alloc::format!("[{:<6}]", i.display_at(0)), "[ret ]");
}
#[test]
fn test_display_at_wraps() {
let j = Inst::new(Mnemonic::Jmp, [Operand::Rel(-2)]);
assert_eq!(j.display_at(0).to_string(), "jmp 0xfffffffffffffffe");
}
}