【操作系统】strace 跟踪系统调用(一)
目录
- strace 使用指南
- 一、基本使用
- 二、系统调用简介
- 三、strace 的两种使用方式
- 1. 跟踪即将启动的进程
- 2. 跟踪已启动的进程
- 四、strace 常见参数介绍
- 1.参数说明
- 2.跟踪某一类系统调用
- 3.其他跟踪工具
- 五、strace 输出详细解读
- 1.输出标准格式
- 2.示例输出解析
- 3.ps 命令典型流程
- 字段含义汇总
- 系统调用说明
strace 使用指南
strace
是一个非常有用的诊断、调试和教学工具,用于 Linux 系统。它能够跟踪程序执行过程中所进行的系统调用(system calls)以及程序接收到的信号(signals)。通过使用 strace
,你可以监控你的应用程序与操作系统内核之间的交互细节,这对于分析程序行为、找出性能瓶颈或定位错误非常有帮助。
一、基本使用
最基本的使用方式是直接跟上你想追踪的命令。例如,要查看 ls
命令执行时的系统调用,你可以运行:
strace ls
这将输出大量的信息到终端,包括每个系统调用的名称、参数和返回值。
如果你只对特定类型的系统调用感兴趣,可以使用 -e
选项来过滤它们。例如,只关注文件打开操作,你可以这样指定:
strace -e open ls
常用选项
-p PID
:附加到一个已经运行的进程,PID 是该进程的进程ID。-o filename
:将输出重定向到一个文件中,方便之后查看。- 示例:
strace -o ps_trace.log ps
- 示例:
-t
或-tt
或-ttt
:在每行输出前加上时间戳。-tt
提供更精确的时间格式。-f
:除了跟踪初始进程外,还要跟踪其 fork 出来的子进程。-c
:统计每个系统调用的花费时间、次数和错误数,并给出总结报告。
当你运行带有 strace
的命令时,你会看到类似如下的输出:
open("file.txt", O_RDONLY) = 3
read(3, "This is a test file.\n", 4096) = 21
close(3) = 0
每一行显示了一个系统调用的信息,包含系统调用的名称、传给它的参数、以及系统调用的返回值。如果发生错误,你还会看到相应的错误消息。
二、系统调用简介
在计算机中,系统调用(System Call) 指运行在用户空间的程序向操作系统内核请求需要更高权限运行的服务。系统调用提供用户程序与操作系统之间的接口。关联文章: 【操作系统】 Linux 系统调用(一)
操作系统的进程空间分为:
- 用户空间:通过 API 请求内核空间的服务。
- 内核空间:直接运行在硬件上,提供设备管理、内存管理、任务调度等功能。
Linux 内核目前有 300 多个系统调用,详细的列表可以通过 syscalls
手册页查看。这些系统调用主要分为几类:
分类 | 示例系统调用 |
---|---|
文件和设备访问 | open , close , read , write , chmod |
进程管理 | fork , clone , execve , exit , getpid |
信号处理 | signal , sigaction , kill |
内存管理 | brk , mmap , mlock |
进程间通信 (IPC) | shmget , semget , msgget |
网络通信 | socket , connect , sendto , sendmsg |
其他 |
熟悉 Linux 系统调用/系统编程,能够让我们在使用 strace
时得心应手。对于运维的问题定位来说,会使用 strace
这个工具,会查系统调用手册,足够应对大多数场景。
三、strace 的两种使用方式
1. 跟踪即将启动的进程
通过 strace
启动要跟踪的进程,用法是在原有的命令前面加上 strace
即可。
示例:
strace -o ps_trace.log ps
cat ps_trace.log
这里可以看出
ps
命令本质是不断读取/proc/<pid>
下面的信息并展示。
2. 跟踪已启动的进程
通过 -p
参数传递正在运行的进程的 PID 来跟踪。
示例:
strace -p 27256 -o ssh_strace.log
完成后使用 Ctrl+C
结束跟踪。
四、strace 常见参数介绍
示例命令:
strace -tt -T -v -f -e trace=file -o /data/log/strace.log -s 1024 -p 23489
1.参数说明
参数 | 说明 |
---|---|
-tt | 显示毫秒级别的时间 |
-T | 显示每次系统调用所花费的时间 |
-v | 输出完整的环境变量、文件 stat 结构等 |
-f | 跟踪目标进程及其创建的所有子进程 |
-e | 控制要跟踪的事件和行为 |
-o | 把输出写入指定的文件 |
-s | 最多输出字符串参数长度,默认是 32 字节 |
-p | 指定要跟踪的进程 PID |
2.跟踪某一类系统调用
类别 | 参数 | 说明 |
---|---|---|
文件相关 | -e trace=file | 包括 open , read , write 等 |
进程管理 | -e trace=process | 包括 fork , exec , exit |
网络通信 | -e trace=network | 包括 socket , connect |
信号处理 | -e trace=signal | 包括 kill , sigaction |
文件描述符 | -e trace=desc | 包括 select , epoll_wait |
进程间通信 | -e trace=ipc | 包括 shmget , semget |
3.其他跟踪工具
ltrace
:跟踪动态库函数调用(如printf
,malloc
)。perf
:更高效的系统级性能分析。bpftrace
:基于 eBPF 的高级跟踪工具。
五、strace 输出详细解读
命令示例:
strace -o ps_strace.log ps -auxww
1.输出标准格式
syscall_name(arguments...) = return_value
2.示例输出解析
execve("/usr/bin/ps", ["ps", "-auxww"], 0x7ffc0957e898 /* 21 vars */) = 0
execve
执行函数,执行ps
命令,参数-auxww
,环境变量指针地址,21 个环境变量,返回值为 0 表示成功。
brk(NULL) = 0x18e7000
获取当前堆空间结束地址,返回具体地址
0x18e7000
。
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fbda92a4000
映射一块匿名内存区域(大小为 4KB),可读写。
access("/etc/ld.so.preload", R_OK) = -1 ENOENT (没有那个文件或目录)
检查文件是否存在,是否可读。返回值
-1 ENOENT
表示文件不存在。
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
打开
/etc/ld.so.cache
文件,只读方式,设置 FD_CLOEXEC 标志,返回文件描述符 3。
fstat(3, {st_mode=S_IFREG|0644, st_size=28128, ...}) = 0
获取文件状态信息,成功返回 0。
mmap(NULL, 28128, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7fbda929d000
将
/etc/ld.so.cache
映射到内存中进行读取。
close(3) = 0
关闭文件描述符 3,释放资源。
munmap(0x7fbda922b000, 4096) = 0
解除地址
0x7fbda922b000
开始的一块大小为4096
字节的内存映射。释放之前通过mmap()
分配的内存区域。
stat("/etc/localtime", {st_mode=S_IFREG|0644, st_size=556, ...}) = 0
获取
/etc/localtime
的元数据(比如权限、大小等),ps
显示时间时需要知道本地时区设置。
write(1, "root 1 0.0 0.1 125632 "..., 133) = 133
将一行
ps
输出写入标准输出(文件描述符1
),长度为133
字节。假设输出如下:
root 1 0.0 0.1 125632 5584 ? Ss 15:20 0:00 /sbin/init
stat("/proc/2", {st_mode=S_IFDIR|0555, st_size=0, ...}) = 0
获取
/proc/2
目录的状态信息,ps
会遍历/proc
下的所有数字(PID)目录,并通过此方式判断是否是合法的进程目录。
open("/proc/2/stat", O_RDONLY) = 6
以只读方式打开
/proc/2/stat
文件,获得文件描述符6
,该文件包含进程详细状态信息,如状态、优先级、父进程号等。
read(6, "2 (kthreadd) S 0 0 0 0 -1 213817"..., 2048) = 169
从文件描述符
6
中读取最多2048
字节的数据,实际读取了169
字节。
close(6) = 0
关闭文件描述符
6
,释放资源。
open("/proc/2/status", O_RDONLY) = 6
打开
/proc/2/status
文件,用于读取更可读的进程状态信息。
read(6, "Name:\tkthreadd\nUmask:\t0000\nState"..., 2048) = 895
从
/proc/2/status
文件中读取895
字节的内容。
close(6) = 0
关闭文件描述符
6
,释放资源。
open("/proc/2/cmdline", O_RDONLY) = 6
打开
/proc/2/cmdline
文件,查看进程启动命令行参数。
read(6, "", 131072) = 0
从
/proc/2/cmdline
中读取内容,结果为空,表示没有命令行参数或已到达文件末尾。
close(6) = 0
关闭文件描述符
6
,完成清理。
3.ps 命令典型流程
- 遍历
/proc
下的数字目录(每个目录名是一个 PID)。 - 对每个 PID 执行以下操作:
- 使用
stat()
检查是否是有效进程目录。 - 打开并读取
/proc/PID/stat
:获取进程状态、CPU 使用率等。 - 打开并读取
/proc/PID/status
:获取更详细的进程信息。 - 打开并读取
/proc/PID/cmdline
:获取命令行参数。
- 使用
- 根据读取的信息构建输出字符串。
- 使用
write()
将格式化后的进程信息写入标准输出。
字段含义汇总
字段 | 含义 |
---|---|
syscall_name() | 系统调用名称及参数 |
arguments | 参数内容,可能包括文件名、标志位、地址等 |
return_value | 返回值,可能是成功状态、资源句柄或错误码 |
PROT_READ | 内存保护标志(可读) |
PROT_WRITE | 内存保护标志(可写) |
MAP_PRIVATE | mmap 的映射类型 |
MAP_ANONYMOUS | 匿名映射 |
O_RDONLY | 文件打开标志(只读) |
O_CLOEXEC | 关闭时自动释放 |
系统调用说明
系统调用 | 参数说明 | 作用说明 |
---|---|---|
execve | execve(const char *filename, char *const argv[], char *const envp[]) | 执行一个新程序 |
open | open(const char *pathname, int flags, mode_t mode) | 打开或创建一个文件 |
read | read(int fd, void *buf, size_t count) | 从文件描述符读取数据 |
write | write(int fd, const void *buf, size_t count) | 向文件描述符写入数据 |
close | close(int fd) | 关闭一个打开的文件描述符 |
mmap | mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset) | 内存映射文件或设备 |
fstat | fstat(int fd, struct stat *statbuf) | 获取文件状态信息 |
brk | brk(void *addr) | 设置进程数据段的结束地址(堆空间) |
mprotect | mprotect(void *addr, size_t len, int prot) | 修改内存区域的访问权限 |
munmap | munmap(void *addr, size_t length) | 解除内存映射 |
stat | stat(const char *path, struct stat *statbuf) | 获取文件或目录的状态信息 |
by 久违