#include "include/v8-function.h"
#include "include/v8-isolate.h"
#include "include/v8-local-handle.h"
#include "include/v8-unwinder-state.h"
#include "src/api/api-inl.h"
#include "src/builtins/builtins.h"
#include "src/execution/isolate.h"
#include "src/heap/spaces.h"
#include "src/objects/code-inl.h"
#include "test/cctest/cctest.h"
namespace v8 {
namespace internal {
namespace test_unwinder_code_pages {
namespace {
#define CHECK_EQ_VALUE_REGISTER …
#ifdef V8_TARGET_ARCH_X64
constexpr int kJSEntryFrameSpace = …;
constexpr int kFPOffset = …;
constexpr int kPCOffset = …;
constexpr int kSPOffset = …;
void BuildJSEntryStack(uintptr_t* stack) { … }
void CheckCalleeSavedRegisters(const RegisterState& register_state) { … }
#elif V8_TARGET_ARCH_ARM
constexpr int kJSEntryFrameSpace = 26;
constexpr int kFPOffset = 0;
constexpr int kPCOffset = 1;
constexpr int kSPOffset = 25;
void BuildJSEntryStack(uintptr_t* stack) {
stack[0] = reinterpret_cast<uintptr_t>(stack);
stack[1] = 100;
for (int i = 0; i < 8; ++i) {
stack[2 + i * 2] = 0;
stack[2 + i * 2 + 1] = 150 + i;
}
for (int i = 0; i < 7; ++i) {
stack[18 + i] = 160 + i;
}
stack[25] = reinterpret_cast<uintptr_t>(stack + 25);
}
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
constexpr int kJSEntryFrameSpace = 21;
constexpr int kFPOffset = 0;
constexpr int kPCOffset = 1;
constexpr int kSPOffset = 20;
void BuildJSEntryStack(uintptr_t* stack) {
stack[0] = reinterpret_cast<uintptr_t>(stack);
stack[1] = 100;
for (int i = 0; i < 10; ++i) {
stack[2 + i] = 150 + i;
}
for (int i = 0; i < 8; ++i) {
stack[12 + i] = 160 + i;
}
stack[20] = reinterpret_cast<uintptr_t>(stack + 20);
}
void CheckCalleeSavedRegisters(const RegisterState& register_state) {}
#else
constexpr int kJSEntryFrameSpace = 1;
constexpr int kFPOffset = 0;
constexpr int kPCOffset = 0;
constexpr int kSPOffset = 0;
void BuildJSEntryStack(uintptr_t* stack) { UNREACHABLE(); }
void CheckCalleeSavedRegisters(const RegisterState& register_state) {
UNREACHABLE();
}
#endif
}
static const void* fake_stack_base = …;
TEST(Unwind_BadState_Fail_CodePagesAPI) { … }
TEST(Unwind_BuiltinPCInMiddle_Success_CodePagesAPI) { … }
TEST(Unwind_BuiltinPCAtStart_Success_CodePagesAPI) { … }
const char* foo_source = …;
bool PagesContainsAddress(size_t length, MemoryRange* pages,
Address search_address) { … }
TEST(Unwind_CodeObjectPCInMiddle_Success_CodePagesAPI) { … }
TEST(Unwind_JSEntryBeforeFrame_Fail_CodePagesAPI) { … }
TEST(Unwind_TwoJSFrames_Success_CodePagesAPI) { … }
TEST(Unwind_JSEntry_Fail_CodePagesAPI) { … }
TEST(Unwind_StackBounds_Basic_CodePagesAPI) { … }
TEST(Unwind_StackBounds_WithUnwinding_CodePagesAPI) { … }
TEST(PCIsInV8_BadState_Fail_CodePagesAPI) { … }
TEST(PCIsInV8_ValidStateNullPC_Fail_CodePagesAPI) { … }
void TestRangeBoundaries(size_t pages_length, MemoryRange* code_pages,
uint8_t* range_start, size_t range_length) { … }
TEST(PCIsInV8_InAllCodePages_CodePagesAPI) { … }
TEST(PCIsInV8_InJSEntryRange_CodePagesAPI) { … }
TEST(PCIsInV8_LargeCodeObject_CodePagesAPI) { … }
#ifdef USE_SIMULATOR
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>& info) {
CHECK(i::ValidateCallbackInfo(info));
instance_->DoTryUnwind();
}
void DoTryUnwind() {
v8::RegisterState register_state;
SimulatorHelper simulator_helper;
if (!simulator_helper.Init(isolate_)) return;
simulator_helper.FillRegisters(®ister_state);
register_state.pc = register_state.lr;
JSEntryStubs entry_stubs = isolate_->GetJSEntryStubs();
MemoryRange code_pages[v8::Isolate::kMinCodePagesBufferSize];
size_t pages_length =
isolate_->CopyCodePages(arraysize(code_pages), code_pages);
CHECK_LE(pages_length, arraysize(code_pages));
void* stack_base = reinterpret_cast<void*>(0xffffffffffffffffL);
bool unwound = v8::Unwinder::TryUnwindV8Frames(
entry_stubs, pages_length, code_pages, ®ister_state, stack_base);
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_CodePagesAPI) {
i::v8_flags.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
#undef CHECK_EQ_VALUE_REGISTER
}
}
}