1. 异常抓取原理
- 上层应用都是由Zygote fork孵化出来的,分为system_server进程和普通应用进程
- 但并非所有的异常都是可被预知的,没有捕获到的异常,会被一层层的往上抛,线程没有捕获的异常会被一层层的往上抛,最终由虚拟机的默认异常处理机制来处理
- 进程创建之初会设置未捕获异常的处理器,这个异常捕获的处理设置就是在 RuntimeInit.commonInit() 函数中完成,zygoteInit的时候会调用Runntime.commonInit()
- Debug版本上,程序崩溃会弹框提示“xx程序停止运行”。正式用户版本上,各厂商通常都修改为直接退出应用,移除了弹框提示。
- Java的Thread类提供了一份Java的Thread类中有一个UncaughtExceptionHandler接口,该接口的作用主要是为了当Thread因未捕获的异常而突然终止时,调用处理程序处理异常
- Android中也同样利用了这种机制。虚拟机在遇到线程没有捕获的异常时,会调用thread的dispatchUncaughtException分发到当前线程中。
//frameworks/base/core/java/com/android/internal/os/RuntimeInit.java
protected static final void commonInit() {
...
LoggingHandler loggingHandler = new LoggingHandler();
RuntimeHooks.setUncaughtExceptionPreHandler(loggingHandler);
Thread.setDefaultUncaughtExceptionHandler(new KillApplicationHandler(loggingHandler));
...
}
- 虚拟机在遇到线程没有捕获的异常时,会调用thread的dispatchUncaughtException分发到当前线程中。
//art/runtime/thread.cc
void Thread::HandleUncaughtExceptions() {
Thread* self = this;
DCHECK_EQ(self, Thread::Current());
if (!self->IsExceptionPending()) {
return;
}
// Get and clear the exception.
ObjPtr<mirror::Object> exception = self->GetException();
self->ClearException();
// Call the Thread instance's dispatchUncaughtException(Throwable)
WellKnownClasses::java_lang_Thread_dispatchUncaughtException->InvokeFinal<'V', 'L'>(
self, tlsPtr_.opeer, exception);
// If the dispatchUncaughtException threw, clear that exception too.
self->ClearException();
}
java_lang_Thread_dispatchUncaughtException,反射的java thread类的dispatchUnCaughtException方法
//Thread.java
public final void dispatchUncaughtException(Throwable e) {
// BEGIN Android-added: uncaughtExceptionPreHandler for use by platform.
Thread.UncaughtExceptionHandler initialUeh =
Thread.getUncaughtExceptionPreHandler();
if (initialUeh != null) {
try {
initialUeh.uncaughtException(this, e);
} catch (RuntimeException | Error ignored) {
// Throwables thrown by the initial handler are ignored
}
}
// END Android-added: uncaughtExceptionPreHandler for use by platform.
getUncaughtExceptionHandler().uncaughtException(this, e);
}
-
这里面会有两个uncaughtExceptionHandler参与处理,分别是preHandler和Handler,分别执行各自的**unCaughtException()**方法。
-
这里,preHandler可以不设置。事实上,Android平台在N之前只有一个Handler,并没有设置preHandler。
-
RunntimeInit初始化了两个handler,一个是preHandler(LoggingHandler),一个是defaultHangler(KillApplicationHandler)。
两个类都继承于Thread.UncaughtExceptionHandler,实现其中的uncaughtException方法。其中:
- LoggingHandler: 主要用来打印异常信息,包括是否是SYSTEM_SERVER进程异常,异常进程名、pid信息、异常堆栈等
- KillApplicationHandler 主要用来检查和确保LoggingHandler的日志打印被调用到,然后通知AMS,杀掉应用进程。
LoggingHandler:
private static class LoggingHandler implements Thread.UncaughtExceptionHandler {
public volatile boolean mTriggered = false;
@Override
public void uncaughtException(Thread t, Throwable e) {
mTriggered = true;
// Don't re-enter if KillApplicationHandler has already run
//已经在crash 流程中,则已经在处理KillApplicationHandler则不再重复进入
if (mCrashing) return;
// mApplicationObject is null for non-zygote java programs (e.g. "am")
// There are also apps running with the system UID. We don't want the
// first clause in either of these two cases, only for system_server.
if (mApplicationObject == null && (Process.SYSTEM_UID == Process.myUid())) {
// sysyem_server进程
Clog_e(TAG, "*** FATAL EXCEPTION IN SYSTEM PROCESS: " + t.getName(), e);
} else {
logUncaught(t.getName(), ActivityThread.currentProcessName(), Process.myPid(), e);
}
}
}
........................
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.
//这个就是日志经常看到的 TAG是AndroidRuntime,打印
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);
}
KillApplicationHandler :
//frameworks/base/core/java/com/android/internal/os/RuntimeInit.java
public void uncaughtException(Thread t, Throwable e) {
try {
//调用LoggingHandler.uncaughtException(),不会反复调用
ensureLogging(t, e);
//全局变量,用以控制重复进入crash流程,第一次进入后会将该变量置true
if (mCrashing) return;
mCrashing = true;
//尝试去停止profiling,因为后面需要kill 进程,内存buffer会丢失,
//所以尝试停止,来 flush 内存buffer
if (ActivityThread.currentActivityThread() != null) {
ActivityThread.currentActivityThread().stopProfiling();
}
ActivityManager.getService().handleApplicationCrash(
mApplicationObject, new ApplicationErrorReport.ParcelableCrashInfo(e));
} catch (Throwable t2) {
if (t2 instanceof DeadObjectException) {
// System process is dead; ignore
} else {
try {
Clog_e(TAG, "Error reporting crash", t2);
} catch (Throwable t3) {
// Even Clog_e() fails! Oh well.
}
}
} finally {
//确保当前进程彻底杀掉
Process.killProcess(Process.myPid());
System.exit(10);
}
}
- Android N之前,只有一个defaultHandler,日志打印和通知AMS杀进程都是在这个Handler中完成的。
- preHandler的设置并非对外公开api,应用无法使用。但是Thread的setDefaultUncaughtExceptionHandler是公开的api,应用进程可以通过重设它来实现自己捕获uncaughtException
- 这种情况下,系统日志中虽然打印了FATAL EXCEPTION,但是应用并没有死掉。应用可以通过这种方式自己捕获异常的堆栈,但是通常情况下,捕捉完信息后,建议还是杀掉或者重启进程,这种如果处理不好的话,容易出现应用假死(无法运行也不会自动退出) 的情况。
app 出现 crash 的时候,需要确定当线程中是否设置了 uncaughtExceptionHandler,那么原来的 defaultUncaughtExceptionHandler 将不会被调用,即如果设置了 uncaughtExceptionHandler,最终调用的是 uncaughtExceptionHandler.uncaughtException()。利用这种方式可以避开 uncaught exception 而引起的 app 被kill
- uncaughtException还会调用AMS的handleApplicationCrash对本次异常进行处理,将抛出的异常throwable通过ApplicationErrorReport类转换为序列化变量再通过binder传递到AMS服务端。
//ApplicationErrorReport.java
public class ApplicationErrorReport implements Parcelable {
public static class CrashInfo {
/**
* Create an instance of CrashInfo initialized from an exception.
*/
public CrashInfo(Throwable tr) {
StringWriter sw = new StringWriter();
PrintWriter pw = new FastPrintWriter(sw, false, 256);
tr.printStackTrace(pw);
pw.flush();
stackTrace = sanitizeString(sw.toString());
exceptionMessage = tr.getMessage();
// Populate fields with the "root cause" exception
Throwable rootTr = tr;
while (tr.getCause() != null) {
tr = tr.getCause();
if (tr.getStackTrace() != null && tr.getStackTrace().length > 0) {
rootTr = tr;
}
String msg = tr.getMessage();
if (msg != null && msg.length() > 0) {
exceptionMessage = msg;
}
}
exceptionClassName = rootTr.getClass().getName();
if (rootTr.getStackTrace().length > 0) {
StackTraceElement trace = rootTr.getStackTrace()[0];
throwFileName = trace.getFileName();
throwClassName = trace.getClassName();
throwMethodName = trace.getMethodName();
throwLineNumber = trace.getLineNumber();
} else {
throwFileName = "unknown";
throwClassName = "unknown";
throwMethodName = "unknown";
throwLineNumber = 0;
}
exceptionMessage = sanitizeString(exceptionMessage);
}
public static class ParcelableCrashInfo extends CrashInfo implements Parcelable {
...
public ParcelableCrashInfo(Throwable tr) {
super(tr);
}
....
}
}
2 handleApplicationCrash
//frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
public void handleApplicationCrash(IBinder app,
ApplicationErrorReport.ParcelableCrashInfo crashInfo) {
ProcessRecord r = findAppProcess(app, "Crash");
final String processName = app == null ? "system_server"
: (r == null ? "unknown" : r.processName);
handleApplicationCrashInner("crash", r, processName, crashInfo);
}
private ProcessRecord findAppProcess(IBinder app, String reason) {
if (app == null) {
return null;
}
synchronized (mProcLock) {
return mProcessList.findAppProcessLOSP(app, reason);
}
}
//ProcessList.java
@GuardedBy(anyOf = {"mService", "mProcLock"})
ProcessRecord findAppProcessLOSP(IBinder app, String reason) {
final int NP = mProcessNames.getMap().size();
for (int ip = 0; ip < NP; ip++) {
SparseArray<ProcessRecord> apps = mProcessNames.getMap().valueAt(ip);
final int NA = apps.size();
for (int ia = 0; ia < NA; ia++) {
ProcessRecord p = apps.valueAt(ia);
final IApplicationThread thread = p.getThread();
//注意此处
if (thread != null && thread.asBinder() == app) {
return p;
}
}
}
Slog.w(TAG, "Can't find mystery application for " + reason
+ " from pid=" + Binder.getCallingPid()
+ " uid=" + Binder.getCallingUid() + ": " + app);
return null;
}
- 当远程Ibinder对象为空,则进程名为“system_server”
- 远程对象不为空时。如果processRecord能查到符合该binder对象的app记录,则打印processRecord对象中相应的进程名;如果processRecord为空,现场可能已经不完整,进程名复制为“unknown”
//ActivityManagerService
void handleApplicationCrashInner(String eventType, ProcessRecord r, String processName,
ApplicationErrorReport.CrashInfo crashInfo) {
...
//对应打印 12-01 16:45:29.663198 1260 3220 I am_crash: [21597,0,com.qualcomm.qti.PresenceApp,550026821,java.lang.NoSuchMethodException,com.qualcomm.qti.PresenceApp.SubsriptionTab.<init> [],Class.java,2363]
EventLogTags.writeAmCrash(Binder.getCallingPid(),
UserHandle.getUserId(Binder.getCallingUid()), processName,
r == null ? -1 : r.info.flags,
crashInfo.exceptionClassName,
crashInfo.exceptionMessage,
crashInfo.throwFileName,
crashInfo.throwLineNumber);
。。。。
addErrorToDropBox(
eventType, r, processName, null, null, null, null, null, null, crashInfo,
new Float(loadingProgress), incrementalMetrics, null);
mAppErrors.crashApplication(r, crashInfo);
}
public void addErrorToDropBox(String eventType,
ProcessRecord process, String processName, String activityShortComponentName,
String parentShortComponentName, ProcessRecord parentProcess,
String subject, final String report, final File dataFile,
final ApplicationErrorReport.CrashInfo crashInfo,
@Nullable Float loadingProgress, @Nullable IncrementalMetrics incrementalMetrics,
@Nullable UUID errorId) {
// NOTE -- this must never acquire the ActivityManagerService lock,
// otherwise the watchdog may be prevented from resetting the system.
// Bail early if not published yet
if (ServiceManager.getService(Context.DROPBOX_SERVICE) == null) return;
final DropBoxManager dbox = mContext.getSystemService(DropBoxManager.class);
// Exit early if the dropbox isn't configured to accept this report type.
final String dropboxTag = processClass(process) + "_" + eventType;
if (dbox == null || !dbox.isTagEnabled(dropboxTag)) return;
// Rate-limit how often we're willing to do the heavy lifting below to
// collect and record logs; currently 5 logs per 10 second period per eventType.
final long now = SystemClock.elapsedRealtime();
synchronized (mErrorClusterRecords) {
long[] errRecord = mErrorClusterRecords.get(eventType);
if (errRecord == null) {
errRecord = new long[2]; // [0]: startTime, [1]: count
mErrorClusterRecords.put(eventType, errRecord);
}
if (now - errRecord[0] > 10 * DateUtils.SECOND_IN_MILLIS) {
errRecord[0] = now;
errRecord[1] = 1L;
} else {
if (errRecord[1]++ >= 5) return;
}
}
final StringBuilder sb = new StringBuilder(1024);
appendDropBoxProcessHeaders(process, processName, sb);
if (process != null) {
sb.append("Foreground: ")
.append(process.isInterestingToUserLocked() ? "Yes" : "No")
.append("\n");
if (process.getStartTime() > 0) {
long runtimeMillis = SystemClock.elapsedRealtime() - process.getStartTime();
sb.append("Process-Runtime: ").append(runtimeMillis).append("\n");
}
}
if (activityShortComponentName != null) {
sb.append("Activity: ").append(activityShortComponentName).append("\n");
}
if (parentShortComponentName != null) {
if (parentProcess != null && parentProcess.getPid() != process.getPid()) {
sb.append("Parent-Process: ").append(parentProcess.processName).append("\n");
}
if (!parentShortComponentName.equals(activityShortComponentName)) {
sb.append("Parent-Activity: ").append(parentShortComponentName).append("\n");
}
}
if (subject != null) {
sb.append("Subject: ").append(subject).append("\n");
}
if (errorId != null) {
sb.append("ErrorId: ").append(errorId.toString()).append("\n");
}
sb.append("Build: ").append(Build.FINGERPRINT).append("\n");
if (Debug.isDebuggerConnected()) {
sb.append("Debugger: Connected\n");
}
if (crashInfo != null && crashInfo.crashTag != null && !crashInfo.crashTag.isEmpty()) {
sb.append("Crash-Tag: ").append(crashInfo.crashTag).append("\n");
}
if (loadingProgress != null) {
sb.append("Loading-Progress: ").append(loadingProgress.floatValue()).append("\n");
}
if (incrementalMetrics != null) {
sb.append("Incremental: Yes").append("\n");
final long millisSinceOldestPendingRead =
incrementalMetrics.getMillisSinceOldestPendingRead();
if (millisSinceOldestPendingRead > 0) {
sb.append("Millis-Since-Oldest-Pending-Read: ").append(
millisSinceOldestPendingRead).append("\n");
}
}
sb.append("\n");
// Do the rest in a worker thread to avoid blocking the caller on I/O
// (After this point, we shouldn't access AMS internal data structures.)
Thread worker = new Thread("Error dump: " + dropboxTag) {
@Override
public void run() {
if (report != null) {
sb.append(report);
}
String logcatSetting = Settings.Global.ERROR_LOGCAT_PREFIX + dropboxTag;
String maxBytesSetting = Settings.Global.MAX_ERROR_BYTES_PREFIX + dropboxTag;
int lines = Settings.Global.getInt(mContext.getContentResolver(), logcatSetting, 0);
int dropboxMaxSize = Settings.Global.getInt(
mContext.getContentResolver(), maxBytesSetting, DROPBOX_DEFAULT_MAX_SIZE);
int maxDataFileSize = dropboxMaxSize - sb.length()
- lines * RESERVED_BYTES_PER_LOGCAT_LINE;
if (dataFile != null && maxDataFileSize > 0) {
try {
sb.append(FileUtils.readTextFile(dataFile, maxDataFileSize,
"\n\n[[TRUNCATED]]"));
} catch (IOException e) {
Slog.e(TAG, "Error reading " + dataFile, e);
}
}
if (crashInfo != null && crashInfo.stackTrace != null) {
sb.append(crashInfo.stackTrace);
}
if (lines > 0) {
sb.append("\n");
// Merge several logcat streams, and take the last N lines
InputStreamReader input = null;
try {
java.lang.Process logcat = new ProcessBuilder(
"/system/bin/timeout", "-k", "15s", "10s",
"/system/bin/logcat", "-v", "threadtime", "-b", "events", "-b", "system",
"-b", "main", "-b", "crash", "-t", String.valueOf(lines))
.redirectErrorStream(true).start();
try { logcat.getOutputStream().close(); } catch (IOException e) {}
try { logcat.getErrorStream().close(); } catch (IOException e) {}
input = new InputStreamReader(logcat.getInputStream());
int num;
char[] buf = new char[8192];
while ((num = input.read(buf)) > 0) sb.append(buf, 0, num);
} catch (IOException e) {
Slog.e(TAG, "Error running logcat", e);
} finally {
if (input != null) try { input.close(); } catch (IOException e) {}
}
}
dbox.addText(dropboxTag, sb.toString());
}
};
if (process == null) {
// If process is null, we are being called from some internal code
// and may be about to die -- run this synchronously.
final int oldMask = StrictMode.allowThreadDiskWritesMask();
try {
worker.run();
} finally {
StrictMode.setThreadPolicyMask(oldMask);
}
} else {
worker.start();
}
}
有了crashInfo,又拿到了processRecord信息和进程名,接下来继续执行crashInner处理方法。HandleApplicationCrashInner主要完成两件事情
- 调用addErrorToDropBox,将crashInfo的关键信息输出到dropbox文件中,目录位于data/system/dropbox,根据异常的进程名,一般名为system_server_crash@xxx.txt,system_app_crashxxxx,data_app_crashxxxx。写入的内容包括进程名、pid、uid、时间、flag、包名、前后台信息、fingerprint版本信息、crashInfo堆栈信息等,厂商定制可能会再加入一些其他的打印。到这里,日志已经保存完
- addErrorToDropBox执行完,接下来AppError.crashApplication用来完成最后的收尾工作,主要用来处理应用退出后带来的状态切换变化,以及呈现crash弹窗给用户。
//AppErrors.java crashApplicationInner
final Message msg = Message.obtain();
msg.what = ActivityManagerService.SHOW_ERROR_UI_MSG;
taskId = data.taskId;
msg.obj = data;
mService.mUiHandler.sendMessage(msg);
dropbox导出后的内容
LOG中的JE异常格式:
3 异常堆栈由来
Java exception的产生,主要有两种原因:
- 编写的程序代码内部错误产生的异常,如调用对象为空(空指针异常)、数组越界异常、除0异常等。这种通常称为未检查的异常,在虚拟机中执行时会集中处理这些异常。
- 其他运行中异常,通过throw语句主动抛出的异常。这类异常称为检查异常,一般是程序认为自己遇到了严重的问题,后续再运行可能会出问题,主动告知方法调用者一些必要的信息。
- 未检查的异常是如何让线程退出循环,执行到uncaughtExceptionHandler的?我们以除0异常来举例。
- art虚拟机对该throw异常,遍历线程方法栈寄存器和PC指针寄存器, 得到函数方法调用栈等信息,最终用setException方法保存在tlsPtr变量中,等thread.destory调用dispatch uncaughtException的时候,将exception信息传递给之前RuntimeInit中注册的handler去处理。
- 其他的未检查的异常也是类似的逻辑。对于检查类的异常,这一块逻辑会简单很多。通常检查类的异常都会由某段程序主动调用throw去抛出一个runntimeException,exception可以是原生的类型,例如SecurityException、NullPointerException、IllegalStateException,也可以是自己定义的某个集成于exception的类。
无论是哪种exception还是error,最终都是继承于Throwable类,throwable类在构造的时候就会调用fillInStackTrace方法。
https://blog.csdn.net/feelabclihu/article/details/107572844