Refactor debugging into instance object

This commit is contained in:
Ilari Liusvaara 2014-05-21 18:32:17 +03:00
parent c5a31e85d8
commit adb1b29c67
8 changed files with 655 additions and 408 deletions

View file

@ -2,12 +2,9 @@
#define _debug__hpp__included__
#include <functional>
#include <fstream>
#include <cstdint>
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<void(uint64_t addr, uint64_t value)> fn, std::function<void()> dtor);
debug_handle debug_add_trace_callback(uint64_t proc, std::function<void(uint64_t proc, const char* str,
bool true_insn)> fn, std::function<void()> dtor);
debug_handle debug_add_frame_callback(std::function<void(uint64_t frame, bool loadstate)> fn,
std::function<void()> 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<void()> 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<void()> 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<debug_callback_base*> cb_list;
std::map<uint64_t, cb_list> read_cb;
std::map<uint64_t, cb_list> write_cb;
std::map<uint64_t, cb_list> exec_cb;
std::map<uint64_t, cb_list> trace_cb;
std::map<uint64_t, cb_list> frame_cb;
private:
cb_list dummy_cb; //Always empty.
uint64_t xmask = 1;
std::function<void()> 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<uint64_t, tracelog_file*> trace_outputs;
std::map<uint64_t, cb_list>& 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

View file

@ -4,6 +4,7 @@
#include <deque>
#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;

View file

@ -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<void(uint64_t addr, uint64_t value)> cb;
std::function<void()> dtor;
};
struct cb_trace
{
std::function<void(uint64_t proc, const char* str, bool true_insn)> cb;
std::function<void()> dtor;
};
struct cb_frame
{
std::function<void(uint64_t frame, bool loadstate)> cb;
std::function<void()> dtor;
};
typedef std::list<cb_rwx> cb_list;
typedef std::list<cb_trace> cb2_list;
typedef std::list<cb_frame> cb3_list;
std::map<uint64_t, cb_list> read_cb;
std::map<uint64_t, cb_list> write_cb;
std::map<uint64_t, cb_list> exec_cb;
std::map<uint64_t, cb2_list> trace_cb;
std::map<uint64_t, cb3_list> 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<void()> 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<uint64_t, std::pair<tracelog_file*, debug_handle>> trace_outputs;
std::map<uint64_t, cb_list>& 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<class T> debug_handle _debug_add_callback(std::map<uint64_t, std::list<T>>& cb, uint64_t addr,
debug_type type, T fn, std::function<void()> 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<class T> 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<class T> void kill_hooks(T& cblist)
template<class T> 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<void(uint64_t addr, uint64_t value)> fn,
std::function<void()> dtor)
debug_callback_base::~debug_callback_base()
{
std::map<uint64_t, cb_list>& 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<void(uint64_t proc, const char* str,
bool true_insn)> fn, std::function<void()> 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<uint64_t, cb_list>& 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<void(uint64_t frame, bool loadstate)> fn,
std::function<void()> 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<uint64_t, cb_list>& 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<void()> 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<void()> 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<uint64_t>(r[2]);
uint64_t val = parse_value<uint64_t>(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<uint64_t>(r[2]);
uint64_t val = parse_value<uint64_t>(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<uint64_t>(r[2]);
uint64_t val = parse_value<uint64_t>(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<uint64_t>(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);
});
}

View file

@ -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());
}

View file

@ -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<uint64_t, std::list<lua_debug_callback*>> 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<std::pair<debug_type, uint64_t>, 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<debug_type, uint64_t> 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<debug_type, uint64_t> 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<debug_type type>
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<debug_type type>
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<typename T, T (memory_space::*rfun)(uint64_t addr), bool (memory_space::*wfun)(uint64_t addr,
@ -477,10 +611,10 @@ namespace
addr = lua_get_read_address(P);
if(P.is_novalue()) {
debug_clear_cheat(addr);
CORE().dbg.clear_cheat(addr);
} else {
P(value);
debug_set_cheat(addr, value);
CORE().dbg.set_cheat(addr, value);
}
return 0;
}
@ -488,7 +622,7 @@ namespace
int setxmask(lua::state& L, lua::parameters& P)
{
auto value = P.arg<uint64_t>();
debug_setxmask(value);
CORE().dbg.setxmask(value);
return 0;
}

View file

@ -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;
}

View file

@ -505,7 +505,7 @@ namespace
std::vector<std::pair<uint64_t, debug_type>> 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<std::pair<uint64_t, debug_type>, debug_handle> rwx_breakpoints;
std::map<std::pair<uint64_t, debug_type>, 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<uint64_t, debug_type> 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<uint64_t, debug_type> 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);
}

View file

@ -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());