找回密码
 注册
关于网站域名变更的通知
查看: 303|回复: 1
打印 上一主题 下一主题

内核工具 – Sparse 简介

[复制链接]

该用户从未签到

跳转到指定楼层
1#
发表于 2020-9-15 16:07 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式

EDA365欢迎您登录!

您需要 登录 才可以下载或查看,没有帐号?注册

x
2 b9 S" |* K) |9 K
Sparse是内核代码静态分析工具, 能够帮助我们找出代码中的隐患.
2 y  c; ~" d: a1 _( V' p3 j+ q" S
' E" u$ X6 l+ }  d2 G

6 J& s; X, {8 i- z主要内容:
. a& m5 H2 ~/ O+ x' p7 }: Q
1 u! W9 B8 x2 n. z" KSparse 介绍' @& M' R( ?  m5 @6 }, U
Sparse 使用方法
% j/ ?8 C* d  r) lSparse 在编译内核中的使用
" k3 O$ Y) b$ h" q2 Z" O补充0 `; ?1 u6 e, L! O" t6 a6 o

+ v2 g" c) n" U5 K4 X( h, _
: \* b% y) S: ~/ [+ Q# L1. Sparse 介绍
% a1 `* l$ S$ P/ f* ^  ^Sparse 诞生于 2004 年, 是由linux之父开发的, 目的就是提供一个静态检查代码的工具, 从而减少linux内核的隐患.! \( R  G8 a2 E- R' E* _, s

: I9 M' k$ i0 C% V# R3 O其实在Sparse之前, 已经有了一个不错的代码静态检查工具("SWAT"), 只不过这个工具不是免费软件, 使用上有一些限制.1 A1 L' W3 G' O8 d1 D' |* M; r* ^
' V# L- f0 [. c$ Q, Q" l6 I
所以 linus 还是自己开发了一个静态检查工具.. z' Y1 E: M9 v# u
7 G1 x( w0 X* @0 b  s* ]" u
具体可以参考这篇文章(2004年的文章了): Finding kernel problems automatically- O* |' ?* h# v6 d; m# V( ]

4 }" h3 H9 o# r  s' o/ G* s
# K% i" M+ ^0 z, ?+ D3 E
& c4 f% x# L0 C* n. b' F! ~- TSparse相关的资料非常少, 关于它的使用方法我也是网上查找+自己实验得出来的.$ O% i! U  M! ~! M! R9 ]5 h

9 P4 K: u5 X; r5 f/ k, p4 t$ @内核代码中还有一个简略的关于 Sparse的说明文件: Documentation/sparse.txt$ `; N5 z  {1 n" @& P
1 T7 k8 I4 k. n5 r
( u9 x  P6 O6 t7 n0 |

2 j6 O4 U9 ?( J( |9 R/ ASparse通过 gcc 的扩展属性 __attribute__ 以及自己定义的 __context__ 来对代码进行静态检查.
8 B! a, v0 \* @: O! h1 x( H2 g7 i; m) J/ J" G% V
这些属性如下(尽量整理的,可能还有些不全的地方):  K# x) J% c: Y+ l! w
$ v, d8 Y* e5 k; c2 J
宏名称
; J4 v" y% K! n
- u. u$ t: m$ v1 l1 H* \& Y* Z+ j宏定义
$ ^6 y* x2 k4 t7 p* X1 l5 U1 B2 q9 F: u) j" j
检查点& F' N% h4 y0 k0 B6 C/ j

: v3 Z& V+ D" m, y% O__bitwise        __attribute__((bitwise))        确保变量是相同的位方式(比如 bit-endian, little-endiandeng)
3 T4 A$ f6 D4 e" k! S/ n! A( h__user        __attribute__((noderef, address_space(1)))        指针地址必须在用户地址空间
% T+ n7 B" L: X5 w+ L* U5 }__kernel        __attribute__((noderef, address_space(0)))        指针地址必须在内核地址空间( Q7 v. [5 b$ o" [5 V3 l
__iomem        __attribute__((noderef, address_space(2)))        指针地址必须在设备地址空间5 Q% `# r6 R2 u/ U
__safe        __attribute__((safe))        变量可以为空
' e! G. S% H  I4 ]! z3 e__force        __attribute__((force))        变量可以进行强制转换
) i. h# p( N* l$ w- R__nocast        __attribute__((nocast))        参数类型与实际参数类型必须一致
+ o* z+ P- ]" U1 |) u__acquires(x)        __attribute__((context(x, 0, 1)))        参数x 在执行前引用计数必须是0,执行后,引用计数必须为1
( u* B! m. w; J: Q! B4 S__releases(x)        __attribute__((context(x, 1, 0)))        与 __acquires(x) 相反( r+ Q5 |. R  k. }( e7 N7 O
__acquire(x)        __context__(x, 1)        参数x 的引用计数 + 1" b$ ]  T/ k/ \; x1 n# z; [
__release(x)        __context__(x, -1)        与 __acquire(x) 相反) j, }0 \9 c9 K5 _% B5 ]
__cond_lock(x,c)        ((c) ? ({ __acquire(x); 1; }) : 0)        参数c 不为0时,引用计数 + 1, 并返回16 {8 G( _( a; F+ u
其中 __acquires(x) 和 __releases(x), __acquire(x) 和 __release(x) 必须配对使用, 否则 Sparse 会给出警告( x4 d5 S- e: w
' h, W0 `/ E' _4 ^+ r& A

1 T# f: ]" I- ?3 T9 Q/ J' ]9 n& R' p/ @/ ^5 B8 `% l; F
注: 在Fedora系统中通过 rpm 安装的 sparse 存在一个小bug.
4 B" l# [8 M0 d0 w% D! k/ h3 B7 w! m# S
即使用时会报出 error: unable to open ’stddef.h’ 的错误, 最好从自己源码编译安装 sparse.6 q$ p. c+ c' E
: t8 K" P# @: s$ p* Z
+ h1 T; S0 M( \, y+ ^. D, c

3 {0 \% U. T- f4 S. b0 T0 \2. Sparse 使用方法
+ |* c# t' X8 }+ C6 O2.1 __bitwise 的使用
. M! Z% ?6 Z$ H0 {4 b1 M* g主要作用就是确保内核使用的整数是在同样的位方式下.7 F4 z- s4 o! G% T
& [5 A6 L9 M/ H) l+ p$ a
在内核代码根目录下 grep -r '__bitwise', 会发现内核代码中很多地方都使用了这个宏.' o% c, y) c0 V0 n0 ?( Z
& ^5 t6 l' K8 I3 r7 G1 r  i
对于使用了这个宏的变量, Sparse 会检查这个变量是否一直在同一种位方式(big-endian, little-endian或其他)下被使用,9 `. ^% g) v) x# [

; w9 v) Y% v. L/ A% `' K6 c$ b如果此变量在多个位方式下被使用了, Sparse 会给出警告.$ i% K. L# t0 k: C( v4 \: e
' \6 d6 x  S/ E/ }* k
内核代码中的例子:
: @8 m9 P$ v& d) P8 _( ^
2 N& r/ ~% H: ^3 N2 ]: K( D- y/* 内核版本:v2.6.32.61  file:include/sound/core.h 51行 */* F3 C1 T$ c8 h2 J4 ^! m& s
typedef int __bitwise snd_device_type_t;
$ h/ K) K2 J+ w3 D: r, s
; A, s0 K- p* M8 D% d+ T4 o1 s* ^, l+ y* b) K- O4 R- [
2.2 __user 的使用
2 u9 a7 t' f  X7 G如果使用了 __user 宏的指针不在用户地址空间初始化, 或者指向内核地址空间, 设备地址空间等等, Sparse会给出警告.4 X7 m+ }. _/ k( b9 ]

; N0 ~2 K- `5 Y4 ^内核代码中的例子:
! q3 Y# W1 W- G8 P4 c" [4 L
- Q4 ^* K# h1 \4 ^% s( f1 ~5 N/* 内核版本:v2.6.32.61  file:arch/score/kernel/signal.c 45行 */
$ @; @* z: g5 N& h: i9 Cstatic int setup_sigcontext(struct pt_regs *regs, struct sigcontext __user *sc)
  M# x% R! b1 n5 J! _; p# R
- M9 t% O! u, L0 |, Z! u
: R1 p  c7 r. q2.3 __kernel 的使用
9 X0 ^& u1 d+ |4 a2 q/ I5 b如果使用了 __kernel 宏的指针不在内核地址空间初始化, 或者指向用户地址空间, 设备地址空间等等, Sparse会给出警告.& i( H7 G+ w# f8 ^0 ?) O4 D

; {" i" L! t" `# H( k6 q3 S1 Z  n内核代码中的例子:+ [- _! R3 a3 T$ o0 b2 k

3 Q* D" e* u( P( B  y! r3 L/* 内核版本:v2.6.32.61  file:arch/s390/lib/uaccess_pt.c 180行 */5 n( M; T1 j. V. J
mEMCpy(to, (void __kernel __force *) from, n);0 b5 O5 p3 ?4 z( h5 u
  k$ r$ m8 j1 A4 i+ c" \
; z" d" I9 x+ o5 d! T. B& w. z& p
2.4 __iomem 的使用
! S- e( ^% ]) V如果使用了 __iomem 宏的指针不在设备地址空间初始化, 或者指向用户地址空间, 内核地址空间等等, Sparse会给出警告.. G7 Z0 L' k" q7 s. q$ M5 p) u

2 v/ q& \/ A7 ~# x- P  P内核代码中的例子:: X0 H5 h7 c3 t: X& h* T$ t

/ B: _! \$ S# s/* 内核版本:v2.6.32.61  file:arch/microblaze/include/asm/io.h 22行 */
- X2 z9 g, w- astatic inline unsigned char __raw_readb(const volatile void __iomem *addr)
1 ]' b# x$ k# s" M3 S# z ! H$ b# f3 Q+ E* C" t  l

6 a1 o1 u9 d. a9 W9 `) t: F2.5 __safe 的使用
( Y+ b9 _# }: f5 z使用了 __safe修饰的变量在使用前没有判断它是否为空(null), Sparse会给出警告.
% L0 J( \$ K- \, }1 ~. R  J( E# ]
% r+ Z0 j5 k! x' L我参考的内核版本(v2.6.32.61) 中的所有内核代码都没有使用 __safe, 估计可能是由于随着gcc版本的更新,0 [) n3 {" \4 ]/ J8 K$ {, B( x
% @6 Y; j/ M/ g/ h6 l8 V
gcc已经会对这种情况给出警告, 所以没有必要用Sparse去检查了.! y; J+ I: \# e7 Q

8 O$ |4 G! F$ N! s$ X ' ]+ J$ U0 q  Y
9 [/ O  n$ G7 p5 A( T
2.6 __force 的使用
. Q) N& D& E- E使用了__force修饰的变量可以进行强制类型转换, 没有使用 __force修饰的变量进行强制类型转换时, Sparse会给出警告./ S5 G3 Q$ U' ]& |

- A- g7 g6 `8 x& v1 C内核代码中的例子:0 {! N3 A( u' K! K8 E

, z5 |  P( h. E+ p/ Z7 K/* 内核版本:v2.6.32.61  file:arch/s390/lib/uaccess_pt.c 180行 */# w( `* a( V4 p4 \+ M
memcpy(to, (void __kernel __force *) from, n);3 q% v/ O: v& K3 }+ a. ]

! e- p; U1 ]  d( _8 F/ @* s1 X; G8 c7 [, _
2.7 __nocast 的使用+ i/ c/ |2 Y* e6 q- U
使用了__nocast修饰的参数的类型必须和实际传入的参数类型一致才行,否则Sparse会给出警告.
1 V) d5 i; {# W/ _; X0 O: C" L. U" W: ?: G* R3 i/ Q$ m# @! K6 L
内核代码中的例子:
) v+ V# z. t2 h$ c: q& r/ C
: a& F- L' Y' {: M5 O/* 内核版本:v2.6.32.61  file:fs/xfs/support/ktrace.c 55行 */
; `. j" T, t$ X% U3 n/ Uktrace_alloc(int nentries, unsigned int __nocast sleep)
; i/ @8 ]+ |8 x( l* H
& y8 K0 S  A3 `7 t9 R  t0 S4 O- _0 Y- r% O4 X( T7 H4 h
2.8 __acquires __releases __acquire __release的使用. B: \* H$ n) l* z9 D4 o
这4个宏都是和锁有关的, __acquires 和 __releases 必须成对使用, __acquire 和 __release 必须成对使用, 否则Sparse会给出警告.
- {7 J0 B# w2 N2 O5 w. Q# P
: k4 w9 c7 F( ?' @ 6 D+ Y- X: F& V
4 Q3 ?) X2 Q: W' V$ O
2.9 __cond_lock 的使用$ }5 y8 l- h) s
这个宏有点特别, 因为没有 __cond_unlock 之类的宏和它对应.4 x' J8 {( a) q2 Y1 q7 }9 a: [+ d
- J5 u4 @! w5 a$ i* M
之所以有这个宏的原因可以参见: http://yarchive.net/comp/linux/sparse.html 最后一段.' j" G# Z( X/ r; {& e1 q

# i  k1 E: ~; x  a这个宏的来源清楚了, 但是为什么这个宏里面还要调用一次 __acquire(x)? 我也不是很清楚, 在网上找了好久也没找到, 谁能指教的话非常感谢!!!
1 Y( M8 {8 H  ]  v( Z! T2 b1 ~4 L5 M8 l; e, n( P# d
+ s# ]. s4 [( K9 J; ?! L4 {% W. J" j3 {
) o0 `3 D* Q. v  p
3. Sparse 在编译内核中的使用- l& A5 [) Y- ~0 S& }
用 Sparse 对内核进行静态分析非常简单.' v+ W0 F4 N4 ]$ J# [1 G; k

+ e' t% ~$ E+ T1 s# 检查所有内核代码
1 v) Z/ l. l2 ]7 J7 pmake C=1 检查所有重新编译的代码
" D. V: D, J" Y9 E, n0 h0 dmake C=2 检查所有代码, 不管是不是被重新编译! l6 A( _. d+ X% y9 Y3 L

' F9 X! S: ?! ]  Q: a
* \; H7 K" I& f4 |; {4. 补充
$ R6 ?3 m1 j* f  F( ^Sparse除了能够用在内核代码的静态分析上, 其实也可以用在一般的C语言程序中.* s0 l" q% [& q) }6 [8 t

% v1 b( _' R4 s  G! r" K! \; s比如下面的小例子:7 P' j+ A/ b# E/ @# R3 h& q
8 S1 h. g* K: q. M8 I9 Y
复制代码
* [# n- h' B: F3 [: t0 Z/******************************************************************************- K* ^& l; p. n; |
* @File    : sparse_test.c; H5 n( s, w% f) X: `3 {: s
* @author  : wangyubin
/ k$ `/ F' f- E' Q * @date    : Fri Feb 28 16:33:34 2014; h& Y$ h; l6 \* ~0 L3 ]! k7 T3 p
* ' Z7 E, Q. |* F! @# G3 i  E
* @brief   : 测试 sparse 的各个检查点* H/ @3 I+ Q: m9 v5 Z: s
* history  : init
3 Z* j( Q: U, n, i# o) U ******************************************************************************/
4 r/ @$ A/ F' ~7 B
4 `; o$ e+ e6 O7 f; k#include <stdio.h>
' S, ^' G4 s" N/ `  w. T' s
* p( u7 ?' h& [9 W& w3 U#define __acquire(x) __context__(x,1)3 T, b1 _% s# s4 R" J8 j- q
#define __release(x) __context__(x,-1)
' J% I  s4 I7 K7 Z% F  M
2 ~4 M8 X. d  m/ Y+ fint main(int argc, char *argv[])) x" e3 Y1 M' x# h1 ]
{
# t7 G9 g' ]4 C    int lock = 1;
( t2 Q  G9 D. k    __acquire(lock);7 w0 b6 t; X6 t/ `6 N2 ^
    /* TODO something */4 T* O, w4 b5 e( K! _
    __release(lock);            /* 注释掉这一句 sparse 就会报错 */1 W; Y: K" i4 H. f- S/ u: _: f, y8 ^) T
    return 0;
9 O: T; q; l) l4 m4 k* x; d}
" w" f! A7 E+ m- m9 Z, D4 y复制代码
4 e% z% X% V9 P$ I. R' X" \; y
+ f% Z9 V7 d$ Y8 @9 c
1 C  y1 A( G* N% @3 x$ E9 G如果安装了 Sparse, 执行静态检查的命令如下:
# _& D; I$ V& {/ q$ f& a
0 t* \' k; M( }$ sparse -a sparse_test.c : Y1 b, E! K$ W
sparse_test.c:15:5: warning: context imbalance in 'main' - wrong count at exit
5 c. T0 x! Y/ l

该用户从未签到

2#
发表于 2020-9-15 17:20 | 只看该作者
内核工具 – Sparse 简介
您需要登录后才可以回帖 登录 | 注册

本版积分规则

关闭

推荐内容上一条 /1 下一条

EDA365公众号

关于我们|手机版|EDA365电子论坛网 ( 粤ICP备18020198号-1 )

GMT+8, 2025-6-27 07:10 , Processed in 0.078125 second(s), 23 queries , Gzip On.

深圳市墨知创新科技有限公司

地址:深圳市南山区科技生态园2栋A座805 电话:19926409050

快速回复 返回顶部 返回列表