快捷搜索:

跟踪 UNIX 应用程序的解决方案

本文示例源代码或素材下载

开始之前

本教程赞助 Unix 系统开拓职员和治理员以最佳要领跟踪系统上运行的利用法度榜样。要想从本教程得到最大年夜劳绩,您应该懂得 UNIX 操作系统的基础常识及其操作要领。具备基础的编程履历会有赞助,但不是必需的。

关于本教程

大年夜多半开拓职员和系统治理员知道在他们的操作系统和利用法度榜样中应该会发生什么环境,但糟糕的是,无意偶尔候不是这样的。当利用法度榜样崩溃或体现非常时,必要查明更多信息。经由过程使用对利用法度榜样正常事情要领的懂得和一些基础 UNIX 技能,可以跟踪利用法度榜样,查明造成问题的缘故原由。本教程解说应用跟踪对象懂得利用法度榜样内部环境的基础技巧。

本教程首先评论争论调试和跟踪的差异,以及这两种办理规划的事情要领差异。然后,经由过程一些详细示例解说若何应用跟踪办理利用法度榜样中的问题。DTrace 供给跟踪和调试两种系统的组件,还支持对利用法度榜样进行计时和基准测试。着末,本教程解说若何跟踪在收集谋略机之间互换的信息,赞助发明收集利用法度榜样中的问题。

跟踪概述

无意偶尔候,必要懂得在利用法度榜样内部正在发生的环境。例如,利用法度榜样可能会运行掉败,而又没有显示有赞助的差错消息,或者系统办事没有按照预期的要领运行。在这些环境下,您可能不掌握利用法度榜样源代码,是以无法经由过程传统的调试历程探求问题的缘故原由。跟踪供给了一种替代措施。

调试

对付开拓职员来说,探求 UNIX 利用法度榜样问题的主要措施是,应用开拓情况或操作系统的调试特点反省源代码,查明造成问题的缘故原由。

大年夜多半调试系统支持逐行监视和反省代码行的履行历程,还支持监视变量和布局的值。可以应用调试器在代码中设置断点,履行历程会停在断点上;在断点上,可以得到关于调用客栈(函数的调用路径)的信息以及变量值。

我们来看一个例子,假设一个利用法度榜样根据人的生日谋略他的年岁,还要斟酌到闰年等身分。要想调试这个利用法度榜样,必要有源代码,还必要在启用调试选项的环境下编译利用法度榜样:$ gcc -g ageindays.c -o ageindays。

运行这个利用法度榜样,供给用户的生日和用来对照的目标日期(见清单 1)。

清单 1. 履行对照

$ ./ageindays 24/1/1980 22/2/2009

You have been alive 10622 days

You were born on 24/1/1980 which is a Thursday

在调试利用法度榜样时,首先狐疑问题出在 calc_diff 函数中,这个函数谋略第一个和第二个日期的差。接下来,可能按照清单 2 这样进行调试。

清单 2. 调试 calc_diff 函数

$ gdb ageindays

GNU gdb 6.3.50-20050815 (Apple version gdb-962) (Sat Jul 26 08:14:40 UTC 2008)

Copyright 2004 Free Software Foundation, Inc.

GDB is free software, covered by the GNU General Public License, and you are

welcome to change it and/or distribute copIEs of it under certain conditions.

Type "show copying" to see the conditions.

There is absolutely no warranty for GDB. Type "show warranty" for details.

This GDB was configured as "i386-apple-darwin"...Reading symbols for shared

libraries ... done

(gdb) b calc_diff

Breakpoint 1 at 0x1bd7: file ageindays.c, line 27.

(gdb) r 24/1/1980 26/3/2009

Starting program: /nfs/MC/UnixSrc/c/bio/ageindays 24/1/1980 26/3/2009

Reading symbols for shared libraries ++. done

Breakpoint 1, calc_diff (day=26, month=3, year=2009) at ageindays.c:27

27  unsigned long days_diff=0;

(gdb) bt

#0 calc_diff (day=26, month=3, year=2009) at ageindays.c:27

#1 0x00001e3d in main (argc=3, argv=0xbffff708) at ageindays.c:89

(gdb) p days_diff

$1 = 8041

(gdb)

经由过程 清单 2 中的输出可以看出,我们打开了调试器,经由过程指定名称在 calc_diff() 函数中设置了一个断点,然后在调试器中运行法度榜样,供给与敕令行相同的参数。

当调试器到达创建的断点时,履行历程竣事,您可以反省利用法度榜样代码和调用的函数。经由过程应用调试器,可以查看供给给函数的参数及其值(在这里是为目标日期供给的日期信息)。履行竣事之后,可以查看客栈跟踪,查看代码中调用 calc_diff 函数的行,可以得到 days_diff 变量的值。由于利用法度榜样的履行历程已经停息了,以是还可以修改变量的值。这样就可以在利用法度榜样中考试测验应用不合的值,从而探求潜在的问题。

可以应用这些信息,由于定义了特定的调试信息(组成函数和变量名的符号)和其他元数据(比如定义函数的代码行)。

必须在编译时把特定的调试信息添加到二进制利用法度榜样中;更紧张的是,必须造访源代码,才能把调试信息包孕在编译的利用法度榜样中。假如无法识别函数名和变量,那么险些弗成能调试法度榜样。

跟踪与调试的比较

系统治理员(和开拓职员)经常盼望发明正在运行的法度榜样中的差错。例如,某个法度榜样为什么造成了其他问题(比如内存和其他差错),利用法度榜样的体现为什么不相符预期,它以前发生了什么环境。在这种环境下,调试利用法度榜样的特定方面每每没什么用。必要查明的实际上是操作系统若何履行利用法度榜样。

在进行调试时,反省的是利用法度榜样中定义的各个函数的履行历程。调试主要关注利用法度榜样本身,包括此中的函数和布局,平日会漠视利用法度榜样向操作系统发出的系统调用和库函数调用。调试能够供给关于利用法度榜样的大年夜量信息,然则对付懂得操作系统若何履行利用法度榜样赞助不大年夜。

在进行跟踪时,监视利用法度榜样和操作系统之间的交互,经常会反省利用法度榜样在履行时代调用的操作系统函数。

除了这些差异之外,跟踪和调试之间的主要差异是,跟踪不要求造访源代码,也不要求以任何特殊要领编译利用法度榜样。这意味着可以跟踪操作系统附带的或第三方厂商供给的利用法度榜样。

经由过程跟踪利用法度榜样,可以查明以下方面的环境:

内存应用量和映射内存的调用

在履行时代打开和关闭的文件

对不合文件的读和写操作

为给定的利用法度榜样装载的库

下面先钻研一下 truss 的输出,truss 是一种可以在 Solaris 和 AIX® 上应用的对象。

应用 truss 和 strace

在 Solaris 和 AIX 上可以应用 truss 对象,它能够跟踪利用法度榜样中的系统调用和旌旗灯号。可以在 Linux® 上应用的 strace 对象供给相似的功能。在不合的系统上,还有供给相似信息的其他对象,包括 ktrace (FreeBSD) 和 trace。

truss/strace 概述

truss 和 strace 对象供给相似的信息,然则敕令行选项稍有差异。应用这两种对象的标准措施都是把对象名放在要履行的敕令前面。

例如,清单 3 给出 truss 对付本教程前面提到的 ageindays 法度榜样的输出。

清单 3. truss 的输出

$ truss ./ageindays 24/1/1980 26/3/2009

execve("ageindays", 0x08047BBC, 0x08047BCC) argc = 3

mmap(0x00000000, 4096, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_PRIVATE|MAP_ANON, -1, 0)

= 0xFEFB0000

resolvepath("/usr/lib/ld.so.1", "/lib/ld.so.1", 1023) = 12

getcwd("/root", 1013)              = 0

resolvepath("/root/ageindays", "/root/ageindays", 1023) = 15

xstat(2, "/root/ageindays", 0x08047880)     = 0

open("/var/ld/ld.config", O_RDONLY)       = 3

fxstat(2, 3, 0x08047760)            = 0

mmap(0x00000000, 144, PROT_READ, MAP_SHARED, 3, 0) = 0xFEFA0000

close(3)                    = 0

sysconfig(_CONFIG_PAGESIZE)           = 4096

xstat(2, "/usr/lib/libc.so.1", 0x08046FA0)   = 0

resolvepath("/usr/lib/libc.so.1", "/lib/libc.so.1", 1023) = 14

open("/usr/lib/libc.so.1", O_RDONLY)      = 3

mmap(0x00010000, 32768, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_ALIGN, 3, 0)

= 0xFEF90000

mmap(0x00010000, 1413120, PROT_NONE, MAP_PRIVATE|MAP_NORESERVE|MAP_ANON|MAP_ALIGN, -1, 0)

= 0xFEE30000

mmap(0xFEE30000, 1302809, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_TEXT, 3, 0)

= 0xFEE30000

mmap(0xFEF7F000, 30862, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|

MAP_INITDATA, 3, 1306624) = 0xFEF7F000

mmap(0xFEF87000, 4776, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_ANON,

-1, 0) = 0xFEF87000

munmap(0xFEF6F000, 65536)            = 0

memcntl(0xFEE30000, 187632, MC_ADVISE, MADV_WILLNEED, 0, 0) = 0

close(3)                    = 0

mmap(0x00010000, 24576, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_PRIVATE|MAP_ANON|MAP_ALIGN,

-1, 0)

= 0xFEE20000

munmap(0xFEF90000, 32768)            = 0

getcontext(0x080475D0)

getrlimit(RLIMIT_STACK, 0x080475C8)       = 0

getpid()                    = 15691 [15690]

lwp_private(0, 1, 0xFEE22A00)          = 0x000001C3

setustack(0xFEE22A60)

sysi86(SI86FPSTART, 0xFEF879BC, 0x0000133F, 0x00001F80) = 0x00000001

ioctl(1, TCGETA, 0x08046C20)          = 0

fstat64(1, 0x08046B80)             = 0

You have been alive 10654 days

write(1, " Y o u  h a v e  b e e".., 31)   = 31

You were born on 24/1/1980 which is a Thursday

write(1, " Y o u  w e r e  b o r".., 47)   = 47

_exit(134511508)

在这里,它没有供给我们必要的信息。现在试试 pfiles(见清单 9)。

清单 9. 应用 pfiles

$ pfiles 15856

15856: ./errlock

Current rlimit: 256 file descriptors

0: S_IFCHR mode:0620 dev:292,0 ino:989038936 uid:101 gid:7 rdev:24,3

O_RDWR|O_NOCTTY|O_LARGEFILE

/dev/pts/3

1: S_IFCHR mode:0620 dev:292,0 ino:989038936 uid:101 gid:7 rdev:24,3

O_RDWR|O_NOCTTY|O_LARGEFILE

/dev/pts/3

2: S_IFCHR mode:0620 dev:292,0 ino:989038936 uid:101 gid:7 rdev:24,3

O_RDWR|O_NOCTTY|O_LARGEFILE

/dev/pts/3

3: S_IFREG mode:0666 dev:182,65545 ino:198 uid:101 gid:10 size:0

O_RDWR

advisory write lock set by process 15828

/export/home/mc/lockdemo

这些输出就对照有用了。可以看到进程已经打开的文件称为 lockdemo ,由于 truss 显指正在等待文件锁,以是可能是这个文件导致了问题。

truss 和 strace 都是被动的跟踪。可以查看正在履行的函数,然则无法懂得关于正在发生的环境的具体信息,也无法更有针对性地指定要跟踪的器械和跟踪时要输出的信息。

用 DTrace 进行动态跟踪

Solaris、FreeBSD 和 Mac OS X 内置的 Dynamic Tracing (DTrace) 功能供给一个加倍动态的跟踪情况。与 truss 和相似的对象不合,可以应用 DTrace 反省正在运行的法度榜样的内部环境,而不光是查看系统调用。别的,可以应用 DTrace 编写利用法度榜样跟踪脚本,从而定制在跟踪历程中盼望提取的信息。

在输出中可以看到,已经调用了 fcntl() 函数,它要在一个文件上设置锁。在这里,这个函数会不停等待到成功地设置锁,然后才继承运行。不幸的是,另一个进程已经锁住了这个文件,以是第二个利用法度榜样必须等待第一个利用法度榜样应用完文件并开释锁。

对付这种环境,truss 有点儿局限性:它无法指出要锁住哪个文件,也无法指出当前锁住了哪个文件,从而阻碍了第二个法度榜样的履行。这是由于跟踪历程是在已经调用了打开文件的函数之后启动的。truss 和 strace 只跟踪在它们履行时代调用的函数;它们无法查明已经调用的函数。

获取客栈跟踪

可以看出,truss 对付监视全部法度榜样很有用,然则对付监视已经启动的法度榜样可能有点儿局限性。假如应用基于 SVR4 的 Unix,比如 Solaris 或 AIX,那么 pstack 敕令可能有赞助。

pstack 敕令实际上属于一组进程反省敕令,这些敕令输出正在运行的进程的相关信息。其他对象包括 pfiles(输出进程应用的文件的列表)和 psig(显示旌旗灯号和旌旗灯号处置惩罚函数的列表)。

在应用这些敕令时,必要指定进程的 PID。pstack 敕令输出一个正在运行的进程的调用客栈,显示在进程到达当前函数之前调用的函数。例如,对正在等待被锁住的文件的进程应用 pstack,会孕育发生清单 8 中的输出。

清单 8. 对正在等待被锁住的文件的进程应用 pstack

$ pstack 15828

15828: ./errlock

feef0877 fcntl  (3, 7, 8047ac4)

feedcd49 fcntl  (3, 7, 8047ac4, 8050e74) + 91

08050f10 main   (1, 8047b24, 8047b2c) + d8

08050cdc _start  (1, 8047c08, 0, 8047c12, 8047c7d, 8047c8e) + 80

问题呈现在这一行上:open("/etc/shadow", O_RDONLY) Err#13 EACCES [file_dac_read]。

在这里,利用法度榜样试图打开一个文件,然则由于有文件权限保护这个文件,运行利用法度榜样的用户没有造访权。由于无法打开这个文件,利用法度榜样终止并在进程中写差错消息。

跟踪正在运行的利用法度榜样

盼望跟踪利用法度榜样经常是由于利用法度榜样已经启动,而您盼望查明利用法度榜样为什么不事情了。与初始化示例一样,利用法度榜样供给的差错消息或其他信息经常没有准确地指出问题。

试图锁定或造访另一个进程正在应用的资本会使利用法度榜样看起来冻结了,没有相应。

strace 和 truss 都能够 “连接 到正在运行的进程。连接到进程的跟踪与从敕令交运行进程的跟踪相似,也孕育发生法度榜样正在履行的系统函数的列表。实际的跟踪从跟踪启动时履行的函数开始;对付在履行时代 “挂起 的法度榜样,跟踪应该会显示法度榜样正在等待的函数。

要想跟踪正在运行的法度榜样,必要指定要跟踪的进程的进程 ID (PID)。例如,在清单 6 中,跟踪的法度榜样已经竣事,然则没有申报差错。这里应用 ps 对象列出正在运行的进程(见清单 7)。

清单 7. 应用 ps 对象列出正在运行的进程

$ ps -ef|grep errlock

mc 15779 15747  0 18:26:59 pts/2    0:00 ./errlock

mc 15742  680  0 18:26:36 pts/3    0:00 ./errlock

mc 15817 15784  0 18:28:44 pts/4    0:00 grep errlock

mc 15734  680  0 18:25:00 pts/3    0:01 /usr/bin/eMacs-nox errlock.c

$ truss -p 15779

fcntl(3, F_SETLKW, 0x08047AC4) (sleeping...)

在这两个输出中,每个输出行对应于利用法度榜样履行的一个函数调用,此中显示函数的参数和函数调用的返回值。与调试示例不合,列出的每个函数调用都是系统或系统库中的函数,是以表示调用的函数的更低层接口。例如,在利用法度榜样中可能应用 C 或 C++ 中的 fpopen() 函数打开文件,然则这个函数实际上是更低层的 open() 函数的包装器。

懂得利用法度榜样正在履行的操作并不必要懂得每个函数的环境。输出中的许多行与操作系统为装载和履行法度榜样所做的初始化相关。这两个跟踪输出的基础布局是相同的:

调用 execve() 函数以启动一个新法度榜样。

装载法度榜样所需的库。在 Solaris 输出中,首先应用 resolvepath() 探求库,然后应用 open() 打开库。对付 Linux,应用 stat() 反省库是否存在,然后应用 open() 打开它。

为进程保留和分配一些内存。此中一部分内存是为利用法度榜样保留的客栈空间,一部分用来保存法度榜样,其他内存保存法度榜样应用的变量。

着末,履行法度榜样,调用 write() 函数输出年岁和生日信息。

假如履行跟踪并盼望懂得每个步骤的详细环境,可以应用 man 敕令造访每个函数的手书页。

识别利用法度榜样启动问题

在启动利用法度榜样时的一个范例问题是,法度榜样无法精确地初始化,然则在终止时给出一个不完备或导致误解的消息。对利用法度榜样运行跟踪经常可以揭示这个问题的根源。例如,清单 5 显示一个测试利用法度榜样运行掉败了。

清单 5. 利用法度榜样掉败

$ ./errnoacc

ERROR: Application failed to initialize

差错消息并没有供给关于利用法度榜样为什么会启动掉败的详细信息。在这里,问题是有意引入的,然则您应用的任何敕令或利用法度榜样都可能呈现相同的问题,而差错消息没什么赞助,无意偶尔候以致没有差错消息。

对利用法度榜样运行跟踪可能会供给一些线索(见清单 6)。

清单 6. 运行跟踪

$ truss ./errnoacc

execve("errnoacc", 0x08047B20, 0x08047B28) argc = 1

mmap(0x00000000, 4096, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_PRIVATE|MAP_ANON, -1, 0)

= 0xFEFB0000

resolvepath("/usr/lib/ld.so.1", "/lib/ld.so.1", 1023) = 12

getcwd("/export/home/mc", 1014)         = 0

resolvepath("/export/home/mc/errnoacc", "/export/home/mc/errnoacc", 1023) = 24

xstat(2, "/export/home/mc/errnoacc", 0x080477E4) = 0

open("/var/ld/ld.config", O_RDONLY)       = 3

fxstat(2, 3, 0x080476C4)            = 0

mmap(0x00000000, 144, PROT_READ, MAP_SHARED, 3, 0) = 0xFEFA0000

close(3)                    = 0

sysconfig(_CONFIG_PAGESIZE)           = 4096

xstat(2, "/usr/lib/libc.so.1", 0x08046F04)   = 0

resolvepath("/usr/lib/libc.so.1", "/lib/libc.so.1", 1023) = 14

open("/usr/lib/libc.so.1", O_RDONLY)      = 3

mmap(0x00010000, 32768, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_ALIGN, 3, 0) = 0xFEF90000

mmap(0x00010000, 1413120, PROT_NONE, MAP_PRIVATE|MAP_NORESERVE|MAP_ANON|MAP_ALIGN,

-1, 0) = 0xFEE30000

mmap(0xFEE30000, 1302809, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_TEXT, 3, 0)

= 0xFEE30000

mmap(0xFEF7F000, 30862, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|

MAP_INITDATA, 3, 1306624) = 0xFEF7F000

mmap(0xFEF87000, 4776, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_ANON,

-1, 0) = 0xFEF87000

munmap(0xFEF6F000, 65536)            = 0

memcntl(0xFEE30000, 187632, MC_ADVISE, MADV_WILLNEED, 0, 0) = 0

close(3)                    = 0

mmap(0x00010000, 24576, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_PRIVATE|MAP_ANON|MAP_ALIGN,

-1, 0) = 0xFEE20000

munmap(0xFEF90000, 32768)            = 0

getcontext(0x08047534)

getrlimit(RLIMIT_STACK, 0x0804752C)       = 0

getpid()                    = 15727 [15726]

lwp_private(0, 1, 0xFEE22A00)          = 0x000001C3

setustack(0xFEE22A60)

sysi86(SI86FPSTART, 0xFEF879BC, 0x0000133F, 0x00001F80) = 0x00000001

open("/etc/shadow", O_RDONLY)          Err#13 EACCES [file_dac_read]

ioctl(1, TCGETA, 0x08046BB0)          = 0

fstat64(1, 0x08046B10)             = 0

ERROR: Application failed to initialize

write(1, " E R R O R :  A p p l i".., 40)   = 40

_exit(0)

您可能还会对下面的文章感兴趣: