打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
gdb运行时结合汇编堆栈分析
#define MYINT intshort addend1 = 1;static int addend2 = 2;const static long addend3 = 3;static MYINT g(MYINT x){    return x + addend1;}static const MYINT f(MYINT x){    return g(x + addend2);}MYINT main(void){    return f(8) + addend3;}

        第一步: 预处理,进行宏替换等工作。执行gcc -E -o example.cpp example.c,得到example.cpp如下:

# 1 "example.c"# 1 "<built-in>"# 1 "<命令行>"# 1 "example.c"short addend1 = 1;static int addend2 = 2;const static long addend3 = 3;static int g(int x){    return x + addend1;}static const int f(int x){    return g(x + addend2);}int main(void){    return f(8) + addend3;}

        第二步:将预处理文件编译成汇编文件。执行 gcc -x cpp-output -S -fno-asynchronous-unwind-tables -o example.s example.cpp,加入 -fno-asynchronous-unwind-tables是为了禁止生成.cfi代码。生成的汇编代码如下:

.file    "example.c"       ; C文件的文件名    .globl    addend1          ; 全局变量    .data                      ; 数据段                               ; short addend1 = 1;开始    .align 2                   ; 地址对齐,按2的整数倍对齐    .type    addend1, @object  ; 类型是对象    .size    addend1, 2        ; 占两个字节addend1:                       ; 起始地址    .value    1                ; 初始值                               ; static int addend2 = 2;开始    .align 4     .type    addend2, @object    .size    addend2, 4addend2:    .long    2    .section    .rodata        ; 常量存储区开始    .align 4    .type    addend3, @object    .size    addend3, 4addend3:    .long    3    .text                      ; 代码段开始    .type    g, @function      ; 函数gg:                             ; g的起始地址    pushl    %ebp              ; %ebp入栈    movl    %esp, %ebp         ; 当前函数栈从%esp开始    movzwl    addend1, %eax    ; 把short放入%eax    cwtl    addl    8(%ebp), %eax      ; int + short    popl    %ebp    ret    .size    g, .-g    .type    f, @functionf:    pushl    %ebp    movl    %esp, %ebp    subl    $4, %esp           ; 为调用g时传递参数准备空间    movl    addend2, %eax      ; 在%eax中计算实参    addl    8(%ebp), %eax    movl    %eax, (%esp)       ; 实参入栈    call    g    leave    ret    .size    f, .-f    .globl    main             ; main未加static,是全局可见的    .type    main, @functionmain:    pushl    %ebp    movl    %esp, %ebp    subl    $4, %esp    movl    $8, (%esp)    call    f    movl    addend3, %edx    addl    %edx, %eax    leave    ret    .size    main, .-main    .ident    "GCC: (Ubuntu/Linaro 4.6.3-1ubuntu5) 4.6.3"    .section    .note.GNU-stack,"",@progbits  

        由汇编代码可见:1.未加static的全局变量和函数都生成了相应的.globl代码,表示是全局的;2.int和long是4字节的;3.const变量放在常量存储区.rodata处。

        第三步,将汇编代码编译成二进制目标文件,gcc -x assembler -c example.s。生成example.o文件,用objdump -D example.o察看,得到如下信息:

example.o:     file format elf32-i386Disassembly of section .text:00000000 <g>:   0:    55                       push   %ebp   1:    89 e5                    mov    %esp,%ebp   3:    0f b7 05 00 00 00 00     movzwl 0x0,%eax   a:    98                       cwtl      b:    03 45 08                 add    0x8(%ebp),%eax   e:    5d                       pop    %ebp   f:    c3                       ret    00000010 <f>:  10:    55                       push   %ebp  11:    89 e5                    mov    %esp,%ebp  13:    83 ec 04                 sub    $0x4,%esp  16:    a1 04 00 00 00           mov    0x4,%eax  1b:    03 45 08                 add    0x8(%ebp),%eax  1e:    89 04 24                 mov    %eax,(%esp)  21:    e8 da ff ff ff           call   0 <g>  26:    c9                       leave    27:    c3                       ret    00000028 <main>:  28:    55                       push   %ebp  29:    89 e5                    mov    %esp,%ebp  2b:    83 ec 04                 sub    $0x4,%esp  2e:    c7 04 24 08 00 00 00     movl   $0x8,(%esp)  35:    e8 d6 ff ff ff           call   10 <f>  3a:    8b 15 00 00 00 00        mov    0x0,%edx  40:    01 d0                    add    %edx,%eax  42:    c9                       leave    43:    c3                       ret    Disassembly of section .data:00000000 <addend1>:   0:    01 00                    add    %eax,(%eax)    ...00000004 <addend2>:   4:    02 00                    add    (%eax),%al    ...Disassembly of section .rodata:00000000 <addend3>:   0:    03 00                    add    (%eax),%eax    ...Disassembly of section .comment:00000000 <.comment>:   0:    00 47 43                 add    %al,0x43(%edi)   3:    43                       inc    %ebx   4:    3a 20                    cmp    (%eax),%ah   6:    28 55 62                 sub    %dl,0x62(%ebp)   9:    75 6e                    jne    79 <main+0x51>   b:    74 75                    je     82 <main+0x5a>   d:    2f                       das       e:    4c                       dec    %esp   f:    69 6e 61 72 6f 20 34     imul   $0x34206f72,0x61(%esi),%ebp  16:    2e 36 2e 33 2d 31 75     cs ss xor %cs:%ss:0x75627531,%ebp  1d:    62 75   1f:    6e                       outsb  %ds:(%esi),(%dx)  20:    74 75                    je     97 <main+0x6f>  22:    35 29 20 34 2e           xor    $0x2e342029,%eax  27:    36 2e 33 00              ss xor %cs:%ss:(%eax),%eax

        第四步,将目标代码编译成可执行文件, gcc -o example example.o。此时可以继续用 objdump -D example > example.objdump察看,可见 example.objdump文件有728行,已经加入了大量的代码,其中我们自己写的部分是:

080483b4 <g>: 80483b4:    55                       push   %ebp 80483b5:    89 e5                    mov    %esp,%ebp 80483b7:    0f b7 05 10 a0 04 08     movzwl 0x804a010,%eax 80483be:    98                       cwtl    80483bf:    03 45 08                 add    0x8(%ebp),%eax 80483c2:    5d                       pop    %ebp 80483c3:    c3                       ret    080483c4 <f>: 80483c4:    55                       push   %ebp 80483c5:    89 e5                    mov    %esp,%ebp 80483c7:    83 ec 04                 sub    $0x4,%esp 80483ca:    a1 14 a0 04 08           mov    0x804a014,%eax 80483cf:    03 45 08                 add    0x8(%ebp),%eax 80483d2:    89 04 24                 mov    %eax,(%esp) 80483d5:    e8 da ff ff ff           call   80483b4 <g> 80483da:    c9                       leave   80483db:    c3                       ret    080483dc <main>: 80483dc:    55                       push   %ebp 80483dd:    89 e5                    mov    %esp,%ebp 80483df:    83 ec 04                 sub    $0x4,%esp 80483e2:    c7 04 24 08 00 00 00     movl   $0x8,(%esp) 80483e9:    e8 d6 ff ff ff           call   80483c4 <f> 80483ee:    8b 15 d0 84 04 08        mov    0x80484d0,%edx 80483f4:    01 d0                    add    %edx,%eax 80483f6:    c9                       leave   80483f7:    c3                       ret     80483f8:    90                       nop 80483f9:    90                       nop 80483fa:    90                       nop 80483fb:    90                       nop 80483fc:    90                       nop 80483fd:    90                       nop 80483fe:    90                       nop 80483ff:    90                       nop.........Disassembly of section .data:0804a008 <__data_start>: 804a008:    00 00                    add    %al,(%eax)    ...0804a00c <__dso_handle>: 804a00c:    00 00                    add    %al,(%eax)    ...0804a010 <addend1>: 804a010:    01 00                    add    %eax,(%eax)    ...0804a014 <addend2>: 804a014:    02 00                    add    (%eax),%al    ...Disassembly of section .bss:0804a018 <completed.6159>: 804a018:    00 00                    add    %al,(%eax)    ...0804a01c <dtor_idx.6161>: 804a01c:    00 00                    add    %al,(%eax)    ...

        可见此时的代码已经有了它运行时的实际地址,并且.rodata段也已经不存在了。

        然后还可以用readelf -a example > example.elf 察看该可执行文件的ELF头部信息,共221行,这里只摘录前57行:

ELF Header:  Magic:   7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00   Class:                             ELF32  Data:                              2's complement, little endian  Version:                           1 (current)  OS/ABI:                            UNIX - System V  ABI Version:                       0  Type:                              EXEC (Executable file)  Machine:                           Intel 80386  Version:                           0x1  Entry point address:               0x8048300  Start of program headers:          52 (bytes into file)  Start of section headers:          4416 (bytes into file)  Flags:                             0x0  Size of this header:               52 (bytes)  Size of program headers:           32 (bytes)  Number of program headers:         9  Size of section headers:           40 (bytes)  Number of section headers:         30  Section header string table index: 27Section Headers:  [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al  [ 0]                   NULL            00000000 000000 000000 00      0   0  0  [ 1] .interp           PROGBITS        08048154 000154 000013 00   A  0   0  1  [ 2] .note.ABI-tag     NOTE            08048168 000168 000020 00   A  0   0  4  [ 3] .note.gnu.build-i NOTE            08048188 000188 000024 00   A  0   0  4  [ 4] .gnu.hash         GNU_HASH        080481ac 0001ac 000020 04   A  5   0  4  [ 5] .dynsym           DYNSYM          080481cc 0001cc 000040 10   A  6   1  4  [ 6] .dynstr           STRTAB          0804820c 00020c 000045 00   A  0   0  1  [ 7] .gnu.version      VERSYM          08048252 000252 000008 02   A  5   0  2  [ 8] .gnu.version_r    VERNEED         0804825c 00025c 000020 00   A  6   1  4  [ 9] .rel.dyn          REL             0804827c 00027c 000008 08   A  5   0  4  [10] .rel.plt          REL             08048284 000284 000010 08   A  5  12  4  [11] .init             PROGBITS        08048294 000294 00002e 00  AX  0   0  4  [12] .plt              PROGBITS        080482d0 0002d0 000030 04  AX  0   0 16  [13] .text             PROGBITS        08048300 000300 0001ac 00  AX  0   0 16  [14] .fini             PROGBITS        080484ac 0004ac 00001a 00  AX  0   0  4  [15] .rodata           PROGBITS        080484c8 0004c8 00000c 00   A  0   0  4  [16] .eh_frame_hdr     PROGBITS        080484d4 0004d4 00002c 00   A  0   0  4  [17] .eh_frame         PROGBITS        08048500 000500 0000a4 00   A  0   0  4  [18] .ctors            PROGBITS        08049f14 000f14 000008 00  WA  0   0  4  [19] .dtors            PROGBITS        08049f1c 000f1c 000008 00  WA  0   0  4  [20] .jcr              PROGBITS        08049f24 000f24 000004 00  WA  0   0  4  [21] .dynamic          DYNAMIC         08049f28 000f28 0000c8 08  WA  6   0  4  [22] .got              PROGBITS        08049ff0 000ff0 000004 04  WA  0   0  4  [23] .got.plt          PROGBITS        08049ff4 000ff4 000014 04  WA  0   0  4  [24] .data             PROGBITS        0804a008 001008 000010 00  WA  0   0  4  [25] .bss              NOBITS          0804a018 001018 000008 00  WA  0   0  4  [26] .comment          PROGBITS        00000000 001018 00002a 01  MS  0   0  1  [27] .shstrtab         STRTAB          00000000 001042 0000fc 00      0   0  1  [28] .symtab           SYMTAB          00000000 0015f0 000450 10     29  49  4  [29] .strtab           STRTAB          00000000 001a40 000209 00      0   0  1Key to Flags:  W (write), A (alloc), X (execute), M (merge), S (strings)  I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)  O (extra OS processing required) o (OS specific), p (processor specific)

二、运行时堆栈分析

        为了使用gdb进行调试,用gcc - g example.c -o example重新编译代码,然后gdb example进入gdb调试。

        在main函数入口处设置断点,运行程序,然后察看运行到的汇编指令、此时的寄存器数据和堆栈:

(gdb) b 17Breakpoint 1 at 0x80483e2: file example.c, line 17.(gdb) rStarting program: /home/qpx/操作系统/example Breakpoint 1, main () at example.c:1919        return f(8) + addend3;(gdb) disassemble Dump of assembler code for function main:   0x080483dc <+0>:    push   %ebp   0x080483dd <+1>:    mov    %esp,%ebp   0x080483df <+3>:    sub    $0x4,%esp=> 0x080483e2 <+6>:    movl   $0x8,(%esp)   0x080483e9 <+13>:    call   0x80483c4 <f>   0x080483ee <+18>:    mov    0x80484d0,%edx   0x080483f4 <+24>:    add    %edx,%eax   0x080483f6 <+26>:    leave     0x080483f7 <+27>:    ret    End of assembler dump.(gdb) info registers eax            0x1    1ecx            0xbffff394    -1073745004edx            0xbffff324    -1073745116ebx            0xb7fc2ff4    -1208209420esp            0xbffff2f4    0xbffff2f4ebp            0xbffff2f8    0xbffff2f8esi            0x0    0edi            0x0    0eip            0x80483e2    0x80483e2 <main+6>eflags         0x200282    [ SF IF ID ]cs             0x73    115ss             0x7b    123ds             0x7b    123es             0x7b    123fs             0x0    0gs             0x33    51

  (gdb) x/2 0xbffff2f4
  0xbffff2f4: 0x000000000 x00000000

可见此时主函数的栈基址为0xbffff2f8,而%esp已经下移4字节准备为函数 f 传递参数8,但目前%esp所指堆栈内容为0,%ebp所指内容也为0。下面展示每一步时%esp、%ebp和堆栈内容的变化:

call指令将下一条指令的地址入栈:

将上一个函数的基址入栈,从当前%esp开始作为新基址:

先为传参做准备:

实参的计算在%eax中进行:

实参入栈:

call指令将下一条指令的地址入栈:

计算short+int:

pop %ebp指令将栈顶弹到%ebp中,同时%esp增加4字节:

ret指令将栈顶弹给%eip

因为函数 f 修改了%esp,所以用leave指令恢复。leave指令先将%esp对其到%ebp,然后把栈顶弹给%ebp:

 程序最终结束。

本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
深入了解GOT,PLT和动态链接
Linux调用栈获取分析及实现
[转]ELF 文件格式分析
Linux 查看 elf可执行文件格式的两个命令
Kernel panic 信息分析方法(转自高人)
从汇编的角度分析C语言(一)char *p与char p[]的区别
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服