我对结构填充和打包的语义感兴趣,特别是与从Linux内核返回的结构有关的语义。
例如,如果对program + stdlib进行了编译,则不会进行结构填充,而对内核进行了编译,从而进行了结构填充does(无论如何,IIRC是GCC的默认设置),由于从内核返回的结构从其角度来看是垃圾,因此程序无法运行。
如果所涉及的编译器随着时间的推移更改了其填充语义,那么肯定会出现同样的问题。 /usr/include/linux/*
和/usr/include/asm-generic/*
中定义的结构似乎没有打包,因此它们取决于所使用的编译器以及该编译器的对齐语义,对吗?
但是我可以在几年前在具有不同内存对齐要求和可能的填充语义的另一台计算机上获取二进制编译的代码,并在我的现代计算机上运行它,看起来工作正常。
它怎么看不到垃圾?这是纯粹的运气吗?编译器作者(例如TCC之类的人)是否注意复制GCC的结构填充语义?在现实世界中如何处理这个潜在问题?
/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];
};
看到__pad0
? int
通常为4个字节,但st_rdev
为long
,即8个字节,因此必须对齐8个字节。但是,它的前面是3个整数= 12个字节,因此添加了4个字节的__pad0
。
本质上,stdlib的实现要小心地对其ABI进行硬编码。
BUT并非适用于所有API。这是struct flock
调用所使用的/usr/include/asm-generic/fcntl.h
(来自同一台计算机,fcntl()
):
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_whence
和l_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
之后的字段确实是垃圾。