[unwinder] Delete the old unwinder API
The new one was created in https://chromium-review.googlesource.com/c/v8/v8/+/1969900, and the old API was deprecated in https://chromium-review.googlesource.com/c/v8/v8/+/2110015, so now we can remove it. Bug: v8:8116 Change-Id: Ia839279609b412c36f1f17368acef23fe07e7c61 Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2369174 Reviewed-by: Peter Marshall <petermarshall@chromium.org> Reviewed-by: Ross McIlroy <rmcilroy@chromium.org> Commit-Queue: Santiago Aboy Solanes <solanes@chromium.org> Cr-Commit-Position: refs/heads/master@{#69536}
This commit is contained in:
parent
58f047aba9
commit
9c5eed71c7
54
include/v8.h
54
include/v8.h
@ -2260,14 +2260,6 @@ struct JSEntryStub {
|
||||
MemoryRange code;
|
||||
};
|
||||
|
||||
struct UnwindState {
|
||||
MemoryRange code_range;
|
||||
MemoryRange embedded_code_range;
|
||||
JSEntryStub js_entry_stub;
|
||||
JSEntryStub js_construct_entry_stub;
|
||||
JSEntryStub js_run_microtasks_entry_stub;
|
||||
};
|
||||
|
||||
struct JSEntryStubs {
|
||||
JSEntryStub js_entry_stub;
|
||||
JSEntryStub js_construct_entry_stub;
|
||||
@ -9325,13 +9317,6 @@ class V8_EXPORT Isolate {
|
||||
*/
|
||||
void GetCodeRange(void** start, size_t* length_in_bytes);
|
||||
|
||||
/**
|
||||
* Returns the UnwindState necessary for use with the Unwinder API.
|
||||
*/
|
||||
// TODO(petermarshall): Remove this API.
|
||||
V8_DEPRECATED("Use entry_stubs + code_pages version.")
|
||||
UnwindState GetUnwindState();
|
||||
|
||||
/**
|
||||
* Returns the JSEntryStubs necessary for use with the Unwinder API.
|
||||
*/
|
||||
@ -10689,12 +10674,14 @@ class V8_EXPORT Unwinder {
|
||||
*
|
||||
* The unwinder also needs the virtual memory range of all possible V8 code
|
||||
* objects. There are two ranges required - the heap code range and the range
|
||||
* for code embedded in the binary. The V8 API provides all required inputs
|
||||
* via an UnwindState object through the Isolate::GetUnwindState() API. These
|
||||
* values will not change after Isolate initialization, so the same
|
||||
* |unwind_state| can be used for multiple calls.
|
||||
* for code embedded in the binary.
|
||||
*
|
||||
* \param unwind_state Input state for the Isolate that the stack comes from.
|
||||
* Available on x64, ARM64 and ARM32.
|
||||
*
|
||||
* \param code_pages A list of all of the ranges in which V8 has allocated
|
||||
* executable code. The caller should obtain this list by calling
|
||||
* Isolate::CopyCodePages() during the same interrupt/thread suspension that
|
||||
* captures the stack.
|
||||
* \param register_state The current registers. This is an in-out param that
|
||||
* will be overwritten with the register values after unwinding, on success.
|
||||
* \param stack_base The resulting stack pointer and frame pointer values are
|
||||
@ -10705,20 +10692,6 @@ class V8_EXPORT Unwinder {
|
||||
*
|
||||
* \return True on success.
|
||||
*/
|
||||
// TODO(petermarshall): Remove this API
|
||||
V8_DEPRECATED("Use entry_stubs + code_pages version.")
|
||||
static bool TryUnwindV8Frames(const UnwindState& unwind_state,
|
||||
RegisterState* register_state,
|
||||
const void* stack_base);
|
||||
|
||||
/**
|
||||
* The same as above, but is available on x64, ARM64 and ARM32.
|
||||
*
|
||||
* \param code_pages A list of all of the ranges in which V8 has allocated
|
||||
* executable code. The caller should obtain this list by calling
|
||||
* Isolate::CopyCodePages() during the same interrupt/thread suspension that
|
||||
* captures the stack.
|
||||
*/
|
||||
static bool TryUnwindV8Frames(const JSEntryStubs& entry_stubs,
|
||||
size_t code_pages_length,
|
||||
const MemoryRange* code_pages,
|
||||
@ -10726,20 +10699,13 @@ class V8_EXPORT Unwinder {
|
||||
const void* stack_base);
|
||||
|
||||
/**
|
||||
* Whether the PC is within the V8 code range represented by code_range or
|
||||
* embedded_code_range in |unwind_state|.
|
||||
* Whether the PC is within the V8 code range represented by code_pages.
|
||||
*
|
||||
* If this returns false, then calling UnwindV8Frames() with the same PC
|
||||
* and unwind_state will always fail. If it returns true, then unwinding may
|
||||
* (but not necessarily) be successful.
|
||||
*/
|
||||
// TODO(petermarshall): Remove this API
|
||||
V8_DEPRECATED("Use code_pages version.")
|
||||
static bool PCIsInV8(const UnwindState& unwind_state, void* pc);
|
||||
|
||||
/**
|
||||
* The same as above, but is available on x64, ARM64 and ARM32. See the
|
||||
* comment on TryUnwindV8Frames.
|
||||
*
|
||||
* Available on x64, ARM64 and ARM32
|
||||
*/
|
||||
static bool PCIsInV8(size_t code_pages_length, const MemoryRange* code_pages,
|
||||
void* pc);
|
||||
|
@ -8933,33 +8933,6 @@ void Isolate::GetCodeRange(void** start, size_t* length_in_bytes) {
|
||||
*length_in_bytes = code_range.size();
|
||||
}
|
||||
|
||||
UnwindState Isolate::GetUnwindState() {
|
||||
UnwindState unwind_state;
|
||||
void* code_range_start;
|
||||
GetCodeRange(&code_range_start, &unwind_state.code_range.length_in_bytes);
|
||||
unwind_state.code_range.start = code_range_start;
|
||||
|
||||
i::Isolate* isolate = reinterpret_cast<i::Isolate*>(this);
|
||||
unwind_state.embedded_code_range.start =
|
||||
reinterpret_cast<const void*>(isolate->embedded_blob_code());
|
||||
unwind_state.embedded_code_range.length_in_bytes =
|
||||
isolate->embedded_blob_code_size();
|
||||
|
||||
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());
|
||||
pair.second->code.length_in_bytes = js_entry.InstructionSize();
|
||||
}
|
||||
|
||||
return unwind_state;
|
||||
}
|
||||
|
||||
JSEntryStubs Isolate::GetJSEntryStubs() {
|
||||
JSEntryStubs entry_stubs;
|
||||
|
||||
|
@ -46,21 +46,6 @@ bool PCIsInCodePages(size_t code_pages_length, const MemoryRange* code_pages,
|
||||
return it->start <= pc && pc < CalculateEnd(it->start, it->length_in_bytes);
|
||||
}
|
||||
|
||||
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
|
||||
// we are safe to unwind the stack. For now, we bail out if the PC is anywhere
|
||||
// within JSEntry.
|
||||
}
|
||||
|
||||
bool IsInJSEntryRange(const JSEntryStubs& entry_stubs, void* pc) {
|
||||
return PCIsInCodeRange(entry_stubs.js_entry_stub.code, pc) ||
|
||||
PCIsInCodeRange(entry_stubs.js_construct_entry_stub.code, pc) ||
|
||||
@ -80,19 +65,6 @@ i::Address Load(i::Address address) {
|
||||
return *reinterpret_cast<i::Address*>(address);
|
||||
}
|
||||
|
||||
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
|
||||
i::Address ret_addr =
|
||||
Load(reinterpret_cast<i::Address>(fp) + caller_pc_offset);
|
||||
return reinterpret_cast<void*>(i::PointerAuthentication::StripPAC(ret_addr));
|
||||
}
|
||||
|
||||
void* GetReturnAddressFromFP(void* fp, void* pc,
|
||||
const JSEntryStubs& entry_stubs) {
|
||||
int caller_pc_offset = i::CommonFrameConstants::kCallerPCOffset;
|
||||
@ -106,18 +78,6 @@ void* GetReturnAddressFromFP(void* fp, void* pc,
|
||||
return reinterpret_cast<void*>(i::PointerAuthentication::StripPAC(ret_addr));
|
||||
}
|
||||
|
||||
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) + caller_fp_offset));
|
||||
}
|
||||
|
||||
void* GetCallerFPFromFP(void* fp, void* pc, const JSEntryStubs& entry_stubs) {
|
||||
int caller_fp_offset = i::CommonFrameConstants::kCallerFPOffset;
|
||||
#ifdef V8_TARGET_ARCH_ARM64
|
||||
@ -129,18 +89,6 @@ void* GetCallerFPFromFP(void* fp, void* pc, const JSEntryStubs& entry_stubs) {
|
||||
Load(reinterpret_cast<i::Address>(fp) + caller_fp_offset));
|
||||
}
|
||||
|
||||
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) +
|
||||
caller_sp_offset);
|
||||
}
|
||||
|
||||
void* GetCallerSPFromFP(void* fp, void* pc, const JSEntryStubs& entry_stubs) {
|
||||
int caller_sp_offset = i::CommonFrameConstants::kCallerSPOffset;
|
||||
#ifdef V8_TARGET_ARCH_ARM64
|
||||
@ -159,46 +107,6 @@ bool AddressIsInStack(const void* address, const void* stack_base,
|
||||
|
||||
} // namespace
|
||||
|
||||
bool Unwinder::TryUnwindV8Frames(const UnwindState& unwind_state,
|
||||
RegisterState* register_state,
|
||||
const void* stack_base) {
|
||||
const void* stack_top = register_state->sp;
|
||||
|
||||
void* pc = register_state->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, pc, unwind_state);
|
||||
while (PCIsInV8(unwind_state, next_pc)) {
|
||||
current_fp = GetCallerFPFromFP(current_fp, pc, unwind_state);
|
||||
if (!AddressIsInStack(current_fp, stack_base, stack_top)) return false;
|
||||
pc = next_pc;
|
||||
next_pc = GetReturnAddressFromFP(current_fp, pc, unwind_state);
|
||||
}
|
||||
|
||||
void* final_sp = GetCallerSPFromFP(current_fp, pc, unwind_state);
|
||||
if (!AddressIsInStack(final_sp, stack_base, stack_top)) return false;
|
||||
register_state->sp = final_sp;
|
||||
|
||||
// We don't check that the final FP value is within the stack bounds because
|
||||
// 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, pc, unwind_state);
|
||||
register_state->fp = final_fp;
|
||||
|
||||
register_state->pc = next_pc;
|
||||
|
||||
// Link register no longer valid after unwinding.
|
||||
register_state->lr = nullptr;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Unwinder::TryUnwindV8Frames(const JSEntryStubs& entry_stubs,
|
||||
size_t code_pages_length,
|
||||
const MemoryRange* code_pages,
|
||||
@ -242,11 +150,6 @@ bool Unwinder::TryUnwindV8Frames(const JSEntryStubs& entry_stubs,
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Unwinder::PCIsInV8(const UnwindState& unwind_state, void* pc) {
|
||||
return pc && (PCIsInCodeRange(unwind_state.code_range, pc) ||
|
||||
PCIsInCodeRange(unwind_state.embedded_code_range, pc));
|
||||
}
|
||||
|
||||
bool Unwinder::PCIsInV8(size_t code_pages_length, const MemoryRange* code_pages,
|
||||
void* pc) {
|
||||
return pc && PCIsInCodePages(code_pages_length, code_pages, pc);
|
||||
|
@ -267,7 +267,6 @@ v8_source_set("cctest_sources") {
|
||||
"test-unboxed-doubles.cc",
|
||||
"test-unscopables-hidden-prototype.cc",
|
||||
"test-unwinder-code-pages.cc",
|
||||
"test-unwinder.cc",
|
||||
"test-usecounters.cc",
|
||||
"test-utils.cc",
|
||||
"test-version.cc",
|
||||
|
@ -26692,42 +26692,6 @@ TEST(BigIntAPI) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST(TestGetUnwindState) {
|
||||
LocalContext env;
|
||||
v8::Isolate* isolate = env->GetIsolate();
|
||||
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
|
||||
|
||||
// Ignore deprecation warnings so that we can keep the tests for now.
|
||||
// TODO(petermarshall): Remove this once the deprecated API is gone.
|
||||
#if __clang__
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wdeprecated"
|
||||
#endif
|
||||
v8::UnwindState unwind_state = isolate->GetUnwindState();
|
||||
#if __clang__
|
||||
#pragma clang diagnostic pop
|
||||
#endif
|
||||
v8::MemoryRange builtins_range = unwind_state.embedded_code_range;
|
||||
|
||||
// Check that each off-heap builtin is within the builtins code range.
|
||||
for (int id = 0; id < i::Builtins::builtin_count; id++) {
|
||||
if (!i::Builtins::IsIsolateIndependent(id)) continue;
|
||||
i::Code builtin = i_isolate->builtins()->builtin(id);
|
||||
i::Address start = builtin.InstructionStart();
|
||||
i::Address end = start + builtin.InstructionSize();
|
||||
|
||||
i::Address builtins_start =
|
||||
reinterpret_cast<i::Address>(builtins_range.start);
|
||||
CHECK(start >= builtins_start &&
|
||||
end < builtins_start + builtins_range.length_in_bytes);
|
||||
}
|
||||
|
||||
v8::JSEntryStub js_entry_stub = unwind_state.js_entry_stub;
|
||||
|
||||
CHECK_EQ(i_isolate->heap()->builtin(i::Builtins::kJSEntry).InstructionStart(),
|
||||
reinterpret_cast<i::Address>(js_entry_stub.code.start));
|
||||
}
|
||||
|
||||
TEST(GetJSEntryStubs) {
|
||||
LocalContext env;
|
||||
v8::Isolate* isolate = env->GetIsolate();
|
||||
|
@ -1,650 +0,0 @@
|
||||
// Copyright 2018 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.h"
|
||||
#include "src/api/api-inl.h"
|
||||
#include "src/builtins/builtins.h"
|
||||
#include "src/execution/isolate.h"
|
||||
#include "src/execution/pointer-authentication.h"
|
||||
#include "src/heap/spaces.h"
|
||||
#include "src/objects/code-inl.h"
|
||||
#include "test/cctest/cctest.h"
|
||||
|
||||
namespace v8 {
|
||||
namespace internal {
|
||||
namespace test_unwinder {
|
||||
|
||||
static const void* fake_stack_base = nullptr;
|
||||
|
||||
// Ignore deprecation warnings so that we can keep the tests for now.
|
||||
// TODO(petermarshall): Delete all the tests here when the old API is removed to
|
||||
// reduce the duplication.
|
||||
#if __clang__
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wdeprecated"
|
||||
#endif
|
||||
|
||||
TEST(Unwind_BadState_Fail) {
|
||||
UnwindState unwind_state; // Fields are intialized to nullptr.
|
||||
RegisterState register_state;
|
||||
|
||||
bool unwound = v8::Unwinder::TryUnwindV8Frames(unwind_state, ®ister_state,
|
||||
fake_stack_base);
|
||||
CHECK(!unwound);
|
||||
// The register state should not change when unwinding fails.
|
||||
CHECK_NULL(register_state.fp);
|
||||
CHECK_NULL(register_state.sp);
|
||||
CHECK_NULL(register_state.pc);
|
||||
}
|
||||
|
||||
void StorePc(uintptr_t stack[], int index, uintptr_t pc) {
|
||||
#ifdef V8_ENABLE_CONTROL_FLOW_INTEGRITY
|
||||
Address sp = reinterpret_cast<Address>(&stack[index]) + kSystemPointerSize;
|
||||
#ifdef USE_SIMULATOR
|
||||
stack[index] = Simulator::AddPAC(pc, sp, Simulator::kPACKeyIB,
|
||||
Simulator::kInstructionPointer);
|
||||
#else
|
||||
asm volatile(
|
||||
" mov x17, %[pc]\n"
|
||||
" mov x16, %[sp]\n"
|
||||
" pacib1716\n"
|
||||
" mov %[pc], x17\n"
|
||||
: [pc] "+r"(pc)
|
||||
: [sp] "r"(sp)
|
||||
: "x16", "x17");
|
||||
stack[index] = pc;
|
||||
#endif // USE_SIMULATOR
|
||||
#else
|
||||
stack[index] = pc;
|
||||
#endif // V8_ENABLE_CONTROL_FLOW_INTEGRITY
|
||||
}
|
||||
|
||||
TEST(Unwind_BuiltinPCInMiddle_Success) {
|
||||
LocalContext env;
|
||||
v8::Isolate* isolate = env->GetIsolate();
|
||||
Isolate* i_isolate = reinterpret_cast<Isolate*>(isolate);
|
||||
|
||||
UnwindState unwind_state = isolate->GetUnwindState();
|
||||
RegisterState register_state;
|
||||
|
||||
uintptr_t stack[3];
|
||||
void* stack_base = stack + arraysize(stack);
|
||||
stack[0] = reinterpret_cast<uintptr_t>(stack + 2); // saved FP (rbp).
|
||||
StorePc(stack, 1, 202); // Return address into C++ code.
|
||||
stack[2] = 303; // The SP points here in the caller's frame.
|
||||
|
||||
register_state.sp = stack;
|
||||
register_state.fp = stack;
|
||||
|
||||
// Put the current PC inside of a valid builtin.
|
||||
Code builtin = i_isolate->builtins()->builtin(Builtins::kStringEqual);
|
||||
const uintptr_t offset = 40;
|
||||
CHECK_LT(offset, builtin.InstructionSize());
|
||||
register_state.pc =
|
||||
reinterpret_cast<void*>(builtin.InstructionStart() + offset);
|
||||
|
||||
bool unwound = v8::Unwinder::TryUnwindV8Frames(unwind_state, ®ister_state,
|
||||
stack_base);
|
||||
CHECK(unwound);
|
||||
CHECK_EQ(reinterpret_cast<void*>(stack + 2), register_state.fp);
|
||||
CHECK_EQ(reinterpret_cast<void*>(stack + 2), register_state.sp);
|
||||
CHECK_EQ(reinterpret_cast<void*>(202), register_state.pc);
|
||||
}
|
||||
|
||||
// The unwinder should be able to unwind even if we haven't properly set up the
|
||||
// current frame, as long as there is another JS frame underneath us (i.e. as
|
||||
// long as the PC isn't in JSEntry). This test puts the PC at the start
|
||||
// of a JS builtin and creates a fake JSEntry frame before it on the stack. The
|
||||
// unwinder should be able to unwind to the C++ frame before the JSEntry frame.
|
||||
TEST(Unwind_BuiltinPCAtStart_Success) {
|
||||
LocalContext env;
|
||||
v8::Isolate* isolate = env->GetIsolate();
|
||||
Isolate* i_isolate = reinterpret_cast<Isolate*>(isolate);
|
||||
|
||||
UnwindState unwind_state = isolate->GetUnwindState();
|
||||
RegisterState register_state;
|
||||
|
||||
const size_t code_length = 40;
|
||||
uintptr_t code[code_length] = {0};
|
||||
unwind_state.code_range.start = code;
|
||||
unwind_state.code_range.length_in_bytes = code_length * sizeof(uintptr_t);
|
||||
|
||||
uintptr_t stack[6];
|
||||
void* stack_base = stack + arraysize(stack);
|
||||
stack[0] = 101;
|
||||
// Return address into JS code. It doesn't matter that this is not actually in
|
||||
// JSEntry, because we only check that for the top frame.
|
||||
StorePc(stack, 1, reinterpret_cast<uintptr_t>(code + 10));
|
||||
stack[2] = reinterpret_cast<uintptr_t>(stack + 5); // saved FP (rbp).
|
||||
StorePc(stack, 3, 303); // Return address into C++ code.
|
||||
stack[4] = 404;
|
||||
stack[5] = 505;
|
||||
|
||||
register_state.sp = stack;
|
||||
register_state.fp = stack + 2; // FP to the JSEntry frame.
|
||||
|
||||
// Put the current PC at the start of a valid builtin, so that we are setting
|
||||
// up the frame.
|
||||
Code builtin = i_isolate->builtins()->builtin(Builtins::kStringEqual);
|
||||
register_state.pc = reinterpret_cast<void*>(builtin.InstructionStart());
|
||||
|
||||
bool unwound = v8::Unwinder::TryUnwindV8Frames(unwind_state, ®ister_state,
|
||||
stack_base);
|
||||
|
||||
CHECK(unwound);
|
||||
CHECK_EQ(reinterpret_cast<void*>(stack + 5), register_state.fp);
|
||||
CHECK_EQ(reinterpret_cast<void*>(stack + 4), register_state.sp);
|
||||
CHECK_EQ(reinterpret_cast<void*>(303), register_state.pc);
|
||||
}
|
||||
|
||||
const char* foo_source = R"(
|
||||
function foo(a, b) {
|
||||
let x = a * b;
|
||||
let y = x ^ b;
|
||||
let z = y / a;
|
||||
return x + y - z;
|
||||
};
|
||||
%PrepareFunctionForOptimization(foo);
|
||||
foo(1, 2);
|
||||
foo(1, 2);
|
||||
%OptimizeFunctionOnNextCall(foo);
|
||||
foo(1, 2);
|
||||
)";
|
||||
|
||||
// Check that we can unwind when the pc is within an optimized code object on
|
||||
// the V8 heap.
|
||||
TEST(Unwind_CodeObjectPCInMiddle_Success) {
|
||||
FLAG_allow_natives_syntax = true;
|
||||
LocalContext env;
|
||||
v8::Isolate* isolate = env->GetIsolate();
|
||||
Isolate* i_isolate = reinterpret_cast<Isolate*>(isolate);
|
||||
HandleScope scope(i_isolate);
|
||||
|
||||
UnwindState unwind_state = isolate->GetUnwindState();
|
||||
RegisterState register_state;
|
||||
|
||||
uintptr_t stack[3];
|
||||
void* stack_base = stack + arraysize(stack);
|
||||
stack[0] = reinterpret_cast<uintptr_t>(stack + 2); // saved FP (rbp).
|
||||
StorePc(stack, 1, 202); // Return address into C++ code.
|
||||
stack[2] = 303; // The SP points here in the caller's frame.
|
||||
|
||||
register_state.sp = stack;
|
||||
register_state.fp = stack;
|
||||
|
||||
// Create an on-heap code object. Make sure we run the function so that it is
|
||||
// compiled and not just marked for lazy compilation.
|
||||
CompileRun(foo_source);
|
||||
v8::Local<v8::Function> local_foo = v8::Local<v8::Function>::Cast(
|
||||
env.local()->Global()->Get(env.local(), v8_str("foo")).ToLocalChecked());
|
||||
Handle<JSFunction> foo =
|
||||
Handle<JSFunction>::cast(v8::Utils::OpenHandle(*local_foo));
|
||||
|
||||
// Put the current PC inside of the created code object.
|
||||
AbstractCode abstract_code = foo->abstract_code();
|
||||
// We don't produce optimized code when run with --no-opt.
|
||||
if (!abstract_code.IsCode() && FLAG_opt == false) return;
|
||||
CHECK(abstract_code.IsCode());
|
||||
|
||||
Code code = abstract_code.GetCode();
|
||||
// We don't want the offset too early or it could be the `push rbp`
|
||||
// instruction (which is not at the start of generated code, because the lazy
|
||||
// deopt check happens before frame setup).
|
||||
const uintptr_t offset = code.InstructionSize() - 20;
|
||||
CHECK_LT(offset, code.InstructionSize());
|
||||
Address pc = code.InstructionStart() + offset;
|
||||
register_state.pc = reinterpret_cast<void*>(pc);
|
||||
|
||||
// Check that the created code is within the code range that we get from the
|
||||
// API.
|
||||
Address start = reinterpret_cast<Address>(unwind_state.code_range.start);
|
||||
CHECK(pc >= start && pc < start + unwind_state.code_range.length_in_bytes);
|
||||
|
||||
bool unwound = v8::Unwinder::TryUnwindV8Frames(unwind_state, ®ister_state,
|
||||
stack_base);
|
||||
CHECK(unwound);
|
||||
CHECK_EQ(reinterpret_cast<void*>(stack + 2), register_state.fp);
|
||||
CHECK_EQ(reinterpret_cast<void*>(stack + 2), register_state.sp);
|
||||
CHECK_EQ(reinterpret_cast<void*>(202), register_state.pc);
|
||||
}
|
||||
|
||||
// If the PC is within JSEntry but we haven't set up the frame yet, then we
|
||||
// cannot unwind.
|
||||
TEST(Unwind_JSEntryBeforeFrame_Fail) {
|
||||
LocalContext env;
|
||||
v8::Isolate* isolate = env->GetIsolate();
|
||||
|
||||
UnwindState unwind_state = isolate->GetUnwindState();
|
||||
RegisterState register_state;
|
||||
|
||||
const size_t code_length = 40;
|
||||
uintptr_t code[code_length] = {0};
|
||||
unwind_state.code_range.start = code;
|
||||
unwind_state.code_range.length_in_bytes = code_length * sizeof(uintptr_t);
|
||||
|
||||
// Pretend that it takes 5 instructions to set up the frame in JSEntry.
|
||||
unwind_state.js_entry_stub.code.start = code + 10;
|
||||
unwind_state.js_entry_stub.code.length_in_bytes = 10 * sizeof(uintptr_t);
|
||||
|
||||
uintptr_t stack[10];
|
||||
void* stack_base = stack + arraysize(stack);
|
||||
stack[0] = 101;
|
||||
stack[1] = 111;
|
||||
stack[2] = 121;
|
||||
stack[3] = 131;
|
||||
stack[4] = 141;
|
||||
stack[5] = 151;
|
||||
StorePc(stack, 6, 100); // Return address into C++ code.
|
||||
stack[7] = 303; // The SP points here in the caller's frame.
|
||||
stack[8] = 404;
|
||||
stack[9] = 505;
|
||||
|
||||
register_state.sp = stack + 5;
|
||||
register_state.fp = stack + 9;
|
||||
|
||||
// Put the current PC inside of JSEntry, before the frame is set up.
|
||||
register_state.pc = code + 12;
|
||||
bool unwound = v8::Unwinder::TryUnwindV8Frames(unwind_state, ®ister_state,
|
||||
stack_base);
|
||||
CHECK(!unwound);
|
||||
// The register state should not change when unwinding fails.
|
||||
CHECK_EQ(reinterpret_cast<void*>(stack + 9), register_state.fp);
|
||||
CHECK_EQ(reinterpret_cast<void*>(stack + 5), register_state.sp);
|
||||
CHECK_EQ(code + 12, register_state.pc);
|
||||
|
||||
// Change the PC to a few instructions later, after the frame is set up.
|
||||
register_state.pc = code + 16;
|
||||
unwound = v8::Unwinder::TryUnwindV8Frames(unwind_state, ®ister_state,
|
||||
stack_base);
|
||||
// TODO(petermarshall): More precisely check position within JSEntry rather
|
||||
// than just assuming the frame is unreadable.
|
||||
CHECK(!unwound);
|
||||
// The register state should not change when unwinding fails.
|
||||
CHECK_EQ(reinterpret_cast<void*>(stack + 9), register_state.fp);
|
||||
CHECK_EQ(reinterpret_cast<void*>(stack + 5), register_state.sp);
|
||||
CHECK_EQ(code + 16, register_state.pc);
|
||||
}
|
||||
|
||||
TEST(Unwind_OneJSFrame_Success) {
|
||||
LocalContext env;
|
||||
v8::Isolate* isolate = env->GetIsolate();
|
||||
|
||||
UnwindState unwind_state = isolate->GetUnwindState();
|
||||
RegisterState register_state;
|
||||
|
||||
// Use a fake code range so that we can initialize it to 0s.
|
||||
const size_t code_length = 40;
|
||||
uintptr_t code[code_length] = {0};
|
||||
unwind_state.code_range.start = code;
|
||||
unwind_state.code_range.length_in_bytes = code_length * sizeof(uintptr_t);
|
||||
|
||||
// Our fake stack has two frames - one C++ frame and one JS frame (on top).
|
||||
// The stack grows from high addresses to low addresses.
|
||||
uintptr_t stack[10];
|
||||
void* stack_base = stack + arraysize(stack);
|
||||
stack[0] = 101;
|
||||
stack[1] = 111;
|
||||
stack[2] = 121;
|
||||
stack[3] = 131;
|
||||
stack[4] = 141;
|
||||
stack[5] = reinterpret_cast<uintptr_t>(stack + 9); // saved FP (rbp).
|
||||
StorePc(stack, 6, 100); // Return address into C++ code.
|
||||
stack[7] = 303; // The SP points here in the caller's frame.
|
||||
stack[8] = 404;
|
||||
stack[9] = 505;
|
||||
|
||||
register_state.sp = stack;
|
||||
register_state.fp = stack + 5;
|
||||
|
||||
// Put the current PC inside of the code range so it looks valid.
|
||||
register_state.pc = code + 30;
|
||||
|
||||
bool unwound = v8::Unwinder::TryUnwindV8Frames(unwind_state, ®ister_state,
|
||||
stack_base);
|
||||
|
||||
CHECK(unwound);
|
||||
CHECK_EQ(reinterpret_cast<void*>(stack + 9), register_state.fp);
|
||||
CHECK_EQ(reinterpret_cast<void*>(stack + 7), register_state.sp);
|
||||
CHECK_EQ(reinterpret_cast<void*>(100), register_state.pc);
|
||||
}
|
||||
|
||||
// Creates a fake stack with two JS frames on top of a C++ frame and checks that
|
||||
// the unwinder correctly unwinds past the JS frames and returns the C++ frame's
|
||||
// details.
|
||||
TEST(Unwind_TwoJSFrames_Success) {
|
||||
LocalContext env;
|
||||
v8::Isolate* isolate = env->GetIsolate();
|
||||
|
||||
UnwindState unwind_state = isolate->GetUnwindState();
|
||||
RegisterState register_state;
|
||||
|
||||
// Use a fake code range so that we can initialize it to 0s.
|
||||
const size_t code_length = 40;
|
||||
uintptr_t code[code_length] = {0};
|
||||
unwind_state.code_range.start = code;
|
||||
unwind_state.code_range.length_in_bytes = code_length * sizeof(uintptr_t);
|
||||
|
||||
// Our fake stack has three frames - one C++ frame and two JS frames (on top).
|
||||
// The stack grows from high addresses to low addresses.
|
||||
uintptr_t stack[10];
|
||||
void* stack_base = stack + arraysize(stack);
|
||||
stack[0] = 101;
|
||||
stack[1] = 111;
|
||||
stack[2] = reinterpret_cast<uintptr_t>(stack + 5); // saved FP (rbp).
|
||||
// The fake return address is in the JS code range.
|
||||
StorePc(stack, 3, reinterpret_cast<uintptr_t>(code + 10));
|
||||
stack[4] = 141;
|
||||
stack[5] = reinterpret_cast<uintptr_t>(stack + 9); // saved FP (rbp).
|
||||
StorePc(stack, 6, 100); // Return address into C++ code.
|
||||
stack[7] = 303; // The SP points here in the caller's frame.
|
||||
stack[8] = 404;
|
||||
stack[9] = 505;
|
||||
|
||||
register_state.sp = stack;
|
||||
register_state.fp = stack + 2;
|
||||
|
||||
// Put the current PC inside of the code range so it looks valid.
|
||||
register_state.pc = code + 30;
|
||||
|
||||
bool unwound = v8::Unwinder::TryUnwindV8Frames(unwind_state, ®ister_state,
|
||||
stack_base);
|
||||
|
||||
CHECK(unwound);
|
||||
CHECK_EQ(reinterpret_cast<void*>(stack + 9), register_state.fp);
|
||||
CHECK_EQ(reinterpret_cast<void*>(stack + 7), register_state.sp);
|
||||
CHECK_EQ(reinterpret_cast<void*>(100), register_state.pc);
|
||||
}
|
||||
|
||||
// If the PC is in JSEntry then the frame might not be set up correctly, meaning
|
||||
// we can't unwind the stack properly.
|
||||
TEST(Unwind_JSEntry_Fail) {
|
||||
LocalContext env;
|
||||
v8::Isolate* isolate = env->GetIsolate();
|
||||
Isolate* i_isolate = reinterpret_cast<Isolate*>(isolate);
|
||||
|
||||
UnwindState unwind_state = isolate->GetUnwindState();
|
||||
RegisterState register_state;
|
||||
|
||||
Code js_entry = i_isolate->heap()->builtin(Builtins::kJSEntry);
|
||||
byte* start = reinterpret_cast<byte*>(js_entry.InstructionStart());
|
||||
register_state.pc = start + 10;
|
||||
|
||||
bool unwound = v8::Unwinder::TryUnwindV8Frames(unwind_state, ®ister_state,
|
||||
fake_stack_base);
|
||||
CHECK(!unwound);
|
||||
// The register state should not change when unwinding fails.
|
||||
CHECK_NULL(register_state.fp);
|
||||
CHECK_NULL(register_state.sp);
|
||||
CHECK_EQ(start + 10, register_state.pc);
|
||||
}
|
||||
|
||||
TEST(Unwind_StackBounds_Basic) {
|
||||
LocalContext env;
|
||||
v8::Isolate* isolate = env->GetIsolate();
|
||||
|
||||
UnwindState unwind_state = isolate->GetUnwindState();
|
||||
RegisterState register_state;
|
||||
|
||||
const size_t code_length = 10;
|
||||
uintptr_t code[code_length] = {0};
|
||||
unwind_state.code_range.start = code;
|
||||
unwind_state.code_range.length_in_bytes = code_length * sizeof(uintptr_t);
|
||||
|
||||
uintptr_t stack[3];
|
||||
stack[0] = reinterpret_cast<uintptr_t>(stack + 2); // saved FP (rbp).
|
||||
StorePc(stack, 1, 202); // Return address into C++ code.
|
||||
stack[2] = 303; // The SP points here in the caller's frame.
|
||||
|
||||
register_state.sp = stack;
|
||||
register_state.fp = stack;
|
||||
register_state.pc = code;
|
||||
|
||||
void* wrong_stack_base = reinterpret_cast<void*>(
|
||||
reinterpret_cast<uintptr_t>(stack) - sizeof(uintptr_t));
|
||||
bool unwound = v8::Unwinder::TryUnwindV8Frames(unwind_state, ®ister_state,
|
||||
wrong_stack_base);
|
||||
CHECK(!unwound);
|
||||
|
||||
// Correct the stack base and unwinding should succeed.
|
||||
void* correct_stack_base = stack + arraysize(stack);
|
||||
unwound = v8::Unwinder::TryUnwindV8Frames(unwind_state, ®ister_state,
|
||||
correct_stack_base);
|
||||
CHECK(unwound);
|
||||
}
|
||||
|
||||
TEST(Unwind_StackBounds_WithUnwinding) {
|
||||
LocalContext env;
|
||||
v8::Isolate* isolate = env->GetIsolate();
|
||||
|
||||
UnwindState unwind_state = isolate->GetUnwindState();
|
||||
RegisterState register_state;
|
||||
|
||||
// Use a fake code range so that we can initialize it to 0s.
|
||||
const size_t code_length = 40;
|
||||
uintptr_t code[code_length] = {0};
|
||||
unwind_state.code_range.start = code;
|
||||
unwind_state.code_range.length_in_bytes = code_length * sizeof(uintptr_t);
|
||||
|
||||
// Our fake stack has two frames - one C++ frame and one JS frame (on top).
|
||||
// The stack grows from high addresses to low addresses.
|
||||
uintptr_t stack[11];
|
||||
void* stack_base = stack + arraysize(stack);
|
||||
stack[0] = 101;
|
||||
stack[1] = 111;
|
||||
stack[2] = 121;
|
||||
stack[3] = 131;
|
||||
stack[4] = 141;
|
||||
stack[5] = reinterpret_cast<uintptr_t>(stack + 9); // saved FP (rbp).
|
||||
StorePc(stack, 6, reinterpret_cast<uintptr_t>(code + 20)); // JS code.
|
||||
stack[7] = 303; // The SP points here in the caller's frame.
|
||||
stack[8] = 404;
|
||||
stack[9] = reinterpret_cast<uintptr_t>(stack) +
|
||||
(12 * sizeof(uintptr_t)); // saved FP (OOB).
|
||||
StorePc(stack, 10, reinterpret_cast<uintptr_t>(code + 20)); // JS code.
|
||||
|
||||
register_state.sp = stack;
|
||||
register_state.fp = stack + 5;
|
||||
|
||||
// Put the current PC inside of the code range so it looks valid.
|
||||
register_state.pc = code + 30;
|
||||
|
||||
// Unwind will fail because stack[9] FP points outside of the stack.
|
||||
bool unwound = v8::Unwinder::TryUnwindV8Frames(unwind_state, ®ister_state,
|
||||
stack_base);
|
||||
CHECK(!unwound);
|
||||
|
||||
// Change the return address so that it is not in range. We will not range
|
||||
// check the stack[9] FP value because we have finished unwinding and the
|
||||
// contents of rbp does not necessarily have to be the FP in this case.
|
||||
StorePc(stack, 10, 202);
|
||||
unwound = v8::Unwinder::TryUnwindV8Frames(unwind_state, ®ister_state,
|
||||
stack_base);
|
||||
CHECK(unwound);
|
||||
}
|
||||
|
||||
TEST(PCIsInV8_BadState_Fail) {
|
||||
UnwindState unwind_state;
|
||||
void* pc = nullptr;
|
||||
|
||||
CHECK(!v8::Unwinder::PCIsInV8(unwind_state, pc));
|
||||
}
|
||||
|
||||
TEST(PCIsInV8_ValidStateNullPC_Fail) {
|
||||
LocalContext env;
|
||||
v8::Isolate* isolate = env->GetIsolate();
|
||||
|
||||
UnwindState unwind_state = isolate->GetUnwindState();
|
||||
void* pc = nullptr;
|
||||
|
||||
CHECK(!v8::Unwinder::PCIsInV8(unwind_state, pc));
|
||||
}
|
||||
|
||||
void TestRangeBoundaries(const UnwindState& unwind_state, byte* range_start,
|
||||
size_t range_length) {
|
||||
void* pc = range_start - 1;
|
||||
CHECK(!v8::Unwinder::PCIsInV8(unwind_state, pc));
|
||||
pc = range_start;
|
||||
CHECK(v8::Unwinder::PCIsInV8(unwind_state, pc));
|
||||
pc = range_start + 1;
|
||||
CHECK(v8::Unwinder::PCIsInV8(unwind_state, pc));
|
||||
pc = range_start + range_length - 1;
|
||||
CHECK(v8::Unwinder::PCIsInV8(unwind_state, pc));
|
||||
pc = range_start + range_length;
|
||||
CHECK(!v8::Unwinder::PCIsInV8(unwind_state, pc));
|
||||
pc = range_start + range_length + 1;
|
||||
CHECK(!v8::Unwinder::PCIsInV8(unwind_state, pc));
|
||||
}
|
||||
|
||||
TEST(PCIsInV8_InCodeOrEmbeddedRange) {
|
||||
LocalContext env;
|
||||
v8::Isolate* isolate = env->GetIsolate();
|
||||
|
||||
UnwindState unwind_state = isolate->GetUnwindState();
|
||||
|
||||
byte* code_range_start = const_cast<byte*>(
|
||||
reinterpret_cast<const byte*>(unwind_state.code_range.start));
|
||||
size_t code_range_length = unwind_state.code_range.length_in_bytes;
|
||||
TestRangeBoundaries(unwind_state, code_range_start, code_range_length);
|
||||
|
||||
byte* embedded_range_start = const_cast<byte*>(
|
||||
reinterpret_cast<const byte*>(unwind_state.embedded_code_range.start));
|
||||
size_t embedded_range_length =
|
||||
unwind_state.embedded_code_range.length_in_bytes;
|
||||
TestRangeBoundaries(unwind_state, embedded_range_start,
|
||||
embedded_range_length);
|
||||
}
|
||||
|
||||
// PCIsInV8 doesn't check if the PC is in JSEntry directly. It's assumed that
|
||||
// the CodeRange or EmbeddedCodeRange contain JSEntry.
|
||||
TEST(PCIsInV8_InJSEntryRange) {
|
||||
LocalContext env;
|
||||
v8::Isolate* isolate = env->GetIsolate();
|
||||
Isolate* i_isolate = reinterpret_cast<Isolate*>(isolate);
|
||||
|
||||
UnwindState unwind_state = isolate->GetUnwindState();
|
||||
|
||||
Code js_entry = i_isolate->heap()->builtin(Builtins::kJSEntry);
|
||||
byte* start = reinterpret_cast<byte*>(js_entry.InstructionStart());
|
||||
size_t length = js_entry.InstructionSize();
|
||||
|
||||
void* pc = start;
|
||||
CHECK(v8::Unwinder::PCIsInV8(unwind_state, pc));
|
||||
pc = start + 1;
|
||||
CHECK(v8::Unwinder::PCIsInV8(unwind_state, pc));
|
||||
pc = start + length - 1;
|
||||
CHECK(v8::Unwinder::PCIsInV8(unwind_state, pc));
|
||||
}
|
||||
|
||||
// Large code objects can be allocated in large object space. Check that this is
|
||||
// inside the CodeRange.
|
||||
TEST(PCIsInV8_LargeCodeObject) {
|
||||
FLAG_allow_natives_syntax = true;
|
||||
LocalContext env;
|
||||
v8::Isolate* isolate = env->GetIsolate();
|
||||
Isolate* i_isolate = reinterpret_cast<Isolate*>(isolate);
|
||||
HandleScope scope(i_isolate);
|
||||
|
||||
UnwindState unwind_state = isolate->GetUnwindState();
|
||||
|
||||
// Create a big function that ends up in CODE_LO_SPACE.
|
||||
const int instruction_size = Page::kPageSize + 1;
|
||||
STATIC_ASSERT(instruction_size > kMaxRegularHeapObjectSize);
|
||||
std::unique_ptr<byte[]> instructions(new byte[instruction_size]);
|
||||
|
||||
CodeDesc desc;
|
||||
desc.buffer = instructions.get();
|
||||
desc.buffer_size = instruction_size;
|
||||
desc.instr_size = instruction_size;
|
||||
desc.reloc_size = 0;
|
||||
desc.constant_pool_size = 0;
|
||||
desc.unwinding_info = nullptr;
|
||||
desc.unwinding_info_size = 0;
|
||||
desc.origin = nullptr;
|
||||
Handle<Code> foo_code =
|
||||
Factory::CodeBuilder(i_isolate, desc, CodeKind::WASM_FUNCTION).Build();
|
||||
|
||||
CHECK(i_isolate->heap()->InSpace(*foo_code, CODE_LO_SPACE));
|
||||
byte* start = reinterpret_cast<byte*>(foo_code->InstructionStart());
|
||||
|
||||
void* pc = start;
|
||||
CHECK(v8::Unwinder::PCIsInV8(unwind_state, pc));
|
||||
}
|
||||
|
||||
#ifdef USE_SIMULATOR
|
||||
// TODO(v8:10026): Make this also work without the simulator. The part that
|
||||
// needs modifications is getting the RegisterState.
|
||||
class UnwinderTestHelper {
|
||||
public:
|
||||
explicit UnwinderTestHelper(const std::string& test_function)
|
||||
: isolate_(CcTest::isolate()) {
|
||||
CHECK(!instance_);
|
||||
instance_ = this;
|
||||
v8::HandleScope scope(isolate_);
|
||||
v8::Local<v8::ObjectTemplate> global = v8::ObjectTemplate::New(isolate_);
|
||||
global->Set(v8_str("TryUnwind"),
|
||||
v8::FunctionTemplate::New(isolate_, TryUnwind));
|
||||
LocalContext env(isolate_, nullptr, global);
|
||||
CompileRun(v8_str(test_function.c_str()));
|
||||
}
|
||||
|
||||
~UnwinderTestHelper() { instance_ = nullptr; }
|
||||
|
||||
private:
|
||||
static void TryUnwind(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
||||
instance_->DoTryUnwind();
|
||||
}
|
||||
|
||||
void DoTryUnwind() {
|
||||
// Set up RegisterState.
|
||||
v8::RegisterState register_state;
|
||||
SimulatorHelper simulator_helper;
|
||||
if (!simulator_helper.Init(isolate_)) return;
|
||||
simulator_helper.FillRegisters(®ister_state);
|
||||
// At this point, the PC will point to a Redirection object, which is not
|
||||
// in V8 as far as the unwinder is concerned. To make this work, point to
|
||||
// the return address, which is in V8, instead.
|
||||
register_state.pc = register_state.lr;
|
||||
|
||||
UnwindState unwind_state = isolate_->GetUnwindState();
|
||||
void* stack_base = reinterpret_cast<void*>(0xffffffffffffffffL);
|
||||
bool unwound = v8::Unwinder::TryUnwindV8Frames(unwind_state,
|
||||
®ister_state, stack_base);
|
||||
// Check that we have successfully unwound past js_entry_sp.
|
||||
CHECK(unwound);
|
||||
CHECK_GT(register_state.sp,
|
||||
reinterpret_cast<void*>(CcTest::i_isolate()->js_entry_sp()));
|
||||
}
|
||||
|
||||
v8::Isolate* isolate_;
|
||||
|
||||
static UnwinderTestHelper* instance_;
|
||||
};
|
||||
|
||||
UnwinderTestHelper* UnwinderTestHelper::instance_;
|
||||
|
||||
TEST(Unwind_TwoNestedFunctions) {
|
||||
i::FLAG_allow_natives_syntax = true;
|
||||
const char* test_script =
|
||||
"function test_unwinder_api_inner() {"
|
||||
" TryUnwind();"
|
||||
" return 0;"
|
||||
"}"
|
||||
"function test_unwinder_api_outer() {"
|
||||
" return test_unwinder_api_inner();"
|
||||
"}"
|
||||
"%NeverOptimizeFunction(test_unwinder_api_inner);"
|
||||
"%NeverOptimizeFunction(test_unwinder_api_outer);"
|
||||
"test_unwinder_api_outer();";
|
||||
|
||||
UnwinderTestHelper helper(test_script);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if __clang__
|
||||
#pragma clang diagnostic pop
|
||||
#endif
|
||||
|
||||
} // namespace test_unwinder
|
||||
} // namespace internal
|
||||
} // namespace v8
|
Loading…
Reference in New Issue
Block a user