560 lines
17 KiB
C++
560 lines
17 KiB
C++
//===-- OperatingSystemGo.cpp -----------------------------------*- C++ -*-===//
|
|
//
|
|
// The LLVM Compiler Infrastructure
|
|
//
|
|
// This file is distributed under the University of Illinois Open Source
|
|
// License. See LICENSE.TXT for details.
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
// C Includes
|
|
// C++ Includes
|
|
#include <unordered_map>
|
|
|
|
// Other libraries and framework includes
|
|
// Project includes
|
|
#include "OperatingSystemGo.h"
|
|
|
|
#include "lldb/Core/DataBufferHeap.h"
|
|
#include "lldb/Core/Debugger.h"
|
|
#include "lldb/Core/Module.h"
|
|
#include "lldb/Core/PluginManager.h"
|
|
#include "lldb/Core/RegisterValue.h"
|
|
#include "lldb/Core/Section.h"
|
|
#include "lldb/Core/StreamString.h"
|
|
#include "lldb/Core/ValueObjectVariable.h"
|
|
#include "lldb/Interpreter/CommandInterpreter.h"
|
|
#include "lldb/Interpreter/OptionValueProperties.h"
|
|
#include "lldb/Interpreter/Options.h"
|
|
#include "lldb/Interpreter/OptionGroupBoolean.h"
|
|
#include "lldb/Interpreter/OptionGroupUInt64.h"
|
|
#include "lldb/Interpreter/Property.h"
|
|
#include "lldb/Symbol/ObjectFile.h"
|
|
#include "lldb/Symbol/Type.h"
|
|
#include "lldb/Symbol/VariableList.h"
|
|
#include "lldb/Target/Process.h"
|
|
#include "lldb/Target/StopInfo.h"
|
|
#include "lldb/Target/Target.h"
|
|
#include "lldb/Target/ThreadList.h"
|
|
#include "lldb/Target/Thread.h"
|
|
#include "Plugins/Process/Utility/DynamicRegisterInfo.h"
|
|
#include "Plugins/Process/Utility/RegisterContextMemory.h"
|
|
#include "Plugins/Process/Utility/ThreadMemory.h"
|
|
|
|
using namespace lldb;
|
|
using namespace lldb_private;
|
|
|
|
namespace
|
|
{
|
|
|
|
static PropertyDefinition g_properties[] = {{"enable", OptionValue::eTypeBoolean, true, true, nullptr, nullptr,
|
|
"Specify whether goroutines should be treated as threads."},
|
|
{NULL, OptionValue::eTypeInvalid, false, 0, NULL, NULL, NULL}};
|
|
|
|
enum
|
|
{
|
|
ePropertyEnableGoroutines,
|
|
};
|
|
|
|
class PluginProperties : public Properties
|
|
{
|
|
public:
|
|
PluginProperties()
|
|
: Properties()
|
|
{
|
|
m_collection_sp.reset(new OptionValueProperties(GetSettingName()));
|
|
m_collection_sp->Initialize(g_properties);
|
|
}
|
|
|
|
~PluginProperties() override = default;
|
|
|
|
static ConstString
|
|
GetSettingName()
|
|
{
|
|
return OperatingSystemGo::GetPluginNameStatic();
|
|
}
|
|
|
|
bool
|
|
GetEnableGoroutines()
|
|
{
|
|
const uint32_t idx = ePropertyEnableGoroutines;
|
|
return m_collection_sp->GetPropertyAtIndexAsBoolean(NULL, idx, g_properties[idx].default_uint_value);
|
|
}
|
|
|
|
bool
|
|
SetEnableGoroutines(bool enable)
|
|
{
|
|
const uint32_t idx = ePropertyEnableGoroutines;
|
|
return m_collection_sp->SetPropertyAtIndexAsUInt64(NULL, idx, enable);
|
|
}
|
|
};
|
|
|
|
typedef std::shared_ptr<PluginProperties> OperatingSystemGoPropertiesSP;
|
|
|
|
static const OperatingSystemGoPropertiesSP &
|
|
GetGlobalPluginProperties()
|
|
{
|
|
static OperatingSystemGoPropertiesSP g_settings_sp;
|
|
if (!g_settings_sp)
|
|
g_settings_sp.reset(new PluginProperties());
|
|
return g_settings_sp;
|
|
}
|
|
|
|
class RegisterContextGo : public RegisterContextMemory
|
|
{
|
|
public:
|
|
RegisterContextGo(lldb_private::Thread &thread, uint32_t concrete_frame_idx, DynamicRegisterInfo ®_info,
|
|
lldb::addr_t reg_data_addr)
|
|
: RegisterContextMemory(thread, concrete_frame_idx, reg_info, reg_data_addr)
|
|
{
|
|
const RegisterInfo *sp = reg_info.GetRegisterInfoAtIndex(
|
|
reg_info.ConvertRegisterKindToRegisterNumber(eRegisterKindGeneric, LLDB_REGNUM_GENERIC_SP));
|
|
const RegisterInfo *pc = reg_info.GetRegisterInfoAtIndex(
|
|
reg_info.ConvertRegisterKindToRegisterNumber(eRegisterKindGeneric, LLDB_REGNUM_GENERIC_PC));
|
|
size_t byte_size = std::max(sp->byte_offset + sp->byte_size, pc->byte_offset + pc->byte_size);
|
|
|
|
DataBufferSP reg_data_sp(new DataBufferHeap(byte_size, 0));
|
|
m_reg_data.SetData(reg_data_sp);
|
|
}
|
|
|
|
~RegisterContextGo() override = default;
|
|
|
|
bool
|
|
ReadRegister(const lldb_private::RegisterInfo *reg_info,
|
|
lldb_private::RegisterValue ®_value) override
|
|
{
|
|
switch (reg_info->kinds[eRegisterKindGeneric])
|
|
{
|
|
case LLDB_REGNUM_GENERIC_SP:
|
|
case LLDB_REGNUM_GENERIC_PC:
|
|
return RegisterContextMemory::ReadRegister(reg_info, reg_value);
|
|
default:
|
|
reg_value.SetValueToInvalid();
|
|
return true;
|
|
}
|
|
}
|
|
|
|
bool
|
|
WriteRegister(const lldb_private::RegisterInfo *reg_info,
|
|
const lldb_private::RegisterValue ®_value) override
|
|
{
|
|
switch (reg_info->kinds[eRegisterKindGeneric])
|
|
{
|
|
case LLDB_REGNUM_GENERIC_SP:
|
|
case LLDB_REGNUM_GENERIC_PC:
|
|
return RegisterContextMemory::WriteRegister(reg_info, reg_value);
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
private:
|
|
DISALLOW_COPY_AND_ASSIGN(RegisterContextGo);
|
|
};
|
|
|
|
} // anonymous namespace
|
|
|
|
struct OperatingSystemGo::Goroutine
|
|
{
|
|
uint64_t m_lostack;
|
|
uint64_t m_histack;
|
|
uint64_t m_goid;
|
|
addr_t m_gobuf;
|
|
uint32_t m_status;
|
|
};
|
|
|
|
void
|
|
OperatingSystemGo::Initialize()
|
|
{
|
|
PluginManager::RegisterPlugin(GetPluginNameStatic(), GetPluginDescriptionStatic(), CreateInstance,
|
|
DebuggerInitialize);
|
|
}
|
|
|
|
void
|
|
OperatingSystemGo::DebuggerInitialize(Debugger &debugger)
|
|
{
|
|
if (!PluginManager::GetSettingForOperatingSystemPlugin(debugger, PluginProperties::GetSettingName()))
|
|
{
|
|
const bool is_global_setting = true;
|
|
PluginManager::CreateSettingForOperatingSystemPlugin(
|
|
debugger, GetGlobalPluginProperties()->GetValueProperties(),
|
|
ConstString("Properties for the goroutine thread plug-in."), is_global_setting);
|
|
}
|
|
}
|
|
|
|
void
|
|
OperatingSystemGo::Terminate()
|
|
{
|
|
PluginManager::UnregisterPlugin(CreateInstance);
|
|
}
|
|
|
|
OperatingSystem *
|
|
OperatingSystemGo::CreateInstance(Process *process, bool force)
|
|
{
|
|
if (!force)
|
|
{
|
|
TargetSP target_sp = process->CalculateTarget();
|
|
if (!target_sp)
|
|
return nullptr;
|
|
ModuleList &module_list = target_sp->GetImages();
|
|
Mutex::Locker modules_locker(module_list.GetMutex());
|
|
const size_t num_modules = module_list.GetSize();
|
|
bool found_go_runtime = false;
|
|
for (size_t i = 0; i < num_modules; ++i)
|
|
{
|
|
Module *module = module_list.GetModulePointerAtIndexUnlocked(i);
|
|
const SectionList *section_list = module->GetSectionList();
|
|
if (section_list)
|
|
{
|
|
SectionSP section_sp(section_list->FindSectionByType(eSectionTypeGoSymtab, true));
|
|
if (section_sp)
|
|
{
|
|
found_go_runtime = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (!found_go_runtime)
|
|
return nullptr;
|
|
}
|
|
return new OperatingSystemGo(process);
|
|
}
|
|
|
|
OperatingSystemGo::OperatingSystemGo(lldb_private::Process *process)
|
|
: OperatingSystem(process)
|
|
, m_reginfo(new DynamicRegisterInfo)
|
|
{
|
|
}
|
|
|
|
OperatingSystemGo::~OperatingSystemGo() = default;
|
|
|
|
ConstString
|
|
OperatingSystemGo::GetPluginNameStatic()
|
|
{
|
|
static ConstString g_name("goroutines");
|
|
return g_name;
|
|
}
|
|
|
|
const char *
|
|
OperatingSystemGo::GetPluginDescriptionStatic()
|
|
{
|
|
return "Operating system plug-in that reads runtime data-structures for goroutines.";
|
|
}
|
|
|
|
bool
|
|
OperatingSystemGo::Init(ThreadList &threads)
|
|
{
|
|
if (threads.GetSize(false) < 1)
|
|
return false;
|
|
TargetSP target_sp = m_process->CalculateTarget();
|
|
if (!target_sp)
|
|
return false;
|
|
m_allg_sp = FindGlobal(target_sp, "runtime.allg");
|
|
m_allglen_sp = FindGlobal(target_sp, "runtime.allglen");
|
|
|
|
if (m_allg_sp && !m_allglen_sp)
|
|
{
|
|
StreamSP error_sp = target_sp->GetDebugger().GetAsyncErrorStream();
|
|
error_sp->Printf("Unsupported Go runtime version detected.");
|
|
return false;
|
|
}
|
|
|
|
if (!m_allg_sp)
|
|
return false;
|
|
|
|
RegisterContextSP real_registers_sp = threads.GetThreadAtIndex(0, false)->GetRegisterContext();
|
|
|
|
std::unordered_map<size_t, ConstString> register_sets;
|
|
for (size_t set_idx = 0; set_idx < real_registers_sp->GetRegisterSetCount(); ++set_idx)
|
|
{
|
|
const RegisterSet *set = real_registers_sp->GetRegisterSet(set_idx);
|
|
ConstString name(set->name);
|
|
for (size_t reg_idx = 0; reg_idx < set->num_registers; ++reg_idx)
|
|
{
|
|
register_sets[reg_idx] = name;
|
|
}
|
|
}
|
|
TypeSP gobuf_sp = FindType(target_sp, "runtime.gobuf");
|
|
if (!gobuf_sp)
|
|
{
|
|
Log *log(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_OS));
|
|
|
|
if (log)
|
|
log->Printf("OperatingSystemGo unable to find struct Gobuf");
|
|
return false;
|
|
}
|
|
CompilerType gobuf_type(gobuf_sp->GetLayoutCompilerType());
|
|
for (size_t idx = 0; idx < real_registers_sp->GetRegisterCount(); ++idx)
|
|
{
|
|
RegisterInfo reg = *real_registers_sp->GetRegisterInfoAtIndex(idx);
|
|
int field_index = -1;
|
|
if (reg.kinds[eRegisterKindGeneric] == LLDB_REGNUM_GENERIC_SP)
|
|
{
|
|
field_index = 0;
|
|
}
|
|
else if (reg.kinds[eRegisterKindGeneric] == LLDB_REGNUM_GENERIC_PC)
|
|
{
|
|
field_index = 1;
|
|
}
|
|
if (field_index == -1)
|
|
{
|
|
reg.byte_offset = ~0;
|
|
}
|
|
else
|
|
{
|
|
std::string field_name;
|
|
uint64_t bit_offset = 0;
|
|
CompilerType field_type =
|
|
gobuf_type.GetFieldAtIndex(field_index, field_name, &bit_offset, nullptr, nullptr);
|
|
reg.byte_size = field_type.GetByteSize(nullptr);
|
|
reg.byte_offset = bit_offset / 8;
|
|
}
|
|
ConstString name(reg.name);
|
|
ConstString alt_name(reg.alt_name);
|
|
m_reginfo->AddRegister(reg, name, alt_name, register_sets[idx]);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
//------------------------------------------------------------------
|
|
// PluginInterface protocol
|
|
//------------------------------------------------------------------
|
|
ConstString
|
|
OperatingSystemGo::GetPluginName()
|
|
{
|
|
return GetPluginNameStatic();
|
|
}
|
|
|
|
uint32_t
|
|
OperatingSystemGo::GetPluginVersion()
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
bool
|
|
OperatingSystemGo::UpdateThreadList(ThreadList &old_thread_list, ThreadList &real_thread_list,
|
|
ThreadList &new_thread_list)
|
|
{
|
|
new_thread_list = real_thread_list;
|
|
Log *log(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_OS));
|
|
|
|
if (!(m_allg_sp || Init(real_thread_list)) || (m_allg_sp && !m_allglen_sp) ||
|
|
!GetGlobalPluginProperties()->GetEnableGoroutines())
|
|
{
|
|
return new_thread_list.GetSize(false) > 0;
|
|
}
|
|
|
|
if (log)
|
|
log->Printf("OperatingSystemGo::UpdateThreadList(%d, %d, %d) fetching thread data from Go for pid %" PRIu64,
|
|
old_thread_list.GetSize(false), real_thread_list.GetSize(false), new_thread_list.GetSize(0),
|
|
m_process->GetID());
|
|
uint64_t allglen = m_allglen_sp->GetValueAsUnsigned(0);
|
|
if (allglen == 0)
|
|
{
|
|
return new_thread_list.GetSize(false) > 0;
|
|
}
|
|
std::vector<Goroutine> goroutines;
|
|
// The threads that are in "new_thread_list" upon entry are the threads from the
|
|
// lldb_private::Process subclass, no memory threads will be in this list.
|
|
|
|
Error err;
|
|
for (uint64_t i = 0; i < allglen; ++i)
|
|
{
|
|
goroutines.push_back(CreateGoroutineAtIndex(i, err));
|
|
if (err.Fail())
|
|
{
|
|
err.PutToLog(log, "OperatingSystemGo::UpdateThreadList");
|
|
return new_thread_list.GetSize(false) > 0;
|
|
}
|
|
}
|
|
// Make a map so we can match goroutines with backing threads.
|
|
std::map<uint64_t, ThreadSP> stack_map;
|
|
for (uint32_t i = 0; i < real_thread_list.GetSize(false); ++i)
|
|
{
|
|
ThreadSP thread = real_thread_list.GetThreadAtIndex(i, false);
|
|
stack_map[thread->GetRegisterContext()->GetSP()] = thread;
|
|
}
|
|
for (const Goroutine &goroutine : goroutines)
|
|
{
|
|
if (0 /* Gidle */ == goroutine.m_status || 6 /* Gdead */ == goroutine.m_status)
|
|
{
|
|
continue;
|
|
}
|
|
ThreadSP memory_thread = old_thread_list.FindThreadByID(goroutine.m_goid, false);
|
|
if (memory_thread && IsOperatingSystemPluginThread(memory_thread) && memory_thread->IsValid())
|
|
{
|
|
memory_thread->ClearBackingThread();
|
|
}
|
|
else
|
|
{
|
|
memory_thread.reset(new ThreadMemory(*m_process, goroutine.m_goid, nullptr, nullptr, goroutine.m_gobuf));
|
|
}
|
|
// Search for the backing thread if the goroutine is running.
|
|
if (2 == (goroutine.m_status & 0xfff))
|
|
{
|
|
auto backing_it = stack_map.lower_bound(goroutine.m_lostack);
|
|
if (backing_it != stack_map.end())
|
|
{
|
|
if (goroutine.m_histack >= backing_it->first)
|
|
{
|
|
if (log)
|
|
log->Printf("OperatingSystemGo::UpdateThreadList found backing thread %" PRIx64 " (%" PRIx64
|
|
") for thread %" PRIx64 "",
|
|
backing_it->second->GetID(), backing_it->second->GetProtocolID(),
|
|
memory_thread->GetID());
|
|
memory_thread->SetBackingThread(backing_it->second);
|
|
new_thread_list.RemoveThreadByID(backing_it->second->GetID(), false);
|
|
}
|
|
}
|
|
}
|
|
new_thread_list.AddThread(memory_thread);
|
|
}
|
|
|
|
return new_thread_list.GetSize(false) > 0;
|
|
}
|
|
|
|
void
|
|
OperatingSystemGo::ThreadWasSelected(Thread *thread)
|
|
{
|
|
}
|
|
|
|
RegisterContextSP
|
|
OperatingSystemGo::CreateRegisterContextForThread(Thread *thread, addr_t reg_data_addr)
|
|
{
|
|
RegisterContextSP reg_ctx_sp;
|
|
if (!thread)
|
|
return reg_ctx_sp;
|
|
|
|
if (!IsOperatingSystemPluginThread(thread->shared_from_this()))
|
|
return reg_ctx_sp;
|
|
|
|
reg_ctx_sp.reset(new RegisterContextGo(*thread, 0, *m_reginfo, reg_data_addr));
|
|
return reg_ctx_sp;
|
|
}
|
|
|
|
StopInfoSP
|
|
OperatingSystemGo::CreateThreadStopReason(lldb_private::Thread *thread)
|
|
{
|
|
StopInfoSP stop_info_sp;
|
|
return stop_info_sp;
|
|
}
|
|
|
|
lldb::ThreadSP
|
|
OperatingSystemGo::CreateThread(lldb::tid_t tid, addr_t context)
|
|
{
|
|
Log *log(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_OS));
|
|
|
|
if (log)
|
|
log->Printf("OperatingSystemGo::CreateThread (tid = 0x%" PRIx64 ", context = 0x%" PRIx64 ") not implemented",
|
|
tid, context);
|
|
|
|
return ThreadSP();
|
|
}
|
|
|
|
ValueObjectSP
|
|
OperatingSystemGo::FindGlobal(TargetSP target, const char *name)
|
|
{
|
|
VariableList variable_list;
|
|
const bool append = true;
|
|
|
|
Log *log(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_OS));
|
|
|
|
if (log)
|
|
{
|
|
log->Printf("exe: %s", target->GetExecutableModule()->GetSpecificationDescription().c_str());
|
|
log->Printf("modules: %zu", target->GetImages().GetSize());
|
|
}
|
|
|
|
uint32_t match_count = target->GetImages().FindGlobalVariables(ConstString(name), append, 1, variable_list);
|
|
if (match_count > 0)
|
|
{
|
|
ExecutionContextScope *exe_scope = target->GetProcessSP().get();
|
|
if (exe_scope == NULL)
|
|
exe_scope = target.get();
|
|
return ValueObjectVariable::Create(exe_scope, variable_list.GetVariableAtIndex(0));
|
|
}
|
|
return ValueObjectSP();
|
|
}
|
|
|
|
TypeSP
|
|
OperatingSystemGo::FindType(TargetSP target_sp, const char *name)
|
|
{
|
|
ConstString const_typename(name);
|
|
SymbolContext sc;
|
|
const bool exact_match = false;
|
|
|
|
const ModuleList &module_list = target_sp->GetImages();
|
|
size_t count = module_list.GetSize();
|
|
for (size_t idx = 0; idx < count; idx++)
|
|
{
|
|
ModuleSP module_sp(module_list.GetModuleAtIndex(idx));
|
|
if (module_sp)
|
|
{
|
|
TypeSP type_sp(module_sp->FindFirstType(sc, const_typename, exact_match));
|
|
if (type_sp)
|
|
return type_sp;
|
|
}
|
|
}
|
|
Log *log(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_OS));
|
|
|
|
if (log)
|
|
log->Printf("OperatingSystemGo::FindType(%s): not found", name);
|
|
return TypeSP();
|
|
}
|
|
|
|
OperatingSystemGo::Goroutine
|
|
OperatingSystemGo::CreateGoroutineAtIndex(uint64_t idx, Error &err)
|
|
{
|
|
err.Clear();
|
|
Goroutine result;
|
|
ValueObjectSP g = m_allg_sp->GetSyntheticArrayMember(idx, true)->Dereference(err);
|
|
if (err.Fail())
|
|
{
|
|
return result;
|
|
}
|
|
|
|
ConstString name("goid");
|
|
ValueObjectSP val = g->GetChildMemberWithName(name, true);
|
|
bool success = false;
|
|
result.m_goid = val->GetValueAsUnsigned(0, &success);
|
|
if (!success)
|
|
{
|
|
err.SetErrorToGenericError();
|
|
err.SetErrorString("unable to read goid");
|
|
return result;
|
|
}
|
|
name.SetCString("atomicstatus");
|
|
val = g->GetChildMemberWithName(name, true);
|
|
result.m_status = (uint32_t)val->GetValueAsUnsigned(0, &success);
|
|
if (!success)
|
|
{
|
|
err.SetErrorToGenericError();
|
|
err.SetErrorString("unable to read atomicstatus");
|
|
return result;
|
|
}
|
|
name.SetCString("sched");
|
|
val = g->GetChildMemberWithName(name, true);
|
|
result.m_gobuf = val->GetAddressOf(false);
|
|
name.SetCString("stack");
|
|
val = g->GetChildMemberWithName(name, true);
|
|
name.SetCString("lo");
|
|
ValueObjectSP child = val->GetChildMemberWithName(name, true);
|
|
result.m_lostack = child->GetValueAsUnsigned(0, &success);
|
|
if (!success)
|
|
{
|
|
err.SetErrorToGenericError();
|
|
err.SetErrorString("unable to read stack.lo");
|
|
return result;
|
|
}
|
|
name.SetCString("hi");
|
|
child = val->GetChildMemberWithName(name, true);
|
|
result.m_histack = child->GetValueAsUnsigned(0, &success);
|
|
if (!success)
|
|
{
|
|
err.SetErrorToGenericError();
|
|
err.SetErrorString("unable to read stack.hi");
|
|
return result;
|
|
}
|
|
return result;
|
|
}
|