From addbf9015a65ed7704a4fc22f36380dd153698da Mon Sep 17 00:00:00 2001 From: Tobias Thierer Date: Thu, 21 Jul 2016 15:05:19 +0100 Subject: [PATCH] Ensure apps cannot prevent uncaught exceptions being logged. Let RuntimeInit use an UncaughtExceptionPreHandler to log an exception rather than relying on UncaughtHandler, which apps can replace. This makes it easier to diagnose application death, especially during app compatibility testing for a new version of Android. Test: Verified manually, with the help of a small sample app (not checked in), that stacktraces for RuntimeExceptions thrown on main or background threads are logged even when the app set a default UncaughtExceptionHandler that swallows the exception with no action. Note that such an inappropriate UncaughtExceptionHandler will still cause threads to die without the app being killed, which it should be. In an exception then happens on the main thread, the app will freeze until the ANR dialog kicks in after a few seconds. I have manually verified that this behavior is unchanged from before this CL. No new integration tests are included because the default system behavior has not changed. Bug: 29624607 Change-Id: Ie87377b0bcadc3ba4083a8ab1bedb8f3dd95a4bd --- .../com/android/internal/os/RuntimeInit.java | 61 +++++++++++++------ preloaded-classes | 3 +- 2 files changed, 44 insertions(+), 20 deletions(-) diff --git a/core/java/com/android/internal/os/RuntimeInit.java b/core/java/com/android/internal/os/RuntimeInit.java index 3b8b7cb2ba2fe..ff81bc67f7a8a 100644 --- a/core/java/com/android/internal/os/RuntimeInit.java +++ b/core/java/com/android/internal/os/RuntimeInit.java @@ -62,30 +62,49 @@ public class RuntimeInit { } /** - * Use this to log a message when a thread exits due to an uncaught - * exception. The framework catches these for the main threads, so - * this should only matter for threads created by applications. + * Logs a message when a thread encounters an uncaught exception. By + * default, {@link KillApplicationHandler} will terminate this process later, + * but apps can override that behavior. */ - private static class UncaughtHandler implements Thread.UncaughtExceptionHandler { + private static class LoggingHandler implements Thread.UncaughtExceptionHandler { + @Override + public void uncaughtException(Thread t, Throwable e) { + // Don't re-enter if KillApplicationHandler has already run + if (mCrashing) return; + if (mApplicationObject == null) { + // The "FATAL EXCEPTION" string is still used on Android even though + // apps can set a custom UncaughtExceptionHandler that renders uncaught + // exceptions non-fatal. + Clog_e(TAG, "*** FATAL EXCEPTION IN SYSTEM PROCESS: " + t.getName(), e); + } else { + StringBuilder message = new StringBuilder(); + // The "FATAL EXCEPTION" string is still used on Android even though + // apps can set a custom UncaughtExceptionHandler that renders uncaught + // exceptions non-fatal. + message.append("FATAL EXCEPTION: ").append(t.getName()).append("\n"); + final String processName = ActivityThread.currentProcessName(); + if (processName != null) { + message.append("Process: ").append(processName).append(", "); + } + message.append("PID: ").append(Process.myPid()); + Clog_e(TAG, message.toString(), e); + } + } + } + + /** + * Handle application death from an uncaught exception. The framework + * catches these for the main threads, so this should only matter for + * threads created by applications. Before this method runs, + * {@link LoggingHandler} will already have logged details. + */ + private static class KillApplicationHandler implements Thread.UncaughtExceptionHandler { public void uncaughtException(Thread t, Throwable e) { try { // Don't re-enter -- avoid infinite loops if crash-reporting crashes. if (mCrashing) return; mCrashing = true; - if (mApplicationObject == null) { - Clog_e(TAG, "*** FATAL EXCEPTION IN SYSTEM PROCESS: " + t.getName(), e); - } else { - StringBuilder message = new StringBuilder(); - message.append("FATAL EXCEPTION: ").append(t.getName()).append("\n"); - final String processName = ActivityThread.currentProcessName(); - if (processName != null) { - message.append("Process: ").append(processName).append(", "); - } - message.append("PID: ").append(Process.myPid()); - Clog_e(TAG, message.toString(), e); - } - // Try to end profiling. If a profiler is running at this point, and we kill the // process (below), the in-memory buffer will be lost. So try to stop, which will // flush the buffer. (This makes method trace profiling useful to debug crashes.) @@ -113,8 +132,12 @@ public class RuntimeInit { private static final void commonInit() { if (DEBUG) Slog.d(TAG, "Entered RuntimeInit!"); - /* set default handler; this applies to all threads in the VM */ - Thread.setDefaultUncaughtExceptionHandler(new UncaughtHandler()); + /* + * set handlers; these apply to all threads in the VM. Apps can replace + * the default handler, but not the pre handler. + */ + Thread.setUncaughtExceptionPreHandler(new LoggingHandler()); + Thread.setDefaultUncaughtExceptionHandler(new KillApplicationHandler()); /* * Install a TimezoneGetter subclass for ZoneInfo.db diff --git a/preloaded-classes b/preloaded-classes index 8e8faf463a94c..cd146f1642e20 100644 --- a/preloaded-classes +++ b/preloaded-classes @@ -2257,7 +2257,8 @@ com.android.internal.os.LoggingPrintStream$1 com.android.internal.os.RuntimeInit com.android.internal.os.RuntimeInit$1 com.android.internal.os.RuntimeInit$Arguments -com.android.internal.os.RuntimeInit$UncaughtHandler +com.android.internal.os.RuntimeInit$KillApplicationHandler +com.android.internal.os.RuntimeInit$LoggingHandler com.android.internal.os.SamplingProfilerIntegration com.android.internal.os.SomeArgs com.android.internal.os.Zygote