跳到内容跳到页面导航:上一页 [access key p]/下一页 [access key n]
documentation.suse.com / SUSE Linux Enterprise Server 文档 / 系统分析和微调指南 / 处理系统转储 / 跟踪工具
适用范围 SUSE Linux Enterprise Server 15 SP3

17 跟踪工具

SUSE Linux Enterprise Server 随附有多个工具,可帮助您获取有关您的系统的有用信息。您可将这些信息用于各种目的,例如,调试和查找程序中的问题、发现性能下降的原因,或者跟踪运行中的进程以确定它使用了哪些系统资源。大部分工具已包含在安装媒体中。某些情况下,需要通过单独下载的 SUSE Software Development Kit 安装这些工具。

注意
注意:跟踪及性能影响

监视运行中进程的系统调用或库调用时,该进程的性能将严重下降。建议您仅当需要收集数据时才使用跟踪工具。

17.1 使用 strace 跟踪系统调用

strace 命令可以跟踪进程的系统调用以及进程收到的信号。strace 可以运行新命令并跟踪其系统调用,或者您可以将 strace 附加到已运行的命令。命令的每行输出都包含系统调用名称,后接其参数(括在括号中)及其返回值。

要运行新命令并开始跟踪其系统调用,请如常输入要监视的命令,然后在命令行的开头添加 strace

tux > 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):

tux > 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 选项可识别多个子选项和参数。例如,要跟踪打开或写入特定文件的所有尝试,请使用以下命令:

tux > 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

tux > 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 用于计算内核花费在每个系统调用上的时间:

tux > 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

tux > 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

tux > 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 还可以跟踪系统调用:

tux > 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 选项更改受跟踪事件的类型。以下示例列显与 fnmatchstrlen 函数相关的库调用:

tux > 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

tux > 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 的联机通道获得,或者从 http://download.suse.com/ 下载(搜索 SUSE Linux Enterprise Software Development Kit 即可找到)。有关详细信息,请参考 第 22 章 “安装模块、扩展和第三方附加产品

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 会检查三个位置:

  1. 运行 Valgrind 的用户的主目录中的 .valgrindrc 文件。

  2. 环境变量 $VALGRIND_OPTS

  3. 运行 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 在运行时期间会报告消息,其中包含详细错误和重要事件。以下示例解释了这些消息:

tux > 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 将其消息发送到三个不同的位置:

  1. 默认情况下,Valgrind 会将其消息发送到文件描述符 2,即标准错误输出。您可以使用 --log-fd=FILE_DESCRIPTOR_NUMBER 选项,告知 Valgrind 将其消息发送到任何其他文件描述符。

  2. 第二种方法(也许是更有用的方法)是使用 --log-file=FILENAME 将 Valgrind 的消息发送到相应的文件。此选项接受多个变量,例如,将 %p 替换为当前所分析进程的 PID。这样,您便可以根据进程的 PID 将消息发送到不同的文件。%q{env_var} 将替换为相关 env_var 环境变量的值。

    以下示例检查重启动 Apache Web 服务器期间可能出现的内存错误,同时跟踪子进程,并将详细的 Valgrind 消息写入到按当前进程 PID 区分的不同文件:

    tux > 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 倍。

    tux > 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
  3. 您还可能倾向于通过网络发送 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 straceman 1 ltraceman 1 valgrind)。

  • 对 Valgrind 高级用法的介绍超出了本文档的讨论范围。《Valgrind User Manual》(Valgrind 用户手册)中包含了非常全面的介绍。如果您需要有关 Valgrind 或其标准工具的用法和用途的更高级信息,请务必阅读这些页。