Fix JSEntry unwinding data on Windows ARM64

The current unwinding data for JSEntry correctly restores the frame
pointer and program counter from the caller frame, which might or might
not be sufficient to continue unwinding, depending on the contents of
that caller frame. Currently, the cctest StackUnwindingWin64 is broken
(at least with my build config) because the caller frame also needs the
stack pointer, which is not restored correctly.

In particular, I see this xdata for v8::internal::GeneratedCode<...>,
which is the function that calls Builtins_JSEntry:

10400015 : 2 code words, 1 epilog, function length=15
01000012 : epilog starts at 12 and its unwind handler starts at 4
e405c8d2 : save_reg x=b z=8
           alloc_s x=5
           end
e405c8d2 : same thing but for the epilog

The prolog that corresponds to the unwind codes above is:

sub sp, sp, #50
str lr, [sp, #0x40]

Note that it does not set fp, so unwinding requires an accurate sp.

This change emits slightly more complicated unwinding data for JSEntry
so that the frame pointer, stack pointer, and program counter can all be
restored.

Change-Id: I0c7f3eba97ef64408f46631b487c4b0ceb06fa9b
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1848860
Commit-Queue: Seth Brenith <seth.brenith@microsoft.com>
Reviewed-by: Jakob Gruber <jgruber@chromium.org>
Reviewed-by: Michael Starzinger <mstarzinger@chromium.org>
Cr-Commit-Position: refs/heads/master@{#64435}
This commit is contained in:
Seth Brenith 2019-10-17 16:12:33 -07:00 committed by Commit Bot
parent 2669f27daa
commit cdf8925e1a
4 changed files with 147 additions and 50 deletions

View File

@ -649,7 +649,8 @@ void Generate_JSEntryVariant(MacroAssembler* masm, StackFrame::Type type,
win64_unwindinfo::XdataEncoder* xdata_encoder = masm->GetXdataEncoder();
if (xdata_encoder) {
xdata_encoder->onFramePointerAdjustment(
EntryFrameConstants::kDirectCallerFPOffset);
EntryFrameConstants::kDirectCallerFPOffset,
EntryFrameConstants::kDirectCallerSPOffset);
}
#endif

View File

@ -182,15 +182,17 @@ void InitUnwindingRecord(Record* record, size_t code_size_in_bytes) {
// https://docs.microsoft.com/en-us/cpp/build/arm64-exception-handling#unwind-codes
enum UnwindOp8Bit {
OpNop = 0xE3,
OpAllocS = 0x00,
OpSaveFpLr = 0x40,
OpSaveFpLrX = 0x80,
OpSetFp = 0xE1,
OpAddFp = 0xE2,
OpEnd = 0xE4,
};
typedef uint32_t UNWIND_CODE;
constexpr UNWIND_CODE Combine8BitUnwindCodes(uint8_t code0,
constexpr UNWIND_CODE Combine8BitUnwindCodes(uint8_t code0 = OpNop,
uint8_t code1 = OpNop,
uint8_t code2 = OpNop,
uint8_t code3 = OpNop) {
@ -211,39 +213,97 @@ struct UNWIND_INFO {
uint32_t CodeWords : 5;
};
static constexpr int kNumberOfUnwindCodes = 1;
static constexpr int kDefaultNumberOfUnwindCodeWords = 1;
static constexpr int kMaxExceptionThunkSize = 16;
static constexpr int kFunctionLengthShiftSize = 2;
static constexpr int kFunctionLengthMask = (1 << kFunctionLengthShiftSize) - 1;
static constexpr int kFramePointerAdjustmentShiftSize = 3;
static constexpr int kFramePointerAdjustmentShiftMask =
(1 << kFramePointerAdjustmentShiftSize) - 1;
static constexpr int kAllocStackShiftSize = 4;
static constexpr int kAllocStackShiftMask = (1 << kAllocStackShiftSize) - 1;
// Generate an unwind code for "stp fp, lr, [sp, #pre_index_offset]!".
uint8_t MakeOpSaveFpLrX(int pre_index_offset) {
// See unwind code save_fplr_x in
// https://docs.microsoft.com/en-us/cpp/build/arm64-exception-handling#unwind-codes
DCHECK_LE(pre_index_offset, -8);
DCHECK_GE(pre_index_offset, -512);
constexpr int kShiftSize = 3;
constexpr int kShiftMask = (1 << kShiftSize) - 1;
DCHECK_EQ(pre_index_offset & kShiftMask, 0);
USE(kShiftMask);
// Solve for Z where -(Z+1)*8 = pre_index_offset.
int encoded_value = (-pre_index_offset >> kShiftSize) - 1;
return OpSaveFpLrX | encoded_value;
}
// Generate an unwind code for "sub sp, sp, #stack_space".
uint8_t MakeOpAllocS(int stack_space) {
// See unwind code alloc_s in
// https://docs.microsoft.com/en-us/cpp/build/arm64-exception-handling#unwind-codes
DCHECK_GE(stack_space, 0);
DCHECK_LT(stack_space, 512);
DCHECK_EQ(stack_space & kAllocStackShiftMask, 0);
return OpAllocS | (stack_space >> kAllocStackShiftSize);
}
// Generate the second byte of the unwind code for "add fp, sp, #offset".
uint8_t MakeOpAddFpArgument(int offset) {
// See unwind code add_fp in
// https://docs.microsoft.com/en-us/cpp/build/arm64-exception-handling#unwind-codes
DCHECK_GE(offset, 0);
constexpr int kShiftSize = 3;
constexpr int kShiftMask = (1 << kShiftSize) - 1;
DCHECK_EQ(offset & kShiftMask, 0);
USE(kShiftMask);
int encoded_value = offset >> kShiftSize;
// Encoded value must fit in 8 bits.
DCHECK_LE(encoded_value, 0xff);
return encoded_value;
}
template <int kNumberOfUnwindCodeWords = kDefaultNumberOfUnwindCodeWords>
struct V8UnwindData {
UNWIND_INFO unwind_info;
UNWIND_CODE unwind_codes[kNumberOfUnwindCodes];
UNWIND_CODE unwind_codes[kNumberOfUnwindCodeWords];
V8UnwindData() {
memset(&unwind_info, 0, sizeof(UNWIND_INFO));
unwind_info.X = 1; // has exception handler after unwind-codes.
unwind_info.CodeWords = 1;
unwind_info.CodeWords = kNumberOfUnwindCodeWords;
// stp fp, lr, [sp, #offset]!
unwind_codes[0] = Combine8BitUnwindCodes(OpSetFp, OpSaveFpLrX, OpEnd);
// Generate unwind codes for the following prolog:
//
// stp fp, lr, [sp, #-kCallerSPOffset]!
// mov fp, sp
//
// This is a very rough approximation of the actual function prologs used in
// V8. In particular, we often push other data before the (fp, lr) pair,
// meaning the stack pointer computed for the caller frame is wrong. That
// error is acceptable when the unwinding info for the caller frame also
// depends on fp rather than sp, as is the case for V8 builtins and runtime-
// generated code.
STATIC_ASSERT(kNumberOfUnwindCodeWords >= 1);
unwind_codes[0] = Combine8BitUnwindCodes(
OpSetFp, MakeOpSaveFpLrX(-CommonFrameConstants::kCallerSPOffset),
OpEnd);
// Fill the rest with nops.
for (int i = 1; i < kNumberOfUnwindCodeWords; ++i) {
unwind_codes[i] = Combine8BitUnwindCodes();
}
}
};
struct CodeRangeUnwindingRecord {
void* dynamic_table;
uint32_t runtime_function_count;
V8UnwindData unwind_info;
V8UnwindData<> unwind_info;
uint32_t exception_handler;
// For Windows ARM64 unwinding, register 2 unwind_info for each code range,
// unwind_info for all full size ranges (1MB - 4 bytes) and unwind_info1 for
// the remaining non full size range. There is at most 1 range which is less
// than full size.
V8UnwindData unwind_info1;
V8UnwindData<> unwind_info1;
uint32_t exception_handler1;
uint8_t exception_thunk[kMaxExceptionThunkSize];
@ -255,33 +315,66 @@ struct CodeRangeUnwindingRecord {
#pragma pack(pop)
std::vector<uint8_t> GetUnwindInfoForBuiltinFunction(uint32_t func_len,
int32_t fp_adjustment) {
FrameOffsets::FrameOffsets()
: fp_to_saved_caller_fp(CommonFrameConstants::kCallerFPOffset),
fp_to_caller_sp(CommonFrameConstants::kCallerSPOffset) {}
bool FrameOffsets::IsDefault() const {
FrameOffsets other;
return fp_to_saved_caller_fp == other.fp_to_saved_caller_fp &&
fp_to_caller_sp == other.fp_to_caller_sp;
}
std::vector<uint8_t> GetUnwindInfoForBuiltinFunction(
uint32_t func_len, FrameOffsets fp_adjustment) {
DCHECK_LE(func_len, kMaxFunctionLength);
DCHECK_EQ((func_len & kFunctionLengthMask), 0);
USE(kFunctionLengthMask);
// Unwind code save_fplr requires the offset to be within range [0, 504].
// This range is defined in below doc for unwind code save_fplr.
// https://docs.microsoft.com/en-us/cpp/build/arm64-exception-handling#unwind-codes
DCHECK_GE(fp_adjustment, 0);
DCHECK_LE(fp_adjustment, 504);
DCHECK_EQ((fp_adjustment & kFramePointerAdjustmentShiftMask), 0);
USE(kFramePointerAdjustmentShiftMask);
// The largest size of unwind data required for all options below.
constexpr int kMaxNumberOfUnwindCodeWords = 2;
V8UnwindData xdata;
V8UnwindData<kMaxNumberOfUnwindCodeWords> xdata;
// FunctionLength is ensured to be aligned at instruction size and Windows
// ARM64 doesn't encoding its 2 LSB.
xdata.unwind_info.FunctionLength = func_len >> kFunctionLengthShiftSize;
xdata.unwind_info.CodeWords = 1;
xdata.unwind_codes[0] = Combine8BitUnwindCodes(
OpSetFp,
(OpSaveFpLr | (fp_adjustment >> kFramePointerAdjustmentShiftSize)),
OpEnd);
if (fp_adjustment.IsDefault()) {
// One code word is plenty.
STATIC_ASSERT(kDefaultNumberOfUnwindCodeWords <
kMaxNumberOfUnwindCodeWords);
xdata.unwind_info.CodeWords = kDefaultNumberOfUnwindCodeWords;
} else {
// We want to convey the following facts:
// 1. The caller's fp is found at [fp + fp_to_saved_caller_fp].
// 2. The caller's pc is found at [fp + fp_to_saved_caller_fp + 8].
// 3. The caller's sp is equal to fp + fp_to_caller_sp.
//
// An imaginary prolog that would establish those relationships might look
// like the following, with appropriate values for the various constants:
//
// stp fp, lr, [sp, #pre_index_amount]!
// sub sp, sp, #stack_space
// add fp, sp, offset_from_stack_top
//
// Why do we need offset_from_stack_top? The unwinding encoding for
// allocating stack space has 16-byte granularity, and the frame pointer has
// only 8-byte alignment.
int pre_index_amount =
fp_adjustment.fp_to_saved_caller_fp - fp_adjustment.fp_to_caller_sp;
int stack_space = fp_adjustment.fp_to_saved_caller_fp;
int offset_from_stack_top = stack_space & kAllocStackShiftMask;
stack_space += offset_from_stack_top;
xdata.unwind_codes[0] = Combine8BitUnwindCodes(
OpAddFp, MakeOpAddFpArgument(offset_from_stack_top),
MakeOpAllocS(stack_space), MakeOpSaveFpLrX(pre_index_amount));
xdata.unwind_codes[1] = Combine8BitUnwindCodes(OpEnd);
}
return std::vector<uint8_t>(
reinterpret_cast<uint8_t*>(&xdata),
reinterpret_cast<uint8_t*>(&xdata) + sizeof(xdata));
reinterpret_cast<uint8_t*>(
&xdata.unwind_codes[xdata.unwind_info.CodeWords]));
}
template <typename Record>
@ -511,17 +604,13 @@ void XdataEncoder::onSaveFpLr() {
current_frame_code_offset_ = assembler_.pc_offset() - 4;
fp_offsets_.push_back(current_frame_code_offset_);
fp_adjustments_.push_back(current_frame_adjustment_);
if (current_frame_adjustment_ != 0) {
current_frame_adjustment_ = 0;
}
current_frame_adjustment_ = FrameOffsets();
}
void XdataEncoder::onFramePointerAdjustment(int bytes) {
// According to below doc, offset for save_fplr is aligned to pointer size.
// https://docs.microsoft.com/en-us/cpp/build/arm64-exception-handling#unwind-codes
DCHECK_EQ((bytes & kPointerAlignmentMask), 0);
current_frame_adjustment_ = bytes;
void XdataEncoder::onFramePointerAdjustment(int fp_to_saved_caller_fp,
int fp_to_caller_sp) {
current_frame_adjustment_.fp_to_saved_caller_fp = fp_to_saved_caller_fp;
current_frame_adjustment_.fp_to_caller_sp = fp_to_caller_sp;
}
#endif // V8_OS_WIN_X64

View File

@ -114,6 +114,13 @@ class XdataEncoder {
*/
static const int kMaxFunctionLength = ((1 << 18) - 1) << 2;
struct FrameOffsets {
FrameOffsets();
bool IsDefault() const;
int fp_to_saved_caller_fp;
int fp_to_caller_sp;
};
/**
* Returns a vector of bytes that contains the Win ARM64 unwind data used for
* all V8 builtin functions.
@ -123,18 +130,20 @@ static const int kMaxFunctionLength = ((1 << 18) - 1) << 2;
* this is necessary to encode unwind data for Windows stack
* unwinder to find correct caller's fp.
*/
std::vector<uint8_t> GetUnwindInfoForBuiltinFunction(uint32_t func_len,
int32_t fp_adjustment);
std::vector<uint8_t> GetUnwindInfoForBuiltinFunction(
uint32_t func_len, FrameOffsets fp_adjustment);
class BuiltinUnwindInfo {
public:
BuiltinUnwindInfo() : is_leaf_function_(true) {}
explicit BuiltinUnwindInfo(const std::vector<int>& fp_offsets,
const std::vector<int>& fp_adjustments)
const std::vector<FrameOffsets>& fp_adjustments)
: is_leaf_function_(false),
fp_offsets_(fp_offsets),
fp_adjustments_(fp_adjustments) {}
const std::vector<int>& fp_adjustments() const { return fp_adjustments_; }
const std::vector<FrameOffsets>& fp_adjustments() const {
return fp_adjustments_;
}
bool is_leaf_function() const { return is_leaf_function_; }
const std::vector<int>& fp_offsets() const { return fp_offsets_; }
@ -142,18 +151,16 @@ class BuiltinUnwindInfo {
private:
bool is_leaf_function_;
std::vector<int> fp_offsets_;
std::vector<int> fp_adjustments_;
std::vector<FrameOffsets> fp_adjustments_;
};
class XdataEncoder {
public:
explicit XdataEncoder(const Assembler& assembler)
: assembler_(assembler),
current_frame_code_offset_(-1),
current_frame_adjustment_(0) {}
: assembler_(assembler), current_frame_code_offset_(-1) {}
void onSaveFpLr();
void onFramePointerAdjustment(int bytes);
void onFramePointerAdjustment(int fp_to_saved_caller_fp, int fp_to_caller_sp);
BuiltinUnwindInfo unwinding_info() const {
return BuiltinUnwindInfo(fp_offsets_, fp_adjustments_);
@ -163,8 +170,8 @@ class XdataEncoder {
const Assembler& assembler_;
std::vector<int> fp_offsets_;
int current_frame_code_offset_;
int current_frame_adjustment_;
std::vector<int> fp_adjustments_;
FrameOffsets current_frame_adjustment_;
std::vector<FrameOffsets> fp_adjustments_;
};
#endif

View File

@ -238,7 +238,7 @@ void EmitUnwindData(PlatformEmbeddedFileWriterWin* w,
w->Comment(" UnwindInfoAddress");
w->StartPdataSection();
std::vector<int> code_chunks;
std::vector<int> fp_adjustments;
std::vector<win64_unwindinfo::FrameOffsets> fp_adjustments;
for (int i = 0; i < Builtins::builtin_count; i++) {
if (!blob->ContainsBuiltin(i)) continue;
@ -249,7 +249,7 @@ void EmitUnwindData(PlatformEmbeddedFileWriterWin* w,
uint32_t builtin_size = blob->InstructionSizeOfBuiltin(i);
const std::vector<int>& xdata_desc = unwind_infos[i].fp_offsets();
const std::vector<int>& xdata_fp_adjustments =
const std::vector<win64_unwindinfo::FrameOffsets>& xdata_fp_adjustments =
unwind_infos[i].fp_adjustments();
DCHECK_EQ(xdata_desc.size(), xdata_fp_adjustments.size());