1 | //============================================================================ |
2 | // |
3 | // SSSS tt lll lll |
4 | // SS SS tt ll ll |
5 | // SS tttttt eeee ll ll aaaa |
6 | // SSSS tt ee ee ll ll aa |
7 | // SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator" |
8 | // SS SS tt ee ll ll aa aa |
9 | // SSSS ttt eeeee llll llll aaaaa |
10 | // |
11 | // Copyright (c) 1995-2019 by Bradford W. Mott, Stephen Anthony |
12 | // and the Stella Team |
13 | // |
14 | // See the file "License.txt" for information on usage and redistribution of |
15 | // this file, and for a DISCLAIMER OF ALL WARRANTIES. |
16 | //============================================================================ |
17 | |
18 | #ifndef CART_DEBUG_HXX |
19 | #define CART_DEBUG_HXX |
20 | |
21 | class Settings; |
22 | class CartDebugWidget; |
23 | |
24 | // Function type for CartDebug instance methods |
25 | class CartDebug; |
26 | using CartMethod = int (CartDebug::*)(); |
27 | |
28 | #include <map> |
29 | #include <set> |
30 | #include <list> |
31 | |
32 | #include "bspf.hxx" |
33 | #include "DebuggerSystem.hxx" |
34 | |
35 | class CartState : public DebuggerState |
36 | { |
37 | public: |
38 | ByteArray ram; // The actual data values |
39 | ShortArray rport; // Address for reading from RAM |
40 | ShortArray wport; // Address for writing to RAM |
41 | string bank; // Current banking layout |
42 | }; |
43 | |
44 | class CartDebug : public DebuggerSystem |
45 | { |
46 | // The disassembler needs special access to this class |
47 | friend class DiStella; |
48 | |
49 | public: |
50 | enum DisasmType { |
51 | NONE = 0, |
52 | REFERENCED = 1 << 0, /* 0x01, code somewhere in the program references it, |
53 | i.e. LDA $F372 referenced $F372 */ |
54 | VALID_ENTRY = 1 << 1, /* 0x02, addresses that can have a label placed in front of it. |
55 | A good counterexample would be "FF00: LDA $FE00"; $FF01 |
56 | would be in the middle of a multi-byte instruction, and |
57 | therefore cannot be labelled. */ |
58 | |
59 | // The following correspond to specific types that can be set within the |
60 | // debugger, or specified in a Distella cfg file, and are listed in order |
61 | // of decreasing hierarchy |
62 | // |
63 | CODE = 1 << 7, // 0x80, disassemble-able code segments |
64 | TCODE = 1 << 6, // 0x40, (tentative) disassemble-able code segments |
65 | GFX = 1 << 5, // 0x20, addresses loaded into GRPx registers |
66 | PGFX = 1 << 4, // 0x10, addresses loaded into PFx registers |
67 | DATA = 1 << 3, // 0x08, addresses loaded into registers other than GRPx / PFx |
68 | ROW = 1 << 2, // 0x04, all other addresses |
69 | // special type for poke() |
70 | WRITE = TCODE // 0x40, address written to |
71 | }; |
72 | struct DisassemblyTag { |
73 | DisasmType type; |
74 | uInt16 address; |
75 | string label; |
76 | string disasm; |
77 | string ccount; |
78 | string ctotal; |
79 | string bytes; |
80 | bool hllabel; |
81 | }; |
82 | using DisassemblyList = vector<DisassemblyTag>; |
83 | struct Disassembly { |
84 | DisassemblyList list; |
85 | int fieldwidth; |
86 | }; |
87 | |
88 | // Determine 'type' of address (ie, what part of the system accessed) |
89 | enum class AddrType { TIA, IO, ZPRAM, ROM }; |
90 | AddrType addressType(uInt16 addr) const; |
91 | |
92 | public: |
93 | CartDebug(Debugger& dbg, Console& console, const OSystem& osystem); |
94 | virtual ~CartDebug() = default; |
95 | |
96 | const DebuggerState& getState() override; |
97 | const DebuggerState& getOldState() override { return myOldState; } |
98 | |
99 | void saveOldState() override; |
100 | string toString() override; |
101 | |
102 | // Used to get/set the debug widget, which contains cart-specific |
103 | // functionality |
104 | CartDebugWidget* getDebugWidget() const { return myDebugWidget; } |
105 | void setDebugWidget(CartDebugWidget* w) { myDebugWidget = w; } |
106 | |
107 | // Return the base (= non-mirrored) address of the last CPU read |
108 | int lastReadBaseAddress(); |
109 | // Return the base (= non-mirrored) address of the last CPU write |
110 | int lastWriteBaseAddress(); |
111 | |
112 | // The following two methods are meant to be used together |
113 | // First, a call is made to disassemble(), which updates the disassembly |
114 | // list; it will figure out when an actual complete disassembly is |
115 | // required, and when the previous results can be used |
116 | // |
117 | // Later, successive calls to disassemblyList() simply return the |
118 | // previous results; no disassembly is done in this case |
119 | /** |
120 | Disassemble from the given address using the Distella disassembler |
121 | Address-to-label mappings (and vice-versa) are also determined here |
122 | |
123 | @param force Force a re-disassembly, even if the state hasn't changed |
124 | |
125 | @return True if disassembly changed from previous call, else false |
126 | */ |
127 | bool disassemble(bool force = false); |
128 | |
129 | /** |
130 | Get the results from the most recent call to disassemble() |
131 | */ |
132 | const Disassembly& disassembly() const { return myDisassembly; } |
133 | |
134 | /** |
135 | Determine the line in the disassembly that corresponds to the given address. |
136 | |
137 | @param address The address to search for |
138 | |
139 | @return Line number of the address, else -1 if no such address exists |
140 | */ |
141 | int addressToLine(uInt16 address) const; |
142 | |
143 | /** |
144 | Disassemble from the starting address the specified number of lines. |
145 | Note that automatic code determination is turned off for this method; |
146 | |
147 | @param start The start address for disassembly |
148 | @param lines The number of disassembled lines to generate |
149 | |
150 | @return The disassembly represented as a string |
151 | */ |
152 | string disassemble(uInt16 start, uInt16 lines) const; |
153 | |
154 | /** |
155 | Add a directive to the disassembler. Directives are basically overrides |
156 | for the automatic code determination algorithm in Distella, since some |
157 | things can't be automatically determined. For now, these directives |
158 | have exactly the same syntax as in a distella configuration file. |
159 | |
160 | @param type Currently, CODE/DATA/GFX are supported |
161 | @param start The start address (inclusive) to mark with the given type |
162 | @param end The end address (inclusive) to mark with the given type |
163 | @param bank Bank to which these directive apply (0 indicated current bank) |
164 | |
165 | @return True if directive was added, else false if it was removed |
166 | */ |
167 | bool addDirective(CartDebug::DisasmType type, uInt16 start, uInt16 end, |
168 | int bank = -1); |
169 | |
170 | // The following are convenience methods that query the cartridge object |
171 | // for the desired information. |
172 | /** |
173 | Get the current bank in use by the cartridge |
174 | (non-const because of use in YaccParser) |
175 | */ |
176 | int getBank(uInt16 addr); |
177 | |
178 | |
179 | int getPCBank(); |
180 | |
181 | /** |
182 | Get the total number of banks supported by the cartridge. |
183 | */ |
184 | int bankCount() const; |
185 | |
186 | /** |
187 | Add a label and associated address. |
188 | Labels that reference either TIA or RIOT spaces will not be processed. |
189 | */ |
190 | bool addLabel(const string& label, uInt16 address); |
191 | |
192 | /** |
193 | Remove the given label and its associated address. |
194 | Labels that reference either TIA or RIOT spaces will not be processed. |
195 | */ |
196 | bool removeLabel(const string& label); |
197 | |
198 | /** |
199 | Accessor methods for labels and addresses |
200 | |
201 | The mapping from address to label can be one-to-many (ie, an |
202 | address can have different labels depending on its context, and |
203 | whether its being read or written; if isRead is true, the context |
204 | is a read, else it's a write |
205 | If places is not -1 and a label hasn't been defined, return a |
206 | formatted hexidecimal address |
207 | */ |
208 | bool getLabel(ostream& buf, uInt16 addr, bool isRead, int places = -1) const; |
209 | string getLabel(uInt16 addr, bool isRead, int places = -1) const; |
210 | int getAddress(const string& label) const; |
211 | |
212 | /** |
213 | Load constants from list file (as generated by DASM). |
214 | */ |
215 | string loadListFile(); |
216 | |
217 | /** |
218 | Load user equates from symbol file (as generated by DASM). |
219 | */ |
220 | string loadSymbolFile(); |
221 | |
222 | /** |
223 | Load/save Distella config files (Distella directives) |
224 | */ |
225 | string loadConfigFile(); |
226 | string saveConfigFile(); |
227 | |
228 | /** |
229 | Save disassembly and ROM file |
230 | */ |
231 | string saveDisassembly(); |
232 | string saveRom(); |
233 | |
234 | /** |
235 | Show Distella directives (both set by the user and determined by Distella) |
236 | for the given bank (or all banks, if no bank is specified). |
237 | */ |
238 | string listConfig(int bank = -1); |
239 | |
240 | /** |
241 | Clear Distella directives (set by the user) for the given bank |
242 | (or all banks, if no bank is specified.) |
243 | */ |
244 | string clearConfig(int bank = -1); |
245 | |
246 | /** |
247 | Methods used by the command parser for tab-completion |
248 | In this case, return completions from the equate list(s) |
249 | */ |
250 | void getCompletions(const char* in, StringList& list) const; |
251 | |
252 | // Convert given address to corresponding disassembly type and append to buf |
253 | void addressTypeAsString(ostream& buf, uInt16 addr) const; |
254 | |
255 | private: |
256 | using AddrToLabel = std::map<uInt16, string>; |
257 | using LabelToAddr = std::map<string, uInt16, |
258 | std::function<bool(const string&, const string&)>>; |
259 | |
260 | using AddrTypeArray = std::array<uInt8, 0x1000>; |
261 | |
262 | struct DirectiveTag { |
263 | DisasmType type; |
264 | uInt16 start; |
265 | uInt16 end; |
266 | }; |
267 | using AddressList = std::list<uInt16>; |
268 | using DirectiveList = std::list<DirectiveTag>; |
269 | |
270 | struct BankInfo { |
271 | uInt16 start; // start of address space |
272 | uInt16 end; // end of address space |
273 | uInt16 offset; // ORG value |
274 | size_t size; // size of a bank (in bytes) |
275 | AddressList addressList; // addresses which PC has hit |
276 | DirectiveList directiveList; // overrides for automatic code determination |
277 | |
278 | BankInfo() : start(0), end(0), offset(0), size(0) { } |
279 | }; |
280 | |
281 | // Address type information determined by Distella |
282 | AddrTypeArray myDisLabels, myDisDirectives; |
283 | |
284 | // Information on equates used in the disassembly |
285 | struct ReservedEquates { |
286 | std::array<bool, 16> TIARead; |
287 | std::array<bool, 64> TIAWrite; |
288 | std::array<bool, 24> IOReadWrite; |
289 | std::array<bool, 128> ZPRAM; |
290 | AddrToLabel Label; |
291 | bool breakFound; |
292 | }; |
293 | ReservedEquates myReserved; |
294 | |
295 | // Actually call DiStella to fill the DisassemblyList structure |
296 | // Return whether the search address was actually in the list |
297 | bool fillDisassemblyList(BankInfo& bankinfo, uInt16 search); |
298 | |
299 | // Analyze of bank of ROM, generating a list of Distella directives |
300 | // based on its disassembly |
301 | void getBankDirectives(ostream& buf, BankInfo& info) const; |
302 | |
303 | // Get disassembly enum type from 'flags', taking precendence into account |
304 | DisasmType disasmTypeAbsolute(uInt8 flags) const; |
305 | |
306 | // Convert disassembly enum type to corresponding string and append to buf |
307 | void disasmTypeAsString(ostream& buf, DisasmType type) const; |
308 | |
309 | // Convert all disassembly types in 'flags' to corresponding string and |
310 | // append to buf |
311 | void disasmTypeAsString(ostream& buf, uInt8 flags) const; |
312 | |
313 | private: |
314 | const OSystem& myOSystem; |
315 | |
316 | CartState myState; |
317 | CartState myOldState; |
318 | |
319 | CartDebugWidget* myDebugWidget; |
320 | |
321 | // A complete record of relevant diassembly information for each bank |
322 | vector<BankInfo> myBankInfo; |
323 | |
324 | // Used for the disassembly display, and mapping from addresses |
325 | // to corresponding lines of text in that display |
326 | Disassembly myDisassembly; |
327 | std::map<uInt16, int> myAddrToLineList; |
328 | bool myAddrToLineIsROM; |
329 | |
330 | // Mappings from label to address (and vice versa) for items |
331 | // defined by the user (either through a DASM symbol file or manually |
332 | // from the commandline in the debugger) |
333 | AddrToLabel myUserLabels; |
334 | LabelToAddr myUserAddresses; |
335 | |
336 | // Mappings from label to address (and vice versa) for constants |
337 | // defined through a DASM lst file |
338 | // AddrToLabel myUserCLabels; |
339 | // LabelToAddr myUserCAddresses; |
340 | |
341 | // Mappings for labels to addresses for system-defined equates |
342 | // Because system equate addresses can have different names |
343 | // (depending on access in read vs. write mode), we can only create |
344 | // a mapping from labels to addresses; addresses to labels are |
345 | // handled differently |
346 | LabelToAddr mySystemAddresses; |
347 | |
348 | // The maximum length of all labels currently defined |
349 | uInt16 myLabelLength; |
350 | |
351 | // Filenames to use for various I/O (currently these are hardcoded) |
352 | string myListFile, mySymbolFile, myCfgFile, myDisasmFile, myRomFile; |
353 | |
354 | /// Table of instruction mnemonics |
355 | static const char* const ourTIAMnemonicR[16]; // read mode |
356 | static const char* const ourTIAMnemonicW[64]; // write mode |
357 | static const char* const ourIOMnemonic[24]; |
358 | static const char* const ourZPMnemonic[128]; |
359 | |
360 | private: |
361 | // Following constructors and assignment operators not supported |
362 | CartDebug() = delete; |
363 | CartDebug(const CartDebug&) = delete; |
364 | CartDebug(CartDebug&&) = delete; |
365 | CartDebug& operator=(const CartDebug&) = delete; |
366 | CartDebug& operator=(CartDebug&&) = delete; |
367 | }; |
368 | |
369 | #endif |
370 | |