UAPI 检查器¶
UAPI 检查器 (scripts/check-uapi.sh
) 是一个 shell 脚本,用于检查 UAPI 头文件在整个 git 树中与用户空间的向后兼容性。
选项¶
本节将描述运行 check-uapi.sh
时可用的选项。
用法
check-uapi.sh [-b BASE_REF] [-p PAST_REF] [-j N] [-l ERROR_LOG] [-i] [-q] [-v]
可用选项
-b BASE_REF Base git reference to use for comparison. If unspecified or empty,
will use any dirty changes in tree to UAPI files. If there are no
dirty changes, HEAD will be used.
-p PAST_REF Compare BASE_REF to PAST_REF (e.g. -p v6.1). If unspecified or empty,
will use BASE_REF^1. Must be an ancestor of BASE_REF. Only headers
that exist on PAST_REF will be checked for compatibility.
-j JOBS Number of checks to run in parallel (default: number of CPU cores).
-l ERROR_LOG Write error log to file (default: no error log is generated).
-i Ignore ambiguous changes that may or may not break UAPI compatibility.
-q Quiet operation.
-v Verbose operation (print more information about each header being checked).
环境变量
ABIDIFF Custom path to abidiff binary
CC C compiler (default is "gcc")
ARCH Target architecture of C compiler (default is host arch)
退出码
0) Success
1) ABI difference detected
2) Prerequisite not met
示例¶
基本用法¶
首先,让我们尝试对 UAPI 头文件进行更改,这显然不会破坏用户空间
cat << 'EOF' | patch -l -p1
--- a/include/uapi/linux/acct.h
+++ b/include/uapi/linux/acct.h
@@ -21,7 +21,9 @@
#include <asm/param.h>
#include <asm/byteorder.h>
-/*
+#define FOO
+
+/*
* comp_t is a 16-bit "floating" point number with a 3-bit base 8
* exponent and a 13-bit fraction.
* comp2_t is 24-bit with 5-bit base 2 exponent and 20 bit fraction
diff --git a/include/uapi/linux/bpf.h b/include/uapi/linux/bpf.h
EOF
现在,让我们使用脚本来验证
% ./scripts/check-uapi.sh
Installing user-facing UAPI headers from dirty tree... OK
Installing user-facing UAPI headers from HEAD... OK
Checking changes to UAPI headers between HEAD and dirty tree...
All 912 UAPI headers compatible with x86 appear to be backwards compatible
让我们添加另一个可能会破坏用户空间的更改
cat << 'EOF' | patch -l -p1
--- a/include/uapi/linux/bpf.h
+++ b/include/uapi/linux/bpf.h
@@ -74,7 +74,7 @@ struct bpf_insn {
__u8 dst_reg:4; /* dest register */
__u8 src_reg:4; /* source register */
__s16 off; /* signed offset */
- __s32 imm; /* signed immediate constant */
+ __u32 imm; /* unsigned immediate constant */
};
/* Key of an a BPF_MAP_TYPE_LPM_TRIE entry */
EOF
脚本会捕获此问题
% ./scripts/check-uapi.sh
Installing user-facing UAPI headers from dirty tree... OK
Installing user-facing UAPI headers from HEAD... OK
Checking changes to UAPI headers between HEAD and dirty tree...
==== ABI differences detected in include/linux/bpf.h from HEAD -> dirty tree ====
[C] 'struct bpf_insn' changed:
type size hasn't changed
1 data member change:
type of '__s32 imm' changed:
typedef name changed from __s32 to __u32 at int-ll64.h:27:1
underlying type 'int' changed:
type name changed from 'int' to 'unsigned int'
type size hasn't changed
==================================================================================
error - 1/912 UAPI headers compatible with x86 appear _not_ to be backwards compatible
在这种情况下,脚本报告类型更改是因为它可能会破坏传入负数的用户空间程序。现在,假设您知道没有用户空间程序可能在 imm
中使用负值,因此将其更改为无符号类型不应造成任何损害。您可以将 -i
标志传递给脚本,以忽略用户空间向后兼容性不明确的更改
% ./scripts/check-uapi.sh -i
Installing user-facing UAPI headers from dirty tree... OK
Installing user-facing UAPI headers from HEAD... OK
Checking changes to UAPI headers between HEAD and dirty tree...
All 912 UAPI headers compatible with x86 appear to be backwards compatible
现在,让我们进行类似的将破坏用户空间的更改
cat << 'EOF' | patch -l -p1
--- a/include/uapi/linux/bpf.h
+++ b/include/uapi/linux/bpf.h
@@ -71,8 +71,8 @@ enum {
struct bpf_insn {
__u8 code; /* opcode */
- __u8 dst_reg:4; /* dest register */
__u8 src_reg:4; /* source register */
+ __u8 dst_reg:4; /* dest register */
__s16 off; /* signed offset */
__s32 imm; /* signed immediate constant */
};
EOF
由于我们正在重新排序现有结构成员,因此没有歧义,即使您传递 -i
,脚本也会报告破坏
% ./scripts/check-uapi.sh -i
Installing user-facing UAPI headers from dirty tree... OK
Installing user-facing UAPI headers from HEAD... OK
Checking changes to UAPI headers between HEAD and dirty tree...
==== ABI differences detected in include/linux/bpf.h from HEAD -> dirty tree ====
[C] 'struct bpf_insn' changed:
type size hasn't changed
2 data member changes:
'__u8 dst_reg' offset changed from 8 to 12 (in bits) (by +4 bits)
'__u8 src_reg' offset changed from 12 to 8 (in bits) (by -4 bits)
==================================================================================
error - 1/912 UAPI headers compatible with x86 appear _not_ to be backwards compatible
让我们提交破坏性更改,然后提交无害的更改
% git commit -m 'Breaking UAPI change' include/uapi/linux/bpf.h
[detached HEAD f758e574663a] Breaking UAPI change
1 file changed, 1 insertion(+), 1 deletion(-)
% git commit -m 'Innocuous UAPI change' include/uapi/linux/acct.h
[detached HEAD 2e87df769081] Innocuous UAPI change
1 file changed, 3 insertions(+), 1 deletion(-)
现在,让我们再次运行没有参数的脚本
% ./scripts/check-uapi.sh
Installing user-facing UAPI headers from HEAD... OK
Installing user-facing UAPI headers from HEAD^1... OK
Checking changes to UAPI headers between HEAD^1 and HEAD...
All 912 UAPI headers compatible with x86 appear to be backwards compatible
它没有捕获任何破坏性更改,因为默认情况下,它仅比较 HEAD
和 HEAD^1
。破坏性更改已在 HEAD~2
上提交。如果我们希望搜索范围进一步追溯,则必须使用 -p
选项来传递不同的过去参考。 在这种情况下,让我们将 -p HEAD~2
传递给脚本,以便它检查 HEAD~2
和 HEAD
之间的 UAPI 更改
% ./scripts/check-uapi.sh -p HEAD~2
Installing user-facing UAPI headers from HEAD... OK
Installing user-facing UAPI headers from HEAD~2... OK
Checking changes to UAPI headers between HEAD~2 and HEAD...
==== ABI differences detected in include/linux/bpf.h from HEAD~2 -> HEAD ====
[C] 'struct bpf_insn' changed:
type size hasn't changed
2 data member changes:
'__u8 dst_reg' offset changed from 8 to 12 (in bits) (by +4 bits)
'__u8 src_reg' offset changed from 12 to 8 (in bits) (by -4 bits)
==============================================================================
error - 1/912 UAPI headers compatible with x86 appear _not_ to be backwards compatible
或者,我们也可以使用 -b HEAD~
运行。这将把基本参考设置为 HEAD~
,然后脚本会将其与 HEAD~^1
进行比较。
架构特定的标头¶
考虑此更改
cat << 'EOF' | patch -l -p1
--- a/arch/arm64/include/uapi/asm/sigcontext.h
+++ b/arch/arm64/include/uapi/asm/sigcontext.h
@@ -70,6 +70,7 @@ struct sigcontext {
struct _aarch64_ctx {
__u32 magic;
__u32 size;
+ __u32 new_var;
};
#define FPSIMD_MAGIC 0x46508001
EOF
这是对 arm64 特定 UAPI 头文件的更改。在此示例中,我正在从具有 x86 编译器的 x86 机器运行脚本,因此默认情况下,该脚本仅检查 x86 兼容的 UAPI 头文件
% ./scripts/check-uapi.sh
Installing user-facing UAPI headers from dirty tree... OK
Installing user-facing UAPI headers from HEAD... OK
No changes to UAPI headers were applied between HEAD and dirty tree
使用 x86 编译器,我们无法检查 arch/arm64
中的头文件,因此脚本甚至没有尝试。
如果我们想检查头文件,则必须使用 arm64 编译器并相应地设置 ARCH
% CC=aarch64-linux-gnu-gcc ARCH=arm64 ./scripts/check-uapi.sh
Installing user-facing UAPI headers from dirty tree... OK
Installing user-facing UAPI headers from HEAD... OK
Checking changes to UAPI headers between HEAD and dirty tree...
==== ABI differences detected in include/asm/sigcontext.h from HEAD -> dirty tree ====
[C] 'struct _aarch64_ctx' changed:
type size changed from 64 to 96 (in bits)
1 data member insertion:
'__u32 new_var', at offset 64 (in bits) at sigcontext.h:73:1
-- snip --
[C] 'struct zt_context' changed:
type size changed from 128 to 160 (in bits)
2 data member changes (1 filtered):
'__u16 nregs' offset changed from 64 to 96 (in bits) (by +32 bits)
'__u16 __reserved[3]' offset changed from 80 to 112 (in bits) (by +32 bits)
=======================================================================================
error - 1/884 UAPI headers compatible with arm64 appear _not_ to be backwards compatible
我们可以看到,通过为文件正确设置 ARCH
和 CC
,可以正确报告 ABI 更改。另请注意,脚本检查的 UAPI 头文件总数发生了变化。这是因为为 arm64 平台安装的头文件数量与 x86 不同。
跨依赖项破坏¶
考虑此更改
cat << 'EOF' | patch -l -p1
--- a/include/uapi/linux/types.h
+++ b/include/uapi/linux/types.h
@@ -52,7 +52,7 @@ typedef __u32 __bitwise __wsum;
#define __aligned_be64 __be64 __attribute__((aligned(8)))
#define __aligned_le64 __le64 __attribute__((aligned(8)))
-typedef unsigned __bitwise __poll_t;
+typedef unsigned short __bitwise __poll_t;
#endif /* __ASSEMBLY__ */
#endif /* _UAPI_LINUX_TYPES_H */
EOF
在这里,我们正在更改 types.h
中的 typedef
。这不会破坏 types.h
中的 UAPI,但是树中的其他 UAPI 可能会因此更改而被破坏
% ./scripts/check-uapi.sh
Installing user-facing UAPI headers from dirty tree... OK
Installing user-facing UAPI headers from HEAD... OK
Checking changes to UAPI headers between HEAD and dirty tree...
==== ABI differences detected in include/linux/eventpoll.h from HEAD -> dirty tree ====
[C] 'struct epoll_event' changed:
type size changed from 96 to 80 (in bits)
2 data member changes:
type of '__poll_t events' changed:
underlying type 'unsigned int' changed:
type name changed from 'unsigned int' to 'unsigned short int'
type size changed from 32 to 16 (in bits)
'__u64 data' offset changed from 32 to 16 (in bits) (by -16 bits)
========================================================================================
include/linux/eventpoll.h did not change between HEAD and dirty tree...
It's possible a change to one of the headers it includes caused this error:
#include <linux/fcntl.h>
#include <linux/types.h>
请注意,脚本注意到失败的头文件没有更改,因此它假定其包含项之一导致了破坏。实际上,我们可以看到 linux/types.h
是从 eventpoll.h
使用的。
UAPI 标头删除¶
考虑此更改
cat << 'EOF' | patch -l -p1
diff --git a/include/uapi/asm-generic/Kbuild b/include/uapi/asm-generic/Kbuild
index ebb180aac74e..a9c88b0a8b3b 100644
--- a/include/uapi/asm-generic/Kbuild
+++ b/include/uapi/asm-generic/Kbuild
@@ -31,6 +31,6 @@ mandatory-y += stat.h
mandatory-y += statfs.h
mandatory-y += swab.h
mandatory-y += termbits.h
-mandatory-y += termios.h
+#mandatory-y += termios.h
mandatory-y += types.h
mandatory-y += unistd.h
EOF
此脚本从安装列表中删除 UAPI 头文件。让我们运行脚本
% ./scripts/check-uapi.sh
Installing user-facing UAPI headers from dirty tree... OK
Installing user-facing UAPI headers from HEAD... OK
Checking changes to UAPI headers between HEAD and dirty tree...
==== UAPI header include/asm/termios.h was removed between HEAD and dirty tree ====
error - 1/912 UAPI headers compatible with x86 appear _not_ to be backwards compatible
删除 UAPI 标头被认为是破坏性更改,脚本会将其标记为如此。
检查历史 UAPI 兼容性¶
您可以使用 -b
和 -p
选项来检查 git 树的不同块。例如,要检查 v6.0 和 v6.1 标记之间的所有已更改的 UAPI 头文件,您将运行
% ./scripts/check-uapi.sh -b v6.1 -p v6.0
Installing user-facing UAPI headers from v6.1... OK
Installing user-facing UAPI headers from v6.0... OK
Checking changes to UAPI headers between v6.0 and v6.1...
--- snip ---
error - 37/907 UAPI headers compatible with x86 appear _not_ to be backwards compatible
注意:在 v5.3 之前,脚本所需的头文件不存在,因此脚本无法检查之前的更改。
您会注意到,脚本检测到许多与向后不兼容的 UAPI 更改。 考虑到内核 UAPI 应该永远稳定,这是一个令人震惊的结果。这使我们进入下一节:注意事项。
注意事项¶
UAPI 检查器对作者的意图不做任何假设,因此即使某些类型的更改有意破坏 UAPI,也可能会被标记。
用于重构或弃用的删除¶
有时会删除非常旧的硬件的驱动程序,例如在此示例中
% ./scripts/check-uapi.sh -b ba47652ba655
Installing user-facing UAPI headers from ba47652ba655... OK
Installing user-facing UAPI headers from ba47652ba655^1... OK
Checking changes to UAPI headers between ba47652ba655^1 and ba47652ba655...
==== UAPI header include/linux/meye.h was removed between ba47652ba655^1 and ba47652ba655 ====
error - 1/910 UAPI headers compatible with x86 appear _not_ to be backwards compatible
脚本将始终标记删除(即使它们是有意的)。
结构扩展¶
根据结构在内核空间中的处理方式,扩展结构的更改可能不是破坏性的。
如果结构用作 ioctl 的参数,则内核驱动程序必须能够处理任何大小的 ioctl 命令。除此之外,从用户复制数据时需要小心。例如,假设像这样更改了 struct foo
struct foo {
__u64 a; /* added in version 1 */
+ __u32 b; /* added in version 2 */
+ __u32 c; /* added in version 2 */
}
默认情况下,脚本将标记这种类型的更改以供进一步审核
[C] 'struct foo' changed:
type size changed from 64 to 128 (in bits)
2 data member insertions:
'__u32 b', at offset 64 (in bits)
'__u32 c', at offset 96 (in bits)
但是,此更改可能是安全进行的。
如果使用版本 1 构建了用户空间程序,它会认为 sizeof(struct foo)
为 8。该大小将编码在发送到内核的 ioctl 值中。如果使用版本 2 构建了内核,它会认为 sizeof(struct foo)
为 16。
内核可以使用 _IOC_SIZE
宏来获取用户传入的 ioctl 代码中编码的大小,然后使用 copy_struct_from_user()
安全地复制该值
int handle_ioctl(unsigned long cmd, unsigned long arg)
{
switch _IOC_NR(cmd) {
0x01: {
struct foo my_cmd; /* size 16 in the kernel */
ret = copy_struct_from_user(&my_cmd, arg, sizeof(struct foo), _IOC_SIZE(cmd));
...
copy_struct_from_user
会将内核中的结构清零,然后仅复制从用户传入的字节(将新成员清零)。如果用户传入了更大的结构,则会忽略多余的成员。
如果您知道内核代码中考虑到了这种情况,则可以将 -i
传递给脚本,并且会忽略这样的结构扩展。
Flex 数组迁移¶
虽然脚本处理扩展到现有 flex 数组,但它仍然标记从 1 元素伪 flex 数组到 flex 数组的初始迁移。例如
struct foo {
__u32 x;
- __u32 flex[1]; /* fake flex */
+ __u32 flex[]; /* real flex */
};
脚本会标记此更改
[C] 'struct foo' changed:
type size changed from 64 to 32 (in bits)
1 data member change:
type of '__u32 flex[1]' changed:
type name changed from '__u32[1]' to '__u32[]'
array type size changed from 32 to 'unknown'
array type subrange 1 changed length from 1 to 'unknown'
目前,没有办法过滤这些类型的更改,因此请注意这种可能的误报。
总结¶
虽然脚本过滤掉了许多类型的误报,但在某些情况下,脚本可能会标记不破坏 UAPI 的更改。也可能脚本不会标记破坏用户空间的更改。虽然该脚本已在大部分内核历史上运行,但可能仍然存在未考虑到的极端情况。
目的是将此脚本用作维护者或自动化工具的快速检查,而不是作为补丁兼容性的最终权威。最好记住:使用您最好的判断力(最好是在用户空间中使用单元测试)来确保您的 UAPI 更改向后兼容!