这种方案才是真正的监控ANR,matrix、xCrash都在使用这种方案,已经在国民应用微信等app上检验过,稳定性和可靠性都能得到保证。
在明白SIGQUIT信号监控之前,要理解一下系统的SignalCatcher线程的职责
1、SignalCatcher的初始化
在初始化中,通过CHECK_PTHREAD_CALL
的pthread_create
创建一个SignalCatcher线程
SignalCatcher::SignalCatcher(const std::string& stack_trace_file)
: stack_trace_file_(stack_trace_file),
lock_("SignalCatcher lock"),
cond_("SignalCatcher::cond_", lock_),
thread_(nullptr) {
SetHaltFlag(false);
// Create a raw pthread; its start routine will attach to the runtime.
CHECK_PTHREAD_CALL(pthread_create, (&pthread_, nullptr, &Run, this), "signal catcher thread");
Thread* self = Thread::Current();
MutexLock mu(self, lock_);
while (thread_ == nullptr) {
cond_.Wait(self);
}
}
2、SignalCatcher线程的执行
在C++层同样的,创建线程之后,就会执行线程的Run方法
void* SignalCatcher::Run(void* arg) {
......
// Set up mask with signals we want to handle.
SignalSet signals;
signals.Add(SIGQUIT);
signals.Add(SIGUSR1);
while (true) {
// 1、监控信号
int signal_number = signal_catcher->WaitForSignal(self, signals);
if (signal_catcher->ShouldHalt()) {
runtime->DetachCurrentThread();
return nullptr;
}
switch (signal_number) {
case SIGQUIT:
signal_catcher->HandleSigQuit();
break;
case SIGUSR1:
signal_catcher->HandleSigUsr1();
break;
default:
LOG(ERROR) << "Unexpected signal %d" << signal_number;
break;
}
}
}
int SignalCatcher::WaitForSignal(Thread* self, SignalSet& signals) {
ScopedThreadStateChange tsc(self, kWaitingInMainSignalCatcherLoop);
2、 阻塞等待线程的信号
int signal_number = signals.Wait();
if (!ShouldHalt()) {
LOG(INFO) << *self << ": reacting to signal " << signal_number;
Runtime::Current()->DumpLockHolders(LOG(INFO));
}
return signal_number;
}
在Run方法中可以看到线程监控SIGQUIT
和SIGUSR1
,并且通过signal_catcher->WaitForSignal(self, signals)
阻塞等待线程的信号。如果遇到SIGQUIT信号后,则通过signal_catcher->HandleSigQuit()
处理当前的信号,并dump出当前的stacktraces
信号的处理在Linux端是比较常见的方式,在这里只要利用Linux特性监听到信号就能满足信号的监控
1、方案1 (sigwait)
模仿SignalCatcher线程使用的sigwait
方法进行同步、阻塞地监听,跟SignalCatcher
线程做一样的事情,创建一个子线程,通过一个死循环sigwait
,一直监听SIGQUIT
#include <jni.h>
#include <string>
#include <pthread.h>
#include <syslog.h>
#include <android/log.h>
#include <dirent.h>
#include <unistd.h>
#define TAG "Hensen"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,TAG ,__VA_ARGS__)
static void *mySigQuitCatcher(void* args) {
while (true) {
int sig;
sigset_t sigSet;
sigemptyset(&sigSet);
sigaddset(&sigSet, SIGQUIT);
sigwait(&sigSet, &sig);
if (sig == SIGQUIT) {
// 2、ANR信号监听
}
}
}
// 1、Android端调用此JNI
extern "C" JNIEXPORT void JNICALL
Java_com_example_syncbarriermonitor_NativeLib_monitor(
JNIEnv *env,
jobject /* this */) {
LOGD("monitor start");
pthread_t pid;
pthread_create(&pid, nullptr, mySigQuitCatcher, nullptr);
pthread_detach(pid);
LOGD("monitor end");
}
通过案例学知识:
- pthread_create:创建线程,在线程创建以后,就开始运行相关的线程函数。
- pthread_detach:是一个不阻塞函数,即主线程与子线程分离,子线程结束后,资源自动回收。
- sigwait:阻塞本进程,去等待指定的信号。等到了就会解除阻塞。
当前方案会产生第二个线程通过sigwait方法监听同一个信号,那么和系统SignalCatcher
线程两个互相抢占,当发生信号的时候,具体是哪一个线程收到信号时不能确定的。因此,当前方案不满足我们的需求
2、方案2 (Signal Handler)
通过Linux提供的sigaction
方法注册signal handler
来监听SIGQUIT
信号,微信的Matrix
库就是运用的这个原理
#include <jni.h>
#include <string>
#include <pthread.h>
#include <syslog.h>
#include <android/log.h>
#include <dirent.h>
#include <unistd.h>
#define TAG "Hensen" // 这个是自定义的LOG的标识
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,TAG ,__VA_ARGS__) // 定义LOGD类型
void signalHandler(int sig, siginfo_t *info, void *uc) {
if (sig == SIGQUIT) {
// 4、ANR信号监听
LOGD("sig == SIGQUIT");
}
}
extern "C" JNIEXPORT void JNICALL
Java_com_example_syncbarriermonitor_NativeLib_monitor(
JNIEnv *env,
jobject /* this */) {
LOGD("monitor start");
// 1、第一步:我们通过pthread_sigmask把SIGQUIT设置为UNBLOCK
sigset_t sigSet;
sigemptyset(&sigSet);
sigaddset(&sigSet, SIGQUIT);
pthread_sigmask(SIG_UNBLOCK, &sigSet, nullptr);
// 2、第二步:建立了SignalHandler监听ANR
struct sigaction sa;
sa.sa_sigaction = signalHandler;
sa.sa_flags = SA_ONSTACK | SA_SIGINFO | SA_RESTART;
sigaction(SIGQUIT, &sa, nullptr);
// 3、第三步:重新向Signal Catcher线程发送一个SIGQUIT
int tid = getSignalCatcherThreadId(); //遍历/proc/[pid]目录,找到SignalCatcher线程的tid
tgkill(getpid(), tid, SIGQUIT);
LOGD("monitor end");
}
SIGQUIT
设置成了BLOCKED
,系统默认只会响应sigwait
而不会进入到我们设置的handler方法中Signal Handler
,系统sigwait
和signal handler
的都能捕获到SIGQUIT
信号Signal Handler
抢到了SIGQUIT
,原本的Signal Catcher
线程中的sigwait
就不再能收到SIGQUIT
,原本的dump堆栈的逻辑就无法完成了,为了保持和系统的ANR流程保持一致,需要在Signal Handler
里面重新向Signal Catcher
线程发送一个SIGQUIT
通过案例学知识:
- pthread_sigmask:用来确定如何更改信号组
- sigaction:用来查询和设置信号处理方式
- tgkill:内核提供的系统调用,向线程发送信号
3、方案2的优化 (Signal Handler)
当我们的signalHandler
收到ANR的信号的时候,我们需要告知Java层调用onANRDumped()
,具体的上报等逻辑由客户端业务处理
void signalHandler(int sig, siginfo_t *info, void *uc) {
if (sig == SIGQUIT) {
anrDumpCallback();
}
}
bool anrDumpCallback() {
JNIEnv *env = JniInvocation::getEnv();
if (!env) return false;
env->CallStaticVoidMethod(gJ.AnrDetective, gJ.AnrDetector_onANRDumped);
return true;
}
最终在我们的Java层onANRDumped()
方法上
private synchronized static void onANRDumped() {
......
}
优化方向
在收到SIGQUIT
信号后,会产生误报的现象,针对两个点做了优化
SIGQUIT
信号后,为了防止SIGQUIT
信号是不是由本应用ANR
导致的,需要判断当前进程是否被置了ProcessErrorStateInfo.NOT_RESPONDING
状态,如果没有,则说明这个信号可能是由其他应用ANR导致的。这里采取的方案是创建Check
线程在20s内每隔500ms就轮询查一次NOT_RESPONDING
状态。这里采取的方案是检查主线程queue#mMessages
的when
字段,判断当前卡顿的时长,如果发现已卡顿前台2s,后台10s,则认为这是一个anr,立即上报,防止漏报private synchronized static void onANRDumped() {
......
confirmRealAnr(true);
}
private static void confirmRealAnr(final boolean isSigQuit) {
//1、第二点优化点:优先确认when字段的卡顿时长
boolean needReport = isMainThreadBlocked();
if (needReport) {
report(false, isSigQuit);
} else {
new Thread(new Runnable() {
@Override
public void run() {
//2、第一点优化点:创建线程轮询ProcessErrorStateInfo.NOT_RESPONDING
checkErrorStateCycle(isSigQuit);
}
}, CHECK_ANR_STATE_THREAD_NAME).start();
}
}
isMainThreadBlocked的关键代码
- 通过反射拿到对应的when字段,并判断当前卡顿的时长
@RequiresApi(api = Build.VERSION_CODES.M)
private static boolean isMainThreadBlocked() {
try {
MessageQueue mainQueue = Looper.getMainLooper().getQueue();
Field field = mainQueue.getClass().getDeclaredField("mMessages");
field.setAccessible(true);
final Message mMessage = (Message) field.get(mainQueue);
if (mMessage != null) {
anrMessageString = mMessage.toString();
MatrixLog.i(TAG, "anrMessageString = " + anrMessageString);
long when = mMessage.getWhen();
if (when == 0) {
return false;
}
long time = when - SystemClock.uptimeMillis();
anrMessageWhen = time;
long timeThreshold = BACKGROUND_MSG_THRESHOLD;
if (currentForeground) {
timeThreshold = FOREGROUND_MSG_THRESHOLD;
}
return time < timeThreshold;
} else {
MatrixLog.i(TAG, "mMessage is null");
}
} catch (Exception e) {
return false;
}
return false;
}
checkErrorStateCycle的关键代码
- 通过遍历ActivityManager,获取当前的进程ProcessErrorStateInfo,再获取它的condition属性做对比
private static boolean checkErrorState() {
try {
MatrixLog.i(TAG, "[checkErrorState] start");
Application application =
sApplication == null ? Matrix.with().getApplication() : sApplication;
ActivityManager am = (ActivityManager) application
.getSystemService(Context.ACTIVITY_SERVICE);
List<ActivityManager.ProcessErrorStateInfo> procs = am.getProcessesInErrorState();
if (procs == null) {
MatrixLog.i(TAG, "[checkErrorState] procs == null");
return false;
}
for (ActivityManager.ProcessErrorStateInfo proc : procs) {
MatrixLog.i(TAG, "[checkErrorState] found Error State proccessName = %s, proc.condition = %d", proc.processName, proc.condition);
if (proc.uid != android.os.Process.myUid()
&& proc.condition == ActivityManager.ProcessErrorStateInfo.NOT_RESPONDING) {
MatrixLog.i(TAG, "maybe received other apps ANR signal");
return false;
}
if (proc.pid != android.os.Process.myPid()) continue;
if (proc.condition != ActivityManager.ProcessErrorStateInfo.NOT_RESPONDING) {
continue;
}
MatrixLog.i(TAG, "error sate longMsg = %s", proc.longMsg);
return true;
}
return false;
} catch (Throwable t) {
MatrixLog.e(TAG, "[checkErrorState] error : %s", t.getMessage());
}
return false;
}
我们都知道监控到信号之后,进程dump堆栈会将文件保存在data/anr/
目录下,这个文件可以获取到Trace文件分析ANR,可我们没有权限读取,这个时候就要Hook掉系统Trace的write
方法,替换成我们的方法,其原理采用native hook
的方式,用plt_hook
框架来实现的,根据不同的版本,找到不同的hook点,其关键代码如下
void hookAnrTraceWrite() {
int apiLevel = getApiLevel();
if (apiLevel < 19) {
return;
}
if (apiLevel >= 27) {
plt_hook("libcutils.so", "connect", (void *) my_connect, (void **) (&original_connect));
} else {
plt_hook("libart.so", "open", (void *) my_open, (void **) (&original_open));
}
if (apiLevel >= 30 || apiLevel == 25 || apiLevel ==24) {
plt_hook("libc.so", "write", (void *) my_write, (void **) (&original_write));
} else if (apiLevel == 29) {
plt_hook("libbase.so", "write", (void *) my_write, (void **) (&original_write));
} else {
plt_hook("libart.so", "write", (void *) my_write, (void **) (&original_write));
}
}