Welcome 微信登录

首页 / 操作系统 / Linux / Linux平台代码覆盖率测试-GCC插桩前后汇编代码对比分析

Content0. 序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.2。test.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 ");    else        printf ("Success ");    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 以上过程可参考http://www.linuxidc.com/Linux/2011-05/36542.htm。 查看test.o文件中的符号# nm test.o00000000 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.o000000eb t _GLOBAL__I_0_main         U __gcov_init         U __gcov_merge_add00000000 T main         U puts 1.3 分析 从上面nm命令的结果可以看出,加入覆盖率测试选项后的test.o文件,多了3个符号,如上。其中,_GLOBAL__I_0_main就是插入的部分桩代码。section2和section3将对比分析插桩前后汇编代码的变化,section3重点分析插入的桩代码。 2. 未加入覆盖率测试选项的汇编代码分析 采用"# gcc -S test.i"命令得到的test.s汇编代码如下。#后面的注释为笔者所加。
    .file    "test.c"    .section    .rodata.LC0:    .string    "Failure".LC1:    .string    "Success"    .text.globl main    .type    main, @functionmain:    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)    #比较循环变量i与9的大小    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, @functionmain:    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)    #比较循环变量i与9的大小    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,即5个counter的内容在.LPBX1结构中    .long    __gcov_merge_add #merge指针,指向__gcov_merge_add函数    .zero    12           #应该是12个0     .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