| // Copyright 2019 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/base/win32-headers.h" |
| #include "src/init/v8.h" |
| #include "test/cctest/cctest.h" |
| |
| #if defined(V8_OS_WIN_X64) |
| #define CONTEXT_PC(context) (context.Rip) |
| #elif defined(V8_OS_WIN_ARM64) |
| #define CONTEXT_PC(context) (context.Pc) |
| #endif |
| |
| class UnwindingWin64Callbacks { |
| public: |
| UnwindingWin64Callbacks() = default; |
| |
| static void Getter(v8::Local<v8::String> name, |
| const v8::PropertyCallbackInfo<v8::Value>& info) { |
| // Expects to find at least 15 stack frames in the call stack. |
| // The stack walking should fail on stack frames for builtin functions if |
| // stack unwinding data has not been correctly registered. |
| int stack_frames = CountCallStackFrames(15); |
| CHECK_GE(stack_frames, 15); |
| } |
| static void Setter(v8::Local<v8::String> name, v8::Local<v8::Value> value, |
| const v8::PropertyCallbackInfo<void>& info) {} |
| |
| private: |
| // Windows-specific code to walk the stack starting from the current |
| // instruction pointer. |
| static int CountCallStackFrames(int max_frames) { |
| CONTEXT context_record; |
| ::RtlCaptureContext(&context_record); |
| |
| int iframe = 0; |
| while (++iframe < max_frames) { |
| uint64_t image_base; |
| PRUNTIME_FUNCTION function_entry = ::RtlLookupFunctionEntry( |
| CONTEXT_PC(context_record), &image_base, nullptr); |
| if (!function_entry) break; |
| |
| void* handler_data; |
| uint64_t establisher_frame; |
| ::RtlVirtualUnwind(UNW_FLAG_NHANDLER, image_base, |
| CONTEXT_PC(context_record), function_entry, |
| &context_record, &handler_data, &establisher_frame, |
| NULL); |
| } |
| return iframe; |
| } |
| }; |
| |
| // Verifies that stack unwinding data has been correctly registered on Win64. |
| UNINITIALIZED_TEST(StackUnwindingWin64) { |
| #ifdef V8_WIN64_UNWINDING_INFO |
| |
| static const char* unwinding_win64_test_source = |
| "function start(count) {\n" |
| " for (var i = 0; i < count; i++) {\n" |
| " var o = instance.foo;\n" |
| " instance.foo = o + 1;\n" |
| " }\n" |
| "};\n" |
| "%PrepareFunctionForOptimization(start);\n"; |
| |
| // This test may fail on Windows 7 |
| if (!::IsWindows8OrGreater()) { |
| return; |
| } |
| |
| i::FLAG_allow_natives_syntax = true; |
| i::FLAG_win64_unwinding_info = true; |
| |
| v8::Isolate::CreateParams create_params; |
| create_params.array_buffer_allocator = CcTest::array_buffer_allocator(); |
| v8::Isolate* isolate = v8::Isolate::New(create_params); |
| isolate->Enter(); |
| { |
| v8::HandleScope scope(isolate); |
| LocalContext env(isolate); |
| |
| v8::Local<v8::FunctionTemplate> func_template = |
| v8::FunctionTemplate::New(isolate); |
| v8::Local<v8::ObjectTemplate> instance_template = |
| func_template->InstanceTemplate(); |
| |
| UnwindingWin64Callbacks accessors; |
| v8::Local<v8::External> data = v8::External::New(isolate, &accessors); |
| instance_template->SetAccessor(v8_str("foo"), |
| &UnwindingWin64Callbacks::Getter, |
| &UnwindingWin64Callbacks::Setter, data); |
| v8::Local<v8::Function> func = |
| func_template->GetFunction(env.local()).ToLocalChecked(); |
| v8::Local<v8::Object> instance = |
| func->NewInstance(env.local()).ToLocalChecked(); |
| env->Global()->Set(env.local(), v8_str("instance"), instance).FromJust(); |
| |
| CompileRun(unwinding_win64_test_source); |
| v8::Local<v8::Function> function = v8::Local<v8::Function>::Cast( |
| env->Global()->Get(env.local(), v8_str("start")).ToLocalChecked()); |
| |
| CompileRun("start(1); %OptimizeFunctionOnNextCall(start);"); |
| |
| int32_t repeat_count = 100; |
| v8::Local<v8::Value> args[] = {v8::Integer::New(isolate, repeat_count)}; |
| function->Call(env.local(), env.local()->Global(), arraysize(args), args) |
| .ToLocalChecked(); |
| } |
| isolate->Exit(); |
| isolate->Dispose(); |
| |
| #endif // V8_WIN64_UNWINDING_INFO |
| } |
| |
| #undef CONTEXT_PC |