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
传递给脚本,并且将忽略此类结构扩展。
弹性数组迁移¶
虽然脚本可以处理扩展到现有弹性数组的情况,但它仍然会标记从 1 元素伪弹性数组到弹性数组的初始迁移。例如
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 更改向后兼容!