Linux内核中结构填充/打包的语义是啥?

Posted

技术标签:

【中文标题】Linux内核中结构填充/打包的语义是啥?【英文标题】:What are the semantics of structure padding/packing in the Linux kernel?Linux内核中结构填充/打包的语义是什么? 【发布时间】:2020-03-05 21:54:24 【问题描述】:

我对结构填充和打包的语义感兴趣,特别是与从 Linux 内核返回的结构相关的语义。

例如,如果一个程序+stdlib 被编译所以结构填充不会发生,而内核被编译时结构填充确实 发生(无论如何,IIRC 是 GCC 的默认值),程序肯定无法运行,因为从内核返回的结构在它看来是垃圾。

如果有问题的编译器随着时间的推移改变了它的填充语义,那么肯定会出现同样的问题。 /usr/include/linux/*/usr/include/asm-generic/* 中定义的结构似乎没有被打包,因此它们取决于使用的编译器和所述编译器的对齐语义,对吧?

但我可以在具有不同内存对齐要求和可能不同填充语义的不同计算机上获取几年前编译的二进制文件,然后在我的现代计算机上运行它,它似乎工作正常。

它怎么看不到垃圾?这只是纯粹的运气吗?编译器作者(比如 TCC 等)是否注意复制 GCC 的结构填充语义?这个潜在的问题在现实世界中是如何处理的?

【问题讨论】:

【参考方案1】:

/usr/include/linux/* 中定义的结构和 /usr/include/asm-generic/* 似乎没有打包,所以他们 取决于使用的编译器和所述的对齐语义 编译器,对吧?

一般来说,这不是真的。这是 GCC 在 64 位 Ubuntu (/usr/include/x86_64-linux-gnu/asm/stat.h) 上的示例:

struct stat 
        __kernel_ulong_t        st_dev;
        __kernel_ulong_t        st_ino;
        __kernel_ulong_t        st_nlink;

        unsigned int            st_mode;
        unsigned int            st_uid;
        unsigned int            st_gid;
        unsigned int            __pad0;
        __kernel_ulong_t        st_rdev;
        __kernel_long_t         st_size;
        __kernel_long_t         st_blksize;
        __kernel_long_t         st_blocks;      /* Number 512-byte blocks allocated. */

        __kernel_ulong_t        st_atime;
        __kernel_ulong_t        st_atime_nsec;
        __kernel_ulong_t        st_mtime;
        __kernel_ulong_t        st_mtime_nsec;
        __kernel_ulong_t        st_ctime;
        __kernel_ulong_t        st_ctime_nsec;
        __kernel_long_t         __unused[3];
;

看到__pad0int一般是4字节,但是st_rdevlong,也就是8字节,所以必须8字节对齐。但是,它前面有 3 个整数 = 12 个字节,因此添加了一个 4 字节的__pad0

从本质上讲,stdlib 的实现会注意对其 ABI 进行硬编码。

但是并非所有 API 都如此。这是fcntl()调用使用的struct flock(来自同一台机器,/usr/include/asm-generic/fcntl.h):

struct flock 
    short   l_type;
    short   l_whence;
    __kernel_off_t  l_start;
    __kernel_off_t  l_len;
    __kernel_pid_t  l_pid;
    __ARCH_FLOCK_PAD
;

如您所见,l_whencel_start 之间没有填充。事实上,对于下面的 C 程序,保存为 abi.c:

#include <fcntl.h>
#include <string.h>

int main(int argc, char **argv)

    struct flock fl;
    int fd;

    fd = open("y", O_RDWR);
    memset(&fl, 0xff, sizeof(fl));
    fl.l_type = F_RDLCK;
    fl.l_whence = SEEK_SET;
    fl.l_start = 200;
    fl.l_len = 1;
    fcntl(fd, F_SETLK, &fl);

我们得到:

$ cc -g -o abi abi.c && strace -e fcntl ./abi
fcntl(3, F_SETLK, l_type=F_RDLCK, l_whence=SEEK_SET, l_start=200, l_len=1) = 0
+++ exited with 0 +++
$ cc -g -fpack-struct -o abi abi.c && strace -e fcntl ./abi
fcntl(3, F_SETLK, l_type=F_RDLCK, l_whence=SEEK_SET, l_start=4294967296, l_len=-4294967296) = 0
+++ exited with 0 +++

如您所见,l_whence 后面的字段确实是垃圾。

此外,C has no ABI,因此这种脆弱的兼容性依赖于良好的实现。上面的struct stat 假设编译器不会插入额外的随机填充。

ANSI C 说:

结构或联合的末尾也可能有未命名的填充,如果结构或联合成为数组的成员,则需要实现适当的对齐。

没有关于如何将填充插入结构中间的措辞,除了对齐之外,还有:

实现定义的行为

每个实现都应在本节列出的每个领域中记录其行为。以下是实现定义的:

...

结构成员的填充和对齐。这应该没有问题,除非一个实现写入的二进制数据被另一个实现读取。

在我的 Ubuntu 机器上,编译器和标准库都来自 GCC,因此它们可以顺利互操作。 Clang 想要成长,所以它与 GNU libc 兼容。大多数时候,每个人都玩得很好。

【讨论】:

啊,有趣。我还没有阅读stat 的定义。当然,由于struct stat 没有被指定为__attribute(packed),那么编译器可能会在 填充元素之间添加额外的填充? 正确。 C 不保证“没有漏洞”,ANSI C 字面意思是“这应该没有问题,除非一个实现写入的二进制数据被另一个实现读取。”

以上是关于Linux内核中结构填充/打包的语义是啥?的主要内容,如果未能解决你的问题,请参考以下文章

.apt是啥文件?

Linux Kernel是啥

linux init

ARM 一共有多少种内核,最新的是啥

HTML语义化是啥意思?

了解 linux 或 BSD 内核内部的最佳方式是啥? [关闭]