GCC Coverage代码分析-编译过程自动化及对链接的解释

本博客(http://blog.csdn.net/livelylittlefish)贴出作者(阿波)相关研究、学习内容所做的笔记,欢迎广大朋友指正!

Content

0. 

1. 生成各个文件的步骤

1.1 未加入覆盖率测试选项

1.1.1 编译步骤

1.1.2 目标文件的符号表

1.2 加入覆盖率测试选项

1.2.1 编译步骤

1.2.2 目标文件的符号表

1.3 gcc verbose选项

2. 编译自动化

2.1使用collect2makefile

2.2不使用collect2makefile

3. 关于链接的讨论

3.1 链接顺序讨论

3.2 错误链接顺序的例子

4. 额外的话

5. 小结

 

 

0.

 

"Linux平台代码覆盖率测试-GCC插桩前后汇编代码对比分析"一文中的编译过程能否自动化?能否使用makefile的方式进行?其链接过程使用ld或者collect2如何?

 

——这将是本文讨论的重点。本文gcc版本为gcc-4.1.2

 

1.生成各个文件的步骤

 

1.1未加入覆盖率测试选项

 

1.1.1编译步骤

 

(1)预处理:生成test.i文件

 

# cpp test.c-o test.i   //或者

# cpp test.c > test.i    //或者

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

 

(2)编译:生成test.s文件

 

# gcc -S test.i

 

(3)汇编:生成test.o文件

 

# as -o test.o test.s    //或者

# gcc -c test.s -o test.o

 

(4)链接:生成可执行文件test

 

# gcc -o test test.o

 

 

或者,

# /usr/libexec/gcc/i386-redhat-linux/4.1.2/collect2 --eh-frame-hdr --build-id -m elf_i386 --hash-style=gnu -dynamic-linker /lib/ld-linux.so.2 /usr/lib/gcc/i386-redhat-linux/4.1.2/../../../crt1.o/usr/lib/gcc/i386-redhat-linux/4.1.2/../../../crti.o/usr/lib/gcc/i386-redhat-linux/4.1.2/crtbegin.o -L/usr/lib/gcc/i386-redhat-linux/4.1.2 -L/usr/lib/gcc/i386-redhat-linux/4.1.2 -L/usr/lib/gcc/i386-redhat-linux/4.1.2/../../.. -lgcc --as-needed -lgcc_s --no-as-needed -lc -lgcc --as-needed -lgcc_s --no-as-needed /usr/lib/gcc/i386-redhat-linux/4.1.2/crtend.o /usr/lib/gcc/i386-redhat-linux/4.1.2/../../../crtn.otest.o -o test

/usr/lib/gcc/i386-redhat-linux/4.1.2/../../..就是/usr/lib,因此,也可以简写为。

/usr/libexec/gcc/i386-redhat-linux/4.1.2/collect2 --eh-frame-hdr --build-id -m elf_i386 --hash-style=gnu -dynamic-linker /lib/ld-linux.so.2 /usr/lib/crt1.o /usr/lib/crti.o /usr/lib/gcc/i386-redhat-linux/4.1.2/crtbegin.o -L/usr/lib/gcc/i386-redhat-linux/4.1.2 -L/usr/lib/gcc/i386-redhat-linux/4.1.2 -L/usr/lib -lgcc --as-needed -lgcc_s --no-as-needed -lc -lgcc --as-needed -lgcc_s --no-as-needed /usr/lib/gcc/i386-redhat-linux/4.1.2/crtend.o /usr/lib/crtn.otest.o -o test

1.1.2目标文件的符号表

 

 

可通过如下命令查看test.o中的符号表。//后是笔者加入的注释。

 

# objdump -t test.o

 

test.o:    file format elf32-i386

 

SYMBOL TABLE:

00000000 l   df *ABS*  00000000 test.c

00000000 l   d  .text  00000000 .text

00000000 l   d  .data  00000000 .data

00000000 l   d  .bss   00000000 .bss

00000000 l   d  .rodata        00000000 .rodata

00000000 l   d  .note.GNU-stack        00000000 .note.GNU-stack

00000000 l   d  .comment       00000000 .comment

00000000 g    F .text  0000005f main

00000000        *UND* 00000000 puts    //就是undefined,故需要连接其他的目标文件

 

# nm test.o

00000000 T main   //T即为text symbol,大写表示global

        U puts   //就是undefined,故需要连接其他的目标文件 

1.2加入覆盖率测试选项

 

1.2.1编译步骤

 

步骤同上,只是在编译生成test.s文件时加上"-fprofile-arcs -ftest-coverage"覆盖率测试选项即可。

 

 

另外,使用collect2的链接步骤稍有不同,需要链接gcov静态库(libgcov.a)。如下。

# /usr/libexec/gcc/i386-redhat-linux/4.1.2/collect2 --eh-frame-hdr --build-id -m elf_i386 --hash-style=gnu -dynamic-linker /lib/ld-linux.so.2 /usr/lib/gcc/i386-redhat-linux/4.1.2/../../../crt1.o/usr/lib/gcc/i386-redhat-linux/4.1.2/../../../crti.o/usr/lib/gcc/i386-redhat-linux/4.1.2/crtbegin.otest.o -L/usr/lib/gcc/i386-redhat-linux/4.1.2 -L/usr/lib/gcc/i386-redhat-linux/4.1.2 -L/usr/lib/gcc/i386-redhat-linux/4.1.2/../../..-lgcov -lgcc --as-needed -lgcc_s --no-as-needed -lc -lgcc --as-needed -lgcc_s --no-as-needed /usr/lib/gcc/i386-redhat-linux/4.1.2/crtend.o/usr/lib/gcc/i386-redhat-linux/4.1.2/../../../crtn.o -o test

或者简写为。

/usr/libexec/gcc/i386-redhat-linux/4.1.2/collect2 --eh-frame-hdr --build-id -m elf_i386 --hash-style=gnu -dynamic-linker /lib/ld-linux.so.2/usr/lib/crt1.o /usr/lib/crti.o/usr/lib/gcc/i386-redhat-linux/4.1.2/crtbegin.otest.o -L/usr/lib/gcc/i386-redhat-linux/4.1.2 -L/usr/lib/gcc/i386-redhat-linux/4.1.2 -L/usr/lib-lgcov -lgcc --as-needed -lgcc_s --no-as-needed -lc -lgcc --as-needed -lgcc_s --no-as-needed/usr/lib/gcc/i386-redhat-linux/4.1.2/crtend.o /usr/lib/crtn.o-o test

当然,命令亦会完成链接并生成可执行文件。

# gcc -o test test.o-lgcov

 

libgcov.a位于/usr/lib/gcc/i386-redhat-linux/4.1.2目录。

 

1.2.2目标文件的符号表

 

 

看看加入覆盖率测试选项后的目标文件test.o中的符号表。

# objdump -t test.o

 

test.o:    file format elf32-i386

 

SYMBOL TABLE:

00000000 l   df *ABS*  00000000 test.c

00000000 l   d  .text  00000000 .text

00000000 l   d  .data  00000000 .data

00000000 l   d  .bss   00000000 .bss

00000000 l   d  .rodata        00000000 .rodata

000000eb l    F .text  00000014 _GLOBAL__I_0_main

00000000 l   d  .ctors 00000000 .ctors

00000000 l   d  .note.GNU-stack        00000000 .note.GNU-stack

00000000 l   d  .comment       00000000 .comment

00000000 g    F .text  000000eb main

00000000        *UND* 00000000 puts             //就是undefined,故需要连接其他的目标文件,下同

00000000        *UND*  00000000 __gcov_merge_add

00000000        *UND*  00000000 __gcov_init

 

# nm test.o

000000eb t _GLOBAL__I_0_main   //t即为text symbol,小写表示local

        U __gcov_init         //就是undefined,故需要连接其他的目标文件,下同

        U __gcov_merge_add

00000000 T main

        U puts

1.3 gcc verbose选项

 

这是如何知道的呢?——gcc命令-v选项即可。

# gcc -v test.c [-o test]

# gcc -fprofile-arcs -ftest-coverage-v test.c [-o test]

[]表示出现次或1次。

-v,即verbose选项,以下是<An Introduction to GCC>中的解释,原文更贴切,此处不翻译。

The'-v' option can also be used todisplay detailed information about theexact sequence of commands used to compile and link a program.

2.编译自动化

 

2.1使用collect2makefile

 

 

编写makefile文件,使上述过程自动化。针对该例子,笔者编写的makefile如下。#后面是笔者加入的注释。

PP = cpp    #预处理程序

CC = gcc    #编译程序

AS = as     #汇编程序

 

CXX_FLAGS += -g -Wall -Wextra

ASM_FLAGS = -S

COV_FLAGS = -fprofile-arcs -ftest-coverage    #如果不加入覆盖率测试选项,只需将其值置为空即可

LINK_GCOV =-lgcov                           #此处链接gcov静态库,如不需要,将其值置为空即可

 

TARGET_FILE =test   #test即是TARGET_FILE全局变量的值,如要用到别的程序,只需修改此值即可使用

 

SRC_FILE = $(TARGET_FILE).c

CPP_FILE = $(TARGET_FILE).i

ASM_FILE = $(TARGET_FILE).s

OBJ_FILE = $(TARGET_FILE).o

EXE_FILE = $(TARGET_FILE)

 

TARGET = $(CPP_FILE) $(ASM_FILE) $(OBJ_FILE) $(EXE_FILE)

 

CLEANUP = rm -f $(TARGET)

 

all : $(TARGET)

 

clean :

$(CLEANUP)

 

#如果是其他的平台或GCC,此3路径或许需要修改才能使用

COLLECT2_DIR=/usr/libexec/gcc/i386-redhat-linux/4.1.2

CRTLIB_DIR=/usr/lib/gcc/i386-redhat-linux/4.1.2

LIB_DIR=/usr/lib

 

COLLECT2=$(COLLECT2_DIR)/collect2

LDSO=/lib/ld-linux.so.2

 

CRT1=$(LIB_DIR)/crt1.o

CRTI=$(LIB_DIR)/crti.o

CRTBEGIN=$(CRTLIB_DIR)/crtbegin.o

CRTEND=$(CRTLIB_DIR)/crtend.o

CRTN=$(LIB_DIR)/crtn.o

 

LINK_FLAGS= --eh-frame-hdr --build-id -m elf_i386 --hash-style=gnu -dynamic-linker

LINK_LIBS = -lgcc --as-needed -lgcc_s --no-as-needed -lc -lgcc --as-needed -lgcc_s --no-as-needed

SEARCH_DIR= -L$(CRTLIB_DIR) -L$(CRTLIB_DIR) -L$(LIB_DIR)

 

$(TARGET):

$(CPP_FILE): $(SRC_FILE)

$(PP) $^ -o $@

 

$(ASM_FILE): $(CPP_FILE)

$(CC) $(ASM_FLAGS)$(COV_FLAGS) $^ -o $@

 

$(OBJ_FILE): $(ASM_FILE)

$(AS) $^ -o $@

 

$(EXE_FILE): $(OBJ_FILE)

$(COLLECT2) $(LINK_FLAGS) $(LDSO) $(OBJ_FILE) $(CRT1) $(CRTI) $(CRTBEGIN) $(SEARCH_DIR)$(LINK_GCOV) $(LINK_LIBS) $(CRTEND) $(CRTN) -o $@

这是一个通用的makefile,如果要用到别的程序,只需修改TARGET_FILE的值即可。

 

2.2不使用collect2makefile

 

 

如果不使用collect2进行链接,直接使用gcc命令,makefile就很简单了,如下。

PP = cpp

CC = gcc

AS = as

 

CXX_FLAGS += -g -Wall -Wextra

ASM_FLAGS = -S

COV_FLAGS = -fprofile-arcs -ftest-coverage    #如果不加入覆盖率测试选项,只需将其值置为空即可

LINK_GCOV =-lgcov                           #此处链接gcov静态库,如不需要,将其值置为空即可

 

TARGET_FILE =test   #test即是TARGET_FILE全局变量的值,如要用到别的程序,只需修改此值即可使用

 

SRC_FILE = $(TARGET_FILE).c

CPP_FILE = $(TARGET_FILE).i

ASM_FILE = $(TARGET_FILE).s

OBJ_FILE = $(TARGET_FILE).o

EXE_FILE = $(TARGET_FILE)

 

TARGET = $(CPP_FILE) $(ASM_FILE) $(OBJ_FILE) $(EXE_FILE)

 

CLEANUP = rm -f $(TARGET)

 

all : $(TARGET)

 

clean :

$(CLEANUP)

 

$(TARGET):

$(CPP_FILE): $(SRC_FILE)

$(PP) $^ -o $@

 

$(ASM_FILE): $(CPP_FILE)

$(CC) $(ASM_FLAGS) $(COV_FLAGS) $^ -o $@

 

$(OBJ_FILE): $(ASM_FILE)

$(AS) $^ -o $@

 

$(EXE_FILE): $(OBJ_FILE)

$(CC) $^ $(LINK_GCOV) -o $@   //链接gcov静态库必须在test.o之后

3.关于链接的讨论

 

3.1链接顺序

 

 

test.o必须在gcov静态库(libgcov.a)之前被链接。因为test.o中引用的符号__gcov_init__gcov_merge_addgcov静态库中。否则,会出现如下错误。

# gcc -lgcov test.o -o test

test.o: In function `global constructors keyed to 0_main':

test.c:(.text+0xf9): undefined reference to `__gcov_init'

test.o:(.data+0x44): undefined reference to `__gcov_merge_add'

collect2: ld returned 1 exit status

为什么会出现错误?

——因为,gcc链接规定,链接时,若AB同时需要链接,不论A/B是目标文件还是库文件,若A中引用了B的符号,例如函数或者全局变量,则在链接时,必须将A写在B前面;因为,链接时从左向右搜索外部符号。

 

以下是中的解释,原文更贴切,此处不翻译。

 

链接目标文件。

On Unix-like systems, the traditional behavior of compilers and linkersis to search for external functionsfrom left to right in the object filesspecified on the command line. This means that theobject file whichcontains the definition of a function should appearafter any files whichcall that function.

链接库文件。

they are searchedfrom left to right a librarycontaining the definition of a function should appearafter any sourcefiles or object files which use it.

当然,现在有的编译器在链接时会搜索所有的目标文件或库文件,不考虑顺序。但并不是所有的编译器都这样做,因此,最好还是遵循从左向右的习惯写目标文件或库文件。

 

关于链接顺序,请参考"GCC的链接顺序"

 

3.2错误链接顺序的例子

 

当然,也可以使用collect2进行链接。调整其中链接的目标文件和库文件的顺序,若顺序不正确,也会出现类似的错误。有兴趣的读者可自行实验。此处只给出两个错误的例子。

 

 

1

$(COLLECT2) $(LINK_FLAGS) $(LDSO) $(CRT1) $(CRTI) $(CRTBEGIN) $(SEARCH_DIR)$(LINK_LIBS) $(OBJ_FILE) $(LINK_GCOV) $(CRTEND) $(CRTN) -o $@

/usr/lib/gcc/i386-redhat-linux/4.1.2/libgcov.a(_gcov.o): In function `gcov_version':

(.text+0xa6): undefined reference to `__stack_chk_fail_local'

/usr/lib/gcc/i386-redhat-linux/4.1.2/libgcov.a(_gcov.o): In function `__gcov_init':

(.text+0x17b): undefined reference to `atexit'

/usr/lib/gcc/i386-redhat-linux/4.1.2/libgcov.a(_gcov.o): In function `gcov_exit':

(.text+0x1471): undefined reference to `__stack_chk_fail_local'

collect2: ld returned 1 exit status

make: *** [test] Error 1

2

$(COLLECT2) $(LINK_FLAGS)$(OBJ_FILE) $(LDSO) $(CRT1) $(CRTI) $(CRTBEGIN) $(SEARCH_DIR)$(LINK_GCOV) $(LINK_LIBS) $(CRTEND) $(CRTN) -o $@

/usr/lib/crt1.o: In function `_start':

(.text+0x18): undefined reference to `main'

collect2: ld returned 1 exit status

make: *** [test] Error 1

为什么出现这样的错误,根据3.1的讨论,原因即知。当然,这里涉及到程序启动_init和结束_fini这两个函数,关于程序的启动和结束分析,将另文讨论。

 

4.额外的话

 

 

从链接命令来看,要链接生成一个可执行程序,需要crt1.o, crti.o, crtn.o, crtbegin.o, crtend.o文件一起链接。

/usr/lib/gcc/i386-redhat-linux/4.1.2/../../../crt1.o,即/usr/lib/crt1.o

/usr/lib/gcc/i386-redhat-linux/4.1.2/../../../crti.o,即/usr/lib/crti.o

/usr/lib/gcc/i386-redhat-linux/4.1.2/crtbegin.o

/usr/lib/gcc/i386-redhat-linux/4.1.2/crtend.o

/usr/lib/gcc/i386-redhat-linux/4.1.2/../../../crtn.o,即/usr/lib/crtn.o

如果是其他版本的gcc,该目录可能会有变化,例如在笔者的虚拟机上,gcc版本为gcc-4.4.1collect2/crtbegin.o/crtend.o的目录变为/usr/lib/gcc/i486-linux-gnu/4.4.1,如下。

/usr/lib/gcc/i486-linux-gnu/4.4.1/collect2

/usr/lib/gcc/i486-linux-gnu/4.4.1/../../../../lib/crt1.o

/usr/lib/gcc/i486-linux-gnu/4.4.1/crtbegin.o

这些目标文件的作用就是为main函数的运行建立运行环境,并提供在运行结束后释放资源。关于其详细解释和代码分析,将另文讨论。

 

5.小结

 

本文针对"Linux平台代码覆盖率测试-GCC插桩前后汇编代码对比分析"一文中的编译过程,重点讨论该编译过程的自动化,并讨论了链接顺序及其原因。编译自动化,关键还是makefile文件的编写。

 

 

Reference

for the GNU Compilers gcc and g++>, byBrian GoughRichard M. Stallman.

<Using the GNU Compiler Collection>, byRichard M. Stallman and the GCC Developer Community.



Technorati 标签: 覆盖率测试,GCC,gcov,gcov-dump


原文链接: http://blog.csdn.net/livelylittlefish/article/details/6448939

你可能感兴趣的:(GCC Coverage代码分析-编译过程自动化及对链接的解释)