-
Notifications
You must be signed in to change notification settings - Fork 341
[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
Changes from all commits
d9b17b3
3e558af
67fc472
1808c33
80758af
6baa56a
184d150
a3187cb
6ce018a
116816e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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" | ||
|
@@ -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" | ||
|
@@ -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, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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"? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To elaborate, using |
||
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 |
---|---|---|
|
@@ -6,6 +6,8 @@ | |
// | ||
//===----------------------------------------------------------------------===// | ||
|
||
#include "llvm/Support/Error.h" | ||
#include <optional> | ||
#if LLDB_ENABLE_SWIFT | ||
|
||
#include "OperatingSystemSwiftTasks.h" | ||
|
@@ -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; | ||
|
||
|
@@ -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); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
|
@@ -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, | ||
|
@@ -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 |
---|---|---|
@@ -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*\)") |
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 | ||
} | ||
} |
Uh oh!
There was an error while loading. Please reload this page.