拿到死机backtrace堆栈后如何确认死在哪一行源码(ARM+Android平台反汇编分析举例)

目录

Android上如何用debuggerd拿到死机堆栈

拿到死机堆栈后如何分析

分析backtrace文件

反汇编分析.so文件

反汇编分析.o文件

相关附件


Android上如何用debuggerd拿到死机堆栈

关于debuggerd的原理,在这里就不赘述了。

需要注意两点:

1,确保要调试的进程中没有重写信号处理函数。

在我们的中间件中,libqin_buslib.so中,重写了信号处理函数,这样会覆盖系统默认的信号处理函数,导致debuggerd无法捕获信号。

修改方法:

common_lib/buslib/src/bus_main.c,将bus_ctrl_init()函数内的如下一行代码注释掉:

bus_ctrl_init() {

...

//bus_ctrl_ShutdownInit();

... }

然后重新编译common_lib仓库,将新的libqin_buslib.so放入机顶盒内的相关路径中。

 

2,确保 /data/tombstones/目录已存在。

如果该目录不存在,那么debuggred无法将堆栈写入tombstone文件。

必要时手动创建:

mkdir /data/tombstones

死机后的堆栈信息,将被debuggerd写入 /data/tombstones/中

 

拿到死机堆栈后如何分析

我们知道,只要拿到的backtrace中有函数信息,我们就可以找到死机对应的大体位置,通过添加多行打印的方法,来一点点逼近死机位置。

但是,这种方法也有他的局限性:

  • 效率不够高,特别是针对小概率死机问题
  • 添加了多行打印后,有可能影响多线程的运行时序,导致死机更难复现
  • 不够高大上

因此,拿到第一次backtrace后就能精准定位死机位置的方法,才是高大上的方法。

 

分析backtrace文件

backtrace-05的死机堆栈:

backtrace:

#00 pc 00000d3c /system/lib/libqin_playerportbc.so

#01 pc 00001a94 /system/lib/libqin_playerportbc.so (istb_porting_player_open+1180)

 

当前函数#00的PC是0xd3c,外层调用函数#01的pc是1a94(返回地址是1a98),因此,0xd3c和0x1a94处到底指的是哪一行源码,是我们要求证的终极目标。

 

反汇编分析.so文件

先来分析so库的反汇编信息:

00000ca8 :

ca8: e59f0004 ldr r0, [pc, #4] ; cb4

cac: e08f0000 add r0, pc, r0

......

d10: e92d4800 push {fp, lr}

d14: e28db004 add fp, sp, #4

d18: e24dd020 sub sp, sp, #32

d1c: e50b0010 str r0, [fp, #-16]

d20: e50b1014 str r1, [fp, #-20] ; 0xffffffec

d24: e59f3098 ldr r3, [pc, #152] ; dc4

d28: e08f3003 add r3, pc, r3

d2c: e5933000 ldr r3, [r3]

d30: e50b3008 str r3, [fp, #-8]

d34: ea00000b b d68

d38: e51b3008 ldr r3, [fp, #-8]

d3c: e5932000 ldr r2, [r3]

 

0xd3c处的指令 ldr r2,[r3] 表示的含义是,将寄存器r3的值所表示的内存地址处的数据,读入寄存器r2。

而我们从backtrace-05.txt中可知,r3=00000010

因此,ldr r2,[r3],相当于C语言代码:int r2 = *(long)0x00000010

而0x00000010显然是个不可访问的非法地址,因此触发了signal 11.

 

但此指令属于哪一个函数的哪一行呢?

我们看到0xd3c的基准地址入口是 ,显然这不是一个普通函数符号的入口,那是什么呢?

我们知道,静态函数只在当前源文件内有效,因此链接器不会浪费PLT资源来寻址静态函数,只需要用段内相对寻址就完全可以了,就是一个典型的段内相对寻址,因此,0xd3c处指令很可能属于一个静态函数,只不过由于编译优化处理,我们从so文件中是无法获取静态函数符号的。

 

我们再看函数#01的PC 0x1a94和返回地址0x1a98,

000015f8 :

......

1a94: ebfffc9d bl d10

1a98: e59f3148 ldr r3, [pc, #328] ; 1be8

 

0x1a94处是istb_porting_player_open函数内的指令,含义是跳转到0xd10地址处执行,完成后返回;

因此,0xd10应该是函数#00的入口。

我们看0xd10处指令,

push {fp, lr}

含义是将fp和lr寄存器压栈,这是典型的函数入口指令,从objdump文件中可以看出,所有其他函数的入口都是这一条指令。

因此,这证实了0xd10是函数#00的入口。

 

由于本人能力有限,从对.so的objdump文件中,只能分析到这一步,结论是:

1,死机的最内层函数的入口地址是0xd10(相对于so文件的.text段的偏移);

2,死机指令的地址是0xd3c,它相对于函数入口的偏移量是 0xd3c - 0xd10 = 0x2c ;

3,死机的第二层函数是istb_porting_player_open函数,#00的返回地址是0x1a98,它在函数#01中的偏移量是 0x1a98 - 0x15f8 = 0x4a0

至于#00到底是哪一个函数,0xd3c到底是对应的是函数#00内的哪一行代码,从so的objdump文件中无法继续分析了。

 

反汇编分析.o文件

我们知道,从.o目标文件链接生成.so动态库文件的过程中,动态连接器虽然对所有.o文件进行了section合并、GOT生成、指令地址修正等大量处理,但是,.o中某条指令相对于该函数入口的偏移地址并没有被改变,它和.so中是一样的。

 

因此,我们可以对.o文件进行反汇编,编译.o时,一定要加-g选项,这样才能生成源码和汇编对应的调试信息;

arm-linux-androideabi-objdump -S player_porting_bus_client.o > backtrace-05-player_porting_bus_client.o.objdump.txt

其中,-S表示反汇编时,同时保留对应的源码,这正是我们想要的信息。

 

下面分析backtrace-05-player_porting_bus_client.o.objdump.txt。

我们先看#00的返回地址(#01入口+ 0x4a0), 0x914 + 0x4a0 = 0xdb4,

00000914 :

......

d94: ebfffffe bl 0 <__android_log_print>

set_signatureCode(iFuncId,*pAVHandle);

d98: e51b3010 ldr r3, [fp, #-16]

d9c: e1a02003 mov r2, r3

da0: e51b3028 ldr r3, [fp, #-40] ; 0xffffffd8

da4: e5933000 ldr r3, [r3]

da8: e1a00002 mov r0, r2

dac: e1a01003 mov r1, r3

db0: ebfffc9d bl 2c

LOGD("<%s,%d> after set_signatureCode\n", __FUNCTION__, __LINE__);

db4: e59f3148 ldr r3, [pc, #328] ; f04

 

从上述汇编可以看出,0xdb4处代表的是源码是

LOGD("<%s,%d> after set_signatureCode\n", __FUNCTION__, __LINE__);

 

并且上一条指令是死机时当前函数的pc:

db0: ebfffc9d bl 2c

 

因此,我们得出#00函数便是set_signatureCode,这正是一个静态函数。

然后我们看set_signatureCode的汇编:

0000002c :

static void set_signatureCode(ITI_U32 mFuncID,ITI_S32 mCode)

{

2c: e92d4800 push {fp, lr}

30: e28db004 add fp, sp, #4

34: e24dd020 sub sp, sp, #32

38: e50b0010 str r0, [fp, #-16]

3c: e50b1014 str r1, [fp, #-20] ; 0xffffffec

bus_CBInfo_s *pCur = gCallBack;

40: e59f3098 ldr r3, [pc, #152] ; e0

44: e08f3003 add r3, pc, r3

48: e5933000 ldr r3, [r3]

4c: e50b3008 str r3, [fp, #-8]

while (NULL != pCur)

50: ea00000b b 84

{

if (pCur->iFuncID == mFuncID )

54: e51b3008 ldr r3, [fp, #-8]

58: e5932000 ldr r2, [r3]

5c: e51b3010 ldr r3, [fp, #-16]

60: e1520003 cmp r2, r3

64: 1a000003 bne 78

 

我们已经知道,死机指令相对于该函数的偏移地址是0x2c,因此,死机位于:0x2c + 0x2c = 0x58,

 

因此死机指令是:

58: e5932000 ldr r2, [r3]

这恰好就是我们从so的反汇编分析中得到的死机指令。

死机指令对应的源代码:

if (pCur->iFuncID == mFuncID )

很明显,r3寄存器目前存的就是pCur变量的值,0x00000010,pCur是一个指针,0x00000010这个值对于指针来说显然是非法的。

 

相关附件

我咋找不到在哪添加附件?

http://note.youdao.com/noteshare?id=b7fbbdd8ffb423740719972efdcc5644

 

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