Android Native程序crash的一些定位方法简介

Android Native程序crash的一些定位方法简介

经常,避免不了,我们的代码会崩溃。如果crash在native代码上,Android会和其他Linux一样,生成一份core dump,将程序运行时的内存,寄存器状态,堆栈指针,内存管理信息以及各种函数调用堆栈信息等存到一个文件中,供调试者使用分析。

Core Dump的生成

Android的Core dump叫tombstone,墓碑文件。由专门的一个daemon服务debuggerd来搜集,保存到/data/tombstone/目录下[logcat中也会打印一份简要信息,基本够分析用]。一般生成过程为:

  1. 系统异常,内核丢出来signal信号
  2. 应用中,bionic中实现的默认signal处理过程,通过socket将pid/tid和异常退出信息,发给debuggerd进程
  3. debuggerd得到pid/tid信息,通过ptrace挂到异常进程,得到异常进程的各种信息,然后生成墓碑文件,保存起来。

debuggerd的具体实现和ptrace就不展开讲了,以前研究挂钩子和benchmark分析处理的时候,参考了这里不少代码。有兴趣同学可以好好学习一下这个,也可以玩出花来的。比如以前做老化测试,就是改了捕鱼达人的金钱,脚本一直放炮整周末自动老化,不过现在好多apk都防着ptrace了,你想调试它它就自杀,越来越没意思了…

Tombstone文件的基本信息:

Tombstone信息通常由如下几部分组成:
1. 系统信息
包括编译信息、版本号、进程信息等。
2. 异常信息
包括产生的异常的原因,如signal 11(即段错误,内存访问异常)、异常所在位置等。
3. 寄存器信息
Dump当前线程栈中的寄存器值,包括通用寄存器和浮点寄存器的值。
4. backtrace信息
Dump当前进程栈中的调用栈。这个信息对异常分析非常有用。Backtrace信息已经做过符号定位和demangle,PC地址已经减去基地址,基本可以直接和so文件中的偏移地址对应。
5. 栈帧数据
以ascii形式,dump当前线程栈的栈帧信息。
6. 通用寄存器周边数据信息
通用寄存器很可能存储的是指针等信息,Android的tombstone会将通用寄存器周边数据也dump出来供分析。
7. 进程的logcat信息
Tombstone中还会dump在发生异常前,该进程输出的logcat信息。

其中,3,4,5,6,7每个线程信息都有对应信息。也不展开详说了,基本玩Android的都见过,除了墓碑文件中保存,在dropbox下,以及logcat的DEBUG TAG打出来的log中也有。

错误大体定位

简单说一下tombstone文件的阅读方式,大体定位错误方法。
一般对tombstone文件,除了知道是哪个进程挂了外,我们首要要知道的,是下面画圈圈的几个信息:
Android Native程序crash的一些定位方法简介_第1张图片
第一个,是signum,一般debuggerd关注的是SIGILL,SIGBUS,SIGABRT,SIGFPE,SIGSEGV,SIGPIPE等。而这里,估计九成都是SIGSEGV (即signal 11),段错误,和非法内存访问等价。
第二个是sigcode。对于段错误来说,sigcode一般就两个:SEGV_MAPERR和SEGV_ACCERR,字面意思,一个是map错误,一个是访问错误。
第三个是错误地址。

简单定位

错误九成以上都是段错误,主要关注这个,其他像非法指令、浮点出错等,也可以分析一下,一般要么是代码越界改写,要么是系统硬件不稳定造成的
对于段错误,基本上大多是SEGV_MAPERR。先说一下段错误的两种类型:
SEGV_MAPERR:这个说明访问出错的地址,压根就没内map到进程地址空间来,这种情况,通常就是野指针,或者越界访问,当然访问空指针也是属于这类。
SEGV_ACCERR:这个说明访问出错地址,被map到地址空间来了,但是没访问权限。基本上是指针越界或野指针,比如写只读map的内存地址。
这里的tombstone中的错误地址,就是非法操作的地址。现在Adroid已经使用了ALSR(地址空间随机布局),错误地址也一般不容易猜出大约哪里出问题,不过有一类地址,还是比较容易猜出来的:即地址的值比较小,基本上都是空指针访问了。地址值是小于4K的地址,可以认为就是解引用空指针了。

根据错误地址,错误类型,再通过tombstone给出的backtrace信息,使用编译时带符号的那份库,用addr2line查PC,直接得到出错的行号,结合错误原因,基本上可以定位错误了,修复即可。

几个错误例子

Android Native程序crash的一些定位方法简介_第2张图片

SEGV_MAPERR,错误的虚拟地址。怀疑野指针或越界。可以通过addr2line定位到行号。
一般我们看backtrace,并不是死板的看栈帧最顶上的。一般最顶上的都是C库,C库通常是被调用的底层函数库,不会做太多容错处理,一般认为是调用它的code给出的参数等有问题,再回溯几个栈帧看一下。这里很明显可以看到,是栈帧号3的地方出问题了,调用的是realloc,可能是越界或野指针,错的代码在这:

SharedBuffer* SharedBuffer::editResize(size_t newSize) const
{
    if (onlyOwner()) {
        SharedBuffer* buf = const_cast(this);
        if (buf->mSize == newSize) return buf;
        buf = (SharedBuffer*)realloc(buf, sizeof(SharedBuffer) + newSize);  //错在这
        if (buf != NULL) {
            buf->mSize = newSize;
            return buf;
        }
    }
    SharedBuffer* sb = alloc(newSize);
    if (sb) {
        const size_t mySize = mSize;
        memcpy(sb->data(), data(), newSize < mySize ? newSize : mySize);
        release();
    }
    return sb;    
}

看一下代码逻辑,基本先怀疑传入的参数newSize有异常,加一些trace,复现看看。

Android Native程序crash的一些定位方法简介_第3张图片
Dalvik虚拟机中有空指针, addr2line看一下具体位置,再看上下文查逻辑

Android Native程序crash的一些定位方法简介_第4张图片
这种错误,已经报说heap有异常,corrupt了。一般是有人越界写,或者double free都有可能,这个要查。很多时候还不是必现的,主要查一下临界区的保护问题。

Android Native程序crash的一些定位方法简介_第5张图片
主动自杀abort的,这个查到代码调用的地方,看一下逻辑就好。其实tombstone上,这类问题,同时会打印abort的原因,解决就好。

你可能感兴趣的:(Android调试)