Skip to content

[lldb] Add support for Task names #10684

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

Merged
merged 10 commits into from
May 19, 2025
Merged
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
12 changes: 12 additions & 0 deletions lldb/source/Plugins/Language/Swift/SwiftFormatters.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1869,6 +1869,18 @@ bool lldb_private::formatters::swift::Task_SummaryProvider(
return 0;
};

addr_t task_addr = get_member("address");
if (auto process_sp = valobj.GetProcessSP()) {
if (auto name_or_err = GetTaskName(task_addr, *process_sp)) {
if (auto maybe_name = *name_or_err)
stream.Format("\"{0}\" ", *maybe_name);
} else {
LLDB_LOG_ERROR(GetLog(LLDBLog::DataFormatters | LLDBLog::Types),
name_or_err.takeError(),
"failed to determine name of task {1:x}: {0}", task_addr);
}
}

stream.Format("id:{0}", get_member("id"));

std::vector<StringRef> flags;
Expand Down
109 changes: 109 additions & 0 deletions lldb/source/Plugins/LanguageRuntime/Swift/SwiftLanguageRuntime.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,15 @@
#include "lldb/Symbol/FuncUnwinders.h"
#include "lldb/Symbol/Function.h"
#include "lldb/Symbol/VariableList.h"
#include "lldb/Target/Process.h"
#include "lldb/Target/RegisterContext.h"
#include "lldb/Target/UnwindLLDB.h"
#include "lldb/Utility/ConstString.h"
#include "lldb/Utility/ErrorMessages.h"
#include "lldb/Utility/LLDBLog.h"
#include "lldb/Utility/Log.h"
#include "lldb/Utility/OptionParsing.h"
#include "lldb/Utility/Status.h"
#include "lldb/Utility/StructuredData.h"
#include "lldb/Utility/Timer.h"
#include "lldb/ValueObject/ValueObject.h"
Expand All @@ -68,6 +71,7 @@
#include "llvm/Support/Error.h"
#include "llvm/Support/FormatAdapters.h"
#include "llvm/Support/Memory.h"
#include <optional>

// FIXME: we should not need this
#include "Plugins/Language/Swift/SwiftFormatters.h"
Expand Down Expand Up @@ -2937,4 +2941,109 @@ llvm::Expected<lldb::addr_t> GetTaskAddrFromThreadLocalStorage(Thread &thread) {
return task_addr;
#endif
}

namespace {

/// Lightweight wrapper around TaskStatusRecord pointers, providing:
/// * traversal over the embedded linnked list of status records
/// * information contained within records
///
/// Currently supports TaskNameStatusRecord. See swift/ABI/TaskStatus.h
struct TaskStatusRecord {
Process &process;
addr_t addr;
size_t addr_size;

TaskStatusRecord(Process &process, addr_t addr)
: process(process), addr(addr) {
addr_size = process.GetAddressByteSize();
}

operator bool() const { return addr && addr != LLDB_INVALID_ADDRESS; }

// The offset of TaskStatusRecord members. The unit is pointers, and must be
// converted to bytes based on the target's address size.
static constexpr unsigned FlagsPointerOffset = 0;
static constexpr unsigned ParentPointerOffset = 1;
static constexpr unsigned TaskNamePointerOffset = 2;

enum Kind : uint64_t {
TaskName = 6,
};

uint64_t getKind(Status &status) {
const offset_t flagsByteOffset = FlagsPointerOffset * addr_size;
if (status.Success())
return process.ReadUnsignedIntegerFromMemory(
addr + flagsByteOffset, addr_size, UINT64_MAX, status);
return UINT64_MAX;
}

std::optional<std::string> getName(Status &status) {
if (getKind(status) != Kind::TaskName)
return {};

const offset_t taskNameByteOffset = TaskNamePointerOffset * addr_size;
addr_t name_addr =
process.ReadPointerFromMemory(addr + taskNameByteOffset, status);
if (!status.Success())
return {};

std::string name;
process.ReadCStringFromMemory(name_addr, name, status);
if (status.Success())
return name;

return {};
}

addr_t getParent(Status &status) {
const offset_t parentByteOffset = ParentPointerOffset * addr_size;
addr_t parent = LLDB_INVALID_ADDRESS;
if (*this && status.Success())
parent = process.ReadPointerFromMemory(addr + parentByteOffset, status);
return parent;
}
};

/// Lightweight wrapper around Task pointers, providing access to a Task's
/// active status record. See swift/ABI/Task.h
struct Task {
Process &process;
addr_t addr;

operator bool() const { return addr && addr != LLDB_INVALID_ADDRESS; }

// The offset of the active TaskStatusRecord pointer. The unit is pointers,
// and must be converted to bytes based on the target's address size.
static constexpr unsigned ActiveTaskStatusRecordPointerOffset = 13;

TaskStatusRecord getActiveTaskStatusRecord(Status &status) {
const offset_t activeTaskStatusRecordByteOffset =
ActiveTaskStatusRecordPointerOffset * process.GetAddressByteSize();
addr_t status_record = LLDB_INVALID_ADDRESS;
if (status.Success())
status_record = process.ReadPointerFromMemory(
addr + activeTaskStatusRecordByteOffset, status);
return {process, status_record};
}
};

}; // namespace

llvm::Expected<std::optional<std::string>> GetTaskName(lldb::addr_t task_addr,

Choose a reason for hiding this comment

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

Does the optional add anything over just using an empty string for "success, but no name"?

Copy link
Author

Choose a reason for hiding this comment

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

My testing shows that I can provide the empty string as a task name, which should be distinguished from no task name.

Copy link
Author

Choose a reason for hiding this comment

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

To elaborate, using std::optional<std::string> allows both a task name of the empty string (which lldb should show), and a task not having a name.

Process &process) {
Status status;
Task task{process, task_addr};
auto status_record = task.getActiveTaskStatusRecord(status);
while (status_record) {
if (auto name = status_record.getName(status))
return *name;
status_record.addr = status_record.getParent(status);
}
if (status.Success())
return std::nullopt;
return status.takeError();
}

} // namespace lldb_private
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include "lldb/Breakpoint/BreakpointPrecondition.h"
#include "lldb/Core/PluginInterface.h"
#include "lldb/Target/LanguageRuntime.h"
#include "lldb/Target/Process.h"
#include "lldb/lldb-private.h"
#include "swift/Demangling/ManglingFlavor.h"

Expand Down Expand Up @@ -901,6 +902,10 @@ GetAsyncUnwindRegisterNumbers(llvm::Triple::ArchType triple);
/// Inspects thread local storage to find the address of the currently executing
/// task.
llvm::Expected<lldb::addr_t> GetTaskAddrFromThreadLocalStorage(Thread &thread);

llvm::Expected<std::optional<std::string>> GetTaskName(lldb::addr_t task,
Process &process);

} // namespace lldb_private

#endif // liblldb_SwiftLanguageRuntime_h_
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
//
//===----------------------------------------------------------------------===//

#include "llvm/Support/Error.h"
#include <optional>
#if LLDB_ENABLE_SWIFT

#include "OperatingSystemSwiftTasks.h"
Expand Down Expand Up @@ -74,16 +76,11 @@ 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_job_id_offset = 4 * ptr_size + 4;
}
: OperatingSystem(&process) {}

ThreadSP
OperatingSystemSwiftTasks::FindOrCreateSwiftThread(ThreadList &old_thread_list,
uint64_t task_id) {
ThreadSP OperatingSystemSwiftTasks::FindOrCreateSwiftThread(
ThreadList &old_thread_list, uint64_t task_id,
std::optional<std::string> task_name) {
// Mask higher bits to avoid conflicts with core thread IDs.
uint64_t masked_task_id = 0x0000000f00000000 | task_id;

Expand All @@ -92,23 +89,71 @@ OperatingSystemSwiftTasks::FindOrCreateSwiftThread(ThreadList &old_thread_list,
IsOperatingSystemPluginThread(old_thread))
return old_thread;

std::string name = llvm::formatv("Swift Task {0}", task_id);
std::string name;
if (task_name)
name = llvm::formatv("{0} (Task {1})", *task_name, task_id);
else
name = llvm::formatv("Task {0}", task_id);

Choose a reason for hiding this comment

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

Similar comment here: let's strive to always initialize at the declaration. A ternary operator should work fine here, and if this ever becomes more complicated we move it to a helper function

Copy link
Author

@kastiglione kastiglione May 14, 2025

Choose a reason for hiding this comment

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

In this case I see a ternary as worse readability. As written above, I consider the initialization to be clearly one of two ways. I'm not aware of any standard conventions or strong arguments that code should be bent to ensure variables are always initialized.

Copy link

@felipepiovezan felipepiovezan May 15, 2025

Choose a reason for hiding this comment

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

I strongly disagree, but won't insist in the interest of timeliness. But please ponder how would you implement this in a language that does not allow for uninitialized variables? (swift, rust, zig, all the modern ones, and why do they make that choice)

Copy link
Author

Choose a reason for hiding this comment

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

Swift does not allow uninitialized variables, but does permit separate declaration and initialization:

var name: String
if whatever {
    name = oneName()
} else {
    name = anotherName()
}


return std::make_shared<ThreadMemoryProvidingName>(*m_process, masked_task_id,
/*register_data_addr*/ 0,
name);
}

static std::optional<addr_t> FindTaskAddress(Thread &thread) {
llvm::Expected<addr_t> task_addr = GetTaskAddrFromThreadLocalStorage(thread);
if (!task_addr) {
LLDB_LOG_ERROR(GetLog(LLDBLog::OS), task_addr.takeError(),
"OperatingSystemSwiftTasks: failed to find task address in "
"thread local storage: {0}");
return {};
}
if (*task_addr == 0 || *task_addr == LLDB_INVALID_ADDRESS)
return std::nullopt;
return *task_addr;
}

static std::optional<uint64_t> FindTaskId(addr_t task_addr, Process &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.
const offset_t job_id_offset = 4 * ptr_size + 4;

Status error;
// The Task ID is at offset job_id_offset from the Task pointer.
constexpr uint32_t num_bytes_task_id = 4;
auto task_id = process.ReadUnsignedIntegerFromMemory(
task_addr + job_id_offset, num_bytes_task_id, LLDB_INVALID_ADDRESS,
error);
if (error.Fail())
return {};
return task_id;
}

static std::optional<std::string> FindTaskName(addr_t task_addr,
Process &process) {
auto task_name_or_err = GetTaskName(task_addr, process);
if (auto err = task_name_or_err.takeError()) {
LLDB_LOG_ERROR(GetLog(LLDBLog::OS), std::move(err),
"OperatingSystemSwiftTasks: failed while looking for name "
"of task {1:x}: {0}",
task_addr);
return {};
}
return *task_name_or_err;
}

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);
std::optional<addr_t> task_addr = FindTaskAddress(*real_thread);

// If this is not a thread running a Task, add it to the list as is.
if (!task_id) {
if (!task_addr) {
new_thread_list.AddThread(real_thread);
LLDB_LOGF(log,
"OperatingSystemSwiftTasks: thread %" PRIx64
Expand All @@ -117,7 +162,16 @@ bool OperatingSystemSwiftTasks::UpdateThreadList(ThreadList &old_thread_list,
continue;
}

ThreadSP swift_thread = FindOrCreateSwiftThread(old_thread_list, *task_id);
assert(m_process != nullptr);
std::optional<uint64_t> task_id = FindTaskId(*task_addr, *m_process);
if (!task_id) {
LLDB_LOG(log, "OperatingSystemSwiftTasks: could not get ID of Task {0:x}",
*task_addr);
continue;
}

ThreadSP swift_thread = FindOrCreateSwiftThread(
old_thread_list, *task_id, FindTaskName(*task_addr, *m_process));
swift_thread->SetBackingThread(real_thread);
new_thread_list.AddThread(swift_thread);
LLDB_LOGF(log,
Expand All @@ -142,24 +196,4 @@ StopInfoSP OperatingSystemSwiftTasks::CreateThreadStopReason(
return thread->GetStopInfo();
}

std::optional<uint64_t> OperatingSystemSwiftTasks::FindTaskId(Thread &thread) {
llvm::Expected<addr_t> task_addr = GetTaskAddrFromThreadLocalStorage(thread);
if (!task_addr) {
LLDB_LOG_ERROR(GetLog(LLDBLog::OS), task_addr.takeError(),
"OperatingSystemSwiftTasks: failed to find task address in "
"thread local storage: {0}");
return {};
}

Status error;
// The Task ID is at offset m_job_id_offset from the Task pointer.
constexpr uint32_t num_bytes_task_id = 4;
auto task_id = m_process->ReadUnsignedIntegerFromMemory(
*task_addr + m_job_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
Expand Up @@ -49,13 +49,8 @@ class OperatingSystemSwiftTasks : public OperatingSystem {
/// If a thread for task_id had been created in the last stop, return it.
/// Otherwise, create a new MemoryThread for it.
lldb::ThreadSP FindOrCreateSwiftThread(ThreadList &old_thread_list,
uint64_t task_id);

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

/// The offset of the Job ID inside a Task data structure.
size_t m_job_id_offset;
uint64_t task_id,
std::optional<std::string> task_name);
};
} // namespace lldb_private

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
SWIFT_SOURCES := main.swift
SWIFTFLAGS_EXTRAS := -parse-as-library -Xfrontend -disable-availability-checking
include Makefile.rules
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import textwrap
import lldb
from lldbsuite.test.decorators import *
from lldbsuite.test.lldbtest import *
from lldbsuite.test import lldbutil


class TestCase(TestBase):

@swiftTest
def test_summary_contains_name(self):
self.build()
lldbutil.run_to_source_breakpoint(
self, "break outside", lldb.SBFileSpec("main.swift")
)
self.expect("v task", patterns=[r'"Chore" id:[1-9]\d*'])

@swiftTest
@skipIfLinux # rdar://151471067
def test_thread_contains_name(self):
self.build()
_, _, thread, _ = lldbutil.run_to_source_breakpoint(
self, "break inside", lldb.SBFileSpec("main.swift")
)
self.assertRegex(thread.name, r"Chore \(Task [1-9]\d*\)")
11 changes: 11 additions & 0 deletions lldb/test/API/lang/swift/async/formatters/task/name/main.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
@main struct Main {
static func main() async {
let task = Task(name: "Chore") {
print("break inside")
_ = readLine()
return 15
}
print("break outside")
_ = await task.value
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ def test(self):
self, "BREAK HERE", source_file
)

self.assertIn("Swift Task", thread.GetName())
self.assertRegex(thread.GetName(), r"^Task [1-9]\d*$")

queue_plugin = self.get_queue_from_thread_info_command(False)
queue_backing = self.get_queue_from_thread_info_command(True)
Expand Down