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 | #include "bspf.hxx" |
19 | #include "System.hxx" |
20 | #include "M6502.hxx" |
21 | #include "FSNode.hxx" |
22 | #include "DiStella.hxx" |
23 | #include "Debugger.hxx" |
24 | #include "DebuggerParser.hxx" |
25 | #include "CpuDebug.hxx" |
26 | #include "OSystem.hxx" |
27 | #include "Settings.hxx" |
28 | #include "Version.hxx" |
29 | #include "Cart.hxx" |
30 | #include "CartDebug.hxx" |
31 | #include "CartDebugWidget.hxx" |
32 | #include "CartRamWidget.hxx" |
33 | #include "RomWidget.hxx" |
34 | #include "Base.hxx" |
35 | #include "exception/EmulationWarning.hxx" |
36 | |
37 | using Common::Base; |
38 | using std::hex; |
39 | using std::dec; |
40 | using std::setfill; |
41 | using std::setw; |
42 | using std::left; |
43 | using std::right; |
44 | |
45 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
46 | CartDebug::CartDebug(Debugger& dbg, Console& console, const OSystem& osystem) |
47 | : DebuggerSystem(dbg, console), |
48 | myOSystem(osystem), |
49 | myDebugWidget(nullptr), |
50 | myAddrToLineIsROM(true), |
51 | myLabelLength(8) // longest pre-defined label |
52 | { |
53 | // Add case sensitive compare for user labels |
54 | // TODO - should user labels be case insensitive too? |
55 | auto usrCmp = [](const string& a, const string& b) { return a < b; }; |
56 | myUserAddresses = LabelToAddr(usrCmp); |
57 | |
58 | // Add case insensitive compare for system labels |
59 | auto sysCmp = [](const string& a, const string& b) { |
60 | return BSPF::compareIgnoreCase(a, b) < 0; |
61 | }; |
62 | mySystemAddresses = LabelToAddr(sysCmp); |
63 | |
64 | // Add Zero-page RAM addresses |
65 | for(uInt16 i = 0x80; i <= 0xFF; ++i) |
66 | { |
67 | myState.rport.push_back(i); |
68 | myState.wport.push_back(i); |
69 | myOldState.rport.push_back(i); |
70 | myOldState.wport.push_back(i); |
71 | } |
72 | |
73 | // Create bank information for each potential bank, and an extra one for ZP RAM |
74 | // Banksizes greater than 4096 indicate multi-bank ROMs, but we handle only |
75 | // 4K pieces at a time |
76 | // Banksizes less than 4K use the actual value |
77 | size_t banksize = 0; |
78 | myConsole.cartridge().getImage(banksize); |
79 | |
80 | BankInfo info; |
81 | info.size = std::min<size_t>(banksize, 4_KB); |
82 | for(uInt32 i = 0; i < myConsole.cartridge().bankCount(); ++i) |
83 | myBankInfo.push_back(info); |
84 | |
85 | info.size = 128; // ZP RAM |
86 | myBankInfo.push_back(info); |
87 | |
88 | // We know the address for the startup bank right now |
89 | myBankInfo[myConsole.cartridge().startBank()].addressList.push_front( |
90 | myDebugger.dpeek(0xfffc)); |
91 | addLabel("Start" , myDebugger.dpeek(0xfffc, DATA)); |
92 | |
93 | // Add system equates |
94 | for(uInt16 addr = 0x00; addr <= 0x0F; ++addr) |
95 | { |
96 | if(ourTIAMnemonicR[addr]) |
97 | mySystemAddresses.emplace(ourTIAMnemonicR[addr], addr); |
98 | myReserved.TIARead[addr] = false; |
99 | } |
100 | for(uInt16 addr = 0x00; addr <= 0x3F; ++addr) |
101 | { |
102 | if(ourTIAMnemonicW[addr]) |
103 | mySystemAddresses.emplace(ourTIAMnemonicW[addr], addr); |
104 | myReserved.TIAWrite[addr] = false; |
105 | } |
106 | for(uInt16 addr = 0x280; addr <= 0x297; ++addr) |
107 | { |
108 | if(ourIOMnemonic[addr-0x280]) |
109 | mySystemAddresses.emplace(ourIOMnemonic[addr-0x280], addr); |
110 | myReserved.IOReadWrite[addr-0x280] = false; |
111 | } |
112 | for(uInt16 addr = 0x80; addr <= 0xFF; ++addr) |
113 | { |
114 | mySystemAddresses.emplace(ourZPMnemonic[addr-0x80], addr); |
115 | myReserved.ZPRAM[addr-0x80] = false; |
116 | } |
117 | |
118 | myReserved.Label.clear(); |
119 | myDisassembly.list.reserve(2048); |
120 | |
121 | // Add settings for Distella |
122 | DiStella::settings.gfxFormat = |
123 | myOSystem.settings().getInt("dis.gfxformat" ) == 16 ? Base::F_16 : Base::F_2; |
124 | DiStella::settings.resolveCode = |
125 | myOSystem.settings().getBool("dis.resolve" ); |
126 | DiStella::settings.showAddresses = |
127 | myOSystem.settings().getBool("dis.showaddr" ); |
128 | DiStella::settings.aFlag = false; // Not currently configurable |
129 | DiStella::settings.fFlag = true; // Not currently configurable |
130 | DiStella::settings.rFlag = myOSystem.settings().getBool("dis.relocate" ); |
131 | DiStella::settings.bFlag = true; // Not currently configurable |
132 | DiStella::settings.bytesWidth = 8+1; // TODO - configure based on window size |
133 | } |
134 | |
135 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
136 | const DebuggerState& CartDebug::getState() |
137 | { |
138 | myState.ram.clear(); |
139 | for(uInt32 i = 0; i < myState.rport.size(); ++i) |
140 | myState.ram.push_back(myDebugger.peek(myState.rport[i])); |
141 | |
142 | if(myDebugWidget) |
143 | myState.bank = myDebugWidget->bankState(); |
144 | |
145 | return myState; |
146 | } |
147 | |
148 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
149 | void CartDebug::saveOldState() |
150 | { |
151 | myOldState.ram.clear(); |
152 | for(uInt32 i = 0; i < myOldState.rport.size(); ++i) |
153 | myOldState.ram.push_back(myDebugger.peek(myOldState.rport[i])); |
154 | |
155 | if(myDebugWidget) |
156 | { |
157 | myOldState.bank = myDebugWidget->bankState(); |
158 | myDebugWidget->saveOldState(); |
159 | } |
160 | } |
161 | |
162 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
163 | int CartDebug::lastReadBaseAddress() |
164 | { |
165 | return mySystem.m6502().lastReadBaseAddress(); |
166 | } |
167 | |
168 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
169 | int CartDebug::lastWriteBaseAddress() |
170 | { |
171 | return mySystem.m6502().lastWriteBaseAddress(); |
172 | } |
173 | |
174 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
175 | string CartDebug::toString() |
176 | { |
177 | ostringstream buf; |
178 | uInt32 bytesPerLine; |
179 | |
180 | switch(Base::format()) |
181 | { |
182 | case Base::F_16: |
183 | case Base::F_10: |
184 | bytesPerLine = 0x10; |
185 | break; |
186 | |
187 | case Base::F_2: |
188 | bytesPerLine = 0x04; |
189 | break; |
190 | |
191 | case Base::F_DEFAULT: |
192 | default: |
193 | return DebuggerParser::red("invalid base, this is a BUG" ); |
194 | } |
195 | |
196 | const CartState& state = static_cast<const CartState&>(getState()); |
197 | const CartState& oldstate = static_cast<const CartState&>(getOldState()); |
198 | |
199 | uInt32 curraddr = 0, bytesSoFar = 0; |
200 | for(uInt32 i = 0; i < state.ram.size(); i += bytesPerLine, bytesSoFar += bytesPerLine) |
201 | { |
202 | // We detect different 'pages' of RAM when the addresses jump by |
203 | // more than the number of bytes on the previous line, or when 256 |
204 | // bytes have been previously output |
205 | if(state.rport[i] - curraddr > bytesPerLine || bytesSoFar >= 256) |
206 | { |
207 | char port[37]; |
208 | std::snprintf(port, 36, "%04x: (rport = %04x, wport = %04x)\n" , |
209 | state.rport[i], state.rport[i], state.wport[i]); |
210 | port[2] = port[3] = 'x'; |
211 | buf << DebuggerParser::red(port); |
212 | bytesSoFar = 0; |
213 | } |
214 | curraddr = state.rport[i]; |
215 | buf << Base::HEX2 << (curraddr & 0x00ff) << ": " ; |
216 | |
217 | for(uInt8 j = 0; j < bytesPerLine; ++j) |
218 | { |
219 | buf << myDebugger.invIfChanged(state.ram[i+j], oldstate.ram[i+j]) << " " ; |
220 | |
221 | if(j == 0x07) buf << " " ; |
222 | } |
223 | buf << endl; |
224 | } |
225 | |
226 | return buf.str(); |
227 | } |
228 | |
229 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
230 | bool CartDebug::disassemble(bool force) |
231 | { |
232 | // Test current disassembly; don't re-disassemble if it hasn't changed |
233 | // Also check if the current PC is in the current list |
234 | bool bankChanged = myConsole.cartridge().bankChanged(); |
235 | uInt16 PC = myDebugger.cpuDebug().pc(); |
236 | int pcline = addressToLine(PC); |
237 | bool pcfound = (pcline != -1) && (uInt32(pcline) < myDisassembly.list.size()) && |
238 | (myDisassembly.list[pcline].disasm[0] != '.'); |
239 | bool pagedirty = (PC & 0x1000) ? mySystem.isPageDirty(0x1000, 0x1FFF) : |
240 | mySystem.isPageDirty(0x80, 0xFF); |
241 | |
242 | bool changed = !mySystem.autodetectMode() && |
243 | (force || bankChanged || !pcfound || pagedirty); |
244 | if(changed) |
245 | { |
246 | // Are we disassembling from ROM or ZP RAM? |
247 | BankInfo& info = (PC & 0x1000) ? myBankInfo[getBank(PC)] : |
248 | myBankInfo[myBankInfo.size()-1]; |
249 | |
250 | // If the offset has changed, all old addresses must be 'converted' |
251 | // For example, if the list contains any $fxxx and the address space is now |
252 | // $bxxx, it must be changed |
253 | uInt16 offset = (PC - (PC % 0x1000)); |
254 | AddressList& addresses = info.addressList; |
255 | for(auto& i: addresses) |
256 | i = (i & 0xFFF) + offset; |
257 | |
258 | // Only add addresses when absolutely necessary, to cut down on the |
259 | // work that Distella has to do |
260 | if(bankChanged || !pcfound) |
261 | { |
262 | AddressList::const_iterator i; |
263 | for(i = addresses.cbegin(); i != addresses.cend(); ++i) |
264 | { |
265 | if (PC == *i) // already present |
266 | break; |
267 | } |
268 | // Otherwise, add the item at the end |
269 | if (i == addresses.end()) |
270 | addresses.push_back(PC); |
271 | } |
272 | |
273 | // Always attempt to resolve code sections unless it's been |
274 | // specifically disabled |
275 | bool found = fillDisassemblyList(info, PC); |
276 | if(!found && DiStella::settings.resolveCode) |
277 | { |
278 | // Temporarily turn off code resolution |
279 | DiStella::settings.resolveCode = false; |
280 | fillDisassemblyList(info, PC); |
281 | DiStella::settings.resolveCode = true; |
282 | } |
283 | } |
284 | |
285 | return changed; |
286 | } |
287 | |
288 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
289 | bool CartDebug::fillDisassemblyList(BankInfo& info, uInt16 search) |
290 | { |
291 | // An empty address list means that DiStella can't do a disassembly |
292 | if(info.addressList.size() == 0) |
293 | return false; |
294 | |
295 | myDisassembly.list.clear(); |
296 | myDisassembly.fieldwidth = 24 + myLabelLength; |
297 | DiStella distella(*this, myDisassembly.list, info, DiStella::settings, |
298 | myDisLabels, myDisDirectives, myReserved); |
299 | |
300 | // Parts of the disassembly will be accessed later in different ways |
301 | // We place those parts in separate maps, to speed up access |
302 | bool found = false; |
303 | myAddrToLineList.clear(); |
304 | myAddrToLineIsROM = info.offset & 0x1000; |
305 | for(uInt32 i = 0; i < myDisassembly.list.size(); ++i) |
306 | { |
307 | const DisassemblyTag& tag = myDisassembly.list[i]; |
308 | const uInt16 address = tag.address & 0xFFF; |
309 | |
310 | // Exclude 'ROW'; they don't have a valid address |
311 | if(tag.type != CartDebug::ROW) |
312 | { |
313 | // Create a mapping from addresses to line numbers |
314 | myAddrToLineList.emplace(address, i); |
315 | |
316 | // Did we find the search value? |
317 | if(address == (search & 0xFFF)) |
318 | found = true; |
319 | } |
320 | } |
321 | return found; |
322 | } |
323 | |
324 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
325 | int CartDebug::addressToLine(uInt16 address) const |
326 | { |
327 | // Switching between ZP RAM address space and Cart/ROM address space |
328 | // means the line isn't present |
329 | if(!myAddrToLineIsROM != !(address & 0x1000)) |
330 | return -1; |
331 | |
332 | const auto& iter = myAddrToLineList.find(address & 0xFFF); |
333 | return iter != myAddrToLineList.end() ? iter->second : -1; |
334 | } |
335 | |
336 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
337 | string CartDebug::disassemble(uInt16 start, uInt16 lines) const |
338 | { |
339 | // Fill the string with disassembled data |
340 | start &= 0xFFF; |
341 | ostringstream buffer; |
342 | |
343 | // First find the lines in the range, and determine the longest string |
344 | uInt32 list_size = uInt32(myDisassembly.list.size()); |
345 | uInt32 begin = list_size, end = 0, length = 0; |
346 | for(end = 0; end < list_size && lines > 0; ++end) |
347 | { |
348 | const CartDebug::DisassemblyTag& tag = myDisassembly.list[end]; |
349 | if((tag.address & 0xfff) >= start) |
350 | { |
351 | if(begin == list_size) begin = end; |
352 | if(tag.type != CartDebug::ROW) |
353 | length = std::max(length, uInt32(tag.disasm.length())); |
354 | |
355 | --lines; |
356 | } |
357 | } |
358 | |
359 | // Now output the disassembly, using as little space as possible |
360 | for(uInt32 i = begin; i < end; ++i) |
361 | { |
362 | const CartDebug::DisassemblyTag& tag = myDisassembly.list[i]; |
363 | if(tag.type == CartDebug::NONE) |
364 | continue; |
365 | else if(tag.address) |
366 | buffer << std::uppercase << std::hex << std::setw(4) |
367 | << std::setfill('0') << tag.address << ": " ; |
368 | else |
369 | buffer << " " ; |
370 | |
371 | buffer << tag.disasm << std::setw(int(length - tag.disasm.length() + 2)) |
372 | << std::setfill(' ') << " " |
373 | << std::setw(4) << std::left << tag.ccount << " " << tag.bytes << endl; |
374 | } |
375 | |
376 | return buffer.str(); |
377 | } |
378 | |
379 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
380 | bool CartDebug::addDirective(CartDebug::DisasmType type, |
381 | uInt16 start, uInt16 end, int bank) |
382 | { |
383 | if(end < start || start == 0 || end == 0) |
384 | return false; |
385 | |
386 | if(bank < 0) // Do we want the current bank or ZP RAM? |
387 | bank = (myDebugger.cpuDebug().pc() & 0x1000) ? |
388 | getBank(myDebugger.cpuDebug().pc()) : int(myBankInfo.size())-1; |
389 | |
390 | bank = std::min(bank, bankCount()); |
391 | BankInfo& info = myBankInfo[bank]; |
392 | DirectiveList& list = info.directiveList; |
393 | |
394 | DirectiveTag tag; |
395 | tag.type = type; |
396 | tag.start = start; |
397 | tag.end = end; |
398 | |
399 | DirectiveList::iterator i; |
400 | |
401 | // If the same directive and range is added, consider it a removal instead |
402 | for(i = list.begin(); i != list.end(); ++i) |
403 | { |
404 | if(i->type == tag.type && i->start == tag.start && i->end == tag.end) |
405 | { |
406 | list.erase(i); |
407 | return false; |
408 | } |
409 | } |
410 | |
411 | // Otherwise, scan the list and make space for a 'smart' merge |
412 | // Note that there are 4 possibilities: |
413 | // 1: a range is completely inside the new range |
414 | // 2: a range is completely outside the new range |
415 | // 3: a range overlaps at the beginning of the new range |
416 | // 4: a range overlaps at the end of the new range |
417 | for(i = list.begin(); i != list.end(); ++i) |
418 | { |
419 | // Case 1: remove range that is completely inside new range |
420 | if(tag.start <= i->start && tag.end >= i->end) |
421 | { |
422 | i = list.erase(i); |
423 | } |
424 | // Case 2: split the old range |
425 | else if(tag.start >= i->start && tag.end <= i->end) |
426 | { |
427 | // Only split when necessary |
428 | if(tag.type == i->type) |
429 | return true; // node is fine as-is |
430 | |
431 | // Create new endpoint |
432 | DirectiveTag tag2; |
433 | tag2.type = i->type; |
434 | tag2.start = tag.end + 1; |
435 | tag2.end = i->end; |
436 | |
437 | // Modify startpoint |
438 | i->end = tag.start - 1; |
439 | |
440 | // Insert new endpoint |
441 | ++i; |
442 | list.insert(i, tag2); |
443 | break; // no need to go further; this is the insertion point |
444 | } |
445 | // Case 3: truncate end of old range |
446 | else if(tag.start >= i->start && tag.start <= i->end) |
447 | { |
448 | i->end = tag.start - 1; |
449 | } |
450 | // Case 4: truncate start of old range |
451 | else if(tag.end >= i->start && tag.end <= i->end) |
452 | { |
453 | i->start = tag.end + 1; |
454 | } |
455 | } |
456 | |
457 | // We now know that the new range can be inserted without overlap |
458 | // Where possible, consecutive ranges should be merged rather than |
459 | // new nodes created |
460 | for(i = list.begin(); i != list.end(); ++i) |
461 | { |
462 | if(tag.end < i->start) // node should be inserted *before* this one |
463 | { |
464 | bool createNode = true; |
465 | |
466 | // Is the new range ending consecutive with the old range beginning? |
467 | // If so, a merge will suffice |
468 | if(i->type == tag.type && tag.end + 1 == i->start) |
469 | { |
470 | i->start = tag.start; |
471 | createNode = false; // a merge was done, so a new node isn't needed |
472 | } |
473 | |
474 | // Can we also merge with the previous range (if any)? |
475 | if(i != list.begin()) |
476 | { |
477 | DirectiveList::iterator p = i; |
478 | --p; |
479 | if(p->type == tag.type && p->end + 1 == tag.start) |
480 | { |
481 | if(createNode) // a merge with right-hand range didn't previously occur |
482 | { |
483 | p->end = tag.end; |
484 | createNode = false; // a merge was done, so a new node isn't needed |
485 | } |
486 | else // merge all three ranges |
487 | { |
488 | i->start = p->start; |
489 | i = list.erase(p); |
490 | createNode = false; // a merge was done, so a new node isn't needed |
491 | } |
492 | } |
493 | } |
494 | |
495 | // Create the node only when necessary |
496 | if(createNode) |
497 | i = list.insert(i, tag); |
498 | |
499 | break; |
500 | } |
501 | } |
502 | // Otherwise, add the tag at the end |
503 | if(i == list.end()) |
504 | list.push_back(tag); |
505 | |
506 | return true; |
507 | } |
508 | |
509 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
510 | int CartDebug::getBank(uInt16 address) |
511 | { |
512 | return myConsole.cartridge().getBank(address); |
513 | } |
514 | |
515 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
516 | int CartDebug::getPCBank() |
517 | { |
518 | return myConsole.cartridge().getBank(myDebugger.cpuDebug().pc()); |
519 | } |
520 | |
521 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
522 | int CartDebug::bankCount() const |
523 | { |
524 | return myConsole.cartridge().bankCount(); |
525 | } |
526 | |
527 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
528 | bool CartDebug::addLabel(const string& label, uInt16 address) |
529 | { |
530 | // Only user-defined labels can be added or redefined |
531 | switch(addressType(address)) |
532 | { |
533 | case AddrType::TIA: |
534 | case AddrType::IO: |
535 | return false; |
536 | default: |
537 | removeLabel(label); |
538 | myUserAddresses.emplace(label, address); |
539 | myUserLabels.emplace(address, label); |
540 | myLabelLength = std::max(myLabelLength, uInt16(label.size())); |
541 | mySystem.setDirtyPage(address); |
542 | return true; |
543 | } |
544 | } |
545 | |
546 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
547 | bool CartDebug::removeLabel(const string& label) |
548 | { |
549 | // Only user-defined labels can be removed |
550 | const auto& iter = myUserAddresses.find(label); |
551 | if(iter != myUserAddresses.end()) |
552 | { |
553 | // Erase the address assigned to the label |
554 | const auto& iter2 = myUserLabels.find(iter->second); |
555 | if(iter2 != myUserLabels.end()) |
556 | myUserLabels.erase(iter2); |
557 | |
558 | // Erase the label itself |
559 | mySystem.setDirtyPage(iter->second); |
560 | myUserAddresses.erase(iter); |
561 | |
562 | return true; |
563 | } |
564 | return false; |
565 | } |
566 | |
567 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
568 | bool CartDebug::getLabel(ostream& buf, uInt16 addr, bool isRead, int places) const |
569 | { |
570 | switch(addressType(addr)) |
571 | { |
572 | case AddrType::TIA: |
573 | { |
574 | if(isRead) |
575 | { |
576 | uInt16 a = addr & 0x0F, offset = addr & 0xFFF0; |
577 | if(ourTIAMnemonicR[a]) |
578 | { |
579 | buf << ourTIAMnemonicR[a]; |
580 | if(offset > 0) |
581 | buf << "|$" << Base::HEX2 << offset; |
582 | } |
583 | else |
584 | buf << "$" << Base::HEX2 << addr; |
585 | } |
586 | else |
587 | { |
588 | uInt16 a = addr & 0x3F, offset = addr & 0xFFC0; |
589 | if(ourTIAMnemonicW[a]) |
590 | { |
591 | buf << ourTIAMnemonicW[a]; |
592 | if(offset > 0) |
593 | buf << "|$" << Base::HEX2 << offset; |
594 | } |
595 | else |
596 | buf << "$" << Base::HEX2 << addr; |
597 | } |
598 | return true; |
599 | } |
600 | |
601 | case AddrType::IO: |
602 | { |
603 | uInt16 a = addr & 0xFF, offset = addr & 0xFD00; |
604 | if(a <= 0x97) |
605 | { |
606 | if(ourIOMnemonic[a - 0x80]) |
607 | { |
608 | buf << ourIOMnemonic[a - 0x80]; |
609 | if(offset > 0) |
610 | buf << "|$" << Base::HEX2 << offset; |
611 | } |
612 | else |
613 | buf << "$" << Base::HEX2 << addr; |
614 | } |
615 | else |
616 | buf << "$" << Base::HEX2 << addr; |
617 | |
618 | return true; |
619 | } |
620 | |
621 | case AddrType::ZPRAM: |
622 | { |
623 | // RAM can use user-defined labels; otherwise we default to |
624 | // standard mnemonics |
625 | auto iter = myUserLabels.find(addr); |
626 | if(iter != myUserLabels.end()) |
627 | { |
628 | buf << iter->second; |
629 | } |
630 | else |
631 | { |
632 | uInt16 a = addr & 0xFF, offset = addr & 0xFF00; |
633 | if((iter = myUserLabels.find(a)) != myUserLabels.end()) |
634 | buf << iter->second; |
635 | else |
636 | buf << ourZPMnemonic[a - 0x80]; |
637 | if(offset > 0) |
638 | buf << "|$" << Base::HEX2 << offset; |
639 | } |
640 | |
641 | return true; |
642 | } |
643 | |
644 | case AddrType::ROM: |
645 | { |
646 | // These addresses can never be in the system labels list |
647 | const auto& iter = myUserLabels.find(addr); |
648 | if(iter != myUserLabels.end()) |
649 | { |
650 | buf << iter->second; |
651 | return true; |
652 | } |
653 | break; |
654 | } |
655 | } |
656 | |
657 | switch(places) |
658 | { |
659 | case 2: |
660 | buf << "$" << Base::HEX2 << addr; |
661 | return true; |
662 | case 4: |
663 | buf << "$" << Base::HEX4 << addr; |
664 | return true; |
665 | case 8: |
666 | buf << "$" << Base::HEX8 << addr; |
667 | return true; |
668 | } |
669 | |
670 | return false; |
671 | } |
672 | |
673 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
674 | string CartDebug::getLabel(uInt16 addr, bool isRead, int places) const |
675 | { |
676 | ostringstream buf; |
677 | getLabel(buf, addr, isRead, places); |
678 | return buf.str(); |
679 | } |
680 | |
681 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
682 | int CartDebug::getAddress(const string& label) const |
683 | { |
684 | LabelToAddr::const_iterator iter; |
685 | |
686 | if((iter = mySystemAddresses.find(label)) != mySystemAddresses.end()) |
687 | return iter->second; |
688 | else if((iter = myUserAddresses.find(label)) != myUserAddresses.end()) |
689 | return iter->second; |
690 | else |
691 | return -1; |
692 | } |
693 | |
694 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
695 | string CartDebug::loadListFile() |
696 | { |
697 | // The default naming/location for list files is the ROM dir based on the |
698 | // actual ROM filename |
699 | |
700 | if(myListFile == "" ) |
701 | { |
702 | FilesystemNode lst(myOSystem.romFile().getPathWithExt("" ) + ".lst" ); |
703 | if(lst.isFile() && lst.isReadable()) |
704 | myListFile = lst.getPath(); |
705 | else |
706 | return DebuggerParser::red("list file \'" + lst.getShortPath() + "\' not found" ); |
707 | } |
708 | |
709 | FilesystemNode node(myListFile); |
710 | ifstream in(node.getPath()); |
711 | if(!in.is_open()) |
712 | return DebuggerParser::red("list file '" + node.getShortPath() + "' not readable" ); |
713 | |
714 | while(!in.eof()) |
715 | { |
716 | string line, addr_s; |
717 | |
718 | getline(in, line); |
719 | |
720 | if(!in.good() || line == "" || line[0] == '-') |
721 | continue; |
722 | else // Search for constants |
723 | { |
724 | stringstream buf(line); |
725 | |
726 | // Swallow first value, then get actual numerical value for address |
727 | // We need to read the address as a string, since it may contain 'U' |
728 | int addr = -1; |
729 | buf >> addr >> addr_s; |
730 | if(addr_s.length() == 0) |
731 | continue; |
732 | const char* p = addr_s[0] == 'U' ? addr_s.c_str() + 1 : addr_s.c_str(); |
733 | addr = int(strtoul(p, nullptr, 16)); |
734 | |
735 | // For now, completely ignore ROM addresses |
736 | if(!(addr & 0x1000)) |
737 | { |
738 | // Search for pattern 'xx yy CONSTANT =' |
739 | buf.seekg(20); // skip potential '????' |
740 | int xx = -1, yy = -1; |
741 | char eq = '\0'; |
742 | buf >> hex >> xx >> hex >> yy >> line >> eq; |
743 | if(xx >= 0 && yy >= 0 && eq == '=') |
744 | //myUserCLabels.emplace(xx*256+yy, line); |
745 | addLabel(line, xx * 256 + yy); |
746 | } |
747 | } |
748 | } |
749 | myDebugger.rom().invalidate(); |
750 | |
751 | return "list file '" + node.getShortPath() + "' loaded OK" ; |
752 | } |
753 | |
754 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
755 | string CartDebug::loadSymbolFile() |
756 | { |
757 | // The default naming/location for symbol files is the ROM dir based on the |
758 | // actual ROM filename |
759 | |
760 | if(mySymbolFile == "" ) |
761 | { |
762 | FilesystemNode sym(myOSystem.romFile().getPathWithExt("" ) + ".sym" ); |
763 | if(sym.isFile() && sym.isReadable()) |
764 | mySymbolFile = sym.getPath(); |
765 | else |
766 | return DebuggerParser::red("symbol file \'" + sym.getShortPath() + "\' not found" ); |
767 | } |
768 | |
769 | FilesystemNode node(mySymbolFile); |
770 | ifstream in(node.getPath()); |
771 | if(!in.is_open()) |
772 | return DebuggerParser::red("symbol file '" + node.getShortPath() + "' not readable" ); |
773 | |
774 | myUserAddresses.clear(); |
775 | myUserLabels.clear(); |
776 | |
777 | while(!in.eof()) |
778 | { |
779 | string label; |
780 | int value = -1; |
781 | |
782 | getline(in, label); |
783 | if(!in.good()) continue; |
784 | stringstream buf(label); |
785 | buf >> label >> hex >> value; |
786 | |
787 | if(label.length() > 0 && label[0] != '-' && value >= 0) |
788 | { |
789 | // Make sure the value doesn't represent a constant |
790 | // For now, we simply ignore constants completely |
791 | //const auto& iter = myUserCLabels.find(value); |
792 | //if(iter == myUserCLabels.end() || !BSPF::equalsIgnoreCase(label, iter->second)) |
793 | const auto& iter = myUserLabels.find(value); |
794 | if (iter == myUserLabels.end() || !BSPF::equalsIgnoreCase(label, iter->second)) |
795 | { |
796 | // Check for period, and strip leading number |
797 | string::size_type pos = label.find_first_of("." , 0); |
798 | if(pos != string::npos) |
799 | addLabel(label.substr(pos), value); |
800 | else |
801 | { |
802 | pos = label.find_last_of("$" ); |
803 | if (pos == string::npos || pos != label.length() - 1) |
804 | addLabel(label, value); |
805 | } |
806 | } |
807 | } |
808 | } |
809 | myDebugger.rom().invalidate(); |
810 | |
811 | return "symbol file '" + node.getShortPath() + "' loaded OK" ; |
812 | } |
813 | |
814 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
815 | string CartDebug::loadConfigFile() |
816 | { |
817 | // The default naming/location for config files is the ROM dir based on the |
818 | // actual ROM filename |
819 | |
820 | if(myCfgFile == "" ) |
821 | { |
822 | FilesystemNode cfg(myOSystem.romFile().getPathWithExt("" ) + ".cfg" ); |
823 | if(cfg.isFile() && cfg.isReadable()) |
824 | myCfgFile = cfg.getPath(); |
825 | else |
826 | return DebuggerParser::red("config file \'" + cfg.getShortPath() + "\' not found" ); |
827 | } |
828 | |
829 | FilesystemNode node(myCfgFile); |
830 | ifstream in(node.getPath()); |
831 | if(!in.is_open()) |
832 | return "Unable to load directives from " + node.getPath(); |
833 | |
834 | // Erase all previous directives |
835 | for(auto& bi: myBankInfo) |
836 | bi.directiveList.clear(); |
837 | |
838 | int currentbank = 0; |
839 | while(!in.eof()) |
840 | { |
841 | // Skip leading space |
842 | int c = in.peek(); |
843 | while(c == ' ' || c == '\t') |
844 | { |
845 | in.get(); |
846 | c = in.peek(); |
847 | } |
848 | |
849 | string line; |
850 | c = in.peek(); |
851 | if(c == '/') // Comment, swallow line and continue |
852 | { |
853 | getline(in, line); |
854 | continue; |
855 | } |
856 | else if(c == '[') |
857 | { |
858 | in.get(); |
859 | getline(in, line, ']'); |
860 | stringstream buf(line); |
861 | buf >> currentbank; |
862 | } |
863 | else // Should be commands from this point on |
864 | { |
865 | getline(in, line); |
866 | stringstream buf; |
867 | buf << line; |
868 | |
869 | string directive; |
870 | uInt16 start = 0, end = 0; |
871 | buf >> directive; |
872 | if(BSPF::startsWithIgnoreCase(directive, "ORG" )) |
873 | { |
874 | // TODO - figure out what to do with this |
875 | buf >> hex >> start; |
876 | } |
877 | else if(BSPF::startsWithIgnoreCase(directive, "CODE" )) |
878 | { |
879 | buf >> hex >> start >> hex >> end; |
880 | addDirective(CartDebug::CODE, start, end, currentbank); |
881 | } |
882 | else if(BSPF::startsWithIgnoreCase(directive, "GFX" )) |
883 | { |
884 | buf >> hex >> start >> hex >> end; |
885 | addDirective(CartDebug::GFX, start, end, currentbank); |
886 | } |
887 | else if(BSPF::startsWithIgnoreCase(directive, "PGFX" )) |
888 | { |
889 | buf >> hex >> start >> hex >> end; |
890 | addDirective(CartDebug::PGFX, start, end, currentbank); |
891 | } |
892 | else if(BSPF::startsWithIgnoreCase(directive, "DATA" )) |
893 | { |
894 | buf >> hex >> start >> hex >> end; |
895 | addDirective(CartDebug::DATA, start, end, currentbank); |
896 | } |
897 | else if(BSPF::startsWithIgnoreCase(directive, "ROW" )) |
898 | { |
899 | buf >> hex >> start; |
900 | buf >> hex >> end; |
901 | addDirective(CartDebug::ROW, start, end, currentbank); |
902 | } |
903 | } |
904 | } |
905 | myDebugger.rom().invalidate(); |
906 | |
907 | stringstream retVal; |
908 | if(myConsole.cartridge().bankCount() > 1) |
909 | retVal << DebuggerParser::red("config file for multi-bank ROM not fully supported\n" ); |
910 | retVal << "config file '" << node.getShortPath() << "' loaded OK" ; |
911 | return retVal.str(); |
912 | |
913 | } |
914 | |
915 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
916 | string CartDebug::saveConfigFile() |
917 | { |
918 | // The default naming/location for config files is the ROM dir based on the |
919 | // actual ROM filename |
920 | |
921 | FilesystemNode cfg; |
922 | if(myCfgFile == "" ) |
923 | { |
924 | cfg = FilesystemNode(myOSystem.romFile().getPathWithExt("" ) + ".cfg" ); |
925 | if(cfg.isFile() && cfg.isWritable()) |
926 | myCfgFile = cfg.getPath(); |
927 | else |
928 | return DebuggerParser::red("config file \'" + cfg.getShortPath() + "\' not writable" ); |
929 | } |
930 | |
931 | const string& name = myConsole.properties().get(PropType::Cart_Name); |
932 | const string& md5 = myConsole.properties().get(PropType::Cart_MD5); |
933 | |
934 | ofstream out(cfg.getPath()); |
935 | if(!out.is_open()) |
936 | return "Unable to save directives to " + cfg.getShortPath(); |
937 | |
938 | // Store all bank information |
939 | out << "//Stella.pro: \"" << name << "\"" << endl |
940 | << "//MD5: " << md5 << endl |
941 | << endl; |
942 | for(uInt32 b = 0; b < myConsole.cartridge().bankCount(); ++b) |
943 | { |
944 | out << "[" << b << "]" << endl; |
945 | getBankDirectives(out, myBankInfo[b]); |
946 | } |
947 | |
948 | stringstream retVal; |
949 | if(myConsole.cartridge().bankCount() > 1) |
950 | retVal << DebuggerParser::red("config file for multi-bank ROM not fully supported\n" ); |
951 | retVal << "config file '" << cfg.getShortPath() << "' saved OK" ; |
952 | return retVal.str(); |
953 | } |
954 | |
955 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
956 | string CartDebug::saveDisassembly() |
957 | { |
958 | if(myDisasmFile == "" ) |
959 | { |
960 | const string& propsname = |
961 | myConsole.properties().get(PropType::Cart_Name) + ".asm" ; |
962 | |
963 | myDisasmFile = FilesystemNode(myOSystem.defaultSaveDir() + propsname).getPath(); |
964 | } |
965 | |
966 | FilesystemNode node(myDisasmFile); |
967 | ofstream out(node.getPath()); |
968 | if(!out.is_open()) |
969 | return "Unable to save disassembly to " + node.getShortPath(); |
970 | |
971 | #define ALIGN(x) setfill(' ') << left << setw(x) |
972 | |
973 | // We can't print the header to the disassembly until it's actually |
974 | // been processed; therefore buffer output to a string first |
975 | ostringstream buf; |
976 | buf << "\n\n;***********************************************************\n" |
977 | << "; Bank " << myConsole.cartridge().getBank(); |
978 | if (myConsole.cartridge().bankCount() > 1) |
979 | buf << " / 0.." << myConsole.cartridge().bankCount() - 1; |
980 | buf << "\n;***********************************************************\n\n" ; |
981 | |
982 | // Use specific settings for disassembly output |
983 | // This will most likely differ from what you see in the debugger |
984 | DiStella::Settings settings; |
985 | settings.gfxFormat = DiStella::settings.gfxFormat; |
986 | settings.resolveCode = true; |
987 | settings.showAddresses = false; |
988 | settings.aFlag = false; // Otherwise DASM gets confused |
989 | settings.fFlag = DiStella::settings.fFlag; |
990 | settings.rFlag = DiStella::settings.rFlag; |
991 | settings.bytesWidth = 8+1; // same as Stella debugger |
992 | settings.bFlag = DiStella::settings.bFlag; // process break routine (TODO) |
993 | |
994 | Disassembly disasm; |
995 | disasm.list.reserve(2048); |
996 | for(int bank = 0; bank < myConsole.cartridge().bankCount(); ++bank) |
997 | { |
998 | BankInfo& info = myBankInfo[bank]; |
999 | // An empty address list means that DiStella can't do a disassembly |
1000 | if(info.addressList.size() == 0) |
1001 | continue; |
1002 | |
1003 | // Disassemble bank |
1004 | disasm.list.clear(); |
1005 | DiStella distella(*this, disasm.list, info, settings, |
1006 | myDisLabels, myDisDirectives, myReserved); |
1007 | |
1008 | if (myReserved.breakFound) |
1009 | addLabel("Break" , myDebugger.dpeek(0xfffe)); |
1010 | |
1011 | buf << " SEG CODE\n" |
1012 | << " ORG $" << Base::HEX4 << info.offset << "\n\n" ; |
1013 | |
1014 | // Format in 'distella' style |
1015 | for(uInt32 i = 0; i < disasm.list.size(); ++i) |
1016 | { |
1017 | const DisassemblyTag& tag = disasm.list[i]; |
1018 | |
1019 | // Add label (if any) |
1020 | if(tag.label != "" ) |
1021 | buf << ALIGN(4) << (tag.label) << "\n" ; |
1022 | buf << " " ; |
1023 | |
1024 | switch(tag.type) |
1025 | { |
1026 | case CartDebug::CODE: |
1027 | { |
1028 | buf << ALIGN(32) << tag.disasm << tag.ccount.substr(0, 5) << tag.ctotal << tag.ccount.substr(5, 2); |
1029 | if (tag.disasm.find("WSYNC" ) != std::string::npos) |
1030 | buf << "\n;---------------------------------------" ; |
1031 | break; |
1032 | } |
1033 | case CartDebug::ROW: |
1034 | { |
1035 | buf << ".byte " << ALIGN(32) << tag.disasm.substr(6, 8*4-1) << "; $" << Base::HEX4 << tag.address << " (*)" ; |
1036 | break; |
1037 | } |
1038 | case CartDebug::GFX: |
1039 | { |
1040 | buf << ".byte " << (settings.gfxFormat == Base::F_2 ? "%" : "$" ) |
1041 | << tag.bytes << " ; |" ; |
1042 | for(int c = 12; c < 20; ++c) |
1043 | buf << ((tag.disasm[c] == '\x1e') ? "#" : " " ); |
1044 | buf << ALIGN(13) << "|" << "$" << Base::HEX4 << tag.address << " (G)" ; |
1045 | break; |
1046 | } |
1047 | case CartDebug::PGFX: |
1048 | { |
1049 | buf << ".byte " << (settings.gfxFormat == Base::F_2 ? "%" : "$" ) |
1050 | << tag.bytes << " ; |" ; |
1051 | for(int c = 12; c < 20; ++c) |
1052 | buf << ((tag.disasm[c] == '\x1f') ? "*" : " " ); |
1053 | buf << ALIGN(13) << "|" << "$" << Base::HEX4 << tag.address << " (P)" ; |
1054 | break; |
1055 | } |
1056 | case CartDebug::DATA: |
1057 | { |
1058 | buf << ".byte " << ALIGN(32) << tag.disasm.substr(6, 8 * 4 - 1) << "; $" << Base::HEX4 << tag.address << " (D)" ; |
1059 | break; |
1060 | } |
1061 | case CartDebug::NONE: |
1062 | default: |
1063 | { |
1064 | break; |
1065 | } |
1066 | } // switch |
1067 | buf << "\n" ; |
1068 | } |
1069 | } |
1070 | |
1071 | // Some boilerplate, similar to what DiStella adds |
1072 | auto timeinfo = BSPF::localTime(); |
1073 | out << "; Disassembly of " << myOSystem.romFile().getShortPath() << "\n" |
1074 | << "; Disassembled " << std::put_time(&timeinfo, "%c\n" ) |
1075 | << "; Using Stella " << STELLA_VERSION << "\n;\n" |
1076 | << "; ROM properties name : " << myConsole.properties().get(PropType::Cart_Name) << "\n" |
1077 | << "; ROM properties MD5 : " << myConsole.properties().get(PropType::Cart_MD5) << "\n" |
1078 | << "; Bankswitch type : " << myConsole.cartridge().about() << "\n;\n" |
1079 | << "; Legend: * = CODE not yet run (tentative code)\n" |
1080 | << "; D = DATA directive (referenced in some way)\n" |
1081 | << "; G = GFX directive, shown as '#' (stored in player, missile, ball)\n" |
1082 | << "; P = PGFX directive, shown as '*' (stored in playfield)\n" |
1083 | << "; i = indexed accessed only\n" |
1084 | << "; c = used by code executed in RAM\n" |
1085 | << "; s = used by stack\n" |
1086 | << "; ! = page crossed, 1 cycle penalty\n" |
1087 | << "\n processor 6502\n\n" ; |
1088 | |
1089 | bool addrUsed = false; |
1090 | for(uInt16 addr = 0x00; addr <= 0x0F; ++addr) |
1091 | addrUsed = addrUsed || myReserved.TIARead[addr] || (mySystem.getAccessFlags(addr) & WRITE); |
1092 | for(uInt16 addr = 0x00; addr <= 0x3F; ++addr) |
1093 | addrUsed = addrUsed || myReserved.TIAWrite[addr] || (mySystem.getAccessFlags(addr) & DATA); |
1094 | for(uInt16 addr = 0x00; addr <= 0x17; ++addr) |
1095 | addrUsed = addrUsed || myReserved.IOReadWrite[addr]; |
1096 | if(addrUsed) |
1097 | { |
1098 | out << "\n;-----------------------------------------------------------\n" |
1099 | << "; TIA and IO constants accessed\n" |
1100 | << ";-----------------------------------------------------------\n\n" ; |
1101 | |
1102 | // TIA read access |
1103 | for(uInt16 addr = 0x00; addr <= 0x0F; ++addr) |
1104 | if(myReserved.TIARead[addr] && ourTIAMnemonicR[addr]) |
1105 | out << ALIGN(16) << ourTIAMnemonicR[addr] << "= $" |
1106 | << Base::HEX2 << right << addr << " ; (R)\n" ; |
1107 | else if (mySystem.getAccessFlags(addr) & DATA) |
1108 | out << ";" << ALIGN(16-1) << ourTIAMnemonicR[addr] << "= $" |
1109 | << Base::HEX2 << right << addr << " ; (Ri)\n" ; |
1110 | out << "\n" ; |
1111 | |
1112 | // TIA write access |
1113 | for(uInt16 addr = 0x00; addr <= 0x3F; ++addr) |
1114 | if(myReserved.TIAWrite[addr] && ourTIAMnemonicW[addr]) |
1115 | out << ALIGN(16) << ourTIAMnemonicW[addr] << "= $" |
1116 | << Base::HEX2 << right << addr << " ; (W)\n" ; |
1117 | else if (mySystem.getAccessFlags(addr) & WRITE) |
1118 | out << ";" << ALIGN(16-1) << ourTIAMnemonicW[addr] << "= $" |
1119 | << Base::HEX2 << right << addr << " ; (Wi)\n" ; |
1120 | out << "\n" ; |
1121 | |
1122 | // RIOT IO access |
1123 | for(uInt16 addr = 0x00; addr <= 0x17; ++addr) |
1124 | if(myReserved.IOReadWrite[addr] && ourIOMnemonic[addr]) |
1125 | out << ALIGN(16) << ourIOMnemonic[addr] << "= $" |
1126 | << Base::HEX4 << right << (addr+0x280) << "\n" ; |
1127 | } |
1128 | |
1129 | addrUsed = false; |
1130 | for(uInt16 addr = 0x80; addr <= 0xFF; ++addr) |
1131 | addrUsed = addrUsed || myReserved.ZPRAM[addr-0x80] |
1132 | || (mySystem.getAccessFlags(addr) & (DATA | WRITE)) |
1133 | || (mySystem.getAccessFlags(addr|0x100) & (DATA | WRITE)); |
1134 | if(addrUsed) |
1135 | { |
1136 | bool addLine = false; |
1137 | out << "\n\n;-----------------------------------------------------------\n" |
1138 | << "; RIOT RAM (zero-page) labels\n" |
1139 | << ";-----------------------------------------------------------\n\n" ; |
1140 | |
1141 | for (uInt16 addr = 0x80; addr <= 0xFF; ++addr) { |
1142 | bool ramUsed = (mySystem.getAccessFlags(addr) & (DATA | WRITE)); |
1143 | bool codeUsed = (mySystem.getAccessFlags(addr) & CODE); |
1144 | bool stackUsed = (mySystem.getAccessFlags(addr|0x100) & (DATA | WRITE)); |
1145 | |
1146 | if (myReserved.ZPRAM[addr - 0x80] && |
1147 | myUserLabels.find(addr) == myUserLabels.end()) { |
1148 | if (addLine) |
1149 | out << "\n" ; |
1150 | out << ALIGN(16) << ourZPMnemonic[addr - 0x80] << "= $" |
1151 | << Base::HEX2 << right << (addr) |
1152 | << ((stackUsed|codeUsed) ? "; (" : "" ) |
1153 | << (codeUsed ? "c" : "" ) |
1154 | << (stackUsed ? "s" : "" ) |
1155 | << ((stackUsed | codeUsed) ? ")" : "" ) |
1156 | << "\n" ; |
1157 | addLine = false; |
1158 | } else if (ramUsed|codeUsed|stackUsed) { |
1159 | if (addLine) |
1160 | out << "\n" ; |
1161 | out << ALIGN(18) << ";" << "$" |
1162 | << Base::HEX2 << right << (addr) |
1163 | << " (" |
1164 | << (ramUsed ? "i" : "" ) |
1165 | << (codeUsed ? "c" : "" ) |
1166 | << (stackUsed ? "s" : "" ) |
1167 | << ")\n" ; |
1168 | addLine = false; |
1169 | } else |
1170 | addLine = true; |
1171 | } |
1172 | } |
1173 | |
1174 | if(myReserved.Label.size() > 0) |
1175 | { |
1176 | out << "\n\n;-----------------------------------------------------------\n" |
1177 | << "; Non Locatable Labels\n" |
1178 | << ";-----------------------------------------------------------\n\n" ; |
1179 | for(const auto& iter: myReserved.Label) |
1180 | out << ALIGN(16) << iter.second << "= $" << iter.first << "\n" ; |
1181 | } |
1182 | |
1183 | if(myUserLabels.size() > 0) |
1184 | { |
1185 | out << "\n\n;-----------------------------------------------------------\n" |
1186 | << "; User Defined Labels\n" |
1187 | << ";-----------------------------------------------------------\n\n" ; |
1188 | int max_len = 16; |
1189 | for(const auto& iter: myUserLabels) |
1190 | max_len = std::max(max_len, int(iter.second.size())); |
1191 | for(const auto& iter: myUserLabels) |
1192 | out << ALIGN(max_len) << iter.second << "= $" << iter.first << "\n" ; |
1193 | } |
1194 | |
1195 | // And finally, output the disassembly |
1196 | out << buf.str(); |
1197 | |
1198 | stringstream retVal; |
1199 | if(myConsole.cartridge().bankCount() > 1) |
1200 | retVal << DebuggerParser::red("disassembly for multi-bank ROM not fully supported, only currently enabled banks disassembled\n" ); |
1201 | retVal << "saved " << node.getShortPath() << " OK" ; |
1202 | return retVal.str(); |
1203 | } |
1204 | |
1205 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
1206 | string CartDebug::saveRom() |
1207 | { |
1208 | const string& rom = myConsole.properties().get(PropType::Cart_Name) + ".a26" ; |
1209 | |
1210 | FilesystemNode node(myOSystem.defaultSaveDir() + rom); |
1211 | ofstream out(node.getPath(), std::ios::binary); |
1212 | if(out && myConsole.cartridge().saveROM(out)) |
1213 | return "saved ROM as " + node.getShortPath(); |
1214 | else |
1215 | return DebuggerParser::red("failed to save ROM" ); |
1216 | } |
1217 | |
1218 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
1219 | string CartDebug::listConfig(int bank) |
1220 | { |
1221 | if(myConsole.cartridge().bankCount() > 1) |
1222 | return DebuggerParser::red("config file for multi-bank ROM not yet supported" ); |
1223 | |
1224 | uInt32 startbank = 0, endbank = bankCount(); |
1225 | if(bank >= 0 && bank < bankCount()) |
1226 | { |
1227 | startbank = bank; |
1228 | endbank = startbank + 1; |
1229 | } |
1230 | |
1231 | ostringstream buf; |
1232 | buf << "(items marked '*' are user-defined)" << endl; |
1233 | for(uInt32 b = startbank; b < endbank; ++b) |
1234 | { |
1235 | BankInfo& info = myBankInfo[b]; |
1236 | buf << "[" << b << "]" << endl; |
1237 | for(const auto& i: info.directiveList) |
1238 | { |
1239 | if(i.type != CartDebug::NONE) |
1240 | { |
1241 | buf << "(*) " ; |
1242 | disasmTypeAsString(buf, i.type); |
1243 | buf << " " << Base::HEX4 << i.start << " " << Base::HEX4 << i.end << endl; |
1244 | } |
1245 | } |
1246 | getBankDirectives(buf, info); |
1247 | } |
1248 | |
1249 | return buf.str(); |
1250 | } |
1251 | |
1252 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
1253 | string CartDebug::clearConfig(int bank) |
1254 | { |
1255 | uInt32 startbank = 0, endbank = bankCount(); |
1256 | if(bank >= 0 && bank < bankCount()) |
1257 | { |
1258 | startbank = bank; |
1259 | endbank = startbank + 1; |
1260 | } |
1261 | |
1262 | size_t count = 0; |
1263 | for(uInt32 b = startbank; b < endbank; ++b) |
1264 | { |
1265 | count += myBankInfo[b].directiveList.size(); |
1266 | myBankInfo[b].directiveList.clear(); |
1267 | } |
1268 | |
1269 | ostringstream buf; |
1270 | if(count > 0) |
1271 | buf << "removed " << dec << count << " directives from " |
1272 | << dec << (endbank - startbank) << " banks" ; |
1273 | else |
1274 | buf << "no directives present" ; |
1275 | return buf.str(); |
1276 | } |
1277 | |
1278 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
1279 | void CartDebug::getCompletions(const char* in, StringList& completions) const |
1280 | { |
1281 | // First scan system equates |
1282 | for(uInt16 addr = 0x00; addr <= 0x0F; ++addr) |
1283 | if(ourTIAMnemonicR[addr] && BSPF::matches(ourTIAMnemonicR[addr], in)) |
1284 | completions.push_back(ourTIAMnemonicR[addr]); |
1285 | for(uInt16 addr = 0x00; addr <= 0x3F; ++addr) |
1286 | if(ourTIAMnemonicW[addr] && BSPF::matches(ourTIAMnemonicW[addr], in)) |
1287 | completions.push_back(ourTIAMnemonicW[addr]); |
1288 | for(uInt16 addr = 0; addr <= 0x297-0x280; ++addr) |
1289 | if(ourIOMnemonic[addr] && BSPF::matches(ourIOMnemonic[addr], in)) |
1290 | completions.push_back(ourIOMnemonic[addr]); |
1291 | for(uInt16 addr = 0; addr <= 0x7F; ++addr) |
1292 | if(ourZPMnemonic[addr] && BSPF::matches(ourZPMnemonic[addr], in)) |
1293 | completions.push_back(ourZPMnemonic[addr]); |
1294 | |
1295 | // Now scan user-defined labels |
1296 | for(const auto& iter: myUserAddresses) |
1297 | { |
1298 | const char* l = iter.first.c_str(); |
1299 | if(BSPF::matches(l, in)) |
1300 | completions.push_back(l); |
1301 | } |
1302 | } |
1303 | |
1304 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
1305 | CartDebug::AddrType CartDebug::addressType(uInt16 addr) const |
1306 | { |
1307 | // Determine the type of address to access the correct list |
1308 | // These addresses were based on (and checked against) Kroko's 2600 memory |
1309 | // map, found at http://www.qotile.net/minidig/docs/2600_mem_map.txt |
1310 | if(addr % 0x2000 < 0x1000) |
1311 | { |
1312 | if((addr & 0x00ff) < 0x80) |
1313 | return AddrType::TIA; |
1314 | else |
1315 | { |
1316 | switch(addr & 0x0f00) |
1317 | { |
1318 | case 0x000: case 0x100: case 0x400: case 0x500: |
1319 | case 0x800: case 0x900: case 0xc00: case 0xd00: |
1320 | return AddrType::ZPRAM; |
1321 | case 0x200: case 0x300: case 0x600: case 0x700: |
1322 | case 0xa00: case 0xb00: case 0xe00: case 0xf00: |
1323 | return AddrType::IO; |
1324 | } |
1325 | } |
1326 | } |
1327 | return AddrType::ROM; |
1328 | } |
1329 | |
1330 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
1331 | void CartDebug::getBankDirectives(ostream& buf, BankInfo& info) const |
1332 | { |
1333 | // Start with the offset for this bank |
1334 | buf << "ORG " << Base::HEX4 << info.offset << endl; |
1335 | |
1336 | // Now consider each byte |
1337 | uInt32 prev = info.offset, addr = prev + 1; |
1338 | DisasmType prevType = disasmTypeAbsolute(mySystem.getAccessFlags(prev)); |
1339 | for( ; addr < info.offset + info.size; ++addr) |
1340 | { |
1341 | DisasmType currType = disasmTypeAbsolute(mySystem.getAccessFlags(addr)); |
1342 | |
1343 | // Have we changed to a new type? |
1344 | if(currType != prevType) |
1345 | { |
1346 | disasmTypeAsString(buf, prevType); |
1347 | buf << " " << Base::HEX4 << prev << " " << Base::HEX4 << (addr-1) << endl; |
1348 | |
1349 | prev = addr; |
1350 | prevType = currType; |
1351 | } |
1352 | } |
1353 | |
1354 | // Grab the last directive, making sure it accounts for all remaining space |
1355 | if(prev != addr) |
1356 | { |
1357 | disasmTypeAsString(buf, prevType); |
1358 | buf << " " << Base::HEX4 << prev << " " << Base::HEX4 << (addr-1) << endl; |
1359 | } |
1360 | } |
1361 | |
1362 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
1363 | void CartDebug::addressTypeAsString(ostream& buf, uInt16 addr) const |
1364 | { |
1365 | if(!(addr & 0x1000)) |
1366 | { |
1367 | buf << DebuggerParser::red("type only defined for cart address space" ); |
1368 | return; |
1369 | } |
1370 | |
1371 | uInt8 directive = myDisDirectives[addr & 0xFFF] & 0xFC, |
1372 | debugger = myDebugger.getAccessFlags(addr) & 0xFC, |
1373 | label = myDisLabels[addr & 0xFFF]; |
1374 | |
1375 | buf << endl << "directive: " << Base::toString(directive, Base::F_2_8) << " " ; |
1376 | disasmTypeAsString(buf, directive); |
1377 | buf << endl << "emulation: " << Base::toString(debugger, Base::F_2_8) << " " ; |
1378 | disasmTypeAsString(buf, debugger); |
1379 | buf << endl << "tentative: " << Base::toString(label, Base::F_2_8) << " " ; |
1380 | disasmTypeAsString(buf, label); |
1381 | buf << endl; |
1382 | } |
1383 | |
1384 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
1385 | CartDebug::DisasmType CartDebug::disasmTypeAbsolute(uInt8 flags) const |
1386 | { |
1387 | if(flags & CartDebug::CODE) |
1388 | return CartDebug::CODE; |
1389 | else if(flags & CartDebug::TCODE) |
1390 | return CartDebug::CODE; // TODO - should this be separate?? |
1391 | else if(flags & CartDebug::GFX) |
1392 | return CartDebug::GFX; |
1393 | else if(flags & CartDebug::PGFX) |
1394 | return CartDebug::PGFX; |
1395 | else if(flags & CartDebug::DATA) |
1396 | return CartDebug::DATA; |
1397 | else if(flags & CartDebug::ROW) |
1398 | return CartDebug::ROW; |
1399 | else |
1400 | return CartDebug::NONE; |
1401 | } |
1402 | |
1403 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
1404 | void CartDebug::disasmTypeAsString(ostream& buf, DisasmType type) const |
1405 | { |
1406 | switch(type) |
1407 | { |
1408 | case CartDebug::CODE: buf << "CODE" ; break; |
1409 | case CartDebug::TCODE: buf << "TCODE" ; break; |
1410 | case CartDebug::GFX: buf << "GFX" ; break; |
1411 | case CartDebug::PGFX: buf << "PGFX" ; break; |
1412 | case CartDebug::DATA: buf << "DATA" ; break; |
1413 | case CartDebug::ROW: buf << "ROW" ; break; |
1414 | case CartDebug::REFERENCED: |
1415 | case CartDebug::VALID_ENTRY: |
1416 | case CartDebug::NONE: break; |
1417 | } |
1418 | } |
1419 | |
1420 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
1421 | void CartDebug::disasmTypeAsString(ostream& buf, uInt8 flags) const |
1422 | { |
1423 | if(flags) |
1424 | { |
1425 | if(flags & CartDebug::CODE) |
1426 | buf << "CODE " ; |
1427 | if(flags & CartDebug::TCODE) |
1428 | buf << "TCODE " ; |
1429 | if(flags & CartDebug::GFX) |
1430 | buf << "GFX " ; |
1431 | if(flags & CartDebug::PGFX) |
1432 | buf << "PGFX " ; |
1433 | if(flags & CartDebug::DATA) |
1434 | buf << "DATA " ; |
1435 | if(flags & CartDebug::ROW) |
1436 | buf << "ROW " ; |
1437 | if(flags & CartDebug::REFERENCED) |
1438 | buf << "*REFERENCED " ; |
1439 | if(flags & CartDebug::VALID_ENTRY) |
1440 | buf << "*VALID_ENTRY " ; |
1441 | } |
1442 | else |
1443 | buf << "no flags set" ; |
1444 | } |
1445 | |
1446 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
1447 | const char* const CartDebug::ourTIAMnemonicR[16] = { |
1448 | "CXM0P" , "CXM1P" , "CXP0FB" , "CXP1FB" , "CXM0FB" , "CXM1FB" , "CXBLPF" , "CXPPMM" , |
1449 | "INPT0" , "INPT1" , "INPT2" , "INPT3" , "INPT4" , "INPT5" , "$1e" , "$1f" |
1450 | }; |
1451 | |
1452 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
1453 | const char* const CartDebug::ourTIAMnemonicW[64] = { |
1454 | "VSYNC" , "VBLANK" , "WSYNC" , "RSYNC" , "NUSIZ0" , "NUSIZ1" , "COLUP0" , "COLUP1" , |
1455 | "COLUPF" , "COLUBK" , "CTRLPF" , "REFP0" , "REFP1" , "PF0" , "PF1" , "PF2" , |
1456 | "RESP0" , "RESP1" , "RESM0" , "RESM1" , "RESBL" , "AUDC0" , "AUDC1" , "AUDF0" , |
1457 | "AUDF1" , "AUDV0" , "AUDV1" , "GRP0" , "GRP1" , "ENAM0" , "ENAM1" , "ENABL" , |
1458 | "HMP0" , "HMP1" , "HMM0" , "HMM1" , "HMBL" , "VDELP0" , "VDELP1" , "VDELBL" , |
1459 | "RESMP0" , "RESMP1" , "HMOVE" , "HMCLR" , "CXCLR" , "$2d" , "$2e" , "$2f" , |
1460 | "$30" , "$31" , "$32" , "$33" , "$34" , "$35" , "$36" , "$37" , |
1461 | "$38" , "$39" , "$3a" , "$3b" , "$3c" , "$3d" , "$3e" , "$3f" |
1462 | }; |
1463 | |
1464 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
1465 | const char* const CartDebug::ourIOMnemonic[24] = { |
1466 | "SWCHA" , "SWACNT" , "SWCHB" , "SWBCNT" , "INTIM" , "TIMINT" , |
1467 | "$286" , "$287" , "$288" , "$289" , "$28a" , "$28b" , "$28c" , |
1468 | "$28d" , "$28e" , "$28f" , "$290" , "$291" , "$292" , "$293" , |
1469 | "TIM1T" , "TIM8T" , "TIM64T" , "T1024T" |
1470 | }; |
1471 | |
1472 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
1473 | const char* const CartDebug::ourZPMnemonic[128] = { |
1474 | "ram_80" , "ram_81" , "ram_82" , "ram_83" , "ram_84" , "ram_85" , "ram_86" , "ram_87" , |
1475 | "ram_88" , "ram_89" , "ram_8A" , "ram_8B" , "ram_8C" , "ram_8D" , "ram_8E" , "ram_8F" , |
1476 | "ram_90" , "ram_91" , "ram_92" , "ram_93" , "ram_94" , "ram_95" , "ram_96" , "ram_97" , |
1477 | "ram_98" , "ram_99" , "ram_9A" , "ram_9B" , "ram_9C" , "ram_9D" , "ram_9E" , "ram_9F" , |
1478 | "ram_A0" , "ram_A1" , "ram_A2" , "ram_A3" , "ram_A4" , "ram_A5" , "ram_A6" , "ram_A7" , |
1479 | "ram_A8" , "ram_A9" , "ram_AA" , "ram_AB" , "ram_AC" , "ram_AD" , "ram_AE" , "ram_AF" , |
1480 | "ram_B0" , "ram_B1" , "ram_B2" , "ram_B3" , "ram_B4" , "ram_B5" , "ram_B6" , "ram_B7" , |
1481 | "ram_B8" , "ram_B9" , "ram_BA" , "ram_BB" , "ram_BC" , "ram_BD" , "ram_BE" , "ram_BF" , |
1482 | "ram_C0" , "ram_C1" , "ram_C2" , "ram_C3" , "ram_C4" , "ram_C5" , "ram_C6" , "ram_C7" , |
1483 | "ram_C8" , "ram_C9" , "ram_CA" , "ram_CB" , "ram_CC" , "ram_CD" , "ram_CE" , "ram_CF" , |
1484 | "ram_D0" , "ram_D1" , "ram_D2" , "ram_D3" , "ram_D4" , "ram_D5" , "ram_D6" , "ram_D7" , |
1485 | "ram_D8" , "ram_D9" , "ram_DA" , "ram_DB" , "ram_DC" , "ram_DD" , "ram_DE" , "ram_DF" , |
1486 | "ram_E0" , "ram_E1" , "ram_E2" , "ram_E3" , "ram_E4" , "ram_E5" , "ram_E6" , "ram_E7" , |
1487 | "ram_E8" , "ram_E9" , "ram_EA" , "ram_EB" , "ram_EC" , "ram_ED" , "ram_EE" , "ram_EF" , |
1488 | "ram_F0" , "ram_F1" , "ram_F2" , "ram_F3" , "ram_F4" , "ram_F5" , "ram_F6" , "ram_F7" , |
1489 | "ram_F8" , "ram_F9" , "ram_FA" , "ram_FB" , "ram_FC" , "ram_FD" , "ram_FE" , "ram_FF" |
1490 | }; |
1491 | |