使用strace了解Linux中的系统调用

本文的翻译特别是为基础高级课程Administrator Linux的学生准备的






syscall是用户程序与Linux内核交互的机制,而strace是跟踪它们的强大工具。为了更好地了解操作系统的工作原理,了解它们的工作原理很有帮助。



操作系统可以分为两种操作模式:



  • 内核模式是操作系统内核使用的特权模式。
  • 用户模式是大多数用户应用程序运行的模式。




用户通常在日常工作中使用命令行实用程序和图形界面(GUI)。同时,系统调用在后台不可见地工作,并访问内核来完成工作。



系统调用与函数调用非常相似,因为它们是传递的参数并返回值。唯一的区别是系统调用在内核级别起作用,而函数则不起作用。使用特殊的中断机制可以从用户模式切换到内核模式



这些细节中的大多数细节在系统库(Linux系统上为glibc)中对用户隐藏。系统调用本质上是通用的,但是尽管如此,它们的执行机制很大程度上取决于硬件。



本文探讨了一些使用解析系统调用的实际示例strace。这些示例使用Red Hat Enterprise Linux,但是所有命令都应在其他Linux发行版上起作用:



[root@sandbox ~]# cat /etc/redhat-release
Red Hat Enterprise Linux Server release 7.7 (Maipo)
[root@sandbox ~]#
[root@sandbox ~]# uname -r
3.10.0-1062.el7.x86_64
[root@sandbox ~]#




首先,请确保您已在系统上安装了必要的工具。strace可以使用以下命令检查是否安装要查看版本,请strace使用-V参数运行它:



[root@sandbox ~]# rpm -qa | grep -i strace
strace-4.12-9.el7.x86_64
[root@sandbox ~]#
[root@sandbox ~]# strace -V
strace -- version 4.12
[root@sandbox ~]#




如果strace未安装,请运行以下命令进行安装:



yum install strace




例如,/tmp使用以下命令两个文件中创建一个测试目录touch



[root@sandbox ~]# cd /tmp/
[root@sandbox tmp]#
[root@sandbox tmp]# mkdir testdir
[root@sandbox tmp]#
[root@sandbox tmp]# touch testdir/file1
[root@sandbox tmp]# touch testdir/file2
[root@sandbox tmp]#




(我/tmp使用目录,因为每个人都可以访问该目录,但是您可以使用任何其他目录。)



使用以下命令ls检查testdir目录中是否已创建文件:



[root@sandbox tmp]# ls testdir/
file1  file2
[root@sandbox tmp]#




您可能ls每天都在使用该命令,而没有意识到系统调用在后台运行。这就是抽象发挥作用的地方。该命令的工作方式如下:



   ->    (glibc) ->  




该命令ls从Linux系统库(glibc)调用函数。这些库又调用系统调用,该系统调用完成了大部分工作。



如果您想知道从glibc库中调用了哪些函数,请使用ltrace以下命令ls testdir/



ltrace ls testdir/




如果ltrace未安装,请安装:



yum install ltrace




屏幕上会显示很多信息,但请放心-我们稍后会介绍。以下是输出中的一些重要库函数ltrace



opendir("testdir/")                                  = { 3 }
readdir({ 3 })                                       = { 101879119, "." }
readdir({ 3 })                                       = { 134, ".." }
readdir({ 3 })                                       = { 101879120, "file1" }
strlen("file1")                                      = 5
memcpy(0x1665be0, "file1\0", 6)                      = 0x1665be0
readdir({ 3 })                                       = { 101879122, "file2" }
strlen("file2")                                      = 5
memcpy(0x166dcb0, "file2\0", 6)                      = 0x166dcb0
readdir({ 3 })                                       = nil
closedir({ 3 })    




通过检查此输出,您可能可以了解发生了什么。testdir使用库函数打开指定的目录opendir,然后调用readdir读取目录内容的函数。最后,调用一个函数closedir来关闭先前打开的目录。现在,请忽略其他功能,例如strlenmemcpy



如您所见,您可以轻松地看到正在调用的库函数,但是在本文中,我们将重点介绍由系统库函数调用的系统调用。



要查看系统调用,请使用如下所示strace的命令ls testdir。再一次,您会得到一堆不连贯的信息:



[root@sandbox tmp]# strace ls testdir/
execve("/usr/bin/ls", ["ls", "testdir/"], [/* 40 vars */]) = 0
brk(NULL)                               = 0x1f12000
<<< truncated strace output >>>
write(1, "file1  file2\n", 13file1  file2
)          = 13
close(1)                                = 0
munmap(0x7fd002c8d000, 4096)            = 0
close(2)                                = 0
exit_group(0)                           = ?
+++ exited with 0 +++
[root@sandbox tmp]#


作为执行的结果,strace您将收到在命令执行期间执行的系统调用的列表ls所有系统调用可以分为以下类别:



  • 流程管理
  • 文件管理
  • 目录和文件系统管理
  • 其他




有一种分析接收到的信息的简便方法-使用选项将输出写入文件-o



[root@sandbox tmp]# strace -o trace.log ls testdir/
file1  file2
[root@sandbox tmp]#




这次,屏幕上将没有数据-该命令将按ls预期工作,显示文件列表并将所有输出写入strace文件trace.log对于一个简单的命令,该ls文件包含近100行:



[root@sandbox tmp]# ls -l trace.log
-rw-r--r--. 1 root root 7809 Oct 12 13:52 trace.log
[root@sandbox tmp]#
[root@sandbox tmp]# wc -l trace.log
114 trace.log
[root@sandbox tmp]#




看一下文件的第一行trace.log



execve("/usr/bin/ls", ["ls", "testdir/"], [/* 40 vars */]) = 0




  • 该行的开头是正在执行的系统调用的名称-execve。
  • 括号中的文本是传递给系统调用的参数。
  • =号后的数字(在本例中为0)是系统调用返回的值。




现在结果似乎并不可怕,不是吗?您也可以将相同的逻辑应用于其他行。



注意您调用的唯一命令- ls testdir您知道命令使用的目录名称ls,那么为什么不在文件中使用grepfor并查看其内容呢?仔细观察结果:testdirtrace.log



[root@sandbox tmp]# grep testdir trace.log
execve("/usr/bin/ls", ["ls", "testdir/"], [/* 40 vars */]) = 0
stat("testdir/", {st_mode=S_IFDIR|0755, st_size=32, ...}) = 0
openat(AT_FDCWD, "testdir/", O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC) = 3
[root@sandbox tmp]#




回到上面的分析execve,您能否确定下一个系统调用在做什么?



execve("/usr/bin/ls", ["ls", "testdir/"], [/* 40 vars */]) = 0




您不必记住所有的系统调用及其作用:所有内容都在文档中。手册页急救!在运行man命令之前,请确保已安装软件包man-pages



[root@sandbox tmp]# rpm -qa | grep -i man-pages
man-pages-3.53-5.el7.noarch
[root@sandbox tmp]#




请记住,您需要在命令man和syscall名称之间添加“ 2” 如果您阅读man有关manman man)的内容,则会看到第2节是为系统调用保留的。同样,如果需要有关库函数的信息,则需要在库函数man的名称和之间添加3



以下是部分编号man



1.       .
2.   (,  ).
3.   (  ).
4.   (    /dev).




要查看系统调用的文档,请使用该系统调用的名称运行man。



man 2 execve




根据文档,系统调用execve执行一个程序,该程序以参数(在本例中为ls)中传递给它ls的其他参数也传递给它。在此示例中,为testdir因此,这个系统调用简单地运行lstestdir作为参数:



'execve - execute program'

'DESCRIPTION
       execve()  executes  the  program  pointed to by filename'




下一个系统调用stat将传递一个参数testdir



stat("testdir/", {st_mode=S_IFDIR|0755, st_size=32, ...}) = 0




要查看文档使用man 2 statstat系统调用返回有关指定文件的信息。请记住,Linux中的所有内容都是文件,包括目录。



接下来,系统调用openat打开testdir请注意,返回值为3。这是将在后续系统调用中使用的文件描述符:



openat(AT_FDCWD, "testdir/", O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC) = 3




现在打开文件
trace.log
并注意系统调用后的行openat您将看到一个系统调用getdents,它执行执行命令所需的大部分工作ls testdir现在让我们执行grep getdents该文件trace.log



[root@sandbox tmp]# grep getdents trace.log
getdents(3, /* 4 entries */, 32768)     = 112
getdents(3, /* 0 entries */, 32768)     = 0
[root@sandbox tmp]#




文档(man getdents)说它getdents读取目录条目,这是我们实际需要的。请注意,getdent3的参数是较早从系统调用获得的文件描述符openat



现在已经接收到目录的内容,我们需要一种在终端中显示信息的方法。因此,我们grep对另一个系统调用进行了处理write,该系统调用用于输出到终端:



[root@sandbox tmp]# grep write trace.log
write(1, "file1  file2\n", 13)          = 13
[root@sandbox tmp]#




在参数中,您可以看到要输出的文件的名称:file1file2对于第一个参数(1),请记住,在Linux上,默认情况下为任何进程打开三个文件描述符:



  • 0-标准输入流
  • 1-标准输出流
  • 2-标准错误流




因此,系统调用write需要file1file2标准输出,这是一个终端,代表数字1。



现在你知道什么样的系统调用来完成大部分工作,为团队ls testdir/。但是文件中的其他100多个系统调用trace.log呢?



操作系统为启动该过程提供了很多支持,因此您在文件中看到的很多内容都在trace.log初始化和清理过程。全面查看trace.log文件,并尝试了解运行该命令时发生的情况ls



现在,您可以分析任何程序的系统调用。 strace实用程序还提供了许多有用的命令行选项,下面介绍其中一些选项。



默认情况下strace,它不会显示有关系统调用的所有信息。但是,它具有一个选项-v verbose,将显示有关每个系统调用的其他信息:



strace -v ls testdir




优良作法是使用参数-f来跟踪正在运行的进程创建的子进程:



strace -f ls testdir




如果您只想要系统调用的名称,它们运行的​​次数以及执行所花费的时间百分比,该怎么办?您可以使用该选项-c获取以下统计信息:



strace -c ls testdir/




例如,如果要跟踪某个系统调用,open而忽略其他系统调用,则可以使用-e带有系统调用名称的选项



[root@sandbox tmp]# strace -e open ls testdir
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
open("/lib64/libselinux.so.1", O_RDONLY|O_CLOEXEC) = 3
open("/lib64/libcap.so.2", O_RDONLY|O_CLOEXEC) = 3
open("/lib64/libacl.so.1", O_RDONLY|O_CLOEXEC) = 3
open("/lib64/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
open("/lib64/libpcre.so.1", O_RDONLY|O_CLOEXEC) = 3
open("/lib64/libdl.so.2", O_RDONLY|O_CLOEXEC) = 3
open("/lib64/libattr.so.1", O_RDONLY|O_CLOEXEC) = 3
open("/lib64/libpthread.so.0", O_RDONLY|O_CLOEXEC) = 3
open("/usr/lib/locale/locale-archive", O_RDONLY|O_CLOEXEC) = 3
file1  file2
+++ exited with 0 +++
[root@sandbox tmp]#




如果您需要按多个系统调用进行过滤怎么办?不用担心,您可以使用相同的选项,-e并用逗号分隔所需的系统调用。例如,writegetdent



[root@sandbox tmp]# strace -e write,getdents ls testdir
getdents(3, /* 4 entries */, 32768)     = 112
getdents(3, /* 0 entries */, 32768)     = 0
write(1, "file1  file2\n", 13file1  file2
)          = 13
+++ exited with 0 +++
[root@sandbox tmp]#




到目前为止,我们仅跟踪显式命令运行。但是,先前运行的命令又如何呢?如果要跟踪恶魔怎么办?为此,您strace具有一个特殊的选项-p,可以将进程ID传递给该选项



我们不会启动守护程序,而是使用命令cat来显示传递给它的文件的内容作为参数。但是,如果您未指定参数,则该命令cat将仅等待用户输入。输入文本后,它将在屏幕上显示输入的文本。依此类推,直到用户单击Ctrl+C退出。在一个终端上



运行命令cat



[root@sandbox tmp]# cat




在另一个终端上,使用以下命令查找进程ID(PID)ps



[root@sandbox ~]# ps -ef | grep cat
root      22443  20164  0 14:19 pts/0    00:00:00 cat
root      22482  20300  0 14:20 pts/1    00:00:00 grep --color=auto cat
[root@sandbox ~]#




现在从您找到strace的选项-p和PID开始ps启动后,它将strace显示有关它所连接的过程及其PID的信息。现在strace监视命令执行的系统调用cat您将看到的第一个系统调用已读取,等待线程0的输入,即标准输入,现在是命令在其上运行的终端cat



[root@sandbox ~]# strace -p 22443
strace: Process 22443 attached
read(0,




现在回到您使命令保持运行状态的终端cat并输入一些文本。为了演示,我进入x0x0请注意,我cat只重复输入的内容,x0x0屏幕将出现两次。



[root@sandbox tmp]# cat
x0x0
x0x0




返回到您strace连接到该进程的终端cat现在,您将看到两个新的系统调用:前一个read已读取x0x0,另一个已写入write,将写x0x0回到终端,另一个新read,正在等待从终端读取。请注意,标准输入(0)和标准输出(1)在同一端子上:



[root@sandbox ~]# strace -p 22443
strace: Process 22443 attached
read(0, "x0x0\n", 65536)                = 5
write(1, "x0x0\n", 5)                   = 5
read(0,




想象一下启动strace守护程序的好处:您可以在后台看到完成的所有操作。完成命令
cat
通过点击
Ctrl+C
... 这也将终止会话
strace
因为监视的进程已终止。



要查看系统调用的时间戳,请使用以下选项-t



[root@sandbox ~]#strace -t ls testdir/

14:24:47 execve("/usr/bin/ls", ["ls", "testdir/"], [/* 40 vars */]) = 0
14:24:47 brk(NULL)                      = 0x1f07000
14:24:47 mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f2530bc8000
14:24:47 access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
14:24:47 open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3




如果您想知道两次系统调用之间所花费的时间怎么办?有一个方便的选项-r显示完成每个系统调用所需的时间。很有帮助,不是吗?



[root@sandbox ~]#strace -r ls testdir/

0.000000 execve("/usr/bin/ls", ["ls", "testdir/"], [/* 40 vars */]) = 0
0.000368 brk(NULL)                 = 0x1966000
0.000073 mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fb6b1155000
0.000047 access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
0.000119 open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3




结论



该实用程序strace对于学习Linux中的系统调用非常方便。有关其他命令行选项,请参见手册和在线文档。






All Articles