Show ArtMethods in imgdiag
Since ArtMethods were moved out of mirror:: classes imgdiag does not
show information about them. Diff ArtMethods to facilitate finding
dirty memory there.
Bug: 38173645
Test: imgdiag --boot-image=/system/framework/boot.art --image-diff-pid=`pid system_server`
Change-Id: Icd86a9ef14d5177a297026c22c81c080f5c85fc1
diff --git a/imgdiag/imgdiag.cc b/imgdiag/imgdiag.cc
index 00e2a89..d3b8ce1 100644
--- a/imgdiag/imgdiag.cc
+++ b/imgdiag/imgdiag.cc
@@ -31,11 +31,15 @@
#include "art_field-inl.h"
#include "art_method-inl.h"
#include "base/unix_file/fd_file.h"
+#include "class_linker.h"
#include "gc/heap.h"
#include "gc/space/image_space.h"
#include "image.h"
#include "mirror/class-inl.h"
#include "mirror/object-inl.h"
+#include "oat.h"
+#include "oat_file.h"
+#include "oat_file_manager.h"
#include "os.h"
#include "scoped_thread_state_change-inl.h"
@@ -326,6 +330,37 @@
};
// Region analysis for mirror::Objects
+class ImgObjectVisitor : public ObjectVisitor {
+ public:
+ using ComputeDirtyFunc = std::function<void(mirror::Object* object,
+ const uint8_t* begin_image_ptr,
+ const std::set<size_t>& dirty_pages)>;
+ ImgObjectVisitor(ComputeDirtyFunc dirty_func,
+ const uint8_t* begin_image_ptr,
+ const std::set<size_t>& dirty_pages) :
+ dirty_func_(dirty_func),
+ begin_image_ptr_(begin_image_ptr),
+ dirty_pages_(dirty_pages) { }
+
+ virtual ~ImgObjectVisitor() OVERRIDE { }
+
+ virtual void Visit(mirror::Object* object) OVERRIDE REQUIRES_SHARED(Locks::mutator_lock_) {
+ // Sanity check that we are reading a real mirror::Object
+ CHECK(object->GetClass() != nullptr) << "Image object at address "
+ << object
+ << " has null class";
+ if (kUseBakerReadBarrier) {
+ object->AssertReadBarrierState();
+ }
+ dirty_func_(object, begin_image_ptr_, dirty_pages_);
+ }
+
+ private:
+ ComputeDirtyFunc dirty_func_;
+ const uint8_t* begin_image_ptr_;
+ const std::set<size_t>& dirty_pages_;
+};
+
template<>
class RegionSpecializedBase<mirror::Object> : public RegionCommon<mirror::Object> {
public:
@@ -339,24 +374,14 @@
os_(*os),
dump_dirty_objects_(dump_dirty_objects) { }
- void CheckEntrySanity(const uint8_t* current) const
- REQUIRES_SHARED(Locks::mutator_lock_) {
- CHECK_ALIGNED(current, kObjectAlignment);
- mirror::Object* entry = reinterpret_cast<mirror::Object*>(const_cast<uint8_t*>(current));
- // Sanity check that we are reading a real mirror::Object
- CHECK(entry->GetClass() != nullptr) << "Image object at address "
- << entry
- << " has null class";
- if (kUseBakerReadBarrier) {
- entry->AssertReadBarrierState();
- }
- }
+ // Define a common public type name for use by RegionData.
+ using VisitorClass = ImgObjectVisitor;
- mirror::Object* GetNextEntry(mirror::Object* entry)
+ void VisitEntries(VisitorClass* visitor,
+ uint8_t* base,
+ PointerSize pointer_size)
REQUIRES_SHARED(Locks::mutator_lock_) {
- uint8_t* next =
- reinterpret_cast<uint8_t*>(entry) + RoundUp(EntrySize(entry), kObjectAlignment);
- return reinterpret_cast<mirror::Object*>(next);
+ RegionCommon<mirror::Object>::image_header_.VisitObjects(visitor, base, pointer_size);
}
void VisitEntry(mirror::Object* entry)
@@ -616,40 +641,93 @@
};
// Region analysis for ArtMethods.
-// TODO: most of these need work.
+class ImgArtMethodVisitor : public ArtMethodVisitor {
+ public:
+ using ComputeDirtyFunc = std::function<void(ArtMethod*,
+ const uint8_t*,
+ const std::set<size_t>&)>;
+ ImgArtMethodVisitor(ComputeDirtyFunc dirty_func,
+ const uint8_t* begin_image_ptr,
+ const std::set<size_t>& dirty_pages) :
+ dirty_func_(dirty_func),
+ begin_image_ptr_(begin_image_ptr),
+ dirty_pages_(dirty_pages) { }
+ virtual ~ImgArtMethodVisitor() OVERRIDE { }
+ virtual void Visit(ArtMethod* method) OVERRIDE {
+ dirty_func_(method, begin_image_ptr_, dirty_pages_);
+ }
+
+ private:
+ ComputeDirtyFunc dirty_func_;
+ const uint8_t* begin_image_ptr_;
+ const std::set<size_t>& dirty_pages_;
+};
+
+// Struct and functor for computing offsets of members of ArtMethods.
+// template <typename RegionType>
+struct MemberInfo {
+ template <typename T>
+ void operator() (const ArtMethod* method, const T* member_address, const std::string& name) {
+ // Check that member_address is a pointer inside *method.
+ DCHECK(reinterpret_cast<uintptr_t>(method) <= reinterpret_cast<uintptr_t>(member_address));
+ DCHECK(reinterpret_cast<uintptr_t>(member_address) + sizeof(T) <=
+ reinterpret_cast<uintptr_t>(method) + sizeof(ArtMethod));
+ size_t offset =
+ reinterpret_cast<uintptr_t>(member_address) - reinterpret_cast<uintptr_t>(method);
+ offset_to_name_size_.insert({offset, NameAndSize(sizeof(T), name)});
+ }
+
+ struct NameAndSize {
+ size_t size_;
+ std::string name_;
+ NameAndSize(size_t size, const std::string& name) : size_(size), name_(name) { }
+ NameAndSize() : size_(0), name_("INVALID") { }
+ };
+
+ std::map<size_t, NameAndSize> offset_to_name_size_;
+};
+
template<>
-class RegionSpecializedBase<ArtMethod> : RegionCommon<ArtMethod> {
+class RegionSpecializedBase<ArtMethod> : public RegionCommon<ArtMethod> {
public:
RegionSpecializedBase(std::ostream* os,
std::vector<uint8_t>* remote_contents,
std::vector<uint8_t>* zygote_contents,
const backtrace_map_t& boot_map,
- const ImageHeader& image_header) :
- RegionCommon<ArtMethod>(os, remote_contents, zygote_contents, boot_map, image_header),
- os_(*os) { }
-
- void CheckEntrySanity(const uint8_t* current ATTRIBUTE_UNUSED) const
- REQUIRES_SHARED(Locks::mutator_lock_) {
+ const ImageHeader& image_header,
+ bool dump_dirty_objects ATTRIBUTE_UNUSED)
+ : RegionCommon<ArtMethod>(os, remote_contents, zygote_contents, boot_map, image_header),
+ os_(*os) {
+ // Prepare the table for offset to member lookups.
+ ArtMethod* art_method = reinterpret_cast<ArtMethod*>(&(*remote_contents)[0]);
+ art_method->VisitMembers(member_info_);
+ // Prepare the table for address to symbolic entry point names.
+ BuildEntryPointNames();
+ class_linker_ = Runtime::Current()->GetClassLinker();
}
- ArtMethod* GetNextEntry(ArtMethod* entry)
+ // Define a common public type name for use by RegionData.
+ using VisitorClass = ImgArtMethodVisitor;
+
+ void VisitEntries(VisitorClass* visitor,
+ uint8_t* base,
+ PointerSize pointer_size)
REQUIRES_SHARED(Locks::mutator_lock_) {
- uint8_t* next = reinterpret_cast<uint8_t*>(entry) + RoundUp(EntrySize(entry), kObjectAlignment);
- return reinterpret_cast<ArtMethod*>(next);
+ RegionCommon<ArtMethod>::image_header_.VisitPackedArtMethods(visitor, base, pointer_size);
}
void VisitEntry(ArtMethod* method ATTRIBUTE_UNUSED)
REQUIRES_SHARED(Locks::mutator_lock_) {
}
+ void AddCleanEntry(ArtMethod* method ATTRIBUTE_UNUSED) {
+ }
+
void AddFalseDirtyEntry(ArtMethod* method)
REQUIRES_SHARED(Locks::mutator_lock_) {
RegionCommon<ArtMethod>::AddFalseDirtyEntry(method);
}
- void AddCleanEntry(ArtMethod* method ATTRIBUTE_UNUSED) {
- }
-
void AddDirtyEntry(ArtMethod* method, ArtMethod* method_remote)
REQUIRES_SHARED(Locks::mutator_lock_) {
size_t entry_size = EntrySize(method);
@@ -667,14 +745,50 @@
dirty_entries_.push_back(method);
}
- void DiffEntryContents(ArtMethod* method ATTRIBUTE_UNUSED,
- uint8_t* remote_bytes ATTRIBUTE_UNUSED,
- const uint8_t* base_ptr ATTRIBUTE_UNUSED)
+ void DiffEntryContents(ArtMethod* method,
+ uint8_t* remote_bytes,
+ const uint8_t* base_ptr,
+ bool log_dirty_objects ATTRIBUTE_UNUSED)
REQUIRES_SHARED(Locks::mutator_lock_) {
+ const char* tabs = " ";
+ os_ << tabs << "ArtMethod " << ArtMethod::PrettyMethod(method) << "\n";
+
+ std::unordered_set<size_t> dirty_members;
+ // Examine the members comprising the ArtMethod, computing which members are dirty.
+ for (const std::pair<size_t, MemberInfo::NameAndSize>& p : member_info_.offset_to_name_size_) {
+ const size_t offset = p.first;
+ if (memcmp(base_ptr + offset, remote_bytes + offset, p.second.size_) != 0) {
+ dirty_members.insert(p.first);
+ }
+ }
+ // Dump different fields.
+ if (!dirty_members.empty()) {
+ os_ << tabs << "Dirty members " << dirty_members.size() << "\n";
+ for (size_t offset : dirty_members) {
+ const MemberInfo::NameAndSize& member_info = member_info_.offset_to_name_size_[offset];
+ os_ << tabs << member_info.name_
+ << " original=" << StringFromBytes(base_ptr + offset, member_info.size_)
+ << " remote=" << StringFromBytes(remote_bytes + offset, member_info.size_)
+ << "\n";
+ }
+ }
+ os_ << "\n";
+ }
+
+ void DumpDirtyObjects() REQUIRES_SHARED(Locks::mutator_lock_) {
}
void DumpDirtyEntries() REQUIRES_SHARED(Locks::mutator_lock_) {
DumpSamplesAndOffsetCount();
+ os_ << " offset to field map:\n";
+ for (const std::pair<size_t, MemberInfo::NameAndSize>& p : member_info_.offset_to_name_size_) {
+ const size_t offset = p.first;
+ const size_t size = p.second.size_;
+ os_ << StringPrintf(" %zu-%zu: ", offset, offset + size - 1)
+ << p.second.name_
+ << std::endl;
+ }
+
os_ << " field contents:\n";
for (ArtMethod* method : dirty_entries_) {
// remote method
@@ -694,6 +808,7 @@
}
void DumpFalseDirtyEntries() REQUIRES_SHARED(Locks::mutator_lock_) {
+ os_ << "\n" << " False-dirty ArtMethods\n";
os_ << " field contents:\n";
for (ArtMethod* method : false_dirty_entries_) {
// local class
@@ -707,6 +822,84 @@
private:
std::ostream& os_;
+ MemberInfo member_info_;
+ std::map<const void*, std::string> entry_point_names_;
+ ClassLinker* class_linker_;
+
+ // Compute a map of addresses to names in the boot OAT file(s).
+ void BuildEntryPointNames() {
+ OatFileManager& oat_file_manager = Runtime::Current()->GetOatFileManager();
+ std::vector<const OatFile*> boot_oat_files = oat_file_manager.GetBootOatFiles();
+ for (const OatFile* oat_file : boot_oat_files) {
+ const OatHeader& oat_header = oat_file->GetOatHeader();
+ const void* i2ib = oat_header.GetInterpreterToInterpreterBridge();
+ if (i2ib != nullptr) {
+ entry_point_names_[i2ib] = "InterpreterToInterpreterBridge (from boot oat file)";
+ }
+ const void* i2ccb = oat_header.GetInterpreterToCompiledCodeBridge();
+ if (i2ccb != nullptr) {
+ entry_point_names_[i2ccb] = "InterpreterToCompiledCodeBridge (from boot oat file)";
+ }
+ const void* jdl = oat_header.GetJniDlsymLookup();
+ if (jdl != nullptr) {
+ entry_point_names_[jdl] = "JniDlsymLookup (from boot oat file)";
+ }
+ const void* qgjt = oat_header.GetQuickGenericJniTrampoline();
+ if (qgjt != nullptr) {
+ entry_point_names_[qgjt] = "QuickGenericJniTrampoline (from boot oat file)";
+ }
+ const void* qrt = oat_header.GetQuickResolutionTrampoline();
+ if (qrt != nullptr) {
+ entry_point_names_[qrt] = "QuickResolutionTrampoline (from boot oat file)";
+ }
+ const void* qict = oat_header.GetQuickImtConflictTrampoline();
+ if (qict != nullptr) {
+ entry_point_names_[qict] = "QuickImtConflictTrampoline (from boot oat file)";
+ }
+ const void* q2ib = oat_header.GetQuickToInterpreterBridge();
+ if (q2ib != nullptr) {
+ entry_point_names_[q2ib] = "QuickToInterpreterBridge (from boot oat file)";
+ }
+ }
+ }
+
+ std::string StringFromBytes(const uint8_t* bytes, size_t size) {
+ switch (size) {
+ case 1:
+ return StringPrintf("%" PRIx8, *bytes);
+ case 2:
+ return StringPrintf("%" PRIx16, *reinterpret_cast<const uint16_t*>(bytes));
+ case 4:
+ case 8: {
+ // Compute an address if the bytes might contain one.
+ uint64_t intval;
+ if (size == 4) {
+ intval = *reinterpret_cast<const uint32_t*>(bytes);
+ } else {
+ intval = *reinterpret_cast<const uint64_t*>(bytes);
+ }
+ const void* addr = reinterpret_cast<const void*>(intval);
+ // Match the address against those that have Is* methods in the ClassLinker.
+ if (class_linker_->IsQuickToInterpreterBridge(addr)) {
+ return "QuickToInterpreterBridge";
+ } else if (class_linker_->IsQuickGenericJniStub(addr)) {
+ return "QuickGenericJniStub";
+ } else if (class_linker_->IsQuickResolutionStub(addr)) {
+ return "QuickResolutionStub";
+ } else if (class_linker_->IsJniDlsymLookupStub(addr)) {
+ return "JniDlsymLookupStub";
+ }
+ // Match the address against those that we saved from the boot OAT files.
+ if (entry_point_names_.find(addr) != entry_point_names_.end()) {
+ return entry_point_names_[addr];
+ }
+ return StringPrintf("%" PRIx64, intval);
+ }
+ default:
+ LOG(WARNING) << "Don't know how to convert " << size << " bytes to integer";
+ return "<UNKNOWN>";
+ }
+ }
void DumpOneArtMethod(ArtMethod* art_method,
mirror::Class* declaring_class,
@@ -721,7 +914,10 @@
art_method->GetEntryPointFromQuickCompiledCodePtrSize(pointer_size))
<< ", ";
os_ << " isNative? " << (art_method->IsNative() ? "yes" : "no") << ", ";
- os_ << " class_status (local): " << declaring_class->GetStatus();
+ // Null for runtime metionds.
+ if (declaring_class != nullptr) {
+ os_ << " class_status (local): " << declaring_class->GetStatus();
+ }
if (remote_declaring_class != nullptr) {
os_ << ", class_status (remote): " << remote_declaring_class->GetStatus();
}
@@ -755,16 +951,20 @@
// collecting and reporting data regarding dirty, difference, etc.
void ProcessRegion(const MappingData& mapping_data,
RemoteProcesses remotes,
- const uint8_t* begin_image_ptr,
- const uint8_t* end_image_ptr)
+ const uint8_t* begin_image_ptr)
REQUIRES_SHARED(Locks::mutator_lock_) {
- const uint8_t* current = begin_image_ptr + RoundUp(sizeof(ImageHeader), kObjectAlignment);
- T* entry = reinterpret_cast<T*>(const_cast<uint8_t*>(current));
- while (reinterpret_cast<uintptr_t>(entry) < reinterpret_cast<uintptr_t>(end_image_ptr)) {
- ComputeEntryDirty(entry, begin_image_ptr, mapping_data.dirty_page_set);
-
- entry = RegionSpecializedBase<T>::GetNextEntry(entry);
- }
+ typename RegionSpecializedBase<T>::VisitorClass visitor(
+ [this](T* entry,
+ const uint8_t* begin_image_ptr,
+ const std::set<size_t>& dirty_page_set) REQUIRES_SHARED(Locks::mutator_lock_) {
+ this->ComputeEntryDirty(entry, begin_image_ptr, dirty_page_set);
+ },
+ begin_image_ptr,
+ mapping_data.dirty_page_set);
+ PointerSize pointer_size = InstructionSetPointerSize(Runtime::Current()->GetInstructionSet());
+ RegionSpecializedBase<T>::VisitEntries(&visitor,
+ const_cast<uint8_t*>(begin_image_ptr),
+ pointer_size);
// Looking at only dirty pages, figure out how many of those bytes belong to dirty entries.
// TODO: fix this now that there are multiple regions in a mapping.
@@ -1208,8 +1408,6 @@
<< "\n\n";
const uint8_t* image_begin_unaligned = image_header_.GetImageBegin();
- const uint8_t* image_mirror_end_unaligned = image_begin_unaligned +
- image_header_.GetObjectsSection().Size();
const uint8_t* image_end_unaligned = image_begin_unaligned + image_header_.GetImageSize();
// Adjust range to nearest page
@@ -1235,14 +1433,6 @@
if (!ComputeDirtyBytes(image_begin, &mapping_data)) {
return false;
}
-
- RegionData<mirror::Object> object_region_data(os_,
- &remote_contents_,
- &zygote_contents_,
- boot_map_,
- image_header_,
- dump_dirty_objects_);
-
RemoteProcesses remotes;
if (zygote_pid_only_) {
remotes = RemoteProcesses::kZygoteOnly;
@@ -1252,11 +1442,27 @@
remotes = RemoteProcesses::kImageOnly;
}
+ // Check all the mirror::Object entries in the image.
+ RegionData<mirror::Object> object_region_data(os_,
+ &remote_contents_,
+ &zygote_contents_,
+ boot_map_,
+ image_header_,
+ dump_dirty_objects_);
object_region_data.ProcessRegion(mapping_data,
remotes,
- image_begin_unaligned,
- image_mirror_end_unaligned);
+ image_begin_unaligned);
+ // Check all the ArtMethod entries in the image.
+ RegionData<ArtMethod> artmethod_region_data(os_,
+ &remote_contents_,
+ &zygote_contents_,
+ boot_map_,
+ image_header_,
+ dump_dirty_objects_);
+ artmethod_region_data.ProcessRegion(mapping_data,
+ remotes,
+ image_begin_unaligned);
return true;
}
diff --git a/runtime/art_method.h b/runtime/art_method.h
index 2d67761..dab3f23 100644
--- a/runtime/art_method.h
+++ b/runtime/art_method.h
@@ -671,6 +671,24 @@
template <ReadBarrierOption kReadBarrierOption = kWithReadBarrier, typename Visitor>
ALWAYS_INLINE void UpdateEntrypoints(const Visitor& visitor, PointerSize pointer_size);
+ // Visit the individual members of an ArtMethod. Used by imgdiag.
+ // As imgdiag does not support mixing instruction sets or pointer sizes (e.g., using imgdiag32
+ // to inspect 64-bit images, etc.), we can go beneath the accessors directly to the class members.
+ template <typename VisitorFunc>
+ void VisitMembers(VisitorFunc& visitor) {
+ DCHECK(IsImagePointerSize(kRuntimePointerSize));
+ visitor(this, &declaring_class_, "declaring_class_");
+ visitor(this, &access_flags_, "access_flags_");
+ visitor(this, &dex_code_item_offset_, "dex_code_item_offset_");
+ visitor(this, &dex_method_index_, "dex_method_index_");
+ visitor(this, &method_index_, "method_index_");
+ visitor(this, &hotness_count_, "hotness_count_");
+ visitor(this, &ptr_sized_fields_.data_, "ptr_sized_fields_.data_");
+ visitor(this,
+ &ptr_sized_fields_.entry_point_from_quick_compiled_code_,
+ "ptr_sized_fields_.entry_point_from_quick_compiled_code_");
+ }
+
protected:
// Field order required by test "ValidateFieldOrderOfJavaCppUnionClasses".
// The class we are a part of.
diff --git a/runtime/class_linker.cc b/runtime/class_linker.cc
index 4161754..835d940 100644
--- a/runtime/class_linker.cc
+++ b/runtime/class_linker.cc
@@ -8368,6 +8368,10 @@
(quick_generic_jni_trampoline_ == entry_point);
}
+bool ClassLinker::IsJniDlsymLookupStub(const void* entry_point) const {
+ return entry_point == GetJniDlsymLookupStub();
+}
+
const void* ClassLinker::GetRuntimeQuickGenericJniStub() const {
return GetQuickGenericJniStub();
}
diff --git a/runtime/class_linker.h b/runtime/class_linker.h
index 9727adf..2f92da3 100644
--- a/runtime/class_linker.h
+++ b/runtime/class_linker.h
@@ -500,6 +500,9 @@
// Is the given entry point quick code to run the generic JNI stub?
bool IsQuickGenericJniStub(const void* entry_point) const;
+ // Is the given entry point the JNI dlsym lookup stub?
+ bool IsJniDlsymLookupStub(const void* entry_point) const;
+
const void* GetQuickToInterpreterBridgeTrampoline() const {
return quick_to_interpreter_bridge_trampoline_;
}
diff --git a/runtime/image.cc b/runtime/image.cc
index c8581c1..0236f47 100644
--- a/runtime/image.cc
+++ b/runtime/image.cc
@@ -144,6 +144,19 @@
return os << "size=" << section.Size() << " range=" << section.Offset() << "-" << section.End();
}
+void ImageHeader::VisitObjects(ObjectVisitor* visitor,
+ uint8_t* base,
+ PointerSize pointer_size) const {
+ DCHECK_EQ(pointer_size, GetPointerSize());
+ const ImageSection& objects = GetObjectsSection();
+ static const size_t kStartPos = RoundUp(sizeof(ImageHeader), kObjectAlignment);
+ for (size_t pos = kStartPos; pos < objects.Size(); ) {
+ mirror::Object* object = reinterpret_cast<mirror::Object*>(base + objects.Offset() + pos);
+ visitor->Visit(object);
+ pos += RoundUp(object->SizeOf(), kObjectAlignment);
+ }
+}
+
void ImageHeader::VisitPackedArtFields(ArtFieldVisitor* visitor, uint8_t* base) const {
const ImageSection& fields = GetFieldsSection();
for (size_t pos = 0; pos < fields.Size(); ) {
diff --git a/runtime/image.h b/runtime/image.h
index 2b24087..da04333 100644
--- a/runtime/image.h
+++ b/runtime/image.h
@@ -29,6 +29,13 @@
class ArtField;
class ArtMethod;
+class ObjectVisitor {
+ public:
+ virtual ~ObjectVisitor() {}
+
+ virtual void Visit(mirror::Object* object) = 0;
+};
+
class ArtMethodVisitor {
public:
virtual ~ArtMethodVisitor() {}
@@ -328,6 +335,13 @@
return RoundUp(tables_size, kPageSize);
}
+ // Visit mirror::Objects in the section starting at base.
+ // TODO: Delete base parameter if it is always equal to GetImageBegin.
+ void VisitObjects(ObjectVisitor* visitor,
+ uint8_t* base,
+ PointerSize pointer_size) const
+ REQUIRES_SHARED(Locks::mutator_lock_);
+
// Visit ArtMethods in the section starting at base. Includes runtime methods.
// TODO: Delete base parameter if it is always equal to GetImageBegin.
void VisitPackedArtMethods(ArtMethodVisitor* visitor,