1use core::fmt;
11
12use super::inst::{Inst, Mnemonic};
13use super::operand::{Mem, Operand};
14
15pub(crate) fn hex(f: &mut fmt::Formatter<'_>, value: i64) -> fmt::Result {
17 if value < 0 {
18 write!(f, "-0x{:x}", value.unsigned_abs())
19 } else {
20 write!(f, "0x{value:x}")
21 }
22}
23
24pub(crate) fn signed_hex(f: &mut fmt::Formatter<'_>, value: i64) -> fmt::Result {
26 if value >= 0 {
27 f.write_str("+")?;
28 }
29 hex(f, value)
30}
31
32fn address(f: &mut fmt::Formatter<'_>, m: &Mem) -> fmt::Result {
34 f.write_str("[")?;
35 let disp = i64::from(m.disp());
36 let mut any = false;
37 if m.is_rip_relative() {
38 f.write_str("rip")?;
39 any = true;
40 }
41 if let Some(base) = m.base() {
42 f.write_str(base.name())?;
43 any = true;
44 }
45 if let Some(index) = m.index() {
46 if any {
47 f.write_str("+")?;
48 }
49 write!(f, "{}*{}", index.name(), m.scale().factor())?;
50 any = true;
51 }
52 if !any {
53 hex(f, disp)?;
54 } else if disp != 0 {
55 signed_hex(f, disp)?;
56 }
57 f.write_str("]")
58}
59
60fn operand(f: &mut fmt::Formatter<'_>, op: &Operand, sized: bool, ip: Option<u64>) -> fmt::Result {
61 match op {
62 Operand::Reg(r) => f.write_str(r.name()),
63 Operand::Mem(m) => {
64 if sized {
65 write!(f, "{} ptr ", m.size().keyword())?;
66 }
67 address(f, m)
68 }
69 Operand::Imm(v) => hex(f, *v),
70 Operand::Rel(d) => match ip {
71 Some(ip) => write!(f, "0x{:x}", ip.wrapping_add(*d as u64)),
72 None => {
73 f.write_str("$")?;
74 signed_hex(f, *d)
75 }
76 },
77 }
78}
79
80fn inst(f: &mut fmt::Formatter<'_>, i: &Inst, ip: Option<u64>) -> fmt::Result {
81 write!(f, "{}", i.mnemonic())?;
82 let sized = i.mnemonic() != Mnemonic::Lea;
84 for (n, op) in i.operands().iter().enumerate() {
85 f.write_str(if n == 0 { " " } else { ", " })?;
86 operand(f, op, sized, ip)?;
87 }
88 Ok(())
89}
90
91impl fmt::Display for Inst {
92 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
93 padded(f, self, None)
94 }
95}
96
97fn padded(f: &mut fmt::Formatter<'_>, i: &Inst, ip: Option<u64>) -> fmt::Result {
100 if f.width().is_none() {
101 return inst(f, i, ip);
102 }
103 f.pad(&alloc::format!("{}", Unpadded(i, ip)))
104}
105
106struct Unpadded<'a>(&'a Inst, Option<u64>);
108
109impl fmt::Display for Unpadded<'_> {
110 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
111 inst(f, self.0, self.1)
112 }
113}
114
115impl fmt::Display for Operand {
116 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
117 operand(f, self, true, None)
118 }
119}
120
121impl fmt::Display for Mem {
122 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
123 write!(f, "{} ptr ", self.size().keyword())?;
124 address(f, self)
125 }
126}
127
128impl fmt::Debug for Mem {
129 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
130 write!(f, "Mem({self})")
131 }
132}
133
134#[derive(Clone, Copy, Debug)]
147pub struct DisplayAt<'a> {
148 inst: &'a Inst,
149 ip: u64,
150}
151
152impl Inst {
153 #[must_use]
165 pub fn display_at(&self, ip: u64) -> DisplayAt<'_> {
166 DisplayAt { inst: self, ip }
167 }
168}
169
170impl fmt::Display for DisplayAt<'_> {
171 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
172 padded(f, self.inst, Some(self.ip))
173 }
174}
175
176#[cfg(test)]
177mod tests {
178 use alloc::string::ToString;
179
180 use super::*;
181 use crate::x86_64::{Reg, Scale, Size};
182
183 #[test]
184 fn test_hex_extremes() {
185 assert_eq!(Operand::Imm(i64::MIN).to_string(), "-0x8000000000000000");
186 assert_eq!(Operand::Imm(i64::MAX).to_string(), "0x7fffffffffffffff");
187 assert_eq!(Operand::Imm(0).to_string(), "0x0");
188 assert_eq!(Operand::Rel(0).to_string(), "$+0x0");
189 assert_eq!(Operand::Rel(-1).to_string(), "$-0x1");
190 }
191
192 #[test]
193 fn test_memory_text() {
194 let q = Size::Qword;
195 assert_eq!(Mem::new(q, Reg::RAX).to_string(), "qword ptr [rax]");
196 assert_eq!(Mem::rip(q, 0).to_string(), "qword ptr [rip]");
197 assert_eq!(Mem::absolute(q, -16).to_string(), "qword ptr [-0x10]");
198 assert_eq!(Mem::absolute(q, 0).to_string(), "qword ptr [0x0]");
199 let idx = Mem::absolute(q, 0).with_index(Reg::R12, Scale::X1);
200 assert_eq!(idx.to_string(), "qword ptr [r12*1]");
201 let full = Mem::new(Size::Byte, Reg::R13)
202 .with_index(Reg::RBP, Scale::X2)
203 .with_disp(i32::MIN);
204 assert_eq!(full.to_string(), "byte ptr [r13+rbp*2-0x80000000]");
205 }
206
207 #[test]
208 fn test_lea_has_no_size() {
209 let lea = Inst::new(
210 Mnemonic::Lea,
211 [
212 Reg::RAX.into(),
213 Mem::new(Size::Qword, Reg::RBX).with_disp(8).into(),
214 ],
215 );
216 assert_eq!(lea.to_string(), "lea rax, [rbx+0x8]");
217 }
218
219 #[test]
220 fn test_padding_is_honoured() {
221 let i = Inst::new(Mnemonic::Ret, []);
222 assert_eq!(alloc::format!("[{i:>5}]"), "[ ret]");
223 assert_eq!(alloc::format!("[{:<6}]", i.display_at(0)), "[ret ]");
224 }
225
226 #[test]
227 fn test_display_at_wraps() {
228 let j = Inst::new(Mnemonic::Jmp, [Operand::Rel(-2)]);
229 assert_eq!(j.display_at(0).to_string(), "jmp 0xfffffffffffffffe");
230 }
231}