Fix some memory leak complaints from Valgrind
[lsnes.git] / src / core / debug.cpp
blobc0626c527d28dc92826ca3df784a05725879848f
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 typedef std::list<cb_rwx> cb_list;
26 typedef std::list<cb_trace> cb2_list;
27 std::map<uint64_t, cb_list> read_cb;
28 std::map<uint64_t, cb_list> write_cb;
29 std::map<uint64_t, cb_list> exec_cb;
30 std::map<uint64_t, cb2_list> trace_cb;
31 cb_list dummy_cb; //Always empty.
32 cb2_list dummy_cb2; //Always empty.
33 uint64_t xmask = 1;
34 std::function<void()> tracelog_change_cb;
35 struct dispatch::target<> corechange;
36 bool corechange_r = false;
37 bool requesting_break = false;
39 struct tracelog_file
41 std::ofstream stream;
42 std::string full_filename;
43 unsigned refcnt;
45 std::map<uint64_t, std::pair<tracelog_file*, debug_handle>> trace_outputs;
47 std::map<uint64_t, cb_list>& get_lists(debug_type type)
49 switch(type) {
50 case DEBUG_READ: return read_cb;
51 case DEBUG_WRITE: return write_cb;
52 case DEBUG_EXEC: return exec_cb;
53 default: throw std::runtime_error("Invalid debug callback type");
57 unsigned debug_flag(debug_type type)
59 switch(type) {
60 case DEBUG_READ: return 1;
61 case DEBUG_WRITE: return 2;
62 case DEBUG_EXEC: return 4;
63 case DEBUG_TRACE: return 8;
64 default: throw std::runtime_error("Invalid debug callback type");
69 const uint64_t debug_all_addr = 0xFFFFFFFFFFFFFFFFULL;
71 namespace
73 template<class T> debug_handle _debug_add_callback(std::map<uint64_t, std::list<T>>& cb, uint64_t addr,
74 debug_type type, T fn, std::function<void()> dtor)
76 if(!corechange_r) {
77 corechange.set(notify_core_change, []() { debug_core_change(); });
78 corechange_r = true;
80 if(!cb.count(addr))
81 our_rom.rtype->set_debug_flags(addr, debug_flag(type), 0);
83 auto& lst = cb[addr];
84 lst.push_back(fn);
85 debug_handle h;
86 h.handle = &*lst.rbegin();
87 return h;
90 template<class T> void _debug_remove_callback(T& cb, uint64_t addr, debug_type type, debug_handle handle)
92 if(!cb.count(addr)) return;
93 auto& l = cb[addr];
94 for(auto i = l.begin(); i != l.end(); i++) {
95 if(&*i == handle.handle) {
96 l.erase(i);
97 break;
100 if(cb[addr].empty()) {
101 cb.erase(addr);
102 our_rom.rtype->set_debug_flags(addr, 0, debug_flag(type));
106 template<class T> void kill_hooks(T& cblist)
108 while(!cblist.empty()) {
109 if(cblist.begin()->second.empty()) {
110 cblist.erase(cblist.begin()->first);
111 continue;
113 auto tmp = cblist.begin()->second.begin();
114 tmp->dtor();
116 cblist.clear();
120 debug_handle debug_add_callback(uint64_t addr, debug_type type, std::function<void(uint64_t addr, uint64_t value)> fn,
121 std::function<void()> dtor)
123 std::map<uint64_t, cb_list>& cb = get_lists(type);
124 cb_rwx t;
125 t.cb = fn;
126 t.dtor = dtor;
127 return _debug_add_callback(cb, addr, type, t, dtor);
130 debug_handle debug_add_trace_callback(uint64_t proc, std::function<void(uint64_t proc, const char* str,
131 bool true_insn)> fn, std::function<void()> dtor)
133 cb_trace t;
134 t.cb = fn;
135 t.dtor = dtor;
136 return _debug_add_callback(trace_cb, proc, DEBUG_TRACE, t, dtor);
139 void debug_remove_callback(uint64_t addr, debug_type type, debug_handle handle)
141 if(type == DEBUG_TRACE) {
142 _debug_remove_callback(trace_cb, addr, DEBUG_TRACE, handle);
143 } else {
144 _debug_remove_callback(get_lists(type), addr, type, handle);
148 void debug_fire_callback_read(uint64_t addr, uint64_t value)
150 requesting_break = false;
151 cb_list* cb1 = read_cb.count(debug_all_addr) ? &read_cb[debug_all_addr] : &dummy_cb;
152 cb_list* cb2 = read_cb.count(addr) ? &read_cb[addr] : &dummy_cb;
153 auto _cb1 = *cb1;
154 auto _cb2 = *cb2;
155 for(auto& i : _cb1) i.cb(addr, value);
156 for(auto& i : _cb2) i.cb(addr, value);
157 if(requesting_break)
158 do_break_pause();
161 void debug_fire_callback_write(uint64_t addr, uint64_t value)
163 requesting_break = false;
164 cb_list* cb1 = write_cb.count(debug_all_addr) ? &write_cb[debug_all_addr] : &dummy_cb;
165 cb_list* cb2 = write_cb.count(addr) ? &write_cb[addr] : &dummy_cb;
166 auto _cb1 = *cb1;
167 auto _cb2 = *cb2;
168 for(auto& i : _cb1) i.cb(addr, value);
169 for(auto& i : _cb2) i.cb(addr, value);
170 if(requesting_break)
171 do_break_pause();
174 void debug_fire_callback_exec(uint64_t addr, uint64_t value)
176 requesting_break = false;
177 cb_list* cb1 = exec_cb.count(debug_all_addr) ? &exec_cb[debug_all_addr] : &dummy_cb;
178 cb_list* cb2 = exec_cb.count(addr) ? &exec_cb[addr] : &dummy_cb;
179 auto _cb1 = *cb1;
180 auto _cb2 = *cb2;
181 if(value & xmask)
182 for(auto& i : _cb1) i.cb(addr, value);
183 for(auto& i : _cb2) i.cb(addr, value);
184 if(requesting_break)
185 do_break_pause();
188 void debug_fire_callback_trace(uint64_t proc, const char* str, bool true_insn)
190 requesting_break = false;
191 cb2_list* cb = trace_cb.count(proc) ? &trace_cb[proc] : &dummy_cb2;
192 auto _cb = *cb;
193 for(auto& i : _cb) i.cb(proc, str, true_insn);
194 if(requesting_break)
195 do_break_pause();
198 void debug_set_cheat(uint64_t addr, uint64_t value)
200 our_rom.rtype->set_cheat(addr, value, true);
203 void debug_clear_cheat(uint64_t addr)
205 our_rom.rtype->set_cheat(addr, 0, false);
208 void debug_setxmask(uint64_t mask)
210 xmask = mask;
213 void debug_tracelog(uint64_t proc, const std::string& filename)
215 if(filename == "") {
216 if(!trace_outputs.count(proc))
217 return;
218 debug_remove_callback(proc, DEBUG_TRACE, trace_outputs[proc].second);
219 trace_outputs[proc].first->refcnt--;
220 if(!trace_outputs[proc].first->refcnt) {
221 delete trace_outputs[proc].first;
223 trace_outputs.erase(proc);
224 messages << "Stopped tracelogging processor #" << proc << std::endl;
225 if(tracelog_change_cb) tracelog_change_cb();
226 return;
228 if(trace_outputs.count(proc)) throw std::runtime_error("Already tracelogging");
229 std::string full_filename = get_absolute_path(filename);
230 bool found = false;
231 for(auto i : trace_outputs) {
232 if(i.second.first->full_filename == full_filename) {
233 i.second.first->refcnt++;
234 trace_outputs[proc].first = i.second.first;
235 found = true;
236 break;
239 if(!found) {
240 trace_outputs[proc].first = new tracelog_file;
241 trace_outputs[proc].first->refcnt = 1;
242 trace_outputs[proc].first->full_filename = full_filename;
243 trace_outputs[proc].first->stream.open(full_filename);
244 if(!trace_outputs[proc].first->stream) {
245 delete trace_outputs[proc].first;
246 trace_outputs.erase(proc);
247 throw std::runtime_error("Can't open '" + full_filename + "'");
250 trace_outputs[proc].second = debug_add_trace_callback(proc, [](uint64_t proc, const char* str, bool dummy) {
251 if(!trace_outputs.count(proc)) return;
252 trace_outputs[proc].first->stream << str << std::endl;
253 }, [proc]() { debug_tracelog(proc, ""); });
254 messages << "Tracelogging processor #" << proc << " to '" << filename << "'" << std::endl;
255 if(tracelog_change_cb) tracelog_change_cb();
258 bool debug_tracelogging(uint64_t proc)
260 return (trace_outputs.count(proc) != 0);
263 void debug_set_tracelog_change_cb(std::function<void()> cb)
265 tracelog_change_cb = cb;
268 void debug_core_change()
270 our_rom.rtype->debug_reset();
271 kill_hooks(read_cb);
272 kill_hooks(write_cb);
273 kill_hooks(exec_cb);
274 kill_hooks(trace_cb);
277 void debug_request_break()
279 requesting_break = true;
282 namespace
284 command::fnptr<> callbacks_show(lsnes_cmd, "show-callbacks", "", "",
285 []() throw(std::bad_alloc, std::runtime_error) {
286 for(auto& i : read_cb)
287 for(auto& j : i.second)
288 messages << "READ addr=" << i.first << " handle=" << &j << std::endl;
289 for(auto& i : write_cb)
290 for(auto& j : i.second)
291 messages << "WRITE addr=" << i.first << " handle=" << &j << std::endl;
292 for(auto& i : exec_cb)
293 for(auto& j : i.second)
294 messages << "EXEC addr=" << i.first << " handle=" << &j << std::endl;
295 for(auto& i : trace_cb)
296 for(auto& j : i.second)
297 messages << "TRACE proc=" << i.first << " handle=" << &j << std::endl;
300 command::fnptr<const std::string&> generate_event(lsnes_cmd, "generate-memory-event", "", "",
301 [](const std::string& args) throw(std::bad_alloc, std::runtime_error) {
302 regex_results r = regex("([^ \t]+) ([^ \t]+) (.+)", args);
303 if(!r) throw std::runtime_error("generate-memory-event: Bad arguments");
304 if(r[1] == "r") {
305 uint64_t addr = parse_value<uint64_t>(r[2]);
306 uint64_t val = parse_value<uint64_t>(r[3]);
307 debug_fire_callback_read(addr, val);
308 } else if(r[1] == "w") {
309 uint64_t addr = parse_value<uint64_t>(r[2]);
310 uint64_t val = parse_value<uint64_t>(r[3]);
311 debug_fire_callback_write(addr, val);
312 } else if(r[1] == "x") {
313 uint64_t addr = parse_value<uint64_t>(r[2]);
314 uint64_t val = parse_value<uint64_t>(r[3]);
315 debug_fire_callback_exec(addr, val);
316 } else if(r[1] == "t") {
317 uint64_t proc = parse_value<uint64_t>(r[2]);
318 std::string str = r[3];
319 debug_fire_callback_trace(proc, str.c_str());
320 } else
321 throw std::runtime_error("Invalid operation");
324 command::fnptr<const std::string&> tracelog(lsnes_cmd, "tracelog", "Trace log control",
325 "Trace log control\nSyntax: tracelog <cpuid> <file> Start tracing\nSyntax: tracelog <cpuid> "
326 "End tracing", [](const std::string& args) throw(std::bad_alloc, std::runtime_error) {
327 regex_results r = regex("([^ \t]+)([ \t]+(.+))?", args);
328 if(!r) throw std::runtime_error("tracelog: Bad arguments");
329 std::string cpu = r[1];
330 std::string filename = r[3];
331 uint64_t _cpu = 0;
332 for(auto i : our_rom.rtype->get_trace_cpus()) {
333 if(cpu == i)
334 goto out;
335 _cpu++;
337 throw std::runtime_error("tracelog: Invalid CPU");
338 out:
339 debug_tracelog(_cpu, filename);