diff --git a/lldb/source/Plugins/LanguageRuntime/Swift/SwiftLanguageRuntime.cpp b/lldb/source/Plugins/LanguageRuntime/Swift/SwiftLanguageRuntime.cpp index fc976507b6f1e01..4a9cda5f80a055d 100644 --- a/lldb/source/Plugins/LanguageRuntime/Swift/SwiftLanguageRuntime.cpp +++ b/lldb/source/Plugins/LanguageRuntime/Swift/SwiftLanguageRuntime.cpp @@ -42,6 +42,7 @@ #include "lldb/Utility/LLDBLog.h" #include "lldb/Utility/Log.h" #include "lldb/Utility/OptionParsing.h" +#include "lldb/Utility/StructuredData.h" #include "lldb/Utility/Timer.h" #include "lldb/ValueObject/ValueObject.h" #include "lldb/ValueObject/ValueObjectCast.h" @@ -51,8 +52,9 @@ #include "lldb/lldb-enumerations.h" #include "swift/AST/ASTMangler.h" #include "swift/Demangling/Demangle.h" -#include "swift/RemoteInspection/ReflectionContext.h" #include "swift/RemoteAST/RemoteAST.h" +#include "swift/RemoteInspection/ReflectionContext.h" +#include "swift/Threading/ThreadLocalStorage.h" #include "clang/AST/ASTContext.h" #include "clang/AST/DeclCXX.h" @@ -84,6 +86,10 @@ const char *SwiftLanguageRuntime::GetStandardLibraryBaseName() { return "swiftCore"; } +const char *SwiftLanguageRuntime::GetConcurrencyLibraryBaseName() { + return "swift_Concurrency"; +} + static ConstString GetStandardLibraryName(Process &process) { // This result needs to be stored in the constructor. PlatformSP platform_sp(process.GetTarget().GetPlatform()); @@ -93,6 +99,14 @@ static ConstString GetStandardLibraryName(Process &process) { return {}; } +static ConstString GetConcurrencyLibraryName(Process &process) { + PlatformSP platform_sp = process.GetTarget().GetPlatform(); + if (platform_sp) + return platform_sp->GetFullNameForDylib( + ConstString(SwiftLanguageRuntime::GetConcurrencyLibraryBaseName())); + return {}; +} + ConstString SwiftLanguageRuntime::GetStandardLibraryName() { return ::GetStandardLibraryName(*m_process); } @@ -102,6 +116,12 @@ static bool IsModuleSwiftRuntime(lldb_private::Process &process, return module.GetFileSpec().GetFilename() == GetStandardLibraryName(process); } +static bool IsModuleSwiftConcurrency(lldb_private::Process &process, + lldb_private::Module &module) { + return module.GetFileSpec().GetFilename() == + GetConcurrencyLibraryName(process); +} + AppleObjCRuntimeV2 * SwiftLanguageRuntime::GetObjCRuntime(lldb_private::Process &process) { if (auto objc_runtime = ObjCLanguageRuntime::Get(process)) { @@ -124,6 +144,11 @@ static bool IsStaticSwiftRuntime(Module &image) { return image.FindFirstSymbolWithNameAndType(swift_reflection_version_sym); } +static bool IsStaticSwiftConcurrency(Module &image) { + static const ConstString task_switch_symbol("_swift_task_switch"); + return image.FindFirstSymbolWithNameAndType(task_switch_symbol); +} + /// \return the Swift or Objective-C runtime found in the loaded images. static ModuleSP findRuntime(Process &process, RuntimeKind runtime_kind) { AppleObjCRuntimeV2 *objc_runtime = nullptr; @@ -161,6 +186,52 @@ static ModuleSP findRuntime(Process &process, RuntimeKind runtime_kind) { return runtime_image; } +ModuleSP SwiftLanguageRuntime::findConcurrencyModule(Process &process) { + ModuleSP concurrency_module; + process.GetTarget().GetImages().ForEach([&](const ModuleSP &candidate) { + if (candidate && IsModuleSwiftConcurrency(process, *candidate)) { + concurrency_module = candidate; + return false; + } + return true; + }); + if (concurrency_module) + return concurrency_module; + + // Do a more expensive search for a statically linked Swift runtime. + process.GetTarget().GetImages().ForEach([&](const ModuleSP &candidate) { + if (candidate && IsStaticSwiftConcurrency(*candidate)) { + concurrency_module = candidate; + return false; + } + return true; + }); + return concurrency_module; +} + +std::optional +SwiftLanguageRuntime::findConcurrencyDebugVersion(Process &process) { + ModuleSP concurrency_module = findConcurrencyModule(process); + if (!concurrency_module) + return {}; + + const Symbol *version_symbol = + concurrency_module->FindFirstSymbolWithNameAndType( + ConstString("_swift_concurrency_debug_internal_layout_version")); + if (!version_symbol) + return 0; + + addr_t symbol_addr = version_symbol->GetLoadAddress(&process.GetTarget()); + if (symbol_addr == LLDB_INVALID_ADDRESS) + return {}; + Status error; + uint64_t version = process.ReadUnsignedIntegerFromMemory( + symbol_addr, /*width*/ 4, /*fail_value=*/0, error); + if (error.Fail()) + return {}; + return version; +} + static std::optional FindSymbolForSwiftObject(Process &process, RuntimeKind runtime_kind, StringRef object, const SymbolType sym_type) { @@ -2524,4 +2595,31 @@ std::optional SwiftLanguageRuntime::TrySkipVirtualParentProlog( : sc.function->GetPrologueByteSize(); return pc_value + prologue_size; } + +std::optional GetTaskAddrFromThreadLocalStorage(Thread &thread) { + // Compute the thread local storage address for this thread. + StructuredData::ObjectSP info_root_sp = thread.GetExtendedInfo(); + if (!info_root_sp) + return {}; + StructuredData::ObjectSP node = + info_root_sp->GetObjectForDotSeparatedPath("tsd_address"); + if (!node) + return {}; + StructuredData::UnsignedInteger *raw_tsd_addr = node->GetAsUnsignedInteger(); + if (!raw_tsd_addr) + return {}; + addr_t tsd_addr = raw_tsd_addr->GetUnsignedIntegerValue(); + + // Offset of the Task pointer in a Thread's local storage. + Process &process = *thread.GetProcess(); + size_t ptr_size = process.GetAddressByteSize(); + uint64_t task_ptr_offset_in_tls = + swift::tls_get_key(swift::tls_key::concurrency_task) * ptr_size; + addr_t task_addr_location = tsd_addr + task_ptr_offset_in_tls; + Status error; + addr_t task_addr = process.ReadPointerFromMemory(task_addr_location, error); + if (error.Fail()) + return {}; + return task_addr; +} } // namespace lldb_private diff --git a/lldb/source/Plugins/LanguageRuntime/Swift/SwiftLanguageRuntime.h b/lldb/source/Plugins/LanguageRuntime/Swift/SwiftLanguageRuntime.h index 902d8f22b8c6b6a..2c2d4ebd8cacce9 100644 --- a/lldb/source/Plugins/LanguageRuntime/Swift/SwiftLanguageRuntime.h +++ b/lldb/source/Plugins/LanguageRuntime/Swift/SwiftLanguageRuntime.h @@ -97,6 +97,16 @@ class SwiftLanguageRuntime : public LanguageRuntime { static SwiftLanguageRuntime *Get(lldb::ProcessSP process_sp) { return SwiftLanguageRuntime::Get(process_sp.get()); } + + /// Returns the Module containing the Swift Concurrency runtime, if it exists. + static lldb::ModuleSP findConcurrencyModule(Process &process); + + /// Returns the version of the swift concurrency runtime debug layout. + /// If no Concurrency module is found, or if errors occur, nullopt is + /// returned. + /// Returns 0 for versions of the module prior to the introduction + /// of versioning. + static std::optional findConcurrencyDebugVersion(Process &process); /// \} /// PluginInterface protocol. @@ -473,6 +483,8 @@ class SwiftLanguageRuntime : public LanguageRuntime { static const char *GetErrorBackstopName(); ConstString GetStandardLibraryName(); static const char *GetStandardLibraryBaseName(); + static const char *GetConcurrencyLibraryBaseName(); + static bool IsSwiftClassName(const char *name); /// Determines wether \c variable is the "self" object. static bool IsSelf(Variable &variable); @@ -841,6 +853,10 @@ struct AsyncUnwindRegisterNumbers { std::optional GetAsyncUnwindRegisterNumbers(llvm::Triple::ArchType triple); + +/// Inspects thread local storage to find the address of the currently executing +/// task. +std::optional GetTaskAddrFromThreadLocalStorage(Thread &thread); } // namespace lldb_private #endif // liblldb_SwiftLanguageRuntime_h_ diff --git a/lldb/source/Plugins/LanguageRuntime/Swift/SwiftLanguageRuntimeNames.cpp b/lldb/source/Plugins/LanguageRuntime/Swift/SwiftLanguageRuntimeNames.cpp index d87e7611a05b607..b61d8fb73fb364d 100644 --- a/lldb/source/Plugins/LanguageRuntime/Swift/SwiftLanguageRuntimeNames.cpp +++ b/lldb/source/Plugins/LanguageRuntime/Swift/SwiftLanguageRuntimeNames.cpp @@ -302,6 +302,7 @@ class ThreadPlanRunToAddressOnAsyncCtx : public ThreadPlan { auto &target = thread.GetProcess()->GetTarget(); m_funclet_bp = target.CreateBreakpoint(destination_addr, true, false); m_funclet_bp->SetBreakpointKind("async-run-to-funclet"); + m_funclet_bp->SetThreadID(thread.GetID()); } bool ValidatePlan(Stream *error) override { diff --git a/lldb/source/Plugins/OperatingSystem/CMakeLists.txt b/lldb/source/Plugins/OperatingSystem/CMakeLists.txt index 06d909b862a0480..faa8d1435746479 100644 --- a/lldb/source/Plugins/OperatingSystem/CMakeLists.txt +++ b/lldb/source/Plugins/OperatingSystem/CMakeLists.txt @@ -1,3 +1,4 @@ if (LLDB_ENABLE_PYTHON) add_subdirectory(Python) + add_subdirectory(SwiftTasks) endif() diff --git a/lldb/source/Plugins/OperatingSystem/SwiftTasks/CMakeLists.txt b/lldb/source/Plugins/OperatingSystem/SwiftTasks/CMakeLists.txt new file mode 100644 index 000000000000000..31c81c5f4643216 --- /dev/null +++ b/lldb/source/Plugins/OperatingSystem/SwiftTasks/CMakeLists.txt @@ -0,0 +1,12 @@ +add_lldb_library(lldbPluginOperatingSystemSwiftTasks PLUGIN + OperatingSystemSwiftTasks.cpp + + LINK_LIBS + lldbCore + lldbInterpreter + lldbSymbol + lldbTarget + lldbValueObject + lldbPluginProcessUtility + lldbPluginSwiftLanguageRuntime + ) diff --git a/lldb/source/Plugins/OperatingSystem/SwiftTasks/OperatingSystemSwiftTasks.cpp b/lldb/source/Plugins/OperatingSystem/SwiftTasks/OperatingSystemSwiftTasks.cpp new file mode 100644 index 000000000000000..407d03795f92a3f --- /dev/null +++ b/lldb/source/Plugins/OperatingSystem/SwiftTasks/OperatingSystemSwiftTasks.cpp @@ -0,0 +1,151 @@ +//===-- OperatingSystemSwiftTasks.cpp -------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#if LLDB_ENABLE_SWIFT + +#include "OperatingSystemSwiftTasks.h" +#include "Plugins/LanguageRuntime/Swift/SwiftLanguageRuntime.h" +#include "Plugins/Process/Utility/ThreadMemory.h" +#include "lldb/Core/Debugger.h" +#include "lldb/Core/Module.h" +#include "lldb/Core/PluginManager.h" +#include "lldb/Target/Process.h" +#include "lldb/Target/Thread.h" +#include "lldb/Target/ThreadList.h" +#include "lldb/Utility/LLDBLog.h" + +#include + +using namespace lldb; +using namespace lldb_private; + +LLDB_PLUGIN_DEFINE(OperatingSystemSwiftTasks) + +void OperatingSystemSwiftTasks::Initialize() { + PluginManager::RegisterPlugin(GetPluginNameStatic(), + GetPluginDescriptionStatic(), CreateInstance, + nullptr); +} + +void OperatingSystemSwiftTasks::Terminate() { + PluginManager::UnregisterPlugin(CreateInstance); +} + +OperatingSystem *OperatingSystemSwiftTasks::CreateInstance(Process *process, + bool force) { + if (!process) + return nullptr; + + Log *log = GetLog(LLDBLog::OS); + std::optional concurrency_version = + SwiftLanguageRuntime::findConcurrencyDebugVersion(*process); + if (!concurrency_version) { + LLDB_LOG(log, + "OperatingSystemSwiftTasks: did not find concurrency module."); + return nullptr; + } + + if (*concurrency_version <= 1) + return new OperatingSystemSwiftTasks(*process); + LLDB_LOGF(log, + "OperatingSystemSwiftTasks: got a concurrency version symbol of %u", + *concurrency_version); + return nullptr; +} + +llvm::StringRef OperatingSystemSwiftTasks::GetPluginDescriptionStatic() { + return "Operating system plug-in converting Swift Tasks into Threads."; +} + +OperatingSystemSwiftTasks::~OperatingSystemSwiftTasks() = default; + +OperatingSystemSwiftTasks::OperatingSystemSwiftTasks( + lldb_private::Process &process) + : OperatingSystem(&process) { + size_t ptr_size = process.GetAddressByteSize(); + // Offset of a Task ID inside a Task data structure, guaranteed by the ABI. + // See Job in swift/RemoteInspection/RuntimeInternals.h. + m_task_id_offset = 4 * ptr_size + 4; +} + +bool OperatingSystemSwiftTasks::UpdateThreadList(ThreadList &old_thread_list, + ThreadList &core_thread_list, + ThreadList &new_thread_list) { + Log *log = GetLog(LLDBLog::OS); + LLDB_LOG(log, "OperatingSystemSwiftTasks: Updating thread list"); + + for (const ThreadSP &real_thread : core_thread_list.Threads()) { + std::optional task_id = FindTaskId(*real_thread); + + // If this is not a thread running a Task, add it to the list as is. + if (!task_id) { + new_thread_list.AddThread(real_thread); + LLDB_LOGF(log, + "OperatingSystemSwiftTasks: thread %" PRIx64 + " is not executing a Task", + real_thread->GetID()); + continue; + } + + // Mask higher bits to avoid conflicts with core thread IDs. + uint64_t masked_task_id = 0x0000000f00000000 | *task_id; + + ThreadSP swift_thread = [&]() -> ThreadSP { + // If we already had a thread for this Task in the last stop, re-use it. + if (ThreadSP old_thread = old_thread_list.FindThreadByID(masked_task_id); + IsOperatingSystemPluginThread(old_thread)) + return old_thread; + + std::string name = llvm::formatv("Swift Task {0:x}", *task_id); + llvm::StringRef queue_name = ""; + return std::make_shared(*m_process, masked_task_id, name, + queue_name, + /*register_data_addr*/ 0); + }(); + + swift_thread->SetBackingThread(real_thread); + new_thread_list.AddThread(swift_thread); + LLDB_LOGF(log, + "OperatingSystemSwiftTasks: mapping thread IDs: %" PRIx64 + " -> %" PRIx64, + real_thread->GetID(), swift_thread->GetID()); + } + return true; +} + +void OperatingSystemSwiftTasks::ThreadWasSelected(Thread *thread) {} + +RegisterContextSP OperatingSystemSwiftTasks::CreateRegisterContextForThread( + Thread *thread, addr_t reg_data_addr) { + if (!thread || !IsOperatingSystemPluginThread(thread->shared_from_this())) + return nullptr; + return thread->GetRegisterContext(); +} + +StopInfoSP OperatingSystemSwiftTasks::CreateThreadStopReason( + lldb_private::Thread *thread) { + return thread->GetStopInfo(); +} + +std::optional OperatingSystemSwiftTasks::FindTaskId(Thread &thread) { + std::optional task_addr = GetTaskAddrFromThreadLocalStorage(thread); + if (!task_addr) + return {}; + + Status error; + // The Task ID is at offset m_task_id_offset from the Task pointer. + constexpr uint32_t num_bytes_task_id = 4; + auto task_id = m_process->ReadUnsignedIntegerFromMemory( + *task_addr + m_task_id_offset, num_bytes_task_id, LLDB_INVALID_ADDRESS, + error); + if (error.Fail()) + return {}; + return task_id; +} + +#endif // #if LLDB_ENABLE_SWIFT diff --git a/lldb/source/Plugins/OperatingSystem/SwiftTasks/OperatingSystemSwiftTasks.h b/lldb/source/Plugins/OperatingSystem/SwiftTasks/OperatingSystemSwiftTasks.h new file mode 100644 index 000000000000000..08ab999b8ac65ba --- /dev/null +++ b/lldb/source/Plugins/OperatingSystem/SwiftTasks/OperatingSystemSwiftTasks.h @@ -0,0 +1,59 @@ +//===-- OperatingSystemSwiftTasks.h -----------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef liblldb_OperatingSystemSwiftTasks_h_ +#define liblldb_OperatingSystemSwiftTasks_h_ + +#if LLDB_ENABLE_SWIFT + +#include "lldb/Target/OperatingSystem.h" + +namespace lldb_private { +class OperatingSystemSwiftTasks : public OperatingSystem { +public: + OperatingSystemSwiftTasks(Process &process); + ~OperatingSystemSwiftTasks() override; + + static OperatingSystem *CreateInstance(Process *process, bool force); + static void Initialize(); + static void Terminate(); + static llvm::StringRef GetPluginNameStatic() { return "swift"; } + static llvm::StringRef GetPluginDescriptionStatic(); + + /// PluginInterface Methods + + llvm::StringRef GetPluginName() override { return GetPluginNameStatic(); } + + /// OperatingSystem Methods + + bool UpdateThreadList(ThreadList &old_thread_list, + ThreadList &real_thread_list, + ThreadList &new_thread_list) override; + + void ThreadWasSelected(Thread *thread) override; + + lldb::RegisterContextSP + CreateRegisterContextForThread(Thread *thread, + lldb::addr_t reg_data_addr) override; + + lldb::StopInfoSP CreateThreadStopReason(Thread *thread) override; + + bool DoesPluginReportAllThreads() override { return false; } + +private: + /// Find the Task ID of the task being executed by `thread`, if any. + std::optional FindTaskId(Thread &thread); + + /// The offset of the Task ID inside a Task data structure. + size_t m_task_id_offset; +}; +} // namespace lldb_private + +#endif // LLDB_ENABLE_SWIFT + +#endif // liblldb_OperatingSystemSwiftTasks_h_ diff --git a/lldb/source/Plugins/Process/Utility/ThreadMemory.cpp b/lldb/source/Plugins/Process/Utility/ThreadMemory.cpp index 550b53688fd39e2..8dd0426a5348b91 100644 --- a/lldb/source/Plugins/Process/Utility/ThreadMemory.cpp +++ b/lldb/source/Plugins/Process/Utility/ThreadMemory.cpp @@ -67,6 +67,7 @@ ThreadMemory::CreateRegisterContextForFrame(StackFrame *frame) { bool ThreadMemory::CalculateStopInfo() { if (m_backing_thread_sp) { + SetStopInfo(StopInfoSP()); lldb::StopInfoSP backing_stop_info_sp( m_backing_thread_sp->GetPrivateStopInfo()); if (backing_stop_info_sp && @@ -75,6 +76,8 @@ bool ThreadMemory::CalculateStopInfo() { SetStopInfo(backing_stop_info_sp); return true; } + if (!backing_stop_info_sp && this->m_stop_info_sp) + return true; } else { ProcessSP process_sp(GetProcess()); diff --git a/lldb/test/API/lang/swift/async/stepping/step_over_lots_of_tasks/Makefile b/lldb/test/API/lang/swift/async/stepping/step_over_lots_of_tasks/Makefile new file mode 100644 index 000000000000000..cca30b939e652f7 --- /dev/null +++ b/lldb/test/API/lang/swift/async/stepping/step_over_lots_of_tasks/Makefile @@ -0,0 +1,3 @@ +SWIFT_SOURCES := main.swift +SWIFTFLAGS_EXTRAS := -parse-as-library +include Makefile.rules diff --git a/lldb/test/API/lang/swift/async/stepping/step_over_lots_of_tasks/TestSwiftAsyncSteppingManyTasks.py b/lldb/test/API/lang/swift/async/stepping/step_over_lots_of_tasks/TestSwiftAsyncSteppingManyTasks.py new file mode 100644 index 000000000000000..92ed5634277cc37 --- /dev/null +++ b/lldb/test/API/lang/swift/async/stepping/step_over_lots_of_tasks/TestSwiftAsyncSteppingManyTasks.py @@ -0,0 +1,64 @@ +import lldb +from lldbsuite.test.decorators import * +import lldbsuite.test.lldbtest as lldbtest +import lldbsuite.test.lldbutil as lldbutil + + +@skipIfAsan # rdar://138777205 +class TestCase(lldbtest.TestBase): + + def check_is_in_line(self, thread, expected_linenum, expected_tid): + """Checks that thread has tid == expected_tid and is stopped at expected_linenum""" + self.assertEqual(expected_tid, thread.GetThreadID()) + + frame = thread.frames[0] + line_entry = frame.GetLineEntry() + self.assertEqual(expected_linenum, line_entry.GetLine()) + + @swiftTest + @skipIf(oslist=["windows", "linux"]) + def test_step_over_main(self): + self.build() + + source_file = lldb.SBFileSpec("main.swift") + target, process, thread, bkpt = lldbutil.run_to_source_breakpoint( + self, "Breakpoint main", source_file + ) + + main_task_id = thread.GetThreadID() + main_first_line = thread.frames[0].GetLineEntry().GetLine() + num_lines_main = 7 + + for line_offset in range(1, num_lines_main): + thread.StepOver() + self.check_is_in_line(thread, main_first_line + line_offset, main_task_id) + + @swiftTest + @skipIf(oslist=["windows", "linux"]) + def test_step_over_top_level_fibonacci(self): + self.build() + + source_file = lldb.SBFileSpec("main.swift") + target, process, thread, bkpt = lldbutil.run_to_source_breakpoint( + self, "Breakpoint main", source_file + ) + + main_task_id = thread.GetThreadID() + fib_bp = target.BreakpointCreateBySourceRegex("Breakpoint fib", source_file) + lldbutil.continue_to_breakpoint(process, fib_bp) + + # Get any of the threads that might have reached this breakpoint. + # Any thread should work: their initial value for `n` is > 2, + # so all threads do the full function body the first time around. + thread_in_fib = lldbutil.get_threads_stopped_at_breakpoint(process, fib_bp)[0] + thread_id_in_fib = thread_in_fib.GetThreadID() + fib_bp.SetEnabled(False) + + fib_first_line = thread_in_fib.frames[0].GetLineEntry().GetLine() + num_lines_fib = 5 + for line_offset in range(1, num_lines_fib): + thread_in_fib.StepOver() + self.assertEqual(process.GetSelectedThread(), thread_in_fib) + self.check_is_in_line( + thread_in_fib, fib_first_line + line_offset, thread_id_in_fib + ) diff --git a/lldb/test/API/lang/swift/async/stepping/step_over_lots_of_tasks/main.swift b/lldb/test/API/lang/swift/async/stepping/step_over_lots_of_tasks/main.swift new file mode 100644 index 000000000000000..87fe4707041aef5 --- /dev/null +++ b/lldb/test/API/lang/swift/async/stepping/step_over_lots_of_tasks/main.swift @@ -0,0 +1,25 @@ +@main enum entry { + static func main() async { + print("Breakpoint main") + async let fib5_task = fib(n: 5) + async let fib6_task = fib(n: 6) + let fib4 = await fib(n: 4) + let fib5 = await fib5_task + let fib6 = await fib6_task + print(fib4, fib5, fib6) + } +} + +func fib(n: Int) async -> Int { + if (n == 0) { + return 1 + } + if (n == 1) { + return 1 + } + async let n1_task = fib(n: n - 1) // Breakpoint fib + async let n2_task = fib(n: n - 2) + let n1 = await n1_task + let n2 = await n2_task + return n1 + n2 +}