//===-- 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 // 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/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/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" #include "lldb/Utility/DataBufferHeap.h" #include "lldb/Utility/StreamString.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 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 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 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 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. Status err; for (uint64_t i = 0; i < allglen; ++i) { goroutines.push_back(CreateGoroutineAtIndex(i, err)); if (err.Fail()) { LLDB_LOG(log, "error: {0}", err); return new_thread_list.GetSize(false) > 0; } } // Make a map so we can match goroutines with backing threads. std::map 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, "", "", 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, Status &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; }