一位同事曾经告诉我,当Linux上所有调试都失败时,最后的选择是使用strace。

我试图学习这个奇怪工具背后的科学,但我不是系统管理专家,我没有真正得到结果。

So,

它到底是什么,有什么作用? 如何以及在哪些情况下使用它? 应该如何理解和处理输出?

简而言之,简单地说,这东西是怎么工作的?


当前回答

下面是我如何使用strace挖掘网站的一些例子。希望这对你有帮助。

检查时间到第一个字节,如下所示:

time php index.php > timeTrace.txt

看看有多少百分比的动作在做什么。大量的lstat和fstat可能表明是时候清理缓存了:

strace -s 200 -c php index.php > traceLstat.txt

输出trace.txt,以便您可以准确地看到正在进行的调用。

strace -Tt -o Fulltrace.txt php index.php

使用它来检查是否有东西在0.1到0.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会告诉你应用程序在哪里寻找那个文件。

而这只是冰山一角。

简单地说,strace跟踪程序发出的所有系统调用及其返回码。想想诸如文件/套接字操作和许多更模糊的操作。

如果你有一些C的工作知识,这是最有用的,因为这里的系统调用更准确地代表标准C库调用。

假设您的程序是/usr/local/bin/cough。简单的使用方法:

strace /usr/local/bin/cough <any required argument for cough here>

or

strace -o <out_file> /usr/local/bin/cough <any required argument for cough here>

写入'out_file'。

所有strace输出都将转到stderr(注意,它的巨大容量通常要求重定向到文件)。在最简单的情况下,您的程序将因错误而中止,您将能够在strace输出中看到它与操作系统的最后一次交互。

如欲获得更多资料,请浏览:

man strace

最小可运行示例

如果一个概念不清楚,有一个你没有见过的更简单的例子可以解释它。

在本例中,这个例子是Linux x86_64程序集独立(无libc) hello world:

你好。年代

.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

GitHub上游。

组装和运行:

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 +++

在这样一个最小的例子中,输出的每个字符都是不言而喻的:

执行行:显示strace如何执行hello。包括CLI参数和man execve中记录的环境 写行:显示我们所做的写系统调用。6是字符串“hello\n”的长度。 = 6是系统调用的返回值,在man 2 write中记录的是写入的字节数。 退出行:显示我们所做的退出系统调用。没有返回值,因为程序退出了!

更复杂的例子

当然,strace的应用是为了查看复杂程序实际上执行了哪些系统调用,以帮助调试/优化程序。

值得注意的是,您在Linux中可能遇到的大多数系统调用都有glibc包装器,其中许多来自POSIX。

在内部,glibc包装器或多或少像这样使用内联汇编:如何在内联汇编中通过sysenter调用系统调用?

你应该学习的下一个例子是POSIX write hello world:

c

#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 +++

因此我们得出结论,写POSIX函数使用,惊喜!, Linux写系统调用。

我们还观察到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 -tfp PID将监控PID进程的系统调用,因此我们可以调试/监控我们的进程/程序状态。

Strace是一个很好的工具,用于了解程序如何进行各种系统调用(对内核的请求),并报告失败的调用以及与该失败相关的错误值。并不是所有的失败都是bug。例如,试图搜索文件的代码可能会得到ENOENT(没有这样的文件或目录)错误,但这可能是代码逻辑中可以接受的场景。

使用strace的一个很好的用例是在临时文件创建期间调试竞态条件。例如,通过将进程ID (PID)附加到某个预先确定的字符串来创建文件的程序在多线程场景中可能会遇到问题。PID+TID(进程id +线程id)或更好的系统调用(如mkstemp)将修复此问题。

它还适用于调试崩溃。您可能会发现这篇(我的)关于strace和调试崩溃的文章很有用。