[unwinder] Restore callee saved registers after unwinding in arm32

Bug: v8:10799
Change-Id: Id912520b6a27e439e204bac47c0723a8f613be4b
Fixed: v8:10799
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2472000
Commit-Queue: Santiago Aboy Solanes <solanes@chromium.org>
Reviewed-by: Ulan Degenbaev <ulan@chromium.org>
Reviewed-by: Ross McIlroy <rmcilroy@chromium.org>
Cr-Commit-Position: refs/heads/master@{#70656}
This commit is contained in:
Santiago Aboy Solanes 2020-10-20 10:40:34 +01:00 committed by Commit Bot
parent fbfa9bf4ec
commit d6c586f756
12 changed files with 241 additions and 37 deletions

View File

@ -2290,6 +2290,7 @@ v8_source_set("v8_base_without_compiler") {
"include/v8-metrics.h",
"include/v8-platform.h",
"include/v8-profiler.h",
"include/v8-unwinder-state.h",
"include/v8-util.h",
"include/v8-wasm-trap-handler-posix.h",
"include/v8.h",
@ -2496,6 +2497,7 @@ v8_source_set("v8_base_without_compiler") {
"src/diagnostics/perf-jit.cc",
"src/diagnostics/perf-jit.h",
"src/diagnostics/unwinder.cc",
"src/diagnostics/unwinder.h",
"src/execution/arguments-inl.h",
"src/execution/arguments.cc",
"src/execution/arguments.h",
@ -3473,6 +3475,7 @@ v8_source_set("v8_base_without_compiler") {
"src/debug/ia32/debug-ia32.cc",
"src/deoptimizer/ia32/deoptimizer-ia32.cc",
"src/diagnostics/ia32/disasm-ia32.cc",
"src/diagnostics/ia32/unwinder-ia32.cc",
"src/execution/ia32/frame-constants-ia32.cc",
"src/execution/ia32/frame-constants-ia32.h",
"src/regexp/ia32/regexp-macro-assembler-ia32.cc",
@ -3502,6 +3505,7 @@ v8_source_set("v8_base_without_compiler") {
"src/deoptimizer/x64/deoptimizer-x64.cc",
"src/diagnostics/x64/disasm-x64.cc",
"src/diagnostics/x64/eh-frame-x64.cc",
"src/diagnostics/x64/unwinder-x64.cc",
"src/execution/x64/frame-constants-x64.cc",
"src/execution/x64/frame-constants-x64.h",
"src/regexp/x64/regexp-macro-assembler-x64.cc",
@ -3550,6 +3554,7 @@ v8_source_set("v8_base_without_compiler") {
"src/deoptimizer/arm/deoptimizer-arm.cc",
"src/diagnostics/arm/disasm-arm.cc",
"src/diagnostics/arm/eh-frame-arm.cc",
"src/diagnostics/arm/unwinder-arm.cc",
"src/execution/arm/frame-constants-arm.cc",
"src/execution/arm/frame-constants-arm.h",
"src/execution/arm/simulator-arm.cc",
@ -3590,6 +3595,7 @@ v8_source_set("v8_base_without_compiler") {
"src/diagnostics/arm64/disasm-arm64.cc",
"src/diagnostics/arm64/disasm-arm64.h",
"src/diagnostics/arm64/eh-frame-arm64.cc",
"src/diagnostics/arm64/unwinder-arm64.cc",
"src/execution/arm64/frame-constants-arm64.cc",
"src/execution/arm64/frame-constants-arm64.h",
"src/execution/arm64/pointer-auth-arm64.cc",

View File

@ -0,0 +1,30 @@
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef INCLUDE_V8_UNWINDER_STATE_H_
#define INCLUDE_V8_UNWINDER_STATE_H_
namespace v8 {
#ifdef V8_TARGET_ARCH_ARM
struct CalleeSavedRegisters {
void* arm_r4;
void* arm_r5;
void* arm_r6;
void* arm_r7;
void* arm_r8;
void* arm_r9;
void* arm_r10;
};
#elif V8_TARGET_ARCH_X64 || V8_TARGET_ARCH_IA32 || V8_TARGET_ARCH_ARM64 || \
V8_TARGET_ARCH_MIPS || V8_TARGET_ARCH_MIPS64 || V8_TARGET_ARCH_PPC || \
V8_TARGET_ARCH_PPC64 || V8_TARGET_ARCH_S390
struct CalleeSavedRegisters {};
#else
#error Target architecture was not detected as supported by v8
#endif
} // namespace v8
#endif // INCLUDE_V8_UNWINDER _STATE_H_

View File

@ -2272,14 +2272,25 @@ enum StateTag {
IDLE
};
// Holds the callee saved registers needed for the stack unwinder. It is the
// empty struct if no registers are required. Implemented in
// include/v8-unwinder-state.h.
struct CalleeSavedRegisters;
// A RegisterState represents the current state of registers used
// by the sampling profiler API.
struct RegisterState {
RegisterState() : pc(nullptr), sp(nullptr), fp(nullptr), lr(nullptr) {}
struct V8_EXPORT RegisterState {
RegisterState();
~RegisterState();
RegisterState(const RegisterState& other);
RegisterState& operator=(const RegisterState& other);
void* pc; // Instruction pointer.
void* sp; // Stack pointer.
void* fp; // Frame pointer.
void* lr; // Link register (or nullptr on platforms without a link register).
// Callee saved registers (or null if no callee saved registers were stored)
std::unique_ptr<CalleeSavedRegisters> callee_saved;
};
// The output structure filled up by GetStackSample API function.

View File

@ -14,6 +14,7 @@
#include "include/v8-cppgc.h"
#include "include/v8-fast-api-calls.h"
#include "include/v8-profiler.h"
#include "include/v8-unwinder-state.h"
#include "include/v8-util.h"
#include "src/api/api-inl.h"
#include "src/api/api-natives.h"
@ -11148,6 +11149,36 @@ CFunction::CFunction(const void* address, const CFunctionInfo* type_info)
}
}
RegisterState::RegisterState()
: pc(nullptr), sp(nullptr), fp(nullptr), lr(nullptr) {}
RegisterState::~RegisterState() = default;
RegisterState::RegisterState(const RegisterState& other) V8_NOEXCEPT {
pc = other.pc;
sp = other.sp;
fp = other.fp;
lr = other.lr;
if (other.callee_saved) {
callee_saved =
std::make_unique<CalleeSavedRegisters>(*(other.callee_saved));
}
}
RegisterState& RegisterState::operator=(const RegisterState& other)
V8_NOEXCEPT {
if (&other != this) {
pc = other.pc;
sp = other.sp;
fp = other.fp;
lr = other.lr;
if (other.callee_saved) {
callee_saved =
std::make_unique<CalleeSavedRegisters>(*(other.callee_saved));
}
}
return *this;
}
namespace internal {
const size_t HandleScopeImplementer::kEnteredContextsOffset =

View File

@ -0,0 +1,37 @@
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "include/v8-unwinder-state.h"
#include "src/diagnostics/unwinder.h"
#include "src/execution/frame-constants.h"
namespace v8 {
void GetCalleeSavedRegistersFromEntryFrame(void* fp,
RegisterState* register_state) {
const i::Address base_addr =
reinterpret_cast<i::Address>(fp) +
i::EntryFrameConstants::kDirectCallerRRegistersOffset;
if (!register_state->callee_saved) {
register_state->callee_saved = std::make_unique<CalleeSavedRegisters>();
}
register_state->callee_saved->arm_r4 =
reinterpret_cast<void*>(Load(base_addr + 0 * i::kSystemPointerSize));
register_state->callee_saved->arm_r5 =
reinterpret_cast<void*>(Load(base_addr + 1 * i::kSystemPointerSize));
register_state->callee_saved->arm_r6 =
reinterpret_cast<void*>(Load(base_addr + 2 * i::kSystemPointerSize));
register_state->callee_saved->arm_r7 =
reinterpret_cast<void*>(Load(base_addr + 3 * i::kSystemPointerSize));
register_state->callee_saved->arm_r8 =
reinterpret_cast<void*>(Load(base_addr + 4 * i::kSystemPointerSize));
register_state->callee_saved->arm_r9 =
reinterpret_cast<void*>(Load(base_addr + 5 * i::kSystemPointerSize));
register_state->callee_saved->arm_r10 =
reinterpret_cast<void*>(Load(base_addr + 6 * i::kSystemPointerSize));
}
} // namespace v8

View File

@ -0,0 +1,12 @@
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/diagnostics/unwinder.h"
namespace v8 {
void GetCalleeSavedRegistersFromEntryFrame(void* fp,
RegisterState* register_state) {}
} // namespace v8

View File

@ -0,0 +1,12 @@
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/diagnostics/unwinder.h"
namespace v8 {
void GetCalleeSavedRegistersFromEntryFrame(void* fp,
RegisterState* register_state) {}
} // namespace v8

View File

@ -2,15 +2,22 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/diagnostics/unwinder.h"
#include <algorithm>
#include "include/v8.h"
#include "src/common/globals.h"
#include "src/execution/frame-constants.h"
#include "src/execution/pointer-authentication.h"
namespace v8 {
// Architecture specific. Implemented in unwinder-<arch>.cc.
void GetCalleeSavedRegistersFromEntryFrame(void* fp,
RegisterState* register_state);
i::Address Load(i::Address address) {
return *reinterpret_cast<i::Address*>(address);
}
namespace {
const i::byte* CalculateEnd(const void* start, size_t length_in_bytes) {
@ -61,13 +68,15 @@ bool IsInUnsafeJSEntryRange(const JSEntryStubs& entry_stubs, void* pc) {
// within JSEntry.
}
i::Address Load(i::Address address) {
return *reinterpret_cast<i::Address*>(address);
bool AddressIsInStack(const void* address, const void* stack_base,
const void* stack_top) {
return address <= stack_base && address >= stack_top;
}
void* GetReturnAddressFromFP(void* fp, void* pc,
const JSEntryStubs& entry_stubs) {
int caller_pc_offset = i::CommonFrameConstants::kCallerPCOffset;
// TODO(solanes): Implement the JSEntry range case also for x64 here and below.
#if V8_TARGET_ARCH_ARM64 || V8_TARGET_ARCH_ARM
if (IsInJSEntryRange(entry_stubs, pc)) {
caller_pc_offset = i::EntryFrameConstants::kDirectCallerPCOffset;
@ -100,11 +109,6 @@ void* GetCallerSPFromFP(void* fp, void* pc, const JSEntryStubs& entry_stubs) {
caller_sp_offset);
}
bool AddressIsInStack(const void* address, const void* stack_base,
const void* stack_top) {
return address <= stack_base && address >= stack_top;
}
} // namespace
bool Unwinder::TryUnwindV8Frames(const JSEntryStubs& entry_stubs,
@ -145,6 +149,10 @@ bool Unwinder::TryUnwindV8Frames(const JSEntryStubs& entry_stubs,
// Link register no longer valid after unwinding.
register_state->lr = nullptr;
if (IsInJSEntryRange(entry_stubs, pc)) {
GetCalleeSavedRegistersFromEntryFrame(current_fp, register_state);
}
return true;
}
return false;

View File

@ -0,0 +1,17 @@
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef V8_DIAGNOSTICS_UNWINDER_H_
#define V8_DIAGNOSTICS_UNWINDER_H_
#include "include/v8.h"
#include "src/common/globals.h"
namespace v8 {
i::Address Load(i::Address address);
} // namespace v8
#endif // V8_DIAGNOSTICS_UNWINDER_H_

View File

@ -0,0 +1,12 @@
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/diagnostics/unwinder.h"
namespace v8 {
void GetCalleeSavedRegistersFromEntryFrame(void* fp,
RegisterState* register_state) {}
} // namespace v8

View File

@ -43,12 +43,14 @@ class EntryFrameConstants : public AllStatic {
static constexpr int kArgvOffset = +1 * kSystemPointerSize;
// These offsets refer to the immediate caller (i.e a native frame).
static constexpr int kDirectCallerFPOffset =
static constexpr int kDirectCallerRRegistersOffset =
/* bad frame pointer (-1) */
kPointerSize +
/* d8...d15 */
kNumDoubleCalleeSaved * kDoubleSize +
/* r4...r10 (i.e callee saved without fp) */
kNumDoubleCalleeSaved * kDoubleSize;
static constexpr int kDirectCallerFPOffset =
kDirectCallerRRegistersOffset +
/* r4...r10 (i.e. callee saved without fp) */
(kNumCalleeSaved - 1) * kPointerSize;
static constexpr int kDirectCallerPCOffset =
kDirectCallerFPOffset + 1 * kSystemPointerSize;

View File

@ -2,8 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "include/v8-unwinder-state.h"
#include "include/v8.h"
#include "src/api/api-inl.h"
#include "src/builtins/builtins.h"
#include "src/execution/isolate.h"
@ -17,6 +17,9 @@ namespace test_unwinder_code_pages {
namespace {
#define CHECK_EQ_VALUE_REGISTER(uiuntptr_value, register_value) \
CHECK_EQ(reinterpret_cast<void*>(uiuntptr_value), register_value)
#ifdef V8_TARGET_ARCH_X64
// How much the JSEntry frame occupies in the stack.
constexpr int kJSEntryFrameSpace = 3;
@ -33,6 +36,10 @@ void BuildJSEntryStack(uintptr_t* stack) {
stack[1] = 100; // Return address into C++ code.
stack[2] = reinterpret_cast<uintptr_t>(stack + 2); // saved SP.
}
// Dummy method since we don't save callee saved registers in x64.
void CheckCalleeSavedRegisters(const RegisterState& register_state) {}
#elif V8_TARGET_ARCH_ARM
// How much the JSEntry frame occupies in the stack.
constexpr int kJSEntryFrameSpace = 27;
@ -59,6 +66,19 @@ void BuildJSEntryStack(uintptr_t* stack) {
stack[25] = 100; // Return address into C++ code (i.e lr/pc)
stack[26] = reinterpret_cast<uintptr_t>(stack + 26); // saved SP.
}
// Checks that the values in the calee saved registers are the same as the ones
// we saved in BuildJSEntryStack.
void CheckCalleeSavedRegisters(const RegisterState& register_state) {
CHECK_EQ_VALUE_REGISTER(160, register_state.callee_saved->arm_r4);
CHECK_EQ_VALUE_REGISTER(161, register_state.callee_saved->arm_r5);
CHECK_EQ_VALUE_REGISTER(162, register_state.callee_saved->arm_r6);
CHECK_EQ_VALUE_REGISTER(163, register_state.callee_saved->arm_r7);
CHECK_EQ_VALUE_REGISTER(164, register_state.callee_saved->arm_r8);
CHECK_EQ_VALUE_REGISTER(165, register_state.callee_saved->arm_r9);
CHECK_EQ_VALUE_REGISTER(166, register_state.callee_saved->arm_r10);
}
#elif V8_TARGET_ARCH_ARM64
// How much the JSEntry frame occupies in the stack.
constexpr int kJSEntryFrameSpace = 22;
@ -83,6 +103,10 @@ void BuildJSEntryStack(uintptr_t* stack) {
}
stack[21] = reinterpret_cast<uintptr_t>(stack + 21); // saved SP.
}
// Dummy method since we don't save callee saved registers in arm64.
void CheckCalleeSavedRegisters(const RegisterState& register_state) {}
#else
// Dummy constants for the rest of the archs which are not supported.
constexpr int kJSEntryFrameSpace = 1;
@ -90,17 +114,17 @@ constexpr int kFPOffset = 0;
constexpr int kPCOffset = 0;
constexpr int kSPOffset = 0;
// Dummy function to be able to compile.
// Dummy methods to be able to compile.
void BuildJSEntryStack(uintptr_t* stack) { UNREACHABLE(); }
void CheckCalleeSavedRegisters(const RegisterState& register_state) {
UNREACHABLE();
}
#endif // V8_TARGET_ARCH_X64
} // namespace
static const void* fake_stack_base = nullptr;
#define CHECK_EQ_STACK_REGISTER(stack_value, register_value) \
CHECK_EQ(reinterpret_cast<void*>(stack_value), register_value)
TEST(Unwind_BadState_Fail_CodePagesAPI) {
JSEntryStubs entry_stubs; // Fields are intialized to nullptr.
RegisterState register_state;
@ -153,9 +177,9 @@ TEST(Unwind_BuiltinPCInMiddle_Success_CodePagesAPI) {
bool unwound = v8::Unwinder::TryUnwindV8Frames(
entry_stubs, pages_length, code_pages, &register_state, stack_base);
CHECK(unwound);
CHECK_EQ_STACK_REGISTER(stack[topmost_fp_index], register_state.fp);
CHECK_EQ_STACK_REGISTER(stack[topmost_fp_index + 1], register_state.pc);
CHECK_EQ_STACK_REGISTER(stack[topmost_fp_index + 2], register_state.sp);
CHECK_EQ_VALUE_REGISTER(stack[topmost_fp_index], register_state.fp);
CHECK_EQ_VALUE_REGISTER(stack[topmost_fp_index + 1], register_state.pc);
CHECK_EQ_VALUE_REGISTER(stack[topmost_fp_index + 2], register_state.sp);
}
// The unwinder should be able to unwind even if we haven't properly set up the
@ -208,9 +232,9 @@ TEST(Unwind_BuiltinPCAtStart_Success_CodePagesAPI) {
entry_stubs, pages_length, code_pages, &register_state, stack_base);
CHECK(unwound);
CHECK_EQ_STACK_REGISTER(stack[topmost_fp_index], register_state.fp);
CHECK_EQ_STACK_REGISTER(stack[topmost_fp_index + 1], register_state.pc);
CHECK_EQ_STACK_REGISTER(stack[topmost_fp_index + 2], register_state.sp);
CHECK_EQ_VALUE_REGISTER(stack[topmost_fp_index], register_state.fp);
CHECK_EQ_VALUE_REGISTER(stack[topmost_fp_index + 1], register_state.pc);
CHECK_EQ_VALUE_REGISTER(stack[topmost_fp_index + 2], register_state.sp);
}
const char* foo_source = R"(
@ -296,9 +320,9 @@ TEST(Unwind_CodeObjectPCInMiddle_Success_CodePagesAPI) {
bool unwound = v8::Unwinder::TryUnwindV8Frames(
entry_stubs, pages_length, code_pages, &register_state, stack_base);
CHECK(unwound);
CHECK_EQ_STACK_REGISTER(stack[topmost_fp_index], register_state.fp);
CHECK_EQ_STACK_REGISTER(stack[topmost_fp_index + 1], register_state.pc);
CHECK_EQ_STACK_REGISTER(stack[topmost_fp_index + 2], register_state.sp);
CHECK_EQ_VALUE_REGISTER(stack[topmost_fp_index], register_state.fp);
CHECK_EQ_VALUE_REGISTER(stack[topmost_fp_index + 1], register_state.pc);
CHECK_EQ_VALUE_REGISTER(stack[topmost_fp_index + 2], register_state.sp);
}
// If the PC is within JSEntry but we haven't set up the frame yet, then we
@ -345,8 +369,8 @@ TEST(Unwind_JSEntryBeforeFrame_Fail_CodePagesAPI) {
entry_stubs, pages_length, code_pages, &register_state, stack_base);
CHECK(!unwound);
// The register state should not change when unwinding fails.
CHECK_EQ_STACK_REGISTER(&stack[9], register_state.fp);
CHECK_EQ_STACK_REGISTER(&stack[5], register_state.sp);
CHECK_EQ_VALUE_REGISTER(&stack[9], register_state.fp);
CHECK_EQ_VALUE_REGISTER(&stack[5], register_state.sp);
CHECK_EQ(jsentry_pc_value, register_state.pc);
// Change the PC to a few instructions later, after the frame is set up.
@ -358,8 +382,8 @@ TEST(Unwind_JSEntryBeforeFrame_Fail_CodePagesAPI) {
// than just assuming the frame is unreadable.
CHECK(!unwound);
// The register state should not change when unwinding fails.
CHECK_EQ_STACK_REGISTER(&stack[9], register_state.fp);
CHECK_EQ_STACK_REGISTER(&stack[5], register_state.sp);
CHECK_EQ_VALUE_REGISTER(&stack[9], register_state.fp);
CHECK_EQ_VALUE_REGISTER(&stack[5], register_state.sp);
CHECK_EQ(jsentry_pc_value, register_state.pc);
}
@ -409,12 +433,13 @@ TEST(Unwind_TwoJSFrames_Success_CodePagesAPI) {
entry_stubs, pages_length, code_pages, &register_state, stack_base);
CHECK(unwound);
CHECK_EQ_STACK_REGISTER(stack[top_of_js_entry + kFPOffset],
CHECK_EQ_VALUE_REGISTER(stack[top_of_js_entry + kFPOffset],
register_state.fp);
CHECK_EQ_STACK_REGISTER(stack[top_of_js_entry + kPCOffset],
CHECK_EQ_VALUE_REGISTER(stack[top_of_js_entry + kPCOffset],
register_state.pc);
CHECK_EQ_STACK_REGISTER(stack[top_of_js_entry + kSPOffset],
CHECK_EQ_VALUE_REGISTER(stack[top_of_js_entry + kSPOffset],
register_state.sp);
CheckCalleeSavedRegisters(register_state);
}
// If the PC is in JSEntry then the frame might not be set up correctly, meaning
@ -543,6 +568,7 @@ TEST(Unwind_StackBounds_WithUnwinding_CodePagesAPI) {
unwound = v8::Unwinder::TryUnwindV8Frames(
entry_stubs, pages_length, code_pages, &register_state, stack_base);
CHECK(unwound);
CheckCalleeSavedRegisters(register_state);
}
TEST(PCIsInV8_BadState_Fail_CodePagesAPI) {
@ -737,7 +763,7 @@ TEST(Unwind_TwoNestedFunctions_CodePagesAPI) {
}
#endif
#undef CHECK_EQ_STACK_REGISTER
#undef CHECK_EQ_VALUE_REGISTER
} // namespace test_unwinder_code_pages
} // namespace internal
} // namespace v8