Linux 程序崩溃调试技术
一,起因
在开发android的ril过程中,遇到了rild异常崩溃的现象。该进程直接控制android RIL相关的所有操作,如果异常终止,将导致android framework的重启。
二,细节
a) 众所周知,linux的程序崩溃时,都会打印出崩溃前的stack trace。该stack trace是我们寻找崩溃原因的重要线索。
b) 以下是android rild的崩溃细节
01-19 17:48:56.550 I/DEBUG ( 683): Build fingerprint: 'augusta/M70P/m70p/miracle_smt:2.2/M70P-daily/20120119:user/test-keys'
01-19 17:48:56.550 I/DEBUG ( 683): pid: 684, tid: 715 >>> /system/bin/rild <<<
01-19 17:48:56.550 I/DEBUG ( 683): signal 11 (SIGSEGV), fault addr deadbaad
01-19 17:48:56.550 I/DEBUG ( 683): r0 00000000 r1 0000000c r2 00000027 r3 00000000
01-19 17:48:56.550 I/DEBUG ( 683): r4 afd40328 r5 deadbaad r6 00001728 r7 00000000
01-19 17:48:56.550 I/DEBUG ( 683): r8 00100000 r9 ae60532d 10 10000000 fp 00000000
01-19 17:48:56.550 I/DEBUG ( 683): ip ffffffff sp 100ffcf0 lr afd154e1 pc afd11dee cpsr 00000030
01-19 17:48:56.560 W/InputManagerService( 887): Window already focused, ignoring focus gain of: com.android.internal.view.IInputMethodClient$Stub$Proxy@45ffbd88
01-19 17:48:56.580 I/DEBUG ( 683): #00 pc 00011dee /system/lib/libc.so
01-19 17:48:56.580 I/DEBUG ( 683): #01 pc 0000be1c /system/lib/libc.so
01-19 17:48:56.580 I/DEBUG ( 683):
01-19 17:48:56.580 I/DEBUG ( 683): code around pc:
01-19 17:48:56.580 I/DEBUG ( 683): afd11dcc 2d00d10d 1c2bd00b 2d00682d e026d1fb
01-19 17:48:56.580 I/DEBUG ( 683): afd11ddc 2b0068db 4e17d003 51a02001 4d164798
01-19 17:48:56.580 I/DEBUG ( 683): afd11dec 702a2227 edfef7fb f7fc2106 2380ef1c
01-19 17:48:56.580 I/DEBUG ( 683): afd11dfc aa010559 60912400 1c116054 94012006
01-19 17:48:56.580 I/DEBUG ( 683): afd11e0c eaa0f7fc 2200a905 f7fc2002 f7fbeaac
01-19 17:48:56.580 I/DEBUG ( 683):
01-19 17:48:56.580 I/DEBUG ( 683): code around lr:
01-19 17:48:56.580 I/DEBUG ( 683): afd154c0 447b4a0d 589cb083 26009001 686768a5
01-19 17:48:56.580 I/DEBUG ( 683): afd154d0 220ce008 2b005eab 1c28d003 47889901
01-19 17:48:56.590 I/DEBUG ( 683): afd154e0 35544306 d5f43f01 2c006824 b003d1ee
01-19 17:48:56.590 I/DEBUG ( 683): afd154f0 bdf01c30 0002ae62 000000d4 1c0fb5f0
01-19 17:48:56.590 I/DEBUG ( 683): afd15500 43551c3d a904b087 1c16ac01 604d9004
01-19 17:48:56.590 I/DEBUG ( 683):
01-19 17:48:56.590 I/DEBUG ( 683): stack:
01-19 17:48:56.590 I/DEBUG ( 683): 100ffcb0 00000718
01-19 17:48:56.590 I/DEBUG ( 683): 100ffcb4 afd1455b /system/lib/libc.so
01-19 17:48:56.590 I/DEBUG ( 683): 100ffcb8 afd405a0 /system/lib/libc.so
01-19 17:48:56.590 I/DEBUG ( 683): 100ffcbc afd4054c /system/lib/libc.so
01-19 17:48:56.590 I/DEBUG ( 683): 100ffcc0 00000000
01-19 17:48:56.590 I/DEBUG ( 683): 100ffcc4 afd154e1 /system/lib/libc.so
01-19 17:48:56.590 I/DEBUG ( 683): 100ffcc8 00000071
01-19 17:48:56.590 I/DEBUG ( 683): 100ffccc afd1452d /system/lib/libc.so
01-19 17:48:56.590 I/DEBUG ( 683): 100ffcd0 00000000
01-19 17:48:56.590 I/DEBUG ( 683): 100ffcd4 afd40328 /system/lib/libc.so
01-19 17:48:56.590 I/DEBUG ( 683): 100ffcd8 00000000
01-19 17:48:56.590 I/DEBUG ( 683): 100ffcdc 00001728
01-19 17:48:56.590 I/DEBUG ( 683): 100ffce0 00000000
01-19 17:48:56.590 I/DEBUG ( 683): 100ffce4 afd147cb /system/lib/libc.so
01-19 17:48:56.590 I/DEBUG ( 683): 100ffce8 df002777
01-19 17:48:56.590 I/DEBUG ( 683): 100ffcec e3a070ad
01-19 17:48:56.590 I/DEBUG ( 683): #00 100ffcf0 0000b4e8 [heap]
01-19 17:48:56.590 I/DEBUG ( 683): 100ffcf4 c0000000
01-19 17:48:56.590 I/DEBUG ( 683): 100ffcf8 afd418dc /system/lib/libc.so
01-19 17:48:56.590 I/DEBUG ( 683): 100ffcfc afd10538 /system/lib/libc.so
01-19 17:48:56.590 I/DEBUG ( 683): 100ffd00 afd40328 /system/lib/libc.so
01-19 17:48:56.590 I/DEBUG ( 683): 100ffd04 fffffbdf
01-19 17:48:56.590 I/DEBUG ( 683): 100ffd08 afd40328 /system/lib/libc.so
01-19 17:48:56.590 I/DEBUG ( 683): 100ffd0c afd41724 /system/lib/libc.so
01-19 17:48:56.590 I/DEBUG ( 683): 100ffd10 0000b000 [heap]
01-19 17:48:56.590 I/DEBUG ( 683): 100ffd14 afd0be21 /system/lib/libc.so
01-19 17:48:56.600 I/DEBUG ( 683): #01 100ffd18 afd40328 /system/lib/libc.so
01-19 17:48:56.600 I/DEBUG ( 683): 100ffd1c afd0be21 /system/lib/libc.so
01-19 17:48:56.600 I/DEBUG ( 683): 100ffd20 00000002
01-19 17:48:56.600 I/DEBUG ( 683): 100ffd24 0000b4ee [heap]
01-19 17:48:56.600 I/DEBUG ( 683): 100ffd28 0000c574 [heap]
01-19 17:48:56.600 I/DEBUG ( 683): 100ffd2c 0000b4e8 [heap]
01-19 17:48:56.600 I/DEBUG ( 683): 100ffd30 0000c574 [heap]
01-19 17:48:56.600 I/DEBUG ( 683): 100ffd34 00000006
01-19 17:48:56.600 I/DEBUG ( 683): 100ffd38 000013fc
01-19 17:48:56.600 I/DEBUG ( 683): 100ffd3c af9059ff /system/lib/libcutils.so
01-19 17:48:56.600 I/DEBUG ( 683): 100ffd40 0000b4e8 [heap]
01-19 17:48:56.600 I/DEBUG ( 683): 100ffd44 800056b5 /system/lib/libaugusta-ril.so
01-19 17:48:56.600 I/DEBUG ( 683): 100ffd48 100ffd6c
01-19 17:48:56.600 I/DEBUG ( 683): 100ffd4c 0000b438 [heap]
01-19 17:48:56.600 I/DEBUG ( 683): 100ffd50 ae6089bc /system/lib/libril.so
01-19 17:48:56.600 I/DEBUG ( 683): 100ffd54 afd0cd81 /system/lib/libc.so
01-19 17:48:56.600 I/DEBUG ( 683): 100ffd58 800056b5 /system/lib/libaugusta-ril.so
01-19 17:48:56.600 I/DEBUG ( 683): 100ffd5c ae6042a3 /system/lib/libril.so
三,分析
幸运的话,我们可以在这个log的前3行,知道崩溃的原因。但更多细节需要我们自己去查。
堆栈记录:
01-19 17:48:56.590 I/DEBUG ( 683): stack: |
寄存器:
01-19 17:48:56.550 I/DEBUG ( 683): r0 00000000 r1 0000000c r2 00000027 r3 00000000 |
PC在模块中的offset:
01-19 17:48:56.580 I/DEBUG ( 683): #00 pc 00011dee /system/lib/libc.so |
PC附近的机器码:
01-19 17:48:56.580 I/DEBUG ( 683): code around pc: |
LR附近的机器码:
01-19 17:48:56.580 I/DEBUG ( 683): code around lr: |
以上几个部分,是log的组成。以下解释每个部分的作用。
堆栈记录:
1. 堆栈在程序中的作用
a) 传递函数参数
b) 记录函数的返回地址
c) 暂存数据
2. 从堆栈记录里我们能看到函数之间的调用过程。
3. 从以上的log上,我们看到libril 调用了libaugusta-ril中的函数,接着又调用了其他模块的函数。
寄存器:
1. 寄存器是计算机运算的基础单元,熟悉汇编语言的人对寄存器都不陌生。寄存器的一部分是通用寄存器,另外一些是专用的寄存器,有自己特定的功能。关于寄存器的具体的内容这里不具体讲述。
2. PC是寄存器中重要的寄存器。保持了当前运行程序的内存空间中正在执行的代码的位置。此例中pc的内容为afd11dee,表示当程序执行到了afd11dee,出现了问题。
注意,此处显示的值,是真实的程序内存空间的值,不是模块内的偏移。
3. LR是类似PC的一个寄存器,保持的是程序跳转时PC的值。看过linux内核起始引导汇编程序的人,应该明白lr的作用。
机器码: 个人感觉用途不是很大,可以辅助定位。
四,动手
明白了log各个部分的作用,现在要寻找导致崩溃的真正原因。
Linux程序在运行时,会将所有用到的模块加载到内存,所有的段分布到统一的虚拟内存空间中。从以上的堆栈的log上,我们能看到程序的调用过程,其中的地址都是内存空间的虚拟地址。我们只知道该位置位于哪个模块,却不知道具体的哪个函数出了问题。但地址又确确实实对应了一个函数,只有知道了模块在内存中的分布情况,才能找到对应偏移位置的函数。
1. 查看内存分布
我们的进程名字叫 rild。使用命令ps –ef ,得到该进程的pid,假设为702。在/proc下,有每个进程对应的运行态信息。使用命令 cat /proc/702/maps ,得到给进程的内存分布。
00008000-0000a000 r-xp 00000000 00:0e 718 /system/bin/rild 0000a000-0000b000 rw-p 00002000 00:0e 718 /system/bin/rild 0000b000-0000e000 rw-p 00000000 00:00 0 [heap] 80000000-8000b000 r-xp 00000000 00:0e 311 /system/lib/libaugusta-ril.so 8000b000-8000c000 rw-p 0000b000 00:0e 311 /system/lib/libaugusta-ril.so a7e00000-a7e04000 r-xp 00000000 00:0e 240 /system/lib/libhardware_legacy.so a7e04000-a7e05000 rw-p 00004000 00:0e 240 /system/lib/libhardware_legacy.so a8100000-a8127000 r-xp 00000000 00:0e 236 /system/lib/libutils.so a8127000-a8128000 rw-p 00027000 00:0e 236 /system/lib/libutils.so a8200000-a821f000 r-xp 00000000 00:0e 269 /system/lib/libbinder.so a821f000-a8225000 rw-p 0001f000 00:0e 269 /system/lib/libbinder.so ae300000-ae304000 r-xp 00000000 00:0e 297 /system/lib/libnetutils.so ae304000-ae305000 rw-p 00004000 00:0e 297 /system/lib/libnetutils.so ae400000-ae402000 r-xp 00000000 00:0e 233 /system/lib/libwpa_client.so ae402000-ae403000 rw-p 00002000 00:0e 233 /system/lib/libwpa_client.so ae600000-ae608000 r-xp 00000000 00:0e 349 /system/lib/libril.so ae608000-ae609000 rw-p 00008000 00:0e 349 /system/lib/libril.so af700000-af714000 r-xp 00000000 00:0e 326 /system/lib/libz.so af714000-af715000 rw-p 00014000 00:0e 326 /system/lib/libz.so af900000-af90e000 r-xp 00000000 00:0e 264 /system/lib/libcutils.so af90e000-af90f000 rw-p 0000e000 00:0e 264 /system/lib/libcutils.so afa00000-afa03000 r-xp 00000000 00:0e 261 /system/lib/liblog.so afa03000-afa04000 rw-p 00003000 00:0e 261 /system/lib/liblog.so afb00000-afb20000 r-xp 00000000 00:0e 356 /system/lib/libm.so afb20000-afb21000 rw-p 00020000 00:0e 356 /system/lib/libm.so afc00000-afc01000 r-xp 00000000 00:0e 323 /system/lib/libstdc++.so afc01000-afc02000 rw-p 00001000 00:0e 323 /system/lib/libstdc++.so afd00000-afd40000 r-xp 00000000 00:0e 332 /system/lib/libc.so afd40000-afd43000 rw-p 00040000 00:0e 332 /system/lib/libc.so b0001000-b0011000 r-xp 00001000 00:0e 735 /system/bin/linker b0011000-b0012000 rw-p 00011000 00:0e 735 /system/bin/linker bee51000-bee53000 rw-p 00000000 00:00 0 [stack] |
2. 具体位置
注意,以上的内存信息,与 stack trace的log是两台机器上取得,所以会有偏差。
从以上的maps中可以看到每个模块在虚拟空间中分布情况。
有了模块的分布位置,我们就可以定位模块里的函数。
从 stack 的log里,发现了800056b5 /system/lib/libaugusta-ril.so,从maps里发现libaugusta-ril.so的起始位置为80000000, 可以很容易的知道该函数位于该模块的00056b5处。
到此,我们已经学会了,定位模块里函数位置的方法了,下一步是获得函数名。
五,寻找
当系统在编译程序时,会分成几个步骤。最后的步骤是strip,就是把生成的模块中不必要的符号和调试信息去掉,以减少体积和加载时间。所以,如果直接使用最终的so,我们是得不到想要的结果的,我们需要的是没有strip过的模块。
在android的编译过程中,生成的中间文件都会放置在out目录的obj下,在obj目录下,对应每个模块都有自己的文件夹。在模块文件夹中,有LINKED目录,里面保持的是没有strip过的结果。这个文件正是我们需要的文件。
六,Dump和反编译
任何一个编译工具都会提供一个dump的工具,用于了解生成结果的内部信息。
使用dump,我们可以知道生成程序的段的信息,符号表信息,甚至可以反汇编。
这里我们使用 arm-eabi-objdump –dx libaugusta-ril.so > dumplog,来进行反汇编。
从dumplog文件中搜索56b5,我们神奇的发现,该位置对应的函数是 onRequest。
按照上面的办法,我们逐步分析,就可以知道出问题时的函数调用路径,可以清晰的知道问题发生的原因。
七,本例的启示
以上的log信息,是一个比较特殊的操作引起的,也是大家很容易发生的错误。这里在具体描述一下。
最终的错误定位在 libc.so的 free函数上,通过这个线索,按照函数的调用路径重新查看了一下代码,发现了问题的所在。
错误的代码模型如下:
struct a { char * b;};
void func2(struct a * s) { s.b += 1;}
void func1() { struct a st; st.b = malloc(100); func2(&st); free(st.b); } |
st的地址作为 指针参数传递给 func2,但是在func2中对成员b进行了修改,并且生效,最后的结果是func1在free 该指针时,由于分配和释放时的指针位置不同发生了异常。
我们在代码设计时,要尽量避免同类的使用,如果实在无法避免,应该做好注释。