1、Android系统java crash异常处理流程

参考:Android8.0 系统异常处理流程_此男子淡漠-CSDN博客

Java处理未捕获异常有个Thread.UncaughtExceptionHandler,在Android系统中当然也是通过实现其来进行未捕获异常处理。Android 默认系统异常处理是在启动SystemServer进程时设置的。

Zygote进程启动SystemServer时会调用ZygoteInit的forkSystemServer()方法,该方法中又通过handleSystemServerProcess()方法来对SystemServer进程做一些处理,最后会调用到RuntimeInit.commonInit()方法

frameworks/base/core/java/com/android/internal/os/RuntimeInit.java

protected static final void commonInit() {

Thread.setUncaughtExceptionPreHandler(new LoggingHandler());

// 该出就设置了默认未捕获异常的处理Handler-KillApplicationHandler

Thread.setDefaultUncaughtExceptionHandler(new KillApplicationHandler());

...

}

KillApplicationHandler代码如下:frameworks/base/core/java/com/android/internal/os/RuntimeInit.java

private static class KillApplicationHandler implements Thread.UncaughtExceptionHandler {

public void uncaughtException(Thread t, Throwable e) {

try {

...

// 1. mApplicationObject标识当前应用

ActivityManager.getService().handleApplicationCrash(

mApplicationObject, new ApplicationErrorReport.ParcelableCrashInfo(e));

} ...

finally {

// 无论如何都要保证出现crash的进程不存活

Process.killProcess(Process.myPid());

System.exit(10);

}

}

}

注:如上ActivityManager.getService()得到的就是ActivityManagerService的服务端代理对象,实现是通过Binder机制。看看AMS在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);

}

void handleApplicationCrashInner(String eventType, ProcessRecord r, String processName,

ApplicationErrorReport.CrashInfo crashInfo) {

// 1. 将crash信息写入event log中

EventLog.writeEvent(EventLogTags.AM_CRASH, 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, crashInfo);

// 2.

mAppErrors.crashApplication(r, crashInfo);

}

备注:如上注释1处将log记录在event log中。注释2处调用AppError的crashApplication方法

frameworks/base/services/core/java/com/android/server/am/AppErrors.java

void crashApplication(ProcessRecord r, ApplicationErrorReport.CrashInfo crashInfo) {

final int callingPid = Binder.getCallingPid();

final int callingUid = Binder.getCallingUid();

final long origId = Binder.clearCallingIdentity();

try {

// 调用内部的crashApplicationInner

crashApplicationInner(r, crashInfo, callingPid, callingUid);

} finally {

Binder.restoreCallingIdentity(origId);

}

}

继续看crashApplicationInner方法frameworks/base/services/core/java/com/android/server/am/AppErrors.java

void crashApplicationInner(ProcessRecord r, ApplicationErrorReport.CrashInfo crashInfo,

int callingPid, int callingUid) {

...

synchronized (mService) {

// 1. 处理有IActivityController的情况,如果Controller已经处理错误,则不会显示错误框

if (handleAppCrashInActivityController(r, crashInfo, shortMsg, longMsg, stackTrace,

timeMillis, callingPid, callingUid)) {

return;

}

...

AppErrorDialog.Data data = new AppErrorDialog.Data();

data.result = result;

data.proc = r;

...

// 2. 发送SHOW_ERROR_UI_MSG给AMS的mUiHandler,将弹出一个错误对话框,提示用户某进程crash

final Message msg = Message.obtain();

msg.what = ActivityManagerService.SHOW_ERROR_UI_MSG;

task = data.task;

msg.obj = data;

mService.mUiHandler.sendMessage(msg);

}

// 3. 调用AppErrorResult的get方法,该方法内部调用了wait方法,故为阻塞状态,当用户处理了对话框后会调用AppErrorResult的set方法,该方法内部调用了notifyAll()方法来唤醒线程。

// 注意此处涉及了两个线程的工作,crashApplicationInner函数工作在Binder调用所在的线程;对话框工作于AMS的Ui线程

int res = result.get();

Intent appErrorIntent = null;

MetricsLogger.action(mContext, MetricsProto.MetricsEvent.ACTION_APP_CRASH, res);

// 4. 判断用户操作结果,然后根据结果做不同处理

if (res == AppErrorDialog.TIMEOUT || res == AppErrorDialog.CANCEL) {

res = AppErrorDialog.FORCE_QUIT;

}

synchronized (mService) {

// 不在提示错误

if (res == AppErrorDialog.MUTE) {

stopReportingCrashesLocked(r);

}

// 尝试重启进程

if (res == AppErrorDialog.RESTART) {

mService.removeProcessLocked(r, false, true, "crash");

if (task != null) {

try {

mService.startActivityFromRecents(task.taskId,

ActivityOptions.makeBasic().toBundle());

} ...

}

}

// 强行结束进程

if (res == AppErrorDialog.FORCE_QUIT) {

long orig = Binder.clearCallingIdentity();

try {

// Kill it with fire!

mService.mStackSupervisor.handleAppCrashLocked(r);

if (!r.persistent) {

mService.removeProcessLocked(r, false, false, "crash");

mService.mStackSupervisor.resumeFocusedStackTopActivityLocked();

}

} finally {

Binder.restoreCallingIdentity(orig);

}

}

// 停止进程并报告错误

if (res == AppErrorDialog.FORCE_QUIT_AND_REPORT) {

appErrorIntent = createAppErrorIntentLocked(r, timeMillis, crashInfo);

}

...

}

if (appErrorIntent != null) {

try {

// 启动报告错误界面

mContext.startActivityAsUser(appErrorIntent, new UserHandle(r.userId));

} catch (ActivityNotFoundException e) {

Slog.w(TAG, "bug report receiver dissappeared", e);

}

}

}

备注:如上,注释1会优先让crash观察者进行crash处理,crash观察者通过AMS的setActivityController()方法进行设置,如果已经处理则不会再弹出错误对话框。注释2会发送SHOW_ERROR_UI_MSG消息给AMS的mUIHandler处理来请求弹出错误对话框。注释3通过调用AppErrorResult中的get()方法来使线程阻塞。需要注意的是此处涉及到两个线程,crashApplicationInner工作在Binder调用所在的线程,对话框显示则处于AMS的UI线程。具体AppErrorResult的工作后面会说到。待用户操作对话框后或者超时时间到时get()方法就会被唤醒,并且返回处理结果。注释4则根据用户操作结果进行不同的处理,例如强制停止进程,重启进程等。

crash对话框的显示和用户行为

frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java

final class UiHandler extends Handler {

@Override

public void handleMessage(Message msg) {

switch (msg.what) {

// 显示错误对话框

case SHOW_ERROR_UI_MSG: {

mAppErrors.handleShowAppErrorUi(msg);

ensureBootCompleted();

} break;

// 显示ANR对话框

case SHOW_NOT_RESPONDING_UI_MSG: {

mAppErrors.handleShowAnrUi(msg);

ensureBootCompleted();

} break;

...

}

可以看到UiHandler对错误和ANR对话框显示的处理,这里看错误对话框的显示,其还是通过AppErrors类进行处理。frameworks/base/services/core/java/com/android/server/am/AppErrors.java

void handleShowAppErrorUi(Message msg) {

...

synchronized (mService) {

ProcessRecord proc = data.proc;

AppErrorResult res = data.result;

// 1. crash 对话框已显示,故无需再显示

if (proc != null && proc.crashDialog != null) {

if (res != null) {

res.set(AppErrorDialog.ALREADY_SHOWING);

}

return;

}

...

final boolean crashSilenced = mAppsNotReportingCrashes != null &&

mAppsNotReportingCrashes.contains(proc.info.packageName);

if ((mService.canShowErrorDialogs() || showBackground) && !crashSilenced) {

// 2. 创建crash对话框

proc.crashDialog = new AppErrorDialog(mContext, mService, data);

} else {

// 3. 如果AMS禁止显示错误对话框,或者当前设备处于睡眠模式则不会让显示对话框

if (res != null) {

res.set(AppErrorDialog.CANT_SHOW);

}

}

}

// 4. 调用Dialog show方法显示crash对话框

if(data.proc.crashDialog != null) {

data.proc.crashDialog.show();

}

}

备注:注释1先对crash进程是否已经显示对话框做了判断,如果已经显示则无需显示。注释2处,手机没有息屏,AMS也允许显示crash对话框,则创建对话框,否则走注释3处,直接说明不显示。如果走到注释4则需要显示crash对话框,故直接调用Dialog的show()方法。这里对注释1和注释3处的res.set()方法做以解释,这res就是AppErrorResult,也就是在crashApplicationInner方法中创建的,该方法在请求AMS显示对话框时调用了result.get()使其阻塞,调用set方法后则会唤醒Binder调用线程,接着走下面代码,进而对结果进行判断。

看下AppErrorResult get()和set()的实现

frameworks/base/services/core/java/com/android/server/am/AppErrorResult.java

final class AppErrorResult {

public void set(int res) {

synchronized (this) {

mHasResult = true;

// 1. set方法设置mResult的值

mResult = res;

// 2.  调用notifyAll唤醒持有当前对象锁且处于阻塞状态的所有线程

notifyAll();

}

}

public int get() {

synchronized (this) {

while (!mHasResult) {

try {

//3. 实质通过wait()使当前线程阻塞

wait();

} catch (InterruptedException e) {

}

}

}

// 4. 返回mResult

return mResult;

}

boolean mHasResult = false;

int mResult;

}

通过get()方法线程阻塞,通过set方法更新mResult的值并唤醒处于等待队列的线程,此时接着get()方法wait后面的代码执行,将set()方法中更新的mResult值作为返回值。

当错误对话框弹出后,用户操作或者超时时间处理

frameworks/base/services/core/java/com/android/server/am/AppErrorDialog.java

@Override

public void onClick(View v) {

// 1. 判断点击控件,来决定操作

switch (v.getId()) {

// 请求重启进程

case com.android.internal.R.id.aerr_restart:

mHandler.obtainMessage(RESTART).sendToTarget();

break;

// 请求反馈报错问题

case com.android.internal.R.id.aerr_report:

mHandler.obtainMessage(FORCE_QUIT_AND_REPORT).sendToTarget();

break;

// 请求关闭crash Dialog并杀死进程

case com.android.internal.R.id.aerr_close:

mHandler.obtainMessage(FORCE_QUIT).sendToTarget();

break;

// 请求不再提示对话框

case com.android.internal.R.id.aerr_mute:

mHandler.obtainMessage(MUTE).sendToTarget();

break;

default:

break;

}

}

// 2. 受到请求信息后调用setResult()方法并关闭对话框

private final Handler mHandler = new Handler() {

public void handleMessage(Message msg) {

setResult(msg.what);

dismiss();

}

};

private void setResult(int result) {

synchronized (mService) {

if (mProc != null && mProc.crashDialog == AppErrorDialog.this) {

mProc.crashDialog = null;

}

}

// 3. 调用AppErrorResult的set方法使阻塞线程运行,并将用户点击结果告知

mResult.set(result);

mHandler.removeMessages(TIMEOUT);

}

如上,最终通过mResult.set()方法唤线程,是线程代码接着执行

frameworks/base/services/core/java/com/android/server/am/AppErrors.java

void crashApplicationInner(ProcessRecord r, ApplicationErrorReport.CrashInfo crashInfo,

int callingPid, int callingUid) {

...

// 3. 阻塞线程直至超时或者用户操作对话框

int res = result.get();

// 4. 判断用户操作结果,然后根据结果做不同处理

...

}

后续清理工作

根据前面的流程,我们知道当进程crash后,最终将被kill掉,此时AMS还需要完成后续的清理工作。

我们先来回忆一下进程启动后,注册到AMS的部分流程

frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java

// 进程启动后,对应的ActivityThread会attach到AMS上

private final boolean attachApplicationLocked(IApplicationThread thread,

int pid) {

...

final String processName = app.processName;

try {

// 1.  创建“讣告”接收者

AppDeathRecipient adr = new AppDeathRecipient(

app, pid, thread);

thread.asBinder().linkToDeath(adr, 0);

app.deathRecipient = adr;

}

...

}

当进程注册到AMS时,AMS注册了一个“讣告”接收者注册到进程中。

因此,当crash进程被kill后,AppDeathRecipient中的binderDied方法将被回调。看源码知道bindDied()方法中又会调用到appDiedLocked()方法

frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java

final void appDiedLocked(ProcessRecord app, int pid, IApplicationThread thread,

boolean fromBinderDied) {

...

// 1. 该进程没有杀死,则杀死进程

if (!app.killed) {

if (!fromBinderDied) {

killProcessQuiet(pid);

}

killProcessGroup(app.uid, pid);

app.killed = true;

}

if (app.pid == pid && app.thread != null &&

app.thread.asBinder() == thread.asBinder()) {

...

// 2.

handleAppDiedLocked(app, false, true);

...

} ...

}

备注:注释1会将进程杀死,注释2处为app死亡的关键处理。

frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java

private final void handleAppDiedLocked(ProcessRecord app,

boolean restarting, boolean allowRestart) {

int pid = app.pid;

// 1. 进行进程中service、ContentProvider、BroadcastReceiver等的收尾工作

boolean kept = cleanUpApplicationRecordLocked(app, restarting, allowRestart, -1,

false /*replacingPid*/);

if (!kept && !restarting) {

removeLruProcessLocked(app);

if (pid > 0) {

ProcessList.remove(pid);

}

}

...

// 2. 判断是否还存在可见的Activity

boolean hasVisibleActivities = mStackSupervisor.handleAppDiedLocked(app);

// 清除activity列表

app.activities.clear();

...

try {

if (!restarting && hasVisibleActivities

&& !mStackSupervisor.resumeFocusedStackTopActivityLocked()) {

// 3. 若当前crash进程中存在可视Activity,那么AMS还是会确保所有可见Activity正常运行,故会重启该进程

mStackSupervisor.ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS);

}

} finally {

mWindowManager.continueSurfaceLayout();

}

}

备注:注释1比较重要的是对于crash进程中的Bounded Service而言,会清理掉service与客户端之间的联系,此外若service的客户端重要性过低,还会被直接kill掉。注释2处判断是否应用还存在可见的Activity,注释3处对于可见的Activity系统要保证其正常运行,还会重新启动进程。

2、Android系统native crash异常处理流程

参考:Android稳定性系列8 Native crash处理流程_liuwg1226的专栏-CSDN博客

从系统全局来说,Crash分为Framework/App Crash, Native Crash,以及Kernel Crash。

(1)对于framework层或者app层的Crash(即Java层面Crash),那么往往是通过抛出未捕获异常而导致的Crash。

(2)至于Kernel Crash,很多情况是发生Kernel panic,对于内核崩溃往往是驱动或者硬件出现故障。

(3)Native Crash,即C/C++层面的Crash,这是介于系统framework层与Linux层之间的一层,这是本文接下来要讲解的内容。

system_server进程启动过程中,调用startOtherServices来启动各种其他系统Service时,也正是这个时机会创建一个用于监听native crash事件的NativeCrashListener对象(继承于线程),通过socket机制来监听,等待即debuggerd与该线程创建连接,并处理相应事件。紧接着通过NativeCrashListener#run()调用到AMS#handleApplicationCrashInner()函数来处理crash流程。

NativeCrashListener的主要工作:

(1)创建socket服务端”/data/system/ndebugsocket”

(2)等待socket客户端(即debuggerd)来建立连接;

(3)调用NativeCrashListener#consumeNativeCrashData来处理native crash信息;

(4)应答debuggerd已经建立连接,并写入应答消息告知debuggerd进程。

Native crash的工作核心是由debuggerd守护进程来完成。要了解Native Crash,首先从应用程序入口位于begin.S中的__linker_init入手。

2.1 begin.S

arch/arm/begin.S

ENTRY(_start)

mov r0, sp

//入口地址 【见小节1.2】

bl __linker_init

/* linker init returns the _entry address in the main image */

mov pc, r0

END(_start)

2.2 __linker_init

linker.cpp

extern "C" ElfW(Addr) __linker_init(void* raw_args) {

KernelArgumentBlock args(raw_args);

ElfW(Addr) linker_addr = args.getauxval(AT_BASE);

...

//【见小节1.3】

ElfW(Addr) start_address = __linker_init_post_relocation(args, linker_addr);

return start_address;

}

2.3 __linker_init_post_relocation

linker.cpp

static ElfW(Addr) __linker_init_post_relocation(KernelArgumentBlock& args, ElfW(Addr) linker_base) {

...

// Sanitize the environment.

__libc_init_AT_SECURE(args);

// Initialize system properties

__system_properties_init();

//【见小节1.4】

debuggerd_init();

...

}

2.4 debuggerd_init

linker/debugger.cpp

__LIBC_HIDDEN__ void debuggerd_init() {

struct sigaction action;

memset(&action, 0, sizeof(action));

sigemptyset(&action.sa_mask);

//【见小节1.5】

action.sa_sigaction = debuggerd_signal_handler;

//SA_RESTART代表中断某个syscall,则会自动重新调用该syscall

//SA_SIGINFO代表信号附带参数siginfo_t结构体可传送到signal_handler函数

action.sa_flags = SA_RESTART | SA_SIGINFO;

//使用备用signal栈(如果可用),以便我们能捕获栈溢出

action.sa_flags |= SA_ONSTACK;

sigaction(SIGABRT, &action, nullptr);

sigaction(SIGBUS, &action, nullptr);

sigaction(SIGFPE, &action, nullptr);

sigaction(SIGILL, &action, nullptr);

sigaction(SIGPIPE, &action, nullptr);

sigaction(SIGSEGV, &action, nullptr);

#if defined(SIGSTKFLT)

sigaction(SIGSTKFLT, &action, nullptr);

#endif

sigaction(SIGTRAP, &action, nullptr);

}

2.6 send_debuggerd_packet

linker/debugger.cpp

static void send_debuggerd_packet(siginfo_t* info) {

// Mutex防止多个crashing线程同一时间来来尝试跟debuggerd进行通信

static pthread_mutex_t crash_mutex = PTHREAD_MUTEX_INITIALIZER;

int ret = pthread_mutex_trylock(&crash_mutex);

if (ret != 0) {

if (ret == EBUSY) {

__libc_format_log(ANDROID_LOG_INFO, "libc",

"Another thread contacted debuggerd first; not contacting debuggerd.");

//等待其他线程释放该锁,从而获取该锁

pthread_mutex_lock(&crash_mutex);

}

return;

}

//建立与debuggerd的socket通道

int s = socket_abstract_client(DEBUGGER_SOCKET_NAME, SOCK_STREAM | SOCK_CLOEXEC);

...

debugger_msg_t msg;

msg.action = DEBUGGER_ACTION_CRASH;

msg.tid = gettid();

msg.abort_msg_address = reinterpret_cast<uintptr_t>(g_abort_message);

msg.original_si_code = (info != nullptr) ? info->si_code : 0;

//将DEBUGGER_ACTION_CRASH消息发送给debuggerd服务端

ret = TEMP_FAILURE_RETRY(write(s, &msg, sizeof(msg)));

if (ret == sizeof(msg)) {

char debuggerd_ack;

//阻塞等待debuggerd服务端的回应数据

ret = TEMP_FAILURE_RETRY(read(s, &debuggerd_ack, 1));

int saved_errno = errno;

notify_gdb_of_libraries();

errno = saved_errno;

}

close(s);

}

该方法的主要功能:

调用socket_abstract_client,建立于debuggerd的socket通道;

将action = DEBUGGER_ACTION_CRASH的消息发送给debuggerd服务端;

阻塞等待debuggerd服务端的回应数据。

接下来,看看debuggerd服务端接收到DEBUGGER_ACTION_CRASH的处理流程

debuggerd服务端

debuggerd 守护进程启动后,一直在等待socket client的连接。当native crash发送后便会向debuggerd发送action = DEBUGGER_ACTION_CRASH的消息。

2.1 do_server

/debuggerd/debuggerd.cpp

static int do_server() {

...

for (;;) {

sockaddr_storage ss;

sockaddr* addrp = reinterpret_cast<sockaddr*>(&ss);

socklen_t alen = sizeof(ss);

//等待客户端连接

int fd = accept4(s, addrp, &alen, SOCK_CLOEXEC);

if (fd == -1) {

continue; //accept失败

}

//处理native crash发送过来的请求【见小节2.2】

handle_request(fd);

}

return 0;

}

-------à一路调用到

worker_process,处于client发送过来的请求,server端通过子进程来处理

/debuggerd/debuggerd.cpp

static void worker_process(int fd, debugger_request_t& request) {

std::string tombstone_path;

int tombstone_fd = -1;

switch (request.action) {

case DEBUGGER_ACTION_CRASH:

//打开tombstone文件

tombstone_fd = open_tombstone(&tombstone_path);

if (tombstone_fd == -1) {

exit(1); //无法打开tombstone文件,则退出该进程

}

break;

...

}

……

if (!attach_gdb) {

//将进程crash情况告知AMS【见小节2.4.3】

activity_manager_write(request.pid, crash_signal, amfd, *amfd_data.get());

}

……

}

整个过程比较复杂,下面只介绍attach_gdb=false的执行流程:

(1)当DEBUGGER_ACTION_CRASH ,则调用open_tombstone并继续执行;

(2)调用ptrace方法attach到目标进程;

(3)调用BacktraceMap::Create来生成backtrace;

(4)当DEBUGGER_ACTION_CRASH,则执行activity_manager_connect;

(5)调用drop_privileges来取消特权模式;

(6)通过perform_dump执行dump操作;

(7)SIGBUS等致命信号,则调用engrave_tombstone(),这是核心方法

(8)调用activity_manager_write,将进程crash情况告知AMS;

(9)调用ptrace方法detach到目标进程;

(10)当DEBUGGER_ACTION_CRASH,发送信号SIGKILL给目标进程tid

备注:如上activity_manager_connect()该方法的功能是建立跟上层ActivityManager的socket连接。对于”/data/system/ndebugsocket”的socket的服务端是在NativeCrashListener.java方法中创建并启动的。

3、Android系统anr异常处理流程

参考:深入探索Android稳定性优化 – Android开发中文站(深入探索Android稳定性优化)

ANR(Application Not responding),是指应用程序未响应,Android系统对于一些事件需要在一定的时间范围内完成,如果超过预定时间能未能得到有效响应或者响应时间过长,都会造成ANR。一般地,这时往往会弹出一个提示框,告知用户当前xxx未响应,用户可选择继续等待或者Force Close。

ANR的几种类型:

(1)KeyDispatchTimeout (5 seconds) 按键或触摸事件处理超时(一般是UI主线程做了耗时的操作,这类ANR最常见)

(2)BroadcastTimeout(10 seconds,即10s内没有执行完成) 广播的分发和处理超时(一般是onReceiver执行时间过长)

(3)ServiceTimeout(20 seconds) Service的启动和执行20s超时

(4)ContentProviderTimeout(10 second)ContentProvider 在10S内没有处理完成发生ANR。

ActivityManagerService.appNotResponding()在程序无响应、ANR时被调用

/frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java

final void appNotResponding(ProcessRecord app, ActivityRecord activity,

ActivityRecord parent, boolean aboveSystem, final String annotation) {

...

updateCpuStatsNow(); //第一次 更新cpu统计信息

synchronized (this) {

//PowerManager.reboot() 会阻塞很长时间,因此忽略关机时的ANR

if (mShuttingDown) {

return;

} else if (app.notResponding) {

return;

} else if (app.crashing) {

return;

}

//记录ANR到EventLog

EventLog.writeEvent(EventLogTags.AM_ANR, app.userId, app.pid,

app.processName, app.info.flags, annotation);

// 将当前进程添加到firstPids

firstPids.add(app.pid);

int parentPid = app.pid;

//将system_server进程添加到firstPids

if (MY_PID != app.pid && MY_PID != parentPid) firstPids.add(MY_PID);

for (int i = mLruProcesses.size() - 1; i >= 0; i--) {

ProcessRecord r = mLruProcesses.get(i);

if (r != null && r.thread != null) {

int pid = r.pid;

if (pid > 0 && pid != app.pid && pid != parentPid && pid != MY_PID) {

if (r.persistent) {

firstPids.add(pid); //将persistent进程添加到firstPids

} else {

lastPids.put(pid, Boolean.TRUE); //其他进程添加到lastPids

}

}

}

}

}

// 记录ANR输出到main log

StringBuilder info = new StringBuilder();

info.setLength(0);

info.append("ANR in ").append(app.processName);

if (activity != null && activity.shortComponentName != null) {

info.append(" (").append(activity.shortComponentName).append(")");

}

info.append("\n");

info.append("PID: ").append(app.pid).append("\n");

if (annotation != null) {

info.append("Reason: ").append(annotation).append("\n");

}

if (parent != null && parent != activity) {

info.append("Parent: ").append(parent.shortComponentName).append("\n");

}

//创建CPU tracker对象

final ProcessCpuTracker processCpuTracker = new ProcessCpuTracker(true);

//输出traces信息【见小节2】

File tracesFile = dumpStackTraces(true, firstPids, processCpuTracker,

lastPids, NATIVE_STACKS_OF_INTEREST);

updateCpuStatsNow(); //第二次更新cpu统计信息

//记录当前各个进程的CPU使用情况

synchronized (mProcessCpuTracker) {

cpuInfo = mProcessCpuTracker.printCurrentState(anrTime);

}

//记录当前CPU负载情况

info.append(processCpuTracker.printCurrentLoad());

info.append(cpuInfo);

//记录从anr时间开始的Cpu使用情况

info.append(processCpuTracker.printCurrentState(anrTime));

//输出当前ANR的reason,以及CPU使用率、负载信息

Slog.e(TAG, info.toString());

//将traces文件 和 CPU使用率信息保存到dropbox,即data/system/dropbox目录

addErrorToDropBox("anr", app, app.processName, activity, parent, annotation,

cpuInfo, tracesFile, null);

synchronized (this) {

...

//后台ANR的情况, 则直接杀掉

if (!showBackground && !app.isInterestingToUserLocked() && app.pid != MY_PID) {

app.kill("bg anr", true);

return;

}

//设置app的ANR状态,病查询错误报告receiver

makeAppNotRespondingLocked(app,

activity != null ? activity.shortComponentName : null,

annotation != null ? "ANR " + annotation : "ANR",

info.toString());

//重命名trace文件

String tracesPath = SystemProperties.get("dalvik.vm.stack-trace-file", null);

if (tracesPath != null && tracesPath.length() != 0) {

//traceRenameFile = "/data/anr/traces.txt"

File traceRenameFile = new File(tracesPath);

String newTracesPath;

int lpos = tracesPath.lastIndexOf (".");

if (-1 != lpos)

// 新的traces文件= /data/anr/traces_进程名_当前日期.txt

newTracesPath = tracesPath.substring (0, lpos) + "_" + app.processName + "_" + mTraceDateFormat.format(new Date()) + tracesPath.substring (lpos);

else

newTracesPath = tracesPath + "_" + app.processName;

traceRenameFile.renameTo(new File(newTracesPath));

}

//弹出ANR对话框

Message msg = Message.obtain();

HashMap<String, Object> map = new HashMap<String, Object>();

msg.what = SHOW_NOT_RESPONDING_MSG;

msg.obj = map;

msg.arg1 = aboveSystem ? 1 : 0;

map.put("app", app);

if (activity != null) {

map.put("activity", activity);

}

//向ui线程发送,内容为SHOW_NOT_RESPONDING_MSG的消息

mUiHandler.sendMessage(msg);

}

}

当发生ANR时, 会按顺序依次执行:

(1)输出ANR Reason信息到Event Log. 也就是说ANR触发的时间点最接近的就是EventLog中输出的am_anr信息;

(2)收集并输出重要进程列表中的各个线程的traces信息,该方法较耗时; 【见小节2】

(3)输出当前各个进程的CPU使用情况以及CPU负载情况;

(4)将traces文件和 CPU使用情况信息保存到dropbox,即/data/system/dropbox目录

(5)根据进程类型,来决定直接后台杀掉,还是弹框告知用户.

ANR输出重要进程的traces信息,这些进程包含:

(1)firstPids队列:第一个是ANR进程,第二个是system_server,剩余是所有persistent进程;

(2)Native队列:是指/system/bin/目录的mediaserver,sdcard 以及surfaceflinger进程;

(3)lastPids队列: 是指mLruProcesses中的不属于firstPids的所有进程。

5、总结:

(1)Java/native crash调用:AMS#handleApplicationCrashInner方法(注意:app进程调用引起的native crash会走到AMS的这里,通过NativeCrashListener# consumeNativeCrashData函数中调用NativeCrashReporter走到AMS)

(2)native crash 调用:NativeCrashListener# consumeNativeCrashData 方法(注:native守护进程crash会走到这里,如wpa_supplicant)

//关键日志:

NativeCrashListener: Read pid=7441 signal=11

/system/bin/tombstoned: Tombstone written to: /data/tombstones/tombstone_04

BootReceiver: Copying /data/tombstones/tombstone_04 to DropBox (SYSTEM_TOMBSTONE)

(3)Anr调用:AMS#appNotResponding

注:需要在开发者选项中打开相应配置才会弹框。

三、DropBoxManagerService日志生成服务

参考:DropBoxManager启动篇 - Gityuan博客 | 袁辉辉的技术博客

DropBoxManagerService(简称DBMS,下同)用于生成和管理系统运行时的一些日志文件。日志文件大多记录的是系统或某个应用出错的日志信息。比如某一个应用发生了crash/anr等,或者系统发生了不可预期的错误,重启,低内存等事件,都会写入到dropbox中。该日志输出在/data/system/dropbox目录下

说明:

详细参考:介绍 Android DropBoxManager Service_natureXin的博客-CSDN博客

(1)常见的日志类型:

system_app_crash 系统app崩溃

system_app_anr 系统app无响应

data_app_crash 普通app崩溃

data_app_anr 普通app无响应

system_server_anr system进程无响应

system_server_watchdog system进程发生watchdog

system_server_crash system进程崩溃

system_server_native_crash system进程native出现崩溃

system_server_wtf system进程发生严重错误

system_server_lowmem system进程内存不足

SYSTEM_TOMBSTONE Native 进程的崩溃

SYSTEM_RECOVERY_LOG 系统恢复

(2)StrictMode:

比正常模式检测得更严格, 通常用来监测不应当在主线程执行的网络, 文件等操作

(3)内核错误相关:

SYSTEM_LAST_KMSG, 如果 /proc/last_kmsg 存在.

APANIC_CONSOLE, 如果 /data/dontpanic/apanic_console 存在.

APANIC_THREADS, 如果 /data/dontpanic/apanic_threads 存在

1、DropBoxManager 和DropBoxManagerService简介

Android系统启动SystemServer进程时,SystemServer确保核心服务,以及DropboxManagerService所依赖的服务初始化完成之后才启动该服务。在startOtherServices()过程会启动DBMS服务。简述大体流程:

和大多数系统服务一样,DropBoxManager 和 DropBoxManagerService 通过aidl实现跨进程通信。客户端可以通过DropBoxManager 提供的接口来 写入 和 读取 日志。

(1)写入日志

frameworks/base/core/java/android/os/DropBoxManager.java

public void addText(String tag, String data) {

mService.add(new Entry(tag, 0, data));

}

}

public void addData(String tag, byte[] data, int flags) {

mService.add(new Entry(tag, 0, data, flags));

}

public void addFile(String tag, File file, int flags) throws IOException {

mService.add(new Entry(tag, 0, file, flags));

}

DropBoxManager#addText方法, 最终进入DropBoxManagerService#add方法, 这两个类就类似于AIDL中的客户端服务端, 他们的本质都是通过Binder通信。DropBoxManager 和 DropBoxManagerService 交互的接口都定义在DropBoxManagerService.aidl。补充:在DropBoxManagerService #add方法,主要是日志文件的生成、压缩, IO操作。

(2)读取日志

frameworks/base/core/java/android/os/DropBoxManager.java

/**

* Gets the next entry from the drop box <em>after</em> the specified time.

* Requires <code>android.permission.READ_LOGS</code>.  You must always call

* {@link Entry#close()} on the return value!

*

* @param tag of entry to look for, null for all tags

* @param msec time of the last entry seen

* @return the next entry, or null if there are no more entries

*/

public Entry getNextEntry(String tag, long msec) {

try {

return mService.getNextEntry(tag, msec);

} catch (RemoteException e) {

throw e.rethrowFromSystemServer();

}

}

读取日志只有这一个方法,获取的就是DropBoxManager#Entry实例。注意,tag传null的话,就会返回所有的日志。通过getNextEntry函数获取指定类型和指定时间点之后的第一条日志,要使用这个功能应用程序需要有“android.permission.READ_LOGS”的权限,并且在使用完毕返回的Entry对象后要调用其close函数确保关闭日志文件的文件描述符(如果不关闭的话可能造成进程打开的文件描述符超过1024而崩溃,Android中限制每个进程的文件描述符上限为1024)。

DropBoxManager是可以供其系统服务直接调用的类。调用如下:

final DropBoxManager dbox = (DropBoxManager)mContext.getSystemService(Context.DROPBOX_SERVICE);
dbox.addText(dropboxTag, sb.toString());

2、DropBoxManagerService服务启动

该服务是在SystemServer启动以后被添加到ServiceManager中:

framework/base/services/java/com/android/server/SystemServer.java

private void startOtherServices() {

//初始化DBMS,并登记该服务【见小节1.2】

ServiceManager.addService(Context.DROPBOX_SERVICE,

new DropBoxManagerService(context, new File("/data/system/dropbox")));

...

}

其中DROPBOX_SERVICE = “dropbox”, DBMS工作目录位于”/data/system/dropbox”,这个过程向ServiceManager 登记名为“dropbox”的服务。那么可通过dumpsys dropbox来查看该dropbox服务信息。启动流程如下

补充:ServiceManager是用来管理所有Service的管理器。addService方法第一个参数是 服务的名字,第二个参数传入服务的对象。 对应的,使用context.getSystemService获取的时候,也需要传入对应的服务名字。ServiceManager本身对上层APP是hide的,当然也可以通过反射ServiceManager获取对应的服务。

如上可知,最终通过调用DropBoxManagerService的onStart()方法初始化服务。

另外,DropBoxManagerService会创建一个内容ContentResolver,并在onBootPhase()的PHASE_SYSTEM_SERVICE_READY阶段监听ACTION_DEVICE_STORAGE_LOW,当存储空间紧张时来通过trimToFit()函数进行空间的清理。接下来看DBSM#onBOotPhase()函数

@Override

public void onBootPhase(int phase) {

switch (phase) {

case PHASE_SYSTEM_SERVICES_READY:

IntentFilter filter = new IntentFilter();

filter.addAction(Intent.ACTION_DEVICE_STORAGE_LOW);

getContext().registerReceiver(mReceiver, filter);

mContentResolver.registerContentObserver(

Settings.Global.CONTENT_URI, true,

new ContentObserver(new Handler()) {

@Override

public void onChange(boolean selfChange) {

mReceiver.onReceive(getContext(), (Intent) null);

}

});

getLowPriorityResourceConfigs();

break;

case PHASE_BOOT_COMPLETED:

mBooted = true;

break;

}

}

说明:在系统启动的各个阶段会调用startBootPhase()函数来针对不同阶段作出不同的处理,SystemServiceManager.java会通过startBootPhase()函数遍历所有服务的onBootPhase()函数来初始化指定的操作,那么这里由于DropboxManagerService.java也属于SystemService.java的子类,因此这里也重写onBootPhase()函数,针对系统不同阶段作出不同响应。有两个监听处理:

(1)监听PHASE_SYSTEM_SERVICES_READY阶段,此阶段系统核心服务已经启动完成,且此时DeviceStorageMonitorService服务已经启动,因此在这里注册Intent.ACTION_DEVICE_STORAGE_LOW 的广播接收器以监听存储空间不足的情况,该Receiverr由DeviceStorageMonitorService发出。这里注册了一个ContentObserver用于处理Settings.Global.CONTENT_URI是否发生了变化,并调用Receiver的onReceiver进行处理。

(2)监听PHASE_SYSTEM_SERVICES_READY阶段,在系统启动的各个阶段会调用startBootPhase函数来针对不同阶段作出不同的处理,SystemServiceManager.java会通过startBootPhase()函数遍历所有服务的onBootPhase()函数来初始化指定的操作,那么这里由于DropboxManagerService.java也属于SystemService.java的子类,因此这里也重写onBootPhase()函数,针对系统不同阶段作出不同响应。

2.1 DropBoxManagerService的构造函数以及onReceive广播

frameworks/base/services/core/java/com/android/server/ DropBoxManagerService.java

public final
class DropBoxManagerService extends IDropBoxManagerService.Stub
{public DropBoxManagerService(final Context context, File path)
{mDropBoxDir = path;  // 目录为/data/system/dropboxmContext = context;mContentResolver = context.getContentResolver();IntentFilter filter = new IntentFilter();// 监听存储设备可用空间低的广播filter.addAction(Intent.ACTION_DEVICE_STORAGE_LOW);// 监听开机完毕的广播filter.addAction(Intent.ACTION_BOOT_COMPLETED);context.registerReceiver(mReceiver, filter);// Settings数据库变化时则回调广播接收者的onReceive方法,此处//CONTENT_URI=content://settings/global"mContentResolver.registerContentObserver(Settings.Global.CONTENT_URI, true,new ContentObserver(new Handler()) {public void onChange(boolean selfChange) {//监听到Settings数据库变化的话,就调用onReceivemReceiver.onReceive(context, (Intent) null);}});mHandler = new Handler() {public void handleMessage(Message msg) {           // 发送广播            if(msg.what == MSG_SEND_BROADCAST) {mContext.sendBroadcastAsUser((Intent)msg.obj, UserHandle.OWNER,android.Manifest.permission.READ_LOGS);}}};}
}

可以看到DBMS的构造方法主要是注册了一个BroadcastReceiver,监听了三个事件:

(1)ACTION_DEVICE_STORAGE_LOW:当设备存储空间不足,就会发送此广播,触发onReceive;

(2)ACTION_BOOT_COMPLETED: 当设备启动完毕后,发送此广播,触发onReceive;

(3)当Settings的数据库变化后,触发onReceive

主要的处理逻辑就在onReceive函数中去了,看一下onReceive函数:

frameworks/base/services/core/java/com/android/server/ DropBoxManagerService.java

private final BroadcastReceiver mReceiver = new BroadcastReceiver() {

@Override

public void onReceive(Context context, Intent intent) {

...

new Thread() {

public void run() {

try {

init(); // 1、先做初始化工作,如生产dropbox目录、统计已有文件的大小等

trimToFit(); //2、整理日志,控制在一定的存储空间范围内

} catch (IOException e) {

Slog.e(TAG, "Can't init", e);

}

}

}.start();

}

};

上述的 代码1 和 代码2 的两个方法都是synchronize类型的,主要作用有

  1. init() 用来扫描磁盘,做一些初始化操作,统计目前总的文件大小等;

该方法主要功能:

创建目录/data/system/dropbox;

列举该目录下所有文件,并对其进行:

将每一个dropbox文件都对应于一个EntryFile对象,根据文件名来获取相应的时间戳;

删除后缀为.tmp的文件;

删除时间戳为0的文件.

(2)trimToFit() 用来控制总的日志文件大小,多了就删除旧的日志文件。

trimToFit方法过程中触发条件是:当文件有效时长超过3天,或者最大文件数超过1000,再或者剩余可用存储设备过低;

DropBoxManagerService.java有很多常量参数:

DEFAULT_AGE_SECONDS = 3 * 86400 //文件最长可存活时长为3天

DEFAULT_MAX_FILES = 1000 //最大dropbox文件个数为1000

DEFAULT_QUOTA_KB = 5 * 1024 //分配dropbox空间的最大值5M

DEFAULT_QUOTA_PERCENT = 10 //是指dropbox目录最多可占用空间比例10%

DEFAULT_RESERVE_PERCENT = 10 //是指dropbox不可使用的存储空间比例10%

QUOTA_RESCAN_MILLIS = 5000 //重新扫描retrim时长为5s

上面这些都是默认值,完全可以通过设置content://settings/global数据库中相应项来设定值

2.2 AMS调用DBMS

由上2.1可知AMS通过addErrorToDropBox方法调用到DBMS

/frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java

public void addErrorToDropBox(String eventType,

ProcessRecord process, String processName, ActivityRecord activity,

ActivityRecord parent, String subject,

final String report, final File logFile,

final ApplicationErrorReport.CrashInfo crashInfo) {

//创建dropbox标签名,通过processClass函数创建dropboxTag

//dropbox日志前缀是一个特定tag,由 “进程类型_事件类型” 组成

//进程类型有:system_server, system_app, data_app三种

//事件类型有:crash, wtf(what a terrible failure),anr ,lowmem四种(以前只有前三种)

final String dropboxTag = processClass(process) + "_" + eventType;

//获取dropbox服务的代理端

final DropBoxManager dbox = (DropBoxManager)

mContext.getSystemService(Context.DROPBOX_SERVICE);

//当不需要输出dropbox报告则直接返回

if (dbox == null || !dbox.isTagEnabled(dropboxTag)) return;

final StringBuilder sb = new StringBuilder(1024);

//输出Process,flags,以及进程中所有package 【见小节2.1.2】

appendDropBoxProcessHeaders(process, processName, sb);

...

if (subject != null) {

sb.append("Subject: ").append(subject).append("\n");

}

sb.append("Build: ").append(Build.FINGERPRINT).append("\n");

sb.append("\n");

//创建新线程,避免将调用者阻塞在I/O

Thread worker = new Thread("Error dump: " + dropboxTag) {

@Override

public void run() {

if (report != null) {

//比如ANR时输出Cpuinfo,或者lowmem时输出的内存信息

sb.append(report);

}

if (logFile != null) {

//比如anr或者Watchdog时输出的traces文件(kill -3),最大上限为256KB

sb.append(FileUtils.readTextFile(logFile, DROPBOX_MAX_SIZE,

"\n\n[[TRUNCATED]]"));

}

if (crashInfo != null && crashInfo.stackTrace != null) {

// 比如crash时输出的调用栈

sb.append(crashInfo.stackTrace);

}

String setting = Settings.Global.ERROR_LOGCAT_PREFIX + dropboxTag;

int lines = Settings.Global.getInt(mContext.getContentResolver(), setting, 0);

//当dropboxTag所对应的settings项不等于0,则输出logcat

if (lines > 0) {

//输出evets/system/main/crash这些log信息

java.lang.Process logcat = new ProcessBuilder("/system/bin/logcat",

"-v", "time", "-b", "events", "-b", "system", "-b", "main",

"-b", "crash",

"-t", String.valueOf(lines)).redirectErrorStream(true).start();

input = new InputStreamReader(logcat.getInputStream());

int num;

char[] buf = new char[8192];

//不断读取input中的log内容,并添加到sb

while ((num = input.read(buf)) > 0) sb.append(buf, 0, num);

...

}

//将log信息输出到DropBox

dbox.addText(dropboxTag, sb.toString());

}

};

if (process == null) {

//当进程为空,意味着system_server进程崩溃,系统可能很快就要挂了,

//那么不再创建新线程,而是直接在system_server进程中同步运行

worker.run();

} else {

//启动新线程

worker.start();

}

}

该方法主要功能是输出以下内容项:

(1)Process,flags, package等头信息;

(2)当report不为空,则比如ANR时输出Cpuinfo,或者lowmem时输出的内存信息

(3)当logFile不为空,则比如anr或者Watchdog时输出的traces文件(kill -3),最大上限为256KB;

(3)当stack不为空,则比如crash时输出的调用栈;

(4)输出logcat的events/system/main/crash信息。

上述代码中,生成日志的内容的过程添加了Activity、process等信息。 最后调用addText把日志内容传递给DBMS生成文件.

2.3 DropBoxManagerService日志文件的生成(/data/system/dropbox)

DBMS主要记录部分日志信息,某个应用crash时,ActivityManagerService的handleApplicationCrash方法就会调用,内部就会调用DBM。DropBoxManager#addText方法, 最终进入DropBoxManagerService#add方法, 这两个类就类似于AIDL中的客户端服务端,他们的本质都是通过Binder通信。

/android/frameworks/base/services/core/java/com/android/server/DropBoxManagerService.java

public void add(DropBoxManager.Entry entry) {

File temp = null;

OutputStream output = null;

final String tag = entry.getTag();//先取出tag

try {

int flags = entry.getFlags();

if ((flags & DropBoxManager.IS_EMPTY) != 0) throw new IllegalArgumentException();

init(); //先做初始化工作,如生产dropbox目录、统计已有文件的大小等

if (!isTagEnabled(tag)) return;//如果tag被禁止,则不能生产日志文件

long max = trimToFit();

long lastTrim = System.currentTimeMillis();

byte[] buffer = new byte[mBlockSize];

InputStream input = entry.getInputStream(); //获取数据

//在决定是否压缩数据前,至少要有一块数据

int read = 0;

while (read < buffer.length) {

int n = input.read(buffer, read, buffer.length - read);

if (n <= 0) break;

read += n;

}

// 如果至少有一块数据,则进行压缩,否则以不压缩形式写入数据

temp = new File(mDropBoxDir, "drop" + Thread.currentThread().getId() + ".tmp");

int bufferSize = mBlockSize;

if (bufferSize > 4096) bufferSize = 4096;

if (bufferSize < 512) bufferSize = 512;

FileOutputStream foutput = new FileOutputStream(temp);

output = new BufferedOutputStream(foutput, bufferSize);

if (read == buffer.length && ((flags & DropBoxManager.IS_GZIPPED) == 0)) {

//如果要压缩,就输出到压缩文件

//DBMS很珍惜data分区,文件size大于一个mBlockSize,则一定要压缩,节省空间

output = new GZIPOutputStream(output);

flags = flags | DropBoxManager.IS_GZIPPED;

}

do {

output.write(buffer, 0, read);

long now = System.currentTimeMillis();

if (now - lastTrim > 30 * 1000) {

max = trimToFit();  // In case data dribbles in slowly

lastTrim = now;

}

read = input.read(buffer);

if (read <= 0) {

FileUtils.sync(foutput);

output.close();  // Get a final size measurement

output = null;

} else {

output.flush();  // So the size measurement is pseudo-reasonable

}

...

} while (read > 0);

...

}

上述add方法主要是日志文件的生成、压缩, IO操作写入dropbox日志目录中。压缩过程能将几十KB 压缩到个位数KB大小.

3、DropBox工作

当发生以下任一场景,都会调用AMS.addErrorToDropBox()来触发DBMS工作。

  1. crash: 执行AMS.handleApplicationCrashInner过程触发DBMS工作;

具体流程参考:http://gityuan.com/2016/06/24/app-crash/

  1. anr: 执行AMS.appNotResponding()过程触发DBMS工作;

具体流程参考:http://gityuan.com/2016/07/02/android-anr/

  1. watchdog: framework层系统服务是否发生死锁时执行Watchdog.run()过程;

具体流程参考:http://gityuan.com/2016/06/21/watchdog/

(4)native_crash: 当调用NativeCrashReporter.run()的过程;

(6)wtf: 当调用Log.wtf()或者Log.wtfQuiet()的过程;

(7)lowmem: 当内存较低时,触发AMS.reportMemUsage()过程;

(8)…

问题1:java crash异常信息如何被收集至DropBoxManagerService?

Android 系统默认已经为所有的应用进程设置了 defaultUncaughtExceptionHandler。这个commonInit方法会在每个应用启动前被调用。

frameworks/base/core/java/com/android/internal/os/ RuntimeInit.java

在RuntimeInit.java中的默认crash拦截器内,会通过方法handleApplicationCrash将Crash信息报给ActivityManagerNative。

总体来说,crash传递给 AMS。此外 AMS 还会通过 DBMS(DropBoxManagerService)将错误日志做持久化存储/data/system/dropbox目录。DBMS 也是系统统一收集管理各类异常的系统服务。

问题2:native crash生成tombstones文件怎样保存到/data/system/dropbox?

在系统AMS中会注册接收native crash的监听器, 以便在native进程crash时清理恢复app的生命周期等事项。在ActivityManagerService中会监听tombstone 事件

frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java

public void startObservingNativeCrashes() {

final NativeCrashListener ncl = new NativeCrashListener(this);

ncl.start();

}

说明:SystemServer.java#startOtherServices()方法启动AMS时调用startObservingNativeCrashes.

查看NativeCrashListener构造,将AMS对象传递给了NativeCrashListener

frameworks/base/services/core/java/com/android/server/am/NativeCrashListener.java

通过NativeCrashListener# NativeCrashReporter方法将crash传递到AMS;当crash时NativeCrashListener#consumeNativeCrashData调用到AMS#handleApplicationCrashInner,最终调用DropBoxManagerService。

【备注:NativeCrashListener 线程启动后开始不断监听debugger的socket:/data/system/ndebugsocket. 开机启动时会开启debuggerd监控进程接收native进程异常信号量,如SIGSTOP,SIGABRT,SIGSEGV等,native处理完成后,即会往上面AMS里介绍的final InetUnixAddress sockAddr = new InetUnixAddress(DEBUGGERD_SOCKET_PATH)这个socket通知.】

补充:跟system_server相关native crash才会传递到AMS,否则只传递到NativeCrashListener#consumeNativeCrashData中。例如wpa_supplicant进程crash只生成 tombstones_XX文件。流程可参考:Android debuggerd 源码分析 - Android移动开发技术文章_手机开发 - 红黑联盟

问题3:dropbox如何保存SYSTEM_TOMBSTONE信息?

Android系统开机自动启动程序,接收开机广播

/frameworks/base/core/java/com/android/server/BootReceiver.java

在/framework/base/core/res/AndroidManifest.xml定义BootReceive接收的广播类型

android.intent.action.BOOT_COMPLETED

主要看BootReceive#logBootEvents方法

private void logBootEvents(Context ctx) throws IOException {

final DropBoxManager db = (DropBoxManager) ctx.getSystemService(Context.DROPBOX_SERVICE);

final String headers = getBootHeadersToLogAndUpdate();

final String bootReason = SystemProperties.get("ro.boot.bootreason", null);

String recovery = RecoverySystem.handleAftermath(ctx);

if (recovery != null && db != null) {

db.addText("SYSTEM_RECOVERY_LOG", headers + recovery);

}

String lastKmsgFooter = "";

if (bootReason != null) {

lastKmsgFooter = new StringBuilder(512)

.append("\n")

.append("Boot info:\n")

.append("Last boot reason: ").append(bootReason).append("\n")

.toString();

}

HashMap<String, Long> timestamps = readTimestamps();

if (SystemProperties.getLong("ro.runtime.firstboot", 0) == 0) {

if (StorageManager.inCryptKeeperBounce()) {

// Encrypted, first boot to get PIN/pattern/password so data is tmpfs

// Don't set ro.runtime.firstboot so that we will do this again

// when data is properly mounted

} else {

String now = Long.toString(System.currentTimeMillis());

SystemProperties.set("ro.runtime.firstboot", now);

}

if (db != null) db.addText("SYSTEM_BOOT", headers);

// Negative sizes mean to take the *tail* of the file (see FileUtils.readTextFile())

addFileWithFootersToDropBox(db, timestamps, headers, lastKmsgFooter,

"/proc/last_kmsg", -LOG_SIZE, "SYSTEM_LAST_KMSG");

addFileWithFootersToDropBox(db, timestamps, headers, lastKmsgFooter,

"/sys/fs/pstore/console-ramoops", -LOG_SIZE, "SYSTEM_LAST_KMSG");

addFileWithFootersToDropBox(db, timestamps, headers, lastKmsgFooter,

"/sys/fs/pstore/console-ramoops-0", -LOG_SIZE, "SYSTEM_LAST_KMSG");

addFileToDropBox(db, timestamps, headers, "/cache/recovery/log", -LOG_SIZE,

"SYSTEM_RECOVERY_LOG");

addFileToDropBox(db, timestamps, headers, "/cache/recovery/last_kmsg",

-LOG_SIZE, "SYSTEM_RECOVERY_KMSG");

addAuditErrorsToDropBox(db, timestamps, headers, -LOG_SIZE, "SYSTEM_AUDIT");

} else {

if (db != null) db.addText("SYSTEM_RESTART", headers);

}

// log always available fs_stat last so that logcat collecting tools can wait until

// fs_stat to get all file system metrics.

logFsShutdownTime();

logFsMountTime();

addFsckErrorsToDropBoxAndLogFsStat(db, timestamps, headers, -LOG_SIZE, "SYSTEM_FSCK");

logSystemServerShutdownTimeMetrics();

// Scan existing tombstones (in case any new ones appeared)

File[] tombstoneFiles = TOMBSTONE_DIR.listFiles();

for (int i = 0; tombstoneFiles != null && i < tombstoneFiles.length; i++) {

if (tombstoneFiles[i].isFile()) {

addFileToDropBox(db, timestamps, headers, tombstoneFiles[i].getPath(),

LOG_SIZE, "SYSTEM_TOMBSTONE");

}

}

writeTimestamps(timestamps);

// Start watching for new tombstone files; will record them as they occur.

// This gets registered with the singleton file observer thread.

sTombstoneObserver = new FileObserver(TOMBSTONE_DIR.getPath(), FileObserver.CREATE) {

@Override

public void onEvent(int event, String path) {

HashMap<String, Long> timestamps = readTimestamps();

try {

File file = new File(TOMBSTONE_DIR, path);

if (file.isFile() && file.getName().startsWith("tombstone_")) {

addFileToDropBox(db, timestamps, headers, file.getPath(), LOG_SIZE,

TAG_TOMBSTONE);

}

} catch (IOException e) {

Slog.e(TAG, "Can't log tombstone", e);

}

writeTimestamps(timestamps);

}

};

sTombstoneObserver.startWatching();

}

如上代码,系统服务(System Serve)启动完成后通过System Server BootReceiver进行一系列自检, 包括:

(1)开机

每次开机都会增加一条 SYSTEM_BOOT 记录.

(2)System Server 重启

如果系统服务(System Server)不是开机后的第一次启动, 会增加一条 SYSTEM_RESTART 记录, 正常情况下系统服务(System Server)在一次开机中只会启动一次, 启动第二次就意味着 bug.

(3)Kernel Panic (内核错误)

发生 Kernel Panic 时, Kernel 会记录一些 log 信息到文件系统, 因为 Kernel 已经挂掉了, 当然这时不可能有其他机会来记录错误信息了. 唯一能检测 Kernel Panic 的办法就是在手机启动后检查这些 log 文件是否存在, 如果存在则意味着上一次手机是因为 Kernel Panic 而宕机, 并记录这些日志到 DropBoxManager 中. DropBoxManager 记录 TAG 名称和对应的文件名分别是:

  • SYSTEM_LAST_KMSG, 如果 /proc/last_kmsg 存在.
  • APANIC_CONSOLE, 如果 /data/dontpanic/apanic_console 存在.
  • APANIC_THREADS, 如果 /data/dontpanic/apanic_threads 存在.

(4)系统恢复(System Recovery)

通过检测文件 /cache/recovery/log 是否存在来检测设备是否因为系统恢复而重启, 并增加一条 SYSTEM_RECOVERY_LOG 记录到 DropBoxManager 中。

BootReceiver关键日志标签:

10-13 18:25:46.318  5884  5884 E crash_dump64: AM data write failed: Broken pipe

10-13 18:25:46.319  1428  1428 E /system/bin/tombstoned: Tombstone written to: /data/tombstones/tombstone_00

10-13 18:25:46.335   818   942 I BootReceiver: Copying /data/tombstones/tombstone_00 to DropBox (SYSTEM_TOMBSTONE)

10-13 18:25:46.337   818   942 I DropBoxManagerService: add tag=SYSTEM_TOMBSTONE isTagEnabled=true flags=0x2

补充:列举部分常见tags及含义

DropboxTags

含义

system_server_anr

system进程无响应

system_server_crash

system进程崩溃

system_server_native_crash

system进程native出现崩溃

system_server_wtf

system进程发生严重错误

system_server_lowmem

system进程内存不足

system_server_watchdog

system进程发生watchdog

system_app_anr

系统app无响应

system_app_crash

系统app崩溃关闭

data_app_anr

普通app无响应

data_app_crash

普通app崩溃关闭

SYSTEM_TOMBSTONE

系统native进程出现奔溃

SYSTEM_RECOVERY_LOG

系统恢复引发异常重启

SYSTEM_LAST_KMSG

Kernel Panic (内核错误)

APANIC_CONSOLE

Kernel Panic (内核错误)

APANIC_THREADS

Kernel Panic (内核错误)

问题4:DropBoxManager 存储记录数据方式?

DropBoxManager 使用的是文件存储, 所有的记录都存储在 /data/system/dropbox 目录中, 一条记录就是一个文件, 当文本文件的尺寸超过文件系统的最小区块尺寸后, DropBoxManager 还会自动压缩该文件, 通常文件名以调用 DropBoxManager 的 TAG 参数开头。

问题5:如何利用 DropBoxManager ?

(1)错误日志记录:

利用 DropBoxManager 来记录需要持久化存储的错误日志信息

DropBoxManager 提供了 logcat 之外的另外一种错误日志记录机制, 程序可以在出错的时候自动将相关信息记录到 DropBoxManager 中。

(2)错误自动上报

可以将 DropBoxManager 和设备的 BugReport 结合起来, 实现自动上报错误到服务器.

每当生成新的记录,DropBoxManager 就会广播一个 DropBoxManager.ACTION_DROPBOX_ENTRY_ADDED Intent, 设备的 BugReport 服务需要侦听DropBoxManagerService发出这个 Intent, 然后触发错误的自动上报。

Android系统java/native crash和anr异常处理流程以及DroboxManagerService日志记录相关推荐

  1. Android系统调试(02)ANR问题总结

    该系列文章总纲链接:专题分纲目录 Android系统基础 ANR问题是Android系统中比较常见的问题,当出现ANR时一般情况会弹出一个带有以下文字的对话框提示(Android版本不同,展示效果会有 ...

  2. android 中断处理流程,Android P的native crash处理流程

    一.概述 Android系统有监控程序异常退出的机制,这便是本文要讲述得debuggerd守护进程.当发生native crash或者主动调用debuggerd时,会输出进程相关的状态信息到文件或者控 ...

  3. spatialite android,一种基于Android系统的Spatialite空间数据库加密方法与流程

    本发明属于数据库技术领域,具体涉及一种基于Android系统的Spatialite空间数据库加密方法. 背景技术: 随着经济建设及智能终端和移动GIS技术的不断发展,终端的CPU.GPU.内存.显示屏 ...

  4. android 音频播放过程,一种Android系统中的音频播放方法与流程

    本申请涉及android系统技术,特别涉及一种android系统中的音频播放方法. 背景技术: 在android系统中,现有的使用audiotrack进行音频播放时,audiotrack应用与andr ...

  5. android字符串块,一种Android系统字符串提取及合并方法与流程

    本发明涉及字符串提取及合并方法,尤其涉及一种Android系统字符串提取及合并方法. 背景技术: 随着智能通讯终端的日益普及,采用Android系统的智能通讯终端设备越来越走向世界各地,而对于多国语言 ...

  6. Android 系统级APP 升级方案 OTA全流程

    支持原创,请关注专栏: 高质量文章导航 一.Android ota固件编译 OTA 介绍 OTA ( over the air )升级是 Android 系统提供的标准软件升级方式.它功能强大,提供了 ...

  7. 【Bug】一次Android系统应用32位升级到64位的踩坑记录

    项目场景: 二期会议室需要替换成OD20的会议平板,为了方便安装,给了framework的同事一个会议室版本的无线投屏APK,作为系统应用打包进去了. 将无线投屏升级到现在调试的版本,启动后,底部通知 ...

  8. java 打印gc_java – 以编程方式打印启用GC日志记录时通常在JVM出口上打印的堆使用情况...

    MXBeans有什么问题?实施并不那么难. 我用过类似的东西: List gcList = ManagementFactory.getGarbageCollectorMXBeans(); for(Ga ...

  9. java 负数int与long值互转方法 日志记录

    因为代码有问题,获取Long值的时候,变成了xxx.intValue(),导致入库的id错误,后来想到 因为是int溢出,所以溢出值为2^31,2^31^2-溢出后的值. 所以 原来 值 a,intV ...

  10. Android系统问题及日志分析

    这篇文章全是干货,我们一起聊聊安卓系统稳定性问题.部分性能问题.本篇列举了作者在某厂工作中遇到实际问题,大部分只有日志概率性问题,通过日志分析问题. 自己对这半年工作做个笔记,也希望对大家有用.方便你 ...

最新文章

  1. YOLO在升级 | PP-YOLO v2开源致敬YOLOV4携带Tricks又准又快地归来(附论文与源码)...
  2. CTFshow php特性 web104
  3. Hibernate 主键维护策略和hibernate 常见的映射类型
  4. 默认标准错误文件linux,Linux中标准输出和标准错误的重导向
  5. 使用git克隆GitHub仓库时报错解决方案
  6. C# 多线程及同步简介示例
  7. Win32页上的所有控件属性与方法
  8. (1)PCIE接口应用领域(学无止境)
  9. 使用Hyper-V Server PowerShell
  10. C#之SqlDependency数据库缓存
  11. Erlang实战练习(一)
  12. taro Button按钮组件
  13. 狼羽:视频营销是今年营销增长渠道重中之重
  14. Springboot 返回数据提示语 国际化 (AOP实现)
  15. 2019-11-29-win10-uwp-如何开始写-uwp-程序
  16. 灵感 | 设计平平无奇?试试这种方法!
  17. 帝国cms导入html模板,帝国CMS模板组导入导出更换模板
  18. 如何正确理解三极管的放大区、饱和区、截止区
  19. 使用邮件发送自定义报表
  20. 2016年度盘点:一家典型互联网公司的必备工具宝箱

热门文章

  1. Intellij IDEA与Eclipse对比
  2. 和生活一起理解51单片机① 如何入门学习单片机
  3. 还原html默认打开方式,Win7旗舰版64位系统下如何还原文件默认打开方式
  4. python中print打印输出
  5. java阴阳师抽卡算法_阴阳师抽卡小技巧,抽出SSR很轻松
  6. html图片绝对地址相对地址,HTML中background的图片地址是相对地址吗?
  7. php视频怎么转mp4,PHP实现将视频转成MP4并获取视频预览图的方法_php技巧
  8. php 判断中文和英文,PHP如何判断中文还是英文?
  9. 中国大学慕课公开课-《视听语言》-学习笔记-1
  10. html5制作星星闪烁和制作时钟