Trace logger: Don't double free trace hooks
[lsnes.git] / src / platform / wxwidgets / tracelogger.cpp
blob65698e4cba5e9e33a45cc8785ab013aaad5156d9
1 #include "platform/wxwidgets/platform.hpp"
2 #include "platform/wxwidgets/textrender.hpp"
3 #include "platform/wxwidgets/scrollbar.hpp"
4 #include "platform/wxwidgets/loadsave.hpp"
5 #include "core/command.hpp"
6 #include "core/debug.hpp"
7 #include "core/mainloop.hpp"
8 #include "core/memorymanip.hpp"
9 #include "core/project.hpp"
10 #include "interface/disassembler.hpp"
11 #include "library/minmax.hpp"
12 #include "library/hex.hpp"
13 #include "library/serialization.hpp"
14 #include <wx/frame.h>
15 #include <wx/clipbrd.h>
16 #include <wx/msgdlg.h>
17 #include <wx/menu.h>
18 #include <wx/button.h>
19 #include <wx/checkbox.h>
20 #include <wx/listbox.h>
21 #include <wx/stattext.h>
22 #include <wx/combobox.h>
23 #include <wx/textctrl.h>
24 #include <wx/spinctrl.h>
25 #include <wx/statusbr.h>
26 #include <wx/dataobj.h>
27 #include <wx/sizer.h>
28 #include <boost/regex.hpp>
29 #include <set>
31 namespace
33 enum
35 wxID_FIND_NEXT = wxID_HIGHEST + 1,
36 wxID_FIND_PREV,
37 wxID_GOTO,
38 wxID_DISASM,
39 wxID_DISASM_MORE,
40 wxID_SINGLESTEP,
41 wxID_BREAKPOINTS,
42 wxID_CONTINUE,
43 wxID_FRAMEADVANCE,
44 wxID_CLEAR,
47 int prompt_for_save(wxWindow* parent, const std::string& what)
49 wxMessageDialog* d = new wxMessageDialog(parent, towxstring(what + " has unsaved changes, "
50 "save before closing?"), towxstring("Save on exit?"), wxCENTER | wxYES_NO | wxCANCEL |
51 wxYES_DEFAULT);
52 d->SetYesNoCancelLabels(wxT("Save"), wxT("Discard"), wxT("Cancel"));
53 int r = d->ShowModal();
54 d->Destroy();
55 if(r == wxID_YES) return 1;
56 if(r == wxID_NO) return 0;
57 if(r == wxID_CANCEL) return -1;
58 return -1;
61 class dialog_find : public wxDialog
63 public:
64 dialog_find(wxWindow* parent);
65 std::string get_pattern();
66 void on_ok(wxCommandEvent& e) { EndModal(wxID_OK); }
67 void on_cancel(wxCommandEvent& e) { EndModal(wxID_CANCEL); }
68 private:
69 wxTextCtrl* text;
70 wxComboBox* type;
71 wxButton* ok;
72 wxButton* cancel;
75 dialog_find::dialog_find(wxWindow* parent)
76 : wxDialog(parent, wxID_ANY, wxT("Find"))
78 wxBoxSizer* top_s = new wxBoxSizer(wxVERTICAL);
79 SetSizer(top_s);
80 wxBoxSizer* t_s = new wxBoxSizer(wxHORIZONTAL);
81 t_s->Add(type = new wxComboBox(this, wxID_ANY, wxT(""), wxDefaultPosition, wxDefaultSize,
82 0, NULL, wxCB_READONLY), 1, wxGROW);
83 type->Append(towxstring("Literal"));
84 type->Append(towxstring("Wildcards"));
85 type->Append(towxstring("Regexp"));
86 type->SetSelection(0);
87 t_s->Add(text = new wxTextCtrl(this, wxID_ANY, wxT(""), wxDefaultPosition, wxSize(350, -1),
88 wxTE_PROCESS_ENTER), 0, wxGROW);
89 top_s->Add(t_s, 1, wxGROW);
90 wxBoxSizer* pbutton_s = new wxBoxSizer(wxHORIZONTAL);
91 pbutton_s->AddStretchSpacer();
92 pbutton_s->Add(ok = new wxButton(this, wxID_ANY, wxT("OK")));
93 pbutton_s->Add(cancel = new wxButton(this, wxID_ANY, wxT("Cancel")));
94 text->Connect(wxEVT_COMMAND_TEXT_ENTER, wxCommandEventHandler(dialog_find::on_ok), NULL, this);
95 ok->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(dialog_find::on_ok), NULL, this);
96 cancel->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(dialog_find::on_cancel), NULL,
97 this);
98 top_s->Add(pbutton_s, 0, wxGROW);
99 top_s->SetSizeHints(this);
100 Fit();
103 std::string dialog_find::get_pattern()
105 if(tostdstring(text->GetValue()) == "")
106 return "";
107 if(type->GetSelection() == 2)
108 return "R" + tostdstring(text->GetValue());
109 else if(type->GetSelection() == 1)
110 return "W" + tostdstring(text->GetValue());
111 else
112 return "F" + tostdstring(text->GetValue());
115 class dialog_disassemble : public wxDialog
117 public:
118 dialog_disassemble(wxWindow* parent);
119 dialog_disassemble(wxWindow* parent, uint64_t dflt_base, const std::string& dflt_lang);
120 std::string get_disassembler();
121 uint64_t get_address();
122 uint64_t get_count();
123 void on_change(wxCommandEvent& e);
124 void on_ok(wxCommandEvent& e);
125 void on_cancel(wxCommandEvent& e) { EndModal(wxID_CANCEL); }
126 private:
127 void init(bool spec, uint64_t dflt_base, std::string dflt_lang);
128 wxComboBox* type;
129 wxComboBox* endian;
130 wxComboBox* vma;
131 wxTextCtrl* address;
132 wxSpinCtrl* count;
133 wxButton* ok;
134 wxButton* cancel;
135 bool has_default;
136 unsigned code_types;
137 static std::string old_dflt_lang;
138 static uint64_t old_dflt_base;
141 std::string dialog_disassemble::old_dflt_lang;
142 uint64_t dialog_disassemble::old_dflt_base;
144 dialog_disassemble::dialog_disassemble(wxWindow* parent)
145 : wxDialog(parent, wxID_ANY, wxT("Disassemble region"))
147 init(false, 0, "");
150 dialog_disassemble::dialog_disassemble(wxWindow* parent, uint64_t dflt_base, const std::string& dflt_lang)
151 : wxDialog(parent, wxID_ANY, wxT("Disassemble region"))
153 init(true, dflt_base, dflt_lang);
156 void dialog_disassemble::init(bool spec, uint64_t dflt_base, std::string dflt_lang)
158 std::map<std::string, std::pair<uint64_t, uint64_t>> regions;
159 std::set<std::string> disasms;
160 runemufn([&regions, &disasms]() {
161 for(auto i : lsnes_memory.get_regions())
162 regions[i->name] = std::make_pair(i->base, i->size);
163 disasms = disassembler::list();
166 wxBoxSizer* top_s = new wxBoxSizer(wxVERTICAL);
167 SetSizer(top_s);
169 wxBoxSizer* type_s = new wxBoxSizer(wxHORIZONTAL);
170 type_s->Add(new wxStaticText(this, wxID_ANY, wxT("Language:")), 0, wxGROW);
171 type_s->Add(type = new wxComboBox(this, wxID_ANY, wxT(""), wxDefaultPosition, wxDefaultSize,
172 0, NULL, wxCB_READONLY), 0, wxGROW);
173 for(auto& i : disasms)
174 type->Append(towxstring(i));
175 code_types = type->GetCount();
176 type->Append(towxstring("Data (signed byte)"));
177 type->Append(towxstring("Data (unsigned byte)"));
178 type->Append(towxstring("Data (hex byte)"));
179 type->Append(towxstring("Data (signed word)"));
180 type->Append(towxstring("Data (unsigned word)"));
181 type->Append(towxstring("Data (hex word)"));
182 type->Append(towxstring("Data (signed onehalfword)"));
183 type->Append(towxstring("Data (unsigned onehalfword)"));
184 type->Append(towxstring("Data (hex onehalfword)"));
185 type->Append(towxstring("Data (signed doubleword)"));
186 type->Append(towxstring("Data (unsigned doubleword)"));
187 type->Append(towxstring("Data (hex doubleword)"));
188 type->Append(towxstring("Data (signed quadword)"));
189 type->Append(towxstring("Data (unsigned quadword)"));
190 type->Append(towxstring("Data (hex quadword)"));
191 type->Append(towxstring("Data (float)"));
192 type->Append(towxstring("Data (double)"));
193 type->SetSelection(0);
194 type->Connect(wxEVT_COMMAND_COMBOBOX_SELECTED, wxCommandEventHandler(dialog_disassemble::on_change),
195 NULL, this);
196 top_s->Add(type_s, 0, wxGROW);
198 wxBoxSizer* endian_s = new wxBoxSizer(wxHORIZONTAL);
199 endian_s->Add(new wxStaticText(this, wxID_ANY, wxT("Endian:")), 0, wxGROW);
200 endian_s->Add(endian = new wxComboBox(this, wxID_ANY, wxT(""), wxDefaultPosition, wxDefaultSize,
201 0, NULL, wxCB_READONLY), 0, wxGROW);
202 endian->Append(towxstring("(Memory area default)"));
203 endian->Append(towxstring("Little-endian"));
204 endian->Append(towxstring("Host-endian"));
205 endian->Append(towxstring("Big-endian"));
206 endian->SetSelection(0);
207 endian->Connect(wxEVT_COMMAND_COMBOBOX_SELECTED, wxCommandEventHandler(dialog_disassemble::on_change),
208 NULL, this);
209 top_s->Add(endian_s, 0, wxGROW);
211 wxBoxSizer* vma_s = new wxBoxSizer(wxHORIZONTAL);
212 vma_s->Add(new wxStaticText(this, wxID_ANY, wxT("Area:")), 0, wxGROW);
213 vma_s->Add(vma = new wxComboBox(this, wxID_ANY, wxT(""), wxDefaultPosition, wxDefaultSize,
214 0, NULL, wxCB_READONLY), 0, wxGROW);
215 vma->Append(towxstring("(Any)"));
216 for(auto& i : regions)
217 vma->Append(towxstring(i.first));
218 vma->SetSelection(0);
219 vma->Connect(wxEVT_COMMAND_COMBOBOX_SELECTED, wxCommandEventHandler(dialog_disassemble::on_change),
220 NULL, this);
221 top_s->Add(vma_s, 0, wxGROW);
223 wxBoxSizer* addr_s = new wxBoxSizer(wxHORIZONTAL);
224 addr_s->Add(new wxStaticText(this, wxID_ANY, wxT("Address:")), 0, wxGROW);
225 addr_s->Add(address = new wxTextCtrl(this, wxID_ANY, wxT(""), wxDefaultPosition, wxSize(200, -1)),
226 0, wxGROW);
227 address->Connect(wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler(dialog_disassemble::on_change),
228 NULL, this);
229 top_s->Add(addr_s, 0, wxGROW);
231 wxBoxSizer* cnt_s = new wxBoxSizer(wxHORIZONTAL);
232 cnt_s->Add(new wxStaticText(this, wxID_ANY, wxT("Count:")), 0, wxGROW);
233 cnt_s->Add(count = new wxSpinCtrl(this, wxID_ANY, wxT(""), wxDefaultPosition, wxDefaultSize,
234 wxSP_ARROW_KEYS, 1, 1000000000, 10), 0, wxGROW);
235 count->Connect(wxEVT_SPINCTRL, wxCommandEventHandler(dialog_disassemble::on_change), NULL,
236 this);
237 top_s->Add(cnt_s, 0, wxGROW);
239 wxBoxSizer* pbutton_s = new wxBoxSizer(wxHORIZONTAL);
240 pbutton_s->AddStretchSpacer();
241 pbutton_s->Add(ok = new wxButton(this, wxID_ANY, wxT("OK")));
242 pbutton_s->Add(cancel = new wxButton(this, wxID_ANY, wxT("Cancel")));
243 ok->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(dialog_disassemble::on_ok), NULL,
244 this);
245 cancel->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(dialog_disassemble::on_cancel),
246 NULL, this);
247 top_s->Add(pbutton_s, 0, wxGROW);
248 top_s->SetSizeHints(this);
249 Fit();
251 has_default = spec;
252 if(!spec) {
253 dflt_lang = old_dflt_lang;
254 dflt_base = old_dflt_base;
256 //Set default language.
257 if(regex_match("\\$data:.*", dflt_lang)) {
258 switch(dflt_lang[6]) {
259 case 'b': type->SetSelection(code_types + 0); break;
260 case 'B': type->SetSelection(code_types + 1); break;
261 case 'c': type->SetSelection(code_types + 2); break;
262 case 'w': type->SetSelection(code_types + 3); break;
263 case 'W': type->SetSelection(code_types + 4); break;
264 case 'C': type->SetSelection(code_types + 5); break;
265 case 'h': type->SetSelection(code_types + 6); break;
266 case 'H': type->SetSelection(code_types + 7); break;
267 case 'i': type->SetSelection(code_types + 8); break;
268 case 'd': type->SetSelection(code_types + 9); break;
269 case 'D': type->SetSelection(code_types + 10); break;
270 case 'I': type->SetSelection(code_types + 11); break;
271 case 'q': type->SetSelection(code_types + 12); break;
272 case 'Q': type->SetSelection(code_types + 13); break;
273 case 'r': type->SetSelection(code_types + 14); break;
274 case 'f': type->SetSelection(code_types + 15); break;
275 case 'F': type->SetSelection(code_types + 16); break;
277 switch(dflt_lang[7]) {
278 case 'l': endian->SetSelection(1); break;
279 case 'h': endian->SetSelection(2); break;
280 case 'b': endian->SetSelection(3); break;
282 } else {
283 unsigned j = 0;
284 //Set default disasm.
285 for(auto& i : disasms) {
286 if(i == dflt_lang)
287 break;
288 j++;
290 if(j < disasms.size())
291 type->SetSelection(j);
293 //Set default address.
294 int k = 0;
295 for(auto& i : regions) {
296 if(dflt_base >= i.second.first && dflt_base < i.second.first + i.second.second) {
297 vma->SetSelection(k + 1);
298 dflt_base -= i.second.first;
299 break;
301 k++;
303 address->SetValue(towxstring((stringfmt() << std::hex << dflt_base).str()));
305 wxCommandEvent e;
306 on_change(e);
309 void dialog_disassemble::on_ok(wxCommandEvent& e)
311 EndModal(wxID_OK);
314 std::string dialog_disassemble::get_disassembler()
316 if(type->GetSelection() >= (ssize_t)code_types && type->GetSelection() < (ssize_t)type->GetCount()) {
317 int _endian = endian->GetSelection();
318 int dtsel = type->GetSelection() - code_types;
319 std::string _vma = tostdstring(vma->GetStringSelection());
320 if(_endian <= 0 || _endian > 3) {
321 _endian = 1;
322 runemufn([&_endian, _vma]() {
323 for(auto i : lsnes_memory.get_regions()) {
324 if(i->name == _vma) {
325 _endian = i->endian + 2;
330 if(dtsel < 0) dtsel = 0;
331 if(dtsel > 16) dtsel = 16;
332 static const char* typechars = "bBcwWChHidDIqQrfF";
333 static const char* endianchars = " lhb";
334 std::string res = std::string("$data:") + std::string(1, typechars[dtsel]) +
335 std::string(1, endianchars[_endian]);
336 if(!has_default)
337 old_dflt_lang = res;
338 return res;
339 } else {
340 std::string res = tostdstring(type->GetStringSelection());
341 if(!has_default)
342 old_dflt_lang = res;
343 return res;
347 uint64_t dialog_disassemble::get_address()
349 uint64_t base = 0;
350 if(vma->GetSelection() && vma->GetSelection() != wxNOT_FOUND) {
351 std::string _vma = tostdstring(vma->GetStringSelection());
352 runemufn([&base, _vma]() {
353 for(auto i : lsnes_memory.get_regions()) {
354 if(i->name == _vma) {
355 base = i->base;
360 uint64_t off = hex::from<uint64_t>(tostdstring(address->GetValue()));
361 uint64_t res = base + off;
362 if(!has_default)
363 old_dflt_base = res;
364 return res;
367 uint64_t dialog_disassemble::get_count()
369 return count->GetValue();
372 void dialog_disassemble::on_change(wxCommandEvent& e)
374 bool is_ok = true;
375 try {
376 hex::from<uint64_t>(tostdstring(address->GetValue()));
377 } catch(std::exception& e) {
378 is_ok = false;
380 is_ok = is_ok && (type->GetSelection() != wxNOT_FOUND);
381 is_ok = is_ok && (vma->GetSelection() != wxNOT_FOUND);
382 endian->Enable(type->GetSelection() >= (ssize_t)code_types && type->GetSelection() <
383 (ssize_t)type->GetCount());
384 is_ok = is_ok && (!endian->IsEnabled() || endian->GetSelection() != wxNOT_FOUND);
385 //If VMA is global, ensure there is valid endian.
386 is_ok = is_ok && (vma->GetSelection() != 0 || !endian->IsEnabled() || endian->GetSelection() != 0);
387 ok->Enable(is_ok);
390 class wxwin_tracelog;
392 class dialog_breakpoint_add : public wxDialog
394 public:
395 dialog_breakpoint_add(wxWindow* parent, std::list<memory_region*> regions);
396 std::pair<uint64_t, debug_type> get_result();
397 void on_ok(wxCommandEvent& e) { EndModal(wxID_OK); }
398 void on_cancel(wxCommandEvent& e) { EndModal(wxID_CANCEL); }
399 void on_address_change(wxCommandEvent& e);
400 private:
401 std::list<memory_region*> regions;
402 wxComboBox* vmasel;
403 wxTextCtrl* address;
404 wxComboBox* typesel;
405 wxButton* ok;
406 wxButton* cancel;
409 dialog_breakpoint_add::dialog_breakpoint_add(wxWindow* parent, std::list<memory_region*> _regions)
410 : wxDialog(parent, wxID_ANY, wxT("Add breakpoint"))
412 regions = _regions;
413 wxBoxSizer* top_s = new wxBoxSizer(wxVERTICAL);
414 SetSizer(top_s);
416 top_s->Add(new wxStaticText(this, wxID_ANY, wxT("Memory region:")), 0, wxGROW);
417 top_s->Add(vmasel = new wxComboBox(this, wxID_ANY, wxT(""), wxDefaultPosition, wxDefaultSize,
418 0, NULL, wxCB_READONLY), 1, wxGROW);
419 vmasel->Append(towxstring(""));
420 for(auto i : regions)
421 vmasel->Append(towxstring(i->name));
422 vmasel->SetSelection(0);
424 top_s->Add(new wxStaticText(this, wxID_ANY, wxT("Offset (hexadecimal):")), 0, wxGROW);
425 top_s->Add(address = new wxTextCtrl(this, wxID_ANY, wxT("0"), wxDefaultPosition, wxSize(350, -1)), 0,
426 wxGROW);
428 top_s->Add(new wxStaticText(this, wxID_ANY, wxT("Breakpoint type:")), 0, wxGROW);
429 top_s->Add(typesel = new wxComboBox(this, wxID_ANY, wxT(""), wxDefaultPosition, wxDefaultSize,
430 0, NULL, wxCB_READONLY), 1, wxGROW);
431 typesel->Append(towxstring("Read"));
432 typesel->Append(towxstring("Write"));
433 typesel->Append(towxstring("Execute"));
434 typesel->SetSelection(0);
436 wxBoxSizer* pbutton_s = new wxBoxSizer(wxHORIZONTAL);
437 pbutton_s->AddStretchSpacer();
438 pbutton_s->Add(ok = new wxButton(this, wxID_ANY, wxT("OK")));
439 pbutton_s->Add(cancel = new wxButton(this, wxID_ANY, wxT("Cancel")));
440 ok->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(dialog_breakpoint_add::on_ok), NULL,
441 this);
442 cancel->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(dialog_breakpoint_add::on_cancel),
443 NULL, this);
444 top_s->Add(pbutton_s, 0, wxGROW);
445 top_s->SetSizeHints(this);
446 Fit();
449 void dialog_breakpoint_add::on_address_change(wxCommandEvent& e)
451 try {
452 hex::from<uint64_t>(tostdstring(address->GetValue()));
453 ok->Enable(true);
454 } catch(...) {
455 ok->Enable(false);
459 std::pair<uint64_t, debug_type> dialog_breakpoint_add::get_result()
461 std::string vmaname = tostdstring(vmasel->GetStringSelection());
462 std::string addrtext = tostdstring(address->GetValue());
463 uint64_t base = 0;
464 if(vmaname != "") {
465 for(auto i : regions)
466 if(i->name == vmaname)
467 base = i->base;
469 uint64_t addr;
470 try {
471 addr = base + hex::from<uint64_t>(addrtext);
472 } catch(std::exception& e) {
473 addr = base;
475 debug_type dtype = DEBUG_EXEC;
476 if(typesel->GetSelection() == 0)
477 dtype = DEBUG_READ;
478 if(typesel->GetSelection() == 1)
479 dtype = DEBUG_WRITE;
480 if(typesel->GetSelection() == 2)
481 dtype = DEBUG_EXEC;
482 return std::make_pair(addr, dtype);
486 class dialog_breakpoints : public wxDialog
488 public:
489 dialog_breakpoints(wxwin_tracelog* parent);
490 void on_ok(wxCommandEvent& e) { EndModal(wxID_OK); }
491 void on_add(wxCommandEvent& e);
492 void on_delete(wxCommandEvent& e);
493 void on_selchange(wxCommandEvent& e);
494 private:
495 std::string format_line(std::pair<uint64_t, debug_type> entry);
496 size_t get_insert_pos(std::pair<uint64_t, debug_type> entry);
497 void populate_breakpoints();
498 std::list<memory_region*> regions;
499 wxButton* ok;
500 wxButton* addb;
501 wxButton* delb;
502 wxListBox* brklist;
503 wxwin_tracelog* pwin;
504 std::vector<std::pair<uint64_t, debug_type>> listsyms;
507 class wxwin_tracelog : public wxFrame
509 public:
510 wxwin_tracelog(wxWindow* parent, int _cpuid, const std::string& cpuname);
511 ~wxwin_tracelog();
512 bool ShouldPreventAppExit() const { return false; }
513 scroll_bar* get_scroll() { return scroll; }
514 void on_wclose(wxCloseEvent& e);
515 void on_enabled(wxCommandEvent& e);
516 void on_menu(wxCommandEvent& e);
517 void process_lines();
518 uint64_t get_find_line() { return find_active ? find_line : 0xFFFFFFFFFFFFFFFFULL; }
519 std::set<std::pair<uint64_t, debug_type>> get_breakpoints();
520 void add_breakpoint(uint64_t addr, debug_type dtype);
521 void remove_breakpoint(uint64_t addr, debug_type dtype);
522 private:
523 class _panel : public text_framebuffer_panel
525 public:
526 _panel(wxwin_tracelog* parent);
527 void on_size(wxSizeEvent& e);
528 void on_mouse(wxMouseEvent& e);
529 wxSize DoGetBestSize() const;
530 uint64_t pos;
531 std::vector<std::string> rows;
532 void on_popup_menu(wxCommandEvent& e);
533 bool scroll_to_end_on_repaint;
534 protected:
535 void prepare_paint();
536 private:
537 uint64_t pressed_row;
538 uint64_t current_row;
539 bool holding;
540 wxwin_tracelog* p;
542 bool do_exit_save();
543 void scroll_pane(uint64_t line);
544 int cpuid;
545 volatile bool trace_active;
546 debug_handle trace_handle;
547 debug_handle trace_handle_frame;
548 void do_rwx_break(uint64_t addr, uint64_t value, debug_type type);
549 void kill_debug_hooks();
550 scroll_bar* scroll;
551 _panel* panel;
552 bool broken;
553 bool broken2;
554 wxCheckBox* enabled;
555 threads::lock buffer_mutex;
556 std::list<std::string> lines_waiting;
557 bool unprocessed_lines;
558 bool closing;
559 bool find_active;
560 uint64_t find_line;
561 std::string find_string;
562 bool dirty;
563 bool singlestepping;
564 std::map<std::pair<uint64_t, debug_type>, debug_handle> rwx_breakpoints;
565 wxMenuItem* m_singlestep;
568 wxwin_tracelog::~wxwin_tracelog()
572 void wxwin_tracelog::on_wclose(wxCloseEvent& e)
574 if(dirty && !wxwidgets_exiting) {
575 int r = prompt_for_save(this, "Trace log");
576 if(r < 0 || (r > 0 && !do_exit_save()))
577 return;
579 if(trace_active)
580 runemufn([this]() { kill_debug_hooks(); });
581 trace_active = false;
582 if(!closing)
583 Destroy();
584 closing = true;
587 void wxwin_tracelog::kill_debug_hooks()
589 debug_remove_callback(cpuid, DEBUG_TRACE, trace_handle);
590 debug_remove_callback(cpuid, DEBUG_FRAME, trace_handle_frame);
591 threads::alock h(buffer_mutex);
592 for(auto& i : rwx_breakpoints) {
593 if(!i.second.handle)
594 continue;
595 debug_remove_callback(i.first.first, i.first.second, i.second);
596 //Dirty hack.
597 i.second.handle = NULL;
599 trace_active = false;
600 convert_break_to_pause();
603 wxwin_tracelog::_panel::_panel(wxwin_tracelog* parent)
604 : text_framebuffer_panel(parent, 20, 5, wxID_ANY, NULL)
606 p = parent;
607 pos = 0;
608 pressed_row = 0;
609 current_row = 0;
610 holding = false;
611 scroll_to_end_on_repaint = false;
612 this->Connect(wxEVT_SIZE, wxSizeEventHandler(wxwin_tracelog::_panel::on_size), NULL, this);
613 this->Connect(wxEVT_RIGHT_DOWN, wxMouseEventHandler(wxwin_tracelog::_panel::on_mouse), NULL, this);
614 this->Connect(wxEVT_RIGHT_UP, wxMouseEventHandler(wxwin_tracelog::_panel::on_mouse), NULL, this);
615 this->Connect(wxEVT_MOTION, wxMouseEventHandler(wxwin_tracelog::_panel::on_mouse), NULL, this);
616 this->Connect(wxEVT_MOUSEWHEEL, wxMouseEventHandler(wxwin_tracelog::_panel::on_mouse), NULL, this);
619 void wxwin_tracelog::_panel::on_size(wxSizeEvent& e)
621 wxSize newsize = e.GetSize();
622 auto tcell = get_cell();
623 size_t lines = newsize.y / tcell.second;
624 size_t linelen = newsize.x / tcell.first;
625 if(lines < 1) lines = 1;
626 if(linelen < 1) linelen = 1;
627 set_size(linelen, lines);
628 p->get_scroll()->set_page_size(lines);
629 request_paint();
630 e.Skip();
633 void wxwin_tracelog::_panel::on_mouse(wxMouseEvent& e)
635 uint64_t local_line = pos + e.GetY() / get_cell().second;
636 if(e.RightDown()) {
637 if(local_line < rows.size()) {
638 holding = true;
639 pressed_row = local_line;
641 }else if(e.RightUp()) {
642 holding = false;
643 wxMenu menu;
644 menu.Connect(wxEVT_COMMAND_MENU_SELECTED,
645 wxCommandEventHandler(wxwin_tracelog::_panel::on_popup_menu), NULL, this);
646 menu.Append(wxID_COPY, wxT("Copy to clipboard"));
647 menu.Append(wxID_SAVE, wxT("Save to file"));
648 menu.AppendSeparator();
649 menu.Append(wxID_DELETE, wxT("Delete"));
650 PopupMenu(&menu);
651 } else {
652 current_row = min(local_line, static_cast<uint64_t>(rows.size()));
653 request_paint();
655 unsigned speed = 1;
656 if(e.ShiftDown())
657 speed = 10;
658 p->get_scroll()->apply_wheel(e.GetWheelRotation(), e.GetWheelDelta(), speed);
661 wxSize wxwin_tracelog::_panel::DoGetBestSize() const
663 return wxSize(120 * 8, 25 * 16);
666 void wxwin_tracelog::_panel::prepare_paint()
668 p->get_scroll()->set_range(rows.size());
669 if(scroll_to_end_on_repaint) {
670 scroll_to_end_on_repaint = false;
671 p->get_scroll()->set_position(rows.size());
672 pos = p->get_scroll()->get_position();
674 uint64_t m = min(pressed_row, current_row);
675 uint64_t M = max(pressed_row, current_row);
676 auto s = get_characters();
677 uint64_t fline = p->get_find_line();
678 for(uint64_t i = pos; i < pos + s.second && i < rows.size(); i++) {
679 bool selected = holding && (i >= m) && (i <= M);
680 bool isfl = (i == fline);
681 uint32_t fg = selected ? 0x0000FF : 0x000000;
682 uint32_t bg = selected ? 0x000000 : (isfl ? 0xC0FFC0 : 0xFFFFFF);
683 write(rows[i], s.first, 0, i - pos, fg, bg);
685 for(uint64_t i = rows.size(); i < pos + s.second; i++)
686 write("", s.first, 0, i - pos, 0xFFFFFF, 0xFFFFFF);
689 void wxwin_tracelog::process_lines()
691 threads::alock h(this->buffer_mutex);
692 size_t osize = panel->rows.size();
693 if(broken) {
694 panel->rows.push_back(std::string(120, '-'));
695 broken = false;
697 for(auto& i : lines_waiting)
698 panel->rows.push_back(i);
699 lines_waiting.clear();
700 unprocessed_lines = false;
701 if(panel->rows.size() != osize) {
702 panel->scroll_to_end_on_repaint = true;
703 dirty = true;
705 panel->request_paint();
708 void wxwin_tracelog::do_rwx_break(uint64_t addr, uint64_t value, debug_type type)
710 debug_request_break();
713 void wxwin_tracelog::on_enabled(wxCommandEvent& e)
715 bool enable = enabled->GetValue();
716 runemufn([this, enable]() {
717 if(enable) {
718 threads::alock h(buffer_mutex);
719 broken = broken2;
720 broken2 = true;
721 for(auto& i : rwx_breakpoints) {
722 auto i2 = i.first;
723 i.second = debug_add_callback(i.first.first, i.first.second,
724 [this, i2](uint64_t addr, uint64_t value) {
725 this->do_rwx_break(addr, value, i2.second);
726 }, [this, i2] {
727 //We need to kill this hook if still active.
728 auto& h = rwx_breakpoints[i2];
729 if(h.handle)
730 debug_remove_callback(i2.first, i2.second, h);
731 h.handle = NULL;
734 this->trace_handle = debug_add_trace_callback(cpuid, [this](uint64_t proc,
735 const char* str, bool true_instruction) {
736 if(!this->trace_active)
737 return;
738 //Got tracelog line, send it.
739 threads::alock h(this->buffer_mutex);
740 lines_waiting.push_back(str);
741 if(!this->unprocessed_lines) {
742 this->unprocessed_lines = true;
743 runuifun([this]() { this->process_lines(); });
745 if(this->singlestepping && true_instruction) {
746 debug_request_break();
747 this->singlestepping = false;
749 }, [this]() {
750 //Dtor!
751 auto tmp = this;
752 if(!tmp->trace_active)
753 return;
754 tmp->kill_debug_hooks();
755 //We can't use this anymore.
756 runuifun([tmp]() {
757 tmp->enabled->SetValue(false);
758 tmp->m_singlestep->Enable(false);
761 this->trace_handle_frame = debug_add_frame_callback([this](uint64_t frame,
762 bool loadstate) {
763 std::ostringstream xstr;
764 xstr << "------------ ";
765 xstr << "Frame " << frame;
766 if(loadstate) xstr << " (loadstated)";
767 xstr << " ------------";
768 std::string str = xstr.str();
769 threads::alock h(this->buffer_mutex);
770 lines_waiting.push_back(str);
771 if(!this->unprocessed_lines) {
772 this->unprocessed_lines = true;
773 runuifun([this]() { this->process_lines(); });
775 }, [this]() {
776 auto tmp = this;
777 if(!tmp->trace_active)
778 return;
779 debug_remove_callback(0, DEBUG_TRACE, trace_handle_frame);
781 this->trace_active = true;
782 } else if(trace_active) {
783 this->trace_active = false;
784 this->kill_debug_hooks();
787 m_singlestep->Enable(enable);
790 bool find_match(const std::string& pattern, const std::string& candidate)
792 static std::string last_find;
793 static boost::regex regex;
794 if(pattern == "")
795 return false;
796 if(pattern[0] == 'F') {
797 //Substring find.
798 if(pattern != last_find) {
799 std::string tmp = pattern;
800 tmp = tmp.substr(1);
801 regex = boost::regex(tmp, boost::regex_constants::literal |
802 boost::regex_constants::icase);
803 last_find = pattern;
806 if(pattern[0] == 'W') {
807 //wildcard find.
808 if(pattern != last_find) {
809 std::ostringstream y;
810 for(size_t i = 1; i < pattern.length(); i++)
811 if(pattern[i] == '?')
812 y << ".";
813 else if(pattern[i] == '*')
814 y << ".*";
815 else if(pattern[i] >= 'A' && pattern[i] <= 'Z')
816 y << pattern[i];
817 else if(pattern[i] >= 'a' && pattern[i] <= 'z')
818 y << pattern[i];
819 else if(pattern[i] >= '0' && pattern[i] <= '9')
820 y << pattern[i];
821 else
822 y << "\\" << pattern[i];
823 std::string tmp = y.str();
824 regex = boost::regex(tmp, boost::regex_constants::extended);
825 last_find = pattern;
828 if(pattern[0] == 'R') {
829 //regexp find.
830 if(pattern != last_find) {
831 std::string tmp = pattern;
832 tmp = tmp.substr(1);
833 regex = boost::regex(tmp, boost::regex_constants::extended |
834 boost::regex_constants::icase);
835 last_find = pattern;
838 return regex_search(candidate, regex);
841 void wxwin_tracelog::on_menu(wxCommandEvent& e)
843 if(e.GetId() == wxID_EXIT) {
844 if(dirty) {
845 int r = prompt_for_save(this, "Trace log");
846 if(r < 0 || (r > 0 && !do_exit_save()))
847 return;
849 if(trace_active) {
850 runemufn([this]() { this->kill_debug_hooks(); });
852 trace_active = false;
853 Destroy();
854 return;
855 } else if(e.GetId() == wxID_SAVE) {
856 try {
857 std::string filename = choose_file_save(this, "Save tracelog to",
858 project_otherpath(), filetype_trace);
859 std::ofstream s(filename, std::ios::app);
860 if(!s) throw std::runtime_error("Error opening output file");
861 for(auto& i : panel->rows)
862 s << i << std::endl;
863 if(!s) throw std::runtime_error("Error writing output file");
864 dirty = false;
865 } catch(canceled_exception& e) {
866 } catch(std::exception& e) {
867 wxMessageBox(towxstring(e.what()), _T("Error creating file"), wxICON_EXCLAMATION |
868 wxOK, this);
870 } else if(e.GetId() == wxID_FIND) {
871 std::string tmp;
872 dialog_find* d = new dialog_find(this);
873 if(d->ShowModal() != wxID_OK) {
874 d->Destroy();
875 return;
877 tmp = d->get_pattern();
878 d->Destroy();
879 if(tmp == "") {
880 find_active = false;
881 return;
883 find_string = tmp;
884 find_active = true;
885 find_line = 0;
886 while(find_line < panel->rows.size()) {
887 if(find_match(find_string, panel->rows[find_line]))
888 break;
889 find_line++;
891 if(find_line == panel->rows.size()) {
892 //Not found.
893 find_active = false;
894 wxMessageBox(towxstring("Found nothing appropriate"), _T("Not found"),
895 wxICON_EXCLAMATION | wxOK, this);
896 } else
897 scroll_pane(find_line);
898 } else if(e.GetId() == wxID_FIND_NEXT) {
899 if(!find_active)
900 return;
901 uint64_t old_find_line = find_line;
902 find_line++;
903 while(!panel->rows.empty() && find_line != old_find_line) {
904 if(find_line >= panel->rows.size())
905 find_line = 0;
906 if(find_match(find_string, panel->rows[find_line]))
907 break;
908 find_line++;
910 scroll_pane(find_line);
911 } else if(e.GetId() == wxID_FIND_PREV) {
912 if(!find_active)
913 return;
914 uint64_t old_find_line = find_line;
915 find_line--;
916 while(!panel->rows.empty() && find_line != old_find_line) {
917 if(find_line >= panel->rows.size())
918 find_line = panel->rows.size() - 1;
919 if(find_match(find_string, panel->rows[find_line]))
920 break;
921 find_line--;
923 scroll_pane(find_line);
924 } else if(e.GetId() == wxID_SINGLESTEP) {
925 runemufn_async([this]() {
926 this->singlestepping = true;
927 lsnes_cmd.invoke("unpause-emulator");
929 } else if(e.GetId() == wxID_FRAMEADVANCE) {
930 runemufn_async([this]() {
931 lsnes_cmd.invoke("+advance-frame");
932 lsnes_cmd.invoke("-advance-frame");
934 } else if(e.GetId() == wxID_CONTINUE) {
935 runemufn_async([this]() { lsnes_cmd.invoke("unpause-emulator"); });
936 } else if(e.GetId() == wxID_BREAKPOINTS) {
937 dialog_breakpoints* d = new dialog_breakpoints(this);
938 d->ShowModal();
939 d->Destroy();
940 } else if(e.GetId() == wxID_CLEAR) {
941 int r = prompt_for_save(this, "Trace log");
942 if(r < 0 || (r > 0 && !do_exit_save()))
943 return;
944 panel->rows.clear();
945 panel->request_paint();
946 find_active = false;
950 void wxwin_tracelog::_panel::on_popup_menu(wxCommandEvent& e)
952 std::string str;
953 uint64_t m = min(pressed_row, current_row);
954 uint64_t M = max(pressed_row, current_row) + 1;
955 m = min(m, rows.size());
956 M = min(M, rows.size());
957 size_t lines = 0;
959 for(uint64_t i = m; i < M && i < rows.size(); i++) {
960 try {
961 std::string mline = rows[i];
962 if(lines == 1) str += "\n";
963 str += mline;
964 if(lines >= 1) str += "\n";
965 lines++;
966 } catch(...) {
970 switch(e.GetId()) {
971 case wxID_COPY:
972 if (wxTheClipboard->Open()) {
973 wxTheClipboard->SetData(new wxTextDataObject(towxstring(str)));
974 wxTheClipboard->Close();
976 break;
977 case wxID_SAVE:
978 try {
979 std::string filename = choose_file_save(this, "Save tracelog fragment to",
980 project_otherpath(), filetype_trace);
981 std::ofstream s(filename, std::ios::app);
982 if(!s) throw std::runtime_error("Error opening output file");
983 if(lines == 1) str += "\n";
984 s << str;
985 if(!s) throw std::runtime_error("Error writing output file");
986 } catch(canceled_exception& e) {
987 } catch(std::exception& e) {
988 wxMessageBox(towxstring(e.what()), _T("Error creating file"), wxICON_EXCLAMATION |
989 wxOK, this);
991 break;
992 case wxID_DELETE:
993 rows.erase(rows.begin() + m, rows.begin() + M);
994 if(m != M)
995 p->dirty = true;
996 request_paint();
997 break;
1001 void wxwin_tracelog::scroll_pane(uint64_t line)
1003 unsigned r = panel->get_characters().second;
1004 unsigned offset = r / 2;
1005 if(offset > line)
1006 scroll->set_position(panel->pos = 0);
1007 else if(line + r <= panel->rows.size())
1008 scroll->set_position(panel->pos = line - offset);
1009 else
1010 scroll->set_position(panel->pos = panel->rows.size() - r);
1011 panel->request_paint();
1014 bool wxwin_tracelog::do_exit_save()
1016 back:
1017 try {
1018 std::string filename = choose_file_save(this, "Save tracelog to",
1019 project_otherpath(), filetype_trace);
1020 std::ofstream s(filename, std::ios::app);
1021 if(!s) throw std::runtime_error("Error opening output file");
1022 for(auto& i : panel->rows)
1023 s << i << std::endl;
1024 if(!s) throw std::runtime_error("Error writing output file");
1025 dirty = false;
1026 } catch(canceled_exception& e) {
1027 return false;
1028 } catch(std::exception& e) {
1029 wxMessageBox(towxstring(e.what()), _T("Error creating file"), wxICON_EXCLAMATION |
1030 wxOK, this);
1031 goto back;
1033 return true;
1036 std::set<std::pair<uint64_t, debug_type>> wxwin_tracelog::get_breakpoints()
1038 std::set<std::pair<uint64_t, debug_type>> ret;
1039 runemufn([this, &ret]() {
1040 for(auto i : rwx_breakpoints)
1041 ret.insert(i.first);
1043 return ret;
1046 void wxwin_tracelog::add_breakpoint(uint64_t addr, debug_type dtype)
1048 std::pair<uint64_t, debug_type> i2 = std::make_pair(addr, dtype);
1049 if(!trace_active) {
1050 //We'll register this later.
1051 rwx_breakpoints[i2] = debug_handle();
1052 return;
1054 rwx_breakpoints[i2] = debug_add_callback(i2.first, i2.second,
1055 [this, i2](uint64_t addr, uint64_t value) {
1056 this->do_rwx_break(addr, value, i2.second);
1057 }, [this, i2] {
1058 //We need to kill this hook if still active.
1059 auto& h = rwx_breakpoints[i2];
1060 if(h.handle)
1061 debug_remove_callback(i2.first, i2.second, h);
1062 h.handle = NULL;
1066 void wxwin_tracelog::remove_breakpoint(uint64_t addr, debug_type dtype)
1068 std::pair<uint64_t, debug_type> i2 = std::make_pair(addr, dtype);
1069 auto& h = rwx_breakpoints[i2];
1070 if(h.handle)
1071 debug_remove_callback(i2.first, i2.second, h);
1072 rwx_breakpoints.erase(i2);
1075 wxwin_tracelog::wxwin_tracelog(wxWindow* parent, int _cpuid, const std::string& cpuname)
1076 : wxFrame(parent, wxID_ANY, towxstring("lsnes: Tracelog for " + cpuname), wxDefaultPosition,
1077 wxDefaultSize, wxMINIMIZE_BOX | wxRESIZE_BORDER | wxSYSTEM_MENU | wxCAPTION | wxCLOSE_BOX |
1078 wxCLIP_CHILDREN)
1080 cpuid = _cpuid;
1081 singlestepping = false;
1082 find_active = false;
1083 find_line = 0;
1084 closing = false;
1085 trace_active = false;
1086 unprocessed_lines = false;
1087 broken = false;
1088 broken2 = false;
1089 dirty = false;
1090 wxBoxSizer* top_s = new wxBoxSizer(wxVERTICAL);
1091 SetSizer(top_s);
1092 wxBoxSizer* bottom_s = new wxBoxSizer(wxHORIZONTAL);
1093 top_s->Add(enabled = new wxCheckBox(this, wxID_ANY, wxT("Enabled")), 0, wxGROW);
1094 bottom_s->Add(panel = new _panel(this), 1, wxGROW);
1095 bottom_s->Add(scroll = new scroll_bar(this, wxID_ANY, true), 0, wxGROW);
1096 top_s->Add(bottom_s, 1, wxGROW);
1097 enabled->Connect(wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler(wxwin_tracelog::on_enabled),
1098 NULL, this);
1099 Connect(wxEVT_CLOSE_WINDOW, wxCloseEventHandler(wxwin_tracelog::on_wclose),
1100 NULL, this);
1101 scroll->set_page_size(panel->get_characters().second);
1102 scroll->set_handler([this](scroll_bar& s) {
1103 this->panel->pos = s.get_position();
1104 this->panel->request_paint();
1106 wxMenuBar* mb;
1107 wxStatusBar* sb;
1108 wxMenu* menu;
1110 SetMenuBar(mb = new wxMenuBar);
1111 SetStatusBar(sb = new wxStatusBar(this));
1112 mb->Append(menu = new wxMenu(), wxT("File"));
1113 menu->Append(wxID_SAVE, wxT("Save"));
1114 menu->AppendSeparator();
1115 menu->Append(wxID_EXIT, wxT("Close"));
1116 mb->Append(menu = new wxMenu(), wxT("Edit"));
1117 menu->Append(wxID_FIND, wxT("Find..."));
1118 menu->Append(wxID_FIND_NEXT, wxT("Find next\tF3"));
1119 menu->Append(wxID_FIND_PREV, wxT("Find previous\tSHIFT+F3"));
1120 menu->AppendSeparator();
1121 menu->Append(wxID_CLEAR, towxstring("Clear"));
1122 mb->Append(menu = new wxMenu(), wxT("Debug"));
1123 m_singlestep = menu->Append(wxID_SINGLESTEP, towxstring("Singlestep\tF2"));
1124 menu->Append(wxID_FRAMEADVANCE, towxstring("Frame advance\tF4"));
1125 menu->Append(wxID_CONTINUE, towxstring("Continue\tF5"));
1126 menu->AppendSeparator();
1127 menu->Append(wxID_BREAKPOINTS, towxstring("Breakpoints"));
1128 m_singlestep->Enable(false);
1129 Connect(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(wxwin_tracelog::on_menu),
1130 NULL, this);
1131 //Very nasty hack.
1132 wxSize tmp = panel->GetMinSize();
1133 panel->SetMinSize(panel->DoGetBestSize());
1134 top_s->SetSizeHints(this);
1135 wxSize tmp2 = GetClientSize();
1136 panel->SetMinSize(tmp);
1137 top_s->SetSizeHints(this);
1138 SetClientSize(tmp2);
1141 struct disasm_row
1143 uint64_t cover;
1144 std::string language;
1145 std::string row;
1148 class wxwin_disassembler : public wxFrame
1150 public:
1151 wxwin_disassembler(wxWindow* parent);
1152 bool ShouldPreventAppExit() const { return false; }
1153 scroll_bar* get_scroll() { return scroll; }
1154 void on_menu(wxCommandEvent& e);
1155 void on_wclose(wxCloseEvent& e);
1156 private:
1157 class _panel : public text_framebuffer_panel
1159 public:
1160 _panel(wxwin_disassembler* parent);
1161 void on_size(wxSizeEvent& e);
1162 void on_mouse(wxMouseEvent& e);
1163 wxSize DoGetBestSize() const;
1164 uint64_t pos;
1165 std::vector<uint64_t> rows;
1166 std::map<uint64_t, disasm_row> row_map;
1167 void on_popup_menu(wxCommandEvent& e);
1168 protected:
1169 void prepare_paint();
1170 private:
1171 uint64_t pressed_row;
1172 uint64_t current_row;
1173 bool holding;
1174 wxwin_disassembler* p;
1176 bool do_exit_save();
1177 void add_row(uint64_t addr, const disasm_row& row, bool last);
1178 void add_rows(const std::map<uint64_t, disasm_row>& rowdata);
1179 void add_rows_main(const std::map<uint64_t, disasm_row>& rowdata);
1180 void run_disassembler(const std::string& disasm, uint64_t addrbase, uint64_t count);
1181 void scroll_pane(uint64_t line);
1182 scroll_bar* scroll;
1183 _panel* panel;
1184 bool dirty;
1185 bool closing;
1188 wxwin_disassembler::_panel::_panel(wxwin_disassembler* parent)
1189 : text_framebuffer_panel(parent, 20, 5, wxID_ANY, NULL)
1191 p = parent;
1192 pos = 0;
1193 pressed_row = 0;
1194 current_row = 0;
1195 holding = false;
1196 this->Connect(wxEVT_SIZE, wxSizeEventHandler(wxwin_disassembler::_panel::on_size), NULL, this);
1197 this->Connect(wxEVT_RIGHT_DOWN, wxMouseEventHandler(wxwin_disassembler::_panel::on_mouse), NULL,
1198 this);
1199 this->Connect(wxEVT_RIGHT_UP, wxMouseEventHandler(wxwin_disassembler::_panel::on_mouse), NULL, this);
1200 this->Connect(wxEVT_MOTION, wxMouseEventHandler(wxwin_disassembler::_panel::on_mouse), NULL, this);
1201 this->Connect(wxEVT_MOUSEWHEEL, wxMouseEventHandler(wxwin_disassembler::_panel::on_mouse), NULL,
1202 this);
1205 void wxwin_disassembler::_panel::on_size(wxSizeEvent& e)
1207 wxSize newsize = e.GetSize();
1208 auto tcell = get_cell();
1209 size_t lines = newsize.y / tcell.second;
1210 size_t linelen = newsize.x / tcell.first;
1211 if(lines < 1) lines = 1;
1212 if(linelen < 1) linelen = 1;
1213 set_size(linelen, lines);
1214 p->get_scroll()->set_page_size(lines);
1215 request_paint();
1216 e.Skip();
1219 void wxwin_disassembler::_panel::on_mouse(wxMouseEvent& e)
1221 uint64_t local_line = pos + e.GetY() / get_cell().second;
1222 if(e.RightDown()) {
1223 if(local_line < rows.size()) {
1224 holding = true;
1225 pressed_row = local_line;
1227 }else if(e.RightUp()) {
1228 holding = false;
1229 wxMenu menu;
1230 menu.Connect(wxEVT_COMMAND_MENU_SELECTED,
1231 wxCommandEventHandler(wxwin_disassembler::_panel::on_popup_menu), NULL, this);
1232 menu.Append(wxID_COPY, wxT("Copy to clipboard"));
1233 menu.Append(wxID_SAVE, wxT("Save to file"));
1234 menu.AppendSeparator();
1235 menu.Append(wxID_DISASM_MORE, wxT("Disassemble more"));
1236 menu.AppendSeparator();
1237 menu.Append(wxID_DELETE, wxT("Delete"));
1238 PopupMenu(&menu);
1239 } else {
1240 current_row = min(local_line, static_cast<uint64_t>(rows.size()));
1241 request_paint();
1243 unsigned speed = 1;
1244 if(e.ShiftDown())
1245 speed = 10;
1246 p->get_scroll()->apply_wheel(e.GetWheelRotation(), e.GetWheelDelta(), speed);
1249 wxSize wxwin_disassembler::_panel::DoGetBestSize() const
1251 return wxSize(40 * 8, 25 * 16);
1254 void wxwin_disassembler::_panel::prepare_paint()
1256 p->get_scroll()->set_range(rows.size());
1257 uint64_t m = min(pressed_row, current_row);
1258 uint64_t M = max(pressed_row, current_row);
1259 auto s = get_characters();
1260 uint64_t i;
1261 for(i = pos; i < pos + s.second && i < rows.size(); i++) {
1262 bool selected = holding && (i >= m) && (i <= M);
1263 uint32_t fg = selected ? 0x0000FF : 0x000000;
1264 uint32_t bg = selected ? 0x000000 : 0xFFFFFF;
1265 write(row_map[rows[i]].row, s.first, 0, i - pos, fg, bg);
1267 for(; i < pos + s.second; i++) {
1268 write("", s.first, 0, i - pos, 0xFFFFFF, 0xFFFFFF);
1272 void wxwin_disassembler::on_menu(wxCommandEvent& e)
1274 if(e.GetId() == wxID_EXIT) {
1275 if(dirty) {
1276 int r = prompt_for_save(this, "Disassembly");
1277 if(r < 0 || (r > 0 && !do_exit_save()))
1278 return;
1280 Destroy();
1281 return;
1282 } else if(e.GetId() == wxID_SAVE) {
1283 try {
1284 std::string filename = choose_file_save(this, "Save disassembly to",
1285 project_otherpath(), filetype_disassembly);
1286 std::ofstream s(filename, std::ios::app);
1287 if(!s) throw std::runtime_error("Error opening output file");
1288 for(auto& i : panel->rows)
1289 s << panel->row_map[i].row << std::endl;
1290 if(!s) throw std::runtime_error("Error writing output file");
1291 dirty = false;
1292 } catch(canceled_exception& e) {
1293 } catch(std::exception& e) {
1294 wxMessageBox(towxstring(e.what()), _T("Error creating file"), wxICON_EXCLAMATION |
1295 wxOK, this);
1297 } else if(e.GetId() == wxID_DISASM) {
1298 std::string tmp;
1299 dialog_disassemble* d = new dialog_disassemble(this);
1300 if(d->ShowModal() != wxID_OK) {
1301 d->Destroy();
1302 return;
1304 std::string disasm = d->get_disassembler();
1305 uint64_t addr = d->get_address();
1306 uint64_t count = d->get_count();
1307 d->Destroy();
1308 runemufn_async([this, disasm, addr, count]() {
1309 this->run_disassembler(disasm, addr, count);
1311 } else if(e.GetId() == wxID_GOTO) {
1312 try {
1313 std::string to = pick_text(this, "Goto", "Enter address to go to:", "");
1314 runemufn_async([this, to]() {
1315 uint64_t addr;
1316 uint64_t base = 0;
1317 std::string vma;
1318 std::string offset;
1319 std::string _to = to;
1320 size_t sp = _to.find_first_of("+");
1321 if(sp >= _to.length()) {
1322 offset = _to;
1323 } else {
1324 vma = _to.substr(0, sp);
1325 offset = _to.substr(sp + 1);
1327 if(vma != "") {
1328 bool found = false;
1329 for(auto i : lsnes_memory.get_regions()) {
1330 if(i->name == vma) {
1331 base = i->base;
1332 found = true;
1335 if(!found) {
1336 runuifun([this] {
1337 show_message_ok(this, "Error in address",
1338 "No such memory area known",
1339 wxICON_EXCLAMATION);
1341 return;
1344 try {
1345 addr = hex::from<uint64_t>(offset);
1346 } catch(std::exception& e) {
1347 runuifun([this] {
1348 show_message_ok(this, "Error in address",
1349 "Expected <hexdigits> or <name>+<hexdigits>",
1350 wxICON_EXCLAMATION);
1352 return;
1354 addr += base;
1355 runuifun([this, addr]() {
1356 uint64_t nrow = 0;
1357 uint64_t low = 0;
1358 uint64_t high = this->panel->rows.size();
1359 while(low < high && low < high - 1) {
1360 nrow = (low + high) / 2;
1361 if(this->panel->rows[nrow] > addr)
1362 high = nrow;
1363 else if(this->panel->rows[nrow] < addr)
1364 low = nrow;
1365 else
1366 break;
1368 this->scroll_pane(nrow);
1371 } catch(canceled_exception& e) {
1376 void remove_from_array(std::vector<uint64_t>& v, uint64_t e)
1378 //Binary search for the element to remove.
1379 size_t low = 0;
1380 size_t high = v.size();
1381 size_t mid = 0;
1382 while(low < high) {
1383 mid = (low + high) / 2;
1384 if(v[mid] < e)
1385 low = mid;
1386 else if(v[mid] > e)
1387 high = mid;
1388 else
1389 break;
1391 if(v[mid] == e)
1392 v.erase(v.begin() + mid);
1395 void wxwin_disassembler::_panel::on_popup_menu(wxCommandEvent& e)
1397 if(e.GetId() == wxID_DISASM_MORE)
1399 if(current_row >= rows.size())
1400 return;
1401 uint64_t base = rows[current_row];
1402 uint64_t rbase = base;
1403 if(!row_map.count(base))
1404 return;
1405 auto& r = row_map[base];
1406 base = base + r.cover;
1407 std::string disasm = r.language;
1408 dialog_disassemble* d = new dialog_disassemble(this, base, disasm);
1409 if(d->ShowModal() != wxID_OK) {
1410 d->Destroy();
1411 return;
1413 disasm = d->get_disassembler();
1414 uint64_t addr = d->get_address();
1415 uint64_t count = d->get_count();
1416 d->Destroy();
1417 auto pp = p;
1418 runemufn_async([pp, disasm, addr, count]() {
1419 pp->run_disassembler(disasm, addr, count);
1421 //Delete entries in (rbase, addr) if addr = base.
1422 if(addr == base) {
1423 for(uint64_t i = rbase + 1; i < addr; i++)
1424 if(row_map.count(i)) {
1425 //This line needs to be removed from rows too.
1426 row_map.erase(i);
1427 remove_from_array(rows, i);
1431 std::string str;
1432 uint64_t m = min(min(pressed_row, current_row), (uint64_t)rows.size());
1433 uint64_t M = min(max(pressed_row, current_row) + 1, (uint64_t)rows.size());
1434 size_t lines = 0;
1436 for(uint64_t i = m; i < M; i++) {
1437 try {
1438 std::string mline = row_map[rows[i]].row;
1439 if(lines == 1) str += "\n";
1440 str += mline;
1441 if(lines >= 1) str += "\n";
1442 lines++;
1443 } catch(...) {
1447 switch(e.GetId()) {
1448 case wxID_COPY:
1449 if (wxTheClipboard->Open()) {
1450 wxTheClipboard->SetData(new wxTextDataObject(towxstring(str)));
1451 wxTheClipboard->Close();
1453 break;
1454 case wxID_SAVE:
1455 try {
1456 std::string filename = choose_file_save(this, "Save disassembly fragment to",
1457 project_otherpath(), filetype_disassembly);
1458 std::ofstream s(filename, std::ios::app);
1459 if(!s) throw std::runtime_error("Error opening output file");
1460 if(lines == 1) str += "\n";
1461 s << str;
1462 if(!s) throw std::runtime_error("Error writing output file");
1463 } catch(canceled_exception& e) {
1464 } catch(std::exception& e) {
1465 wxMessageBox(towxstring(e.what()), _T("Error creating file"), wxICON_EXCLAMATION |
1466 wxOK, this);
1468 break;
1469 case wxID_DELETE:
1470 for(uint64_t i = m; i < M; i++)
1471 row_map.erase(rows[i]);
1472 rows.erase(rows.begin() + m, rows.begin() + M);
1473 if(m != M)
1474 p->dirty = true;
1475 request_paint();
1476 break;
1480 std::string format_vma_offset(memory_region& region, uint64_t offset)
1482 std::ostringstream y;
1483 y << region.name;
1484 size_t sizedigits = 0;
1485 uint64_t tmp = region.size - 1;
1486 while(tmp > 0) {
1487 tmp >>= 4;
1488 sizedigits++;
1490 y << "+" << std::hex << std::setfill('0') << std::setw(sizedigits) << offset;
1491 return y.str();
1494 std::string lookup_address(uint64_t raw)
1496 auto g = lsnes_memory.lookup(raw);
1497 if(!g.first)
1498 return hex::to<uint64_t>(raw);
1499 else
1500 return format_vma_offset(*g.first, g.second);
1503 inline int sign_compare(uint64_t a, uint64_t b)
1505 if(a < b) return -1;
1506 if(b < a) return 1;
1507 return 0;
1510 void insert_into_array(std::vector<uint64_t>& v, uint64_t e)
1512 //Binary search for the gap to insert to.
1513 size_t low = 0;
1514 size_t high = v.size();
1515 size_t mid = 0;
1516 while(low < high) {
1517 mid = (low + high) / 2;
1518 int s1 = sign_compare(v[mid], e);
1519 int s2 = ((mid + 1) < v.size()) ? sign_compare(v[mid + 1], e) : 1;
1520 if(s1 < 0 && s2 > 0)
1521 break;
1522 else if(s1 == 0 || s2 == 0)
1523 return;
1524 else if(s1 > 0)
1525 high = mid;
1526 else if(s2 < 0)
1527 low = mid;
1529 if(mid < v.size() && v[mid] < e)
1530 mid++;
1531 v.insert(v.begin() + mid, e);
1534 void wxwin_disassembler::add_row(uint64_t addr, const disasm_row& row, bool last)
1536 auto& rows = panel->rows;
1537 auto& row_map = panel->row_map;
1538 if(row_map.count(addr)) {
1539 row_map[addr] = row;
1540 } else {
1541 //We need to insert the row into rows.
1542 row_map[addr] = row;
1543 insert_into_array(rows, addr);
1545 dirty = true;
1546 if(!last)
1547 for(uint64_t i = addr + 1; i < addr + row.cover; i++)
1548 if(row_map.count(i)) {
1549 //This line needs to be removed from rows too.
1550 row_map.erase(i);
1551 remove_from_array(rows, i);
1555 void wxwin_disassembler::add_rows(const std::map<uint64_t, disasm_row>& rowdata)
1557 for(auto i = rowdata.begin(); i != rowdata.end(); i++) {
1558 auto j = i;
1559 j++;
1560 bool last = (j == rowdata.end());
1561 add_row(i->first, i->second, last);
1563 panel->request_paint();
1566 void wxwin_disassembler::add_rows_main(const std::map<uint64_t, disasm_row>& rowdata)
1568 std::map<uint64_t, disasm_row> _rowdata;
1569 for(auto& i : rowdata) {
1570 _rowdata[i.first] = i.second;
1571 _rowdata[i.first].row = lookup_address(i.first) + " " + i.second.row;
1573 runuifun([this, _rowdata]() { this->add_rows(_rowdata); });
1576 template<typename T, bool hex> disasm_row _disassemble_data_item(uint64_t& addrbase, int endian,
1577 const std::string& disasm)
1579 char buf[sizeof(T)];
1580 for(size_t i = 0; i < sizeof(T); i++)
1581 buf[i] = lsnes_memory.read<uint8_t>(addrbase + i);
1582 disasm_row r;
1583 if(hex)
1584 r.row = (stringfmt() << "DATA 0x" << hex::to<T>(serialization::read_endian<T>(buf, endian))).
1585 str();
1586 else if(sizeof(T) > 1)
1587 r.row = (stringfmt() << "DATA " << serialization::read_endian<T>(buf, endian)).str();
1588 else
1589 r.row = (stringfmt() << "DATA " << (int)serialization::read_endian<T>(buf, endian)).str();
1590 r.cover = sizeof(T);
1591 r.language = disasm;
1592 addrbase += sizeof(T);
1593 return r;
1596 disasm_row disassemble_data_item(uint64_t& addrbase, const std::string& disasm)
1598 int endian;
1599 if(disasm[7] == 'l') endian = -1;
1600 if(disasm[7] == 'h') endian = 0;
1601 if(disasm[7] == 'b') endian = 1;
1602 switch(disasm[6]) {
1603 case 'b': return _disassemble_data_item<int8_t, false>(addrbase, endian, disasm);
1604 case 'B': return _disassemble_data_item<uint8_t, false>(addrbase, endian, disasm);
1605 case 'c': return _disassemble_data_item<uint8_t, true>(addrbase, endian, disasm);
1606 case 'C': return _disassemble_data_item<uint16_t, true>(addrbase, endian, disasm);
1607 case 'd': return _disassemble_data_item<int32_t, false>(addrbase, endian, disasm);
1608 case 'D': return _disassemble_data_item<uint32_t, false>(addrbase, endian, disasm);
1609 case 'f': return _disassemble_data_item<float, false>(addrbase, endian, disasm);
1610 case 'F': return _disassemble_data_item<double, false>(addrbase, endian, disasm);
1611 case 'h': return _disassemble_data_item<ss_int24_t, false>(addrbase, endian, disasm);
1612 case 'H': return _disassemble_data_item<ss_uint24_t, false>(addrbase, endian, disasm);
1613 case 'i': return _disassemble_data_item<ss_uint24_t, true>(addrbase, endian, disasm);
1614 case 'I': return _disassemble_data_item<uint32_t, true>(addrbase, endian, disasm);
1615 case 'q': return _disassemble_data_item<int64_t, false>(addrbase, endian, disasm);
1616 case 'Q': return _disassemble_data_item<uint64_t, false>(addrbase, endian, disasm);
1617 case 'r': return _disassemble_data_item<uint64_t, true>(addrbase, endian, disasm);
1618 case 'w': return _disassemble_data_item<int16_t, false>(addrbase, endian, disasm);
1619 case 'W': return _disassemble_data_item<uint16_t, false>(addrbase, endian, disasm);
1621 throw std::runtime_error("Invalid kind of data");
1624 void wxwin_disassembler::scroll_pane(uint64_t line)
1626 unsigned r = panel->get_characters().second;
1627 unsigned offset = r / 2;
1628 if(offset > line)
1629 scroll->set_position(panel->pos = 0);
1630 else if(line + r < panel->rows.size())
1631 scroll->set_position(panel->pos = line - offset);
1632 else
1633 scroll->set_position(panel->pos = panel->rows.size() - r);
1634 panel->request_paint();
1637 void wxwin_disassembler::on_wclose(wxCloseEvent& e)
1639 if(dirty && !wxwidgets_exiting) {
1640 int r = prompt_for_save(this, "Disassembly");
1641 if(r < 0 || (r > 0 && !do_exit_save()))
1642 return;
1644 if(!closing)
1645 Destroy();
1646 closing = true;
1649 void wxwin_disassembler::run_disassembler(const std::string& disasm, uint64_t addrbase, uint64_t count)
1651 std::map<uint64_t, disasm_row> rowdata;
1652 if(regex_match("\\$data:.*", disasm)) {
1653 try {
1654 for(uint64_t i = 0; i < count; i++) {
1655 uint64_t base = addrbase;
1656 disasm_row r = disassemble_data_item(addrbase, disasm);
1657 rowdata[base] = r;
1659 } catch(std::exception& e) {
1660 std::string err = e.what();
1661 runuifun([this, err]() { show_message_ok(this, "Error in disassembler",
1662 "Error in disassember: " + err, wxICON_EXCLAMATION); });
1663 return;
1665 add_rows_main(rowdata);
1666 return;
1668 disassembler* d;
1669 try {
1670 d = &disassembler::byname(disasm);
1671 } catch(std::exception& e) {
1672 runuifun([this, disasm]() { show_message_ok(this, "Error in disassembler",
1673 "No disassembler '" + disasm + "' found", wxICON_EXCLAMATION); });
1674 return;
1676 for(uint64_t i = 0; i < count; i++) {
1677 uint64_t base = addrbase;
1678 disasm_row r;
1679 r.row = d->disassemble(addrbase, [&addrbase]() -> unsigned char {
1680 return lsnes_memory.read<uint8_t>(addrbase++);
1682 r.cover = addrbase - base;
1683 r.language = disasm;
1684 rowdata[base] = r;
1686 add_rows_main(rowdata);
1689 bool wxwin_disassembler::do_exit_save()
1691 back:
1692 try {
1693 std::string filename = choose_file_save(this, "Save disassembly to",
1694 project_otherpath(), filetype_disassembly);
1695 std::ofstream s(filename, std::ios::app);
1696 if(!s) throw std::runtime_error("Error opening output file");
1697 for(auto& i : panel->rows)
1698 s << panel->row_map[i].row << std::endl;
1699 if(!s) throw std::runtime_error("Error writing output file");
1700 dirty = false;
1701 } catch(canceled_exception& e) {
1702 return false;
1703 } catch(std::exception& e) {
1704 wxMessageBox(towxstring(e.what()), _T("Error creating file"), wxICON_EXCLAMATION |
1705 wxOK, this);
1706 goto back;
1708 return true;
1711 wxwin_disassembler::wxwin_disassembler(wxWindow* parent)
1712 : wxFrame(parent, wxID_ANY, towxstring("lsnes: Disassembler"), wxDefaultPosition,
1713 wxDefaultSize, wxMINIMIZE_BOX | wxRESIZE_BORDER | wxSYSTEM_MENU | wxCAPTION | wxCLOSE_BOX |
1714 wxCLIP_CHILDREN)
1716 closing = false;
1717 dirty = false;
1718 wxBoxSizer* top_s = new wxBoxSizer(wxHORIZONTAL);
1719 SetSizer(top_s);
1720 top_s->Add(panel = new _panel(this), 1, wxGROW);
1721 top_s->Add(scroll = new scroll_bar(this, wxID_ANY, true), 0, wxGROW);
1722 scroll->set_page_size(panel->get_characters().second);
1723 scroll->set_handler([this](scroll_bar& s) {
1724 this->panel->pos = s.get_position();
1725 this->panel->request_paint();
1727 wxMenuBar* mb;
1728 wxStatusBar* sb;
1729 wxMenu* menu;
1731 SetMenuBar(mb = new wxMenuBar);
1732 SetStatusBar(sb = new wxStatusBar(this));
1733 mb->Append(menu = new wxMenu(), wxT("File"));
1734 menu->Append(wxID_DISASM, wxT("Disassemble..."));
1735 menu->AppendSeparator();
1736 menu->Append(wxID_SAVE, wxT("Save"));
1737 menu->AppendSeparator();
1738 menu->Append(wxID_EXIT, wxT("Close"));
1739 Connect(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(wxwin_disassembler::on_menu),
1740 NULL, this);
1741 mb->Append(menu = new wxMenu(), wxT("Edit"));
1742 menu->Append(wxID_GOTO, wxT("Goto"));
1744 Connect(wxEVT_CLOSE_WINDOW, wxCloseEventHandler(wxwin_disassembler::on_wclose),
1745 NULL, this);
1746 //Very nasty hack.
1747 wxSize tmp = panel->GetMinSize();
1748 panel->SetMinSize(panel->DoGetBestSize());
1749 top_s->SetSizeHints(this);
1750 wxSize tmp2 = GetClientSize();
1751 panel->SetMinSize(tmp);
1752 top_s->SetSizeHints(this);
1753 SetClientSize(tmp2);
1756 dialog_breakpoints::dialog_breakpoints(wxwin_tracelog* parent)
1757 : wxDialog(parent, wxID_ANY, wxT("Breakpoints"))
1759 pwin = parent;
1760 regions = lsnes_memory.get_regions();
1761 wxBoxSizer* top_s = new wxBoxSizer(wxVERTICAL);
1762 SetSizer(top_s);
1763 top_s->Add(brklist = new wxListBox(this, wxID_ANY, wxDefaultPosition, wxSize(300, 400)), 1, wxGROW);
1764 brklist->Connect(wxEVT_COMMAND_LISTBOX_SELECTED,
1765 wxCommandEventHandler(dialog_breakpoints::on_selchange), NULL, this);
1766 populate_breakpoints();
1767 wxBoxSizer* pbutton_s = new wxBoxSizer(wxHORIZONTAL);
1768 pbutton_s->Add(addb = new wxButton(this, wxID_ANY, wxT("Add")), 0, wxGROW);
1769 pbutton_s->Add(delb = new wxButton(this, wxID_ANY, wxT("Remove")), 0, wxGROW);
1770 pbutton_s->AddStretchSpacer();
1771 pbutton_s->Add(ok = new wxButton(this, wxID_ANY, wxT("Close")), 0, wxGROW);
1772 delb->Enable(false);
1773 addb->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(dialog_breakpoints::on_add), NULL,
1774 this);
1775 delb->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(dialog_breakpoints::on_delete),
1776 NULL, this);
1777 ok->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(dialog_breakpoints::on_ok), NULL,
1778 this);
1779 top_s->Add(pbutton_s, 0, wxGROW);
1780 top_s->SetSizeHints(this);
1781 Fit();
1784 void dialog_breakpoints::populate_breakpoints()
1786 auto t = pwin->get_breakpoints();
1787 for(auto i : t) {
1788 std::string line = format_line(i);
1789 unsigned insert_pos = get_insert_pos(i);
1790 brklist->Insert(towxstring(line), insert_pos);
1791 listsyms.insert(listsyms.begin() + insert_pos, i);
1795 void dialog_breakpoints::on_add(wxCommandEvent& e)
1797 uint64_t addr;
1798 debug_type dtype;
1799 dialog_breakpoint_add* d = new dialog_breakpoint_add(this, regions);
1800 if(d->ShowModal() != wxID_OK) {
1801 d->Destroy();
1802 return;
1804 rpair(addr, dtype) = d->get_result();
1805 d->Destroy();
1806 runemufn_async([this, addr, dtype]() { pwin->add_breakpoint(addr, dtype); });
1807 auto ent = std::make_pair(addr, dtype);
1808 std::string line = format_line(ent);
1809 unsigned insert_pos = get_insert_pos(ent);
1810 brklist->Insert(towxstring(line), insert_pos);
1811 listsyms.insert(listsyms.begin() + insert_pos, ent);
1814 void dialog_breakpoints::on_delete(wxCommandEvent& e)
1816 int idx = brklist->GetSelection();
1817 if(idx == wxNOT_FOUND)
1818 return;
1819 uint64_t addr;
1820 debug_type dtype;
1821 addr = listsyms[idx].first;
1822 dtype = listsyms[idx].second;
1823 runemufn_async([this, addr, dtype]() { pwin->remove_breakpoint(addr, dtype); });
1824 brklist->Delete(idx);
1825 listsyms.erase(listsyms.begin() + idx);
1828 size_t dialog_breakpoints::get_insert_pos(std::pair<uint64_t, debug_type> entry)
1830 size_t i = 0;
1831 for(i = 0; i < listsyms.size(); i++)
1832 if(entry < listsyms[i])
1833 return i;
1834 return i;
1837 std::string dialog_breakpoints::format_line(std::pair<uint64_t, debug_type> entry)
1839 std::string base = "";
1840 for(auto i : regions) {
1841 if(entry.first >= i->base && entry.first < i->base + i->size) {
1842 base = format_vma_offset(*i, entry.first - i->base);
1843 break;
1846 if(base == "")
1847 base = hex::to<uint64_t>(entry.first);
1848 if(entry.second == DEBUG_READ)
1849 return base + ": Read";
1850 if(entry.second == DEBUG_WRITE)
1851 return base + ": Write";
1852 if(entry.second == DEBUG_EXEC)
1853 return base + ": Execute";
1854 return base + ": Unknown";
1857 void dialog_breakpoints::on_selchange(wxCommandEvent& e)
1859 delb->Enable(brklist->GetSelection() != wxNOT_FOUND);
1863 void wxeditor_tracelog_display(wxWindow* parent, int cpuid, const std::string& cpuname)
1865 try {
1866 wxwin_tracelog* d = new wxwin_tracelog(parent, cpuid, cpuname);
1867 d->Show();
1868 } catch(std::exception& e) {
1869 show_message_ok(parent, "Error opening trace logger", e.what(), wxICON_EXCLAMATION);
1873 void wxeditor_disassembler_display(wxWindow* parent)
1875 wxwin_disassembler* d = new wxwin_disassembler(parent);
1876 d->Show();