一位同事曾经告诉我,当在 Linux 上调试一切都失败时,最后一个选择是使用strace。
我试图学习这个奇怪工具背后的科学,但我不是系统管理员,我并没有真正得到结果。
所以,
- 它到底是什么,它有什么作用?
- 应该如何以及在哪些情况下使用它?
- 应该如何理解和处理输出?
简而言之,简单来说,这些东西是如何工作的?
Strace 概述
strace 可以看作是一个轻量级的调试器。它允许程序员/用户快速了解程序如何与操作系统交互。它通过监视系统调用和信号来做到这一点。
当您没有源代码或不想被打扰去真正阅读它时使用很好。
此外,如果您不想打开 GDB,但只想了解外部交互,这对您自己的代码很有用。
一个很好的小介绍
前几天我遇到了这个关于 strace 使用的介绍:strace hello world
简而言之,strace 跟踪程序发出的所有系统调用及其返回码。考虑诸如文件/套接字操作之类的事情以及更多晦涩的事情。
如果您对 C 有一定的应用知识,这是最有用的,因为这里的系统调用将更准确地代表标准 C 库调用。
假设您的程序是 /usr/local/bin/cough。只需使用:
strace /usr/local/bin/cough <any required argument for cough here>
或者
strace -o <out_file> /usr/local/bin/cough <any required argument for cough here>
写入“out_file”。
所有 strace 输出都将转到 stderr(请注意,它的庞大数量通常要求重定向到文件)。在最简单的情况下,您的程序将因错误而中止,您将能够在 strace 输出中看到它与操作系统的最后一次交互在哪里。
更多信息应通过以下方式获得:
man strace
strace 列出了它所应用的进程完成的所有系统调用。如果您不知道系统调用的含义,那么您将无法从中获得很多好处。
不过,如果您的问题涉及文件或路径或环境值,则在有问题的程序上运行 strace 并将输出重定向到文件,然后为您的路径/文件/env 字符串 grepping 该文件可能会帮助您查看程序实际尝试的内容做,与你所期望的不同。
Strace 是一种用于调查生产系统的工具,您无法在调试器下运行这些程序。特别是,我们在以下两种情况下使用了 strace:
有关使用 strace 进行分析的示例,请参阅我对这个问题的回答。
我一直使用 strace 来调试权限问题。该技术是这样的:
$ strace -e trace=open,stat,read,write gnome-calculator
gnome-calculator
您要运行的命令在哪里。
Strace 可以用作调试工具,也可以用作原始分析器。
作为调试器,您可以看到给定的系统调用是如何被调用、执行的以及它们返回什么。这非常重要,因为它不仅可以让您看到程序失败,还可以看到程序失败的原因。通常这只是糟糕的编码没有捕捉到程序的所有可能结果的结果。其他时候,它只是硬编码的文件路径。如果没有 strace,您就可以猜测出问题的地方和方式。使用 strace,您可以获得系统调用的细分,通常只需查看返回值即可告诉您很多信息。
剖析是另一种用途。您可以使用它来单独或作为聚合来计时每个系统调用的执行时间。虽然这可能不足以解决您的问题,但它至少会大大缩小潜在嫌疑人的范围。如果您在单个文件上看到很多 fopen/close 对,那么您可能在每次执行循环时都不必要地打开和关闭文件,而不是在循环之外打开和关闭它。
Ltrace 是 strace 的近亲,也非常有用。你必须学会区分你的瓶颈在哪里。如果总执行时间是 8 秒,而你只在系统调用上花费了 0.05 秒,那么跟踪程序对你没有多大好处,问题出在你的代码中,这通常是逻辑问题,或者程序实际上需要跑那么久。
strace/ltrace 最大的问题是读取它们的输出。如果您不知道调用是如何进行的,或者至少不知道系统调用/函数的名称,那么就很难理解其含义。了解函数返回的内容也非常有益,尤其是对于不同的错误代码。虽然破译很痛苦,但它们有时真的会返回一颗知识的珍珠;一旦我看到我的 inode 用完了,但没有用完可用空间,因此所有常用的实用程序都没有给我任何警告,我就是无法创建新文件。从 strace 的输出中读取错误代码为我指明了正确的方向。
strace -tfp PID 将监控 PID 进程的系统调用,因此我们可以调试/监控我们的进程/程序状态。
最小可运行示例
如果一个概念不清楚,有一个你没见过的更简单的例子来解释它。
在本例中,该示例是 Linux x86_64 程序集独立式(无 libc)hello world:
你好.S
.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\n"
len = . - msg
组装并运行:
as -o hello.o hello.S
ld -o hello.out hello.o
./hello.out
输出预期:
hello
现在让我们在该示例中使用 strace:
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
在日志中显示更完整的信息strace.log
现在包含:
execve("./hello.out", ["./hello.out", "arg0", "arg1"], ["ASDF=qwer"]) = 0
write(1, "hello\n", 6) = 6
exit(0) = ?
+++ exited with 0 +++
有了这样一个最小的例子,输出的每一个字符都是不言而喻的:
execve
行:显示如何strace
执行hello.out
,包括 CLI 参数和环境,如记录在man execve
write
line:显示了我们所做的 write 系统调用。6
是字符串的长度"hello\n"
。
= 6
是系统调用的返回值,如中所述,它man 2 write
是写入的字节数。
exit
line: 显示我们所做的退出系统调用。没有返回值,因为程序退出了!
更复杂的例子
strace的应用当然是看复杂程序实际做了哪些系统调用来帮助调试/优化你的程序。
值得注意的是,您可能在 Linux 中遇到的大多数系统调用都有 glibc 包装器,其中许多来自 POSIX。
在内部,glibc 包装器或多或少像这样使用内联汇编:How to invoke a system call via sysenter in inline assembly?
您应该学习的下一个示例是 POSIX write
hello world:
主程序
#define _XOPEN_SOURCE 700
#include <unistd.h>
int main(void) {
char *msg = "hello\n";
write(1, msg, 6);
return 0;
}
编译并运行:
gcc -std=c99 -Wall -Wextra -pedantic -o main.out main.c
./main.out
这一次,您将看到 glibc 之前main
进行了一系列系统调用,以便为 main 设置一个良好的环境。
这是因为我们现在使用的不是独立程序,而是更常见的 glibc 程序,它允许 libc 功能。
然后,在每一端,strace.log
包含:
write(1, "hello\n", 6) = 6
exit_group(0) = ?
+++ exited with 0 +++
因此我们得出结论,write
POSIX 函数使用了 Linuxwrite
系统调用,令人惊讶!
我们还观察到return 0
导致exit_group
调用而不是exit
. 哈,我不知道这个!这就是为什么strace
这么酷。man exit_group
然后解释:
这个系统调用等价于 exit(2),除了它不仅终止调用线程,而且终止调用进程的线程组中的所有线程。
这是我研究使用哪个系统调用的另一个示例dlopen
:https ://unix.stackexchange.com/questions/226524/what-system-call-is-used-to-load-libraries-in-linux/462710#462710
在 Ubuntu 16.04、GCC 6.4.0、Linux 内核 4.4.0 中测试。
Strace 是一个工具,可以告诉您应用程序如何与操作系统交互。
它通过告诉您应用程序使用的操作系统系统调用以及调用它们的参数来做到这一点。
因此,例如,您会看到程序尝试打开哪些文件,并判断调用是否成功。
您可以使用此工具调试各种问题。例如,如果应用程序说它找不到您知道已安装的库,那么 strace 会告诉您应用程序在哪里寻找该文件。
而这只是冰山一角。
strace 是一个很好的工具,用于了解您的程序如何进行各种系统调用(对内核的请求),并且还报告失败的系统调用以及与该失败相关的错误值。并非所有的失败都是错误。例如,尝试搜索文件的代码可能会收到 ENOENT(没有此类文件或目录)错误,但这可能是代码逻辑中可接受的场景。
使用 strace 的一个很好的用例是在临时文件创建期间调试竞争条件。例如,可能通过将进程 ID (PID) 附加到某些预先确定的字符串来创建文件的程序在多线程场景中可能会遇到问题。[PID+TID(进程 ID + 线程 ID)或更好的系统调用,如 mkstemp 将解决此问题]。
它也适用于调试崩溃。您可能会发现这篇(我的)关于 strace 和调试崩溃的文章很有用。
以下是我如何使用 strace 挖掘网站的一些示例。希望这会有所帮助。
检查第一个字节的时间,如下所示:
time php index.php > timeTrace.txt
看看有多少百分比的动作在做什么。很多lstat
并且fstat
可能表明是时候清除缓存了:
strace -s 200 -c php index.php > traceLstat.txt
输出 atrace.txt
这样您就可以准确地看到正在进行的调用。
strace -Tt -o Fulltrace.txt php index.php
使用它来检查是否有任何东西需要.1
一.9
秒钟才能加载:
cat Fulltrace.txt | grep "[<]0.[1-9]" > traceSlowest.txt
查看strace
. 这将输出很多涉及我们系统的内容——唯一相关的部分涉及客户的文件:
strace -vv php index.php 2>&1 | sed -n '/= -1/p' > traceFailures.txt
我喜欢其中一些答案,它读取strace
检查您如何与操作系统交互。
这正是我们可以看到的。系统调用。如果对比一下strace
,ltrace
差别就更明显了。
$>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
另一方面是ltrace
跟踪功能。
$>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
虽然我查了几次手册,但我没有找到名称的来源,strace
但很可能是系统调用跟踪,因为这很明显。
有三个更大的注意事项要说strace
。
注1:这两个函数strace
都ltrace
使用系统调用ptrace
。所以ptrace
系统调用是有效的strace
工作方式。
ptrace() 系统调用提供了一种方法,一个进程(“跟踪器”)可以通过该方法观察和控制另一个进程(“跟踪者”)的执行,并检查和更改跟踪者的内存和寄存器。主要用于实现断点调试和系统调用跟踪。
注意 2:您可以使用不同的参数strace
,因为strace
可能非常冗长。我喜欢实验,-c
这就像对事物的总结。根据-c
您可以选择一个系统调用,例如-e trace=open
您只会看到该调用的位置。如果您正在检查在您正在跟踪的命令期间将打开哪些文件,这可能会很有趣。当然,您可以将grep
用于相同目的,但请注意,您需要像这样重定向2>&1 | grep etc
以了解在发出命令时引用了配置文件。
注意 3:我发现这个非常重要的注释。您不限于特定的架构。strace
会让你大吃一惊,因为它可以追踪不同架构的二进制文件。