From b393f3dd28f6cc5f1d73bd5f32afa2ae975a6c4a Mon Sep 17 00:00:00 2001 From: T0b1 Date: Thu, 8 Jun 2023 01:07:57 +0200 Subject: [PATCH] breakpoints; thread/frame selection that should work; TODO: is slow --- example_cus.txt | 57 ++++ src/backend/backend.cpp | 14 + src/backend/debug_backend.h | 17 +- src/backend/lldb/lldb_backend.cpp | 424 ++++++++++++++++++++++++-- src/backend/lldb/lldb_backend.h | 10 +- src/data.h | 12 +- src/frontend/frontend.cpp | 60 +++- src/frontend/target.h | 37 ++- src/frontend/window.cpp | 488 ++++++++++++++++++++++++++++-- src/frontend/window.h | 20 +- src/main.cpp | 3 + src/msg.h | 15 +- src/util/dag.h | 9 +- todos.txt | 5 +- 14 files changed, 1098 insertions(+), 73 deletions(-) create mode 100644 example_cus.txt diff --git a/example_cus.txt b/example_cus.txt new file mode 100644 index 0000000..989b3ef --- /dev/null +++ b/example_cus.txt @@ -0,0 +1,57 @@ +Symctx: Module: file = "/usr/lib64/ld-linux-x86-64.so.2", arch = "x86_64" +CompileUnit: id = {0x0000012c}, file = "/usr/src/debug/glibc-2.37-4.fc38.x86_64/elf/rtld.c", language = "c11" + Function: id = {0x0002de35}, name = "_dl_start", range = [0x000000000001c050-0x000000000001c786) + FuncType: id = {0x0002de35}, byte-size = 0, decl = rtld.c:520:1, compiler_type = "Elf64_Addr (void *)" + Blocks: id = {0x0002de35}, range = [0x0001c050-0x0001c786) + LineEntry: [0x000000000001c050-0x000000000001c084): /usr/src/debug/glibc-2.37-4.fc38.x86_64/elf/rtld.c:521:1, is_prologue_end = TRUE + Symbol: id = {0x000000c5}, range = [0x000000000001c050-0x000000000001c786), name="_dl_start" + +Module: (x86_64) /usr/lib64/ld-linux-x86-64.so.2 +CU: 0x2c7a330: CompileUnit{0x0000012c}, language = "c11", file = '/usr/src/debug/glibc-2.37-4.fc38.x86_64/elf/rtld.c' +0x2570850: Function{0x0002de35}, demangled = _dl_start, type = 0x7f645c1a2ad0 +0x2570888: Block{0x0002de35}, ranges = [0x0001c050-0x0001c786) +0x29f93d0: Block{0x0002de65}, parent = {0x0002de35}, ranges = [0x0001c2a8-0x0001c500) [0x0001c528-0x0001c528) [0x0001c5e4-0x0001c610) [0x0001c63e-0x0001c64a) [0x0001c6ad-0x0001c6cc) +0x291f950: Block{0x0002de80}, parent = {0x0002de65}, ranges = [0x0001c2a8-0x0001c327) +0x25658f0: Block{0x0002ded3}, parent = {0x0002de80}, ranges = [0x0001c2d8-0x0001c2db) [0x0001c2df-0x0001c2e6) [0x0001c2eb-0x0001c310) [0x0001c314-0x0001c31b) +0x25710a0: Block{0x0002deec}, parent = {0x0002ded3}, ranges = [0x0001c2f2-0x0001c310) +0x2571120: Block{0x0002df0b}, parent = {0x0002de65}, ranges = [0x0001c327-0x0001c500) [0x0001c5e4-0x0001c610) [0x0001c63e-0x0001c64a) [0x0001c6ad-0x0001c6cc) +0x2578790: Block{0x0002df61}, parent = {0x0002df0b}, ranges = [0x0001c39c-0x0001c3fb) [0x0001c63e-0x0001c645) +0x2578830: Block{0x0002df8a}, parent = {0x0002df0b}, ranges = [0x0001c3fb-0x0001c500) [0x0001c5e4-0x0001c610) [0x0001c645-0x0001c64a) [0x0001c6ad-0x0001c6cc) +0x2570390: Block{0x0002dfa5}, parent = {0x0002df8a}, name = "elf_dynamic_do_Rela", decl = do-rel.h:43:1, ranges = [0x0001c403-0x0001c414) [0x0001c417-0x0001c4de) [0x0001c4e7-0x0001c500) [0x0001c5e4-0x0001c610) [0x0001c6ad-0x0001c6cc) +0x2584e30: Block{0x0002e006}, parent = {0x0002dfa5}, ranges = [0x0001c403-0x0001c414) [0x0001c417-0x0001c4de) [0x0001c4e7-0x0001c500) [0x0001c5e4-0x0001c610) [0x0001c6ad-0x0001c6cc) +0x2571df0: Block{0x0002e045}, parent = {0x0002e006}, ranges = [0x0001c403-0x0001c410) [0x0001c470-0x0001c4d5) [0x0001c5e4-0x0001c610) +0x2571e70: Block{0x0002e062}, parent = {0x0002e045}, name = "elf_machine_rela", decl = dl-machine.h:245:1, ranges = [0x0001c403-0x0001c410) [0x0001c47f-0x0001c482) [0x0001c48a-0x0001c4d5) [0x0001c5e4-0x0001c610) +0x2579eb0: Block{0x0002e0b3}, parent = {0x0002e062}, ranges = [0x0001c403-0x0001c410) [0x0001c47f-0x0001c482) [0x0001c48a-0x0001c4d5) [0x0001c5e4-0x0001c610) +0x29b2080: Block{0x0002e0c0}, parent = {0x0002e0b3}, ranges = [0x0001c403-0x0001c410) [0x0001c48f-0x0001c4d5) [0x0001c5e4-0x0001c610) +0x29b21c0: Block{0x0002e0e1}, parent = {0x0002e006}, name = "dl_relocate_ld", decl = _itoa.h:77:1, ranges = [0x0001c434-0x0001c434) [0x0001c4ee-0x0001c500) +0x257fc90: Block{0x0002e107}, parent = {0x0002e006}, name = "elf_machine_rela_relative", decl = dl-machine.h:475:1, ranges = [0x0001c44e-0x0001c45c) [0x0001c460-0x0001c466) [0x0001c6ad-0x0001c6cc) +0x256e590: Block{0x0002e134}, parent = {0x0002e107}, ranges = [0x0001c44e-0x0001c45c) [0x0001c460-0x0001c466) [0x0001c6ad-0x0001c6cc) +0x256e670: Block{0x0002e185}, parent = {0x0002df0b}, name = "dl_relocate_ld", decl = _itoa.h:77:1, ranges = [0x0001c32b-0x0001c32d) [0x0001c33a-0x0001c33d) [0x0001c35b-0x0001c35b) +0x256e7b0: Block{0x0002e1aa}, parent = {0x0002de65}, name = "elf_machine_runtime_setup", decl = dl-machine.h:61:1, ranges = [0x0001c2a8-0x0001c2a8) +0x256e8d0: Block{0x0002e1f4}, parent = {0x0002de35}, name = "rtld_timer_start", decl = rtld.c:83:1, ranges = [0x0001c08f-0x0001c091) [0x0001c098-0x0001c09c) [0x0001c0a3-0x0001c0a6) [0x0001c0ad-0x0001c0b4) +0x256c4d0: Block{0x0002e216}, parent = {0x0002de35}, name = "elf_machine_load_address", decl = dl-machine.h:43:1, ranges = [0x0001c0b4-0x0001c0b4) +0x256c5d0: Block{0x0002e236}, parent = {0x0002de35}, name = "elf_machine_dynamic", decl = dl-machine.h:51:1, ranges = [0x0001c0b4-0x0001c0b4) +0x256c6f0: Block{0x0002e250}, parent = {0x0002de35}, name = "elf_get_dynamic_info", decl = get-dynamic-info.h:29:1, ranges = [0x0001c0b4-0x0001c0bb) [0x0001c0c2-0x0001c2a8) [0x0001c500-0x0001c528) [0x0001c610-0x0001c63e) [0x0001c64a-0x0001c688) [0x0001c6cc-0x0001c786) +0x256c830: Block{0x0002e287}, parent = {0x0002e250}, ranges = [0x0001c0b4-0x0001c0bb) [0x0001c0c2-0x0001c2a8) [0x0001c500-0x0001c528) [0x0001c610-0x0001c63e) [0x0001c64a-0x0001c688) [0x0001c6cc-0x0001c786) +0x2584640: Block{0x0002e297}, parent = {0x0002e287}, ranges = [0x0001c0b4-0x0001c0bb) [0x0001c0c2-0x0001c158) [0x0001c500-0x0001c528) +0x2584740: Block{0x0002e2ab}, parent = {0x0002e297}, ranges = [0x0001c0cb-0x0001c0fd) [0x0001c10a-0x0001c158) [0x0001c500-0x0001c528) +0x2584820: Block{0x0002e2bf}, parent = {0x0002e287}, name = "dl_relocate_ld", decl = _itoa.h:77:1, ranges = [0x0001c158-0x0001c15f) +0x2584920: Block{0x0002e2ea}, parent = {0x0002e287}, ranges = [0x0001c173-0x0001c216) [0x0001c610-0x0001c610) [0x0001c64a-0x0001c656) +0x25849e0: Block{0x0002e4a2}, parent = {0x0002de35}, name = "_dl_start_final", decl = rtld.c:451:1, ranges = [0x0001c534-0x0001c5ac) [0x0001c688-0x0001c6a8) +0x2584b20: Block{0x0002e4c3}, parent = {0x0002e4a2}, ranges = [0x0001c534-0x0001c5ac) [0x0001c688-0x0001c6a8) +0x2495c10: Block{0x0002e4d3}, parent = {0x0002e4c3}, name = "rtld_timer_start", decl = rtld.c:83:1, ranges = [0x0001c542-0x0001c544) [0x0001c54e-0x0001c55c) +0x2495d70: Block{0x0002e4f5}, parent = {0x0002e4c3}, ranges = [0x0001c688-0x0001c6a8) +0x2495df0: Block{0x0002e50a}, parent = {0x0002e4f5}, name = "rtld_timer_stop", decl = rtld.c:89:1, ranges = [0x0001c688-0x0001c68a) [0x0001c691-0x0001c6a3) +0x2495f50: Block{0x0002e536}, parent = {0x0002e50a}, ranges = [0x0001c688-0x0001c68a) [0x0001c691-0x0001c6a3) + + +Module: (x86_64) /home/klee/projects/dbgui/tmp/main +CU: 0x7f26dc135960: CompileUnit{0x00000001}, language = "c++14", file = '/home/klee/projects/dbgui/tmp/sec.cpp' +0x7f26dc13e5d0: Function{0x0000046f}, mangled = _Z9helper_fnv, demangled = helper_fn(), type = 0x7f26dc145a80 +0x7f26dc13e608: Block{0x0000046f}, ranges = [0x0040115a-0x00401176) + + +Module: (x86_64) /home/klee/projects/dbgui/tmp/main +CU: 0x7f26dc135870: CompileUnit{0x00000000}, language = "c++14", file = '/home/klee/projects/dbgui/tmp/main.cpp' +0x7f26dc13cea0: Function{0x000000a0}, demangled = main, type = 0x7f26dc19cbb0 +0x7f26dc13ced8: Block{0x000000a0}, ranges = [0x00401126-0x0040115a) diff --git a/src/backend/backend.cpp b/src/backend/backend.cpp index 41f2bf8..5df92db 100644 --- a/src/backend/backend.cpp +++ b/src/backend/backend.cpp @@ -57,4 +57,18 @@ void Backend::send_data_result(BackToFront::DataResult &&info) auto msg = std::make_unique( BackToFront::MsgType::data_result, std::move(info)); this->send_msg(std::move(msg)); +} +void Backend::send_selected_thread_changed(uint16_t idx) +{ + auto msg = std::make_unique( + BackToFront::MsgType::selected_thread_changed, + BackToFront::SelectedThreadChanged{.idx = idx}); + this->send_msg(std::move(msg)); +} +void Backend::send_selected_frame_changed(uint16_t idx) +{ + auto msg = std::make_unique( + BackToFront::MsgType::selected_frame_changed, + BackToFront::SelectedFrameChanged{.idx = idx}); + this->send_msg(std::move(msg)); } \ No newline at end of file diff --git a/src/backend/debug_backend.h b/src/backend/debug_backend.h index c0fd214..506790a 100644 --- a/src/backend/debug_backend.h +++ b/src/backend/debug_backend.h @@ -18,10 +18,10 @@ namespace dbgui::backend virtual void start() = 0; // returns whether the command was submitted - virtual bool step_into() = 0; + virtual bool step_into(bool source_step) = 0; // TODO: allow these two to not be implemented or emulated by the base class or frontend? - virtual bool step_over() = 0; - virtual bool step_out() = 0; + virtual bool step_over(bool source_step) = 0; + virtual bool step_out() = 0; virtual void cont() = 0; virtual void pause() = 0; @@ -29,8 +29,13 @@ namespace dbgui::backend virtual void add_data_node(const data::DataNode &) = 0; virtual void remove_data_node(uint64_t id) = 0; - virtual void add_breakpoint(uint64_t addr, size_t id) = 0; - virtual void remove_breakpoint(size_t id) = 0; + virtual void add_breakpoint(uint64_t addr, size_t id) = 0; + virtual bool add_breakpoint(const char *file, uint32_t line, size_t id) = 0; + virtual void remove_breakpoint(size_t id) = 0; + + // TODO: need generation to prevent race? + virtual void select_thread(uint16_t idx) = 0; + virtual void select_frame(uint16_t idx) = 0; auto retrieve_msg_for_frontend() -> std::optional> @@ -63,6 +68,8 @@ namespace dbgui::backend void send_frame_changed(BackToFront::FrameChanged &&); void send_frame_removed(BackToFront::FrameRemoved &&); void send_data_result(BackToFront::DataResult &&); + void send_selected_thread_changed(uint16_t idx); + void send_selected_frame_changed(uint16_t idx); private: std::mutex _back_front_msg_mutex; diff --git a/src/backend/lldb/lldb_backend.cpp b/src/backend/lldb/lldb_backend.cpp index 6a63fb0..2557970 100644 --- a/src/backend/lldb/lldb_backend.cpp +++ b/src/backend/lldb/lldb_backend.cpp @@ -8,12 +8,16 @@ #include #include #include +#include +#include #include #include #include #include #include +#include + using namespace dbgui::backend; namespace @@ -24,9 +28,15 @@ LLDBBackend::~LLDBBackend() {} LLDBBackend::LLDBBackend(std::string filename) { _filename = filename; + char buf[256]; + buf[0] = '\0'; + pthread_getname_np(pthread_self(), buf, sizeof(buf)); + // name lldb threads + pthread_setname_np(pthread_self(), "lldb_internal_thread"); lldb::SBDebugger::Initialize(); _instance = lldb::SBDebugger::Create(false); _target = _instance.CreateTarget(filename.c_str()); + pthread_setname_np(pthread_self(), buf); } void LLDBBackend::start() @@ -36,9 +46,19 @@ void LLDBBackend::start() auto error = lldb::SBError(); auto listener = lldb::SBListener(); + char buf[256]; + buf[0] = '\0'; + { + pthread_getname_np(pthread_self(), buf, sizeof(buf)); + // name lldb threads + pthread_setname_np(pthread_self(), "lldb_internal_thread"); + } _process = _target.Launch(listener, argv, nullptr, nullptr, nullptr, nullptr, cwd.c_str(), lldb::LaunchFlags::eLaunchFlagNone, true, error); + { + pthread_setname_np(pthread_self(), buf); + } _msg_thread = std::thread{[this]() { auto ptr = this->shared_from_this(); @@ -48,6 +68,7 @@ void LLDBBackend::start() void LLDBBackend::run_msg_loop() { + pthread_setname_np(pthread_self(), "msg_loop"); std::thread event_thread{[this]() { this->wait_for_debug_events(); }}; while (true) @@ -59,6 +80,7 @@ void LLDBBackend::run_msg_loop() void LLDBBackend::wait_for_debug_events() { using namespace lldb; + pthread_setname_np(pthread_self(), "lldb_msg_loop"); auto listener = _instance.GetListener(); while (true) @@ -329,7 +351,38 @@ void LLDBBackend::dump_threads() printf("Symctx: %.*s\n", static_cast(stream.GetSize()), stream.GetData()); - auto list = _target.FindTypes("test::MyType"); + /*auto mod = symctx.GetModule(); + auto cu = symctx.GetCompileUnit(); + auto block = symctx.GetBlock(); + auto le = symctx.GetLineEntry(); + + stream.Clear(); + mod.GetDescription(stream); + printf("Module: %.*s\n", static_cast(stream.GetSize()), + stream.GetData()); + + stream.Clear(); + cu.GetDescription(stream); + printf("CU: %.*s\n", static_cast(stream.GetSize()), stream.GetData()); + + stream.Clear(); + block.GetDescription(stream); + printf("Block: %.*s\n", static_cast(stream.GetSize()), + stream.GetData()); + + if (!le.IsValid()) + { + printf("LE not valid\n"); + } + /*uint32_t line, col; + line = le.GetLine(); + col = le.GetColumn(); + printf("LE: %u:%u\n", line, col);* + stream.Clear(); + le.GetDescription(stream); + printf("LE: %.*s\n", static_cast(stream.GetSize()), stream.GetData());*/ + + /*auto list = _target.FindTypes("test::MyType"); printf("List len: %lu\n", list.GetSize()); auto len = list.GetSize(); for (uint32_t i = 0; i < len; ++i) @@ -340,9 +393,9 @@ void LLDBBackend::dump_threads() printf("Type %u: %.*s\n", i, static_cast(stream.GetSize()), stream.GetData()); - } + }*/ - auto pc = frame.GetPC(); + /*auto pc = frame.GetPC(); stream.Clear(); //auto sc = @@ -366,7 +419,7 @@ void LLDBBackend::dump_threads() { start = pc; end = start + 0x100; - } + }*/ /*auto buf = std::vector{}; buf.resize(end - start); @@ -399,7 +452,7 @@ void LLDBBackend::dump_threads() } } -bool LLDBBackend::step_into() +bool LLDBBackend::step_into(bool source_step) { std::lock_guard g{_data_lock}; if (!_process) @@ -414,15 +467,18 @@ bool LLDBBackend::step_into() } // TODO: figure out what the runmodes mean - // TODO: this is source step - // thread.StepInto(); - - thread.StepInstruction(false); + if (source_step) + { + thread.StepInto(); + } else + { + thread.StepInstruction(false); + } return false; } -bool LLDBBackend::step_over() +bool LLDBBackend::step_over(bool source_step) { std::lock_guard g{_data_lock}; if (!_process) @@ -436,9 +492,13 @@ bool LLDBBackend::step_over() return false; } - //thread.StepOver(); - - thread.StepInstruction(true); + if (source_step) + { + thread.StepOver(); + } else + { + thread.StepInstruction(true); + } return false; } @@ -457,6 +517,7 @@ bool LLDBBackend::step_out() return false; } thread.StepOut(); + // TODO: allow step out of frame? return false; } @@ -485,6 +546,54 @@ void LLDBBackend::pause() _process->Stop(); } +void LLDBBackend::select_thread(uint16_t idx) +{ + std::lock_guard g{_data_lock}; + if (!_process) + { + return; + } + + if (_threads.size() <= idx) + { + return; + } + + printf("Set selected thread: %lu (%u)", _threads[idx].id, idx); + _process->SetSelectedThreadByID(_threads[idx].id); + auto thread = _process->GetSelectedThread(); + thread.SetSelectedFrame(0); + + this->check_thread_changes(); + this->check_frame_changes(); + // TODO: these can be a lot more selective + this->check_data_changes(); +} + +void LLDBBackend::select_frame(uint16_t idx) +{ + std::lock_guard g{_data_lock}; + if (!_process) + { + return; + } + + if (_frames.size() <= idx) + { + return; + } + + auto thread = _process->GetSelectedThread(); + uint64_t id = thread.GetThreadID(); + printf("ID: %lu\n", id); + thread.SetSelectedFrame(_frames[idx].lldb_idx); + uint64_t id2 = thread.GetThreadID(); + printf("ID2: %lu\n", id2); + this->check_frame_changes(); + // TODO: these can be a lot more selective + this->check_data_changes(); +} + void LLDBBackend::check_reg_changes() { auto thread = _process->GetSelectedThread(); @@ -569,6 +678,7 @@ void LLDBBackend::check_thread_changes() this->send_thread_removed(BackToFront::ThreadRemoved{i}); } _threads.clear(); + this->send_selected_thread_changed(0); } return; } @@ -576,6 +686,7 @@ void LLDBBackend::check_thread_changes() // TODO: work with SBThread::GetIndexID instead of relying // on the index order? uint16_t cache_idx = 0; + auto sel_thread = _process->GetSelectedThread(); for (uint32_t i = 0; i < len; ++i) { auto thread = _process->GetThreadAtIndex(i); @@ -584,14 +695,44 @@ void LLDBBackend::check_thread_changes() continue; } + if (thread.GetThreadID() == sel_thread.GetThreadID()) + { + if (_selected_thread != cache_idx) + { + _selected_thread = cache_idx; + this->send_selected_thread_changed(cache_idx); + // reset selected frame + if (_selected_frame != 0) + { + this->send_selected_frame_changed(0); + thread.SetSelectedFrame(0); + } + _selected_frame = 0; + } + } + + // this causes lldb to compute a proper stacktrace apparently + thread.GetCurrentExceptionBacktrace(); + auto frame = thread.GetFrameAtIndex(0); if (_threads.size() <= cache_idx) { - _threads.push_back( - Thread{.id = thread.GetThreadID(), - .ip = frame.GetPC(), - .name = thread.GetName(), - .cur_display_fn = frame.GetDisplayFunctionName()}); + auto thread_name = std::string{}; + auto lldb_thread_name = thread.GetName(); + if (lldb_thread_name) + { + // it is not valid to construct a string from a nullptr? + thread_name = lldb_thread_name; + } + auto frame_name = std::string{}; + if (auto frame_str = frame.GetDisplayFunctionName(); frame_str) + { + frame_name = frame_str; + } + _threads.push_back(Thread{.id = thread.GetThreadID(), + .ip = frame.GetPC(), + .name = thread_name, + .cur_display_fn = frame_name}); auto &info = _threads.back(); switch (thread.GetStopReason()) @@ -642,13 +783,21 @@ void LLDBBackend::check_thread_changes() cache_entry.ip = frame.GetPC(); send_change = true; } - auto name = std::string_view{thread.GetName()}; + auto name = std::string_view{}; + if (auto lldb_name = thread.GetName(); lldb_name) + { + name = lldb_name; + } if (name != cache_entry.name) { change_entry.name = name; send_change = true; } - auto display_name = std::string_view{frame.GetDisplayFunctionName()}; + auto display_name = std::string_view{}; + if (auto lldb_name = frame.GetDisplayFunctionName(); lldb_name) + { + display_name = lldb_name; + } if (display_name != cache_entry.cur_display_fn) { change_entry.cur_display_fn = display_name; @@ -703,7 +852,7 @@ void LLDBBackend::check_thread_changes() for (size_t i = 0; i < rem_count; ++i) { this->send_thread_removed( - BackToFront::ThreadRemoved{_threads.size() - i - 1}); + BackToFront::ThreadRemoved{_threads.size() - 1}); _threads.pop_back(); } } @@ -716,19 +865,26 @@ void LLDBBackend::check_frame_changes() if (!thread.IsValid()) { // TODO: cleanup + printf("Current thread not valid\n"); return; } + uint64_t id = thread.GetThreadID(); + printf("ID3: %lu\n", id); + size_t len = thread.GetNumFrames(); uint16_t cache_idx = 0; size_t selected_idx = 0; + printf("Num frames: %lu\n", len); for (size_t i = 0; i < len; ++i) { auto frame = thread.GetFrameAtIndex(i); if (!frame.IsValid()) { + printf("Frame %lu invalid\n", i); continue; } + printf("Looking at frame %lu\n", i); if (thread.GetSelectedFrame().IsEqual(frame)) { @@ -736,14 +892,23 @@ void LLDBBackend::check_frame_changes() } auto ip = frame.GetPC(); - auto name = std::string_view{frame.GetDisplayFunctionName()}; + auto name = std::string_view{}; + if (auto lldb_name = frame.GetDisplayFunctionName(); lldb_name) + { + // dont construct from nullptr + name = lldb_name; + } + printf(" IP: %lu, Name: %.*s\n", ip, static_cast(name.size()), + name.data()); if (_frames.size() <= cache_idx) { _frames.push_back(Frame{ .ip = ip, + .lldb_idx = i, .display_name = std::string{name}, }); + printf("Frame %u (%lu) is new\n", cache_idx, i); this->send_frame_changed(BackToFront::FrameChanged{ .ip = ip, .idx = cache_idx, .display_name = std::string{name}}); cache_idx++; @@ -751,22 +916,40 @@ void LLDBBackend::check_frame_changes() } auto &cache_entry = _frames[cache_idx]; + printf(" Cached IP: %lu, Name: %s\n", cache_entry.ip, + cache_entry.display_name.c_str()); if (cache_entry.ip != ip || cache_entry.display_name != name) { + printf("Frame %u (%lu) changed\n", cache_idx, i); this->send_frame_changed(BackToFront::FrameChanged{ .ip = ip, .idx = cache_idx, .display_name = std::string{name}}); + cache_entry.ip = ip; + if (cache_entry.display_name != name) + { + cache_entry.display_name = name; + } + } + if (cache_entry.lldb_idx != i) + { + cache_entry.lldb_idx = i; } cache_idx++; } + if (_selected_frame != selected_idx) + { + this->send_selected_frame_changed(selected_idx); + _selected_frame = selected_idx; + } + + printf("cache_idx: %u frame_size: %lu\n", cache_idx, _frames.size()); if (_frames.size() > cache_idx) { // here cache_idx == num_frames size_t rem_count = _frames.size() - cache_idx; for (size_t i = 0; i < rem_count; ++i) { - this->send_frame_removed( - BackToFront::FrameRemoved{_frames.size() - i - 1}); + this->send_frame_removed(BackToFront::FrameRemoved{_frames.size() - 1}); _frames.pop_back(); } } @@ -786,6 +969,7 @@ void LLDBBackend::add_data_node(const data::DataNode &node) _data_dag.add_node(node.id); // add child nodes + // TODO: make this somehow automatic switch (node.type) { using enum data::DataNode::Type; @@ -804,6 +988,17 @@ void LLDBBackend::add_data_node(const data::DataNode &node) _data_dag.add_edge(node.id, src_id); break; } + case line_entry: + { + auto src_id = std::get(node.data).src_id; + if (!_data_dag.nodes.contains(src_id)) + { + printf("Invalid add sequence\n"); + exit(1); + } + _data_dag.add_edge(node.id, src_id); + break; + } } _dag_linear_valid = false; @@ -934,6 +1129,30 @@ dbgui::data::DataResult LLDBBackend::calc_data_res(const data::DataNode &node) .type = data::TypeInfo{.type = data::TypeInfo::Type::u64}, .data = _reg_sets[info.set].values[info.idx]}; } + case frame_ip: + { + auto thread = _process->GetSelectedThread(); + auto frame = thread.GetSelectedFrame(); + if (!thread.IsValid() || !frame.IsValid()) + { + return data::DataResult{ + .id = node.id, + .success = false, + }; + } + + uint64_t pc = frame.GetPC(); + std::vector out{}; + out.resize(sizeof(uint64_t)); + *reinterpret_cast(out.data()) = pc; + + return data::DataResult{ + .id = node.id, + .success = true, + .type = data::TypeInfo{.type = data::TypeInfo::Type::u64}, + .data = std::move(out), + }; + } } break; } @@ -961,6 +1180,15 @@ dbgui::data::DataResult LLDBBackend::calc_data_res(const data::DataNode &node) auto sc = _target.ResolveSymbolContextForAddress( SBAddress{pc, _target}, eSymbolContextFunction | eSymbolContextSymbol); + auto le = sc.GetLineEntry(); + if (le.IsValid()) + { + SBStream stream{}; + le.GetDescription(stream); + printf("LE: %.*s\n", static_cast(stream.GetSize()), + stream.GetData()); + } + uint64_t start, end; if (sc.GetFunction().IsValid()) { @@ -1006,17 +1234,35 @@ dbgui::data::DataResult LLDBBackend::calc_data_res(const data::DataNode &node) for (size_t i = 0; i < inst_list.GetSize(); ++i) { - auto inst = inst_list.GetInstructionAtIndex(i); - const auto mnem = std::string_view{inst.GetMnemonic(_target)}; - const auto op = std::string_view{inst.GetOperands(_target)}; - const auto comm = std::string_view{inst.GetComment(_target)}; - auto addr = inst.GetAddress().GetLoadAddress(_target); - const auto len = inst.GetByteSize(); + auto inst = inst_list.GetInstructionAtIndex(i); + auto mnem = std::string_view{}; + auto op = std::string_view{}; + auto comm = std::string_view{}; + if (auto mnem_str = inst.GetMnemonic(_target); mnem_str) + { + mnem = mnem_str; + } + if (auto op_str = inst.GetOperands(_target); op_str) + { + op = op_str; + } + if (auto comm_str = inst.GetComment(_target); comm_str) + { + comm = comm_str; + } + auto addr = inst.GetAddress().GetLoadAddress(_target); + const auto len = inst.GetByteSize(); //SBStream stream{}; //inst.GetDescription(stream); //printf("Got inst: %.*s\n", stream.GetSize(), stream.GetData()); + if (comm.size() > 255) + { + // TODO: better handling + comm = comm.substr(0, 255); + } + if (mnem.size() > 255 || op.size() > 255 || comm.size() > 255) { printf("Instruction length limits exceeded:\n"); @@ -1042,6 +1288,77 @@ dbgui::data::DataResult LLDBBackend::calc_data_res(const data::DataNode &node) std::copy(comm.begin(), comm.end(), out.begin() + insert_idx); } + return data::DataResult{ + .id = node.id, + .success = true, + .type = data::TypeInfo{.type = data::TypeInfo::Type::custom}, + .data = std::move(out)}; + } + case line_entry: + { + using namespace lldb; + size_t addr_id = std::get(node.data).src_id; + const auto res_it = + std::find_if(_cached_data_results.begin(), _cached_data_results.end(), + [addr_id](const auto &el) { return el.id == addr_id; }); + if (res_it == _cached_data_results.end() || !res_it->success) + { + return data::DataResult{.id = node.id, .success = false}; + } + + // TODO: for now only accept u64 + if (res_it->type.type != data::TypeInfo::Type::u64) + { + return data::DataResult{.id = node.id, .success = false}; + } + + const auto addr = *reinterpret_cast(res_it->data.data()); + + auto sc = _target.ResolveSymbolContextForAddress(SBAddress{addr, _target}, + eSymbolContextLineEntry); + + auto le = sc.GetLineEntry(); + auto file_spec = le.GetFileSpec(); + + /*auto stream = SBStream{}; + sc.GetDescription(stream); + printf("SC DBG: %.*s\n", static_cast(stream.GetSize()), + stream.GetData()); + stream.Clear(); + le.GetDescription(stream); + printf("LE DBG: %.*s\n", static_cast(stream.GetSize()), + stream.GetData()); + stream.Clear(); + file_spec.GetDescription(stream); + printf("FS DBG: %.*s\n", static_cast(stream.GetSize()), + stream.GetData());*/ + if (!le.IsValid() || !file_spec.IsValid()) + { + return data::DataResult{.id = node.id, .success = false}; + } + + // TODO: for now the instlist is serialized in a custom format + // use a type structure for it later? + // it's basically an array of + // struct LineEntry { + // uint32_t line_no; + // uint32_t name_len; + // char file_name[]; + // }; + + // we can't know how long the path actually is without at least calling GetPath twice + // because it will return the smaller of the buffer size or actual path len + char path_buf[512]; + uint32_t path_len = file_spec.GetPath(path_buf, sizeof(path_buf)); + + printf("Calculated LE: %.*s\n", static_cast(path_len), path_buf); + + std::vector out; + out.resize(8 + path_len); + *reinterpret_cast(out.data()) = le.GetLine(); + *reinterpret_cast(out.data() + 4) = path_len; + std::memcpy(out.data() + 8, path_buf, path_len); + return data::DataResult{ .id = node.id, .success = true, @@ -1074,6 +1391,53 @@ void LLDBBackend::add_breakpoint(uint64_t addr, size_t id) _breakpoints.push_back(Breakpoint{.id = id, .lldb_id = bp.GetID()}); } +bool LLDBBackend::add_breakpoint(const char *file, uint32_t line, size_t id) +{ + using namespace lldb; + std::lock_guard g{_data_lock}; + if (!_process) + { + return false; + } + + if (std::find_if(_breakpoints.begin(), _breakpoints.end(), + [id](const auto &el) { return el.id == id; }) + != _breakpoints.end()) + { + printf("Trying to register same breakpoint id\n"); + exit(1); + } + + // TODO: try false? + auto file_spec = SBFileSpec{file, true}; + auto mod_list = SBFileSpecList{}; + + auto bp = + _target.BreakpointCreateByLocation(file_spec, line, 0, 0, mod_list, true); + if (!bp.IsValid()) + { + printf("BP not valid\n"); + return false; + } + + /*auto num = bp.GetNumLocations(); + auto res_num = bp.GetNumResolvedLocations(); + printf("Num: %lu, Res: %lu\n", num, res_num); + auto stream = SBStream{}; + for (size_t i = 0; i < num; ++i) + { + stream.Clear(); + auto loc = bp.GetLocationAtIndex(i); + loc.GetDescription(stream, eDescriptionLevelVerbose); + + printf("Loc %lu: %.*s\n", i, static_cast(stream.GetSize()), + stream.GetData()); + }*/ + + _breakpoints.push_back(Breakpoint{.id = id, .lldb_id = bp.GetID()}); + return true; +} + void LLDBBackend::remove_breakpoint(size_t id) { std::lock_guard g{_data_lock}; diff --git a/src/backend/lldb/lldb_backend.h b/src/backend/lldb/lldb_backend.h index 2f2688b..81dc67e 100644 --- a/src/backend/lldb/lldb_backend.h +++ b/src/backend/lldb/lldb_backend.h @@ -37,6 +37,7 @@ namespace dbgui::backend struct Frame { uint64_t ip; + size_t lldb_idx; std::string display_name; }; @@ -52,8 +53,8 @@ namespace dbgui::backend void start() override; - bool step_into() override; - bool step_over() override; + bool step_into(bool source_step) override; + bool step_over(bool source_step) override; bool step_out() override; void cont() override; @@ -63,8 +64,12 @@ namespace dbgui::backend void remove_data_node(uint64_t id) override; void add_breakpoint(uint64_t addr, size_t id) override; + bool add_breakpoint(const char *file, uint32_t line, size_t id) override; void remove_breakpoint(size_t id) override; + void select_thread(uint16_t idx); + void select_frame(uint16_t idx); + private: void run_msg_loop(); void wait_for_debug_events(); @@ -95,6 +100,7 @@ namespace dbgui::backend std::vector _threads = {}; std::vector _frames = {}; uint16_t _selected_frame = 0; + uint16_t _selected_thread = 0; util::DAG _data_dag = {}; std::vector _data_nodes = {}; diff --git a/src/data.h b/src/data.h index 36a3a81..b12c010 100644 --- a/src/data.h +++ b/src/data.h @@ -42,6 +42,7 @@ namespace dbgui::data enum class Type : uint8_t { reg, + frame_ip, // binds the selected frame // TODO: special IP/SP source? so that scope selection can apply to that? // variable, // const, @@ -65,21 +66,30 @@ namespace dbgui::data size_t src_id; }; + struct LineEntry + { + // Node that provides address to resolve + // must be u64 + size_t src_id; + }; + struct DataNode { enum class Type : uint8_t { data_source, disassemble, + line_entry, }; size_t id; Type type; - std::variant data; + std::variant data; }; // TODO: this should allow only updating part of arrays // and moving in it + // TODO: also allow inlining of small data (e.g smallvec or union) struct DataResult { // TODO: needs indicator that data was failed to be retrieved diff --git a/src/frontend/frontend.cpp b/src/frontend/frontend.cpp index 8e1b74d..727e64a 100644 --- a/src/frontend/frontend.cpp +++ b/src/frontend/frontend.cpp @@ -25,6 +25,7 @@ Frontend::Frontend() _windows.push_back(Window::create_frames(window_id++)); _windows.push_back(Window::create_disas(window_id++)); _windows.push_back(Window::create_bp(window_id++)); + _windows.push_back(Window::create_source(window_id++)); } void Frontend::run_frame() @@ -132,7 +133,6 @@ void Frontend::draw_header() { if (ImGui::BeginMenuBar()) { - const auto orig_cursor_x = ImGui::GetCursorPosX(); if (this->target) { switch (this->target->state) @@ -155,7 +155,7 @@ void Frontend::draw_header() } break; case running: - if (ImGui::Button("Pause")) + if (ImGui::Button("Pause ")) { this->target->backend->pause(); } @@ -168,9 +168,6 @@ void Frontend::draw_header() ImGui::EndDisabled(); } - // TODO: this depends on font-size, we just want to make sure the the other buttons don't flicker even if the state is changed - ImGui::SetCursorPosX(orig_cursor_x + 71.f); - ImGui::SeparatorEx(ImGuiSeparatorFlags_Vertical, 2.f); const auto is_paused = @@ -185,14 +182,14 @@ void Frontend::draw_header() if (ImGui::Button("Step Over")) { - if (this->target->backend->step_over()) + if (this->target->backend->step_over(!this->target->step_instruction)) { // TODO: already disable the UI into running mode } } if (ImGui::Button("Step Into")) { - if (this->target->backend->step_into()) + if (this->target->backend->step_into(!this->target->step_instruction)) { // TODO: already disable the UI into running mode } @@ -225,6 +222,28 @@ void Frontend::draw_header() ImGui::EndDisabled(); } + ImGui::SeparatorEx(ImGuiSeparatorFlags_Vertical, 2.f); + + if (!this->target) + { + ImGui::Button("Step Mode: Source"); + } else + { + if (this->target->step_instruction) + { + if (ImGui::Button("Step Mode: Instruction")) + { + this->target->step_instruction = false; + } + } else + { + if (ImGui::Button("Step Mode: Source")) + { + this->target->step_instruction = true; + } + } + } + ImGui::EndMenuBar(); } ImGui::End(); @@ -334,6 +353,25 @@ void Frontend::handle_msgs() { window.handle_data_res(result); } + break; + } + case selected_frame_changed: + { + if (this->target) + { + this->target->selected_frame = + std::get(msg->data).idx; + } + break; + } + case selected_thread_changed: + { + if (this->target) + { + this->target->selected_thread = + std::get(msg->data).idx; + } + break; } } } @@ -386,7 +424,7 @@ void Frontend::handle_reg_change(const BackToFront::RegsChanged &info) void Frontend::handle_thread_remove(const BackToFront::ThreadRemoved &info) { auto &threads = this->target->threads; - if (threads.size() >= info.idx) + if (threads.size() <= info.idx) { return; } @@ -439,7 +477,7 @@ void Frontend::handle_thread_change(BackToFront::ThreadChange &&info) void Frontend::handle_frame_remove(const BackToFront::FrameRemoved &info) { auto &frames = this->target->frames; - if (frames.size() >= info.idx) + if (frames.size() <= info.idx) { return; } @@ -458,6 +496,8 @@ void Frontend::handle_frame_change(BackToFront::FrameChanged &&info) auto &frame = frames[info.idx]; if (!frame) { + printf("Adding new frame at %u with name '%s'\n", info.idx, + info.display_name.c_str()); frame = Target::Frame{ .ip = info.ip, .display_name = info.display_name, @@ -465,6 +505,8 @@ void Frontend::handle_frame_change(BackToFront::FrameChanged &&info) return; } + printf("Updating frame at %u with name '%s'\n", info.idx, + info.display_name.c_str()); frame->ip = info.ip; frame->display_name = info.display_name; } \ No newline at end of file diff --git a/src/frontend/target.h b/src/frontend/target.h index d1cac3d..37a14a1 100644 --- a/src/frontend/target.h +++ b/src/frontend/target.h @@ -41,11 +41,41 @@ namespace dbgui::frontend std::string display_name; }; + // TODO: need a way for the backend to report where the breakpoint was actually placed + // (might have been moved) or if it could be placed at all + // iow let the backend resolve the address + possible file location for the breakpoint + // and then display it + // this should be needed anyways when you want to add expression-based breakpoints, e.g. 'main' struct Breakpoint { + struct FileLoc + { + std::string name; + uint32_t line; + }; + bool removed = false; - // TODO: srcloc - uint64_t addr; + std::variant data; + + bool at_addr(uint64_t addr) const + { + return this->data.index() == 0 + && std::get(this->data) == addr; + } + + bool at_file_loc(std::string_view file, uint32_t line) const + { + if (this->data.index() != 1) + { + return false; + } + const auto &loc = std::get(this->data); + if (loc.line == line && loc.name == file) + { + return true; + } + return false; + } }; Target(std::string filename); @@ -55,6 +85,9 @@ namespace dbgui::frontend uint64_t id; size_t data_node_id = 0; Arch arch; + bool step_instruction = false; + uint16_t selected_thread = 0; + uint16_t selected_frame = 0; std::vector reg_sets; std::vector> threads; diff --git a/src/frontend/window.cpp b/src/frontend/window.cpp index 0b988bf..6af8ce7 100644 --- a/src/frontend/window.cpp +++ b/src/frontend/window.cpp @@ -2,6 +2,8 @@ #include "frontend.h" #include #include "imgui_internal.h" +#include +#include using namespace dbgui; using namespace dbgui::frontend; @@ -23,6 +25,9 @@ bool Window::draw(Frontend &frontend) case breakpoints: return std::get(this->data).draw(frontend); break; + case source: + return std::get(this->data).draw(frontend); + break; default: printf("Unhandled window draw: %u\n", this->type); exit(1); } } @@ -73,6 +78,16 @@ Window Window::create_bp(size_t id) .data = BreakpointWindow{.id = id_str, .open = true}}; } +Window Window::create_source(size_t id) +{ + auto id_str = std::string{"Source##"}; + id_str.append(std::to_string(id)); + + return Window{.type = WindowType::source, + .data = + SourceWindow{.id = id_str, .open = true, .first = true}}; +} + bool RegWindow::draw(const Frontend &frontend) { //ImGui::SetNextWindowDockID(frontend.dock_id, ImGuiCond_Appearing); @@ -202,7 +217,8 @@ bool ThreadWindow::draw(const Frontend &frontend) } if (ImGui::BeginTable("table", 3, - ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) + ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg + | ImGuiTableFlags_ScrollX)) { ImGui::TableSetupColumn("ID"); ImGui::TableSetupColumn("Name"); @@ -222,6 +238,14 @@ bool ThreadWindow::draw(const Frontend &frontend) ImGui::TableNextRow(); ImGui::TableSetColumnIndex(0); + if (frontend.target->selected_thread == idx) + { + // TODO: style coloring + const auto col = + ImGui::GetColorU32(ImGui::GetStyleColorVec4(ImGuiCol_Button)); + ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg0, col); + } + std::snprintf(buf, sizeof(buf), "%lu", thread->id); ImGui::Text(buf); ImGui::TableNextColumn(); @@ -237,6 +261,45 @@ bool ThreadWindow::draw(const Frontend &frontend) std::snprintf(buf, sizeof(buf), "%lX", thread->ip); ImGui::Text(buf); } + + auto min_pos = + ImGui::TableGetCellBgRect(ImGui::GetCurrentTable(), 0).GetTL(); + auto max_pos = + ImGui::TableGetCellBgRect(ImGui::GetCurrentTable(), 2).GetBR(); + + /*if (idx & 1) + { + ImGui::GetWindowDrawList()->AddRect(min_pos, max_pos, + IM_COL32(255, 0, 0, 255)); + } else + { + ImGui::GetWindowDrawList()->AddRect(min_pos, max_pos, + IM_COL32(0, 0, 255, 255)); + }*/ + + // TODO: better scrollbar handling + auto win_pos = ImGui::GetWindowPos(); + auto min = ImGui::GetWindowContentRegionMin(); + auto max = ImGui::GetWindowContentRegionMax(); + min.x += win_pos.x; + min.y += win_pos.y; + max.x += win_pos.x; + max.y += win_pos.y; + + auto win = ImGui::GetCurrentWindow(); + if (win->ScrollbarY) + { + max.x -= win->ScrollbarSizes.x; + } + + ImGui::PushClipRect(min, max, false); + if (frontend.target->selected_thread != idx + && ImGui::IsMouseClicked(ImGuiMouseButton_Left) + && ImGui::IsMouseHoveringRect(min_pos, max_pos, true)) + { + frontend.target->backend->select_thread(idx); + } + ImGui::PopClipRect(); } ImGui::EndTable(); @@ -273,7 +336,8 @@ bool FrameWindow::draw(const Frontend &frontend) } if (ImGui::BeginTable("table", 1, - ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) + ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg + | ImGuiTableFlags_ScrollX)) { ImGui::TableSetupColumn("Location"); ImGui::TableHeadersRow(); @@ -291,6 +355,14 @@ bool FrameWindow::draw(const Frontend &frontend) ImGui::TableNextRow(); ImGui::TableSetColumnIndex(0); + if (frontend.target->selected_frame == idx) + { + // TODO: style coloring + const auto col = + ImGui::GetColorU32(ImGui::GetStyleColorVec4(ImGuiCol_Button)); + ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg0, col); + } + if (frame->display_name != "") { ImGui::Text(frame->display_name.c_str()); @@ -299,6 +371,23 @@ bool FrameWindow::draw(const Frontend &frontend) std::snprintf(buf, sizeof(buf), "%lX", frame->ip); ImGui::Text(buf); } + + auto min_pos = + ImGui::TableGetCellBgRect(ImGui::GetCurrentTable(), 0).GetTL(); + auto max_pos = + ImGui::TableGetCellBgRect(ImGui::GetCurrentTable(), 0).GetBR(); + max_pos.y += ImGui::GetTextLineHeight(); +#if 0 + ImGui::GetWindowDrawList()->AddRect(min_pos, max_pos, + IM_COL32(255, 0, 0, 255)); +#endif + if (frontend.target->selected_frame != idx + && ImGui::IsMouseClicked(ImGuiMouseButton_Left) + && ImGui::IsMouseHoveringRect(min_pos, max_pos, true)) + { + printf("Select frame %lu\n", idx); + frontend.target->backend->select_frame(idx); + } } ImGui::EndTable(); @@ -348,7 +437,7 @@ bool DisasmWindow::draw(Frontend &frontend) if (first) { - auto found = false; + /*auto found = false; uint16_t set_idx, reg_idx; for (size_t i = 0; i < frontend.target->reg_sets.size(); ++i) { @@ -375,16 +464,16 @@ bool DisasmWindow::draw(Frontend &frontend) this->ip_unsuccessful = true; this->disas_unsuccessful = true; return false; - } + }*/ this->ip_src_id = frontend.target->data_node_id++; this->disas_src_id = frontend.target->data_node_id++; - frontend.target->backend->add_data_node(data::DataNode{ - .id = this->ip_src_id, - .type = data::DataNode::Type::data_source, - .data = data::DataSource{ - .type = data::DataSource::Type::reg, - .data = data::DataSource::Reg{.set = set_idx, .idx = reg_idx}}}); + frontend.target->backend->add_data_node( + data::DataNode{.id = this->ip_src_id, + .type = data::DataNode::Type::data_source, + .data = data::DataSource{ + .type = data::DataSource::Type::frame_ip, + }}); frontend.target->backend->add_data_node( data::DataNode{.id = this->disas_src_id, @@ -418,7 +507,7 @@ bool DisasmWindow::draw(Frontend &frontend) auto &bps = frontend.target->breakpoints; const auto bp_it = std::find_if(bps.begin(), bps.end(), - [inst](const auto &el) { return el.addr == inst.addr; }); + [inst](const auto &el) { return el.at_addr(inst.addr); }); const auto is_bp = bp_it != bps.end() && !bp_it->removed; if (is_bp) { @@ -430,9 +519,16 @@ bool DisasmWindow::draw(Frontend &frontend) auto end_pos = pos; end_pos.x += 10.f; end_pos.y += 10.f; - ImGui::GetWindowDrawList()->AddRectFilled(pos, end_pos, - IM_COL32(255, 0, 0, 255)); + + pos.x += 5.f; + pos.y += 5.f; + + ImGui::GetWindowDrawList()->AddCircleFilled(pos, 5.f, + IM_COL32(255, 0, 0, 255)); } + + // TODO: write custom code to catch mouse clicks in the whole cell, including padding + // to make targeting easier if (ImGui::InvisibleButton("Break", ImVec2{10.f, ImGui::GetTextLineHeight()})) { @@ -449,10 +545,10 @@ bool DisasmWindow::draw(Frontend &frontend) if (idx == bps.size()) { bps.push_back( - Target::Breakpoint{.removed = false, .addr = inst.addr}); + Target::Breakpoint{.removed = false, .data = inst.addr}); } else { - bps[idx] = Target::Breakpoint{.removed = false, .addr = inst.addr}; + bps[idx] = Target::Breakpoint{.removed = false, .data = inst.addr}; } frontend.target->backend->add_breakpoint(inst.addr, idx); } else @@ -535,10 +631,11 @@ bool BreakpointWindow::draw(Frontend &frontend) } if (ImGui::BeginTable("table", 2, - ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) + ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg + | ImGuiTableFlags_SizingFixedFit)) { ImGui::TableSetupColumn("ID"); - ImGui::TableSetupColumn("Address"); + ImGui::TableSetupColumn("Location"); ImGui::TableHeadersRow(); auto &bps = frontend.target->breakpoints; @@ -558,7 +655,14 @@ bool BreakpointWindow::draw(Frontend &frontend) ImGui::Text("%zu", idx); ImGui::TableNextColumn(); - ImGui::Text("%lX", bp.addr); + if (auto addr = std::get_if(&bp.data)) + { + ImGui::Text("%lX", *addr); + } else + { + const auto &loc = std::get(bp.data); + ImGui::Text("%s:%u", loc.name.c_str(), loc.line); + } auto min_pos = ImGui::TableGetCellBgRect(ImGui::GetCurrentTable(), 0).GetTL(); @@ -593,6 +697,277 @@ bool BreakpointWindow::draw(Frontend &frontend) return false; } +bool SourceWindow::draw(Frontend &frontend) +{ + if (!ImGui::Begin(this->id.c_str())) + { + ImGui::End(); + return false; + } + + if (!frontend.target) + { + this->first = true; + this->file_name = ""; + this->lines.clear(); + this->file_data.clear(); + ImGui::End(); + return false; + } + + if (frontend.target->state == TargetState::stopped + || frontend.target->state == TargetState::startup) + { + this->file_name = ""; + this->lines.clear(); + this->file_data.clear(); + ImGui::End(); + return false; + } + + // TODO: we need to clean up the initializations/sync of the DAG somehow + if (first) + { + /*auto found = false; + uint16_t set_idx, reg_idx; + for (size_t i = 0; i < frontend.target->reg_sets.size(); ++i) + { + const auto &set = frontend.target->reg_sets[i]; + for (size_t j = 0; j < set.regs.size(); ++j) + { + if (frontend.target->arch == Arch::x86_64 && set.regs[j].name == "rip") + { + found = true; + set_idx = i; + reg_idx = j; + break; + } + } + if (found) + { + break; + } + } + + if (!found) + { + ImGui::End(); + this->file_name = ""; + this->lines.clear(); + this->file_data.clear(); + return false; + }*/ + + this->ip_src_id = frontend.target->data_node_id++; + this->line_entry_src_id = frontend.target->data_node_id++; + frontend.target->backend->add_data_node( + data::DataNode{.id = this->ip_src_id, + .type = data::DataNode::Type::data_source, + .data = data::DataSource{ + .type = data::DataSource::Type::frame_ip, + }}); + + frontend.target->backend->add_data_node( + data::DataNode{.id = this->line_entry_src_id, + .type = data::DataNode::Type::line_entry, + .data = data::LineEntry{.src_id = this->ip_src_id}}); + + first = false; + } + + if (!this->file_name.empty() && this->file_data.empty()) + { + // parse file + auto file = + std::ifstream{this->file_name, std::ios::binary | std::ios::ate}; + if (file.is_open()) + { + const auto size = file.tellg(); + if (size != 0) + { + this->file_data.resize(size); + file.seekg(std::ios::beg); + file.read(this->file_data.data(), this->file_data.size()); + + auto view = + std::string_view{this->file_data.data(), this->file_data.size()}; + assert(this->lines.empty()); + while (true) + { + // TODO: better line handling? + const auto pos = view.find_first_of('\n'); + if (pos == std::string_view::npos) + { + this->lines.push_back(view); + break; + } + + this->lines.push_back(view.substr(0, pos)); + view.remove_prefix(pos + 1); + } + } else + { + printf("File is empty: %s\n", this->file_name.c_str()); + // prevent spamming the open call + this->file_data.resize(1); + } + } else + { + printf("Couldn't open file: %s\n", this->file_name.c_str()); + // prevent spamming the open call + this->file_data.resize(1); + } + } + + if (this->file_name.empty()) + { + //auto txt_pos = ImGui::GetWindowPos(); + auto txt_pos = ImGui::GetWindowContentRegionMin(); + auto max = ImGui::GetWindowContentRegionMax(); + txt_pos.x += (max.x - txt_pos.x) * 0.5f; + txt_pos.y += (max.y - txt_pos.y) * 0.5f; + + const auto txt = "No source available"; + const auto txt_size = ImGui::CalcTextSize(txt); + txt_pos.x -= txt_size.x * 0.5f; + txt_pos.y -= txt_size.y * 0.5f; + + ImGui::SetCursorPos(txt_pos); + ImGui::TextDisabled(txt); + } + + auto pad = ImGui::GetStyle().CellPadding; + pad.x += 10.f; + + // TODO: use ImGuiListClipper to work with larger files + if (ImGui::BeginTable("table", 3, + ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_ScrollY + | ImGuiTableFlags_PadOuterX + | ImGuiTableFlags_ScrollX)) + { + ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed, 10.f); + ImGui::TableSetupColumn("LineNo"); + ImGui::TableSetupColumn("LineContent"); + //ImGui::TableHeadersRow(); + + for (size_t idx = 0; idx < this->lines.size(); ++idx) + { + //printf("Inst at %lx with '%s'\n", inst.addr, inst.fmt_str.c_str()); + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + + ImGui::PushID(idx); + auto &bps = frontend.target->breakpoints; + const auto bp_it = + std::find_if(bps.begin(), bps.end(), [this, idx](const auto &el) { + return el.at_file_loc(this->file_name, idx + 1); + }); + const auto is_bp = bp_it != bps.end() && !bp_it->removed; + if (is_bp) + { + auto win_pos = ImGui::GetWindowPos(); + auto pos = ImGui::GetCursorPos(); + pos.x += win_pos.x; + pos.y += win_pos.y - ImGui::GetScrollY(); + pos.y += (ImGui::GetTextLineHeight() - 10.f) / 2.f; + auto end_pos = pos; + end_pos.x += 10.f; + end_pos.y += 10.f; + + pos.x += 5.f; + pos.y += 5.f; + + ImGui::GetWindowDrawList()->AddCircleFilled(pos, 5.f, + IM_COL32(255, 0, 0, 255)); + } + + if (this->line == idx + 1) + { + auto win_pos = ImGui::GetWindowPos(); + auto pos = ImGui::GetCursorPos(); + pos.x += win_pos.x; + pos.y += win_pos.y - ImGui::GetScrollY(); + pos.y += (ImGui::GetTextLineHeight() - 10.f) / 2.f; + auto p2 = pos; + auto p3 = pos; + p2.x += 10.f; + p2.y += 5.f; + p3.y += 10.f; + + ImGui::GetWindowDrawList()->AddTriangleFilled( + pos, p2, p3, IM_COL32(227, 197, 103, 255)); + } + + // TODO: write custom code to catch mouse clicks in the whole cell, including padding + // to make targeting easier + if (ImGui::InvisibleButton("Break", + ImVec2{10.f, ImGui::GetTextLineHeight()})) + { + if (!is_bp) + { + size_t bp_idx = 0; + for (; bp_idx < bps.size(); ++bp_idx) + { + if (bps[bp_idx].removed) + { + break; + } + } + + if (frontend.target->backend->add_breakpoint(this->file_name.c_str(), + idx + 1, bp_idx)) + { + auto bp = + Target::Breakpoint{.removed = false, + .data = Target::Breakpoint::FileLoc{ + .name = this->file_name, + .line = static_cast(idx + 1)}}; + if (bp_idx == bps.size()) + { + bps.push_back(bp); + } else + { + bps[bp_idx] = bp; + } + } + } else + { + frontend.target->backend->remove_breakpoint(bp_it - bps.begin()); + bp_it->removed = true; + } + } + + ImGui::PopID(); + + ImGui::TableNextColumn(); + if (this->line == idx + 1) + { + // TODO: color config + //const auto col = + // ImGui::GetColorU32(ImGui::GetStyleColorVec4(ImGuiCol_Button)); + //ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg0, col); + if (this->line_changed) + { + ImGui::SetScrollHereY(); + } + } + + ImGui::Text("%lu ", idx + 1); + ImGui::TableNextColumn(); + + const auto line = this->lines[idx]; + ImGui::Text("%.*s", static_cast(line.size()), line.data()); + } + + ImGui::EndTable(); + } + + ImGui::End(); + + this->line_changed = false; + return false; +} + void Window::handle_data_res(const BackToFront::DataResult &result) { switch (this->type) @@ -602,6 +977,9 @@ void Window::handle_data_res(const BackToFront::DataResult &result) case disassembly: std::get(this->data).handle_data_res(result); break; + case source: + std::get(this->data).handle_data_res(result); + break; default: break; } } @@ -706,4 +1084,78 @@ void DisasmWindow::handle_data_res( this->max_mnem_len = max_mnem_len; this->max_op_len = max_op_len; +} + +void SourceWindow::handle_data_res( + const BackToFront::DataResult &result_wrapper) +{ + const auto &result = result_wrapper.result; + if (result.id == this->ip_src_id) + { + // should not need to care + return; + } + + if (result.id != this->line_entry_src_id) + { + return; + } + + if (!result.success || result.type.type != data::TypeInfo::Type::custom) + { + this->lines.clear(); + this->file_data.clear(); + this->file_name.clear(); + this->line = 0; + return; + } + + // parse line entry + // struct LineEntry { + // uint32_t line_no; + // uint32_t name_len; + // char file_name[]; + // }; + + if (result.data.size() < 8) + { + this->lines.clear(); + this->file_data.clear(); + this->file_name.clear(); + this->line = 0; + return; + } + + const auto line = *reinterpret_cast(result.data.data()); + const auto name_len = + *reinterpret_cast(result.data.data() + 4); + if (result.data.size() < 8 + name_len) + { + this->lines.clear(); + this->file_data.clear(); + this->file_name.clear(); + this->line = 0; + return; + } + + const auto file_view = std::string_view{ + reinterpret_cast(result.data.data() + 8), name_len}; + + printf("New LE: %.*s:%u", static_cast(file_view.size()), + file_view.data(), line); + + if (this->file_name != file_view) + { + // force reload + this->lines.clear(); + this->file_data.clear(); + this->file_name = file_view; + this->line_changed = true; + } + + if (this->line != line) + { + this->line = line; + this->line_changed = true; + } } \ No newline at end of file diff --git a/src/frontend/window.h b/src/frontend/window.h index 66f004c..8004bda 100644 --- a/src/frontend/window.h +++ b/src/frontend/window.h @@ -107,11 +107,28 @@ namespace dbgui::frontend bool open; }; + struct SourceWindow + { + bool draw(Frontend &); + void handle_data_res(const BackToFront::DataResult &result); + + std::string id; + bool open; + bool first; + bool line_changed; + + size_t ip_src_id, line_entry_src_id; + std::string file_name; + uint32_t line; + std::vector file_data; + std::vector lines; + }; + struct Window { WindowType type; std::variant + DisasmWindow, BreakpointWindow, SourceWindow> data; // if true, window is closed and should be deleted @@ -122,6 +139,7 @@ namespace dbgui::frontend static Window create_frames(size_t window_id); static Window create_disas(size_t window_id); static Window create_bp(size_t window_id); + static Window create_source(size_t window_id); void handle_data_res(const BackToFront::DataResult &result); }; diff --git a/src/main.cpp b/src/main.cpp index 9264d4e..e2a0ac4 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -12,6 +12,7 @@ #include #define GL_SILENCE_DEPRECATION #include // Will drag system OpenGL headers +#include #include "frontend/frontend.h" @@ -23,6 +24,8 @@ static void glfw_error_callback(int error, const char *description) // Main code int main(int, char **) { + pthread_setname_np(pthread_self(), "render_thread"); + glfwSetErrorCallback(glfw_error_callback); if (!glfwInit()) return 1; diff --git a/src/msg.h b/src/msg.h index 369147f..00f08c8 100644 --- a/src/msg.h +++ b/src/msg.h @@ -71,6 +71,8 @@ namespace dbgui frame_removed, // TODO: frame_moved, frame_added, etc? data_result, + selected_frame_changed, + selected_thread_changed, }; struct StateChange @@ -137,12 +139,23 @@ namespace dbgui data::DataResult result; }; + struct SelectedFrameChanged + { + uint16_t idx; + }; + + struct SelectedThreadChanged + { + uint16_t idx; + }; + struct Msg { MsgType type; std::variant + FrameRemoved, DataResult, SelectedFrameChanged, + SelectedThreadChanged> data; }; } // namespace BackToFront diff --git a/src/util/dag.h b/src/util/dag.h index 86d4eb5..dc79e25 100644 --- a/src/util/dag.h +++ b/src/util/dag.h @@ -71,7 +71,8 @@ namespace dbgui::util } } - if (out_edges.empty()) + // TODO: ghetto fix for linearize + if (out_edges.empty() && nodes.contains(from)) { root_nodes.emplace(from); } @@ -93,7 +94,9 @@ namespace dbgui::util std::erase(out_edges, id); if (out_edges.empty()) { - root_nodes.insert(el); + // TODO: ghetto fix for linearize + if (nodes.contains(el)) + root_nodes.insert(el); } } @@ -125,7 +128,7 @@ namespace dbgui::util for (auto id : self_copy.root_nodes) { - out.push_back(insert_idx); + out.push_back(id); } while (insert_idx < out.size()) diff --git a/todos.txt b/todos.txt index e736870..459142d 100644 --- a/todos.txt +++ b/todos.txt @@ -1 +1,4 @@ -make sure all the ImGui::Text stuff is converted to TextUnformatted unless formatting is needed \ No newline at end of file +- make sure all the ImGui::Text stuff is converted to TextUnformatted unless formatting is needed +- have a panel to create custom types (C++ to DWARF through compiler or smth for now) for custom casting +- memory + cpu statistics like VS +- visualization for branches (draw arror if branch is taken (or in both cases?)) \ No newline at end of file