1856 lines
47 KiB
C++
1856 lines
47 KiB
C++
#include "window.h"
|
|
#include "frontend.h"
|
|
#include <algorithm>
|
|
#include "imgui_internal.h"
|
|
#include <fstream>
|
|
#include <filesystem>
|
|
|
|
using namespace dbgui;
|
|
using namespace dbgui::frontend;
|
|
|
|
bool Window::draw(Frontend &frontend)
|
|
{
|
|
switch (this->type)
|
|
{
|
|
using enum WindowType;
|
|
|
|
case regs: return std::get<RegWindow>(this->data).draw(frontend); break;
|
|
case threads:
|
|
return std::get<ThreadWindow>(this->data).draw(frontend);
|
|
break;
|
|
case frames: return std::get<FrameWindow>(this->data).draw(frontend); break;
|
|
case disassembly:
|
|
return std::get<DisasmWindow>(this->data).draw(frontend);
|
|
break;
|
|
case breakpoints:
|
|
return std::get<BreakpointWindow>(this->data).draw(frontend);
|
|
break;
|
|
case source:
|
|
return std::get<SourceWindow>(this->data).draw(frontend);
|
|
break;
|
|
case watch: return std::get<WatchWindow>(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("<unk>");
|
|
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<const uint16_t *>(reg.bytes.data()));
|
|
break;
|
|
case 4:
|
|
std::snprintf(
|
|
buf, sizeof(buf), "%X",
|
|
*reinterpret_cast<const uint32_t *>(reg.bytes.data()));
|
|
break;
|
|
case 8:
|
|
std::snprintf(
|
|
buf, sizeof(buf), "%lX",
|
|
*reinterpret_cast<const uint64_t *>(reg.bytes.data()));
|
|
break;
|
|
default: std::snprintf(buf, sizeof(buf), "<val too large>"); 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<uint64_t>(&bp.data))
|
|
{
|
|
ImGui::Text("%lX", *addr);
|
|
} else
|
|
{
|
|
const auto &loc = std::get<Target::Breakpoint::FileLoc>(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<uint32_t>(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<int>(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<ExprPathPart>{};
|
|
|
|
// 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<const uint16_t *>(locals_data.data() + cur_off);
|
|
if (cur_off + 2 + str_len > locals_data.size())
|
|
{
|
|
break;
|
|
}
|
|
|
|
auto name = std::string_view{
|
|
reinterpret_cast<const char *>(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("<not available>");
|
|
}
|
|
}
|
|
|
|
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<std::string_view, ExtraSlot *> name,
|
|
data::result::NodeIdx node_idx, size_t off,
|
|
std::vector<ExprPathPart> &expr_path)
|
|
{
|
|
ImGui::TableNextRow();
|
|
ImGui::TableSetColumnIndex(0);
|
|
const char *name_begin, *name_end;
|
|
if (name.index() == 0)
|
|
{
|
|
name_begin = std::get<std::string_view>(name).begin();
|
|
name_end = std::get<std::string_view>(name).end();
|
|
} else
|
|
{
|
|
name_begin = &*std::get<ExtraSlot *>(name)->bak.begin();
|
|
name_end = &*std::get<ExtraSlot *>(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("<unavailable>");
|
|
return;
|
|
}
|
|
|
|
const auto &node = *node_opt;
|
|
using namespace data::type_info;
|
|
while (type_id.type == Type::alias)
|
|
{
|
|
type_id =
|
|
std::get<TypeID>(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<ExtraSlot *>(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<int>(name_end - name_begin), name_begin);
|
|
if (name.index() == 1
|
|
&& ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)
|
|
&& ImGui::IsItemHovered())
|
|
{
|
|
auto *slot = std::get<ExtraSlot *>(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<TypeID>(
|
|
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<uint64_t *>(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("<invalid: %lu>", value);
|
|
}
|
|
} else
|
|
{
|
|
ImGui::Text("%ld", value);
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
expr_path.push_back(ExprPathPart{
|
|
.ident = std::string_view{name_begin, static_cast<size_t>(
|
|
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<TypeID>(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<size_t>(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<ExtraSlot *>(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<int>(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<ExtraSlot *>(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<int8_t>(off);
|
|
if (std::isprint(val))
|
|
{
|
|
ImGui::Text("%c", val);
|
|
} else
|
|
{
|
|
ImGui::Text("%d", val);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case u8: ImGui::Text("%lu", node.get_primitive<uint8_t>(off)); break;
|
|
case u16: ImGui::Text("%lu", node.get_primitive<uint16_t>(off)); break;
|
|
case u32: ImGui::Text("%lu", node.get_primitive<uint32_t>(off)); break;
|
|
case u64: ImGui::Text("%lu", node.get_primitive<uint64_t>(off)); break;
|
|
case i16: ImGui::Text("%ld", node.get_primitive<int16_t>(off)); break;
|
|
case i32: ImGui::Text("%ld", node.get_primitive<int32_t>(off)); break;
|
|
case i64: ImGui::Text("%ld", node.get_primitive<int64_t>(off)); break;
|
|
case _bool:
|
|
{
|
|
auto val = node.get_primitive<uint8_t>(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<uint64_t>(off);
|
|
const auto hi = node.get_primitive<uint64_t>(off + 8);
|
|
ImGui::Text("%16lX-%16lX", hi, lo);
|
|
break;
|
|
}
|
|
case f32:
|
|
{
|
|
ImGui::Text("%f", node.get_primitive<float>(off));
|
|
break;
|
|
}
|
|
case f64:
|
|
{
|
|
ImGui::Text("%f", node.get_primitive<double>(off));
|
|
break;
|
|
}
|
|
case ptr:
|
|
{
|
|
// TODO: ptr size
|
|
ImGui::Text("%lX", node.get_primitive<uint64_t>(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<size_t>(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<size_t>(
|
|
name_end - name_begin)},
|
|
.deref = true});
|
|
|
|
this->draw_value(frontend, data_node.type_id,
|
|
std::string_view{"<pointee>"}, 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<uint64_t>(off);
|
|
} else if (byte_size == 4)
|
|
{
|
|
val = node.get_primitive<uint32_t>(off);
|
|
} else if (byte_size == 2)
|
|
{
|
|
val = node.get_primitive<uint16_t>(off);
|
|
} else if (byte_size == 1)
|
|
{
|
|
val = node.get_primitive<uint8_t>(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("<invalid: %lu>", val);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void WatchWindow::construct_expr_path(std::string &out,
|
|
const std::vector<ExprPathPart> &path,
|
|
std::string_view tail)
|
|
{
|
|
for (const auto &entry : path)
|
|
{
|
|
// FIXME: BIGGEST HACK in cinema history
|
|
if (entry.ident == "<pointee>")
|
|
{
|
|
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<DisasmWindow>(this->data).handle_source_updated(target, id);
|
|
break;
|
|
case source:
|
|
std::get<SourceWindow>(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<uint64_t>(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<const uint64_t *>(&data[idx]);
|
|
uint8_t mnem_len = *reinterpret_cast<const uint8_t *>(&data[idx + 8]);
|
|
uint8_t op_len = *reinterpret_cast<const uint8_t *>(&data[idx + 9]);
|
|
uint8_t comment_len = *reinterpret_cast<const uint8_t *>(&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<const uint32_t *>(data.data());
|
|
const auto name_len = *reinterpret_cast<const uint32_t *>(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<const char *>(data.data() + 8), name_len};
|
|
|
|
printf("New LE: %.*s:%u", static_cast<int>(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;
|
|
}
|
|
} |