Commit f4bb38c3 authored by Peter Marshall's avatar Peter Marshall Committed by Commit Bot

[tools] Add an API for unwinding the V8 stack

This API allows the embedder to provide a stack and PC, FP and
SP registers. V8 will then attempt to unwind the stack to the C++ frame
that called into JS. This API is signal-safe, meaning it does not call
any signal-unsafe OS functions or read/write any V8 state.

Bug: v8:8116

Cq-Include-Trybots: luci.chromium.try:linux_chromium_rel_ng
Change-Id: I7e3e73753b711737020b6a5f11946096658afa6f
Reviewed-on: https://chromium-review.googlesource.com/c/1186724
Commit-Queue: Peter Marshall <petermarshall@chromium.org>
Reviewed-by: 's avatarYang Guo <yangguo@chromium.org>
Cr-Commit-Position: refs/heads/master@{#57749}
parent d1d35504
...@@ -2627,6 +2627,7 @@ v8_source_set("v8_base") { ...@@ -2627,6 +2627,7 @@ v8_source_set("v8_base") {
"src/unicode.h", "src/unicode.h",
"src/unoptimized-compilation-info.cc", "src/unoptimized-compilation-info.cc",
"src/unoptimized-compilation-info.h", "src/unoptimized-compilation-info.h",
"src/unwinder.cc",
"src/uri.cc", "src/uri.cc",
"src/uri.h", "src/uri.h",
"src/utils-inl.h", "src/utils-inl.h",
......
...@@ -1822,8 +1822,18 @@ struct SampleInfo { ...@@ -1822,8 +1822,18 @@ struct SampleInfo {
}; };
struct MemoryRange { struct MemoryRange {
const void* start; const void* start = nullptr;
size_t length_in_bytes; size_t length_in_bytes = 0;
};
struct JSEntryStub {
MemoryRange code;
};
struct UnwindState {
MemoryRange code_range;
MemoryRange embedded_code_range;
JSEntryStub js_entry_stub;
}; };
/** /**
...@@ -8137,13 +8147,9 @@ class V8_EXPORT Isolate { ...@@ -8137,13 +8147,9 @@ class V8_EXPORT Isolate {
void GetCodeRange(void** start, size_t* length_in_bytes); void GetCodeRange(void** start, size_t* length_in_bytes);
/** /**
* Returns a memory range containing the code for V8's embedded functions * Returns the UnwindState necessary for use with the Unwinder API.
* (e.g. builtins) which are shared across isolates.
*
* If embedded builtins are disabled, then the memory range will be a null
* pointer with 0 length.
*/ */
MemoryRange GetEmbeddedCodeRange(); UnwindState GetUnwindState();
/** Set the callback to invoke in case of fatal errors. */ /** Set the callback to invoke in case of fatal errors. */
void SetFatalErrorHandler(FatalErrorCallback that); void SetFatalErrorHandler(FatalErrorCallback that);
...@@ -9313,6 +9319,51 @@ class V8_EXPORT Locker { ...@@ -9313,6 +9319,51 @@ class V8_EXPORT Locker {
internal::Isolate* isolate_; internal::Isolate* isolate_;
}; };
/**
* Various helpers for skipping over V8 frames in a given stack.
*
* The unwinder API is only supported on the x64 architecture.
*/
class V8_EXPORT Unwinder {
public:
/**
* Attempt to unwind the stack to the most recent C++ frame. This function is
* signal-safe and does not access any V8 state and thus doesn't require an
* Isolate.
*
* The unwinder needs to know the location of the JS Entry Stub (a piece of
* code that is run when C++ code calls into generated JS code). This is used
* for edge cases where the current frame is being constructed or torn down
* when the stack sample occurs.
*
* 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.
*
* \param unwind_state Input state for the Isolate that the stack comes from.
* \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 Currently unused.
*
* \return True on success.
*/
static bool TryUnwindV8Frames(const UnwindState& unwind_state,
RegisterState* register_state,
const void* stack_base);
/**
* Whether the PC is within the V8 code range represented by code_range or
* embedded_code_range in |unwind_state|.
*
* 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.
*/
static bool PCIsInV8(const UnwindState& unwind_state, void* pc);
};
// --- Implementation --- // --- Implementation ---
......
...@@ -8812,10 +8812,24 @@ void Isolate::GetCodeRange(void** start, size_t* length_in_bytes) { ...@@ -8812,10 +8812,24 @@ void Isolate::GetCodeRange(void** start, size_t* length_in_bytes) {
*length_in_bytes = code_range.size(); *length_in_bytes = code_range.size();
} }
MemoryRange Isolate::GetEmbeddedCodeRange() { 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); i::Isolate* isolate = reinterpret_cast<i::Isolate*>(this);
return {reinterpret_cast<const void*>(isolate->embedded_blob()), unwind_state.embedded_code_range.start =
isolate->embedded_blob_size()}; reinterpret_cast<const void*>(isolate->embedded_blob());
unwind_state.embedded_code_range.length_in_bytes =
isolate->embedded_blob_size();
i::Code js_entry = isolate->heap()->js_entry_code();
unwind_state.js_entry_stub.code.start =
reinterpret_cast<const void*>(js_entry->InstructionStart());
unwind_state.js_entry_stub.code.length_in_bytes = js_entry->InstructionSize();
return unwind_state;
} }
#define CALLBACK_SETTER(ExternalName, Type, InternalName) \ #define CALLBACK_SETTER(ExternalName, Type, InternalName) \
......
// 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/frame-constants.h"
#include "src/globals.h"
namespace v8 {
namespace {
bool PCIsInCodeRange(const v8::MemoryRange& code_range, void* pc) {
// Given that the length of the memory range is in bytes and it is not
// necessarily aligned, we need to do the pointer arithmetic in byte* here.
const i::byte* pc_as_byte = reinterpret_cast<i::byte*>(pc);
const i::byte* start = reinterpret_cast<const i::byte*>(code_range.start);
const i::byte* end = start + code_range.length_in_bytes;
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);
// TODO(petermarshall): We can be more precise by checking whether we are
// in the JSEntryStub 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 the JSEntryStub.
}
i::Address Load(i::Address address) {
return *reinterpret_cast<i::Address*>(address);
}
void* GetReturnAddressFromFP(void* fp) {
return reinterpret_cast<void*>(
Load(reinterpret_cast<i::Address>(fp) +
i::CommonFrameConstants::kCallerPCOffset));
}
void* GetCallerFPFromFP(void* fp) {
return reinterpret_cast<void*>(
Load(reinterpret_cast<i::Address>(fp) +
i::CommonFrameConstants::kCallerFPOffset));
}
void* GetCallerSPFromFP(void* fp) {
return reinterpret_cast<void*>(reinterpret_cast<i::Address>(fp) +
i::CommonFrameConstants::kCallerSPOffset);
}
} // namespace
bool Unwinder::TryUnwindV8Frames(const UnwindState& unwind_state,
RegisterState* register_state,
const void* stack_base) {
void* pc = register_state->pc;
if (PCIsInV8(unwind_state, pc) &&
!IsInUnsafeJSEntryRange(unwind_state.js_entry_stub, pc)) {
void* current_fp = register_state->fp;
// 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);
while (PCIsInV8(unwind_state, next_pc)) {
current_fp = GetCallerFPFromFP(current_fp);
next_pc = GetReturnAddressFromFP(current_fp);
}
register_state->sp = GetCallerSPFromFP(current_fp);
register_state->fp = GetCallerFPFromFP(current_fp);
register_state->pc = next_pc;
return true;
}
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));
}
} // namespace v8
...@@ -230,6 +230,7 @@ v8_source_set("cctest_sources") { ...@@ -230,6 +230,7 @@ v8_source_set("cctest_sources") {
"test-unbound-queue.cc", "test-unbound-queue.cc",
"test-unboxed-doubles.cc", "test-unboxed-doubles.cc",
"test-unscopables-hidden-prototype.cc", "test-unscopables-hidden-prototype.cc",
"test-unwinder.cc",
"test-usecounters.cc", "test-usecounters.cc",
"test-utils.cc", "test-utils.cc",
"test-version.cc", "test-version.cc",
......
...@@ -477,4 +477,10 @@ ...@@ -477,4 +477,10 @@
'test-dtoa/*': [SKIP], 'test-dtoa/*': [SKIP],
}], # variant == no_wasm_traps }], # variant == no_wasm_traps
##############################################################################
# The stack unwinder API is only supported on x64.
['arch != x64', {
'test-unwinder/*': [SKIP]
}],
] ]
...@@ -29092,12 +29092,13 @@ TEST(TestSetWasmThreadsEnabledCallback) { ...@@ -29092,12 +29092,13 @@ TEST(TestSetWasmThreadsEnabledCallback) {
CHECK(i_isolate->AreWasmThreadsEnabled(i_context)); CHECK(i_isolate->AreWasmThreadsEnabled(i_context));
} }
TEST(TestGetEmbeddedCodeRange) { TEST(TestGetUnwindState) {
LocalContext env; LocalContext env;
v8::Isolate* isolate = env->GetIsolate(); v8::Isolate* isolate = env->GetIsolate();
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate); i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
v8::MemoryRange builtins_range = isolate->GetEmbeddedCodeRange(); v8::UnwindState unwind_state = isolate->GetUnwindState();
v8::MemoryRange builtins_range = unwind_state.embedded_code_range;
// Check that each off-heap builtin is within the builtins code range. // Check that each off-heap builtin is within the builtins code range.
if (i::FLAG_embedded_builtins) { if (i::FLAG_embedded_builtins) {
...@@ -29116,6 +29117,11 @@ TEST(TestGetEmbeddedCodeRange) { ...@@ -29116,6 +29117,11 @@ TEST(TestGetEmbeddedCodeRange) {
CHECK_EQ(nullptr, builtins_range.start); CHECK_EQ(nullptr, builtins_range.start);
CHECK_EQ(0, builtins_range.length_in_bytes); CHECK_EQ(0, builtins_range.length_in_bytes);
} }
v8::JSEntryStub js_entry_stub = unwind_state.js_entry_stub;
CHECK_EQ(i_isolate->heap()->js_entry_code()->InstructionStart(),
reinterpret_cast<i::Address>(js_entry_stub.code.start));
} }
TEST(MicrotaskContextShouldBeNativeContext) { TEST(MicrotaskContextShouldBeNativeContext) {
// 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-inl.h"
#include "src/builtins/builtins.h"
#include "src/isolate.h"
#include "src/objects/code-inl.h"
#include "test/cctest/cctest.h"
namespace v8 {
namespace internal {
namespace test_unwinder {
static void* unlimited_stack_base = std::numeric_limits<void*>::max();
TEST(Unwind_BadState_Fail) {
UnwindState unwind_state; // Fields are intialized to nullptr.
RegisterState register_state;
bool unwound = v8::Unwinder::TryUnwindV8Frames(unwind_state, &register_state,
unlimited_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);
}
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).
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, &register_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 the JSEntryStub). 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
// the JSEntryStub, because we only check that for the top frame.
stack[1] = reinterpret_cast<uintptr_t>(code + 10);
stack[2] = reinterpret_cast<uintptr_t>(stack + 5); // saved FP (rbp).
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, &register_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;
}
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).
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, &register_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 the JSEntryStub 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 JSEntryStub.
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;
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 the JSEntryStub, before the frame is set up.
register_state.pc = code + 12;
bool unwound = v8::Unwinder::TryUnwindV8Frames(unwind_state, &register_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, &register_state,
stack_base);
// TODO(petermarshall): More precisely check position within the JSEntryStub
// 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).
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, &register_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.
stack[3] = reinterpret_cast<uintptr_t>(code + 10);
stack[4] = 141;
stack[5] = reinterpret_cast<uintptr_t>(stack + 9); // saved FP (rbp).
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, &register_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 the JSEntryStub then the frame might not be set up correctly,
// meaning we can't unwind the stack properly.
TEST(Unwind_JSEntryStub_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()->js_entry_code();
byte* start = reinterpret_cast<byte*>(js_entry->InstructionStart());
register_state.pc = start + 10;
bool unwound = v8::Unwinder::TryUnwindV8Frames(unwind_state, &register_state,
unlimited_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(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 the JSEntryStub directly. It's assumed
// that the CodeRange or EmbeddedCodeRange contain the JSEntryStub.
TEST(PCIsInV8_InJSEntryStubRange) {
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()->js_entry_code();
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));
}
} // namespace test_unwinder
} // namespace internal
} // namespace v8
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