brintos

brintos / linux-shallow public Read only

0
0
Text · 12.4 KiB · 629154d Raw
360 lines · plain
1.. SPDX-License-Identifier: GPL-2.02 3.. include:: ../disclaimer-zh_CN.rst4 5:Original: Documentation/dev-tools/kcov.rst6:Translator: 刘浩阳 Haoyang Liu <tttturtleruss@hust.edu.cn>7 8KCOV: 用于模糊测试的代码覆盖率9==============================10 11KCOV 以一种适用于覆盖率引导的模糊测试的形式收集和暴露内核代码覆盖率信息。12一个正在运行的内核的覆盖率数据可以通过 ``kcov`` 调试文件导出。覆盖率的收集是基13于任务启用的,因此 KCOV 可以精确捕获单个系统调用的覆盖率。14 15要注意的是 KCOV 不是为了收集尽可能多的覆盖率数据。而是为了收集相对稳定的覆盖率16,这是系统调用输入的函数。为了完成这个目标,它不收集软硬中断的覆盖率(除非移除17覆盖率收集被启用,见下文)以及内核中固有的不确定部分的覆盖率(如调度器,锁定)18 19除了收集代码覆盖率,KCOV 还收集操作数比较的覆盖率。见 "操作数比较收集" 一节20查看详细信息。21 22除了从系统调用处理器收集覆盖率数据,KCOV 还从后台内核或软中断任务中执行的内核23被标注的部分收集覆盖率。见 "远程覆盖率收集" 一节查看详细信息。24 25先决条件26--------27 28KCOV 依赖编译器插桩,要求 GCC 6.1.0 及更高版本或者内核支持的任意版本的 Clang。29 30收集操作数比较的覆盖率需要 GCC 8+ 或者 Clang。31 32为了启用 KCOV,需要使用如下参数配置内核::33 34        CONFIG_KCOV=y35 36为了启用操作数比较覆盖率的收集,使用如下参数::37 38    CONFIG_KCOV_ENABLE_COMPARISONS=y39 40覆盖率数据只会在调试文件系统被挂载后才可以获取::41 42        mount -t debugfs none /sys/kernel/debug43 44覆盖率收集45----------46 47下面的程序演示了如何使用 KCOV 在一个测试程序中收集单个系统调用的覆盖率:48 49.. code-block:: c50 51    #include <stdio.h>52    #include <stddef.h>53    #include <stdint.h>54    #include <stdlib.h>55    #include <sys/types.h>56    #include <sys/stat.h>57    #include <sys/ioctl.h>58    #include <sys/mman.h>59    #include <unistd.h>60    #include <fcntl.h>61    #include <linux/types.h>62 63    #define KCOV_INIT_TRACE			_IOR('c', 1, unsigned long)64    #define KCOV_ENABLE			_IO('c', 100)65    #define KCOV_DISABLE			_IO('c', 101)66    #define COVER_SIZE			(64<<10)67 68    #define KCOV_TRACE_PC  069    #define KCOV_TRACE_CMP 170 71    int main(int argc, char **argv)72    {73	int fd;74	unsigned long *cover, n, i;75 76	/* 单个文件描述符允许77	 * 在单线程上收集覆盖率。78	 */79	fd = open("/sys/kernel/debug/kcov", O_RDWR);80	if (fd == -1)81		perror("open"), exit(1);82	/* 设置跟踪模式和跟踪大小。 */83	if (ioctl(fd, KCOV_INIT_TRACE, COVER_SIZE))84		perror("ioctl"), exit(1);85	/* 映射内核空间和用户空间共享的缓冲区。 */86	cover = (unsigned long*)mmap(NULL, COVER_SIZE * sizeof(unsigned long),87				     PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);88	if ((void*)cover == MAP_FAILED)89		perror("mmap"), exit(1);90	/* 在当前线程中启用覆盖率收集。 */91	if (ioctl(fd, KCOV_ENABLE, KCOV_TRACE_PC))92		perror("ioctl"), exit(1);93	/* 在调用 ioctl() 之后重置覆盖率。 */94	__atomic_store_n(&cover[0], 0, __ATOMIC_RELAXED);95	/* 调用目标系统调用。 */96	read(-1, NULL, 0);97	/* 读取收集到的 PC 的数目。 */98	n = __atomic_load_n(&cover[0], __ATOMIC_RELAXED);99	for (i = 0; i < n; i++)100		printf("0x%lx\n", cover[i + 1]);101	/* 在当前线程上禁用覆盖率收集。在这之后102	 * 可以在其他线程上收集覆盖率103	 */104	if (ioctl(fd, KCOV_DISABLE, 0))105		perror("ioctl"), exit(1);106	/* 释放资源 */107	if (munmap(cover, COVER_SIZE * sizeof(unsigned long)))108		perror("munmap"), exit(1);109	if (close(fd))110		perror("close"), exit(1);111	return 0;112    }113 114在使用 ``addr2line`` 传输后,程序输出应该如下所示::115 116    SyS_read117    fs/read_write.c:562118    __fdget_pos119    fs/file.c:774120    __fget_light121    fs/file.c:746122    __fget_light123    fs/file.c:750124    __fget_light125    fs/file.c:760126    __fdget_pos127    fs/file.c:784128    SyS_read129    fs/read_write.c:562130 131如果一个程序需要从多个线程收集覆盖率(独立地)。那么每个线程都需要单独打开132``/sys/kernel/debug/kcov``。133 134接口的细粒度允许高效的创建测试进程。即,一个父进程打开了135``/sys/kernel/debug/kcov``,启用了追踪模式,映射了覆盖率缓冲区,然后在一个循136环中创建了子进程。这个子进程只需要启用覆盖率收集即可(当一个线程退出时将自动禁137用覆盖率收集)。138 139操作数比较收集140--------------141 142操作数比较收集和覆盖率收集类似:143 144.. code-block:: c145 146    /* 包含和上文一样的头文件和宏定义。 */147 148    /* 每次记录的 64 位字的数量。 */149    #define KCOV_WORDS_PER_CMP 4150 151    /*152     * 收集的比较种类的格式。153     *154     * 0 比特表示是否是一个编译时常量。155     * 1 & 2 比特包含参数大小的 log2 值,最大 8 字节。156     */157 158    #define KCOV_CMP_CONST          (1 << 0)159    #define KCOV_CMP_SIZE(n)        ((n) << 1)160    #define KCOV_CMP_MASK           KCOV_CMP_SIZE(3)161 162    int main(int argc, char **argv)163    {164	int fd;165	uint64_t *cover, type, arg1, arg2, is_const, size;166	unsigned long n, i;167 168	fd = open("/sys/kernel/debug/kcov", O_RDWR);169	if (fd == -1)170		perror("open"), exit(1);171	if (ioctl(fd, KCOV_INIT_TRACE, COVER_SIZE))172		perror("ioctl"), exit(1);173	/*174	* 注意缓冲区指针的类型是 uint64_t*,因为所有的175	* 比较操作数都被提升为 uint64_t 类型。176	*/177	cover = (uint64_t *)mmap(NULL, COVER_SIZE * sizeof(unsigned long),178				     PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);179	if ((void*)cover == MAP_FAILED)180		perror("mmap"), exit(1);181	/* 注意这里是 KCOV_TRACE_CMP 而不是 KCOV_TRACE_PC。 */182	if (ioctl(fd, KCOV_ENABLE, KCOV_TRACE_CMP))183		perror("ioctl"), exit(1);184	__atomic_store_n(&cover[0], 0, __ATOMIC_RELAXED);185	read(-1, NULL, 0);186	/* 读取收集到的比较操作数的数量。 */187	n = __atomic_load_n(&cover[0], __ATOMIC_RELAXED);188	for (i = 0; i < n; i++) {189		uint64_t ip;190 191		type = cover[i * KCOV_WORDS_PER_CMP + 1];192		/* arg1 和 arg2 - 比较的两个操作数。 */193		arg1 = cover[i * KCOV_WORDS_PER_CMP + 2];194		arg2 = cover[i * KCOV_WORDS_PER_CMP + 3];195		/* ip - 调用者的地址。 */196		ip = cover[i * KCOV_WORDS_PER_CMP + 4];197		/* 操作数的大小。 */198		size = 1 << ((type & KCOV_CMP_MASK) >> 1);199		/* is_const - 当操作数是一个编译时常量时为真。*/200		is_const = type & KCOV_CMP_CONST;201		printf("ip: 0x%lx type: 0x%lx, arg1: 0x%lx, arg2: 0x%lx, "202			"size: %lu, %s\n",203			ip, type, arg1, arg2, size,204		is_const ? "const" : "non-const");205	}206	if (ioctl(fd, KCOV_DISABLE, 0))207		perror("ioctl"), exit(1);208	/* 释放资源。 */209	if (munmap(cover, COVER_SIZE * sizeof(unsigned long)))210		perror("munmap"), exit(1);211	if (close(fd))212		perror("close"), exit(1);213	return 0;214    }215 216注意 KCOV 的模式(代码覆盖率收集或操作数比较收集)是互斥的。217 218远程覆盖率收集219--------------220 221除了从用户空间进程发布的系统调用句柄收集覆盖率数据以外,KCOV 也可以从部分在其222他上下文中执行的内核中收集覆盖率 - 称为“远程”覆盖率。223 224使用 KCOV 收集远程覆盖率要求:225 2261. 修改内核源码并使用 ``kcov_remote_start`` 和 ``kcov_remote_stop`` 来标注要收集227   覆盖率的代码片段。228 2292. 在用户空间的收集覆盖率的进程应使用 ``KCOV_REMOTE_ENABLE`` 而不是 ``KCOV_ENABLE``。230 231``kcov_remote_start`` 和 ``kcov_remote_stop`` 的标注以及 ``KCOV_REMOTE_ENABLE``232ioctl 都接受可以识别特定覆盖率收集片段的句柄。句柄的使用方式取决于匹配代码片段执233行的上下文。234 235KCOV 支持在如下上下文中收集远程覆盖率:236 2371. 全局内核后台任务。这些任务是内核启动时创建的数量有限的实例(如,每一个238   USB HCD 产生一个 USB ``hub_event`` 工作器)。239 2402. 局部内核后台任务。这些任务通常是由于用户空间进程与某些内核接口进行交互时产241   生的,并且通常在进程退出时会被停止(如,vhost 工作器)。242 2433. 软中断。244 245对于 #1 和 #3,必须选择一个独特的全局句柄并将其传递给对应的246``kcov_remote_start`` 调用。一个用户空间进程必须将该句柄存储在247``kcov_remote_arg`` 结构体的 ``handle`` 数组字段中并将其传递给248``KCOV_REMOTE_ENABLE``。这会将使用的 KCOV 设备附加到由此句柄引用的代码片段。多个全局249句柄标识的不同代码片段可以一次性传递。250 251对于 #2,用户空间进程必须通过 ``kcov_remote_arg`` 结构体的 ``common_handle`` 字段252传递一个非零句柄。这个通用句柄将会被保存在当前 ``task_struct`` 结构体的253``kcov_handle`` 字段中并且需要通过自定义内核代码的修改来传递给新创建的本地任务254。这些任务需要在 ``kcov_remote_start`` 和 ``kcov_remote_stop`` 标注中依次使用传递过来的255句柄。256 257KCOV 对全局句柄和通用句柄均遵循一个预定义的格式。每一个句柄都是一个 ``u64`` 整形258。当前,只有最高位和低四位字节被使用。第 4-7 字节是保留位并且值必须为 0。259 260对于全局句柄,最高位的字节表示该句柄属于的子系统的标识。比如,KCOV 使用 ``1``261表示 USB 子系统类型。全局句柄的低 4 字节表示子系统中任务实例的标识。比如,每一262个 ``hub_event`` 工作器使用 USB 总线号作为任务实例的标识。263 264对于通用句柄,使用一个保留值 ``0`` 作为子系统标识,因为这些句柄不属于一个特定265的子系统。通用句柄的低 4 字节用于识别有用户进程生成的所有本地句柄的集合实例,266该进程将通用句柄传递给 ``KCOV_REMOTE_ENABLE``。267 268实际上,如果只从系统中的单个用户空间进程收集覆盖率,那么可以使用任意值作为通用269句柄的实例标识。然而,如果通用句柄被多个用户空间进程使用,每个进程必须使用唯一270的实例标识。一个选择是使用进程标识作为通用句柄实例的标识。271 272下面的程序演示了如何使用 KCOV 从一个由进程产生的本地任务和处理 USB 总线的全局273任务 #1 收集覆盖率:274 275.. code-block:: c276 277    /* 包含和上文一样的头文件和宏定义。 */278 279    struct kcov_remote_arg {280	__u32		trace_mode;281	__u32		area_size;282	__u32		num_handles;283	__aligned_u64	common_handle;284	__aligned_u64	handles[0];285    };286 287    #define KCOV_INIT_TRACE			_IOR('c', 1, unsigned long)288    #define KCOV_DISABLE			_IO('c', 101)289    #define KCOV_REMOTE_ENABLE		_IOW('c', 102, struct kcov_remote_arg)290 291    #define COVER_SIZE	(64 << 10)292 293    #define KCOV_TRACE_PC	0294 295    #define KCOV_SUBSYSTEM_COMMON	(0x00ull << 56)296    #define KCOV_SUBSYSTEM_USB	(0x01ull << 56)297 298    #define KCOV_SUBSYSTEM_MASK	(0xffull << 56)299    #define KCOV_INSTANCE_MASK	(0xffffffffull)300 301    static inline __u64 kcov_remote_handle(__u64 subsys, __u64 inst)302    {303	if (subsys & ~KCOV_SUBSYSTEM_MASK || inst & ~KCOV_INSTANCE_MASK)304		return 0;305	return subsys | inst;306    }307 308    #define KCOV_COMMON_ID	0x42309    #define KCOV_USB_BUS_NUM	1310 311    int main(int argc, char **argv)312    {313	int fd;314	unsigned long *cover, n, i;315	struct kcov_remote_arg *arg;316 317	fd = open("/sys/kernel/debug/kcov", O_RDWR);318	if (fd == -1)319		perror("open"), exit(1);320	if (ioctl(fd, KCOV_INIT_TRACE, COVER_SIZE))321		perror("ioctl"), exit(1);322	cover = (unsigned long*)mmap(NULL, COVER_SIZE * sizeof(unsigned long),323				     PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);324	if ((void*)cover == MAP_FAILED)325		perror("mmap"), exit(1);326 327	/* 通过通用句柄和 USB 总线 #1 启用代码覆盖率收集。 */328	arg = calloc(1, sizeof(*arg) + sizeof(uint64_t));329	if (!arg)330		perror("calloc"), exit(1);331	arg->trace_mode = KCOV_TRACE_PC;332	arg->area_size = COVER_SIZE;333	arg->num_handles = 1;334	arg->common_handle = kcov_remote_handle(KCOV_SUBSYSTEM_COMMON,335							KCOV_COMMON_ID);336	arg->handles[0] = kcov_remote_handle(KCOV_SUBSYSTEM_USB,337						KCOV_USB_BUS_NUM);338	if (ioctl(fd, KCOV_REMOTE_ENABLE, arg))339		perror("ioctl"), free(arg), exit(1);340	free(arg);341 342	/*343	 * 在这里用户需要触发执行一个内核代码段344	 * 该代码段要么使用通用句柄标识345	 * 要么触发了一些 USB 总线 #1 上的一些活动。346	 */347	sleep(2);348 349	n = __atomic_load_n(&cover[0], __ATOMIC_RELAXED);350	for (i = 0; i < n; i++)351		printf("0x%lx\n", cover[i + 1]);352	if (ioctl(fd, KCOV_DISABLE, 0))353		perror("ioctl"), exit(1);354	if (munmap(cover, COVER_SIZE * sizeof(unsigned long)))355		perror("munmap"), exit(1);356	if (close(fd))357		perror("close"), exit(1);358	return 0;359    }360