|
|
EDA365欢迎您登录!
您需要 登录 才可以下载或查看,没有帐号?注册
x
( A: y7 P$ K" i$ g# R5 y6 E+ f; NSparse是内核代码静态分析工具, 能够帮助我们找出代码中的隐患.
# n$ ^) g) x# {$ D4 G: \
" A d7 P4 O% b. [2 ^+ ]9 b
9 l) i. ~7 J; g( P7 `
; C7 l9 v# q$ {主要内容:
) S* A: |7 \8 @3 `- [
9 M* c/ m& L. }+ ~( MSparse 介绍
, K& e! ~) c6 z' OSparse 使用方法
- t: W! F8 i) N. vSparse 在编译内核中的使用
/ x! L% u2 r+ D3 J& `. Z补充
0 ]" d/ N& f6 `$ b
2 l5 i8 Q8 S+ I E9 M9 g4 L/ h4 ]2 A
1. Sparse 介绍$ ?0 m3 D3 q8 d# L: J7 [. r" w
Sparse 诞生于 2004 年, 是由linux之父开发的, 目的就是提供一个静态检查代码的工具, 从而减少linux内核的隐患." E- E7 [- |: `* X8 @5 J9 ]
9 D% T6 Y5 Z3 ?" {! r$ T其实在Sparse之前, 已经有了一个不错的代码静态检查工具("SWAT"), 只不过这个工具不是免费软件, 使用上有一些限制.
( Y* g' T$ I/ `7 ^3 z+ m) i. u4 I! M' y& n8 L) R
所以 linus 还是自己开发了一个静态检查工具.0 D* b# a2 Q p" i7 W, P
1 n0 P+ y+ P' N4 n: t具体可以参考这篇文章(2004年的文章了): Finding kernel problems automatically, P0 F B; Q9 t( A
5 G- O7 B! v9 L& `
0 x9 Q p u5 g" b9 u7 L: k$ V) ]( ^: V. ]# D
Sparse相关的资料非常少, 关于它的使用方法我也是网上查找+自己实验得出来的.
. q/ v3 w" G% a% h2 i& Y
8 ^- a4 x- S$ X# a' ]6 F内核代码中还有一个简略的关于 Sparse的说明文件: Documentation/sparse.txt
1 @5 ^/ Y9 u2 b
) b" A1 _5 u+ s/ o: q- z! j & l* O5 Z7 F% d; J* W& M
, U) r$ c0 g. D; DSparse通过 gcc 的扩展属性 __attribute__ 以及自己定义的 __context__ 来对代码进行静态检查.
& H! R/ P6 p4 c
$ P/ d3 C$ X. l这些属性如下(尽量整理的,可能还有些不全的地方):9 H. L7 d- D' W) M2 R
1 c. k0 [* Z' H# n: i3 F宏名称, X/ I7 B/ B1 w! r( Y( {/ v
2 J- j& @6 `. j9 B6 _: z7 G宏定义: T+ X8 b4 N! s3 v. m
6 F/ \$ |1 F" r) ]: ~2 h8 ^检查点
% i4 t( i6 l! X- `3 C" S. G! F, C- ^3 J. Q
__bitwise __attribute__((bitwise)) 确保变量是相同的位方式(比如 bit-endian, little-endiandeng)
( l) P) n# K9 O& b& ___user __attribute__((noderef, address_space(1))) 指针地址必须在用户地址空间7 h6 H4 G6 H+ M5 x/ K
__kernel __attribute__((noderef, address_space(0))) 指针地址必须在内核地址空间
6 I. H# ^# M% [" Q; M- j__iomem __attribute__((noderef, address_space(2))) 指针地址必须在设备地址空间9 X4 ]$ p' t2 k' t: e& z
__safe __attribute__((safe)) 变量可以为空
7 T+ E4 X9 V6 b# e__force __attribute__((force)) 变量可以进行强制转换
; |; m7 U$ m5 r ?6 f__nocast __attribute__((nocast)) 参数类型与实际参数类型必须一致
- J0 r. ~' x8 r- Z, n- R$ Y__acquires(x) __attribute__((context(x, 0, 1))) 参数x 在执行前引用计数必须是0,执行后,引用计数必须为18 O0 g% k4 ^: H( U
__releases(x) __attribute__((context(x, 1, 0))) 与 __acquires(x) 相反
6 W2 i1 z( u# j6 i2 _8 N2 |4 J__acquire(x) __context__(x, 1) 参数x 的引用计数 + 1/ z( Z- s1 P+ ?& n9 Y
__release(x) __context__(x, -1) 与 __acquire(x) 相反
+ h2 [2 z$ ~0 Y1 D d# R1 o__cond_lock(x,c) ((c) ? ({ __acquire(x); 1; }) : 0) 参数c 不为0时,引用计数 + 1, 并返回1
2 j5 J* {; D9 [其中 __acquires(x) 和 __releases(x), __acquire(x) 和 __release(x) 必须配对使用, 否则 Sparse 会给出警告
# r1 }; A) y; M
) M& C' S2 @8 h& x6 s
# t6 q3 { d; f
* a, Z% @. U% z注: 在Fedora系统中通过 rpm 安装的 sparse 存在一个小bug.
7 C4 u3 k3 P; O8 T n F+ w9 k8 L' T, g5 v. \' P4 i: \
即使用时会报出 error: unable to open ’stddef.h’ 的错误, 最好从自己源码编译安装 sparse.
/ f7 t2 |# W: d+ R4 _
: |0 | B. W4 X- E8 O ]9 z/ ?9 v4 U" t9 h
h& X0 W6 J' k; E
2. Sparse 使用方法3 `+ m- l, {# m& X; b
2.1 __bitwise 的使用$ r0 e' z% j" d
主要作用就是确保内核使用的整数是在同样的位方式下.$ M, ^. k. ]; f1 ^, W
1 [9 H& c7 F+ X; G- Z4 o" f Q: d在内核代码根目录下 grep -r '__bitwise', 会发现内核代码中很多地方都使用了这个宏.
$ z. l7 L6 M# `/ |, m j2 J7 p8 m) m# A. X* S6 ]3 g0 m
对于使用了这个宏的变量, Sparse 会检查这个变量是否一直在同一种位方式(big-endian, little-endian或其他)下被使用,
" ^4 z; n V4 Z6 W# V* ]
, L$ u5 k2 ^) {# ?6 |- o如果此变量在多个位方式下被使用了, Sparse 会给出警告., X3 E/ k7 E$ S; Y1 J- k! C5 \$ r! d
- Y1 W" S# ~( ^9 A# b3 V9 H内核代码中的例子:
0 U$ x4 y! ]5 ?; L g0 N/ `4 x
! E. E& b+ `/ h' G# p2 R/* 内核版本:v2.6.32.61 file:include/sound/core.h 51行 */5 ?! a+ V& T+ S2 k0 G, {6 o( I e
typedef int __bitwise snd_device_type_t;
! J) t/ t5 u: @9 [6 W' B
- K$ T/ F6 a- l- S5 H% A1 h' k9 j0 f3 a
2.2 __user 的使用8 v: C/ q7 V9 b7 q
如果使用了 __user 宏的指针不在用户地址空间初始化, 或者指向内核地址空间, 设备地址空间等等, Sparse会给出警告.& S8 f2 D( k# ?: T8 v
6 I. x( h8 {7 M0 X; X- I$ E1 A' C
内核代码中的例子:
4 b" J+ I7 ~. g+ p$ x, X2 H- C9 j
/* 内核版本:v2.6.32.61 file:arch/score/kernel/signal.c 45行 */$ c+ b6 \3 I; M& ~' i0 G9 B( d3 F
static int setup_sigcontext(struct pt_regs *regs, struct sigcontext __user *sc): c$ a' _& V% s A
3 b7 C9 [9 _4 d/ v8 f( I" k. m f+ d
2.3 __kernel 的使用. w) T8 r% k/ Y9 q" y
如果使用了 __kernel 宏的指针不在内核地址空间初始化, 或者指向用户地址空间, 设备地址空间等等, Sparse会给出警告.
- e, `8 ]" c4 A
3 z& h$ e: ?5 E3 |( f/ ^$ p9 {+ X内核代码中的例子:
! A, | O, I/ T# x4 w8 P. v( }
/* 内核版本:v2.6.32.61 file:arch/s390/lib/uaccess_pt.c 180行 */
- `, F7 [3 G, M& g& P. LmEMCpy(to, (void __kernel __force *) from, n);2 B4 {, ?1 X7 J) a
5 S1 |+ j9 ~' J$ i, s
, I. K( m4 |# }2.4 __iomem 的使用! O5 ]( |, g: ^! V+ X
如果使用了 __iomem 宏的指针不在设备地址空间初始化, 或者指向用户地址空间, 内核地址空间等等, Sparse会给出警告.5 P1 w$ U" }! g% y% l. ?+ B
% C' b" I0 {4 Y3 r! _内核代码中的例子:
+ F+ h% F5 W! `! n0 Y1 N: H* u. v% E, _) g' @
/* 内核版本:v2.6.32.61 file:arch/microblaze/include/asm/io.h 22行 */
+ S$ o5 D: D8 P9 e. o* zstatic inline unsigned char __raw_readb(const volatile void __iomem *addr)
+ p% s3 @' j& b1 V5 I* _! g 9 v3 \3 I; A+ i1 k% c
) ]2 U& l$ U3 @4 e9 L
2.5 __safe 的使用
" u2 {& Y% F4 Z" T' f使用了 __safe修饰的变量在使用前没有判断它是否为空(null), Sparse会给出警告.% q7 f% Z* @7 p
+ R( @" ]1 V1 q$ _' H我参考的内核版本(v2.6.32.61) 中的所有内核代码都没有使用 __safe, 估计可能是由于随着gcc版本的更新,
( g% F7 O$ C, b% |2 R1 _
! k$ |9 T* i4 A7 xgcc已经会对这种情况给出警告, 所以没有必要用Sparse去检查了.
: I$ y5 g) [4 U) U9 h( C# I- [+ ]- g1 a' ]& f) E
2 Y8 b. K: H7 l4 _9 P& T0 Y6 Z
: `0 E9 K6 s" t( R
2.6 __force 的使用/ z0 q! Y9 b% G1 k8 J
使用了__force修饰的变量可以进行强制类型转换, 没有使用 __force修饰的变量进行强制类型转换时, Sparse会给出警告.
- u9 O8 r# R2 I3 H1 s- n% E: c( E7 Z0 p! @
内核代码中的例子:
: F y; i" F: z
& D' r9 G$ M& c2 |/ |/* 内核版本:v2.6.32.61 file:arch/s390/lib/uaccess_pt.c 180行 */
0 w( {5 z) O5 o dmemcpy(to, (void __kernel __force *) from, n);
9 |7 K2 Z6 Y9 C ( ~, P* N: D! g
& l' f+ r/ ]+ e; |0 b6 p$ \
2.7 __nocast 的使用
5 M, ~) u( i9 b使用了__nocast修饰的参数的类型必须和实际传入的参数类型一致才行,否则Sparse会给出警告.* r2 y# q3 s1 t+ s
0 R8 Z% g5 a' u' e+ w3 l内核代码中的例子:9 z8 Z+ Y, B, \! j. V
7 x% n) c) S- ~ q; g- b
/* 内核版本:v2.6.32.61 file:fs/xfs/support/ktrace.c 55行 */0 i, V, w8 o. z% T' d$ U' V
ktrace_alloc(int nentries, unsigned int __nocast sleep)
$ r! X+ O& W$ j3 P$ [2 l8 A
1 @1 j- T6 _# m$ Q) G ~7 u5 }5 Q( M" y; i
2.8 __acquires __releases __acquire __release的使用: t* k. `0 a% A/ _1 A2 k4 Q
这4个宏都是和锁有关的, __acquires 和 __releases 必须成对使用, __acquire 和 __release 必须成对使用, 否则Sparse会给出警告.3 g" N' W: [8 P: t- ~: r6 L
' c" }! k; g4 q" }5 L" M 8 F0 u5 E1 k- ^! _& }9 Q) k# Q4 a9 r
2 y! y2 v4 n" u7 k z+ K' N4 C8 y2.9 __cond_lock 的使用
7 a# S1 [% ^ ^这个宏有点特别, 因为没有 __cond_unlock 之类的宏和它对应.$ E% [' ~& D+ {) E
; _' N: G# R" \; ^5 v& |之所以有这个宏的原因可以参见: http://yarchive.net/comp/linux/sparse.html 最后一段.
7 O: r* }! s" Q4 g- N, q- P+ Z! y/ X5 s3 W
这个宏的来源清楚了, 但是为什么这个宏里面还要调用一次 __acquire(x)? 我也不是很清楚, 在网上找了好久也没找到, 谁能指教的话非常感谢!!!
8 `& o8 f3 l3 g1 Y. B( U
: }6 Q, _3 A9 r* c# b0 @# v( }0 r5 _
1 |0 Y- |& x; U- Q6 u2 R
6 b0 W% C1 e( c" f; o1 [, ]; M3. Sparse 在编译内核中的使用
; v/ R' I9 d' s7 V用 Sparse 对内核进行静态分析非常简单.
/ M: A3 v: o+ H6 P7 J2 q& }4 C/ k
# 检查所有内核代码
+ D- i; M. u9 S: Y8 u8 A1 Nmake C=1 检查所有重新编译的代码
8 P( w F6 k' V6 Ymake C=2 检查所有代码, 不管是不是被重新编译) v7 B. t V) ?- ^. I! G
# ?! @7 T. v' x, T) t) d
+ T' V+ U6 i8 l( V& c' o" l" h8 k
4. 补充
: E2 M0 n" C, m9 c. I QSparse除了能够用在内核代码的静态分析上, 其实也可以用在一般的C语言程序中.9 I9 g! M1 w6 R( X' S: T6 v
2 ~ t+ a: D( C8 [4 x& W5 I比如下面的小例子:
, S7 W. e. E" M, n( {/ ?. f0 A P, q
复制代码* _" f; L2 ~) r: v6 y
/******************************************************************************+ r7 m- ? R- h& f' `
* @File : sparse_test.c( w( \9 a h4 `# a
* @author : wangyubin
4 \2 {) e( q5 U: S [ * @date : Fri Feb 28 16:33:34 2014
1 N3 T* X. f: g$ E *
8 {, a$ U* ], G2 x' r * @brief : 测试 sparse 的各个检查点! ]9 J) H4 S0 B" q
* history : init8 R9 _: q; X& g G
******************************************************************************/
; p! A' m2 p p Q" ~/ b* N
* |( L5 q4 S* |5 Q5 Y! m* c#include <stdio.h>
) B( c+ Y3 u3 [) X& Y
% r8 U* ]; T8 f/ K, K# |* b#define __acquire(x) __context__(x,1)) z. O2 d. s) ~) c4 W* O2 r
#define __release(x) __context__(x,-1)
+ s1 G1 Q+ f$ N& p
+ N1 G1 X9 N$ V5 ^int main(int argc, char *argv[])6 k4 K+ T% u( r+ s8 k! R! h
{
" i6 u6 Y5 h2 ]2 p2 ] int lock = 1;
I# P( y8 L/ c Q! n" o3 G; _; ~ __acquire(lock);
$ @; ?) a6 v/ ]9 z* | /* TODO something */
4 ~* v$ J4 T% x+ y4 [7 F1 o! o. V) \ __release(lock); /* 注释掉这一句 sparse 就会报错 */2 {' u3 r8 h) \4 k
return 0;" ]7 | H: a% O; R; G; D
}. e( e( r- x' c
复制代码: j& {' z$ z1 |- p
+ }$ i; E' n: y$ c6 ^
0 `5 c& x6 D1 t5 O8 z0 H( k+ C如果安装了 Sparse, 执行静态检查的命令如下:$ `) W) P) W3 Y: I `: Z7 x
+ |& e9 V" d7 d0 U1 x5 Z$ sparse -a sparse_test.c / u1 o' |: x4 i+ m( q
sparse_test.c:15:5: warning: context imbalance in 'main' - wrong count at exit8 D" a( y6 |3 M6 L# |4 ?) _8 j
|
|