diff options
314 files changed, 9038 insertions, 1741 deletions
diff --git a/Android.bp b/Android.bp index b9f1db5f46..d0e22fb873 100644 --- a/Android.bp +++ b/Android.bp @@ -27,6 +27,7 @@ subdirs = [ "dexdump", "dexlayout", "dexlist", + "dexoptanalyzer", "disassembler", "imgdiag", "oatdump", diff --git a/build/Android.common_build.mk b/build/Android.common_build.mk index 4c82506516..f5a95fa0cf 100644 --- a/build/Android.common_build.mk +++ b/build/Android.common_build.mk @@ -46,6 +46,9 @@ ifeq ($(ART_BUILD_HOST_DEBUG),false) $(info Disabling ART_BUILD_HOST_DEBUG) endif +# Enable the read barrier by default. +ART_USE_READ_BARRIER ?= true + ART_CPP_EXTENSION := .cc ifndef LIBART_IMG_HOST_BASE_ADDRESS diff --git a/build/Android.common_path.mk b/build/Android.common_path.mk index e568ce283e..6de5aefc0b 100644 --- a/build/Android.common_path.mk +++ b/build/Android.common_path.mk @@ -109,6 +109,7 @@ endif ART_CORE_DEBUGGABLE_EXECUTABLES := \ dex2oat \ + dexoptanalyzer \ imgdiag \ oatdump \ patchoat \ diff --git a/build/Android.gtest.mk b/build/Android.gtest.mk index 5bdfbc74eb..bc0838435c 100644 --- a/build/Android.gtest.mk +++ b/build/Android.gtest.mk @@ -28,6 +28,7 @@ GTEST_DEX_DIRECTORIES := \ DexToDexDecompiler \ ErroneousA \ ErroneousB \ + ErroneousInit \ ExceptionHandle \ GetMethodSignature \ ImageLayoutA \ @@ -87,7 +88,7 @@ $(ART_TEST_TARGET_GTEST_VerifierDeps_DEX): $(ART_TEST_GTEST_VerifierDeps_SRC) $( ART_GTEST_dex2oat_environment_tests_DEX_DEPS := Main MainStripped MultiDex MultiDexModifiedSecondary Nested ART_GTEST_atomic_method_ref_map_test_DEX_DEPS := Interfaces -ART_GTEST_class_linker_test_DEX_DEPS := ErroneousA ErroneousB Interfaces MethodTypes MultiDex MyClass Nested Statics StaticsFromCode +ART_GTEST_class_linker_test_DEX_DEPS := AllFields ErroneousA ErroneousB ErroneousInit Interfaces MethodTypes MultiDex MyClass Nested Statics StaticsFromCode 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 @@ -100,6 +101,7 @@ ART_GTEST_instrumentation_test_DEX_DEPS := Instrumentation ART_GTEST_jni_compiler_test_DEX_DEPS := MyClassNatives ART_GTEST_jni_internal_test_DEX_DEPS := AllFields StaticLeafMethods ART_GTEST_oat_file_assistant_test_DEX_DEPS := $(ART_GTEST_dex2oat_environment_tests_DEX_DEPS) +ART_GTEST_dexoptanalyzer_test_DEX_DEPS := $(ART_GTEST_dex2oat_environment_tests_DEX_DEPS) ART_GTEST_oat_file_test_DEX_DEPS := Main MultiDex ART_GTEST_oat_test_DEX_DEPS := Main ART_GTEST_object_test_DEX_DEPS := ProtoCompare ProtoCompare2 StaticsFromCode XandY @@ -107,6 +109,7 @@ ART_GTEST_proxy_test_DEX_DEPS := Interfaces ART_GTEST_reflection_test_DEX_DEPS := Main NonStaticLeafMethods StaticLeafMethods ART_GTEST_profile_assistant_test_DEX_DEPS := ProfileTestMultiDex ART_GTEST_profile_compilation_info_test_DEX_DEPS := ProfileTestMultiDex +ART_GTEST_runtime_callbacks_test_DEX_DEPS := XandY ART_GTEST_stub_test_DEX_DEPS := AllFields ART_GTEST_transaction_test_DEX_DEPS := Transaction ART_GTEST_type_lookup_table_test_DEX_DEPS := Lookup @@ -120,14 +123,14 @@ ART_GTEST_elf_writer_test_TARGET_DEPS := $(TARGET_CORE_IMAGE_optimizing_no-pic_6 ART_GTEST_dex2oat_environment_tests_HOST_DEPS := \ $(HOST_CORE_IMAGE_optimizing_pic_64) \ $(HOST_CORE_IMAGE_optimizing_pic_32) \ - $(HOST_CORE_IMAGE_optimizing_no-pic_64) \ - $(HOST_CORE_IMAGE_optimizing_no-pic_32) \ + $(HOST_CORE_IMAGE_interpreter_pic_64) \ + $(HOST_CORE_IMAGE_interpreter_pic_32) \ $(HOST_OUT_EXECUTABLES)/patchoatd ART_GTEST_dex2oat_environment_tests_TARGET_DEPS := \ $(TARGET_CORE_IMAGE_optimizing_pic_64) \ $(TARGET_CORE_IMAGE_optimizing_pic_32) \ - $(TARGET_CORE_IMAGE_optimizing_no-pic_64) \ - $(TARGET_CORE_IMAGE_optimizing_no-pic_32) \ + $(TARGET_CORE_IMAGE_interpreter_pic_64) \ + $(TARGET_CORE_IMAGE_interpreter_pic_32) \ $(TARGET_OUT_EXECUTABLES)/patchoatd ART_GTEST_oat_file_assistant_test_HOST_DEPS := \ @@ -135,6 +138,12 @@ ART_GTEST_oat_file_assistant_test_HOST_DEPS := \ ART_GTEST_oat_file_assistant_test_TARGET_DEPS := \ $(ART_GTEST_dex2oat_environment_tests_TARGET_DEPS) +ART_GTEST_dexoptanalyzer_test_HOST_DEPS := \ + $(ART_GTEST_dex2oat_environment_tests_HOST_DEPS) \ + $(HOST_OUT_EXECUTABLES)/dexoptanalyzerd +ART_GTEST_dexoptanalyzer_test_TARGET_DEPS := \ + $(ART_GTEST_dex2oat_environment_tests_TARGET_DEPS) \ + dexoptanalyzerd ART_GTEST_dex2oat_test_HOST_DEPS := \ $(ART_GTEST_dex2oat_environment_tests_HOST_DEPS) @@ -218,6 +227,7 @@ ART_TEST_MODULES := \ art_dexdump_tests \ art_dexlayout_tests \ art_dexlist_tests \ + art_dexoptanalyzer_tests \ art_imgdiag_tests \ art_oatdump_tests \ art_profman_tests \ @@ -613,6 +623,9 @@ 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_dexoptanalyzer_test_DEX_DEPS := +ART_GTEST_dexoptanalyzer_test_HOST_DEPS := +ART_GTEST_dexoptanalyzer_test_TARGET_DEPS := ART_GTEST_dex2oat_test_DEX_DEPS := ART_GTEST_dex2oat_test_HOST_DEPS := ART_GTEST_dex2oat_test_TARGET_DEPS := diff --git a/build/art.go b/build/art.go index e6e0544e4d..baa6e59b55 100644 --- a/build/art.go +++ b/build/art.go @@ -58,7 +58,7 @@ func globalFlags(ctx android.BaseContext) ([]string, []string) { asflags = append(asflags, "-DART_HEAP_POISONING=1") } - if envTrue(ctx, "ART_USE_READ_BARRIER") || ctx.AConfig().ArtUseReadBarrier() { + if !envFalse(ctx, "ART_USE_READ_BARRIER") && ctx.AConfig().ArtUseReadBarrier() { // Used to change the read barrier type. Valid values are BAKER, BROOKS, TABLELOOKUP. // The default is BAKER. barrierType := envDefault(ctx, "ART_READ_BARRIER_TYPE", "BAKER") diff --git a/cmdline/cmdline_types.h b/cmdline/cmdline_types.h index 28c009edf0..f1123eb692 100644 --- a/cmdline/cmdline_types.h +++ b/cmdline/cmdline_types.h @@ -766,10 +766,6 @@ struct CmdlineType<ExperimentalFlags> : CmdlineTypeParser<ExperimentalFlags> { Result ParseAndAppend(const std::string& option, ExperimentalFlags& existing) { if (option == "none") { existing = ExperimentalFlags::kNone; - } else if (option == "agents") { - existing = existing | ExperimentalFlags::kAgents; - } else if (option == "runtime-plugins") { - existing = existing | ExperimentalFlags::kRuntimePlugins; } else { return Result::Failure(std::string("Unknown option '") + option + "'"); } diff --git a/compiler/common_compiler_test.h b/compiler/common_compiler_test.h index f4838c1119..0d45a50053 100644 --- a/compiler/common_compiler_test.h +++ b/compiler/common_compiler_test.h @@ -23,7 +23,7 @@ #include "common_runtime_test.h" #include "compiler.h" -#include "jit/offline_profiling_info.h" +#include "jit/profile_compilation_info.h" #include "oat_file.h" namespace art { diff --git a/compiler/debug/elf_debug_line_writer.h b/compiler/debug/elf_debug_line_writer.h index 3db73064ff..18a9165854 100644 --- a/compiler/debug/elf_debug_line_writer.h +++ b/compiler/debug/elf_debug_line_writer.h @@ -53,7 +53,8 @@ class ElfDebugLineWriter { // Write line table for given set of methods. // Returns the number of bytes written. size_t WriteCompilationUnit(ElfCompilationUnit& compilation_unit) { - const bool is64bit = Is64BitInstructionSet(builder_->GetIsa()); + const InstructionSet isa = builder_->GetIsa(); + const bool is64bit = Is64BitInstructionSet(isa); const Elf_Addr base_address = compilation_unit.is_code_address_text_relative ? builder_->GetText()->GetAddress() : 0; @@ -66,7 +67,7 @@ class ElfDebugLineWriter { std::unordered_map<std::string, size_t> directories_map; int code_factor_bits_ = 0; int dwarf_isa = -1; - switch (builder_->GetIsa()) { + switch (isa) { case kArm: // arm actually means thumb2. case kThumb2: code_factor_bits_ = 1; // 16-bit instuctions @@ -103,7 +104,7 @@ class ElfDebugLineWriter { for (uint32_t s = 0; s < code_info.GetNumberOfStackMaps(encoding); s++) { StackMap stack_map = code_info.GetStackMapAt(s, encoding); DCHECK(stack_map.IsValid()); - const uint32_t pc = stack_map.GetNativePcOffset(encoding.stack_map_encoding); + const uint32_t pc = stack_map.GetNativePcOffset(encoding.stack_map_encoding, isa); const int32_t dex = stack_map.GetDexPc(encoding.stack_map_encoding); pc2dex_map.push_back({pc, dex}); if (stack_map.HasDexRegisterMap(encoding.stack_map_encoding)) { diff --git a/compiler/debug/elf_debug_loc_writer.h b/compiler/debug/elf_debug_loc_writer.h index 9645643edd..bce538743b 100644 --- a/compiler/debug/elf_debug_loc_writer.h +++ b/compiler/debug/elf_debug_loc_writer.h @@ -92,7 +92,8 @@ std::vector<VariableLocation> GetVariableLocations( bool is64bitValue, uint64_t compilation_unit_code_address, uint32_t dex_pc_low, - uint32_t dex_pc_high) { + uint32_t dex_pc_high, + InstructionSet isa) { std::vector<VariableLocation> variable_locations; // Get stack maps sorted by pc (they might not be sorted internally). @@ -111,7 +112,7 @@ std::vector<VariableLocation> GetVariableLocations( // The main reason for this is to save space by avoiding undefined gaps. continue; } - const uint32_t pc_offset = stack_map.GetNativePcOffset(encoding.stack_map_encoding); + const uint32_t pc_offset = stack_map.GetNativePcOffset(encoding.stack_map_encoding, isa); DCHECK_LE(pc_offset, method_info->code_size); DCHECK_LE(compilation_unit_code_address, method_info->code_address); const uint32_t low_pc = dchecked_integral_cast<uint32_t>( @@ -196,7 +197,8 @@ static void WriteDebugLocEntry(const MethodDebugInfo* method_info, is64bitValue, compilation_unit_code_address, dex_pc_low, - dex_pc_high); + dex_pc_high, + isa); // Write .debug_loc entries. dwarf::Writer<> debug_loc(debug_loc_buffer); diff --git a/compiler/driver/compiler_driver.cc b/compiler/driver/compiler_driver.cc index faf8b41be1..bf065ee4ba 100644 --- a/compiler/driver/compiler_driver.cc +++ b/compiler/driver/compiler_driver.cc @@ -284,7 +284,7 @@ CompilerDriver::CompilerDriver( verification_results_(verification_results), compiler_(Compiler::Create(this, compiler_kind)), compiler_kind_(compiler_kind), - instruction_set_(instruction_set == kArm ? kThumb2: instruction_set), + instruction_set_(instruction_set == kArm ? kThumb2 : instruction_set), instruction_set_features_(instruction_set_features), requires_constructor_barrier_lock_("constructor barrier lock"), compiled_classes_lock_("compiled classes lock"), @@ -940,6 +940,31 @@ inline void CompilerDriver::CheckThreadPools() { DCHECK(single_thread_pool_ != nullptr); } +static void EnsureVerifiedOrVerifyAtRuntime(jobject jclass_loader, + const std::vector<const DexFile*>& dex_files) { + ScopedObjectAccess soa(Thread::Current()); + StackHandleScope<2> hs(soa.Self()); + Handle<mirror::ClassLoader> class_loader( + hs.NewHandle(soa.Decode<mirror::ClassLoader>(jclass_loader))); + MutableHandle<mirror::Class> cls(hs.NewHandle<mirror::Class>(nullptr)); + ClassLinker* class_linker = Runtime::Current()->GetClassLinker(); + + for (const DexFile* dex_file : dex_files) { + for (uint32_t i = 0; i < dex_file->NumClassDefs(); ++i) { + const DexFile::ClassDef& class_def = dex_file->GetClassDef(i); + const char* descriptor = dex_file->GetClassDescriptor(class_def); + cls.Assign(class_linker->FindClass(soa.Self(), descriptor, class_loader)); + if (cls.Get() == nullptr) { + soa.Self()->ClearException(); + } else if (&cls->GetDexFile() == dex_file) { + DCHECK(cls->IsErroneous() || cls->IsVerified() || cls->IsCompileTimeVerified()) + << cls->PrettyClass() + << " " << cls->GetStatus(); + } + } + } +} + void CompilerDriver::PreCompile(jobject class_loader, const std::vector<const DexFile*>& dex_files, TimingLogger* timings) { @@ -984,6 +1009,9 @@ void CompilerDriver::PreCompile(jobject class_loader, } if (compiler_options_->IsAnyMethodCompilationEnabled()) { + if (kIsDebugBuild) { + EnsureVerifiedOrVerifyAtRuntime(class_loader, dex_files); + } InitializeClasses(class_loader, dex_files, timings); VLOG(compiler) << "InitializeClasses: " << GetMemoryUsageString(false); } @@ -1034,23 +1062,6 @@ bool CompilerDriver::ShouldCompileBasedOnProfile(const MethodReference& method_r return result; } -bool CompilerDriver::ShouldVerifyClassBasedOnProfile(const DexFile& dex_file, - uint16_t class_idx) const { - if (!compiler_options_->VerifyOnlyProfile()) { - // No profile, verify everything. - return true; - } - DCHECK(profile_compilation_info_ != nullptr); - const DexFile::ClassDef& class_def = dex_file.GetClassDef(class_idx); - dex::TypeIndex type_idx = class_def.class_idx_; - bool result = profile_compilation_info_->ContainsClass(dex_file, type_idx); - if (kDebugProfileGuidedCompilation) { - LOG(INFO) << "[ProfileGuidedCompilation] " << (result ? "Verified" : "Skipped") << " method:" - << dex_file.GetClassDescriptor(class_def); - } - return result; -} - class ResolveCatchBlockExceptionsClassVisitor : public ClassVisitor { public: explicit ResolveCatchBlockExceptionsClassVisitor( @@ -1251,7 +1262,7 @@ class ClinitImageUpdate { } } - // java.lang.Reference visitor for VisitReferences. + // java.lang.ref.Reference visitor for VisitReferences. void operator()(ObjPtr<mirror::Class> klass ATTRIBUTE_UNUSED, ObjPtr<mirror::Reference> ref ATTRIBUTE_UNUSED) const {} @@ -1949,6 +1960,31 @@ static void PopulateVerifiedMethods(const DexFile& dex_file, DCHECK(!it.HasNext()); } +static void LoadAndUpdateStatus(const DexFile& dex_file, + const DexFile::ClassDef& class_def, + mirror::Class::Status status, + Handle<mirror::ClassLoader> class_loader, + Thread* self) + REQUIRES_SHARED(Locks::mutator_lock_) { + StackHandleScope<1> hs(self); + const char* descriptor = dex_file.GetClassDescriptor(class_def); + ClassLinker* class_linker = Runtime::Current()->GetClassLinker(); + Handle<mirror::Class> cls(hs.NewHandle<mirror::Class>( + class_linker->FindClass(self, descriptor, class_loader))); + if (cls.Get() != nullptr) { + // Check that the class is resolved with the current dex file. We might get + // a boot image class, or a class in a different dex file for multidex, and + // we should not update the status in that case. + if (&cls->GetDexFile() == &dex_file) { + ObjectLock<mirror::Class> lock(self, cls); + mirror::Class::SetStatus(cls, status, self); + } + } else { + DCHECK(self->IsExceptionPending()); + self->ClearException(); + } +} + bool CompilerDriver::FastVerify(jobject jclass_loader, const std::vector<const DexFile*>& dex_files, TimingLogger* timings) { @@ -1963,12 +1999,12 @@ bool CompilerDriver::FastVerify(jobject jclass_loader, StackHandleScope<2> hs(soa.Self()); Handle<mirror::ClassLoader> class_loader( hs.NewHandle(soa.Decode<mirror::ClassLoader>(jclass_loader))); - MutableHandle<mirror::Class> cls(hs.NewHandle<mirror::Class>(nullptr)); - ClassLinker* class_linker = Runtime::Current()->GetClassLinker(); if (!verifier_deps->ValidateDependencies(class_loader, soa.Self())) { return false; } + bool compiler_only_verifies = !GetCompilerOptions().IsAnyMethodCompilationEnabled(); + // We successfully validated the dependencies, now update class status // of verified classes. Note that the dependencies also record which classes // could not be fully verified; we could try again, but that would hurt verification @@ -1983,28 +2019,16 @@ bool CompilerDriver::FastVerify(jobject jclass_loader, for (uint32_t i = 0; i < dex_file->NumClassDefs(); ++i) { const DexFile::ClassDef& class_def = dex_file->GetClassDef(i); if (set.find(class_def.class_idx_) == set.end()) { - if (!GetCompilerOptions().IsAnyMethodCompilationEnabled()) { + if (compiler_only_verifies) { // Just update the compiled_classes_ map. The compiler doesn't need to resolve // the type. compiled_classes_.Overwrite( ClassReference(dex_file, i), new CompiledClass(mirror::Class::kStatusVerified)); } else { - // Resolve the type, so later compilation stages know they don't need to verify + // Update the class status, so later compilation stages know they don't need to verify // the class. - const char* descriptor = dex_file->GetClassDescriptor(class_def); - cls.Assign(class_linker->FindClass(soa.Self(), descriptor, class_loader)); - if (cls.Get() != nullptr) { - // Check that the class is resolved with the current dex file. We might get - // a boot image class, or a class in a different dex file for multidex, and - // we should not update the status in that case. - if (&cls->GetDexFile() == dex_file) { - ObjectLock<mirror::Class> lock(soa.Self(), cls); - mirror::Class::SetStatus(cls, mirror::Class::kStatusVerified, soa.Self()); - } - } else { - DCHECK(soa.Self()->IsExceptionPending()); - soa.Self()->ClearException(); - } + LoadAndUpdateStatus( + *dex_file, class_def, mirror::Class::kStatusVerified, class_loader, soa.Self()); // Create `VerifiedMethod`s for each methods, the compiler expects one for // quickening or compiling. // Note that this means: @@ -2013,6 +2037,14 @@ bool CompilerDriver::FastVerify(jobject jclass_loader, // TODO(ngeoffray): Reconsider this once we refactor compiler filters. PopulateVerifiedMethods(*dex_file, i, verification_results_); } + } else if (!compiler_only_verifies) { + // Make sure later compilation stages know they should not try to verify + // this class again. + LoadAndUpdateStatus(*dex_file, + class_def, + mirror::Class::kStatusRetryVerificationAtRuntime, + class_loader, + soa.Self()); } } } @@ -2077,13 +2109,6 @@ class VerifyClassVisitor : public CompilationVisitor { ATRACE_CALL(); ScopedObjectAccess soa(Thread::Current()); const DexFile& dex_file = *manager_->GetDexFile(); - if (!manager_->GetCompiler()->ShouldVerifyClassBasedOnProfile(dex_file, class_def_index)) { - // Skip verification since the class is not in the profile, and let the VerifierDeps know - // that the class will need to be verified at runtime. - verifier::VerifierDeps::MaybeRecordVerificationStatus( - dex_file, dex::TypeIndex(class_def_index), verifier::MethodVerifier::kSoftFailure); - return; - } const DexFile::ClassDef& class_def = dex_file.GetClassDef(class_def_index); const char* descriptor = dex_file.GetClassDescriptor(class_def); ClassLinker* class_linker = manager_->GetClassLinker(); @@ -2199,7 +2224,7 @@ class SetVerifiedClassVisitor : public CompilationVisitor { if (klass.Get() != nullptr) { // Only do this if the class is resolved. If even resolution fails, quickening will go very, // very wrong. - if (klass->IsResolved()) { + if (klass->IsResolved() && !klass->IsErroneousResolved()) { if (klass->GetStatus() < mirror::Class::kStatusVerified) { ObjectLock<mirror::Class> lock(soa.Self(), klass); // Set class status to verified. @@ -2626,7 +2651,8 @@ CompiledClass* CompilerDriver::GetCompiledClass(ClassReference ref) const { void CompilerDriver::RecordClassStatus(ClassReference ref, mirror::Class::Status status) { switch (status) { case mirror::Class::kStatusNotReady: - case mirror::Class::kStatusError: + case mirror::Class::kStatusErrorResolved: + case mirror::Class::kStatusErrorUnresolved: case mirror::Class::kStatusRetryVerificationAtRuntime: case mirror::Class::kStatusVerified: case mirror::Class::kStatusInitialized: diff --git a/compiler/driver/compiler_driver.h b/compiler/driver/compiler_driver.h index 6bfdd4da9c..503fe3adfc 100644 --- a/compiler/driver/compiler_driver.h +++ b/compiler/driver/compiler_driver.h @@ -33,7 +33,7 @@ #include "dex_file.h" #include "dex_file_types.h" #include "driver/compiled_method_storage.h" -#include "jit/offline_profiling_info.h" +#include "jit/profile_compilation_info.h" #include "invoke_type.h" #include "method_reference.h" #include "mirror/class.h" // For mirror::Class::Status. diff --git a/compiler/driver/compiler_driver_test.cc b/compiler/driver/compiler_driver_test.cc index 12684c09c0..1e4ca16844 100644 --- a/compiler/driver/compiler_driver_test.cc +++ b/compiler/driver/compiler_driver_test.cc @@ -32,7 +32,7 @@ #include "mirror/object_array-inl.h" #include "mirror/object-inl.h" #include "handle_scope-inl.h" -#include "jit/offline_profiling_info.h" +#include "jit/profile_compilation_info.h" #include "scoped_thread_state_change-inl.h" namespace art { diff --git a/compiler/exception_test.cc b/compiler/exception_test.cc index f9e5cb9cb7..eac46e5909 100644 --- a/compiler/exception_test.cc +++ b/compiler/exception_test.cc @@ -61,7 +61,7 @@ class ExceptionTest : public CommonRuntimeTest { ArenaPool pool; ArenaAllocator allocator(&pool); - StackMapStream stack_maps(&allocator); + StackMapStream stack_maps(&allocator, kRuntimeISA); stack_maps.BeginStackMapEntry(/* dex_pc */ 3u, /* native_pc_offset */ 3u, /* register_mask */ 0u, diff --git a/compiler/image_writer.cc b/compiler/image_writer.cc index 4109345f80..15e4cd8e9c 100644 --- a/compiler/image_writer.cc +++ b/compiler/image_writer.cc @@ -756,7 +756,7 @@ bool ImageWriter::PruneAppImageClassInternal( bool my_early_exit = false; // Only for ourselves, ignore caller. // Remove classes that failed to verify since we don't want to have java.lang.VerifyError in the // app image. - if (klass->GetStatus() == mirror::Class::kStatusError) { + if (klass->IsErroneous()) { result = true; } else { ObjPtr<mirror::ClassExt> ext(klass->GetExtData()); @@ -777,8 +777,8 @@ bool ImageWriter::PruneAppImageClassInternal( visited); } // Check static fields and their classes. - size_t num_static_fields = klass->NumReferenceStaticFields(); - if (num_static_fields != 0 && klass->IsResolved()) { + if (klass->IsResolved() && klass->NumReferenceStaticFields() != 0) { + size_t num_static_fields = klass->NumReferenceStaticFields(); // Presumably GC can happen when we are cross compiling, it should not cause performance // problems to do pointer size logic. MemberOffset field_offset = klass->GetFirstReferenceStaticFieldOffset( @@ -1154,7 +1154,7 @@ mirror::Object* ImageWriter::TryAssignBinSlot(WorkStack& work_stack, // Visit and assign offsets for fields and field arrays. mirror::Class* as_klass = obj->AsClass(); mirror::DexCache* dex_cache = as_klass->GetDexCache(); - DCHECK_NE(as_klass->GetStatus(), mirror::Class::kStatusError); + DCHECK(!as_klass->IsErroneous()) << as_klass->GetStatus(); if (compile_app_image_) { // Extra sanity, no boot loader classes should be left! CHECK(!IsBootClassLoaderClass(as_klass)) << as_klass->PrettyClass(); @@ -1166,9 +1166,9 @@ mirror::Object* ImageWriter::TryAssignBinSlot(WorkStack& work_stack, // belongs. oat_index = GetOatIndexForDexCache(dex_cache); ImageInfo& image_info = GetImageInfo(oat_index); - { - // Note: This table is only accessed from the image writer, avoid locking to prevent lock - // order violations from root visiting. + if (!compile_app_image_) { + // Note: Avoid locking to prevent lock order violations from root visiting; + // image_info.class_table_ is only accessed from the image writer. image_info.class_table_->InsertWithoutLocks(as_klass); } for (LengthPrefixedArray<ArtField>* cur_fields : fields) { @@ -1265,7 +1265,14 @@ mirror::Object* ImageWriter::TryAssignBinSlot(WorkStack& work_stack, // class loader. mirror::ClassLoader* class_loader = obj->AsClassLoader(); if (class_loader->GetClassTable() != nullptr) { + DCHECK(compile_app_image_); + DCHECK(class_loaders_.empty()); class_loaders_.insert(class_loader); + ImageInfo& image_info = GetImageInfo(oat_index); + // Note: Avoid locking to prevent lock order violations from root visiting; + // image_info.class_table_ table is only accessed from the image writer + // and class_loader->GetClassTable() is iterated but not modified. + image_info.class_table_->CopyWithoutLocks(*class_loader->GetClassTable()); } } AssignImageBinSlot(obj, oat_index); diff --git a/compiler/jni/quick/x86_64/calling_convention_x86_64.cc b/compiler/jni/quick/x86_64/calling_convention_x86_64.cc index 8ca0ffe53e..ba654f4750 100644 --- a/compiler/jni/quick/x86_64/calling_convention_x86_64.cc +++ b/compiler/jni/quick/x86_64/calling_convention_x86_64.cc @@ -160,7 +160,7 @@ const ManagedRegisterEntrySpills& X86_64ManagedRuntimeCallingConvention::EntrySp while (HasNext()) { ManagedRegister in_reg = CurrentParamRegister(); if (!in_reg.IsNoRegister()) { - int32_t size = IsParamALongOrDouble(itr_args_)? 8 : 4; + int32_t size = IsParamALongOrDouble(itr_args_) ? 8 : 4; int32_t spill_offset = CurrentParamStackOffset().Uint32Value(); ManagedRegisterSpill spill(in_reg, size, spill_offset); entry_spills_.push_back(spill); diff --git a/compiler/oat_writer.cc b/compiler/oat_writer.cc index de5af97b9a..bd2c5e3bfc 100644 --- a/compiler/oat_writer.cc +++ b/compiler/oat_writer.cc @@ -716,7 +716,10 @@ class OatWriter::InitOatClassesMethodVisitor : public DexMethodVisitor { if (compiled_class != nullptr) { status = compiled_class->GetStatus(); } else if (writer_->compiler_driver_->GetVerificationResults()->IsClassRejected(class_ref)) { - status = mirror::Class::kStatusError; + // The oat class status is used only for verification of resolved classes, + // so use kStatusErrorResolved whether the class was resolved or unresolved + // during compile-time verification. + status = mirror::Class::kStatusErrorResolved; } else { status = mirror::Class::kStatusNotReady; } diff --git a/compiler/optimizing/code_generator.cc b/compiler/optimizing/code_generator.cc index 70c2738010..99427f05da 100644 --- a/compiler/optimizing/code_generator.cc +++ b/compiler/optimizing/code_generator.cc @@ -839,8 +839,8 @@ void CodeGenerator::RecordPcInfo(HInstruction* instruction, // last emitted is different than the native pc of the stack map just emitted. size_t number_of_stack_maps = stack_map_stream_.GetNumberOfStackMaps(); if (number_of_stack_maps > 1) { - DCHECK_NE(stack_map_stream_.GetStackMap(number_of_stack_maps - 1).native_pc_offset, - stack_map_stream_.GetStackMap(number_of_stack_maps - 2).native_pc_offset); + DCHECK_NE(stack_map_stream_.GetStackMap(number_of_stack_maps - 1).native_pc_code_offset, + stack_map_stream_.GetStackMap(number_of_stack_maps - 2).native_pc_code_offset); } } } @@ -848,7 +848,8 @@ void CodeGenerator::RecordPcInfo(HInstruction* instruction, bool CodeGenerator::HasStackMapAtCurrentPc() { uint32_t pc = GetAssembler()->CodeSize(); size_t count = stack_map_stream_.GetNumberOfStackMaps(); - return count > 0 && stack_map_stream_.GetStackMap(count - 1).native_pc_offset == pc; + CodeOffset native_pc_offset = stack_map_stream_.GetStackMap(count - 1).native_pc_code_offset; + return (count > 0) && (native_pc_offset.Uint32Value(GetInstructionSet()) == pc); } void CodeGenerator::MaybeRecordNativeDebugInfo(HInstruction* instruction, diff --git a/compiler/optimizing/code_generator.h b/compiler/optimizing/code_generator.h index 38d532e1e9..2d129aff22 100644 --- a/compiler/optimizing/code_generator.h +++ b/compiler/optimizing/code_generator.h @@ -608,7 +608,7 @@ class CodeGenerator : public DeletableArenaObject<kArenaAllocCodeGenerator> { number_of_register_pairs_(number_of_register_pairs), core_callee_save_mask_(core_callee_save_mask), fpu_callee_save_mask_(fpu_callee_save_mask), - stack_map_stream_(graph->GetArena()), + stack_map_stream_(graph->GetArena(), graph->GetInstructionSet()), block_order_(nullptr), jit_string_roots_(StringReferenceValueComparator(), graph->GetArena()->Adapter(kArenaAllocCodeGenerator)), diff --git a/compiler/optimizing/code_generator_arm.cc b/compiler/optimizing/code_generator_arm.cc index 9c9c604dca..f5b6ebef9c 100644 --- a/compiler/optimizing/code_generator_arm.cc +++ b/compiler/optimizing/code_generator_arm.cc @@ -1239,7 +1239,8 @@ void CodeGeneratorARM::Finalize(CodeAllocator* allocator) { // Adjust native pc offsets in stack maps. for (size_t i = 0, num = stack_map_stream_.GetNumberOfStackMaps(); i != num; ++i) { - uint32_t old_position = stack_map_stream_.GetStackMap(i).native_pc_offset; + uint32_t old_position = + stack_map_stream_.GetStackMap(i).native_pc_code_offset.Uint32Value(kThumb2); uint32_t new_position = __ GetAdjustedPosition(old_position); stack_map_stream_.SetStackMapNativePcOffset(i, new_position); } @@ -3331,7 +3332,7 @@ void LocationsBuilderARM::VisitDiv(HDiv* div) { InvokeRuntimeCallingConvention calling_convention; locations->SetInAt(0, Location::RegisterLocation(calling_convention.GetRegisterAt(0))); locations->SetInAt(1, Location::RegisterLocation(calling_convention.GetRegisterAt(1))); - // Note: divrem will compute both the quotient and the remainder as the pair R0 and R1, but + // Note: divmod will compute both the quotient and the remainder as the pair R0 and R1, but // we only need the former. locations->SetOut(Location::RegisterLocation(R0)); } @@ -3458,7 +3459,7 @@ void LocationsBuilderARM::VisitRem(HRem* rem) { InvokeRuntimeCallingConvention calling_convention; locations->SetInAt(0, Location::RegisterLocation(calling_convention.GetRegisterAt(0))); locations->SetInAt(1, Location::RegisterLocation(calling_convention.GetRegisterAt(1))); - // Note: divrem will compute both the quotient and the remainder as the pair R0 and R1, but + // Note: divmod will compute both the quotient and the remainder as the pair R0 and R1, but // we only need the latter. locations->SetOut(Location::RegisterLocation(R1)); } @@ -7150,18 +7151,7 @@ void CodeGeneratorARM::GenerateReadBarrierForRootSlow(HInstruction* instruction, HInvokeStaticOrDirect::DispatchInfo CodeGeneratorARM::GetSupportedInvokeStaticOrDirectDispatch( const HInvokeStaticOrDirect::DispatchInfo& desired_dispatch_info, HInvokeStaticOrDirect* invoke ATTRIBUTE_UNUSED) { - HInvokeStaticOrDirect::DispatchInfo dispatch_info = desired_dispatch_info; - // We disable pc-relative load when there is an irreducible loop, as the optimization - // is incompatible with it. - // TODO: Create as many ArmDexCacheArraysBase instructions as needed for methods - // with irreducible loops. - if (GetGraph()->HasIrreducibleLoops() && - (dispatch_info.method_load_kind == - HInvokeStaticOrDirect::MethodLoadKind::kDexCachePcRelative)) { - dispatch_info.method_load_kind = HInvokeStaticOrDirect::MethodLoadKind::kDexCacheViaMethod; - } - - return dispatch_info; + return desired_dispatch_info; } Register CodeGeneratorARM::GetInvokeStaticOrDirectExtraParameter(HInvokeStaticOrDirect* invoke, @@ -7181,8 +7171,7 @@ Register CodeGeneratorARM::GetInvokeStaticOrDirectExtraParameter(HInvokeStaticOr // save one load. However, since this is just an intrinsic slow path we prefer this // simple and more robust approach rather that trying to determine if that's the case. SlowPathCode* slow_path = GetCurrentSlowPath(); - DCHECK(slow_path != nullptr); // For intrinsified invokes the call is emitted on the slow path. - if (slow_path->IsCoreRegisterSaved(location.AsRegister<Register>())) { + if (slow_path != nullptr && slow_path->IsCoreRegisterSaved(location.AsRegister<Register>())) { int stack_offset = slow_path->GetStackOffsetOfCoreRegister(location.AsRegister<Register>()); __ LoadFromOffset(kLoadWord, temp, SP, stack_offset); return temp; @@ -7190,7 +7179,8 @@ Register CodeGeneratorARM::GetInvokeStaticOrDirectExtraParameter(HInvokeStaticOr return location.AsRegister<Register>(); } -void CodeGeneratorARM::GenerateStaticOrDirectCall(HInvokeStaticOrDirect* invoke, Location temp) { +Location CodeGeneratorARM::GenerateCalleeMethodStaticOrDirectCall(HInvokeStaticOrDirect* invoke, + Location temp) { Location callee_method = temp; // For all kinds except kRecursive, callee will be in temp. switch (invoke->GetMethodLoadKind()) { case HInvokeStaticOrDirect::MethodLoadKind::kStringInit: { @@ -7239,6 +7229,11 @@ void CodeGeneratorARM::GenerateStaticOrDirectCall(HInvokeStaticOrDirect* invoke, break; } } + return callee_method; +} + +void CodeGeneratorARM::GenerateStaticOrDirectCall(HInvokeStaticOrDirect* invoke, Location temp) { + Location callee_method = GenerateCalleeMethodStaticOrDirectCall(invoke, temp); switch (invoke->GetCodePtrLocation()) { case HInvokeStaticOrDirect::CodePtrLocation::kCallSelf: diff --git a/compiler/optimizing/code_generator_arm.h b/compiler/optimizing/code_generator_arm.h index 52d18575ff..df2dbc74ab 100644 --- a/compiler/optimizing/code_generator_arm.h +++ b/compiler/optimizing/code_generator_arm.h @@ -456,6 +456,7 @@ class CodeGeneratorARM : public CodeGenerator { const HInvokeStaticOrDirect::DispatchInfo& desired_dispatch_info, HInvokeStaticOrDirect* invoke) OVERRIDE; + Location GenerateCalleeMethodStaticOrDirectCall(HInvokeStaticOrDirect* invoke, Location temp); void GenerateStaticOrDirectCall(HInvokeStaticOrDirect* invoke, Location temp) OVERRIDE; void GenerateVirtualCall(HInvokeVirtual* invoke, Location temp) OVERRIDE; diff --git a/compiler/optimizing/code_generator_arm64.cc b/compiler/optimizing/code_generator_arm64.cc index 68d0b869e7..9762ee81b1 100644 --- a/compiler/optimizing/code_generator_arm64.cc +++ b/compiler/optimizing/code_generator_arm64.cc @@ -3993,7 +3993,8 @@ HInvokeStaticOrDirect::DispatchInfo CodeGeneratorARM64::GetSupportedInvokeStatic return desired_dispatch_info; } -void CodeGeneratorARM64::GenerateStaticOrDirectCall(HInvokeStaticOrDirect* invoke, Location temp) { +Location CodeGeneratorARM64::GenerateCalleeMethodStaticOrDirectCall(HInvokeStaticOrDirect* invoke, + Location temp) { // Make sure that ArtMethod* is passed in kArtMethodRegister as per the calling convention. Location callee_method = temp; // For all kinds except kRecursive, callee will be in temp. switch (invoke->GetMethodLoadKind()) { @@ -4047,6 +4048,12 @@ void CodeGeneratorARM64::GenerateStaticOrDirectCall(HInvokeStaticOrDirect* invok break; } } + return callee_method; +} + +void CodeGeneratorARM64::GenerateStaticOrDirectCall(HInvokeStaticOrDirect* invoke, Location temp) { + // All registers are assumed to be correctly set up. + Location callee_method = GenerateCalleeMethodStaticOrDirectCall(invoke, temp); switch (invoke->GetCodePtrLocation()) { case HInvokeStaticOrDirect::CodePtrLocation::kCallSelf: @@ -4655,7 +4662,7 @@ void LocationsBuilderARM64::VisitMonitorOperation(HMonitorOperation* instruction } void InstructionCodeGeneratorARM64::VisitMonitorOperation(HMonitorOperation* instruction) { - codegen_->InvokeRuntime(instruction->IsEnter() ? kQuickLockObject: kQuickUnlockObject, + codegen_->InvokeRuntime(instruction->IsEnter() ? kQuickLockObject : kQuickUnlockObject, instruction, instruction->GetDexPc()); if (instruction->IsEnter()) { diff --git a/compiler/optimizing/code_generator_arm64.h b/compiler/optimizing/code_generator_arm64.h index 224f17072f..f6cb90a63a 100644 --- a/compiler/optimizing/code_generator_arm64.h +++ b/compiler/optimizing/code_generator_arm64.h @@ -526,6 +526,7 @@ class CodeGeneratorARM64 : public CodeGenerator { const HInvokeStaticOrDirect::DispatchInfo& desired_dispatch_info, HInvokeStaticOrDirect* invoke) OVERRIDE; + Location GenerateCalleeMethodStaticOrDirectCall(HInvokeStaticOrDirect* invoke, Location temp); void GenerateStaticOrDirectCall(HInvokeStaticOrDirect* invoke, Location temp) OVERRIDE; void GenerateVirtualCall(HInvokeVirtual* invoke, Location temp) OVERRIDE; diff --git a/compiler/optimizing/code_generator_arm_vixl.cc b/compiler/optimizing/code_generator_arm_vixl.cc index 592ee5aba6..893e465392 100644 --- a/compiler/optimizing/code_generator_arm_vixl.cc +++ b/compiler/optimizing/code_generator_arm_vixl.cc @@ -3336,7 +3336,7 @@ void LocationsBuilderARMVIXL::VisitDiv(HDiv* div) { InvokeRuntimeCallingConventionARMVIXL calling_convention; locations->SetInAt(0, LocationFrom(calling_convention.GetRegisterAt(0))); locations->SetInAt(1, LocationFrom(calling_convention.GetRegisterAt(1))); - // Note: divrem will compute both the quotient and the remainder as the pair R0 and R1, but + // Note: divmod will compute both the quotient and the remainder as the pair R0 and R1, but // we only need the former. locations->SetOut(LocationFrom(r0)); } @@ -3450,7 +3450,7 @@ void LocationsBuilderARMVIXL::VisitRem(HRem* rem) { InvokeRuntimeCallingConventionARMVIXL calling_convention; locations->SetInAt(0, LocationFrom(calling_convention.GetRegisterAt(0))); locations->SetInAt(1, LocationFrom(calling_convention.GetRegisterAt(1))); - // Note: divrem will compute both the quotient and the remainder as the pair R0 and R1, but + // Note: divmod will compute both the quotient and the remainder as the pair R0 and R1, but // we only need the latter. locations->SetOut(LocationFrom(r1)); } @@ -3998,8 +3998,8 @@ void LocationsBuilderARMVIXL::VisitNewArray(HNewArray* instruction) { new (GetGraph()->GetArena()) LocationSummary(instruction, LocationSummary::kCallOnMainOnly); InvokeRuntimeCallingConventionARMVIXL calling_convention; locations->SetOut(LocationFrom(r0)); - locations->SetInAt(0, LocationFrom(calling_convention.GetRegisterAt(1))); - locations->SetInAt(1, LocationFrom(calling_convention.GetRegisterAt(2))); + locations->SetInAt(0, LocationFrom(calling_convention.GetRegisterAt(0))); + locations->SetInAt(1, LocationFrom(calling_convention.GetRegisterAt(1))); } void InstructionCodeGeneratorARMVIXL::VisitNewArray(HNewArray* instruction) { @@ -7233,18 +7233,7 @@ void CodeGeneratorARMVIXL::GenerateReadBarrierForRootSlow(HInstruction* instruct HInvokeStaticOrDirect::DispatchInfo CodeGeneratorARMVIXL::GetSupportedInvokeStaticOrDirectDispatch( const HInvokeStaticOrDirect::DispatchInfo& desired_dispatch_info, HInvokeStaticOrDirect* invoke ATTRIBUTE_UNUSED) { - HInvokeStaticOrDirect::DispatchInfo dispatch_info = desired_dispatch_info; - // We disable pc-relative load when there is an irreducible loop, as the optimization - // is incompatible with it. - // TODO: Create as many ArmDexCacheArraysBase instructions as needed for methods - // with irreducible loops. - if (GetGraph()->HasIrreducibleLoops() && - (dispatch_info.method_load_kind == - HInvokeStaticOrDirect::MethodLoadKind::kDexCachePcRelative)) { - dispatch_info.method_load_kind = HInvokeStaticOrDirect::MethodLoadKind::kDexCacheViaMethod; - } - - return dispatch_info; + return desired_dispatch_info; } vixl32::Register CodeGeneratorARMVIXL::GetInvokeStaticOrDirectExtraParameter( @@ -7273,7 +7262,7 @@ vixl32::Register CodeGeneratorARMVIXL::GetInvokeStaticOrDirectExtraParameter( return RegisterFrom(location); } -void CodeGeneratorARMVIXL::GenerateStaticOrDirectCall( +Location CodeGeneratorARMVIXL::GenerateCalleeMethodStaticOrDirectCall( HInvokeStaticOrDirect* invoke, Location temp) { Location callee_method = temp; // For all kinds except kRecursive, callee will be in temp. switch (invoke->GetMethodLoadKind()) { @@ -7324,6 +7313,12 @@ void CodeGeneratorARMVIXL::GenerateStaticOrDirectCall( break; } } + return callee_method; +} + +void CodeGeneratorARMVIXL::GenerateStaticOrDirectCall(HInvokeStaticOrDirect* invoke, + Location temp) { + Location callee_method = GenerateCalleeMethodStaticOrDirectCall(invoke, temp); switch (invoke->GetCodePtrLocation()) { case HInvokeStaticOrDirect::CodePtrLocation::kCallSelf: diff --git a/compiler/optimizing/code_generator_arm_vixl.h b/compiler/optimizing/code_generator_arm_vixl.h index be653535ea..8ae3b7dc39 100644 --- a/compiler/optimizing/code_generator_arm_vixl.h +++ b/compiler/optimizing/code_generator_arm_vixl.h @@ -537,6 +537,7 @@ class CodeGeneratorARMVIXL : public CodeGenerator { const HInvokeStaticOrDirect::DispatchInfo& desired_dispatch_info, HInvokeStaticOrDirect* invoke) OVERRIDE; + Location GenerateCalleeMethodStaticOrDirectCall(HInvokeStaticOrDirect* invoke, Location temp); void GenerateStaticOrDirectCall(HInvokeStaticOrDirect* invoke, Location temp) OVERRIDE; void GenerateVirtualCall(HInvokeVirtual* invoke, Location temp) OVERRIDE; diff --git a/compiler/optimizing/code_generator_mips.cc b/compiler/optimizing/code_generator_mips.cc index a0383826d3..76be74e921 100644 --- a/compiler/optimizing/code_generator_mips.cc +++ b/compiler/optimizing/code_generator_mips.cc @@ -496,7 +496,8 @@ void CodeGeneratorMIPS::Finalize(CodeAllocator* allocator) { // Adjust native pc offsets in stack maps. for (size_t i = 0, num = stack_map_stream_.GetNumberOfStackMaps(); i != num; ++i) { - uint32_t old_position = stack_map_stream_.GetStackMap(i).native_pc_offset; + uint32_t old_position = + stack_map_stream_.GetStackMap(i).native_pc_code_offset.Uint32Value(kMips); uint32_t new_position = __ GetAdjustedPosition(old_position); DCHECK_GE(new_position, old_position); stack_map_stream_.SetStackMapNativePcOffset(i, new_position); diff --git a/compiler/optimizing/code_generator_mips64.cc b/compiler/optimizing/code_generator_mips64.cc index 446dea659e..192b4a5050 100644 --- a/compiler/optimizing/code_generator_mips64.cc +++ b/compiler/optimizing/code_generator_mips64.cc @@ -450,7 +450,8 @@ void CodeGeneratorMIPS64::Finalize(CodeAllocator* allocator) { // Adjust native pc offsets in stack maps. for (size_t i = 0, num = stack_map_stream_.GetNumberOfStackMaps(); i != num; ++i) { - uint32_t old_position = stack_map_stream_.GetStackMap(i).native_pc_offset; + uint32_t old_position = + stack_map_stream_.GetStackMap(i).native_pc_code_offset.Uint32Value(kMips64); uint32_t new_position = __ GetAdjustedPosition(old_position); DCHECK_GE(new_position, old_position); stack_map_stream_.SetStackMapNativePcOffset(i, new_position); diff --git a/compiler/optimizing/code_generator_x86.cc b/compiler/optimizing/code_generator_x86.cc index 853c91fac8..1b7431612d 100644 --- a/compiler/optimizing/code_generator_x86.cc +++ b/compiler/optimizing/code_generator_x86.cc @@ -1023,7 +1023,8 @@ CodeGeneratorX86::CodeGeneratorX86(HGraph* graph, jit_class_patches_(graph->GetArena()->Adapter(kArenaAllocCodeGenerator)), constant_area_start_(-1), fixups_to_jump_tables_(graph->GetArena()->Adapter(kArenaAllocCodeGenerator)), - method_address_offset_(-1) { + method_address_offset_(std::less<uint32_t>(), + graph->GetArena()->Adapter(kArenaAllocCodeGenerator)) { // Use a fake return address register to mimic Quick. AddAllocatedRegister(Location::RegisterLocation(kFakeReturnRegister)); } @@ -1498,8 +1499,9 @@ void InstructionCodeGeneratorX86::GenerateFPCompare(Location lhs, DCHECK(const_area->IsEmittedAtUseSite()); __ ucomisd(lhs.AsFpuRegister<XmmRegister>(), codegen_->LiteralDoubleAddress( - const_area->GetConstant()->AsDoubleConstant()->GetValue(), - const_area->GetLocations()->InAt(0).AsRegister<Register>())); + const_area->GetConstant()->AsDoubleConstant()->GetValue(), + const_area->GetBaseMethodAddress(), + const_area->GetLocations()->InAt(0).AsRegister<Register>())); } else { DCHECK(rhs.IsDoubleStackSlot()); __ ucomisd(lhs.AsFpuRegister<XmmRegister>(), Address(ESP, rhs.GetStackIndex())); @@ -1511,8 +1513,9 @@ void InstructionCodeGeneratorX86::GenerateFPCompare(Location lhs, DCHECK(const_area->IsEmittedAtUseSite()); __ ucomiss(lhs.AsFpuRegister<XmmRegister>(), codegen_->LiteralFloatAddress( - const_area->GetConstant()->AsFloatConstant()->GetValue(), - const_area->GetLocations()->InAt(0).AsRegister<Register>())); + const_area->GetConstant()->AsFloatConstant()->GetValue(), + const_area->GetBaseMethodAddress(), + const_area->GetLocations()->InAt(0).AsRegister<Register>())); } else { DCHECK(rhs.IsStackSlot()); __ ucomiss(lhs.AsFpuRegister<XmmRegister>(), Address(ESP, rhs.GetStackIndex())); @@ -1778,7 +1781,7 @@ void InstructionCodeGeneratorX86::VisitSelect(HSelect* select) { cond = X86Condition(condition->GetCondition()); } } else { - // Must be a boolean condition, which needs to be compared to 0. + // Must be a Boolean condition, which needs to be compared to 0. Register cond_reg = locations->InAt(2).AsRegister<Register>(); __ testl(cond_reg, cond_reg); } @@ -2360,10 +2363,14 @@ void InstructionCodeGeneratorX86::VisitX86FPNeg(HX86FPNeg* neg) { Register constant_area = locations->InAt(1).AsRegister<Register>(); XmmRegister mask = locations->GetTemp(0).AsFpuRegister<XmmRegister>(); if (neg->GetType() == Primitive::kPrimFloat) { - __ movss(mask, codegen_->LiteralInt32Address(INT32_C(0x80000000), constant_area)); + __ movss(mask, codegen_->LiteralInt32Address(INT32_C(0x80000000), + neg->GetBaseMethodAddress(), + constant_area)); __ xorps(out.AsFpuRegister<XmmRegister>(), mask); } else { - __ movsd(mask, codegen_->LiteralInt64Address(INT64_C(0x8000000000000000), constant_area)); + __ movsd(mask, codegen_->LiteralInt64Address(INT64_C(0x8000000000000000), + neg->GetBaseMethodAddress(), + constant_area)); __ xorpd(out.AsFpuRegister<XmmRegister>(), mask); } } @@ -3012,8 +3019,9 @@ void InstructionCodeGeneratorX86::VisitAdd(HAdd* add) { DCHECK(const_area->IsEmittedAtUseSite()); __ addss(first.AsFpuRegister<XmmRegister>(), codegen_->LiteralFloatAddress( - const_area->GetConstant()->AsFloatConstant()->GetValue(), - const_area->GetLocations()->InAt(0).AsRegister<Register>())); + const_area->GetConstant()->AsFloatConstant()->GetValue(), + const_area->GetBaseMethodAddress(), + const_area->GetLocations()->InAt(0).AsRegister<Register>())); } else { DCHECK(second.IsStackSlot()); __ addss(first.AsFpuRegister<XmmRegister>(), Address(ESP, second.GetStackIndex())); @@ -3029,8 +3037,9 @@ void InstructionCodeGeneratorX86::VisitAdd(HAdd* add) { DCHECK(const_area->IsEmittedAtUseSite()); __ addsd(first.AsFpuRegister<XmmRegister>(), codegen_->LiteralDoubleAddress( - const_area->GetConstant()->AsDoubleConstant()->GetValue(), - const_area->GetLocations()->InAt(0).AsRegister<Register>())); + const_area->GetConstant()->AsDoubleConstant()->GetValue(), + const_area->GetBaseMethodAddress(), + const_area->GetLocations()->InAt(0).AsRegister<Register>())); } else { DCHECK(second.IsDoubleStackSlot()); __ addsd(first.AsFpuRegister<XmmRegister>(), Address(ESP, second.GetStackIndex())); @@ -3116,8 +3125,9 @@ void InstructionCodeGeneratorX86::VisitSub(HSub* sub) { DCHECK(const_area->IsEmittedAtUseSite()); __ subss(first.AsFpuRegister<XmmRegister>(), codegen_->LiteralFloatAddress( - const_area->GetConstant()->AsFloatConstant()->GetValue(), - const_area->GetLocations()->InAt(0).AsRegister<Register>())); + const_area->GetConstant()->AsFloatConstant()->GetValue(), + const_area->GetBaseMethodAddress(), + const_area->GetLocations()->InAt(0).AsRegister<Register>())); } else { DCHECK(second.IsStackSlot()); __ subss(first.AsFpuRegister<XmmRegister>(), Address(ESP, second.GetStackIndex())); @@ -3134,6 +3144,7 @@ void InstructionCodeGeneratorX86::VisitSub(HSub* sub) { __ subsd(first.AsFpuRegister<XmmRegister>(), codegen_->LiteralDoubleAddress( const_area->GetConstant()->AsDoubleConstant()->GetValue(), + const_area->GetBaseMethodAddress(), const_area->GetLocations()->InAt(0).AsRegister<Register>())); } else { DCHECK(second.IsDoubleStackSlot()); @@ -3304,6 +3315,7 @@ void InstructionCodeGeneratorX86::VisitMul(HMul* mul) { __ mulss(first.AsFpuRegister<XmmRegister>(), codegen_->LiteralFloatAddress( const_area->GetConstant()->AsFloatConstant()->GetValue(), + const_area->GetBaseMethodAddress(), const_area->GetLocations()->InAt(0).AsRegister<Register>())); } else { DCHECK(second.IsStackSlot()); @@ -3322,6 +3334,7 @@ void InstructionCodeGeneratorX86::VisitMul(HMul* mul) { __ mulsd(first.AsFpuRegister<XmmRegister>(), codegen_->LiteralDoubleAddress( const_area->GetConstant()->AsDoubleConstant()->GetValue(), + const_area->GetBaseMethodAddress(), const_area->GetLocations()->InAt(0).AsRegister<Register>())); } else { DCHECK(second.IsDoubleStackSlot()); @@ -3690,6 +3703,7 @@ void InstructionCodeGeneratorX86::VisitDiv(HDiv* div) { __ divss(first.AsFpuRegister<XmmRegister>(), codegen_->LiteralFloatAddress( const_area->GetConstant()->AsFloatConstant()->GetValue(), + const_area->GetBaseMethodAddress(), const_area->GetLocations()->InAt(0).AsRegister<Register>())); } else { DCHECK(second.IsStackSlot()); @@ -3706,8 +3720,9 @@ void InstructionCodeGeneratorX86::VisitDiv(HDiv* div) { DCHECK(const_area->IsEmittedAtUseSite()); __ divsd(first.AsFpuRegister<XmmRegister>(), codegen_->LiteralDoubleAddress( - const_area->GetConstant()->AsDoubleConstant()->GetValue(), - const_area->GetLocations()->InAt(0).AsRegister<Register>())); + const_area->GetConstant()->AsDoubleConstant()->GetValue(), + const_area->GetBaseMethodAddress(), + const_area->GetLocations()->InAt(0).AsRegister<Register>())); } else { DCHECK(second.IsDoubleStackSlot()); __ divsd(first.AsFpuRegister<XmmRegister>(), Address(ESP, second.GetStackIndex())); @@ -4454,18 +4469,7 @@ void CodeGeneratorX86::GenerateMemoryBarrier(MemBarrierKind kind) { HInvokeStaticOrDirect::DispatchInfo CodeGeneratorX86::GetSupportedInvokeStaticOrDirectDispatch( const HInvokeStaticOrDirect::DispatchInfo& desired_dispatch_info, HInvokeStaticOrDirect* invoke ATTRIBUTE_UNUSED) { - HInvokeStaticOrDirect::DispatchInfo dispatch_info = desired_dispatch_info; - - // We disable pc-relative load when there is an irreducible loop, as the optimization - // is incompatible with it. - // TODO: Create as many X86ComputeBaseMethodAddress instructions - // as needed for methods with irreducible loops. - if (GetGraph()->HasIrreducibleLoops() && - (dispatch_info.method_load_kind == - HInvokeStaticOrDirect::MethodLoadKind::kDexCachePcRelative)) { - dispatch_info.method_load_kind = HInvokeStaticOrDirect::MethodLoadKind::kDexCacheViaMethod; - } - return dispatch_info; + return desired_dispatch_info; } Register CodeGeneratorX86::GetInvokeStaticOrDirectExtraParameter(HInvokeStaticOrDirect* invoke, @@ -4518,7 +4522,10 @@ Location CodeGeneratorX86::GenerateCalleeMethodStaticOrDirectCall(HInvokeStaticO __ movl(temp.AsRegister<Register>(), Address(base_reg, kDummy32BitOffset)); // Bind a new fixup label at the end of the "movl" insn. uint32_t offset = invoke->GetDexCacheArrayOffset(); - __ Bind(NewPcRelativeDexCacheArrayPatch(invoke->GetDexFileForPcRelativeDexCache(), offset)); + __ Bind(NewPcRelativeDexCacheArrayPatch( + invoke->InputAt(invoke->GetSpecialInputIndex())->AsX86ComputeBaseMethodAddress(), + invoke->GetDexFileForPcRelativeDexCache(), + offset)); break; } case HInvokeStaticOrDirect::MethodLoadKind::kDexCacheViaMethod: { @@ -4603,31 +4610,54 @@ void CodeGeneratorX86::RecordSimplePatch() { void CodeGeneratorX86::RecordBootStringPatch(HLoadString* load_string) { DCHECK(GetCompilerOptions().IsBootImage()); - string_patches_.emplace_back(load_string->GetDexFile(), load_string->GetStringIndex().index_); + HX86ComputeBaseMethodAddress* address = nullptr; + if (GetCompilerOptions().GetCompilePic()) { + address = load_string->InputAt(0)->AsX86ComputeBaseMethodAddress(); + } else { + DCHECK_EQ(load_string->InputCount(), 0u); + } + string_patches_.emplace_back(address, + load_string->GetDexFile(), + load_string->GetStringIndex().index_); __ Bind(&string_patches_.back().label); } void CodeGeneratorX86::RecordBootTypePatch(HLoadClass* load_class) { - boot_image_type_patches_.emplace_back(load_class->GetDexFile(), + HX86ComputeBaseMethodAddress* address = nullptr; + if (GetCompilerOptions().GetCompilePic()) { + address = load_class->InputAt(0)->AsX86ComputeBaseMethodAddress(); + } else { + DCHECK_EQ(load_class->InputCount(), 0u); + } + boot_image_type_patches_.emplace_back(address, + load_class->GetDexFile(), load_class->GetTypeIndex().index_); __ Bind(&boot_image_type_patches_.back().label); } Label* CodeGeneratorX86::NewTypeBssEntryPatch(HLoadClass* load_class) { - type_bss_entry_patches_.emplace_back(load_class->GetDexFile(), load_class->GetTypeIndex().index_); + HX86ComputeBaseMethodAddress* address = + load_class->InputAt(0)->AsX86ComputeBaseMethodAddress(); + type_bss_entry_patches_.emplace_back( + address, load_class->GetDexFile(), load_class->GetTypeIndex().index_); return &type_bss_entry_patches_.back().label; } Label* CodeGeneratorX86::NewStringBssEntryPatch(HLoadString* load_string) { DCHECK(!GetCompilerOptions().IsBootImage()); - string_patches_.emplace_back(load_string->GetDexFile(), load_string->GetStringIndex().index_); + HX86ComputeBaseMethodAddress* address = + load_string->InputAt(0)->AsX86ComputeBaseMethodAddress(); + string_patches_.emplace_back( + address, load_string->GetDexFile(), load_string->GetStringIndex().index_); return &string_patches_.back().label; } -Label* CodeGeneratorX86::NewPcRelativeDexCacheArrayPatch(const DexFile& dex_file, - uint32_t element_offset) { +Label* CodeGeneratorX86::NewPcRelativeDexCacheArrayPatch( + HX86ComputeBaseMethodAddress* method_address, + const DexFile& dex_file, + uint32_t element_offset) { // Add the patch entry and bind its label at the end of the instruction. - pc_relative_dex_cache_patches_.emplace_back(dex_file, element_offset); + pc_relative_dex_cache_patches_.emplace_back(method_address, dex_file, element_offset); return &pc_relative_dex_cache_patches_.back().label; } @@ -4637,12 +4667,12 @@ constexpr uint32_t kLabelPositionToLiteralOffsetAdjustment = 4u; template <LinkerPatch (*Factory)(size_t, const DexFile*, uint32_t, uint32_t)> inline void CodeGeneratorX86::EmitPcRelativeLinkerPatches( - const ArenaDeque<PatchInfo<Label>>& infos, + const ArenaDeque<X86PcRelativePatchInfo>& infos, ArenaVector<LinkerPatch>* linker_patches) { - for (const PatchInfo<Label>& info : infos) { + for (const X86PcRelativePatchInfo& info : infos) { uint32_t literal_offset = info.label.Position() - kLabelPositionToLiteralOffsetAdjustment; - linker_patches->push_back( - Factory(literal_offset, &info.dex_file, GetMethodAddressOffset(), info.index)); + linker_patches->push_back(Factory( + literal_offset, &info.dex_file, GetMethodAddressOffset(info.method_address), info.index)); } } @@ -6002,13 +6032,6 @@ HLoadClass::LoadKind CodeGeneratorX86::GetSupportedLoadClassKind( FALLTHROUGH_INTENDED; case HLoadClass::LoadKind::kBssEntry: DCHECK(!Runtime::Current()->UseJitCompilation()); // Note: boot image is also non-JIT. - // We disable pc-relative load when there is an irreducible loop, as the optimization - // is incompatible with it. - // TODO: Create as many X86ComputeBaseMethodAddress instructions as needed for methods - // with irreducible loops. - if (GetGraph()->HasIrreducibleLoops()) { - return HLoadClass::LoadKind::kDexCacheViaMethod; - } break; case HLoadClass::LoadKind::kBootImageAddress: break; @@ -6195,13 +6218,6 @@ HLoadString::LoadKind CodeGeneratorX86::GetSupportedLoadStringKind( FALLTHROUGH_INTENDED; case HLoadString::LoadKind::kBssEntry: DCHECK(!Runtime::Current()->UseJitCompilation()); // Note: boot image is also non-JIT. - // We disable pc-relative load when there is an irreducible loop, as the optimization - // is incompatible with it. - // TODO: Create as many X86ComputeBaseMethodAddress instructions as needed for methods - // with irreducible loops. - if (GetGraph()->HasIrreducibleLoops()) { - return HLoadString::LoadKind::kDexCacheViaMethod; - } break; case HLoadString::LoadKind::kBootImageAddress: break; @@ -7489,7 +7505,7 @@ void InstructionCodeGeneratorX86::VisitX86ComputeBaseMethodAddress( __ Bind(&next_instruction); // Remember this offset for later use with constant area. - codegen_->SetMethodAddressOffset(GetAssembler()->CodeSize()); + codegen_->AddMethodAddressOffset(insn, GetAssembler()->CodeSize()); // Grab the return address off the stack. __ popl(reg); @@ -7536,17 +7552,20 @@ void InstructionCodeGeneratorX86::VisitX86LoadFromConstantTable(HX86LoadFromCons switch (insn->GetType()) { case Primitive::kPrimFloat: __ movss(out.AsFpuRegister<XmmRegister>(), - codegen_->LiteralFloatAddress(value->AsFloatConstant()->GetValue(), const_area)); + codegen_->LiteralFloatAddress( + value->AsFloatConstant()->GetValue(), insn->GetBaseMethodAddress(), const_area)); break; case Primitive::kPrimDouble: __ movsd(out.AsFpuRegister<XmmRegister>(), - codegen_->LiteralDoubleAddress(value->AsDoubleConstant()->GetValue(), const_area)); + codegen_->LiteralDoubleAddress( + value->AsDoubleConstant()->GetValue(), insn->GetBaseMethodAddress(), const_area)); break; case Primitive::kPrimInt: __ movl(out.AsRegister<Register>(), - codegen_->LiteralInt32Address(value->AsIntConstant()->GetValue(), const_area)); + codegen_->LiteralInt32Address( + value->AsIntConstant()->GetValue(), insn->GetBaseMethodAddress(), const_area)); break; default: @@ -7559,13 +7578,18 @@ void InstructionCodeGeneratorX86::VisitX86LoadFromConstantTable(HX86LoadFromCons */ class RIPFixup : public AssemblerFixup, public ArenaObject<kArenaAllocCodeGenerator> { public: - RIPFixup(CodeGeneratorX86& codegen, size_t offset) - : codegen_(&codegen), offset_into_constant_area_(offset) {} + RIPFixup(CodeGeneratorX86& codegen, + HX86ComputeBaseMethodAddress* base_method_address, + size_t offset) + : codegen_(&codegen), + base_method_address_(base_method_address), + offset_into_constant_area_(offset) {} protected: void SetOffset(size_t offset) { offset_into_constant_area_ = offset; } CodeGeneratorX86* codegen_; + HX86ComputeBaseMethodAddress* base_method_address_; private: void Process(const MemoryRegion& region, int pos) OVERRIDE { @@ -7574,7 +7598,8 @@ class RIPFixup : public AssemblerFixup, public ArenaObject<kArenaAllocCodeGenera // The value to patch is the distance from the offset in the constant area // from the address computed by the HX86ComputeBaseMethodAddress instruction. int32_t constant_offset = codegen_->ConstantAreaStart() + offset_into_constant_area_; - int32_t relative_position = constant_offset - codegen_->GetMethodAddressOffset(); + int32_t relative_position = + constant_offset - codegen_->GetMethodAddressOffset(base_method_address_); // Patch in the right value. region.StoreUnaligned<int32_t>(pos - 4, relative_position); @@ -7591,7 +7616,8 @@ class RIPFixup : public AssemblerFixup, public ArenaObject<kArenaAllocCodeGenera class JumpTableRIPFixup : public RIPFixup { public: JumpTableRIPFixup(CodeGeneratorX86& codegen, HX86PackedSwitch* switch_instr) - : RIPFixup(codegen, static_cast<size_t>(-1)), switch_instr_(switch_instr) {} + : RIPFixup(codegen, switch_instr->GetBaseMethodAddress(), static_cast<size_t>(-1)), + switch_instr_(switch_instr) {} void CreateJumpTable() { X86Assembler* assembler = codegen_->GetAssembler(); @@ -7602,7 +7628,7 @@ class JumpTableRIPFixup : public RIPFixup { // The label values in the jump table are computed relative to the // instruction addressing the constant area. - const int32_t relative_offset = codegen_->GetMethodAddressOffset(); + const int32_t relative_offset = codegen_->GetMethodAddressOffset(base_method_address_); // Populate the jump table with the correct values for the jump table. int32_t num_entries = switch_instr_->GetNumEntries(); @@ -7644,23 +7670,32 @@ void CodeGeneratorX86::Finalize(CodeAllocator* allocator) { CodeGenerator::Finalize(allocator); } -Address CodeGeneratorX86::LiteralDoubleAddress(double v, Register reg) { - AssemblerFixup* fixup = new (GetGraph()->GetArena()) RIPFixup(*this, __ AddDouble(v)); +Address CodeGeneratorX86::LiteralDoubleAddress(double v, + HX86ComputeBaseMethodAddress* method_base, + Register reg) { + AssemblerFixup* fixup = + new (GetGraph()->GetArena()) RIPFixup(*this, method_base, __ AddDouble(v)); return Address(reg, kDummy32BitOffset, fixup); } -Address CodeGeneratorX86::LiteralFloatAddress(float v, Register reg) { - AssemblerFixup* fixup = new (GetGraph()->GetArena()) RIPFixup(*this, __ AddFloat(v)); +Address CodeGeneratorX86::LiteralFloatAddress(float v, + HX86ComputeBaseMethodAddress* method_base, + Register reg) { + AssemblerFixup* fixup = new (GetGraph()->GetArena()) RIPFixup(*this, method_base, __ AddFloat(v)); return Address(reg, kDummy32BitOffset, fixup); } -Address CodeGeneratorX86::LiteralInt32Address(int32_t v, Register reg) { - AssemblerFixup* fixup = new (GetGraph()->GetArena()) RIPFixup(*this, __ AddInt32(v)); +Address CodeGeneratorX86::LiteralInt32Address(int32_t v, + HX86ComputeBaseMethodAddress* method_base, + Register reg) { + AssemblerFixup* fixup = new (GetGraph()->GetArena()) RIPFixup(*this, method_base, __ AddInt32(v)); return Address(reg, kDummy32BitOffset, fixup); } -Address CodeGeneratorX86::LiteralInt64Address(int64_t v, Register reg) { - AssemblerFixup* fixup = new (GetGraph()->GetArena()) RIPFixup(*this, __ AddInt64(v)); +Address CodeGeneratorX86::LiteralInt64Address(int64_t v, + HX86ComputeBaseMethodAddress* method_base, + Register reg) { + AssemblerFixup* fixup = new (GetGraph()->GetArena()) RIPFixup(*this, method_base, __ AddInt64(v)); return Address(reg, kDummy32BitOffset, fixup); } diff --git a/compiler/optimizing/code_generator_x86.h b/compiler/optimizing/code_generator_x86.h index 285a3fecc3..5360dc9209 100644 --- a/compiler/optimizing/code_generator_x86.h +++ b/compiler/optimizing/code_generator_x86.h @@ -417,7 +417,9 @@ class CodeGeneratorX86 : public CodeGenerator { void RecordBootTypePatch(HLoadClass* load_class); Label* NewTypeBssEntryPatch(HLoadClass* load_class); Label* NewStringBssEntryPatch(HLoadString* load_string); - Label* NewPcRelativeDexCacheArrayPatch(const DexFile& dex_file, uint32_t element_offset); + Label* NewPcRelativeDexCacheArrayPatch(HX86ComputeBaseMethodAddress* method_address, + const DexFile& dex_file, + uint32_t element_offset); Label* NewJitRootStringPatch(const DexFile& dex_file, dex::StringIndex dex_index, Handle<mirror::String> handle); @@ -465,22 +467,22 @@ class CodeGeneratorX86 : public CodeGenerator { return isa_features_; } - void SetMethodAddressOffset(int32_t offset) { - method_address_offset_ = offset; + void AddMethodAddressOffset(HX86ComputeBaseMethodAddress* method_base, int32_t offset) { + method_address_offset_.Put(method_base->GetId(), offset); } - int32_t GetMethodAddressOffset() const { - return method_address_offset_; + int32_t GetMethodAddressOffset(HX86ComputeBaseMethodAddress* method_base) const { + return method_address_offset_.Get(method_base->GetId()); } int32_t ConstantAreaStart() const { return constant_area_start_; } - Address LiteralDoubleAddress(double v, Register reg); - Address LiteralFloatAddress(float v, Register reg); - Address LiteralInt32Address(int32_t v, Register reg); - Address LiteralInt64Address(int64_t v, Register reg); + Address LiteralDoubleAddress(double v, HX86ComputeBaseMethodAddress* method_base, Register reg); + Address LiteralFloatAddress(float v, HX86ComputeBaseMethodAddress* method_base, Register reg); + Address LiteralInt32Address(int32_t v, HX86ComputeBaseMethodAddress* method_base, Register reg); + Address LiteralInt64Address(int64_t v, HX86ComputeBaseMethodAddress* method_base, Register reg); // Load a 32-bit value into a register in the most efficient manner. void Load32BitValue(Register dest, int32_t value); @@ -605,12 +607,21 @@ class CodeGeneratorX86 : public CodeGenerator { static constexpr int32_t kDummy32BitOffset = 256; private: - Register GetInvokeStaticOrDirectExtraParameter(HInvokeStaticOrDirect* invoke, Register temp); + struct X86PcRelativePatchInfo : PatchInfo<Label> { + X86PcRelativePatchInfo(HX86ComputeBaseMethodAddress* address, + const DexFile& target_dex_file, + uint32_t target_index) + : PatchInfo(target_dex_file, target_index), + method_address(address) {} + HX86ComputeBaseMethodAddress* method_address; + }; template <LinkerPatch (*Factory)(size_t, const DexFile*, uint32_t, uint32_t)> - void EmitPcRelativeLinkerPatches(const ArenaDeque<PatchInfo<Label>>& infos, + void EmitPcRelativeLinkerPatches(const ArenaDeque<X86PcRelativePatchInfo>& infos, ArenaVector<LinkerPatch>* linker_patches); + Register GetInvokeStaticOrDirectExtraParameter(HInvokeStaticOrDirect* invoke, Register temp); + // Labels for each block that will be compiled. Label* block_labels_; // Indexed by block id. Label frame_entry_label_; @@ -621,15 +632,15 @@ class CodeGeneratorX86 : public CodeGenerator { const X86InstructionSetFeatures& isa_features_; // PC-relative DexCache access info. - ArenaDeque<PatchInfo<Label>> pc_relative_dex_cache_patches_; + ArenaDeque<X86PcRelativePatchInfo> pc_relative_dex_cache_patches_; // Patch locations for patchoat where the linker doesn't do any other work. ArenaDeque<Label> simple_patches_; // String patch locations; type depends on configuration (app .bss or boot image PIC/non-PIC). - ArenaDeque<PatchInfo<Label>> string_patches_; + ArenaDeque<X86PcRelativePatchInfo> string_patches_; // Type patch locations for boot image; type depends on configuration (boot image PIC/non-PIC). - ArenaDeque<PatchInfo<Label>> boot_image_type_patches_; + ArenaDeque<X86PcRelativePatchInfo> boot_image_type_patches_; // Type patch locations for kBssEntry. - ArenaDeque<PatchInfo<Label>> type_bss_entry_patches_; + ArenaDeque<X86PcRelativePatchInfo> type_bss_entry_patches_; // Patches for string root accesses in JIT compiled code. ArenaDeque<PatchInfo<Label>> jit_string_patches_; @@ -644,11 +655,9 @@ class CodeGeneratorX86 : public CodeGenerator { // Fixups for jump tables that need to be patched after the constant table is generated. ArenaVector<JumpTableRIPFixup*> fixups_to_jump_tables_; - // If there is a HX86ComputeBaseMethodAddress instruction in the graph - // (which shall be the sole instruction of this kind), subtracting this offset - // from the value contained in the out register of this HX86ComputeBaseMethodAddress - // instruction gives the address of the start of this method. - int32_t method_address_offset_; + // Maps a HX86ComputeBaseMethodAddress instruction id, to its offset in the + // compiled code. + ArenaSafeMap<uint32_t, int32_t> method_address_offset_; DISALLOW_COPY_AND_ASSIGN(CodeGeneratorX86); }; diff --git a/compiler/optimizing/code_generator_x86_64.cc b/compiler/optimizing/code_generator_x86_64.cc index 74c71cce1f..c4caf4bf9d 100644 --- a/compiler/optimizing/code_generator_x86_64.cc +++ b/compiler/optimizing/code_generator_x86_64.cc @@ -1809,7 +1809,7 @@ void InstructionCodeGeneratorX86_64::VisitSelect(HSelect* select) { cond = X86_64IntegerCondition(condition->GetCondition()); } } else { - // Must be a boolean condition, which needs to be compared to 0. + // Must be a Boolean condition, which needs to be compared to 0. CpuRegister cond_reg = locations->InAt(2).AsRegister<CpuRegister>(); __ testl(cond_reg, cond_reg); } @@ -4210,7 +4210,7 @@ void InstructionCodeGeneratorX86_64::VisitPhi(HPhi* instruction ATTRIBUTE_UNUSED void CodeGeneratorX86_64::GenerateMemoryBarrier(MemBarrierKind kind) { /* - * According to the JSR-133 Cookbook, for x86 only StoreLoad/AnyAny barriers need memory fence. + * According to the JSR-133 Cookbook, for x86-64 only StoreLoad/AnyAny barriers need memory fence. * All other barriers (LoadAny, AnyStore, StoreStore) are nops due to the x86-64 memory model. * For those cases, all we need to ensure is that there is a scheduling barrier in place. */ diff --git a/compiler/optimizing/dex_cache_array_fixups_arm.cc b/compiler/optimizing/dex_cache_array_fixups_arm.cc index 9ddcd563ca..cfcb276a98 100644 --- a/compiler/optimizing/dex_cache_array_fixups_arm.cc +++ b/compiler/optimizing/dex_cache_array_fixups_arm.cc @@ -65,7 +65,7 @@ class DexCacheArrayFixupsVisitor : public HGraphVisitor { if (invoke->HasPcRelativeDexCache() && !IsCallFreeIntrinsic<IntrinsicLocationsBuilderARMType>(invoke, codegen_)) { HArmDexCacheArraysBase* base = - GetOrCreateDexCacheArrayBase(invoke->GetDexFileForPcRelativeDexCache()); + GetOrCreateDexCacheArrayBase(invoke, invoke->GetDexFileForPcRelativeDexCache()); // Update the element offset in base. DexCacheArraysLayout layout(kArmPointerSize, &invoke->GetDexFileForPcRelativeDexCache()); base->UpdateElementOffset(layout.MethodOffset(invoke->GetDexMethodIndex())); @@ -75,21 +75,28 @@ class DexCacheArrayFixupsVisitor : public HGraphVisitor { } } - HArmDexCacheArraysBase* GetOrCreateDexCacheArrayBase(const DexFile& dex_file) { - // Ensure we only initialize the pointer once for each dex file. - auto lb = dex_cache_array_bases_.lower_bound(&dex_file); - if (lb != dex_cache_array_bases_.end() && - !dex_cache_array_bases_.key_comp()(&dex_file, lb->first)) { - return lb->second; - } + HArmDexCacheArraysBase* GetOrCreateDexCacheArrayBase(HInstruction* cursor, + const DexFile& dex_file) { + if (GetGraph()->HasIrreducibleLoops()) { + HArmDexCacheArraysBase* base = new (GetGraph()->GetArena()) HArmDexCacheArraysBase(dex_file); + cursor->GetBlock()->InsertInstructionBefore(base, cursor); + return base; + } else { + // Ensure we only initialize the pointer once for each dex file. + auto lb = dex_cache_array_bases_.lower_bound(&dex_file); + if (lb != dex_cache_array_bases_.end() && + !dex_cache_array_bases_.key_comp()(&dex_file, lb->first)) { + return lb->second; + } - // Insert the base at the start of the entry block, move it to a better - // position later in MoveBaseIfNeeded(). - HArmDexCacheArraysBase* base = new (GetGraph()->GetArena()) HArmDexCacheArraysBase(dex_file); - HBasicBlock* entry_block = GetGraph()->GetEntryBlock(); - entry_block->InsertInstructionBefore(base, entry_block->GetFirstInstruction()); - dex_cache_array_bases_.PutBefore(lb, &dex_file, base); - return base; + // Insert the base at the start of the entry block, move it to a better + // position later in MoveBaseIfNeeded(). + HArmDexCacheArraysBase* base = new (GetGraph()->GetArena()) HArmDexCacheArraysBase(dex_file); + HBasicBlock* entry_block = GetGraph()->GetEntryBlock(); + entry_block->InsertInstructionBefore(base, entry_block->GetFirstInstruction()); + dex_cache_array_bases_.PutBefore(lb, &dex_file, base); + return base; + } } CodeGeneratorARMType* codegen_; @@ -100,11 +107,6 @@ class DexCacheArrayFixupsVisitor : public HGraphVisitor { }; void DexCacheArrayFixups::Run() { - if (graph_->HasIrreducibleLoops()) { - // Do not run this optimization, as irreducible loops do not work with an instruction - // that can be live-in at the irreducible loop header. - return; - } DexCacheArrayFixupsVisitor visitor(graph_, codegen_); visitor.VisitInsertionOrder(); visitor.MoveBasesIfNeeded(); diff --git a/compiler/optimizing/gvn.cc b/compiler/optimizing/gvn.cc index f5931a2f81..c93bc210be 100644 --- a/compiler/optimizing/gvn.cc +++ b/compiler/optimizing/gvn.cc @@ -399,7 +399,7 @@ class GlobalValueNumberer : public ValueObject { ArenaVector<ValueSet*> sets_; // BitVector which serves as a fast-access map from block id to - // visited/unvisited boolean. + // visited/unvisited Boolean. ArenaBitVector visited_blocks_; DISALLOW_COPY_AND_ASSIGN(GlobalValueNumberer); diff --git a/compiler/optimizing/instruction_builder.cc b/compiler/optimizing/instruction_builder.cc index ef8d74dce0..cac385ce3c 100644 --- a/compiler/optimizing/instruction_builder.cc +++ b/compiler/optimizing/instruction_builder.cc @@ -1,4 +1,3 @@ - /* * Copyright (C) 2016 The Android Open Source Project * diff --git a/compiler/optimizing/intrinsics_arm.cc b/compiler/optimizing/intrinsics_arm.cc index 8f64faeac0..c262cf983d 100644 --- a/compiler/optimizing/intrinsics_arm.cc +++ b/compiler/optimizing/intrinsics_arm.cc @@ -2592,6 +2592,58 @@ void IntrinsicCodeGeneratorARM::VisitDoubleIsInfinite(HInvoke* invoke) { __ Lsr(out, out, 5); } +void IntrinsicLocationsBuilderARM::VisitReferenceGetReferent(HInvoke* invoke) { + if (kEmitCompilerReadBarrier) { + // Do not intrinsify this call with the read barrier configuration. + return; + } + LocationSummary* locations = new (arena_) LocationSummary(invoke, + LocationSummary::kCallOnSlowPath, + kIntrinsified); + locations->SetInAt(0, Location::RequiresRegister()); + locations->SetOut(Location::SameAsFirstInput()); + locations->AddTemp(Location::RequiresRegister()); +} + +void IntrinsicCodeGeneratorARM::VisitReferenceGetReferent(HInvoke* invoke) { + DCHECK(!kEmitCompilerReadBarrier); + ArmAssembler* const assembler = GetAssembler(); + LocationSummary* locations = invoke->GetLocations(); + + Register obj = locations->InAt(0).AsRegister<Register>(); + Register out = locations->Out().AsRegister<Register>(); + + SlowPathCode* slow_path = new (GetAllocator()) IntrinsicSlowPathARM(invoke); + codegen_->AddSlowPath(slow_path); + + // Load ArtMethod first. + HInvokeStaticOrDirect* invoke_direct = invoke->AsInvokeStaticOrDirect(); + DCHECK(invoke_direct != nullptr); + Register temp = codegen_->GenerateCalleeMethodStaticOrDirectCall( + invoke_direct, locations->GetTemp(0)).AsRegister<Register>(); + + // Now get declaring class. + __ ldr(temp, Address(temp, ArtMethod::DeclaringClassOffset().Int32Value())); + + uint32_t slow_path_flag_offset = codegen_->GetReferenceSlowFlagOffset(); + uint32_t disable_flag_offset = codegen_->GetReferenceDisableFlagOffset(); + DCHECK_NE(slow_path_flag_offset, 0u); + DCHECK_NE(disable_flag_offset, 0u); + DCHECK_NE(slow_path_flag_offset, disable_flag_offset); + + // Check static flags that prevent using intrinsic. + __ ldr(IP, Address(temp, disable_flag_offset)); + __ ldr(temp, Address(temp, slow_path_flag_offset)); + __ orr(IP, IP, ShifterOperand(temp)); + __ CompareAndBranchIfNonZero(IP, slow_path->GetEntryLabel()); + + // Fast path. + __ ldr(out, Address(obj, mirror::Reference::ReferentOffset().Int32Value())); + codegen_->MaybeRecordImplicitNullCheck(invoke); + __ MaybeUnpoisonHeapReference(out); + __ Bind(slow_path->GetExitLabel()); +} + UNIMPLEMENTED_INTRINSIC(ARM, MathMinDoubleDouble) UNIMPLEMENTED_INTRINSIC(ARM, MathMinFloatFloat) UNIMPLEMENTED_INTRINSIC(ARM, MathMaxDoubleDouble) @@ -2605,7 +2657,6 @@ UNIMPLEMENTED_INTRINSIC(ARM, MathRoundDouble) // Could be done by changing rou UNIMPLEMENTED_INTRINSIC(ARM, MathRoundFloat) // Could be done by changing rounding mode, maybe? UNIMPLEMENTED_INTRINSIC(ARM, UnsafeCASLong) // High register pressure. UNIMPLEMENTED_INTRINSIC(ARM, SystemArrayCopyChar) -UNIMPLEMENTED_INTRINSIC(ARM, ReferenceGetReferent) UNIMPLEMENTED_INTRINSIC(ARM, IntegerHighestOneBit) UNIMPLEMENTED_INTRINSIC(ARM, LongHighestOneBit) UNIMPLEMENTED_INTRINSIC(ARM, IntegerLowestOneBit) diff --git a/compiler/optimizing/intrinsics_arm64.cc b/compiler/optimizing/intrinsics_arm64.cc index d8a896e926..bbf826ce7e 100644 --- a/compiler/optimizing/intrinsics_arm64.cc +++ b/compiler/optimizing/intrinsics_arm64.cc @@ -2773,7 +2773,65 @@ void IntrinsicCodeGeneratorARM64::VisitDoubleIsInfinite(HInvoke* invoke) { GenIsInfinite(invoke->GetLocations(), /* is64bit */ true, GetVIXLAssembler()); } -UNIMPLEMENTED_INTRINSIC(ARM64, ReferenceGetReferent) +void IntrinsicLocationsBuilderARM64::VisitReferenceGetReferent(HInvoke* invoke) { + if (kEmitCompilerReadBarrier) { + // Do not intrinsify this call with the read barrier configuration. + return; + } + LocationSummary* locations = new (arena_) LocationSummary(invoke, + LocationSummary::kCallOnSlowPath, + kIntrinsified); + locations->SetInAt(0, Location::RequiresRegister()); + locations->SetOut(Location::SameAsFirstInput()); + locations->AddTemp(Location::RequiresRegister()); +} + +void IntrinsicCodeGeneratorARM64::VisitReferenceGetReferent(HInvoke* invoke) { + DCHECK(!kEmitCompilerReadBarrier); + MacroAssembler* masm = GetVIXLAssembler(); + LocationSummary* locations = invoke->GetLocations(); + + Register obj = InputRegisterAt(invoke, 0); + Register out = OutputRegister(invoke); + + SlowPathCodeARM64* slow_path = new (GetAllocator()) IntrinsicSlowPathARM64(invoke); + codegen_->AddSlowPath(slow_path); + + // Load ArtMethod first. + HInvokeStaticOrDirect* invoke_direct = invoke->AsInvokeStaticOrDirect(); + DCHECK(invoke_direct != nullptr); + Register temp0 = XRegisterFrom(codegen_->GenerateCalleeMethodStaticOrDirectCall( + invoke_direct, locations->GetTemp(0))); + + // Now get declaring class. + __ Ldr(temp0.W(), MemOperand(temp0, ArtMethod::DeclaringClassOffset().Int32Value())); + + uint32_t slow_path_flag_offset = codegen_->GetReferenceSlowFlagOffset(); + uint32_t disable_flag_offset = codegen_->GetReferenceDisableFlagOffset(); + DCHECK_NE(slow_path_flag_offset, 0u); + DCHECK_NE(disable_flag_offset, 0u); + DCHECK_NE(slow_path_flag_offset, disable_flag_offset); + + // Check static flags that prevent using intrinsic. + if (slow_path_flag_offset == disable_flag_offset + 1) { + // Load two adjacent flags in one 64-bit load. + __ Ldr(temp0, MemOperand(temp0, disable_flag_offset)); + } else { + UseScratchRegisterScope temps(masm); + Register temp1 = temps.AcquireW(); + __ Ldr(temp1.W(), MemOperand(temp0, disable_flag_offset)); + __ Ldr(temp0.W(), MemOperand(temp0, slow_path_flag_offset)); + __ Orr(temp0, temp1, temp0); + } + __ Cbnz(temp0, slow_path->GetEntryLabel()); + + // Fast path. + __ Ldr(out, HeapOperand(obj, mirror::Reference::ReferentOffset().Int32Value())); + codegen_->MaybeRecordImplicitNullCheck(invoke); + codegen_->GetAssembler()->MaybeUnpoisonHeapReference(out); + __ Bind(slow_path->GetExitLabel()); +} + UNIMPLEMENTED_INTRINSIC(ARM64, IntegerHighestOneBit) UNIMPLEMENTED_INTRINSIC(ARM64, LongHighestOneBit) UNIMPLEMENTED_INTRINSIC(ARM64, IntegerLowestOneBit) diff --git a/compiler/optimizing/intrinsics_arm_vixl.cc b/compiler/optimizing/intrinsics_arm_vixl.cc index 85e84d8d2c..68c2d2e36e 100644 --- a/compiler/optimizing/intrinsics_arm_vixl.cc +++ b/compiler/optimizing/intrinsics_arm_vixl.cc @@ -2688,6 +2688,60 @@ void IntrinsicCodeGeneratorARMVIXL::VisitDoubleIsInfinite(HInvoke* invoke) { __ Lsr(out, out, 5); } +void IntrinsicLocationsBuilderARMVIXL::VisitReferenceGetReferent(HInvoke* invoke) { + if (kEmitCompilerReadBarrier) { + // Do not intrinsify this call with the read barrier configuration. + return; + } + LocationSummary* locations = new (arena_) LocationSummary(invoke, + LocationSummary::kCallOnSlowPath, + kIntrinsified); + locations->SetInAt(0, Location::RequiresRegister()); + locations->SetOut(Location::SameAsFirstInput()); + locations->AddTemp(Location::RequiresRegister()); +} + +void IntrinsicCodeGeneratorARMVIXL::VisitReferenceGetReferent(HInvoke* invoke) { + DCHECK(!kEmitCompilerReadBarrier); + ArmVIXLAssembler* assembler = GetAssembler(); + LocationSummary* locations = invoke->GetLocations(); + + vixl32::Register obj = InputRegisterAt(invoke, 0); + vixl32::Register out = OutputRegister(invoke); + + SlowPathCodeARMVIXL* slow_path = new (GetAllocator()) IntrinsicSlowPathARMVIXL(invoke); + codegen_->AddSlowPath(slow_path); + + // Load ArtMethod first. + HInvokeStaticOrDirect* invoke_direct = invoke->AsInvokeStaticOrDirect(); + DCHECK(invoke_direct != nullptr); + vixl32::Register temp0 = RegisterFrom(codegen_->GenerateCalleeMethodStaticOrDirectCall( + invoke_direct, locations->GetTemp(0))); + + // Now get declaring class. + __ Ldr(temp0, MemOperand(temp0, ArtMethod::DeclaringClassOffset().Int32Value())); + + uint32_t slow_path_flag_offset = codegen_->GetReferenceSlowFlagOffset(); + uint32_t disable_flag_offset = codegen_->GetReferenceDisableFlagOffset(); + DCHECK_NE(slow_path_flag_offset, 0u); + DCHECK_NE(disable_flag_offset, 0u); + DCHECK_NE(slow_path_flag_offset, disable_flag_offset); + + // Check static flags that prevent using intrinsic. + UseScratchRegisterScope temps(assembler->GetVIXLAssembler()); + vixl32::Register temp1 = temps.Acquire(); + __ Ldr(temp1, MemOperand(temp0, disable_flag_offset)); + __ Ldr(temp0, MemOperand(temp0, slow_path_flag_offset)); + __ Orr(temp0, temp1, temp0); + __ CompareAndBranchIfNonZero(temp0, slow_path->GetEntryLabel()); + + // Fast path. + __ Ldr(out, MemOperand(obj, mirror::Reference::ReferentOffset().Int32Value())); + codegen_->MaybeRecordImplicitNullCheck(invoke); + assembler->MaybeUnpoisonHeapReference(out); + __ Bind(slow_path->GetExitLabel()); +} + UNIMPLEMENTED_INTRINSIC(ARMVIXL, MathMinDoubleDouble) UNIMPLEMENTED_INTRINSIC(ARMVIXL, MathMinFloatFloat) UNIMPLEMENTED_INTRINSIC(ARMVIXL, MathMaxDoubleDouble) @@ -2701,7 +2755,6 @@ UNIMPLEMENTED_INTRINSIC(ARMVIXL, MathRoundDouble) // Could be done by changing UNIMPLEMENTED_INTRINSIC(ARMVIXL, MathRoundFloat) // Could be done by changing rounding mode, maybe? UNIMPLEMENTED_INTRINSIC(ARMVIXL, UnsafeCASLong) // High register pressure. UNIMPLEMENTED_INTRINSIC(ARMVIXL, SystemArrayCopyChar) -UNIMPLEMENTED_INTRINSIC(ARMVIXL, ReferenceGetReferent) UNIMPLEMENTED_INTRINSIC(ARMVIXL, IntegerHighestOneBit) UNIMPLEMENTED_INTRINSIC(ARMVIXL, LongHighestOneBit) UNIMPLEMENTED_INTRINSIC(ARMVIXL, IntegerLowestOneBit) diff --git a/compiler/optimizing/intrinsics_mips.cc b/compiler/optimizing/intrinsics_mips.cc index f1ae549928..6cf9b83d44 100644 --- a/compiler/optimizing/intrinsics_mips.cc +++ b/compiler/optimizing/intrinsics_mips.cc @@ -1878,7 +1878,7 @@ static void GenCas(LocationSummary* locations, Primitive::Type type, CodeGenerat // If we use 'value' directly, we would lose 'value' // in the case that the store fails. Whether the // store succeeds, or fails, it will load the - // correct boolean value into the 'out' register. + // correct Boolean value into the 'out' register. // This test isn't really necessary. We only support Primitive::kPrimInt, // Primitive::kPrimNot, and we already verified that we're working on one // of those two types. It's left here in case the code needs to support diff --git a/compiler/optimizing/intrinsics_mips64.cc b/compiler/optimizing/intrinsics_mips64.cc index 3022e975e8..00a1fa11bb 100644 --- a/compiler/optimizing/intrinsics_mips64.cc +++ b/compiler/optimizing/intrinsics_mips64.cc @@ -1477,7 +1477,7 @@ static void GenCas(LocationSummary* locations, Primitive::Type type, CodeGenerat // If we use 'value' directly, we would lose 'value' // in the case that the store fails. Whether the // store succeeds, or fails, it will load the - // correct boolean value into the 'out' register. + // correct Boolean value into the 'out' register. if (type == Primitive::kPrimLong) { __ Scd(out, TMP); } else { diff --git a/compiler/optimizing/intrinsics_x86.cc b/compiler/optimizing/intrinsics_x86.cc index 922c3bcac9..e1b7ea53b4 100644 --- a/compiler/optimizing/intrinsics_x86.cc +++ b/compiler/optimizing/intrinsics_x86.cc @@ -356,23 +356,28 @@ static void CreateFloatToFloat(ArenaAllocator* arena, HInvoke* invoke) { } } -static void MathAbsFP(LocationSummary* locations, +static void MathAbsFP(HInvoke* invoke, bool is64bit, X86Assembler* assembler, CodeGeneratorX86* codegen) { + LocationSummary* locations = invoke->GetLocations(); Location output = locations->Out(); DCHECK(output.IsFpuRegister()); if (locations->GetInputCount() == 2 && locations->InAt(1).IsValid()) { + HX86ComputeBaseMethodAddress* method_address = + invoke->InputAt(1)->AsX86ComputeBaseMethodAddress(); DCHECK(locations->InAt(1).IsRegister()); // We also have a constant area pointer. Register constant_area = locations->InAt(1).AsRegister<Register>(); XmmRegister temp = locations->GetTemp(0).AsFpuRegister<XmmRegister>(); if (is64bit) { - __ movsd(temp, codegen->LiteralInt64Address(INT64_C(0x7FFFFFFFFFFFFFFF), constant_area)); + __ movsd(temp, codegen->LiteralInt64Address( + INT64_C(0x7FFFFFFFFFFFFFFF), method_address, constant_area)); __ andpd(output.AsFpuRegister<XmmRegister>(), temp); } else { - __ movss(temp, codegen->LiteralInt32Address(INT32_C(0x7FFFFFFF), constant_area)); + __ movss(temp, codegen->LiteralInt32Address( + INT32_C(0x7FFFFFFF), method_address, constant_area)); __ andps(output.AsFpuRegister<XmmRegister>(), temp); } } else { @@ -396,7 +401,7 @@ void IntrinsicLocationsBuilderX86::VisitMathAbsDouble(HInvoke* invoke) { } void IntrinsicCodeGeneratorX86::VisitMathAbsDouble(HInvoke* invoke) { - MathAbsFP(invoke->GetLocations(), /* is64bit */ true, GetAssembler(), codegen_); + MathAbsFP(invoke, /* is64bit */ true, GetAssembler(), codegen_); } void IntrinsicLocationsBuilderX86::VisitMathAbsFloat(HInvoke* invoke) { @@ -404,7 +409,7 @@ void IntrinsicLocationsBuilderX86::VisitMathAbsFloat(HInvoke* invoke) { } void IntrinsicCodeGeneratorX86::VisitMathAbsFloat(HInvoke* invoke) { - MathAbsFP(invoke->GetLocations(), /* is64bit */ false, GetAssembler(), codegen_); + MathAbsFP(invoke, /* is64bit */ false, GetAssembler(), codegen_); } static void CreateAbsIntLocation(ArenaAllocator* arena, HInvoke* invoke) { @@ -486,11 +491,12 @@ void IntrinsicCodeGeneratorX86::VisitMathAbsLong(HInvoke* invoke) { GenAbsLong(invoke->GetLocations(), GetAssembler()); } -static void GenMinMaxFP(LocationSummary* locations, +static void GenMinMaxFP(HInvoke* invoke, bool is_min, bool is_double, X86Assembler* assembler, CodeGeneratorX86* codegen) { + LocationSummary* locations = invoke->GetLocations(); Location op1_loc = locations->InAt(0); Location op2_loc = locations->InAt(1); Location out_loc = locations->Out(); @@ -553,12 +559,14 @@ static void GenMinMaxFP(LocationSummary* locations, __ Bind(&nan); // Do we have a constant area pointer? if (locations->GetInputCount() == 3 && locations->InAt(2).IsValid()) { + HX86ComputeBaseMethodAddress* method_address = + invoke->InputAt(2)->AsX86ComputeBaseMethodAddress(); DCHECK(locations->InAt(2).IsRegister()); Register constant_area = locations->InAt(2).AsRegister<Register>(); if (is_double) { - __ movsd(out, codegen->LiteralInt64Address(kDoubleNaN, constant_area)); + __ movsd(out, codegen->LiteralInt64Address(kDoubleNaN, method_address, constant_area)); } else { - __ movss(out, codegen->LiteralInt32Address(kFloatNaN, constant_area)); + __ movss(out, codegen->LiteralInt32Address(kFloatNaN, method_address, constant_area)); } } else { if (is_double) { @@ -608,7 +616,7 @@ void IntrinsicLocationsBuilderX86::VisitMathMinDoubleDouble(HInvoke* invoke) { } void IntrinsicCodeGeneratorX86::VisitMathMinDoubleDouble(HInvoke* invoke) { - GenMinMaxFP(invoke->GetLocations(), + GenMinMaxFP(invoke, /* is_min */ true, /* is_double */ true, GetAssembler(), @@ -620,7 +628,7 @@ void IntrinsicLocationsBuilderX86::VisitMathMinFloatFloat(HInvoke* invoke) { } void IntrinsicCodeGeneratorX86::VisitMathMinFloatFloat(HInvoke* invoke) { - GenMinMaxFP(invoke->GetLocations(), + GenMinMaxFP(invoke, /* is_min */ true, /* is_double */ false, GetAssembler(), @@ -632,7 +640,7 @@ void IntrinsicLocationsBuilderX86::VisitMathMaxDoubleDouble(HInvoke* invoke) { } void IntrinsicCodeGeneratorX86::VisitMathMaxDoubleDouble(HInvoke* invoke) { - GenMinMaxFP(invoke->GetLocations(), + GenMinMaxFP(invoke, /* is_min */ false, /* is_double */ true, GetAssembler(), @@ -644,7 +652,7 @@ void IntrinsicLocationsBuilderX86::VisitMathMaxFloatFloat(HInvoke* invoke) { } void IntrinsicCodeGeneratorX86::VisitMathMaxFloatFloat(HInvoke* invoke) { - GenMinMaxFP(invoke->GetLocations(), + GenMinMaxFP(invoke, /* is_min */ false, /* is_double */ false, GetAssembler(), @@ -905,10 +913,16 @@ void IntrinsicCodeGeneratorX86::VisitMathRoundFloat(HInvoke* invoke) { __ subss(t2, t1); if (locations->GetInputCount() == 2 && locations->InAt(1).IsValid()) { // Direct constant area available. + HX86ComputeBaseMethodAddress* method_address = + invoke->InputAt(1)->AsX86ComputeBaseMethodAddress(); Register constant_area = locations->InAt(1).AsRegister<Register>(); - __ comiss(t2, codegen_->LiteralInt32Address(bit_cast<int32_t, float>(0.5f), constant_area)); + __ comiss(t2, codegen_->LiteralInt32Address(bit_cast<int32_t, float>(0.5f), + method_address, + constant_area)); __ j(kBelow, &skip_incr); - __ addss(t1, codegen_->LiteralInt32Address(bit_cast<int32_t, float>(1.0f), constant_area)); + __ addss(t1, codegen_->LiteralInt32Address(bit_cast<int32_t, float>(1.0f), + method_address, + constant_area)); __ Bind(&skip_incr); } else { // No constant area: go through stack. diff --git a/compiler/optimizing/nodes.h b/compiler/optimizing/nodes.h index a2980dca20..f0ea9e20e6 100644 --- a/compiler/optimizing/nodes.h +++ b/compiler/optimizing/nodes.h @@ -565,7 +565,7 @@ class HGraph : public ArenaObject<kArenaAllocGraph> { ArtMethod* GetArtMethod() const { return art_method_; } void SetArtMethod(ArtMethod* method) { art_method_ = method; } - // Returns an instruction with the opposite boolean value from 'cond'. + // Returns an instruction with the opposite Boolean value from 'cond'. // The instruction has been inserted into the graph, either as a constant, or // before cursor. HInstruction* InsertOppositeCondition(HInstruction* cond, HInstruction* cursor); diff --git a/compiler/optimizing/nodes_x86.h b/compiler/optimizing/nodes_x86.h index fa479760fe..75893c3129 100644 --- a/compiler/optimizing/nodes_x86.h +++ b/compiler/optimizing/nodes_x86.h @@ -71,6 +71,10 @@ class HX86FPNeg FINAL : public HExpression<2> { SetRawInputAt(1, method_base); } + HX86ComputeBaseMethodAddress* GetBaseMethodAddress() const { + return InputAt(1)->AsX86ComputeBaseMethodAddress(); + } + DECLARE_INSTRUCTION(X86FPNeg); private: diff --git a/compiler/optimizing/pc_relative_fixups_x86.cc b/compiler/optimizing/pc_relative_fixups_x86.cc index 2befc8ca4e..a1c916f43a 100644 --- a/compiler/optimizing/pc_relative_fixups_x86.cc +++ b/compiler/optimizing/pc_relative_fixups_x86.cc @@ -84,8 +84,8 @@ class PCRelativeHandlerVisitor : public HGraphVisitor { HLoadClass::LoadKind load_kind = load_class->GetLoadKind(); if (load_kind == HLoadClass::LoadKind::kBootImageLinkTimePcRelative || load_kind == HLoadClass::LoadKind::kBssEntry) { - InitializePCRelativeBasePointer(); - load_class->AddSpecialInput(base_); + HX86ComputeBaseMethodAddress* method_address = GetPCRelativeBasePointer(load_class); + load_class->AddSpecialInput(method_address); } } @@ -93,8 +93,8 @@ class PCRelativeHandlerVisitor : public HGraphVisitor { HLoadString::LoadKind load_kind = load_string->GetLoadKind(); if (load_kind == HLoadString::LoadKind::kBootImageLinkTimePcRelative || load_kind == HLoadString::LoadKind::kBssEntry) { - InitializePCRelativeBasePointer(); - load_string->AddSpecialInput(base_); + HX86ComputeBaseMethodAddress* method_address = GetPCRelativeBasePointer(load_string); + load_string->AddSpecialInput(method_address); } } @@ -132,13 +132,13 @@ class PCRelativeHandlerVisitor : public HGraphVisitor { void VisitNeg(HNeg* neg) OVERRIDE { if (Primitive::IsFloatingPointType(neg->GetType())) { // We need to replace the HNeg with a HX86FPNeg in order to address the constant area. - InitializePCRelativeBasePointer(); + HX86ComputeBaseMethodAddress* method_address = GetPCRelativeBasePointer(neg); HGraph* graph = GetGraph(); HBasicBlock* block = neg->GetBlock(); HX86FPNeg* x86_fp_neg = new (graph->GetArena()) HX86FPNeg( neg->GetType(), neg->InputAt(0), - base_, + method_address, neg->GetDexPc()); block->ReplaceAndRemoveInstructionWith(neg, x86_fp_neg); } @@ -151,35 +151,44 @@ class PCRelativeHandlerVisitor : public HGraphVisitor { } // We need to replace the HPackedSwitch with a HX86PackedSwitch in order to // address the constant area. - InitializePCRelativeBasePointer(); + HX86ComputeBaseMethodAddress* method_address = GetPCRelativeBasePointer(switch_insn); HGraph* graph = GetGraph(); HBasicBlock* block = switch_insn->GetBlock(); HX86PackedSwitch* x86_switch = new (graph->GetArena()) HX86PackedSwitch( switch_insn->GetStartValue(), switch_insn->GetNumEntries(), switch_insn->InputAt(0), - base_, + method_address, switch_insn->GetDexPc()); block->ReplaceAndRemoveInstructionWith(switch_insn, x86_switch); } - void InitializePCRelativeBasePointer() { - // Ensure we only initialize the pointer once. - if (base_ != nullptr) { - return; + HX86ComputeBaseMethodAddress* GetPCRelativeBasePointer(HInstruction* cursor) { + bool has_irreducible_loops = GetGraph()->HasIrreducibleLoops(); + if (!has_irreducible_loops) { + // Ensure we only initialize the pointer once. + if (base_ != nullptr) { + return base_; + } } // Insert the base at the start of the entry block, move it to a better // position later in MoveBaseIfNeeded(). - base_ = new (GetGraph()->GetArena()) HX86ComputeBaseMethodAddress(); - HBasicBlock* entry_block = GetGraph()->GetEntryBlock(); - entry_block->InsertInstructionBefore(base_, entry_block->GetFirstInstruction()); - DCHECK(base_ != nullptr); + HX86ComputeBaseMethodAddress* method_address = + new (GetGraph()->GetArena()) HX86ComputeBaseMethodAddress(); + if (has_irreducible_loops) { + cursor->GetBlock()->InsertInstructionBefore(method_address, cursor); + } else { + HBasicBlock* entry_block = GetGraph()->GetEntryBlock(); + entry_block->InsertInstructionBefore(method_address, entry_block->GetFirstInstruction()); + base_ = method_address; + } + return method_address; } void ReplaceInput(HInstruction* insn, HConstant* value, int input_index, bool materialize) { - InitializePCRelativeBasePointer(); + HX86ComputeBaseMethodAddress* method_address = GetPCRelativeBasePointer(insn); HX86LoadFromConstantTable* load_constant = - new (GetGraph()->GetArena()) HX86LoadFromConstantTable(base_, value); + new (GetGraph()->GetArena()) HX86LoadFromConstantTable(method_address, value); if (!materialize) { load_constant->MarkEmittedAtUseSite(); } @@ -204,9 +213,9 @@ class PCRelativeHandlerVisitor : public HGraphVisitor { if (invoke_static_or_direct != nullptr && invoke_static_or_direct->HasPcRelativeDexCache() && !IsCallFreeIntrinsic<IntrinsicLocationsBuilderX86>(invoke, codegen_)) { - InitializePCRelativeBasePointer(); - // Add the extra parameter base_. - invoke_static_or_direct->AddSpecialInput(base_); + HX86ComputeBaseMethodAddress* method_address = GetPCRelativeBasePointer(invoke); + // Add the extra parameter. + invoke_static_or_direct->AddSpecialInput(method_address); base_added = true; } @@ -231,8 +240,8 @@ class PCRelativeHandlerVisitor : public HGraphVisitor { if (!base_added) { DCHECK(invoke_static_or_direct != nullptr); DCHECK(!invoke_static_or_direct->HasCurrentMethodInput()); - InitializePCRelativeBasePointer(); - invoke_static_or_direct->AddSpecialInput(base_); + HX86ComputeBaseMethodAddress* method_address = GetPCRelativeBasePointer(invoke); + invoke_static_or_direct->AddSpecialInput(method_address); } break; default: @@ -243,16 +252,12 @@ class PCRelativeHandlerVisitor : public HGraphVisitor { CodeGeneratorX86* codegen_; // The generated HX86ComputeBaseMethodAddress in the entry block needed as an - // input to the HX86LoadFromConstantTable instructions. + // input to the HX86LoadFromConstantTable instructions. Only set for + // graphs with reducible loops. HX86ComputeBaseMethodAddress* base_; }; void PcRelativeFixups::Run() { - if (graph_->HasIrreducibleLoops()) { - // Do not run this optimization, as irreducible loops do not work with an instruction - // that can be live-in at the irreducible loop header. - return; - } PCRelativeHandlerVisitor visitor(graph_, codegen_); visitor.VisitInsertionOrder(); visitor.MoveBaseIfNeeded(); diff --git a/compiler/optimizing/stack_map_stream.cc b/compiler/optimizing/stack_map_stream.cc index 6087e36507..a9a1e6f592 100644 --- a/compiler/optimizing/stack_map_stream.cc +++ b/compiler/optimizing/stack_map_stream.cc @@ -31,7 +31,7 @@ void StackMapStream::BeginStackMapEntry(uint32_t dex_pc, DCHECK_EQ(0u, current_entry_.dex_pc) << "EndStackMapEntry not called after BeginStackMapEntry"; DCHECK_NE(dex_pc, static_cast<uint32_t>(-1)) << "invalid dex_pc"; current_entry_.dex_pc = dex_pc; - current_entry_.native_pc_offset = native_pc_offset; + current_entry_.native_pc_code_offset = CodeOffset::FromOffset(native_pc_offset, instruction_set_); current_entry_.register_mask = register_mask; current_entry_.sp_mask = sp_mask; current_entry_.num_dex_registers = num_dex_registers; @@ -144,10 +144,10 @@ void StackMapStream::EndInlineInfoEntry() { current_inline_info_ = InlineInfoEntry(); } -uint32_t StackMapStream::ComputeMaxNativePcOffset() const { - uint32_t max_native_pc_offset = 0u; +CodeOffset StackMapStream::ComputeMaxNativePcCodeOffset() const { + CodeOffset max_native_pc_offset; for (const StackMapEntry& entry : stack_maps_) { - max_native_pc_offset = std::max(max_native_pc_offset, entry.native_pc_offset); + max_native_pc_offset = std::max(max_native_pc_offset, entry.native_pc_code_offset); } return max_native_pc_offset; } @@ -157,8 +157,9 @@ size_t StackMapStream::PrepareForFillIn() { dex_register_maps_size_ = ComputeDexRegisterMapsSize(); ComputeInlineInfoEncoding(); // needs dex_register_maps_size_. inline_info_size_ = inline_infos_.size() * inline_info_encoding_.GetEntrySize(); - uint32_t max_native_pc_offset = ComputeMaxNativePcOffset(); - size_t stack_map_size = stack_map_encoding_.SetFromSizes(max_native_pc_offset, + CodeOffset max_native_pc_offset = ComputeMaxNativePcCodeOffset(); + // The stack map contains compressed native offsets. + size_t stack_map_size = stack_map_encoding_.SetFromSizes(max_native_pc_offset.CompressedValue(), dex_pc_max_, dex_register_maps_size_, inline_info_size_, @@ -319,7 +320,7 @@ void StackMapStream::FillIn(MemoryRegion region) { StackMapEntry entry = stack_maps_[i]; stack_map.SetDexPc(stack_map_encoding_, entry.dex_pc); - stack_map.SetNativePcOffset(stack_map_encoding_, entry.native_pc_offset); + stack_map.SetNativePcCodeOffset(stack_map_encoding_, entry.native_pc_code_offset); stack_map.SetRegisterMask(stack_map_encoding_, entry.register_mask); size_t number_of_stack_mask_bits = stack_map.GetNumberOfStackMaskBits(stack_map_encoding_); if (entry.sp_mask != nullptr) { @@ -546,7 +547,8 @@ void StackMapStream::CheckCodeInfo(MemoryRegion region) const { StackMapEntry entry = stack_maps_[s]; // Check main stack map fields. - DCHECK_EQ(stack_map.GetNativePcOffset(stack_map_encoding), entry.native_pc_offset); + DCHECK_EQ(stack_map.GetNativePcOffset(stack_map_encoding, instruction_set_), + entry.native_pc_code_offset.Uint32Value(instruction_set_)); DCHECK_EQ(stack_map.GetDexPc(stack_map_encoding), entry.dex_pc); DCHECK_EQ(stack_map.GetRegisterMask(stack_map_encoding), entry.register_mask); size_t num_stack_mask_bits = stack_map.GetNumberOfStackMaskBits(stack_map_encoding); diff --git a/compiler/optimizing/stack_map_stream.h b/compiler/optimizing/stack_map_stream.h index d6f42b373c..8fec472437 100644 --- a/compiler/optimizing/stack_map_stream.h +++ b/compiler/optimizing/stack_map_stream.h @@ -59,8 +59,10 @@ class DexRegisterLocationHashFn { */ class StackMapStream : public ValueObject { public: - explicit StackMapStream(ArenaAllocator* allocator) + explicit StackMapStream(ArenaAllocator* allocator, + InstructionSet instruction_set) : allocator_(allocator), + instruction_set_(instruction_set), stack_maps_(allocator->Adapter(kArenaAllocStackMapStream)), location_catalog_entries_(allocator->Adapter(kArenaAllocStackMapStream)), location_catalog_entries_indices_(allocator->Adapter(kArenaAllocStackMapStream)), @@ -95,7 +97,7 @@ class StackMapStream : public ValueObject { // See runtime/stack_map.h to know what these fields contain. struct StackMapEntry { uint32_t dex_pc; - uint32_t native_pc_offset; + CodeOffset native_pc_code_offset; uint32_t register_mask; BitVector* sp_mask; uint32_t num_dex_registers; @@ -141,11 +143,9 @@ class StackMapStream : public ValueObject { } void SetStackMapNativePcOffset(size_t i, uint32_t native_pc_offset) { - stack_maps_[i].native_pc_offset = native_pc_offset; + stack_maps_[i].native_pc_code_offset = CodeOffset::FromOffset(native_pc_offset, instruction_set_); } - uint32_t ComputeMaxNativePcOffset() const; - // Prepares the stream to fill in a memory region. Must be called before FillIn. // Returns the size (in bytes) needed to store this stream. size_t PrepareForFillIn(); @@ -158,6 +158,8 @@ class StackMapStream : public ValueObject { size_t ComputeDexRegisterMapsSize() const; void ComputeInlineInfoEncoding(); + CodeOffset ComputeMaxNativePcCodeOffset() const; + // Returns the index of an entry with the same dex register map as the current_entry, // or kNoSameDexMapFound if no such entry exists. size_t FindEntryWithTheSameDexMap(); @@ -175,6 +177,7 @@ class StackMapStream : public ValueObject { void CheckCodeInfo(MemoryRegion region) const; ArenaAllocator* allocator_; + const InstructionSet instruction_set_; ArenaVector<StackMapEntry> stack_maps_; // A catalog of unique [location_kind, register_value] pairs (per method). diff --git a/compiler/optimizing/stack_map_test.cc b/compiler/optimizing/stack_map_test.cc index 22810ea4f7..f68695bcbc 100644 --- a/compiler/optimizing/stack_map_test.cc +++ b/compiler/optimizing/stack_map_test.cc @@ -47,7 +47,7 @@ using Kind = DexRegisterLocation::Kind; TEST(StackMapTest, Test1) { ArenaPool pool; ArenaAllocator arena(&pool); - StackMapStream stream(&arena); + StackMapStream stream(&arena, kRuntimeISA); ArenaBitVector sp_mask(&arena, 0, false); size_t number_of_dex_registers = 2; @@ -78,7 +78,7 @@ TEST(StackMapTest, Test1) { ASSERT_TRUE(stack_map.Equals(code_info.GetStackMapForDexPc(0, encoding))); ASSERT_TRUE(stack_map.Equals(code_info.GetStackMapForNativePcOffset(64, encoding))); ASSERT_EQ(0u, stack_map.GetDexPc(encoding.stack_map_encoding)); - ASSERT_EQ(64u, stack_map.GetNativePcOffset(encoding.stack_map_encoding)); + ASSERT_EQ(64u, stack_map.GetNativePcOffset(encoding.stack_map_encoding, kRuntimeISA)); ASSERT_EQ(0x3u, stack_map.GetRegisterMask(encoding.stack_map_encoding)); ASSERT_TRUE(CheckStackMask(stack_map, encoding.stack_map_encoding, sp_mask)); @@ -128,7 +128,7 @@ TEST(StackMapTest, Test1) { TEST(StackMapTest, Test2) { ArenaPool pool; ArenaAllocator arena(&pool); - StackMapStream stream(&arena); + StackMapStream stream(&arena, kRuntimeISA); ArtMethod art_method; ArenaBitVector sp_mask1(&arena, 0, true); @@ -193,7 +193,7 @@ TEST(StackMapTest, Test2) { ASSERT_TRUE(stack_map.Equals(code_info.GetStackMapForDexPc(0, encoding))); ASSERT_TRUE(stack_map.Equals(code_info.GetStackMapForNativePcOffset(64, encoding))); ASSERT_EQ(0u, stack_map.GetDexPc(encoding.stack_map_encoding)); - ASSERT_EQ(64u, stack_map.GetNativePcOffset(encoding.stack_map_encoding)); + ASSERT_EQ(64u, stack_map.GetNativePcOffset(encoding.stack_map_encoding, kRuntimeISA)); ASSERT_EQ(0x3u, stack_map.GetRegisterMask(encoding.stack_map_encoding)); ASSERT_TRUE(CheckStackMask(stack_map, encoding.stack_map_encoding, sp_mask1)); @@ -252,7 +252,7 @@ TEST(StackMapTest, Test2) { ASSERT_TRUE(stack_map.Equals(code_info.GetStackMapForDexPc(1u, encoding))); ASSERT_TRUE(stack_map.Equals(code_info.GetStackMapForNativePcOffset(128u, encoding))); ASSERT_EQ(1u, stack_map.GetDexPc(encoding.stack_map_encoding)); - ASSERT_EQ(128u, stack_map.GetNativePcOffset(encoding.stack_map_encoding)); + ASSERT_EQ(128u, stack_map.GetNativePcOffset(encoding.stack_map_encoding, kRuntimeISA)); ASSERT_EQ(0xFFu, stack_map.GetRegisterMask(encoding.stack_map_encoding)); ASSERT_TRUE(CheckStackMask(stack_map, encoding.stack_map_encoding, sp_mask2)); @@ -306,7 +306,7 @@ TEST(StackMapTest, Test2) { ASSERT_TRUE(stack_map.Equals(code_info.GetStackMapForDexPc(2u, encoding))); ASSERT_TRUE(stack_map.Equals(code_info.GetStackMapForNativePcOffset(192u, encoding))); ASSERT_EQ(2u, stack_map.GetDexPc(encoding.stack_map_encoding)); - ASSERT_EQ(192u, stack_map.GetNativePcOffset(encoding.stack_map_encoding)); + ASSERT_EQ(192u, stack_map.GetNativePcOffset(encoding.stack_map_encoding, kRuntimeISA)); ASSERT_EQ(0xABu, stack_map.GetRegisterMask(encoding.stack_map_encoding)); ASSERT_TRUE(CheckStackMask(stack_map, encoding.stack_map_encoding, sp_mask3)); @@ -360,7 +360,7 @@ TEST(StackMapTest, Test2) { ASSERT_TRUE(stack_map.Equals(code_info.GetStackMapForDexPc(3u, encoding))); ASSERT_TRUE(stack_map.Equals(code_info.GetStackMapForNativePcOffset(256u, encoding))); ASSERT_EQ(3u, stack_map.GetDexPc(encoding.stack_map_encoding)); - ASSERT_EQ(256u, stack_map.GetNativePcOffset(encoding.stack_map_encoding)); + ASSERT_EQ(256u, stack_map.GetNativePcOffset(encoding.stack_map_encoding, kRuntimeISA)); ASSERT_EQ(0xCDu, stack_map.GetRegisterMask(encoding.stack_map_encoding)); ASSERT_TRUE(CheckStackMask(stack_map, encoding.stack_map_encoding, sp_mask4)); @@ -412,7 +412,7 @@ TEST(StackMapTest, Test2) { TEST(StackMapTest, TestNonLiveDexRegisters) { ArenaPool pool; ArenaAllocator arena(&pool); - StackMapStream stream(&arena); + StackMapStream stream(&arena, kRuntimeISA); ArenaBitVector sp_mask(&arena, 0, false); uint32_t number_of_dex_registers = 2; @@ -442,7 +442,7 @@ TEST(StackMapTest, TestNonLiveDexRegisters) { ASSERT_TRUE(stack_map.Equals(code_info.GetStackMapForDexPc(0, encoding))); ASSERT_TRUE(stack_map.Equals(code_info.GetStackMapForNativePcOffset(64, encoding))); ASSERT_EQ(0u, stack_map.GetDexPc(encoding.stack_map_encoding)); - ASSERT_EQ(64u, stack_map.GetNativePcOffset(encoding.stack_map_encoding)); + ASSERT_EQ(64u, stack_map.GetNativePcOffset(encoding.stack_map_encoding, kRuntimeISA)); ASSERT_EQ(0x3u, stack_map.GetRegisterMask(encoding.stack_map_encoding)); ASSERT_TRUE(stack_map.HasDexRegisterMap(encoding.stack_map_encoding)); @@ -491,7 +491,7 @@ TEST(StackMapTest, TestNonLiveDexRegisters) { TEST(StackMapTest, DexRegisterMapOffsetOverflow) { ArenaPool pool; ArenaAllocator arena(&pool); - StackMapStream stream(&arena); + StackMapStream stream(&arena, kRuntimeISA); ArenaBitVector sp_mask(&arena, 0, false); uint32_t number_of_dex_registers = 1024; @@ -554,7 +554,7 @@ TEST(StackMapTest, DexRegisterMapOffsetOverflow) { TEST(StackMapTest, TestShareDexRegisterMap) { ArenaPool pool; ArenaAllocator arena(&pool); - StackMapStream stream(&arena); + StackMapStream stream(&arena, kRuntimeISA); ArenaBitVector sp_mask(&arena, 0, false); uint32_t number_of_dex_registers = 2; @@ -612,7 +612,7 @@ TEST(StackMapTest, TestShareDexRegisterMap) { TEST(StackMapTest, TestNoDexRegisterMap) { ArenaPool pool; ArenaAllocator arena(&pool); - StackMapStream stream(&arena); + StackMapStream stream(&arena, kRuntimeISA); ArenaBitVector sp_mask(&arena, 0, false); uint32_t number_of_dex_registers = 0; @@ -620,7 +620,7 @@ TEST(StackMapTest, TestNoDexRegisterMap) { stream.EndStackMapEntry(); number_of_dex_registers = 1; - stream.BeginStackMapEntry(1, 67, 0x4, &sp_mask, number_of_dex_registers, 0); + stream.BeginStackMapEntry(1, 68, 0x4, &sp_mask, number_of_dex_registers, 0); stream.EndStackMapEntry(); size_t size = stream.PrepareForFillIn(); @@ -641,7 +641,7 @@ TEST(StackMapTest, TestNoDexRegisterMap) { ASSERT_TRUE(stack_map.Equals(code_info.GetStackMapForDexPc(0, encoding))); ASSERT_TRUE(stack_map.Equals(code_info.GetStackMapForNativePcOffset(64, encoding))); ASSERT_EQ(0u, stack_map.GetDexPc(encoding.stack_map_encoding)); - ASSERT_EQ(64u, stack_map.GetNativePcOffset(encoding.stack_map_encoding)); + ASSERT_EQ(64u, stack_map.GetNativePcOffset(encoding.stack_map_encoding, kRuntimeISA)); ASSERT_EQ(0x3u, stack_map.GetRegisterMask(encoding.stack_map_encoding)); ASSERT_FALSE(stack_map.HasDexRegisterMap(encoding.stack_map_encoding)); @@ -649,9 +649,9 @@ TEST(StackMapTest, TestNoDexRegisterMap) { stack_map = code_info.GetStackMapAt(1, encoding); ASSERT_TRUE(stack_map.Equals(code_info.GetStackMapForDexPc(1, encoding))); - ASSERT_TRUE(stack_map.Equals(code_info.GetStackMapForNativePcOffset(67, encoding))); + ASSERT_TRUE(stack_map.Equals(code_info.GetStackMapForNativePcOffset(68, encoding))); ASSERT_EQ(1u, stack_map.GetDexPc(encoding.stack_map_encoding)); - ASSERT_EQ(67u, stack_map.GetNativePcOffset(encoding.stack_map_encoding)); + ASSERT_EQ(68u, stack_map.GetNativePcOffset(encoding.stack_map_encoding, kRuntimeISA)); ASSERT_EQ(0x4u, stack_map.GetRegisterMask(encoding.stack_map_encoding)); ASSERT_FALSE(stack_map.HasDexRegisterMap(encoding.stack_map_encoding)); @@ -661,7 +661,7 @@ TEST(StackMapTest, TestNoDexRegisterMap) { TEST(StackMapTest, InlineTest) { ArenaPool pool; ArenaAllocator arena(&pool); - StackMapStream stream(&arena); + StackMapStream stream(&arena, kRuntimeISA); ArtMethod art_method; ArenaBitVector sp_mask1(&arena, 0, true); @@ -823,4 +823,20 @@ TEST(StackMapTest, InlineTest) { } } +TEST(StackMapTest, CodeOffsetTest) { + // Test minimum alignments, encoding, and decoding. + CodeOffset offset_thumb2 = CodeOffset::FromOffset(kThumb2InstructionAlignment, kThumb2); + CodeOffset offset_arm64 = CodeOffset::FromOffset(kArm64InstructionAlignment, kArm64); + CodeOffset offset_x86 = CodeOffset::FromOffset(kX86InstructionAlignment, kX86); + CodeOffset offset_x86_64 = CodeOffset::FromOffset(kX86_64InstructionAlignment, kX86_64); + CodeOffset offset_mips = CodeOffset::FromOffset(kMipsInstructionAlignment, kMips); + CodeOffset offset_mips64 = CodeOffset::FromOffset(kMips64InstructionAlignment, kMips64); + EXPECT_EQ(offset_thumb2.Uint32Value(kThumb2), kThumb2InstructionAlignment); + EXPECT_EQ(offset_arm64.Uint32Value(kArm64), kArm64InstructionAlignment); + EXPECT_EQ(offset_x86.Uint32Value(kX86), kX86InstructionAlignment); + EXPECT_EQ(offset_x86_64.Uint32Value(kX86_64), kX86_64InstructionAlignment); + EXPECT_EQ(offset_mips.Uint32Value(kMips), kMipsInstructionAlignment); + EXPECT_EQ(offset_mips64.Uint32Value(kMips64), kMips64InstructionAlignment); +} + } // namespace art diff --git a/compiler/utils/mips64/assembler_mips64_test.cc b/compiler/utils/mips64/assembler_mips64_test.cc index f2cbebbfd7..74b8f068c1 100644 --- a/compiler/utils/mips64/assembler_mips64_test.cc +++ b/compiler/utils/mips64/assembler_mips64_test.cc @@ -283,6 +283,38 @@ TEST_F(AssemblerMIPS64Test, Toolchain) { // FP Operations // /////////////////// +TEST_F(AssemblerMIPS64Test, AddS) { + DriverStr(RepeatFFF(&mips64::Mips64Assembler::AddS, "add.s ${reg1}, ${reg2}, ${reg3}"), "add.s"); +} + +TEST_F(AssemblerMIPS64Test, AddD) { + DriverStr(RepeatFFF(&mips64::Mips64Assembler::AddD, "add.d ${reg1}, ${reg2}, ${reg3}"), "add.d"); +} + +TEST_F(AssemblerMIPS64Test, SubS) { + DriverStr(RepeatFFF(&mips64::Mips64Assembler::SubS, "sub.s ${reg1}, ${reg2}, ${reg3}"), "sub.s"); +} + +TEST_F(AssemblerMIPS64Test, SubD) { + DriverStr(RepeatFFF(&mips64::Mips64Assembler::SubD, "sub.d ${reg1}, ${reg2}, ${reg3}"), "sub.d"); +} + +TEST_F(AssemblerMIPS64Test, MulS) { + DriverStr(RepeatFFF(&mips64::Mips64Assembler::MulS, "mul.s ${reg1}, ${reg2}, ${reg3}"), "mul.s"); +} + +TEST_F(AssemblerMIPS64Test, MulD) { + DriverStr(RepeatFFF(&mips64::Mips64Assembler::MulD, "mul.d ${reg1}, ${reg2}, ${reg3}"), "mul.d"); +} + +TEST_F(AssemblerMIPS64Test, DivS) { + DriverStr(RepeatFFF(&mips64::Mips64Assembler::DivS, "div.s ${reg1}, ${reg2}, ${reg3}"), "div.s"); +} + +TEST_F(AssemblerMIPS64Test, DivD) { + DriverStr(RepeatFFF(&mips64::Mips64Assembler::DivD, "div.d ${reg1}, ${reg2}, ${reg3}"), "div.d"); +} + TEST_F(AssemblerMIPS64Test, SqrtS) { DriverStr(RepeatFF(&mips64::Mips64Assembler::SqrtS, "sqrt.s ${reg1}, ${reg2}"), "sqrt.s"); } @@ -567,6 +599,26 @@ TEST_F(AssemblerMIPS64Test, Dmtc1) { DriverStr(RepeatRF(&mips64::Mips64Assembler::Dmtc1, "dmtc1 ${reg1}, ${reg2}"), "Dmtc1"); } +TEST_F(AssemblerMIPS64Test, Lwc1) { + DriverStr(RepeatFRIb(&mips64::Mips64Assembler::Lwc1, -16, "lwc1 ${reg1}, {imm}(${reg2})"), + "lwc1"); +} + +TEST_F(AssemblerMIPS64Test, Ldc1) { + DriverStr(RepeatFRIb(&mips64::Mips64Assembler::Ldc1, -16, "ldc1 ${reg1}, {imm}(${reg2})"), + "ldc1"); +} + +TEST_F(AssemblerMIPS64Test, Swc1) { + DriverStr(RepeatFRIb(&mips64::Mips64Assembler::Swc1, -16, "swc1 ${reg1}, {imm}(${reg2})"), + "swc1"); +} + +TEST_F(AssemblerMIPS64Test, Sdc1) { + DriverStr(RepeatFRIb(&mips64::Mips64Assembler::Sdc1, -16, "sdc1 ${reg1}, {imm}(${reg2})"), + "sdc1"); +} + //////////////// // CALL / JMP // //////////////// @@ -850,6 +902,16 @@ TEST_F(AssemblerMIPS64Test, Ldpc) { DriverStr(RepeatRIb(&mips64::Mips64Assembler::Ldpc, 18, code), "Ldpc"); } +TEST_F(AssemblerMIPS64Test, Auipc) { + DriverStr(RepeatRIb(&mips64::Mips64Assembler::Auipc, 16, "auipc ${reg}, {imm}"), "Auipc"); +} + +TEST_F(AssemblerMIPS64Test, Addiupc) { + // The comment from the Lwpc() test applies to this Addiupc() test as well. + const char* code = ".set imm, {imm}\naddiupc ${reg}, (imm - ((imm & 0x40000) << 1)) << 2"; + DriverStr(RepeatRIb(&mips64::Mips64Assembler::Addiupc, 19, code), "Addiupc"); +} + TEST_F(AssemblerMIPS64Test, LoadFarthestNearLabelAddress) { mips64::Mips64Label label; __ LoadLabelAddress(mips64::V0, &label); @@ -1079,6 +1141,188 @@ TEST_F(AssemblerMIPS64Test, FarLongLiteralAlignmentNop) { EXPECT_EQ(__ GetLabelLocation(literal->GetLabel()), (5 + kAdduCount) * 4); } +TEST_F(AssemblerMIPS64Test, Addu) { + DriverStr(RepeatRRR(&mips64::Mips64Assembler::Addu, "addu ${reg1}, ${reg2}, ${reg3}"), "addu"); +} + +TEST_F(AssemblerMIPS64Test, Addiu) { + DriverStr(RepeatRRIb(&mips64::Mips64Assembler::Addiu, -16, "addiu ${reg1}, ${reg2}, {imm}"), + "addiu"); +} + +TEST_F(AssemblerMIPS64Test, Daddu) { + DriverStr(RepeatRRR(&mips64::Mips64Assembler::Daddu, "daddu ${reg1}, ${reg2}, ${reg3}"), "daddu"); +} + +TEST_F(AssemblerMIPS64Test, Daddiu) { + DriverStr(RepeatRRIb(&mips64::Mips64Assembler::Daddiu, -16, "daddiu ${reg1}, ${reg2}, {imm}"), + "daddiu"); +} + +TEST_F(AssemblerMIPS64Test, Subu) { + DriverStr(RepeatRRR(&mips64::Mips64Assembler::Subu, "subu ${reg1}, ${reg2}, ${reg3}"), "subu"); +} + +TEST_F(AssemblerMIPS64Test, Dsubu) { + DriverStr(RepeatRRR(&mips64::Mips64Assembler::Dsubu, "dsubu ${reg1}, ${reg2}, ${reg3}"), "dsubu"); +} + +TEST_F(AssemblerMIPS64Test, MulR6) { + DriverStr(RepeatRRR(&mips64::Mips64Assembler::MulR6, "mul ${reg1}, ${reg2}, ${reg3}"), "mulR6"); +} + +TEST_F(AssemblerMIPS64Test, DivR6) { + DriverStr(RepeatRRR(&mips64::Mips64Assembler::DivR6, "div ${reg1}, ${reg2}, ${reg3}"), "divR6"); +} + +TEST_F(AssemblerMIPS64Test, ModR6) { + DriverStr(RepeatRRR(&mips64::Mips64Assembler::ModR6, "mod ${reg1}, ${reg2}, ${reg3}"), "modR6"); +} + +TEST_F(AssemblerMIPS64Test, DivuR6) { + DriverStr(RepeatRRR(&mips64::Mips64Assembler::DivuR6, "divu ${reg1}, ${reg2}, ${reg3}"), + "divuR6"); +} + +TEST_F(AssemblerMIPS64Test, ModuR6) { + DriverStr(RepeatRRR(&mips64::Mips64Assembler::ModuR6, "modu ${reg1}, ${reg2}, ${reg3}"), + "moduR6"); +} + +TEST_F(AssemblerMIPS64Test, Dmul) { + DriverStr(RepeatRRR(&mips64::Mips64Assembler::Dmul, "dmul ${reg1}, ${reg2}, ${reg3}"), "dmul"); +} + +TEST_F(AssemblerMIPS64Test, Ddiv) { + DriverStr(RepeatRRR(&mips64::Mips64Assembler::Ddiv, "ddiv ${reg1}, ${reg2}, ${reg3}"), "ddiv"); +} + +TEST_F(AssemblerMIPS64Test, Dmod) { + DriverStr(RepeatRRR(&mips64::Mips64Assembler::Dmod, "dmod ${reg1}, ${reg2}, ${reg3}"), "dmod"); +} + +TEST_F(AssemblerMIPS64Test, Ddivu) { + DriverStr(RepeatRRR(&mips64::Mips64Assembler::Ddivu, "ddivu ${reg1}, ${reg2}, ${reg3}"), "ddivu"); +} + +TEST_F(AssemblerMIPS64Test, Dmodu) { + DriverStr(RepeatRRR(&mips64::Mips64Assembler::Dmodu, "dmodu ${reg1}, ${reg2}, ${reg3}"), "dmodu"); +} + +TEST_F(AssemblerMIPS64Test, And) { + DriverStr(RepeatRRR(&mips64::Mips64Assembler::And, "and ${reg1}, ${reg2}, ${reg3}"), "and"); +} + +TEST_F(AssemblerMIPS64Test, Andi) { + DriverStr(RepeatRRIb(&mips64::Mips64Assembler::Andi, 16, "andi ${reg1}, ${reg2}, {imm}"), "andi"); +} + +TEST_F(AssemblerMIPS64Test, Or) { + DriverStr(RepeatRRR(&mips64::Mips64Assembler::Or, "or ${reg1}, ${reg2}, ${reg3}"), "or"); +} + +TEST_F(AssemblerMIPS64Test, Ori) { + DriverStr(RepeatRRIb(&mips64::Mips64Assembler::Ori, 16, "ori ${reg1}, ${reg2}, {imm}"), "ori"); +} + +TEST_F(AssemblerMIPS64Test, Xor) { + DriverStr(RepeatRRR(&mips64::Mips64Assembler::Xor, "xor ${reg1}, ${reg2}, ${reg3}"), "xor"); +} + +TEST_F(AssemblerMIPS64Test, Xori) { + DriverStr(RepeatRRIb(&mips64::Mips64Assembler::Xori, 16, "xori ${reg1}, ${reg2}, {imm}"), "xori"); +} + +TEST_F(AssemblerMIPS64Test, Nor) { + DriverStr(RepeatRRR(&mips64::Mips64Assembler::Nor, "nor ${reg1}, ${reg2}, ${reg3}"), "nor"); +} + +TEST_F(AssemblerMIPS64Test, Lb) { + DriverStr(RepeatRRIb(&mips64::Mips64Assembler::Lb, -16, "lb ${reg1}, {imm}(${reg2})"), "lb"); +} + +TEST_F(AssemblerMIPS64Test, Lh) { + DriverStr(RepeatRRIb(&mips64::Mips64Assembler::Lh, -16, "lh ${reg1}, {imm}(${reg2})"), "lh"); +} + +TEST_F(AssemblerMIPS64Test, Lw) { + DriverStr(RepeatRRIb(&mips64::Mips64Assembler::Lw, -16, "lw ${reg1}, {imm}(${reg2})"), "lw"); +} + +TEST_F(AssemblerMIPS64Test, Ld) { + DriverStr(RepeatRRIb(&mips64::Mips64Assembler::Ld, -16, "ld ${reg1}, {imm}(${reg2})"), "ld"); +} + +TEST_F(AssemblerMIPS64Test, Lbu) { + DriverStr(RepeatRRIb(&mips64::Mips64Assembler::Lbu, -16, "lbu ${reg1}, {imm}(${reg2})"), "lbu"); +} + +TEST_F(AssemblerMIPS64Test, Lhu) { + DriverStr(RepeatRRIb(&mips64::Mips64Assembler::Lhu, -16, "lhu ${reg1}, {imm}(${reg2})"), "lhu"); +} + +TEST_F(AssemblerMIPS64Test, Lwu) { + DriverStr(RepeatRRIb(&mips64::Mips64Assembler::Lwu, -16, "lwu ${reg1}, {imm}(${reg2})"), "lwu"); +} + +TEST_F(AssemblerMIPS64Test, Lui) { + DriverStr(RepeatRIb(&mips64::Mips64Assembler::Lui, 16, "lui ${reg}, {imm}"), "lui"); +} + +TEST_F(AssemblerMIPS64Test, Dahi) { + DriverStr(RepeatRIb(&mips64::Mips64Assembler::Dahi, 16, "dahi ${reg}, ${reg}, {imm}"), "dahi"); +} + +TEST_F(AssemblerMIPS64Test, Dati) { + DriverStr(RepeatRIb(&mips64::Mips64Assembler::Dati, 16, "dati ${reg}, ${reg}, {imm}"), "dati"); +} + +TEST_F(AssemblerMIPS64Test, Sb) { + DriverStr(RepeatRRIb(&mips64::Mips64Assembler::Sb, -16, "sb ${reg1}, {imm}(${reg2})"), "sb"); +} + +TEST_F(AssemblerMIPS64Test, Sh) { + DriverStr(RepeatRRIb(&mips64::Mips64Assembler::Sh, -16, "sh ${reg1}, {imm}(${reg2})"), "sh"); +} + +TEST_F(AssemblerMIPS64Test, Sw) { + DriverStr(RepeatRRIb(&mips64::Mips64Assembler::Sw, -16, "sw ${reg1}, {imm}(${reg2})"), "sw"); +} + +TEST_F(AssemblerMIPS64Test, Sd) { + DriverStr(RepeatRRIb(&mips64::Mips64Assembler::Sd, -16, "sd ${reg1}, {imm}(${reg2})"), "sd"); +} + +TEST_F(AssemblerMIPS64Test, Slt) { + DriverStr(RepeatRRR(&mips64::Mips64Assembler::Slt, "slt ${reg1}, ${reg2}, ${reg3}"), "slt"); +} + +TEST_F(AssemblerMIPS64Test, Sltu) { + DriverStr(RepeatRRR(&mips64::Mips64Assembler::Sltu, "sltu ${reg1}, ${reg2}, ${reg3}"), "sltu"); +} + +TEST_F(AssemblerMIPS64Test, Slti) { + DriverStr(RepeatRRIb(&mips64::Mips64Assembler::Slti, -16, "slti ${reg1}, ${reg2}, {imm}"), + "slti"); +} + +TEST_F(AssemblerMIPS64Test, Sltiu) { + DriverStr(RepeatRRIb(&mips64::Mips64Assembler::Sltiu, -16, "sltiu ${reg1}, ${reg2}, {imm}"), + "sltiu"); +} + +TEST_F(AssemblerMIPS64Test, Move) { + DriverStr(RepeatRR(&mips64::Mips64Assembler::Move, "or ${reg1}, ${reg2}, $zero"), "move"); +} + +TEST_F(AssemblerMIPS64Test, Clear) { + DriverStr(RepeatR(&mips64::Mips64Assembler::Clear, "or ${reg}, $zero, $zero"), "clear"); +} + +TEST_F(AssemblerMIPS64Test, Not) { + DriverStr(RepeatRR(&mips64::Mips64Assembler::Not, "nor ${reg1}, ${reg2}, $zero"), "not"); +} + TEST_F(AssemblerMIPS64Test, Bitswap) { DriverStr(RepeatRR(&mips64::Mips64Assembler::Bitswap, "bitswap ${reg1}, ${reg2}"), "bitswap"); } @@ -1230,6 +1474,18 @@ TEST_F(AssemblerMIPS64Test, Dsra32) { "dsra32"); } +TEST_F(AssemblerMIPS64Test, Dsllv) { + DriverStr(RepeatRRR(&mips64::Mips64Assembler::Dsllv, "dsllv ${reg1}, ${reg2}, ${reg3}"), "dsllv"); +} + +TEST_F(AssemblerMIPS64Test, Dsrlv) { + DriverStr(RepeatRRR(&mips64::Mips64Assembler::Dsrlv, "dsrlv ${reg1}, ${reg2}, ${reg3}"), "dsrlv"); +} + +TEST_F(AssemblerMIPS64Test, Dsrav) { + DriverStr(RepeatRRR(&mips64::Mips64Assembler::Dsrav, "dsrav ${reg1}, ${reg2}, ${reg3}"), "dsrav"); +} + TEST_F(AssemblerMIPS64Test, Sc) { DriverStr(RepeatRRIb(&mips64::Mips64Assembler::Sc, -9, "sc ${reg1}, {imm}(${reg2})"), "sc"); } diff --git a/compiler/verifier_deps_test.cc b/compiler/verifier_deps_test.cc index 4f06a91448..5fc9972d09 100644 --- a/compiler/verifier_deps_test.cc +++ b/compiler/verifier_deps_test.cc @@ -1414,7 +1414,14 @@ TEST_F(VerifierDepsTest, VerifyDeps) { ASSERT_FALSE(decoded_deps.ValidateDependencies(new_class_loader, soa.Self())); } - { + // The two tests below make sure that fiddling with the method kind + // (static, virtual, interface) is detected by `ValidateDependencies`. + + // An interface method lookup can succeed with a virtual method lookup on the same class. + // That's OK, as we only want to make sure there is a method being defined with the right + // flags. Therefore, polluting the interface methods with virtual methods does not have + // to fail verification. + if (resolution_kind != kVirtualMethodResolution) { VerifierDeps decoded_deps(dex_files_, ArrayRef<const uint8_t>(buffer)); VerifierDeps::DexFileDeps* deps = decoded_deps.GetDexFileDeps(*primary_dex_file_); bool found = false; @@ -1433,7 +1440,8 @@ TEST_F(VerifierDepsTest, VerifyDeps) { ASSERT_FALSE(decoded_deps.ValidateDependencies(new_class_loader, soa.Self())); } - { + // See comment above that applies the same way. + if (resolution_kind != kInterfaceMethodResolution) { VerifierDeps decoded_deps(dex_files_, ArrayRef<const uint8_t>(buffer)); VerifierDeps::DexFileDeps* deps = decoded_deps.GetDexFileDeps(*primary_dex_file_); bool found = false; diff --git a/dex2oat/dex2oat.cc b/dex2oat/dex2oat.cc index 3fbdb89a74..e8a92c1914 100644 --- a/dex2oat/dex2oat.cc +++ b/dex2oat/dex2oat.cc @@ -64,7 +64,7 @@ #include "gc/space/space-inl.h" #include "image_writer.h" #include "interpreter/unstarted_runtime.h" -#include "jit/offline_profiling_info.h" +#include "jit/profile_compilation_info.h" #include "leb128.h" #include "linker/buffered_output_stream.h" #include "linker/file_output_stream.h" diff --git a/dex2oat/dex2oat_test.cc b/dex2oat/dex2oat_test.cc index cdb3b9fe2a..e86e560b1a 100644 --- a/dex2oat/dex2oat_test.cc +++ b/dex2oat/dex2oat_test.cc @@ -30,7 +30,7 @@ #include "base/macros.h" #include "dex_file-inl.h" #include "dex2oat_environment_test.h" -#include "jit/offline_profiling_info.h" +#include "jit/profile_compilation_info.h" #include "oat.h" #include "oat_file.h" #include "utils.h" diff --git a/dexlayout/dex_ir.h b/dexlayout/dex_ir.h index a2d1190d03..e2ee94022f 100644 --- a/dexlayout/dex_ir.h +++ b/dexlayout/dex_ir.h @@ -741,7 +741,7 @@ class ClassDef : public IndexedItem { uint32_t GetAccessFlags() const { return access_flags_; } const TypeId* Superclass() const { return superclass_; } const TypeIdVector* Interfaces() - { return interfaces_ == nullptr ? nullptr: interfaces_->GetTypeList(); } + { return interfaces_ == nullptr ? nullptr : interfaces_->GetTypeList(); } uint32_t InterfacesOffset() { return interfaces_ == nullptr ? 0 : interfaces_->GetOffset(); } const StringId* SourceFile() const { return source_file_; } AnnotationsDirectoryItem* Annotations() const { return annotations_; } diff --git a/dexlayout/dex_visualize.cc b/dexlayout/dex_visualize.cc index 02274b25a3..75d47e4013 100644 --- a/dexlayout/dex_visualize.cc +++ b/dexlayout/dex_visualize.cc @@ -31,7 +31,7 @@ #include "dex_ir.h" #include "dexlayout.h" -#include "jit/offline_profiling_info.h" +#include "jit/profile_compilation_info.h" namespace art { diff --git a/dexlayout/dexlayout.cc b/dexlayout/dexlayout.cc index cac60900bc..1add6bfede 100644 --- a/dexlayout/dexlayout.cc +++ b/dexlayout/dexlayout.cc @@ -37,7 +37,7 @@ #include "dex_instruction-inl.h" #include "dex_visualize.h" #include "dex_writer.h" -#include "jit/offline_profiling_info.h" +#include "jit/profile_compilation_info.h" #include "mem_map.h" #include "os.h" #include "utils.h" diff --git a/dexlayout/dexlayout_main.cc b/dexlayout/dexlayout_main.cc index 5f8a118bde..ad599aed93 100644 --- a/dexlayout/dexlayout_main.cc +++ b/dexlayout/dexlayout_main.cc @@ -30,7 +30,7 @@ #include <fcntl.h> #include "base/logging.h" -#include "jit/offline_profiling_info.h" +#include "jit/profile_compilation_info.h" #include "runtime.h" #include "mem_map.h" diff --git a/dexoptanalyzer/Android.bp b/dexoptanalyzer/Android.bp new file mode 100644 index 0000000000..cf4c99ec6d --- /dev/null +++ b/dexoptanalyzer/Android.bp @@ -0,0 +1,68 @@ +// +// 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. +// + +cc_defaults { + name: "dexoptanalyzer-defaults", + host_supported: true, + defaults: ["art_defaults"], + srcs: [ + "dexoptanalyzer.cc", + ], + + target: { + android: { + compile_multilib: "prefer32", + }, + }, + + include_dirs: [ + "art/cmdline", + ], + + shared_libs: [ + "libbase", + ], +} + +art_cc_binary { + name: "dexoptanalyzer", + defaults: ["dexoptanalyzer-defaults"], + shared_libs: [ + "libart", + ], +} + +art_cc_binary { + name: "dexoptanalyzerd", + defaults: [ + "dexoptanalyzer-defaults", + "art_debug_defaults", + ], + shared_libs: [ + "libartd", + ], +} + +art_cc_test { + name: "art_dexoptanalyzer_tests", + defaults: [ + "art_gtest_defaults", + ], + shared_libs: [ + "libbacktrace" + ], + srcs: ["dexoptanalyzer_test.cc"], +} diff --git a/dexoptanalyzer/dexoptanalyzer.cc b/dexoptanalyzer/dexoptanalyzer.cc new file mode 100644 index 0000000000..965e4073ea --- /dev/null +++ b/dexoptanalyzer/dexoptanalyzer.cc @@ -0,0 +1,265 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <string> + +#include "android-base/stringprintf.h" +#include "android-base/strings.h" +#include "compiler_filter.h" +#include "dex_file.h" +#include "noop_compiler_callbacks.h" +#include "oat_file_assistant.h" +#include "os.h" +#include "runtime.h" +#include "thread-inl.h" +#include "utils.h" + +namespace art { + +// See OatFileAssistant docs for the meaning of the valid return codes. +enum ReturnCodes { + kNoDexOptNeeded = 0, + kDex2OatFromScratch = 1, + kDex2OatForBootImageOat = 2, + kDex2OatForFilterOat = 3, + kDex2OatForRelocationOat = 4, + kDex2OatForBootImageOdex = 5, + kDex2OatForFilterOdex = 6, + kDex2OatForRelocationOdex = 7, + + kErrorInvalidArguments = 101, + kErrorCannotCreateRuntime = 102, + kErrorUnknownDexOptNeeded = 103 +}; + +static int original_argc; +static char** original_argv; + +static std::string CommandLine() { + std::vector<std::string> command; + for (int i = 0; i < original_argc; ++i) { + command.push_back(original_argv[i]); + } + return android::base::Join(command, ' '); +} + +static void UsageErrorV(const char* fmt, va_list ap) { + std::string error; + android::base::StringAppendV(&error, fmt, ap); + LOG(ERROR) << error; +} + +static void UsageError(const char* fmt, ...) { + va_list ap; + va_start(ap, fmt); + UsageErrorV(fmt, ap); + va_end(ap); +} + +NO_RETURN static void Usage(const char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + UsageErrorV(fmt, ap); + va_end(ap); + + UsageError("Command: %s", CommandLine().c_str()); + UsageError(" Performs a dexopt analysis on the given dex file and returns whether or not"); + UsageError(" the dex file needs to be dexopted."); + UsageError("Usage: dexoptanalyzer [options]..."); + UsageError(""); + UsageError(" --dex-file=<filename>: the dex file which should be analyzed."); + UsageError(""); + UsageError(" --isa=<string>: the instruction set for which the analysis should be performed."); + UsageError(""); + UsageError(" --compiler-filter=<string>: the target compiler filter to be used as reference"); + UsageError(" when deciding if the dex file needs to be optimized."); + UsageError(""); + UsageError(" --assume-profile-changed: assumes the profile information has changed"); + UsageError(" when deciding if the dex file needs to be optimized."); + UsageError(""); + UsageError(" --image=<filename>: optional, the image to be used to decide if the associated"); + UsageError(" oat file is up to date. Defaults to $ANDROID_ROOT/framework/boot.art."); + UsageError(" Example: --image=/system/framework/boot.art"); + UsageError(""); + UsageError(" --android-data=<directory>: optional, the directory which should be used as"); + UsageError(" android-data. By default ANDROID_DATA env variable is used."); + UsageError(""); + UsageError("Return code:"); + UsageError(" To make it easier to integrate with the internal tools this command will make"); + UsageError(" available its result (dexoptNeeded) as the exit/return code. i.e. it will not"); + UsageError(" return 0 for success and a non zero values for errors as the conventional"); + UsageError(" commands. The following return codes are possible:"); + UsageError(" kNoDexOptNeeded = 0"); + UsageError(" kDex2OatFromScratch = 1"); + UsageError(" kDex2OatForBootImageOat = 2"); + UsageError(" kDex2OatForFilterOat = 3"); + UsageError(" kDex2OatForRelocationOat = 4"); + UsageError(" kDex2OatForBootImageOdex = 5"); + UsageError(" kDex2OatForFilterOdex = 6"); + UsageError(" kDex2OatForRelocationOdex = 7"); + + UsageError(" kErrorInvalidArguments = 101"); + UsageError(" kErrorCannotCreateRuntime = 102"); + UsageError(" kErrorUnknownDexOptNeeded = 103"); + UsageError(""); + + exit(kErrorInvalidArguments); +} + +class DexoptAnalyzer FINAL { + public: + DexoptAnalyzer() : assume_profile_changed_(false) {} + + void ParseArgs(int argc, char **argv) { + original_argc = argc; + original_argv = argv; + + InitLogging(argv, Runtime::Aborter); + // Skip over the command name. + argv++; + argc--; + + if (argc == 0) { + Usage("No arguments specified"); + } + + for (int i = 0; i < argc; ++i) { + const StringPiece option(argv[i]); + if (option == "--assume-profile-changed") { + assume_profile_changed_ = true; + } else if (option.starts_with("--dex-file=")) { + dex_file_ = option.substr(strlen("--dex-file=")).ToString(); + } else if (option.starts_with("--compiler-filter=")) { + std::string filter_str = option.substr(strlen("--compiler-filter=")).ToString(); + if (!CompilerFilter::ParseCompilerFilter(filter_str.c_str(), &compiler_filter_)) { + Usage("Invalid compiler filter '%s'", option.data()); + } + } else if (option.starts_with("--isa=")) { + std::string isa_str = option.substr(strlen("--isa=")).ToString(); + isa_ = GetInstructionSetFromString(isa_str.c_str()); + if (isa_ == kNone) { + Usage("Invalid isa '%s'", option.data()); + } + } else if (option.starts_with("--image=")) { + image_ = option.substr(strlen("--image=")).ToString(); + } else if (option.starts_with("--android-data=")) { + // Overwrite android-data if needed (oat file assistant relies on a valid directory to + // compute dalvik-cache folder). This is mostly used in tests. + std::string new_android_data = option.substr(strlen("--android-data=")).ToString(); + setenv("ANDROID_DATA", new_android_data.c_str(), 1); + } else { + Usage("Unknown argument '%s'", option.data()); + } + } + + if (image_.empty()) { + // If we don't receive the image, try to use the default one. + // Tests may specify a different image (e.g. core image). + std::string error_msg; + image_ = GetDefaultBootImageLocation(&error_msg); + + if (image_.empty()) { + LOG(ERROR) << error_msg; + Usage("--image unspecified and ANDROID_ROOT not set or image file does not exist."); + } + } + } + + bool CreateRuntime() { + RuntimeOptions options; + // The image could be custom, so make sure we explicitly pass it. + std::string img = "-Ximage:" + image_; + options.push_back(std::make_pair(img.c_str(), nullptr)); + // The instruction set of the image should match the instruction set we will test. + const void* isa_opt = reinterpret_cast<const void*>(GetInstructionSetString(isa_)); + options.push_back(std::make_pair("imageinstructionset", isa_opt)); + // Disable libsigchain. We don't don't need it to evaluate DexOptNeeded status. + options.push_back(std::make_pair("-Xno-sig-chain", nullptr)); + // Pretend we are a compiler so that we can re-use the same infrastructure to load a different + // ISA image and minimize the amount of things that get started. + NoopCompilerCallbacks callbacks; + options.push_back(std::make_pair("compilercallbacks", &callbacks)); + // Make sure we don't attempt to relocate. The tool should only retrieve the DexOptNeeded + // status and not attempt to relocate the boot image. + options.push_back(std::make_pair("-Xnorelocate", nullptr)); + + if (!Runtime::Create(options, false)) { + LOG(ERROR) << "Unable to initialize runtime"; + return false; + } + // Runtime::Create acquired the mutator_lock_ that is normally given away when we + // Runtime::Start. Give it away now. + Thread::Current()->TransitionFromRunnableToSuspended(kNative); + + return true; + } + + int GetDexOptNeeded() { + // If the file does not exist there's nothing to do. + // This is a fast path to avoid creating the runtime (b/34385298). + if (!OS::FileExists(dex_file_.c_str())) { + return kNoDexOptNeeded; + } + if (!CreateRuntime()) { + return kErrorCannotCreateRuntime; + } + OatFileAssistant oat_file_assistant(dex_file_.c_str(), isa_, /*load_executable*/ false); + // Always treat elements of the bootclasspath as up-to-date. + // TODO(calin): this check should be in OatFileAssistant. + if (oat_file_assistant.IsInBootClassPath()) { + return kNoDexOptNeeded; + } + int dexoptNeeded = oat_file_assistant.GetDexOptNeeded( + compiler_filter_, assume_profile_changed_); + + // Convert OatFileAssitant codes to dexoptanalyzer codes. + switch (dexoptNeeded) { + case OatFileAssistant::kNoDexOptNeeded: return kNoDexOptNeeded; + case OatFileAssistant::kDex2OatFromScratch: return kDex2OatFromScratch; + case OatFileAssistant::kDex2OatForBootImage: return kDex2OatForBootImageOat; + case OatFileAssistant::kDex2OatForFilter: return kDex2OatForFilterOat; + case OatFileAssistant::kDex2OatForRelocation: return kDex2OatForRelocationOat; + + case -OatFileAssistant::kDex2OatForBootImage: return kDex2OatForBootImageOdex; + case -OatFileAssistant::kDex2OatForFilter: return kDex2OatForFilterOdex; + case -OatFileAssistant::kDex2OatForRelocation: return kDex2OatForRelocationOdex; + default: + LOG(ERROR) << "Unknown dexoptNeeded " << dexoptNeeded; + return kErrorUnknownDexOptNeeded; + } + } + + private: + std::string dex_file_; + InstructionSet isa_; + CompilerFilter::Filter compiler_filter_; + bool assume_profile_changed_; + std::string image_; +}; + +static int dexoptAnalyze(int argc, char** argv) { + DexoptAnalyzer analyzer; + + // Parse arguments. Argument mistakes will lead to exit(kErrorInvalidArguments) in UsageError. + analyzer.ParseArgs(argc, argv); + return analyzer.GetDexOptNeeded(); +} + +} // namespace art + +int main(int argc, char **argv) { + return art::dexoptAnalyze(argc, argv); +} diff --git a/dexoptanalyzer/dexoptanalyzer_test.cc b/dexoptanalyzer/dexoptanalyzer_test.cc new file mode 100644 index 0000000000..57d3f1f68b --- /dev/null +++ b/dexoptanalyzer/dexoptanalyzer_test.cc @@ -0,0 +1,311 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <gtest/gtest.h> + +#include "arch/instruction_set.h" +#include "compiler_filter.h" +#include "dexopt_test.h" + +namespace art { + +class DexoptAnalyzerTest : public DexoptTest { + protected: + std::string GetDexoptAnalyzerCmd() { + std::string file_path = GetTestAndroidRoot(); + file_path += "/bin/dexoptanalyzer"; + if (kIsDebugBuild) { + file_path += "d"; + } + EXPECT_TRUE(OS::FileExists(file_path.c_str())) << file_path << " should be a valid file path"; + return file_path; + } + + int Analyze(const std::string& dex_file, + CompilerFilter::Filter compiler_filter, + bool assume_profile_changed) { + std::string dexoptanalyzer_cmd = GetDexoptAnalyzerCmd(); + std::vector<std::string> argv_str; + argv_str.push_back(dexoptanalyzer_cmd); + argv_str.push_back("--dex-file=" + dex_file); + argv_str.push_back("--isa=" + std::string(GetInstructionSetString(kRuntimeISA))); + argv_str.push_back("--compiler-filter=" + CompilerFilter::NameOfFilter(compiler_filter)); + if (assume_profile_changed) { + argv_str.push_back("--assume-profile-changed"); + } + argv_str.push_back("--image=" + GetImageLocation()); + argv_str.push_back("--android-data=" + android_data_); + + std::string error; + return ExecAndReturnCode(argv_str, &error); + } + + int DexoptanalyzerToOatFileAssistant(int dexoptanalyzerResult) { + switch (dexoptanalyzerResult) { + case 0: return OatFileAssistant::kNoDexOptNeeded; + case 1: return OatFileAssistant::kDex2OatFromScratch; + case 2: return OatFileAssistant::kDex2OatForBootImage; + case 3: return OatFileAssistant::kDex2OatForFilter; + case 4: return OatFileAssistant::kDex2OatForRelocation; + case 5: return -OatFileAssistant::kDex2OatForBootImage; + case 6: return -OatFileAssistant::kDex2OatForFilter; + case 7: return -OatFileAssistant::kDex2OatForRelocation; + default: return dexoptanalyzerResult; + } + } + + // Verify that the output of dexoptanalyzer for the given arguments is the same + // as the output of OatFileAssistant::GetDexOptNeeded. + void Verify(const std::string& dex_file, + CompilerFilter::Filter compiler_filter, + bool assume_profile_changed = false) { + int dexoptanalyzerResult = Analyze(dex_file, compiler_filter, assume_profile_changed); + dexoptanalyzerResult = DexoptanalyzerToOatFileAssistant(dexoptanalyzerResult); + OatFileAssistant oat_file_assistant(dex_file.c_str(), kRuntimeISA, /*load_executable*/ false); + int assistantResult = oat_file_assistant.GetDexOptNeeded( + compiler_filter, assume_profile_changed); + EXPECT_EQ(assistantResult, dexoptanalyzerResult); + } +}; + +// The tests below exercise the same test case from oat_file_assistant_test.cc. + +// Case: We have a DEX file, but no OAT file for it. +TEST_F(DexoptAnalyzerTest, DexNoOat) { + std::string dex_location = GetScratchDir() + "/DexNoOat.jar"; + Copy(GetDexSrc1(), dex_location); + + Verify(dex_location, CompilerFilter::kSpeed); + Verify(dex_location, CompilerFilter::kVerifyAtRuntime); + Verify(dex_location, CompilerFilter::kInterpretOnly); + Verify(dex_location, CompilerFilter::kSpeedProfile); +} + +// Case: We have a DEX file and up-to-date OAT file for it. +TEST_F(DexoptAnalyzerTest, OatUpToDate) { + std::string dex_location = GetScratchDir() + "/OatUpToDate.jar"; + Copy(GetDexSrc1(), dex_location); + GenerateOatForTest(dex_location.c_str(), CompilerFilter::kSpeed); + + Verify(dex_location, CompilerFilter::kSpeed); + Verify(dex_location, CompilerFilter::kInterpretOnly); + Verify(dex_location, CompilerFilter::kVerifyAtRuntime); + Verify(dex_location, CompilerFilter::kEverything); +} + +// Case: We have a DEX file and speed-profile OAT file for it. +TEST_F(DexoptAnalyzerTest, ProfileOatUpToDate) { + std::string dex_location = GetScratchDir() + "/ProfileOatUpToDate.jar"; + Copy(GetDexSrc1(), dex_location); + GenerateOatForTest(dex_location.c_str(), CompilerFilter::kSpeedProfile); + + Verify(dex_location, CompilerFilter::kSpeedProfile, false); + Verify(dex_location, CompilerFilter::kInterpretOnly, false); + Verify(dex_location, CompilerFilter::kSpeedProfile, true); + Verify(dex_location, CompilerFilter::kInterpretOnly, true); +} + +// Case: We have a MultiDEX file and up-to-date OAT file for it. +TEST_F(DexoptAnalyzerTest, MultiDexOatUpToDate) { + std::string dex_location = GetScratchDir() + "/MultiDexOatUpToDate.jar"; + Copy(GetMultiDexSrc1(), dex_location); + GenerateOatForTest(dex_location.c_str(), CompilerFilter::kSpeed); + + Verify(dex_location, CompilerFilter::kSpeed, false); +} + +// Case: We have a MultiDEX file where the secondary dex file is out of date. +TEST_F(DexoptAnalyzerTest, MultiDexSecondaryOutOfDate) { + std::string dex_location = GetScratchDir() + "/MultiDexSecondaryOutOfDate.jar"; + + // Compile code for GetMultiDexSrc1. + Copy(GetMultiDexSrc1(), dex_location); + GenerateOatForTest(dex_location.c_str(), CompilerFilter::kSpeed); + + // Now overwrite the dex file with GetMultiDexSrc2 so the secondary checksum + // is out of date. + Copy(GetMultiDexSrc2(), dex_location); + + Verify(dex_location, CompilerFilter::kSpeed, false); +} + + +// Case: We have a DEX file and an OAT file out of date with respect to the +// dex checksum. +TEST_F(DexoptAnalyzerTest, OatDexOutOfDate) { + std::string dex_location = GetScratchDir() + "/OatDexOutOfDate.jar"; + + // We create a dex, generate an oat for it, then overwrite the dex with a + // different dex to make the oat out of date. + Copy(GetDexSrc1(), dex_location); + GenerateOatForTest(dex_location.c_str(), CompilerFilter::kSpeed); + Copy(GetDexSrc2(), dex_location); + + Verify(dex_location, CompilerFilter::kVerifyAtRuntime); + Verify(dex_location, CompilerFilter::kSpeed); +} + +// Case: We have a DEX file and an OAT file out of date with respect to the +// boot image. +TEST_F(DexoptAnalyzerTest, OatImageOutOfDate) { + std::string dex_location = GetScratchDir() + "/OatImageOutOfDate.jar"; + + Copy(GetDexSrc1(), dex_location); + GenerateOatForTest(dex_location.c_str(), + CompilerFilter::kSpeed, + /*relocate*/true, + /*pic*/false, + /*with_alternate_image*/true); + + Verify(dex_location, CompilerFilter::kVerifyAtRuntime); + Verify(dex_location, CompilerFilter::kInterpretOnly); + Verify(dex_location, CompilerFilter::kSpeed); +} + +// Case: We have a DEX file and a verify-at-runtime OAT file out of date with +// respect to the boot image. +// It shouldn't matter that the OAT file is out of date, because it is +// verify-at-runtime. +TEST_F(DexoptAnalyzerTest, OatVerifyAtRuntimeImageOutOfDate) { + std::string dex_location = GetScratchDir() + "/OatVerifyAtRuntimeImageOutOfDate.jar"; + + Copy(GetDexSrc1(), dex_location); + GenerateOatForTest(dex_location.c_str(), + CompilerFilter::kVerifyAtRuntime, + /*relocate*/true, + /*pic*/false, + /*with_alternate_image*/true); + + Verify(dex_location, CompilerFilter::kVerifyAtRuntime); + Verify(dex_location, CompilerFilter::kInterpretOnly); +} + +// Case: We have a DEX file and an ODEX file, but no OAT file. +TEST_F(DexoptAnalyzerTest, DexOdexNoOat) { + std::string dex_location = GetScratchDir() + "/DexOdexNoOat.jar"; + std::string odex_location = GetOdexDir() + "/DexOdexNoOat.odex"; + + Copy(GetDexSrc1(), dex_location); + GenerateOdexForTest(dex_location, odex_location, CompilerFilter::kSpeed); + + Verify(dex_location, CompilerFilter::kVerifyAtRuntime); + Verify(dex_location, CompilerFilter::kSpeed); +} + +// Case: We have a stripped DEX file and a PIC ODEX file, but no OAT file. +TEST_F(DexoptAnalyzerTest, StrippedDexOdexNoOat) { + std::string dex_location = GetScratchDir() + "/StrippedDexOdexNoOat.jar"; + std::string odex_location = GetOdexDir() + "/StrippedDexOdexNoOat.odex"; + + Copy(GetDexSrc1(), dex_location); + GeneratePicOdexForTest(dex_location, odex_location, CompilerFilter::kSpeed); + + // Strip the dex file + Copy(GetStrippedDexSrc1(), dex_location); + + Verify(dex_location, CompilerFilter::kSpeed); +} + +// Case: We have a stripped DEX file, a PIC ODEX file, and an out-of-date OAT file. +TEST_F(DexoptAnalyzerTest, StrippedDexOdexOat) { + std::string dex_location = GetScratchDir() + "/StrippedDexOdexOat.jar"; + std::string odex_location = GetOdexDir() + "/StrippedDexOdexOat.odex"; + + // Create the oat file from a different dex file so it looks out of date. + Copy(GetDexSrc2(), dex_location); + GenerateOatForTest(dex_location.c_str(), CompilerFilter::kSpeed); + + // Create the odex file + Copy(GetDexSrc1(), dex_location); + GeneratePicOdexForTest(dex_location, odex_location, CompilerFilter::kSpeed); + + // Strip the dex file. + Copy(GetStrippedDexSrc1(), dex_location); + + Verify(dex_location, CompilerFilter::kVerifyAtRuntime); + Verify(dex_location, CompilerFilter::kSpeed); + Verify(dex_location, CompilerFilter::kEverything); +} + +// Case: We have a stripped (or resource-only) DEX file, no ODEX file and no +// OAT file. Expect: The status is kNoDexOptNeeded. +TEST_F(DexoptAnalyzerTest, ResourceOnlyDex) { + std::string dex_location = GetScratchDir() + "/ResourceOnlyDex.jar"; + + Copy(GetStrippedDexSrc1(), dex_location); + + Verify(dex_location, CompilerFilter::kSpeed); + Verify(dex_location, CompilerFilter::kVerifyAtRuntime); + Verify(dex_location, CompilerFilter::kInterpretOnly); +} + +// Case: We have a DEX file, an ODEX file and an OAT file, where the ODEX and +// OAT files both have patch delta of 0. +TEST_F(DexoptAnalyzerTest, OdexOatOverlap) { + std::string dex_location = GetScratchDir() + "/OdexOatOverlap.jar"; + std::string odex_location = GetOdexDir() + "/OdexOatOverlap.odex"; + std::string oat_location = GetOdexDir() + "/OdexOatOverlap.oat"; + + Copy(GetDexSrc1(), dex_location); + GenerateOdexForTest(dex_location, odex_location, CompilerFilter::kSpeed); + + // Create the oat file by copying the odex so they are located in the same + // place in memory. + Copy(odex_location, oat_location); + + Verify(dex_location, CompilerFilter::kSpeed); +} + +// Case: We have a DEX file and a PIC ODEX file, but no OAT file. +TEST_F(DexoptAnalyzerTest, DexPicOdexNoOat) { + std::string dex_location = GetScratchDir() + "/DexPicOdexNoOat.jar"; + std::string odex_location = GetOdexDir() + "/DexPicOdexNoOat.odex"; + + Copy(GetDexSrc1(), dex_location); + GeneratePicOdexForTest(dex_location, odex_location, CompilerFilter::kSpeed); + + Verify(dex_location, CompilerFilter::kSpeed); + Verify(dex_location, CompilerFilter::kEverything); +} + +// Case: We have a DEX file and a VerifyAtRuntime ODEX file, but no OAT file.. +TEST_F(DexoptAnalyzerTest, DexVerifyAtRuntimeOdexNoOat) { + std::string dex_location = GetScratchDir() + "/DexVerifyAtRuntimeOdexNoOat.jar"; + std::string odex_location = GetOdexDir() + "/DexVerifyAtRuntimeOdexNoOat.odex"; + + Copy(GetDexSrc1(), dex_location); + GenerateOdexForTest(dex_location, odex_location, CompilerFilter::kVerifyAtRuntime); + + Verify(dex_location, CompilerFilter::kVerifyAtRuntime); + Verify(dex_location, CompilerFilter::kSpeed); +} + +// Case: Non-standard extension for dex file. +TEST_F(DexoptAnalyzerTest, LongDexExtension) { + std::string dex_location = GetScratchDir() + "/LongDexExtension.jarx"; + Copy(GetDexSrc1(), dex_location); + + Verify(dex_location, CompilerFilter::kSpeed); +} + +// Case: Very short, non-existent Dex location. +TEST_F(DexoptAnalyzerTest, ShortDexLocation) { + std::string dex_location = "/xx"; + + Verify(dex_location, CompilerFilter::kSpeed); +} + +} // namespace art diff --git a/oatdump/oatdump.cc b/oatdump/oatdump.cc index 148ee88669..69901c13cd 100644 --- a/oatdump/oatdump.cc +++ b/oatdump/oatdump.cc @@ -529,6 +529,12 @@ class OatDumper { } } + { + os << "OAT FILE STATS:\n"; + VariableIndentationOutputStream vios(&os); + stats_.Dump(vios); + } + os << std::flush; return success; } @@ -574,6 +580,116 @@ class OatDumper { return nullptr; } + struct Stats { + enum ByteKind { + kByteKindCode, + kByteKindQuickMethodHeader, + kByteKindCodeInfoLocationCatalog, + kByteKindCodeInfoDexRegisterMap, + kByteKindCodeInfoInlineInfo, + kByteKindCodeInfoEncoding, + kByteKindCodeInfoOther, + kByteKindStackMapNativePc, + kByteKindStackMapDexPc, + kByteKindStackMapDexRegisterMap, + kByteKindStackMapInlineInfo, + kByteKindStackMapRegisterMask, + kByteKindStackMapMask, + kByteKindStackMapOther, + kByteKindCount, + kByteKindStackMapFirst = kByteKindCodeInfoOther, + kByteKindStackMapLast = kByteKindStackMapOther, + }; + int64_t bits[kByteKindCount] = {}; + // Since code has deduplication, seen tracks already seen pointers to avoid double counting + // deduplicated code and tables. + std::unordered_set<const void*> seen; + + // Returns true if it was newly added. + bool AddBitsIfUnique(ByteKind kind, int64_t count, const void* address) { + if (seen.insert(address).second == true) { + // True means the address was not already in the set. + AddBits(kind, count); + return true; + } + return false; + } + + void AddBits(ByteKind kind, int64_t count) { + bits[kind] += count; + } + + void Dump(VariableIndentationOutputStream& os) { + const int64_t sum = std::accumulate(bits, bits + kByteKindCount, 0u); + os.Stream() << "Dumping cumulative use of " << sum / kBitsPerByte << " accounted bytes\n"; + if (sum > 0) { + const int64_t stack_map_bits = std::accumulate(bits + kByteKindStackMapFirst, + bits + kByteKindStackMapLast + 1, + 0u); + Dump(os, "Code ", bits[kByteKindCode], sum); + Dump(os, "QuickMethodHeader ", bits[kByteKindQuickMethodHeader], sum); + Dump(os, "CodeInfoEncoding ", bits[kByteKindCodeInfoEncoding], sum); + Dump(os, "CodeInfoLocationCatalog ", bits[kByteKindCodeInfoLocationCatalog], sum); + Dump(os, "CodeInfoDexRegisterMap ", bits[kByteKindCodeInfoDexRegisterMap], sum); + Dump(os, "CodeInfoInlineInfo ", bits[kByteKindCodeInfoInlineInfo], sum); + Dump(os, "CodeInfoStackMap ", stack_map_bits, sum); + { + ScopedIndentation indent1(&os); + Dump(os, + "StackMapNativePc ", + bits[kByteKindStackMapNativePc], + stack_map_bits, + "stack map"); + Dump(os, + "StackMapDexPcEncoding ", + bits[kByteKindStackMapDexPc], + stack_map_bits, + "stack map"); + Dump(os, + "StackMapDexRegisterMap ", + bits[kByteKindStackMapDexRegisterMap], + stack_map_bits, + "stack map"); + Dump(os, + "StackMapInlineInfo ", + bits[kByteKindStackMapInlineInfo], + stack_map_bits, + "stack map"); + Dump(os, + "StackMapRegisterMaskEncoding ", + bits[kByteKindStackMapRegisterMask], + stack_map_bits, + "stack map"); + Dump(os, + "StackMapMask ", + bits[kByteKindStackMapMask], + stack_map_bits, + "stack map"); + Dump(os, + "StackMapOther ", + bits[kByteKindStackMapOther], + stack_map_bits, + "stack map"); + } + } + os.Stream() << "\n" << std::flush; + } + + private: + void Dump(VariableIndentationOutputStream& os, + const char* name, + int64_t size, + int64_t total, + const char* sum_of = "total") { + const double percent = (static_cast<double>(size) / static_cast<double>(total)) * 100; + os.Stream() << StringPrintf("%s = %8" PRId64 " (%2.0f%% of %s)\n", + name, + size / kBitsPerByte, + percent, + sum_of); + } + }; + private: void AddAllOffsets() { // We don't know the length of the code for each method, but we need to know where to stop @@ -1046,7 +1162,9 @@ class OatDumper { vios->Stream() << "OatQuickMethodHeader "; uint32_t method_header_offset = oat_method.GetOatQuickMethodHeaderOffset(); const OatQuickMethodHeader* method_header = oat_method.GetOatQuickMethodHeader(); - + stats_.AddBitsIfUnique(Stats::kByteKindQuickMethodHeader, + sizeof(*method_header) * kBitsPerByte, + method_header); if (options_.absolute_addresses_) { vios->Stream() << StringPrintf("%p ", method_header); } @@ -1118,6 +1236,7 @@ class OatDumper { const void* code = oat_method.GetQuickCode(); uint32_t aligned_code_begin = AlignCodeOffset(code_offset); uint64_t aligned_code_end = aligned_code_begin + code_size; + stats_.AddBitsIfUnique(Stats::kByteKindCode, code_size * kBitsPerByte, code); if (options_.absolute_addresses_) { vios->Stream() << StringPrintf("%p ", code); @@ -1223,7 +1342,8 @@ class OatDumper { code_info.Dump(vios, oat_method.GetCodeOffset(), code_item.registers_size_, - options_.dump_code_info_stack_maps_); + options_.dump_code_info_stack_maps_, + instruction_set_); } void DumpVregLocations(std::ostream& os, const OatFile::OatMethod& oat_method, @@ -1329,21 +1449,22 @@ class OatDumper { // For identical native PCs, the order from the CodeInfo is preserved. class StackMapsHelper { public: - explicit StackMapsHelper(const uint8_t* raw_code_info) + explicit StackMapsHelper(const uint8_t* raw_code_info, InstructionSet instruction_set) : code_info_(raw_code_info), encoding_(code_info_.ExtractEncoding()), number_of_stack_maps_(code_info_.GetNumberOfStackMaps(encoding_)), indexes_(), - offset_(static_cast<size_t>(-1)), - stack_map_index_(0u) { + offset_(static_cast<uint32_t>(-1)), + stack_map_index_(0u), + instruction_set_(instruction_set) { if (number_of_stack_maps_ != 0u) { // Check if native PCs are ordered. bool ordered = true; StackMap last = code_info_.GetStackMapAt(0u, encoding_); for (size_t i = 1; i != number_of_stack_maps_; ++i) { StackMap current = code_info_.GetStackMapAt(i, encoding_); - if (last.GetNativePcOffset(encoding_.stack_map_encoding) > - current.GetNativePcOffset(encoding_.stack_map_encoding)) { + if (last.GetNativePcOffset(encoding_.stack_map_encoding, instruction_set) > + current.GetNativePcOffset(encoding_.stack_map_encoding, instruction_set)) { ordered = false; break; } @@ -1359,14 +1480,17 @@ class OatDumper { indexes_.end(), [this](size_t lhs, size_t rhs) { StackMap left = code_info_.GetStackMapAt(lhs, encoding_); - uint32_t left_pc = left.GetNativePcOffset(encoding_.stack_map_encoding); + uint32_t left_pc = left.GetNativePcOffset(encoding_.stack_map_encoding, + instruction_set_); StackMap right = code_info_.GetStackMapAt(rhs, encoding_); - uint32_t right_pc = right.GetNativePcOffset(encoding_.stack_map_encoding); + uint32_t right_pc = right.GetNativePcOffset(encoding_.stack_map_encoding, + instruction_set_); // If the PCs are the same, compare indexes to preserve the original order. return (left_pc < right_pc) || (left_pc == right_pc && lhs < rhs); }); } - offset_ = GetStackMapAt(0).GetNativePcOffset(encoding_.stack_map_encoding); + offset_ = GetStackMapAt(0).GetNativePcOffset(encoding_.stack_map_encoding, + instruction_set_); } } @@ -1378,7 +1502,7 @@ class OatDumper { return encoding_; } - size_t GetOffset() const { + uint32_t GetOffset() const { return offset_; } @@ -1389,8 +1513,9 @@ class OatDumper { void Next() { ++stack_map_index_; offset_ = (stack_map_index_ == number_of_stack_maps_) - ? static_cast<size_t>(-1) - : GetStackMapAt(stack_map_index_).GetNativePcOffset(encoding_.stack_map_encoding); + ? static_cast<uint32_t>(-1) + : GetStackMapAt(stack_map_index_).GetNativePcOffset(encoding_.stack_map_encoding, + instruction_set_); } private: @@ -1406,8 +1531,9 @@ class OatDumper { const CodeInfoEncoding encoding_; const size_t number_of_stack_maps_; dchecked_vector<size_t> indexes_; // Used if stack map native PCs are not ordered. - size_t offset_; + uint32_t offset_; size_t stack_map_index_; + const InstructionSet instruction_set_; }; void DumpCode(VariableIndentationOutputStream* vios, @@ -1423,7 +1549,61 @@ class OatDumper { return; } else if (!bad_input && IsMethodGeneratedByOptimizingCompiler(oat_method, code_item)) { // The optimizing compiler outputs its CodeInfo data in the vmap table. - StackMapsHelper helper(oat_method.GetVmapTable()); + StackMapsHelper helper(oat_method.GetVmapTable(), instruction_set_); + { + CodeInfoEncoding encoding(helper.GetEncoding()); + StackMapEncoding stack_map_encoding(encoding.stack_map_encoding); + // helper.GetCodeInfo().GetStackMapAt(0, encoding).; + const size_t num_stack_maps = encoding.number_of_stack_maps; + std::vector<uint8_t> size_vector; + encoding.Compress(&size_vector); + if (stats_.AddBitsIfUnique(Stats::kByteKindCodeInfoEncoding, + size_vector.size() * kBitsPerByte, + oat_method.GetVmapTable())) { + stats_.AddBits( + Stats::kByteKindStackMapNativePc, + stack_map_encoding.GetNativePcEncoding().BitSize() * num_stack_maps); + stats_.AddBits( + Stats::kByteKindStackMapDexPc, + stack_map_encoding.GetDexPcEncoding().BitSize() * num_stack_maps); + stats_.AddBits( + Stats::kByteKindStackMapDexRegisterMap, + stack_map_encoding.GetDexRegisterMapEncoding().BitSize() * num_stack_maps); + stats_.AddBits( + Stats::kByteKindStackMapInlineInfo, + stack_map_encoding.GetInlineInfoEncoding().BitSize() * num_stack_maps); + stats_.AddBits( + Stats::kByteKindStackMapRegisterMask, + stack_map_encoding.GetRegisterMaskEncoding().BitSize() * num_stack_maps); + const size_t stack_mask_bits = encoding.stack_map_size_in_bytes * kBitsPerByte - + stack_map_encoding.GetStackMaskBitOffset(); + stats_.AddBits( + Stats::kByteKindStackMapMask, + stack_mask_bits * num_stack_maps); + const size_t stack_map_bits = + stack_map_encoding.GetStackMaskBitOffset() + stack_mask_bits; + stats_.AddBits( + Stats::kByteKindStackMapOther, + (encoding.stack_map_size_in_bytes * kBitsPerByte - stack_map_bits) * num_stack_maps); + const size_t stack_map_bytes = helper.GetCodeInfo().GetStackMapsSize(encoding); + const size_t location_catalog_bytes = + helper.GetCodeInfo().GetDexRegisterLocationCatalogSize(encoding); + stats_.AddBits(Stats::kByteKindCodeInfoLocationCatalog, + kBitsPerByte * location_catalog_bytes); + const size_t dex_register_bytes = + helper.GetCodeInfo().GetDexRegisterMapsSize(encoding, code_item->registers_size_); + stats_.AddBits( + Stats::kByteKindCodeInfoDexRegisterMap, + kBitsPerByte * dex_register_bytes); + const size_t inline_info_bytes = + encoding.non_header_size - + stack_map_bytes - + location_catalog_bytes - + dex_register_bytes; + stats_.AddBits(Stats::kByteKindCodeInfoInlineInfo, + inline_info_bytes * kBitsPerByte); + } + } const uint8_t* quick_native_pc = reinterpret_cast<const uint8_t*>(quick_code); size_t offset = 0; while (offset < code_size) { @@ -1436,7 +1616,8 @@ class OatDumper { helper.GetCodeInfo(), helper.GetEncoding(), oat_method.GetCodeOffset(), - code_item->registers_size_); + code_item->registers_size_, + instruction_set_); do { helper.Next(); // There may be multiple stack maps at a given PC. We display only the first one. @@ -1460,6 +1641,7 @@ class OatDumper { const InstructionSet instruction_set_; std::set<uintptr_t> offsets_; Disassembler* disassembler_; + Stats stats_; }; class ImageDumper { @@ -1776,7 +1958,7 @@ class ImageDumper { os << StringPrintf("%d (0x%x)\n", field->GetShort(obj), field->GetShort(obj)); break; case Primitive::kPrimBoolean: - os << StringPrintf("%s (0x%x)\n", field->GetBoolean(obj)? "true" : "false", + os << StringPrintf("%s (0x%x)\n", field->GetBoolean(obj) ? "true" : "false", field->GetBoolean(obj)); break; case Primitive::kPrimByte: @@ -2132,7 +2314,6 @@ class ImageDumper { size_t managed_code_bytes; size_t managed_code_bytes_ignoring_deduplication; - size_t managed_to_native_code_bytes; size_t native_to_managed_code_bytes; size_t class_initializer_code_bytes; size_t large_initializer_code_bytes; @@ -2161,7 +2342,6 @@ class ImageDumper { alignment_bytes(0), managed_code_bytes(0), managed_code_bytes_ignoring_deduplication(0), - managed_to_native_code_bytes(0), native_to_managed_code_bytes(0), class_initializer_code_bytes(0), large_initializer_code_bytes(0), @@ -2359,7 +2539,6 @@ class ImageDumper { os << StringPrintf("oat_file_bytes = %8zd\n" "managed_code_bytes = %8zd (%2.0f%% of oat file bytes)\n" - "managed_to_native_code_bytes = %8zd (%2.0f%% of oat file bytes)\n" "native_to_managed_code_bytes = %8zd (%2.0f%% of oat file bytes)\n\n" "class_initializer_code_bytes = %8zd (%2.0f%% of oat file bytes)\n" "large_initializer_code_bytes = %8zd (%2.0f%% of oat file bytes)\n" @@ -2367,8 +2546,6 @@ class ImageDumper { oat_file_bytes, managed_code_bytes, PercentOfOatBytes(managed_code_bytes), - managed_to_native_code_bytes, - PercentOfOatBytes(managed_to_native_code_bytes), native_to_managed_code_bytes, PercentOfOatBytes(native_to_managed_code_bytes), class_initializer_code_bytes, diff --git a/oatdump/oatdump_test.cc b/oatdump/oatdump_test.cc index e77d03bae7..ba57d1860c 100644 --- a/oatdump/oatdump_test.cc +++ b/oatdump/oatdump_test.cc @@ -102,6 +102,7 @@ class OatDumpTest : public CommonRuntimeTest { // Code and dex code do not show up if list only. expected_prefixes.push_back("DEX CODE:"); expected_prefixes.push_back("CODE:"); + expected_prefixes.push_back("CodeInfoEncoding"); } if (mode == kModeArt) { exec_argv.push_back("--image=" + core_art_location_); diff --git a/profman/profile_assistant.h b/profman/profile_assistant.h index d3c75b817a..be703abda8 100644 --- a/profman/profile_assistant.h +++ b/profman/profile_assistant.h @@ -21,7 +21,7 @@ #include <vector> #include "base/scoped_flock.h" -#include "jit/offline_profiling_info.h" +#include "jit/profile_compilation_info.h" namespace art { diff --git a/profman/profile_assistant_test.cc b/profman/profile_assistant_test.cc index 776c31a662..2f40fef42e 100644 --- a/profman/profile_assistant_test.cc +++ b/profman/profile_assistant_test.cc @@ -19,7 +19,7 @@ #include "base/unix_file/fd_file.h" #include "common_runtime_test.h" #include "profile_assistant.h" -#include "jit/offline_profiling_info.h" +#include "jit/profile_compilation_info.h" #include "utils.h" namespace art { diff --git a/profman/profman.cc b/profman/profman.cc index e5384078f1..ffebb6a2ea 100644 --- a/profman/profman.cc +++ b/profman/profman.cc @@ -34,7 +34,7 @@ #include "base/time_utils.h" #include "base/unix_file/fd_file.h" #include "dex_file.h" -#include "jit/offline_profiling_info.h" +#include "jit/profile_compilation_info.h" #include "runtime.h" #include "utils.h" #include "zip_archive.h" diff --git a/runtime/Android.bp b/runtime/Android.bp index 86019bf71c..7f985139bb 100644 --- a/runtime/Android.bp +++ b/runtime/Android.bp @@ -112,7 +112,7 @@ cc_defaults { "jit/debugger_interface.cc", "jit/jit.cc", "jit/jit_code_cache.cc", - "jit/offline_profiling_info.cc", + "jit/profile_compilation_info.cc", "jit/profiling_info.cc", "jit/profile_saver.cc", "jni_internal.cc", @@ -184,6 +184,7 @@ cc_defaults { "reference_table.cc", "reflection.cc", "runtime.cc", + "runtime_callbacks.cc", "runtime_options.cc", "signal_catcher.cc", "stack.cc", @@ -473,10 +474,14 @@ art_cc_library { art_cc_library { name: "libart-runtime-gtest", defaults: ["libart-gtest-defaults"], - srcs: ["common_runtime_test.cc"], + srcs: [ + "common_runtime_test.cc", + "dexopt_test.cc" + ], shared_libs: [ "libartd", "libbase", + "libbacktrace" ], } @@ -563,6 +568,7 @@ art_cc_test { "parsed_options_test.cc", "prebuilt_tools_test.cc", "reference_table_test.cc", + "runtime_callbacks_test.cc", "thread_pool_test.cc", "transaction_test.cc", "type_lookup_table_test.cc", diff --git a/runtime/arch/arm/instruction_set_features_arm.cc b/runtime/arch/arm/instruction_set_features_arm.cc index 6c2c81576e..8384460171 100644 --- a/runtime/arch/arm/instruction_set_features_arm.cc +++ b/runtime/arch/arm/instruction_set_features_arm.cc @@ -31,6 +31,7 @@ #if defined(__arm__) extern "C" bool artCheckForArmSdivInstruction(); +extern "C" bool artCheckForArmv8AInstructions(); #endif namespace art { @@ -39,22 +40,34 @@ using android::base::StringPrintf; ArmFeaturesUniquePtr ArmInstructionSetFeatures::FromVariant( const std::string& variant, std::string* error_msg) { + static const char* arm_variants_with_armv8a[] = { + "cortex-a32", + "cortex-a35", + "cortex-a53", + "cortex-a53.a57", + "cortex-a53.a72", + "cortex-a57", + "cortex-a72", + "cortex-a73", + "exynos-m1", + "denver", + "kryo" + }; + bool has_armv8a = FindVariantInArray(arm_variants_with_armv8a, + arraysize(arm_variants_with_armv8a), + variant); + // Look for variants that have divide support. static const char* arm_variants_with_div[] = { "cortex-a7", "cortex-a12", "cortex-a15", "cortex-a17", - "cortex-a53", - "cortex-a53.a57", - "cortex-a57", - "denver", "krait", }; - - bool has_div = FindVariantInArray(arm_variants_with_div, - arraysize(arm_variants_with_div), - variant); + bool has_div = has_armv8a || FindVariantInArray(arm_variants_with_div, + arraysize(arm_variants_with_div), + variant); // Look for variants that have LPAE support. static const char* arm_variants_with_lpae[] = { @@ -62,17 +75,13 @@ ArmFeaturesUniquePtr ArmInstructionSetFeatures::FromVariant( "cortex-a12", "cortex-a15", "cortex-a17", - "cortex-a53", - "cortex-a53.a57", - "cortex-a57", - "denver", "krait", }; - bool has_lpae = FindVariantInArray(arm_variants_with_lpae, - arraysize(arm_variants_with_lpae), - variant); + bool has_atomic_ldrd_strd = has_armv8a || FindVariantInArray(arm_variants_with_lpae, + arraysize(arm_variants_with_lpae), + variant); - if (has_div == false && has_lpae == false) { + if (has_armv8a == false && has_div == false && has_atomic_ldrd_strd == false) { static const char* arm_variants_with_default_features[] = { "cortex-a5", "cortex-a8", @@ -92,34 +101,48 @@ ArmFeaturesUniquePtr ArmInstructionSetFeatures::FromVariant( << ") using conservative defaults"; } } - return ArmFeaturesUniquePtr(new ArmInstructionSetFeatures(has_div, has_lpae)); + return ArmFeaturesUniquePtr(new ArmInstructionSetFeatures(has_div, + has_atomic_ldrd_strd, + has_armv8a)); } ArmFeaturesUniquePtr ArmInstructionSetFeatures::FromBitmap(uint32_t bitmap) { bool has_div = (bitmap & kDivBitfield) != 0; bool has_atomic_ldrd_strd = (bitmap & kAtomicLdrdStrdBitfield) != 0; - return ArmFeaturesUniquePtr(new ArmInstructionSetFeatures(has_div, has_atomic_ldrd_strd)); + bool has_armv8a = (bitmap & kARMv8A) != 0; + return ArmFeaturesUniquePtr(new ArmInstructionSetFeatures(has_div, + has_atomic_ldrd_strd, + has_armv8a)); } ArmFeaturesUniquePtr ArmInstructionSetFeatures::FromCppDefines() { -#if defined(__ARM_ARCH_EXT_IDIV__) +// Note: This will not work for now since we still build the 32-bit as __ARCH_ARM_7A__. +#if defined(__ARM_ARCH_8A__) + const bool has_armv8a = true; +#else + const bool has_armv8a = false; +#endif +#if defined (__ARM_ARCH_8A__) || defined(__ARM_ARCH_EXT_IDIV__) const bool has_div = true; #else const bool has_div = false; #endif -#if defined(__ARM_FEATURE_LPAE) - const bool has_lpae = true; +#if defined (__ARM_ARCH_8A__) || defined(__ARM_FEATURE_LPAE) + const bool has_atomic_ldrd_strd = true; #else - const bool has_lpae = false; + const bool has_atomic_ldrd_strd = false; #endif - return ArmFeaturesUniquePtr(new ArmInstructionSetFeatures(has_div, has_lpae)); + return ArmFeaturesUniquePtr(new ArmInstructionSetFeatures(has_div, + has_atomic_ldrd_strd, + has_armv8a)); } ArmFeaturesUniquePtr ArmInstructionSetFeatures::FromCpuInfo() { // Look in /proc/cpuinfo for features we need. Only use this when we can guarantee that // the kernel puts the appropriate feature flags in here. Sometimes it doesn't. - bool has_lpae = false; + bool has_atomic_ldrd_strd = false; bool has_div = false; + bool has_armv8a = false; std::ifstream in("/proc/cpuinfo"); if (!in.fail()) { @@ -137,21 +160,33 @@ ArmFeaturesUniquePtr ArmInstructionSetFeatures::FromCpuInfo() { has_div = true; } if (line.find("lpae") != std::string::npos) { - has_lpae = true; + has_atomic_ldrd_strd = true; } } + if (line.find("architecture") != std::string::npos + && line.find(": 8") != std::string::npos) { + LOG(INFO) << "found architecture ARMv8"; + // Android is only run on A cores, so ARMv8 implies ARMv8-A. + has_armv8a = true; + // ARMv8 CPUs have LPAE and div support. + has_div = true; + has_atomic_ldrd_strd = true; + } } } in.close(); } else { LOG(ERROR) << "Failed to open /proc/cpuinfo"; } - return ArmFeaturesUniquePtr(new ArmInstructionSetFeatures(has_div, has_lpae)); + return ArmFeaturesUniquePtr(new ArmInstructionSetFeatures(has_div, + has_atomic_ldrd_strd, + has_armv8a)); } ArmFeaturesUniquePtr ArmInstructionSetFeatures::FromHwcap() { bool has_div = false; - bool has_lpae = false; + bool has_atomic_ldrd_strd = false; + bool has_armv8a = false; #if defined(ART_TARGET_ANDROID) && defined(__arm__) uint64_t hwcaps = getauxval(AT_HWCAP); @@ -163,18 +198,27 @@ ArmFeaturesUniquePtr ArmInstructionSetFeatures::FromHwcap() { has_div = true; } if ((hwcaps & HWCAP_LPAE) != 0) { - has_lpae = true; + has_atomic_ldrd_strd = true; + } + // TODO: Fix this once FPMISC makes it upstream. + // For now we detect if we run on an ARMv8 CPU by looking for CRC32 and SHA1 + // (only available on ARMv8 CPUs). + if ((hwcaps & HWCAP2_CRC32) != 0 && (hwcaps & HWCAP2_SHA1) != 0) { + has_armv8a = true; } #endif - return ArmFeaturesUniquePtr(new ArmInstructionSetFeatures(has_div, has_lpae)); + return ArmFeaturesUniquePtr(new ArmInstructionSetFeatures(has_div, + has_atomic_ldrd_strd, + has_armv8a)); } // A signal handler called by a fault for an illegal instruction. We record the fact in r0 // and then increment the PC in the signal context to return to the next instruction. We know the -// instruction is an sdiv (4 bytes long). -static void bad_divide_inst_handle(int signo ATTRIBUTE_UNUSED, siginfo_t* si ATTRIBUTE_UNUSED, - void* data) { +// instruction is 4 bytes long. +static void bad_instr_handle(int signo ATTRIBUTE_UNUSED, + siginfo_t* si ATTRIBUTE_UNUSED, + void* data) { #if defined(__arm__) struct ucontext *uc = (struct ucontext *)data; struct sigcontext *sc = &uc->uc_mcontext; @@ -190,15 +234,19 @@ ArmFeaturesUniquePtr ArmInstructionSetFeatures::FromAssembly() { // instruction. If we get a SIGILL then it's not supported. struct sigaction sa, osa; sa.sa_flags = SA_ONSTACK | SA_RESTART | SA_SIGINFO; - sa.sa_sigaction = bad_divide_inst_handle; + sa.sa_sigaction = bad_instr_handle; sigemptyset(&sa.sa_mask); sigaction(SIGILL, &sa, &osa); bool has_div = false; + bool has_armv8a = false; #if defined(__arm__) if (artCheckForArmSdivInstruction()) { has_div = true; } + if (artCheckForArmv8AInstructions()) { + has_armv8a = true; + } #endif // Restore the signal handler. @@ -207,11 +255,13 @@ ArmFeaturesUniquePtr ArmInstructionSetFeatures::FromAssembly() { // Use compile time features to "detect" LPAE support. // TODO: write an assembly LPAE support test. #if defined(__ARM_FEATURE_LPAE) - const bool has_lpae = true; + const bool has_atomic_ldrd_strd = true; #else - const bool has_lpae = false; + const bool has_atomic_ldrd_strd = false; #endif - return ArmFeaturesUniquePtr(new ArmInstructionSetFeatures(has_div, has_lpae)); + return ArmFeaturesUniquePtr(new ArmInstructionSetFeatures(has_div, + has_atomic_ldrd_strd, + has_armv8a)); } bool ArmInstructionSetFeatures::Equals(const InstructionSetFeatures* other) const { @@ -219,13 +269,26 @@ bool ArmInstructionSetFeatures::Equals(const InstructionSetFeatures* other) cons return false; } const ArmInstructionSetFeatures* other_as_arm = other->AsArmInstructionSetFeatures(); - return has_div_ == other_as_arm->has_div_ && - has_atomic_ldrd_strd_ == other_as_arm->has_atomic_ldrd_strd_; + return has_div_ == other_as_arm->has_div_ + && has_atomic_ldrd_strd_ == other_as_arm->has_atomic_ldrd_strd_ + && has_armv8a_ == other_as_arm->has_armv8a_; +} + +bool ArmInstructionSetFeatures::HasAtLeast(const InstructionSetFeatures* other) const { + if (kArm != other->GetInstructionSet()) { + return false; + } + const ArmInstructionSetFeatures* other_as_arm = other->AsArmInstructionSetFeatures(); + + return (has_div_ || (has_div_ == other_as_arm->has_div_)) + && (has_atomic_ldrd_strd_ || (has_atomic_ldrd_strd_ == other_as_arm->has_atomic_ldrd_strd_)) + && (has_armv8a_ || (has_armv8a_ == other_as_arm->has_armv8a_)); } uint32_t ArmInstructionSetFeatures::AsBitmap() const { - return (has_div_ ? kDivBitfield : 0) | - (has_atomic_ldrd_strd_ ? kAtomicLdrdStrdBitfield : 0); + return (has_div_ ? kDivBitfield : 0) + | (has_atomic_ldrd_strd_ ? kAtomicLdrdStrdBitfield : 0) + | (has_armv8a_ ? kARMv8A : 0); } std::string ArmInstructionSetFeatures::GetFeatureString() const { @@ -240,6 +303,11 @@ std::string ArmInstructionSetFeatures::GetFeatureString() const { } else { result += ",-atomic_ldrd_strd"; } + if (has_armv8a_) { + result += ",armv8a"; + } else { + result += ",-armv8a"; + } return result; } @@ -248,6 +316,7 @@ ArmInstructionSetFeatures::AddFeaturesFromSplitString( const std::vector<std::string>& features, std::string* error_msg) const { bool has_atomic_ldrd_strd = has_atomic_ldrd_strd_; bool has_div = has_div_; + bool has_armv8a = has_armv8a_; for (auto i = features.begin(); i != features.end(); i++) { std::string feature = android::base::Trim(*i); if (feature == "div") { @@ -258,13 +327,17 @@ ArmInstructionSetFeatures::AddFeaturesFromSplitString( has_atomic_ldrd_strd = true; } else if (feature == "-atomic_ldrd_strd") { has_atomic_ldrd_strd = false; + } else if (feature == "armv8a") { + has_armv8a = true; + } else if (feature == "-armv8a") { + has_armv8a = false; } else { *error_msg = StringPrintf("Unknown instruction set feature: '%s'", feature.c_str()); return nullptr; } } return std::unique_ptr<const InstructionSetFeatures>( - new ArmInstructionSetFeatures(has_div, has_atomic_ldrd_strd)); + new ArmInstructionSetFeatures(has_div, has_atomic_ldrd_strd, has_armv8a)); } } // namespace art diff --git a/runtime/arch/arm/instruction_set_features_arm.h b/runtime/arch/arm/instruction_set_features_arm.h index 11f8bf0117..f438a768a5 100644 --- a/runtime/arch/arm/instruction_set_features_arm.h +++ b/runtime/arch/arm/instruction_set_features_arm.h @@ -49,6 +49,8 @@ class ArmInstructionSetFeatures FINAL : public InstructionSetFeatures { bool Equals(const InstructionSetFeatures* other) const OVERRIDE; + bool HasAtLeast(const InstructionSetFeatures* other) const OVERRIDE; + InstructionSet GetInstructionSet() const OVERRIDE { return kArm; } @@ -69,6 +71,11 @@ class ArmInstructionSetFeatures FINAL : public InstructionSetFeatures { return has_atomic_ldrd_strd_; } + // Are ARMv8-A instructions available? + bool HasARMv8AInstructions() const { + return has_armv8a_; + } + virtual ~ArmInstructionSetFeatures() {} protected: @@ -78,19 +85,24 @@ class ArmInstructionSetFeatures FINAL : public InstructionSetFeatures { std::string* error_msg) const OVERRIDE; private: - ArmInstructionSetFeatures(bool has_div, bool has_atomic_ldrd_strd) + ArmInstructionSetFeatures(bool has_div, + bool has_atomic_ldrd_strd, + bool has_armv8a) : InstructionSetFeatures(), - has_div_(has_div), has_atomic_ldrd_strd_(has_atomic_ldrd_strd) { - } + has_div_(has_div), + has_atomic_ldrd_strd_(has_atomic_ldrd_strd), + has_armv8a_(has_armv8a) {} // Bitmap positions for encoding features as a bitmap. enum { kDivBitfield = 1 << 0, kAtomicLdrdStrdBitfield = 1 << 1, + kARMv8A = 1 << 2, }; const bool has_div_; const bool has_atomic_ldrd_strd_; + const bool has_armv8a_; DISALLOW_COPY_AND_ASSIGN(ArmInstructionSetFeatures); }; diff --git a/runtime/arch/arm/instruction_set_features_arm_test.cc b/runtime/arch/arm/instruction_set_features_arm_test.cc index 697ca9015e..6d5dd6d50d 100644 --- a/runtime/arch/arm/instruction_set_features_arm_test.cc +++ b/runtime/arch/arm/instruction_set_features_arm_test.cc @@ -31,7 +31,7 @@ TEST(ArmInstructionSetFeaturesTest, ArmFeaturesFromVariant) { EXPECT_TRUE(krait_features->Equals(krait_features.get())); EXPECT_TRUE(krait_features->AsArmInstructionSetFeatures()->HasDivideInstruction()); EXPECT_TRUE(krait_features->AsArmInstructionSetFeatures()->HasAtomicLdrdAndStrd()); - EXPECT_STREQ("div,atomic_ldrd_strd", krait_features->GetFeatureString().c_str()); + EXPECT_STREQ("div,atomic_ldrd_strd,-armv8a", krait_features->GetFeatureString().c_str()); EXPECT_EQ(krait_features->AsBitmap(), 3U); // Build features for a 32-bit ARM denver processor. @@ -40,12 +40,13 @@ TEST(ArmInstructionSetFeaturesTest, ArmFeaturesFromVariant) { ASSERT_TRUE(denver_features.get() != nullptr) << error_msg; EXPECT_TRUE(denver_features->Equals(denver_features.get())); - EXPECT_TRUE(denver_features->Equals(krait_features.get())); - EXPECT_TRUE(krait_features->Equals(denver_features.get())); + EXPECT_TRUE(denver_features->HasAtLeast(krait_features.get())); + EXPECT_FALSE(krait_features->Equals(denver_features.get())); + EXPECT_FALSE(krait_features->HasAtLeast(denver_features.get())); EXPECT_TRUE(denver_features->AsArmInstructionSetFeatures()->HasDivideInstruction()); EXPECT_TRUE(denver_features->AsArmInstructionSetFeatures()->HasAtomicLdrdAndStrd()); - EXPECT_STREQ("div,atomic_ldrd_strd", denver_features->GetFeatureString().c_str()); - EXPECT_EQ(denver_features->AsBitmap(), 3U); + EXPECT_STREQ("div,atomic_ldrd_strd,armv8a", denver_features->GetFeatureString().c_str()); + EXPECT_EQ(denver_features->AsBitmap(), 7U); // Build features for a 32-bit ARMv7 processor. std::unique_ptr<const InstructionSetFeatures> generic_features( @@ -57,7 +58,7 @@ TEST(ArmInstructionSetFeaturesTest, ArmFeaturesFromVariant) { EXPECT_FALSE(krait_features->Equals(generic_features.get())); EXPECT_FALSE(generic_features->AsArmInstructionSetFeatures()->HasDivideInstruction()); EXPECT_FALSE(generic_features->AsArmInstructionSetFeatures()->HasAtomicLdrdAndStrd()); - EXPECT_STREQ("-div,-atomic_ldrd_strd", generic_features->GetFeatureString().c_str()); + EXPECT_STREQ("-div,-atomic_ldrd_strd,-armv8a", generic_features->GetFeatureString().c_str()); EXPECT_EQ(generic_features->AsBitmap(), 0U); // ARM6 is not a supported architecture variant. @@ -82,21 +83,22 @@ TEST(ArmInstructionSetFeaturesTest, ArmAddFeaturesFromString) { EXPECT_TRUE(krait_features->Equals(krait_features.get())); EXPECT_TRUE(krait_features->AsArmInstructionSetFeatures()->HasDivideInstruction()); EXPECT_TRUE(krait_features->AsArmInstructionSetFeatures()->HasAtomicLdrdAndStrd()); - EXPECT_STREQ("div,atomic_ldrd_strd", krait_features->GetFeatureString().c_str()); + EXPECT_STREQ("div,atomic_ldrd_strd,-armv8a", krait_features->GetFeatureString().c_str()); EXPECT_EQ(krait_features->AsBitmap(), 3U); // Build features for a 32-bit ARM processor with LPAE and div flipped. std::unique_ptr<const InstructionSetFeatures> denver_features( - base_features->AddFeaturesFromString("div,atomic_ldrd_strd", &error_msg)); + base_features->AddFeaturesFromString("div,atomic_ldrd_strd,armv8a", &error_msg)); ASSERT_TRUE(denver_features.get() != nullptr) << error_msg; EXPECT_TRUE(denver_features->Equals(denver_features.get())); - EXPECT_TRUE(denver_features->Equals(krait_features.get())); - EXPECT_TRUE(krait_features->Equals(denver_features.get())); + EXPECT_FALSE(denver_features->Equals(krait_features.get())); + EXPECT_TRUE(denver_features->HasAtLeast(krait_features.get())); + EXPECT_FALSE(krait_features->Equals(denver_features.get())); EXPECT_TRUE(denver_features->AsArmInstructionSetFeatures()->HasDivideInstruction()); EXPECT_TRUE(denver_features->AsArmInstructionSetFeatures()->HasAtomicLdrdAndStrd()); - EXPECT_STREQ("div,atomic_ldrd_strd", denver_features->GetFeatureString().c_str()); - EXPECT_EQ(denver_features->AsBitmap(), 3U); + EXPECT_STREQ("div,atomic_ldrd_strd,armv8a", denver_features->GetFeatureString().c_str()); + EXPECT_EQ(denver_features->AsBitmap(), 7U); // Build features for a 32-bit default ARM processor. std::unique_ptr<const InstructionSetFeatures> generic_features( @@ -108,7 +110,7 @@ TEST(ArmInstructionSetFeaturesTest, ArmAddFeaturesFromString) { EXPECT_FALSE(krait_features->Equals(generic_features.get())); EXPECT_FALSE(generic_features->AsArmInstructionSetFeatures()->HasDivideInstruction()); EXPECT_FALSE(generic_features->AsArmInstructionSetFeatures()->HasAtomicLdrdAndStrd()); - EXPECT_STREQ("-div,-atomic_ldrd_strd", generic_features->GetFeatureString().c_str()); + EXPECT_STREQ("-div,-atomic_ldrd_strd,-armv8a", generic_features->GetFeatureString().c_str()); EXPECT_EQ(generic_features->AsBitmap(), 0U); } diff --git a/runtime/arch/arm/instruction_set_features_assembly_tests.S b/runtime/arch/arm/instruction_set_features_assembly_tests.S index c1086df0f6..5c7f2025c2 100644 --- a/runtime/arch/arm/instruction_set_features_assembly_tests.S +++ b/runtime/arch/arm/instruction_set_features_assembly_tests.S @@ -17,22 +17,49 @@ #include "asm_support_arm.S" .section .text -// This function is used to check for the CPU's support for the sdiv -// instruction at runtime. It will either return the value 1 or -// will cause an invalid instruction trap (SIGILL signal). The -// caller must arrange for the signal handler to set the r0 -// register to 0 and move the pc forward by 4 bytes (to skip -// the invalid instruction). +// These functions are used to check for the CPU's support for the sdiv and +// ARMv8-A instructions at runtime. They will either return the value 1 or will +// cause an invalid instruction trap (SIGILL signal), for which the signal handler +// (bad_instr_handle(), in instruction_set_features_arm.cc) must arrange to set +// the r0 register to 0 and move the pc forward by 4 bytes (to skip the invalid +// instruction). +// Note: For ARM T32, instructions can be either 16b or 32b, but bad_instr_handle() +// deals only with 32b instructions for now. + ENTRY artCheckForArmSdivInstruction mov r1,#1 - // depending on the architecture, the assembler will not allow an + // Depending on the architecture, the assembler will not allow an // sdiv instruction, so we will have to output the bytes directly. - // sdiv r0,r1,r1 is two words: 0xfb91 0xf1f0. We need little endian. - .byte 0x91,0xfb,0xf1,0xf0 + // The T32 encoding for sdiv r0,r1,r1 is two 16bit words: 0xfb91 0xf0f1, with little endianness. + .byte 0x91,0xfb + .byte 0xf1,0xf0 - // if the divide worked, r0 will have the value #1 (result of sdiv). + // If the divide worked, r0 will have the value #1 (result of sdiv). // It will have 0 otherwise (set by the signal handler) // the value is just returned from this function. bx lr END artCheckForArmSdivInstruction + +ENTRY artCheckForArmv8AInstructions + // Depending on the architecture, the assembler will not allow a + // `vrint` instruction, so we will have to output the bytes directly. + + // Move `true` into the result register. The signal handler will set it to 0 + // if execution of the instruction below fails + mov r0,#1 + + // Store S0 in the caller saved R1. If the instruction below succeeds, S0 will + // be clobbered but it will not be caller saved (ARM still uses soft FP). + vmov r1, s0 + + // The T32 encoding for vrinta.f32.f32 s0,s0 is two 16bit words: 0xfeb8,0x0a40, with little + // endianness. + .byte 0xb8,0xfe + .byte 0x40,0x0a + + // Restore S0 (see above comment). + vmov s0, r1 + + bx lr +END artCheckForArmv8AInstructions diff --git a/runtime/arch/arm/quick_method_frame_info_arm.h b/runtime/arch/arm/quick_method_frame_info_arm.h index 4b23c77bc5..35f1948138 100644 --- a/runtime/arch/arm/quick_method_frame_info_arm.h +++ b/runtime/arch/arm/quick_method_frame_info_arm.h @@ -62,7 +62,7 @@ constexpr uint32_t ArmCalleeSaveCoreSpills(Runtime::CalleeSaveType type) { constexpr uint32_t ArmCalleeSaveFpSpills(Runtime::CalleeSaveType type) { return kArmCalleeSaveFpAlwaysSpills | kArmCalleeSaveFpRefSpills | - (type == Runtime::kSaveRefsAndArgs ? kArmCalleeSaveFpArgSpills: 0) | + (type == Runtime::kSaveRefsAndArgs ? kArmCalleeSaveFpArgSpills : 0) | (type == Runtime::kSaveAllCalleeSaves ? kArmCalleeSaveFpAllSpills : 0) | (type == Runtime::kSaveEverything ? kArmCalleeSaveFpEverythingSpills : 0); } diff --git a/runtime/arch/arm64/instruction_set_features_arm64.cc b/runtime/arch/arm64/instruction_set_features_arm64.cc index c59874332f..01bd177221 100644 --- a/runtime/arch/arm64/instruction_set_features_arm64.cc +++ b/runtime/arch/arm64/instruction_set_features_arm64.cc @@ -33,7 +33,16 @@ Arm64FeaturesUniquePtr Arm64InstructionSetFeatures::FromVariant( const std::string& variant, std::string* error_msg) { // Look for variants that need a fix for a53 erratum 835769. static const char* arm64_variants_with_a53_835769_bug[] = { - "default", "generic", "cortex-a53" // Pessimistically assume all generic ARM64s are A53s. + // Pessimistically assume all generic CPUs are cortex-a53. + "default", + "generic", + "cortex-a53", + "cortex-a53.a57", + "cortex-a53.a72", + // Pessimistically assume all "big" cortex CPUs are paired with a cortex-a53. + "cortex-a57", + "cortex-a72", + "cortex-a73", }; bool needs_a53_835769_fix = FindVariantInArray(arm64_variants_with_a53_835769_bug, arraysize(arm64_variants_with_a53_835769_bug), @@ -42,7 +51,10 @@ Arm64FeaturesUniquePtr Arm64InstructionSetFeatures::FromVariant( if (!needs_a53_835769_fix) { // Check to see if this is an expected variant. static const char* arm64_known_variants[] = { - "denver64", "kryo", "exynos-m1" + "cortex-a35", + "exynos-m1", + "denver64", + "kryo" }; if (!FindVariantInArray(arm64_known_variants, arraysize(arm64_known_variants), variant)) { std::ostringstream os; diff --git a/runtime/arch/arm64/instruction_set_features_arm64_test.cc b/runtime/arch/arm64/instruction_set_features_arm64_test.cc index cefa4993c8..91cb58fedf 100644 --- a/runtime/arch/arm64/instruction_set_features_arm64_test.cc +++ b/runtime/arch/arm64/instruction_set_features_arm64_test.cc @@ -30,6 +30,40 @@ TEST(Arm64InstructionSetFeaturesTest, Arm64Features) { EXPECT_TRUE(arm64_features->Equals(arm64_features.get())); EXPECT_STREQ("a53", arm64_features->GetFeatureString().c_str()); EXPECT_EQ(arm64_features->AsBitmap(), 1U); + + std::unique_ptr<const InstructionSetFeatures> cortex_a57_features( + InstructionSetFeatures::FromVariant(kArm64, "cortex-a57", &error_msg)); + ASSERT_TRUE(cortex_a57_features.get() != nullptr) << error_msg; + EXPECT_EQ(cortex_a57_features->GetInstructionSet(), kArm64); + EXPECT_TRUE(cortex_a57_features->Equals(cortex_a57_features.get())); + EXPECT_STREQ("a53", cortex_a57_features->GetFeatureString().c_str()); + EXPECT_EQ(cortex_a57_features->AsBitmap(), 1U); + + std::unique_ptr<const InstructionSetFeatures> cortex_a73_features( + InstructionSetFeatures::FromVariant(kArm64, "cortex-a73", &error_msg)); + ASSERT_TRUE(cortex_a73_features.get() != nullptr) << error_msg; + EXPECT_EQ(cortex_a73_features->GetInstructionSet(), kArm64); + EXPECT_TRUE(cortex_a73_features->Equals(cortex_a73_features.get())); + EXPECT_STREQ("a53", cortex_a73_features->GetFeatureString().c_str()); + EXPECT_EQ(cortex_a73_features->AsBitmap(), 1U); + + std::unique_ptr<const InstructionSetFeatures> cortex_a35_features( + InstructionSetFeatures::FromVariant(kArm64, "cortex-a35", &error_msg)); + ASSERT_TRUE(cortex_a35_features.get() != nullptr) << error_msg; + EXPECT_EQ(cortex_a35_features->GetInstructionSet(), kArm64); + EXPECT_TRUE(cortex_a35_features->Equals(cortex_a35_features.get())); + EXPECT_STREQ("-a53", cortex_a35_features->GetFeatureString().c_str()); + EXPECT_EQ(cortex_a35_features->AsBitmap(), 0U); + + std::unique_ptr<const InstructionSetFeatures> kryo_features( + InstructionSetFeatures::FromVariant(kArm64, "kryo", &error_msg)); + ASSERT_TRUE(kryo_features.get() != nullptr) << error_msg; + EXPECT_EQ(kryo_features->GetInstructionSet(), kArm64); + EXPECT_TRUE(kryo_features->Equals(kryo_features.get())); + EXPECT_TRUE(kryo_features->Equals(cortex_a35_features.get())); + EXPECT_FALSE(kryo_features->Equals(cortex_a57_features.get())); + EXPECT_STREQ("-a53", kryo_features->GetFeatureString().c_str()); + EXPECT_EQ(kryo_features->AsBitmap(), 0U); } } // namespace art diff --git a/runtime/arch/arm64/quick_method_frame_info_arm64.h b/runtime/arch/arm64/quick_method_frame_info_arm64.h index 36f283bc91..32d9d08739 100644 --- a/runtime/arch/arm64/quick_method_frame_info_arm64.h +++ b/runtime/arch/arm64/quick_method_frame_info_arm64.h @@ -85,7 +85,7 @@ constexpr uint32_t Arm64CalleeSaveCoreSpills(Runtime::CalleeSaveType type) { constexpr uint32_t Arm64CalleeSaveFpSpills(Runtime::CalleeSaveType type) { return kArm64CalleeSaveFpAlwaysSpills | kArm64CalleeSaveFpRefSpills | - (type == Runtime::kSaveRefsAndArgs ? kArm64CalleeSaveFpArgSpills: 0) | + (type == Runtime::kSaveRefsAndArgs ? kArm64CalleeSaveFpArgSpills : 0) | (type == Runtime::kSaveAllCalleeSaves ? kArm64CalleeSaveFpAllSpills : 0) | (type == Runtime::kSaveEverything ? kArm64CalleeSaveFpEverythingSpills : 0); } diff --git a/runtime/arch/code_offset.h b/runtime/arch/code_offset.h new file mode 100644 index 0000000000..ab04b1eaa7 --- /dev/null +++ b/runtime/arch/code_offset.h @@ -0,0 +1,92 @@ +/* + * 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. + */ + +#ifndef ART_RUNTIME_ARCH_CODE_OFFSET_H_ +#define ART_RUNTIME_ARCH_CODE_OFFSET_H_ + +#include <iosfwd> + +#include "base/bit_utils.h" +#include "base/logging.h" +#include "instruction_set.h" + +namespace art { + +// CodeOffset is a holder for compressed code offsets. Since some architectures have alignment +// requirements it is possible to compress code offsets to reduce stack map sizes. +class CodeOffset { + public: + ALWAYS_INLINE static CodeOffset FromOffset(uint32_t offset, InstructionSet isa = kRuntimeISA) { + return CodeOffset(offset / GetInstructionSetInstructionAlignment(isa)); + } + + ALWAYS_INLINE static CodeOffset FromCompressedOffset(uint32_t offset) { + return CodeOffset(offset); + } + + ALWAYS_INLINE uint32_t Uint32Value(InstructionSet isa = kRuntimeISA) const { + uint32_t decoded = value_ * GetInstructionSetInstructionAlignment(isa); + DCHECK_GE(decoded, value_) << "Integer overflow"; + return decoded; + } + + // Return compressed internal value. + ALWAYS_INLINE uint32_t CompressedValue() const { + return value_; + } + + ALWAYS_INLINE CodeOffset() = default; + ALWAYS_INLINE CodeOffset(const CodeOffset&) = default; + ALWAYS_INLINE CodeOffset& operator=(const CodeOffset&) = default; + ALWAYS_INLINE CodeOffset& operator=(CodeOffset&&) = default; + + private: + ALWAYS_INLINE explicit CodeOffset(uint32_t value) : value_(value) {} + + uint32_t value_ = 0u; +}; + +inline bool operator==(const CodeOffset& a, const CodeOffset& b) { + return a.CompressedValue() == b.CompressedValue(); +} + +inline bool operator!=(const CodeOffset& a, const CodeOffset& b) { + return !(a == b); +} + +inline bool operator<(const CodeOffset& a, const CodeOffset& b) { + return a.CompressedValue() < b.CompressedValue(); +} + +inline bool operator<=(const CodeOffset& a, const CodeOffset& b) { + return a.CompressedValue() <= b.CompressedValue(); +} + +inline bool operator>(const CodeOffset& a, const CodeOffset& b) { + return a.CompressedValue() > b.CompressedValue(); +} + +inline bool operator>=(const CodeOffset& a, const CodeOffset& b) { + return a.CompressedValue() >= b.CompressedValue(); +} + +inline std::ostream& operator<<(std::ostream& os, const CodeOffset& offset) { + return os << offset.Uint32Value(); +} + +} // namespace art + +#endif // ART_RUNTIME_ARCH_CODE_OFFSET_H_ diff --git a/runtime/arch/instruction_set.h b/runtime/arch/instruction_set.h index 4a8bea45e5..99aea62468 100644 --- a/runtime/arch/instruction_set.h +++ b/runtime/arch/instruction_set.h @@ -75,6 +75,14 @@ static constexpr size_t kMipsAlignment = 8; // X86 instruction alignment. This is the recommended alignment for maximum performance. static constexpr size_t kX86Alignment = 16; +// Different than code alignment since code alignment is only first instruction of method. +static constexpr size_t kThumb2InstructionAlignment = 2; +static constexpr size_t kArm64InstructionAlignment = 4; +static constexpr size_t kX86InstructionAlignment = 1; +static constexpr size_t kX86_64InstructionAlignment = 1; +static constexpr size_t kMipsInstructionAlignment = 2; +static constexpr size_t kMips64InstructionAlignment = 2; + const char* GetInstructionSetString(InstructionSet isa); // Note: Returns kNone when the string cannot be parsed to a known value. @@ -106,6 +114,17 @@ static inline PointerSize GetInstructionSetPointerSize(InstructionSet isa) { } } +ALWAYS_INLINE static inline constexpr size_t GetInstructionSetInstructionAlignment( + InstructionSet isa) { + return (isa == kThumb2 || isa == kArm) ? kThumb2InstructionAlignment : + (isa == kArm64) ? kArm64InstructionAlignment : + (isa == kX86) ? kX86InstructionAlignment : + (isa == kX86_64) ? kX86_64InstructionAlignment : + (isa == kMips) ? kMipsInstructionAlignment : + (isa == kMips64) ? kMips64InstructionAlignment : + 0; // Invalid case, but constexpr doesn't support asserts. +} + static inline bool IsValidInstructionSet(InstructionSet isa) { switch (isa) { case kArm: diff --git a/runtime/arch/instruction_set_features.h b/runtime/arch/instruction_set_features.h index b6c5c71818..5f1a507f7a 100644 --- a/runtime/arch/instruction_set_features.h +++ b/runtime/arch/instruction_set_features.h @@ -67,6 +67,24 @@ class InstructionSetFeatures { // Are these features the same as the other given features? virtual bool Equals(const InstructionSetFeatures* other) const = 0; + // For testing purposes we want to make sure that the system we run on has at + // least the options we claim it has. In this cases Equals() does not + // suffice and will cause the test to fail, since the runtime cpu feature + // detection claims more capabilities then statically specified from the + // build system. + // + // A good example of this is the armv8 ART test target that declares + // "CPU_VARIANT=generic". If the generic target is specified and the code + // is run on a platform with enhanced capabilities, the + // instruction_set_features test will fail if we resort to using Equals() + // between statically defined cpu features and runtime cpu features. + // + // For now we default this to Equals() in case the architecture does not + // provide it. + virtual bool HasAtLeast(const InstructionSetFeatures* other) const { + return Equals(other); + } + // Return the ISA these features relate to. virtual InstructionSet GetInstructionSet() const = 0; diff --git a/runtime/arch/instruction_set_features_test.cc b/runtime/arch/instruction_set_features_test.cc index d4893923d6..67e2f358c8 100644 --- a/runtime/arch/instruction_set_features_test.cc +++ b/runtime/arch/instruction_set_features_test.cc @@ -52,7 +52,7 @@ TEST(InstructionSetFeaturesTest, FeaturesFromSystemPropertyVariant) { InstructionSetFeatures::FromVariant(kRuntimeISA, dex2oat_isa_variant, &error_msg)); ASSERT_TRUE(property_features.get() != nullptr) << error_msg; - EXPECT_TRUE(property_features->Equals(instruction_set_features.get())) + EXPECT_TRUE(property_features->HasAtLeast(instruction_set_features.get())) << "System property features: " << *property_features.get() << "\nFeatures from build: " << *instruction_set_features.get(); } @@ -89,7 +89,7 @@ TEST(InstructionSetFeaturesTest, FeaturesFromSystemPropertyString) { base_features->AddFeaturesFromString(dex2oat_isa_features, &error_msg)); ASSERT_TRUE(property_features.get() != nullptr) << error_msg; - EXPECT_TRUE(property_features->Equals(instruction_set_features.get())) + EXPECT_TRUE(property_features->HasAtLeast(instruction_set_features.get())) << "System property features: " << *property_features.get() << "\nFeatures from build: " << *instruction_set_features.get(); } @@ -109,7 +109,7 @@ TEST(InstructionSetFeaturesTest, FeaturesFromCpuInfo) { // Check we get the same instruction set features using /proc/cpuinfo. std::unique_ptr<const InstructionSetFeatures> cpuinfo_features( InstructionSetFeatures::FromCpuInfo()); - EXPECT_TRUE(cpuinfo_features->Equals(instruction_set_features.get())) + EXPECT_TRUE(cpuinfo_features->HasAtLeast(instruction_set_features.get())) << "CPU Info features: " << *cpuinfo_features.get() << "\nFeatures from build: " << *instruction_set_features.get(); } @@ -124,7 +124,7 @@ TEST(InstructionSetFeaturesTest, HostFeaturesFromCppDefines) { std::unique_ptr<const InstructionSetFeatures> cpp_features( InstructionSetFeatures::FromCppDefines()); - EXPECT_TRUE(default_features->Equals(cpp_features.get())) + EXPECT_TRUE(cpp_features->HasAtLeast(default_features.get())) << "Default variant features: " << *default_features.get() << "\nFeatures from build: " << *cpp_features.get(); } @@ -143,7 +143,7 @@ TEST(InstructionSetFeaturesTest, FeaturesFromHwcap) { // Check we get the same instruction set features using AT_HWCAP. std::unique_ptr<const InstructionSetFeatures> hwcap_features( InstructionSetFeatures::FromHwcap()); - EXPECT_TRUE(hwcap_features->Equals(instruction_set_features.get())) + EXPECT_TRUE(hwcap_features->HasAtLeast(instruction_set_features.get())) << "Hwcap features: " << *hwcap_features.get() << "\nFeatures from build: " << *instruction_set_features.get(); } @@ -156,7 +156,7 @@ TEST(InstructionSetFeaturesTest, FeaturesFromAssembly) { // Check we get the same instruction set features using assembly tests. std::unique_ptr<const InstructionSetFeatures> assembly_features( InstructionSetFeatures::FromAssembly()); - EXPECT_TRUE(assembly_features->Equals(instruction_set_features.get())) + EXPECT_TRUE(assembly_features->HasAtLeast(instruction_set_features.get())) << "Assembly features: " << *assembly_features.get() << "\nFeatures from build: " << *instruction_set_features.get(); } diff --git a/runtime/arch/instruction_set_test.cc b/runtime/arch/instruction_set_test.cc index 5aae93acc5..b251b57c99 100644 --- a/runtime/arch/instruction_set_test.cc +++ b/runtime/arch/instruction_set_test.cc @@ -44,6 +44,15 @@ TEST(InstructionSetTest, GetInstructionSetString) { EXPECT_STREQ("none", GetInstructionSetString(kNone)); } +TEST(InstructionSetTest, GetInstructionSetInstructionAlignment) { + EXPECT_EQ(GetInstructionSetInstructionAlignment(kThumb2), kThumb2InstructionAlignment); + EXPECT_EQ(GetInstructionSetInstructionAlignment(kArm64), kArm64InstructionAlignment); + EXPECT_EQ(GetInstructionSetInstructionAlignment(kX86), kX86InstructionAlignment); + EXPECT_EQ(GetInstructionSetInstructionAlignment(kX86_64), kX86_64InstructionAlignment); + EXPECT_EQ(GetInstructionSetInstructionAlignment(kMips), kMipsInstructionAlignment); + EXPECT_EQ(GetInstructionSetInstructionAlignment(kMips64), kMips64InstructionAlignment); +} + TEST(InstructionSetTest, TestRoundTrip) { EXPECT_EQ(kRuntimeISA, GetInstructionSetFromString(GetInstructionSetString(kRuntimeISA))); } diff --git a/runtime/arch/mips64/quick_method_frame_info_mips64.h b/runtime/arch/mips64/quick_method_frame_info_mips64.h index 397776e999..d774473289 100644 --- a/runtime/arch/mips64/quick_method_frame_info_mips64.h +++ b/runtime/arch/mips64/quick_method_frame_info_mips64.h @@ -78,7 +78,7 @@ constexpr uint32_t Mips64CalleeSaveCoreSpills(Runtime::CalleeSaveType type) { constexpr uint32_t Mips64CalleeSaveFpSpills(Runtime::CalleeSaveType type) { return kMips64CalleeSaveFpRefSpills | - (type == Runtime::kSaveRefsAndArgs ? kMips64CalleeSaveFpArgSpills: 0) | + (type == Runtime::kSaveRefsAndArgs ? kMips64CalleeSaveFpArgSpills : 0) | (type == Runtime::kSaveAllCalleeSaves ? kMips64CalleeSaveFpAllSpills : 0) | (type == Runtime::kSaveEverything ? kMips64CalleeSaveFpEverythingSpills : 0); } diff --git a/runtime/arch/x86/quick_entrypoints_x86.S b/runtime/arch/x86/quick_entrypoints_x86.S index 1d81b7d1e6..47dc34a355 100644 --- a/runtime/arch/x86/quick_entrypoints_x86.S +++ b/runtime/arch/x86/quick_entrypoints_x86.S @@ -1582,14 +1582,15 @@ DEFINE_FUNCTION art_quick_set64_instance SETUP_SAVE_REFS_ONLY_FRAME ebx, ebx // save ref containing registers for GC movd %xmm0, %ebx // Outgoing argument set up - PUSH eax // alignment padding + subl LITERAL(12), %esp // alignment padding + CFI_ADJUST_CFA_OFFSET(12) pushl %fs:THREAD_SELF_OFFSET // pass Thread::Current() CFI_ADJUST_CFA_OFFSET(4) PUSH ebx // pass high half of new_val PUSH edx // pass low half of new_val PUSH ecx // pass object PUSH eax // pass field_idx - call SYMBOL(artSet64InstanceFromCode) // (field_idx, Object*, new_val, Thread*) + call SYMBOL(artSet64InstanceFromCompiledCode) // (field_idx, Object*, new_val, Thread*) addl LITERAL(32), %esp // pop arguments CFI_ADJUST_CFA_OFFSET(-32) RESTORE_SAVE_REFS_ONLY_FRAME // restore frame up to return address diff --git a/runtime/art_field-inl.h b/runtime/art_field-inl.h index ea02027b5c..80af8e7bde 100644 --- a/runtime/art_field-inl.h +++ b/runtime/art_field-inl.h @@ -52,7 +52,7 @@ inline uint32_t ArtField::GetAccessFlags() { } inline MemberOffset ArtField::GetOffset() { - DCHECK(GetDeclaringClass()->IsResolved() || GetDeclaringClass()->IsErroneous()); + DCHECK(GetDeclaringClass()->IsResolved()); return MemberOffset(offset_); } diff --git a/runtime/art_method-inl.h b/runtime/art_method-inl.h index 15938c52be..a35c7ab701 100644 --- a/runtime/art_method-inl.h +++ b/runtime/art_method-inl.h @@ -109,8 +109,7 @@ inline uint32_t ArtMethod::GetAccessFlags() { } inline uint16_t ArtMethod::GetMethodIndex() { - DCHECK(IsRuntimeMethod() || GetDeclaringClass()->IsResolved() || - GetDeclaringClass()->IsErroneous()); + DCHECK(IsRuntimeMethod() || GetDeclaringClass()->IsResolved()); return method_index_; } diff --git a/runtime/art_method.cc b/runtime/art_method.cc index d7d39afa8f..a3d9ba61f3 100644 --- a/runtime/art_method.cc +++ b/runtime/art_method.cc @@ -719,21 +719,7 @@ std::string ArtMethod::PrettyMethod(bool with_signature) { } std::string ArtMethod::JniShortName() { - std::string class_name(GetDeclaringClassDescriptor()); - // Remove the leading 'L' and trailing ';'... - CHECK_EQ(class_name[0], 'L') << class_name; - CHECK_EQ(class_name[class_name.size() - 1], ';') << class_name; - class_name.erase(0, 1); - class_name.erase(class_name.size() - 1, 1); - - std::string method_name(GetName()); - - std::string short_name; - short_name += "Java_"; - short_name += MangleForJni(class_name); - short_name += "_"; - short_name += MangleForJni(method_name); - return short_name; + return GetJniShortName(GetDeclaringClassDescriptor(), GetName()); } std::string ArtMethod::JniLongName() { diff --git a/runtime/class_linker.cc b/runtime/class_linker.cc index 49cffed559..02b26c6568 100644 --- a/runtime/class_linker.cc +++ b/runtime/class_linker.cc @@ -19,6 +19,7 @@ #include <algorithm> #include <deque> #include <iostream> +#include <map> #include <memory> #include <queue> #include <string> @@ -65,7 +66,7 @@ #include "interpreter/interpreter.h" #include "jit/jit.h" #include "jit/jit_code_cache.h" -#include "jit/offline_profiling_info.h" +#include "jit/profile_compilation_info.h" #include "jni_internal.h" #include "leb128.h" #include "linear_alloc.h" @@ -96,6 +97,7 @@ #include "object_lock.h" #include "os.h" #include "runtime.h" +#include "runtime_callbacks.h" #include "ScopedLocalRef.h" #include "scoped_thread_state_change-inl.h" #include "thread-inl.h" @@ -1339,7 +1341,7 @@ bool ClassLinker::UpdateAppImageClassLoadersAndDexCaches( // The image space is not yet added to the heap, avoid read barriers. ObjPtr<mirror::Class> klass = types[j].Read(); if (space->HasAddress(klass.Ptr())) { - DCHECK_NE(klass->GetStatus(), mirror::Class::kStatusError); + DCHECK(!klass->IsErroneous()) << klass->GetStatus(); auto it = new_class_set->Find(ClassTable::TableSlot(klass)); DCHECK(it != new_class_set->end()); DCHECK_EQ(it->Read(), klass); @@ -1397,7 +1399,11 @@ class UpdateClassLoaderVisitor { class_loader_(class_loader) {} bool operator()(ObjPtr<mirror::Class> klass) const REQUIRES_SHARED(Locks::mutator_lock_) { - klass->SetClassLoader(class_loader_); + // Do not update class loader for boot image classes where the app image + // class loader is only the initiating loader but not the defining loader. + if (klass->GetClassLoader() != nullptr) { + klass->SetClassLoader(class_loader_); + } return true; } @@ -1698,7 +1704,7 @@ bool ClassLinker::AddImageSpace( for (int32_t j = 0, num_types = h_dex_cache->NumResolvedTypes(); j < num_types; j++) { ObjPtr<mirror::Class> klass = types[j].Read(); if (klass != nullptr) { - DCHECK_NE(klass->GetStatus(), mirror::Class::kStatusError); + DCHECK(!klass->IsErroneous()) << klass->GetStatus(); } } } else { @@ -2227,7 +2233,7 @@ mirror::Class* ClassLinker::EnsureResolved(Thread* self, // For temporary classes we must wait for them to be retired. if (init_done_ && klass->IsTemp()) { CHECK(!klass->IsResolved()); - if (klass->IsErroneous()) { + if (klass->IsErroneousUnresolved()) { ThrowEarlierClassFailure(klass); return nullptr; } @@ -2235,10 +2241,10 @@ mirror::Class* ClassLinker::EnsureResolved(Thread* self, Handle<mirror::Class> h_class(hs.NewHandle(klass)); ObjectLock<mirror::Class> lock(self, h_class); // Loop and wait for the resolving thread to retire this class. - while (!h_class->IsRetired() && !h_class->IsErroneous()) { + while (!h_class->IsRetired() && !h_class->IsErroneousUnresolved()) { lock.WaitIgnoringInterrupts(); } - if (h_class->IsErroneous()) { + if (h_class->IsErroneousUnresolved()) { ThrowEarlierClassFailure(h_class.Get()); return nullptr; } @@ -2253,7 +2259,7 @@ mirror::Class* ClassLinker::EnsureResolved(Thread* self, static const size_t kNumYieldIterations = 1000; // How long each sleep is in us. static const size_t kSleepDurationUS = 1000; // 1 ms. - while (!klass->IsResolved() && !klass->IsErroneous()) { + while (!klass->IsResolved() && !klass->IsErroneousUnresolved()) { StackHandleScope<1> hs(self); HandleWrapperObjPtr<mirror::Class> h_class(hs.NewHandleWrapper(&klass)); { @@ -2264,7 +2270,7 @@ mirror::Class* ClassLinker::EnsureResolved(Thread* self, // Check for circular dependencies between classes, the lock is required for SetStatus. if (!h_class->IsResolved() && h_class->GetClinitThreadId() == self->GetTid()) { ThrowClassCircularityError(h_class.Get()); - mirror::Class::SetStatus(h_class, mirror::Class::kStatusError, self); + mirror::Class::SetStatus(h_class, mirror::Class::kStatusErrorUnresolved, self); return nullptr; } } @@ -2281,7 +2287,7 @@ mirror::Class* ClassLinker::EnsureResolved(Thread* self, ++index; } - if (klass->IsErroneous()) { + if (klass->IsErroneousUnresolved()) { ThrowEarlierClassFailure(klass); return nullptr; } @@ -2457,10 +2463,8 @@ mirror::Class* ClassLinker::FindClass(Thread* self, return EnsureResolved(self, descriptor, klass); } // Class is not yet loaded. - if (descriptor[0] == '[') { - return CreateArrayClass(self, descriptor, hash, class_loader); - } else if (class_loader.Get() == nullptr) { - // The boot class loader, search the boot class path. + if (descriptor[0] != '[' && class_loader.Get() == nullptr) { + // Non-array class and the boot class loader, search the boot class path. ClassPathEntry pair = FindInClassPath(descriptor, hash, boot_class_path_); if (pair.second != nullptr) { return DefineClass(self, @@ -2473,14 +2477,21 @@ mirror::Class* ClassLinker::FindClass(Thread* self, // The boot class loader is searched ahead of the application class loader, failures are // expected and will be wrapped in a ClassNotFoundException. Use the pre-allocated error to // trigger the chaining with a proper stack trace. - ObjPtr<mirror::Throwable> pre_allocated = Runtime::Current()->GetPreAllocatedNoClassDefFoundError(); + ObjPtr<mirror::Throwable> pre_allocated = + Runtime::Current()->GetPreAllocatedNoClassDefFoundError(); self->SetException(pre_allocated); return nullptr; } + } + ObjPtr<mirror::Class> result_ptr; + bool descriptor_equals; + if (descriptor[0] == '[') { + result_ptr = CreateArrayClass(self, descriptor, hash, class_loader); + DCHECK_EQ(result_ptr == nullptr, self->IsExceptionPending()); + DCHECK(result_ptr == nullptr || result_ptr->DescriptorEquals(descriptor)); + descriptor_equals = true; } else { ScopedObjectAccessUnchecked soa(self); - ObjPtr<mirror::Class> result_ptr; - bool descriptor_equals; bool known_hierarchy = FindClassInBaseDexClassLoader(soa, self, descriptor, hash, class_loader, &result_ptr); if (result_ptr != nullptr) { @@ -2524,16 +2535,7 @@ mirror::Class* ClassLinker::FindClass(Thread* self, WellKnownClasses::java_lang_ClassLoader_loadClass, class_name_object.get())); } - if (self->IsExceptionPending()) { - // If the ClassLoader threw, pass that exception up. - // However, to comply with the RI behavior, first check if another thread succeeded. - result_ptr = LookupClass(self, descriptor, hash, class_loader.Get()); - if (result_ptr != nullptr && !result_ptr->IsErroneous()) { - self->ClearException(); - return EnsureResolved(self, descriptor, result_ptr); - } - return nullptr; - } else if (result.get() == nullptr) { + if (result.get() == nullptr && !self->IsExceptionPending()) { // broken loader - throw NPE to be compatible with Dalvik ThrowNullPointerException(StringPrintf("ClassLoader.loadClass returned null for %s", class_name_string.c_str()).c_str()); @@ -2541,50 +2543,60 @@ mirror::Class* ClassLinker::FindClass(Thread* self, } result_ptr = soa.Decode<mirror::Class>(result.get()); // Check the name of the returned class. - descriptor_equals = result_ptr->DescriptorEquals(descriptor); + descriptor_equals = (result_ptr != nullptr) && result_ptr->DescriptorEquals(descriptor); } + } - // Try to insert the class to the class table, checking for mismatch. - ObjPtr<mirror::Class> old; - { - WriterMutexLock mu(self, *Locks::classlinker_classes_lock_); - ClassTable* const class_table = InsertClassTableForClassLoader(class_loader.Get()); - old = class_table->Lookup(descriptor, hash); - if (old == nullptr) { - old = result_ptr; // For the comparison below, after releasing the lock. - if (descriptor_equals) { - class_table->InsertWithHash(result_ptr.Ptr(), hash); - Runtime::Current()->GetHeap()->WriteBarrierEveryFieldOf(class_loader.Get()); - } // else throw below, after releasing the lock. - } - } - if (UNLIKELY(old != result_ptr)) { - // Return `old` (even if `!descriptor_equals`) to mimic the RI behavior for parallel - // capable class loaders. (All class loaders are considered parallel capable on Android.) - mirror::Class* loader_class = class_loader->GetClass(); - const char* loader_class_name = - loader_class->GetDexFile().StringByTypeIdx(loader_class->GetDexTypeIndex()); - LOG(WARNING) << "Initiating class loader of type " << DescriptorToDot(loader_class_name) - << " is not well-behaved; it returned a different Class for racing loadClass(\"" - << DescriptorToDot(descriptor) << "\")."; - return EnsureResolved(self, descriptor, old); - } - if (UNLIKELY(!descriptor_equals)) { - std::string result_storage; - const char* result_name = result_ptr->GetDescriptor(&result_storage); - std::string loader_storage; - const char* loader_class_name = class_loader->GetClass()->GetDescriptor(&loader_storage); - ThrowNoClassDefFoundError( - "Initiating class loader of type %s returned class %s instead of %s.", - DescriptorToDot(loader_class_name).c_str(), - DescriptorToDot(result_name).c_str(), - DescriptorToDot(descriptor).c_str()); - return nullptr; + if (self->IsExceptionPending()) { + // If the ClassLoader threw or array class allocation failed, pass that exception up. + // However, to comply with the RI behavior, first check if another thread succeeded. + result_ptr = LookupClass(self, descriptor, hash, class_loader.Get()); + if (result_ptr != nullptr && !result_ptr->IsErroneous()) { + self->ClearException(); + return EnsureResolved(self, descriptor, result_ptr); } - // success, return mirror::Class* - return result_ptr.Ptr(); + return nullptr; } - UNREACHABLE(); + + // Try to insert the class to the class table, checking for mismatch. + ObjPtr<mirror::Class> old; + { + WriterMutexLock mu(self, *Locks::classlinker_classes_lock_); + ClassTable* const class_table = InsertClassTableForClassLoader(class_loader.Get()); + old = class_table->Lookup(descriptor, hash); + if (old == nullptr) { + old = result_ptr; // For the comparison below, after releasing the lock. + if (descriptor_equals) { + class_table->InsertWithHash(result_ptr.Ptr(), hash); + Runtime::Current()->GetHeap()->WriteBarrierEveryFieldOf(class_loader.Get()); + } // else throw below, after releasing the lock. + } + } + if (UNLIKELY(old != result_ptr)) { + // Return `old` (even if `!descriptor_equals`) to mimic the RI behavior for parallel + // capable class loaders. (All class loaders are considered parallel capable on Android.) + mirror::Class* loader_class = class_loader->GetClass(); + const char* loader_class_name = + loader_class->GetDexFile().StringByTypeIdx(loader_class->GetDexTypeIndex()); + LOG(WARNING) << "Initiating class loader of type " << DescriptorToDot(loader_class_name) + << " is not well-behaved; it returned a different Class for racing loadClass(\"" + << DescriptorToDot(descriptor) << "\")."; + return EnsureResolved(self, descriptor, old); + } + if (UNLIKELY(!descriptor_equals)) { + std::string result_storage; + const char* result_name = result_ptr->GetDescriptor(&result_storage); + std::string loader_storage; + const char* loader_class_name = class_loader->GetClass()->GetDescriptor(&loader_storage); + ThrowNoClassDefFoundError( + "Initiating class loader of type %s returned class %s instead of %s.", + DescriptorToDot(loader_class_name).c_str(), + DescriptorToDot(result_name).c_str(), + DescriptorToDot(descriptor).c_str()); + return nullptr; + } + // success, return mirror::Class* + return result_ptr.Ptr(); } mirror::Class* ClassLinker::DefineClass(Thread* self, @@ -2625,13 +2637,30 @@ mirror::Class* ClassLinker::DefineClass(Thread* self, self->AssertPendingOOMException(); return nullptr; } - ObjPtr<mirror::DexCache> dex_cache = RegisterDexFile(dex_file, class_loader.Get()); + // Get the real dex file. This will return the input if there aren't any callbacks or they do + // nothing. + DexFile const* new_dex_file = nullptr; + DexFile::ClassDef const* new_class_def = nullptr; + // TODO We should ideally figure out some way to move this after we get a lock on the klass so it + // will only be called once. + Runtime::Current()->GetRuntimeCallbacks()->ClassPreDefine(descriptor, + klass, + class_loader, + dex_file, + dex_class_def, + &new_dex_file, + &new_class_def); + // Check to see if an exception happened during runtime callbacks. Return if so. + if (self->IsExceptionPending()) { + return nullptr; + } + ObjPtr<mirror::DexCache> dex_cache = RegisterDexFile(*new_dex_file, class_loader.Get()); if (dex_cache == nullptr) { self->AssertPendingOOMException(); return nullptr; } klass->SetDexCache(dex_cache); - SetupClass(dex_file, dex_class_def, klass, class_loader.Get()); + SetupClass(*new_dex_file, *new_class_def, klass, class_loader.Get()); // Mark the string class by setting its access flag. if (UNLIKELY(!init_done_)) { @@ -2657,27 +2686,32 @@ mirror::Class* ClassLinker::DefineClass(Thread* self, // end up allocating unfree-able linear alloc resources and then lose the race condition. The // other reason is that the field roots are only visited from the class table. So we need to be // inserted before we allocate / fill in these fields. - LoadClass(self, dex_file, dex_class_def, klass); + LoadClass(self, *new_dex_file, *new_class_def, klass); if (self->IsExceptionPending()) { VLOG(class_linker) << self->GetException()->Dump(); // An exception occured during load, set status to erroneous while holding klass' lock in case // notification is necessary. if (!klass->IsErroneous()) { - mirror::Class::SetStatus(klass, mirror::Class::kStatusError, self); + mirror::Class::SetStatus(klass, mirror::Class::kStatusErrorUnresolved, self); } return nullptr; } // Finish loading (if necessary) by finding parents CHECK(!klass->IsLoaded()); - if (!LoadSuperAndInterfaces(klass, dex_file)) { + if (!LoadSuperAndInterfaces(klass, *new_dex_file)) { // Loading failed. if (!klass->IsErroneous()) { - mirror::Class::SetStatus(klass, mirror::Class::kStatusError, self); + mirror::Class::SetStatus(klass, mirror::Class::kStatusErrorUnresolved, self); } return nullptr; } CHECK(klass->IsLoaded()); + + // At this point the class is loaded. Publish a ClassLoad even. + // Note: this may be a temporary class. It is a listener's responsibility to handle this. + Runtime::Current()->GetRuntimeCallbacks()->ClassLoad(klass); + // Link the class (if necessary) CHECK(!klass->IsResolved()); // TODO: Use fast jobjects? @@ -2687,13 +2721,13 @@ mirror::Class* ClassLinker::DefineClass(Thread* self, if (!LinkClass(self, descriptor, klass, interfaces, &h_new_class)) { // Linking failed. if (!klass->IsErroneous()) { - mirror::Class::SetStatus(klass, mirror::Class::kStatusError, self); + mirror::Class::SetStatus(klass, mirror::Class::kStatusErrorUnresolved, self); } return nullptr; } self->AssertNoPendingException(); CHECK(h_new_class.Get() != nullptr) << descriptor; - CHECK(h_new_class->IsResolved()) << descriptor; + CHECK(h_new_class->IsResolved() && !h_new_class->IsErroneousResolved()) << descriptor; // Instrumentation may have updated entrypoints for all methods of all // classes. However it could not update methods of this class while we @@ -2718,7 +2752,7 @@ mirror::Class* ClassLinker::DefineClass(Thread* self, * The class has been prepared and resolved but possibly not yet verified * at this point. */ - Dbg::PostClassPrepare(h_new_class.Get()); + Runtime::Current()->GetRuntimeCallbacks()->ClassPrepare(klass, h_new_class); // Notify native debugger of the new class and its layout. jit::Jit::NewTypeLoadedIfUsingJit(h_new_class.Get()); @@ -3488,7 +3522,8 @@ mirror::Class* ClassLinker::CreateArrayClass(Thread* self, const char* descripto // class to the hash table --- necessary because of possible races with // other threads.) if (class_loader.Get() != component_type->GetClassLoader()) { - ObjPtr<mirror::Class> new_class = LookupClass(self, descriptor, hash, component_type->GetClassLoader()); + ObjPtr<mirror::Class> new_class = + LookupClass(self, descriptor, hash, component_type->GetClassLoader()); if (new_class != nullptr) { return new_class.Ptr(); } @@ -3787,7 +3822,7 @@ bool ClassLinker::AttemptSupertypeVerification(Thread* self, } // Need to grab the lock to change status. ObjectLock<mirror::Class> super_lock(self, klass); - mirror::Class::SetStatus(klass, mirror::Class::kStatusError, self); + mirror::Class::SetStatus(klass, mirror::Class::kStatusErrorResolved, self); return false; } @@ -3909,8 +3944,8 @@ verifier::MethodVerifier::FailureKind ClassLinker::VerifyClass( bool preverified = VerifyClassUsingOatFile(dex_file, klass.Get(), oat_file_class_status); // If the oat file says the class had an error, re-run the verifier. That way we will get a // precise error message. To ensure a rerun, test: - // oat_file_class_status == mirror::Class::kStatusError => !preverified - DCHECK(!(oat_file_class_status == mirror::Class::kStatusError) || !preverified); + // mirror::Class::IsErroneous(oat_file_class_status) => !preverified + DCHECK(!mirror::Class::IsErroneous(oat_file_class_status) || !preverified); std::string error_msg; verifier::MethodVerifier::FailureKind verifier_failure = verifier::MethodVerifier::kNoFailure; @@ -3968,7 +4003,7 @@ verifier::MethodVerifier::FailureKind ClassLinker::VerifyClass( << " because: " << error_msg; self->AssertNoPendingException(); ThrowVerifyError(klass.Get(), "%s", error_msg.c_str()); - mirror::Class::SetStatus(klass, mirror::Class::kStatusError, self); + mirror::Class::SetStatus(klass, mirror::Class::kStatusErrorResolved, self); } if (preverified || verifier_failure == verifier::MethodVerifier::kNoFailure) { // Class is verified so we don't need to do any access check on its methods. @@ -4059,7 +4094,7 @@ bool ClassLinker::VerifyClassUsingOatFile(const DexFile& dex_file, // at compile time). return false; } - if (oat_file_class_status == mirror::Class::kStatusError) { + if (mirror::Class::IsErroneous(oat_file_class_status)) { // Compile time verification failed with a hard error. This is caused by invalid instructions // in the class. These errors are unrecoverable. return false; @@ -4218,7 +4253,7 @@ mirror::Class* ClassLinker::CreateProxyClass(ScopedObjectAccessAlreadyRunnable& Handle<mirror::ObjectArray<mirror::Class>> h_interfaces( hs.NewHandle(soa.Decode<mirror::ObjectArray<mirror::Class>>(interfaces))); if (!LinkClass(self, descriptor.c_str(), klass, h_interfaces, &new_class)) { - mirror::Class::SetStatus(klass, mirror::Class::kStatusError, self); + mirror::Class::SetStatus(klass, mirror::Class::kStatusErrorUnresolved, self); return nullptr; } } @@ -4433,7 +4468,8 @@ bool ClassLinker::InitializeClass(Thread* self, Handle<mirror::Class> klass, return false; } - CHECK(klass->IsResolved()) << klass->PrettyClass() << ": state=" << klass->GetStatus(); + CHECK(klass->IsResolved() && !klass->IsErroneousResolved()) + << klass->PrettyClass() << ": state=" << klass->GetStatus(); if (!klass->IsVerified()) { VerifyClass(self, klass); @@ -4468,7 +4504,7 @@ bool ClassLinker::InitializeClass(Thread* self, Handle<mirror::Class> klass, // A separate thread could have moved us all the way to initialized. A "simple" example // involves a subclass of the current class being initialized at the same time (which // will implicitly initialize the superclass, if scheduled that way). b/28254258 - DCHECK_NE(mirror::Class::kStatusError, klass->GetStatus()); + DCHECK(!klass->IsErroneous()) << klass->GetStatus(); if (klass->IsInitialized()) { return true; } @@ -4495,7 +4531,7 @@ bool ClassLinker::InitializeClass(Thread* self, Handle<mirror::Class> klass, } if (!ValidateSuperClassDescriptors(klass)) { - mirror::Class::SetStatus(klass, mirror::Class::kStatusError, self); + mirror::Class::SetStatus(klass, mirror::Class::kStatusErrorResolved, self); return false; } self->AllowThreadSuspension(); @@ -4531,7 +4567,7 @@ bool ClassLinker::InitializeClass(Thread* self, Handle<mirror::Class> klass, << (self->GetException() != nullptr ? self->GetException()->Dump() : ""); ObjectLock<mirror::Class> lock(self, klass); // Initialization failed because the super-class is erroneous. - mirror::Class::SetStatus(klass, mirror::Class::kStatusError, self); + mirror::Class::SetStatus(klass, mirror::Class::kStatusErrorResolved, self); return false; } } @@ -4562,7 +4598,7 @@ bool ClassLinker::InitializeClass(Thread* self, Handle<mirror::Class> klass, if (!iface_initialized) { ObjectLock<mirror::Class> lock(self, klass); // Initialization failed because one of our interfaces with default methods is erroneous. - mirror::Class::SetStatus(klass, mirror::Class::kStatusError, self); + mirror::Class::SetStatus(klass, mirror::Class::kStatusErrorResolved, self); return false; } } @@ -4635,7 +4671,7 @@ bool ClassLinker::InitializeClass(Thread* self, Handle<mirror::Class> klass, if (self->IsExceptionPending()) { WrapExceptionInInitializer(klass); - mirror::Class::SetStatus(klass, mirror::Class::kStatusError, self); + mirror::Class::SetStatus(klass, mirror::Class::kStatusErrorResolved, self); success = false; } else if (Runtime::Current()->IsTransactionAborted()) { // The exception thrown when the transaction aborted has been caught and cleared @@ -4644,7 +4680,7 @@ bool ClassLinker::InitializeClass(Thread* self, Handle<mirror::Class> klass, << mirror::Class::PrettyDescriptor(klass.Get()) << " without exception while transaction was aborted: re-throw it now."; Runtime::Current()->ThrowTransactionAbortError(self); - mirror::Class::SetStatus(klass, mirror::Class::kStatusError, self); + mirror::Class::SetStatus(klass, mirror::Class::kStatusErrorResolved, self); success = false; } else { RuntimeStats* global_stats = Runtime::Current()->GetStats(); @@ -4728,7 +4764,7 @@ bool ClassLinker::WaitForInitializeClass(Handle<mirror::Class> klass, // we were not using WaitIgnoringInterrupts), bail out. if (self->IsExceptionPending()) { WrapExceptionInInitializer(klass); - mirror::Class::SetStatus(klass, mirror::Class::kStatusError, self); + mirror::Class::SetStatus(klass, mirror::Class::kStatusErrorResolved, self); return false; } // Spurious wakeup? Go back to waiting. @@ -5139,7 +5175,7 @@ bool ClassLinker::LinkClass(Thread* self, klass->SetIFieldsPtrUnchecked(nullptr); if (UNLIKELY(h_new_class.Get() == nullptr)) { self->AssertPendingOOMException(); - mirror::Class::SetStatus(klass, mirror::Class::kStatusError, self); + mirror::Class::SetStatus(klass, mirror::Class::kStatusErrorUnresolved, self); return false; } @@ -7706,7 +7742,7 @@ ObjPtr<mirror::Class> ClassLinker::LookupResolvedType(const DexFile& dex_file, type = LookupClass(self, descriptor, hash, class_loader.Ptr()); } } - if (type != nullptr || type->IsResolved()) { + if (type != nullptr && type->IsResolved()) { return type.Ptr(); } return nullptr; @@ -7751,7 +7787,7 @@ mirror::Class* ClassLinker::ResolveType(const DexFile& dex_file, } } } - DCHECK((resolved == nullptr) || resolved->IsResolved() || resolved->IsErroneous()) + DCHECK((resolved == nullptr) || resolved->IsResolved()) << resolved->PrettyDescriptor() << " " << resolved->GetStatus(); return resolved.Ptr(); } @@ -8448,71 +8484,94 @@ void ClassLinker::CleanupClassLoaders() { } } -std::set<DexCacheResolvedClasses> ClassLinker::GetResolvedClasses(bool ignore_boot_classes) { - ScopedTrace trace(__PRETTY_FUNCTION__); - ScopedObjectAccess soa(Thread::Current()); - ScopedAssertNoThreadSuspension ants(__FUNCTION__); - std::set<DexCacheResolvedClasses> ret; - VLOG(class_linker) << "Collecting resolved classes"; - const uint64_t start_time = NanoTime(); - ReaderMutexLock mu(soa.Self(), *Locks::dex_lock_); - // Loop through all the dex caches and inspect resolved classes. - for (const ClassLinker::DexCacheData& data : GetDexCachesData()) { - if (soa.Self()->IsJWeakCleared(data.weak_root)) { - continue; - } - ObjPtr<mirror::DexCache> dex_cache = soa.Decode<mirror::DexCache>(data.weak_root); - if (dex_cache == nullptr) { - continue; - } - const DexFile* dex_file = dex_cache->GetDexFile(); - const std::string& location = dex_file->GetLocation(); - const size_t num_class_defs = dex_file->NumClassDefs(); - // Use the resolved types, this will miss array classes. - const size_t num_types = dex_file->NumTypeIds(); - VLOG(class_linker) << "Collecting class profile for dex file " << location - << " types=" << num_types << " class_defs=" << num_class_defs; - DexCacheResolvedClasses resolved_classes(dex_file->GetLocation(), - dex_file->GetBaseLocation(), - dex_file->GetLocationChecksum()); - size_t num_resolved = 0; - std::unordered_set<dex::TypeIndex> class_set; - CHECK_EQ(num_types, dex_cache->NumResolvedTypes()); - for (size_t i = 0; i < num_types; ++i) { - ObjPtr<mirror::Class> klass = dex_cache->GetResolvedType(dex::TypeIndex(i)); - // Filter out null class loader since that is the boot class loader. - if (klass == nullptr || (ignore_boot_classes && klass->GetClassLoader() == nullptr)) { - continue; - } - ++num_resolved; - DCHECK(!klass->IsProxyClass()); - if (!klass->IsResolved()) { - DCHECK(klass->IsErroneous()); - continue; +class GetResolvedClassesVisitor : public ClassVisitor { + public: + GetResolvedClassesVisitor(std::set<DexCacheResolvedClasses>* result, bool ignore_boot_classes) + : result_(result), + ignore_boot_classes_(ignore_boot_classes), + last_resolved_classes_(result->end()), + last_dex_file_(nullptr), + vlog_is_on_(VLOG_IS_ON(class_linker)), + extra_stats_(), + last_extra_stats_(extra_stats_.end()) { } + + bool operator()(ObjPtr<mirror::Class> klass) OVERRIDE REQUIRES_SHARED(Locks::mutator_lock_) { + if (!klass->IsProxyClass() && + !klass->IsArrayClass() && + klass->IsResolved() && + !klass->IsErroneousResolved() && + (!ignore_boot_classes_ || klass->GetClassLoader() != nullptr)) { + const DexFile& dex_file = klass->GetDexFile(); + if (&dex_file != last_dex_file_) { + last_dex_file_ = &dex_file; + DexCacheResolvedClasses resolved_classes(dex_file.GetLocation(), + dex_file.GetBaseLocation(), + dex_file.GetLocationChecksum()); + last_resolved_classes_ = result_->find(resolved_classes); + if (last_resolved_classes_ == result_->end()) { + last_resolved_classes_ = result_->insert(resolved_classes).first; + } } - ObjPtr<mirror::DexCache> klass_dex_cache = klass->GetDexCache(); - if (klass_dex_cache == dex_cache) { - DCHECK(klass->IsResolved()); - CHECK_LT(klass->GetDexClassDefIndex(), num_class_defs); - class_set.insert(klass->GetDexTypeIndex()); + bool added = last_resolved_classes_->AddClass(klass->GetDexTypeIndex()); + if (UNLIKELY(vlog_is_on_) && added) { + const DexCacheResolvedClasses* resolved_classes = std::addressof(*last_resolved_classes_); + if (last_extra_stats_ == extra_stats_.end() || + last_extra_stats_->first != resolved_classes) { + last_extra_stats_ = extra_stats_.find(resolved_classes); + if (last_extra_stats_ == extra_stats_.end()) { + last_extra_stats_ = + extra_stats_.emplace(resolved_classes, ExtraStats(dex_file.NumClassDefs())).first; + } + } } } + return true; + } - if (!class_set.empty()) { - auto it = ret.find(resolved_classes); - if (it != ret.end()) { - // Already have the key, union the class type indexes. - it->AddClasses(class_set.begin(), class_set.end()); - } else { - resolved_classes.AddClasses(class_set.begin(), class_set.end()); - ret.insert(resolved_classes); + void PrintStatistics() const { + if (vlog_is_on_) { + for (const DexCacheResolvedClasses& resolved_classes : *result_) { + auto it = extra_stats_.find(std::addressof(resolved_classes)); + DCHECK(it != extra_stats_.end()); + const ExtraStats& extra_stats = it->second; + LOG(INFO) << "Dex location " << resolved_classes.GetDexLocation() + << " has " << resolved_classes.GetClasses().size() << " / " + << extra_stats.number_of_class_defs_ << " resolved classes"; } } + } + + private: + struct ExtraStats { + explicit ExtraStats(uint32_t number_of_class_defs) + : number_of_class_defs_(number_of_class_defs) {} + uint32_t number_of_class_defs_; + }; - VLOG(class_linker) << "Dex location " << location << " has " << num_resolved << " / " - << num_class_defs << " resolved classes"; + std::set<DexCacheResolvedClasses>* result_; + bool ignore_boot_classes_; + std::set<DexCacheResolvedClasses>::iterator last_resolved_classes_; + const DexFile* last_dex_file_; + + // Statistics. + bool vlog_is_on_; + std::map<const DexCacheResolvedClasses*, ExtraStats> extra_stats_; + std::map<const DexCacheResolvedClasses*, ExtraStats>::iterator last_extra_stats_; +}; + +std::set<DexCacheResolvedClasses> ClassLinker::GetResolvedClasses(bool ignore_boot_classes) { + ScopedTrace trace(__PRETTY_FUNCTION__); + ScopedObjectAccess soa(Thread::Current()); + ScopedAssertNoThreadSuspension ants(__FUNCTION__); + std::set<DexCacheResolvedClasses> ret; + VLOG(class_linker) << "Collecting resolved classes"; + const uint64_t start_time = NanoTime(); + GetResolvedClassesVisitor visitor(&ret, ignore_boot_classes); + VisitClasses(&visitor); + if (VLOG_IS_ON(class_linker)) { + visitor.PrintStatistics(); + LOG(INFO) << "Collecting class profile took " << PrettyDuration(NanoTime() - start_time); } - VLOG(class_linker) << "Collecting class profile took " << PrettyDuration(NanoTime() - start_time); return ret; } diff --git a/runtime/class_linker.h b/runtime/class_linker.h index 9b98671cb4..5042fb7609 100644 --- a/runtime/class_linker.h +++ b/runtime/class_linker.h @@ -34,6 +34,7 @@ #include "dex_file.h" #include "dex_file_types.h" #include "gc_root.h" +#include "handle.h" #include "jni.h" #include "mirror/class.h" #include "object_callbacks.h" @@ -1194,6 +1195,38 @@ class ClassLinker { DISALLOW_COPY_AND_ASSIGN(ClassLinker); }; +class ClassLoadCallback { + public: + virtual ~ClassLoadCallback() {} + + // If set we will replace initial_class_def & initial_dex_file with the final versions. The + // callback author is responsible for ensuring these are allocated in such a way they can be + // cleaned up if another transformation occurs. Note that both must be set or null/unchanged on + // return. + // Note: the class may be temporary, in which case a following ClassPrepare event will be a + // different object. It is the listener's responsibility to handle this. + // Note: This callback is rarely useful so a default implementation has been given that does + // nothing. + virtual void ClassPreDefine(const char* descriptor ATTRIBUTE_UNUSED, + Handle<mirror::Class> klass ATTRIBUTE_UNUSED, + Handle<mirror::ClassLoader> class_loader ATTRIBUTE_UNUSED, + const DexFile& initial_dex_file ATTRIBUTE_UNUSED, + const DexFile::ClassDef& initial_class_def ATTRIBUTE_UNUSED, + /*out*/DexFile const** final_dex_file ATTRIBUTE_UNUSED, + /*out*/DexFile::ClassDef const** final_class_def ATTRIBUTE_UNUSED) + REQUIRES_SHARED(Locks::mutator_lock_) {} + + // A class has been loaded. + // Note: the class may be temporary, in which case a following ClassPrepare event will be a + // different object. It is the listener's responsibility to handle this. + virtual void ClassLoad(Handle<mirror::Class> klass) REQUIRES_SHARED(Locks::mutator_lock_) = 0; + + // A class has been prepared, i.e., resolved. As the ClassLoad event might have been for a + // temporary class, provide both the former and the current class. + virtual void ClassPrepare(Handle<mirror::Class> temp_klass, + Handle<mirror::Class> klass) REQUIRES_SHARED(Locks::mutator_lock_) = 0; +}; + } // namespace art #endif // ART_RUNTIME_CLASS_LINKER_H_ diff --git a/runtime/class_linker_test.cc b/runtime/class_linker_test.cc index 0341c64969..e806e7d608 100644 --- a/runtime/class_linker_test.cc +++ b/runtime/class_linker_test.cc @@ -87,6 +87,7 @@ class ClassLinkerTest : public CommonRuntimeTest { EXPECT_FALSE(primitive->IsErroneous()); EXPECT_TRUE(primitive->IsLoaded()); EXPECT_TRUE(primitive->IsResolved()); + EXPECT_FALSE(primitive->IsErroneousResolved()); EXPECT_TRUE(primitive->IsVerified()); EXPECT_TRUE(primitive->IsInitialized()); EXPECT_FALSE(primitive->IsArrayInstance()); @@ -125,6 +126,7 @@ class ClassLinkerTest : public CommonRuntimeTest { EXPECT_FALSE(JavaLangObject->IsErroneous()); EXPECT_TRUE(JavaLangObject->IsLoaded()); EXPECT_TRUE(JavaLangObject->IsResolved()); + EXPECT_FALSE(JavaLangObject->IsErroneousResolved()); EXPECT_TRUE(JavaLangObject->IsVerified()); EXPECT_TRUE(JavaLangObject->IsInitialized()); EXPECT_FALSE(JavaLangObject->IsArrayInstance()); @@ -199,6 +201,7 @@ class ClassLinkerTest : public CommonRuntimeTest { EXPECT_FALSE(array->IsErroneous()); EXPECT_TRUE(array->IsLoaded()); EXPECT_TRUE(array->IsResolved()); + EXPECT_FALSE(array->IsErroneousResolved()); EXPECT_TRUE(array->IsVerified()); EXPECT_TRUE(array->IsInitialized()); EXPECT_FALSE(array->IsArrayInstance()); @@ -270,6 +273,7 @@ class ClassLinkerTest : public CommonRuntimeTest { EXPECT_TRUE(klass->GetDexCache() != nullptr); EXPECT_TRUE(klass->IsLoaded()); EXPECT_TRUE(klass->IsResolved()); + EXPECT_FALSE(klass->IsErroneousResolved()); EXPECT_FALSE(klass->IsErroneous()); EXPECT_FALSE(klass->IsArrayClass()); EXPECT_TRUE(klass->GetComponentType() == nullptr); @@ -487,7 +491,7 @@ struct CheckOffsets { // says AccessibleObject is 9 bytes but sizeof(AccessibleObject) is 12 bytes due to padding. // The RoundUp is to get around this case. static constexpr size_t kPackAlignment = 4; - size_t expected_size = RoundUp(is_static ? klass->GetClassSize(): klass->GetObjectSize(), + size_t expected_size = RoundUp(is_static ? klass->GetClassSize() : klass->GetObjectSize(), kPackAlignment); if (sizeof(T) != expected_size) { LOG(ERROR) << "Class size mismatch:" @@ -612,7 +616,7 @@ struct ClassExtOffsets : public CheckOffsets<mirror::ClassExt> { ClassExtOffsets() : CheckOffsets<mirror::ClassExt>(false, "Ldalvik/system/ClassExt;") { addOffset(OFFSETOF_MEMBER(mirror::ClassExt, obsolete_dex_caches_), "obsoleteDexCaches"); addOffset(OFFSETOF_MEMBER(mirror::ClassExt, obsolete_methods_), "obsoleteMethods"); - addOffset(OFFSETOF_MEMBER(mirror::ClassExt, original_dex_cache_), "originalDexCache"); + addOffset(OFFSETOF_MEMBER(mirror::ClassExt, original_dex_file_bytes_), "originalDexFile"); addOffset(OFFSETOF_MEMBER(mirror::ClassExt, verify_error_), "verifyError"); } }; @@ -743,6 +747,8 @@ struct MethodHandleImplOffsets : public CheckOffsets<mirror::MethodHandleImpl> { MethodHandleImplOffsets() : CheckOffsets<mirror::MethodHandleImpl>( false, "Ljava/lang/invoke/MethodHandle;") { addOffset(OFFSETOF_MEMBER(mirror::MethodHandleImpl, art_field_or_method_), "artFieldOrMethod"); + addOffset(OFFSETOF_MEMBER(mirror::MethodHandleImpl, cached_spread_invoker_), + "cachedSpreadInvoker"); addOffset(OFFSETOF_MEMBER(mirror::MethodHandleImpl, handle_kind_), "handleKind"); addOffset(OFFSETOF_MEMBER(mirror::MethodHandleImpl, nominal_type_), "nominalType"); addOffset(OFFSETOF_MEMBER(mirror::MethodHandleImpl, method_type_), "type"); @@ -857,6 +863,7 @@ TEST_F(ClassLinkerTest, FindClass) { EXPECT_FALSE(MyClass->IsErroneous()); EXPECT_TRUE(MyClass->IsLoaded()); EXPECT_TRUE(MyClass->IsResolved()); + EXPECT_FALSE(MyClass->IsErroneousResolved()); EXPECT_FALSE(MyClass->IsVerified()); EXPECT_FALSE(MyClass->IsInitialized()); EXPECT_FALSE(MyClass->IsArrayInstance()); @@ -906,6 +913,82 @@ TEST_F(ClassLinkerTest, LookupResolvedType) { klass); } +TEST_F(ClassLinkerTest, LookupResolvedTypeArray) { + ScopedObjectAccess soa(Thread::Current()); + StackHandleScope<2> hs(soa.Self()); + Handle<mirror::ClassLoader> class_loader( + hs.NewHandle(soa.Decode<mirror::ClassLoader>(LoadDex("AllFields")))); + // Get the AllFields class for the dex cache and dex file. + ObjPtr<mirror::Class> all_fields_klass + = class_linker_->FindClass(soa.Self(), "LAllFields;", class_loader); + ASSERT_OBJ_PTR_NE(all_fields_klass, ObjPtr<mirror::Class>(nullptr)); + Handle<mirror::DexCache> dex_cache = hs.NewHandle(all_fields_klass->GetDexCache()); + const DexFile& dex_file = *dex_cache->GetDexFile(); + // Get the index of the array class we want to test. + const DexFile::TypeId* array_id = dex_file.FindTypeId("[Ljava/lang/Object;"); + ASSERT_TRUE(array_id != nullptr); + dex::TypeIndex array_idx = dex_file.GetIndexForTypeId(*array_id); + // Check that the array class wasn't resolved yet. + EXPECT_OBJ_PTR_EQ( + class_linker_->LookupResolvedType(dex_file, array_idx, dex_cache.Get(), class_loader.Get()), + ObjPtr<mirror::Class>(nullptr)); + // Resolve the array class we want to test. + ObjPtr<mirror::Class> array_klass + = class_linker_->FindClass(soa.Self(), "[Ljava/lang/Object;", class_loader); + ASSERT_OBJ_PTR_NE(array_klass, ObjPtr<mirror::Class>(nullptr)); + // Test that LookupResolvedType() finds the array class. + EXPECT_OBJ_PTR_EQ( + class_linker_->LookupResolvedType(dex_file, array_idx, dex_cache.Get(), class_loader.Get()), + array_klass); + // Zero out the resolved type and make sure LookupResolvedType() still finds it. + dex_cache->SetResolvedType(array_idx, nullptr); + EXPECT_TRUE(dex_cache->GetResolvedType(array_idx) == nullptr); + EXPECT_OBJ_PTR_EQ( + class_linker_->LookupResolvedType(dex_file, array_idx, dex_cache.Get(), class_loader.Get()), + array_klass); +} + +TEST_F(ClassLinkerTest, LookupResolvedTypeErroneousInit) { + ScopedObjectAccess soa(Thread::Current()); + StackHandleScope<3> hs(soa.Self()); + Handle<mirror::ClassLoader> class_loader( + hs.NewHandle(soa.Decode<mirror::ClassLoader>(LoadDex("ErroneousInit")))); + AssertNonExistentClass("LErroneousInit;"); + Handle<mirror::Class> klass = + hs.NewHandle(class_linker_->FindClass(soa.Self(), "LErroneousInit;", class_loader)); + ASSERT_OBJ_PTR_NE(klass.Get(), ObjPtr<mirror::Class>(nullptr)); + dex::TypeIndex type_idx = klass->GetClassDef()->class_idx_; + Handle<mirror::DexCache> dex_cache = hs.NewHandle(klass->GetDexCache()); + const DexFile& dex_file = klass->GetDexFile(); + EXPECT_OBJ_PTR_EQ( + class_linker_->LookupResolvedType(dex_file, type_idx, dex_cache.Get(), class_loader.Get()), + klass.Get()); + // Zero out the resolved type and make sure LookupResolvedType still finds it. + dex_cache->SetResolvedType(type_idx, nullptr); + EXPECT_TRUE(dex_cache->GetResolvedType(type_idx) == nullptr); + EXPECT_OBJ_PTR_EQ( + class_linker_->LookupResolvedType(dex_file, type_idx, dex_cache.Get(), class_loader.Get()), + klass.Get()); + // Force initialization to turn the class erroneous. + bool initialized = class_linker_->EnsureInitialized(soa.Self(), + klass, + /* can_init_fields */ true, + /* can_init_parents */ true); + EXPECT_FALSE(initialized); + EXPECT_TRUE(soa.Self()->IsExceptionPending()); + soa.Self()->ClearException(); + // Check that the LookupResolvedType() can still find the resolved type. + EXPECT_OBJ_PTR_EQ( + class_linker_->LookupResolvedType(dex_file, type_idx, dex_cache.Get(), class_loader.Get()), + klass.Get()); + // Zero out the resolved type and make sure LookupResolvedType() still finds it. + dex_cache->SetResolvedType(type_idx, nullptr); + EXPECT_TRUE(dex_cache->GetResolvedType(type_idx) == nullptr); + EXPECT_OBJ_PTR_EQ( + class_linker_->LookupResolvedType(dex_file, type_idx, dex_cache.Get(), class_loader.Get()), + klass.Get()); +} + TEST_F(ClassLinkerTest, LibCore) { ScopedObjectAccess soa(Thread::Current()); ASSERT_TRUE(java_lang_dex_file_ != nullptr); diff --git a/runtime/class_table.cc b/runtime/class_table.cc index 0f985c6424..ff846a718e 100644 --- a/runtime/class_table.cc +++ b/runtime/class_table.cc @@ -129,6 +129,19 @@ void ClassTable::Insert(ObjPtr<mirror::Class> klass) { classes_.back().InsertWithHash(TableSlot(klass, hash), hash); } +void ClassTable::CopyWithoutLocks(const ClassTable& source_table) { + if (kIsDebugBuild) { + for (ClassSet& class_set : classes_) { + CHECK(class_set.Empty()); + } + } + for (const ClassSet& class_set : source_table.classes_) { + for (const TableSlot& slot : class_set) { + classes_.back().Insert(slot); + } + } +} + void ClassTable::InsertWithoutLocks(ObjPtr<mirror::Class> klass) { const uint32_t hash = TableSlot::HashDescriptor(klass); classes_.back().InsertWithHash(TableSlot(klass, hash), hash); diff --git a/runtime/class_table.h b/runtime/class_table.h index f27d8093ce..c8ec28eca4 100644 --- a/runtime/class_table.h +++ b/runtime/class_table.h @@ -240,6 +240,7 @@ class ClassTable { } private: + void CopyWithoutLocks(const ClassTable& source_table) NO_THREAD_SAFETY_ANALYSIS; void InsertWithoutLocks(ObjPtr<mirror::Class> klass) NO_THREAD_SAFETY_ANALYSIS; size_t CountDefiningLoaderClasses(ObjPtr<mirror::ClassLoader> defining_loader, diff --git a/runtime/common_runtime_test.cc b/runtime/common_runtime_test.cc index 743fcc87eb..fc82264b6a 100644 --- a/runtime/common_runtime_test.cc +++ b/runtime/common_runtime_test.cc @@ -133,7 +133,9 @@ void ScratchFile::Unlink() { static bool unstarted_initialized_ = false; -CommonRuntimeTestImpl::CommonRuntimeTestImpl() {} +CommonRuntimeTestImpl::CommonRuntimeTestImpl() + : class_linker_(nullptr), java_lang_dex_file_(nullptr) { +} CommonRuntimeTestImpl::~CommonRuntimeTestImpl() { // Ensure the dex files are cleaned up before the runtime. @@ -425,7 +427,9 @@ void CommonRuntimeTestImpl::TearDown() { TearDownAndroidData(android_data_, true); dalvik_cache_.clear(); - Runtime::Current()->GetHeap()->VerifyHeap(); // Check for heap corruption after the test + if (runtime_ != nullptr) { + runtime_->GetHeap()->VerifyHeap(); // Check for heap corruption after the test + } } static std::string GetDexFileName(const std::string& jar_prefix, bool host) { diff --git a/runtime/debugger.cc b/runtime/debugger.cc index 006476bafc..22a31635a6 100644 --- a/runtime/debugger.cc +++ b/runtime/debugger.cc @@ -320,6 +320,9 @@ size_t Dbg::field_write_event_ref_count_ = 0; size_t Dbg::exception_catch_event_ref_count_ = 0; uint32_t Dbg::instrumentation_events_ = 0; +Dbg::DbgThreadLifecycleCallback Dbg::thread_lifecycle_callback_; +Dbg::DbgClassLoadCallback Dbg::class_load_callback_; + // Breakpoints. static std::vector<Breakpoint> gBreakpoints GUARDED_BY(Locks::breakpoint_lock_); @@ -5135,4 +5138,20 @@ void Dbg::VisitRoots(RootVisitor* visitor) { } } +void Dbg::DbgThreadLifecycleCallback::ThreadStart(Thread* self) { + Dbg::PostThreadStart(self); +} + +void Dbg::DbgThreadLifecycleCallback::ThreadDeath(Thread* self) { + Dbg::PostThreadDeath(self); +} + +void Dbg::DbgClassLoadCallback::ClassLoad(Handle<mirror::Class> klass ATTRIBUTE_UNUSED) { + // Ignore ClassLoad; +} +void Dbg::DbgClassLoadCallback::ClassPrepare(Handle<mirror::Class> temp_klass ATTRIBUTE_UNUSED, + Handle<mirror::Class> klass) { + Dbg::PostClassPrepare(klass.Get()); +} + } // namespace art diff --git a/runtime/debugger.h b/runtime/debugger.h index 3b4a5e16b0..a7fd1605df 100644 --- a/runtime/debugger.h +++ b/runtime/debugger.h @@ -28,6 +28,8 @@ #include <vector> #include "gc_root.h" +#include "class_linker.h" +#include "handle.h" #include "jdwp/jdwp.h" #include "jni.h" #include "jvalue.h" @@ -502,12 +504,6 @@ class Dbg { REQUIRES_SHARED(Locks::mutator_lock_); static void PostException(mirror::Throwable* exception) REQUIRES_SHARED(Locks::mutator_lock_); - static void PostThreadStart(Thread* t) - REQUIRES_SHARED(Locks::mutator_lock_); - static void PostThreadDeath(Thread* t) - REQUIRES_SHARED(Locks::mutator_lock_); - static void PostClassPrepare(mirror::Class* c) - REQUIRES_SHARED(Locks::mutator_lock_); static void UpdateDebugger(Thread* thread, mirror::Object* this_object, ArtMethod* method, uint32_t new_dex_pc, @@ -707,6 +703,13 @@ class Dbg { return instrumentation_events_; } + static ThreadLifecycleCallback* GetThreadLifecycleCallback() { + return &thread_lifecycle_callback_; + } + static ClassLoadCallback* GetClassLoadCallback() { + return &class_load_callback_; + } + private: static void ExecuteMethodWithoutPendingException(ScopedObjectAccess& soa, DebugInvokeReq* pReq) REQUIRES_SHARED(Locks::mutator_lock_); @@ -725,9 +728,17 @@ class Dbg { REQUIRES(!Locks::thread_list_lock_) REQUIRES_SHARED(Locks::mutator_lock_); static void DdmBroadcast(bool connect) REQUIRES_SHARED(Locks::mutator_lock_); + + static void PostThreadStart(Thread* t) + REQUIRES_SHARED(Locks::mutator_lock_); + static void PostThreadDeath(Thread* t) + REQUIRES_SHARED(Locks::mutator_lock_); static void PostThreadStartOrStop(Thread*, uint32_t) REQUIRES_SHARED(Locks::mutator_lock_); + static void PostClassPrepare(mirror::Class* c) + REQUIRES_SHARED(Locks::mutator_lock_); + static void PostLocationEvent(ArtMethod* method, int pcOffset, mirror::Object* thisPtr, int eventFlags, const JValue* return_value) @@ -789,6 +800,22 @@ class Dbg { static size_t exception_catch_event_ref_count_ GUARDED_BY(Locks::deoptimization_lock_); static uint32_t instrumentation_events_ GUARDED_BY(Locks::mutator_lock_); + class DbgThreadLifecycleCallback : public ThreadLifecycleCallback { + public: + void ThreadStart(Thread* self) OVERRIDE REQUIRES_SHARED(Locks::mutator_lock_); + void ThreadDeath(Thread* self) OVERRIDE REQUIRES_SHARED(Locks::mutator_lock_); + }; + + class DbgClassLoadCallback : public ClassLoadCallback { + public: + void ClassLoad(Handle<mirror::Class> klass) OVERRIDE REQUIRES_SHARED(Locks::mutator_lock_); + void ClassPrepare(Handle<mirror::Class> temp_klass, + Handle<mirror::Class> klass) OVERRIDE REQUIRES_SHARED(Locks::mutator_lock_); + }; + + static DbgThreadLifecycleCallback thread_lifecycle_callback_; + static DbgClassLoadCallback class_load_callback_; + DISALLOW_COPY_AND_ASSIGN(Dbg); }; diff --git a/runtime/dex2oat_environment_test.h b/runtime/dex2oat_environment_test.h index b0c4597d73..7ae9f03c83 100644 --- a/runtime/dex2oat_environment_test.h +++ b/runtime/dex2oat_environment_test.h @@ -160,7 +160,7 @@ class Dex2oatEnvironmentTest : public CommonRuntimeTest { // image at GetImageLocation(). This is used for testing mismatched // image checksums in the oat_file_assistant_tests. std::string GetImageLocation2() const { - return GetImageDirectory() + "/core-npic.art"; + return GetImageDirectory() + "/core-interpreter.art"; } std::string GetDexSrc1() const { diff --git a/runtime/dex_cache_resolved_classes.h b/runtime/dex_cache_resolved_classes.h index f53ca4acb6..bebdf0dbfe 100644 --- a/runtime/dex_cache_resolved_classes.h +++ b/runtime/dex_cache_resolved_classes.h @@ -44,6 +44,10 @@ class DexCacheResolvedClasses { return dex_location_.compare(other.dex_location_); } + bool AddClass(dex::TypeIndex index) const { + return classes_.insert(index).second; + } + template <class InputIt> void AddClasses(InputIt begin, InputIt end) const { classes_.insert(begin, end); diff --git a/runtime/dexopt_test.cc b/runtime/dexopt_test.cc new file mode 100644 index 0000000000..69c6151d9e --- /dev/null +++ b/runtime/dexopt_test.cc @@ -0,0 +1,236 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <string> +#include <vector> + +#include <backtrace/BacktraceMap.h> +#include <gtest/gtest.h> + +#include "common_runtime_test.h" +#include "compiler_callbacks.h" +#include "dex2oat_environment_test.h" +#include "dexopt_test.h" +#include "gc/space/image_space.h" +#include "mem_map.h" + +namespace art { +void DexoptTest::SetUp() { + ReserveImageSpace(); + Dex2oatEnvironmentTest::SetUp(); +} + +void DexoptTest::PreRuntimeCreate() { + std::string error_msg; + ASSERT_TRUE(PreRelocateImage(GetImageLocation(), &error_msg)) << error_msg; + ASSERT_TRUE(PreRelocateImage(GetImageLocation2(), &error_msg)) << error_msg; + UnreserveImageSpace(); +} + +void DexoptTest::PostRuntimeCreate() { + ReserveImageSpace(); +} + +void DexoptTest::GenerateOatForTest(const std::string& dex_location, + const std::string& oat_location, + CompilerFilter::Filter filter, + bool relocate, + bool pic, + bool with_alternate_image) { + std::string dalvik_cache = GetDalvikCache(GetInstructionSetString(kRuntimeISA)); + std::string dalvik_cache_tmp = dalvik_cache + ".redirected"; + + if (!relocate) { + // Temporarily redirect the dalvik cache so dex2oat doesn't find the + // relocated image file. + ASSERT_EQ(0, rename(dalvik_cache.c_str(), dalvik_cache_tmp.c_str())) << strerror(errno); + } + + std::vector<std::string> args; + args.push_back("--dex-file=" + dex_location); + args.push_back("--oat-file=" + oat_location); + args.push_back("--compiler-filter=" + CompilerFilter::NameOfFilter(filter)); + args.push_back("--runtime-arg"); + + // Use -Xnorelocate regardless of the relocate argument. + // We control relocation by redirecting the dalvik cache when needed + // rather than use this flag. + args.push_back("-Xnorelocate"); + + if (pic) { + args.push_back("--compile-pic"); + } + + std::string image_location = GetImageLocation(); + if (with_alternate_image) { + args.push_back("--boot-image=" + GetImageLocation2()); + } + + std::string error_msg; + ASSERT_TRUE(OatFileAssistant::Dex2Oat(args, &error_msg)) << error_msg; + + if (!relocate) { + // Restore the dalvik cache if needed. + ASSERT_EQ(0, rename(dalvik_cache_tmp.c_str(), dalvik_cache.c_str())) << strerror(errno); + } + + // Verify the odex file was generated as expected. + std::unique_ptr<OatFile> odex_file(OatFile::Open(oat_location.c_str(), + oat_location.c_str(), + nullptr, + nullptr, + false, + /*low_4gb*/false, + dex_location.c_str(), + &error_msg)); + ASSERT_TRUE(odex_file.get() != nullptr) << error_msg; + EXPECT_EQ(pic, odex_file->IsPic()); + EXPECT_EQ(filter, odex_file->GetCompilerFilter()); + + std::unique_ptr<ImageHeader> image_header( + gc::space::ImageSpace::ReadImageHeader(image_location.c_str(), + kRuntimeISA, + &error_msg)); + ASSERT_TRUE(image_header != nullptr) << error_msg; + const OatHeader& oat_header = odex_file->GetOatHeader(); + uint32_t combined_checksum = OatFileAssistant::CalculateCombinedImageChecksum(); + + if (CompilerFilter::DependsOnImageChecksum(filter)) { + if (with_alternate_image) { + EXPECT_NE(combined_checksum, oat_header.GetImageFileLocationOatChecksum()); + } else { + EXPECT_EQ(combined_checksum, oat_header.GetImageFileLocationOatChecksum()); + } + } + + if (!with_alternate_image) { + if (CompilerFilter::IsBytecodeCompilationEnabled(filter)) { + if (relocate) { + EXPECT_EQ(reinterpret_cast<uintptr_t>(image_header->GetOatDataBegin()), + oat_header.GetImageFileLocationOatDataBegin()); + EXPECT_EQ(image_header->GetPatchDelta(), oat_header.GetImagePatchDelta()); + } else { + EXPECT_NE(reinterpret_cast<uintptr_t>(image_header->GetOatDataBegin()), + oat_header.GetImageFileLocationOatDataBegin()); + EXPECT_NE(image_header->GetPatchDelta(), oat_header.GetImagePatchDelta()); + } + } + } +} + +void DexoptTest::GenerateOdexForTest(const std::string& dex_location, + const std::string& odex_location, + CompilerFilter::Filter filter) { + GenerateOatForTest(dex_location, + odex_location, + filter, + /*relocate*/false, + /*pic*/false, + /*with_alternate_image*/false); +} + +void DexoptTest::GeneratePicOdexForTest(const std::string& dex_location, + const std::string& odex_location, + CompilerFilter::Filter filter) { + GenerateOatForTest(dex_location, + odex_location, + filter, + /*relocate*/false, + /*pic*/true, + /*with_alternate_image*/false); +} + +void DexoptTest::GenerateOatForTest(const char* dex_location, + CompilerFilter::Filter filter, + bool relocate, + bool pic, + bool with_alternate_image) { + std::string oat_location; + std::string error_msg; + ASSERT_TRUE(OatFileAssistant::DexLocationToOatFilename( + dex_location, kRuntimeISA, &oat_location, &error_msg)) << error_msg; + GenerateOatForTest(dex_location, + oat_location, + filter, + relocate, + pic, + with_alternate_image); +} + +void DexoptTest::GenerateOatForTest(const char* dex_location, CompilerFilter::Filter filter) { + GenerateOatForTest(dex_location, + filter, + /*relocate*/true, + /*pic*/false, + /*with_alternate_image*/false); +} + +bool DexoptTest::PreRelocateImage(const std::string& image_location, std::string* error_msg) { + std::string image; + if (!GetCachedImageFile(image_location, &image, error_msg)) { + return false; + } + + std::string patchoat = GetAndroidRoot(); + patchoat += kIsDebugBuild ? "/bin/patchoatd" : "/bin/patchoat"; + + std::vector<std::string> argv; + argv.push_back(patchoat); + argv.push_back("--input-image-location=" + image_location); + argv.push_back("--output-image-file=" + image); + argv.push_back("--instruction-set=" + std::string(GetInstructionSetString(kRuntimeISA))); + argv.push_back("--base-offset-delta=0x00008000"); + return Exec(argv, error_msg); +} + +void DexoptTest::ReserveImageSpace() { + MemMap::Init(); + + // Ensure a chunk of memory is reserved for the image space. + // The reservation_end includes room for the main space that has to come + // right after the image in case of the GSS collector. + uintptr_t reservation_start = ART_BASE_ADDRESS; + uintptr_t reservation_end = ART_BASE_ADDRESS + 384 * MB; + + std::unique_ptr<BacktraceMap> map(BacktraceMap::Create(getpid(), true)); + ASSERT_TRUE(map.get() != nullptr) << "Failed to build process map"; + for (BacktraceMap::const_iterator it = map->begin(); + reservation_start < reservation_end && it != map->end(); ++it) { + ReserveImageSpaceChunk(reservation_start, std::min(it->start, reservation_end)); + reservation_start = std::max(reservation_start, it->end); + } + ReserveImageSpaceChunk(reservation_start, reservation_end); +} + +void DexoptTest::ReserveImageSpaceChunk(uintptr_t start, uintptr_t end) { + if (start < end) { + std::string error_msg; + image_reservation_.push_back(std::unique_ptr<MemMap>( + MemMap::MapAnonymous("image reservation", + reinterpret_cast<uint8_t*>(start), end - start, + PROT_NONE, false, false, &error_msg))); + ASSERT_TRUE(image_reservation_.back().get() != nullptr) << error_msg; + LOG(INFO) << "Reserved space for image " << + reinterpret_cast<void*>(image_reservation_.back()->Begin()) << "-" << + reinterpret_cast<void*>(image_reservation_.back()->End()); + } +} + +void DexoptTest::UnreserveImageSpace() { + image_reservation_.clear(); +} + +} // namespace art diff --git a/runtime/dexopt_test.h b/runtime/dexopt_test.h new file mode 100644 index 0000000000..5f0eafd8eb --- /dev/null +++ b/runtime/dexopt_test.h @@ -0,0 +1,97 @@ +/* + * 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. + */ + +#ifndef ART_RUNTIME_DEXOPT_TEST_H_ +#define ART_RUNTIME_DEXOPT_TEST_H_ + +#include <string> +#include <vector> + +#include "dex2oat_environment_test.h" + +namespace art { + +class DexoptTest : public Dex2oatEnvironmentTest { + public: + virtual void SetUp() OVERRIDE; + + virtual void PreRuntimeCreate(); + + virtual void PostRuntimeCreate() OVERRIDE; + + // Generate an oat file for the purposes of test. + // The oat file will be generated for dex_location in the given oat_location + // with the following configuration: + // filter - controls the compilation filter + // pic - whether or not the code will be PIC + // relocate - if true, the oat file will be relocated with respect to the + // boot image. Otherwise the oat file will not be relocated. + // with_alternate_image - if true, the oat file will be generated with an + // image checksum different than the current image checksum. + void GenerateOatForTest(const std::string& dex_location, + const std::string& oat_location, + CompilerFilter::Filter filter, + bool relocate, + bool pic, + bool with_alternate_image); + + // Generate a non-PIC odex file for the purposes of test. + // The generated odex file will be un-relocated. + void GenerateOdexForTest(const std::string& dex_location, + const std::string& odex_location, + CompilerFilter::Filter filter); + + void GeneratePicOdexForTest(const std::string& dex_location, + const std::string& odex_location, + CompilerFilter::Filter filter); + + // Generate an oat file for the given dex location in its oat location (under + // the dalvik cache). + void GenerateOatForTest(const char* dex_location, + CompilerFilter::Filter filter, + bool relocate, + bool pic, + bool with_alternate_image); + + // Generate a standard oat file in the oat location. + void GenerateOatForTest(const char* dex_location, CompilerFilter::Filter filter); + + private: + // Pre-Relocate the image to a known non-zero offset so we don't have to + // deal with the runtime randomly relocating the image by 0 and messing up + // the expected results of the tests. + bool PreRelocateImage(const std::string& image_location, std::string* error_msg); + + // Reserve memory around where the image will be loaded so other memory + // won't conflict when it comes time to load the image. + // This can be called with an already loaded image to reserve the space + // around it. + void ReserveImageSpace(); + + // Reserve a chunk of memory for the image space in the given range. + // Only has effect for chunks with a positive number of bytes. + void ReserveImageSpaceChunk(uintptr_t start, uintptr_t end); + + // Unreserve any memory reserved by ReserveImageSpace. This should be called + // before the image is loaded. + void UnreserveImageSpace(); + + std::vector<std::unique_ptr<MemMap>> image_reservation_; +}; + +} // namespace art + +#endif // ART_RUNTIME_DEXOPT_TEST_H_ diff --git a/runtime/experimental_flags.h b/runtime/experimental_flags.h index 5ddb9fa27a..0471c969f9 100644 --- a/runtime/experimental_flags.h +++ b/runtime/experimental_flags.h @@ -26,8 +26,6 @@ struct ExperimentalFlags { // The actual flag values. enum { kNone = 0x0000, - kAgents = 0x0001, // 0b00000001 - kRuntimePlugins = 0x0002, // 0b00000010 kMethodHandles = 0x0004, // 0b00000100 }; @@ -67,14 +65,6 @@ struct ExperimentalFlags { inline std::ostream& operator<<(std::ostream& stream, const ExperimentalFlags& e) { bool started = false; - if (e & ExperimentalFlags::kAgents) { - stream << (started ? "|" : "") << "kAgents"; - started = true; - } - if (e & ExperimentalFlags::kRuntimePlugins) { - stream << (started ? "|" : "") << "kRuntimePlugins"; - started = true; - } if (e & ExperimentalFlags::kMethodHandles) { stream << (started ? "|" : "") << "kMethodHandles"; started = true; diff --git a/runtime/gc/heap.cc b/runtime/gc/heap.cc index 34d82845dc..70449797c1 100644 --- a/runtime/gc/heap.cc +++ b/runtime/gc/heap.cc @@ -360,7 +360,7 @@ Heap::Heap(size_t initial_size, // If we are the zygote, the non moving space becomes the zygote space when we run // PreZygoteFork the first time. In this case, call the map "zygote space" since we can't // rename the mem map later. - const char* space_name = is_zygote ? kZygoteSpaceName: kNonMovingSpaceName; + const char* space_name = is_zygote ? kZygoteSpaceName : kNonMovingSpaceName; // Reserve the non moving mem map before the other two since it needs to be at a specific // address. non_moving_space_mem_map.reset( @@ -3543,8 +3543,11 @@ void Heap::GrowForUtilization(collector::GarbageCollector* collector_ran, collector::GcType gc_type = collector_ran->GetGcType(); const double multiplier = HeapGrowthMultiplier(); // Use the multiplier to grow more for // foreground. - const uint64_t adjusted_min_free = static_cast<uint64_t>(min_free_ * multiplier); - const uint64_t adjusted_max_free = static_cast<uint64_t>(max_free_ * multiplier); + // Ensure at least 2.5 MB to temporarily fix excessive GC caused by TLAB ergonomics. + const uint64_t adjusted_min_free = std::max(static_cast<uint64_t>(min_free_ * multiplier), + static_cast<uint64_t>(5 * MB / 2)); + const uint64_t adjusted_max_free = std::max(static_cast<uint64_t>(max_free_ * multiplier), + static_cast<uint64_t>(5 * MB / 2)); if (gc_type != collector::kGcTypeSticky) { // Grow the heap for non sticky GC. ssize_t delta = bytes_allocated / GetTargetHeapUtilization() - bytes_allocated; diff --git a/runtime/gc/reference_processor.h b/runtime/gc/reference_processor.h index b15544d549..38b68cbbe8 100644 --- a/runtime/gc/reference_processor.h +++ b/runtime/gc/reference_processor.h @@ -42,7 +42,7 @@ class GarbageCollector; class Heap; -// Used to process java.lang.References concurrently or paused. +// Used to process java.lang.ref.Reference instances concurrently or paused. class ReferenceProcessor { public: explicit ReferenceProcessor(); diff --git a/runtime/gc/scoped_gc_critical_section.h b/runtime/gc/scoped_gc_critical_section.h index ec93bca802..1271ff7af3 100644 --- a/runtime/gc/scoped_gc_critical_section.h +++ b/runtime/gc/scoped_gc_critical_section.h @@ -27,8 +27,8 @@ class Thread; namespace gc { -// Wait until the GC is finished and then prevent GC from starting until the destructor. Used -// to prevent deadlocks in places where we call ClassLinker::VisitClass with all th threads +// Wait until the GC is finished and then prevent the GC from starting until the destructor. Used +// to prevent deadlocks in places where we call ClassLinker::VisitClass with all the threads // suspended. class ScopedGCCriticalSection { public: diff --git a/runtime/gc/space/large_object_space.cc b/runtime/gc/space/large_object_space.cc index e71a397039..4c6b5bfadd 100644 --- a/runtime/gc/space/large_object_space.cc +++ b/runtime/gc/space/large_object_space.cc @@ -141,16 +141,6 @@ mirror::Object* LargeObjectMapSpace::Alloc(Thread* self, size_t num_bytes, return nullptr; } mirror::Object* const obj = reinterpret_cast<mirror::Object*>(mem_map->Begin()); - if (kIsDebugBuild) { - ReaderMutexLock mu2(Thread::Current(), *Locks::heap_bitmap_lock_); - auto* heap = Runtime::Current()->GetHeap(); - auto* live_bitmap = heap->GetLiveBitmap(); - auto* space_bitmap = live_bitmap->GetContinuousSpaceBitmap(obj); - CHECK(space_bitmap == nullptr) << obj << " overlaps with bitmap " << *space_bitmap; - auto* obj_end = reinterpret_cast<mirror::Object*>(mem_map->End()); - space_bitmap = live_bitmap->GetContinuousSpaceBitmap(obj_end - 1); - CHECK(space_bitmap == nullptr) << obj_end << " overlaps with bitmap " << *space_bitmap; - } MutexLock mu(self, lock_); large_objects_.Put(obj, LargeObject {mem_map, false /* not zygote */}); const size_t allocation_size = mem_map->BaseSize(); diff --git a/runtime/hprof/hprof.cc b/runtime/hprof/hprof.cc index fe6a6e9140..3d3ad593b3 100644 --- a/runtime/hprof/hprof.cc +++ b/runtime/hprof/hprof.cc @@ -1169,7 +1169,7 @@ void Hprof::DumpHeapObject(mirror::Object* obj) { } void Hprof::DumpHeapClass(mirror::Class* klass) { - if (!klass->IsResolved() && !klass->IsErroneous()) { + if (!klass->IsResolved()) { // Class is allocated but not yet resolved: we cannot access its fields or super class. return; } diff --git a/runtime/image.cc b/runtime/image.cc index 6d8889532f..54b099eb14 100644 --- a/runtime/image.cc +++ b/runtime/image.cc @@ -25,7 +25,7 @@ namespace art { const uint8_t ImageHeader::kImageMagic[] = { 'a', 'r', 't', '\n' }; -const uint8_t ImageHeader::kImageVersion[] = { '0', '3', '5', '\0' }; // ArtMethod update +const uint8_t ImageHeader::kImageVersion[] = { '0', '3', '6', '\0' }; // Erroneous resolved class. ImageHeader::ImageHeader(uint32_t image_begin, uint32_t image_size, diff --git a/runtime/instrumentation.cc b/runtime/instrumentation.cc index 4ea1130947..bbd6d352d3 100644 --- a/runtime/instrumentation.cc +++ b/runtime/instrumentation.cc @@ -88,11 +88,11 @@ Instrumentation::Instrumentation() } void Instrumentation::InstallStubsForClass(mirror::Class* klass) { - if (klass->IsErroneous()) { - // We can't execute code in a erroneous class: do nothing. - } else if (!klass->IsResolved()) { + if (!klass->IsResolved()) { // We need the class to be resolved to install/uninstall stubs. Otherwise its methods // could not be initialized or linked with regards to class inheritance. + } else if (klass->IsErroneousResolved()) { + // We can't execute code in a erroneous class: do nothing. } else { for (ArtMethod& method : klass->GetMethods(kRuntimePointerSize)) { InstallStubsForMethod(&method); diff --git a/runtime/interpreter/interpreter_common.cc b/runtime/interpreter/interpreter_common.cc index 76777d938b..28bcb97105 100644 --- a/runtime/interpreter/interpreter_common.cc +++ b/runtime/interpreter/interpreter_common.cc @@ -596,7 +596,7 @@ bool DoInvokePolymorphic(Thread* self, // Get the register arguments for the invoke. inst->GetVarArgs(args, inst_data); // Drop the first register which is the method handle performing the invoke. - memcpy(args, args + 1, sizeof(args[0]) * (Instruction::kMaxVarArgRegs - 1)); + memmove(args, args + 1, sizeof(args[0]) * (Instruction::kMaxVarArgRegs - 1)); args[Instruction::kMaxVarArgRegs - 1] = 0; return DoInvokePolymorphic<is_range, do_access_check>(self, invoke_method, diff --git a/runtime/interpreter/mterp/arm64/op_iput_wide_quick.S b/runtime/interpreter/mterp/arm64/op_iput_wide_quick.S index 6cec3633a6..28e831a5f8 100644 --- a/runtime/interpreter/mterp/arm64/op_iput_wide_quick.S +++ b/runtime/interpreter/mterp/arm64/op_iput_wide_quick.S @@ -4,7 +4,7 @@ GET_VREG w2, w2 // w2<- fp[B], the object pointer ubfx w0, wINST, #8, #4 // w0<- A cbz w2, common_errNullObject // object was null - GET_VREG_WIDE x0, w0 // x0-< fp[A] + GET_VREG_WIDE x0, w0 // x0<- fp[A] FETCH_ADVANCE_INST 2 // advance rPC, load wINST str x0, [x2, x3] // obj.field<- x0 GET_INST_OPCODE ip // extract opcode from wINST diff --git a/runtime/interpreter/mterp/out/mterp_arm64.S b/runtime/interpreter/mterp/out/mterp_arm64.S index 681790daa8..7d442c0b4b 100644 --- a/runtime/interpreter/mterp/out/mterp_arm64.S +++ b/runtime/interpreter/mterp/out/mterp_arm64.S @@ -6593,7 +6593,7 @@ artMterpAsmInstructionStart = .L_op_nop GET_VREG w2, w2 // w2<- fp[B], the object pointer ubfx w0, wINST, #8, #4 // w0<- A cbz w2, common_errNullObject // object was null - GET_VREG_WIDE x0, w0 // x0-< fp[A] + GET_VREG_WIDE x0, w0 // x0<- fp[A] FETCH_ADVANCE_INST 2 // advance rPC, load wINST str x0, [x2, x3] // obj.field<- x0 GET_INST_OPCODE ip // extract opcode from wINST diff --git a/runtime/interpreter/unstarted_runtime.cc b/runtime/interpreter/unstarted_runtime.cc index 7dd3d3db4d..feb6e0857a 100644 --- a/runtime/interpreter/unstarted_runtime.cc +++ b/runtime/interpreter/unstarted_runtime.cc @@ -1443,7 +1443,7 @@ void UnstartedRuntime::UnstartedMethodInvoke( ObjPtr<mirror::Object> java_method_obj = shadow_frame->GetVRegReference(arg_offset); ScopedLocalRef<jobject> java_method(env, - java_method_obj == nullptr ? nullptr :env->AddLocalReference<jobject>(java_method_obj)); + java_method_obj == nullptr ? nullptr : env->AddLocalReference<jobject>(java_method_obj)); ObjPtr<mirror::Object> java_receiver_obj = shadow_frame->GetVRegReference(arg_offset + 1); ScopedLocalRef<jobject> java_receiver(env, diff --git a/runtime/jit/jit.cc b/runtime/jit/jit.cc index b7125a8a43..6deb03dc41 100644 --- a/runtime/jit/jit.cc +++ b/runtime/jit/jit.cc @@ -26,7 +26,7 @@ #include "jit_code_cache.h" #include "oat_file_manager.h" #include "oat_quick_method_header.h" -#include "offline_profiling_info.h" +#include "profile_compilation_info.h" #include "profile_saver.h" #include "runtime.h" #include "runtime_options.h" @@ -514,7 +514,7 @@ bool Jit::MaybeDoOnStackReplacement(Thread* thread, } } - native_pc = stack_map.GetNativePcOffset(encoding.stack_map_encoding) + + native_pc = stack_map.GetNativePcOffset(encoding.stack_map_encoding, kRuntimeISA) + osr_method->GetEntryPoint(); VLOG(jit) << "Jumping to " << method_name diff --git a/runtime/jit/jit.h b/runtime/jit/jit.h index 05c390590b..4112142a4f 100644 --- a/runtime/jit/jit.h +++ b/runtime/jit/jit.h @@ -25,7 +25,7 @@ #include "jit/profile_saver_options.h" #include "obj_ptr.h" #include "object_callbacks.h" -#include "offline_profiling_info.h" +#include "profile_compilation_info.h" #include "thread_pool.h" namespace art { diff --git a/runtime/jit/offline_profiling_info.cc b/runtime/jit/profile_compilation_info.cc index 6f2a8c673f..1405c40096 100644 --- a/runtime/jit/offline_profiling_info.cc +++ b/runtime/jit/profile_compilation_info.cc @@ -14,7 +14,7 @@ * limitations under the License. */ -#include "offline_profiling_info.h" +#include "profile_compilation_info.h" #include "errno.h" #include <limits.h> diff --git a/runtime/jit/offline_profiling_info.h b/runtime/jit/profile_compilation_info.h index 53d0eea932..f8061bcfd8 100644 --- a/runtime/jit/offline_profiling_info.h +++ b/runtime/jit/profile_compilation_info.h @@ -14,8 +14,8 @@ * limitations under the License. */ -#ifndef ART_RUNTIME_JIT_OFFLINE_PROFILING_INFO_H_ -#define ART_RUNTIME_JIT_OFFLINE_PROFILING_INFO_H_ +#ifndef ART_RUNTIME_JIT_PROFILE_COMPILATION_INFO_H_ +#define ART_RUNTIME_JIT_PROFILE_COMPILATION_INFO_H_ #include <set> #include <vector> @@ -29,7 +29,6 @@ namespace art { -// TODO: rename file. /** * Profile information in a format suitable to be queried by the compiler and * performing profile guided compilation. @@ -187,4 +186,4 @@ class ProfileCompilationInfo { } // namespace art -#endif // ART_RUNTIME_JIT_OFFLINE_PROFILING_INFO_H_ +#endif // ART_RUNTIME_JIT_PROFILE_COMPILATION_INFO_H_ diff --git a/runtime/jit/profile_compilation_info_test.cc b/runtime/jit/profile_compilation_info_test.cc index 1dd1e36e74..835a5f3495 100644 --- a/runtime/jit/profile_compilation_info_test.cc +++ b/runtime/jit/profile_compilation_info_test.cc @@ -25,7 +25,7 @@ #include "mirror/class-inl.h" #include "mirror/class_loader.h" #include "handle_scope-inl.h" -#include "jit/offline_profiling_info.h" +#include "jit/profile_compilation_info.h" #include "scoped_thread_state_change-inl.h" namespace art { diff --git a/runtime/jit/profile_saver.h b/runtime/jit/profile_saver.h index 59e2c94790..9c5e41fd13 100644 --- a/runtime/jit/profile_saver.h +++ b/runtime/jit/profile_saver.h @@ -19,7 +19,7 @@ #include "base/mutex.h" #include "jit_code_cache.h" -#include "offline_profiling_info.h" +#include "profile_compilation_info.h" #include "profile_saver_options.h" #include "safe_map.h" diff --git a/runtime/memory_region.cc b/runtime/memory_region.cc index a5c70c3b26..5bf0f40eff 100644 --- a/runtime/memory_region.cc +++ b/runtime/memory_region.cc @@ -33,4 +33,36 @@ void MemoryRegion::CopyFrom(size_t offset, const MemoryRegion& from) const { from.pointer(), from.size()); } +void MemoryRegion::StoreBits(uintptr_t bit_offset, uint32_t value, size_t length) { + DCHECK_LE(value, MaxInt<uint32_t>(length)); + DCHECK_LE(length, BitSizeOf<uint32_t>()); + DCHECK_LE(bit_offset + length, size_in_bits()); + if (length == 0) { + return; + } + // Bits are stored in this order {7 6 5 4 3 2 1 0}. + // How many remaining bits in current byte is (bit_offset % kBitsPerByte) + 1. + uint8_t* out = ComputeInternalPointer<uint8_t>(bit_offset >> kBitsPerByteLog2); + auto orig_len = length; + auto orig_value = value; + uintptr_t bit_remainder = bit_offset % kBitsPerByte; + while (true) { + const uintptr_t remaining_bits = kBitsPerByte - bit_remainder; + if (length <= remaining_bits) { + // Length is smaller than all of remainder bits. + size_t mask = ((1 << length) - 1) << bit_remainder; + *out = (*out & ~mask) | (value << bit_remainder); + break; + } + // Copy remaining bits in current byte. + size_t value_mask = (1 << remaining_bits) - 1; + *out = (*out & ~(value_mask << bit_remainder)) | ((value & value_mask) << bit_remainder); + value >>= remaining_bits; + bit_remainder = 0; + length -= remaining_bits; + ++out; + } + DCHECK_EQ(LoadBits(bit_offset, orig_len), orig_value) << bit_offset << " " << orig_len; +} + } // namespace art diff --git a/runtime/memory_region.h b/runtime/memory_region.h index f018c1f4c1..f55dff7a50 100644 --- a/runtime/memory_region.h +++ b/runtime/memory_region.h @@ -124,11 +124,35 @@ class MemoryRegion FINAL : public ValueObject { // The bit at the smallest offset is the least significant bit in the // loaded value. `length` must not be larger than the number of bits // contained in the return value (32). - uint32_t LoadBits(uintptr_t bit_offset, size_t length) const { - CHECK_LE(length, sizeof(uint32_t) * kBitsPerByte); - uint32_t value = 0u; + ALWAYS_INLINE uint32_t LoadBits(uintptr_t bit_offset, size_t length) const { + DCHECK_LE(length, BitSizeOf<uint32_t>()); + DCHECK_LE(bit_offset + length, size_in_bits()); + if (UNLIKELY(length == 0)) { + // Do not touch any memory if the range is empty. + return 0; + } + const uint8_t* address = start() + bit_offset / kBitsPerByte; + const uint32_t shift = bit_offset & (kBitsPerByte - 1); + // Load the value (reading only the strictly needed bytes). + const uint32_t load_bit_count = shift + length; + uint32_t value = address[0] >> shift; + if (load_bit_count > 8) { + value |= static_cast<uint32_t>(address[1]) << (8 - shift); + if (load_bit_count > 16) { + value |= static_cast<uint32_t>(address[2]) << (16 - shift); + if (load_bit_count > 24) { + value |= static_cast<uint32_t>(address[3]) << (24 - shift); + if (load_bit_count > 32) { + value |= static_cast<uint32_t>(address[4]) << (32 - shift); + } + } + } + } + // Clear unwanted most significant bits. + uint32_t clear_bit_count = BitSizeOf(value) - length; + value = (value << clear_bit_count) >> clear_bit_count; for (size_t i = 0; i < length; ++i) { - value |= LoadBit(bit_offset + i) << i; + DCHECK_EQ((value >> i) & 1, LoadBit(bit_offset + i)); } return value; } @@ -137,25 +161,19 @@ class MemoryRegion FINAL : public ValueObject { // `bit_offset`. The bit at the smallest offset is the least significant // bit of the stored `value`. `value` must not be larger than `length` // bits. - void StoreBits(uintptr_t bit_offset, uint32_t value, size_t length) { - CHECK_LE(value, MaxInt<uint32_t>(length)); - for (size_t i = 0; i < length; ++i) { - bool ith_bit = value & (1 << i); - StoreBit(bit_offset + i, ith_bit); - } - } + void StoreBits(uintptr_t bit_offset, uint32_t value, size_t length); void CopyFrom(size_t offset, const MemoryRegion& from) const; // Compute a sub memory region based on an existing one. - MemoryRegion Subregion(uintptr_t offset, uintptr_t size_in) const { + ALWAYS_INLINE MemoryRegion Subregion(uintptr_t offset, uintptr_t size_in) const { CHECK_GE(this->size(), size_in); CHECK_LE(offset, this->size() - size_in); return MemoryRegion(reinterpret_cast<void*>(start() + offset), size_in); } // Compute an extended memory region based on an existing one. - void Extend(const MemoryRegion& region, uintptr_t extra) { + ALWAYS_INLINE void Extend(const MemoryRegion& region, uintptr_t extra) { pointer_ = region.pointer(); size_ = (region.size() + extra); } diff --git a/runtime/mirror/array-inl.h b/runtime/mirror/array-inl.h index a5db0c0a8a..f56226bd98 100644 --- a/runtime/mirror/array-inl.h +++ b/runtime/mirror/array-inl.h @@ -207,6 +207,19 @@ inline void PrimitiveArray<T>::VisitRoots(RootVisitor* visitor) { } template<typename T> +inline PrimitiveArray<T>* PrimitiveArray<T>::AllocateAndFill(Thread* self, + const T* data, + size_t length) { + StackHandleScope<1> hs(self); + Handle<PrimitiveArray<T>> arr(hs.NewHandle(PrimitiveArray<T>::Alloc(self, length))); + if (!arr.IsNull()) { + // Copy it in. Just skip if it's null + memcpy(arr->GetData(), data, sizeof(T) * length); + } + return arr.Get(); +} + +template<typename T> inline PrimitiveArray<T>* PrimitiveArray<T>::Alloc(Thread* self, size_t length) { Array* raw_array = Array::Alloc<true>(self, GetArrayClass(), diff --git a/runtime/mirror/array.h b/runtime/mirror/array.h index 19d300e1f4..16cf30f1e2 100644 --- a/runtime/mirror/array.h +++ b/runtime/mirror/array.h @@ -119,6 +119,10 @@ class MANAGED PrimitiveArray : public Array { static PrimitiveArray<T>* Alloc(Thread* self, size_t length) REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(!Roles::uninterruptible_); + static PrimitiveArray<T>* AllocateAndFill(Thread* self, const T* data, size_t length) + REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(!Roles::uninterruptible_); + + const T* GetData() const ALWAYS_INLINE REQUIRES_SHARED(Locks::mutator_lock_) { return reinterpret_cast<const T*>(GetRawData(sizeof(T), 0)); } diff --git a/runtime/mirror/class.cc b/runtime/mirror/class.cc index 9964b73c98..f08d4daf95 100644 --- a/runtime/mirror/class.cc +++ b/runtime/mirror/class.cc @@ -115,7 +115,9 @@ void Class::SetStatus(Handle<Class> h_this, Status new_status, Thread* self) { ClassLinker* class_linker = Runtime::Current()->GetClassLinker(); bool class_linker_initialized = class_linker != nullptr && class_linker->IsInitialized(); if (LIKELY(class_linker_initialized)) { - if (UNLIKELY(new_status <= old_status && new_status != kStatusError && + if (UNLIKELY(new_status <= old_status && + new_status != kStatusErrorUnresolved && + new_status != kStatusErrorResolved && new_status != kStatusRetired)) { LOG(FATAL) << "Unexpected change back of class status for " << h_this->PrettyClass() << " " << old_status << " -> " << new_status; @@ -127,10 +129,12 @@ void Class::SetStatus(Handle<Class> h_this, Status new_status, Thread* self) { << h_this->PrettyClass() << " " << old_status << " -> " << new_status; } } - if (UNLIKELY(new_status == kStatusError)) { - CHECK_NE(h_this->GetStatus(), kStatusError) + if (UNLIKELY(IsErroneous(new_status))) { + CHECK(!h_this->IsErroneous()) << "Attempt to set as erroneous an already erroneous class " - << h_this->PrettyClass(); + << h_this->PrettyClass() + << " old_status: " << old_status << " new_status: " << new_status; + CHECK_EQ(new_status == kStatusErrorResolved, old_status >= kStatusResolved); if (VLOG_IS_ON(class_linker)) { LOG(ERROR) << "Setting " << h_this->PrettyDescriptor() << " to erroneous."; if (self->IsExceptionPending()) { @@ -177,7 +181,7 @@ void Class::SetStatus(Handle<Class> h_this, Status new_status, Thread* self) { // Class is a temporary one, ensure that waiters for resolution get notified of retirement // so that they can grab the new version of the class from the class linker's table. CHECK_LT(new_status, kStatusResolved) << h_this->PrettyDescriptor(); - if (new_status == kStatusRetired || new_status == kStatusError) { + if (new_status == kStatusRetired || new_status == kStatusErrorUnresolved) { h_this->NotifyAll(self); } } else { @@ -305,7 +309,7 @@ void Class::DumpClass(std::ostream& os, int flags) { } if (h_this->NumStaticFields() > 0) { os << " static fields (" << h_this->NumStaticFields() << " entries):\n"; - if (h_this->IsResolved() || h_this->IsErroneous()) { + if (h_this->IsResolved()) { for (size_t i = 0; i < h_this->NumStaticFields(); ++i) { os << StringPrintf(" %2zd: %s\n", i, ArtField::PrettyField(h_this->GetStaticField(i)).c_str()); @@ -316,7 +320,7 @@ void Class::DumpClass(std::ostream& os, int flags) { } if (h_this->NumInstanceFields() > 0) { os << " instance fields (" << h_this->NumInstanceFields() << " entries):\n"; - if (h_this->IsResolved() || h_this->IsErroneous()) { + if (h_this->IsResolved()) { for (size_t i = 0; i < h_this->NumInstanceFields(); ++i) { os << StringPrintf(" %2zd: %s\n", i, ArtField::PrettyField(h_this->GetInstanceField(i)).c_str()); diff --git a/runtime/mirror/class.h b/runtime/mirror/class.h index fb2792a316..7f6aa12fdb 100644 --- a/runtime/mirror/class.h +++ b/runtime/mirror/class.h @@ -84,6 +84,13 @@ class MANAGED Class FINAL : public Object { // will be gc'ed once all refs to the class point to the newly // cloned version. // + // kStatusErrorUnresolved, kStatusErrorResolved: Class is erroneous. We need + // to distinguish between classes that have been resolved and classes that + // have not. This is important because the const-class instruction needs to + // return a previously resolved class even if its subsequent initialization + // failed. We also need this to decide whether to wrap a previous + // initialization failure in ClassDefNotFound error or not. + // // kStatusNotReady: If a Class cannot be found in the class table by // FindClass, it allocates an new one with AllocClass in the // kStatusNotReady and calls LoadClass. Note if it does find a @@ -119,8 +126,9 @@ class MANAGED Class FINAL : public Object { // // TODO: Explain the other states enum Status { - kStatusRetired = -2, // Retired, should not be used. Use the newly cloned one instead. - kStatusError = -1, + kStatusRetired = -3, // Retired, should not be used. Use the newly cloned one instead. + kStatusErrorResolved = -2, + kStatusErrorUnresolved = -1, kStatusNotReady = 0, kStatusIdx = 1, // Loaded, DEX idx in super_class_type_idx_ and interfaces_type_idx_. kStatusLoaded = 2, // DEX idx values resolved. @@ -158,8 +166,25 @@ class MANAGED Class FINAL : public Object { // Returns true if the class has failed to link. template<VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags> + bool IsErroneousUnresolved() REQUIRES_SHARED(Locks::mutator_lock_) { + return GetStatus<kVerifyFlags>() == kStatusErrorUnresolved; + } + + // Returns true if the class has failed to initialize. + template<VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags> + bool IsErroneousResolved() REQUIRES_SHARED(Locks::mutator_lock_) { + return GetStatus<kVerifyFlags>() == kStatusErrorResolved; + } + + // Returns true if the class status indicets that the class has failed to link or initialize. + static bool IsErroneous(Status status) { + return status == kStatusErrorUnresolved || status == kStatusErrorResolved; + } + + // Returns true if the class has failed to link or initialize. + template<VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags> bool IsErroneous() REQUIRES_SHARED(Locks::mutator_lock_) { - return GetStatus<kVerifyFlags>() == kStatusError; + return IsErroneous(GetStatus<kVerifyFlags>()); } // Returns true if the class has been loaded. @@ -177,7 +202,8 @@ class MANAGED Class FINAL : public Object { // Returns true if the class has been linked. template<VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags> bool IsResolved() REQUIRES_SHARED(Locks::mutator_lock_) { - return GetStatus<kVerifyFlags>() >= kStatusResolved; + Status status = GetStatus<kVerifyFlags>(); + return status >= kStatusResolved || status == kStatusErrorResolved; } // Returns true if the class was compile-time verified. @@ -345,7 +371,7 @@ class MANAGED Class FINAL : public Object { // be replaced with a class with the right size for embedded imt/vtable. bool IsTemp() REQUIRES_SHARED(Locks::mutator_lock_) { Status s = GetStatus(); - return s < Status::kStatusResolving && ShouldHaveEmbeddedVTable(); + return s < Status::kStatusResolving && s != kStatusErrorResolved && ShouldHaveEmbeddedVTable(); } String* GetName() REQUIRES_SHARED(Locks::mutator_lock_); // Returns the cached name. @@ -1017,7 +1043,7 @@ class MANAGED Class FINAL : public Object { // Returns the number of instance fields containing reference types. Does not count fields in any // super classes. uint32_t NumReferenceInstanceFields() REQUIRES_SHARED(Locks::mutator_lock_) { - DCHECK(IsResolved() || IsErroneous()); + DCHECK(IsResolved()); return GetField32(OFFSET_OF_OBJECT_MEMBER(Class, num_reference_instance_fields_)); } @@ -1045,7 +1071,7 @@ class MANAGED Class FINAL : public Object { // Returns the number of static fields containing reference types. uint32_t NumReferenceStaticFields() REQUIRES_SHARED(Locks::mutator_lock_) { - DCHECK(IsResolved() || IsErroneous()); + DCHECK(IsResolved()); return GetField32(OFFSET_OF_OBJECT_MEMBER(Class, num_reference_static_fields_)); } diff --git a/runtime/mirror/class_ext.cc b/runtime/mirror/class_ext.cc index 7c6a710cef..efd949e031 100644 --- a/runtime/mirror/class_ext.cc +++ b/runtime/mirror/class_ext.cc @@ -113,6 +113,11 @@ void ClassExt::SetVerifyError(ObjPtr<Object> err) { } } +void ClassExt::SetOriginalDexFileBytes(ObjPtr<ByteArray> bytes) { + DCHECK(!Runtime::Current()->IsActiveTransaction()); + SetFieldObject<false>(OFFSET_OF_OBJECT_MEMBER(ClassExt, original_dex_file_bytes_), bytes); +} + void ClassExt::SetClass(ObjPtr<Class> dalvik_system_ClassExt) { CHECK(dalvik_system_ClassExt != nullptr); dalvik_system_ClassExt_ = GcRoot<Class>(dalvik_system_ClassExt); diff --git a/runtime/mirror/class_ext.h b/runtime/mirror/class_ext.h index 91046314db..ad8a61b676 100644 --- a/runtime/mirror/class_ext.h +++ b/runtime/mirror/class_ext.h @@ -61,6 +61,12 @@ class MANAGED ClassExt : public Object { return GetFieldObject<PointerArray>(OFFSET_OF_OBJECT_MEMBER(ClassExt, obsolete_methods_)); } + ByteArray* GetOriginalDexFileBytes() REQUIRES_SHARED(Locks::mutator_lock_) { + return GetFieldObject<ByteArray>(OFFSET_OF_OBJECT_MEMBER(ClassExt, original_dex_file_bytes_)); + } + + void SetOriginalDexFileBytes(ObjPtr<ByteArray> bytes) REQUIRES_SHARED(Locks::mutator_lock_); + void SetObsoleteArrays(ObjPtr<PointerArray> methods, ObjPtr<ObjectArray<DexCache>> dex_caches) REQUIRES_SHARED(Locks::mutator_lock_); @@ -80,7 +86,7 @@ class MANAGED ClassExt : public Object { HeapReference<PointerArray> obsolete_methods_; - HeapReference<DexCache> original_dex_cache_; + HeapReference<ByteArray> original_dex_file_bytes_; // The saved verification error of this class. HeapReference<Object> verify_error_; diff --git a/runtime/mirror/method_handle_impl.h b/runtime/mirror/method_handle_impl.h index abe999a5ac..2f26a22ca8 100644 --- a/runtime/mirror/method_handle_impl.h +++ b/runtime/mirror/method_handle_impl.h @@ -84,10 +84,12 @@ class MANAGED MethodHandle : public Object { static mirror::Class* StaticClass() REQUIRES_SHARED(Locks::mutator_lock_); private: + // NOTE: cached_spread_invoker_ isn't used by the runtime. + HeapReference<mirror::MethodHandle> cached_spread_invoker_; HeapReference<mirror::MethodType> nominal_type_; HeapReference<mirror::MethodType> method_type_; - uint64_t art_field_or_method_; uint32_t handle_kind_; + uint64_t art_field_or_method_; private: static MemberOffset NominalTypeOffset() { diff --git a/runtime/monitor.cc b/runtime/monitor.cc index 9c0927584e..0ceb23a7a2 100644 --- a/runtime/monitor.cc +++ b/runtime/monitor.cc @@ -303,6 +303,7 @@ std::string Monitor::PrettyContentionInfo(const std::string& owner_name, ArtMethod* owners_method, uint32_t owners_dex_pc, size_t num_waiters) { + Locks::mutator_lock_->AssertSharedHeld(Thread::Current()); const char* owners_filename; int32_t owners_line_number = 0; if (owners_method != nullptr) { @@ -359,7 +360,7 @@ void Monitor::Lock(Thread* self) { self->SetMonitorEnterObject(GetObject()); { uint32_t original_owner_thread_id = 0u; - ScopedThreadStateChange tsc(self, kBlocked); // Change to blocked and give up mutator_lock_. + ScopedThreadSuspension tsc(self, kBlocked); // Change to blocked and give up mutator_lock_. { // Reacquire monitor_lock_ without mutator_lock_ for Wait. MutexLock mu2(self, monitor_lock_); @@ -367,22 +368,26 @@ void Monitor::Lock(Thread* self) { original_owner_thread_id = owner_->GetThreadId(); if (ATRACE_ENABLED()) { std::ostringstream oss; - std::string name; - owner_->GetThreadName(name); - oss << PrettyContentionInfo(name, - owner_->GetTid(), - owners_method, - owners_dex_pc, - num_waiters); - // Add info for contending thread. - uint32_t pc; - ArtMethod* m = self->GetCurrentMethod(&pc); - const char* filename; - int32_t line_number; - TranslateLocation(m, pc, &filename, &line_number); - oss << " blocking from " - << ArtMethod::PrettyMethod(m) << "(" << (filename != nullptr ? filename : "null") - << ":" << line_number << ")"; + { + // Reacquire mutator_lock_ for getting the location info. + ScopedObjectAccess soa(self); + std::string name; + owner_->GetThreadName(name); + oss << PrettyContentionInfo(name, + owner_->GetTid(), + owners_method, + owners_dex_pc, + num_waiters); + // Add info for contending thread. + uint32_t pc; + ArtMethod* m = self->GetCurrentMethod(&pc); + const char* filename; + int32_t line_number; + TranslateLocation(m, pc, &filename, &line_number); + oss << " blocking from " + << ArtMethod::PrettyMethod(m) << "(" << (filename != nullptr ? filename : "null") + << ":" << line_number << ")"; + } ATRACE_BEGIN(oss.str().c_str()); } monitor_contenders_.Wait(self); // Still contended so wait. @@ -414,6 +419,8 @@ void Monitor::Lock(Thread* self) { sample_percent = 100 * wait_ms / lock_profiling_threshold_; } if (sample_percent != 0 && (static_cast<uint32_t>(rand() % 100) < sample_percent)) { + // Reacquire mutator_lock_ for logging. + ScopedObjectAccess soa(self); if (wait_ms > kLongWaitMs && owners_method != nullptr) { uint32_t pc; ArtMethod* m = self->GetCurrentMethod(&pc); @@ -1394,6 +1401,12 @@ void MonitorList::SweepMonitorList(IsMarkedVisitor* visitor) { } } +size_t MonitorList::Size() { + Thread* self = Thread::Current(); + MutexLock mu(self, monitor_list_lock_); + return list_.size(); +} + class MonitorDeflateVisitor : public IsMarkedVisitor { public: MonitorDeflateVisitor() : self_(Thread::Current()), deflate_count_(0) {} diff --git a/runtime/monitor.h b/runtime/monitor.h index c3da5636ad..1fa46826eb 100644 --- a/runtime/monitor.h +++ b/runtime/monitor.h @@ -331,6 +331,7 @@ class MonitorList { void BroadcastForNewMonitors() REQUIRES(!monitor_list_lock_); // Returns how many monitors were deflated. size_t DeflateMonitors() REQUIRES(!monitor_list_lock_) REQUIRES(Locks::mutator_lock_); + size_t Size() REQUIRES(!monitor_list_lock_); typedef std::list<Monitor*, TrackingAllocator<Monitor*, kAllocatorTagMonitorList>> Monitors; diff --git a/runtime/native/dalvik_system_VMDebug.cc b/runtime/native/dalvik_system_VMDebug.cc index 67b2e1c503..0d24587998 100644 --- a/runtime/native/dalvik_system_VMDebug.cc +++ b/runtime/native/dalvik_system_VMDebug.cc @@ -90,7 +90,8 @@ static void VMDebug_startMethodTracingDdmsImpl(JNIEnv*, jclass, jint bufferSize, static void VMDebug_startMethodTracingFd(JNIEnv* env, jclass, jstring javaTraceFilename, jobject javaFd, jint bufferSize, jint flags, - jboolean samplingEnabled, jint intervalUs) { + jboolean samplingEnabled, jint intervalUs, + jboolean streamingOutput) { int originalFd = jniGetFDFromFileDescriptor(env, javaFd); if (originalFd < 0) { return; @@ -108,7 +109,10 @@ static void VMDebug_startMethodTracingFd(JNIEnv* env, jclass, jstring javaTraceF if (traceFilename.c_str() == nullptr) { return; } - Trace::Start(traceFilename.c_str(), fd, bufferSize, flags, Trace::TraceOutputMode::kFile, + Trace::TraceOutputMode outputMode = streamingOutput + ? Trace::TraceOutputMode::kStreaming + : Trace::TraceOutputMode::kFile; + Trace::Start(traceFilename.c_str(), fd, bufferSize, flags, outputMode, samplingEnabled ? Trace::TraceMode::kSampling : Trace::TraceMode::kMethodTracing, intervalUs); } @@ -547,7 +551,7 @@ static JNINativeMethod gMethods[] = { NATIVE_METHOD(VMDebug, startEmulatorTracing, "()V"), NATIVE_METHOD(VMDebug, startInstructionCounting, "()V"), NATIVE_METHOD(VMDebug, startMethodTracingDdmsImpl, "(IIZI)V"), - NATIVE_METHOD(VMDebug, startMethodTracingFd, "(Ljava/lang/String;Ljava/io/FileDescriptor;IIZI)V"), + NATIVE_METHOD(VMDebug, startMethodTracingFd, "(Ljava/lang/String;Ljava/io/FileDescriptor;IIZIZ)V"), NATIVE_METHOD(VMDebug, startMethodTracingFilename, "(Ljava/lang/String;IIZI)V"), NATIVE_METHOD(VMDebug, stopAllocCounting, "()V"), NATIVE_METHOD(VMDebug, stopEmulatorTracing, "()V"), diff --git a/runtime/native/java_lang_VMClassLoader.cc b/runtime/native/java_lang_VMClassLoader.cc index 284d2d10c5..a8fa7db688 100644 --- a/runtime/native/java_lang_VMClassLoader.cc +++ b/runtime/native/java_lang_VMClassLoader.cc @@ -81,14 +81,12 @@ static jclass VMClassLoader_findLoadedClass(JNIEnv* env, jclass, jobject javaLoa if (c != nullptr && c->IsErroneous()) { cl->ThrowEarlierClassFailure(c.Ptr()); Thread* self = soa.Self(); - ObjPtr<mirror::Class> eiie_class = - self->DecodeJObject(WellKnownClasses::java_lang_ExceptionInInitializerError)->AsClass(); ObjPtr<mirror::Class> iae_class = self->DecodeJObject(WellKnownClasses::java_lang_IllegalAccessError)->AsClass(); ObjPtr<mirror::Class> ncdfe_class = self->DecodeJObject(WellKnownClasses::java_lang_NoClassDefFoundError)->AsClass(); ObjPtr<mirror::Class> exception = self->GetException()->GetClass(); - if (exception == eiie_class || exception == iae_class || exception == ncdfe_class) { + if (exception == iae_class || exception == ncdfe_class) { self->ThrowNewWrappedException("Ljava/lang/ClassNotFoundException;", c->PrettyDescriptor().c_str()); } diff --git a/runtime/oat.h b/runtime/oat.h index 3b3ab5aa09..29821a2eea 100644 --- a/runtime/oat.h +++ b/runtime/oat.h @@ -32,7 +32,7 @@ class InstructionSetFeatures; class PACKED(4) OatHeader { public: static constexpr uint8_t kOatMagic[] = { 'o', 'a', 't', '\n' }; - static constexpr uint8_t kOatVersion[] = { '1', '0', '1', '\0' }; // Array entrypoints change + static constexpr uint8_t kOatVersion[] = { '1', '0', '3', '\0' }; // Native pc change static constexpr const char* kImageLocationKey = "image-location"; static constexpr const char* kDex2OatCmdLineKey = "dex2oat-cmdline"; diff --git a/runtime/oat_file.h b/runtime/oat_file.h index 62d99fb51c..111755e7a1 100644 --- a/runtime/oat_file.h +++ b/runtime/oat_file.h @@ -201,8 +201,12 @@ class OatFile { // A representation of an invalid OatClass, used when an OatClass can't be found. // See FindOatClass(). static OatClass Invalid() { - return OatClass(nullptr, mirror::Class::kStatusError, kOatClassNoneCompiled, 0, nullptr, - nullptr); + return OatClass(/* oat_file */ nullptr, + mirror::Class::kStatusErrorUnresolved, + kOatClassNoneCompiled, + /* bitmap_size */ 0, + /* bitmap_pointer */ nullptr, + /* methods_pointer */ nullptr); } private: diff --git a/runtime/oat_file_assistant_test.cc b/runtime/oat_file_assistant_test.cc index afa804c08c..577200847a 100644 --- a/runtime/oat_file_assistant_test.cc +++ b/runtime/oat_file_assistant_test.cc @@ -14,23 +14,16 @@ * limitations under the License. */ -#include <algorithm> -#include <fstream> #include <string> #include <vector> #include <sys/param.h> #include "android-base/strings.h" -#include <backtrace/BacktraceMap.h> #include <gtest/gtest.h> #include "art_field-inl.h" #include "class_linker-inl.h" -#include "common_runtime_test.h" -#include "compiler_callbacks.h" -#include "dex2oat_environment_test.h" -#include "gc/space/image_space.h" -#include "mem_map.h" +#include "dexopt_test.h" #include "oat_file_assistant.h" #include "oat_file_manager.h" #include "os.h" @@ -40,240 +33,17 @@ namespace art { -class OatFileAssistantTest : public Dex2oatEnvironmentTest { - public: - virtual void SetUp() OVERRIDE { - ReserveImageSpace(); - Dex2oatEnvironmentTest::SetUp(); - } - - // Pre-Relocate the image to a known non-zero offset so we don't have to - // deal with the runtime randomly relocating the image by 0 and messing up - // the expected results of the tests. - bool PreRelocateImage(const std::string& image_location, std::string* error_msg) { - std::string image; - if (!GetCachedImageFile(image_location, &image, error_msg)) { - return false; - } - - std::string patchoat = GetAndroidRoot(); - patchoat += kIsDebugBuild ? "/bin/patchoatd" : "/bin/patchoat"; - - std::vector<std::string> argv; - argv.push_back(patchoat); - argv.push_back("--input-image-location=" + image_location); - argv.push_back("--output-image-file=" + image); - argv.push_back("--instruction-set=" + std::string(GetInstructionSetString(kRuntimeISA))); - argv.push_back("--base-offset-delta=0x00008000"); - return Exec(argv, error_msg); - } - - virtual void PreRuntimeCreate() { - std::string error_msg; - ASSERT_TRUE(PreRelocateImage(GetImageLocation(), &error_msg)) << error_msg; - ASSERT_TRUE(PreRelocateImage(GetImageLocation2(), &error_msg)) << error_msg; - UnreserveImageSpace(); - } - - virtual void PostRuntimeCreate() OVERRIDE { - ReserveImageSpace(); - } +class OatFileAssistantTest : public DexoptTest {}; - // Generate an oat file for the purposes of test. - void GenerateOatForTest(const std::string& dex_location, - const std::string& oat_location, - CompilerFilter::Filter filter, - bool relocate, - bool pic, - bool with_alternate_image) { - std::string dalvik_cache = GetDalvikCache(GetInstructionSetString(kRuntimeISA)); - std::string dalvik_cache_tmp = dalvik_cache + ".redirected"; - - if (!relocate) { - // Temporarily redirect the dalvik cache so dex2oat doesn't find the - // relocated image file. - ASSERT_EQ(0, rename(dalvik_cache.c_str(), dalvik_cache_tmp.c_str())) << strerror(errno); - } - - std::vector<std::string> args; - args.push_back("--dex-file=" + dex_location); - args.push_back("--oat-file=" + oat_location); - args.push_back("--compiler-filter=" + CompilerFilter::NameOfFilter(filter)); - args.push_back("--runtime-arg"); - - // Use -Xnorelocate regardless of the relocate argument. - // We control relocation by redirecting the dalvik cache when needed - // rather than use this flag. - args.push_back("-Xnorelocate"); - - if (pic) { - args.push_back("--compile-pic"); - } - - std::string image_location = GetImageLocation(); - if (with_alternate_image) { - args.push_back("--boot-image=" + GetImageLocation2()); - } - - std::string error_msg; - ASSERT_TRUE(OatFileAssistant::Dex2Oat(args, &error_msg)) << error_msg; - - if (!relocate) { - // Restore the dalvik cache if needed. - ASSERT_EQ(0, rename(dalvik_cache_tmp.c_str(), dalvik_cache.c_str())) << strerror(errno); - } - - // Verify the odex file was generated as expected. - std::unique_ptr<OatFile> odex_file(OatFile::Open(oat_location.c_str(), - oat_location.c_str(), - nullptr, - nullptr, - false, - /*low_4gb*/false, - dex_location.c_str(), - &error_msg)); - ASSERT_TRUE(odex_file.get() != nullptr) << error_msg; - EXPECT_EQ(pic, odex_file->IsPic()); - EXPECT_EQ(filter, odex_file->GetCompilerFilter()); - - std::unique_ptr<ImageHeader> image_header( - gc::space::ImageSpace::ReadImageHeader(image_location.c_str(), - kRuntimeISA, - &error_msg)); - ASSERT_TRUE(image_header != nullptr) << error_msg; - const OatHeader& oat_header = odex_file->GetOatHeader(); - uint32_t combined_checksum = OatFileAssistant::CalculateCombinedImageChecksum(); - - if (CompilerFilter::DependsOnImageChecksum(filter)) { - if (with_alternate_image) { - EXPECT_NE(combined_checksum, oat_header.GetImageFileLocationOatChecksum()); - } else { - EXPECT_EQ(combined_checksum, oat_header.GetImageFileLocationOatChecksum()); - } - } - - if (CompilerFilter::IsBytecodeCompilationEnabled(filter)) { - if (relocate) { - EXPECT_EQ(reinterpret_cast<uintptr_t>(image_header->GetOatDataBegin()), - oat_header.GetImageFileLocationOatDataBegin()); - EXPECT_EQ(image_header->GetPatchDelta(), oat_header.GetImagePatchDelta()); - } else { - EXPECT_NE(reinterpret_cast<uintptr_t>(image_header->GetOatDataBegin()), - oat_header.GetImageFileLocationOatDataBegin()); - EXPECT_NE(image_header->GetPatchDelta(), oat_header.GetImagePatchDelta()); - } - } - } - - // Generate a non-PIC odex file for the purposes of test. - // The generated odex file will be un-relocated. - void GenerateOdexForTest(const std::string& dex_location, - const std::string& odex_location, - CompilerFilter::Filter filter) { - GenerateOatForTest(dex_location, - odex_location, - filter, - /*relocate*/false, - /*pic*/false, - /*with_alternate_image*/false); - } - - void GeneratePicOdexForTest(const std::string& dex_location, - const std::string& odex_location, - CompilerFilter::Filter filter) { - GenerateOatForTest(dex_location, - odex_location, - filter, - /*relocate*/false, - /*pic*/true, - /*with_alternate_image*/false); - } - - // Generate an oat file in the oat location. - void GenerateOatForTest(const char* dex_location, - CompilerFilter::Filter filter, - bool relocate, - bool pic, - bool with_alternate_image) { - std::string oat_location; - std::string error_msg; - ASSERT_TRUE(OatFileAssistant::DexLocationToOatFilename( - dex_location, kRuntimeISA, &oat_location, &error_msg)) << error_msg; - GenerateOatForTest(dex_location, - oat_location, - filter, - relocate, - pic, - with_alternate_image); - } - - // Generate a standard oat file in the oat location. - void GenerateOatForTest(const char* dex_location, CompilerFilter::Filter filter) { - GenerateOatForTest(dex_location, - filter, - /*relocate*/true, - /*pic*/false, - /*with_alternate_image*/false); - } - - private: - // Reserve memory around where the image will be loaded so other memory - // won't conflict when it comes time to load the image. - // This can be called with an already loaded image to reserve the space - // around it. - void ReserveImageSpace() { - MemMap::Init(); - - // Ensure a chunk of memory is reserved for the image space. - // The reservation_end includes room for the main space that has to come - // right after the image in case of the GSS collector. - uintptr_t reservation_start = ART_BASE_ADDRESS; - uintptr_t reservation_end = ART_BASE_ADDRESS + 384 * MB; - - std::unique_ptr<BacktraceMap> map(BacktraceMap::Create(getpid(), true)); - ASSERT_TRUE(map.get() != nullptr) << "Failed to build process map"; - for (BacktraceMap::const_iterator it = map->begin(); - reservation_start < reservation_end && it != map->end(); ++it) { - ReserveImageSpaceChunk(reservation_start, std::min(it->start, reservation_end)); - reservation_start = std::max(reservation_start, it->end); - } - ReserveImageSpaceChunk(reservation_start, reservation_end); - } - - // Reserve a chunk of memory for the image space in the given range. - // Only has effect for chunks with a positive number of bytes. - void ReserveImageSpaceChunk(uintptr_t start, uintptr_t end) { - if (start < end) { - std::string error_msg; - image_reservation_.push_back(std::unique_ptr<MemMap>( - MemMap::MapAnonymous("image reservation", - reinterpret_cast<uint8_t*>(start), end - start, - PROT_NONE, false, false, &error_msg))); - ASSERT_TRUE(image_reservation_.back().get() != nullptr) << error_msg; - LOG(INFO) << "Reserved space for image " << - reinterpret_cast<void*>(image_reservation_.back()->Begin()) << "-" << - reinterpret_cast<void*>(image_reservation_.back()->End()); - } - } - - - // Unreserve any memory reserved by ReserveImageSpace. This should be called - // before the image is loaded. - void UnreserveImageSpace() { - image_reservation_.clear(); - } - - std::vector<std::unique_ptr<MemMap>> image_reservation_; -}; - -class OatFileAssistantNoDex2OatTest : public OatFileAssistantTest { +class OatFileAssistantNoDex2OatTest : public DexoptTest { public: virtual void SetUpRuntimeOptions(RuntimeOptions* options) { - OatFileAssistantTest::SetUpRuntimeOptions(options); + DexoptTest::SetUpRuntimeOptions(options); options->push_back(std::make_pair("-Xnodex2oat", nullptr)); } }; + // Case: We have a DEX file, but no OAT file for it. // Expect: The status is kDex2OatNeeded. TEST_F(OatFileAssistantTest, DexNoOat) { diff --git a/runtime/oat_quick_method_header.cc b/runtime/oat_quick_method_header.cc index 9c2378d42d..fd84426bb8 100644 --- a/runtime/oat_quick_method_header.cc +++ b/runtime/oat_quick_method_header.cc @@ -80,7 +80,7 @@ uintptr_t OatQuickMethodHeader::ToNativeQuickPc(ArtMethod* method, : code_info.GetStackMapForDexPc(dex_pc, encoding); if (stack_map.IsValid()) { return reinterpret_cast<uintptr_t>(entry_point) + - stack_map.GetNativePcOffset(encoding.stack_map_encoding); + stack_map.GetNativePcOffset(encoding.stack_map_encoding, kRuntimeISA); } if (abort_on_failure) { ScopedObjectAccess soa(Thread::Current()); diff --git a/runtime/openjdkjvmti/Android.bp b/runtime/openjdkjvmti/Android.bp index d5c652035a..976a1e7902 100644 --- a/runtime/openjdkjvmti/Android.bp +++ b/runtime/openjdkjvmti/Android.bp @@ -21,12 +21,15 @@ cc_defaults { "object_tagging.cc", "OpenjdkJvmTi.cc", "ti_class.cc", + "ti_class_definition.cc", + "ti_dump.cc", "ti_field.cc", "ti_heap.cc", "ti_jni.cc", "ti_method.cc", "ti_monitor.cc", "ti_object.cc", + "ti_phase.cc", "ti_properties.cc", "ti_search.cc", "ti_stack.cc", diff --git a/runtime/openjdkjvmti/OpenjdkJvmTi.cc b/runtime/openjdkjvmti/OpenjdkJvmTi.cc index 90467db8f6..c0c301fae5 100644 --- a/runtime/openjdkjvmti/OpenjdkJvmTi.cc +++ b/runtime/openjdkjvmti/OpenjdkJvmTi.cc @@ -49,12 +49,14 @@ #include "thread-inl.h" #include "thread_list.h" #include "ti_class.h" +#include "ti_dump.h" #include "ti_field.h" #include "ti_heap.h" #include "ti_jni.h" #include "ti_method.h" #include "ti_monitor.h" #include "ti_object.h" +#include "ti_phase.h" #include "ti_properties.h" #include "ti_redefine.h" #include "ti_search.h" @@ -136,6 +138,7 @@ class JvmtiFunctions { } static jvmtiError SuspendThread(jvmtiEnv* env, jthread thread) { + ENSURE_HAS_CAP(env, can_suspend); return ERR(NOT_IMPLEMENTED); } @@ -143,10 +146,12 @@ class JvmtiFunctions { jint request_count, const jthread* request_list, jvmtiError* results) { + ENSURE_HAS_CAP(env, can_suspend); return ERR(NOT_IMPLEMENTED); } static jvmtiError ResumeThread(jvmtiEnv* env, jthread thread) { + ENSURE_HAS_CAP(env, can_suspend); return ERR(NOT_IMPLEMENTED); } @@ -154,14 +159,17 @@ class JvmtiFunctions { jint request_count, const jthread* request_list, jvmtiError* results) { + ENSURE_HAS_CAP(env, can_suspend); return ERR(NOT_IMPLEMENTED); } static jvmtiError StopThread(jvmtiEnv* env, jthread thread, jobject exception) { + ENSURE_HAS_CAP(env, can_signal_thread); return ERR(NOT_IMPLEMENTED); } static jvmtiError InterruptThread(jvmtiEnv* env, jthread thread) { + ENSURE_HAS_CAP(env, can_signal_thread); return ERR(NOT_IMPLEMENTED); } @@ -173,6 +181,7 @@ class JvmtiFunctions { jthread thread, jint* owned_monitor_count_ptr, jobject** owned_monitors_ptr) { + ENSURE_HAS_CAP(env, can_get_owned_monitor_info); return ERR(NOT_IMPLEMENTED); } @@ -180,12 +189,14 @@ class JvmtiFunctions { jthread thread, jint* monitor_info_count_ptr, jvmtiMonitorStackDepthInfo** monitor_info_ptr) { + ENSURE_HAS_CAP(env, can_get_owned_monitor_stack_depth_info); return ERR(NOT_IMPLEMENTED); } static jvmtiError GetCurrentContendedMonitor(jvmtiEnv* env, jthread thread, jobject* monitor_ptr) { + ENSURE_HAS_CAP(env, can_get_current_contended_monitor); return ERR(NOT_IMPLEMENTED); } @@ -194,15 +205,15 @@ class JvmtiFunctions { jvmtiStartFunction proc, const void* arg, jint priority) { - return ERR(NOT_IMPLEMENTED); + return ThreadUtil::RunAgentThread(env, thread, proc, arg, priority); } static jvmtiError SetThreadLocalStorage(jvmtiEnv* env, jthread thread, const void* data) { - return ERR(NOT_IMPLEMENTED); + return ThreadUtil::SetThreadLocalStorage(env, thread, data); } static jvmtiError GetThreadLocalStorage(jvmtiEnv* env, jthread thread, void** data_ptr) { - return ERR(NOT_IMPLEMENTED); + return ThreadUtil::GetThreadLocalStorage(env, thread, data_ptr); } static jvmtiError GetTopThreadGroups(jvmtiEnv* env, @@ -269,6 +280,7 @@ class JvmtiFunctions { } static jvmtiError PopFrame(jvmtiEnv* env, jthread thread) { + ENSURE_HAS_CAP(env, can_pop_frame); return ERR(NOT_IMPLEMENTED); } @@ -281,30 +293,37 @@ class JvmtiFunctions { } static jvmtiError NotifyFramePop(jvmtiEnv* env, jthread thread, jint depth) { + ENSURE_HAS_CAP(env, can_generate_frame_pop_events); return ERR(NOT_IMPLEMENTED); } static jvmtiError ForceEarlyReturnObject(jvmtiEnv* env, jthread thread, jobject value) { + ENSURE_HAS_CAP(env, can_force_early_return); return ERR(NOT_IMPLEMENTED); } static jvmtiError ForceEarlyReturnInt(jvmtiEnv* env, jthread thread, jint value) { + ENSURE_HAS_CAP(env, can_force_early_return); return ERR(NOT_IMPLEMENTED); } static jvmtiError ForceEarlyReturnLong(jvmtiEnv* env, jthread thread, jlong value) { + ENSURE_HAS_CAP(env, can_force_early_return); return ERR(NOT_IMPLEMENTED); } static jvmtiError ForceEarlyReturnFloat(jvmtiEnv* env, jthread thread, jfloat value) { + ENSURE_HAS_CAP(env, can_force_early_return); return ERR(NOT_IMPLEMENTED); } static jvmtiError ForceEarlyReturnDouble(jvmtiEnv* env, jthread thread, jdouble value) { + ENSURE_HAS_CAP(env, can_force_early_return); return ERR(NOT_IMPLEMENTED); } static jvmtiError ForceEarlyReturnVoid(jvmtiEnv* env, jthread thread) { + ENSURE_HAS_CAP(env, can_force_early_return); return ERR(NOT_IMPLEMENTED); } @@ -314,6 +333,7 @@ class JvmtiFunctions { jobject initial_object, const jvmtiHeapCallbacks* callbacks, const void* user_data) { + ENSURE_HAS_CAP(env, can_tag_objects); HeapUtil heap_util(&gObjectTagTable); return heap_util.FollowReferences(env, heap_filter, @@ -400,6 +420,7 @@ class JvmtiFunctions { jobject object, jvmtiObjectReferenceCallback object_reference_callback, const void* user_data) { + ENSURE_HAS_CAP(env, can_tag_objects); return ERR(NOT_IMPLEMENTED); } @@ -408,6 +429,7 @@ class JvmtiFunctions { jvmtiStackReferenceCallback stack_ref_callback, jvmtiObjectReferenceCallback object_ref_callback, const void* user_data) { + ENSURE_HAS_CAP(env, can_tag_objects); return ERR(NOT_IMPLEMENTED); } @@ -415,6 +437,7 @@ class JvmtiFunctions { jvmtiHeapObjectFilter object_filter, jvmtiHeapObjectCallback heap_object_callback, const void* user_data) { + ENSURE_HAS_CAP(env, can_tag_objects); return ERR(NOT_IMPLEMENTED); } @@ -423,6 +446,7 @@ class JvmtiFunctions { jvmtiHeapObjectFilter object_filter, jvmtiHeapObjectCallback heap_object_callback, const void* user_data) { + ENSURE_HAS_CAP(env, can_tag_objects); return ERR(NOT_IMPLEMENTED); } @@ -431,6 +455,7 @@ class JvmtiFunctions { jint depth, jint slot, jobject* value_ptr) { + ENSURE_HAS_CAP(env, can_access_local_variables); return ERR(NOT_IMPLEMENTED); } @@ -438,6 +463,7 @@ class JvmtiFunctions { jthread thread, jint depth, jobject* value_ptr) { + ENSURE_HAS_CAP(env, can_access_local_variables); return ERR(NOT_IMPLEMENTED); } @@ -446,6 +472,7 @@ class JvmtiFunctions { jint depth, jint slot, jint* value_ptr) { + ENSURE_HAS_CAP(env, can_access_local_variables); return ERR(NOT_IMPLEMENTED); } @@ -454,6 +481,7 @@ class JvmtiFunctions { jint depth, jint slot, jlong* value_ptr) { + ENSURE_HAS_CAP(env, can_access_local_variables); return ERR(NOT_IMPLEMENTED); } @@ -462,6 +490,7 @@ class JvmtiFunctions { jint depth, jint slot, jfloat* value_ptr) { + ENSURE_HAS_CAP(env, can_access_local_variables); return ERR(NOT_IMPLEMENTED); } @@ -470,6 +499,7 @@ class JvmtiFunctions { jint depth, jint slot, jdouble* value_ptr) { + ENSURE_HAS_CAP(env, can_access_local_variables); return ERR(NOT_IMPLEMENTED); } @@ -478,6 +508,7 @@ class JvmtiFunctions { jint depth, jint slot, jobject value) { + ENSURE_HAS_CAP(env, can_access_local_variables); return ERR(NOT_IMPLEMENTED); } @@ -486,6 +517,7 @@ class JvmtiFunctions { jint depth, jint slot, jint value) { + ENSURE_HAS_CAP(env, can_access_local_variables); return ERR(NOT_IMPLEMENTED); } @@ -494,6 +526,7 @@ class JvmtiFunctions { jint depth, jint slot, jlong value) { + ENSURE_HAS_CAP(env, can_access_local_variables); return ERR(NOT_IMPLEMENTED); } @@ -502,6 +535,7 @@ class JvmtiFunctions { jint depth, jint slot, jfloat value) { + ENSURE_HAS_CAP(env, can_access_local_variables); return ERR(NOT_IMPLEMENTED); } @@ -510,30 +544,37 @@ class JvmtiFunctions { jint depth, jint slot, jdouble value) { + ENSURE_HAS_CAP(env, can_access_local_variables); return ERR(NOT_IMPLEMENTED); } static jvmtiError SetBreakpoint(jvmtiEnv* env, jmethodID method, jlocation location) { + ENSURE_HAS_CAP(env, can_generate_breakpoint_events); return ERR(NOT_IMPLEMENTED); } static jvmtiError ClearBreakpoint(jvmtiEnv* env, jmethodID method, jlocation location) { + ENSURE_HAS_CAP(env, can_generate_breakpoint_events); return ERR(NOT_IMPLEMENTED); } static jvmtiError SetFieldAccessWatch(jvmtiEnv* env, jclass klass, jfieldID field) { + ENSURE_HAS_CAP(env, can_generate_field_access_events); return ERR(NOT_IMPLEMENTED); } static jvmtiError ClearFieldAccessWatch(jvmtiEnv* env, jclass klass, jfieldID field) { + ENSURE_HAS_CAP(env, can_generate_field_access_events); return ERR(NOT_IMPLEMENTED); } static jvmtiError SetFieldModificationWatch(jvmtiEnv* env, jclass klass, jfieldID field) { + ENSURE_HAS_CAP(env, can_generate_field_modification_events); return ERR(NOT_IMPLEMENTED); } static jvmtiError ClearFieldModificationWatch(jvmtiEnv* env, jclass klass, jfieldID field) { + ENSURE_HAS_CAP(env, can_generate_field_modification_events); return ERR(NOT_IMPLEMENTED); } @@ -561,6 +602,7 @@ class JvmtiFunctions { } static jvmtiError GetSourceFileName(jvmtiEnv* env, jclass klass, char** source_name_ptr) { + ENSURE_HAS_CAP(env, can_get_source_file_name); return ERR(NOT_IMPLEMENTED); } @@ -593,7 +635,7 @@ class JvmtiFunctions { jclass klass, jint* minor_version_ptr, jint* major_version_ptr) { - return ERR(NOT_IMPLEMENTED); + return ClassUtil::GetClassVersionNumbers(env, klass, minor_version_ptr, major_version_ptr); } static jvmtiError GetConstantPool(jvmtiEnv* env, @@ -601,6 +643,7 @@ class JvmtiFunctions { jint* constant_pool_count_ptr, jint* constant_pool_byte_count_ptr, unsigned char** constant_pool_bytes_ptr) { + ENSURE_HAS_CAP(env, can_get_constant_pool); return ERR(NOT_IMPLEMENTED); } @@ -627,16 +670,29 @@ class JvmtiFunctions { static jvmtiError GetSourceDebugExtension(jvmtiEnv* env, jclass klass, char** source_debug_extension_ptr) { + ENSURE_HAS_CAP(env, can_get_source_debug_extension); return ERR(NOT_IMPLEMENTED); } static jvmtiError RetransformClasses(jvmtiEnv* env, jint class_count, const jclass* classes) { - return ERR(NOT_IMPLEMENTED); + ENSURE_HAS_CAP(env, can_retransform_classes); + std::string error_msg; + jvmtiError res = Transformer::RetransformClasses(ArtJvmTiEnv::AsArtJvmTiEnv(env), + art::Runtime::Current(), + art::Thread::Current(), + class_count, + classes, + &error_msg); + if (res != OK) { + LOG(WARNING) << "FAILURE TO RETRANFORM " << error_msg; + } + return res; } static jvmtiError RedefineClasses(jvmtiEnv* env, jint class_count, const jvmtiClassDefinition* class_definitions) { + ENSURE_HAS_CAP(env, can_redefine_classes); std::string error_msg; jvmtiError res = Redefiner::RedefineClasses(ArtJvmTiEnv::AsArtJvmTiEnv(env), art::Runtime::Current(), @@ -661,6 +717,7 @@ class JvmtiFunctions { static jvmtiError GetObjectMonitorUsage(jvmtiEnv* env, jobject object, jvmtiMonitorUsage* info_ptr) { + ENSURE_HAS_CAP(env, can_get_monitor_info); return ERR(NOT_IMPLEMENTED); } @@ -691,6 +748,7 @@ class JvmtiFunctions { jclass klass, jfieldID field, jboolean* is_synthetic_ptr) { + ENSURE_HAS_CAP(env, can_get_synthetic_attribute); return FieldUtil::IsFieldSynthetic(env, klass, field, is_synthetic_ptr); } @@ -730,6 +788,7 @@ class JvmtiFunctions { jmethodID method, jint* entry_count_ptr, jvmtiLineNumberEntry** table_ptr) { + ENSURE_HAS_CAP(env, can_get_line_numbers); return MethodUtil::GetLineNumberTable(env, method, entry_count_ptr, table_ptr); } @@ -744,6 +803,7 @@ class JvmtiFunctions { jmethodID method, jint* entry_count_ptr, jvmtiLocalVariableEntry** table_ptr) { + ENSURE_HAS_CAP(env, can_access_local_variables); return ERR(NOT_IMPLEMENTED); } @@ -751,6 +811,7 @@ class JvmtiFunctions { jmethodID method, jint* bytecode_count_ptr, unsigned char** bytecodes_ptr) { + ENSURE_HAS_CAP(env, can_get_bytecodes); return ERR(NOT_IMPLEMENTED); } @@ -759,6 +820,7 @@ class JvmtiFunctions { } static jvmtiError IsMethodSynthetic(jvmtiEnv* env, jmethodID method, jboolean* is_synthetic_ptr) { + ENSURE_HAS_CAP(env, can_get_synthetic_attribute); return MethodUtil::IsMethodSynthetic(env, method, is_synthetic_ptr); } @@ -767,10 +829,12 @@ class JvmtiFunctions { } static jvmtiError SetNativeMethodPrefix(jvmtiEnv* env, const char* prefix) { + ENSURE_HAS_CAP(env, can_set_native_method_prefix); return ERR(NOT_IMPLEMENTED); } static jvmtiError SetNativeMethodPrefixes(jvmtiEnv* env, jint prefix_count, char** prefixes) { + ENSURE_HAS_CAP(env, can_set_native_method_prefix); return ERR(NOT_IMPLEMENTED); } @@ -843,7 +907,6 @@ class JvmtiFunctions { jthread event_thread, ...) { ENSURE_VALID_ENV(env); - // TODO: Check for capabilities. art::Thread* art_thread = nullptr; if (event_thread != nullptr) { // TODO: Need non-aborting call here, to return JVMTI_ERROR_INVALID_THREAD. @@ -1041,18 +1104,22 @@ class JvmtiFunctions { } static jvmtiError GetCurrentThreadCpuTimerInfo(jvmtiEnv* env, jvmtiTimerInfo* info_ptr) { + ENSURE_HAS_CAP(env, can_get_current_thread_cpu_time); return ERR(NOT_IMPLEMENTED); } static jvmtiError GetCurrentThreadCpuTime(jvmtiEnv* env, jlong* nanos_ptr) { + ENSURE_HAS_CAP(env, can_get_current_thread_cpu_time); return ERR(NOT_IMPLEMENTED); } static jvmtiError GetThreadCpuTimerInfo(jvmtiEnv* env, jvmtiTimerInfo* info_ptr) { + ENSURE_HAS_CAP(env, can_get_thread_cpu_time); return ERR(NOT_IMPLEMENTED); } static jvmtiError GetThreadCpuTime(jvmtiEnv* env, jthread thread, jlong* nanos_ptr) { + ENSURE_HAS_CAP(env, can_get_thread_cpu_time); return ERR(NOT_IMPLEMENTED); } @@ -1089,11 +1156,12 @@ class JvmtiFunctions { } static jvmtiError GetPhase(jvmtiEnv* env, jvmtiPhase* phase_ptr) { - return ERR(NOT_IMPLEMENTED); + return PhaseUtil::GetPhase(env, phase_ptr); } static jvmtiError DisposeEnvironment(jvmtiEnv* env) { ENSURE_VALID_ENV(env); + gEventHandler.RemoveArtJvmTiEnv(ArtJvmTiEnv::AsArtJvmTiEnv(env)); delete env; return OK; } @@ -1255,78 +1323,6 @@ class JvmtiFunctions { *format_ptr = jvmtiJlocationFormat::JVMTI_JLOCATION_JVMBCI; return ERR(NONE); } - - // TODO Remove this once events are working. - static jvmtiError RetransformClassWithHook(jvmtiEnv* env, - jclass klass, - jvmtiEventClassFileLoadHook hook) { - std::vector<jclass> classes; - classes.push_back(klass); - return RetransformClassesWithHook(reinterpret_cast<ArtJvmTiEnv*>(env), classes, hook); - } - - // TODO This will be called by the event handler for the art::ti Event Load Event - static jvmtiError RetransformClassesWithHook(ArtJvmTiEnv* env, - const std::vector<jclass>& classes, - jvmtiEventClassFileLoadHook hook) { - if (!IsValidEnv(env)) { - return ERR(INVALID_ENVIRONMENT); - } - jvmtiError res = OK; - std::string error; - for (jclass klass : classes) { - JNIEnv* jni_env = nullptr; - jobject loader = nullptr; - std::string name; - jobject protection_domain = nullptr; - jint data_len = 0; - unsigned char* dex_data = nullptr; - jvmtiError ret = OK; - std::string location; - if ((ret = GetTransformationData(env, - klass, - /*out*/&location, - /*out*/&jni_env, - /*out*/&loader, - /*out*/&name, - /*out*/&protection_domain, - /*out*/&data_len, - /*out*/&dex_data)) != OK) { - // TODO Do something more here? Maybe give log statements? - return ret; - } - jint new_data_len = 0; - unsigned char* new_dex_data = nullptr; - hook(env, - jni_env, - klass, - loader, - name.c_str(), - protection_domain, - data_len, - dex_data, - /*out*/&new_data_len, - /*out*/&new_dex_data); - // Check if anything actually changed. - if ((new_data_len != 0 || new_dex_data != nullptr) && new_dex_data != dex_data) { - jvmtiClassDefinition def = { klass, new_data_len, new_dex_data }; - res = Redefiner::RedefineClasses(env, - art::Runtime::Current(), - art::Thread::Current(), - 1, - &def, - &error); - env->Deallocate(new_dex_data); - } - // Deallocate the old dex data. - env->Deallocate(dex_data); - if (res != OK) { - LOG(ERROR) << "FAILURE TO REDEFINE " << error; - return res; - } - } - return OK; - } }; static bool IsJvmtiVersion(jint version) { @@ -1362,17 +1358,35 @@ static jint GetEnvHandler(art::JavaVMExt* vm, /*out*/void** env, jint version) { // The plugin initialization function. This adds the jvmti environment. extern "C" bool ArtPlugin_Initialize() { art::Runtime* runtime = art::Runtime::Current(); + + if (runtime->IsStarted()) { + PhaseUtil::SetToLive(); + } else { + PhaseUtil::SetToOnLoad(); + } + PhaseUtil::Register(&gEventHandler); + ThreadUtil::Register(&gEventHandler); + ClassUtil::Register(&gEventHandler); + DumpUtil::Register(&gEventHandler); + runtime->GetJavaVM()->AddEnvironmentHook(GetEnvHandler); runtime->AddSystemWeakHolder(&gObjectTagTable); + + return true; +} + +extern "C" bool ArtPlugin_Deinitialize() { + PhaseUtil::Unregister(); + ThreadUtil::Unregister(); + ClassUtil::Unregister(); + DumpUtil::Unregister(); + return true; } // The actual struct holding all of the entrypoints into the jvmti interface. const jvmtiInterface_1 gJvmtiInterface = { - // SPECIAL FUNCTION: RetransformClassWithHook Is normally reserved1 - // TODO Remove once we have events working. - reinterpret_cast<void*>(JvmtiFunctions::RetransformClassWithHook), - // nullptr, // reserved1 + nullptr, // reserved1 JvmtiFunctions::SetEventNotificationMode, nullptr, // reserved3 JvmtiFunctions::GetAllThreads, diff --git a/runtime/openjdkjvmti/art_jvmti.h b/runtime/openjdkjvmti/art_jvmti.h index 5eadc5a8e0..106165c38b 100644 --- a/runtime/openjdkjvmti/art_jvmti.h +++ b/runtime/openjdkjvmti/art_jvmti.h @@ -36,6 +36,7 @@ #include <jni.h> +#include "base/array_slice.h" #include "base/casts.h" #include "base/logging.h" #include "base/macros.h" @@ -47,6 +48,7 @@ namespace openjdkjvmti { extern const jvmtiInterface_1 gJvmtiInterface; +extern EventHandler gEventHandler; // A structure that is a jvmtiEnv with additional information for the runtime. struct ArtJvmTiEnv : public jvmtiEnv { @@ -112,6 +114,21 @@ static inline JvmtiUniquePtr MakeJvmtiUniquePtr(jvmtiEnv* env, T* mem) { } ALWAYS_INLINE +static inline jvmtiError CopyDataIntoJvmtiBuffer(ArtJvmTiEnv* env, + const unsigned char* source, + jint len, + /*out*/unsigned char** dest) { + jvmtiError res = env->Allocate(len, dest); + if (res != OK) { + return res; + } + memcpy(reinterpret_cast<void*>(*dest), + reinterpret_cast<const void*>(source), + len); + return OK; +} + +ALWAYS_INLINE static inline jvmtiError CopyString(jvmtiEnv* env, const char* src, unsigned char** copy) { size_t len = strlen(src) + 1; unsigned char* buf; @@ -129,15 +146,15 @@ const jvmtiCapabilities kPotentialCapabilities = { .can_generate_field_modification_events = 0, .can_generate_field_access_events = 0, .can_get_bytecodes = 0, - .can_get_synthetic_attribute = 0, + .can_get_synthetic_attribute = 1, .can_get_owned_monitor_info = 0, .can_get_current_contended_monitor = 0, .can_get_monitor_info = 0, .can_pop_frame = 0, - .can_redefine_classes = 0, + .can_redefine_classes = 1, .can_signal_thread = 0, .can_get_source_file_name = 0, - .can_get_line_numbers = 0, + .can_get_line_numbers = 1, .can_get_source_debug_extension = 0, .can_access_local_variables = 0, .can_maintain_original_method_order = 0, @@ -154,15 +171,15 @@ const jvmtiCapabilities kPotentialCapabilities = { .can_generate_all_class_hook_events = 0, .can_generate_compiled_method_load_events = 0, .can_generate_monitor_events = 0, - .can_generate_vm_object_alloc_events = 0, + .can_generate_vm_object_alloc_events = 1, .can_generate_native_method_bind_events = 0, - .can_generate_garbage_collection_events = 0, - .can_generate_object_free_events = 0, + .can_generate_garbage_collection_events = 1, + .can_generate_object_free_events = 1, .can_force_early_return = 0, .can_get_owned_monitor_stack_depth_info = 0, .can_get_constant_pool = 0, .can_set_native_method_prefix = 0, - .can_retransform_classes = 0, + .can_retransform_classes = 1, .can_retransform_any_class = 0, .can_generate_resource_exhaustion_heap_events = 0, .can_generate_resource_exhaustion_threads_events = 0, diff --git a/runtime/openjdkjvmti/events-inl.h b/runtime/openjdkjvmti/events-inl.h index 1e07bc6b7b..4f5eb0c33f 100644 --- a/runtime/openjdkjvmti/events-inl.h +++ b/runtime/openjdkjvmti/events-inl.h @@ -37,91 +37,143 @@ static inline ArtJvmtiEvent GetArtJvmtiEvent(ArtJvmTiEnv* env, jvmtiEvent e) { } } -template <typename FnType> -ALWAYS_INLINE static inline FnType* GetCallback(ArtJvmTiEnv* env, ArtJvmtiEvent event) { - if (env->event_callbacks == nullptr) { - return nullptr; - } +namespace impl { + +// Infrastructure to achieve type safety for event dispatch. + +#define FORALL_EVENT_TYPES(fn) \ + fn(VMInit, ArtJvmtiEvent::kVmInit) \ + fn(VMDeath, ArtJvmtiEvent::kVmDeath) \ + fn(ThreadStart, ArtJvmtiEvent::kThreadStart) \ + fn(ThreadEnd, ArtJvmtiEvent::kThreadEnd) \ + fn(ClassFileLoadHook, ArtJvmtiEvent::kClassFileLoadHookRetransformable) \ + fn(ClassFileLoadHook, ArtJvmtiEvent::kClassFileLoadHookNonRetransformable) \ + fn(ClassLoad, ArtJvmtiEvent::kClassLoad) \ + fn(ClassPrepare, ArtJvmtiEvent::kClassPrepare) \ + fn(VMStart, ArtJvmtiEvent::kVmStart) \ + fn(Exception, ArtJvmtiEvent::kException) \ + fn(ExceptionCatch, ArtJvmtiEvent::kExceptionCatch) \ + fn(SingleStep, ArtJvmtiEvent::kSingleStep) \ + fn(FramePop, ArtJvmtiEvent::kFramePop) \ + fn(Breakpoint, ArtJvmtiEvent::kBreakpoint) \ + fn(FieldAccess, ArtJvmtiEvent::kFieldAccess) \ + fn(FieldModification, ArtJvmtiEvent::kFieldModification) \ + fn(MethodEntry, ArtJvmtiEvent::kMethodEntry) \ + fn(MethodExit, ArtJvmtiEvent::kMethodExit) \ + fn(NativeMethodBind, ArtJvmtiEvent::kNativeMethodBind) \ + fn(CompiledMethodLoad, ArtJvmtiEvent::kCompiledMethodLoad) \ + fn(CompiledMethodUnload, ArtJvmtiEvent::kCompiledMethodUnload) \ + fn(DynamicCodeGenerated, ArtJvmtiEvent::kDynamicCodeGenerated) \ + fn(DataDumpRequest, ArtJvmtiEvent::kDataDumpRequest) \ + fn(MonitorWait, ArtJvmtiEvent::kMonitorWait) \ + fn(MonitorWaited, ArtJvmtiEvent::kMonitorWaited) \ + fn(MonitorContendedEnter, ArtJvmtiEvent::kMonitorContendedEnter) \ + fn(MonitorContendedEntered, ArtJvmtiEvent::kMonitorContendedEntered) \ + fn(ResourceExhausted, ArtJvmtiEvent::kResourceExhausted) \ + fn(GarbageCollectionStart, ArtJvmtiEvent::kGarbageCollectionStart) \ + fn(GarbageCollectionFinish, ArtJvmtiEvent::kGarbageCollectionFinish) \ + fn(ObjectFree, ArtJvmtiEvent::kObjectFree) \ + fn(VMObjectAlloc, ArtJvmtiEvent::kVmObjectAlloc) + +template <ArtJvmtiEvent kEvent> +struct EventFnType { +}; + +#define EVENT_FN_TYPE(name, enum_name) \ +template <> \ +struct EventFnType<enum_name> { \ + using type = decltype(jvmtiEventCallbacks().name); \ +}; + +FORALL_EVENT_TYPES(EVENT_FN_TYPE) + +#undef EVENT_FN_TYPE + +template <ArtJvmtiEvent kEvent> +ALWAYS_INLINE inline typename EventFnType<kEvent>::type GetCallback(ArtJvmTiEnv* env); + +#define GET_CALLBACK(name, enum_name) \ +template <> \ +ALWAYS_INLINE inline EventFnType<enum_name>::type GetCallback<enum_name>( \ + ArtJvmTiEnv* env) { \ + if (env->event_callbacks == nullptr) { \ + return nullptr; \ + } \ + return env->event_callbacks->name; \ +} + +FORALL_EVENT_TYPES(GET_CALLBACK) - // TODO: Add a type check. Can be done, for example, by an explicitly instantiated template - // function. - - switch (event) { - case ArtJvmtiEvent::kVmInit: - return reinterpret_cast<FnType*>(env->event_callbacks->VMInit); - case ArtJvmtiEvent::kVmDeath: - return reinterpret_cast<FnType*>(env->event_callbacks->VMDeath); - case ArtJvmtiEvent::kThreadStart: - return reinterpret_cast<FnType*>(env->event_callbacks->ThreadStart); - case ArtJvmtiEvent::kThreadEnd: - return reinterpret_cast<FnType*>(env->event_callbacks->ThreadEnd); - case ArtJvmtiEvent::kClassFileLoadHookRetransformable: - case ArtJvmtiEvent::kClassFileLoadHookNonRetransformable: - return reinterpret_cast<FnType*>(env->event_callbacks->ClassFileLoadHook); - case ArtJvmtiEvent::kClassLoad: - return reinterpret_cast<FnType*>(env->event_callbacks->ClassLoad); - case ArtJvmtiEvent::kClassPrepare: - return reinterpret_cast<FnType*>(env->event_callbacks->ClassPrepare); - case ArtJvmtiEvent::kVmStart: - return reinterpret_cast<FnType*>(env->event_callbacks->VMStart); - case ArtJvmtiEvent::kException: - return reinterpret_cast<FnType*>(env->event_callbacks->Exception); - case ArtJvmtiEvent::kExceptionCatch: - return reinterpret_cast<FnType*>(env->event_callbacks->ExceptionCatch); - case ArtJvmtiEvent::kSingleStep: - return reinterpret_cast<FnType*>(env->event_callbacks->SingleStep); - case ArtJvmtiEvent::kFramePop: - return reinterpret_cast<FnType*>(env->event_callbacks->FramePop); - case ArtJvmtiEvent::kBreakpoint: - return reinterpret_cast<FnType*>(env->event_callbacks->Breakpoint); - case ArtJvmtiEvent::kFieldAccess: - return reinterpret_cast<FnType*>(env->event_callbacks->FieldAccess); - case ArtJvmtiEvent::kFieldModification: - return reinterpret_cast<FnType*>(env->event_callbacks->FieldModification); - case ArtJvmtiEvent::kMethodEntry: - return reinterpret_cast<FnType*>(env->event_callbacks->MethodEntry); - case ArtJvmtiEvent::kMethodExit: - return reinterpret_cast<FnType*>(env->event_callbacks->MethodExit); - case ArtJvmtiEvent::kNativeMethodBind: - return reinterpret_cast<FnType*>(env->event_callbacks->NativeMethodBind); - case ArtJvmtiEvent::kCompiledMethodLoad: - return reinterpret_cast<FnType*>(env->event_callbacks->CompiledMethodLoad); - case ArtJvmtiEvent::kCompiledMethodUnload: - return reinterpret_cast<FnType*>(env->event_callbacks->CompiledMethodUnload); - case ArtJvmtiEvent::kDynamicCodeGenerated: - return reinterpret_cast<FnType*>(env->event_callbacks->DynamicCodeGenerated); - case ArtJvmtiEvent::kDataDumpRequest: - return reinterpret_cast<FnType*>(env->event_callbacks->DataDumpRequest); - case ArtJvmtiEvent::kMonitorWait: - return reinterpret_cast<FnType*>(env->event_callbacks->MonitorWait); - case ArtJvmtiEvent::kMonitorWaited: - return reinterpret_cast<FnType*>(env->event_callbacks->MonitorWaited); - case ArtJvmtiEvent::kMonitorContendedEnter: - return reinterpret_cast<FnType*>(env->event_callbacks->MonitorContendedEnter); - case ArtJvmtiEvent::kMonitorContendedEntered: - return reinterpret_cast<FnType*>(env->event_callbacks->MonitorContendedEntered); - case ArtJvmtiEvent::kResourceExhausted: - return reinterpret_cast<FnType*>(env->event_callbacks->ResourceExhausted); - case ArtJvmtiEvent::kGarbageCollectionStart: - return reinterpret_cast<FnType*>(env->event_callbacks->GarbageCollectionStart); - case ArtJvmtiEvent::kGarbageCollectionFinish: - return reinterpret_cast<FnType*>(env->event_callbacks->GarbageCollectionFinish); - case ArtJvmtiEvent::kObjectFree: - return reinterpret_cast<FnType*>(env->event_callbacks->ObjectFree); - case ArtJvmtiEvent::kVmObjectAlloc: - return reinterpret_cast<FnType*>(env->event_callbacks->VMObjectAlloc); +#undef GET_CALLBACK + +#undef FORALL_EVENT_TYPES + +} // namespace impl + +// C++ does not allow partial template function specialization. The dispatch for our separated +// ClassFileLoadHook event types is the same, so use this helper for code deduplication. +// TODO Locking of some type! +template <ArtJvmtiEvent kEvent> +inline void EventHandler::DispatchClassFileLoadHookEvent(art::Thread* thread, + JNIEnv* jnienv, + jclass class_being_redefined, + jobject loader, + const char* name, + jobject protection_domain, + jint class_data_len, + const unsigned char* class_data, + jint* new_class_data_len, + unsigned char** new_class_data) const { + static_assert(kEvent == ArtJvmtiEvent::kClassFileLoadHookRetransformable || + kEvent == ArtJvmtiEvent::kClassFileLoadHookNonRetransformable, "Unsupported event"); + jint current_len = class_data_len; + unsigned char* current_class_data = const_cast<unsigned char*>(class_data); + ArtJvmTiEnv* last_env = nullptr; + for (ArtJvmTiEnv* env : envs) { + if (ShouldDispatch<kEvent>(env, thread)) { + jint new_len = 0; + unsigned char* new_data = nullptr; + auto callback = impl::GetCallback<kEvent>(env); + callback(env, + jnienv, + class_being_redefined, + loader, + name, + protection_domain, + current_len, + current_class_data, + &new_len, + &new_data); + if (new_data != nullptr && new_data != current_class_data) { + // Destroy the data the last transformer made. We skip this if the previous state was the + // initial one since we don't know here which jvmtiEnv allocated it. + // NB Currently this doesn't matter since all allocations just go to malloc but in the + // future we might have jvmtiEnv's keep track of their allocations for leak-checking. + if (last_env != nullptr) { + last_env->Deallocate(current_class_data); + } + last_env = env; + current_class_data = new_data; + current_len = new_len; + } + } + } + if (last_env != nullptr) { + *new_class_data_len = current_len; + *new_class_data = current_class_data; } - return nullptr; } -template <typename ...Args> +// Our goal for DispatchEvent: Do not allow implicit type conversion. Types of ...args must match +// exactly the argument types of the corresponding Jvmti kEvent function pointer. + +template <ArtJvmtiEvent kEvent, typename ...Args> inline void EventHandler::DispatchEvent(art::Thread* thread, - ArtJvmtiEvent event, Args... args) const { using FnType = void(jvmtiEnv*, Args...); for (ArtJvmTiEnv* env : envs) { - if (ShouldDispatch(event, env, thread)) { - FnType* callback = GetCallback<FnType>(env, event); + if (ShouldDispatch<kEvent>(env, thread)) { + FnType* callback = impl::GetCallback<kEvent>(env); if (callback != nullptr) { (*callback)(env, args...); } @@ -129,14 +181,66 @@ inline void EventHandler::DispatchEvent(art::Thread* thread, } } -inline bool EventHandler::ShouldDispatch(ArtJvmtiEvent event, - ArtJvmTiEnv* env, +// C++ does not allow partial template function specialization. The dispatch for our separated +// ClassFileLoadHook event types is the same, and in the DispatchClassFileLoadHookEvent helper. +// The following two DispatchEvent specializations dispatch to it. +template <> +inline void EventHandler::DispatchEvent<ArtJvmtiEvent::kClassFileLoadHookRetransformable>( + art::Thread* thread, + JNIEnv* jnienv, + jclass class_being_redefined, + jobject loader, + const char* name, + jobject protection_domain, + jint class_data_len, + const unsigned char* class_data, + jint* new_class_data_len, + unsigned char** new_class_data) const { + return DispatchClassFileLoadHookEvent<ArtJvmtiEvent::kClassFileLoadHookRetransformable>( + thread, + jnienv, + class_being_redefined, + loader, + name, + protection_domain, + class_data_len, + class_data, + new_class_data_len, + new_class_data); +} +template <> +inline void EventHandler::DispatchEvent<ArtJvmtiEvent::kClassFileLoadHookNonRetransformable>( + art::Thread* thread, + JNIEnv* jnienv, + jclass class_being_redefined, + jobject loader, + const char* name, + jobject protection_domain, + jint class_data_len, + const unsigned char* class_data, + jint* new_class_data_len, + unsigned char** new_class_data) const { + return DispatchClassFileLoadHookEvent<ArtJvmtiEvent::kClassFileLoadHookNonRetransformable>( + thread, + jnienv, + class_being_redefined, + loader, + name, + protection_domain, + class_data_len, + class_data, + new_class_data_len, + new_class_data); +} + +template <ArtJvmtiEvent kEvent> +inline bool EventHandler::ShouldDispatch(ArtJvmTiEnv* env, art::Thread* thread) { - bool dispatch = env->event_masks.global_event_mask.Test(event); + bool dispatch = env->event_masks.global_event_mask.Test(kEvent); - if (!dispatch && thread != nullptr && env->event_masks.unioned_thread_event_mask.Test(event)) { + if (!dispatch && thread != nullptr && env->event_masks.unioned_thread_event_mask.Test(kEvent)) { EventMask* mask = env->event_masks.GetEventMaskOrNull(thread); - dispatch = mask != nullptr && mask->Test(event); + dispatch = mask != nullptr && mask->Test(kEvent); } return dispatch; } diff --git a/runtime/openjdkjvmti/events.cc b/runtime/openjdkjvmti/events.cc index f38aa869d9..34492a91fd 100644 --- a/runtime/openjdkjvmti/events.cc +++ b/runtime/openjdkjvmti/events.cc @@ -144,6 +144,18 @@ void EventHandler::RegisterArtJvmTiEnv(ArtJvmTiEnv* env) { envs.push_back(env); } +void EventHandler::RemoveArtJvmTiEnv(ArtJvmTiEnv* env) { + auto it = std::find(envs.begin(), envs.end(), env); + if (it != envs.end()) { + envs.erase(it); + for (size_t i = static_cast<size_t>(ArtJvmtiEvent::kMinEventTypeVal); + i <= static_cast<size_t>(ArtJvmtiEvent::kMaxEventTypeVal); + ++i) { + RecalculateGlobalEventMask(static_cast<ArtJvmtiEvent>(i)); + } + } +} + static bool IsThreadControllable(ArtJvmtiEvent event) { switch (event) { case ArtJvmtiEvent::kVmInit: @@ -194,13 +206,12 @@ class JvmtiAllocationListener : public art::gc::AllocationListener { ScopedLocalRef<jclass> klass( jni_env, jni_env->AddLocalReference<jclass>(obj->Ptr()->GetClass())); - handler_->DispatchEvent(self, - ArtJvmtiEvent::kVmObjectAlloc, - jni_env, - thread.get(), - object.get(), - klass.get(), - byte_count); + handler_->DispatchEvent<ArtJvmtiEvent::kVmObjectAlloc>(self, + reinterpret_cast<JNIEnv*>(jni_env), + thread.get(), + object.get(), + klass.get(), + static_cast<jlong>(byte_count)); } } @@ -229,11 +240,11 @@ class JvmtiGcPauseListener : public art::gc::GcPauseListener { finish_enabled_(false) {} void StartPause() OVERRIDE { - handler_->DispatchEvent(nullptr, ArtJvmtiEvent::kGarbageCollectionStart); + handler_->DispatchEvent<ArtJvmtiEvent::kGarbageCollectionStart>(nullptr); } void EndPause() OVERRIDE { - handler_->DispatchEvent(nullptr, ArtJvmtiEvent::kGarbageCollectionFinish); + handler_->DispatchEvent<ArtJvmtiEvent::kGarbageCollectionFinish>(nullptr); } bool IsEnabled() { @@ -291,6 +302,64 @@ void EventHandler::HandleEventType(ArtJvmtiEvent event, bool enable) { } } +// Checks to see if the env has the capabilities associated with the given event. +static bool HasAssociatedCapability(ArtJvmTiEnv* env, + ArtJvmtiEvent event) { + jvmtiCapabilities caps = env->capabilities; + switch (event) { + case ArtJvmtiEvent::kBreakpoint: + return caps.can_generate_breakpoint_events == 1; + + case ArtJvmtiEvent::kCompiledMethodLoad: + case ArtJvmtiEvent::kCompiledMethodUnload: + return caps.can_generate_compiled_method_load_events == 1; + + case ArtJvmtiEvent::kException: + case ArtJvmtiEvent::kExceptionCatch: + return caps.can_generate_exception_events == 1; + + case ArtJvmtiEvent::kFieldAccess: + return caps.can_generate_field_access_events == 1; + + case ArtJvmtiEvent::kFieldModification: + return caps.can_generate_field_modification_events == 1; + + case ArtJvmtiEvent::kFramePop: + return caps.can_generate_frame_pop_events == 1; + + case ArtJvmtiEvent::kGarbageCollectionStart: + case ArtJvmtiEvent::kGarbageCollectionFinish: + return caps.can_generate_garbage_collection_events == 1; + + case ArtJvmtiEvent::kMethodEntry: + return caps.can_generate_method_entry_events == 1; + + case ArtJvmtiEvent::kMethodExit: + return caps.can_generate_method_exit_events == 1; + + case ArtJvmtiEvent::kMonitorContendedEnter: + case ArtJvmtiEvent::kMonitorContendedEntered: + case ArtJvmtiEvent::kMonitorWait: + case ArtJvmtiEvent::kMonitorWaited: + return caps.can_generate_monitor_events == 1; + + case ArtJvmtiEvent::kNativeMethodBind: + return caps.can_generate_native_method_bind_events == 1; + + case ArtJvmtiEvent::kObjectFree: + return caps.can_generate_object_free_events == 1; + + case ArtJvmtiEvent::kSingleStep: + return caps.can_generate_single_step_events == 1; + + case ArtJvmtiEvent::kVmObjectAlloc: + return caps.can_generate_vm_object_alloc_events == 1; + + default: + return true; + } +} + jvmtiError EventHandler::SetEvent(ArtJvmTiEnv* env, art::Thread* thread, ArtJvmtiEvent event, @@ -307,8 +376,6 @@ jvmtiError EventHandler::SetEvent(ArtJvmTiEnv* env, } } - // TODO: Capability check. - if (mode != JVMTI_ENABLE && mode != JVMTI_DISABLE) { return ERR(ILLEGAL_ARGUMENT); } @@ -317,6 +384,10 @@ jvmtiError EventHandler::SetEvent(ArtJvmTiEnv* env, return ERR(INVALID_EVENT_TYPE); } + if (!HasAssociatedCapability(env, event)) { + return ERR(MUST_POSSESS_CAPABILITY); + } + bool old_state = global_mask.Test(event); if (mode == JVMTI_ENABLE) { diff --git a/runtime/openjdkjvmti/events.h b/runtime/openjdkjvmti/events.h index 7990141562..4e20d1776b 100644 --- a/runtime/openjdkjvmti/events.h +++ b/runtime/openjdkjvmti/events.h @@ -141,6 +141,9 @@ class EventHandler { // enabled, yet. void RegisterArtJvmTiEnv(ArtJvmTiEnv* env); + // Remove an env. + void RemoveArtJvmTiEnv(ArtJvmTiEnv* env); + bool IsEventEnabledAnywhere(ArtJvmtiEvent event) const { if (!EventMask::EventIsInRange(event)) { return false; @@ -153,9 +156,9 @@ class EventHandler { ArtJvmtiEvent event, jvmtiEventMode mode); - template <typename ...Args> + template <ArtJvmtiEvent kEvent, typename ...Args> ALWAYS_INLINE - inline void DispatchEvent(art::Thread* thread, ArtJvmtiEvent event, Args... args) const; + inline void DispatchEvent(art::Thread* thread, Args... args) const; // Tell the event handler capabilities were added/lost so it can adjust the sent events.If // caps_added is true then caps is all the newly set capabilities of the jvmtiEnv. If it is false @@ -166,8 +169,9 @@ class EventHandler { bool added); private: + template <ArtJvmtiEvent kEvent> ALWAYS_INLINE - static inline bool ShouldDispatch(ArtJvmtiEvent event, ArtJvmTiEnv* env, art::Thread* thread); + static inline bool ShouldDispatch(ArtJvmTiEnv* env, art::Thread* thread); ALWAYS_INLINE inline bool NeedsEventUpdate(ArtJvmTiEnv* env, @@ -178,6 +182,18 @@ class EventHandler { ALWAYS_INLINE inline void RecalculateGlobalEventMask(ArtJvmtiEvent event); + template <ArtJvmtiEvent kEvent> + ALWAYS_INLINE inline void DispatchClassFileLoadHookEvent(art::Thread* thread, + JNIEnv* jnienv, + jclass class_being_redefined, + jobject loader, + const char* name, + jobject protection_domain, + jint class_data_len, + const unsigned char* class_data, + jint* new_class_data_len, + unsigned char** new_class_data) const; + void HandleEventType(ArtJvmtiEvent event, bool enable); // List of all JvmTiEnv objects that have been created, in their creation order. diff --git a/runtime/openjdkjvmti/object_tagging.cc b/runtime/openjdkjvmti/object_tagging.cc index 94cb46a428..b27c2a3834 100644 --- a/runtime/openjdkjvmti/object_tagging.cc +++ b/runtime/openjdkjvmti/object_tagging.cc @@ -207,7 +207,7 @@ void ObjectTagTable::SweepImpl(art::IsMarkedVisitor* visitor) { } void ObjectTagTable::HandleNullSweep(jlong tag) { - event_handler_->DispatchEvent(nullptr, ArtJvmtiEvent::kObjectFree, tag); + event_handler_->DispatchEvent<ArtJvmtiEvent::kObjectFree>(nullptr, tag); } template <typename T, ObjectTagTable::TableUpdateNullTarget kTargetNull> diff --git a/runtime/openjdkjvmti/ti_class.cc b/runtime/openjdkjvmti/ti_class.cc index d1324bc13f..b6de592142 100644 --- a/runtime/openjdkjvmti/ti_class.cc +++ b/runtime/openjdkjvmti/ti_class.cc @@ -31,16 +31,309 @@ #include "ti_class.h" +#include "android-base/stringprintf.h" + +#include <mutex> +#include <unordered_set> + #include "art_jvmti.h" +#include "base/macros.h" #include "class_table-inl.h" #include "class_linker.h" +#include "common_throws.h" +#include "events-inl.h" +#include "handle.h" +#include "jni_env_ext-inl.h" #include "jni_internal.h" +#include "mirror/array-inl.h" +#include "mirror/class-inl.h" +#include "mirror/class_ext.h" #include "runtime.h" +#include "runtime_callbacks.h" +#include "ScopedLocalRef.h" #include "scoped_thread_state_change-inl.h" #include "thread-inl.h" +#include "thread_list.h" +#include "ti_redefine.h" +#include "utils.h" namespace openjdkjvmti { +using android::base::StringPrintf; + +static std::unique_ptr<const art::DexFile> MakeSingleDexFile(art::Thread* self, + const char* descriptor, + const std::string& orig_location, + jint final_len, + const unsigned char* final_dex_data) + REQUIRES_SHARED(art::Locks::mutator_lock_) { + // Make the mmap + std::string error_msg; + std::unique_ptr<art::MemMap> map(Redefiner::MoveDataToMemMap(orig_location, + final_len, + final_dex_data, + &error_msg)); + if (map.get() == nullptr) { + LOG(WARNING) << "Unable to allocate mmap for redefined dex file! Error was: " << error_msg; + self->ThrowOutOfMemoryError(StringPrintf( + "Unable to allocate dex file for transformation of %s", descriptor).c_str()); + return nullptr; + } + + // Make a dex-file + if (map->Size() < sizeof(art::DexFile::Header)) { + LOG(WARNING) << "Could not read dex file header because dex_data was too short"; + art::ThrowClassFormatError(nullptr, + "Unable to read transformed dex file of %s", + descriptor); + return nullptr; + } + uint32_t checksum = reinterpret_cast<const art::DexFile::Header*>(map->Begin())->checksum_; + std::unique_ptr<const art::DexFile> dex_file(art::DexFile::Open(map->GetName(), + checksum, + std::move(map), + /*verify*/true, + /*verify_checksum*/true, + &error_msg)); + if (dex_file.get() == nullptr) { + LOG(WARNING) << "Unable to load modified dex file for " << descriptor << ": " << error_msg; + art::ThrowClassFormatError(nullptr, + "Unable to read transformed dex file of %s because %s", + descriptor, + error_msg.c_str()); + return nullptr; + } + if (dex_file->NumClassDefs() != 1) { + LOG(WARNING) << "Dex file contains more than 1 class_def. Ignoring."; + // TODO Throw some other sort of error here maybe? + art::ThrowClassFormatError( + nullptr, + "Unable to use transformed dex file of %s because it contained too many classes", + descriptor); + return nullptr; + } + return dex_file; +} + +struct ClassCallback : public art::ClassLoadCallback { + void ClassPreDefine(const char* descriptor, + art::Handle<art::mirror::Class> klass, + art::Handle<art::mirror::ClassLoader> class_loader, + const art::DexFile& initial_dex_file, + const art::DexFile::ClassDef& initial_class_def ATTRIBUTE_UNUSED, + /*out*/art::DexFile const** final_dex_file, + /*out*/art::DexFile::ClassDef const** final_class_def) + OVERRIDE REQUIRES_SHARED(art::Locks::mutator_lock_) { + bool is_enabled = + event_handler->IsEventEnabledAnywhere(ArtJvmtiEvent::kClassFileLoadHookRetransformable) || + event_handler->IsEventEnabledAnywhere(ArtJvmtiEvent::kClassFileLoadHookNonRetransformable); + if (!is_enabled) { + return; + } + if (descriptor[0] != 'L') { + // It is a primitive or array. Just return + return; + } + std::string name(art::PrettyDescriptor(descriptor)); + + art::Thread* self = art::Thread::Current(); + art::JNIEnvExt* env = self->GetJniEnv(); + ScopedLocalRef<jobject> loader( + env, class_loader.IsNull() ? nullptr : env->AddLocalReference<jobject>(class_loader.Get())); + // Go back to native. + art::ScopedThreadSuspension sts(self, art::ThreadState::kNative); + // Call all Non-retransformable agents. + jint post_no_redefine_len = 0; + unsigned char* post_no_redefine_dex_data = nullptr; + std::unique_ptr<const unsigned char> post_no_redefine_unique_ptr(nullptr); + event_handler->DispatchEvent<ArtJvmtiEvent::kClassFileLoadHookNonRetransformable>( + self, + static_cast<JNIEnv*>(env), + static_cast<jclass>(nullptr), // The class doesn't really exist yet so send null. + loader.get(), + name.c_str(), + static_cast<jobject>(nullptr), // Android doesn't seem to have protection domains + static_cast<jint>(initial_dex_file.Size()), + static_cast<const unsigned char*>(initial_dex_file.Begin()), + static_cast<jint*>(&post_no_redefine_len), + static_cast<unsigned char**>(&post_no_redefine_dex_data)); + if (post_no_redefine_dex_data == nullptr) { + DCHECK_EQ(post_no_redefine_len, 0); + post_no_redefine_dex_data = const_cast<unsigned char*>(initial_dex_file.Begin()); + post_no_redefine_len = initial_dex_file.Size(); + } else { + post_no_redefine_unique_ptr = std::unique_ptr<const unsigned char>(post_no_redefine_dex_data); + DCHECK_GT(post_no_redefine_len, 0); + } + // Call all retransformable agents. + jint final_len = 0; + unsigned char* final_dex_data = nullptr; + std::unique_ptr<const unsigned char> final_dex_unique_ptr(nullptr); + event_handler->DispatchEvent<ArtJvmtiEvent::kClassFileLoadHookRetransformable>( + self, + static_cast<JNIEnv*>(env), + static_cast<jclass>(nullptr), // The class doesn't really exist yet so send null. + loader.get(), + name.c_str(), + static_cast<jobject>(nullptr), // Android doesn't seem to have protection domains + static_cast<jint>(post_no_redefine_len), + static_cast<const unsigned char*>(post_no_redefine_dex_data), + static_cast<jint*>(&final_len), + static_cast<unsigned char**>(&final_dex_data)); + if (final_dex_data == nullptr) { + DCHECK_EQ(final_len, 0); + final_dex_data = post_no_redefine_dex_data; + final_len = post_no_redefine_len; + } else { + final_dex_unique_ptr = std::unique_ptr<const unsigned char>(final_dex_data); + DCHECK_GT(final_len, 0); + } + + if (final_dex_data != initial_dex_file.Begin()) { + LOG(WARNING) << "Changing class " << descriptor; + art::ScopedObjectAccess soa(self); + art::StackHandleScope<2> hs(self); + // Save the results of all the non-retransformable agents. + // First allocate the ClassExt + art::Handle<art::mirror::ClassExt> ext(hs.NewHandle(klass->EnsureExtDataPresent(self))); + // Make sure we have a ClassExt. This is fine even though we are a temporary since it will + // get copied. + if (ext.IsNull()) { + // We will just return failure if we fail to allocate + LOG(WARNING) << "Could not allocate ext-data for class '" << descriptor << "'. " + << "Aborting transformation since we will be unable to store it."; + self->AssertPendingOOMException(); + return; + } + + // Allocate the byte array to store the dex file bytes in. + art::Handle<art::mirror::ByteArray> arr(hs.NewHandle( + art::mirror::ByteArray::AllocateAndFill( + self, + reinterpret_cast<const signed char*>(post_no_redefine_dex_data), + post_no_redefine_len))); + if (arr.IsNull()) { + LOG(WARNING) << "Unable to allocate byte array for initial dex-file bytes. Aborting " + << "transformation"; + self->AssertPendingOOMException(); + return; + } + + std::unique_ptr<const art::DexFile> dex_file(MakeSingleDexFile(self, + descriptor, + initial_dex_file.GetLocation(), + final_len, + final_dex_data)); + if (dex_file.get() == nullptr) { + return; + } + + // TODO Check Redefined dex file for invariants. + LOG(WARNING) << "Dex file created by class-definition time transformation of " + << descriptor << " is not checked for all retransformation invariants."; + // TODO Put it in classpath + LOG(WARNING) << "Dex file created for class-definition time transformation of " + << descriptor << " was not added to any classpaths!"; + // Actually set the ClassExt's original bytes once we have actually succeeded. + ext->SetOriginalDexFileBytes(arr.Get()); + // Set the return values + *final_class_def = &dex_file->GetClassDef(0); + *final_dex_file = dex_file.release(); + } + } + + void ClassLoad(art::Handle<art::mirror::Class> klass) REQUIRES_SHARED(art::Locks::mutator_lock_) { + if (event_handler->IsEventEnabledAnywhere(ArtJvmtiEvent::kClassLoad)) { + art::Thread* thread = art::Thread::Current(); + ScopedLocalRef<jclass> jklass(thread->GetJniEnv(), + thread->GetJniEnv()->AddLocalReference<jclass>(klass.Get())); + ScopedLocalRef<jthread> thread_jni( + thread->GetJniEnv(), thread->GetJniEnv()->AddLocalReference<jthread>(thread->GetPeer())); + { + art::ScopedThreadSuspension sts(thread, art::ThreadState::kNative); + event_handler->DispatchEvent<ArtJvmtiEvent::kClassLoad>( + thread, + static_cast<JNIEnv*>(thread->GetJniEnv()), + thread_jni.get(), + jklass.get()); + } + AddTempClass(thread, jklass.get()); + } + } + + void ClassPrepare(art::Handle<art::mirror::Class> temp_klass ATTRIBUTE_UNUSED, + art::Handle<art::mirror::Class> klass) + REQUIRES_SHARED(art::Locks::mutator_lock_) { + if (event_handler->IsEventEnabledAnywhere(ArtJvmtiEvent::kClassPrepare)) { + art::Thread* thread = art::Thread::Current(); + ScopedLocalRef<jclass> jklass(thread->GetJniEnv(), + thread->GetJniEnv()->AddLocalReference<jclass>(klass.Get())); + ScopedLocalRef<jthread> thread_jni( + thread->GetJniEnv(), thread->GetJniEnv()->AddLocalReference<jthread>(thread->GetPeer())); + art::ScopedThreadSuspension sts(thread, art::ThreadState::kNative); + event_handler->DispatchEvent<ArtJvmtiEvent::kClassPrepare>( + thread, + static_cast<JNIEnv*>(thread->GetJniEnv()), + thread_jni.get(), + jklass.get()); + } + } + + void AddTempClass(art::Thread* self, jclass klass) { + std::unique_lock<std::mutex> mu(temp_classes_lock); + temp_classes.push_back(reinterpret_cast<jclass>(self->GetJniEnv()->NewGlobalRef(klass))); + } + + void HandleTempClass(art::Handle<art::mirror::Class> temp_klass, + art::Handle<art::mirror::Class> klass) + REQUIRES_SHARED(art::Locks::mutator_lock_) { + std::unique_lock<std::mutex> mu(temp_classes_lock); + if (temp_classes.empty()) { + return; + } + + art::Thread* self = art::Thread::Current(); + for (auto it = temp_classes.begin(); it != temp_classes.end(); ++it) { + if (temp_klass.Get() == art::ObjPtr<art::mirror::Class>::DownCast(self->DecodeJObject(*it))) { + temp_classes.erase(it); + FixupTempClass(temp_klass, klass); + } + } + } + + void FixupTempClass(art::Handle<art::mirror::Class> temp_klass ATTRIBUTE_UNUSED, + art::Handle<art::mirror::Class> klass ATTRIBUTE_UNUSED) + REQUIRES_SHARED(art::Locks::mutator_lock_) { + // TODO: Implement. + } + + // A set of all the temp classes we have handed out. We have to fix up references to these. + // For simplicity, we store the temp classes as JNI global references in a vector. Normally a + // Prepare event will closely follow, so the vector should be small. + std::mutex temp_classes_lock; + std::vector<jclass> temp_classes; + + EventHandler* event_handler = nullptr; +}; + +ClassCallback gClassCallback; + +void ClassUtil::Register(EventHandler* handler) { + gClassCallback.event_handler = handler; + art::ScopedThreadStateChange stsc(art::Thread::Current(), + art::ThreadState::kWaitingForDebuggerToAttach); + art::ScopedSuspendAll ssa("Add load callback"); + art::Runtime::Current()->GetRuntimeCallbacks()->AddClassLoadCallback(&gClassCallback); +} + +void ClassUtil::Unregister() { + art::ScopedThreadStateChange stsc(art::Thread::Current(), + art::ThreadState::kWaitingForDebuggerToAttach); + art::ScopedSuspendAll ssa("Remove thread callback"); + art::Runtime* runtime = art::Runtime::Current(); + runtime->GetRuntimeCallbacks()->RemoveClassLoadCallback(&gClassCallback); +} + jvmtiError ClassUtil::GetClassFields(jvmtiEnv* env, jclass jklass, jint* field_count_ptr, @@ -200,7 +493,9 @@ jvmtiError ClassUtil::GetClassSignature(jvmtiEnv* env, } // TODO: Support generic signature. - *generic_ptr = nullptr; + if (generic_ptr != nullptr) { + *generic_ptr = nullptr; + } // Everything is fine, release the buffers. sig_copy.release(); @@ -417,4 +712,35 @@ jvmtiError ClassUtil::GetClassLoaderClasses(jvmtiEnv* env, return ERR(NONE); } +jvmtiError ClassUtil::GetClassVersionNumbers(jvmtiEnv* env ATTRIBUTE_UNUSED, + jclass jklass, + jint* minor_version_ptr, + jint* major_version_ptr) { + art::ScopedObjectAccess soa(art::Thread::Current()); + if (jklass == nullptr) { + return ERR(INVALID_CLASS); + } + art::ObjPtr<art::mirror::Object> jklass_obj = soa.Decode<art::mirror::Object>(jklass); + if (!jklass_obj->IsClass()) { + return ERR(INVALID_CLASS); + } + art::ObjPtr<art::mirror::Class> klass = jklass_obj->AsClass(); + if (klass->IsPrimitive() || klass->IsArrayClass()) { + return ERR(INVALID_CLASS); + } + + if (minor_version_ptr == nullptr || major_version_ptr == nullptr) { + return ERR(NULL_POINTER); + } + + // Note: proxies will show the dex file version of java.lang.reflect.Proxy, as that is + // what their dex cache copies from. + uint32_t version = klass->GetDexFile().GetHeader().GetVersion(); + + *major_version_ptr = static_cast<jint>(version); + *minor_version_ptr = 0; + + return ERR(NONE); +} + } // namespace openjdkjvmti diff --git a/runtime/openjdkjvmti/ti_class.h b/runtime/openjdkjvmti/ti_class.h index 7a0fafbc83..aa2260f035 100644 --- a/runtime/openjdkjvmti/ti_class.h +++ b/runtime/openjdkjvmti/ti_class.h @@ -37,8 +37,13 @@ namespace openjdkjvmti { +class EventHandler; + class ClassUtil { public: + static void Register(EventHandler* event_handler); + static void Unregister(); + static jvmtiError GetClassFields(jvmtiEnv* env, jclass klass, jint* field_count_ptr, @@ -72,6 +77,11 @@ class ClassUtil { static jvmtiError IsInterface(jvmtiEnv* env, jclass klass, jboolean* is_interface_ptr); static jvmtiError IsArrayClass(jvmtiEnv* env, jclass klass, jboolean* is_array_class_ptr); + + static jvmtiError GetClassVersionNumbers(jvmtiEnv* env, + jclass klass, + jint* minor_version_ptr, + jint* major_version_ptr); }; } // namespace openjdkjvmti diff --git a/runtime/openjdkjvmti/ti_class_definition.cc b/runtime/openjdkjvmti/ti_class_definition.cc new file mode 100644 index 0000000000..2c2a79bc58 --- /dev/null +++ b/runtime/openjdkjvmti/ti_class_definition.cc @@ -0,0 +1,55 @@ +/* Copyright (C) 2016 The Android Open Source Project + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This file implements interfaces from the file jvmti.h. This implementation + * is licensed under the same terms as the file jvmti.h. The + * copyright and license information for the file jvmti.h follows. + * + * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#include "ti_class_definition.h" + +#include "dex_file.h" +#include "handle_scope-inl.h" +#include "handle.h" +#include "mirror/class-inl.h" +#include "mirror/object-inl.h" +#include "thread.h" + +namespace openjdkjvmti { + +bool ArtClassDefinition::IsModified(art::Thread* self) const { + if (modified) { + return true; + } + // Check if the dex file we want to set is the same as the current one. + art::StackHandleScope<1> hs(self); + art::Handle<art::mirror::Class> h_klass(hs.NewHandle(self->DecodeJObject(klass)->AsClass())); + const art::DexFile& cur_dex_file = h_klass->GetDexFile(); + return static_cast<jint>(cur_dex_file.Size()) != dex_len || + memcmp(cur_dex_file.Begin(), dex_data.get(), dex_len) != 0; +} + +} // namespace openjdkjvmti diff --git a/runtime/openjdkjvmti/ti_class_definition.h b/runtime/openjdkjvmti/ti_class_definition.h new file mode 100644 index 0000000000..dbe5da2d63 --- /dev/null +++ b/runtime/openjdkjvmti/ti_class_definition.h @@ -0,0 +1,77 @@ +/* Copyright (C) 2017 The Android Open Source Project + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This file implements interfaces from the file jvmti.h. This implementation + * is licensed under the same terms as the file jvmti.h. The + * copyright and license information for the file jvmti.h follows. + * + * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#ifndef ART_RUNTIME_OPENJDKJVMTI_TI_CLASS_DEFINITION_H_ +#define ART_RUNTIME_OPENJDKJVMTI_TI_CLASS_DEFINITION_H_ + +#include "art_jvmti.h" + +namespace openjdkjvmti { + +// A struct that stores data needed for redefining/transforming classes. This structure should only +// even be accessed from a single thread and must not survive past the completion of the +// redefinition/retransformation function that created it. +struct ArtClassDefinition { + public: + jclass klass; + jobject loader; + std::string name; + jobject protection_domain; + jint dex_len; + JvmtiUniquePtr dex_data; + art::ArraySlice<const unsigned char> original_dex_file; + + ArtClassDefinition() = default; + ArtClassDefinition(ArtClassDefinition&& o) = default; + + void SetNewDexData(ArtJvmTiEnv* env, jint new_dex_len, unsigned char* new_dex_data) { + if (new_dex_data == nullptr) { + return; + } else if (new_dex_data != dex_data.get() || new_dex_len != dex_len) { + SetModified(); + dex_len = new_dex_len; + dex_data = MakeJvmtiUniquePtr(env, new_dex_data); + } + } + + void SetModified() { + modified = true; + } + + bool IsModified(art::Thread* self) const REQUIRES_SHARED(art::Locks::mutator_lock_); + + private: + bool modified; +}; + +} // namespace openjdkjvmti + +#endif // ART_RUNTIME_OPENJDKJVMTI_TI_CLASS_DEFINITION_H_ diff --git a/runtime/openjdkjvmti/ti_dump.cc b/runtime/openjdkjvmti/ti_dump.cc new file mode 100644 index 0000000000..d9e3ef1bcf --- /dev/null +++ b/runtime/openjdkjvmti/ti_dump.cc @@ -0,0 +1,74 @@ +/* Copyright (C) 2017 The Android Open Source Project + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This file implements interfaces from the file jvmti.h. This implementation + * is licensed under the same terms as the file jvmti.h. The + * copyright and license information for the file jvmti.h follows. + * + * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#include "ti_dump.h" + +#include <limits> + + +#include "art_jvmti.h" +#include "base/mutex.h" +#include "events-inl.h" +#include "runtime_callbacks.h" +#include "scoped_thread_state_change-inl.h" +#include "thread-inl.h" +#include "thread_list.h" + +namespace openjdkjvmti { + +struct DumpCallback : public art::RuntimeSigQuitCallback { + void SigQuit() OVERRIDE REQUIRES_SHARED(art::Locks::mutator_lock_) { + art::Thread* thread = art::Thread::Current(); + art::ScopedThreadSuspension sts(thread, art::ThreadState::kNative); + event_handler->DispatchEvent<ArtJvmtiEvent::kDataDumpRequest>(nullptr); + } + + EventHandler* event_handler = nullptr; +}; + +static DumpCallback gDumpCallback; + +void DumpUtil::Register(EventHandler* handler) { + gDumpCallback.event_handler = handler; + art::ScopedThreadStateChange stsc(art::Thread::Current(), + art::ThreadState::kWaitingForDebuggerToAttach); + art::ScopedSuspendAll ssa("Add sigquit callback"); + art::Runtime::Current()->GetRuntimeCallbacks()->AddRuntimeSigQuitCallback(&gDumpCallback); +} + +void DumpUtil::Unregister() { + art::ScopedThreadStateChange stsc(art::Thread::Current(), + art::ThreadState::kWaitingForDebuggerToAttach); + art::ScopedSuspendAll ssa("Remove sigquit callback"); + art::Runtime::Current()->GetRuntimeCallbacks()->RemoveRuntimeSigQuitCallback(&gDumpCallback); +} + +} // namespace openjdkjvmti diff --git a/runtime/openjdkjvmti/ti_dump.h b/runtime/openjdkjvmti/ti_dump.h new file mode 100644 index 0000000000..67cb2394f8 --- /dev/null +++ b/runtime/openjdkjvmti/ti_dump.h @@ -0,0 +1,50 @@ +/* Copyright (C) 2017 The Android Open Source Project + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This file implements interfaces from the file jvmti.h. This implementation + * is licensed under the same terms as the file jvmti.h. The + * copyright and license information for the file jvmti.h follows. + * + * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#ifndef ART_RUNTIME_OPENJDKJVMTI_TI_DUMP_H_ +#define ART_RUNTIME_OPENJDKJVMTI_TI_DUMP_H_ + +#include "jni.h" +#include "jvmti.h" + +namespace openjdkjvmti { + +class EventHandler; + +class DumpUtil { + public: + static void Register(EventHandler* event_handler); + static void Unregister(); +}; + +} // namespace openjdkjvmti + +#endif // ART_RUNTIME_OPENJDKJVMTI_TI_DUMP_H_ diff --git a/runtime/openjdkjvmti/ti_phase.cc b/runtime/openjdkjvmti/ti_phase.cc new file mode 100644 index 0000000000..4970288250 --- /dev/null +++ b/runtime/openjdkjvmti/ti_phase.cc @@ -0,0 +1,139 @@ +/* Copyright (C) 2017 The Android Open Source Project + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This file implements interfaces from the file jvmti.h. This implementation + * is licensed under the same terms as the file jvmti.h. The + * copyright and license information for the file jvmti.h follows. + * + * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#include "ti_phase.h" + +#include "art_jvmti.h" +#include "base/macros.h" +#include "events-inl.h" +#include "runtime.h" +#include "runtime_callbacks.h" +#include "ScopedLocalRef.h" +#include "scoped_thread_state_change-inl.h" +#include "thread-inl.h" +#include "thread_list.h" + +namespace openjdkjvmti { + +jvmtiPhase PhaseUtil::current_phase_ = static_cast<jvmtiPhase>(0); + +struct PhaseUtil::PhaseCallback : public art::RuntimePhaseCallback { + inline static JNIEnv* GetJniEnv() { + return reinterpret_cast<JNIEnv*>(art::Thread::Current()->GetJniEnv()); + } + + inline static jthread GetCurrentJThread() { + art::ScopedObjectAccess soa(art::Thread::Current()); + return soa.AddLocalReference<jthread>(soa.Self()->GetPeer()); + } + + void NextRuntimePhase(RuntimePhase phase) REQUIRES_SHARED(art::Locks::mutator_lock_) OVERRIDE { + // TODO: Events. + switch (phase) { + case RuntimePhase::kInitialAgents: + PhaseUtil::current_phase_ = JVMTI_PHASE_PRIMORDIAL; + break; + case RuntimePhase::kStart: + { + art::ScopedThreadSuspension sts(art::Thread::Current(), art::ThreadState::kNative); + event_handler->DispatchEvent<ArtJvmtiEvent::kVmStart>(nullptr, GetJniEnv()); + PhaseUtil::current_phase_ = JVMTI_PHASE_START; + } + break; + case RuntimePhase::kInit: + { + ScopedLocalRef<jthread> thread(GetJniEnv(), GetCurrentJThread()); + art::ScopedThreadSuspension sts(art::Thread::Current(), art::ThreadState::kNative); + event_handler->DispatchEvent<ArtJvmtiEvent::kVmInit>(nullptr, GetJniEnv(), thread.get()); + PhaseUtil::current_phase_ = JVMTI_PHASE_LIVE; + } + break; + case RuntimePhase::kDeath: + { + art::ScopedThreadSuspension sts(art::Thread::Current(), art::ThreadState::kNative); + event_handler->DispatchEvent<ArtJvmtiEvent::kVmDeath>(nullptr, GetJniEnv()); + PhaseUtil::current_phase_ = JVMTI_PHASE_DEAD; + } + // TODO: Block events now. + break; + } + } + + EventHandler* event_handler = nullptr; +}; + +PhaseUtil::PhaseCallback gPhaseCallback; + +jvmtiError PhaseUtil::GetPhase(jvmtiEnv* env ATTRIBUTE_UNUSED, jvmtiPhase* phase_ptr) { + if (phase_ptr == nullptr) { + return ERR(NULL_POINTER); + } + jvmtiPhase now = PhaseUtil::current_phase_; + DCHECK(now == JVMTI_PHASE_ONLOAD || + now == JVMTI_PHASE_PRIMORDIAL || + now == JVMTI_PHASE_START || + now == JVMTI_PHASE_LIVE || + now == JVMTI_PHASE_DEAD); + *phase_ptr = now; + return ERR(NONE); +} + +void PhaseUtil::SetToOnLoad() { + DCHECK_EQ(0u, static_cast<size_t>(PhaseUtil::current_phase_)); + PhaseUtil::current_phase_ = JVMTI_PHASE_ONLOAD; +} + +void PhaseUtil::SetToPrimordial() { + DCHECK_EQ(static_cast<size_t>(JVMTI_PHASE_ONLOAD), static_cast<size_t>(PhaseUtil::current_phase_)); + PhaseUtil::current_phase_ = JVMTI_PHASE_ONLOAD; +} + +void PhaseUtil::SetToLive() { + DCHECK_EQ(static_cast<size_t>(0), static_cast<size_t>(PhaseUtil::current_phase_)); + PhaseUtil::current_phase_ = JVMTI_PHASE_LIVE; +} + +void PhaseUtil::Register(EventHandler* handler) { + gPhaseCallback.event_handler = handler; + art::ScopedThreadStateChange stsc(art::Thread::Current(), + art::ThreadState::kWaitingForDebuggerToAttach); + art::ScopedSuspendAll ssa("Add phase callback"); + art::Runtime::Current()->GetRuntimeCallbacks()->AddRuntimePhaseCallback(&gPhaseCallback); +} + +void PhaseUtil::Unregister() { + art::ScopedThreadStateChange stsc(art::Thread::Current(), + art::ThreadState::kWaitingForDebuggerToAttach); + art::ScopedSuspendAll ssa("Remove phase callback"); + art::Runtime::Current()->GetRuntimeCallbacks()->RemoveRuntimePhaseCallback(&gPhaseCallback); +} + +} // namespace openjdkjvmti diff --git a/runtime/openjdkjvmti/ti_phase.h b/runtime/openjdkjvmti/ti_phase.h new file mode 100644 index 0000000000..bd15fa6891 --- /dev/null +++ b/runtime/openjdkjvmti/ti_phase.h @@ -0,0 +1,66 @@ +/* Copyright (C) 2017 The Android Open Source Project + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This file implements interfaces from the file jvmti.h. This implementation + * is licensed under the same terms as the file jvmti.h. The + * copyright and license information for the file jvmti.h follows. + * + * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#ifndef ART_RUNTIME_OPENJDKJVMTI_TI_PHASE_H_ +#define ART_RUNTIME_OPENJDKJVMTI_TI_PHASE_H_ + +#include "jni.h" +#include "jvmti.h" + +namespace openjdkjvmti { + +class EventHandler; + +class PhaseUtil { + public: + static jvmtiError GetPhase(jvmtiEnv* env, jvmtiPhase* phase_ptr); + + static void Register(EventHandler* event_handler); + static void Unregister(); + + // Move the phase from unitialized to LOAD. + static void SetToOnLoad(); + + // Move the phase from LOAD to PRIMORDIAL. + static void SetToPrimordial(); + + // Move the phase from unitialized to LIVE. + static void SetToLive(); + + struct PhaseCallback; + + private: + static jvmtiPhase current_phase_; +}; + +} // namespace openjdkjvmti + +#endif // ART_RUNTIME_OPENJDKJVMTI_TI_PHASE_H_ diff --git a/runtime/openjdkjvmti/ti_redefine.cc b/runtime/openjdkjvmti/ti_redefine.cc index 6af51c4c6c..d2ddc21cd4 100644 --- a/runtime/openjdkjvmti/ti_redefine.cc +++ b/runtime/openjdkjvmti/ti_redefine.cc @@ -36,6 +36,7 @@ #include "android-base/stringprintf.h" #include "art_jvmti.h" +#include "base/array_slice.h" #include "base/logging.h" #include "dex_file.h" #include "dex_file_types.h" @@ -228,11 +229,17 @@ std::unique_ptr<art::MemMap> Redefiner::MoveDataToMemMap(const std::string& orig return map; } -Redefiner::ClassRedefinition::ClassRedefinition(Redefiner* driver, - jclass klass, - const art::DexFile* redefined_dex_file, - const char* class_sig) : - driver_(driver), klass_(klass), dex_file_(redefined_dex_file), class_sig_(class_sig) { +Redefiner::ClassRedefinition::ClassRedefinition( + Redefiner* driver, + jclass klass, + const art::DexFile* redefined_dex_file, + const char* class_sig, + art::ArraySlice<const unsigned char> orig_dex_file) : + driver_(driver), + klass_(klass), + dex_file_(redefined_dex_file), + class_sig_(class_sig), + original_dex_file_(orig_dex_file) { GetMirrorClass()->MonitorEnter(driver_->self_); } @@ -242,14 +249,12 @@ Redefiner::ClassRedefinition::~ClassRedefinition() { } } -// TODO This should handle doing multiple classes at once so we need to do less cleanup when things -// go wrong. jvmtiError Redefiner::RedefineClasses(ArtJvmTiEnv* env, art::Runtime* runtime, art::Thread* self, jint class_count, const jvmtiClassDefinition* definitions, - std::string* error_msg) { + /*out*/std::string* error_msg) { if (env == nullptr) { *error_msg = "env was null!"; return ERR(INVALID_ENVIRONMENT); @@ -263,46 +268,95 @@ jvmtiError Redefiner::RedefineClasses(ArtJvmTiEnv* env, *error_msg = "null definitions!"; return ERR(NULL_POINTER); } + std::vector<ArtClassDefinition> def_vector; + def_vector.reserve(class_count); + for (jint i = 0; i < class_count; i++) { + // 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 + // will need to keep the original bytes around unaltered for subsequent RetransformClasses calls + // to get the passed in bytes. + // TODO Implement saving the original bytes. + unsigned char* class_bytes_copy = nullptr; + jvmtiError res = env->Allocate(definitions[i].class_byte_count, &class_bytes_copy); + if (res != OK) { + return res; + } + memcpy(class_bytes_copy, definitions[i].class_bytes, definitions[i].class_byte_count); + + ArtClassDefinition def; + def.dex_len = definitions[i].class_byte_count; + def.dex_data = MakeJvmtiUniquePtr(env, class_bytes_copy); + // We are definitely modified. + def.SetModified(); + def.original_dex_file = art::ArraySlice<const unsigned char>(definitions[i].class_bytes, + definitions[i].class_byte_count); + res = Transformer::FillInTransformationData(env, definitions[i].klass, &def); + if (res != OK) { + return res; + } + def_vector.push_back(std::move(def)); + } + // Call all the transformation events. + jvmtiError res = Transformer::RetransformClassesDirect(env, + self, + &def_vector); + if (res != OK) { + // Something went wrong with transformation! + return res; + } + return RedefineClassesDirect(env, runtime, self, def_vector, error_msg); +} + +jvmtiError Redefiner::RedefineClassesDirect(ArtJvmTiEnv* env, + art::Runtime* runtime, + art::Thread* self, + const std::vector<ArtClassDefinition>& definitions, + std::string* error_msg) { + DCHECK(env != nullptr); + if (definitions.size() == 0) { + // We don't actually need to do anything. Just return OK. + return OK; + } // Stop JIT for the duration of this redefine since the JIT might concurrently compile a method we // are going to redefine. art::jit::ScopedJitSuspend suspend_jit; // Get shared mutator lock so we can lock all the classes. art::ScopedObjectAccess soa(self); - std::vector<Redefiner::ClassRedefinition> redefinitions; - redefinitions.reserve(class_count); Redefiner r(runtime, self, error_msg); - for (jint i = 0; i < class_count; i++) { - jvmtiError res = r.AddRedefinition(env, definitions[i]); - if (res != OK) { - return res; + for (const ArtClassDefinition& def : definitions) { + // Only try to transform classes that have been modified. + if (def.IsModified(self)) { + jvmtiError res = r.AddRedefinition(env, def); + if (res != OK) { + return res; + } } } return r.Run(); } -jvmtiError Redefiner::AddRedefinition(ArtJvmTiEnv* env, const jvmtiClassDefinition& def) { +jvmtiError Redefiner::AddRedefinition(ArtJvmTiEnv* env, const ArtClassDefinition& def) { std::string original_dex_location; jvmtiError ret = OK; if ((ret = GetClassLocation(env, def.klass, &original_dex_location))) { *error_msg_ = "Unable to get original dex file location!"; return ret; } - std::unique_ptr<art::MemMap> map(MoveDataToMemMap(original_dex_location, - def.class_byte_count, - def.class_bytes, - error_msg_)); - std::ostringstream os; char* generic_ptr_unused = nullptr; char* signature_ptr = nullptr; - if (env->GetClassSignature(def.klass, &signature_ptr, &generic_ptr_unused) != OK) { - *error_msg_ = "A jclass passed in does not seem to be valid"; - return ERR(INVALID_CLASS); + if ((ret = env->GetClassSignature(def.klass, &signature_ptr, &generic_ptr_unused)) != OK) { + *error_msg_ = "Unable to get class signature!"; + return ret; } - // These will make sure we deallocate the signature. - JvmtiUniquePtr sig_unique_ptr(MakeJvmtiUniquePtr(env, signature_ptr)); JvmtiUniquePtr generic_unique_ptr(MakeJvmtiUniquePtr(env, generic_ptr_unused)); + JvmtiUniquePtr signature_unique_ptr(MakeJvmtiUniquePtr(env, signature_ptr)); + std::unique_ptr<art::MemMap> map(MoveDataToMemMap(original_dex_location, + def.dex_len, + def.dex_data.get(), + error_msg_)); + std::ostringstream os; if (map.get() == nullptr) { - os << "Failed to create anonymous mmap for modified dex file of class " << signature_ptr + os << "Failed to create anonymous mmap for modified dex file of class " << def.name << "in dex file " << original_dex_location << " because: " << *error_msg_; *error_msg_ = os.str(); return ERR(OUT_OF_MEMORY); @@ -319,12 +373,16 @@ jvmtiError Redefiner::AddRedefinition(ArtJvmTiEnv* env, const jvmtiClassDefiniti /*verify_checksum*/true, error_msg_)); if (dex_file.get() == nullptr) { - os << "Unable to load modified dex file for " << signature_ptr << ": " << *error_msg_; + os << "Unable to load modified dex file for " << def.name << ": " << *error_msg_; *error_msg_ = os.str(); return ERR(INVALID_CLASS_FORMAT); } redefinitions_.push_back( - Redefiner::ClassRedefinition(this, def.klass, dex_file.release(), signature_ptr)); + Redefiner::ClassRedefinition(this, + def.klass, + dex_file.release(), + signature_ptr, + def.original_dex_file)); return OK; } @@ -462,44 +520,36 @@ void Redefiner::RecordFailure(jvmtiError result, result_ = result; } -bool Redefiner::ClassRedefinition::FinishRemainingAllocations( - /*out*/art::MutableHandle<art::mirror::ClassLoader>* source_class_loader, - /*out*/art::MutableHandle<art::mirror::Object>* java_dex_file_obj, - /*out*/art::MutableHandle<art::mirror::LongArray>* new_dex_file_cookie, - /*out*/art::MutableHandle<art::mirror::DexCache>* new_dex_cache) { - art::StackHandleScope<4> hs(driver_->self_); - // This shouldn't allocate - art::Handle<art::mirror::ClassLoader> loader(hs.NewHandle(GetClassLoader())); - if (loader.Get() == nullptr) { - // TODO Better error msg. - RecordFailure(ERR(INTERNAL), "Unable to find class loader!"); - return false; +art::mirror::ByteArray* Redefiner::ClassRedefinition::AllocateOrGetOriginalDexFileBytes() { + // If we have been specifically given a new set of bytes use that + if (original_dex_file_.size() != 0) { + return art::mirror::ByteArray::AllocateAndFill( + driver_->self_, + reinterpret_cast<const signed char*>(&original_dex_file_.At(0)), + original_dex_file_.size()); } - art::Handle<art::mirror::Object> dex_file_obj(hs.NewHandle(FindSourceDexFileObject(loader))); - if (dex_file_obj.Get() == nullptr) { - // TODO Better error msg. - RecordFailure(ERR(INTERNAL), "Unable to find class loader!"); - return false; - } - art::Handle<art::mirror::LongArray> new_cookie(hs.NewHandle(AllocateDexFileCookie(dex_file_obj))); - if (new_cookie.Get() == nullptr) { - driver_->self_->AssertPendingOOMException(); - driver_->self_->ClearException(); - RecordFailure(ERR(OUT_OF_MEMORY), "Unable to allocate dex file array for class loader"); - return false; + + // See if we already have one set. + art::ObjPtr<art::mirror::ClassExt> ext(GetMirrorClass()->GetExtData()); + if (!ext.IsNull()) { + art::ObjPtr<art::mirror::ByteArray> old_original_bytes(ext->GetOriginalDexFileBytes()); + if (!old_original_bytes.IsNull()) { + // We do. Use it. + return old_original_bytes.Ptr(); + } } - art::Handle<art::mirror::DexCache> dex_cache(hs.NewHandle(CreateNewDexCache(loader))); - if (dex_cache.Get() == nullptr) { - driver_->self_->AssertPendingOOMException(); - driver_->self_->ClearException(); - RecordFailure(ERR(OUT_OF_MEMORY), "Unable to allocate DexCache"); - return false; + + // Copy the current dex_file + const art::DexFile& current_dex_file = GetMirrorClass()->GetDexFile(); + // TODO Handle this or make it so it cannot happen. + if (current_dex_file.NumClassDefs() != 1) { + LOG(WARNING) << "Current dex file has more than one class in it. Calling RetransformClasses " + << "on this class might fail if no transformations are applied to it!"; } - source_class_loader->Assign(loader.Get()); - java_dex_file_obj->Assign(dex_file_obj.Get()); - new_dex_file_cookie->Assign(new_cookie.Get()); - new_dex_cache->Assign(dex_cache.Get()); - return true; + return art::mirror::ByteArray::AllocateAndFill( + driver_->self_, + reinterpret_cast<const signed char*>(current_dex_file.Begin()), + current_dex_file.Size()); } struct CallbackCtx { @@ -694,9 +744,10 @@ class RedefinitionDataHolder { kSlotNewDexFileCookie = 2, kSlotNewDexCache = 3, kSlotMirrorClass = 4, + kSlotOrigDexFile = 5, // Must be last one. - kNumSlots = 5, + kNumSlots = 6, }; // This needs to have a HandleScope passed in that is capable of creating a new Handle without @@ -737,6 +788,11 @@ class RedefinitionDataHolder { return art::down_cast<art::mirror::Class*>(GetSlot(klass_index, kSlotMirrorClass)); } + art::mirror::ByteArray* GetOriginalDexFileBytes(jint klass_index) + REQUIRES_SHARED(art::Locks::mutator_lock_) { + return art::down_cast<art::mirror::ByteArray*>(GetSlot(klass_index, kSlotOrigDexFile)); + } + void SetSourceClassLoader(jint klass_index, art::mirror::ClassLoader* loader) REQUIRES_SHARED(art::Locks::mutator_lock_) { SetSlot(klass_index, kSlotSourceClassLoader, loader); @@ -757,6 +813,10 @@ class RedefinitionDataHolder { REQUIRES_SHARED(art::Locks::mutator_lock_) { SetSlot(klass_index, kSlotMirrorClass, klass); } + void SetOriginalDexFileBytes(jint klass_index, art::mirror::ByteArray* bytes) + REQUIRES_SHARED(art::Locks::mutator_lock_) { + SetSlot(klass_index, kSlotOrigDexFile, bytes); + } int32_t Length() REQUIRES_SHARED(art::Locks::mutator_lock_) { return arr_->GetLength() / kNumSlots; @@ -782,6 +842,51 @@ class RedefinitionDataHolder { DISALLOW_COPY_AND_ASSIGN(RedefinitionDataHolder); }; +bool Redefiner::ClassRedefinition::FinishRemainingAllocations( + int32_t klass_index, /*out*/RedefinitionDataHolder* holder) { + art::StackHandleScope<2> hs(driver_->self_); + holder->SetMirrorClass(klass_index, GetMirrorClass()); + // This shouldn't allocate + art::Handle<art::mirror::ClassLoader> loader(hs.NewHandle(GetClassLoader())); + holder->SetSourceClassLoader(klass_index, loader.Get()); + if (loader.Get() == nullptr) { + // TODO Better error msg. + RecordFailure(ERR(INTERNAL), "Unable to find class loader!"); + return false; + } + art::Handle<art::mirror::Object> dex_file_obj(hs.NewHandle(FindSourceDexFileObject(loader))); + holder->SetJavaDexFile(klass_index, dex_file_obj.Get()); + if (dex_file_obj.Get() == nullptr) { + // TODO Better error msg. + RecordFailure(ERR(INTERNAL), "Unable to find class loader!"); + return false; + } + holder->SetNewDexFileCookie(klass_index, AllocateDexFileCookie(dex_file_obj)); + if (holder->GetNewDexFileCookie(klass_index) == nullptr) { + driver_->self_->AssertPendingOOMException(); + driver_->self_->ClearException(); + RecordFailure(ERR(OUT_OF_MEMORY), "Unable to allocate dex file array for class loader"); + return false; + } + holder->SetNewDexCache(klass_index, CreateNewDexCache(loader)); + if (holder->GetNewDexCache(klass_index) == nullptr) { + driver_->self_->AssertPendingOOMException(); + driver_->self_->ClearException(); + RecordFailure(ERR(OUT_OF_MEMORY), "Unable to allocate DexCache"); + return false; + } + + // We won't always need to set this field. + holder->SetOriginalDexFileBytes(klass_index, AllocateOrGetOriginalDexFileBytes()); + if (holder->GetOriginalDexFileBytes(klass_index) == nullptr) { + driver_->self_->AssertPendingOOMException(); + driver_->self_->ClearException(); + RecordFailure(ERR(OUT_OF_MEMORY), "Unable to allocate array for original dex file"); + return false; + } + return true; +} + bool Redefiner::CheckAllRedefinitionAreValid() { for (Redefiner::ClassRedefinition& redef : redefinitions_) { if (!redef.CheckRedefinitionIsValid()) { @@ -802,33 +907,11 @@ bool Redefiner::EnsureAllClassAllocationsFinished() { bool Redefiner::FinishAllRemainingAllocations(RedefinitionDataHolder& holder) { int32_t cnt = 0; - art::StackHandleScope<4> hs(self_); - art::MutableHandle<art::mirror::Object> java_dex_file(hs.NewHandle<art::mirror::Object>(nullptr)); - art::MutableHandle<art::mirror::ClassLoader> source_class_loader( - hs.NewHandle<art::mirror::ClassLoader>(nullptr)); - art::MutableHandle<art::mirror::LongArray> new_dex_file_cookie( - hs.NewHandle<art::mirror::LongArray>(nullptr)); - art::MutableHandle<art::mirror::DexCache> new_dex_cache( - hs.NewHandle<art::mirror::DexCache>(nullptr)); for (Redefiner::ClassRedefinition& redef : redefinitions_) { - // Reset the out pointers to null - source_class_loader.Assign(nullptr); - java_dex_file.Assign(nullptr); - new_dex_file_cookie.Assign(nullptr); - new_dex_cache.Assign(nullptr); // Allocate the data this redefinition requires. - if (!redef.FinishRemainingAllocations(&source_class_loader, - &java_dex_file, - &new_dex_file_cookie, - &new_dex_cache)) { + if (!redef.FinishRemainingAllocations(cnt, &holder)) { return false; } - // Save the allocated data into the holder. - holder.SetSourceClassLoader(cnt, source_class_loader.Get()); - holder.SetJavaDexFile(cnt, java_dex_file.Get()); - holder.SetNewDexFileCookie(cnt, new_dex_file_cookie.Get()); - holder.SetNewDexCache(cnt, new_dex_cache.Get()); - holder.SetMirrorClass(cnt, redef.GetMirrorClass()); cnt++; } return true; @@ -894,7 +977,7 @@ jvmtiError Redefiner::Run() { redef.UpdateJavaDexFile(holder.GetJavaDexFile(cnt), holder.GetNewDexFileCookie(cnt)); // TODO Rewrite so we don't do a stack walk for each and every class. redef.FindAndAllocateObsoleteMethods(klass); - redef.UpdateClass(klass, holder.GetNewDexCache(cnt)); + redef.UpdateClass(klass, holder.GetNewDexCache(cnt), holder.GetOriginalDexFileBytes(cnt)); cnt++; } // Ensure that obsolete methods are deoptimized. This is needed since optimized methods may have @@ -987,20 +1070,24 @@ void Redefiner::ClassRedefinition::UpdateFields(art::ObjPtr<art::mirror::Class> } // Performs updates to class that will allow us to verify it. -void Redefiner::ClassRedefinition::UpdateClass(art::ObjPtr<art::mirror::Class> mclass, - art::ObjPtr<art::mirror::DexCache> new_dex_cache) { - const art::DexFile::ClassDef* class_def = art::OatFile::OatDexFile::FindClassDef( - *dex_file_, class_sig_.c_str(), art::ComputeModifiedUtf8Hash(class_sig_.c_str())); - DCHECK(class_def != nullptr); - UpdateMethods(mclass, new_dex_cache, *class_def); +void Redefiner::ClassRedefinition::UpdateClass( + art::ObjPtr<art::mirror::Class> mclass, + art::ObjPtr<art::mirror::DexCache> new_dex_cache, + art::ObjPtr<art::mirror::ByteArray> original_dex_file) { + DCHECK_EQ(dex_file_->NumClassDefs(), 1u); + const art::DexFile::ClassDef& class_def = dex_file_->GetClassDef(0); + UpdateMethods(mclass, new_dex_cache, class_def); UpdateFields(mclass); // Update the class fields. // Need to update class last since the ArtMethod gets its DexFile from the class (which is needed // to call GetReturnTypeDescriptor and GetParameterTypeList above). mclass->SetDexCache(new_dex_cache.Ptr()); - mclass->SetDexClassDefIndex(dex_file_->GetIndexForClassDef(*class_def)); + mclass->SetDexClassDefIndex(dex_file_->GetIndexForClassDef(class_def)); mclass->SetDexTypeIndex(dex_file_->GetIndexForTypeId(*dex_file_->FindTypeId(class_sig_.c_str()))); + art::ObjPtr<art::mirror::ClassExt> ext(mclass->GetExtData()); + CHECK(!ext.IsNull()); + ext->SetOriginalDexFileBytes(original_dex_file); } void Redefiner::ClassRedefinition::UpdateJavaDexFile( diff --git a/runtime/openjdkjvmti/ti_redefine.h b/runtime/openjdkjvmti/ti_redefine.h index 8626bc54d5..fdc13eee32 100644 --- a/runtime/openjdkjvmti/ti_redefine.h +++ b/runtime/openjdkjvmti/ti_redefine.h @@ -38,6 +38,7 @@ #include "art_jvmti.h" #include "art_method.h" +#include "base/array_slice.h" #include "class_linker.h" #include "dex_file.h" #include "gc_root-inl.h" @@ -56,6 +57,7 @@ #include "obj_ptr.h" #include "scoped_thread_state_change-inl.h" #include "stack.h" +#include "ti_class_definition.h" #include "thread_list.h" #include "transform.h" #include "utf.h" @@ -72,23 +74,41 @@ class Redefiner { public: // Redefine the given classes with the given dex data. Note this function does not take ownership // of the dex_data pointers. It is not used after this call however and may be freed if desired. + // The caller is responsible for freeing it. The runtime makes its own copy of the data. This + // function does not call the transformation events. + // TODO Check modified flag of the definitions. + static jvmtiError RedefineClassesDirect(ArtJvmTiEnv* env, + art::Runtime* runtime, + art::Thread* self, + const std::vector<ArtClassDefinition>& definitions, + /*out*/std::string* error_msg); + + // Redefine the given classes with the given dex data. Note this function does not take ownership + // of the dex_data pointers. It is not used after this call however and may be freed if desired. // The caller is responsible for freeing it. The runtime makes its own copy of the data. + // TODO This function should call the transformation events. static jvmtiError RedefineClasses(ArtJvmTiEnv* env, art::Runtime* runtime, art::Thread* self, jint class_count, const jvmtiClassDefinition* definitions, - std::string* error_msg); + /*out*/std::string* error_msg); static jvmtiError IsModifiableClass(jvmtiEnv* env, jclass klass, jboolean* is_redefinable); + static std::unique_ptr<art::MemMap> MoveDataToMemMap(const std::string& original_location, + jint data_len, + const unsigned char* dex_data, + std::string* error_msg); + private: class ClassRedefinition { public: ClassRedefinition(Redefiner* driver, jclass klass, const art::DexFile* redefined_dex_file, - const char* class_sig) + const char* class_sig, + art::ArraySlice<const unsigned char> orig_dex_file) REQUIRES_SHARED(art::Locks::mutator_lock_); // NO_THREAD_SAFETY_ANALYSIS so we can unlock the class in the destructor. @@ -99,7 +119,8 @@ class Redefiner { : driver_(other.driver_), klass_(other.klass_), dex_file_(std::move(other.dex_file_)), - class_sig_(std::move(other.class_sig_)) { + class_sig_(std::move(other.class_sig_)), + original_dex_file_(other.original_dex_file_) { other.driver_ = nullptr; } @@ -118,15 +139,15 @@ class Redefiner { art::mirror::LongArray* AllocateDexFileCookie(art::Handle<art::mirror::Object> j_dex_file_obj) REQUIRES_SHARED(art::Locks::mutator_lock_); + // This may return nullptr with a OOME pending if allocation fails. + art::mirror::ByteArray* AllocateOrGetOriginalDexFileBytes() + REQUIRES_SHARED(art::Locks::mutator_lock_); + void RecordFailure(jvmtiError e, const std::string& err) { driver_->RecordFailure(e, class_sig_, err); } - bool FinishRemainingAllocations( - /*out*/art::MutableHandle<art::mirror::ClassLoader>* source_class_loader, - /*out*/art::MutableHandle<art::mirror::Object>* source_dex_file_obj, - /*out*/art::MutableHandle<art::mirror::LongArray>* new_dex_file_cookie, - /*out*/art::MutableHandle<art::mirror::DexCache>* new_dex_cache) + bool FinishRemainingAllocations(int32_t klass_index, /*out*/RedefinitionDataHolder* holder) REQUIRES_SHARED(art::Locks::mutator_lock_); void FindAndAllocateObsoleteMethods(art::mirror::Class* art_klass) @@ -179,7 +200,8 @@ class Redefiner { REQUIRES(art::Locks::mutator_lock_); void UpdateClass(art::ObjPtr<art::mirror::Class> mclass, - art::ObjPtr<art::mirror::DexCache> new_dex_cache) + art::ObjPtr<art::mirror::DexCache> new_dex_cache, + art::ObjPtr<art::mirror::ByteArray> original_dex_file) REQUIRES(art::Locks::mutator_lock_); void ReleaseDexFile() REQUIRES_SHARED(art::Locks::mutator_lock_); @@ -189,6 +211,7 @@ class Redefiner { jclass klass_; std::unique_ptr<const art::DexFile> dex_file_; std::string class_sig_; + art::ArraySlice<const unsigned char> original_dex_file_; }; jvmtiError result_; @@ -209,18 +232,13 @@ class Redefiner { redefinitions_(), error_msg_(error_msg) { } - jvmtiError AddRedefinition(ArtJvmTiEnv* env, const jvmtiClassDefinition& def) + jvmtiError AddRedefinition(ArtJvmTiEnv* env, const ArtClassDefinition& def) REQUIRES_SHARED(art::Locks::mutator_lock_); static jvmtiError GetClassRedefinitionError(art::Handle<art::mirror::Class> klass, /*out*/std::string* error_msg) REQUIRES_SHARED(art::Locks::mutator_lock_); - static std::unique_ptr<art::MemMap> MoveDataToMemMap(const std::string& original_location, - jint data_len, - const unsigned char* dex_data, - std::string* error_msg); - // TODO Put on all the lock qualifiers. jvmtiError Run() REQUIRES_SHARED(art::Locks::mutator_lock_); diff --git a/runtime/openjdkjvmti/ti_thread.cc b/runtime/openjdkjvmti/ti_thread.cc index 2bcdd8cda1..b18a5cd746 100644 --- a/runtime/openjdkjvmti/ti_thread.cc +++ b/runtime/openjdkjvmti/ti_thread.cc @@ -31,10 +31,12 @@ #include "ti_thread.h" +#include "android-base/strings.h" #include "art_field.h" #include "art_jvmti.h" #include "base/logging.h" #include "base/mutex.h" +#include "events-inl.h" #include "gc/system_weak.h" #include "gc_root-inl.h" #include "jni_internal.h" @@ -43,6 +45,8 @@ #include "mirror/string.h" #include "obj_ptr.h" #include "runtime.h" +#include "runtime_callbacks.h" +#include "ScopedLocalRef.h" #include "scoped_thread_state_change-inl.h" #include "thread-inl.h" #include "thread_list.h" @@ -50,6 +54,80 @@ namespace openjdkjvmti { +struct ThreadCallback : public art::ThreadLifecycleCallback, public art::RuntimePhaseCallback { + jthread GetThreadObject(art::Thread* self) REQUIRES_SHARED(art::Locks::mutator_lock_) { + if (self->GetPeer() == nullptr) { + return nullptr; + } + return self->GetJniEnv()->AddLocalReference<jthread>(self->GetPeer()); + } + template <ArtJvmtiEvent kEvent> + void Post(art::Thread* self) REQUIRES_SHARED(art::Locks::mutator_lock_) { + DCHECK_EQ(self, art::Thread::Current()); + ScopedLocalRef<jthread> thread(self->GetJniEnv(), GetThreadObject(self)); + art::ScopedThreadSuspension sts(self, art::ThreadState::kNative); + event_handler->DispatchEvent<kEvent>(self, + reinterpret_cast<JNIEnv*>(self->GetJniEnv()), + thread.get()); + } + + void ThreadStart(art::Thread* self) OVERRIDE REQUIRES_SHARED(art::Locks::mutator_lock_) { + if (!started) { + // Runtime isn't started. We only expect at most the signal handler or JIT threads to be + // started here. + if (art::kIsDebugBuild) { + std::string name; + self->GetThreadName(name); + if (name != "Signal Catcher" && !android::base::StartsWith(name, "Jit thread pool")) { + LOG(FATAL) << "Unexpected thread before start: " << name; + } + } + return; + } + Post<ArtJvmtiEvent::kThreadStart>(self); + } + + void ThreadDeath(art::Thread* self) OVERRIDE REQUIRES_SHARED(art::Locks::mutator_lock_) { + Post<ArtJvmtiEvent::kThreadEnd>(self); + } + + void NextRuntimePhase(RuntimePhase phase) OVERRIDE REQUIRES_SHARED(art::Locks::mutator_lock_) { + if (phase == RuntimePhase::kInit) { + // We moved to VMInit. Report the main thread as started (it was attached early, and must + // not be reported until Init. + started = true; + Post<ArtJvmtiEvent::kThreadStart>(art::Thread::Current()); + } + } + + EventHandler* event_handler = nullptr; + bool started = false; +}; + +ThreadCallback gThreadCallback; + +void ThreadUtil::Register(EventHandler* handler) { + art::Runtime* runtime = art::Runtime::Current(); + + gThreadCallback.started = runtime->IsStarted(); + gThreadCallback.event_handler = handler; + + art::ScopedThreadStateChange stsc(art::Thread::Current(), + art::ThreadState::kWaitingForDebuggerToAttach); + art::ScopedSuspendAll ssa("Add thread callback"); + runtime->GetRuntimeCallbacks()->AddThreadLifecycleCallback(&gThreadCallback); + runtime->GetRuntimeCallbacks()->AddRuntimePhaseCallback(&gThreadCallback); +} + +void ThreadUtil::Unregister() { + art::ScopedThreadStateChange stsc(art::Thread::Current(), + art::ThreadState::kWaitingForDebuggerToAttach); + art::ScopedSuspendAll ssa("Remove thread callback"); + art::Runtime* runtime = art::Runtime::Current(); + runtime->GetRuntimeCallbacks()->RemoveThreadLifecycleCallback(&gThreadCallback); + runtime->GetRuntimeCallbacks()->RemoveRuntimePhaseCallback(&gThreadCallback); +} + jvmtiError ThreadUtil::GetCurrentThread(jvmtiEnv* env ATTRIBUTE_UNUSED, jthread* thread_ptr) { art::Thread* self = art::Thread::Current(); @@ -443,4 +521,80 @@ jvmtiError ThreadUtil::GetThreadLocalStorage(jvmtiEnv* env ATTRIBUTE_UNUSED, return ERR(NONE); } +struct AgentData { + const void* arg; + jvmtiStartFunction proc; + jthread thread; + JavaVM* java_vm; + jvmtiEnv* jvmti_env; + jint priority; +}; + +static void* AgentCallback(void* arg) { + std::unique_ptr<AgentData> data(reinterpret_cast<AgentData*>(arg)); + CHECK(data->thread != nullptr); + + // We already have a peer. So call our special Attach function. + art::Thread* self = art::Thread::Attach("JVMTI Agent thread", true, data->thread); + CHECK(self != nullptr); + // The name in Attach() is only for logging. Set the thread name. This is important so + // that the thread is no longer seen as starting up. + { + art::ScopedObjectAccess soa(self); + self->SetThreadName("JVMTI Agent thread"); + } + + // Release the peer. + JNIEnv* env = self->GetJniEnv(); + env->DeleteGlobalRef(data->thread); + data->thread = nullptr; + + // Run the agent code. + data->proc(data->jvmti_env, env, const_cast<void*>(data->arg)); + + // Detach the thread. + int detach_result = data->java_vm->DetachCurrentThread(); + CHECK_EQ(detach_result, 0); + + return nullptr; +} + +jvmtiError ThreadUtil::RunAgentThread(jvmtiEnv* jvmti_env, + jthread thread, + jvmtiStartFunction proc, + const void* arg, + jint priority) { + if (priority < JVMTI_THREAD_MIN_PRIORITY || priority > JVMTI_THREAD_MAX_PRIORITY) { + return ERR(INVALID_PRIORITY); + } + JNIEnv* env = art::Thread::Current()->GetJniEnv(); + if (thread == nullptr || !env->IsInstanceOf(thread, art::WellKnownClasses::java_lang_Thread)) { + return ERR(INVALID_THREAD); + } + if (proc == nullptr) { + return ERR(NULL_POINTER); + } + + std::unique_ptr<AgentData> data(new AgentData); + data->arg = arg; + data->proc = proc; + // We need a global ref for Java objects, as local refs will be invalid. + data->thread = env->NewGlobalRef(thread); + data->java_vm = art::Runtime::Current()->GetJavaVM(); + data->jvmti_env = jvmti_env; + data->priority = priority; + + pthread_t pthread; + int pthread_create_result = pthread_create(&pthread, + nullptr, + &AgentCallback, + reinterpret_cast<void*>(data.get())); + if (pthread_create_result != 0) { + return ERR(INTERNAL); + } + data.release(); + + return ERR(NONE); +} + } // namespace openjdkjvmti diff --git a/runtime/openjdkjvmti/ti_thread.h b/runtime/openjdkjvmti/ti_thread.h index 290e9d49b2..f6f93ee91a 100644 --- a/runtime/openjdkjvmti/ti_thread.h +++ b/runtime/openjdkjvmti/ti_thread.h @@ -37,8 +37,13 @@ namespace openjdkjvmti { +class EventHandler; + class ThreadUtil { public: + static void Register(EventHandler* event_handler); + static void Unregister(); + static jvmtiError GetAllThreads(jvmtiEnv* env, jint* threads_count_ptr, jthread** threads_ptr); static jvmtiError GetCurrentThread(jvmtiEnv* env, jthread* thread_ptr); @@ -49,6 +54,12 @@ class ThreadUtil { static jvmtiError SetThreadLocalStorage(jvmtiEnv* env, jthread thread, const void* data); static jvmtiError GetThreadLocalStorage(jvmtiEnv* env, jthread thread, void** data_ptr); + + static jvmtiError RunAgentThread(jvmtiEnv* env, + jthread thread, + jvmtiStartFunction proc, + const void* arg, + jint priority); }; } // namespace openjdkjvmti diff --git a/runtime/openjdkjvmti/transform.cc b/runtime/openjdkjvmti/transform.cc index f5451254c0..3c4cfea19a 100644 --- a/runtime/openjdkjvmti/transform.cc +++ b/runtime/openjdkjvmti/transform.cc @@ -38,6 +38,7 @@ #include "class_linker.h" #include "dex_file.h" #include "dex_file_types.h" +#include "events-inl.h" #include "gc_root-inl.h" #include "globals.h" #include "jni_env_ext-inl.h" @@ -46,18 +47,81 @@ #include "mem_map.h" #include "mirror/array.h" #include "mirror/class-inl.h" +#include "mirror/class_ext.h" #include "mirror/class_loader-inl.h" #include "mirror/string-inl.h" #include "oat_file.h" #include "scoped_thread_state_change-inl.h" #include "stack.h" #include "thread_list.h" +#include "ti_redefine.h" #include "transform.h" #include "utf.h" #include "utils/dex_cache_arrays_layout-inl.h" namespace openjdkjvmti { +jvmtiError Transformer::RetransformClassesDirect( + ArtJvmTiEnv* env, + art::Thread* self, + /*in-out*/std::vector<ArtClassDefinition>* definitions) { + for (ArtClassDefinition& def : *definitions) { + jint new_len = -1; + unsigned char* new_data = nullptr; + gEventHandler.DispatchEvent<ArtJvmtiEvent::kClassFileLoadHookRetransformable>( + self, + GetJniEnv(env), + def.klass, + def.loader, + def.name.c_str(), + def.protection_domain, + def.dex_len, + static_cast<const unsigned char*>(def.dex_data.get()), + &new_len, + &new_data); + def.SetNewDexData(env, new_len, new_data); + } + return OK; +} + +jvmtiError Transformer::RetransformClasses(ArtJvmTiEnv* env, + art::Runtime* runtime, + art::Thread* self, + jint class_count, + const jclass* classes, + /*out*/std::string* error_msg) { + if (env == nullptr) { + *error_msg = "env was null!"; + return ERR(INVALID_ENVIRONMENT); + } else if (class_count < 0) { + *error_msg = "class_count was less then 0"; + return ERR(ILLEGAL_ARGUMENT); + } else if (class_count == 0) { + // We don't actually need to do anything. Just return OK. + return OK; + } else if (classes == nullptr) { + *error_msg = "null classes!"; + return ERR(NULL_POINTER); + } + // A holder that will Deallocate all the class bytes buffers on destruction. + std::vector<ArtClassDefinition> definitions; + jvmtiError res = OK; + for (jint i = 0; i < class_count; i++) { + ArtClassDefinition def; + res = FillInTransformationData(env, classes[i], &def); + if (res != OK) { + return res; + } + definitions.push_back(std::move(def)); + } + res = RetransformClassesDirect(env, self, &definitions); + if (res != OK) { + return res; + } + return Redefiner::RedefineClassesDirect(env, runtime, self, definitions, error_msg); +} + +// TODO Move this somewhere else, ti_class? jvmtiError GetClassLocation(ArtJvmTiEnv* env, jclass klass, /*out*/std::string* location) { JNIEnv* jni_env = nullptr; jint ret = env->art_vm->GetEnv(reinterpret_cast<void**>(&jni_env), JNI_VERSION_1_1); @@ -73,42 +137,60 @@ jvmtiError GetClassLocation(ArtJvmTiEnv* env, jclass klass, /*out*/std::string* return OK; } +jvmtiError Transformer::GetDexDataForRetransformation(ArtJvmTiEnv* env, + art::Handle<art::mirror::Class> klass, + /*out*/jint* dex_data_len, + /*out*/unsigned char** dex_data) { + art::StackHandleScope<2> hs(art::Thread::Current()); + art::Handle<art::mirror::ClassExt> ext(hs.NewHandle(klass->GetExtData())); + if (!ext.IsNull()) { + art::Handle<art::mirror::ByteArray> orig_dex(hs.NewHandle(ext->GetOriginalDexFileBytes())); + if (!orig_dex.IsNull()) { + *dex_data_len = static_cast<jint>(orig_dex->GetLength()); + return CopyDataIntoJvmtiBuffer(env, + reinterpret_cast<const unsigned char*>(orig_dex->GetData()), + *dex_data_len, + /*out*/dex_data); + } + } + // TODO De-quicken the dex file before passing it to the agents. + LOG(WARNING) << "Dex file is not de-quickened yet! Quickened dex instructions might be present"; + const art::DexFile& dex = klass->GetDexFile(); + *dex_data_len = static_cast<jint>(dex.Size()); + return CopyDataIntoJvmtiBuffer(env, dex.Begin(), *dex_data_len, /*out*/dex_data); +} + // TODO Move this function somewhere more appropriate. // Gets the data surrounding the given class. -jvmtiError GetTransformationData(ArtJvmTiEnv* env, - jclass klass, - /*out*/std::string* location, - /*out*/JNIEnv** jni_env_ptr, - /*out*/jobject* loader, - /*out*/std::string* name, - /*out*/jobject* protection_domain, - /*out*/jint* data_len, - /*out*/unsigned char** dex_data) { - jint ret = env->art_vm->GetEnv(reinterpret_cast<void**>(jni_env_ptr), JNI_VERSION_1_1); - if (ret != JNI_OK) { +// TODO Make this less magical. +jvmtiError Transformer::FillInTransformationData(ArtJvmTiEnv* env, + jclass klass, + ArtClassDefinition* def) { + JNIEnv* jni_env = GetJniEnv(env); + if (jni_env == nullptr) { // TODO Different error might be better? return ERR(INTERNAL); } - JNIEnv* jni_env = *jni_env_ptr; art::ScopedObjectAccess soa(jni_env); art::StackHandleScope<3> hs(art::Thread::Current()); art::Handle<art::mirror::Class> hs_klass(hs.NewHandle(soa.Decode<art::mirror::Class>(klass))); - *loader = soa.AddLocalReference<jobject>(hs_klass->GetClassLoader()); - *name = art::mirror::Class::ComputeName(hs_klass)->ToModifiedUtf8(); + if (hs_klass.IsNull()) { + return ERR(INVALID_CLASS); + } + def->klass = klass; + def->loader = soa.AddLocalReference<jobject>(hs_klass->GetClassLoader()); + def->name = art::mirror::Class::ComputeName(hs_klass)->ToModifiedUtf8(); // TODO is this always null? - *protection_domain = nullptr; - const art::DexFile& dex = hs_klass->GetDexFile(); - *location = dex.GetLocation(); - *data_len = static_cast<jint>(dex.Size()); - // TODO We should maybe change env->Allocate to allow us to mprotect this memory and stop writes. - jvmtiError alloc_error = env->Allocate(*data_len, dex_data); - if (alloc_error != OK) { - return alloc_error; + def->protection_domain = nullptr; + if (def->dex_data.get() == nullptr) { + unsigned char* new_data; + jvmtiError res = GetDexDataForRetransformation(env, hs_klass, &def->dex_len, &new_data); + if (res == OK) { + def->dex_data = MakeJvmtiUniquePtr(env, new_data); + } else { + return res; + } } - // Copy the data into a temporary buffer. - memcpy(reinterpret_cast<void*>(*dex_data), - reinterpret_cast<const void*>(dex.Begin()), - *data_len); return OK; } diff --git a/runtime/openjdkjvmti/transform.h b/runtime/openjdkjvmti/transform.h index 0ad5099daf..65f2ae1353 100644 --- a/runtime/openjdkjvmti/transform.h +++ b/runtime/openjdkjvmti/transform.h @@ -37,22 +37,37 @@ #include <jni.h> #include "art_jvmti.h" +#include "ti_class_definition.h" #include "jvmti.h" namespace openjdkjvmti { jvmtiError GetClassLocation(ArtJvmTiEnv* env, jclass klass, /*out*/std::string* location); -// Gets the data surrounding the given class. -jvmtiError GetTransformationData(ArtJvmTiEnv* env, - jclass klass, - /*out*/std::string* location, - /*out*/JNIEnv** jni_env_ptr, - /*out*/jobject* loader, - /*out*/std::string* name, - /*out*/jobject* protection_domain, - /*out*/jint* data_len, - /*out*/unsigned char** dex_data); +class Transformer { + public: + static jvmtiError RetransformClassesDirect( + ArtJvmTiEnv* env, art::Thread* self, /*in-out*/std::vector<ArtClassDefinition>* definitions); + + static jvmtiError RetransformClasses(ArtJvmTiEnv* env, + art::Runtime* runtime, + art::Thread* self, + jint class_count, + const jclass* classes, + /*out*/std::string* error_msg); + + // Gets the data surrounding the given class. + static jvmtiError FillInTransformationData(ArtJvmTiEnv* env, + jclass klass, + ArtClassDefinition* def); + + private: + static jvmtiError GetDexDataForRetransformation(ArtJvmTiEnv* env, + art::Handle<art::mirror::Class> klass, + /*out*/jint* dex_data_length, + /*out*/unsigned char** dex_data) + REQUIRES_SHARED(art::Locks::mutator_lock_); +}; } // namespace openjdkjvmti diff --git a/runtime/parsed_options.cc b/runtime/parsed_options.cc index a72159bc80..ccc5f7a8ee 100644 --- a/runtime/parsed_options.cc +++ b/runtime/parsed_options.cc @@ -302,6 +302,9 @@ std::unique_ptr<RuntimeParser> ParsedOptions::MakeParser(bool ignore_unrecognize .IntoKey(M::Plugins) .Define("-Xfully-deoptable") .IntoKey(M::FullyDeoptable) + .Define("-XX:ThreadSuspendTimeout=_") // in ms + .WithType<MillisecondsToNanoseconds>() // store as ns + .IntoKey(M::ThreadSuspendTimeout) .Ignore({ "-ea", "-da", "-enableassertions", "-disableassertions", "--runtime-arg", "-esa", "-dsa", "-enablesystemassertions", "-disablesystemassertions", "-Xrs", "-Xint:_", @@ -596,42 +599,6 @@ bool ParsedOptions::DoParse(const RuntimeOptions& options, args.Set(M::HeapGrowthLimit, args.GetOrDefault(M::MemoryMaximumSize)); } - if (args.GetOrDefault(M::Experimental) & ExperimentalFlags::kRuntimePlugins) { - LOG(WARNING) << "Experimental runtime plugin support has been enabled. No guarantees are made " - << "about stability or usage of this plugin support. Use at your own risk. Do " - << "not attempt to write shipping code that relies on the implementation of " - << "runtime plugins."; - } else if (!args.GetOrDefault(M::Plugins).empty()) { - LOG(WARNING) << "Experimental runtime plugin support has not been enabled. Ignored options: "; - for (const auto& op : args.GetOrDefault(M::Plugins)) { - LOG(WARNING) << " -plugin:" << op.GetLibrary(); - } - } - - if (args.GetOrDefault(M::Experimental) & ExperimentalFlags::kAgents) { - LOG(WARNING) << "Experimental runtime agent support has been enabled. No guarantees are made " - << "the completeness, accuracy, reliability, or stability of the agent " - << "implementation. Use at your own risk. Do not attempt to write shipping code " - << "that relies on the implementation of any part of this api."; - } else if (!args.GetOrDefault(M::AgentLib).empty() || !args.GetOrDefault(M::AgentPath).empty()) { - LOG(WARNING) << "agent support has not been enabled. Enable experimental agent " - << " support with '-XExperimental:agent'. Ignored options are:"; - for (const auto& op : args.GetOrDefault(M::AgentLib)) { - if (op.HasArgs()) { - LOG(WARNING) << " -agentlib:" << op.GetName() << "=" << op.GetArgs(); - } else { - LOG(WARNING) << " -agentlib:" << op.GetName(); - } - } - for (const auto& op : args.GetOrDefault(M::AgentPath)) { - if (op.HasArgs()) { - LOG(WARNING) << " -agentpath:" << op.GetName() << "=" << op.GetArgs(); - } else { - LOG(WARNING) << " -agentpath:" << op.GetName(); - } - } - } - *runtime_options = std::move(args); return true; } @@ -724,6 +691,7 @@ void ParsedOptions::Usage(const char* fmt, ...) { UsageMessage(stream, " -XX:MaxSpinsBeforeThinLockInflation=integervalue\n"); UsageMessage(stream, " -XX:LongPauseLogThreshold=integervalue\n"); UsageMessage(stream, " -XX:LongGCLogThreshold=integervalue\n"); + UsageMessage(stream, " -XX:ThreadSuspendTimeout=integervalue\n"); UsageMessage(stream, " -XX:DumpGCPerformanceOnShutdown\n"); UsageMessage(stream, " -XX:DumpJITInfoOnShutdown\n"); UsageMessage(stream, " -XX:IgnoreMaxFootprint\n"); diff --git a/runtime/reflection.cc b/runtime/reflection.cc index 75176f938e..a2b4cb37a9 100644 --- a/runtime/reflection.cc +++ b/runtime/reflection.cc @@ -216,43 +216,54 @@ class ArgArray { } bool BuildArgArrayFromObjectArray(ObjPtr<mirror::Object> receiver, - ObjPtr<mirror::ObjectArray<mirror::Object>> args, - ArtMethod* m) + ObjPtr<mirror::ObjectArray<mirror::Object>> raw_args, + ArtMethod* m, + Thread* self) REQUIRES_SHARED(Locks::mutator_lock_) { const DexFile::TypeList* classes = m->GetParameterTypeList(); // Set receiver if non-null (method is not static) if (receiver != nullptr) { Append(receiver); } + StackHandleScope<2> hs(self); + MutableHandle<mirror::Object> arg(hs.NewHandle<mirror::Object>(nullptr)); + Handle<mirror::ObjectArray<mirror::Object>> args( + hs.NewHandle<mirror::ObjectArray<mirror::Object>>(raw_args)); for (size_t i = 1, args_offset = 0; i < shorty_len_; ++i, ++args_offset) { - ObjPtr<mirror::Object> arg(args->Get(args_offset)); - if (((shorty_[i] == 'L') && (arg != nullptr)) || ((arg == nullptr && shorty_[i] != 'L'))) { - // Note: The method's parameter's type must have been previously resolved. + arg.Assign(args->Get(args_offset)); + if (((shorty_[i] == 'L') && (arg.Get() != nullptr)) || + ((arg.Get() == nullptr && shorty_[i] != 'L'))) { + // TODO: The method's parameter's type must have been previously resolved, yet + // we've seen cases where it's not b/34440020. ObjPtr<mirror::Class> dst_class( m->GetClassFromTypeIndex(classes->GetTypeItem(args_offset).type_idx_, - false /* resolve */)); - DCHECK(dst_class != nullptr) << m->PrettyMethod() << " arg #" << i; - if (UNLIKELY(arg == nullptr || !arg->InstanceOf(dst_class))) { + true /* resolve */)); + if (dst_class.Ptr() == nullptr) { + CHECK(self->IsExceptionPending()); + return false; + } + if (UNLIKELY(arg.Get() == nullptr || !arg->InstanceOf(dst_class))) { ThrowIllegalArgumentException( StringPrintf("method %s argument %zd has type %s, got %s", m->PrettyMethod(false).c_str(), args_offset + 1, // Humans don't count from 0. mirror::Class::PrettyDescriptor(dst_class).c_str(), - mirror::Object::PrettyTypeOf(arg).c_str()).c_str()); + mirror::Object::PrettyTypeOf(arg.Get()).c_str()).c_str()); return false; } } #define DO_FIRST_ARG(match_descriptor, get_fn, append) { \ - if (LIKELY(arg != nullptr && arg->GetClass()->DescriptorEquals(match_descriptor))) { \ + if (LIKELY(arg.Get() != nullptr && \ + arg->GetClass()->DescriptorEquals(match_descriptor))) { \ ArtField* primitive_field = arg->GetClass()->GetInstanceField(0); \ - append(primitive_field-> get_fn(arg)); + append(primitive_field-> get_fn(arg.Get())); #define DO_ARG(match_descriptor, get_fn, append) \ - } else if (LIKELY(arg != nullptr && \ + } else if (LIKELY(arg.Get() != nullptr && \ arg->GetClass<>()->DescriptorEquals(match_descriptor))) { \ ArtField* primitive_field = arg->GetClass()->GetInstanceField(0); \ - append(primitive_field-> get_fn(arg)); + append(primitive_field-> get_fn(arg.Get())); #define DO_FAIL(expected) \ } else { \ @@ -266,14 +277,14 @@ class ArgArray { ArtMethod::PrettyMethod(m, false).c_str(), \ args_offset + 1, \ expected, \ - mirror::Object::PrettyTypeOf(arg).c_str()).c_str()); \ + mirror::Object::PrettyTypeOf(arg.Get()).c_str()).c_str()); \ } \ return false; \ } } switch (shorty_[i]) { case 'L': - Append(arg); + Append(arg.Get()); break; case 'Z': DO_FIRST_ARG("Ljava/lang/Boolean;", GetBoolean, Append) @@ -646,7 +657,7 @@ jobject InvokeMethod(const ScopedObjectAccessAlreadyRunnable& soa, jobject javaM uint32_t shorty_len = 0; const char* shorty = np_method->GetShorty(&shorty_len); ArgArray arg_array(shorty, shorty_len); - if (!arg_array.BuildArgArrayFromObjectArray(receiver, objects, np_method)) { + if (!arg_array.BuildArgArrayFromObjectArray(receiver, objects, np_method, soa.Self())) { CHECK(soa.Self()->IsExceptionPending()); return nullptr; } diff --git a/runtime/runtime.cc b/runtime/runtime.cc index 55e1852c0c..5e008a8bb8 100644 --- a/runtime/runtime.cc +++ b/runtime/runtime.cc @@ -137,6 +137,7 @@ #include "jit/profile_saver.h" #include "quick/quick_method_frame_info.h" #include "reflection.h" +#include "runtime_callbacks.h" #include "runtime_options.h" #include "ScopedLocalRef.h" #include "scoped_thread_state_change-inl.h" @@ -253,10 +254,12 @@ Runtime::Runtime() pruned_dalvik_cache_(false), // Initially assume we perceive jank in case the process state is never updated. process_state_(kProcessStateJankPerceptible), - zygote_no_threads_(false) { + zygote_no_threads_(false), + cha_(nullptr) { CheckAsmSupportOffsetsAndSizes(); std::fill(callee_save_methods_, callee_save_methods_ + arraysize(callee_save_methods_), 0u); interpreter::CheckInterpreterAsmConstants(); + callbacks_.reset(new RuntimeCallbacks()); } Runtime::~Runtime() { @@ -301,6 +304,13 @@ Runtime::~Runtime() { Trace::Shutdown(); + // Report death. Clients me require a working thread, still, so do it before GC completes and + // all non-daemon threads are done. + { + ScopedObjectAccess soa(self); + callbacks_->NextRuntimePhase(RuntimePhaseCallback::RuntimePhase::kDeath); + } + if (attach_shutdown_thread) { DetachCurrentThread(); self = nullptr; @@ -703,6 +713,13 @@ bool Runtime::Start() { Thread::FinishStartup(); + // Send the start phase event. We have to wait till here as this is when the main thread peer + // has just been generated, important root clinits have been run and JNI is completely functional. + { + ScopedObjectAccess soa(self); + callbacks_->NextRuntimePhase(RuntimePhaseCallback::RuntimePhase::kStart); + } + system_class_loader_ = CreateSystemClassLoader(this); if (!is_zygote_) { @@ -718,6 +735,13 @@ bool Runtime::Start() { GetInstructionSetString(kRuntimeISA)); } + // Send the initialized phase event. Send it before starting daemons, as otherwise + // sending thread events becomes complicated. + { + ScopedObjectAccess soa(self); + callbacks_->NextRuntimePhase(RuntimePhaseCallback::RuntimePhase::kInit); + } + StartDaemonThreads(); { @@ -1022,7 +1046,7 @@ bool Runtime::Init(RuntimeArgumentMap&& runtime_options_in) { monitor_list_ = new MonitorList; monitor_pool_ = MonitorPool::Create(); - thread_list_ = new ThreadList; + thread_list_ = new ThreadList(runtime_options.GetOrDefault(Opt::ThreadSuspendTimeout)); intern_table_ = new InternTable; verify_ = runtime_options.GetOrDefault(Opt::Verify); @@ -1045,16 +1069,13 @@ bool Runtime::Init(RuntimeArgumentMap&& runtime_options_in) { experimental_flags_ = runtime_options.GetOrDefault(Opt::Experimental); is_low_memory_mode_ = runtime_options.Exists(Opt::LowMemoryMode); - if (experimental_flags_ & ExperimentalFlags::kRuntimePlugins) { - plugins_ = runtime_options.ReleaseOrDefault(Opt::Plugins); - } - if (experimental_flags_ & ExperimentalFlags::kAgents) { - agents_ = runtime_options.ReleaseOrDefault(Opt::AgentPath); - // TODO Add back in -agentlib - // for (auto lib : runtime_options.ReleaseOrDefault(Opt::AgentLib)) { - // agents_.push_back(lib); - // } - } + plugins_ = runtime_options.ReleaseOrDefault(Opt::Plugins); + agents_ = runtime_options.ReleaseOrDefault(Opt::AgentPath); + // TODO Add back in -agentlib + // for (auto lib : runtime_options.ReleaseOrDefault(Opt::AgentLib)) { + // agents_.push_back(lib); + // } + XGcOption xgc_option = runtime_options.GetOrDefault(Opt::GcOption); heap_ = new gc::Heap(runtime_options.GetOrDefault(Opt::MemoryInitialSize), runtime_options.GetOrDefault(Opt::HeapGrowthLimit), @@ -1100,6 +1121,8 @@ bool Runtime::Init(RuntimeArgumentMap&& runtime_options_in) { if (runtime_options.Exists(Opt::JdwpOptions)) { Dbg::ConfigureJdwp(runtime_options.GetOrDefault(Opt::JdwpOptions)); } + callbacks_->AddThreadLifecycleCallback(Dbg::GetThreadLifecycleCallback()); + callbacks_->AddClassLoadCallback(Dbg::GetClassLoadCallback()); jit_options_.reset(jit::JitOptions::CreateFromRuntimeArguments(runtime_options)); if (IsAotCompiler()) { @@ -1358,6 +1381,10 @@ bool Runtime::Init(RuntimeArgumentMap&& runtime_options_in) { LOG(ERROR) << "Unable to load an agent: " << err; } } + { + ScopedObjectAccess soa(self); + callbacks_->NextRuntimePhase(RuntimePhaseCallback::RuntimePhase::kInitialAgents); + } VLOG(startup) << "Runtime::Init exiting"; @@ -1370,7 +1397,7 @@ static bool EnsureJvmtiPlugin(Runtime* runtime, constexpr const char* plugin_name = kIsDebugBuild ? "libopenjdkjvmtid.so" : "libopenjdkjvmti.so"; // Is the plugin already loaded? - for (Plugin p : *plugins) { + for (const Plugin& p : *plugins) { if (p.GetLibrary() == plugin_name) { return true; } @@ -1547,6 +1574,12 @@ void Runtime::DumpForSigQuit(std::ostream& os) { thread_list_->DumpForSigQuit(os); BaseMutex::DumpAll(os); + + // Inform anyone else who is interested in SigQuit. + { + ScopedObjectAccess soa(Thread::Current()); + callbacks_->SigQuit(); + } } void Runtime::DumpLockHolders(std::ostream& os) { @@ -2253,4 +2286,8 @@ void Runtime::Aborter(const char* abort_message) { Runtime::Abort(abort_message); } +RuntimeCallbacks* Runtime::GetRuntimeCallbacks() { + return callbacks_.get(); +} + } // namespace art diff --git a/runtime/runtime.h b/runtime/runtime.h index cf23d0510d..f7d6810ff5 100644 --- a/runtime/runtime.h +++ b/runtime/runtime.h @@ -28,6 +28,7 @@ #include "arch/instruction_set.h" #include "base/macros.h" +#include "base/mutex.h" #include "dex_file_types.h" #include "experimental_flags.h" #include "gc_root.h" @@ -89,6 +90,7 @@ class NullPointerHandler; class OatFileManager; class Plugin; struct RuntimeArgumentMap; +class RuntimeCallbacks; class SignalCatcher; class StackOverflowHandler; class SuspensionHandler; @@ -659,6 +661,8 @@ class Runtime { void AttachAgent(const std::string& agent_arg); + RuntimeCallbacks* GetRuntimeCallbacks(); + private: static void InitPlatformSignalHandlers(); @@ -916,6 +920,8 @@ class Runtime { ClassHierarchyAnalysis* cha_; + std::unique_ptr<RuntimeCallbacks> callbacks_; + DISALLOW_COPY_AND_ASSIGN(Runtime); }; std::ostream& operator<<(std::ostream& os, const Runtime::CalleeSaveType& rhs); diff --git a/runtime/runtime_callbacks.cc b/runtime/runtime_callbacks.cc new file mode 100644 index 0000000000..25324b52d1 --- /dev/null +++ b/runtime/runtime_callbacks.cc @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "runtime_callbacks.h" + +#include <algorithm> + +#include "base/macros.h" +#include "class_linker.h" +#include "thread.h" + +namespace art { + +void RuntimeCallbacks::AddThreadLifecycleCallback(ThreadLifecycleCallback* cb) { + thread_callbacks_.push_back(cb); +} + +template <typename T> +ALWAYS_INLINE +static inline void Remove(T* cb, std::vector<T*>* data) { + auto it = std::find(data->begin(), data->end(), cb); + if (it != data->end()) { + data->erase(it); + } +} + +void RuntimeCallbacks::RemoveThreadLifecycleCallback(ThreadLifecycleCallback* cb) { + Remove(cb, &thread_callbacks_); +} + +void RuntimeCallbacks::ThreadStart(Thread* self) { + for (ThreadLifecycleCallback* cb : thread_callbacks_) { + cb->ThreadStart(self); + } +} + +void RuntimeCallbacks::ThreadDeath(Thread* self) { + for (ThreadLifecycleCallback* cb : thread_callbacks_) { + cb->ThreadDeath(self); + } +} + +void RuntimeCallbacks::AddClassLoadCallback(ClassLoadCallback* cb) { + class_callbacks_.push_back(cb); +} + +void RuntimeCallbacks::RemoveClassLoadCallback(ClassLoadCallback* cb) { + Remove(cb, &class_callbacks_); +} + +void RuntimeCallbacks::ClassLoad(Handle<mirror::Class> klass) { + for (ClassLoadCallback* cb : class_callbacks_) { + cb->ClassLoad(klass); + } +} + +void RuntimeCallbacks::ClassPreDefine(const char* descriptor, + Handle<mirror::Class> temp_class, + Handle<mirror::ClassLoader> loader, + const DexFile& initial_dex_file, + const DexFile::ClassDef& initial_class_def, + /*out*/DexFile const** final_dex_file, + /*out*/DexFile::ClassDef const** final_class_def) { + DexFile const* current_dex_file = &initial_dex_file; + DexFile::ClassDef const* current_class_def = &initial_class_def; + for (ClassLoadCallback* cb : class_callbacks_) { + DexFile const* new_dex_file = nullptr; + DexFile::ClassDef const* new_class_def = nullptr; + cb->ClassPreDefine(descriptor, + temp_class, + loader, + *current_dex_file, + *current_class_def, + &new_dex_file, + &new_class_def); + if ((new_dex_file != nullptr && new_dex_file != current_dex_file) || + (new_class_def != nullptr && new_class_def != current_class_def)) { + DCHECK(new_dex_file != nullptr && new_class_def != nullptr); + current_dex_file = new_dex_file; + current_class_def = new_class_def; + } + } + *final_dex_file = current_dex_file; + *final_class_def = current_class_def; +} + +void RuntimeCallbacks::ClassPrepare(Handle<mirror::Class> temp_klass, Handle<mirror::Class> klass) { + for (ClassLoadCallback* cb : class_callbacks_) { + cb->ClassPrepare(temp_klass, klass); + } +} + +void RuntimeCallbacks::AddRuntimeSigQuitCallback(RuntimeSigQuitCallback* cb) { + sigquit_callbacks_.push_back(cb); +} + +void RuntimeCallbacks::RemoveRuntimeSigQuitCallback(RuntimeSigQuitCallback* cb) { + Remove(cb, &sigquit_callbacks_); +} + +void RuntimeCallbacks::SigQuit() { + for (RuntimeSigQuitCallback* cb : sigquit_callbacks_) { + cb->SigQuit(); + } +} + +void RuntimeCallbacks::AddRuntimePhaseCallback(RuntimePhaseCallback* cb) { + phase_callbacks_.push_back(cb); +} + +void RuntimeCallbacks::RemoveRuntimePhaseCallback(RuntimePhaseCallback* cb) { + Remove(cb, &phase_callbacks_); +} + +void RuntimeCallbacks::NextRuntimePhase(RuntimePhaseCallback::RuntimePhase phase) { + for (RuntimePhaseCallback* cb : phase_callbacks_) { + cb->NextRuntimePhase(phase); + } +} + +} // namespace art diff --git a/runtime/runtime_callbacks.h b/runtime/runtime_callbacks.h new file mode 100644 index 0000000000..d321254e17 --- /dev/null +++ b/runtime/runtime_callbacks.h @@ -0,0 +1,126 @@ +/* + * 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. + */ + +#ifndef ART_RUNTIME_RUNTIME_CALLBACKS_H_ +#define ART_RUNTIME_RUNTIME_CALLBACKS_H_ + +#include <vector> + +#include "base/macros.h" +#include "base/mutex.h" +#include "dex_file.h" +#include "handle.h" + +namespace art { + +namespace mirror { +class Class; +class ClassLoader; +} // namespace mirror + +class ClassLoadCallback; +class Thread; +class ThreadLifecycleCallback; + +// Note: RuntimeCallbacks uses the mutator lock to synchronize the callback lists. A thread must +// hold the exclusive lock to add or remove a listener. A thread must hold the shared lock +// to dispatch an event. This setup is chosen as some clients may want to suspend the +// dispatching thread or all threads. +// +// To make this safe, the following restrictions apply: +// * Only the owner of a listener may ever add or remove said listener. +// * A listener must never add or remove itself or any other listener while running. +// * It is the responsibility of the owner to not remove the listener while it is running +// (and suspended). +// +// The simplest way to satisfy these restrictions is to never remove a listener, and to do +// any state checking (is the listener enabled) in the listener itself. For an example, see +// Dbg. + +class RuntimeSigQuitCallback { + public: + virtual ~RuntimeSigQuitCallback() {} + + virtual void SigQuit() REQUIRES_SHARED(Locks::mutator_lock_) = 0; +}; + +class RuntimePhaseCallback { + public: + enum RuntimePhase { + kInitialAgents, // Initial agent loading is done. + kStart, // The runtime is started. + kInit, // The runtime is initialized (and will run user code soon). + kDeath, // The runtime just died. + }; + + virtual ~RuntimePhaseCallback() {} + + virtual void NextRuntimePhase(RuntimePhase phase) REQUIRES_SHARED(Locks::mutator_lock_) = 0; +}; + +class RuntimeCallbacks { + public: + void AddThreadLifecycleCallback(ThreadLifecycleCallback* cb) REQUIRES(Locks::mutator_lock_); + void RemoveThreadLifecycleCallback(ThreadLifecycleCallback* cb) REQUIRES(Locks::mutator_lock_); + + void ThreadStart(Thread* self) REQUIRES_SHARED(Locks::mutator_lock_); + void ThreadDeath(Thread* self) REQUIRES_SHARED(Locks::mutator_lock_); + + void AddClassLoadCallback(ClassLoadCallback* cb) REQUIRES(Locks::mutator_lock_); + void RemoveClassLoadCallback(ClassLoadCallback* cb) REQUIRES(Locks::mutator_lock_); + + void ClassLoad(Handle<mirror::Class> klass) REQUIRES_SHARED(Locks::mutator_lock_); + void ClassPrepare(Handle<mirror::Class> temp_klass, Handle<mirror::Class> klass) + REQUIRES_SHARED(Locks::mutator_lock_); + + void AddRuntimeSigQuitCallback(RuntimeSigQuitCallback* cb) + REQUIRES(Locks::mutator_lock_); + void RemoveRuntimeSigQuitCallback(RuntimeSigQuitCallback* cb) + REQUIRES(Locks::mutator_lock_); + + void SigQuit() REQUIRES_SHARED(Locks::mutator_lock_); + + void AddRuntimePhaseCallback(RuntimePhaseCallback* cb) + REQUIRES(Locks::mutator_lock_); + void RemoveRuntimePhaseCallback(RuntimePhaseCallback* cb) + REQUIRES(Locks::mutator_lock_); + + void NextRuntimePhase(RuntimePhaseCallback::RuntimePhase phase) + REQUIRES_SHARED(Locks::mutator_lock_); + + void ClassPreDefine(const char* descriptor, + Handle<mirror::Class> temp_class, + Handle<mirror::ClassLoader> loader, + const DexFile& initial_dex_file, + const DexFile::ClassDef& initial_class_def, + /*out*/DexFile const** final_dex_file, + /*out*/DexFile::ClassDef const** final_class_def) + REQUIRES_SHARED(Locks::mutator_lock_); + + private: + std::vector<ThreadLifecycleCallback*> thread_callbacks_ + GUARDED_BY(Locks::mutator_lock_); + std::vector<ClassLoadCallback*> class_callbacks_ + GUARDED_BY(Locks::mutator_lock_); + std::vector<RuntimeSigQuitCallback*> sigquit_callbacks_ + GUARDED_BY(Locks::mutator_lock_); + std::vector<RuntimePhaseCallback*> phase_callbacks_ + GUARDED_BY(Locks::mutator_lock_); +}; + +} // namespace art + +#endif // ART_RUNTIME_RUNTIME_CALLBACKS_H_ diff --git a/runtime/runtime_callbacks_test.cc b/runtime/runtime_callbacks_test.cc new file mode 100644 index 0000000000..f1e78b40e0 --- /dev/null +++ b/runtime/runtime_callbacks_test.cc @@ -0,0 +1,432 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "runtime_callbacks.h" + +#include "jni.h" +#include <signal.h> +#include <sys/types.h> +#include <unistd.h> + +#include <initializer_list> +#include <memory> +#include <string> + +#include "art_method-inl.h" +#include "base/mutex.h" +#include "class_linker.h" +#include "common_runtime_test.h" +#include "handle.h" +#include "handle_scope-inl.h" +#include "mem_map.h" +#include "mirror/class-inl.h" +#include "mirror/class_loader.h" +#include "obj_ptr.h" +#include "runtime.h" +#include "scoped_thread_state_change-inl.h" +#include "ScopedLocalRef.h" +#include "thread-inl.h" +#include "thread_list.h" +#include "well_known_classes.h" + +namespace art { + +class RuntimeCallbacksTest : public CommonRuntimeTest { + protected: + void SetUp() OVERRIDE { + CommonRuntimeTest::SetUp(); + + Thread* self = Thread::Current(); + ScopedObjectAccess soa(self); + ScopedThreadSuspension sts(self, kWaitingForDebuggerToAttach); + ScopedSuspendAll ssa("RuntimeCallbacksTest SetUp"); + AddListener(); + } + + void TearDown() OVERRIDE { + { + Thread* self = Thread::Current(); + ScopedObjectAccess soa(self); + ScopedThreadSuspension sts(self, kWaitingForDebuggerToAttach); + ScopedSuspendAll ssa("RuntimeCallbacksTest TearDown"); + RemoveListener(); + } + + CommonRuntimeTest::TearDown(); + } + + virtual void AddListener() REQUIRES(Locks::mutator_lock_) = 0; + virtual void RemoveListener() REQUIRES(Locks::mutator_lock_) = 0; + + void MakeExecutable(ObjPtr<mirror::Class> klass) REQUIRES_SHARED(Locks::mutator_lock_) { + CHECK(klass != nullptr); + PointerSize pointer_size = class_linker_->GetImagePointerSize(); + for (auto& m : klass->GetMethods(pointer_size)) { + if (!m.IsAbstract()) { + class_linker_->SetEntryPointsToInterpreter(&m); + } + } + } +}; + +class ThreadLifecycleCallbackRuntimeCallbacksTest : public RuntimeCallbacksTest { + public: + static void* PthreadsCallback(void* arg ATTRIBUTE_UNUSED) { + // Attach. + Runtime* runtime = Runtime::Current(); + CHECK(runtime->AttachCurrentThread("ThreadLifecycle test thread", true, nullptr, false)); + + // Detach. + runtime->DetachCurrentThread(); + + // Die... + return nullptr; + } + + protected: + void AddListener() OVERRIDE REQUIRES(Locks::mutator_lock_) { + Runtime::Current()->GetRuntimeCallbacks()->AddThreadLifecycleCallback(&cb_); + } + void RemoveListener() OVERRIDE REQUIRES(Locks::mutator_lock_) { + Runtime::Current()->GetRuntimeCallbacks()->RemoveThreadLifecycleCallback(&cb_); + } + + enum CallbackState { + kBase, + kStarted, + kDied, + kWrongStart, + kWrongDeath, + }; + + struct Callback : public ThreadLifecycleCallback { + void ThreadStart(Thread* self) OVERRIDE { + if (state == CallbackState::kBase) { + state = CallbackState::kStarted; + stored_self = self; + } else { + state = CallbackState::kWrongStart; + } + } + + void ThreadDeath(Thread* self) OVERRIDE { + if (state == CallbackState::kStarted && self == stored_self) { + state = CallbackState::kDied; + } else { + state = CallbackState::kWrongDeath; + } + } + + Thread* stored_self; + CallbackState state = CallbackState::kBase; + }; + + Callback cb_; +}; + +TEST_F(ThreadLifecycleCallbackRuntimeCallbacksTest, ThreadLifecycleCallbackJava) { + Thread* self = Thread::Current(); + + self->TransitionFromSuspendedToRunnable(); + bool started = runtime_->Start(); + ASSERT_TRUE(started); + + cb_.state = CallbackState::kBase; // Ignore main thread attach. + + { + ScopedObjectAccess soa(self); + MakeExecutable(soa.Decode<mirror::Class>(WellKnownClasses::java_lang_Thread)); + } + + JNIEnv* env = self->GetJniEnv(); + + ScopedLocalRef<jobject> thread_name(env, + env->NewStringUTF("ThreadLifecycleCallback test thread")); + ASSERT_TRUE(thread_name.get() != nullptr); + + ScopedLocalRef<jobject> thread(env, env->AllocObject(WellKnownClasses::java_lang_Thread)); + ASSERT_TRUE(thread.get() != nullptr); + + env->CallNonvirtualVoidMethod(thread.get(), + WellKnownClasses::java_lang_Thread, + WellKnownClasses::java_lang_Thread_init, + runtime_->GetMainThreadGroup(), + thread_name.get(), + kMinThreadPriority, + JNI_FALSE); + ASSERT_FALSE(env->ExceptionCheck()); + + jmethodID start_id = env->GetMethodID(WellKnownClasses::java_lang_Thread, "start", "()V"); + ASSERT_TRUE(start_id != nullptr); + + env->CallVoidMethod(thread.get(), start_id); + ASSERT_FALSE(env->ExceptionCheck()); + + jmethodID join_id = env->GetMethodID(WellKnownClasses::java_lang_Thread, "join", "()V"); + ASSERT_TRUE(join_id != nullptr); + + env->CallVoidMethod(thread.get(), join_id); + ASSERT_FALSE(env->ExceptionCheck()); + + EXPECT_TRUE(cb_.state == CallbackState::kDied) << static_cast<int>(cb_.state); +} + +TEST_F(ThreadLifecycleCallbackRuntimeCallbacksTest, ThreadLifecycleCallbackAttach) { + std::string error_msg; + std::unique_ptr<MemMap> stack(MemMap::MapAnonymous("ThreadLifecycleCallback Thread", + nullptr, + 128 * kPageSize, // Just some small stack. + PROT_READ | PROT_WRITE, + false, + false, + &error_msg)); + ASSERT_FALSE(stack == nullptr) << error_msg; + + const char* reason = "ThreadLifecycleCallback test thread"; + pthread_attr_t attr; + CHECK_PTHREAD_CALL(pthread_attr_init, (&attr), reason); + CHECK_PTHREAD_CALL(pthread_attr_setstack, (&attr, stack->Begin(), stack->Size()), reason); + pthread_t pthread; + CHECK_PTHREAD_CALL(pthread_create, + (&pthread, + &attr, + &ThreadLifecycleCallbackRuntimeCallbacksTest::PthreadsCallback, + this), + reason); + CHECK_PTHREAD_CALL(pthread_attr_destroy, (&attr), reason); + + CHECK_PTHREAD_CALL(pthread_join, (pthread, nullptr), "ThreadLifecycleCallback test shutdown"); + + // Detach is not a ThreadDeath event, so we expect to be in state Started. + EXPECT_TRUE(cb_.state == CallbackState::kStarted) << static_cast<int>(cb_.state); +} + +class ClassLoadCallbackRuntimeCallbacksTest : public RuntimeCallbacksTest { + protected: + void AddListener() OVERRIDE REQUIRES(Locks::mutator_lock_) { + Runtime::Current()->GetRuntimeCallbacks()->AddClassLoadCallback(&cb_); + } + void RemoveListener() OVERRIDE REQUIRES(Locks::mutator_lock_) { + Runtime::Current()->GetRuntimeCallbacks()->RemoveClassLoadCallback(&cb_); + } + + bool Expect(std::initializer_list<const char*> list) { + if (cb_.data.size() != list.size()) { + PrintError(list); + return false; + } + + if (!std::equal(cb_.data.begin(), cb_.data.end(), list.begin())) { + PrintError(list); + return false; + } + + return true; + } + + void PrintError(std::initializer_list<const char*> list) { + LOG(ERROR) << "Expected:"; + for (const char* expected : list) { + LOG(ERROR) << " " << expected; + } + LOG(ERROR) << "Found:"; + for (const auto& s : cb_.data) { + LOG(ERROR) << " " << s; + } + } + + struct Callback : public ClassLoadCallback { + virtual void ClassPreDefine(const char* descriptor, + Handle<mirror::Class> klass ATTRIBUTE_UNUSED, + Handle<mirror::ClassLoader> class_loader ATTRIBUTE_UNUSED, + const DexFile& initial_dex_file, + const DexFile::ClassDef& initial_class_def ATTRIBUTE_UNUSED, + /*out*/DexFile const** final_dex_file ATTRIBUTE_UNUSED, + /*out*/DexFile::ClassDef const** final_class_def ATTRIBUTE_UNUSED) + OVERRIDE REQUIRES_SHARED(Locks::mutator_lock_) { + std::string location(initial_dex_file.GetLocation()); + std::string event = + std::string("PreDefine:") + descriptor + " <" + + location.substr(location.rfind("/") + 1, location.size()) + ">"; + data.push_back(event); + } + + void ClassLoad(Handle<mirror::Class> klass) OVERRIDE REQUIRES_SHARED(Locks::mutator_lock_) { + std::string tmp; + std::string event = std::string("Load:") + klass->GetDescriptor(&tmp); + data.push_back(event); + } + + void ClassPrepare(Handle<mirror::Class> temp_klass, + Handle<mirror::Class> klass) OVERRIDE REQUIRES_SHARED(Locks::mutator_lock_) { + std::string tmp, tmp2; + std::string event = std::string("Prepare:") + klass->GetDescriptor(&tmp) + + "[" + temp_klass->GetDescriptor(&tmp2) + "]"; + data.push_back(event); + } + + std::vector<std::string> data; + }; + + Callback cb_; +}; + +TEST_F(ClassLoadCallbackRuntimeCallbacksTest, ClassLoadCallback) { + ScopedObjectAccess soa(Thread::Current()); + jobject jclass_loader = LoadDex("XandY"); + VariableSizedHandleScope hs(soa.Self()); + Handle<mirror::ClassLoader> class_loader(hs.NewHandle( + soa.Decode<mirror::ClassLoader>(jclass_loader))); + + const char* descriptor_y = "LY;"; + Handle<mirror::Class> h_Y( + hs.NewHandle(class_linker_->FindClass(soa.Self(), descriptor_y, class_loader))); + ASSERT_TRUE(h_Y.Get() != nullptr); + + bool expect1 = Expect({ "PreDefine:LY; <art-gtest-XandY.jar>", + "PreDefine:LX; <art-gtest-XandY.jar>", + "Load:LX;", + "Prepare:LX;[LX;]", + "Load:LY;", + "Prepare:LY;[LY;]" }); + EXPECT_TRUE(expect1); + + cb_.data.clear(); + + ASSERT_TRUE(class_linker_->EnsureInitialized(Thread::Current(), h_Y, true, true)); + + bool expect2 = Expect({ "PreDefine:LY$Z; <art-gtest-XandY.jar>", + "Load:LY$Z;", + "Prepare:LY$Z;[LY$Z;]" }); + EXPECT_TRUE(expect2); +} + +class RuntimeSigQuitCallbackRuntimeCallbacksTest : public RuntimeCallbacksTest { + protected: + void AddListener() OVERRIDE REQUIRES(Locks::mutator_lock_) { + Runtime::Current()->GetRuntimeCallbacks()->AddRuntimeSigQuitCallback(&cb_); + } + void RemoveListener() OVERRIDE REQUIRES(Locks::mutator_lock_) { + Runtime::Current()->GetRuntimeCallbacks()->RemoveRuntimeSigQuitCallback(&cb_); + } + + struct Callback : public RuntimeSigQuitCallback { + void SigQuit() OVERRIDE { + ++sigquit_count; + } + + size_t sigquit_count = 0; + }; + + Callback cb_; +}; + +TEST_F(RuntimeSigQuitCallbackRuntimeCallbacksTest, SigQuit) { + // The runtime needs to be started for the signal handler. + Thread* self = Thread::Current(); + + self->TransitionFromSuspendedToRunnable(); + bool started = runtime_->Start(); + ASSERT_TRUE(started); + + EXPECT_EQ(0u, cb_.sigquit_count); + + kill(getpid(), SIGQUIT); + + // Try a few times. + for (size_t i = 0; i != 30; ++i) { + if (cb_.sigquit_count == 0) { + sleep(1); + } else { + break; + } + } + EXPECT_EQ(1u, cb_.sigquit_count); +} + +class RuntimePhaseCallbackRuntimeCallbacksTest : public RuntimeCallbacksTest { + protected: + void AddListener() OVERRIDE REQUIRES(Locks::mutator_lock_) { + Runtime::Current()->GetRuntimeCallbacks()->AddRuntimePhaseCallback(&cb_); + } + void RemoveListener() OVERRIDE REQUIRES(Locks::mutator_lock_) { + Runtime::Current()->GetRuntimeCallbacks()->RemoveRuntimePhaseCallback(&cb_); + } + + void TearDown() OVERRIDE { + // Bypass RuntimeCallbacksTest::TearDown, as the runtime is already gone. + CommonRuntimeTest::TearDown(); + } + + struct Callback : public RuntimePhaseCallback { + void NextRuntimePhase(RuntimePhaseCallback::RuntimePhase p) OVERRIDE { + if (p == RuntimePhaseCallback::RuntimePhase::kInitialAgents) { + if (start_seen > 0 || init_seen > 0 || death_seen > 0) { + LOG(FATAL) << "Unexpected order"; + } + ++initial_agents_seen; + } else if (p == RuntimePhaseCallback::RuntimePhase::kStart) { + if (init_seen > 0 || death_seen > 0) { + LOG(FATAL) << "Init seen before start."; + } + ++start_seen; + } else if (p == RuntimePhaseCallback::RuntimePhase::kInit) { + ++init_seen; + } else if (p == RuntimePhaseCallback::RuntimePhase::kDeath) { + ++death_seen; + } else { + LOG(FATAL) << "Unknown phase " << static_cast<uint32_t>(p); + } + } + + size_t initial_agents_seen = 0; + size_t start_seen = 0; + size_t init_seen = 0; + size_t death_seen = 0; + }; + + Callback cb_; +}; + +TEST_F(RuntimePhaseCallbackRuntimeCallbacksTest, Phases) { + ASSERT_EQ(0u, cb_.initial_agents_seen); + ASSERT_EQ(0u, cb_.start_seen); + ASSERT_EQ(0u, cb_.init_seen); + ASSERT_EQ(0u, cb_.death_seen); + + // Start the runtime. + { + Thread* self = Thread::Current(); + self->TransitionFromSuspendedToRunnable(); + bool started = runtime_->Start(); + ASSERT_TRUE(started); + } + + ASSERT_EQ(0u, cb_.initial_agents_seen); + ASSERT_EQ(1u, cb_.start_seen); + ASSERT_EQ(1u, cb_.init_seen); + ASSERT_EQ(0u, cb_.death_seen); + + // Delete the runtime. + runtime_.reset(); + + ASSERT_EQ(0u, cb_.initial_agents_seen); + ASSERT_EQ(1u, cb_.start_seen); + ASSERT_EQ(1u, cb_.init_seen); + ASSERT_EQ(1u, cb_.death_seen); +} + +} // namespace art diff --git a/runtime/runtime_options.cc b/runtime/runtime_options.cc index e75481c78a..aa147198fc 100644 --- a/runtime/runtime_options.cc +++ b/runtime/runtime_options.cc @@ -21,6 +21,7 @@ #include "gc/heap.h" #include "monitor.h" #include "runtime.h" +#include "thread_list.h" #include "trace.h" #include "utils.h" #include "debugger.h" diff --git a/runtime/runtime_options.def b/runtime/runtime_options.def index ecabf9adc5..ad748b04d3 100644 --- a/runtime/runtime_options.def +++ b/runtime/runtime_options.def @@ -60,6 +60,8 @@ RUNTIME_OPTIONS_KEY (MillisecondsToNanoseconds, \ LongPauseLogThreshold, gc::Heap::kDefaultLongPauseLogThreshold) RUNTIME_OPTIONS_KEY (MillisecondsToNanoseconds, \ LongGCLogThreshold, gc::Heap::kDefaultLongGCLogThreshold) +RUNTIME_OPTIONS_KEY (MillisecondsToNanoseconds, \ + ThreadSuspendTimeout, ThreadList::kDefaultThreadSuspendTimeout) RUNTIME_OPTIONS_KEY (Unit, DumpGCPerformanceOnShutdown) RUNTIME_OPTIONS_KEY (Unit, DumpJITInfoOnShutdown) RUNTIME_OPTIONS_KEY (Unit, IgnoreMaxFootprint) @@ -117,10 +119,10 @@ RUNTIME_OPTIONS_KEY (unsigned int, ZygoteMaxFailedBoots, 10) RUNTIME_OPTIONS_KEY (Unit, NoDexFileFallback) RUNTIME_OPTIONS_KEY (std::string, CpuAbiList) RUNTIME_OPTIONS_KEY (std::string, Fingerprint) -RUNTIME_OPTIONS_KEY (ExperimentalFlags, Experimental, ExperimentalFlags::kNone) // -Xexperimental:{none, agents} -RUNTIME_OPTIONS_KEY (std::vector<ti::Agent>, AgentLib) // -agentlib:<libname>=<options>, Requires -Xexperimental:agents -RUNTIME_OPTIONS_KEY (std::vector<ti::Agent>, AgentPath) // -agentpath:<libname>=<options>, Requires -Xexperimental:agents -RUNTIME_OPTIONS_KEY (std::vector<Plugin>, Plugins) // -Xplugin:<library> Requires -Xexperimental:runtime-plugins +RUNTIME_OPTIONS_KEY (ExperimentalFlags, Experimental, ExperimentalFlags::kNone) // -Xexperimental:{...} +RUNTIME_OPTIONS_KEY (std::vector<ti::Agent>, AgentLib) // -agentlib:<libname>=<options> +RUNTIME_OPTIONS_KEY (std::vector<ti::Agent>, AgentPath) // -agentpath:<libname>=<options> +RUNTIME_OPTIONS_KEY (std::vector<Plugin>, Plugins) // -Xplugin:<library> RUNTIME_OPTIONS_KEY (Unit, FullyDeoptable) // -Xfully-deoptable // Not parse-able from command line, but can be provided explicitly. diff --git a/runtime/stack_map.cc b/runtime/stack_map.cc index 9ebf9a7bdd..690b069c8e 100644 --- a/runtime/stack_map.cc +++ b/runtime/stack_map.cc @@ -116,7 +116,8 @@ void InlineInfoEncoding::Dump(VariableIndentationOutputStream* vios) const { void CodeInfo::Dump(VariableIndentationOutputStream* vios, uint32_t code_offset, uint16_t number_of_dex_registers, - bool dump_stack_maps) const { + bool dump_stack_maps, + InstructionSet instruction_set) const { CodeInfoEncoding encoding = ExtractEncoding(); size_t number_of_stack_maps = GetNumberOfStackMaps(encoding); vios->Stream() @@ -139,6 +140,7 @@ void CodeInfo::Dump(VariableIndentationOutputStream* vios, encoding, code_offset, number_of_dex_registers, + instruction_set, " " + std::to_string(i)); } } @@ -188,14 +190,17 @@ void StackMap::Dump(VariableIndentationOutputStream* vios, const CodeInfoEncoding& encoding, uint32_t code_offset, uint16_t number_of_dex_registers, + InstructionSet instruction_set, const std::string& header_suffix) const { StackMapEncoding stack_map_encoding = encoding.stack_map_encoding; + const uint32_t pc_offset = GetNativePcOffset(stack_map_encoding, instruction_set); vios->Stream() << "StackMap" << header_suffix << std::hex - << " [native_pc=0x" << code_offset + GetNativePcOffset(stack_map_encoding) << "]" + << " [native_pc=0x" << code_offset + pc_offset << "]" + << " [entry_size=0x" << encoding.stack_map_size_in_bytes << "]" << " (dex_pc=0x" << GetDexPc(stack_map_encoding) - << ", native_pc_offset=0x" << GetNativePcOffset(stack_map_encoding) + << ", native_pc_offset=0x" << pc_offset << ", dex_register_map_offset=0x" << GetDexRegisterMapOffset(stack_map_encoding) << ", inline_info_offset=0x" << GetInlineDescriptorOffset(stack_map_encoding) << ", register_mask=0x" << GetRegisterMask(stack_map_encoding) diff --git a/runtime/stack_map.h b/runtime/stack_map.h index 15d7816031..578252181d 100644 --- a/runtime/stack_map.h +++ b/runtime/stack_map.h @@ -17,6 +17,7 @@ #ifndef ART_RUNTIME_STACK_MAP_H_ #define ART_RUNTIME_STACK_MAP_H_ +#include "arch/code_offset.h" #include "base/bit_vector.h" #include "base/bit_utils.h" #include "dex_file.h" @@ -666,32 +667,7 @@ struct FieldEncoding { ALWAYS_INLINE int32_t Load(const MemoryRegion& region) const { DCHECK_LE(end_offset_, region.size_in_bits()); - const size_t bit_count = BitSize(); - if (bit_count == 0) { - // Do not touch any memory if the range is empty. - return min_value_; - } - uint8_t* address = region.start() + start_offset_ / kBitsPerByte; - const uint32_t shift = start_offset_ & (kBitsPerByte - 1); - // Load the value (reading only the strictly needed bytes). - const uint32_t load_bit_count = shift + bit_count; - uint32_t value = *address++ >> shift; - if (load_bit_count > 8) { - value |= static_cast<uint32_t>(*address++) << (8 - shift); - if (load_bit_count > 16) { - value |= static_cast<uint32_t>(*address++) << (16 - shift); - if (load_bit_count > 24) { - value |= static_cast<uint32_t>(*address++) << (24 - shift); - if (load_bit_count > 32) { - value |= static_cast<uint32_t>(*address++) << (32 - shift); - } - } - } - } - // Clear unwanted most significant bits. - uint32_t clear_bit_count = 32 - bit_count; - value = (value << clear_bit_count) >> clear_bit_count; - return value + min_value_; + return static_cast<int32_t>(region.LoadBits(start_offset_, BitSize())) + min_value_; } ALWAYS_INLINE void Store(MemoryRegion region, int32_t value) const { @@ -805,12 +781,16 @@ class StackMap { encoding.GetDexPcEncoding().Store(region_, dex_pc); } - ALWAYS_INLINE uint32_t GetNativePcOffset(const StackMapEncoding& encoding) const { - return encoding.GetNativePcEncoding().Load(region_); + ALWAYS_INLINE uint32_t GetNativePcOffset(const StackMapEncoding& encoding, + InstructionSet instruction_set) const { + CodeOffset offset( + CodeOffset::FromCompressedOffset(encoding.GetNativePcEncoding().Load(region_))); + return offset.Uint32Value(instruction_set); } - ALWAYS_INLINE void SetNativePcOffset(const StackMapEncoding& encoding, uint32_t native_pc_offset) { - encoding.GetNativePcEncoding().Store(region_, native_pc_offset); + ALWAYS_INLINE void SetNativePcCodeOffset(const StackMapEncoding& encoding, + CodeOffset native_pc_offset) { + encoding.GetNativePcEncoding().Store(region_, native_pc_offset.CompressedValue()); } ALWAYS_INLINE uint32_t GetDexRegisterMapOffset(const StackMapEncoding& encoding) const { @@ -866,6 +846,7 @@ class StackMap { const CodeInfoEncoding& encoding, uint32_t code_offset, uint16_t number_of_dex_registers, + InstructionSet instruction_set, const std::string& header_suffix = "") const; // Special (invalid) offset for the DexRegisterMapOffset field meaning @@ -1127,7 +1108,7 @@ class CodeInfo { GetDexRegisterLocationCatalogSize(encoding))); } - StackMap GetStackMapAt(size_t i, const CodeInfoEncoding& encoding) const { + ALWAYS_INLINE StackMap GetStackMapAt(size_t i, const CodeInfoEncoding& encoding) const { size_t stack_map_size = encoding.stack_map_size_in_bytes; return StackMap(GetStackMaps(encoding).Subregion(i * stack_map_size, stack_map_size)); } @@ -1176,6 +1157,17 @@ class CodeInfo { } } + size_t GetDexRegisterMapsSize(const CodeInfoEncoding& encoding, + uint32_t number_of_dex_registers) const { + size_t total = 0; + for (size_t i = 0, e = GetNumberOfStackMaps(encoding); i < e; ++i) { + StackMap stack_map = GetStackMapAt(i, encoding); + DexRegisterMap map(GetDexRegisterMapOf(stack_map, encoding, number_of_dex_registers)); + total += map.Size(); + } + return total; + } + // Return the `DexRegisterMap` pointed by `inline_info` at depth `depth`. DexRegisterMap GetDexRegisterMapAtDepth(uint8_t depth, InlineInfo inline_info, @@ -1234,15 +1226,16 @@ class CodeInfo { if (stack_map.GetDexPc(stack_map_encoding) == dex_pc) { StackMap other = GetStackMapAt(i + 1, encoding); if (other.GetDexPc(stack_map_encoding) == dex_pc && - other.GetNativePcOffset(stack_map_encoding) == - stack_map.GetNativePcOffset(stack_map_encoding)) { + other.GetNativePcOffset(stack_map_encoding, kRuntimeISA) == + stack_map.GetNativePcOffset(stack_map_encoding, kRuntimeISA)) { DCHECK_EQ(other.GetDexRegisterMapOffset(stack_map_encoding), stack_map.GetDexRegisterMapOffset(stack_map_encoding)); DCHECK(!stack_map.HasInlineInfo(stack_map_encoding)); if (i < e - 2) { // Make sure there are not three identical stack maps following each other. - DCHECK_NE(stack_map.GetNativePcOffset(stack_map_encoding), - GetStackMapAt(i + 2, encoding).GetNativePcOffset(stack_map_encoding)); + DCHECK_NE( + stack_map.GetNativePcOffset(stack_map_encoding, kRuntimeISA), + GetStackMapAt(i + 2, encoding).GetNativePcOffset(stack_map_encoding, kRuntimeISA)); } return stack_map; } @@ -1258,7 +1251,8 @@ class CodeInfo { // we could do binary search. for (size_t i = 0, e = GetNumberOfStackMaps(encoding); i < e; ++i) { StackMap stack_map = GetStackMapAt(i, encoding); - if (stack_map.GetNativePcOffset(encoding.stack_map_encoding) == native_pc_offset) { + if (stack_map.GetNativePcOffset(encoding.stack_map_encoding, kRuntimeISA) == + native_pc_offset) { return stack_map; } } @@ -1273,7 +1267,8 @@ class CodeInfo { void Dump(VariableIndentationOutputStream* vios, uint32_t code_offset, uint16_t number_of_dex_registers, - bool dump_stack_maps) const; + bool dump_stack_maps, + InstructionSet instruction_set) const; // Check that the code info has valid stack map and abort if it does not. void AssertValidStackMap(const CodeInfoEncoding& encoding) const { @@ -1288,7 +1283,7 @@ class CodeInfo { } private: - MemoryRegion GetStackMaps(const CodeInfoEncoding& encoding) const { + ALWAYS_INLINE MemoryRegion GetStackMaps(const CodeInfoEncoding& encoding) const { return region_.size() == 0 ? MemoryRegion() : region_.Subregion(GetStackMapsOffset(encoding), GetStackMapsSize(encoding)); diff --git a/runtime/thread.cc b/runtime/thread.cc index ebf14c19b1..d93eab10a0 100644 --- a/runtime/thread.cc +++ b/runtime/thread.cc @@ -67,6 +67,7 @@ #include "quick/quick_method_frame_info.h" #include "reflection.h" #include "runtime.h" +#include "runtime_callbacks.h" #include "scoped_thread_state_change-inl.h" #include "ScopedLocalRef.h" #include "ScopedUtfChars.h" @@ -431,7 +432,8 @@ void* Thread::CreateCallback(void* arg) { ArtField* priorityField = jni::DecodeArtField(WellKnownClasses::java_lang_Thread_priority); self->SetNativePriority(priorityField->GetInt(self->tlsPtr_.opeer)); - Dbg::PostThreadStart(self); + + runtime->GetRuntimeCallbacks()->ThreadStart(self); // Invoke the 'run' method of our java.lang.Thread. ObjPtr<mirror::Object> receiver = self->tlsPtr_.opeer; @@ -723,8 +725,8 @@ bool Thread::Init(ThreadList* thread_list, JavaVMExt* java_vm, JNIEnvExt* jni_en return true; } -Thread* Thread::Attach(const char* thread_name, bool as_daemon, jobject thread_group, - bool create_peer) { +template <typename PeerAction> +Thread* Thread::Attach(const char* thread_name, bool as_daemon, PeerAction peer_action) { Runtime* runtime = Runtime::Current(); if (runtime == nullptr) { LOG(ERROR) << "Thread attaching to non-existent runtime: " << thread_name; @@ -753,32 +755,11 @@ Thread* Thread::Attach(const char* thread_name, bool as_daemon, jobject thread_g CHECK_NE(self->GetState(), kRunnable); self->SetState(kNative); - // If we're the main thread, ClassLinker won't be created until after we're attached, - // so that thread needs a two-stage attach. Regular threads don't need this hack. - // In the compiler, all threads need this hack, because no-one's going to be getting - // a native peer! - if (create_peer) { - self->CreatePeer(thread_name, as_daemon, thread_group); - if (self->IsExceptionPending()) { - // We cannot keep the exception around, as we're deleting self. Try to be helpful and log it. - { - ScopedObjectAccess soa(self); - LOG(ERROR) << "Exception creating thread peer:"; - LOG(ERROR) << self->GetException()->Dump(); - self->ClearException(); - } - runtime->GetThreadList()->Unregister(self); - // Unregister deletes self, no need to do this here. - return nullptr; - } - } else { - // These aren't necessary, but they improve diagnostics for unit tests & command-line tools. - if (thread_name != nullptr) { - self->tlsPtr_.name->assign(thread_name); - ::art::SetThreadName(thread_name); - } else if (self->GetJniEnv()->check_jni) { - LOG(WARNING) << *Thread::Current() << " attached without supplying a name"; - } + // Run the action that is acting on the peer. + if (!peer_action(self)) { + runtime->GetThreadList()->Unregister(self); + // Unregister deletes self, no need to do this here. + return nullptr; } if (VLOG_IS_ON(threads)) { @@ -793,12 +774,63 @@ Thread* Thread::Attach(const char* thread_name, bool as_daemon, jobject thread_g { ScopedObjectAccess soa(self); - Dbg::PostThreadStart(self); + runtime->GetRuntimeCallbacks()->ThreadStart(self); } return self; } +Thread* Thread::Attach(const char* thread_name, + bool as_daemon, + jobject thread_group, + bool create_peer) { + auto create_peer_action = [&](Thread* self) { + // If we're the main thread, ClassLinker won't be created until after we're attached, + // so that thread needs a two-stage attach. Regular threads don't need this hack. + // In the compiler, all threads need this hack, because no-one's going to be getting + // a native peer! + if (create_peer) { + self->CreatePeer(thread_name, as_daemon, thread_group); + if (self->IsExceptionPending()) { + // We cannot keep the exception around, as we're deleting self. Try to be helpful and log it. + { + ScopedObjectAccess soa(self); + LOG(ERROR) << "Exception creating thread peer:"; + LOG(ERROR) << self->GetException()->Dump(); + self->ClearException(); + } + return false; + } + } else { + // These aren't necessary, but they improve diagnostics for unit tests & command-line tools. + if (thread_name != nullptr) { + self->tlsPtr_.name->assign(thread_name); + ::art::SetThreadName(thread_name); + } else if (self->GetJniEnv()->check_jni) { + LOG(WARNING) << *Thread::Current() << " attached without supplying a name"; + } + } + return true; + }; + return Attach(thread_name, as_daemon, create_peer_action); +} + +Thread* Thread::Attach(const char* thread_name, bool as_daemon, jobject thread_peer) { + auto set_peer_action = [&](Thread* self) { + // Install the given peer. + { + DCHECK(self == Thread::Current()); + ScopedObjectAccess soa(self); + self->tlsPtr_.opeer = soa.Decode<mirror::Object>(thread_peer).Ptr(); + } + self->GetJniEnv()->SetLongField(thread_peer, + WellKnownClasses::java_lang_Thread_nativePeer, + reinterpret_cast<jlong>(self)); + return true; + }; + return Attach(thread_name, as_daemon, set_peer_action); +} + void Thread::CreatePeer(const char* name, bool as_daemon, jobject thread_group) { Runtime* runtime = Runtime::Current(); CHECK(runtime->IsStarted()); @@ -1929,7 +1961,11 @@ void Thread::Destroy() { jni::DecodeArtField(WellKnownClasses::java_lang_Thread_nativePeer) ->SetLong<false>(tlsPtr_.opeer, 0); } - Dbg::PostThreadDeath(self); + Runtime* runtime = Runtime::Current(); + if (runtime != nullptr) { + runtime->GetRuntimeCallbacks()->ThreadDeath(self); + } + // Thread.join() is implemented as an Object.wait() on the Thread.lock object. Signal anyone // who is waiting. diff --git a/runtime/thread.h b/runtime/thread.h index 2b451bcaee..b609e723e9 100644 --- a/runtime/thread.h +++ b/runtime/thread.h @@ -158,6 +158,8 @@ class Thread { // Used to implement JNI AttachCurrentThread and AttachCurrentThreadAsDaemon calls. static Thread* Attach(const char* thread_name, bool as_daemon, jobject thread_group, bool create_peer); + // Attaches the calling native thread to the runtime, returning the new native peer. + static Thread* Attach(const char* thread_name, bool as_daemon, jobject thread_peer); // Reset internal state of child thread after fork. void InitAfterFork(); @@ -1166,6 +1168,13 @@ class Thread { ~Thread() REQUIRES(!Locks::mutator_lock_, !Locks::thread_suspend_count_lock_); void Destroy(); + // Attaches the calling native thread to the runtime, returning the new native peer. + // Used to implement JNI AttachCurrentThread and AttachCurrentThreadAsDaemon calls. + template <typename PeerAction> + static Thread* Attach(const char* thread_name, + bool as_daemon, + PeerAction p); + void CreatePeer(const char* name, bool as_daemon, jobject thread_group); template<bool kTransactionActive> @@ -1704,6 +1713,14 @@ class ScopedTransitioningToRunnable : public ValueObject { Thread* const self_; }; +class ThreadLifecycleCallback { + public: + virtual ~ThreadLifecycleCallback() {} + + virtual void ThreadStart(Thread* self) REQUIRES_SHARED(Locks::mutator_lock_) = 0; + virtual void ThreadDeath(Thread* self) REQUIRES_SHARED(Locks::mutator_lock_) = 0; +}; + std::ostream& operator<<(std::ostream& os, const Thread& thread); std::ostream& operator<<(std::ostream& os, const StackedShadowFrameType& thread); diff --git a/runtime/thread_list.cc b/runtime/thread_list.cc index c5c7e2cc16..01c940e9df 100644 --- a/runtime/thread_list.cc +++ b/runtime/thread_list.cc @@ -57,7 +57,6 @@ namespace art { using android::base::StringPrintf; static constexpr uint64_t kLongThreadSuspendThreshold = MsToNs(5); -static constexpr uint64_t kThreadSuspendTimeoutMs = 30 * 1000; // 30s. // Use 0 since we want to yield to prevent blocking for an unpredictable amount of time. static constexpr useconds_t kThreadSuspendInitialSleepUs = 0; static constexpr useconds_t kThreadSuspendMaxYieldUs = 3000; @@ -68,12 +67,13 @@ static constexpr useconds_t kThreadSuspendMaxSleepUs = 5000; // Turned off again. b/29248079 static constexpr bool kDumpUnattachedThreadNativeStackForSigQuit = false; -ThreadList::ThreadList() +ThreadList::ThreadList(uint64_t thread_suspend_timeout_ns) : suspend_all_count_(0), debug_suspend_all_count_(0), unregistering_count_(0), suspend_all_historam_("suspend all histogram", 16, 64), long_suspend_(false), + thread_suspend_timeout_ns_(thread_suspend_timeout_ns), empty_checkpoint_barrier_(new Barrier(0)) { CHECK(Monitor::IsValidLockWord(LockWord::FromThinLockId(kMaxThreadId, 1, 0U))); } @@ -554,12 +554,14 @@ void ThreadList::SuspendAll(const char* cause, bool long_suspend) { // Make sure this thread grabs exclusive access to the mutator lock and its protected data. #if HAVE_TIMED_RWLOCK while (true) { - if (Locks::mutator_lock_->ExclusiveLockWithTimeout(self, kThreadSuspendTimeoutMs, 0)) { + if (Locks::mutator_lock_->ExclusiveLockWithTimeout(self, + NsToMs(thread_suspend_timeout_ns_), + 0)) { break; } else if (!long_suspend_) { // Reading long_suspend without the mutator lock is slightly racy, in some rare cases, this // could result in a thread suspend timeout. - // Timeout if we wait more than kThreadSuspendTimeoutMs seconds. + // Timeout if we wait more than thread_suspend_timeout_ns_ nanoseconds. UnsafeLogFatalForThreadSuspendAllTimeout(); } } @@ -653,7 +655,7 @@ void ThreadList::SuspendAllInternal(Thread* self, // is done with a timeout so that we can detect problems. #if ART_USE_FUTEXES timespec wait_timeout; - InitTimeSpec(false, CLOCK_MONOTONIC, kIsDebugBuild ? 50000 : 10000, 0, &wait_timeout); + InitTimeSpec(false, CLOCK_MONOTONIC, NsToMs(thread_suspend_timeout_ns_), 0, &wait_timeout); #endif const uint64_t start_time = NanoTime(); while (true) { @@ -863,7 +865,7 @@ Thread* ThreadList::SuspendThreadByPeer(jobject peer, return thread; } const uint64_t total_delay = NanoTime() - start_time; - if (total_delay >= MsToNs(kThreadSuspendTimeoutMs)) { + if (total_delay >= thread_suspend_timeout_ns_) { ThreadSuspendByPeerWarning(self, ::android::base::FATAL, "Thread suspension timed out", @@ -969,7 +971,7 @@ Thread* ThreadList::SuspendThreadByThreadId(uint32_t thread_id, return thread; } const uint64_t total_delay = NanoTime() - start_time; - if (total_delay >= MsToNs(kThreadSuspendTimeoutMs)) { + if (total_delay >= thread_suspend_timeout_ns_) { ThreadSuspendByThreadIdWarning(::android::base::WARNING, "Thread suspension timed out", thread_id); diff --git a/runtime/thread_list.h b/runtime/thread_list.h index 658db00ec3..b60fca1fdc 100644 --- a/runtime/thread_list.h +++ b/runtime/thread_list.h @@ -20,6 +20,7 @@ #include "barrier.h" #include "base/histogram.h" #include "base/mutex.h" +#include "base/time_utils.h" #include "base/value_object.h" #include "gc_root.h" #include "jni.h" @@ -41,11 +42,12 @@ class TimingLogger; class ThreadList { public: - static const uint32_t kMaxThreadId = 0xFFFF; - static const uint32_t kInvalidThreadId = 0; - static const uint32_t kMainThreadId = 1; + static constexpr uint32_t kMaxThreadId = 0xFFFF; + static constexpr uint32_t kInvalidThreadId = 0; + static constexpr uint32_t kMainThreadId = 1; + static constexpr uint64_t kDefaultThreadSuspendTimeout = MsToNs(kIsDebugBuild ? 50000 : 10000); - explicit ThreadList(); + explicit ThreadList(uint64_t thread_suspend_timeout_ns); ~ThreadList(); void DumpForSigQuit(std::ostream& os) @@ -219,6 +221,9 @@ class ThreadList { // Whether or not the current thread suspension is long. bool long_suspend_; + // Thread suspension timeout in nanoseconds. + const uint64_t thread_suspend_timeout_ns_; + std::unique_ptr<Barrier> empty_checkpoint_barrier_; friend class Thread; diff --git a/runtime/trace.cc b/runtime/trace.cc index 9d9360e9cb..2add955f8e 100644 --- a/runtime/trace.cc +++ b/runtime/trace.cc @@ -54,6 +54,7 @@ static constexpr size_t TraceActionBits = MinimumBitsToStore( static_cast<size_t>(kTraceMethodActionMask)); static constexpr uint8_t kOpNewMethod = 1U; static constexpr uint8_t kOpNewThread = 2U; +static constexpr uint8_t kOpTraceSummary = 3U; class BuildStackTraceVisitor : public StackVisitor { public: @@ -700,20 +701,19 @@ void Trace::FinishTracing() { std::string header(os.str()); if (trace_output_mode_ == TraceOutputMode::kStreaming) { - File file(streaming_file_name_ + ".sec", O_CREAT | O_WRONLY, true); - if (!file.IsOpened()) { - LOG(WARNING) << "Could not open secondary trace file!"; - return; - } - if (!file.WriteFully(header.c_str(), header.length())) { - file.Erase(); - std::string detail(StringPrintf("Trace data write failed: %s", strerror(errno))); - PLOG(ERROR) << detail; - ThrowRuntimeException("%s", detail.c_str()); - } - if (file.FlushCloseOrErase() != 0) { - PLOG(ERROR) << "Could not write secondary file"; - } + MutexLock mu(Thread::Current(), *streaming_lock_); // To serialize writing. + // Write a special token to mark the end of trace records and the start of + // trace summary. + uint8_t buf[7]; + Append2LE(buf, 0); + buf[2] = kOpTraceSummary; + Append4LE(buf + 3, static_cast<uint32_t>(header.length())); + WriteToBuf(buf, sizeof(buf)); + // Write the trace summary. The summary is identical to the file header when + // the output mode is not streaming (except for methods). + WriteToBuf(reinterpret_cast<const uint8_t*>(header.c_str()), header.length()); + // Flush the buffer, which may include some trace records before the summary. + FlushBuf(); } else { if (trace_file_.get() == nullptr) { iovec iov[2]; @@ -894,6 +894,14 @@ void Trace::WriteToBuf(const uint8_t* src, size_t src_size) { memcpy(buf_.get() + old_offset, src, src_size); } +void Trace::FlushBuf() { + int32_t offset = cur_offset_.LoadRelaxed(); + if (!trace_file_->WriteFully(buf_.get(), offset)) { + PLOG(WARNING) << "Failed flush the remaining data in streaming."; + } + cur_offset_.StoreRelease(0); +} + void Trace::LogMethodTraceEvent(Thread* thread, ArtMethod* method, instrumentation::Instrumentation::InstrumentationEvent event, uint32_t thread_clock_diff, uint32_t wall_clock_diff) { diff --git a/runtime/trace.h b/runtime/trace.h index 824b15003a..485e9a133a 100644 --- a/runtime/trace.h +++ b/runtime/trace.h @@ -202,7 +202,8 @@ class Trace FINAL : public instrumentation::InstrumentationListener { // This causes the negative annotations to incorrectly have a false positive. TODO: Figure out // how to annotate this. NO_THREAD_SAFETY_ANALYSIS; - void FinishTracing() REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(!*unique_methods_lock_); + void FinishTracing() + REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(!*unique_methods_lock_, !*streaming_lock_); void ReadClocks(Thread* thread, uint32_t* thread_clock_diff, uint32_t* wall_clock_diff); @@ -229,6 +230,9 @@ class Trace FINAL : public instrumentation::InstrumentationListener { // annotation. void WriteToBuf(const uint8_t* src, size_t src_size) REQUIRES(streaming_lock_); + // Flush the main buffer to file. Used for streaming. Exposed here for lock annotation. + void FlushBuf() + REQUIRES(streaming_lock_); uint32_t EncodeTraceMethod(ArtMethod* method) REQUIRES(!*unique_methods_lock_); uint32_t EncodeTraceMethodAndAction(ArtMethod* method, TraceAction action) diff --git a/runtime/utils.cc b/runtime/utils.cc index 8867743ec1..80a427b1e7 100644 --- a/runtime/utils.cc +++ b/runtime/utils.cc @@ -415,6 +415,22 @@ std::string PrintableString(const char* utf) { return result; } +std::string GetJniShortName(const std::string& class_descriptor, const std::string& method) { + // Remove the leading 'L' and trailing ';'... + std::string class_name(class_descriptor); + CHECK_EQ(class_name[0], 'L') << class_name; + CHECK_EQ(class_name[class_name.size() - 1], ';') << class_name; + class_name.erase(0, 1); + class_name.erase(class_name.size() - 1, 1); + + std::string short_name; + short_name += "Java_"; + short_name += MangleForJni(class_name); + short_name += "_"; + short_name += MangleForJni(method); + return short_name; +} + // See http://java.sun.com/j2se/1.5.0/docs/guide/jni/spec/design.html#wp615 for the full rules. std::string MangleForJni(const std::string& s) { std::string result; @@ -788,49 +804,58 @@ void GetTaskStats(pid_t tid, char* state, int* utime, int* stime, int* task_cpu) *task_cpu = strtoull(fields[36].c_str(), nullptr, 10); } -const char* GetAndroidRoot() { - const char* android_root = getenv("ANDROID_ROOT"); - if (android_root == nullptr) { - if (OS::DirectoryExists("/system")) { - android_root = "/system"; +static const char* GetAndroidDirSafe(const char* env_var, + const char* default_dir, + std::string* error_msg) { + const char* android_dir = getenv(env_var); + if (android_dir == nullptr) { + if (OS::DirectoryExists(default_dir)) { + android_dir = default_dir; } else { - LOG(FATAL) << "ANDROID_ROOT not set and /system does not exist"; - return ""; + *error_msg = StringPrintf("%s not set and %s does not exist", env_var, default_dir); + return nullptr; } } - if (!OS::DirectoryExists(android_root)) { - LOG(FATAL) << "Failed to find ANDROID_ROOT directory " << android_root; - return ""; + if (!OS::DirectoryExists(android_dir)) { + *error_msg = StringPrintf("Failed to find %s directory %s", env_var, android_dir); + return nullptr; } - return android_root; + return android_dir; } -const char* GetAndroidData() { +const char* GetAndroidDir(const char* env_var, const char* default_dir) { std::string error_msg; - const char* dir = GetAndroidDataSafe(&error_msg); + const char* dir = GetAndroidDirSafe(env_var, default_dir, &error_msg); if (dir != nullptr) { return dir; } else { LOG(FATAL) << error_msg; - return ""; + return nullptr; } } +const char* GetAndroidRoot() { + return GetAndroidDir("ANDROID_ROOT", "/system"); +} + +const char* GetAndroidRootSafe(std::string* error_msg) { + return GetAndroidDirSafe("ANDROID_ROOT", "/system", error_msg); +} + +const char* GetAndroidData() { + return GetAndroidDir("ANDROID_DATA", "/data"); +} + const char* GetAndroidDataSafe(std::string* error_msg) { - const char* android_data = getenv("ANDROID_DATA"); - if (android_data == nullptr) { - if (OS::DirectoryExists("/data")) { - android_data = "/data"; - } else { - *error_msg = "ANDROID_DATA not set and /data does not exist"; - return nullptr; - } - } - if (!OS::DirectoryExists(android_data)) { - *error_msg = StringPrintf("Failed to find ANDROID_DATA directory %s", android_data); - return nullptr; + return GetAndroidDirSafe("ANDROID_DATA", "/data", error_msg); +} + +std::string GetDefaultBootImageLocation(std::string* error_msg) { + const char* android_root = GetAndroidRootSafe(error_msg); + if (android_root == nullptr) { + return ""; } - return android_data; + return StringPrintf("%s/framework/boot.art", android_root); } void GetDalvikCache(const char* subdir, const bool create_if_absent, std::string* dalvik_cache, diff --git a/runtime/utils.h b/runtime/utils.h index 16ef706159..5f53608f65 100644 --- a/runtime/utils.h +++ b/runtime/utils.h @@ -101,6 +101,8 @@ std::string PrettySize(int64_t size_in_bytes); // of the JNI spec. std::string MangleForJni(const std::string& s); +std::string GetJniShortName(const std::string& class_name, const std::string& method_name); + // Turn "java.lang.String" into "Ljava/lang/String;". std::string DotToDescriptor(const char* class_name); @@ -143,12 +145,18 @@ void SetThreadName(const char* thread_name); // Find $ANDROID_ROOT, /system, or abort. const char* GetAndroidRoot(); +// Find $ANDROID_ROOT, /system, or return null. +const char* GetAndroidRootSafe(std::string* error_msg); // Find $ANDROID_DATA, /data, or abort. const char* GetAndroidData(); // Find $ANDROID_DATA, /data, or return null. const char* GetAndroidDataSafe(std::string* error_msg); +// Returns the default boot image location (ANDROID_ROOT/framework/boot.art). +// Returns an empty string if ANDROID_ROOT is not set. +std::string GetDefaultBootImageLocation(std::string* error_msg); + // Returns the dalvik-cache location, with subdir appended. Returns the empty string if the cache // could not be found. std::string GetDalvikCache(const char* subdir); diff --git a/runtime/vdex_file.h b/runtime/vdex_file.h index bb9844af65..330b955c2a 100644 --- a/runtime/vdex_file.h +++ b/runtime/vdex_file.h @@ -61,7 +61,7 @@ class VdexFile { private: static constexpr uint8_t kVdexMagic[] = { 'v', 'd', 'e', 'x' }; - static constexpr uint8_t kVdexVersion[] = { '0', '0', '2', '\0' }; // Handle verify-profile + static constexpr uint8_t kVdexVersion[] = { '0', '0', '3', '\0' }; // Remove verify-profile uint8_t magic_[4]; uint8_t version_[4]; diff --git a/runtime/verifier/verifier_deps.cc b/runtime/verifier/verifier_deps.cc index 15cc566cc6..113160785c 100644 --- a/runtime/verifier/verifier_deps.cc +++ b/runtime/verifier/verifier_deps.cc @@ -963,20 +963,25 @@ bool VerifierDeps::VerifyFields(Handle<mirror::ClassLoader> class_loader, // Check recorded fields are resolved the same way, have the same recorded class, // and have the same recorded flags. ClassLinker* class_linker = Runtime::Current()->GetClassLinker(); - StackHandleScope<1> hs(self); - Handle<mirror::DexCache> dex_cache( - hs.NewHandle(class_linker->FindDexCache(self, dex_file, /* allow_failure */ false))); for (const auto& entry : fields) { - ArtField* field = class_linker->ResolveFieldJLS( - dex_file, entry.GetDexFieldIndex(), dex_cache, class_loader); - - if (field == nullptr) { - DCHECK(self->IsExceptionPending()); - self->ClearException(); + const DexFile::FieldId& field_id = dex_file.GetFieldId(entry.GetDexFieldIndex()); + StringPiece name(dex_file.StringDataByIdx(field_id.name_idx_)); + StringPiece type(dex_file.StringDataByIdx(dex_file.GetTypeId(field_id.type_idx_).descriptor_idx_)); + // Only use field_id.class_idx_ when the entry is unresolved, which is rare. + // Otherwise, we might end up resolving an application class, which is expensive. + std::string expected_decl_klass = entry.IsResolved() + ? GetStringFromId(dex_file, entry.GetDeclaringClassIndex()) + : dex_file.StringByTypeIdx(field_id.class_idx_); + mirror::Class* cls = FindClassAndClearException( + class_linker, self, expected_decl_klass.c_str(), class_loader); + if (cls == nullptr) { + LOG(INFO) << "VerifierDeps: Could not resolve class " << expected_decl_klass; + return false; } + DCHECK(cls->IsResolved()); + ArtField* field = mirror::Class::FindField(self, cls, name, type); if (entry.IsResolved()) { - std::string expected_decl_klass = GetStringFromId(dex_file, entry.GetDeclaringClassIndex()); std::string temp; if (field == nullptr) { LOG(INFO) << "VerifierDeps: Could not resolve field " @@ -1025,11 +1030,16 @@ bool VerifierDeps::VerifyMethods(Handle<mirror::ClassLoader> class_loader, const char* name = dex_file.GetMethodName(method_id); const Signature signature = dex_file.GetMethodSignature(method_id); - const char* descriptor = dex_file.GetMethodDeclaringClassDescriptor(method_id); - - mirror::Class* cls = FindClassAndClearException(class_linker, self, descriptor, class_loader); + // Only use method_id.class_idx_ when the entry is unresolved, which is rare. + // Otherwise, we might end up resolving an application class, which is expensive. + std::string expected_decl_klass = entry.IsResolved() + ? GetStringFromId(dex_file, entry.GetDeclaringClassIndex()) + : dex_file.StringByTypeIdx(method_id.class_idx_); + + mirror::Class* cls = FindClassAndClearException( + class_linker, self, expected_decl_klass.c_str(), class_loader); if (cls == nullptr) { - LOG(INFO) << "VerifierDeps: Could not resolve class " << descriptor; + LOG(INFO) << "VerifierDeps: Could not resolve class " << expected_decl_klass; return false; } DCHECK(cls->IsResolved()); @@ -1045,7 +1055,6 @@ bool VerifierDeps::VerifyMethods(Handle<mirror::ClassLoader> class_loader, if (entry.IsResolved()) { std::string temp; - std::string expected_decl_klass = GetStringFromId(dex_file, entry.GetDeclaringClassIndex()); if (method == nullptr) { LOG(INFO) << "VerifierDeps: Could not resolve " << kind diff --git a/runtime/well_known_classes.cc b/runtime/well_known_classes.cc index 507ea165e5..2610252aa7 100644 --- a/runtime/well_known_classes.cc +++ b/runtime/well_known_classes.cc @@ -51,7 +51,6 @@ jclass WellKnownClasses::java_lang_ClassLoader; jclass WellKnownClasses::java_lang_ClassNotFoundException; jclass WellKnownClasses::java_lang_Daemons; jclass WellKnownClasses::java_lang_Error; -jclass WellKnownClasses::java_lang_ExceptionInInitializerError; jclass WellKnownClasses::java_lang_invoke_MethodHandle; jclass WellKnownClasses::java_lang_IllegalAccessError; jclass WellKnownClasses::java_lang_NoClassDefFoundError; @@ -290,7 +289,6 @@ void WellKnownClasses::Init(JNIEnv* env) { java_lang_Object = CacheClass(env, "java/lang/Object"); java_lang_OutOfMemoryError = CacheClass(env, "java/lang/OutOfMemoryError"); java_lang_Error = CacheClass(env, "java/lang/Error"); - java_lang_ExceptionInInitializerError = CacheClass(env, "java/lang/ExceptionInInitializerError"); java_lang_IllegalAccessError = CacheClass(env, "java/lang/IllegalAccessError"); java_lang_invoke_MethodHandle = CacheClass(env, "java/lang/invoke/MethodHandle"); java_lang_NoClassDefFoundError = CacheClass(env, "java/lang/NoClassDefFoundError"); diff --git a/runtime/well_known_classes.h b/runtime/well_known_classes.h index b3ce3d1597..db8a53c44c 100644 --- a/runtime/well_known_classes.h +++ b/runtime/well_known_classes.h @@ -61,7 +61,6 @@ struct WellKnownClasses { static jclass java_lang_ClassNotFoundException; static jclass java_lang_Daemons; static jclass java_lang_Error; - static jclass java_lang_ExceptionInInitializerError; static jclass java_lang_IllegalAccessError; static jclass java_lang_invoke_MethodHandle; static jclass java_lang_NoClassDefFoundError; diff --git a/test/008-exceptions/expected.txt b/test/008-exceptions/expected.txt index 083ecf7ced..fcf2ef49f3 100644 --- a/test/008-exceptions/expected.txt +++ b/test/008-exceptions/expected.txt @@ -1,11 +1,11 @@ Got an NPE: second throw java.lang.NullPointerException: second throw - at Main.catchAndRethrow(Main.java:77) - at Main.exceptions_007(Main.java:59) - at Main.main(Main.java:67) + at Main.catchAndRethrow(Main.java:94) + at Main.exceptions_007(Main.java:74) + at Main.main(Main.java:82) Caused by: java.lang.NullPointerException: first throw - at Main.throwNullPointerException(Main.java:84) - at Main.catchAndRethrow(Main.java:74) + at Main.throwNullPointerException(Main.java:101) + at Main.catchAndRethrow(Main.java:91) ... 2 more Static Init BadError: This is bad by convention: BadInit @@ -15,3 +15,11 @@ Static BadInitNoStringInit BadErrorNoStringInit: This is bad by convention java.lang.NoClassDefFoundError: BadInitNoStringInit BadErrorNoStringInit: This is bad by convention +BadSuperClass Static Init +BadError: This is bad by convention: BadInit +MultiDexBadInit Static Init +java.lang.Error: MultiDexBadInit +java.lang.NoClassDefFoundError: MultiDexBadInit + cause: java.lang.Error: MultiDexBadInit +java.lang.NoClassDefFoundError: MultiDexBadInit + cause: java.lang.Error: MultiDexBadInit diff --git a/test/008-exceptions/multidex.jpp b/test/008-exceptions/multidex.jpp new file mode 100644 index 0000000000..a3746f5149 --- /dev/null +++ b/test/008-exceptions/multidex.jpp @@ -0,0 +1,27 @@ +BadError: + @@com.android.jack.annotations.ForceInMainDex + class BadError +BadInit: + @@com.android.jack.annotations.ForceInMainDex + class BadInit +BadErrorNoStringInit: + @@com.android.jack.annotations.ForceInMainDex + class BadErrorNoStringInit +BadInitNoStringInit: + @@com.android.jack.annotations.ForceInMainDex + class BadInitNoStringInit +BadSuperClass: + @@com.android.jack.annotations.ForceInMainDex + class BadSuperClass +DerivedFromBadSuperClass: + @@com.android.jack.annotations.ForceInMainDex + class DerivedFromBadSuperClass +Main: + @@com.android.jack.annotations.ForceInMainDex + class Main +MultiDexBadInit: + @@com.android.jack.annotations.ForceInMainDex + class MultiDexBadInit +MultiDexBadInitWrapper1: + @@com.android.jack.annotations.ForceInMainDex + class MultiDexBadInitWrapper1 diff --git a/test/008-exceptions/src-multidex/MultiDexBadInitWrapper2.java b/test/008-exceptions/src-multidex/MultiDexBadInitWrapper2.java new file mode 100644 index 0000000000..f3953bd4c7 --- /dev/null +++ b/test/008-exceptions/src-multidex/MultiDexBadInitWrapper2.java @@ -0,0 +1,24 @@ +/* + * 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. + */ + +class MultiDexBadInitWrapper2 { + public static void setDummy(int value) { + if (doThrow) { throw new Error(); } + MultiDexBadInit.dummy = value; + } + + public static boolean doThrow = false; +} diff --git a/test/008-exceptions/src/Main.java b/test/008-exceptions/src/Main.java index b8231f12bd..74af00ccf7 100644 --- a/test/008-exceptions/src/Main.java +++ b/test/008-exceptions/src/Main.java @@ -50,6 +50,21 @@ class BadInitNoStringInit { } } +// A class that throws BadError during static initialization, serving as a super class. +class BadSuperClass { + static int dummy; + static { + System.out.println("BadSuperClass Static Init"); + if (true) { + throw new BadError("BadInit"); + } + } +} + +// A class that derives from BadSuperClass. +class DerivedFromBadSuperClass extends BadSuperClass { +} + /** * Exceptions across method calls */ @@ -63,10 +78,12 @@ public class Main { npe.printStackTrace(System.out); } } - public static void main (String args[]) { + public static void main(String args[]) { exceptions_007(); exceptionsRethrowClassInitFailure(); exceptionsRethrowClassInitFailureNoStringInit(); + exceptionsForSuperClassInitFailure(); + exceptionsInMultiDex(); } private static void catchAndRethrow() { @@ -129,4 +146,70 @@ public class Main { error.printStackTrace(System.out); } } + + private static void exceptionsForSuperClassInitFailure() { + try { + // Resolve DerivedFromBadSuperClass. + BadSuperClass.dummy = 1; + throw new IllegalStateException("Should not reach here."); + } catch (BadError e) { + System.out.println(e); + } catch (Throwable t) { + t.printStackTrace(); + } + try { + // Before splitting mirror::Class::kStatusError into + // kStatusErrorUnresolved and kStatusErrorResolved, + // this would trigger a + // CHECK(super_class->IsResolved()) + // failure in + // ClassLinker::LoadSuperAndInterfaces(). + // After the change we're getting either VerifyError + // (for Optimizing) or NoClassDefFoundError wrapping + // BadError (for interpreter or JIT). + new DerivedFromBadSuperClass(); + throw new IllegalStateException("Should not reach here."); + } catch (NoClassDefFoundError ncdfe) { + if (!(ncdfe.getCause() instanceof BadError)) { + ncdfe.getCause().printStackTrace(); + } + } catch (VerifyError e) { + } catch (Throwable t) { + t.printStackTrace(); + } + } + + private static void exceptionsInMultiDex() { + try { + MultiDexBadInit.dummy = 1; + throw new IllegalStateException("Should not reach here."); + } catch (Error e) { + System.out.println(e); + } catch (Throwable t) { + t.printStackTrace(); + } + // Before splitting mirror::Class::kStatusError into + // kStatusErrorUnresolved and kStatusErrorResolved, + // the exception from wrapper 1 would have been + // wrapped in NoClassDefFoundError but the exception + // from wrapper 2 would have been unwrapped. + try { + MultiDexBadInitWrapper1.setDummy(1); + throw new IllegalStateException("Should not reach here."); + } catch (NoClassDefFoundError ncdfe) { + System.out.println(ncdfe); + System.out.println(" cause: " + ncdfe.getCause()); + } catch (Throwable t) { + t.printStackTrace(); + } + try { + MultiDexBadInitWrapper2.setDummy(1); + throw new IllegalStateException("Should not reach here."); + } catch (NoClassDefFoundError ncdfe) { + System.out.println(ncdfe); + System.out.println(" cause: " + ncdfe.getCause()); + } catch (Throwable t) { + t.printStackTrace(); + } + } } diff --git a/test/008-exceptions/src/MultiDexBadInit.java b/test/008-exceptions/src/MultiDexBadInit.java new file mode 100644 index 0000000000..e3ebb9ca45 --- /dev/null +++ b/test/008-exceptions/src/MultiDexBadInit.java @@ -0,0 +1,25 @@ +/* + * 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. + */ + +class MultiDexBadInit { + static int dummy; + static { + System.out.println("MultiDexBadInit Static Init"); + if (true) { + throw new Error("MultiDexBadInit"); + } + } +} diff --git a/test/008-exceptions/src/MultiDexBadInitWrapper1.java b/test/008-exceptions/src/MultiDexBadInitWrapper1.java new file mode 100644 index 0000000000..059e6a3d7d --- /dev/null +++ b/test/008-exceptions/src/MultiDexBadInitWrapper1.java @@ -0,0 +1,24 @@ +/* + * 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. + */ + +class MultiDexBadInitWrapper1 { + public static void setDummy(int value) { + if (doThrow) { throw new Error(); } + MultiDexBadInit.dummy = value; + } + + public static boolean doThrow = false; +} diff --git a/test/142-classloader2/expected.txt b/test/142-classloader2/expected.txt index 86f5e220e2..056d9785de 100644 --- a/test/142-classloader2/expected.txt +++ b/test/142-classloader2/expected.txt @@ -1 +1,5 @@ +Loaded class B. +Caught VerifyError. +Loaded class B. +Caught wrapped VerifyError. Everything OK. diff --git a/test/142-classloader2/src/Main.java b/test/142-classloader2/src/Main.java index 80b00e7dd2..a0c77645a3 100644 --- a/test/142-classloader2/src/Main.java +++ b/test/142-classloader2/src/Main.java @@ -74,16 +74,25 @@ public class Main { // Try to load a dex file with bad dex code. Use new instance to force verification. try { Class<?> badClass = Main.class.getClassLoader().loadClass("B"); + System.out.println("Loaded class B."); badClass.newInstance(); - System.out.println("Should not be able to load class from bad dex file."); + System.out.println("Should not be able to instantiate B with bad dex bytecode."); } catch (VerifyError e) { + System.out.println("Caught VerifyError."); } // Make sure the same error is rethrown when reloading the bad class. try { Class<?> badClass = Main.class.getClassLoader().loadClass("B"); - System.out.println("Should not be able to load class from bad dex file."); - } catch (VerifyError e) { + System.out.println("Loaded class B."); + badClass.newInstance(); + System.out.println("Should not be able to instantiate B with bad dex bytecode."); + } catch (NoClassDefFoundError e) { + if (e.getCause() instanceof VerifyError) { + System.out.println("Caught wrapped VerifyError."); + } else { + e.printStackTrace(); + } } System.out.println("Everything OK."); diff --git a/test/154-gc-loop/expected.txt b/test/154-gc-loop/expected.txt new file mode 100644 index 0000000000..6106818c79 --- /dev/null +++ b/test/154-gc-loop/expected.txt @@ -0,0 +1,2 @@ +JNI_OnLoad called +Finalize count too large: false diff --git a/test/154-gc-loop/heap_interface.cc b/test/154-gc-loop/heap_interface.cc new file mode 100644 index 0000000000..8d610a87fa --- /dev/null +++ b/test/154-gc-loop/heap_interface.cc @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2015 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 "gc/heap.h" +#include "runtime.h" + +namespace art { +namespace { + +extern "C" JNIEXPORT void JNICALL Java_Main_backgroundProcessState(JNIEnv*, jclass) { + Runtime::Current()->UpdateProcessState(kProcessStateJankImperceptible); +} + +} // namespace +} // namespace art diff --git a/test/154-gc-loop/info.txt b/test/154-gc-loop/info.txt new file mode 100644 index 0000000000..f599db1d01 --- /dev/null +++ b/test/154-gc-loop/info.txt @@ -0,0 +1 @@ +Test that GC doesn't happen too often for a few small allocations. diff --git a/test/154-gc-loop/src/Main.java b/test/154-gc-loop/src/Main.java new file mode 100644 index 0000000000..3a256c109e --- /dev/null +++ b/test/154-gc-loop/src/Main.java @@ -0,0 +1,45 @@ +/* + * 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. + */ + +import java.lang.ref.WeakReference; + +public class Main { + static final class GcWatcher { + protected void finalize() throws Throwable { + watcher = new WeakReference<GcWatcher>(new GcWatcher()); + ++finalizeCounter; + } + } + static WeakReference<GcWatcher> watcher = new WeakReference<GcWatcher>(new GcWatcher()); + static Object o = new Object(); + static int finalizeCounter = 0; + + public static void main(String[] args) { + System.loadLibrary(args[0]); + backgroundProcessState(); + try { + Runtime.getRuntime().gc(); + for (int i = 0; i < 10; ++i) { + o = new Object(); + Thread.sleep(1000); + } + } catch (Exception e) {} + System.out.println("Finalize count too large: " + + ((finalizeCounter >= 10) ? Integer.toString(finalizeCounter) : "false")); + } + + private static native void backgroundProcessState(); +} diff --git a/test/564-checker-irreducible-loop/smali/IrreducibleLoop.smali b/test/564-checker-irreducible-loop/smali/IrreducibleLoop.smali index 75344f74bf..e4bf236266 100644 --- a/test/564-checker-irreducible-loop/smali/IrreducibleLoop.smali +++ b/test/564-checker-irreducible-loop/smali/IrreducibleLoop.smali @@ -17,10 +17,9 @@ .super Ljava/lang/Object; ## CHECK-START-X86: int IrreducibleLoop.simpleLoop(int) dead_code_elimination$initial (before) -## CHECK-DAG: <<Method:(i|j)\d+>> CurrentMethod ## CHECK-DAG: <<Constant:i\d+>> IntConstant 42 -## CHECK-DAG: InvokeStaticOrDirect [<<Constant>>,<<Method>>] loop:{{B\d+}} irreducible:true -## CHECK-DAG: InvokeStaticOrDirect [<<Constant>>,<<Method>>] loop:none +## CHECK-DAG: InvokeStaticOrDirect [<<Constant>>] loop:{{B\d+}} irreducible:true +## CHECK-DAG: InvokeStaticOrDirect [<<Constant>>] loop:none .method public static simpleLoop(I)I .registers 3 const/16 v0, 42 diff --git a/test/588-checker-irreducib-lifetime-hole/smali/IrreducibleLoop.smali b/test/588-checker-irreducib-lifetime-hole/smali/IrreducibleLoop.smali index 186f0ab3e8..9b8aa510a4 100644 --- a/test/588-checker-irreducib-lifetime-hole/smali/IrreducibleLoop.smali +++ b/test/588-checker-irreducib-lifetime-hole/smali/IrreducibleLoop.smali @@ -17,11 +17,10 @@ .super Ljava/lang/Object; ## CHECK-START-X86: int IrreducibleLoop.simpleLoop1(int) dead_code_elimination$initial (before) -## CHECK-DAG: <<Method:(i|j)\d+>> CurrentMethod ## CHECK-DAG: <<Constant:i\d+>> IntConstant 42 ## CHECK-DAG: Goto irreducible:true -## CHECK-DAG: InvokeStaticOrDirect [<<Constant>>,<<Method>>] loop:none -## CHECK-DAG: InvokeStaticOrDirect [{{i\d+}},<<Method>>] loop:none +## CHECK-DAG: InvokeStaticOrDirect [<<Constant>>] loop:none +## CHECK-DAG: InvokeStaticOrDirect [{{i\d+}}] loop:none .method public static simpleLoop1(I)I .registers 3 const/16 v0, 42 @@ -58,11 +57,10 @@ .end method ## CHECK-START-X86: int IrreducibleLoop.simpleLoop2(int) dead_code_elimination$initial (before) -## CHECK-DAG: <<Method:(i|j)\d+>> CurrentMethod ## CHECK-DAG: <<Constant:i\d+>> IntConstant 42 ## CHECK-DAG: Goto irreducible:true -## CHECK-DAG: InvokeStaticOrDirect [<<Constant>>,<<Method>>] loop:none -## CHECK-DAG: InvokeStaticOrDirect [{{i\d+}},<<Method>>] loop:none +## CHECK-DAG: InvokeStaticOrDirect [<<Constant>>] loop:none +## CHECK-DAG: InvokeStaticOrDirect [{{i\d+}}] loop:none .method public static simpleLoop2(I)I .registers 3 const/16 v0, 42 diff --git a/test/595-profile-saving/profile-saving.cc b/test/595-profile-saving/profile-saving.cc index bf3d812f94..0f8dd57385 100644 --- a/test/595-profile-saving/profile-saving.cc +++ b/test/595-profile-saving/profile-saving.cc @@ -17,7 +17,7 @@ #include "dex_file.h" #include "art_method-inl.h" -#include "jit/offline_profiling_info.h" +#include "jit/profile_compilation_info.h" #include "jit/profile_saver.h" #include "jni.h" #include "method_reference.h" diff --git a/test/596-monitor-inflation/expected.txt b/test/596-monitor-inflation/expected.txt new file mode 100644 index 0000000000..2add6966a0 --- /dev/null +++ b/test/596-monitor-inflation/expected.txt @@ -0,0 +1,6 @@ +JNI_OnLoad called +Monitor list grew by at least 4000 monitors +Monitor list shrank correctly +Finished first check +Finished second check +Total checks: 10000 diff --git a/test/596-monitor-inflation/info.txt b/test/596-monitor-inflation/info.txt new file mode 100644 index 0000000000..81dedb60aa --- /dev/null +++ b/test/596-monitor-inflation/info.txt @@ -0,0 +1,5 @@ +A simple test that forces many monitors to be inflated, while checking +that hashcodes are consistently maintained. + +This allocates more monitors and hence may exercise the monitor pool +differently, and with more context, than the monitor_pool_test gtest. diff --git a/test/596-monitor-inflation/monitor_inflation.cc b/test/596-monitor-inflation/monitor_inflation.cc new file mode 100644 index 0000000000..fb4275b711 --- /dev/null +++ b/test/596-monitor-inflation/monitor_inflation.cc @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "gc/heap.h" +#include "jni.h" +#include "monitor.h" +#include "runtime.h" +#include "thread-inl.h" + +namespace art { +namespace { + +extern "C" JNIEXPORT void JNICALL Java_Main_trim(JNIEnv*, jclass) { + Runtime::Current()->GetHeap()->Trim(Thread::Current()); +} + +extern "C" JNIEXPORT jint JNICALL Java_Main_monitorListSize(JNIEnv*, jclass) { + return Runtime::Current()->GetMonitorList()->Size(); +} + +} // namespace +} // namespace art diff --git a/test/596-monitor-inflation/src/Main.java b/test/596-monitor-inflation/src/Main.java new file mode 100644 index 0000000000..d97c7667e9 --- /dev/null +++ b/test/596-monitor-inflation/src/Main.java @@ -0,0 +1,79 @@ +/* + * 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 java.util.IdentityHashMap; +import dalvik.system.VMRuntime; + +public class Main { + public static void main(String[] args) { + System.loadLibrary(args[0]); + int initialSize = monitorListSize(); + IdentityHashMap<Object, Integer> all = new IdentityHashMap(); + for (int i = 0; i < 5000; ++i) { + Object obj = new Object(); + synchronized(obj) { + // Should force inflation. + all.put(obj, obj.hashCode()); + } + } + // Since monitor deflation is delayed significantly, we believe that even with an intervening + // GC, monitors should remain inflated. We allow some slop for unrelated concurrent runtime + // actions. + int inflatedSize = monitorListSize(); + if (inflatedSize >= initialSize + 4000) { + System.out.println("Monitor list grew by at least 4000 monitors"); + } else { + System.out.println("Monitor list did not grow as expected"); + } + // Encourage monitor deflation. + // trim() (Heap::Trim()) deflates only in JANK_IMPERCEPTIBLE state. + // Some of this mirrors code in ActivityThread.java. + final int DALVIK_PROCESS_STATE_JANK_PERCEPTIBLE = 0; + final int DALVIK_PROCESS_STATE_JANK_IMPERCEPTIBLE = 1; + VMRuntime.getRuntime().updateProcessState(DALVIK_PROCESS_STATE_JANK_IMPERCEPTIBLE); + System.gc(); + System.runFinalization(); + trim(); + VMRuntime.getRuntime().updateProcessState(DALVIK_PROCESS_STATE_JANK_PERCEPTIBLE); + int finalSize = monitorListSize(); + if (finalSize > initialSize + 1000) { + System.out.println("Monitor list failed to shrink properly"); + } else { + System.out.println("Monitor list shrank correctly"); + } + int j = 0; + for (Object obj: all.keySet()) { + ++j; + if (obj.hashCode() != all.get(obj)) { + throw new AssertionError("Failed hashcode test!"); + } + } + System.out.println("Finished first check"); + for (Object obj: all.keySet()) { + ++j; + synchronized(obj) { + if (obj.hashCode() != all.get(obj)) { + throw new AssertionError("Failed hashcode test!"); + } + } + } + System.out.println("Finished second check"); + System.out.println("Total checks: " + j); + } + + private static native void trim(); + + private static native int monitorListSize(); +} diff --git a/test/900-hello-plugin/run b/test/900-hello-plugin/run index 35b08715a1..50835f89af 100755 --- a/test/900-hello-plugin/run +++ b/test/900-hello-plugin/run @@ -18,7 +18,5 @@ plugin=libartagentd.so if [[ "$@" == *"-O"* ]]; then plugin=libartagent.so fi -./default-run "$@" --experimental agents \ - --experimental runtime-plugins \ - --runtime-option -agentpath:${plugin}=test_900 \ +./default-run "$@" --runtime-option -agentpath:${plugin}=test_900 \ --android-runtime-option -Xplugin:${plugin} diff --git a/test/901-hello-ti-agent/basics.cc b/test/901-hello-ti-agent/basics.cc index 052fb9ac13..0b17656303 100644 --- a/test/901-hello-ti-agent/basics.cc +++ b/test/901-hello-ti-agent/basics.cc @@ -28,6 +28,46 @@ namespace art { namespace Test901HelloTi { +static void EnableEvent(jvmtiEnv* env, jvmtiEvent evt) { + jvmtiError error = env->SetEventNotificationMode(JVMTI_ENABLE, evt, nullptr); + if (error != JVMTI_ERROR_NONE) { + printf("Failed to enable event"); + } +} + +static void JNICALL VMStartCallback(jvmtiEnv *jenv ATTRIBUTE_UNUSED, + JNIEnv* jni_env ATTRIBUTE_UNUSED) { + printf("VMStart\n"); +} + +static void JNICALL VMInitCallback(jvmtiEnv *jvmti_env ATTRIBUTE_UNUSED, + JNIEnv* jni_env ATTRIBUTE_UNUSED, + jthread thread ATTRIBUTE_UNUSED) { + printf("VMInit\n"); +} + +static void JNICALL VMDeatchCallback(jvmtiEnv *jenv ATTRIBUTE_UNUSED, + JNIEnv* jni_env ATTRIBUTE_UNUSED) { + printf("VMDeath\n"); +} + + +static void InstallVMEvents(jvmtiEnv* env) { + jvmtiEventCallbacks callbacks; + memset(&callbacks, 0, sizeof(jvmtiEventCallbacks)); + callbacks.VMStart = VMStartCallback; + callbacks.VMInit = VMInitCallback; + callbacks.VMDeath = VMDeatchCallback; + jvmtiError ret = env->SetEventCallbacks(&callbacks, sizeof(callbacks)); + if (ret != JVMTI_ERROR_NONE) { + printf("Failed to install callbacks"); + } + + EnableEvent(env, JVMTI_EVENT_VM_START); + EnableEvent(env, JVMTI_EVENT_VM_INIT); + EnableEvent(env, JVMTI_EVENT_VM_DEATH); +} + jint OnLoad(JavaVM* vm, char* options ATTRIBUTE_UNUSED, void* reserved ATTRIBUTE_UNUSED) { @@ -72,6 +112,10 @@ jint OnLoad(JavaVM* vm, printf("Unexpected version number!\n"); return -1; } + + InstallVMEvents(env); + InstallVMEvents(env2); + CHECK_CALL_SUCCESS(env->DisposeEnvironment()); CHECK_CALL_SUCCESS(env2->DisposeEnvironment()); #undef CHECK_CALL_SUCCESS @@ -82,6 +126,19 @@ jint OnLoad(JavaVM* vm, } SetAllCapabilities(jvmti_env); + jvmtiPhase current_phase; + jvmtiError phase_result = jvmti_env->GetPhase(¤t_phase); + if (phase_result != JVMTI_ERROR_NONE) { + printf("Could not get phase"); + return 1; + } + if (current_phase != JVMTI_PHASE_ONLOAD) { + printf("Wrong phase"); + return 1; + } + + InstallVMEvents(jvmti_env); + return JNI_OK; } @@ -92,5 +149,15 @@ extern "C" JNIEXPORT void JNICALL Java_Main_setVerboseFlag( JvmtiErrorToException(env, result); } +extern "C" JNIEXPORT jboolean JNICALL Java_Main_checkLivePhase( + JNIEnv* env, jclass Main_klass ATTRIBUTE_UNUSED) { + jvmtiPhase current_phase; + jvmtiError phase_result = jvmti_env->GetPhase(¤t_phase); + if (JvmtiErrorToException(env, phase_result)) { + return JNI_FALSE; + } + return (current_phase == JVMTI_PHASE_LIVE) ? JNI_TRUE : JNI_FALSE; +} + } // namespace Test901HelloTi } // namespace art diff --git a/test/901-hello-ti-agent/expected.txt b/test/901-hello-ti-agent/expected.txt index 2aee99b25a..c4b24cba90 100644 --- a/test/901-hello-ti-agent/expected.txt +++ b/test/901-hello-ti-agent/expected.txt @@ -1,8 +1,12 @@ Loaded Agent for test 901-hello-ti-agent +VMStart +VMInit Hello, world! +Agent in live phase. 0 1 2 4 8 JVMTI_ERROR_ILLEGAL_ARGUMENT +VMDeath diff --git a/test/901-hello-ti-agent/run b/test/901-hello-ti-agent/run index 4379349cb2..c6e62ae6cd 100755 --- a/test/901-hello-ti-agent/run +++ b/test/901-hello-ti-agent/run @@ -14,6 +14,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -./default-run "$@" --experimental agents \ - --experimental runtime-plugins \ - --jvmti +./default-run "$@" --jvmti diff --git a/test/901-hello-ti-agent/src/Main.java b/test/901-hello-ti-agent/src/Main.java index 775e5c2e0c..4d62ed3f5d 100644 --- a/test/901-hello-ti-agent/src/Main.java +++ b/test/901-hello-ti-agent/src/Main.java @@ -16,10 +16,12 @@ public class Main { public static void main(String[] args) { - System.loadLibrary(args[1]); - System.out.println("Hello, world!"); + if (checkLivePhase()) { + System.out.println("Agent in live phase."); + } + set(0); // OTHER set(1); // GC set(2); // CLASS @@ -37,5 +39,6 @@ public class Main { } } + private static native boolean checkLivePhase(); private static native void setVerboseFlag(int flag, boolean value); } diff --git a/test/902-hello-transformation/run b/test/902-hello-transformation/run index 4379349cb2..c6e62ae6cd 100755 --- a/test/902-hello-transformation/run +++ b/test/902-hello-transformation/run @@ -14,6 +14,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -./default-run "$@" --experimental agents \ - --experimental runtime-plugins \ - --jvmti +./default-run "$@" --jvmti diff --git a/test/902-hello-transformation/src/Main.java b/test/902-hello-transformation/src/Main.java index ec4711954a..471c82ba28 100644 --- a/test/902-hello-transformation/src/Main.java +++ b/test/902-hello-transformation/src/Main.java @@ -49,7 +49,6 @@ public class Main { "AgAAABMCAAAAIAAAAQAAAB4CAAAAEAAAAQAAACwCAAA="); public static void main(String[] args) { - System.loadLibrary(args[1]); doTest(new Transform()); } diff --git a/test/903-hello-tagging/run b/test/903-hello-tagging/run index 4379349cb2..c6e62ae6cd 100755 --- a/test/903-hello-tagging/run +++ b/test/903-hello-tagging/run @@ -14,6 +14,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -./default-run "$@" --experimental agents \ - --experimental runtime-plugins \ - --jvmti +./default-run "$@" --jvmti diff --git a/test/903-hello-tagging/src/Main.java b/test/903-hello-tagging/src/Main.java index a8aedb410b..2f0365a921 100644 --- a/test/903-hello-tagging/src/Main.java +++ b/test/903-hello-tagging/src/Main.java @@ -20,7 +20,6 @@ import java.util.Arrays; public class Main { public static void main(String[] args) { - System.loadLibrary(args[1]); doTest(); testGetTaggedObjects(); } diff --git a/test/904-object-allocation/run b/test/904-object-allocation/run index 4379349cb2..c6e62ae6cd 100755 --- a/test/904-object-allocation/run +++ b/test/904-object-allocation/run @@ -14,6 +14,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -./default-run "$@" --experimental agents \ - --experimental runtime-plugins \ - --jvmti +./default-run "$@" --jvmti diff --git a/test/904-object-allocation/src/Main.java b/test/904-object-allocation/src/Main.java index fc8a112e49..df59179cc8 100644 --- a/test/904-object-allocation/src/Main.java +++ b/test/904-object-allocation/src/Main.java @@ -18,8 +18,6 @@ import java.util.ArrayList; public class Main { public static void main(String[] args) throws Exception { - System.loadLibrary(args[1]); - // Use a list to ensure objects must be allocated. ArrayList<Object> l = new ArrayList<>(100); diff --git a/test/905-object-free/run b/test/905-object-free/run index 4379349cb2..c6e62ae6cd 100755 --- a/test/905-object-free/run +++ b/test/905-object-free/run @@ -14,6 +14,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -./default-run "$@" --experimental agents \ - --experimental runtime-plugins \ - --jvmti +./default-run "$@" --jvmti diff --git a/test/905-object-free/src/Main.java b/test/905-object-free/src/Main.java index 16dec5d3e1..e41e378c19 100644 --- a/test/905-object-free/src/Main.java +++ b/test/905-object-free/src/Main.java @@ -19,8 +19,6 @@ import java.util.Arrays; public class Main { public static void main(String[] args) throws Exception { - System.loadLibrary(args[1]); - doTest(); } diff --git a/test/906-iterate-heap/run b/test/906-iterate-heap/run index 4379349cb2..c6e62ae6cd 100755 --- a/test/906-iterate-heap/run +++ b/test/906-iterate-heap/run @@ -14,6 +14,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -./default-run "$@" --experimental agents \ - --experimental runtime-plugins \ - --jvmti +./default-run "$@" --jvmti diff --git a/test/906-iterate-heap/src/Main.java b/test/906-iterate-heap/src/Main.java index 544a3656b2..cab27be4d2 100644 --- a/test/906-iterate-heap/src/Main.java +++ b/test/906-iterate-heap/src/Main.java @@ -19,8 +19,6 @@ import java.util.Collections; public class Main { public static void main(String[] args) throws Exception { - System.loadLibrary(args[1]); - doTest(); } diff --git a/test/907-get-loaded-classes/run b/test/907-get-loaded-classes/run index 4379349cb2..c6e62ae6cd 100755 --- a/test/907-get-loaded-classes/run +++ b/test/907-get-loaded-classes/run @@ -14,6 +14,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -./default-run "$@" --experimental agents \ - --experimental runtime-plugins \ - --jvmti +./default-run "$@" --jvmti diff --git a/test/907-get-loaded-classes/src/Main.java b/test/907-get-loaded-classes/src/Main.java index 468d037a52..370185a4d9 100644 --- a/test/907-get-loaded-classes/src/Main.java +++ b/test/907-get-loaded-classes/src/Main.java @@ -20,8 +20,6 @@ import java.util.HashSet; public class Main { public static void main(String[] args) throws Exception { - System.loadLibrary(args[1]); - doTest(); } diff --git a/test/908-gc-start-finish/run b/test/908-gc-start-finish/run index 4379349cb2..c6e62ae6cd 100755 --- a/test/908-gc-start-finish/run +++ b/test/908-gc-start-finish/run @@ -14,6 +14,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -./default-run "$@" --experimental agents \ - --experimental runtime-plugins \ - --jvmti +./default-run "$@" --jvmti diff --git a/test/908-gc-start-finish/src/Main.java b/test/908-gc-start-finish/src/Main.java index 2be0eea975..05388c917a 100644 --- a/test/908-gc-start-finish/src/Main.java +++ b/test/908-gc-start-finish/src/Main.java @@ -18,8 +18,6 @@ import java.util.ArrayList; public class Main { public static void main(String[] args) throws Exception { - System.loadLibrary(args[1]); - doTest(); } diff --git a/test/909-attach-agent/run b/test/909-attach-agent/run index 985341bd4f..0664592cd1 100755 --- a/test/909-attach-agent/run +++ b/test/909-attach-agent/run @@ -21,17 +21,11 @@ if [[ "$@" == *"-O"* ]]; then plugin=libopenjdkjvmti.so fi -./default-run "$@" --experimental agents \ - --experimental runtime-plugins \ - --android-runtime-option -Xplugin:${plugin} \ +./default-run "$@" --android-runtime-option -Xplugin:${plugin} \ --android-runtime-option -Xfully-deoptable \ --args agent:${agent}=909-attach-agent -./default-run "$@" --experimental agents \ - --experimental runtime-plugins \ - --android-runtime-option -Xfully-deoptable \ +./default-run "$@" --android-runtime-option -Xfully-deoptable \ --args agent:${agent}=909-attach-agent -./default-run "$@" --experimental agents \ - --experimental runtime-plugins \ - --args agent:${agent}=909-attach-agent +./default-run "$@" --args agent:${agent}=909-attach-agent diff --git a/test/910-methods/run b/test/910-methods/run index 4379349cb2..c6e62ae6cd 100755 --- a/test/910-methods/run +++ b/test/910-methods/run @@ -14,6 +14,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -./default-run "$@" --experimental agents \ - --experimental runtime-plugins \ - --jvmti +./default-run "$@" --jvmti diff --git a/test/910-methods/src/Main.java b/test/910-methods/src/Main.java index bf25a0d028..932a1ea414 100644 --- a/test/910-methods/src/Main.java +++ b/test/910-methods/src/Main.java @@ -20,8 +20,6 @@ import java.util.Arrays; public class Main { public static void main(String[] args) throws Exception { - System.loadLibrary(args[1]); - doTest(); } diff --git a/test/911-get-stack-trace/expected.txt b/test/911-get-stack-trace/expected.txt index dad08c9f97..2687f85b09 100644 --- a/test/911-get-stack-trace/expected.txt +++ b/test/911-get-stack-trace/expected.txt @@ -22,7 +22,7 @@ From top bar (IIILControlData;)J 0 24 foo (IIILControlData;)I 0 19 doTest ()V 38 23 - main ([Ljava/lang/String;)V 6 21 + main ([Ljava/lang/String;)V 3 21 --------- print (Ljava/lang/Thread;II)V 0 34 printOrWait (IILControlData;)V 6 39 @@ -42,7 +42,7 @@ From top bar (IIILControlData;)J 0 24 foo (IIILControlData;)I 0 19 doTest ()V 42 24 - main ([Ljava/lang/String;)V 6 21 + main ([Ljava/lang/String;)V 3 21 --------- getStackTrace (Ljava/lang/Thread;II)[[Ljava/lang/String; -1 -2 print (Ljava/lang/Thread;II)V 0 34 @@ -57,13 +57,13 @@ From top baz (IIILControlData;)Ljava/lang/Object; 9 32 From bottom --------- - main ([Ljava/lang/String;)V 6 21 + main ([Ljava/lang/String;)V 3 21 --------- baz (IIILControlData;)Ljava/lang/Object; 9 32 bar (IIILControlData;)J 0 24 foo (IIILControlData;)I 0 19 doTest ()V 65 30 - main ([Ljava/lang/String;)V 6 21 + main ([Ljava/lang/String;)V 3 21 --------- bar (IIILControlData;)J 0 24 foo (IIILControlData;)I 0 19 @@ -358,7 +358,7 @@ main getAllStackTraces (I)[[Ljava/lang/Object; -1 -2 printAll (I)V 0 73 doTest ()V 102 57 - main ([Ljava/lang/String;)V 30 33 + main ([Ljava/lang/String;)V 27 33 --------- FinalizerDaemon @@ -590,7 +590,7 @@ main getAllStackTraces (I)[[Ljava/lang/Object; -1 -2 printAll (I)V 0 73 doTest ()V 107 59 - main ([Ljava/lang/String;)V 30 33 + main ([Ljava/lang/String;)V 27 33 ######################################## @@ -659,7 +659,7 @@ main getThreadListStackTraces ([Ljava/lang/Thread;I)[[Ljava/lang/Object; -1 -2 printList ([Ljava/lang/Thread;I)V 0 66 doTest ()V 96 52 - main ([Ljava/lang/String;)V 38 37 + main ([Ljava/lang/String;)V 35 37 --------- Thread-14 @@ -771,7 +771,7 @@ main getThreadListStackTraces ([Ljava/lang/Thread;I)[[Ljava/lang/Object; -1 -2 printList ([Ljava/lang/Thread;I)V 0 66 doTest ()V 101 54 - main ([Ljava/lang/String;)V 38 37 + main ([Ljava/lang/String;)V 35 37 ################### @@ -782,7 +782,7 @@ JVMTI_ERROR_ILLEGAL_ARGUMENT [public static native java.lang.Object[] Frames.getFrameLocation(java.lang.Thread,int), ffffffff] [public static void Frames.doTestSameThread(), 38] [public static void Frames.doTest() throws java.lang.Exception, 0] -[public static void Main.main(java.lang.String[]) throws java.lang.Exception, 2e] +[public static void Main.main(java.lang.String[]) throws java.lang.Exception, 2b] JVMTI_ERROR_NO_MORE_FRAMES ################################ diff --git a/test/911-get-stack-trace/run b/test/911-get-stack-trace/run index 4379349cb2..c6e62ae6cd 100755 --- a/test/911-get-stack-trace/run +++ b/test/911-get-stack-trace/run @@ -14,6 +14,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -./default-run "$@" --experimental agents \ - --experimental runtime-plugins \ - --jvmti +./default-run "$@" --jvmti diff --git a/test/911-get-stack-trace/src/Main.java b/test/911-get-stack-trace/src/Main.java index b199033c32..96a427d77b 100644 --- a/test/911-get-stack-trace/src/Main.java +++ b/test/911-get-stack-trace/src/Main.java @@ -16,7 +16,7 @@ public class Main { public static void main(String[] args) throws Exception { - System.loadLibrary(args[1]); + bindTest911Classes(); SameThread.doTest(); @@ -42,4 +42,6 @@ public class Main { System.out.println("Done"); } + + private static native void bindTest911Classes(); } diff --git a/test/911-get-stack-trace/stack_trace.cc b/test/911-get-stack-trace/stack_trace.cc index d162e8a169..68f6d8dfb2 100644 --- a/test/911-get-stack-trace/stack_trace.cc +++ b/test/911-get-stack-trace/stack_trace.cc @@ -34,6 +34,14 @@ namespace Test911GetStackTrace { using android::base::StringPrintf; +extern "C" JNIEXPORT void JNICALL Java_Main_bindTest911Classes( + JNIEnv* env, jclass klass ATTRIBUTE_UNUSED) { + BindFunctions(jvmti_env, env, "AllTraces"); + BindFunctions(jvmti_env, env, "Frames"); + BindFunctions(jvmti_env, env, "PrintThread"); + BindFunctions(jvmti_env, env, "ThreadListTraces"); +} + static jint FindLineNumber(jint line_number_count, jvmtiLineNumberEntry* line_number_table, jlocation location) { diff --git a/test/912-classes/classes.cc b/test/912-classes/classes.cc index a22d1d72d8..d13436ebf6 100644 --- a/test/912-classes/classes.cc +++ b/test/912-classes/classes.cc @@ -20,6 +20,7 @@ #include "jni.h" #include "openjdkjvmti/jvmti.h" #include "ScopedLocalRef.h" +#include "thread-inl.h" #include "ti-agent/common_helper.h" #include "ti-agent/common_load.h" @@ -241,5 +242,138 @@ extern "C" JNIEXPORT jobjectArray JNICALL Java_Main_getClassLoaderClasses( return ret; } +extern "C" JNIEXPORT jintArray JNICALL Java_Main_getClassVersion( + JNIEnv* env, jclass Main_klass ATTRIBUTE_UNUSED, jclass klass) { + jint major, minor; + jvmtiError result = jvmti_env->GetClassVersionNumbers(klass, &minor, &major); + if (JvmtiErrorToException(env, result)) { + return nullptr; + } + + jintArray int_array = env->NewIntArray(2); + if (int_array == nullptr) { + return nullptr; + } + jint buf[2] = { major, minor }; + env->SetIntArrayRegion(int_array, 0, 2, buf); + + return int_array; +} + +static std::string GetClassName(jvmtiEnv* jenv, JNIEnv* jni_env, jclass klass) { + char* name; + jvmtiError result = jenv->GetClassSignature(klass, &name, nullptr); + if (result != JVMTI_ERROR_NONE) { + if (jni_env != nullptr) { + JvmtiErrorToException(jni_env, result); + } else { + printf("Failed to get class signature.\n"); + } + return ""; + } + + std::string tmp(name); + jenv->Deallocate(reinterpret_cast<unsigned char*>(name)); + + return tmp; +} + +static std::string GetThreadName(jvmtiEnv* jenv, JNIEnv* jni_env, jthread thread) { + jvmtiThreadInfo info; + jvmtiError result = jenv->GetThreadInfo(thread, &info); + if (result != JVMTI_ERROR_NONE) { + if (jni_env != nullptr) { + JvmtiErrorToException(jni_env, result); + } else { + printf("Failed to get thread name.\n"); + } + return ""; + } + + std::string tmp(info.name); + jenv->Deallocate(reinterpret_cast<unsigned char*>(info.name)); + jni_env->DeleteLocalRef(info.context_class_loader); + jni_env->DeleteLocalRef(info.thread_group); + + return tmp; +} + +static std::string GetThreadName(Thread* thread) { + std::string tmp; + thread->GetThreadName(tmp); + return tmp; +} + +static void JNICALL ClassPrepareCallback(jvmtiEnv* jenv, + JNIEnv* jni_env, + jthread thread, + jclass klass) { + std::string name = GetClassName(jenv, jni_env, klass); + if (name == "") { + return; + } + std::string thread_name = GetThreadName(jenv, jni_env, thread); + if (thread_name == "") { + return; + } + std::string cur_thread_name = GetThreadName(Thread::Current()); + printf("Prepare: %s on %s (cur=%s)\n", + name.c_str(), + thread_name.c_str(), + cur_thread_name.c_str()); +} + +static void JNICALL ClassLoadCallback(jvmtiEnv* jenv, + JNIEnv* jni_env, + jthread thread, + jclass klass) { + std::string name = GetClassName(jenv, jni_env, klass); + if (name == "") { + return; + } + std::string thread_name = GetThreadName(jenv, jni_env, thread); + if (thread_name == "") { + return; + } + printf("Load: %s on %s\n", name.c_str(), thread_name.c_str()); +} + +extern "C" JNIEXPORT void JNICALL Java_Main_enableClassLoadEvents( + JNIEnv* env, jclass Main_klass ATTRIBUTE_UNUSED, jboolean b) { + if (b == JNI_FALSE) { + jvmtiError ret = jvmti_env->SetEventNotificationMode(JVMTI_DISABLE, + JVMTI_EVENT_CLASS_LOAD, + nullptr); + if (JvmtiErrorToException(env, ret)) { + return; + } + ret = jvmti_env->SetEventNotificationMode(JVMTI_DISABLE, + JVMTI_EVENT_CLASS_PREPARE, + nullptr); + JvmtiErrorToException(env, ret); + return; + } + + jvmtiEventCallbacks callbacks; + memset(&callbacks, 0, sizeof(jvmtiEventCallbacks)); + callbacks.ClassLoad = ClassLoadCallback; + callbacks.ClassPrepare = ClassPrepareCallback; + jvmtiError ret = jvmti_env->SetEventCallbacks(&callbacks, sizeof(callbacks)); + if (JvmtiErrorToException(env, ret)) { + return; + } + + ret = jvmti_env->SetEventNotificationMode(JVMTI_ENABLE, + JVMTI_EVENT_CLASS_LOAD, + nullptr); + if (JvmtiErrorToException(env, ret)) { + return; + } + ret = jvmti_env->SetEventNotificationMode(JVMTI_ENABLE, + JVMTI_EVENT_CLASS_PREPARE, + nullptr); + JvmtiErrorToException(env, ret); +} + } // namespace Test912Classes } // namespace art diff --git a/test/912-classes/expected.txt b/test/912-classes/expected.txt index a95a465860..328216b324 100644 --- a/test/912-classes/expected.txt +++ b/test/912-classes/expected.txt @@ -29,7 +29,7 @@ int 100000 class [Ljava.lang.String; 10000 class java.lang.Object 111 class Main$TestForNonInit 11 -class Main$TestForInitFail 1001 +class Main$TestForInitFail 1011 int [] class [Ljava.lang.String; [] class java.lang.Object [] @@ -59,3 +59,35 @@ boot <- src (B) <- src-ex (A, List) boot <- src+src-ex (A,B) 912-classes.jar+ -> [class A, class B, class java.lang.Object] + +[37, 0] + +B, false +Load: LB; on main +Prepare: LB; on main (cur=main) +B, true +Load: LB; on main +Prepare: LB; on main (cur=main) +C, false +Load: LA; on main +Prepare: LA; on main (cur=main) +Load: LC; on main +Prepare: LC; on main (cur=main) +A, false +C, true +Load: LA; on main +Prepare: LA; on main (cur=main) +Load: LC; on main +Prepare: LC; on main (cur=main) +A, true +A, true +Load: LA; on main +Prepare: LA; on main (cur=main) +C, true +Load: LC; on main +Prepare: LC; on main (cur=main) +C, true +Load: LA; on TestRunner +Prepare: LA; on TestRunner (cur=TestRunner) +Load: LC; on TestRunner +Prepare: LC; on TestRunner (cur=TestRunner) diff --git a/test/912-classes/run b/test/912-classes/run index 20dfc4b767..f24db40cb0 100755 --- a/test/912-classes/run +++ b/test/912-classes/run @@ -18,7 +18,5 @@ # In certain configurations, the app images may be valid even in a new classloader. Turn off # app images to avoid the issue. -./default-run "$@" --experimental agents \ - --experimental runtime-plugins \ - --jvmti \ +./default-run "$@" --jvmti \ --no-app-image diff --git a/test/912-classes/src-ex/A.java b/test/912-classes/src-ex/A.java index 64acb2fcfe..2c43cfbd79 100644 --- a/test/912-classes/src-ex/A.java +++ b/test/912-classes/src-ex/A.java @@ -15,4 +15,4 @@ */ public class A { -}
\ No newline at end of file +} diff --git a/test/912-classes/src-ex/C.java b/test/912-classes/src-ex/C.java new file mode 100644 index 0000000000..97f8021486 --- /dev/null +++ b/test/912-classes/src-ex/C.java @@ -0,0 +1,18 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +public class C extends A { +} diff --git a/test/912-classes/src/B.java b/test/912-classes/src/B.java index f1458c3bca..52ce4dd58e 100644 --- a/test/912-classes/src/B.java +++ b/test/912-classes/src/B.java @@ -15,4 +15,4 @@ */ public class B { -}
\ No newline at end of file +} diff --git a/test/912-classes/src/Main.java b/test/912-classes/src/Main.java index ea3c49c87b..6ad23a4869 100644 --- a/test/912-classes/src/Main.java +++ b/test/912-classes/src/Main.java @@ -21,8 +21,6 @@ import java.util.Comparator; public class Main { public static void main(String[] args) throws Exception { - System.loadLibrary(args[1]); - doTest(); } @@ -80,6 +78,14 @@ public class Main { testClassLoader(getProxyClass()); testClassLoaderClasses(); + + System.out.println(); + + testClassVersion(); + + System.out.println(); + + testClassEvents(); } private static Class<?> proxyClass = null; @@ -202,6 +208,71 @@ public class Main { } } + private static void testClassVersion() { + System.out.println(Arrays.toString(getClassVersion(Main.class))); + } + + private static void testClassEvents() throws Exception { + ClassLoader cl = Main.class.getClassLoader(); + while (cl.getParent() != null) { + cl = cl.getParent(); + } + final ClassLoader boot = cl; + + Runnable r = new Runnable() { + @Override + public void run() { + try { + ClassLoader cl6 = create(boot, DEX1, DEX2); + System.out.println("C, true"); + Class.forName("C", true, cl6); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + }; + + Thread dummyThread = new Thread(); + dummyThread.start(); + dummyThread.join(); + + ensureJitCompiled(Main.class, "testClassEvents"); + + enableClassLoadEvents(true); + + ClassLoader cl1 = create(boot, DEX1, DEX2); + System.out.println("B, false"); + Class.forName("B", false, cl1); + + ClassLoader cl2 = create(boot, DEX1, DEX2); + System.out.println("B, true"); + Class.forName("B", true, cl2); + + ClassLoader cl3 = create(boot, DEX1, DEX2); + System.out.println("C, false"); + Class.forName("C", false, cl3); + System.out.println("A, false"); + Class.forName("A", false, cl3); + + ClassLoader cl4 = create(boot, DEX1, DEX2); + System.out.println("C, true"); + Class.forName("C", true, cl4); + System.out.println("A, true"); + Class.forName("A", true, cl4); + + ClassLoader cl5 = create(boot, DEX1, DEX2); + System.out.println("A, true"); + Class.forName("A", true, cl5); + System.out.println("C, true"); + Class.forName("C", true, cl5); + + Thread t = new Thread(r, "TestRunner"); + t.start(); + t.join(); + + enableClassLoadEvents(false); + } + private static void printClassLoaderClasses(ClassLoader cl) { for (;;) { if (cl == null || !cl.getClass().getName().startsWith("dalvik.system")) { @@ -262,6 +333,12 @@ public class Main { private static native Class<?>[] getClassLoaderClasses(ClassLoader cl); + private static native int[] getClassVersion(Class<?> c); + + private static native void enableClassLoadEvents(boolean b); + + private static native void ensureJitCompiled(Class c, String name); + private static class TestForNonInit { public static double dummy = Math.random(); // So it can't be compile-time initialized. } diff --git a/test/913-heaps/run b/test/913-heaps/run index 4379349cb2..c6e62ae6cd 100755 --- a/test/913-heaps/run +++ b/test/913-heaps/run @@ -14,6 +14,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -./default-run "$@" --experimental agents \ - --experimental runtime-plugins \ - --jvmti +./default-run "$@" --jvmti diff --git a/test/913-heaps/src/Main.java b/test/913-heaps/src/Main.java index 564596e02b..5a11a5b144 100644 --- a/test/913-heaps/src/Main.java +++ b/test/913-heaps/src/Main.java @@ -21,8 +21,6 @@ import java.util.HashSet; public class Main { public static void main(String[] args) throws Exception { - System.loadLibrary(args[1]); - doTest(); doFollowReferencesTest(); } diff --git a/test/914-hello-obsolescence/run b/test/914-hello-obsolescence/run index 4379349cb2..c6e62ae6cd 100755 --- a/test/914-hello-obsolescence/run +++ b/test/914-hello-obsolescence/run @@ -14,6 +14,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -./default-run "$@" --experimental agents \ - --experimental runtime-plugins \ - --jvmti +./default-run "$@" --jvmti diff --git a/test/914-hello-obsolescence/src/Main.java b/test/914-hello-obsolescence/src/Main.java index 46266efb28..8a1471693a 100644 --- a/test/914-hello-obsolescence/src/Main.java +++ b/test/914-hello-obsolescence/src/Main.java @@ -53,7 +53,6 @@ public class Main { "AAACIAAAEQAAAKIBAAADIAAAAgAAAJECAAAAIAAAAQAAAJ8CAAAAEAAAAQAAALACAAA="); public static void main(String[] args) { - System.loadLibrary(args[1]); doTest(new Transform()); } diff --git a/test/915-obsolete-2/run b/test/915-obsolete-2/run index 4379349cb2..c6e62ae6cd 100755 --- a/test/915-obsolete-2/run +++ b/test/915-obsolete-2/run @@ -14,6 +14,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -./default-run "$@" --experimental agents \ - --experimental runtime-plugins \ - --jvmti +./default-run "$@" --jvmti diff --git a/test/915-obsolete-2/src/Main.java b/test/915-obsolete-2/src/Main.java index bbeb726858..0e3145c220 100644 --- a/test/915-obsolete-2/src/Main.java +++ b/test/915-obsolete-2/src/Main.java @@ -79,7 +79,6 @@ public class Main { "IAAAFwAAAD4CAAADIAAABAAAAAgEAAAAIAAAAQAAACYEAAAAEAAAAQAAADwEAAA="); public static void main(String[] args) { - System.loadLibrary(args[1]); doTest(new Transform()); } diff --git a/test/916-obsolete-jit/run b/test/916-obsolete-jit/run index 9056211284..b6d406fd99 100755 --- a/test/916-obsolete-jit/run +++ b/test/916-obsolete-jit/run @@ -21,7 +21,5 @@ if [[ "$@" == *"--jit"* ]]; then else other_args="--jit" fi -./default-run "$@" --experimental agents \ - --experimental runtime-plugins \ - ${other_args} \ +./default-run "$@" ${other_args} \ --jvmti diff --git a/test/916-obsolete-jit/src/Main.java b/test/916-obsolete-jit/src/Main.java index 1e43f7ee9d..1b03200ba5 100644 --- a/test/916-obsolete-jit/src/Main.java +++ b/test/916-obsolete-jit/src/Main.java @@ -113,7 +113,6 @@ public class Main { } public static void main(String[] args) { - System.loadLibrary(args[1]); doTest(new Transform(), new TestWatcher()); } diff --git a/test/917-fields-transformation/run b/test/917-fields-transformation/run index 4379349cb2..c6e62ae6cd 100755 --- a/test/917-fields-transformation/run +++ b/test/917-fields-transformation/run @@ -14,6 +14,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -./default-run "$@" --experimental agents \ - --experimental runtime-plugins \ - --jvmti +./default-run "$@" --jvmti diff --git a/test/917-fields-transformation/src/Main.java b/test/917-fields-transformation/src/Main.java index 5378bb7a05..632a5c8b21 100644 --- a/test/917-fields-transformation/src/Main.java +++ b/test/917-fields-transformation/src/Main.java @@ -55,7 +55,6 @@ public class Main { "AAIgAAAMAAAAXAEAAAMgAAACAAAA4QEAAAAgAAABAAAA8AEAAAAQAAABAAAABAIAAA=="); public static void main(String[] args) { - System.loadLibrary(args[1]); doTest(new Transform("Hello", "Goodbye"), new Transform("start", "end")); } diff --git a/test/918-fields/run b/test/918-fields/run index 4379349cb2..c6e62ae6cd 100755 --- a/test/918-fields/run +++ b/test/918-fields/run @@ -14,6 +14,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -./default-run "$@" --experimental agents \ - --experimental runtime-plugins \ - --jvmti +./default-run "$@" --jvmti diff --git a/test/918-fields/src/Main.java b/test/918-fields/src/Main.java index 8af6e7b375..3ba535b31b 100644 --- a/test/918-fields/src/Main.java +++ b/test/918-fields/src/Main.java @@ -19,8 +19,6 @@ import java.util.Arrays; public class Main { public static void main(String[] args) throws Exception { - System.loadLibrary(args[1]); - doTest(); } diff --git a/test/919-obsolete-fields/run b/test/919-obsolete-fields/run index 4379349cb2..c6e62ae6cd 100755 --- a/test/919-obsolete-fields/run +++ b/test/919-obsolete-fields/run @@ -14,6 +14,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -./default-run "$@" --experimental agents \ - --experimental runtime-plugins \ - --jvmti +./default-run "$@" --jvmti diff --git a/test/919-obsolete-fields/src/Main.java b/test/919-obsolete-fields/src/Main.java index 895c7a3fa4..1d893f125a 100644 --- a/test/919-obsolete-fields/src/Main.java +++ b/test/919-obsolete-fields/src/Main.java @@ -116,7 +116,6 @@ public class Main { } public static void main(String[] args) { - System.loadLibrary(args[1]); TestWatcher w = new TestWatcher(); doTest(new Transform(w), w); } diff --git a/test/920-objects/run b/test/920-objects/run index 4379349cb2..c6e62ae6cd 100755 --- a/test/920-objects/run +++ b/test/920-objects/run @@ -14,6 +14,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -./default-run "$@" --experimental agents \ - --experimental runtime-plugins \ - --jvmti +./default-run "$@" --jvmti diff --git a/test/921-hello-failure/expected.txt b/test/921-hello-failure/expected.txt index 1c1d4d9b80..9615e6b33d 100644 --- a/test/921-hello-failure/expected.txt +++ b/test/921-hello-failure/expected.txt @@ -21,3 +21,11 @@ hello2 - MultiRedef Transformation error : java.lang.Exception(Failed to redefine classes <LTransform;, LTransform2;> due to JVMTI_ERROR_NAMES_DONT_MATCH) hello - MultiRedef hello2 - MultiRedef +hello - MultiRetrans +hello2 - MultiRetrans +Transformation error : java.lang.Exception(Failed to retransform classes <LTransform2;, LTransform;> due to JVMTI_ERROR_NAMES_DONT_MATCH) +hello - MultiRetrans +hello2 - MultiRetrans +Transformation error : java.lang.Exception(Failed to retransform classes <LTransform;, LTransform2;> due to JVMTI_ERROR_NAMES_DONT_MATCH) +hello - MultiRetrans +hello2 - MultiRetrans diff --git a/test/921-hello-failure/run b/test/921-hello-failure/run index 3ef4832da2..8be0ed4aed 100755 --- a/test/921-hello-failure/run +++ b/test/921-hello-failure/run @@ -15,6 +15,4 @@ # limitations under the License. -./default-run "$@" --experimental agents \ - --experimental runtime-plugins \ - --jvmti +./default-run "$@" --jvmti diff --git a/test/921-hello-failure/src/Main.java b/test/921-hello-failure/src/Main.java index 1fe259961d..67ca1e15d6 100644 --- a/test/921-hello-failure/src/Main.java +++ b/test/921-hello-failure/src/Main.java @@ -18,13 +18,13 @@ import java.util.ArrayList; public class Main { public static void main(String[] args) { - System.loadLibrary(args[1]); NewName.doTest(new Transform()); DifferentAccess.doTest(new Transform()); NewInterface.doTest(new Transform2()); MissingInterface.doTest(new Transform2()); ReorderInterface.doTest(new Transform2()); MultiRedef.doTest(new Transform(), new Transform2()); + MultiRetrans.doTest(new Transform(), new Transform2()); } // Transforms the class. This throws an exception if something goes wrong. @@ -47,7 +47,20 @@ public class Main { dex_files.toArray(new byte[0][])); } + public static void addMultiTransformationResults(CommonClassDefinition... defs) throws Exception { + 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) throws Exception; + public static native void doCommonClassRetransformation(Class<?>... target) throws Exception; + 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/921-hello-failure/src/MultiRetrans.java b/test/921-hello-failure/src/MultiRetrans.java new file mode 100644 index 0000000000..95aaf074e9 --- /dev/null +++ b/test/921-hello-failure/src/MultiRetrans.java @@ -0,0 +1,108 @@ +/* + * 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. + */ + +import java.util.Base64; + +class MultiRetrans { + + // class NotTransform { + // public void sayHi(String name) { + // throw new Error("Should not be called!"); + // } + // } + private static CommonClassDefinition INVALID_DEFINITION_T1 = new CommonClassDefinition( + Transform.class, + Base64.getDecoder().decode( + "yv66vgAAADQAFQoABgAPBwAQCAARCgACABIHABMHABQBAAY8aW5pdD4BAAMoKVYBAARDb2RlAQAP" + + "TGluZU51bWJlclRhYmxlAQAFc2F5SGkBABUoTGphdmEvbGFuZy9TdHJpbmc7KVYBAApTb3VyY2VG" + + "aWxlAQARTm90VHJhbnNmb3JtLmphdmEMAAcACAEAD2phdmEvbGFuZy9FcnJvcgEAFVNob3VsZCBu" + + "b3QgYmUgY2FsbGVkIQwABwAMAQAMTm90VHJhbnNmb3JtAQAQamF2YS9sYW5nL09iamVjdAAgAAUA" + + "BgAAAAAAAgAAAAcACAABAAkAAAAdAAEAAQAAAAUqtwABsQAAAAEACgAAAAYAAQAAAAEAAQALAAwA" + + "AQAJAAAAIgADAAIAAAAKuwACWRIDtwAEvwAAAAEACgAAAAYAAQAAAAMAAQANAAAAAgAO"), + Base64.getDecoder().decode( + "ZGV4CjAzNQDLV95i5xnv6iUi6uIeDoY5jP5Xe9NP1AiYAgAAcAAAAHhWNBIAAAAAAAAAAAQCAAAL" + + "AAAAcAAAAAUAAACcAAAAAgAAALAAAAAAAAAAAAAAAAQAAADIAAAAAQAAAOgAAACQAQAACAEAAEoB" + + "AABSAQAAYgEAAHUBAACJAQAAnQEAALABAADHAQAAygEAAM4BAADiAQAAAQAAAAIAAAADAAAABAAA" + + "AAcAAAAHAAAABAAAAAAAAAAIAAAABAAAAEQBAAAAAAAAAAAAAAAAAQAKAAAAAQABAAAAAAACAAAA" + + "AAAAAAAAAAAAAAAAAgAAAAAAAAAFAAAAAAAAAPQBAAAAAAAAAQABAAEAAADpAQAABAAAAHAQAwAA" + + "AA4ABAACAAIAAADuAQAACQAAACIAAQAbAQYAAABwIAIAEAAnAAAAAQAAAAMABjxpbml0PgAOTE5v" + + "dFRyYW5zZm9ybTsAEUxqYXZhL2xhbmcvRXJyb3I7ABJMamF2YS9sYW5nL09iamVjdDsAEkxqYXZh" + + "L2xhbmcvU3RyaW5nOwARTm90VHJhbnNmb3JtLmphdmEAFVNob3VsZCBub3QgYmUgY2FsbGVkIQAB" + + "VgACVkwAEmVtaXR0ZXI6IGphY2stNC4yMAAFc2F5SGkAAQAHDgADAQAHDgAAAAEBAICABIgCAQGg" + + "AgAADAAAAAAAAAABAAAAAAAAAAEAAAALAAAAcAAAAAIAAAAFAAAAnAAAAAMAAAACAAAAsAAAAAUA" + + "AAAEAAAAyAAAAAYAAAABAAAA6AAAAAEgAAACAAAACAEAAAEQAAABAAAARAEAAAIgAAALAAAASgEA" + + "AAMgAAACAAAA6QEAAAAgAAABAAAA9AEAAAAQAAABAAAABAIAAA==")); + + // Valid redefinition of Transform2 + // class Transform2 implements Iface1, Iface2 { + // public void sayHi(String name) { + // throw new Error("Should not be called!"); + // } + // } + private static CommonClassDefinition VALID_DEFINITION_T2 = new CommonClassDefinition( + Transform2.class, + Base64.getDecoder().decode( + "yv66vgAAADQAGQoABgARBwASCAATCgACABQHABUHABYHABcHABgBAAY8aW5pdD4BAAMoKVYBAARD" + + "b2RlAQAPTGluZU51bWJlclRhYmxlAQAFc2F5SGkBABUoTGphdmEvbGFuZy9TdHJpbmc7KVYBAApT" + + "b3VyY2VGaWxlAQAPVHJhbnNmb3JtMi5qYXZhDAAJAAoBAA9qYXZhL2xhbmcvRXJyb3IBABVTaG91" + + "bGQgbm90IGJlIGNhbGxlZCEMAAkADgEAClRyYW5zZm9ybTIBABBqYXZhL2xhbmcvT2JqZWN0AQAG" + + "SWZhY2UxAQAGSWZhY2UyACAABQAGAAIABwAIAAAAAgAAAAkACgABAAsAAAAdAAEAAQAAAAUqtwAB" + + "sQAAAAEADAAAAAYAAQAAAAEAAQANAA4AAQALAAAAIgADAAIAAAAKuwACWRIDtwAEvwAAAAEADAAA" + + "AAYAAQAAAAMAAQAPAAAAAgAQ"), + Base64.getDecoder().decode( + "ZGV4CjAzNQDSWls05CPkX+gbTGMVRvx9dc9vozzVbu7AAgAAcAAAAHhWNBIAAAAAAAAAACwCAAAN" + + "AAAAcAAAAAcAAACkAAAAAgAAAMAAAAAAAAAAAAAAAAQAAADYAAAAAQAAAPgAAACoAQAAGAEAAGIB" + + "AABqAQAAdAEAAH4BAACMAQAAnwEAALMBAADHAQAA3gEAAO8BAADyAQAA9gEAAAoCAAABAAAAAgAA" + + "AAMAAAAEAAAABQAAAAYAAAAJAAAACQAAAAYAAAAAAAAACgAAAAYAAABcAQAAAgAAAAAAAAACAAEA" + + "DAAAAAMAAQAAAAAABAAAAAAAAAACAAAAAAAAAAQAAABUAQAACAAAAAAAAAAcAgAAAAAAAAEAAQAB" + + "AAAAEQIAAAQAAABwEAMAAAAOAAQAAgACAAAAFgIAAAkAAAAiAAMAGwEHAAAAcCACABAAJwAAAAIA" + + "AAAAAAEAAQAAAAUABjxpbml0PgAITElmYWNlMTsACExJZmFjZTI7AAxMVHJhbnNmb3JtMjsAEUxq" + + "YXZhL2xhbmcvRXJyb3I7ABJMamF2YS9sYW5nL09iamVjdDsAEkxqYXZhL2xhbmcvU3RyaW5nOwAV" + + "U2hvdWxkIG5vdCBiZSBjYWxsZWQhAA9UcmFuc2Zvcm0yLmphdmEAAVYAAlZMABJlbWl0dGVyOiBq" + + "YWNrLTQuMjAABXNheUhpAAEABw4AAwEABw4AAAABAQCAgASYAgEBsAIAAAwAAAAAAAAAAQAAAAAA" + + "AAABAAAADQAAAHAAAAACAAAABwAAAKQAAAADAAAAAgAAAMAAAAAFAAAABAAAANgAAAAGAAAAAQAA" + + "APgAAAABIAAAAgAAABgBAAABEAAAAgAAAFQBAAACIAAADQAAAGIBAAADIAAAAgAAABECAAAAIAAA" + + "AQAAABwCAAAAEAAAAQAAACwCAAA=")); + + public static void doTest(Transform t1, Transform2 t2) { + t1.sayHi("MultiRetrans"); + t2.sayHi("MultiRetrans"); + try { + Main.addMultiTransformationResults(VALID_DEFINITION_T2, INVALID_DEFINITION_T1); + Main.enableCommonRetransformation(true); + Main.doCommonClassRetransformation(Transform2.class, Transform.class); + } catch (Exception e) { + System.out.println( + "Transformation error : " + e.getClass().getName() + "(" + e.getMessage() + ")"); + } finally { + Main.enableCommonRetransformation(false); + } + t1.sayHi("MultiRetrans"); + t2.sayHi("MultiRetrans"); + try { + Main.addMultiTransformationResults(VALID_DEFINITION_T2, INVALID_DEFINITION_T1); + Main.enableCommonRetransformation(true); + Main.doCommonClassRetransformation(Transform.class, Transform2.class); + } catch (Exception e) { + System.out.println( + "Transformation error : " + e.getClass().getName() + "(" + e.getMessage() + ")"); + } finally { + Main.enableCommonRetransformation(false); + } + t1.sayHi("MultiRetrans"); + t2.sayHi("MultiRetrans"); + } +} diff --git a/test/922-properties/run b/test/922-properties/run index 4379349cb2..c6e62ae6cd 100755 --- a/test/922-properties/run +++ b/test/922-properties/run @@ -14,6 +14,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -./default-run "$@" --experimental agents \ - --experimental runtime-plugins \ - --jvmti +./default-run "$@" --jvmti diff --git a/test/922-properties/src/Main.java b/test/922-properties/src/Main.java index 6cec6e97f2..8ad742f0ca 100644 --- a/test/922-properties/src/Main.java +++ b/test/922-properties/src/Main.java @@ -19,8 +19,6 @@ import java.util.TreeSet; public class Main { public static void main(String[] args) throws Exception { - System.loadLibrary(args[1]); - doTest(); } diff --git a/test/923-monitors/run b/test/923-monitors/run index 4379349cb2..c6e62ae6cd 100755 --- a/test/923-monitors/run +++ b/test/923-monitors/run @@ -14,6 +14,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -./default-run "$@" --experimental agents \ - --experimental runtime-plugins \ - --jvmti +./default-run "$@" --jvmti diff --git a/test/923-monitors/src/Main.java b/test/923-monitors/src/Main.java index e35ce12608..ef00728d10 100644 --- a/test/923-monitors/src/Main.java +++ b/test/923-monitors/src/Main.java @@ -21,8 +21,6 @@ import java.util.List; public class Main { public static void main(String[] args) throws Exception { - System.loadLibrary(args[1]); - doTest(); } diff --git a/test/924-threads/expected.txt b/test/924-threads/expected.txt index 32e3368d02..67d20eb750 100644 --- a/test/924-threads/expected.txt +++ b/test/924-threads/expected.txt @@ -29,3 +29,9 @@ e1 = ALIVE|WAITING_WITH_TIMEOUT|SLEEPING|WAITING 5 = ALIVE|RUNNABLE 2 = TERMINATED [Thread[FinalizerDaemon,5,system], Thread[FinalizerWatchdogDaemon,5,system], Thread[HeapTaskDaemon,5,system], Thread[ReferenceQueueDaemon,5,system], Thread[Signal Catcher,5,system], Thread[main,5,main]] +JVMTI_ERROR_THREAD_NOT_ALIVE +JVMTI_ERROR_THREAD_NOT_ALIVE +Constructed thread +Thread(EventTestThread): start +Thread(EventTestThread): end +Thread joined diff --git a/test/924-threads/run b/test/924-threads/run index 4379349cb2..c6e62ae6cd 100755 --- a/test/924-threads/run +++ b/test/924-threads/run @@ -14,6 +14,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -./default-run "$@" --experimental agents \ - --experimental runtime-plugins \ - --jvmti +./default-run "$@" --jvmti diff --git a/test/924-threads/src/Main.java b/test/924-threads/src/Main.java index 492a7ac6d1..29c4aa330c 100644 --- a/test/924-threads/src/Main.java +++ b/test/924-threads/src/Main.java @@ -25,8 +25,6 @@ import java.util.Map; public class Main { public static void main(String[] args) throws Exception { - System.loadLibrary(args[1]); - doTest(); } @@ -56,6 +54,10 @@ public class Main { doStateTests(); doAllThreadsTests(); + + doTLSTests(); + + doTestEvents(); } private static class Holder { @@ -164,6 +166,84 @@ public class Main { System.out.println(Arrays.toString(threads)); } + private static void doTLSTests() throws Exception { + doTLSNonLiveTests(); + doTLSLiveTests(); + } + + private static void doTLSNonLiveTests() throws Exception { + Thread t = new Thread(); + try { + setTLS(t, 1); + System.out.println("Expected failure setting TLS for non-live thread"); + } catch (Exception e) { + System.out.println(e.getMessage()); + } + t.start(); + t.join(); + try { + setTLS(t, 1); + System.out.println("Expected failure setting TLS for non-live thread"); + } catch (Exception e) { + System.out.println(e.getMessage()); + } + } + + private static void doTLSLiveTests() throws Exception { + setTLS(Thread.currentThread(), 1); + + long l = getTLS(Thread.currentThread()); + if (l != 1) { + throw new RuntimeException("Unexpected TLS value: " + l); + }; + + final CountDownLatch cdl1 = new CountDownLatch(1); + final CountDownLatch cdl2 = new CountDownLatch(1); + + Runnable r = new Runnable() { + @Override + public void run() { + try { + cdl1.countDown(); + cdl2.await(); + setTLS(Thread.currentThread(), 2); + if (getTLS(Thread.currentThread()) != 2) { + throw new RuntimeException("Different thread issue"); + } + } catch (Exception e) { + throw new RuntimeException(e); + } + } + }; + + Thread t = new Thread(r); + t.start(); + cdl1.await(); + setTLS(Thread.currentThread(), 1); + cdl2.countDown(); + + t.join(); + if (getTLS(Thread.currentThread()) != 1) { + throw new RuntimeException("Got clobbered"); + } + } + + private static void doTestEvents() throws Exception { + enableThreadEvents(true); + + Thread t = new Thread("EventTestThread"); + + System.out.println("Constructed thread"); + Thread.yield(); + + t.start(); + t.join(); + + System.out.println("Thread joined"); + + enableThreadEvents(false); + } + private final static Comparator<Thread> THREAD_COMP = new Comparator<Thread>() { public int compare(Thread o1, Thread o2) { return o1.getName().compareTo(o2.getName()); @@ -229,4 +309,7 @@ public class Main { private static native Object[] getThreadInfo(Thread t); private static native int getThreadState(Thread t); private static native Thread[] getAllThreads(); + private static native void setTLS(Thread t, long l); + private static native long getTLS(Thread t); + private static native void enableThreadEvents(boolean b); } diff --git a/test/924-threads/threads.cc b/test/924-threads/threads.cc index 1487b7c64d..0380433d19 100644 --- a/test/924-threads/threads.cc +++ b/test/924-threads/threads.cc @@ -120,5 +120,88 @@ extern "C" JNIEXPORT jobjectArray JNICALL Java_Main_getAllThreads( return ret; } +extern "C" JNIEXPORT jlong JNICALL Java_Main_getTLS( + JNIEnv* env, jclass Main_klass ATTRIBUTE_UNUSED, jthread thread) { + void* tls; + jvmtiError result = jvmti_env->GetThreadLocalStorage(thread, &tls); + if (JvmtiErrorToException(env, result)) { + return 0; + } + return static_cast<jlong>(reinterpret_cast<uintptr_t>(tls)); +} + +extern "C" JNIEXPORT void JNICALL Java_Main_setTLS( + JNIEnv* env, jclass Main_klass ATTRIBUTE_UNUSED, jthread thread, jlong val) { + const void* tls = reinterpret_cast<void*>(static_cast<uintptr_t>(val)); + jvmtiError result = jvmti_env->SetThreadLocalStorage(thread, tls); + JvmtiErrorToException(env, result); +} + +static void JNICALL ThreadEvent(jvmtiEnv* jvmti_env, + JNIEnv* jni_env, + jthread thread, + bool is_start) { + jvmtiThreadInfo info; + jvmtiError result = jvmti_env->GetThreadInfo(thread, &info); + if (result != JVMTI_ERROR_NONE) { + printf("Error getting thread info"); + return; + } + printf("Thread(%s): %s\n", info.name, is_start ? "start" : "end"); + + jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(info.name)); + jni_env->DeleteLocalRef(info.thread_group); + jni_env->DeleteLocalRef(info.context_class_loader); +} + +static void JNICALL ThreadStart(jvmtiEnv* jvmti_env, + JNIEnv* jni_env, + jthread thread) { + ThreadEvent(jvmti_env, jni_env, thread, true); +} + +static void JNICALL ThreadEnd(jvmtiEnv* jvmti_env, + JNIEnv* jni_env, + jthread thread) { + ThreadEvent(jvmti_env, jni_env, thread, false); +} + +extern "C" JNIEXPORT void JNICALL Java_Main_enableThreadEvents( + JNIEnv* env, jclass Main_klass ATTRIBUTE_UNUSED, jboolean b) { + if (b == JNI_FALSE) { + jvmtiError ret = jvmti_env->SetEventNotificationMode(JVMTI_DISABLE, + JVMTI_EVENT_THREAD_START, + nullptr); + if (JvmtiErrorToException(env, ret)) { + return; + } + ret = jvmti_env->SetEventNotificationMode(JVMTI_DISABLE, + JVMTI_EVENT_THREAD_END, + nullptr); + JvmtiErrorToException(env, ret); + return; + } + + jvmtiEventCallbacks callbacks; + memset(&callbacks, 0, sizeof(jvmtiEventCallbacks)); + callbacks.ThreadStart = ThreadStart; + callbacks.ThreadEnd = ThreadEnd; + jvmtiError ret = jvmti_env->SetEventCallbacks(&callbacks, sizeof(callbacks)); + if (JvmtiErrorToException(env, ret)) { + return; + } + + ret = jvmti_env->SetEventNotificationMode(JVMTI_ENABLE, + JVMTI_EVENT_THREAD_START, + nullptr); + if (JvmtiErrorToException(env, ret)) { + return; + } + ret = jvmti_env->SetEventNotificationMode(JVMTI_ENABLE, + JVMTI_EVENT_THREAD_END, + nullptr); + JvmtiErrorToException(env, ret); +} + } // namespace Test924Threads } // namespace art diff --git a/test/925-threadgroups/run b/test/925-threadgroups/run index 4379349cb2..c6e62ae6cd 100755 --- a/test/925-threadgroups/run +++ b/test/925-threadgroups/run @@ -14,6 +14,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -./default-run "$@" --experimental agents \ - --experimental runtime-plugins \ - --jvmti +./default-run "$@" --jvmti diff --git a/test/925-threadgroups/src/Main.java b/test/925-threadgroups/src/Main.java index c59efe2f7b..3d7a4ca740 100644 --- a/test/925-threadgroups/src/Main.java +++ b/test/925-threadgroups/src/Main.java @@ -19,8 +19,6 @@ import java.util.Comparator; public class Main { public static void main(String[] args) throws Exception { - System.loadLibrary(args[1]); - doTest(); } diff --git a/test/926-multi-obsolescence/run b/test/926-multi-obsolescence/run index 4379349cb2..c6e62ae6cd 100755 --- a/test/926-multi-obsolescence/run +++ b/test/926-multi-obsolescence/run @@ -14,6 +14,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -./default-run "$@" --experimental agents \ - --experimental runtime-plugins \ - --jvmti +./default-run "$@" --jvmti diff --git a/test/926-multi-obsolescence/src/Main.java b/test/926-multi-obsolescence/src/Main.java index 8a6cf84b8b..6d9f96ca0e 100644 --- a/test/926-multi-obsolescence/src/Main.java +++ b/test/926-multi-obsolescence/src/Main.java @@ -92,7 +92,6 @@ public class Main { "AACUAQAAAiAAABEAAACiAQAAAyAAAAIAAACXAgAAACAAAAEAAAClAgAAABAAAAEAAAC0AgAA")); public static void main(String[] args) { - System.loadLibrary(args[1]); doTest(new Transform(), new Transform2()); } diff --git a/test/927-timers/run b/test/927-timers/run index 4379349cb2..c6e62ae6cd 100755 --- a/test/927-timers/run +++ b/test/927-timers/run @@ -14,6 +14,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -./default-run "$@" --experimental agents \ - --experimental runtime-plugins \ - --jvmti +./default-run "$@" --jvmti diff --git a/test/927-timers/src/Main.java b/test/927-timers/src/Main.java index 2f5c85cab5..b67f66d3a7 100644 --- a/test/927-timers/src/Main.java +++ b/test/927-timers/src/Main.java @@ -18,8 +18,6 @@ import java.util.Arrays; public class Main { public static void main(String[] args) throws Exception { - System.loadLibrary(args[1]); - doTest(); } diff --git a/test/928-jni-table/run b/test/928-jni-table/run index 4379349cb2..c6e62ae6cd 100755 --- a/test/928-jni-table/run +++ b/test/928-jni-table/run @@ -14,6 +14,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -./default-run "$@" --experimental agents \ - --experimental runtime-plugins \ - --jvmti +./default-run "$@" --jvmti diff --git a/test/928-jni-table/src/Main.java b/test/928-jni-table/src/Main.java index b0baea1f9d..fd61b7d955 100644 --- a/test/928-jni-table/src/Main.java +++ b/test/928-jni-table/src/Main.java @@ -16,8 +16,6 @@ public class Main { public static void main(String[] args) throws Exception { - System.loadLibrary(args[1]); - doJNITableTest(); System.out.println("Done"); diff --git a/test/929-search/run b/test/929-search/run index 0a8d0672f6..67923a7984 100755 --- a/test/929-search/run +++ b/test/929-search/run @@ -17,7 +17,5 @@ # This test checks whether dex files can be injected into parent classloaders. App images preload # classes, which will make the injection moot. Turn off app images to avoid the issue. -./default-run "$@" --experimental agents \ - --experimental runtime-plugins \ - --jvmti \ +./default-run "$@" --jvmti \ --no-app-image diff --git a/test/929-search/src/Main.java b/test/929-search/src/Main.java index d253e6fdf6..bbeb0816c8 100644 --- a/test/929-search/src/Main.java +++ b/test/929-search/src/Main.java @@ -18,8 +18,6 @@ import java.util.Arrays; public class Main { public static void main(String[] args) throws Exception { - System.loadLibrary(args[1]); - doTest(); } diff --git a/test/930-hello-retransform/build b/test/930-hello-retransform/build new file mode 100755 index 0000000000..898e2e54a2 --- /dev/null +++ b/test/930-hello-retransform/build @@ -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-build "$@" --experimental agents diff --git a/test/930-hello-retransform/expected.txt b/test/930-hello-retransform/expected.txt new file mode 100644 index 0000000000..4774b81b49 --- /dev/null +++ b/test/930-hello-retransform/expected.txt @@ -0,0 +1,2 @@ +hello +Goodbye diff --git a/test/930-hello-retransform/info.txt b/test/930-hello-retransform/info.txt new file mode 100644 index 0000000000..875a5f6ec1 --- /dev/null +++ b/test/930-hello-retransform/info.txt @@ -0,0 +1 @@ +Tests basic functions in the jvmti plugin. diff --git a/test/930-hello-retransform/run b/test/930-hello-retransform/run new file mode 100755 index 0000000000..c6e62ae6cd --- /dev/null +++ b/test/930-hello-retransform/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 diff --git a/test/930-hello-retransform/src/Main.java b/test/930-hello-retransform/src/Main.java new file mode 100644 index 0000000000..0063c82897 --- /dev/null +++ b/test/930-hello-retransform/src/Main.java @@ -0,0 +1,69 @@ +/* + * 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 java.util.Base64; +public class Main { + + /** + * base64 encoded class/dex file for + * class Transform { + * public void sayHi() { + * System.out.println("Goodbye"); + * } + * } + */ + private static final byte[] CLASS_BYTES = Base64.getDecoder().decode( + "yv66vgAAADQAHAoABgAOCQAPABAIABEKABIAEwcAFAcAFQEABjxpbml0PgEAAygpVgEABENvZGUB" + + "AA9MaW5lTnVtYmVyVGFibGUBAAVzYXlIaQEAClNvdXJjZUZpbGUBAA5UcmFuc2Zvcm0uamF2YQwA" + + "BwAIBwAWDAAXABgBAAdHb29kYnllBwAZDAAaABsBAAlUcmFuc2Zvcm0BABBqYXZhL2xhbmcvT2Jq" + + "ZWN0AQAQamF2YS9sYW5nL1N5c3RlbQEAA291dAEAFUxqYXZhL2lvL1ByaW50U3RyZWFtOwEAE2ph" + + "dmEvaW8vUHJpbnRTdHJlYW0BAAdwcmludGxuAQAVKExqYXZhL2xhbmcvU3RyaW5nOylWACAABQAG" + + "AAAAAAACAAAABwAIAAEACQAAAB0AAQABAAAABSq3AAGxAAAAAQAKAAAABgABAAAAEQABAAsACAAB" + + "AAkAAAAlAAIAAQAAAAmyAAISA7YABLEAAAABAAoAAAAKAAIAAAATAAgAFAABAAwAAAACAA0="); + private static final byte[] DEX_BYTES = Base64.getDecoder().decode( + "ZGV4CjAzNQCLXSBQ5FiS3f16krSYZFF8xYZtFVp0GRXMAgAAcAAAAHhWNBIAAAAAAAAAACwCAAAO" + + "AAAAcAAAAAYAAACoAAAAAgAAAMAAAAABAAAA2AAAAAQAAADgAAAAAQAAAAABAACsAQAAIAEAAGIB" + + "AABqAQAAcwEAAIABAACXAQAAqwEAAL8BAADTAQAA4wEAAOYBAADqAQAA/gEAAAMCAAAMAgAAAgAA" + + "AAMAAAAEAAAABQAAAAYAAAAIAAAACAAAAAUAAAAAAAAACQAAAAUAAABcAQAABAABAAsAAAAAAAAA" + + "AAAAAAAAAAANAAAAAQABAAwAAAACAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAHAAAAAAAAAB4CAAAA" + + "AAAAAQABAAEAAAATAgAABAAAAHAQAwAAAA4AAwABAAIAAAAYAgAACQAAAGIAAAAbAQEAAABuIAIA" + + "EAAOAAAAAQAAAAMABjxpbml0PgAHR29vZGJ5ZQALTFRyYW5zZm9ybTsAFUxqYXZhL2lvL1ByaW50" + + "U3RyZWFtOwASTGphdmEvbGFuZy9PYmplY3Q7ABJMamF2YS9sYW5nL1N0cmluZzsAEkxqYXZhL2xh" + + "bmcvU3lzdGVtOwAOVHJhbnNmb3JtLmphdmEAAVYAAlZMABJlbWl0dGVyOiBqYWNrLTMuMzYAA291" + + "dAAHcHJpbnRsbgAFc2F5SGkAEQAHDgATAAcOhQAAAAEBAICABKACAQG4Ag0AAAAAAAAAAQAAAAAA" + + "AAABAAAADgAAAHAAAAACAAAABgAAAKgAAAADAAAAAgAAAMAAAAAEAAAAAQAAANgAAAAFAAAABAAA" + + "AOAAAAAGAAAAAQAAAAABAAABIAAAAgAAACABAAABEAAAAQAAAFwBAAACIAAADgAAAGIBAAADIAAA" + + "AgAAABMCAAAAIAAAAQAAAB4CAAAAEAAAAQAAACwCAAA="); + + public static void main(String[] args) { + doTest(new Transform()); + } + + public static void doTest(Transform t) { + t.sayHi(); + addCommonTransformationResult("Transform", CLASS_BYTES, DEX_BYTES); + enableCommonRetransformation(true); + doCommonClassRetransformation(Transform.class); + t.sayHi(); + } + + // Transforms the class + private static native void doCommonClassRetransformation(Class<?>... target); + private static native void enableCommonRetransformation(boolean enable); + private static native void addCommonTransformationResult(String target_name, + byte[] class_bytes, + byte[] dex_bytes); +} diff --git a/test/930-hello-retransform/src/Transform.java b/test/930-hello-retransform/src/Transform.java new file mode 100644 index 0000000000..8e8af355da --- /dev/null +++ b/test/930-hello-retransform/src/Transform.java @@ -0,0 +1,28 @@ +/* + * 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. + */ + +class Transform { + public void sayHi() { + // Use lower 'h' to make sure the string will have a different string id + // than the transformation (the transformation code is the same except + // the actual printed String, which was making the test inacurately passing + // in JIT mode when loading the string from the dex cache, as the string ids + // of the two different strings were the same). + // We know the string ids will be different because lexicographically: + // "Goodbye" < "LTransform;" < "hello". + System.out.println("hello"); + } +} diff --git a/test/931-agent-thread/agent_thread.cc b/test/931-agent-thread/agent_thread.cc new file mode 100644 index 0000000000..6ace4cea68 --- /dev/null +++ b/test/931-agent-thread/agent_thread.cc @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <inttypes.h> + +#include "barrier.h" +#include "base/logging.h" +#include "base/macros.h" +#include "jni.h" +#include "openjdkjvmti/jvmti.h" +#include "runtime.h" +#include "ScopedLocalRef.h" +#include "thread-inl.h" +#include "well_known_classes.h" + +#include "ti-agent/common_helper.h" +#include "ti-agent/common_load.h" + +namespace art { +namespace Test930AgentThread { + +struct AgentData { + AgentData() : main_thread(nullptr), + jvmti_env(nullptr), + b(2) { + } + + jthread main_thread; + jvmtiEnv* jvmti_env; + Barrier b; + jint priority; +}; + +static void AgentMain(jvmtiEnv* jenv, JNIEnv* env, void* arg) { + AgentData* data = reinterpret_cast<AgentData*>(arg); + + // Check some basics. + // This thread is not the main thread. + jthread this_thread; + jvmtiError this_thread_result = jenv->GetCurrentThread(&this_thread); + CHECK(!JvmtiErrorToException(env, this_thread_result)); + CHECK(!env->IsSameObject(this_thread, data->main_thread)); + + // The thread is a daemon. + jvmtiThreadInfo info; + jvmtiError info_result = jenv->GetThreadInfo(this_thread, &info); + CHECK(!JvmtiErrorToException(env, info_result)); + CHECK(info.is_daemon); + + // The thread has the requested priority. + // TODO: Our thread priorities do not work on the host. + // CHECK_EQ(info.priority, data->priority); + + // Check further parts of the thread: + jint thread_count; + jthread* threads; + jvmtiError threads_result = jenv->GetAllThreads(&thread_count, &threads); + CHECK(!JvmtiErrorToException(env, threads_result)); + bool found = false; + for (jint i = 0; i != thread_count; ++i) { + if (env->IsSameObject(threads[i], this_thread)) { + found = true; + break; + } + } + CHECK(found); + + // Done, let the main thread progress. + data->b.Pass(Thread::Current()); +} + +extern "C" JNIEXPORT void JNICALL Java_Main_testAgentThread( + JNIEnv* env, jclass Main_klass ATTRIBUTE_UNUSED) { + // Create a Thread object. + ScopedLocalRef<jobject> thread_name(env, + env->NewStringUTF("Agent Thread")); + if (thread_name.get() == nullptr) { + return; + } + + ScopedLocalRef<jobject> thread(env, env->AllocObject(WellKnownClasses::java_lang_Thread)); + if (thread.get() == nullptr) { + return; + } + + env->CallNonvirtualVoidMethod(thread.get(), + WellKnownClasses::java_lang_Thread, + WellKnownClasses::java_lang_Thread_init, + Runtime::Current()->GetMainThreadGroup(), + thread_name.get(), + kMinThreadPriority, + JNI_FALSE); + if (env->ExceptionCheck()) { + return; + } + + jthread main_thread; + jvmtiError main_thread_result = jvmti_env->GetCurrentThread(&main_thread); + if (JvmtiErrorToException(env, main_thread_result)) { + return; + } + + AgentData data; + data.main_thread = env->NewGlobalRef(main_thread); + data.jvmti_env = jvmti_env; + data.priority = JVMTI_THREAD_MIN_PRIORITY; + + jvmtiError result = jvmti_env->RunAgentThread(thread.get(), AgentMain, &data, data.priority); + if (JvmtiErrorToException(env, result)) { + return; + } + + data.b.Wait(Thread::Current()); + + env->DeleteGlobalRef(data.main_thread); +} + +} // namespace Test930AgentThread +} // namespace art diff --git a/test/931-agent-thread/build b/test/931-agent-thread/build new file mode 100755 index 0000000000..898e2e54a2 --- /dev/null +++ b/test/931-agent-thread/build @@ -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-build "$@" --experimental agents diff --git a/test/931-agent-thread/expected.txt b/test/931-agent-thread/expected.txt new file mode 100644 index 0000000000..a965a70ed4 --- /dev/null +++ b/test/931-agent-thread/expected.txt @@ -0,0 +1 @@ +Done diff --git a/test/931-agent-thread/info.txt b/test/931-agent-thread/info.txt new file mode 100644 index 0000000000..875a5f6ec1 --- /dev/null +++ b/test/931-agent-thread/info.txt @@ -0,0 +1 @@ +Tests basic functions in the jvmti plugin. diff --git a/test/931-agent-thread/run b/test/931-agent-thread/run new file mode 100755 index 0000000000..67923a7984 --- /dev/null +++ b/test/931-agent-thread/run @@ -0,0 +1,21 @@ +#!/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. + +# This test checks whether dex files can be injected into parent classloaders. App images preload +# classes, which will make the injection moot. Turn off app images to avoid the issue. + +./default-run "$@" --jvmti \ + --no-app-image diff --git a/test/931-agent-thread/src/Main.java b/test/931-agent-thread/src/Main.java new file mode 100644 index 0000000000..a7639fbfb2 --- /dev/null +++ b/test/931-agent-thread/src/Main.java @@ -0,0 +1,27 @@ +/* + * 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. + */ + +import java.util.Arrays; + +public class Main { + public static void main(String[] args) throws Exception { + testAgentThread(); + + System.out.println("Done"); + } + + private static native void testAgentThread(); +} diff --git a/test/932-transform-saves/build b/test/932-transform-saves/build new file mode 100755 index 0000000000..898e2e54a2 --- /dev/null +++ b/test/932-transform-saves/build @@ -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-build "$@" --experimental agents diff --git a/test/932-transform-saves/expected.txt b/test/932-transform-saves/expected.txt new file mode 100644 index 0000000000..5097771994 --- /dev/null +++ b/test/932-transform-saves/expected.txt @@ -0,0 +1,3 @@ +hello +Goodbye +hello diff --git a/test/932-transform-saves/info.txt b/test/932-transform-saves/info.txt new file mode 100644 index 0000000000..875a5f6ec1 --- /dev/null +++ b/test/932-transform-saves/info.txt @@ -0,0 +1 @@ +Tests basic functions in the jvmti plugin. diff --git a/test/932-transform-saves/run b/test/932-transform-saves/run new file mode 100755 index 0000000000..c6e62ae6cd --- /dev/null +++ b/test/932-transform-saves/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 diff --git a/test/932-transform-saves/src/Main.java b/test/932-transform-saves/src/Main.java new file mode 100644 index 0000000000..d960322071 --- /dev/null +++ b/test/932-transform-saves/src/Main.java @@ -0,0 +1,115 @@ +/* + * 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 java.util.Base64; +public class Main { + /** + * base64 encoded class/dex file for + * class Transform { + * public void sayHi() { + * System.out.println("hello"); + * } + * } + */ + private static final byte[] CLASS_BYTES_A = Base64.getDecoder().decode( + "yv66vgAAADQAHAoABgAOCQAPABAIABEKABIAEwcAFAcAFQEABjxpbml0PgEAAygpVgEABENvZGUB" + + "AA9MaW5lTnVtYmVyVGFibGUBAAVzYXlIaQEAClNvdXJjZUZpbGUBAA5UcmFuc2Zvcm0uamF2YQwA" + + "BwAIBwAWDAAXABgBAAVoZWxsbwcAGQwAGgAbAQAJVHJhbnNmb3JtAQAQamF2YS9sYW5nL09iamVj" + + "dAEAEGphdmEvbGFuZy9TeXN0ZW0BAANvdXQBABVMamF2YS9pby9QcmludFN0cmVhbTsBABNqYXZh" + + "L2lvL1ByaW50U3RyZWFtAQAHcHJpbnRsbgEAFShMamF2YS9sYW5nL1N0cmluZzspVgAgAAUABgAA" + + "AAAAAgAAAAcACAABAAkAAAAdAAEAAQAAAAUqtwABsQAAAAEACgAAAAYAAQAAABEAAQALAAgAAQAJ" + + "AAAAJQACAAEAAAAJsgACEgO2AASxAAAAAQAKAAAACgACAAAAGgAIABsAAQAMAAAAAgAN"); + private static final byte[] DEX_BYTES_A = Base64.getDecoder().decode( + "ZGV4CjAzNQC6XWInnnDd1H4NdQ3P3inH8eCVmQI6W7LMAgAAcAAAAHhWNBIAAAAAAAAAACwCAAAO" + + "AAAAcAAAAAYAAACoAAAAAgAAAMAAAAABAAAA2AAAAAQAAADgAAAAAQAAAAABAACsAQAAIAEAAGIB" + + "AABqAQAAdwEAAI4BAACiAQAAtgEAAMoBAADaAQAA3QEAAOEBAAD1AQAA/AEAAAECAAAKAgAAAQAA" + + "AAIAAAADAAAABAAAAAUAAAAHAAAABwAAAAUAAAAAAAAACAAAAAUAAABcAQAABAABAAsAAAAAAAAA" + + "AAAAAAAAAAANAAAAAQABAAwAAAACAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAGAAAAAAAAABwCAAAA" + + "AAAAAQABAAEAAAARAgAABAAAAHAQAwAAAA4AAwABAAIAAAAWAgAACQAAAGIAAAAbAQoAAABuIAIA" + + "EAAOAAAAAQAAAAMABjxpbml0PgALTFRyYW5zZm9ybTsAFUxqYXZhL2lvL1ByaW50U3RyZWFtOwAS" + + "TGphdmEvbGFuZy9PYmplY3Q7ABJMamF2YS9sYW5nL1N0cmluZzsAEkxqYXZhL2xhbmcvU3lzdGVt" + + "OwAOVHJhbnNmb3JtLmphdmEAAVYAAlZMABJlbWl0dGVyOiBqYWNrLTQuMjIABWhlbGxvAANvdXQA" + + "B3ByaW50bG4ABXNheUhpABEABw4AGgAHDocAAAABAQCAgASgAgEBuAIAAA0AAAAAAAAAAQAAAAAA" + + "AAABAAAADgAAAHAAAAACAAAABgAAAKgAAAADAAAAAgAAAMAAAAAEAAAAAQAAANgAAAAFAAAABAAA" + + "AOAAAAAGAAAAAQAAAAABAAABIAAAAgAAACABAAABEAAAAQAAAFwBAAACIAAADgAAAGIBAAADIAAA" + + "AgAAABECAAAAIAAAAQAAABwCAAAAEAAAAQAAACwCAAA="); + + /** + * base64 encoded class/dex file for + * class Transform { + * public void sayHi() { + * System.out.println("Goodbye"); + * } + * } + */ + private static final byte[] CLASS_BYTES_B = Base64.getDecoder().decode( + "yv66vgAAADQAHAoABgAOCQAPABAIABEKABIAEwcAFAcAFQEABjxpbml0PgEAAygpVgEABENvZGUB" + + "AA9MaW5lTnVtYmVyVGFibGUBAAVzYXlIaQEAClNvdXJjZUZpbGUBAA5UcmFuc2Zvcm0uamF2YQwA" + + "BwAIBwAWDAAXABgBAAdHb29kYnllBwAZDAAaABsBAAlUcmFuc2Zvcm0BABBqYXZhL2xhbmcvT2Jq" + + "ZWN0AQAQamF2YS9sYW5nL1N5c3RlbQEAA291dAEAFUxqYXZhL2lvL1ByaW50U3RyZWFtOwEAE2ph" + + "dmEvaW8vUHJpbnRTdHJlYW0BAAdwcmludGxuAQAVKExqYXZhL2xhbmcvU3RyaW5nOylWACAABQAG" + + "AAAAAAACAAAABwAIAAEACQAAAB0AAQABAAAABSq3AAGxAAAAAQAKAAAABgABAAAAEQABAAsACAAB" + + "AAkAAAAlAAIAAQAAAAmyAAISA7YABLEAAAABAAoAAAAKAAIAAAATAAgAFAABAAwAAAACAA0="); + private static final byte[] DEX_BYTES_B = Base64.getDecoder().decode( + "ZGV4CjAzNQCLXSBQ5FiS3f16krSYZFF8xYZtFVp0GRXMAgAAcAAAAHhWNBIAAAAAAAAAACwCAAAO" + + "AAAAcAAAAAYAAACoAAAAAgAAAMAAAAABAAAA2AAAAAQAAADgAAAAAQAAAAABAACsAQAAIAEAAGIB" + + "AABqAQAAcwEAAIABAACXAQAAqwEAAL8BAADTAQAA4wEAAOYBAADqAQAA/gEAAAMCAAAMAgAAAgAA" + + "AAMAAAAEAAAABQAAAAYAAAAIAAAACAAAAAUAAAAAAAAACQAAAAUAAABcAQAABAABAAsAAAAAAAAA" + + "AAAAAAAAAAANAAAAAQABAAwAAAACAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAHAAAAAAAAAB4CAAAA" + + "AAAAAQABAAEAAAATAgAABAAAAHAQAwAAAA4AAwABAAIAAAAYAgAACQAAAGIAAAAbAQEAAABuIAIA" + + "EAAOAAAAAQAAAAMABjxpbml0PgAHR29vZGJ5ZQALTFRyYW5zZm9ybTsAFUxqYXZhL2lvL1ByaW50" + + "U3RyZWFtOwASTGphdmEvbGFuZy9PYmplY3Q7ABJMamF2YS9sYW5nL1N0cmluZzsAEkxqYXZhL2xh" + + "bmcvU3lzdGVtOwAOVHJhbnNmb3JtLmphdmEAAVYAAlZMABJlbWl0dGVyOiBqYWNrLTMuMzYAA291" + + "dAAHcHJpbnRsbgAFc2F5SGkAEQAHDgATAAcOhQAAAAEBAICABKACAQG4Ag0AAAAAAAAAAQAAAAAA" + + "AAABAAAADgAAAHAAAAACAAAABgAAAKgAAAADAAAAAgAAAMAAAAAEAAAAAQAAANgAAAAFAAAABAAA" + + "AOAAAAAGAAAAAQAAAAABAAABIAAAAgAAACABAAABEAAAAQAAAFwBAAACIAAADgAAAGIBAAADIAAA" + + "AgAAABMCAAAAIAAAAQAAAB4CAAAAEAAAAQAAACwCAAA="); + + public static void main(String[] args) { + doTest(new Transform()); + } + + public static void doTest(Transform t) { + // TODO We currently need to do this transform call since we don't have any way to make the + // original-dex-file a single-class dex-file letting us restore it easily. We should use the + // manipulation library that is being made when we store the original dex file. + // TODO REMOVE this theoretically does nothing but it ensures the original-dex-file we have set + // is one we can return to unaltered. + doCommonClassRedefinition(Transform.class, CLASS_BYTES_A, DEX_BYTES_A); + t.sayHi(); + + // Now turn it into DEX_BYTES_B so it says 'Goodbye' + addCommonTransformationResult("Transform", CLASS_BYTES_B, DEX_BYTES_B); + enableCommonRetransformation(true); + doCommonClassRetransformation(Transform.class); + t.sayHi(); + + // Now turn it back to normal by removing the load-hook and transforming again. + enableCommonRetransformation(false); + doCommonClassRetransformation(Transform.class); + t.sayHi(); + } + + // Transforms the class + private static native void doCommonClassRedefinition(Class<?> target, + byte[] class_bytes, + byte[] dex_bytes); + private static native void doCommonClassRetransformation(Class<?>... target); + private static native void enableCommonRetransformation(boolean enable); + private static native void addCommonTransformationResult(String target_name, + byte[] class_bytes, + byte[] dex_bytes); +} diff --git a/test/932-transform-saves/src/Transform.java b/test/932-transform-saves/src/Transform.java new file mode 100644 index 0000000000..8e8af355da --- /dev/null +++ b/test/932-transform-saves/src/Transform.java @@ -0,0 +1,28 @@ +/* + * 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. + */ + +class Transform { + public void sayHi() { + // Use lower 'h' to make sure the string will have a different string id + // than the transformation (the transformation code is the same except + // the actual printed String, which was making the test inacurately passing + // in JIT mode when loading the string from the dex cache, as the string ids + // of the two different strings were the same). + // We know the string ids will be different because lexicographically: + // "Goodbye" < "LTransform;" < "hello". + System.out.println("hello"); + } +} diff --git a/test/933-misc-events/build b/test/933-misc-events/build new file mode 100755 index 0000000000..898e2e54a2 --- /dev/null +++ b/test/933-misc-events/build @@ -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-build "$@" --experimental agents diff --git a/test/933-misc-events/expected.txt b/test/933-misc-events/expected.txt new file mode 100644 index 0000000000..024c560b26 --- /dev/null +++ b/test/933-misc-events/expected.txt @@ -0,0 +1,2 @@ +Received dump request. +Done diff --git a/test/933-misc-events/info.txt b/test/933-misc-events/info.txt new file mode 100644 index 0000000000..875a5f6ec1 --- /dev/null +++ b/test/933-misc-events/info.txt @@ -0,0 +1 @@ +Tests basic functions in the jvmti plugin. diff --git a/test/933-misc-events/misc_events.cc b/test/933-misc-events/misc_events.cc new file mode 100644 index 0000000000..860d4b5e16 --- /dev/null +++ b/test/933-misc-events/misc_events.cc @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <atomic> +#include <signal.h> +#include <sys/types.h> + +#include "base/logging.h" +#include "base/macros.h" +#include "jni.h" +#include "openjdkjvmti/jvmti.h" + +#include "ti-agent/common_helper.h" +#include "ti-agent/common_load.h" + +namespace art { +namespace Test933MiscEvents { + +static std::atomic<bool> saw_dump_request(false); + +static void DumpRequestCallback(jvmtiEnv* jenv ATTRIBUTE_UNUSED) { + printf("Received dump request.\n"); + saw_dump_request.store(true, std::memory_order::memory_order_relaxed); +} + +extern "C" JNIEXPORT void JNICALL Java_Main_testSigQuit( + JNIEnv* env, jclass Main_klass ATTRIBUTE_UNUSED) { + jvmtiEventCallbacks callbacks; + memset(&callbacks, 0, sizeof(jvmtiEventCallbacks)); + callbacks.DataDumpRequest = DumpRequestCallback; + jvmtiError ret = jvmti_env->SetEventCallbacks(&callbacks, sizeof(callbacks)); + if (JvmtiErrorToException(env, ret)) { + return; + } + + ret = jvmti_env->SetEventNotificationMode(JVMTI_ENABLE, + JVMTI_EVENT_DATA_DUMP_REQUEST, + nullptr); + if (JvmtiErrorToException(env, ret)) { + return; + } + + // Send sigquit to self. + kill(getpid(), SIGQUIT); + + // Busy-wait for request. + for (;;) { + sleep(1); + if (saw_dump_request.load(std::memory_order::memory_order_relaxed)) { + break; + } + } + + ret = jvmti_env->SetEventNotificationMode(JVMTI_DISABLE, JVMTI_EVENT_DATA_DUMP_REQUEST, nullptr); + JvmtiErrorToException(env, ret); +} + +} // namespace Test933MiscEvents +} // namespace art diff --git a/test/933-misc-events/run b/test/933-misc-events/run new file mode 100755 index 0000000000..67923a7984 --- /dev/null +++ b/test/933-misc-events/run @@ -0,0 +1,21 @@ +#!/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. + +# This test checks whether dex files can be injected into parent classloaders. App images preload +# classes, which will make the injection moot. Turn off app images to avoid the issue. + +./default-run "$@" --jvmti \ + --no-app-image diff --git a/test/933-misc-events/src/Main.java b/test/933-misc-events/src/Main.java new file mode 100644 index 0000000000..89801a3555 --- /dev/null +++ b/test/933-misc-events/src/Main.java @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +public class Main { + public static void main(String[] args) throws Exception { + testSigQuit(); + + System.out.println("Done"); + } + + private static native void testSigQuit(); +} diff --git a/test/934-load-transform/build b/test/934-load-transform/build new file mode 100755 index 0000000000..898e2e54a2 --- /dev/null +++ b/test/934-load-transform/build @@ -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-build "$@" --experimental agents diff --git a/test/934-load-transform/expected.txt b/test/934-load-transform/expected.txt new file mode 100644 index 0000000000..2b60207f03 --- /dev/null +++ b/test/934-load-transform/expected.txt @@ -0,0 +1 @@ +Goodbye diff --git a/test/934-load-transform/info.txt b/test/934-load-transform/info.txt new file mode 100644 index 0000000000..875a5f6ec1 --- /dev/null +++ b/test/934-load-transform/info.txt @@ -0,0 +1 @@ +Tests basic functions in the jvmti plugin. diff --git a/test/934-load-transform/run b/test/934-load-transform/run new file mode 100755 index 0000000000..c6e62ae6cd --- /dev/null +++ b/test/934-load-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 diff --git a/test/934-load-transform/src/Main.java b/test/934-load-transform/src/Main.java new file mode 100644 index 0000000000..0b7f26890f --- /dev/null +++ b/test/934-load-transform/src/Main.java @@ -0,0 +1,66 @@ +/* + * 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 java.util.Base64; +public class Main { + + /** + * base64 encoded class/dex file for + * class Transform { + * public void sayHi() { + * System.out.println("Goodbye"); + * } + * } + */ + private static final byte[] CLASS_BYTES = Base64.getDecoder().decode( + "yv66vgAAADQAHAoABgAOCQAPABAIABEKABIAEwcAFAcAFQEABjxpbml0PgEAAygpVgEABENvZGUB" + + "AA9MaW5lTnVtYmVyVGFibGUBAAVzYXlIaQEAClNvdXJjZUZpbGUBAA5UcmFuc2Zvcm0uamF2YQwA" + + "BwAIBwAWDAAXABgBAAdHb29kYnllBwAZDAAaABsBAAlUcmFuc2Zvcm0BABBqYXZhL2xhbmcvT2Jq" + + "ZWN0AQAQamF2YS9sYW5nL1N5c3RlbQEAA291dAEAFUxqYXZhL2lvL1ByaW50U3RyZWFtOwEAE2ph" + + "dmEvaW8vUHJpbnRTdHJlYW0BAAdwcmludGxuAQAVKExqYXZhL2xhbmcvU3RyaW5nOylWACAABQAG" + + "AAAAAAACAAAABwAIAAEACQAAAB0AAQABAAAABSq3AAGxAAAAAQAKAAAABgABAAAAEQABAAsACAAB" + + "AAkAAAAlAAIAAQAAAAmyAAISA7YABLEAAAABAAoAAAAKAAIAAAATAAgAFAABAAwAAAACAA0="); + private static final byte[] DEX_BYTES = Base64.getDecoder().decode( + "ZGV4CjAzNQCLXSBQ5FiS3f16krSYZFF8xYZtFVp0GRXMAgAAcAAAAHhWNBIAAAAAAAAAACwCAAAO" + + "AAAAcAAAAAYAAACoAAAAAgAAAMAAAAABAAAA2AAAAAQAAADgAAAAAQAAAAABAACsAQAAIAEAAGIB" + + "AABqAQAAcwEAAIABAACXAQAAqwEAAL8BAADTAQAA4wEAAOYBAADqAQAA/gEAAAMCAAAMAgAAAgAA" + + "AAMAAAAEAAAABQAAAAYAAAAIAAAACAAAAAUAAAAAAAAACQAAAAUAAABcAQAABAABAAsAAAAAAAAA" + + "AAAAAAAAAAANAAAAAQABAAwAAAACAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAHAAAAAAAAAB4CAAAA" + + "AAAAAQABAAEAAAATAgAABAAAAHAQAwAAAA4AAwABAAIAAAAYAgAACQAAAGIAAAAbAQEAAABuIAIA" + + "EAAOAAAAAQAAAAMABjxpbml0PgAHR29vZGJ5ZQALTFRyYW5zZm9ybTsAFUxqYXZhL2lvL1ByaW50" + + "U3RyZWFtOwASTGphdmEvbGFuZy9PYmplY3Q7ABJMamF2YS9sYW5nL1N0cmluZzsAEkxqYXZhL2xh" + + "bmcvU3lzdGVtOwAOVHJhbnNmb3JtLmphdmEAAVYAAlZMABJlbWl0dGVyOiBqYWNrLTMuMzYAA291" + + "dAAHcHJpbnRsbgAFc2F5SGkAEQAHDgATAAcOhQAAAAEBAICABKACAQG4Ag0AAAAAAAAAAQAAAAAA" + + "AAABAAAADgAAAHAAAAACAAAABgAAAKgAAAADAAAAAgAAAMAAAAAEAAAAAQAAANgAAAAFAAAABAAA" + + "AOAAAAAGAAAAAQAAAAABAAABIAAAAgAAACABAAABEAAAAQAAAFwBAAACIAAADgAAAGIBAAADIAAA" + + "AgAAABMCAAAAIAAAAQAAAB4CAAAAEAAAAQAAACwCAAA="); + + public static void main(String[] args) { + doTest(); + } + + public static void doTest() { + addCommonTransformationResult("Transform", CLASS_BYTES, DEX_BYTES); + enableCommonRetransformation(true); + new Transform().sayHi(); + } + + // Transforms the class + private static native void enableCommonRetransformation(boolean enable); + private static native void addCommonTransformationResult(String target_name, + byte[] class_bytes, + byte[] dex_bytes); +} diff --git a/test/934-load-transform/src/Transform.java b/test/934-load-transform/src/Transform.java new file mode 100644 index 0000000000..f624c3ac99 --- /dev/null +++ b/test/934-load-transform/src/Transform.java @@ -0,0 +1,21 @@ +/* + * 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. + */ + +class Transform { + public void sayHi() { + throw new Error("Should not be called!"); + } +} diff --git a/test/935-non-retransformable/build b/test/935-non-retransformable/build new file mode 100755 index 0000000000..898e2e54a2 --- /dev/null +++ b/test/935-non-retransformable/build @@ -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-build "$@" --experimental agents diff --git a/test/935-non-retransformable/expected.txt b/test/935-non-retransformable/expected.txt new file mode 100644 index 0000000000..ccd50a66a0 --- /dev/null +++ b/test/935-non-retransformable/expected.txt @@ -0,0 +1,6 @@ +Hello +Hello +Goodbye +Hello +Hello +Goodbye diff --git a/test/935-non-retransformable/info.txt b/test/935-non-retransformable/info.txt new file mode 100644 index 0000000000..875a5f6ec1 --- /dev/null +++ b/test/935-non-retransformable/info.txt @@ -0,0 +1 @@ +Tests basic functions in the jvmti plugin. diff --git a/test/935-non-retransformable/run b/test/935-non-retransformable/run new file mode 100755 index 0000000000..c6e62ae6cd --- /dev/null +++ b/test/935-non-retransformable/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 diff --git a/test/935-non-retransformable/src/Main.java b/test/935-non-retransformable/src/Main.java new file mode 100644 index 0000000000..d9cc329ed9 --- /dev/null +++ b/test/935-non-retransformable/src/Main.java @@ -0,0 +1,95 @@ +/* + * 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 java.lang.reflect.Method; +import java.util.Base64; + +public class Main { + + /** + * base64 encoded class/dex file for + * class Transform { + * public void sayHi() { + * System.out.println("Hello"); + * } + * public void sayGoodbye() { + * System.out.println("Goodbye"); + * } + * } + */ + private static final byte[] CLASS_BYTES = Base64.getDecoder().decode( + "yv66vgAAADQAHwoABwAQCQARABIIABMKABQAFQgAFgcAFwcAGAEABjxpbml0PgEAAygpVgEABENv" + + "ZGUBAA9MaW5lTnVtYmVyVGFibGUBAAVzYXlIaQEACnNheUdvb2RieWUBAApTb3VyY2VGaWxlAQAO" + + "VHJhbnNmb3JtLmphdmEMAAgACQcAGQwAGgAbAQAFSGVsbG8HABwMAB0AHgEAB0dvb2RieWUBAAlU" + + "cmFuc2Zvcm0BABBqYXZhL2xhbmcvT2JqZWN0AQAQamF2YS9sYW5nL1N5c3RlbQEAA291dAEAFUxq" + + "YXZhL2lvL1ByaW50U3RyZWFtOwEAE2phdmEvaW8vUHJpbnRTdHJlYW0BAAdwcmludGxuAQAVKExq" + + "YXZhL2xhbmcvU3RyaW5nOylWACAABgAHAAAAAAADAAAACAAJAAEACgAAAB0AAQABAAAABSq3AAGx" + + "AAAAAQALAAAABgABAAAAAQABAAwACQABAAoAAAAlAAIAAQAAAAmyAAISA7YABLEAAAABAAsAAAAK" + + "AAIAAAADAAgABAABAA0ACQABAAoAAAAlAAIAAQAAAAmyAAISBbYABLEAAAABAAsAAAAKAAIAAAAG" + + "AAgABwABAA4AAAACAA8="); + private static final byte[] DEX_BYTES = Base64.getDecoder().decode( + "ZGV4CjAzNQDpaN+7jX/ZLl9Jr0HAEV7nqL1YDuakKakgAwAAcAAAAHhWNBIAAAAAAAAAAIACAAAQ" + + "AAAAcAAAAAYAAACwAAAAAgAAAMgAAAABAAAA4AAAAAUAAADoAAAAAQAAABABAADwAQAAMAEAAJYB" + + "AACeAQAApwEAAK4BAAC7AQAA0gEAAOYBAAD6AQAADgIAAB4CAAAhAgAAJQIAADkCAAA+AgAARwIA" + + "AFMCAAADAAAABAAAAAUAAAAGAAAABwAAAAkAAAAJAAAABQAAAAAAAAAKAAAABQAAAJABAAAEAAEA" + + "DAAAAAAAAAAAAAAAAAAAAA4AAAAAAAAADwAAAAEAAQANAAAAAgAAAAAAAAAAAAAAAAAAAAIAAAAA" + + "AAAACAAAAAAAAABrAgAAAAAAAAEAAQABAAAAWgIAAAQAAABwEAQAAAAOAAMAAQACAAAAXwIAAAkA" + + "AABiAAAAGwEBAAAAbiADABAADgAAAAMAAQACAAAAZQIAAAkAAABiAAAAGwECAAAAbiADABAADgAA" + + "AAEAAAADAAY8aW5pdD4AB0dvb2RieWUABUhlbGxvAAtMVHJhbnNmb3JtOwAVTGphdmEvaW8vUHJp" + + "bnRTdHJlYW07ABJMamF2YS9sYW5nL09iamVjdDsAEkxqYXZhL2xhbmcvU3RyaW5nOwASTGphdmEv" + + "bGFuZy9TeXN0ZW07AA5UcmFuc2Zvcm0uamF2YQABVgACVkwAEmVtaXR0ZXI6IGphY2stNC4yMgAD" + + "b3V0AAdwcmludGxuAApzYXlHb29kYnllAAVzYXlIaQABAAcOAAYABw6HAAMABw6HAAAAAQIAgIAE" + + "sAIBAcgCAQHsAgAAAA0AAAAAAAAAAQAAAAAAAAABAAAAEAAAAHAAAAACAAAABgAAALAAAAADAAAA" + + "AgAAAMgAAAAEAAAAAQAAAOAAAAAFAAAABQAAAOgAAAAGAAAAAQAAABABAAABIAAAAwAAADABAAAB" + + "EAAAAQAAAJABAAACIAAAEAAAAJYBAAADIAAAAwAAAFoCAAAAIAAAAQAAAGsCAAAAEAAAAQAAAIAC" + + "AAA="); + + public static void main(String[] args) { + doTest(); + } + + public static void doTest() { + addCommonTransformationResult("Transform", CLASS_BYTES, DEX_BYTES); + enableCommonRetransformation(true); + // Actually load the class. + Transform t = new Transform(); + try { + // Call functions with reflection. Since the sayGoodbye function does not exist in the + // LTransform; when we compile this for the first time we need to use reflection. + Method hi = Transform.class.getMethod("sayHi"); + Method bye = Transform.class.getMethod("sayGoodbye"); + hi.invoke(t); + t.sayHi(); + bye.invoke(t); + // Make sure we don't get called for transformation again. + addCommonTransformationResult("Transform", new byte[0], new byte[0]); + doCommonClassRetransformation(Transform.class); + hi.invoke(t); + t.sayHi(); + bye.invoke(t); + } catch (Exception e) { + System.out.println("Unexpected error occured! " + e.toString()); + e.printStackTrace(); + } + } + + // Transforms the class + private static native void doCommonClassRetransformation(Class<?>... klasses); + private static native void enableCommonRetransformation(boolean enable); + private static native void addCommonTransformationResult(String target_name, + byte[] class_bytes, + byte[] dex_bytes); +} diff --git a/test/935-non-retransformable/src/Transform.java b/test/935-non-retransformable/src/Transform.java new file mode 100644 index 0000000000..f624c3ac99 --- /dev/null +++ b/test/935-non-retransformable/src/Transform.java @@ -0,0 +1,21 @@ +/* + * 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. + */ + +class Transform { + public void sayHi() { + throw new Error("Should not be called!"); + } +} diff --git a/test/957-methodhandle-transforms/expected.txt b/test/957-methodhandle-transforms/expected.txt index 7540ef74f3..05b80e78a7 100644 --- a/test/957-methodhandle-transforms/expected.txt +++ b/test/957-methodhandle-transforms/expected.txt @@ -16,3 +16,46 @@ target: target, 42, 56 fallback: fallback, 42, 56 target: target, 42, 56 target: target, 42, 56 +a: a, b:b, c: c +a: a, b:b, c: c +a: a, b:b, c: c +a: a, b:b, c: c +a: a, b:b, c: c +a: a, b:b, c: c +a: a, b:b, c: c +a: a, b:43 +a: a, b:43 +a: a, b:43 +a: a, b:43 +a: a, b:43 +a: a, b:b, c: c +a: a, b:b, c: c +a: a, b:b, c: c +a: a, b:true, c: false +a: a, b:true, c: false +a: a, b:1, c: 2, d: 3, e: 4, f:5, g: 6.0, h: 7.0 +a: a, b:1, c: 2, d: 3, e: 4, f:5, g: 6.0, h: 7.0 +a: a, b:1, c: 2, d: 51, e: 52, f:53.0, g: 54.0 +a: a, b:1, c: 2, d: 51, e: 52, f:53.0, g: 54.0 +a: a, b:1, c: 2, d: 3, e: 4, f:5.0, g:6.0 +a: a, b:1, c: 2, d: 3, e: 4, f:5.0, g:6.0 +a: a, b:1, c: 2, d: 3, e: 4.0, f:5.0 +a: a, b:1, c: 2, d: 3, e: 4.0, f:5.0 +a: a, b:1, c: 2, d: 3.0, e: 4.0 +a: a, b:1, c: 2, d: 3.0, e: 4.0 +a: a, b:1.0, c: 2.0, d: 3.0 +a: a, b:1.0, c: 2.0, d: 3.0 +a: a, b:1.0, c: 2.0 +a: a, b:1.0, c: 2.0 +a: a, b:b, c: c +a: a, b:b, c: c +a: a, b:43 +a: a, b:b, c: c +a: a, b:true, c: false +a: a, b:1, c: 2 +a: a, b:a, c: b +a: a, b:3, c: 4 +a: a, b:42, c: 43 +a: a, b:100, c: 99 +a: a, b:8.9, c: 9.1 +a: a, b:6.7, c: 7.8 diff --git a/test/957-methodhandle-transforms/src/Main.java b/test/957-methodhandle-transforms/src/Main.java index eebf55fb61..4035857b9a 100644 --- a/test/957-methodhandle-transforms/src/Main.java +++ b/test/957-methodhandle-transforms/src/Main.java @@ -34,6 +34,10 @@ public class Main { testFilterReturnValue(); testPermuteArguments(); testInvokers(); + testSpreaders_reference(); + testSpreaders_primitive(); + testInvokeWithArguments(); + testAsCollector(); } public static void testThrowException() throws Throwable { @@ -921,6 +925,455 @@ public class Main { } } + public static int spreadReferences(String a, String b, String c) { + System.out.println("a: " + a + ", b:" + b + ", c: " + c); + return 42; + } + + public static int spreadReferences_Unbox(String a, int b) { + System.out.println("a: " + a + ", b:" + b); + return 43; + } + + public static void testSpreaders_reference() throws Throwable { + MethodType methodType = MethodType.methodType(int.class, + new Class<?>[] { String.class, String.class, String.class }); + MethodHandle delegate = MethodHandles.lookup().findStatic( + Main.class, "spreadReferences", methodType); + + // Basic checks on array lengths. + // + // Array size = 0 + MethodHandle mhAsSpreader = delegate.asSpreader(String[].class, 0); + int ret = (int) mhAsSpreader.invoke("a", "b", "c", new String[] {}); + assertEquals(42, ret); + // Array size = 1 + mhAsSpreader = delegate.asSpreader(String[].class, 1); + ret = (int) mhAsSpreader.invoke("a", "b", new String[] { "c" }); + assertEquals(42, ret); + // Array size = 2 + mhAsSpreader = delegate.asSpreader(String[].class, 2); + ret = (int) mhAsSpreader.invoke("a", new String[] { "b", "c" }); + assertEquals(42, ret); + // Array size = 3 + mhAsSpreader = delegate.asSpreader(String[].class, 3); + ret = (int) mhAsSpreader.invoke(new String[] { "a", "b", "c"}); + assertEquals(42, ret); + + // Exception case, array size = 4 is illegal. + try { + delegate.asSpreader(String[].class, 4); + fail(); + } catch (IllegalArgumentException expected) { + } + + // Exception case, calling with an arg of the wrong size. + // Array size = 3 + mhAsSpreader = delegate.asSpreader(String[].class, 3); + try { + ret = (int) mhAsSpreader.invoke(new String[] { "a", "b"}); + } catch (IllegalArgumentException expected) { + } + + // Various other hijinks, pass as Object[] arrays, Object etc. + mhAsSpreader = delegate.asSpreader(Object[].class, 2); + ret = (int) mhAsSpreader.invoke("a", new String[] { "b", "c" }); + assertEquals(42, ret); + + mhAsSpreader = delegate.asSpreader(Object[].class, 2); + ret = (int) mhAsSpreader.invoke("a", new Object[] { "b", "c" }); + assertEquals(42, ret); + + mhAsSpreader = delegate.asSpreader(Object[].class, 2); + ret = (int) mhAsSpreader.invoke("a", (Object) new Object[] { "b", "c" }); + assertEquals(42, ret); + + // Test implicit unboxing. + MethodType methodType2 = MethodType.methodType(int.class, + new Class<?>[] { String.class, int.class }); + MethodHandle delegate2 = MethodHandles.lookup().findStatic( + Main.class, "spreadReferences_Unbox", methodType2); + + // .. with an Integer[] array. + mhAsSpreader = delegate2.asSpreader(Integer[].class, 1); + ret = (int) mhAsSpreader.invoke("a", new Integer[] { 43 }); + assertEquals(43, ret); + + // .. with an Integer[] array declared as an Object[] argument type. + mhAsSpreader = delegate2.asSpreader(Object[].class, 1); + ret = (int) mhAsSpreader.invoke("a", new Integer[] { 43 }); + assertEquals(43, ret); + + // .. with an Object[] array. + mhAsSpreader = delegate2.asSpreader(Object[].class, 1); + ret = (int) mhAsSpreader.invoke("a", new Object[] { Integer.valueOf(43)}); + assertEquals(43, ret); + + // -- Part 2-- + // Run a subset of these tests on MethodHandles.spreadInvoker, which only accepts + // a trailing argument type of Object[]. + MethodHandle spreadInvoker = MethodHandles.spreadInvoker(methodType2, 1); + ret = (int) spreadInvoker.invoke(delegate2, "a", new Object[] { Integer.valueOf(43)}); + assertEquals(43, ret); + + ret = (int) spreadInvoker.invoke(delegate2, "a", new Integer[] { 43 }); + assertEquals(43, ret); + + // NOTE: Annoyingly, the second argument here is leadingArgCount and not + // arrayLength. + spreadInvoker = MethodHandles.spreadInvoker(methodType, 3); + ret = (int) spreadInvoker.invoke(delegate, "a", "b", "c", new String[] {}); + assertEquals(42, ret); + + spreadInvoker = MethodHandles.spreadInvoker(methodType, 0); + ret = (int) spreadInvoker.invoke(delegate, new String[] { "a", "b", "c" }); + assertEquals(42, ret); + + // Exact invokes: Double check that the expected parameter type is + // Object[] and not T[]. + try { + spreadInvoker.invokeExact(delegate, new String[] { "a", "b", "c" }); + fail(); + } catch (WrongMethodTypeException expected) { + } + + ret = (int) spreadInvoker.invoke(delegate, new Object[] { "a", "b", "c" }); + assertEquals(42, ret); + } + + public static int spreadBoolean(String a, Boolean b, boolean c) { + System.out.println("a: " + a + ", b:" + b + ", c: " + c); + return 44; + } + + public static int spreadByte(String a, Byte b, byte c, + short d, int e, long f, float g, double h) { + System.out.println("a: " + a + ", b:" + b + ", c: " + c + + ", d: " + d + ", e: " + e + ", f:" + f + ", g: " + g + + ", h: " + h); + return 45; + } + + public static int spreadChar(String a, Character b, char c, + int d, long e, float f, double g) { + System.out.println("a: " + a + ", b:" + b + ", c: " + c + + ", d: " + d + ", e: " + e + ", f:" + f + ", g: " + g); + return 46; + } + + public static int spreadShort(String a, Short b, short c, + int d, long e, float f, double g) { + System.out.println("a: " + a + ", b:" + b + ", c: " + c + + ", d: " + d + ", e: " + e + ", f:" + f + ", g:" + g); + return 47; + } + + public static int spreadInt(String a, Integer b, int c, + long d, float e, double f) { + System.out.println("a: " + a + ", b:" + b + ", c: " + c + + ", d: " + d + ", e: " + e + ", f:" + f); + return 48; + } + + public static int spreadLong(String a, Long b, long c, float d, double e) { + System.out.println("a: " + a + ", b:" + b + ", c: " + c + + ", d: " + d + ", e: " + e); + return 49; + } + + public static int spreadFloat(String a, Float b, float c, double d) { + System.out.println("a: " + a + ", b:" + b + ", c: " + c + ", d: " + d); + return 50; + } + + public static int spreadDouble(String a, Double b, double c) { + System.out.println("a: " + a + ", b:" + b + ", c: " + c); + return 51; + } + + public static void testSpreaders_primitive() throws Throwable { + // boolean[] + // --------------------- + MethodType type = MethodType.methodType(int.class, + new Class<?>[] { String.class, Boolean.class, boolean.class }); + MethodHandle delegate = MethodHandles.lookup().findStatic( + Main.class, "spreadBoolean", type); + + MethodHandle spreader = delegate.asSpreader(boolean[].class, 2); + int ret = (int) spreader.invokeExact("a", new boolean[] { true, false }); + assertEquals(44, ret); + ret = (int) spreader.invoke("a", new boolean[] { true, false }); + assertEquals(44, ret); + + // boolean can't be cast to String (the first argument to the method). + try { + delegate.asSpreader(boolean[].class, 3); + fail(); + } catch (WrongMethodTypeException expected) { + } + + // int can't be cast to boolean to supply the last argument to the method. + try { + delegate.asSpreader(int[].class, 1); + fail(); + } catch (WrongMethodTypeException expected) { + } + + // byte[] + // --------------------- + type = MethodType.methodType(int.class, + new Class<?>[] { + String.class, Byte.class, byte.class, + short.class, int.class, long.class, + float.class, double.class }); + delegate = MethodHandles.lookup().findStatic(Main.class, "spreadByte", type); + + spreader = delegate.asSpreader(byte[].class, 7); + ret = (int) spreader.invokeExact("a", + new byte[] { 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7 }); + assertEquals(45, ret); + ret = (int) spreader.invoke("a", + new byte[] { 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7 }); + assertEquals(45, ret); + + // char[] + // --------------------- + type = MethodType.methodType(int.class, + new Class<?>[] { + String.class, Character.class,char.class, + int.class, long.class, float.class, double.class }); + delegate = MethodHandles.lookup().findStatic(Main.class, "spreadChar", type); + + spreader = delegate.asSpreader(char[].class, 6); + ret = (int) spreader.invokeExact("a", + new char[] { '1', '2', '3', '4', '5', '6' }); + assertEquals(46, ret); + ret = (int) spreader.invokeExact("a", + new char[] { '1', '2', '3', '4', '5', '6' }); + assertEquals(46, ret); + + // short[] + // --------------------- + type = MethodType.methodType(int.class, + new Class<?>[] { + String.class, Short.class, short.class, + int.class, long.class, float.class, double.class }); + delegate = MethodHandles.lookup().findStatic(Main.class, "spreadShort", type); + + spreader = delegate.asSpreader(short[].class, 6); + ret = (int) spreader.invokeExact("a", + new short[] { 0x1, 0x2, 0x3, 0x4, 0x5, 0x6 }); + assertEquals(47, ret); + ret = (int) spreader.invoke("a", + new short[] { 0x1, 0x2, 0x3, 0x4, 0x5, 0x6 }); + assertEquals(47, ret); + + // int[] + // --------------------- + type = MethodType.methodType(int.class, + new Class<?>[] { + String.class, Integer.class, int.class, + long.class, float.class, double.class }); + delegate = MethodHandles.lookup().findStatic(Main.class, "spreadInt", type); + + spreader = delegate.asSpreader(int[].class, 5); + ret = (int) spreader.invokeExact("a", new int[] { 1, 2, 3, 4, 5 }); + assertEquals(48, ret); + ret = (int) spreader.invokeExact("a", new int[] { 1, 2, 3, 4, 5 }); + assertEquals(48, ret); + + // long[] + // --------------------- + type = MethodType.methodType(int.class, + new Class<?>[] { + String.class, Long.class, long.class, float.class, double.class }); + delegate = MethodHandles.lookup().findStatic(Main.class, "spreadLong", type); + + spreader = delegate.asSpreader(long[].class, 4); + ret = (int) spreader.invokeExact("a", + new long[] { 0x1, 0x2, 0x3, 0x4 }); + assertEquals(49, ret); + ret = (int) spreader.invoke("a", + new long[] { 0x1, 0x2, 0x3, 0x4 }); + assertEquals(49, ret); + + // float[] + // --------------------- + type = MethodType.methodType(int.class, + new Class<?>[] { + String.class, Float.class, float.class, double.class }); + delegate = MethodHandles.lookup().findStatic(Main.class, "spreadFloat", type); + + spreader = delegate.asSpreader(float[].class, 3); + ret = (int) spreader.invokeExact("a", + new float[] { 1.0f, 2.0f, 3.0f }); + assertEquals(50, ret); + ret = (int) spreader.invokeExact("a", + new float[] { 1.0f, 2.0f, 3.0f }); + assertEquals(50, ret); + + // double[] + // --------------------- + type = MethodType.methodType(int.class, + new Class<?>[] { String.class, Double.class, double.class }); + delegate = MethodHandles.lookup().findStatic(Main.class, "spreadDouble", type); + + spreader = delegate.asSpreader(double[].class, 2); + ret = (int) spreader.invokeExact("a", new double[] { 1.0, 2.0 }); + assertEquals(51, ret); + ret = (int) spreader.invokeExact("a", new double[] { 1.0, 2.0 }); + assertEquals(51, ret); + } + + public static void testInvokeWithArguments() throws Throwable { + MethodType methodType = MethodType.methodType(int.class, + new Class<?>[] { String.class, String.class, String.class }); + MethodHandle handle = MethodHandles.lookup().findStatic( + Main.class, "spreadReferences", methodType); + + Object ret = handle.invokeWithArguments(new Object[] { "a", "b", "c"}); + assertEquals(42, (int) ret); + handle.invokeWithArguments(new String[] { "a", "b", "c" }); + assertEquals(42, (int) ret); + + // Pass in an array that's too small. Should throw an IAE. + try { + handle.invokeWithArguments(new Object[] { "a", "b" }); + fail(); + } catch (IllegalArgumentException expected) { + } catch (WrongMethodTypeException expected) { + } + + // Test implicit unboxing. + MethodType methodType2 = MethodType.methodType(int.class, + new Class<?>[] { String.class, int.class }); + MethodHandle handle2 = MethodHandles.lookup().findStatic( + Main.class, "spreadReferences_Unbox", methodType2); + + ret = (int) handle2.invokeWithArguments(new Object[] { "a", 43 }); + assertEquals(43, (int) ret); + } + + public static int collectBoolean(String a, boolean[] b) { + System.out.println("a: " + a + ", b:" + b[0] + ", c: " + b[1]); + return 44; + } + + public static int collectByte(String a, byte[] b) { + System.out.println("a: " + a + ", b:" + b[0] + ", c: " + b[1]); + return 45; + } + + public static int collectChar(String a, char[] b) { + System.out.println("a: " + a + ", b:" + b[0] + ", c: " + b[1]); + return 46; + } + + public static int collectShort(String a, short[] b) { + System.out.println("a: " + a + ", b:" + b[0] + ", c: " + b[1]); + return 47; + } + + public static int collectInt(String a, int[] b) { + System.out.println("a: " + a + ", b:" + b[0] + ", c: " + b[1]); + return 48; + } + + public static int collectLong(String a, long[] b) { + System.out.println("a: " + a + ", b:" + b[0] + ", c: " + b[1]); + return 49; + } + + public static int collectFloat(String a, float[] b) { + System.out.println("a: " + a + ", b:" + b[0] + ", c: " + b[1]); + return 50; + } + + public static int collectDouble(String a, double[] b) { + System.out.println("a: " + a + ", b:" + b[0] + ", c: " + b[1]); + return 51; + } + + public static int collectCharSequence(String a, CharSequence[] b) { + System.out.println("a: " + a + ", b:" + b[0] + ", c: " + b[1]); + return 99; + } + + public static void testAsCollector() throws Throwable { + // Reference arrays. + // ------------------- + MethodHandle trailingRef = MethodHandles.lookup().findStatic( + Main.class, "collectCharSequence", + MethodType.methodType(int.class, String.class, CharSequence[].class)); + + // int[] is not convertible to CharSequence[].class. + try { + trailingRef.asCollector(int[].class, 1); + fail(); + } catch (IllegalArgumentException expected) { + } + + // Object[] is not convertible to CharSequence[].class. + try { + trailingRef.asCollector(Object[].class, 1); + fail(); + } catch (IllegalArgumentException expected) { + } + + // String[].class is convertible to CharSequence.class + MethodHandle collector = trailingRef.asCollector(String[].class, 2); + assertEquals(99, (int) collector.invoke("a", "b", "c")); + + // Too few arguments should fail with a WMTE. + try { + collector.invoke("a", "b"); + fail(); + } catch (WrongMethodTypeException expected) { + } + + // Too many arguments should fail with a WMTE. + try { + collector.invoke("a", "b", "c", "d"); + fail(); + } catch (WrongMethodTypeException expected) { + } + + // Sanity checks on other array types. + + MethodHandle target = MethodHandles.lookup().findStatic( + Main.class, "collectBoolean", + MethodType.methodType(int.class, String.class, boolean[].class)); + assertEquals(44, (int) target.asCollector(boolean[].class, 2).invoke("a", true, false)); + + target = MethodHandles.lookup().findStatic(Main.class, "collectByte", + MethodType.methodType(int.class, String.class, byte[].class)); + assertEquals(45, (int) target.asCollector(byte[].class, 2).invoke("a", (byte) 1, (byte) 2)); + + target = MethodHandles.lookup().findStatic(Main.class, "collectChar", + MethodType.methodType(int.class, String.class, char[].class)); + assertEquals(46, (int) target.asCollector(char[].class, 2).invoke("a", 'a', 'b')); + + target = MethodHandles.lookup().findStatic(Main.class, "collectShort", + MethodType.methodType(int.class, String.class, short[].class)); + assertEquals(47, (int) target.asCollector(short[].class, 2).invoke("a", (short) 3, (short) 4)); + + target = MethodHandles.lookup().findStatic(Main.class, "collectInt", + MethodType.methodType(int.class, String.class, int[].class)); + assertEquals(48, (int) target.asCollector(int[].class, 2).invoke("a", 42, 43)); + + target = MethodHandles.lookup().findStatic(Main.class, "collectLong", + MethodType.methodType(int.class, String.class, long[].class)); + assertEquals(49, (int) target.asCollector(long[].class, 2).invoke("a", 100, 99)); + + target = MethodHandles.lookup().findStatic(Main.class, "collectFloat", + MethodType.methodType(int.class, String.class, float[].class)); + assertEquals(50, (int) target.asCollector(float[].class, 2).invoke("a", 8.9f, 9.1f)); + + target = MethodHandles.lookup().findStatic(Main.class, "collectDouble", + MethodType.methodType(int.class, String.class, double[].class)); + assertEquals(51, (int) target.asCollector(double[].class, 2).invoke("a", 6.7, 7.8)); + } + public static void fail() { System.out.println("FAIL"); Thread.dumpStack(); @@ -931,6 +1384,10 @@ public class Main { Thread.dumpStack(); } + public static void assertEquals(int i1, int i2) { + if (i1 != i2) throw new AssertionError("Expected: " + i1 + " was " + i2); + } + public static void assertEquals(String s1, String s2) { if (s1 == s2) { return; diff --git a/test/Android.bp b/test/Android.bp index 965d07aa43..287df1375e 100644 --- a/test/Android.bp +++ b/test/Android.bp @@ -173,12 +173,13 @@ art_cc_library { whole_static_libs: [ "libart-compiler-gtest", "libart-runtime-gtest", - "libgtest", + "libgtest" ], shared_libs: [ "libartd", "libartd-compiler", "libbase", + "libbacktrace" ], target: { android: { @@ -269,6 +270,8 @@ art_cc_defaults { "927-timers/timers.cc", "928-jni-table/jni_table.cc", "929-search/search.cc", + "931-agent-thread/agent_thread.cc", + "933-misc-events/misc_events.cc", ], shared_libs: [ "libbase", @@ -315,6 +318,7 @@ cc_defaults { "141-class-unload/jni_unload.cc", "148-multithread-gc-annotations/gc_coverage.cc", "149-suspend-all-stress/suspend_all.cc", + "154-gc-loop/heap_interface.cc", "454-get-vreg/get_vreg_jni.cc", "457-regs/regs_jni.cc", "461-get-reference-vreg/get_reference_vreg_jni.cc", @@ -325,6 +329,7 @@ cc_defaults { "570-checker-osr/osr.cc", "595-profile-saving/profile-saving.cc", "596-app-images/app_images.cc", + "596-monitor-inflation/monitor_inflation.cc", "597-deopt-new-string/deopt.cc", "626-const-class-linking/clear_dex_cache_types.cc", ], diff --git a/test/Android.run-test.mk b/test/Android.run-test.mk index e604c93c72..afd199806d 100644 --- a/test/Android.run-test.mk +++ b/test/Android.run-test.mk @@ -277,39 +277,6 @@ TEST_ART_BROKEN_TARGET_TESTS := \ 147-stripped-dex-fallback \ 569-checker-pattern-replacement -# These 9** tests are not supported in current form due to linker -# restrictions. See b/31681198 -TEST_ART_BROKEN_TARGET_TESTS += \ - 901-hello-ti-agent \ - 902-hello-transformation \ - 903-hello-tagging \ - 904-object-allocation \ - 905-object-free \ - 906-iterate-heap \ - 907-get-loaded-classes \ - 908-gc-start-finish \ - 909-attach-agent \ - 910-methods \ - 911-get-stack-trace \ - 912-classes \ - 913-heaps \ - 914-hello-obsolescence \ - 915-obsolete-2 \ - 916-obsolete-jit \ - 917-fields-transformation \ - 918-fields \ - 919-obsolete-fields \ - 920-objects \ - 921-hello-failure \ - 922-properties \ - 923-monitors \ - 924-threads \ - 925-threadgroups \ - 926-multi-obsolescence \ - 927-timers \ - 928-jni-table \ - 929-search \ - ifneq (,$(filter target,$(TARGET_TYPES))) ART_TEST_KNOWN_BROKEN += $(call all-run-test-names,target,$(RUN_TYPES),$(PREBUILD_TYPES), \ $(COMPILER_TYPES),$(RELOCATE_TYPES),$(TRACE_TYPES),$(GC_TYPES),$(JNI_TYPES), \ @@ -374,11 +341,14 @@ TEST_ART_BROKEN_NO_PREBUILD_TESTS := # Note 117-nopatchoat is not broken per-se it just doesn't work (and isn't meant to) without # --prebuild --relocate +# 934 & 935 are broken due to dex2dex issues and app-images TEST_ART_BROKEN_NO_RELOCATE_TESTS := \ 117-nopatchoat \ 118-noimage-dex2oat \ 119-noimage-patchoat \ - 554-jit-profile-file + 554-jit-profile-file \ + 934-load-transform \ + 935-non-retransformable \ ifneq (,$(filter no-relocate,$(RELOCATE_TYPES))) ART_TEST_KNOWN_BROKEN += $(call all-run-test-names,$(TARGET_TYPES),$(RUN_TYPES),$(PREBUILD_TYPES), \ @@ -390,9 +360,12 @@ TEST_ART_BROKEN_NO_RELOCATE_TESTS := # Temporarily disable some broken tests when forcing access checks in interpreter b/22414682 # 629 requires compilation. +# 934 & 935 are broken due to dex2dex issues and app-images TEST_ART_BROKEN_INTERPRETER_ACCESS_CHECK_TESTS := \ 137-cfi \ - 629-vdex-speed + 629-vdex-speed \ + 934-load-transform \ + 935-non-retransformable \ ifneq (,$(filter interp-ac,$(COMPILER_TYPES))) ART_TEST_KNOWN_BROKEN += $(call all-run-test-names,$(TARGET_TYPES),$(RUN_TYPES),$(PREBUILD_TYPES), \ @@ -412,6 +385,7 @@ TEST_ART_BROKEN_INTERPRETER_ACCESS_CHECK_TESTS := # slows down allocations significantly which these tests do a lot. TEST_ART_BROKEN_GCSTRESS_RUN_TESTS := \ 137-cfi \ + 154-gc-loop \ 908-gc-start-finish \ 913-heaps \ 961-default-iface-resolution-gen \ @@ -457,6 +431,7 @@ ART_TEST_KNOWN_BROKEN += $(call all-run-test-names,$(TARGET_TYPES),ndebug,$(PREB # 147-stripped-dex-fallback is disabled because it requires --prebuild. # 554-jit-profile-file is disabled because it needs a primary oat file to know what it should save. # 629-vdex-speed requires compiled code. +# 934 & 935 are broken due to dex2dex issues and app-images TEST_ART_BROKEN_FALLBACK_RUN_TESTS := \ 116-nodex2oat \ 117-nopatchoat \ @@ -466,7 +441,9 @@ TEST_ART_BROKEN_FALLBACK_RUN_TESTS := \ 138-duplicate-classes-check2 \ 147-stripped-dex-fallback \ 554-jit-profile-file \ - 629-vdex-speed + 629-vdex-speed \ + 934-load-transform \ + 935-non-retransformable \ # This test fails without an image. # 018, 961, 964 often time out. b/34369284 @@ -544,10 +521,13 @@ TEST_ART_BROKEN_JIT_TRACING_RUN_TESTS := # Known broken tests for the interpreter. # CFI unwinding expects managed frames. # 629 requires compilation. +# 934 and 935 are broken due to the PreDefine hook not yet inserting them into the classpath. This should be fixed shortly TEST_ART_BROKEN_INTERPRETER_RUN_TESTS := \ 137-cfi \ 554-jit-profile-file \ - 629-vdex-speed + 629-vdex-speed \ + 934-load-transform \ + 935-non-retransformable \ ifneq (,$(filter interpreter,$(COMPILER_TYPES))) ART_TEST_KNOWN_BROKEN += $(call all-run-test-names,$(TARGET_TYPES),$(RUN_TYPES),$(PREBUILD_TYPES), \ @@ -569,17 +549,22 @@ TEST_ART_BROKEN_INTERPRETER_RUN_TESTS := # flaky as JIT tests. This should be fixed once b/33630159 or b/33616143 are # resolved but until then just disable them. Test 916 already checks this # feature for JIT use cases in a way that is resilient to the jit frames. +# 912: b/34655682 +# 934 and 935 are broken due to the PreDefine hook not yet inserting them into the classpath. This should be fixed shortly TEST_ART_BROKEN_JIT_RUN_TESTS := \ 137-cfi \ 629-vdex-speed \ 902-hello-transformation \ 904-object-allocation \ 906-iterate-heap \ + 912-classes \ 914-hello-obsolescence \ 915-obsolete-2 \ 917-fields-transformation \ 919-obsolete-fields \ 926-multi-obsolescence \ + 934-load-transform \ + 935-non-retransformable \ ifneq (,$(filter jit,$(COMPILER_TYPES))) ART_TEST_KNOWN_BROKEN += $(call all-run-test-names,$(TARGET_TYPES),$(RUN_TYPES),$(PREBUILD_TYPES), \ diff --git a/test/ErroneousInit/ErroneousInit.java b/test/ErroneousInit/ErroneousInit.java new file mode 100644 index 0000000000..67b7b204dc --- /dev/null +++ b/test/ErroneousInit/ErroneousInit.java @@ -0,0 +1,23 @@ +/* + * 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. + */ + +class ErroneousInit { + static { + if (true) { + throw new Error(); + } + } +} diff --git a/test/XandY/Y.java b/test/XandY/Y.java index ecead6e35f..2a1f03698c 100644 --- a/test/XandY/Y.java +++ b/test/XandY/Y.java @@ -14,4 +14,8 @@ * limitations under the License. */ -class Y extends X {} +class Y extends X { + static Z z = new Z(); + static class Z { + } +} diff --git a/test/etc/run-test-jar b/test/etc/run-test-jar index 5f1071f658..28fa130443 100755 --- a/test/etc/run-test-jar +++ b/test/etc/run-test-jar @@ -44,7 +44,7 @@ STRIP_DEX="n" SECONDARY_DEX="" TIME_OUT="gdb" # "n" (disabled), "timeout" (use timeout), "gdb" (use gdb) # Value in seconds -if [ "$ART_USE_READ_BARRIER" = "true" ]; then +if [ "$ART_USE_READ_BARRIER" != "false" ]; then TIME_OUT_VALUE=2400 # 40 minutes. else TIME_OUT_VALUE=1200 # 20 minutes. diff --git a/test/run-test b/test/run-test index a913e783d3..a228789701 100755 --- a/test/run-test +++ b/test/run-test @@ -131,6 +131,7 @@ pic_image_suffix="" multi_image_suffix="" android_root="/system" bisection_search="no" +suspend_timeout="500000" # By default we will use optimizing. image_args="" image_suffix="" @@ -219,6 +220,10 @@ while true; do basic_verify="true" gc_stress="true" shift + elif [ "x$1" = "x--suspend-timeout" ]; then + shift + suspend_timeout="$1" + shift elif [ "x$1" = "x--image" ]; then shift image="$1" @@ -402,6 +407,11 @@ noncanonical_tmp_dir=$tmp_dir tmp_dir="`cd $oldwd ; python -c "import os; print os.path.realpath('$tmp_dir')"`" mkdir -p $tmp_dir +# Add thread suspend timeout flag +if [ ! "$runtime" = "jvm" ]; then + run_args="${run_args} --runtime-option -XX:ThreadSuspendTimeout=$suspend_timeout" +fi + if [ "$basic_verify" = "true" ]; then # Set HspaceCompactForOOMMinIntervalMs to zero to run hspace compaction for OOM more frequently in tests. run_args="${run_args} --runtime-option -Xgc:preverify --runtime-option -Xgc:postverify --runtime-option -XX:HspaceCompactForOOMMinIntervalMs=0" @@ -649,6 +659,7 @@ if [ "$usage" = "yes" ]; then echo " --quiet Don't print anything except failure messages" echo " --bisection-search Perform bisection bug search." echo " --vdex Test using vdex as in input to dex2oat. Only works with --prebuild." + echo " --suspend-timeout Change thread suspend timeout ms (default 500000)." ) 1>&2 # Direct to stderr so usage is not printed if --quiet is set. exit 1 fi @@ -722,7 +733,7 @@ if [[ "$TEST_NAME" =~ ^[0-9]+-checker- ]]; then # # TODO: Enable Checker when read barrier support is added to more # architectures (b/12687968). - if [ "x$ART_USE_READ_BARRIER" = xtrue ] \ + if [ "x$ART_USE_READ_BARRIER" != xfalse ] \ && (([ "x$host_mode" = "xyes" ] \ && ! arch_supports_read_barrier "$host_arch_name") \ || ([ "x$target_mode" = "xyes" ] \ diff --git a/test/ti-agent/common_helper.cc b/test/ti-agent/common_helper.cc index 2c6d3eda00..ed82bb04cf 100644 --- a/test/ti-agent/common_helper.cc +++ b/test/ti-agent/common_helper.cc @@ -16,13 +16,18 @@ #include "ti-agent/common_helper.h" +#include <dlfcn.h> #include <stdio.h> #include <sstream> +#include <deque> +#include "android-base/stringprintf.h" #include "art_method.h" #include "jni.h" +#include "jni_internal.h" #include "openjdkjvmti/jvmti.h" #include "scoped_thread_state_change-inl.h" +#include "ScopedLocalRef.h" #include "stack.h" #include "ti-agent/common_load.h" #include "utils.h" @@ -60,17 +65,17 @@ bool JvmtiErrorToException(JNIEnv* env, jvmtiError error) { return true; } -namespace common_redefine { -static void throwRedefinitionError(jvmtiEnv* jvmti, - JNIEnv* env, - jint num_targets, - jclass* target, - jvmtiError res) { +template <bool is_redefine> +static void throwCommonRedefinitionError(jvmtiEnv* jvmti, + JNIEnv* env, + jint num_targets, + jclass* target, + jvmtiError res) { std::stringstream err; char* error = nullptr; jvmti->GetErrorName(res, &error); - err << "Failed to redefine class"; + err << "Failed to " << (is_redefine ? "redefine" : "retransform") << " class"; if (num_targets > 1) { err << "es"; } @@ -92,6 +97,16 @@ static void throwRedefinitionError(jvmtiEnv* jvmti, env->ThrowNew(env->FindClass("java/lang/Exception"), message.c_str()); } +namespace common_redefine { + +static void throwRedefinitionError(jvmtiEnv* jvmti, + JNIEnv* env, + jint num_targets, + jclass* target, + jvmtiError res) { + return throwCommonRedefinitionError<true>(jvmti, env, num_targets, target, res); +} + static void DoMultiClassRedefine(jvmtiEnv* jvmti_env, JNIEnv* env, jint num_redefines, @@ -161,7 +176,7 @@ extern "C" JNIEXPORT void JNICALL Java_Main_doCommonMultiClassRedefinition( dex_files.data()); } -// Don't do anything +// Get all capabilities except those related to retransformation. jint OnLoad(JavaVM* vm, char* options ATTRIBUTE_UNUSED, void* reserved ATTRIBUTE_UNUSED) { @@ -169,10 +184,339 @@ jint OnLoad(JavaVM* vm, printf("Unable to get jvmti env!\n"); return 1; } - SetAllCapabilities(jvmti_env); + jvmtiCapabilities caps; + jvmti_env->GetPotentialCapabilities(&caps); + caps.can_retransform_classes = 0; + caps.can_retransform_any_class = 0; + jvmti_env->AddCapabilities(&caps); return 0; } } // namespace common_redefine +namespace common_retransform { + +struct CommonTransformationResult { + std::vector<unsigned char> class_bytes; + std::vector<unsigned char> dex_bytes; + + CommonTransformationResult(size_t class_size, size_t dex_size) + : class_bytes(class_size), dex_bytes(dex_size) {} + + CommonTransformationResult() = default; + CommonTransformationResult(CommonTransformationResult&&) = default; + CommonTransformationResult(CommonTransformationResult&) = default; +}; + +// Map from class name to transformation result. +std::map<std::string, std::deque<CommonTransformationResult>> gTransformations; + +extern "C" JNIEXPORT void JNICALL Java_Main_addCommonTransformationResult(JNIEnv* env, + jclass, + jstring class_name, + jbyteArray class_array, + jbyteArray dex_array) { + const char* name_chrs = env->GetStringUTFChars(class_name, nullptr); + std::string name_str(name_chrs); + env->ReleaseStringUTFChars(class_name, name_chrs); + CommonTransformationResult trans(env->GetArrayLength(class_array), + env->GetArrayLength(dex_array)); + if (env->ExceptionOccurred()) { + return; + } + env->GetByteArrayRegion(class_array, + 0, + env->GetArrayLength(class_array), + reinterpret_cast<jbyte*>(trans.class_bytes.data())); + if (env->ExceptionOccurred()) { + return; + } + env->GetByteArrayRegion(dex_array, + 0, + env->GetArrayLength(dex_array), + reinterpret_cast<jbyte*>(trans.dex_bytes.data())); + if (env->ExceptionOccurred()) { + return; + } + if (gTransformations.find(name_str) == gTransformations.end()) { + std::deque<CommonTransformationResult> list; + gTransformations[name_str] = std::move(list); + } + gTransformations[name_str].push_back(std::move(trans)); +} + +// The hook we are using. +void JNICALL CommonClassFileLoadHookRetransformable(jvmtiEnv* jvmti_env, + JNIEnv* jni_env ATTRIBUTE_UNUSED, + jclass class_being_redefined ATTRIBUTE_UNUSED, + jobject loader ATTRIBUTE_UNUSED, + const char* name, + jobject protection_domain ATTRIBUTE_UNUSED, + jint class_data_len ATTRIBUTE_UNUSED, + const unsigned char* class_dat ATTRIBUTE_UNUSED, + jint* new_class_data_len, + unsigned char** new_class_data) { + std::string name_str(name); + if (gTransformations.find(name_str) != gTransformations.end() && + gTransformations[name_str].size() > 0) { + CommonTransformationResult& res = gTransformations[name_str][0]; + const std::vector<unsigned char>& desired_array = IsJVM() ? res.class_bytes : res.dex_bytes; + unsigned char* new_data; + CHECK_EQ(JVMTI_ERROR_NONE, jvmti_env->Allocate(desired_array.size(), &new_data)); + memcpy(new_data, desired_array.data(), desired_array.size()); + *new_class_data = new_data; + *new_class_data_len = desired_array.size(); + gTransformations[name_str].pop_front(); + } +} + +extern "C" JNIEXPORT void Java_Main_enableCommonRetransformation(JNIEnv* env, + jclass, + jboolean enable) { + jvmtiError res = jvmti_env->SetEventNotificationMode(enable ? JVMTI_ENABLE : JVMTI_DISABLE, + JVMTI_EVENT_CLASS_FILE_LOAD_HOOK, + nullptr); + if (res != JVMTI_ERROR_NONE) { + JvmtiErrorToException(env, res); + } +} + +static void throwRetransformationError(jvmtiEnv* jvmti, + JNIEnv* env, + jint num_targets, + jclass* targets, + jvmtiError res) { + return throwCommonRedefinitionError<false>(jvmti, env, num_targets, targets, res); +} + +static void DoClassRetransformation(jvmtiEnv* jvmti_env, JNIEnv* env, jobjectArray targets) { + std::vector<jclass> classes; + jint len = env->GetArrayLength(targets); + for (jint i = 0; i < len; i++) { + classes.push_back(static_cast<jclass>(env->GetObjectArrayElement(targets, i))); + } + jvmtiError res = jvmti_env->RetransformClasses(len, classes.data()); + if (res != JVMTI_ERROR_NONE) { + throwRetransformationError(jvmti_env, env, len, classes.data(), res); + } +} + +extern "C" JNIEXPORT void JNICALL Java_Main_doCommonClassRetransformation(JNIEnv* env, + jclass, + jobjectArray targets) { + jvmtiCapabilities caps; + jvmtiError caps_err = jvmti_env->GetCapabilities(&caps); + if (caps_err != JVMTI_ERROR_NONE) { + env->ThrowNew(env->FindClass("java/lang/Exception"), + "Unable to get current jvmtiEnv capabilities"); + return; + } + + // Allocate a new environment if we don't have the can_retransform_classes capability needed to + // call the RetransformClasses function. + jvmtiEnv* real_env = nullptr; + if (caps.can_retransform_classes != 1) { + JavaVM* vm = nullptr; + if (env->GetJavaVM(&vm) != 0 || + vm->GetEnv(reinterpret_cast<void**>(&real_env), JVMTI_VERSION_1_0) != 0) { + env->ThrowNew(env->FindClass("java/lang/Exception"), + "Unable to create temporary jvmtiEnv for RetransformClasses call."); + return; + } + SetAllCapabilities(real_env); + } else { + real_env = jvmti_env; + } + DoClassRetransformation(real_env, env, targets); + if (caps.can_retransform_classes != 1) { + real_env->DisposeEnvironment(); + } +} + +// Get all capabilities except those related to retransformation. +jint OnLoad(JavaVM* vm, + char* options ATTRIBUTE_UNUSED, + void* reserved ATTRIBUTE_UNUSED) { + if (vm->GetEnv(reinterpret_cast<void**>(&jvmti_env), JVMTI_VERSION_1_0)) { + printf("Unable to get jvmti env!\n"); + return 1; + } + SetAllCapabilities(jvmti_env); + jvmtiEventCallbacks cb; + memset(&cb, 0, sizeof(cb)); + cb.ClassFileLoadHook = CommonClassFileLoadHookRetransformable; + if (jvmti_env->SetEventCallbacks(&cb, sizeof(cb)) != JVMTI_ERROR_NONE) { + printf("Unable to set class file load hook cb!\n"); + return 1; + } + return 0; +} + +} // namespace common_retransform + +namespace common_transform { + +using art::common_retransform::CommonClassFileLoadHookRetransformable; + +// Get all capabilities except those related to retransformation. +jint OnLoad(JavaVM* vm, + char* options ATTRIBUTE_UNUSED, + void* reserved ATTRIBUTE_UNUSED) { + if (vm->GetEnv(reinterpret_cast<void**>(&jvmti_env), JVMTI_VERSION_1_0)) { + printf("Unable to get jvmti env!\n"); + return 1; + } + // Don't set the retransform caps + jvmtiCapabilities caps; + jvmti_env->GetPotentialCapabilities(&caps); + caps.can_retransform_classes = 0; + caps.can_retransform_any_class = 0; + jvmti_env->AddCapabilities(&caps); + + // Use the same callback as the retransform test. + jvmtiEventCallbacks cb; + memset(&cb, 0, sizeof(cb)); + cb.ClassFileLoadHook = CommonClassFileLoadHookRetransformable; + if (jvmti_env->SetEventCallbacks(&cb, sizeof(cb)) != JVMTI_ERROR_NONE) { + printf("Unable to set class file load hook cb!\n"); + return 1; + } + return 0; +} + +} // namespace common_transform + +static void BindMethod(jvmtiEnv* jenv, + JNIEnv* env, + jclass klass, + jmethodID method) { + char* name; + char* signature; + jvmtiError name_result = jenv->GetMethodName(method, &name, &signature, nullptr); + if (name_result != JVMTI_ERROR_NONE) { + LOG(FATAL) << "Could not get methods"; + } + + std::string names[2]; + if (IsJVM()) { + // TODO Get the JNI long name + char* klass_name; + jvmtiError klass_result = jenv->GetClassSignature(klass, &klass_name, nullptr); + if (klass_result == JVMTI_ERROR_NONE) { + std::string name_str(name); + std::string klass_str(klass_name); + names[0] = GetJniShortName(klass_str, name_str); + jenv->Deallocate(reinterpret_cast<unsigned char*>(klass_name)); + } else { + LOG(FATAL) << "Could not get class name!"; + } + } else { + ScopedObjectAccess soa(Thread::Current()); + ArtMethod* m = jni::DecodeArtMethod(method); + names[0] = m->JniShortName(); + names[1] = m->JniLongName(); + } + for (const std::string& mangled_name : names) { + if (mangled_name == "") { + continue; + } + void* sym = dlsym(RTLD_DEFAULT, mangled_name.c_str()); + if (sym == nullptr) { + continue; + } + + JNINativeMethod native_method; + native_method.fnPtr = sym; + native_method.name = name; + native_method.signature = signature; + + env->RegisterNatives(klass, &native_method, 1); + + jenv->Deallocate(reinterpret_cast<unsigned char*>(name)); + jenv->Deallocate(reinterpret_cast<unsigned char*>(signature)); + return; + } + + LOG(FATAL) << "Could not find " << names[0]; +} + +static jclass FindClassWithSystemClassLoader(JNIEnv* env, const char* class_name) { + // Find the system classloader. + ScopedLocalRef<jclass> cl_klass(env, env->FindClass("java/lang/ClassLoader")); + if (cl_klass.get() == nullptr) { + return nullptr; + } + jmethodID getsystemclassloader_method = env->GetStaticMethodID(cl_klass.get(), + "getSystemClassLoader", + "()Ljava/lang/ClassLoader;"); + if (getsystemclassloader_method == nullptr) { + return nullptr; + } + ScopedLocalRef<jobject> cl(env, env->CallStaticObjectMethod(cl_klass.get(), + getsystemclassloader_method)); + if (cl.get() == nullptr) { + return nullptr; + } + + // Create a String of the name. + std::string descriptor = android::base::StringPrintf("L%s;", class_name); + std::string dot_name = DescriptorToDot(descriptor.c_str()); + ScopedLocalRef<jstring> name_str(env, env->NewStringUTF(dot_name.c_str())); + + // Call Class.forName with it. + ScopedLocalRef<jclass> c_klass(env, env->FindClass("java/lang/Class")); + if (c_klass.get() == nullptr) { + return nullptr; + } + jmethodID forname_method = env->GetStaticMethodID( + c_klass.get(), + "forName", + "(Ljava/lang/String;ZLjava/lang/ClassLoader;)Ljava/lang/Class;"); + if (forname_method == nullptr) { + return nullptr; + } + + return reinterpret_cast<jclass>(env->CallStaticObjectMethod(c_klass.get(), + forname_method, + name_str.get(), + JNI_FALSE, + cl.get())); +} + +void BindFunctions(jvmtiEnv* jenv, JNIEnv* env, const char* class_name) { + // Use JNI to load the class. + ScopedLocalRef<jclass> klass(env, env->FindClass(class_name)); + if (klass.get() == nullptr) { + // We may be called with the wrong classloader. Try explicitly using the system classloader. + env->ExceptionClear(); + klass.reset(FindClassWithSystemClassLoader(env, class_name)); + if (klass.get() == nullptr) { + LOG(FATAL) << "Could not load " << class_name; + } + } + + // Use JVMTI to get the methods. + jint method_count; + jmethodID* methods; + jvmtiError methods_result = jenv->GetClassMethods(klass.get(), &method_count, &methods); + if (methods_result != JVMTI_ERROR_NONE) { + LOG(FATAL) << "Could not get methods"; + } + + // Check each method. + for (jint i = 0; i < method_count; ++i) { + jint modifiers; + jvmtiError mod_result = jenv->GetMethodModifiers(methods[i], &modifiers); + if (mod_result != JVMTI_ERROR_NONE) { + LOG(FATAL) << "Could not get methods"; + } + constexpr jint kNative = static_cast<jint>(kAccNative); + if ((modifiers & kNative) != 0) { + BindMethod(jenv, env, klass.get(), methods[i]); + } + } + + jenv->Deallocate(reinterpret_cast<unsigned char*>(methods)); +} + } // namespace art diff --git a/test/ti-agent/common_helper.h b/test/ti-agent/common_helper.h index 642ca03274..031850147e 100644 --- a/test/ti-agent/common_helper.h +++ b/test/ti-agent/common_helper.h @@ -28,6 +28,15 @@ jint OnLoad(JavaVM* vm, char* options, void* reserved); } // namespace common_redefine +namespace common_retransform { +jint OnLoad(JavaVM* vm, char* options, void* reserved); +} // namespace common_retransform + +namespace common_transform { +jint OnLoad(JavaVM* vm, char* options, void* reserved); +} // namespace common_transform + + extern bool RuntimeIsJVM; bool IsJVM(); @@ -67,6 +76,12 @@ void SetAllCapabilities(jvmtiEnv* env); bool JvmtiErrorToException(JNIEnv* env, jvmtiError error); +// Load the class through JNI. Inspect it, find all native methods. Construct the corresponding +// mangled name, run dlsym and bind the method. +// +// This will abort on failure. +void BindFunctions(jvmtiEnv* jvmti_env, JNIEnv* env, const char* class_name); + } // namespace art #endif // ART_TEST_TI_AGENT_COMMON_HELPER_H_ diff --git a/test/ti-agent/common_load.cc b/test/ti-agent/common_load.cc index 521e672330..f5074519b8 100644 --- a/test/ti-agent/common_load.cc +++ b/test/ti-agent/common_load.cc @@ -14,6 +14,8 @@ * limitations under the License. */ +#include "common_load.h" + #include <jni.h> #include <stdio.h> // TODO I don't know? @@ -22,7 +24,6 @@ #include "art_method-inl.h" #include "base/logging.h" #include "base/macros.h" -#include "common_load.h" #include "common_helper.h" #include "901-hello-ti-agent/basics.h" @@ -32,6 +33,8 @@ namespace art { jvmtiEnv* jvmti_env; +namespace { + using OnLoad = jint (*)(JavaVM* vm, char* options, void* reserved); using OnAttach = jint (*)(JavaVM* vm, char* options, void* reserved); @@ -41,11 +44,50 @@ struct AgentLib { OnAttach attach; }; +static void JNICALL VMInitCallback(jvmtiEnv *jvmti_env, + JNIEnv* jni_env, + jthread thread ATTRIBUTE_UNUSED) { + // Bind Main native methods. + BindFunctions(jvmti_env, jni_env, "Main"); +} + +// Install a phase callback that will bind JNI functions on VMInit. +bool InstallBindCallback(JavaVM* vm) { + // Use a new jvmtiEnv. Otherwise we might collide with table changes. + jvmtiEnv* install_env; + if (vm->GetEnv(reinterpret_cast<void**>(&install_env), JVMTI_VERSION_1_0) != 0) { + return false; + } + SetAllCapabilities(install_env); + + { + jvmtiEventCallbacks callbacks; + memset(&callbacks, 0, sizeof(jvmtiEventCallbacks)); + callbacks.VMInit = VMInitCallback; + + jvmtiError install_error = install_env->SetEventCallbacks(&callbacks, sizeof(callbacks)); + if (install_error != JVMTI_ERROR_NONE) { + return false; + } + } + + { + jvmtiError enable_error = install_env->SetEventNotificationMode(JVMTI_ENABLE, + JVMTI_EVENT_VM_INIT, + nullptr); + if (enable_error != JVMTI_ERROR_NONE) { + return false; + } + } + + return true; +} + // A trivial OnLoad implementation that only initializes the global jvmti_env. static jint MinimalOnLoad(JavaVM* vm, char* options ATTRIBUTE_UNUSED, void* reserved ATTRIBUTE_UNUSED) { - if (vm->GetEnv(reinterpret_cast<void**>(&jvmti_env), JVMTI_VERSION_1_0)) { + if (vm->GetEnv(reinterpret_cast<void**>(&jvmti_env), JVMTI_VERSION_1_0) != 0) { printf("Unable to get jvmti env!\n"); return 1; } @@ -55,7 +97,7 @@ static jint MinimalOnLoad(JavaVM* vm, // A list of all non-standard the agents we have for testing. All other agents will use // MinimalOnLoad. -AgentLib agents[] = { +static AgentLib agents[] = { { "901-hello-ti-agent", Test901HelloTi::OnLoad, nullptr }, { "902-hello-transformation", common_redefine::OnLoad, nullptr }, { "909-attach-agent", nullptr, Test909AttachAgent::OnAttach }, @@ -64,8 +106,12 @@ AgentLib agents[] = { { "916-obsolete-jit", common_redefine::OnLoad, nullptr }, { "917-fields-transformation", common_redefine::OnLoad, nullptr }, { "919-obsolete-fields", common_redefine::OnLoad, nullptr }, - { "921-hello-failure", common_redefine::OnLoad, nullptr }, + { "921-hello-failure", common_retransform::OnLoad, nullptr }, { "926-multi-obsolescence", common_redefine::OnLoad, nullptr }, + { "930-hello-retransform", common_retransform::OnLoad, nullptr }, + { "932-transform-saves", common_retransform::OnLoad, nullptr }, + { "934-load-transform", common_retransform::OnLoad, nullptr }, + { "935-non-retransformable", common_transform::OnLoad, nullptr }, }; static AgentLib* FindAgent(char* name) { @@ -99,6 +145,28 @@ static void SetIsJVM(char* options) { RuntimeIsJVM = strncmp(options, "jvm", 3) == 0; } +static bool BindFunctionsAttached(JavaVM* vm, const char* class_name) { + // Get a JNIEnv. As the thread is attached, we must not destroy it. + JNIEnv* env; + if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != 0) { + printf("Unable to get JNI env!\n"); + return false; + } + + jvmtiEnv* jenv; + if (vm->GetEnv(reinterpret_cast<void**>(&jenv), JVMTI_VERSION_1_0) != 0) { + printf("Unable to get jvmti env!\n"); + return false; + } + SetAllCapabilities(jenv); + + BindFunctions(jenv, env, class_name); + + return true; +} + +} // namespace + extern "C" JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM* vm, char* options, void* reserved) { char* remaining_options = nullptr; char* name_option = nullptr; @@ -109,6 +177,10 @@ extern "C" JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM* vm, char* options, void* SetIsJVM(remaining_options); + if (!InstallBindCallback(vm)) { + return 1; + } + AgentLib* lib = FindAgent(name_option); OnLoad fn = nullptr; if (lib == nullptr) { @@ -130,6 +202,9 @@ extern "C" JNIEXPORT jint JNICALL Agent_OnAttach(JavaVM* vm, char* options, void printf("Unable to find agent name in options: %s\n", options); return -1; } + + BindFunctionsAttached(vm, "Main"); + AgentLib* lib = FindAgent(name_option); if (lib == nullptr) { printf("Unable to find agent named: %s, add it to the list in test/ti-agent/common_load.cc\n", diff --git a/test/ti-agent/common_load.h b/test/ti-agent/common_load.h index fac94b4c6e..d2544214ec 100644 --- a/test/ti-agent/common_load.h +++ b/test/ti-agent/common_load.h @@ -17,6 +17,7 @@ #ifndef ART_TEST_TI_AGENT_COMMON_LOAD_H_ #define ART_TEST_TI_AGENT_COMMON_LOAD_H_ +#include "jni.h" #include "openjdkjvmti/jvmti.h" namespace art { diff --git a/test/valgrind-suppressions.txt b/test/valgrind-suppressions.txt index fd3c3318ce..c775f98b70 100644 --- a/test/valgrind-suppressions.txt +++ b/test/valgrind-suppressions.txt @@ -22,3 +22,50 @@ ... fun:_ZN3art7Runtime17InitNativeMethodsEv } + +# SigQuit runs libbacktrace +{ + BackTraceReading64 + Memcheck:Addr8 + fun:access_mem_unrestricted + fun:_Uelf64_memory_read + fun:_Uelf64_valid_object_memory + fun:map_create_list + fun:unw_map_local_create + fun:_ZN14UnwindMapLocal5BuildEv + fun:_ZN12BacktraceMap6CreateEib +} +{ + BackTraceReading32 + Memcheck:Addr4 + fun:access_mem_unrestricted + fun:_Uelf32_memory_read + fun:_Uelf32_valid_object_memory + fun:map_create_list + fun:unw_map_local_create + fun:_ZN14UnwindMapLocal5BuildEv + fun:_ZN12BacktraceMap6CreateEib +} +{ + BackTraceReading64 + Memcheck:Addr8 + fun:access_mem_unrestricted + fun:_Uelf64_memory_read + fun:_Uelf64_get_load_base + fun:map_create_list + fun:unw_map_local_create + fun:_ZN14UnwindMapLocal5BuildEv + fun:_ZN12BacktraceMap6CreateEib +} +{ + BackTraceReading32 + Memcheck:Addr4 + fun:access_mem_unrestricted + fun:_Uelf32_memory_read + fun:_Uelf32_get_load_base + fun:map_create_list + fun:unw_map_local_create + fun:_ZN14UnwindMapLocal5BuildEv + fun:_ZN12BacktraceMap6CreateEib +} + diff --git a/tools/dexfuzz/README b/tools/dexfuzz/README index c1cdf1e3f4..78f73f5c11 100644 --- a/tools/dexfuzz/README +++ b/tools/dexfuzz/README @@ -98,7 +98,7 @@ MutateFail - because mutation is a random process, and has attempt thresholds to Timed Out - mutated files that timed out for one or more backends. Current timeouts are: Optimizing - 5 seconds - Intepreter - 30 seconds + Interpreter - 30 seconds (use --short-timeouts to set all backends to 2 seconds.) Successful - mutated files that executed and all backends agreed on the resulting output. NB: if all backends crashed with the same output, this would diff --git a/tools/dexfuzz/src/dexfuzz/Options.java b/tools/dexfuzz/src/dexfuzz/Options.java index 99e03e8ea7..af8a05cdbc 100644 --- a/tools/dexfuzz/src/dexfuzz/Options.java +++ b/tools/dexfuzz/src/dexfuzz/Options.java @@ -50,6 +50,7 @@ public class Options { public static String deviceName = ""; public static boolean usingSpecificDevice = false; public static int repeat = 1; + public static int divergenceRetry = 10; public static String executeDirectory = "/data/art-test"; public static String androidRoot = ""; public static String dumpMutationsFile = "mutations.dump"; @@ -118,6 +119,8 @@ public class Options { Log.always(" --repeat=<n> : Fuzz N programs, executing each one."); Log.always(" --short-timeouts : Shorten timeouts (faster; use if"); Log.always(" you want to focus on output divergences)"); + Log.always(" --divergence-retry=<n> : Number of retries when checking if test is"); + Log.always(" self-divergent. (Default: 10)"); Log.always(" --seed=<seed> : RNG seed to use"); Log.always(" --method-mutations=<n> : Maximum number of mutations to perform on each method."); Log.always(" (Default: 3)"); @@ -239,6 +242,8 @@ public class Options { maxMethods = Integer.parseInt(value); } else if (key.equals("repeat")) { repeat = Integer.parseInt(value); + } else if (key.equals("divergence-retry")) { + divergenceRetry = Integer.parseInt(value); } else if (key.equals("log")) { Log.setLoggingLevel(LogTag.valueOf(value.toUpperCase())); } else if (key.equals("likelihoods")) { @@ -360,6 +365,10 @@ public class Options { Log.error("--repeat must be at least 1!"); return false; } + if (divergenceRetry < 0) { + Log.error("--divergence-retry cannot be negative!"); + return false; + } if (usingProvidedSeed && repeat > 1) { Log.error("Cannot use --repeat with --seed"); return false; diff --git a/tools/dexfuzz/src/dexfuzz/fuzzers/Fuzzer.java b/tools/dexfuzz/src/dexfuzz/fuzzers/Fuzzer.java index 1797d90141..ccc426c5e4 100644 --- a/tools/dexfuzz/src/dexfuzz/fuzzers/Fuzzer.java +++ b/tools/dexfuzz/src/dexfuzz/fuzzers/Fuzzer.java @@ -298,13 +298,13 @@ public abstract class Fuzzer { } private boolean checkGoldenExecutorForSelfDivergence(String programName) { - // Run golden executor 5 times, make sure it always produces + // Run golden executor multiple times, make sure it always produces // the same output, otherwise report that it is self-divergent. // TODO: Instead, produce a list of acceptable outputs, and see if the divergent // outputs of the backends fall within this set of outputs. String seenOutput = null; - for (int i = 0; i < 5; i++) { + for (int i = 0; i < Options.divergenceRetry + 1; i++) { goldenExecutor.reset(); goldenExecutor.execute(programName); String output = goldenExecutor.getResult().getFlattenedOutput(); diff --git a/tools/jfuzz/run_dex_fuzz_test.py b/tools/jfuzz/run_dex_fuzz_test.py index 50c4f20aa7..34a92f6ea8 100755 --- a/tools/jfuzz/run_dex_fuzz_test.py +++ b/tools/jfuzz/run_dex_fuzz_test.py @@ -19,7 +19,7 @@ import os import shutil import sys -from subprocess import check_call +from subprocess import call from tempfile import mkdtemp sys.path.append(os.path.dirname(os.path.dirname( @@ -75,6 +75,9 @@ class DexFuzzTester(object): top = GetEnvVariableOrError('ANDROID_BUILD_TOP') self._dexfuzz_env['PATH'] = (top + '/art/tools/bisection_search:' + self._dexfuzz_env['PATH']) + android_root = GetEnvVariableOrError('ANDROID_HOST_OUT') + self._dexfuzz_env['ANDROID_ROOT'] = android_root + self._dexfuzz_env['LD_LIBRARY_PATH'] = android_root + '/lib' os.chdir(self._dexfuzz_dir) os.mkdir('divergent_programs') os.mkdir('bisection_outputs') @@ -119,24 +122,30 @@ class DexFuzzTester(object): def RunDexFuzz(self): """Starts the DexFuzz testing.""" os.chdir(self._dexfuzz_dir) - dexfuzz_args = ['--inputs=' + self._inputs_dir, '--execute', - '--execute-class=Test', '--repeat=' + str(self._num_tests), - '--dump-output', '--interpreter', '--optimizing', + dexfuzz_args = ['--inputs=' + self._inputs_dir, + '--execute', + '--execute-class=Test', + '--repeat=' + str(self._num_tests), + '--dump-output', '--dump-verify', + '--interpreter', '--optimizing', '--bisection-search'] if self._device is not None: dexfuzz_args += ['--device=' + self._device, '--allarm'] else: dexfuzz_args += ['--host'] # Assume host otherwise. - check_call(['dexfuzz'] + dexfuzz_args, env=self._dexfuzz_env) - # TODO: summarize findings. + cmd = ['dexfuzz'] + dexfuzz_args + print('**** Running ****\n\n', cmd, '\n') + call(cmd, env=self._dexfuzz_env) + print('\n**** Results (report.log) ****\n') + call(['tail', '-n 24', 'report.log']) def main(): # Handle arguments. parser = argparse.ArgumentParser() - parser.add_argument('--num_tests', default=10000, + parser.add_argument('--num_tests', default=1000, type=int, help='number of tests to run') - parser.add_argument('--num_inputs', default=50, + parser.add_argument('--num_inputs', default=10, type=int, help='number of JFuzz program to generate') parser.add_argument('--device', help='target device serial number') args = parser.parse_args() diff --git a/tools/jfuzz/run_jfuzz_test_nightly.py b/tools/jfuzz/run_jfuzz_test_nightly.py index 29595f2886..a9f8365c8f 100755 --- a/tools/jfuzz/run_jfuzz_test_nightly.py +++ b/tools/jfuzz/run_jfuzz_test_nightly.py @@ -26,9 +26,6 @@ from glob import glob from tempfile import mkdtemp from tempfile import TemporaryFile -# Default arguments for run_jfuzz_test.py. -DEFAULT_ARGS = ['--num_tests=20000'] - # run_jfuzz_test.py success string. SUCCESS_STRING = 'success (no divergences)' @@ -36,17 +33,22 @@ SUCCESS_STRING = 'success (no divergences)' NOT_FOUND = -1 def main(argv): + # Set up. cwd = os.path.dirname(os.path.realpath(__file__)) - cmd = [cwd + '/run_jfuzz_test.py'] + DEFAULT_ARGS + cmd = [cwd + '/run_jfuzz_test.py'] parser = argparse.ArgumentParser() parser.add_argument('--num_proc', default=8, type=int, help='number of processes to run') # Unknown arguments are passed to run_jfuzz_test.py. (args, unknown_args) = parser.parse_known_args() + # Run processes. + cmd = cmd + unknown_args + print('\n**** Running ****\n\n', cmd, '\n') output_files = [TemporaryFile('wb+') for _ in range(args.num_proc)] processes = [] - for output_file in output_files: - processes.append(subprocess.Popen(cmd + unknown_args, stdout=output_file, + for i, output_file in enumerate(output_files): + print('Tester', i) + processes.append(subprocess.Popen(cmd, stdout=output_file, stderr=subprocess.STDOUT)) try: # Wait for processes to terminate. @@ -56,6 +58,7 @@ def main(argv): for proc in processes: proc.kill() # Output results. + print('\n**** Results ****\n') output_dirs = [] for i, output_file in enumerate(output_files): output_file.seek(0) @@ -65,20 +68,24 @@ def main(argv): directory_match = re.search(r'Directory[^:]*: ([^\n]+)\n', output_str) if directory_match: output_dirs.append(directory_match.group(1)) - print('Tester', i) if output_str.find(SUCCESS_STRING) == NOT_FOUND: - print(output_str) + print('Tester', i, output_str) else: - print(SUCCESS_STRING) + print('Tester', i, SUCCESS_STRING) # Gather divergences. global_out_dir = mkdtemp('jfuzz_nightly') - divergence_nr = 1 + divergence_nr = 0 for out_dir in output_dirs: for divergence_dir in glob(out_dir + '/divergence*/'): + divergence_nr += 1 shutil.copytree(divergence_dir, global_out_dir + '/divergence' + str(divergence_nr)) - divergence_nr += 1 - print('Global output directory:', global_out_dir) + if divergence_nr > 0: + print('\n!!!! Divergences !!!!', divergence_nr) + else: + print ('\nSuccess') + print('\nGlobal output directory:', global_out_dir) + print() if __name__ == '__main__': main(sys.argv) diff --git a/tools/stream-trace-converter.py b/tools/stream-trace-converter.py index 951b05bf33..7e341f2bcf 100755 --- a/tools/stream-trace-converter.py +++ b/tools/stream-trace-converter.py @@ -124,12 +124,20 @@ class Rewriter: self._threads.append('%d\t%s\n' % (tid, str)) print 'New thread: %d/%s' % (tid, str) + def ProcessTraceSummary(self, input): + summaryLength = ReadIntLE(input) + str = input.read(summaryLength) + self._summary = str + print 'Summary: \"%s\"' % str + def ProcessSpecial(self, input): code = ord(input.read(1)) if code == 1: self.ProcessMethod(input) elif code == 2: self.ProcessThread(input) + elif code == 3: + self.ProcessTraceSummary(input) else: raise MyException("Unknown special!") @@ -147,9 +155,24 @@ class Rewriter: print 'Buffer underrun, file was probably truncated. Results should still be usable.' def Finalize(self, header): - header.write('*threads\n') - for t in self._threads: - header.write(t) + # If the summary is present in the input file, use it as the header except + # for the methods section which is emtpy in the input file. If not present, + # apppend header with the threads that are recorded in the input stream. + if (self._summary): + # Erase the contents that's already written earlier by PrintHeader. + header.seek(0) + header.truncate() + # Copy the lines from the input summary to the output header until + # the methods section is seen. + for line in self._summary.splitlines(True): + if line == "*methods\n": + break + else: + header.write(line) + else: + header.write('*threads\n') + for t in self._threads: + header.write(t) header.write('*methods\n') for m in self._methods: header.write(m) @@ -166,6 +189,7 @@ class Rewriter: self._methods = [] self._threads = [] + self._summary = None self.Process(input, body) self.Finalize(header) |