Native Crash问题分析

1. 何为Native Crash

简单说,Native Crash就是发生在native层的用户进程的crash。在Android 6.0上,又可以分为几类:

发生在Java应用(包括system_server)的native(jni)层的native crash;

发生在native进程(比如surfaceflinger, mediaserver)的native crash

发生在Java虚拟机(libart.so)中的native crash

发生在oat代码中的native crash从分析问题的角度说,前三者的分析方法比较类似,而最后一个需要特殊的分析方法。

另外,native crash总是由内核发出特别的signal而触发,常见的信号有以下几种:

SIGSEGV(signal 11): 一般可以称为非法地址访问,进程访问的地址不在该进程的地址空间范围内,或者虽然在进程地址空间范围内,但是违反了相应地址的访问权限(比如尝试修改只读区域).

SIGBUS(signal 7): 其实和SIGSEGV有些类似,都是访问了非法地址,不同的是,SIGBUS访问的地址确实在进程的地址空间范围内,访问权限也正确,但是访问的地址是不对齐的(比如访问的地址不能被2/4整除);还有一种情况,调用mmap()进行文件映射后,文件被截短,此时又访问了超出文件目前尺寸的区域。

SIGABORT(signal 6): 用户进程主动调用abort()函数,常见的情况是进行关键参数检查时发现异常,决定终止执行。

SIGPIPE(signal 13): 用户进程尝试读写socket/pipe时,对端意外终止。

可能还会有其他信号,比如SIGSTKFLT(signal 16),发生的比较少,就不一一描述的。

Native Crash的分析方法随着signal的不同有所区别,其中SIGSEGV和SIGBUS分析方法类似。其余的信号各有不同。如果是那些少见的信号,可以通过查找内核代码或者google/baidu,确定信号的意义,然后再进行分析。

2. Native Crash分析所需的材料

2.1 tombstone文件

顾名思义,tombstone就是进程的墓碑,也就是用户进程发生Native Crash之后保存下来的基本信息,里面包含Native Crash分析的最少内容。tombstone文件由debuggerd进程抓取,生成在/data/tombstone目录下。snapshot机制会抓取相关文件并生成最终的snapshot文件。原始tombstone文件可能可以在slog的dropbox目录下找到。如果是本地复现的native crash, 可以直接由/data/tombstone下获取。

典型的tombstone文件包含以下信息:

2.1.1 头信息

*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***

Native Crash TIME: 8388223

*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***

Build fingerprint:' XXXX/XXXX/XXXX/XXXX/XXXX:userdebug/test-keys'

Revision: '0'

ABI: 'arm'

这一节表示这是一个Native Crash及发生时间,还有system.img生成时的fingerprint, 可以用来确定问题版本。

2.1.2 进程信息和寄存器信息

pid: 10067, tid: 10067, name: mediaserver  >>> /system/bin/mediaserver <<<

signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0xa5a5a645

    r0 b7bc2898  r1 a5a5a5a5  r2 5f65b07f  r3 0000079f

    r4 b7bc2898  r5 b7bc2898  r6 bec11c50  r7 00000001

    r8 5f65b07f  r9 0000079f  sl b79d87c0  fp 00000000

    ip b6f19d4c  sp bec11c40  lr b6ef5ba1  pc b6ef5bb6  cpsr 600f0030

    d0  bec11ba8bec11318  d1  0000000000000001

    d2  bec11ba8bec11318  d3  00000001b6115f25

    d4  0000079f5f65b07f  d5  00000000b79d87c0

    d6  bec11290b6134ec0  d7  b61184abb61184ab

    d8  0000079f5f65b07f  d9  0000000000000000

    d10 0000000000000000  d11 0000000000000000

    d12 0000000000000000  d13 0000000000000000

    d14 0000000000000000  d15 0000000000000000

    d16 0000000000000000  d17 0000000000000000

    d18 4023e79600000000  d19 4006666660000000

    d20 405ca5dc1a63c1f8  d21 4006666660000000

    d22 3fc35fe27b800000  d23 3fc2e2cedd55ea5c

    d24 3ff0000000000000  d25 3f96de83a904ab51

    d26 4150624de0000000  d27 40112e0bf5d78812

    d28 3f88b0d283c30939  d29 bf88b0d28a518506

    d30 bfc4eaefa4251850  d31 3f40624dd2f1a9fc

scr 28000010

第一行是发生Native crash的进程号,线程号,线程名,进程名

第二行显示触发native crash的signal, 和signal相关的参数以及触发该信号的地址。

后面四行是Native crash发生时通用寄存器的值(r0 – r15, cpsr) (和系统架构有关) 64位系统会不一样

后面是neon指令集需要用到的寄存器的值(大多数情况下不用关心,多媒体相关的native crash有可能需要关心)

2.1.3 Call stack
backtrace:
    #00 pc 00075bb6  /system/lib/libcameraservice.so (android::camera3::Camera3IOStreamBase::returnAnyBufferLocked(camera3_stream_buffer const&, long long, bool)+77)
    #01 pc 00076b13  /system/lib/libcameraservice.so (android::camera3::Camera3OutputStream::returnBufferLocked(camera3_stream_buffer const&, long long)+42)
    #02 pc 00074d65  /system/lib/libcameraservice.so (android::camera3::Camera3Stream::returnBuffer(camera3_stream_buffer const&, long long)+52)
    #03 pc 0006edd7  /system/lib/libcameraservice.so (android::Camera3Device::returnOutputBuffers(camera3_stream_buffer const*, unsigned int, long long)+42)
    #04 pc 00072755  /system/lib/libcameraservice.so (android::Camera3Device::processCaptureResult(camera3_capture_result const*)+560)
    #05 pc 00051b09  /system/lib/hw/camera.sc8830.so (sprdcamera::SprdCamera3HWI::handleCbDataWithLock(sprdcamera::cam_result_data_info_t*)+860)
    #06 pc 0003b835  /system/lib/hw/camera.sc8830.so (sprdcamera::SprdCamera3PicChannel::channelClearAllQBuff(long long, sprdcamera::camera_stream_type_t)+196)
    #07 pc 0005211f  /system/lib/hw/camera.sc8830.so (sprdcamera::SprdCamera3HWI::~SprdCamera3HWI()+214)
    #08 pc 000521f9  /system/lib/hw/camera.sc8830.so (sprdcamera::SprdCamera3HWI::~SprdCamera3HWI()+4)
    #09 pc 000502b5  /system/lib/hw/camera.sc8830.so (sprdcamera::SprdCamera3HWI::close_camera_device(hw_device_t*)+56)
    #10 pc 000703d7  /system/lib/libcameraservice.so (android::Camera3Device::disconnect()+278)
    #11 pc 00052025  /system/lib/libcameraservice.so (android::Camera2ClientBase<android::CameraService::Client>::detachDevice()+12)
    #12 pc 0006a225  /system/lib/libcameraservice.so (android::CameraDeviceClient::detachDevice()+104)
    #13 pc 00052195  /system/lib/libcameraservice.so (android::Camera2ClientBase<android::CameraDeviceClientBase>::disconnect()+60)
    #14 pc 00020845  /system/lib/libcamera_client.so (android::BnCameraDeviceUser::onTransact(unsigned int, android::Parcel const&, android::Parcel*, unsigned int)+80)
    #15 pc 000198b1  /system/lib/libbinder.so (android::BBinder::transact(unsigned int, android::Parcel const&, android::Parcel*, unsigned int)+60)
    #16 pc 0001ebbf  /system/lib/libbinder.so (android::IPCThreadState::executeCommand(int)+594)
    #17 pc 0001ed01  /system/lib/libbinder.so (android::IPCThreadState::getAndExecuteCommand()+64)
    #18 pc 0001ed65  /system/lib/libbinder.so (android::IPCThreadState::joinThreadPool(bool)+48)
    #19 pc 00001cc1  /system/bin/mediaserver
    #20 pc 00016661  /system/lib/libc.so (__libc_init+44)
    #21 pc 00001f24  /system/bin/mediaserver

随后是触发native crash的线程当时的call stack, #x表示frame number, pc yyyyyyyy 表示的是该栈帧对应的PC值(此处说明一下,这里的PC值是相对PC值,并非真正的虚拟地址。我们知道动态链接库在加载时,其加载的虚拟地址空间并不是固定的,同样的动态链接库,在不同的进程里,加载的虚拟地址空间也是不同的,这里的PC值,却是对应代码相对本动态链接库代码段的首地址的偏移量。此地址可以用addr2line工具分析。

在PC后面是对应栈帧PC值所归属的动态链接库或者可执行程序名。最后是对应的函数名及对应汇编指令相对函数入口的偏移量。

2.1.4 原始stack内容
stack:
         bec11c00  00000002
         bec11c04  b7bc2898  [heap]
         bec11c08  b7bc2898  [heap]
         bec11c0c  b7bc2968  [heap]
         bec11c10  b7bc2898  [heap]
         bec11c14  00000001
         bec11c18  5f65b07f
         bec11c1c  0000079f
         bec11c20  b79d87c0  [heap]
         bec11c24  b6ef739b  /system/lib/libcameraservice.so (android::camera3::Camera3OutputStream::~Camera3OutputStream()+14)
         bec11c28  b7c1d5a0  [heap]
         bec11c2c  b6be6725  /system/lib/libutils.so (android::RefBase::decStrong(void const*) const+52)
         bec11c30  b7bc2898  [heap]
         bec11c34  b7bc2898  [heap]
         bec11c38  00000002
         bec11c3c  b6ef5ba1  /system/lib/libcameraservice.so (android::camera3::Camera3IOStreamBase::returnAnyBufferLocked(camera3_stream_buffer const&, long long, bool)+56)
………………………………………………..

这一段是根据当前的sp(栈指针)所dump出来的原始的stack数据,第一个是栈地址,第二个是对应地址的内存内容,第三个是对应内存内容的解释,如果这个数据属于某个符号,那么就会显示为对应符号的内容

不过如果某个栈帧很长,这里可能不会全部dump。

2.1.5 内存内容
memory near r0:
    b7bc2878 aaaaaaaa aaaaaaaa aaaaaaaa aaaaaaaa  ................
    b7bc2888 aaaaaaaa aaaaaaaa aaaaaaaa a5a5a5a5  ................
    b7bc2898 b6f15f94 a5a5a5a5 a5a5a5a5 a5a5a5a5  ._..............
    b7bc28a8 a5a5a5a5 a5a5a5a5 a5a5a5a5 a5a5a5a5  ................
    b7bc28b8 a5a5a5a5 a5a5a5a5 a5a5a5a5 a5a5a5a5  ................
    b7bc28c8 a5a5a5a5 a5a5a5a5 a5a5a5a5 a5a5a5a5  ................
    b7bc28d8 a5a5a5a5 a5a5a5a5 a5a5a5a5 a5a5a5a5  ................
    b7bc28e8 a5a5a5a5 a5a5a5a5 a5a5a5a5 a5a5a5a5  ................
    b7bc28f8 a5a5a5a5 a5a5a5a5 a5a5a5a5 a5a5a5a5  ................
    b7bc2908 a5a5a5a5 a5a5a5a5 a5a5a5a5 a5a5a5a5  ................
    b7bc2918 a5a5a5a5 a5a5a5a5 a5a5a5a5 a5a5a5a5  ................
    b7bc2928 a5a5a5a5 a5a5a5a5 a5a5a5a5 a5a5a5a5  ................
    b7bc2938 a5a5a5a5 a5a5a5a5 a5a5a5a5 a5a5a5a5  ................
    b7bc2948 a5a5a5a5 a5a5a5a5 a5a5a5a5 b6f162ec  .............b..
    b7bc2958 a5a5a5a5 a5a5a5a5 a5a5a5a5 a5a5a5a5  ................
    b7bc2968 b6f16054 a5a5a5a5 bbbbbbbb bbbbbbbb  T`..............

这一段是内存内容的dump,就是把每个通用寄存器里的值假设为地址,把这个地址前后总共256字节的内容dump出来,如果这个地址不能访问,那么对应的内容会以--------替代。

在没有corefile的情况下,这里的数据和前面stack的原始数据是仅有的内存dump,有些时候可以辅助分析。

2.1.6 memory map
memory map: (fault address prefixed with --->)
--->Fault address falls at a5a5a645 before any mapped regions
    aa71f000-ab11efff rw-         0    a00000  anon_inode:dmabuf
    ab3df000-ab95dfff rw-         0    57f000  anon_inode:dmabuf
    abfdd000-ac098fff rw-         0     bc000  anon_inode:dmabuf
    ac198000-ac198fff ---         0      1000
    ac199000-ac296fff rw-         0     fe000
2.1.7 其余线程的信息

随后就是从2.1.3到2.1.6的重复,只是显示的是该进程的其它线程的相关信息。

2.1.8 snapshot文件多出的信息

debug机制对ANR和tombstone都会生成snapshot文件,相对标准的tombstone文件,还多出很多信息,比如adb log, meminfo, ps, binder等。这一部分和tombstone无关,这里也不多加描述了。

2.2 corefile

2.1.5中提到,tombstone文件中包含少量的内存dump,而corefile就是一个进程的完整的内存dump,也就是把2.1.6中所有的虚拟地址空间段的内存都dump出来,有了它的帮助,我们可以深入分析native crash并查找原因。

corefile一般生成在/data/corefile下,一般是两个文件,一个是core-nnnnn-xxxx, 其中nnnnn代表线程名,xxxx是进程号,这个是真正的内存dump,另一个是maps-xxxx, xxxx和前面一样是进程号,这个就是进程的memory map, 和2.1.6是同一个东西。

2.3 符号表

我们知道C/C++代码编译生成目标文件时,如果使用了-g选项,那么会把符号表也编译到目标文件里。内存中的值,如果是地址,那么都是纯粹的虚拟地址,没有符号表的帮助,难以和源代码中的函数名,变量名对应在一起,在纯汇编的基础上分析,效率很低。

对于平台来说,官方的jenkins release中除了pac包以外,还包括symbols.vmlinux.xxxxx和symbols.system.xxxxx这两个包含符号表的文件,symbols.vmlinux.xxxxx是带符号表的内核,用于分析sysdump, symbols.system.xxxxx是所有用户程序,包括动态链接库的带符号表的版本。如果是本地的build, 那么带符号表的版本位于out/target/product//symbols下。

2.4 分析工具

调试过unix/linux用户程序的同事都知道,分析coredump使用的是gdb, 这里我们分析corefile, 使用的也是gdb, 不过是arm版本的。除了gdb以外,可能经常用到的tool还有addr2line, readelf, objdump等,还有一个c++filt可以用于将C++的变量/函数名转换为可读的名称(称为demangle).

相关的工具其实在代码里都找的到,但是路径很深,使用起来不甚方便,我这里提供一个简单的方法,就是按照build的流程, source build/envsteup.sh; lunch; 选择产品(这里往往只需要注意32bit产品和64bit产品的区别,具体到产品,不必完全匹配)。

lunch完成后,会把相应tool的路径加入当前终端的PATH环境变量,以后直接敲工具名就可以,不必输入路径。

对32bit产品,使用的工具一般是arm-eabi-xxx, xxx是工具名,比如arm-eabi-gdb, arm-eabi-addr2line

对64bit产品,使用的工具一般是aarch64-linux-android-xxx。

但是这种方法有个弊病,只有当前终端可以用,别的终端要用,还得重新lunch.

所以也可以将相应的tool copy到自己的目录下,加入环境变量以后就可以在所有终端使用了。

3 分析Native crash所需的knowledge

3.1 arm架构基础知识

这个不需要学习很深入,但一般的基础知识要有,比如32bit架构(armv7之前), r0-r15是通用寄存器,r15是PC,r14是lr, r13是sp, PC一般是当前指令后2条指令对应的地址等等。

3.2 arm指令

Native crash的分析往往需要基于汇编代码,所以需要对arm汇编指令有一定了解,包括指令本身,寻址方式,条件码等等。这个不需要记住,因为可以查,但是记住一些基础指令,效率会提高很多。

3.3 C/C++函数参数传递和通用寄存器的对应

这里简单介绍几条(针对32bit系统,64bit略有不同,但我分析不多,这里就不介绍了)

C代码的函数调用,r0, r1, r2, r3分别用于保存参数1到参数4,如果超过5个参数,那么后面的参数会放在栈里。同时如果函数有返回值,放在r0里.

C++的函数调用稍有不同, r0用于保存this指针,r1, r2, r3保存参数1到参数3,后面的参数压栈。返回值还是r0。

局部变量,如果是简单变量,往往用通用寄存器保存,如果是数组,结构等大尺寸变量,往往会放在栈上。

PC经常用来访问某些全局变量

3.4 对gdb等工具的熟悉

熟练使用gdb工具也是提高分析效率的重要环节。

3.5 对相关模块代码的熟悉

归根到底,分析native crash,gdb等都只是工具,帮忙定位问题,具体到分析问题,找出解决方案,还是需要对相关模块代码熟悉起来,才能有效率。

4. Native crash分析的一般流程

这里以546056为例介绍一下Native Crash分析的一般流程

4.1 确定问题发生的进程

对于Native crash, 我们需要确定发生问题的进程是由哪个可执行程序加载起来的。

如果是native进程发生的Native Crash,那么进程名就是Native crash发生的进程,比如mediaserver, surfaceflinger, modemd; 如果是java进程,所有的java进程都是由app_process这个进程开始的(32bit系统是app_process32, 64bit系统应该是app_process64)。

然后使用gdb加载对应的可执行程序:

以546056为例:

01-01 11:24:11.247   264   264 F DEBUG   : pid: 266, tid: 10027, name: Binder_2  >>> /system/bin/mediaserver <<<

所以: arm-eabi-gdb ./symbols/system/bin/mediaserver

4.2 设置符号表搜索路径

(gdb) set solib-search-path ../../../symbols/system/bin/:../../../symbols/system/lib/:../../../symbols/system/lib/hw/

这里是设置符号表搜索路径,gdb会自动把内存内容和符号表进行匹配,后面的gdb命令可能把相关内存内容翻译成对应的符号。但要注意,带符号表的so文件或者可执行文件可能位于不同的目录,set solib-search-path不会自动加载子目录,所以最好把所有的目录都加进来,每一个目录之间用冒号分隔。上面列的三个目录是常见的。也可以根据tombstone的backtrace, 把相关so所在的路径加进来。

4.3 设置代码路径

(gdb) directory /home/workspace/6.0_prime

Source directories searched: /home/workspace/6.0_prime:$cdir:$cwd

如果代码路径下的代码和发生native crash的版本能够对应的话,那么设置好代码路径后就可以混合查看汇编代码和源代码了。但这一步并非必须。

4.4 加载corefile

(gdb) core-file core-Binder_2-266 
[New LWP 10027]
[New LWP 10032]
[New LWP 1289]
[New LWP 10013]
[New LWP 1291]
[New LWP 1301]
[New LWP 1290]
[New LWP 10006]
[New LWP 10020]
[New LWP 1296]
[New LWP 10023]
[New LWP 10014]
[New LWP 10015]
[New LWP 10022]
[New LWP 10030]
[New LWP 10038]
[New LWP 10011]
[New LWP 10017]
[New LWP 10005]
[New LWP 10029]
[New LWP 10002]
[New LWP 10031]
[New LWP 10021]
[New LWP 10024]
[New LWP 10035]
[New LWP 1300]
[New LWP 1298]
[New LWP 10016]
[New LWP 1295]
[New LWP 10010]
[New LWP 10019]
[New LWP 10039]
[New LWP 10003]
[New LWP 1297]
[New LWP 10018]
[New LWP 1294]
[New LWP 1947]
[New LWP 10007]
[New LWP 1293]
[New LWP 10004]
[New LWP 10034]
[New LWP 10028]
[New LWP 1305]
[New LWP 1299]
[New LWP 10025]
[New LWP 10033]
[New LWP 10012]
[New LWP 10026]
[New LWP 18944]
[New LWP 1346]
[New LWP 1345]
[New LWP 266]
[New LWP 7071]
warning: Could not load shared library symbols for 5 libraries, e.g. /system/lib/soundfx/libbundlewrapper.so.
Use the "info sharedlibrary" command to see the complete listing.
Do you need "set solib-search-path" or "set sysroot"?
Core was generated by `/system/bin/mediaserver'.
Program terminated with signal 11, Segmentation fault.
#0  0xb6e60c00 in promote (this=<optimized out>) at system/core/include/utils/RefBase.h:451

451 system/core/include/utils/RefBase.h: 没有那个文件或目录.

这里也可以用directory命令吧源代码的路径设置好,那么最后的这行提示就不会出现了

4.5 查看问题线程的backtrace

(gdb) bt
#0  0xb6e60c00 in promote (this=<optimized out>) at system/core/include/utils/RefBase.h:451
#1  android::camera3::Camera3Stream::fireBufferListenersLocked (this=this@entry=0xb872cc28, acquired=acquired@entry=false, output=output@entry=true)
    at frameworks/av/services/camera/libcameraservice/device3/Camera3Stream.cpp:546
#2  0xb6e60d76 in android::camera3::Camera3Stream::returnBuffer (this=0xb872cc28, buffer=..., timestamp=6165339964000) at frameworks/av/services/camera/libcameraservice/device3/Camera3Stream.cpp:471
#3  0xb6e5add8 in android::Camera3Device::returnOutputBuffers (this=this@entry=0xb83702d8, outputBuffers=<optimized out>, numBuffers=1, timestamp=timestamp@entry=6165339964000)
    at frameworks/av/services/camera/libcameraservice/device3/Camera3Device.cpp:2074
#4  0xb6e5e758 in android::Camera3Device::processCaptureResult (this=0xb83702d8, result=0xad790f64) at frameworks/av/services/camera/libcameraservice/device3/Camera3Device.cpp:2365
#5  0xb2bd2b0a in sprdcamera::SprdCamera3HWI::handleCbDataWithLock (this=0xb7deb440, result_info=<optimized out>) at vendor/sprd/modules/libcamera/hal3/SprdCamera3HWI.cpp:1276
#6  0xb2bbb5c4 in sprdcamera::SprdCamera3RegularChannel::channelCbRoutine (this=0xb86dd2b0, frame_number=0, timestamp=<optimized out>, stream_type=sprdcamera::CAMERA_STREAM_TYPE_CALLBACK)
    at vendor/sprd/modules/libcamera/hal3/SprdCamera3Channel.cpp:169
#7  0xb2bcecbc in sprdcamera::SprdCamera3OEMIf::receivePreviewFrame (this=this@entry=0xb82ef650, frame=frame@entry=0xb817db90) at vendor/sprd/modules/libcamera/hal3/SprdCamera3OEMIf.cpp:3010
#8  0xb2bcf48c in sprdcamera::SprdCamera3OEMIf::HandleStartPreview (this=this@entry=0xb82ef650, cb=cb@entry=CAMERA_EVT_CB_FRAME, parm4=parm4@entry=0xb817db90)
    at vendor/sprd/modules/libcamera/hal3/SprdCamera3OEMIf.cpp:3517
#9  0xb2bcf73c in sprdcamera::SprdCamera3OEMIf::camera_cb (cb=CAMERA_EVT_CB_FRAME, client_data=0xb82ef650, func=CAMERA_FUNC_START_PREVIEW, parm4=0xb817db90)
    at vendor/sprd/modules/libcamera/hal3/SprdCamera3OEMIf.cpp:3930
#10 0xb2c154de in camera_preview_cb_thread_proc (message=<optimized out>, data=<optimized out>) at vendor/sprd/modules/libcamera/oem2v0/src/cmr_oem.c:2999
#11 0xb2c3c9bc in cmr_common_routine (client_data=0xb84aba38) at vendor/sprd/modules/libcamera/common/src/cmr_msg.c:362
#12 0xb6a2a554 in __init_alternate_signal_stack (thread=0xad790a70) at bionic/libc/bionic/pthread_create.cpp:70
#13 0xb6a02276 in clone (fn=0x78, child_stack=<optimized out>, flags=-1384576400, arg=0x0) at bionic/libc/bionic/clone.cpp:57
#14 0x00000000 in ?? ()

4.6 查看出错的汇编代码

(gdb) disas
Dump of assembler code for function android::camera3::Camera3Stream::fireBufferListenersLocked(camera3_stream_buffer const&, bool, bool):
   0xb6e60bdc <+0>: stmdb sp!, {r4, r5, r6, r7, r8, r9, lr}
   0xb6e60be0 <+4>: sub sp, #60 ; 0x3c
   0xb6e60be2 <+6>: mov r4, r0
   0xb6e60be4 <+8>: add r5, sp, #8
   0xb6e60be6 <+10>: mov r6, r3
   0xb6e60be8 <+12>: mov r8, r2
   0xb6e60bea <+14>: mov r0, r5
   0xb6e60bec <+16>: movs r1, #0
   0xb6e60bee <+18>: movs r2, #48 ; 0x30
   0xb6e60bf0 <+20>: mov.w r9, #0
   0xb6e60bf4 <+24>: blx 0xb6e2f224
   0xb6e60bf8 <+28>: strb.w r6, [sp, #8]
   0xb6e60bfc <+32>: add r7, sp, #4
   0xb6e60bfe <+34>: ldr r6, [r4, #124] ; 0x7c
=> 0xb6e60c00 <+36>: ldr r4, [r6, #12]

4.7 查看寄存器内容

这一部分也可以从tombstone文件里得到,也可以通过下面命令

(gdb) info r(egister)
r0             0xad790a70 2910390896
r1             0x0 0
r2             0x30 48
r3             0xad790aa0 2910390944
r4             0xb872cc28 3094531112
r5             0xad790a70 2910390896
r6             0xa5a5a5a5 2779096485
r7             0xad790a6c 2910390892
r8             0x0 0
r9             0x0 0
r10            0x0 0
r11            0x0 0
r12            0x0 0
sp             0xad790a68 0xad790a68
lr             0xb6e60bf9 -1226437639
pc             0xb6e60c00 0xb6e60c00 <android::camera3::Camera3Stream::fireBufferListenersLocked(camera3_stream_buffer const&, bool, bool)+36>
cpsr           0x400f0030 1074724912

因为r6 = 0xa5a5a5a5, 出错指令“0xb6e60c00 <+36>: ldr r4, [r6, #12]”访问r6+12 = 0xa5a5a5b1, 所以出错

这点和tombstone完全对应

01-01 11:24:11.248   264   264 F DEBUG   : signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0xa5a5a5b1

4.8 分析汇编和源代码

看过前面的汇编,可以知道r6的来源是以下两句汇编代码:

0xb6e60be2 <+6>: mov r4, r0这是一个C++函数,所以r0就是this指针,所以ldr r6, [r4, #124]就是访问当前类的一个成员。结合代码:

void Camera3Stream::fireBufferListenersLocked(
        const camera3_stream_buffer& /*buffer*/, bool acquired, bool output) {
    List<wp<Camera3StreamBufferListener> >::iterator it, end;

    // TODO: finish implementing

    Camera3StreamBufferListener::BufferInfo info =
        Camera3StreamBufferListener::BufferInfo();
    info.mOutput = output;
    // TODO: rest of fields

    for (it = mBufferListenerList.begin(), end = mBufferListenerList.end();
         it != end;
         ++it) {

        sp<Camera3StreamBufferListener> listener = it->promote();
        if (listener != 0) {
            if (acquired) {
                listener->onBufferAcquired(info);
            } else {
                listener->onBufferReleased(info);
            }
        }
    }
}

可知这里访问的是mBufferListenerList.begin()。

确认的办法如下

我们知道this是android::camera3::Camera3Stream类

(gdb) p (android::camera3::Camera3Stream*)0

$1 = (android::camera3::Camera3Stream *) 0x0

(gdb) p ((android::camera3::Camera3Stream*)0)->mBufferListenerList

Cannot access memory at address 0x78

而出问题前两条汇编指令是:

0xb6e60bfe <+34>: ldr r6, [r4, #124] ; 0x7c

=> 0xb6e60c00 <+36>: ldr r4, [r6, #12]

(gdb) ptype/m ((android::camera3::Camera3Stream*)0)->mBufferListenerList

type = class android::List > [with T = android::wp] {

  private:

    android::List::_Node *mpMiddle;

    typedef android::List::_ListIterator::NON_CONST_ITERATOR> iterator;

    typedef android::List::_ListIterator::CONST_ITERATOR> const_iterator;

}

mBufferListenerList本事是个android::List类, 里面只有一个成员变量mpMiddle.

(gdb) p ((android::camera3::Camera3Stream*)0)->mBufferListenerList.mpMiddle

Cannot access memory at address 0x7c

这里就匹配了第一行汇编代码

(gdb) ptype/m ((android::camera3::Camera3Stream*)0)->mBufferListenerList.mpMiddle

type = class android::List >::_Node {

  private:

    android::wp mVal;

    android::List >::_Node *mpPrev;

    android::List >::_Node *mpNext;

} *

再看看android::List的相关代码:

template<typename T>
class List
{
protected:
    /*
     * One element in the list.
     */
class _Node {
   public:
        inline _Node* getNext() const { return mpNext; }
};
public:
    inline iterator begin() {
        return iterator(mpMiddle->getNext());
    }
    inline const_iterator begin() const {
        return const_iterator(const_cast<_Node const*>(mpMiddle->getNext()));
    }
    inline iterator end() {
        return iterator(mpMiddle);
    }
    inline const_iterator end() const {
        return const_iterator(const_cast<_Node const*>(mpMiddle));
}
}

可见begin()访问的是mpMiddle->getNext()函数, 而getNext函数实际访问的是mpMiddle->mpNext

(gdb) p ((android::List >::_Node*)0)->mpNext

Cannot access memory at address 0xc

这样又匹配了第二行汇编代码

$4 = {
  <camera3_stream> = {
    stream_type = 0xa5a5a5a5, 
    width = 0xa5a5a5a5, 
    height = 0xa5a5a5a5, 
    format = 0xa5a5a5a5, 
    usage = 0xa5a5a5a5, 
    max_buffers = 0xa5a5a5a5, 
    priv = 0xa5a5a5a5, 
    data_space = 0xa5a5a5a5, 
    rotation = 0xa5a5a5a5, 
    reserved = {0xa5a5a5a5, 0xa5a5a5a5, 0xa5a5a5a5, 0xa5a5a5a5, 0xa5a5a5a5, 0xa5a5a5a5, 0xa5a5a5a5}
  }, 
  <android::camera3::Camera3StreamInterface> = <invalid address>, 
  <android::RefBase> = <invalid address>, 
  members of android::camera3::Camera3Stream: 
  mId = 0xa5a5a5a5, 
  mName = {
    mString = 0xa5a5a5a5
  }, 
  mMaxSize = 0xa5a5a5a5, 
  mState = 0xa5a5a5a5, 
  mLock = {
    mMutex = {
      __private = {0xa5a5a5a5}
    }
  }, 
  mStatusTracker = {
    m_ptr = 0xa5a5a5a5, 
    m_refs = 0xa5a5a5a5
  }, 
  mStatusId = 0xa5a5a5a5, 
  mStreamUnpreparable = 0xa5, 
  oldUsage = 0xa5a5a5a5, 
  oldMaxBuffers = 0xa5a5a5a5, 
  mOutputBufferReturnedSignal = {
    mCond = {
      __private = {0xa5a5a5a5}
    }
  }, 
  mInputBufferReturnedSignal = {
    mCond = {
      __private = {0xa5a5a5a5}
    }
  }, 
  static kWaitForBufferDuration = 0xb2d05e00, 
  mBufferListenerList = {
    _vptr.List = 0xa5a5a5a5, 
    mpMiddle = 0xa5a5a5a5
  },
……………………………………………………….
}

可以看到mpMiddle = 0xa5a5a5a5, 这就是Native crash的原因了,因为mpMiddle=0xa5a5a5a5, 所以访问mpMiddle.mpNext的时候就触发了SIGSEGV, 因为0xa5a5a5b1并不在本进程的虚拟地址空间里。

4.9 继续深入分析

通过gdb和corefile, 我们已经确定了问题的直接原因,就是this->mBufferListenerList有问题,下面需要继续查找问题的根本原因了。

通过上面对this的dump,可以发现整个对象全部都被0xa5a5a5a5所覆盖。而类的对象,一般是由new操作符进行的动态内存分配,是从heap上分配的。而问题版本heap的管理是由dlmalloc进行的。下面就需要结合dlmalloc的代码来分析问题原因了。这一部分和dlmalloc这个模块直接相关,分析这个问题就需要对dlmalloc模块比较熟悉才行。但这一部分和本次training无关,这里就不介绍了。具体何以参考546056上的分析。

5. SIGABRT(signal 6)造成的Native Crash分析一例

前面提到过signal 6造成的Native crash分析略有不同,以553743为例

(gdb) bt
#0  tgkill () at bionic/libc/arch-arm/syscalls/tgkill.S:9
#1  0xb6d1ef14 in pthread_kill (t=<optimized out>, sig=268) at bionic/libc/bionic/pthread_kill.cpp:45
#2  0xb6cfb742 in raise (sig=6) at bionic/libc/bionic/raise.cpp:34
#3  0xb6cf891c in __libc_android_abort () at bionic/libc/bionic/abort.cpp:57
#4  0xb6cf64b4 in abort () at bionic/libc/arch-arm/bionic/abort_arm.S:43
#5  0xb4c10a0c in art::Runtime::Abort () at art/runtime/runtime.cc:389
#6  0xb49e412c in art::LogMessage::~LogMessage (this=0xa297eea4, __in_chrg=<optimized out>) at art/runtime/base/logging.cc:226
#7  0xb4b3fd78 in art::JavaVMExt::JniAbort (this=this@entry=0xb4dec140, 
    jni_function_name=jni_function_name@entry=0xb4d27df8 <art::JNI::SetObjectField(_JNIEnv*, _jobject*, _jfieldID*, _jobject*)::__FUNCTION__> "SetObjectField", msg=<optimized out>)
    at art/runtime/java_vm_ext.cc:410
#8  0xb4b40176 in JniAbortV (ap=..., fmt=0xb4d0c120 "fid == null", jni_function_name=0xb4d27df8 <art::JNI::SetObjectField(_JNIEnv*, _jobject*, _jfieldID*, _jobject*)::__FUNCTION__> "SetObjectField", 
    this=0xb4dec140) at art/runtime/java_vm_ext.cc:418
#9  art::JavaVMExt::JniAbortF (this=0xb4dec140, jni_function_name=0xb4d27df8 <art::JNI::SetObjectField(_JNIEnv*, _jobject*, _jfieldID*, _jobject*)::__FUNCTION__> "SetObjectField", 
    fmt=0xb4d0c120 "fid == null") at art/runtime/java_vm_ext.cc:424
#10 0xb4b5af96 in art::JNI::SetObjectField (env=<optimized out>, java_object=0xa297f070, fid=0x0, java_value=<optimized out>) at art/runtime/jni_internal.cc:1239
#11 0xb6eb8b98 in android::nativeGetSensorAtIndex (env=0xafbf7a80, clazz=<optimized out>, sensorManager=<optimized out>, sensor=0xa297f070, index=0)
    at frameworks/base/core/jni/android_hardware_SensorManager.cpp:160
#12 0x735273e2 in ?? ()
#13 0x735273e2 in ?? ()
Backtrace stopped: previous frame identical to this frame (corrupt stack?)

与signal 11/7不同的是,signal 6的debug不是从frame 0开始,而是从调用abort()函数的那个frame开始,以553743为例,就是frame 5

(gdb) f 5

#5  0xb4c10a0c in art::Runtime::Abort () at art/runtime/runtime.cc:389

根据源代码,是Runtime::Abort()调用了abort()函数。

然后根据frame 6, frame 7层层往上,最终可以知道ART中抛出abort()的原因是传入setObjectField()的第二个参数是空。(具体的问题分析,需要对libart.so的源代码有一定程度的了解,不在本次培训的安排中,这里就不详细描述了)

6. GDB的常用命令

6.1 bt

输出当前线程的backtrace

4.5节中输出的bt是简版,里面只包含栈帧号,该栈帧的地址,对应的函数名,输入参数,还有源代码路径

还可以用bt full输出完全版,还会输出局部变量的值

(gdb) bt full
#0  0xb6e60c00 in promote (this=<optimized out>) at system/core/include/utils/RefBase.h:451
No locals.
#1  android::camera3::Camera3Stream::fireBufferListenersLocked (this=this@entry=0xb872cc28, acquired=acquired@entry=false, output=output@entry=true)
    at frameworks/av/services/camera/libcameraservice/device3/Camera3Stream.cpp:546
        listener = {
          m_ptr = 0xb872cc28
        }
        it = <optimized out>
        end = {
          mpNode = 0xa5a5a5a5
        }
        info = {
          mOutput = true, 
          mCrop = {
            <ARect> = {
              left = 0, 
              top = 0, 
              right = 0, 
              bottom = 0
            }, 
            <android::LightFlattenablePod<android::Rect>> = {
              <android::LightFlattenable<android::Rect>> = {<No data fields>}, <No data fields>}, 
            members of android::Rect: 
            static INVALID_RECT = {
              <ARect> = {
                left = 0, 
                top = 0, 
                right = -1, 
                bottom = -1
              }, 
              <android::LightFlattenablePod<android::Rect>> = {
                <android::LightFlattenable<android::Rect>> = {<No data fields>}, <No data fields>}, 
              members of android::Rect: 
              static INVALID_RECT = <same as static member of an already seen type>
            }
          }, 
          mTransform = 0, 
          mScalingMode = 0, 
          mTimestamp = 0, 
          mFrameNumber = 0
        }
#2  0xb6e60d76 in android::camera3::Camera3Stream::returnBuffer (this=0xb872cc28, buffer=..., timestamp=6165339964000) at frameworks/av/services/camera/libcameraservice/device3/Camera3Stream.cpp:471
        ___tracer = {
          mTag = 1024
        }
        __FUNCTION__ = "returnBuffer"
        l = {
          mLock = @0xb872cc7c
        }
        res = 0

6.2 f(rame)

切换当前的栈帧,后面的命令,比如disas, info locals, info args等命令都是针对当前栈帧的

6.3 info r(egister)

显示对应当前栈帧的寄存器内容

6.4 info locals/info args

显示当前栈帧的局部变量和入参的值

(gdb) f 2
#2  0xb6e60d76 in android::camera3::Camera3Stream::returnBuffer (this=0xb872cc28, buffer=..., timestamp=6165339964000) at frameworks/av/services/camera/libcameraservice/device3/Camera3Stream.cpp:471
471         fireBufferListenersLocked(buffer, /*acquired*/false, /*output*/true);
(gdb) info locals
___tracer = {
  mTag = 1024
}
__FUNCTION__ = "returnBuffer"
l = {
  mLock = @0xb872cc7c
}
res = 0
(gdb) info args
this = 0xb872cc28
buffer = @0xb86c8d60: {
  stream = 0xb872cc2c, 
  buffer = 0xb855528c, 
  status = 0, 
  acquire_fence = -1, 
  release_fence = -1
}
timestamp = 6165339964000

6.5 info f(rame) xx

显示某个栈帧对应的信息

(gdb) info f 2

Stack frame at 0xad790ae8:

 pc = 0xb6e60d76 in android::camera3::Camera3Stream::returnBuffer (frameworks/av/services/camera/libcameraservice/device3/Camera3Stream.cpp:471); saved pc 0xb6e5add8

 called by frame at 0xad790b10, caller of frame at 0xad790ac0

 source language c++.

 Arglist at 0xad790ac0, args: this=0xb872cc28, buffer=..., timestamp=6165339964000

 Locals at 0xad790ac0, Previous frame's sp is 0xad790ae8

 Saved registers:

  r4 at 0xad790ad8, r5 at 0xad790adc, r6 at 0xad790ae0, lr at 0xad790ae4

6.6 disas(semble)

6.6.1显示汇编代码

1. disas:

显示当前栈帧PC所在函数的汇编代码

2. disas addr

显示指定地址所在函数的汇编代码

3. disas start,end

将指定地址范围内的内容按照当作指令进行反汇编

这个命令对于函数重定位有时很有帮助

(gdb) f 1
#1  android::camera3::Camera3Stream::fireBufferListenersLocked (this=this@entry=0xb872cc28, acquired=acquired@entry=false, output=output@entry=true)
    at frameworks/av/services/camera/libcameraservice/device3/Camera3Stream.cpp:546
546         sp<Camera3StreamBufferListener> listener = it->promote();
(gdb) disas
Dump of assembler code for function android::camera3::Camera3Stream::fireBufferListenersLocked(camera3_stream_buffer const&, bool, bool):
   0xb6e60bdc <+0>: stmdb sp!, {r4, r5, r6, r7, r8, r9, lr}
   0xb6e60be0 <+4>: sub sp, #60 ; 0x3c
   0xb6e60be2 <+6>: mov r4, r0
   0xb6e60be4 <+8>: add r5, sp, #8
   0xb6e60be6 <+10>: mov r6, r3
   0xb6e60be8 <+12>: mov r8, r2
   0xb6e60bea <+14>: mov r0, r5
   0xb6e60bec <+16>: movs r1, #0
   0xb6e60bee <+18>: movs r2, #48 ; 0x30
   0xb6e60bf0 <+20>: mov.w r9, #0
   0xb6e60bf4 <+24>: blx 0xb6e2f224
   0xb6e60bf8 <+28>: strb.w r6, [sp, #8]
   0xb6e60bfc <+32>: add r7, sp, #4
   0xb6e60bfe <+34>: ldr r6, [r4, #124] ; 0x7c
=> 0xb6e60c00 <+36>: ldr r4, [r6, #12]
红色的一行是调用子函数,但这里不知道调用的是哪个函数
(gdb) disas 0xb6e2f224
No function contains specified address.
(gdb) disas 0xb6e2f224,0xb6e2f234
Dump of assembler code from 0xb6e2f224 to 0xb6e2f234:
   0xb6e2f224: add r12, pc, #0, 12
   0xb6e2f228: add r12, r12, #348160 ; 0x55000
   0xb6e2f22c: ldr pc, [r12, #724]! ; 0x2d4
   0xb6e2f230: add r12, pc, #0, 12
End of assembler dump.
(gdb) x 0xb6e2f22c + 0x55000 + 0x2d4
0xb6e84500: 0xb69ffb88
(gdb) disas 0xb69ffb88
Dump of assembler code for function memset:
   0xb69ffb48 <+0>: stmfd sp!, {r0}
   0xb69ffb4c <+4>: cmp r2, #16
   0xb69ffb50 <+8>: bcc 0xb69ffc28 <memset+224>
   0xb69ffb54 <+12>: mov r3, r0

4. disas /m

如果设定了源代码路径,该命令将汇编代码和源代码混合显示(但要注意,由于编译器的优化,这个命令的显示有时不准确,具体的对应还是需要根据汇编代码来推敲,这里的信息仅供参考)

(gdb) disas/m
Dump of assembler code for function android::camera3::Camera3Stream::returnBuffer(camera3_stream_buffer const&, long long):
457         nsecs_t timestamp) {
   0xb6e60d30 <+0>: push {r4, r5, r6, lr}
   0xb6e60d32 <+2>: sub sp, #24
   0xb6e60d34 <+4>: mov r4, r0
   0xb6e60d36 <+6>: mov r6, r1
   0xb6e60d44 <+20>: strd r2, r3, [sp]

458     ATRACE_CALL();
459     Mutex::Autolock l(mLock);
   0xb6e60d4e <+30>: add.w r0, r4, #84 ; 0x54
   0xb6e60d7e <+78>: add r0, sp, #12
   0xb6e60d80 <+80>: bl 0xb6e349bc <android::Mutex::Autolock::~Autolock()>

6.7 x /FMT address

显示指定地址的内容

/FMT包括三部分,第一部分是个数字,是个count, 每个count的大小由第二部分确定;第二部分是单位定义(b: byte, h: half-work, w: word, g(giant, 8bytes); 第三部分是显示格式,就是把对应的数据按照什么格式显示(如: o:八进制显示; x: 16进制; d: 十进制;s: 字符串; i:指令等等)

(gdb) p this
$5 = (android::camera3::Camera3Stream * const) 0xb872cc28
(gdb) x/8wx 0xb872cc28
0xb872cc28: 0xa5a5a5a5 0xa5a5a5a5 0xa5a5a5a5 0xa5a5a5a5
0xb872cc38: 0xa5a5a5a5 0xa5a5a5a5 0xa5a5a5a5 0xa5a5a5a5
(gdb) x/8wi 0xb872cc28
   0xb872cc28: add r5, pc, #660 ; (adr r5, 0xb872cec0)
   0xb872cc2a: add r5, pc, #660 ; (adr r5, 0xb872cec0)
   0xb872cc2c: add r5, pc, #660 ; (adr r5, 0xb872cec4)
   0xb872cc2e: add r5, pc, #660 ; (adr r5, 0xb872cec4)
   0xb872cc30: add r5, pc, #660 ; (adr r5, 0xb872cec8)
   0xb872cc32: add r5, pc, #660 ; (adr r5, 0xb872cec8)
   0xb872cc34: add r5, pc, #660 ; (adr r5, 0xb872cecc)
   0xb872cc36: add r5, pc, #660 ; (adr r5, 0xb872cecc)
(gdb) x/8wf 0xb872cc28
0xb872cc28: -2.87351825e-16 -2.87351825e-16 -2.87351825e-16 -2.87351825e-16
0xb872cc38: -2.87351825e-16 -2.87351825e-16 -2.87351825e-16 -2.87351825e-16

第一部分省略,count就是1,第二第三部分省略,就沿用上一次的

一个技巧,address可以是简单的表达式:

(gdb) x/8wx 0xb872cc28 + 16

0xb872cc38: 0xa5a5a5a5 0xa5a5a5a5 0xa5a5a5a5 0xa5a5a5a5

0xb872cc48: 0xa5a5a5a5 0xa5a5a5a5 0xa5a5a5a5 0xa5a5a5a5

6.8 p(print)

显示指定表达式的结果,这个命令可以派生出很多用法

进制转换:

(gdb) p/x 12345678

$6 = 0xbc614e

(gdb) p 0xbc614e

$7 = 12345678

(gdb) p/o 12345678

$8 = 057060516

计算器:

(gdb) p 12345678 + (0xbc614e*2)

$11 = 37037034

(gdb) p/f 1.1+2.2

$12 = 3.2999999999999994

显示变量内容:

(gdb) p this

$13 = (android::camera3::Camera3Stream * const) 0xb872cc28

(gdb) p/x *this

$14 = {

   = {

    stream_type = 0xa5a5a5a5, 

    width = 0xa5a5a5a5, 

    height = 0xa5a5a5a5, 

    format = 0xa5a5a5a5, 

    usage = 0xa5a5a5a5, 

    max_buffers = 0xa5a5a5a5, 

    priv = 0xa5a5a5a5, 

    data_space = 0xa5a5a5a5, 

    rotation = 0xa5a5a5a5, 

    reserved = {0xa5a5a5a5, 0xa5a5a5a5, 0xa5a5a5a5, 0xa5a5a5a5, 0xa5a5a5a5, 0xa5a5a5a5, 0xa5a5a5a5}

  },

}

显示指定数据结构或者变量的大小:

(gdb) p sizeof(struct malloc_state)

$25 = 476

(gdb) p sizeof(_gm_)

$26 = 476

强制类型转换:

(gdb) p (android::camera3::Camera3Stream * const)0xb872cc28

$15 = (android::camera3::Camera3Stream * const) 0xb872cc28

(gdb) p ((android::camera3::Camera3Stream * const)0xb872cc28)->mBufferListenerList

$17 = {

  _vptr.List = 0xa5a5a5a5, 

  mpMiddle = 0xa5a5a5a5

}

(gdb) p *((android::camera3::Camera3Stream * const)0xb872cc28)

$16 = {

   = {

    stream_type = -1515870811, 

    width = 2779096485, 

    height = 2779096485, 

    format = -1515870811, 

    usage = 2779096485, 

    max_buffers = 2779096485, 

    priv = 0xa5a5a5a5, 

    data_space = 2779096485, 

    rotation = -1515870811, 

    reserved = {0xa5a5a5a5, 0xa5a5a5a5, 0xa5a5a5a5, 0xa5a5a5a5, 0xa5a5a5a5, 0xa5a5a5a5, 0xa5a5a5a5}

  },

}

有时在进行C++ class的强制类型转换时可能会报错,此时可以试一试将其中的一段用’括起来;

(gdb) p (('android::camera3'::Camera3Stream * const)0xb872cc28)

$22 = (android::camera3::Camera3Stream * const) 0xb872cc28

6.9 ptype

输出指定的数据结构,后面可以是结构名,也可以是变量名

(gdb) ptype _gm_

type = struct malloc_state {

    binmap_t smallmap;

    binmap_t treemap;

size_t dvsize;

}

(gdb) ptype struct malloc_state

type = struct malloc_state {

    binmap_t smallmap;

    binmap_t treemap;

size_t dvsize;

}

6.10 info thread

列出当前进程的所有线程

(gdb) info thread

  Id   Target Id         Frame 

  53   LWP 7071          chdir () at bionic/libc/arch-arm/syscalls/chdir.S:9

  52   LWP 266           chdir () at bionic/libc/arch-arm/syscalls/chdir.S:9

………………………………….

  3    LWP 1289          __memcpy_base () at bionic/libc/arch-arm/cortex-a15/bionic/memcpy_base.S:92

  2    LWP 10032         __memcpy_base () at bionic/libc/arch-arm/cortex-a15/bionic/memcpy_base.S:92

* 1    LWP 10027         0xb6e60c00 in promote (this=) at system/core/include/utils/RefBase.h:451

6.11 thread xx

切换的debug的线程,xx是线程编号,不是线程号,就是info thread里每一行的第一个数字

(gdb) thread 29

[Switching to thread 29 (LWP 1295)]

#0  mount () at bionic/libc/arch-arm/syscalls/mount.S:19

19     bxls    lr

(gdb) bt

#0  mount () at bionic/libc/arch-arm/syscalls/mount.S:19

#1  0xb0ea1670 in read (count=512, buf=0xb0eaf5ff, fd=17) at bionic/libc/include/unistd.h:328

#2  vbc_ctl_modem_monitor_routine (arg=0xb0c2b6f0) at vendor/sprd/modules/audio/normal/vb_control_parameters.c:2302

#3  0xb6a2a554 in __init_alternate_signal_stack (thread=0x11) at bionic/libc/bionic/pthread_create.cpp:70

#4  0xb6a02276 in clone (fn=0x78, child_stack=, flags=17, arg=0x0) at bionic/libc/bionic/clone.cpp:57

#5  0x00000000 in ?? ()

6.12 dump

将指定内存块的内容输出到一个文件里

(gdb) p _gm_

$23 = {

  smallmap = 4294967292, 

  treemap = 660543, 

  dvsize = 416, 

  topsize = 8952, 

  least_addr = 0xa9b76000, 

  dv = 0xb86c8d90, 

  top = 0xb8a8bce0, 

  trim_check = 2097152, 

  release_checks = 182, 

  magic = 464186520, 

  smallbins = {0x0, 0x0, 0xb6a4dee0 <_gm_+40>, 0xb6a4dee0 <_gm_+40>, 0xb6a4dee8 <_gm_+48>, 0xb6a4dee8 <_gm_+48>, 0xb80b4188, 0xb7a4e370, 0xb87fc7e8, 0xb7a2a660, 0xb8903168, 0xb7a227a0, 0xb7e9cba0, 

    0xb7b34190, 0xb87c59c8, 0xb7a28480, 0xb8059278, 0xb7a0d3f0, 0xb85d0498, 0xb7a52c50, 0xb8362058, 0xb7a46e70, 0xb86dd430, 0xb7a3b490, 0xb8064460, 0xb7a93578, 0xb86eb130, 0xb7a23128, 0xb8755490, 

    0xb7a18c88, 0xb7e7bea8, 0xb7a2bd98, 0xb854e120, 0xb7a2ae10, 0xb7f80748, 0xb7a71f10, 0xb8094b30, 0xb7a2b2b0, 0xb7a70f70, 0xb7a1c860, 0xb8501098, 0xb7b2e410, 0xb8697b90, 0xb7a8f820, 0xb8503f00, 

    0xb7a7fb38, 0xb8452d10, 0xb7b32940, 0xb7aa3de0, 0xb7a4fc88, 0xb8798f00, 0xb7a4e098, 0xb7b89e60, 0xb7a3bdb0, 0xb88d93b8, 0xb7a17ca8, 0xb872cd20, 0xb7a3b898, 0xb8900c78, 0xb7a31130, 0xb7b96070, 

    0xb81b72e0, 0xb875a170, 0xb7b920b8, 0xb8908f68, 0xb8908f68}, 

  treebins = {0xb85cde88, 0xb7eb4f40, 0xb826aa38, 0xb82787e0, 0xb7e225a8, 0xb7ebf638, 0x0, 0x0, 0x0, 0x0, 0xb8807108, 0x0, 0xb8801bf8, 0x0, 0x0, 0x0, 0x0, 0xb8a71b28, 0x0, 0xb888f990, 

    0x0 }, 

  footprint = 27037696, 

  max_footprint = 66985984, 

  footprint_limit = 0, 

  mflags = 3, 

  mutex = {

    __private = {0}

  }, 

  seg = {

    base = 0xb7a0d000 "", 

    size = 17305600, 

    next = 0x0, 

    sflags = 0

  }, 

  extp = 0x0, 

  exts = 0

}

(gdb) p/x 0xb7a0d000 + 17305600

$24 = 0xb8a8e000

(gdb) dump memory heap.bin 0xb7a0d000 0xb8a8e000

6.13 set logging:

有三个命令:

Set logging file : 设置gdb的log file 的名称

Set logging on: gdb的命令输出将会保存到log file中去

Set logging off: gdb的命令停止输出到log file中去

crash发生在49行,因为entry->prev = 0, 所以在进行list_del()操作的时候因为访问0地址从而触发了kernel crash。

从上面的函数来看,这里就是把指定的timer从timer_list里删除,但是对应的链表被破坏了,导致问题的出现。

2. 深入分析

根据堆栈信息,我们最终推导出出问题的timer_list结构,其地址是存放于r6(0xc143a02c)

这个timer_list, 其处理函数是delayed_work_timer_fn, 其tvec_base是0xef2bc000(低两位用作flag, 所以忽略之)。

另外可以用timer命令查看系统中目前存在的timer_list。

这是cpu0上的。

这是cpu1上的。

前面我们看到当前正在处理的这个timer_list, 其tvec_base是ef2bc000, 但是根据OOPS信息,正在处理这个timer_list的是cpu0, 而不是cpu1。这是一个很大的问题,这意味着当前处理的这个timer_list(0xc143a02c)同时挂在cpu0和cpu1上。而cpu0和cpu1的处理是彼此独立的。那么就有可能cpu0正在处理这个timer_list的时候, cpu1也要处理,最终导致些奇怪的问题,链表断链可能只是其中的一个问题。

那么下一步需要查找这个timer_list同时挂在两个cpu上的原因了。

delayed_work_timer_fn这个处理函数其实不是真正的处理函数,是delayed work的公用处理函数,所有的delayed work都是用这个函数作为定时器处理函数的。但是每个work还有真正私有的处理函数,是被delayed_work_timer_fn所调用的。

这个__data才是存放每个delayed workqueue的私有数据的地方。前面已知data是0xc143a018。

真正的处理函数是vmstat_update。

事实上,类似的问题之前已经报过几例了,同样都是有timer_list同时挂在两个cpu上,它们的处理函数都是vmstat_update, 这应该不是巧合,所以需要仔细分析一下使用vmstat_update的地方。

首先看一下与vmstat_update相关的几个函数:

实现并不复杂,在setup_cpu_timer()的时候创建一个delayed work, 并紧接着调用start_cpu_timer()开始调度这个delayed_work, 同时在vmstat_off_cpu中清除本cpu的bit,表示本cpu目前vmstat是on状态。

在vmstat_update()中根据refresh_cpu_vm_stats()的结果决定是否再次调度vmstat_update。如果决定不再调度,那么在vmstat_off_cpu中设置本cpu的bit, 表示本cpu目前vmstat是off状态。

在vmstat_cpuup_callback()中根据cpu hotplug的状态,进行相应的操作:

在cpu online时,调用setup_cpu_timer()开始调度vmstat_update().

在cpu down之前(down_prepare), 去取消vmstat_update, 前提是vmstat_off_cpu中该cpu的vmstat处于on状态。

在cpu down fail时,再次调用setup_cpu_timer(cpu)去调度vmstat_update。

如果单看上面的代码,似乎没有问题。但是会操作vmstat_update这个delayed work的还有一处:

在vmstat模块初始化的时候还会初始化一个delayed work, 其处理函数是vmstat_update_monitor(). 这个delayed work会周而复始的执行,除非关机,否则不会停止调度。

Vmstat_update_monitor()会不停地检测vmstat_off_cpu和cpu_online_mask, 如果一个cpu处于online状态且vmstat_off_cpu中对应cpu的vmstat状态是on, 那么vmstat_update_monitor()会调用start_cpu_timer()重新调度vmstat_update。

由于vmstat_update_monitor()可能会重启vmstat_update, 其重启条件中有cpu_online_mask, 使得原来简单的处理变得复杂了,我们需要看一看cpu_online_mask在哪些地方会被修改。

Cpu_online_mask反映的是cpu_online_bits的变化,而cpu_online_bits是通过上面这个函数set_cpu_online()改变的。

查看代码,在系统启动结束,非panic的情况下,一共有三处调用此函数:

cpu从offline变为online调用的第一个函数,所以这里调用set_cpu_online(true)把cpu设为online状态。

cpu收到别的cpu发来的IPI_STOP请求时会stop自身,所以这里调用set_cpu_online(false)把cpu设为offline状态。

这是正常情况下cpu offline时会调用的函数,其调用过程如下:

这里调用__cpu_disable(),设置cpu为offline, 然后发出CPU_DYING的notification。

这里的调用很关键,首先发出CPU_DOWN_PREPARE通知信息,然后会调用到take_cpu_down(), 其中将cpu状态改为offline。最后会发出CPU_DEAD通知。

这个过程本身没有问题,但是请注意系统从发出CPU_DOWN_PREPARE到cpu_online_mask真正变化之间是有一段时间的; 前面也说到,在vmstat模块收到CPU_DOWN_PREPARE时,如果vmstat_update这个delayed work在活动,那么会取消这个delayed work;另外还有一个一直不停运行的vmstat_update_monitor()会根据vmstat_off_cpus和cpu_online_mask决定是否需要重启vmstat_update。这三个条件加在一起,就可能触发本bug。

具体来说,就是下面这样的流程:

1. Cpu1 online, vmstat_update启动。

2. Vmstat_update()中发现暂时没有vm活动,所以不再重启vmstat_update, 同时在vmstat_off_cpus里把cpu1标记上。

3. 此时cpu1开始offline操作, CPU_DOWN_PREPARE被发出。vmstat模块收到通知,但由于vmstat_update不在运行状态,所以什么也不做。

4. 此时vmstat_update_monitor()的定时时间到,由于vmstat_off_cpus中cpu1被标记(表示vmstat_update没有运行), 同时cpu_online_mask中cpu1仍处于online状态,假设这段时间中有过vm操作,那么就会重启vmstat_update。

5. 由于CPU_DOWN_PREPARE在第三步中已经发过了,所以此时vmstat_update不可能再被取消了。

6. CPU_DEAD通知发出, timer模块会收到这个通知,然后调用migrate_timers()函数将cpu1上还在运行的timer迁移到别的cpu上去,其中就包括vmstat_update所对应的timer。

7. 如果cpu1很快又online了,那么在cpu1启动过程中,vmstat模块又可能重新启动vmstat_update, 这时就出现了问题,vmstat_update所对应的timer之前已经被迁移到cpu0上了,而这里又挂在了cpu1上, 就是说此时vmstat_update所对应的timer同时挂在了cpu0和cpu1上,其中没有任何同步机制。当cpu0和cpu1分别操作这个timer的时候,就可能出现各种异常,包括timer链的断裂。

根据上面的描述,可以认为这个问题主要是由vmstat_update_monitor()这个周期性运行的task引入的。原本vmstat_update的启动和取消很简单,就是根据收到的cpu状态通知来进行,引入vmstat_update_monitor()之后可能出现对这个cpu才有意义的vmstat_update在cpu开始offline之后还被重启,而且挂到了不相关的别的cpu上去了,造成问题出现。

2.1 寻找解决问题的办法

首先通过git blame命令可以查到vmstat_update_monitor()函数是由下面这个commit引入的:

Vmstat是内核公共模块没有修改过,所以第一时间去linux社区查找,但奇怪的是linux社区里的代码,哪一个版本找不到这个提交,无论是3.10还是3.18,甚至4.x,对应的vmstat_update_monitor()这个函数也确实不存在。

使用gitk查看历史,发现它们是从linux_linaro_lsk这个分支merge过来的,总共有4个commit。linaro这个分析不是linux社区维护的分支,所以在linux社区里找不到。

从commit本身情况说,只需要revert这一个commit就好,但是负责同事决定revert所有的四个改动,最终确认,在revert之后,问题确实不再发生,说明前面的分析是正确的。

 

  • 30
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

塞外图腾狼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值