ubuntu下LLCov安装心得

本文记录了在Ubuntu 12.04 32位系统上安装LLCov的过程,包括从获取源码到解决编译问题的详细步骤。在安装过程中,作者遇到了LLCov的依赖文件丢失、配置文件错误等问题,并逐一解决,最终成功运行LLCov。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

这几天由于公司项目需要,我下载了LLCov的源码,结合llvm、clang的代码进行使用。感觉现在LLCov的名气并不是很大,网上搜寻到的相关资料也不是非常的多。通过这几天的努力和同事的帮忙,我成功地在自己的机子上将LLCov运行了起来。现在打算将整个过程分享给大家。


首先给出LLCov的github源码地址: https://github.com/choller/LLCov

还有两个参考网站,后面会用到:

http://clang.llvm.org/get_started.html   clang安装教程

http://llvm.org/docs/GettingStarted.html#requirements   llvm安装教程


我的系统配置:

OS: Ubuntu 12.04  32bits

CPU: Intel Core i7  4cores


LLCov是个什么东西呢?从字面上理解,就是LLVM 编译环境下的代码Covery统计程序。使用过gcov的人应该对此并不陌生。在LLCov的README中找到的相关定义是这样子的:


LLCov is a C/C++ code instrumentation tool that can be used to measure block coverage.
Unlike the popular GCOV coverage tool for GCC, LLCov instruments each basic block in 
the code with a call to an external function (provided by an arbitrary runtime library). 
In addition, the instrumentation can be limited to parts of the code (e.g. individual 
files, functions or blocks) to improve performance and focus on the relevant code parts.
LLCov is based on LLVM and integrates into the Clang C/C++ compiler.

简言之,LLCov在执行程序过程中会不断调用自己的一些库函数完成代码运行的统计工作。


首先从github上得到源码,解压缩后进入文件夹LLCov-master。其中INSTALL文件介绍了整个安装的过程,HOWTO介绍的是怎样运行LLCov. 整个过程看起来简单粗暴,但由于可能LLCov最后的更新时间比较久远,很多步骤并不完整。下面可以听我慢慢说来。


具体步骤如下:

1.  在svn上check out llvm和clang的源代码。

这一步没有什么问题,按照INSTALL的步骤做即可:

svn co http://llvm.org/svn/llvm-project/llvm/branches/release_33 llvm
export LLVM_ROOT=`pwd`/llvm
cd llvm/tools/
svn co http://llvm.org/svn/llvm-project/cfe/branches/release_33 clang

注意如果在一些公司的内部网络,svn无法下载资源的话,请设置svn的代理。对于我的情况,我是在/etc/subversion/servers里面设置http-proxy-host和http-proxy-port。同时注意到这里设置了环境变量LLVM_ROOT为 LLCov-master/llvm,但是使用export只是在当前bash中使用。如果重启bash这个变量就被重置了。解决的办法是直接修改~/.bashrc文件。修改环境变量的方法我就不多说了,上网搜一下都有的。


2. 在clang和llvm的代码中打入LLCov的补丁:

cd clang
patch -p0 < ../../../clang-llcov.patch
cd ../..
patch -p0 < ../llvm-llcov.patch
这一步也没有什么问题。并且从这一步我们就更清楚LLCov进行代码统计的运行机制了:LLCov会修改LLVM和clang的源代码,在里面加入一些自己的函数。LLVM和clang成功编译并执行的时候就会调用一些LLCov的库函数完成相应的功能。有点类似与寄生虫。有兴趣的朋友打开那两个patch文件,看看到底对哪些文件进行了修改。


3. 拷贝LLCov.cpp

cd ..
make srcinstall
接下来慢慢地就开始出问题了。首先cd ..后回到LLCov-master目录,输入make srcinstall的时候可能会遇到类似 “/build/Release+Asserts/bin/clang: No such file or directory”的问题。我们打开LLCov-master下面的Makefile文件看看srcinstall后面写的是:

srcinstall: $(LLCOV_CPP)
	cp $(LLCOV_CPP) $(LLCOV_CPP_INST_DIR)
其中变量LLCOV_CPP指的是本目录下的LLCov.cpp文件,LLCOV_CPP_INST_DIR是目录$(LLVM_ROOT)/build/Release+Asserts/lib/clang/$(OS)/$(ARCH),LLVM_ROOT是我们刚才设置的,OS是从系统获取的,我的是linux,ARCH是在makefile中复制的,我的是i386. 所以srcinstall做的事情就是把LLCov.cpp拷贝到了这个LLCOV_CPP_INST_DIR目录下。所以即使找不到clang也没关系。事实上只有后面将clang和llvm编译完后才会出现Release+Asserts文件夹的。


4. 编译clang和llvm

坑爹的一步开始了。从INSTALL文件中我们知道我们需要进入llvm文件夹,再进行configure+make的配置编译过程。但是这样做后你会发现根本找不到LLCOV_CPP_INST_DIR的路径。其中build文件夹是不存在的。这时候就需要上面说到的那两个网站了。从网站中我们得知,我们需在llvm文件夹下先创建build文件夹,然后再在文件夹里面配置。所以在llvm目录下我们需要执行以下步骤:

mkdir build
cd build

进入build文件夹下后进行configure,问题不大。我之前是因为自己已经使用apt-get安装过clang了,所以在configure执行过程中会提示我需要 "Rerun CC=c compiler CXX= c++ compiler ../configure",这里只要把将CC设置为自己的gcc编译器,将CXX设置为自己的g++编译器即可。如:

CC=/usr/bin/gcc-4.6 CXX=/usr/bin/g++-4.6 ../configure

剧透一下,在后面你会发现整个LLCov的安装提示非常地乱:在一些文件路径中使用的是Release+Asserts文件夹,有些里面又使用的是Debug+Asserts文件夹。在这里我们统一使用了Release+Asserts文件夹,即将clang编译成Release版本,具体做法是在configure后面加入参数 --enable-optimized。


configure成功后就接着make吧,我是4核的,所以开四线程make就可以了:

make -j4

经过漫长的等待,终于编译好了。


5. 编译LLCov相关文件

先看INSTALL上的说明:

# Switch back to LLCov repository and build runtime libraries
cd ..
make
这一步做的是生成一个静态链接库libclang_rt.llcov.a还有一个动态链接库libllcov32.so。这两个库作用是一样的。在经过LLCov修改后的clang编译器链接的时候把这两个库中的一个链接进去。注意到因为在INSTALL中并没有提及要创建build文件夹一点,所以我们需要cd ../..回到LLCov目录下再进行make.


这时候你可能会遇到这样的一条错误:

mkdir -p bin_linux
make: *** No rule to make target `bin_linux/llcov_print32.o', needed by `/home/xxxx/LLCov-master/llvm/build/Release+Asserts/lib/clang/linux/i386/libclang_rt.llcov.a'.  Stop.

make后面紧跟着的三个 '*'表示这是一个致命错误。这句话的意思是在生成 llcov_print32.o的时候在makefile中指定的依赖文件找不到了。我一开始在这里卡了挺久的。打开LLCov-master文件目录下的makefile一看究竟:(以下代码节选自makefile,我只是挑了一些关键的代码出来)

all: lib

lib: $(LIBLLCOV_A)

$(BIN)/%$(SUFF).o: %.cc $(MAKEFILE)
    $(CXX) $(PIE) $(CFLAGS) -std=c++0x -fPIC -c -O2 -fno-exceptions -o $@ -g $< $(LLCOV_FLAGS)

$(BIN)/%$(SUFF).o: %.c $(MAKEFILE)
    $(CC) $(PIE) $(CFLAGS) -fPIC -c -O2 -o $@ -g $< $(LLCOV_FLAGS) $(LIBLLCOV_A): $(BIN) $(LIBLLCOV_OBJ) $(MAKEFILE)
	mkdir -p $(LIBLLCOV_INST_DIR)
	ar ru $@ $(LIBLLCOV_OBJ)
	$(CXX) -shared $(CFLAGS) $(LIBLLCOV_OBJ) $(LD_FLAGS) -o $(BIN)/libllcov$(SUFF).so

$(LIBLLCOV_A): $(BIN) $(LIBLLCOV_OBJ) $(MAKEFILE)
    mkdir -p $(LIBLLCOV_INST_DIR)
    ar ru $@ $(LIBLLCOV_OBJ)
    $(CXX) -shared $(CFLAGS) $(LIBLLCOV_OBJ) $(LD_FLAGS) -o $(BIN)/libllcov$(SUFF).so

如果看不懂的同学可以上网搜一下makefile的语法规则,简单了解一下就能读懂了。这里表示的是make最终需要生成的文件是LIBLLCOV_A变量指定的文件。查看里面的变量赋值情况,我发现LIBLLCOV_A指的就是libclang_rt.llcov.a。然后看到下面的LIBLLCOV_A生成规则,里面提到一个LIBLLCOV_OBJ文件,查了一下,就是LLCov-master/bin_linux中的llcov_print32.o(64位的机子就是llcov_print64.o)。然后再看看llcov_print32.o的生成规则,里面提到需要 “%.cc”,就是该文件目录下面的一些.cc文件。然后打开当前目录,你会发现只有两个.cc文件:

xxxx:newLLCov$ ls *.cc
llcov_assert.cc  llcov_network.cc
我就是在这里卡了很久。怎么也看不出哪里出现了问题。这时同事到github网站上重新看了一下文件列表:

好吧,乖乖的,有没有发现里面有个llcov_print.cc?这还是一个链接文件(相当于windows中的快捷方式)。用svn检出的时候这个文件就丢失了...你可以点进去看看,就知道其实它是链接到了旁边的llcov_assert.cc上去了。所以我们在LLCov-master本地需要做的就是重新链接这个文件:

XXXX:LLCov-master$ ln -s llcov_assert.cc llcov_print.cc
XXXX:LLCov-master$ ls
bin_linux          example.cpp  INSTALL           llcov_assert.cc  llcov_network.cc  llvm              Makefile
clang-llcov.patch  HOWTO        LICENSE-LLVM.TXT  LLCov.cpp        llcov_print.cc    llvm-llcov.patch  README

好,这回就有了llcov_print.cc文件了吧?再make一次试试看。我接下来就没什么问题了。打开bin_linux文件夹,你会发现里面有libllcov32.so,在Release+Asserts/lib/clang/$OS/$ARCH/ 下也会有libclang_rt.llcov.a库了。至此,INSTALL的工作就完成了。


但是我们的工作还没完成!


为什么呢?这时候我们需要试一下组装好的clang能不能用吧?打开HOWTO说明文件。上面写着在使用clang编译链接的时候需要加上刚才说的两个库文件中的一个。不过在我们这种情况下,使用-fllcov标志就可以隐式包含这个库了。所以我们要做的是按照HOWTO上面说的运行clang++来编译链接给出来的测试文件example.cpp:(注意自己的clang路径)

XXXX:LLCov-master$ $LLVM_ROOT/build/Release+Asserts/bin/clang++ -fllcov -g -o example example.cpp
XXXX:LLCov-master$ ./example 
One argument

本来想着应该就没问题了,结果现在发现LLCov的辅助结果压根就没显示!都到这份上了出这样的问题实在让人觉得很沮丧啊...睡了个觉吃了个饭,回来继续看...


我的思路是这样的:在HOWTO文件中它说LLCov会出现提示信息,告诉你代码执行到了哪一句对吧?那个"Block executed in file XXXX, line XX"就是。那么现在没有输出这句话,应该就是写输出这句话的函数有问题,或者没有被执行。是哪个文件有这个函数呢?我想到函数里应该有printf语句,里面会包含"Block executed"等字符串。于是我用了最笨的grep的办法搜寻文件的关键字:

XXXX:LLCov-master$ grep -r "Block executed" . 
./llcov_assert.cc:          fprintf(stderr, "Assertion failure: LLCov: Block executed in file %s, line %u (function %s, line-relative block %u)\n", filename, line, funcname, relblock);
./llcov_print.cc:           fprintf(stderr, "Assertion failure: LLCov: Block executed in file %s, line %u (function %s, line-relative block %u)\n", filename, line, funcname, relblock);
Binary file ./bin_linux/libllcov32.so matches
Binary file ./bin_linux/llcov_print32.o matches
Binary file ./example matches
Binary file ./llvm/build/Release+Asserts/lib/clang/linux/i386/libclang_rt.llcov.a matches
./llcov_network.cc:         fprintf(stderr, "Assertion failure: LLCov: Block executed in file %s, line %u (function %s, line-relative block %u)\n", filename, line, funcname, relblock);
./HOWTO:Block executed in file example.cpp, line 3
./HOWTO:Block executed in file example.cpp, line 6
./HOWTO:Block executed in file example.cpp, line 16
./HOWTO:Block executed in file example.cpp, line 19
./HOWTO:Block executed in file example.cpp, line 20
./HOWTO:Block executed in file example.cpp, line 3
./HOWTO:Block executed in file example.cpp, line 10
./HOWTO:Block executed in file example.cpp, line 16
./HOWTO:Block executed in file example.cpp, line 19
./HOWTO:Block executed in file example.cpp, line 20
./HOWTO:Block executed in file example.cpp, line 3
./HOWTO:Block executed in file example.cpp, line 10
./HOWTO:Block executed in file example.cpp, line 16
./HOWTO:Block executed in file example.cpp, line 17
./HOWTO:Block executed in file example.cpp, line 20
./HOWTO:Block executed in file example.cpp, line 3
./HOWTO:Block executed in file example.cpp, line 13
./HOWTO:Block executed in file example.cpp, line 16
./HOWTO:Block executed in file example.cpp, line 17
./HOWTO:Block executed in file example.cpp, line 20

因为我grep没有设置过滤掉二进制文件,所以会有一些二进制文件里面也有该字符串信息。不过还是发现./example文件中也有这个关键字。这应该说明我们的clang文件链接是没有问题的吧。好从上面知道那几个.cc文件就是问题所在了。llcov_network.cc没有发现相关的信息(里面有关键字,但是是输出到stderr上的,和我们没有太大关系)。在llcov_assert.cc中我们找到了writeData函数:

inline __attribute__((always_inline))
void writeData(const char* funcname, const char* filename, uint32_t line, uint32_t relblock) {
    static std::set< std::tuple<uint32_t, uint32_t, std::string> > seen;
    std::tuple<uint32_t, uint32_t, std::string> tup(line, relblock, std::string(filename));
    if (seen.find(tup) == seen.end()) {
        fprintf(filefd, "file:%s line:%u relblock:%u\n", filename, line, relblock);
        /*fwrite(funcname, strlen(funcname)+1, 1, filefd);
        fwrite(filename, strlen(filename)+1, 1, filefd);
        fwrite(&line, sizeof(uint32_t), 1, filefd);
        fwrite(&relblock, sizeof(uint32_t), 1, filefd);*/
        fflush(filefd);
        seen.insert(tup);
    }
}

里面有一个函数:

<pre name="code" class="cpp">fprintf(filefd, "file:%s line:%u relblock:%u\n", filename, line, relblock);


这句应该就是输出函数了。不过它是输出到文件里的,文件由filefd指定。所以它并没有直接输出到终端。而且后面的字符串输出也和HOWTO上说的不符(这不坑爹嘛!)我在这里下面加了一句:

printf( "file:%s line:%u relblock:%u\n", filename, line, relblock);

让它同时输出到终端上。


再看看assert.cc后面的执行函数:

extern "C" void llvm_llcov_block_call(const char* funcname, const char* filename, uint32_t line, uint32_t relblock) {
    if (filefd != NULL) {
        writeData(funcname, filename, line, relblock);
    } else if (getenv("LLCOV_ABORT")) {
	    fprintf(stderr, "Assertion failure: LLCov: Block executed in file %s, line %u (function %s, line-relative block %u)\n", filename, line, funcname, relblock);
        abort();
    } else if (getenv("LLCOV_STDERR")) {
        fprintf(stderr, "file:%s line:%u func:%s relblock:%u\n", filename, line, funcname, relblock);
    } else if (getenv("LLCOV_FILE")) {
        filefd = fopen(getenv("LLCOV_FILE"), "a");
        if (filefd != NULL) {
            writeData(funcname, filename, line, relblock);
        }
    }
}

我们来看看writeData什么时候被调用。当filefd 不是NULL的时候,writeData就被调用。但是最前面有一句赋值把filefd初始化为NULL了。再看后面这句:

else if (getenv("LLCOV_FILE")) {
        filefd = fopen(getenv("LLCOV_FILE"), "a");
        if (filefd != NULL) {
            writeData(funcname, filename, line, relblock);
        }
    }

这里filefd才被赋值,但是前提是getenv函数返回不为空。这个函数作用是查看LLCOV_FILE变量指定的文件是否存在。然后我才明白过来...我们压根就没有设置LLCOV_FILE变量!所以getenv函数返回0,writeData就不会被执行了。但是在整个LLCov编译安装过程中也没提过这个变量。实在是让人无语啊......好吧然后我就地创建了一个llcov.log文件并将地址赋给了LLCOV_FILE。这时在LLCov-master下make clean 再make 一下生成新的.so文件。然后重复上述步骤编译链接example.cpp,最后执行./example得到结果:

file:/usr/lib/gcc/i686-linux-gnu/4.6/../../../../include/c++/4.6/iostream line:75 relblock:0
file:example.cpp line:19 relblock:0
file:example.cpp line:3 relblock:0
One argument
file:example.cpp line:6 relblock:0
file:example.cpp line:16 relblock:0
file:example.cpp line:19 relblock:1
这时结果终于出来了,同时在你创建的llcov.log中也有该信息。


写到这里就差不多了。因为自己对linux了解得不是很多,所以现在只求先能解决问题,里面的一些原理和实现都不是特别懂,也没法往深层次地去写。希望能对大家有点帮助吧。再说一句,这玩意儿装起来实在太坑人了...




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值