主要讲解使用如何在客户端侧使用breakpad收集crash数据,当然还有定制breakpad。填之前collect_crash的坑
在linux中,当native发生crash的时候,我们可以通过注册signal来捕获对应的signal,函数原型如下:
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
下面说一下参数的意义:
signum:表示signal的类别,比如,SIGSEGV、SIGABRT等等,但是不包含SIGKILL 和 SIGSTOP,我们一般捕获的是以下6个:SIGSEGV, SIGABRT, SIGFPE, SIGILL, SIGBUS, SIGTRAP
sigaction *act:首先介绍一下sigaction是一个结构体,其中比较关键的就是 sa_sigaction和sa_flags,/ sa_sigaction作为回调,而如果需要回调起作用,则需要设置sa_flags,通常的做法都是
struct sigaction action{}; action.sa_sigaction = SignalHandler; action.sa_flags = SA_SIGINFO | SA_ONSTACK;
Sigaction *oldact:由于每个信息只允许存在一个处理的函数,因此当我们设置我们的处理函数时会覆盖原来的处理函数,因此需要将原来的处理函数保存下来,然后当我们的函数执行完之后,再处理执行原先的处理函数。
如此设置之后,当有signal出现的时候就会回调到SignalHandler中,而这个的函数原型如下:
void SignalHandler(int sig, siginfo_t *info, void *ucontext)
下面分别介绍一下参数:
sig:表示的是哪个signal,参考上面的signum
*info:是一个结构体指针,先介绍一下siginfo_t这个结构体
__SIGINFO struct { int si_signo; int si_errno; int si_code; union __sifields _sifields; / }
其中si_signo与sig一致,si_errno的值一般是0,si_code指示为什么这个signal会发送,__sifields一般不关心。
然后我们在SignalHandler中处理*oldact就完成了整个流程。
首先放一张表示流程的自然语言:
// SignalHandler (uses a global stack of ExceptionHandler objects to find // | one to handle the signal. If the first rejects it, try // | the second etc...) // V // HandleSignal ----------------------------| (clones a new process which // | | shares an address space with // (wait for cloned | the crashed process. This // process) | allows us to ptrace the crashed // | | process) // V V // (set signal handler to ThreadEntry (static function to bounce // SIG_DFL and rethrow, | back into the object) // killing the crashed | // process) V // DoDump (writes minidump) // | // V // sys_exit //
上述的流程就是breakpad处理signal的流程,我们主要看一下DoDump()方法,主要做了如下事情:
首先我们需要知道minidump文件的格式,格式的定义是在minidump_format.h中,但是有些结构并没有在代码中直接使用相应的对象,比如MDRawThreadList,按照之前解析class文件的经验,都是直接生成对应结构的对象,但是,由于是C语言可以直接操作地址,因此,可以不通过构建对象的方式来构建这个结构体,那么如何实现呢?不要急,我们先看一下写minidump文件的大致流程:
写header,一般都是这样处理的,不多说
写MDRawDirectory,默认是13个,结构如下:
typedef uint32_t MDRVA; /* RVA */ typedef struct { uint32_t data_size; //MDRawDirectory的大小 MDRVA rva; //MDRawDirectory中第一个元素的偏移量或者说起始位置 } MDLocationDescriptor; /* MINIDUMP_LOCATION_DESCRIPTOR */ typedef struct { uint32_t stream_type; MDLocationDescriptor location; } MDRawDirectory;
写MDRawThreadList,这里就是上面说的问题了,你会发现整个breakpad中,并没有构建MDRawThreadList对象,而是通过偏移量来操作,首先是获取thread的数目,然后rva = originPosition+numsOfThread*sizeof(MDRawThread),这样就知道第一个MDRawThread的位置。
所以,修改的方式简单来说就是定义一个struct,然后将其插入到minidump文件的最后,然后按照规则解析出来。
在breakpad的MinidumpCallback中是无法收集java 堆栈的,经过我的测试,只要涉及到 String
类型的数据,就会直接退出,比如你在收集Java堆栈的方法中,定义一个 String
类型的数据,当运行到这行代码时,就会直接退出,后面的代码不会运行,解决的方式是开启一个新线程收集,这样就需要涉及到线程的同步问题,换句话说,就是崩溃线程A依赖于收集java堆栈的线程B,线程B也依赖于线程A,于是我们就会想到使用互斥锁+条件变量的方式解决。具体的做法如下:
首先我们定义一个java方法,该方法用于收集java的堆栈。 public static void generateCrashProto(int crashId, final String path)
在 JNI_OnLoad
的时候将该方法的 jmethodID
以及所属类的 jclass
保存为全局引用
在 JNI_OnLoad
中使用 pthread_create
创建一个线程,定义回调 void* DumpJavaThreadInfo(void *argv)
定于 如下几个变量:
static int tidCrash; //crash线程id static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; static pthread_cond_t cond = PTHREAD_COND_INITIALIZER; static pthread_mutex_t mutex_finish = PTHREAD_MUTEX_INITIALIZER; static pthread_cond_t cond_finish = PTHREAD_COND_INITIALIZER; pthread_t ntid; //新建线程id
在 DumpJavaThreadInfo
中判断 tidCrash
是否为0,不为0则一直等待,即等待MinidumpCallback回调
pthread_mutex_lock(&mutex); //当条件不满足时等待 while (tidCrash == 0) { pthread_cond_wait(&cond, &mutex); } ... pthread_mutex_unlock(&mutex); SetDumpJavaFinish(); //通知crash线程,java堆栈收集完毕
在MinidumpCallback回调中对 tidCrash
赋值,并且发生信号给上一步阻塞的线程
tidCrash = gettid(); minidumpPath = const_cast<char *>(descriptor.path()); if (ntid != NULL){ pthread_mutex_lock(&mutex); pthread_cond_signal(&cond); pthread_mutex_unlock(&mutex); WaitDumpJava(); //等待获取java堆栈函数完成 } void WaitDumpJava(){ struct timeval now; gettimeofday(&now, NULL); struct timespec outtime; outtime.tv_sec = now.tv_sec + c_waitSecond; outtime.tv_nsec = 0; pthread_mutex_lock(&mutex_finish); pthread_cond_timedwait(&cond_finish, &mutex_finish, &outtime); pthread_mutex_unlock(&mutex_finish); } //在DumpJavaThreadInfo被调用 static void SetDumpJavaFinish(){ pthread_mutex_lock(&mutex_finish); pthread_cond_signal(&cond_finish); pthread_mutex_unlock(&mutex_finish); }