Files
dbgui/src/backend/lldb/lldb_backend.cpp
2023-06-25 02:57:11 +02:00

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> &reg_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;
}