[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:
Vyacheslav Egorov
2021-03-15 15:35:10 +00:00
committed by commit-bot@chromium.org
parent 69ec09825a
commit d42de4ed06
21 changed files with 460 additions and 383 deletions
@@ -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);
+13 -3
View File
@@ -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) {
-10
View File
@@ -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;
-10
View File
@@ -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;
-7
View File
@@ -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;
-10
View File
@@ -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();
}
+8
View File
@@ -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.
+8
View File
@@ -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.
+10
View File
@@ -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;
+8
View File
@@ -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;
+4
View File
@@ -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);
+54 -1
View File
@@ -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);
+1
View File
@@ -38,6 +38,7 @@ namespace dart {
V(NullErrorWithSelector) \
V(NullCastError) \
V(ArgumentNullError) \
V(DispatchTableNullError) \
V(ArgumentError) \
V(ArgumentErrorUnboxedInt64) \
V(IntegerDivisionByZeroException) \