start work on new watch syntax

This commit is contained in:
T0b1
2024-05-12 03:23:51 +02:00
parent cdf015302a
commit aa80d690d9
5 changed files with 978 additions and 735 deletions

View File

@@ -33,10 +33,14 @@ set(DBGUI_HEADERS
src/frontend/frontend.h
src/frontend/target.h
src/frontend/window.h
src/frontend/window/watch_window.h
src/msg.h)
set(DBGUI_SOURCES
src/main.cpp src/frontend/frontend.cpp src/frontend/window.cpp
src/main.cpp src/frontend/frontend.cpp
src/frontend/window.cpp
src/frontend/window/watch_window.cpp
src/backend/backend.cpp
src/backend/lldb/lldb_backend.cpp
src/data.cpp

View File

@@ -982,700 +982,6 @@ bool SourceWindow::draw(Frontend &frontend)
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,
.address_of_or_pointer = false}}});
}
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++;
const auto *expr_path_buf = this->add_slot_buf;
bool address_of_or_pointer = false;
if (expr_path_buf[0] == '+')
{
address_of_or_pointer = true;
++expr_path_buf;
}
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 = expr_path_buf,
.address_of_or_pointer =
address_of_or_pointer}}});
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);
auto expr_path_size = expr_path.size();
expr_path.back().array_idx = i;
this->draw_value(frontend, member_ty_id, buf, node_idx,
off + member_off, expr_path);
assert(expr_path.size() == expr_path_size);
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)

View File

@@ -7,6 +7,8 @@
#include "msg.h"
#include "frontend/target.h"
#include "window/watch_window.h"
namespace dbgui::frontend
{
struct Frontend;
@@ -124,46 +126,6 @@ namespace dbgui::frontend
std::vector<std::string_view> lines;
};
struct WatchWindow
{
struct ExtraSlot
{
size_t id;
char buf[256];
std::string bak;
bool is_editing = false;
bool edit_was_started = false;
};
// TODO: cache the expr_path by node_idx + off or smth?
struct ExprPathPart
{
std::string_view ident;
bool deref = false;
bool array = false;
uint64_t array_idx = 0;
};
bool draw(Frontend &);
void draw_value(Frontend &, data::type_info::TypeID,
std::variant<std::string_view, ExtraSlot *> name,
data::result::NodeIdx node_idx, size_t off,
std::vector<ExprPathPart> &expr_path);
// void handle_source_updated(Target& target, size_t id);
void construct_expr_path(std::string &out,
const std::vector<ExprPathPart> &,
std::string_view tail);
std::string id;
bool open;
bool first;
size_t locals_src_id;
std::vector<ExtraSlot> extra_slots;
char add_slot_buf[256] = {};
};
struct Window
{
WindowType type;

View File

@@ -0,0 +1,896 @@
#include "watch_window.h"
#include "frontend/frontend.h"
#include <algorithm>
#include "imgui_internal.h"
#include <fstream>
#include <filesystem>
using namespace dbgui;
using namespace dbgui::frontend;
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);
create_node_for_extra_slot(frontend, i);
}
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++;
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';
create_node_for_extra_slot(frontend, extra_slots.size() - 1);
}
}
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);
const auto slot_idx = std::distance(extra_slots.data(), slot);
create_node_for_extra_slot(frontend, slot_idx);
}
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);
auto expr_path_size = expr_path.size();
expr_path.back().array_idx = i;
this->draw_value(frontend, member_ty_id, buf, node_idx,
off + member_off, expr_path);
assert(expr_path.size() == expr_path_size);
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);
const auto slot_idx = std::distance(extra_slots.data(), slot);
create_node_for_extra_slot(frontend, slot_idx);
}
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;
}
namespace
{
enum class WatchToken : uint32_t
{
comma,
dot,
bracket_open,
bracket_close,
circumflex,
dollar,
hashtag,
percent,
literal_bit = 0x10000000,
};
struct ParseState
{
Frontend *frontend;
std::string_view buf;
std::vector<char> literal_buf;
std::string path_buf{};
std::vector<data::source::Node> nodes{};
};
bool token_is_literal(const WatchToken token)
{
return (static_cast<uint32_t>(token)
& static_cast<uint32_t>(WatchToken::literal_bit))
== static_cast<uint32_t>(WatchToken::literal_bit);
}
std::string_view token_literal(const ParseState &state,
const WatchToken token)
{
assert(token_is_literal(token));
const auto idx = static_cast<uint32_t>(token)
& ~static_cast<uint32_t>(WatchToken::literal_bit);
return {state.literal_buf.data() + idx};
}
std::string_view token_to_str(const ParseState &state, WatchToken token)
{
switch (token)
{
case WatchToken::comma: return ",";
case WatchToken::dot: return ".";
case WatchToken::bracket_open: return "[";
case WatchToken::bracket_close: return "]";
case WatchToken::circumflex: return "^";
case WatchToken::dollar: return "$";
case WatchToken::hashtag: return "#";
case WatchToken::percent: return "%";
default:
{
if (token_is_literal(token))
{
const auto idx = static_cast<uint32_t>(token)
& ~static_cast<uint32_t>(WatchToken::literal_bit);
return {state.literal_buf.data() + idx};
}
return "<unknown>";
}
}
}
std::optional<WatchToken> next_token(ParseState &state)
{
if (state.buf.empty())
{
spdlog::trace("Token buffer empty");
return {};
}
// TODO: utf8?
auto c = state.buf[0];
assert(c < 128);
state.buf.remove_prefix(1);
switch (c)
{
case ',': return WatchToken::comma;
case '.': return WatchToken::dot;
case '[': return WatchToken::bracket_open;
case ']': return WatchToken::bracket_close;
case '^': return WatchToken::circumflex;
case '$': return WatchToken::dollar;
case '#': return WatchToken::hashtag;
case '%': return WatchToken::percent;
default:
{
if (!std::isalnum(c))
{
// TODO: error handling
spdlog::trace("Encountered non-alphanumeric token");
return {};
}
const auto literal_end =
std::find_if(state.buf.begin(), state.buf.end(),
[](auto c) { return !std::isalnum(c); });
const auto idx = state.literal_buf.size();
assert(idx < (1ull << 31));
state.literal_buf.push_back(c);
state.literal_buf.insert(state.literal_buf.end(), state.buf.begin(),
literal_end);
state.literal_buf.push_back('\0');
state.buf.remove_prefix(literal_end - state.buf.begin() + 1);
return WatchToken{static_cast<uint32_t>(WatchToken::literal_bit)
| static_cast<uint32_t>(idx)};
}
}
}
std::optional<size_t> parse_data_desc(ParseState &state)
{
const auto token_opt = next_token(state);
if (!token_opt)
{
spdlog::trace("Failed to parse data_desc: out of token");
return {};
}
const auto token = *token_opt;
spdlog::trace("Parsing data_desc: Token {}", token_to_str(state, token));
// check for register
if (token == WatchToken::dollar)
{
spdlog::trace("Parsing register");
const auto token_opt = next_token(state);
if (!token_opt)
{
spdlog::trace("Failed to parse register: out of token");
return {};
}
const auto token = *token_opt;
if (!token_is_literal(token))
{
spdlog::trace("Failed to parse register: encountered non-literal {}",
token_to_str(state, token));
return {};
}
const auto reg_name = token_literal(state, token);
assert(state.frontend->target);
const auto &reg_sets = state.frontend->target->reg_sets;
uint16_t set_idx = 0, reg_idx = 0;
auto found = false;
for (; set_idx < reg_sets.size(); ++set_idx)
{
const auto &set = reg_sets[set_idx];
for (reg_idx = 0; reg_idx < set.regs.size(); ++reg_idx)
{
if (set.regs[reg_idx].name == reg_name)
{
found = true;
break;
}
}
if (found)
{
break;
}
}
if (!found)
{
spdlog::trace("Failed to parse register: invalid register name '{}'",
reg_name);
return {};
}
auto node_id = state.frontend->target->data_node_id++;
state.nodes.push_back(data::source::Node{
.id = node_id,
.type = data::source::Node::Type::source,
.data =
data::source::Source{
.type = data::source::Source::Type::reg,
.data =
data::source::Source::Reg{
.set = set_idx,
.idx = reg_idx,
},
},
});
spdlog::trace("Created register node with id {}", node_id);
return node_id;
}
}
} // namespace
void WatchWindow::create_node_for_extra_slot(Frontend &frontend,
const size_t slot_idx)
{
const auto &slot = extra_slots[slot_idx];
auto parse_state = ParseState{
.frontend = &frontend,
.buf = slot.buf,
.literal_buf = {},
};
assert(0);
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,
.address_of_or_pointer = false}}});
}

View File

@@ -0,0 +1,75 @@
#pragma once
#include <cstdint>
#include <vector>
#include <string>
#include <variant>
#include "msg.h"
namespace dbgui::frontend
{
struct Frontend;
struct WatchWindow
{
enum class VarFormatTy : uint8_t
{
ty_builtin,
i8,
i16,
i32,
i64,
i128,
u8,
u16,
u32,
u64,
u128,
f32,
f128,
str,
cstr,
};
struct ExtraSlot
{
size_t id;
char buf[256];
std::string bak;
bool is_editing = false;
bool edit_was_started = false;
VarFormatTy fmt_ty = VarFormatTy::ty_builtin;
uint32_t fmt_matrix_x = 0, fmt_matrix_y = 0;
};
// TODO: cache the expr_path by node_idx + off or smth?
struct ExprPathPart
{
std::string_view ident;
bool deref = false;
bool array = false;
uint64_t array_idx = 0;
};
bool draw(Frontend &);
void draw_value(Frontend &, data::type_info::TypeID,
std::variant<std::string_view, ExtraSlot *> name,
data::result::NodeIdx node_idx, size_t off,
std::vector<ExprPathPart> &expr_path);
// void handle_source_updated(Target& target, size_t id);
void construct_expr_path(std::string &out,
const std::vector<ExprPathPart> &,
std::string_view tail);
void create_node_for_extra_slot(Frontend &frontend, size_t slot_idx);
std::string id;
bool open;
bool first;
size_t locals_src_id;
std::vector<ExtraSlot> extra_slots;
char add_slot_buf[256] = {};
};
} // namespace dbgui::frontend