use alloc::format;
use alloc::string::{String, ToString};
use alloc::vec::Vec;
use core::fmt;
use super::csr;
use super::isa::{self, Fmt, Op, Xlen};
use super::{F_NAMES, X_NAMES};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Missing {
Untranslated,
Unmapped,
}
impl fmt::Display for Missing {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Missing::Untranslated => f.write_str("not mapped"),
Missing::Unmapped => f.write_str("no memory"),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Disassembled {
pub addr: u64,
pub len: u64,
pub encoding: u32,
pub text: String,
pub hole: Option<Missing>,
}
impl Disassembled {
#[must_use]
pub fn missing(addr: u64, why: Missing) -> Disassembled {
Disassembled {
addr,
len: 2,
encoding: 0,
text: format!("?? <{why}>"),
hole: Some(why),
}
}
}
impl fmt::Display for Disassembled {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.hole.is_some() {
return write!(f, "{:016x}: {}", self.addr, self.text);
}
if self.len == 2 {
write!(
f,
"{:016x}: {:04x} {}",
self.addr, self.encoding, self.text
)
} else {
write!(
f,
"{:016x}: {:08x} {}",
self.addr, self.encoding, self.text
)
}
}
}
fn xn(i: u32) -> &'static str {
X_NAMES[(i & 31) as usize]
}
fn fnm(i: u32) -> &'static str {
F_NAMES[(i & 31) as usize]
}
fn imm(v: i64) -> String {
if v.abs() < 10 {
format!("{v}")
} else if v < 0 {
format!("-0x{:x}", v.unsigned_abs())
} else {
format!("0x{v:x}")
}
}
fn fence_set(bits: u32) -> String {
let mut s = String::new();
for (bit, letter) in [(8, 'i'), (4, 'o'), (2, 'r'), (1, 'w')] {
if bits & bit != 0 {
s.push(letter);
}
}
if s.is_empty() {
s.push_str("none");
}
s
}
fn rm_suffix(word: u32) -> &'static str {
match isa::funct3(word) {
0 => ", rne",
1 => ", rtz",
2 => ", rdn",
3 => ", rup",
4 => ", rmm",
7 => "",
_ => ", <reserved>",
}
}
fn ordering_suffix(word: u32) -> &'static str {
match (isa::aq(word), isa::rl(word)) {
(false, false) => "",
(true, false) => ".aq",
(false, true) => ".rl",
(true, true) => ".aqrl",
}
}
#[must_use]
pub fn format_word(word: u32, pc: u64, xlen: Xlen) -> String {
let Some(insn) = isa::decode(word, xlen) else {
return format!(".word 0x{word:08x}");
};
format_decoded(insn.op, insn.fmt, insn.op.mnemonic(), word, pc)
}
fn format_decoded(op: Op, fmt: Fmt, mnemonic: &str, word: u32, pc: u64) -> String {
let rd = isa::rd(word);
let rs1 = isa::rs1(word);
let rs2 = isa::rs2(word);
let rs3 = isa::rs3(word);
match fmt {
Fmt::R => format!("{mnemonic} {}, {}, {}", xn(rd), xn(rs1), xn(rs2)),
Fmt::I => format!(
"{mnemonic} {}, {}, {}",
xn(rd),
xn(rs1),
imm(isa::imm_i(word))
),
Fmt::Shift => format!("{mnemonic} {}, {}, {}", xn(rd), xn(rs1), isa::shamt(word)),
Fmt::Load => format!(
"{mnemonic} {}, {}({})",
xn(rd),
imm(isa::imm_i(word)),
xn(rs1)
),
Fmt::Store => format!(
"{mnemonic} {}, {}({})",
xn(rs2),
imm(isa::imm_s(word)),
xn(rs1)
),
Fmt::Branch => format!(
"{mnemonic} {}, {}, 0x{:x}",
xn(rs1),
xn(rs2),
pc.wrapping_add(isa::imm_b(word) as u64)
),
Fmt::U => format!("{mnemonic} {}, 0x{:x}", xn(rd), isa::imm_u(word) as u64),
Fmt::Jump => format!(
"{mnemonic} {}, 0x{:x}",
xn(rd),
pc.wrapping_add(isa::imm_j(word) as u64)
),
Fmt::Fence => format!(
"{mnemonic} {}, {}",
fence_set((word >> 24) & 0xf),
fence_set((word >> 20) & 0xf)
),
Fmt::None => mnemonic.to_string(),
Fmt::Sfence => format!("{mnemonic} {}, {}", xn(rs1), xn(rs2)),
Fmt::Csr => format!(
"{mnemonic} {}, {}, {}",
xn(rd),
csr_operand(isa::csr(word)),
xn(rs1)
),
Fmt::CsrImm => format!(
"{mnemonic} {}, {}, {}",
xn(rd),
csr_operand(isa::csr(word)),
rs1
),
Fmt::AmoLoad => format!(
"{mnemonic}{} {}, ({})",
ordering_suffix(word),
xn(rd),
xn(rs1)
),
Fmt::Amo => format!(
"{mnemonic}{} {}, {}, ({})",
ordering_suffix(word),
xn(rd),
xn(rs2),
xn(rs1)
),
Fmt::FpLoad => format!(
"{mnemonic} {}, {}({})",
fnm(rd),
imm(isa::imm_i(word)),
xn(rs1)
),
Fmt::FpStore => format!(
"{mnemonic} {}, {}({})",
fnm(rs2),
imm(isa::imm_s(word)),
xn(rs1)
),
Fmt::FpR => {
let suffix = if fp_has_rounding(op) {
rm_suffix(word)
} else {
""
};
format!("{mnemonic} {}, {}, {}{suffix}", fnm(rd), fnm(rs1), fnm(rs2))
}
Fmt::FpUnary => format!("{mnemonic} {}, {}{}", fnm(rd), fnm(rs1), rm_suffix(word)),
Fmt::FpR4 => format!(
"{mnemonic} {}, {}, {}, {}{}",
fnm(rd),
fnm(rs1),
fnm(rs2),
fnm(rs3),
rm_suffix(word)
),
Fmt::FpCmp => format!("{mnemonic} {}, {}, {}", xn(rd), fnm(rs1), fnm(rs2)),
Fmt::FpToInt => {
let suffix = if fp_has_rounding(op) {
rm_suffix(word)
} else {
""
};
format!("{mnemonic} {}, {}{suffix}", xn(rd), fnm(rs1))
}
Fmt::FpFromInt => {
let suffix = if fp_has_rounding(op) {
rm_suffix(word)
} else {
""
};
format!("{mnemonic} {}, {}{suffix}", fnm(rd), xn(rs1))
}
}
}
fn fp_has_rounding(op: Op) -> bool {
!matches!(
op,
Op::FmvXW
| Op::FmvWX
| Op::FmvXD
| Op::FmvDX
| Op::FclassS
| Op::FclassD
| Op::FsgnjS
| Op::FsgnjnS
| Op::FsgnjxS
| Op::FsgnjD
| Op::FsgnjnD
| Op::FsgnjxD
| Op::FminS
| Op::FmaxS
| Op::FminD
| Op::FmaxD
)
}
fn csr_operand(number: u32) -> String {
csr::csr_name(number).map_or_else(|| format!("0x{number:x}"), ToString::to_string)
}
#[must_use]
pub fn disassemble_one(
addr: u64,
xlen: Xlen,
read: &mut impl FnMut(u64) -> Option<u16>,
) -> Option<Disassembled> {
let low = read(addr)?;
if !isa::is_32bit(low) {
let text = match isa::decode_compressed(low, xlen) {
Some(c) => match isa::expand(low, xlen) {
Some(word) => {
match isa::decode(word, xlen) {
Some(insn) => {
format_decoded(insn.op, insn.fmt, c.op.mnemonic(), word, addr)
}
None => c.op.mnemonic().to_string(),
}
}
None => format!("{} <reserved>", c.op.mnemonic()),
},
None => format!(".half 0x{low:04x}"),
};
return Some(Disassembled {
addr,
len: 2,
encoding: u32::from(low),
text,
hole: None,
});
}
let high = read(addr.wrapping_add(2))?;
let word = u32::from(low) | (u32::from(high) << 16);
Some(Disassembled {
addr,
len: 4,
encoding: word,
text: format_word(word, addr, xlen),
hole: None,
})
}
#[must_use]
pub fn disassemble_run(
addr: u64,
count: usize,
xlen: Xlen,
mut read: impl FnMut(u64) -> Result<u16, Missing>,
) -> Vec<Disassembled> {
let mut out = Vec::with_capacity(count);
let mut at = addr;
for _ in 0..count {
let mut why = None;
let one = disassemble_one(at, xlen, &mut |a| match read(a) {
Ok(half) => Some(half),
Err(reason) => {
why = why.or(Some(reason));
None
}
});
let one =
one.unwrap_or_else(|| Disassembled::missing(at, why.unwrap_or(Missing::Unmapped)));
at = at.wrapping_add(one.len);
out.push(one);
}
out
}
#[cfg(test)]
mod tests {
use super::*;
fn d(word: u32) -> String {
format_word(word, 0x8000_0000, Xlen::Rv64)
}
#[test]
fn integer_instructions_print_abi_names() {
assert_eq!(d(0x00c5_8533), "add a0, a1, a2");
assert_eq!(d(0xffb5_0513), "addi a0, a0, -5");
assert_eq!(d(0x0080_a503), "lw a0, 8(ra)");
assert_eq!(d(0x00b1_2423), "sw a1, 8(sp)");
assert_eq!(d(0x0000_0013), "addi zero, zero, 0");
}
#[test]
fn branches_and_jumps_resolve_their_targets() {
let word = 0x00b5_0463;
assert_eq!(d(word), "beq a0, a1, 0x80000008");
let word = 0x0100_00ef;
assert_eq!(d(word), "jal ra, 0x80000010");
}
#[test]
fn system_instructions_name_their_csrs() {
assert_eq!(d(0x0000_0073), "ecall");
assert_eq!(d(0x3020_0073), "mret");
assert_eq!(d(0x3400_2573), "csrrs a0, mscratch, zero");
assert_eq!(d(0x3000_5073), "csrrwi zero, mstatus, 0");
}
#[test]
fn atomics_show_their_ordering_bits() {
assert_eq!(d(0x00b6_252f), "amoadd.w a0, a1, (a2)");
assert_eq!(d(0x06b6_252f), "amoadd.w.aqrl a0, a1, (a2)");
assert_eq!(d(0x1005_a52f), "lr.w a0, (a1)");
}
#[test]
fn floating_point_prints_its_rounding_mode_only_when_static() {
assert_eq!(d(0x02c5_f553), "fadd.d fa0, fa1, fa2");
assert_eq!(d(0x02c5_9553), "fadd.d fa0, fa1, fa2, rtz");
assert_eq!(d(0x22c5_8553), "fsgnj.d fa0, fa1, fa2");
assert_eq!(d(0xe205_9553), "fclass.d a0, fa1");
}
#[test]
fn fences_decode_their_ordering_sets() {
assert_eq!(d(0x0ff0_000f), "fence iorw, iorw");
assert_eq!(d(0x0330_000f), "fence rw, rw");
}
#[test]
fn an_unknown_encoding_is_printed_as_data() {
assert!(d(0xffff_ffff).starts_with(".word"));
}
#[test]
fn compressed_instructions_keep_their_own_mnemonic() {
let mut halves = [0x0505u16, 0x8082].into_iter();
let out = disassemble_run(0x8000_0000, 2, Xlen::Rv64, |_| {
halves.next().ok_or(Missing::Unmapped)
});
assert_eq!(out[0].len, 2);
assert_eq!(out[0].text, "c.addi a0, a0, 1");
assert_eq!(out[1].text, "c.jr zero, 0(ra)");
}
#[test]
fn a_run_carries_on_past_where_memory_stops_and_says_why() {
let out = disassemble_run(0, 4, Xlen::Rv64, |addr| {
if addr < 4 {
Ok(0x0013)
} else {
Err(Missing::Untranslated)
}
});
assert_eq!(out.len(), 4);
assert_eq!(out[0].len, 4);
assert_eq!(out[0].hole, None);
for one in &out[1..] {
assert_eq!(one.hole, Some(Missing::Untranslated));
assert_eq!(one.len, 2, "a hole advances by the narrowest instruction");
}
assert_eq!(out[1].addr, 4);
assert_eq!(out[2].addr, 6);
}
#[test]
fn the_display_form_lines_up() {
let one = Disassembled {
addr: 0x8000_0000,
len: 4,
encoding: 0x0000_0013,
text: "addi zero, zero, 0".into(),
hole: None,
};
assert_eq!(
one.to_string(),
"0000000080000000: 00000013 addi zero, zero, 0"
);
}
#[test]
fn a_hole_names_the_reason_it_is_a_hole() {
let one = Disassembled::missing(0x8000_0000, Missing::Untranslated);
assert_eq!(
one.to_string(),
"0000000080000000: ?? <not mapped>"
);
let one = Disassembled::missing(0x8000_0000, Missing::Unmapped);
assert_eq!(
one.to_string(),
"0000000080000000: ?? <no memory>"
);
}
#[test]
fn every_table_row_disassembles_without_panicking() {
for insn in isa::TABLE {
let word = insn.bits;
let text = format_decoded(insn.op, insn.fmt, insn.op.mnemonic(), word, 0);
assert!(
text.starts_with(insn.op.mnemonic()),
"{} formatted as {text}",
insn.op.mnemonic()
);
}
}
}