diff options
96 files changed, 3176 insertions, 721 deletions
diff --git a/build/Android.bp b/build/Android.bp index ed6de3546f..6d35debec2 100644 --- a/build/Android.bp +++ b/build/Android.bp @@ -153,6 +153,16 @@ art_global_defaults {          // No exceptions.          "-misc-noexcept-move-constructor",      ], + +    tidy_flags: [ +        // The static analyzer treats DCHECK as always enabled; we sometimes get +        // false positives when we use DCHECKs with code that relies on NDEBUG. +        "-extra-arg=-UNDEBUG", +        // clang-tidy complains about functions like: +        // void foo() { CHECK(kIsFooEnabled); /* do foo... */ } +        // not being marked noreturn if kIsFooEnabled is false. +        "-extra-arg=-Wno-missing-noreturn", +    ],  }  art_debug_defaults { diff --git a/dexlayout/dexlayout.cc b/dexlayout/dexlayout.cc index 22f0cb042e..f886de24b6 100644 --- a/dexlayout/dexlayout.cc +++ b/dexlayout/dexlayout.cc @@ -1557,7 +1557,7 @@ void DexLayout::LayoutStringData(const DexFile* dex_file) {              (method->GetAccessFlags() & kAccConstructor) != 0 &&              (method->GetAccessFlags() & kAccStatic) != 0;          const bool method_executed = is_clinit || -            info_->GetMethodHotness(MethodReference(dex_file, method_id->GetIndex())).HasAnyFlags(); +            info_->GetMethodHotness(MethodReference(dex_file, method_id->GetIndex())).IsInProfile();          if (!method_executed) {            continue;          } @@ -1712,7 +1712,7 @@ int32_t DexLayout::LayoutCodeItems(const DexFile* dex_file,            state = kCodeItemStateExecStartupOnly;          } else if (is_clinit) {            state = kCodeItemStateClinit; -        } else if (hotness.HasAnyFlags()) { +        } else if (hotness.IsInProfile()) {            state = kCodeItemStateExec;          }          code_items[state].insert(code_item); diff --git a/imgdiag/imgdiag.cc b/imgdiag/imgdiag.cc index e3b11951e3..d3ab838ce7 100644 --- a/imgdiag/imgdiag.cc +++ b/imgdiag/imgdiag.cc @@ -64,61 +64,24 @@ class ImgDiagDumper {          zygote_diff_pid_(zygote_diff_pid),          zygote_pid_only_(false) {} -  bool Dump() REQUIRES_SHARED(Locks::mutator_lock_) { +  bool Init() {      std::ostream& os = *os_; -    os << "IMAGE LOCATION: " << image_location_ << "\n\n"; - -    os << "MAGIC: " << image_header_.GetMagic() << "\n\n"; - -    os << "IMAGE BEGIN: " << reinterpret_cast<void*>(image_header_.GetImageBegin()) << "\n\n"; - -    PrintPidLine("IMAGE", image_diff_pid_); -    os << "\n\n"; -    PrintPidLine("ZYGOTE", zygote_diff_pid_); -    bool ret = true; -    if (image_diff_pid_ >= 0 || zygote_diff_pid_ >= 0) { -      if (image_diff_pid_ < 0) { -        image_diff_pid_ = zygote_diff_pid_; -        zygote_diff_pid_ = -1; -        zygote_pid_only_ = true; -      } -      ret = DumpImageDiff(); -      os << "\n\n"; -    } -    os << std::flush; - -    return ret; -  } - - private: -  void PrintPidLine(const std::string& kind, pid_t pid) { -    if (pid < 0) { -      *os_ << kind << " DIFF PID: disabled\n\n"; -    } else { -      *os_ << kind << " DIFF PID (" << pid << "): "; +    if (image_diff_pid_ < 0 && zygote_diff_pid_ < 0) { +      os << "Either --image-diff-pid or --zygote-diff-pid (or both) must be specified.\n"; +      return false;      } -  } -  static bool EndsWith(const std::string& str, const std::string& suffix) { -    return str.size() >= suffix.size() && -           str.compare(str.size() - suffix.size(), suffix.size(), suffix) == 0; -  } - -  // Return suffix of the file path after the last /. (e.g. /foo/bar -> bar, bar -> bar) -  static std::string BaseName(const std::string& str) { -    size_t idx = str.rfind('/'); -    if (idx == std::string::npos) { -      return str; +    // To avoid the combinations of command-line argument use cases: +    // If the user invoked with only --zygote-diff-pid, shuffle that to +    // image_diff_pid_, invalidate zygote_diff_pid_, and remember that +    // image_diff_pid_ is now special. +    if (image_diff_pid_ < 0) { +      image_diff_pid_ = zygote_diff_pid_; +      zygote_diff_pid_ = -1; +      zygote_pid_only_ = true;      } -    return str.substr(idx + 1); -  } - -  bool DumpImageDiff() -      REQUIRES_SHARED(Locks::mutator_lock_) { -    std::ostream& os = *os_; -      {        struct stat sts;        std::string proc_pid_str = @@ -130,19 +93,18 @@ class ImgDiagDumper {      }      // Open /proc/$pid/maps to view memory maps -    auto proc_maps = std::unique_ptr<BacktraceMap>(BacktraceMap::Create(image_diff_pid_)); -    if (proc_maps == nullptr) { +    auto tmp_proc_maps = std::unique_ptr<BacktraceMap>(BacktraceMap::Create(image_diff_pid_)); +    if (tmp_proc_maps == nullptr) {        os << "Could not read backtrace maps";        return false;      }      bool found_boot_map = false; -    backtrace_map_t boot_map = backtrace_map_t();      // Find the memory map only for boot.art -    for (const backtrace_map_t& map : *proc_maps) { +    for (const backtrace_map_t& map : *tmp_proc_maps) {        if (EndsWith(map.name, GetImageLocationBaseName())) {          if ((map.flags & PROT_WRITE) != 0) { -          boot_map = map; +          boot_map_ = map;            found_boot_map = true;            break;          } @@ -157,9 +119,232 @@ class ImgDiagDumper {        os << "Could not find map for " << GetImageLocationBaseName();        return false;      } +    // Sanity check boot_map_. +    CHECK(boot_map_.end >= boot_map_.start); +    boot_map_size_ = boot_map_.end - boot_map_.start; -    // Future idea: diff against zygote so we can ignore the shared dirty pages. -    return DumpImageDiffMap(boot_map); +    pointer_size_ = InstructionSetPointerSize(Runtime::Current()->GetInstructionSet()); + +    // Open /proc/<image_diff_pid_>/mem and read as remote_contents_. +    std::string image_file_name = +        StringPrintf("/proc/%ld/mem", static_cast<long>(image_diff_pid_));  // NOLINT [runtime/int] +    auto image_map_file = std::unique_ptr<File>(OS::OpenFileForReading(image_file_name.c_str())); +    if (image_map_file == nullptr) { +      os << "Failed to open " << image_file_name << " for reading"; +      return false; +    } +    std::vector<uint8_t> tmp_remote_contents(boot_map_size_); +    if (!image_map_file->PreadFully(&tmp_remote_contents[0], boot_map_size_, boot_map_.start)) { +      os << "Could not fully read file " << image_file_name; +      return false; +    } + +    // If zygote_diff_pid_ != -1, open /proc/<zygote_diff_pid_>/mem and read as zygote_contents_. +    std::vector<uint8_t> tmp_zygote_contents; +    if (zygote_diff_pid_ != -1) { +      std::string zygote_file_name = +          StringPrintf("/proc/%ld/mem", static_cast<long>(zygote_diff_pid_));  // NOLINT [runtime/int] +      std::unique_ptr<File> zygote_map_file(OS::OpenFileForReading(zygote_file_name.c_str())); +      if (zygote_map_file == nullptr) { +        os << "Failed to open " << zygote_file_name << " for reading"; +        return false; +      } +      // The boot map should be at the same address. +      tmp_zygote_contents.reserve(boot_map_size_); +      if (!zygote_map_file->PreadFully(&tmp_zygote_contents[0], boot_map_size_, boot_map_.start)) { +        LOG(WARNING) << "Could not fully read zygote file " << zygote_file_name; +        return false; +      } +    } + +    // Open /proc/<image_diff_pid_>/pagemap. +    std::string pagemap_file_name = StringPrintf( +        "/proc/%ld/pagemap", static_cast<long>(image_diff_pid_));  // NOLINT [runtime/int] +    auto tmp_pagemap_file = +        std::unique_ptr<File>(OS::OpenFileForReading(pagemap_file_name.c_str())); +    if (tmp_pagemap_file == nullptr) { +      os << "Failed to open " << pagemap_file_name << " for reading: " << strerror(errno); +      return false; +    } + +    // Not truly clean, mmap-ing boot.art again would be more pristine, but close enough +    const char* clean_pagemap_file_name = "/proc/self/pagemap"; +    auto tmp_clean_pagemap_file = std::unique_ptr<File>( +        OS::OpenFileForReading(clean_pagemap_file_name)); +    if (tmp_clean_pagemap_file == nullptr) { +      os << "Failed to open " << clean_pagemap_file_name << " for reading: " << strerror(errno); +      return false; +    } + +    auto tmp_kpageflags_file = std::unique_ptr<File>(OS::OpenFileForReading("/proc/kpageflags")); +    if (tmp_kpageflags_file == nullptr) { +      os << "Failed to open /proc/kpageflags for reading: " << strerror(errno); +      return false; +    } + +    auto tmp_kpagecount_file = std::unique_ptr<File>(OS::OpenFileForReading("/proc/kpagecount")); +    if (tmp_kpagecount_file == nullptr) { +      os << "Failed to open /proc/kpagecount for reading:" << strerror(errno); +      return false; +    } + +    // Commit the mappings, etc., to the object state. +    proc_maps_ = std::move(tmp_proc_maps); +    remote_contents_ = std::move(tmp_remote_contents); +    zygote_contents_ = std::move(tmp_zygote_contents); +    pagemap_file_ = std::move(*tmp_pagemap_file.release()); +    clean_pagemap_file_ = std::move(*tmp_clean_pagemap_file.release()); +    kpageflags_file_ = std::move(*tmp_kpageflags_file.release()); +    kpagecount_file_ = std::move(*tmp_kpagecount_file.release()); + +    return true; +  } + +  bool Dump() REQUIRES_SHARED(Locks::mutator_lock_) { +    std::ostream& os = *os_; +    os << "IMAGE LOCATION: " << image_location_ << "\n\n"; + +    os << "MAGIC: " << image_header_.GetMagic() << "\n\n"; + +    os << "IMAGE BEGIN: " << reinterpret_cast<void*>(image_header_.GetImageBegin()) << "\n\n"; + +    PrintPidLine("IMAGE", image_diff_pid_); +    os << "\n\n"; +    PrintPidLine("ZYGOTE", zygote_diff_pid_); +    bool ret = true; +    if (image_diff_pid_ >= 0 || zygote_diff_pid_ >= 0) { +      ret = DumpImageDiff(); +      os << "\n\n"; +    } + +    os << std::flush; + +    return ret; +  } + + private: +  bool DumpImageDiff() +      REQUIRES_SHARED(Locks::mutator_lock_) { +    return DumpImageDiffMap(); +  } + +  bool ComputeDirtyBytes(const uint8_t* image_begin, +                         size_t* dirty_pages /*out*/, +                         size_t* different_pages /*out*/, +                         size_t* different_bytes /*out*/, +                         size_t* different_int32s /*out*/, +                         size_t* private_pages /*out*/, +                         size_t* private_dirty_pages /*out*/, +                         std::set<size_t>* dirty_page_set_local) { +    std::ostream& os = *os_; + +    size_t virtual_page_idx = 0;   // Virtual page number (for an absolute memory address) +    size_t page_idx = 0;           // Page index relative to 0 +    size_t previous_page_idx = 0;  // Previous page index relative to 0 + + +    // Iterate through one page at a time. Boot map begin/end already implicitly aligned. +    for (uintptr_t begin = boot_map_.start; begin != boot_map_.end; begin += kPageSize) { +      ptrdiff_t offset = begin - boot_map_.start; + +      // We treat the image header as part of the memory map for now +      // If we wanted to change this, we could pass base=start+sizeof(ImageHeader) +      // But it might still be interesting to see if any of the ImageHeader data mutated +      const uint8_t* local_ptr = reinterpret_cast<const uint8_t*>(&image_header_) + offset; +      uint8_t* remote_ptr = &remote_contents_[offset]; + +      if (memcmp(local_ptr, remote_ptr, kPageSize) != 0) { +        different_pages++; + +        // Count the number of 32-bit integers that are different. +        for (size_t i = 0; i < kPageSize / sizeof(uint32_t); ++i) { +          uint32_t* remote_ptr_int32 = reinterpret_cast<uint32_t*>(remote_ptr); +          const uint32_t* local_ptr_int32 = reinterpret_cast<const uint32_t*>(local_ptr); + +          if (remote_ptr_int32[i] != local_ptr_int32[i]) { +            different_int32s++; +          } +        } +      } +    } + +    // Iterate through one byte at a time. +    ptrdiff_t page_off_begin = image_header_.GetImageBegin() - image_begin; +    for (uintptr_t begin = boot_map_.start; begin != boot_map_.end; ++begin) { +      previous_page_idx = page_idx; +      ptrdiff_t offset = begin - boot_map_.start; + +      // We treat the image header as part of the memory map for now +      // If we wanted to change this, we could pass base=start+sizeof(ImageHeader) +      // But it might still be interesting to see if any of the ImageHeader data mutated +      const uint8_t* local_ptr = reinterpret_cast<const uint8_t*>(&image_header_) + offset; +      uint8_t* remote_ptr = &remote_contents_[offset]; + +      virtual_page_idx = reinterpret_cast<uintptr_t>(local_ptr) / kPageSize; + +      // Calculate the page index, relative to the 0th page where the image begins +      page_idx = (offset + page_off_begin) / kPageSize; +      if (*local_ptr != *remote_ptr) { +        // Track number of bytes that are different +        different_bytes++; +      } + +      // Independently count the # of dirty pages on the remote side +      size_t remote_virtual_page_idx = begin / kPageSize; +      if (previous_page_idx != page_idx) { +        uint64_t page_count = 0xC0FFEE; +        // TODO: virtual_page_idx needs to be from the same process +        std::string error_msg; +        int dirtiness = (IsPageDirty(&pagemap_file_,           // Image-diff-pid procmap +                                     &clean_pagemap_file_,     // Self procmap +                                     &kpageflags_file_, +                                     &kpagecount_file_, +                                     remote_virtual_page_idx,  // potentially "dirty" page +                                     virtual_page_idx,         // true "clean" page +                                     &page_count, +                                     &error_msg)); +        if (dirtiness < 0) { +          os << error_msg; +          return false; +        } else if (dirtiness > 0) { +          (*dirty_pages)++; +          dirty_page_set_local->insert(dirty_page_set_local->end(), virtual_page_idx); +        } + +        bool is_dirty = dirtiness > 0; +        bool is_private = page_count == 1; + +        if (page_count == 1) { +          (*private_pages)++; +        } + +        if (is_dirty && is_private) { +          (*private_dirty_pages)++; +        } +      } +    } +    return true; +  } + +  bool ObjectIsOnDirtyPage(const uint8_t* item, +                           size_t size, +                           const std::set<size_t>& dirty_page_set_local) { +    size_t page_off = 0; +    size_t current_page_idx; +    uintptr_t object_address = reinterpret_cast<uintptr_t>(item); +    // Iterate every page this object belongs to +    do { +      current_page_idx = object_address / kPageSize + page_off; + +      if (dirty_page_set_local.find(current_page_idx) != dirty_page_set_local.end()) { +        // This object is on a dirty page +        return true; +      } + +      page_off++; +    } while ((current_page_idx * kPageSize) < RoundUp(object_address + size, kObjectAlignment)); + +    return false;    }    static std::string PrettyFieldValue(ArtField* field, mirror::Object* obj) @@ -213,24 +398,24 @@ class ImgDiagDumper {    // Aggregate and detail class data from an image diff.    struct ClassData { -    int dirty_object_count = 0; +    size_t dirty_object_count = 0;      // Track only the byte-per-byte dirtiness (in bytes) -    int dirty_object_byte_count = 0; +    size_t dirty_object_byte_count = 0;      // Track the object-by-object dirtiness (in bytes) -    int dirty_object_size_in_bytes = 0; +    size_t dirty_object_size_in_bytes = 0; -    int clean_object_count = 0; +    size_t clean_object_count = 0;      std::string descriptor; -    int false_dirty_byte_count = 0; -    int false_dirty_object_count = 0; -    std::vector<mirror::Object*> false_dirty_objects; +    size_t false_dirty_byte_count = 0; +    size_t false_dirty_object_count = 0; +    std::vector<const uint8_t*> false_dirty_objects;      // Remote pointers to dirty objects -    std::vector<mirror::Object*> dirty_objects; +    std::vector<const uint8_t*> dirty_objects;    };    void DiffObjectContents(mirror::Object* obj, @@ -297,238 +482,189 @@ class ImgDiagDumper {      os << "\n";    } -  // Look at /proc/$pid/mem and only diff the things from there -  bool DumpImageDiffMap(const backtrace_map_t& boot_map) -    REQUIRES_SHARED(Locks::mutator_lock_) { -    std::ostream& os = *os_; -    const PointerSize pointer_size = InstructionSetPointerSize( -        Runtime::Current()->GetInstructionSet()); +  struct ObjectRegionData { +    // Count of objects that are different. +    size_t different_objects = 0; -    std::string file_name = -        StringPrintf("/proc/%ld/mem", static_cast<long>(image_diff_pid_));  // NOLINT [runtime/int] +    // Local objects that are dirty (differ in at least one byte). +    size_t dirty_object_bytes = 0; +    std::vector<const uint8_t*>* dirty_objects; -    size_t boot_map_size = boot_map.end - boot_map.start; +    // Local objects that are clean, but located on dirty pages. +    size_t false_dirty_object_bytes = 0; +    std::vector<const uint8_t*> false_dirty_objects; -    // Open /proc/$pid/mem as a file -    auto map_file = std::unique_ptr<File>(OS::OpenFileForReading(file_name.c_str())); -    if (map_file == nullptr) { -      os << "Failed to open " << file_name << " for reading"; -      return false; +    // Image dirty objects +    // If zygote_pid_only_ == true, these are shared dirty objects in the zygote. +    // If zygote_pid_only_ == false, these are private dirty objects in the application. +    std::set<const uint8_t*> image_dirty_objects; + +    // Zygote dirty objects (probably private dirty). +    // We only add objects here if they differed in both the image and the zygote, so +    // they are probably private dirty. +    std::set<const uint8_t*> zygote_dirty_objects; + +    std::map<off_t /* field offset */, size_t /* count */>* field_dirty_count; +  }; + +  void ComputeObjectDirty(const uint8_t* current, +                          const uint8_t* current_remote, +                          const uint8_t* current_zygote, +                          ClassData* obj_class_data, +                          size_t obj_size, +                          const std::set<size_t>& dirty_page_set_local, +                          ObjectRegionData* region_data /*out*/) { +    bool different_image_object = memcmp(current, current_remote, obj_size) != 0; +    if (different_image_object) { +      bool different_zygote_object = false; +      if (!zygote_contents_.empty()) { +        different_zygote_object = memcmp(current, current_zygote, obj_size) != 0; +      } +      if (different_zygote_object) { +        // Different from zygote. +        region_data->zygote_dirty_objects.insert(current); +      } else { +        // Just different from image. +        region_data->image_dirty_objects.insert(current); +      } + +      ++region_data->different_objects; +      region_data->dirty_object_bytes += obj_size; + +      ++obj_class_data->dirty_object_count; + +      // Go byte-by-byte and figure out what exactly got dirtied +      size_t dirty_byte_count_per_object = 0; +      for (size_t i = 0; i < obj_size; ++i) { +        if (current[i] != current_remote[i]) { +          dirty_byte_count_per_object++; +        } +      } +      obj_class_data->dirty_object_byte_count += dirty_byte_count_per_object; +      obj_class_data->dirty_object_size_in_bytes += obj_size; +      obj_class_data->dirty_objects.push_back(current_remote); +    } else { +      ++obj_class_data->clean_object_count;      } -    // Memory-map /proc/$pid/mem subset from the boot map -    CHECK(boot_map.end >= boot_map.start); +    if (different_image_object) { +      if (region_data->dirty_objects != nullptr) { +        // print the fields that are dirty +        for (size_t i = 0; i < obj_size; ++i) { +          if (current[i] != current_remote[i]) { +            size_t dirty_count = 0; +            if (region_data->field_dirty_count->find(i) != region_data->field_dirty_count->end()) { +              dirty_count = (*region_data->field_dirty_count)[i]; +            } +            (*region_data->field_dirty_count)[i] = dirty_count; +          } +        } + +        region_data->dirty_objects->push_back(current); +      } +      /* +       * TODO: Resurrect this stuff in the client when we add ArtMethod iterator. +      } else { +        std::string descriptor = GetClassDescriptor(klass); +        if (strcmp(descriptor.c_str(), "Ljava/lang/reflect/ArtMethod;") == 0) { +          // this is an ArtMethod +          ArtMethod* art_method = reinterpret_cast<ArtMethod*>(remote_obj); + +          // print the fields that are dirty +          for (size_t i = 0; i < obj_size; ++i) { +            if (current[i] != current_remote[i]) { +              art_method_field_dirty_count[i]++; +            } +          } +          art_method_dirty_objects.push_back(art_method); +        } +      } +      */ +    } else if (ObjectIsOnDirtyPage(current, obj_size, dirty_page_set_local)) { +      // This object was either never mutated or got mutated back to the same value. +      // TODO: Do I want to distinguish a "different" vs a "dirty" page here? +      region_data->false_dirty_objects.push_back(current); +      obj_class_data->false_dirty_objects.push_back(current); +      region_data->false_dirty_object_bytes += obj_size; +      obj_class_data->false_dirty_byte_count += obj_size; +      obj_class_data->false_dirty_object_count += 1; +    } +  } + +  // Look at /proc/$pid/mem and only diff the things from there +  bool DumpImageDiffMap() +    REQUIRES_SHARED(Locks::mutator_lock_) { +    std::ostream& os = *os_;      std::string error_msg;      // Walk the bytes and diff against our boot image -    const ImageHeader& boot_image_header = image_header_; -      os << "\nObserving boot image header at address " -       << reinterpret_cast<const void*>(&boot_image_header) +       << reinterpret_cast<const void*>(&image_header_)         << "\n\n"; -    const uint8_t* image_begin_unaligned = boot_image_header.GetImageBegin(); +    const uint8_t* image_begin_unaligned = image_header_.GetImageBegin();      const uint8_t* image_mirror_end_unaligned = image_begin_unaligned + -        boot_image_header.GetImageSection(ImageHeader::kSectionObjects).Size(); -    const uint8_t* image_end_unaligned = image_begin_unaligned + boot_image_header.GetImageSize(); +        image_header_.GetImageSection(ImageHeader::kSectionObjects).Size(); +    const uint8_t* image_end_unaligned = image_begin_unaligned + image_header_.GetImageSize();      // Adjust range to nearest page      const uint8_t* image_begin = AlignDown(image_begin_unaligned, kPageSize);      const uint8_t* image_end = AlignUp(image_end_unaligned, kPageSize); -    ptrdiff_t page_off_begin = boot_image_header.GetImageBegin() - image_begin; - -    if (reinterpret_cast<uintptr_t>(image_begin) > boot_map.start || -        reinterpret_cast<uintptr_t>(image_end) < boot_map.end) { +    if (reinterpret_cast<uintptr_t>(image_begin) > boot_map_.start || +        reinterpret_cast<uintptr_t>(image_end) < boot_map_.end) {        // Sanity check that we aren't trying to read a completely different boot image        os << "Remote boot map is out of range of local boot map: " <<          "local begin " << reinterpret_cast<const void*>(image_begin) <<          ", local end " << reinterpret_cast<const void*>(image_end) << -        ", remote begin " << reinterpret_cast<const void*>(boot_map.start) << -        ", remote end " << reinterpret_cast<const void*>(boot_map.end); +        ", remote begin " << reinterpret_cast<const void*>(boot_map_.start) << +        ", remote end " << reinterpret_cast<const void*>(boot_map_.end);        return false;        // If we wanted even more validation we could map the ImageHeader from the file      } -    std::vector<uint8_t> remote_contents(boot_map_size); -    if (!map_file->PreadFully(&remote_contents[0], boot_map_size, boot_map.start)) { -      os << "Could not fully read file " << file_name; -      return false; -    } - -    std::vector<uint8_t> zygote_contents; -    std::unique_ptr<File> zygote_map_file; -    if (zygote_diff_pid_ != -1) { -      std::string zygote_file_name = -          StringPrintf("/proc/%ld/mem", static_cast<long>(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<long>(image_diff_pid_));  // NOLINT [runtime/int] -    auto page_map_file = std::unique_ptr<File>(OS::OpenFileForReading(page_map_file_name.c_str())); -    if (page_map_file == nullptr) { -      os << "Failed to open " << page_map_file_name << " for reading: " << strerror(errno); -      return false; -    } - -    // Not truly clean, mmap-ing boot.art again would be more pristine, but close enough -    const char* clean_page_map_file_name = "/proc/self/pagemap"; -    auto clean_page_map_file = std::unique_ptr<File>( -        OS::OpenFileForReading(clean_page_map_file_name)); -    if (clean_page_map_file == nullptr) { -      os << "Failed to open " << clean_page_map_file_name << " for reading: " << strerror(errno); -      return false; -    } - -    auto kpage_flags_file = std::unique_ptr<File>(OS::OpenFileForReading("/proc/kpageflags")); -    if (kpage_flags_file == nullptr) { -      os << "Failed to open /proc/kpageflags for reading: " << strerror(errno); -      return false; -    } - -    auto kpage_count_file = std::unique_ptr<File>(OS::OpenFileForReading("/proc/kpagecount")); -    if (kpage_count_file == nullptr) { -      os << "Failed to open /proc/kpagecount for reading:" << strerror(errno); -      return false; -    } - -    // Set of the remote virtual page indices that are dirty -    std::set<size_t> dirty_page_set_remote; -    // Set of the local virtual page indices that are dirty -    std::set<size_t> dirty_page_set_local; - -    size_t different_int32s = 0; -    size_t different_bytes = 0; -    size_t different_pages = 0; -    size_t virtual_page_idx = 0;   // Virtual page number (for an absolute memory address) -    size_t page_idx = 0;           // Page index relative to 0 -    size_t previous_page_idx = 0;  // Previous page index relative to 0      size_t dirty_pages = 0; +    size_t different_pages = 0; +    size_t different_bytes = 0; +    size_t different_int32s = 0;      size_t private_pages = 0;      size_t private_dirty_pages = 0; -    // Iterate through one page at a time. Boot map begin/end already implicitly aligned. -    for (uintptr_t begin = boot_map.start; begin != boot_map.end; begin += kPageSize) { -      ptrdiff_t offset = begin - boot_map.start; - -      // We treat the image header as part of the memory map for now -      // If we wanted to change this, we could pass base=start+sizeof(ImageHeader) -      // But it might still be interesting to see if any of the ImageHeader data mutated -      const uint8_t* local_ptr = reinterpret_cast<const uint8_t*>(&boot_image_header) + offset; -      uint8_t* remote_ptr = &remote_contents[offset]; - -      if (memcmp(local_ptr, remote_ptr, kPageSize) != 0) { -        different_pages++; - -        // Count the number of 32-bit integers that are different. -        for (size_t i = 0; i < kPageSize / sizeof(uint32_t); ++i) { -          uint32_t* remote_ptr_int32 = reinterpret_cast<uint32_t*>(remote_ptr); -          const uint32_t* local_ptr_int32 = reinterpret_cast<const uint32_t*>(local_ptr); - -          if (remote_ptr_int32[i] != local_ptr_int32[i]) { -            different_int32s++; -          } -        } -      } -    } - -    // Iterate through one byte at a time. -    for (uintptr_t begin = boot_map.start; begin != boot_map.end; ++begin) { -      previous_page_idx = page_idx; -      ptrdiff_t offset = begin - boot_map.start; - -      // We treat the image header as part of the memory map for now -      // If we wanted to change this, we could pass base=start+sizeof(ImageHeader) -      // But it might still be interesting to see if any of the ImageHeader data mutated -      const uint8_t* local_ptr = reinterpret_cast<const uint8_t*>(&boot_image_header) + offset; -      uint8_t* remote_ptr = &remote_contents[offset]; - -      virtual_page_idx = reinterpret_cast<uintptr_t>(local_ptr) / kPageSize; - -      // Calculate the page index, relative to the 0th page where the image begins -      page_idx = (offset + page_off_begin) / kPageSize; -      if (*local_ptr != *remote_ptr) { -        // Track number of bytes that are different -        different_bytes++; -      } - -      // Independently count the # of dirty pages on the remote side -      size_t remote_virtual_page_idx = begin / kPageSize; -      if (previous_page_idx != page_idx) { -        uint64_t page_count = 0xC0FFEE; -        // TODO: virtual_page_idx needs to be from the same process -        int dirtiness = (IsPageDirty(page_map_file.get(),        // Image-diff-pid procmap -                                     clean_page_map_file.get(),  // Self procmap -                                     kpage_flags_file.get(), -                                     kpage_count_file.get(), -                                     remote_virtual_page_idx,    // potentially "dirty" page -                                     virtual_page_idx,           // true "clean" page -                                     &page_count, -                                     &error_msg)); -        if (dirtiness < 0) { -          os << error_msg; -          return false; -        } else if (dirtiness > 0) { -          dirty_pages++; -          dirty_page_set_remote.insert(dirty_page_set_remote.end(), remote_virtual_page_idx); -          dirty_page_set_local.insert(dirty_page_set_local.end(), virtual_page_idx); -        } - -        bool is_dirty = dirtiness > 0; -        bool is_private = page_count == 1; - -        if (page_count == 1) { -          private_pages++; -        } +    // Set of the local virtual page indices that are dirty +    std::set<size_t> dirty_page_set_local; -        if (is_dirty && is_private) { -          private_dirty_pages++; -        } -      } +    if (!ComputeDirtyBytes(image_begin, +                           &dirty_pages, +                           &different_pages, +                           &different_bytes, +                           &different_int32s, +                           &private_pages, +                           &private_dirty_pages, +                           &dirty_page_set_local)) { +      return false;      }      std::map<mirror::Class*, ClassData> class_data;      // Walk each object in the remote image space and compare it against ours -    size_t different_objects = 0; -      std::map<off_t /* field offset */, int /* count */> art_method_field_dirty_count;      std::vector<ArtMethod*> art_method_dirty_objects; -    std::map<off_t /* field offset */, int /* count */> class_field_dirty_count; -    std::vector<mirror::Class*> class_dirty_objects; +    std::map<off_t /* field offset */, size_t /* count */> class_field_dirty_count; +    std::vector<const uint8_t*> class_dirty_objects; -    // List of local objects that are clean, but located on dirty pages. -    std::vector<mirror::Object*> false_dirty_objects; -    size_t false_dirty_object_bytes = 0;      // Look up remote classes by their descriptor      std::map<std::string, mirror::Class*> remote_class_map;      // Look up local classes by their descriptor      std::map<std::string, mirror::Class*> local_class_map; -    // Image dirty objects -    // If zygote_pid_only_ == true, these are shared dirty objects in the zygote. -    // If zygote_pid_only_ == false, these are private dirty objects in the application. -    std::set<mirror::Object*> image_dirty_objects; - -    // Zygote dirty objects (probably private dirty). -    // We only add objects here if they differed in both the image and the zygote, so -    // they are probably private dirty. -    std::set<mirror::Object*> zygote_dirty_objects; - -    size_t dirty_object_bytes = 0;      const uint8_t* begin_image_ptr = image_begin_unaligned;      const uint8_t* end_image_ptr = image_mirror_end_unaligned; +    ObjectRegionData region_data; +      const uint8_t* current = begin_image_ptr + RoundUp(sizeof(ImageHeader), kObjectAlignment);      while (reinterpret_cast<uintptr_t>(current) < reinterpret_cast<uintptr_t>(end_image_ptr)) {        CHECK_ALIGNED(current, kObjectAlignment); @@ -540,125 +676,60 @@ class ImgDiagDumper {          obj->AssertReadBarrierState();        } -      // Iterate every page this object belongs to -      bool on_dirty_page = false; -      size_t page_off = 0; -      size_t current_page_idx; -      uintptr_t object_address; -      do { -        object_address = reinterpret_cast<uintptr_t>(current); -        current_page_idx = object_address / kPageSize + page_off; - -        if (dirty_page_set_local.find(current_page_idx) != dirty_page_set_local.end()) { -          // This object is on a dirty page -          on_dirty_page = true; -        } - -        page_off++; -      } while ((current_page_idx * kPageSize) < -               RoundUp(object_address + obj->SizeOf(), kObjectAlignment)); -        mirror::Class* klass = obj->GetClass(); +      size_t obj_size = obj->SizeOf(); +      ClassData& obj_class_data = class_data[klass];        // 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<mirror::Object*>( -          const_cast<uint8_t*>(current_remote)); - -      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 image. -          image_dirty_objects.insert(obj); -        } +      const uint8_t* current_remote = &remote_contents_[offset]; +      const uint8_t* current_zygote = +          zygote_contents_.empty() ? nullptr : &zygote_contents_[offset]; -        different_objects++; -        dirty_object_bytes += obj->SizeOf(); - -        ++class_data[klass].dirty_object_count; - -        // Go byte-by-byte and figure out what exactly got dirtied -        size_t dirty_byte_count_per_object = 0; -        for (size_t i = 0; i < obj->SizeOf(); ++i) { -          if (current[i] != current_remote[i]) { -            dirty_byte_count_per_object++; -          } -        } -        class_data[klass].dirty_object_byte_count += dirty_byte_count_per_object; -        class_data[klass].dirty_object_size_in_bytes += obj->SizeOf(); -        class_data[klass].dirty_objects.push_back(remote_obj); +      if (klass->IsClassClass()) { +        region_data.field_dirty_count = &class_field_dirty_count; +        region_data.dirty_objects = &class_dirty_objects;        } else { -        ++class_data[klass].clean_object_count; +        region_data.field_dirty_count = nullptr; +        region_data.dirty_objects = nullptr;        } -      std::string descriptor = GetClassDescriptor(klass); -      if (different_image_object) { -        if (klass->IsClassClass()) { -          // this is a "Class" -          mirror::Class* obj_as_class  = reinterpret_cast<mirror::Class*>(remote_obj); - -          // print the fields that are dirty -          for (size_t i = 0; i < obj->SizeOf(); ++i) { -            if (current[i] != current_remote[i]) { -              class_field_dirty_count[i]++; -            } -          } - -          class_dirty_objects.push_back(obj_as_class); -        } else if (strcmp(descriptor.c_str(), "Ljava/lang/reflect/ArtMethod;") == 0) { -          // this is an ArtMethod -          ArtMethod* art_method = reinterpret_cast<ArtMethod*>(remote_obj); - -          // print the fields that are dirty -          for (size_t i = 0; i < obj->SizeOf(); ++i) { -            if (current[i] != current_remote[i]) { -              art_method_field_dirty_count[i]++; -            } -          } -          art_method_dirty_objects.push_back(art_method); -        } -      } else if (on_dirty_page) { -        // This object was either never mutated or got mutated back to the same value. -        // TODO: Do I want to distinguish a "different" vs a "dirty" page here? -        false_dirty_objects.push_back(obj); -        class_data[klass].false_dirty_objects.push_back(obj); -        false_dirty_object_bytes += obj->SizeOf(); -        class_data[obj->GetClass()].false_dirty_byte_count += obj->SizeOf(); -        class_data[obj->GetClass()].false_dirty_object_count += 1; -      } +      ComputeObjectDirty(current, +                         current_remote, +                         current_zygote, +                         &obj_class_data, +                         obj_size, +                         dirty_page_set_local, +                         ®ion_data); +      // Object specific stuff. +      std::string descriptor = GetClassDescriptor(klass);        if (strcmp(descriptor.c_str(), "Ljava/lang/Class;") == 0) {          local_class_map[descriptor] = reinterpret_cast<mirror::Class*>(obj); +        mirror::Object* remote_obj = reinterpret_cast<mirror::Object*>( +            const_cast<uint8_t*>(current_remote));          remote_class_map[descriptor] = reinterpret_cast<mirror::Class*>(remote_obj);        }        // Unconditionally store the class descriptor in case we need it later -      class_data[klass].descriptor = descriptor; -      current += RoundUp(obj->SizeOf(), kObjectAlignment); +      obj_class_data.descriptor = descriptor; + +      current += RoundUp(obj_size, kObjectAlignment);      }      // Looking at only dirty pages, figure out how many of those bytes belong to dirty objects. -    float true_dirtied_percent = dirty_object_bytes * 1.0f / (dirty_pages * kPageSize); +    float true_dirtied_percent = region_data.dirty_object_bytes * 1.0f / (dirty_pages * kPageSize);      size_t false_dirty_pages = dirty_pages - different_pages; -    os << "Mapping at [" << reinterpret_cast<void*>(boot_map.start) << ", " -       << reinterpret_cast<void*>(boot_map.end) << ") had: \n  " +    os << "Mapping at [" << reinterpret_cast<void*>(boot_map_.start) << ", " +       << reinterpret_cast<void*>(boot_map_.end) << ") had: \n  "         << different_bytes << " differing bytes, \n  "         << different_int32s << " differing int32s, \n  " -       << different_objects << " different objects, \n  " -       << dirty_object_bytes << " different object [bytes], \n  " -       << false_dirty_objects.size() << " false dirty objects,\n  " -       << false_dirty_object_bytes << " false dirty object [bytes], \n  " +       << region_data.different_objects << " different objects, \n  " +       << region_data.dirty_object_bytes << " different object [bytes], \n  " +       << region_data.false_dirty_objects.size() << " false dirty objects,\n  " +       << region_data.false_dirty_object_bytes << " false dirty object [bytes], \n  "         << true_dirtied_percent << " different objects-vs-total in a dirty page;\n  "         << different_pages << " different pages; \n  "         << dirty_pages << " pages are dirty; \n  " @@ -673,17 +744,17 @@ class ImgDiagDumper {      auto clean_object_class_values = SortByValueDesc<mirror::Class*, int, ClassData>(          class_data, [](const ClassData& d) { return d.clean_object_count; }); -    if (!zygote_dirty_objects.empty()) { +    if (!region_data.zygote_dirty_objects.empty()) {        // We only reach this point if both pids were specified.  Furthermore,        // objects are only displayed here if they differed in both the image        // and the zygote, so they are probably private dirty.        CHECK(image_diff_pid_ > 0 && zygote_diff_pid_ > 0);        os << "\n" << "  Zygote dirty objects (probably shared dirty): " -         << zygote_dirty_objects.size() << "\n"; -      for (mirror::Object* obj : zygote_dirty_objects) { -        const uint8_t* obj_bytes = reinterpret_cast<const uint8_t*>(obj); +         << region_data.zygote_dirty_objects.size() << "\n"; +      for (const uint8_t* obj_bytes : region_data.zygote_dirty_objects) { +        auto obj = const_cast<mirror::Object*>(reinterpret_cast<const mirror::Object*>(obj_bytes));          ptrdiff_t offset = obj_bytes - begin_image_ptr; -        uint8_t* remote_bytes = &zygote_contents[offset]; +        uint8_t* remote_bytes = &zygote_contents_[offset];          DiffObjectContents(obj, remote_bytes, os);        }      } @@ -699,11 +770,11 @@ class ImgDiagDumper {          os << "  Application dirty objects (unknown whether private or shared dirty): ";        }      } -    os << image_dirty_objects.size() << "\n"; -    for (mirror::Object* obj : image_dirty_objects) { -      const uint8_t* obj_bytes = reinterpret_cast<const uint8_t*>(obj); +    os << region_data.image_dirty_objects.size() << "\n"; +    for (const uint8_t* obj_bytes : region_data.image_dirty_objects) { +      auto obj = const_cast<mirror::Object*>(reinterpret_cast<const mirror::Object*>(obj_bytes));        ptrdiff_t offset = obj_bytes - begin_image_ptr; -      uint8_t* remote_bytes = &remote_contents[offset]; +      uint8_t* remote_bytes = &remote_contents_[offset];        DiffObjectContents(obj, remote_bytes, os);      } @@ -747,27 +818,26 @@ class ImgDiagDumper {          os << "      field contents:\n";          const auto& dirty_objects_list = class_data[klass].dirty_objects; -        for (mirror::Object* obj : dirty_objects_list) { +        for (const uint8_t* uobj : dirty_objects_list) { +          auto obj = const_cast<mirror::Object*>(reinterpret_cast<const mirror::Object*>(uobj));            // remote method            auto art_method = reinterpret_cast<ArtMethod*>(obj);            // remote class            mirror::Class* remote_declaring_class = -            FixUpRemotePointer(art_method->GetDeclaringClass(), remote_contents, boot_map); +            FixUpRemotePointer(art_method->GetDeclaringClass(), remote_contents_, boot_map_);            // local class            mirror::Class* declaring_class = -            RemoteContentsPointerToLocal(remote_declaring_class, -                                         remote_contents, -                                         boot_image_header); +            RemoteContentsPointerToLocal(remote_declaring_class, remote_contents_, image_header_);            os << "        " << reinterpret_cast<void*>(obj) << " ";            os << "  entryPointFromJni: "               << reinterpret_cast<const void*>( -                    art_method->GetDataPtrSize(pointer_size)) << ", "; +                    art_method->GetDataPtrSize(pointer_size_)) << ", ";            os << "  entryPointFromQuickCompiledCode: "               << reinterpret_cast<const void*>( -                    art_method->GetEntryPointFromQuickCompiledCodePtrSize(pointer_size)) +                    art_method->GetEntryPointFromQuickCompiledCodePtrSize(pointer_size_))               << ", ";            os << "  isNative? " << (art_method->IsNative() ? "yes" : "no") << ", ";            os << "  class_status (local): " << declaring_class->GetStatus(); @@ -780,13 +850,13 @@ class ImgDiagDumper {          for (size_t i = 0; i < class_dirty_objects.size() && i < kMaxAddressPrint; ++i) {            auto class_ptr = class_dirty_objects[i]; -          os << reinterpret_cast<void*>(class_ptr) << ", "; +          os << reinterpret_cast<const void*>(class_ptr) << ", ";          }          os << "\n";          os << "       dirty byte +offset:count list = ";          auto class_field_dirty_count_sorted = -            SortByValueDesc<off_t, int, int>(class_field_dirty_count); +            SortByValueDesc<off_t, int, size_t>(class_field_dirty_count);          for (auto pair : class_field_dirty_count_sorted) {            off_t offset = pair.second;            int count = pair.first; @@ -796,17 +866,19 @@ class ImgDiagDumper {          os << "\n";          os << "      field contents:\n"; +        // TODO: templatize this to avoid the awful casts down to uint8_t* and back.          const auto& dirty_objects_list = class_data[klass].dirty_objects; -        for (mirror::Object* obj : dirty_objects_list) { +        for (const uint8_t* uobj : dirty_objects_list) { +          auto obj = const_cast<mirror::Object*>(reinterpret_cast<const mirror::Object*>(uobj));            // remote class object            auto remote_klass = reinterpret_cast<mirror::Class*>(obj);            // local class object            auto local_klass = RemoteContentsPointerToLocal(remote_klass, -                                                          remote_contents, -                                                          boot_image_header); +                                                          remote_contents_, +                                                          image_header_); -          os << "        " << reinterpret_cast<void*>(obj) << " "; +          os << "        " << reinterpret_cast<const void*>(obj) << " ";            os << "  class_status (remote): " << remote_klass->GetStatus() << ", ";            os << "  class_status (local): " << local_klass->GetStatus();            os << "\n"; @@ -832,23 +904,25 @@ class ImgDiagDumper {           << ")\n";        if (strcmp(descriptor.c_str(), "Ljava/lang/reflect/ArtMethod;") == 0) { +        // TODO: templatize this to avoid the awful casts down to uint8_t* and back.          auto& art_method_false_dirty_objects = class_data[klass].false_dirty_objects;          os << "      field contents:\n"; -        for (mirror::Object* obj : art_method_false_dirty_objects) { +        for (const uint8_t* uobj : art_method_false_dirty_objects) { +          auto obj = const_cast<mirror::Object*>(reinterpret_cast<const mirror::Object*>(uobj));            // local method            auto art_method = reinterpret_cast<ArtMethod*>(obj);            // local class            mirror::Class* declaring_class = art_method->GetDeclaringClass(); -          os << "        " << reinterpret_cast<void*>(obj) << " "; +          os << "        " << reinterpret_cast<const void*>(obj) << " ";            os << "  entryPointFromJni: "               << reinterpret_cast<const void*>( -                    art_method->GetDataPtrSize(pointer_size)) << ", "; +                    art_method->GetDataPtrSize(pointer_size_)) << ", ";            os << "  entryPointFromQuickCompiledCode: "               << reinterpret_cast<const void*>( -                    art_method->GetEntryPointFromQuickCompiledCodePtrSize(pointer_size)) +                    art_method->GetEntryPointFromQuickCompiledCodePtrSize(pointer_size_))               << ", ";            os << "  isNative? " << (art_method->IsNative() ? "yes" : "no") << ", ";            os << "  class_status (local): " << declaring_class->GetStatus(); @@ -963,18 +1037,18 @@ class ImgDiagDumper {    }    static int IsPageDirty(File* page_map_file, -                         File* clean_page_map_file, -                         File* kpage_flags_file, -                         File* kpage_count_file, +                         File* clean_pagemap_file, +                         File* kpageflags_file, +                         File* kpagecount_file,                           size_t virtual_page_idx,                           size_t clean_virtual_page_idx,                           // Out parameters:                           uint64_t* page_count, std::string* error_msg) {      CHECK(page_map_file != nullptr); -    CHECK(clean_page_map_file != nullptr); -    CHECK_NE(page_map_file, clean_page_map_file); -    CHECK(kpage_flags_file != nullptr); -    CHECK(kpage_count_file != nullptr); +    CHECK(clean_pagemap_file != nullptr); +    CHECK_NE(page_map_file, clean_pagemap_file); +    CHECK(kpageflags_file != nullptr); +    CHECK(kpagecount_file != nullptr);      CHECK(page_count != nullptr);      CHECK(error_msg != nullptr); @@ -992,27 +1066,27 @@ class ImgDiagDumper {      }      uint64_t page_frame_number_clean = 0; -    if (!GetPageFrameNumber(clean_page_map_file, clean_virtual_page_idx, &page_frame_number_clean, +    if (!GetPageFrameNumber(clean_pagemap_file, clean_virtual_page_idx, &page_frame_number_clean,                              error_msg)) {        return -1;      }      // Read 64-bit entry from /proc/kpageflags to get the dirty bit for a page      uint64_t kpage_flags_entry = 0; -    if (!kpage_flags_file->PreadFully(&kpage_flags_entry, +    if (!kpageflags_file->PreadFully(&kpage_flags_entry,                                       kPageFlagsEntrySize,                                       page_frame_number * kPageFlagsEntrySize)) {        *error_msg = StringPrintf("Failed to read the page flags from %s", -                                kpage_flags_file->GetPath().c_str()); +                                kpageflags_file->GetPath().c_str());        return -1;      }      // Read 64-bit entyry from /proc/kpagecount to get mapping counts for a page -    if (!kpage_count_file->PreadFully(page_count /*out*/, +    if (!kpagecount_file->PreadFully(page_count /*out*/,                                       kPageCountEntrySize,                                       page_frame_number * kPageCountEntrySize)) {        *error_msg = StringPrintf("Failed to read the page count from %s", -                                kpage_count_file->GetPath().c_str()); +                                kpagecount_file->GetPath().c_str());        return -1;      } @@ -1033,7 +1107,29 @@ class ImgDiagDumper {      return page_frame_number != page_frame_number_clean;    } - private: +  void PrintPidLine(const std::string& kind, pid_t pid) { +    if (pid < 0) { +      *os_ << kind << " DIFF PID: disabled\n\n"; +    } else { +      *os_ << kind << " DIFF PID (" << pid << "): "; +    } +  } + +  static bool EndsWith(const std::string& str, const std::string& suffix) { +    return str.size() >= suffix.size() && +           str.compare(str.size() - suffix.size(), suffix.size(), suffix) == 0; +  } + +  // Return suffix of the file path after the last /. (e.g. /foo/bar -> bar, bar -> bar) +  static std::string BaseName(const std::string& str) { +    size_t idx = str.rfind('/'); +    if (idx == std::string::npos) { +      return str; +    } + +    return str.substr(idx + 1); +  } +    // Return the image location, stripped of any directories, e.g. "boot.art" or "core.art"    std::string GetImageLocationBaseName() const {      return BaseName(std::string(image_location_)); @@ -1046,6 +1142,27 @@ class ImgDiagDumper {    pid_t zygote_diff_pid_;  // Dump image diff against zygote boot.art if pid is non-negative    bool zygote_pid_only_;  // The user only specified a pid for the zygote. +  // Pointer size constant for object fields, etc. +  PointerSize pointer_size_; +  // BacktraceMap used for finding the memory mapping of the image file. +  std::unique_ptr<BacktraceMap> proc_maps_; +  // Boot image mapping. +  backtrace_map_t boot_map_{};  // NOLINT +  // The size of the boot image mapping. +  size_t boot_map_size_; +  // The contents of /proc/<image_diff_pid_>/maps. +  std::vector<uint8_t> remote_contents_; +  // The contents of /proc/<zygote_diff_pid_>/maps. +  std::vector<uint8_t> zygote_contents_; +  // A File for reading /proc/<zygote_diff_pid_>/maps. +  File pagemap_file_; +  // A File for reading /proc/self/pagemap. +  File clean_pagemap_file_; +  // A File for reading /proc/kpageflags. +  File kpageflags_file_; +  // A File for reading /proc/kpagecount. +  File kpagecount_file_; +    DISALLOW_COPY_AND_ASSIGN(ImgDiagDumper);  }; @@ -1069,6 +1186,9 @@ static int DumpImage(Runtime* runtime,                                    image_space->GetImageLocation(),                                    image_diff_pid,                                    zygote_diff_pid); +    if (!img_diag_dumper.Init()) { +      return EXIT_FAILURE; +    }      if (!img_diag_dumper.Dump()) {        return EXIT_FAILURE;      } diff --git a/profman/profman.cc b/profman/profman.cc index d8b5dafffe..f763b8ea05 100644 --- a/profman/profman.cc +++ b/profman/profman.cc @@ -849,7 +849,7 @@ class ProfMan FINAL {        if (!profile->AddMethodIndex(static_cast<Hotness::Flag>(flags), ref)) {          return false;        } -      DCHECK(profile->GetMethodHotness(ref).HasAnyFlags()); +      DCHECK(profile->GetMethodHotness(ref).IsInProfile());      }      return true;    } diff --git a/runtime/Android.bp b/runtime/Android.bp index 20f95c0c74..46307ddde8 100644 --- a/runtime/Android.bp +++ b/runtime/Android.bp @@ -193,6 +193,7 @@ cc_defaults {          "plugin.cc",          "primitive.cc",          "quick_exception_handler.cc", +        "read_barrier.cc",          "reference_table.cc",          "reflection.cc",          "runtime.cc", @@ -528,6 +529,7 @@ art_cc_test {          "base/hash_set_test.cc",          "base/hex_dump_test.cc",          "base/histogram_test.cc", +        "base/logging_test.cc",          "base/mutex_test.cc",          "base/safe_copy_test.cc",          "base/scoped_flock_test.cc", diff --git a/runtime/art_method.cc b/runtime/art_method.cc index 32946ef0b4..ac433dd403 100644 --- a/runtime/art_method.cc +++ b/runtime/art_method.cc @@ -55,6 +55,8 @@ extern "C" void art_quick_invoke_stub(ArtMethod*, uint32_t*, uint32_t, Thread*,  extern "C" void art_quick_invoke_static_stub(ArtMethod*, uint32_t*, uint32_t, Thread*, JValue*,                                               const char*); +DEFINE_RUNTIME_DEBUG_FLAG(ArtMethod, kCheckDeclaringClassState); +  // Enforce that we he have the right index for runtime methods.  static_assert(ArtMethod::kRuntimeMethodDexMethodIndex == DexFile::kDexNoIndex,                "Wrong runtime-method dex method index"); diff --git a/runtime/art_method.h b/runtime/art_method.h index 3a8d279606..d537764cac 100644 --- a/runtime/art_method.h +++ b/runtime/art_method.h @@ -22,6 +22,7 @@  #include "base/bit_utils.h"  #include "base/casts.h"  #include "base/enums.h" +#include "base/logging.h"  #include "dex_file.h"  #include "gc_root.h"  #include "modifiers.h" @@ -56,7 +57,8 @@ class String;  class ArtMethod FINAL {   public: -  static constexpr bool kCheckDeclaringClassState = kIsDebugBuild; +  // Should the class state be checked on sensitive operations? +  DECLARE_RUNTIME_DEBUG_FLAG(kCheckDeclaringClassState);    // The runtime dex_method_index is kDexNoIndex. To lower dependencies, we use this    // constexpr, and ensure that the value is correct in art_method.cc. diff --git a/runtime/base/logging.cc b/runtime/base/logging.cc index adfd7d323c..2be9067e64 100644 --- a/runtime/base/logging.cc +++ b/runtime/base/logging.cc @@ -34,6 +34,55 @@  namespace art { +// We test here that the runtime-debug-checks are actually a no-op constexpr false in release +// builds, as we can't check that in gtests (which are always debug). + +#ifdef NDEBUG +namespace { +DECLARE_RUNTIME_DEBUG_FLAG(kTestForConstexpr); +static_assert(!kTestForConstexpr, "Issue with DECLARE_RUNTIME_DEBUG_FLAG in NDEBUG."); +} +#endif + +// Implementation of runtime debug flags. This should be compile-time optimized away in release +// builds. +namespace { +bool gSlowEnabled = false;  // Default for slow flags is "off." + +// Use a function with a static to ensure our vector storage doesn't have initialization order +// issues. +std::vector<bool*>& GetFlagPtrs() { +  static std::vector<bool*> g_flag_ptrs; +  return g_flag_ptrs; +} + +bool RegisterRuntimeDebugFlagImpl(bool* flag_ptr) { +  GetFlagPtrs().push_back(flag_ptr); +  return gSlowEnabled; +} + +void SetRuntimeDebugFlagsEnabledImpl(bool enabled) { +  gSlowEnabled = enabled; +  for (bool* flag_ptr : GetFlagPtrs()) { +    *flag_ptr = enabled; +  } +} + +}  // namespace + +bool RegisterRuntimeDebugFlag(bool* flag_ptr) { +  if (kIsDebugBuild) { +    return RegisterRuntimeDebugFlagImpl(flag_ptr); +  } +  return false; +} + +void SetRuntimeDebugFlagsEnabled(bool enabled) { +  if (kIsDebugBuild) { +    SetRuntimeDebugFlagsEnabledImpl(enabled); +  } +} +  LogVerbosity gLogVerbosity;  unsigned int gAborting = 0; diff --git a/runtime/base/logging.h b/runtime/base/logging.h index 7a9184e07e..d8954e59d0 100644 --- a/runtime/base/logging.h +++ b/runtime/base/logging.h @@ -62,6 +62,43 @@ struct LogVerbosity {  // Global log verbosity setting, initialized by InitLogging.  extern LogVerbosity gLogVerbosity; +// Runtime debug flags are flags that have a runtime component, that is, their value can be changed. +// This is meant to implement fast vs slow debug builds, in that certain debug flags can be turned +// on and off. To that effect, expose two macros to help implement and globally drive these flags: +// +// In the header, declare a (class) flag like this: +// +//   class C { +//     DECLARE_RUNTIME_DEBUG_FLAG(kFlag); +//   }; +// +// This will declare a flag kFlag that is a constexpr false in release builds, and a static field +// in debug builds. Usage is than uniform as C::kFlag. +// +// In the cc file, define the flag like this: +// +//   DEFINE_RUNTIME_DEBUG_FLAG(C, kFlag); +// +// This will define the static storage, as necessary, and register the flag with the runtime +// infrastructure to toggle the value. + +#ifdef NDEBUG +#define DECLARE_RUNTIME_DEBUG_FLAG(x) \ +  static constexpr bool x = false; +// Note: the static_assert in the following only works for public flags. Fix this when we cross +//       the line at some point. +#define DEFINE_RUNTIME_DEBUG_FLAG(C, x) \ +  static_assert(!C::x, "Unexpected enabled flag in release build"); +#else +#define DECLARE_RUNTIME_DEBUG_FLAG(x) \ +  static bool x; +#define DEFINE_RUNTIME_DEBUG_FLAG(C, x) \ +  bool C::x = RegisterRuntimeDebugFlag(&C::x); +#endif  // NDEBUG + +bool RegisterRuntimeDebugFlag(bool* runtime_debug_flag); +void SetRuntimeDebugFlagsEnabled(bool enabled); +  // 0 if not abort, non-zero if an abort is in progress. Used on fatal exit to prevents recursive  // aborts. Global declaration allows us to disable some error checking to ensure fatal shutdown  // makes forward progress. diff --git a/runtime/base/logging_test.cc b/runtime/base/logging_test.cc new file mode 100644 index 0000000000..d380b9eccc --- /dev/null +++ b/runtime/base/logging_test.cc @@ -0,0 +1,59 @@ +/* + * 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 "logging.h" + +#include <type_traits> + +#include "android-base/logging.h" +#include "base/bit_utils.h" +#include "base/macros.h" +#include "common_runtime_test.h" + +namespace art { + +static void SimpleAborter(const char* msg) { +  LOG(FATAL_WITHOUT_ABORT) << msg; +  _exit(1); +} + +class LoggingTest : public CommonRuntimeTest { + protected: +  void PostRuntimeCreate() OVERRIDE { +    // In our abort tests we really don't want the runtime to create a real dump. +    android::base::SetAborter(SimpleAborter); +  } +}; + +#ifdef NDEBUG +#error Unexpected NDEBUG +#endif + +class TestClass { + public: +  DECLARE_RUNTIME_DEBUG_FLAG(kFlag); +}; +DEFINE_RUNTIME_DEBUG_FLAG(TestClass, kFlag); + +TEST_F(LoggingTest, DECL_DEF) { +  SetRuntimeDebugFlagsEnabled(true); +  EXPECT_TRUE(TestClass::kFlag); + +  SetRuntimeDebugFlagsEnabled(false); +  EXPECT_FALSE(TestClass::kFlag); +} + +}  // namespace art diff --git a/runtime/common_dex_operations.h b/runtime/common_dex_operations.h index 133ddb0721..528db96dd5 100644 --- a/runtime/common_dex_operations.h +++ b/runtime/common_dex_operations.h @@ -62,7 +62,7 @@ inline void PerformCall(Thread* self,  }  template<Primitive::Type field_type> -static ALWAYS_INLINE void DoFieldGetCommon(Thread* self, +static ALWAYS_INLINE bool DoFieldGetCommon(Thread* self,                                             const ShadowFrame& shadow_frame,                                             ObjPtr<mirror::Object> obj,                                             ArtField* field, @@ -85,6 +85,9 @@ static ALWAYS_INLINE void DoFieldGetCommon(Thread* self,                                      shadow_frame.GetMethod(),                                      shadow_frame.GetDexPC(),                                      field); +    if (UNLIKELY(self->IsExceptionPending())) { +      return false; +    }    }    switch (field_type) { @@ -113,6 +116,7 @@ static ALWAYS_INLINE void DoFieldGetCommon(Thread* self,        LOG(FATAL) << "Unreachable " << field_type;        break;    } +  return true;  }  template<Primitive::Type field_type, bool do_assignability_check, bool transaction_active> @@ -120,7 +124,7 @@ ALWAYS_INLINE bool DoFieldPutCommon(Thread* self,                                      const ShadowFrame& shadow_frame,                                      ObjPtr<mirror::Object> obj,                                      ArtField* field, -                                    const JValue& value) +                                    JValue& value)      REQUIRES_SHARED(Locks::mutator_lock_) {    field->GetDeclaringClass()->AssertInitializedOrInitializingInThread(self); @@ -128,15 +132,22 @@ ALWAYS_INLINE bool DoFieldPutCommon(Thread* self,    // the field from the base of the object, we need to look for it first.    instrumentation::Instrumentation* instrumentation = Runtime::Current()->GetInstrumentation();    if (UNLIKELY(instrumentation->HasFieldWriteListeners())) { -    StackHandleScope<1> hs(self); -    // Wrap in handle wrapper in case the listener does thread suspension. +    StackHandleScope<2> hs(self); +    // Save this and return value (if needed) in case the instrumentation causes a suspend.      HandleWrapperObjPtr<mirror::Object> h(hs.NewHandleWrapper(&obj));      ObjPtr<mirror::Object> this_object = field->IsStatic() ? nullptr : obj; -    instrumentation->FieldWriteEvent(self, this_object.Ptr(), +    mirror::Object* fake_root = nullptr; +    HandleWrapper<mirror::Object> ret(hs.NewHandleWrapper<mirror::Object>( +        field_type == Primitive::kPrimNot ? value.GetGCRoot() : &fake_root)); +    instrumentation->FieldWriteEvent(self, +                                     this_object.Ptr(),                                       shadow_frame.GetMethod(),                                       shadow_frame.GetDexPC(),                                       field,                                       value); +    if (UNLIKELY(self->IsExceptionPending())) { +      return false; +    }    }    switch (field_type) { diff --git a/runtime/common_runtime_test.cc b/runtime/common_runtime_test.cc index f9259944b4..5a4a26a390 100644 --- a/runtime/common_runtime_test.cc +++ b/runtime/common_runtime_test.cc @@ -404,6 +404,7 @@ void CommonRuntimeTestImpl::SetUp() {    options.push_back(std::make_pair("-Xcheck:jni", nullptr));    options.push_back(std::make_pair(min_heap_string, nullptr));    options.push_back(std::make_pair(max_heap_string, nullptr)); +  options.push_back(std::make_pair("-XX:SlowDebug=true", nullptr));    callbacks_.reset(new NoopCompilerCallbacks()); diff --git a/runtime/interpreter/interpreter_common.cc b/runtime/interpreter/interpreter_common.cc index d06ac23d3c..1b36c3f12b 100644 --- a/runtime/interpreter/interpreter_common.cc +++ b/runtime/interpreter/interpreter_common.cc @@ -66,7 +66,11 @@ bool DoFieldGet(Thread* self, ShadowFrame& shadow_frame, const Instruction* inst    }    JValue result; -  DoFieldGetCommon<field_type>(self, shadow_frame, obj, f, &result); +  if (UNLIKELY(!DoFieldGetCommon<field_type>(self, shadow_frame, obj, f, &result))) { +    // Instrumentation threw an error! +    CHECK(self->IsExceptionPending()); +    return false; +  }    uint32_t vregA = is_static ? inst->VRegA_21c(inst_data) : inst->VRegA_22c(inst_data);    switch (field_type) {      case Primitive::kPrimBoolean: @@ -149,14 +153,18 @@ bool DoIGetQuick(ShadowFrame& shadow_frame, const Instruction* inst, uint16_t in                                                          field_offset.Uint32Value());      DCHECK(f != nullptr);      DCHECK(!f->IsStatic()); -    StackHandleScope<1> hs(Thread::Current()); +    Thread* self = Thread::Current(); +    StackHandleScope<1> hs(self);      // Save obj in case the instrumentation event has thread suspension.      HandleWrapperObjPtr<mirror::Object> h = hs.NewHandleWrapper(&obj); -    instrumentation->FieldReadEvent(Thread::Current(), +    instrumentation->FieldReadEvent(self,                                      obj.Ptr(),                                      shadow_frame.GetMethod(),                                      shadow_frame.GetDexPC(),                                      f); +    if (UNLIKELY(self->IsExceptionPending())) { +      return false; +    }    }    // Note: iget-x-quick instructions are only for non-volatile fields.    const uint32_t vregA = inst->VRegA_22c(inst_data); @@ -322,15 +330,22 @@ bool DoIPutQuick(const ShadowFrame& shadow_frame, const Instruction* inst, uint1      DCHECK(f != nullptr);      DCHECK(!f->IsStatic());      JValue field_value = GetFieldValue<field_type>(shadow_frame, vregA); -    StackHandleScope<1> hs(Thread::Current()); +    Thread* self = Thread::Current(); +    StackHandleScope<2> hs(self);      // Save obj in case the instrumentation event has thread suspension.      HandleWrapperObjPtr<mirror::Object> h = hs.NewHandleWrapper(&obj); -    instrumentation->FieldWriteEvent(Thread::Current(), +    mirror::Object* fake_root = nullptr; +    HandleWrapper<mirror::Object> ret(hs.NewHandleWrapper<mirror::Object>( +        field_type == Primitive::kPrimNot ? field_value.GetGCRoot() : &fake_root)); +    instrumentation->FieldWriteEvent(self,                                       obj.Ptr(),                                       shadow_frame.GetMethod(),                                       shadow_frame.GetDexPC(),                                       f,                                       field_value); +    if (UNLIKELY(self->IsExceptionPending())) { +      return false; +    }    }    // Note: iput-x-quick instructions are only for non-volatile fields.    switch (field_type) { diff --git a/runtime/jit/profile_compilation_info.cc b/runtime/jit/profile_compilation_info.cc index 175563ab9c..960030d577 100644 --- a/runtime/jit/profile_compilation_info.cc +++ b/runtime/jit/profile_compilation_info.cc @@ -1154,6 +1154,8 @@ bool ProfileCompilationInfo::MergeWith(const ProfileCompilationInfo& other) {    // Note that the number of elements should be very small, so this should not    // be a performance issue.    for (const DexFileData* other_dex_data : other.info_) { +    // verify_checksum is false because we want to differentiate between a missing dex data and +    // a mismatched checksum.      const DexFileData* dex_data = FindDexData(other_dex_data->profile_key,                                                0u,                                                /* verify_checksum */ false); @@ -1251,11 +1253,11 @@ std::unique_ptr<ProfileCompilationInfo::OfflineProfileMethodInfo> ProfileCompila      uint32_t dex_checksum,      uint16_t dex_method_index) const {    MethodHotness hotness(GetMethodHotness(dex_location, dex_checksum, dex_method_index)); -  const InlineCacheMap* inline_caches = hotness.GetInlineCacheMap(); -  if (inline_caches == nullptr) { +  if (!hotness.IsHot()) {      return nullptr;    } - +  const InlineCacheMap* inline_caches = hotness.GetInlineCacheMap(); +  DCHECK(inline_caches != nullptr);    std::unique_ptr<OfflineProfileMethodInfo> pmi(new OfflineProfileMethodInfo(inline_caches));    pmi->dex_references.resize(info_.size()); @@ -1596,6 +1598,38 @@ ProfileCompilationInfo::DexFileData::FindOrAddMethod(uint16_t method_index) {        InlineCacheMap(std::less<uint16_t>(), arena_->Adapter(kArenaAllocProfile)))->second);  } +// Mark a method as executed at least once. +void ProfileCompilationInfo::DexFileData::AddMethod(MethodHotness::Flag flags, size_t index) { +  if ((flags & MethodHotness::kFlagStartup) != 0) { +    method_bitmap.StoreBit(MethodBitIndex(/*startup*/ true, index), /*value*/ true); +  } +  if ((flags & MethodHotness::kFlagPostStartup) != 0) { +    method_bitmap.StoreBit(MethodBitIndex(/*startup*/ false, index), /*value*/ true); +  } +  if ((flags & MethodHotness::kFlagHot) != 0) { +    method_map.FindOrAdd( +        index, +        InlineCacheMap(std::less<uint16_t>(), arena_->Adapter(kArenaAllocProfile))); +  } +} + +ProfileCompilationInfo::MethodHotness ProfileCompilationInfo::DexFileData::GetHotnessInfo( +    uint32_t dex_method_index) const { +  MethodHotness ret; +  if (method_bitmap.LoadBit(MethodBitIndex(/*startup*/ true, dex_method_index))) { +    ret.AddFlag(MethodHotness::kFlagStartup); +  } +  if (method_bitmap.LoadBit(MethodBitIndex(/*startup*/ false, dex_method_index))) { +    ret.AddFlag(MethodHotness::kFlagPostStartup); +  } +  auto it = method_map.find(dex_method_index); +  if (it != method_map.end()) { +    ret.SetInlineCacheMap(&it->second); +    ret.AddFlag(MethodHotness::kFlagHot); +  } +  return ret; +} +  ProfileCompilationInfo::DexPcData*  ProfileCompilationInfo::FindOrAddDexPc(InlineCacheMap* inline_cache, uint32_t dex_pc) {    return &(inline_cache->FindOrAdd(dex_pc, DexPcData(&arena_))->second); diff --git a/runtime/jit/profile_compilation_info.h b/runtime/jit/profile_compilation_info.h index b2d541f896..8d1e578875 100644 --- a/runtime/jit/profile_compilation_info.h +++ b/runtime/jit/profile_compilation_info.h @@ -198,10 +198,14 @@ class ProfileCompilationInfo {        return flags_;      } -    bool HasAnyFlags() const { +    bool IsInProfile() const {        return flags_ != 0;      } +   private: +    const InlineCacheMap* inline_cache_map_ = nullptr; +    uint8_t flags_ = 0; +      const InlineCacheMap* GetInlineCacheMap() const {        return inline_cache_map_;      } @@ -210,9 +214,7 @@ class ProfileCompilationInfo {        inline_cache_map_ = info;      } -   private: -    const InlineCacheMap* inline_cache_map_ = nullptr; -    uint8_t flags_ = 0; +    friend class ProfileCompilationInfo;    };    // Encodes the full set of inline caches for a given method. @@ -421,19 +423,7 @@ class ProfileCompilationInfo {      }      // Mark a method as executed at least once. -    void AddMethod(MethodHotness::Flag flags, size_t index) { -      if ((flags & MethodHotness::kFlagStartup) != 0) { -        method_bitmap.StoreBit(MethodBitIndex(/*startup*/ true, index), /*value*/ true); -      } -      if ((flags & MethodHotness::kFlagPostStartup) != 0) { -        method_bitmap.StoreBit(MethodBitIndex(/*startup*/ false, index), /*value*/ true); -      } -      if ((flags & MethodHotness::kFlagHot) != 0) { -        method_map.FindOrAdd( -            index, -            InlineCacheMap(std::less<uint16_t>(), arena_->Adapter(kArenaAllocProfile))); -      } -    } +    void AddMethod(MethodHotness::Flag flags, size_t index);      void MergeBitmap(const DexFileData& other) {        DCHECK_EQ(bitmap_storage.size(), other.bitmap_storage.size()); @@ -442,21 +432,7 @@ class ProfileCompilationInfo {        }      } -    MethodHotness GetHotnessInfo(uint32_t dex_method_index) const { -      MethodHotness ret; -      if (method_bitmap.LoadBit(MethodBitIndex(/*startup*/ true, dex_method_index))) { -        ret.AddFlag(MethodHotness::kFlagStartup); -      } -      if (method_bitmap.LoadBit(MethodBitIndex(/*startup*/ false, dex_method_index))) { -        ret.AddFlag(MethodHotness::kFlagPostStartup); -      } -      auto it = method_map.find(dex_method_index); -      if (it != method_map.end()) { -        ret.SetInlineCacheMap(&it->second); -        ret.AddFlag(MethodHotness::kFlagHot); -      } -      return ret; -    } +    MethodHotness GetHotnessInfo(uint32_t dex_method_index) const;      // The arena used to allocate new inline cache maps.      ArenaAllocator* arena_; diff --git a/runtime/jit/profile_compilation_info_test.cc b/runtime/jit/profile_compilation_info_test.cc index c3a34156ad..1ba98acab8 100644 --- a/runtime/jit/profile_compilation_info_test.cc +++ b/runtime/jit/profile_compilation_info_test.cc @@ -868,8 +868,8 @@ TEST_F(ProfileCompilationInfoTest, SampledMethodsTest) {    test_info.AddMethodIndex(Hotness::kFlagStartup, kDex2, kChecksum2, 2, kNumMethods);    test_info.AddMethodIndex(Hotness::kFlagPostStartup, kDex2, kChecksum2, 4, kNumMethods);    auto run_test = [](const ProfileCompilationInfo& info) { -    EXPECT_FALSE(info.GetMethodHotness(kDex1, kChecksum1, 2).HasAnyFlags()); -    EXPECT_FALSE(info.GetMethodHotness(kDex1, kChecksum1, 4).HasAnyFlags()); +    EXPECT_FALSE(info.GetMethodHotness(kDex1, kChecksum1, 2).IsInProfile()); +    EXPECT_FALSE(info.GetMethodHotness(kDex1, kChecksum1, 4).IsInProfile());      EXPECT_TRUE(info.GetMethodHotness(kDex1, kChecksum1, 1).IsStartup());      EXPECT_FALSE(info.GetMethodHotness(kDex1, kChecksum1, 3).IsStartup());      EXPECT_TRUE(info.GetMethodHotness(kDex1, kChecksum1, 5).IsPostStartup()); @@ -931,9 +931,9 @@ TEST_F(ProfileCompilationInfoTest, SampledMethodsTest) {      }      EXPECT_TRUE(info.GetMethodHotness(MethodReference(dex.get(), 6)).IsPostStartup());      // Check that methods that shouldn't have been touched are OK. -    EXPECT_TRUE(info.GetMethodHotness(MethodReference(dex.get(), 0)).HasAnyFlags()); -    EXPECT_FALSE(info.GetMethodHotness(MethodReference(dex.get(), 4)).HasAnyFlags()); -    EXPECT_FALSE(info.GetMethodHotness(MethodReference(dex.get(), 7)).HasAnyFlags()); +    EXPECT_TRUE(info.GetMethodHotness(MethodReference(dex.get(), 0)).IsInProfile()); +    EXPECT_FALSE(info.GetMethodHotness(MethodReference(dex.get(), 4)).IsInProfile()); +    EXPECT_FALSE(info.GetMethodHotness(MethodReference(dex.get(), 7)).IsInProfile());      EXPECT_FALSE(info.GetMethodHotness(MethodReference(dex.get(), 1)).IsPostStartup());      EXPECT_FALSE(info.GetMethodHotness(MethodReference(dex.get(), 4)).IsStartup());      EXPECT_FALSE(info.GetMethodHotness(MethodReference(dex.get(), 6)).IsStartup()); diff --git a/runtime/jit/profile_saver.cc b/runtime/jit/profile_saver.cc index 94363c6f2d..b41bc78170 100644 --- a/runtime/jit/profile_saver.cc +++ b/runtime/jit/profile_saver.cc @@ -681,7 +681,7 @@ bool ProfileSaver::HasSeenMethod(const std::string& profile,      if (!info.Load(profile, /*clear_if_invalid*/false)) {        return false;      } -    return info.GetMethodHotness(MethodReference(dex_file, method_idx)).HasAnyFlags(); +    return info.GetMethodHotness(MethodReference(dex_file, method_idx)).IsInProfile();    }    return false;  } diff --git a/runtime/method_handles.cc b/runtime/method_handles.cc index 090bac1173..f0d3cae4b4 100644 --- a/runtime/method_handles.cc +++ b/runtime/method_handles.cc @@ -822,7 +822,7 @@ inline bool DoFieldPutForInvokePolymorphic(Thread* self,                                             ObjPtr<mirror::Object>& obj,                                             ArtField* field,                                             Primitive::Type field_type, -                                           const JValue& value) +                                           JValue& value)      REQUIRES_SHARED(Locks::mutator_lock_) {    DCHECK(!Runtime::Current()->IsActiveTransaction());    static const bool kTransaction = false;         // Not in a transaction. diff --git a/runtime/openjdkjvmti/OpenjdkJvmTi.cc b/runtime/openjdkjvmti/OpenjdkJvmTi.cc index 3ec5b323c8..0896210f1c 100644 --- a/runtime/openjdkjvmti/OpenjdkJvmTi.cc +++ b/runtime/openjdkjvmti/OpenjdkJvmTi.cc @@ -635,36 +635,28 @@ class JvmtiFunctions {      return ERR(NOT_IMPLEMENTED);    } -  static jvmtiError SetFieldAccessWatch(jvmtiEnv* env, -                                        jclass klass ATTRIBUTE_UNUSED, -                                        jfieldID field ATTRIBUTE_UNUSED) { +  static jvmtiError SetFieldAccessWatch(jvmtiEnv* env, jclass klass, jfieldID field) {      ENSURE_VALID_ENV(env);      ENSURE_HAS_CAP(env, can_generate_field_access_events); -    return ERR(NOT_IMPLEMENTED); +    return FieldUtil::SetFieldAccessWatch(env, klass, field);    } -  static jvmtiError ClearFieldAccessWatch(jvmtiEnv* env, -                                          jclass klass ATTRIBUTE_UNUSED, -                                          jfieldID field ATTRIBUTE_UNUSED) { +  static jvmtiError ClearFieldAccessWatch(jvmtiEnv* env, jclass klass, jfieldID field) {      ENSURE_VALID_ENV(env);      ENSURE_HAS_CAP(env, can_generate_field_access_events); -    return ERR(NOT_IMPLEMENTED); +    return FieldUtil::ClearFieldAccessWatch(env, klass, field);    } -  static jvmtiError SetFieldModificationWatch(jvmtiEnv* env, -                                              jclass klass ATTRIBUTE_UNUSED, -                                              jfieldID field ATTRIBUTE_UNUSED) { +  static jvmtiError SetFieldModificationWatch(jvmtiEnv* env, jclass klass, jfieldID field) {      ENSURE_VALID_ENV(env);      ENSURE_HAS_CAP(env, can_generate_field_modification_events); -    return ERR(NOT_IMPLEMENTED); +    return FieldUtil::SetFieldModificationWatch(env, klass, field);    } -  static jvmtiError ClearFieldModificationWatch(jvmtiEnv* env, -                                                jclass klass ATTRIBUTE_UNUSED, -                                                jfieldID field ATTRIBUTE_UNUSED) { +  static jvmtiError ClearFieldModificationWatch(jvmtiEnv* env, jclass klass, jfieldID field) {      ENSURE_VALID_ENV(env);      ENSURE_HAS_CAP(env, can_generate_field_modification_events); -    return ERR(NOT_IMPLEMENTED); +    return FieldUtil::ClearFieldModificationWatch(env, klass, field);    }    static jvmtiError GetLoadedClasses(jvmtiEnv* env, jint* class_count_ptr, jclass** classes_ptr) { @@ -694,12 +686,10 @@ class JvmtiFunctions {      return ClassUtil::GetClassStatus(env, klass, status_ptr);    } -  static jvmtiError GetSourceFileName(jvmtiEnv* env, -                                      jclass klass ATTRIBUTE_UNUSED, -                                      char** source_name_ptr ATTRIBUTE_UNUSED) { +  static jvmtiError GetSourceFileName(jvmtiEnv* env, jclass klass, char** source_name_ptr) {      ENSURE_VALID_ENV(env);      ENSURE_HAS_CAP(env, can_get_source_file_name); -    return ERR(NOT_IMPLEMENTED); +    return ClassUtil::GetSourceFileName(env, klass, source_name_ptr);    }    static jvmtiError GetClassModifiers(jvmtiEnv* env, jclass klass, jint* modifiers_ptr) { @@ -774,11 +764,11 @@ class JvmtiFunctions {    }    static jvmtiError GetSourceDebugExtension(jvmtiEnv* env, -                                            jclass klass ATTRIBUTE_UNUSED, -                                            char** source_debug_extension_ptr ATTRIBUTE_UNUSED) { +                                            jclass klass, +                                            char** source_debug_extension_ptr) {      ENSURE_VALID_ENV(env);      ENSURE_HAS_CAP(env, can_get_source_debug_extension); -    return ERR(NOT_IMPLEMENTED); +    return ClassUtil::GetSourceDebugExtension(env, klass, source_debug_extension_ptr);    }    static jvmtiError RetransformClasses(jvmtiEnv* env, jint class_count, const jclass* classes) { diff --git a/runtime/openjdkjvmti/art_jvmti.h b/runtime/openjdkjvmti/art_jvmti.h index 369b2d7c00..b5f12191e6 100644 --- a/runtime/openjdkjvmti/art_jvmti.h +++ b/runtime/openjdkjvmti/art_jvmti.h @@ -34,6 +34,7 @@  #include <memory>  #include <type_traits> +#include <unordered_set>  #include <jni.h> @@ -46,6 +47,10 @@  #include "jni_env_ext.h"  #include "jvmti.h" +namespace art { +class ArtField; +} +  namespace openjdkjvmti {  class ObjectTagTable; @@ -62,6 +67,15 @@ struct ArtJvmTiEnv : public jvmtiEnv {    // Tagging is specific to the jvmtiEnv.    std::unique_ptr<ObjectTagTable> object_tag_table; +  // Set of watched fields is unique to each jvmtiEnv. +  // TODO It might be good to follow the RI and only let one jvmtiEnv ever have the watch caps so +  // we can record this on the field directly. We could do this either using free access-flag bits +  // or by putting a list in the ClassExt of a field's DeclaringClass. +  // TODO Maybe just have an extension to let one put a watch on every field, that would probably be +  // good enough maybe since you probably want either a few or all/almost all of them. +  std::unordered_set<art::ArtField*> access_watched_fields; +  std::unordered_set<art::ArtField*> modify_watched_fields; +    ArtJvmTiEnv(art::JavaVMExt* runtime, EventHandler* event_handler);    static ArtJvmTiEnv* AsArtJvmTiEnv(jvmtiEnv* env) { @@ -194,8 +208,8 @@ static inline JvmtiUniquePtr<char[]> CopyString(jvmtiEnv* env, const char* src,  const jvmtiCapabilities kPotentialCapabilities = {      .can_tag_objects                                 = 1, -    .can_generate_field_modification_events          = 0, -    .can_generate_field_access_events                = 0, +    .can_generate_field_modification_events          = 1, +    .can_generate_field_access_events                = 1,      .can_get_bytecodes                               = 0,      .can_get_synthetic_attribute                     = 1,      .can_get_owned_monitor_info                      = 0, @@ -204,9 +218,9 @@ const jvmtiCapabilities kPotentialCapabilities = {      .can_pop_frame                                   = 0,      .can_redefine_classes                            = 1,      .can_signal_thread                               = 0, -    .can_get_source_file_name                        = 0, +    .can_get_source_file_name                        = 1,      .can_get_line_numbers                            = 1, -    .can_get_source_debug_extension                  = 0, +    .can_get_source_debug_extension                  = 1,      .can_access_local_variables                      = 0,      .can_maintain_original_method_order              = 0,      .can_generate_single_step_events                 = 0, diff --git a/runtime/openjdkjvmti/events-inl.h b/runtime/openjdkjvmti/events-inl.h index cb7e6a9ad0..af99233f90 100644 --- a/runtime/openjdkjvmti/events-inl.h +++ b/runtime/openjdkjvmti/events-inl.h @@ -20,6 +20,7 @@  #include <array>  #include "events.h" +#include "jni_internal.h"  #include "ScopedLocalRef.h"  #include "art_jvmti.h" @@ -216,6 +217,71 @@ inline void EventHandler::DispatchEvent(ArtJvmTiEnv* env, art::Thread* thread, A    }  } +// Need to give custom specializations for FieldAccess and FieldModification since they need to +// filter out which particular fields agents want to get notified on. +// TODO The spec allows us to do shortcuts like only allow one agent to ever set these watches. This +// could make the system more performant. +template <> +inline void EventHandler::DispatchEvent<ArtJvmtiEvent::kFieldModification>(art::Thread* thread, +                                                                           JNIEnv* jnienv, +                                                                           jthread jni_thread, +                                                                           jmethodID method, +                                                                           jlocation location, +                                                                           jclass field_klass, +                                                                           jobject object, +                                                                           jfieldID field, +                                                                           char type_char, +                                                                           jvalue val) const { +  for (ArtJvmTiEnv* env : envs) { +    if (env != nullptr && +        ShouldDispatch<ArtJvmtiEvent::kFieldModification>(env, thread) && +        env->modify_watched_fields.find( +            art::jni::DecodeArtField(field)) != env->modify_watched_fields.end()) { +      ScopedLocalRef<jthrowable> thr(jnienv, jnienv->ExceptionOccurred()); +      jnienv->ExceptionClear(); +      auto callback = impl::GetCallback<ArtJvmtiEvent::kFieldModification>(env); +      (*callback)(env, +                  jnienv, +                  jni_thread, +                  method, +                  location, +                  field_klass, +                  object, +                  field, +                  type_char, +                  val); +      if (thr.get() != nullptr && !jnienv->ExceptionCheck()) { +        jnienv->Throw(thr.get()); +      } +    } +  } +} + +template <> +inline void EventHandler::DispatchEvent<ArtJvmtiEvent::kFieldAccess>(art::Thread* thread, +                                                                     JNIEnv* jnienv, +                                                                     jthread jni_thread, +                                                                     jmethodID method, +                                                                     jlocation location, +                                                                     jclass field_klass, +                                                                     jobject object, +                                                                     jfieldID field) const { +  for (ArtJvmTiEnv* env : envs) { +    if (env != nullptr && +        ShouldDispatch<ArtJvmtiEvent::kFieldAccess>(env, thread) && +        env->access_watched_fields.find( +            art::jni::DecodeArtField(field)) != env->access_watched_fields.end()) { +      ScopedLocalRef<jthrowable> thr(jnienv, jnienv->ExceptionOccurred()); +      jnienv->ExceptionClear(); +      auto callback = impl::GetCallback<ArtJvmtiEvent::kFieldAccess>(env); +      (*callback)(env, jnienv, jni_thread, method, location, field_klass, object, field); +      if (thr.get() != nullptr && !jnienv->ExceptionCheck()) { +        jnienv->Throw(thr.get()); +      } +    } +  } +} +  // Need to give a custom specialization for NativeMethodBind since it has to deal with an out  // variable.  template <> diff --git a/runtime/openjdkjvmti/events.cc b/runtime/openjdkjvmti/events.cc index 90bc122220..989b9af591 100644 --- a/runtime/openjdkjvmti/events.cc +++ b/runtime/openjdkjvmti/events.cc @@ -33,6 +33,7 @@  #include "art_jvmti.h"  #include "art_method-inl.h" +#include "art_field-inl.h"  #include "base/logging.h"  #include "gc/allocation_listener.h"  #include "gc/gc_pause_listener.h" @@ -433,24 +434,92 @@ class JvmtiMethodTraceListener FINAL : public art::instrumentation::Instrumentat    }    // Call-back for when we read from a field. -  void FieldRead(art::Thread* self ATTRIBUTE_UNUSED, -                 art::Handle<art::mirror::Object> this_object ATTRIBUTE_UNUSED, -                 art::ArtMethod* method ATTRIBUTE_UNUSED, -                 uint32_t dex_pc ATTRIBUTE_UNUSED, -                 art::ArtField* field ATTRIBUTE_UNUSED) +  void FieldRead(art::Thread* self, +                 art::Handle<art::mirror::Object> this_object, +                 art::ArtMethod* method, +                 uint32_t dex_pc, +                 art::ArtField* field)        REQUIRES_SHARED(art::Locks::mutator_lock_) OVERRIDE { -    return; +    if (event_handler_->IsEventEnabledAnywhere(ArtJvmtiEvent::kFieldAccess)) { +      art::JNIEnvExt* jnienv = self->GetJniEnv(); +      // DCHECK(!self->IsExceptionPending()); +      ScopedLocalRef<jobject> this_ref(jnienv, AddLocalRef<jobject>(jnienv, this_object.Get())); +      ScopedLocalRef<jobject> fklass(jnienv, +                                     AddLocalRef<jobject>(jnienv, +                                                          field->GetDeclaringClass().Ptr())); +      RunEventCallback<ArtJvmtiEvent::kFieldAccess>(self, +                                                    jnienv, +                                                    art::jni::EncodeArtMethod(method), +                                                    static_cast<jlocation>(dex_pc), +                                                    static_cast<jclass>(fklass.get()), +                                                    this_ref.get(), +                                                    art::jni::EncodeArtField(field)); +    } +  } + +  void FieldWritten(art::Thread* self, +                    art::Handle<art::mirror::Object> this_object, +                    art::ArtMethod* method, +                    uint32_t dex_pc, +                    art::ArtField* field, +                    art::Handle<art::mirror::Object> new_val) +      REQUIRES_SHARED(art::Locks::mutator_lock_) OVERRIDE { +    if (event_handler_->IsEventEnabledAnywhere(ArtJvmtiEvent::kFieldModification)) { +      art::JNIEnvExt* jnienv = self->GetJniEnv(); +      // DCHECK(!self->IsExceptionPending()); +      ScopedLocalRef<jobject> this_ref(jnienv, AddLocalRef<jobject>(jnienv, this_object.Get())); +      ScopedLocalRef<jobject> fklass(jnienv, +                                     AddLocalRef<jobject>(jnienv, +                                                          field->GetDeclaringClass().Ptr())); +      ScopedLocalRef<jobject> fval(jnienv, AddLocalRef<jobject>(jnienv, new_val.Get())); +      jvalue val; +      val.l = fval.get(); +      RunEventCallback<ArtJvmtiEvent::kFieldModification>( +          self, +          jnienv, +          art::jni::EncodeArtMethod(method), +          static_cast<jlocation>(dex_pc), +          static_cast<jclass>(fklass.get()), +          field->IsStatic() ? nullptr :  this_ref.get(), +          art::jni::EncodeArtField(field), +          'L',  // type_char +          val); +    }    }    // Call-back for when we write into a field. -  void FieldWritten(art::Thread* self ATTRIBUTE_UNUSED, -                    art::Handle<art::mirror::Object> this_object ATTRIBUTE_UNUSED, -                    art::ArtMethod* method ATTRIBUTE_UNUSED, -                    uint32_t dex_pc ATTRIBUTE_UNUSED, -                    art::ArtField* field ATTRIBUTE_UNUSED, -                    const art::JValue& field_value ATTRIBUTE_UNUSED) +  void FieldWritten(art::Thread* self, +                    art::Handle<art::mirror::Object> this_object, +                    art::ArtMethod* method, +                    uint32_t dex_pc, +                    art::ArtField* field, +                    const art::JValue& field_value)        REQUIRES_SHARED(art::Locks::mutator_lock_) OVERRIDE { -    return; +    if (event_handler_->IsEventEnabledAnywhere(ArtJvmtiEvent::kFieldModification)) { +      art::JNIEnvExt* jnienv = self->GetJniEnv(); +      DCHECK(!self->IsExceptionPending()); +      ScopedLocalRef<jobject> this_ref(jnienv, AddLocalRef<jobject>(jnienv, this_object.Get())); +      ScopedLocalRef<jobject> fklass(jnienv, +                                     AddLocalRef<jobject>(jnienv, +                                                          field->GetDeclaringClass().Ptr())); +      char type_char = art::Primitive::Descriptor(field->GetTypeAsPrimitiveType())[0]; +      jvalue val; +      // 64bit integer is the largest value in the union so we should be fine simply copying it into +      // the union. +      val.j = field_value.GetJ(); +      RunEventCallback<ArtJvmtiEvent::kFieldModification>( +          self, +          jnienv, +          art::jni::EncodeArtMethod(method), +          static_cast<jlocation>(dex_pc), +          static_cast<jclass>(fklass.get()), +          field->IsStatic() ? nullptr :  this_ref.get(),  // nb static field modification get given +                                                          // the class as this_object for some +                                                          // reason. +          art::jni::EncodeArtField(field), +          type_char, +          val); +    }    }    // Call-back when an exception is caught. @@ -490,15 +559,20 @@ static uint32_t GetInstrumentationEventsFor(ArtJvmtiEvent event) {      case ArtJvmtiEvent::kMethodExit:        return art::instrumentation::Instrumentation::kMethodExited |               art::instrumentation::Instrumentation::kMethodUnwind; +    case ArtJvmtiEvent::kFieldModification: +      return art::instrumentation::Instrumentation::kFieldWritten; +    case ArtJvmtiEvent::kFieldAccess: +      return art::instrumentation::Instrumentation::kFieldRead;      default:        LOG(FATAL) << "Unknown event ";        return 0;    }  } -static void SetupMethodTraceListener(JvmtiMethodTraceListener* listener, -                                     ArtJvmtiEvent event, -                                     bool enable) { +static void SetupTraceListener(JvmtiMethodTraceListener* listener, +                               ArtJvmtiEvent event, +                               bool enable) { +  art::ScopedThreadStateChange stsc(art::Thread::Current(), art::ThreadState::kNative);    uint32_t new_events = GetInstrumentationEventsFor(event);    art::instrumentation::Instrumentation* instr = art::Runtime::Current()->GetInstrumentation();    art::gc::ScopedGCCriticalSection gcs(art::Thread::Current(), @@ -529,7 +603,9 @@ void EventHandler::HandleEventType(ArtJvmtiEvent event, bool enable) {      case ArtJvmtiEvent::kMethodEntry:      case ArtJvmtiEvent::kMethodExit: -      SetupMethodTraceListener(method_trace_listener_.get(), event, enable); +    case ArtJvmtiEvent::kFieldAccess: +    case ArtJvmtiEvent::kFieldModification: +      SetupTraceListener(method_trace_listener_.get(), event, enable);        return;      default: diff --git a/runtime/openjdkjvmti/ti_class.cc b/runtime/openjdkjvmti/ti_class.cc index 332181497c..0ac08d9cb8 100644 --- a/runtime/openjdkjvmti/ti_class.cc +++ b/runtime/openjdkjvmti/ti_class.cc @@ -1017,4 +1017,61 @@ jvmtiError ClassUtil::GetClassVersionNumbers(jvmtiEnv* env ATTRIBUTE_UNUSED,    return ERR(NONE);  } +jvmtiError ClassUtil::GetSourceFileName(jvmtiEnv* env, jclass jklass, char** source_name_ptr) { +  art::ScopedObjectAccess soa(art::Thread::Current()); +  if (jklass == nullptr) { +    return ERR(INVALID_CLASS); +  } +  art::ObjPtr<art::mirror::Object> jklass_obj = soa.Decode<art::mirror::Object>(jklass); +  if (!jklass_obj->IsClass()) { +    return ERR(INVALID_CLASS); +  } +  art::ObjPtr<art::mirror::Class> klass = jklass_obj->AsClass(); +  if (klass->IsPrimitive() || klass->IsArrayClass()) { +    return ERR(ABSENT_INFORMATION); +  } +  JvmtiUniquePtr<char[]> source_copy; +  const char* file_name = klass->GetSourceFile(); +  if (file_name == nullptr) { +    return ERR(ABSENT_INFORMATION); +  } +  jvmtiError ret; +  source_copy = CopyString(env, file_name, &ret); +  if (source_copy == nullptr) { +    return ret; +  } +  *source_name_ptr = source_copy.release(); +  return OK; +} + +jvmtiError ClassUtil::GetSourceDebugExtension(jvmtiEnv* env, +                                              jclass jklass, +                                              char** source_debug_extension_ptr) { +  art::ScopedObjectAccess soa(art::Thread::Current()); +  if (jklass == nullptr) { +    return ERR(INVALID_CLASS); +  } +  art::ObjPtr<art::mirror::Object> jklass_obj = soa.Decode<art::mirror::Object>(jklass); +  if (!jklass_obj->IsClass()) { +    return ERR(INVALID_CLASS); +  } +  art::StackHandleScope<1> hs(art::Thread::Current()); +  art::Handle<art::mirror::Class> klass(hs.NewHandle(jklass_obj->AsClass())); +  if (klass->IsPrimitive() || klass->IsArrayClass()) { +    return ERR(ABSENT_INFORMATION); +  } +  JvmtiUniquePtr<char[]> ext_copy; +  const char* data = art::annotations::GetSourceDebugExtension(klass); +  if (data == nullptr) { +    return ERR(ABSENT_INFORMATION); +  } +  jvmtiError ret; +  ext_copy = CopyString(env, data, &ret); +  if (ext_copy == nullptr) { +    return ret; +  } +  *source_debug_extension_ptr = ext_copy.release(); +  return OK; +} +  }  // namespace openjdkjvmti diff --git a/runtime/openjdkjvmti/ti_class.h b/runtime/openjdkjvmti/ti_class.h index aa2260f035..7bb6b3e5de 100644 --- a/runtime/openjdkjvmti/ti_class.h +++ b/runtime/openjdkjvmti/ti_class.h @@ -82,6 +82,12 @@ class ClassUtil {                                             jclass klass,                                             jint* minor_version_ptr,                                             jint* major_version_ptr); + +  static jvmtiError GetSourceFileName(jvmtiEnv* env, jclass klass, char** source_name_ptr); + +  static jvmtiError GetSourceDebugExtension(jvmtiEnv* env, +                                            jclass klass, +                                            char** source_debug_extension_ptr);  };  }  // namespace openjdkjvmti diff --git a/runtime/openjdkjvmti/ti_field.cc b/runtime/openjdkjvmti/ti_field.cc index 342d8be2b0..32c064e89c 100644 --- a/runtime/openjdkjvmti/ti_field.cc +++ b/runtime/openjdkjvmti/ti_field.cc @@ -187,4 +187,68 @@ jvmtiError FieldUtil::IsFieldSynthetic(jvmtiEnv* env ATTRIBUTE_UNUSED,    return ERR(NONE);  } +jvmtiError FieldUtil::SetFieldModificationWatch(jvmtiEnv* jenv, jclass klass, jfieldID field) { +  ArtJvmTiEnv* env = ArtJvmTiEnv::AsArtJvmTiEnv(jenv); +  if (klass == nullptr) { +    return ERR(INVALID_CLASS); +  } +  if (field == nullptr) { +    return ERR(INVALID_FIELDID); +  } +  auto res_pair = env->modify_watched_fields.insert(art::jni::DecodeArtField(field)); +  if (!res_pair.second) { +    // Didn't get inserted because it's already present! +    return ERR(DUPLICATE); +  } +  return OK; +} + +jvmtiError FieldUtil::ClearFieldModificationWatch(jvmtiEnv* jenv, jclass klass, jfieldID field) { +  ArtJvmTiEnv* env = ArtJvmTiEnv::AsArtJvmTiEnv(jenv); +  if (klass == nullptr) { +    return ERR(INVALID_CLASS); +  } +  if (field == nullptr) { +    return ERR(INVALID_FIELDID); +  } +  auto pos = env->modify_watched_fields.find(art::jni::DecodeArtField(field)); +  if (pos == env->modify_watched_fields.end()) { +    return ERR(NOT_FOUND); +  } +  env->modify_watched_fields.erase(pos); +  return OK; +} + +jvmtiError FieldUtil::SetFieldAccessWatch(jvmtiEnv* jenv, jclass klass, jfieldID field) { +  ArtJvmTiEnv* env = ArtJvmTiEnv::AsArtJvmTiEnv(jenv); +  if (klass == nullptr) { +    return ERR(INVALID_CLASS); +  } +  if (field == nullptr) { +    return ERR(INVALID_FIELDID); +  } +  auto res_pair = env->access_watched_fields.insert(art::jni::DecodeArtField(field)); +  if (!res_pair.second) { +    // Didn't get inserted because it's already present! +    return ERR(DUPLICATE); +  } +  return OK; +} + +jvmtiError FieldUtil::ClearFieldAccessWatch(jvmtiEnv* jenv, jclass klass, jfieldID field) { +  ArtJvmTiEnv* env = ArtJvmTiEnv::AsArtJvmTiEnv(jenv); +  if (klass == nullptr) { +    return ERR(INVALID_CLASS); +  } +  if (field == nullptr) { +    return ERR(INVALID_FIELDID); +  } +  auto pos = env->access_watched_fields.find(art::jni::DecodeArtField(field)); +  if (pos == env->access_watched_fields.end()) { +    return ERR(NOT_FOUND); +  } +  env->access_watched_fields.erase(pos); +  return OK; +} +  }  // namespace openjdkjvmti diff --git a/runtime/openjdkjvmti/ti_field.h b/runtime/openjdkjvmti/ti_field.h index 9a29f81d76..880949eecb 100644 --- a/runtime/openjdkjvmti/ti_field.h +++ b/runtime/openjdkjvmti/ti_field.h @@ -60,6 +60,11 @@ class FieldUtil {                                       jclass klass,                                       jfieldID field,                                       jboolean* is_synthetic_ptr); + +  static jvmtiError SetFieldModificationWatch(jvmtiEnv* env, jclass klass, jfieldID field); +  static jvmtiError ClearFieldModificationWatch(jvmtiEnv* env, jclass klass, jfieldID field); +  static jvmtiError SetFieldAccessWatch(jvmtiEnv* env, jclass klass, jfieldID field); +  static jvmtiError ClearFieldAccessWatch(jvmtiEnv* env, jclass klass, jfieldID field);  };  }  // namespace openjdkjvmti diff --git a/runtime/parsed_options.cc b/runtime/parsed_options.cc index abb6f8c018..b1eb506209 100644 --- a/runtime/parsed_options.cc +++ b/runtime/parsed_options.cc @@ -18,6 +18,7 @@  #include <sstream> +#include "base/logging.h"  #include "base/stringpiece.h"  #include "debugger.h"  #include "gc/heap.h" @@ -306,6 +307,10 @@ std::unique_ptr<RuntimeParser> ParsedOptions::MakeParser(bool ignore_unrecognize        .Define("-XX:ThreadSuspendTimeout=_")  // in ms            .WithType<MillisecondsToNanoseconds>()  // store as ns            .IntoKey(M::ThreadSuspendTimeout) +      .Define("-XX:SlowDebug=_") +          .WithType<bool>() +          .WithValueMap({{"false", false}, {"true", true}}) +          .IntoKey(M::SlowDebug)        .Ignore({            "-ea", "-da", "-enableassertions", "-disableassertions", "--runtime-arg", "-esa",            "-dsa", "-enablesystemassertions", "-disablesystemassertions", "-Xrs", "-Xint:_", @@ -517,6 +522,8 @@ bool ParsedOptions::DoParse(const RuntimeOptions& options,    MaybeOverrideVerbosity(); +  SetRuntimeDebugFlagsEnabled(args.Get(M::SlowDebug)); +    // -Xprofile:    Trace::SetDefaultClockSource(args.GetOrDefault(M::ProfileClock)); @@ -704,6 +711,7 @@ void ParsedOptions::Usage(const char* fmt, ...) {    UsageMessage(stream, "  -XX:LargeObjectSpace={disabled,map,freelist}\n");    UsageMessage(stream, "  -XX:LargeObjectThreshold=N\n");    UsageMessage(stream, "  -XX:DumpNativeStackOnSigQuit=booleanvalue\n"); +  UsageMessage(stream, "  -XX:SlowDebug={false,true}\n");    UsageMessage(stream, "  -Xmethod-trace\n");    UsageMessage(stream, "  -Xmethod-trace-file:filename");    UsageMessage(stream, "  -Xmethod-trace-file-size:integervalue\n"); diff --git a/runtime/read_barrier.cc b/runtime/read_barrier.cc new file mode 100644 index 0000000000..89ae91040a --- /dev/null +++ b/runtime/read_barrier.cc @@ -0,0 +1,24 @@ +/* + * 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 "read_barrier.h" + +namespace art { + +DEFINE_RUNTIME_DEBUG_FLAG(ReadBarrier, kEnableToSpaceInvariantChecks); +DEFINE_RUNTIME_DEBUG_FLAG(ReadBarrier, kEnableReadBarrierInvariantChecks); + +}  // namespace art diff --git a/runtime/read_barrier.h b/runtime/read_barrier.h index 296409014e..ca776854cb 100644 --- a/runtime/read_barrier.h +++ b/runtime/read_barrier.h @@ -17,6 +17,7 @@  #ifndef ART_RUNTIME_READ_BARRIER_H_  #define ART_RUNTIME_READ_BARRIER_H_ +#include "base/logging.h"  #include "base/mutex.h"  #include "base/macros.h"  #include "gc_root.h" @@ -37,10 +38,13 @@ class ArtMethod;  class ReadBarrier {   public: -  // Enable the to-space invariant checks. -  static constexpr bool kEnableToSpaceInvariantChecks = kIsDebugBuild; -  // Enable the read barrier checks. -  static constexpr bool kEnableReadBarrierInvariantChecks = kIsDebugBuild; +  // Enable the to-space invariant checks. This is slow and happens very often. Do not enable in +  // fast-debug environment. +  DECLARE_RUNTIME_DEBUG_FLAG(kEnableToSpaceInvariantChecks); + +  // Enable the read barrier checks. This is slow and happens very often. Do not enable in +  // fast-debug environment. +  DECLARE_RUNTIME_DEBUG_FLAG(kEnableReadBarrierInvariantChecks);    // It's up to the implementation whether the given field gets updated whereas the return value    // must be an updated reference unless kAlwaysUpdateField is true. diff --git a/runtime/runtime_options.def b/runtime/runtime_options.def index cfc681f23f..3d23e20e1e 100644 --- a/runtime/runtime_options.def +++ b/runtime/runtime_options.def @@ -142,4 +142,6 @@ RUNTIME_OPTIONS_KEY (void (*)(int32_t status), \                                                                            // Runtime::Abort.  RUNTIME_OPTIONS_KEY (void (*)(),          HookAbort,                      nullptr) +RUNTIME_OPTIONS_KEY (bool,                SlowDebug,                      false) +  #undef RUNTIME_OPTIONS_KEY diff --git a/runtime/vdex_file.h b/runtime/vdex_file.h index ece5491472..93d282b9bb 100644 --- a/runtime/vdex_file.h +++ b/runtime/vdex_file.h @@ -65,7 +65,8 @@ class VdexFile {     private:      static constexpr uint8_t kVdexMagic[] = { 'v', 'd', 'e', 'x' }; -    static constexpr uint8_t kVdexVersion[] = { '0', '0', '5', '\0' };  // access flags +    // Last update: Disable in-place vdex update +    static constexpr uint8_t kVdexVersion[] = { '0', '0', '6', '\0' };      uint8_t magic_[4];      uint8_t version_[4]; diff --git a/test/004-NativeAllocations/src/Main.java b/test/004-NativeAllocations/src-art/Main.java index 8712755125..8712755125 100644 --- a/test/004-NativeAllocations/src/Main.java +++ b/test/004-NativeAllocations/src-art/Main.java diff --git a/test/138-duplicate-classes-check/src/A.java b/test/138-duplicate-classes-check/src-art/A.java index e1773e5bc6..e1773e5bc6 100644 --- a/test/138-duplicate-classes-check/src/A.java +++ b/test/138-duplicate-classes-check/src-art/A.java diff --git a/test/138-duplicate-classes-check/src/Main.java b/test/138-duplicate-classes-check/src-art/Main.java index b32f0bcc46..b32f0bcc46 100644 --- a/test/138-duplicate-classes-check/src/Main.java +++ b/test/138-duplicate-classes-check/src-art/Main.java diff --git a/test/146-bad-interface/src/Main.java b/test/146-bad-interface/src-art/Main.java index 958ec7c3c4..958ec7c3c4 100644 --- a/test/146-bad-interface/src/Main.java +++ b/test/146-bad-interface/src-art/Main.java diff --git a/test/157-void-class/src/Main.java b/test/157-void-class/src-art/Main.java index 322b705f1d..322b705f1d 100644 --- a/test/157-void-class/src/Main.java +++ b/test/157-void-class/src-art/Main.java diff --git a/test/596-monitor-inflation/src/Main.java b/test/596-monitor-inflation/src-art/Main.java index d97c7667e9..d97c7667e9 100644 --- a/test/596-monitor-inflation/src/Main.java +++ b/test/596-monitor-inflation/src-art/Main.java diff --git a/test/612-jit-dex-cache/src/A.java b/test/612-jit-dex-cache/src-art/A.java index 415c712477..415c712477 100644 --- a/test/612-jit-dex-cache/src/A.java +++ b/test/612-jit-dex-cache/src-art/A.java diff --git a/test/612-jit-dex-cache/src/B.java b/test/612-jit-dex-cache/src-art/B.java index 46c878b572..46c878b572 100644 --- a/test/612-jit-dex-cache/src/B.java +++ b/test/612-jit-dex-cache/src-art/B.java diff --git a/test/612-jit-dex-cache/src/Main.java b/test/612-jit-dex-cache/src-art/Main.java index 89ebe09827..89ebe09827 100644 --- a/test/612-jit-dex-cache/src/Main.java +++ b/test/612-jit-dex-cache/src-art/Main.java diff --git a/test/613-inlining-dex-cache/src/B.java b/test/613-inlining-dex-cache/src-art/B.java index 6e7e55d430..6e7e55d430 100644 --- a/test/613-inlining-dex-cache/src/B.java +++ b/test/613-inlining-dex-cache/src-art/B.java diff --git a/test/613-inlining-dex-cache/src/Main.java b/test/613-inlining-dex-cache/src-art/Main.java index 31ab1d2bfa..31ab1d2bfa 100644 --- a/test/613-inlining-dex-cache/src/Main.java +++ b/test/613-inlining-dex-cache/src-art/Main.java diff --git a/test/909-attach-agent/src/Main.java b/test/909-attach-agent/src-art/Main.java index 25ebd57236..25ebd57236 100644 --- a/test/909-attach-agent/src/Main.java +++ b/test/909-attach-agent/src-art/Main.java diff --git a/test/912-classes/src/Main.java b/test/912-classes/src-art/Main.java index 395cf6fb98..395cf6fb98 100644 --- a/test/912-classes/src/Main.java +++ b/test/912-classes/src-art/Main.java diff --git a/test/912-classes/src/art/DexData.java b/test/912-classes/src-art/art/DexData.java index 7d150322ca..7d150322ca 100644 --- a/test/912-classes/src/art/DexData.java +++ b/test/912-classes/src-art/art/DexData.java diff --git a/test/912-classes/src/art/Main.java b/test/912-classes/src-art/art/Main.java index 8b01920638..8b01920638 100644 --- a/test/912-classes/src/art/Main.java +++ b/test/912-classes/src-art/art/Main.java diff --git a/test/912-classes/src/art/Test912.java b/test/912-classes/src-art/art/Test912.java index 9896eacfeb..9896eacfeb 100644 --- a/test/912-classes/src/art/Test912.java +++ b/test/912-classes/src-art/art/Test912.java diff --git a/test/912-classes/src/art/Test912Art.java b/test/912-classes/src-art/art/Test912Art.java index a1e7ff2005..a1e7ff2005 100644 --- a/test/912-classes/src/art/Test912Art.java +++ b/test/912-classes/src-art/art/Test912Art.java diff --git a/test/952-invoke-custom/src/Main.java b/test/952-invoke-custom/src-art/Main.java index 2abc3122fa..2abc3122fa 100644 --- a/test/952-invoke-custom/src/Main.java +++ b/test/952-invoke-custom/src-art/Main.java diff --git a/test/952-invoke-custom/src/TestDataInvokeCustomWithConcurrentThreads.java b/test/952-invoke-custom/src-art/TestDataInvokeCustomWithConcurrentThreads.java index 076acd71d2..076acd71d2 100644 --- a/test/952-invoke-custom/src/TestDataInvokeCustomWithConcurrentThreads.java +++ b/test/952-invoke-custom/src-art/TestDataInvokeCustomWithConcurrentThreads.java diff --git a/test/952-invoke-custom/src/TestDataLinkerMethodMinimalArguments.java b/test/952-invoke-custom/src-art/TestDataLinkerMethodMinimalArguments.java index 443a7af187..443a7af187 100644 --- a/test/952-invoke-custom/src/TestDataLinkerMethodMinimalArguments.java +++ b/test/952-invoke-custom/src-art/TestDataLinkerMethodMinimalArguments.java diff --git a/test/952-invoke-custom/src/TestDataLinkerMethodMultipleArgumentTypes.java b/test/952-invoke-custom/src-art/TestDataLinkerMethodMultipleArgumentTypes.java index b96e18486f..b96e18486f 100644 --- a/test/952-invoke-custom/src/TestDataLinkerMethodMultipleArgumentTypes.java +++ b/test/952-invoke-custom/src-art/TestDataLinkerMethodMultipleArgumentTypes.java diff --git a/test/958-methodhandle-stackframe/src/Main.java b/test/958-methodhandle-stackframe/src-art/Main.java index f739d47d08..f739d47d08 100644 --- a/test/958-methodhandle-stackframe/src/Main.java +++ b/test/958-methodhandle-stackframe/src-art/Main.java diff --git a/test/981-dedup-original-dex/src/Main.java b/test/981-dedup-original-dex/src-art/Main.java index f90c15ce8a..f90c15ce8a 100644 --- a/test/981-dedup-original-dex/src/Main.java +++ b/test/981-dedup-original-dex/src-art/Main.java diff --git a/test/981-dedup-original-dex/src/art/Redefinition.java b/test/981-dedup-original-dex/src-art/art/Redefinition.java index 56d2938a01..56d2938a01 100644 --- a/test/981-dedup-original-dex/src/art/Redefinition.java +++ b/test/981-dedup-original-dex/src-art/art/Redefinition.java diff --git a/test/981-dedup-original-dex/src/art/Test981.java b/test/981-dedup-original-dex/src-art/art/Test981.java index 3a97268ef9..3a97268ef9 100644 --- a/test/981-dedup-original-dex/src/art/Test981.java +++ b/test/981-dedup-original-dex/src-art/art/Test981.java diff --git a/test/988-method-trace/expected.txt b/test/988-method-trace/expected.txt index d3d9249b1f..30ad532f6c 100644 --- a/test/988-method-trace/expected.txt +++ b/test/988-method-trace/expected.txt @@ -1,4 +1,5 @@ -<= public static native void art.Trace.enableMethodTracing(java.lang.Class,java.lang.reflect.Method,java.lang.reflect.Method,java.lang.Thread) -> <null: null> +.<= public static native void art.Trace.enableTracing(java.lang.Class,java.lang.reflect.Method,java.lang.reflect.Method,java.lang.reflect.Method,java.lang.reflect.Method,java.lang.Thread) -> <null: null> +<= public static void art.Trace.enableMethodTracing(java.lang.Class,java.lang.reflect.Method,java.lang.reflect.Method,java.lang.Thread) -> <null: null>  => art.Test988$IterOp()  .=> public java.lang.Object()  .<= public java.lang.Object() -> <null: null> @@ -142,10 +143,10 @@ fibonacci(5)=5  ......=> private static native java.lang.Object java.lang.Throwable.nativeFillInStackTrace()  ......<= private static native java.lang.Object java.lang.Throwable.nativeFillInStackTrace() -> <class [Ljava.lang.Object;: <non-deterministic>>  .....<= public synchronized java.lang.Throwable java.lang.Throwable.fillInStackTrace() -> <class java.lang.Error: java.lang.Error: Bad argument: -19 < 0 -	at art.Test988.iter_fibonacci(Test988.java:207) -	at art.Test988$IterOp.applyAsInt(Test988.java:202) -	at art.Test988.doFibTest(Test988.java:295) -	at art.Test988.run(Test988.java:265) +	at art.Test988.iter_fibonacci(Test988.java:209) +	at art.Test988$IterOp.applyAsInt(Test988.java:204) +	at art.Test988.doFibTest(Test988.java:297) +	at art.Test988.run(Test988.java:267)  	at Main.main(Main.java:19)  >  ....<= public java.lang.Throwable(java.lang.String) -> <null: null> @@ -162,10 +163,10 @@ fibonacci(5)=5  ...<= private void java.util.ArrayList.ensureExplicitCapacity(int) -> <null: null>  ..<= private void java.util.ArrayList.ensureCapacityInternal(int) -> <null: null>  fibonacci(-19) -> java.lang.Error: Bad argument: -19 < 0 -	at art.Test988.iter_fibonacci(Test988.java:207) -	at art.Test988$IterOp.applyAsInt(Test988.java:202) -	at art.Test988.doFibTest(Test988.java:295) -	at art.Test988.run(Test988.java:265) +	at art.Test988.iter_fibonacci(Test988.java:209) +	at art.Test988$IterOp.applyAsInt(Test988.java:204) +	at art.Test988.doFibTest(Test988.java:297) +	at art.Test988.run(Test988.java:267)  	at Main.main(Main.java:19)  .<= public boolean java.util.ArrayList.add(java.lang.Object) -> <class java.lang.Boolean: true> @@ -243,10 +244,10 @@ fibonacci(-19) -> java.lang.Error: Bad argument: -19 < 0  ......=> private static native java.lang.Object java.lang.Throwable.nativeFillInStackTrace()  ......<= private static native java.lang.Object java.lang.Throwable.nativeFillInStackTrace() -> <class [Ljava.lang.Object;: <non-deterministic>>  .....<= public synchronized java.lang.Throwable java.lang.Throwable.fillInStackTrace() -> <class java.lang.Error: java.lang.Error: Bad argument: -19 < 0 -	at art.Test988.fibonacci(Test988.java:229) -	at art.Test988$RecurOp.applyAsInt(Test988.java:224) -	at art.Test988.doFibTest(Test988.java:295) -	at art.Test988.run(Test988.java:266) +	at art.Test988.fibonacci(Test988.java:231) +	at art.Test988$RecurOp.applyAsInt(Test988.java:226) +	at art.Test988.doFibTest(Test988.java:297) +	at art.Test988.run(Test988.java:268)  	at Main.main(Main.java:19)  >  ....<= public java.lang.Throwable(java.lang.String) -> <null: null> @@ -263,14 +264,14 @@ fibonacci(-19) -> java.lang.Error: Bad argument: -19 < 0  ...<= private void java.util.ArrayList.ensureExplicitCapacity(int) -> <null: null>  ..<= private void java.util.ArrayList.ensureCapacityInternal(int) -> <null: null>  fibonacci(-19) -> java.lang.Error: Bad argument: -19 < 0 -	at art.Test988.fibonacci(Test988.java:229) -	at art.Test988$RecurOp.applyAsInt(Test988.java:224) -	at art.Test988.doFibTest(Test988.java:295) -	at art.Test988.run(Test988.java:266) +	at art.Test988.fibonacci(Test988.java:231) +	at art.Test988$RecurOp.applyAsInt(Test988.java:226) +	at art.Test988.doFibTest(Test988.java:297) +	at art.Test988.run(Test988.java:268)  	at Main.main(Main.java:19)  .<= public boolean java.util.ArrayList.add(java.lang.Object) -> <class java.lang.Boolean: true>  <= public static void art.Test988.doFibTest(int,java.util.function.IntUnaryOperator) -> <null: null>  => public static native java.lang.Thread java.lang.Thread.currentThread()  <= public static native java.lang.Thread java.lang.Thread.currentThread() -> <class java.lang.Thread: <non-deterministic>> -=> public static native void art.Trace.disableMethodTracing(java.lang.Thread) +=> public static native void art.Trace.disableTracing(java.lang.Thread) diff --git a/test/988-method-trace/src/art/Test988.java b/test/988-method-trace/src/art/Test988.java index 37ff136b6c..6a45c0eaa2 100644 --- a/test/988-method-trace/src/art/Test988.java +++ b/test/988-method-trace/src/art/Test988.java @@ -194,7 +194,9 @@ public class Test988 {      }      private static List<Printable> results = new ArrayList<>(); -    private static int cnt = 1; +    // Starts with => enableMethodTracing +    //             .=> enableTracing +    private static int cnt = 2;      // Iterative version      static final class IterOp implements IntUnaryOperator { @@ -253,7 +255,7 @@ public class Test988 {      public static void run() throws Exception {          // call this here so it is linked. It doesn't actually do anything here.          loadAllClasses(); -        Trace.disableMethodTracing(Thread.currentThread()); +        Trace.disableTracing(Thread.currentThread());          Trace.enableMethodTracing(              Test988.class,              Test988.class.getDeclaredMethod("notifyMethodEntry", Object.class), @@ -265,7 +267,7 @@ public class Test988 {          doFibTest(-19, new IterOp());          doFibTest(-19, new RecurOp());          // Turn off method tracing so we don't have to deal with print internals. -        Trace.disableMethodTracing(Thread.currentThread()); +        Trace.disableTracing(Thread.currentThread());          printResults();      } diff --git a/test/988-method-trace/src/art/Trace.java b/test/988-method-trace/src/art/Trace.java index 3370996df3..9c27c9f69e 100644 --- a/test/988-method-trace/src/art/Trace.java +++ b/test/988-method-trace/src/art/Trace.java @@ -16,10 +16,34 @@  package art; +import java.lang.reflect.Field;  import java.lang.reflect.Method;  public class Trace { -  public static native void enableMethodTracing( -      Class<?> methodClass, Method entryMethod, Method exitMethod, Thread thr); -  public static native void disableMethodTracing(Thread thr); +  public static native void enableTracing(Class<?> methodClass, +                                          Method entryMethod, +                                          Method exitMethod, +                                          Method fieldAccess, +                                          Method fieldModify, +                                          Thread thr); +  public static native void disableTracing(Thread thr); + +  public static void enableFieldTracing(Class<?> methodClass, +                                        Method fieldAccess, +                                        Method fieldModify, +                                        Thread thr) { +    enableTracing(methodClass, null, null, fieldAccess, fieldModify, thr); +  } + +  public static void enableMethodTracing(Class<?> methodClass, +                                         Method entryMethod, +                                         Method exitMethod, +                                         Thread thr) { +    enableTracing(methodClass, entryMethod, exitMethod, null, null, thr); +  } + +  public static native void watchFieldAccess(Field f); +  public static native void watchFieldModification(Field f); +  public static native void watchAllFieldAccesses(); +  public static native void watchAllFieldModifications();  } diff --git a/test/989-method-trace-throw/src/art/Test989.java b/test/989-method-trace-throw/src/art/Test989.java index 18421bd08b..4feb29cf9d 100644 --- a/test/989-method-trace-throw/src/art/Test989.java +++ b/test/989-method-trace-throw/src/art/Test989.java @@ -56,7 +56,7 @@ public class Test989 {    // to an infinite loop on the RI.    private static void disableTraceForRI() {      if (!System.getProperty("java.vm.name").equals("Dalvik")) { -      Trace.disableMethodTracing(Thread.currentThread()); +      Trace.disableTracing(Thread.currentThread());      }    } @@ -158,7 +158,7 @@ public class Test989 {    private static void maybeDisableTracing() throws Exception {      if (DISABLE_TRACING) { -      Trace.disableMethodTracing(Thread.currentThread()); +      Trace.disableTracing(Thread.currentThread());      }    } @@ -179,7 +179,7 @@ public class Test989 {    }    private static void setEntry(MethodTracer type) throws Exception {      if (DISABLE_TRACING || !System.getProperty("java.vm.name").equals("Dalvik")) { -      Trace.disableMethodTracing(Thread.currentThread()); +      Trace.disableTracing(Thread.currentThread());        setupTracing();      }      currentTracer = type; @@ -274,7 +274,7 @@ public class Test989 {      maybeDisableTracing();      System.out.println("Finished!"); -    Trace.disableMethodTracing(Thread.currentThread()); +    Trace.disableTracing(Thread.currentThread());    }    private static final class throwAClass implements MyRunnable { diff --git a/test/989-method-trace-throw/src/art/Trace.java b/test/989-method-trace-throw/src/art/Trace.java index 3370996df3..9c27c9f69e 100644 --- a/test/989-method-trace-throw/src/art/Trace.java +++ b/test/989-method-trace-throw/src/art/Trace.java @@ -16,10 +16,34 @@  package art; +import java.lang.reflect.Field;  import java.lang.reflect.Method;  public class Trace { -  public static native void enableMethodTracing( -      Class<?> methodClass, Method entryMethod, Method exitMethod, Thread thr); -  public static native void disableMethodTracing(Thread thr); +  public static native void enableTracing(Class<?> methodClass, +                                          Method entryMethod, +                                          Method exitMethod, +                                          Method fieldAccess, +                                          Method fieldModify, +                                          Thread thr); +  public static native void disableTracing(Thread thr); + +  public static void enableFieldTracing(Class<?> methodClass, +                                        Method fieldAccess, +                                        Method fieldModify, +                                        Thread thr) { +    enableTracing(methodClass, null, null, fieldAccess, fieldModify, thr); +  } + +  public static void enableMethodTracing(Class<?> methodClass, +                                         Method entryMethod, +                                         Method exitMethod, +                                         Thread thr) { +    enableTracing(methodClass, entryMethod, exitMethod, null, null, thr); +  } + +  public static native void watchFieldAccess(Field f); +  public static native void watchFieldModification(Field f); +  public static native void watchAllFieldAccesses(); +  public static native void watchAllFieldModifications();  } diff --git a/test/990-field-trace/expected.txt b/test/990-field-trace/expected.txt new file mode 100644 index 0000000000..cceb008383 --- /dev/null +++ b/test/990-field-trace/expected.txt @@ -0,0 +1,52 @@ +MODIFY of int art.Test990$TestClass1.xyz on object of type: class art.Test990$TestClass1 in method public art.Test990$TestClass1(int,java.lang.Object). New value: 1 (type: class java.lang.Integer) +MODIFY of java.lang.Object art.Test990$TestClass1.abc on object of type: class art.Test990$TestClass1 in method public art.Test990$TestClass1(int,java.lang.Object). New value: tc1 (type: class java.lang.String) +MODIFY of static long art.Test990$TestClass2.TOTAL on object of type: null in method art.Test990$TestClass2(). New value: 0 (type: class java.lang.Long) +MODIFY of int art.Test990$TestClass1.xyz on object of type: class art.Test990$TestClass2 in method public art.Test990$TestClass1(int,java.lang.Object). New value: 1337 (type: class java.lang.Integer) +MODIFY of java.lang.Object art.Test990$TestClass1.abc on object of type: class art.Test990$TestClass2 in method public art.Test990$TestClass1(int,java.lang.Object). New value: TESTING (type: class java.lang.String) +MODIFY of long art.Test990$TestClass2.baz on object of type: class art.Test990$TestClass2 in method public art.Test990$TestClass2(long). New value: 2 (type: class java.lang.Long) +MODIFY of int art.Test990$TestClass1.xyz on object of type: class art.Test990$TestClass1 in method public art.Test990$TestClass1(int,java.lang.Object). New value: 3 (type: class java.lang.Integer) +MODIFY of java.lang.Object art.Test990$TestClass1.abc on object of type: class art.Test990$TestClass1 in method public art.Test990$TestClass1(int,java.lang.Object). New value: TestClass1 { abc: "tc1", xyz: 1, foobar: 0 } (type: class art.Test990$TestClass1) +MODIFY of int art.Test990$TestClass1.xyz on object of type: class art.Test990$TestClass1 in method public art.Test990$TestClass1(int,java.lang.Object). New value: 4 (type: class java.lang.Integer) +MODIFY of java.lang.Object art.Test990$TestClass1.abc on object of type: class art.Test990$TestClass1 in method public art.Test990$TestClass1(int,java.lang.Object). New value: TestClass1 { abc: "TESTING", xyz: 1337, foobar: 0 } (type: class art.Test990$TestClass2) +ACCESS of int art.Test990$TestClass1.xyz on object of type class art.Test990$TestClass1 in method public void art.Test990$TestClass1.tweak(int) +ACCESS of int art.Test990$TestClass1.foobar on object of type class art.Test990$TestClass1 in method public void art.Test990$TestClass1.tweak(int) +MODIFY of int art.Test990$TestClass1.foobar on object of type: class art.Test990$TestClass1 in method public void art.Test990$TestClass1.tweak(int). New value: 1 (type: class java.lang.Integer) +ACCESS of int art.Test990$TestClass1.xyz on object of type class art.Test990$TestClass1 in method public void art.Test990$TestClass1.tweak(int) +ACCESS of int art.Test990$TestClass1.foobar on object of type class art.Test990$TestClass1 in method public void art.Test990$TestClass1.tweak(int) +MODIFY of int art.Test990$TestClass1.foobar on object of type: class art.Test990$TestClass1 in method public void art.Test990$TestClass1.tweak(int). New value: 2 (type: class java.lang.Integer) +ACCESS of static long art.Test990$TestClass2.TOTAL on object of type null in method public void art.Test990$TestClass2.tweak(int) +MODIFY of static long art.Test990$TestClass2.TOTAL on object of type: null in method public void art.Test990$TestClass2.tweak(int). New value: 1 (type: class java.lang.Long) +ACCESS of int art.Test990$TestClass1.xyz on object of type class art.Test990$TestClass2 in method public void art.Test990$TestClass1.tweak(int) +ACCESS of long art.Test990$TestClass2.baz on object of type class art.Test990$TestClass2 in method public void art.Test990$TestClass2.tweak(int) +MODIFY of long art.Test990$TestClass2.baz on object of type: class art.Test990$TestClass2 in method public void art.Test990$TestClass2.tweak(int). New value: 3 (type: class java.lang.Long) +ACCESS of static long art.Test990$TestClass2.TOTAL on object of type null in method public void art.Test990$TestClass2.tweak(int) +MODIFY of static long art.Test990$TestClass2.TOTAL on object of type: null in method public void art.Test990$TestClass2.tweak(int). New value: 2 (type: class java.lang.Long) +ACCESS of int art.Test990$TestClass1.xyz on object of type class art.Test990$TestClass2 in method public void art.Test990$TestClass1.tweak(int) +ACCESS of int art.Test990$TestClass1.foobar on object of type class art.Test990$TestClass2 in method public void art.Test990$TestClass1.tweak(int) +MODIFY of int art.Test990$TestClass1.foobar on object of type: class art.Test990$TestClass2 in method public void art.Test990$TestClass1.tweak(int). New value: 1 (type: class java.lang.Integer) +ACCESS of long art.Test990$TestClass2.baz on object of type class art.Test990$TestClass2 in method public void art.Test990$TestClass2.tweak(int) +MODIFY of long art.Test990$TestClass2.baz on object of type: class art.Test990$TestClass2 in method public void art.Test990$TestClass2.tweak(int). New value: 4 (type: class java.lang.Long) +ACCESS of static long art.Test990$TestClass2.TOTAL on object of type null in method public void art.Test990$TestClass2.tweak(int) +MODIFY of static long art.Test990$TestClass2.TOTAL on object of type: null in method public void art.Test990$TestClass2.tweak(int). New value: 3 (type: class java.lang.Long) +ACCESS of int art.Test990$TestClass1.xyz on object of type class art.Test990$TestClass2 in method public void art.Test990$TestClass1.tweak(int) +ACCESS of long art.Test990$TestClass2.baz on object of type class art.Test990$TestClass2 in method public void art.Test990$TestClass2.tweak(int) +MODIFY of long art.Test990$TestClass2.baz on object of type: class art.Test990$TestClass2 in method public void art.Test990$TestClass2.tweak(int). New value: 5 (type: class java.lang.Long) +ACCESS of static long art.Test990$TestClass2.TOTAL on object of type null in method public void art.Test990$TestClass2.tweak(int) +MODIFY of static long art.Test990$TestClass2.TOTAL on object of type: null in method public void art.Test990$TestClass2.tweak(int). New value: 4 (type: class java.lang.Long) +ACCESS of int art.Test990$TestClass1.xyz on object of type class art.Test990$TestClass2 in method public void art.Test990$TestClass1.tweak(int) +ACCESS of long art.Test990$TestClass2.baz on object of type class art.Test990$TestClass2 in method public void art.Test990$TestClass2.tweak(int) +MODIFY of long art.Test990$TestClass2.baz on object of type: class art.Test990$TestClass2 in method public void art.Test990$TestClass2.tweak(int). New value: 6 (type: class java.lang.Long) +ACCESS of int art.Test990$TestClass1.foobar on object of type class art.Test990$TestClass1 in method public static void art.Test990.run() throws java.lang.Exception +ACCESS of int art.Test990$TestClass1.xyz on object of type class art.Test990$TestClass1 in method public void art.Test990$TestClass1.tweak(int) +ACCESS of long art.Test990$TestClass2.baz on object of type class art.Test990$TestClass2 in method public static void art.Test990.run() throws java.lang.Exception +ACCESS of int art.Test990$TestClass1.xyz on object of type class art.Test990$TestClass1 in method public void art.Test990$TestClass1.tweak(int) +ACCESS of static long art.Test990$TestClass2.TOTAL on object of type null in method public static void art.Test990.run() throws java.lang.Exception +ACCESS of int art.Test990$TestClass1.xyz on object of type class art.Test990$TestClass1 in method public void art.Test990$TestClass1.tweak(int) +ACCESS of int art.Test990$TestClass1.foobar on object of type class art.Test990$TestClass1 in method public void art.Test990$TestClass1.tweak(int) +MODIFY of int art.Test990$TestClass1.foobar on object of type: class art.Test990$TestClass1 in method public void art.Test990$TestClass1.tweak(int). New value: 1 (type: class java.lang.Integer) +ACCESS of static long art.Test990$TestClass2.TOTAL on object of type null in method public static void art.Test990.run() throws java.lang.Exception +ACCESS of static long art.Test990$TestClass2.TOTAL on object of type null in method public void art.Test990$TestClass2.tweak(int) +MODIFY of static long art.Test990$TestClass2.TOTAL on object of type: null in method public void art.Test990$TestClass2.tweak(int). New value: 5 (type: class java.lang.Long) +ACCESS of int art.Test990$TestClass1.xyz on object of type class art.Test990$TestClass2 in method public void art.Test990$TestClass1.tweak(int) +ACCESS of long art.Test990$TestClass2.baz on object of type class art.Test990$TestClass2 in method public void art.Test990$TestClass2.tweak(int) +MODIFY of long art.Test990$TestClass2.baz on object of type: class art.Test990$TestClass2 in method public void art.Test990$TestClass2.tweak(int). New value: 7 (type: class java.lang.Long) diff --git a/test/990-field-trace/info.txt b/test/990-field-trace/info.txt new file mode 100644 index 0000000000..67d164e3bf --- /dev/null +++ b/test/990-field-trace/info.txt @@ -0,0 +1 @@ +Tests field access and modification watches in JVMTI diff --git a/test/990-field-trace/run b/test/990-field-trace/run new file mode 100755 index 0000000000..51875a7e86 --- /dev/null +++ b/test/990-field-trace/run @@ -0,0 +1,18 @@ +#!/bin/bash +# +# Copyright 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. + +# Ask for stack traces to be dumped to a file rather than to stdout. +./default-run "$@" --jvmti diff --git a/test/990-field-trace/src/Main.java b/test/990-field-trace/src/Main.java new file mode 100644 index 0000000000..cb14f5d511 --- /dev/null +++ b/test/990-field-trace/src/Main.java @@ -0,0 +1,21 @@ +/* + * 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. + */ + +public class Main { +  public static void main(String[] args) throws Exception { +    art.Test990.run(); +  } +} diff --git a/test/990-field-trace/src/art/Test990.java b/test/990-field-trace/src/art/Test990.java new file mode 100644 index 0000000000..d766876412 --- /dev/null +++ b/test/990-field-trace/src/art/Test990.java @@ -0,0 +1,232 @@ +/* + * 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. + */ + +package art; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.lang.reflect.Executable; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Vector; +import java.util.function.Function; + +public class Test990 { + +  // Fields of these classes are accessed/modified differently in the RI and ART so we ignore them. +  static Collection<Class<?>> IGNORED_CLASSES = Arrays.asList(new Class<?>[] { +    ClassLoader.class, +    Vector.class, +  }); + +  static interface Printable { public void Print(); } + +  static final class FieldWrite implements Printable { +    private Executable method; +    private Object target; +    private Field f; +    private String initialValue; +    private Class<?> initialValueType; + +    public FieldWrite(Executable method, Object target, Field f, Object v) { +      this.method = method; +      this.target = target; +      this.f = f; +      this.initialValue =  genericToString(v); +      this.initialValueType = v != null ? v.getClass() : null; +    } + +    @Override +    public void Print() { +        System.out.println("MODIFY of " + f + " on object of" + +            " type: " + (target == null ? null : target.getClass()) + +            " in method " + method + +            ". New value: " + initialValue + " (type: " + initialValueType + ")"); +    } +  } + +  static final class FieldRead implements Printable { +    private Executable method; +    private Object target; +    private Field f; + +    public FieldRead(Executable method, Object target, Field f) { +      this.method = method; +      this.target = target; +      this.f = f; +    } + +    @Override +    public void Print() { +        System.out.println("ACCESS of " + f + " on object of" + +            " type " + (target == null ? null : target.getClass()) + +            " in method " + method); +    } +  } + +  private static String genericToString(Object val) { +    if (val == null) { +      return "null"; +    } else if (val.getClass().isArray()) { +      return arrayToString(val); +    } else if (val instanceof Throwable) { +      StringWriter w = new StringWriter(); +      ((Throwable) val).printStackTrace(new PrintWriter(w)); +      return w.toString(); +    } else { +      return val.toString(); +    } +  } + +  private static String charArrayToString(char[] src) { +    String[] res = new String[src.length]; +    for (int i = 0; i < src.length; i++) { +      if (Character.isISOControl(src[i])) { +        res[i] = Character.getName(src[i]); +      } else { +        res[i] = Character.toString(src[i]); +      } +    } +    return Arrays.toString(res); +  } + +  private static String arrayToString(Object val) { +    Class<?> klass = val.getClass(); +    if ((new Object[0]).getClass().isAssignableFrom(klass)) { +      return Arrays.toString( +          Arrays.stream((Object[])val).map(new Function<Object, String>() { +            public String apply(Object o) { +              return genericToString(o); +            } +          }).toArray()); +    } else if ((new byte[0]).getClass().isAssignableFrom(klass)) { +      return Arrays.toString((byte[])val); +    } else if ((new char[0]).getClass().isAssignableFrom(klass)) { +      return charArrayToString((char[])val); +    } else if ((new short[0]).getClass().isAssignableFrom(klass)) { +      return Arrays.toString((short[])val); +    } else if ((new int[0]).getClass().isAssignableFrom(klass)) { +      return Arrays.toString((int[])val); +    } else if ((new long[0]).getClass().isAssignableFrom(klass)) { +      return Arrays.toString((long[])val); +    } else if ((new float[0]).getClass().isAssignableFrom(klass)) { +      return Arrays.toString((float[])val); +    } else if ((new double[0]).getClass().isAssignableFrom(klass)) { +      return Arrays.toString((double[])val); +    } else { +      throw new Error("Unknown type " + klass); +    } +  } + +  private static List<Printable> results = new ArrayList<>(); + +  public static void notifyFieldModify( +      Executable m, long location, Class<?> f_klass, Object target, Field f, Object value) { +    if (IGNORED_CLASSES.contains(f_klass)) { +      return; +    } +    results.add(new FieldWrite(m, target, f, value)); +  } + +  public static void notifyFieldAccess( +      Executable m, long location, Class<?> f_klass, Object target, Field f) { +    if (IGNORED_CLASSES.contains(f_klass)) { +      return; +    } +    results.add(new FieldRead(m, target, f)); +  } + +  static class TestClass1 { +    Object abc; +    int xyz; +    int foobar; +    public TestClass1(int xyz, Object abc) { +      this.xyz = xyz; +      this.abc = abc; +    } + +    public void tweak(int def) { +      if (def == xyz) { +        foobar++; +      } +    } +    public String toString() { +      return "TestClass1 { abc: \"" + genericToString(abc) + "\", xyz: " + xyz +          + ", foobar: " + foobar + " }"; +    } +  } + +  static class TestClass2 extends TestClass1 { +    static long TOTAL = 0; +    long baz; +    public TestClass2(long baz) { +      super(1337, "TESTING"); +      this.baz = baz; +    } + +    public void tweak(int def) { +      TOTAL++; +      super.tweak(def); +      baz++; +    } + +    public String toString() { +      return "TestClass2 { super: \"%s\", TOTAL: %d, baz: %d }".format( +          super.toString(), TOTAL, baz); +    } +  } + + +  public static void run() throws Exception { +      Trace.disableTracing(Thread.currentThread()); +      Trace.enableFieldTracing( +          Test990.class, +          Test990.class.getDeclaredMethod("notifyFieldAccess", +            Executable.class, Long.TYPE, Class.class, Object.class, Field.class), +          Test990.class.getDeclaredMethod("notifyFieldModify", +            Executable.class, Long.TYPE, Class.class, Object.class, Field.class, Object.class), +          Thread.currentThread()); +      Trace.watchAllFieldAccesses(); +      Trace.watchAllFieldModifications(); +      TestClass1 t1 = new TestClass1(1, "tc1"); +      TestClass1 t2 = new TestClass2(2); +      TestClass1 t3 = new TestClass1(3, t1); +      TestClass1 t4 = new TestClass1(4, t2); +      t1.tweak(1); +      t1.tweak(1); +      t2.tweak(12); +      t2.tweak(1337); +      t2.tweak(12); +      t2.tweak(1338); +      t1.tweak(t3.foobar); +      t4.tweak((int)((TestClass2)t2).baz); +      t4.tweak((int)TestClass2.TOTAL); +      t2.tweak((int)TestClass2.TOTAL); + +      // Turn off tracing so we don't have to deal with print internals. +      Trace.disableTracing(Thread.currentThread()); +      printResults(); +  } + +  public static void printResults() { +    for (Printable p : results) { +      p.Print(); +    } +  } +} diff --git a/test/990-field-trace/src/art/Trace.java b/test/990-field-trace/src/art/Trace.java new file mode 100644 index 0000000000..9c27c9f69e --- /dev/null +++ b/test/990-field-trace/src/art/Trace.java @@ -0,0 +1,49 @@ +/* + * 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. + */ + +package art; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +public class Trace { +  public static native void enableTracing(Class<?> methodClass, +                                          Method entryMethod, +                                          Method exitMethod, +                                          Method fieldAccess, +                                          Method fieldModify, +                                          Thread thr); +  public static native void disableTracing(Thread thr); + +  public static void enableFieldTracing(Class<?> methodClass, +                                        Method fieldAccess, +                                        Method fieldModify, +                                        Thread thr) { +    enableTracing(methodClass, null, null, fieldAccess, fieldModify, thr); +  } + +  public static void enableMethodTracing(Class<?> methodClass, +                                         Method entryMethod, +                                         Method exitMethod, +                                         Thread thr) { +    enableTracing(methodClass, entryMethod, exitMethod, null, null, thr); +  } + +  public static native void watchFieldAccess(Field f); +  public static native void watchFieldModification(Field f); +  public static native void watchAllFieldAccesses(); +  public static native void watchAllFieldModifications(); +} diff --git a/test/991-field-trace-2/expected.txt b/test/991-field-trace-2/expected.txt new file mode 100644 index 0000000000..8da8ffdae6 --- /dev/null +++ b/test/991-field-trace-2/expected.txt @@ -0,0 +1,118 @@ +Test is class art.Test991$DoNothingFieldTracer & class art.Test991$JavaReadWrite +Initial state: xyz = 0 +FieldTracer: class art.Test991$DoNothingFieldTracer +	ACCESS of public int art.Test991$TestClass1.xyz on object of type: class art.Test991$TestClass1 in method public void art.Test991$JavaReadWrite.accept(art.Test991$TestClass1) +normal read: xyz = 0 +FieldTracer: class art.Test991$DoNothingFieldTracer +	MODIFY of public int art.Test991$TestClass1.xyz on object of type: class art.Test991$TestClass1 in method public void art.Test991$JavaReadWrite.accept(art.Test991$TestClass1). New value: 1 (type: class java.lang.Integer) +Final state: xyz = 1 +Test is class art.Test991$ThrowReadFieldTracer & class art.Test991$JavaReadWrite +Initial state: xyz = 0 +FieldTracer: class art.Test991$ThrowReadFieldTracer +	ACCESS of public int art.Test991$TestClass1.xyz on object of type: class art.Test991$TestClass1 in method public void art.Test991$JavaReadWrite.accept(art.Test991$TestClass1) +Caught error. art.Test991$TestError: Throwing error during access +Final state: xyz = 0 +Test is class art.Test991$ThrowWriteFieldTracer & class art.Test991$JavaReadWrite +Initial state: xyz = 0 +FieldTracer: class art.Test991$ThrowWriteFieldTracer +	ACCESS of public int art.Test991$TestClass1.xyz on object of type: class art.Test991$TestClass1 in method public void art.Test991$JavaReadWrite.accept(art.Test991$TestClass1) +normal read: xyz = 0 +FieldTracer: class art.Test991$ThrowWriteFieldTracer +	MODIFY of public int art.Test991$TestClass1.xyz on object of type: class art.Test991$TestClass1 in method public void art.Test991$JavaReadWrite.accept(art.Test991$TestClass1). New value: 1 (type: class java.lang.Integer) +Caught error. art.Test991$TestError: Throwing error during modify +Final state: xyz = 0 +Test is class art.Test991$ModifyDuringReadFieldTracer & class art.Test991$JavaReadWrite +Initial state: xyz = 0 +FieldTracer: class art.Test991$ModifyDuringReadFieldTracer +	ACCESS of public int art.Test991$TestClass1.xyz on object of type: class art.Test991$TestClass1 in method public void art.Test991$JavaReadWrite.accept(art.Test991$TestClass1) +normal read: xyz = 20 +FieldTracer: class art.Test991$ModifyDuringReadFieldTracer +	MODIFY of public int art.Test991$TestClass1.xyz on object of type: class art.Test991$TestClass1 in method public void art.Test991$JavaReadWrite.accept(art.Test991$TestClass1). New value: 21 (type: class java.lang.Integer) +Final state: xyz = 21 +Test is class art.Test991$ModifyDuringWriteFieldTracer & class art.Test991$JavaReadWrite +Initial state: xyz = 0 +FieldTracer: class art.Test991$ModifyDuringWriteFieldTracer +	ACCESS of public int art.Test991$TestClass1.xyz on object of type: class art.Test991$TestClass1 in method public void art.Test991$JavaReadWrite.accept(art.Test991$TestClass1) +normal read: xyz = 0 +FieldTracer: class art.Test991$ModifyDuringWriteFieldTracer +	MODIFY of public int art.Test991$TestClass1.xyz on object of type: class art.Test991$TestClass1 in method public void art.Test991$JavaReadWrite.accept(art.Test991$TestClass1). New value: 1 (type: class java.lang.Integer) +Final state: xyz = 1 +Test is class art.Test991$ModifyDuringReadAndWriteFieldTracer & class art.Test991$JavaReadWrite +Initial state: xyz = 0 +FieldTracer: class art.Test991$ModifyDuringReadAndWriteFieldTracer +	ACCESS of public int art.Test991$TestClass1.xyz on object of type: class art.Test991$TestClass1 in method public void art.Test991$JavaReadWrite.accept(art.Test991$TestClass1) +normal read: xyz = 10 +FieldTracer: class art.Test991$ModifyDuringReadAndWriteFieldTracer +	MODIFY of public int art.Test991$TestClass1.xyz on object of type: class art.Test991$TestClass1 in method public void art.Test991$JavaReadWrite.accept(art.Test991$TestClass1). New value: 11 (type: class java.lang.Integer) +Final state: xyz = 11 +Test is class art.Test991$DoNothingFieldTracer & class art.Test991$ReflectiveReadWrite +Initial state: xyz = 0 +reflective read: xyz = 0 +Final state: xyz = 1 +Test is class art.Test991$ThrowReadFieldTracer & class art.Test991$ReflectiveReadWrite +Initial state: xyz = 0 +reflective read: xyz = 0 +Final state: xyz = 1 +Test is class art.Test991$ThrowWriteFieldTracer & class art.Test991$ReflectiveReadWrite +Initial state: xyz = 0 +reflective read: xyz = 0 +Final state: xyz = 1 +Test is class art.Test991$ModifyDuringReadFieldTracer & class art.Test991$ReflectiveReadWrite +Initial state: xyz = 0 +reflective read: xyz = 0 +Final state: xyz = 1 +Test is class art.Test991$ModifyDuringWriteFieldTracer & class art.Test991$ReflectiveReadWrite +Initial state: xyz = 0 +reflective read: xyz = 0 +Final state: xyz = 1 +Test is class art.Test991$ModifyDuringReadAndWriteFieldTracer & class art.Test991$ReflectiveReadWrite +Initial state: xyz = 0 +reflective read: xyz = 0 +Final state: xyz = 1 +Test is class art.Test991$DoNothingFieldTracer & class art.Test991$NativeReadWrite +Initial state: xyz = 0 +FieldTracer: class art.Test991$DoNothingFieldTracer +	ACCESS of public int art.Test991$TestClass1.xyz on object of type: class art.Test991$TestClass1 in method public static native void art.Test991.doNativeReadWrite(art.Test991$TestClass1) +native read: xyz = 0 +FieldTracer: class art.Test991$DoNothingFieldTracer +	MODIFY of public int art.Test991$TestClass1.xyz on object of type: class art.Test991$TestClass1 in method public static native void art.Test991.doNativeReadWrite(art.Test991$TestClass1). New value: 1 (type: class java.lang.Integer) +Final state: xyz = 1 +Test is class art.Test991$ThrowReadFieldTracer & class art.Test991$NativeReadWrite +Initial state: xyz = 0 +FieldTracer: class art.Test991$ThrowReadFieldTracer +	ACCESS of public int art.Test991$TestClass1.xyz on object of type: class art.Test991$TestClass1 in method public static native void art.Test991.doNativeReadWrite(art.Test991$TestClass1) +Caught error. art.Test991$TestError: Throwing error during access +Final state: xyz = 0 +Test is class art.Test991$ThrowWriteFieldTracer & class art.Test991$NativeReadWrite +Initial state: xyz = 0 +FieldTracer: class art.Test991$ThrowWriteFieldTracer +	ACCESS of public int art.Test991$TestClass1.xyz on object of type: class art.Test991$TestClass1 in method public static native void art.Test991.doNativeReadWrite(art.Test991$TestClass1) +native read: xyz = 0 +FieldTracer: class art.Test991$ThrowWriteFieldTracer +	MODIFY of public int art.Test991$TestClass1.xyz on object of type: class art.Test991$TestClass1 in method public static native void art.Test991.doNativeReadWrite(art.Test991$TestClass1). New value: 1 (type: class java.lang.Integer) +Caught error. art.Test991$TestError: Throwing error during modify +Final state: xyz = 1 +Test is class art.Test991$ModifyDuringReadFieldTracer & class art.Test991$NativeReadWrite +Initial state: xyz = 0 +FieldTracer: class art.Test991$ModifyDuringReadFieldTracer +	ACCESS of public int art.Test991$TestClass1.xyz on object of type: class art.Test991$TestClass1 in method public static native void art.Test991.doNativeReadWrite(art.Test991$TestClass1) +native read: xyz = 20 +FieldTracer: class art.Test991$ModifyDuringReadFieldTracer +	MODIFY of public int art.Test991$TestClass1.xyz on object of type: class art.Test991$TestClass1 in method public static native void art.Test991.doNativeReadWrite(art.Test991$TestClass1). New value: 21 (type: class java.lang.Integer) +Final state: xyz = 21 +Test is class art.Test991$ModifyDuringWriteFieldTracer & class art.Test991$NativeReadWrite +Initial state: xyz = 0 +FieldTracer: class art.Test991$ModifyDuringWriteFieldTracer +	ACCESS of public int art.Test991$TestClass1.xyz on object of type: class art.Test991$TestClass1 in method public static native void art.Test991.doNativeReadWrite(art.Test991$TestClass1) +native read: xyz = 0 +FieldTracer: class art.Test991$ModifyDuringWriteFieldTracer +	MODIFY of public int art.Test991$TestClass1.xyz on object of type: class art.Test991$TestClass1 in method public static native void art.Test991.doNativeReadWrite(art.Test991$TestClass1). New value: 1 (type: class java.lang.Integer) +Final state: xyz = 1 +Test is class art.Test991$ModifyDuringReadAndWriteFieldTracer & class art.Test991$NativeReadWrite +Initial state: xyz = 0 +FieldTracer: class art.Test991$ModifyDuringReadAndWriteFieldTracer +	ACCESS of public int art.Test991$TestClass1.xyz on object of type: class art.Test991$TestClass1 in method public static native void art.Test991.doNativeReadWrite(art.Test991$TestClass1) +native read: xyz = 10 +FieldTracer: class art.Test991$ModifyDuringReadAndWriteFieldTracer +	MODIFY of public int art.Test991$TestClass1.xyz on object of type: class art.Test991$TestClass1 in method public static native void art.Test991.doNativeReadWrite(art.Test991$TestClass1). New value: 11 (type: class java.lang.Integer) +Final state: xyz = 11 diff --git a/test/991-field-trace-2/field_trace.cc b/test/991-field-trace-2/field_trace.cc new file mode 100644 index 0000000000..823f9fd9c8 --- /dev/null +++ b/test/991-field-trace-2/field_trace.cc @@ -0,0 +1,59 @@ +/* + * 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 <stdio.h> + +#include "android-base/macros.h" + +#include "jni.h" +#include "jvmti.h" +#include "scoped_local_ref.h" + +// Test infrastructure +#include "jni_helper.h" +#include "jvmti_helper.h" +#include "test_env.h" + +namespace art { +namespace Test991FieldTrace { + +extern "C" JNIEXPORT void JNICALL Java_art_Test991_doNativeReadWrite( +    JNIEnv* env, jclass klass, jobject testclass) { +  CHECK(testclass != nullptr); +  ScopedLocalRef<jclass> testclass_klass(env, env->GetObjectClass(testclass)); +  jmethodID notifyMethod = env->GetStaticMethodID(klass, "doPrintNativeNotification", "(I)V"); +  if (env->ExceptionCheck()) { +    return; +  } +  jfieldID xyz_field = env->GetFieldID(testclass_klass.get(), "xyz", "I"); +  if (env->ExceptionCheck()) { +    return; +  } +  jint val = env->GetIntField(testclass, xyz_field); +  if (env->ExceptionCheck()) { +    return; +  } +  env->CallStaticVoidMethod(klass, notifyMethod, val); +  if (env->ExceptionCheck()) { +    return; +  } +  val += 1; +  env->SetIntField(testclass, xyz_field, val); +} + +}  // namespace Test991FieldTrace +}  // namespace art + diff --git a/test/991-field-trace-2/info.txt b/test/991-field-trace-2/info.txt new file mode 100644 index 0000000000..c2a1b68a56 --- /dev/null +++ b/test/991-field-trace-2/info.txt @@ -0,0 +1,5 @@ +Tests field access and modification watches in JVMTI. + +This test specifically examines how the runtime responds to exceptions occurring +while handling these events. It also verifies the situations in which these +events are sent. diff --git a/test/991-field-trace-2/run b/test/991-field-trace-2/run new file mode 100755 index 0000000000..51875a7e86 --- /dev/null +++ b/test/991-field-trace-2/run @@ -0,0 +1,18 @@ +#!/bin/bash +# +# Copyright 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. + +# Ask for stack traces to be dumped to a file rather than to stdout. +./default-run "$@" --jvmti diff --git a/test/991-field-trace-2/src/Main.java b/test/991-field-trace-2/src/Main.java new file mode 100644 index 0000000000..d945a5c183 --- /dev/null +++ b/test/991-field-trace-2/src/Main.java @@ -0,0 +1,21 @@ +/* + * 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. + */ + +public class Main { +  public static void main(String[] args) throws Exception { +    art.Test991.run(); +  } +} diff --git a/test/991-field-trace-2/src/art/Test991.java b/test/991-field-trace-2/src/art/Test991.java new file mode 100644 index 0000000000..644f4e10ed --- /dev/null +++ b/test/991-field-trace-2/src/art/Test991.java @@ -0,0 +1,219 @@ +/* + * 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. + */ + +package art; + +import java.lang.reflect.Executable; +import java.lang.reflect.Field; +import java.util.Arrays; +import java.util.List; +import java.util.function.Consumer; + +public class Test991 { +  static List<Field> WATCH_FIELDS = Arrays.asList(TestClass1.class.getDeclaredFields()); + +  static FieldTracer TRACE = null; + +  static abstract class FieldTracer { +    public final void notifyFieldAccess( +        Executable method, long location, Class<?> f_klass, Object target, Field f) { +      System.out.println("FieldTracer: " + this.getClass()); +      System.out.println("\tACCESS of " + f + " on object of" + +          " type: " + (target == null ? null : target.getClass()) + +          " in method " + method); +      handleFieldAccess(method, location, f_klass, target, f); +    } + +    public final void notifyFieldModify( +        Executable method, long location, Class<?> f_klass, Object target, Field f, Object value) { +      System.out.println("FieldTracer: " + this.getClass()); +      System.out.println("\tMODIFY of " + f + " on object of" + +          " type: " + (target == null ? null : target.getClass()) + +          " in method " + method + +          ". New value: " + value + " (type: " + value.getClass() + ")"); +      handleFieldModify(method, location, f_klass, target, f, value); +    } + +    public void handleFieldAccess(Executable m, long l, Class<?> fk, Object t, Field f) {} +    public void handleFieldModify(Executable m, long l, Class<?> fk, Object t, Field f, Object v) {} +  } + +  private static class TestError extends Error { +    private static final long serialVersionUID = 0; +    public TestError(String s) { super(s); } +  } +  static class DoNothingFieldTracer extends FieldTracer {} +  static class ThrowReadFieldTracer extends FieldTracer { +    @Override +    public void handleFieldAccess(Executable m, long l, Class<?> fk, Object t, Field f) { +      throw new TestError("Throwing error during access"); +    } +  } +  static class ThrowWriteFieldTracer extends FieldTracer { +    @Override +    public void handleFieldModify(Executable m, long l, Class<?> fk, Object t, Field f, Object v) { +      throw new TestError("Throwing error during modify"); +    } +  } +  static class ModifyDuringReadAndWriteFieldTracer extends FieldTracer { +    @Override +    public void handleFieldModify(Executable m, long l, Class<?> fk, Object t, Field f, Object v) { +      // NB This is only safe because the agent doesn't send recursive access/modification events up +      // to the java layer here. +      ((TestClass1)t).xyz += 100; +    } +    @Override +    public void handleFieldAccess(Executable m, long l, Class<?> fk, Object t, Field f) { +      // NB This is only safe because the agent doesn't send recursive access/modification events up +      // to the java layer here. +      ((TestClass1)t).xyz += 10; +    } +  } + +  static class ModifyDuringWriteFieldTracer extends FieldTracer { +    @Override +    public void handleFieldModify(Executable m, long l, Class<?> fk, Object t, Field f, Object v) { +      // NB This is only safe because the agent doesn't send recursive access/modification events up +      // to the java layer here. +      ((TestClass1)t).xyz += 200; +    } +  } + +  static class ModifyDuringReadFieldTracer extends FieldTracer { +    @Override +    public void handleFieldAccess(Executable m, long l, Class<?> fk, Object t, Field f) { +      // NB This is only safe because the agent doesn't send recursive access/modification events up +      // to the java layer here. +      ((TestClass1)t).xyz += 20; +    } +  } + +  public static void notifyFieldModify( +      Executable m, long location, Class<?> f_klass, Object target, Field f, Object value) { +    if (TRACE != null) { +      TRACE.notifyFieldModify(m, location, f_klass, target, f, value); +    } +  } + +  public static void notifyFieldAccess( +      Executable m, long location, Class<?> f_klass, Object target, Field f) { +    if (TRACE != null) { +      TRACE.notifyFieldAccess(m, location, f_klass, target, f); +    } +  } + +  public static class TestClass1 { +    public int xyz; +    public TestClass1(int xyz) { +      this.xyz = xyz; +    } +  } + +  public static int readFieldUntraced(TestClass1 target) { +    FieldTracer tmp = TRACE; +    TRACE = null; +    int res = target.xyz; +    TRACE = tmp; +    return res; +  } + +  public static class JavaReadWrite implements Consumer<TestClass1> { +    public void accept(TestClass1 t1) { +      int val = t1.xyz; +      System.out.println("normal read: xyz = " + val); +      t1.xyz = val + 1; +    } +  } + +  public static class ReflectiveReadWrite implements Consumer<TestClass1> { +    public void accept(TestClass1 t1) { +      try { +        Field f = t1.getClass().getDeclaredField("xyz"); +        int val = f.getInt(t1); +        System.out.println("reflective read: xyz = " + val); +        f.setInt(t1, val + 1); +      } catch (IllegalAccessException iae) { +        throw new InternalError("Could not set field xyz", iae); +      } catch (NoSuchFieldException nsfe) { +        throw new InternalError("Could not find field xyz", nsfe); +      } +    } +  } + +  public static class NativeReadWrite implements Consumer<TestClass1> { +    public void accept(TestClass1 t1) { +      doNativeReadWrite(t1); +    } +  } + +  public static TestClass1 createTestClassNonTraced() { +    FieldTracer tmp = TRACE; +    TRACE = null; +    TestClass1 n = new TestClass1(0); +    TRACE = tmp; +    return n; +  } + +  public static void run() throws Exception { +    Trace.disableTracing(Thread.currentThread()); +    Trace.enableFieldTracing( +        Test991.class, +        Test991.class.getDeclaredMethod("notifyFieldAccess", +          Executable.class, Long.TYPE, Class.class, Object.class, Field.class), +        Test991.class.getDeclaredMethod("notifyFieldModify", +          Executable.class, Long.TYPE, Class.class, Object.class, Field.class, Object.class), +        Thread.currentThread()); +    for (Field f : WATCH_FIELDS) { +      Trace.watchFieldAccess(f); +      Trace.watchFieldModification(f); +    } +    FieldTracer[] tracers = new FieldTracer[] { +      new DoNothingFieldTracer(), +      new ThrowReadFieldTracer(), +      new ThrowWriteFieldTracer(), +      new ModifyDuringReadFieldTracer(), +      new ModifyDuringWriteFieldTracer(), +      new ModifyDuringReadAndWriteFieldTracer(), +    }; +    Consumer<TestClass1>[] field_modification = new Consumer[] { +      new JavaReadWrite(), +      new ReflectiveReadWrite(), +      new NativeReadWrite(), +    }; +    for (Consumer<TestClass1> c : field_modification) { +      for (FieldTracer trace : tracers) { +        System.out.println("Test is " + trace.getClass() + " & " + c.getClass()); +        TestClass1 t1 = createTestClassNonTraced(); +        TRACE = trace; +        System.out.println("Initial state: xyz = " + readFieldUntraced(t1)); +        try { +          c.accept(t1); +        } catch (TestError e) { +          System.out.println("Caught error. " + e); +        } finally { +          System.out.println("Final state: xyz = " + readFieldUntraced(t1)); +        } +      } +    } +    Trace.disableTracing(Thread.currentThread()); +  } + +  public static native void doNativeReadWrite(TestClass1 t1); + +  public static void doPrintNativeNotification(int val) { +    System.out.println("native read: xyz = " + val); +  } +} diff --git a/test/991-field-trace-2/src/art/Trace.java b/test/991-field-trace-2/src/art/Trace.java new file mode 100644 index 0000000000..9c27c9f69e --- /dev/null +++ b/test/991-field-trace-2/src/art/Trace.java @@ -0,0 +1,49 @@ +/* + * 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. + */ + +package art; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +public class Trace { +  public static native void enableTracing(Class<?> methodClass, +                                          Method entryMethod, +                                          Method exitMethod, +                                          Method fieldAccess, +                                          Method fieldModify, +                                          Thread thr); +  public static native void disableTracing(Thread thr); + +  public static void enableFieldTracing(Class<?> methodClass, +                                        Method fieldAccess, +                                        Method fieldModify, +                                        Thread thr) { +    enableTracing(methodClass, null, null, fieldAccess, fieldModify, thr); +  } + +  public static void enableMethodTracing(Class<?> methodClass, +                                         Method entryMethod, +                                         Method exitMethod, +                                         Thread thr) { +    enableTracing(methodClass, entryMethod, exitMethod, null, null, thr); +  } + +  public static native void watchFieldAccess(Field f); +  public static native void watchFieldModification(Field f); +  public static native void watchAllFieldAccesses(); +  public static native void watchAllFieldModifications(); +} diff --git a/test/992-source-data/expected.txt b/test/992-source-data/expected.txt new file mode 100644 index 0000000000..480d8a4fe7 --- /dev/null +++ b/test/992-source-data/expected.txt @@ -0,0 +1,10 @@ +class art.Test992 is defined in file "Test992.java" +class art.Test992$Target1 is defined in file "Test992.java" +class art.Test2 is defined in file "Test2.java" +int does not have a known source file because java.lang.RuntimeException: JVMTI_ERROR_ABSENT_INFORMATION +class java.lang.Integer is defined in file "Integer.java" +class java.lang.Object is defined in file "Object.java" +interface java.lang.Runnable is defined in file "Runnable.java" +class [Ljava.lang.Object; does not have a known source file because java.lang.RuntimeException: JVMTI_ERROR_ABSENT_INFORMATION +class [I does not have a known source file because java.lang.RuntimeException: JVMTI_ERROR_ABSENT_INFORMATION +null does not have a known source file because java.lang.RuntimeException: JVMTI_ERROR_INVALID_CLASS diff --git a/test/992-source-data/info.txt b/test/992-source-data/info.txt new file mode 100644 index 0000000000..5d487a4bde --- /dev/null +++ b/test/992-source-data/info.txt @@ -0,0 +1 @@ +Tests that we can get the source file of a class from JVMTI. diff --git a/test/992-source-data/run b/test/992-source-data/run new file mode 100755 index 0000000000..e92b873956 --- /dev/null +++ b/test/992-source-data/run @@ -0,0 +1,17 @@ +#!/bin/bash +# +# Copyright 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. + +./default-run "$@" --jvmti diff --git a/test/992-source-data/source_file.cc b/test/992-source-data/source_file.cc new file mode 100644 index 0000000000..3e8989e403 --- /dev/null +++ b/test/992-source-data/source_file.cc @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2013 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 <inttypes.h> +#include <memory> +#include <stdio.h> + +#include "android-base/logging.h" +#include "android-base/stringprintf.h" + +#include "jni.h" +#include "jvmti.h" +#include "scoped_local_ref.h" + +// Test infrastructure +#include "jni_binder.h" +#include "jni_helper.h" +#include "jvmti_helper.h" +#include "test_env.h" +#include "ti_macros.h" + +namespace art { +namespace Test992SourceFile { + +extern "C" JNIEXPORT +jstring JNICALL Java_art_Test992_getSourceFileName(JNIEnv* env, +                                                   jclass klass ATTRIBUTE_UNUSED, +                                                   jclass target) { +  char* file = nullptr; +  if (JvmtiErrorToException(env, jvmti_env, jvmti_env->GetSourceFileName(target, &file))) { +    return nullptr; +  } +  jstring ret = env->NewStringUTF(file); +  jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(file)); +  return ret; +} + +}  // namespace Test992SourceFile +}  // namespace art + diff --git a/test/992-source-data/src/Main.java b/test/992-source-data/src/Main.java new file mode 100644 index 0000000000..31106f41fb --- /dev/null +++ b/test/992-source-data/src/Main.java @@ -0,0 +1,21 @@ +/* + * 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. + */ + +public class Main { +  public static void main(String[] args) throws Exception { +    art.Test992.run(); +  } +} diff --git a/test/992-source-data/src/art/Test2.java b/test/992-source-data/src/art/Test2.java new file mode 100644 index 0000000000..dbb1089c5e --- /dev/null +++ b/test/992-source-data/src/art/Test2.java @@ -0,0 +1,19 @@ +/* + * 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. + */ + +package art; + +public class Test2 {} diff --git a/test/992-source-data/src/art/Test992.java b/test/992-source-data/src/art/Test992.java new file mode 100644 index 0000000000..db6ea73856 --- /dev/null +++ b/test/992-source-data/src/art/Test992.java @@ -0,0 +1,47 @@ +/* + * 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. + */ + +package art; + +import java.util.Base64; + +public class Test992 { + +  static class Target1 { } + +  public static void run() { +    doTest(Test992.class); +    doTest(Target1.class); +    doTest(Test2.class); +    doTest(Integer.TYPE); +    doTest(Integer.class); +    doTest(Object.class); +    doTest(Runnable.class); +    doTest(new Object[0].getClass()); +    doTest(new int[0].getClass()); +    doTest(null); +  } + +  public static void doTest(Class<?> k) { +    try { +      System.out.println(k + " is defined in file \"" + getSourceFileName(k) + "\""); +    } catch (Exception e) { +      System.out.println(k + " does not have a known source file because " + e); +    } +  } + +  public static native String getSourceFileName(Class<?> k) throws Exception; +} diff --git a/test/Android.bp b/test/Android.bp index 0937c62469..9e6ecffe79 100644 --- a/test/Android.bp +++ b/test/Android.bp @@ -279,6 +279,8 @@ art_cc_defaults {          "986-native-method-bind/native_bind.cc",          "987-agent-bind/agent_bind.cc",          "989-method-trace-throw/method_trace.cc", +        "991-field-trace-2/field_trace.cc", +        "992-source-data/source_file.cc",      ],      shared_libs: [          "libbase", diff --git a/test/Android.run-test-jvmti-java-library.mk b/test/Android.run-test-jvmti-java-library.mk index da28b4c4bd..55553226b3 100644 --- a/test/Android.run-test-jvmti-java-library.mk +++ b/test/Android.run-test-jvmti-java-library.mk @@ -45,8 +45,8 @@ LOCAL_SRC_FILES += \      911-get-stack-trace/src/art/Recurse.java \      911-get-stack-trace/src/art/SameThread.java \      911-get-stack-trace/src/art/ThreadListTraces.java \ -  912-classes/src/art/Test912.java \ -    912-classes/src/art/DexData.java \ +  912-classes/src-art/art/Test912.java \ +    912-classes/src-art/art/DexData.java \    913-heaps/src/art/Test913.java \    914-hello-obsolescence/src/art/Test914.java \    915-obsolete-2/src/art/Test915.java \ @@ -71,7 +71,7 @@ LOCAL_SRC_FILES += \    945-obsolete-native/src/art/Test945.java \    947-reflect-method/src/art/Test947.java \    951-threaded-obsolete/src/art/Test951.java \ -  981-dedup-original-dex/src/art/Test981.java \ +  981-dedup-original-dex/src-art/art/Test981.java \    982-ok-no-retransform/src/art/Test982.java \    984-obsolete-invoke/src/art/Test984.java \    985-re-obsolete/src/art/Test985.java \ diff --git a/test/etc/default-build b/test/etc/default-build index a88ef924a6..977b07189b 100755 --- a/test/etc/default-build +++ b/test/etc/default-build @@ -30,6 +30,13 @@ else    HAS_SRC=false  fi +# .java files in src-art get compiled with libcore on the bootclasspath +if [ -d src-art ]; then +  HAS_SRC_ART=true +else +  HAS_SRC_ART=false +fi +  if [ -d src2 ]; then    HAS_SRC2=true  else @@ -225,6 +232,20 @@ function desugar() {    "$DESUGAR" --core-only $desugar_args "$@"  } +# Like regular javac but includes libcore on the bootclasspath. +function javac_with_bootclasspath { +  local javac_args=--mode=host +  if [[ $BUILD_MODE == target ]]; then +    javac_args=--mode=target +  fi + +  if [[ $DEV_MODE == yes ]]; then +    javac_args="$javac_args --show-commands" +  fi + +  "$ANDROID_BUILD_TOP/art/tools/javac-helper.sh" --core-only $javac_args "$@" +} +  # Make a "dex" file given a directory of classes in $1.  # Also calls desugar on the classes first to convert lambdas.  function make_dex() { @@ -247,12 +268,20 @@ function make_dex() {    ${DX} -JXmx256m ${DX_VM_FLAGS} --debug --dex --dump-to=${name}.lst --output=${name}.dex --dump-width=1000 ${DX_FLAGS} "${dx_input}"  } +# Print the directory name only if it exists. +function maybe_dir() { +  local dirname="$1" +  if [[ -d "$dirname" ]]; then +    echo "$dirname" +  fi +} +  if [ -e classes.dex ]; then    zip $TEST_NAME.jar classes.dex    exit 0  fi -if ! [ "${HAS_SRC}" = "true" ] && ! [ "${HAS_SRC2}" = "true" ]; then +if ! [ "${HAS_SRC}" = "true" ] && ! [ "${HAS_SRC2}" = "true" ] && ! [ "${HAS_SRC_ART}" = "true" ]; then    # No src directory? Then forget about trying to run dx.    SKIP_DX_MERGER="true"  fi @@ -280,16 +309,16 @@ if [ ${HAS_SRC_DEX2OAT_UNRESOLVED} = "true" ]; then  else    if [ ${USE_JACK} = "true" ]; then      # Jack toolchain -    if [ "${HAS_SRC}" = "true" ]; then +    if [[ "$HAS_SRC" == true || "$HAS_SRC_ART" == true ]]; then        if [ "${HAS_SRC_MULTIDEX}" = "true" ]; then          # Compile src and src-multidex in the same .jack file. We will apply multidex partitioning          # when creating the output .dex file. -        ${JACK} ${JACK_ARGS} --output-jack src.jack src src src-multidex +        ${JACK} ${JACK_ARGS} --output-jack src.jack $(maybe_dir src) src-multidex $(maybe_dir src-art)          jack_extra_args="${jack_extra_args} -D jack.dex.output.policy=minimal-multidex"          jack_extra_args="${jack_extra_args} -D jack.preprocessor=true"          jack_extra_args="${jack_extra_args} -D jack.preprocessor.file=multidex.jpp"        else -        ${JACK} ${JACK_ARGS} --output-jack src.jack src +        ${JACK} ${JACK_ARGS} --output-jack src.jack $(maybe_dir src) $(maybe_dir src-art)        fi        jack_extra_args="${jack_extra_args} --import src.jack"      fi @@ -303,7 +332,7 @@ else      fi      # Compile jack files into a DEX file. -    if [ "${HAS_SRC}" = "true" ] || [ "${HAS_SRC2}" = "true" ]; then +    if [ "${HAS_SRC}" = "true" ] || [ "${HAS_SRC2}" = "true" ] || [ "${HAS_SRC_ART}" ]; then        ${JACK} ${JACK_ARGS} ${jack_extra_args} --output-dex .      fi    else @@ -313,6 +342,11 @@ else        ${JAVAC} ${JAVAC_ARGS} -implicit:none -classpath src-multidex -d classes `find src -name '*.java'`      fi +    if [ "${HAS_SRC_ART}" = "true" ]; then +      mkdir -p classes +      javac_with_bootclasspath ${JAVAC_ARGS} -implicit:none -classpath src-multidex -d classes `find src-art -name '*.java'` +    fi +      if [ "${HAS_SRC_MULTIDEX}" = "true" ]; then        mkdir classes2        ${JAVAC} -implicit:none -classpath src -d classes2 `find src-multidex -name '*.java'` @@ -326,7 +360,7 @@ else        ${JAVAC} ${JAVAC_ARGS} -d classes `find src2 -name '*.java'`      fi -    if [ "${HAS_SRC}" = "true" ] || [ "${HAS_SRC2}" = "true" ]; then +    if [[ "${HAS_SRC}" == "true" || "${HAS_SRC2}" == "true" || "${HAS_SRC_ART}" == "true" ]]; then        if [ ${NEED_DEX} = "true" -a ${SKIP_DX_MERGER} = "false" ]; then          make_dex classes        fi diff --git a/test/etc/run-test-jar b/test/etc/run-test-jar index 8aacc8c9b7..d24edccd8b 100755 --- a/test/etc/run-test-jar +++ b/test/etc/run-test-jar @@ -63,6 +63,7 @@ TEST_VDEX="n"  TEST_IS_NDEBUG="n"  APP_IMAGE="y"  JVMTI_STRESS="n" +JVMTI_FIELD_STRESS="n"  JVMTI_TRACE_STRESS="n"  JVMTI_REDEFINE_STRESS="n"  VDEX_FILTER="" @@ -78,6 +79,9 @@ SET_STACK_TRACE_DUMP_DIR="n"  # build step (e.g. dex2oat) were finished writing.  SYNC_BEFORE_RUN="n" +# When running a debug build, we want to run with all checks. +ANDROID_FLAGS="${ANDROID_FLAGS} -XX:SlowDebug=true" +  while true; do      if [ "x$1" = "x--quiet" ]; then          QUIET="y" @@ -159,6 +163,10 @@ while true; do          JVMTI_STRESS="y"          JVMTI_REDEFINE_STRESS="y"          shift +    elif [ "x$1" = "x--jvmti-field-stress" ]; then +        JVMTI_STRESS="y" +        JVMTI_FIELD_STRESS="y" +        shift      elif [ "x$1" = "x--jvmti-trace-stress" ]; then          JVMTI_STRESS="y"          JVMTI_TRACE_STRESS="y" @@ -415,6 +423,9 @@ if [[ "$JVMTI_STRESS" = "y" ]]; then        agent_args="${agent_args},redefine,${DEXTER_BINARY},${file_1},${file_2}"      fi    fi +  if [[ "$JVMTI_FIELD_STRESS" = "y" ]]; then +    agent_args="${agent_args},field" +  fi    if [[ "$JVMTI_TRACE_STRESS" = "y" ]]; then      agent_args="${agent_args},trace"    fi diff --git a/test/knownfailures.json b/test/knownfailures.json index a4b8be9091..a3b8dd6cf0 100644 --- a/test/knownfailures.json +++ b/test/knownfailures.json @@ -511,7 +511,7 @@              "645-checker-abs-simd",              "706-checker-scheduler"],          "description": ["Checker tests are not compatible with jvmti."], -        "variant": "jvmti-stress | redefine-stress | trace-stress" +        "variant": "jvmti-stress | redefine-stress | trace-stress | field-stress"      },      {          "tests": [ @@ -550,7 +550,7 @@              "981-dedup-original-dex"          ],          "description": ["Tests that require exact knowledge of the number of plugins and agents."], -        "variant": "jvmti-stress | redefine-stress | trace-stress" +        "variant": "jvmti-stress | redefine-stress | trace-stress | field-stress"      },      {          "tests": [ @@ -585,6 +585,13 @@      },      {          "tests": [ +            "004-ThreadStress" +        ], +        "description": "The thread stress test just takes too long with field-stress", +        "variant": "jvmti-stress | field-stress" +    }, +    { +        "tests": [              "031-class-attributes",              "911-get-stack-trace"          ], @@ -596,30 +603,18 @@      },      {          "tests": [ -            "004-NativeAllocations",              "004-ReferenceMap",              "004-StackWalk",              "089-many-methods", -            "138-duplicate-classes-check", -            "146-bad-interface", -            "157-void-class",              "580-checker-string-fact-intrinsics", -            "596-monitor-inflation", -            "612-jit-dex-cache", -            "613-inlining-dex-cache",              "616-cha-interface-default",              "636-wrong-static-access", -            "909-attach-agent",              "910-methods",              "911-get-stack-trace", -            "912-classes",              "913-heaps",              "948-change-annotations", -            "952-invoke-custom",              "953-invoke-polymorphic-compiler", -            "958-methodhandle-stackframe", -            "960-default-smali", -            "981-dedup-original-dex" +            "958-methodhandle-stackframe"          ],          "description": "The tests above fail with --build-with-javac-dx.",          "env_vars": {"ANDROID_COMPILE_WITH_JACK": "false"}, diff --git a/test/run-test b/test/run-test index 1b6df16dae..044f63fa1e 100755 --- a/test/run-test +++ b/test/run-test @@ -144,6 +144,7 @@ basic_verify="false"  gc_verify="false"  gc_stress="false"  jvmti_trace_stress="false" +jvmti_field_stress="false"  jvmti_redefine_stress="false"  strace="false"  always_clean="no" @@ -244,6 +245,9 @@ while true; do      elif [ "x$1" = "x--jvmti-redefine-stress" ]; then          jvmti_redefine_stress="true"          shift +    elif [ "x$1" = "x--jvmti-field-stress" ]; then +        jvmti_field_stress="true" +        shift      elif [ "x$1" = "x--jvmti-trace-stress" ]; then          jvmti_trace_stress="true"          shift @@ -460,6 +464,9 @@ fi  if [ "$jvmti_redefine_stress" = "true" ]; then      run_args="${run_args} --no-app-image --jvmti-redefine-stress"  fi +if [ "$jvmti_field_stress" = "true" ]; then +    run_args="${run_args} --no-app-image --jvmti-field-stress" +fi  if [ "$jvmti_trace_stress" = "true" ]; then      run_args="${run_args} --no-app-image --jvmti-trace-stress"  fi diff --git a/test/testrunner/testrunner.py b/test/testrunner/testrunner.py index 344507115b..b6a5963cf4 100755 --- a/test/testrunner/testrunner.py +++ b/test/testrunner/testrunner.py @@ -147,7 +147,8 @@ def gather_test_info():    VARIANT_TYPE_DICT['relocate'] = {'relocate-npatchoat', 'relocate', 'no-relocate'}    VARIANT_TYPE_DICT['jni'] = {'jni', 'forcecopy', 'checkjni'}    VARIANT_TYPE_DICT['address_sizes'] = {'64', '32'} -  VARIANT_TYPE_DICT['jvmti'] = {'no-jvmti', 'jvmti-stress', 'redefine-stress', 'trace-stress'} +  VARIANT_TYPE_DICT['jvmti'] = {'no-jvmti', 'jvmti-stress', 'redefine-stress', 'trace-stress', +                                'field-stress'}    VARIANT_TYPE_DICT['compiler'] = {'interp-ac', 'interpreter', 'jit', 'optimizing',                                'regalloc_gc', 'speed-profile'} @@ -437,7 +438,9 @@ def run_tests(tests):          options_test += ' --debuggable'        if jvmti == 'jvmti-stress': -        options_test += ' --jvmti-trace-stress --jvmti-redefine-stress' +        options_test += ' --jvmti-trace-stress --jvmti-redefine-stress --jvmti-field-stress' +      elif jvmti == 'field-stress': +        options_test += ' --jvmti-field-stress'        elif jvmti == 'trace-stress':          options_test += ' --jvmti-trace-stress'        elif jvmti == 'redefine-stress': @@ -960,6 +963,8 @@ def parse_option():      JVMTI_TYPES.add('jvmti-stress')    if options['redefine_stress']:      JVMTI_TYPES.add('redefine-stress') +  if options['field_stress']: +    JVMTI_TYPES.add('field-stress')    if options['trace_stress']:      JVMTI_TYPES.add('trace-stress')    if options['no_jvmti']: diff --git a/test/ti-agent/common_helper.cc b/test/ti-agent/common_helper.cc index 6eaa5c37df..4fe58db169 100644 --- a/test/ti-agent/common_helper.cc +++ b/test/ti-agent/common_helper.cc @@ -78,9 +78,23 @@ struct TraceData {    jclass test_klass;    jmethodID enter_method;    jmethodID exit_method; +  jmethodID field_access; +  jmethodID field_modify;    bool in_callback; +  bool access_watch_on_load; +  bool modify_watch_on_load;  }; +static jobject GetJavaField(jvmtiEnv* jvmti, JNIEnv* env, jclass field_klass, jfieldID f) { +  jint mods = 0; +  if (JvmtiErrorToException(env, jvmti, jvmti->GetFieldModifiers(field_klass, f, &mods))) { +    return nullptr; +  } + +  bool is_static = (mods & kAccStatic) != 0; +  return env->ToReflectedField(field_klass, f, is_static); +} +  static jobject GetJavaMethod(jvmtiEnv* jvmti, JNIEnv* env, jmethodID m) {    jint mods = 0;    if (JvmtiErrorToException(env, jvmti, jvmti->GetMethodModifiers(m, &mods))) { @@ -97,21 +111,9 @@ static jobject GetJavaMethod(jvmtiEnv* jvmti, JNIEnv* env, jmethodID m) {    return res;  } -static jobject GetJavaValue(jvmtiEnv* jvmtienv, -                            JNIEnv* env, -                            jmethodID m, -                            jvalue value) { -  char *fname, *fsig, *fgen; -  if (JvmtiErrorToException(env, jvmtienv, jvmtienv->GetMethodName(m, &fname, &fsig, &fgen))) { -    return nullptr; -  } -  std::string type(fsig); -  type = type.substr(type.find(")") + 1); -  jvmtienv->Deallocate(reinterpret_cast<unsigned char*>(fsig)); -  jvmtienv->Deallocate(reinterpret_cast<unsigned char*>(fname)); -  jvmtienv->Deallocate(reinterpret_cast<unsigned char*>(fgen)); +static jobject GetJavaValueByType(JNIEnv* env, char type, jvalue value) {    std::string name; -  switch (type[0]) { +  switch (type) {      case 'V':        return nullptr;      case '[': @@ -146,7 +148,7 @@ static jobject GetJavaValue(jvmtiEnv* jvmtienv,        return nullptr;    }    std::ostringstream oss; -  oss << "(" << type[0] << ")L" << name << ";"; +  oss << "(" << type << ")L" << name << ";";    std::string args = oss.str();    jclass target = env->FindClass(name.c_str());    jmethodID valueOfMethod = env->GetStaticMethodID(target, "valueOf", args.c_str()); @@ -157,6 +159,98 @@ static jobject GetJavaValue(jvmtiEnv* jvmtienv,    return res;  } +static jobject GetJavaValue(jvmtiEnv* jvmtienv, +                            JNIEnv* env, +                            jmethodID m, +                            jvalue value) { +  char *fname, *fsig, *fgen; +  if (JvmtiErrorToException(env, jvmtienv, jvmtienv->GetMethodName(m, &fname, &fsig, &fgen))) { +    return nullptr; +  } +  std::string type(fsig); +  type = type.substr(type.find(")") + 1); +  jvmtienv->Deallocate(reinterpret_cast<unsigned char*>(fsig)); +  jvmtienv->Deallocate(reinterpret_cast<unsigned char*>(fname)); +  jvmtienv->Deallocate(reinterpret_cast<unsigned char*>(fgen)); +  return GetJavaValueByType(env, type[0], value); +} + +static void fieldAccessCB(jvmtiEnv* jvmti, +                          JNIEnv* jnienv, +                          jthread thr ATTRIBUTE_UNUSED, +                          jmethodID method, +                          jlocation location, +                          jclass field_klass, +                          jobject object, +                          jfieldID field) { +  TraceData* data = nullptr; +  if (JvmtiErrorToException(jnienv, jvmti, +                            jvmti->GetEnvironmentLocalStorage(reinterpret_cast<void**>(&data)))) { +    return; +  } +  if (data->in_callback) { +    // Don't do callback for either of these to prevent an infinite loop. +    return; +  } +  CHECK(data->field_access != nullptr); +  data->in_callback = true; +  jobject method_arg = GetJavaMethod(jvmti, jnienv, method); +  jobject field_arg = GetJavaField(jvmti, jnienv, field_klass, field); +  jnienv->CallStaticVoidMethod(data->test_klass, +                               data->field_access, +                               method_arg, +                               static_cast<jlong>(location), +                               field_klass, +                               object, +                               field_arg); +  jnienv->DeleteLocalRef(method_arg); +  jnienv->DeleteLocalRef(field_arg); +  data->in_callback = false; +} + +static void fieldModificationCB(jvmtiEnv* jvmti, +                                JNIEnv* jnienv, +                                jthread thr ATTRIBUTE_UNUSED, +                                jmethodID method, +                                jlocation location, +                                jclass field_klass, +                                jobject object, +                                jfieldID field, +                                char type_char, +                                jvalue new_value) { +  TraceData* data = nullptr; +  if (JvmtiErrorToException(jnienv, jvmti, +                            jvmti->GetEnvironmentLocalStorage(reinterpret_cast<void**>(&data)))) { +    return; +  } +  if (data->in_callback) { +    // Don't do callback recursively to prevent an infinite loop. +    return; +  } +  CHECK(data->field_modify != nullptr); +  data->in_callback = true; +  jobject method_arg = GetJavaMethod(jvmti, jnienv, method); +  jobject field_arg = GetJavaField(jvmti, jnienv, field_klass, field); +  jobject value = GetJavaValueByType(jnienv, type_char, new_value); +  if (jnienv->ExceptionCheck()) { +    data->in_callback = false; +    jnienv->DeleteLocalRef(method_arg); +    jnienv->DeleteLocalRef(field_arg); +    return; +  } +  jnienv->CallStaticVoidMethod(data->test_klass, +                               data->field_modify, +                               method_arg, +                               static_cast<jlong>(location), +                               field_klass, +                               object, +                               field_arg, +                               value); +  jnienv->DeleteLocalRef(method_arg); +  jnienv->DeleteLocalRef(field_arg); +  data->in_callback = false; +} +  static void methodExitCB(jvmtiEnv* jvmti,                           JNIEnv* jnienv,                           jthread thr ATTRIBUTE_UNUSED, @@ -172,6 +266,7 @@ static void methodExitCB(jvmtiEnv* jvmti,      // Don't do callback for either of these to prevent an infinite loop.      return;    } +  CHECK(data->exit_method != nullptr);    data->in_callback = true;    jobject method_arg = GetJavaMethod(jvmti, jnienv, method);    jobject result = @@ -198,6 +293,7 @@ static void methodEntryCB(jvmtiEnv* jvmti,                              jvmti->GetEnvironmentLocalStorage(reinterpret_cast<void**>(&data)))) {      return;    } +  CHECK(data->enter_method != nullptr);    if (method == data->exit_method || method == data->enter_method || data->in_callback) {      // Don't do callback for either of these to prevent an infinite loop.      return; @@ -212,12 +308,179 @@ static void methodEntryCB(jvmtiEnv* jvmti,    data->in_callback = false;  } -extern "C" JNIEXPORT void JNICALL Java_art_Trace_enableMethodTracing( +static void classPrepareCB(jvmtiEnv* jvmti, +                           JNIEnv* jnienv, +                           jthread thr ATTRIBUTE_UNUSED, +                           jclass klass) { +  TraceData* data = nullptr; +  if (JvmtiErrorToException(jnienv, jvmti, +                            jvmti->GetEnvironmentLocalStorage(reinterpret_cast<void**>(&data)))) { +    return; +  } +  if (data->access_watch_on_load || data->modify_watch_on_load) { +    jint nfields; +    jfieldID* fields; +    if (JvmtiErrorToException(jnienv, jvmti, jvmti->GetClassFields(klass, &nfields, &fields))) { +      return; +    } +    for (jint i = 0; i < nfields; i++) { +      jfieldID f = fields[i]; +      // Ignore errors +      if (data->access_watch_on_load) { +        jvmti->SetFieldAccessWatch(klass, f); +      } + +      if (data->modify_watch_on_load) { +        jvmti->SetFieldModificationWatch(klass, f); +      } +    } +    jvmti->Deallocate(reinterpret_cast<unsigned char*>(fields)); +  } +} + +extern "C" JNIEXPORT void JNICALL Java_art_Trace_watchAllFieldAccesses(JNIEnv* env) { +  TraceData* data = nullptr; +  if (JvmtiErrorToException( +      env, jvmti_env, jvmti_env->GetEnvironmentLocalStorage(reinterpret_cast<void**>(&data)))) { +    return; +  } +  data->access_watch_on_load = true; +  // We need the classPrepareCB to watch new fields as the classes are loaded/prepared. +  if (JvmtiErrorToException(env, +                            jvmti_env, +                            jvmti_env->SetEventNotificationMode(JVMTI_ENABLE, +                                                                JVMTI_EVENT_CLASS_PREPARE, +                                                                nullptr))) { +    return; +  } +  jint nklasses; +  jclass* klasses; +  if (JvmtiErrorToException(env, jvmti_env, jvmti_env->GetLoadedClasses(&nklasses, &klasses))) { +    return; +  } +  for (jint i = 0; i < nklasses; i++) { +    jclass k = klasses[i]; + +    jint nfields; +    jfieldID* fields; +    jvmtiError err = jvmti_env->GetClassFields(k, &nfields, &fields); +    if (err == JVMTI_ERROR_CLASS_NOT_PREPARED) { +      continue; +    } else if (JvmtiErrorToException(env, jvmti_env, err)) { +      jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(klasses)); +      return; +    } +    for (jint j = 0; j < nfields; j++) { +      jvmti_env->SetFieldAccessWatch(k, fields[j]); +    } +    jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(fields)); +  } +  jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(klasses)); +} + +extern "C" JNIEXPORT void JNICALL Java_art_Trace_watchAllFieldModifications(JNIEnv* env) { +  TraceData* data = nullptr; +  if (JvmtiErrorToException( +      env, jvmti_env, jvmti_env->GetEnvironmentLocalStorage(reinterpret_cast<void**>(&data)))) { +    return; +  } +  data->modify_watch_on_load = true; +  // We need the classPrepareCB to watch new fields as the classes are loaded/prepared. +  if (JvmtiErrorToException(env, +                            jvmti_env, +                            jvmti_env->SetEventNotificationMode(JVMTI_ENABLE, +                                                                JVMTI_EVENT_CLASS_PREPARE, +                                                                nullptr))) { +    return; +  } +  jint nklasses; +  jclass* klasses; +  if (JvmtiErrorToException(env, jvmti_env, jvmti_env->GetLoadedClasses(&nklasses, &klasses))) { +    return; +  } +  for (jint i = 0; i < nklasses; i++) { +    jclass k = klasses[i]; + +    jint nfields; +    jfieldID* fields; +    jvmtiError err = jvmti_env->GetClassFields(k, &nfields, &fields); +    if (err == JVMTI_ERROR_CLASS_NOT_PREPARED) { +      continue; +    } else if (JvmtiErrorToException(env, jvmti_env, err)) { +      jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(klasses)); +      return; +    } +    for (jint j = 0; j < nfields; j++) { +      jvmti_env->SetFieldModificationWatch(k, fields[j]); +    } +    jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(fields)); +  } +  jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(klasses)); +} + +static bool GetFieldAndClass(JNIEnv* env, +                             jobject ref_field, +                             jclass* out_klass, +                             jfieldID* out_field) { +  *out_field = env->FromReflectedField(ref_field); +  if (env->ExceptionCheck()) { +    return false; +  } +  jclass field_klass = env->FindClass("java/lang/reflect/Field"); +  if (env->ExceptionCheck()) { +    return false; +  } +  jmethodID get_declaring_class_method = +      env->GetMethodID(field_klass, "getDeclaringClass", "()Ljava/lang/Class;"); +  if (env->ExceptionCheck()) { +    env->DeleteLocalRef(field_klass); +    return false; +  } +  *out_klass = static_cast<jclass>(env->CallObjectMethod(ref_field, get_declaring_class_method)); +  if (env->ExceptionCheck()) { +    *out_klass = nullptr; +    env->DeleteLocalRef(field_klass); +    return false; +  } +  env->DeleteLocalRef(field_klass); +  return true; +} + +extern "C" JNIEXPORT void JNICALL Java_art_Trace_watchFieldModification( +    JNIEnv* env, +    jclass trace ATTRIBUTE_UNUSED, +    jobject field_obj) { +  jfieldID field; +  jclass klass; +  if (!GetFieldAndClass(env, field_obj, &klass, &field)) { +    return; +  } + +  JvmtiErrorToException(env, jvmti_env, jvmti_env->SetFieldModificationWatch(klass, field)); +  env->DeleteLocalRef(klass); +} + +extern "C" JNIEXPORT void JNICALL Java_art_Trace_watchFieldAccess( +    JNIEnv* env, +    jclass trace ATTRIBUTE_UNUSED, +    jobject field_obj) { +  jfieldID field; +  jclass klass; +  if (!GetFieldAndClass(env, field_obj, &klass, &field)) { +    return; +  } +  JvmtiErrorToException(env, jvmti_env, jvmti_env->SetFieldAccessWatch(klass, field)); +  env->DeleteLocalRef(klass); +} + +extern "C" JNIEXPORT void JNICALL Java_art_Trace_enableTracing(      JNIEnv* env,      jclass trace ATTRIBUTE_UNUSED,      jclass klass,      jobject enter,      jobject exit, +    jobject field_access, +    jobject field_modify,      jthread thr) {    TraceData* data = nullptr;    if (JvmtiErrorToException(env, @@ -228,8 +491,10 @@ extern "C" JNIEXPORT void JNICALL Java_art_Trace_enableMethodTracing(    }    memset(data, 0, sizeof(TraceData));    data->test_klass = reinterpret_cast<jclass>(env->NewGlobalRef(klass)); -  data->enter_method = env->FromReflectedMethod(enter); -  data->exit_method = env->FromReflectedMethod(exit); +  data->enter_method = enter != nullptr ? env->FromReflectedMethod(enter) : nullptr; +  data->exit_method = exit != nullptr ? env->FromReflectedMethod(exit) : nullptr; +  data->field_access = field_access != nullptr ? env->FromReflectedMethod(field_access) : nullptr; +  data->field_modify = field_modify != nullptr ? env->FromReflectedMethod(field_modify) : nullptr;    data->in_callback = false;    if (JvmtiErrorToException(env, jvmti_env, jvmti_env->SetEnvironmentLocalStorage(data))) { @@ -240,29 +505,62 @@ extern "C" JNIEXPORT void JNICALL Java_art_Trace_enableMethodTracing(    memset(&cb, 0, sizeof(cb));    cb.MethodEntry = methodEntryCB;    cb.MethodExit = methodExitCB; +  cb.FieldAccess = fieldAccessCB; +  cb.FieldModification = fieldModificationCB; +  cb.ClassPrepare = classPrepareCB;    if (JvmtiErrorToException(env, jvmti_env, jvmti_env->SetEventCallbacks(&cb, sizeof(cb)))) {      return;    } -  if (JvmtiErrorToException(env, +  if (enter != nullptr && +      JvmtiErrorToException(env,                              jvmti_env,                              jvmti_env->SetEventNotificationMode(JVMTI_ENABLE,                                                                  JVMTI_EVENT_METHOD_ENTRY,                                                                  thr))) {      return;    } -  if (JvmtiErrorToException(env, +  if (exit != nullptr && +      JvmtiErrorToException(env,                              jvmti_env,                              jvmti_env->SetEventNotificationMode(JVMTI_ENABLE,                                                                  JVMTI_EVENT_METHOD_EXIT,                                                                  thr))) {      return;    } +  if (field_access != nullptr && +      JvmtiErrorToException(env, +                            jvmti_env, +                            jvmti_env->SetEventNotificationMode(JVMTI_ENABLE, +                                                                JVMTI_EVENT_FIELD_ACCESS, +                                                                thr))) { +    return; +  } +  if (field_modify != nullptr && +      JvmtiErrorToException(env, +                            jvmti_env, +                            jvmti_env->SetEventNotificationMode(JVMTI_ENABLE, +                                                                JVMTI_EVENT_FIELD_MODIFICATION, +                                                                thr))) { +    return; +  }  } -extern "C" JNIEXPORT void JNICALL Java_art_Trace_disableMethodTracing( +extern "C" JNIEXPORT void JNICALL Java_art_Trace_disableTracing(      JNIEnv* env, jclass klass ATTRIBUTE_UNUSED, jthread thr) {    if (JvmtiErrorToException(env, jvmti_env,                              jvmti_env->SetEventNotificationMode(JVMTI_DISABLE, +                                                                JVMTI_EVENT_FIELD_ACCESS, +                                                                thr))) { +    return; +  } +  if (JvmtiErrorToException(env, jvmti_env, +                            jvmti_env->SetEventNotificationMode(JVMTI_DISABLE, +                                                                JVMTI_EVENT_FIELD_MODIFICATION, +                                                                thr))) { +    return; +  } +  if (JvmtiErrorToException(env, jvmti_env, +                            jvmti_env->SetEventNotificationMode(JVMTI_DISABLE,                                                                  JVMTI_EVENT_METHOD_ENTRY,                                                                  thr))) {      return; diff --git a/test/ti-stress/stress.cc b/test/ti-stress/stress.cc index 76f894325b..40fcc4f11d 100644 --- a/test/ti-stress/stress.cc +++ b/test/ti-stress/stress.cc @@ -39,6 +39,7 @@ struct StressData {    bool vm_class_loader_initialized;    bool trace_stress;    bool redefine_stress; +  bool field_stress;  };  static void WriteToFile(const std::string& fname, jint data_len, const unsigned char* data) { @@ -127,15 +128,33 @@ class ScopedClassInfo {        : jvmtienv_(jvmtienv),          class_(c),          name_(nullptr), -        generic_(nullptr) {} +        generic_(nullptr), +        file_(nullptr), +        debug_ext_(nullptr) {}    ~ScopedClassInfo() { -    jvmtienv_->Deallocate(reinterpret_cast<unsigned char*>(name_)); -    jvmtienv_->Deallocate(reinterpret_cast<unsigned char*>(generic_)); +    if (class_ != nullptr) { +      jvmtienv_->Deallocate(reinterpret_cast<unsigned char*>(name_)); +      jvmtienv_->Deallocate(reinterpret_cast<unsigned char*>(generic_)); +      jvmtienv_->Deallocate(reinterpret_cast<unsigned char*>(file_)); +      jvmtienv_->Deallocate(reinterpret_cast<unsigned char*>(debug_ext_)); +    }    }    bool Init() { -    return jvmtienv_->GetClassSignature(class_, &name_, &generic_) == JVMTI_ERROR_NONE; +    if (class_ == nullptr) { +      name_ = const_cast<char*>("<NONE>"); +      generic_ = const_cast<char*>("<NONE>"); +      return true; +    } else { +      jvmtiError ret1 = jvmtienv_->GetSourceFileName(class_, &file_); +      jvmtiError ret2 = jvmtienv_->GetSourceDebugExtension(class_, &debug_ext_); +      return jvmtienv_->GetClassSignature(class_, &name_, &generic_) == JVMTI_ERROR_NONE && +          ret1 != JVMTI_ERROR_MUST_POSSESS_CAPABILITY && +          ret1 != JVMTI_ERROR_INVALID_CLASS && +          ret2 != JVMTI_ERROR_MUST_POSSESS_CAPABILITY && +          ret2 != JVMTI_ERROR_INVALID_CLASS; +    }    }    jclass GetClass() const { @@ -147,12 +166,28 @@ class ScopedClassInfo {    const char* GetGeneric() const {      return generic_;    } +  const char* GetSourceDebugExtension() const { +    if (debug_ext_ == nullptr) { +      return "<UNKNOWN_SOURCE_DEBUG_EXTENSION>"; +    } else { +      return debug_ext_; +    } +  } +  const char* GetSourceFileName() const { +    if (file_ == nullptr) { +      return "<UNKNOWN_FILE>"; +    } else { +      return file_; +    } +  }   private:    jvmtiEnv* jvmtienv_;    jclass class_;    char* name_;    char* generic_; +  char* file_; +  char* debug_ext_;  };  class ScopedMethodInfo { @@ -165,7 +200,8 @@ class ScopedMethodInfo {          class_info_(nullptr),          name_(nullptr),          signature_(nullptr), -        generic_(nullptr) {} +        generic_(nullptr), +        first_line_(-1) {}    ~ScopedMethodInfo() {      env_->DeleteLocalRef(declaring_class_); @@ -179,6 +215,18 @@ class ScopedMethodInfo {        return false;      }      class_info_.reset(new ScopedClassInfo(jvmtienv_, declaring_class_)); +    jint nlines; +    jvmtiLineNumberEntry* lines; +    jvmtiError err = jvmtienv_->GetLineNumberTable(method_, &nlines, &lines); +    if (err == JVMTI_ERROR_NONE) { +      if (nlines > 0) { +        first_line_ = lines[0].line_number; +      } +      jvmtienv_->Deallocate(reinterpret_cast<unsigned char*>(lines)); +    } else if (err != JVMTI_ERROR_ABSENT_INFORMATION && +               err != JVMTI_ERROR_NATIVE_METHOD) { +      return false; +    }      return class_info_->Init() &&          (jvmtienv_->GetMethodName(method_, &name_, &signature_, &generic_) == JVMTI_ERROR_NONE);    } @@ -203,6 +251,10 @@ class ScopedMethodInfo {      return generic_;    } +  jint GetFirstLine() const { +    return first_line_; +  } +   private:    jvmtiEnv* jvmtienv_;    JNIEnv* env_; @@ -212,16 +264,84 @@ class ScopedMethodInfo {    char* name_;    char* signature_;    char* generic_; +  jint first_line_;    friend std::ostream& operator<<(std::ostream &os, ScopedMethodInfo const& m);  }; +class ScopedFieldInfo { + public: +  ScopedFieldInfo(jvmtiEnv* jvmtienv, jclass field_klass, jfieldID field) +      : jvmtienv_(jvmtienv), +        declaring_class_(field_klass), +        field_(field), +        class_info_(nullptr), +        name_(nullptr), +        type_(nullptr), +        generic_(nullptr) {} + +  ~ScopedFieldInfo() { +    jvmtienv_->Deallocate(reinterpret_cast<unsigned char*>(name_)); +    jvmtienv_->Deallocate(reinterpret_cast<unsigned char*>(type_)); +    jvmtienv_->Deallocate(reinterpret_cast<unsigned char*>(generic_)); +  } + +  bool Init() { +    class_info_.reset(new ScopedClassInfo(jvmtienv_, declaring_class_)); +    return class_info_->Init() && +        (jvmtienv_->GetFieldName( +            declaring_class_, field_, &name_, &type_, &generic_) == JVMTI_ERROR_NONE); +  } + +  const ScopedClassInfo& GetDeclaringClassInfo() const { +    return *class_info_; +  } + +  jclass GetDeclaringClass() const { +    return declaring_class_; +  } + +  const char* GetName() const { +    return name_; +  } + +  const char* GetType() const { +    return type_; +  } + +  const char* GetGeneric() const { +    return generic_; +  } + + private: +  jvmtiEnv* jvmtienv_; +  jclass declaring_class_; +  jfieldID field_; +  std::unique_ptr<ScopedClassInfo> class_info_; +  char* name_; +  char* type_; +  char* generic_; + +  friend std::ostream& operator<<(std::ostream &os, ScopedFieldInfo const& m); +}; + +std::ostream& operator<<(std::ostream &os, const ScopedFieldInfo* m) { +  return os << *m; +} + +std::ostream& operator<<(std::ostream &os, ScopedFieldInfo const& m) { +  return os << m.GetDeclaringClassInfo().GetName() << "->" << m.GetName() +            << ":" << m.GetType(); +} +  std::ostream& operator<<(std::ostream &os, const ScopedMethodInfo* m) {    return os << *m;  }  std::ostream& operator<<(std::ostream &os, ScopedMethodInfo const& m) { -  return os << m.GetDeclaringClassInfo().GetName() << "->" << m.GetName() << m.GetSignature(); +  return os << m.GetDeclaringClassInfo().GetName() << "->" << m.GetName() << m.GetSignature() +            << " (source: " << m.GetDeclaringClassInfo().GetSourceFileName() << ":" +            << m.GetFirstLine() << ")";  }  static void doJvmtiMethodBind(jvmtiEnv* jvmtienv, @@ -304,6 +424,100 @@ static std::string GetValOf(jvmtiEnv* env, JNIEnv* jnienv, std::string type, jva    }  } +void JNICALL FieldAccessHook(jvmtiEnv* jvmtienv, +                             JNIEnv* env, +                             jthread thread, +                             jmethodID m, +                             jlocation location, +                             jclass field_klass, +                             jobject object, +                             jfieldID field) { +  ScopedThreadInfo info(jvmtienv, env, thread); +  ScopedMethodInfo method_info(jvmtienv, env, m); +  ScopedFieldInfo field_info(jvmtienv, field_klass, field); +  jclass oklass = (object != nullptr) ? env->GetObjectClass(object) : nullptr; +  ScopedClassInfo obj_class_info(jvmtienv, oklass); +  if (!method_info.Init() || !field_info.Init() || !obj_class_info.Init()) { +    LOG(ERROR) << "Unable to get callback info!"; +    return; +  } +  LOG(INFO) << "ACCESS field \"" << field_info << "\" on object of " +            << "type \"" << obj_class_info.GetName() << "\" in method \"" << method_info +            << "\" at location 0x" << std::hex << location << ". Thread is \"" +            << info.GetName() << "\"."; +  env->DeleteLocalRef(oklass); +} + +static std::string PrintJValue(jvmtiEnv* jvmtienv, JNIEnv* env, char type, jvalue new_value) { +  std::ostringstream oss; +  switch (type) { +    case 'L': { +      jobject nv = new_value.l; +      if (nv == nullptr) { +        oss << "\"null\""; +      } else { +        jclass nv_klass = env->GetObjectClass(nv); +        ScopedClassInfo nv_class_info(jvmtienv, nv_klass); +        if (!nv_class_info.Init()) { +          oss << "with unknown type"; +        } else { +          oss << "of type \"" << nv_class_info.GetName() << "\""; +        } +        env->DeleteLocalRef(nv_klass); +      } +      break; +    } +    case 'Z': { +      if (new_value.z) { +        oss << "true"; +      } else { +        oss << "false"; +      } +      break; +    } +#define SEND_VALUE(chr, sym, type) \ +    case chr: { \ +      oss << static_cast<type>(new_value.sym); \ +      break; \ +    } +    SEND_VALUE('B', b, int8_t); +    SEND_VALUE('C', c, uint16_t); +    SEND_VALUE('S', s, int16_t); +    SEND_VALUE('I', i, int32_t); +    SEND_VALUE('J', j, int64_t); +    SEND_VALUE('F', f, float); +    SEND_VALUE('D', d, double); +#undef SEND_VALUE +  } +  return oss.str(); +} + +void JNICALL FieldModificationHook(jvmtiEnv* jvmtienv, +                                   JNIEnv* env, +                                   jthread thread, +                                   jmethodID m, +                                   jlocation location, +                                   jclass field_klass, +                                   jobject object, +                                   jfieldID field, +                                   char type, +                                   jvalue new_value) { +  ScopedThreadInfo info(jvmtienv, env, thread); +  ScopedMethodInfo method_info(jvmtienv, env, m); +  ScopedFieldInfo field_info(jvmtienv, field_klass, field); +  jclass oklass = (object != nullptr) ? env->GetObjectClass(object) : nullptr; +  ScopedClassInfo obj_class_info(jvmtienv, oklass); +  if (!method_info.Init() || !field_info.Init() || !obj_class_info.Init()) { +    LOG(ERROR) << "Unable to get callback info!"; +    return; +  } +  LOG(INFO) << "MODIFY field \"" << field_info << "\" on object of " +            << "type \"" << obj_class_info.GetName() << "\" in method \"" << method_info +            << "\" at location 0x" << std::hex << location << std::dec << ". New value is " +            << PrintJValue(jvmtienv, env, type, new_value) << ". Thread is \"" +            << info.GetName() << "\"."; +  env->DeleteLocalRef(oklass); +}  void JNICALL MethodExitHook(jvmtiEnv* jvmtienv,                              JNIEnv* env,                              jthread thread, @@ -342,14 +556,34 @@ void JNICALL ClassPrepareHook(jvmtiEnv* jvmtienv,                                JNIEnv* env,                                jthread thread,                                jclass klass) { -  ScopedThreadInfo info(jvmtienv, env, thread); -  ScopedClassInfo class_info(jvmtienv, klass); -  if (!class_info.Init()) { -    LOG(ERROR) << "Unable to get class info!"; -    return; +  StressData* data = nullptr; +  CHECK_EQ(jvmtienv->GetEnvironmentLocalStorage(reinterpret_cast<void**>(&data)), +           JVMTI_ERROR_NONE); +  if (data->field_stress) { +    jint nfields; +    jfieldID* fields; +    if (jvmtienv->GetClassFields(klass, &nfields, &fields) != JVMTI_ERROR_NONE) { +      LOG(ERROR) << "Unable to get a classes fields!"; +      return; +    } +    for (jint i = 0; i < nfields; i++) { +      jfieldID f = fields[i]; +      // Ignore errors +      jvmtienv->SetFieldAccessWatch(klass, f); +      jvmtienv->SetFieldModificationWatch(klass, f); +    } +    jvmtienv->Deallocate(reinterpret_cast<unsigned char*>(fields)); +  } +  if (data->trace_stress) { +    ScopedThreadInfo info(jvmtienv, env, thread); +    ScopedClassInfo class_info(jvmtienv, klass); +    if (!class_info.Init()) { +      LOG(ERROR) << "Unable to get class info!"; +      return; +    } +    LOG(INFO) << "Prepared class \"" << class_info.GetName() << "\". Thread is \"" +              << info.GetName() << "\"";    } -  LOG(INFO) << "Prepared class \"" << class_info.GetName() << "\". Thread is \"" -            << info.GetName() << "\"";  }  // The hook we are using. @@ -402,7 +636,7 @@ static std::string GetOption(const std::string& in) {  }  // Options are -// jvmti-stress,[redefine,${DEXTER_BINARY},${TEMP_FILE_1},${TEMP_FILE_2},][trace] +// jvmti-stress,[redefine,${DEXTER_BINARY},${TEMP_FILE_1},${TEMP_FILE_2},][trace,][field]  static void ReadOptions(StressData* data, char* options) {    std::string ops(options);    CHECK_EQ(GetOption(ops), "jvmti-stress") << "Options should start with jvmti-stress"; @@ -411,6 +645,8 @@ static void ReadOptions(StressData* data, char* options) {      std::string cur = GetOption(ops);      if (cur == "trace") {        data->trace_stress = true; +    } else if (cur == "field") { +      data->field_stress = true;      } else if (cur == "redefine") {        data->redefine_stress = true;        ops = AdvanceOption(ops); @@ -451,18 +687,54 @@ static void JNICALL PerformFinalSetupVMInit(jvmtiEnv *jvmti_env,      jni_env->DeleteLocalRef(klass);      data->vm_class_loader_initialized = true;    } -  if (data->trace_stress) { -    if (jvmti_env->SetEventNotificationMode(JVMTI_ENABLE, -                                            JVMTI_EVENT_METHOD_ENTRY, -                                            nullptr) != JVMTI_ERROR_NONE) { -      LOG(ERROR) << "Unable to enable JVMTI_EVENT_METHOD_ENTRY event!"; +} + +static bool WatchAllFields(JavaVM* vm, jvmtiEnv* jvmti) { +  if (jvmti->SetEventNotificationMode(JVMTI_ENABLE, +                                      JVMTI_EVENT_CLASS_PREPARE, +                                      nullptr) != JVMTI_ERROR_NONE) { +    LOG(ERROR) << "Couldn't set prepare event!"; +    return false; +  } +  // TODO We really shouldn't need to do this step here. +  jint nklass; +  jclass* klasses; +  if (jvmti->GetLoadedClasses(&nklass, &klasses) != JVMTI_ERROR_NONE) { +    LOG(WARNING) << "Couldn't get loaded classes! Ignoring."; +    return true; +  } +  JNIEnv* jni = nullptr; +  if (vm->GetEnv(reinterpret_cast<void**>(&jni), JNI_VERSION_1_6)) { +    LOG(ERROR) << "Unable to get jni env. Ignoring and potentially leaking jobjects."; +    return false; +  } +  for (jint i = 0; i < nklass; i++) { +    jclass k = klasses[i]; +    ScopedClassInfo sci(jvmti, k); +    if (sci.Init()) { +      LOG(INFO) << "NOTE: class " << sci.GetName() << " already loaded.";      } -    if (jvmti_env->SetEventNotificationMode(JVMTI_ENABLE, -                                        JVMTI_EVENT_METHOD_EXIT, -                                        nullptr) != JVMTI_ERROR_NONE) { -      LOG(ERROR) << "Unable to enable JVMTI_EVENT_METHOD_EXIT event!"; +    jint nfields; +    jfieldID* fields; +    jvmtiError err = jvmti->GetClassFields(k, &nfields, &fields); +    if (err == JVMTI_ERROR_NONE) { +      for (jint j = 0; j < nfields; j++) { +        jfieldID f = fields[j]; +        if (jvmti->SetFieldModificationWatch(k, f) != JVMTI_ERROR_NONE || +            jvmti->SetFieldAccessWatch(k, f) != JVMTI_ERROR_NONE) { +          LOG(ERROR) << "Unable to set watches on a field."; +          return false; +        } +      } +    } else if (err != JVMTI_ERROR_CLASS_NOT_PREPARED) { +      LOG(ERROR) << "Unexpected error getting class fields!"; +      return false;      } +    jvmti->Deallocate(reinterpret_cast<unsigned char*>(fields)); +    jni->DeleteLocalRef(k);    } +  jvmti->Deallocate(reinterpret_cast<unsigned char*>(klasses)); +  return true;  }  extern "C" JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM* vm, @@ -501,23 +773,27 @@ extern "C" JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM* vm,    cb.VMInit = PerformFinalSetupVMInit;    cb.MethodEntry = MethodEntryHook;    cb.MethodExit = MethodExitHook; +  cb.FieldAccess = FieldAccessHook; +  cb.FieldModification = FieldModificationHook;    cb.ClassPrepare = ClassPrepareHook;    if (jvmti->SetEventCallbacks(&cb, sizeof(cb)) != JVMTI_ERROR_NONE) {      LOG(ERROR) << "Unable to set class file load hook cb!";      return 1;    }    if (jvmti->SetEventNotificationMode(JVMTI_ENABLE, -                                      JVMTI_EVENT_NATIVE_METHOD_BIND, -                                      nullptr) != JVMTI_ERROR_NONE) { -    LOG(ERROR) << "Unable to enable JVMTI_EVENT_NATIVE_METHOD_BIND event!"; -    return 1; -  } -  if (jvmti->SetEventNotificationMode(JVMTI_ENABLE,                                        JVMTI_EVENT_VM_INIT,                                        nullptr) != JVMTI_ERROR_NONE) {      LOG(ERROR) << "Unable to enable JVMTI_EVENT_VM_INIT event!";      return 1;    } +  if (data->redefine_stress) { +    if (jvmti->SetEventNotificationMode(JVMTI_ENABLE, +                                        JVMTI_EVENT_CLASS_FILE_LOAD_HOOK, +                                        nullptr) != JVMTI_ERROR_NONE) { +      LOG(ERROR) << "Unable to enable CLASS_FILE_LOAD_HOOK event!"; +      return 1; +    } +  }    if (data->trace_stress) {      if (jvmti->SetEventNotificationMode(JVMTI_ENABLE,                                          JVMTI_EVENT_CLASS_PREPARE, @@ -525,12 +801,39 @@ extern "C" JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM* vm,        LOG(ERROR) << "Unable to enable CLASS_PREPARE event!";        return 1;      } +    if (jvmti->SetEventNotificationMode(JVMTI_ENABLE, +                                        JVMTI_EVENT_NATIVE_METHOD_BIND, +                                        nullptr) != JVMTI_ERROR_NONE) { +      LOG(ERROR) << "Unable to enable JVMTI_EVENT_NATIVE_METHOD_BIND event!"; +      return 1; +    } +    if (jvmti->SetEventNotificationMode(JVMTI_ENABLE, +                                        JVMTI_EVENT_METHOD_ENTRY, +                                        nullptr) != JVMTI_ERROR_NONE) { +      LOG(ERROR) << "Unable to enable JVMTI_EVENT_METHOD_ENTRY event!"; +      return 1; +    } +    if (jvmti->SetEventNotificationMode(JVMTI_ENABLE, +                                        JVMTI_EVENT_METHOD_EXIT, +                                        nullptr) != JVMTI_ERROR_NONE) { +      LOG(ERROR) << "Unable to enable JVMTI_EVENT_METHOD_EXIT event!"; +      return 1; +    }    } -  if (data->redefine_stress) { +  if (data->field_stress) {      if (jvmti->SetEventNotificationMode(JVMTI_ENABLE, -                                        JVMTI_EVENT_CLASS_FILE_LOAD_HOOK, +                                        JVMTI_EVENT_FIELD_MODIFICATION,                                          nullptr) != JVMTI_ERROR_NONE) { -      LOG(ERROR) << "Unable to enable CLASS_FILE_LOAD_HOOK event!"; +      LOG(ERROR) << "Unable to enable FIELD_MODIFICATION event!"; +      return 1; +    } +    if (jvmti->SetEventNotificationMode(JVMTI_ENABLE, +                                        JVMTI_EVENT_FIELD_ACCESS, +                                        nullptr) != JVMTI_ERROR_NONE) { +      LOG(ERROR) << "Unable to enable FIELD_ACCESS event!"; +      return 1; +    } +    if (!WatchAllFields(vm, jvmti)) {        return 1;      }    } @@ -24,6 +24,7 @@ LAUNCH_WRAPPER=  LIBART=libart.so  JIT_PROFILE="no"  VERBOSE="no" +EXTRA_OPTIONS=""  # Follow all sym links to get the program name.  if [ z"$BASH_SOURCE" != z ]; then @@ -147,6 +148,8 @@ while [[ "$1" = "-"* ]]; do      ;& # Fallthrough    --debug)      LIBART="libartd.so" +    # Expect that debug mode wants all checks. +    EXTRA_OPTIONS="${EXTRA_OPTIONS} -XX:SlowDebug=true"      ;;    --gdb)      LIBART="libartd.so" @@ -203,7 +206,6 @@ fi  LIBDIR="$(find_libdir $ART_BINARY_PATH)"  LD_LIBRARY_PATH=$ANDROID_ROOT/$LIBDIR -EXTRA_OPTIONS=""  # If ANDROID_DATA is the system ANDROID_DATA or is not set, use our own,  # and ensure we delete it at the end. diff --git a/tools/javac-helper.sh b/tools/javac-helper.sh new file mode 100755 index 0000000000..2d71d35d75 --- /dev/null +++ b/tools/javac-helper.sh @@ -0,0 +1,87 @@ +#!/bin/bash +# +# 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. + +# +# Calls javac with the -bootclasspath values passed in automatically. +# (This avoids having to manually set a boot class path). +# +# +# Script-specific args: +#   --mode=[host|target]: Select between host or target bootclasspath (default target). +#   --core-only:          Use only "core" bootclasspath (e.g. do not include framework). +#   --show-commands:      Print the desugar command being executed. +#   --help:               Print above list of args. +# +# All other args are forwarded to javac +# + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +TOP=$DIR/../.. + +if [[ -z $JAVAC ]]; then +  JAVAC=javac +fi + +bootjars_args= +mode=target +showcommands=n +while true; do +  case $1 in +    --help) +      echo "Usage: $0 [--mode=host|target] [--core-only] [--show-commands] <javac args>" +      exit 0 +      ;; +    --mode=host) +      bootjars_args="$bootjars_args --host" +      ;; +    --mode=target) +      bootjars_args="$bootjars_args --target" +      ;; +    --core-only) +      bootjars_args="$bootjars_args --core" +      ;; +    --show-commands) +      showcommands=y +      ;; +    *) +      break +      ;; +  esac +  shift +done + +javac_bootclasspath=() +boot_class_path_list=$($TOP/art/tools/bootjars.sh $bootjars_args --path) + + +for path in $boot_class_path_list; do +  javac_bootclasspath+=("$path") +done + +if [[ ${#javac_bootclasspath[@]} -eq 0 ]]; then +  echo "FATAL: Missing bootjars.sh file path list" >&2 +  exit 1 +fi + +function join_by { local IFS="$1"; shift; echo "$*"; } +bcp_arg="$(join_by ":" "${javac_bootclasspath[@]}")" +javac_args=(-bootclasspath "$bcp_arg") + +if [[ $showcommands == y ]]; then +  echo ${JAVAC} "${javac_args[@]}" "$@" +fi + +${JAVAC} "${javac_args[@]}" "$@" diff --git a/tools/run-jdwp-tests.sh b/tools/run-jdwp-tests.sh index f7427676eb..225fb394d1 100755 --- a/tools/run-jdwp-tests.sh +++ b/tools/run-jdwp-tests.sh @@ -154,7 +154,7 @@ debuggee_args="$debuggee_args -Xusejit:$use_jit"  if [[ $debug == "yes" ]]; then    art="$art -d"    art_debugee="$art_debugee -d" -  vm_args="$vm_args --vm-arg -XXlib:libartd.so" +  vm_args="$vm_args --vm-arg -XXlib:libartd.so --vm-arg -XX:SlowDebug=true"  fi  if [[ $verbose == "yes" ]]; then    # Enable JDWP logs in the debuggee. diff --git a/tools/run-libcore-tests.sh b/tools/run-libcore-tests.sh index 8b3df3a28c..6dcc23a9fc 100755 --- a/tools/run-libcore-tests.sh +++ b/tools/run-libcore-tests.sh @@ -127,7 +127,7 @@ while true; do    elif [[ "$1" == "--debug" ]]; then      # Remove the --debug from the arguments.      vogar_args=${vogar_args/$1} -    vogar_args="$vogar_args --vm-arg -XXlib:libartd.so" +    vogar_args="$vogar_args --vm-arg -XXlib:libartd.so --vm-arg -XX:SlowDebug=true"      debug=true      shift    elif [[ "$1" == "-Xgc:gcstress" ]]; then diff --git a/tools/runtime_memusage/prune_sanitizer_output.py b/tools/runtime_memusage/prune_sanitizer_output.py index 222c3c77b9..d95b2ced1c 100755 --- a/tools/runtime_memusage/prune_sanitizer_output.py +++ b/tools/runtime_memusage/prune_sanitizer_output.py @@ -21,86 +21,73 @@ from __future__ import division  from __future__ import print_function  import argparse +import re  import os -import sys - -STACK_DIVIDER = 65 * '=' - - -def has_min_lines(trace, stack_min_size): -    """Checks if the trace has a minimum amount of levels in trace.""" -    # Line containing 'use-after-poison' contains address accessed, which is -    # useful for extracting Dex File offsets -    string_checks = ['use-after-poison', 'READ'] -    required_checks = string_checks + ['#%d ' % line_ctr -                                       for line_ctr in -                                       range(stack_min_size) -                                       ] -    try: -        trace_indices = [trace.index(check) for check in required_checks] -        return all(trace_indices[trace_ind] < trace_indices[trace_ind + 1] -                   for trace_ind in range(len(trace_indices) - 1)) -    except ValueError: -        return False -    return True - - -def prune_exact(trace, stack_min_size): -    """Removes all of trace that comes after the (stack_min_size)th trace.""" -    string_checks = ['use-after-poison', 'READ'] -    required_checks = string_checks + ['#%d ' % line_ctr -                                       for line_ctr in -                                       range(stack_min_size) -                                       ] -    trace_indices = [trace.index(check) for check in required_checks] -    new_line_index = trace.index("\n", trace_indices[-1]) -    return trace[:new_line_index + 1] - - -def make_unique(trace): -    """Removes overlapping line numbers and lines out of order.""" -    string_checks = ['use-after-poison', 'READ'] -    hard_checks = string_checks + ['#%d ' % line_ctr -                                   for line_ctr in range(100) -                                   ] -    last_ind = -1 -    for str_check in hard_checks: -        try: -            location_ind = trace.index(str_check) -            if last_ind > location_ind: -                trace = trace[:trace[:location_ind].find("\n") + 1] -            last_ind = location_ind -            try: -                next_location_ind = trace.index(str_check, location_ind + 1) -                trace = trace[:next_location_ind] -            except ValueError: -                pass -        except ValueError: -            pass -    return trace - - -def parse_args(argv): -    """Parses arguments passed in.""" -    parser = argparse.ArgumentParser() -    parser.add_argument('-d', action='store', -                        default="", dest="out_dir_name", type=is_directory, -                        help='Output Directory') -    parser.add_argument('-e', action='store_true', -                        default=False, dest='check_exact', -                        help='Forces each trace to be cut to have ' -                             'minimum number of lines') -    parser.add_argument('-m', action='store', -                        default=4, dest='stack_min_size', type=int, -                        help='minimum number of lines a trace should have') -    parser.add_argument('trace_file', action='store', -                        type=argparse.FileType('r'), -                        help='File only containing lines that are related to ' -                             'Sanitizer traces') -    return parser.parse_args(argv) - - -def is_directory(path_name): + +STACK_DIVIDER = 65 * "=" + + +def match_to_int(match): +    """Returns trace line number matches as integers for sorting. +    Maps other matches to negative integers. +    """ +    # Hard coded string are necessary since each trace must have the address +    # accessed, which is printed before trace lines. +    if match == "use-after-poison": +        return -2 +    elif match == "READ": +        return -1 +    # Cutting off non-integer part of match +    return int(match[1:-1]) + + +def clean_trace_if_valid(trace, stack_min_size, prune_exact): +    """Cleans trace if it meets a certain standard. Returns None otherwise.""" +    # Sample input: +    #   trace: +    # "...ERROR: AddressSanitizer: use-after-poison on address 0x0071126a870a... +    # ...READ of size 2 at 0x0071126a870a thread T0 (droid.deskclock) +    # ...    #0 0x71281013b3  (/data/asan/system/lib64/libart.so+0x2263b3) +    # ...    #1 0x71280fe6b7  (/data/asan/system/lib64/libart.so+0x2236b7) +    # ...    #3 0x71280c22ef  (/data/asan/system/lib64/libart.so+0x1e72ef) +    # ...    #2 0x712810a84f  (/data/asan/system/lib64/libart.so+0x22f84f)" +    # +    #   stack_min_size: 2 +    #   prune_exact: False +    # +    # Sample output: +    # +    # "...ERROR: AddressSanitizer: use-after-poison on address 0x0071126a870a... +    # ...READ of size 2 at 0x0071126a870a thread T0 (droid.deskclock) +    # ...    #0 0x71281013b3  (/data/asan/system/lib64/libart.so+0x2263b3) +    # ...    #1 0x71280fe6b7  (/data/asan/system/lib64/libart.so+0x2236b7) +    # " + +    # Adds a newline character if not present at the end of trace +    trace = trace if trace[-1] == "\n" else trace + "\n" +    trace_line_matches = [(match_to_int(match.group()), match.start()) +                          for match in re.finditer("#[0-9]+ " +                                                   "|use-after-poison" +                                                   "|READ", trace) +                          ] +    # Finds the first index where the line number ordering isn't in sequence or +    # returns the number of matches if it everything is in order. +    bad_line_no = next((i - 2 for i, match in enumerate(trace_line_matches) +                        if i - 2 != match[0]), len(trace_line_matches) - 2) +    # If the number ordering breaks after minimum stack size, then the trace is +    # still valid. +    if bad_line_no >= stack_min_size: +        # Added if the trace is already clean +        trace_line_matches.append((trace_line_matches[-1][0] + 1, len(trace))) +        bad_match = trace_line_matches[bad_line_no + 2] +        if prune_exact: +            bad_match = trace_line_matches[stack_min_size + 2] +        # Up to new-line that comes before bad line number +        return trace[:trace.rindex("\n", 0, bad_match[1]) + 1] +    return None + + +def extant_directory(path_name):      """Checks if a path is an actual directory."""      if not os.path.isdir(path_name):          dir_error = "%s is not a directory" % (path_name) @@ -108,15 +95,32 @@ def is_directory(path_name):      return path_name -def main(argv=None): +def parse_args(): +    """Parses arguments passed in.""" +    parser = argparse.ArgumentParser() +    parser.add_argument("-d", action="store", +                        default="", dest="out_dir_name", type=extant_directory, +                        help="Output Directory") +    parser.add_argument("-e", action="store_true", +                        default=False, dest="check_exact", +                        help="Forces each trace to be cut to have " +                             "minimum number of lines") +    parser.add_argument("-m", action="store", +                        default=4, dest="stack_min_size", type=int, +                        help="minimum number of lines a trace should have") +    parser.add_argument("trace_file", action="store", +                        type=argparse.FileType("r"), +                        help="File only containing lines that are related to " +                             "Sanitizer traces") +    return parser.parse_args() + + +def main():      """Parses arguments and cleans up traces using other functions."""      stack_min_size = 4      check_exact = False -    if argv is None: -        argv = sys.argv - -    parsed_argv = parse_args(argv[1:]) +    parsed_argv = parse_args()      stack_min_size = parsed_argv.stack_min_size      check_exact = parsed_argv.check_exact      out_dir_name = parsed_argv.out_dir_name @@ -124,28 +128,15 @@ def main(argv=None):      trace_split = trace_file.read().split(STACK_DIVIDER)      trace_file.close() -    # if flag -e is enabled -    if check_exact: -        trace_prune_split = [prune_exact(trace, stack_min_size) -                             for trace in trace_split if -                             has_min_lines(trace, stack_min_size) -                             ] -        trace_unique_split = [make_unique(trace) -                              for trace in trace_prune_split -                              ] -    else: -        trace_unique_split = [make_unique(trace) -                              for trace in trace_split if -                              has_min_lines(trace, stack_min_size) -                              ] -    # has_min_lines is called again because removing lines can prune too much -    trace_clean_split = [trace for trace -                         in trace_unique_split if -                         has_min_lines(trace, -                                       stack_min_size) +    trace_clean_split = [clean_trace_if_valid(trace, +                                              stack_min_size, +                                              check_exact) +                         for trace in trace_split                           ] +    trace_clean_split = [trace for trace in trace_clean_split +                         if trace is not None] -    outfile = os.path.join(out_dir_name, trace_file.name + '_filtered') +    outfile = os.path.join(out_dir_name, trace_file.name + "_filtered")      with open(outfile, "w") as output_file:          output_file.write(STACK_DIVIDER.join(trace_clean_split)) diff --git a/tools/runtime_memusage/symbol_trace_info.py b/tools/runtime_memusage/symbol_trace_info.py index 57ed6ce954..e539be2217 100755 --- a/tools/runtime_memusage/symbol_trace_info.py +++ b/tools/runtime_memusage/symbol_trace_info.py @@ -120,23 +120,23 @@ def is_directory(path_name):  def parse_args(argv):      """Parses arguments passed in."""      parser = argparse.ArgumentParser() -    parser.add_argument('-d', action='store', +    parser.add_argument("-d", action="store",                          default="", dest="out_dir_name", type=is_directory, -                        help='Output Directory') -    parser.add_argument('sanitizer_trace', action='store', -                        type=argparse.FileType('r'), -                        help='File containing sanitizer traces filtered by ' -                             'prune_sanitizer_output.py') -    parser.add_argument('symbol_trace', action='store', -                        type=argparse.FileType('r'), -                        help='File containing symbolized traces that match ' -                             'sanitizer_trace') -    parser.add_argument('dex_starts', action='store', -                        type=argparse.FileType('r'), -                        help='File containing starting addresses of Dex Files') -    parser.add_argument('categories', action='store', nargs='*', -                        help='Keywords expected to show in large amounts of' -                             ' symbolized traces') +                        help="Output Directory") +    parser.add_argument("sanitizer_trace", action="store", +                        type=argparse.FileType("r"), +                        help="File containing sanitizer traces filtered by " +                             "prune_sanitizer_output.py") +    parser.add_argument("symbol_trace", action="store", +                        type=argparse.FileType("r"), +                        help="File containing symbolized traces that match " +                             "sanitizer_trace") +    parser.add_argument("dex_starts", action="store", +                        type=argparse.FileType("r"), +                        help="File containing starting addresses of Dex Files") +    parser.add_argument("categories", action="store", nargs="*", +                        help="Keywords expected to show in large amounts of" +                             " symbolized traces")      return parser.parse_args(argv) @@ -177,6 +177,10 @@ def read_data(parsed_argv):                                      for line in dex_start_file_data                                      if "RegisterDexFile" in line                                      ] +    # Dex File Starting addresses must be sorted because bisect requires sorted +    # lists. +    data_lists["dex_start_list"].sort() +      return data_lists, categories, symbol_file_split @@ -210,5 +214,5 @@ def main(argv=None):      print_categories(categories, symbol_file_split, parsed_argv.out_dir_name) -if __name__ == '__main__': +if __name__ == "__main__":      main()  |