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 5 commits into
base: swift/release/6.1
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
1 change: 1 addition & 0 deletions lldb/include/lldb/Core/FormatEntity.h
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ struct Entry {
FunctionNameWithArgs,
FunctionNameNoArgs,
FunctionMangledName,
FunctionCFA,
FunctionAddrOffset,
FunctionAddrOffsetConcrete,
FunctionLineOffset,
Expand Down
2 changes: 2 additions & 0 deletions lldb/include/lldb/Target/OperatingSystem.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ class OperatingSystem : public PluginInterface {

virtual bool IsOperatingSystemPluginThread(const lldb::ThreadSP &thread_sp);

virtual std::optional<bool> DoesPluginReportAllThreads() { return {}; }

protected:
// Member variables.
Process
Expand Down
42 changes: 42 additions & 0 deletions lldb/source/Core/FormatEntity.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

#include "lldb/Core/FormatEntity.h"

#include "Utility/ARM64_DWARF_Registers.h"
#include "lldb/Core/Address.h"
#include "lldb/Core/AddressRange.h"
#include "lldb/Core/Debugger.h"
Expand Down Expand Up @@ -115,6 +116,7 @@ constexpr Definition g_function_child_entries[] = {
Definition("name-without-args", EntryType::FunctionNameNoArgs),
Definition("name-with-args", EntryType::FunctionNameWithArgs),
Definition("mangled-name", EntryType::FunctionMangledName),
Definition("cfa", EntryType::FunctionCFA),
Definition("addr-offset", EntryType::FunctionAddrOffset),
Definition("concrete-only-addr-offset-no-padding",
EntryType::FunctionAddrOffsetConcrete),
Expand Down Expand Up @@ -342,6 +344,7 @@ const char *FormatEntity::Entry::TypeToCString(Type t) {
ENUM_TO_CSTR(FunctionNameWithArgs);
ENUM_TO_CSTR(FunctionNameNoArgs);
ENUM_TO_CSTR(FunctionMangledName);
ENUM_TO_CSTR(FunctionCFA);
ENUM_TO_CSTR(FunctionAddrOffset);
ENUM_TO_CSTR(FunctionAddrOffsetConcrete);
ENUM_TO_CSTR(FunctionLineOffset);
Expand Down Expand Up @@ -437,6 +440,15 @@ static bool DumpAddressAndContent(Stream &s, const SymbolContext *sc,
return true;
}

lldb::addr_t GetAsyncContext(RegisterContext *regctx) {
if (!regctx)
return LLDB_INVALID_ADDRESS;
auto arch = regctx->CalculateTarget()->GetArchitecture();
auto reg = regctx->ConvertRegisterKindToRegisterNumber(
RegisterKind::eRegisterKindDWARF, arm64_dwarf::x22);
return regctx->ReadRegisterAsUnsigned(reg, LLDB_INVALID_ADDRESS);
}

static bool DumpAddressOffsetFromFunction(Stream &s, const SymbolContext *sc,
const ExecutionContext *exe_ctx,
const Address &format_addr,
Expand Down Expand Up @@ -1796,6 +1808,36 @@ bool FormatEntity::Format(const Entry &entry, Stream &s,
FormatInlinedBlock(s, sc->block);
return true;
}
case Entry::Type::FunctionCFA: {
if (exe_ctx) {
auto read_addr = [&](addr_t addr) {
auto *process = exe_ctx->GetProcessPtr();
lldb::addr_t value_read = LLDB_INVALID_ADDRESS;
Status error;
process->ReadMemory(addr, &value_read, 8, error);
return value_read;
};

if (auto *frame = exe_ctx->GetFramePtr()) {
s.Printf("x22 = 0x%" PRIx64, GetAsyncContext(frame->GetRegisterContext().get()));

auto stack_id = frame->GetStackID();
auto cfa = stack_id.GetCallFrameAddress();
s.Printf(" cfa = 0x%" PRIx64, cfa);
if (stack_id.IsCFAOnStack(exe_ctx->GetProcessRef()))
s.PutCString("[on stack]");
else {
s.PutCString("[on heap] ");
while (cfa && cfa != LLDB_INVALID_ADDRESS) {
cfa = read_addr(cfa);
s.Printf("-> 0x%" PRIx64, cfa);
}
}
return true;
}
}
return false;
}
case Entry::Type::FunctionAddrOffset:
if (addr) {
if (DumpAddressOffsetFromFunction(s, sc, exe_ctx, *addr, false, false,
Expand Down
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());
}

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(Swift)
endif()
11 changes: 11 additions & 0 deletions lldb/source/Plugins/OperatingSystem/Swift/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
add_lldb_library(lldbPluginOperatingSystemSwift PLUGIN
OperatingSystemSwift.cpp

LINK_LIBS
lldbCore
lldbInterpreter
lldbSymbol
lldbTarget
lldbValueObject
lldbPluginProcessUtility
)
164 changes: 164 additions & 0 deletions lldb/source/Plugins/OperatingSystem/Swift/OperatingSystemSwift.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
//===-- OperatingSystemSwift.cpp -----------------------------------------===//

Choose a reason for hiding this comment

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

Not important. "OperatingSystemSwift" sounds slightly wrong to me. What about "OperatingSystemSwiftTasks" or "OperatingSystemSwiftConcurrency"?

Choose a reason for hiding this comment

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

Yeah the name seems misleading the full lldb affordance name is "Operating System Thread Plugin"

//
// 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 "OperatingSystemSwift.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 "lldb/Utility/StructuredData.h"

#include "swift/Threading/ThreadLocalStorage.h"

#include <memory>

using namespace lldb;
using namespace lldb_private;

LLDB_PLUGIN_DEFINE(OperatingSystemSwift)

void OperatingSystemSwift::Initialize() {
PluginManager::RegisterPlugin(GetPluginNameStatic(),
GetPluginDescriptionStatic(), CreateInstance,
nullptr);
}

void OperatingSystemSwift::Terminate() {
PluginManager::UnregisterPlugin(CreateInstance);
}

OperatingSystem *OperatingSystemSwift::CreateInstance(Process *process,
bool force) {
if (!process)
return nullptr;

/// Heuristic to only load the plugin for swift programs.
FileSpec swift_module_name = FileSpec("libswiftCore.dylib");

Choose a reason for hiding this comment

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

This only works in a very narrow subset of Swift programs. You should defer to SwiftLanguageRuntime's helper code.
The cases this doesn't support are: non-Darwin (".dylib"), statically linked Swift stdlib, embedded Swift (no dylibs at all).

Copy link
Author

Choose a reason for hiding this comment

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

Do you have a helper function mind? The only thing I found is GetLikelySwiftImageNamesForModule, which has some very weird behavior I'm not sure I understand.

Choose a reason for hiding this comment

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

You could also arrange that this can only be accessed by the SwiftLanguageRuntime, and then its loading will be gated on the Runtime loading, which already has to do this sort of computation.

Copy link
Author

@felipepiovezan felipepiovezan Jan 15, 2025

Choose a reason for hiding this comment

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

@jimingham I looked into that option, but AFAICT the SwiftLanguageRuntime is loaded regardless of modules.
I.e. the first call to "load runtimes" will always load it, even if the relevant libraries haven't been loaded yet

if (process->GetTarget().GetImages().FindFirstModule(swift_module_name))
return new OperatingSystemSwift(*process);
return nullptr;
}

llvm::StringRef OperatingSystemSwift::GetPluginDescriptionStatic() {
return "Operating system plug-in converting Swift Tasks into Threads.";
}

OperatingSystemSwift::~OperatingSystemSwift() = default;

OperatingSystemSwift::OperatingSystemSwift(lldb_private::Process &process)
: OperatingSystem(&process) {
size_t ptr_size = process.GetAddressByteSize();
// Offset of the Task pointer in a Thread's local storage.
m_task_ptr_offset_in_tls =
swift::tls_get_key(swift::tls_key::concurrency_task) * ptr_size;
// 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;

Choose a reason for hiding this comment

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

You should check that the swift concurrency version is still 1 here.

See: swiftlang/swift#76437

}

bool OperatingSystemSwift::UpdateThreadList(ThreadList &old_thread_list,
ThreadList &core_thread_list,
ThreadList &new_thread_list) {
Log *log = GetLog(LLDBLog::OS);
LLDB_LOG(log, "OperatingSystemSwift: 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,
"OperatingSystemSwift: 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 = 0xdeadbeef00000000 | *task_id;
Copy link
Author

Choose a reason for hiding this comment

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

Btw it is probably fine to not do this, TIDs are generally higher numbers and there might be value in exposing the task id directly. If we do mask, we should use some other mask

Choose a reason for hiding this comment

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

This will be the visible "TID" for running tasks, right? And what I'd have to type if I wanted to set a "task specific" breakpoint? If so, we might not want it to be deadbeef. That's cute if you know the reference, but otherwise somewhat off-putting...

Copy link
Author

Choose a reason for hiding this comment

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

Oh yeah, I'm actually looking for suggestions here, if we should mask it at all and what to use if we do
Deadbeef was just something to help me grep the logs as I debugged.


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))

Choose a reason for hiding this comment

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

Do you have to handle the case where when you stopped the first time, this Task was backed by "core thread 1" but the next time you stopped it was backed by a different core thread? I don't see where you are changing the backing thread in this case?

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,
"OperatingSystemSwift: mapping thread IDs: %" PRIx64
" -> %" PRIx64,
real_thread->GetID(), swift_thread->GetID());
}
return true;
}

void OperatingSystemSwift::ThreadWasSelected(Thread *thread) {}

RegisterContextSP
OperatingSystemSwift::CreateRegisterContextForThread(Thread *thread,
addr_t reg_data_addr) {
if (!thread || !IsOperatingSystemPluginThread(thread->shared_from_this()))
return nullptr;
return thread->GetRegisterContext();
}

StopInfoSP
OperatingSystemSwift::CreateThreadStopReason(lldb_private::Thread *thread) {
return thread->GetStopInfo();
}

std::optional<uint64_t> OperatingSystemSwift::FindTaskId(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();

// The Task address is at offset m_task_ptr_offset_in_tls from the thread
// local storage base pointer.
addr_t task_addr_location = tsd_addr + m_task_ptr_offset_in_tls;
Status error;
addr_t task_addr =
m_process->ReadPointerFromMemory(task_addr_location, error);
if (error.Fail())
return {};

// 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
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
//===-- OperatingSystemSwift.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_OperatingSystemSwift_h_
#define liblldb_OperatingSystemSwift_h_

#if LLDB_ENABLE_SWIFT

#include "lldb/Target/OperatingSystem.h"

namespace lldb_private {
class OperatingSystemSwift : public OperatingSystem {
public:
OperatingSystemSwift(Process &process);
~OperatingSystemSwift() 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;

std::optional<bool> DoesPluginReportAllThreads() override { return false; }

private:
/// Find the Task ID of the task being executed by `thread`, if any.
std::optional<uint64_t> FindTaskId(Thread &thread);

/// The offset of the Task pointer inside thread local storage.
size_t m_task_ptr_offset_in_tls;

/// 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_OperatingSystemSwift_h_
3 changes: 3 additions & 0 deletions lldb/source/Plugins/Process/Utility/ThreadMemory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 &&
Expand All @@ -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());

Expand Down
7 changes: 6 additions & 1 deletion lldb/source/Target/Process.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -387,6 +387,7 @@ bool ProcessProperties::GetOSPluginReportsAllThreads() const {
if (!exp_values)
return fail_value;

return false;
return exp_values
->GetPropertyAtIndexAs<bool>(ePropertyOSPluginReportsAllThreads)
.value_or(fail_value);
Expand Down Expand Up @@ -1319,7 +1320,11 @@ void Process::UpdateThreadListIfNeeded() {
// See if the OS plugin reports all threads. If it does, then
// it is safe to clear unseen thread's plans here. Otherwise we
// should preserve them in case they show up again:
clear_unused_threads = GetOSPluginReportsAllThreads();
std::optional<bool> os_reports_all_threads =
os->DoesPluginReportAllThreads();
clear_unused_threads = os_reports_all_threads
? *os_reports_all_threads
: GetOSPluginReportsAllThreads();

// Turn off dynamic types to ensure we don't run any expressions.
// Objective-C can run an expression to determine if a SBValue is a
Expand Down
Loading