关于linux:应该如何使用strace?

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

其中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显示有关日志的更全面信息

strace.log现在包含:

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 write hello world:

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在main之前正在进行一系列系统调用,以便为main设置一个良好的环境。

这是因为我们现在不使用独立程序,而是使用更常见的glibc程序,它允许libc功能。

然后,在每一端,strace.log包含:

1
2
3
4
write(1,"hello
", 6)                  = 6
exit_group(0)                           = ?
+++ exited with 0 +++

因此,我们得出结论,write POSIX函数使用了令人惊讶的Linux write系统调用。

我们还观察到return 0导致exit_group调用而不是exit。哈,我不知道这一个!这就是strace如此酷的原因。 man exit_group然后解释:

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.

这是我研究了哪个系统调用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检查您与操作系统的交互方式。

这正是我们所能看到的。系统调用。如果比较straceltrace,差异就更明显了。

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

另一方面,有跟踪函数的ltrace

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

虽然我多次检查手册,但我没有找到名称strace的来源,但它很可能是系统调用跟踪,因为这很明显。

关于strace,有三个更大的注释要说。

注1:这些函数straceltrace都使用系统调用ptrace。所以ptrace系统调用实际上是strace的工作方式。

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:strace可以使用不同的参数,因为strace可能非常详细。我喜欢尝试-c,这就像是对事物的总结。基于-c,您可以选择一个系统调用,如-e trace=open,您将只看到该调用。如果您正在检查在跟踪命令期间将打开哪些文件,这可能会很有趣。
当然,您可以将grep用于相同目的,但请注意,您需要像2>&1 | grep etc一样重定向,以了解在发出命令时引用配置文件。

注3:我发现这个非常重要的注意事项。您不仅限于特定的架构。 strace会让你大吃一惊,因为它可以追踪不同架构的二进制文件。
enter image description here