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
来关闭先前打开的目录。现在,请忽略其他功能,例如strlen
和memcpy
。
如您所见,您可以轻松地看到正在调用的库函数,但是在本文中,我们将重点介绍由系统库函数调用的系统调用。
要查看系统调用,请使用如下所示
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
,那么为什么不在文件中使用grep
for并查看其内容呢?仔细观察结果:testdir
trace.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
有关man
(man man
)的内容,则会看到第2节是为系统调用保留的。同样,如果需要有关库函数的信息,则需要在库函数man
的名称和之间添加3 。
以下是部分编号
man
:
1. .
2. (, ).
3. ( ).
4. ( /dev).
要查看系统调用的文档,请使用该系统调用的名称运行man。
man 2 execve
根据文档,系统调用
execve
执行一个程序,该程序以参数(在本例中为ls
)中传递给它。ls的其他参数也传递给它。在此示例中,为testdir
。因此,这个系统调用简单地运行ls
与testdir
作为参数:
'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 stat
。stat系统调用返回有关指定文件的信息。请记住,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
读取目录条目,这是我们实际需要的。请注意,getdent
3的参数是较早从系统调用获得的文件描述符openat
。
现在已经接收到目录的内容,我们需要一种在终端中显示信息的方法。因此,我们
grep
对另一个系统调用进行了处理write
,该系统调用用于输出到终端:
[root@sandbox tmp]# grep write trace.log
write(1, "file1 file2\n", 13) = 13
[root@sandbox tmp]#
在参数中,您可以看到要输出的文件的名称:
file1
和file2
。对于第一个参数(1),请记住,在Linux上,默认情况下为任何进程打开三个文件描述符:
- 0-标准输入流
- 1-标准输出流
- 2-标准错误流
因此,系统调用
write
需要file1
和file2
标准输出,这是一个终端,代表数字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
并用逗号分隔所需的系统调用。例如,write
和getdent
:
[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中的系统调用非常方便。有关其他命令行选项,请参见手册和在线文档。