总结Linux kernel开发中0的巧妙使用
作者:snail 发布于:2015-7-20 17:53
kernel的源代码中,有很多令人拍案叫好的用法,此文简单总结了一下,我所知道的0的巧妙用法,权当抛砖引玉,希望能激发起读者相当更多的0的用法。
用法一:0长数组
0长数组在GNU C中是被允许的,0长数组作为一个结构体的最后一个元素是非常有用的,尤其
是当这个结构体表达的一个可变长度的对象时,如下:
struct line {
int length;
char contents[0];
};
struct line *thisline = (struct line *)malloc(sizeof (struct line) + this_length);
thisline->length = this_length;
特别要注意的一点是:结构体sizeof(struct line) == 4,也即数组contents[0]不占用任何空间,
在使用的时候动态分配。
详细描述可参考下面的链接:
https://gcc.gnu.org/onlinedocs/gcc-4.6.2/gcc/Zero-Length.html#Zero-Length
用法二: 获取结构体中成员变量的offset
#define offsetof(TYPE, MEMBER) ((size_t)&((TYPE *)0)->MEMBER)
上面是kernel源码中的一个宏定义,功能是获取结构体(TYPE)中成员变量(MEMBER)的offset。
我们都知道指针就是一个整型数值,这个数值代表的是内存的地址,而结构体所占用的内存是
连续分配的。假如成员MEMBER的地址为p1, TYPE的地址为p2,那么MEMBER的offset为p1-p2,这个
宏中假设p2为0,所以offset == p1.
用法三:debug kernel panic
当kernel panic时,终端会输出类似如下的log:
Call Trace:
[<c0406081>] show_trace_log_lvl+0x1a/0x2f
[<c0406131>] show_stack_log_lvl+0x9b/0xa3
[<c04061dc>] show_registers+0xa3/0x1df
Code:
e3 a0 20 01 <e5> 83 20 00 f5 7f f0 4e e3 a0 30 00 e5 c3 20 00
如果有此次kernel版本的vmlinux,那就很简单了,直接
#gdb vmlinux
(gdb) b*(0xc0406081 + 0x1a)
就可以知道panic发生在哪一行了。如果没有vmlinux怎么办呢?
从网上看到有人说,Linus通常使用一个小程序,类似这样:
const char array[] = "\xnn\xnn\xnn\xnn\xnn\xnn\xnn\xnn";
int main(int argc, char * argv[])
{
printf("%p\n", array);
*(int *)0 = 0;
}
数组里就是发生panic时,最后的几个指令,上面用<>标记的红色的指令,就是发生panic的指令,
当实际解析这些指令的时候,需要注意大小端问题,如下:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
const char a[] = "\xe3\xa0\x20\x01\xe5\x83\x20\x00\xf5\x7f\xf0\x4e\xe3\xa0\x30\x00\xe5\xc3\x20\x00";
int main(int argc, char *argv[])
{
printf("%p\n", a);
*(int *)0 = 0;
}
#gcc h.c
然后用gdb分析, 如下图,因为错误指令是a[4], 也即in $0x83, %eax
为了使反汇编的结果与c代码对应起来,kernel的Kbuild子系统提供了一个这样的功能,任何一个c文件,都可以
单独编译成汇编文件,如下:
make O=~/kernel/ ARCH=arm CROSS_COMPILE=~/prebuilts/gcc/linux-x86/arm/arm-eabi-4.8/bin/arm-eabi- KCFLAGS="-fno-pic -mno-unaligned-access" V=1 kernel/user.s
说这个例子,只是解释一下0的使用技巧,这只是老的调试方法,因为kernel中有一个脚本:scripts/decodecode来做这个事了,用法:
把放生panic那行code,写入~/oops.txt,然后:
.scripts/decodecode < ~/oops.txt
可能大家会有一个疑问:为什么在宏的定义中访问0地址,不会放生程序错误,而在这个例子中会产生段错误呢?
这是因为在宏中,我们对0地址的访问方式是读,而在这个例子中的访问方式是向0地址写数据,kernel对0地址初始化为只读的,
可能就是这个原因吧,这是我的个人理解。
用法四:do {} while(0)
这个用法大家都已经很熟悉了,就是把宏定义用do {} while(0)语句括起来,形成一个整体。
例子就不枚举了,但是定义这样的宏也有一些限制,具体的可以参看kernel文档:
Documentation/zh_CN/CodingStyle
暂时先总结这些吧,不妥之处,敬请批评指正。
haobenqu@126.com
写于上海

评论:
2015-10-24 14:13
#gdb vmlinux
(gdb) b*(0xc0406081 + 0x1a)
这个来定位 Trace 具体的位置?应该不行吧?
功能
最新评论
文章分类
随机文章
文章存档
- 2024年2月(1)
- 2023年5月(1)
- 2022年10月(1)
- 2022年8月(1)
- 2022年6月(1)
- 2022年5月(1)
- 2022年4月(2)
- 2022年2月(2)
- 2021年12月(1)
- 2021年11月(5)
- 2021年7月(1)
- 2021年6月(1)
- 2021年5月(3)
- 2020年3月(3)
- 2020年2月(2)
- 2020年1月(3)
- 2019年12月(3)
- 2019年5月(4)
- 2019年3月(1)
- 2019年1月(3)
- 2018年12月(2)
- 2018年11月(1)
- 2018年10月(2)
- 2018年8月(1)
- 2018年6月(1)
- 2018年5月(1)
- 2018年4月(7)
- 2018年2月(4)
- 2018年1月(5)
- 2017年12月(2)
- 2017年11月(2)
- 2017年10月(1)
- 2017年9月(5)
- 2017年8月(4)
- 2017年7月(4)
- 2017年6月(3)
- 2017年5月(3)
- 2017年4月(1)
- 2017年3月(8)
- 2017年2月(6)
- 2017年1月(5)
- 2016年12月(6)
- 2016年11月(11)
- 2016年10月(9)
- 2016年9月(6)
- 2016年8月(9)
- 2016年7月(5)
- 2016年6月(8)
- 2016年5月(8)
- 2016年4月(7)
- 2016年3月(5)
- 2016年2月(5)
- 2016年1月(6)
- 2015年12月(6)
- 2015年11月(9)
- 2015年10月(9)
- 2015年9月(4)
- 2015年8月(3)
- 2015年7月(7)
- 2015年6月(3)
- 2015年5月(6)
- 2015年4月(9)
- 2015年3月(9)
- 2015年2月(6)
- 2015年1月(6)
- 2014年12月(17)
- 2014年11月(8)
- 2014年10月(9)
- 2014年9月(7)
- 2014年8月(12)
- 2014年7月(6)
- 2014年6月(6)
- 2014年5月(9)
- 2014年4月(9)
- 2014年3月(7)
- 2014年2月(3)
- 2014年1月(4)
2016-01-03 19:58
&((TYPE *)0)->MEMBER之所以不会有错误,其实是因为编译器会做出结构体内偏移的计算,这个值也就是一个常量。
do {} while(0)我的理解是为了保证宏展开时的安全性