Linux平台代码覆盖率测试-GCC插桩前后汇编代码对比分析


Content

0.

1. 如何编译

1.1 未加入覆盖率测试选项

1.2 加入覆盖率测试选项

1.3 分析

2. 未加入覆盖率测试选项的汇编代码分析

3. 加入覆盖率测试选项的汇编代码分析

3.1 计数桩代码分析

3.2 构造函数桩代码分析

3.3 数据结构分析

3.4 构造函数桩代码小结

4. 说明

5. 小结

 

 

0.

 

在"Linux平台代码覆盖率测试-GCC插桩基本概念和原理分析"一文中,我们已经知道,GCC插桩乃汇编级的插桩,那么,本文仍然以test.c为例,来分析加入覆盖率测试选项"-fprofile-arcs -ftest-coverage"前后,即插桩前后汇编代码的变化。本文所用gcc版本为gcc-4.1.2test.c代码如下。

/**

 * filename: test.c

 */

#include

 

int main (void)

{

    int i, total;

 

    total = 0;

 

    for (i = 0; i < 10; i++)

        total += i;

 

    if (total != 45)

        printf ("Failure\n");

    else

        printf ("Success\n");

    return 0;

}

1. 如何编译

 

1.1 未加入覆盖率测试选项

 

# cpp test.c -o test.i   //预处理:生成test.i文件,或者"cpp test.c > test.i"

或者

# gcc -E test.c -o test.i

# gcc -S test.i          //编译:生成test.s文件(未加入覆盖率测试选项)

# as -o test.o test.s    //汇编:生成test.o文件,或者"gcc -c test.s -o test.o"

# gcc -o test test.o     //链接:生成可执行文件test

 

以上过程可参考

 

查看test.o文件中的符号

# nm test.o

00000000 T main

         U puts

 

1.2 加入覆盖率测试选项

 

# cpp test.c -o test.i                          //预处理:生成test.i文件

# gcc -fprofile-arcs -ftest-coverage -S test.i  //编译:生成test.s文件(加入覆盖率测试选项)

# as -o test.o test.s                           //汇编:生成test.o文件

# gcc -o test test.o                            //链接:生成可执行文件test

 

查看test.o文件中的符号

# nm test.o

000000eb t _GLOBAL__I_0_main

         U __gcov_init

         U __gcov_merge_add

00000000 T main

         U puts

 

1.3 分析

 

从上面nm命令的结果可以看出,加入覆盖率测试选项后的test.o文件,多了3个符号,如上。其中,_GLOBAL__I_0_main就是插入的部分桩代码。section2section3将对比分析插桩前后汇编代码的变化,section3重点分析插入的桩代码。

 

2. 未加入覆盖率测试选项的汇编代码分析

 

采用"# gcc -S test.i"命令得到的test.s汇编代码如下。#后面的注释为笔者所加。

    .file    "test.c"

    .section    .rodata

.LC0:

    .string    "Failure"

.LC1:

    .string    "Success"

    .text

.globl main

    .type    main, @function

main:

    leal    4(%esp), %ecx    #这几句就是保护现场

    andl    $-16, %esp

    pushl    -4(%ecx)

    pushl    %ebp

    movl    %esp, %ebp

    pushl    %ecx

    subl    $20, %esp

 

    movl    $0, -8(%ebp)     #初始化total=0,total的值在-8(%ebp)

    movl    $0, -12(%ebp)    #初始化循环变量i=0,i的值在-12(%ebp)

    jmp    .L2

.L3:

    movl    -12(%ebp), %eax  #i的值移到%eax中,即%eax=i

    addl    %eax, -8(%ebp)   #%eax的值加到-8(%ebp)total=total+i

    addl    $1, -12(%ebp)    #循环变量加1,即i++

.L2:

    cmpl    $9, -12(%ebp)    #比较循环变量i9的大小

    jle    .L3               #如果i<=9,跳到.L3,继续累加

    cmpl    $45, -8(%ebp)    #否则,比较total的值与45的大小

    je     .L5               #total=45,跳到.L5

    movl    $.LC0, (%esp)    #total的值不为45,则将$.LC0放入%esp

    call    puts             #输出Failure

    jmp    .L7               #跳到.L7

.L5:

    movl    $.LC1, (%esp)    #$.LC1放入%esp

    call    puts             #输出Success

.L7:

    movl    $0, %eax         #返回值0放入%eax

 

    addl    $20, %esp        #这几句恢复现场

    popl    %ecx

    popl    %ebp

    leal    -4(%ecx), %esp

    ret

 

    .size    main, .-main

    .ident    "GCC: (GNU) 4.1.2 20070925 (Red Hat 4.1.2-33)"

    .section    .note.GNU-stack,"",@progbits

注:$9表示常量9,即立即数(Immediate Operand)-8(%ebp)即为total-12(%ebp)即是循环变量i

 

3. 加入覆盖率测试选项的汇编代码分析

 

采用"# gcc -fprofile-arcs -ftest-coverage -S test.i"命令得到的test.s汇编代码如下。前面的蓝色部分及后面的.LC2, .LC3, .LPBX0, _GLOBAL__I_0_main等均为插入的桩代码。#后面的注释为笔者所加。

    .file      "test.c"

    .section   .rodata

.LC0:

    .string    "Failure"

.LC1:

    .string    "Success"

    .text

.globl main

    .type    main, @function

main:

    leal    4(%esp), %ecx    #这几句就是保护现场

    andl    $-16, %esp

    pushl    -4(%ecx)

    pushl    %ebp

    movl    %esp, %ebp

    pushl    %ecx

    subl    $20, %esp

 

    movl    $0, -8(%ebp)     #初始化total=0,total的值在-8(%ebp)

    movl    $0, -12(%ebp)    #初始化循环变量i=0,i的值在-12(%ebp)

    jmp    .L2

 

.L3:                         #以下这几句就是插入的桩代码

    movl    .LPBX1, %eax     #.LPBX1移到%eax,即%eax=.LPBX1

    movl    .LPBX1+4, %edx   #edx=.LPBX1+4

    addl    $1, %eax         #eax=%eax+1

    adcl    $0, %edx         #edx=%edx+0

    movl    %eax, .LPBX1     #%eax移回.LPBX1

    movl    %edx, .LPBX1+4   #%edx移回.LPBX1+4

 

    movl    -12(%ebp), %eax  #i的值移到%eax中,即%eax=i

    addl    %eax, -8(%ebp)   #%eax的值加到-8(%ebp)total=total+i

    addl    $1, -12(%ebp)    #循环变量加1,即i++

 

.L2:

    cmpl    $9, -12(%ebp)    #比较循环变量i9的大小

    jle    .L3               #如果i<=9,跳到.L3,继续累加

    cmpl    $45, -8(%ebp)    #否则,比较total的值与45的大小

    je     .L5               #total=45,跳到.L5

 

    #以下也为桩代码

    movl    .LPBX1+8, %eax   #eax=.LPBX1+8

    movl    .LPBX1+12, %edx  #edx=.LPBX1+12

    addl    $1, %eax         #eax=%eax+1

    adcl    $0, %edx         #edx=%edx+0

    movl    %eax, .LPBX1+8   #%eax移回.LPBX1+8

    movl    %edx, .LPBX1+12  #%eax移回.LPBX1+12

 

    movl    $.LC0, (%esp)    #total的值不为45,则将$.LC0放入%esp

    call    puts             #输出Failure

 

    #以下也为桩代码,功能同上,不再解释

    movl    .LPBX1+24, %eax

    movl    .LPBX1+28, %edx

    addl    $1, %eax

    adcl    $0, %edx

    movl    %eax, .LPBX1+24

    movl    %edx, .LPBX1+28

 

    jmp    .L7               #跳到.L7

 

.L5:

    #以下也为桩代码,功能同上,不再解释

    movl    .LPBX1+16, %eax

    movl    .LPBX1+20, %edx

    addl    $1, %eax

    adcl    $0, %edx

    movl    %eax, .LPBX1+16

    movl    %edx, .LPBX1+20

 

    movl    $.LC1, (%esp)    #$.LC1放入%esp

    call    puts             #输出Success

 

    #以下也为桩代码,功能同上,不再解释

    movl    .LPBX1+32, %eax

    movl    .LPBX1+36, %edx

    addl    $1, %eax

    adcl    $0, %edx

    movl    %eax, .LPBX1+32

    movl    %edx, .LPBX1+36

 

.L7:

    movl    $0, %eax         #返回值0放入%eax

    addl    $20, %esp        #这几句回复现场

    popl    %ecx

    popl    %ebp

    leal    -4(%ecx), %esp

    ret

 

    .size    main, .-main

 

    #以下部分均是加入coverage选项后编译器加入的桩代码

 

    .local   .LPBX1

    .comm    .LPBX1,40,32

 

    .section .rodata      #只读section

    .align   4

.LC2:                     #文件名常量,只读

    .string  "/home/zubo/gcc/test/test.gcda"

 

    .data                 #data数据段

    .align   4

.LC3:

    .long    3            #ident=3

    .long    -345659544   #checksum=0xeb65a768

    .long    5            #counters

 

    .align   32

    .type    .LPBX0, @object #.LPBX0是一个对象

    .size    .LPBX0, 52   #.LPBX0大小为52字节

.LPBX0:                   #结构的起始地址,即结构名,该结构即为gcov_info结构

    .long    875573616    #version=0x34303170,即版本为4.1p

    .long    0            #next指针,为0

    .long    -979544300   #stamp=0xc59d5714

    .long    .LC2         #filename,值为.LC2的常量

    .long    1            #n_functions=1

    .long    .LC3         #functions指针,指向.LC3

    .long    1            #ctr_mask=1

    .long    5            #以下3个字段构成gcov_ctr_info结构,该字段num=5,即counter的个数

    .long    .LPBX1       #values指针,指向.LPBX1,即5counter的内容在.LPBX1结构中

    .long    __gcov_merge_add #merge指针,指向__gcov_merge_add函数

    .zero    12           #应该是120

 

    .text                                  #text代码段

    .type    _GLOBAL__I_0_main, @function  #类型是function

_GLOBAL__I_0_main:                         #以下是函数体

    pushl    %ebp

    movl     %esp, %ebp

    subl     $8, %esp

    movl     $.LPBX0, (%esp)   #$.LPBX0,即.LPBX0的地址,存入%esp所指单元

                               #实际上是为下面调用__gcov_init准备参数,gcov_info结构指针

    call     __gcov_init       #调用__gcov_init

    leave

    ret

 

    .size    _GLOBAL__I_0_main, .-_GLOBAL__I_0_main

    .section    .ctors,"aw",@progbits      #该函数位于ctors

    .align 4

    .long    _GLOBAL__I_0_main

    .align 4

    .long    _GLOBAL__I_0_main

 

   .ident    "GCC: (GNU) 4.1.2 20070925 (Red Hat 4.1.2-33)"

   .section    .note.GNU-stack,"",@progbits

  • 1
  • 2
  • 下一页

相关内容