diff options
Diffstat (limited to 'source/Plugins/OperatingSystem/Go/OperatingSystemGo.cpp')
-rw-r--r-- | source/Plugins/OperatingSystem/Go/OperatingSystemGo.cpp | 559 |
1 files changed, 559 insertions, 0 deletions
diff --git a/source/Plugins/OperatingSystem/Go/OperatingSystemGo.cpp b/source/Plugins/OperatingSystem/Go/OperatingSystemGo.cpp new file mode 100644 index 000000000000..86c574f2776c --- /dev/null +++ b/source/Plugins/OperatingSystem/Go/OperatingSystemGo.cpp @@ -0,0 +1,559 @@ +//===-- 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; +} |