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

自助Linux之strace问题诊断工具

[复制链接]

该用户从未签到

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

EDA365欢迎您登录!

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

x
# @' O- @" d) _" P4 {: c$ X- n
引言
! r# f5 E7 l! }4 t! U
  @2 r$ Z. C' }: o5 A: t0 O  V“Oops,系统挂死了...") }# y! h$ \+ u6 J$ }4 B+ K

( G! y( L" s0 q) a3 {; w0 d$ z“Oops,程序崩溃了..."2 D# F0 j4 V+ C6 M0 H% x" ^- h

/ o: y, U$ l) c“Oops,命令执行报错..."6 Y/ A' |$ j- A  X
$ `, S( S1 R/ v: ^" M

9 ?2 t4 U5 e$ k' y7 f- Q! Y$ R% n" t2 X' s$ [5 |
对于维护人员来说,这样的悲剧每天都在上演。理想情况下,系统或应用程序的错误日志提供了足够全面的信息,通过查看相关日志,维护人员就能很快地定位出问题发生的原因。但现实情况,许多错误日志打印模凌两可,更多地描述了出错时的现象(比如"could not open file","connect to XXX time out"),而非出错的原因。
, U! f6 I9 S) r2 p  w& g7 f- ?+ [; a5 |) s9 l; O; r& G
% ~, ^4 F  G4 D' p/ a# C

9 \* S- [: g6 z& a& ~9 W9 C6 l错误日志不能满足定位问题的需求,我们能从更“深层”的方面着手分析吗?程序或命令的执行,需要通过系统调用(system call)与操作系统产生交互,其实我们可以通过观察这些系统调用及其参数、返回值,界定出错的范围,甚至找出问题出现的根因。* F% z/ Z& t5 Y2 ?* z
. f* Z4 ^6 u4 L

9 I: h) v3 C- b% _
# R- g+ Q& J9 \) W- H2 ^% M在Linux中,strace就是这样一款工具。通过它,我们可以跟踪程序执行过程中产生的系统调用及接收到的信号,帮助我们分析程序或命令执行中遇到的异常情况。
. Y; [. L- b( }! \0 m( W* H/ H1 i" a  @2 }2 f

4 M! `: C1 {" m: k# S. ?9 w" j* s+ k
一个简单的例子. F) G" P& y) H* [3 f6 N& n
& z  X) n& G$ ?: ?
如何使用strace对程序进行跟踪,如何查看相应的输出?下面我们通过一个例子来说明。
% z& k0 y* a# I# T. v4 Y: ?
: Z5 @+ D  B0 E: a; K1.被跟踪程序示例4 g1 _3 [" `" ?; @2 [( v* E, @
" @: b/ [9 c# C9 u) P0 ~9 d
  • //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;
  • }
    7 }9 N( }0 h5 g* p& F

( `: a8 F5 Y( ?% M) U9 L4 M( ~. C& @$ B: F" a- J; N
以上程序尝试以只读的方式打开/tmp/foo文件,然后退出,其中只使用了open这一个系统调用函数。之后我们对该程序进行编译,生成可执行文件:
+ U  n7 r9 B9 ~5 ^
( ~1 W! w* @$ L% m
  • lx@LX:~$ gcc main.c -o main
      o# x4 z, v  p$ B6 E

- D& j6 m7 E. ?3 l
* {( W( n. H" m# K2.strace跟踪输出  r* x% V& [9 D7 z% J4 G. q9 e8 @

+ ^7 w! T) I# q  T! u+ s9 T0 A/ m+ @使用以下命令,我们将使用strace对以上程序进行跟踪,并将结果重定向至main.strace文件:
3 S0 C* W  U+ Q* g, O! i1 k0 ~
- }  C2 A) {1 O, ?! W5 G! u
  • lx@LX:~$ strace -o main.strace ./main) O. }3 |! o4 {2 o6 S3 c& r
7 e' B$ k2 {" m# B) g  P7 [! G
接下来我们来看main.strace文件的内容:7 `# Y# b) p, s& v( t
) a0 A2 V# q: O) `" E
  • 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执行输出
    # W! e5 h( S+ U3 z' A) [
; W& c2 ?) I( S! J% _& W) t- s

+ F5 k5 E: S; i6 y看到这一堆输出,是否心生畏难情绪?不用担心,下面我们对输出逐条进行分析。+ N0 d: ^. [0 K: R0 D  P
6 }- l3 b6 S8 `# u

0 N  W5 w# \9 l( Z7 u: ]  l- m% p7 q4 N) M
strace跟踪程序与系统交互时产生的系统调用,以上每一行就对应一个系统调用,格式为:
1 {. B$ C+ y7 K' {  f, M* Y+ r  }1 g* U4 q4 K9 \
系统调用的名称( 参数... ) = 返回值  错误标志和描述
9 h/ y( U) N7 j7 V; R% k6 O- c! r, \8 |

2 E: o0 {# f  LLine 1:  对于命令行下执行的程序,execve(或exec系列调用中的某一个)均为strace输出系统调用中的第一个。strace首先调用fork或clone函数新建一个子进程,然后在子进程中调用exec载入需要执行的程序(这里为./main)
( x+ r- N3 r% O/ q4 m' i2 @$ l# [* v: i) a8 U8 _& H
Line 2:  以0作为参数调用brk,返回值为内存管理的起始地址(若在子进程中调用malloc,则从0x9ac4000地址开始分配空间)
9 v. m( g2 |+ b; {* ?) s/ y2 S* \; e3 P  c1 x
Line 3:  调用access函数检验/etc/ld.so.nohwcap是否存在
+ A; f1 H: h/ M/ K1 u, r7 s, U+ R( Y) L! G6 Q! `, R! `. X( E# V; T
Line 4:  使用mmap2函数进行匿名内存映射,以此来获取8192bytes内存空间,该空间起始地址为0xb7739000,关于匿名内存映射,可以看这里; S" @5 g: \+ e  c1 [# V5 b
; Z7 J3 D9 J+ P0 D5 v# T" \" ~
Line 6:  调用open函数尝试打开/etc/ld.so.cache文件,返回文件描述符为38 z9 r* {4 X( o/ Y

6 p) d. X, p2 t: N: p6 h3 `2 pLine 7:  fstat64函数获取/etc/ld.so.cache文件信息
  ~2 s8 p! @: O( q% x/ W3 X( d. R. ?, m8 h, X
Line 8:  调用mmap2函数将/etc/ld.so.cache文件映射至内存,关于使用mmap映射文件至内存,可以看这里
% M: |  P9 ^2 ~6 z/ Q" n. M2 S+ L- K4 P8 O
Line 9:  close关闭文件描述符为3指向的/etc/ld.so.cache文件) ^6 y; T; f$ C' \9 l
( U4 [. X7 ?  C* {) k6 b
Line12:  调用read,从/lib/i386-linux-gnu/libc.so.6该libc库文件中读取512bytes,即读取ELF头信息8 p4 c- f8 g! q/ s
4 l* }9 a1 e: C
Line15:  使用mprotect函数对0x6c7000起始的4096bytes空间进行保护(PROT_NONE表示不能访问,PROT_READ表示可以读取)
; x- x' q( {  j% D% C) j8 S1 f1 N
; C3 H  U7 M1 j% n5 i9 b* Z6 r: ULine24:  调用munmap函数,将/etc/ld.so.cache文件从内存中去映射,与Line 8的mmap2对应, V  C3 f( R2 N; x4 {2 n# g1 u
. S4 }8 _, Z: o3 L1 d! d
Line25:  对应源码中使用到的唯一的系统调用——open函数,使用其打开/tmp/foo文件
# B( M/ i0 D/ z
  O8 X* {' b. A8 |2 F0 C2 O7 T0 h2 JLine26:  子进程结束,退出码为5(为什么退出值为5?返回前面程序示例部分看看源码吧:)
- S1 g1 L6 r, q
, j3 Z/ w! a7 n5 k
: U: X; C" n2 m4 ?# b6 V6 ?3.输出分析
  q- X0 Y- q$ x* j- ?# _0 l8 d, g0 l) m* z
呼呼!看完这么多系统调用函数,是不是有点摸不着北?让我们从整体入手,回到主题strace上来。, m$ E9 S3 o9 u5 z& c, K( b) n% _
1 @* o, p; @. E# \# |6 ~# Y0 k
从上面输出可以发现,真正能与源码对应上的只有open这一个系统调用(Line25),其他系统调用几乎都用于进行进程初始化工作:装载被执行程序、载入libc函数库、设置内存映射等。
" N1 ~1 Y5 b1 m- P1 i" p& D6 o1 e! U$ z: p0 w0 k
% L4 ~- @) M9 k) \, ^) ]) }  T
源码中的if语句或其他代码在相应strace输出中并没有体现,因为它们并没有唤起系统调用。strace只关心程序与系统之间产生的交互,因而strace不适用于程序逻辑代码的排错和分析。
  W3 L4 x* A- y 2 m% z1 Z& i, j4 K$ k

' n6 m% K) v+ r* C' y对于Linux中几百个系统调用,上面strace输出的几个只是冰山一角,想要更深入地了解Linux系统调用,那就man一下吧!1 {7 j. a# T. G

  c7 d0 a# ~. T/ _- `
  • man 2 系统调用名称
  • man ld.so  //Linux动态链接的manpage
      i/ N& ~2 l; c5 k' }
- ~" K4 t) ]/ t: i& I
& ]4 H( l9 E! p9 s, S
strace常用选项1 [" n% S' y3 c* ]$ Z5 |; s
& G+ D1 L  l, ~3 y' q
该节介绍经常用到的几个strace命令选项,以及在何时使用这些选项合适。
8 ], v* R  F3 I$ ?8 a2 X; a6 p; h$ l5 _/ T0 v
1.跟踪子进程
1 Z+ y! m0 W0 R; X6 m6 Y+ Y% q; @, y
" y: [( ~) ]7 Y默认情况下,strace只跟踪指定的进程,而不对指定进程中新建的子进程进行跟踪。使用-f选项,可对进程中新建的子进程进行跟踪,并在输出结果中打印相应进程PID:9 ]" V) N9 k: Q8 U+ y4 t. F
% ]6 N$ J- L, q# h' F5 g8 U
  • 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
  • ……
    8 w/ C% c- |! ]+ g, A* M
' S1 C+ B2 J6 c+ }

8 y' ^$ H7 O% s4 t. s对多进程程序、命令和脚本使用strace进行跟踪的时,一般打开-f选项。' ?2 y, O. i7 x+ A  G8 @

  g+ t) U- w8 i
0 e0 \( h/ e1 [/ y- p2.记录系统调用时间" D+ l: c% t) x

+ A0 f% K5 a& ?! A  l5 k) Cstrace还可以记录程序与系统交互时,各个系统调用发生时的时间信息,有r、t、tt、ttt、T等几个选项,它们记录时间的方式为:( }8 \. a2 t! X4 \' I) e8 W

% ^' s6 H& k  h1 w3 D' T-T:   记录各个系统调用花费的时间,精确到微秒; {1 X* W5 d+ |# E

) L8 S/ f  i" M: W1 m-r:   以第一个系统调用(通常为execve)计时,精确到微秒+ B" J! |0 E& d8 ^" s
  j% v2 ?0 B8 _, v' h1 ~' a; x
-t:   时:分:秒
4 e# u! P8 |( A( m# {# s& G8 b
6 r$ l2 r. q2 [-tt:  时:分:秒 . 微秒
9 t3 J  {, j* P+ F. g4 S; B4 A  L/ t/ P" P0 C/ P
-ttt: 计算机纪元以来的秒数 . 微秒
  n$ \+ p  _4 w" a# c" p: A) \0 a
; Y" c4 b8 {5 o! y比较常用的为T选项,因为其提供了每个系统调用花费时间。而其他选项的时间记录既包含系统调用时间,又算上用户级代码执行用时,参考意义就小一些。对部分时间选项我们可以组合起来使用,例如:" V/ _; s9 A0 l% h

7 |9 P6 f: B# ~
  • 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>
  • ……
    8 D* u+ ^" Y, G8 s" D
+ p& A) U' v3 Y

- \( p) o& X2 T% y- P. R最左边一列为-r选项对应的时间输出,最右边一列为-T选项对应的输出。
4 T, i- g: x1 A% e" Y) i! ?1 ]6 M9 D6 y4 B0 t; i3 \' G, G
5 `8 v4 f$ k6 u
3.跟踪正在运行的进程! q- L' Z6 ^( c; C5 l/ {/ {

9 I2 k: e/ h+ z; Z使用strace对运行中的程序进行跟踪,使用命令“strace -p PID”即可,命令执行之后,被跟踪的进程照常执行,strace的其他选项也适用于运行中的进程跟踪。
! u( {7 r" `5 ?. R
/ h% D1 y+ z+ ^. v6 B" u) a3 v+ d, x9 y# T: t
使用strace处理程序挂死
0 d  {* ^. K8 ~
" f" j4 n- b( k) G! F$ h* u最后我们通过一个程序示例,学习使用strace分析程序挂死的方法。
$ z* q( r0 \! r2 L* G8 {: c, v! z$ B! H  n$ Y0 b1 |
1.挂死程序源码# }" z9 D. O( B" N

! J6 O! `) u. [, T
  • //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;
  • }  I$ X& f8 h! }; T3 N- @/ d- _( C
6 S; O( z5 P" L+ a  Y$ Y/ y
8 G2 T; m8 `& j( b" E6 k; Q
可向该程序传送user和system参数,以上代码使用死循环模拟用户态挂死,调用sleep模拟内核态程序挂死。! @- s  v$ n1 {3 b
/ B1 A; L4 l: E! s+ R
+ H: D3 j2 N. v% o5 N
2.strace跟踪输出
8 n2 X$ x$ X1 A
' ~% S7 g0 w& \& |; z. d用户态挂死跟踪输出:4 R& ~8 L" a% L6 g7 R

3 f: E8 \8 a7 S5 ~9 S' X
  • 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$ r8 f( t9 Y: b

: H% D2 w; h$ K# K! L. M' ~4 l
! ]- |" C  M4 @* x( A( f" N- k内核态挂死跟踪输出:
; e( y. ^1 [; `  g8 l6 j" ^$ ^$ G- a, j! i' M) z# P, o
  • 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}," U1 G" Z: Z  O9 c& K

: b2 c0 w. o- \  A7 J
2 `: P4 w  J, V% a" L, k3.输出分析
$ Z2 y/ s6 J  w4 w1 ~, X. k) k+ p' B
用户态挂死情况下,strace在getpid()一行输出之后没有其他系统调用输出;进程在内核态挂死,最后一行的系统调用nanosleep不能完整显示,这里nanosleep没有返回值表示该调用尚未完成。$ I, O8 {, I: ~% \& ~: l

) h, D7 b4 e7 S. P' F% f& c7 t! I# U6 Y! ]! K0 u; R  t0 R
因而我们可以得出以下结论:使用strace跟踪挂死程序,如果最后一行系统调用显示完整,程序在逻辑代码处挂死;如果最后一行系统调用显示不完整,程序在该系统调用处挂死。
7 Q2 H0 \' \* Z* n* I( @3 o5 K& X3 J
1 S4 _8 e1 B, F- ]7 D5 t
当程序挂死在系统调用处,我们可以查看相应系统调用的man手册,了解在什么情况下该系统调用会出现挂死情况。另外,系统调用的参数也为我们提供了一些信息,例如挂死在如下系统调用:
4 l2 N/ n, ^8 \  x6 P6 E; v, L( g$ R9 }) ^( v- `; D$ b5 I
  • read(16,
    % L% r" N; x2 e0 l
. z/ Z# q' ?' B  M; ?% P+ r. n. b

" M/ P1 J2 Q: ^! t  ?那我们可以知道read函数正在对文件描述符为16的文件或socket进行读取,进一步地,我们可以使用lsof工具,获取对应于文件描述符为16的文件名、该文件被哪些进程占用等信息。
1 o: P  |' }8 m9 B6 m( G4 C9 v% q
; _" I+ K7 j8 z8 ^2 F7 M* N+ T1 H. h/ S, I2 P+ `
小结9 y, Q. d4 M1 c) ^3 P  B* L
) b* M2 E& t! H/ L7 K1 y$ j' U
本文对Linux中常用的问题诊断工具strace进行了介绍,通过程序示例,介绍了strace的使用方法、输出格式以及使用strace分析程序挂死问题的方法,另外对strace工具的几个常用选项进行了说明,描述了这几个选项适用的场景。
; D# r6 `' _4 [: G; z  y# C" d- a; ^& N7 O# P9 \
下次再遇到程序挂死、命令执行报错的问题,如果从程序日志和系统日志中看不出问题出现的原因,先别急着google或找高手帮忙,别忘了一个强大的工具它就在那里,不离不弃,strace一下吧!& y2 ^7 |" a+ [* I

该用户从未签到

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 18:51 , Processed in 0.234375 second(s), 24 queries , Gzip On.

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

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

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