EDA365电子论坛网

标题: 自助Linux之strace问题诊断工具 [打印本页]

作者: haidaowang    时间: 2020-3-10 10:15
标题: 自助Linux之strace问题诊断工具
3 D  a0 j% f; G( e2 x' e4 z6 b7 J" I
引言
8 r' l2 w$ ~7 w4 ]  [' ]* |9 A4 N9 ^+ p
“Oops,系统挂死了..."/ h. g) L  q. w$ \
/ C5 Q: L1 s4 I' f, F9 c" D! [* s0 n
“Oops,程序崩溃了..."
& f! L. V8 P  J7 T! r- B9 I
6 K- T5 l5 P3 x! `( {( e1 i“Oops,命令执行报错..."! D, S& a2 W* J$ G" U

/ C. Y* Q3 N0 c7 T) [6 k. Q$ i% k % ]- M8 `9 q* F# n. ?; P, L
4 H6 O# Y) n, X; l: i) r% W: _; h# H
对于维护人员来说,这样的悲剧每天都在上演。理想情况下,系统或应用程序的错误日志提供了足够全面的信息,通过查看相关日志,维护人员就能很快地定位出问题发生的原因。但现实情况,许多错误日志打印模凌两可,更多地描述了出错时的现象(比如"could not open file","connect to XXX time out"),而非出错的原因。- m; {8 G% C% ?. l" I5 M' h6 y

' O3 i1 B2 m5 u2 t: ]% k8 [2 G2 x 1 U" y  c# U( Q: V4 f
5 W9 S3 h2 E4 Z& Y
错误日志不能满足定位问题的需求,我们能从更“深层”的方面着手分析吗?程序或命令的执行,需要通过系统调用(system call)与操作系统产生交互,其实我们可以通过观察这些系统调用及其参数、返回值,界定出错的范围,甚至找出问题出现的根因。
& _. n$ u3 ]1 r$ q$ L- {/ [% i! t0 R# V4 l8 c+ P0 F3 |* I" S6 ^( P

) n9 Q8 r' y; q, k% s: L. c# }& Y: ~+ r
在Linux中,strace就是这样一款工具。通过它,我们可以跟踪程序执行过程中产生的系统调用及接收到的信号,帮助我们分析程序或命令执行中遇到的异常情况。
3 P! B! y! q. j% H/ _, ]9 Q* j
7 t6 l9 s* H( J3 r 1 E5 c! s- S  _/ [7 M1 D. K+ W( W

( N/ T3 ~2 \' W; \5 n2 L一个简单的例子
, V4 j; t" e2 W0 V: h: t- z, D; w: n9 n& u
如何使用strace对程序进行跟踪,如何查看相应的输出?下面我们通过一个例子来说明。
7 ^. K5 C2 n/ r) G' X+ G6 r* r! k' j& k0 k
1.被跟踪程序示例
: k: I$ A5 k- v, s, v; ^! k$ m1 q, c' N! ]5 g
3 x7 @; j/ |8 d- X4 Z, [
1 }5 V9 h# _. n! C7 r5 F
以上程序尝试以只读的方式打开/tmp/foo文件,然后退出,其中只使用了open这一个系统调用函数。之后我们对该程序进行编译,生成可执行文件:
. z! \( E8 y3 O* g; J2 H9 j% M9 W( a, |3 Q; q0 ?! H. S

$ G8 t8 m$ B! S
* |' F1 {- Y3 ?4 p# J2.strace跟踪输出4 V# \/ @, |0 M6 H8 |4 }0 p

5 o& ~- A! X1 G4 J: e3 \使用以下命令,我们将使用strace对以上程序进行跟踪,并将结果重定向至main.strace文件:
- S! s) z$ y$ i0 B
* j( `( X, C+ h1 w
% p: X* e$ _7 Q& o: I1 N0 w% _6 A接下来我们来看main.strace文件的内容:0 ^( g" I. ^# Z; ]
% j5 n! F9 \' @$ X: n7 h, L

; d& ~' @# W+ [6 W8 m% b# C1 t/ [% T1 b
看到这一堆输出,是否心生畏难情绪?不用担心,下面我们对输出逐条进行分析。4 N) l9 k5 V1 k" D8 i+ {* y$ A
+ O6 Z9 d) ^# h) U/ r0 z! M6 ^. g

  X, U. h% N( p  ^+ P% X
  r) H, s1 l5 D4 ]% sstrace跟踪程序与系统交互时产生的系统调用,以上每一行就对应一个系统调用,格式为:( A8 G) W+ f' K( `7 }

6 O: i, l2 R9 N0 e; _系统调用的名称( 参数... ) = 返回值  错误标志和描述* y7 }  ^; E, j
5 u9 N  e& k  f

2 K- Z: j$ H, Q; ?* j  MLine 1:  对于命令行下执行的程序,execve(或exec系列调用中的某一个)均为strace输出系统调用中的第一个。strace首先调用fork或clone函数新建一个子进程,然后在子进程中调用exec载入需要执行的程序(这里为./main)
  c: ~) Q; r) h+ o  ~% R4 Y3 W
Line 2:  以0作为参数调用brk,返回值为内存管理的起始地址(若在子进程中调用malloc,则从0x9ac4000地址开始分配空间)
! ^: A' R5 P5 p. d: W& j  @5 _' M+ D6 ?5 W4 f3 ^
Line 3:  调用access函数检验/etc/ld.so.nohwcap是否存在3 v. N7 D* A3 s

2 v6 o% N9 M. f3 a, v& mLine 4:  使用mmap2函数进行匿名内存映射,以此来获取8192bytes内存空间,该空间起始地址为0xb7739000,关于匿名内存映射,可以看这里
" z, T( Y* K6 B( E% t" [0 k# s, g
) E/ U" w' J& I8 j3 HLine 6:  调用open函数尝试打开/etc/ld.so.cache文件,返回文件描述符为3
8 _6 O9 m( ^# b/ M' y- R, O% M' ]3 J- T2 k* a' D; I
Line 7:  fstat64函数获取/etc/ld.so.cache文件信息1 S. e, q+ ?! q5 l8 `+ x8 T
; b( P2 _5 U6 K6 \, O# m
Line 8:  调用mmap2函数将/etc/ld.so.cache文件映射至内存,关于使用mmap映射文件至内存,可以看这里
# O; `, S+ L* q( ^5 r4 g6 x% X+ P9 u8 K; f3 n/ _' r
Line 9:  close关闭文件描述符为3指向的/etc/ld.so.cache文件
* D) N5 {- w8 e( k6 H0 y. Q4 I: w8 U# ]. B4 {! ~9 {4 b2 n$ w* h7 [$ ?
Line12:  调用read,从/lib/i386-linux-gnu/libc.so.6该libc库文件中读取512bytes,即读取ELF头信息
* I/ e8 G# p6 C7 J3 n
1 L5 I: F3 X/ l% WLine15:  使用mprotect函数对0x6c7000起始的4096bytes空间进行保护(PROT_NONE表示不能访问,PROT_READ表示可以读取)
8 i; `" C) C: e" Y# V
9 y& u1 E7 q" J0 YLine24:  调用munmap函数,将/etc/ld.so.cache文件从内存中去映射,与Line 8的mmap2对应
: u' U$ X1 r5 w% O! h# @0 @" f1 F; H
Line25:  对应源码中使用到的唯一的系统调用——open函数,使用其打开/tmp/foo文件
8 A  o' v) G# Y/ v. q( x. L6 I  O& D4 Y
Line26:  子进程结束,退出码为5(为什么退出值为5?返回前面程序示例部分看看源码吧:)
; o! d& B# `1 W# b' J+ `- ?* t( |/ a) [4 j1 b  e
) J  U; }2 s, L; }* V
3.输出分析
" @+ u' [( O2 U/ ~. a
0 q1 E3 r* A5 {0 X4 i6 o呼呼!看完这么多系统调用函数,是不是有点摸不着北?让我们从整体入手,回到主题strace上来。
' X4 i+ h6 L6 |2 ?
' j: S- h1 _6 j! R& Q) \  X  o从上面输出可以发现,真正能与源码对应上的只有open这一个系统调用(Line25),其他系统调用几乎都用于进行进程初始化工作:装载被执行程序、载入libc函数库、设置内存映射等。  T' \9 Y5 M8 [+ j# o
8 `  q) ?* z9 D2 [- A

. I# p, ]$ ]9 m/ g. T$ \* f4 S源码中的if语句或其他代码在相应strace输出中并没有体现,因为它们并没有唤起系统调用。strace只关心程序与系统之间产生的交互,因而strace不适用于程序逻辑代码的排错和分析。
) z4 I* S( E3 q" s! s, r( I
0 K4 I5 }; c* l5 w: m6 n( H8 d6 a* e
对于Linux中几百个系统调用,上面strace输出的几个只是冰山一角,想要更深入地了解Linux系统调用,那就man一下吧!
: F+ ?$ s! W- j: k7 n/ |+ L0 ~
" v* I7 Y1 w: c; k  d! { 7 w. P2 V6 `; I  Y) Y
9 v6 c: N% p$ E0 P# _9 N+ S  b
strace常用选项" o; }# {; ~, S9 l6 [7 ~9 e

' _  e+ v* W! J, E该节介绍经常用到的几个strace命令选项,以及在何时使用这些选项合适。
- [" G1 v" T& u( A) T
9 r: c. r, e2 i' ~% L& l; q! \1.跟踪子进程& s* I) c/ \" S, Z% V9 a! |
0 v' M! P" b( ~0 I* f/ {" P
默认情况下,strace只跟踪指定的进程,而不对指定进程中新建的子进程进行跟踪。使用-f选项,可对进程中新建的子进程进行跟踪,并在输出结果中打印相应进程PID:
6 h& C7 k5 r: F$ C, m8 t' a5 \, J3 [: `) Q1 P  l1 P
3 [/ m' h) z$ e( F+ w2 [8 P  w

: I5 U2 j/ O4 i6 U. g对多进程程序、命令和脚本使用strace进行跟踪的时,一般打开-f选项。
2 G7 b, |5 Z9 _+ ^$ P$ t; {# e2 c5 o" ?; ^/ x

5 Q( Q5 n: S% x+ U% S9 @7 M9 K2.记录系统调用时间/ F1 V8 ?" X! Z

2 x5 ^$ f* X# Gstrace还可以记录程序与系统交互时,各个系统调用发生时的时间信息,有r、t、tt、ttt、T等几个选项,它们记录时间的方式为:0 v1 ~+ T6 w; J, i

/ z7 D, a4 q5 [% N  o$ a$ G-T:   记录各个系统调用花费的时间,精确到微秒& S8 B  t6 o* l+ K* I+ c5 Q

" Y9 p  F" Q7 R4 X) B3 M3 ]; ~-r:   以第一个系统调用(通常为execve)计时,精确到微秒
( [. L* S0 s& w7 `/ C  @  R  _. L0 N- H7 ?  _
-t:   时:分:秒  q1 o3 X8 J* c) `

( \, |0 ^6 t! L7 r, E-tt:  时:分:秒 . 微秒9 w+ a2 A: M! C4 k0 r# W

2 _0 a8 u! i' I) q8 n3 U-ttt: 计算机纪元以来的秒数 . 微秒
7 K/ C9 V  N( u4 k; b2 ]% i, u, b# f0 ^
比较常用的为T选项,因为其提供了每个系统调用花费时间。而其他选项的时间记录既包含系统调用时间,又算上用户级代码执行用时,参考意义就小一些。对部分时间选项我们可以组合起来使用,例如:
( g7 B, y6 u. O7 d
8 f. r4 w3 T7 J2 _' v+ C& T3 @3 \/ c1 O7 @
5 m& X0 B( n5 k- h
最左边一列为-r选项对应的时间输出,最右边一列为-T选项对应的输出。
! X; p, c" q! Z9 Y
8 ~# m4 I# R6 h! l' ]4 T; ^9 w4 X  k* s6 Z+ j) S/ l# k
3.跟踪正在运行的进程
8 v. q* J; p" J+ `  k( c( g5 x3 |5 P- o6 ~2 q
使用strace对运行中的程序进行跟踪,使用命令“strace -p PID”即可,命令执行之后,被跟踪的进程照常执行,strace的其他选项也适用于运行中的进程跟踪。3 y9 i* i2 j7 T5 S+ A

9 F( c) W$ V: G2 [  |
; Y+ y3 q& _6 ^, V. k使用strace处理程序挂死- b0 l6 E; H' d

1 ~5 S1 W8 p4 x( L5 i最后我们通过一个程序示例,学习使用strace分析程序挂死的方法。
7 N2 o0 s* a4 a: }/ q0 c, F% @$ J/ X$ S6 B3 o
1.挂死程序源码
+ T, L% |/ O2 G* ?. j* [
- |. R: s: A& i. q6 D5 n/ b5 }' A% y' r/ z# s+ `
; B! s( ]5 X. K, b
可向该程序传送user和system参数,以上代码使用死循环模拟用户态挂死,调用sleep模拟内核态程序挂死。& C: p( U$ G, q9 ~0 u9 O8 E

& }/ S0 N: R* ~2 B# Z/ Y+ j# X. K+ y, N7 A
2.strace跟踪输出' r* g( c9 s6 \
, ?( n- y* p5 L' e( w/ [
用户态挂死跟踪输出:
+ H. _' Q3 b, R
7 T3 D2 L7 ~. v% J: L
+ `8 _7 _2 t' l% J. T+ p9 L$ o  p% U% [8 `0 _/ c
内核态挂死跟踪输出:5 M3 r1 n0 h: L% P& _+ u0 n: h

  y2 l; b  y" L! e2 r2 _# V
8 g0 G8 [# h& Q0 {" m1 F
9 N% h8 ~& L: I# j3.输出分析
+ o1 G4 M2 b) V% d* t! p( q  v* N5 [- @9 E3 X% F) M& n
用户态挂死情况下,strace在getpid()一行输出之后没有其他系统调用输出;进程在内核态挂死,最后一行的系统调用nanosleep不能完整显示,这里nanosleep没有返回值表示该调用尚未完成。
7 S  u) {# i5 t; T+ j
! ^/ B3 a4 Q4 M; k9 H# {) R  ]3 c, ?
因而我们可以得出以下结论:使用strace跟踪挂死程序,如果最后一行系统调用显示完整,程序在逻辑代码处挂死;如果最后一行系统调用显示不完整,程序在该系统调用处挂死。. L5 A- _/ h9 d# d

2 T5 F0 c3 N' t5 I7 N" F0 a4 u( c6 `% f" i3 S9 p* w6 ]* D# W
当程序挂死在系统调用处,我们可以查看相应系统调用的man手册,了解在什么情况下该系统调用会出现挂死情况。另外,系统调用的参数也为我们提供了一些信息,例如挂死在如下系统调用:- U; T8 E/ a" V2 N
9 f3 j. k8 a; y# y9 @
9 ^4 @. f  e$ ]

( x3 L2 x) J/ V% G* Y1 X! d那我们可以知道read函数正在对文件描述符为16的文件或socket进行读取,进一步地,我们可以使用lsof工具,获取对应于文件描述符为16的文件名、该文件被哪些进程占用等信息。5 p5 \  ]5 N- p" C. \4 p+ q
- r6 ]( p: ^; Z

! `3 L& d7 c; R- {( R5 S小结
) g2 w2 \. B( X8 Z( v) \9 R
+ `5 C% E# z! s) w/ o  n; y本文对Linux中常用的问题诊断工具strace进行了介绍,通过程序示例,介绍了strace的使用方法、输出格式以及使用strace分析程序挂死问题的方法,另外对strace工具的几个常用选项进行了说明,描述了这几个选项适用的场景。/ p" k9 n( D# Y& ~

1 P& [; A9 n1 m8 {" b- B下次再遇到程序挂死、命令执行报错的问题,如果从程序日志和系统日志中看不出问题出现的原因,先别急着google或找高手帮忙,别忘了一个强大的工具它就在那里,不离不弃,strace一下吧!
! n1 ], h7 ~; C1 N) B
作者: CCxiaom    时间: 2020-3-10 16:41
自助Linux之问题诊断工具strace
作者: NNNei256    时间: 2020-3-11 16:35
自助Linux之strace问题诊断工具




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