关于性能监控工具以及网络分析工具,我们已经推出两篇文章进行了简单的介绍与分析,今天我们主要来介绍DC2服务器预装的系统调试工具,下面是今天的文章涉及的工具列表。
系统调试工具 | 功能介绍 |
---|---|
strace | 跟踪进程中的系统调用 |
lsof | 列出系统打开的文件 |
fuser | 报告进程使用的文件 |
pstree | 树状图的方式展现进程之间的派生问题 |
tree | 以树状图列出目录的内容 |
准备工作我们已经在前两篇文章中描述过了,下面我们直接使用DC2服务器进行实践。
在Linux中,进程不能直接访问硬件设备,当进程要读取磁盘文件、接受数据、缺页异常等操作时,必须由用户态切换到内核态,通过系统调用来访问硬件设备。strace可以跟踪进程执行的系统调用和所接受的信号,包括参数、返回值、执行消耗时间等。它的参数及其返回值将打印到标准错误输出或通过-o选项保存到指定文件。
关于strace的使用规则如下:
strace [-options] [PROG] ;
其中 -options表示可选参数,PROG代表要执行的程序
如下,我们使用strace命令,来跟踪cat 2.txt这个命令的执行。
[dc2-user@10-254-164-116 ~]$ ls
22.txt 2.txt ip.txt test
[dc2-user@10-254-164-116 ~]$ cat 2.txt
Hello didi
[dc2-user@10-254-164-116 ~]$ strace cat 2.txt
execve("/usr/bin/cat", ["cat", "2.txt"], [/* 21 vars */]) = 0
brk(NULL) = 0x13be000
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f5fb0dd5000
access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=31632, ...}) = 0
mmap(NULL, 31632, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f5fb0dcd000
close(3) = 0
write(1, "Hello didi\n", 11Hello didi
) = 11
read(3, "", 65536) = 0
···
跟踪信息的每一行都是一条系统调用,等号左面是系统调用的函数名及其参数(括号内的代表其参数),右面是该调用的返回值。上述命令有一条输出如下所示:
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
返回值为3代表成功打开该文件。若出现类似于:
open("/usr/share/locale/en/LC_MESSAGES/libc.mo", O_RDONLY) = -1 ENOENT (No such file or directory)
代表没有某文件或目录。
其他的系统调用如:mmap代表了为进程分配更多的内存;fstat返回文件系统保存的文件大小、最后修改、权限等各种信息;write将提供的缓冲区复制到内核空间,并返回实际写入的字节数等等
若想了解详细信息,可以仔细研究一下Linux的系统调用。
先写一个简单的小程序如下:
[dc2-user@10-254-164-116 ~]$ cat test.c
#include
int main()
{
int a;
printf("Hello didi");
scanf("%d", &a);
printf("%09d\n", a);
return 0;
}
通过DC2服务器预装软件gcc进行编译
[dc2-user@10-254-164-116 ~]$ vi test.c
[dc2-user@10-254-164-116 ~]$ gcc -o test test.c
若我们直接执行,结果如下:
[dc2-user@10-254-164-116 ~]$ ./test
Hello didi
8
000000008
然后我们用strace调用执行
[dc2-user@10-254-164-116 ~]$ strace ./test
execve("./test", ["./test"], [/* 21 vars */]) = 0
brk(NULL) = 0x1607000
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f2d1cbc1000
access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=31632, ...}) = 0
mmap(NULL, 31632, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f2d1cbb9000
close(3) = 0
open("/lib64/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\20\35\2\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=2127336, ...}) = 0
mmap(NULL, 3940800, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f2d1c5de000
mprotect(0x7f2d1c796000, 2097152, PROT_NONE) = 0
mmap(0x7f2d1c996000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1b8000) = 0x7f2d1c996000
mmap(0x7f2d1c99c000, 16832, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f2d1c99c000
close(3) = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f2d1cbb8000
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f2d1cbb6000
arch_prctl(ARCH_SET_FS, 0x7f2d1cbb6740) = 0
mprotect(0x7f2d1c996000, 16384, PROT_READ) = 0
mprotect(0x600000, 4096, PROT_READ) = 0
mprotect(0x7f2d1cbc2000, 4096, PROT_READ) = 0
munmap(0x7f2d1cbb9000, 31632) = 0
fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 0), ...}) = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f2d1cbc0000
fstat(0, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 0), ...}) = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f2d1cbbf000
write(1, "Hello didi", 10Hello didi) = 10
read(0, 8
"8\n", 1024) = 2
write(1, "000000008\n", 10000000008
) = 10
exit_group(0) = ?
+++ exited with 0 +++
从追踪的结果分析,系统首先调用了execve开始一个新的进程,接着进行环境的初始化操作。
write(1, "Hello didi", 10Hello didi) = 10
完成了程序中的输出。
read(0, 8
"8\n", 1024) = 2
write(1, "000000008\n", 10000000008
) = 10
read执行了我们程序中的scanf函数,并等待控制台输入,在输入了8之后,在调用write函数完成输出。最后通过exit_group(0)函数退出,完成整个程序的执行。
我们仍使用上面的test程序来观察进程接收信号的情况。
使用strace ./test来进行追踪,执行到read函数的时候不进行输入,打开另外一个窗口,输入如下命令:
killall test
此时,使用strace追踪的窗口将停止追踪,并返回如下信息:
read(0, 0x7f8b9b2a9000, 1024) = ? ERESTARTSYS (To be restarted if SA_RESTART is set)
--- SIGTERM {si_signo=SIGTERM, si_code=SI_USER, si_pid=21651, si_uid=1001} ---
+++ killed by SIGTERM +++
追踪记录中明确指出了test进程被杀死(killed by SIGTERM)
strace工具不仅可以跟踪系统调用,还可以将进程的所有调用做一个统计分析,这个过程可以通过strace 的-c参数来实现。
[dc2-user@10-254-164-116 ~]$ strace -c ./test
Hello didi
1
000000001
% time seconds usecs/call calls errors syscall
------ ----------- ----------- --------- --------- ----------------
33.54 0.000054 18 3 read
19.88 0.000032 8 4 fstat
13.66 0.000022 2 9 mmap
9.94 0.000016 4 4 mprotect
8.70 0.000014 7 2 write
4.97 0.000008 4 2 open
4.35 0.000007 7 1 munmap
2.48 0.000004 4 1 1 access
1.24 0.000002 1 2 close
0.62 0.000001 1 1 execve
0.62 0.000001 1 1 arch_prctl
0.00 0.000000 0 1 brk
------ ----------- ----------- --------- --------- ----------------
100.00 0.000161 31 1 total
参数-o用于将strace的结果输出到文件,若不指定-o参数,默认的输出设备是STDERR,具体如下:
strace -c -o test.txt ./test
strace -c ./test 2>test.txt
上面两个命令都是将结果输出到test.txt
strace的-T参数,可以完成对每个系统调用花费时间的统计并打印,结果显示在调用行最右面的尖括号内。
[dc2-user@10-254-164-116 ~]$ strace -T ./test
execve("./test", ["./test"], [/* 21 vars */]) = 0 <0.000241>
brk(NULL) = 0x1b57000 <0.000066>
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7ff21fb0a000 <0.000069>
access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory) <0.000070>
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3 <0.000070>
fstat(3, {st_mode=S_IFREG|0644, st_size=31632, ...}) = 0 <0.000044>
mmap(NULL, 31632, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7ff21fb02000 <0.000062>
close(3) = 0 <0.000043>
open("/lib64/libc.so.6", O_RDONLY|O_CLOEXEC) = 3 <0.000055>
···
关于strace的时间参数,还有-t -tt -ttt,示例如下
[dc2-user@10-254-164-116 ~]$ strace -t ./test
17:23:47 execve("./test", ["./test"], [/* 21 vars */]) = 0
···
[dc2-user@10-254-164-116 ~]$ strace -tt ./test
17:25:26.036275 execve("./test", ["./test"], [/* 21 vars */]) = 0
···
[dc2-user@10-254-164-116 ~]$ strace -ttt ./test
1532424368.126783 execve("./test", ["./test"], [/* 21 vars */]) = 0
···
-t参数将输出结果精确到秒,-tt参数将结果精确到微秒,-ttt将结果精确到微秒,并且时间为unix时间戳。
[dc2-user@10-254-164-116 ~]$ strace -e trace=all -p 21159
-p参数跟踪21159进程,-e trace=all 代表追踪所有系统调用。
lsof(list open files)是一个列出当前系统打开文件的工具。在Linux环境下一切皆文件,通过文件不仅可以访问常规文件,还可以访问网络连接与硬件。常规文件可以通过ls来查看信息,但是如需要查看其他类型的文件,可以使用lsof命令。
lsof的命令格式如下:
lsof [-options] [file]
无参数使用方式如下:
[dc2-user@10-254-164-116 ~]$ lsof
COMMAND PID TID USER FD TYPE DEVICE SIZE/OFF NODE NAME
systemd 1 root cwd unknown /proc/1/cwd (readlink: Permission denied)
systemd 1 root rtd unknown /proc/1/root (readlink: Permission denied)
systemd 1 root txt unknown /proc/1/exe (readlink: Permission denied)
systemd 1 root NOFD /proc/1/fd (opendir: Permission denied)
kthreadd 2 root cwd unknown /proc/2/cwd (readlink: Permission denied)
...
lsof 需要访问核心内存和各种文件,所以使用root用户执行,才能发挥用处,否则结果如上所示会出现Permission denied。
切换root权限之后示例如下:
[dc2-user@10-254-164-116 ~]$ sudo lsof | grep bash
bash 31808 dc2-user cwd DIR 253,1 195 4268530 /home/dc2-user
bash 31808 dc2-user rtd DIR 253,1 244 64 /
bash 31808 dc2-user txt REG 253,1 960608 56164 /usr/bin/bash
bash 31808 dc2-user mem REG 253,1 106070960 8592900 /usr/lib/locale/locale-archive
bash 31808 dc2-user mem REG 253,1 62184 4198801 /usr/lib64/libnss_files-2.17.so
lsof输出各列信息的意义如下:
[root@10-254-164-116 dc2-user]# lsof /bin/bash
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
bash 31808 dc2-user txt REG 253,1 960608 56164 /usr/bin/bash
bash 31996 root txt REG 253,1 960608 56164 /usr/bin/bash
lsof的-u参数后面加用户名,可以列出该用户打开的文件,下面展示的是部分结果。
[root@10-254-164-116 dc2-user]# lsof -u dc2-user
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
sshd 31807 dc2-user cwd DIR 253,1 244 64 /
sshd 31807 dc2-user rtd DIR 253,1 244 64 /
sshd 31807 dc2-user txt REG 253,1 853024 4195340 /usr/sbin/sshd
sshd 31807 dc2-user mem REG 253,1 526992 4305530 /usr/lib64/libfreeblpriv3.so
sshd 31807 dc2-user mem REG 253,1 15480 12686315 /usr/lib64/security/pam_lastlog.so
...
[root@10-254-164-116 dc2-user]# lsof -p 31995
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
su 31995 root cwd DIR 253,1 244 64 /
su 31995 root rtd DIR 253,1 244 64 /
su 31995 root txt REG 253,1 32096 315309 /usr/bin/su
su 31995 root mem REG 253,1 19752 12687086 /usr/lib64/security/pam_xauth.so
...
[root@10-254-164-116 dc2-user]# lsof -g 1
COMMAND PID PGID USER FD TYPE DEVICE SIZE/OFF NODE NAME
systemd 1 1 root cwd DIR 253,1 244 64 /
systemd 1 1 root rtd DIR 253,1 244 64 /
systemd 1 1 root txt REG 253,1 1523584 8465034 /usr/lib/systemd/systemd
lsof 的-i参数可以查看所有的网络连接
[root@10-254-164-116 dc2-user]# lsof -i
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
systemd 1 root 35u IPv6 13919 0t0 TCP *:sunrpc (LISTEN)
chronyd 523 chrony 1u IPv4 14166 0t0 UDP localhost:323
master 912 root 13u IPv4 15897 0t0 TCP localhost:smtp (LISTEN)
master 912 root 14u IPv6 15898 0t0 TCP localhost:smtp (LISTEN)
sshd 951 root 3u IPv4 16458 0t0 TCP *:ssh (LISTEN)
sshd 951 root 4u IPv6 16460 0t0 TCP *:ssh (LISTEN)
rpcbind 4794 rpc 11u IPv6 104018 0t0 UDP *:netviewdm1
sshd 31805 root 3u IPv4 3932058 0t0 TCP 10-254-164-116:ssh->111.202.166.3:31060 (ESTABLISHED)
sshd 31807 dc2-user 3u IPv4 3932058 0t0 TCP 10-254-164-116:ssh->111.202.166.3:31060 (ESTABLISHED)
lsof也支持指定所要查看的网络连接类型,示例如下:
//查看所有tcp连接
[root@10-254-164-116 dc2-user]# lsof -i tcp
//查看所有udp连接
[root@10-254-164-116 dc2-user]# lsof -i udp
-i参数 还可以指定端口号,表示使用该端口的文件
[root@10-254-164-116 dc2-user]# lsof -i :323
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
chronyd 523 chrony 1u IPv4 14166 0t0 UDP localhost:323
chronyd 523 chrony 2u IPv6 14167 0t0 UDP localhost:323
我们通过使用之前介绍的网络工具nc,连接本台DC2服务器,再使用-i参数查看主机连接情况
[root@10-254-164-116 dc2-user]# lsof -i @10.254.96.249
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
nc 32745 root 3u IPv4 3961703 0t0 TCP 10-254-164-116:58898->10.254.96.249:ftp-data (ESTABLISHED)
[root@10-254-164-116 dc2-user]# lsof -d txt
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
systemd 1 root txt REG 253,1 1523584 8465034 /usr/lib/systemd/systemd
kthreadd 2 root txt unknown /proc/2/exe
ksoftirqd 3 root txt unknown /proc/3/exe
kworker/0 5 root txt unknown /proc/5/exe
fuser命令用于报告使用某指定文件或目录的进程信息。对于阻塞特别设备,此命令列出了使用该设备上任何文件的进程。
fuser的命令格式如下:
fuser [-options] [file] [dir]
[dc2-user@10-254-164-116 ~]$ fuser hello.py
fuser的-k参数可以杀死访问指定文件的所有进程;-u参数可以为本地进程提供登录名,下面结果中得圆括号内的即为进程的登录名;-c参数表示包含该文件的文件系统中关于所有打开的文件的报告。
[dc2-user@10-254-164-116 ~]$ fuser -kuc /home
/home: 12278rce(dc2-user) 12567rce(dc2-user)
Connection closing...Socket close.
Connection closed by foreign host.
Disconnected from remote host(116.85.255.187:22) at 19:55:28.
fuser的-m,–mount参数显示所有正在使用文件的进程
[dc2-user@10-254-164-116 ~]$ fuser -m hello.py
/home/dc2-user/hello.py: 12729rce
我们从上可以观察到每个进程号后面连接了字母,该字母表示了进程如何使用文件。如
* c 将此文件作为当前目录使用
* e 将此文件作为程序的可执行对象使用
* r 将此文件作为根目录使用
* s 将此文件作为共享库(或其他可装入对象)使用。
fuser还有其他的参数如下:
* -s 静默模式,这时候-u,-v会被忽略,-a不能和-s一起使用
* -v 详细模式。输出似ps命令的输出
* -n space 指定一个不同的命名空间(space).这里支持不同的空间文件
* -l 列出所有已知的信号名称。
这些参数的用法可以在使用我们的DC2服务器的过程中进行实验。
pstree命令可以以树状图的方式展现进程之间的派生关系,显示效果比较直观,他的命令格式如下:
pstree [-options]
直接无参数的使用pstree命令就可以直观地看到进程之间的派生关系
[dc2-user@10-254-164-116 ~]$ pstree
systemd─┬─2*[agetty]
├─chronyd
├─crond
├─dbus-daemon
├─dhclient
├─gssproxy───5*[{gssproxy}]
├─master─┬─pickup
│ └─qmgr
├─polkitd───5*[{polkitd}]
├─rpcbind
├─rsyslogd───2*[{rsyslogd}]
├─sshd─┬─sshd───sshd───bash───pstree
│ └─sshd───sshd
├─systemd-journal
├─systemd-logind
├─systemd-udevd
└─tuned───4*[{tuned}]
-H参数可以在列出树状图时,特别标明现在执行的程序,但是需要指出进程号。我们切换至root权限,并查看当前进程的进程号。
[root@10-254-164-116 dc2-user]# ps
PID TTY TIME CMD
16978 pts/0 00:00:00 sudo
16979 pts/0 00:00:00 su
16980 pts/0 00:00:00 bash
16991 pts/0 00:00:00 ps
再根据su的进程号为16979,查询树状图,从根目录到su的支线会高亮显示。
[root@10-254-164-116 dc2-user]# pstree -H 16979
systemd─┬─2*[agetty]
├─chronyd
├─crond
├─dbus-daemon
├─dhclient
├─gssproxy───5*[{gssproxy}]
├─master─┬─cleanup
│ ├─local
│ ├─pickup
│ ├─qmgr
│ └─trivial-rewrite
├─polkitd───5*[{polkitd}]
├─rpcbind
├─rsyslogd───2*[{rsyslogd}]
├─sshd─┬─sshd───sshd───bash───sudo───su───bash───pstree
│ └─sshd───sshd
├─systemd-journal
├─systemd-logind
├─systemd-udevd
└─tuned───4*[{tuned}]
pstree有很多参数,可以显示不同内容,下面进行简单介绍。
1. -a可以显示每个程序的完整指令,包含路径、参数或常驻服务标示,截取输出的一部分如下:
├─dhclient -1 -q -lf /var/lib/dhclient/dhclient--eth0.lease -pf/var/run/dhclient-eth0.pi
输出的就是dhclient的完整指令;
├─gssproxy───5*[{gssproxy}]
而使用-c参数的表示方式如下:
├─gssproxy─┬─{gssproxy}
├─{gssproxy}
├─{gssproxy}
├─{gssproxy}
└─{gssproxy}
-h参数在输出时当前正在执行的程序会高亮显示;
-n参数用程序识别码排序。预设是以程序名称来排序;
-p参数显示程序识别码如下:
├─sshd(951)─┬─sshd(15335)───sshd(15349)───bash(15350)───pstree(15756)
├─sshd─┬─sshd───sshd(dc2-user)───bash───pstree
-U参数使用UTF-8列绘图字符;
-G参数使用VT100终端机的列绘图字符;
tree命令用于以树状图列出目录的内容。执行tree指令,它会列出指定目录下的所有文件,包括子目录里的文件。
tree的命令格式如下:
tree [-options]
[dc2-user@10-254-164-116 ~]$ tree
.
├── 22.txt
├── 2.txt
├── a.out
├── dev
│ ├── 1
│ │ └── 1.txt
│ └── 2.txt
├── didi
├── hello.py
├── ip.txt
├── P
├── test
├── test1
├── test.c
├── test.txt
└── y
可以使用更多的参数来以不同的方式显示文件目录。如-C可以为目录清单加入色彩,以便于区分;-d只显示目录名称而不显示内容等等。更多的tree的参数使用可以查看–help查看。
以上就是我们系统工具的简单介绍,Linux命令的强大之处还需要我们的实践,欢迎使用DC2服务器进行更多的实践。