diff options
64 files changed, 1271 insertions, 153 deletions
diff --git a/build/Android.gtest.mk b/build/Android.gtest.mk index 7ca7409f88..3b2a5bef26 100644 --- a/build/Android.gtest.mk +++ b/build/Android.gtest.mk @@ -172,6 +172,7 @@ ART_GTEST_class_loader_context_test_DEX_DEPS := Main MultiDex MyClass ForClassLo ART_GTEST_class_table_test_DEX_DEPS := XandY ART_GTEST_compiler_driver_test_DEX_DEPS := AbstractMethod StaticLeafMethods ProfileTestMultiDex ART_GTEST_dex_cache_test_DEX_DEPS := Main Packages MethodTypes +ART_GTEST_dexanalyze_test_DEX_DEPS := MultiDex ART_GTEST_dexlayout_test_DEX_DEPS := ManyMethods ART_GTEST_dex2oat_test_DEX_DEPS := $(ART_GTEST_dex2oat_environment_tests_DEX_DEPS) ManyMethods Statics VerifierDeps MainUncompressed EmptyUncompressed ART_GTEST_dex2oat_image_test_DEX_DEPS := $(ART_GTEST_dex2oat_environment_tests_DEX_DEPS) Statics VerifierDeps @@ -311,6 +312,12 @@ ART_GTEST_imgdiag_test_TARGET_DEPS := \ $(TARGET_CORE_IMAGE_DEFAULT_32) \ imgdiagd-target +# Dex analyze test requires dexanalyze. +ART_GTEST_dexanalyze_test_HOST_DEPS := \ + dexanalyze-host +ART_GTEST_dexanalyze_test_TARGET_DEPS := \ + dexanalyze-target + # Oatdump test requires an image and oatfile to dump. ART_GTEST_oatdump_test_HOST_DEPS := \ $(HOST_CORE_IMAGE_DEFAULT_64) \ @@ -353,6 +360,7 @@ ART_TEST_MODULES := \ art_compiler_tests \ art_compiler_host_tests \ art_dex2oat_tests \ + art_dexanalyze_tests \ art_dexdiag_tests \ art_dexdump_tests \ art_dexlayout_tests \ @@ -798,6 +806,7 @@ ART_GTEST_jni_internal_test_DEX_DEPS := ART_GTEST_oat_file_assistant_test_DEX_DEPS := ART_GTEST_oat_file_assistant_test_HOST_DEPS := ART_GTEST_oat_file_assistant_test_TARGET_DEPS := +ART_GTEST_dexanalyze_test_DEX_DEPS := ART_GTEST_dexoptanalyzer_test_DEX_DEPS := ART_GTEST_dexoptanalyzer_test_HOST_DEPS := ART_GTEST_dexoptanalyzer_test_TARGET_DEPS := diff --git a/compiler/driver/compiler_driver.h b/compiler/driver/compiler_driver.h index 682cf16d45..a5462eefe2 100644 --- a/compiler/driver/compiler_driver.h +++ b/compiler/driver/compiler_driver.h @@ -378,11 +378,11 @@ class CompilerDriver { if (!android::base::EndsWith(boot_image_filename, ".art")) { return false; } - size_t dash_pos = boot_image_filename.find_last_of("-/"); - if (dash_pos == std::string::npos || boot_image_filename[dash_pos] != '-') { - return false; + size_t slash_pos = boot_image_filename.rfind('/'); + if (slash_pos == std::string::npos) { + return android::base::StartsWith(boot_image_filename, "core-"); } - return (dash_pos >= 4u) && (boot_image_filename.compare(dash_pos - 4u, 4u, "core") == 0); + return boot_image_filename.compare(slash_pos + 1, 5u, "core-") == 0; } optimizer::DexToDexCompiler& GetDexToDexCompiler() { diff --git a/compiler/verifier_deps_test.cc b/compiler/verifier_deps_test.cc index 76448d819c..553d131e2f 100644 --- a/compiler/verifier_deps_test.cc +++ b/compiler/verifier_deps_test.cc @@ -18,6 +18,7 @@ #include "verifier/verifier_deps.h" #include "art_method-inl.h" +#include "base/indenter.h" #include "class_linker.h" #include "common_compiler_test.h" #include "compiler_callbacks.h" @@ -28,7 +29,6 @@ #include "driver/compiler_driver-inl.h" #include "driver/compiler_options.h" #include "handle_scope-inl.h" -#include "indenter.h" #include "mirror/class_loader.h" #include "runtime.h" #include "scoped_thread_state_change-inl.h" diff --git a/dex2oat/dex2oat.cc b/dex2oat/dex2oat.cc index ee570ad4de..693ead56bb 100644 --- a/dex2oat/dex2oat.cc +++ b/dex2oat/dex2oat.cc @@ -428,6 +428,10 @@ NO_RETURN static void Usage(const char* fmt, ...) { UsageError(" --class-loader-context=<string spec>: a string specifying the intended"); UsageError(" runtime loading context for the compiled dex files."); UsageError(""); + UsageError(" --stored-class-loader-context=<string spec>: a string specifying the intended"); + UsageError(" runtime loading context that is stored in the oat file. Overrides"); + UsageError(" --class-loader-context. Note that this ignores the classpath_dir arg."); + UsageError(""); UsageError(" It describes how the class loader chain should be built in order to ensure"); UsageError(" classes are resolved during dex2aot as they would be resolved at runtime."); UsageError(" This spec will be encoded in the oat file. If at runtime the dex file is"); @@ -1260,11 +1264,32 @@ class Dex2Oat FINAL { ParseInstructionSetFeatures(*args.Get(M::TargetInstructionSetFeatures), parser_options.get()); } if (args.Exists(M::ClassLoaderContext)) { - class_loader_context_ = ClassLoaderContext::Create(*args.Get(M::ClassLoaderContext)); + std::string class_loader_context_arg = *args.Get(M::ClassLoaderContext); + class_loader_context_ = ClassLoaderContext::Create(class_loader_context_arg); if (class_loader_context_ == nullptr) { Usage("Option --class-loader-context has an incorrect format: %s", - args.Get(M::ClassLoaderContext)->c_str()); + class_loader_context_arg.c_str()); + } + if (args.Exists(M::StoredClassLoaderContext)) { + stored_class_loader_context_.reset(new std::string(*args.Get(M::StoredClassLoaderContext))); + std::unique_ptr<ClassLoaderContext> temp_context = + ClassLoaderContext::Create(*stored_class_loader_context_); + if (temp_context == nullptr) { + Usage("Option --stored-class-loader-context has an incorrect format: %s", + stored_class_loader_context_->c_str()); + } else if (!class_loader_context_->VerifyClassLoaderContextMatch( + *stored_class_loader_context_, + /*verify_names*/ false, + /*verify_checksums*/ false)) { + Usage( + "Option --stored-class-loader-context '%s' mismatches --class-loader-context '%s'", + stored_class_loader_context_->c_str(), + class_loader_context_arg.c_str()); + } } + } else if (args.Exists(M::StoredClassLoaderContext)) { + Usage("Option --stored-class-loader-context should only be used if " + "--class-loader-context is also specified"); } if (!ReadCompilerOptions(args, compiler_options_.get(), &error_msg)) { @@ -1579,7 +1604,7 @@ class Dex2Oat FINAL { if (class_loader_context_ == nullptr) { // If no context was specified use the default one (which is an empty PathClassLoader). - class_loader_context_ = std::unique_ptr<ClassLoaderContext>(ClassLoaderContext::Default()); + class_loader_context_ = ClassLoaderContext::Default(); } DCHECK_EQ(oat_writers_.size(), 1u); @@ -1605,8 +1630,15 @@ class Dex2Oat FINAL { } // Store the class loader context in the oat header. - key_value_store_->Put(OatHeader::kClassPathKey, - class_loader_context_->EncodeContextForOatFile(classpath_dir_)); + // TODO: deprecate this since store_class_loader_context should be enough to cover the users + // of classpath_dir as well. + std::string class_path_key; + if (stored_class_loader_context_ != nullptr) { + class_path_key = *stored_class_loader_context_; + } else { + class_path_key = class_loader_context_->EncodeContextForOatFile(classpath_dir_); + } + key_value_store_->Put(OatHeader::kClassPathKey, class_path_key); } // Now that we have finalized key_value_store_, start writing the oat file. @@ -2026,7 +2058,7 @@ class Dex2Oat FINAL { // We need to prepare method offsets in the image address space for direct method patching. TimingLogger::ScopedTiming t2("dex2oat Prepare image address space", timings_); - if (!image_writer_->PrepareImageAddressSpace()) { + if (!image_writer_->PrepareImageAddressSpace(timings_)) { LOG(ERROR) << "Failed to prepare image address space."; return false; } @@ -2855,6 +2887,9 @@ class Dex2Oat FINAL { // The spec describing how the class loader should be setup for compilation. std::unique_ptr<ClassLoaderContext> class_loader_context_; + // The class loader context stored in the oat file. May be equal to class_loader_context_. + std::unique_ptr<std::string> stored_class_loader_context_; + size_t thread_count_; uint64_t start_ns_; uint64_t start_cputime_ns_; diff --git a/dex2oat/dex2oat_options.cc b/dex2oat/dex2oat_options.cc index 0d68f4fab6..5843691a24 100644 --- a/dex2oat/dex2oat_options.cc +++ b/dex2oat/dex2oat_options.cc @@ -245,6 +245,9 @@ static Parser CreateArgumentParser() { .Define("--class-loader-context=_") .WithType<std::string>() .IntoKey(M::ClassLoaderContext) + .Define("--stored-class-loader-context=_") + .WithType<std::string>() + .IntoKey(M::StoredClassLoaderContext) .Define("--compact-dex-level=_") .WithType<CompactDexLevel>() .WithValueMap({{"none", CompactDexLevel::kCompactDexLevelNone}, diff --git a/dex2oat/dex2oat_options.def b/dex2oat/dex2oat_options.def index 01f9d9425f..1a913a9bbf 100644 --- a/dex2oat/dex2oat_options.def +++ b/dex2oat/dex2oat_options.def @@ -88,6 +88,7 @@ DEX2OAT_OPTIONS_KEY (std::string, NoInlineFrom) DEX2OAT_OPTIONS_KEY (Unit, ForceDeterminism) DEX2OAT_OPTIONS_KEY (std::string, ClasspathDir) DEX2OAT_OPTIONS_KEY (std::string, ClassLoaderContext) +DEX2OAT_OPTIONS_KEY (std::string, StoredClassLoaderContext) DEX2OAT_OPTIONS_KEY (std::string, DirtyImageObjects) DEX2OAT_OPTIONS_KEY (std::vector<std::string>, RuntimeOptions) DEX2OAT_OPTIONS_KEY (std::string, CompilationReason) diff --git a/dex2oat/dex2oat_test.cc b/dex2oat/dex2oat_test.cc index c890f8bef0..710a6af792 100644 --- a/dex2oat/dex2oat_test.cc +++ b/dex2oat/dex2oat_test.cc @@ -2125,4 +2125,33 @@ TEST_F(Dex2oatTest, AppImageNoProfile) { EXPECT_EQ(header.GetImageSection(ImageHeader::kSectionArtFields).Size(), 0u); } +TEST_F(Dex2oatClassLoaderContextTest, StoredClassLoaderContext) { + const std::string out_dir = GetScratchDir(); + const std::string odex_location = out_dir + "/base.odex"; + const std::string valid_context = "PCL[" + GetUsedDexLocation() + "]"; + const std::string stored_context = "PCL[/system/not_real_lib.jar]"; + // The class path should not be valid and should fail being stored. + GenerateOdexForTest(GetTestDexFileName("ManyMethods"), + odex_location, + CompilerFilter::Filter::kQuicken, + { "--class-loader-context=" + stored_context }, + true, // expect_success + false, // use_fd + [&](const OatFile& oat_file) { + EXPECT_NE(oat_file.GetClassLoaderContext(), stored_context); + EXPECT_NE(oat_file.GetClassLoaderContext(), valid_context); + }); + // The stored context should match what we expect even though it's invalid. + GenerateOdexForTest(GetTestDexFileName("ManyMethods"), + odex_location, + CompilerFilter::Filter::kQuicken, + { "--class-loader-context=" + valid_context, + "--stored-class-loader-context=" + stored_context }, + true, // expect_success + false, // use_fd + [&](const OatFile& oat_file) { + EXPECT_EQ(oat_file.GetClassLoaderContext(), stored_context); + }); +} + } // namespace art diff --git a/dex2oat/linker/image_test.h b/dex2oat/linker/image_test.h index 476a843821..a95252d3ed 100644 --- a/dex2oat/linker/image_test.h +++ b/dex2oat/linker/image_test.h @@ -293,7 +293,7 @@ inline void CompilationHelper::Compile(CompilerDriver* driver, ASSERT_TRUE(cur_opened_dex_files.empty()); } } - bool image_space_ok = writer->PrepareImageAddressSpace(); + bool image_space_ok = writer->PrepareImageAddressSpace(&timings); ASSERT_TRUE(image_space_ok); DCHECK_EQ(vdex_files.size(), oat_files.size()); diff --git a/dex2oat/linker/image_writer.cc b/dex2oat/linker/image_writer.cc index 6530ead2d1..c7a30a06ed 100644 --- a/dex2oat/linker/image_writer.cc +++ b/dex2oat/linker/image_writer.cc @@ -133,23 +133,31 @@ static void ClearDexFileCookies() REQUIRES_SHARED(Locks::mutator_lock_) { Runtime::Current()->GetHeap()->VisitObjects(visitor); } -bool ImageWriter::PrepareImageAddressSpace() { +bool ImageWriter::PrepareImageAddressSpace(TimingLogger* timings) { target_ptr_size_ = InstructionSetPointerSize(compiler_driver_.GetInstructionSet()); gc::Heap* const heap = Runtime::Current()->GetHeap(); { ScopedObjectAccess soa(Thread::Current()); - PruneNonImageClasses(); // Remove junk + { + TimingLogger::ScopedTiming t("PruneNonImageClasses", timings); + PruneNonImageClasses(); // Remove junk + } if (compile_app_image_) { + TimingLogger::ScopedTiming t("ClearDexFileCookies", timings); // Clear dex file cookies for app images to enable app image determinism. This is required // since the cookie field contains long pointers to DexFiles which are not deterministic. // b/34090128 ClearDexFileCookies(); } else { + TimingLogger::ScopedTiming t("ComputeLazyFieldsForImageClasses", timings); // Avoid for app image since this may increase RAM and image size. ComputeLazyFieldsForImageClasses(); // Add useful information } } - heap->CollectGarbage(/* clear_soft_references */ false); // Remove garbage. + { + TimingLogger::ScopedTiming t("CollectGarbage", timings); + heap->CollectGarbage(/* clear_soft_references */ false); // Remove garbage. + } if (kIsDebugBuild) { ScopedObjectAccess soa(Thread::Current()); @@ -157,12 +165,14 @@ bool ImageWriter::PrepareImageAddressSpace() { } { + TimingLogger::ScopedTiming t("CalculateNewObjectOffsets", timings); ScopedObjectAccess soa(Thread::Current()); CalculateNewObjectOffsets(); } // This needs to happen after CalculateNewObjectOffsets since it relies on intern_table_bytes_ and // bin size sums being calculated. + TimingLogger::ScopedTiming t("AllocMemory", timings); if (!AllocMemory()) { return false; } diff --git a/dex2oat/linker/image_writer.h b/dex2oat/linker/image_writer.h index c67835b455..197253e102 100644 --- a/dex2oat/linker/image_writer.h +++ b/dex2oat/linker/image_writer.h @@ -64,6 +64,7 @@ class ClassLoader; class ClassLoaderVisitor; class ImTable; class ImtConflictTable; +class TimingLogger; static constexpr int kInvalidFd = -1; @@ -81,7 +82,7 @@ class ImageWriter FINAL { const std::unordered_map<const DexFile*, size_t>& dex_file_oat_index_map, const std::unordered_set<std::string>* dirty_image_objects); - bool PrepareImageAddressSpace(); + bool PrepareImageAddressSpace(TimingLogger* timings); bool IsImageAddressSpaceReady() const { DCHECK(!image_infos_.empty()); diff --git a/dex2oat/linker/oat_writer.cc b/dex2oat/linker/oat_writer.cc index bcc59098e5..690b565090 100644 --- a/dex2oat/linker/oat_writer.cc +++ b/dex2oat/linker/oat_writer.cc @@ -40,6 +40,7 @@ #include "dex/dex_file_loader.h" #include "dex/dex_file_types.h" #include "dex/standard_dex_file.h" +#include "dex/type_lookup_table.h" #include "dex/verification_results.h" #include "dex_container.h" #include "dexlayout.h" @@ -63,7 +64,6 @@ #include "oat_quick_method_header.h" #include "quicken_info.h" #include "scoped_thread_state_change-inl.h" -#include "type_lookup_table.h" #include "utils/dex_cache_arrays_layout-inl.h" #include "vdex_file.h" #include "verifier/verifier_deps.h" diff --git a/dexlayout/dexlayout.cc b/dexlayout/dexlayout.cc index ec0cbe6a60..da1573cef1 100644 --- a/dexlayout/dexlayout.cc +++ b/dexlayout/dexlayout.cc @@ -49,7 +49,6 @@ #include "dex_visualize.h" #include "dex_writer.h" #include "jit/profile_compilation_info.h" -#include "mem_map.h" namespace art { diff --git a/libartbase/Android.bp b/libartbase/Android.bp index 62157e196d..e68c2fba5b 100644 --- a/libartbase/Android.bp +++ b/libartbase/Android.bp @@ -104,6 +104,7 @@ art_cc_test { "base/hash_set_test.cc", "base/hex_dump_test.cc", "base/histogram_test.cc", + "base/indenter_test.cc", "base/leb128_test.cc", "base/logging_test.cc", "base/memory_region_test.cc", diff --git a/runtime/indenter.h b/libartbase/base/indenter.h index 6361dd2092..850b7c4f73 100644 --- a/runtime/indenter.h +++ b/libartbase/base/indenter.h @@ -14,8 +14,8 @@ * limitations under the License. */ -#ifndef ART_RUNTIME_INDENTER_H_ -#define ART_RUNTIME_INDENTER_H_ +#ifndef ART_LIBARTBASE_BASE_INDENTER_H_ +#define ART_LIBARTBASE_BASE_INDENTER_H_ #include <ostream> #include <streambuf> @@ -160,4 +160,4 @@ class ScopedIndentation { } // namespace art -#endif // ART_RUNTIME_INDENTER_H_ +#endif // ART_LIBARTBASE_BASE_INDENTER_H_ diff --git a/runtime/indenter_test.cc b/libartbase/base/indenter_test.cc index 09c0c54e5a..09c0c54e5a 100644 --- a/runtime/indenter_test.cc +++ b/libartbase/base/indenter_test.cc diff --git a/libartbase/base/logging_test.cc b/libartbase/base/logging_test.cc index 404e080b03..1456eb30fa 100644 --- a/libartbase/base/logging_test.cc +++ b/libartbase/base/logging_test.cc @@ -21,7 +21,7 @@ #include "android-base/logging.h" #include "base/bit_utils.h" #include "base/macros.h" -#include "common_runtime_test.h" +#include "gtest/gtest.h" #include "runtime_debug.h" namespace art { @@ -31,9 +31,9 @@ static void SimpleAborter(const char* msg) { _exit(1); } -class LoggingTest : public CommonRuntimeTest { +class LoggingTest : public testing::Test { protected: - void PostRuntimeCreate() OVERRIDE { + LoggingTest() { // In our abort tests we really don't want the runtime to create a real dump. android::base::SetAborter(SimpleAborter); } diff --git a/libartbase/base/safe_copy_test.cc b/libartbase/base/safe_copy_test.cc index a9ec9528a1..f1d7c55e77 100644 --- a/libartbase/base/safe_copy_test.cc +++ b/libartbase/base/safe_copy_test.cc @@ -16,14 +16,15 @@ #include "safe_copy.h" -#include "common_runtime_test.h" - #include <errno.h> #include <string.h> #include <sys/mman.h> #include <sys/user.h> -#include "globals.h" +#include "android-base/logging.h" +#include "base/globals.h" +#include "gtest/gtest.h" + namespace art { diff --git a/libdexfile/Android.bp b/libdexfile/Android.bp index 3fd61ee251..b2c041c81f 100644 --- a/libdexfile/Android.bp +++ b/libdexfile/Android.bp @@ -32,6 +32,7 @@ cc_defaults { "dex/modifiers.cc", "dex/primitive.cc", "dex/standard_dex_file.cc", + "dex/type_lookup_table.cc", "dex/utf.cc", ], @@ -123,6 +124,7 @@ art_cc_test { "dex/dex_instruction_test.cc", "dex/primitive_test.cc", "dex/string_reference_test.cc", + "dex/type_lookup_table_test.cc", "dex/utf_test.cc", ], shared_libs: [ diff --git a/runtime/type_lookup_table.cc b/libdexfile/dex/type_lookup_table.cc index 7e204fc03a..ca5ec2f798 100644 --- a/runtime/type_lookup_table.cc +++ b/libdexfile/dex/type_lookup_table.cc @@ -20,7 +20,6 @@ #include <memory> #include "base/bit_utils.h" -#include "base/utils.h" #include "dex/dex_file-inl.h" #include "dex/utf-inl.h" diff --git a/runtime/type_lookup_table.h b/libdexfile/dex/type_lookup_table.h index 3352d60ed1..0ba2b75dc6 100644 --- a/runtime/type_lookup_table.h +++ b/libdexfile/dex/type_lookup_table.h @@ -14,8 +14,8 @@ * limitations under the License. */ -#ifndef ART_RUNTIME_TYPE_LOOKUP_TABLE_H_ -#define ART_RUNTIME_TYPE_LOOKUP_TABLE_H_ +#ifndef ART_LIBDEXFILE_DEX_TYPE_LOOKUP_TABLE_H_ +#define ART_LIBDEXFILE_DEX_TYPE_LOOKUP_TABLE_H_ #include "base/leb128.h" #include "dex/dex_file_types.h" @@ -172,4 +172,4 @@ class TypeLookupTable { } // namespace art -#endif // ART_RUNTIME_TYPE_LOOKUP_TABLE_H_ +#endif // ART_LIBDEXFILE_DEX_TYPE_LOOKUP_TABLE_H_ diff --git a/runtime/type_lookup_table_test.cc b/libdexfile/dex/type_lookup_table_test.cc index b6ab6da78c..b6ab6da78c 100644 --- a/runtime/type_lookup_table_test.cc +++ b/libdexfile/dex/type_lookup_table_test.cc diff --git a/oatdump/oatdump.cc b/oatdump/oatdump.cc index 3bff123386..7530a9c5a8 100644 --- a/oatdump/oatdump.cc +++ b/oatdump/oatdump.cc @@ -34,6 +34,7 @@ #include "art_field-inl.h" #include "art_method-inl.h" #include "base/bit_utils_iterator.h" +#include "base/indenter.h" #include "base/os.h" #include "base/safe_map.h" #include "base/stl_util.h" @@ -49,6 +50,7 @@ #include "dex/dex_file-inl.h" #include "dex/dex_instruction-inl.h" #include "dex/string_reference.h" +#include "dex/type_lookup_table.h" #include "disassembler.h" #include "gc/accounting/space_bitmap-inl.h" #include "gc/space/image_space.h" @@ -56,7 +58,6 @@ #include "gc/space/space-inl.h" #include "image-inl.h" #include "imtable-inl.h" -#include "indenter.h" #include "subtype_check.h" #include "index_bss_mapping.h" #include "interpreter/unstarted_runtime.h" @@ -75,7 +76,6 @@ #include "stack.h" #include "stack_map.h" #include "thread_list.h" -#include "type_lookup_table.h" #include "vdex_file.h" #include "verifier/method_verifier.h" #include "verifier/verifier_deps.h" diff --git a/openjdkjvmti/ti_redefine.cc b/openjdkjvmti/ti_redefine.cc index 5d430d2073..a23baa5095 100644 --- a/openjdkjvmti/ti_redefine.cc +++ b/openjdkjvmti/ti_redefine.cc @@ -234,12 +234,39 @@ jvmtiError Redefiner::IsModifiableClass(jvmtiEnv* env ATTRIBUTE_UNUSED, art::Handle<art::mirror::Class> h_klass(hs.NewHandle(obj->AsClass())); std::string err_unused; *is_redefinable = - Redefiner::GetClassRedefinitionError(h_klass, &err_unused) == OK ? JNI_TRUE : JNI_FALSE; + Redefiner::GetClassRedefinitionError(h_klass, &err_unused) != ERR(UNMODIFIABLE_CLASS) + ? JNI_TRUE : JNI_FALSE; return OK; } +jvmtiError Redefiner::GetClassRedefinitionError(jclass klass, /*out*/std::string* error_msg) { + art::Thread* self = art::Thread::Current(); + art::ScopedObjectAccess soa(self); + art::StackHandleScope<1> hs(self); + art::ObjPtr<art::mirror::Object> obj(self->DecodeJObject(klass)); + if (obj.IsNull()) { + return ERR(INVALID_CLASS); + } + art::Handle<art::mirror::Class> h_klass(hs.NewHandle(obj->AsClass())); + return Redefiner::GetClassRedefinitionError(h_klass, error_msg); +} + jvmtiError Redefiner::GetClassRedefinitionError(art::Handle<art::mirror::Class> klass, /*out*/std::string* error_msg) { + if (!klass->IsResolved()) { + // It's only a problem to try to retransform/redefine a unprepared class if it's happening on + // the same thread as the class-linking process. If it's on another thread we will be able to + // wait for the preparation to finish and continue from there. + if (klass->GetLockOwnerThreadId() == art::Thread::Current()->GetThreadId()) { + *error_msg = "Modification of class " + klass->PrettyClass() + + " from within the classes ClassLoad callback is not supported to prevent deadlocks." + + " Please use ClassFileLoadHook directly instead."; + return ERR(INTERNAL); + } else { + LOG(WARNING) << klass->PrettyClass() << " is not yet resolved. Attempting to transform " + << "it could cause arbitrary length waits as the class is being resolved."; + } + } if (klass->IsPrimitive()) { *error_msg = "Modification of primitive classes is not supported"; return ERR(UNMODIFIABLE_CLASS); @@ -332,12 +359,9 @@ jvmtiError Redefiner::RedefineClasses(ArtJvmTiEnv* env, std::vector<ArtClassDefinition> def_vector; def_vector.reserve(class_count); for (jint i = 0; i < class_count; i++) { - jboolean is_modifiable = JNI_FALSE; - jvmtiError res = env->IsModifiableClass(definitions[i].klass, &is_modifiable); + jvmtiError res = Redefiner::GetClassRedefinitionError(definitions[i].klass, error_msg); if (res != OK) { return res; - } else if (!is_modifiable) { - return ERR(UNMODIFIABLE_CLASS); } // We make a copy of the class_bytes to pass into the retransformation. // This makes cleanup easier (since we unambiguously own the bytes) and also is useful since we diff --git a/openjdkjvmti/ti_redefine.h b/openjdkjvmti/ti_redefine.h index 778f87e68e..fa7d28648d 100644 --- a/openjdkjvmti/ti_redefine.h +++ b/openjdkjvmti/ti_redefine.h @@ -98,6 +98,10 @@ class Redefiner { art::ArrayRef<const unsigned char> data, std::string* error_msg); + // Helper for checking if redefinition/retransformation is allowed. + static jvmtiError GetClassRedefinitionError(jclass klass, /*out*/std::string* error_msg) + REQUIRES(!art::Locks::mutator_lock_); + private: class ClassRedefinition { public: diff --git a/openjdkjvmti/transform.cc b/openjdkjvmti/transform.cc index 43b8fe94f4..62094a327d 100644 --- a/openjdkjvmti/transform.cc +++ b/openjdkjvmti/transform.cc @@ -313,12 +313,9 @@ jvmtiError Transformer::RetransformClasses(ArtJvmTiEnv* env, std::vector<ArtClassDefinition> definitions; jvmtiError res = OK; for (jint i = 0; i < class_count; i++) { - jboolean is_modifiable = JNI_FALSE; - res = env->IsModifiableClass(classes[i], &is_modifiable); + res = Redefiner::GetClassRedefinitionError(classes[i], error_msg); if (res != OK) { return res; - } else if (!is_modifiable) { - return ERR(UNMODIFIABLE_CLASS); } ArtClassDefinition def; res = def.Init(self, classes[i]); diff --git a/patchoat/patchoat_test.cc b/patchoat/patchoat_test.cc index 974ed3217d..69728ae051 100644 --- a/patchoat/patchoat_test.cc +++ b/patchoat/patchoat_test.cc @@ -24,6 +24,7 @@ #include "android-base/stringprintf.h" #include "android-base/strings.h" +#include "base/hex_dump.h" #include "base/leb128.h" #include "dexopt_test.h" #include "runtime.h" @@ -320,6 +321,12 @@ class PatchoatTest : public DexoptTest { if (image1[i] != image2[i]) { *error_msg = StringPrintf("%s and %s differ at offset %zu", filename1.c_str(), filename2.c_str(), i); + size_t hexdump_size = std::min<size_t>(16u, size - i); + HexDump dump1(&image1[i], hexdump_size, /* show_actual_addresses */ false, /* prefix */ ""); + HexDump dump2(&image2[i], hexdump_size, /* show_actual_addresses */ false, /* prefix */ ""); + std::ostringstream oss; + oss << "\n" << dump1 << "\n" << dump2; + *error_msg += oss.str(); return true; } } diff --git a/runtime/Android.bp b/runtime/Android.bp index 00d4a6080a..8329b62618 100644 --- a/runtime/Android.bp +++ b/runtime/Android.bp @@ -199,7 +199,6 @@ cc_defaults { "ti/agent.cc", "trace.cc", "transaction.cc", - "type_lookup_table.cc", "vdex_file.cc", "verifier/instruction_flags.cc", "verifier/method_verifier.cc", @@ -449,6 +448,7 @@ gensrcs { "thread.h", "thread_state.h", "ti/agent.h", + "trace.h", "verifier/verifier_enums.h", ], output_extension: "operator_out.cc", @@ -571,7 +571,6 @@ art_cc_test { "handle_scope_test.cc", "hidden_api_test.cc", "imtable_test.cc", - "indenter_test.cc", "indirect_reference_table_test.cc", "instrumentation_test.cc", "intern_table_test.cc", @@ -598,7 +597,6 @@ art_cc_test { "subtype_check_test.cc", "thread_pool_test.cc", "transaction_test.cc", - "type_lookup_table_test.cc", "vdex_file_test.cc", "verifier/method_verifier_test.cc", "verifier/reg_type_test.cc", diff --git a/runtime/class_loader_context.cc b/runtime/class_loader_context.cc index e646520f3d..216ad8f794 100644 --- a/runtime/class_loader_context.cc +++ b/runtime/class_loader_context.cc @@ -649,12 +649,16 @@ static bool IsAbsoluteLocation(const std::string& location) { return !location.empty() && location[0] == '/'; } -bool ClassLoaderContext::VerifyClassLoaderContextMatch(const std::string& context_spec) const { - DCHECK(dex_files_open_attempted_); - DCHECK(dex_files_open_result_); +bool ClassLoaderContext::VerifyClassLoaderContextMatch(const std::string& context_spec, + bool verify_names, + bool verify_checksums) const { + if (verify_names || verify_checksums) { + DCHECK(dex_files_open_attempted_); + DCHECK(dex_files_open_result_); + } ClassLoaderContext expected_context; - if (!expected_context.Parse(context_spec, /*parse_checksums*/ true)) { + if (!expected_context.Parse(context_spec, verify_checksums)) { LOG(WARNING) << "Invalid class loader context: " << context_spec; return false; } @@ -693,8 +697,14 @@ bool ClassLoaderContext::VerifyClassLoaderContextMatch(const std::string& contex return false; } - DCHECK_EQ(info.classpath.size(), info.checksums.size()); - DCHECK_EQ(expected_info.classpath.size(), expected_info.checksums.size()); + if (verify_checksums) { + DCHECK_EQ(info.classpath.size(), info.checksums.size()); + DCHECK_EQ(expected_info.classpath.size(), expected_info.checksums.size()); + } + + if (!verify_names) { + continue; + } for (size_t k = 0; k < info.classpath.size(); k++) { // Compute the dex location that must be compared. diff --git a/runtime/class_loader_context.h b/runtime/class_loader_context.h index 692a6cda5b..231acc46ac 100644 --- a/runtime/class_loader_context.h +++ b/runtime/class_loader_context.h @@ -104,7 +104,11 @@ class ClassLoaderContext { // - the class loader from the same position have the same classpath // (the order and checksum of the dex files matches) // This should be called after OpenDexFiles(). - bool VerifyClassLoaderContextMatch(const std::string& context_spec) const; + // Names are only verified if verify_names is true. + // Checksums are only verified if verify_checksums is true. + bool VerifyClassLoaderContextMatch(const std::string& context_spec, + bool verify_names = true, + bool verify_checksums = true) const; // Creates the class loader context from the given string. // The format: ClassLoaderType1[ClasspathElem1:ClasspathElem2...];ClassLoaderType2[...]... diff --git a/runtime/debug_print.cc b/runtime/debug_print.cc index c7530bec6e..60487670ac 100644 --- a/runtime/debug_print.cc +++ b/runtime/debug_print.cc @@ -73,11 +73,12 @@ std::string DescribeLoaders(ObjPtr<mirror::ClassLoader> loader, const char* clas oss << "BootClassLoader"; // This would be unexpected. } for (; loader != nullptr; loader = loader->GetParent()) { - oss << loader_separator << loader->GetClass()->PrettyDescriptor(); + ClassTable* table = Runtime::Current()->GetClassLinker()->ClassTableForClassLoader(loader); + oss << loader_separator << loader->GetClass()->PrettyDescriptor() + << "/" << static_cast<const void*>(table); loader_separator = ";"; // If we didn't find the class yet, try to find it in the current class loader. if (!found_class) { - ClassTable* table = Runtime::Current()->GetClassLinker()->ClassTableForClassLoader(loader); ObjPtr<mirror::Class> klass = (table != nullptr) ? table->Lookup(class_descriptor, hash) : nullptr; if (klass != nullptr) { @@ -99,7 +100,8 @@ std::string DescribeLoaders(ObjPtr<mirror::ClassLoader> loader, const char* clas VisitClassLoaderDexFiles(soa, handle, [&](const DexFile* dex_file) { - oss << path_separator << dex_file->GetLocation(); + oss << path_separator << dex_file->GetLocation() + << "/" << static_cast<const void*>(dex_file); path_separator = ":"; return true; // Continue with the next DexFile. }); @@ -135,8 +137,9 @@ void DumpB77342775DebugData(ObjPtr<mirror::Class> target_class, ObjPtr<mirror::C CHECK(iftable != nullptr); size_t ifcount = iftable->Count(); LOG(ERROR) << "Maybe bug 77342775, looking for " << target_descriptor - << " with loader " << DescribeLoaders(src_class->GetClassLoader(), target_descriptor) - << " in interface table for " << source_descriptor << " ifcount=" << ifcount; + << " with loader " << DescribeLoaders(target_class->GetClassLoader(), target_descriptor) + << " in interface table for " << source_descriptor << " ifcount=" << ifcount + << " with loader " << DescribeLoaders(src_class->GetClassLoader(), source_descriptor); for (size_t i = 0; i != ifcount; ++i) { ObjPtr<mirror::Class> iface = iftable->GetInterface(i); CHECK(iface != nullptr); @@ -145,8 +148,9 @@ void DumpB77342775DebugData(ObjPtr<mirror::Class> target_class, ObjPtr<mirror::C } } else { LOG(ERROR) << "Maybe bug 77342775, looking for " << target_descriptor - << " with loader " << DescribeLoaders(src_class->GetClassLoader(), target_descriptor) - << " in superclass chain for " << source_descriptor; + << " with loader " << DescribeLoaders(target_class->GetClassLoader(), target_descriptor) + << " in superclass chain for " << source_descriptor + << " with loader " << DescribeLoaders(src_class->GetClassLoader(), source_descriptor); for (ObjPtr<mirror::Class> klass = src_class; klass != nullptr; klass = klass->GetSuperClass()) { diff --git a/runtime/gc/heap.cc b/runtime/gc/heap.cc index 684922099b..e85824de70 100644 --- a/runtime/gc/heap.cc +++ b/runtime/gc/heap.cc @@ -1203,7 +1203,8 @@ void Heap::ThrowOutOfMemoryError(Thread* self, size_t byte_count, AllocatorType // If we're in a stack overflow, do not create a new exception. It would require running the // constructor, which will of course still be in a stack overflow. if (self->IsHandlingStackOverflow()) { - self->SetException(Runtime::Current()->GetPreAllocatedOutOfMemoryError()); + self->SetException( + Runtime::Current()->GetPreAllocatedOutOfMemoryErrorWhenHandlingStackOverflow()); return; } diff --git a/runtime/oat_file.cc b/runtime/oat_file.cc index cfbcda36b9..bda6604724 100644 --- a/runtime/oat_file.cc +++ b/runtime/oat_file.cc @@ -49,6 +49,7 @@ #include "dex/dex_file_loader.h" #include "dex/dex_file_types.h" #include "dex/standard_dex_file.h" +#include "dex/type_lookup_table.h" #include "dex/utf-inl.h" #include "elf_file.h" #include "elf_utils.h" @@ -61,7 +62,6 @@ #include "oat_file-inl.h" #include "oat_file_manager.h" #include "runtime.h" -#include "type_lookup_table.h" #include "vdex_file.h" namespace art { diff --git a/runtime/oat_file.h b/runtime/oat_file.h index 24868dd55d..6494b4c58e 100644 --- a/runtime/oat_file.h +++ b/runtime/oat_file.h @@ -32,11 +32,11 @@ #include "compiler_filter.h" #include "dex/dex_file.h" #include "dex/dex_file_layout.h" +#include "dex/type_lookup_table.h" #include "dex/utf.h" #include "index_bss_mapping.h" #include "mirror/object.h" #include "oat.h" -#include "type_lookup_table.h" namespace art { diff --git a/runtime/runtime.cc b/runtime/runtime.cc index f550a151bb..c394fefb38 100644 --- a/runtime/runtime.cc +++ b/runtime/runtime.cc @@ -1503,19 +1503,34 @@ bool Runtime::Init(RuntimeArgumentMap&& runtime_options_in) { // TODO: move this to just be an Trace::Start argument Trace::SetDefaultClockSource(runtime_options.GetOrDefault(Opt::ProfileClock)); + // Pre-allocate an OutOfMemoryError for the case when we fail to + // allocate the exception to be thrown. + InitPreAllocatedException(self, + &Runtime::pre_allocated_OutOfMemoryError_when_throwing_exception_, + "Ljava/lang/OutOfMemoryError;", + "OutOfMemoryError thrown while trying to throw an exception; " + "no stack trace available"); // Pre-allocate an OutOfMemoryError for the double-OOME case. - self->ThrowNewException("Ljava/lang/OutOfMemoryError;", - "OutOfMemoryError thrown while trying to throw OutOfMemoryError; " - "no stack trace available"); - pre_allocated_OutOfMemoryError_ = GcRoot<mirror::Throwable>(self->GetException()); - self->ClearException(); + InitPreAllocatedException(self, + &Runtime::pre_allocated_OutOfMemoryError_when_throwing_oome_, + "Ljava/lang/OutOfMemoryError;", + "OutOfMemoryError thrown while trying to throw OutOfMemoryError; " + "no stack trace available"); + // Pre-allocate an OutOfMemoryError for the case when we fail to + // allocate while handling a stack overflow. + InitPreAllocatedException(self, + &Runtime::pre_allocated_OutOfMemoryError_when_handling_stack_overflow_, + "Ljava/lang/OutOfMemoryError;", + "OutOfMemoryError thrown while trying to handle a stack overflow; " + "no stack trace available"); // Pre-allocate a NoClassDefFoundError for the common case of failing to find a system class // ahead of checking the application's class loader. - self->ThrowNewException("Ljava/lang/NoClassDefFoundError;", - "Class not found using the boot class loader; no stack trace available"); - pre_allocated_NoClassDefFoundError_ = GcRoot<mirror::Throwable>(self->GetException()); - self->ClearException(); + InitPreAllocatedException(self, + &Runtime::pre_allocated_NoClassDefFoundError_, + "Ljava/lang/NoClassDefFoundError;", + "Class not found using the boot class loader; " + "no stack trace available"); // Runtime initialization is largely done now. // We load plugins first since that can modify the runtime state slightly. @@ -1666,6 +1681,16 @@ void Runtime::AttachAgent(JNIEnv* env, } } +void Runtime::InitPreAllocatedException(Thread* self, + GcRoot<mirror::Throwable> Runtime::* exception, + const char* exception_class_descriptor, + const char* msg) { + DCHECK_EQ(self, Thread::Current()); + self->ThrowNewException(exception_class_descriptor, msg); + this->*exception = GcRoot<mirror::Throwable>(self->GetException()); + self->ClearException(); +} + void Runtime::InitNativeMethods() { VLOG(startup) << "Runtime::InitNativeMethods entering"; Thread* self = Thread::Current(); @@ -1917,10 +1942,26 @@ void Runtime::DetachCurrentThread() { thread_list_->Unregister(self); } -mirror::Throwable* Runtime::GetPreAllocatedOutOfMemoryError() { - mirror::Throwable* oome = pre_allocated_OutOfMemoryError_.Read(); +mirror::Throwable* Runtime::GetPreAllocatedOutOfMemoryErrorWhenThrowingException() { + mirror::Throwable* oome = pre_allocated_OutOfMemoryError_when_throwing_exception_.Read(); + if (oome == nullptr) { + LOG(ERROR) << "Failed to return pre-allocated OOME-when-throwing-exception"; + } + return oome; +} + +mirror::Throwable* Runtime::GetPreAllocatedOutOfMemoryErrorWhenThrowingOOME() { + mirror::Throwable* oome = pre_allocated_OutOfMemoryError_when_throwing_oome_.Read(); + if (oome == nullptr) { + LOG(ERROR) << "Failed to return pre-allocated OOME-when-throwing-OOME"; + } + return oome; +} + +mirror::Throwable* Runtime::GetPreAllocatedOutOfMemoryErrorWhenHandlingStackOverflow() { + mirror::Throwable* oome = pre_allocated_OutOfMemoryError_when_handling_stack_overflow_.Read(); if (oome == nullptr) { - LOG(ERROR) << "Failed to return pre-allocated OOME"; + LOG(ERROR) << "Failed to return pre-allocated OOME-when-handling-stack-overflow"; } return oome; } @@ -2005,7 +2046,12 @@ void Runtime::VisitTransactionRoots(RootVisitor* visitor) { void Runtime::VisitNonThreadRoots(RootVisitor* visitor) { java_vm_->VisitRoots(visitor); sentinel_.VisitRootIfNonNull(visitor, RootInfo(kRootVMInternal)); - pre_allocated_OutOfMemoryError_.VisitRootIfNonNull(visitor, RootInfo(kRootVMInternal)); + pre_allocated_OutOfMemoryError_when_throwing_exception_ + .VisitRootIfNonNull(visitor, RootInfo(kRootVMInternal)); + pre_allocated_OutOfMemoryError_when_throwing_oome_ + .VisitRootIfNonNull(visitor, RootInfo(kRootVMInternal)); + pre_allocated_OutOfMemoryError_when_handling_stack_overflow_ + .VisitRootIfNonNull(visitor, RootInfo(kRootVMInternal)); pre_allocated_NoClassDefFoundError_.VisitRootIfNonNull(visitor, RootInfo(kRootVMInternal)); verifier::MethodVerifier::VisitStaticRoots(visitor); VisitTransactionRoots(visitor); diff --git a/runtime/runtime.h b/runtime/runtime.h index 03f17bc04a..3d4b596349 100644 --- a/runtime/runtime.h +++ b/runtime/runtime.h @@ -291,7 +291,12 @@ class Runtime { // Get the special object used to mark a cleared JNI weak global. mirror::Object* GetClearedJniWeakGlobal() REQUIRES_SHARED(Locks::mutator_lock_); - mirror::Throwable* GetPreAllocatedOutOfMemoryError() REQUIRES_SHARED(Locks::mutator_lock_); + mirror::Throwable* GetPreAllocatedOutOfMemoryErrorWhenThrowingException() + REQUIRES_SHARED(Locks::mutator_lock_); + mirror::Throwable* GetPreAllocatedOutOfMemoryErrorWhenThrowingOOME() + REQUIRES_SHARED(Locks::mutator_lock_); + mirror::Throwable* GetPreAllocatedOutOfMemoryErrorWhenHandlingStackOverflow() + REQUIRES_SHARED(Locks::mutator_lock_); mirror::Throwable* GetPreAllocatedNoClassDefFoundError() REQUIRES_SHARED(Locks::mutator_lock_); @@ -764,6 +769,11 @@ class Runtime { bool Init(RuntimeArgumentMap&& runtime_options) SHARED_TRYLOCK_FUNCTION(true, Locks::mutator_lock_); + void InitPreAllocatedException(Thread* self, + GcRoot<mirror::Throwable> Runtime::* exception, + const char* exception_class_descriptor, + const char* msg) + REQUIRES_SHARED(Locks::mutator_lock_); void InitNativeMethods() REQUIRES(!Locks::mutator_lock_); void RegisterRuntimeNativeMethods(JNIEnv* env); @@ -796,7 +806,10 @@ class Runtime { // 64 bit so that we can share the same asm offsets for both 32 and 64 bits. uint64_t callee_save_methods_[kCalleeSaveSize]; - GcRoot<mirror::Throwable> pre_allocated_OutOfMemoryError_; + // Pre-allocated exceptions (see Runtime::Init). + GcRoot<mirror::Throwable> pre_allocated_OutOfMemoryError_when_throwing_exception_; + GcRoot<mirror::Throwable> pre_allocated_OutOfMemoryError_when_throwing_oome_; + GcRoot<mirror::Throwable> pre_allocated_OutOfMemoryError_when_handling_stack_overflow_; GcRoot<mirror::Throwable> pre_allocated_NoClassDefFoundError_; ArtMethod* resolution_method_; ArtMethod* imt_conflict_method_; diff --git a/runtime/stack_map.cc b/runtime/stack_map.cc index 250ff2af1a..9c7b6875cf 100644 --- a/runtime/stack_map.cc +++ b/runtime/stack_map.cc @@ -19,7 +19,7 @@ #include <stdint.h> #include "art_method.h" -#include "indenter.h" +#include "base/indenter.h" #include "scoped_thread_state_change-inl.h" namespace art { diff --git a/runtime/thread.cc b/runtime/thread.cc index d17f409a7d..f6ac64f7bd 100644 --- a/runtime/thread.cc +++ b/runtime/thread.cc @@ -3021,7 +3021,8 @@ void Thread::ThrowNewWrappedException(const char* exception_class_descriptor, // If we couldn't allocate the exception, throw the pre-allocated out of memory exception. if (exception == nullptr) { - SetException(Runtime::Current()->GetPreAllocatedOutOfMemoryError()); + Dump(LOG_STREAM(WARNING)); // The pre-allocated OOME has no stack, so help out and log one. + SetException(Runtime::Current()->GetPreAllocatedOutOfMemoryErrorWhenThrowingException()); return; } @@ -3101,7 +3102,7 @@ void Thread::ThrowOutOfMemoryError(const char* msg) { tls32_.throwing_OutOfMemoryError = false; } else { Dump(LOG_STREAM(WARNING)); // The pre-allocated OOME has no stack, so help out and log one. - SetException(Runtime::Current()->GetPreAllocatedOutOfMemoryError()); + SetException(Runtime::Current()->GetPreAllocatedOutOfMemoryErrorWhenThrowingOOME()); } } diff --git a/runtime/trace.cc b/runtime/trace.cc index bea510ab61..292cac6d0a 100644 --- a/runtime/trace.cc +++ b/runtime/trace.cc @@ -470,24 +470,30 @@ void Trace::StopTracing(bool finish_tracing, bool flush_file) { if (the_trace != nullptr) { stop_alloc_counting = (the_trace->flags_ & Trace::kTraceCountAllocs) != 0; + // Stop the trace sources adding more entries to the trace buffer and synchronise stores. + { + gc::ScopedGCCriticalSection gcs(self, + gc::kGcCauseInstrumentation, + gc::kCollectorTypeInstrumentation); + ScopedSuspendAll ssa(__FUNCTION__); + + if (the_trace->trace_mode_ == TraceMode::kSampling) { + MutexLock mu(self, *Locks::thread_list_lock_); + runtime->GetThreadList()->ForEach(ClearThreadStackTraceAndClockBase, nullptr); + } else { + runtime->GetInstrumentation()->DisableMethodTracing(kTracerInstrumentationKey); + runtime->GetInstrumentation()->RemoveListener( + the_trace, instrumentation::Instrumentation::kMethodEntered | + instrumentation::Instrumentation::kMethodExited | + instrumentation::Instrumentation::kMethodUnwind); + } + } + // At this point, code may read buf_ as it's writers are shutdown + // and the ScopedSuspendAll above has ensured all stores to buf_ + // are now visible. if (finish_tracing) { the_trace->FinishTracing(); } - gc::ScopedGCCriticalSection gcs(self, - gc::kGcCauseInstrumentation, - gc::kCollectorTypeInstrumentation); - ScopedSuspendAll ssa(__FUNCTION__); - - if (the_trace->trace_mode_ == TraceMode::kSampling) { - MutexLock mu(self, *Locks::thread_list_lock_); - runtime->GetThreadList()->ForEach(ClearThreadStackTraceAndClockBase, nullptr); - } else { - runtime->GetInstrumentation()->DisableMethodTracing(kTracerInstrumentationKey); - runtime->GetInstrumentation()->RemoveListener( - the_trace, instrumentation::Instrumentation::kMethodEntered | - instrumentation::Instrumentation::kMethodExited | - instrumentation::Instrumentation::kMethodUnwind); - } if (the_trace->trace_file_.get() != nullptr) { // Do not try to erase, so flush and close explicitly. if (flush_file) { @@ -653,7 +659,7 @@ Trace::Trace(File* trace_file, flags_(flags), trace_output_mode_(output_mode), trace_mode_(trace_mode), clock_source_(default_clock_source_), buffer_size_(std::max(kMinBufSize, buffer_size)), - start_time_(MicroTime()), clock_overhead_ns_(GetClockOverheadNanoSeconds()), cur_offset_(0), + start_time_(MicroTime()), clock_overhead_ns_(GetClockOverheadNanoSeconds()), overflow_(false), interval_us_(0), streaming_lock_(nullptr), unique_methods_lock_(new Mutex("unique methods lock", kTracingUniqueMethodsLock)) { CHECK(trace_file != nullptr || output_mode == TraceOutputMode::kDDMS); @@ -674,7 +680,6 @@ Trace::Trace(File* trace_file, } static_assert(18 <= kMinBufSize, "Minimum buffer size not large enough for trace header"); - // Update current offset. cur_offset_.store(kTraceHeaderLength, std::memory_order_relaxed); if (output_mode == TraceOutputMode::kStreaming) { @@ -711,10 +716,10 @@ void Trace::DumpBuf(uint8_t* buf, size_t buf_size, TraceClockSource clock_source void Trace::FinishTracing() { size_t final_offset = 0; - std::set<ArtMethod*> visited_methods; if (trace_output_mode_ == TraceOutputMode::kStreaming) { // Clean up. + MutexLock mu(Thread::Current(), *streaming_lock_); STLDeleteValues(&seen_methods_); } else { final_offset = cur_offset_.load(std::memory_order_relaxed); @@ -759,7 +764,8 @@ void Trace::FinishTracing() { std::string header(os.str()); if (trace_output_mode_ == TraceOutputMode::kStreaming) { - MutexLock mu(Thread::Current(), *streaming_lock_); // To serialize writing. + // Protect access to buf_ and satisfy sanitizer for calls to WriteBuf / FlushBuf. + MutexLock mu(Thread::Current(), *streaming_lock_); // Write a special token to mark the end of trace records and the start of // trace summary. uint8_t buf[7]; @@ -944,6 +950,7 @@ std::string Trace::GetMethodLine(ArtMethod* method) { } void Trace::WriteToBuf(const uint8_t* src, size_t src_size) { + // Updates to cur_offset_ are done under the streaming_lock_ here as in streaming mode. int32_t old_offset = cur_offset_.load(std::memory_order_relaxed); int32_t new_offset = old_offset + static_cast<int32_t>(src_size); if (dchecked_integral_cast<size_t>(new_offset) > buffer_size_) { @@ -957,46 +964,59 @@ void Trace::WriteToBuf(const uint8_t* src, size_t src_size) { if (!trace_file_->WriteFully(src, src_size)) { PLOG(WARNING) << "Failed streaming a tracing event."; } - cur_offset_.store(0, std::memory_order_release); // Buffer is empty now. + cur_offset_.store(0, std::memory_order_relaxed); // Buffer is empty now. return; } old_offset = 0; new_offset = static_cast<int32_t>(src_size); } - cur_offset_.store(new_offset, std::memory_order_release); + cur_offset_.store(new_offset, std::memory_order_relaxed); // Fill in data. memcpy(buf_.get() + old_offset, src, src_size); } void Trace::FlushBuf() { + // Updates to cur_offset_ are done under the streaming_lock_ here as in streaming mode. int32_t offset = cur_offset_.load(std::memory_order_relaxed); if (!trace_file_->WriteFully(buf_.get(), offset)) { PLOG(WARNING) << "Failed flush the remaining data in streaming."; } - cur_offset_.store(0, std::memory_order_release); + cur_offset_.store(0, std::memory_order_relaxed); } void Trace::LogMethodTraceEvent(Thread* thread, ArtMethod* method, instrumentation::Instrumentation::InstrumentationEvent event, uint32_t thread_clock_diff, uint32_t wall_clock_diff) { + // This method is called in both tracing modes (method and + // sampling). In sampling mode, this method is only called by the + // sampling thread. In method tracing mode, it can be called + // concurrently. + // Ensure we always use the non-obsolete version of the method so that entry/exit events have the // same pointer value. method = method->GetNonObsoleteMethod(); + // Advance cur_offset_ atomically. int32_t new_offset; int32_t old_offset = 0; - // We do a busy loop here trying to acquire the next offset. + // In the non-streaming case, we do a busy loop here trying to get + // an offset to write our record and advance cur_offset_ for the + // next use. if (trace_output_mode_ != TraceOutputMode::kStreaming) { + // Although multiple threads can call this method concurrently, + // the compare_exchange_weak here is still atomic (by definition). + // A succeeding update is visible to other cores when they pass + // through this point. + old_offset = cur_offset_.load(std::memory_order_relaxed); // Speculative read do { - old_offset = cur_offset_.load(std::memory_order_relaxed); new_offset = old_offset + GetRecordSize(clock_source_); if (static_cast<size_t>(new_offset) > buffer_size_) { overflow_ = true; return; } - } while (!cur_offset_.CompareAndSetWeakSequentiallyConsistent(old_offset, new_offset)); + } while (!cur_offset_.compare_exchange_weak(old_offset, new_offset, std::memory_order_relaxed)); } TraceAction action = kTraceMethodEnter; @@ -1016,7 +1036,14 @@ void Trace::LogMethodTraceEvent(Thread* thread, ArtMethod* method, uint32_t method_value = EncodeTraceMethodAndAction(method, action); - // Write data + // Write data into the tracing buffer (if not streaming) or into a + // small buffer on the stack (if streaming) which we'll put into the + // tracing buffer below. + // + // These writes to the tracing buffer are synchronised with the + // future reads that (only) occur under FinishTracing(). The callers + // of FinishTracing() acquire locks and (implicitly) synchronise + // the buffer memory. uint8_t* ptr; static constexpr size_t kPacketSize = 14U; // The maximum size of data in a packet. uint8_t stack_buf[kPacketSize]; // Space to store a packet when in streaming mode. diff --git a/runtime/trace.h b/runtime/trace.h index 7171f759c9..b242d1596c 100644 --- a/runtime/trace.h +++ b/runtime/trace.h @@ -52,9 +52,10 @@ using ThreadIDBitSet = std::bitset<kMaxThreadIdNumber>; enum TracingMode { kTracingInactive, - kMethodTracingActive, - kSampleProfilingActive, + kMethodTracingActive, // Trace activity synchronous with method progress. + kSampleProfilingActive, // Trace activity captured by sampling thread. }; +std::ostream& operator<<(std::ostream& os, const TracingMode& rhs); // File format: // header @@ -98,6 +99,9 @@ enum TraceAction { kTraceMethodActionMask = 0x03, // two bits }; +// Class for recording event traces. Trace data is either collected +// synchronously during execution (TracingMode::kMethodTracingActive), +// or by a separate sampling thread (TracingMode::kSampleProfilingActive). class Trace FINAL : public instrumentation::InstrumentationListener { public: enum TraceFlag { @@ -316,7 +320,10 @@ class Trace FINAL : public instrumentation::InstrumentationListener { // File to write trace data out to, null if direct to ddms. std::unique_ptr<File> trace_file_; - // Buffer to store trace data. + // Buffer to store trace data. In streaming mode, this is protected + // by the streaming_lock_. In non-streaming mode, reserved regions + // are atomically allocated (using cur_offset_) for log entries to + // be written. std::unique_ptr<uint8_t[]> buf_; // Flags enabling extra tracing of things such as alloc counts. @@ -339,7 +346,27 @@ class Trace FINAL : public instrumentation::InstrumentationListener { // Clock overhead. const uint32_t clock_overhead_ns_; - // Offset into buf_. + // Offset into buf_. The field is atomic to allow multiple writers + // to concurrently reserve space in the buffer. The newly written + // buffer contents are not read without some other form of thread + // synchronization, such as suspending all potential writers or + // acquiring *streaming_lock_. Reading cur_offset_ is thus never + // used to ensure visibility of any other objects, and all accesses + // are memory_order_relaxed. + // + // All accesses to buf_ in streaming mode occur whilst holding the + // streaming lock. In streaming mode, the buffer may be written out + // so cur_offset_ can move forwards and backwards. + // + // When not in streaming mode, the buf_ writes can come from + // multiple threads when the trace mode is kMethodTracing. When + // trace mode is kSampling, writes only come from the sampling + // thread. + // + // Reads to the buffer happen after the event sources writing to the + // buffer have been shutdown and all stores have completed. The + // stores are made visible in StopTracing() when execution leaves + // the ScopedSuspendAll block. AtomicInteger cur_offset_; // Did we overflow the buffer recording traces? @@ -353,8 +380,8 @@ class Trace FINAL : public instrumentation::InstrumentationListener { // Streaming mode data. Mutex* streaming_lock_; - std::map<const DexFile*, DexIndexBitSet*> seen_methods_; - std::unique_ptr<ThreadIDBitSet> seen_threads_; + std::map<const DexFile*, DexIndexBitSet*> seen_methods_ GUARDED_BY(streaming_lock_); + std::unique_ptr<ThreadIDBitSet> seen_threads_ GUARDED_BY(streaming_lock_); // Bijective map from ArtMethod* to index. // Map from ArtMethod* to index in unique_methods_; diff --git a/runtime/verifier/method_verifier.cc b/runtime/verifier/method_verifier.cc index cee717610d..72292c33f8 100644 --- a/runtime/verifier/method_verifier.cc +++ b/runtime/verifier/method_verifier.cc @@ -25,6 +25,7 @@ #include "base/aborting.h" #include "base/enums.h" #include "base/leb128.h" +#include "base/indenter.h" #include "base/logging.h" // For VLOG. #include "base/mutex-inl.h" #include "base/stl_util.h" @@ -41,7 +42,6 @@ #include "experimental_flags.h" #include "gc/accounting/card_table-inl.h" #include "handle_scope-inl.h" -#include "indenter.h" #include "intern_table.h" #include "mirror/class-inl.h" #include "mirror/class.h" diff --git a/runtime/verifier/verifier_deps.cc b/runtime/verifier/verifier_deps.cc index 4772e538aa..fe839f7312 100644 --- a/runtime/verifier/verifier_deps.cc +++ b/runtime/verifier/verifier_deps.cc @@ -20,11 +20,11 @@ #include "art_field-inl.h" #include "art_method-inl.h" +#include "base/indenter.h" #include "base/leb128.h" #include "base/stl_util.h" #include "compiler_callbacks.h" #include "dex/dex_file-inl.h" -#include "indenter.h" #include "mirror/class-inl.h" #include "mirror/class_loader.h" #include "obj_ptr-inl.h" diff --git a/test/1950-unprepared-transform/check b/test/1950-unprepared-transform/check new file mode 100755 index 0000000000..8a84388a8f --- /dev/null +++ b/test/1950-unprepared-transform/check @@ -0,0 +1,22 @@ +#!/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. + +# The RI sends an extra event that art doesn't. Add it to the expected output. +if [[ "$TEST_RUNTIME" == "jvm" ]]; then + patch -p0 expected.txt < jvm-expected.patch >/dev/null +fi + +./default-check "$@" diff --git a/test/1950-unprepared-transform/expected.txt b/test/1950-unprepared-transform/expected.txt new file mode 100644 index 0000000000..3be28a5696 --- /dev/null +++ b/test/1950-unprepared-transform/expected.txt @@ -0,0 +1,7 @@ +Redefine in ClassLoad on current thread. +Trying to redefine: class Transform. Caught error class java.lang.Exception: Failed to retransform class <LTransform;> due to JVMTI_ERROR_INTERNAL +Object out is: NON Transformed Object +Redefine during ClassLoad on another thread. +retransformClasses on an unprepared class succeeded +Object out is: Transformed object! +Redefinition thread finished. diff --git a/test/1950-unprepared-transform/info.txt b/test/1950-unprepared-transform/info.txt new file mode 100644 index 0000000000..875a5f6ec1 --- /dev/null +++ b/test/1950-unprepared-transform/info.txt @@ -0,0 +1 @@ +Tests basic functions in the jvmti plugin. diff --git a/test/1950-unprepared-transform/jvm-expected.patch b/test/1950-unprepared-transform/jvm-expected.patch new file mode 100644 index 0000000000..fd0131e5e4 --- /dev/null +++ b/test/1950-unprepared-transform/jvm-expected.patch @@ -0,0 +1,6 @@ +2,3c2,3 +< Trying to redefine: class Transform. Caught error class java.lang.Exception: Failed to retransform class <LTransform;> due to JVMTI_ERROR_INTERNAL +< Object out is: NON Transformed Object +--- +> retransformClasses on an unprepared class succeeded +> Object out is: Transformed object! diff --git a/test/1950-unprepared-transform/run b/test/1950-unprepared-transform/run new file mode 100755 index 0000000000..adb1a1c507 --- /dev/null +++ b/test/1950-unprepared-transform/run @@ -0,0 +1,17 @@ +#!/bin/bash +# +# Copyright 2016 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 --no-app-image diff --git a/test/1950-unprepared-transform/src-ex/Transform.java b/test/1950-unprepared-transform/src-ex/Transform.java new file mode 100644 index 0000000000..29aa8c68dd --- /dev/null +++ b/test/1950-unprepared-transform/src-ex/Transform.java @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2016 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. + */ + +// This class is caught during loading. +public class Transform { + public String toString() { + return "NON Transformed Object"; + } +} diff --git a/test/1950-unprepared-transform/src/Main.java b/test/1950-unprepared-transform/src/Main.java new file mode 100644 index 0000000000..adbcf38ce3 --- /dev/null +++ b/test/1950-unprepared-transform/src/Main.java @@ -0,0 +1,153 @@ +/* + * Copyright (C) 2016 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. + */ + +import art.Redefinition; + +import java.lang.reflect.*; +import java.util.Base64; +import java.util.concurrent.CountDownLatch; +import java.util.function.Consumer; + +class Main { + public static String TEST_NAME = "1950-unprepared-transform"; + + // Base 64 encoding of the following class: + // + // public class Transform { + // public String toString() { + // return "Transformed object!"; + // } + // } + public static final byte[] CLASS_BYTES = Base64.getDecoder().decode( + "yv66vgAAADQAEQoABAANCAAOBwAPBwAQAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1i" + + "ZXJUYWJsZQEACHRvU3RyaW5nAQAUKClMamF2YS9sYW5nL1N0cmluZzsBAApTb3VyY2VGaWxlAQAO" + + "VHJhbnNmb3JtLmphdmEMAAUABgEAE1RyYW5zZm9ybWVkIG9iamVjdCEBAAlUcmFuc2Zvcm0BABBq" + + "YXZhL2xhbmcvT2JqZWN0ACEAAwAEAAAAAAACAAEABQAGAAEABwAAAB0AAQABAAAABSq3AAGxAAAA" + + "AQAIAAAABgABAAAAEgABAAkACgABAAcAAAAbAAEAAQAAAAMSArAAAAABAAgAAAAGAAEAAAAUAAEA" + + "CwAAAAIADA=="); + + public static final byte[] DEX_BYTES = Base64.getDecoder().decode( + "ZGV4CjAzOACaXU/P8oJOECPrdN1Cu9/ob2cUb2vOKxqYAgAAcAAAAHhWNBIAAAAAAAAAABACAAAK" + + "AAAAcAAAAAQAAACYAAAAAgAAAKgAAAAAAAAAAAAAAAMAAADAAAAAAQAAANgAAACgAQAA+AAAADAB" + + "AAA4AQAAOwEAAEgBAABcAQAAcAEAAIABAACVAQAAmAEAAKIBAAACAAAAAwAAAAQAAAAHAAAAAQAA" + + "AAIAAAAAAAAABwAAAAMAAAAAAAAAAAABAAAAAAAAAAAACAAAAAEAAQAAAAAAAAAAAAEAAAABAAAA" + + "AAAAAAUAAAAAAAAAAAIAAAAAAAACAAEAAAAAACwBAAADAAAAGgAGABEAAAABAAEAAQAAACgBAAAE" + + "AAAAcBACAAAADgASAA4AFAAOAAY8aW5pdD4AAUwAC0xUcmFuc2Zvcm07ABJMamF2YS9sYW5nL09i" + + "amVjdDsAEkxqYXZhL2xhbmcvU3RyaW5nOwAOVHJhbnNmb3JtLmphdmEAE1RyYW5zZm9ybWVkIG9i" + + "amVjdCEAAVYACHRvU3RyaW5nAFx+fkQ4eyJtaW4tYXBpIjoyNywic2hhLTEiOiI3YTdjNDlhY2Nj" + + "NTkzNTIyNzY4MTY3MThhNGM3YWU1MmY5NjgzZjk5IiwidmVyc2lvbiI6InYxLjIuNC1kZXYifQAA" + + "AAEBAIGABJACAQH4AQAACwAAAAAAAAABAAAAAAAAAAEAAAAKAAAAcAAAAAIAAAAEAAAAmAAAAAMA" + + "AAACAAAAqAAAAAUAAAADAAAAwAAAAAYAAAABAAAA2AAAAAEgAAACAAAA+AAAAAMgAAACAAAAKAEA" + + "AAIgAAAKAAAAMAEAAAAgAAABAAAAAAIAAAAQAAABAAAAEAIAAA=="); + + public static native void setupClassLoadHook(Thread target); + public static native void clearClassLoadHook(Thread target); + private static Consumer<Class<?>> doRedefine = null; + + public static void doClassLoad(Class<?> c) { + try { + if (c.getName().equals("Transform")) { + Redefinition.addCommonTransformationResult("Transform", CLASS_BYTES, DEX_BYTES); + doRedefine.accept(c); + System.out.println("retransformClasses on an unprepared class succeeded"); + } + } catch (Throwable e) { + System.out.println("Trying to redefine: " + c + ". " + + "Caught error " + e.getClass() + ": " + e.getMessage()); + } + } + + public static ClassLoader getClassLoaderFor(String location) throws Exception { + try { + Class<?> class_loader_class = Class.forName("dalvik.system.PathClassLoader"); + Constructor<?> ctor = class_loader_class.getConstructor(String.class, ClassLoader.class); + /* on Dalvik, this is a DexFile; otherwise, it's null */ + return (ClassLoader)ctor.newInstance(location + "/" + TEST_NAME + "-ex.jar", + Main.class.getClassLoader()); + } catch (ClassNotFoundException e) { + // Running on RI. Use URLClassLoader. + return new java.net.URLClassLoader( + new java.net.URL[] { new java.net.URL("file://" + location + "/classes-ex/") }); + } + } + + public static void testCurrentThread() throws Throwable { + System.out.println("Redefine in ClassLoad on current thread."); + doRedefine = (c) -> { Redefinition.doCommonClassRetransformation(c); }; + ClassLoader new_loader = getClassLoaderFor(System.getenv("DEX_LOCATION")); + Class<?> klass = (Class<?>)new_loader.loadClass("Transform"); + if (klass == null) { + throw new AssertionError("loadClass failed"); + } + Object o = klass.newInstance(); + System.out.println("Object out is: " + o); + } + + public static void testRemoteThread() throws Throwable { + System.out.println("Redefine during ClassLoad on another thread."); + final Class[] loaded = new Class[] { null, }; + final CountDownLatch gotClass = new CountDownLatch(1); + final CountDownLatch wokeUp = new CountDownLatch(1); + Thread redef_thread = new Thread(() -> { + try { + gotClass.await(); + wokeUp.countDown(); + // This will wait until the otehr thread returns so we need to wake up the other thread + // first. + Redefinition.doCommonClassRetransformation(loaded[0]); + } catch (Exception e) { + throw new Error("Failed to do redef!", e); + } + }); + redef_thread.start(); + doRedefine = (c) -> { + try { + loaded[0] = c; + gotClass.countDown(); + wokeUp.await(); + // Let the other thread do some stuff. + Thread.sleep(5000); + } catch (Exception e) { + throw new Error("Failed to do redef!", e); + } + }; + ClassLoader new_loader = getClassLoaderFor(System.getenv("DEX_LOCATION")); + Class<?> klass = (Class<?>)new_loader.loadClass("Transform"); + if (klass == null) { + throw new AssertionError("loadClass failed"); + } + Object o = klass.newInstance(); + System.out.println("Object out is: " + o); + redef_thread.join(); + System.out.println("Redefinition thread finished."); + } + + public static void main(String[] args) { + // make sure we can do the transform. + Redefinition.setTestConfiguration(Redefinition.Config.COMMON_RETRANSFORM); + Redefinition.setPopRetransformations(false); + Redefinition.enableCommonRetransformation(true); + setupClassLoadHook(Thread.currentThread()); + try { + testCurrentThread(); + testRemoteThread(); + } catch (Throwable e) { + System.out.println(e.toString()); + e.printStackTrace(System.out); + } + clearClassLoadHook(Thread.currentThread()); + } +} diff --git a/test/1950-unprepared-transform/src/art/Redefinition.java b/test/1950-unprepared-transform/src/art/Redefinition.java new file mode 100644 index 0000000000..56d2938a01 --- /dev/null +++ b/test/1950-unprepared-transform/src/art/Redefinition.java @@ -0,0 +1,91 @@ +/* + * 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.ArrayList; +// Common Redefinition functions. Placed here for use by CTS +public class Redefinition { + public static final class CommonClassDefinition { + public final Class<?> target; + public final byte[] class_file_bytes; + public final byte[] dex_file_bytes; + + public CommonClassDefinition(Class<?> target, byte[] class_file_bytes, byte[] dex_file_bytes) { + this.target = target; + this.class_file_bytes = class_file_bytes; + this.dex_file_bytes = dex_file_bytes; + } + } + + // A set of possible test configurations. Test should set this if they need to. + // This must be kept in sync with the defines in ti-agent/common_helper.cc + public static enum Config { + COMMON_REDEFINE(0), + COMMON_RETRANSFORM(1), + COMMON_TRANSFORM(2); + + private final int val; + private Config(int val) { + this.val = val; + } + } + + public static void setTestConfiguration(Config type) { + nativeSetTestConfiguration(type.val); + } + + private static native void nativeSetTestConfiguration(int type); + + // Transforms the class + public static native void doCommonClassRedefinition(Class<?> target, + byte[] classfile, + byte[] dexfile); + + public static void doMultiClassRedefinition(CommonClassDefinition... defs) { + ArrayList<Class<?>> classes = new ArrayList<>(); + ArrayList<byte[]> class_files = new ArrayList<>(); + ArrayList<byte[]> dex_files = new ArrayList<>(); + + for (CommonClassDefinition d : defs) { + classes.add(d.target); + class_files.add(d.class_file_bytes); + dex_files.add(d.dex_file_bytes); + } + doCommonMultiClassRedefinition(classes.toArray(new Class<?>[0]), + class_files.toArray(new byte[0][]), + dex_files.toArray(new byte[0][])); + } + + public static void addMultiTransformationResults(CommonClassDefinition... defs) { + for (CommonClassDefinition d : defs) { + addCommonTransformationResult(d.target.getCanonicalName(), + d.class_file_bytes, + d.dex_file_bytes); + } + } + + public static native void doCommonMultiClassRedefinition(Class<?>[] targets, + byte[][] classfiles, + byte[][] dexfiles); + public static native void doCommonClassRetransformation(Class<?>... target); + public static native void setPopRetransformations(boolean pop); + public static native void popTransformationFor(String name); + public static native void enableCommonRetransformation(boolean enable); + public static native void addCommonTransformationResult(String target_name, + byte[] class_bytes, + byte[] dex_bytes); +} diff --git a/test/1950-unprepared-transform/unprepared_transform.cc b/test/1950-unprepared-transform/unprepared_transform.cc new file mode 100644 index 0000000000..620ede887f --- /dev/null +++ b/test/1950-unprepared-transform/unprepared_transform.cc @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2018 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 <cstdio> +#include <iostream> +#include <mutex> +#include <vector> + +#include "android-base/logging.h" +#include "android-base/stringprintf.h" +#include "jni.h" +#include "jvmti.h" +#include "scoped_local_ref.h" +#include "scoped_utf_chars.h" + +// Test infrastructure +#include "jvmti_helper.h" +#include "test_env.h" + +namespace art { +namespace Test1950UnpreparedTransform { + +jclass kMainClass = nullptr; +jmethodID kPrepareFunc = nullptr; + +extern "C" JNIEXPORT void ClassLoadCallback(jvmtiEnv* jvmti ATTRIBUTE_UNUSED, + JNIEnv* env, + jthread thr ATTRIBUTE_UNUSED, + jclass klass) { + env->CallStaticVoidMethod(kMainClass, kPrepareFunc, klass); +} + +extern "C" JNIEXPORT void JNICALL Java_Main_clearClassLoadHook( + JNIEnv* env, jclass main ATTRIBUTE_UNUSED, jthread thr) { + JvmtiErrorToException(env, + jvmti_env, + jvmti_env->SetEventNotificationMode(JVMTI_DISABLE, + JVMTI_EVENT_CLASS_LOAD, + thr)); +} +extern "C" JNIEXPORT void JNICALL Java_Main_setupClassLoadHook( + JNIEnv* env, jclass main, jthread thr) { + kMainClass = reinterpret_cast<jclass>(env->NewGlobalRef(main)); + kPrepareFunc = env->GetStaticMethodID(main, "doClassLoad", "(Ljava/lang/Class;)V"); + if (env->ExceptionCheck()) { + return; + } + current_callbacks.ClassLoad = ClassLoadCallback; + if (JvmtiErrorToException(env, + jvmti_env, + jvmti_env->SetEventCallbacks( + ¤t_callbacks, sizeof(current_callbacks)))) { + return; + } + JvmtiErrorToException(env, + jvmti_env, + jvmti_env->SetEventNotificationMode(JVMTI_ENABLE, + JVMTI_EVENT_CLASS_LOAD, + thr)); +} + +} // namespace Test1950UnpreparedTransform +} // namespace art diff --git a/test/Android.bp b/test/Android.bp index 0c1edcaab8..bd13de2fa9 100644 --- a/test/Android.bp +++ b/test/Android.bp @@ -277,6 +277,7 @@ art_cc_defaults { "1942-suspend-raw-monitor-exit/native_suspend_monitor.cc", "1943-suspend-raw-monitor-wait/native_suspend_monitor.cc", "1946-list-descriptors/descriptors.cc", + "1950-unprepared-transform/unprepared_transform.cc", ], // Use NDK-compatible headers for ctstiagent. header_libs: [ diff --git a/test/ti-agent/breakpoint_helper.cc b/test/ti-agent/breakpoint_helper.cc index 78aab4376f..db4ea61f1c 100644 --- a/test/ti-agent/breakpoint_helper.cc +++ b/test/ti-agent/breakpoint_helper.cc @@ -172,10 +172,11 @@ extern "C" JNIEXPORT void JNICALL Java_art_Breakpoint_startBreakpointWatch( if (JvmtiErrorToException(env, jvmti_env, jvmti_env->SetEnvironmentLocalStorage(data))) { return; } - jvmtiEventCallbacks cb; - memset(&cb, 0, sizeof(cb)); - cb.Breakpoint = breakpointCB; - if (JvmtiErrorToException(env, jvmti_env, jvmti_env->SetEventCallbacks(&cb, sizeof(cb)))) { + current_callbacks.Breakpoint = breakpointCB; + if (JvmtiErrorToException(env, + jvmti_env, + jvmti_env->SetEventCallbacks(¤t_callbacks, + sizeof(current_callbacks)))) { return; } if (JvmtiErrorToException(env, diff --git a/test/ti-agent/exceptions_helper.cc b/test/ti-agent/exceptions_helper.cc index 74f7ecc881..e56c39b9eb 100644 --- a/test/ti-agent/exceptions_helper.cc +++ b/test/ti-agent/exceptions_helper.cc @@ -147,11 +147,12 @@ extern "C" JNIEXPORT void JNICALL Java_art_Exceptions_setupExceptionTracing( return; } - jvmtiEventCallbacks cb; - memset(&cb, 0, sizeof(cb)); - cb.Exception = exceptionCB; - cb.ExceptionCatch = exceptionCatchCB; - if (JvmtiErrorToException(env, jvmti_env, jvmti_env->SetEventCallbacks(&cb, sizeof(cb)))) { + current_callbacks.Exception = exceptionCB; + current_callbacks.ExceptionCatch = exceptionCatchCB; + if (JvmtiErrorToException(env, + jvmti_env, + jvmti_env->SetEventCallbacks(¤t_callbacks, + sizeof(current_callbacks)))) { return; } } diff --git a/test/ti-agent/frame_pop_helper.cc b/test/ti-agent/frame_pop_helper.cc index 4571032ce6..f39e1854bc 100644 --- a/test/ti-agent/frame_pop_helper.cc +++ b/test/ti-agent/frame_pop_helper.cc @@ -90,10 +90,11 @@ extern "C" JNIEXPORT void JNICALL Java_art_FramePop_enableFramePopEvent( if (JvmtiErrorToException(env, jvmti_env, jvmti_env->AddCapabilities(&caps))) { return; } - jvmtiEventCallbacks cb; - memset(&cb, 0, sizeof(cb)); - cb.FramePop = framePopCB; - if (JvmtiErrorToException(env, jvmti_env, jvmti_env->SetEventCallbacks(&cb, sizeof(cb)))) { + current_callbacks.FramePop = framePopCB; + if (JvmtiErrorToException(env, + jvmti_env, + jvmti_env->SetEventCallbacks(¤t_callbacks, + sizeof(current_callbacks)))) { return; } JvmtiErrorToException(env, diff --git a/test/ti-agent/monitors_helper.cc b/test/ti-agent/monitors_helper.cc index 81d4cdc3ae..4434baf01c 100644 --- a/test/ti-agent/monitors_helper.cc +++ b/test/ti-agent/monitors_helper.cc @@ -182,13 +182,14 @@ extern "C" JNIEXPORT void JNICALL Java_art_Monitors_setupMonitorEvents( return; } - jvmtiEventCallbacks cb; - memset(&cb, 0, sizeof(cb)); - cb.MonitorContendedEnter = monitorEnterCB; - cb.MonitorContendedEntered = monitorEnteredCB; - cb.MonitorWait = monitorWaitCB; - cb.MonitorWaited = monitorWaitedCB; - if (JvmtiErrorToException(env, jvmti_env, jvmti_env->SetEventCallbacks(&cb, sizeof(cb)))) { + current_callbacks.MonitorContendedEnter = monitorEnterCB; + current_callbacks.MonitorContendedEntered = monitorEnteredCB; + current_callbacks.MonitorWait = monitorWaitCB; + current_callbacks.MonitorWaited = monitorWaitedCB; + if (JvmtiErrorToException(env, + jvmti_env, + jvmti_env->SetEventCallbacks(¤t_callbacks, + sizeof(current_callbacks)))) { return; } if (JvmtiErrorToException(env, diff --git a/test/ti-agent/redefinition_helper.cc b/test/ti-agent/redefinition_helper.cc index 76371de20a..0e4b1bdd8c 100644 --- a/test/ti-agent/redefinition_helper.cc +++ b/test/ti-agent/redefinition_helper.cc @@ -381,10 +381,8 @@ static void SetupCommonRedefine() { static void SetupCommonRetransform() { SetStandardCapabilities(jvmti_env); - jvmtiEventCallbacks cb; - memset(&cb, 0, sizeof(cb)); - cb.ClassFileLoadHook = common_retransform::CommonClassFileLoadHookRetransformable; - jvmtiError res = jvmti_env->SetEventCallbacks(&cb, sizeof(cb)); + current_callbacks.ClassFileLoadHook = common_retransform::CommonClassFileLoadHookRetransformable; + jvmtiError res = jvmti_env->SetEventCallbacks(¤t_callbacks, sizeof(current_callbacks)); CHECK_EQ(res, JVMTI_ERROR_NONE); common_retransform::gTransformations.clear(); } @@ -397,10 +395,8 @@ static void SetupCommonTransform() { jvmti_env->AddCapabilities(&caps); // Use the same callback as the retransform test. - jvmtiEventCallbacks cb; - memset(&cb, 0, sizeof(cb)); - cb.ClassFileLoadHook = common_retransform::CommonClassFileLoadHookRetransformable; - jvmtiError res = jvmti_env->SetEventCallbacks(&cb, sizeof(cb)); + current_callbacks.ClassFileLoadHook = common_retransform::CommonClassFileLoadHookRetransformable; + jvmtiError res = jvmti_env->SetEventCallbacks(¤t_callbacks, sizeof(current_callbacks)); CHECK_EQ(res, JVMTI_ERROR_NONE); common_retransform::gTransformations.clear(); } diff --git a/test/ti-agent/test_env.cc b/test/ti-agent/test_env.cc index cf47f22b03..49313876c9 100644 --- a/test/ti-agent/test_env.cc +++ b/test/ti-agent/test_env.cc @@ -19,6 +19,7 @@ namespace art { jvmtiEnv* jvmti_env = nullptr; +jvmtiEventCallbacks current_callbacks = {}; static bool gRuntimeIsJVM = false; diff --git a/test/ti-agent/test_env.h b/test/ti-agent/test_env.h index 2eb631c36c..a8a9f57097 100644 --- a/test/ti-agent/test_env.h +++ b/test/ti-agent/test_env.h @@ -23,6 +23,11 @@ namespace art { extern jvmtiEnv* jvmti_env; +// This is a jvmtiEventCallbacks struct that is used by all common ti-agent code whenever it calls +// SetEventCallbacks. This can be used by single tests to add additional event callbacks without +// being unable to use the rest of the ti-agent support code. +extern jvmtiEventCallbacks current_callbacks; + bool IsJVM(); void SetJVM(bool b); diff --git a/test/ti-agent/trace_helper.cc b/test/ti-agent/trace_helper.cc index b590175d77..11e1c15757 100644 --- a/test/ti-agent/trace_helper.cc +++ b/test/ti-agent/trace_helper.cc @@ -513,17 +513,18 @@ extern "C" JNIEXPORT void JNICALL Java_art_Trace_enableTracing2( return; } - jvmtiEventCallbacks cb; - memset(&cb, 0, sizeof(cb)); - cb.MethodEntry = methodEntryCB; - cb.MethodExit = methodExitCB; - cb.FieldAccess = fieldAccessCB; - cb.FieldModification = fieldModificationCB; - cb.ClassPrepare = classPrepareCB; - cb.SingleStep = singleStepCB; - cb.ThreadStart = threadStartCB; - cb.ThreadEnd = threadEndCB; - if (JvmtiErrorToException(env, jvmti_env, jvmti_env->SetEventCallbacks(&cb, sizeof(cb)))) { + current_callbacks.MethodEntry = methodEntryCB; + current_callbacks.MethodExit = methodExitCB; + current_callbacks.FieldAccess = fieldAccessCB; + current_callbacks.FieldModification = fieldModificationCB; + current_callbacks.ClassPrepare = classPrepareCB; + current_callbacks.SingleStep = singleStepCB; + current_callbacks.ThreadStart = threadStartCB; + current_callbacks.ThreadEnd = threadEndCB; + if (JvmtiErrorToException(env, + jvmti_env, + jvmti_env->SetEventCallbacks(¤t_callbacks, + sizeof(current_callbacks)))) { return; } if (enter != nullptr && diff --git a/tools/dexanalyze/Android.bp b/tools/dexanalyze/Android.bp new file mode 100644 index 0000000000..2754e6445e --- /dev/null +++ b/tools/dexanalyze/Android.bp @@ -0,0 +1,53 @@ +// +// Copyright (C) 2018 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. +// + +cc_defaults { + name: "dexanalyze-defaults", + defaults: ["art_defaults"], + host_supported: true, + srcs: [ + "dexanalyze.cc", + "dexanalyze_experiments.cc", + ], + target: { + android: { + shared_libs: ["libcutils"], + }, + }, + header_libs: [ + "art_cmdlineparser_headers", + ], +} + +art_cc_binary { + name: "dexanalyze", + defaults: ["dexanalyze-defaults"], + shared_libs: [ + "libdexfile", + "libbase", + ], +} + +art_cc_test { + name: "art_dexanalyze_tests", + required: ["dexanalyze"], + defaults: [ + "art_gtest_defaults", + ], + srcs: [ + "dexanalyze_test.cc", + ], +} diff --git a/tools/dexanalyze/dexanalyze.cc b/tools/dexanalyze/dexanalyze.cc new file mode 100644 index 0000000000..a5f647cc56 --- /dev/null +++ b/tools/dexanalyze/dexanalyze.cc @@ -0,0 +1,160 @@ +/* + * Copyright (C) 2018 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 <cstdint> +#include <set> +#include <sstream> + +#include <android-base/file.h> + +#include "dexanalyze_experiments.h" +#include "dex/code_item_accessors-inl.h" +#include "dex/dex_file.h" +#include "dex/dex_file_loader.h" +#include "dex/dex_instruction-inl.h" + +namespace art { + +class DexAnalyze { + static const int kExitCodeUsageError = 1; + + static int Usage(char** argv) { + LOG(ERROR) + << "Usage " << argv[0] << " [options] <dex files>\n" + << " [options] is a combination of the following\n" + << " -count_indices (Count dex indices accessed from code items)\n" + << " -i (Ignore DEX checksum failure)\n" + << " -a (Run all experiments)\n" + << " -d (Dump on per DEX basis)\n"; + return kExitCodeUsageError; + } + + struct Options { + int Parse(int argc, char** argv) { + int i; + for (i = 1; i < argc; ++i) { + const std::string arg = argv[i]; + if (arg == "-i") { + verify_checksum_ = false; + } else if (arg == "-a") { + run_all_experiments_ = true; + } else if (arg == "-count-indices") { + exp_count_indices_ = true; + } else if (arg == "-d") { + dump_per_input_dex_ = true; + } else if (!arg.empty() && arg[0] == '-') { + return Usage(argv); + } else { + break; + } + } + filenames_.insert(filenames_.end(), argv + i, argv + argc); + if (filenames_.empty()) { + return Usage(argv); + } + return 0; + } + + bool verify_checksum_ = true; + bool run_dex_file_verifier_ = true; + bool dump_per_input_dex_ = false; + bool exp_count_indices_ = false; + bool run_all_experiments_ = false; + std::vector<std::string> filenames_; + }; + + class Analysis { + public: + explicit Analysis(const Options* options) : options_(options) { + if (options->run_all_experiments_ || options->exp_count_indices_) { + experiments_.emplace_back(new CountDexIndices); + } + } + + bool ProcessDexFile(const DexFile& dex_file) { + for (std::unique_ptr<Experiment>& experiment : experiments_) { + experiment->ProcessDexFile(dex_file); + } + ++dex_count_; + return true; + } + + void Dump(std::ostream& os) { + for (std::unique_ptr<Experiment>& experiment : experiments_) { + experiment->Dump(os); + } + } + + const Options* const options_; + std::vector<std::unique_ptr<Experiment>> experiments_; + size_t dex_count_ = 0; + }; + + public: + static int Run(int argc, char** argv) { + Options options; + int result = options.Parse(argc, argv); + if (result != 0) { + return result; + } + + std::string error_msg; + Analysis cumulative(&options); + for (const std::string& filename : options.filenames_) { + std::string content; + // TODO: once added, use an api to android::base to read a std::vector<uint8_t>. + if (!android::base::ReadFileToString(filename.c_str(), &content)) { + LOG(ERROR) << "ReadFileToString failed for " + filename << std::endl; + continue; + } + std::vector<std::unique_ptr<const DexFile>> dex_files; + const DexFileLoader dex_file_loader; + if (!dex_file_loader.OpenAll(reinterpret_cast<const uint8_t*>(content.data()), + content.size(), + filename.c_str(), + options.run_dex_file_verifier_, + options.verify_checksum_, + &error_msg, + &dex_files)) { + LOG(ERROR) << "OpenAll failed for " + filename << " with " << error_msg << std::endl; + continue; + } + for (std::unique_ptr<const DexFile>& dex_file : dex_files) { + if (options.dump_per_input_dex_) { + Analysis current(&options); + if (!current.ProcessDexFile(*dex_file)) { + LOG(ERROR) << "Failed to process " << filename << " with error " << error_msg; + continue; + } + LOG(INFO) << "Analysis for " << dex_file->GetLocation() << std::endl; + current.Dump(LOG_STREAM(INFO)); + } + cumulative.ProcessDexFile(*dex_file); + } + } + LOG(INFO) << "Cumulative analysis for " << cumulative.dex_count_ << " DEX files" << std::endl; + cumulative.Dump(LOG_STREAM(INFO)); + return 0; + } +}; + +} // namespace art + +int main(int argc, char** argv) { + android::base::SetLogger(android::base::StderrLogger); + return art::DexAnalyze::Run(argc, argv); +} + diff --git a/tools/dexanalyze/dexanalyze_experiments.cc b/tools/dexanalyze/dexanalyze_experiments.cc new file mode 100644 index 0000000000..e1f119df59 --- /dev/null +++ b/tools/dexanalyze/dexanalyze_experiments.cc @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2018 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 "dexanalyze_experiments.h" +#include "dex/code_item_accessors-inl.h" +#include "dex/dex_instruction-inl.h" +#include "dex/standard_dex_file.h" + +namespace art { + +void CountDexIndices::ProcessDexFile(const DexFile& dex_file) { + num_string_ids_ += dex_file.NumStringIds(); + num_method_ids_ += dex_file.NumMethodIds(); + num_field_ids_ += dex_file.NumFieldIds(); + num_type_ids_ += dex_file.NumTypeIds(); + num_class_defs_ += dex_file.NumClassDefs(); + for (size_t class_def_index = 0; class_def_index < dex_file.NumClassDefs(); ++class_def_index) { + const DexFile::ClassDef& class_def = dex_file.GetClassDef(class_def_index); + const uint8_t* class_data = dex_file.GetClassData(class_def); + if (class_data == nullptr) { + continue; + } + ClassDataItemIterator it(dex_file, class_data); + it.SkipAllFields(); + std::set<size_t> unique_method_ids; + std::set<size_t> unique_string_ids; + while (it.HasNextMethod()) { + const DexFile::CodeItem* code_item = it.GetMethodCodeItem(); + if (code_item != nullptr) { + CodeItemInstructionAccessor instructions(dex_file, code_item); + const uint16_t* code_ptr = instructions.Insns(); + dex_code_bytes_ += instructions.InsnsSizeInCodeUnits() * sizeof(code_ptr[0]); + for (const DexInstructionPcPair& inst : instructions) { + switch (inst->Opcode()) { + case Instruction::CONST_STRING: { + const dex::StringIndex string_index(inst->VRegB_21c()); + unique_string_ids.insert(string_index.index_); + ++num_string_ids_from_code_; + break; + } + case Instruction::CONST_STRING_JUMBO: { + const dex::StringIndex string_index(inst->VRegB_31c()); + unique_string_ids.insert(string_index.index_); + ++num_string_ids_from_code_; + break; + } + // Invoke cases. + case Instruction::INVOKE_VIRTUAL: + case Instruction::INVOKE_VIRTUAL_RANGE: { + bool is_range = (inst->Opcode() == Instruction::INVOKE_VIRTUAL_RANGE); + uint32_t method_idx = is_range ? inst->VRegB_3rc() : inst->VRegB_35c(); + if (dex_file.GetMethodId(method_idx).class_idx_ == class_def.class_idx_) { + ++same_class_virtual_; + } else { + ++other_class_virtual_; + unique_method_ids.insert(method_idx); + } + break; + } + case Instruction::INVOKE_DIRECT: + case Instruction::INVOKE_DIRECT_RANGE: { + bool is_range = (inst->Opcode() == Instruction::INVOKE_DIRECT_RANGE); + uint32_t method_idx = (is_range) ? inst->VRegB_3rc() : inst->VRegB_35c(); + if (dex_file.GetMethodId(method_idx).class_idx_ == class_def.class_idx_) { + ++same_class_direct_; + } else { + ++other_class_direct_; + unique_method_ids.insert(method_idx); + } + break; + } + case Instruction::INVOKE_STATIC: + case Instruction::INVOKE_STATIC_RANGE: { + bool is_range = (inst->Opcode() == Instruction::INVOKE_STATIC_RANGE); + uint32_t method_idx = (is_range) ? inst->VRegB_3rc() : inst->VRegB_35c(); + if (dex_file.GetMethodId(method_idx).class_idx_ == class_def.class_idx_) { + ++same_class_static_; + } else { + ++other_class_static_; + unique_method_ids.insert(method_idx); + } + break; + } + default: + break; + } + } + } + it.Next(); + } + DCHECK(!it.HasNext()); + total_unique_method_idx_ += unique_method_ids.size(); + total_unique_string_ids_ += unique_string_ids.size(); + } +} + +void CountDexIndices::Dump(std::ostream& os) const { + os << "Num string ids: " << num_string_ids_ << "\n"; + os << "Num method ids: " << num_method_ids_ << "\n"; + os << "Num field ids: " << num_field_ids_ << "\n"; + os << "Num type ids: " << num_type_ids_ << "\n"; + os << "Num class defs: " << num_class_defs_ << "\n"; + os << "Same class direct: " << same_class_direct_ << "\n"; + os << "Other class direct: " << other_class_direct_ << "\n"; + os << "Same class virtual: " << same_class_virtual_ << "\n"; + os << "Other class virtual: " << other_class_virtual_ << "\n"; + os << "Same class static: " << same_class_static_ << "\n"; + os << "Other class static: " << other_class_static_ << "\n"; + os << "Num strings accessed from code: " << num_string_ids_from_code_ << "\n"; + os << "Unique(per class) method ids accessed from code: " << total_unique_method_idx_ << "\n"; + os << "Unique(per class) string ids accessed from code: " << total_unique_string_ids_ << "\n"; + size_t same_class_total = same_class_direct_ + same_class_virtual_ + same_class_static_; + size_t other_class_total = other_class_direct_ + other_class_virtual_ + other_class_static_; + os << "Same class invoke: " << same_class_total << "\n"; + os << "Other class invoke: " << other_class_total << "\n"; + os << "Invokes from code: " << (same_class_total + other_class_total) << "\n"; +} + +} // namespace art + diff --git a/tools/dexanalyze/dexanalyze_experiments.h b/tools/dexanalyze/dexanalyze_experiments.h new file mode 100644 index 0000000000..5d0f51b821 --- /dev/null +++ b/tools/dexanalyze/dexanalyze_experiments.h @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ART_TOOLS_DEXANALYZE_DEXANALYZE_EXPERIMENTS_H_ +#define ART_TOOLS_DEXANALYZE_DEXANALYZE_EXPERIMENTS_H_ + +#include <iosfwd> +#include <set> + +namespace art { + +class DexFile; + +// An experiment a stateful visitor that runs on dex files. Results are cumulative. +class Experiment { + public: + virtual ~Experiment() {} + virtual void ProcessDexFile(const DexFile& dex_file) = 0; + virtual void Dump(std::ostream& os) const = 0; +}; + +// Count numbers of dex indices. +class CountDexIndices : public Experiment { + public: + void ProcessDexFile(const DexFile& dex_file); + + void Dump(std::ostream& os) const; + + private: + // Total string ids loaded from dex code. + size_t num_string_ids_from_code_ = 0; + size_t total_unique_method_idx_ = 0; + size_t total_unique_string_ids_ = 0; + + // Other dex ids. + size_t dex_code_bytes_ = 0; + size_t num_string_ids_ = 0; + size_t num_method_ids_ = 0; + size_t num_field_ids_ = 0; + size_t num_type_ids_ = 0; + size_t num_class_defs_ = 0; + + // Invokes + size_t same_class_direct_ = 0; + size_t other_class_direct_ = 0; + size_t same_class_virtual_ = 0; + size_t other_class_virtual_ = 0; + size_t same_class_static_ = 0; + size_t other_class_static_ = 0; +}; + +} // namespace art + +#endif // ART_TOOLS_DEXANALYZE_DEXANALYZE_EXPERIMENTS_H_ + diff --git a/tools/dexanalyze/dexanalyze_test.cc b/tools/dexanalyze/dexanalyze_test.cc new file mode 100644 index 0000000000..c9b8f53d24 --- /dev/null +++ b/tools/dexanalyze/dexanalyze_test.cc @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "common_runtime_test.h" +#include "exec_utils.h" + +namespace art { + +class DexAnalyzeTest : public CommonRuntimeTest { + public: + std::string GetDexAnalyzePath() { + return GetTestAndroidRoot() + "/bin/dexanalyze"; + } + + void DexAnalyzeExec(const std::vector<std::string>& args, bool expect_success) { + std::string binary = GetDexAnalyzePath(); + CHECK(OS::FileExists(binary.c_str())) << binary << " should be a valid file path"; + std::vector<std::string> argv; + argv.push_back(binary); + argv.insert(argv.end(), args.begin(), args.end()); + std::string error_msg; + ASSERT_EQ(::art::Exec(argv, &error_msg), expect_success) << error_msg; + } +}; + +TEST_F(DexAnalyzeTest, TestAnalyzeMultidex) { + DexAnalyzeExec({ "-a", GetTestDexFileName("MultiDex") }, /*expect_success*/ true); +} + +TEST_F(DexAnalyzeTest, TestInvalidArg) { + DexAnalyzeExec({ "-invalid-option" }, /*expect_success*/ false); +} + +} // namespace art |