Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[lldb] Swift OS plugin #9839

Draft
wants to merge 4 commits into
base: stable/20240723
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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"
Expand Down Expand Up @@ -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());
Expand All @@ -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);
}
Expand All @@ -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)) {
Expand All @@ -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;
Expand Down Expand Up @@ -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<uint32_t>
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<lldb::addr_t>
FindSymbolForSwiftObject(Process &process, RuntimeKind runtime_kind,
StringRef object, const SymbolType sym_type) {
Expand Down Expand Up @@ -2524,4 +2595,31 @@ std::optional<lldb::addr_t> SwiftLanguageRuntime::TrySkipVirtualParentProlog(
: sc.function->GetPrologueByteSize();
return pc_value + prologue_size;
}

std::optional<lldb::addr_t> 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
Original file line number Diff line number Diff line change
Expand Up @@ -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<uint32_t> findConcurrencyDebugVersion(Process &process);
/// \}

/// PluginInterface protocol.
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -841,6 +853,10 @@ struct AsyncUnwindRegisterNumbers {

std::optional<AsyncUnwindRegisterNumbers>
GetAsyncUnwindRegisterNumbers(llvm::Triple::ArchType triple);

/// Inspects thread local storage to find the address of the currently executing
/// task.
std::optional<lldb::addr_t> GetTaskAddrFromThreadLocalStorage(Thread &thread);
} // namespace lldb_private

#endif // liblldb_SwiftLanguageRuntime_h_
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Copy link
Author

@felipepiovezan felipepiovezan Jan 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

to refresh reviewer's memories: this is the thread plan to "run through a task switch and stop when we schedule this same async function again. This plan now sets thread specific breakpoints, since they are now task breakpoints!

If we somehow push this type of plan without having the OS plugin, we are at a risk of breaking in the wrong task. But if the plugin is not there and there are tasks, we are going to have much bigger problems anyway.

}

bool ValidatePlan(Stream *error) override {
Expand Down
1 change: 1 addition & 0 deletions lldb/source/Plugins/OperatingSystem/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
if (LLDB_ENABLE_PYTHON)
add_subdirectory(Python)
add_subdirectory(SwiftTasks)
endif()
12 changes: 12 additions & 0 deletions lldb/source/Plugins/OperatingSystem/SwiftTasks/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
add_lldb_library(lldbPluginOperatingSystemSwiftTasks PLUGIN
OperatingSystemSwiftTasks.cpp

LINK_LIBS
lldbCore
lldbInterpreter
lldbSymbol
lldbTarget
lldbValueObject
lldbPluginProcessUtility
lldbPluginSwiftLanguageRuntime
)
Original file line number Diff line number Diff line change
@@ -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 <memory>

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<uint32_t> 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<uint64_t> 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<ThreadMemory>(*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<uint64_t> OperatingSystemSwiftTasks::FindTaskId(Thread &thread) {
std::optional<addr_t> 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
Loading