#include "lldb_backend.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace dbgui::backend; namespace {} LLDBBackend::~LLDBBackend() {} LLDBBackend::LLDBBackend(std::string filename) { _filename = filename; char buf[256]; buf[0] = '\0'; pthread_getname_np(pthread_self(), buf, sizeof(buf)); // name lldb threads pthread_setname_np(pthread_self(), "lldb_internal_thread"); lldb::SBDebugger::Initialize(); _instance = lldb::SBDebugger::Create(false); _target = _instance.CreateTarget(filename.c_str()); pthread_setname_np(pthread_self(), buf); } void LLDBBackend::start() { const char *argv[2] = {_filename.c_str(), nullptr}; const auto cwd = std::filesystem::current_path(); auto error = lldb::SBError(); auto listener = lldb::SBListener(); char buf[256]; buf[0] = '\0'; { pthread_getname_np(pthread_self(), buf, sizeof(buf)); // name lldb threads pthread_setname_np(pthread_self(), "lldb_internal_thread"); } _process = _target.Launch(listener, argv, nullptr, nullptr, nullptr, nullptr, cwd.c_str(), lldb::LaunchFlags::eLaunchFlagNone, true, error); { pthread_setname_np(pthread_self(), buf); } _msg_thread = std::thread{[this]() { auto ptr = this->shared_from_this(); static_cast(ptr.get())->run_msg_loop(); }}; } void LLDBBackend::run_msg_loop() { pthread_setname_np(pthread_self(), "msg_loop"); std::thread event_thread{[this]() { this->wait_for_debug_events(); }}; while (true) { std::this_thread::sleep_for(std::chrono::milliseconds{1000}); } } void LLDBBackend::wait_for_debug_events() { using namespace lldb; pthread_setname_np(pthread_self(), "lldb_msg_loop"); auto listener = _instance.GetListener(); while (true) { auto event = lldb::SBEvent(); if (listener.WaitForEvent(0xFFFFFFFF, event)) { std::lock_guard g{_data_lock}; printf("Got Event:\n"); auto stream = lldb::SBStream{}; if (event.GetDescription(stream)) { printf(" Desc: %.*s\n", static_cast(stream.GetSize()), stream.GetData()); } else { printf(" Failed to get stream\n"); } if (SBProcess::EventIsProcessEvent(event)) { // Check if state changed auto state = SBProcess::GetStateFromEvent(event); // TODO: get state from process if we can't get it from the event? if (state != StateType::eStateInvalid) { this->handle_state_change(state); } } else { printf(" Unknown event source!\n"); } } } } void LLDBBackend::handle_state_change(lldb::StateType state) { using namespace lldb; if (_first_run) { _first_run = false; // TODO: do initialization // TODO: we should only do this when the process was stopped, no? auto proc_info = BackToFront::InitialProcessInfo{}; auto reg_infos = std::vector{}; this->prepare_proc_info(proc_info, reg_infos); this->dump_threads(); this->send_proc_info(std::move(proc_info)); for (size_t i = 0; i < reg_infos.size(); ++i) { this->send_reg_change(std::move(reg_infos[i])); } reg_infos.clear(); this->check_thread_changes(); this->check_frame_changes(); this->check_data_changes(); if (state == StateType::eStateStopped) { _state = TargetState::paused; this->send_state_change(TargetState::paused, StateChangeReason::initial_entry); return; } } //this->dump_threads(); //this->dump_variables(); switch (state) { case eStateStopped: { this->check_reg_changes(); this->check_thread_changes(); this->check_frame_changes(); this->check_data_changes(); _state = TargetState::paused; // TODO: does the selected thread auto-switch? auto sel_thread = _process->GetSelectedThread(); auto change_reason = StateChangeReason::unknown; auto extra = 0; switch (sel_thread.GetStopReason()) { case eStopReasonBreakpoint: { // what is location id/N for breakpoints? // does this have to do when there are multiple // place the same source code ends up? auto bp_id = sel_thread.GetStopReasonDataAtIndex(0); // clang-format bugs out on this for some reason // clang-format off auto it = std::find_if( _breakpoints.begin(), _breakpoints.end(), [bp_id](const auto &el) { return el.lldb_id == bp_id; }); // clang-format on if (it != _breakpoints.end()) { change_reason = StateChangeReason::breakpoint; extra = it->id; } break; } } this->send_state_change(TargetState::paused, change_reason, extra); break; } case eStateRunning: case eStateStepping: _state = TargetState::running; this->send_state_change(TargetState::running, StateChangeReason::unknown); return; default: printf("Unknown StateType %u encountered\n", state); exit(1); } } std::array x86_64_gpr = { "rax", "rbx", "rcx", "rdx", "rdi", "rsi", "rbp", "rsp", "r8", "r9", "r10", "r11", "r12", "r13", "r14", "r15", "rip", "rflags", "cs", "fs", "gs", "ss", "ds", "es"}; std::array x86_64_fp_special = { "fctrl", "fstat", "ftag", "fop", "fiseg", "fioff", "fip", "foseg", "fooff", "foseg", "fooff", "fdp", "mxcsr", "mxcsrmask"}; std::array x86_64_fp = {"st0", "st1", "st2", "st3", "st4", "st5", "st6", "st7"}; std::array x86_64_xmm = { "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5", "xmm6", "xmm7", "xmm8", "xmm9", "xmm10", "xmm11", "xmm12", "xmm13", "xmm14", "xmm15"}; std::array x86_64_ymm = { "ymm0", "ymm1", "ymm2", "ymm3", "ymm4", "ymm5", "ymm6", "ymm7", "ymm8", "ymm9", "ymm10", "ymm11", "ymm12", "ymm13", "ymm14", "ymm15"}; std::array x86_64_zmm = { "zmm0", "zmm1", "zmm2", "zmm3", "zmm4", "zmm5", "zmm6", "zmm7", "zmm8", "zmm9", "zmm10", "zmm11", "zmm12", "zmm13", "zmm14", "zmm15"}; void LLDBBackend::prepare_proc_info( BackToFront::InitialProcessInfo &info, std::vector ®_infos) { using namespace lldb; info.pid = _process->GetProcessID(); const auto target_triple = std::string_view{_process->GetProcessInfo().GetTriple()}; printf("Target Triple: %s\n", target_triple.data()); if (target_triple.starts_with("x86_64-")) { info.arch = Arch::x86_64; } /* else if (target_triple.starts_with("x86-")) { // TODO: is this the right triple? info.arch = Arch::x86; }*/ else { printf("Arch not supported!\n"); exit(1); } auto frame = _process->GetThreadAtIndex(0).GetFrameAtIndex(0); const auto regs = frame.GetRegisters(); /*const auto len = regs.GetSize(); for (size_t i = 0; i < len; ++i) { auto reg_or_set = regs.GetValueAtIndex(i); 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)); info.reg_names.emplace_back(reg_or_set.GetName()); continue; } printf("Got register set %s\n", reg_or_set.GetName()); 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) { auto reg = reg_or_set.GetChildAtIndex(child_idx); printf("Got register %s (%s) with value: %lX\n", reg.GetName(), reg.GetTypeName(), reg.GetValueAsUnsigned(0)); if (is_gp_set && info.arch == Arch::x86_64 && reg.GetByteSize() < 8) { // skip non-full regs here printf(" -> Skipped\n"); continue; } info.reg_names.emplace_back(reg.GetName()); } }*/ if (info.arch == Arch::x86_64) { { auto reg_set = RegSet{}; auto info_set = BackToFront::InitialProcessInfo::RegSet{}; auto reg_info = BackToFront::RegsChanged{}; reg_info.set_idx = 0; info_set.name = "GPR"; reg_set.set_name = "General Purpose Registers"; auto lldb_set = regs.GetFirstValueByName("General Purpose Registers"); for (size_t i = 0; i < x86_64_gpr.size(); ++i) { auto val = lldb_set.GetChildMemberWithName(x86_64_gpr[i]); if (!val.IsValid()) { printf("GPR %s not found!\n", x86_64_gpr[i]); exit(1); } info_set.reg_names.push_back(x86_64_gpr[i]); reg_set.names.push_back(x86_64_gpr[i]); reg_set.values.push_back({}); auto data = val.GetData(); auto len = data.GetByteSize(); reg_set.values.back().resize(data.GetByteSize()); auto error = SBError{}; data.ReadRawData(error, 0, reg_set.values.back().data(), len); reg_info.changes.push_back( std::make_pair((uint16_t)i, reg_set.values.back())); } _reg_sets.push_back(reg_set); info.reg_sets.push_back(info_set); reg_infos.push_back(reg_info); } } } void LLDBBackend::dump_threads() { using namespace lldb; const auto thread_count = _process->GetNumThreads(); for (size_t i = 0; i < thread_count; ++i) { auto thread = _process->GetThreadAtIndex(i); auto stop_reason = thread.GetStopReason(); switch (stop_reason) { case eStopReasonSignal: { auto signal_num = thread.GetStopReasonDataAtIndex(0); printf("Thread %lu (%s: %s): TID: %lu, Stop Reason: Signal: %lu\n", i, thread.GetFrameAtIndex(0).GetDisplayFunctionName(), thread.GetName(), thread.GetThreadID(), signal_num); break; } default: printf("Thread %lu (%s: %s): TID: %lu, Stop Reason: %lu\n", i, thread.GetFrameAtIndex(0).GetDisplayFunctionName(), thread.GetName(), thread.GetThreadID(), stop_reason); } } auto sel = _process->GetSelectedThread(); if (sel.IsValid()) { printf("Selected Thread: %lu\n", sel.GetThreadID()); auto frame = sel.GetFrameAtIndex(0); auto symctx = frame.GetSymbolContext(eSymbolContextEverything); auto stream = SBStream{}; symctx.GetDescription(stream); printf("Symctx: %.*s\n", static_cast(stream.GetSize()), stream.GetData()); /*auto mod = symctx.GetModule(); auto cu = symctx.GetCompileUnit(); auto block = symctx.GetBlock(); auto le = symctx.GetLineEntry(); stream.Clear(); mod.GetDescription(stream); printf("Module: %.*s\n", static_cast(stream.GetSize()), stream.GetData()); stream.Clear(); cu.GetDescription(stream); printf("CU: %.*s\n", static_cast(stream.GetSize()), stream.GetData()); stream.Clear(); block.GetDescription(stream); printf("Block: %.*s\n", static_cast(stream.GetSize()), stream.GetData()); if (!le.IsValid()) { printf("LE not valid\n"); } /*uint32_t line, col; line = le.GetLine(); col = le.GetColumn(); printf("LE: %u:%u\n", line, col);* stream.Clear(); le.GetDescription(stream); printf("LE: %.*s\n", static_cast(stream.GetSize()), stream.GetData());*/ /*auto list = _target.FindTypes("test::MyType"); printf("List len: %lu\n", list.GetSize()); auto len = list.GetSize(); for (uint32_t i = 0; i < len; ++i) { auto typ = list.GetTypeAtIndex(i); stream.Clear(); typ.GetDescription(stream, eDescriptionLevelFull); printf("Type %u: %.*s\n", i, static_cast(stream.GetSize()), stream.GetData()); }*/ /*auto pc = frame.GetPC(); stream.Clear(); //auto sc = // frame.GetSymbolContext(eSymbolContextFunction | eSymbolContextSymbol); auto sc = _target.ResolveSymbolContextForAddress( SBAddress{pc, _target}, eSymbolContextFunction | eSymbolContextSymbol); uint64_t start, end; if (sc.GetFunction().IsValid()) { auto fn = sc.GetFunction(); start = fn.GetStartAddress().GetLoadAddress(_target); end = fn.GetEndAddress().GetLoadAddress(_target); } else if (sc.GetSymbol().IsValid() && sc.GetSymbol().GetStartAddress().IsValid()) { auto sym = sc.GetSymbol(); start = sym.GetStartAddress().GetLoadAddress(_target); end = sym.GetEndAddress().GetLoadAddress(_target); } else { start = pc; end = start + 0x100; }*/ /*auto buf = std::vector{}; buf.resize(end - start); auto err = SBError{}; _target.ReadMemory(SBAddress{start, _target}, buf.data(), buf.size(), err); auto inst_list = _target.GetInstructionsWithFlavor(start, "intel", buf.data(), buf.size()); stream.Clear(); inst_list.GetDescription(stream); printf("InstList: %.*s\n", static_cast(stream.GetSize()), stream.GetData()); auto inst = inst_list.GetInstructionAtIndex(0); const auto *comment = inst.GetComment(_target); if (comment && *comment != '\0') { printf("Selfprint: %s%s ; %s\n", inst.GetMnemonic(_target), inst.GetOperands(_target), comment); } else { printf("Selfprint: %s%s\n", inst.GetMnemonic(_target), inst.GetOperands(_target)); }*/ //printf("Disasm: %s\n", frame.Disassemble()); } else { printf("Selected thread not valid\n"); } } std::optional basic_type_id(lldb::BasicType type) { using namespace dbgui::data::type_info; using namespace lldb; switch (type) { case eBasicTypeVoid: return TypeID::basic(Type::_void); case eBasicTypeChar: case eBasicTypeSignedChar: return TypeID::basic(Type::i8); case eBasicTypeUnsignedChar: return TypeID::basic(Type::u8); case eBasicTypeWChar: case eBasicTypeSignedWChar: case eBasicTypeChar16: case eBasicTypeShort: return TypeID::basic(Type::i16); case eBasicTypeUnsignedWChar: case eBasicTypeUnsignedShort: return TypeID::basic(Type::u16); case eBasicTypeChar32: case eBasicTypeInt: return TypeID::basic(Type::i32); case eBasicTypeUnsignedInt: return TypeID::basic(Type::u32); case eBasicTypeLong: // TODO: decide based on target platform return TypeID::basic(Type::i32); case eBasicTypeUnsignedLong: // TODO: decide based on target platform return TypeID::basic(Type::u32); case eBasicTypeLongLong: return TypeID::basic(Type::i64); case eBasicTypeUnsignedLongLong: return TypeID::basic(Type::u64); case eBasicTypeInt128: return TypeID::basic(Type::i128); case eBasicTypeUnsignedInt128: return TypeID::basic(Type::u128); case eBasicTypeBool: return TypeID::basic(Type::_bool); case eBasicTypeFloat: return TypeID::basic(Type::f32); case eBasicTypeDouble: return TypeID::basic(Type::f64); case eBasicTypeLongDouble: return TypeID::basic(Type::f128); case eBasicTypeNullPtr: // TODO: target platform dependent return TypeID::basic(Type::u64); // unhandled case eBasicTypeHalf: case eBasicTypeFloatComplex: case eBasicTypeDoubleComplex: case eBasicTypeLongDoubleComplex: case eBasicTypeObjCID: case eBasicTypeObjCClass: case eBasicTypeObjCSel: case eBasicTypeOther: case eBasicTypeInvalid: return {}; } assert(0); exit(1); } dbgui::data::type_info::TypeID parse_type_inner(std::vector &out_vec, lldb::SBType &type) { using namespace lldb; using namespace dbgui::data; if (auto tmp = basic_type_id(type.GetBasicType()); tmp) { return *tmp; } if (type.IsPointerType() || type.IsReferenceType()) { auto inner_type = SBType{}; if (type.IsPointerType()) { inner_type = type.GetPointeeType(); } else { inner_type = type.GetDereferencedType(); } auto inner_id = parse_type_inner(out_vec, inner_type); if (inner_id.is_basic()) { return type_info::TypeID{ .type = type_info::Type::ptr, .sub_type = inner_id.type, .idx = 0}; } else if (inner_id.type == type_info::Type::ptr) { 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, .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}; } else { return type_info::TypeID{.type = type_info::Type::ptr, .sub_type = inner_id.type, .idx = inner_id.idx}; } } // should not be a basic type if (type.GetBasicType() != eBasicTypeInvalid || type.IsPointerType() || type.IsReferenceType()) { assert(0); exit(1); } auto display_name = type.GetDisplayTypeName(); auto internal_name = type.GetName(); auto type_copy = type; while (type_copy.IsTypedefType()) { type_copy = type_copy.GetTypedefedType(); } // TODO: create entry for named basic types? if (auto tmp = basic_type_id(type_copy.GetBasicType()); tmp) { return *tmp; } 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()); printf("Poly: %u\n", type_copy.IsPolymorphicClass()); printf("Array: %u\n", type_copy.IsArrayType()); printf("Vec: %u\n", type_copy.IsVectorType()); printf("Typedef: %u\n", type_copy.IsTypedefType()); printf("Anonymous: %u\n", type_copy.IsAnonymousType()); printf("ScopedEnum: %u\n", type_copy.IsScopedEnumerationType()); printf("Aggregate: %u\n", type_copy.IsAggregateType()); printf("EnumIntTypeValid: %u\n", type_copy.GetEnumerationIntegerType().IsValid()); // 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(), .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); out_vec[self_idx].member_types = inner_id; return type_info::TypeID{.type = type_info::Type::array, .idx = self_idx}; } else if (auto base_type = type_copy.GetEnumerationIntegerType(); base_type.IsValid()) { 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, .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(); 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 = parse_type_inner(out_vec, member_type); auto name = member.GetName(); if (!name) { name = ""; } auto val = member.GetValueAsUnsigned(); out_vec[self_idx].member_vec().push_back(type_info::MemberInfo{ .name = name, .type_id = member_id, .enum_val = val}); } return type_info::TypeID{.type = type_info::Type::_enum, .idx = self_idx}; } else if (type_copy.IsAggregateType()) { out_vec.push_back(type_info::TypeInfo{ .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) { auto member = type_copy.GetFieldAtIndex(i); auto member_type = member.GetType(); auto member_id = parse_type_inner(out_vec, 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(); } out_vec[self_idx].member_vec().push_back( type_info::MemberInfo{.name = name, .type_id = member_id, .bitfield_size = bitfield_size, .offset = offset}); } return type_info::TypeID{.type = type_info::Type::complex, .idx = self_idx}; } else { printf("Unknown type encountered!\n"); assert(0); exit(1); } } void format_type(lldb::SBType &type, std::string &out) { using namespace lldb; using namespace dbgui::data; auto vec = std::vector{}; auto type_id = parse_type_inner(vec, type); out += "Types:\n"; for (size_t i = 0; i < vec.size(); ++i) { char buf[32]; std::snprintf(buf, sizeof(buf), "Type %lu:\n", i); out += buf; vec[i].format(vec, out); out += "\n\n"; } out += "Formatted type: "; type_id.format(out); out += "\n"; /*switch (type.GetBasicType()) { case eBasicTypeVoid: out = "void"; return; case eBasicTypeChar: case eBasicTypeSignedChar: out = "i8"; return; case eBasicTypeUnsignedChar: out = "u8"; return; case eBasicTypeWChar: case eBasicTypeSignedWChar: case eBasicTypeChar16: case eBasicTypeShort: out = "i16"; return; case eBasicTypeUnsignedWChar: case eBasicTypeUnsignedShort: out = "u16"; return; case eBasicTypeChar32: case eBasicTypeInt: out = "i32"; return; case eBasicTypeUnsignedInt: out = "u32"; return; case eBasicTypeLong: // TODO: decide based on target platform out = "i32"; return; case eBasicTypeUnsignedLong: // TODO: decide based on target platform out = "u32"; return; case eBasicTypeLongLong: out = "i64"; return; case eBasicTypeUnsignedLongLong: out = "u64"; return; case eBasicTypeInt128: out = "i128"; return; case eBasicTypeUnsignedInt128: out = "u128"; return; case eBasicTypeBool: out = "bool"; return; case eBasicTypeFloat: out = "f32"; return; case eBasicTypeDouble: out = "f64"; return; case eBasicTypeLongDouble: out = "f128"; return; case eBasicTypeNullPtr: // TODO: target platform dependent out = "u64"; return; // unhandled case eBasicTypeHalf: case eBasicTypeFloatComplex: case eBasicTypeDoubleComplex: case eBasicTypeLongDoubleComplex: case eBasicTypeObjCID: case eBasicTypeObjCClass: case eBasicTypeObjCSel: case eBasicTypeOther: out = ""; return; case eBasicTypeInvalid: { if (type.IsPointerType()) { auto inner_type = type.GetPointeeType(); std::string tmp = {}; format_type(inner_type, tmp); out = "ptr<"; out += tmp; out += ">"; } else if (type.IsReferenceType()) { auto inner_type = type.GetDereferencedType(); std::string tmp = {}; format_type(inner_type, tmp); out = "ref<"; out += tmp; out += ">"; } else { auto vec = std::vector{}; parse_type_inner(vec, type); dbgui::data::type_info::TypeInfo::format(vec, 0, out); return; } } default: assert(0); exit(1); }*/ } #if 0 namespace llvm { int DisableABIBreakingChecks; } #endif void LLDBBackend::dump_variables() { using namespace lldb; auto thread = _process->GetSelectedThread(); auto frame = thread.GetSelectedFrame(); if (!thread.IsValid() || !frame.IsValid()) { return; } printf("Dumping Variables...\n"); if (!_last_frame || !_last_frame->IsEqual(frame)) { printf("Frame changed!\n"); _last_frame = frame; } auto stream = SBStream{}; for (auto &[var, touched] : _locals) { touched = false; stream.Clear(); var.GetExpressionPath(stream); auto name = var.GetName(); if (!name) { name = ""; } printf("Var %.*s ('%s'): %lu\n", static_cast(stream.GetSize()), stream.GetData(), name, var.GetID()); stream.Clear(); var.GetDescription(stream); printf(" -> Desc: %.*s\n", static_cast(stream.GetSize()), stream.GetData()); printf(" -> Valid: %u\n", var.IsValid()); printf(" -> InScope: %u\n", var.IsInScope()); printf(" -> ValueDidChange: %u\n", var.GetValueDidChange()); printf(" -> IsSynthetic: %u\n", var.IsSynthetic()); printf(" -> IsDynamic: %u\n", var.IsDynamic()); printf(" -> Value: %s\n", var.GetValue()); auto loc = var.GetLocation(); if (!loc) { loc = ""; } printf(" -> Location: %s\n", loc); /*auto static_var = var.GetStaticValue(); printf(" -> Static: '%s' %lu\n", static_var.GetName(), static_var.GetID()); printf(" -> Valid: %u\n", static_var.IsValid()); printf(" -> InScope: %u\n", static_var.IsInScope()); printf(" -> ValueDidChange: %u\n", static_var.GetValueDidChange()); printf(" -> IsSynthetic: %u\n", static_var.IsSynthetic()); printf(" -> IsDynamic: %u\n", static_var.IsDynamic()); printf(" -> Value: %s\n", static_var.GetValue());*/ } // 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, 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); if (!var.IsValid()) { continue; } /*for (auto &[prev_var, touched] : _locals) { if (prev_var.) }*/ stream.Clear(); var.GetDescription(stream); printf("Var %u: %.*s\n", i, static_cast(stream.GetSize()), stream.GetData()); stream.Clear(); var.GetExpressionPath(stream); printf("ExprPath: %.*s\n", static_cast(stream.GetSize()), stream.GetData()); if (!tmp_val && std::string_view{var.GetName()} == "tmp") { tmp_val = var; } auto var_frame = var.GetFrame(); stream.Clear(); var_frame.GetDescription(stream); printf(" -> VarFrame: %.*s\n", static_cast(stream.GetSize()), stream.GetData()); auto decl = var.GetDeclaration(); stream.Clear(); 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() ?: ""; printf(" -> TypeName: '%s', DisplayName: '%s'\n", type_name, type_display_name); auto type = var.GetType(); auto type_filter = var.GetTypeFilter(); stream.Clear(); type.GetDescription(stream, eDescriptionLevelFull); printf(" -> TypeDesc: %.*s\n", static_cast(stream.GetSize()), stream.GetData()); stream.Clear(); type_filter.GetDescription(stream, eDescriptionLevelFull); printf(" -> TypeFilter: %.*s\n", static_cast(stream.GetSize()), stream.GetData()); stream.Clear(); var.GetTypeFormat().GetDescription(stream, eDescriptionLevelFull); printf(" -> TypeFormat: %.*s\n", static_cast(stream.GetSize()), stream.GetData()); stream.Clear(); 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 for (uint32_t i = 0; i < num_fields; ++i) { auto field = type.GetFieldAtIndex(i); stream.Clear(); field.GetType().GetDescription(stream, eDescriptionLevelFull); printf(" -> Field %u: '%s': %.*s\n", i, field.GetName(), static_cast(stream.GetSize()), stream.GetData()); } //std::string fmt{}; //format_type(type, fmt); //printf(" -> Custom Type Info: %s\n", fmt.c_str()); } } bool LLDBBackend::step_into(bool source_step) { std::lock_guard g{_data_lock}; if (!_process) { return false; } auto thread = _process->GetSelectedThread(); if (!thread.IsValid()) { return false; } // TODO: figure out what the runmodes mean if (source_step) { thread.StepInto(); } else { thread.StepInstruction(false); } return false; } bool LLDBBackend::step_over(bool source_step) { std::lock_guard g{_data_lock}; if (!_process) { return false; } auto thread = _process->GetSelectedThread(); if (!thread.IsValid()) { return false; } if (source_step) { thread.StepOver(); } else { thread.StepInstruction(true); } return false; } bool LLDBBackend::step_out() { std::lock_guard g{_data_lock}; if (!_process) { return false; } auto thread = _process->GetSelectedThread(); if (!thread.IsValid()) { return false; } thread.StepOut(); // TODO: allow step out of frame? return false; } void LLDBBackend::cont() { std::lock_guard g{_data_lock}; if (!_process) { return; } // TODO: error handling _process->Continue(); } void LLDBBackend::pause() { std::lock_guard g{_data_lock}; if (!_process) { return; } // TODO: error handling _process->Stop(); } void LLDBBackend::select_thread(uint16_t idx) { std::lock_guard g{_data_lock}; if (!_process) { return; } if (_threads.size() <= idx) { return; } printf("Set selected thread: %lu (%u)", _threads[idx].id, idx); _process->SetSelectedThreadByID(_threads[idx].id); auto thread = _process->GetSelectedThread(); thread.SetSelectedFrame(0); this->check_thread_changes(); this->check_frame_changes(); // TODO: these can be a lot more selective this->check_data_changes(); } void LLDBBackend::select_frame(uint16_t idx) { std::lock_guard g{_data_lock}; if (!_process) { return; } if (_frames.size() <= idx) { return; } auto thread = _process->GetSelectedThread(); uint64_t id = thread.GetThreadID(); printf("ID: %lu\n", id); thread.SetSelectedFrame(_frames[idx].lldb_idx); uint64_t id2 = thread.GetThreadID(); printf("ID2: %lu\n", id2); this->check_frame_changes(); // TODO: these can be a lot more selective this->check_data_changes(); } void LLDBBackend::check_reg_changes() { auto thread = _process->GetSelectedThread(); if (!thread.IsValid()) { // TODO: try to find another thread to select return; } // TODO: figure out if 0 is always the lowest one? auto frame = thread.GetFrameAtIndex(0); const auto regs = frame.GetRegisters(); uint8_t tmp_buf[64]; for (size_t set_idx = 0; set_idx < _reg_sets.size(); ++set_idx) { auto &set = _reg_sets[set_idx]; auto lldb_set = regs.GetFirstValueByName(set.set_name.c_str()); if (!lldb_set.IsValid()) { printf("Failed to get set %s for frame\n", set.set_name.c_str()); continue; } auto change_info = BackToFront::RegsChanged{.set_idx = set_idx}; assert(set.names.size() == set.values.size()); for (size_t i = 0; i < set.names.size(); ++i) { auto val = lldb_set.GetChildMemberWithName(set.names[i]); if (!val.IsValid()) { printf("Failed to get %s for frame\n", set.names[i]); continue; } auto data = val.GetData(); const auto len = data.GetByteSize(); if (len != set.values[i].size()) { set.values[i].resize(len); // TODO: error handling auto error = lldb::SBError{}; data.ReadRawData(error, 0, set.values[i].data(), len); change_info.changes.emplace_back(std::make_pair(i, set.values[i])); continue; } if (len > sizeof(tmp_buf)) { printf("Register too large\n"); exit(1); } // TODO: error handling auto error = lldb::SBError{}; data.ReadRawData(error, 0, tmp_buf, len); if (std::memcmp(tmp_buf, set.values[i].data(), len)) { std::copy(tmp_buf, tmp_buf + len, set.values[i].begin()); change_info.changes.emplace_back(std::make_pair(i, set.values[i])); } } if (!change_info.changes.empty()) { this->send_reg_change(std::move(change_info)); } } } void LLDBBackend::check_thread_changes() { using namespace lldb; const auto len = _process->GetNumThreads(); if (len == 0) { if (!_threads.empty()) { for (size_t i = 0; i < _threads.size(); ++i) { this->send_thread_removed(BackToFront::ThreadRemoved{i}); } _threads.clear(); this->send_selected_thread_changed(0); } return; } // TODO: work with SBThread::GetIndexID instead of relying // on the index order? uint16_t cache_idx = 0; auto sel_thread = _process->GetSelectedThread(); for (uint32_t i = 0; i < len; ++i) { auto thread = _process->GetThreadAtIndex(i); if (!thread.IsValid()) { continue; } if (thread.GetThreadID() == sel_thread.GetThreadID()) { if (_selected_thread != cache_idx) { _selected_thread = cache_idx; this->send_selected_thread_changed(cache_idx); // reset selected frame if (_selected_frame != 0) { this->send_selected_frame_changed(0); thread.SetSelectedFrame(0); } _selected_frame = 0; } } // this causes lldb to compute a proper stacktrace apparently thread.GetCurrentExceptionBacktrace(); auto frame = thread.GetFrameAtIndex(0); if (_threads.size() <= cache_idx) { auto thread_name = std::string{}; auto lldb_thread_name = thread.GetName(); if (lldb_thread_name) { // it is not valid to construct a string from a nullptr? thread_name = lldb_thread_name; } auto frame_name = std::string{}; if (auto frame_str = frame.GetDisplayFunctionName(); frame_str) { frame_name = frame_str; } _threads.push_back(Thread{.id = thread.GetThreadID(), .ip = frame.GetPC(), .name = thread_name, .cur_display_fn = frame_name}); auto &info = _threads.back(); switch (thread.GetStopReason()) { case eStopReasonBreakpoint: info.stop_reason = ThreadStopReason::breakpoint; info.stop_extra = thread.GetStopReasonDataAtIndex(0); break; case eStopReasonWatchpoint: info.stop_reason = ThreadStopReason::watchpoint; info.stop_extra = thread.GetStopReasonDataAtIndex(0); break; case eStopReasonSignal: info.stop_reason = ThreadStopReason::signal; info.stop_extra = thread.GetStopReasonDataAtIndex(0); break; case eStopReasonException: info.stop_reason = ThreadStopReason::exception; break; default: info.stop_reason = ThreadStopReason::unknown; break; } this->send_thread_change(BackToFront::ThreadChange{ .id = info.id, .ip = info.ip, .stop_extra = info.stop_extra, .idx = cache_idx, .stop_reason = info.stop_reason, .name = info.name, .cur_display_fn = info.cur_display_fn, }); cache_idx++; continue; } auto &cache_entry = _threads[cache_idx]; auto change_entry = BackToFront::ThreadChange{}; change_entry.idx = cache_idx; auto send_change = false; if (thread.GetThreadID() != cache_entry.id) { cache_entry.id = thread.GetThreadID(); send_change = true; } if (frame.GetPC() != cache_entry.ip) { cache_entry.ip = frame.GetPC(); send_change = true; } auto name = std::string_view{}; if (auto lldb_name = thread.GetName(); lldb_name) { name = lldb_name; } if (name != cache_entry.name) { change_entry.name = name; send_change = true; } auto display_name = std::string_view{}; if (auto lldb_name = frame.GetDisplayFunctionName(); lldb_name) { display_name = lldb_name; } if (display_name != cache_entry.cur_display_fn) { change_entry.cur_display_fn = display_name; send_change = true; } auto stop_reason = cache_entry.stop_reason; auto stop_extra = cache_entry.stop_extra; switch (thread.GetStopReason()) { case eStopReasonBreakpoint: stop_reason = ThreadStopReason::breakpoint; stop_extra = thread.GetStopReasonDataAtIndex(0); break; case eStopReasonWatchpoint: stop_reason = ThreadStopReason::watchpoint; stop_extra = thread.GetStopReasonDataAtIndex(0); break; case eStopReasonSignal: stop_reason = ThreadStopReason::signal; stop_extra = thread.GetStopReasonDataAtIndex(0); break; case eStopReasonException: stop_reason = ThreadStopReason::exception; break; default: stop_reason = ThreadStopReason::unknown; break; } if (stop_reason != cache_entry.stop_reason || stop_extra != cache_entry.stop_extra) { send_change = true; cache_entry.stop_reason = stop_reason; cache_entry.stop_extra = stop_extra; } if (send_change) { change_entry.id = cache_entry.id; change_entry.ip = cache_entry.ip; change_entry.stop_reason = cache_entry.stop_reason; change_entry.stop_extra = cache_entry.stop_extra; this->send_thread_change(std::move(change_entry)); } cache_idx++; } if (_threads.size() > cache_idx) { // here cache_idx == num_threads size_t rem_count = _threads.size() - cache_idx; for (size_t i = 0; i < rem_count; ++i) { this->send_thread_removed( BackToFront::ThreadRemoved{_threads.size() - 1}); _threads.pop_back(); } } } void LLDBBackend::check_frame_changes() { using namespace lldb; auto thread = _process->GetSelectedThread(); if (!thread.IsValid()) { // TODO: cleanup printf("Current thread not valid\n"); return; } uint64_t id = thread.GetThreadID(); printf("ID3: %lu\n", id); size_t len = thread.GetNumFrames(); uint16_t cache_idx = 0; size_t selected_idx = 0; printf("Num frames: %lu\n", len); for (size_t i = 0; i < len; ++i) { auto frame = thread.GetFrameAtIndex(i); if (!frame.IsValid()) { printf("Frame %lu invalid\n", i); continue; } printf("Looking at frame %lu\n", i); if (thread.GetSelectedFrame().IsEqual(frame)) { selected_idx = cache_idx; } auto ip = frame.GetPC(); auto name = std::string_view{}; if (auto lldb_name = frame.GetDisplayFunctionName(); lldb_name) { // dont construct from nullptr name = lldb_name; } printf(" IP: %lu, Name: %.*s\n", ip, static_cast(name.size()), name.data()); if (_frames.size() <= cache_idx) { _frames.push_back(Frame{ .ip = ip, .lldb_idx = i, .display_name = std::string{name}, }); printf("Frame %u (%lu) is new\n", cache_idx, i); this->send_frame_changed(BackToFront::FrameChanged{ .ip = ip, .idx = cache_idx, .display_name = std::string{name}}); cache_idx++; continue; } auto &cache_entry = _frames[cache_idx]; printf(" Cached IP: %lu, Name: %s\n", cache_entry.ip, cache_entry.display_name.c_str()); if (cache_entry.ip != ip || cache_entry.display_name != name) { printf("Frame %u (%lu) changed\n", cache_idx, i); this->send_frame_changed(BackToFront::FrameChanged{ .ip = ip, .idx = cache_idx, .display_name = std::string{name}}); cache_entry.ip = ip; if (cache_entry.display_name != name) { cache_entry.display_name = name; } } if (cache_entry.lldb_idx != i) { cache_entry.lldb_idx = i; } cache_idx++; } if (_selected_frame != selected_idx) { this->send_selected_frame_changed(selected_idx); _selected_frame = selected_idx; } printf("cache_idx: %u frame_size: %lu\n", cache_idx, _frames.size()); if (_frames.size() > cache_idx) { // here cache_idx == num_frames size_t rem_count = _frames.size() - cache_idx; for (size_t i = 0; i < rem_count; ++i) { this->send_frame_removed(BackToFront::FrameRemoved{_frames.size() - 1}); _frames.pop_back(); } } } void LLDBBackend::add_data_node(const data::source::Node &node) { std::lock_guard g{_data_lock}; if (_data_dag.nodes.contains(node.id)) { printf("Double ID in DAG\n"); exit(1); } _data_nodes.push_back(node); _data_dag.add_node(node.id); // add child nodes // TODO: make this somehow automatic switch (node.type) { using enum data::source::Node::Type; case source: // nothing to do rn break; case disassemble: { auto src_id = std::get(node.data).src_id; if (!_data_dag.nodes.contains(src_id)) { printf("Invalid add sequence\n"); exit(1); } _data_dag.add_edge(node.id, src_id); break; } case line_entry: { auto src_id = std::get(node.data).src_id; if (!_data_dag.nodes.contains(src_id)) { printf("Invalid add sequence\n"); exit(1); } _data_dag.add_edge(node.id, src_id); break; } } _dag_linear_valid = false; // TODO: just insert a dummy event? if (_state == TargetState::paused) { this->check_data_changes(); } } void LLDBBackend::remove_data_node(size_t id) { std::lock_guard g{_data_lock}; _data_dag.remove_node(id); auto it = std::find_if(_data_nodes.begin(), _data_nodes.end(), [id](const auto &el) { return el.id == id; }); if (it == _data_nodes.end()) { printf("Could not find node for DAG element\n"); exit(1); } _data_nodes.erase(it); _dag_linear_valid = false; auto res_it = _data_src_id_to_res_idx.find(id); if (res_it == _data_src_id_to_res_idx.end()) { return; } if (!_data_res[res_it->second] || _data_res[res_it->second]->no_delete_on_src_delete) { return; } std::vector to_delete{}; to_delete.push_back(res_it->second); 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]; if (_data_res[child_idx]->no_delete_on_src_delete) { continue; } 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] = {}; } } void LLDBBackend::check_data_changes() { if (!_dag_linear_valid) { _dag_linear.clear(); _data_dag.linearize(_dag_linear); _dag_linear_valid = true; } for (auto &entry : _var_cache) { entry.used = false; } for (auto id : _dag_linear) { const auto &node_it = std::find_if(_data_nodes.begin(), _data_nodes.end(), [id](const auto &el) { return el.id == id; }); if (node_it == _data_nodes.end()) { printf("Could not find node for DAG element\n"); exit(1); } auto res_vec = std::vector{}; auto src_id_mapping = this->calc_data_res(*node_it, res_vec); if (src_id_mapping) { auto it = _data_src_id_to_res_idx.find(src_id_mapping->second); if (it != _data_src_id_to_res_idx.end()) { it->second = src_id_mapping->first; } else { _data_src_id_to_res_idx.emplace(src_id_mapping->second, src_id_mapping->first); } } if (!res_vec.empty()) { // TODO: queue and send at once to prevent UI lag? this->send_data_result( {.nodes = std::move(res_vec), .src_node_id = src_id_mapping}); } } this->clear_unused_vars_from_cache(); } // TODO: call node src_node? std::optional> LLDBBackend::calc_data_res(const data::source::Node &node, std::vector &data_res) { const auto find_node_for_src_id = [this](size_t src_id) -> std::optional { for (uint16_t i = 0; i < this->_data_res.size(); ++i) { if (!this->_data_res[i]) { continue; } if (this->_data_res[i]->src_id == src_id) { return i; } } return {}; }; const auto get_free_res_slot = [this]() -> uint16_t { 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; }; const auto check_single_res_changed = [this, node, &data_res]( data::result::Node &&res) -> std::optional> { auto val_changed = true; if (this->_data_res.size() > res.idx && this->_data_res[res.idx]) { auto &cached = *this->_data_res[res.idx]; if (cached.src_id == node.id) { //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.children == res.children) { val_changed = false; } } //} } } if (!val_changed) { return {}; } if (this->_data_res.size() <= res.idx) { this->_data_res.resize(res.idx + 1); } this->_data_res[res.idx] = CachedDataRes{.node = res, .src_id = node.id}; auto idx = res.idx; data_res.push_back(std::move(res)); return std::make_pair(idx, node.id); }; switch (node.type) { using enum data::source::Node::Type; case source: { const auto &src_data = std::get(node.data); switch (src_data.type) { using enum data::source::Source::Type; case reg: { 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(); } auto res = data::result::Node{.idx = cache_idx, .type_id = data::type_info::TypeID::none(), .success = false}; { const auto &info = std::get(src_data.data); if (info.set < _reg_sets.size() && info.idx < _reg_sets[info.set].values.size()) { res.success = true; res.type_id = data::type_info::TypeID::u64(); // TODO: these indices *could* (very not likely) be incorrect // TODO: for now, pretend every register is u64 res.data = *reinterpret_cast( _reg_sets[info.set].values[info.idx].data()); } } return check_single_res_changed(std::move(res)); } case frame_ip: { 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(); } auto thread = _process->GetSelectedThread(); auto frame = thread.GetSelectedFrame(); if (!thread.IsValid() || !frame.IsValid()) { return check_single_res_changed(data::result::Node{ .idx = cache_idx, .type_id = data::type_info::TypeID::none(), .success = false, }); } uint64_t pc = frame.GetPC(); return check_single_res_changed(data::result::Node{ .idx = cache_idx, .type_id = data::type_info::TypeID::u64(), .success = true, .data = pc, }); } case variable: { 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(); // TODO: this will probably fail if cache_idx == 0 _data_res[cache_idx] = CachedDataRes{.src_id = node.id}; } // TODO const auto &info = std::get(src_data.data); auto res_node = data::result::Node{ .idx = cache_idx, .type_id = data::type_info::TypeID::none(), .success = false, }; auto thread = _process->GetSelectedThread(); auto frame = thread.GetSelectedFrame(); if (!frame.IsValid()) { return check_single_res_changed(std::move(res_node)); } auto var = frame.GetValueForVariablePath(info.expr_path.c_str(), lldb::eDynamicDontRunTarget); if (!var.IsValid() || !var.IsInScope()) { return check_single_res_changed(std::move(res_node)); } auto res_idx = this->build_nodes_for_var(var, data_res); if (!res_idx) { return check_single_res_changed(std::move(res_node)); } printf("Got res for %s: %u (%u)\n", info.expr_path.c_str(), *res_idx, _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)); } } break; } case disassemble: { 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(); } size_t addr_id = std::get(node.data).src_id; uint16_t addr_idx = *find_node_for_src_id(addr_id); if (!_data_res[addr_idx] || !_data_res[addr_idx]->node.success) { return check_single_res_changed(data::result::Node{ .idx = cache_idx, .type_id = data::type_info::TypeID::none(), .success = false, }); } // TODO: for now only accept u64 if (_data_res[addr_idx]->node.type_id.type != data::type_info::Type::u64) { return check_single_res_changed(data::result::Node{ .idx = cache_idx, .type_id = data::type_info::TypeID::none(), .success = false, }); } const auto pc = std::get(_data_res[addr_idx]->node.data); auto sc = _target.ResolveSymbolContextForAddress( SBAddress{pc, _target}, eSymbolContextFunction | eSymbolContextSymbol); auto le = sc.GetLineEntry(); if (le.IsValid()) { SBStream stream{}; le.GetDescription(stream); printf("LE: %.*s\n", static_cast(stream.GetSize()), stream.GetData()); } uint64_t start, end; if (sc.GetFunction().IsValid()) { auto fn = sc.GetFunction(); start = fn.GetStartAddress().GetLoadAddress(_target); end = fn.GetEndAddress().GetLoadAddress(_target); } else if (sc.GetSymbol().IsValid() && sc.GetSymbol().GetStartAddress().IsValid()) { auto sym = sc.GetSymbol(); start = sym.GetStartAddress().GetLoadAddress(_target); end = sym.GetEndAddress().GetLoadAddress(_target); } else { start = pc; end = start + 0x100; } auto buf = std::vector{}; buf.resize(end - start); auto err = SBError{}; _target.ReadMemory(SBAddress{start, _target}, buf.data(), buf.size(), err); auto inst_list = _target.GetInstructionsWithFlavor( start, "intel", buf.data(), buf.size()); // TODO: for now the instlist is serialized in a custom format // use a type structure for it later? // it's basically an array of // struct Inst { // uint64_t addr; // uint8_t mnem_len; // uint8_t op_len; // uint8_t comment_len; // uint8_t inst_len; // char mnem[]; // char op[]; // char comment[]; // }; std::vector out{}; for (size_t i = 0; i < inst_list.GetSize(); ++i) { auto inst = inst_list.GetInstructionAtIndex(i); auto mnem = std::string_view{}; auto op = std::string_view{}; auto comm = std::string_view{}; if (auto mnem_str = inst.GetMnemonic(_target); mnem_str) { mnem = mnem_str; } if (auto op_str = inst.GetOperands(_target); op_str) { op = op_str; } if (auto comm_str = inst.GetComment(_target); comm_str) { comm = comm_str; } auto addr = inst.GetAddress().GetLoadAddress(_target); const auto len = inst.GetByteSize(); //SBStream stream{}; //inst.GetDescription(stream); //printf("Got inst: %.*s\n", stream.GetSize(), stream.GetData()); if (comm.size() > 255) { // TODO: better handling comm = comm.substr(0, 255); } if (mnem.size() > 255 || op.size() > 255 || comm.size() > 255) { printf("Instruction length limits exceeded:\n"); auto stream = SBStream{}; inst.GetDescription(stream); printf("%.*s\n", static_cast(stream.GetSize()), stream.GetData()); exit(1); } size_t insert_idx = out.size(); out.resize(out.size() + 8 + 4 + mnem.size() + op.size() + comm.size()); *reinterpret_cast(&out[insert_idx]) = addr; *reinterpret_cast(&out[insert_idx + 8]) = mnem.size(); *reinterpret_cast(&out[insert_idx + 9]) = op.size(); *reinterpret_cast(&out[insert_idx + 10]) = comm.size(); *reinterpret_cast(&out[insert_idx + 11]) = len; insert_idx += 12; std::copy(mnem.begin(), mnem.end(), out.begin() + insert_idx); insert_idx += mnem.size(); std::copy(op.begin(), op.end(), out.begin() + insert_idx); insert_idx += op.size(); std::copy(comm.begin(), comm.end(), out.begin() + insert_idx); } return check_single_res_changed(data::result::Node{ .idx = cache_idx, .type_id = data::type_info::TypeID::custom(), .success = true, .data = std::move(out), }); } case line_entry: { 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(); } size_t addr_id = std::get(node.data).src_id; uint16_t addr_idx = *find_node_for_src_id(addr_id); if (!_data_res[addr_idx] || !_data_res[addr_idx]->node.success) { return check_single_res_changed(data::result::Node{ .idx = cache_idx, .type_id = data::type_info::TypeID::none(), .success = false, }); } // TODO: for now only accept u64 if (_data_res[addr_idx]->node.type_id.type != data::type_info::Type::u64) { return check_single_res_changed(data::result::Node{ .idx = cache_idx, .type_id = data::type_info::TypeID::none(), .success = false, }); } const auto addr = std::get(_data_res[addr_idx]->node.data); auto sc = _target.ResolveSymbolContextForAddress(SBAddress{addr, _target}, eSymbolContextLineEntry); auto le = sc.GetLineEntry(); auto file_spec = le.GetFileSpec(); /*auto stream = SBStream{}; sc.GetDescription(stream); printf("SC DBG: %.*s\n", static_cast(stream.GetSize()), stream.GetData()); stream.Clear(); le.GetDescription(stream); printf("LE DBG: %.*s\n", static_cast(stream.GetSize()), stream.GetData()); stream.Clear(); file_spec.GetDescription(stream); printf("FS DBG: %.*s\n", static_cast(stream.GetSize()), stream.GetData());*/ if (!le.IsValid() || !file_spec.IsValid()) { return check_single_res_changed(data::result::Node{ .idx = cache_idx, .type_id = data::type_info::TypeID::none(), .success = false, }); } // TODO: for now the instlist is serialized in a custom format // use a type structure for it later? // it's basically an array of // struct LineEntry { // uint32_t line_no; // uint32_t name_len; // char file_name[]; // }; // we can't know how long the path actually is without at least calling GetPath twice // because it will return the smaller of the buffer size or actual path len char path_buf[512]; uint32_t path_len = file_spec.GetPath(path_buf, sizeof(path_buf)); printf("Calculated LE: %.*s\n", static_cast(path_len), path_buf); std::vector out; out.resize(8 + path_len); *reinterpret_cast(out.data()) = le.GetLine(); *reinterpret_cast(out.data() + 4) = path_len; std::memcpy(out.data() + 8, path_buf, path_len); return check_single_res_changed(data::result::Node{ .idx = cache_idx, .type_id = data::type_info::TypeID::custom(), .success = true, .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}; if (!_process) { return; } if (std::find_if(_breakpoints.begin(), _breakpoints.end(), [id](const auto &el) { return el.id == id; }) != _breakpoints.end()) { printf("Trying to register same breakpoint id\n"); exit(1); } auto bp = _target.BreakpointCreateByAddress(addr); _breakpoints.push_back(Breakpoint{.id = id, .lldb_id = bp.GetID()}); } bool LLDBBackend::add_breakpoint(const char *file, uint32_t line, size_t id) { using namespace lldb; std::lock_guard g{_data_lock}; if (!_process) { return false; } if (std::find_if(_breakpoints.begin(), _breakpoints.end(), [id](const auto &el) { return el.id == id; }) != _breakpoints.end()) { printf("Trying to register same breakpoint id\n"); exit(1); } // TODO: try false? auto file_spec = SBFileSpec{file, true}; auto mod_list = SBFileSpecList{}; auto bp = _target.BreakpointCreateByLocation(file_spec, line, 0, 0, mod_list, true); if (!bp.IsValid()) { printf("BP not valid\n"); return false; } /*auto num = bp.GetNumLocations(); auto res_num = bp.GetNumResolvedLocations(); printf("Num: %lu, Res: %lu\n", num, res_num); auto stream = SBStream{}; for (size_t i = 0; i < num; ++i) { stream.Clear(); auto loc = bp.GetLocationAtIndex(i); loc.GetDescription(stream, eDescriptionLevelVerbose); printf("Loc %lu: %.*s\n", i, static_cast(stream.GetSize()), stream.GetData()); }*/ _breakpoints.push_back(Breakpoint{.id = id, .lldb_id = bp.GetID()}); return true; } void LLDBBackend::remove_breakpoint(size_t id) { std::lock_guard g{_data_lock}; if (!_process) { return; } auto it = std::find_if(_breakpoints.begin(), _breakpoints.end(), [id](const auto &el) { return el.id == id; }); if (it == _breakpoints.end()) { printf("Trying to delete nonexistant breakpoint\n"); exit(1); } // TODO: error handling? _target.BreakpointDelete(it->lldb_id); // TODO: this should check if there are no pending events in the debugger event queue // to make sure we did not hit the breakpoint before deleting it // maybe really add like a custom event type to send to the event queue? // afterwards it should send a bp deleted msg so the UI can sync this, too _breakpoints.erase(it); } /* Reg output for x64 Got register set General Purpose Registers Got register rax (unsigned long) with value: 0 Got register rbx (unsigned long) with value: 0 Got register rcx (unsigned long) with value: 0 Got register rdx (unsigned long) with value: 0 Got register rdi (unsigned long) with value: 0 Got register rsi (unsigned long) with value: 0 Got register rbp (unsigned long) with value: 0 Got register rsp (unsigned long) with value: 7FFF30F924E0 Got register r8 (unsigned long) with value: 0 Got register r9 (unsigned long) with value: 0 Got register r10 (unsigned long) with value: 0 Got register r11 (unsigned long) with value: 0 Got register r12 (unsigned long) with value: 0 Got register r13 (unsigned long) with value: 0 Got register r14 (unsigned long) with value: 0 Got register r15 (unsigned long) with value: 0 Got register rip (unsigned long) with value: 7FD5ABB9B3B0 Got register rflags (unsigned long) with value: 200 Got register cs (unsigned long) with value: 33 Got register fs (unsigned long) with value: 0 Got register gs (unsigned long) with value: 0 Got register ss (unsigned long) with value: 2B Got register ds (unsigned long) with value: 0 Got register es (unsigned long) with value: 0 Got register eax (unsigned int) with value: 0 Got register ebx (unsigned int) with value: 0 Got register ecx (unsigned int) with value: 0 Got register edx (unsigned int) with value: 0 Got register edi (unsigned int) with value: 0 Got register esi (unsigned int) with value: 0 Got register ebp (unsigned int) with value: 0 Got register esp (unsigned int) with value: 30F924E0 Got register r8d (unsigned int) with value: 0 Got register r9d (unsigned int) with value: 0 Got register r10d (unsigned int) with value: 0 Got register r11d (unsigned int) with value: 0 Got register r12d (unsigned int) with value: 0 Got register r13d (unsigned int) with value: 0 Got register r14d (unsigned int) with value: 0 Got register r15d (unsigned int) with value: 0 Got register ax (unsigned short) with value: 0 Got register bx (unsigned short) with value: 0 Got register cx (unsigned short) with value: 0 Got register dx (unsigned short) with value: 0 Got register di (unsigned short) with value: 0 Got register si (unsigned short) with value: 0 Got register bp (unsigned short) with value: 0 Got register sp (unsigned short) with value: 24E0 Got register r8w (unsigned short) with value: 0 Got register r9w (unsigned short) with value: 0 Got register r10w (unsigned short) with value: 0 Got register r11w (unsigned short) with value: 0 Got register r12w (unsigned short) with value: 0 Got register r13w (unsigned short) with value: 0 Got register r14w (unsigned short) with value: 0 Got register r15w (unsigned short) with value: 0 Got register ah (unsigned char) with value: 0 Got register bh (unsigned char) with value: 0 Got register ch (unsigned char) with value: 0 Got register dh (unsigned char) with value: 0 Got register al (unsigned char) with value: 0 Got register bl (unsigned char) with value: 0 Got register cl (unsigned char) with value: 0 Got register dl (unsigned char) with value: 0 Got register dil (unsigned char) with value: 0 Got register sil (unsigned char) with value: 0 Got register bpl (unsigned char) with value: 0 Got register spl (unsigned char) with value: E0 Got register r8l (unsigned char) with value: 0 Got register r9l (unsigned char) with value: 0 Got register r10l (unsigned char) with value: 0 Got register r11l (unsigned char) with value: 0 Got register r12l (unsigned char) with value: 0 Got register r13l (unsigned char) with value: 0 Got register r14l (unsigned char) with value: 0 Got register r15l (unsigned char) with value: 0 Got register set Floating Point Registers Got register fctrl (unsigned short) with value: 37F Got register fstat (unsigned short) with value: 0 Got register ftag (unsigned short) with value: FFFF Got register fop (unsigned short) with value: 0 Got register fiseg (unsigned int) with value: 0 Got register fioff (unsigned int) with value: 0 Got register fip (unsigned long) with value: 0 Got register foseg (unsigned int) with value: 0 Got register fooff (unsigned int) with value: 0 Got register fdp (unsigned long) with value: 0 Got register mxcsr (unsigned int) with value: 1F80 Got register mxcsrmask (unsigned int) with value: 2FFFF Got register st0 (unsigned char __attribute__((ext_vector_type(10)))) with value: 0 Got register st1 (unsigned char __attribute__((ext_vector_type(10)))) with value: 0 Got register st2 (unsigned char __attribute__((ext_vector_type(10)))) with value: 0 Got register st3 (unsigned char __attribute__((ext_vector_type(10)))) with value: 0 Got register st4 (unsigned char __attribute__((ext_vector_type(10)))) with value: 0 Got register st5 (unsigned char __attribute__((ext_vector_type(10)))) with value: 0 Got register st6 (unsigned char __attribute__((ext_vector_type(10)))) with value: 0 Got register st7 (unsigned char __attribute__((ext_vector_type(10)))) with value: 0 Got register mm0 (unsigned long) with value: 0 Got register mm1 (unsigned long) with value: 0 Got register mm2 (unsigned long) with value: 0 Got register mm3 (unsigned long) with value: 0 Got register mm4 (unsigned long) with value: 0 Got register mm5 (unsigned long) with value: 0 Got register mm6 (unsigned long) with value: 0 Got register mm7 (unsigned long) with value: 0 Got register xmm0 (unsigned char __attribute__((ext_vector_type(16)))) with value: 0 Got register xmm1 (unsigned char __attribute__((ext_vector_type(16)))) with value: 0 Got register xmm2 (unsigned char __attribute__((ext_vector_type(16)))) with value: 0 Got register xmm3 (unsigned char __attribute__((ext_vector_type(16)))) with value: 0 Got register xmm4 (unsigned char __attribute__((ext_vector_type(16)))) with value: 0 Got register xmm5 (unsigned char __attribute__((ext_vector_type(16)))) with value: 0 Got register xmm6 (unsigned char __attribute__((ext_vector_type(16)))) with value: 0 Got register xmm7 (unsigned char __attribute__((ext_vector_type(16)))) with value: 0 Got register xmm8 (unsigned char __attribute__((ext_vector_type(16)))) with value: 0 Got register xmm9 (unsigned char __attribute__((ext_vector_type(16)))) with value: 0 Got register xmm10 (unsigned char __attribute__((ext_vector_type(16)))) with value: 0 Got register xmm11 (unsigned char __attribute__((ext_vector_type(16)))) with value: 0 Got register xmm12 (unsigned char __attribute__((ext_vector_type(16)))) with value: 0 Got register xmm13 (unsigned char __attribute__((ext_vector_type(16)))) with value: 0 Got register xmm14 (unsigned char __attribute__((ext_vector_type(16)))) with value: 0 Got register xmm15 (unsigned char __attribute__((ext_vector_type(16)))) with value: 0 Got register set Advanced Vector Extensions Got register ymm0 (unsigned char __attribute__((ext_vector_type(32)))) with value: 0 Got register ymm1 (unsigned char __attribute__((ext_vector_type(32)))) with value: 0 Got register ymm2 (unsigned char __attribute__((ext_vector_type(32)))) with value: 0 Got register ymm3 (unsigned char __attribute__((ext_vector_type(32)))) with value: 0 Got register ymm4 (unsigned char __attribute__((ext_vector_type(32)))) with value: 0 Got register ymm5 (unsigned char __attribute__((ext_vector_type(32)))) with value: 0 Got register ymm6 (unsigned char __attribute__((ext_vector_type(32)))) with value: 0 Got register ymm7 (unsigned char __attribute__((ext_vector_type(32)))) with value: 0 Got register ymm8 (unsigned char __attribute__((ext_vector_type(32)))) with value: 0 Got register ymm9 (unsigned char __attribute__((ext_vector_type(32)))) with value: 0 Got register ymm10 (unsigned char __attribute__((ext_vector_type(32)))) with value: 0 Got register ymm11 (unsigned char __attribute__((ext_vector_type(32)))) with value: 0 Got register ymm12 (unsigned char __attribute__((ext_vector_type(32)))) with value: 0 Got register ymm13 (unsigned char __attribute__((ext_vector_type(32)))) with value: 0 Got register ymm14 (unsigned char __attribute__((ext_vector_type(32)))) with value: 0 Got register ymm15 (unsigned char __attribute__((ext_vector_type(32)))) with value: 0 */ namespace { using namespace dbgui; }