From adb1b29c67a98a575504a28da7a4220e266dd4dc Mon Sep 17 00:00:00 2001 From: Ilari Liusvaara Date: Wed, 21 May 2014 18:32:17 +0300 Subject: [PATCH] Refactor debugging into instance object --- include/core/debug.hpp | 183 ++++++++++-- include/core/instance.hpp | 2 + src/core/debug.cpp | 352 +++++++++++------------- src/core/mainloop.cpp | 15 +- src/lua/memory.cpp | 326 +++++++++++++++------- src/lua/memory2.cpp | 4 +- src/platform/wxwidgets/tracelogger.cpp | 173 ++++++------ src/platform/wxwidgets/tracelogmenu.cpp | 8 +- 8 files changed, 655 insertions(+), 408 deletions(-) diff --git a/include/core/debug.hpp b/include/core/debug.hpp index 786b234c..3cd40289 100644 --- a/include/core/debug.hpp +++ b/include/core/debug.hpp @@ -2,12 +2,9 @@ #define _debug__hpp__included__ #include +#include #include - -struct debug_handle -{ - void* handle; -}; +#include "library/dispatch.hpp" enum debug_type { @@ -18,27 +15,161 @@ enum debug_type DEBUG_FRAME, }; -extern const uint64_t debug_all_addr; +struct debug_callback_params_rwx +{ + uint64_t addr; //Address. + uint64_t value; //Value/CPU. +}; -debug_handle debug_add_callback(uint64_t addr, debug_type type, - std::function fn, std::function dtor); -debug_handle debug_add_trace_callback(uint64_t proc, std::function fn, std::function dtor); -debug_handle debug_add_frame_callback(std::function fn, - std::function dtor); -void debug_remove_callback(uint64_t addr, debug_type type, debug_handle handle); -void debug_fire_callback_read(uint64_t addr, uint64_t value); -void debug_fire_callback_write(uint64_t addr, uint64_t value); -void debug_fire_callback_exec(uint64_t addr, uint64_t value); -void debug_fire_callback_trace(uint64_t proc, const char* str, bool true_insn = true); -void debug_fire_callback_frame(uint64_t frame, bool loadstate); -void debug_set_cheat(uint64_t addr, uint64_t value); -void debug_clear_cheat(uint64_t addr); -void debug_setxmask(uint64_t mask); -void debug_tracelog(uint64_t proc, const std::string& filename); -bool debug_tracelogging(uint64_t proc); -void debug_set_tracelog_change_cb(std::function cb); -void debug_core_change(); -void debug_request_break(); +struct debug_callback_params_trace +{ + uint64_t cpu; //CPU number. + const char* decoded_insn; //Decoded instruction + bool true_insn; //True instruction flag. +}; + +struct debug_callback_params_frame +{ + uint64_t frame; //Frame number. + bool loadstated; //Loadstate flag. +}; + +struct debug_callback_params +{ + debug_type type; + union { + debug_callback_params_rwx rwx; //READ/WRITE/EXECUTE + debug_callback_params_trace trace; //TRACE. + debug_callback_params_frame frame; //FRAME. + }; +}; + +struct debug_callback_base +{ +/** + * Destructor. + */ + virtual ~debug_callback_base(); +/** + * Do a callback. + */ + virtual void callback(const debug_callback_params& params) = 0; +/** + * Notify about killed callback. + */ + virtual void killed(uint64_t addr, debug_type type) = 0; +}; + +/** + * Debugging context. + */ +class debug_context +{ +public: +/** + * Placeholder for all addresses. + */ + static const uint64_t all_addresses; +/** + * Add a callback. + */ + void add_callback(uint64_t addr, debug_type type, debug_callback_base& cb); +/** + * Remove a callback. + */ + void remove_callback(uint64_t addr, debug_type type, debug_callback_base& cb); +/** + * Fire a read callback. + */ + void do_callback_read(uint64_t addr, uint64_t value); +/** + * Fire a write callback. + */ + void do_callback_write(uint64_t addr, uint64_t value); +/** + * Fire a exec callback. + */ + void do_callback_exec(uint64_t addr, uint64_t value); +/** + * Fire a trace callback. + */ + void do_callback_trace(uint64_t cpu, const char* str, bool true_insn = true); +/** + * Fire a frame callback. + */ + void do_callback_frame(uint64_t frame, bool loadstate); +/** + * Set a cheat. + */ + void set_cheat(uint64_t addr, uint64_t value); +/** + * Clear a cheat. + */ + void clear_cheat(uint64_t addr); +/** + * Set execute callback mask. + */ + void setxmask(uint64_t mask); +/** + * Set tracelog file. + */ + void tracelog(uint64_t cpu, const std::string& filename); +/** + * Tracelogging on? + */ + bool is_tracelogging(uint64_t cpu); +/** + * Change tracelog change callback. + */ + void set_tracelog_change_cb(std::function cb); +/** + * Notify a core change. + */ + void core_change(); +/** + * Request a break. + */ + void request_break(); + //These are public only for some debugging stuff. + typedef std::list cb_list; + std::map read_cb; + std::map write_cb; + std::map exec_cb; + std::map trace_cb; + std::map frame_cb; +private: + cb_list dummy_cb; //Always empty. + uint64_t xmask = 1; + std::function tracelog_change_cb; + struct dispatch::target<> corechange; + bool corechange_r = false; + bool requesting_break = false; + + struct tracelog_file : public debug_callback_base + { + std::ofstream stream; + std::string full_filename; + unsigned refcnt; + tracelog_file(debug_context& parent); + ~tracelog_file(); + void callback(const debug_callback_params& p); + void killed(uint64_t addr, debug_type type); + private: + debug_context& parent; + }; + std::map trace_outputs; + + std::map& get_lists(debug_type type) + { + switch(type) { + case DEBUG_READ: return read_cb; + case DEBUG_WRITE: return write_cb; + case DEBUG_EXEC: return exec_cb; + case DEBUG_TRACE: return trace_cb; + case DEBUG_FRAME: return frame_cb; + default: throw std::runtime_error("Invalid debug callback type"); + } + } +}; #endif diff --git a/include/core/instance.hpp b/include/core/instance.hpp index 23cbbb28..76c67e4e 100644 --- a/include/core/instance.hpp +++ b/include/core/instance.hpp @@ -4,6 +4,7 @@ #include #include "core/command.hpp" #include "core/controllerframe.hpp" +#include "core/debug.hpp" #include "core/emustatus.hpp" #include "core/inthread.hpp" #include "core/movie.hpp" @@ -95,6 +96,7 @@ struct emulator_instance cart_mappings_refresher cmapper; controller_state controls; project_state project; + debug_context dbg; threads::id emu_thread; //Queue stuff. threads::lock queue_lock; diff --git a/src/core/debug.cpp b/src/core/debug.cpp index 6ec8f032..7e3a86e9 100644 --- a/src/core/debug.cpp +++ b/src/core/debug.cpp @@ -1,6 +1,7 @@ #include "core/command.hpp" #include "core/debug.hpp" #include "core/dispatch.hpp" +#include "core/instance.hpp" #include "core/mainloop.hpp" #include "core/moviedata.hpp" #include "library/directory.hpp" @@ -12,55 +13,6 @@ namespace { - struct cb_rwx - { - std::function cb; - std::function dtor; - }; - struct cb_trace - { - std::function cb; - std::function dtor; - }; - struct cb_frame - { - std::function cb; - std::function dtor; - }; - typedef std::list cb_list; - typedef std::list cb2_list; - typedef std::list cb3_list; - std::map read_cb; - std::map write_cb; - std::map exec_cb; - std::map trace_cb; - std::map frame_cb; - cb_list dummy_cb; //Always empty. - cb2_list dummy_cb2; //Always empty. - cb3_list dummy_cb3; //Always empty. - uint64_t xmask = 1; - std::function tracelog_change_cb; - struct dispatch::target<> corechange; - bool corechange_r = false; - bool requesting_break = false; - - struct tracelog_file - { - std::ofstream stream; - std::string full_filename; - unsigned refcnt; - }; - std::map> trace_outputs; - - std::map& get_lists(debug_type type) - { - switch(type) { - case DEBUG_READ: return read_cb; - case DEBUG_WRITE: return write_cb; - case DEBUG_EXEC: return exec_cb; - default: throw std::runtime_error("Invalid debug callback type"); - } - } unsigned debug_flag(debug_type type) { @@ -75,179 +27,215 @@ namespace } } -const uint64_t debug_all_addr = 0xFFFFFFFFFFFFFFFFULL; - namespace { - template debug_handle _debug_add_callback(std::map>& cb, uint64_t addr, - debug_type type, T fn, std::function dtor) - { - if(!corechange_r) { - corechange.set(notify_core_change, []() { debug_core_change(); }); - corechange_r = true; - } - if(!cb.count(addr) && type != DEBUG_FRAME) - our_rom.rtype->set_debug_flags(addr, debug_flag(type), 0); - - auto& lst = cb[addr]; - lst.push_back(fn); - debug_handle h; - h.handle = &*lst.rbegin(); - return h; - } - - template void _debug_remove_callback(T& cb, uint64_t addr, debug_type type, debug_handle handle) - { - if(!cb.count(addr)) return; - auto& l = cb[addr]; - for(auto i = l.begin(); i != l.end(); i++) { - if(&*i == handle.handle) { - l.erase(i); - break; - } - } - if(cb[addr].empty()) { - cb.erase(addr); - if(type != DEBUG_FRAME) - our_rom.rtype->set_debug_flags(addr, 0, debug_flag(type)); - } - } - - template void kill_hooks(T& cblist) + template void kill_hooks(T& cblist, debug_type type) { while(!cblist.empty()) { if(cblist.begin()->second.empty()) { cblist.erase(cblist.begin()->first); continue; } + auto key = cblist.begin()->first; auto tmp = cblist.begin()->second.begin(); - tmp->dtor(); + cblist.begin()->second.erase(cblist.begin()->second.begin()); + (*tmp)->killed(key, type); } cblist.clear(); } } -debug_handle debug_add_callback(uint64_t addr, debug_type type, std::function fn, - std::function dtor) +debug_callback_base::~debug_callback_base() { - std::map& cb = get_lists(type); - cb_rwx t; - t.cb = fn; - t.dtor = dtor; - return _debug_add_callback(cb, addr, type, t, dtor); } -debug_handle debug_add_trace_callback(uint64_t proc, std::function fn, std::function dtor) +const uint64_t debug_context::all_addresses = 0xFFFFFFFFFFFFFFFFULL; + +void debug_context::add_callback(uint64_t addr, debug_type type, debug_callback_base& cb) { - cb_trace t; - t.cb = fn; - t.dtor = dtor; - return _debug_add_callback(trace_cb, proc, DEBUG_TRACE, t, dtor); + std::map& xcb = get_lists(type); + if(!corechange_r) { + corechange.set(notify_core_change, [this]() { this->core_change(); }); + corechange_r = true; + } + if(!xcb.count(addr) && type != DEBUG_FRAME) + our_rom.rtype->set_debug_flags(addr, debug_flag(type), 0); + auto& lst = xcb[addr]; + lst.push_back(&cb); } -debug_handle debug_add_frame_callback(std::function fn, - std::function dtor) +void debug_context::remove_callback(uint64_t addr, debug_type type, debug_callback_base& cb) { - cb_frame t; - t.cb = fn; - t.dtor = dtor; - return _debug_add_callback(frame_cb, 0, DEBUG_FRAME, t, dtor); -} - -void debug_remove_callback(uint64_t addr, debug_type type, debug_handle handle) -{ - if(type == DEBUG_FRAME) { - _debug_remove_callback(frame_cb, 0, DEBUG_FRAME, handle); - } else if(type == DEBUG_TRACE) { - _debug_remove_callback(trace_cb, addr, DEBUG_TRACE, handle); - } else { - _debug_remove_callback(get_lists(type), addr, type, handle); + std::map& xcb = get_lists(type); + if(type == DEBUG_FRAME) addr = 0; + if(!xcb.count(addr)) return; + auto& l = xcb[addr]; + for(auto i = l.begin(); i != l.end(); i++) { + if(*i == &cb) { + l.erase(i); + break; + } + } + if(xcb[addr].empty()) { + xcb.erase(addr); + if(type != DEBUG_FRAME) + our_rom.rtype->set_debug_flags(addr, 0, debug_flag(type)); } } -void debug_fire_callback_read(uint64_t addr, uint64_t value) +void debug_context::do_callback_read(uint64_t addr, uint64_t value) { + debug_callback_params p; + p.type = DEBUG_READ; + p.rwx.addr = addr; + p.rwx.value = value; + requesting_break = false; - cb_list* cb1 = read_cb.count(debug_all_addr) ? &read_cb[debug_all_addr] : &dummy_cb; + cb_list* cb1 = read_cb.count(all_addresses) ? &read_cb[all_addresses] : &dummy_cb; cb_list* cb2 = read_cb.count(addr) ? &read_cb[addr] : &dummy_cb; auto _cb1 = *cb1; auto _cb2 = *cb2; - for(auto& i : _cb1) i.cb(addr, value); - for(auto& i : _cb2) i.cb(addr, value); + for(auto& i : _cb1) i->callback(p); + for(auto& i : _cb2) i->callback(p); if(requesting_break) do_break_pause(); } -void debug_fire_callback_write(uint64_t addr, uint64_t value) +void debug_context::do_callback_write(uint64_t addr, uint64_t value) { + debug_callback_params p; + p.type = DEBUG_WRITE; + p.rwx.addr = addr; + p.rwx.value = value; + requesting_break = false; - cb_list* cb1 = write_cb.count(debug_all_addr) ? &write_cb[debug_all_addr] : &dummy_cb; + cb_list* cb1 = write_cb.count(all_addresses) ? &write_cb[all_addresses] : &dummy_cb; cb_list* cb2 = write_cb.count(addr) ? &write_cb[addr] : &dummy_cb; auto _cb1 = *cb1; auto _cb2 = *cb2; - for(auto& i : _cb1) i.cb(addr, value); - for(auto& i : _cb2) i.cb(addr, value); + for(auto& i : _cb1) i->callback(p); + for(auto& i : _cb2) i->callback(p); if(requesting_break) do_break_pause(); } -void debug_fire_callback_exec(uint64_t addr, uint64_t value) +void debug_context::do_callback_exec(uint64_t addr, uint64_t cpu) { + debug_callback_params p; + p.type = DEBUG_EXEC; + p.rwx.addr = addr; + p.rwx.value = cpu; + requesting_break = false; - cb_list* cb1 = exec_cb.count(debug_all_addr) ? &exec_cb[debug_all_addr] : &dummy_cb; + cb_list* cb1 = exec_cb.count(all_addresses) ? &exec_cb[all_addresses] : &dummy_cb; cb_list* cb2 = exec_cb.count(addr) ? &exec_cb[addr] : &dummy_cb; auto _cb1 = *cb1; auto _cb2 = *cb2; - if(value & xmask) - for(auto& i : _cb1) i.cb(addr, value); - for(auto& i : _cb2) i.cb(addr, value); + if((1ULL << cpu) & xmask) + for(auto& i : _cb1) i->callback(p); + for(auto& i : _cb2) i->callback(p); if(requesting_break) do_break_pause(); } -void debug_fire_callback_trace(uint64_t proc, const char* str, bool true_insn) +void debug_context::do_callback_trace(uint64_t cpu, const char* str, bool true_insn) { + debug_callback_params p; + p.type = DEBUG_TRACE; + p.trace.cpu = cpu; + p.trace.decoded_insn = str; + p.trace.true_insn = true_insn; + requesting_break = false; - cb2_list* cb = trace_cb.count(proc) ? &trace_cb[proc] : &dummy_cb2; + cb_list* cb = trace_cb.count(cpu) ? &trace_cb[cpu] : &dummy_cb; auto _cb = *cb; - for(auto& i : _cb) i.cb(proc, str, true_insn); + for(auto& i : _cb) i->callback(p); if(requesting_break) do_break_pause(); } -void debug_fire_callback_frame(uint64_t frame, bool loadstate) +void debug_context::do_callback_frame(uint64_t frame, bool loadstate) { - cb3_list* cb = frame_cb.count(0) ? &frame_cb[0] : &dummy_cb3; + debug_callback_params p; + p.type = DEBUG_FRAME; + p.frame.frame = frame; + p.frame.loadstated = loadstate; + + cb_list* cb = frame_cb.count(0) ? &frame_cb[0] : &dummy_cb; auto _cb = *cb; - for(auto& i : _cb) i.cb(frame, loadstate); + for(auto& i : _cb) i->callback(p); } -void debug_set_cheat(uint64_t addr, uint64_t value) +void debug_context::set_cheat(uint64_t addr, uint64_t value) { our_rom.rtype->set_cheat(addr, value, true); } -void debug_clear_cheat(uint64_t addr) +void debug_context::clear_cheat(uint64_t addr) { our_rom.rtype->set_cheat(addr, 0, false); } -void debug_setxmask(uint64_t mask) +void debug_context::setxmask(uint64_t mask) { xmask = mask; } -void debug_tracelog(uint64_t proc, const std::string& filename) +bool debug_context::is_tracelogging(uint64_t cpu) +{ + return (trace_outputs.count(cpu) != 0); +} + +void debug_context::set_tracelog_change_cb(std::function cb) +{ + tracelog_change_cb = cb; +} + +void debug_context::core_change() +{ + our_rom.rtype->debug_reset(); + kill_hooks(read_cb, DEBUG_READ); + kill_hooks(write_cb, DEBUG_WRITE); + kill_hooks(exec_cb, DEBUG_EXEC); + kill_hooks(trace_cb, DEBUG_TRACE); +} + +void debug_context::request_break() +{ + requesting_break = true; +} + +debug_context::tracelog_file::tracelog_file(debug_context& _parent) + : parent(_parent) +{ +} + +debug_context::tracelog_file::~tracelog_file() +{ +} + +void debug_context::tracelog_file::callback(const debug_callback_params& p) +{ + if(!parent.trace_outputs.count(p.trace.cpu)) return; + parent.trace_outputs[p.trace.cpu]->stream << p.trace.decoded_insn << std::endl; +} + +void debug_context::tracelog_file::killed(uint64_t addr, debug_type type) +{ + refcnt--; + if(!refcnt) + delete this; +} + +void debug_context::tracelog(uint64_t proc, const std::string& filename) { if(filename == "") { if(!trace_outputs.count(proc)) return; - debug_remove_callback(proc, DEBUG_TRACE, trace_outputs[proc].second); - trace_outputs[proc].first->refcnt--; - if(!trace_outputs[proc].first->refcnt) { - delete trace_outputs[proc].first; - } + remove_callback(proc, DEBUG_TRACE, *trace_outputs[proc]); + trace_outputs[proc]->refcnt--; + if(!trace_outputs[proc]->refcnt) + delete trace_outputs[proc]; trace_outputs.erase(proc); messages << "Stopped tracelogging processor #" << proc << std::endl; if(tracelog_change_cb) tracelog_change_cb(); @@ -257,73 +245,55 @@ void debug_tracelog(uint64_t proc, const std::string& filename) std::string full_filename = directory::absolute_path(filename); bool found = false; for(auto i : trace_outputs) { - if(i.second.first->full_filename == full_filename) { - i.second.first->refcnt++; - trace_outputs[proc].first = i.second.first; + if(i.second->full_filename == full_filename) { + i.second->refcnt++; + trace_outputs[proc] = i.second; found = true; break; } } if(!found) { - trace_outputs[proc].first = new tracelog_file; - trace_outputs[proc].first->refcnt = 1; - trace_outputs[proc].first->full_filename = full_filename; - trace_outputs[proc].first->stream.open(full_filename); - if(!trace_outputs[proc].first->stream) { - delete trace_outputs[proc].first; + trace_outputs[proc] = new tracelog_file(*this); + trace_outputs[proc]->refcnt = 1; + trace_outputs[proc]->full_filename = full_filename; + trace_outputs[proc]->stream.open(full_filename); + if(!trace_outputs[proc]->stream) { + delete trace_outputs[proc]; trace_outputs.erase(proc); throw std::runtime_error("Can't open '" + full_filename + "'"); } } - trace_outputs[proc].second = debug_add_trace_callback(proc, [](uint64_t proc, const char* str, bool dummy) { - if(!trace_outputs.count(proc)) return; - trace_outputs[proc].first->stream << str << std::endl; - }, [proc]() { debug_tracelog(proc, ""); }); + try { + add_callback(proc, DEBUG_TRACE, *trace_outputs[proc]); + } catch(std::exception& e) { + messages << "Error starting tracelogging: " << e.what() << std::endl; + trace_outputs[proc]->refcnt--; + if(!trace_outputs[proc]->refcnt) + delete trace_outputs[proc]; + trace_outputs.erase(proc); + throw; + } messages << "Tracelogging processor #" << proc << " to '" << filename << "'" << std::endl; if(tracelog_change_cb) tracelog_change_cb(); } -bool debug_tracelogging(uint64_t proc) -{ - return (trace_outputs.count(proc) != 0); -} - -void debug_set_tracelog_change_cb(std::function cb) -{ - tracelog_change_cb = cb; -} - -void debug_core_change() -{ - our_rom.rtype->debug_reset(); - kill_hooks(read_cb); - kill_hooks(write_cb); - kill_hooks(exec_cb); - kill_hooks(trace_cb); -} - -void debug_request_break() -{ - requesting_break = true; -} - namespace { command::fnptr<> callbacks_show(lsnes_cmds, "show-callbacks", "", "", []() throw(std::bad_alloc, std::runtime_error) { - for(auto& i : read_cb) + for(auto& i : CORE().dbg.read_cb) for(auto& j : i.second) messages << "READ addr=" << i.first << " handle=" << &j << std::endl; - for(auto& i : write_cb) + for(auto& i : CORE().dbg.write_cb) for(auto& j : i.second) messages << "WRITE addr=" << i.first << " handle=" << &j << std::endl; - for(auto& i : exec_cb) + for(auto& i : CORE().dbg.exec_cb) for(auto& j : i.second) messages << "EXEC addr=" << i.first << " handle=" << &j << std::endl; - for(auto& i : trace_cb) + for(auto& i : CORE().dbg.trace_cb) for(auto& j : i.second) messages << "TRACE proc=" << i.first << " handle=" << &j << std::endl; - for(auto& i : frame_cb) + for(auto& i : CORE().dbg.frame_cb) for(auto& j : i.second) messages << "FRAME handle=" << &j << std::endl; }); @@ -335,19 +305,19 @@ namespace if(r[1] == "r") { uint64_t addr = parse_value(r[2]); uint64_t val = parse_value(r[3]); - debug_fire_callback_read(addr, val); + CORE().dbg.do_callback_read(addr, val); } else if(r[1] == "w") { uint64_t addr = parse_value(r[2]); uint64_t val = parse_value(r[3]); - debug_fire_callback_write(addr, val); + CORE().dbg.do_callback_write(addr, val); } else if(r[1] == "x") { uint64_t addr = parse_value(r[2]); uint64_t val = parse_value(r[3]); - debug_fire_callback_exec(addr, val); + CORE().dbg.do_callback_exec(addr, val); } else if(r[1] == "t") { uint64_t proc = parse_value(r[2]); std::string str = r[3]; - debug_fire_callback_trace(proc, str.c_str()); + CORE().dbg.do_callback_trace(proc, str.c_str()); } else throw std::runtime_error("Invalid operation"); }); @@ -367,7 +337,7 @@ namespace } throw std::runtime_error("tracelog: Invalid CPU"); out: - debug_tracelog(_cpu, filename); + CORE().dbg.tracelog(_cpu, filename); }); } diff --git a/src/core/mainloop.cpp b/src/core/mainloop.cpp index 8192d102..598e6543 100644 --- a/src/core/mainloop.cpp +++ b/src/core/mainloop.cpp @@ -540,22 +540,22 @@ public: void memory_read(uint64_t addr, uint64_t value) { - debug_fire_callback_read(addr, value); + CORE().dbg.do_callback_read(addr, value); } void memory_write(uint64_t addr, uint64_t value) { - debug_fire_callback_write(addr, value); + CORE().dbg.do_callback_write(addr, value); } void memory_execute(uint64_t addr, uint64_t proc) { - debug_fire_callback_exec(addr, proc); + CORE().dbg.do_callback_exec(addr, proc); } void memory_trace(uint64_t proc, const char* str, bool insn) { - debug_fire_callback_trace(proc, str, insn); + CORE().dbg.do_callback_trace(proc, str, insn); } }; @@ -1334,8 +1334,7 @@ void main_loop(struct loaded_rom& rom, struct moviefile& initial, bool load_has_ stop_at_frame_active = false; just_did_loadstate = first_round; CORE().controls.reset_framehold(); - debug_fire_callback_frame(CORE().mlogic.get_movie().get_current_frame(), - true); + CORE().dbg.do_callback_frame(CORE().mlogic.get_movie().get_current_frame(), true); continue; } else if(r < 0) { //Not exactly desriable, but this at least won't desync. @@ -1357,7 +1356,7 @@ void main_loop(struct loaded_rom& rom, struct moviefile& initial, bool load_has_ just_did_loadstate = false; } frame_irq_time = get_utime() - time_x; - debug_fire_callback_frame(CORE().mlogic.get_movie().get_current_frame(), false); + CORE().dbg.do_callback_frame(CORE().mlogic.get_movie().get_current_frame(), false); our_rom.rtype->emulate(); random_mix_timing_entropy(); time_x = get_utime(); @@ -1372,7 +1371,7 @@ out: CORE().commentary.kill(); CORE().system_thread_available = false; //Kill some things to avoid crashes. - debug_core_change(); + CORE().dbg.core_change(); CORE().project.set(NULL, true); CORE().mwatch.clear_multi(CORE().mwatch.enumerate()); } diff --git a/src/lua/memory.cpp b/src/lua/memory.cpp index dfb7a0cb..94239d8f 100644 --- a/src/lua/memory.cpp +++ b/src/lua/memory.cpp @@ -200,40 +200,6 @@ namespace L.setmetatable(-2); } - struct lua_debug_callback - { - uint64_t addr; - debug_type type; - debug_handle h; - bool dead; - const void* lua_fn; - static int dtor(lua_State* L) - { - lua_debug_callback* D = (lua_debug_callback*)lua_touserdata(L, 1); - return D->_dtor(L); - } - int _dtor(lua_State* L); - }; - std::map> cbs; - - int lua_debug_callback::_dtor(lua_State* L) - { - if(dead) return 0; - dead = true; - lua_pushlightuserdata(L, &type); - lua_pushnil(L); - lua_rawset(L, LUA_REGISTRYINDEX); - debug_remove_callback(addr, type, h); - for(auto j = cbs[addr].begin(); j != cbs[addr].end(); j++) - if(*j == this) { - cbs[addr].erase(j); - break; - } - if(cbs[addr].empty()) - cbs.erase(addr); - return 0; - } - void do_lua_error(lua::state& L, int ret) { if(!ret) return; @@ -253,81 +219,236 @@ namespace return; } } + + char lua_cb_list_key = 0; + + struct lua_debug_callback2 : public debug_callback_base + { + lua::state* L; + uint64_t addr; + debug_type type; + bool dead; + const void* lua_fn; + ~lua_debug_callback2(); + void link_to_list(); + void set_lua_fn(int slot); + void unregister(); + void callback(const debug_callback_params& p); + void killed(uint64_t addr, debug_type type); + static int on_lua_gc(lua_State* L); + lua_debug_callback2* prev; + lua_debug_callback2* next; + }; + + struct lua_debug_callback_dict + { + ~lua_debug_callback_dict(); + std::map, lua_debug_callback2*> cblist; + static int on_lua_gc(lua_State* L); + }; + + lua_debug_callback2::~lua_debug_callback2() + { + if(!prev) { + L->pushlightuserdata(&lua_cb_list_key); + L->rawget(LUA_REGISTRYINDEX); + if(!L->isnil(-1)) { + lua_debug_callback_dict* dc = (lua_debug_callback_dict*)L->touserdata(-1); + std::pair key = std::make_pair(type, addr); + if(dc->cblist.count(key)) { + dc->cblist[key] = next; + if(!next) + dc->cblist.erase(key); + } + } + L->pop(1); + } + //Unlink from list. + if(prev) prev->next = next; + if(next) next->prev = prev; + prev = next = NULL; + } + + void lua_debug_callback2::link_to_list() + { + prev = NULL; + L->pushlightuserdata(&lua_cb_list_key); + L->rawget(LUA_REGISTRYINDEX); + if(L->isnil(-1)) { + //No existing dict, create one. + L->pop(1); + L->pushlightuserdata(&lua_cb_list_key); + lua_debug_callback_dict* D = (lua_debug_callback_dict*) + L->newuserdata(sizeof(lua_debug_callback_dict)); + new(D) lua_debug_callback_dict; + L->newtable(); + L->pushstring("__gc"); + L->pushcclosure(&lua_debug_callback_dict::on_lua_gc, 0); + L->rawset(-3); + L->setmetatable(-2); + L->rawset(LUA_REGISTRYINDEX); + } + L->pushlightuserdata(&lua_cb_list_key); + L->rawget(LUA_REGISTRYINDEX); + lua_debug_callback2* was = NULL; + lua_debug_callback_dict* dc = (lua_debug_callback_dict*)L->touserdata(-1); + std::pair key = std::make_pair(type, addr); + if(dc->cblist.count(key)) + was = dc->cblist[key]; + dc->cblist[key] = this; + next = was; + L->pop(1); + + } + + void lua_debug_callback2::set_lua_fn(int slot) + { + //Convert to absolute slot. + if(slot < 0) + slot = L->gettop() + slot; + //Write the function. + L->pushlightuserdata((char*)this + 1); + L->pushvalue(slot); + L->rawset(LUA_REGISTRYINDEX); + lua_fn = L->topointer(slot); + } + + void lua_debug_callback2::unregister() + { + //Unregister. + CORE().dbg.remove_callback(addr, type, *this); + dead = true; + //Delink from Lua, prompting Lua to GC this. + L->pushlightuserdata(this); + L->pushnil(); + L->rawset(LUA_REGISTRYINDEX); + L->pushlightuserdata((char*)this + 1); + L->pushnil(); + L->rawset(LUA_REGISTRYINDEX); + } + + void lua_debug_callback2::callback(const debug_callback_params& p) + { + L->pushlightuserdata((char*)this + 1); + L->rawget(LUA_REGISTRYINDEX); + switch(p.type) { + case DEBUG_READ: + case DEBUG_WRITE: + case DEBUG_EXEC: + L->pushnumber(p.rwx.addr); + L->pushnumber(p.rwx.value); + do_lua_error(*L, L->pcall(2, 0, 0)); + break; + case DEBUG_TRACE: + L->pushnumber(p.trace.cpu); + L->pushstring(p.trace.decoded_insn); + L->pushboolean(p.trace.true_insn); + do_lua_error(*L, L->pcall(3, 0, 0)); + break; + case DEBUG_FRAME: + L->pushnumber(p.frame.frame); + L->pushboolean(p.frame.loadstated); + do_lua_error(*L, L->pcall(2, 0, 0)); + break; + default: + //Remove the junk from stack. + L->pop(1); + break; + } + } + + void lua_debug_callback2::killed(uint64_t addr, debug_type type) + { + //Assume this has been unregistered. + dead = true; + //Delink from Lua, lua will GC this. + L->pushlightuserdata(this); + L->pushnil(); + L->rawset(LUA_REGISTRYINDEX); + L->pushlightuserdata((char*)this + 1); + L->pushnil(); + L->rawset(LUA_REGISTRYINDEX); + } + + int lua_debug_callback2::on_lua_gc(lua_State* _L) + { + //We need to destroy the object. + lua_debug_callback2* D = (lua_debug_callback2*)lua_touserdata(_L, 1); + if(!D->dead) { + //Unregister this! + CORE().dbg.remove_callback(D->addr, D->type, *D); + D->dead = true; + } + D->~lua_debug_callback2(); + return 0; + } + + lua_debug_callback_dict::~lua_debug_callback_dict() + { + } + + int lua_debug_callback_dict::on_lua_gc(lua_State* _L) + { + lua_pushlightuserdata(_L, &lua_cb_list_key); + lua_pushnil(_L); + lua_rawset(_L, LUA_REGISTRYINDEX); + lua_debug_callback_dict* D = (lua_debug_callback_dict*)lua_touserdata(_L, 1); + D->~lua_debug_callback_dict(); + return 0; + } } template void handle_registerX(lua::state& L, uint64_t addr, int lfn) { - auto& cbl = cbs[addr]; - //Put the context in userdata so it can be gc'd when Lua context is terminated. - lua_debug_callback* D = (lua_debug_callback*)L.newuserdata(sizeof(lua_debug_callback)); + lua_debug_callback2* D = (lua_debug_callback2*)L.newuserdata(sizeof(lua_debug_callback2)); + new(D) lua_debug_callback2; L.newtable(); L.pushstring("__gc"); - L.pushcclosure(&lua_debug_callback::dtor, 0); + L.pushcclosure(&lua_debug_callback2::on_lua_gc, 0); L.rawset(-3); L.setmetatable(-2); - L.pushlightuserdata(&D->addr); + L.pushlightuserdata(D); L.pushvalue(-2); L.rawset(LUA_REGISTRYINDEX); L.pop(1); //Pop the copy of object. - cbl.push_back(D); - - D->dead = false; + D->L = &L.get_master(); D->addr = addr; D->type = type; - D->lua_fn = L.topointer(lfn); - lua::state* LL = &L.get_master(); - void* D2 = &D->type; - if(type != DEBUG_TRACE) - D->h = debug_add_callback(addr, type, [LL, D2](uint64_t addr, uint64_t value) { - LL->pushlightuserdata(D2); - LL->rawget(LUA_REGISTRYINDEX); - LL->pushnumber(addr); - LL->pushnumber(value); - do_lua_error(*LL, LL->pcall(2, 0, 0)); - }, [LL, D]() { - LL->pushlightuserdata(&D->addr); - LL->pushnil(); - LL->rawset(LUA_REGISTRYINDEX); - D->_dtor(LL->handle()); - }); - else - D->h = debug_add_trace_callback(addr, [LL, D2](uint64_t proc, const char* str, bool true_insn) { - LL->pushlightuserdata(D2); - LL->rawget(LUA_REGISTRYINDEX); - LL->pushnumber(proc); - LL->pushstring(str); - LL->pushboolean(true_insn); - do_lua_error(*LL, LL->pcall(3, 0, 0)); - }, [LL, D]() { - LL->pushlightuserdata(&D->addr); - LL->pushnil(); - LL->rawset(LUA_REGISTRYINDEX); - D->_dtor(LL->handle()); - }); - L.pushlightuserdata(D2); - L.pushvalue(lfn); - L.rawset(LUA_REGISTRYINDEX); + D->dead = false; + D->set_lua_fn(lfn); + D->link_to_list(); + + CORE().dbg.add_callback(addr, type, *D); } template void handle_unregisterX(lua::state& L, uint64_t addr, int lfn) { - if(!cbs.count(addr)) - return; - auto& cbl = cbs[addr]; - for(auto i = cbl.begin(); i != cbl.end(); i++) { - if((*i)->type != type) continue; - if(L.topointer(lfn) != (*i)->lua_fn) continue; - L.pushlightuserdata(&(*i)->type); - L.pushnil(); - L.rawset(LUA_REGISTRYINDEX); - (*i)->_dtor(L.handle()); - //Lua will GC the object. - break; - } + lua_debug_callback_dict* Dx; + lua_debug_callback2* D = NULL; + L.pushlightuserdata(&lua_cb_list_key); + L.rawget(LUA_REGISTRYINDEX); + if(!L.isnil(-1)) { + Dx = (lua_debug_callback_dict*)L.touserdata(-1); + auto key = std::make_pair(type, addr); + if(Dx->cblist.count(key)) + D = Dx->cblist[key]; + L.pop(1); + while(D) { + if(D->dead || D->type != type || D->addr != addr || L.topointer(lfn) != D->lua_fn) { + D = D->next; + continue; + } + //Remove this. + auto Dold = D; + D = D->next; + Dold->unregister(); + } + } else + L.pop(1); } typedef void(*dummy1_t)(lua::state& L, uint64_t addr, int lfn); @@ -368,11 +489,24 @@ namespace command::fnptr<> callbacks_show_lua(lsnes_cmds, "show-lua-callbacks", "", "", []() throw(std::bad_alloc, std::runtime_error) { - for(auto& i : cbs) - for(auto& j : i.second) - messages << "addr=" << j->addr << " type=" << j->type << " handle=" - << j->h.handle << " dead=" << j->dead << " lua_fn=" - << j->lua_fn << std::endl; + lua::state& L = CORE().lua; + lua_debug_callback2* D; + lua_debug_callback_dict* Dx; + L.pushlightuserdata(&lua_cb_list_key); + L.rawget(LUA_REGISTRYINDEX); + if(!L.isnil(-1)) { + Dx = (lua_debug_callback_dict*)L.touserdata(-1); + for(auto Dy : Dx->cblist) { + D = Dy.second; + while(D) { + messages << "addr=" << D->addr << " type=" << D->type << " handle=" + << D << " dead=" << D->dead << " lua_fn=" + << D->lua_fn << std::endl; + D = D->next; + } + } + } + L.pop(1); }); template(); - debug_setxmask(value); + CORE().dbg.setxmask(value); return 0; } diff --git a/src/lua/memory2.cpp b/src/lua/memory2.cpp index 0053a776..494cd82b 100644 --- a/src/lua/memory2.cpp +++ b/src/lua/memory2.cpp @@ -435,10 +435,10 @@ namespace if(addr >= vmasize) throw std::runtime_error("Address out of range"); if(P.is_novalue()) { - debug_clear_cheat(vmabase + addr); + CORE().dbg.clear_cheat(vmabase + addr); } else { P(value); - debug_set_cheat(vmabase + addr, value); + CORE().dbg.set_cheat(vmabase + addr, value); } return 0; } diff --git a/src/platform/wxwidgets/tracelogger.cpp b/src/platform/wxwidgets/tracelogger.cpp index 16926a3a..f2087b50 100644 --- a/src/platform/wxwidgets/tracelogger.cpp +++ b/src/platform/wxwidgets/tracelogger.cpp @@ -505,7 +505,7 @@ namespace std::vector> listsyms; }; - class wxwin_tracelog : public wxFrame + class wxwin_tracelog : public wxFrame, public debug_callback_base { public: wxwin_tracelog(wxWindow* parent, int _cpuid, const std::string& cpuname); @@ -544,8 +544,8 @@ namespace void scroll_pane(uint64_t line); int cpuid; volatile bool trace_active; - debug_handle trace_handle; - debug_handle trace_handle_frame; + void callback(const debug_callback_params& params); + void killed(uint64_t addr, debug_type type); void do_rwx_break(uint64_t addr, uint64_t value, debug_type type); void kill_debug_hooks(); scroll_bar* scroll; @@ -562,7 +562,7 @@ namespace std::string find_string; bool dirty; bool singlestepping; - std::map, debug_handle> rwx_breakpoints; + std::map, bool> rwx_breakpoints; wxMenuItem* m_singlestep; }; @@ -587,15 +587,14 @@ namespace void wxwin_tracelog::kill_debug_hooks() { - debug_remove_callback(cpuid, DEBUG_TRACE, trace_handle); - debug_remove_callback(cpuid, DEBUG_FRAME, trace_handle_frame); + CORE().dbg.remove_callback(cpuid, DEBUG_TRACE, *this); + CORE().dbg.remove_callback(cpuid, DEBUG_FRAME, *this); threads::alock h(buffer_mutex); for(auto& i : rwx_breakpoints) { - if(!i.second.handle) + if(!i.second) continue; - debug_remove_callback(i.first.first, i.first.second, i.second); - //Dirty hack. - i.second.handle = NULL; + CORE().dbg.remove_callback(i.first.first, i.first.second, *this); + i.second = false; } trace_active = false; convert_break_to_pause(); @@ -708,7 +707,80 @@ namespace void wxwin_tracelog::do_rwx_break(uint64_t addr, uint64_t value, debug_type type) { - debug_request_break(); + lsnes_instance.dbg.request_break(); + } + + void wxwin_tracelog::callback(const debug_callback_params& p) + { + switch(p.type) { + case DEBUG_READ: + case DEBUG_WRITE: + case DEBUG_EXEC: + do_rwx_break(p.rwx.addr, p.rwx.value, p.type); + break; + case DEBUG_TRACE: { + if(!trace_active) + return; + //Got tracelog line, send it. + threads::alock h(buffer_mutex); + lines_waiting.push_back(p.trace.decoded_insn); + if(!unprocessed_lines) { + unprocessed_lines = true; + runuifun([this]() { this->process_lines(); }); + } + if(singlestepping && p.trace.true_insn) { + lsnes_instance.dbg.request_break(); + singlestepping = false; + } + break; + } + case DEBUG_FRAME: { + std::ostringstream xstr; + xstr << "------------ "; + xstr << "Frame " << p.frame.frame; + if(p.frame.loadstated) xstr << " (loadstated)"; + xstr << " ------------"; + std::string str = xstr.str(); + threads::alock h(buffer_mutex); + lines_waiting.push_back(str); + if(!unprocessed_lines) { + unprocessed_lines = true; + runuifun([this]() { this->process_lines(); }); + } + break; + } + } + } + + void wxwin_tracelog::killed(uint64_t addr, debug_type type) + { + switch(type) { + case DEBUG_READ: + case DEBUG_WRITE: + case DEBUG_EXEC: { + //We need to kill this hook if still active. + auto i2 = std::make_pair(addr, type); + auto& h = rwx_breakpoints[i2]; + if(h) + lsnes_instance.dbg.remove_callback(addr, type, *this); + h = false; + break; + } + case DEBUG_TRACE: + //Dtor! + if(!trace_active) + return; + kill_debug_hooks(); + runuifun([this]() { + this->enabled->SetValue(false); + this->enabled->Enable(false); + this->m_singlestep->Enable(false); + }); + break; + case DEBUG_FRAME: + //Do nothing. + break; + } } void wxwin_tracelog::on_enabled(wxCommandEvent& e) @@ -721,64 +793,11 @@ namespace broken2 = true; for(auto& i : rwx_breakpoints) { auto i2 = i.first; - i.second = debug_add_callback(i.first.first, i.first.second, - [this, i2](uint64_t addr, uint64_t value) { - this->do_rwx_break(addr, value, i2.second); - }, [this, i2] { - //We need to kill this hook if still active. - auto& h = rwx_breakpoints[i2]; - if(h.handle) - debug_remove_callback(i2.first, i2.second, h); - h.handle = NULL; - }); + lsnes_instance.dbg.add_callback(i2.first, i2.second, *this); + i.second = true; } - this->trace_handle = debug_add_trace_callback(cpuid, [this](uint64_t proc, - const char* str, bool true_instruction) { - if(!this->trace_active) - return; - //Got tracelog line, send it. - threads::alock h(this->buffer_mutex); - lines_waiting.push_back(str); - if(!this->unprocessed_lines) { - this->unprocessed_lines = true; - runuifun([this]() { this->process_lines(); }); - } - if(this->singlestepping && true_instruction) { - debug_request_break(); - this->singlestepping = false; - } - }, [this]() { - //Dtor! - auto tmp = this; - if(!tmp->trace_active) - return; - tmp->kill_debug_hooks(); - //We can't use this anymore. - runuifun([tmp]() { - tmp->enabled->SetValue(false); - tmp->m_singlestep->Enable(false); - }); - }); - this->trace_handle_frame = debug_add_frame_callback([this](uint64_t frame, - bool loadstate) { - std::ostringstream xstr; - xstr << "------------ "; - xstr << "Frame " << frame; - if(loadstate) xstr << " (loadstated)"; - xstr << " ------------"; - std::string str = xstr.str(); - threads::alock h(this->buffer_mutex); - lines_waiting.push_back(str); - if(!this->unprocessed_lines) { - this->unprocessed_lines = true; - runuifun([this]() { this->process_lines(); }); - } - }, [this]() { - auto tmp = this; - if(!tmp->trace_active) - return; - debug_remove_callback(0, DEBUG_TRACE, trace_handle_frame); - }); + lsnes_instance.dbg.add_callback(cpuid, DEBUG_TRACE, *this); + lsnes_instance.dbg.add_callback(0, DEBUG_FRAME, *this); this->trace_active = true; } else if(trace_active) { this->trace_active = false; @@ -1051,27 +1070,19 @@ back: std::pair i2 = std::make_pair(addr, dtype); if(!trace_active) { //We'll register this later. - rwx_breakpoints[i2] = debug_handle(); + rwx_breakpoints[i2] = false; return; } - rwx_breakpoints[i2] = debug_add_callback(i2.first, i2.second, - [this, i2](uint64_t addr, uint64_t value) { - this->do_rwx_break(addr, value, i2.second); - }, [this, i2] { - //We need to kill this hook if still active. - auto& h = rwx_breakpoints[i2]; - if(h.handle) - debug_remove_callback(i2.first, i2.second, h); - h.handle = NULL; - }); + lsnes_instance.dbg.add_callback(i2.first, i2.second, *this); + rwx_breakpoints[i2] = true; } void wxwin_tracelog::remove_breakpoint(uint64_t addr, debug_type dtype) { std::pair i2 = std::make_pair(addr, dtype); auto& h = rwx_breakpoints[i2]; - if(h.handle) - debug_remove_callback(i2.first, i2.second, h); + if(h) + lsnes_instance.dbg.remove_callback(i2.first, i2.second, *this); rwx_breakpoints.erase(i2); } diff --git a/src/platform/wxwidgets/tracelogmenu.cpp b/src/platform/wxwidgets/tracelogmenu.cpp index 2803bba9..2d82fd80 100644 --- a/src/platform/wxwidgets/tracelogmenu.cpp +++ b/src/platform/wxwidgets/tracelogmenu.cpp @@ -13,7 +13,7 @@ tracelog_menu::tracelog_menu(wxWindow* win, int wxid_low, int wxid_high) wxid_range_high = wxid_high; win->Connect(wxid_low, wxid_high, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(tracelog_menu::on_select), NULL, this); - debug_set_tracelog_change_cb([this]() { runuifun([this]() { this->update(); }); }); + lsnes_instance.dbg.set_tracelog_change_cb([this]() { runuifun([this]() { this->update(); }); }); corechange.set(notify_core_change, [this]() { runuifun([this]() { this->update(); }); }); } @@ -36,11 +36,11 @@ void tracelog_menu::on_select(wxCommandEvent& e) try { std::string filename = choose_file_save(pwin, "Save " + cpunames[rid] + " Trace", lsnes_instance.project.moviepath(), filetype_trace, ""); - debug_tracelog(rid, filename); + lsnes_instance.dbg.tracelog(rid, filename); } catch(canceled_exception& e) { } } else { - debug_tracelog(rid, ""); + lsnes_instance.dbg.tracelog(rid, ""); } } update(); @@ -56,7 +56,7 @@ void tracelog_menu::update() for(auto i : _items) { items.push_back(AppendCheckItem(wxid_range_low + 2 * id, towxstring(i + " (to file)..."))); cpunames[id] = i; - items[id]->Check(debug_tracelogging(id)); + items[id]->Check(lsnes_instance.dbg.is_tracelogging(id)); id++; } items.push_back(AppendSeparator());