总结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

进而确定发生panic的源代码的位置。


说这个例子,只是解释一下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

写于上海


标签: Kernel panic 空指针

评论:

simonzhang
2016-01-03 19:58
其实对0地址,或0+一个小的offset,在运行时无论读写应该都会exception的,因为这个地址是不在当前的virtual memory space里面。
&((TYPE *)0)->MEMBER之所以不会有错误,其实是因为编译器会做出结构体内偏移的计算,这个值也就是一个常量。

do {} while(0)我的理解是为了保证宏展开时的安全性
wowo
2016-01-03 20:56
@simonzhang:"&((TYPE *)0)->MEMBER",这个应该是预编译时的计算出来的,不是运行时的事情,否则肯定会exception的。
cuijier
2015-10-24 14:13
博主你好,我想问下,在调试android手机的过程中,是否也可以用
#gdb vmlinux
(gdb) b*(0xc0406081 + 0x1a)
这个来定位 Trace 具体的位置?应该不行吧?
wowo
2015-10-25 14:40
@cuijier:如果只是调试kernel(vmlinux),是可以的。kernel是不区分Android还是其它系统的。
linuxer
2015-07-29 09:16
文章写的真心不错,很有调理。
snail
2015-07-29 00:00
这篇文章的发布时间,系统搞错了!

发表评论:

Copyright @ 2013-2015 蜗窝科技 All rights reserved. Powered by emlog