Skip to content

Fix swift async frames unwinding unwinding #9025

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

Conversation

felipepiovezan
Copy link

@felipepiovezan felipepiovezan commented Jul 29, 2024

This series of patches aims to solve the problem where LLDB is unable to print variables in backtraces involving swift async functions. The only frame where this works today is the first async frame, which is a real frame and is physically on the stack. We fail to do so for all virtual frames.

The underlying problem is simple: when unwinding the PC, we use the first instruction of the continuation funclets. This instruction is part of the prologue, where no variables are in scope.

The solution is similarly simple: we just offset the PC by the size of the prologue of the continuation funclet.

These commits are meant to be reviewed independently, please the commit messages too.

The first commit is a cherry-pick from: llvm#100624

@felipepiovezan
Copy link
Author

felipepiovezan commented Jul 29, 2024

Requires: swiftlang/swift#75553
edit: no longer true, reworked to remove the Q funclet fixes

// Creates an expression accessing (fp - 8), with an optional dereference
// operation. This is only valid for x86_64 or aarch64.
llvm::ArrayRef<uint8_t>
getAsyncRegFromFramePointerDWARFExpr(llvm::Triple::ArchType triple,

Choose a reason for hiding this comment

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

lldb functions start with an Upper case letter.

Copy link
Author

Choose a reason for hiding this comment

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

Fixed in the new version

@felipepiovezan felipepiovezan force-pushed the felipe/unwinding_pc_patches_to_release branch from 9964bc1 to 317e4a6 Compare July 30, 2024 15:21
@@ -2586,6 +2582,39 @@ lldb::addr_t SwiftLanguageRuntime::GetAsyncContext(RegisterContext *regctx) {
return LLDB_INVALID_ADDRESS;
}

// Creates an expression accessing (fp - 8), with an optional dereference

Choose a reason for hiding this comment

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

///

Copy link
Author

Choose a reason for hiding this comment

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

Done

// works.
static const uint8_t g_cfa_dwarf_expression_x86_64[] = {
llvm::dwarf::DW_OP_breg6, // DW_OP_breg6, register 6 == rbp
0x78, // sleb128 -8 (ptrsize)

Choose a reason for hiding this comment

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

I don't understand this comment. Yes, it's 128-8, but sleb128 is a variable-length encoding, so I'm confused.

Choose a reason for hiding this comment

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

I also understand this is not new in your patch.

Copy link
Author

Choose a reason for hiding this comment

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

Can you elaborate? -8 is 0x78 in sleb128 encoding, and it takes a single byte to represent it.
The comment is explaining where the magic 0x78 comes from.

@@ -2765,9 +2761,9 @@ SwiftLanguageRuntime::GetRuntimeUnwindPlan(ProcessSP process_sp,

// Creates an UnwindPlan for following the AsyncContext chain
// up the stack, from a current AsyncContext frame.

Choose a reason for hiding this comment

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

This should be a doxygen comment on the declaration.

Copy link
Author

Choose a reason for hiding this comment

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

Done


auto pc_after_prologue = [&]() -> std::optional<addr_t> {
// If we're in the prologue, we can just use our x22, it is unmodified.

Choose a reason for hiding this comment

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

is there a generic name for x22 (since this is arch specific?)

Choose a reason for hiding this comment

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

context_reg or some thing like that?

Copy link
Author

Choose a reason for hiding this comment

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

calling is "async_reg" to match other comments in this file which refer to it as "asynchronous register"

// Otherwise, check the ABI guaranteed slot for the saved x22 reg: *(fp - 8).
Status error;
addr_t x22_entry_value;
process_sp->ReadMemory(fp - ptr_size, &x22_entry_value, ptr_size, error);

Choose a reason for hiding this comment

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

We should comment that this is true on all ABIs, otherwise this screams for being an ABI callback.

Copy link
Author

Choose a reason for hiding this comment

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

done

row->SetRegisterLocationToAtCFAPlusOffset(regnums->pc_regnum, ptr_size,
false);
// Since we pretend x22 is the register "passed" to our child frame, there
// is always at least one indirection.

Choose a reason for hiding this comment

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

Can you add the missing detail to this comment that explains why the indirection follows from it being a virtual frame?

Copy link
Author

Choose a reason for hiding this comment

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

I've reworded this entire paragraph to remove this part of the explanation that requires a lot of background information.

if (async_reg_val == LLDB_INVALID_ADDRESS || async_reg_val == 0)
return {};

const auto ptr_size = 8;

Choose a reason for hiding this comment

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

get this from process?

Copy link
Author

Choose a reason for hiding this comment

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

Updated

return {};

Address pc;
Target &target(process.GetTarget());

Choose a reason for hiding this comment

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

Target &target = process.GetTarget(); ?

Copy link
Author

Choose a reason for hiding this comment

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

Updated

@felipepiovezan felipepiovezan force-pushed the felipe/unwinding_pc_patches_to_release branch from 317e4a6 to 1aa6a85 Compare July 30, 2024 20:18
Copy link
Author

@felipepiovezan felipepiovezan left a comment

Choose a reason for hiding this comment

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

Address review comments

return {};

Address pc;
Target &target(process.GetTarget());
Copy link
Author

Choose a reason for hiding this comment

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

Updated

if (async_reg_val == LLDB_INVALID_ADDRESS || async_reg_val == 0)
return {};

const auto ptr_size = 8;
Copy link
Author

Choose a reason for hiding this comment

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

Updated

// works.
static const uint8_t g_cfa_dwarf_expression_x86_64[] = {
llvm::dwarf::DW_OP_breg6, // DW_OP_breg6, register 6 == rbp
0x78, // sleb128 -8 (ptrsize)
Copy link
Author

Choose a reason for hiding this comment

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

Can you elaborate? -8 is 0x78 in sleb128 encoding, and it takes a single byte to represent it.
The comment is explaining where the magic 0x78 comes from.


auto pc_after_prologue = [&]() -> std::optional<addr_t> {
// If we're in the prologue, we can just use our x22, it is unmodified.
Copy link
Author

Choose a reason for hiding this comment

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

calling is "async_reg" to match other comments in this file which refer to it as "asynchronous register"

// Otherwise, check the ABI guaranteed slot for the saved x22 reg: *(fp - 8).
Status error;
addr_t x22_entry_value;
process_sp->ReadMemory(fp - ptr_size, &x22_entry_value, ptr_size, error);
Copy link
Author

Choose a reason for hiding this comment

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

done

row->SetRegisterLocationToAtCFAPlusOffset(regnums->pc_regnum, ptr_size,
false);
// Since we pretend x22 is the register "passed" to our child frame, there
// is always at least one indirection.
Copy link
Author

Choose a reason for hiding this comment

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

I've reworded this entire paragraph to remove this part of the explanation that requires a lot of background information.

@@ -2765,9 +2761,9 @@ SwiftLanguageRuntime::GetRuntimeUnwindPlan(ProcessSP process_sp,

// Creates an UnwindPlan for following the AsyncContext chain
// up the stack, from a current AsyncContext frame.
Copy link
Author

Choose a reason for hiding this comment

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

Done

@@ -2586,6 +2582,39 @@ lldb::addr_t SwiftLanguageRuntime::GetAsyncContext(RegisterContext *regctx) {
return LLDB_INVALID_ADDRESS;
}

// Creates an expression accessing (fp - 8), with an optional dereference
Copy link
Author

Choose a reason for hiding this comment

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

Done

This will enable subsequent commits to introduce calls to other member functions
from that method.
@felipepiovezan felipepiovezan force-pushed the felipe/unwinding_pc_patches_to_release branch from 1aa6a85 to 8e0cfd2 Compare July 31, 2024 17:51
@felipepiovezan felipepiovezan changed the base branch from stable/20230725 to swift/release/6.0 July 31, 2024 17:52
@felipepiovezan
Copy link
Author

Removed the last two commits, which dealt with Q funclets as frame 0 and required compiler changes.
This PR no longer needs any compiler changes, going to propose it for the 6.0 branch

@felipepiovezan felipepiovezan marked this pull request as ready for review July 31, 2024 18:16
@felipepiovezan
Copy link
Author

@swift-ci test


// Compute the CFA of this frame.
addr_t cfa = async_reg_val;
while (num_indirections--) {

Choose a reason for hiding this comment

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

The fact that this underflows on the last iteration bothers me a bit.
for (; num_indirections != 0; --num_indirections) ?

Copy link
Author

Choose a reason for hiding this comment

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

Done, good catch!


func ASYNC___2___() async -> Int {
var myvar = 222;
let result = await ASYNC___1___() //breakpoint1

Choose a reason for hiding this comment

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

// breakpoint1

Copy link
Author

Choose a reason for hiding this comment

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

Fixed

Variables are never available in the prologue of a function. By unwinding the pc
of the virtual frames as the first instruction of the continuation function,
LLDB is unable to display any variable information.

This patch addresses the issue by computing the continuation pointer from the
context of the function being unwound, and then querying the continuation's
SymbolContext to determine its prologue size.
@felipepiovezan felipepiovezan force-pushed the felipe/unwinding_pc_patches_to_release branch from 8e0cfd2 to 85069c8 Compare July 31, 2024 20:55
Copy link
Author

@felipepiovezan felipepiovezan left a comment

Choose a reason for hiding this comment

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

Addressed review comments


func ASYNC___2___() async -> Int {
var myvar = 222;
let result = await ASYNC___1___() //breakpoint1
Copy link
Author

Choose a reason for hiding this comment

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

Fixed


// Compute the CFA of this frame.
addr_t cfa = async_reg_val;
while (num_indirections--) {
Copy link
Author

Choose a reason for hiding this comment

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

Done, good catch!

@felipepiovezan
Copy link
Author

@swift-ci test

@felipepiovezan
Copy link
Author

@swift-ci test platform windows

@felipepiovezan
Copy link
Author

@swift-ci test windows platform

@felipepiovezan
Copy link
Author

@swift-ci test windows platform

@adrian-prantl adrian-prantl merged commit d85bebe into swiftlang:swift/release/6.0 Aug 1, 2024
3 checks passed
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.

3 participants