Gcov、clov统计代码覆盖率,模拟fuzz统计代码覆盖率趋势

本文描述了一个实验,通过Python脚本模拟fuzz测试,以每10秒为步长统计代码覆盖率,旨在评估种子变异策略。作者详细介绍了如何使用GCC编译器的test_coverage和profile_arcs选项生成覆盖率数据,以及如何利用lcov和genhtml进行可视化分析。
摘要由CSDN通过智能技术生成

1.目的

我想实现如上图效果,统计随着fuzz的时间增加,统计代码覆盖率的趋势,这样可以反映出我的种子变异策略的好坏,是否能覆盖更多的API。

在titanfuzz中,他们给每一个API一分钟的fuzz时间,然后把所有api循环一边,然后统计最后的代码覆盖率。但是如何得到10、20、30等直接的代码覆盖率。这也是我想要知道的问题。

因此,我对该实验进行了模拟。

2.测试代码

我目录下有一个main.cpp,内容如下:

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[]) {
    // 手动编写 if-else if 语句,从 0 扩展到 20
    if (argc != 2) {
        printf("Usage: %s <flag>\n", argv[0]);
        return 1;
    }
    int flag = atoi(argv[1]);
    if (flag >= 1) {
        printf("flag>=1\n");
        if (flag >= 2) {
            printf("flag>=2\n");
            if (flag >= 3) {
                printf("flag>=3\n");
                if (flag >= 4) {
                    printf("flag>=4\n");
                    if (flag >= 5) {
                        printf("flag>=5\n");
                        if (flag >= 6) {
                            printf("flag>=6\n");
                            if (flag >= 7) {
                                printf("flag>=7\n");
                                if (flag >= 8) {
                                    printf("flag>=8\n");
                                    if (flag >= 9) {
                                        printf("flag>=9\n");
                                        if (flag >= 10) {
                                            printf("flag>=10\n");
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    return 0;
}

代码的逻辑很简单,就是根据flag的值,嵌套判断,并输出结果。为什么要写成这样,这是为了方便使用gcov统计代码覆盖率。 可以直观的看出 代码覆盖率的值。

然后编译代码:

gcc -o main main.cpp

查看代码逻辑是否正常:

3.使用gcov选项编译代码

如何使用gcc编译时开启代码覆盖率统计,推荐大家看这篇文章

https://blog.csdn.net/yanxiangyfg/article/details/80989680

删除上一步编译好的main,重新编译,添加代码覆盖率统计选项。

-ftest-coverage在编译的时候产生.gcno文件,它包含了重建基本块图和相应的块的源码的行号的信息。
-fprofile-arcs在运行编译过的程序的时候,会产生.gcda文件,它包含了弧跳变的次数等信息。

gcc -o main main.cpp -fprofile-arcs -ftest-coverage

此时目录下应该有三个文件,main,main.cpp,和main.gcno。

其中main.gcno,只要源码不变,编译出来永远不改变.

4.统计代码覆盖率

运行下面的命令产生代码覆盖率数据文件。

gcov main.cpp

可以看出代码覆盖率为0,因为我们还没有执行main,所以代码覆盖率还没有统计。

注意:编译一次后,后续只要你调用一次main,代码覆盖率就会累计增加,所以我们的目的是,执行10s后,就应该要统计一次代码覆盖率,不然等1分钟后再统计,就是第60秒的结果。

 执行gcov main.cpp 后,文件夹下面会多出一个 main.cpp.gcov文件,代码覆盖率数据,其数据的来源为main.gcda。

而main.gcda,是每次调用main时就会更新。

 可以看出,执行main 1后,文件夹下面就有了main.gcda, 后续只要调用了main,main.gcda的数据就会更新。

cat main.cpp.gcov

通过该命令查看代码覆盖率的情况

现在,我们看代码覆盖率,依然还是0,因为虽然我们执行了main,但是想查看执行后的代码覆盖率,还需要手动执行

gcov main.cpp

再次查看代码覆盖率,可以看出,每行最前面有数字1,代码这行代码被执行了1次。而后续的if由于条件不满足,没有被触发,而我们模拟fuzz,想突破这些路径,就是改变flag的值,当然这个例子很简单,只需要让flag的值不断变大就可以实现,真实环境里面的条件判断是很复杂的,所以我们需要一个很好的变异策略,引导种子变异

可以看出,每次执行main后,想要查看代码覆盖率,都要手动执行gcov命令,而且这样的方式也不太容易观察,所以我们一般使用clov生成可视化的代码覆盖率。

lcov -c -d . -o main.info

现在文件夹下面多了一个main.info,这个就是clov根据.gcno .gcda生成的,用于存放覆盖率数据的。

然后执行下面的代码,去得到web版的代码覆盖率。

genhtml -o html_output main.info

进入html_output,打开index.html即可。或者在当前目录下开一个web服务器。

python -m http.server 8080

 5.实现代码覆盖率趋势统计

回到我们最初的目的,我们想统计一分钟内的代码覆盖率趋势,并且是按照10s、20s、30s这样的步长去统计,但是如果我们直接写个脚本,一分钟内不断执行main,给出不同的flag值。那么最后得到的代码覆盖率其实是总体的代码覆盖率,也就是这一分钟内,所有测试用例所触发的代码行数统计。

因此,我们只有设置一个定时器,以10s为步长,暂停fuzz,然后执行下面的命令。记录当前的代码覆盖率。

gcov main.cpp
lcov -c -d . -o main.info
genhtml -o html_output main.info

 因此,我写了一个python脚本来实现这个功能。其中flag是随机从1-10生成。统计10s、20s、30s...后的代码覆盖率。

import re
import subprocess
import threading
import time
import random

flag = 0

# 定义输出函数
def print_output():
    # 获取当前时间
    current_time = time.strftime("%H:%M:%S", time.localtime())
    print("Output at", current_time)


# 定义定时任务函数
def fuzzer():
    print("Fuzz start")
    count = 0
    result_list = []
    global flag


    while True:
        time.sleep(1)
        count += 1
        print("fuzzing...")
        my_exec()
        if count % 10 == 0:
            # 在每个时间点执行代码
            print(f"Measuring coverage at {count}s...")
            # 统计代码覆盖率
            # 这里使用 coverage 工具来统计代码覆盖率
            code_lines = save_coverage_info()
            result_list.append(code_lines)
            flag = generate_random_number(1,10)
            if count == 60:
                break
    print("fuzz end")
    print(result_list)


    # 创建定时器,每 10 秒执行一次 my_exec 函数


# 定义函数,生成指定范围内的随机整数
def generate_random_number(start, end):
    return random.randint(start, end)

def my_exec():
    global flag
    print("now flag is ", flag)

    # 调用 C 程序并传递参数
    process = subprocess.Popen(["./main", str(flag)], stdout=subprocess.PIPE)
    # 获取输出
    output, error = process.communicate()
    # 打印输出
    print(output.decode("utf-8"))


def save_coverage_info():
    # 执行 gcov 命令
    lines_covered = 0
    gcov_command = "gcov main.cpp"
    result = subprocess.run(gcov_command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
    if result.returncode == 0:
        # 打印命令的输出
        print("Command output:")
        print(result.stdout)
    else:
        # 打印错误信息
        print("Error occurred:")
        print(result.stderr)

    # 执行 lcov 命令
    lcov_command = "lcov -c -d . -o main.info"
    result = subprocess.run(lcov_command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
    if result.returncode == 0:
        # 打印命令的输出
        print("Command output:")
        print(result.stdout)

    else:
        # 打印错误信息
        print("Error occurred:")
        print(result.stderr)

    # 执行 genhtml 命令
    genhtml_command = "genhtml -o html_output main.info"
    result = subprocess.run(genhtml_command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
    if result.returncode == 0:
        # 打印命令的输出
        print("Command output:")
        print(result.stdout)
        pattern = r"lines\.{6}: (\d+\.\d+)% \((\d+) of (\d+) lines\)"
        matches = re.findall(pattern, result.stdout)
        # 打印匹配结果
        for match in matches:
            # coverage_percentage = float(match[0])
            lines_covered = int(match[1])
            # total_lines = int(match[2])
            # print("Lines covered:", lines_covered)
    else:
        # 打印错误信息
        print("Error occurred:")
        print(result.stderr)

    return lines_covered

if __name__ == '__main__':
    fuzzer()

执行结果如下:

rm -rf html_output/ main main.cpp.g* main.info main.g*

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值