明白了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 该指针时,由于分配和释放时的指针位置不同发生了异常。
我们在代码设计时,要尽量避免同类的使用,如果实在无法避免,应该做好注释。