首页 用户态源码反汇编定位方法记录
文章
取消

用户态源码反汇编定位方法记录

前言

问题背景就是进程崩溃,产生了 core 文件。其实这次定位非常简单,bt, disassemble 就能直接看出来了。既然简单,就利用这次机会尝试了一下源码反汇编的定位方法。此前在别人的帮助下,运用该方法定位过内核态的问题,但都事发紧急,事后没能复盘,留下了遗憾。尽管存在差异,还是决定在用户态“模拟”一下。

定位过程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Program terminated with signal SIGSEGV, Segmentation fault.
#0  0x00007fcff9ad157a in check ()
   from ....so
(gdb) bt
#0  0x00007fcff9ad157a in check ()
   from ....so
#1  0x00007fcff9ad27c1 in func ()
   from ....so
#2  0x00007fcff9ad3483 in func ()
   from ....so
#3  0x00007fcff9ad6696 in func ()
   from ....so
#4  0x00007fcff9ad9a40 in func ()
   from ....so
#5  0x00007fd018fa50d6 in func () from ....so
#6  0x000000000041dbe9 in func ()
#7  0x00000000004178bd in func ()
#8  0x00007fd018fc39fd in func () from ....so
#9  0x000000000040b56c in main ()

死在 check() 中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
(gdb) disassemble check
Dump of assembler code for function check:
   0x00007fcff9ad1408 <+0>:     push   %r15
   0x00007fcff9ad140a <+2>:     push   %r14
   0x00007fcff9ad140c <+4>:     mov    %rdi,%r14
...
   0x00007fcff9ad156c <+356>:   mov    0x48(%r14),%rax
   0x00007fcff9ad1570 <+360>:   or     $0xffffffffffffffff,%rcx
   0x00007fcff9ad1574 <+364>:   mov    (%rax,%rbp,1),%rdi
   0x00007fcff9ad1578 <+368>:   xor    %eax,%eax
=> 0x00007fcff9ad157a <+370>:   repnz scas %es:(%rdi),%al
   0x00007fcff9ad157c <+372>:   mov    %rcx,%rax
   0x00007fcff9ad157f <+375>:   not    %rax
   0x00007fcff9ad1582 <+378>:   dec    %rax
   0x00007fcff9ad1585 <+381>:   cmp    $0x200,%rax
...

找到崩溃指令的偏移量为 +370,其十六进制为 172

接下来准备编译源码。编译前准备:

  1. 源码版本须与问题版本一致
  2. 给源码编译加上 -g 参数,去掉 STRIP,除此以外的编译参数都不变(与编译问题版本时的参数保持一致)

将问题代码所在的文件编译出来,得到对应的 so

随后,在开发环境中反汇编:

1
2
3
# -S  Intermix source code with disassembly
# -l  Include line numbers and filenames in output
objdump check.so -S -l > check.c.asm

check.c.asm 中直接找到 check 函数。其第一条指令的首地址为 2653a。我们将之加上先前在调试环境中得到的崩溃指令的偏移量: 2653a + 172 = 266ac,就得到了开发环境中崩溃指令的偏移量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
   2653a:    ...
...
/.../check.c:3498
        return ERR_MAX_CFG;
    }

    for (i = 0 ;i < obj->sizeList; i++)
    {
        snprintf(err_str, NAME_LEN,"list[%d].host", i);
   26699:    e8 42 3b ff ff           callq  1a1e0 <snprintf@plt>
/.../check.c:3499
        if (strlen(obj->list[i].host) > 512)
   2669e:    49 8b 46 48              mov    0x48(%r14),%rax
   266a2:    48 83 c9 ff              or     $0xffffffffffffffff,%rcx
   266a6:    48 8b 3c 28              mov    (%rax,%rbp,1),%rdi
   266aa:    31 c0                    xor    %eax,%eax
   266ac:    f2 ae                    repnz scas %es:(%rdi),%al
   266ae:    48 89 c8                 mov    %rcx,%rax
   266b1:    48 f7 d0                 not    %rax
   266b4:    48 ff c8                 dec    %rax
   266b7:    48 3d 00 02 00 00        cmp    $0x200,%rax

可以看到 266ac 的崩溃指令 repnz scas %es:(%rdi),%al 与调试环境中的崩溃指令是一致的。随即观察附近的源码,就知道问题所在了。

1000% 是 strlen() 的问题,多半是 hostNULL 了。实测一番,果不其然,直接复现。

注意事项

在开发环境中重新编译相关源码时,需保证代码版本、编译参数(除 -g, STRIP 外)与问题版本一致,否则反汇编结果将与问题版本所在环境的反汇编结果不一致,在开发环境中就无法精准地找到同样的崩溃指令了,二者汇编都对不上,也就无法定位了。

本文由作者按照 CC BY 4.0 进行授权
热门标签

gdb无-g调试实战记录

死机定位-内存不足死在vmalloc

热门标签