稳定性二——JE流程及处理方法

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;
    }
  1. 当远程Ibinder对象为空,则进程名为“system_server”
  2. 远程对象不为空时。如果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主要完成两件事情

  1. 调用addErrorToDropBox,将crashInfo的关键信息输出到dropbox文件中,目录位于data/system/dropbox,根据异常的进程名,一般名为system_server_crash@xxx.txt,system_app_crashxxxx,data_app_crashxxxx。写入的内容包括进程名、pid、uid、时间、flag、包名、前后台信息、fingerprint版本信息、crashInfo堆栈信息等,厂商定制可能会再加入一些其他的打印。到这里,日志已经保存完
  2. 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的产生,主要有两种原因:

  1. 编写的程序代码内部错误产生的异常,如调用对象为空(空指针异常)、数组越界异常、除0异常等。这种通常称为未检查的异常,在虚拟机中执行时会集中处理这些异常。
  2. 其他运行中异常,通过throw语句主动抛出的异常。这类异常称为检查异常,一般是程序认为自己遇到了严重的问题,后续再运行可能会出问题,主动告知方法调用者一些必要的信息。
  3. 未检查的异常是如何让线程退出循环,执行到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

  • 8
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值