Index: lldb/trunk/cmake/LLDBDependencies.cmake =================================================================== --- lldb/trunk/cmake/LLDBDependencies.cmake +++ lldb/trunk/cmake/LLDBDependencies.cmake @@ -65,6 +65,7 @@ lldbPluginInstructionMIPS lldbPluginInstructionMIPS64 lldbPluginObjectFilePECOFF + lldbPluginOSGo lldbPluginOSPython lldbPluginMemoryHistoryASan lldbPluginInstrumentationRuntimeAddressSanitizer Index: lldb/trunk/include/lldb/Core/PluginManager.h =================================================================== --- lldb/trunk/include/lldb/Core/PluginManager.h +++ lldb/trunk/include/lldb/Core/PluginManager.h @@ -117,11 +117,10 @@ //------------------------------------------------------------------ // OperatingSystem //------------------------------------------------------------------ - static bool - RegisterPlugin (const ConstString &name, - const char *description, - OperatingSystemCreateInstance create_callback); - + static bool RegisterPlugin(const ConstString &name, const char *description, + OperatingSystemCreateInstance create_callback, + DebuggerInitializeCallback debugger_init_callback); + static bool UnregisterPlugin (OperatingSystemCreateInstance create_callback); @@ -480,6 +479,13 @@ const lldb::OptionValuePropertiesSP &properties_sp, const ConstString &description, bool is_global_property); + + static lldb::OptionValuePropertiesSP GetSettingForOperatingSystemPlugin(Debugger &debugger, + const ConstString &setting_name); + + static bool CreateSettingForOperatingSystemPlugin(Debugger &debugger, + const lldb::OptionValuePropertiesSP &properties_sp, + const ConstString &description, bool is_global_property); }; Index: lldb/trunk/include/lldb/lldb-enumerations.h =================================================================== --- lldb/trunk/include/lldb/lldb-enumerations.h +++ lldb/trunk/include/lldb/lldb-enumerations.h @@ -617,6 +617,7 @@ eSectionTypeELFDynamicLinkInfo, // Elf SHT_DYNAMIC section eSectionTypeEHFrame, eSectionTypeCompactUnwind, // compact unwind section in Mach-O, __TEXT,__unwind_info + eSectionTypeGoSymtab, eSectionTypeOther }; Index: lldb/trunk/lib/Makefile =================================================================== --- lldb/trunk/lib/Makefile +++ lldb/trunk/lib/Makefile @@ -69,6 +69,7 @@ lldbPluginObjectFileJIT.a \ lldbPluginSymbolVendorELF.a \ lldbPluginObjectFilePECOFF.a \ + lldbPluginOSGo.a \ lldbPluginOSPython.a \ lldbPluginPlatformGDB.a \ lldbPluginProcessElfCore.a \ Index: lldb/trunk/lldb.xcodeproj/project.pbxproj =================================================================== --- lldb/trunk/lldb.xcodeproj/project.pbxproj +++ lldb/trunk/lldb.xcodeproj/project.pbxproj @@ -817,6 +817,8 @@ 9AC703AF117675410086C050 /* SBInstruction.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9AC703AE117675410086C050 /* SBInstruction.cpp */; }; 9AC703B1117675490086C050 /* SBInstructionList.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9AC703B0117675490086C050 /* SBInstructionList.cpp */; }; A36FF33C17D8E94600244D40 /* OptionParser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A36FF33B17D8E94600244D40 /* OptionParser.cpp */; }; + AE8F624919EF3E1E00326B21 /* OperatingSystemGo.cpp in Sources */ = {isa = PBXBuildFile; fileRef = AE8F624719EF3E1E00326B21 /* OperatingSystemGo.cpp */; }; + AEB74D931A8C1BE300FE1A4A /* OperatingSystemGo.h in Headers */ = {isa = PBXBuildFile; fileRef = AE8F624819EF3E1E00326B21 /* OperatingSystemGo.h */; }; AE6897281B94F6DE0018845D /* DWARFASTParserGo.cpp in Sources */ = {isa = PBXBuildFile; fileRef = AE6897261B94F6DE0018845D /* DWARFASTParserGo.cpp */; }; AE7F56291B8FE418001377A8 /* GoASTContext.cpp in Sources */ = {isa = PBXBuildFile; fileRef = AEFFBA7C1AC4835D0087B932 /* GoASTContext.cpp */; }; AEEA34051AC88A7400AB639D /* TypeSystem.cpp in Sources */ = {isa = PBXBuildFile; fileRef = AEEA34041AC88A7400AB639D /* TypeSystem.cpp */; }; @@ -2624,6 +2626,8 @@ 9AF16CC7114086A1007A7B3F /* SBBreakpointLocation.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = SBBreakpointLocation.cpp; path = source/API/SBBreakpointLocation.cpp; sourceTree = ""; }; A36FF33B17D8E94600244D40 /* OptionParser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = OptionParser.cpp; sourceTree = ""; }; A36FF33D17D8E98800244D40 /* OptionParser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = OptionParser.h; path = include/lldb/Host/OptionParser.h; sourceTree = ""; }; + AE8F624719EF3E1E00326B21 /* OperatingSystemGo.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = OperatingSystemGo.cpp; path = Go/OperatingSystemGo.cpp; sourceTree = ""; }; + AE8F624819EF3E1E00326B21 /* OperatingSystemGo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = OperatingSystemGo.h; path = Go/OperatingSystemGo.h; sourceTree = ""; }; AE6897261B94F6DE0018845D /* DWARFASTParserGo.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = DWARFASTParserGo.cpp; sourceTree = ""; }; AE6897271B94F6DE0018845D /* DWARFASTParserGo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DWARFASTParserGo.h; sourceTree = ""; }; AEEA33F61AC74FE700AB639D /* TypeSystem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = TypeSystem.h; path = include/lldb/Symbol/TypeSystem.h; sourceTree = ""; }; @@ -3582,6 +3586,7 @@ 266DFE9013FD64D200D0C574 /* OperatingSystem */ = { isa = PBXGroup; children = ( + AE8F624519EF3DFC00326B21 /* Go */, 2698699715E6CBD0002415FF /* Python */, ); path = OperatingSystem; @@ -5422,6 +5427,15 @@ name = DataFormatters; sourceTree = ""; }; + AE8F624519EF3DFC00326B21 /* Go */ = { + isa = PBXGroup; + children = ( + AE8F624719EF3E1E00326B21 /* OperatingSystemGo.cpp */, + AE8F624819EF3E1E00326B21 /* OperatingSystemGo.h */, + ); + name = Go; + sourceTree = ""; + }; 9694FA6E1B32AA35005EBB16 /* SysV-mips */ = { isa = PBXGroup; children = ( @@ -6366,6 +6380,7 @@ 2689008D13353E4200698AC0 /* DynamicLoaderMacOSXDYLD.cpp in Sources */, 2689008E13353E4200698AC0 /* DynamicLoaderStatic.cpp in Sources */, 2689009613353E4200698AC0 /* ObjectContainerBSDArchive.cpp in Sources */, + AE8F624919EF3E1E00326B21 /* OperatingSystemGo.cpp in Sources */, 26BC179A18C7F2B300D2196D /* JITLoaderList.cpp in Sources */, 2689009713353E4200698AC0 /* ObjectContainerUniversalMachO.cpp in Sources */, 2689009813353E4200698AC0 /* ELFHeader.cpp in Sources */, Index: lldb/trunk/source/Core/PluginManager.cpp =================================================================== --- lldb/trunk/source/Core/PluginManager.cpp +++ lldb/trunk/source/Core/PluginManager.cpp @@ -771,16 +771,18 @@ struct OperatingSystemInstance { - OperatingSystemInstance() : - name(), - description(), - create_callback(NULL) + OperatingSystemInstance () : + name (), + description (), + create_callback (nullptr), + debugger_init_callback (nullptr) { } ConstString name; std::string description; OperatingSystemCreateInstance create_callback; + DebuggerInitializeCallback debugger_init_callback; }; typedef std::vector OperatingSystemInstances; @@ -800,9 +802,9 @@ } bool -PluginManager::RegisterPlugin (const ConstString &name, - const char *description, - OperatingSystemCreateInstance create_callback) +PluginManager::RegisterPlugin(const ConstString &name, const char *description, + OperatingSystemCreateInstance create_callback, + DebuggerInitializeCallback debugger_init_callback) { if (create_callback) { @@ -812,6 +814,7 @@ if (description && description[0]) instance.description = description; instance.create_callback = create_callback; + instance.debugger_init_callback = debugger_init_callback; Mutex::Locker locker (GetOperatingSystemMutex ()); GetOperatingSystemInstances ().push_back (instance); } @@ -2579,6 +2582,16 @@ sym_file.debugger_init_callback (debugger); } } + + // Initialize the OperatingSystem plugins + { + Mutex::Locker locker(GetOperatingSystemMutex()); + for (auto &os : GetOperatingSystemInstances()) + { + if (os.debugger_init_callback) + os.debugger_init_callback(debugger); + } + } } // This is the preferred new way to register plugin specific settings. e.g. @@ -2823,3 +2836,38 @@ description, is_global_property); } + +static const char *kOperatingSystemPluginName("os"); + +lldb::OptionValuePropertiesSP +PluginManager::GetSettingForOperatingSystemPlugin(Debugger &debugger, const ConstString &setting_name) +{ + lldb::OptionValuePropertiesSP properties_sp; + lldb::OptionValuePropertiesSP plugin_type_properties_sp( + GetDebuggerPropertyForPlugins(debugger, ConstString(kOperatingSystemPluginName), + ConstString(), // not creating to so we don't need the description + false)); + if (plugin_type_properties_sp) + properties_sp = plugin_type_properties_sp->GetSubProperty(nullptr, setting_name); + return properties_sp; +} + +bool +PluginManager::CreateSettingForOperatingSystemPlugin(Debugger &debugger, + const lldb::OptionValuePropertiesSP &properties_sp, + const ConstString &description, bool is_global_property) +{ + if (properties_sp) + { + lldb::OptionValuePropertiesSP plugin_type_properties_sp( + GetDebuggerPropertyForPlugins(debugger, ConstString(kOperatingSystemPluginName), + ConstString("Settings for operating system plug-ins"), true)); + if (plugin_type_properties_sp) + { + plugin_type_properties_sp->AppendProperty(properties_sp->GetName(), description, is_global_property, + properties_sp); + return true; + } + } + return false; +} Index: lldb/trunk/source/Initialization/SystemInitializerCommon.cpp =================================================================== --- lldb/trunk/source/Initialization/SystemInitializerCommon.cpp +++ lldb/trunk/source/Initialization/SystemInitializerCommon.cpp @@ -25,6 +25,7 @@ #include "Plugins/ObjectFile/ELF/ObjectFileELF.h" #include "Plugins/ObjectFile/PECOFF/ObjectFilePECOFF.h" #include "Plugins/OperatingSystem/Python/OperatingSystemPython.h" +#include "Plugins/OperatingSystem/Go/OperatingSystemGo.h" #include "Plugins/Platform/Android/PlatformAndroid.h" #include "Plugins/Platform/FreeBSD/PlatformFreeBSD.h" #include "Plugins/Platform/Kalimba/PlatformKalimba.h" @@ -142,6 +143,7 @@ #ifndef LLDB_DISABLE_PYTHON OperatingSystemPython::Initialize(); #endif + OperatingSystemGo::Initialize(); } void @@ -181,6 +183,7 @@ #ifndef LLDB_DISABLE_PYTHON OperatingSystemPython::Terminate(); #endif + OperatingSystemGo::Terminate(); Log::Terminate(); } Index: lldb/trunk/source/Plugins/Makefile =================================================================== --- lldb/trunk/source/Plugins/Makefile +++ lldb/trunk/source/Plugins/Makefile @@ -33,6 +33,7 @@ DynamicLoader/Windows-DYLD \ JITLoader/GDB \ ExpressionParser/Clang \ + OperatingSystem/Go \ OperatingSystem/Python \ SystemRuntime/MacOSX \ SymbolVendor/ELF \ Index: lldb/trunk/source/Plugins/ObjectFile/ELF/ObjectFileELF.cpp =================================================================== --- lldb/trunk/source/Plugins/ObjectFile/ELF/ObjectFileELF.cpp +++ lldb/trunk/source/Plugins/ObjectFile/ELF/ObjectFileELF.cpp @@ -1720,6 +1720,7 @@ static ConstString g_sect_name_dwarf_debug_str_dwo (".debug_str.dwo"); static ConstString g_sect_name_dwarf_debug_str_offsets_dwo (".debug_str_offsets.dwo"); static ConstString g_sect_name_eh_frame (".eh_frame"); + static ConstString g_sect_name_go_symtab (".gosymtab"); SectionType sect_type = eSectionTypeOther; @@ -1772,6 +1773,7 @@ else if (name == g_sect_name_dwarf_debug_str_dwo) sect_type = eSectionTypeDWARFDebugStr; else if (name == g_sect_name_dwarf_debug_str_offsets_dwo) sect_type = eSectionTypeDWARFDebugStrOffsets; else if (name == g_sect_name_eh_frame) sect_type = eSectionTypeEHFrame; + else if (name == g_sect_name_go_symtab) sect_type = eSectionTypeGoSymtab; switch (header.sh_type) { Index: lldb/trunk/source/Plugins/ObjectFile/Mach-O/ObjectFileMachO.cpp =================================================================== --- lldb/trunk/source/Plugins/ObjectFile/Mach-O/ObjectFileMachO.cpp +++ lldb/trunk/source/Plugins/ObjectFile/Mach-O/ObjectFileMachO.cpp @@ -1318,6 +1318,7 @@ case eSectionTypeZeroFill: case eSectionTypeDataObjCMessageRefs: case eSectionTypeDataObjCCFStrings: + case eSectionTypeGoSymtab: return eAddressClassData; case eSectionTypeDebug: @@ -1777,6 +1778,7 @@ static ConstString g_sect_name_compact_unwind ("__unwind_info"); static ConstString g_sect_name_text ("__text"); static ConstString g_sect_name_data ("__data"); + static ConstString g_sect_name_go_symtab ("__gosymtab"); if (section_name == g_sect_name_dwarf_debug_abbrev) @@ -1819,6 +1821,8 @@ sect_type = eSectionTypeCompactUnwind; else if (section_name == g_sect_name_cfstring) sect_type = eSectionTypeDataObjCCFStrings; + else if (section_name == g_sect_name_go_symtab) + sect_type = eSectionTypeGoSymtab; else if (section_name == g_sect_name_objc_data || section_name == g_sect_name_objc_classrefs || section_name == g_sect_name_objc_superrefs || Index: lldb/trunk/source/Plugins/ObjectFile/PECOFF/ObjectFilePECOFF.cpp =================================================================== --- lldb/trunk/source/Plugins/ObjectFile/PECOFF/ObjectFilePECOFF.cpp +++ lldb/trunk/source/Plugins/ObjectFile/PECOFF/ObjectFilePECOFF.cpp @@ -693,6 +693,7 @@ static ConstString g_sect_name_dwarf_debug_ranges (".debug_ranges"); static ConstString g_sect_name_dwarf_debug_str (".debug_str"); static ConstString g_sect_name_eh_frame (".eh_frame"); + static ConstString g_sect_name_go_symtab (".gosymtab"); SectionType section_type = eSectionTypeOther; if (m_sect_headers[idx].flags & llvm::COFF::IMAGE_SCN_CNT_CODE && ((const_sect_name == g_code_sect_name) || (const_sect_name == g_CODE_sect_name))) @@ -736,6 +737,7 @@ else if (const_sect_name == g_sect_name_dwarf_debug_ranges) section_type = eSectionTypeDWARFDebugRanges; else if (const_sect_name == g_sect_name_dwarf_debug_str) section_type = eSectionTypeDWARFDebugStr; else if (const_sect_name == g_sect_name_eh_frame) section_type = eSectionTypeEHFrame; + else if (const_sect_name == g_sect_name_go_symtab) section_type = eSectionTypeGoSymtab; else if (m_sect_headers[idx].flags & llvm::COFF::IMAGE_SCN_CNT_CODE) { section_type = eSectionTypeCode; Index: lldb/trunk/source/Plugins/OperatingSystem/CMakeLists.txt =================================================================== --- lldb/trunk/source/Plugins/OperatingSystem/CMakeLists.txt +++ lldb/trunk/source/Plugins/OperatingSystem/CMakeLists.txt @@ -1 +1,2 @@ +add_subdirectory(Go) add_subdirectory(Python) Index: lldb/trunk/source/Plugins/OperatingSystem/Go/CMakeLists.txt =================================================================== --- lldb/trunk/source/Plugins/OperatingSystem/Go/CMakeLists.txt +++ lldb/trunk/source/Plugins/OperatingSystem/Go/CMakeLists.txt @@ -0,0 +1,3 @@ +add_lldb_library(lldbPluginOSGo + OperatingSystemGo.cpp + ) Index: lldb/trunk/source/Plugins/OperatingSystem/Go/Makefile =================================================================== --- lldb/trunk/source/Plugins/OperatingSystem/Go/Makefile +++ lldb/trunk/source/Plugins/OperatingSystem/Go/Makefile @@ -0,0 +1,14 @@ +##==- source/Plugins/OperatingSystem/Go/Makefile --------*- Makefile -*-==## +# +# The LLVM Compiler Infrastructure +# +# This file is distributed under the University of Illinois Open Source +# License. See LICENSE.TXT for details. +# +##===----------------------------------------------------------------------===## + +LLDB_LEVEL := ../../../.. +LIBRARYNAME := lldbPluginOSGo +BUILD_ARCHIVE = 1 + +include $(LLDB_LEVEL)/Makefile Index: lldb/trunk/source/Plugins/OperatingSystem/Go/OperatingSystemGo.h =================================================================== --- lldb/trunk/source/Plugins/OperatingSystem/Go/OperatingSystemGo.h +++ lldb/trunk/source/Plugins/OperatingSystem/Go/OperatingSystemGo.h @@ -0,0 +1,85 @@ +//===-- OperatingSystemGo.h ----------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===-------------------------------------------------------------------===// + +#ifndef _liblldb_OperatingSystemGo_h_ +#define _liblldb_OperatingSystemGo_h_ + +#include + +#include "lldb/Target/OperatingSystem.h" + +class DynamicRegisterInfo; + +class OperatingSystemGo : public lldb_private::OperatingSystem +{ + public: + //------------------------------------------------------------------ + // Static Functions + //------------------------------------------------------------------ + static lldb_private::OperatingSystem *CreateInstance(lldb_private::Process *process, bool force); + + static void Initialize(); + + static void DebuggerInitialize(lldb_private::Debugger &debugger); + + static void Terminate(); + + static lldb_private::ConstString GetPluginNameStatic(); + + static const char *GetPluginDescriptionStatic(); + + //------------------------------------------------------------------ + // Class Methods + //------------------------------------------------------------------ + OperatingSystemGo(lldb_private::Process *process); + + virtual ~OperatingSystemGo(); + + //------------------------------------------------------------------ + // lldb_private::PluginInterface Methods + //------------------------------------------------------------------ + virtual lldb_private::ConstString GetPluginName(); + + virtual uint32_t GetPluginVersion(); + + //------------------------------------------------------------------ + // lldb_private::OperatingSystem Methods + //------------------------------------------------------------------ + virtual bool UpdateThreadList(lldb_private::ThreadList &old_thread_list, lldb_private::ThreadList &real_thread_list, + lldb_private::ThreadList &new_thread_list); + + virtual void ThreadWasSelected(lldb_private::Thread *thread); + + virtual lldb::RegisterContextSP CreateRegisterContextForThread(lldb_private::Thread *thread, + lldb::addr_t reg_data_addr); + + virtual lldb::StopInfoSP CreateThreadStopReason(lldb_private::Thread *thread); + + //------------------------------------------------------------------ + // Method for lazy creation of threads on demand + //------------------------------------------------------------------ + virtual lldb::ThreadSP CreateThread(lldb::tid_t tid, lldb::addr_t context); + + private: + struct Goroutine; + + static lldb::ValueObjectSP FindGlobal(lldb::TargetSP target, const char *name); + + static lldb::TypeSP FindType(lldb::TargetSP target_sp, const char *name); + + bool Init(lldb_private::ThreadList &threads); + + Goroutine CreateGoroutineAtIndex(uint64_t idx, lldb_private::Error &err); + + std::unique_ptr m_reginfo; + lldb::ValueObjectSP m_allg_sp; + lldb::ValueObjectSP m_allglen_sp; +}; + +#endif // #ifndef liblldb_OperatingSystemGo_h_ Index: lldb/trunk/source/Plugins/OperatingSystem/Go/OperatingSystemGo.cpp =================================================================== --- lldb/trunk/source/Plugins/OperatingSystem/Go/OperatingSystemGo.cpp +++ lldb/trunk/source/Plugins/OperatingSystem/Go/OperatingSystemGo.cpp @@ -0,0 +1,560 @@ +//===-- OperatingSystemGo.cpp --------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +#include "OperatingSystemGo.h" + +// C Includes +// C++ Includes +#include + +// Other libraries and framework includes +#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: + static ConstString + GetSettingName() + { + return OperatingSystemGo::GetPluginNameStatic(); + } + + PluginProperties() + : Properties() + { + m_collection_sp.reset(new OptionValueProperties(GetSettingName())); + m_collection_sp->Initialize(g_properties); + } + + virtual ~PluginProperties() {} + + 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: + //------------------------------------------------------------------ + // Constructors and Destructors + //------------------------------------------------------------------ + 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); + } + + virtual ~RegisterContextGo() {} + + virtual bool + ReadRegister(const lldb_private::RegisterInfo *reg_info, lldb_private::RegisterValue ®_value) + { + 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; + } + } + + virtual bool + WriteRegister(const lldb_private::RegisterInfo *reg_info, const lldb_private::RegisterValue ®_value) + { + 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); +}; + +} // 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); +} + +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."; +} + +OperatingSystemGo::OperatingSystemGo(lldb_private::Process *process) + : OperatingSystem(process) + , m_reginfo(new DynamicRegisterInfo) +{ +} + +OperatingSystemGo::~OperatingSystemGo() +{ +} + +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 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. + + 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 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; +} Index: lldb/trunk/source/Plugins/OperatingSystem/Python/OperatingSystemPython.cpp =================================================================== --- lldb/trunk/source/Plugins/OperatingSystem/Python/OperatingSystemPython.cpp +++ lldb/trunk/source/Plugins/OperatingSystem/Python/OperatingSystemPython.cpp @@ -42,9 +42,7 @@ void OperatingSystemPython::Initialize() { - PluginManager::RegisterPlugin (GetPluginNameStatic(), - GetPluginDescriptionStatic(), - CreateInstance); + PluginManager::RegisterPlugin(GetPluginNameStatic(), GetPluginDescriptionStatic(), CreateInstance, nullptr); } void Index: lldb/trunk/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp =================================================================== --- lldb/trunk/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp +++ lldb/trunk/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp @@ -2044,6 +2044,10 @@ if (!thread_sp->StopInfoIsUpToDate()) { thread_sp->SetStopInfo (StopInfoSP()); + // If there's a memory thread backed by this thread, we need to use it to calcualte StopInfo. + ThreadSP memory_thread_sp = m_thread_list.FindThreadByProtocolID(thread_sp->GetProtocolID()); + if (memory_thread_sp) + thread_sp = memory_thread_sp; if (exc_type != 0) { Index: lldb/trunk/source/Symbol/ObjectFile.cpp =================================================================== --- lldb/trunk/source/Symbol/ObjectFile.cpp +++ lldb/trunk/source/Symbol/ObjectFile.cpp @@ -351,6 +351,7 @@ case eSectionTypeZeroFill: case eSectionTypeDataObjCMessageRefs: case eSectionTypeDataObjCCFStrings: + case eSectionTypeGoSymtab: return eAddressClassData; case eSectionTypeDebug: case eSectionTypeDWARFDebugAbbrev: Index: lldb/trunk/source/Target/Process.cpp =================================================================== --- lldb/trunk/source/Target/Process.cpp +++ lldb/trunk/source/Target/Process.cpp @@ -6553,6 +6553,8 @@ if (language_runtime_sp) language_runtime_sp->ModulesDidLoad(module_list); } + + LoadOperatingSystemPlugin(false); } void Index: lldb/trunk/source/Target/StackFrameList.cpp =================================================================== --- lldb/trunk/source/Target/StackFrameList.cpp +++ lldb/trunk/source/Target/StackFrameList.cpp @@ -108,6 +108,8 @@ if (m_show_inlined_frames) { GetFramesUpTo(0); + if (m_frames.size() == 0) + return; if (!m_frames[0]->IsInlined()) { m_current_inlined_depth = UINT32_MAX; Index: lldb/trunk/source/Target/ThreadList.cpp =================================================================== --- lldb/trunk/source/Target/ThreadList.cpp +++ lldb/trunk/source/Target/ThreadList.cpp @@ -773,7 +773,8 @@ const uint32_t num_threads = m_threads.size(); for (uint32_t idx = 0; idx < num_threads; ++idx) { - if (m_threads[idx]->GetID() == tid) + ThreadSP backing_thread = m_threads[idx]->GetBackingThread(); + if (m_threads[idx]->GetID() == tid || (backing_thread && backing_thread->GetID() == tid)) { thread_is_alive = true; break; Index: lldb/trunk/source/Utility/ConvertEnum.cpp =================================================================== --- lldb/trunk/source/Utility/ConvertEnum.cpp +++ lldb/trunk/source/Utility/ConvertEnum.cpp @@ -107,6 +107,8 @@ return "eh-frame"; case eSectionTypeCompactUnwind: return "compact-unwind"; + case eSectionTypeGoSymtab: + return "go-symtab"; case eSectionTypeOther: return "regular"; } Index: lldb/trunk/test/lang/go/goroutines/TestGoroutines.py =================================================================== --- lldb/trunk/test/lang/go/goroutines/TestGoroutines.py +++ lldb/trunk/test/lang/go/goroutines/TestGoroutines.py @@ -0,0 +1,87 @@ +"""Test the Go OS Plugin.""" + +import os, time +import unittest2 +import lldb +import lldbutil +from lldbtest import * + +class TestGoASTContext(TestBase): + + mydir = TestBase.compute_mydir(__file__) + + @python_api_test + @skipIfRemote # Not remote test suite ready + @skipUnlessGoInstalled + def test_goroutine_plugin(self): + """Test goroutine as threads support.""" + self.buildGo() + self.launchProcess() + self.check_goroutines() + + def setUp(self): + # Call super's setUp(). + TestBase.setUp(self) + # Find the line numbers to break inside main(). + self.main_source = "main.go" + self.break_line1 = line_number(self.main_source, '// stop1') + self.break_line2 = line_number(self.main_source, '// stop2') + self.break_line3 = line_number(self.main_source, '// stop3') + + def launchProcess(self): + exe = os.path.join(os.getcwd(), "a.out") + + target = self.dbg.CreateTarget(exe) + self.assertTrue(target, VALID_TARGET) + + self.bpt1 = target.BreakpointCreateByLocation(self.main_source, self.break_line1) + self.assertTrue(self.bpt1, VALID_BREAKPOINT) + self.bpt2 = target.BreakpointCreateByLocation(self.main_source, self.break_line2) + self.assertTrue(self.bpt2, VALID_BREAKPOINT) + self.bpt3 = target.BreakpointCreateByLocation(self.main_source, self.break_line3) + self.assertTrue(self.bpt3, VALID_BREAKPOINT) + + # Now launch the process, and do not stop at entry point. + process = target.LaunchSimple (None, None, self.get_process_working_directory()) + + self.assertTrue(process, PROCESS_IS_VALID) + + # The stop reason of the thread should be breakpoint. + thread_list = lldbutil.get_threads_stopped_at_breakpoint (process, self.bpt1) + + # Make sure we stopped at the first breakpoint. + self.assertTrue (len(thread_list) != 0, "No thread stopped at our breakpoint.") + self.assertTrue (len(thread_list) == 1, "More than one thread stopped at our breakpoint.") + + frame = thread_list[0].GetFrameAtIndex(0) + self.assertTrue (frame, "Got a valid frame 0 frame.") + + def check_goroutines(self): + self.assertLess(len(self.process().threads), 20) + self.process().Continue() + + # Make sure we stopped at the 2nd breakpoint + thread_list = lldbutil.get_threads_stopped_at_breakpoint (self.process(), self.bpt2) + self.assertTrue (len(thread_list) != 0, "No thread stopped at our breakpoint.") + self.assertTrue (len(thread_list) == 1, "More than one thread stopped at our breakpoint.") + + # There's (at least) 21 goroutines. + self.assertGreater(len(self.process().threads), 20) + # self.dbg.HandleCommand("log enable lldb os") + + # Now test that stepping works if the memory thread moves to a different backing thread. + for i in xrange(11): + self.thread().StepOver() + self.assertEqual(lldb.eStopReasonPlanComplete, self.thread().GetStopReason(), self.thread().GetStopDescription(100)) + + # Disable the plugin and make sure the goroutines disappear + self.dbg.HandleCommand("settings set plugin.os.goroutines.enable false") + self.thread().StepInstruction(False) + self.assertLess(len(self.process().threads), 20) + + +if __name__ == '__main__': + import atexit + lldb.SBDebugger.Initialize() + atexit.register(lambda: lldb.SBDebugger.Terminate()) + unittest2.main() Index: lldb/trunk/test/lang/go/goroutines/main.go =================================================================== --- lldb/trunk/test/lang/go/goroutines/main.go +++ lldb/trunk/test/lang/go/goroutines/main.go @@ -0,0 +1,89 @@ +package main + +import ( + "fmt" + "runtime" +) + +type philosopher struct { + i int + forks [2]chan bool + eating chan int + done chan struct{} +} + +func (p philosopher) run() { + for { + select { + case <-p.done: + return + case <-p.forks[0]: + p.eat() + } + } +} + +func (p philosopher) eat() { + select { + case <-p.done: + return + case <-p.forks[1]: + p.eating <- p.i + p.forks[0] <- true + p.forks[1] <- true + runtime.Gosched() + } +} + +func startPhilosophers(n int) (chan struct{}, chan int) { + philosophers := make([]*philosopher, n) + chans := make([]chan bool, n) + for i := range chans { + chans[i] = make(chan bool, 1) + chans[i] <- true + } + eating := make(chan int, n) + done := make(chan struct{}) + for i := range philosophers { + var min, max int + if i == n - 1 { + min = 0 + max = i + } else { + min = i + max = i + 1 + } + philosophers[i] = &philosopher{i: i, forks: [2]chan bool{chans[min], chans[max]}, eating: eating, done: done} + go philosophers[i].run() + } + return done, eating +} + +func wait(c chan int) { + fmt.Println(<- c) + runtime.Gosched() +} + +func main() { + // Restrict go to 1 real thread so we can be sure we're seeing goroutines + // and not threads. + runtime.GOMAXPROCS(1) + // Create a bunch of goroutines + done, eating := startPhilosophers(20) // stop1 + // Now turn up the number of threads so this goroutine is likely to get + // scheduled on a different thread. + runtime.GOMAXPROCS(runtime.NumCPU()) // stop2 + // Now let things run. Hopefully we'll bounce around + wait(eating) + wait(eating) + wait(eating) + wait(eating) + wait(eating) + wait(eating) + wait(eating) + wait(eating) + wait(eating) + wait(eating) + close(done) + fmt.Println("done") // stop3 +}