openMSX
Dasm.cc
Go to the documentation of this file.
1#include "Dasm.hh"
2
3#include "DasmTables.hh"
4#include "MSXCPUInterface.hh"
5
6#include "narrow.hh"
7#include "strCat.hh"
8
9namespace openmsx {
10
11static constexpr char sign(uint8_t a)
12{
13 return (a & 128) ? '-' : '+';
14}
15
16static constexpr int abs(uint8_t a)
17{
18 return (a & 128) ? (256 - a) : a;
19}
20
21void appendAddrAsHex(std::string& output, uint16_t addr)
22{
23 strAppend(output, '#', hex_string<4>(addr));
24}
25
26std::optional<unsigned> instructionLength(std::span<const uint8_t> bin)
27{
28 if (bin.empty()) return {};
29 auto t = instr_len_tab[bin[0]];
30 if (t < 4) return t;
31
32 if (bin.size() < 2) return {};
33 t = instr_len_tab[64 * t + bin[1]];
34 if (t > bin.size()) return {};
35 return t;
36}
37
38unsigned dasm(std::span<const uint8_t> opcode, uint16_t pc, std::string& dest,
39 function_ref<void(std::string&, uint16_t)> appendAddr)
40{
41 const char* r = nullptr;
42
43 auto [s, i] = [&]() -> std::pair<const char*, unsigned> {
44 switch (opcode[0]) {
45 case 0xCB:
46 return {mnemonic_cb[opcode[1]], 2};
47 case 0xED:
48 return {mnemonic_ed[opcode[1]], 2};
49 case 0xDD:
50 case 0xFD:
51 r = (opcode[0] == 0xDD) ? "ix" : "iy";
52 if (opcode[1] != 0xCB) {
53 return {mnemonic_xx[opcode[1]], 2};
54 } else {
55 return {mnemonic_xx_cb[opcode[3]], 4};
56 }
57 default:
58 return {mnemonic_main[opcode[0]], 1};
59 }
60 }();
61
62 for (int j = 0; s[j]; ++j) {
63 switch (s[j]) {
64 case 'B':
65 strAppend(dest, '#', hex_string<2>(
66 static_cast<uint16_t>(opcode[i])));
67 i += 1;
68 break;
69 case 'R':
70 appendAddr(dest, uint16_t(pc + 2 + static_cast<int8_t>(opcode[i])));
71 i += 1;
72 break;
73 case 'A':
74 case 'W':
75 appendAddr(dest, opcode[i] + opcode[i + 1] * 256);
76 i += 2;
77 break;
78 case 'X':
79 strAppend(dest, '(', r, sign(opcode[i]), '#',
80 hex_string<2>(abs(opcode[i])), ')');
81 i += 1;
82 break;
83 case 'Y':
84 strAppend(dest, r, sign(opcode[2]), '#', hex_string<2>(abs(opcode[2])));
85 break;
86 case 'I':
87 dest += r;
88 break;
89 case '!':
90 case '@':
91 case '#': // invalid z80 instruction
92 strAppend(dest, "db");
93 for (auto k : xrange(i)) {
94 strAppend(dest, ((k == 0) ? ' ' : ','), '#', hex_string<2>(opcode[k]));
95 }
96 break;
97 case ' ': {
98 dest.resize(7, ' ');
99 break;
100 }
101 default:
102 dest += s[j];
103 break;
104 }
105 }
106 return i; // only used by unittest
107}
108
109std::span<uint8_t> fetchInstruction(const MSXCPUInterface& interface, uint16_t addr,
110 std::span<uint8_t, 4> buffer, EmuTime::param time)
111{
112 uint16_t idx = 0;
113 buffer[idx++] = interface.peekMem(addr, time);
114 auto len = instr_len_tab[buffer[0]];
115 if (len >= 4) {
116 buffer[idx++] = interface.peekMem(addr + 1, time);
117 len = instr_len_tab[64 * len + buffer[1]];
118 assert(len <= 4);
119 }
120 for (; idx < len; ++idx) {
121 buffer[idx] = interface.peekMem(addr + idx, time);
122 }
123 return buffer.subspan(0, len);
124}
125
126unsigned dasm(const MSXCPUInterface& interface, uint16_t pc, std::span<uint8_t, 4> buf,
127 std::string& dest, EmuTime::param time,
128 function_ref<void(std::string&, uint16_t)> appendAddr)
129{
130 auto opcodes = fetchInstruction(interface, pc, buf, time);
131 dasm(opcodes, pc, dest, appendAddr);
132 return narrow_cast<unsigned>(opcodes.size());
133}
134
135static unsigned instructionLength(const MSXCPUInterface& interface, uint16_t pc,
136 EmuTime::param time)
137{
138 auto op0 = interface.peekMem(pc, time);
139 auto t = instr_len_tab[op0];
140 if (t < 4) return t;
141
142 auto op1 = interface.peekMem(pc + 1, time);
143 return instr_len_tab[64 * t + op1];
144}
145
146// Calculates an address smaller or equal to the given 'addr' where there is for
147// sure a boundary. Though this is not (yet) necessarily the largest address
148// with this property.
149// In addition return the length of the instruction at the resulting address.
150static std::pair<uint16_t, unsigned> findGuaranteedBoundary(const MSXCPUInterface& interface, uint16_t addr, EmuTime::param time)
151{
152 if (addr < 3) {
153 // address 0 (the top) is a boundary
154 return {0, instructionLength(interface, 0, time)};
155 }
156 std::array<unsigned, 4> length;
157 for (auto i : xrange(4)) {
158 length[i] = instructionLength(interface, uint16_t(addr - i), time);
159 }
160
161 while (true) {
162 // Criteria:
163 // * Assuming a maximum instruction length of 4:
164 // * IF at 'addr - 1' starts an instruction with length 1
165 // * and at 'addr - 2' starts an instruction with length 1 or 2
166 // * and at 'addr - 3' starts an instruction with length 1 or 2 or 3
167 // * THEN 'addr' must be the start of an instruction.
168 if ((length[1] == 1) && (length[2] <= 2) && (length[3] <= 3)) {
169 return {addr, length[0]};
170 }
171 if (addr == 3) {
172 // address 0 (the top) is a boundary
173 return {0, length[3]};
174 }
175 --addr;
176 length[0] = length[1];
177 length[1] = length[2];
178 length[2] = length[3];
179 length[3] = instructionLength(interface, addr - 3, time);
180 }
181}
182
183static std::pair<uint16_t, unsigned> instructionBoundaryAndLength(
184 const MSXCPUInterface& interface, uint16_t addr, EmuTime::param time)
185{
186 // scan backwards for a guaranteed boundary
187 auto [candidate, len] = findGuaranteedBoundary(interface, addr, time);
188 // scan forwards for the boundary with the largest address
189 while (true) {
190 if ((candidate + len) > addr) return {candidate, len};
191 candidate += len;
192 len = instructionLength(interface, candidate, time);
193 }
194}
195
196uint16_t instructionBoundary(const MSXCPUInterface& interface, uint16_t addr,
197 EmuTime::param time)
198{
199 auto [result, len] = instructionBoundaryAndLength(interface, addr, time);
200 return result;
201}
202
203uint16_t nInstructionsBefore(const MSXCPUInterface& interface, uint16_t addr,
204 EmuTime::param time, int n)
205{
206 auto start = uint16_t(std::max(0, int(addr - 4 * n))); // for sure small enough
207 auto [tmp, len] = instructionBoundaryAndLength(interface, start, time);
208
209 std::vector<uint16_t> addresses;
210 while ((tmp + len) <= addr) {
211 addresses.push_back(tmp);
212 tmp += len;
213 len = instructionLength(interface, tmp, time);
214 }
215 addresses.push_back(tmp);
216
217 return addresses[std::max(0, narrow<int>(addresses.size()) - 1 - n)];
218}
219
220} // namespace openmsx
TclObject t
byte peekMem(word address, EmuTime::param time) const
Peek memory location.
unsigned size() const
Definition TclObject.hh:179
T length(const vecN< N, T > &x)
Definition gl_vec.hh:505
This file implemented 3 utility functions:
Definition Autofire.cc:11
const std::array< const char *, 256 > mnemonic_xx
std::span< uint8_t > fetchInstruction(const MSXCPUInterface &interface, uint16_t addr, std::span< uint8_t, 4 > buffer, EmuTime::param time)
Definition Dasm.cc:109
const std::array< const char *, 256 > mnemonic_ed
Definition DasmTables.cc:92
std::optional< unsigned > instructionLength(std::span< const uint8_t > bin)
Calculate the length of the instruction at the given address.
Definition Dasm.cc:26
const std::array< const char *, 256 > mnemonic_xx_cb
Definition DasmTables.cc:20
const std::array< const char *, 256 > mnemonic_cb
Definition DasmTables.cc:56
const std::array< uint8_t, std::size_t(3) *256 > instr_len_tab
const std::array< const char *, 256 > mnemonic_main
uint16_t instructionBoundary(const MSXCPUInterface &interface, uint16_t addr, EmuTime::param time)
This is only an heuristic to display instructions in a debugger disassembly view.
Definition Dasm.cc:196
uint16_t nInstructionsBefore(const MSXCPUInterface &interface, uint16_t addr, EmuTime::param time, int n)
Get the start address of the 'n'th instruction before the instruction containing the byte at the give...
Definition Dasm.cc:203
void appendAddrAsHex(std::string &output, uint16_t addr)
Definition Dasm.cc:21
unsigned dasm(std::span< const uint8_t > opcode, uint16_t pc, std::string &dest, function_ref< void(std::string &, uint16_t)> appendAddr)
Disassemble.
Definition Dasm.cc:38
void strAppend(std::string &result, Ts &&...ts)
Definition strCat.hh:752
constexpr auto xrange(T e)
Definition xrange.hh:132