diff --git a/BUILD.gn b/BUILD.gn index 4284379b60..59b0c99529 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -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", diff --git a/include/v8-unwinder-state.h b/include/v8-unwinder-state.h new file mode 100644 index 0000000000..ed9988711b --- /dev/null +++ b/include/v8-unwinder-state.h @@ -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_ diff --git a/include/v8.h b/include/v8.h index 0c327bc53c..a9e1cad12b 100644 --- a/include/v8.h +++ b/include/v8.h @@ -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 callee_saved; }; // The output structure filled up by GetStackSample API function. diff --git a/src/api/api.cc b/src/api/api.cc index a9c9bffc8a..073f015a1b 100644 --- a/src/api/api.cc +++ b/src/api/api.cc @@ -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(*(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(*(other.callee_saved)); + } + } + return *this; +} + namespace internal { const size_t HandleScopeImplementer::kEnteredContextsOffset = diff --git a/src/diagnostics/arm/unwinder-arm.cc b/src/diagnostics/arm/unwinder-arm.cc new file mode 100644 index 0000000000..171a258a0c --- /dev/null +++ b/src/diagnostics/arm/unwinder-arm.cc @@ -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(fp) + + i::EntryFrameConstants::kDirectCallerRRegistersOffset; + + if (!register_state->callee_saved) { + register_state->callee_saved = std::make_unique(); + } + + register_state->callee_saved->arm_r4 = + reinterpret_cast(Load(base_addr + 0 * i::kSystemPointerSize)); + register_state->callee_saved->arm_r5 = + reinterpret_cast(Load(base_addr + 1 * i::kSystemPointerSize)); + register_state->callee_saved->arm_r6 = + reinterpret_cast(Load(base_addr + 2 * i::kSystemPointerSize)); + register_state->callee_saved->arm_r7 = + reinterpret_cast(Load(base_addr + 3 * i::kSystemPointerSize)); + register_state->callee_saved->arm_r8 = + reinterpret_cast(Load(base_addr + 4 * i::kSystemPointerSize)); + register_state->callee_saved->arm_r9 = + reinterpret_cast(Load(base_addr + 5 * i::kSystemPointerSize)); + register_state->callee_saved->arm_r10 = + reinterpret_cast(Load(base_addr + 6 * i::kSystemPointerSize)); +} + +} // namespace v8 diff --git a/src/diagnostics/arm64/unwinder-arm64.cc b/src/diagnostics/arm64/unwinder-arm64.cc new file mode 100644 index 0000000000..5a92512a17 --- /dev/null +++ b/src/diagnostics/arm64/unwinder-arm64.cc @@ -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 diff --git a/src/diagnostics/ia32/unwinder-ia32.cc b/src/diagnostics/ia32/unwinder-ia32.cc new file mode 100644 index 0000000000..5a92512a17 --- /dev/null +++ b/src/diagnostics/ia32/unwinder-ia32.cc @@ -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 diff --git a/src/diagnostics/unwinder.cc b/src/diagnostics/unwinder.cc index c4a559c9d9..1dd122a118 100644 --- a/src/diagnostics/unwinder.cc +++ b/src/diagnostics/unwinder.cc @@ -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 -#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-.cc. +void GetCalleeSavedRegistersFromEntryFrame(void* fp, + RegisterState* register_state); + +i::Address Load(i::Address address) { + return *reinterpret_cast(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(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; diff --git a/src/diagnostics/unwinder.h b/src/diagnostics/unwinder.h new file mode 100644 index 0000000000..4cad2897fd --- /dev/null +++ b/src/diagnostics/unwinder.h @@ -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_ diff --git a/src/diagnostics/x64/unwinder-x64.cc b/src/diagnostics/x64/unwinder-x64.cc new file mode 100644 index 0000000000..5a92512a17 --- /dev/null +++ b/src/diagnostics/x64/unwinder-x64.cc @@ -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 diff --git a/src/execution/arm/frame-constants-arm.h b/src/execution/arm/frame-constants-arm.h index e8bee055d2..47e901ea99 100644 --- a/src/execution/arm/frame-constants-arm.h +++ b/src/execution/arm/frame-constants-arm.h @@ -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; diff --git a/test/cctest/test-unwinder-code-pages.cc b/test/cctest/test-unwinder-code-pages.cc index 3483ed65fe..18c8658b3e 100644 --- a/test/cctest/test-unwinder-code-pages.cc +++ b/test/cctest/test-unwinder-code-pages.cc @@ -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(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(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(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(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(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, ®ister_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, ®ister_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, ®ister_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, ®ister_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, ®ister_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, ®ister_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