Merge changes Ie70d0155,I0692a91d,I8c5041a4
* changes: LockAgent: Add agent parameters to start_lock_agent script LockAgent: Add option to synthesize Java crash logging LockAgent: Add ability to generate a native crash
This commit is contained in:
@@ -64,6 +64,19 @@ public class RuntimeInit {
|
|||||||
return Log.printlns(Log.LOG_ID_CRASH, Log.ERROR, tag, msg, tr);
|
return Log.printlns(Log.LOG_ID_CRASH, Log.ERROR, tag, msg, tr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void logUncaught(String threadName, String processName, int pid, Throwable e) {
|
||||||
|
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(threadName).append("\n");
|
||||||
|
if (processName != null) {
|
||||||
|
message.append("Process: ").append(processName).append(", ");
|
||||||
|
}
|
||||||
|
message.append("PID: ").append(pid);
|
||||||
|
Clog_e(TAG, message.toString(), e);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Logs a message when a thread encounters an uncaught exception. By
|
* Logs a message when a thread encounters an uncaught exception. By
|
||||||
* default, {@link KillApplicationHandler} will terminate this process later,
|
* default, {@link KillApplicationHandler} will terminate this process later,
|
||||||
@@ -85,17 +98,7 @@ public class RuntimeInit {
|
|||||||
if (mApplicationObject == null && (Process.SYSTEM_UID == Process.myUid())) {
|
if (mApplicationObject == null && (Process.SYSTEM_UID == Process.myUid())) {
|
||||||
Clog_e(TAG, "*** FATAL EXCEPTION IN SYSTEM PROCESS: " + t.getName(), e);
|
Clog_e(TAG, "*** FATAL EXCEPTION IN SYSTEM PROCESS: " + t.getName(), e);
|
||||||
} else {
|
} else {
|
||||||
StringBuilder message = new StringBuilder();
|
logUncaught(t.getName(), ActivityThread.currentProcessName(), Process.myPid(), e);
|
||||||
// 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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ cc_library {
|
|||||||
include_dirs: [
|
include_dirs: [
|
||||||
// NDK headers aren't available in platform NDK builds.
|
// NDK headers aren't available in platform NDK builds.
|
||||||
"libnativehelper/include_jni",
|
"libnativehelper/include_jni",
|
||||||
|
// Use ScopedUtfChars.
|
||||||
|
"libnativehelper/header_only_include",
|
||||||
],
|
],
|
||||||
header_libs: [
|
header_libs: [
|
||||||
"libopenjdkjvmti_headers",
|
"libopenjdkjvmti_headers",
|
||||||
@@ -33,6 +35,8 @@ cc_binary_host {
|
|||||||
include_dirs: [
|
include_dirs: [
|
||||||
// NDK headers aren't available in platform NDK builds.
|
// NDK headers aren't available in platform NDK builds.
|
||||||
"libnativehelper/include_jni",
|
"libnativehelper/include_jni",
|
||||||
|
// Use ScopedUtfChars.
|
||||||
|
"libnativehelper/header_only_include",
|
||||||
],
|
],
|
||||||
header_libs: [
|
header_libs: [
|
||||||
"libopenjdkjvmti_headers",
|
"libopenjdkjvmti_headers",
|
||||||
|
|||||||
@@ -19,6 +19,8 @@
|
|||||||
#include <memory>
|
#include <memory>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
#include <jni.h>
|
#include <jni.h>
|
||||||
|
|
||||||
#include <jvmti.h>
|
#include <jvmti.h>
|
||||||
@@ -26,10 +28,14 @@
|
|||||||
#include <android-base/file.h>
|
#include <android-base/file.h>
|
||||||
#include <android-base/logging.h>
|
#include <android-base/logging.h>
|
||||||
#include <android-base/macros.h>
|
#include <android-base/macros.h>
|
||||||
|
#include <android-base/strings.h>
|
||||||
#include <android-base/unique_fd.h>
|
#include <android-base/unique_fd.h>
|
||||||
|
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
|
#include <sys/wait.h>
|
||||||
|
|
||||||
|
#include <nativehelper/scoped_utf_chars.h>
|
||||||
|
|
||||||
// We need dladdr.
|
// We need dladdr.
|
||||||
#if !defined(__APPLE__) && !defined(_WIN32)
|
#if !defined(__APPLE__) && !defined(_WIN32)
|
||||||
@@ -61,6 +67,8 @@
|
|||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
JavaVM* gJavaVM = nullptr;
|
JavaVM* gJavaVM = nullptr;
|
||||||
|
bool gForkCrash = false;
|
||||||
|
bool gJavaCrash = false;
|
||||||
|
|
||||||
// Converts a class name to a type descriptor
|
// Converts a class name to a type descriptor
|
||||||
// (ex. "java.lang.String" to "Ljava/lang/String;")
|
// (ex. "java.lang.String" to "Ljava/lang/String;")
|
||||||
@@ -372,7 +380,7 @@ void prepareHook(jvmtiEnv* env) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
jint attach(JavaVM* vm, char* options ATTRIBUTE_UNUSED, void* reserved ATTRIBUTE_UNUSED) {
|
jint attach(JavaVM* vm, char* options, void* reserved ATTRIBUTE_UNUSED) {
|
||||||
gJavaVM = vm;
|
gJavaVM = vm;
|
||||||
|
|
||||||
jvmtiEnv* env;
|
jvmtiEnv* env;
|
||||||
@@ -383,9 +391,66 @@ jint attach(JavaVM* vm, char* options ATTRIBUTE_UNUSED, void* reserved ATTRIBUTE
|
|||||||
|
|
||||||
prepareHook(env);
|
prepareHook(env);
|
||||||
|
|
||||||
|
std::vector<std::string> config = android::base::Split(options, ",");
|
||||||
|
for (const std::string& c : config) {
|
||||||
|
if (c == "native_crash") {
|
||||||
|
gForkCrash = true;
|
||||||
|
} else if (c == "java_crash") {
|
||||||
|
gJavaCrash = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return JVMTI_ERROR_NONE;
|
return JVMTI_ERROR_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extern "C" JNIEXPORT
|
||||||
|
jboolean JNICALL Java_com_android_lock_1checker_LockHook_getNativeHandlingConfig(JNIEnv*, jclass) {
|
||||||
|
return gForkCrash ? JNI_TRUE : JNI_FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" JNIEXPORT jboolean JNICALL
|
||||||
|
Java_com_android_lock_1checker_LockHook_getSimulateCrashConfig(JNIEnv*, jclass) {
|
||||||
|
return gJavaCrash ? JNI_TRUE : JNI_FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" JNIEXPORT void JNICALL Java_com_android_lock_1checker_LockHook_nWtf(JNIEnv* env, jclass,
|
||||||
|
jstring msg) {
|
||||||
|
if (!gForkCrash || msg == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a native crash with the given message. Decouple from the current crash to create a
|
||||||
|
// tombstone but continue on.
|
||||||
|
//
|
||||||
|
// TODO: Once there are not so many reports, consider making this fatal for the calling process.
|
||||||
|
ScopedUtfChars utf(env, msg);
|
||||||
|
if (utf.c_str() == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const char* args[] = {
|
||||||
|
"/system/bin/lockagent_crasher",
|
||||||
|
utf.c_str(),
|
||||||
|
nullptr
|
||||||
|
};
|
||||||
|
pid_t pid = fork();
|
||||||
|
if (pid < 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (pid == 0) {
|
||||||
|
// Double fork so we return quickly. Leave init to deal with the zombie.
|
||||||
|
pid_t pid2 = fork();
|
||||||
|
if (pid2 == 0) {
|
||||||
|
execv(args[0], const_cast<char* const*>(args));
|
||||||
|
_exit(1);
|
||||||
|
__builtin_unreachable();
|
||||||
|
}
|
||||||
|
_exit(0);
|
||||||
|
__builtin_unreachable();
|
||||||
|
}
|
||||||
|
int status;
|
||||||
|
waitpid(pid, &status, 0); // Ignore any results.
|
||||||
|
}
|
||||||
|
|
||||||
extern "C" JNIEXPORT jint JNICALL Agent_OnAttach(JavaVM* vm, char* options, void* reserved) {
|
extern "C" JNIEXPORT jint JNICALL Agent_OnAttach(JavaVM* vm, char* options, void* reserved) {
|
||||||
return attach(vm, options, reserved);
|
return attach(vm, options, reserved);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
package com.android.lock_checker;
|
package com.android.lock_checker;
|
||||||
|
|
||||||
|
import android.app.ActivityThread;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.HandlerThread;
|
import android.os.HandlerThread;
|
||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
@@ -24,6 +25,7 @@ import android.os.Process;
|
|||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.util.LogWriter;
|
import android.util.LogWriter;
|
||||||
|
|
||||||
|
import com.android.internal.os.RuntimeInit;
|
||||||
import com.android.internal.os.SomeArgs;
|
import com.android.internal.os.SomeArgs;
|
||||||
import com.android.internal.util.StatLogger;
|
import com.android.internal.util.StatLogger;
|
||||||
|
|
||||||
@@ -72,14 +74,23 @@ public class LockHook {
|
|||||||
|
|
||||||
private static final LockChecker[] sCheckers;
|
private static final LockChecker[] sCheckers;
|
||||||
|
|
||||||
|
private static boolean sNativeHandling = false;
|
||||||
|
private static boolean sSimulateCrash = false;
|
||||||
|
|
||||||
static {
|
static {
|
||||||
sHandlerThread = new HandlerThread("LockHook:wtf", Process.THREAD_PRIORITY_BACKGROUND);
|
sHandlerThread = new HandlerThread("LockHook:wtf", Process.THREAD_PRIORITY_BACKGROUND);
|
||||||
sHandlerThread.start();
|
sHandlerThread.start();
|
||||||
sHandler = new WtfHandler(sHandlerThread.getLooper());
|
sHandler = new WtfHandler(sHandlerThread.getLooper());
|
||||||
|
|
||||||
sCheckers = new LockChecker[] { new OnThreadLockChecker() };
|
sCheckers = new LockChecker[] { new OnThreadLockChecker() };
|
||||||
|
|
||||||
|
sNativeHandling = getNativeHandlingConfig();
|
||||||
|
sSimulateCrash = getSimulateCrashConfig();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static native boolean getNativeHandlingConfig();
|
||||||
|
private static native boolean getSimulateCrashConfig();
|
||||||
|
|
||||||
static <T> boolean shouldDumpStacktrace(StacktraceHasher hasher, Map<String, T> dumpedSet,
|
static <T> boolean shouldDumpStacktrace(StacktraceHasher hasher, Map<String, T> dumpedSet,
|
||||||
T val, AnnotatedStackTraceElement[] st, int from, int to) {
|
T val, AnnotatedStackTraceElement[] st, int from, int to) {
|
||||||
final String stacktraceHash = hasher.stacktraceHash(st, from, to);
|
final String stacktraceHash = hasher.stacktraceHash(st, from, to);
|
||||||
@@ -175,7 +186,18 @@ public class LockHook {
|
|||||||
private static void handleViolation(Violation v) {
|
private static void handleViolation(Violation v) {
|
||||||
String msg = v.toString();
|
String msg = v.toString();
|
||||||
Log.wtf(TAG, msg);
|
Log.wtf(TAG, msg);
|
||||||
|
if (sNativeHandling) {
|
||||||
|
nWtf(msg); // Also send to native.
|
||||||
}
|
}
|
||||||
|
if (sSimulateCrash) {
|
||||||
|
RuntimeInit.logUncaught("LockAgent",
|
||||||
|
ActivityThread.isSystem() ? "system_server"
|
||||||
|
: ActivityThread.currentProcessName(),
|
||||||
|
Process.myPid(), v.getException());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static native void nWtf(String msg);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates a hash for a given stacktrace of a {@link Throwable}.
|
* Generates a hash for a given stacktrace of a {@link Throwable}.
|
||||||
@@ -297,5 +319,6 @@ public class LockHook {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface Violation {
|
interface Violation {
|
||||||
|
Throwable getException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -228,6 +228,8 @@ class OnThreadLockChecker implements LockHook.LockChecker {
|
|||||||
AnnotatedStackTraceElement[] mStack;
|
AnnotatedStackTraceElement[] mStack;
|
||||||
OrderData mOppositeData;
|
OrderData mOppositeData;
|
||||||
|
|
||||||
|
private static final int STACK_OFFSET = 4;
|
||||||
|
|
||||||
Violation(Thread self, Object alreadyHeld, Object lock,
|
Violation(Thread self, Object alreadyHeld, Object lock,
|
||||||
AnnotatedStackTraceElement[] stack, OrderData oppositeData) {
|
AnnotatedStackTraceElement[] stack, OrderData oppositeData) {
|
||||||
this.mSelfTid = (int) self.getId();
|
this.mSelfTid = (int) self.getId();
|
||||||
@@ -284,6 +286,26 @@ class OnThreadLockChecker implements LockHook.LockChecker {
|
|||||||
lock.getClass().getName());
|
lock.getClass().getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Synthesize an exception.
|
||||||
|
public Throwable getException() {
|
||||||
|
RuntimeException inner = new RuntimeException("Previously locked");
|
||||||
|
inner.setStackTrace(synthesizeStackTrace(mOppositeData.mStack));
|
||||||
|
|
||||||
|
RuntimeException outer = new RuntimeException(toString(), inner);
|
||||||
|
outer.setStackTrace(synthesizeStackTrace(mStack));
|
||||||
|
|
||||||
|
return outer;
|
||||||
|
}
|
||||||
|
|
||||||
|
private StackTraceElement[] synthesizeStackTrace(AnnotatedStackTraceElement[] stack) {
|
||||||
|
|
||||||
|
StackTraceElement[] out = new StackTraceElement[stack.length - STACK_OFFSET];
|
||||||
|
for (int i = 0; i < out.length; i++) {
|
||||||
|
out[i] = stack[i + STACK_OFFSET].getStackTraceElement();
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
public String toString() {
|
public String toString() {
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
sb.append("Lock inversion detected!\n");
|
sb.append("Lock inversion detected!\n");
|
||||||
@@ -294,7 +316,7 @@ class OnThreadLockChecker implements LockHook.LockChecker {
|
|||||||
sb.append(" on thread ").append(mOppositeData.mTid).append(" (")
|
sb.append(" on thread ").append(mOppositeData.mTid).append(" (")
|
||||||
.append(mOppositeData.mThreadName).append(")");
|
.append(mOppositeData.mThreadName).append(")");
|
||||||
sb.append(" at:\n");
|
sb.append(" at:\n");
|
||||||
sb.append(getAnnotatedStackString(mOppositeData.mStack, 4,
|
sb.append(getAnnotatedStackString(mOppositeData.mStack, STACK_OFFSET,
|
||||||
describeLocking(mAlreadyHeld, "will lock"), getTo(mOppositeData.mStack, mLock)
|
describeLocking(mAlreadyHeld, "will lock"), getTo(mOppositeData.mStack, mLock)
|
||||||
+ 1, " | "));
|
+ 1, " | "));
|
||||||
sb.append(" Locking ");
|
sb.append(" Locking ");
|
||||||
@@ -303,7 +325,8 @@ class OnThreadLockChecker implements LockHook.LockChecker {
|
|||||||
sb.append(describeLock(mLock));
|
sb.append(describeLock(mLock));
|
||||||
sb.append(" on thread ").append(mSelfTid).append(" (").append(mSelfName).append(")");
|
sb.append(" on thread ").append(mSelfTid).append(" (").append(mSelfName).append(")");
|
||||||
sb.append(" at:\n");
|
sb.append(" at:\n");
|
||||||
sb.append(getAnnotatedStackString(mStack, 4, describeLocking(mLock, "will lock"),
|
sb.append(getAnnotatedStackString(mStack, STACK_OFFSET,
|
||||||
|
describeLocking(mLock, "will lock"),
|
||||||
getTo(mStack, mAlreadyHeld) + 1, " | "));
|
getTo(mStack, mAlreadyHeld) + 1, " | "));
|
||||||
|
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
|
|||||||
@@ -1,5 +1,13 @@
|
|||||||
#!/system/bin/sh
|
#!/system/bin/sh
|
||||||
|
|
||||||
|
AGENT_OPTIONS=
|
||||||
|
if [[ "$1" == --agent-options ]] ; then
|
||||||
|
shift
|
||||||
|
AGENT_OPTIONS="=$1"
|
||||||
|
shift
|
||||||
|
fi
|
||||||
|
|
||||||
APP=$1
|
APP=$1
|
||||||
shift
|
shift
|
||||||
$APP -Xplugin:libopenjdkjvmti.so -agentpath:liblockagent.so $@
|
|
||||||
|
|
||||||
|
$APP -Xplugin:libopenjdkjvmti.so "-agentpath:liblockagent.so$AGENT_OPTIONS" $@
|
||||||
|
|||||||
Reference in New Issue
Block a user