Provide remote stack trace information
The stack is truncated up to 5 lines at parcel time. When unparceling,
a separate RemoteException will be created and set as a cause of the
exception being thrown.
Performance results(in nanoseconds):
timeWriteExceptionWithStackTraceParceling 4168
timeWriteException 2201
timeReadException 15878
timeReadExceptionWithStackTraceParceling 23805
Test: manual + ParcelPerfTest
Bug: 36561158
Change-Id: I18b64a6c39c24ab067115874ddb5bd71f556a601
diff --git a/apct-tests/perftests/core/src/android/os/ParcelPerfTest.java b/apct-tests/perftests/core/src/android/os/ParcelPerfTest.java
index a92597f..6e4c9c5 100644
--- a/apct-tests/perftests/core/src/android/os/ParcelPerfTest.java
+++ b/apct-tests/perftests/core/src/android/os/ParcelPerfTest.java
@@ -27,6 +27,10 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
@RunWith(AndroidJUnit4.class)
@LargeTest
public class ParcelPerfTest {
@@ -167,4 +171,80 @@
Parcel.obtain().recycle();
}
}
+
+ @Test
+ public void timeWriteException() {
+ timeWriteException(false);
+ }
+
+ @Test
+ public void timeWriteExceptionWithStackTraceParceling() {
+ timeWriteException(true);
+ }
+
+ @Test
+ public void timeReadException() {
+ timeReadException(false);
+ }
+
+ @Test
+ public void timeReadExceptionWithStackTraceParceling() {
+ timeReadException(true);
+ }
+
+ private void timeWriteException(boolean enableParceling) {
+ if (enableParceling) {
+ Parcel.setStackTraceParceling(true);
+ }
+ try {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ Parcel p = Parcel.obtain();
+ SecurityException e = new SecurityException("TestMessage");
+ while (state.keepRunning()) {
+ p.setDataPosition(0);
+ p.writeException(e);
+ }
+ } finally {
+ if (enableParceling) {
+ Parcel.setStackTraceParceling(false);
+ }
+ }
+ }
+
+ private void timeReadException(boolean enableParceling) {
+ if (enableParceling) {
+ Parcel.setStackTraceParceling(true);
+ }
+ try {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ Parcel p = Parcel.obtain();
+ String msg = "TestMessage";
+ p.writeException(new SecurityException(msg));
+ p.setDataPosition(0);
+ // First verify that remote cause is set (if parceling is enabled)
+ try {
+ p.readException();
+ } catch (SecurityException e) {
+ assertEquals(e.getMessage(), msg);
+ if (enableParceling) {
+ assertTrue(e.getCause() instanceof RemoteException);
+ } else {
+ assertNull(e.getCause());
+ }
+ }
+
+ while (state.keepRunning()) {
+ p.setDataPosition(0);
+ try {
+ p.readException();
+ } catch (SecurityException expected) {
+ }
+ }
+ } finally {
+ if (enableParceling) {
+ Parcel.setStackTraceParceling(false);
+ }
+ }
+ }
+
}
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index 10adb5a..d092018 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -191,6 +191,7 @@
* {@link #readSparseArray(ClassLoader)}.
*/
public final class Parcel {
+
private static final boolean DEBUG_RECYCLE = false;
private static final boolean DEBUG_ARRAY_MAP = false;
private static final String TAG = "Parcel";
@@ -209,6 +210,12 @@
private RuntimeException mStack;
+ /**
+ * Whether or not to parcel the stack trace of an exception. This has a performance
+ * impact, so should only be included in specific processes and only on debug builds.
+ */
+ private static boolean sParcelExceptionStackTrace;
+
private static final int POOL_SIZE = 6;
private static final Parcel[] sOwnedPool = new Parcel[POOL_SIZE];
private static final Parcel[] sHolderPool = new Parcel[POOL_SIZE];
@@ -325,6 +332,11 @@
private static native void nativeWriteInterfaceToken(long nativePtr, String interfaceName);
private static native void nativeEnforceInterface(long nativePtr, String interfaceName);
+ /** Last time exception with a stack trace was written */
+ private static volatile long sLastWriteExceptionStackTrace;
+ /** Used for throttling of writing stack trace, which is costly */
+ private static final int WRITE_EXCEPTION_STACK_TRACE_THRESHOLD_MS = 1000;
+
@CriticalNative
private static native long nativeGetBlobAshmemSize(long nativePtr);
@@ -1696,6 +1708,11 @@
}
}
+ /** @hide For debugging purposes */
+ public static void setStackTraceParceling(boolean enabled) {
+ sParcelExceptionStackTrace = enabled;
+ }
+
/**
* Special function for writing an exception result at the header of
* a parcel, to be used when returning an exception from a transaction.
@@ -1753,6 +1770,27 @@
throw new RuntimeException(e);
}
writeString(e.getMessage());
+ final long timeNow = sParcelExceptionStackTrace ? SystemClock.elapsedRealtime() : 0;
+ if (sParcelExceptionStackTrace && (timeNow - sLastWriteExceptionStackTrace
+ > WRITE_EXCEPTION_STACK_TRACE_THRESHOLD_MS)) {
+ sLastWriteExceptionStackTrace = timeNow;
+ final int sizePosition = dataPosition();
+ writeInt(0); // Header size will be filled in later
+ StackTraceElement[] stackTrace = e.getStackTrace();
+ final int truncatedSize = Math.min(stackTrace.length, 5);
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < truncatedSize; i++) {
+ sb.append("\tat ").append(stackTrace[i]);
+ }
+ writeString(sb.toString());
+ final int payloadPosition = dataPosition();
+ setDataPosition(sizePosition);
+ // Write stack trace header size. Used in native side to skip the header
+ writeInt(payloadPosition - sizePosition);
+ setDataPosition(payloadPosition);
+ } else {
+ writeInt(0);
+ }
switch (code) {
case EX_SERVICE_SPECIFIC:
writeInt(((ServiceSpecificException) e).errorCode);
@@ -1818,7 +1856,19 @@
int code = readExceptionCode();
if (code != 0) {
String msg = readString();
- readException(code, msg);
+ String remoteStackTrace = null;
+ final int remoteStackPayloadSize = readInt();
+ if (remoteStackPayloadSize > 0) {
+ remoteStackTrace = readString();
+ }
+ Exception e = createException(code, msg);
+ // Attach remote stack trace if availalble
+ if (remoteStackTrace != null) {
+ RemoteException cause = new RemoteException(
+ "Remote stack trace:\n" + remoteStackTrace, null, false, false);
+ e.initCause(cause);
+ }
+ SneakyThrow.sneakyThrow(e);
}
}
@@ -1863,32 +1913,41 @@
* @param msg The exception message.
*/
public final void readException(int code, String msg) {
+ SneakyThrow.sneakyThrow(createException(code, msg));
+ }
+
+ /**
+ * Creates an exception with the given message.
+ *
+ * @param code Used to determine which exception class to throw.
+ * @param msg The exception message.
+ */
+ private Exception createException(int code, String msg) {
switch (code) {
case EX_PARCELABLE:
if (readInt() > 0) {
- SneakyThrow.sneakyThrow(
- (Exception) readParcelable(Parcelable.class.getClassLoader()));
+ return (Exception) readParcelable(Parcelable.class.getClassLoader());
} else {
- throw new RuntimeException(msg + " [missing Parcelable]");
+ return new RuntimeException(msg + " [missing Parcelable]");
}
case EX_SECURITY:
- throw new SecurityException(msg);
+ return new SecurityException(msg);
case EX_BAD_PARCELABLE:
- throw new BadParcelableException(msg);
+ return new BadParcelableException(msg);
case EX_ILLEGAL_ARGUMENT:
- throw new IllegalArgumentException(msg);
+ return new IllegalArgumentException(msg);
case EX_NULL_POINTER:
- throw new NullPointerException(msg);
+ return new NullPointerException(msg);
case EX_ILLEGAL_STATE:
- throw new IllegalStateException(msg);
+ return new IllegalStateException(msg);
case EX_NETWORK_MAIN_THREAD:
- throw new NetworkOnMainThreadException();
+ return new NetworkOnMainThreadException();
case EX_UNSUPPORTED_OPERATION:
- throw new UnsupportedOperationException(msg);
+ return new UnsupportedOperationException(msg);
case EX_SERVICE_SPECIFIC:
- throw new ServiceSpecificException(readInt(), msg);
+ return new ServiceSpecificException(readInt(), msg);
}
- throw new RuntimeException("Unknown exception code: " + code
+ return new RuntimeException("Unknown exception code: " + code
+ " msg " + msg);
}
diff --git a/core/java/android/os/RemoteException.java b/core/java/android/os/RemoteException.java
index 6d25fc1..4e8b971 100644
--- a/core/java/android/os/RemoteException.java
+++ b/core/java/android/os/RemoteException.java
@@ -30,6 +30,12 @@
super(message);
}
+ /** @hide */
+ public RemoteException(String message, Throwable cause, boolean enableSuppression,
+ boolean writableStackTrace) {
+ super(message, cause, enableSuppression, writableStackTrace);
+ }
+
/** {@hide} */
public RuntimeException rethrowAsRuntimeException() {
throw new RuntimeException(this);
diff --git a/core/java/android/util/AndroidException.java b/core/java/android/util/AndroidException.java
index dfe00c9b..1345ddf 100644
--- a/core/java/android/util/AndroidException.java
+++ b/core/java/android/util/AndroidException.java
@@ -34,5 +34,11 @@
public AndroidException(Exception cause) {
super(cause);
}
+
+ /** @hide */
+ protected AndroidException(String message, Throwable cause, boolean enableSuppression,
+ boolean writableStackTrace) {
+ super(message, cause, enableSuppression, writableStackTrace);
+ }
};
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 74a7bd4a..33f4e34 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -35,6 +35,7 @@
import android.os.IIncidentManager;
import android.os.Looper;
import android.os.Message;
+import android.os.Parcel;
import android.os.PowerManager;
import android.os.Process;
import android.os.ServiceManager;
@@ -360,6 +361,9 @@
// to avoid throwing BadParcelableException.
BaseBundle.setShouldDefuse(true);
+ // Within the system server, when parceling exceptions, include the stack trace
+ Parcel.setStackTraceParceling(true);
+
// Ensure binder calls into the system always run at foreground priority.
BinderInternal.disableBackgroundScheduling(true);