Coccinelle

Coccinelle 是一个用于模式匹配和文本转换的工具,在内核开发中有许多用途,包括应用复杂的全树范围补丁以及检测有问题的编程模式。

获取 Coccinelle

内核中包含的语义补丁使用了 Coccinelle 1.0.0-rc11 及以上版本提供的功能和选项。使用早期版本将会失败,因为 Coccinelle 文件和 coccicheck 使用的选项名称已被更新。

Coccinelle 可以通过许多发行版的包管理器获取,例如

  • Debian

  • Fedora

  • Ubuntu

  • OpenSUSE

  • Arch Linux

  • Gentoo

  • NetBSD

  • FreeBSD

一些发行版软件包已经过时,建议使用从 Coccinelle 主页发布的最新版本 https://coccinelle.gitlabpages.inria.fr/website

或者从 Github 获取

https://github.com/coccinelle/coccinelle

获取后,请运行以下命令

./autogen
./configure
make

以普通用户身份,并使用以下命令安装

sudo make install

从源码构建的更详细安装说明可以在以下网址找到

https://github.com/coccinelle/coccinelle/blob/master/install.txt

补充文档

有关补充文档,请参考 wiki

https://bottest.wiki.kernel.org/coccicheck.html

wiki 文档始终对应于脚本的 linux-next 版本。

有关语义补丁语言 (SmPL) 语法文档,请参考

https://coccinelle.gitlabpages.inria.fr/website/docs/main_grammar.html

在 Linux 内核上使用 Coccinelle

顶层 Makefile 中定义了一个 Coccinelle 专用的目标。该目标名为 coccicheck,并会调用 scripts 目录中的 coccicheck 前端。

定义了四种基本模式:patchreportcontextorg。要使用的模式可以通过设置 MODE=<mode> 变量来指定。

  • patch 在可能的情况下提出修复方案。

  • report 生成以下格式的列表:file:line:column-column: 消息

  • context 以类似 diff 的风格高亮显示关注的行及其上下文。关注的行用 - 表示。

  • org 以 Emacs 的 Org 模式格式生成报告。

请注意,并非所有语义补丁都实现了所有模式。为了便于使用 Coccinelle,默认模式为 “report”。

另外两种模式提供了这些模式的一些常见组合。

  • chain 按照上述顺序尝试先前的模式,直到其中一个成功。

  • rep+ctxt 依次运行 report 模式和 context 模式。它应该与基于文件检查代码的 C 选项(稍后描述)一起使用。

示例

要为每个语义补丁生成报告,请运行以下命令

make coccicheck MODE=report

要生成补丁,请运行

make coccicheck MODE=patch

coccicheck 目标将 scripts/coccinelle 子目录中可用的每个语义补丁应用到整个 Linux 内核。

对于每个语义补丁,都会建议一个提交信息。它给出了语义补丁正在检查的问题的描述,并包含了对 Coccinelle 的引用。

与任何静态代码分析器一样,Coccinelle 会产生误报。因此,必须仔细检查报告并审阅补丁。

要启用详细消息,请设置 V= 变量,例如

make coccicheck MODE=report V=1

默认情况下,coccicheck 会将调试日志打印到 stdout 并将 stderr 重定向到 /dev/null。这可能会使 coccicheck 的输出难以阅读和理解。可以通过设置 DEBUG_FILE 变量,将调试和错误消息写入调试文件替代上述行为

make coccicheck MODE=report DEBUG_FILE="cocci.log"

Coccinelle 无法覆盖调试文件。与其重复删除日志文件,不如在调试文件名中包含日期时间

make coccicheck MODE=report DEBUG_FILE="cocci-$(date -Iseconds).log"

Coccinelle 并行化

默认情况下,coccicheck 尽量以最大并行度运行。要更改并行度,请设置 J= 变量。例如,要在 4 个 CPU 上运行

make coccicheck MODE=report J=4

从 Coccinelle 1.0.2 开始,Coccinelle 使用 Ocaml parmap 进行并行化;如果检测到对此的支持,您将受益于 parmap 并行化。

启用 parmap 后,coccicheck 将通过使用 --chunksize 1 参数来启用动态负载均衡。这确保我们不断地向线程逐个分发工作,从而避免大部分工作仅由少数几个线程完成的情况。通过动态负载均衡,如果某个线程提前完成,我们会继续向其提供更多工作。

启用 parmap 后,如果 Coccinelle 中发生错误,该错误值会被向上传播,并且 make coccicheck 命令的返回值会捕获此返回值。

将 Coccinelle 用于单个语义补丁

可选的 make 变量 COCCI 可用于检查单个语义补丁。在这种情况下,必须使用要应用的语义补丁的名称来初始化该变量。

例如

make coccicheck COCCI=<my_SP.cocci> MODE=patch

或者

make coccicheck COCCI=<my_SP.cocci> MODE=report

控制 Coccinelle 处理哪些文件

默认情况下,会检查整个内核源码树。

要将 Coccinelle 应用于特定目录,可以使用 M=。例如,要检查 drivers/net/wireless/,可以编写

make coccicheck M=drivers/net/wireless/

为了按文件而不是按目录应用 Coccinelle,makefile 使用 C 变量来选择要处理的文件。该变量可用于为整个内核、特定目录或单个文件运行脚本。

例如,要检查 drivers/bluetooth/bfusb.c,将值 1 传递给 C 变量以检查 make 认为需要编译的文件。

make C=1 CHECK=scripts/coccicheck drivers/bluetooth/bfusb.o

将值 2 传递给 C 变量以检查文件,而不管它们是否需要编译。

make C=2 CHECK=scripts/coccicheck drivers/bluetooth/bfusb.o

在这些按文件工作的模式下,不会显示有关语义补丁的信息,也不会建议提交信息。

这默认会运行 scripts/coccinelle 中的每个语义补丁。此外,还可以使用 COCCI 变量来仅应用单个语义补丁,如上一节所示。

“report”模式是默认模式。您可以使用上面解释的 MODE 变量选择另一个模式。

调试 Coccinelle SmPL 补丁

使用 coccicheck 是最好的,因为它在 spatch 命令行中提供了与编译内核时使用的选项相匹配的包含选项。您可以通过使用 V=1 来了解这些选项是什么;然后您可以手动运行添加了调试选项的 Coccinelle。

调试针对 SmPL 补丁运行 Coccinelle 的一种更简单的方法是让 coccicheck 将 stderr 重定向到调试文件。正如在示例中所提到的,默认情况下 stderr 被重定向到 /dev/null;如果您想捕获 stderr,可以向 coccicheck 指定 DEBUG_FILE="file.txt" 选项。例如

rm -f cocci.err
make coccicheck COCCI=scripts/coccinelle/free/kfree.cocci MODE=report DEBUG_FILE=cocci.err
cat cocci.err

您可以使用 SPFLAGS 添加调试标志;例如,在调试时,您可能希望将 --profile --show-trying 都添加到 SPFLAGS 中。例如,您可能希望使用

rm -f err.log
export COCCI=scripts/coccinelle/misc/irqf_oneshot.cocci
make coccicheck DEBUG_FILE="err.log" MODE=report SPFLAGS="--profile --show-trying" M=./drivers/mfd

现在 err.log 将包含性能分析信息,而 stdout 将在 Coccinelle 继续工作时提供一些进度信息。

注意

DEBUG_FILE 支持仅在使用 coccinelle >= 1.0.2 时受支持。

目前,DEBUG_FILE 支持仅适用于检查文件夹,而不适用于单个文件。这是因为检查单个文件需要调用 spatch 两次,导致 DEBUG_FILE 两次都被设置为相同的值,从而引发错误。

.cocciconfig 支持

Coccinelle 支持读取 .cocciconfig 以获取每次启动 spatch 时应使用的默认 Coccinelle 选项。.cocciconfig 中变量的优先级顺序如下

  • 首先处理当前用户的家目录

  • 接下来处理调用 spatch 所在的目录

  • 如果使用了 --dir 选项,最后处理该选项提供的目录

make coccicheck 还支持使用 M= 目标。如果您不提供任何 M= 目标,则假定您的目标是整个内核。内核 coccicheck 脚本具有

OPTIONS="--dir $srcroot $COCCIINCLUDE"

这里,$srcroot 指的是目标的源码目录:当使用 M= 时,它指向外部模块的源码目录,否则指向内核源码目录。第三条规则确保 spatch 从目标目录读取 .cocciconfig,从而允许外部模块拥有自己的 .cocciconfig 文件。

如果不使用内核的 coccicheck 目标,请保持上述 .cocciconfig 读取的优先级顺序逻辑。如果使用内核的 coccicheck 目标,请使用 SPFLAGS 覆盖内核的任何 .cocciconfig 设置。

我们通过提供一套针对 Linux 的合理默认选项以及我们自己的 Linux .cocciconfig,在 Coccinelle 用于 Linux 时为其提供帮助。这向 coccinelle 暗示 git 可用于通过 coccigrep 进行 git grep 查询。目前 200 秒的超时时间应该足够了。

coccinelle 在读取 .cocciconfig 时拾取的选项不会作为参数出现在系统上运行的 spatch 进程中。要确认 Coccinelle 将使用哪些选项,请运行

spatch --print-options-only

您可以使用 SPFLAGS 覆盖为您自己首选的索引选项。请注意,当存在冲突选项时,Coccinelle 优先采用最后传递的选项。使用 .cocciconfig 可以使用 idutils,但是鉴于 Coccinelle 遵循的优先级顺序,由于内核现在自带了其自己的 .cocciconfig,如果需要,您需要使用 SPFLAGS 来使用 idutils。有关如何使用 idutils 的更多详细信息,请参阅下面的“附加标志”一节。

附加标志

可以通过 SPFLAGS 变量将附加标志传递给 spatch。这之所以有效,是因为当选项冲突时,Coccinelle 会遵循给它的最后几个标志。

make SPFLAGS=--use-glimpse coccicheck

Coccinelle 也支持 idutils,但需要 coccinelle >= 1.0.6。当未指定 ID 文件时,coccinelle 假定您的 ID 数据库文件位于内核顶层的 .id-utils.index 文件中。Coccinelle 附带了一个脚本 scripts/idutils_index.sh,它使用以下命令创建数据库

mkid -i C --output .id-utils.index

如果您有另一个数据库文件名,也可以直接创建指向该名称的符号链接。

make SPFLAGS=--use-idutils coccicheck

或者,您可以显式指定数据库文件名,例如

make SPFLAGS="--use-idutils /full-path/to/ID" coccicheck

请参阅 spatch --help 以了解有关 spatch 选项的更多信息。

请注意,--use-glimpse--use-idutils 选项需要用于索引代码的外部工具。因此,它们默认都不是活动的。但是,通过使用这些工具之一对代码进行索引,并且根据所使用的 cocci 文件,spatch 可以更快地处理整个代码库。

SmPL 补丁特定选项

SmPL 补丁对传递给 Coccinelle 的选项可以有其自己的要求。SmPL 补丁特定选项可以通过在 SmPL 补丁的顶部提供它们来指定,例如

// Options: --no-includes --include-headers

SmPL 补丁对 Coccinelle 的要求

随着 Coccinelle 功能的添加,一些更高级的 SmPL 补丁可能需要更新版本的 Coccinelle。如果一个 SmPL 补丁需要最低版本的 Coccinelle,可以按如下方式指定,例如如果要求至少 Coccinelle >= 1.0.5

// Requires: 1.0.5

提交新的语义补丁

内核开发人员可以提出并提交新的语义补丁。为清晰起见,它们应该组织在 scripts/coccinelle/ 的子目录中。

report 模式详细说明

report 生成以下格式的列表

file:line:column-column: message

示例

运行

make coccicheck MODE=report COCCI=scripts/coccinelle/api/err_cast.cocci

将执行 SmPL 脚本的以下部分

<smpl>
@r depends on !context && !patch && (org || report)@
expression x;
position p;
@@

  ERR_PTR@p(PTR_ERR(x))

@script:python depends on report@
p << r.p;
x << r.x;
@@

msg="ERR_CAST can be used with %s" % (x)
coccilib.report.print_report(p[0], msg)
</smpl>

此 SmPL 片段在标准输出上生成条目,如下所示

/home/user/linux/crypto/ctr.c:188:9-16: ERR_CAST can be used with alg
/home/user/linux/crypto/authenc.c:619:9-16: ERR_CAST can be used with auth
/home/user/linux/crypto/xts.c:227:9-16: ERR_CAST can be used with alg

patch 模式详细说明

patch 模式可用时,它会为识别出的每个问题提出修复方案。

示例

运行

make coccicheck MODE=patch COCCI=scripts/coccinelle/api/err_cast.cocci

将执行 SmPL 脚本的以下部分

<smpl>
@ depends on !context && patch && !org && !report @
expression x;
@@

- ERR_PTR(PTR_ERR(x))
+ ERR_CAST(x)
</smpl>

此 SmPL 片段在标准输出上生成补丁块,如下所示

diff -u -p a/crypto/ctr.c b/crypto/ctr.c
--- a/crypto/ctr.c 2010-05-26 10:49:38.000000000 +0200
+++ b/crypto/ctr.c 2010-06-03 23:44:49.000000000 +0200
@@ -185,7 +185,7 @@ static struct crypto_instance *crypto_ct
    alg = crypto_attr_alg(tb[1], CRYPTO_ALG_TYPE_CIPHER,
                              CRYPTO_ALG_TYPE_MASK);
    if (IS_ERR(alg))
-           return ERR_PTR(PTR_ERR(alg));
+           return ERR_CAST(alg);

    /* Block size must be >= 4 bytes. */
    err = -EINVAL;

context 模式详细说明

context 以类似 diff 的风格高亮显示关注的行及其上下文。

注意:生成的类似 diff 的输出不是一个可应用的补丁。context 模式的目的是高亮显示重要行(用减号 - 注解)并给出一些周围的上下文行。此输出可以与 Emacs 的 diff 模式一起使用来审阅代码。

示例

运行

make coccicheck MODE=context COCCI=scripts/coccinelle/api/err_cast.cocci

将执行 SmPL 脚本的以下部分

<smpl>
@ depends on context && !patch && !org && !report@
expression x;
@@

* ERR_PTR(PTR_ERR(x))
</smpl>

此 SmPL 片段在标准输出上生成 diff 块,如下所示

diff -u -p /home/user/linux/crypto/ctr.c /tmp/nothing
--- /home/user/linux/crypto/ctr.c   2010-05-26 10:49:38.000000000 +0200
+++ /tmp/nothing
@@ -185,7 +185,6 @@ static struct crypto_instance *crypto_ct
    alg = crypto_attr_alg(tb[1], CRYPTO_ALG_TYPE_CIPHER,
                              CRYPTO_ALG_TYPE_MASK);
    if (IS_ERR(alg))
-           return ERR_PTR(PTR_ERR(alg));

    /* Block size must be >= 4 bytes. */
    err = -EINVAL;

org 模式详细说明

org 以 Emacs 的 Org 模式格式生成报告。

示例

运行

make coccicheck MODE=org COCCI=scripts/coccinelle/api/err_cast.cocci

将执行 SmPL 脚本的以下部分

<smpl>
@r depends on !context && !patch && (org || report)@
expression x;
position p;
@@

  ERR_PTR@p(PTR_ERR(x))

@script:python depends on org@
p << r.p;
x << r.x;
@@

msg="ERR_CAST can be used with %s" % (x)
msg_safe=msg.replace("[","@(").replace("]",")")
coccilib.org.print_todo(p[0], msg_safe)
</smpl>

此 SmPL 片段在标准输出上生成 Org 条目,如下所示

* TODO [[view:/home/user/linux/crypto/ctr.c::face=ovl-face1::linb=188::colb=9::cole=16][ERR_CAST can be used with alg]]
* TODO [[view:/home/user/linux/crypto/authenc.c::face=ovl-face1::linb=619::colb=9::cole=16][ERR_CAST can be used with auth]]
* TODO [[view:/home/user/linux/crypto/xts.c::face=ovl-face1::linb=227::colb=9::cole=16][ERR_CAST can be used with alg]]