Linux程序崩溃调试技术(3)

明白了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 该指针时,由于分配和释放时的指针位置不同发生了异常。

我们在代码设计时,要尽量避免同类的使用,如果实在无法避免,应该做好注释。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wyzjgw.html