从 strace 输出解码信息
当一个程序被执行时,它会多次切换到用户模式和内核模式。在用户模式下,进程对资源的访问受到限制,而在内核模式下,它可以访问特权硬件资源及其数据。进程使用系统调用从用户模式切换到内核模式。
Strace是一个分析进程系统调用活动的工具。它为我们提供了以下信息:
- 访问的文件。
- 执行期间使用的系统调用
- 每个系统调用进程所花费的时间等。
当您无法访问源代码并且仅使用可执行二进制文件完成调试时,分析系统调用会很有帮助。这篇文章不是关于如何使用 Strace 工具,它更多是关于分析Strace工具的输出,因为在进程上执行Strace时,它会转储很多与系统调用相关的信息。乍一看,它看起来非常可怕,分析每个系统调用将是一项非常耗时的任务。此外,它可能不是必需的,因为大多数启动系统调用都是为了内务处理,并没有为调试增加太多价值。一旦了解了一个进程的系统调用流程,就可以轻松识别并删除内务系统调用,并专注于调试我们实际问题的重要系统调用。
程序:
C
// C program to print Hello World!
// filename: hello.c
#include
// Driver Code
int main(int argc, char* argv[])
{
// Print Hello World
printf(" geeksforgeeks: hello world !! \n");
return 0;
}
geeksforgeeks: hello world !!
使用以下命令编译上述程序:
$ gcc hello.c
使用以下命令从上面编译的程序中找到 Strace:
$ strace ./a.out
现在,上述程序的Strace输出为:
在开始分析系统调用之前,先简单说一下程序执行wrt系统调用:
- “Hello World”程序将打开并将所有共享库的内存映射到进程的虚拟内存中。大多数系统调用都与此活动相关。
- 设置对内存部分的正确访问。
- 最后执行程序,它会写出消息“geeksforgeeks: hello world !!”进入流程的标准输出。
解码 Strace 输出:
现在将Strace输出分成有意义的块以便更好地理解:
- 执行():
当我们在 bash 控制台中运行./a.out可执行文件时,一个新的子进程被派生并运行execve()以加载新程序“a.out” 。
它有3个参数:- 第一个参数是可执行文件名
- 第二个参数是可执行文件的参数数组,其中第一个参数是可执行文件名称本身。由于没有给可执行文件提供参数,我们只能在参数列表中看到./a.out 。
- 第三个参数是字符串环境变量。
这里的返回值为0 ,表示成功。有关execve()系统调用的更多详细信息,请使用“man 2 execve”在 execve 的手册页上找到。
- brk():此系统调用将数据段大小设置为指定地址。这里使用brk(NULL)来获取数据段的顶部地址,也就是堆起始地址。因此,使用 NULL 调用brk() 会返回堆起始地址,该地址稍后用于分配堆内存。
- access():这个系统调用检查文件权限。它有2个参数:
- 第一个参数是必须检查权限的文件名。
- 第二个参数是模式,它指定可访问性检查。检查文件的读取、写入和可执行可访问性。这里F_OK用于存在检查, R_OK用于读取检查。
- 如果返回值为-1 ,则表示检查的文件不存在。
- ld.so.nohwcap:此文件的存在会禁用优化库的加载。在最新的发行版中,此文件不存在。 ld.so.preload文件包含要在程序之前加载的共享对象文件列表。
- openat()打开一个文件/etc/ld.so.cache并返回文件描述符3 。 /etc/ld.so.cache包含应搜索共享库的目录列表。
- fstat()获取相同文件描述符的文件属性,如模式、大小、创建/修改时间戳等。第二个参数是读取的属性的详细信息。
- mmap()使用文件大小127481,由fstat()读取并将整个文件映射到进程的虚拟内存中,并返回映射的虚拟内存地址0x7ff58cf81000。
- 成功映射后,使用close()系统调用关闭文件。
- /etc/ld.so.cache:这包含应在其中搜索共享库的目录列表。
这与第 3 行相同。
上面的系统调用块是关于打开libc库并将其映射到进程的虚拟内存中。
- openat()打开/lib/x86_64-linux-gnu/libc.so.6并返回文件描述符3 。文件描述符 3 进一步用于处理libc文件。
- read()读取 832 字节的libc.so文件。第二个参数是读取832字节的数据,是ELF文件的头信息,可能用于ELF文件的校验。
- fstat()获取libc文件属性。
- mmap()将文件映射到虚拟内存中。
- mprotect()更新内存区域的保护。
- close()释放文件描述符,因为文件已成功映射到进程虚拟内存中,并且不再需要通过文件描述符进行访问。
- arch_prctl():它设置特定于体系结构的线程状态。这里将 FS 寄存器的64 位基址设置为地址0x7ff58cf804c0 。
- mprotect():调用为不同的内存区域设置保护。 PROT_READ用于使内存区域可读。
- munmap():调用取消映射文件/etc/ld.so.cache 。地址0x7ff58cf81000在第 7 行映射到 ld.so.cache。
- fstat():这是在文件描述符1上完成的,以获取它的属性,因为 printf() 将使用 stdout 描述符写入数据。
当一个进程启动时,它会打开 3 个默认文件:- stdin 的文件描述符 0。
- stdout 的文件描述符 1。
- stderr 的文件描述符 2。
- brk():调用获取和设置数据段边界。
- 这对应于printf()语句,它使用write() 系统调用将数据放在进程的标准输出上。
- 程序以0(SUCCESS)退出。
Now it has seen that most of the system calls were to prepare process to execute. Mostly shared library mapping was causing most of the system calls. It can cross-check this by running Strace on statically built executable binary.
以下是使用“-static”选项构建的“Hello World”程序的 Strace 输出:
可以看到静态构建的可执行文件没有调用open() 、 mmap() 、 close()等,这是为了映射共享库而完成的。现在我们对系统调用有了足够的了解,可以分析 Strace 工具输出并筛选出感兴趣的系统调用进行调试。对于任何系统调用细节,最好的地方是它的手册页。可以使用以下命令访问。
$ man 2