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] Introduce backtracing of Swift Tasks #9787

Draft
wants to merge 2 commits into
base: swift/release/6.1
Choose a base branch
from

Conversation

kastiglione
Copy link

No description provided.

@kastiglione kastiglione requested a review from a team as a code owner December 20, 2024 23:34
@kastiglione kastiglione marked this pull request as draft December 20, 2024 23:35
Comment on lines +824 to +831
// 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());
Copy link
Author

Choose a reason for hiding this comment

The 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 language swift task backtrace.

ExecutionContext &exe_ctx)
: Thread(exe_ctx.GetProcessRef(), tid, true),
m_concrete_reg_ctx_sp(exe_ctx.GetFrameSP()->GetRegisterContext()) {
m_async_ctx = async_ctx;

Choose a reason for hiding this comment

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

we can initialize m_sync_ctx in the member initializer list?

Copy link
Author

@kastiglione kastiglione Dec 23, 2024

Choose a reason for hiding this comment

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

I wanted like with like: to place the logic for initializing the two register values side by side.

/// 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;

Choose a reason for hiding this comment

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

The name pc here is a bit misleading: it would be natural to think this contains the PC of the top frame of the thread, which is not what this variable contains.
Maybe it would be better to call this m_continuation_address?

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 pc, so I don't think the same interpretation would apply.

Choose a reason for hiding this comment

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

Actually I am confused now, is this really printing frame 0 correctly?
In the register context class, there is this code:

  if (reg_info->kinds[eRegisterKindGeneric] == LLDB_REGNUM_GENERIC_PC) {
     reg_value = m_pc;
     return true;
   }

I could be missing something, but this looks like it prints the PC of frame 1 instead of frame 0.

Copy link
Author

@kastiglione kastiglione Dec 23, 2024

Choose a reason for hiding this comment

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

maybe m_resume_address? since "resume" is the term used in AsyncContext.

I'm not sure where the confusion stems from, but yes this prints "frame 0" correctly.

Copy link
Author

Choose a reason for hiding this comment

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

I changed it to m_resume_fn, how does that sound?

Choose a reason for hiding this comment

The 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.

Copy link
Author

Choose a reason for hiding this comment

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

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.

👍

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

Choose a reason for hiding this comment

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

I'm moderately concerned that this ctor may fail.
Should we make the constructor take the continuation ptr directly and instead create a "make thread function" that can fail?

Copy link
Author

Choose a reason for hiding this comment

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

That seems fine to me. Note that if ReadPointerFromMemory fails, the resulting address is LLDB_INVALID_ADDRESS, which subsequent code should handle.

Choose a reason for hiding this comment

The 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?

Copy link
Author

Choose a reason for hiding this comment

The 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?

uint32_t resume_ptr = async_ctx + resume_offset;
Status status;
m_resume_fn =
exe_ctx.GetProcessRef().ReadPointerFromMemory(resume_ptr, status);

Choose a reason for hiding this comment

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

IIUC to apply the skip prologue trick, you would need to factor out the fragment below of SwiftLanguageRuntime::TrySkipVirtualParentProlog into a separate function, and say that pc_value = m_resume_fn:

  Address pc;
  Target &target = process.GetTarget();
  pc.SetLoadAddress(pc_value, &target);
  if (!pc.IsValid())
    return {};

  SymbolContext sc;
  if (!pc.CalculateSymbolContext(&sc,
                                 eSymbolContextFunction | eSymbolContextSymbol))
    return {};
  if (!sc.symbol && !sc.function)
    return {};

  auto prologue_size = sc.symbol ? sc.symbol->GetPrologueByteSize()
                                 : sc.function->GetPrologueByteSize();
  return pc_value + prologue_size;

// TaskContinuationFunction *ResumeParent; // offset 8
// };
uint32_t resume_offset = ptr_size; // offsetof(AsyncContext, ResumeParent)
uint32_t resume_ptr = async_ctx + resume_offset;

Choose a reason for hiding this comment

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

Given that you show the offsets of Parent and ResumeParent in the comment, is it clarifying to have resume_offset. Why not just addr_t resume_ptr = async_ctx + ptr_size. (also why is resume_ptr uint32_t, it's adding an addr_t and an offset isn't it?)

Copy link
Author

Choose a reason for hiding this comment

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

also why is resume_ptr uint32_t, it's adding an addr_t and an offset isn't it?

yep you caught a bug, it was after I switched these variables away from auto types.

Choose a reason for hiding this comment

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

That's one of the reasons I will die on the proverbial auto hill 👀

Copy link
Author

Choose a reason for hiding this comment

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

I'm with you

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants