Android WatchDog

0. 前言

基础知识

技术基础

说明

参考

操作系统

进程的调度,内存分配的知识,对理解程序运行很有帮助

ARM Architecture

针对android操作系统指令运行及问题分析帮助很大

链接,装载与库

计算机程序运行的基本原理,万变不离其宗

<程序员的自我修养>

ELF spec

elf里面提供很多debug信息,掌握可方便使用arm-eabi-readelf来看这些debug信息

coredump机制

coredump是进程出问题的现场,是对debug NE最有效的材料

ptrace机制

ptrace是用一个进程debug另外一个进程的机制,这也是GDB的核心实现机制

Linux的signal机制

NE都会伴随signal的发出

业务逻辑

熟悉业务逻辑能更快的了解上下文以更快的分析场景和根本原因

线程安全

线程安全是一类很大几率引起NE的因素

概述

为防止嵌入式系统MCU里的程序因干扰而跑飞,专门在MCU里设计了一个定时器电路,叫做看门狗。当MCU正常工作的,每隔一段时间会输出一个信号给看门狗,即喂狗。若程序跑飞,MCU在规定的时间内没法喂狗,这时看门狗就会直接触发一个reset信号,让CPU重新启动。

在Android系统的framework中,设计了一个系统服务Watchdog,它类似于一个软件看门狗,用来保护重要的系统服务。源码位于:

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

Android中Watchdog用来看护system_server进程,system_server进程运行着系统最重要的服务,譬如AMS、PKMS、WMS等, 当这些服务不能正常运转时,Watchdog可能会杀掉system_server,触发上层重启。Watchdog的实现利用了锁和消息队列机制。当system_server发生死锁或消息队列一直处于忙碌状态时,则认为系统已没有响应了(System Not Responding)。在分析Watchdog问题时,首先要有详尽的日志,其次要能定位出导致Watchdog超时的直接原因,最重要的是能还原出问题发生的场景。watchdog不是引发重启的原因,其它模块获取锁等相关导致系统卡住,才导致引发watchdog.

dropboxTag 含义

流程图

1. 获取WatchDog 对象

scala 复制代码
public class Watchdog extends Thread { }

想要分析一个功能代码,可先从本身的源头找起,对于Java 类首先看的就是类的定义及构造构造函数

从这里看 WatchDog 其实是一个Thread,这个Thread 可能较特殊而已,至于怎么特殊,下面会在SystemServer 分析时说明。那对于一个Thread,核心的操作部分就是run()了,这个最重要的部分会放在最后解析。

再来看下WatchDog 的构造函数:

csharp 复制代码
private Watchdog() { }

WatchDog 构造函数是private,对于外界获取对象的接口为:

csharp 复制代码
    public static Watchdog getInstance() {
        if (sWatchdog == null) {
            sWatchdog = new Watchdog();
        }
        return sWatchdog;


    }

外界获取WatchDog 就是通过getInstance(),至于这个"外界"后面会补充。

2. WatchDog 的启动

less 复制代码
//frameworks/base/services/java/com/android/server/SystemServer.java
  private void startBootstrapServices(@NonNull TimingsTraceAndSlog t) {
        final Watchdog watchdog = Watchdog.getInstance();
        watchdog.start();
    }

从第一节得知WatchDog 是一个单独的 thread,且以单例的形式存在,则需确定其启动的地方。

这部分主要作用:

  • 创建WatchDog 实例;
  • 运行WatchDog thread;
  • 在AMS 启动后,执行init 接口;

在system_server 进程中会启动WatchDog 线程,专门用来给system_server 中的其他线程喂狗。

3. 构造函数

csharp 复制代码
private Watchdog() {
    //线程名为watchdog
	super("watchdog");
	//将fg thread 单独提出作为主要的checker
	mMonitorChecker = new HandlerChecker(FgThread.getHandler(),
			"foreground thread", DEFAULT_TIMEOUT);
	mHandlerCheckers.add(mMonitorChecker);
    //创建主线程的checker
	mHandlerCheckers.add(new HandlerChecker(new Handler(Looper.getMainLooper()),
			"main thread", DEFAULT_TIMEOUT));
	//创建UI thread 的checker
	mHandlerCheckers.add(new HandlerChecker(UiThread.getHandler(),
			"ui thread", DEFAULT_TIMEOUT));
	//创建Io thread 的checker
	mHandlerCheckers.add(new HandlerChecker(IoThread.getHandler(),
			"i/o thread", DEFAULT_TIMEOUT));
	//创建display thread 的checker
	mHandlerCheckers.add(new HandlerChecker(DisplayThread.getHandler(),
			"display thread", DEFAULT_TIMEOUT));
	//创建animation thread 的checker
	mHandlerCheckers.add(new HandlerChecker(AnimationThread.getHandler(),
			"animation thread", DEFAULT_TIMEOUT));
	//创建surface animation thread 的checker
	mHandlerCheckers.add(new HandlerChecker(SurfaceAnimationThread.getHandler(),
			"surface animation thread", DEFAULT_TIMEOUT));
	//fg thread的checker 中添加对binder 的checker
	addMonitor(new BinderThreadMonitor());
    // 添加对watchdog 相关目录的监控
	mOpenFdMonitor = OpenFdMonitor.create();
	mInterestingJavaPids.add(Process.myPid());
	// See the notes on DEFAULT_TIMEOUT.
	assert DB ||
			DEFAULT_TIMEOUT > ZygoteConnectionConstants.WRAPPED_PID_TIMEOUT_MILLIS;
}

3.1 设定线程名

设WatchDog 的线程名为 watchdog

3.2 创建 HandlerChecker

WatchDog 中靠 HandlerChecker 来完成check工作,每个 HandlerChecker 伴随一个Handler,即一个独立的 Looper 和 Thread。

WatchDog 在创建时指定对 FgThread、MainThread、UIThread、IoThread、DisplayThread、AnimationThread、SurfaceAnimationThread 等thread的监控,当然后期可通过接口动态添加到check list(mHandlerCheckers) 中,例如通过接口 addThread(),详细看第 5.2 节。

MainThread 是在 SystemServer 运行时创建的

csharp 复制代码
frameworks/base/services/java/com/android/server/SystemServer.java
private void run() {
    ...
    Looper.prepareMainLooper();
    ...
}

其他的Thread 最终都是继承自HandlerThread(详细看 Android HandlerThread 详解

HandlerChecker 有3 个参数分别是Handler 对象、name、及触发watchdog 的最大时间间隔,详细的HandlerChecker 看下面第6节。

3.3 添加对binder 的监控

java 复制代码
    private static final class BinderThreadMonitor implements Watchdog.Monitor {
        @Override
        public void monitor() {
            Binder.blockUntilThreadAvailable();
        }
    }

monitor 会调用到native 层 IPCThreadState

scss 复制代码
frameworks/native/libs/binder/IPCThreadState.cpp
void IPCThreadState::blockUntilThreadAvailable()


{
    pthread_mutex_lock(&mProcess->mThreadCountLock);
    while (mProcess->mExecutingThreadsCount >= mProcess->mMaxThreads) {
        ALOGW("Waiting for thread to be free. mExecutingThreadsCount=%lu mMaxThreads=%lu\n",
                static_cast<unsigned long>(mProcess->mExecutingThreadsCount),
                static_cast<unsigned long>(mProcess->mMaxThreads));
        pthread_cond_wait(&mProcess->mThreadCountDecrement, &mProcess->mThreadCountLock);
    }
    pthread_mutex_unlock(&mProcess->mThreadCountLock);


}

BinderThreadMonitor 会被添加到fg thread 中,主要是用于确认binder 是否有出现不够用的情况,例如,假设binder 的max thread 为15个,超过15后就需check 是否存在binder 阻塞。

3.4 创建OpenFdMonitor

java 复制代码
public static OpenFdMonitor create() {
	// Only run the FD monitor on debuggable builds (such as userdebug and eng builds).
	if (!Build.IS_DEBUGGABLE) {
		return null;
	}
	final StructRlimit rlimit;
	try {
		rlimit = android.system.Os.getrlimit(OsConstants.RLIMIT_NOFILE);
	} catch (ErrnoException errno) {
		Slog.w(TAG, "Error thrown from getrlimit(RLIMIT_NOFILE)", errno);
		return null;
	}
	final File fdThreshold = new File("/proc/self/fd/" + (rlimit.rlim_cur - FD_HIGH_WATER_MARK));
	return new OpenFdMonitor(new File("/data/anr"), fdThreshold);


}
  • 只有 debug 版本才做fd monitor,例如 userdebug 或 eng;
  • 主要是确认 /data/anr 路径的正常性;

3.5 mInterestingJavaPids

主要为了AMS dump stack 时使用,通过接口 processStarted 添加interest pid,AMS 中每次start 一个process 时,都会调用handleProcessStartedLocked 最终调用WatchDog.processStarted 添加到WatchDog 中,而process 也是有限定的,如下:

typescript 复制代码
    private static boolean isInterestingJavaProcess(String processName) {
        return processName.equals(StorageManagerService.sMediaStoreAuthorityProcessName)
                || processName.equals("com.android.phone");
    }




    public void processStarted(String processName, int pid) {
        if (isInterestingJavaProcess(processName)) {
            Slog.i(TAG, "Interesting Java process " + processName + " started. Pid " + pid);
            synchronized (this) {
                mInterestingJavaPids.add(pid);
            }
        }
    }

4. init 函数

ini 复制代码
    public void init(Context context, ActivityManagerService activity) {
        mResolver = context.getContentResolver();
        mActivity = activity;
        context.registerReceiver(new RebootRequestReceiver(),
                new IntentFilter(Intent.ACTION_REBOOT),
                android.Manifest.permission.REBOOT, null);
    }

注册了reboot 的广播,软重启的操作时在这里进行的

scala 复制代码
    final class RebootRequestReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context c, Intent intent) {
            if (intent.getIntExtra("nowait", 0) != 0) {
                rebootSystem("Received ACTION_REBOOT broadcast");
                return;
            }
            Slog.w(TAG, "Unsupported ACTION_REBOOT broadcast: " + intent);
        }
    }


 
    void rebootSystem(String reason) {
        Slog.i(TAG, "Rebooting system because: " + reason);
        IPowerManager pms = (IPowerManager)ServiceManager.getService(Context.POWER_SERVICE);
        try {
            pms.reboot(false, reason, false);
        } catch (RemoteException ex) {
        }
    }

5. 其他几个重要函数

5.1 addMonitor()

java 复制代码
    public void addMonitor(Monitor monitor) {
        synchronized (mLock) {
            mMonitorChecker.addMonitorLocked(monitor);
        }
    }

本函数是将 monitor 添加到 mMonitorChecker 中监控;

例如AMS 构造函数中:

kotlin 复制代码
Watchdog.getInstance().addMonitor(this);

5.2 addThread()

arduino 复制代码
    public void addThread(Handler thread) {
        synchronized (mLock) {
            final String name = thread.getLooper().getThread().getName();
            mHandlerCheckers.add(withDefaultTimeout(new HandlerChecker(thread, name)));
        }
    }
    public void addThread(Handler thread, long timeoutMillis) {//通过此接口新建HandlerChecker
        synchronized (mLock) {
            final String name = thread.getLooper().getThread().getName();
            mHandlerCheckers.add(
                    withCustomTimeout(new HandlerChecker(thread, name), timeoutMillis));
        }
    }
    public void addThread(Handler thread) {


        addThread(thread, DEFAULT_TIMEOUT);


    }

通过本函数新建HandlerChecker 并添加到 mHandlerCheckers 中,然后进行 check 该线程是否阻塞,默认timeout 为60s;

例如 AMS 构造函数中:

scss 复制代码
Watchdog.getInstance().addThread(mHandler);

6. HandlerChecker类

在分析WatchDog 核心函数run 之前,先来分析下核心处理类 HandlerChecker。

6.1 HandlerChecker 是一个Runnable

java 复制代码
public final class HandlerChecker implements Runnable {

6.2 HandlerChecker的构造

ini 复制代码
        HandlerChecker(Handler handler, String name) {
            mHandler = handler;
            mName = name;
            mCompleted = true;
        }

构造参数有3个:

  • handler 用来发消息,通过handler 确定thread;
  • name 为HandlerChecker对应thread 的描述时使用;
  • waitMaxMillis 为监控时最大时长,默认为60s;

6.3 核心变量

swift 复制代码
private final ArrayList<Monitor> mMonitors = new ArrayList<Monitor>();
private final ArrayList<Monitor> mMonitorQueue = new ArrayList<Monitor>();

一个HandlerChecker 中可添加多个monitor,为了安全考虑,防止在monitor check 过程中添加新的monitor,将过程分2部分:

  • 每次HandlerChecker 在schedule check时,只检查 mMonitors;
  • schedule check 过程中,新添加的monitor 都临时存放在 mMonitorQueue 中;

详细看scheduleCheckLocked(),即第 6.4 节。

6.4 scheduleCheckLocked

scss 复制代码
        public void scheduleCheckLocked(long handlerCheckerTimeoutMillis) {
            mWaitMax = handlerCheckerTimeoutMillis;
            if (mCompleted) {
                // Safe to update monitors in queue, Handler is not in the middle of work
                mMonitors.addAll(mMonitorQueue);
                mMonitorQueue.clear();
            }
            if ((mMonitors.size() == 0 && mHandler.getLooper().getQueue().isPolling())
                    || (mPauseCount > 0)) {
                // Don't schedule until after resume OR
                // If the target looper has recently been polling, then
                // there is no reason to enqueue our checker on it since that
                // is as good as it not being deadlocked.  This avoid having
                // to do a context switch to check the thread. Note that we
                // only do this if we have no monitors since those would need to
                // be executed at this point.
                mCompleted = true;
                return;
            }
            if (!mCompleted) {
                // we already have a check in flight, so no need
                return;
            }
            mCompleted = false;
            mCurrentMonitor = null;
            mStartTime = SystemClock.uptimeMillis();
            mHandler.postAtFrontOfQueue(this);
        }

逻辑较简单,见代码中注释,将逻辑分为4个部分:

  • step1: 新的 schedule 时 mCompleted 为 true,会将 mMonitorQueue 中的 monitor copy到mMonitor 中,用以在 run()中 check;
  • step2:若 handle 的Looper 处于 polling 状态,但是没有 monitor 添加进来,或者是HandlerChecker 处于pause 状态,不做 schedule check,直接指定 schedule 完成状态;
  • step3:若 HandlerChecker 中有monitor 或者是 Looper 中有消息在处理,会跳过 step2, 若是第一次执行 step3 的 case 是不会处理,若 mCompleted 此时为false,那肯定是schedule已执行过一次,但是30s 时并未执行完成,于是这里直接return,为了再给一次机会,再等 30s 等待 monitor 或者 msg 处理完成;
  • step4:能进入 step4,是因为上一次的 schedule 顺利执行完成,run() 会在顺利执行完成将mCompleted 置为true,不会进入 step3;

6.5 run 函数

ini 复制代码
        public void run() {
            // Once we get here, we ensure that mMonitors does not change even if we call
            // #addMonitorLocked because we first add the new monitors to mMonitorQueue and
            // move them to mMonitors on the next schedule when mCompleted is true, at which
            // point we have completed execution of this method.
            final int size = mMonitors.size();
            for (int i = 0 ; i < size ; i++) {
                synchronized (mLock) {
                    mCurrentMonitor = mMonitors.get(i);
                }
                mCurrentMonitor.monitor();
            }
            synchronized (mLock) {
                mCompleted = true;
                mCurrentMonitor = null;
            }
        }

通过一个循环分别调用每一个 monitor 的 monitor(),若其中一个阻塞了,则无法退出该循环,mCompleted 状态也不会变成 true,若 mCompleted 为false,WatchDog 只能通过时间来进一步确认状态,在下面一节中会详细说明。

用 AMS 中的 monitor 来举例:

csharp 复制代码
frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java    public void monitor() {
/** In this method we try to acquire our lock to make sure that we have not deadlocked */
public void monitor() {
    synchronized (this) { }
}

AMS 的 monitor 就是为了 check AMS 是否死锁了。

7. WatchDog 的run()

上面的基本函数都大概解析完了,对于一个Thread 那最重要的肯定还是run(),这里也是WatchDog 的监测机制所在,因代码较多,这里裁剪分析。

step 1:建立死循环

WatchDog 在整个系统运行过程中都需存在的,除非进程或是系统重启了,不然WatchDog 需长期运行。

typescript 复制代码
public void run() {
    boolean waitedHalf = false;
    while (true) {
        ...
    }
}

step2:创建schedule

ini 复制代码
synchronized (mLock) {
    long sfHangTime;


    long timeout = checkIntervalMillis;
    for (int i=0; i<mHandlerCheckers.size(); i++) {
        HandlerCheckerAndTimeout hc = mHandlerCheckers.get(i);
        hc.checker().scheduleCheckLocked(hc.customTimeoutMillis()
                .orElse(watchdogTimeoutMillis * Build.HW_TIMEOUT_MULTIPLIER));
    }

注意2点:

  • timeout 默认为30s,所有的schedule 启动完会wait 30s 等待所有的schedule 都能顺利执行完;
  • 启动schedule 会加上锁,与HandlerChecker 的run 函数互斥,这就要求,在wait 之前,HandlerChecker 的runnable 是无法运行的,wait时会释放this 对象的锁,runnable 才可顺利执行(wait/synchronized 使用);

step3:收集check 完成状态

ini 复制代码
final int waitState = evaluateCheckerCompletionLocked();
if (waitState == COMPLETED) {
    // The monitors have returned; reset
    waitedHalf = false;
    continue;
} else if (waitState == WAITING) {
    // still waiting but within their configured intervals; back off and recheck
    continue;
} else if (waitState == WAITED_HALF) {
    if (!waitedHalf) {
        Slog.i(TAG, "WAITED_HALF");
        waitedHalf = true;
        // We've waited half, but we'd need to do the stack trace dump w/o the lock.
        blockedCheckers = getCheckersWithStateLocked(WAITED_HALF);
        subject = describeCheckersLocked(blockedCheckers);
        pids = new ArrayList<>(mInterestingJavaPids);
        doWaitedHalfDump = true;
    } else {
        continue;
    }
}

状态分:

  • COMPLETED:顺利完成
  • WAITING:有可能HandlerChecker中的monitor 较多,而部分monitor 占用时间较长,这就导致有些monitor 执行不到30s时,wait 就结束了,这时候会返回WAITING 状态,继续等待下一个schedule;
  • WATIED_HALF:同WAITING,会继续等待下一个schedule,不同于WAITING,WAITED_HALF 会通过AMS dump 一次stack;
  • OVERDUE:有monitor 执行超过了60s,这个是不允许的,step4 会处理OVERDUE的情况

step4:处理OVERDUE 情况

ini 复制代码
blockedCheckers = getCheckersWithStateLocked(OVERDUE);
subject = describeCheckersLocked(blockedCheckers);

最开始是统计出OVERDUE 的HandlerChecker,存入blockedCheckers 中,并统计这些HandlerChecker 的描述。

ini 复制代码
    private ArrayList<HandlerChecker> getCheckersWithStateLocked(int completionState) {
        ArrayList<HandlerChecker> checkers = new ArrayList<HandlerChecker>();
        for (int i=0; i<mHandlerCheckers.size(); i++) {
            HandlerChecker hc = mHandlerCheckers.get(i).checker();
            if (hc.getCompletionStateLocked() == completionState) {
                checkers.add(hc);
            }
        }
        return checkers;
    }

统计所有状态OVERDUE 的HandlerChecker。

scss 复制代码
    private String describeCheckersLocked(List<HandlerChecker> checkers) {
        StringBuilder builder = new StringBuilder(128);
        for (int i=0; i<checkers.size(); i++) {
            if (builder.length() > 0) {
                builder.append(", ");
            }
            builder.append(checkers.get(i).describeBlockedStateLocked());
        }
        return builder.toString();
    }

统计所有符合条件的OVERDUE 的HandlerChecker 的描述,以逗号分割。

例如:

vbscript 复制代码
11-06 00:05:28.603  2197  2441 W Watchdog: *** WATCHDOG KILLING SYSTEM PROCESS: Blocked in monitor com.android.server.am.ActivityManagerService on foreground thread (android.fg), Blocked in handler on ActivityManager (ActivityManager)

接着就是保存日志,包括一些运行时的堆栈信息,这些日志是解决Watchdog问题的重要依据。若判断需杀掉system_server进程,则给当前进程(system_server)发送signal 9,详细信息看 Android 系统中WatchDog 日志分析

8. 通过AMS引入WatchDog 监控的根本形式

scss 复制代码
    Watchdog.getInstance().addMonitor(this);
    Watchdog.getInstance().addThread(mHandler);

来看下一直说的monitor 是什么东西:

csharp 复制代码
    public void monitor() {
        synchronized (this) { }
    }

这里其实就是确认AMS 中是否存在死锁,30秒后还是没有放出来这个锁,WatchDog 会在给一次机会,若还是没有释放的话,会打印堆栈信息并且结束AMS 所在进程。

需注意的是WatchDog 的监控,除了WatchDog 构造函数中的默认Thread,外界若需添加监控,需通过2种途径:

  • addMonitor(Monitor monitor)
  • addThread(Handler thread)

2个接口区别在于参数,一个传入的是monitor,例如WatchDog 内部的BinderThreadMonitor 或AMS 的monitor;另一个传入的是Handler,通过第 5 节得知,addThread 会创建一个HandlerChecker 单独监控;

那么,结合 HandlerChecker 的 scheduleCheckLocked(),可认定,WatchDog 可监控的形式有2种:

  • monitor:HandlerChecker专门用来维护monitor,run() 中逐个确认monitor是否正常运行
  • handler - msg:HandlerChecker 专门用来维护消息的监控,run() 中不会确认 monitor 的状态,而是直接将 mCompleted 置为 true,但是前提是Handler 中的 Looper 此时若正在处理消息,这个消息不能阻塞此次 schedule 的 post,若 Looper 中的消息处理超过 30s,那么HandlerChecker 中的 run() 只能等下一次 30s 时候才能有机会执行;

9. WatchDog 中monitor 监控

用来检查Monitor对象可能发生的死锁,AMS、WMS等核心系统服务都是Monitor对象。

能被Watchdog监控的系统服务都实现了Watchdog.Monitor接口,并实现其中的monitor()。运行在android.fg线程, 系统中实现该接口类主要有:

  • ActivityManagerService
  • InputManagerService
  • MediaProjectionManagerService
  • MediaRouterService
  • MediaSessionService
  • PowerManagerService
  • StorageManagerService
  • TvRemoteService
  • WindowManagerService

都是通过addMonitor() 添加到 fg thread 的 HandlerChecker 中。

10. WatchDog 中Handler msg监控

Watchdog触发主要过程:

都是通过 Handler 的方式添加到各自的 HandlerChecker 中,有的是 WatchDog 在构造时添加,有的是通过addThread 方式添加。

检测Systsem_server关键工作线程handler消息处理是否被block

Watchdog监控的线程有:

(默认地DEFAULT_TIMEOUT=60s,调试时才为10s方便找出潜在的ANR问题)

Monitor检测与Handler检测的区别是:Monitor检测需执行monitor()来获取锁,获取不到就一直block直至超时,可能是死锁或锁一直被其他占用;而Handler检测是只要执行了run(),说明核心服务的Handler是正常工作的,没被其他消息堵塞,若mCompleted = false,说明该runnable没被执行,可能是Handler内部有一直执行的消息导致了阻塞;

输出信息

watchdog在check过程中出现阻塞1min的情况,则会输出:

  1. AMS.dumpStackTraces:输出system_server和3个native进程的traces
  2. 该方法会输出2次,第一次在超时30s的地方;第二次在超时1min;
  3. WD.dumpKernelStackTraces,输出system_server进程中所有线程的kernel stack;
  4. 节点/proc/%d/task获取进程内所有的线程列表
  5. 节点/proc/%d/stack获取kernel的栈
  6. doSysRq, 触发kernel来dump所有阻塞线程,输出所有CPU的backtrace到kernel log;
  7. 节点/proc/sysrq-trigger
  8. dropBox,输出文件到/data/system/dropbox,内容是trace + blocked信息
  9. 杀掉system_server,进而触发zygote进程自杀,从而重启上层framewor

Debug材料及工具

分析材料

tombstone文件

位置:data/tombstones/中,最多存10个,超过会从最旧的复写掉

tombstone信息:

Symbol文件

GCC编译加-g参数编译器会在目标文件中加上调试信息符号的行号信息等,android项目上在out/target/product/xxxx/symbol下对应so的目录下:

使用strip命令去除debug等无用信息,瘦身作用.

多出来的section即是debug信息部分,包含符号所在行数.

看出symbol文件中有较多的debug section,掌握这些section的作用在分析问题时便可获得更多的debug信息.

coredump

文件作用:

当进程意外终止时,系统可将该进程的地址空间的内容及终止时的一些其他信息转储到coredump中,也是elf文件

可使用GDB对进程异常时的现场进行调试查找问题.

抓取coredump的方法需提前设置

coredump中提取oat文件的方法:www.cnblogs.com/YYPapa/p/68...

分析工具

addr2line

功能:用来分析单个pc地址对应的源码行数

使用方法:

sql 复制代码
chengang@mi:~$ addr2line -h
Usage: addr2line [option(s)] [addr(s)]
 Convert addresses into line number/file name pairs.
 If no addresses are specified on the command line, they will be read from stdin
 The options are:
  @<file>                Read options from <file>
  -a --addresses         Show addresses
  -b --target=<bfdname>  Set the binary file format
  -e --exe=<executable>  Set the input file name (default is a.out)
  -i --inlines           Unwind inlined functions
  -j --section=<name>    Read section-relative offsets instead of addresses
  -p --pretty-print      Make the output easier to read for humans
  -s --basenames         Strip directory names
  -f --functions         Show function names
  -C --demangle[=style]  Demangle function names
  -h --help              Display this information
  -v --version           Display the program's version
 
addr2line: supported targets: elf64-x86-64 elf32-i386 elf32-iamcu elf32-x86-64 a.out-i386-linux pei-i386 pei-x86-64 elf64-l1om elf64-k1om elf64-little elf64-big elf32-little elf32-big pe-x86-64 pe-bigobj-x86-64 pe-i386 plugin srec symbolsrec verilog tekhex binary ihex
Report bugs to <http://www.sourceware.org/bugzilla/>

注意若有inline的函数,添加-i参数

ndk-stack

使用方法:

ruby 复制代码
chengang@mi:~/Android/Sdk/ndk/20.1.5948944$ ./ndk-stack -h
usage: ndk-stack.py [-h] -sym SYMBOL_DIR [-i INPUT]
 
Symbolizes Android crashes.
 
optional arguments:
  -h, --help            show this help message and exit
  -sym SYMBOL_DIR, --sym SYMBOL_DIR
                        directory containing unstripped .so files
  -i INPUT, -dump INPUT, --dump INPUT
                        input filename
 
See <https://developer.android.com/ndk/guides/ndk-stack>.

使用举例:

c++filt

对于被编译器转换过的函数名,可通过c++filt工具查看原始函数

函数签名:所有的符号都以"_Z"开头,对于嵌套的名字(在命名空间或类里面的),后面紧跟"N",然后是各个名称空间和类的名字,每个名字前是名字字符串长度,再以E结尾,对于一个函数来说,他的参数列表紧跟在"E"后面,对于int即"i",void即"v"

使用方法:

sql 复制代码
chengang@mi:~$ c++filt -h
Usage: c++filt [options] [mangled names]
Options are:
  [-_|--strip-underscore]     Ignore first leading underscore
  [-n|--no-strip-underscore]  Do not ignore a leading underscore (default)
  [-p|--no-params]            Do not display function arguments
  [-i|--no-verbose]           Do not show implementation details (if any)
  [-t|--types]                Also attempt to demangle type encodings
  [-s|--format {none,auto,gnu,lucid,arm,hp,edg,gnu-v3,java,gnat,dlang}]
  [@<file>]                   Read extra options from <file>
  [-h|--help]                 Display this information
  [-v|--version]              Show the version information
Demangled names are displayed to stdout.
If a name cannot be demangled it is just echoed to stdout.
If no names are provided on the command line, stdin is read.
Report bugs to <http://www.sourceware.org/bugzilla/>.

使用举例:

tombstone中的frame,方法名优化过:

还原后:

Objdump

功能:用来把相应的so变成汇编语言的asm文件

使用方法:

sql 复制代码
chengang@mi:~/miui/miui_code/g7b_q_dev_11_20/prebuilts$ ./gcc/linux-x86/x86/x86_64-linux-android-4.9/x86_64-linux-android/bin/objdump --help
Usage: ./gcc/linux-x86/x86/x86_64-linux-android-4.9/x86_64-linux-android/bin/objdump <option(s)> <file(s)>
 Display information from object <file(s)>.
 At least one of the following switches must be given:
  -a, --archive-headers    Display archive header information
  -f, --file-headers       Display the contents of the overall file header
  -p, --private-headers    Display object format specific file header contents
  -P, --private=OPT,OPT... Display object format specific contents
  -h, --[section-]headers  Display the contents of the section headers
  -x, --all-headers        Display the contents of all headers
  -d, --disassemble        Display assembler contents of executable sections
  -D, --disassemble-all    Display assembler contents of all sections
  -S, --source             Intermix source code with disassembly
  -s, --full-contents      Display the full contents of all sections requested
  -g, --debugging          Display debug information in object file
  -e, --debugging-tags     Display debug information using ctags style
 ......

IDA工具

blog.csdn.net/u012195899/...

Debuggerd

功能:查看目标进程的所有线程的当前调用栈

使用方法:

ruby 复制代码
phoenix:/ # debuggerd -h                                                                                                                       
usage: debuggerd [-bj] PID
 
-b, --backtrace    just a backtrace rather than a full tombstone
-j                 collect java traces
1|phoenix:/ #
 
 
chengang@mi:~/Documents/gdb_file$ adb shell ps -ef | grep camera
cameraserver  1042     1 0 14:51:52 ?     00:00:30 cameraserver
u0_a63       16781   661 0 15:56:27 ?     00:00:14 com.android.camera
cameraserver 24563     1 0 20:29:35 ?     00:00:01 [email protected]_64
chengang@mi:~/Documents/gdb_file$ adb shell debuggerd -b 24563 > camera.trace.txt

Oatdump

功能:解析oat文件

工具位置:

使用方法:

GDB

功能:暂停程序以调试程序

使用方法:

sql 复制代码
chengang@mi:~/miui/miui_code/g7b_q_dev_11_20$ gdbclient.py --help
usage: gdbclient.py [-h] [--adb ADB_PATH] [-a | -d | -e | -s SERIAL]
                    (-p PID | -n NAME | -r ...) [--port [PORT]]
                    [--user [USER]] [--setup-forwarding {gdb,vscode}]
                    [--env VAR=VALUE]
 
optional arguments:
  -h, --help            show this help message and exit
  --adb ADB_PATH        use specific adb command
  --port [PORT]         override the port used on the host [default: 5039]
  --user [USER]         user to run commands as on the device [default: root]
  --setup-forwarding {gdb,vscode}
                        Setup the gdbserver and port forwarding. Prints
                        commands or .vscode/launch.json configuration needed
                        to connect the debugging client to the server.
  --env VAR=VALUE       set environment variable when running a binary
 
device selection:
  -a                    directs commands to all interfaces
  -d                    directs commands to the only connected USB device
  -e                    directs commands to the only connected emulator
  -s SERIAL             directs commands to device/emulator with the given
                        serial
 
attach target:
  -p PID                attach to a process with specified PID
  -n NAME               attach to a process with specified name
  -r ...                run a binary on the device, with args

常用的功能有:

1.设置断点(条件断点),打印调用栈

2.查看各线程等信息

线程调用栈,切换到调用栈的第n层,附近代码,汇编代码查看,寄存器查看,变量查看(值或类型),查看内存信息,查看变量偏移

3.单步调试

改变变量值

4.对coredump进行分析调试

case分析

case1

mainlog

yaml 复制代码
00:05:28.603  2197  2441 W Watchdog: *** WATCHDOG KILLING SYSTEM PROCESS: Blocked in monitor com.android.server.am.ActivityManagerService on foreground thread (android.fg), Blocked in handler on ActivityManager (ActivityManager)
00:05:28.603  2197  2441 W Watchdog: foreground thread stack trace:
00:05:28.603  2197  2441 W Watchdog:     at com.android.server.am.ActivityManagerService.monitor(ActivityManagerService.java:23862)
00:05:28.604  2197  2441 W Watchdog:     at com.android.server.Watchdog$HandlerChecker.run(Watchdog.java:211)
00:05:28.604  2197  2441 W Watchdog:     at android.os.Handler.handleCallback(Handler.java:790)
00:05:28.604  2197  2441 W Watchdog:     at android.os.Handler.dispatchMessage(Handler.java:99)
00:05:28.604  2197  2441 W Watchdog:     at android.os.Looper.loop(Looper.java:164)
00:05:28.604  2197  2441 W Watchdog:     at android.os.HandlerThread.run(HandlerThread.java:65)
00:05:28.604  2197  2441 W Watchdog:     at com.android.server.ServiceThread.run(ServiceThread.java:46)
00:05:28.604  2197  2441 W Watchdog: ActivityManager stack trace:
00:05:28.604  2197  2441 W Watchdog:     at com.android.server.am.ActivityManagerService.idleUids(ActivityManagerService.java:23305)
00:05:28.604  2197  2441 W Watchdog:     at com.android.server.am.ActivityManagerService$MainHandler.handleMessage(ActivityManagerService.java:2428)
00:05:28.604  2197  2441 W Watchdog:     at android.os.Handler.dispatchMessage(Handler.java:106)
00:05:28.604  2197  2441 W Watchdog:     at android.os.Looper.loop(Looper.java:164)
00:05:28.604  2197  2441 W Watchdog:     at android.os.HandlerThread.run(HandlerThread.java:65)
00:05:28.604  2197  2441 W Watchdog:     at com.android.server.ServiceThread.run(ServiceThread.java:46)
00:05:28.604  2197  2441 W Watchdog: *** GOODBYE!
00:05:28.605  2197  2441 I Process : Sending signal. PID: 2197 SIG: 9

从log 中看到:

  • monitor是在 fg thread 中,也就是 WatchDog 中的 mMonitorChecker 中的 monitor() 触发了
  • 除了 monitor(),AMS 中的 Handler 也触发了WatchDog
  • 最后会把出现问题的进程kill 掉,这里是 pid 2197

traces

先来看下 /data/anr/ 的 watchdog 文件,根据上面 logcat 可以知道最后的pid 是2197,那么就要找结尾是 2197 字符串的traces文件,也就是 traces_SystemServer_WDT06_Nov_00_05_28.143_pid2197

搜"android.fg"

ini 复制代码
"android.fg" prio=5 tid=17 Blocked
  | group="main" sCount=1 dsCount=0 flags=1 obj=0x13380dc8 self=0x9e3b3200
  | sysTid=2225 nice=0 cgrp=default sched=0/0 handle=0x9159d970
  | state=S schedstat=( 0 0 0 ) utm=5 stm=2 core=2 HZ=100
  | stack=0x9149b000-0x9149d000 stackSize=1038KB
  | held mutexes=
  at com.android.server.am.ActivityManagerService.monitor(ActivityManagerService.java:23862)
  - waiting to lock <0x0316e620> (a com.android.server.am.ActivityManagerService) held by thread 81
  at com.android.server.Watchdog$HandlerChecker.run(Watchdog.java:211)
  at android.os.Handler.handleCallback(Handler.java:790)
  at android.os.Handler.dispatchMessage(Handler.java:99)
  at android.os.Looper.loop(Looper.java:164)
  at android.os.HandlerThread.run(HandlerThread.java:65)
  at com.android.server.ServiceThread.run(ServiceThread.java:46)

case2

Watchdog出现的日志很明显,logcat中的event, system中都会有体现,要定位问题,可从检索日志中的watchdog关键字开始。

发生Watchdog检测超时这么重要的系统事件,Android会打印一个EventLog:

yaml 复制代码
watchdog: Blocked in handler XXX    # HandlerChecker超时了
watchdog: Blocked in monitor XXX    # MonitorChecker超时了

接下来,需进一步定位在watchdog出现前,system_server进程在干啥,处于一个什么状态。 这与排除ANR问题差不多,需进程的traces信息、当前系统的CPU运行信息、IO信息。

找到Watchddog出现前的traces.txt文件,这个时间差最好不要太大,因为Watchdog默认的超时时间是1min,太久前的traces并不能说明问题。 诱导Watchdong出现的直接原因其实就是system_server中某个线程被阻塞了,这个信息在event和system的log中清晰可见。 以一个systemLog为例:

yaml 复制代码
W Watchdog: *** WATCHDOG KILLING SYSTEM PROCESS: Blocked in monitor com.android.server.wm.WindowManagerService on foreground thread (android.fg)

Watchdog告诉Monitor Checker超时了,具体在哪呢? 名为android.fg的线程在WindowManagerService的monitor()被阻塞了。这里隐含了2层意思:

  • WindowManagerService实现了Watchdog.Monitor这个接口,并将自己作为Monitor Checker的对象加入到了Watchdog的监测集中
  • monitor()是运行在android.fg线程中的。Android将android.fg设计为一个全局共享的线程,意味着它的消息队列可被其他线程共享, Watchdog的Monitor Checker就是使用的android.fg线程的消息队列。因此,出现Monitor Checker的超时,肯定是android.fg线程阻塞在monitor()上。

打开system_server进程的traces,检索 android.fg 可快速定位到该线程的函数调用栈:

ini 复制代码
"android.fg" prio=5 tid=25 Blocked
  | group="main" sCount=1 dsCount=0 obj=0x12eef900 self=0x7f7a8b1000
  | sysTid=973 nice=0 cgrp=default sched=0/0 handle=0x7f644e9000
  | state=S schedstat=( 3181688530 2206454929 8991 ) utm=251 stm=67 core=1 HZ=100
  | stack=0x7f643e7000-0x7f643e9000 stackSize=1036KB
  | held mutexes=
  at com.android.server.wm.WindowManagerService.monitor(WindowManagerService.java:13125)
  - waiting to lock <0x126dccb8> (a java.util.HashMap) held by thread 91
  at com.android.server.Watchdog$HandlerChecker.run(Watchdog.java:204)
  at android.os.Handler.handleCallback(Handler.java:815)
  at android.os.Handler.dispatchMessage(Handler.java:104)
  at android.os.Looper.loop(Looper.java:194)
  at android.os.HandlerThread.run(HandlerThread.java:61)
  at com.android.server.ServiceThread.run(ServiceThread.java:46)

android.fg线程调用栈告诉几个关键的信息:

  • 这个线程当前的状态是Blocked,阻塞

  • 由Watchdog发起调用monitor(),这是一个Watchdog检查,阻塞已超时

  • waiting to lock <0x126dccb8>: 阻塞的原因是monitor()中在等锁<0x126dccb8>

  • held by thread 91: 这个锁被编号为91的线程持有,需进一步观察91号线程的状态。

    题外话:每一个进程都会对自己所辖的线程编号,从1开始。1号线程通常就是所说的主线程。 线程在Linux系统中还有一个全局的编号,由sysTid表示。在logcat等日志中看到的一般是线程的全局编号。 譬如,本例中android.fg线程在system_server进程中的编号是25,系统全局编号是973。

可在traces.txt文件中检索 tid=91 来快速找到91号线程的函数调用栈信息:

php 复制代码
"Binder_C" prio=5 tid=91 Native
  | group="main" sCount=1 dsCount=0 obj=0x12e540a0 self=0x7f63289000
  | sysTid=1736 nice=0 cgrp=default sched=0/0 handle=0x7f6127c000
  | state=S schedstat=( 96931835222 49673449591 260122 ) utm=7046 stm=2647 core=2 HZ=100
  | stack=0x7f5ffbc000-0x7f5ffbe000 stackSize=1008KB
  | held mutexes=
  at libcore.io.Posix.writeBytes(Native method)
  at libcore.io.Posix.write(Posix.java:258)
  at libcore.io.BlockGuardOs.write(BlockGuardOs.java:313)
  at libcore.io.IoBridge.write(IoBridge.java:537)
  at java.io.FileOutputStream.write(FileOutputStream.java:186)
  at com.android.internal.util.FastPrintWriter.flushBytesLocked(FastPrintWriter.java:334)
  at com.android.internal.util.FastPrintWriter.flushLocked(FastPrintWriter.java:355)
  at com.android.internal.util.FastPrintWriter.appendLocked(FastPrintWriter.java:303)
  at com.android.internal.util.FastPrintWriter.print(FastPrintWriter.java:466)
  - locked <@addr=0x134c4910> (a com.android.internal.util.FastPrintWriter$DummyWriter)
  at com.android.server.wm.WindowState.dump(WindowState.java:1510)
  at com.android.server.wm.WindowManagerService.dumpWindowsNoHeaderLocked(WindowManagerService.java:12279)
  at com.android.server.wm.WindowManagerService.dumpWindowsLocked(WindowManagerService.java:12266)
  at com.android.server.wm.WindowManagerService.dump(WindowManagerService.java:12654)
  - locked <0x126dccb8> (a java.util.HashMap)
  at android.os.Binder.dump(Binder.java:324)
  at android.os.Binder.onTransact(Binder.java:290)

91号线程的名字是Binder_C,它的函数调用栈告诉几个关键信息:

  • Native,表示线程处于运行状态(RUNNING),并且正在执行JNI方法

  • 在WindowManagerService.dump()申请了锁<0x126dccb8>,这个锁正是android.fg线程所等待的

  • FileOutputStream.write()表示Binder_C线程在执行IO写操作,正式因为这个写操作一直在阻塞,导致线程持有的锁不能释放

    题外话:关于Binder线程。当Android进程启动时,就会创建一个线程池,专门处理Binder事务。线程池中会根据当前的binder线程计数器的值来构造新创建的binder线程, 线程名"Binder_%X",X是十六进制。当然,线程池的线程数也有上限,默认情况下为16,所以,可看到 Binder_1 ~ Binder_F 这样的线程命名。

聪明的你看到这或许已能想到解决办法了,在这个IO写操作上加一个超时机制,并且这个超时小于Watchdog的超时,不就可让线程释放它所占有的锁了吗? 是的,这确实可作为一个临时解决方案(Workaround),或者说一个保护机制。但可再往深处想一想,这个IO写操作为什么会阻塞:

  • 是不是IO缓冲区满了,导致写阻塞呢?
  • 是不是写操作有什么锁,导致这个write方法在等锁呢?
  • 是不是当前系统的IO负载过于高,导致写操作效率很低呢?

这都需再进一步从日志中去找原因。若已有的日志不全,找不到论据,还需设计场景来验证假设,解决问题的难度陡然上升。

通过event或system类型的logcat日志,检索Watchdog出现的关键信息;通过traces,分析出导致Watchdog检查超时的直接原因;通过其他日志,还原出问题出现的场景。

case3

5702179-binder通信卡对端

应该是卡在获取获取锁:gCtls->tetherCtrl.lock上。

scss 复制代码
binder::Status NetdNativeService::tetherGetStats(
        std::vector<TetherStatsParcel>* tetherStatsParcelVec) {
    NETD_LOCKING_RPC(gCtls->tetherCtrl.lock, PERM_NETWORK_STACK, PERM_MAINLINE_NETWORK_STACK);
    const auto& statsList = gCtls->tetherCtrl.getTetherStats();
    if (!isOk(statsList)) {
        return asBinderStatus(statsList);
    }
    setTetherStatsParcelVecByInterface(tetherStatsParcelVec, statsList.value());
    auto statsResults = tetherStatsParcelVecToStringVec(tetherStatsParcelVec);
    return binder::Status::ok();
}

support-qualcomm.force.com/s/case/5004...

//From the ANR log, netd thread is stuck in stopTethering,

"Binder:881_6" sysTid=22000

#00 pc 00000000000c6714 /apex/com.android.runtime/lib64/bionic/libc.so (wait4+4) (BuildId: e02ca84f32aa7fdeeddddba113c245f8)

#01 pc 000000000009970c /system/bin/netd (android::net::TetherController::stopTethering()+104) (BuildId: a69e9a4f42b380fac1028d0ef0cf295b)

#02 pc 000000000003d070 /system/bin/netd (android::net::NetdNativeService::tetherStop()+164) (BuildId: a69e9a4f42b380fac1028d0ef0cf295b)

//From the logcat log, stopTethering takes 20s to stop, because dnsmasq bind IPv6 timeout,

11-24 18:32:21.833 8868 8886 I TetheringManager: stopTethering caller:com.heytap.appplatform

11-24 18:32:41.370 23678 23678 E dnsmasq : failed to bind listening socket for 2408:84f3:d824:6588::df: Cannot assign requested address

11-24 18:32:41.370 23678 23678 E dnsmasq : FAILED to start up

//This is AOSP issue, Please refer to below,

There is an Android gerrit for discussing this dnsmasq wrong state & die issue.

android-review.googlesource.com/c/platform/...

If still have further issue with this gerrit, you may need to raise case to Google for discussion, since dnsmasq is not maintained by Qcom but Google.

总结

Watchdog是一个运行在system_server进程的名为"watchdog"的线程:

  • Watchdog运作过程,当阻塞时间超过1min则触发一次watchdog,会杀死system_server,触发上层重启;
  • mHandlerCheckers记录所有的HandlerChecker对象的列表,包括foreground, main, ui, i/o, display线程的handler;
  • mHandlerChecker.mMonitors记录所有Watchdog目前正在监控Monitor,所有的monitors都运行在foreground线程。

有2种方式加入Watchdog监控:

  • addThread():用于监测Handler线程,默认超时时长为60s.这种超时往往是所对应的handler线程消息处理得慢;
  • addMonitor(): 用于监控实现了Watchdog.Monitor接口的服务.这种超时可能是"android.fg"线程消息处理得慢,也可能是monitor迟迟拿不到锁;

以下情况,即使触发了Watchdog,也不会杀掉system_server进程:

  • monkey: 设置IActivityController,拦截systemNotResponding事件, 比如monkey.
  • hang: 执行am hang命令,不重启;
  • debugger: 连接debugger的情况, 不重启;
  • 对于Looper Checker而言,会判断线程的消息队列是否处于空闲状态。 若被监测的消息队列一直闲不下来,则说明可能已阻塞等待了很长时间
  • 对于Monitor Checker而言,会调用实现类的monitor方法,譬如上文中提到的AMS.monitor(), 方法实现一般很简单,就是获取当前类的对象锁,
  • 若当前对象锁已被持有,则monitor()会一直处于wait状态,直到超时,这种情况下,很可能是线程发生了死锁
相关推荐
Rabbb38 分钟前
C# JSON属性排序、比较 Newtonsoft.Json
后端
蓝易云40 分钟前
在Linux、CentOS7中设置shell脚本开机自启动服务
前端·后端·centos
一千柯橘1 小时前
Nestjs 解决 request entity too large
javascript·后端
userkang1 小时前
消失的前后端,崛起的智能体
前端·人工智能·后端·ai·硬件工程
慧一居士2 小时前
Kafka HA集群配置搭建与SpringBoot使用示例总结
spring boot·后端·kafka
@_猿来如此2 小时前
Django 实现电影推荐系统:从搭建到功能完善(附源码)
数据库·后端·python·django
言之。2 小时前
【Go语言】ORM(对象关系映射)库
开发语言·后端·golang
极客智谷3 小时前
深入理解Java线程池:从原理到实战的完整指南
java·后端
我的耳机没电了3 小时前
mySpace项目遇到的问题
后端
陈随易3 小时前
长跑8年,Node.js框架Koa v3.0终发布
前端·后端·程序员