Commit 969cb0c7 authored by Paolo Severini's avatar Paolo Severini Committed by Commit Bot

Reland "V8 x64 backend doesn't emit ABI compliant stack frames"

This is a reland of 3cda21de

Original change's description:
> 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/+/1469329
> Reviewed-by: Michael Starzinger <mstarzinger@chromium.org>
> Reviewed-by: Jakob Gruber <jgruber@chromium.org>
> Reviewed-by: Ulan Degenbaev <ulan@chromium.org>
> Commit-Queue: Paolo Severini <paolosev@microsoft.com>
> Cr-Commit-Position: refs/heads/master@{#60330}

Bug: v8:3598
Change-Id: If988baf7d3e4af165b919d6e54c1ad985f8e25e3
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1534618Reviewed-by: 's avatarJakob Gruber <jgruber@chromium.org>
Reviewed-by: 's avatarUlan Degenbaev <ulan@chromium.org>
Reviewed-by: 's avatarMichael Starzinger <mstarzinger@chromium.org>
Commit-Queue: Paolo Severini <paolosev@microsoft.com>
Cr-Commit-Position: refs/heads/master@{#60581}
parent dcd2032b
......@@ -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
......@@ -435,6 +438,9 @@ config("features") {
if (v8_use_perfetto) {
defines += [ "V8_USE_PERFETTO" ]
}
if (v8_win64_unwinding_info) {
defines += [ "V8_WIN64_UNWINDING_INFO" ]
}
}
config("toolchain") {
......@@ -1120,6 +1126,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 += [
......@@ -2884,6 +2894,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);
}
......
......@@ -1412,6 +1412,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(
......
......@@ -2519,6 +2519,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);
......
......@@ -286,6 +286,10 @@ class Heap {
V8_EXPORT_PRIVATE 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.
......
......@@ -85,6 +85,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_;
......@@ -2949,6 +2953,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);
......@@ -3519,6 +3533,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);
......@@ -4327,6 +4351,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.
......@@ -353,6 +466,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);
......@@ -567,6 +716,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, const char* filename, 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());
......@@ -265,10 +296,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_);
......@@ -277,7 +304,7 @@ class EmbeddedFileWriter : public EmbeddedFileWriterInterface {
w->SectionData();
w->AlignToDataAlignment();
w->DeclarePointerToSymbol(embedded_blob_symbol,
embedded_blob_data_symbol);
EmbeddedBlobDataSymbol().c_str());
w->Newline();
}
......@@ -293,9 +320,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
......@@ -423,6 +464,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 && FLAG_win64_unwinding_info;
}
bool RegisterUnwindInfoForExceptionHandlingOnly() {
DCHECK(CanRegisterUnwindInfoForNonABICompliantCodeRange());
return !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__); \
......@@ -391,6 +395,17 @@ 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()) {
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] {});
......@@ -1198,6 +1213,14 @@ 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()) {
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;
......@@ -1329,6 +1352,14 @@ 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()) {
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 {
......@@ -1780,6 +1783,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);
......@@ -2250,6 +2257,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
};
......
......@@ -351,6 +351,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