summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--build/Android.bp10
-rw-r--r--dexlayout/dexlayout.cc4
-rw-r--r--imgdiag/imgdiag.cc884
-rw-r--r--profman/profman.cc2
-rw-r--r--runtime/Android.bp2
-rw-r--r--runtime/art_method.cc2
-rw-r--r--runtime/art_method.h4
-rw-r--r--runtime/base/logging.cc49
-rw-r--r--runtime/base/logging.h37
-rw-r--r--runtime/base/logging_test.cc59
-rw-r--r--runtime/common_dex_operations.h21
-rw-r--r--runtime/common_runtime_test.cc1
-rw-r--r--runtime/interpreter/interpreter_common.cc25
-rw-r--r--runtime/jit/profile_compilation_info.cc40
-rw-r--r--runtime/jit/profile_compilation_info.h40
-rw-r--r--runtime/jit/profile_compilation_info_test.cc10
-rw-r--r--runtime/jit/profile_saver.cc2
-rw-r--r--runtime/method_handles.cc2
-rw-r--r--runtime/openjdkjvmti/OpenjdkJvmTi.cc36
-rw-r--r--runtime/openjdkjvmti/art_jvmti.h22
-rw-r--r--runtime/openjdkjvmti/events-inl.h66
-rw-r--r--runtime/openjdkjvmti/events.cc110
-rw-r--r--runtime/openjdkjvmti/ti_class.cc57
-rw-r--r--runtime/openjdkjvmti/ti_class.h6
-rw-r--r--runtime/openjdkjvmti/ti_field.cc64
-rw-r--r--runtime/openjdkjvmti/ti_field.h5
-rw-r--r--runtime/parsed_options.cc8
-rw-r--r--runtime/read_barrier.cc24
-rw-r--r--runtime/read_barrier.h12
-rw-r--r--runtime/runtime_options.def2
-rw-r--r--runtime/vdex_file.h3
-rw-r--r--test/004-NativeAllocations/src-art/Main.java (renamed from test/004-NativeAllocations/src/Main.java)0
-rw-r--r--test/138-duplicate-classes-check/src-art/A.java (renamed from test/138-duplicate-classes-check/src/A.java)0
-rw-r--r--test/138-duplicate-classes-check/src-art/Main.java (renamed from test/138-duplicate-classes-check/src/Main.java)0
-rw-r--r--test/146-bad-interface/src-art/Main.java (renamed from test/146-bad-interface/src/Main.java)0
-rw-r--r--test/157-void-class/src-art/Main.java (renamed from test/157-void-class/src/Main.java)0
-rw-r--r--test/596-monitor-inflation/src-art/Main.java (renamed from test/596-monitor-inflation/src/Main.java)0
-rw-r--r--test/612-jit-dex-cache/src-art/A.java (renamed from test/612-jit-dex-cache/src/A.java)0
-rw-r--r--test/612-jit-dex-cache/src-art/B.java (renamed from test/612-jit-dex-cache/src/B.java)0
-rw-r--r--test/612-jit-dex-cache/src-art/Main.java (renamed from test/612-jit-dex-cache/src/Main.java)0
-rw-r--r--test/613-inlining-dex-cache/src-art/B.java (renamed from test/613-inlining-dex-cache/src/B.java)0
-rw-r--r--test/613-inlining-dex-cache/src-art/Main.java (renamed from test/613-inlining-dex-cache/src/Main.java)0
-rw-r--r--test/909-attach-agent/src-art/Main.java (renamed from test/909-attach-agent/src/Main.java)0
-rw-r--r--test/912-classes/src-art/Main.java (renamed from test/912-classes/src/Main.java)0
-rw-r--r--test/912-classes/src-art/art/DexData.java (renamed from test/912-classes/src/art/DexData.java)0
-rw-r--r--test/912-classes/src-art/art/Main.java (renamed from test/912-classes/src/art/Main.java)0
-rw-r--r--test/912-classes/src-art/art/Test912.java (renamed from test/912-classes/src/art/Test912.java)0
-rw-r--r--test/912-classes/src-art/art/Test912Art.java (renamed from test/912-classes/src/art/Test912Art.java)0
-rw-r--r--test/952-invoke-custom/src-art/Main.java (renamed from test/952-invoke-custom/src/Main.java)0
-rw-r--r--test/952-invoke-custom/src-art/TestDataInvokeCustomWithConcurrentThreads.java (renamed from test/952-invoke-custom/src/TestDataInvokeCustomWithConcurrentThreads.java)0
-rw-r--r--test/952-invoke-custom/src-art/TestDataLinkerMethodMinimalArguments.java (renamed from test/952-invoke-custom/src/TestDataLinkerMethodMinimalArguments.java)0
-rw-r--r--test/952-invoke-custom/src-art/TestDataLinkerMethodMultipleArgumentTypes.java (renamed from test/952-invoke-custom/src/TestDataLinkerMethodMultipleArgumentTypes.java)0
-rw-r--r--test/958-methodhandle-stackframe/src-art/Main.java (renamed from test/958-methodhandle-stackframe/src/Main.java)0
-rw-r--r--test/981-dedup-original-dex/src-art/Main.java (renamed from test/981-dedup-original-dex/src/Main.java)0
-rw-r--r--test/981-dedup-original-dex/src-art/art/Redefinition.java (renamed from test/981-dedup-original-dex/src/art/Redefinition.java)0
-rw-r--r--test/981-dedup-original-dex/src-art/art/Test981.java (renamed from test/981-dedup-original-dex/src/art/Test981.java)0
-rw-r--r--test/988-method-trace/expected.txt37
-rw-r--r--test/988-method-trace/src/art/Test988.java8
-rw-r--r--test/988-method-trace/src/art/Trace.java30
-rw-r--r--test/989-method-trace-throw/src/art/Test989.java8
-rw-r--r--test/989-method-trace-throw/src/art/Trace.java30
-rw-r--r--test/990-field-trace/expected.txt52
-rw-r--r--test/990-field-trace/info.txt1
-rwxr-xr-xtest/990-field-trace/run18
-rw-r--r--test/990-field-trace/src/Main.java21
-rw-r--r--test/990-field-trace/src/art/Test990.java232
-rw-r--r--test/990-field-trace/src/art/Trace.java49
-rw-r--r--test/991-field-trace-2/expected.txt118
-rw-r--r--test/991-field-trace-2/field_trace.cc59
-rw-r--r--test/991-field-trace-2/info.txt5
-rwxr-xr-xtest/991-field-trace-2/run18
-rw-r--r--test/991-field-trace-2/src/Main.java21
-rw-r--r--test/991-field-trace-2/src/art/Test991.java219
-rw-r--r--test/991-field-trace-2/src/art/Trace.java49
-rw-r--r--test/992-source-data/expected.txt10
-rw-r--r--test/992-source-data/info.txt1
-rwxr-xr-xtest/992-source-data/run17
-rw-r--r--test/992-source-data/source_file.cc53
-rw-r--r--test/992-source-data/src/Main.java21
-rw-r--r--test/992-source-data/src/art/Test2.java19
-rw-r--r--test/992-source-data/src/art/Test992.java47
-rw-r--r--test/Android.bp2
-rw-r--r--test/Android.run-test-jvmti-java-library.mk6
-rwxr-xr-xtest/etc/default-build46
-rwxr-xr-xtest/etc/run-test-jar11
-rw-r--r--test/knownfailures.json25
-rwxr-xr-xtest/run-test7
-rwxr-xr-xtest/testrunner/testrunner.py9
-rw-r--r--test/ti-agent/common_helper.cc340
-rw-r--r--test/ti-stress/stress.cc367
-rw-r--r--tools/art4
-rwxr-xr-xtools/javac-helper.sh87
-rwxr-xr-xtools/run-jdwp-tests.sh2
-rwxr-xr-xtools/run-libcore-tests.sh2
-rwxr-xr-xtools/runtime_memusage/prune_sanitizer_output.py199
-rwxr-xr-xtools/runtime_memusage/symbol_trace_info.py38
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,
+ &region_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;
}
}
diff --git a/tools/art b/tools/art
index 0bc08f0fce..2e5df91bfd 100644
--- a/tools/art
+++ b/tools/art
@@ -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()