Coccinelle
Coccinelle 是模式匹配与文本转换工具,在内核开发中用途广泛,包括应用复杂的全树补丁,以及检测有问题的编程模式。
备注
支持 Linux 和 macOS 开发环境,不支持 Windows。
获取 Coccinelle
内核附带的语义补丁使用 Coccinelle 1.0.0-rc11 及更新版本提供的功能和选项。由于 Coccinelle 文件和 coccicheck 使用的选项名称已更新,使用更早版本会失败。
许多发行版的包管理器都提供 Coccinelle,例如:
Debian
Fedora
Ubuntu
OpenSUSE
Arch Linux
NetBSD
FreeBSD
某些发行版的软件包已过时,建议使用 Coccinelle 主页 https://coccinelle.lip6.fr/ 发布的最新版本。
也可以从 GitHub 获取:
https://github.com/coccinelle/coccinelle
获取源码后,运行以下命令:
./autogen
./configure
make
以上命令以普通用户身份运行,再通过以下命令安装:
sudo make install
从源码构建的详细安装说明见:
https://github.com/coccinelle/coccinelle/blob/master/install.txt
补充文档
语义补丁语言(SmPL)语法文档见:
https://coccinelle.gitlabpages.inria.fr/website/documentation.html
在 Zephyr 上使用 Coccinelle
coccicheck 检查器是 Coccinelle 基础设施的前端,提供多种模式:
四种基本模式为 patch、report、context 和 org,通过 --mode=<mode> 或 -m=<mode> 指定。
patch:在可能时提出修复。report:按 file:line:column-column: message 格式生成列表。context:以类似 diff 的形式突出显示相关行及其上下文,相关行用-标记。org:生成 Emacs Org mode 格式报告。
注意,并非每个语义补丁都实现所有模式。为便于使用,默认模式为 report。
另外两种模式提供常用组合:
chain:按上述顺序尝试各模式,直到某个模式成功。rep+ctxt:依次运行 report 和 context 模式,应配合后文介绍的 C 选项使用,按文件检查代码。
示例
要为每个语义补丁生成报告,运行:
./scripts/coccicheck --mode=report
生成补丁,运行:
./scripts/coccicheck --mode=patch
coccicheck 目标将 scripts/coccinelle 子目录中的所有语义补丁应用到整个源码树。
每个语义补丁都会给出建议的提交说明,描述其检查的问题,并引用 Coccinelle。
与任何静态代码分析器一样,Coccinelle 会产生误报,因此必须仔细检查报告并审查补丁。
设置 --verbose=1 可启用详细消息,例如:
./scripts/coccicheck --mode=report --verbose=1
Coccinelle 并行化
默认情况下,coccicheck 会尽可能并行运行。通过 --jobs=<number> 调整并行度,例如使用 4 个 CPU:
./scripts/coccicheck --mode=report --jobs=4
从 1.0.2 起,Coccinelle 使用 OCaml parmap 并行化。检测到相关支持后,即可利用 parmap 并行执行。
启用 parmap 后,coccicheck 会通过 --chunksize 1 启用动态负载均衡,逐个向线程分派工作,避免大部分工作落在少数线程上。线程提前完成时,会继续获得更多工作。
启用 parmap 后,Coccinelle 发生错误时,错误值会向上传递,并由 coccicheck 命令返回。
使用单个语义补丁
--cocci 选项可用于检查单个语义补丁,此时其值必须为要应用的语义补丁名称。
例如:
./scripts/coccicheck --mode=report --cocci=<example.cocci>
或者:
./scripts/coccicheck --mode=report --cocci=./path/to/<example.cocci>
控制 Coccinelle 处理的文件
默认检查整个源码树。
要只处理特定目录,将目录路径作为参数传入。
例如,检查 drivers/usb/:
./scripts/coccicheck --mode=patch drivers/usb/
默认模式为 report,可通过前述 --mode=<mode> 选择其他模式。
调试 Coccinelle SmPL 补丁
最好使用 coccicheck,因为它会为 spatch 命令提供与内核编译一致的包含选项。通过详细输出可查看这些选项,再手动运行 Coccinelle 并添加调试选项。
也可以通过保留标准错误输出来调试 Coccinelle 对 SmPL 补丁的处理。默认标准错误被重定向到 /dev/null;要捕获它,可向 coccicheck 传入 --debug=file.err,例如:
rm -f cocci.err
./scripts/coccicheck --mode=patch --debug=cocci.err
cat cocci.err
仅 Coccinelle 1.0.2 及更新版本支持调试。
附加标志
可通过 SPFLAGS 变量向 spatch 传递附加标志。选项冲突时,Coccinelle 采用最后传入的标志,因此可以这样覆盖。
./scripts/coccicheck --sp-flag="--use-glimpse"
Coccinelle 还支持 idutils,但要求版本至少为 1.0.6。未指定 ID 文件时,默认数据库为内核顶层的 .id-utils.index。Coccinelle 附带 scripts/idutils_index.sh 脚本,可通过以下方式创建数据库:
mkid -i C --output .id-utils.index
如果数据库使用其他文件名,也可以创建使用此名称的符号链接。
./scripts/coccicheck --sp-flag="--use-idutils"
也可以明确指定数据库文件名,例如:
./scripts/coccicheck --sp-flag="--use-idutils /full-path/to/ID"
有时由于定义不足,Coccinelle 无法识别或解析复杂宏变量。为使其可解析,可通过 ---macro-file-builtins <headerfile.h> 标志显式提供复杂宏的原型。
<headerfile.h> 应包含复杂宏的完整原型,供 spatch 引擎提取转换所需的类型信息。
例如:
Coccinelle 无法识别 Z_SYSCALL_HANDLER,因此将其原型放入头文件,例如 mymacros.h。
$ cat mymacros.h
#define Z_SYSCALL_HANDLER int xxx
然后在转换时传入 mymacros.h 头文件:
./scripts/coccicheck --sp-flag="---macro-file-builtins mymacros.h"
spatch 选项详情见 spatch --help。
注意,--use-glimpse 和 --use-idutils 需要外部工具为代码建立索引,因此默认均未启用。不过,使用这些工具之一建立索引后,视 cocci 文件而定,spatch 可能更快地处理整个代码库。
SmPL 补丁专用选项
SmPL 补丁可能对传给 Coccinelle 的选项有特定要求,可以将专用选项写在补丁顶部,例如:
// Options: --no-includes --include-headers
提出新的语义补丁
内核开发者可以提出并提交新的语义补丁。为保持清晰,应按类别放在 scripts/coccinelle/ 子目录中。
cocci 脚本应具有以下属性:
脚本 必须 提供
report模式。开头几行应使用
///注释说明脚本用途。基于脚本提出补丁时,通常用这段说明作为提交日志。
示例
/// Use ARRAY_SIZE instead of dividing sizeof array with sizeof an element
更详细的脚本信息,包括特殊情况或可能的误报,可通过
//#注释列出。
Example
//# This makes an effort to find cases where ARRAY_SIZE can be used such as
//# where there is a division of sizeof the array by the sizeof its first
//# element or by any indexed element or the element type. It replaces the
//# division of the two sizeofs by ARRAY_SIZE.
Confidence:表示脚本准确程度的属性,根据观察到的误报数量设为
High、Moderate或Low。
Example
// Confidence: High
虚拟规则:用于支持脚本提供的各种模式。脚本中指定的虚拟规则应有对应的模式处理规则。
Example
virtual context
@depends on context@
type T;
T[] E;
@@
(
* (sizeof(E)/sizeof(*E))
|
* (sizeof(E)/sizeof(E[...]))
|
* (sizeof(E)/sizeof(T))
)
report 模式详解
report 按以下格式生成列表:
file:line:column-column: message
Example
运行:
./scripts/coccicheck --mode=report --cocci=scripts/coccinelle/array_size.cocci
将执行 SmPL 脚本中的以下部分:
<smpl>
@r depends on (org || report)@
type T;
T[] E;
position p;
@@
(
(sizeof(E)@p /sizeof(*E))
|
(sizeof(E)@p /sizeof(E[...]))
|
(sizeof(E)@p /sizeof(T))
)
@script:python depends on report@
p << r.p;
@@
msg="WARNING: Use ARRAY_SIZE"
coccilib.report.print_report(p[0], msg)
</smpl>
此 SmPL 片段向标准输出生成如下条目:
ext/hal/nxp/mcux/drivers/lpc/fsl_wwdt.c:66:49-50: WARNING: Use ARRAY_SIZE
ext/hal/nxp/mcux/drivers/lpc/fsl_ctimer.c:74:53-54: WARNING: Use ARRAY_SIZE
ext/hal/nxp/mcux/drivers/imx/fsl_dcp.c:944:45-46: WARNING: Use ARRAY_SIZE
patch 模式详解
提供 patch 模式时,会为每个发现的问题提出修复。
Example
运行:
./scripts/coccicheck --mode=patch --cocci=scripts/coccinelle/misc/array_size.cocci
将执行 SmPL 脚本中的以下部分:
<smpl>
@depends on patch@
type T;
T[] E;
@@
(
- (sizeof(E)/sizeof(*E))
+ ARRAY_SIZE(E)
|
- (sizeof(E)/sizeof(E[...]))
+ ARRAY_SIZE(E)
|
- (sizeof(E)/sizeof(T))
+ ARRAY_SIZE(E)
)
</smpl>
此 SmPL 片段向标准输出生成如下补丁块:
diff -u -p a/ext/lib/encoding/tinycbor/src/cborvalidation.c b/ext/lib/encoding/tinycbor/src/cborvalidation.c
--- a/ext/lib/encoding/tinycbor/src/cborvalidation.c
+++ b/ext/lib/encoding/tinycbor/src/cborvalidation.c
@@ -325,7 +325,7 @@ static inline CborError validate_number(
static inline CborError validate_tag(CborValue *it, CborTag tag, int flags, int recursionLeft)
{
CborType type = cbor_value_get_type(it);
- const size_t knownTagCount = sizeof(knownTagData) / sizeof(knownTagData[0]);
+ const size_t knownTagCount = ARRAY_SIZE(knownTagData);
const struct KnownTagData *tagData = knownTagData;
const struct KnownTagData * const knownTagDataEnd = knownTagData + knownTagCount;
context 模式详解
context 以类似 diff 的形式突出显示相关行及其上下文。
备注
生成的类 diff 输出并非可应用的补丁。context 模式旨在突出显示重要行(以减号 - 标记),并提供周围上下文。可以使用 Emacs 的 diff 模式审查此输出中的代码。
Example
运行:
./scripts/coccicheck --mode=context --cocci=scripts/coccinelle/array_size.cocci
将执行 SmPL 脚本中的以下部分:
<smpl>
@depends on context@
type T;
T[] E;
@@
(
* (sizeof(E)/sizeof(*E))
|
* (sizeof(E)/sizeof(E[...]))
|
* (sizeof(E)/sizeof(T))
)
</smpl>
此 SmPL 片段向标准输出生成如下 diff 块:
diff -u -p ext/lib/encoding/tinycbor/src/cborvalidation.c /tmp/nothing/ext/lib/encoding/tinycbor/src/cborvalidation.c
--- ext/lib/encoding/tinycbor/src/cborvalidation.c
+++ /tmp/nothing/ext/lib/encoding/tinycbor/src/cborvalidation.c
@@ -325,7 +325,6 @@ static inline CborError validate_number(
static inline CborError validate_tag(CborValue *it, CborTag tag, int flags, int recursionLeft)
{
CborType type = cbor_value_get_type(it);
- const size_t knownTagCount = sizeof(knownTagData) / sizeof(knownTagData[0]);
const struct KnownTagData *tagData = knownTagData;
const struct KnownTagData * const knownTagDataEnd = knownTagData + knownTagCount;
org 模式详解
org:生成 Emacs Org mode 格式报告。
Example
运行:
./scripts/coccicheck --mode=org --cocci=scripts/coccinelle/misc/array_size.cocci
将执行 SmPL 脚本中的以下部分:
<smpl>
@r depends on (org || report)@
type T;
T[] E;
position p;
@@
(
(sizeof(E)@p /sizeof(*E))
|
(sizeof(E)@p /sizeof(E[...]))
|
(sizeof(E)@p /sizeof(T))
)
@script:python depends on org@
p << r.p;
@@
coccilib.org.print_todo(p[0], "WARNING should use ARRAY_SIZE")
</smpl>
此 SmPL 片段向标准输出生成如下 Org 条目:
* TODO [[view:ext/lib/encoding/tinycbor/src/cborvalidation.c::face=ovl-face1::linb=328::colb=52::cole=53][WARNING should use ARRAY_SIZE]]
Coccinelle 邮件列表
订阅 Coccinelle 邮件列表:
归档: