From c5196cddffb67db0a09185cbd11b47290c1ed029 Mon Sep 17 00:00:00 2001 From: Mathieu Chartier Date: Fri, 8 Apr 2016 14:08:37 -0700 Subject: [PATCH] Add imgdiag support for diff against zygote Adds option --zygote-diff-pid. If this option is passed in then it tries to differentiate objects that are private dirty by comparing them against the zygote image contents if they are different from the boot image contents. Bug: 27906566 (cherry picked from commit 1398cf23ab47d250ea3eea39812e29a748b70777) Change-Id: I99b1552d046e65dd9da0d1bdb8cb309aa0f9b0db --- imgdiag/imgdiag.cc | 226 +++++++++++++++++++++++++++++++----------------- imgdiag/imgdiag_test.cc | 18 +++- 2 files changed, 161 insertions(+), 83 deletions(-) diff --git a/imgdiag/imgdiag.cc b/imgdiag/imgdiag.cc index cbd0c40b5..214222dd8 100644 --- a/imgdiag/imgdiag.cc +++ b/imgdiag/imgdiag.cc @@ -51,11 +51,13 @@ class ImgDiagDumper { explicit ImgDiagDumper(std::ostream* os, const ImageHeader& image_header, const std::string& image_location, - pid_t image_diff_pid) + pid_t image_diff_pid, + pid_t zygote_diff_pid) : os_(os), image_header_(image_header), image_location_(image_location), - image_diff_pid_(image_diff_pid) {} + image_diff_pid_(image_diff_pid), + zygote_diff_pid_(zygote_diff_pid) {} bool Dump() SHARED_REQUIRES(Locks::mutator_lock_) { std::ostream& os = *os_; @@ -68,7 +70,7 @@ class ImgDiagDumper { bool ret = true; if (image_diff_pid_ >= 0) { os << "IMAGE DIFF PID (" << image_diff_pid_ << "): "; - ret = DumpImageDiff(image_diff_pid_); + ret = DumpImageDiff(image_diff_pid_, zygote_diff_pid_); os << "\n\n"; } else { os << "IMAGE DIFF PID: disabled\n\n"; @@ -95,7 +97,8 @@ class ImgDiagDumper { return str.substr(idx + 1); } - bool DumpImageDiff(pid_t image_diff_pid) SHARED_REQUIRES(Locks::mutator_lock_) { + bool DumpImageDiff(pid_t image_diff_pid, pid_t zygote_diff_pid) + SHARED_REQUIRES(Locks::mutator_lock_) { std::ostream& os = *os_; { @@ -138,7 +141,7 @@ class ImgDiagDumper { } // Future idea: diff against zygote so we can ignore the shared dirty pages. - return DumpImageDiffMap(image_diff_pid, boot_map); + return DumpImageDiffMap(image_diff_pid, zygote_diff_pid, boot_map); } static std::string PrettyFieldValue(ArtField* field, mirror::Object* obj) @@ -212,8 +215,74 @@ class ImgDiagDumper { std::vector dirty_objects; }; + void DiffObjectContents(mirror::Object* obj, + uint8_t* remote_bytes, + std::ostream& os) SHARED_REQUIRES(Locks::mutator_lock_) { + const char* tabs = " "; + // Attempt to find fields for all dirty bytes. + mirror::Class* klass = obj->GetClass(); + if (obj->IsClass()) { + os << tabs << "Class " << PrettyClass(obj->AsClass()) << " " << obj << "\n"; + } else { + os << tabs << "Instance of " << PrettyClass(klass) << " " << obj << "\n"; + } + + std::unordered_set dirty_instance_fields; + std::unordered_set dirty_static_fields; + const uint8_t* obj_bytes = reinterpret_cast(obj); + mirror::Object* remote_obj = reinterpret_cast(remote_bytes); + for (size_t i = 0, count = obj->SizeOf(); i < count; ++i) { + if (obj_bytes[i] != remote_bytes[i]) { + ArtField* field = ArtField::FindInstanceFieldWithOffset(klass, i); + if (field != nullptr) { + dirty_instance_fields.insert(field); + } else if (obj->IsClass()) { + field = ArtField::FindStaticFieldWithOffset(obj->AsClass(), i); + if (field != nullptr) { + dirty_static_fields.insert(field); + } + } + if (field == nullptr) { + if (klass->IsArrayClass()) { + mirror::Class* component_type = klass->GetComponentType(); + Primitive::Type primitive_type = component_type->GetPrimitiveType(); + size_t component_size = Primitive::ComponentSize(primitive_type); + size_t data_offset = mirror::Array::DataOffset(component_size).Uint32Value(); + if (i >= data_offset) { + os << tabs << "Dirty array element " << (i - data_offset) / component_size << "\n"; + // Skip to next element to prevent spam. + i += component_size - 1; + continue; + } + } + os << tabs << "No field for byte offset " << i << "\n"; + } + } + } + // Dump different fields. TODO: Dump field contents. + if (!dirty_instance_fields.empty()) { + os << tabs << "Dirty instance fields " << dirty_instance_fields.size() << "\n"; + for (ArtField* field : dirty_instance_fields) { + os << tabs << PrettyField(field) + << " original=" << PrettyFieldValue(field, obj) + << " remote=" << PrettyFieldValue(field, remote_obj) << "\n"; + } + } + if (!dirty_static_fields.empty()) { + os << tabs << "Dirty static fields " << dirty_static_fields.size() << "\n"; + for (ArtField* field : dirty_static_fields) { + os << tabs << PrettyField(field) + << " original=" << PrettyFieldValue(field, obj) + << " remote=" << PrettyFieldValue(field, remote_obj) << "\n"; + } + } + os << "\n"; + } + // Look at /proc/$pid/mem and only diff the things from there - bool DumpImageDiffMap(pid_t image_diff_pid, const backtrace_map_t& boot_map) + bool DumpImageDiffMap(pid_t image_diff_pid, + pid_t zygote_diff_pid, + const backtrace_map_t& boot_map) SHARED_REQUIRES(Locks::mutator_lock_) { std::ostream& os = *os_; const size_t pointer_size = InstructionSetPointerSize( @@ -272,6 +341,20 @@ class ImgDiagDumper { return false; } + std::vector zygote_contents; + std::unique_ptr zygote_map_file; + if (zygote_diff_pid != -1) { + std::string zygote_file_name = + StringPrintf("/proc/%ld/mem", static_cast(zygote_diff_pid)); // NOLINT [runtime/int] + zygote_map_file.reset(OS::OpenFileForReading(zygote_file_name.c_str())); + // The boot map should be at the same address. + zygote_contents.resize(boot_map_size); + if (!zygote_map_file->PreadFully(&zygote_contents[0], boot_map_size, boot_map.start)) { + LOG(WARNING) << "Could not fully read zygote file " << zygote_file_name; + zygote_contents.clear(); + } + } + std::string page_map_file_name = StringPrintf( "/proc/%ld/pagemap", static_cast(image_diff_pid)); // NOLINT [runtime/int] auto page_map_file = std::unique_ptr(OS::OpenFileForReading(page_map_file_name.c_str())); @@ -416,8 +499,11 @@ class ImgDiagDumper { // Look up local classes by their descriptor std::map local_class_map; - // Use set to have sorted output. - std::set dirty_objects; + // Objects that are dirty against the image (possibly shared or private dirty). + std::set image_dirty_objects; + + // Objects that are dirty against the zygote (probably private dirty). + std::set zygote_dirty_objects; size_t dirty_object_bytes = 0; const uint8_t* begin_image_ptr = image_begin_unaligned; @@ -454,17 +540,29 @@ class ImgDiagDumper { mirror::Class* klass = obj->GetClass(); - bool different_object = false; - // Check against the other object and see if they are different ptrdiff_t offset = current - begin_image_ptr; const uint8_t* current_remote = &remote_contents[offset]; mirror::Object* remote_obj = reinterpret_cast( const_cast(current_remote)); - if (memcmp(current, current_remote, obj->SizeOf()) != 0) { + + bool different_image_object = memcmp(current, current_remote, obj->SizeOf()) != 0; + if (different_image_object) { + bool different_zygote_object = false; + if (!zygote_contents.empty()) { + const uint8_t* zygote_ptr = &zygote_contents[offset]; + different_zygote_object = memcmp(current, zygote_ptr, obj->SizeOf()) != 0; + } + if (different_zygote_object) { + // Different from zygote. + zygote_dirty_objects.insert(obj); + } else { + // Just different from iamge. + image_dirty_objects.insert(obj); + } + different_objects++; dirty_object_bytes += obj->SizeOf(); - dirty_objects.insert(obj); ++class_data[klass].dirty_object_count; @@ -477,16 +575,13 @@ class ImgDiagDumper { } class_data[klass].dirty_object_byte_count += dirty_byte_count_per_object; class_data[klass].dirty_object_size_in_bytes += obj->SizeOf(); - - different_object = true; - class_data[klass].dirty_objects.push_back(remote_obj); } else { ++class_data[klass].clean_object_count; } std::string descriptor = GetClassDescriptor(klass); - if (different_object) { + if (different_image_object) { if (klass->IsClassClass()) { // this is a "Class" mirror::Class* obj_as_class = reinterpret_cast(remote_obj); @@ -558,69 +653,23 @@ class ImgDiagDumper { auto clean_object_class_values = SortByValueDesc( class_data, [](const ClassData& d) { return d.clean_object_count; }); - os << "\n" << " Dirty objects: " << dirty_objects.size() << "\n"; - for (mirror::Object* obj : dirty_objects) { - const char* tabs = " "; - // Attempt to find fields for all dirty bytes. - mirror::Class* klass = obj->GetClass(); - if (obj->IsClass()) { - os << tabs << "Class " << PrettyClass(obj->AsClass()) << " " << obj << "\n"; - } else { - os << tabs << "Instance of " << PrettyClass(klass) << " " << obj << "\n"; + if (!zygote_dirty_objects.empty()) { + os << "\n" << " Dirty objects compared to zygote (probably private dirty): " + << zygote_dirty_objects.size() << "\n"; + for (mirror::Object* obj : zygote_dirty_objects) { + const uint8_t* obj_bytes = reinterpret_cast(obj); + ptrdiff_t offset = obj_bytes - begin_image_ptr; + uint8_t* remote_bytes = &zygote_contents[offset]; + DiffObjectContents(obj, remote_bytes, os); } - - std::unordered_set dirty_instance_fields; - std::unordered_set dirty_static_fields; + } + os << "\n" << " Dirty objects compared to image (private or shared dirty): " + << image_dirty_objects.size() << "\n"; + for (mirror::Object* obj : image_dirty_objects) { const uint8_t* obj_bytes = reinterpret_cast(obj); ptrdiff_t offset = obj_bytes - begin_image_ptr; uint8_t* remote_bytes = &remote_contents[offset]; - mirror::Object* remote_obj = reinterpret_cast(remote_bytes); - for (size_t i = 0, count = obj->SizeOf(); i < count; ++i) { - if (obj_bytes[i] != remote_bytes[i]) { - ArtField* field = ArtField::FindInstanceFieldWithOffset(klass, i); - if (field != nullptr) { - dirty_instance_fields.insert(field); - } else if (obj->IsClass()) { - field = ArtField::FindStaticFieldWithOffset(obj->AsClass(), i); - if (field != nullptr) { - dirty_static_fields.insert(field); - } - } - if (field == nullptr) { - if (klass->IsArrayClass()) { - mirror::Class* component_type = klass->GetComponentType(); - Primitive::Type primitive_type = component_type->GetPrimitiveType(); - size_t component_size = Primitive::ComponentSize(primitive_type); - size_t data_offset = mirror::Array::DataOffset(component_size).Uint32Value(); - if (i >= data_offset) { - os << tabs << "Dirty array element " << (i - data_offset) / component_size << "\n"; - // Skip to next element to prevent spam. - i += component_size - 1; - continue; - } - } - os << tabs << "No field for byte offset " << i << "\n"; - } - } - } - // Dump different fields. TODO: Dump field contents. - if (!dirty_instance_fields.empty()) { - os << tabs << "Dirty instance fields " << dirty_instance_fields.size() << "\n"; - for (ArtField* field : dirty_instance_fields) { - os << tabs << PrettyField(field) - << " original=" << PrettyFieldValue(field, obj) - << " remote=" << PrettyFieldValue(field, remote_obj) << "\n"; - } - } - if (!dirty_static_fields.empty()) { - os << tabs << "Dirty static fields " << dirty_static_fields.size() << "\n"; - for (ArtField* field : dirty_static_fields) { - os << tabs << PrettyField(field) - << " original=" << PrettyFieldValue(field, obj) - << " remote=" << PrettyFieldValue(field, remote_obj) << "\n"; - } - } - os << "\n"; + DiffObjectContents(obj, remote_bytes, os); } os << "\n" << " Dirty object count by class:\n"; @@ -959,11 +1008,15 @@ class ImgDiagDumper { const ImageHeader& image_header_; const std::string image_location_; pid_t image_diff_pid_; // Dump image diff against boot.art if pid is non-negative + pid_t zygote_diff_pid_; // Dump image diff against zygote boot.art if pid is non-negative DISALLOW_COPY_AND_ASSIGN(ImgDiagDumper); }; -static int DumpImage(Runtime* runtime, std::ostream* os, pid_t image_diff_pid) { +static int DumpImage(Runtime* runtime, + std::ostream* os, + pid_t image_diff_pid, + pid_t zygote_diff_pid) { ScopedObjectAccess soa(Thread::Current()); gc::Heap* heap = runtime->GetHeap(); std::vector image_spaces = heap->GetBootImageSpaces(); @@ -975,8 +1028,11 @@ static int DumpImage(Runtime* runtime, std::ostream* os, pid_t image_diff_pid) { return EXIT_FAILURE; } - ImgDiagDumper img_diag_dumper( - os, image_header, image_space->GetImageLocation(), image_diff_pid); + ImgDiagDumper img_diag_dumper(os, + image_header, + image_space->GetImageLocation(), + image_diff_pid, + zygote_diff_pid); if (!img_diag_dumper.Dump()) { return EXIT_FAILURE; } @@ -1004,6 +1060,13 @@ struct ImgDiagArgs : public CmdlineArgs { *error_msg = "Image diff pid out of range"; return kParseError; } + } else if (option.starts_with("--zygote-diff-pid=")) { + const char* zygote_diff_pid = option.substr(strlen("--zygote-diff-pid=")).data(); + + if (!ParseInt(zygote_diff_pid, &zygote_diff_pid_)) { + *error_msg = "Zygote diff pid out of range"; + return kParseError; + } } else { return kParseUnknownArgument; } @@ -1053,6 +1116,9 @@ struct ImgDiagArgs : public CmdlineArgs { usage += // Optional. " --image-diff-pid=: provide the PID of a process whose boot.art you want to diff.\n" " Example: --image-diff-pid=$(pid zygote)\n" + " --zygote-diff-pid=: provide the PID of the zygote whose boot.art you want to diff " + "against.\n" + " Example: --zygote-diff-pid=$(pid zygote)\n" "\n"; return usage; @@ -1060,6 +1126,7 @@ struct ImgDiagArgs : public CmdlineArgs { public: pid_t image_diff_pid_ = -1; + pid_t zygote_diff_pid_ = -1; }; struct ImgDiagMain : public CmdlineMain { @@ -1068,7 +1135,8 @@ struct ImgDiagMain : public CmdlineMain { return DumpImage(runtime, args_->os_, - args_->image_diff_pid_) == EXIT_SUCCESS; + args_->image_diff_pid_, + args_->zygote_diff_pid_) == EXIT_SUCCESS; } }; diff --git a/imgdiag/imgdiag_test.cc b/imgdiag/imgdiag_test.cc index dc101e50b..9f771ba8a 100644 --- a/imgdiag/imgdiag_test.cc +++ b/imgdiag/imgdiag_test.cc @@ -36,6 +36,8 @@ static const char* kImgDiagDiffPid = "--image-diff-pid"; static const char* kImgDiagBootImage = "--boot-image"; static const char* kImgDiagBinaryName = "imgdiag"; +static const char* kImgDiagZygoteDiffPid = "--zygote-diff-pid"; + // from kernel #define PID_MAX_LIMIT (4*1024*1024) // Upper bound. Most kernel configs will have smaller max pid. @@ -90,17 +92,25 @@ class ImgDiagTest : public CommonRuntimeTest { // Run imgdiag --image-diff-pid=$image_diff_pid and wait until it's done with a 0 exit code. std::string diff_pid_args; + std::string zygote_diff_pid_args; { std::stringstream diff_pid_args_ss; diff_pid_args_ss << kImgDiagDiffPid << "=" << image_diff_pid; diff_pid_args = diff_pid_args_ss.str(); } - std::string boot_image_args; { - boot_image_args = boot_image_args + kImgDiagBootImage + "=" + boot_image; + std::stringstream zygote_pid_args_ss; + zygote_pid_args_ss << kImgDiagZygoteDiffPid << "=" << image_diff_pid; + zygote_diff_pid_args = zygote_pid_args_ss.str(); } - - std::vector exec_argv = { file_path, diff_pid_args, boot_image_args }; + std::string boot_image_args = std::string(kImgDiagBootImage) + "=" + boot_image; + + std::vector exec_argv = { + file_path, + diff_pid_args, + zygote_diff_pid_args, + boot_image_args + }; return ::art::Exec(exec_argv, error_msg); } -- 2.11.0