How should strace be used?
一位同事曾告诉我,当Linux上的所有内容都无法调试时,最后一个选项是使用strace。
我试图学习这个奇怪工具背后的科学,但我不是系统管理大师,我没有真正得到结果。
所以,
- 究竟是什么,它做了什么?
- 应该如何以及在何种情况下使用它?
- 如何理解和处理输出?
简而言之,简单来说,这些东西是如何工作的?
Strace概述
strace可以看作是轻量级调试器。它允许程序员/用户快速了解程序如何与OS交互。它通过监视系统调用和信号来实现。
用途
当你没有源代码或者不想被打扰时真的很好。
此外,如果您不想打开GDB,但只是对了解外部交互感兴趣,则对您自己的代码很有用。
一个很好的小介绍
前几天我遇到了这个介绍,以便使用strace hello world
简单来说,strace跟踪程序发出的所有系统调用及其返回代码。想一想像文件/套接字操作这样的东西,以及更多不起眼的操作。
如果你有一些C的工作知识,这是非常有用的,因为这里系统调用更准确地代表标准C库调用。
假设你的程序是/ usr / local / bin / cough。只需使用:
1 | strace /usr/local/bin/cough |
要么
1 | strace -o <out_file> /usr/local/bin/cough |
写入'out_file'。
所有strace输出都将转到stderr(请注意,它的绝对数量通常会要求重定向到文件)。在最简单的情况下,您的程序将中止错误,您将能够看到它在strace输出中与OS的最后交互。
应提供更多信息:
1 | man strace |
strace列出了应用它的进程完成的所有系统调用。如果您不知道系统调用的意思,那么您将无法获得多少里程数。
然而,如果您的问题涉及文件或路径或环境值,在有问题的程序上运行strace并将输出重定向到文件然后为您的路径/文件/ env字符串grepping该文件可能会帮助您查看您的程序实际尝试的内容做,与你的预期截然不同。
Strace作为调查生产系统的工具脱颖而出,您无法在调试器下运行这些程序。特别是,我们在以下两种情况下使用了strace:
- 程序foo似乎陷入僵局并且变得反应迟钝。这可能是gdb的目标;但是,我们并不总是有源代码,或者有时候处理的脚本语言不是直接在调试器下运行。在这种情况下,您在已经运行的程序上运行strace,您将获得正在进行的系统调用列表。如果您正在研究客户端/服务器应用程序或与数据库交互的应用程序,这将特别有用
- 调查程序缓慢的原因。特别是,我们刚刚迁移到新的分布式文件系统,系统的新吞吐量非常慢。您可以使用'-T'选项指定strace,它将告诉您每次系统调用花费了多少时间。这有助于确定文件系统导致速度变慢的原因。
有关使用strace进行分析的示例,请参阅我对此问题的回答。
我一直使用strace来调试权限问题。技术如下:
1 | $ strace -e trace=open,stat,read,write gnome-calculator |
其中
strace -tfp PID将监视PID进程的系统调用,因此我们可以调试/监视我们的进程/程序状态。
Strace可以用作调试工具,也可以用作原始分析器。
作为调试器,您可以看到给定的系统调用如何被调用,执行以及它们返回的内容。这非常重要,因为它不仅可以让您看到程序失败,还可以看到程序失败的原因。通常,这只是糟糕的编码没有捕捉到程序的所有可能结果的结果。其他时候它只是文件的硬编码路径。没有strace,你可以猜到在哪里以及如何出错。使用strace,您可以获得系统调用的细分,通常只是查看返回值会告诉您很多。
分析是另一种用途。您可以使用它来单独执行每个系统调用,或者作为聚合执行。虽然这可能不足以解决您的问题,但它至少会大大缩小潜在嫌疑人名单的范围。如果你在一个文件上看到很多fopen / close对,你可能不必在每次执行循环时都打开和关闭文件,而不是在循环之外打开和关闭它。
Ltrace是strace的近亲,也非常有用。你必须学会??区分你的瓶颈所在。如果总执行时间为8秒,并且您在系统调用上只花费了0.05秒,那么对程序进行支持并不会对您有好处,问题在于您的代码,这通常是一个逻辑问题,或者程序实际需要花那么长时间才能跑。
strace / ltrace的最大问题是读取它们的输出。如果您不知道如何进行调用,或者至少知道系统调用/函数的名称,那么解释其含义就很困难了。了解函数返回的内容也非常有用,尤其是对于不同的错误代码。虽然破译是一种痛苦,但它们有时真的会回归知识的一颗珍珠;一旦我看到我用完inode但没有空闲空间的情况,所以所有常用的实用工具都没有给我任何警告,我只是无法创建一个新文件。从strace的输出中读取错误代码指向了正确的方向。
strace是一个很好的工具,用于了解程序如何进行各种系统调用(对内核的请求),并报告失败的程序以及与该故障相关的错误值。并非所有的失败都是错误。例如,尝试搜索文件的代码可能会获得ENOENT(无此类文件或目录)错误,但这可能是代码逻辑中可接受的方案。
使用strace的一个好用例是在临时文件创建期间调试竞争条件。例如,可能通过将进程ID(PID)附加到某些预定字符串来创建文件的程序可能在多线程场景中遇到问题。 [PID + TID(进程ID +线程ID)或更好的系统调用,如mkstemp将解决此问题]。
它也适用于调试崩溃。您可能会发现有关strace和调试崩溃的这篇(我的)文章很有用。
Strace是一个工具,可以告诉您应用程序如何与您的操作系统进行交互。
它通过告诉您应用程序使用的OS系统调用以及调用它们的参数来实现此目的。
因此,举例来说,您会看到程序尝试打开哪些文件,并使调用成功。
您可以使用此工具调试各种问题。例如,如果应用程序说它找不到您知道已安装的库,那么strace会告诉您应用程序在哪里查找该文件。
这只是冰山一角。
最小的可运行示例
如果一个概念不清楚,有一个更简单的例子,你没有看到解释它。
在这种情况下,该示例是Linux x86_64程序集独立(无libc)hello world:
hello.S
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | .text .global _start _start: /* write */ mov $1, %rax /* syscall number */ mov $1, %rdi /* stdout */ mov $msg, %rsi /* buffer */ mov $len, %rdx /* buffer len */ syscall /* exit */ mov $60, %rax /* exit status */ mov $0, %rdi /* syscall number */ syscall msg: .ascii"hello " len = . - msg |
GitHub上游。
组装并运行:
1 2 3 | as -o hello.o hello.S ld -o hello.out hello.o ./hello.out |
输出预期的:
1 | hello |
现在让我们在该示例中使用strace:
1 2 | env -i ASDF=qwer strace -o strace.log -s999 -v ./hello.out arg0 arg1 cat strace.log |
我们用:
-
env -i ASDF=qwer 控制环境变量:https://unix.stackexchange.com/questions/48994/how-to-run-a-program-in-a-clean-environment-in-bash -
-s999 -v 显示有关日志的更全面信息
1 2 3 4 5 | execve("./hello.out", ["./hello.out","arg0","arg1"], ["ASDF=qwer"]) = 0 write(1,"hello ", 6) = 6 exit(0) = ? +++ exited with 0 +++ |
有了这么小的例子,输出的每个字符都是不言而喻的:
-
execve 行:显示strace 如何执行hello.out ,包括man execve 中记录的CLI参数和环境 -
write line:显示我们进行的写入系统调用。6 是字符串"hello 的长度。
"= 6 是系统调用的返回值,如man 2 write 中所述,是写入的字节数。 -
exit line:显示我们已经进行的退出系统调用。由于程序退出,因此没有返回值!
更复杂的例子
strace的应用当然是为了看看复杂程序实际上正在做什么来帮助调试/优化你的程序。
值得注意的是,您在Linux中可能遇到的大多数系统调用都有glibc包装器,其中许多来自POSIX。
在内部,glibc包装器或多或少使用内联汇编:如何通过内联汇编中的sysenter调用系统调用?
您应该学习的下一个示例是POSIX
main.c中
1 2 3 4 5 6 7 8 9 | #define _XOPEN_SOURCE 700 #include <unistd.h> int main(void) { char *msg ="hello "; write(1, msg, 6); return 0; } |
编译并运行:
1 2 | gcc -std=c99 -Wall -Wextra -pedantic -o main.out main.c ./main.out |
这一次,您将看到glibc在
这是因为我们现在不使用独立程序,而是使用更常见的glibc程序,它允许libc功能。
然后,在每一端,
1 2 3 4 | write(1,"hello ", 6) = 6 exit_group(0) = ? +++ exited with 0 +++ |
因此,我们得出结论,
我们还观察到
This system call is equivalent to exit(2) except that it terminates not only the calling thread, but all threads in the calling process's thread group.
这是我研究了哪个系统调用
在Ubuntu 16.04,GCC 6.4.0,Linux内核4.4.0中测试。
我喜欢其中的一些答案,其中
这正是我们所能看到的。系统调用。如果比较
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | $>strace -c cd Desktop Documents Downloads examples.desktop Music Pictures Public Templates Videos % time seconds usecs/call calls errors syscall ------ ----------- ----------- --------- --------- ---------------- 0.00 0.000000 0 7 read 0.00 0.000000 0 1 write 0.00 0.000000 0 11 close 0.00 0.000000 0 10 fstat 0.00 0.000000 0 17 mmap 0.00 0.000000 0 12 mprotect 0.00 0.000000 0 1 munmap 0.00 0.000000 0 3 brk 0.00 0.000000 0 2 rt_sigaction 0.00 0.000000 0 1 rt_sigprocmask 0.00 0.000000 0 2 ioctl 0.00 0.000000 0 8 8 access 0.00 0.000000 0 1 execve 0.00 0.000000 0 2 getdents 0.00 0.000000 0 2 2 statfs 0.00 0.000000 0 1 arch_prctl 0.00 0.000000 0 1 set_tid_address 0.00 0.000000 0 9 openat 0.00 0.000000 0 1 set_robust_list 0.00 0.000000 0 1 prlimit64 ------ ----------- ----------- --------- --------- ---------------- 100.00 0.000000 93 10 total |
另一方面,有跟踪函数的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | $>ltrace -c cd Desktop Documents Downloads examples.desktop Music Pictures Public Templates Videos % time seconds usecs/call calls function ------ ----------- ----------- --------- -------------------- 15.52 0.004946 329 15 memcpy 13.34 0.004249 94 45 __ctype_get_mb_cur_max 12.87 0.004099 2049 2 fclose 12.12 0.003861 83 46 strlen 10.96 0.003491 109 32 __errno_location 10.37 0.003303 117 28 readdir 8.41 0.002679 133 20 strcoll 5.62 0.001791 111 16 __overflow 3.24 0.001032 114 9 fwrite_unlocked 1.26 0.000400 100 4 __freading 1.17 0.000372 41 9 getenv 0.70 0.000222 111 2 fflush 0.67 0.000214 107 2 __fpending 0.64 0.000203 101 2 fileno 0.62 0.000196 196 1 closedir 0.43 0.000138 138 1 setlocale 0.36 0.000114 114 1 _setjmp 0.31 0.000098 98 1 realloc 0.25 0.000080 80 1 bindtextdomain 0.21 0.000068 68 1 opendir 0.19 0.000062 62 1 strrchr 0.18 0.000056 56 1 isatty 0.16 0.000051 51 1 ioctl 0.15 0.000047 47 1 getopt_long 0.14 0.000045 45 1 textdomain 0.13 0.000042 42 1 __cxa_atexit ------ ----------- ----------- --------- -------------------- 100.00 0.031859 244 total |
虽然我多次检查手册,但我没有找到名称
关于
注1:这些函数
The ptrace() system call provides a means by which one process (the
"tracer") may observe and control the execution of another process
(the"tracee"), and examine and change the tracee's memory and
registers. It is primarily used to implement breakpoint debugging
and system call tracing.
注意2:
当然,您可以将
注3:我发现这个非常重要的注意事项。您不仅限于特定的架构。