Commit edd7434b authored by Seth Brenith's avatar Seth Brenith Committed by Commit Bot

Update Unwinder to handle JSEntry frame on Windows ARM64

On Windows ARM64, it is insufficient to just follow the linked list of
frame pointers in all cases. This is similar to logic added in
https://crrev.com/c/v8/v8/+/1701133 except this affects the Unwinder
methods rather than the function metadata for RtlVirtualUnwind.

Together with https://crrev.com/c/chromium/src/+/1844276 , this allows
the Chromium unit test V8UnwinderTest.UnwindThroughV8Frames to pass on
Windows ARM64.

Change-Id: I82d4d894be14d4a6ace75bba10c13b10342d0b12
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1845189Reviewed-by: 's avatarPeter Marshall <petermarshall@chromium.org>
Reviewed-by: 's avatarUlan Degenbaev <ulan@chromium.org>
Reviewed-by: 's avatarJakob Gruber <jgruber@chromium.org>
Reviewed-by: 's avatarMichael Starzinger <mstarzinger@chromium.org>
Commit-Queue: Seth Brenith <seth.brenith@microsoft.com>
Cr-Commit-Position: refs/heads/master@{#64432}
parent e676ba80
......@@ -2206,6 +2206,8 @@ struct UnwindState {
MemoryRange code_range;
MemoryRange embedded_code_range;
JSEntryStub js_entry_stub;
JSEntryStub js_construct_entry_stub;
JSEntryStub js_run_microtasks_entry_stub;
};
/**
......
......@@ -8730,10 +8730,17 @@ UnwindState Isolate::GetUnwindState() {
unwind_state.embedded_code_range.length_in_bytes =
isolate->embedded_blob_size();
i::Code js_entry = isolate->heap()->builtin(i::Builtins::kJSEntry);
unwind_state.js_entry_stub.code.start =
std::array<std::pair<i::Builtins::Name, JSEntryStub*>, 3> entry_stubs = {
{{i::Builtins::kJSEntry, &unwind_state.js_entry_stub},
{i::Builtins::kJSConstructEntry, &unwind_state.js_construct_entry_stub},
{i::Builtins::kJSRunMicrotasksEntry,
&unwind_state.js_run_microtasks_entry_stub}}};
for (auto& pair : entry_stubs) {
i::Code js_entry = isolate->heap()->builtin(pair.first);
pair.second->code.start =
reinterpret_cast<const void*>(js_entry.InstructionStart());
unwind_state.js_entry_stub.code.length_in_bytes = js_entry.InstructionSize();
pair.second->code.length_in_bytes = js_entry.InstructionSize();
}
return unwind_state;
}
......
......@@ -645,14 +645,11 @@ void Generate_JSEntryVariant(MacroAssembler* masm, StackFrame::Type type,
// to point to bad_frame_pointer below. To fix unwind information for this
// case, JSEntry registers the offset (from current fp to the caller's fp
// saved by PushCalleeSavedRegisters on stack) to xdata_encoder which then
// emits the offset value as part of result unwind data accordingly. The
// current offset is kFramePointerOffset which includes bad_frame_pointer
// saved below plus kFramePointerOffsetInPushCalleeSavedRegisters.
const int kFramePointerOffset =
kFramePointerOffsetInPushCalleeSavedRegisters + kSystemPointerSize;
// emits the offset value as part of result unwind data accordingly.
win64_unwindinfo::XdataEncoder* xdata_encoder = masm->GetXdataEncoder();
if (xdata_encoder) {
xdata_encoder->onFramePointerAdjustment(kFramePointerOffset);
xdata_encoder->onFramePointerAdjustment(
EntryFrameConstants::kDirectCallerFPOffset);
}
#endif
......@@ -674,6 +671,10 @@ void Generate_JSEntryVariant(MacroAssembler* masm, StackFrame::Type type,
masm->isolate()));
__ Ldr(x10, MemOperand(x11));
// x13 (the bad frame pointer) is the first item pushed.
STATIC_ASSERT(EntryFrameConstants::kOffsetToCalleeSavedRegisters ==
1 * kSystemPointerSize);
__ Push(x13, x12, xzr, x10);
// Set up fp.
__ Sub(fp, sp, EntryFrameConstants::kCallerFPOffset);
......
......@@ -1295,15 +1295,16 @@ void MacroAssembler::PushCalleeSavedRegisters() {
stp(d10, d11, tos);
stp(d8, d9, tos);
stp(x29, x30, tos);
#if defined(V8_OS_WIN)
// kFramePointerOffsetInPushCalleeSavedRegisters is the offset from tos at
// the end of this function to the saved caller's fp/x29 pointer. It includes
// registers from x19 to x28, which is 10 pointers defined by below stp
// instructions.
STATIC_ASSERT(kFramePointerOffsetInPushCalleeSavedRegisters ==
STATIC_ASSERT(
EntryFrameConstants::kCalleeSavedRegisterBytesPushedBeforeFpLrPair ==
8 * kSystemPointerSize);
stp(x29, x30, tos); // fp, lr
STATIC_ASSERT(
EntryFrameConstants::kCalleeSavedRegisterBytesPushedAfterFpLrPair ==
10 * kSystemPointerSize);
#endif // defined(V8_OS_WIN)
stp(x27, x28, tos);
stp(x25, x26, tos);
stp(x23, x24, tos);
......
......@@ -83,12 +83,6 @@ inline MemOperand FieldMemOperand(Register object, int offset);
// ----------------------------------------------------------------------------
// MacroAssembler
#if defined(V8_OS_WIN)
// This offset is originated from PushCalleeSavedRegisters.
static constexpr int kFramePointerOffsetInPushCalleeSavedRegisters =
10 * kSystemPointerSize;
#endif // V8_OS_WIN
enum BranchType {
// Copies of architectural conditions.
// The associated conditions can be used in place of those, the code will
......
......@@ -19,8 +19,14 @@ bool PCIsInCodeRange(const v8::MemoryRange& code_range, void* pc) {
return pc_as_byte >= start && pc_as_byte < end;
}
bool IsInUnsafeJSEntryRange(const v8::JSEntryStub& js_entry_stub, void* pc) {
return PCIsInCodeRange(js_entry_stub.code, pc);
bool IsInJSEntryRange(const UnwindState& unwind_state, void* pc) {
return PCIsInCodeRange(unwind_state.js_entry_stub.code, pc) ||
PCIsInCodeRange(unwind_state.js_construct_entry_stub.code, pc) ||
PCIsInCodeRange(unwind_state.js_run_microtasks_entry_stub.code, pc);
}
bool IsInUnsafeJSEntryRange(const UnwindState& unwind_state, void* pc) {
return IsInJSEntryRange(unwind_state, pc);
// TODO(petermarshall): We can be more precise by checking whether we are
// in JSEntry but after frame setup and before frame teardown, in which case
......@@ -32,21 +38,40 @@ i::Address Load(i::Address address) {
return *reinterpret_cast<i::Address*>(address);
}
void* GetReturnAddressFromFP(void* fp) {
void* GetReturnAddressFromFP(void* fp, void* pc,
const v8::UnwindState& unwind_state) {
int caller_pc_offset = i::CommonFrameConstants::kCallerPCOffset;
#ifdef V8_TARGET_ARCH_ARM64
if (IsInJSEntryRange(unwind_state, pc)) {
caller_pc_offset = i::EntryFrameConstants::kDirectCallerPCOffset;
}
#endif
return reinterpret_cast<void*>(
Load(reinterpret_cast<i::Address>(fp) +
i::CommonFrameConstants::kCallerPCOffset));
Load(reinterpret_cast<i::Address>(fp) + caller_pc_offset));
}
void* GetCallerFPFromFP(void* fp) {
void* GetCallerFPFromFP(void* fp, void* pc,
const v8::UnwindState& unwind_state) {
int caller_fp_offset = i::CommonFrameConstants::kCallerFPOffset;
#ifdef V8_TARGET_ARCH_ARM64
if (IsInJSEntryRange(unwind_state, pc)) {
caller_fp_offset = i::EntryFrameConstants::kDirectCallerFPOffset;
}
#endif
return reinterpret_cast<void*>(
Load(reinterpret_cast<i::Address>(fp) +
i::CommonFrameConstants::kCallerFPOffset));
Load(reinterpret_cast<i::Address>(fp) + caller_fp_offset));
}
void* GetCallerSPFromFP(void* fp) {
void* GetCallerSPFromFP(void* fp, void* pc,
const v8::UnwindState& unwind_state) {
int caller_sp_offset = i::CommonFrameConstants::kCallerSPOffset;
#ifdef V8_TARGET_ARCH_ARM64
if (IsInJSEntryRange(unwind_state, pc)) {
caller_sp_offset = i::EntryFrameConstants::kDirectCallerSPOffset;
}
#endif
return reinterpret_cast<void*>(reinterpret_cast<i::Address>(fp) +
i::CommonFrameConstants::kCallerSPOffset);
caller_sp_offset);
}
bool AddressIsInStack(const void* address, const void* stack_base,
......@@ -62,21 +87,21 @@ bool Unwinder::TryUnwindV8Frames(const UnwindState& unwind_state,
const void* stack_top = register_state->sp;
void* pc = register_state->pc;
if (PCIsInV8(unwind_state, pc) &&
!IsInUnsafeJSEntryRange(unwind_state.js_entry_stub, pc)) {
if (PCIsInV8(unwind_state, pc) && !IsInUnsafeJSEntryRange(unwind_state, pc)) {
void* current_fp = register_state->fp;
if (!AddressIsInStack(current_fp, stack_base, stack_top)) return false;
// Peek at the return address that the caller pushed. If it's in V8, then we
// assume the caller frame is a JS frame and continue to unwind.
void* next_pc = GetReturnAddressFromFP(current_fp);
void* next_pc = GetReturnAddressFromFP(current_fp, pc, unwind_state);
while (PCIsInV8(unwind_state, next_pc)) {
current_fp = GetCallerFPFromFP(current_fp);
current_fp = GetCallerFPFromFP(current_fp, pc, unwind_state);
if (!AddressIsInStack(current_fp, stack_base, stack_top)) return false;
next_pc = GetReturnAddressFromFP(current_fp);
pc = next_pc;
next_pc = GetReturnAddressFromFP(current_fp, pc, unwind_state);
}
void* final_sp = GetCallerSPFromFP(current_fp);
void* final_sp = GetCallerSPFromFP(current_fp, pc, unwind_state);
if (!AddressIsInStack(final_sp, stack_base, stack_top)) return false;
register_state->sp = final_sp;
......@@ -84,7 +109,7 @@ bool Unwinder::TryUnwindV8Frames(const UnwindState& unwind_state,
// this is just the rbp value that JSEntryStub pushed. On platforms like
// Win64 this is not used as a dedicated FP register, and could contain
// anything.
void* final_fp = GetCallerFPFromFP(current_fp);
void* final_fp = GetCallerFPFromFP(current_fp, pc, unwind_state);
register_state->fp = final_fp;
register_state->pc = next_pc;
......
......@@ -16,6 +16,18 @@ namespace internal {
//
// slot Entry frame
// +---------------------+-----------------------
// -20 | saved register d15 |
// ... | ... |
// -13 | saved register d8 |
// |- - - - - - - - - - -|
// -12 | saved lr (x30) |
// |- - - - - - - - - - -|
// -11 | saved fp (x29) |
// |- - - - - - - - - - -|
// -10 | saved register x28 |
// ... | ... |
// -1 | saved register x19 |
// |- - - - - - - - - - -|
// 0 | bad frame pointer | <-- frame ptr
// | (0xFFF.. FF) |
// |- - - - - - - - - - -|
......@@ -39,6 +51,26 @@ class EntryFrameConstants : public AllStatic {
// Isolate::c_entry_fp onto the stack.
static constexpr int kCallerFPOffset = -3 * kSystemPointerSize;
static constexpr int kFixedFrameSize = 6 * kSystemPointerSize;
// The following constants are defined so we can static-assert their values
// near the relevant JSEntry assembly code, not because they're actually very
// useful.
static constexpr int kCalleeSavedRegisterBytesPushedBeforeFpLrPair =
8 * kSystemPointerSize;
static constexpr int kCalleeSavedRegisterBytesPushedAfterFpLrPair =
10 * kSystemPointerSize;
static constexpr int kOffsetToCalleeSavedRegisters = 1 * kSystemPointerSize;
// These offsets refer to the immediate caller (a native frame), not to the
// previous JS exit frame like kCallerFPOffset above.
static constexpr int kDirectCallerFPOffset =
kCalleeSavedRegisterBytesPushedAfterFpLrPair +
kOffsetToCalleeSavedRegisters;
static constexpr int kDirectCallerPCOffset =
kDirectCallerFPOffset + 1 * kSystemPointerSize;
static constexpr int kDirectCallerSPOffset =
kDirectCallerPCOffset + 1 * kSystemPointerSize +
kCalleeSavedRegisterBytesPushedBeforeFpLrPair;
};
class ExitFrameConstants : public TypedFrameConstants {
......
......@@ -439,17 +439,11 @@
}], # variant == no_wasm_traps
##############################################################################
# The stack unwinder API is only supported on x64.
['arch != x64', {
# The stack unwinder API is only supported on x64 and arm64.
['arch != x64 and arch != arm64', {
'test-unwinder/*': [SKIP]
}],
##############################################################################
# Windows stack unwinding is only supported on x64.
['arch != x64 or system != windows', {
'test-stack-unwinding-x64/*': [SKIP]
}],
##############################################################################
['lite_mode or variant == jitless', {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment