More robust GC verification and corruption dumping
Added a test for GC heap corruption dumping, added more info to the
dump like adjacent bytes and card table.
Added heap corruption detection in
ConcurrentCopying::MarkNonMoving().
Bug: 37187694
Bug: 12687968
Test: mm test-art-host-gtest-verification_test -j20
Change-Id: I8c90e45796d0784265aa091b2f8082f0cfb62719
diff --git a/runtime/gc/collector/concurrent_copying-inl.h b/runtime/gc/collector/concurrent_copying-inl.h
index 854d0a5..5364547 100644
--- a/runtime/gc/collector/concurrent_copying-inl.h
+++ b/runtime/gc/collector/concurrent_copying-inl.h
@@ -96,7 +96,9 @@
}
template<bool kGrayImmuneObject, bool kFromGCThread>
-inline mirror::Object* ConcurrentCopying::Mark(mirror::Object* from_ref) {
+inline mirror::Object* ConcurrentCopying::Mark(mirror::Object* from_ref,
+ mirror::Object* holder,
+ MemberOffset offset) {
if (from_ref == nullptr) {
return nullptr;
}
@@ -141,7 +143,7 @@
if (immune_spaces_.ContainsObject(from_ref)) {
return MarkImmuneSpace<kGrayImmuneObject>(from_ref);
} else {
- return MarkNonMoving(from_ref);
+ return MarkNonMoving(from_ref, holder, offset);
}
default:
UNREACHABLE();
diff --git a/runtime/gc/collector/concurrent_copying.cc b/runtime/gc/collector/concurrent_copying.cc
index a091fce..80b569a 100644
--- a/runtime/gc/collector/concurrent_copying.cc
+++ b/runtime/gc/collector/concurrent_copying.cc
@@ -29,6 +29,7 @@
#include "gc/reference_processor.h"
#include "gc/space/image_space.h"
#include "gc/space/space-inl.h"
+#include "gc/verification.h"
#include "image-inl.h"
#include "intern_table.h"
#include "mirror/class-inl.h"
@@ -2362,7 +2363,9 @@
return alloc_stack->Contains(ref);
}
-mirror::Object* ConcurrentCopying::MarkNonMoving(mirror::Object* ref) {
+mirror::Object* ConcurrentCopying::MarkNonMoving(mirror::Object* ref,
+ mirror::Object* holder,
+ MemberOffset offset) {
// ref is in a non-moving space (from_ref == to_ref).
DCHECK(!region_space_->HasAddress(ref)) << ref;
DCHECK(!immune_spaces_.ContainsObject(ref));
@@ -2408,6 +2411,11 @@
return ref;
}
}
+ if (is_los && !IsAligned<kPageSize>(ref)) {
+ // Ref is a large object that is not aligned, it must be heap corruption. Dump data before
+ // AtomicSetReadBarrierState since it will fault if the address is not valid.
+ heap_->GetVerification()->LogHeapCorruption(ref, offset, holder, /* fatal */ true);
+ }
// Not marked or on the allocation stack. Try to mark it.
// This may or may not succeed, which is ok.
bool cas_success = false;
diff --git a/runtime/gc/collector/concurrent_copying.h b/runtime/gc/collector/concurrent_copying.h
index 398a7e2..c21520d 100644
--- a/runtime/gc/collector/concurrent_copying.h
+++ b/runtime/gc/collector/concurrent_copying.h
@@ -106,7 +106,9 @@
return IsMarked(ref) == ref;
}
template<bool kGrayImmuneObject = true, bool kFromGCThread = false>
- ALWAYS_INLINE mirror::Object* Mark(mirror::Object* from_ref)
+ ALWAYS_INLINE mirror::Object* Mark(mirror::Object* from_ref,
+ mirror::Object* holder = nullptr,
+ MemberOffset offset = MemberOffset(0))
REQUIRES_SHARED(Locks::mutator_lock_)
REQUIRES(!mark_stack_lock_, !skipped_blocks_lock_, !immune_gray_stack_lock_);
ALWAYS_INLINE mirror::Object* MarkFromReadBarrier(mirror::Object* from_ref)
@@ -224,7 +226,10 @@
void DisableMarking() REQUIRES_SHARED(Locks::mutator_lock_);
void IssueDisableMarkingCheckpoint() REQUIRES_SHARED(Locks::mutator_lock_);
void ExpandGcMarkStack() REQUIRES_SHARED(Locks::mutator_lock_);
- mirror::Object* MarkNonMoving(mirror::Object* from_ref) REQUIRES_SHARED(Locks::mutator_lock_)
+ mirror::Object* MarkNonMoving(mirror::Object* from_ref,
+ mirror::Object* holder = nullptr,
+ MemberOffset offset = MemberOffset(0))
+ REQUIRES_SHARED(Locks::mutator_lock_)
REQUIRES(!mark_stack_lock_, !skipped_blocks_lock_);
ALWAYS_INLINE mirror::Object* MarkUnevacFromSpaceRegion(mirror::Object* from_ref,
accounting::SpaceBitmap<kObjectAlignment>* bitmap)
diff --git a/runtime/gc/heap.cc b/runtime/gc/heap.cc
index e08784d..7a0eea4 100644
--- a/runtime/gc/heap.cc
+++ b/runtime/gc/heap.cc
@@ -60,6 +60,7 @@
#include "gc/space/space-inl.h"
#include "gc/space/zygote_space.h"
#include "gc/task_processor.h"
+#include "gc/verification.h"
#include "entrypoints/quick/quick_alloc_entrypoints.h"
#include "gc_pause_listener.h"
#include "heap-inl.h"
@@ -286,6 +287,7 @@
CHECK_EQ(foreground_collector_type_, kCollectorTypeCC);
CHECK_EQ(background_collector_type_, kCollectorTypeCCBackground);
}
+ verification_.reset(new Verification(this));
CHECK_GE(large_object_threshold, kMinLargeObjectThreshold);
ScopedTrace trace(__FUNCTION__);
Runtime* const runtime = Runtime::Current();
@@ -4267,5 +4269,9 @@
return ret;
}
+const Verification* Heap::GetVerification() const {
+ return verification_.get();
+}
+
} // namespace gc
} // namespace art
diff --git a/runtime/gc/heap.h b/runtime/gc/heap.h
index 241d84c..aa123d8 100644
--- a/runtime/gc/heap.h
+++ b/runtime/gc/heap.h
@@ -64,6 +64,7 @@
class GcPauseListener;
class ReferenceProcessor;
class TaskProcessor;
+class Verification;
namespace accounting {
class HeapBitmap;
@@ -821,6 +822,8 @@
// reasons, we assume it stays valid when we read it (so that we don't require a lock).
void RemoveGcPauseListener();
+ const Verification* GetVerification() const;
+
private:
class ConcurrentGCTask;
class CollectorTransitionTask;
@@ -1433,6 +1436,8 @@
// An installed GC Pause listener.
Atomic<GcPauseListener*> gc_pause_listener_;
+ std::unique_ptr<Verification> verification_;
+
friend class CollectorTransitionTask;
friend class collector::GarbageCollector;
friend class collector::MarkCompact;
diff --git a/runtime/gc/heap_verification_test.cc b/runtime/gc/heap_verification_test.cc
new file mode 100644
index 0000000..480ba2a
--- /dev/null
+++ b/runtime/gc/heap_verification_test.cc
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "common_runtime_test.h"
+#include "verification.h"
+#include "mirror/string.h"
+#include "scoped_thread_state_change-inl.h"
+
+namespace art {
+namespace gc {
+
+class VerificationTest : public CommonRuntimeTest {
+ protected:
+ VerificationTest() {}
+
+ template <class T>
+ mirror::ObjectArray<T>* AllocObjectArray(Thread* self, size_t length)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ ClassLinker* const class_linker = Runtime::Current()->GetClassLinker();
+ return mirror::ObjectArray<T>::Alloc(
+ self,
+ class_linker->GetClassRoot(ClassLinker::ClassRoot::kObjectArrayClass),
+ length);
+ }
+};
+
+TEST_F(VerificationTest, IsValidHeapObjectAddress) {
+ ScopedObjectAccess soa(Thread::Current());
+ const Verification* const v = Runtime::Current()->GetHeap()->GetVerification();
+ EXPECT_FALSE(v->IsValidHeapObjectAddress(reinterpret_cast<const void*>(1)));
+ EXPECT_FALSE(v->IsValidHeapObjectAddress(reinterpret_cast<const void*>(4)));
+ EXPECT_FALSE(v->IsValidHeapObjectAddress(nullptr));
+ VariableSizedHandleScope hs(soa.Self());
+ Handle<mirror::String> string(
+ hs.NewHandle(mirror::String::AllocFromModifiedUtf8(soa.Self(), "test")));
+ EXPECT_TRUE(v->IsValidHeapObjectAddress(string.Get()));
+ EXPECT_TRUE(v->IsValidHeapObjectAddress(string->GetClass()));
+ const uintptr_t uint_klass = reinterpret_cast<uintptr_t>(string->GetClass());
+ // Not actually a valid object but the verification can't know that. Guaranteed to be inside a
+ // heap space.
+ EXPECT_TRUE(v->IsValidHeapObjectAddress(
+ reinterpret_cast<const void*>(uint_klass + kObjectAlignment)));
+ EXPECT_FALSE(v->IsValidHeapObjectAddress(
+ reinterpret_cast<const void*>(&uint_klass)));
+}
+
+TEST_F(VerificationTest, IsValidClass) {
+ ScopedObjectAccess soa(Thread::Current());
+ VariableSizedHandleScope hs(soa.Self());
+ Handle<mirror::String> string(
+ hs.NewHandle(mirror::String::AllocFromModifiedUtf8(soa.Self(), "test")));
+ const Verification* const v = Runtime::Current()->GetHeap()->GetVerification();
+ EXPECT_FALSE(v->IsValidClass(reinterpret_cast<const void*>(1)));
+ EXPECT_FALSE(v->IsValidClass(reinterpret_cast<const void*>(4)));
+ EXPECT_FALSE(v->IsValidClass(nullptr));
+ EXPECT_FALSE(v->IsValidClass(string.Get()));
+ EXPECT_TRUE(v->IsValidClass(string->GetClass()));
+ const uintptr_t uint_klass = reinterpret_cast<uintptr_t>(string->GetClass());
+ EXPECT_FALSE(v->IsValidClass(reinterpret_cast<const void*>(uint_klass - kObjectAlignment)));
+ EXPECT_FALSE(v->IsValidClass(reinterpret_cast<const void*>(&uint_klass)));
+}
+
+TEST_F(VerificationTest, DumpObjectInfo) {
+ ScopedLogSeverity sls(LogSeverity::INFO);
+ ScopedObjectAccess soa(Thread::Current());
+ Runtime* const runtime = Runtime::Current();
+ VariableSizedHandleScope hs(soa.Self());
+ Handle<mirror::String> string(
+ hs.NewHandle(mirror::String::AllocFromModifiedUtf8(soa.Self(), "obj")));
+ Handle<mirror::ObjectArray<mirror::Object>> arr(
+ hs.NewHandle(AllocObjectArray<mirror::Object>(soa.Self(), 256)));
+ const Verification* const v = runtime->GetHeap()->GetVerification();
+ LOG(INFO) << v->DumpObjectInfo(reinterpret_cast<const void*>(1), "obj");
+ LOG(INFO) << v->DumpObjectInfo(reinterpret_cast<const void*>(4), "obj");
+ LOG(INFO) << v->DumpObjectInfo(nullptr, "obj");
+ LOG(INFO) << v->DumpObjectInfo(string.Get(), "test");
+ LOG(INFO) << v->DumpObjectInfo(string->GetClass(), "obj");
+ const uintptr_t uint_klass = reinterpret_cast<uintptr_t>(string->GetClass());
+ LOG(INFO) << v->DumpObjectInfo(reinterpret_cast<const void*>(uint_klass - kObjectAlignment),
+ "obj");
+ LOG(INFO) << v->DumpObjectInfo(reinterpret_cast<const void*>(&uint_klass), "obj");
+ LOG(INFO) << v->DumpObjectInfo(arr.Get(), "arr");
+}
+
+TEST_F(VerificationTest, LogHeapCorruption) {
+ ScopedLogSeverity sls(LogSeverity::INFO);
+ ScopedObjectAccess soa(Thread::Current());
+ Runtime* const runtime = Runtime::Current();
+ VariableSizedHandleScope hs(soa.Self());
+ Handle<mirror::String> string(
+ hs.NewHandle(mirror::String::AllocFromModifiedUtf8(soa.Self(), "obj")));
+ using ObjArray = mirror::ObjectArray<mirror::Object>;
+ Handle<ObjArray> arr(
+ hs.NewHandle(AllocObjectArray<mirror::Object>(soa.Self(), 256)));
+ const Verification* const v = runtime->GetHeap()->GetVerification();
+ arr->Set(0, string.Get());
+ // Test normal cases.
+ v->LogHeapCorruption(arr.Get(), ObjArray::DataOffset(kHeapReferenceSize), string.Get(), false);
+ v->LogHeapCorruption(string.Get(), mirror::Object::ClassOffset(), string->GetClass(), false);
+ // Test null holder cases.
+ v->LogHeapCorruption(nullptr, MemberOffset(0), string.Get(), false);
+ v->LogHeapCorruption(nullptr, MemberOffset(0), arr.Get(), false);
+}
+
+} // namespace gc
+} // namespace art
diff --git a/runtime/gc/verification.cc b/runtime/gc/verification.cc
new file mode 100644
index 0000000..9e79cb4
--- /dev/null
+++ b/runtime/gc/verification.cc
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "verification.h"
+
+#include <iomanip>
+#include <sstream>
+
+#include "mirror/class-inl.h"
+
+namespace art {
+namespace gc {
+
+std::string Verification::DumpObjectInfo(const void* addr, const char* tag) const {
+ std::ostringstream oss;
+ oss << tag << "=" << addr;
+ if (IsValidHeapObjectAddress(addr)) {
+ mirror::Object* obj = reinterpret_cast<mirror::Object*>(const_cast<void*>(addr));
+ mirror::Class* klass = obj->GetClass<kVerifyNone, kWithoutReadBarrier>();
+ oss << " klass=" << klass;
+ if (IsValidClass(klass)) {
+ oss << "(" << klass->PrettyClass() << ")";
+ if (klass->IsArrayClass<kVerifyNone, kWithoutReadBarrier>()) {
+ oss << " length=" << obj->AsArray<kVerifyNone, kWithoutReadBarrier>()->GetLength();
+ }
+ } else {
+ oss << " <invalid address>";
+ }
+ space::Space* const space = heap_->FindSpaceFromAddress(addr);
+ if (space != nullptr) {
+ oss << " space=" << *space;
+ }
+ accounting::CardTable* card_table = heap_->GetCardTable();
+ if (card_table->AddrIsInCardTable(addr)) {
+ oss << " card=" << static_cast<size_t>(
+ card_table->GetCard(reinterpret_cast<const mirror::Object*>(addr)));
+ }
+ // Dump adjacent RAM.
+ const uintptr_t uint_addr = reinterpret_cast<uintptr_t>(addr);
+ static constexpr size_t kBytesBeforeAfter = 2 * kObjectAlignment;
+ const uintptr_t dump_start = uint_addr - kBytesBeforeAfter;
+ const uintptr_t dump_end = uint_addr + kBytesBeforeAfter;
+ if (dump_start < dump_end &&
+ IsValidHeapObjectAddress(reinterpret_cast<const void*>(dump_start)) &&
+ IsValidHeapObjectAddress(reinterpret_cast<const void*>(dump_end - kObjectAlignment))) {
+ oss << " adjacent_ram=";
+ for (uintptr_t p = dump_start; p < dump_end; ++p) {
+ if (p == uint_addr) {
+ // Marker of where the object is.
+ oss << "|";
+ }
+ uint8_t* ptr = reinterpret_cast<uint8_t*>(p);
+ oss << std::hex << std::setfill('0') << std::setw(2) << static_cast<uintptr_t>(*ptr);
+ }
+ }
+ } else {
+ oss << " <invalid address>";
+ }
+ return oss.str();
+}
+
+void Verification::LogHeapCorruption(ObjPtr<mirror::Object> holder,
+ MemberOffset offset,
+ mirror::Object* ref,
+ bool fatal) const {
+ // Lowest priority logging first:
+ PrintFileToLog("/proc/self/maps", LogSeverity::FATAL_WITHOUT_ABORT);
+ MemMap::DumpMaps(LOG_STREAM(FATAL_WITHOUT_ABORT), true);
+ // Buffer the output in the string stream since it is more important than the stack traces
+ // and we want it to have log priority. The stack traces are printed from Runtime::Abort
+ // which is called from LOG(FATAL) but before the abort message.
+ std::ostringstream oss;
+ oss << "GC tried to mark invalid reference " << ref << std::endl;
+ oss << DumpObjectInfo(ref, "ref") << "\n";
+ if (holder != nullptr) {
+ oss << DumpObjectInfo(holder.Ptr(), "holder");
+ mirror::Class* holder_klass = holder->GetClass<kVerifyNone, kWithoutReadBarrier>();
+ if (IsValidClass(holder_klass)) {
+ oss << "field_offset=" << offset.Uint32Value();
+ ArtField* field = holder->FindFieldByOffset(offset);
+ if (field != nullptr) {
+ oss << " name=" << field->GetName();
+ }
+ }
+ }
+
+ if (fatal) {
+ LOG(FATAL) << oss.str();
+ } else {
+ LOG(FATAL_WITHOUT_ABORT) << oss.str();
+ }
+}
+
+bool Verification::IsValidHeapObjectAddress(const void* addr, space::Space** out_space) const {
+ if (!IsAligned<kObjectAlignment>(addr)) {
+ return false;
+ }
+ space::Space* const space = heap_->FindSpaceFromAddress(addr);
+ if (space != nullptr) {
+ if (out_space != nullptr) {
+ *out_space = space;
+ }
+ return true;
+ }
+ return false;
+}
+
+bool Verification::IsValidClass(const void* addr) const {
+ if (!IsValidHeapObjectAddress(addr)) {
+ return false;
+ }
+ mirror::Class* klass = reinterpret_cast<mirror::Class*>(const_cast<void*>(addr));
+ mirror::Class* k1 = klass->GetClass<kVerifyNone, kWithoutReadBarrier>();
+ if (!IsValidHeapObjectAddress(k1)) {
+ return false;
+ }
+ // k should be class class, take the class again to verify.
+ // Note that this check may not be valid for the no image space since the class class might move
+ // around from moving GC.
+ mirror::Class* k2 = k1->GetClass<kVerifyNone, kWithoutReadBarrier>();
+ if (!IsValidHeapObjectAddress(k2)) {
+ return false;
+ }
+ return k1 == k2;
+}
+
+} // namespace gc
+} // namespace art
diff --git a/runtime/gc/verification.h b/runtime/gc/verification.h
new file mode 100644
index 0000000..3d95d93
--- /dev/null
+++ b/runtime/gc/verification.h
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ART_RUNTIME_GC_VERIFICATION_H_
+#define ART_RUNTIME_GC_VERIFICATION_H_
+
+#include "obj_ptr.h"
+#include "offsets.h"
+
+namespace art {
+
+namespace mirror {
+class Class;
+class Object;
+} // namespace mirror
+
+namespace gc {
+
+namespace space {
+class Space;
+} // namespace space
+
+class Heap;
+
+class Verification {
+ public:
+ explicit Verification(gc::Heap* heap) : heap_(heap) {}
+
+ // Dump some reveant to debugging info about an object.
+ std::string DumpObjectInfo(const void* obj, const char* tag) const
+ REQUIRES_SHARED(Locks::mutator_lock_);
+
+ // Don't use ObjPtr for things that might not be aligned like the invalid reference.
+ void LogHeapCorruption(ObjPtr<mirror::Object> holder,
+ MemberOffset offset,
+ mirror::Object* ref,
+ bool fatal) const REQUIRES_SHARED(Locks::mutator_lock_);
+
+
+ // Return true if the klass is likely to be a valid mirror::Class.
+ bool IsValidClass(const void* klass) const REQUIRES_SHARED(Locks::mutator_lock_);
+
+ // Does not allow null.
+ bool IsValidHeapObjectAddress(const void* addr, space::Space** out_space = nullptr) const
+ REQUIRES_SHARED(Locks::mutator_lock_);
+
+ private:
+ gc::Heap* const heap_;
+};
+
+} // namespace gc
+} // namespace art
+
+#endif // ART_RUNTIME_GC_VERIFICATION_H_