Fix kill_hooks()
[lsnes.git] / src / core / debug.cpp
blob082e5ae132c33b3b552e8b296cdd84ea035ca832
1 #include "core/command.hpp"
2 #include "core/debug.hpp"
3 #include "core/dispatch.hpp"
4 #include "core/moviedata.hpp"
5 #include "library/directory.hpp"
6 #include <stdexcept>
7 #include <list>
8 #include <map>
9 #include <fstream>
12 namespace
14 struct cb_rwx
16 std::function<void(uint64_t addr, uint64_t value)> cb;
17 std::function<void()> dtor;
19 struct cb_trace
21 std::function<void(uint64_t proc, const char* str)> cb;
22 std::function<void()> dtor;
24 typedef std::list<cb_rwx> cb_list;
25 typedef std::list<cb_trace> cb2_list;
26 std::map<uint64_t, cb_list> read_cb;
27 std::map<uint64_t, cb_list> write_cb;
28 std::map<uint64_t, cb_list> exec_cb;
29 std::map<uint64_t, cb2_list> trace_cb;
30 cb_list dummy_cb; //Always empty.
31 cb2_list dummy_cb2; //Always empty.
32 uint64_t xmask = 1;
33 std::function<void()> tracelog_change_cb;
34 struct dispatch::target<> corechange;
35 bool corechange_r = false;
37 struct tracelog_file
39 std::ofstream stream;
40 std::string full_filename;
41 unsigned refcnt;
43 std::map<uint64_t, std::pair<tracelog_file*, debug_handle>> trace_outputs;
45 std::map<uint64_t, cb_list>& get_lists(debug_type type)
47 switch(type) {
48 case DEBUG_READ: return read_cb;
49 case DEBUG_WRITE: return write_cb;
50 case DEBUG_EXEC: return exec_cb;
51 default: throw std::runtime_error("Invalid debug callback type");
55 unsigned debug_flag(debug_type type)
57 switch(type) {
58 case DEBUG_READ: return 1;
59 case DEBUG_WRITE: return 2;
60 case DEBUG_EXEC: return 4;
61 case DEBUG_TRACE: return 8;
62 default: throw std::runtime_error("Invalid debug callback type");
67 const uint64_t debug_all_addr = 0xFFFFFFFFFFFFFFFFULL;
69 namespace
71 template<class T> debug_handle _debug_add_callback(std::map<uint64_t, std::list<T>>& cb, uint64_t addr,
72 debug_type type, T fn, std::function<void()> dtor)
74 if(!corechange_r) {
75 corechange.set(notify_core_change, []() { debug_core_change(); });
76 corechange_r = true;
78 if(!cb.count(addr))
79 our_rom.rtype->set_debug_flags(addr, debug_flag(type), 0);
81 auto& lst = cb[addr];
82 lst.push_back(fn);
83 debug_handle h;
84 h.handle = &*lst.rbegin();
85 return h;
88 template<class T> void _debug_remove_callback(T& cb, uint64_t addr, debug_type type, debug_handle handle)
90 if(!cb.count(addr)) return;
91 auto& l = cb[addr];
92 for(auto i = l.begin(); i != l.end(); i++) {
93 if(&*i == handle.handle) {
94 l.erase(i);
95 break;
98 if(cb[addr].empty()) {
99 cb.erase(addr);
100 our_rom.rtype->set_debug_flags(addr, 0, debug_flag(type));
104 template<class T> void kill_hooks(T& cblist)
106 while(!cblist.empty()) {
107 if(cblist.begin()->second.empty()) {
108 cblist.erase(cblist.begin()->first);
109 continue;
111 auto tmp = cblist.begin()->second.begin();
112 tmp->dtor();
114 cblist.clear();
118 debug_handle debug_add_callback(uint64_t addr, debug_type type, std::function<void(uint64_t addr, uint64_t value)> fn,
119 std::function<void()> dtor)
121 std::map<uint64_t, cb_list>& cb = get_lists(type);
122 cb_rwx t;
123 t.cb = fn;
124 t.dtor = dtor;
125 return _debug_add_callback(cb, addr, type, t, dtor);
128 debug_handle debug_add_trace_callback(uint64_t proc, std::function<void(uint64_t proc, const char* str)> fn,
129 std::function<void()> dtor)
131 cb_trace t;
132 t.cb = fn;
133 t.dtor = dtor;
134 return _debug_add_callback(trace_cb, proc, DEBUG_TRACE, t, dtor);
137 void debug_remove_callback(uint64_t addr, debug_type type, debug_handle handle)
139 if(type == DEBUG_TRACE) {
140 _debug_remove_callback(trace_cb, addr, DEBUG_TRACE, handle);
141 } else {
142 _debug_remove_callback(get_lists(type), addr, type, handle);
146 void debug_fire_callback_read(uint64_t addr, uint64_t value)
148 cb_list* cb1 = read_cb.count(debug_all_addr) ? &read_cb[debug_all_addr] : &dummy_cb;
149 cb_list* cb2 = read_cb.count(addr) ? &read_cb[addr] : &dummy_cb;
150 for(auto& i : *cb1) i.cb(addr, value);
151 for(auto& i : *cb2) i.cb(addr, value);
154 void debug_fire_callback_write(uint64_t addr, uint64_t value)
156 cb_list* cb1 = write_cb.count(debug_all_addr) ? &write_cb[debug_all_addr] : &dummy_cb;
157 cb_list* cb2 = write_cb.count(addr) ? &write_cb[addr] : &dummy_cb;
158 for(auto& i : *cb1) i.cb(addr, value);
159 for(auto& i : *cb2) i.cb(addr, value);
162 void debug_fire_callback_exec(uint64_t addr, uint64_t value)
164 cb_list* cb1 = exec_cb.count(debug_all_addr) ? &exec_cb[debug_all_addr] : &dummy_cb;
165 cb_list* cb2 = exec_cb.count(addr) ? &exec_cb[addr] : &dummy_cb;
166 if(value & xmask)
167 for(auto& i : *cb1) i.cb(addr, value);
168 for(auto& i : *cb2) i.cb(addr, value);
171 void debug_fire_callback_trace(uint64_t proc, const char* str)
173 cb2_list* cb = trace_cb.count(proc) ? &trace_cb[proc] : &dummy_cb2;
174 for(auto& i : *cb) i.cb(proc, str);
177 void debug_set_cheat(uint64_t addr, uint64_t value)
179 our_rom.rtype->set_cheat(addr, value, true);
182 void debug_clear_cheat(uint64_t addr)
184 our_rom.rtype->set_cheat(addr, 0, false);
187 void debug_setxmask(uint64_t mask)
189 xmask = mask;
192 void debug_tracelog(uint64_t proc, const std::string& filename)
194 if(filename == "") {
195 if(!trace_outputs.count(proc))
196 return;
197 debug_remove_callback(proc, DEBUG_TRACE, trace_outputs[proc].second);
198 trace_outputs[proc].first->refcnt--;
199 if(!trace_outputs[proc].first->refcnt) {
200 delete trace_outputs[proc].first;
202 trace_outputs.erase(proc);
203 messages << "Stopped tracelogging processor #" << proc << std::endl;
204 if(tracelog_change_cb) tracelog_change_cb();
205 return;
207 if(trace_outputs.count(proc)) throw std::runtime_error("Already tracelogging");
208 std::string full_filename = get_absolute_path(filename);
209 bool found = false;
210 for(auto i : trace_outputs) {
211 if(i.second.first->full_filename == full_filename) {
212 i.second.first->refcnt++;
213 trace_outputs[proc].first = i.second.first;
214 found = true;
215 break;
218 if(!found) {
219 trace_outputs[proc].first = new tracelog_file;
220 trace_outputs[proc].first->refcnt = 1;
221 trace_outputs[proc].first->full_filename = full_filename;
222 trace_outputs[proc].first->stream.open(full_filename);
223 if(!trace_outputs[proc].first->stream) {
224 delete trace_outputs[proc].first;
225 trace_outputs.erase(proc);
226 throw std::runtime_error("Can't open '" + full_filename + "'");
229 trace_outputs[proc].second = debug_add_trace_callback(proc, [](uint64_t proc, const char* str) {
230 if(!trace_outputs.count(proc)) return;
231 trace_outputs[proc].first->stream << str << std::endl;
232 }, [proc]() { debug_tracelog(proc, ""); });
233 messages << "Tracelogging processor #" << proc << " to '" << filename << "'" << std::endl;
234 if(tracelog_change_cb) tracelog_change_cb();
237 bool debug_tracelogging(uint64_t proc)
239 return (trace_outputs.count(proc) != 0);
242 void debug_set_tracelog_change_cb(std::function<void()> cb)
244 tracelog_change_cb = cb;
247 void debug_core_change()
249 our_rom.rtype->debug_reset();
250 kill_hooks(read_cb);
251 kill_hooks(write_cb);
252 kill_hooks(exec_cb);
253 kill_hooks(trace_cb);
256 namespace
258 command::fnptr<> callbacks_show(lsnes_cmd, "show-callbacks", "", "",
259 []() throw(std::bad_alloc, std::runtime_error) {
260 for(auto& i : read_cb)
261 for(auto& j : i.second)
262 messages << "READ addr=" << i.first << " handle=" << &j << std::endl;
263 for(auto& i : write_cb)
264 for(auto& j : i.second)
265 messages << "WRITE addr=" << i.first << " handle=" << &j << std::endl;
266 for(auto& i : exec_cb)
267 for(auto& j : i.second)
268 messages << "EXEC addr=" << i.first << " handle=" << &j << std::endl;
269 for(auto& i : trace_cb)
270 for(auto& j : i.second)
271 messages << "TRACE proc=" << i.first << " handle=" << &j << std::endl;
274 command::fnptr<const std::string&> generate_event(lsnes_cmd, "generate-memory-event", "", "",
275 [](const std::string& args) throw(std::bad_alloc, std::runtime_error) {
276 regex_results r = regex("([^ \t]+) ([^ \t]+) (.+)", args);
277 if(!r) throw std::runtime_error("generate-memory-event: Bad arguments");
278 if(r[1] == "r") {
279 uint64_t addr = parse_value<uint64_t>(r[2]);
280 uint64_t val = parse_value<uint64_t>(r[3]);
281 debug_fire_callback_read(addr, val);
282 } else if(r[1] == "w") {
283 uint64_t addr = parse_value<uint64_t>(r[2]);
284 uint64_t val = parse_value<uint64_t>(r[3]);
285 debug_fire_callback_write(addr, val);
286 } else if(r[1] == "x") {
287 uint64_t addr = parse_value<uint64_t>(r[2]);
288 uint64_t val = parse_value<uint64_t>(r[3]);
289 debug_fire_callback_exec(addr, val);
290 } else if(r[1] == "t") {
291 uint64_t proc = parse_value<uint64_t>(r[2]);
292 std::string str = r[3];
293 debug_fire_callback_trace(proc, str.c_str());
294 } else
295 throw std::runtime_error("Invalid operation");
298 command::fnptr<const std::string&> tracelog(lsnes_cmd, "tracelog", "Trace log control",
299 "Trace log control\nSyntax: tracelog <cpuid> <file> Start tracing\nSyntax: tracelog <cpuid> "
300 "End tracing", [](const std::string& args) throw(std::bad_alloc, std::runtime_error) {
301 regex_results r = regex("([^ \t]+)([ \t]+(.+))?", args);
302 if(!r) throw std::runtime_error("tracelog: Bad arguments");
303 std::string cpu = r[1];
304 std::string filename = r[3];
305 uint64_t _cpu = 0;
306 for(auto i : our_rom.rtype->get_trace_cpus()) {
307 if(cpu == i)
308 goto out;
309 _cpu++;
311 throw std::runtime_error("tracelog: Invalid CPU");
312 out:
313 debug_tracelog(_cpu, filename);