Commit 3cda21de authored by Paolo Severini's avatar Paolo Severini Committed by Commit Bot

V8 x64 backend doesn't emit ABI compliant stack frames

On 64 bit Windows, the OS stack walking does not work because the V8 x64
backend doesn't emit unwinding info and also because it doesn't emit ABI
compliant stack frames. See
https://docs.google.com/document/d/1-wf50jFlii0c_Pr52lm2ZU-49m220nhYMrHDi3vXnh0/edit
for more details.

This problem can be fixed by observing that V8 frames usually all have the same
prolog and epilog:

push rbp,
mov rbp, rsp
...
pop rbp
ret N

and that it is possible to define XDATA (UNWIND_CODEs) that specify how Windows
should walk through V8 frames. Furthermore, since V8 Code objects are all
allocated in the same code-range for an Isolate, it is possible to register a
single PDATA/XDATA entry to cover stack walking for all the code generated
inside that code-range.

This PR contains changes required to enable stack walking on Win64:

EmbeddedFileWriter now adds assembler directives to the builtins
snapshot source file (embedded.cc) to emit additional entries in the .pdata and
in the .xdata section of the V8 executable. This takes care of stack walking
for embedded builtins. (The case of non-embedded builtins is not supported).
The x64 Assembler has been modified to collect the information required to emit
this unwind info for builtins.

Stack walking for jitted code is handled is Isolate.cpp, by registering
dynamically PDATA/XDATA for the whole code-range address space every time a new
Isolate is initialized, and by unregistering them when the Isolate is
destroyed.

Stack walking for WASM jitted code is handled is the same way in
wasm::NativeModule (wasm/wasm-code-manager.cpp).

It is important to note that Crashpad and Breakpad are already registering
PDATA/XDATA to manage and report unhandled exceptions (but not for embedded
builtins). Since it is not possible to register multiple PDATA entries for the
same address range, a new function is added to the V8 API:
SetUnhandledExceptionCallback() can be used by an embedder to register its own
unhandled exception handler for exceptions that arise in v8-generated code.
V8 embedders should be modified accordingly (code for this is in a separate PR
in the Chromium repository:
https://chromium-review.googlesource.com/c/chromium/src/+/1474703).

All these changes are experimental, behind:

the 'v8_win64_unwinding_info' build flag, and
the '--win64-unwinding-info' runtime flag.

Bug: v8:3598
Change-Id: Iea455ab6d0e2bf1c556aa1cf870841d44ab6e4b1
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1469329Reviewed-by: 's avatarMichael Starzinger <mstarzinger@chromium.org>
Reviewed-by: 's avatarJakob Gruber <jgruber@chromium.org>
Reviewed-by: 's avatarUlan Degenbaev <ulan@chromium.org>
Commit-Queue: Paolo Severini <paolosev@microsoft.com>
Cr-Commit-Position: refs/heads/master@{#60330}
parent a82d562f
......@@ -88,6 +88,9 @@ declare_args() {
# Enable embedded builtins.
v8_enable_embedded_builtins = true
# Enable the registration of unwinding info for Windows/x64.
v8_win64_unwinding_info = false
# Enable code comments for builtins in the snapshot (impacts performance).
v8_enable_snapshot_code_comments = false
......@@ -413,6 +416,9 @@ config("features") {
if (v8_enable_shared_ro_heap) {
defines += [ "V8_SHARED_RO_HEAP" ]
}
if (v8_win64_unwinding_info) {
defines += [ "V8_WIN64_UNWINDING_INFO" ]
}
}
config("toolchain") {
......@@ -1121,6 +1127,10 @@ template("run_mksnapshot") {
args += invoker.args
if (v8_win64_unwinding_info) {
args += [ "--win64-unwinding-info" ]
}
if (v8_enable_embedded_builtins) {
outputs += [ "$target_gen_dir/embedded${suffix}.S" ]
args += [
......@@ -2876,6 +2886,8 @@ v8_source_set("v8_base") {
"src/trap-handler/handler-inside-win.cc",
"src/trap-handler/handler-inside-win.h",
"src/trap-handler/handler-outside-win.cc",
"src/unwinding-info-win64.cc",
"src/unwinding-info-win64.h",
]
}
} else if (v8_current_cpu == "arm") {
......
......@@ -7166,6 +7166,13 @@ enum JitCodeEventOptions {
*/
typedef void (*JitCodeEventHandler)(const JitCodeEvent* event);
/**
* Callback function passed to SetUnhandledExceptionCallback.
*/
#if defined(V8_OS_WIN_X64)
typedef int (*UnhandledExceptionCallback)(
_EXCEPTION_POINTERS* exception_pointers);
#endif
/**
* Interface for iterating through all external resources in the heap.
......@@ -8380,13 +8387,13 @@ class V8_EXPORT Isolate {
/**
* Returns a memory range that can potentially contain jitted code. Code for
* V8's 'builtins' will not be in this range if embedded builtins is enabled.
* Instead, see GetEmbeddedCodeRange.
*
* On Win64, embedders are advised to install function table callbacks for
* these ranges, as default SEH won't be able to unwind through jitted code.
*
* The first page of the code range is reserved for the embedder and is
* committed, writable, and executable.
* committed, writable, and executable, to be used to store unwind data, as
* documented in
* https://docs.microsoft.com/en-us/cpp/build/exception-handling-x64.
*
* Might be empty on other platforms.
*
......@@ -8786,6 +8793,20 @@ class V8_EXPORT V8 {
*/
static bool EnableWebAssemblyTrapHandler(bool use_v8_signal_handler);
#if defined(V8_OS_WIN_X64)
/**
* On Win64, by default V8 does not emit unwinding data for jitted code,
* which means the OS cannot walk the stack frames and the system Structured
* Exception Handling (SEH) cannot unwind through V8-generated code:
* https://code.google.com/p/v8/issues/detail?id=3598.
*
* This function allows embedders to register a custom exception handler for
* exceptions in V8-generated code.
*/
static void SetUnhandledExceptionCallback(
UnhandledExceptionCallback unhandled_exception_callback);
#endif
private:
V8();
......
......@@ -364,6 +364,10 @@
# define V8_EXPORT
#endif // BUILDING_V8_SHARED
#if defined(_M_X64) || defined(__x86_64__)
# define V8_OS_WIN_X64 true
#endif
#else // V8_OS_WIN
// Setup for Linux shared library export.
......
......@@ -116,10 +116,14 @@
#endif
#if V8_OS_WIN
#include <versionhelpers.h>
#include <windows.h>
#include "include/v8-wasm-trap-handler-win.h"
#include "src/trap-handler/handler-inside-win.h"
#endif
#if V8_TARGET_ARCH_X64
#include "src/unwinding-info-win64.h"
#endif // V8_TARGET_ARCH_X64
#endif // V8_OS_WIN
namespace v8 {
......@@ -5787,6 +5791,14 @@ bool V8::EnableWebAssemblyTrapHandler(bool use_v8_signal_handler) {
return v8::internal::trap_handler::EnableTrapHandler(use_v8_signal_handler);
}
#if defined(V8_OS_WIN_X64)
void V8::SetUnhandledExceptionCallback(
UnhandledExceptionCallback unhandled_exception_callback) {
v8::internal::win64_unwindinfo::SetUnhandledExceptionCallback(
unhandled_exception_callback);
}
#endif
void v8::V8::SetEntropySource(EntropySource entropy_source) {
base::RandomNumberGenerator::SetEntropySource(entropy_source);
}
......
......@@ -166,6 +166,10 @@ struct V8_EXPORT_PRIVATE AssemblerOptions {
// this flag, the code range must be small enough to fit all offsets into
// the instruction immediates.
bool use_pc_relative_calls_and_jumps = false;
// Enables the collection of information useful for the generation of unwind
// info. This is useful in some platform (Win64) where the unwind info depends
// on a function prologue/epilogue.
bool collect_win64_unwind_info = false;
// Constructs V8-agnostic set of options from current state.
AssemblerOptions EnableV8AgnosticCode() const;
......
......@@ -45,8 +45,9 @@
#define _WIN32_WINNT 0x501
#endif // __MINGW32__
#if !defined(__MINGW32__) || defined(__MINGW64_VERSION_MAJOR)
#include <dbghelp.h> // For SymLoadModule64 and al.
#include <errno.h> // For STRUNCATE
#include <dbghelp.h> // For SymLoadModule64 and al.
#include <errno.h> // For STRUNCATE
#include <versionhelpers.h> // For IsWindows8OrGreater().
#endif // !defined(__MINGW32__) || defined(__MINGW64_VERSION_MAJOR)
#include <limits.h> // For INT_MAX and al.
#include <tlhelp32.h> // For Module32First and al.
......
......@@ -41,6 +41,7 @@ AssemblerOptions BuiltinAssemblerOptions(Isolate* isolate,
AssemblerOptions options = AssemblerOptions::Default(isolate);
CHECK(!options.isolate_independent_code);
CHECK(!options.use_pc_relative_calls_and_jumps);
CHECK(!options.collect_win64_unwind_info);
if (!isolate->IsGeneratingEmbeddedBuiltins() ||
!Builtins::IsIsolateIndependent(builtin_index)) {
......@@ -56,6 +57,7 @@ AssemblerOptions BuiltinAssemblerOptions(Isolate* isolate,
options.isolate_independent_code = true;
options.use_pc_relative_calls_and_jumps = pc_relative_calls_fit_in_code_range;
options.collect_win64_unwind_info = true;
return options;
}
......@@ -126,6 +128,9 @@ Code BuildWithMacroAssembler(Isolate* isolate, int32_t builtin_index,
desc, Code::BUILTIN, masm.CodeObject(), builtin_index,
MaybeHandle<ByteArray>(), DeoptimizationData::Empty(isolate), kMovable,
kIsNotTurbofanned, kStackSlots);
#if defined(V8_OS_WIN_X64)
isolate->SetBuiltinUnwindData(builtin_index, masm.GetUnwindInfo());
#endif
PostBuildProfileAndTracing(isolate, *code, s_name);
return *code;
}
......
......@@ -396,6 +396,14 @@ MaybeHandle<Code> CodeGenerator::FinalizeCode() {
// Allocate and install the code.
CodeDesc desc;
tasm()->GetCode(isolate(), &desc, safepoints(), handler_table_offset_);
#if defined(V8_OS_WIN_X64)
if (Builtins::IsBuiltinId(info_->builtin_index())) {
isolate_->SetBuiltinUnwindData(info_->builtin_index(),
tasm()->GetUnwindInfo());
}
#endif
if (unwinding_info_writer_.eh_frame_writer()) {
unwinding_info_writer_.eh_frame_writer()->GetEhFrame(&desc);
}
......
......@@ -1395,6 +1395,9 @@ DEFINE_STRING(redirect_code_traces_to, nullptr,
DEFINE_BOOL(print_opt_source, false,
"print source code of optimized and inlined functions")
DEFINE_BOOL(win64_unwinding_info, false,
"Enable unwinding info for Windows/x64 (experimental).")
#ifdef V8_TARGET_ARCH_ARM
// Unsupported on arm. See https://crbug.com/v8/8713.
DEFINE_BOOL_READONLY(
......
......@@ -2499,6 +2499,10 @@ int Heap::GetFillToAlign(Address address, AllocationAlignment alignment) {
return 0;
}
size_t Heap::GetCodeRangeReservedAreaSize() {
return kReservedCodeRangePages * MemoryAllocator::GetCommitPageSize();
}
HeapObject Heap::PrecedeWithFiller(HeapObject object, int filler_size) {
CreateFillerObjectAt(object->address(), filler_size, ClearRecordedSlots::kNo);
return HeapObject::FromAddress(object->address() + filler_size);
......
......@@ -276,6 +276,10 @@ class Heap {
// given alignment.
static int GetFillToAlign(Address address, AllocationAlignment alignment);
// Returns the size of the initial area of a code-range, which is marked
// writable and reserved to contain unwind information.
static size_t GetCodeRangeReservedAreaSize();
void FatalProcessOutOfMemory(const char* location);
// Checks whether the space is valid.
......
......@@ -84,6 +84,10 @@
#include "unicode/uobject.h"
#endif // V8_INTL_SUPPORT
#if defined(V8_OS_WIN_X64)
#include "src/unwinding-info-win64.h"
#endif
extern "C" const uint8_t* v8_Default_embedded_blob_;
extern "C" uint32_t v8_Default_embedded_blob_size_;
......@@ -2925,6 +2929,16 @@ void Isolate::Deinit() {
heap_profiler()->StopSamplingHeapProfiler();
}
#if defined(V8_OS_WIN_X64)
if (win64_unwindinfo::CanRegisterUnwindInfoForNonABICompliantCodeRange() &&
heap()->memory_allocator()) {
const base::AddressRegion& code_range =
heap()->memory_allocator()->code_range();
void* start = reinterpret_cast<void*>(code_range.begin());
win64_unwindinfo::UnregisterNonABICompliantCodeRange(start);
}
#endif
debug()->Unload();
wasm_engine()->DeleteCompileJobsOnIsolate(this);
......@@ -3495,6 +3509,16 @@ bool Isolate::Init(ReadOnlyDeserializer* read_only_deserializer,
sampling_flags);
}
#if defined(V8_OS_WIN_X64)
if (win64_unwindinfo::CanRegisterUnwindInfoForNonABICompliantCodeRange()) {
const base::AddressRegion& code_range =
heap()->memory_allocator()->code_range();
void* start = reinterpret_cast<void*>(code_range.begin());
size_t size_in_bytes = code_range.size();
win64_unwindinfo::RegisterNonABICompliantCodeRange(start, size_in_bytes);
}
#endif
if (create_heap_objects && FLAG_profile_deserialization) {
double ms = timer.Elapsed().InMillisecondsF();
PrintF("[Initializing isolate from scratch took %0.3f ms]\n", ms);
......@@ -4303,6 +4327,16 @@ void Isolate::PrepareBuiltinSourcePositionMap() {
}
}
#if defined(V8_OS_WIN_X64)
void Isolate::SetBuiltinUnwindData(
int builtin_index,
const win64_unwindinfo::BuiltinUnwindInfo& unwinding_info) {
if (embedded_file_writer_ != nullptr) {
embedded_file_writer_->SetBuiltinUnwindData(builtin_index, unwinding_info);
}
}
#endif
void Isolate::SetPrepareStackTraceCallback(PrepareStackTraceCallback callback) {
prepare_stack_trace_callback_ = callback;
}
......
......@@ -114,6 +114,10 @@ namespace wasm {
class WasmEngine;
}
namespace win64_unwindinfo {
class BuiltinUnwindInfo;
}
#define RETURN_FAILURE_IF_SCHEDULED_EXCEPTION(isolate) \
do { \
Isolate* __isolate__ = (isolate); \
......@@ -1442,6 +1446,12 @@ class Isolate final : private HiddenFactory {
// annotate the builtin blob with debugging information.
void PrepareBuiltinSourcePositionMap();
#if defined(V8_OS_WIN_X64)
void SetBuiltinUnwindData(
int builtin_index,
const win64_unwindinfo::BuiltinUnwindInfo& unwinding_info);
#endif
void SetPrepareStackTraceCallback(PrepareStackTraceCallback callback);
MaybeHandle<Object> RunPrepareStackTraceCallback(Handle<Context>,
Handle<JSObject> Error,
......
......@@ -127,6 +127,119 @@ void EmbeddedFileWriter::PrepareBuiltinSourcePositionMap(Builtins* builtins) {
}
}
#if defined(V8_OS_WIN_X64)
std::string EmbeddedFileWriter::BuiltinsUnwindInfoLabel() const {
char embedded_blob_data_symbol[kTemporaryStringLength];
i::SNPrintF(i::Vector<char>(embedded_blob_data_symbol),
"%s_Builtins_UnwindInfo", embedded_variant_);
return embedded_blob_data_symbol;
}
void EmbeddedFileWriter::SetBuiltinUnwindData(
int builtin_index, const win64_unwindinfo::BuiltinUnwindInfo& unwind_info) {
DCHECK_LT(builtin_index, Builtins::builtin_count);
unwind_infos_[builtin_index] = unwind_info;
}
void EmbeddedFileWriter::WriteUnwindInfoEntry(
PlatformDependentEmbeddedFileWriter* w, uint64_t rva_start,
uint64_t rva_end) const {
w->DeclareRvaToSymbol(EmbeddedBlobDataSymbol().c_str(), rva_start);
w->DeclareRvaToSymbol(EmbeddedBlobDataSymbol().c_str(), rva_end);
w->DeclareRvaToSymbol(BuiltinsUnwindInfoLabel().c_str());
}
void EmbeddedFileWriter::WriteUnwindInfo(PlatformDependentEmbeddedFileWriter* w,
const i::EmbeddedData* blob) const {
// Emit an UNWIND_INFO (XDATA) struct, which contains the unwinding
// information that is used for all builtin functions.
DCHECK(win64_unwindinfo::CanEmitUnwindInfoForBuiltins());
w->Comment("xdata for all the code in the embedded blob.");
w->DeclareExternalFunction(CRASH_HANDLER_FUNCTION_NAME_STRING);
w->StartXdataSection();
{
w->DeclareLabel(BuiltinsUnwindInfoLabel().c_str());
std::vector<uint8_t> xdata =
win64_unwindinfo::GetUnwindInfoForBuiltinFunctions();
WriteBinaryContentsAsInlineAssembly(w, xdata.data(),
static_cast<uint32_t>(xdata.size()));
w->Comment(" ExceptionHandler");
w->DeclareRvaToSymbol(CRASH_HANDLER_FUNCTION_NAME_STRING);
}
w->EndXdataSection();
w->Newline();
// Emit a RUNTIME_FUNCTION (PDATA) entry for each builtin function, as
// documented here:
// https://docs.microsoft.com/en-us/cpp/build/exception-handling-x64.
w->Comment(
"pdata for all the code in the embedded blob (structs of type "
"RUNTIME_FUNCTION).");
w->Comment(" BeginAddress");
w->Comment(" EndAddress");
w->Comment(" UnwindInfoAddress");
w->StartPdataSection();
{
Address prev_builtin_end_offset = 0;
for (int i = 0; i < Builtins::builtin_count; i++) {
// Some builtins are leaf functions from the point of view of Win64 stack
// walking: they do not move the stack pointer and do not require a PDATA
// entry because the return address can be retrieved from [rsp].
if (!blob->ContainsBuiltin(i)) continue;
if (unwind_infos_[i].is_leaf_function()) continue;
uint64_t builtin_start_offset = blob->InstructionStartOfBuiltin(i) -
reinterpret_cast<Address>(blob->data());
uint32_t builtin_size = blob->InstructionSizeOfBuiltin(i);
const std::vector<int>& xdata_desc = unwind_infos_[i].fp_offsets();
if (xdata_desc.empty()) {
// Some builtins do not have any "push rbp - mov rbp, rsp" instructions
// to start a stack frame. We still emit a PDATA entry as if they had,
// relying on the fact that we can find the previous frame address from
// rbp in most cases. Note that since the function does not really start
// with a 'push rbp' we need to specify the start RVA in the PDATA entry
// a few bytes before the beginning of the function, if it does not
// overlap the end of the previous builtin.
WriteUnwindInfoEntry(
w,
std::max(prev_builtin_end_offset,
builtin_start_offset - win64_unwindinfo::kRbpPrefixLength),
builtin_start_offset + builtin_size);
} else {
// Some builtins have one or more "push rbp - mov rbp, rsp" sequences,
// but not necessarily at the beginning of the function. In this case
// we want to yield a PDATA entry for each block of instructions that
// emit an rbp frame. If the function does not start with 'push rbp'
// we also emit a PDATA entry for the initial block of code up to the
// first 'push rbp', like in the case above.
if (xdata_desc[0] > 0) {
WriteUnwindInfoEntry(w,
std::max(prev_builtin_end_offset,
builtin_start_offset -
win64_unwindinfo::kRbpPrefixLength),
builtin_start_offset + xdata_desc[0]);
}
for (size_t j = 0; j < xdata_desc.size(); j++) {
int chunk_start = xdata_desc[j];
int chunk_end =
(j < xdata_desc.size() - 1) ? xdata_desc[j + 1] : builtin_size;
WriteUnwindInfoEntry(w, builtin_start_offset + chunk_start,
builtin_start_offset + chunk_end);
}
}
prev_builtin_end_offset = builtin_start_offset + builtin_size;
w->Newline();
}
}
w->EndPdataSection();
w->Newline();
}
#endif
// V8_OS_MACOSX
// Fuchsia target is explicitly excluded here for Mac hosts. This is to avoid
// generating uncompilable assembly files for the Fuchsia target.
......@@ -347,6 +460,42 @@ void PlatformDependentEmbeddedFileWriter::DeclarePointerToSymbol(
DirectiveAsString(PointerSizeDirective()), SYMBOL_PREFIX, target);
}
#if defined(V8_OS_WIN_X64)
void PlatformDependentEmbeddedFileWriter::StartPdataSection() {
fprintf(fp_, "OPTION DOTNAME\n");
fprintf(fp_, ".pdata SEGMENT DWORD READ ''\n");
}
void PlatformDependentEmbeddedFileWriter::EndPdataSection() {
fprintf(fp_, ".pdata ENDS\n");
}
void PlatformDependentEmbeddedFileWriter::StartXdataSection() {
fprintf(fp_, "OPTION DOTNAME\n");
fprintf(fp_, ".xdata SEGMENT DWORD READ ''\n");
}
void PlatformDependentEmbeddedFileWriter::EndXdataSection() {
fprintf(fp_, ".xdata ENDS\n");
}
void PlatformDependentEmbeddedFileWriter::DeclareExternalFunction(
const char* name) {
fprintf(fp_, "EXTERN %s : PROC\n", name);
}
void PlatformDependentEmbeddedFileWriter::DeclareRvaToSymbol(const char* name,
uint64_t offset) {
if (offset > 0) {
fprintf(fp_, "DD IMAGEREL %s+%llu\n", name, offset);
} else {
fprintf(fp_, "DD IMAGEREL %s\n", name);
}
}
#endif // defined(V8_OS_WIN_X64)
void PlatformDependentEmbeddedFileWriter::DeclareSymbolGlobal(
const char* name) {
fprintf(fp_, "PUBLIC %s%s\n", SYMBOL_PREFIX, name);
......@@ -557,6 +706,34 @@ void PlatformDependentEmbeddedFileWriter::DeclarePointerToSymbol(
SYMBOL_PREFIX, target);
}
#if defined(V8_OS_WIN_X64)
void PlatformDependentEmbeddedFileWriter::StartPdataSection() {
fprintf(fp_, ".section .pdata\n");
}
void PlatformDependentEmbeddedFileWriter::EndPdataSection() {}
void PlatformDependentEmbeddedFileWriter::StartXdataSection() {
fprintf(fp_, ".section .xdata\n");
}
void PlatformDependentEmbeddedFileWriter::EndXdataSection() {}
void PlatformDependentEmbeddedFileWriter::DeclareExternalFunction(
const char* name) {}
void PlatformDependentEmbeddedFileWriter::DeclareRvaToSymbol(const char* name,
uint64_t offset) {
if (offset > 0) {
fprintf(fp_, ".rva %s + %llu\n", name, offset);
} else {
fprintf(fp_, ".rva %s\n", name);
}
}
#endif // defined(V8_OS_WIN_X64)
void PlatformDependentEmbeddedFileWriter::DeclareSymbolGlobal(
const char* name) {
fprintf(fp_, ".global %s%s\n", SYMBOL_PREFIX, name);
......
......@@ -12,6 +12,10 @@
#include "src/snapshot/snapshot.h"
#include "src/source-position-table.h"
#if defined(V8_OS_WIN_X64)
#include "src/unwinding-info-win64.h"
#endif
namespace v8 {
namespace internal {
......@@ -41,6 +45,18 @@ class PlatformDependentEmbeddedFileWriter final {
void DeclareUint32(const char* name, uint32_t value);
void DeclarePointerToSymbol(const char* name, const char* target);
#if defined(V8_OS_WIN_X64)
void StartPdataSection();
void EndPdataSection();
void StartXdataSection();
void EndXdataSection();
void DeclareExternalFunction(const char* name);
// Emits an RVA (address relative to the module load address) specified as an
// offset from a given symbol.
void DeclareRvaToSymbol(const char* name, uint64_t offset = 0);
#endif
void DeclareLabel(const char* name);
void SourceInfo(int fileid, int line);
......@@ -81,6 +97,12 @@ class EmbeddedFileWriterInterface {
// The isolate will call the method below just prior to replacing the
// compiled builtin Code objects with trampolines.
virtual void PrepareBuiltinSourcePositionMap(Builtins* builtins) = 0;
#if defined(V8_OS_WIN_X64)
virtual void SetBuiltinUnwindData(
int builtin_index,
const win64_unwindinfo::BuiltinUnwindInfo& unwinding_info) = 0;
#endif
};
// Generates the embedded.S file which is later compiled into the final v8
......@@ -121,6 +143,12 @@ class EmbeddedFileWriter : public EmbeddedFileWriterInterface {
void PrepareBuiltinSourcePositionMap(Builtins* builtins) override;
#if defined(V8_OS_WIN_X64)
void SetBuiltinUnwindData(
int builtin_index,
const win64_unwindinfo::BuiltinUnwindInfo& unwinding_info) override;
#endif
void SetEmbeddedFile(const char* embedded_src_path) {
embedded_src_path_ = embedded_src_path;
}
......@@ -183,17 +211,20 @@ class EmbeddedFileWriter : public EmbeddedFileWriterInterface {
// Fairly arbitrary but should fit all symbol names.
static constexpr int kTemporaryStringLength = 256;
void WriteMetadataSection(PlatformDependentEmbeddedFileWriter* w,
const i::EmbeddedData* blob) const {
std::string EmbeddedBlobDataSymbol() const {
char embedded_blob_data_symbol[kTemporaryStringLength];
i::SNPrintF(i::Vector<char>(embedded_blob_data_symbol),
"v8_%s_embedded_blob_data_", embedded_variant_);
return embedded_blob_data_symbol;
}
void WriteMetadataSection(PlatformDependentEmbeddedFileWriter* w,
const i::EmbeddedData* blob) const {
w->Comment("The embedded blob starts here. Metadata comes first, followed");
w->Comment("by builtin instruction streams.");
w->SectionText();
w->AlignToCodeAlignment();
w->DeclareLabel(embedded_blob_data_symbol);
w->DeclareLabel(EmbeddedBlobDataSymbol().c_str());
WriteBinaryContentsAsInlineAssembly(w, blob->data(),
i::EmbeddedData::RawDataOffset());
......@@ -263,10 +294,6 @@ class EmbeddedFileWriter : public EmbeddedFileWriterInterface {
void WriteFileEpilogue(PlatformDependentEmbeddedFileWriter* w,
const i::EmbeddedData* blob) const {
{
char embedded_blob_data_symbol[kTemporaryStringLength];
i::SNPrintF(i::Vector<char>(embedded_blob_data_symbol),
"v8_%s_embedded_blob_data_", embedded_variant_);
char embedded_blob_symbol[kTemporaryStringLength];
i::SNPrintF(i::Vector<char>(embedded_blob_symbol), "v8_%s_embedded_blob_",
embedded_variant_);
......@@ -275,7 +302,7 @@ class EmbeddedFileWriter : public EmbeddedFileWriterInterface {
w->SectionData();
w->AlignToDataAlignment();
w->DeclarePointerToSymbol(embedded_blob_symbol,
embedded_blob_data_symbol);
EmbeddedBlobDataSymbol().c_str());
w->Newline();
}
......@@ -291,9 +318,23 @@ class EmbeddedFileWriter : public EmbeddedFileWriterInterface {
w->Newline();
}
#if defined(V8_OS_WIN_X64)
if (win64_unwindinfo::CanEmitUnwindInfoForBuiltins()) {
WriteUnwindInfo(w, blob);
}
#endif
w->FileEpilogue();
}
#if defined(V8_OS_WIN_X64)
std::string BuiltinsUnwindInfoLabel() const;
void WriteUnwindInfo(PlatformDependentEmbeddedFileWriter* w,
const i::EmbeddedData* blob) const;
void WriteUnwindInfoEntry(PlatformDependentEmbeddedFileWriter* w,
uint64_t rva_start, uint64_t rva_end) const;
#endif
#if defined(_MSC_VER) && !defined(__clang__)
#define V8_COMPILER_IS_MSVC
#endif
......@@ -411,6 +452,10 @@ class EmbeddedFileWriter : public EmbeddedFileWriterInterface {
std::vector<byte> source_positions_[Builtins::builtin_count];
#if defined(V8_OS_WIN_X64)
win64_unwindinfo::BuiltinUnwindInfo unwind_infos_[Builtins::builtin_count];
#endif
// In assembly directives, filename ids need to begin with 1.
static const int kFirstExternalFilenameId = 1;
std::map<const char*, int> external_filenames_;
......
// 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/unwinding-info-win64.h"
#if defined(V8_OS_WIN_X64)
#include "src/allocation.h"
#include "src/macro-assembler.h"
#include "src/x64/assembler-x64.h"
namespace v8 {
namespace internal {
namespace win64_unwindinfo {
bool CanEmitUnwindInfoForBuiltins() { return FLAG_win64_unwinding_info; }
bool CanRegisterUnwindInfoForNonABICompliantCodeRange() {
return !FLAG_jitless;
}
bool RegisterUnwindInfoForExceptionHandlingOnly() {
return !CanEmitUnwindInfoForBuiltins() || !IsWindows8OrGreater();
}
#pragma pack(push, 1)
/*
* From Windows SDK ehdata.h, which does not compile with Clang.
* See https://msdn.microsoft.com/en-us/library/ddssxxy8.aspx.
*/
typedef union _UNWIND_CODE {
struct {
unsigned char CodeOffset;
unsigned char UnwindOp : 4;
unsigned char OpInfo : 4;
};
uint16_t FrameOffset;
} UNWIND_CODE, *PUNWIND_CODE;
typedef struct _UNWIND_INFO {
unsigned char Version : 3;
unsigned char Flags : 5;
unsigned char SizeOfProlog;
unsigned char CountOfCodes;
unsigned char FrameRegister : 4;
unsigned char FrameOffset : 4;
} UNWIND_INFO, *PUNWIND_INFO;
struct V8UnwindData {
UNWIND_INFO unwind_info;
UNWIND_CODE unwind_codes[2];
V8UnwindData() {
static constexpr int kOpPushNonvol = 0;
static constexpr int kOpSetFPReg = 3;
unwind_info.Version = 1;
unwind_info.Flags = UNW_FLAG_EHANDLER;
unwind_info.SizeOfProlog = kRbpPrefixLength;
unwind_info.CountOfCodes = kRbpPrefixCodes;
unwind_info.FrameRegister = rbp.code();
unwind_info.FrameOffset = 0;
unwind_codes[0].CodeOffset = kRbpPrefixLength; // movq rbp, rsp
unwind_codes[0].UnwindOp = kOpSetFPReg;
unwind_codes[0].OpInfo = 0;
unwind_codes[1].CodeOffset = kPushRbpInstructionLength; // push rbp
unwind_codes[1].UnwindOp = kOpPushNonvol;
unwind_codes[1].OpInfo = rbp.code();
}
};
struct ExceptionHandlerUnwindData {
UNWIND_INFO unwind_info;
ExceptionHandlerUnwindData() {
unwind_info.Version = 1;
unwind_info.Flags = UNW_FLAG_EHANDLER;
unwind_info.SizeOfProlog = 0;
unwind_info.CountOfCodes = 0;
unwind_info.FrameRegister = 0;
unwind_info.FrameOffset = 0;
}
};
#pragma pack(pop)
v8::UnhandledExceptionCallback unhandled_exception_callback_g = nullptr;
void SetUnhandledExceptionCallback(
v8::UnhandledExceptionCallback unhandled_exception_callback) {
unhandled_exception_callback_g = unhandled_exception_callback;
}
// This function is registered as exception handler for V8-generated code as
// part of the registration of unwinding info. It is referenced by
// RegisterNonABICompliantCodeRange(), below, and by the unwinding info for
// builtins declared in the embedded blob.
extern "C" int CRASH_HANDLER_FUNCTION_NAME(
PEXCEPTION_RECORD ExceptionRecord, ULONG64 EstablisherFrame,
PCONTEXT ContextRecord, PDISPATCHER_CONTEXT DispatcherContext) {
if (unhandled_exception_callback_g != nullptr) {
EXCEPTION_POINTERS info = {ExceptionRecord, ContextRecord};
return unhandled_exception_callback_g(&info);
}
return EXCEPTION_CONTINUE_SEARCH;
}
static constexpr int kMaxExceptionThunkSize = 12;
struct CodeRangeUnwindingRecord {
RUNTIME_FUNCTION runtime_function;
V8UnwindData unwind_info;
uint32_t exception_handler;
uint8_t exception_thunk[kMaxExceptionThunkSize];
void* dynamic_table;
};
struct ExceptionHandlerRecord {
RUNTIME_FUNCTION runtime_function;
ExceptionHandlerUnwindData unwind_info;
uint32_t exception_handler;
uint8_t exception_thunk[kMaxExceptionThunkSize];
};
static decltype(
&::RtlAddGrowableFunctionTable) add_growable_function_table_func = nullptr;
static decltype(
&::RtlDeleteGrowableFunctionTable) delete_growable_function_table_func =
nullptr;
namespace {
void LoadNtdllUnwindingFunctions() {
static bool loaded = false;
if (loaded) {
return;
}
loaded = true;
// Load functions from the ntdll.dll module.
HMODULE ntdll_module =
LoadLibraryEx(L"ntdll.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32);
DCHECK_NOT_NULL(ntdll_module);
// This fails on Windows 7.
add_growable_function_table_func =
reinterpret_cast<decltype(&::RtlAddGrowableFunctionTable)>(
::GetProcAddress(ntdll_module, "RtlAddGrowableFunctionTable"));
DCHECK_IMPLIES(IsWindows8OrGreater(), add_growable_function_table_func);
delete_growable_function_table_func =
reinterpret_cast<decltype(&::RtlDeleteGrowableFunctionTable)>(
::GetProcAddress(ntdll_module, "RtlDeleteGrowableFunctionTable"));
DCHECK_IMPLIES(IsWindows8OrGreater(), delete_growable_function_table_func);
}
bool AddGrowableFunctionTable(PVOID* DynamicTable,
PRUNTIME_FUNCTION FunctionTable, DWORD EntryCount,
DWORD MaximumEntryCount, ULONG_PTR RangeBase,
ULONG_PTR RangeEnd) {
DCHECK(::IsWindows8OrGreater());
LoadNtdllUnwindingFunctions();
DCHECK_NOT_NULL(add_growable_function_table_func);
*DynamicTable = nullptr;
DWORD status =
add_growable_function_table_func(DynamicTable, FunctionTable, EntryCount,
MaximumEntryCount, RangeBase, RangeEnd);
DCHECK((status == 0 && *DynamicTable != nullptr) ||
status == 0xC000009A); // STATUS_INSUFFICIENT_RESOURCES
return (status == 0);
}
void DeleteGrowableFunctionTable(PVOID dynamic_table) {
DCHECK(::IsWindows8OrGreater());
LoadNtdllUnwindingFunctions();
DCHECK_NOT_NULL(delete_growable_function_table_func);
delete_growable_function_table_func(dynamic_table);
}
} // namespace
std::vector<uint8_t> GetUnwindInfoForBuiltinFunctions() {
V8UnwindData xdata;
return std::vector<uint8_t>(
reinterpret_cast<uint8_t*>(&xdata),
reinterpret_cast<uint8_t*>(&xdata) + sizeof(xdata));
}
template <typename Record>
void InitUnwindingRecord(Record* record, size_t code_size_in_bytes) {
// We assume that the first page of the code range is executable and
// committed and reserved to contain PDATA/XDATA.
// All addresses are 32bit relative offsets to start.
record->runtime_function.BeginAddress = 0;
record->runtime_function.EndAddress = static_cast<DWORD>(code_size_in_bytes);
record->runtime_function.UnwindData = offsetof(Record, unwind_info);
record->exception_handler = offsetof(Record, exception_thunk);
// Hardcoded thunk.
MacroAssembler masm(AssemblerOptions{}, NewAssemblerBuffer(64));
masm.movq(rax, reinterpret_cast<uint64_t>(&CRASH_HANDLER_FUNCTION_NAME));
masm.jmp(rax);
DCHECK_GE(masm.buffer_size(), sizeof(record->exception_thunk));
memcpy(&record->exception_thunk[0], masm.buffer_start(), masm.buffer_size());
}
void RegisterNonABICompliantCodeRange(void* start, size_t size_in_bytes) {
DCHECK(CanRegisterUnwindInfoForNonABICompliantCodeRange());
// When the --win64-unwinding-info flag is set, we call
// RtlAddGrowableFunctionTable to register unwinding info for the whole code
// range of an isolate or WASM module. This enables the Windows OS stack
// unwinder to work correctly with V8-generated code, enabling stack walking
// in Windows debuggers and performance tools. However, the
// RtlAddGrowableFunctionTable API is only supported on Windows 8 and above.
//
// On Windows 7, or when --win64-unwinding-info is not set, we may still need
// to call RtlAddFunctionTable to register a custom exception handler passed
// by the embedder (like Crashpad).
if (RegisterUnwindInfoForExceptionHandlingOnly()) {
if (unhandled_exception_callback_g) {
ExceptionHandlerRecord* record = new (start) ExceptionHandlerRecord();
InitUnwindingRecord(record, size_in_bytes);
CHECK(::RtlAddFunctionTable(&record->runtime_function, 1,
reinterpret_cast<DWORD64>(start)));
}
} else {
CodeRangeUnwindingRecord* record = new (start) CodeRangeUnwindingRecord();
InitUnwindingRecord(record, size_in_bytes);
CHECK(AddGrowableFunctionTable(
&record->dynamic_table, &record->runtime_function, 1, 1,
reinterpret_cast<DWORD64>(start),
reinterpret_cast<DWORD64>(reinterpret_cast<uint8_t*>(start) +
size_in_bytes)));
}
// Protect reserved page against modifications.
DWORD old_protect;
CHECK(VirtualProtect(start, sizeof(CodeRangeUnwindingRecord),
PAGE_EXECUTE_READ, &old_protect));
}
void UnregisterNonABICompliantCodeRange(void* start) {
DCHECK(CanRegisterUnwindInfoForNonABICompliantCodeRange());
if (RegisterUnwindInfoForExceptionHandlingOnly()) {
if (unhandled_exception_callback_g) {
ExceptionHandlerRecord* record =
reinterpret_cast<ExceptionHandlerRecord*>(start);
CHECK(::RtlDeleteFunctionTable(&record->runtime_function));
}
} else {
CodeRangeUnwindingRecord* record =
reinterpret_cast<CodeRangeUnwindingRecord*>(start);
if (record->dynamic_table) {
DeleteGrowableFunctionTable(record->dynamic_table);
}
}
}
void XdataEncoder::onPushRbp() {
current_push_rbp_offset_ = assembler_.pc_offset() - kPushRbpInstructionLength;
}
void XdataEncoder::onMovRbpRsp() {
if (current_push_rbp_offset_ >= 0 &&
current_push_rbp_offset_ == assembler_.pc_offset() - kRbpPrefixLength) {
fp_offsets_.push_back(current_push_rbp_offset_);
}
}
} // namespace win64_unwindinfo
} // namespace internal
} // namespace v8
#endif // defined(V8_OS_WIN_X64)
// 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.
#ifndef V8_UNWINDING_INFO_WIN64_H_
#define V8_UNWINDING_INFO_WIN64_H_
#include "include/v8config.h"
#if defined(V8_OS_WIN_X64)
#include "include/v8.h"
#include "src/base/win32-headers.h"
#include "src/globals.h"
namespace v8 {
namespace internal {
namespace win64_unwindinfo {
#define CRASH_HANDLER_FUNCTION_NAME CrashForExceptionInNonABICompliantCodeRange
#define CRASH_HANDLER_FUNCTION_NAME_STRING \
"CrashForExceptionInNonABICompliantCodeRange"
static const int kPushRbpInstructionLength = 1;
static const int kMovRbpRspInstructionLength = 3;
static const int kRbpPrefixCodes = 2;
static const int kRbpPrefixLength =
kPushRbpInstructionLength + kMovRbpRspInstructionLength;
/**
* Returns true if V8 is configured to emit unwinding data for embedded in the
* pdata/xdata sections of the executable. Currently, this happens when V8 is
* built with "v8_win64_unwinding_info = true".
*/
bool CanEmitUnwindInfoForBuiltins();
/**
* Returns true if V8 if we can register unwinding data for the whole code range
* of an isolate or WASM module. The first page of the code range is reserved
* and writable, to be used to store unwind data, as documented in:
* https://docs.microsoft.com/en-us/cpp/build/exception-handling-x64.
* In jitless mode V8 does not allocate any executable memory itself so the only
* non-abi-compliant code range is in the embedded blob.
*/
bool CanRegisterUnwindInfoForNonABICompliantCodeRange();
/**
* Registers a custom exception handler for exceptions in V8-generated code.
*/
void SetUnhandledExceptionCallback(
v8::UnhandledExceptionCallback unhandled_exception_callback);
/**
* Returns a vector of bytes that contains the Win64 unwind data used for all
* V8 builtin functions.
*/
std::vector<uint8_t> GetUnwindInfoForBuiltinFunctions();
void RegisterNonABICompliantCodeRange(void* start, size_t size_in_bytes);
void UnregisterNonABICompliantCodeRange(void* start);
class BuiltinUnwindInfo {
public:
BuiltinUnwindInfo() : is_leaf_function_(true) {}
explicit BuiltinUnwindInfo(const std::vector<int>& fp_offsets)
: is_leaf_function_(false), fp_offsets_(fp_offsets) {}
bool is_leaf_function() const { return is_leaf_function_; }
const std::vector<int>& fp_offsets() const { return fp_offsets_; }
private:
bool is_leaf_function_;
std::vector<int> fp_offsets_;
};
class XdataEncoder {
public:
explicit XdataEncoder(const Assembler& assembler)
: assembler_(assembler), current_push_rbp_offset_(-1) {}
void onPushRbp();
void onMovRbpRsp();
BuiltinUnwindInfo unwinding_info() const {
return BuiltinUnwindInfo(fp_offsets_);
}
private:
const Assembler& assembler_;
std::vector<int> fp_offsets_;
int current_push_rbp_offset_;
};
} // namespace win64_unwindinfo
} // namespace internal
} // namespace v8
#endif // defined(V8_OS_WIN_X64)
#endif // V8_UNWINDING_INFO_WIN64_H_
......@@ -27,6 +27,10 @@
#include "src/wasm/wasm-objects-inl.h"
#include "src/wasm/wasm-objects.h"
#if defined(V8_OS_WIN_X64)
#include "src/unwinding-info-win64.h"
#endif
#define TRACE_HEAP(...) \
do { \
if (FLAG_trace_wasm_native_heap) PrintF(__VA_ARGS__); \
......@@ -390,6 +394,18 @@ NativeModule::NativeModule(WasmEngine* engine, const WasmFeatures& enabled,
owned_code_space_.emplace_back(std::move(code_space));
owned_code_.reserve(num_functions());
#if defined(V8_OS_WIN_X64)
// On some platforms, specifically Win64, we need to reserve some pages at
// the beginning of an executable space.
// See src/heap/spaces.cc, MemoryAllocator::InitializeCodePageAllocator() and
// https://cs.chromium.org/chromium/src/components/crash/content/app/crashpad_win.cc?rcl=fd680447881449fba2edcf0589320e7253719212&l=204
// for details.
if (win64_unwindinfo::CanRegisterUnwindInfoForNonABICompliantCodeRange() &&
FLAG_win64_unwinding_info) {
AllocateForCode(Heap::GetCodeRangeReservedAreaSize());
}
#endif
uint32_t num_wasm_functions = module_->num_declared_functions;
if (num_wasm_functions > 0) {
code_table_.reset(new WasmCode*[num_wasm_functions]);
......@@ -1124,6 +1140,15 @@ std::shared_ptr<NativeModule> WasmCodeManager::NewNativeModule(
DCHECK_NOT_NULL(ret);
TRACE_HEAP("New NativeModule %p: Mem: %" PRIuPTR ",+%zu\n", ret.get(), start,
size);
#if defined(V8_OS_WIN_X64)
if (win64_unwindinfo::CanRegisterUnwindInfoForNonABICompliantCodeRange() &&
FLAG_win64_unwinding_info) {
win64_unwindinfo::RegisterNonABICompliantCodeRange(
reinterpret_cast<void*>(start), size);
}
#endif
base::MutexGuard lock(&native_modules_mutex_);
lookup_map_.insert(std::make_pair(start, std::make_pair(end, ret.get())));
return ret;
......@@ -1244,6 +1269,15 @@ void WasmCodeManager::FreeNativeModule(NativeModule* native_module) {
DCHECK(code_space.IsReserved());
TRACE_HEAP("VMem Release: %" PRIxPTR ":%" PRIxPTR " (%zu)\n",
code_space.address(), code_space.end(), code_space.size());
#if defined(V8_OS_WIN_X64)
if (win64_unwindinfo::CanRegisterUnwindInfoForNonABICompliantCodeRange() &&
FLAG_win64_unwinding_info) {
win64_unwindinfo::UnregisterNonABICompliantCodeRange(
reinterpret_cast<void*>(code_space.address()));
}
#endif
lookup_map_.erase(code_space.address());
memory_tracker_->ReleaseReservation(code_space.size());
code_space.Free();
......
......@@ -433,6 +433,12 @@ Assembler::Assembler(const AssemblerOptions& options,
if (CpuFeatures::IsSupported(SSE4_1)) {
EnableCpuFeature(SSSE3);
}
#if defined(V8_OS_WIN_X64)
if (options.collect_win64_unwind_info) {
xdata_encoder_ = std::make_unique<win64_unwindinfo::XdataEncoder>(*this);
}
#endif
}
void Assembler::GetCode(Isolate* isolate, CodeDesc* desc,
......@@ -496,6 +502,14 @@ void Assembler::FinalizeJumpOptimizationInfo() {
}
}
#if defined(V8_OS_WIN_X64)
win64_unwindinfo::BuiltinUnwindInfo Assembler::GetUnwindInfo() const {
DCHECK(options().collect_win64_unwind_info);
DCHECK_NOT_NULL(xdata_encoder_);
return xdata_encoder_->unwinding_info();
}
#endif
void Assembler::Align(int m) {
DCHECK(base::bits::IsPowerOfTwo(m));
int delta = (m - (pc_offset() & (m - 1))) & (m - 1);
......@@ -1750,6 +1764,12 @@ void Assembler::emit_mov(Register dst, Register src, int size) {
emit(0x8B);
emit_modrm(dst, src);
}
#if defined(V8_OS_WIN_X64)
if (xdata_encoder_ && dst == rbp && src == rsp) {
xdata_encoder_->onMovRbpRsp();
}
#endif
}
void Assembler::emit_mov(Operand dst, Register src, int size) {
......@@ -2154,6 +2174,12 @@ void Assembler::pushq(Register src) {
EnsureSpace ensure_space(this);
emit_optional_rex_32(src);
emit(0x50 | src.low_bits());
#if defined(V8_OS_WIN_X64)
if (xdata_encoder_ && src == rbp) {
xdata_encoder_->onPushRbp();
}
#endif
}
void Assembler::pushq(Operand src) {
......
......@@ -47,6 +47,9 @@
#include "src/x64/constants-x64.h"
#include "src/x64/register-x64.h"
#include "src/x64/sse-instr.h"
#if defined(V8_OS_WIN_X64)
#include "src/unwinding-info-win64.h"
#endif
namespace v8 {
namespace internal {
......@@ -1776,6 +1779,10 @@ class V8_EXPORT_PRIVATE Assembler : public AssemblerBase {
byte byte_at(int pos) { return buffer_start_[pos]; }
void set_byte_at(int pos, byte value) { buffer_start_[pos] = value; }
#if defined(V8_OS_WIN_X64)
win64_unwindinfo::BuiltinUnwindInfo GetUnwindInfo() const;
#endif
protected:
// Call near indirect
void call(Operand operand);
......@@ -2246,6 +2253,10 @@ class V8_EXPORT_PRIVATE Assembler : public AssemblerBase {
ConstPool constpool_;
friend class ConstPool;
#if defined(V8_OS_WIN_X64)
std::unique_ptr<win64_unwindinfo::XdataEncoder> xdata_encoder_;
#endif
};
......
......@@ -350,6 +350,9 @@ v8_source_set("cctest_sources") {
"test-log-stack-tracer.cc",
"test-macro-assembler-x64.cc",
]
if (is_win) {
sources += [ "test-stack-unwinding-x64.cc" ]
}
} else if (v8_current_cpu == "ppc" || v8_current_cpu == "ppc64") {
sources += [ ### gcmole(arch:ppc) ###
"test-assembler-ppc.cc",
......
......@@ -449,6 +449,12 @@
'test-unwinder/*': [SKIP]
}],
##############################################################################
# Windows stack unwinding is only supported on x64.
['arch != x64 or system != windows', {
'test-stack-unwinding-x64/*': [SKIP]
}],
##############################################################################
['lite_mode or variant == jitless', {
......
// 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/v8.h"
#include "test/cctest/cctest.h"
class UnwindingWinX64Callbacks {
public:
UnwindingWinX64Callbacks() = 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_record.Rip, &image_base, nullptr);
if (!function_entry) break;
void* handler_data;
uint64_t establisher_frame;
::RtlVirtualUnwind(UNW_FLAG_NHANDLER, image_base, context_record.Rip,
function_entry, &context_record, &handler_data,
&establisher_frame, NULL);
}
return iframe;
}
};
// Verifies that stack unwinding data has been correctly registered on Win/x64.
UNINITIALIZED_TEST(StackUnwindingWinX64) {
#ifdef V8_WIN64_UNWINDING_INFO
static const char* unwinding_win_x64_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";
// 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();
UnwindingWinX64Callbacks accessors;
v8::Local<v8::External> data = v8::External::New(isolate, &accessors);
instance_template->SetAccessor(v8_str("foo"),
&UnwindingWinX64Callbacks::Getter,
&UnwindingWinX64Callbacks::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_win_x64_test_source);
v8::Local<v8::Function> function = v8::Local<v8::Function>::Cast(
env->Global()->Get(env.local(), v8_str("start")).ToLocalChecked());
CompileRun("%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
}
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