Skip to content

[lldb] Introduce command to select task "threads" #9840

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
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
115 changes: 71 additions & 44 deletions lldb/source/Plugins/LanguageRuntime/Swift/SwiftLanguageRuntime.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2086,6 +2086,46 @@ class CommandObjectSwift_RefCount : public CommandObjectRaw {
}
};

/// Construct a `ThreadTask` instance for a Task variable contained in the first
/// argument.
static llvm::Expected<ThreadSP>
ThreadForTaskVariable(Args &command, ExecutionContext &exe_ctx) {
if (!exe_ctx.GetFramePtr())
return llvm::createStringError("no active frame selected");

if (command.empty() || command[0].ref().empty())
return llvm::createStringError("missing task variable argument");

StackFrame &frame = exe_ctx.GetFrameRef();
uint32_t path_options =
StackFrame::eExpressionPathOptionsAllowDirectIVarAccess;
VariableSP var_sp;
Status status;
ValueObjectSP valobj_sp = frame.GetValueForVariableExpressionPath(
command[0].c_str(), eDynamicDontRunTarget, path_options, var_sp, status);
if (!valobj_sp)
return status.takeError();

addr_t task_ptr = LLDB_INVALID_ADDRESS;
ThreadSafeReflectionContext reflection_ctx;
if (ValueObjectSP task_obj_sp = valobj_sp->GetChildMemberWithName("_task")) {
task_ptr = task_obj_sp->GetValueAsUnsigned(LLDB_INVALID_ADDRESS);
if (task_ptr != LLDB_INVALID_ADDRESS)
if (auto *runtime = SwiftLanguageRuntime::Get(exe_ctx.GetProcessSP()))
reflection_ctx = runtime->GetReflectionContext();
}
if (task_ptr == LLDB_INVALID_ADDRESS || !reflection_ctx)
return llvm::createStringError("failed to access Task data from runtime");

llvm::Expected<ReflectionContextInterface::AsyncTaskInfo> task_info =
reflection_ctx->asyncTaskInfo(task_ptr);
if (auto error = task_info.takeError())
return error;

return ThreadTask::Create(task_info->id, task_info->resumeAsyncContext,
exe_ctx);
}

class CommandObjectLanguageSwiftTaskBacktrace final
: public CommandObjectParsed {
public:
Expand All @@ -2099,60 +2139,44 @@ class CommandObjectLanguageSwiftTaskBacktrace final

private:
void DoExecute(Args &command, CommandReturnObject &result) override {
if (!m_exe_ctx.GetFramePtr()) {
result.AppendError("no active frame selected");
return;
}

if (command.empty() || command[0].ref().empty()) {
result.AppendError("no task variable");
return;
}

StackFrame &frame = m_exe_ctx.GetFrameRef();
uint32_t path_options =
StackFrame::eExpressionPathOptionsAllowDirectIVarAccess;
VariableSP var_sp;
Status status;
ValueObjectSP valobj_sp = frame.GetValueForVariableExpressionPath(
command[0].c_str(), eDynamicDontRunTarget, path_options, var_sp,
status);
if (!valobj_sp) {
result.AppendError(status.AsCString());
llvm::Expected<ThreadSP> thread_task =
ThreadForTaskVariable(command, m_exe_ctx);
if (auto error = thread_task.takeError()) {
result.AppendError(toString(std::move(error)));
return;
}

addr_t task_ptr = LLDB_INVALID_ADDRESS;
ThreadSafeReflectionContext reflection_ctx;
if (ValueObjectSP task_obj_sp =
valobj_sp->GetChildMemberWithName("_task")) {
task_ptr = task_obj_sp->GetValueAsUnsigned(LLDB_INVALID_ADDRESS);
if (task_ptr != LLDB_INVALID_ADDRESS)
if (auto *runtime = SwiftLanguageRuntime::Get(m_exe_ctx.GetProcessSP()))
reflection_ctx = runtime->GetReflectionContext();
}
if (task_ptr == LLDB_INVALID_ADDRESS || !reflection_ctx) {
result.AppendError("failed to access Task data from runtime");
return;
}
// GetStatus prints the backtrace.
thread_task.get()->GetStatus(result.GetOutputStream(), 0, UINT32_MAX, 0,
false, true);
result.SetStatus(lldb::eReturnStatusSuccessFinishResult);
}
};

llvm::Expected<ReflectionContextInterface::AsyncTaskInfo> task_info =
reflection_ctx->asyncTaskInfo(task_ptr);
if (auto error = task_info.takeError()) {
result.AppendError(toString(std::move(error)));
return;
}
class CommandObjectLanguageSwiftTaskSelect final : public CommandObjectParsed {
public:
CommandObjectLanguageSwiftTaskSelect(CommandInterpreter &interpreter)
: CommandObjectParsed(
interpreter, "select",
"Change the currently selected thread to thread representation of "
"the given Swift Task. See `thread select`.",
"language swift task select <variable-name>") {
AddSimpleArgumentList(eArgTypeVarName);
}

auto thread_task = ThreadTask::Create(
task_info->id, task_info->resumeAsyncContext, m_exe_ctx);
private:
void DoExecute(Args &command, CommandReturnObject &result) override {
llvm::Expected<ThreadSP> thread_task =
ThreadForTaskVariable(command, m_exe_ctx);
if (auto error = thread_task.takeError()) {
result.AppendError(toString(std::move(error)));
return;
}

// GetStatus prints the backtrace.
thread_task.get()->GetStatus(result.GetOutputStream(), 0, UINT32_MAX, 0,
false, true);
auto &thread_list = m_exe_ctx.GetProcessRef().GetThreadList();

Choose a reason for hiding this comment

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

The Process class has a mutex to protect its thread list; should we lock it here?

Copy link
Author

Choose a reason for hiding this comment

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

Good question. The ThreadList references the processes lock. The lock is taken inside AddThread, I would say there's no need to take the lock here explicitly.

Choose a reason for hiding this comment

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

Ah, I wasn't aware that these methods were already thread-safe.
I guess the only argument for locking outside would be if these two operations need to be atomic:

   thread_list.AddThread(thread_task.get());
   thread_list.SetSelectedThreadByID(thread_task.get()->GetID())

If they don't, then no locking is needed

Copy link
Author

Choose a reason for hiding this comment

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

I think these being non-atomic is ok. If another thread is attempting to remove the thread_task (which strikes me as unlikely), then I'm not sure it matters whether the removal happens before or after SetSelectedThreadByID.

thread_list.AddThread(thread_task.get());
thread_list.SetSelectedThreadByID(thread_task.get()->GetID());

result.SetStatus(lldb::eReturnStatusSuccessFinishResult);
}
};
Expand All @@ -2166,6 +2190,9 @@ class CommandObjectLanguageSwiftTask final : public CommandObjectMultiword {
LoadSubCommand("backtrace",
CommandObjectSP(new CommandObjectLanguageSwiftTaskBacktrace(
interpreter)));
LoadSubCommand(
"select",
CommandObjectSP(new CommandObjectLanguageSwiftTaskSelect(interpreter)));
}
};

Expand Down
55 changes: 55 additions & 0 deletions lldb/test/API/lang/swift/async/tasks/TestSwiftTaskSelect.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import lldb
from lldbsuite.test.decorators import *
from lldbsuite.test.lldbtest import TestBase
import lldbsuite.test.lldbutil as lldbutil


class TestCase(TestBase):

def test_backtrace_selected_task(self):
self.build()
lldbutil.run_to_source_breakpoint(
self, "break here", lldb.SBFileSpec("main.swift")
)
self.runCmd("language swift task select task")
self.expect(
"thread backtrace",
substrs=[
".sleep(",
"`second() at main.swift:6:",
"`first() at main.swift:2:",
"`closure #1 in static Main.main() at main.swift:12:",
],
)

def test_navigate_selected_task_stack(self):
self.build()
_, process, _, _ = lldbutil.run_to_source_breakpoint(
self, "break here", lldb.SBFileSpec("main.swift")
)
self.runCmd("language swift task select task")

thread = process.selected_thread
self.assertEqual(thread.id, 2)
self.assertEqual(thread.idx, 0xFFFFFFFF)
self.assertIn(
"libswift_Concurrency.", thread.GetSelectedFrame().module.file.basename
)

frame_idx = -1
for frame in thread:
if "`second()" in str(frame):
frame_idx = frame.idx
self.assertNotEqual(frame_idx, -1)

self.expect(f"frame select {frame_idx}", substrs=[f"frame #{frame_idx}:"])
frame = thread.GetSelectedFrame()
self.assertIn(".second()", frame.function.name)

self.expect("up", substrs=[f"frame #{frame_idx + 1}:"])
frame = thread.GetSelectedFrame()
self.assertIn(".first()", frame.function.name)

self.expect("up", substrs=[f"frame #{frame_idx + 2}:"])
frame = thread.GetSelectedFrame()
self.assertIn(".Main.main()", frame.function.name)