[vm] Crash on impossible GDT null errors
This CL adds code to detect situations when we hit null error through GDT call with non-null receiver. We achieve this by making receiver's cid part of the GDT calling convention and checking this cid in runtime entry responsible for throwing the null error. This CL is an attempt to narrow down b/179632636 and collapse various impossible crash reports into a single native crash cluster by crashing VM instead of throwing a null error. TEST=tested manually Cq-Include-Trybots: luci.dart.try:vm-kernel-precomp-linux-release-x64-try,vm-kernel-precomp-linux-release-simarm-try,vm-kernel-precomp-linux-release-simarm64-try Change-Id: If2ed4646c4c0f403016266e4e83e296a7b234cb5 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/191141 Auto-Submit: Vyacheslav Egorov <vegorov@google.com> Commit-Queue: Vyacheslav Egorov <vegorov@google.com> Reviewed-by: Martin Kustermann <kustermann@google.com>
This commit is contained in:
committed by
commit-bot@chromium.org
parent
69ec09825a
commit
d42de4ed06
@@ -767,8 +767,7 @@ class FlowGraphCompiler : public ValueObject {
|
||||
intptr_t total_ic_calls,
|
||||
Code::EntryKind entry_kind = Code::EntryKind::kNormal);
|
||||
|
||||
void EmitDispatchTableCall(Register cid_reg,
|
||||
int32_t selector_offset,
|
||||
void EmitDispatchTableCall(int32_t selector_offset,
|
||||
const Array& arguments_descriptor);
|
||||
|
||||
Condition EmitEqualityRegConstCompare(Register reg,
|
||||
|
||||
@@ -701,9 +701,9 @@ void FlowGraphCompiler::EmitOptimizedStaticCall(
|
||||
}
|
||||
|
||||
void FlowGraphCompiler::EmitDispatchTableCall(
|
||||
Register cid_reg,
|
||||
int32_t selector_offset,
|
||||
const Array& arguments_descriptor) {
|
||||
const auto cid_reg = DispatchTableNullErrorABI::kClassIdReg;
|
||||
ASSERT(CanCallDart());
|
||||
ASSERT(cid_reg != ARGS_DESC_REG);
|
||||
if (!arguments_descriptor.IsNull()) {
|
||||
@@ -712,6 +712,9 @@ void FlowGraphCompiler::EmitDispatchTableCall(
|
||||
intptr_t offset = (selector_offset - DispatchTable::OriginElement()) *
|
||||
compiler::target::kWordSize;
|
||||
CLOBBERS_LR({
|
||||
// Would like cid_reg to be available on entry to the target function
|
||||
// for checking purposes.
|
||||
ASSERT(cid_reg != LR);
|
||||
if (offset == 0) {
|
||||
__ ldr(LR, compiler::Address(DISPATCH_TABLE_REG, cid_reg, LSL,
|
||||
compiler::target::kWordSizeLog2));
|
||||
|
||||
@@ -694,18 +694,23 @@ void FlowGraphCompiler::EmitOptimizedStaticCall(
|
||||
}
|
||||
|
||||
void FlowGraphCompiler::EmitDispatchTableCall(
|
||||
Register cid_reg,
|
||||
int32_t selector_offset,
|
||||
const Array& arguments_descriptor) {
|
||||
const auto cid_reg = DispatchTableNullErrorABI::kClassIdReg;
|
||||
ASSERT(CanCallDart());
|
||||
ASSERT(cid_reg != ARGS_DESC_REG);
|
||||
if (!arguments_descriptor.IsNull()) {
|
||||
__ LoadObject(ARGS_DESC_REG, arguments_descriptor);
|
||||
}
|
||||
const intptr_t offset = selector_offset - DispatchTable::OriginElement();
|
||||
__ AddImmediate(cid_reg, cid_reg, offset);
|
||||
__ Call(compiler::Address(DISPATCH_TABLE_REG, cid_reg, UXTX,
|
||||
compiler::Address::Scaled));
|
||||
CLOBBERS_LR({
|
||||
// Would like cid_reg to be available on entry to the target function
|
||||
// for checking purposes.
|
||||
ASSERT(cid_reg != LR);
|
||||
__ AddImmediate(LR, cid_reg, offset);
|
||||
__ Call(compiler::Address(DISPATCH_TABLE_REG, LR, UXTX,
|
||||
compiler::Address::Scaled));
|
||||
});
|
||||
}
|
||||
|
||||
Condition FlowGraphCompiler::EmitEqualityRegConstCompare(
|
||||
|
||||
@@ -726,7 +726,6 @@ void FlowGraphCompiler::EmitOptimizedStaticCall(
|
||||
}
|
||||
|
||||
void FlowGraphCompiler::EmitDispatchTableCall(
|
||||
Register cid_reg,
|
||||
int32_t selector_offset,
|
||||
const Array& arguments_descriptor) {
|
||||
// Only generated with precompilation.
|
||||
|
||||
@@ -682,9 +682,9 @@ void FlowGraphCompiler::EmitOptimizedStaticCall(
|
||||
}
|
||||
|
||||
void FlowGraphCompiler::EmitDispatchTableCall(
|
||||
Register cid_reg,
|
||||
int32_t selector_offset,
|
||||
const Array& arguments_descriptor) {
|
||||
const auto cid_reg = DispatchTableNullErrorABI::kClassIdReg;
|
||||
ASSERT(CanCallDart());
|
||||
const Register table_reg = RAX;
|
||||
ASSERT(cid_reg != table_reg);
|
||||
|
||||
@@ -5042,16 +5042,26 @@ DispatchTableCallInstr* DispatchTableCallInstr::FromCall(
|
||||
return dispatch_table_call;
|
||||
}
|
||||
|
||||
LocationSummary* DispatchTableCallInstr::MakeLocationSummary(Zone* zone,
|
||||
bool opt) const {
|
||||
const intptr_t kNumInputs = 1;
|
||||
const intptr_t kNumTemps = 0;
|
||||
LocationSummary* summary = new (zone)
|
||||
LocationSummary(zone, kNumInputs, kNumTemps, LocationSummary::kCall);
|
||||
summary->set_in(
|
||||
0, Location::RegisterLocation(DispatchTableNullErrorABI::kClassIdReg));
|
||||
return MakeCallSummary(zone, this, summary);
|
||||
}
|
||||
|
||||
void DispatchTableCallInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
|
||||
ASSERT(locs()->in(0).reg() == DispatchTableNullErrorABI::kClassIdReg);
|
||||
Array& arguments_descriptor = Array::ZoneHandle();
|
||||
if (selector()->requires_args_descriptor) {
|
||||
ArgumentsInfo args_info(type_args_len(), ArgumentCount(), ArgumentsSize(),
|
||||
argument_names());
|
||||
arguments_descriptor = args_info.ToArgumentsDescriptor();
|
||||
}
|
||||
const Register cid_reg = locs()->in(0).reg();
|
||||
compiler->EmitDispatchTableCall(cid_reg, selector()->offset,
|
||||
arguments_descriptor);
|
||||
compiler->EmitDispatchTableCall(selector()->offset, arguments_descriptor);
|
||||
compiler->EmitCallsiteMetadata(source(), DeoptId::kNone,
|
||||
UntaggedPcDescriptors::kOther, locs());
|
||||
if (selector()->called_on_null && !selector()->on_null_interface) {
|
||||
|
||||
@@ -580,16 +580,6 @@ void IfThenElseInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
|
||||
}
|
||||
}
|
||||
|
||||
LocationSummary* DispatchTableCallInstr::MakeLocationSummary(Zone* zone,
|
||||
bool opt) const {
|
||||
const intptr_t kNumInputs = 1;
|
||||
const intptr_t kNumTemps = 0;
|
||||
LocationSummary* summary = new (zone)
|
||||
LocationSummary(zone, kNumInputs, kNumTemps, LocationSummary::kCall);
|
||||
summary->set_in(0, Location::RegisterLocation(R0)); // ClassId
|
||||
return MakeCallSummary(zone, this, summary);
|
||||
}
|
||||
|
||||
LocationSummary* ClosureCallInstr::MakeLocationSummary(Zone* zone,
|
||||
bool opt) const {
|
||||
const intptr_t kNumInputs = 1;
|
||||
|
||||
@@ -497,16 +497,6 @@ void IfThenElseInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
|
||||
}
|
||||
}
|
||||
|
||||
LocationSummary* DispatchTableCallInstr::MakeLocationSummary(Zone* zone,
|
||||
bool opt) const {
|
||||
const intptr_t kNumInputs = 1;
|
||||
const intptr_t kNumTemps = 0;
|
||||
LocationSummary* summary = new (zone)
|
||||
LocationSummary(zone, kNumInputs, kNumTemps, LocationSummary::kCall);
|
||||
summary->set_in(0, Location::RegisterLocation(R0)); // ClassId
|
||||
return MakeCallSummary(zone, this, summary);
|
||||
}
|
||||
|
||||
LocationSummary* ClosureCallInstr::MakeLocationSummary(Zone* zone,
|
||||
bool opt) const {
|
||||
const intptr_t kNumInputs = 1;
|
||||
|
||||
@@ -6812,13 +6812,6 @@ void IfThenElseInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
|
||||
}
|
||||
}
|
||||
|
||||
LocationSummary* DispatchTableCallInstr::MakeLocationSummary(Zone* zone,
|
||||
bool opt) const {
|
||||
// Only generated with precompilation.
|
||||
UNREACHABLE();
|
||||
return NULL;
|
||||
}
|
||||
|
||||
LocationSummary* ClosureCallInstr::MakeLocationSummary(Zone* zone,
|
||||
bool opt) const {
|
||||
const intptr_t kNumInputs = 1;
|
||||
|
||||
@@ -7317,16 +7317,6 @@ Condition StrictCompareInstr::EmitComparisonCodeRegConstant(
|
||||
source(), deopt_id());
|
||||
}
|
||||
|
||||
LocationSummary* DispatchTableCallInstr::MakeLocationSummary(Zone* zone,
|
||||
bool opt) const {
|
||||
const intptr_t kNumInputs = 1;
|
||||
const intptr_t kNumTemps = 0;
|
||||
LocationSummary* summary = new (zone)
|
||||
LocationSummary(zone, kNumInputs, kNumTemps, LocationSummary::kCall);
|
||||
summary->set_in(0, Location::RegisterLocation(RCX)); // ClassId
|
||||
return MakeCallSummary(zone, this, summary);
|
||||
}
|
||||
|
||||
LocationSummary* ClosureCallInstr::MakeLocationSummary(Zone* zone,
|
||||
bool opt) const {
|
||||
const intptr_t kNumInputs = 1;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -487,7 +487,9 @@ void StubCodeCompiler::GenerateJITCallbackTrampolines(
|
||||
void StubCodeCompiler::GenerateDispatchTableNullErrorStub(
|
||||
Assembler* assembler) {
|
||||
__ EnterStubFrame();
|
||||
__ CallRuntime(kNullErrorRuntimeEntry, /*argument_count=*/0);
|
||||
__ SmiTag(DispatchTableNullErrorABI::kClassIdReg);
|
||||
__ PushRegister(DispatchTableNullErrorABI::kClassIdReg);
|
||||
__ CallRuntime(kDispatchTableNullErrorRuntimeEntry, /*argument_count=*/1);
|
||||
// The NullError runtime entry does not return.
|
||||
__ Breakpoint();
|
||||
}
|
||||
|
||||
@@ -545,7 +545,9 @@ void StubCodeCompiler::GenerateBuildMethodExtractorStub(
|
||||
void StubCodeCompiler::GenerateDispatchTableNullErrorStub(
|
||||
Assembler* assembler) {
|
||||
__ EnterStubFrame();
|
||||
__ CallRuntime(kNullErrorRuntimeEntry, /*argument_count=*/0);
|
||||
__ SmiTag(DispatchTableNullErrorABI::kClassIdReg);
|
||||
__ PushRegister(DispatchTableNullErrorABI::kClassIdReg);
|
||||
__ CallRuntime(kDispatchTableNullErrorRuntimeEntry, /*argument_count=*/1);
|
||||
// The NullError runtime entry does not return.
|
||||
__ Breakpoint();
|
||||
}
|
||||
|
||||
@@ -488,7 +488,9 @@ void StubCodeCompiler::GenerateBuildMethodExtractorStub(
|
||||
void StubCodeCompiler::GenerateDispatchTableNullErrorStub(
|
||||
Assembler* assembler) {
|
||||
__ EnterStubFrame();
|
||||
__ CallRuntime(kNullErrorRuntimeEntry, /*argument_count=*/0);
|
||||
__ SmiTag(DispatchTableNullErrorABI::kClassIdReg);
|
||||
__ PushRegister(DispatchTableNullErrorABI::kClassIdReg);
|
||||
__ CallRuntime(kDispatchTableNullErrorRuntimeEntry, /*argument_count=*/1);
|
||||
// The NullError runtime entry does not return.
|
||||
__ Breakpoint();
|
||||
}
|
||||
|
||||
@@ -463,6 +463,14 @@ struct AllocateTypedDataArrayABI {
|
||||
static const Register kResultReg = R0;
|
||||
};
|
||||
|
||||
// ABI for DispatchTableNullErrorStub and consequently for all dispatch
|
||||
// table calls (though normal functions will not expect or use this
|
||||
// register). This ABI is added to distinguish memory corruption errors from
|
||||
// null errors.
|
||||
struct DispatchTableNullErrorABI {
|
||||
static const Register kClassIdReg = R0;
|
||||
};
|
||||
|
||||
// TODO(regis): Add ABIs for type testing stubs and is-type test stubs instead
|
||||
// of reusing the constants of the instantiation stubs ABI.
|
||||
|
||||
|
||||
@@ -304,6 +304,14 @@ struct AllocateTypedDataArrayABI {
|
||||
static const Register kResultReg = R0;
|
||||
};
|
||||
|
||||
// ABI for DispatchTableNullErrorStub and consequently for all dispatch
|
||||
// table calls (though normal functions will not expect or use this
|
||||
// register). This ABI is added to distinguish memory corruption errors from
|
||||
// null errors.
|
||||
struct DispatchTableNullErrorABI {
|
||||
static const Register kClassIdReg = R0;
|
||||
};
|
||||
|
||||
// TODO(regis): Add ABIs for type testing stubs and is-type test stubs instead
|
||||
// of reusing the constants of the instantiation stubs ABI.
|
||||
|
||||
|
||||
@@ -202,6 +202,16 @@ struct AllocateTypedDataArrayABI {
|
||||
static const Register kResultReg = EAX;
|
||||
};
|
||||
|
||||
// ABI for DispatchTableNullErrorStub and consequently for all dispatch
|
||||
// table calls (though normal functions will not expect or use this
|
||||
// register). This ABI is added to distinguish memory corruption errors from
|
||||
// null errors.
|
||||
// Note: dispatch table calls are never actually generated on IA32, this
|
||||
// declaration is only added for completeness.
|
||||
struct DispatchTableNullErrorABI {
|
||||
static const Register kClassIdReg = EAX;
|
||||
};
|
||||
|
||||
typedef uint32_t RegList;
|
||||
const RegList kAllCpuRegistersList = 0xFF;
|
||||
|
||||
|
||||
@@ -275,6 +275,14 @@ struct AllocateTypedDataArrayABI {
|
||||
static const Register kResultReg = RAX;
|
||||
};
|
||||
|
||||
// ABI for DispatchTableNullErrorStub and consequently for all dispatch
|
||||
// table calls (though normal functions will not expect or use this
|
||||
// register). This ABI is added to distinguish memory corruption errors from
|
||||
// null errors.
|
||||
struct DispatchTableNullErrorABI {
|
||||
static const Register kClassIdReg = RCX;
|
||||
};
|
||||
|
||||
typedef uint32_t RegList;
|
||||
const RegList kAllCpuRegistersList = 0xFFFF;
|
||||
const RegList kAllFpuRegistersList = 0xFFFF;
|
||||
|
||||
@@ -43,6 +43,7 @@ class IsolateGroup;
|
||||
CLASS_LIST(DEFINE_FORWARD_DECLARATION)
|
||||
#undef DEFINE_FORWARD_DECLARATION
|
||||
class CodeStatistics;
|
||||
class StackFrame;
|
||||
|
||||
#define VISIT_FROM(type, first) \
|
||||
type* from() { return reinterpret_cast<type*>(&first##_); }
|
||||
@@ -733,6 +734,9 @@ class UntaggedObject {
|
||||
friend class WriteBarrierUpdateVisitor; // CheckHeapPointerStore
|
||||
friend class OffsetsTable;
|
||||
friend class Object;
|
||||
friend void ReportImpossibleNullError(intptr_t cid,
|
||||
StackFrame* caller_frame,
|
||||
Thread* thread);
|
||||
|
||||
DISALLOW_ALLOCATION();
|
||||
DISALLOW_IMPLICIT_CONSTRUCTORS(UntaggedObject);
|
||||
|
||||
@@ -176,7 +176,7 @@ static void NullErrorHelper(Zone* zone, const String& selector) {
|
||||
Exceptions::ThrowByType(Exceptions::kNoSuchMethod, args);
|
||||
}
|
||||
|
||||
DEFINE_RUNTIME_ENTRY(NullError, 0) {
|
||||
static void DoThrowNullError(Isolate* isolate, Thread* thread, Zone* zone) {
|
||||
DartFrameIterator iterator(thread,
|
||||
StackFrameIterator::kNoCrossThreadIteration);
|
||||
const StackFrame* caller_frame = iterator.NextFrame();
|
||||
@@ -206,6 +206,59 @@ DEFINE_RUNTIME_ENTRY(NullError, 0) {
|
||||
NullErrorHelper(zone, member_name);
|
||||
}
|
||||
|
||||
DEFINE_RUNTIME_ENTRY(NullError, 0) {
|
||||
DoThrowNullError(isolate, thread, zone);
|
||||
}
|
||||
|
||||
// Collects information about pointers within the top |kMaxSlotsCollected|
|
||||
// slots on the stack.
|
||||
// TODO(b/179632636) This code is added in attempt to better understand
|
||||
// b/179632636 and should be removed in the future.
|
||||
void ReportImpossibleNullError(intptr_t cid,
|
||||
StackFrame* caller_frame,
|
||||
Thread* thread) {
|
||||
TextBuffer buffer(512);
|
||||
buffer.Printf("hit null error with cid %" Pd ", caller context: ", cid);
|
||||
|
||||
const intptr_t kMaxSlotsCollected = 5;
|
||||
const auto slots = reinterpret_cast<ObjectPtr*>(caller_frame->sp());
|
||||
const auto num_slots_in_frame =
|
||||
reinterpret_cast<ObjectPtr*>(caller_frame->fp()) - slots;
|
||||
const auto num_slots_to_collect =
|
||||
Utils::Maximum(kMaxSlotsCollected, num_slots_in_frame);
|
||||
bool comma = false;
|
||||
for (intptr_t i = 0; i < num_slots_to_collect; i++) {
|
||||
const ObjectPtr ptr = slots[i];
|
||||
buffer.Printf("%s[sp+%" Pd "] %" Pp "", comma ? ", " : "", i,
|
||||
static_cast<uword>(ptr));
|
||||
if (ptr->IsHeapObject() &&
|
||||
(Dart::vm_isolate_group()->heap()->Contains(
|
||||
UntaggedObject::ToAddr(ptr)) ||
|
||||
thread->heap()->Contains(UntaggedObject::ToAddr(ptr)))) {
|
||||
buffer.Printf("(%" Pp ")", static_cast<uword>(ptr->untag()->tags_));
|
||||
}
|
||||
comma = true;
|
||||
}
|
||||
|
||||
const char* message = buffer.buffer();
|
||||
FATAL("%s", message);
|
||||
}
|
||||
|
||||
DEFINE_RUNTIME_ENTRY(DispatchTableNullError, 1) {
|
||||
const Smi& cid = Smi::CheckedHandle(zone, arguments.ArgAt(0));
|
||||
if (cid.Value() != kNullCid) {
|
||||
// We hit null error, but receiver is not null itself. This most likely
|
||||
// is a memory corruption. Crash the VM but provide some additonal
|
||||
// information about the arguments on the stack.
|
||||
DartFrameIterator iterator(thread,
|
||||
StackFrameIterator::kNoCrossThreadIteration);
|
||||
StackFrame* caller_frame = iterator.NextFrame();
|
||||
RELEASE_ASSERT(caller_frame->IsDartFrame());
|
||||
ReportImpossibleNullError(cid.Value(), caller_frame, thread);
|
||||
}
|
||||
DoThrowNullError(isolate, thread, zone);
|
||||
}
|
||||
|
||||
DEFINE_RUNTIME_ENTRY(NullErrorWithSelector, 1) {
|
||||
const String& selector = String::CheckedHandle(zone, arguments.ArgAt(0));
|
||||
NullErrorHelper(zone, selector);
|
||||
|
||||
@@ -38,6 +38,7 @@ namespace dart {
|
||||
V(NullErrorWithSelector) \
|
||||
V(NullCastError) \
|
||||
V(ArgumentNullError) \
|
||||
V(DispatchTableNullError) \
|
||||
V(ArgumentError) \
|
||||
V(ArgumentErrorUnboxedInt64) \
|
||||
V(IntegerDivisionByZeroException) \
|
||||
|
||||
Reference in New Issue
Block a user