内容


使用跟踪来解决应用程序中的问题

使用 truss 来仔细研究应用程序的执行情况

Comments

系统管理员常常对应用程序中发生的事情感到困惑。您肯定碰到过这种情况:看起来启动了应用程序,但随后它却停止了,或者应用程序挂起了而没有任何输出。日志和文档都无法提供任何帮助。下一步您应该进行应用程序跟踪

应用程序跟踪显示了应用程序对外部库和内核的调用情况。应用程序通过这些调用来访问网络、文件系统及显示。通过观察这些调用及其结果,可以了解应用程序需要什么,并由此找到解决方案。

每个 UNIX® 系统都提供了进行跟踪的命令。本文向您介绍 Solaris 和 AIX® 支持的 truss 命令。在 Linux® 中,可以使用 strace 命令来执行跟踪。尽管命令行参数可能有些细微的差别,其他 UNIX 版本中的应用程序跟踪可能使用名称 ptrace、ktrace、trace 和 tusc。

一个经典的文件权限问题

有一类问题常常困扰着系统管理员,即文件权限。应用程序在完成工作的过程中可能需要打开某些文件。如果打开操作失败,该应用程序应该通知系统管理员。然而,开发人员常常忘记检查函数的结果,或者更令人困惑的是,执行了检查却没有恰当地处理该错误。例如,下面是一个应用程序在打开操作失败时的输出:

$ ./openapp
This should never happen!

在运行了虚构的应用程序 openapp 后,得到的是毫无帮助的(和错误的)错误消息,This should never happen!。现在是介绍 truss 的好时候了。清单 1 显示了同一应用程序在 truss 命令下的运行情况,而该命令显示了这个应用程序对外部库的所有函数调用。

清单 1. Openapp 在 truss 命令下的运行
$ truss ./openapp
execve("openapp", 0xFFBFFDEC, 0xFFBFFDF4)  argc = 1
getcwd("/export/home/sean", 1015)               = 0
stat("/export/home/sean/openapp", 0xFFBFFBC8)   = 0
open("/var/ld/ld.config", O_RDONLY)             Err#2 ENOENT
stat("/opt/csw/lib/libc.so.1", 0xFFBFF6F8)      Err#2 ENOENT
stat("/lib/libc.so.1", 0xFFBFF6F8)              = 0
resolvepath("/lib/libc.so.1", "/lib/libc.so.1", 1023) = 14
open("/lib/libc.so.1", O_RDONLY)                = 3
memcntl(0xFF280000, 139692, MC_ADVISE, MADV_WILLNEED, 0, 0) = 0
close(3)                                        = 0
getcontext(0xFFBFF8C0)
getrlimit(RLIMIT_STACK, 0xFFBFF8A0)             = 0
getpid()                                        = 7895 [7894]
setustack(0xFF3A2088)
open("/etc/configfile", O_RDONLY)               Err#13 EACCES [file_dac_read]
ioctl(1, TCGETA, 0xFFBFEF14)                    = 0
fstat64(1, 0xFFBFEE30)                          = 0
stat("/platform/SUNW,Sun-Blade-100/lib/libc_psr.so.1", 0xFFBFEAB0) = 0
open("/platform/SUNW,Sun-Blade-100/lib/libc_psr.so.1", O_RDONLY) = 3
close(3)                                        = 0
This should never happen!
write(1, " T h i s   s h o u l d  ".., 26)      = 26
_exit(3)

输出中的每一行表示了该应用程序进行的一个函数调用,并且在合适的情况下,还显示了其返回值。(您并不需要了解每个函数调用,有关这些函数的详细信息,可以查看该函数的 man 页面,例如,可以使用命令 man open。)要查找潜在地导致该问题的调用,最简单的方法通常是从最末尾处开始(或尽可能地从接近问题出现的地方开始)。例如,知道应用程序输出了 This should never happen!,它出现在输出的末尾附近。如果找到这个消息,就有可能在向上搜索 truss 命令输出的过程中发现问题。

向上滚动错误消息,可以发现以 open("/etc/configfile"... 开始的一行,该行不仅看起来相关,并且还返回了错误 Err#13 EACCES。使用 man open 查看 open() 函数的 man 页面,很明显,该函数的目的是打开一个文件,在本示例中是 /etc/configfile,并且返回值 EACCES 意味着该问题与权限有关。可以肯定,如果对 /etc/configfile 进行查看,则将显示该用户没有读取这个文件的权限。随后可以使用 chmod 命令来更改权限,那么这个应用程序就可以正常地运行了。

清单 1 的输出还显示了其他两个返回错误信息的调用,open()stat()。在应用程序开始处的许多调用,包括其他两个错误,都是操作系统在运行该应用程序时添加的。只有凭经验才能判断出哪些错误是良性的,哪些不是。在本例中,有两个错误及其后面的三行试图查找 libc.so.1 的位置,并且最终找到了。稍后您将了解更多关于共享库的问题。

应用程序没有启动

有时,应用程序无法正常启动,但它却只是挂起了而并没有退出。这种行为通常是资源争用的表现(如两个进程争用一个文件锁),或者该应用程序正在请求无法返回的对象。后面这类问题可能是几乎任何情况,如需要长时间解析的名称查找,或者一个文件应该能在某个位置找到,但它却不在该处。总之,观察 truss 下的应用程序可以揭示出问题的原因。

第一个代码示例介绍了导致问题的系统调用和文件之间存在明显的联系的情况,而下面您将看到的示例,则需要进行更多的跟踪操作。清单 2 显示了一个行为异常的应用程序 Getlocktruss 下的运行情况。

清单 2. Getlock 在 truss 命令下的运行
$ truss ./getlock
execve("getlock", 0xFFBFFDFC, 0xFFBFFE04)  argc = 1
getcwd("/export/home/sean", 1015)               = 0
resolvepath("/export/home/sean/getlock", "/export/home/sean/getlock", 1023) = 25
resolvepath("/usr/lib/ld.so.1", "/lib/ld.so.1", 1023) = 12
stat("/export/home/sean/getlock", 0xFFBFFBD8)   = 0
open("/var/ld/ld.config", O_RDONLY)             Err#2 ENOENT
stat("/opt/csw/lib/libc.so.1", 0xFFBFF708)      Err#2 ENOENT
stat("/lib/libc.so.1", 0xFFBFF708)              = 0
resolvepath("/lib/libc.so.1", "/lib/libc.so.1", 1023) = 14
open("/lib/libc.so.1", O_RDONLY)                = 3
close(3)                                        = 0
getcontext(0xFFBFF8D0)
getrlimit(RLIMIT_STACK, 0xFFBFF8B0)             = 0
getpid()                                        = 10715 [10714]
setustack(0xFF3A2088)
open("/tmp/lockfile", O_WRONLY|O_CREAT, 0755)   = 3
getpid()                                        = 10715 [10714]
fcntl(3, F_SETLKW, 0xFFBFFD60)  (sleeping...)

最后一个调用,fcntl(),因为函数发生了阻塞,所以被标记为 sleeping。这意味着该函数正在等待某个事件的发生,并且内核将这个进程置于睡眠状态,直到发生了相应事件。要确定究竟是何种事件,必须查看 fcntl()

fcntl() 的 man 页面 (man fcntl) 介绍了该函数在 Solaris 上仅用作“文件控制”,而在 Linux 上用来“操作文件描述符”。在这两种情况下,fcntl() 都需要一个文件描述符 和一个命令,其中文件描述符是用来描述进程所打开的文件的一个整数,而命令则用来指定将对该文件描述符进行的操作以及对于特定的函数而言最终需要的参数。在清单 2 的示例中,文件描述符为 3,而命令为 F_SETLKW。(0xFFBFFD60 是指向一个数据结构的指针,而我们现在并不关心这个数据结构。)经过更深入的研究,我们了解到,man 页面声明 F_SETLKW 打开文件上的一个锁,并将等待,直到获得该锁。

从涉及 open() 系统调用的第一个示例中,您将看到成功的调用返回了一个文件描述符。在清单 2truss 的输出中,有两处 open() 的返回结果为 3。因为在关闭文件描述符后对其进行了重用,所以与 fcntl() 相关的是它上面的 open(),即打开 /tmp/lockfile 的 open()。lsof 等实用程序可以列出打开了文件的所有进程。如果失败,您还可以跟踪 /proc 以查找打开文件的进程。然而通常情况下,锁定文件是有原因的,如限制应用程序的实例数目,或者将应用程序配置为运行于用户特定的目录中。

附加到一个正在运行的进程

在某些情况下,出现问题时,应用程序已处于运行状态。如果能把一个已开始运行的进程放到 truss 下,这将非常有用。例如,请注意在应用程序 Top 的输出中,某个进程在很长一段时间内使用了百分之九十五的 CPU,如清单 3 所示。

清单 3. Top 的输出表明它是一个 CPU 密集型的进程
   PID USERNAME LWP PRI NICE  SIZE   RES STATE    TIME    CPU COMMAND
 11063 sean       1   0    0 1872K  952K run     87.9H 94.68% udpsend

truss-p 选项允许进程的所有者或 root 用户将它附加到一个正在运行的进程,并查看系统调用活动。其中需要使用进程 ID (PID)。在清单 3 的示例中,PID 为 11063。清单 4 则显示了相关应用程序的系统调用活动。

清单 4. 附加到正在运行的进程后 truss 的输出
$ truss -p 11063
sendto(3, " a b c", 3, 0, 0xFFBFFD58, 16)       = 3
sendto(3, " a b c", 3, 0, 0xFFBFFD58, 16)       = 3
sendto(3, " a b c", 3, 0, 0xFFBFFD58, 16)       = 3
sendto(3, " a b c", 3, 0, 0xFFBFFD58, 16)       = 3
sendto(3, " a b c", 3, 0, 0xFFBFFD58, 16)       = 3
sendto(3, " a b c", 3, 0, 0xFFBFFD58, 16)       = 3
sendto(3, " a b c", 3, 0, 0xFFBFFD58, 16)       = 3
sendto(3, " a b c", 3, 0, 0xFFBFFD58, 16)       = 3
. repeats ...

sendto() 函数的 man 页面 (man sendto) 表明此函数用来通过套接字(通常为网络连接)发送消息。truss 的输出显示了相应的文件描述符(第一个 3)以及所发送的数据 (abc)。事实上,使用 snoop 或 tcpdump 工具截获的网络流量样本表明,大量的通信量定向到一个特定的主机,而这可能并不是正常运行的应用程序所应有的结果。

请注意,truss 没能显示文件描述符 3 的创建过程,因为在该描述符创建之后才进行了附加操作。这是附加到正在运行的应用程序的一个限制,并且也是您在得出结论前应该使用工具(如数据包分析程序)收集其他相关信息的原因。

这个示例也许看起来是经过精心设计的(并且从技术上看,它的确如此,因为我编写了 udpsend 应用程序来介绍如何使用 truss),但它却基于实际情况。我研究了在具有 CPU 限制 (CPU-bound) 的进程的基于 UNIX 的设备上运行进程的情况。跟踪该应用程序显示了相同的数据包活动。使用网络分析程序对其进行跟踪则显示出这些数据包定向到 Internet 上的一个主机。在供应商对其进行升级之后,我发现这个问题是因为他们的应用程序没有对一个二进制配置文件执行适当的错误检查。而该文件受到了一定程度的损坏。所以,该应用程序对文件进行了错误的解释并重复地向一个随机 IP 地址发送用户数据报协议 (UDP) 数据报。在我替换了该文件之后,进程就正常了。

筛选输出

稍后,您将了解到查找相关信息的技巧。虽然可以使用 grep 命令来仔细检查输出,但更简单的方法是对 truss 进行配置,以使它仅关注某些调用。如果您正在试图确定应用程序的工作方式,如应用程序正在使用哪一个配置文件,这种做法是比较常见的。在本例中,open()stat() 系统调用指向应用程序试图打开的任何文件。

可以使用 open() 来打开一个文件,而使用 stat() 来查找文件的相关信息。通常,应用程序通过一系列的 stat() 调用来查找文件,然后再打开它所需要的文件。

对于 truss,您可以使用 -t 选项来添加系统调用筛选。对于 Linux 下的 strace,您可以使用 -e。不论何种情况,您都需要传递一个用逗号分隔的列表来指定要在命令行中显示的系统调用。在该列表前面加上感叹号 (!),这样一来,将从输出中筛选出给定的调用。清单 5 显示了一个正在查找配置文件的虚构的应用程序。

清单 5. 对 truss 输出进行筛选仅显示 stat() 和 open() 函数
$ truss -tstat,open ./app
stat("/export/home/sean/app", 0xFFBFFBD0)   = 0
open("/var/ld/ld.config", O_RDONLY)             Err#2 ENOENT
stat("/opt/csw/lib/libc.so.1", 0xFFBFF700)      Err#2 ENOENT
stat("/lib/libc.so.1", 0xFFBFF700)              = 0
open("/lib/libc.so.1", O_RDONLY)                = 3
stat("/export/home/sean/.config", 0xFFBFFCF0)   Err#2 ENOENT
stat("/etc/app/configfile", 0xFFBFFCF0)         Err#2 ENOENT
stat("/etc/configfile", 0xFFBFFCF0)             = 0
open("/etc/configfile", O_RDONLY)               = 3

这里的关键之处是最后四行。对 /export/home/sean/.config 文件使用的 stat() 函数的结果为 ENOENT,这表示无法找到该文件。接下来,代码在 /etc/app/configfile 中进行尝试,直到最后在 /etc/configfile 中找到正确的信息。首先检查用户 home 目录的意义在于,用户可以对配置进行重载。

最后的想法

无论您的操作系统使用 trussstracetrace 还是其他命令,能够查看应用程序行为是解决问题的有效手段。对这个方法可以进行如下总结:

  1. 描述问题。
  2. 跟踪应用程序。
  3. 从出现问题的地方着手,逆向检查系统调用以确定该问题。使用 man 页面来获取关于这些系统调用的解释。
  4. 更正应用程序的行为并进行测试。

跟踪应用程序行为是有效的排除故障的方式,因为您可以观察到该应用程序对操作系统进行的系统调用。当常规的问题解决方法失效时,可以进行应用程序跟踪。


相关主题

  • 您可以参阅本文在 developerWorks 全球站点上的 英文原文
  • IBM 试用软件:使用 IBM 试用软件开发您的下一个项目,可直接从 developerWorks 下载这些试用软件。
  • "AIX 5.2 performance tools update, Part 2," (developerWorks, Nov 2003) 提供了关于 AIX 上 truss 命令的可用选项的概述。
  • Comparison of truss and DTrace:Solaris 10 提供了一个称为 DTrace 的工具,与 truss 相比,它能进行更加深入的跟踪操作。这篇文章对 truss 和 DTrace 进行了深入的比较,并在性能优化过程中分别使用了这两个工具。
  • Manipulating Files and Directories in UNIX:这个关于 UNIX 文件处理的教程非常容易(针对非程序开发人员),它介绍了进行文件处理必须使用的一些系统调用。因为在您使用应用程序跟踪来进行故障排除时,大部分情况下会涉及到文件,所以该教程提供了关于这些调用的详细背景知识。
  • UNIX Network Programming:Richard Stevens 编写的 UNIX Network Programming (Prentice Hall, 1990) 是关于 UNIX 系统的最好的书籍。它详细地介绍了信号和系统调用、套接字和文件,对于那些想要了解比 man 页面更深入的信息的开发人员来说,这本书是很好的参考手册。已经对最初的版本进行了更新并扩展为两卷。

评论

添加或订阅评论,请先登录注册

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=AIX and UNIX
ArticleID=124412
ArticleTitle=使用跟踪来解决应用程序中的问题
publish-date=05252006