[wasm] Initial signal handler
This is basically the minimum viable signal handler for Wasm bounds checks. It includes the TLS check and the fine grained instructions checks. These two checks provide most of the safety for the signal handler. Future CLs will add code range and data range checks for more robustness. The trap handling code and data structures are all in src/trap-handler, with the code that actually runs in the signal handler confined to src/trap-handler/signal-handler.cc. This changes adds a new V8 API that the embedder should call from a signal handler that will give V8 the chance to handle the fault first. For hosts that do not want to implement their own signal handler, we include the option to install a simple one. This simple handler is also used for the tests. When a Wasm module is instantiated, information about each function is passed to the trap handler, which is used to classify faults. These are removed during the instance finalizer. Several future enhancements are planned before turning this on by default. Obviously, the additional checks will be added to MaybeHandleFault. We are also planning to add a two-level CodeObjectData table that is grouped by isolates to make cleanup easier and also reduce potential for contending on a single data structure. BUG= https://bugs.chromium.org/p/v8/issues/detail?id=5277 Review-Url: https://codereview.chromium.org/2371833007 Cr-Original-Original-Commit-Position: refs/heads/master@{#43523} Committed:a5af7fe9ee
Review-Url: https://codereview.chromium.org/2371833007 Cr-Original-Commit-Position: refs/heads/master@{#43755} Committed:338622d7ca
Review-Url: https://codereview.chromium.org/2371833007 Cr-Commit-Position: refs/heads/master@{#43759}
This commit is contained in:
parent
65200967b7
commit
118c376fcb
6
BUILD.gn
6
BUILD.gn
@ -1775,6 +1775,9 @@ v8_source_set("v8_base") {
|
||||
"src/transitions-inl.h",
|
||||
"src/transitions.cc",
|
||||
"src/transitions.h",
|
||||
"src/trap-handler/handler-outside.cc",
|
||||
"src/trap-handler/handler-shared.cc",
|
||||
"src/trap-handler/trap-handler-internal.h",
|
||||
"src/trap-handler/trap-handler.h",
|
||||
"src/type-hints.cc",
|
||||
"src/type-hints.h",
|
||||
@ -1931,6 +1934,9 @@ v8_source_set("v8_base") {
|
||||
"src/x64/simulator-x64.h",
|
||||
"src/x64/sse-instr.h",
|
||||
]
|
||||
if (is_linux) {
|
||||
sources += [ "src/trap-handler/handler-inside.cc" ]
|
||||
}
|
||||
} else if (v8_current_cpu == "arm") {
|
||||
sources += [ ### gcmole(arch:arm) ###
|
||||
"src/arm/assembler-arm-inl.h",
|
||||
|
29
include/v8.h
29
include/v8.h
@ -7633,6 +7633,35 @@ class V8_EXPORT V8 {
|
||||
*/
|
||||
static void ShutdownPlatform();
|
||||
|
||||
#if V8_OS_LINUX && V8_TARGET_ARCH_X64
|
||||
/**
|
||||
* Give the V8 signal handler a chance to handle a fault.
|
||||
*
|
||||
* This function determines whether a memory access violation can be recovered
|
||||
* by V8. If so, it will return true and modify context to return to a code
|
||||
* fragment that can recover from the fault. Otherwise, TryHandleSignal will
|
||||
* return false.
|
||||
*
|
||||
* The parameters to this function correspond to those passed to a Linux
|
||||
* signal handler.
|
||||
*
|
||||
* \param signal_number The signal number.
|
||||
*
|
||||
* \param info A pointer to the siginfo_t structure provided to the signal
|
||||
* handler.
|
||||
*
|
||||
* \param context The third argument passed to the Linux signal handler, which
|
||||
* points to a ucontext_t structure.
|
||||
*/
|
||||
static bool TryHandleSignal(int signal_number, void* info, void* context);
|
||||
#endif // V8_OS_LINUX
|
||||
|
||||
/**
|
||||
* Enable the default signal handler rather than using one provided by the
|
||||
* embedder.
|
||||
*/
|
||||
static bool RegisterDefaultSignalHandler();
|
||||
|
||||
private:
|
||||
V8();
|
||||
|
||||
|
2
src/DEPS
2
src/DEPS
@ -18,6 +18,8 @@ include_rules = [
|
||||
"+src/interpreter/bytecode-register.h",
|
||||
"+src/interpreter/bytecodes.h",
|
||||
"+src/interpreter/interpreter.h",
|
||||
"-src/trap-handler",
|
||||
"+src/trap-handler/trap-handler.h",
|
||||
"+testing/gtest/include/gtest/gtest_prod.h",
|
||||
"-src/libplatform",
|
||||
"-include/libplatform"
|
||||
|
11
src/api.cc
11
src/api.cc
@ -69,6 +69,7 @@
|
||||
#include "src/snapshot/snapshot.h"
|
||||
#include "src/startup-data-util.h"
|
||||
#include "src/tracing/trace-event.h"
|
||||
#include "src/trap-handler/trap-handler.h"
|
||||
#include "src/unicode-inl.h"
|
||||
#include "src/v8.h"
|
||||
#include "src/v8threads.h"
|
||||
@ -6154,6 +6155,16 @@ bool v8::V8::Initialize() {
|
||||
return true;
|
||||
}
|
||||
|
||||
#if V8_OS_LINUX && V8_TARGET_ARCH_X64
|
||||
bool V8::TryHandleSignal(int signum, void* info, void* context) {
|
||||
return v8::internal::trap_handler::TryHandleSignal(
|
||||
signum, static_cast<siginfo_t*>(info), static_cast<ucontext_t*>(context));
|
||||
}
|
||||
#endif // V8_OS_LINUX
|
||||
|
||||
bool V8::RegisterDefaultSignalHandler() {
|
||||
return v8::internal::trap_handler::RegisterDefaultSignalHandler();
|
||||
}
|
||||
|
||||
void v8::V8::SetEntropySource(EntropySource entropy_source) {
|
||||
base::RandomNumberGenerator::SetEntropySource(entropy_source);
|
||||
|
@ -111,13 +111,6 @@ Node* BuildCallToRuntime(Runtime::FunctionId f, JSGraph* jsgraph,
|
||||
|
||||
} // namespace
|
||||
|
||||
// TODO(eholk): Support trap handlers on other platforms.
|
||||
#if V8_TARGET_ARCH_X64 && V8_OS_LINUX
|
||||
const bool kTrapHandlerSupported = true;
|
||||
#else
|
||||
const bool kTrapHandlerSupported = false;
|
||||
#endif
|
||||
|
||||
// A helper that handles building graph fragments for trapping.
|
||||
// To avoid generating a ton of redundant code that just calls the runtime
|
||||
// to trap, we generate a per-trap-reason block of code that all trap sites
|
||||
@ -2807,6 +2800,15 @@ void WasmGraphBuilder::BuildJSToWasmWrapper(Handle<Code> wasm_code,
|
||||
args[pos++] = wasm_param;
|
||||
}
|
||||
|
||||
// Set the ThreadInWasm flag before we do the actual call.
|
||||
if (trap_handler::UseTrapHandler()) {
|
||||
// TODO(eholk): Set the flag directly without a runtime call. We should be
|
||||
// able to store directly to a location in the isolate (later TLS) that sets
|
||||
// the g_thread_in_wasm_code flag.
|
||||
BuildCallToRuntime(Runtime::kSetThreadInWasm, jsgraph(), nullptr, 0,
|
||||
effect_, *control_);
|
||||
}
|
||||
|
||||
args[pos++] = *effect_;
|
||||
args[pos++] = *control_;
|
||||
|
||||
@ -2816,6 +2818,16 @@ void WasmGraphBuilder::BuildJSToWasmWrapper(Handle<Code> wasm_code,
|
||||
|
||||
Node* call = graph()->NewNode(jsgraph()->common()->Call(desc), count, args);
|
||||
*effect_ = call;
|
||||
|
||||
// Clear the ThreadInWasmFlag
|
||||
if (trap_handler::UseTrapHandler()) {
|
||||
// TODO(eholk): Set the flag directly without a runtime call. We should be
|
||||
// able to store directly to a location in the isolate (later TLS) that sets
|
||||
// the g_thread_in_wasm_code flag.
|
||||
BuildCallToRuntime(Runtime::kClearThreadInWasm, jsgraph(), nullptr, 0,
|
||||
effect_, *control_);
|
||||
}
|
||||
|
||||
Node* retval = call;
|
||||
Node* jsval = ToJS(
|
||||
retval, sig->return_count() == 0 ? wasm::kWasmStmt : sig->GetReturn());
|
||||
@ -2862,6 +2874,11 @@ void WasmGraphBuilder::BuildWasmToJSWrapper(Handle<JSReceiver> target,
|
||||
Node* call;
|
||||
bool direct_call = false;
|
||||
|
||||
if (trap_handler::UseTrapHandler()) {
|
||||
BuildCallToRuntime(Runtime::kClearThreadInWasm, jsgraph(), nullptr, 0,
|
||||
effect_, *control_);
|
||||
}
|
||||
|
||||
if (target->IsJSFunction()) {
|
||||
Handle<JSFunction> function = Handle<JSFunction>::cast(target);
|
||||
if (function->shared()->internal_formal_parameter_count() == wasm_count) {
|
||||
@ -2926,6 +2943,11 @@ void WasmGraphBuilder::BuildWasmToJSWrapper(Handle<JSReceiver> target,
|
||||
*effect_ = call;
|
||||
SetSourcePosition(call, 0);
|
||||
|
||||
if (trap_handler::UseTrapHandler()) {
|
||||
BuildCallToRuntime(Runtime::kSetThreadInWasm, jsgraph(), nullptr, 0,
|
||||
effect_, *control_);
|
||||
}
|
||||
|
||||
// Convert the return value back.
|
||||
Node* i32_zero = jsgraph()->Int32Constant(0);
|
||||
Node* val = sig->return_count() == 0
|
||||
@ -3195,7 +3217,7 @@ Node* WasmGraphBuilder::LoadMem(wasm::ValueType type, MachineType memtype,
|
||||
Node* load;
|
||||
|
||||
// WASM semantics throw on OOB. Introduce explicit bounds check.
|
||||
if (!FLAG_wasm_trap_handler || !kTrapHandlerSupported) {
|
||||
if (!FLAG_wasm_trap_handler || !V8_TRAP_HANDLER_SUPPORTED) {
|
||||
BoundsCheckMem(memtype, index, offset, position);
|
||||
}
|
||||
bool aligned = static_cast<int>(alignment) >=
|
||||
@ -3203,7 +3225,7 @@ Node* WasmGraphBuilder::LoadMem(wasm::ValueType type, MachineType memtype,
|
||||
|
||||
if (aligned ||
|
||||
jsgraph()->machine()->UnalignedLoadSupported(memtype, alignment)) {
|
||||
if (FLAG_wasm_trap_handler && kTrapHandlerSupported) {
|
||||
if (FLAG_wasm_trap_handler && V8_TRAP_HANDLER_SUPPORTED) {
|
||||
DCHECK(FLAG_wasm_guard_pages);
|
||||
Node* position_node = jsgraph()->Int32Constant(position);
|
||||
load = graph()->NewNode(jsgraph()->machine()->ProtectedLoad(memtype),
|
||||
@ -3215,7 +3237,7 @@ Node* WasmGraphBuilder::LoadMem(wasm::ValueType type, MachineType memtype,
|
||||
}
|
||||
} else {
|
||||
// TODO(eholk): Support unaligned loads with trap handlers.
|
||||
DCHECK(!FLAG_wasm_trap_handler || !kTrapHandlerSupported);
|
||||
DCHECK(!FLAG_wasm_trap_handler || !V8_TRAP_HANDLER_SUPPORTED);
|
||||
load = graph()->NewNode(jsgraph()->machine()->UnalignedLoad(memtype),
|
||||
MemBuffer(offset), index, *effect_, *control_);
|
||||
}
|
||||
@ -3249,7 +3271,7 @@ Node* WasmGraphBuilder::StoreMem(MachineType memtype, Node* index,
|
||||
Node* store;
|
||||
|
||||
// WASM semantics throw on OOB. Introduce explicit bounds check.
|
||||
if (!FLAG_wasm_trap_handler || !kTrapHandlerSupported) {
|
||||
if (!FLAG_wasm_trap_handler || !V8_TRAP_HANDLER_SUPPORTED) {
|
||||
BoundsCheckMem(memtype, index, offset, position);
|
||||
}
|
||||
StoreRepresentation rep(memtype.representation(), kNoWriteBarrier);
|
||||
@ -3263,7 +3285,7 @@ Node* WasmGraphBuilder::StoreMem(MachineType memtype, Node* index,
|
||||
|
||||
if (aligned ||
|
||||
jsgraph()->machine()->UnalignedStoreSupported(memtype, alignment)) {
|
||||
if (FLAG_wasm_trap_handler && kTrapHandlerSupported) {
|
||||
if (FLAG_wasm_trap_handler && V8_TRAP_HANDLER_SUPPORTED) {
|
||||
Node* position_node = jsgraph()->Int32Constant(position);
|
||||
store = graph()->NewNode(
|
||||
jsgraph()->machine()->ProtectedStore(memtype.representation()),
|
||||
@ -3276,7 +3298,7 @@ Node* WasmGraphBuilder::StoreMem(MachineType memtype, Node* index,
|
||||
}
|
||||
} else {
|
||||
// TODO(eholk): Support unaligned stores with trap handlers.
|
||||
DCHECK(!FLAG_wasm_trap_handler || !kTrapHandlerSupported);
|
||||
DCHECK(!FLAG_wasm_trap_handler || !V8_TRAP_HANDLER_SUPPORTED);
|
||||
UnalignedStoreRepresentation rep(memtype.representation());
|
||||
store =
|
||||
graph()->NewNode(jsgraph()->machine()->UnalignedStore(rep),
|
||||
|
@ -270,13 +270,12 @@ class OutOfLineRecordWrite final : public OutOfLineCode {
|
||||
class WasmOutOfLineTrap final : public OutOfLineCode {
|
||||
public:
|
||||
WasmOutOfLineTrap(CodeGenerator* gen, int pc, bool frame_elided,
|
||||
int32_t position, Instruction* instr)
|
||||
int32_t position)
|
||||
: OutOfLineCode(gen),
|
||||
gen_(gen),
|
||||
pc_(pc),
|
||||
frame_elided_(frame_elided),
|
||||
position_(position),
|
||||
instr_(instr) {}
|
||||
position_(position) {}
|
||||
|
||||
// TODO(eholk): Refactor this method to take the code generator as a
|
||||
// parameter.
|
||||
@ -290,14 +289,17 @@ class WasmOutOfLineTrap final : public OutOfLineCode {
|
||||
wasm::TrapReason trap_id = wasm::kTrapMemOutOfBounds;
|
||||
int trap_reason = wasm::WasmOpcodes::TrapReasonToMessageId(trap_id);
|
||||
__ Push(Smi::FromInt(trap_reason));
|
||||
// TODO(eholk): use AssembleSourcePosition instead of passing in position_
|
||||
// as a parameter. See AssembleArchTrap as an example. Consider sharing code
|
||||
// with AssembleArchTrap.
|
||||
__ Push(Smi::FromInt(position_));
|
||||
__ Move(rsi, gen_->isolate()->native_context());
|
||||
__ Move(rsi, Smi::kZero);
|
||||
__ CallRuntime(Runtime::kThrowWasmError);
|
||||
|
||||
if (instr_->reference_map() != nullptr) {
|
||||
gen_->RecordSafepoint(instr_->reference_map(), Safepoint::kSimple, 0,
|
||||
Safepoint::kNoLazyDeopt);
|
||||
}
|
||||
ReferenceMap* reference_map =
|
||||
new (gen_->code()->zone()) ReferenceMap(gen_->code()->zone());
|
||||
gen_->RecordSafepoint(reference_map, Safepoint::kSimple, 0,
|
||||
Safepoint::kNoLazyDeopt);
|
||||
}
|
||||
|
||||
private:
|
||||
@ -305,18 +307,17 @@ class WasmOutOfLineTrap final : public OutOfLineCode {
|
||||
int pc_;
|
||||
bool frame_elided_;
|
||||
int32_t position_;
|
||||
Instruction* instr_;
|
||||
};
|
||||
|
||||
void EmitOOLTrapIfNeeded(Zone* zone, CodeGenerator* codegen,
|
||||
InstructionCode opcode, size_t input_count,
|
||||
X64OperandConverter& i, int pc, Instruction* instr) {
|
||||
X64OperandConverter& i, int pc) {
|
||||
const X64MemoryProtection protection =
|
||||
static_cast<X64MemoryProtection>(MiscField::decode(opcode));
|
||||
if (protection == X64MemoryProtection::kProtected) {
|
||||
const bool frame_elided = !codegen->frame_access_state()->has_frame();
|
||||
const int32_t position = i.InputInt32(input_count - 1);
|
||||
new (zone) WasmOutOfLineTrap(codegen, pc, frame_elided, position, instr);
|
||||
new (zone) WasmOutOfLineTrap(codegen, pc, frame_elided, position);
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
@ -1859,30 +1860,30 @@ CodeGenerator::CodeGenResult CodeGenerator::AssembleArchInstruction(
|
||||
break;
|
||||
case kX64Movsxbl:
|
||||
EmitOOLTrapIfNeeded(zone(), this, opcode, instr->InputCount(), i,
|
||||
__ pc_offset(), instr);
|
||||
__ pc_offset());
|
||||
ASSEMBLE_MOVX(movsxbl);
|
||||
__ AssertZeroExtended(i.OutputRegister());
|
||||
break;
|
||||
case kX64Movzxbl:
|
||||
EmitOOLTrapIfNeeded(zone(), this, opcode, instr->InputCount(), i,
|
||||
__ pc_offset(), instr);
|
||||
__ pc_offset());
|
||||
ASSEMBLE_MOVX(movzxbl);
|
||||
__ AssertZeroExtended(i.OutputRegister());
|
||||
break;
|
||||
case kX64Movsxbq:
|
||||
EmitOOLTrapIfNeeded(zone(), this, opcode, instr->InputCount(), i,
|
||||
__ pc_offset(), instr);
|
||||
__ pc_offset());
|
||||
ASSEMBLE_MOVX(movsxbq);
|
||||
break;
|
||||
case kX64Movzxbq:
|
||||
EmitOOLTrapIfNeeded(zone(), this, opcode, instr->InputCount(), i,
|
||||
__ pc_offset(), instr);
|
||||
__ pc_offset());
|
||||
ASSEMBLE_MOVX(movzxbq);
|
||||
__ AssertZeroExtended(i.OutputRegister());
|
||||
break;
|
||||
case kX64Movb: {
|
||||
EmitOOLTrapIfNeeded(zone(), this, opcode, instr->InputCount(), i,
|
||||
__ pc_offset(), instr);
|
||||
__ pc_offset());
|
||||
size_t index = 0;
|
||||
Operand operand = i.MemoryOperand(&index);
|
||||
if (HasImmediateInput(instr, index)) {
|
||||
@ -1894,30 +1895,30 @@ CodeGenerator::CodeGenResult CodeGenerator::AssembleArchInstruction(
|
||||
}
|
||||
case kX64Movsxwl:
|
||||
EmitOOLTrapIfNeeded(zone(), this, opcode, instr->InputCount(), i,
|
||||
__ pc_offset(), instr);
|
||||
__ pc_offset());
|
||||
ASSEMBLE_MOVX(movsxwl);
|
||||
__ AssertZeroExtended(i.OutputRegister());
|
||||
break;
|
||||
case kX64Movzxwl:
|
||||
EmitOOLTrapIfNeeded(zone(), this, opcode, instr->InputCount(), i,
|
||||
__ pc_offset(), instr);
|
||||
__ pc_offset());
|
||||
ASSEMBLE_MOVX(movzxwl);
|
||||
__ AssertZeroExtended(i.OutputRegister());
|
||||
break;
|
||||
case kX64Movsxwq:
|
||||
EmitOOLTrapIfNeeded(zone(), this, opcode, instr->InputCount(), i,
|
||||
__ pc_offset(), instr);
|
||||
__ pc_offset());
|
||||
ASSEMBLE_MOVX(movsxwq);
|
||||
break;
|
||||
case kX64Movzxwq:
|
||||
EmitOOLTrapIfNeeded(zone(), this, opcode, instr->InputCount(), i,
|
||||
__ pc_offset(), instr);
|
||||
__ pc_offset());
|
||||
ASSEMBLE_MOVX(movzxwq);
|
||||
__ AssertZeroExtended(i.OutputRegister());
|
||||
break;
|
||||
case kX64Movw: {
|
||||
EmitOOLTrapIfNeeded(zone(), this, opcode, instr->InputCount(), i,
|
||||
__ pc_offset(), instr);
|
||||
__ pc_offset());
|
||||
size_t index = 0;
|
||||
Operand operand = i.MemoryOperand(&index);
|
||||
if (HasImmediateInput(instr, index)) {
|
||||
@ -1929,7 +1930,7 @@ CodeGenerator::CodeGenResult CodeGenerator::AssembleArchInstruction(
|
||||
}
|
||||
case kX64Movl:
|
||||
EmitOOLTrapIfNeeded(zone(), this, opcode, instr->InputCount(), i,
|
||||
__ pc_offset(), instr);
|
||||
__ pc_offset());
|
||||
if (instr->HasOutput()) {
|
||||
if (instr->addressing_mode() == kMode_None) {
|
||||
if (instr->InputAt(0)->IsRegister()) {
|
||||
@ -1953,12 +1954,12 @@ CodeGenerator::CodeGenResult CodeGenerator::AssembleArchInstruction(
|
||||
break;
|
||||
case kX64Movsxlq:
|
||||
EmitOOLTrapIfNeeded(zone(), this, opcode, instr->InputCount(), i,
|
||||
__ pc_offset(), instr);
|
||||
__ pc_offset());
|
||||
ASSEMBLE_MOVX(movsxlq);
|
||||
break;
|
||||
case kX64Movq:
|
||||
EmitOOLTrapIfNeeded(zone(), this, opcode, instr->InputCount(), i,
|
||||
__ pc_offset(), instr);
|
||||
__ pc_offset());
|
||||
if (instr->HasOutput()) {
|
||||
__ movq(i.OutputRegister(), i.MemoryOperand());
|
||||
} else {
|
||||
@ -1973,7 +1974,7 @@ CodeGenerator::CodeGenResult CodeGenerator::AssembleArchInstruction(
|
||||
break;
|
||||
case kX64Movss:
|
||||
EmitOOLTrapIfNeeded(zone(), this, opcode, instr->InputCount(), i,
|
||||
__ pc_offset(), instr);
|
||||
__ pc_offset());
|
||||
if (instr->HasOutput()) {
|
||||
__ movss(i.OutputDoubleRegister(), i.MemoryOperand());
|
||||
} else {
|
||||
@ -1984,7 +1985,7 @@ CodeGenerator::CodeGenResult CodeGenerator::AssembleArchInstruction(
|
||||
break;
|
||||
case kX64Movsd:
|
||||
EmitOOLTrapIfNeeded(zone(), this, opcode, instr->InputCount(), i,
|
||||
__ pc_offset(), instr);
|
||||
__ pc_offset());
|
||||
if (instr->HasOutput()) {
|
||||
__ Movsd(i.OutputDoubleRegister(), i.MemoryOperand());
|
||||
} else {
|
||||
|
@ -36,6 +36,7 @@
|
||||
#include "src/msan.h"
|
||||
#include "src/objects-inl.h"
|
||||
#include "src/snapshot/natives.h"
|
||||
#include "src/trap-handler/trap-handler.h"
|
||||
#include "src/utils.h"
|
||||
#include "src/v8.h"
|
||||
|
||||
@ -2953,6 +2954,13 @@ int Shell::Main(int argc, char* argv[]) {
|
||||
create_params.add_histogram_sample_callback = AddHistogramSample;
|
||||
}
|
||||
|
||||
if (i::trap_handler::UseTrapHandler()) {
|
||||
if (!v8::V8::RegisterDefaultSignalHandler()) {
|
||||
fprintf(stderr, "Could not register signal handler");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
Isolate* isolate = Isolate::New(create_params);
|
||||
{
|
||||
Isolate::Scope scope(isolate);
|
||||
|
@ -1700,6 +1700,7 @@ Handle<Code> Factory::NewCode(const CodeDesc& desc,
|
||||
code->set_prologue_offset(prologue_offset);
|
||||
code->set_constant_pool_offset(desc.instr_size - desc.constant_pool_size);
|
||||
code->set_builtin_index(-1);
|
||||
code->set_trap_handler_index(Smi::FromInt(-1));
|
||||
|
||||
if (code->kind() == Code::OPTIMIZED_FUNCTION) {
|
||||
code->set_marked_for_deoptimization(false);
|
||||
|
@ -1202,6 +1202,10 @@ Object* Isolate::UnwindAndFindHandler() {
|
||||
for (StackFrameIterator iter(this); !iter.done(); iter.Advance()) {
|
||||
StackFrame* frame = iter.frame();
|
||||
|
||||
if (frame->is_wasm() && trap_handler::IsThreadInWasm()) {
|
||||
trap_handler::ClearThreadInWasm();
|
||||
}
|
||||
|
||||
// For JSEntryStub frames we always have a handler.
|
||||
if (frame->is_entry() || frame->is_entry_construct()) {
|
||||
StackHandler* handler = frame->top_handler();
|
||||
@ -1233,6 +1237,11 @@ Object* Isolate::UnwindAndFindHandler() {
|
||||
|
||||
handler_sp = return_sp;
|
||||
handler_fp = frame->fp();
|
||||
|
||||
// This is going to be handled by Wasm, so we need to set the TLS flag
|
||||
// again.
|
||||
trap_handler::SetThreadInWasm();
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -6652,6 +6652,7 @@ CODE_ACCESSORS(relocation_info, ByteArray, kRelocationInfoOffset)
|
||||
CODE_ACCESSORS(handler_table, FixedArray, kHandlerTableOffset)
|
||||
CODE_ACCESSORS(deoptimization_data, FixedArray, kDeoptimizationDataOffset)
|
||||
CODE_ACCESSORS(source_position_table, ByteArray, kSourcePositionTableOffset)
|
||||
CODE_ACCESSORS(trap_handler_index, Smi, kTrapHandlerIndex)
|
||||
CODE_ACCESSORS(raw_type_feedback_info, Object, kTypeFeedbackInfoOffset)
|
||||
CODE_ACCESSORS(next_code_link, Object, kNextCodeLinkOffset)
|
||||
#undef CODE_ACCESSORS
|
||||
|
@ -13829,6 +13829,12 @@ void Code::InvalidateEmbeddedObjects() {
|
||||
|
||||
|
||||
void Code::Relocate(intptr_t delta) {
|
||||
if (trap_handler::UseTrapHandler() && is_wasm_code()) {
|
||||
const int index = trap_handler_index()->value();
|
||||
if (index >= 0) {
|
||||
trap_handler::UpdateHandlerDataCodePointer(index, instruction_start());
|
||||
}
|
||||
}
|
||||
for (RelocIterator it(this, RelocInfo::kApplyMask); !it.done(); it.next()) {
|
||||
it.rinfo()->apply(delta);
|
||||
}
|
||||
|
@ -4915,6 +4915,10 @@ class Code: public HeapObject {
|
||||
// [source_position_table]: ByteArray for the source positions table.
|
||||
DECL_ACCESSORS(source_position_table, ByteArray)
|
||||
|
||||
// [trap_handler_index]: An index into the trap handler's master list of code
|
||||
// objects.
|
||||
DECL_ACCESSORS(trap_handler_index, Smi)
|
||||
|
||||
// [raw_type_feedback_info]: This field stores various things, depending on
|
||||
// the kind of the code object.
|
||||
// FUNCTION => type feedback information.
|
||||
@ -5303,7 +5307,8 @@ class Code: public HeapObject {
|
||||
static const int kConstantPoolOffset = kPrologueOffset + kIntSize;
|
||||
static const int kBuiltinIndexOffset =
|
||||
kConstantPoolOffset + kConstantPoolSize;
|
||||
static const int kHeaderPaddingStart = kBuiltinIndexOffset + kIntSize;
|
||||
static const int kTrapHandlerIndex = kBuiltinIndexOffset + kIntSize;
|
||||
static const int kHeaderPaddingStart = kTrapHandlerIndex + kPointerSize;
|
||||
|
||||
enum TrapFields { kTrapCodeOffset, kTrapLandingOffset, kTrapDataSize };
|
||||
|
||||
|
@ -12,6 +12,7 @@
|
||||
#include "src/factory.h"
|
||||
#include "src/frames-inl.h"
|
||||
#include "src/objects-inl.h"
|
||||
#include "src/trap-handler/trap-handler.h"
|
||||
#include "src/v8memory.h"
|
||||
#include "src/wasm/wasm-module.h"
|
||||
#include "src/wasm/wasm-objects.h"
|
||||
@ -168,6 +169,16 @@ RUNTIME_FUNCTION(Runtime_WasmGetCaughtExceptionValue) {
|
||||
return exception;
|
||||
}
|
||||
|
||||
RUNTIME_FUNCTION(Runtime_SetThreadInWasm) {
|
||||
trap_handler::SetThreadInWasm();
|
||||
return isolate->heap()->undefined_value();
|
||||
}
|
||||
|
||||
RUNTIME_FUNCTION(Runtime_ClearThreadInWasm) {
|
||||
trap_handler::ClearThreadInWasm();
|
||||
return isolate->heap()->undefined_value();
|
||||
}
|
||||
|
||||
RUNTIME_FUNCTION(Runtime_WasmRunInterpreter) {
|
||||
DCHECK_EQ(3, args.length());
|
||||
HandleScope scope(isolate);
|
||||
|
@ -633,7 +633,9 @@ namespace internal {
|
||||
F(WasmThrow, 2, 1) \
|
||||
F(WasmGetCaughtExceptionValue, 1, 1) \
|
||||
F(WasmRunInterpreter, 3, 1) \
|
||||
F(WasmStackGuard, 0, 1)
|
||||
F(WasmStackGuard, 0, 1) \
|
||||
F(SetThreadInWasm, 0, 1) \
|
||||
F(ClearThreadInWasm, 0, 1)
|
||||
|
||||
#define FOR_EACH_INTRINSIC_RETURN_PAIR(F) \
|
||||
F(LoadLookupSlotForCall, 1, 2)
|
||||
|
17
src/trap-handler/DEPS
Normal file
17
src/trap-handler/DEPS
Normal file
@ -0,0 +1,17 @@
|
||||
# In order to make it easier to audit the signal handler code, we use very
|
||||
# restrictive include rules to limit the amount of code that the signal handler
|
||||
# can depend on.
|
||||
|
||||
include_rules = [
|
||||
"-src",
|
||||
"-include",
|
||||
"+src/trap-handler",
|
||||
]
|
||||
|
||||
specific_include_rules = {
|
||||
"trap-handler.h": [
|
||||
"+src/base/build_config.h",
|
||||
"+src/globals.h",
|
||||
"+src/flags.h",
|
||||
]
|
||||
}
|
10
src/trap-handler/OWNERS
Normal file
10
src/trap-handler/OWNERS
Normal file
@ -0,0 +1,10 @@
|
||||
set noparent
|
||||
|
||||
jochen@chromium.org
|
||||
bradnelson@chromium.org
|
||||
|
||||
# Changes to this directory should also be reviewed by:
|
||||
#
|
||||
# eholk@chromium.org
|
||||
# mseaborn@chromium.org
|
||||
# mark@chromium.org
|
170
src/trap-handler/handler-inside.cc
Normal file
170
src/trap-handler/handler-inside.cc
Normal file
@ -0,0 +1,170 @@
|
||||
// Copyright 2017 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.
|
||||
|
||||
// PLEASE READ BEFORE CHANGING THIS FILE!
|
||||
//
|
||||
// This file implements the out of bounds signal handler for
|
||||
// WebAssembly. Signal handlers are notoriously difficult to get
|
||||
// right, and getting it wrong can lead to security
|
||||
// vulnerabilities. In order to minimize this risk, here are some
|
||||
// rules to follow.
|
||||
//
|
||||
// 1. Do not introduce any new external dependencies. This file needs
|
||||
// to be self contained so it is easy to audit everything that a
|
||||
// signal handler might do.
|
||||
//
|
||||
// 2. Any changes must be reviewed by someone from the crash reporting
|
||||
// or security team. See OWNERS for suggested reviewers.
|
||||
//
|
||||
// For more information, see https://goo.gl/yMeyUY.
|
||||
//
|
||||
// This file contains most of the code that actually runs in a signal handler
|
||||
// context. Some additional code is used both inside and outside the signal
|
||||
// handler. This code can be found in handler-shared.cc.
|
||||
|
||||
#include <signal.h>
|
||||
#include <stddef.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "src/trap-handler/trap-handler-internal.h"
|
||||
#include "src/trap-handler/trap-handler.h"
|
||||
|
||||
namespace v8 {
|
||||
namespace internal {
|
||||
namespace trap_handler {
|
||||
|
||||
namespace {
|
||||
|
||||
bool IsKernelGeneratedSignal(siginfo_t* info) {
|
||||
return info->si_code > 0 && info->si_code != SI_USER &&
|
||||
info->si_code != SI_QUEUE && info->si_code != SI_TIMER &&
|
||||
info->si_code != SI_ASYNCIO && info->si_code != SI_MESGQ;
|
||||
}
|
||||
|
||||
#if V8_TRAP_HANDLER_SUPPORTED
|
||||
class SigUnmaskStack {
|
||||
public:
|
||||
explicit SigUnmaskStack(sigset_t sigs) {
|
||||
// TODO(eholk): consider using linux-syscall-support for calling this
|
||||
// syscall.
|
||||
pthread_sigmask(SIG_UNBLOCK, &sigs, &old_mask_);
|
||||
}
|
||||
|
||||
~SigUnmaskStack() { pthread_sigmask(SIG_SETMASK, &old_mask_, nullptr); }
|
||||
|
||||
private:
|
||||
sigset_t old_mask_;
|
||||
|
||||
// We'd normally use DISALLOW_COPY_AND_ASSIGN, but we're avoiding a dependency
|
||||
// on base/macros.h
|
||||
SigUnmaskStack(const SigUnmaskStack&) = delete;
|
||||
void operator=(const SigUnmaskStack&) = delete;
|
||||
};
|
||||
#endif
|
||||
} // namespace
|
||||
|
||||
#if V8_TRAP_HANDLER_SUPPORTED && V8_OS_LINUX
|
||||
bool TryHandleSignal(int signum, siginfo_t* info, ucontext_t* context) {
|
||||
// Bail out early in case we got called for the wrong kind of signal.
|
||||
if (signum != SIGSEGV) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Make sure the signal was generated by the kernel and not some other source.
|
||||
if (!IsKernelGeneratedSignal(info)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Ensure the faulting thread was actually running Wasm code.
|
||||
if (!IsThreadInWasm()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Clear g_thread_in_wasm_code, primarily to protect against nested faults.
|
||||
g_thread_in_wasm_code = false;
|
||||
|
||||
// Begin signal mask scope. We need to be sure to restore the signal mask
|
||||
// before we restore the g_thread_in_wasm_code flag.
|
||||
{
|
||||
// Unmask the signal so that if this signal handler crashes, the crash will
|
||||
// be handled by the crash reporter. Otherwise, the process might be killed
|
||||
// with the crash going unreported.
|
||||
sigset_t sigs;
|
||||
// Fortunately, sigemptyset and sigaddset are async-signal-safe according to
|
||||
// the POSIX standard.
|
||||
sigemptyset(&sigs);
|
||||
sigaddset(&sigs, SIGSEGV);
|
||||
SigUnmaskStack unmask(sigs);
|
||||
|
||||
uintptr_t fault_addr = context->uc_mcontext.gregs[REG_RIP];
|
||||
|
||||
// TODO(eholk): broad code range check
|
||||
|
||||
// Taking locks in a signal handler is risky because a fault in the signal
|
||||
// handler could lead to a deadlock when attempting to acquire the lock
|
||||
// again. We guard against this case with g_thread_in_wasm_code. The lock
|
||||
// may only be taken when not executing Wasm code (an assert in
|
||||
// MetadataLock's constructor ensures this). This signal handler will bail
|
||||
// out before trying to take the lock if g_thread_in_wasm_code is not set.
|
||||
MetadataLock lock_holder;
|
||||
|
||||
for (size_t i = 0; i < gNumCodeObjects; ++i) {
|
||||
const CodeProtectionInfo* data = gCodeObjects[i].code_info;
|
||||
if (data == nullptr) {
|
||||
continue;
|
||||
}
|
||||
const uintptr_t base = reinterpret_cast<uintptr_t>(data->base);
|
||||
|
||||
if (fault_addr >= base && fault_addr < base + data->size) {
|
||||
// Hurray, we found the code object. Check for protected addresses.
|
||||
const ptrdiff_t offset = fault_addr - base;
|
||||
|
||||
for (unsigned i = 0; i < data->num_protected_instructions; ++i) {
|
||||
if (data->instructions[i].instr_offset == offset) {
|
||||
// Hurray again, we found the actual instruction. Tell the caller to
|
||||
// return to the landing pad.
|
||||
context->uc_mcontext.gregs[REG_RIP] =
|
||||
data->instructions[i].landing_offset + base;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} // end signal mask scope
|
||||
|
||||
// If we get here, it's not a recoverable wasm fault, so we go to the next
|
||||
// handler.
|
||||
g_thread_in_wasm_code = true;
|
||||
return false;
|
||||
}
|
||||
#endif // V8_TRAP_HANDLER_SUPPORTED && V8_OS_LINUX
|
||||
|
||||
#if V8_TRAP_HANDLER_SUPPORTED
|
||||
void HandleSignal(int signum, siginfo_t* info, void* context) {
|
||||
ucontext_t* uc = reinterpret_cast<ucontext_t*>(context);
|
||||
|
||||
if (!TryHandleSignal(signum, info, uc)) {
|
||||
// Since V8 didn't handle this signal, we want to re-raise the same signal.
|
||||
// For kernel-generated SEGV signals, we do this by restoring the default
|
||||
// SEGV handler and then returning. The fault will happen again and the
|
||||
// usual SEGV handling will happen.
|
||||
//
|
||||
// We handle user-generated signals by calling raise() instead. This is for
|
||||
// completeness. We should never actually see one of these, but just in
|
||||
// case, we do the right thing.
|
||||
struct sigaction action;
|
||||
action.sa_handler = SIG_DFL;
|
||||
sigemptyset(&action.sa_mask);
|
||||
action.sa_flags = 0;
|
||||
sigaction(signum, &action, nullptr);
|
||||
if (!IsKernelGeneratedSignal(info)) {
|
||||
raise(signum);
|
||||
}
|
||||
}
|
||||
// TryHandleSignal modifies context to change where we return to.
|
||||
}
|
||||
#endif
|
||||
} // namespace trap_handler
|
||||
} // namespace internal
|
||||
} // namespace v8
|
191
src/trap-handler/handler-outside.cc
Normal file
191
src/trap-handler/handler-outside.cc
Normal file
@ -0,0 +1,191 @@
|
||||
// Copyright 2017 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.
|
||||
|
||||
// PLEASE READ BEFORE CHANGING THIS FILE!
|
||||
//
|
||||
// This file implements the support code for the out of bounds signal handler.
|
||||
// Nothing in here actually runs in the signal handler, but the code here
|
||||
// manipulates data structures used by the signal handler so we still need to be
|
||||
// careful. In order to minimize this risk, here are some rules to follow.
|
||||
//
|
||||
// 1. Avoid introducing new external dependencies. The files in src/trap-handler
|
||||
// should be as self-contained as possible to make it easy to audit the code.
|
||||
//
|
||||
// 2. Any changes must be reviewed by someone from the crash reporting
|
||||
// or security team. Se OWNERS for suggested reviewers.
|
||||
//
|
||||
// For more information, see https://goo.gl/yMeyUY.
|
||||
//
|
||||
// For the code that runs in the signal handler itself, see handler-inside.cc.
|
||||
|
||||
#include <signal.h>
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <limits>
|
||||
|
||||
#include "src/trap-handler/trap-handler-internal.h"
|
||||
#include "src/trap-handler/trap-handler.h"
|
||||
|
||||
namespace {
|
||||
size_t gNextCodeObject = 0;
|
||||
}
|
||||
|
||||
namespace v8 {
|
||||
namespace internal {
|
||||
namespace trap_handler {
|
||||
|
||||
const size_t kInitialCodeObjectSize = 1024;
|
||||
const size_t kCodeObjectGrowthFactor = 2;
|
||||
|
||||
constexpr size_t HandlerDataSize(size_t num_protected_instructions) {
|
||||
return offsetof(CodeProtectionInfo, instructions) +
|
||||
num_protected_instructions * sizeof(ProtectedInstructionData);
|
||||
}
|
||||
|
||||
CodeProtectionInfo* CreateHandlerData(
|
||||
void* base, size_t size, size_t num_protected_instructions,
|
||||
ProtectedInstructionData* protected_instructions) {
|
||||
const size_t alloc_size = HandlerDataSize(num_protected_instructions);
|
||||
CodeProtectionInfo* data =
|
||||
reinterpret_cast<CodeProtectionInfo*>(malloc(alloc_size));
|
||||
|
||||
if (data == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
data->base = base;
|
||||
data->size = size;
|
||||
data->num_protected_instructions = num_protected_instructions;
|
||||
|
||||
memcpy(data->instructions, protected_instructions,
|
||||
num_protected_instructions * sizeof(ProtectedInstructionData));
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
void UpdateHandlerDataCodePointer(int index, void* base) {
|
||||
MetadataLock lock;
|
||||
if (static_cast<size_t>(index) >= gNumCodeObjects) {
|
||||
abort();
|
||||
}
|
||||
CodeProtectionInfo* data = gCodeObjects[index].code_info;
|
||||
data->base = base;
|
||||
}
|
||||
|
||||
int RegisterHandlerData(void* base, size_t size,
|
||||
size_t num_protected_instructions,
|
||||
ProtectedInstructionData* protected_instructions) {
|
||||
// TODO(eholk): in debug builds, make sure this data isn't already registered.
|
||||
|
||||
CodeProtectionInfo* data = CreateHandlerData(
|
||||
base, size, num_protected_instructions, protected_instructions);
|
||||
|
||||
if (data == nullptr) {
|
||||
abort();
|
||||
}
|
||||
|
||||
MetadataLock lock;
|
||||
|
||||
size_t i = gNextCodeObject;
|
||||
|
||||
// Explicitly convert std::numeric_limits<int>::max() to unsigned to avoid
|
||||
// compiler warnings about signed/unsigned comparisons. We aren't worried
|
||||
// about sign extension because we know std::numeric_limits<int>::max() is
|
||||
// positive.
|
||||
const size_t int_max = std::numeric_limits<int>::max();
|
||||
|
||||
// We didn't find an opening in the available space, so grow.
|
||||
if (i == gNumCodeObjects) {
|
||||
size_t new_size = gNumCodeObjects > 0
|
||||
? gNumCodeObjects * kCodeObjectGrowthFactor
|
||||
: kInitialCodeObjectSize;
|
||||
|
||||
// Because we must return an int, there is no point in allocating space for
|
||||
// more objects than can fit in an int.
|
||||
if (new_size > int_max) {
|
||||
new_size = int_max;
|
||||
}
|
||||
if (new_size == gNumCodeObjects) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Now that we know our new size is valid, we can go ahead and realloc the
|
||||
// array.
|
||||
gCodeObjects = static_cast<CodeProtectionInfoListEntry*>(
|
||||
realloc(gCodeObjects, sizeof(*gCodeObjects) * new_size));
|
||||
|
||||
if (gCodeObjects == nullptr) {
|
||||
abort();
|
||||
}
|
||||
|
||||
memset(gCodeObjects + gNumCodeObjects, 0,
|
||||
sizeof(*gCodeObjects) * (new_size - gNumCodeObjects));
|
||||
gNumCodeObjects = new_size;
|
||||
}
|
||||
|
||||
DCHECK(gCodeObjects[i].code_info == nullptr);
|
||||
|
||||
// Find out where the next entry should go.
|
||||
if (gCodeObjects[i].next_free == 0) {
|
||||
// if this is a fresh entry, use the next one.
|
||||
gNextCodeObject = i + 1;
|
||||
DCHECK(gNextCodeObject == gNumCodeObjects ||
|
||||
(gCodeObjects[gNextCodeObject].code_info == nullptr &&
|
||||
gCodeObjects[gNextCodeObject].next_free == 0));
|
||||
} else {
|
||||
gNextCodeObject = gCodeObjects[i].next_free - 1;
|
||||
}
|
||||
|
||||
if (i <= int_max) {
|
||||
gCodeObjects[i].code_info = data;
|
||||
return static_cast<int>(i);
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
void ReleaseHandlerData(int index) {
|
||||
// Remove the data from the global list if it's there.
|
||||
CodeProtectionInfo* data = nullptr;
|
||||
{
|
||||
MetadataLock lock;
|
||||
|
||||
data = gCodeObjects[index].code_info;
|
||||
gCodeObjects[index].code_info = nullptr;
|
||||
|
||||
// +1 because we reserve {next_entry == 0} to indicate a fresh list entry.
|
||||
gCodeObjects[index].next_free = gNextCodeObject + 1;
|
||||
gNextCodeObject = index;
|
||||
}
|
||||
// TODO(eholk): on debug builds, ensure there are no more copies in
|
||||
// the list.
|
||||
free(data);
|
||||
}
|
||||
|
||||
bool RegisterDefaultSignalHandler() {
|
||||
#if V8_TRAP_HANDLER_SUPPORTED
|
||||
struct sigaction action;
|
||||
action.sa_sigaction = HandleSignal;
|
||||
action.sa_flags = SA_SIGINFO;
|
||||
sigemptyset(&action.sa_mask);
|
||||
// {sigaction} installs a new custom segfault handler. On success, it returns
|
||||
// 0. If we get a nonzero value, we report an error to the caller by returning
|
||||
// false.
|
||||
if (sigaction(SIGSEGV, &action, nullptr) != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace trap_handler
|
||||
} // namespace internal
|
||||
} // namespace v8
|
52
src/trap-handler/handler-shared.cc
Normal file
52
src/trap-handler/handler-shared.cc
Normal file
@ -0,0 +1,52 @@
|
||||
// Copyright 2017 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.
|
||||
|
||||
// PLEASE READ BEFORE CHANGING THIS FILE!
|
||||
//
|
||||
// This file contains code that is used both inside and outside the out of
|
||||
// bounds signal handler. Because this code runs in a signal handler context,
|
||||
// use extra care when modifying this file. Here are some rules to follow.
|
||||
//
|
||||
// 1. Do not introduce any new external dependencies. This file needs
|
||||
// to be self contained so it is easy to audit everything that a
|
||||
// signal handler might do.
|
||||
//
|
||||
// 2. Any changes must be reviewed by someone from the crash reporting
|
||||
// or security team. See OWNERS for suggested reviewers.
|
||||
//
|
||||
// For more information, see https://goo.gl/yMeyUY.
|
||||
|
||||
#include "src/trap-handler/trap-handler-internal.h"
|
||||
|
||||
namespace v8 {
|
||||
namespace internal {
|
||||
namespace trap_handler {
|
||||
|
||||
THREAD_LOCAL bool g_thread_in_wasm_code = false;
|
||||
|
||||
size_t gNumCodeObjects = 0;
|
||||
CodeProtectionInfoListEntry* gCodeObjects = nullptr;
|
||||
|
||||
std::atomic_flag MetadataLock::spinlock_ = ATOMIC_FLAG_INIT;
|
||||
|
||||
MetadataLock::MetadataLock() {
|
||||
if (g_thread_in_wasm_code) {
|
||||
abort();
|
||||
}
|
||||
|
||||
while (spinlock_.test_and_set(std::memory_order::memory_order_acquire)) {
|
||||
}
|
||||
}
|
||||
|
||||
MetadataLock::~MetadataLock() {
|
||||
if (g_thread_in_wasm_code) {
|
||||
abort();
|
||||
}
|
||||
|
||||
spinlock_.clear(std::memory_order::memory_order_release);
|
||||
}
|
||||
|
||||
} // namespace trap_handler
|
||||
} // namespace internal
|
||||
} // namespace v8
|
67
src/trap-handler/trap-handler-internal.h
Normal file
67
src/trap-handler/trap-handler-internal.h
Normal file
@ -0,0 +1,67 @@
|
||||
// Copyright 2016 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 TRAP_HANDLER_INTERNAL_H_
|
||||
#define TRAP_HANDLER_INTERNAL_H_
|
||||
|
||||
// This file should not be included (even transitively) by files outside of
|
||||
// src/trap-handler.
|
||||
|
||||
#include "src/trap-handler/trap-handler.h"
|
||||
|
||||
#include <atomic>
|
||||
|
||||
namespace v8 {
|
||||
namespace internal {
|
||||
namespace trap_handler {
|
||||
|
||||
// This describes a chunk of code that the signal handler will be able to handle
|
||||
// faults in. {base} points to the beginning of the chunk, and {size} is the
|
||||
// number of bytes in the code chunk. The remainder of the struct is a list of
|
||||
// protected memory access instructions and an offset to a landing pad to handle
|
||||
// faults on that instruction.
|
||||
struct CodeProtectionInfo {
|
||||
void* base;
|
||||
size_t size;
|
||||
size_t num_protected_instructions;
|
||||
ProtectedInstructionData instructions[1];
|
||||
};
|
||||
|
||||
class MetadataLock {
|
||||
static std::atomic_flag spinlock_;
|
||||
|
||||
public:
|
||||
MetadataLock();
|
||||
~MetadataLock();
|
||||
|
||||
// We'd normally use DISALLOW_COPY_AND_ASSIGN, but we're avoiding a dependency
|
||||
// on base/macros.h
|
||||
MetadataLock(const MetadataLock&) = delete;
|
||||
void operator=(const MetadataLock&) = delete;
|
||||
};
|
||||
|
||||
#if V8_TRAP_HANDLER_SUPPORTED
|
||||
void HandleSignal(int signum, siginfo_t* info, void* context);
|
||||
#endif
|
||||
|
||||
// To enable constant time registration of handler data, we keep a free list of
|
||||
// entries in the gCodeObjects table. Each entry contains a {next_free} field,
|
||||
// which can be used to figure out where the next entry should be inserted.
|
||||
// In order to avoid having to initialize all the links to start with, we use
|
||||
// 0 to indicate that this is a fresh, never-used list entry and that therefore
|
||||
// the next entry is known to be free. If {next_entry} is greater than zero,
|
||||
// then {next_entry - 1} is the index that we should insert into next.
|
||||
struct CodeProtectionInfoListEntry {
|
||||
CodeProtectionInfo* code_info;
|
||||
size_t next_free;
|
||||
};
|
||||
|
||||
extern size_t gNumCodeObjects;
|
||||
extern CodeProtectionInfoListEntry* gCodeObjects;
|
||||
|
||||
} // namespace trap_handler
|
||||
} // namespace internal
|
||||
} // namespace v8
|
||||
|
||||
#endif // TRAP_HANDLER_INTERNAL_H_
|
@ -5,10 +5,29 @@
|
||||
#ifndef V8_TRAP_HANDLER_H_
|
||||
#define V8_TRAP_HANDLER_H_
|
||||
|
||||
#include <signal.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "src/base/build_config.h"
|
||||
#include "src/flags.h"
|
||||
#include "src/globals.h"
|
||||
|
||||
#if V8_OS_LINUX
|
||||
#include <ucontext.h>
|
||||
#endif
|
||||
|
||||
namespace v8 {
|
||||
namespace internal {
|
||||
namespace trap_handler {
|
||||
|
||||
// TODO(eholk): Support trap handlers on other platforms.
|
||||
#if V8_TARGET_ARCH_X64 && V8_OS_LINUX
|
||||
#define V8_TRAP_HANDLER_SUPPORTED 1
|
||||
#else
|
||||
#define V8_TRAP_HANDLER_SUPPORTED 0
|
||||
#endif
|
||||
|
||||
struct ProtectedInstructionData {
|
||||
// The offset of this instruction from the start of its code object.
|
||||
intptr_t instr_offset;
|
||||
@ -19,6 +38,56 @@ struct ProtectedInstructionData {
|
||||
intptr_t landing_offset;
|
||||
};
|
||||
|
||||
/// Adjusts the base code pointer.
|
||||
void UpdateHandlerDataCodePointer(int index, void* base);
|
||||
|
||||
/// Adds the handler data to the place where the signal handler will find it.
|
||||
///
|
||||
/// This returns a number that can be used to identify the handler data to
|
||||
/// UpdateHandlerDataCodePointer and ReleaseHandlerData, or -1 on failure.
|
||||
int RegisterHandlerData(void* base, size_t size,
|
||||
size_t num_protected_instructions,
|
||||
ProtectedInstructionData* protected_instructions);
|
||||
|
||||
/// Removes the data from the master list and frees any memory, if necessary.
|
||||
void ReleaseHandlerData(int index);
|
||||
|
||||
#if V8_OS_WIN
|
||||
#define THREAD_LOCAL __declspec(thread)
|
||||
#elif V8_OS_ANDROID
|
||||
// TODO(eholk): fix this before enabling for trap handlers for Android.
|
||||
#define THREAD_LOCAL
|
||||
#else
|
||||
#define THREAD_LOCAL __thread
|
||||
#endif
|
||||
|
||||
inline bool UseTrapHandler() {
|
||||
return FLAG_wasm_trap_handler && V8_TRAP_HANDLER_SUPPORTED;
|
||||
}
|
||||
|
||||
extern THREAD_LOCAL bool g_thread_in_wasm_code;
|
||||
|
||||
inline bool IsThreadInWasm() { return g_thread_in_wasm_code; }
|
||||
|
||||
inline void SetThreadInWasm() {
|
||||
if (UseTrapHandler()) {
|
||||
DCHECK(!IsThreadInWasm());
|
||||
g_thread_in_wasm_code = true;
|
||||
}
|
||||
}
|
||||
inline void ClearThreadInWasm() {
|
||||
if (UseTrapHandler()) {
|
||||
DCHECK(IsThreadInWasm());
|
||||
g_thread_in_wasm_code = false;
|
||||
}
|
||||
}
|
||||
|
||||
bool RegisterDefaultSignalHandler();
|
||||
|
||||
#if V8_OS_LINUX
|
||||
bool TryHandleSignal(int signum, siginfo_t* info, ucontext_t* context);
|
||||
#endif // V8_OS_LINUX
|
||||
|
||||
} // namespace trap_handler
|
||||
} // namespace internal
|
||||
} // namespace v8
|
||||
|
@ -1277,7 +1277,10 @@
|
||||
'transitions-inl.h',
|
||||
'transitions.cc',
|
||||
'transitions.h',
|
||||
'trap-handler/handler-outside.cc',
|
||||
'trap-handler/handler-shared.cc',
|
||||
'trap-handler/trap-handler.h',
|
||||
'trap-handler/trap-handler-internal.h',
|
||||
'type-hints.cc',
|
||||
'type-hints.h',
|
||||
'type-info.cc',
|
||||
@ -1670,6 +1673,9 @@
|
||||
'third_party/valgrind/valgrind.h',
|
||||
],
|
||||
}],
|
||||
['v8_target_arch=="x64" and OS=="linux"', {
|
||||
'sources': ['trap-handler/handler-inside.cc']
|
||||
}],
|
||||
['v8_target_arch=="ppc" or v8_target_arch=="ppc64"', {
|
||||
'sources': [ ### gcmole(arch:ppc) ###
|
||||
'builtins/ppc/builtins-ppc.cc',
|
||||
|
@ -14,6 +14,7 @@
|
||||
#include "src/property-descriptor.h"
|
||||
#include "src/simulator.h"
|
||||
#include "src/snapshot/snapshot.h"
|
||||
#include "src/trap-handler/trap-handler.h"
|
||||
#include "src/v8.h"
|
||||
|
||||
#include "src/asmjs/asm-wasm-builder.h"
|
||||
@ -671,6 +672,18 @@ static void InstanceFinalizer(const v8::WeakCallbackInfo<void>& data) {
|
||||
DCHECK(compiled_module->has_weak_wasm_module());
|
||||
WeakCell* weak_wasm_module = compiled_module->ptr_to_weak_wasm_module();
|
||||
|
||||
if (trap_handler::UseTrapHandler()) {
|
||||
Handle<FixedArray> code_table = compiled_module->code_table();
|
||||
for (int i = 0; i < code_table->length(); ++i) {
|
||||
Handle<Code> code = code_table->GetValueChecked<Code>(isolate, i);
|
||||
int index = code->trap_handler_index()->value();
|
||||
if (index >= 0) {
|
||||
trap_handler::ReleaseHandlerData(index);
|
||||
code->set_trap_handler_index(Smi::FromInt(-1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// weak_wasm_module may have been cleared, meaning the module object
|
||||
// was GC-ed. In that case, there won't be any new instances created,
|
||||
// and we don't need to maintain the links between instances.
|
||||
@ -1230,7 +1243,7 @@ class InstantiationHelper {
|
||||
//--------------------------------------------------------------------------
|
||||
// Unpack and notify signal handler of protected instructions.
|
||||
//--------------------------------------------------------------------------
|
||||
if (FLAG_wasm_trap_handler) {
|
||||
if (trap_handler::UseTrapHandler()) {
|
||||
for (int i = 0; i < code_table->length(); ++i) {
|
||||
Handle<Code> code = code_table->GetValueChecked<Code>(isolate_, i);
|
||||
|
||||
@ -1251,8 +1264,15 @@ class InstantiationHelper {
|
||||
reinterpret_cast<intptr_t>(it.rinfo()->pc()) - base;
|
||||
unpacked.emplace_back(data);
|
||||
}
|
||||
// TODO(eholk): Register the protected instruction information once the
|
||||
// trap handler is in place.
|
||||
if (unpacked.size() > 0) {
|
||||
int size = code->CodeSize();
|
||||
const int index =
|
||||
RegisterHandlerData(reinterpret_cast<void*>(base), size,
|
||||
unpacked.size(), &unpacked[0]);
|
||||
// TODO(eholk): if index is negative, fail.
|
||||
DCHECK(index >= 0);
|
||||
code->set_trap_handler_index(Smi::FromInt(index));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -2153,7 +2173,8 @@ Handle<JSArrayBuffer> GrowMemoryBuffer(Isolate* isolate,
|
||||
// TODO(gdeepti): Change the protection here instead of allocating a new
|
||||
// buffer before guard regions are turned on, see issue #5886.
|
||||
const bool enable_guard_regions =
|
||||
!old_buffer.is_null() && old_buffer->has_guard_region();
|
||||
(old_buffer.is_null() && EnableGuardRegions()) ||
|
||||
(!old_buffer.is_null() && old_buffer->has_guard_region());
|
||||
Handle<JSArrayBuffer> new_buffer =
|
||||
NewArrayBuffer(isolate, new_size, enable_guard_regions);
|
||||
if (new_buffer.is_null()) return new_buffer;
|
||||
|
@ -31,6 +31,7 @@
|
||||
#include "include/libplatform/libplatform.h"
|
||||
#include "src/debug/debug.h"
|
||||
#include "src/objects-inl.h"
|
||||
#include "src/trap-handler/trap-handler.h"
|
||||
#include "test/cctest/print-extension.h"
|
||||
#include "test/cctest/profiler-extension.h"
|
||||
#include "test/cctest/trace-extension.h"
|
||||
@ -268,6 +269,10 @@ int main(int argc, char* argv[]) {
|
||||
v8::V8::Initialize();
|
||||
v8::V8::InitializeExternalStartupData(argv[0]);
|
||||
|
||||
if (i::trap_handler::UseTrapHandler()) {
|
||||
v8::V8::RegisterDefaultSignalHandler();
|
||||
}
|
||||
|
||||
CcTestArrayBufferAllocator array_buffer_allocator;
|
||||
CcTest::set_array_buffer_allocator(&array_buffer_allocator);
|
||||
|
||||
|
@ -3,6 +3,7 @@
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "src/assembler-inl.h"
|
||||
#include "src/trap-handler/trap-handler.h"
|
||||
#include "src/wasm/wasm-macro-gen.h"
|
||||
#include "test/cctest/cctest.h"
|
||||
#include "test/cctest/compiler/value-helper.h"
|
||||
@ -98,6 +99,14 @@ TEST(Unreachable) {
|
||||
|
||||
// Trigger a trap for loading from out-of-bounds.
|
||||
TEST(IllegalLoad) {
|
||||
if (trap_handler::UseTrapHandler()) {
|
||||
// r.module().AddMemory() does not allocate guard pages, so we skip this
|
||||
// test for now when using trap handlers. The simple out of bounds access
|
||||
// case is covered by mjsunit tests, so we are still getting test coverage.
|
||||
//
|
||||
// TODO(eholk): make this test work with trap handlers.
|
||||
return;
|
||||
}
|
||||
WasmRunner<void> r(kExecuteCompiled);
|
||||
TestSignatures sigs;
|
||||
// Set the execution context, such that a runtime error can be thrown.
|
||||
|
@ -23,6 +23,7 @@
|
||||
#include "src/compiler/pipeline.h"
|
||||
#include "src/compiler/wasm-compiler.h"
|
||||
#include "src/compiler/zone-stats.h"
|
||||
#include "src/trap-handler/trap-handler.h"
|
||||
#include "src/wasm/function-body-decoder.h"
|
||||
#include "src/wasm/wasm-external-refs.h"
|
||||
#include "src/wasm/wasm-interpreter.h"
|
||||
@ -824,17 +825,29 @@ bool WasmRunnerBase::trap_happened;
|
||||
TEST(RunWasmInterpreted_##name) { RunWasm_##name(kExecuteInterpreted); } \
|
||||
void RunWasm_##name(WasmExecutionMode execution_mode)
|
||||
|
||||
#define WASM_EXEC_TEST_WITH_TRAP(name) \
|
||||
void RunWasm_##name(WasmExecutionMode execution_mode); \
|
||||
TEST(RunWasmCompiled_##name) { RunWasm_##name(kExecuteCompiled); } \
|
||||
void RunWasm_##name(WasmExecutionMode execution_mode); \
|
||||
TEST(RunWasmCompiledWithoutTrapIf_##name) { \
|
||||
bool trap_if = FLAG_wasm_trap_if; \
|
||||
FLAG_wasm_trap_if = false; \
|
||||
RunWasm_##name(kExecuteCompiled); \
|
||||
FLAG_wasm_trap_if = trap_if; \
|
||||
} \
|
||||
TEST(RunWasmInterpreted_##name) { RunWasm_##name(kExecuteInterpreted); } \
|
||||
#define WASM_EXEC_TEST_WITH_TRAP(name) \
|
||||
void RunWasm_##name(WasmExecutionMode execution_mode); \
|
||||
TEST(RunWasmCompiled_##name) { \
|
||||
if (trap_handler::UseTrapHandler()) { \
|
||||
return; \
|
||||
} \
|
||||
RunWasm_##name(kExecuteCompiled); \
|
||||
} \
|
||||
TEST(RunWasmCompiledWithoutTrapIf_##name) { \
|
||||
if (trap_handler::UseTrapHandler()) { \
|
||||
return; \
|
||||
} \
|
||||
bool trap_if = FLAG_wasm_trap_if; \
|
||||
FLAG_wasm_trap_if = true; \
|
||||
RunWasm_##name(kExecuteCompiled); \
|
||||
FLAG_wasm_trap_if = trap_if; \
|
||||
} \
|
||||
TEST(RunWasmInterpreted_##name) { \
|
||||
if (trap_handler::UseTrapHandler()) { \
|
||||
return; \
|
||||
} \
|
||||
RunWasm_##name(kExecuteInterpreted); \
|
||||
} \
|
||||
void RunWasm_##name(WasmExecutionMode execution_mode)
|
||||
|
||||
#define WASM_EXEC_COMPILED_TEST(name) \
|
||||
|
@ -14,7 +14,7 @@ ALL_VARIANT_FLAGS = {
|
||||
"ignition": [["--ignition"]],
|
||||
"ignition_turbofan": [["--ignition-staging", "--turbo"]],
|
||||
"asm_wasm": [["--validate-asm"]],
|
||||
"wasm_traps": [["--wasm_guard_pages", "--invoke-weak-callbacks"]],
|
||||
"wasm_traps": [["--wasm_guard_pages", "--wasm_trap_handler", "--invoke-weak-callbacks"]],
|
||||
}
|
||||
|
||||
# FAST_VARIANTS implies no --always-opt.
|
||||
@ -28,7 +28,7 @@ FAST_VARIANT_FLAGS = {
|
||||
"ignition": [["--ignition"]],
|
||||
"ignition_turbofan": [["--ignition-staging", "--turbo"]],
|
||||
"asm_wasm": [["--validate-asm"]],
|
||||
"wasm_traps": [["--wasm_guard_pages", "--invoke-weak-callbacks"]],
|
||||
"wasm_traps": [["--wasm_guard_pages", "--wasm_trap_handler", "--invoke-weak-callbacks"]],
|
||||
}
|
||||
|
||||
ALL_VARIANTS = set(["default", "stress", "turbofan", "turbofan_opt",
|
||||
|
Loading…
Reference in New Issue
Block a user