#include "window.h" #include "frontend.h" #include #include "imgui_internal.h" #include #include using namespace dbgui; using namespace dbgui::frontend; bool Window::draw(Frontend &frontend) { switch (this->type) { using enum WindowType; case regs: return std::get(this->data).draw(frontend); break; case threads: return std::get(this->data).draw(frontend); break; case frames: return std::get(this->data).draw(frontend); break; case disassembly: return std::get(this->data).draw(frontend); break; case breakpoints: return std::get(this->data).draw(frontend); break; case source: return std::get(this->data).draw(frontend); break; case watch: return std::get(this->data).draw(frontend); default: printf("Unhandled window draw: %u\n", this->type); exit(1); } } Window Window::create_regs(size_t id) { auto id_str = std::string{"Registers##"}; id_str.append(std::to_string(id)); return Window{.type = WindowType::regs, .data = RegWindow{.id = id_str, .open = true}}; } Window Window::create_threads(size_t id) { auto id_str = std::string{"Threads##"}; id_str.append(std::to_string(id)); return Window{.type = WindowType::threads, .data = ThreadWindow{.id = id_str, .open = true}}; } Window Window::create_frames(size_t id) { auto id_str = std::string{"Frames##"}; id_str.append(std::to_string(id)); return Window{.type = WindowType::frames, .data = FrameWindow{.id = id_str, .open = true}}; } Window Window::create_disas(size_t id) { auto id_str = std::string{"Disassembly##"}; id_str.append(std::to_string(id)); return Window{.type = WindowType::disassembly, .data = DisasmWindow{.id = id_str, .open = true, .first = true}}; } Window Window::create_bp(size_t id) { auto id_str = std::string{"Breakpoints##"}; id_str.append(std::to_string(id)); return Window{.type = WindowType::breakpoints, .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}}; } Window Window::create_watch(size_t id) { auto id_str = std::string{"Watch##"}; id_str.append(std::to_string(id)); return Window{.type = WindowType::watch, .data = WatchWindow{.id = id_str, .open = true, .first = true}}; } bool RegWindow::draw(const Frontend &frontend) { //ImGui::SetNextWindowDockID(frontend.dock_id, ImGuiCond_Appearing); if (!ImGui::Begin(this->id.c_str())) { ImGui::End(); return false; } if (!frontend.target) { ImGui::End(); return false; } if (frontend.target->state == TargetState::running) { ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetStyleColorVec4(ImGuiCol_TextDisabled)); } if (ImGui::BeginTable("table", 1, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) { const auto &sets = frontend.target->reg_sets; for (size_t set_idx = 0; set_idx < sets.size(); ++set_idx) { const auto &set = sets[set_idx]; ImGui::TableNextRow(); ImGui::TableSetColumnIndex(0); ImGui::Text(set.name.c_str()); char buf[20]; std::snprintf(buf, sizeof(buf), "nested_%lu", set_idx); if (ImGui::BeginTable(buf, 2, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) { for (size_t i = 0; i < set.regs.size(); ++i) { const auto ® = set.regs[i]; ImGui::TableNextRow(); ImGui::TableSetColumnIndex(0); ImGui::Text(reg.name.c_str()); ImGui::TableNextColumn(); if (reg.bytes.size() == 0) { ImGui::Text(""); continue; } // TODO: formatting options switch (reg.bytes.size()) { case 1: std::snprintf(buf, sizeof(buf), "%X", reg.bytes[0]); break; case 2: std::snprintf( buf, sizeof(buf), "%X", *reinterpret_cast(reg.bytes.data())); break; case 4: std::snprintf( buf, sizeof(buf), "%X", *reinterpret_cast(reg.bytes.data())); break; case 8: std::snprintf( buf, sizeof(buf), "%lX", *reinterpret_cast(reg.bytes.data())); break; default: std::snprintf(buf, sizeof(buf), ""); break; } ImGui::PushID(set_idx * 1000 + i); ImGui::Text(buf); if (ImGui::IsItemHovered() && ImGui::IsMouseClicked(ImGuiMouseButton_Right)) { ImGui::OpenPopup("Context"); } if (ImGui::BeginPopup("Context")) { if (ImGui::Selectable("Copy")) { ImGui::SetClipboardText(buf); } ImGui::EndPopup(); } ImGui::PopID(); } ImGui::EndTable(); } } ImGui::EndTable(); } if (frontend.target->state == TargetState::running) { ImGui::PopStyleColor(); } ImGui::End(); return false; } bool ThreadWindow::draw(const Frontend &frontend) { //ImGui::SetNextWindowDockID(frontend.dock_id, ImGuiCond_Appearing); if (!ImGui::Begin(this->id.c_str())) { ImGui::End(); return false; } if (!frontend.target) { ImGui::End(); return false; } if (frontend.target->state == TargetState::running) { ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetStyleColorVec4(ImGuiCol_TextDisabled)); } if (ImGui::BeginTable("table", 3, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_ScrollX)) { ImGui::TableSetupColumn("ID"); ImGui::TableSetupColumn("Name"); ImGui::TableSetupColumn("Location"); ImGui::TableHeadersRow(); const auto &threads = frontend.target->threads; char buf[32]; for (size_t idx = 0; idx < threads.size(); ++idx) { const auto &thread = threads[idx]; if (!thread) { continue; } 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(); ImGui::Text(thread->name.c_str()); ImGui::TableNextColumn(); if (thread->cur_display_fn != "") { ImGui::Text(thread->cur_display_fn.c_str()); } else { 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(); } if (frontend.target->state == TargetState::running) { ImGui::PopStyleColor(); } ImGui::End(); return false; } bool FrameWindow::draw(const Frontend &frontend) { //ImGui::SetNextWindowDockID(frontend.dock_id, ImGuiCond_Appearing); if (!ImGui::Begin(this->id.c_str())) { ImGui::End(); return false; } if (!frontend.target) { ImGui::End(); return false; } if (frontend.target->state == TargetState::running) { ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetStyleColorVec4(ImGuiCol_TextDisabled)); } if (ImGui::BeginTable("table", 1, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_ScrollX)) { ImGui::TableSetupColumn("Location"); ImGui::TableHeadersRow(); const auto &frames = frontend.target->frames; char buf[32]; for (size_t idx = 0; idx < frames.size(); ++idx) { const auto &frame = frames[idx]; if (!frame) { continue; } 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()); } else { 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(); } if (frontend.target->state == TargetState::running) { ImGui::PopStyleColor(); } ImGui::End(); return false; } bool DisasmWindow::draw(Frontend &frontend) { if (!ImGui::Begin(this->id.c_str())) { ImGui::End(); return false; } if (!frontend.target) { this->insts.clear(); this->first = true; this->ip_unsuccessful = true; this->disas_unsuccessful = true; ImGui::End(); return false; } if (frontend.target->state == TargetState::stopped || frontend.target->state == TargetState::startup) { this->ip_unsuccessful = true; this->disas_unsuccessful = true; ImGui::End(); return false; } if (frontend.target->state == TargetState::running) { ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetStyleColorVec4(ImGuiCol_TextDisabled)); } 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->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++; using namespace data::source; frontend.target->backend->add_data_node( Node{.id = this->ip_src_id, .type = Node::Type::source, .data = Source{ .type = Source::Type::frame_ip, }}); frontend.target->backend->add_data_node( Node{.id = this->disas_src_id, .type = Node::Type::disassemble, .data = Disassemble{.src_id = this->ip_src_id}}); first = false; } auto pad = ImGui::GetStyle().CellPadding; pad.x += 10.f; if (ImGui::BeginTable("table", 3, ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_ScrollY | ImGuiTableFlags_RowBg | ImGuiTableFlags_PadOuterX)) { ImGui::TableSetupScrollFreeze(0, 1); ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed, 10.f); ImGui::TableSetupColumn("Address"); ImGui::TableSetupColumn("Instruction"); ImGui::TableHeadersRow(); for (size_t idx = 0; idx < insts.size(); ++idx) { const auto &inst = insts[idx]; //printf("Inst at %lx with '%s'\n", inst.addr, inst.fmt_str.c_str()); ImGui::TableNextRow(); ImGui::TableSetColumnIndex(0); ImGui::PushID(idx); // TODO: draw nice break sign auto &bps = frontend.target->breakpoints; const auto bp_it = std::find_if(bps.begin(), bps.end(), [inst](const auto &el) { return el.at_addr(inst.addr); }); 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)); } // 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 idx = 0; for (; idx < bps.size(); ++idx) { if (bps[idx].removed) { break; } } if (idx == bps.size()) { bps.push_back( Target::Breakpoint{.removed = false, .data = inst.addr}); } else { bps[idx] = Target::Breakpoint{.removed = false, .data = inst.addr}; } frontend.target->backend->add_breakpoint(inst.addr, idx); } else { frontend.target->backend->remove_breakpoint(bp_it - bps.begin()); bp_it->removed = true; } } ImGui::PopID(); ImGui::TableNextColumn(); if (!this->ip_unsuccessful && inst.addr == ip) { // TODO: color config const auto col = ImGui::GetColorU32(ImGui::GetStyleColorVec4(ImGuiCol_Button)); ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg0, col); if (this->ip_changed) { ImGui::SetScrollHereY(); } } // TODO: make some unified handler for int vars ImGui::PushID(idx); ImGui::Text("%lX ", inst.addr); if (ImGui::IsItemHovered() && ImGui::IsMouseClicked(ImGuiMouseButton_Right)) { ImGui::OpenPopup("Context"); } if (ImGui::BeginPopup("Context")) { if (ImGui::Selectable("Copy")) { char buf[32]; std::snprintf(buf, sizeof(buf), "%lX", inst.addr); ImGui::SetClipboardText(buf); } ImGui::EndPopup(); } ImGui::PopID(); ImGui::TableNextColumn(); const int mnem_space = max_mnem_len - inst.mnem_len + 1; const int op_space = /*max_op_len - inst.op_len +*/ 1; ImGui::Text(inst.fmt_str.c_str(), mnem_space, ' ', op_space, ' '); } ImGui::EndTable(); } if (frontend.target->state == TargetState::running) { ImGui::PopStyleColor(); } ImGui::End(); ip_changed = false; return false; } bool BreakpointWindow::draw(Frontend &frontend) { if (!ImGui::Begin(this->id.c_str())) { ImGui::End(); return false; } if (!frontend.target) { ImGui::End(); return false; } if (ImGui::BeginTable("table", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_SizingFixedFit)) { ImGui::TableSetupColumn("ID"); ImGui::TableSetupColumn("Location"); ImGui::TableHeadersRow(); auto &bps = frontend.target->breakpoints; for (size_t idx = 0; idx < bps.size(); ++idx) { auto &bp = bps[idx]; if (bp.removed) { continue; } ImGui::TableNextRow(); ImGui::TableSetColumnIndex(0); ImGui::PushID(idx); ImGui::Text("%zu", idx); ImGui::TableNextColumn(); 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(); auto max_pos = ImGui::TableGetCellBgRect(ImGui::GetCurrentTable(), 1).GetBR(); if (ImGui::IsMouseClicked(ImGuiMouseButton_Right) && ImGui::IsMouseHoveringRect(min_pos, max_pos, false)) { ImGui::OpenPopup("Context", ImGuiPopupFlags_NoOpenOverExistingPopup); } if (ImGui::BeginPopup("Context")) { if (ImGui::Selectable("Remove")) { printf("Removing bp %zu\n", idx); frontend.target->backend->remove_breakpoint(idx); bp.removed = true; } // TODO: disable/endable? ImGui::EndPopup(); } ImGui::PopID(); } ImGui::EndTable(); } ImGui::End(); 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++; using namespace data::source; frontend.target->backend->add_data_node( Node{.id = this->ip_src_id, .type = Node::Type::source, .data = Source{ .type = Source::Type::frame_ip, }}); frontend.target->backend->add_data_node( Node{.id = this->line_entry_src_id, .type = Node::Type::line_entry, .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)); ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg0, ImGui::GetColorU32(ImVec4(1.f, 1.f, 1.f, 0.2f))); } // 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; } bool WatchWindow::draw(Frontend &frontend) { if (!ImGui::Begin(this->id.c_str())) { ImGui::End(); return false; } if (!frontend.target) { this->first = true; ImGui::End(); return false; } if (frontend.target->state == TargetState::stopped || frontend.target->state == TargetState::startup) { ImGui::End(); return false; } // TODO: we need to clean up the initializations/sync of the DAG somehow if (first) { this->locals_src_id = frontend.target->data_node_id++; using namespace data::source; frontend.target->backend->add_data_node(Node{.id = this->locals_src_id, .type = Node::Type::locals, .data = std::monostate{}}); first = false; } auto expr_path_vec = std::vector{}; // ImGuiTableFlags_SizingFixedFit if (ImGui::BeginTable("##Variables", 2, ImGuiTableFlags_RowBg | ImGuiTableFlags_Resizable)) { ImGui::TableSetupColumn("Name"); ImGui::TableSetupColumn("Value"); ImGui::TableHeadersRow(); ImGui::TableNextRow(); ImGui::TableSetColumnIndex(0); ImGui::PushID("TTTTTT"); if (ImGui::TreeNodeEx("Locals", ImGuiTreeNodeFlags_SpanFullWidth | ImGuiTreeNodeFlags_DefaultOpen)) { auto locals_idx = frontend.target->data_idx_for_src_id(this->locals_src_id); if (locals_idx) { const auto &local_node = *frontend.target->data_res_nodes[*locals_idx]; const auto &locals_data = local_node.vec_data(); size_t cur_off = 0; size_t cur_idx = 0; while (cur_off < locals_data.size()) { if (cur_off + 2 > locals_data.size()) { break; } auto str_len = *reinterpret_cast(locals_data.data() + cur_off); if (cur_off + 2 + str_len > locals_data.size()) { break; } auto name = std::string_view{ reinterpret_cast(locals_data.data() + cur_off + 2), str_len}; auto node_idx = local_node.children[cur_idx]; expr_path_vec.clear(); this->draw_value(frontend, frontend.target->data_res_nodes[node_idx]->type_id, name, node_idx, 0, expr_path_vec); cur_idx += 1; cur_off += 2 + str_len; } } ImGui::TreePop(); } ImGui::PopID(); for (size_t i = 0; i < extra_slots.size(); ++i) { auto res_idx = frontend.target->data_idx_for_src_id(extra_slots[i].id); auto no_success = true; if (res_idx) { const auto &node = *frontend.target->data_res_nodes[*res_idx]; if (node.success && node.children.size() == 1) { no_success = false; const auto &child_node = *frontend.target->data_res_nodes[node.children[0]]; expr_path_vec.clear(); this->draw_value(frontend, child_node.type_id, &extra_slots[i], node.children[0], 0, expr_path_vec); if (extra_slots[i].bak == "") { frontend.target->backend->remove_data_node(extra_slots[i].id); extra_slots.erase(extra_slots.begin() + i); --i; continue; } } } if (no_success) { auto &slot = extra_slots[i]; ImGui::TableNextRow(); ImGui::TableSetColumnIndex(0); auto is_editing = false; if (slot.is_editing) { is_editing = true; if (slot.edit_was_started) { slot.edit_was_started = false; ImGui::SetKeyboardFocusHere(); } if (ImGui::InputText("##dddd", slot.buf, sizeof(slot.buf), ImGuiInputTextFlags_AutoSelectAll | ImGuiInputTextFlags_EnterReturnsTrue) && ImGui::IsItemDeactivatedAfterEdit()) { if (slot.buf[0] != '\0') { frontend.target->backend->remove_data_node(slot.id); using namespace data::source; frontend.target->backend->add_data_node(Node{ .id = slot.id, .type = Node::Type::source, .data = Source{.type = Source::Type::variable, .data = Source::Variable{.expr_path = slot.buf}}}); } slot.bak = slot.buf; slot.is_editing = false; } if (ImGui::IsItemDeactivated()) { memcpy(slot.buf, slot.bak.data(), slot.bak.size()); slot.buf[slot.bak.size()] = '\0'; slot.is_editing = false; } } if (!is_editing) { char tree_id_buf[128]; std::snprintf(tree_id_buf, sizeof(tree_id_buf), "slot%lu", i); // TODO: better id ImGui::TreeNodeEx(tree_id_buf, ImGuiTreeNodeFlags_SpanFullWidth | ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_NoTreePushOnOpen, "%s", slot.bak.c_str()); if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left) && ImGui::IsItemHovered()) { slot.is_editing = true; slot.edit_was_started = true; } } ImGui::TableNextColumn(); ImGui::TextDisabled(""); } } ImGui::TableNextRow(); ImGui::TableSetColumnIndex(0); if (ImGui::InputText("##EXTRASLOT", this->add_slot_buf, sizeof(this->add_slot_buf), ImGuiInputTextFlags_AutoSelectAll | ImGuiInputTextFlags_EnterReturnsTrue) && ImGui::IsItemDeactivatedAfterEdit()) { if (this->add_slot_buf[0] != '\0') { auto id = frontend.target->data_node_id++; using namespace data::source; frontend.target->backend->add_data_node(Node{ .id = id, .type = Node::Type::source, .data = Source{.type = Source::Type::variable, .data = Source::Variable{.expr_path = this->add_slot_buf}}}); extra_slots.push_back(ExtraSlot{.id = id, .bak = this->add_slot_buf}); memcpy(extra_slots.back().buf, this->add_slot_buf, sizeof(this->add_slot_buf)); this->add_slot_buf[0] = '\0'; } } ImGui::EndTable(); } ImGui::End(); return false; } void WatchWindow::draw_value(Frontend &frontend, data::type_info::TypeID type_id, std::variant name, data::result::NodeIdx node_idx, size_t off, std::vector &expr_path) { ImGui::TableNextRow(); ImGui::TableSetColumnIndex(0); const char *name_begin, *name_end; if (name.index() == 0) { name_begin = std::get(name).begin(); name_end = std::get(name).end(); } else { name_begin = &*std::get(name)->bak.begin(); name_end = &*std::get(name)->bak.end(); } const auto &node_opt = frontend.target->data_res_nodes[node_idx]; if (!node_opt || !node_opt->success) { ImGui::TextUnformatted(name_begin, name_end); ImGui::TableNextColumn(); ImGui::TextDisabled(""); return; } const auto &node = *node_opt; using namespace data::type_info; while (type_id.type == Type::alias) { type_id = std::get(frontend.target->types[type_id.idx].member_types); } auto tree_open = false; auto pop_id = false; if (type_id.type == Type::complex || type_id.type == Type::array) { ImGui::PushID(name_begin, name_end); pop_id = true; char tree_id_buf[128]; std::snprintf(tree_id_buf, sizeof(tree_id_buf), "%u#off%lu", node_idx, off); auto is_editing = false; if (name.index() == 1) { auto *slot = std::get(name); if (slot->is_editing) { is_editing = true; ImGui::BeginDisabled(); tree_open = ImGui::TreeNodeEx(tree_id_buf, 0, ""); ImGui::EndDisabled(); ImGui::SameLine(); if (slot->edit_was_started) { slot->edit_was_started = false; ImGui::SetKeyboardFocusHere(); } if (ImGui::InputText("##dddd", slot->buf, sizeof(slot->buf), ImGuiInputTextFlags_AutoSelectAll | ImGuiInputTextFlags_EnterReturnsTrue) && ImGui::IsItemDeactivatedAfterEdit()) { if (slot->buf[0] != '\0') { frontend.target->backend->remove_data_node(slot->id); using namespace data::source; frontend.target->backend->add_data_node( Node{.id = slot->id, .type = Node::Type::source, .data = Source{.type = Source::Type::variable, .data = Source::Variable{.expr_path = slot->buf}}}); } slot->bak = slot->buf; slot->is_editing = false; } if (ImGui::IsItemDeactivated()) { slot->is_editing = false; } } } if (!is_editing) { tree_open = ImGui::TreeNodeEx( tree_id_buf, ImGuiTreeNodeFlags_SpanFullWidth | ImGuiTreeNodeFlags_SpanAvailWidth, "%.*s", static_cast(name_end - name_begin), name_begin); if (name.index() == 1 && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left) && ImGui::IsItemHovered()) { auto *slot = std::get(name); slot->is_editing = true; slot->edit_was_started = true; } } if (tree_open) { if (type_id.type == Type::complex) { char tree_id_buf[128]; const auto &members = frontend.target->types[type_id.idx].member_vec(); for (size_t i = 0; i < members.size(); ++i) { const auto &member = members[i]; if (member.bitfield_size) { std::snprintf(tree_id_buf, sizeof(tree_id_buf), "%u#member%lu", node_idx, i); ImGui::TableNextRow(); ImGui::TableSetColumnIndex(0); ImGui::TreeNodeEx(tree_id_buf, ImGuiTreeNodeFlags_SpanFullWidth | ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_NoTreePushOnOpen, "%s", member.name.c_str()); ImGui::TableNextColumn(); auto member_type = member.type_id; while (member_type.type == Type::alias) { member_type = std::get( frontend.target->types[member_type.idx].member_types); } // TODO: handle u128/i128 assert(member_type.type != Type::i128 && member_type.type != Type::u128); assert(member_type.is_integral() || member_type.type == Type::_enum); assert(member.bitfield_size <= 8 * 8); auto byte_off = member.offset / 8; auto bit_off = member.offset % 8; auto byte_size = (member.bitfield_size + 7) / 8; uint64_t value = 0; uint8_t arr[8] = {}; const auto &data = node.vec_data(); std::copy(data.begin() + off + byte_off, data.begin() + off + byte_off + byte_size, arr); value = *reinterpret_cast(arr); assert(bit_off + member.bitfield_size <= 64); value = value >> bit_off; value = value & (0xFFFFFFFF'FFFFFFFF >> (64 - member.bitfield_size)); if (member_type.type == Type::_enum) { const auto &members = frontend.target->types[type_id.idx].member_vec(); auto printed = false; for (const auto &member : members) { if (value == member.enum_val) { ImGui::Text("%s", member.name.c_str()); printed = true; break; } } if (!printed) { ImGui::Text("", value); } } else { ImGui::Text("%ld", value); } continue; } expr_path.push_back(ExprPathPart{ .ident = std::string_view{name_begin, static_cast( name_end - name_begin)}, .deref = false}); this->draw_value(frontend, member.type_id, member.name, node_idx, off + member.offset, expr_path); expr_path.pop_back(); } } else { // array auto member_ty_id = std::get(frontend.target->types[type_id.idx].member_types); auto member_size = member_ty_id.byte_size(frontend.target->types); size_t el_count = frontend.target->types[type_id.idx].byte_size / member_size; expr_path.push_back(ExprPathPart{ .ident = std::string_view{name_begin, static_cast(name_end - name_begin)}, .array = true}); char buf[32]; size_t member_off = 0; for (size_t i = 0; i < el_count; ++i) { std::snprintf(buf, sizeof(buf), "[%lu]", i); this->draw_value(frontend, member_ty_id, buf, node_idx, off + member_off, expr_path); member_off += member_size; } expr_path.pop_back(); } ImGui::TreePop(); } ImGui::PopID(); } else { auto is_editing = false; if (name.index() == 1) { auto *slot = std::get(name); if (slot->is_editing) { is_editing = true; if (slot->edit_was_started) { slot->edit_was_started = false; ImGui::SetKeyboardFocusHere(); } if (ImGui::InputText("##dddd", slot->buf, sizeof(slot->buf), ImGuiInputTextFlags_AutoSelectAll | ImGuiInputTextFlags_EnterReturnsTrue) && ImGui::IsItemDeactivatedAfterEdit()) { if (slot->buf[0] != '\0') { frontend.target->backend->remove_data_node(slot->id); using namespace data::source; frontend.target->backend->add_data_node( Node{.id = slot->id, .type = Node::Type::source, .data = Source{.type = Source::Type::variable, .data = Source::Variable{.expr_path = slot->buf}}}); } slot->bak = slot->buf; slot->is_editing = false; } if (ImGui::IsItemDeactivated()) { slot->is_editing = false; } } } auto tree_open = false; if (!is_editing) { char tree_id_buf[128]; std::snprintf(tree_id_buf, sizeof(tree_id_buf), "%u#off%lu", node_idx, off); ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_SpanFullWidth; if (type_id.type != Type::ptr) { flags |= ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_NoTreePushOnOpen; } // TODO: better id tree_open = ImGui::TreeNodeEx(tree_id_buf, flags, "%.*s", static_cast(name_end - name_begin), name_begin); //ImGui::TextUnformatted(name_begin, name_end); if (name.index() == 1 && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left) && ImGui::IsItemHovered()) { auto *slot = std::get(name); slot->is_editing = true; slot->edit_was_started = true; } } ImGui::TableSetColumnIndex(1); switch (type_id.type) { using enum Type; case i8: { auto val = node.get_primitive(off); if (std::isprint(val)) { ImGui::Text("%c", val); } else { ImGui::Text("%d", val); } break; } case u8: ImGui::Text("%lu", node.get_primitive(off)); break; case u16: ImGui::Text("%lu", node.get_primitive(off)); break; case u32: ImGui::Text("%lu", node.get_primitive(off)); break; case u64: ImGui::Text("%lu", node.get_primitive(off)); break; case i16: ImGui::Text("%ld", node.get_primitive(off)); break; case i32: ImGui::Text("%ld", node.get_primitive(off)); break; case i64: ImGui::Text("%ld", node.get_primitive(off)); break; case _bool: { auto val = node.get_primitive(off); if (val) { ImGui::TextUnformatted("true"); } else { ImGui::TextUnformatted("false"); } break; } // TODO case f128: case i128: case u128: { const auto &data = node.vec_data(); const auto lo = node.get_primitive(off); const auto hi = node.get_primitive(off + 8); ImGui::Text("%16lX-%16lX", hi, lo); break; } case f32: { ImGui::Text("%f", node.get_primitive(off)); break; } case f64: { ImGui::Text("%f", node.get_primitive(off)); break; } case ptr: { // TODO: ptr size ImGui::Text("%lX", node.get_primitive(off)); if (tree_open) { // TODO: make this func be able to not print the "outer" line // and just accept a bool to only draw the containing value std::string path{}; if (type_id.sub_type != Type::i8) { // TODO: better check if this is really a string path = "*"; } // why cant i just path name_begin, name_end to string_view? :[ construct_expr_path( path, expr_path, std::string_view{name_begin, static_cast(name_end - name_begin)}); auto src_id = frontend.target->find_or_create_expr_path( std::move(path), type_id.sub_type == Type::i8); auto res_idx = frontend.target->data_idx_for_src_id(src_id); if (res_idx) { const auto &node = *frontend.target->data_res_nodes[*res_idx]; if (type_id.sub_type == Type::i8 && node.success) { // TODO: make span whole row (see https://github.com/ocornut/imgui/issues/3565) assert(node.type_id.type == Type::custom); assert(node.vec_data().back() == '\0'); ImGui::TableNextRow(); ImGui::TableSetColumnIndex(1); ImGui::TextWrapped("\"%s\"", node.vec_data().data()); } else if (node.success && node.children.size() == 1) { auto data_idx = node.children[0]; const auto &data_node = *frontend.target->data_res_nodes[data_idx]; expr_path.push_back(ExprPathPart{ .ident = std::string_view{name_begin, static_cast( name_end - name_begin)}, .deref = true}); this->draw_value(frontend, data_node.type_id, std::string_view{""}, data_idx, 0, expr_path); expr_path.pop_back(); } } ImGui::TreePop(); } break; } case _enum: { // TODO: robustness uint64_t val; const auto byte_size = frontend.target->types[type_id.idx].byte_size; if (byte_size == 8) { val = node.get_primitive(off); } else if (byte_size == 4) { val = node.get_primitive(off); } else if (byte_size == 2) { val = node.get_primitive(off); } else if (byte_size == 1) { val = node.get_primitive(off); } else { assert(0); val = 0; } const auto &members = frontend.target->types[type_id.idx].member_vec(); auto printed = false; for (const auto &member : members) { if (val == member.enum_val) { ImGui::Text("%s", member.name.c_str()); printed = true; break; } } if (!printed) { ImGui::Text("", val); } break; } } } } void WatchWindow::construct_expr_path(std::string &out, const std::vector &path, std::string_view tail) { for (const auto &entry : path) { // FIXME: BIGGEST HACK in cinema history if (entry.ident == "") { continue; } out += entry.ident; if (entry.deref) { out += "->"; } else if (!entry.array) { out += '.'; } } out += tail; } void Window::handle_source_updated(Target &target, size_t id) { switch (this->type) { using enum WindowType; case disassembly: std::get(this->data).handle_source_updated(target, id); break; case source: std::get(this->data).handle_source_updated(target, id); break; default: break; } } void DisasmWindow::handle_source_updated(Target &target, size_t id) { if (id == this->ip_src_id) { auto result = target.data_node_for_src_id(id); if (!result || !result->success || result->type_id.type != data::type_info::Type::u64) { this->ip_unsuccessful = true; return; } this->ip_unsuccessful = false; this->ip_changed = true; this->ip = std::get(result->data); printf("IP changed to %lX\n", this->ip); return; } if (id != this->disas_src_id) { return; } auto result = target.data_node_for_src_id(id); if (!result || !result->success || result->type_id.type != data::type_info::Type::custom) { this->disas_unsuccessful = true; this->insts.clear(); return; } this->disas_unsuccessful = false; this->insts.clear(); // parse insts // struct Inst { // uint64_t addr; // uint8_t mnem_len; // uint8_t op_len; // uint8_t comment_len; // uint8_t inst_len; // char mnem[]; // char op[]; // char comment[]; // }; char buf[256]; size_t idx = 0; uint8_t max_mnem_len = 0; uint8_t max_op_len = 0; const auto &data = result->vec_data(); while (idx < data.size()) { if (data.size() - idx < 12) { break; } uint64_t addr = *reinterpret_cast(&data[idx]); uint8_t mnem_len = *reinterpret_cast(&data[idx + 8]); uint8_t op_len = *reinterpret_cast(&data[idx + 9]); uint8_t comment_len = *reinterpret_cast(&data[idx + 10]); // dc about inst_len rn idx += 12; if (data.size() - idx < mnem_len + op_len + comment_len) { break; } if (comment_len) { std::snprintf(buf, sizeof(buf), "%.*s%%*c%.*s%%.*c ; %.*s", mnem_len, &data[idx], op_len, &data[idx + mnem_len], comment_len, &data[idx + mnem_len + op_len]); } else { std::snprintf(buf, sizeof(buf), "%.*s%%*c%.*s%%.*c", mnem_len, &data[idx], op_len, &data[idx + mnem_len]); } idx += mnem_len + op_len + comment_len; if (mnem_len > max_mnem_len) { max_mnem_len = mnem_len; } if (op_len > max_op_len) { max_op_len = op_len; } insts.push_back(Instruction{.fmt_str = buf, .addr = addr, .mnem_len = mnem_len, .op_len = op_len, .comm_len = comment_len}); } this->max_mnem_len = max_mnem_len; this->max_op_len = max_op_len; } void SourceWindow::handle_source_updated(Target &target, size_t id) { if (id == this->ip_src_id) { // should not need to care return; } if (id != this->line_entry_src_id) { return; } auto result = target.data_node_for_src_id(id); if (!result || !result->success || result->type_id.type != data::type_info::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[]; // }; const auto &data = result->vec_data(); if (data.size() < 8) { this->lines.clear(); this->file_data.clear(); this->file_name.clear(); this->line = 0; return; } const auto line = *reinterpret_cast(data.data()); const auto name_len = *reinterpret_cast(data.data() + 4); if (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(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; } }