EDA365电子论坛网

标题: 内核工具 – Sparse 简介 [打印本页]

作者: dapmood    时间: 2020-9-15 16:07
标题: 内核工具 – Sparse 简介

# M7 k6 k  ?$ J9 |& USparse是内核代码静态分析工具, 能够帮助我们找出代码中的隐患.5 A# {' {- j1 T/ g3 A" P' L4 i
: _% Y7 U; w+ ~4 r& t" ^; m

% c9 Y! s. Q6 H3 ]
0 Q6 a- X; @) C; n主要内容:
' \0 v% A3 _! H, o1 u5 N+ h8 ^! f/ p0 h9 R4 w1 l7 U/ B7 g: t
Sparse 介绍4 W3 R! [+ F' e& Y. g
Sparse 使用方法& d" H7 H+ a5 }0 R! m1 k$ q  o% B" `
Sparse 在编译内核中的使用
- T2 d( e4 e% Q  k补充
9 m0 @5 s" X- k- x( d! n+ z  g # V: h, M2 a( e$ t; u
7 [+ a) ^9 ?4 f5 a
1. Sparse 介绍! `$ Q3 b7 R3 P1 ~7 C- |9 s
Sparse 诞生于 2004 年, 是由linux之父开发的, 目的就是提供一个静态检查代码的工具, 从而减少linux内核的隐患.
) \+ q4 x: {" u* Q1 L1 J3 j) G
" k, [! p+ g6 ^8 W% Z其实在Sparse之前, 已经有了一个不错的代码静态检查工具("SWAT"), 只不过这个工具不是免费软件, 使用上有一些限制.
# \* ^0 x8 v; |  K7 Z2 v" \6 N3 a6 \. r* j2 ]. `) Q
所以 linus 还是自己开发了一个静态检查工具.
$ b! X$ P" l+ S# m7 d- G+ i- D, B( W
具体可以参考这篇文章(2004年的文章了): Finding kernel problems automatically+ M0 I# l0 P4 E7 q9 {
6 g  G* H" h, C' f

( X; @% H- b% M1 w! ~, E
( a9 L: {' H; C7 u: Z: s  K( oSparse相关的资料非常少, 关于它的使用方法我也是网上查找+自己实验得出来的.
. S/ y, `  Z- F& g5 h4 U
5 \! j/ D0 u1 {. y) v+ i内核代码中还有一个简略的关于 Sparse的说明文件: Documentation/sparse.txt5 F# C! b- P& K3 C/ F# f

' Z$ d8 q- n9 K% r5 ?+ M. I6 J
; V4 c: a4 s: T5 p/ T$ H0 K
6 J1 ]/ `8 U9 _/ Z/ ?" [) q1 e3 I2 G8 q/ zSparse通过 gcc 的扩展属性 __attribute__ 以及自己定义的 __context__ 来对代码进行静态检查.  a  M" d, F3 B2 Y9 W

5 m4 ]. h5 r' g4 a$ X0 Y0 H: w这些属性如下(尽量整理的,可能还有些不全的地方):
1 J2 c7 R. Y- ~
6 P+ t& Z1 h, t5 s; n4 i宏名称6 N# H" \( k' S
3 i! B4 [; ?" K0 a
宏定义! R7 ^% R8 X0 A' y$ q& C

3 H4 `$ W# e0 s, n8 l( k" n1 J. b5 l检查点
* p5 g: S0 {+ a  n# M7 ]8 t" |) B- ]  E
__bitwise        __attribute__((bitwise))        确保变量是相同的位方式(比如 bit-endian, little-endiandeng)% B9 p& F4 @. V0 M$ |3 y+ k% I
__user        __attribute__((noderef, address_space(1)))        指针地址必须在用户地址空间: s8 [/ L) g/ T. N
__kernel        __attribute__((noderef, address_space(0)))        指针地址必须在内核地址空间
  w6 p* C' q3 ]( N5 w__iomem        __attribute__((noderef, address_space(2)))        指针地址必须在设备地址空间% [% [1 Z& Y6 `9 p9 [: |
__safe        __attribute__((safe))        变量可以为空
" w7 y# ]: g6 n+ q__force        __attribute__((force))        变量可以进行强制转换
8 U1 q: e: `' A; w  Z/ S8 v( k__nocast        __attribute__((nocast))        参数类型与实际参数类型必须一致9 N! h1 b! c4 \6 q8 W9 `
__acquires(x)        __attribute__((context(x, 0, 1)))        参数x 在执行前引用计数必须是0,执行后,引用计数必须为1
4 n& C" }8 `/ f" _; M3 o5 E__releases(x)        __attribute__((context(x, 1, 0)))        与 __acquires(x) 相反
$ E9 I) |# f6 [: j1 I__acquire(x)        __context__(x, 1)        参数x 的引用计数 + 17 U$ z) Z' c' U9 z" j1 I: {
__release(x)        __context__(x, -1)        与 __acquire(x) 相反
4 w$ s; u7 t: x) r7 @6 y__cond_lock(x,c)        ((c) ? ({ __acquire(x); 1; }) : 0)        参数c 不为0时,引用计数 + 1, 并返回1
- J2 b1 E4 l+ g: m' Y) b其中 __acquires(x) 和 __releases(x), __acquire(x) 和 __release(x) 必须配对使用, 否则 Sparse 会给出警告+ V1 }6 n) f9 |1 e

7 U. b6 y  `# Q4 D  {5 c1 ^
3 h5 d, d2 u8 ]% l4 l; r" Y6 P' B" I1 \
注: 在Fedora系统中通过 rpm 安装的 sparse 存在一个小bug.3 N* G0 k" q! `* @$ M+ v+ q1 ]
( E6 ?0 {, W6 s( p5 b
即使用时会报出 error: unable to open ’stddef.h’ 的错误, 最好从自己源码编译安装 sparse.
! L5 E4 y% K# p7 P; T. W4 w$ k( A
# J4 f) z! R' P  R) f+ S5 F3 a 5 M  _) W& N7 F' K3 X/ U, I
/ V* u9 z6 k; h% Q' _, _+ k7 z3 C
2. Sparse 使用方法
  a, _/ C4 G3 Z4 ?, }1 X% k2.1 __bitwise 的使用
' ?/ i: v( b2 H% E) r, Q" p2 s  t' q主要作用就是确保内核使用的整数是在同样的位方式下.$ ]3 N9 t% [. s

# V& N, l. D4 h' q( O在内核代码根目录下 grep -r '__bitwise', 会发现内核代码中很多地方都使用了这个宏.; l* M6 h7 e' S" R- W- n# c

! H0 o4 i' w. ^0 w$ z* E) Q5 A( O对于使用了这个宏的变量, Sparse 会检查这个变量是否一直在同一种位方式(big-endian, little-endian或其他)下被使用,
! h0 B' Z1 [# U' I9 w/ V' R4 _+ `  s4 B
如果此变量在多个位方式下被使用了, Sparse 会给出警告.2 w( E7 [' s* D" X4 n5 v7 B) W
7 ~1 {$ P4 c. @' _/ O; {
内核代码中的例子:
) A  M& [7 K% {3 l. \
+ b# E/ p9 n8 I. |- ^) D/* 内核版本:v2.6.32.61  file:include/sound/core.h 51行 */: ]$ `# \. |% j9 l; [
typedef int __bitwise snd_device_type_t;
9 c0 w6 h# \6 z+ z2 Y . W& M+ h  f# l- }* f7 n/ \
6 o2 o% I: B: K. C1 E
2.2 __user 的使用
" L4 |1 c2 r0 S; x/ e如果使用了 __user 宏的指针不在用户地址空间初始化, 或者指向内核地址空间, 设备地址空间等等, Sparse会给出警告." F7 t% z; O* a4 [* P, `

$ p; r$ t8 \% I, G1 ]# l内核代码中的例子:
8 s; e& c6 |8 L$ o
, s& o3 Q  r& z  Q- c% d; I8 R" s: s/* 内核版本:v2.6.32.61  file:arch/score/kernel/signal.c 45行 */
/ L8 O* `5 \. {3 x& F) ~; Tstatic int setup_sigcontext(struct pt_regs *regs, struct sigcontext __user *sc)
+ N* c/ [4 `2 H5 e! f! h
2 }& {# A; o2 L! P  g0 Z5 t: D: j9 {: x- Y. ^
2.3 __kernel 的使用
1 G# K* K2 D  z' r* {如果使用了 __kernel 宏的指针不在内核地址空间初始化, 或者指向用户地址空间, 设备地址空间等等, Sparse会给出警告.  b- z  ?' w$ n% N6 I* Y& g
1 ]% z% w" D. a- }* C7 S( P, V
内核代码中的例子:
! C, \% |! T* G" g0 i5 ^7 V: j  ]+ v3 A1 n8 `, e* `
/* 内核版本:v2.6.32.61  file:arch/s390/lib/uaccess_pt.c 180行 */6 F1 Y1 @- C4 F# J
memcpy(to, (void __kernel __force *) from, n);- _) B9 X( }7 q$ s- Y9 L9 Z
; P1 v6 v% g" u
. L; `( B& V9 _" b/ n2 i' _4 b
2.4 __iomem 的使用
, I/ {9 U! q# Z9 e6 A% `5 p: s如果使用了 __iomem 宏的指针不在设备地址空间初始化, 或者指向用户地址空间, 内核地址空间等等, Sparse会给出警告.9 ?! w& a9 |5 j

' a8 k8 R( y! a, A0 D* @内核代码中的例子:- J7 |' O6 y3 v
% J& K9 g, U! \
/* 内核版本:v2.6.32.61  file:arch/microblaze/include/asm/io.h 22行 */8 _+ }7 I3 m& L7 Y, B4 B, L7 g2 [) v
static inline unsigned char __raw_readb(const volatile void __iomem *addr)3 f5 c0 [3 V9 |! u0 Z
$ H% H- f( k" ]0 Z) F$ d

' G4 j3 n8 l- d6 g6 x. N8 J( y2.5 __safe 的使用4 ?! V) }; ?+ Y  ]% h) p
使用了 __safe修饰的变量在使用前没有判断它是否为空(null), Sparse会给出警告.
$ i4 O% J+ W6 h$ X9 @( W6 k
% Y% x8 X( P9 P' A我参考的内核版本(v2.6.32.61) 中的所有内核代码都没有使用 __safe, 估计可能是由于随着gcc版本的更新,
" h8 l8 U. e' \) H' `/ F5 O+ B6 b$ W
. h2 S5 Q3 d) a3 I8 @% `$ @4 i- i3 Xgcc已经会对这种情况给出警告, 所以没有必要用Sparse去检查了.% g* N7 C1 d' Z

: J% |- S; E; R% ~1 N7 p- o8 \# l
* R5 T" s7 @9 R/ u" d, v, y: j8 K; Q+ j6 I6 M+ c
2.6 __force 的使用# w3 q. C0 d8 x  e7 Q0 D/ X( v1 M8 h: c
使用了__force修饰的变量可以进行强制类型转换, 没有使用 __force修饰的变量进行强制类型转换时, Sparse会给出警告.; f+ T. @' o5 p& W. d

: w$ K/ A+ {% }9 K内核代码中的例子:
& a5 T6 k  |$ m/ [2 h  X% u4 W' X, j
/* 内核版本:v2.6.32.61  file:arch/s390/lib/uaccess_pt.c 180行 */
8 H! D* ?* ^$ S: ?0 T& S/ A' lmemcpy(to, (void __kernel __force *) from, n);
* H$ `! o4 @2 F' f  l9 J + K) l  O3 r5 @. e
0 U/ j4 u. {5 a  h5 T
2.7 __nocast 的使用4 l: e/ k% o( D* A% [, S, N) h
使用了__nocast修饰的参数的类型必须和实际传入的参数类型一致才行,否则Sparse会给出警告.
) C# K9 I4 P& o& k& H% _" I0 y
% a: a5 u  s6 d内核代码中的例子:
; n' S$ m5 `8 `$ U, o; s& U3 Z, f( N  T
/* 内核版本:v2.6.32.61  file:fs/xfs/support/ktrace.c 55行 */
& R* k+ z3 U" _; p9 q2 l$ r( ]ktrace_alloc(int nentries, unsigned int __nocast sleep)
  v5 H7 U& \" s) F" B
$ d# ~( a& O# I! J3 A8 _! ~
4 D0 b1 Z2 G3 e. H2.8 __acquires __releases __acquire __release的使用
* s: D' o! d4 v6 h8 f2 ]5 S/ t这4个宏都是和锁有关的, __acquires 和 __releases 必须成对使用, __acquire 和 __release 必须成对使用, 否则Sparse会给出警告.! e" o9 C* e7 T0 F! W( e& |1 j
2 D5 a; p' Q- s) K( w

* k- T: {$ k+ w  X
6 ?# i4 r$ {8 L# T2.9 __cond_lock 的使用6 }0 @0 s/ N, c$ K1 z. A* ]  t
这个宏有点特别, 因为没有 __cond_unlock 之类的宏和它对应.  T& P+ @" g- t2 w4 Z6 J
, S! e4 [5 w' ]: R# n3 e, m% Q* H
之所以有这个宏的原因可以参见: http://yarchive.net/comp/linux/sparse.html 最后一段.6 h% s! a  n- J( a+ ^3 m0 h3 N2 k2 K
. {' p9 h6 Z3 Y% [, q. t$ X
这个宏的来源清楚了, 但是为什么这个宏里面还要调用一次 __acquire(x)? 我也不是很清楚, 在网上找了好久也没找到, 谁能指教的话非常感谢!!!
5 q# S. J5 {7 e( n: q9 F1 N! I; Q5 [" @$ U9 Y
; p% Y6 \/ ]" }# ]! k" R

  k3 b, H7 R" _, Z3. Sparse 在编译内核中的使用" b4 u; {" y9 W/ _7 _) P5 n1 z
用 Sparse 对内核进行静态分析非常简单.1 J5 e3 P4 G  Z

) o% Y" O+ k/ o, I# 检查所有内核代码% r9 R! k3 U5 q% n) h) j* L4 g: W/ D
make C=1 检查所有重新编译的代码
3 Z6 W, V6 V. W: G0 L# |make C=2 检查所有代码, 不管是不是被重新编译
2 ~% h* S, e- w( c* ^
  E9 `6 c, Q/ \1 t. l  _, c$ l, y6 R# E5 D' b6 c$ K
4. 补充7 Q; |2 C' L5 ?  o% I  c# Y+ @
Sparse除了能够用在内核代码的静态分析上, 其实也可以用在一般的C语言程序中.% s! Q# i$ y  L( j. v
+ `% t, a: O! t- ]
比如下面的小例子:( F8 B' l# E0 h6 g( E) X4 S* M
0 h! Z1 x1 ~% u0 z: N7 U
复制代码
4 ?" D* G4 O* Y* g/******************************************************************************+ T* F$ u+ X! c5 d
* @File    : sparse_test.c9 U& ?) b5 ~: ?' c
* @author  : wangyubin! [* r. Z3 m& a$ `1 G) p
* @date    : Fri Feb 28 16:33:34 2014
  a0 n( `/ k7 ^# S* Y3 c, _ * - H# r0 c) Z( O6 K# L& e
* @brief   : 测试 sparse 的各个检查点
" s4 @0 x* y$ h * history  : init. t5 |4 K8 q" {5 c4 s! V* s. h  c
******************************************************************************/# K, Z& L: h9 l) m% A
* @3 I% B7 ?; |1 c* n" C3 S
#include <stdio.h>' I  D( m7 x/ E4 d
' r( L* u5 Y- |# A7 s! f
#define __acquire(x) __context__(x,1)* p9 L7 Z, A( I1 E, @. D
#define __release(x) __context__(x,-1)
; N1 |6 F' U3 J
# u; b" e5 G' w! H; Rint main(int argc, char *argv[])2 E" T9 }& ~, D8 O6 d
{4 m4 q- H# G9 i, i
    int lock = 1;; _: z1 u5 G9 Y9 y& x8 Q% ?( z
    __acquire(lock);6 H6 a  x" S7 u  q
    /* TODO something */3 @+ t, d1 A2 V$ F1 c$ W
    __release(lock);            /* 注释掉这一句 sparse 就会报错 */9 n1 c5 y  w7 T
    return 0;
2 f1 T. k. `1 S$ O, b4 Q}$ k8 ?, |3 ?/ U3 S* ?% L* _4 p/ l
复制代码
) I' Q* y9 m! W8 p8 J7 @2 ^! L: z' ^   i$ u' ^% H/ K! j- `" O
4 H/ O  Y3 R4 z4 W- |
如果安装了 Sparse, 执行静态检查的命令如下:( T, k  h$ Z3 b' @- b9 U# H

. Q9 j- E# @3 q+ x( t8 r$ sparse -a sparse_test.c   I7 p6 Y- l7 ^* `/ y6 b
sparse_test.c:15:5: warning: context imbalance in 'main' - wrong count at exit8 h. ?/ j& j8 `3 t# j  ]3 Y" C  e

作者: NNNei256    时间: 2020-9-15 17:20
内核工具 – Sparse 简介




欢迎光临 EDA365电子论坛网 (https://bbs.eda365.com/) Powered by Discuz! X3.2