Reland "[vm] When run under TSAN use longjmp() to skip over C++ frames before manually unwinding to the catch entry"
TSAN instruments C++ code by adding prologue/epilogue code which maintains a shadow stack. Using setjmp()/longjmp() is intercepted by TSAN and correspondingly unwinds the shadow stack. When Dart VM throws exceptions we call the JumpToFrame stub from C++ which will directly reset the stack to the exception handler catch entry. This leaves the TSAN shadow stack unchanged. This means whenever an exception is thrown we leak frames in TSAN's shadow stack. Due to using a fixed-size shadow stack, it will cause a buffer-overflow in TSAN when too many such frame leaks happen. This can cause arbitrary memory to be overriden, leading to awkward crashes. This is especially an issue on the "iso-stres" builder because it launches - in the same process - *many* small tests, more easily hitting that limit. This CL will workaround the TSAN issue by making runtime call save it's state via setjmp() and make exception throughing process go via longjmp() (which TSAN will intercept) before actually calling the JumpToFrame stub. => This will ensure the TSAN shadow stack is correctly maintained. The [jmp_buf]'s encoding of register state is non-trivial (e.g. it uses XOR'ing of the actual saved state under certain glibc versions). So we store any state we need to pass to the target of the `longjmp()` on the [Thread] instead of overriding the [jmp_buf]s register state with the arguments. Issue https://github.com/dart-lang/sdk/issues/47472#issuecomment-948235479 TEST=vm/dart{,_2}/regress47472_test.dart Change-Id: Ifbf6580aa15bcce54d0584cdc3cd18cc19be0a9c Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/222300 Reviewed-by: Ryan Macnak <rmacnak@google.com> Commit-Queue: Martin Kustermann <kustermann@google.com>
This commit is contained in:
committed by
Commit Bot
parent
016e09f080
commit
228c52ed4a
@@ -21,4 +21,16 @@ extern "C" void __tsan_release(void* addr);
|
||||
#define NO_SANITIZE_THREAD
|
||||
#endif
|
||||
|
||||
#if defined(USING_THREAD_SANITIZER)
|
||||
#define DO_IF_TSAN(CODE) CODE
|
||||
#else
|
||||
#define DO_IF_TSAN(CODE)
|
||||
#endif
|
||||
|
||||
#if defined(USING_THREAD_SANITIZER)
|
||||
#define DO_IF_NOT_TSAN(CODE)
|
||||
#else
|
||||
#define DO_IF_NOT_TSAN(CODE) CODE
|
||||
#endif
|
||||
|
||||
#endif // RUNTIME_PLATFORM_THREAD_SANITIZER_H_
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
main() {
|
||||
for (int i = 0; i < 1000000; ++i) {
|
||||
try {
|
||||
throw 'a';
|
||||
} catch (e, s) {}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
main() {
|
||||
for (int i = 0; i < 1000000; ++i) {
|
||||
try {
|
||||
throw 'a';
|
||||
} catch (e, s) {}
|
||||
}
|
||||
}
|
||||
@@ -1154,8 +1154,9 @@ void NativeCallInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
|
||||
// Push the result place holder initialized to NULL.
|
||||
__ PushObject(Object::null_object());
|
||||
|
||||
// Pass a pointer to the first argument in RAX.
|
||||
__ leaq(RAX, compiler::Address(RSP, ArgumentCount() * kWordSize));
|
||||
// Pass a pointer to the first argument in R13 (we avoid using RAX here to
|
||||
// simplify the stub code that will call native code).
|
||||
__ leaq(R13, compiler::Address(RSP, ArgumentCount() * kWordSize));
|
||||
|
||||
__ LoadImmediate(R10, compiler::Immediate(argc_tag));
|
||||
const Code* stub;
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
#include "platform/thread_sanitizer.h"
|
||||
|
||||
#include "vm/compiler/runtime_api.h"
|
||||
|
||||
#include "vm/object.h"
|
||||
|
||||
@@ -19,7 +19,9 @@
|
||||
// in compiler::target namespace.
|
||||
|
||||
#include "platform/globals.h"
|
||||
#include "platform/thread_sanitizer.h"
|
||||
#include "platform/utils.h"
|
||||
|
||||
#include "vm/allocation.h"
|
||||
#include "vm/bitfield.h"
|
||||
#include "vm/bss_relocs.h"
|
||||
@@ -1059,6 +1061,15 @@ class MonomorphicSmiableCall : public AllStatic {
|
||||
FINAL_CLASS();
|
||||
};
|
||||
|
||||
class TsanUtils : public AllStatic {
|
||||
public:
|
||||
static word setjmp_function_offset();
|
||||
static word setjmp_buffer_offset();
|
||||
static word exception_pc_offset();
|
||||
static word exception_sp_offset();
|
||||
static word exception_fp_offset();
|
||||
};
|
||||
|
||||
class Thread : public AllStatic {
|
||||
public:
|
||||
static word api_top_scope_offset();
|
||||
@@ -1122,6 +1133,8 @@ class Thread : public AllStatic {
|
||||
|
||||
static word callback_code_offset();
|
||||
static word callback_stack_return_offset();
|
||||
static word tsan_utils_offset();
|
||||
static word jump_to_frame_entry_point_offset();
|
||||
|
||||
static word AllocateArray_entry_point_offset();
|
||||
static word write_barrier_code_offset();
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -286,6 +286,13 @@
|
||||
FIELD(Thread, heap_base_offset) \
|
||||
FIELD(Thread, callback_code_offset) \
|
||||
FIELD(Thread, callback_stack_return_offset) \
|
||||
FIELD(Thread, jump_to_frame_entry_point_offset) \
|
||||
FIELD(Thread, tsan_utils_offset) \
|
||||
FIELD(TsanUtils, setjmp_function_offset) \
|
||||
FIELD(TsanUtils, setjmp_buffer_offset) \
|
||||
FIELD(TsanUtils, exception_pc_offset) \
|
||||
FIELD(TsanUtils, exception_sp_offset) \
|
||||
FIELD(TsanUtils, exception_fp_offset) \
|
||||
FIELD(TimelineStream, enabled_offset) \
|
||||
FIELD(TwoByteString, data_offset) \
|
||||
FIELD(Type, arguments_offset) \
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
#include <setjmp.h>
|
||||
|
||||
#include "vm/compiler/runtime_api.h"
|
||||
#include "vm/globals.h"
|
||||
|
||||
@@ -58,6 +60,102 @@ void StubCodeCompiler::EnsureIsNewOrRemembered(Assembler* assembler,
|
||||
__ Bind(&done);
|
||||
}
|
||||
|
||||
// In TSAN mode the runtime will throw an exception using an intermediary
|
||||
// longjmp() call to unwind the C frames in a way that TSAN can understand.
|
||||
//
|
||||
// This wrapper will setup a [jmp_buf] on the stack and initialize it to be a
|
||||
// target for a possible longjmp(). In the exceptional case we'll forward
|
||||
// control of execution to the usual JumpToFrame stub.
|
||||
//
|
||||
// In non-TSAN mode this will do nothing and the runtime will call the
|
||||
// JumpToFrame stub directly.
|
||||
//
|
||||
// The callback [fun] may be invoked with a modified [RSP] due to allocating
|
||||
// a [jmp_buf] allocating structure on the stack (as well as the saved old
|
||||
// [Thread::tsan_utils_->setjmp_buffer_]).
|
||||
static void WithExceptionCatchingTrampoline(Assembler* assembler,
|
||||
std::function<void()> fun) {
|
||||
#if defined(USING_THREAD_SANITIZER)
|
||||
const Register kTsanUtilsReg = RAX;
|
||||
|
||||
// Reserve space for arguments and align frame before entering C++ world.
|
||||
const intptr_t kJumpBufferSize = sizeof(jmp_buf);
|
||||
// Save & Restore the volatile CPU registers across the setjmp() call.
|
||||
const RegisterSet volatile_registers(
|
||||
CallingConventions::kVolatileCpuRegisters & ~(1 << RAX),
|
||||
/*fpu_registers=*/0);
|
||||
|
||||
const Register kSavedRspReg = R12;
|
||||
COMPILE_ASSERT(IsCalleeSavedRegister(kSavedRspReg));
|
||||
// We rely on THR being preserved across the setjmp() call.
|
||||
COMPILE_ASSERT(IsCalleeSavedRegister(THR));
|
||||
|
||||
Label do_native_call;
|
||||
|
||||
// Save old jmp_buf.
|
||||
__ movq(kTsanUtilsReg, Address(THR, target::Thread::tsan_utils_offset()));
|
||||
__ pushq(Address(kTsanUtilsReg, target::TsanUtils::setjmp_buffer_offset()));
|
||||
|
||||
// Allocate jmp_buf struct on stack & remember pointer to it on the
|
||||
// [Thread::tsan_utils_->setjmp_buffer] (which exceptions.cc will longjmp()
|
||||
// to)
|
||||
__ AddImmediate(RSP, Immediate(-kJumpBufferSize));
|
||||
__ movq(Address(kTsanUtilsReg, target::TsanUtils::setjmp_buffer_offset()),
|
||||
RSP);
|
||||
|
||||
// Call setjmp() with a pointer to the allocated jmp_buf struct.
|
||||
__ MoveRegister(CallingConventions::kArg1Reg, RSP);
|
||||
__ PushRegisters(volatile_registers);
|
||||
if (OS::ActivationFrameAlignment() > 1) {
|
||||
__ MoveRegister(kSavedRspReg, RSP);
|
||||
__ andq(RSP, Immediate(~(OS::ActivationFrameAlignment() - 1)));
|
||||
}
|
||||
__ movq(kTsanUtilsReg, Address(THR, target::Thread::tsan_utils_offset()));
|
||||
__ CallCFunction(
|
||||
Address(kTsanUtilsReg, target::TsanUtils::setjmp_function_offset()),
|
||||
/*restore_rsp=*/true);
|
||||
if (OS::ActivationFrameAlignment() > 1) {
|
||||
__ MoveRegister(RSP, kSavedRspReg);
|
||||
}
|
||||
__ PopRegisters(volatile_registers);
|
||||
|
||||
// We are the target of a longjmp() iff setjmp() returns non-0.
|
||||
__ CompareImmediate(RAX, 0);
|
||||
__ BranchIf(EQUAL, &do_native_call);
|
||||
|
||||
// We are the target of a longjmp: Cleanup the stack and tail-call the
|
||||
// JumpToFrame stub which will take care of unwinding the stack and hand
|
||||
// execution to the catch entry.
|
||||
__ AddImmediate(RSP, Immediate(kJumpBufferSize));
|
||||
__ movq(kTsanUtilsReg, Address(THR, target::Thread::tsan_utils_offset()));
|
||||
__ popq(Address(kTsanUtilsReg, target::TsanUtils::setjmp_buffer_offset()));
|
||||
|
||||
__ movq(CallingConventions::kArg1Reg,
|
||||
Address(kTsanUtilsReg, target::TsanUtils::exception_pc_offset()));
|
||||
__ movq(CallingConventions::kArg2Reg,
|
||||
Address(kTsanUtilsReg, target::TsanUtils::exception_sp_offset()));
|
||||
__ movq(CallingConventions::kArg3Reg,
|
||||
Address(kTsanUtilsReg, target::TsanUtils::exception_fp_offset()));
|
||||
__ MoveRegister(CallingConventions::kArg4Reg, THR);
|
||||
__ jmp(Address(THR, target::Thread::jump_to_frame_entry_point_offset()));
|
||||
|
||||
// We leave the created [jump_buf] structure on the stack as well as the
|
||||
// pushed old [Thread::tsan_utils_->setjmp_buffer_].
|
||||
__ Bind(&do_native_call);
|
||||
__ MoveRegister(kSavedRspReg, RSP);
|
||||
#endif // defined(USING_THREAD_SANITIZER)
|
||||
|
||||
fun();
|
||||
|
||||
#if defined(USING_THREAD_SANITIZER)
|
||||
__ MoveRegister(RSP, kSavedRspReg);
|
||||
__ AddImmediate(RSP, Immediate(kJumpBufferSize));
|
||||
const Register kTsanUtilsReg2 = kSavedRspReg;
|
||||
__ movq(kTsanUtilsReg2, Address(THR, target::Thread::tsan_utils_offset()));
|
||||
__ popq(Address(kTsanUtilsReg2, target::TsanUtils::setjmp_buffer_offset()));
|
||||
#endif // defined(USING_THREAD_SANITIZER)
|
||||
}
|
||||
|
||||
// Input parameters:
|
||||
// RSP : points to return address.
|
||||
// RSP + 8 : address of last argument in argument array.
|
||||
@@ -99,51 +197,54 @@ void StubCodeCompiler::GenerateCallToRuntimeStub(Assembler* assembler) {
|
||||
// Mark that the thread is executing VM code.
|
||||
__ movq(Assembler::VMTagAddress(), RBX);
|
||||
|
||||
// Reserve space for arguments and align frame before entering C++ world.
|
||||
__ subq(RSP, Immediate(target::NativeArguments::StructSize()));
|
||||
if (OS::ActivationFrameAlignment() > 1) {
|
||||
__ andq(RSP, Immediate(~(OS::ActivationFrameAlignment() - 1)));
|
||||
}
|
||||
WithExceptionCatchingTrampoline(assembler, [&]() {
|
||||
// Reserve space for arguments and align frame before entering C++ world.
|
||||
__ subq(RSP, Immediate(target::NativeArguments::StructSize()));
|
||||
if (OS::ActivationFrameAlignment() > 1) {
|
||||
__ andq(RSP, Immediate(~(OS::ActivationFrameAlignment() - 1)));
|
||||
}
|
||||
|
||||
// Pass target::NativeArguments structure by value and call runtime.
|
||||
__ movq(Address(RSP, thread_offset), THR); // Set thread in NativeArgs.
|
||||
// There are no runtime calls to closures, so we do not need to set the tag
|
||||
// bits kClosureFunctionBit and kInstanceFunctionBit in argc_tag_.
|
||||
__ movq(Address(RSP, argc_tag_offset),
|
||||
R10); // Set argc in target::NativeArguments.
|
||||
// Compute argv.
|
||||
__ leaq(RAX,
|
||||
Address(RBP, R10, TIMES_8,
|
||||
target::frame_layout.param_end_from_fp * target::kWordSize));
|
||||
__ movq(Address(RSP, argv_offset),
|
||||
RAX); // Set argv in target::NativeArguments.
|
||||
__ addq(RAX,
|
||||
Immediate(1 * target::kWordSize)); // Retval is next to 1st argument.
|
||||
__ movq(Address(RSP, retval_offset),
|
||||
RAX); // Set retval in target::NativeArguments.
|
||||
// Pass target::NativeArguments structure by value and call runtime.
|
||||
__ movq(Address(RSP, thread_offset), THR); // Set thread in NativeArgs.
|
||||
// There are no runtime calls to closures, so we do not need to set the tag
|
||||
// bits kClosureFunctionBit and kInstanceFunctionBit in argc_tag_.
|
||||
__ movq(Address(RSP, argc_tag_offset),
|
||||
R10); // Set argc in target::NativeArguments.
|
||||
// Compute argv.
|
||||
__ leaq(RAX, Address(RBP, R10, TIMES_8,
|
||||
target::frame_layout.param_end_from_fp *
|
||||
target::kWordSize));
|
||||
__ movq(Address(RSP, argv_offset),
|
||||
RAX); // Set argv in target::NativeArguments.
|
||||
__ addq(
|
||||
RAX,
|
||||
Immediate(1 * target::kWordSize)); // Retval is next to 1st argument.
|
||||
__ movq(Address(RSP, retval_offset),
|
||||
RAX); // Set retval in target::NativeArguments.
|
||||
#if defined(DART_TARGET_OS_WINDOWS)
|
||||
ASSERT(target::NativeArguments::StructSize() >
|
||||
CallingConventions::kRegisterTransferLimit);
|
||||
__ movq(CallingConventions::kArg1Reg, RSP);
|
||||
ASSERT(target::NativeArguments::StructSize() >
|
||||
CallingConventions::kRegisterTransferLimit);
|
||||
__ movq(CallingConventions::kArg1Reg, RSP);
|
||||
#endif
|
||||
__ CallCFunction(RBX);
|
||||
__ CallCFunction(RBX);
|
||||
|
||||
// Mark that the thread is executing Dart code.
|
||||
__ movq(Assembler::VMTagAddress(), Immediate(VMTag::kDartTagId));
|
||||
// Mark that the thread is executing Dart code.
|
||||
__ movq(Assembler::VMTagAddress(), Immediate(VMTag::kDartTagId));
|
||||
|
||||
// Mark that the thread has not exited generated Dart code.
|
||||
__ movq(Address(THR, target::Thread::exit_through_ffi_offset()),
|
||||
Immediate(0));
|
||||
// Mark that the thread has not exited generated Dart code.
|
||||
__ movq(Address(THR, target::Thread::exit_through_ffi_offset()),
|
||||
Immediate(0));
|
||||
|
||||
// Reset exit frame information in Isolate's mutator thread structure.
|
||||
__ movq(Address(THR, target::Thread::top_exit_frame_info_offset()),
|
||||
Immediate(0));
|
||||
// Reset exit frame information in Isolate's mutator thread structure.
|
||||
__ movq(Address(THR, target::Thread::top_exit_frame_info_offset()),
|
||||
Immediate(0));
|
||||
|
||||
// Restore the global object pool after returning from runtime (old space is
|
||||
// moving, so the GOP could have been relocated).
|
||||
if (FLAG_precompiled_mode) {
|
||||
__ movq(PP, Address(THR, target::Thread::global_object_pool_offset()));
|
||||
}
|
||||
// Restore the global object pool after returning from runtime (old space is
|
||||
// moving, so the GOP could have been relocated).
|
||||
if (FLAG_precompiled_mode) {
|
||||
__ movq(PP, Address(THR, target::Thread::global_object_pool_offset()));
|
||||
}
|
||||
});
|
||||
|
||||
__ LeaveStubFrame();
|
||||
|
||||
@@ -565,7 +666,7 @@ void StubCodeCompiler::GenerateRangeError(Assembler* assembler,
|
||||
// Input parameters:
|
||||
// RSP : points to return address.
|
||||
// RSP + 8 : address of return value.
|
||||
// RAX : address of first argument in argument array.
|
||||
// R13 : address of first argument in argument array.
|
||||
// RBX : address of the native function to call.
|
||||
// R10 : argc_tag including number of arguments and function kind.
|
||||
static void GenerateCallNativeWithWrapperStub(Assembler* assembler,
|
||||
@@ -605,49 +706,51 @@ static void GenerateCallNativeWithWrapperStub(Assembler* assembler,
|
||||
// Mark that the thread is executing native code.
|
||||
__ movq(Assembler::VMTagAddress(), RBX);
|
||||
|
||||
// Reserve space for the native arguments structure passed on the stack (the
|
||||
// outgoing pointer parameter to the native arguments structure is passed in
|
||||
// RDI) and align frame before entering the C++ world.
|
||||
__ subq(RSP, Immediate(target::NativeArguments::StructSize()));
|
||||
if (OS::ActivationFrameAlignment() > 1) {
|
||||
__ andq(RSP, Immediate(~(OS::ActivationFrameAlignment() - 1)));
|
||||
}
|
||||
WithExceptionCatchingTrampoline(assembler, [&]() {
|
||||
// Reserve space for the native arguments structure passed on the stack (the
|
||||
// outgoing pointer parameter to the native arguments structure is passed in
|
||||
// RDI) and align frame before entering the C++ world.
|
||||
__ subq(RSP, Immediate(target::NativeArguments::StructSize()));
|
||||
if (OS::ActivationFrameAlignment() > 1) {
|
||||
__ andq(RSP, Immediate(~(OS::ActivationFrameAlignment() - 1)));
|
||||
}
|
||||
|
||||
// Pass target::NativeArguments structure by value and call native function.
|
||||
__ movq(Address(RSP, thread_offset), THR); // Set thread in NativeArgs.
|
||||
__ movq(Address(RSP, argc_tag_offset),
|
||||
R10); // Set argc in target::NativeArguments.
|
||||
__ movq(Address(RSP, argv_offset),
|
||||
RAX); // Set argv in target::NativeArguments.
|
||||
__ leaq(RAX,
|
||||
Address(RBP, 2 * target::kWordSize)); // Compute return value addr.
|
||||
__ movq(Address(RSP, retval_offset),
|
||||
RAX); // Set retval in target::NativeArguments.
|
||||
// Pass target::NativeArguments structure by value and call native function.
|
||||
__ movq(Address(RSP, thread_offset), THR); // Set thread in NativeArgs.
|
||||
__ movq(Address(RSP, argc_tag_offset),
|
||||
R10); // Set argc in target::NativeArguments.
|
||||
__ movq(Address(RSP, argv_offset),
|
||||
R13); // Set argv in target::NativeArguments.
|
||||
__ leaq(RAX,
|
||||
Address(RBP, 2 * target::kWordSize)); // Compute return value addr.
|
||||
__ movq(Address(RSP, retval_offset),
|
||||
RAX); // Set retval in target::NativeArguments.
|
||||
|
||||
// Pass the pointer to the target::NativeArguments.
|
||||
__ movq(CallingConventions::kArg1Reg, RSP);
|
||||
// Pass pointer to function entrypoint.
|
||||
__ movq(CallingConventions::kArg2Reg, RBX);
|
||||
// Pass the pointer to the target::NativeArguments.
|
||||
__ movq(CallingConventions::kArg1Reg, RSP);
|
||||
// Pass pointer to function entrypoint.
|
||||
__ movq(CallingConventions::kArg2Reg, RBX);
|
||||
|
||||
__ movq(RAX, wrapper_address);
|
||||
__ CallCFunction(RAX);
|
||||
__ movq(RAX, wrapper_address);
|
||||
__ CallCFunction(RAX);
|
||||
|
||||
// Mark that the thread is executing Dart code.
|
||||
__ movq(Assembler::VMTagAddress(), Immediate(VMTag::kDartTagId));
|
||||
// Mark that the thread is executing Dart code.
|
||||
__ movq(Assembler::VMTagAddress(), Immediate(VMTag::kDartTagId));
|
||||
|
||||
// Mark that the thread has not exited generated Dart code.
|
||||
__ movq(Address(THR, target::Thread::exit_through_ffi_offset()),
|
||||
Immediate(0));
|
||||
// Mark that the thread has not exited generated Dart code.
|
||||
__ movq(Address(THR, target::Thread::exit_through_ffi_offset()),
|
||||
Immediate(0));
|
||||
|
||||
// Reset exit frame information in Isolate's mutator thread structure.
|
||||
__ movq(Address(THR, target::Thread::top_exit_frame_info_offset()),
|
||||
Immediate(0));
|
||||
// Reset exit frame information in Isolate's mutator thread structure.
|
||||
__ movq(Address(THR, target::Thread::top_exit_frame_info_offset()),
|
||||
Immediate(0));
|
||||
|
||||
// Restore the global object pool after returning from runtime (old space is
|
||||
// moving, so the GOP could have been relocated).
|
||||
if (FLAG_precompiled_mode) {
|
||||
__ movq(PP, Address(THR, target::Thread::global_object_pool_offset()));
|
||||
}
|
||||
// Restore the global object pool after returning from runtime (old space is
|
||||
// moving, so the GOP could have been relocated).
|
||||
if (FLAG_precompiled_mode) {
|
||||
__ movq(PP, Address(THR, target::Thread::global_object_pool_offset()));
|
||||
}
|
||||
});
|
||||
|
||||
__ LeaveStubFrame();
|
||||
__ ret();
|
||||
|
||||
@@ -2,9 +2,12 @@
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
#include <setjmp.h>
|
||||
|
||||
#include "vm/exceptions.h"
|
||||
|
||||
#include "platform/address_sanitizer.h"
|
||||
#include "platform/thread_sanitizer.h"
|
||||
|
||||
#include "lib/stacktrace.h"
|
||||
|
||||
@@ -612,13 +615,6 @@ void Exceptions::JumpToFrame(Thread* thread,
|
||||
// in the previous frames.
|
||||
StackResource::Unwind(thread);
|
||||
|
||||
// Call a stub to set up the exception object in kExceptionObjectReg,
|
||||
// to set up the stacktrace object in kStackTraceObjectReg, and to
|
||||
// continue execution at the given pc in the given frame.
|
||||
typedef void (*ExcpHandler)(uword, uword, uword, Thread*);
|
||||
ExcpHandler func =
|
||||
reinterpret_cast<ExcpHandler>(StubCode::JumpToFrame().EntryPoint());
|
||||
|
||||
// Unpoison the stack before we tear it down in the generated stub code.
|
||||
uword current_sp = OSThread::GetCurrentStackPointer() - 1024;
|
||||
ASAN_UNPOISON(reinterpret_cast<void*>(current_sp),
|
||||
@@ -635,7 +631,25 @@ void Exceptions::JumpToFrame(Thread* thread,
|
||||
// The shadow call stack register will be restored by the JumpToFrame stub.
|
||||
#endif
|
||||
|
||||
#if defined(USING_THREAD_SANITIZER)
|
||||
if (thread->exit_through_ffi() == Thread::kExitThroughRuntimeCall) {
|
||||
auto tsan_utils = thread->tsan_utils();
|
||||
tsan_utils->exception_pc = program_counter;
|
||||
tsan_utils->exception_sp = stack_pointer;
|
||||
tsan_utils->exception_fp = frame_pointer;
|
||||
longjmp(*(tsan_utils->setjmp_buffer), 1);
|
||||
}
|
||||
#endif // defined(USING_THREAD_SANITIZER)
|
||||
|
||||
// Call a stub to set up the exception object in kExceptionObjectReg,
|
||||
// to set up the stacktrace object in kStackTraceObjectReg, and to
|
||||
// continue execution at the given pc in the given frame.
|
||||
typedef void (*ExcpHandler)(uword, uword, uword, Thread*);
|
||||
ExcpHandler func =
|
||||
reinterpret_cast<ExcpHandler>(StubCode::JumpToFrame().EntryPoint());
|
||||
|
||||
func(program_counter, stack_pointer, frame_pointer, thread);
|
||||
|
||||
#endif
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
@@ -48,6 +48,8 @@ Thread::~Thread() {
|
||||
delete api_reusable_scope_;
|
||||
api_reusable_scope_ = NULL;
|
||||
}
|
||||
|
||||
DO_IF_TSAN(delete tsan_utils_);
|
||||
}
|
||||
|
||||
#if defined(DEBUG)
|
||||
@@ -86,6 +88,7 @@ Thread::Thread(bool is_vm_isolate)
|
||||
api_top_scope_(NULL),
|
||||
double_truncate_round_supported_(
|
||||
TargetCPUFeatures::double_truncate_round_supported() ? 1 : 0),
|
||||
tsan_utils_(DO_IF_TSAN(new TsanUtils()) DO_IF_NOT_TSAN(nullptr)),
|
||||
task_kind_(kUnknownTask),
|
||||
dart_stream_(NULL),
|
||||
thread_lock_(),
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
#error "Should not include runtime"
|
||||
#endif
|
||||
|
||||
#include <setjmp.h>
|
||||
|
||||
#include "include/dart_api.h"
|
||||
#include "platform/assert.h"
|
||||
#include "platform/atomic.h"
|
||||
@@ -198,6 +200,8 @@ class Thread;
|
||||
V(uword, deoptimize_entry_, StubCode::Deoptimize().EntryPoint(), 0) \
|
||||
V(uword, call_native_through_safepoint_entry_point_, \
|
||||
StubCode::CallNativeThroughSafepoint().EntryPoint(), 0) \
|
||||
V(uword, jump_to_frame_entry_point_, StubCode::JumpToFrame().EntryPoint(), \
|
||||
0) \
|
||||
V(uword, slow_type_test_entry_point_, StubCode::SlowTypeTest().EntryPoint(), \
|
||||
0)
|
||||
|
||||
@@ -257,6 +261,35 @@ enum SafepointLevel {
|
||||
kNumLevels,
|
||||
};
|
||||
|
||||
// Accessed from generated code.
|
||||
struct TsanUtils {
|
||||
// Used to allow unwinding runtime C frames using longjmp() when throwing
|
||||
// exceptions. This allows triggering the normal TSAN shadow stack unwinding
|
||||
// implementation.
|
||||
// -> See https://dartbug.com/47472#issuecomment-948235479 for details.
|
||||
void* setjmp_function = reinterpret_cast<void*>(&setjmp);
|
||||
jmp_buf* setjmp_buffer = nullptr;
|
||||
uword exception_pc = 0;
|
||||
uword exception_sp = 0;
|
||||
uword exception_fp = 0;
|
||||
|
||||
static intptr_t setjmp_function_offset() {
|
||||
return OFFSET_OF(TsanUtils, setjmp_function);
|
||||
}
|
||||
static intptr_t setjmp_buffer_offset() {
|
||||
return OFFSET_OF(TsanUtils, setjmp_buffer);
|
||||
}
|
||||
static intptr_t exception_pc_offset() {
|
||||
return OFFSET_OF(TsanUtils, exception_pc);
|
||||
}
|
||||
static intptr_t exception_sp_offset() {
|
||||
return OFFSET_OF(TsanUtils, exception_sp);
|
||||
}
|
||||
static intptr_t exception_fp_offset() {
|
||||
return OFFSET_OF(TsanUtils, exception_fp);
|
||||
}
|
||||
};
|
||||
|
||||
// A VM thread; may be executing Dart code or performing helper tasks like
|
||||
// garbage collection or compilation. The Thread structure associated with
|
||||
// a thread is allocated by EnsureInit before entering an isolate, and destroyed
|
||||
@@ -441,6 +474,13 @@ class Thread : public ThreadState {
|
||||
return OFFSET_OF(Thread, double_truncate_round_supported_);
|
||||
}
|
||||
|
||||
static intptr_t tsan_utils_offset() { return OFFSET_OF(Thread, tsan_utils_); }
|
||||
|
||||
#if defined(USING_THREAD_SANITIZER)
|
||||
uword exit_through_ffi() const { return exit_through_ffi_; }
|
||||
TsanUtils* tsan_utils() const { return tsan_utils_; }
|
||||
#endif // defined(USING_THREAD_SANITIZER)
|
||||
|
||||
// The isolate that this thread is operating on, or nullptr if none.
|
||||
Isolate* isolate() const { return isolate_; }
|
||||
static intptr_t isolate_offset() { return OFFSET_OF(Thread, isolate_); }
|
||||
@@ -1099,6 +1139,8 @@ class Thread : public ThreadState {
|
||||
ApiLocalScope* api_top_scope_;
|
||||
uint8_t double_truncate_round_supported_;
|
||||
|
||||
TsanUtils* tsan_utils_ = nullptr;
|
||||
|
||||
// ---- End accessed from generated code. ----
|
||||
|
||||
// The layout of Thread object up to this point should not depend
|
||||
|
||||
Reference in New Issue
Block a user