Trace logger (Window): Show start of frame
[lsnes.git] / src / core / debug.cpp
blob32ce481fcc9502e79dcb03df30cd3853ae4c7fd8
1 #include "core/command.hpp"
2 #include "core/debug.hpp"
3 #include "core/dispatch.hpp"
4 #include "core/mainloop.hpp"
5 #include "core/moviedata.hpp"
6 #include "library/directory.hpp"
7 #include <stdexcept>
8 #include <list>
9 #include <map>
10 #include <fstream>
13 namespace
15 struct cb_rwx
17 std::function<void(uint64_t addr, uint64_t value)> cb;
18 std::function<void()> dtor;
20 struct cb_trace
22 std::function<void(uint64_t proc, const char* str, bool true_insn)> cb;
23 std::function<void()> dtor;
25 struct cb_frame
27 std::function<void(uint64_t frame, bool loadstate)> cb;
28 std::function<void()> dtor;
30 typedef std::list<cb_rwx> cb_list;
31 typedef std::list<cb_trace> cb2_list;
32 typedef std::list<cb_frame> cb3_list;
33 std::map<uint64_t, cb_list> read_cb;
34 std::map<uint64_t, cb_list> write_cb;
35 std::map<uint64_t, cb_list> exec_cb;
36 std::map<uint64_t, cb2_list> trace_cb;
37 std::map<uint64_t, cb3_list> frame_cb;
38 cb_list dummy_cb; //Always empty.
39 cb2_list dummy_cb2; //Always empty.
40 cb3_list dummy_cb3; //Always empty.
41 uint64_t xmask = 1;
42 std::function<void()> tracelog_change_cb;
43 struct dispatch::target<> corechange;
44 bool corechange_r = false;
45 bool requesting_break = false;
47 struct tracelog_file
49 std::ofstream stream;
50 std::string full_filename;
51 unsigned refcnt;
53 std::map<uint64_t, std::pair<tracelog_file*, debug_handle>> trace_outputs;
55 std::map<uint64_t, cb_list>& get_lists(debug_type type)
57 switch(type) {
58 case DEBUG_READ: return read_cb;
59 case DEBUG_WRITE: return write_cb;
60 case DEBUG_EXEC: return exec_cb;
61 default: throw std::runtime_error("Invalid debug callback type");
65 unsigned debug_flag(debug_type type)
67 switch(type) {
68 case DEBUG_READ: return 1;
69 case DEBUG_WRITE: return 2;
70 case DEBUG_EXEC: return 4;
71 case DEBUG_TRACE: return 8;
72 case DEBUG_FRAME: return 0;
73 default: throw std::runtime_error("Invalid debug callback type");
78 const uint64_t debug_all_addr = 0xFFFFFFFFFFFFFFFFULL;
80 namespace
82 template<class T> debug_handle _debug_add_callback(std::map<uint64_t, std::list<T>>& cb, uint64_t addr,
83 debug_type type, T fn, std::function<void()> dtor)
85 if(!corechange_r) {
86 corechange.set(notify_core_change, []() { debug_core_change(); });
87 corechange_r = true;
89 if(!cb.count(addr) && type != DEBUG_FRAME)
90 our_rom.rtype->set_debug_flags(addr, debug_flag(type), 0);
92 auto& lst = cb[addr];
93 lst.push_back(fn);
94 debug_handle h;
95 h.handle = &*lst.rbegin();
96 return h;
99 template<class T> void _debug_remove_callback(T& cb, uint64_t addr, debug_type type, debug_handle handle)
101 if(!cb.count(addr)) return;
102 auto& l = cb[addr];
103 for(auto i = l.begin(); i != l.end(); i++) {
104 if(&*i == handle.handle) {
105 l.erase(i);
106 break;
109 if(cb[addr].empty()) {
110 cb.erase(addr);
111 if(type != DEBUG_FRAME)
112 our_rom.rtype->set_debug_flags(addr, 0, debug_flag(type));
116 template<class T> void kill_hooks(T& cblist)
118 while(!cblist.empty()) {
119 if(cblist.begin()->second.empty()) {
120 cblist.erase(cblist.begin()->first);
121 continue;
123 auto tmp = cblist.begin()->second.begin();
124 tmp->dtor();
126 cblist.clear();
130 debug_handle debug_add_callback(uint64_t addr, debug_type type, std::function<void(uint64_t addr, uint64_t value)> fn,
131 std::function<void()> dtor)
133 std::map<uint64_t, cb_list>& cb = get_lists(type);
134 cb_rwx t;
135 t.cb = fn;
136 t.dtor = dtor;
137 return _debug_add_callback(cb, addr, type, t, dtor);
140 debug_handle debug_add_trace_callback(uint64_t proc, std::function<void(uint64_t proc, const char* str,
141 bool true_insn)> fn, std::function<void()> dtor)
143 cb_trace t;
144 t.cb = fn;
145 t.dtor = dtor;
146 return _debug_add_callback(trace_cb, proc, DEBUG_TRACE, t, dtor);
149 debug_handle debug_add_frame_callback(std::function<void(uint64_t frame, bool loadstate)> fn,
150 std::function<void()> dtor)
152 cb_frame t;
153 t.cb = fn;
154 t.dtor = dtor;
155 return _debug_add_callback(frame_cb, 0, DEBUG_FRAME, t, dtor);
158 void debug_remove_callback(uint64_t addr, debug_type type, debug_handle handle)
160 if(type == DEBUG_FRAME) {
161 _debug_remove_callback(frame_cb, 0, DEBUG_FRAME, handle);
162 } else if(type == DEBUG_TRACE) {
163 _debug_remove_callback(trace_cb, addr, DEBUG_TRACE, handle);
164 } else {
165 _debug_remove_callback(get_lists(type), addr, type, handle);
169 void debug_fire_callback_read(uint64_t addr, uint64_t value)
171 requesting_break = false;
172 cb_list* cb1 = read_cb.count(debug_all_addr) ? &read_cb[debug_all_addr] : &dummy_cb;
173 cb_list* cb2 = read_cb.count(addr) ? &read_cb[addr] : &dummy_cb;
174 auto _cb1 = *cb1;
175 auto _cb2 = *cb2;
176 for(auto& i : _cb1) i.cb(addr, value);
177 for(auto& i : _cb2) i.cb(addr, value);
178 if(requesting_break)
179 do_break_pause();
182 void debug_fire_callback_write(uint64_t addr, uint64_t value)
184 requesting_break = false;
185 cb_list* cb1 = write_cb.count(debug_all_addr) ? &write_cb[debug_all_addr] : &dummy_cb;
186 cb_list* cb2 = write_cb.count(addr) ? &write_cb[addr] : &dummy_cb;
187 auto _cb1 = *cb1;
188 auto _cb2 = *cb2;
189 for(auto& i : _cb1) i.cb(addr, value);
190 for(auto& i : _cb2) i.cb(addr, value);
191 if(requesting_break)
192 do_break_pause();
195 void debug_fire_callback_exec(uint64_t addr, uint64_t value)
197 requesting_break = false;
198 cb_list* cb1 = exec_cb.count(debug_all_addr) ? &exec_cb[debug_all_addr] : &dummy_cb;
199 cb_list* cb2 = exec_cb.count(addr) ? &exec_cb[addr] : &dummy_cb;
200 auto _cb1 = *cb1;
201 auto _cb2 = *cb2;
202 if(value & xmask)
203 for(auto& i : _cb1) i.cb(addr, value);
204 for(auto& i : _cb2) i.cb(addr, value);
205 if(requesting_break)
206 do_break_pause();
209 void debug_fire_callback_trace(uint64_t proc, const char* str, bool true_insn)
211 requesting_break = false;
212 cb2_list* cb = trace_cb.count(proc) ? &trace_cb[proc] : &dummy_cb2;
213 auto _cb = *cb;
214 for(auto& i : _cb) i.cb(proc, str, true_insn);
215 if(requesting_break)
216 do_break_pause();
219 void debug_fire_callback_frame(uint64_t frame, bool loadstate)
221 cb3_list* cb = frame_cb.count(0) ? &frame_cb[0] : &dummy_cb3;
222 auto _cb = *cb;
223 for(auto& i : _cb) i.cb(frame, loadstate);
226 void debug_set_cheat(uint64_t addr, uint64_t value)
228 our_rom.rtype->set_cheat(addr, value, true);
231 void debug_clear_cheat(uint64_t addr)
233 our_rom.rtype->set_cheat(addr, 0, false);
236 void debug_setxmask(uint64_t mask)
238 xmask = mask;
241 void debug_tracelog(uint64_t proc, const std::string& filename)
243 if(filename == "") {
244 if(!trace_outputs.count(proc))
245 return;
246 debug_remove_callback(proc, DEBUG_TRACE, trace_outputs[proc].second);
247 trace_outputs[proc].first->refcnt--;
248 if(!trace_outputs[proc].first->refcnt) {
249 delete trace_outputs[proc].first;
251 trace_outputs.erase(proc);
252 messages << "Stopped tracelogging processor #" << proc << std::endl;
253 if(tracelog_change_cb) tracelog_change_cb();
254 return;
256 if(trace_outputs.count(proc)) throw std::runtime_error("Already tracelogging");
257 std::string full_filename = get_absolute_path(filename);
258 bool found = false;
259 for(auto i : trace_outputs) {
260 if(i.second.first->full_filename == full_filename) {
261 i.second.first->refcnt++;
262 trace_outputs[proc].first = i.second.first;
263 found = true;
264 break;
267 if(!found) {
268 trace_outputs[proc].first = new tracelog_file;
269 trace_outputs[proc].first->refcnt = 1;
270 trace_outputs[proc].first->full_filename = full_filename;
271 trace_outputs[proc].first->stream.open(full_filename);
272 if(!trace_outputs[proc].first->stream) {
273 delete trace_outputs[proc].first;
274 trace_outputs.erase(proc);
275 throw std::runtime_error("Can't open '" + full_filename + "'");
278 trace_outputs[proc].second = debug_add_trace_callback(proc, [](uint64_t proc, const char* str, bool dummy) {
279 if(!trace_outputs.count(proc)) return;
280 trace_outputs[proc].first->stream << str << std::endl;
281 }, [proc]() { debug_tracelog(proc, ""); });
282 messages << "Tracelogging processor #" << proc << " to '" << filename << "'" << std::endl;
283 if(tracelog_change_cb) tracelog_change_cb();
286 bool debug_tracelogging(uint64_t proc)
288 return (trace_outputs.count(proc) != 0);
291 void debug_set_tracelog_change_cb(std::function<void()> cb)
293 tracelog_change_cb = cb;
296 void debug_core_change()
298 our_rom.rtype->debug_reset();
299 kill_hooks(read_cb);
300 kill_hooks(write_cb);
301 kill_hooks(exec_cb);
302 kill_hooks(trace_cb);
305 void debug_request_break()
307 requesting_break = true;
310 namespace
312 command::fnptr<> callbacks_show(lsnes_cmd, "show-callbacks", "", "",
313 []() throw(std::bad_alloc, std::runtime_error) {
314 for(auto& i : read_cb)
315 for(auto& j : i.second)
316 messages << "READ addr=" << i.first << " handle=" << &j << std::endl;
317 for(auto& i : write_cb)
318 for(auto& j : i.second)
319 messages << "WRITE addr=" << i.first << " handle=" << &j << std::endl;
320 for(auto& i : exec_cb)
321 for(auto& j : i.second)
322 messages << "EXEC addr=" << i.first << " handle=" << &j << std::endl;
323 for(auto& i : trace_cb)
324 for(auto& j : i.second)
325 messages << "TRACE proc=" << i.first << " handle=" << &j << std::endl;
326 for(auto& i : frame_cb)
327 for(auto& j : i.second)
328 messages << "FRAME handle=" << &j << std::endl;
331 command::fnptr<const std::string&> generate_event(lsnes_cmd, "generate-memory-event", "", "",
332 [](const std::string& args) throw(std::bad_alloc, std::runtime_error) {
333 regex_results r = regex("([^ \t]+) ([^ \t]+) (.+)", args);
334 if(!r) throw std::runtime_error("generate-memory-event: Bad arguments");
335 if(r[1] == "r") {
336 uint64_t addr = parse_value<uint64_t>(r[2]);
337 uint64_t val = parse_value<uint64_t>(r[3]);
338 debug_fire_callback_read(addr, val);
339 } else if(r[1] == "w") {
340 uint64_t addr = parse_value<uint64_t>(r[2]);
341 uint64_t val = parse_value<uint64_t>(r[3]);
342 debug_fire_callback_write(addr, val);
343 } else if(r[1] == "x") {
344 uint64_t addr = parse_value<uint64_t>(r[2]);
345 uint64_t val = parse_value<uint64_t>(r[3]);
346 debug_fire_callback_exec(addr, val);
347 } else if(r[1] == "t") {
348 uint64_t proc = parse_value<uint64_t>(r[2]);
349 std::string str = r[3];
350 debug_fire_callback_trace(proc, str.c_str());
351 } else
352 throw std::runtime_error("Invalid operation");
355 command::fnptr<const std::string&> tracelog(lsnes_cmd, "tracelog", "Trace log control",
356 "Trace log control\nSyntax: tracelog <cpuid> <file> Start tracing\nSyntax: tracelog <cpuid> "
357 "End tracing", [](const std::string& args) throw(std::bad_alloc, std::runtime_error) {
358 regex_results r = regex("([^ \t]+)([ \t]+(.+))?", args);
359 if(!r) throw std::runtime_error("tracelog: Bad arguments");
360 std::string cpu = r[1];
361 std::string filename = r[3];
362 uint64_t _cpu = 0;
363 for(auto i : our_rom.rtype->get_trace_cpus()) {
364 if(cpu == i)
365 goto out;
366 _cpu++;
368 throw std::runtime_error("tracelog: Invalid CPU");
369 out:
370 debug_tracelog(_cpu, filename);