From 679e38e711dc217cf24f06304e0ea1e6740ac7af Mon Sep 17 00:00:00 2001 From: T0b1 Date: Sat, 17 Jun 2023 01:12:11 +0200 Subject: [PATCH] primitive locals --- src/backend/backend.cpp | 8 +- src/backend/debug_backend.h | 3 +- src/backend/lldb/lldb_backend.cpp | 1277 ++++++++++++++++++++++++++++- src/backend/lldb/lldb_backend.h | 67 +- src/data.cpp | 81 +- src/data.h | 88 +- src/frontend/frontend.cpp | 10 + src/frontend/target.h | 1 + src/frontend/window.cpp | 282 +++++++ src/frontend/window.h | 25 +- src/msg.h | 10 +- tmp/main | Bin 82400 -> 82584 bytes tmp/sec.cpp | 4 + 13 files changed, 1778 insertions(+), 78 deletions(-) diff --git a/src/backend/backend.cpp b/src/backend/backend.cpp index 2333cc8..269a2a0 100644 --- a/src/backend/backend.cpp +++ b/src/backend/backend.cpp @@ -58,7 +58,7 @@ void Backend::send_data_result(BackToFront::DataResult &&info) BackToFront::MsgType::data_result, std::move(info)); this->send_msg(std::move(msg)); } -void Backend::remove_data_node(uint16_t idx) +void Backend::send_remove_data_node(uint16_t idx) { auto msg = std::make_unique( BackToFront::MsgType::remove_data_node, @@ -78,4 +78,10 @@ void Backend::send_selected_frame_changed(uint16_t idx) BackToFront::MsgType::selected_frame_changed, BackToFront::SelectedFrameChanged{.idx = idx}); this->send_msg(std::move(msg)); +} +void Backend::send_type_info(BackToFront::TypeInfo &&info) +{ + auto msg = std::make_unique(BackToFront::MsgType::type_info, + std::move(info)); + this->send_msg(std::move(msg)); } \ No newline at end of file diff --git a/src/backend/debug_backend.h b/src/backend/debug_backend.h index 15c08aa..e771050 100644 --- a/src/backend/debug_backend.h +++ b/src/backend/debug_backend.h @@ -68,9 +68,10 @@ namespace dbgui::backend void send_frame_changed(BackToFront::FrameChanged &&); void send_frame_removed(BackToFront::FrameRemoved &&); void send_data_result(BackToFront::DataResult &&); - void remove_data_node(uint16_t); + void send_remove_data_node(uint16_t); void send_selected_thread_changed(uint16_t idx); void send_selected_frame_changed(uint16_t idx); + void send_type_info(BackToFront::TypeInfo &&); private: std::mutex _back_front_msg_mutex; diff --git a/src/backend/lldb/lldb_backend.cpp b/src/backend/lldb/lldb_backend.cpp index b984f3c..6827ace 100644 --- a/src/backend/lldb/lldb_backend.cpp +++ b/src/backend/lldb/lldb_backend.cpp @@ -155,7 +155,7 @@ void LLDBBackend::handle_state_change(lldb::StateType state) } //this->dump_threads(); - this->dump_variables(); + //this->dump_variables(); switch (state) { @@ -543,10 +543,11 @@ dbgui::data::type_info::TypeID auto idx = static_cast(out_vec.size()); // TODO: byte_size out_vec.push_back(type_info::TypeInfo{ - .type = type_info::Type::ptr, - .byte_size = 8, - .name = std::string{inner_type.GetDisplayTypeName()}, - .member_types = inner_id}); + .type = type_info::Type::ptr, + .byte_size = 8, + .display_name = std::string{inner_type.GetDisplayTypeName()}, + .internal_name = std::string{inner_type.GetName()}, + .member_types = inner_id}); return type_info::TypeID{.type = type_info::Type::ptr, .sub_type = type_info::Type::ptr, .idx = idx}; @@ -566,8 +567,9 @@ dbgui::data::type_info::TypeID exit(1); } - auto name = type.GetDisplayTypeName(); - auto type_copy = type; + auto display_name = type.GetDisplayTypeName(); + auto internal_name = type.GetName(); + auto type_copy = type; while (type_copy.IsTypedefType()) { type_copy = type_copy.GetTypedefedType(); @@ -579,8 +581,8 @@ dbgui::data::type_info::TypeID return *tmp; } - printf("Name: %s\n", name); - printf("TrueName: %s\n", type_copy.GetDisplayTypeName()); + printf("Name: %s\n", display_name); + printf("TrueName: %s\n", internal_name); printf("Pointer: %u\n", type_copy.IsPointerType()); printf("Ref: %u\n", type_copy.IsReferenceType()); printf("Func: %u\n", type_copy.IsFunctionType()); @@ -597,9 +599,11 @@ dbgui::data::type_info::TypeID // note: type can be both array and aggregate if (type_copy.IsArrayType()) { - out_vec.push_back(type_info::TypeInfo{.type = type_info::Type::array, - .byte_size = type.GetByteSize(), - .name = std::string{name}}); + out_vec.push_back( + type_info::TypeInfo{.type = type_info::Type::array, + .byte_size = type.GetByteSize(), + .display_name = std::string{display_name}, + .internal_name = std::string{internal_name}}); uint32_t self_idx = out_vec.size() - 1; auto inner_type = type_copy.GetArrayElementType(); auto inner_id = parse_type_inner(out_vec, inner_type); @@ -610,11 +614,12 @@ dbgui::data::type_info::TypeID { auto base_id = parse_type_inner(out_vec, base_type); out_vec.push_back(type_info::TypeInfo{ - .type = type_info::Type::_enum, - .byte_size = type_copy.GetByteSize(), - .enum_base = base_id, - .name = std::string{name}, - .member_types = std::vector{}}); + .type = type_info::Type::_enum, + .byte_size = type_copy.GetByteSize(), + .enum_base = base_id, + .display_name = std::string{display_name}, + .internal_name = std::string{internal_name}, + .member_types = std::vector{}}); uint32_t self_idx = out_vec.size() - 1; auto enum_list = type_copy.GetEnumMembers(); @@ -637,10 +642,11 @@ dbgui::data::type_info::TypeID } else if (type_copy.IsAggregateType()) { out_vec.push_back(type_info::TypeInfo{ - .type = type_info::Type::complex, - .byte_size = type_copy.GetByteSize(), - .name = std::string{name}, - .member_types = std::vector{}}); + .type = type_info::Type::complex, + .byte_size = type_copy.GetByteSize(), + .display_name = std::string{display_name}, + .internal_name = std::string{internal_name}, + .member_types = std::vector{}}); uint32_t self_idx = out_vec.size() - 1; uint32_t len = type_copy.GetNumberOfFields(); for (uint32_t i = 0; i < len; ++i) @@ -835,14 +841,18 @@ void LLDBBackend::dump_variables() printf(" -> Value: %s\n", static_var.GetValue());*/ } - auto fill_locals = _locals.empty(); - // static includes globals and thread local values // can't include them as we have no way of finding out // whether a static is defined inside our scope or not - auto list = frame.GetVariables(true, true, false, true); + auto list = frame.GetVariables(true, true, true, true); auto len = list.GetSize(); + static std::optional tmp_val{}; + if (tmp_val) + { + printf("\n\nTmpLoc: %lX\n\n", tmp_val->GetLoadAddress()); + } + for (uint32_t i = 0; i < len; ++i) { auto var = list.GetValueAtIndex(i); @@ -865,9 +875,9 @@ void LLDBBackend::dump_variables() printf("ExprPath: %.*s\n", static_cast(stream.GetSize()), stream.GetData()); - if (fill_locals) + if (!tmp_val && std::string_view{var.GetName()} == "tmp") { - _locals.push_back(std::make_pair(var, true)); + tmp_val = var; } auto var_frame = var.GetFrame(); @@ -881,6 +891,7 @@ void LLDBBackend::dump_variables() decl.GetDescription(stream); printf(" -> Declaration: %.*s\n", static_cast(stream.GetSize()), stream.GetData()); + printf(" -> Location: %lX\n", var.GetLoadAddress()); auto type_name = var.GetTypeName() ?: ""; auto type_display_name = var.GetDisplayTypeName() ?: ""; @@ -909,6 +920,7 @@ void LLDBBackend::dump_variables() type.GetDescription(stream, eDescriptionLevelVerbose); printf(" -> Type: %.*s\n", static_cast(stream.GetSize()), stream.GetData()); + printf(" -> BasicType: %u\n", type.GetBasicType()); uint32_t num_fields = type.GetNumberOfFields(); printf(" -> NumberOfFields: %u\n", num_fields); // TODO: look through typedef to check whether it is a primitive type @@ -921,9 +933,9 @@ void LLDBBackend::dump_variables() static_cast(stream.GetSize()), stream.GetData()); } - std::string fmt{}; - format_type(type, fmt); - printf(" -> Custom Type Info: %s\n", fmt.c_str()); + //std::string fmt{}; + //format_type(type, fmt); + //printf(" -> Custom Type Info: %s\n", fmt.c_str()); } } @@ -1507,12 +1519,15 @@ void LLDBBackend::remove_data_node(size_t id) auto cur_size = to_delete.size(); for (size_t i = old_size; i < cur_size; ++i) { - for (size_t j = 0; j < _data_res.size(); ++j) + auto node_idx = to_delete[i]; + for (size_t j = 0; j < _data_res[node_idx]->node.children.size(); ++j) { - if (_data_res[j] && _data_res[j]->parent_node == to_delete[i]) + auto child_idx = _data_res[node_idx]->node.children[j]; + if (_data_res[child_idx]->no_delete_on_src_delete) { - to_delete.push_back(j); + continue; } + to_delete.push_back(_data_res[node_idx]->node.children[j]); } } @@ -1521,7 +1536,7 @@ void LLDBBackend::remove_data_node(size_t id) for (auto idx : to_delete) { - this->remove_data_node(id); + this->send_remove_data_node(idx); _data_res[idx] = {}; } @@ -1537,6 +1552,11 @@ void LLDBBackend::check_data_changes() _dag_linear_valid = true; } + for (auto &entry : _var_cache) + { + entry.used = false; + } + for (auto id : _dag_linear) { const auto &node_it = @@ -1558,6 +1578,8 @@ void LLDBBackend::check_data_changes() {.nodes = std::move(res_vec), .src_node_id = src_id_mapping}); } } + + this->clear_unused_vars_from_cache(); } // TODO: call node src_node? @@ -1606,15 +1628,18 @@ std::optional> auto &cached = *this->_data_res[res.idx]; if (cached.src_id == node.id) { - if (!cached.child_node) + //if (!cached.child_node) + //{ + if (cached.node.success == res.success + && cached.node.type_id == res.type_id + && cached.node.data == res.data) { - if (cached.node.success == res.success - && cached.node.type_id == res.type_id - && cached.node.data == res.data) + if (cached.node.children == res.children) { val_changed = false; } } + //} } } @@ -1627,11 +1652,7 @@ std::optional> { this->_data_res.resize(res.idx + 1); } - this->_data_res[res.idx] = CachedDataRes{.node = res, - .child_node = false, - .parent_node = 0, - .parent_member_id = 0, - .src_id = node.id}; + this->_data_res[res.idx] = CachedDataRes{.node = res, .src_id = node.id}; auto idx = res.idx; data_res.push_back(std::move(res)); @@ -1960,12 +1981,1184 @@ std::optional> .data = std::move(out), }); } + case locals: + { + using namespace lldb; + + uint16_t cache_idx = 0; + auto cached = true; + if (auto found_idx = find_node_for_src_id(node.id); found_idx) + { + cache_idx = *found_idx; + } else + { + cached = false; + cache_idx = get_free_res_slot(); + _data_res[cache_idx] = CachedDataRes{.src_id = node.id}; + } + + auto thread = _process->GetSelectedThread(); + auto frame = thread.GetSelectedFrame(); + if (!frame.IsValid()) + { + return check_single_res_changed(data::result::Node{ + .idx = cache_idx, + .type_id = data::type_info::TypeID::none(), + .success = false, + }); + } + + auto list = frame.GetVariables(true, true, false, true); + uint32_t len = list.GetSize(); + + auto local_nodes = std::vector>{}; + + for (auto i = 0; i < len; ++i) + { + auto var = list.GetValueAtIndex(i); + if (!var.IsValid()) + { + continue; + } + + // check if the var already exists + // TODO: need to handle shadows + auto name = var.GetName(); + if (!name) + { + name = ""; + } + printf("Got local %s\n", name); + + auto res_idx = this->build_nodes_for_var(var, data_res); + if (!res_idx) + { + printf("No result\n"); + continue; + } + + local_nodes.push_back(std::make_pair(*res_idx, std::string{name})); + } + + auto res_node = + data::result::Node{.idx = cache_idx, + .type_id = data::type_info::TypeID::custom(), + .success = true}; + + auto &data = res_node.vec_data(); + for (auto &[idx, name] : local_nodes) + { + res_node.children.push_back(idx); + assert(name.size() < 0xFFFF); + size_t start_off = data.size(); + data.resize(data.size() + sizeof(uint16_t) + name.size()); + *reinterpret_cast(data.data() + start_off) = name.size(); + std::memcpy(data.data() + start_off + 2, name.data(), name.size()); + } + + if (cached) + { + auto &cached_node = *_data_res[cache_idx]; + if (cached_node.node.data != res_node.data + || cached_node.node.children != res_node.children) + { + data_res.push_back(res_node); + cached_node.node = std::move(res_node); + return std::make_pair(cache_idx, node.id); + } + return {}; + } else + { + _data_res[cache_idx] = + CachedDataRes{.node = res_node, .src_id = node.id}; + data_res.push_back(std::move(res_node)); + return std::make_pair(cache_idx, node.id); + } + } } printf("Unhandled data type\n"); exit(1); } +#if 0 +std::pair LLDBBackend::build_locals() +{ + // clear locals which are no longer in scope + for (size_t i = 0; i < _locals.size();) + { + if (!_locals[i].lldb_val.IsInScope()) + { + if (_locals[i].cached_node) + { + this->delete_node_and_childs(*_locals[i].cached_node); + } + _locals.erase(_locals.begin() + i); + } else + { + ++i; + } + } + + auto thread = _process->GetSelectedThread(); + auto frame = thread.GetSelectedFrame(); + + if (!thread.IsValid() || !frame.IsValid()) + { + assert(0); + exit(1); + // return {}; + } + + // static includes globals and thread local values + // can't include them as we have no way of finding out + // whether a static is defined inside our scope or not + auto list = frame.GetVariables(true, true, false, true); + uint32_t len = list.GetSize(); + + auto local_nodes = std::vector>{}; + auto nodes_to_send = std::vector{}; + + for (auto i = 0; i < len; ++i) + { + auto var = list.GetValueAtIndex(i); + if (!var.IsValid()) + { + continue; + } + + // check if the var already exists + // TODO: need to handle shadows + auto name = var.GetName(); + if (!name) + { + name = ""; + } + + // TODO: this is useful for type resolution + // as it gives us the compilation unit & co + auto addr = var.GetAddress(); + auto load_addr = addr.GetLoadAddress(_target); + auto decl = var.GetDeclaration(); + + std::optional cached_idx = {}; + for (uint16_t idx = 0; idx < _locals.size(); ++idx) + { + auto &local = _locals[idx].lldb_val; + auto local_load_addr = local.GetLoadAddress(); + + if (local_load_addr != load_addr) + { + continue; + } + + auto local_name = local.GetName(); + if (!local_name) + { + local_name = ""; + } + + if (strcmp(name, local_name)) + { + continue; + } + + auto local_decl = local.GetDeclaration(); + if (local_decl.IsValid() && local_decl.IsValid() == decl.IsValid() + && local_decl != decl) + { + continue; + } + + cached_idx = idx; + break; + } + + using namespace data; + type_info::TypeID type_id; + uint16_t res_idx; + if (cached_idx) + { + type_id = _data_res[*cached_idx]->node.type_id; + res_idx = *cached_idx; + } else + { + type_id = this->build_type_from_var(var); + res_idx = this->get_free_data_node(); + } + + auto var_data = var.GetData(); + + auto res_node = + result::Node{.idx = res_idx, .type_id = type_id, .success = true}; + // TODO: error handling + auto err = lldb::SBError{}; + switch (type_id.type) + { + using enum type_info::Type; + + case _bool: + case u8: + res_node.data = static_cast(var_data.GetUnsignedInt8(err, 0)); + break; + case u16: + res_node.data = + static_cast(var_data.GetUnsignedInt16(err, 0)); + break; + case u32: + res_node.data = + static_cast(var_data.GetUnsignedInt32(err, 0)); + break; + case u64: + res_node.data = + static_cast(var_data.GetUnsignedInt64(err, 0)); + break; + case i8: + res_node.data = static_cast(var_data.GetSignedInt8(err, 0)); + break; + case i16: + res_node.data = static_cast(var_data.GetSignedInt16(err, 0)); + break; + case i32: + res_node.data = static_cast(var_data.GetSignedInt32(err, 0)); + break; + case i64: + res_node.data = static_cast(var_data.GetSignedInt64(err, 0)); + break; + case f32: res_node.data = var_data.GetFloat(err, 0); break; + case f64: res_node.data = var_data.GetDouble(err, 0); break; + default: + { + res_node.data = std::vector{}; + auto &vec = res_node.vec_data(); + auto size = var_data.GetByteSize(); + vec.resize(size); + var_data.ReadRawData(err, 0, vec.data(), size); + break; + } + } + + // TODO: just wrap this in a recursive function, we need it later anyways + // TODO: get child member data (always for first level, otherwise only if requested) + + local_nodes.push_back(std::make_pair(res_node.idx, std::string{name})); + + if (cached_idx) + { + auto &cached_node = _data_res[res_idx]; + if (cached_node->node.data != res_node.data) + { + cached_node->node.data = res_node.data; + nodes_to_send.push_back(std::move(res_node)); + } + } else + { + _data_res[res_idx] = CachedDataRes{ + .node = res_node, + .src_id = std::numeric_limits::max(), + }; + nodes_to_send.push_back(std::move(res_node)); + } + } + + uint16_t res_idx; + auto cached = true; + if (_locals_node) + { + res_idx = *_locals_node; + } else + { + res_idx = this->get_free_data_node(); + _locals_node = res_idx; + cached = false; + } + + auto res_node = + data::result::Node{.idx = res_idx, + .type_id = data::type_info::TypeID::custom(), + .success = true}; + + auto &data = res_node.vec_data(); + for (auto &[idx, name] : local_nodes) + { + res_node.children.push_back(idx); + assert(name.size() < 0xFFFF); + size_t start_off = data.size(); + data.resize(data.size() + sizeof(uint16_t) + sizeof(name.size())); + *reinterpret_cast(data.data() + start_off) = name.size(); + std::memcpy(data.data() + start_off + 2, name.data(), name.size()); + } + + if (cached) + { + auto &cached_node = *_data_res[res_idx]; + auto changed = false; + if (cached_node.node.data != res_node.data + || cached_node.node.children != res_node.children) + { + cached_node.node = std::move(res_node); + changed = true; + } + return std::make_pair(res_idx, changed); + } else + { + _data_res[res_idx] = CachedDataRes{ + .node = res_node, .src_id = std::numeric_limits::max()}; + return std::make_pair(res_idx, true); + } +} +#endif + +dbgui::data::type_info::TypeID + LLDBBackend::build_type_from_lldb_type(lldb::SBType &lldb_type) +{ + using namespace data::type_info; + using namespace lldb; + if (auto tmp = basic_type_id(lldb_type.GetBasicType()); tmp) + { + return *tmp; + } + + // check if we have the type already, + // very ghetto since LLDB doesnt give us the DIE offset + // and im not sure whether the impl ptrs in SBType are unique + // TODO: type might have no name, use declaration to find suitable candidate? + // TODO: make hashmap for name lookup + for (uint32_t i = 0; i < _types.size(); ++i) + { + if (!_types[i]) + { + continue; + } + + if (_types[i]->first == lldb_type) + { + return _types[i]->second.type_id(i); + } + + if (_types[i]->second.internal_name != lldb_type.GetName()) + { + continue; + } + + if (this->is_type_equal(_types[i]->second.type_id(i), lldb_type)) + { + return _types[i]->second.type_id(i); + } + } + + // build the type + if (lldb_type.IsPointerType() || lldb_type.IsReferenceType()) + { + auto inner_type = SBType{}; + if (lldb_type.IsPointerType()) + { + inner_type = lldb_type.GetPointeeType(); + } else + { + inner_type = lldb_type.GetDereferencedType(); + } + + auto inner_id = this->build_type_from_lldb_type(inner_type); + if (inner_id.is_basic()) + { + return TypeID{.type = Type::ptr, .sub_type = inner_id.type, .idx = 0}; + } else if (inner_id.type == Type::ptr) + { + auto idx = static_cast(_types.size()); + // TODO: byte_size + _types.emplace_back(std::make_pair( + lldb_type, + TypeInfo{.type = Type::ptr, + .byte_size = 8, // TODO: depending on target arch + .display_name = std::string{inner_type.GetDisplayTypeName()}, + .internal_name = std::string{inner_type.GetName()}, + .member_types = inner_id})); + this->send_type_info(std::move( + BackToFront::TypeInfo{.type_info = _types[idx]->second, .idx = idx})); + return TypeID{.type = Type::ptr, .sub_type = inner_id.type, .idx = idx}; + } else + { + return TypeID{ + .type = Type::ptr, .sub_type = inner_id.type, .idx = inner_id.idx}; + } + } + + if (lldb_type.IsTypedefType()) + { + auto inner_type = lldb_type.GetTypedefedType(); + auto inner_id = this->build_type_from_lldb_type(inner_type); + + auto idx = static_cast(_types.size()); + _types.emplace_back(std::make_pair( + lldb_type, + TypeInfo{.type = Type::alias, + .byte_size = 8, // TODO: depending on target arch + .display_name = std::string{lldb_type.GetDisplayTypeName()}, + .internal_name = std::string{lldb_type.GetName()}, + .member_types = inner_id})); + this->send_type_info(std::move( + BackToFront::TypeInfo{.type_info = _types[idx]->second, .idx = idx})); + return TypeID{.type = Type::alias, .sub_type = inner_id.type, .idx = idx}; + } + + // should not be a basic type + if (lldb_type.GetBasicType() != eBasicTypeInvalid) + { + assert(0); + exit(1); + } + + auto display_name = lldb_type.GetDisplayTypeName(); + auto internal_name = lldb_type.GetName(); + +#if 0 + printf("Name: %s\n", name); + printf("TrueName: %s\n", lldb_type.GetName()); + printf("Pointer: %u\n", lldb_type.IsPointerType()); + printf("Ref: %u\n", lldb_type.IsReferenceType()); + printf("Func: %u\n", lldb_type.IsFunctionType()); + printf("Poly: %u\n", lldb_type.IsPolymorphicClass()); + printf("Array: %u\n", lldb_type.IsArrayType()); + printf("Vec: %u\n", lldb_type.IsVectorType()); + printf("Typedef: %u\n", lldb_type.IsTypedefType()); + printf("Anonymous: %u\n", lldb_type.IsAnonymousType()); + printf("ScopedEnum: %u\n", lldb_type.IsScopedEnumerationType()); + printf("Aggregate: %u\n", lldb_type.IsAggregateType()); + printf("EnumIntTypeValid: %u\n", + lldb_type.GetEnumerationIntegerType().IsValid()); +#endif + + // note: type can be both array and aggregate + if (lldb_type.IsArrayType()) + { + _types.emplace_back(std::make_pair( + lldb_type, TypeInfo{.type = Type::array, + .byte_size = lldb_type.GetByteSize(), + .display_name = std::string{display_name}, + .internal_name = std::string{internal_name}})); + uint32_t self_idx = _types.size() - 1; + auto inner_type = lldb_type.GetArrayElementType(); + auto inner_id = this->build_type_from_lldb_type(inner_type); + _types[self_idx]->second.member_types = inner_id; + this->send_type_info(std::move(BackToFront::TypeInfo{ + .type_info = _types[self_idx]->second, .idx = self_idx})); + return TypeID{.type = Type::array, .idx = self_idx}; + } else if (auto base_type = lldb_type.GetEnumerationIntegerType(); + base_type.IsValid()) + { + auto base_id = this->build_type_from_lldb_type(base_type); + _types.emplace_back(std::make_pair( + lldb_type, TypeInfo{.type = Type::_enum, + .byte_size = lldb_type.GetByteSize(), + .enum_base = base_id, + .display_name = std::string{display_name}, + .internal_name = std::string{internal_name}, + .member_types = std::vector{}})); + uint32_t self_idx = _types.size() - 1; + + auto enum_list = lldb_type.GetEnumMembers(); + uint32_t len = enum_list.GetSize(); + for (uint32_t i = 0; i < len; ++i) + { + auto member = enum_list.GetTypeEnumMemberAtIndex(i); + auto member_type = member.GetType(); + auto member_id = this->build_type_from_lldb_type(member_type); + auto name = member.GetName(); + if (!name) + { + name = ""; + } + auto val = member.GetValueAsUnsigned(); + _types[self_idx]->second.member_vec().push_back( + MemberInfo{.name = name, .type_id = member_id, .enum_val = val}); + } + + this->send_type_info(std::move(BackToFront::TypeInfo{ + .type_info = _types[self_idx]->second, .idx = self_idx})); + return TypeID{.type = Type::_enum, .idx = self_idx}; + } else if (lldb_type.IsAggregateType()) + { + _types.emplace_back(std::make_pair( + lldb_type, TypeInfo{.type = Type::complex, + .byte_size = lldb_type.GetByteSize(), + .display_name = std::string{display_name}, + .internal_name = std::string{internal_name}, + .member_types = std::vector{}})); + uint32_t self_idx = _types.size() - 1; + uint32_t len = lldb_type.GetNumberOfFields(); + for (uint32_t i = 0; i < len; ++i) + { + auto member = lldb_type.GetFieldAtIndex(i); + auto member_type = member.GetType(); + auto member_id = this->build_type_from_lldb_type(member_type); + auto name = member.GetName(); + if (!name) + { + name = ""; + } + auto offset = member.GetOffsetInBytes(); + auto bitfield_size = member.GetBitfieldSizeInBits(); + if (bitfield_size != 0) + { + offset = member.GetOffsetInBits(); + } + _types[self_idx]->second.member_vec().push_back( + MemberInfo{.name = name, + .type_id = member_id, + .bitfield_size = bitfield_size, + .offset = offset}); + } + this->send_type_info(std::move(BackToFront::TypeInfo{ + .type_info = _types[self_idx]->second, .idx = self_idx})); + return TypeID{.type = Type::complex, .idx = self_idx}; + } else + { + printf("Unknown type encountered!\n"); + assert(0); + exit(1); + } +} + +bool LLDBBackend::is_type_equal(data::type_info::TypeID type_id, + lldb::SBType &lldb_type) +{ + using namespace lldb; + using namespace data::type_info; + switch (type_id.type) + { + using enum Type; + case _bool: return lldb_type.GetBasicType() == eBasicTypeBool; + case u8: return lldb_type.GetBasicType() == eBasicTypeUnsignedChar; + case u16: return lldb_type.GetBasicType() == eBasicTypeUnsignedShort; + case u32: return lldb_type.GetBasicType() == eBasicTypeUnsignedInt; + case u64: + { + // TODO: arch/platform dependent + auto basic_type = lldb_type.GetBasicType(); + return basic_type == eBasicTypeUnsignedLong + || basic_type == eBasicTypeUnsignedLongLong; + } + case i8: + { + auto basic_type = lldb_type.GetBasicType(); + return basic_type == eBasicTypeSignedChar || basic_type == eBasicTypeChar + || basic_type == eBasicTypeChar8; + } + case i16: + { + auto basic_type = lldb_type.GetBasicType(); + return basic_type == eBasicTypeSignedWChar + || basic_type == eBasicTypeShort || basic_type == eBasicTypeChar16; + } + case i32: + { + auto basic_type = lldb_type.GetBasicType(); + return basic_type == eBasicTypeInt || basic_type == eBasicTypeChar32; + } + case i64: + { + // TODO: arch/platform dependent + auto basic_type = lldb_type.GetBasicType(); + return basic_type == eBasicTypeLong || basic_type == eBasicTypeLongLong; + } + case u128: return lldb_type.GetBasicType() == eBasicTypeUnsignedInt128; + case i128: return lldb_type.GetBasicType() == eBasicTypeInt128; + case f32: return lldb_type.GetBasicType() == eBasicTypeFloat; + case f64: return lldb_type.GetBasicType() == eBasicTypeDouble; + case f128: return lldb_type.GetBasicType() == eBasicTypeLongDouble; + + case ptr: + { + if (!lldb_type.IsPointerType()) + { + return false; + } + + auto sub_type = lldb_type.GetPointeeType(); + if (type_id.sub_type != Type::ptr) + { + auto sub_id = TypeID{.type = type_id.sub_type, .idx = type_id.idx}; + return this->is_type_equal(sub_id, sub_type); + } + + auto sub_id = std::get(_types[type_id.idx]->second.member_types); + return this->is_type_equal(sub_id, sub_type); + } + + case array: + { + if (!lldb_type.IsArrayType()) + { + return false; + } + + auto &info = _types[type_id.idx]; + if (info->second.byte_size != lldb_type.GetByteSize()) + { + return false; + } + + if (std::string_view{lldb_type.GetName()} != info->second.internal_name) + { + return false; + } + + auto member_type = lldb_type.GetArrayElementType(); + return this->is_type_equal(std::get(info->second.member_types), + member_type); + } + + case alias: + { + if (!lldb_type.IsTypedefType()) + { + return false; + } + + const auto &info = _types[type_id.idx]; + if (std::string_view{lldb_type.GetName()} != info->second.internal_name) + { + return false; + } + + auto sub_id = std::get(info->second.member_types); + auto sub_type = lldb_type.GetTypedefedType(); + return this->is_type_equal(sub_id, sub_type); + } + + case complex: + { + if (!lldb_type.IsAggregateType()) + { + return false; + } + + auto &type = _types[type_id.idx]->second; + + if (type.byte_size != lldb_type.GetByteSize()) + { + return false; + } + + if (std::string_view{lldb_type.GetName()} != type.internal_name) + { + return false; + } + + const auto &members = type.member_vec(); + const auto lldb_len = lldb_type.GetNumberOfFields(); + if (lldb_len != members.size()) + { + return false; + } + + for (uint32_t i = 0; i < lldb_len; ++i) + { + const auto &member = members[i]; + auto lldb_member = lldb_type.GetFieldAtIndex(i); + + if (member.bitfield_size != lldb_member.GetBitfieldSizeInBits()) + { + return false; + } + if (member.bitfield_size) + { + if (member.offset != lldb_member.GetOffsetInBits()) + { + return false; + } + } else + { + if (member.offset != lldb_member.GetOffsetInBytes()) + { + return false; + } + } + + auto name = lldb_member.GetName(); + if (!name) + { + name = ""; + } + if (member.name != name) + { + return false; + } + + auto member_type = lldb_member.GetType(); + if (!this->is_type_equal(member.type_id, member_type)) + { + return false; + } + } + + return true; + } + + case _enum: + { + auto base_type = lldb_type.GetEnumerationIntegerType(); + if (!base_type.IsValid()) + { + return false; + } + + auto &type = _types[type_id.idx]->second; + + if (type.byte_size != lldb_type.GetByteSize()) + { + return false; + } + + if (std::string_view{lldb_type.GetName()} != type.internal_name) + { + return false; + } + + if (!this->is_type_equal(type.enum_base, base_type)) + { + return false; + } + + const auto &members = type.member_vec(); + auto lldb_members = lldb_type.GetEnumMembers(); + const auto lldb_len = lldb_members.GetSize(); + if (lldb_len != members.size()) + { + return false; + } + + for (uint32_t i = 0; i < lldb_len; ++i) + { + const auto &member = members[i]; + auto lldb_member = lldb_members.GetTypeEnumMemberAtIndex(i); + + auto val = lldb_member.GetValueAsUnsigned(); + if (val != member.enum_val) + { + return false; + } + + if (member.name != lldb_member.GetName()) + { + return false; + } + } + + return true; + } + + default: return false; + } +} + +std::optional + LLDBBackend::build_nodes_for_var(lldb::SBValue &val, + std::vector &to_send) +{ + using namespace lldb; + using namespace data; + + if (!val.IsValid()) + { + return {}; + } + + auto stream = SBStream{}; + // TODO: error handling + val.GetExpressionPath(stream); + auto path = std::string_view{stream.GetData(), stream.GetSize()}; + + auto load_addr = val.GetLoadAddress(); + auto val_is_global = false; + std::optional cache_entry = {}; + if (val.GetValueType() == eValueTypeVariableGlobal + || val.GetValueType() == eValueTypeVariableStatic) + { + val_is_global = true; + for (auto i = 0; i < _var_cache.size(); ++i) + { + auto &entry = _var_cache[i]; + if (!entry.global_or_static) + { + continue; + } + + if (entry.lldb_val.GetLoadAddress() != load_addr) + { + continue; + } + + if (entry.expr_path != path) + { + continue; + } + + cache_entry = i; + break; + } + } else + { + auto sym_ctx = val.GetFrame().GetSymbolContext(true); + auto mod = sym_ctx.GetModule(); + auto cu = sym_ctx.GetCompileUnit(); + auto fun = sym_ctx.GetFunction(); + auto block = sym_ctx.GetBlock(); + auto block_range_cnt = block.GetNumRanges(); + for (auto i = 0; i < _var_cache.size(); ++i) + { + auto &entry = _var_cache[i]; + if (entry.global_or_static) + { + continue; + } + + if (entry.lldb_val.GetLoadAddress() != load_addr) + { + continue; + } + + if (entry.expr_path != path) + { + continue; + } + + if (mod != entry.sym_ctx.GetModule()) + { + continue; + } + + if (cu != entry.sym_ctx.GetCompileUnit()) + { + continue; + } + + if (fun != entry.sym_ctx.GetFunction()) + { + continue; + } + + auto entry_block = entry.sym_ctx.GetBlock(); + if (entry_block.GetNumRanges() != block_range_cnt) + { + continue; + } + + auto eq = true; + for (auto range = 0; range < block_range_cnt; ++range) + { + if (entry_block.GetRangeStartAddress(range) + != block.GetRangeStartAddress(range)) + { + eq = false; + break; + } + if (entry_block.GetRangeEndAddress(range) + != block.GetRangeEndAddress(range)) + { + eq = false; + break; + } + } + + if (!eq) + { + continue; + } + + cache_entry = i; + break; + } + } + + if (cache_entry) + { + if (_var_cache[*cache_entry].used) + { + // was already diffed + return *cache_entry; + } + auto node_idx = *_var_cache[*cache_entry].cached_node; + _var_cache[*cache_entry].used = true; + this->build_nodes_for_var_cached(val, node_idx, to_send); + + return node_idx; + } + + auto idx = this->build_nodes_for_var_uncached(val, to_send); + _var_cache.push_back( + VarCacheEntry{.lldb_val = val, + .sym_ctx = val.GetFrame().GetSymbolContext(true), + .expr_path = std::string{path}, + .used = true, + .global_or_static = val_is_global, + .cached_node = idx}); + return idx; +} + +dbgui::data::result::NodeIdx LLDBBackend::build_nodes_for_var_uncached( + lldb::SBValue &val, std::vector &to_send) +{ + auto lldb_type = val.GetType(); + auto type_id = this->build_type_from_lldb_type(lldb_type); + + auto res_idx = this->get_free_data_node(); + auto res_node = + data::result::Node{.idx = res_idx, .type_id = type_id, .success = false}; + + // for now just dump the whole value + auto var_data = val.GetData(); + if (!var_data.IsValid()) + { + 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::max()}; + return res_idx; + } + + auto no_alias_type = type_id; + while (no_alias_type.type == data::type_info::Type::alias) + { + no_alias_type = std::get( + _types[no_alias_type.idx]->second.member_types); + } + + // TODO: error handling + auto err = lldb::SBError{}; + switch (no_alias_type.type) + { + using enum data::type_info::Type; + + case _bool: + case u8: + res_node.data = static_cast(var_data.GetUnsignedInt8(err, 0)); + break; + case u16: + res_node.data = static_cast(var_data.GetUnsignedInt16(err, 0)); + break; + case u32: + res_node.data = static_cast(var_data.GetUnsignedInt32(err, 0)); + break; + case u64: + res_node.data = static_cast(var_data.GetUnsignedInt64(err, 0)); + break; + case i8: + res_node.data = static_cast(var_data.GetSignedInt8(err, 0)); + break; + case i16: + res_node.data = static_cast(var_data.GetSignedInt16(err, 0)); + break; + case i32: + res_node.data = static_cast(var_data.GetSignedInt32(err, 0)); + break; + case i64: + res_node.data = static_cast(var_data.GetSignedInt64(err, 0)); + break; + case f32: res_node.data = var_data.GetFloat(err, 0); break; + case f64: res_node.data = var_data.GetDouble(err, 0); break; + default: + { + res_node.data = std::vector{}; + auto &vec = res_node.vec_data(); + auto size = var_data.GetByteSize(); + vec.resize(size); + var_data.ReadRawData(err, 0, vec.data(), size); + break; + } + } + + // TODO: wrap this in recursive func + // TODO: always get child info for first level + // TODO: depending on what is previewed? + + res_node.success = true; + 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::max()}; + return res_idx; +} + +void LLDBBackend::build_nodes_for_var_cached( + lldb::SBValue &val, data::result::NodeIdx cache_idx, + std::vector &to_send) +{ + auto lldb_type = val.GetType(); + // auto type_id = this->build_type_from_lldb_type(lldb_type); + auto type_id = _data_res[cache_idx]->node.type_id; + + auto res_node = + data::result::Node{.idx = cache_idx, .type_id = type_id, .success = false}; + + // for now just dump the whole value + auto var_data = val.GetData(); + if (!var_data.IsValid()) + { + if (_data_res[cache_idx]->node.success) + { + to_send.push_back(res_node); + _data_res[cache_idx]->node.success = false; + } + return; + } + + auto no_alias_type = type_id; + while (no_alias_type.type == data::type_info::Type::alias) + { + no_alias_type = std::get( + _types[no_alias_type.idx]->second.member_types); + } + + // TODO: error handling + auto err = lldb::SBError{}; + switch (no_alias_type.type) + { + using enum data::type_info::Type; + + case _bool: + case u8: + res_node.data = static_cast(var_data.GetUnsignedInt8(err, 0)); + break; + case u16: + res_node.data = static_cast(var_data.GetUnsignedInt16(err, 0)); + break; + case u32: + res_node.data = static_cast(var_data.GetUnsignedInt32(err, 0)); + break; + case u64: + res_node.data = static_cast(var_data.GetUnsignedInt64(err, 0)); + break; + case i8: + res_node.data = static_cast(var_data.GetSignedInt8(err, 0)); + break; + case i16: + res_node.data = static_cast(var_data.GetSignedInt16(err, 0)); + break; + case i32: + res_node.data = static_cast(var_data.GetSignedInt32(err, 0)); + break; + case i64: + res_node.data = static_cast(var_data.GetSignedInt64(err, 0)); + break; + case f32: res_node.data = var_data.GetFloat(err, 0); break; + case f64: res_node.data = var_data.GetDouble(err, 0); break; + default: + { + res_node.data = std::vector{}; + auto &vec = res_node.vec_data(); + auto size = var_data.GetByteSize(); + vec.resize(size); + var_data.ReadRawData(err, 0, vec.data(), size); + break; + } + } + + // TODO: wrap this in recursive func + // TODO: always get child info for first level + // TODO: depending on what is previewed? + + res_node.success = true; + if (!_data_res[cache_idx]->node.success + || _data_res[cache_idx]->node.data != res_node.data) + { + to_send.push_back(res_node); + _data_res[cache_idx]->node.success = true; + _data_res[cache_idx]->node.data = std::move(res_node.data); + } +} + +void LLDBBackend::clear_unused_vars_from_cache() +{ + // TODO: buffer? + std::vector to_delete; + + for (size_t i = 0; i < _var_cache.size(); ++i) + { + if (_var_cache[i].used) + { + continue; + } + + if (_var_cache[i].cached_node) + { + to_delete.push_back(*_var_cache[i].cached_node); + } + _var_cache.erase(_var_cache.begin() + i); + } + + size_t old_size = 0; + while (to_delete.size() != old_size) + { + auto cur_size = to_delete.size(); + for (size_t i = old_size; i < cur_size; ++i) + { + auto node_idx = to_delete[i]; + for (size_t j = 0; j < _data_res[node_idx]->node.children.size(); ++j) + { + auto child_idx = _data_res[node_idx]->node.children[j]; + to_delete.push_back(_data_res[node_idx]->node.children[j]); + } + } + + old_size = cur_size; + } + + for (auto idx : to_delete) + { + this->send_remove_data_node(idx); + _data_res[idx] = {}; + } +} + +dbgui::data::result::NodeIdx LLDBBackend::get_free_data_node() +{ + for (uint16_t i = 0; i < this->_data_res.size(); ++i) + { + if (!this->_data_res[i]) + { + return i; + } + } + + auto idx = this->_data_res.size(); + this->_data_res.resize(idx + 1); + return idx; +} + +void LLDBBackend::delete_node_and_childs(uint16_t idx) +{ + std::vector to_delete; + to_delete.push_back(idx); + + size_t old_size = 0; + while (old_size != to_delete.size()) + { + size_t cur_size = to_delete.size(); + for (size_t i = old_size; i < cur_size; ++i) + { + if (_data_res[to_delete[i]]) + { + for (auto child : _data_res[to_delete[i]]->node.children) + { + to_delete.push_back(child); + } + } + } + + old_size = cur_size; + } + + for (auto idx : to_delete) + { + _data_res[idx] = {}; + this->send_remove_data_node(idx); + } +} + void LLDBBackend::add_breakpoint(uint64_t addr, size_t id) { std::lock_guard g{_data_lock}; diff --git a/src/backend/lldb/lldb_backend.h b/src/backend/lldb/lldb_backend.h index a64450e..23591ab 100644 --- a/src/backend/lldb/lldb_backend.h +++ b/src/backend/lldb/lldb_backend.h @@ -57,14 +57,38 @@ namespace dbgui::backend }; data::result::Node node; - bool child_node; - uint16_t parent_node; - uint16_t parent_member_id; + // bool child_node; + bool no_delete_on_src_delete = false; size_t src_id; // invalid = size_t::MAX // std::variant extra_info; }; + struct Local + { + lldb::SBValue lldb_val; + std::optional cached_node; + }; + + struct VarCacheEntry + { + // for globals/statics compare location + expr path (bc unions) + // for others compare location + expr path + symctx (without line entry) + // TODO: this will repeatedly retrieve cyclic values + // probably bad so maybe detect this some other way mby without expr paths? + // but then we need to cache types somehow... + + lldb::SBValue lldb_val; + lldb::SBSymbolContext sym_ctx; + std::string expr_path; + // NOTE: when used is set to true, this means that the var was + // diffed this step already and will no longer be so careful with changing this + bool used; + bool + global_or_static; // need to only compare compilation unit and module + std::optional cached_node; + }; + // TODO: source_init_file: false LLDBBackend(std::string filename); virtual ~LLDBBackend(); @@ -102,7 +126,29 @@ namespace dbgui::backend void check_thread_changes(); void check_frame_changes(); void check_data_changes(); - std::optional> + + // TODO: func to clear locals if they are no longer requested + // node_id, did_change + // std::pair build_locals(); + data::type_info::TypeID build_type_from_lldb_type(lldb::SBType &); + bool is_type_equal(data::type_info::TypeID, lldb::SBType &); + + std::optional + build_nodes_for_var(lldb::SBValue &val, + std::vector &to_send); + data::result::NodeIdx + build_nodes_for_var_uncached(lldb::SBValue &, + std::vector &to_send); + void build_nodes_for_var_cached(lldb::SBValue &, + data::result::NodeIdx cache_idx, + std::vector &to_send); + + void clear_unused_vars_from_cache(); + + void delete_node_and_childs(uint16_t); + data::result::NodeIdx get_free_data_node(); + + std::optional> calc_data_res(const data::source::Node &, std::vector &data_res); @@ -129,8 +175,17 @@ namespace dbgui::backend bool _dag_linear_valid = false; // util::DAG _data_res_dag = {}; std::vector> _data_res = {}; - std::vector> _locals = {}; - std::optional _last_frame = {}; + + std::vector _locals = {}; + std::optional _locals_node = {}; + + std::optional _last_frame = {}; + + std::vector< + std::optional>> + _types = {}; + + std::vector _var_cache{}; // std::unordered_map _src_id_to_data_idx = {}; // std::vector _cached_data_results = {}; diff --git a/src/data.cpp b/src/data.cpp index 28e4705..9de6993 100644 --- a/src/data.cpp +++ b/src/data.cpp @@ -41,30 +41,30 @@ void type_info::TypeInfo::format(const std::vector &types, out += "complex"; break; } - case _union: - { - out += "union"; - break; - } case _enum: { out += "enum"; break; } + case alias: + { + out += "alias"; + break; + } } out += " "; - out += this->name; + out += this->display_name; char buf[32]; std::snprintf(buf, sizeof(buf), " $%lu", this->byte_size); out += buf; - if (this->type == Type::array) + if (this->type == Type::array || this->type == Type::alias) { out += " "; std::get(this->member_types).format(out); // TODO: calc member size? - } else if (this->type == Type::complex || this->type == Type::_union) + } else if (this->type == Type::complex) { out += " {"; for (const auto &member : this->member_vec()) @@ -135,6 +135,13 @@ void type_info::TypeID::format(std::string &out) const out += buf; return; } + case alias: + { + char buf[32]; + std::snprintf(buf, sizeof(buf), "alias #%u", this->idx); + out += buf; + return; + } case ptr: { out += "ptr "; @@ -157,13 +164,6 @@ void type_info::TypeID::format(std::string &out) const out += buf; return; } - case _union: - { - char buf[32]; - std::snprintf(buf, sizeof(buf), "union #%u", this->idx); - out += buf; - return; - } case _enum: { char buf[32]; @@ -172,4 +172,55 @@ void type_info::TypeID::format(std::string &out) const return; } } +} + +size_t type_info::TypeID::byte_size( + const std::vector &types) const +{ + switch (this->type) + { + using enum Type; + case none: + case _void: return 0; + case custom: assert(0); return 0; + case _bool: return 1; + case u8: return 1; + case u16: return 2; + case u32: return 4; + case u64: return 8; + case u128: return 16; + case i8: return 1; + case i16: return 2; + case i32: return 4; + case i64: return 8; + case i128: return 16; + case f32: return 4; + case f64: return 8; + case f128: return 16; + case array: + { + return types[this->idx].byte_size; + } + case alias: + { + return std::get(types[this->idx].member_types) + .byte_size(types); + } + case ptr: + { + // TODO: platform dependent + return 8; + } + case complex: + { + return types[this->idx].byte_size; + } + case _enum: + { + return types[this->idx].byte_size; + } + } + + assert(0); + return 0; } \ No newline at end of file diff --git a/src/data.h b/src/data.h index 53c9579..ebf90a0 100644 --- a/src/data.h +++ b/src/data.h @@ -5,6 +5,7 @@ #include #include #include +#include namespace dbgui::data { @@ -127,8 +128,8 @@ namespace dbgui::data array, ptr, complex, - _union, _enum, + alias, // if this grows to more than 32 elements, change the bitfield size in TypeID }; @@ -136,8 +137,11 @@ namespace dbgui::data { // TODO: just remove the bitfield? Type type : 5; - // if type == ptr, then this is valid + // if type == ptr || type == alias, then this is valid Type sub_type : 5; + // for alias, this is always valid + // TODO: make this always point at self even in the ptr case + // and use the embedded hint purely as a size hint? uint32_t idx : 22; static TypeID none() @@ -167,11 +171,14 @@ namespace dbgui::data void format(std::string &out) const; + // TODO: move this to target as its target dependent? + size_t byte_size(const std::vector &) const; + bool is_basic() const { return type != Type::none && type != Type::custom && type != Type::array && type != Type::complex && type != Type::ptr - && type != Type::_union && type != Type::_enum; + && type != Type::_enum && type != Type::alias; } bool operator==(const TypeID &rhs) const @@ -198,13 +205,20 @@ namespace dbgui::data } if (this->sub_type == Type::array || this->sub_type == Type::complex - || this->sub_type == Type::custom) + || this->sub_type == Type::custom || this->sub_type == Type::ptr + || this->sub_type == Type::alias) { if (this->idx != rhs.idx) { return false; } } + } else if (this->type == Type::alias) + { + if (this->sub_type != rhs.sub_type || this->idx != rhs.idx) + { + return false; + } } return true; @@ -234,7 +248,8 @@ namespace dbgui::data Type type; uint64_t byte_size; TypeID enum_base; - std::string name; + std::string display_name; + std::string internal_name; // sike, lldb doesnt give this to us // std::string def_file_path; // uint32_t def_file_line; @@ -247,6 +262,31 @@ namespace dbgui::data return std::get>(this->member_types); } + TypeID type_id(uint32_t self_idx) const + { + if (type == Type::ptr) + { + auto inner_type = std::get(this->member_types); + if (inner_type.type == Type::ptr) + return TypeID{ + .type = Type::ptr, .sub_type = inner_type.type, .idx = self_idx}; + else + { + return TypeID{.type = Type::ptr, + .sub_type = inner_type.type, + .idx = inner_type.idx}; + } + } else if (type == Type::alias) + { + auto inner_type = std::get(this->member_types); + return TypeID{ + .type = type, .sub_type = inner_type.type, .idx = self_idx}; + } else + { + return TypeID{.type = type, .sub_type = Type::none, .idx = self_idx}; + } + } + void format(const std::vector &types, std::string &out); }; } // namespace type_info @@ -298,30 +338,64 @@ namespace dbgui::data source, disassemble, line_entry, + locals, }; size_t id; Type type; // when adding something here, remember to update LLDBBackend::add_data_node - std::variant data; + std::variant data; }; } // namespace source // result stuff namespace result { + using NodeIdx = uint16_t; + struct Node { - uint16_t idx; + NodeIdx idx; // uint16_t gen; type_info::TypeID type_id; bool success; std::variant, uint64_t, float, double> data; + std::vector children; const std::vector &vec_data() const { return std::get>(this->data); } + + std::vector &vec_data() + { + return std::get>(this->data); + } + + template + T get_primitive(size_t off) const + { + if (data.index() == 0) + { + const auto &vec = this->vec_data(); + assert(vec.size() >= off + sizeof(T)); + return *reinterpret_cast(vec.data() + off); + } else + { + assert(off == 0); + if constexpr (std::is_same_v) + { + assert(data.index() == 2); + return std::get(data); + } else if constexpr (std::is_same_v) + { + assert(data.index() == 3); + return std::get(data); + } + assert(data.index() == 1); + return static_cast(std::get(data)); + } + } }; } // namespace result diff --git a/src/frontend/frontend.cpp b/src/frontend/frontend.cpp index 07b27aa..ba8f0c3 100644 --- a/src/frontend/frontend.cpp +++ b/src/frontend/frontend.cpp @@ -26,6 +26,7 @@ Frontend::Frontend() _windows.push_back(Window::create_disas(window_id++)); _windows.push_back(Window::create_bp(window_id++)); _windows.push_back(Window::create_source(window_id++)); + _windows.push_back(Window::create_watch(window_id++)); } void Frontend::run_frame() @@ -400,6 +401,15 @@ void Frontend::handle_msgs() } break; } + case type_info: + { + auto &info = std::get(msg->data); + if (this->target->types.size() <= info.idx) + { + this->target->types.resize(info.idx + 1); + } + this->target->types[info.idx] = std::move(info.type_info); + } } } } diff --git a/src/frontend/target.h b/src/frontend/target.h index 5b3cbdb..f81b3b9 100644 --- a/src/frontend/target.h +++ b/src/frontend/target.h @@ -123,6 +123,7 @@ namespace dbgui::frontend std::vector breakpoints; std::vector> src_id_to_data_idx; std::vector> data_res_nodes; + std::vector types; std::shared_ptr backend = nullptr; }; diff --git a/src/frontend/window.cpp b/src/frontend/window.cpp index 8fa3125..d1acc9b 100644 --- a/src/frontend/window.cpp +++ b/src/frontend/window.cpp @@ -28,6 +28,7 @@ bool Window::draw(Frontend &frontend) case source: return std::get(this->data).draw(frontend); break; + case watch: return std::get(this->data).draw(frontend); default: printf("Unhandled window draw: %u\n", this->type); exit(1); } } @@ -88,6 +89,15 @@ Window Window::create_source(size_t id) 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); @@ -970,6 +980,278 @@ 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; + } + + // ImGuiTableFlags_SizingFixedFit + if (ImGui::BeginTable("##Variables", 2)) + { + ImGui::TableSetupColumn("Name"); + ImGui::TableSetupColumn("Value"); + ImGui::TableHeadersRow(); + + auto locals_idx = frontend.target->data_idx_for_src_id(this->locals_src_id); + if (locals_idx) + { + const auto &local_node = *frontend.target->data_res_nodes[*locals_idx]; + const auto &locals_data = local_node.vec_data(); + size_t cur_off = 0; + size_t cur_idx = 0; + while (cur_off < locals_data.size()) + { + if (cur_off + 2 > locals_data.size()) + { + break; + } + auto str_len = + *reinterpret_cast(locals_data.data() + cur_off); + if (cur_off + 2 + str_len > locals_data.size()) + { + break; + } + + auto name = std::string_view{ + reinterpret_cast(locals_data.data() + cur_off + 2), + str_len}; + auto node_idx = local_node.children[cur_idx]; + this->draw_value(frontend, + frontend.target->data_res_nodes[node_idx]->type_id, + name, node_idx, 0); + + cur_idx += 1; + cur_off += 2 + str_len; + } + } + + ImGui::EndTable(); + } + + ImGui::End(); + + return false; +} + +void WatchWindow::draw_value(Frontend &frontend, + data::type_info::TypeID type_id, + std::string_view name, + data::result::NodeIdx node_idx, size_t off) +{ + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + const auto &node_opt = frontend.target->data_res_nodes[node_idx]; + if (!node_opt || !node_opt->success) + { + ImGui::TextUnformatted(name.begin(), name.end()); + ImGui::TableNextColumn(); + ImGui::TextDisabled(""); + return; + } + + const auto &node = *node_opt; + using namespace data::type_info; + while (type_id.type == Type::alias) + { + type_id = + std::get(frontend.target->types[type_id.idx].member_types); + } + + auto tree_open = false; + auto pop_id = false; + if (type_id.type == Type::complex || type_id.type == Type::array) + { + ImGui::PushID(name.begin(), name.end()); + pop_id = true; + + tree_open = + ImGui::TreeNodeEx((void *)node_idx, ImGuiTreeNodeFlags_SpanFullWidth, + "%.*s", static_cast(name.size()), name.data()); + + if (tree_open) + { + if (type_id.type == Type::complex) + { + 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]; + // Skip bitfield + if (member.bitfield_size) + { + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + ImGui::Text("%s", member.name.c_str()); + ImGui::TableNextColumn(); + ImGui::TextDisabled(""); + continue; + } + + this->draw_value(frontend, member.type_id, member.name, node_idx, + off + member.offset); + } + } else + { + // array + auto member_ty_id = + std::get(frontend.target->types[type_id.idx].member_types); + auto member_size = member_ty_id.byte_size(frontend.target->types); + size_t el_count = + frontend.target->types[type_id.idx].byte_size / member_size; + + 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); + member_off += member_size; + } + } + + ImGui::TreePop(); + } + + ImGui::PopID(); + } else + { + ImGui::TextUnformatted(name.begin(), name.end()); + ImGui::TableSetColumnIndex(1); + switch (type_id.type) + { + using enum Type; + case i8: + { + auto val = node.get_primitive(off); + if (std::isprint(val)) + { + ImGui::Text("%c", val); + } else + { + ImGui::Text("%d", val); + } + break; + } + + case u8: ImGui::Text("%lu", node.get_primitive(off)); break; + case u16: ImGui::Text("%lu", node.get_primitive(off)); break; + case u32: ImGui::Text("%lu", node.get_primitive(off)); break; + case u64: ImGui::Text("%lu", node.get_primitive(off)); break; + case i16: ImGui::Text("%ld", node.get_primitive(off)); break; + case i32: ImGui::Text("%ld", node.get_primitive(off)); break; + case i64: ImGui::Text("%ld", node.get_primitive(off)); break; + case _bool: + { + auto val = node.get_primitive(off); + if (val) + { + ImGui::TextUnformatted("true"); + } else + { + ImGui::TextUnformatted("false"); + } + break; + } + // TODO + case f128: + case i128: + case u128: + { + const auto &data = node.vec_data(); + const auto lo = node.get_primitive(off); + const auto hi = node.get_primitive(off + 8); + ImGui::Text("%16lX-%16lX", hi, lo); + break; + } + case f32: + { + ImGui::Text("%f", node.get_primitive(off)); + break; + } + case f64: + { + ImGui::Text("%f", node.get_primitive(off)); + break; + } + case ptr: + { + // TODO: ptr size + ImGui::Text("%lX", node.get_primitive(off)); + break; + } + case _enum: + { + // TODO: robustness + uint64_t val; + const auto byte_size = frontend.target->types[type_id.idx].byte_size; + if (byte_size == 8) + { + val = node.get_primitive(off); + } else if (byte_size == 4) + { + val = node.get_primitive(off); + } else if (byte_size == 2) + { + val = node.get_primitive(off); + } else if (byte_size == 1) + { + val = node.get_primitive(off); + } else + { + assert(0); + val = 0; + } + const auto &members = frontend.target->types[type_id.idx].member_vec(); + auto printed = false; + for (const auto &member : members) + { + if (val == member.enum_val) + { + ImGui::Text("%s", member.name.c_str()); + printed = true; + break; + } + } + if (!printed) + { + ImGui::Text("", val); + } + break; + } + } + } +} + void Window::handle_source_updated(Target &target, size_t id) { switch (this->type) diff --git a/src/frontend/window.h b/src/frontend/window.h index da39aa1..c993178 100644 --- a/src/frontend/window.h +++ b/src/frontend/window.h @@ -16,7 +16,7 @@ namespace dbgui::frontend regs, source, memory, - variables, + watch, frames, threads, disassembly, @@ -84,7 +84,7 @@ namespace dbgui::frontend bool draw(Frontend &); - void handle_source_updated(Target& target, size_t id); + void handle_source_updated(Target &target, size_t id); std::string id; bool open; @@ -110,7 +110,7 @@ namespace dbgui::frontend struct SourceWindow { bool draw(Frontend &); - void handle_source_updated(Target& target, size_t id); + void handle_source_updated(Target &target, size_t id); std::string id; bool open; @@ -124,11 +124,25 @@ namespace dbgui::frontend std::vector lines; }; + struct WatchWindow + { + bool draw(Frontend &); + void draw_value(Frontend &, data::type_info::TypeID, std::string_view name, + data::result::NodeIdx node_idx, size_t off); + // void handle_source_updated(Target& target, size_t id); + + std::string id; + bool open; + bool first; + + size_t locals_src_id; + }; + struct Window { WindowType type; std::variant + DisasmWindow, BreakpointWindow, SourceWindow, WatchWindow> data; // if true, window is closed and should be deleted @@ -140,7 +154,8 @@ namespace dbgui::frontend static Window create_disas(size_t window_id); static Window create_bp(size_t window_id); static Window create_source(size_t window_id); + static Window create_watch(size_t window_id); - void handle_source_updated(Target& target, size_t id); + void handle_source_updated(Target &target, size_t id); }; } // namespace dbgui::frontend \ No newline at end of file diff --git a/src/msg.h b/src/msg.h index 98e1c7f..f16010d 100644 --- a/src/msg.h +++ b/src/msg.h @@ -74,6 +74,8 @@ namespace dbgui remove_data_node, selected_frame_changed, selected_thread_changed, + remove_src_id_mapping, + type_info, }; struct StateChange @@ -148,6 +150,11 @@ namespace dbgui uint16_t node; }; + struct RemoveSrcIDMapping + { + size_t src_id; + }; + struct SelectedFrameChanged { uint16_t idx; @@ -170,7 +177,8 @@ namespace dbgui std::variant + SelectedThreadChanged, RemoveDataNode, RemoveSrcIDMapping, + TypeInfo> data; }; } // namespace BackToFront diff --git a/tmp/main b/tmp/main index 17ee69e6b0807553df8bd80ec7fd64af0ed28cee..5c35f55b20aa10718fc83a03abd5c9f6c9ee50a7 100755 GIT binary patch delta 6864 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 JZ$~Ai{2w@(yE*^> delta 6471 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 #include +uint32_t my_glob = 42; + namespace test { enum MyEnum : uint16_t { ENUM_VAL1 = 10, @@ -20,6 +22,8 @@ namespace test { void helper_fn() { test::MyType tmp = test::MyType{1}; std::string test = "Hello World"; + uint32_t x = 25; + unsigned int y = 30; sleep(tmp.data); { test::MyType tmp = test::MyType{2};