~ [ source navigation ] ~ [ diff markup ] ~ [ identifier search ] ~

TOMOYO Linux Cross Reference
Linux/Documentation/translations/zh_CN/dev-tools/kcov.rst

Version: ~ [ linux-6.12-rc7 ] ~ [ linux-6.11.7 ] ~ [ linux-6.10.14 ] ~ [ linux-6.9.12 ] ~ [ linux-6.8.12 ] ~ [ linux-6.7.12 ] ~ [ linux-6.6.60 ] ~ [ linux-6.5.13 ] ~ [ linux-6.4.16 ] ~ [ linux-6.3.13 ] ~ [ linux-6.2.16 ] ~ [ linux-6.1.116 ] ~ [ linux-6.0.19 ] ~ [ linux-5.19.17 ] ~ [ linux-5.18.19 ] ~ [ linux-5.17.15 ] ~ [ linux-5.16.20 ] ~ [ linux-5.15.171 ] ~ [ linux-5.14.21 ] ~ [ linux-5.13.19 ] ~ [ linux-5.12.19 ] ~ [ linux-5.11.22 ] ~ [ linux-5.10.229 ] ~ [ linux-5.9.16 ] ~ [ linux-5.8.18 ] ~ [ linux-5.7.19 ] ~ [ linux-5.6.19 ] ~ [ linux-5.5.19 ] ~ [ linux-5.4.285 ] ~ [ linux-5.3.18 ] ~ [ linux-5.2.21 ] ~ [ linux-5.1.21 ] ~ [ linux-5.0.21 ] ~ [ linux-4.20.17 ] ~ [ linux-4.19.323 ] ~ [ linux-4.18.20 ] ~ [ linux-4.17.19 ] ~ [ linux-4.16.18 ] ~ [ linux-4.15.18 ] ~ [ linux-4.14.336 ] ~ [ linux-4.13.16 ] ~ [ linux-4.12.14 ] ~ [ linux-4.11.12 ] ~ [ linux-4.10.17 ] ~ [ linux-4.9.337 ] ~ [ linux-4.4.302 ] ~ [ linux-3.10.108 ] ~ [ linux-2.6.32.71 ] ~ [ linux-2.6.0 ] ~ [ linux-2.4.37.11 ] ~ [ unix-v6-master ] ~ [ ccs-tools-1.8.12 ] ~ [ policy-sample ] ~
Architecture: ~ [ i386 ] ~ [ alpha ] ~ [ m68k ] ~ [ mips ] ~ [ ppc ] ~ [ sparc ] ~ [ sparc64 ] ~

  1 .. SPDX-License-Identifier: GPL-2.0
  2 
  3 .. include:: ../disclaimer-zh_CN.rst
  4 
  5 :Original: Documentation/dev-tools/kcov.rst
  6 :Translator: 刘浩阳 Haoyang Liu <tttturtleruss@hust.edu.cn>
  7 
  8 KCOV: 用于模糊测试的代码覆盖率
  9 ==============================
 10 
 11 KCOV 以一种适用于覆盖率引导的模糊测试的形式收集和暴露内核代码覆盖率信息。
 12 一个正在运行的内核的覆盖率数据可以通过 ``kcov`` 调试文件导出。覆盖率的收集是基
 13 于任务启用的,因此 KCOV 可以精确捕获单个系统调用的覆盖率。
 14 
 15 要注意的是 KCOV 不是为了收集尽可能多的覆盖率数据。而是为了收集相对稳定的覆盖率
 16 ,这是系统调用输入的函数。为了完成这个目标,它不收集软硬中断的覆盖率(除非移除
 17 覆盖率收集被启用,见下文)以及内核中固有的不确定部分的覆盖率(如调度器,锁定)
 18 
 19 除了收集代码覆盖率,KCOV 还收集操作数比较的覆盖率。见 "操作数比较收集" 一节
 20 查看详细信息。
 21 
 22 除了从系统调用处理器收集覆盖率数据,KCOV 还从后台内核或软中断任务中执行的内核
 23 被标注的部分收集覆盖率。见 "远程覆盖率收集" 一节查看详细信息。
 24 
 25 先决条件
 26 --------
 27 
 28 KCOV 依赖编译器插桩,要求 GCC 6.1.0 及更高版本或者内核支持的任意版本的 Clang。
 29 
 30 收集操作数比较的覆盖率需要 GCC 8+ 或者 Clang。
 31 
 32 为了启用 KCOV,需要使用如下参数配置内核::
 33 
 34         CONFIG_KCOV=y
 35 
 36 为了启用操作数比较覆盖率的收集,使用如下参数::
 37 
 38     CONFIG_KCOV_ENABLE_COMPARISONS=y
 39 
 40 覆盖率数据只会在调试文件系统被挂载后才可以获取::
 41 
 42         mount -t debugfs none /sys/kernel/debug
 43 
 44 覆盖率收集
 45 ----------
 46 
 47 下面的程序演示了如何使用 KCOV 在一个测试程序中收集单个系统调用的覆盖率:
 48 
 49 .. code-block:: c
 50 
 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  0
 69     #define KCOV_TRACE_CMP 1
 70 
 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_read
117     fs/read_write.c:562
118     __fdget_pos
119     fs/file.c:774
120     __fget_light
121     fs/file.c:746
122     __fget_light
123     fs/file.c:750
124     __fget_light
125     fs/file.c:760
126     __fdget_pos
127     fs/file.c:784
128     SyS_read
129     fs/read_write.c:562
130 
131 如果一个程序需要从多个线程收集覆盖率(独立地)。那么每个线程都需要单独打开
132 ``/sys/kernel/debug/kcov``。
133 
134 接口的细粒度允许高效的创建测试进程。即,一个父进程打开了
135 ``/sys/kernel/debug/kcov``,启用了追踪模式,映射了覆盖率缓冲区,然后在一个循
136 环中创建了子进程。这个子进程只需要启用覆盖率收集即可(当一个线程退出时将自动禁
137 用覆盖率收集)。
138 
139 操作数比较收集
140 --------------
141 
142 操作数比较收集和覆盖率收集类似:
143 
144 .. code-block:: c
145 
146     /* 包含和上文一样的头文件和宏定义。 */
147 
148     /* 每次记录的 64 位字的数量。 */
149     #define KCOV_WORDS_PER_CMP 4
150 
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 
226 1. 修改内核源码并使用 ``kcov_remote_start`` 和 ``kcov_remote_stop`` 来标注要收集
227    覆盖率的代码片段。
228 
229 2. 在用户空间的收集覆盖率的进程应使用 ``KCOV_REMOTE_ENABLE`` 而不是 ``KCOV_ENABLE``。
230 
231 ``kcov_remote_start`` 和 ``kcov_remote_stop`` 的标注以及 ``KCOV_REMOTE_ENABLE``
232 ioctl 都接受可以识别特定覆盖率收集片段的句柄。句柄的使用方式取决于匹配代码片段执
233 行的上下文。
234 
235 KCOV 支持在如下上下文中收集远程覆盖率:
236 
237 1. 全局内核后台任务。这些任务是内核启动时创建的数量有限的实例(如,每一个
238    USB HCD 产生一个 USB ``hub_event`` 工作器)。
239 
240 2. 局部内核后台任务。这些任务通常是由于用户空间进程与某些内核接口进行交互时产
241    生的,并且通常在进程退出时会被停止(如,vhost 工作器)。
242 
243 3. 软中断。
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 
257 KCOV 对全局句柄和通用句柄均遵循一个预定义的格式。每一个句柄都是一个 ``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:: c
276 
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       0
294 
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      0x42
309     #define KCOV_USB_BUS_NUM    1
310 
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     }

~ [ source navigation ] ~ [ diff markup ] ~ [ identifier search ] ~

kernel.org | git.kernel.org | LWN.net | Project Home | SVN repository | Mail admin

Linux® is a registered trademark of Linus Torvalds in the United States and other countries.
TOMOYO® is a registered trademark of NTT DATA CORPORATION.

sflogo.php