mirror of
https://github.com/rodamaral/lsnes.git
synced 2024-06-02 03:27:28 -04:00
358 lines
9.4 KiB
C++
358 lines
9.4 KiB
C++
#include "cmdhelp/debug.hpp"
|
|
#include "core/command.hpp"
|
|
#include "core/debug.hpp"
|
|
#include "core/dispatch.hpp"
|
|
#include "core/instance.hpp"
|
|
#include "core/mainloop.hpp"
|
|
#include "core/messages.hpp"
|
|
#include "core/moviedata.hpp"
|
|
#include "core/rom.hpp"
|
|
#include "library/directory.hpp"
|
|
#include "library/memoryspace.hpp"
|
|
|
|
#include <functional>
|
|
#include <stdexcept>
|
|
#include <list>
|
|
#include <map>
|
|
#include <fstream>
|
|
|
|
|
|
namespace
|
|
{
|
|
|
|
unsigned debug_flag(debug_context::etype type)
|
|
{
|
|
switch(type) {
|
|
case debug_context::DEBUG_READ: return 1;
|
|
case debug_context::DEBUG_WRITE: return 2;
|
|
case debug_context::DEBUG_EXEC: return 4;
|
|
case debug_context::DEBUG_TRACE: return 8;
|
|
case debug_context::DEBUG_FRAME: return 0;
|
|
default: throw std::runtime_error("Invalid debug callback type");
|
|
}
|
|
}
|
|
}
|
|
|
|
namespace
|
|
{
|
|
template<class T> void kill_hooks(T& cblist, debug_context::etype 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)->killed(key, type);
|
|
cblist.begin()->second.erase(cblist.begin()->second.begin());
|
|
}
|
|
cblist.clear();
|
|
}
|
|
}
|
|
|
|
debug_context::debug_context(emulator_dispatch& _dispatch, loaded_rom& _rom, memory_space& _mspace,
|
|
command::group& _cmd)
|
|
: edispatch(_dispatch), rom(_rom), mspace(_mspace), cmd(_cmd),
|
|
showhooks(cmd, CDEBUG::scb, [this]() { this->do_showhooks(); }),
|
|
genevent(cmd, CDEBUG::genevt, [this](const std::string& a) { this->do_genevent(a); }),
|
|
tracecmd(cmd, CDEBUG::tr, [this](const std::string& a) { this->do_tracecmd(a); })
|
|
{
|
|
}
|
|
|
|
debug_context::callback_base::~callback_base()
|
|
{
|
|
}
|
|
|
|
const uint64_t debug_context::all_addresses = 0xFFFFFFFFFFFFFFFFULL;
|
|
|
|
void debug_context::add_callback(uint64_t addr, debug_context::etype type, debug_context::callback_base& cb)
|
|
{
|
|
auto& core = CORE();
|
|
std::map<uint64_t, cb_list>& xcb = get_lists(type);
|
|
if(!corechange_r) {
|
|
corechange.set(edispatch.core_change, [this]() { this->core_change(); });
|
|
corechange_r = true;
|
|
}
|
|
if(!xcb.count(addr) && type != DEBUG_FRAME)
|
|
core.rom->set_debug_flags(addr, debug_flag(type), 0);
|
|
auto& lst = xcb[addr];
|
|
lst.push_back(&cb);
|
|
}
|
|
|
|
void debug_context::remove_callback(uint64_t addr, debug_context::etype type, debug_context::callback_base& cb)
|
|
{
|
|
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)
|
|
rom.set_debug_flags(addr, 0, debug_flag(type));
|
|
}
|
|
}
|
|
|
|
void debug_context::do_callback_read(uint64_t addr, uint64_t value)
|
|
{
|
|
params p;
|
|
p.type = DEBUG_READ;
|
|
p.rwx.addr = addr;
|
|
p.rwx.value = value;
|
|
|
|
requesting_break = false;
|
|
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->callback(p);
|
|
for(auto& i : _cb2) i->callback(p);
|
|
if(requesting_break)
|
|
do_break_pause();
|
|
}
|
|
|
|
void debug_context::do_callback_write(uint64_t addr, uint64_t value)
|
|
{
|
|
params p;
|
|
p.type = DEBUG_WRITE;
|
|
p.rwx.addr = addr;
|
|
p.rwx.value = value;
|
|
|
|
requesting_break = false;
|
|
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->callback(p);
|
|
for(auto& i : _cb2) i->callback(p);
|
|
if(requesting_break)
|
|
do_break_pause();
|
|
}
|
|
|
|
void debug_context::do_callback_exec(uint64_t addr, uint64_t cpu)
|
|
{
|
|
params p;
|
|
p.type = DEBUG_EXEC;
|
|
p.rwx.addr = addr;
|
|
p.rwx.value = cpu;
|
|
|
|
requesting_break = false;
|
|
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((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_context::do_callback_trace(uint64_t cpu, const char* str, bool true_insn)
|
|
{
|
|
params p;
|
|
p.type = DEBUG_TRACE;
|
|
p.trace.cpu = cpu;
|
|
p.trace.decoded_insn = str;
|
|
p.trace.true_insn = true_insn;
|
|
|
|
requesting_break = false;
|
|
cb_list* cb = trace_cb.count(cpu) ? &trace_cb[cpu] : &dummy_cb;
|
|
auto _cb = *cb;
|
|
for(auto& i : _cb) i->callback(p);
|
|
if(requesting_break)
|
|
do_break_pause();
|
|
}
|
|
|
|
void debug_context::do_callback_frame(uint64_t frame, bool loadstate)
|
|
{
|
|
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->callback(p);
|
|
}
|
|
|
|
void debug_context::set_cheat(uint64_t addr, uint64_t value)
|
|
{
|
|
rom.set_cheat(addr, value, true);
|
|
}
|
|
|
|
void debug_context::clear_cheat(uint64_t addr)
|
|
{
|
|
rom.set_cheat(addr, 0, false);
|
|
}
|
|
|
|
void debug_context::setxmask(uint64_t mask)
|
|
{
|
|
xmask = mask;
|
|
}
|
|
|
|
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()
|
|
{
|
|
rom.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_context::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_context::etype 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;
|
|
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();
|
|
return;
|
|
}
|
|
if(trace_outputs.count(proc)) throw std::runtime_error("Already tracelogging");
|
|
std::string full_filename = directory::absolute_path(filename);
|
|
bool found = false;
|
|
for(auto i : trace_outputs) {
|
|
if(i.second->full_filename == full_filename) {
|
|
i.second->refcnt++;
|
|
trace_outputs[proc] = i.second;
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
if(!found) {
|
|
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 + "'");
|
|
}
|
|
}
|
|
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();
|
|
}
|
|
|
|
void debug_context::do_showhooks()
|
|
{
|
|
for(auto& i : read_cb)
|
|
for(auto& j : i.second)
|
|
messages << "READ addr=" << mspace.address_to_textual(i.first) << " handle=" << &j
|
|
<< std::endl;
|
|
for(auto& i : write_cb)
|
|
for(auto& j : i.second)
|
|
messages << "WRITE addr=" << mspace.address_to_textual(i.first) << " handle=" << &j
|
|
<< std::endl;
|
|
for(auto& i : exec_cb)
|
|
for(auto& j : i.second)
|
|
messages << "EXEC addr=" << mspace.address_to_textual(i.first) << " handle=" << &j
|
|
<< std::endl;
|
|
for(auto& i : trace_cb)
|
|
for(auto& j : i.second)
|
|
messages << "TRACE proc=" << i.first << " handle=" << &j << std::endl;
|
|
for(auto& i : frame_cb)
|
|
for(auto& j : i.second)
|
|
messages << "FRAME handle=" << &j << std::endl;
|
|
}
|
|
|
|
void debug_context::do_genevent(const std::string& args)
|
|
{
|
|
regex_results r = regex("([^ \t]+) ([^ \t]+) (.+)", args);
|
|
if(!r) throw std::runtime_error("generate-memory-event: Bad arguments");
|
|
if(r[1] == "r") {
|
|
uint64_t addr = parse_value<uint64_t>(r[2]);
|
|
uint64_t val = parse_value<uint64_t>(r[3]);
|
|
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]);
|
|
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]);
|
|
do_callback_exec(addr, val);
|
|
} else if(r[1] == "t") {
|
|
uint64_t proc = parse_value<uint64_t>(r[2]);
|
|
std::string str = r[3];
|
|
do_callback_trace(proc, str.c_str());
|
|
} else
|
|
throw std::runtime_error("Invalid operation");
|
|
}
|
|
|
|
void debug_context::do_tracecmd(const std::string& args)
|
|
{
|
|
regex_results r = regex("([^ \t]+)([ \t]+(.+))?", args);
|
|
if(!r) throw std::runtime_error("tracelog: Bad arguments");
|
|
std::string cpu = r[1];
|
|
std::string filename = r[3];
|
|
uint64_t _cpu = 0;
|
|
for(auto i : rom.get_trace_cpus()) {
|
|
if(cpu == i)
|
|
goto out;
|
|
_cpu++;
|
|
}
|
|
throw std::runtime_error("tracelog: Invalid CPU");
|
|
out:
|
|
tracelog(_cpu, filename);
|
|
}
|