最近遇到了不少结构体,在计算偏移的时候有些麻烦,故摸鱼写下此文。
基础知识
在X86或ARM上,基本数据类型并不是存放在任意内存地址上的。 每种类型除了char都有对齐要求(alignment requirement); char类型可以开始于任何地址,但2字节的short类型必须存放在偶数地址上,4字节的整型或浮点型必须放在能被4整除的位置上,而8字节的long或double型必须放在能被8整除的地址上。有符号或无符号没有差别。术语上,这叫自对齐(self-aligned),至于为什么要这么对齐,可以简单的理解为是为了提高cpu的访问速度。
在没有#pragma pack这个宏的声明下,遵循下面三个原则:
1、***结构体中元素按照定义顺序依次置于内存中,但并不是紧密排列。从结构体首地址开始依次将元素放入内存时,元素会被放置在其自身对齐大小的整数倍地址上。***(这里说的地址是元素在结构体中的偏移量,结构体首地址偏移量为0。)
2、*如果结构体大小不是所有元素中最大对齐大小的整数倍,则结构体对齐到最大元素对齐大小的整数倍,填充空间放置到结构体末尾。*
3、基本数据类型的对齐大小为其自身的大小,结构体数据类型的对齐大小为其元素中最大对齐大小元素的对齐大小。
举例:
1 | struct test |
由于对齐的原因,该结构体大小不是1+1+2=4字节,第二个short地址应该为2字节对齐,然而该结构体大小也不是5,而是6,因为还需遵守第2个规则,结构体的总大小,为其成员中所含最大类型的整数倍,故test结构体的大小为6字节。
大致如下:
在缺省情况下,C编译器为每一个变量或是数据单元按其自然对界条件分配空间。
但也可以声明让编译器按其他方式优化对齐,如:
· __attribute((aligned (n))),让所作用的结构成员对齐在n字节自然边界上。如果结构中有成员的长度大于n,则按照最大成员的长度来对齐。
· attribute ((packed)),取消结构在编译过程中的优化对齐,按照实际占用字节数进行对齐。
计算偏移
内核中有大量的庞大结构体,按上面的方法自己去计算是非常麻烦的,在我查询资料的途中,找到了几种计算方法
方法一:
使用#include <stddef.h> 中的 offsetof函数来实现
1 | offsetof(type, member-designator) |
示例:
1 | #include <stddef.h> |
结果:
1 | address 结构中的 name 偏移 = 0 字节。 |
方法二
把源码中结构体的定义截出来,然后自己编译运行计算一下
比如说随便找个内核结构体
1 | struct old_timespec32 { |
这样的
方法三
然而,很多内核结构体编译的时候都有各种条件,很多时候没法还原,以及各种结构体之间是嵌套的,要自己编译非常麻烦
如task_struct 结构体位于/linux-5.8/include/linux/sched.h 中,但定义是带各种ifdef 条件的
这种时候我们要调试计算结构体,一开始我是用gdb直接内存地址偏移相差来计算的,但这样非常非常麻烦。
最后找到了pahole工具,只能说非常方便(
pahole 工具位于dwarves包中,可以直接apt安装:
1 | sudo apt install dwarves -y |
官方使用手册:
1 | https://lwn.net/Articles/335942/ |
–help可以查看帮助
一般来说
我们直接无脑使用-V参数即可,它会打印所有的hole情况
1 | pahole -V vmlinux > outputfile |
最终会看到
非常详细
结构体末尾处还有这样的统计:
当然我们也可以使用-C参数:
1 | pahole -C task_struct vmlinux > output |
这样就会只打印task_struct结构体的
参考链接:
https://blog.csdn.net/sinat_31039061/article/details/104366376
https://lwn.net/Articles/335942/
https://cloud.tencent.com/developer/article/1703257
https://blog.csdn.net/zzxfbdfhbdfhbdb/article/details/120561973