dtrace与btrace的差别
使用这些惊人的工具,无需源代码或对环境的深入了解即可跟踪应用程序中的错误及其外部依赖项!通常,在调试时,我们需要跳出IDE的舒适范围来重现或跟踪问题。在本系列中,我想介绍一些您可能会发现对这些情况有用的工具。
使用这些惊人的工具,无需源代码或对环境的深入了解即可跟踪应用程序中的错误及其外部依赖项!
通常,在调试时,我们需要跳出 IDE 的舒适范围来重现或跟踪问题。在本系列中,我想介绍一些您可能会发现对这些情况有用的工具。我会尽量限制自己使用 100% 调试工具,而不是那些对开发测试有用的工具。
例如,curl或jq等工具非常有用。您可以/应该在调试时使用它们。但是您可能在构建和测试功能时使用了它们。因此,您应该已经熟悉它们,并且应该对它们的工作有所了解。我想专注于调试时最常使用的工具。从这个意义上说,SDKMan等工具在这里也没有任何意义。
我还想避免使用数据库工具等工具。它们在调试时非常有用,但同样,您也可能在开发过程中使用它们。它们也是非常特定于供应商的,因此这是一个广泛的主题,无法在此处涵盖。
我将在本系列中介绍的工具包括以下类别:
- 系统监控工具:就像我们今天要讨论的 strace 和 DTrace 一样
- 网络监视器:也属于上述范围,但需要属于自己的类别
- VM/运行时监控:例如,让我们检查 JVM 的工具等。
- 分析器和内存监视器
在第一篇文章中,我想讨论两个重量级冠军:DTrace 和 strace。如果您是 Java 开发人员或 Windows 用户,那么您很有可能从未听说过这些工具。您可能无意中使用了其中一个,因为在它们之上构建了如此多的工具,但情况可能并非如此。
这两种工具都可以让您在没有源代码的情况下调试任何东西。您可以发现问题并获得您从未想象过的理解水平。
DTrace
早在 2004 年,我在 Sun Microsystems 工作时第一次听说 DTrace。它在走廊里风靡一时,因为它是 Sun Microsystems 正在推广的一项创新。DTrace 后来被移植到 MacOS X(它起源于 Solaris)。今天,Windows 和 Linux 上也有端口。
DTrace 是一个强大的低级动态跟踪框架。但这只是另一个最高级,而且,如果您从未使用过这样的工具并且没有系统编程背景,您可能会感到有点困惑:它到底是做什么的?
它让你“看到”一切。想知道进程打开了哪些文件?
好的
想知道谁调用了内核 API 并获得调用者的堆栈跟踪?
好的
想知道一个进程为什么会死掉?
好的
想知道一个操作花费了多少 CPU 时间?
好的
您可能会认为 DTrace 是一种会彻底破坏您的 CPU 的工具……但这里有一个杀手级功能:它足够快,可以在生产环境中运行,而对性能影响最小甚至没有!
它在近二十年前推出时是革命性的,直到今天仍然如此!
运行 DTrace
在我们开始之前,先警告一句。保存您的数据!
这个工具很容易让你的机器崩溃。启用它需要禁用 MacOS 上的重要安全设施。这是一个有风险的“低级”系统服务,应该这样对待。
在 Mac 上,DTrace 与“系统完整性保护”冲突,后者是一种安全功能,可阻止进程之间的某些交互(除其他外)。在正常情况下,这会很棒。但是如果你想运行 DTrace,这将是一个问题。
解决方案是在 Intel 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]br
传递给 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 的启用要归功于称为 ptrace 的内核功能。由于 ptrace 已经在 Linux 中,我们不需要添加额外的内核代码或模块。通常,DTrace 需要更深入的内核支持,以解决 Linux 上的许可问题,它位于单独的可加载模块中,但这仍然存在一些挑战。
使用 strace 类似于每次我们进行内核调用时打印一个日志条目。这会为您执行的每个命令创建非常详细的日志记录。因此,您可以了解正在运行的进程背后 的真实情况。
运行 strace
现在,strace 在 Linux 中很常用。这是该平台上我最喜欢的系统诊断工具。使用它非常方便,因为我们可以在没有特殊权限的情况下运行它。请注意,与 DTrace 不同,您应该使 strace 远离生产环境(除非代码是隔离的)。它会带来巨大的性能开销,并且会导致生产系统停机。
strace 最基本的用法只是将命令行传递给它:
strace java -classpath. PrimeMain
strace 的输出很长,我们来看几行:
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 系统调用。我们可以用谷歌搜索他们每个人,以了解发生了什么。这是一个简单的例子:
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)br
Java 尝试使用系统打开调用pthread从目录中加载库以加载文件。tls系统调用的退出码是-1,表示文件不存在。在正常情况下,我们应该从这个 API 中取回一个文件描述符值。查看目录,似乎tls缺少目录。我猜这是因为缺少 JCE 安装。这可能没问题,但在某些情况下可能很有趣。
显然,有时输出量是压倒性的。我们通常只想查看“打开了哪个文件”和“我们的网络调用发生了什么”之类的内容。我们可以通过仅查看使用-e参数的特定系统调用来轻松实现这一点。
strace -e open java -classpath . PrimeMain
只会显示打开的系统调用:
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 上写了一些最详尽、最有趣的帖子。如果您想了解更多关于它的信息,可能没有比这更好的地方了(还可以查看她的其他资料……惊人的资源!)。
![dtrace与btrace的差别](https://www.anshangmei.com/wp-content/themes/justnews6.0.3/themer/assets/images/lazy.png)
strace 和 Java
正如您之前看到的,strace 与 JVM 配合得很好。由于 strace 早于 Java 并且是一个非常低级的工具,因此它不了解 JVM。JVM 与大多数其他平台一样工作,并调用可用于调试其行为的系统调用。但是,由于它对某些问题的独特处理方法,某些方面可能不会像 strace 那样可见。
一个很好的例子是分配。系统工具使用 malloc,它映射到内核分配逻辑,但 Java 采用不同的路线。它管理自己的内存以提高效率并简化垃圾收集逻辑。因此,内存分配的某些方面将从 strace 输出中隐藏。这可能是因祸得福,因为有时输出可能是压倒性的。
在撰写本文时,线程与 strace 配合得很好。但未来可能并非如此,因为Loom 项目可能会改变 Java 线程和系统线程之间的一对一映射。这可能会使 strace 输出更难在重线程应用程序中查明。
最后
有各种形式的“*trace”实用程序的字母汤,它们不断地相互借鉴想法。跟上所有这些噪音是一项重大挑战。有太多很棒的工具需要介绍,不过我想在以后的文章中讨论 btrace。它与 DTrace 非常相似,但也非常特定于 JVM,因此可能值得另外发表一篇文章。
我今天讨论的工具采用不同的方法来解决类似的问题:我们如何理解二进制应用程序“真正”做了什么?安全研究人员和黑客使用这些工具来了解您的程序。他们不需要代码,也不需要反汇编来查看您实际在做什么。
您还可以使用这些工具来了解您的操作的影响。我们经常调用 API 并让事情到此结束。但魔鬼在细节中,而这些细节可能会带来沉重的代价。作为一名 Java 开发人员,我很少考虑信号传递、进程管理或其他此类低级主要内容。但我确实会花时间研究这些东西,因为它们最终会影响我的应用程序的稳定性和性能。
如若转载,请注明出处:https://www.anshangmei.com/17223.html