当前位置: 首页 > 科技观察

在Linux上使用strace理解系统调用

时间:2023-03-15 01:39:04 科技观察

使用strace跟踪用户进程与Linux内核之间的交互。系统调用是程序向内核请求服务的一种编程方式,而strace是一个强大的工具,可以让你跟踪用户进程与Linux内核之间的交互。要了解操作系统的工作原理,首先需要了解系统调用的工作原理。操作系统的主要功能之一是为用户程序提供抽象机制。操作系统大致可以分为两种模式:内核模式:操作系统内核使用的一种强大的特权模式用户模式:大多数用户应用程序运行的地方用户主要使用命令行实用程序和图形用户界面(GUI)来执行日常任务.系统调用在后台静默运行,与内核交互以完成工作。系统调用与函数调用非常相似,这意味着它们都接受和处理参数并返回一个值。唯一的区别是系统调用进入内核,而函数调用则不会。从用户空间切换到内核空间是使用一种特殊的陷阱机制完成的。通过使用系统库(在Linux系统上也称为glibc),大多数系统调用对用户都是隐藏的。尽管系统调用本质上是通用的,但发出系统调用的机制非常依赖于机器(体系结构)。本文以一些常见的命令为例,通过一些实际的例子来探索,并使用strace来分析每个命令进行的系统调用。这些示例使用RedHatEnterpriseLinux,但命令在其他Linux发行版上应该同样有效:[root@sandbox~]#cat/etc/redhat-releaseRedHatEnterpriseLinuxServerrelease7.7(Maipo)[root@sandbox~]#[root@sandbox~]#uname-r3.10.0-1062.el7.x86_64[root@sandbox~]#首先,确保你的系统上安装了所需的工具。您可以使用下面的rpm命令来验证是否安装了strace。如果已安装,您可以使用-V选项检查strace实用程序的版本号:[root@sandbox~]#rpm-qa|grep-istracestrace-4.12-9.el7.x86_64[root@sandbox~]#[root@sandbox~]#strace-Vstrace--version4.12[root@sandbox~]#如果没有安装,运行命令安装:yuminstallstrace出于本示例的目的,在/tmp中创建一个测试目录并使用touch命令创建两个文件:[root@sandbox~]#cd/tmp/[root@sandboxtmp]#[root@sandboxtmp]#mkdirtestdir[root@sandboxtmp]#[root@sandboxtmp]#touchtestdir/file1[root@sandboxtmp]#touchtestdir/file2[root@sandboxtmp]#(我用的是/tmp目录,因为大家都可以访问它,但如果你愿意,你可以选择另一个目录。)在testdir目录中使用ls命令来验证文件是否已创建:[root@sandboxtmp]#lstestdir/file1file2[root@sandboxtmp]#你可以天天用ls命令,却没有意识到它下面的系统调用的作用。抽象地,该命令的工作原理如下:命令行工具->从系统库(glibc)调用函数->调用系统调用ls命令在Linux上从内部调用系统库(即glibc)的函数。这些库调用完成大部分工作的系统调用。如果您想知道从glibc库中调用了哪些函数,请使用ltrace命令,然后是常规的lstestdir/命令:ltracelstestdir/如果未安装ltrace,请键入以下命令进行安装:yuminstallltraceAlot输出将堆到屏幕上;别担心,继续吧。ltrace命令输出中与此示例相关的一些重要库函数包括:opendir("testdir/")={3}readdir({3})={101879119,"."}readdir({3})={134,".."}readdir({3})={101879120,"file1"}strlen("file1")=5memcpy(0x1665be0,"file1\0",6)=0x1665be0readdir({3})={101879122,"file2"}strlen("file2")=5memcpy(0x166dcb0,"file2\0",6)=0x166dcb0readdir({3})=nilclosedir({3})通过查看上面的输出,你大概可以大概知道是怎么回事了。opendir库函数打开一个名为testdir的目录,然后调用readdir函数,该函数读取该目录的内容。最后,调用closedir函数,它会关闭之前打开的目录。请暂时忽略其他strlen和memcpy函数。可以看到调用了哪些库函数,但本文将重点介绍系统库函数调用的系统调用。与上面类似,要找出调用了哪些系统调用,只需将strace放在lstestdir命令之前,如下所示。同样,屏幕上出现一堆乱码,您可以按照以下步骤操作:[root@sandboxtmp]#stracelstestdir/execve("/usr/bin/ls",["ls","testdir/"],[/*40vars*/])=0brk(NULL)=0x1f12000<<<截断的strace输出>>>write(1,"file1file2\n",13file1file2)=13close(1)=0munmap(0x7fd002c8d000,4096)=0close(2)=0exit_group(0)=?+++exitedwith0+++[root@sandboxtmp]#运行strace命令后,屏幕上输出的是运行ls命令的系统调用.每个系统调用都为操作系统提供了特定的用途,它们大致可以分为以下几个部分:进程管理系统调用文件管理系统调用目录和文件系统管理系统调用其他系统调用分析屏幕上显示的信息是使用strace方便的-o标志将输出记录到文件中。在-o标志后添加适当的文件名,然后再次运行命令:[root@sandboxtmp]#strace-otrace.loglstestdir/file1file2[root@sandboxtmp]#这次没有输出干扰屏幕显示ls命令按预期工作,显示文件名并将所有输出记录到文件trace.log。就一个简单的ls命令,文件就有将近100行:[root@sandboxtmp]#ls-ltrace.log-rw-r--r--。1rootroot7809Oct1213:52trace.log[root@sandboxtmp]#[root@sandboxtmp]#wc-ltrace.log114trace.log[root@sandboxtmp]#我们看第一行此示例的trace.log文件:execve("/usr/bin/ls",["ls","testdir/"],[/*40vars*/])=0该行的第一个单词execve是正在执行的系统调用的名称。括号中的文本是提供给系统调用的参数。符号=(本例中为0)后的数字是execve系统调用的返回值。现在的输出看起来并不太可怕,是吗?您可以应用相同的逻辑来理解其他行。现在,专注于您调用的单个命令,lstestdir。您知道ls命令使用的目录名称,那么为什么不在trace.log文件中为testdirgrep并查看您得到了什么?让我们详细看看结果的每一行:[root@sandboxtmp]#greptestdirtrace.logexecve("/usr/bin/ls",["ls","testdir/"],[/*40vars*/])=0stat("testdir/",{st_mode=S_IFDIR|0755,st_size=32,...})=0openat(AT_FDCWD,"testdir/",O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC)=3[root@sandboxtmp]#回顾一下上面对execve的分析,能说说这个系统调用的作用吗?execve("/usr/bin/ls",["ls","testdir/"],[/*40vars*/])=0你不需要记住所有的系统调用或者它们做了什么,因为您可以在需要时参考文档。手册页可以为您提供帮助!在运行man命令之前,请确保安装了以下软件包:[root@sandboxtmp]#rpm-qa|grep-iman-pagesman-pages-3.53-5.el7.noarch[root@sandboxtmp]#记住你需要在man命令和系统调用名之间加2。如果您使用manman阅读man命令的手册页,您会看到第2部分是为系统调用保留的。同样,如果您需要有关库函数的信息,则需要在man和库函数名称之间添加一个3。以下是手册的章节编号及其包含的页面类型:1:可执行程序或shell命令2:系统调用(内核提供的函数)3:库调用(程序库中的函数)4:特殊文件(通常位于/dev中)使用系统调用名称运行以下man命令以查看该系统调用的文档:man2execve根据execve手册页,这将执行参数中传递的程序(在本例中为ls).可以向ls提供其他参数,例如本例中的testdir。所以这个系统调用只是以testdir作为参数运行ls:execve-executeprogramDESCRIPTIONexecve()执行文件名指向的程序下一个名为stat的系统调用采用testdir参数:stat("testdir/",{st_mode=S_IFDIR|0755,st_size=32,...})=0使用man2stat访问此文档。stat是获取文件状态的系统调用,记住,Linux中的一切都是文件,包括目录。接下来,openat系统调用将打开testdir。密切注意返回的3。这是将在以后的系统调用中使用的文件描述符:openat(AT_FDCWD,"testdir/",O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC)=3到目前为止一切顺利。现在,打开trace.log文件并转到openat系统调用之后的行。您将看到正在调用getdents系统调用,它完成了执行lstestdir命令所需的大部分工作。现在从trace.log文件中grepgetdents:[root@sandboxtmp]#grepgetdentstrace.loggetdents(3,/*4entries*/,32768)=112getdents(3,/*0entries*/,32768)=0[root@sandboxtmp]#getdents手册页将其描述为“获取目录条目”,这是您想要执行的操作。请注意,getdents的参数是3,这是来自上面openat系统调用的文件描述符。现在您有了一个目录列表,您需要一种在终端中显示它的方法。因此,grep日志以获取另一个写入终端的系统调用写入:[root@sandboxtmp]#grepwritetrace.logwrite(1,"file1file2\n",13)=13[root@sandboxtmp]#Inthese参数,可以看到会显示的文件名:file1和file2。关于第一个参数(1),请记住,在Linux中,当运行任何进程时,默认情况下会为其打开三个文件描述符。以下是默认文件描述符:0:标准输入1:标准输出2:标准错误因此,write系统调用将在标准显示器(这是终端,由1标识)上显示file1和file2。现在您知道哪个系统调用完成了lstestdir/命令的大部分工作。但是trace.log文件中的其他100多个系统调用呢?操作系统必须做很多内务处理才能运行进程,因此您在该日志文件中看到的很多内容都是进程初始化和清理。阅读整个trace.log文件并尝试了解ls命令的工作原理。现在您已经知道如何分析给定命令的系统调用,您可以将这些知识应用到其他命令以了解正在执行哪些系统调用。strace提供了许多有用的命令行标志,使您更容易使用,其中一些在下面进行了描述。默认情况下,strace不包含所有系统调用信息。但是,它有一个方便的-v冗余选项,可提供有关每个系统调用的附加信息:strace-vlstestdir运行strace命令时始终使用-f选项是一种很好的做法。它允许strace跟踪当前正在跟踪的进程创建的任何子进程:strace-flstestdir假设您只需要系统调用的名称、它运行了多少次以及每个系统调用所花费的时间百分比。您可以使用-c标志来获取这些统计信息:strace-clstestdir/假设您想要专注于特定的系统调用,例如专注于打开的系统调用,而忽略其余部分。您可以使用-e标志跟随系统调用的名称:[root@sandboxtmp]#strace-eopenlstestdiropen("/etc/ld.so.cache",O_RDONLY|O_CLOEXEC)=3open("/lib64/libselinux.so.1",O_RDONLY|O_CLOEXEC)=3open("/lib64/libcap.so.2",O_RDONLY|O_CLOEXEC)=3open("/lib64/libacl.so.1",O_RDONLY|O_CLOEXEC)=3open("/lib64/libc.so.6",O_RDONLY|O_CLOEXEC)=3open("/lib64/libpcre.so.1",O_RDONLY|O_CLOEXEC)=3open("/lib64/libdl.so.2",O_RDONLY|O_CLOEXEC)=3open("/lib64/libattr.so.1",O_RDONLY|O_CLOEXEC)=3open("/lib64/libpthread.so.0",O_RDONLY|O_CLOEXEC)=3open("/usr/lib/locale/locale-archive",O_RDONLY|O_CLOEXEC)=3file1file2+++exitedwith0+++[root@sandboxtmp]#如果要关注多个系统调用怎么办?别担心,您还可以使用-e命令行标志并用逗号分隔两个系统调用的名称。例如,查看write和getdents系统调用:[root@sandboxtmp]#strace-ewrite,getdentslstestdirgetdents(3,/*4entries*/,32768)=112getdents(3,/*0entries*/,32768)=0write(1,"file1file2\n",13file1file2)=13+++exitedwith0+++[root@sandboxtmp]#这些示例到目前为止都是显式运行的命令被跟踪。但是如何跟踪已经运行和正在执行的命令呢?例如,如果您想跟踪用于长时间运行的进程的守护进程怎么办?为此,strace提供了一个特殊的-p标志,您可以向其提供进程ID。我们的示例没有在守护进程上运行strace,而是使用cat命令,如果您给它一个文件名作为参数,它通常会显示文件的内容。如果没有给出参数,cat命令会在终端上等待用户输入文本。输入文本后,它会重复给定的文本,直到用户按Ctrl+C退出。从一个终端运行cat命令;它会显示一个提示并在那里等待(记住cat仍在运行并且尚未退出):[root@sandboxtmp]#cat在另一个终端上,使用ps命令查找进程标识符(PID):[root@sandbox~]#ps-ef|grepcatroot2244320164014:19pts/000:00:00catroot2248220300014:20pts/100:00:00grep--color=autocat[root@sandbox~]#现在,运行strace使用-p标志和PID的运行进程(在上面使用ps找到)。运行strace后,其输出描述了附加进程的内容及其PID。现在,strace正在跟踪cat命令进行的系统调用。看到的第一个系统调用是读取,它正在等待文件描述符0上的输入(标准输入,这是运行cat命令的终端):[root@sandbox~]#strace-p22443strace:Process22443attachedread(0,现在,回到你运行cat命令的终端,输入一些文本。我输入x0x0是为了演示目的。注意cat如何简单地重复我输入的内容。所以,x0x0出现了两次。我输入了第一个,第二个是cat命令的重复输出:[root@sandboxtmp]#catx0x0x0x0返回到将strace挂接到cat进程的终端。现在您将看到两个额外的系统调用:之前的read系统调用,现在在终端中读取x0x0,另一个写入,将x0x0写回终端,然后是一个新的读取,等待从终端读取。注意标准输入(0)和标准输出(1)都在同一个终端:[root@sandbox~]#strace-p22443strace:Process22443attachedread(0,"x0x0\n",65536)=5write(1,"x0x0\n",5)=5read(0,想象一下,当在守护进程上运行strace以查看它在后台所做的一切时,这有多么有用。按Ctrl+C终止cat命令;由于该进程不再运行,这也会终止您的strace会话。如果您想查看所有系统调用的时间戳,只需将-t选项与strace一起使用:[root@sandbox~]#strace-tlstestdir/14:24:47execve("/usr/bin/ls",["ls","testdir/"],[/*40vars*/])=014:24:47brk(NULL)=0x1f0700014:24:47mmap(NULL,4096,PROT_READ|PROT_WRITE,MAP_PRIVATE|MAP_ANONYMOUS,-1,0)=0x7f2530bc800014:24:47access("/etc/ld.so.preload",R_OK)=-1ENOENT(Nosuchfileordirectory)14:24:47open("/etc/ld.so.cache",O_RDONLY|O_CLOEXEC)=3如果你想知道系统调用之间花费的时间怎么办?strace有一个方便的-r命令,可以显示每个系统调用执行的时间。非常有用,不是吗?[root@sandbox~]#strace-rlstestdir/0.000000execve("/usr/bin/ls",["ls","testdir/"],[/*40vars*/])=00.000368brk(NULL)=0x19660000.000073mmap(NULL,4096,PROT_READ|PROT_WRITE,MAP_PRIVATE|MAP_ANONYMOUS,-1,0)=0x7fb6b11550000.000047access("/etc/ld.so.preload",R_OK)=-1uchENOENT(Nos文件或目录)0.000119open("/etc/ld.so.cache",O_RDONLY|O_CLOEXEC)=3总结strace实用程序对于理解Linux上的系统调用非常有帮助。有关其其他命令行标志,请参阅手册页和在线文档。