Compare commits

...

9 Commits

Author SHA1 Message Date
T0b1
e63179e14f add basic array sizing with constant size, tho the parsing is probably not correct
this should just be a normal array index
also missing handling to correctly construct the expr path when accessing pointers
(maybe just make this node based in the future?)
2024-05-13 21:17:18 +02:00
T0b1
70871a5671 adjust gitignore 2024-05-13 18:29:28 +02:00
T0b1
e1be42052d fix bug with initial state change 2024-05-13 18:28:14 +02:00
T0b1
894ff6a408 allow nested data desc with dots and create the nodes 2024-05-13 03:46:04 +02:00
T0b1
aa80d690d9 start work on new watch syntax 2024-05-12 03:23:51 +02:00
T0b1
cdf015302a dump 2024-05-12 01:25:11 +02:00
T0b1
eb98d911f6 add way to get the address of variable 2024-05-06 04:10:13 +02:00
T0b1
53899ef3c3 fix debugger not stopping at main 2024-05-06 04:09:10 +02:00
T0b1
843ca4dd5f make TypeInfo smaller 2024-05-04 02:06:03 +02:00
13 changed files with 1585 additions and 839 deletions

3
.gitignore vendored
View File

@@ -1,4 +1,5 @@
build/ build*
.idea/
.vscode/ .vscode/
.cache/ .cache/

View File

@@ -33,10 +33,14 @@ set(DBGUI_HEADERS
src/frontend/frontend.h src/frontend/frontend.h
src/frontend/target.h src/frontend/target.h
src/frontend/window.h src/frontend/window.h
src/frontend/window/watch_window.h
src/msg.h) src/msg.h)
set(DBGUI_SOURCES 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/backend.cpp
src/backend/lldb/lldb_backend.cpp src/backend/lldb/lldb_backend.cpp
src/data.cpp src/data.cpp

View File

@@ -141,6 +141,16 @@ void LLDBBackend::handle_state_change(lldb::StateType state)
using namespace lldb; using namespace lldb;
if (_first_run) if (_first_run)
{ {
spdlog::trace("Got initial state change: {}", static_cast<uint32_t>(state));
if (state != lldb::StateType::eStateStopped)
{
// TODO: handle other stopped states, e.g. crashed
spdlog::trace("Not handling initial state {}",
static_cast<uint32_t>(state));
return;
}
_first_run = false; _first_run = false;
// TODO: do initialization // TODO: do initialization
// TODO: we should only do this when the process was stopped, no? // TODO: we should only do this when the process was stopped, no?
@@ -162,7 +172,8 @@ void LLDBBackend::handle_state_change(lldb::StateType state)
if (!_stop_at_entry) if (!_stop_at_entry)
{ {
_target.BreakpointDelete(_hidden_main_bp); // TODO: if we delete this, lldb continues the process again (?)
//_target.BreakpointDelete(_hidden_main_bp);
} }
if (state == StateType::eStateStopped) if (state == StateType::eStateStopped)
@@ -190,7 +201,11 @@ void LLDBBackend::handle_state_change(lldb::StateType state)
auto sel_thread = _process->GetSelectedThread(); auto sel_thread = _process->GetSelectedThread();
auto change_reason = StateChangeReason::unknown; auto change_reason = StateChangeReason::unknown;
auto extra = 0; auto extra = 0;
switch (sel_thread.GetStopReason()) auto stop_reason = sel_thread.GetStopReason();
// step over seems to cause the reason to be eStopReasonPlanComplete, when pausing the stop reason seems to be eStopReasonSignal
spdlog::trace("Thread stopped because of reason {}",
static_cast<int>(stop_reason));
switch (stop_reason)
{ {
case eStopReasonBreakpoint: case eStopReasonBreakpoint:
{ {
@@ -278,21 +293,21 @@ void LLDBBackend::prepare_proc_info(
for (size_t i = 0; i < len; ++i) { for (size_t i = 0; i < len; ++i) {
auto reg_or_set = regs.GetValueAtIndex(i); auto reg_or_set = regs.GetValueAtIndex(i);
if (reg_or_set.GetValueType() == eValueTypeRegister) { if (reg_or_set.GetValueType() == eValueTypeRegister) {
printf("Got register %s (%s) with value: %lX\n", reg_or_set.GetName(), reg_or_set.GetTypeName(), reg_or_set.GetValueAsUnsigned(0)); spdlog::trace("Got register {} ({}) with value: {:X}", reg_or_set.GetName(), reg_or_set.GetTypeName(), reg_or_set.GetValueAsUnsigned(0));
info.reg_names.emplace_back(reg_or_set.GetName()); //info.reg_names.emplace_back(reg_or_set.GetName());
continue; continue;
} }
printf("Got register set %s\n", reg_or_set.GetName()); spdlog::trace("Got register set {}\n", reg_or_set.GetName());
const auto is_gp_set = std::string_view{reg_or_set.GetName()} == "General Purpose Registers"; const auto is_gp_set = std::string_view{reg_or_set.GetName()} == "General Purpose Registers";
for (uint32_t child_idx = 0; child_idx < reg_or_set.GetNumChildren(); ++child_idx) { for (uint32_t child_idx = 0; child_idx < reg_or_set.GetNumChildren(); ++child_idx) {
auto reg = reg_or_set.GetChildAtIndex(child_idx); auto reg = reg_or_set.GetChildAtIndex(child_idx);
printf("Got register %s (%s) with value: %lX\n", reg.GetName(), reg.GetTypeName(), reg.GetValueAsUnsigned(0)); spdlog::trace("Got register {} ({}) with value: {:X}", reg.GetName(), reg.GetTypeName(), reg.GetValueAsUnsigned(0));
if (is_gp_set && info.arch == Arch::x86_64 && reg.GetByteSize() < 8) { if (is_gp_set && info.arch == Arch::x86_64 && reg.GetByteSize() < 8) {
// skip non-full regs here // skip non-full regs here
printf(" -> Skipped\n"); spdlog::trace(" -> Skipped");
continue; continue;
} }
info.reg_names.emplace_back(reg.GetName()); //info.reg_names.emplace_back(reg.GetName());
} }
}*/ }*/
@@ -635,8 +650,8 @@ dbgui::data::type_info::TypeID
auto base_id = parse_type_inner(out_vec, base_type); auto base_id = parse_type_inner(out_vec, base_type);
out_vec.push_back(type_info::TypeInfo{ out_vec.push_back(type_info::TypeInfo{
.type = type_info::Type::_enum, .type = type_info::Type::_enum,
.byte_size = type_copy.GetByteSize(),
.enum_base = base_id, .enum_base = base_id,
.byte_size = type_copy.GetByteSize(),
.display_name = std::string{display_name}, .display_name = std::string{display_name},
.internal_name = std::string{internal_name}, .internal_name = std::string{internal_name},
.member_types = std::vector<type_info::MemberInfo>{}}); .member_types = std::vector<type_info::MemberInfo>{}});
@@ -1517,6 +1532,29 @@ void LLDBBackend::add_data_node(const data::source::Node &node)
_data_dag.add_edge(node.id, src_id); _data_dag.add_edge(node.id, src_id);
break; break;
} }
case read_array:
{
const auto &arr_node = std::get<data::source::ReadAsArray>(node.data);
auto src_id = arr_node.addr_src_id;
if (!_data_dag.nodes.contains(src_id))
{
printf("Invalid add sequence\n");
exit(1);
}
_data_dag.add_edge(node.id, src_id);
if (!arr_node.is_const_size)
{
auto size_id = arr_node.size_src_id;
if (!_data_dag.nodes.contains(size_id))
{
printf("Invalid add sequence\n");
exit(1);
}
_data_dag.add_edge(node.id, size_id);
}
break;
}
} }
_dag_linear_valid = false; _dag_linear_valid = false;
@@ -1668,6 +1706,7 @@ std::optional<std::pair<uint16_t, size_t>>
} }
} }
// TODO: reserve the slot
auto idx = this->_data_res.size(); auto idx = this->_data_res.size();
this->_data_res.resize(idx + 1); this->_data_res.resize(idx + 1);
return idx; return idx;
@@ -1823,14 +1862,16 @@ std::optional<std::pair<uint16_t, size_t>>
return check_single_res_changed(std::move(res_node)); return check_single_res_changed(std::move(res_node));
} }
auto res_idx = this->build_nodes_for_var(var, data_res); auto res_idx = this->build_nodes_for_var(var, data_res,
info.address_of_or_pointer);
if (!res_idx) if (!res_idx)
{ {
return check_single_res_changed(std::move(res_node)); return check_single_res_changed(std::move(res_node));
} }
printf("Got res for %s: %u (%u)\n", info.expr_path.c_str(), *res_idx, spdlog::trace(
_data_res[*res_idx]->node.type_id.type); "Got res for {}: {} ({})", info.expr_path, *res_idx,
static_cast<uint8_t>(_data_res[*res_idx]->node.type_id.type));
res_node.success = true; res_node.success = true;
res_node.children.push_back(*res_idx); res_node.children.push_back(*res_idx);
return check_single_res_changed(std::move(res_node)); return check_single_res_changed(std::move(res_node));
@@ -2132,7 +2173,7 @@ std::optional<std::pair<uint16_t, size_t>>
} }
printf("Got local %s\n", name); printf("Got local %s\n", name);
auto res_idx = this->build_nodes_for_var(var, data_res); auto res_idx = this->build_nodes_for_var(var, data_res, false);
if (!res_idx) if (!res_idx)
{ {
printf("No result\n"); printf("No result\n");
@@ -2277,6 +2318,121 @@ std::optional<std::pair<uint16_t, size_t>>
.success = true, .success = true,
.data = std::move(buf)}); .data = std::move(buf)});
} }
case read_array:
{
using namespace lldb;
uint16_t cache_idx = 0;
if (auto found_idx = find_node_for_src_id(node.id); found_idx)
{
cache_idx = *found_idx;
} else
{
cache_idx = get_free_res_slot();
// reserve the slot
_data_res[cache_idx] = CachedDataRes{.src_id = node.id};
}
const auto &arr_node = std::get<data::source::ReadAsArray>(node.data);
auto res_node = data::result::Node{
.idx = cache_idx,
.type_id = data::type_info::TypeID::none(),
.success = false,
};
size_t addr_id = arr_node.addr_src_id;
uint16_t addr_parent_idx = *find_node_for_src_id(addr_id);
if (!_data_res[addr_parent_idx]
|| !_data_res[addr_parent_idx]->node.success
|| _data_res[addr_parent_idx]->node.children.size() != 1)
{
return check_single_res_changed(std::move(res_node));
}
uint16_t addr_idx = _data_res[addr_parent_idx]->node.children[0];
if (!_data_res[addr_idx] || !_data_res[addr_idx]->node.success
|| _data_res[addr_idx]->node.type_id.type
!= data::type_info::Type::ptr)
{
return check_single_res_changed(std::move(res_node));
}
if (!arr_node.is_const_size)
{
spdlog::trace("read_as_array: non-const size not supported");
return check_single_res_changed(std::move(res_node));
}
const auto addr = _data_res[addr_idx]->node.get_primitive<uint64_t>(0);
spdlog::trace("read_as_array: addr: {:X}", addr);
const auto pointee_type_idx = _data_res[addr_idx]->node.type_id.idx;
if (!_types[pointee_type_idx])
{
spdlog::trace("Found no pointee type for read_as_array");
return check_single_res_changed(std::move(res_node));
}
auto pointee_type = _types[pointee_type_idx]->first;
if (pointee_type.IsArrayType())
{
// TODO: should we do this until there is no array left?
pointee_type = pointee_type.GetArrayElementType();
}
spdlog::trace("read_as_array: pointee_type valid?: {}",
pointee_type.IsValid());
std::string type_buf;
format_type(pointee_type, type_buf);
spdlog::trace("read_as_array: pointee_type: {}", type_buf);
auto arr_type = pointee_type.GetArrayType(arr_node.size_const);
spdlog::trace("read_as_array: arr_type valid?: {}", arr_type.IsValid());
type_buf.clear();
format_type(arr_type, type_buf);
spdlog::trace("read_as_array: arr_type: {}", type_buf);
char buf[128], buf2[32];
/*std::snprintf(buf2, sizeof(buf2), "%zX", node.id);
std::snprintf(buf, sizeof(buf), "*((%s *)0x%zX)", arr_type.GetName(),
addr);
auto arr_val = _target.CreateValueFromExpression(buf2, buf);
if (!arr_val.IsValid())
{
spdlog::trace("read_as_array: arr_val not valid");
return check_single_res_changed(std::move(res_node));
}*/
std::snprintf(buf, sizeof(buf), "(char*)0x%zX", addr);
auto tmp_val = _target.CreateValueFromExpression("arr_tmp", buf);
if (!tmp_val.IsValid())
{
spdlog::trace("read_as_array: tmp_val not valid");
return check_single_res_changed(std::move(res_node));
}
std::snprintf(buf, sizeof(buf), "%zX", node.id);
auto arr_val = tmp_val.CreateValueFromAddress(buf, addr, arr_type);
if (!arr_val.IsValid())
{
spdlog::trace("read_as_array: arr_val not valid");
return check_single_res_changed(std::move(res_node));
}
//arr_val = arr_val.Cast(arr_type);
auto res_idx = this->build_nodes_for_var(arr_val, data_res, false);
if (!res_idx)
{
return check_single_res_changed(std::move(res_node));
}
spdlog::trace(
"Got res for {}: {} ({})", node.id, *res_idx,
static_cast<uint8_t>(_data_res[*res_idx]->node.type_id.type));
res_node.success = true;
res_node.children.push_back(*res_idx);
return check_single_res_changed(std::move(res_node));
}
} }
printf("Unhandled data type\n"); printf("Unhandled data type\n");
@@ -2652,8 +2808,8 @@ dbgui::data::type_info::TypeID
auto base_id = this->build_type_from_lldb_type(base_type); auto base_id = this->build_type_from_lldb_type(base_type);
_types.emplace_back(std::make_pair( _types.emplace_back(std::make_pair(
lldb_type, TypeInfo{.type = Type::_enum, lldb_type, TypeInfo{.type = Type::_enum,
.byte_size = lldb_type.GetByteSize(),
.enum_base = base_id, .enum_base = base_id,
.byte_size = lldb_type.GetByteSize(),
.display_name = std::string{display_name}, .display_name = std::string{display_name},
.internal_name = std::string{internal_name}, .internal_name = std::string{internal_name},
.member_types = std::vector<MemberInfo>{}})); .member_types = std::vector<MemberInfo>{}}));
@@ -2957,7 +3113,8 @@ bool LLDBBackend::is_type_equal(data::type_info::TypeID type_id,
std::optional<dbgui::data::result::NodeIdx> std::optional<dbgui::data::result::NodeIdx>
LLDBBackend::build_nodes_for_var(lldb::SBValue &val, LLDBBackend::build_nodes_for_var(lldb::SBValue &val,
std::vector<data::result::Node> &to_send) std::vector<data::result::Node> &to_send,
bool address_of_or_pointer)
{ {
using namespace lldb; using namespace lldb;
using namespace data; using namespace data;
@@ -2982,7 +3139,8 @@ std::optional<dbgui::data::result::NodeIdx>
for (auto i = 0; i < _var_cache.size(); ++i) for (auto i = 0; i < _var_cache.size(); ++i)
{ {
auto &entry = _var_cache[i]; auto &entry = _var_cache[i];
if (!entry.global_or_static) if (!entry.global_or_static
|| entry.address_of_or_pointer != address_of_or_pointer)
{ {
continue; continue;
} }
@@ -3011,7 +3169,8 @@ std::optional<dbgui::data::result::NodeIdx>
for (auto i = 0; i < _var_cache.size(); ++i) for (auto i = 0; i < _var_cache.size(); ++i)
{ {
auto &entry = _var_cache[i]; auto &entry = _var_cache[i];
if (entry.global_or_static) if (entry.global_or_static
|| entry.address_of_or_pointer != address_of_or_pointer)
{ {
continue; continue;
} }
@@ -3083,12 +3242,14 @@ std::optional<dbgui::data::result::NodeIdx>
} }
auto node_idx = *_var_cache[*cache_entry].cached_node; auto node_idx = *_var_cache[*cache_entry].cached_node;
_var_cache[*cache_entry].used = true; _var_cache[*cache_entry].used = true;
this->build_nodes_for_var_cached(val, node_idx, to_send); this->build_nodes_for_var_cached(val, node_idx, to_send,
address_of_or_pointer);
return node_idx; return node_idx;
} }
auto idx = this->build_nodes_for_var_uncached(val, to_send); auto idx =
this->build_nodes_for_var_uncached(val, to_send, address_of_or_pointer);
_var_cache.push_back( _var_cache.push_back(
VarCacheEntry{.lldb_val = val, VarCacheEntry{.lldb_val = val,
.sym_ctx = val.GetFrame().GetSymbolContext(true), .sym_ctx = val.GetFrame().GetSymbolContext(true),
@@ -3100,7 +3261,8 @@ std::optional<dbgui::data::result::NodeIdx>
} }
dbgui::data::result::NodeIdx LLDBBackend::build_nodes_for_var_uncached( dbgui::data::result::NodeIdx LLDBBackend::build_nodes_for_var_uncached(
lldb::SBValue &val, std::vector<data::result::Node> &to_send) lldb::SBValue &val, std::vector<data::result::Node> &to_send,
bool address_of_or_pointer)
{ {
auto lldb_type = val.GetType(); auto lldb_type = val.GetType();
auto type_id = this->build_type_from_lldb_type(lldb_type); auto type_id = this->build_type_from_lldb_type(lldb_type);
@@ -3130,6 +3292,34 @@ dbgui::data::result::NodeIdx LLDBBackend::build_nodes_for_var_uncached(
// TODO: error handling // TODO: error handling
auto err = lldb::SBError{}; auto err = lldb::SBError{};
if (address_of_or_pointer)
{
if (no_alias_type.type == data::type_info::Type::ptr)
{
res_node.data = static_cast<uint64_t>(var_data.GetAddress(err, 0));
} else
{
// so we don't need to create a new type for the pointer i think so just replace the node type
res_node.type_id =
data::type_info::TypeID{.type = data::type_info::Type::ptr,
.sub_type = type_id.type,
.idx = type_id.idx};
auto addr_of = val.AddressOf();
auto data = addr_of.GetData();
if (!data.IsValid())
{
// copypasted from above
to_send.push_back(res_node);
_data_res[res_idx] =
CachedDataRes{.node = std::move(res_node),
.no_delete_on_src_delete = true,
.src_id = std::numeric_limits<size_t>::max()};
return res_idx;
}
res_node.data = static_cast<uint64_t>(data.GetAddress(err, 0));
}
} else
{
switch (no_alias_type.type) switch (no_alias_type.type)
{ {
using enum data::type_info::Type; using enum data::type_info::Type;
@@ -3139,13 +3329,16 @@ dbgui::data::result::NodeIdx LLDBBackend::build_nodes_for_var_uncached(
res_node.data = static_cast<uint64_t>(var_data.GetUnsignedInt8(err, 0)); res_node.data = static_cast<uint64_t>(var_data.GetUnsignedInt8(err, 0));
break; break;
case u16: case u16:
res_node.data = static_cast<uint64_t>(var_data.GetUnsignedInt16(err, 0)); res_node.data =
static_cast<uint64_t>(var_data.GetUnsignedInt16(err, 0));
break; break;
case u32: case u32:
res_node.data = static_cast<uint64_t>(var_data.GetUnsignedInt32(err, 0)); res_node.data =
static_cast<uint64_t>(var_data.GetUnsignedInt32(err, 0));
break; break;
case u64: case u64:
res_node.data = static_cast<uint64_t>(var_data.GetUnsignedInt64(err, 0)); res_node.data =
static_cast<uint64_t>(var_data.GetUnsignedInt64(err, 0));
break; break;
case i8: case i8:
res_node.data = static_cast<uint64_t>(var_data.GetSignedInt8(err, 0)); res_node.data = static_cast<uint64_t>(var_data.GetSignedInt8(err, 0));
@@ -3171,6 +3364,7 @@ dbgui::data::result::NodeIdx LLDBBackend::build_nodes_for_var_uncached(
break; break;
} }
} }
}
// TODO: wrap this in recursive func // TODO: wrap this in recursive func
// TODO: always get child info for first level // TODO: always get child info for first level
@@ -3187,7 +3381,7 @@ dbgui::data::result::NodeIdx LLDBBackend::build_nodes_for_var_uncached(
void LLDBBackend::build_nodes_for_var_cached( void LLDBBackend::build_nodes_for_var_cached(
lldb::SBValue &val, data::result::NodeIdx cache_idx, lldb::SBValue &val, data::result::NodeIdx cache_idx,
std::vector<data::result::Node> &to_send) std::vector<data::result::Node> &to_send, bool address_of_or_pointer)
{ {
auto lldb_type = val.GetType(); auto lldb_type = val.GetType();
// auto type_id = this->build_type_from_lldb_type(lldb_type); // auto type_id = this->build_type_from_lldb_type(lldb_type);
@@ -3215,8 +3409,38 @@ void LLDBBackend::build_nodes_for_var_cached(
_types[no_alias_type.idx]->second.member_types); _types[no_alias_type.idx]->second.member_types);
} }
// TODO: this is copypasted from build_nodes_for_var_uncached so that should be consolidated in the future
// TODO: error handling // TODO: error handling
auto err = lldb::SBError{}; auto err = lldb::SBError{};
if (address_of_or_pointer)
{
if (no_alias_type.type == data::type_info::Type::ptr)
{
res_node.data = static_cast<uint64_t>(var_data.GetAddress(err, 0));
} else
{
// so we don't need to create a new type for the pointer i think so just replace the node type
res_node.type_id =
data::type_info::TypeID{.type = data::type_info::Type::ptr,
.sub_type = type_id.type,
.idx = type_id.idx};
auto addr_of = val.AddressOf();
auto data = addr_of.GetData();
if (!data.IsValid())
{
// copypasted from above
if (_data_res[cache_idx]->node.success)
{
to_send.push_back(res_node);
_data_res[cache_idx]->node.success = false;
}
return;
}
res_node.data = static_cast<uint64_t>(data.GetAddress(err, 0));
}
} else
{
switch (no_alias_type.type) switch (no_alias_type.type)
{ {
using enum data::type_info::Type; using enum data::type_info::Type;
@@ -3226,13 +3450,16 @@ void LLDBBackend::build_nodes_for_var_cached(
res_node.data = static_cast<uint64_t>(var_data.GetUnsignedInt8(err, 0)); res_node.data = static_cast<uint64_t>(var_data.GetUnsignedInt8(err, 0));
break; break;
case u16: case u16:
res_node.data = static_cast<uint64_t>(var_data.GetUnsignedInt16(err, 0)); res_node.data =
static_cast<uint64_t>(var_data.GetUnsignedInt16(err, 0));
break; break;
case u32: case u32:
res_node.data = static_cast<uint64_t>(var_data.GetUnsignedInt32(err, 0)); res_node.data =
static_cast<uint64_t>(var_data.GetUnsignedInt32(err, 0));
break; break;
case u64: case u64:
res_node.data = static_cast<uint64_t>(var_data.GetUnsignedInt64(err, 0)); res_node.data =
static_cast<uint64_t>(var_data.GetUnsignedInt64(err, 0));
break; break;
case i8: case i8:
res_node.data = static_cast<uint64_t>(var_data.GetSignedInt8(err, 0)); res_node.data = static_cast<uint64_t>(var_data.GetSignedInt8(err, 0));
@@ -3258,6 +3485,7 @@ void LLDBBackend::build_nodes_for_var_cached(
break; break;
} }
} }
}
// TODO: wrap this in recursive func // TODO: wrap this in recursive func
// TODO: always get child info for first level // TODO: always get child info for first level

View File

@@ -86,6 +86,7 @@ namespace dbgui::backend
bool used; bool used;
bool bool
global_or_static; // need to only compare compilation unit and module global_or_static; // need to only compare compilation unit and module
bool address_of_or_pointer;
std::optional<data::result::NodeIdx> cached_node; std::optional<data::result::NodeIdx> cached_node;
}; };
@@ -135,13 +136,16 @@ namespace dbgui::backend
std::optional<data::result::NodeIdx> std::optional<data::result::NodeIdx>
build_nodes_for_var(lldb::SBValue &val, build_nodes_for_var(lldb::SBValue &val,
std::vector<data::result::Node> &to_send); std::vector<data::result::Node> &to_send,
bool address_of_or_pointer);
data::result::NodeIdx data::result::NodeIdx
build_nodes_for_var_uncached(lldb::SBValue &, build_nodes_for_var_uncached(lldb::SBValue &,
std::vector<data::result::Node> &to_send); std::vector<data::result::Node> &to_send,
bool address_of_or_pointer);
void build_nodes_for_var_cached(lldb::SBValue &, void build_nodes_for_var_cached(lldb::SBValue &,
data::result::NodeIdx cache_idx, data::result::NodeIdx cache_idx,
std::vector<data::result::Node> &to_send); std::vector<data::result::Node> &to_send,
bool address_of_or_pointer);
void clear_unused_vars_from_cache(); void clear_unused_vars_from_cache();

View File

@@ -254,8 +254,9 @@ namespace dbgui::data
struct TypeInfo struct TypeInfo
{ {
Type type; Type type;
uint64_t byte_size;
TypeID enum_base; TypeID enum_base;
uint64_t byte_size;
// TODO: maybe we want a string array somewhere else? Harder to garbage collect but we save some space here
std::string display_name; std::string display_name;
std::string internal_name; std::string internal_name;
// sike, lldb doesnt give this to us // sike, lldb doesnt give this to us
@@ -324,6 +325,8 @@ namespace dbgui::data
struct Variable struct Variable
{ {
std::string expr_path; std::string expr_path;
// if this is set we want the address of whatever we got or the pointer value in case what we have is a pointer
bool address_of_or_pointer = false;
}; };
Type type; Type type;
@@ -352,6 +355,15 @@ namespace dbgui::data
size_t src_id; size_t src_id;
}; };
struct ReadAsArray {
size_t addr_src_id;
union {
size_t size_src_id;
uint64_t size_const;
};
bool is_const_size;
};
struct Node struct Node
{ {
enum class Type : uint8_t enum class Type : uint8_t
@@ -361,12 +373,13 @@ namespace dbgui::data
line_entry, line_entry,
locals, locals,
read_cstr, read_cstr,
read_array,
}; };
size_t id; size_t id;
Type type; Type type;
// when adding something here, remember to update LLDBBackend::add_data_node // when adding something here, remember to update LLDBBackend::add_data_node
std::variant<std::monostate, Source, Disassemble, LineEntry, ReadAsCStr> std::variant<std::monostate, Source, Disassemble, LineEntry, ReadAsCStr, ReadAsArray>
data; data;
}; };
} // namespace source } // namespace source

View File

@@ -982,686 +982,6 @@ bool SourceWindow::draw(Frontend &frontend)
return 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) void Window::handle_source_updated(Target &target, size_t id)
{ {
switch (this->type) switch (this->type)

View File

@@ -7,6 +7,8 @@
#include "msg.h" #include "msg.h"
#include "frontend/target.h" #include "frontend/target.h"
#include "window/watch_window.h"
namespace dbgui::frontend namespace dbgui::frontend
{ {
struct Frontend; struct Frontend;
@@ -124,45 +126,6 @@ namespace dbgui::frontend
std::vector<std::string_view> lines; 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;
};
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 struct Window
{ {
WindowType type; WindowType type;

File diff suppressed because it is too large Load Diff

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

BIN
tmp/main

Binary file not shown.

View File

@@ -3,7 +3,7 @@
namespace { namespace {
struct MyType { struct MyType {
int data; int data[3];
}; };
} }
@@ -11,11 +11,11 @@ void helper_fn();
void helper_fn2(); void helper_fn2();
int main(int argc, char* argv[]) { int main(int argc, char* argv[]) {
MyType tmp = MyType{10}; MyType tmp = MyType{{10, 5, 19}};
while (tmp.data != 0) { while (tmp.data[0] != 0) {
helper_fn(); helper_fn();
helper_fn2(); helper_fn2();
tmp.data--; tmp.data[0]--;
} }
return 0; return 0;
} }

View File

@@ -4,3 +4,16 @@
- visualization for branches (draw arror if branch is taken (or in both cases?)) - visualization for branches (draw arror if branch is taken (or in both cases?))
- allow (partial) path override for symbol source paths - allow (partial) path override for symbol source paths
- check if classes with base classes work - check if classes with base classes work
- next item: array variable specifier
- search/show/open all files in the debug symbols of the executable
- allow setting breakpoint by name + manually set them with a textbox
- make stop/restart buttons work
- make it work if you open a file again that it doesn't break
- add a context menu option to display the type of a variable in the watch window
- auto-switch to disassembly if there is no source (auto-switch to source if there is one for the first time?)
- allow for copying + changing the display type for a variable in the watch window (esp. important for locals)
- when stopping, still keep the binary around (so like a workspace)
- add smth that detects if the binary changes and adds a button to reload/restart it
- automatically detect cycles in pointer and stop the UI recursion if so
old imgui commit: a02315e1c4c80f94008779bfa0cc552542cb2f08

View File

@@ -0,0 +1,44 @@
mystruct.ptr, mystruct.arrsize
mystruct.ptr[$1].field.ptr[$2], mystruct.arrsize, mystruct.ptr[$1].field.arrsize
mystruct.ptr.field
mystruct.ptr[.arrsize].field.ptr[.arrsize]
cast<MyType*>(mystruct.ptr).field, dbl
%rax
%rbx
(int[mystruct.arraysize]*)(mystruct.ptr)
builtin functions? (e.g. casts)
<watch desc> = <data desc>, <format spec>
<data desc> = <data desc>.<data desc> | <array desc> | <ident> | <reg>
<array desc> = <ident>\[<arrsize spec>|<arrpath>\]
<arrsize spec> = [<arrpath>]..<arrpath>
<arrpath> = (\^*<ident path>) | <num literal> | #<num literal>
<ident path> = <ident> | <ident>.<ident path>
<reg> = $<ident>
$ for registers
# for special indices
% for various builtins
<format spec> = <type>[<num literal>[x<num literal>]]
<type> = <base type>
<base type> = i8 | i16 | i32 | i64 | i128 | u8 | u16 | u32 | u64 | u128 | f32 | f64 | flt | dbl | str | cstr
this is still missing the syntax to search in upper frames for variables