|  | //===-- 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 "Plugins/Process/Utility/DynamicRegisterInfo.h" | 
|  | #include "Plugins/Process/Utility/RegisterContextMemory.h" | 
|  | #include "Plugins/Process/Utility/ThreadMemory.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/OptionGroupBoolean.h" | 
|  | #include "lldb/Interpreter/OptionGroupUInt64.h" | 
|  | #include "lldb/Interpreter/OptionValueProperties.h" | 
|  | #include "lldb/Interpreter/Options.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/Thread.h" | 
|  | #include "lldb/Target/ThreadList.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(); | 
|  | std::lock_guard<std::recursive_mutex> guard(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; | 
|  | // Go 1.6 stores goroutines in a slice called runtime.allgs | 
|  | ValueObjectSP allgs_sp = FindGlobal(target_sp, "runtime.allgs"); | 
|  | if (allgs_sp) { | 
|  | m_allg_sp = allgs_sp->GetChildMemberWithName(ConstString("array"), true); | 
|  | m_allglen_sp = allgs_sp->GetChildMemberWithName(ConstString("len"), true); | 
|  | } else { | 
|  | // Go 1.4 stores goroutines in the variable runtime.allg. | 
|  | 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; | 
|  | } |