gcc交叉編譯,gcov/gcovr生成html覆蓋率報告
1 背景
從質量的角度考慮,需要評估代碼的執行情況,現對linux平臺的代碼生成覆蓋率報告。
目前公司采用CodeSourcery GCC 4.3的編譯工具鏈編譯。
在windows上完成交叉編譯,在linux平臺上運行。
2 gcov簡介
關于覆蓋率,常用的方法是 gcc編譯套件自帶gcov. 查閱gcc的官方網站也有介紹。網址如下:
Cross-profiling (Using the GNU Compiler Collection (GCC))
關于gcov的使用說明,有一部分是交叉編譯的使用方法,現在摘錄如下:
10.5 Data File Relocation to Support Cross-Profiling Running the program will cause profile output to be generated. For each source file compiled with -fprofile-arcs, an accompanying .gcda file will be placed in the object file directory. That implicitly requires running the program on the same system as it was built or having the same absolute directory structure on the target system. The program will try to create the needed directory structure, if it is not already present. To support cross-profiling, a program compiled with -fprofile-arcs can relocate the data files based on two environment variables: GCOV_PREFIX contains the prefix to add to the absolute paths in the object file. Prefix can be absolute, or relative. The default is no prefix. GCOV_PREFIX_STRIP indicates the how many initial directory names to strip off the hardwired absolute paths. Default value is 0. Note: If GCOV_PREFIX_STRIP is set without GCOV_PREFIX is undefined, then a relative path is made out of the hardwired absolute paths. For example, if the object file /user/build/foo.o was built with -fprofile-arcs, the final executable will try to create the data file /user/build/foo.gcda when running on the target system. This will fail if the corresponding directory does not exist and it is unable to create it. This can be overcome by, for example, setting the environment as ‘GCOV_PREFIX=/target/run’ and ‘GCOV_PREFIX_STRIP=1’. Such a setting will name the data file /target/run/build/foo.gcda. You must move the data files to the expected directory tree in order to use them for profile directed optimizations (-fprofile-use), or to use the gcov tool.
其中提到了兩個環境變量GCOV_PREFIX和GCOV_PREFIX_STRIP,這兩個變量在下面的例子中會使用到。
3 gcov的基本原理
百言不如一圖,引用vaughn.huang中的介紹:
- 在 GCC 編譯的時加入特殊的編譯選項,生成可執行文件,和 *.gcno;
- 運行(測試)生成的可執行文件,生成了 *.gcda 數據文件;
- 有了 *.gcno 和 *.gcda,通過源碼生成 gcov 文件,最后生成代碼覆蓋率報告。
4 使用演示
4.1 示例代碼
1 // example.cpp 2 3 int foo(int param) 4 { 5 if (param) 6 { 7 return 1; 8 } 9 else 10 { 11 return 0; 12 } 13 } 14 15 int main(int argc, char* argv[]) 16 { 17 foo(0); 18 19 return 0; 20 }
4.2 在windows上交叉編譯
E:\ddd_terry\coverage\arm>arm-none-linux-gnueabi-g++.exe -fprofile-arcs -ftest-coverage -O0 example.cpp -o program
得到文件如下:
E:\ddd_terry\coverage\arm>dir 驅動器 E 中的卷是 新加卷 卷的序列號是 1CCD-08B2 E:\ddd_terry\coverage\arm 的目錄 2022/07/20 14:50 <DIR> . 2022/07/20 14:50 <DIR> .. 2022/07/20 14:47 92 build.bat 2022/07/20 14:48 206 example.cpp 2022/07/20 14:48 568 example.gcno 2022/07/20 14:48 17,065 program 4 個文件 17,931 字節 2 個目錄 357,037,658,112 可用字節
4.3 在linux上運行程序
根據gcov關于交叉編譯的解釋,設置環境變量,設置好的變量如下:
說明:
GCOV_PREFIX表示產生*gcda的文件路徑。本處放到/var/log/gcov路徑
GCOV_PREFIX_STRIP表示裁剪掉編譯時,絕對路徑中的級數。具體這個數值的配置取決于編譯時的目錄結構。
比如本例:example在編譯的時候,使用的路徑是:E:\ddd_terry\coverage\arm>,可以看到是3級目錄。現在需要在program執行的時候,忽略掉這3組目錄,因為linux系統不認識這個目標路徑。
在linux運行program程序后,會生成*.gcda文件。跳轉到/var/log/gcov目錄,可以看到example.gcda文件。
把*.gcda文件拷貝到windows磁盤中。
E:\ddd_terry\coverage\arm>dir 驅動器 E 中的卷是 新加卷 卷的序列號是 1CCD-08B2 E:\ddd_terry\coverage\arm 的目錄 2022/07/20 15:04 <DIR> . 2022/07/20 15:04 <DIR> .. .. 2022/07/20 14:47 92 build.bat 2022/07/20 14:48 206 example.cpp 2022/07/20 15:04 184 example.gcda 2022/07/20 14:48 568 example.gcno 2022/07/20 14:48 17,065 program 5 個文件 18,115 字節 2 個目錄 357,037,658,112 可用字節 E:\ddd_terry\coverage\arm>
4.4 gcov生成覆蓋率報告
使用gcov可以直接生成覆蓋率報告
E:\ddd_terry\coverage\arm>gcov example.gcda File 'example.cpp' Lines executed:85.71% of 7 example.cpp:creating 'example.cpp.gcov'
得到gcov的文本文件。內容如下:
-: 0:Source:example.cpp
-: 0:Graph:example.gcno
-: 0:Data:example.gcda
-: 0:Runs:1
-: 0:Programs:1
-: 1:// example.cpp
-: 2:
1: 3:int foo(int param)
-: 4:{
1: 5: if (param)
-: 6: {
#####: 7: return 1;
-: 8: }
-: 9: else
-: 10: {
1: 11: return 0;
-: 12: }
-: 13:}
-: 14:
1: 15:int main(int argc, char* argv[])
-: 16:{
1: 17: foo(0);
-: 18:
1: 19: return 0;
-: 20:}這個文件太原始,沒有整體的報告。測試的兄弟們不答應。
4.5 lcov生成覆蓋率報告
Linux Test Project - Coverage ? lcov (sourceforge.net)
lcov是一個linux平臺的工具,沒有看到windows平臺的介紹,這顯然也不符合需求。
不過,也不是沒有辦法。對于windows平臺需要借助wsl的環境。
但生成失敗了。主是因為windows交叉編譯使用的是gcc是4.3的版本,wsl環境中的gcc安裝的9.4的版本。兩個不匹配。
如果在wsl安裝gcc4.3的老版本,太折騰了。而且ubuntu 20.04LTS自帶的軟件源中也沒有這么老的gcc版本。能否正常安裝上,還是一個問題。果斷放棄了。
有興趣的同學可以繼續研究。
4.6 gcovr生成覆蓋率報告
gcovr是一個python插件,幫生成覆蓋率報告需要先安裝Python。筆者使用的是Python 3.10.5 x64位版本。安裝過程略。
gcover的官方網站如下:
gcovr — gcovr 5.1 documentation
4.6.1 gcovr的安裝
Installation
Gcovr is available as a Python package that can be installed via pip.
Install newest stable gcovr release from PyPI:
pip install gcovr
Install development version from GitHub:
pip install git+https://github.com/gcovr/gcovr.git
根據官方的提示安裝完成。需要注意的時,這里要下載源代碼。因為后面將會用到。
4.6.2 gcovr生成html
You can also generate detailed HTML reports:
gcovr --html-details coverage.html
Gcovr will create one HTML report per source file next to the coverage.html summary.
根據上面的指令操作生成html
E:\ddd_terry\coverage\arm>gcovr -v --html-details coverage.html
html如下:
看到沒有,啥也沒有!List of functions中:main函數都沒有。
加上-v參數再試一次,看看gcov插件做了什么?
E:\ddd_terry\coverage\arm>gcovr -v --html-details coverage.html
(DEBUG) Filters for --root: (1)
(DEBUG) - re.compile('^E:\\\\ddd_terry\\\\coverage\\\\arm\\\\'
(DEBUG) Filters for --filter: (1)
(DEBUG) - DirectoryPrefixFilter(E:/ddd_terry/coverage/arm/)
(DEBUG) Filters for --exclude: (0)
(DEBUG) Filters for --gcov-filter: (1)
(DEBUG) - AlwaysMatchFilter()
(DEBUG) Filters for --gcov-exclude: (0)
(DEBUG) Filters for --exclude-directories: (0)
(DEBUG) Scanning directory . for gcda/gcno files...
(DEBUG) Found 2 files (and will process 1)
(DEBUG) Pool started with 1 threads
(DEBUG) Processing file: E:\ddd_terry\coverage\arm\example.gcda
(DEBUG) Running gcov: 'gcov --help' in '.'
(DEBUG) Running gcov: 'gcov --help-hidden' in '.'
(DEBUG) Running gcov: 'gcov E:\ddd_terry\coverage\arm\example.gcda --branch-counts --branch-probabilities --preserve-paths --object-directory E:\ddd_terry\coverage\arm' in 'E:\ddd_terry\coverage\arm'
(WARNING) GCOV produced the following errors processing E:\ddd_terry\coverage\arm\example.gcda:
E:\ddd_terry\coverage\arm/E:\ddd_terry\coverage\arm\example.gcno:cannot open graph file
(gcovr could not infer a working directory that resolved it.)
(DEBUG) Gathered coveraged data for 0 files
從上面的控制臺輸出,看到路徑出現問題。生成了0個文件的報告。
4.6.3 為什么沒有生成報告?修改gcovr
查閱了gcovr網站上的所有內容。其中在講述Filters時,有一段話引起了注意:
Using Filters — gcovr 5.1 documentation
再看DEBUG信息中的路徑,和上面的斜杠說明不符。
(DEBUG) Processing file: E:\ddd_terry\coverage\arm\example.gcda
找到gcovr中的python代碼,嘗試把它改掉。
打開C:\Users\$用戶名$\AppData\Local\Programs\Python\Python310\Lib\site-packages\gcovr\gcov.py文件,在第295行增加如下代碼
abs_filename = abs_filename.replace("\\","/") logger.debug(f"Processing file1: {abs_filename}")
更改后的效果如下圖:
4.6.4 gcovr再次生成報告
E:\ddd_terry\coverage\arm>gcovr -v --html-details coverage.html
(DEBUG) Filters for --root: (1)
(DEBUG) - re.compile('^E:\\\\ddd_terry\\\\coverage\\\\arm\\\\')
(DEBUG) Filters for --filter: (1)
(DEBUG) - DirectoryPrefixFilter(E:/ddd_terry/coverage/arm/)
(DEBUG) Filters for --exclude: (0)
(DEBUG) Filters for --gcov-filter: (1)
(DEBUG) - AlwaysMatchFilter()
(DEBUG) Filters for --gcov-exclude: (0)
(DEBUG) Filters for --exclude-directories: (0)
(DEBUG) Scanning directory . for gcda/gcno files...
(DEBUG) Found 2 files (and will process 1)
(DEBUG) Pool started with 1 threads
(DEBUG) Processing file: E:\ddd_terry\coverage\arm\example.gcda
(DEBUG) Processing file1: E:/ddd_terry/coverage/arm/example.gcda
(DEBUG) Running gcov: 'gcov --help' in '.'
(DEBUG) Running gcov: 'gcov --help-hidden' in '.'
(DEBUG) Running gcov: 'gcov E:/ddd_terry/coverage/arm/example.gcda --branch-counts --branch-probabilities --preserve-paths --object-directory E:/ddd_terry/coverage/arm' in 'E:\ddd_terry\coverage\arm'
(DEBUG) Finding source file corresponding to a gcov data file
currdir E:\ddd_terry\coverage\arm
gcov_fname E:\ddd_terry\coverage\arm\example.cpp.gcov
source_fname E:/ddd_terry/coverage/arm/example.gcda
root E:\ddd_terry\coverage\arm
fname E:\ddd_terry\coverage\arm\example.cpp
(DEBUG) Parsing coverage data for file E:\ddd_terry\coverage\arm\example.cpp
(DEBUG) Gathered coveraged data for 1 files看到沒有,example.cpp文件中的函數被列出來的。
5 總結
1、在windows上,使用gcc_4.3的編譯源代碼,生成可執行文件program和*.gcno文件
2、把可執行文件program拷貝到linux目標機器
3、在linux目標機器上,正確配置GCOV_PREFIX和GCOV_PREFIX_STRIP環境變量
4、在linux上,運行可執行文件program。把生成*.gcda文件拷貝到windows機器
5、在windows環境上,準備gcovr環境,并修改gcovr模塊中的python源代碼
6、通過gcovr生成html報告。
6 注意
在過程中,也遇到了一些坑,主要有:
1、如果安裝了多個gcc版本(CodeSourcery/MinGW等),且在windows環境變量中可以找到多個gcov.exe,請清理環境,確認只有gcc4.3的版本。(筆者使用的是這個版本)
2、gcov.exe的版本與gcc的版本要相同。否則會出現4.5節的報錯。
參考資料
Cross-profiling (Using the GNU Compiler Collection (GCC))
Linux Test Project - Coverage ? lcov (sourceforge.net)
Using Filters — gcovr 5.1 documentation
Linux 交叉編譯使用代碼覆蓋GCOV及LCOV - vaughn.huang - 博客園 (cnblogs.com)










浙公網安備 33010602011771號