-
Notifications
You must be signed in to change notification settings - Fork 342
[lldb] Introduce backtracing of Swift Tasks #9787
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 1 commit
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 |
---|---|---|
|
@@ -14,6 +14,7 @@ | |
#include "Plugins/Language/Swift/SwiftStringIndex.h" | ||
#include "Plugins/LanguageRuntime/Swift/ReflectionContextInterface.h" | ||
#include "Plugins/LanguageRuntime/Swift/SwiftLanguageRuntime.h" | ||
#include "Plugins/LanguageRuntime/Swift/SwiftTask.h" | ||
#include "Plugins/TypeSystem/Clang/TypeSystemClang.h" | ||
#include "lldb/DataFormatters/FormattersHelpers.h" | ||
#include "lldb/DataFormatters/StringPrinter.h" | ||
|
@@ -23,6 +24,7 @@ | |
#include "lldb/Utility/LLDBLog.h" | ||
#include "lldb/Utility/Log.h" | ||
#include "lldb/Utility/Status.h" | ||
#include "lldb/Utility/StreamString.h" | ||
#include "lldb/Utility/Timer.h" | ||
#include "lldb/ValueObject/ValueObject.h" | ||
#include "lldb/lldb-enumerations.h" | ||
|
@@ -818,6 +820,16 @@ class TaskSyntheticFrontEnd : public SyntheticChildrenFrontEnd { | |
"could not get info for async task {0:x}: {1}", task_ptr, | ||
fmt_consume(std::move(err))); | ||
} else { | ||
|
||
// Print a backtrace of the Task to stdout. | ||
ExecutionContext exe_ctx{m_backend.GetExecutionContextRef()}; | ||
auto tt = std::make_shared<ThreadTask>( | ||
3000, task_info->resumeAsyncContext, exe_ctx); | ||
StreamString ss; | ||
tt->GetStatus(ss, 0, 100, 0, false, true); | ||
auto desc = ss.GetString(); | ||
printf("%.*s\n", (int)desc.size(), desc.data()); | ||
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. This part is the reason this PR is in draft state. This code prints the backtrace of a task to stdout, but that will be migrated to a separate command. Initially something like |
||
|
||
m_task_info = *task_info; | ||
for (auto child : | ||
{m_is_child_task_sp, m_is_future_sp, m_is_group_child_task_sp, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
#include "SwiftTask.h" | ||
#include "SwiftLanguageRuntime.h" | ||
#include "lldb/Target/Process.h" | ||
|
||
using namespace llvm; | ||
using namespace lldb; | ||
|
||
kastiglione marked this conversation as resolved.
Show resolved
Hide resolved
|
||
lldb_private::ThreadTask::ThreadTask(tid_t tid, addr_t async_ctx, | ||
ExecutionContext &exe_ctx) | ||
: Thread(exe_ctx.GetProcessRef(), tid, true), | ||
m_concrete_reg_ctx_sp(exe_ctx.GetFrameSP()->GetRegisterContext()) { | ||
m_async_ctx = async_ctx; | ||
kastiglione marked this conversation as resolved.
Show resolved
Hide resolved
|
||
auto ptr_size = exe_ctx.GetTargetRef().GetArchitecture().GetAddressByteSize(); | ||
kastiglione marked this conversation as resolved.
Show resolved
Hide resolved
|
||
// A simplified description of AsyncContext. See swift/Task/ABI.h | ||
// struct AsyncContext { | ||
// AsyncContext *Parent; // offset 0 | ||
// TaskContinuationFunction *ResumeParent; // offset 8 | ||
// }; | ||
auto resume_offset = ptr_size; // offsetof(AsyncContext, ResumeParent) | ||
auto resume_ptr = async_ctx + resume_offset; | ||
Status status; | ||
m_pc = exe_ctx.GetProcessRef().ReadPointerFromMemory(resume_ptr, status); | ||
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'm moderately concerned that this ctor may fail. 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. That seems fine to me. Note that if 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. Looking at the code where ThreadTask's are created, I don't see any handling of invalid pcs. IIUC we will just print a thread with a weird PC? 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. there's no handling of invalid pcs, what do you think should be done? I assumed the async context's resume function can be trusted to be valid, but you must see some ways in which it would be invalid? 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. There's now a failable |
||
} | ||
|
||
RegisterContextSP lldb_private::ThreadTask::GetRegisterContext() { | ||
if (!m_async_reg_ctx_sp) | ||
m_async_reg_ctx_sp = std::make_shared<RegisterContextTask>( | ||
*this, m_concrete_reg_ctx_sp, m_pc, m_async_ctx); | ||
return m_async_reg_ctx_sp; | ||
} | ||
|
||
lldb_private::RegisterContextTask::RegisterContextTask( | ||
Thread &thread, RegisterContextSP reg_info_sp, addr_t pc, addr_t async_ctx) | ||
: RegisterContext(thread, 0), m_reg_info_sp(reg_info_sp), | ||
m_async_ctx(async_ctx), m_pc(pc) { | ||
auto &target = thread.GetProcess()->GetTarget(); | ||
auto triple = target.GetArchitecture().GetTriple(); | ||
if (auto regnums = GetAsyncUnwindRegisterNumbers(triple.getArch())) | ||
m_async_ctx_regnum = regnums->async_ctx_regnum; | ||
} | ||
|
||
bool lldb_private::RegisterContextTask::ReadRegister( | ||
const RegisterInfo *reg_info, RegisterValue ®_value) { | ||
kastiglione marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if (reg_info->kinds[eRegisterKindGeneric] == LLDB_REGNUM_GENERIC_PC) { | ||
reg_value = m_pc; | ||
return true; | ||
} | ||
if (reg_info->kinds[eRegisterKindLLDB] == m_async_ctx_regnum) { | ||
reg_value = m_async_ctx; | ||
return true; | ||
} | ||
return false; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
|
||
#include "lldb/Target/RegisterContext.h" | ||
#include "lldb/Target/Thread.h" | ||
#include "lldb/Utility/RegisterValue.h" | ||
#include "lldb/lldb-forward.h" | ||
|
||
namespace lldb_private { | ||
|
||
using namespace lldb; | ||
|
||
/// Provides a subset of Thread operations for Swift Tasks. | ||
/// | ||
/// Currently, this supports backtraces of Tasks, and selecting frames in the | ||
/// backtrace. Async frames make available the variables that are stored in the | ||
/// Task's "async context" (instead of the stack). | ||
/// | ||
/// See `Task<Success, Failure>` and `UnsafeCurrentTask` | ||
class ThreadTask : public Thread { | ||
felipepiovezan marked this conversation as resolved.
Show resolved
Hide resolved
|
||
public: | ||
ThreadTask(tid_t tid, addr_t async_ctx, ExecutionContext &exe_ctx); | ||
|
||
/// Returns a Task specific register context (RegisterContextTask). | ||
RegisterContextSP GetRegisterContext() override; | ||
|
||
~ThreadTask() override { DestroyThread(); } | ||
felipepiovezan marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
// No-op overrides. | ||
void RefreshStateAfterStop() override {} | ||
lldb::RegisterContextSP | ||
CreateRegisterContextForFrame(StackFrame *frame) override { | ||
return {}; | ||
} | ||
bool CalculateStopInfo() override { return false; } | ||
|
||
private: | ||
/// A register context that is the source of `RegisterInfo` data. | ||
RegisterContextSP m_concrete_reg_ctx_sp; | ||
/// Lazily initialized `RegisterContextTask`. | ||
RegisterContextSP m_async_reg_ctx_sp; | ||
/// The Task's async context. | ||
addr_t m_async_ctx = LLDB_INVALID_ADDRESS; | ||
/// The address of the async context's resume function. | ||
addr_t m_pc = LLDB_INVALID_ADDRESS; | ||
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. The name When we are unwinding, we say "pc" because we are producing the register context of the frame above the current frame. Here, we just have a Thread class with a member 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. Actually I am confused now, is this really printing frame 0 correctly?
I could be missing something, but this looks like it prints the PC of frame 1 instead of frame 0. 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. maybe I'm not sure where the confusion stems from, but yes this prints "frame 0" correctly. 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 changed it to 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 think I understand 2024 Felipe's confusion now. Usually, the "resume address" (to use the terminology of the code here) means the PC of the "frame above" or the "next funclet to execute after the current frame". However, here we have Tasks which are suspended, so there is no "current frame"; in other words, the "next funclet to execute" is going to be frame zero. 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.
👍 |
||
}; | ||
|
||
/// A Swift Task specific register context. Supporting class for `ThreadTask`, | ||
/// see its documentation for details. | ||
class RegisterContextTask : public RegisterContext { | ||
public: | ||
RegisterContextTask(Thread &thread, RegisterContextSP reg_info_sp, addr_t pc, | ||
addr_t async_ctx); | ||
|
||
/// RegisterContextTask supports readonly from only two (necessary) | ||
/// registers. Namely, the pc and the async context registers. | ||
bool ReadRegister(const RegisterInfo *reg_info, | ||
RegisterValue ®_value) override; | ||
|
||
// Pass through overrides. | ||
size_t GetRegisterCount() override { | ||
return m_reg_info_sp->GetRegisterCount(); | ||
} | ||
const RegisterInfo *GetRegisterInfoAtIndex(size_t idx) override { | ||
return m_reg_info_sp->GetRegisterInfoAtIndex(idx); | ||
} | ||
size_t GetRegisterSetCount() override { | ||
return m_reg_info_sp->GetRegisterSetCount(); | ||
} | ||
const RegisterSet *GetRegisterSet(size_t reg_set) override { | ||
return m_reg_info_sp->GetRegisterSet(reg_set); | ||
} | ||
lldb::ByteOrder GetByteOrder() override { | ||
return m_reg_info_sp->GetByteOrder(); | ||
} | ||
|
||
// No-op overrides. | ||
void InvalidateAllRegisters() override {} | ||
bool WriteRegister(const RegisterInfo *reg_info, | ||
const RegisterValue ®_value) override { | ||
return false; | ||
} | ||
|
||
private: | ||
/// A register context that is the source of `RegisterInfo` data. | ||
RegisterContextSP m_reg_info_sp; | ||
/// The architecture specific regnum (LLDB) which holds the async context. | ||
uint32_t m_async_ctx_regnum = LLDB_INVALID_REGNUM; | ||
/// The Task's async context. | ||
RegisterValue m_async_ctx; | ||
/// The address of the async context's resume function. | ||
RegisterValue m_pc; | ||
}; | ||
|
||
} // namespace lldb_private |
Uh oh!
There was an error while loading. Please reload this page.