赞
踩
在研究IPV4解析过程时发现数据结构iphdr出现__be16类型,追踪后发现这个类型的定义为typedef __u16 __bitwise __be16;
__bitwise属性表示GCC编译器需要对指定大小端格式的变量运算时进行检查,提示一些可能导致数据泄露和数据错误的操作。
内核工具-Sparse也可以检查这些变量操作。
-------------------------------------------------------------------------------------------------转载部分1------------------------------------------------------------------------------------------
http://blog.csdn.net/gui694278452/article/details/7964694 《sparce与__be32》
be表示big endian,大端,le表示小端
__be32,__le32都是一样的,其实就是__u32,具体是什么端数据又系统决定的。
网络协议也是采用大端数据。
目前主要是用来发现大小端不匹配的错误。比如往big-endian的寄存器里面写入little-endian的数据。
cpu_to_be32()
be32_to_cpu()
sparse是一个C/C++源文件静态分析工具,官方文档:linux-2.6.38.8/Documentation/sparse.txt
Man手册:http://linux.die.net/man/1/sparse
主页:https://sparse.wiki.kernel.org/index.php/Main_Page
__be32,其定义扩展开为:
- <span style="font-size: 18px; ">#define __bitwise __attribute__((bitwise))
- typedef unsigned int __u32;
- typedef __u32 __bitwise __be32;
- </span>
__be32只是一个带有bitwise属性的整型类型,而这个属性对gcc本身没有任何作用,所以如果不利用sparse,__be32和__u32没有任何差别,但是如果利用sparse,它就能提供一种超强制的类型匹配检查。
比如下面这段代码:
- <span style="font-size: 18px; ">int __bitwise i;
- int __bitwise j;
- …
- i = j;
- </span>
最后一句代码是要被sparse告警的,如下:
CHECK /home/lenky/hello/hello.c
/home/lenky/hello/hello.c:17:3: warning: incorrect type in assignment (different base types)
/home/lenky/hello/hello.c:17:3: expected restricted int i
/home/lenky/hello/hello.c:17:3: got restricted int j
而下面这样的代码就没有问题:
- <span style="font-size: 18px; ">int __bitwise i, j;
- …
- i = j;
- </span>
根据上面的这个极端例子可以看到,bitwise属性总是创建一个新的数据类型,所以一般的情况就是把bitwise用在typedef内,如前面看到的__be32类型。由于typedef本身会创建一个新数据类型,所以下面这样的代码是没有问题的:
- <span style="font-size: 18px; ">__be32 i;
- __be32 j;
- …
- i = j;
- </span>
用在typedef内的bitwise貌似功能一已经发挥不了作用,但是正如其命名所示,它还有功能二,即强制安全位运算。这个怎么理解呢?举例来说,我们知道加法运算不是位运算安全的,因为加法运算会导致位循环移动;但与运算(后面有特例)、比较运算就是位运算安全的:
- <span style="font-size: 18px; ">__be32 i;
- __be32 j;
- …
- i += j;
- i &= j;
- </span>
倒数第二句代码将被sparse如下告警:
/home/lenky/hello/hello.c:17:3: warning: bad assignment (+=) to restricted __be32
另外的就是(特例),如果对一个bitwise的short或char做与运算也是不安全的。因为会导致类型提升,即转为int类型,此时可能会导致符号位改变。简而言之,bitwise的功能二就是保证数据位不丢失或循环移动。
我们的GFP_KERNEL就是一个带有bitwise属性的unsigned类型,所以对于GFP_KERNEL的意外操作都将提示告警:
- <span style="font-size: 18px; ">kmalloc(GFP_KERNEL, size);
- </span>
像上面这种参数搞反的情况,编译器检查不出来,但是sparse却可以。
除了__be32,还有如下类似:
typedef __u16 __bitwise __le16;
typedef __u16 __bitwise __be16;
typedef __u32 __bitwise __le32;
typedef __u32 __bitwise __be32;
typedef __u64 __bitwise __le64;
typedef __u64 __bitwise __be64;
上面这些宏定义头文件linux/types.h内,对于一个__be32变量i,经过前面的分析可以知道如下几点:
1.不同类型之间的赋值将告警:
- <span style="font-size: 18px; ">__be32 i;
- __be32 j;
- __le32 k;
- int t;
- …
- i = j; // ok
- i = k; // warning: incorrect type in assignment (different base types)
- i =t; // warning: incorrect type in assignment (different base types)
- t = i; // warning: incorrect type in assignment (different base types)
- </span>
2.即使是相同类型,如果操作不是位运算安全的,将告警:
- <span style="font-size: 18px; ">__be32 i;
- __be32 j;
- …
- i = i & j; // ok
- i = i << j; // warning: incorrect type in assignment (different base types)
- </span>
3.要对这些类型进行安全运输可以先进行强制转换(另外一个__force属性):
- <span style="font-size: 18px; ">__be32 i;
- __be32 j;
- __be32 sum;
- …
- sum = i + j; // warning: incorrect type in assignment (different base types)
- sum = cpu_to_be32(be32_to_cpu(i) + be32_to_cpu(j)); // ok
- </span>
根据字面意思,__be32等这些类型是用在有字节序(大小端)相关环境的。我们知道网络数据字节序为大端,而我们常用的x86 CPU为小端。假设要打印(printk)一个从网络上接收到数据包的源IP,我们可以这样:
<span style="font-size: 18px; ">printk(KERN_ALERT "source ip:%d\n", ntohl(iph->saddr));</span>
当然,也可以这样:
<span style="font-size: 18px; ">printk(KERN_ALERT "source ip:%d\n", __be32_to_cpu(iph->saddr));</span>
而大多数情况一般都是利用ntohl,毕竟这个是公开的接口,而带双下划线的__be32_to_cpu用得就比较少了,事实上ntohl就是__be32_to_cpu的宏定义:
- <span style="font-size: 18px; ">#define ___ntohl(x) __be32_to_cpu(x)
- #define ntohl(x) ___ntohl(x)
- </span>
最后介绍一下Sparse的使用,使用非常简单,编译时输入:
<span style="font-size: 18px; ">make C=2</span>
即可自动调用Sparse进行分析、检查。如果要单独的使用Sparse(详细选项请看man手册):
<span style="font-size: 18px; ">sparse hello.c</span>
对于__be32等这些类型,如果没有检查,请注意是否定义了__CHECK_ENDIAN__宏,因为根据头文件linux/types.h内代码,只有当定义了__CHECK_ENDIAN__宏时,__bitwise才有效:
- <span style="font-size: 18px; ">#ifdef __CHECK_ENDIAN__
- #define __bitwise __bitwise__
- #else
- #define __bitwise
- #endif</span>
-------------------------------------------------------------------------------------------------转载部分2---------------------------------------------------------------------------------------------
http://www.cnblogs.com/wang_yb/p/3575039.html 《内核工具 – Sparse 简介》
Sparse是内核代码静态分析工具, 能够帮助我们找出代码中的隐患.
主要内容:
Sparse 诞生于 2004 年, 是由linux之父开发的, 目的就是提供一个静态检查代码的工具, 从而减少linux内核的隐患.
其实在Sparse之前, 已经有了一个不错的代码静态检查工具("SWAT"), 只不过这个工具不是免费软件, 使用上有一些限制.
所以 linus 还是自己开发了一个静态检查工具.
具体可以参考这篇文章(2004年的文章了): Finding kernel problems automatically
Sparse相关的资料非常少, 关于它的使用方法我也是网上查找+自己实验得出来的.
内核代码中还有一个简略的关于 Sparse的说明文件: Documentation/sparse.txt
Sparse通过 gcc 的扩展属性 __attribute__ 以及自己定义的 __context__ 来对代码进行静态检查.
这些属性如下(尽量整理的,可能还有些不全的地方):
宏名称 | 宏定义 | 检查点 |
__bitwise | __attribute__((bitwise)) | 确保变量是相同的位方式(比如 bit-endian, little-endiandeng) |
__user | __attribute__((noderef, address_space(1))) | 指针地址必须在用户地址空间 |
__kernel | __attribute__((noderef, address_space(0))) | 指针地址必须在内核地址空间 |
__iomem | __attribute__((noderef, address_space(2))) | 指针地址必须在设备地址空间 |
__safe | __attribute__((safe)) | 变量可以为空 |
__force | __attribute__((force)) | 变量可以进行强制转换 |
__nocast | __attribute__((nocast)) | 参数类型与实际参数类型必须一致 |
__acquires(x) | __attribute__((context(x, 0, 1))) | 参数x 在执行前引用计数必须是0,执行后,引用计数必须为1 |
__releases(x) | __attribute__((context(x, 1, 0))) | 与 __acquires(x) 相反 |
__acquire(x) | __context__(x, 1) | 参数x 的引用计数 + 1 |
__release(x) | __context__(x, -1) | 与 __acquire(x) 相反 |
__cond_lock(x,c) | ((c) ? ({ __acquire(x); 1; }) : 0) | 参数c 不为0时,引用计数 + 1, 并返回1 |
其中 __acquires(x) 和 __releases(x), __acquire(x) 和 __release(x) 必须配对使用, 否则 Sparse 会给出警告
注: 在Fedora系统中通过 rpm 安装的 sparse 存在一个小bug.
即使用时会报出 error: unable to open ’stddef.h’ 的错误, 最好从自己源码编译安装 sparse.
参考: http://wangcong.org/blog/archives/504
主要作用就是确保内核使用的整数是在同样的位方式下.
在内核代码根目录下 grep -r '__bitwise', 会发现内核代码中很多地方都使用了这个宏.
对于使用了这个宏的变量, Sparse 会检查这个变量是否一直在同一种位方式(big-endian, little-endian或其他)下被使用,
如果此变量在多个位方式下被使用了, Sparse 会给出警告.
内核代码中的例子:
/* 内核版本:v2.6.32.61 file:include/sound/core.h 51行 */ typedef int __bitwise snd_device_type_t;
如果使用了 __user 宏的指针不在用户地址空间初始化, 或者指向内核地址空间, 设备地址空间等等, Sparse会给出警告.
内核代码中的例子:
/* 内核版本:v2.6.32.61 file:arch/score/kernel/signal.c 45行 */ static int setup_sigcontext(struct pt_regs *regs, struct sigcontext __user *sc)
如果使用了 __kernel 宏的指针不在内核地址空间初始化, 或者指向用户地址空间, 设备地址空间等等, Sparse会给出警告.
内核代码中的例子:
/* 内核版本:v2.6.32.61 file:arch/s390/lib/uaccess_pt.c 180行 */ memcpy(to, (void __kernel __force *) from, n);
如果使用了 __iomem 宏的指针不在设备地址空间初始化, 或者指向用户地址空间, 内核地址空间等等, Sparse会给出警告.
内核代码中的例子:
/* 内核版本:v2.6.32.61 file:arch/microblaze/include/asm/io.h 22行 */ static inline unsigned char __raw_readb(const volatile void __iomem *addr)
使用了 __safe修饰的变量在使用前没有判断它是否为空(null), Sparse会给出警告.
我参考的内核版本(v2.6.32.61) 中的所有内核代码都没有使用 __safe, 估计可能是由于随着gcc版本的更新,
gcc已经会对这种情况给出警告, 所以没有必要用Sparse去检查了.
使用了__force修饰的变量可以进行强制类型转换, 没有使用 __force修饰的变量进行强制类型转换时, Sparse会给出警告.
内核代码中的例子:
/* 内核版本:v2.6.32.61 file:arch/s390/lib/uaccess_pt.c 180行 */ memcpy(to, (void __kernel __force *) from, n);
使用了__nocast修饰的参数的类型必须和实际传入的参数类型一致才行,否则Sparse会给出警告.
内核代码中的例子:
/* 内核版本:v2.6.32.61 file:fs/xfs/support/ktrace.c 55行 */ ktrace_alloc(int nentries, unsigned int __nocast sleep)
这4个宏都是和锁有关的, __acquires 和 __releases 必须成对使用, __acquire 和 __release 必须成对使用, 否则Sparse会给出警告.
这个宏有点特别, 因为没有 __cond_unlock 之类的宏和它对应.
之所以有这个宏的原因可以参见: http://yarchive.net/comp/linux/sparse.html 最后一段.
这个宏的来源清楚了, 但是为什么这个宏里面还要调用一次 __acquire(x)? 我也不是很清楚, 在网上找了好久也没找到, 谁能指教的话非常感谢!!!
用 Sparse 对内核进行静态分析非常简单.
# 检查所有内核代码 make C=1 检查所有重新编译的代码 make C=2 检查所有代码, 不管是不是被重新编译
Sparse除了能够用在内核代码的静态分析上, 其实也可以用在一般的C语言程序中.
比如下面的小例子:
/****************************************************************************** * @file : sparse_test.c * @author : wangyubin * @date : Fri Feb 28 16:33:34 2014 * * @brief : 测试 sparse 的各个检查点 * history : init ******************************************************************************/ #include <stdio.h> #define __acquire(x) __context__(x,1) #define __release(x) __context__(x,-1) int main(int argc, char *argv[]) { int lock = 1; __acquire(lock); /* TODO something */ __release(lock); /* 注释掉这一句 sparse 就会报错 */ return 0; }
如果安装了 Sparse, 执行静态检查的命令如下:
$ sparse -a sparse_test.c sparse_test.c:15:5: warning: context imbalance in 'main' - wrong count at exit
Sparse相关资料可以参考wiki: Sparse wiki
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。