前言
6 A" p4 s2 n& X" }6 a
0 ~6 E6 @" F) l/ T3 {3 U; tLinux常用命令中有一些命令可以在开发或调试过程中起到很好的帮助作用,有些可以帮助了解或优化我们的程序,有些可以帮我们定位疑难问题。本文将简单介绍一下这些命令。7 W0 W: t( h1 D u
示例程序
# o' N. A7 z; @& x/ v
& ~: C, G ~" D$ F! r我们用一个小程序,来帮助后面我们对这些命令的描述,程序清单cmdTest.c如下: F8 r6 a3 |. H+ c: o
#include<stdio.h>
9 Y1 a' k$ M$ vint test(int a,int b)5 p4 O) d( Y& A
{# Y" e$ O1 s* ?8 s
return a/b;8 m7 ^: X! P( y5 k
}
" A( j M/ Q/ M A7 i: t# Zint main(int argc,char *argv[])7 K+ P8 C$ R% u6 I: m( @: Y( j$ t
{
}3 Z7 i3 U+ t# L6 u G int a = 10;4 c8 L+ ]" }* s# r. P5 x) ~ M
int b = 0;; R5 R3 L8 z, a1 q
printf("a=%d,b=%d\n",a,b);
" u1 ~1 s0 |! M$ ] test(a,b);( f8 `9 ]- j0 v9 X- \
return 0;
; o$ M9 {! Z# h) a3 X}
' ]0 } l9 _9 a编译获得elf文件cmdTest并运行:
# l; l; T) g, `* Ugcc -g -o cmdTest cmdTest.c
/ F9 y1 o0 X1 q; _4 h8 X3 S./cmdTest
. l$ a5 T+ r8 ^4 c3 L9 t, Sa=10,b=0
% y* b. U+ c% V% ` MFloating point exception (core dumped)6 H) n/ ?$ C' \' g: D: S" R
程序内容是在main函数中调用test,计算a/b的值,其中b的值为0,因此程序由于除0错误异常终止。3 a$ x; ~& ?! a5 o
查看文件基本信息--file, |2 ^8 N) ?' [! o0 `% Y: G( S- l9 m
8 J0 o8 j8 r. W# Bfile cmdTest/ u- k2 I- N: `$ B3 Z
cmdTest: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.24, BuildID[sha1]=448e1c34b4c548120e2c04f6a2bfce4e6d2281a3, not stripped
f; a C. g7 h% n通过file命令可以看到cmdTest的类型为elf,是64位、运行于x86-64的程序,not striped表明elf文件中还保留着符号信息以及调试信息等不影响程序运行的内容。* ?8 C: N" D4 N. V/ }: W* C& K. V
查看程序依赖库--ldd
3 ~7 H' g( g3 _7 x! X" S# N: F
% e- L! r( P- Q8 C6 T3 v2 M& bldd cmdTest
2 R7 U: H3 M0 I8 K+ v linux-vdso.so.1 => (0x00007ffc8e548000)) Y# n( [! V% \) u
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f0621931000)
7 E3 l" b5 G$ v% V /lib64/ld-linux-x86-64.so.2 (0x00007f0621cf6000)+ f: w: C8 a+ M1 }* X9 n
我们可以看到cmdTest依赖了libc.so等库。* ~1 Z8 T2 A; z; C
查看函数或者全局变量是否存在于elf文件中--nm
! z# m6 ?: |5 O* w7 k
# j' T1 r, L% E* ~5 E) \. Gnm命令用于查看elf文件的符号信息。文件编译出来之后,我们可能不知道新增加的函数或者全局变量是否已经成功编译进去。这时候,我们可以使用nm命令来查看。% p# |# {8 ^$ y$ a! }. ?/ P
例如,查看前面所提到的elf文件有没有test函数,可以用命令:
( D* z+ H. e# \% Y: {0 |6 H% Pnm cmdTest|grep test
* F* L, R; s, ~& }+ f8 u9 r000000000040052d T test #打印结果! M2 x A$ v D) P+ A& Z. h& H/ c
按照地址顺序列出符号信息:4 `3 }8 Y) m9 S- ]
nm -n cmdTest
* ?& ]* i9 @9 d! D4 j$ P w _ITM_deregisterTMCloneTable7 f" E) }) G- z/ H* y
w _ITM_registerTMCloneTable' O4 n. N# \! c; i E9 M% W4 r
w _Jv_RegisterClasses' S7 Y! a. T V6 U, W8 D6 z+ j' M
w __gmon_start__
- f1 x& F+ D' @' R" O U __libc_start_main@@GLIBC_2.2.52 r. O! Q) I6 H; t4 d
U printf@@GLIBC_2.2.5, p8 N* J) h% G
00000000004003e0 T _init
+ n Z1 b; z" M) ?4 i2 t0000000000400440 T _start1 ]" Q0 }: {, C
0000000000400470 t deregister_tm_clones: ~% u" O) ^# u( n7 G
00000000004004a0 t register_tm_clones
) i) `* |5 M0 M/ h00000000004004e0 t __do_global_dtors_aux% w: V8 f k! q5 S6 ]
0000000000400500 t frame_dummy
5 }! s! y/ i8 ? G3 Z' _7 b: u000000000040052d T test0 y6 }5 v# o0 O) v* ?8 K
0000000000400540 T main2 F, I# w, T* H! l. E; y1 C
0000000000400590 T __libc_csu_init3 R- S' P. W' s7 v" \" A6 h: t
0000000000400600 T __libc_csu_fini
7 Z: q: n2 J; K4 {$ h$ w(列出部分内容)
`1 M$ v7 y$ X3 A5 A4 n3 g/ h- S可以看到test函数的开始地址为0x000000000040052d,结束地址为0x0000000000400540。' v. L6 U3 O" a" a8 J4 H, k
打印elf文件中的可打印字符串--strings
1 R; s m8 q' }% o. D3 q. \/ A A5 s6 f
例如你在代码中存储了一个版本号信息,那么即使编译成elf文件后,仍然可以通过strings搜索其中的字符串甚至可以搜索某个.c文件是否编译在其中: Z/ A5 z. A0 ~/ q/ A
strings elfFile| grep "someString". m$ o; L" n8 t& e) y: J: H* s7 J
查看文件段大小--size$ X; w- E9 G( B6 d8 q
( z8 I$ ~% Z! |" H* u' N. d0 G- w可以通过size命令查看各段大小:
_+ U' W9 @# L- s% G3 g, s" Fsize cmdTest- M. `5 s# l8 d' N5 k
text data bss dec hex filename7 G( Y( O8 U# u( k& H) V
1319 560 8 1887 75f cmdTest- d( S5 I: D5 H% y3 x# o0 U5 C
text段:正文段字节数大小
; J% u! \2 G' gdata段:包含静态变量和已经初始化的全局变量的数据段字节数大小
; R: ^* k# L- @ \) w) mbss段:存放程序中未初始化的全局变量的字节数大小
) W- q3 B) F6 K1 V7 ^. x, `当我们知道各个段的大小之后,如果有减小程序大小的需求,就可以有针对性的对elf文件进行优化处理。 K" C! l% D A8 p) h% c, h6 W
为elf文件”瘦身“--strip: o* { R% ~( x/ c( F) @
& u* b9 C" c, m+ \& O9 w
strip用于去掉elf文件中所有的符号信息:0 `% R+ \" t' \ M( M4 [' W/ s
ls -al cmdTest3 y8 D* ^9 T \- v. V
-rwxr-xr-x 1 hyb root 9792 Sep 25 20:30 cmdTest #总大小为9792字节 J2 E5 f8 U2 S* U u. f; z0 u
strip cmdTest. n- k' i4 u0 \1 U6 D$ ^
ls -al cmdTest" x. _* w- v6 V6 d0 v3 l
-rwxr-xr-x 1 hyb root 6248 Sep 25 20:35 cmdTest#strip之后大小为6248字节
# E! e' | U' }/ M: o可以看到,“瘦身”之后,大小减少将近三分之一。但是要特别注意的是,“瘦身”之后的elf文件由于没有了符号信息,许多调试命令将无法正常使用,出现core dump时,问题也较难定位,因此只建议在正式发布时对其进行“瘦身”。
7 ^9 f# H0 g7 O* F查看elf文件信息--readelf
, w, u* v# e8 T9 p# t- h
9 Q; o% R- I: ]readelf用于查看elf文件信息,它可以查看各段信息,符号信息等,下面的例子是查看elf文件头信息:
. a( V/ F1 {: o1 F8 nreadelf -h cmdTest
4 m1 }0 m Q7 w( d4 @/ s Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 #elf文件魔数字
. t* [) V" L9 n) o* n7 c& E Class: ELF64 #64位 elf文件/ L7 x5 \: i; f- d+ r6 S: t" G1 y8 X
Data: 2's complement, little endian#字节序为小端序# D- s# s/ M/ ~; }7 I1 q
Version: 1 (current)
" |5 M0 L( s. Z% L OS/ABI: UNIX - System V #: N6 Z1 ^4 a( l
ABI Version: 0
. O! R7 Q) H$ {, M+ V" Z! J- d Type: EXEC (Executable file)#目标文件类型5 q r$ L( g: c
Machine: Advanced Micro Devices X86-64 #目标处理器体系9 R5 \1 G; D8 k( X
Version: 0x1
6 i5 x9 e, S) _% a! C) V! ] Entry point address: 0x400440 #入口地址/ s1 g$ e- q' a# D- r
Start of program headers: 64 (bytes into file)$ L+ \: l `' _$ F) e, X5 u' D
Start of section headers: 4456 (bytes into file)
9 `9 A" C0 P! b" {+ v Flags: 0x0/ [! W. a& G* h7 o9 V4 p
Size of this header: 64 (bytes)2 {$ f8 k* n' s3 K
Size of program headers: 56 (bytes)5 W2 \+ [$ e" F! B( \5 X4 f
Number of program headers: 9& g& _5 `. b' ^; ]+ E1 G
Size of section headers: 64 (bytes)
' L- v# a5 g w) x' e Number of section headers: 28
1 h7 M" u$ H5 R; ^$ g Section header string table index: 27
/ m& {# k% q }从elf头信息中,我们可以知道该elf是64位可执行文件,运行在x86-64中,且字节序为小端序。另外,我们还注意到它的入口地址是0x400440(_start),而不是400540(main)。也就是说,我们的程序运行并非从main开始。
8 o6 |! W c6 H( r反汇编指定函数--objdump
4 k( |% y1 J( e, @( D, x6 n% F" E) Q! w! I. e3 \
objdump用于展示elf文件信息,功能较多,在此不逐一介绍。有时候我们需要反汇编来定位一些问题,可以使用命令:8 `( v, F" T1 V F
objdump -d cmdTest #反汇编整个cmdTest程序* K8 F5 _7 ^ Y% }8 O9 Q
但是如果程序较大,那么反汇编时间将会变长,而且反汇编文件也会很大。如果我们已经知道了问题在某个函数,只想反汇编某一个函数,怎么处理呢?- \+ S2 ]8 k+ h' `7 A5 ^2 l- y
我们可以利用前面介绍的nm命令获取到函数test的地址,然后使用下面的方式反汇编:7 @2 C& r( q* ~* Y( p _% W
objdump -d cmdTest --start-address=0x40052d --stop-address=0x400540 ##反汇编指定地址区间: S% Z" J9 N7 r) P5 s
端口占用情况查看--netstat' \/ V* M {7 z* j; L: j4 C1 X4 N
& E6 d& L: I3 u. c" v+ v/ k6 j/ c
我们可能常常会遇到进程第一次启动后,再次启动会出现端口绑定失败的问题,我们可以通过netstat命令查看端口占用情况:
$ x* c& o# l* J6 \netstat -anp|grep 端口号
: u+ g$ e# K' ~# x进程状态查看--ps&top
5 Y6 L# i% D* F7 W0 p( V& @3 T# I/ C, C/ G' L
ps命令的用法可以参考ps命令常见实用用法。. `7 b8 g5 K8 P/ J0 X" Z% L
top命令实时显示当前进程状态,最活跃的进程显示在最顶部。% j. g% a X! {0 h
core dump文件生成配置--ulimit -c
! m3 Q7 g- ~; i! Y
1 L" W0 D- f7 K& r1 V1 O2 P有时候我们的程序core dump了却没有生成core文件,很可能是我们设置的问题:6 d; I* I$ N6 S" J; I- R' X# i: Z( j
ulimit -c #查看core文件配置,如果结果为0,程序core dump时将不会生成core文件
+ W% Q" }% G+ ?; ?( @/ b- ]6 tulimit -c unlimited #不限制core文件生成大小
- y* \: s* E+ Q# Rulimit -c 10 #设置最大生成大小为10kb1 j5 j) @7 E+ _: x( M
调试神器--gdb
) d2 s) e) O6 o6 {* ?% ~; a9 k/ d5 ?4 j# M5 a6 s
gdb是一个强大的调试工具,但这里仅介绍两个简单使用示例。
d' o' M! Z$ Z$ n6 _' l. s% k/ R& a& d有时候程序可能已经正在运行,但是又不能终止它,这时候仍然可以使用gdb调试正在运行的进程:$ E3 L5 k$ H N* ^' X; x
gdb processFile PID #processFile为进程文件,pid为进程id,可通过ps命令查找到
# A: F7 x) ~7 Y4 o+ F有时候程序可能core dump了,但是系统还留给了我们一个礼物--core文件。+ I" C( J# e: {- y
在core文件生成配置完成之后,运行cmdTest程序,产生core文件。我们可以用下面的方法通过core文件定位出错位置:
- _4 T' Y/ i3 t. ]) J4 S+ I" jgdb cmdTest core #processFile为进程文件,core为生成的core文件
, \! i/ [( M0 B# m( Q6 ~% N4 Y6 UCore was generated by `./cmdTest'.( I) ^$ q" l; C8 n
Program terminated with signal SIGFPE, Arithmetic exception.; Q) w" H8 N, g; Z+ U
#0 0x00000000004004fb in test (a=10, b=0) at cmdTest.c:4
! ^3 M- |/ j" h! H( C4 return a/b;, e, G o. D1 W& z3 x" z; v
(gdb)bt
6 }% ?! N" \, u. z8 ~' o( x ^* b#0 0x00000000004004fb in test (a=10, b=0) at cmdTest.c:4
D9 c. U0 r8 l7 E" f/ T4 V- p#1 0x000000000040052c in main (argc=1, argv=0x7ffca9536d38) at cmdTest.c:10
/ T" @9 x! w6 n(gdb)
3 j; h) i: [" V8 T, u输入bt后,就可以看到调用栈了,出错位置在test函数,cmdTest.c的第4行。
4 C" \/ i, U* ~定位crash问题--addr2line
. |6 x/ H6 Z3 U+ P" S* x' q
) d7 J- I; e# z& f3 o# [$ l3 g有时候程序崩溃了但不幸没有生成core文件,是不是就完全没有办法了呢?还是cmdTest的例子。运行完cmdTest之后,我们通过dmesg命令可以获取到以下内容* D9 l8 t/ Q- U) A* B m5 ^
[27153070.538380] traps: cmdTest[2836] trap divide error ip:40053b sp:7ffc230d9280 error:0 in cmdTest[400000+1000]
+ U5 o, y- ^8 R7 n1 r; c8 q8 ~该信息记录了cmdTest运行出错的基本原因(divide error)和出错位置(40053b),我们使用addr2line命令获取出错具体行号:; V1 H# f4 v' r/ [& c" }) p- Q* p
addr2line -e cmdTest 40053b
8 i! \3 n8 ~4 g- f' u/home/hyb/practice/cmdTest.c:49 l3 A3 y3 Y; J6 P
可以看到addr2line命令将地址(40053b)翻译成了文件名(cmdTest.c)和行号(4),确定了出错位置。
9 B* }! ]) t* H2 x3 F* p. v总结
+ p2 V* {8 i' ~, K( h- Q9 H6 h) b$ J7 ~$ ?
本文对以上命令仅介绍其经典使用,这些命令都还有其他一些有帮助的用法,但由于篇幅有限,不在此介绍,更多使用方法可以通过man 命令名的方式去了解。
|