diff options
| -rw-r--r-- | compiler/jit/jit_compiler.cc | 2 | ||||
| -rw-r--r-- | dex2oat/dex2oat_test.cc | 2 | ||||
| -rw-r--r-- | runtime/class_linker.cc | 22 | ||||
| -rw-r--r-- | runtime/debugger.cc | 39 | ||||
| -rw-r--r-- | runtime/entrypoints/quick/quick_trampoline_entrypoints.cc | 22 | ||||
| -rw-r--r-- | runtime/instrumentation.cc | 38 | ||||
| -rw-r--r-- | runtime/instrumentation.h | 4 | ||||
| -rw-r--r-- | runtime/jit/jit_code_cache.cc | 2 | ||||
| -rw-r--r-- | runtime/native/dalvik_system_ZygoteHooks.cc | 17 | ||||
| -rw-r--r-- | runtime/oat_file_assistant.cc | 2 | ||||
| -rw-r--r-- | runtime/parsed_options.cc | 2 | ||||
| -rw-r--r-- | runtime/quick_exception_handler.cc | 4 | ||||
| -rw-r--r-- | runtime/runtime.cc | 76 | ||||
| -rw-r--r-- | runtime/runtime.h | 23 | ||||
| -rw-r--r-- | runtime/runtime_options.def | 1 | ||||
| -rw-r--r-- | runtime/thread.cc | 5 | ||||
| -rw-r--r-- | test/466-get-live-vreg/get_live_vreg_jni.cc | 2 | ||||
| -rwxr-xr-x | test/909-attach-agent/run | 6 | ||||
| -rw-r--r-- | test/Android.run-test.mk | 1 | ||||
| -rwxr-xr-x | test/etc/run-test-jar | 2 |
20 files changed, 159 insertions, 113 deletions
diff --git a/compiler/jit/jit_compiler.cc b/compiler/jit/jit_compiler.cc index 148ce4f9ee..eaac0b40f5 100644 --- a/compiler/jit/jit_compiler.cc +++ b/compiler/jit/jit_compiler.cc @@ -102,7 +102,7 @@ JitCompiler::JitCompiler() { /* no_inline_from */ nullptr, /* include_patch_information */ false, CompilerOptions::kDefaultTopKProfileThreshold, - Runtime::Current()->IsDebuggable(), + Runtime::Current()->IsJavaDebuggable(), CompilerOptions::kDefaultGenerateDebugInfo, /* implicit_null_checks */ true, /* implicit_so_checks */ true, diff --git a/dex2oat/dex2oat_test.cc b/dex2oat/dex2oat_test.cc index e86e560b1a..864ca763c3 100644 --- a/dex2oat/dex2oat_test.cc +++ b/dex2oat/dex2oat_test.cc @@ -125,7 +125,7 @@ class Dex2oatTest : public Dex2oatEnvironmentTest { class_path = OatFile::kSpecialSharedLibrary; } argv.push_back(class_path); - if (runtime->IsDebuggable()) { + if (runtime->IsJavaDebuggable()) { argv.push_back("--debuggable"); } runtime->AddCurrentRuntimeFeaturesAsDex2OatArguments(&argv); diff --git a/runtime/class_linker.cc b/runtime/class_linker.cc index 02b26c6568..edd6e3b522 100644 --- a/runtime/class_linker.cc +++ b/runtime/class_linker.cc @@ -2862,9 +2862,12 @@ bool ClassLinker::ShouldUseInterpreterEntrypoint(ArtMethod* method, const void* return true; } - if (runtime->IsFullyDeoptable()) { - // We need to be able to deoptimize at any time so we should always just ignore precompiled - // code and go to the interpreter assuming we don't already have jitted code. + if (runtime->IsJavaDebuggable()) { + // For simplicity, we ignore precompiled code and go to the interpreter + // assuming we don't already have jitted code. + // We could look at the oat file where `quick_code` is being defined, + // and check whether it's been compiled debuggable, but we decided to + // only rely on the JIT for debuggable apps. jit::Jit* jit = Runtime::Current()->GetJit(); return (jit == nullptr) || !jit->GetCodeCache()->ContainsPc(quick_code); } @@ -2872,18 +2875,13 @@ bool ClassLinker::ShouldUseInterpreterEntrypoint(ArtMethod* method, const void* if (runtime->IsNativeDebuggable()) { DCHECK(runtime->UseJitCompilation() && runtime->GetJit()->JitAtFirstUse()); // If we are doing native debugging, ignore application's AOT code, - // since we want to JIT it with extra stackmaps for native debugging. - // On the other hand, keep all AOT code from the boot image, since the - // blocking JIT would results in non-negligible performance impact. + // since we want to JIT it (at first use) with extra stackmaps for native + // debugging. We keep however all AOT code from the boot image, + // since the JIT-at-first-use is blocking and would result in non-negligible + // startup performance impact. return !runtime->GetHeap()->IsInBootImageOatFile(quick_code); } - if (Dbg::IsDebuggerActive()) { - // Boot image classes may be AOT-compiled as non-debuggable. - // This is not suitable for the Java debugger, so ignore the AOT code. - return runtime->GetHeap()->IsInBootImageOatFile(quick_code); - } - return false; } diff --git a/runtime/debugger.cc b/runtime/debugger.cc index 22a31635a6..1a0cec075c 100644 --- a/runtime/debugger.cc +++ b/runtime/debugger.cc @@ -588,29 +588,6 @@ bool Dbg::RequiresDeoptimization() { return !Runtime::Current()->GetInstrumentation()->IsForcedInterpretOnly(); } -// Used to patch boot image method entry point to interpreter bridge. -class UpdateEntryPointsClassVisitor : public ClassVisitor { - public: - explicit UpdateEntryPointsClassVisitor(instrumentation::Instrumentation* instrumentation) - : instrumentation_(instrumentation) {} - - bool operator()(ObjPtr<mirror::Class> klass) OVERRIDE REQUIRES(Locks::mutator_lock_) { - auto pointer_size = Runtime::Current()->GetClassLinker()->GetImagePointerSize(); - for (auto& m : klass->GetMethods(pointer_size)) { - const void* code = m.GetEntryPointFromQuickCompiledCode(); - if (Runtime::Current()->GetHeap()->IsInBootImageOatFile(code) && - !m.IsNative() && - !m.IsProxyMethod()) { - instrumentation_->UpdateMethodsCodeFromDebugger(&m, GetQuickToInterpreterBridge()); - } - } - return true; - } - - private: - instrumentation::Instrumentation* const instrumentation_; -}; - void Dbg::GoActive() { // Enable all debugging features, including scans for breakpoints. // This is a no-op if we're already active. @@ -639,14 +616,16 @@ void Dbg::GoActive() { } Runtime* runtime = Runtime::Current(); - // Since boot image code may be AOT compiled as not debuggable, we need to patch - // entry points of methods in boot image to interpreter bridge. - // However, the performance cost of this is non-negligible during native-debugging due to the + // Best effort deoptimization if the runtime is non-Java debuggable. This happens when + // ro.debuggable is set, but the application is not debuggable, or when a standalone + // dalvikvm invocation is not passed the debuggable option (-Xcompiler-option --debuggable). + // + // The performance cost of this is non-negligible during native-debugging due to the // forced JIT, so we keep the AOT code in that case in exchange for limited native debugging. - if (!runtime->GetInstrumentation()->IsForcedInterpretOnly() && !runtime->IsNativeDebuggable()) { - ScopedObjectAccess soa(self); - UpdateEntryPointsClassVisitor visitor(runtime->GetInstrumentation()); - runtime->GetClassLinker()->VisitClasses(&visitor); + if (!runtime->IsJavaDebuggable() && + !runtime->GetInstrumentation()->IsForcedInterpretOnly() && + !runtime->IsNativeDebuggable()) { + runtime->DeoptimizeBootImage(); } ScopedSuspendAll ssa(__FUNCTION__); diff --git a/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc b/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc index eb76fb6b88..bde9009f7b 100644 --- a/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc +++ b/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc @@ -781,15 +781,19 @@ extern "C" uint64_t artQuickToInterpreterBridge(ArtMethod* method, Thread* self, // If caller_pc is the instrumentation exit stub, the stub will check to see if deoptimization // should be done and it knows the real return pc. if (UNLIKELY(caller_pc != reinterpret_cast<uintptr_t>(GetQuickInstrumentationExitPc()) && - Dbg::IsForcedInterpreterNeededForUpcall(self, caller) && - Runtime::Current()->IsDeoptimizeable(caller_pc))) { - // Push the context of the deoptimization stack so we can restore the return value and the - // exception before executing the deoptimized frames. - self->PushDeoptimizationContext( - result, shorty[0] == 'L', /* from_code */ false, self->GetException()); - - // Set special exception to cause deoptimization. - self->SetException(Thread::GetDeoptimizationException()); + Dbg::IsForcedInterpreterNeededForUpcall(self, caller))) { + if (!Runtime::Current()->IsAsyncDeoptimizeable(caller_pc)) { + LOG(WARNING) << "Got a deoptimization request on un-deoptimizable method " + << caller->PrettyMethod(); + } else { + // Push the context of the deoptimization stack so we can restore the return value and the + // exception before executing the deoptimized frames. + self->PushDeoptimizationContext( + result, shorty[0] == 'L', /* from_code */ false, self->GetException()); + + // Set special exception to cause deoptimization. + self->SetException(Thread::GetDeoptimizationException()); + } } // No need to restore the args since the method has already been run by the interpreter. diff --git a/runtime/instrumentation.cc b/runtime/instrumentation.cc index bbd6d352d3..3005c2a2f1 100644 --- a/runtime/instrumentation.cc +++ b/runtime/instrumentation.cc @@ -105,10 +105,9 @@ static void UpdateEntrypoints(ArtMethod* method, const void* quick_code) method->SetEntryPointFromQuickCompiledCode(quick_code); } -bool Instrumentation::NeedDebugVersionForBootImageCode(ArtMethod* method, const void* code) const - REQUIRES_SHARED(Locks::mutator_lock_) { +bool Instrumentation::NeedDebugVersionFor(ArtMethod* method) const REQUIRES_SHARED(Locks::mutator_lock_) { return Dbg::IsDebuggerActive() && - Runtime::Current()->GetHeap()->IsInBootImageOatFile(code) && + Runtime::Current()->IsJavaDebuggable() && !method->IsNative() && !method->IsProxyMethod(); } @@ -132,9 +131,10 @@ void Instrumentation::InstallStubsForMethod(ArtMethod* method) { if ((forced_interpret_only_ || IsDeoptimized(method)) && !method->IsNative()) { new_quick_code = GetQuickToInterpreterBridge(); } else if (is_class_initialized || !method->IsStatic() || method->IsConstructor()) { - new_quick_code = class_linker->GetQuickOatCodeFor(method); - if (NeedDebugVersionForBootImageCode(method, new_quick_code)) { + if (NeedDebugVersionFor(method)) { new_quick_code = GetQuickToInterpreterBridge(); + } else { + new_quick_code = class_linker->GetQuickOatCodeFor(method); } } else { new_quick_code = GetQuickResolutionStub(); @@ -148,13 +148,14 @@ void Instrumentation::InstallStubsForMethod(ArtMethod* method) { // class, all its static methods code will be set to the instrumentation entry point. // For more details, see ClassLinker::FixupStaticTrampolines. if (is_class_initialized || !method->IsStatic() || method->IsConstructor()) { - new_quick_code = class_linker->GetQuickOatCodeFor(method); - if (NeedDebugVersionForBootImageCode(method, new_quick_code)) { + if (NeedDebugVersionFor(method)) { // Oat code should not be used. Don't install instrumentation stub and // use interpreter for instrumentation. new_quick_code = GetQuickToInterpreterBridge(); } else if (entry_exit_stubs_installed_) { new_quick_code = GetQuickInstrumentationEntryPoint(); + } else { + new_quick_code = class_linker->GetQuickOatCodeFor(method); } } else { new_quick_code = GetQuickResolutionStub(); @@ -731,10 +732,12 @@ void Instrumentation::UpdateMethodsCode(ArtMethod* method, const void* quick_cod UpdateMethodsCodeImpl(method, quick_code); } -void Instrumentation::UpdateMethodsCodeFromDebugger(ArtMethod* method, const void* quick_code) { - // When debugger attaches, we may update the entry points of all methods of a class - // to the interpreter bridge. A method's declaring class might not be in resolved - // state yet in that case. +void Instrumentation::UpdateMethodsCodeForJavaDebuggable(ArtMethod* method, + const void* quick_code) { + // When the runtime is set to Java debuggable, we may update the entry points of + // all methods of a class to the interpreter bridge. A method's declaring class + // might not be in resolved state yet in that case, so we bypass the DCHECK in + // UpdateMethodsCode. UpdateMethodsCodeImpl(method, quick_code); } @@ -819,10 +822,9 @@ void Instrumentation::Undeoptimize(ArtMethod* method) { !method->GetDeclaringClass()->IsInitialized()) { UpdateEntrypoints(method, GetQuickResolutionStub()); } else { - const void* quick_code = class_linker->GetQuickOatCodeFor(method); - if (NeedDebugVersionForBootImageCode(method, quick_code)) { - quick_code = GetQuickToInterpreterBridge(); - } + const void* quick_code = NeedDebugVersionFor(method) + ? GetQuickToInterpreterBridge() + : class_linker->GetQuickOatCodeFor(method); UpdateEntrypoints(method, quick_code); } @@ -1114,7 +1116,7 @@ TwoWordReturn Instrumentation::PopInstrumentationStackFrame(Thread* self, uintpt bool deoptimize = (visitor.caller != nullptr) && (interpreter_stubs_installed_ || IsDeoptimized(visitor.caller) || Dbg::IsForcedInterpreterNeededForUpcall(self, visitor.caller)); - if (deoptimize && Runtime::Current()->IsDeoptimizeable(*return_pc)) { + if (deoptimize && Runtime::Current()->IsAsyncDeoptimizeable(*return_pc)) { if (kVerboseInstrumentation) { LOG(INFO) << "Deoptimizing " << visitor.caller->PrettyMethod() @@ -1132,6 +1134,10 @@ TwoWordReturn Instrumentation::PopInstrumentationStackFrame(Thread* self, uintpt return GetTwoWordSuccessValue(*return_pc, reinterpret_cast<uintptr_t>(GetQuickDeoptimizationEntryPoint())); } else { + if (deoptimize && !Runtime::Current()->IsAsyncDeoptimizeable(*return_pc)) { + LOG(WARNING) << "Got a deoptimization request on un-deoptimizable " << method->PrettyMethod() + << " at PC " << reinterpret_cast<void*>(*return_pc); + } if (kVerboseInstrumentation) { LOG(INFO) << "Returning from " << method->PrettyMethod() << " to PC " << reinterpret_cast<void*>(*return_pc); diff --git a/runtime/instrumentation.h b/runtime/instrumentation.h index 05c0aaa081..39606f73f6 100644 --- a/runtime/instrumentation.h +++ b/runtime/instrumentation.h @@ -239,7 +239,7 @@ class Instrumentation { REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(!deoptimized_methods_lock_); // Update the code of a method respecting any installed stubs from debugger. - void UpdateMethodsCodeFromDebugger(ArtMethod* method, const void* quick_code) + void UpdateMethodsCodeForJavaDebuggable(ArtMethod* method, const void* quick_code) REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(!deoptimized_methods_lock_); // Get the quick code for the given method. More efficient than asking the class linker as it @@ -264,7 +264,7 @@ class Instrumentation { // Code is in boot image oat file which isn't compiled as debuggable. // Need debug version (interpreter or jitted) if that's the case. - bool NeedDebugVersionForBootImageCode(ArtMethod* method, const void* code) const + bool NeedDebugVersionFor(ArtMethod* method) const REQUIRES_SHARED(Locks::mutator_lock_); bool AreExitStubsInstalled() const { diff --git a/runtime/jit/jit_code_cache.cc b/runtime/jit/jit_code_cache.cc index 6336cddc07..45611a93f7 100644 --- a/runtime/jit/jit_code_cache.cc +++ b/runtime/jit/jit_code_cache.cc @@ -594,7 +594,7 @@ uint8_t* JitCodeCache::CommitCodeInternal(Thread* self, VLOG(jit) << "JIT discarded jitted code due to invalid single-implementation assumptions."; return nullptr; } - DCHECK(cha_single_implementation_list.empty() || !Runtime::Current()->IsDebuggable()) + DCHECK(cha_single_implementation_list.empty() || !Runtime::Current()->IsJavaDebuggable()) << "Should not be using cha on debuggable apps/runs!"; for (ArtMethod* single_impl : cha_single_implementation_list) { diff --git a/runtime/native/dalvik_system_ZygoteHooks.cc b/runtime/native/dalvik_system_ZygoteHooks.cc index 10fc90bc27..fd22d9e646 100644 --- a/runtime/native/dalvik_system_ZygoteHooks.cc +++ b/runtime/native/dalvik_system_ZygoteHooks.cc @@ -71,7 +71,7 @@ static void EnableDebugger() { static void EnableDebugFeatures(uint32_t debug_flags) { // Must match values in com.android.internal.os.Zygote. enum { - DEBUG_ENABLE_DEBUGGER = 1, + DEBUG_ENABLE_JDWP = 1, DEBUG_ENABLE_CHECKJNI = 1 << 1, DEBUG_ENABLE_ASSERT = 1 << 2, DEBUG_ENABLE_SAFEMODE = 1 << 3, @@ -79,6 +79,7 @@ static void EnableDebugFeatures(uint32_t debug_flags) { DEBUG_GENERATE_DEBUG_INFO = 1 << 5, DEBUG_ALWAYS_JIT = 1 << 6, DEBUG_NATIVE_DEBUGGABLE = 1 << 7, + DEBUG_JAVA_DEBUGGABLE = 1 << 8, }; Runtime* const runtime = Runtime::Current(); @@ -100,11 +101,11 @@ static void EnableDebugFeatures(uint32_t debug_flags) { debug_flags &= ~DEBUG_ENABLE_JNI_LOGGING; } - Dbg::SetJdwpAllowed((debug_flags & DEBUG_ENABLE_DEBUGGER) != 0); - if ((debug_flags & DEBUG_ENABLE_DEBUGGER) != 0) { + Dbg::SetJdwpAllowed((debug_flags & DEBUG_ENABLE_JDWP) != 0); + if ((debug_flags & DEBUG_ENABLE_JDWP) != 0) { EnableDebugger(); } - debug_flags &= ~DEBUG_ENABLE_DEBUGGER; + debug_flags &= ~DEBUG_ENABLE_JDWP; const bool safe_mode = (debug_flags & DEBUG_ENABLE_SAFEMODE) != 0; if (safe_mode) { @@ -130,6 +131,14 @@ static void EnableDebugFeatures(uint32_t debug_flags) { debug_flags &= ~DEBUG_ALWAYS_JIT; } + if ((debug_flags & DEBUG_JAVA_DEBUGGABLE) != 0) { + runtime->AddCompilerOption("--debuggable"); + runtime->SetJavaDebuggable(true); + // Deoptimize the boot image as it may be non-debuggable. + runtime->DeoptimizeBootImage(); + debug_flags &= ~DEBUG_JAVA_DEBUGGABLE; + } + if ((debug_flags & DEBUG_NATIVE_DEBUGGABLE) != 0) { runtime->AddCompilerOption("--debuggable"); runtime->AddCompilerOption("--generate-debug-info"); diff --git a/runtime/oat_file_assistant.cc b/runtime/oat_file_assistant.cc index 8554fa2693..b19ace5464 100644 --- a/runtime/oat_file_assistant.cc +++ b/runtime/oat_file_assistant.cc @@ -530,7 +530,7 @@ bool OatFileAssistant::Dex2Oat(const std::vector<std::string>& args, class_path = OatFile::kSpecialSharedLibrary; } argv.push_back(class_path); - if (runtime->IsDebuggable()) { + if (runtime->IsJavaDebuggable()) { argv.push_back("--debuggable"); } runtime->AddCurrentRuntimeFeaturesAsDex2OatArguments(&argv); diff --git a/runtime/parsed_options.cc b/runtime/parsed_options.cc index ccc5f7a8ee..9113f83cd4 100644 --- a/runtime/parsed_options.cc +++ b/runtime/parsed_options.cc @@ -300,8 +300,6 @@ std::unique_ptr<RuntimeParser> ParsedOptions::MakeParser(bool ignore_unrecognize .Define("-Xplugin:_") .WithType<std::vector<Plugin>>().AppendValues() .IntoKey(M::Plugins) - .Define("-Xfully-deoptable") - .IntoKey(M::FullyDeoptable) .Define("-XX:ThreadSuspendTimeout=_") // in ms .WithType<MillisecondsToNanoseconds>() // store as ns .IntoKey(M::ThreadSuspendTimeout) diff --git a/runtime/quick_exception_handler.cc b/runtime/quick_exception_handler.cc index b809c3eb56..110c74b884 100644 --- a/runtime/quick_exception_handler.cc +++ b/runtime/quick_exception_handler.cc @@ -347,9 +347,11 @@ class DeoptimizeStackVisitor FINAL : public StackVisitor { callee_method_ = method; return true; } else if (!single_frame_deopt_ && - !Runtime::Current()->IsDeoptimizeable(GetCurrentQuickFramePc())) { + !Runtime::Current()->IsAsyncDeoptimizeable(GetCurrentQuickFramePc())) { // We hit some code that's not deoptimizeable. However, Single-frame deoptimization triggered // from compiled code is always allowed since HDeoptimize always saves the full environment. + LOG(WARNING) << "Got request to deoptimize un-deoptimizable method " + << method->PrettyMethod(); FinishStackWalk(); return false; // End stack walk. } else { diff --git a/runtime/runtime.cc b/runtime/runtime.cc index 5e008a8bb8..0bf30184ea 100644 --- a/runtime/runtime.cc +++ b/runtime/runtime.cc @@ -244,7 +244,7 @@ Runtime::Runtime() force_native_bridge_(false), is_native_bridge_loaded_(false), is_native_debuggable_(false), - is_fully_deoptable_(false), + is_java_debuggable_(false), zygote_max_failed_boots_(0), experimental_flags_(ExperimentalFlags::kNone), oat_file_manager_(nullptr), @@ -825,14 +825,6 @@ bool Runtime::IsShuttingDown(Thread* self) { return IsShuttingDownLocked(); } -bool Runtime::IsDebuggable() const { - if (IsFullyDeoptable()) { - return true; - } - const OatFile* oat_file = GetOatFileManager().GetPrimaryOatFile(); - return oat_file != nullptr && oat_file->IsDebuggable(); -} - void Runtime::StartDaemonThreads() { ScopedTrace trace(__FUNCTION__); VLOG(startup) << "Runtime::StartDaemonThreads entering"; @@ -1038,6 +1030,12 @@ bool Runtime::Init(RuntimeArgumentMap&& runtime_options_in) { compiler_executable_ = runtime_options.ReleaseOrDefault(Opt::Compiler); compiler_options_ = runtime_options.ReleaseOrDefault(Opt::CompilerOptions); + for (StringPiece option : Runtime::Current()->GetCompilerOptions()) { + if (option.starts_with("--debuggable")) { + SetJavaDebuggable(true); + break; + } + } image_compiler_options_ = runtime_options.ReleaseOrDefault(Opt::ImageCompilerOptions); image_location_ = runtime_options.GetOrDefault(Opt::Image); @@ -1052,8 +1050,6 @@ bool Runtime::Init(RuntimeArgumentMap&& runtime_options_in) { verify_ = runtime_options.GetOrDefault(Opt::Verify); allow_dex_file_fallback_ = !runtime_options.Exists(Opt::NoDexFileFallback); - is_fully_deoptable_ = runtime_options.Exists(Opt::FullyDeoptable); - no_sig_chain_ = runtime_options.Exists(Opt::NoSigChain); force_native_bridge_ = runtime_options.Exists(Opt::ForceNativeBridge); @@ -1258,6 +1254,11 @@ bool Runtime::Init(RuntimeArgumentMap&& runtime_options_in) { ScopedTrace trace2("AddImageStringsToTable"); GetInternTable()->AddImagesStringsToTable(heap_->GetBootImageSpaces()); } + if (IsJavaDebuggable()) { + // Now that we have loaded the boot image, deoptimize its methods if we are running + // debuggable, as the code may have been compiled non-debuggable. + DeoptimizeBootImage(); + } } else { std::vector<std::string> dex_filenames; Split(boot_class_path_string_, ':', &dex_filenames); @@ -1404,7 +1405,7 @@ static bool EnsureJvmtiPlugin(Runtime* runtime, } // Is the process debuggable? Otherwise, do not attempt to load the plugin. - if (!runtime->IsDebuggable()) { + if (!runtime->IsJavaDebuggable()) { *error_msg = "Process is not debuggable."; return false; } @@ -2204,9 +2205,15 @@ bool Runtime::IsVerificationSoftFail() const { return verify_ == verifier::VerifyMode::kSoftFail; } -bool Runtime::IsDeoptimizeable(uintptr_t code) const - REQUIRES_SHARED(Locks::mutator_lock_) { - return !heap_->IsInBootImageOatFile(reinterpret_cast<void *>(code)); +bool Runtime::IsAsyncDeoptimizeable(uintptr_t code) const { + // We only support async deopt (ie the compiled code is not explicitly asking for + // deopt, but something else like the debugger) in debuggable JIT code. + // We could look at the oat file where `code` is being defined, + // and check whether it's been compiled debuggable, but we decided to + // only rely on the JIT for debuggable apps. + return IsJavaDebuggable() && + GetJit() != nullptr && + GetJit()->GetCodeCache()->ContainsPc(reinterpret_cast<const void*>(code)); } LinearAlloc* Runtime::CreateLinearAlloc() { @@ -2290,4 +2297,43 @@ RuntimeCallbacks* Runtime::GetRuntimeCallbacks() { return callbacks_.get(); } +// Used to patch boot image method entry point to interpreter bridge. +class UpdateEntryPointsClassVisitor : public ClassVisitor { + public: + explicit UpdateEntryPointsClassVisitor(instrumentation::Instrumentation* instrumentation) + : instrumentation_(instrumentation) {} + + bool operator()(ObjPtr<mirror::Class> klass) OVERRIDE REQUIRES(Locks::mutator_lock_) { + auto pointer_size = Runtime::Current()->GetClassLinker()->GetImagePointerSize(); + for (auto& m : klass->GetMethods(pointer_size)) { + const void* code = m.GetEntryPointFromQuickCompiledCode(); + if (Runtime::Current()->GetHeap()->IsInBootImageOatFile(code) && + !m.IsNative() && + !m.IsProxyMethod()) { + instrumentation_->UpdateMethodsCodeForJavaDebuggable(&m, GetQuickToInterpreterBridge()); + } + } + return true; + } + + private: + instrumentation::Instrumentation* const instrumentation_; +}; + +void Runtime::SetJavaDebuggable(bool value) { + is_java_debuggable_ = value; + // Do not call DeoptimizeBootImage just yet, the runtime may still be starting up. +} + +void Runtime::DeoptimizeBootImage() { + // If we've already started and we are setting this runtime to debuggable, + // we patch entry points of methods in boot image to interpreter bridge, as + // boot image code may be AOT compiled as not debuggable. + if (!GetInstrumentation()->IsForcedInterpretOnly()) { + ScopedObjectAccess soa(Thread::Current()); + UpdateEntryPointsClassVisitor visitor(GetInstrumentation()); + GetClassLinker()->VisitClasses(&visitor); + } +} + } // namespace art diff --git a/runtime/runtime.h b/runtime/runtime.h index f7d6810ff5..30b1756d5d 100644 --- a/runtime/runtime.h +++ b/runtime/runtime.h @@ -434,7 +434,7 @@ class Runtime { kInitialize }; - jit::Jit* GetJit() { + jit::Jit* GetJit() const { return jit_.get(); } @@ -569,15 +569,14 @@ class Runtime { return jit_options_.get(); } - bool IsDebuggable() const; - - bool IsFullyDeoptable() const { - return is_fully_deoptable_; + bool IsJavaDebuggable() const { + return is_java_debuggable_; } - void SetFullyDeoptable(bool value) { - is_fully_deoptable_ = value; - } + void SetJavaDebuggable(bool value); + + // Deoptimize the boot image, called for Java debuggable apps. + void DeoptimizeBootImage(); bool IsNativeDebuggable() const { return is_native_debuggable_; @@ -639,9 +638,9 @@ class Runtime { return zygote_no_threads_; } - // Returns if the code can be deoptimized. Code may be compiled with some + // Returns if the code can be deoptimized asynchronously. Code may be compiled with some // optimization that makes it impossible to deoptimize. - bool IsDeoptimizeable(uintptr_t code) const REQUIRES_SHARED(Locks::mutator_lock_); + bool IsAsyncDeoptimizeable(uintptr_t code) const REQUIRES_SHARED(Locks::mutator_lock_); // Returns a saved copy of the environment (getenv/setenv values). // Used by Fork to protect against overwriting LD_LIBRARY_PATH, etc. @@ -863,8 +862,8 @@ class Runtime { // Whether we are running under native debugger. bool is_native_debuggable_; - // Whether we are expected to be deoptable at all points. - bool is_fully_deoptable_; + // Whether Java code needs to be debuggable. + bool is_java_debuggable_; // The maximum number of failed boots we allow before pruning the dalvik cache // and trying again. This option is only inspected when we're running as a diff --git a/runtime/runtime_options.def b/runtime/runtime_options.def index ad748b04d3..e68a1b2681 100644 --- a/runtime/runtime_options.def +++ b/runtime/runtime_options.def @@ -123,7 +123,6 @@ RUNTIME_OPTIONS_KEY (ExperimentalFlags, Experimental, ExperimentalFlags::k 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. // (Do not add anything here that is defined in ParsedOptions::MakeParser) diff --git a/runtime/thread.cc b/runtime/thread.cc index 66a03a6826..151e91ce8b 100644 --- a/runtime/thread.cc +++ b/runtime/thread.cc @@ -2852,13 +2852,16 @@ void Thread::QuickDeliverException() { if (Dbg::IsForcedInterpreterNeededForException(this)) { NthCallerVisitor visitor(this, 0, false); visitor.WalkStack(); - if (Runtime::Current()->IsDeoptimizeable(visitor.caller_pc)) { + if (Runtime::Current()->IsAsyncDeoptimizeable(visitor.caller_pc)) { // Save the exception into the deoptimization context so it can be restored // before entering the interpreter. PushDeoptimizationContext( JValue(), /*is_reference */ false, /* from_code */ false, exception); artDeoptimize(this); UNREACHABLE(); + } else { + LOG(WARNING) << "Got a deoptimization request on un-deoptimizable method " + << visitor.caller->PrettyMethod(); } } diff --git a/test/466-get-live-vreg/get_live_vreg_jni.cc b/test/466-get-live-vreg/get_live_vreg_jni.cc index d3a033b12a..6cea673b41 100644 --- a/test/466-get-live-vreg/get_live_vreg_jni.cc +++ b/test/466-get-live-vreg/get_live_vreg_jni.cc @@ -47,7 +47,7 @@ class TestVisitor : public StackVisitor { uint32_t value = 0; if (GetCurrentQuickFrame() != nullptr && GetCurrentOatQuickMethodHeader()->IsOptimized() && - !Runtime::Current()->IsDebuggable()) { + !Runtime::Current()->IsJavaDebuggable()) { CHECK_EQ(GetVReg(m, dex_register_of_first_parameter, kIntVReg, &value), false); } else { CHECK(GetVReg(m, dex_register_of_first_parameter, kIntVReg, &value)); diff --git a/test/909-attach-agent/run b/test/909-attach-agent/run index 0664592cd1..4a2eb34a1d 100755 --- a/test/909-attach-agent/run +++ b/test/909-attach-agent/run @@ -22,10 +22,12 @@ if [[ "$@" == *"-O"* ]]; then fi ./default-run "$@" --android-runtime-option -Xplugin:${plugin} \ - --android-runtime-option -Xfully-deoptable \ + --android-runtime-option -Xcompiler-option \ + --android-runtime-option --debuggable \ --args agent:${agent}=909-attach-agent -./default-run "$@" --android-runtime-option -Xfully-deoptable \ +./default-run "$@" --android-runtime-option -Xcompiler-option \ + --android-runtime-option --debuggable \ --args agent:${agent}=909-attach-agent ./default-run "$@" --args agent:${agent}=909-attach-agent diff --git a/test/Android.run-test.mk b/test/Android.run-test.mk index cb798f049d..4c526a2033 100644 --- a/test/Android.run-test.mk +++ b/test/Android.run-test.mk @@ -605,6 +605,7 @@ TEST_ART_BROKEN_OPTIMIZING_MIPS64_RUN_TESTS := TEST_ART_BROKEN_OPTIMIZING_NONDEBUGGABLE_RUN_TESTS := \ 454-get-vreg \ 457-regs \ + 602-deoptimizeable ifneq (,$(filter $(OPTIMIZING_COMPILER_TYPES),$(COMPILER_TYPES))) ART_TEST_KNOWN_BROKEN += $(call all-run-test-names,$(TARGET_TYPES),$(RUN_TYPES),$(PREBUILD_TYPES), \ diff --git a/test/etc/run-test-jar b/test/etc/run-test-jar index 28fa130443..421405398b 100755 --- a/test/etc/run-test-jar +++ b/test/etc/run-test-jar @@ -344,7 +344,7 @@ if [ "$IS_JVMTI_TEST" = "y" ]; then else FLAGS="${FLAGS} -agentpath:${agent}=${TEST_NAME},art" FLAGS="${FLAGS} -Xplugin:${plugin}" - FLAGS="${FLAGS} -Xfully-deoptable" + FLAGS="${FLAGS} -Xcompiler-option --debuggable" # Always make the compilation be debuggable. COMPILE_FLAGS="${COMPILE_FLAGS} --debuggable" fi |