17 跟踪工具 #
SUSE Linux Enterprise Server 随附有多个工具,可帮助您获取有关您的系统的有用信息。您可将这些信息用于各种目的,例如,调试和查找程序中的问题、发现性能下降的原因,或者跟踪运行中的进程以确定它使用了哪些系统资源。大部分工具已包含在安装媒体中。某些情况下,需要通过单独下载的 SUSE Software Development Kit 安装这些工具。
监视运行中进程的系统调用或库调用时,该进程的性能将严重下降。建议您仅当需要收集数据时才使用跟踪工具。
17.1 使用 strace 跟踪系统调用 #
strace
命令可以跟踪进程的系统调用以及进程收到的信号。strace
可以运行新命令并跟踪其系统调用,或者您可以将 strace
附加到已运行的命令。命令的每行输出都包含系统调用名称,后接其参数(括在括号中)及其返回值。
要运行新命令并开始跟踪其系统调用,请如常输入要监视的命令,然后在命令行的开头添加 strace
:
>
strace ls
execve("/bin/ls", ["ls"], [/* 52 vars */]) = 0
brk(0) = 0x618000
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) \
= 0x7f9848667000
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) \
= 0x7f9848666000
access("/etc/ld.so.preload", R_OK) = -1 ENOENT \
(No such file or directory)
open("/etc/ld.so.cache", O_RDONLY) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=200411, ...}) = 0
mmap(NULL, 200411, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f9848635000
close(3) = 0
open("/lib64/librt.so.1", O_RDONLY) = 3
[...]
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) \
= 0x7fd780f79000
write(1, "Desktop\nDocuments\nbin\ninst-sys\n", 31Desktop
Documents
bin
inst-sys
) = 31
close(1) = 0
munmap(0x7fd780f79000, 4096) = 0
close(2) = 0
exit_group(0) = ?
要将 strace
附加到已运行的进程,需要指定 -p
并添加您要监视的进程的进程 ID (PID
):
>
strace -p `pidof cron`
Process 1261 attached
restart_syscall(<... resuming interrupted call ...>) = 0
stat("/etc/localtime", {st_mode=S_IFREG|0644, st_size=2309, ...}) = 0
select(5, [4], NULL, NULL, {0, 0}) = 0 (Timeout)
socket(PF_LOCAL, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0) = 5
connect(5, {sa_family=AF_LOCAL, sun_path="/var/run/nscd/socket"}, 110) = 0
sendto(5, "\2\0\0\0\0\0\0\0\5\0\0\0root\0", 17, MSG_NOSIGNAL, NULL, 0) = 17
poll([{fd=5, events=POLLIN|POLLERR|POLLHUP}], 1, 5000) = 1 ([{fd=5, revents=POLLIN|POLLHUP}])
read(5, "\2\0\0\0\1\0\0\0\5\0\0\0\2\0\0\0\0\0\0\0\0\0\0\0\5\0\0\0\6\0\0\0"..., 36) = 36
read(5, "root\0x\0root\0/root\0/bin/bash\0", 28) = 28
close(5) = 0
rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0
rt_sigaction(SIGCHLD, NULL, {0x7f772b9ea890, [], SA_RESTORER|SA_RESTART, 0x7f772adf7880}, 8) = 0
rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
nanosleep({60, 0}, 0x7fff87d8c580) = 0
stat("/etc/localtime", {st_mode=S_IFREG|0644, st_size=2309, ...}) = 0
select(5, [4], NULL, NULL, {0, 0}) = 0 (Timeout)
socket(PF_LOCAL, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0) = 5
connect(5, {sa_family=AF_LOCAL, sun_path="/var/run/nscd/socket"}, 110) = 0
sendto(5, "\2\0\0\0\0\0\0\0\5\0\0\0root\0", 17, MSG_NOSIGNAL, NULL, 0) = 17
poll([{fd=5, events=POLLIN|POLLERR|POLLHUP}], 1, 5000) = 1 ([{fd=5, revents=POLLIN|POLLHUP}])
read(5, "\2\0\0\0\1\0\0\0\5\0\0\0\2\0\0\0\0\0\0\0\0\0\0\0\5\0\0\0\6\0\0\0"..., 36) = 36
read(5, "root\0x\0root\0/root\0/bin/bash\0", 28) = 28
close(5)
[...]
-e
选项可识别多个子选项和参数。例如,要跟踪打开或写入特定文件的所有尝试,请使用以下命令:
>
strace -e trace=open,write ls ~
open("/etc/ld.so.cache", O_RDONLY) = 3
open("/lib64/librt.so.1", O_RDONLY) = 3
open("/lib64/libselinux.so.1", O_RDONLY) = 3
open("/lib64/libacl.so.1", O_RDONLY) = 3
open("/lib64/libc.so.6", O_RDONLY) = 3
open("/lib64/libpthread.so.0", O_RDONLY) = 3
[...]
open("/usr/lib/locale/cs_CZ.utf8/LC_CTYPE", O_RDONLY) = 3
open(".", O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC) = 3
write(1, "addressbook.db.bak\nbin\ncxoffice\n"..., 311) = 311
要仅跟踪网络相关的系统调用,请使用 -e
trace=network
:
>
strace -e trace=network -p 26520
Process 26520 attached - interrupt to quit
socket(PF_NETLINK, SOCK_RAW, 0) = 50
bind(50, {sa_family=AF_NETLINK, pid=0, groups=00000000}, 12) = 0
getsockname(50, {sa_family=AF_NETLINK, pid=26520, groups=00000000}, \
[12]) = 0
sendto(50, "\24\0\0\0\26\0\1\3~p\315K\0\0\0\0\0\0\0\0", 20, 0,
{sa_family=AF_NETLINK, pid=0, groups=00000000}, 12) = 20
[...]
-c
用于计算内核花费在每个系统调用上的时间:
>
strace -c find /etc -name xorg.conf
/etc/X11/xorg.conf
% time seconds usecs/call calls errors syscall
------ ----------- ----------- --------- --------- ----------------
32.38 0.000181 181 1 execve
22.00 0.000123 0 576 getdents64
19.50 0.000109 0 917 31 open
19.14 0.000107 0 888 close
4.11 0.000023 2 10 mprotect
0.00 0.000000 0 1 write
[...]
0.00 0.000000 0 1 getrlimit
0.00 0.000000 0 1 arch_prctl
0.00 0.000000 0 3 1 futex
0.00 0.000000 0 1 set_tid_address
0.00 0.000000 0 4 fadvise64
0.00 0.000000 0 1 set_robust_list
------ ----------- ----------- --------- --------- ----------------
100.00 0.000559 3633 33 total
要跟踪某个进程的所有子进程,请使用 -f
:
>
strace -f systemctl status apache2.service
execve("/usr/bin/systemctl", ["systemctl", "status", "apache2.service"], \
0x7ffea44a3318 /* 56 vars */) = 0
brk(NULL) = 0x5560f664a000
[...]
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f98c58a5000
mmap(NULL, 4420544, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f98c524a000
mprotect(0x7f98c53f4000, 2097152, PROT_NONE) = 0
[...]
[pid 9130] read(0, "\342\227\217 apache2.service - The Apache"..., 8192) = 165
[pid 9130] read(0, "", 8027) = 0
● apache2.service - The Apache Webserver227\217 apache2.service - Th"..., 193
Loaded: loaded (/usr/lib/systemd/system/apache2.service; disabled; vendor preset: disabled)
Active: inactive (dead)
) = 193
[pid 9130] ioctl(3, SNDCTL_TMR_STOP or TCSETSW, {B38400 opost isig icanon echo ...}) = 0
[pid 9130] exit_group(0) = ?
[pid 9130] +++ exited with 0 +++
<... waitid resumed>{si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=9130, \
si_uid=0, si_status=0, si_utime=0, si_stime=0}, WEXITED, NULL) = 0
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=9130, si_uid=0, \
si_status=0, si_utime=0, si_stime=0} ---
exit_group(3) = ?
+++ exited with 3 +++
如果您需要分析 strace
的输出,但输出消息太长,以致无法在控制台窗口中直接检查,请使用 -o
。在这种情况下,将会隐藏不需要的消息,例如有关附加和分离进程的信息。您还可以使用 -q
隐藏这些消息(正常情况下会列显在标准输出中)。要在系统调用的每行开头添加时戳,请使用 -t
:
>
strace -t -o strace_sleep.txt sleep 1; more strace_sleep.txt
08:44:06 execve("/bin/sleep", ["sleep", "1"], [/* 81 vars */]) = 0
08:44:06 brk(0) = 0x606000
08:44:06 mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, \
-1, 0) = 0x7f8e78cc5000
[...]
08:44:06 close(3) = 0
08:44:06 nanosleep({1, 0}, NULL) = 0
08:44:07 close(1) = 0
08:44:07 close(2) = 0
08:44:07 exit_group(0) = ?
可以在很大程度上控制 strace 的行为和输出格式。有关详细信息,请参见相关手册页 (man 1 strace)。
17.2 使用 ltrace 跟踪库调用 #
ltrace
可跟踪进程的动态库调用。其用法类似于 strace
,两者的大部分参数具有非常类似或完全相同的含义。默认情况下,ltrace
使用 /etc/ltrace.conf
或 ~/.ltrace.conf
配置文件。不过,您可以使用 -F
CONFIG_FILE
选项指定替代的配置文件。
除了跟踪库调用以外,带有 -S
选项的 ltrace
还可以跟踪系统调用:
>
ltrace -S -o ltrace_find.txt find /etc -name \
xorg.conf; more ltrace_find.txt
SYS_brk(NULL) = 0x00628000
SYS_mmap(0, 4096, 3, 34, 0xffffffff) = 0x7f1327ea1000
SYS_mmap(0, 4096, 3, 34, 0xffffffff) = 0x7f1327ea0000
[...]
fnmatch("xorg.conf", "xorg.conf", 0) = 0
free(0x0062db80) = <void>
__errno_location() = 0x7f1327e5d698
__ctype_get_mb_cur_max(0x7fff25227af0, 8192, 0x62e020, -1, 0) = 6
__ctype_get_mb_cur_max(0x7fff25227af0, 18, 0x7f1327e5d6f0, 0x7fff25227af0,
0x62e031) = 6
__fprintf_chk(0x7f1327821780, 1, 0x420cf7, 0x7fff25227af0, 0x62e031
<unfinished ...>
SYS_fstat(1, 0x7fff25227230) = 0
SYS_mmap(0, 4096, 3, 34, 0xffffffff) = 0x7f1327e72000
SYS_write(1, "/etc/X11/xorg.conf\n", 19) = 19
[...]
您可以使用 -e
选项更改受跟踪事件的类型。以下示例列显与 fnmatch
和 strlen
函数相关的库调用:
>
ltrace -e fnmatch,strlen find /etc -name xorg.conf
[...]
fnmatch("xorg.conf", "xorg.conf", 0) = 0
strlen("Xresources") = 10
strlen("Xresources") = 10
strlen("Xresources") = 10
fnmatch("xorg.conf", "Xresources", 0) = 1
strlen("xorg.conf.install") = 17
[...]
要仅显示特定库中包含的符号,请使用 -l /path/to/library
:
>
ltrace -l /lib64/librt.so.1 sleep 1
clock_gettime(1, 0x7fff4b5c34d0, 0, 0, 0) = 0
clock_gettime(1, 0x7fff4b5c34c0, 0xffffffffff600180, -1, 0) = 0
+++ exited (status 0) +++
您可以使用 -n
NUM_OF_SPACES
将每个嵌套调用按指定的空格数缩进,以此提高输出的易读性。
17.3 使用 Valgrind 进行调试和分析 #
Valgrind 是用于调试和分析您的程序,使其能够更快运行且更少出错的一套工具。Valgrind 可以检测与内存管理和线程相关的问题,还可以充当用于构建新调试工具的框架。但众所周知,此工具可能会产生较高的开销,例如导致运行时间变长,或在基于计时的并发工作负载下改变正常程序行为。
17.3.1 安装 #
标准的 SUSE Linux Enterprise Server 发行套件中并未随附 Valgrind。要在系统上安装 Valgrind,需要获取 SUSE Software Development Kit,然后安装该软件并运行
zypper
install
VALGRIND
或者浏览 SUSE Software Development Kit 目录树,找到 Valgrind 软件包并使用以下命令来安装它
rpm
-i
valgrind-VERSION_ARCHITECTURE.rpm
SDK 是适用于 SUSE Linux Enterprise 的模块,可以通过 SUSE Customer Center 的联机通道获得,有关细节,请参见 模块和扩展快速入门。
17.3.2 支持的体系结构 #
SUSE Linux Enterprise Server 支持在以下体系结构上使用 Valgrind:
AMD64/Intel 64
POWER
IBM Z
17.3.3 一般信息 #
Valgrind 的主要优势是它能够与现有的已编译可执行文件配合使用。您无需重新编译或修改程序即可使用它。按如下所示运行 Valgrind:
valgrind
VALGRIND_OPTIONS your-prog YOUR-PROGRAM-OPTIONS
Valgrind 包含多个工具,每个工具提供特定功能。本节中的信息是通用信息,无论使用哪个工具都适用。最重要的配置选项是 --tool
。此选项告知 Valgrind 要运行哪个工具。如果省略此选项,默认会选择 memcheck
。例如,要使用 Valgrind 的 memcheck
工具运行 find ~
-name
.bashrc,请在命令行中输入以下命令:
valgrind
--tool
=memcheck find ~ -name .bashrc
下面提供了标准 Valgrind 工具列表和简要说明:
memcheck
检测内存错误。它可以帮助您微调程序,使其正常运行。
cachegrind
分析缓存预测。它可以帮助您微调程序,使其运行速度更快。
callgrind
工作方式与
cachegrind
类似,但还会收集其他缓存分析信息。exp-drd
检测线程错误。它可以帮助您微调多线程程序,使其正常运行。
helgrind
另一个线程错误检测器。类似于
exp-drd
,但使用不同的技术来分析问题。massif
一个堆分析器。堆是用于动态内存分配的内存区域。此工具可帮助您微调程序,以减少内存用量。
lackey
用于演示工具基本功能的示例工具。
17.3.4 默认选项 #
Valgrind 可以在启动时读取选项。Valgrind 会检查三个位置:
运行 Valgrind 的用户的主目录中的
.valgrindrc
文件。环境变量
$VALGRIND_OPTS
运行 Valgrind 的当前目录中的
.valgrindrc
文件。
完全按照上面所列顺序分析这些资源,后面指定的选项优先于前面处理的选项。与特定 Valgrind 工具相关的选项必须使用该工具的名称加上冒号作为前缀。例如,如果您希望 cachegrind
始终将分析数据写入 /tmp/cachegrind_PID.log
,请将下面一行添加到主目录中的 .valgrindrc
文件:
--cachegrind:cachegrind-out-file=/tmp/cachegrind_%p.log
17.3.5 Valgrind 的工作原理 #
Valgrind 会在可执行文件启动之前取得其控制权。它会从该可执行文件和相关的共享库中读取调试信息。可执行文件的代码将重定向到选定的 Valgrind 工具,该工具会添加自身的代码来处理调试。然后,该代码将交回给 Valgrind 核心,而执行将会继续。
例如 memcheck
会添加自身的代码用于检查每个内存访问。因此,程序的运行速度比在本机执行环境中要慢得多。
Valgrind 会模拟程序的每条指令。因此,它不仅会检查程序的代码,还会检查所有相关库(包括 C 库)、用于图形环境的库,等等。如果您尝试使用 Valgrind 检测错误,它还会检测关联库(例如 C、X11 或 Gtk 库)中的错误。您或许不需要了解这些错误,对于这种情况,Valgrind 可以选择性地将这些错误消息隐藏到隐藏文件。--gen-suppressions=yes
告知 Valgrind 要报告这些隐藏内容,使您可以将其复制到文件中。
您应提供真实的可执行文件(机器码)作为 Valgrind 参数。例如,如果您的应用程序是通过外壳或 Perl 脚本运行的,则将错误地收到与 /bin/sh
(或 /usr/bin/perl
)相关的错误报告。在这种情况下,可以使用 --trace-children=yes
来解决此问题。但是,使用可执行文件本身可避免此问题造成的任何混淆。
17.3.6 消息 #
Valgrind 在运行时期间会报告消息,其中包含详细错误和重要事件。以下示例解释了这些消息:
>
valgrind --tool=memcheck find ~ -name .bashrc
[...]
==6558== Conditional jump or move depends on uninitialised value(s)
==6558== at 0x400AE79: _dl_relocate_object (in /lib64/ld-2.11.1.so)
==6558== by 0x4003868: dl_main (in /lib64/ld-2.11.1.so)
[...]
==6558== Conditional jump or move depends on uninitialised value(s)
==6558== at 0x400AE82: _dl_relocate_object (in /lib64/ld-2.11.1.so)
==6558== by 0x4003868: dl_main (in /lib64/ld-2.11.1.so)
[...]
==6558== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 0 from 0)
==6558== malloc/free: in use at exit: 2,228 bytes in 8 blocks.
==6558== malloc/free: 235 allocs, 227 frees, 489,675 bytes allocated.
==6558== For counts of detected errors, rerun with: -v
==6558== searching for pointers to 8 not-freed blocks.
==6558== checked 122,584 bytes.
==6558==
==6558== LEAK SUMMARY:
==6558== definitely lost: 0 bytes in 0 blocks.
==6558== possibly lost: 0 bytes in 0 blocks.
==6558== still reachable: 2,228 bytes in 8 blocks.
==6558== suppressed: 0 bytes in 0 blocks.
==6558== Rerun with --leak-check=full to see details of leaked memory.
==6558==
引入 Valgrind 的消息,并包含进程 ID 编号 (PID)。您可以轻松将 Valgrind 的消息与程序本身的输出区分开来,并确定哪些消息属于特定的进程。
要使 Valgrind 的消息变得更详细,请使用 -v
,甚至可以使用 -v -v
。
您可以让 Valgrind 将其消息发送到三个不同的位置:
默认情况下,Valgrind 会将其消息发送到文件描述符 2,即标准错误输出。您可以使用
--log-fd=FILE_DESCRIPTOR_NUMBER
选项,告知 Valgrind 将其消息发送到任何其他文件描述符。第二种方法(也许是更有用的方法)是使用
--log-file=FILENAME
将 Valgrind 的消息发送到相应的文件。此选项接受多个变量,例如,将%p
替换为当前所分析进程的 PID。这样,您便可以根据进程的 PID 将消息发送到不同的文件。%q{env_var}
将替换为相关env_var
环境变量的值。以下示例检查重启动 Apache Web 服务器期间可能出现的内存错误,同时跟踪子进程,并将详细的 Valgrind 消息写入到按当前进程 PID 区分的不同文件:
>
valgrind -v --tool=memcheck --trace-children=yes \ --log-file=valgrind_pid_%p.log systemctl restart apache2.service此进程在测试系统中创建了 52 个日志文件,在不使用 Valgrind 的情况下它需要 75 秒来运行
sudo systemctl restart apache2.service
,而正常情况下只需 7 秒,运行时间大约多了 10 倍。>
ls -1 valgrind_pid_*log valgrind_pid_11780.log valgrind_pid_11782.log valgrind_pid_11783.log [...] valgrind_pid_11860.log valgrind_pid_11862.log valgrind_pid_11863.log您还可能倾向于通过网络发送 Valgrind 的消息。需要使用
--log-socket=AA.BB.CC.DD:PORT_NUM
选项指定网络套接字的aa.bb.cc.dd
IP 地址和port_num
端口号。如果您省略端口号,将使用 1500。如果远程计算机上没有任何应用程序能够接收 Valgrind 的消息,将这些消息发送到网络套接字就没有意义。正因如此,我们还连同 Valgrind 一起随附了简单的监听程序
valgrind-listener
。此监听程序接受指定端口上的连接,并将它收到的任何内容复制到标准输出。
17.3.7 错误消息 #
Valgrind 会记住所有错误消息,如果检测到新错误,它会将该错误与旧错误消息进行比较。此方法可让 Valgrind 检查重复的错误消息。如果出现重复的错误,它将记录该错误,但不显示任何消息。此机制可避免几百万条重复的错误让您不堪其扰。
-v
选项会将所有报告的摘要(按其总计排序)添加到 Valgrind 执行输出的末尾。此外,如果检测到 1000 个不同的错误或者总共检测了 10000000 个错误,Valgrind 会停止收集错误。如果您想要取消此限制并希望查看所有错误消息,请使用 --error-limit=no
。
有些错误往往会导致其他错误。因此,请按错误的出现顺序修复错误,并持续对程序进行重新检查。
17.4 更多信息 #
有关与所述跟踪工具相关的选项的完整列表,请参见相应的手册页(
man 1 strace
、man 1 ltrace
和man 1 valgrind
)。对 Valgrind 高级用法的介绍超出了本文档的讨论范围。有关 Valgrind 的详细介绍,请参见 Valgrind User Manual。如果您需要有关 Valgrind 或其标准工具的用法和用途的更高级信息,请务必阅读这些页。