关于DTrace和strace的概述

721 阅读12分钟

DTrace和strace概述

使用这些神奇的工具,无需源代码或对环境的深入了解,就可以追踪应用程序内部及其外部依赖的bug

在调试的时候,我们经常需要跳出IDE的舒适怀抱来重现或追踪一个问题。在这个系列中,我想介绍一些你可能会发现对这些情况有用的工具。我将尝试把自己限制在100%的调试工具上,而不是那些对开发测试有用的工具。

例如,像curljq这样的工具是非常有用的。你可以/应该在调试时使用它们。但你可能在构建和测试功能时使用它们。所以你已经对它们很熟悉了,应该对它们的作用有一定的了解。我想把重点放在那些你在调试时主要会用到的工具上。在这个意义上,像SDKMan等工具在这里也没有意义。

我还想避开像数据库工具这样的工具。它们在调试时非常有用,但同样,你也可能在开发过程中使用它们。它们也是非常具体的供应商,所以这是一个广泛的主题,在这里无法涵盖。

我在这个系列中要介绍的工具包括以下几类:

  • 系统监控工具:如我们今天要讨论的strace和DTrace的情况
  • 网络监控器:也属于上述范畴,但需要有一个自己的类别
  • 虚拟机/运行时监控:例如,让我们检查JVM的工具,等等。
  • 剖析器和内存监视器

在这第一篇文章中,我想讨论两个重量级的冠军。DTrace和strace。如果你是一个Java开发者或Windows用户,你很有可能从未听说过这些工具。你可能无意中使用过其中的一个,因为许多工具都是建立在它们之上的,但情况可能并非如此。

这两个工具可以让你在没有源代码的情况下调试任何东西。你可以发现问题并获得你从未想象过的理解水平。

DTrace

早在2004年,我在Sun Microsystems工作时第一次听说了DTrace。它成为走廊上的热门话题,因为它是Sun Microsystems正在推广的一项创新。DTrace后来被移植到MacOS X上(它起源于Solaris)。今天,Windows和Linux上也有端口。

DTrace是一个强大的底层动态跟踪框架。但这只是另一个超级术语,而且,如果你从未使用过这样的工具,也没有系统编程的背景,你可能会感到有点困惑。它到底是做什么的?

它能让你 "看到 "一切。想知道一个进程打开了哪些文件?

好的

想知道谁调用了某个内核API,并得到调用者的堆栈跟踪吗?

好的

想知道一个进程为什么死亡?

好的

想知道在一个操作上花费了多少CPU时间?

OK

你可能认为DTrace是那种会完全破坏你的CPU的工具......但这里有一个致命的特点。它的速度快到可以在生产中运行,对性能的影响很小,甚至没有!它是一个革命性的工具。

它在近二十年前推出时是革命性的,直到今天仍然如此

运行 DTrace

在我们开始之前,先说一下警告。保存你的数据!

这个工具很容易使你的机器崩溃。启用它需要禁用MacOS上的重要安全设施。这是一个有风险的 "低级 "系统服务,应该如此对待。

在Mac上,DTrace与 "系统完整性保护 "相冲突,后者是一个安全功能,可以阻止进程之间的一些互动(除其他事项外)。在正常情况下,这将是很好的。但如果你想运行DTrace,这将是一个问题。

解决方案是在英特尔Mac上启动到恢复模式;这意味着在启动时按住Command-R 键。在ARM Mac上,只需长按电源键。

然后,在恢复模式的终端,发出命令:csrutil disable.

重启后,DTrace应该能如期工作。

基本用法

如前所述,DTrace是一个非常强大的工具。有很多书都是关于它的。它有自己的基于C语法的编程语言,你可以用它来建立复杂的逻辑。例如,下面的命令将记录来自给定回调的一些信息。

sudo DTrace -qn 'syscall::write:entry, syscall::sendto:entry /pid == $target/ { printf("(%d) %s %s", pid, probefunc, copyinstr(arg1)); }' -p [PID]

传递给DTrace命令的代码片段监听目标进程ID上的sendto回调。然后,它将信息打印到控制台,例如:(pid) text

如果这看起来有点多,而且太难上手...你是100%正确的。在你需要的时候,它是一个强大的工具。但对于我们大多数的日常使用,它实在是太强大了。我们要的是了解一些基本的东西。

简单的使用方法

运气好的话,我们有一个简单的解决方案。

壳牌

man -k DTrace

这将打印出一个值得一读的工具列表,以了解这个东西有多么广泛。下面是该命令的几行有趣的输出。

普通文本

bitesize.d(1m)           - analyse disk I/O size by process. Uses DTrace
dapptrace(1m)            - trace user and library function usage. Uses DTrace
errinfo(1m)              - print errno for syscall fails. Uses DTrace
iotop(1m)                - display top disk I/O events by process. Uses DTrace
plockstat(1)             - front-end to DTrace to print statistics about POSIX mutexes and read/write locks

值得你花时间去看这个列表,以了解你在这里可以真正做什么。

例子

你正面临着磁盘写入量增加的问题,这导致你的应用程序的性能下降......但是,是你的应用程序有问题还是其他应用程序有问题?

只要运行

sudo rwbypid.d

它将打印出对磁盘的读/写情况。

纯文本

   PID CMD                       DIR    COUNT
  2957 wordexp-helper              W        1
  2959 wc                          W        1
  2961 grep                        W        1

... snipped for clarity ...

   637 firefox                     R     6937
   637 firefox                     W    15325
   343 sentineld                   W   100287

安全软件确实在拖累性能......

你也可以使用bitesize.d ,以获得更具体的写入/分配的字节数的结果。

但这是相当高的水平。如果你想知道具体内容:文件名、进程名等,该怎么办?

外壳

sudo iosnoop -a

打印出包括几乎所有你需要的输出。

纯文本

STRTIME              DEVICE  MAJ MIN   UID   PID D      BLOCK     SIZE                     PATHNAME ARGS
2022 Jun 30 12:16:56 ??        1  17   501  1111 W  150777072     4096 ??/idb/3166453069wcaw.sqlite-wal firefox\0
2022 Jun 30 12:16:56 ??        1  17   501   661 W  150777175   487424  ??/index-dir/the-real-index Slack Helper\0
2022 Jun 30 12:16:57 ??        1  17   499   342 W  150777294     4096 ??/persistent/.dat.nosync0156.ztvXap sentineld\0

我可以看到进程的ID和它向特定文件写了多少字节!

假设你的程序跨越了进程,你想看看发生了什么事。例如,我在我建立的一个服务器中运行一个源代码构建。

sudo errinfo

这让我可以检测到从系统调用返回的错误以及最初触发它的命令。

纯文本

            EXEC          SYSCALL  ERR  DESC
    WindowServer workq_kernreturn   -2 
    WindowServer workq_kernreturn   -2 
   SentinelAgent workq_kernreturn   -2 
   SentinelAgent workq_kernreturn   -2 
          Signal           Helper    0 
          Google           Chrome    0 
           Brave          Browser    0 
          Google           Chrome    0

这些只是冰山一角。我建议你看看Oracle的这个老的DTrace教程 或书。免责声明:我没有读过这本书...

strace

有趣的是,strace工具也起源于90年代的Sun Microsystems。不过这并不奇怪,因为源于Sun Microsystems的技术清单绝对是令人头疼的。

Strace 在使用和功能上都比 DTrace 简单得多。有好有坏。由于DTrace需要操作系统的深度支持,它从未成为普通Linux发行版的官方功能,因此,人们在Linux上使用strace而不是DTrace。不过,它们并不完全可以互换。

strace的启用得益于被称为trace的内核特性。由于trace已经存在于Linux中,我们不需要添加额外的内核代码或模块。通常情况下,DTrace需要更深层次的内核支持,为了绕过Linux上的许可问题,它在一个单独的可加载模块中,但这仍然带来一些挑战。

与strace一起工作,就像我们每次调用内核时都要打印一个日志条目一样。这就为你执行的每条命令创造了非常详细的日志记录。因此,你可以了解一个运行中的进程内部情况。

运行strace

现在,strace在Linux中被普遍使用。它是该平台上我最喜欢的系统诊断工具。它工作起来非常方便,因为我们可以在没有特殊权限的情况下运行它。注意,与DTrace不同,你应该让strace远离生产环境(除非代码被隔离)。它的性能开销很大,会使生产系统崩溃。

strace 最基本的用法是把命令行传给它:

Shell

strace java -classpath. PrimeMain

strace的输出相当长,让我们来看看其中的几行:

Shell

execve("/home/ec2-user/jdk1.8.0_45/bin/java", ["java", "-classpath.", "PrimeMain"], 0x7fffd689ec20 /* 23 vars */) = 0
brk(NULL)                               = 0xb85000
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f0294272000
readlink("/proc/self/exe", "/home/ec2-user/jdk1.8.0_45/bin/j"..., 4096) = 35
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
open("/home/ec2-user/jdk1.8.0_45/bin/../lib/amd64/jli/tls/x86_64/libpthread.so.0", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat("/home/ec2-user/jdk1.8.0_45/bin/../lib/amd64/jli/tls/x86_64", 0x7fff37af09a0) = -1 ENOENT (No such file or directory)
open("/home/ec2-user/jdk1.8.0_45/bin/../lib/amd64/jli/tls/libpthread.so.0", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat("/home/ec2-user/jdk1.8.0_45/bin/../lib/amd64/jli/tls", 0x7fff37af09a0) = -1 ENOENT (No such file or directory)

这些行中的每一行都是Linux的系统调用。我们可以用谷歌搜索每一行,以了解发生了什么。这里有一个简单的例子。

Shell

open("/home/ec2-user/jdk1.8.0_45/bin/../lib/amd64/jli/tls/x86_64/libpthread.so.0", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)

Java 试图从tls 目录中加载pthread 库,使用一个系统开放调用来加载该文件。系统调用的退出代码是-1 ,这意味着该文件不在那里。在正常情况下,我们应该从这个API得到一个文件描述符的值。在目录中查看,似乎tls 目录丢失了。我猜测这是因为缺少JCE的安装。这可能没问题,但在某些情况下可能会很有趣。

很明显,输出量有时会让人不知所措。我们通常只想看看 "哪个文件被打开了 "和 "我们的网络调用是怎么回事 "这样的东西。我们可以通过使用-e 参数只看特定的系统调用来轻松实现。

strace -e open java -classpath . PrimeMain

将只显示打开的系统调用。

Shell

open("/home/ec2-user/jdk1.8.0_45/bin/../lib/amd64/jli/tls/x86_64/libpthread.so.0", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
open("/home/ec2-user/jdk1.8.0_45/bin/../lib/amd64/jli/tls/libpthread.so.0", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
open("/home/ec2-user/jdk1.8.0_45/bin/../lib/amd64/jli/x86_64/libpthread.so.0", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
open("/home/ec2-user/jdk1.8.0_45/bin/../lib/amd64/jli/libpthread.so.0", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
open("/home/ec2-user/jdk1.8.0_45/bin/../lib/amd64/tls/x86_64/libpthread.so.0", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
open("/home/ec2-user/jdk1.8.0_45/bin/../lib/amd64/tls/libpthread.so.0", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
open("/home/ec2-user/jdk1.8.0_45/bin/../lib/amd64/x86_64/libpthread.so.0", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
open("/home/ec2-user/jdk1.8.0_45/bin/../lib/amd64/libpthread.so.0", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
open("/lib64/libpthread.so.0", O_RDONLY|O_CLOEXEC) = 3
open("/home/ec2-user/jdk1.8.0_45/bin/../lib/amd64/jli/libjli.so", O_RDONLY|O_CLOEXEC) = 3
open("/home/ec2-user/jdk1.8.0_45/bin/../lib/amd64/jli/libdl.so.2", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)

你可以学习和使用许多系统调用来跟踪许多行为,例如:连接、写入等等。这只是你用strace能做的事情的冰山一角。Julia Evans写了一些关于strace的最详尽和最有趣的帖子。如果你想了解更多关于它的信息,可能没有比这更好的地方了(也可以看看她的其他东西......惊人的资源!)。

strace和Java

正如你之前所看到的,strace 在 JVM 上的工作非常好。由于 strace 早于 Java,并且是一个非常低级的工具,它对 JVM 没有认识。JVM 的工作方式与其他大多数平台一样,调用系统调用,你可以用它来调试其行为。然而,由于它对一些问题有独特的处理方法,有些方面用strace可能不那么明显。

一个很好的例子是分配。系统工具使用malloc,它映射到内核分配逻辑,但Java采取不同的路线。它管理自己的内存,以提高效率和更容易的垃圾回收逻辑。因此,内存分配的某些方面会被隐藏在strace输出中。这可以说是不幸中的万幸,因为输出有时会让人不知所措。

在写这篇文章的时候,线程与strace工作得很好。但今后可能就不是这样了,因为Loom项目可能会改变Java线程和系统线程之间的一对一映射。这可能会使strace的输出在大量线程的应用中更难确定。

最后

现在有很多不同形式的 "trace "工具,它们不断地相互借鉴。要跟上所有这些噪音是一个重大挑战。有太多的好工具可以介绍了,不过我想在以后的文章中讨论btrace。它与DTrace非常相似,但也是针对JVM的,所以它可能值得另起炉灶。

我今天讨论的这些工具采取了不同的方法来解决类似的问题:我们如何理解一个二进制程序 "真正 "做什么?安全研究人员和黑客使用这些工具来了解你的程序。他们不需要代码,也不需要反汇编,就能看到你到底在做什么。

你也可以用这些工具来了解你的行为的影响。我们经常调用一个API,让事情到此为止。但魔鬼在细节中,而这些细节会带来沉重的代价。作为一个Java开发者,我很少考虑信号传递、流程管理或其他这些低级别的主干。但我确实花时间去看这些东西,因为它们最终会影响我的应用程序的稳定性和性能。