3567 lines
98 KiB
C++
3567 lines
98 KiB
C++
#include "lldb_backend.h"
|
|
#include <lldb/API/SBListener.h>
|
|
#include <lldb/API/SBEvent.h>
|
|
#include <lldb/API/SBStream.h>
|
|
#include <lldb/API/SBThread.h>
|
|
#include <lldb/API/SBFrame.h>
|
|
#include <lldb/API/SBValueList.h>
|
|
#include <lldb/API/SBValue.h>
|
|
#include <lldb/API/SBInstructionList.h>
|
|
#include <lldb/API/SBInstruction.h>
|
|
#include <lldb/API/SBBreakpoint.h>
|
|
#include <lldb/API/SBBreakpointLocation.h>
|
|
#include <lldb/API/SBTypeFilter.h>
|
|
#include <lldb/API/SBTypeFormat.h>
|
|
#include <lldb/API/SBDeclaration.h>
|
|
#include <lldb/API/SBTypeEnumMember.h>
|
|
#include <filesystem>
|
|
#include <array>
|
|
#include <cassert>
|
|
#include <cstring>
|
|
#include <algorithm>
|
|
|
|
#include <pthread.h>
|
|
|
|
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<LLDBBackend *>(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<int>(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<BackToFront::RegsChanged>{};
|
|
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<const char *, 24> 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<const char *, 14> x86_64_fp_special = {
|
|
"fctrl", "fstat", "ftag", "fop", "fiseg", "fioff", "fip",
|
|
"foseg", "fooff", "foseg", "fooff", "fdp", "mxcsr", "mxcsrmask"};
|
|
|
|
std::array<const char *, 8> x86_64_fp = {"st0", "st1", "st2", "st3",
|
|
"st4", "st5", "st6", "st7"};
|
|
|
|
std::array<const char *, 16> x86_64_xmm = {
|
|
"xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5", "xmm6", "xmm7",
|
|
"xmm8", "xmm9", "xmm10", "xmm11", "xmm12", "xmm13", "xmm14", "xmm15"};
|
|
|
|
std::array<const char *, 16> x86_64_ymm = {
|
|
"ymm0", "ymm1", "ymm2", "ymm3", "ymm4", "ymm5", "ymm6", "ymm7",
|
|
"ymm8", "ymm9", "ymm10", "ymm11", "ymm12", "ymm13", "ymm14", "ymm15"};
|
|
|
|
std::array<const char *, 16> 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<BackToFront::RegsChanged> ®_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<int>(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<int>(stream.GetSize()),
|
|
stream.GetData());
|
|
|
|
stream.Clear();
|
|
cu.GetDescription(stream);
|
|
printf("CU: %.*s\n", static_cast<int>(stream.GetSize()), stream.GetData());
|
|
|
|
stream.Clear();
|
|
block.GetDescription(stream);
|
|
printf("Block: %.*s\n", static_cast<int>(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<int>(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<int>(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<uint8_t>{};
|
|
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<int>(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<dbgui::data::type_info::TypeID>
|
|
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<dbgui::data::type_info::TypeInfo> &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<uint32_t>(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<type_info::MemberInfo>{}});
|
|
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 = "<none>";
|
|
}
|
|
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<type_info::MemberInfo>{}});
|
|
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 = "<none>";
|
|
}
|
|
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<type_info::TypeInfo>{};
|
|
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 = "<unk>"; 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<dbgui::data::type_info::TypeInfo>{};
|
|
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 = "<null>";
|
|
}
|
|
printf("Var %.*s ('%s'): %lu\n", static_cast<int>(stream.GetSize()),
|
|
stream.GetData(), name, var.GetID());
|
|
stream.Clear();
|
|
var.GetDescription(stream);
|
|
printf(" -> Desc: %.*s\n", static_cast<int>(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 = "<null>";
|
|
}
|
|
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<SBValue> 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<int>(stream.GetSize()),
|
|
stream.GetData());
|
|
|
|
stream.Clear();
|
|
var.GetExpressionPath(stream);
|
|
printf("ExprPath: %.*s\n", static_cast<int>(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<int>(stream.GetSize()),
|
|
stream.GetData());
|
|
|
|
auto decl = var.GetDeclaration();
|
|
stream.Clear();
|
|
decl.GetDescription(stream);
|
|
printf(" -> Declaration: %.*s\n", static_cast<int>(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<int>(stream.GetSize()),
|
|
stream.GetData());
|
|
|
|
stream.Clear();
|
|
type_filter.GetDescription(stream, eDescriptionLevelFull);
|
|
printf(" -> TypeFilter: %.*s\n", static_cast<int>(stream.GetSize()),
|
|
stream.GetData());
|
|
|
|
stream.Clear();
|
|
var.GetTypeFormat().GetDescription(stream, eDescriptionLevelFull);
|
|
printf(" -> TypeFormat: %.*s\n", static_cast<int>(stream.GetSize()),
|
|
stream.GetData());
|
|
|
|
stream.Clear();
|
|
type.GetDescription(stream, eDescriptionLevelVerbose);
|
|
printf(" -> Type: %.*s\n", static_cast<int>(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<int>(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<int>(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<data::source::Disassemble>(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<data::source::LineEntry>(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<uint16_t> 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<data::result::Node>{};
|
|
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<std::pair<uint16_t, size_t>>
|
|
LLDBBackend::calc_data_res(const data::source::Node &node,
|
|
std::vector<data::result::Node> &data_res)
|
|
{
|
|
const auto find_node_for_src_id =
|
|
[this](size_t src_id) -> std::optional<uint16_t> {
|
|
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<std::pair<uint16_t, size_t>> {
|
|
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<data::source::Source>(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<data::source::Source::Reg>(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<const uint64_t *>(
|
|
_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<data::source::Source::Variable>(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<data::source::Disassemble>(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<uint64_t>(_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<int>(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<uint8_t>{};
|
|
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<uint8_t> 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<int>(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<uint64_t *>(&out[insert_idx]) = addr;
|
|
*reinterpret_cast<uint8_t *>(&out[insert_idx + 8]) = mnem.size();
|
|
*reinterpret_cast<uint8_t *>(&out[insert_idx + 9]) = op.size();
|
|
*reinterpret_cast<uint8_t *>(&out[insert_idx + 10]) = comm.size();
|
|
*reinterpret_cast<uint8_t *>(&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<data::source::LineEntry>(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<uint64_t>(_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<int>(stream.GetSize()),
|
|
stream.GetData());
|
|
stream.Clear();
|
|
le.GetDescription(stream);
|
|
printf("LE DBG: %.*s\n", static_cast<int>(stream.GetSize()),
|
|
stream.GetData());
|
|
stream.Clear();
|
|
file_spec.GetDescription(stream);
|
|
printf("FS DBG: %.*s\n", static_cast<int>(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<int>(path_len), path_buf);
|
|
|
|
std::vector<uint8_t> out;
|
|
out.resize(8 + path_len);
|
|
*reinterpret_cast<uint32_t *>(out.data()) = le.GetLine();
|
|
*reinterpret_cast<uint32_t *>(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<std::pair<uint16_t, std::string>>{};
|
|
|
|
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 = "<null>";
|
|
}
|
|
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<uint16_t *>(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);
|
|
}
|
|
}
|
|
case read_cstr:
|
|
{
|
|
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<data::source::ReadAsCStr>(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,
|
|
});
|
|
}
|
|
|
|
if (_data_res[addr_idx]->node.type_id.type != data::type_info::Type::ptr)
|
|
{
|
|
return check_single_res_changed(data::result::Node{
|
|
.idx = cache_idx,
|
|
.type_id = data::type_info::TypeID::none(),
|
|
.success = false,
|
|
});
|
|
}
|
|
|
|
auto addr = std::get<uint64_t>(_data_res[addr_idx]->node.data);
|
|
std::vector<uint8_t> buf{};
|
|
|
|
char tmp_buf[256];
|
|
auto success = true;
|
|
while (true)
|
|
{
|
|
auto lldb_addr = _target.ResolveLoadAddress(addr);
|
|
auto err = SBError{};
|
|
err.Clear();
|
|
auto bytes_read =
|
|
_target.ReadMemory(lldb_addr, tmp_buf, sizeof(tmp_buf), err);
|
|
if (bytes_read == 0)
|
|
{
|
|
success = false;
|
|
break;
|
|
}
|
|
|
|
auto null_pos = std::find(tmp_buf, tmp_buf + bytes_read, '\0');
|
|
if (null_pos != tmp_buf + bytes_read)
|
|
{
|
|
size_t len = null_pos - tmp_buf;
|
|
auto old_size = buf.size();
|
|
buf.resize(old_size + len + 1);
|
|
std::copy(tmp_buf, null_pos + 1, buf.data() + old_size);
|
|
break;
|
|
}
|
|
|
|
if (bytes_read != sizeof(tmp_buf))
|
|
{
|
|
if (err.Fail())
|
|
{
|
|
printf("ReadAsCStr failed: %s\n", err.GetCString());
|
|
success = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
auto old_size = buf.size();
|
|
buf.resize(old_size + bytes_read);
|
|
std::copy(tmp_buf, tmp_buf + bytes_read, buf.data() + old_size);
|
|
|
|
addr += bytes_read;
|
|
}
|
|
|
|
if (!success)
|
|
{
|
|
return check_single_res_changed(data::result::Node{
|
|
.idx = cache_idx,
|
|
.type_id = data::type_info::TypeID::none(),
|
|
.success = false,
|
|
});
|
|
}
|
|
|
|
// TODO: embed the array size into the TypeID and then make this
|
|
// an i8 array type?
|
|
return check_single_res_changed(
|
|
data::result::Node{.idx = cache_idx,
|
|
.type_id = data::type_info::TypeID::custom(),
|
|
.success = true,
|
|
.data = std::move(buf)});
|
|
}
|
|
}
|
|
|
|
printf("Unhandled data type\n");
|
|
exit(1);
|
|
}
|
|
|
|
#if 0
|
|
std::pair<uint16_t, bool> 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<std::pair<uint16_t, std::string>>{};
|
|
auto nodes_to_send = std::vector<data::result::Node>{};
|
|
|
|
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 = "<null>";
|
|
}
|
|
|
|
// 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<uint16_t> 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 = "<null>";
|
|
}
|
|
|
|
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<uint64_t>(var_data.GetUnsignedInt8(err, 0));
|
|
break;
|
|
case u16:
|
|
res_node.data =
|
|
static_cast<uint64_t>(var_data.GetUnsignedInt16(err, 0));
|
|
break;
|
|
case u32:
|
|
res_node.data =
|
|
static_cast<uint64_t>(var_data.GetUnsignedInt32(err, 0));
|
|
break;
|
|
case u64:
|
|
res_node.data =
|
|
static_cast<uint64_t>(var_data.GetUnsignedInt64(err, 0));
|
|
break;
|
|
case i8:
|
|
res_node.data = static_cast<uint64_t>(var_data.GetSignedInt8(err, 0));
|
|
break;
|
|
case i16:
|
|
res_node.data = static_cast<uint64_t>(var_data.GetSignedInt16(err, 0));
|
|
break;
|
|
case i32:
|
|
res_node.data = static_cast<uint64_t>(var_data.GetSignedInt32(err, 0));
|
|
break;
|
|
case i64:
|
|
res_node.data = static_cast<uint64_t>(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<uint8_t>{};
|
|
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<size_t>::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<uint16_t *>(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<size_t>::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<uint32_t>(_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<uint32_t>(_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<MemberInfo>{}}));
|
|
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 = "<none>";
|
|
}
|
|
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<MemberInfo>{}}));
|
|
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 = "<none>";
|
|
}
|
|
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<TypeID>(_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<TypeID>(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<TypeID>(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 = "<none>";
|
|
}
|
|
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<dbgui::data::result::NodeIdx>
|
|
LLDBBackend::build_nodes_for_var(lldb::SBValue &val,
|
|
std::vector<data::result::Node> &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<result::NodeIdx> 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 *_var_cache[*cache_entry].cached_node;
|
|
}
|
|
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<data::result::Node> &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<size_t>::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<data::type_info::TypeID>(
|
|
_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<uint64_t>(var_data.GetUnsignedInt8(err, 0));
|
|
break;
|
|
case u16:
|
|
res_node.data = static_cast<uint64_t>(var_data.GetUnsignedInt16(err, 0));
|
|
break;
|
|
case u32:
|
|
res_node.data = static_cast<uint64_t>(var_data.GetUnsignedInt32(err, 0));
|
|
break;
|
|
case u64:
|
|
res_node.data = static_cast<uint64_t>(var_data.GetUnsignedInt64(err, 0));
|
|
break;
|
|
case i8:
|
|
res_node.data = static_cast<uint64_t>(var_data.GetSignedInt8(err, 0));
|
|
break;
|
|
case i16:
|
|
res_node.data = static_cast<uint64_t>(var_data.GetSignedInt16(err, 0));
|
|
break;
|
|
case i32:
|
|
res_node.data = static_cast<uint64_t>(var_data.GetSignedInt32(err, 0));
|
|
break;
|
|
case i64:
|
|
res_node.data = static_cast<uint64_t>(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<uint8_t>{};
|
|
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 = err.Success();
|
|
to_send.push_back(res_node);
|
|
_data_res[res_idx] =
|
|
CachedDataRes{.node = std::move(res_node),
|
|
.no_delete_on_src_delete = true,
|
|
.src_id = std::numeric_limits<size_t>::max()};
|
|
return res_idx;
|
|
}
|
|
|
|
void LLDBBackend::build_nodes_for_var_cached(
|
|
lldb::SBValue &val, data::result::NodeIdx cache_idx,
|
|
std::vector<data::result::Node> &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<data::type_info::TypeID>(
|
|
_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<uint64_t>(var_data.GetUnsignedInt8(err, 0));
|
|
break;
|
|
case u16:
|
|
res_node.data = static_cast<uint64_t>(var_data.GetUnsignedInt16(err, 0));
|
|
break;
|
|
case u32:
|
|
res_node.data = static_cast<uint64_t>(var_data.GetUnsignedInt32(err, 0));
|
|
break;
|
|
case u64:
|
|
res_node.data = static_cast<uint64_t>(var_data.GetUnsignedInt64(err, 0));
|
|
break;
|
|
case i8:
|
|
res_node.data = static_cast<uint64_t>(var_data.GetSignedInt8(err, 0));
|
|
break;
|
|
case i16:
|
|
res_node.data = static_cast<uint64_t>(var_data.GetSignedInt16(err, 0));
|
|
break;
|
|
case i32:
|
|
res_node.data = static_cast<uint64_t>(var_data.GetSignedInt32(err, 0));
|
|
break;
|
|
case i64:
|
|
res_node.data = static_cast<uint64_t>(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<uint8_t>{};
|
|
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 = err.Success();
|
|
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<data::result::NodeIdx> 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<uint16_t> 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<int>(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;
|
|
} |