1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
/*
    z80emu: ZiLOG Z80 microprocessor emulation library.
    Copyright (C) 2019-2024  Rafal Michalski

    For the full copyright notice, see the lib.rs file.
*/
//! Utilities for disassembling Z80 machine code.
use core::fmt;
use crate::*;

type TsClock = host::TsCounter<u32>;

#[derive(Clone, Debug, Default)]
struct Mem<'a>(u16, &'a [u8]);

impl Io for Mem<'_> {
    type Timestamp = u32;
    type WrIoBreak = ();
    type RetiBreak = ();
}

impl Memory for Mem<'_> {
    type Timestamp = u32;
    fn read_debug(&self, addr: u16) -> u8 {
        *self.1.get(addr.wrapping_sub(self.0) as usize).unwrap_or(&0xff)
    }
}

/// Interprets a given `memory` chunk as machine code instructions.
///
/// * `pc` - the address of the first byte of memory chunk given.
/// * `memory` - a chunk of memory to disassemble.
/// * `debug` - a function called for each interpreted instruction with a single argument: [CpuDebug].
///
/// After each instruction the address is increased by the number of bytes occupied by this
/// instruction until the end of memory is reached.
///
/// If `debug` closure returns `Err(E)` disassembling will stop and `Err(E)` will be returned immediately.
///
/// *NOTE*: `debug` is being invoked only for multi-byte instructions which entirely fit
/// in the provided memory.
pub fn disasm_memory<C: Cpu, F: FnMut(CpuDebug) -> Result<(), E>, E>(
        pc: u16,
        memory: &[u8],
        mut debug: F
    ) -> Result<(), E>
{
    let mut cpu = C::default();
    let mut cursor: usize = 0;
    let mut dbg: Option<CpuDebug> = None;
    let mut tsc = TsClock::default();
    while cursor < memory.len() {
        let pc = pc.wrapping_add(cursor as u16);
        cpu.set_pc(pc);
        let _ = cpu.execute_next(&mut Mem(pc, &memory[cursor..]), &mut tsc,
            Some(|deb: CpuDebug| {
                cursor += deb.code.len();
                if deb.prefix.is_some() {
                    cursor -= 1;
                }
                dbg = Some(deb);
            })
        );
        if cursor > memory.len() {
            break;
        }
        else if let Some(deb) = dbg.take() {
            debug(deb)?;
        }
        else if cpu.is_after_prefix() {
            cursor += 1;
        }
        else { // reset after halt
            cpu.reset();
        }
    }
    Ok(())
}

/// Interprets a given `memory` chunk as a machine code instruction.
///
/// * `pc` - the address of the first byte of memory chunk given.
/// * `memory` - a chunk of memory to disassemble.
///
/// When an instruction is found the function returns `Some(CpuDebug)`. Otherwise returns `None`.
pub fn disasm_memory_once<C: Cpu>(
        pc: u16,
        memory: &[u8]
    ) -> Option<CpuDebug>
{
    let mut dbg = None;
    let _ = disasm_memory::<C,_,_>(pc, memory, |deb| {
        dbg = Some(deb);
        Err(()) /* break */
    });
    dbg
}

/// Instead of providing your own `debug` function to [disasm_memory], provide a [fmt::Write]
/// implementation which will get disassembled instructions written as lines of text.
pub fn disasm_memory_write_text<C: Cpu, W: fmt::Write>(
        pc: u16,
        mem: &[u8],
        mut f: W
    ) -> fmt::Result
{
    disasm_memory::<C,_,_>(pc, mem, |deb|
        writeln!(f, "{:x}", deb)
    )
}

/// Prints disassembled instructions to stdout as lines of text.
#[cfg(feature = "std")]
pub fn disasm_memory_print_text<C: Cpu>(pc: u16, mem: &[u8]) {
    let _ = disasm_memory::<C,_,()>(pc, mem, |deb| {
        println!("{:x}", deb);
        Ok(())
    });
}

#[cfg(test)]
mod tests {
    use super::*;
    use arrayvec::ArrayString;

    #[test]
    fn disasm_works() {
        let mem = [0x09, 0xdd, 0xfd, 0xdd, 0x09, 0x76, 0, 0xcb];
        let mut s = ArrayString::<128>::new();
        disasm_memory_write_text::<Z80NMOS,_>(0xfffe, &mem, &mut s).unwrap();
        assert_eq!(&s.as_ref(), &concat!(
            "fffeh ADD  HL, BC         [09]\n",
            "0001h ADD  IX, BC         [dd, 09]\n",
            "0003h HALT                [76]\n",
            "0004h NOP                 [00]\n"));
        let mem = [0xdd, 0xcb, 0x00, 0x00, 0xfd, 0x00];
        let mut s = ArrayString::<128>::new();
        disasm_memory_write_text::<Z80NMOS,_>(0xffff, &mem, &mut s).unwrap();
        assert_eq!(&s.as_ref(), &concat!(
            "ffffh RLC  (IX+00h), B    [dd, cb, 00, 00]\n",
            "0004h NOP                 [00]\n"));
    }
}