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

自助Linux之strace问题诊断工具

[复制链接]

该用户从未签到

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

EDA365欢迎您登录!

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

x

( ]- l/ N( [/ e( K9 D0 y4 u引言
1 Z2 q, J' [+ w" l8 I, O3 G; ]# q( ?! }  _! x
“Oops,系统挂死了..."0 c; X9 a( D6 f$ d; k! ^! q% |

* V9 O' Q* t% I' o4 e“Oops,程序崩溃了...". F4 n5 q0 T) h; V0 I2 U" o8 L( t

- [' a- z: W; m1 b' Q“Oops,命令执行报错..."# w/ Z- I7 \; g: k( |8 P: k- w5 _, l

4 R* }8 i$ B  r5 `
% |" X* X4 g. l3 r! V+ A" z4 S# C1 x7 i0 I7 T' d
对于维护人员来说,这样的悲剧每天都在上演。理想情况下,系统或应用程序的错误日志提供了足够全面的信息,通过查看相关日志,维护人员就能很快地定位出问题发生的原因。但现实情况,许多错误日志打印模凌两可,更多地描述了出错时的现象(比如"could not open file","connect to XXX time out"),而非出错的原因。
4 E, M. {4 @4 W. D+ P: N. n  j/ s3 M1 Z* W
' Y6 Y' ?2 g8 [- ^' g! x
2 ~( r! j5 m( J% e; [1 P3 _
错误日志不能满足定位问题的需求,我们能从更“深层”的方面着手分析吗?程序或命令的执行,需要通过系统调用(system call)与操作系统产生交互,其实我们可以通过观察这些系统调用及其参数、返回值,界定出错的范围,甚至找出问题出现的根因。* U1 A+ x3 O- u4 ]5 L7 |
+ `4 a! n. N5 x# ?# t+ B

3 ^$ L3 O7 y6 g8 _" B( O* ]+ ?" x( G. y* f8 x
在Linux中,strace就是这样一款工具。通过它,我们可以跟踪程序执行过程中产生的系统调用及接收到的信号,帮助我们分析程序或命令执行中遇到的异常情况。
, t  h# s/ G* O2 a0 }" M" v
: `: ]+ h2 ^$ Z" T# L, Q " u' k) Y& p! V6 x

" U7 J0 g4 z, _# F% D4 P  H一个简单的例子
+ c# @8 C) {- ]2 v) Z  ^6 f. r2 z5 y: }7 l
如何使用strace对程序进行跟踪,如何查看相应的输出?下面我们通过一个例子来说明。
# z% _; x3 q* l6 [  _2 l+ C4 R+ ~  D# Q$ D
1.被跟踪程序示例
5 J( c0 X! _; ^" V
% j8 V5 U9 H8 g: L
  • //main.c
  • #include <sys/types.h>
  • #include <sys/stat.h>
  • #include <fcntl.h>
  • int main( )
  • {
  •   int fd ;
  •   int i = 0 ;
  •   fd = open( “/tmp/foo”, O_RDONLY ) ;
  •   if ( fd < 0 )
  •     i=5;
  •   else
  •     i=2;
  •   return i;
  • }
    $ ^1 U7 b" o( ~

* ?: V+ L6 w, g5 z8 I
/ }( f  v" t& G/ k" G+ L6 l以上程序尝试以只读的方式打开/tmp/foo文件,然后退出,其中只使用了open这一个系统调用函数。之后我们对该程序进行编译,生成可执行文件:
; N# f9 Q, y" m' B/ z1 Z4 I  O
. b+ n) {. ~0 b  A5 H. b
  • lx@LX:~$ gcc main.c -o main
    # p+ ?, k* W& |6 F. a3 I  l
+ L; x/ @, c6 @

. }3 b: a/ B( c5 T3 R' _. S) k2.strace跟踪输出9 V# q  O$ `2 G" R9 z
" K, G8 r$ N5 K) |
使用以下命令,我们将使用strace对以上程序进行跟踪,并将结果重定向至main.strace文件:
- ^* }' u& a9 A' T  t
+ [- ?9 R, k' X0 U) \' E, C" _
  • lx@LX:~$ strace -o main.strace ./main
    % ]' {! O. N5 H: q# K; b6 V

+ n' S6 Q0 o. I! E2 w% A. m: z接下来我们来看main.strace文件的内容:
- x' X! R4 j* Q8 E; `! }% G
( \7 [7 X" w. l8 C" t
  • lx@LX:~$ cat main.strace
  • 1 execve("./main", ["./main"], [/* 43 vars */]) = 0
  • 2 brk(0)                                  = 0x9ac4000
  • 3 access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
  • 4 mmap2(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7739000
  • 5 access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
  • 6 open("/etc/ld.so.cache", O_RDONLY)      = 3
  • 7 fstat64(3, {st_mode=S_IFREG|0644, st_size=80682, ...}) = 0
  • 8 mmap2(NULL, 80682, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb7725000
  • 9 close(3)                                = 0
  • 10 access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
  • 11 open("/lib/i386-linux-gnu/libc.so.6", O_RDONLY) = 3
  • 12 read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\220o\1\0004\0\0\0"..., 512) = 512
  • 13 fstat64(3, {st_mode=S_IFREG|0755, st_size=1434180, ...}) = 0
  • 14 mmap2(NULL, 1444360, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x56d000
  • 15 mprotect(0x6c7000, 4096, PROT_NONE)     = 0
  • 16 mmap2(0x6c8000, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x15a) = 0x6c8000
  • 17 mmap2(0x6cb000, 10760, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x6cb000
  • 18 close(3)                                = 0
  • 19 mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7724000
  • 20 set_thread_area({entry_number:-1 -> 6, base_addr:0xb77248d0, limit:1048575, seg_32bit:1, contents:0, read_exec_    only:0, limit_in_pages:1, seg_not_present:0, useable:1}) = 0
  • 21 mprotect(0x6c8000, 8192, PROT_READ)     = 0
  • 22 mprotect(0x8049000, 4096, PROT_READ)    = 0
  • 23 mprotect(0x4b0000, 4096, PROT_READ)     = 0
  • 24 munmap(0xb7725000, 80682)               = 0
  • 25 open("/tmp/foo", O_RDONLY)              = -1 ENOENT (No such file or directory)
  • 26 exit_group(5)                           = ?
  • //标红的行号为方便说明而添加,非strace执行输出
    7 f' [; I' f( E& j& n

; [2 m3 `( F# d- `$ W6 ^9 N/ y" h! {) c6 H0 {0 I4 c
看到这一堆输出,是否心生畏难情绪?不用担心,下面我们对输出逐条进行分析。& P! o( {" |. p& v, v  v7 @' I

- N7 w2 p9 y/ w( G. O& I4 N2 D: `$ Z. Y& P $ E& Q1 Z8 Z/ R' s2 u  H2 |

$ e1 i- w% r! \7 {2 k/ ^strace跟踪程序与系统交互时产生的系统调用,以上每一行就对应一个系统调用,格式为:
" _8 S7 f8 m$ X$ T
  S: N( k8 C7 Z# |, _; k系统调用的名称( 参数... ) = 返回值  错误标志和描述8 U: Z. M6 L& n% [$ K# N
$ a: K, S! j5 R1 b/ N  J6 |
# Z( `6 K! O' x
Line 1:  对于命令行下执行的程序,execve(或exec系列调用中的某一个)均为strace输出系统调用中的第一个。strace首先调用fork或clone函数新建一个子进程,然后在子进程中调用exec载入需要执行的程序(这里为./main)
. R4 ]& y- Y+ |
" F/ C6 n( {! ^/ f$ bLine 2:  以0作为参数调用brk,返回值为内存管理的起始地址(若在子进程中调用malloc,则从0x9ac4000地址开始分配空间)
! T$ G( z9 g8 o/ s+ v- @) z% G8 S" v6 c; @* A- i" w
Line 3:  调用access函数检验/etc/ld.so.nohwcap是否存在
9 _; h# I* ?- Z6 n! O# W; Z8 L6 i5 S. D: D) Y
Line 4:  使用mmap2函数进行匿名内存映射,以此来获取8192bytes内存空间,该空间起始地址为0xb7739000,关于匿名内存映射,可以看这里
1 B& S0 g: G' s% C  M8 l
1 Y, `$ `! |% kLine 6:  调用open函数尝试打开/etc/ld.so.cache文件,返回文件描述符为3
1 _: \  C4 W. F% m/ `6 b- u1 R# R0 ]+ H$ f+ y2 c7 k
Line 7:  fstat64函数获取/etc/ld.so.cache文件信息
, P. d) O  f& t& [: \2 U9 x0 _1 k2 I
Line 8:  调用mmap2函数将/etc/ld.so.cache文件映射至内存,关于使用mmap映射文件至内存,可以看这里
" i3 L  h5 f, C) s
7 Z; D4 x" v( rLine 9:  close关闭文件描述符为3指向的/etc/ld.so.cache文件6 o$ j7 s7 J. E1 z

) \8 {$ S  y) \7 r" O" k, bLine12:  调用read,从/lib/i386-linux-gnu/libc.so.6该libc库文件中读取512bytes,即读取ELF头信息
- y6 R2 ]/ x- A, c
; Y, O3 i$ h0 T3 k; rLine15:  使用mprotect函数对0x6c7000起始的4096bytes空间进行保护(PROT_NONE表示不能访问,PROT_READ表示可以读取)  @( q9 E+ O, f( \. t8 c

* P/ A" v: ^/ L( JLine24:  调用munmap函数,将/etc/ld.so.cache文件从内存中去映射,与Line 8的mmap2对应, a9 |' w& R  B& Q0 x9 B
4 a) A! c5 U' r5 H2 U
Line25:  对应源码中使用到的唯一的系统调用——open函数,使用其打开/tmp/foo文件8 A! o9 \$ ~% R! ^: ~0 }
, Z+ W* g/ V+ k! q/ a2 N: I5 Y
Line26:  子进程结束,退出码为5(为什么退出值为5?返回前面程序示例部分看看源码吧:)+ w+ i" [7 \9 k8 \8 z" R1 N

; g: M/ i' }4 r9 U+ M
4 H, a9 G& y# |8 e- u3.输出分析! j$ Q. t: e+ X& V- b: Y0 g
" M8 @/ L1 u0 `  p& k: B
呼呼!看完这么多系统调用函数,是不是有点摸不着北?让我们从整体入手,回到主题strace上来。! m' e9 T4 q% K$ u, G

3 k; E+ C; U) U' l$ G# s. V从上面输出可以发现,真正能与源码对应上的只有open这一个系统调用(Line25),其他系统调用几乎都用于进行进程初始化工作:装载被执行程序、载入libc函数库、设置内存映射等。
" f. P( ^+ H. `7 l+ y9 g( |: ^8 D% J4 `& B

, [! x2 r; s( `# Y  s' H源码中的if语句或其他代码在相应strace输出中并没有体现,因为它们并没有唤起系统调用。strace只关心程序与系统之间产生的交互,因而strace不适用于程序逻辑代码的排错和分析。
& I, `  x0 l" f9 v. g + F  w: `0 c8 B& k+ y# g5 b0 D

7 x' ]$ x; e7 P对于Linux中几百个系统调用,上面strace输出的几个只是冰山一角,想要更深入地了解Linux系统调用,那就man一下吧!6 |# t5 P4 `. o; H0 K" y

& d$ o9 W* M- j0 u7 [
  • man 2 系统调用名称
  • man ld.so  //Linux动态链接的manpage
    & E# G2 b- C# u
# j3 D- i' T$ {# W2 X1 {% H& X9 k) y

( q$ M8 R' j1 S; D4 p  v# P+ }strace常用选项
6 E  d6 B1 O. P: Q8 ?7 o9 k5 a3 R) m- @
该节介绍经常用到的几个strace命令选项,以及在何时使用这些选项合适。
( |7 n* f: [9 h/ H7 c
" l) T) c: C8 D! m. B) q3 q6 s% i1.跟踪子进程  ?" |2 N: c; d; n+ Q: Z, Z
) z; H( n  |7 p6 ]" M
默认情况下,strace只跟踪指定的进程,而不对指定进程中新建的子进程进行跟踪。使用-f选项,可对进程中新建的子进程进行跟踪,并在输出结果中打印相应进程PID:, Y1 b2 S% G) J$ j1 C0 @

6 j# @. Q% C, ]
  • mprotect(0x5b1000, 4096, PROT_READ)     = 0
  • munmap(0xb77fc000, 80682)               = 0
  • clone(Process 13600 attached
  • child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0xb77fb938) = 13600
  • [pid 13599] fstat64(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 0), ...}) = 0
  • [pid 13600] fstat64(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 0), ...}) = 0
  • [pid 13599] mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0 <unfinished ...>
  • [pid 13600] mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb780f000
  • ……
    4 w1 [4 e. _0 }3 z8 n: [& w

' P+ `* D. @" a. h9 R' V) F8 o0 F# g7 m* s
对多进程程序、命令和脚本使用strace进行跟踪的时,一般打开-f选项。5 ~9 |2 }* H- ]% Z* ?1 X! D

, [1 `. u6 U5 F% S
* E9 E7 ]* U/ S8 Y2.记录系统调用时间
' c0 A; a' [. W% M- a1 N: u9 L/ J+ M6 A% |
strace还可以记录程序与系统交互时,各个系统调用发生时的时间信息,有r、t、tt、ttt、T等几个选项,它们记录时间的方式为:$ Q. o' s4 a& s7 `

) S' m3 S" p' K/ @-T:   记录各个系统调用花费的时间,精确到微秒
& M; N# N6 {6 W. a9 s( I
: K3 G& g' B! d+ k5 f9 W-r:   以第一个系统调用(通常为execve)计时,精确到微秒% B/ p4 @+ f: e' r1 V

8 P* S; h3 B: u& K& _% k% N-t:   时:分:秒) U) O. X7 t4 J, U/ c

/ _, D2 y5 J- W$ i-tt:  时:分:秒 . 微秒& K8 }, g6 W  f8 P

& b9 x0 @/ A: N( z3 s; r-ttt: 计算机纪元以来的秒数 . 微秒
7 O5 t+ _  }6 U2 N* V5 J  t* s+ ]$ M6 S" w  R
比较常用的为T选项,因为其提供了每个系统调用花费时间。而其他选项的时间记录既包含系统调用时间,又算上用户级代码执行用时,参考意义就小一些。对部分时间选项我们可以组合起来使用,例如:% J1 C' S/ J% s2 u1 J& G

# N& T4 @9 X4 k
  • strace -Tr ./main
  • 0.000000 execve(“./main”, [“main”], [/* 64 vars */]) = 0
  • 0.000931 fcntl64(0, F_GETFD)= 0 <0.000012>
  • 0.000090 fcntl64(1, F_GETFD)= 0 <0.000022>
  • 0.000060 fcntl64(2, F_GETFD)= 0 <0.000012>
  • 0.000054 uname({sys=”Linux”, node=”ion”, ...}) = 0 <0.000014>
  • 0.000307 geteuid32()= 7903 <0.000011>
  • 0.000040 getuid32()= 7903 <0.000012>
  • 0.000039 getegid32()= 200 <0.000011>
  • 0.000039 getgid32()= 200 <0.000011>
  • ……' `$ i: S, ]! J  B; d
, v. b: ?, J# p. l+ |

, V5 Z, U- u7 k- f' }: f最左边一列为-r选项对应的时间输出,最右边一列为-T选项对应的输出。
, i; \. y" T( Y: o( U. J) o' a$ C: V. g8 u% Z, m( m& E! Y
& z5 ^% d& Y; c" u, P, r) \
3.跟踪正在运行的进程2 f6 y+ M9 @0 t$ m3 ~6 i
. q( L2 t* X8 A4 b- Z( R( x9 \# h+ u
使用strace对运行中的程序进行跟踪,使用命令“strace -p PID”即可,命令执行之后,被跟踪的进程照常执行,strace的其他选项也适用于运行中的进程跟踪。
3 W' p5 L: b" ?9 J9 P0 j* V
! ~- o6 p! T0 n2 P/ E' |( g* W  I' F# J) j' L/ `
使用strace处理程序挂死% ]$ U- l' O9 T1 z" I0 f5 j1 t
" x5 S; Q- @  }- u
最后我们通过一个程序示例,学习使用strace分析程序挂死的方法。; Q3 y1 z7 |0 H% A4 P0 k, A
8 Q! _1 e' U: O+ u8 ~5 f8 y: a
1.挂死程序源码
( X: A4 r$ ~, R& p9 s" k9 r: g- Q& r5 [! \! T3 b" r+ Q5 N$ _  n: ^
  • //hang.c
  • #include <stdio.h>
  • #include <sys/types.h>
  • #include <unistd.h>
  • #include <string.h>
  • int main(int argc, char** argv)
  • {
  •     getpid(); //该系统调用起到标识作用
  •     if(argc < 2)
  •     {
  •         printf("hang (user|system)\n");
  •         return 1;
  •     }
  •     if(!strcmp(argv[1], "user"))
  •         while(1);
  •     else if(!strcmp(argv[1], "system"))
  •         sleep(500);
  •     return 0;
  • }
    8 @2 B* t7 @/ W3 K% U

$ J! s% M- r) ^
5 S3 v, Y$ ~, z3 r% M" L. C6 q3 ?可向该程序传送user和system参数,以上代码使用死循环模拟用户态挂死,调用sleep模拟内核态程序挂死。$ |5 q7 c+ h8 l7 }

- r; z  [: C  C: c" _
8 ~6 E5 n7 c0 h4 D2.strace跟踪输出
7 f! |$ r' Y( b! ?+ i/ ]- |
3 \7 K2 I- U/ b; C( S2 q/ }用户态挂死跟踪输出:! b9 m9 i4 x& v$ h: z" X1 M
% v( K& \8 z. _' g8 c$ w
  • lx@LX:~$ gcc hang.c -o hang
  • lx@LX:~$ strace ./hang user
  • ……
  • mprotect(0x8049000, 4096, PROT_READ)    = 0
  • mprotect(0xb59000, 4096, PROT_READ)     = 0
  • munmap(0xb77bf000, 80682)               = 0
  • getpid()                                = 14539% C- ^) q0 N' A1 X

0 P* Y+ F8 n2 j8 s9 Y* F2 N
, ]% n7 W( A# {6 H+ a内核态挂死跟踪输出:8 f) r- }; r- m3 A9 G
5 Q/ P* _! d' z! M& O+ E& S7 {/ ]9 y
  • lx@LX:~$ strace ./hang system
  • ……
  • mprotect(0x8049000, 4096, PROT_READ)    = 0
  • mprotect(0xddf000, 4096, PROT_READ)     = 0
  • munmap(0xb7855000, 80682)               = 0
  • getpid()                                = 14543
  • rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0
  • rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0
  • rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
  • nanosleep({500, 0},
      G( B( J$ `$ m. c- E% Y% E' m! p
, t; x! g4 {, |, O
7 O$ p# ^! v( F0 X" ?
3.输出分析
3 F; K' B0 H7 E9 y! @  V1 ^3 w- z7 h5 F
用户态挂死情况下,strace在getpid()一行输出之后没有其他系统调用输出;进程在内核态挂死,最后一行的系统调用nanosleep不能完整显示,这里nanosleep没有返回值表示该调用尚未完成。
/ x0 }1 X/ o8 z: P! |7 K! ]+ R; h0 s  Y# p/ a- u8 L

/ c2 j' `: U* [/ I5 T因而我们可以得出以下结论:使用strace跟踪挂死程序,如果最后一行系统调用显示完整,程序在逻辑代码处挂死;如果最后一行系统调用显示不完整,程序在该系统调用处挂死。, S. V1 R6 g# S0 y2 z% D8 w# T

- B' j  m& C, Z* f' `1 z' Z) L7 j$ Y% }: Q) H7 R& o5 ^
当程序挂死在系统调用处,我们可以查看相应系统调用的man手册,了解在什么情况下该系统调用会出现挂死情况。另外,系统调用的参数也为我们提供了一些信息,例如挂死在如下系统调用:
  A9 Y' ]6 {+ K+ E1 v' v6 t3 `+ a! k2 I) u, y+ V) y" ]  f( l
  • read(16,4 R' U- z4 q8 {% G& @8 m

8 g5 W% z9 _. X2 c. S2 C7 B
9 L9 ~8 l; b2 Q那我们可以知道read函数正在对文件描述符为16的文件或socket进行读取,进一步地,我们可以使用lsof工具,获取对应于文件描述符为16的文件名、该文件被哪些进程占用等信息。! ^  [0 u, U5 P4 J4 g& G% L

, L' u' B- V, H& K$ H+ B( l* Q% P$ a$ j. u
小结" Y4 E" [' F6 H( K

) C9 u7 q' s5 I9 O! [. g; j9 T本文对Linux中常用的问题诊断工具strace进行了介绍,通过程序示例,介绍了strace的使用方法、输出格式以及使用strace分析程序挂死问题的方法,另外对strace工具的几个常用选项进行了说明,描述了这几个选项适用的场景。% Z5 ~' b( Z( F! F/ l7 H
, E# l2 W  @' z2 Q# E; |# w) t: `
下次再遇到程序挂死、命令执行报错的问题,如果从程序日志和系统日志中看不出问题出现的原因,先别急着google或找高手帮忙,别忘了一个强大的工具它就在那里,不离不弃,strace一下吧!
4 w" x5 A. f/ c5 a$ I# d

该用户从未签到

2#
发表于 2020-3-10 16:41 | 只看该作者
自助Linux之问题诊断工具strace

该用户从未签到

3#
发表于 2020-3-11 16:35 | 只看该作者
自助Linux之strace问题诊断工具
您需要登录后才可以回帖 登录 | 注册

本版积分规则

关闭

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

EDA365公众号

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

GMT+8, 2025-11-25 17:37 , Processed in 0.171875 second(s), 24 queries , Gzip On.

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

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

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