附录 A 追踪系统调用 (Tracing System Calls)
核心结论
-
strace 跟踪系统调用:
strace command arg…通过 ptrace 拦截目标进程系统调用;默认输出到 stderr,可用-o file改写。展示形式:参数括号内 →=→ 返回值;若失败则接 errno 名字(如ENOENT)。 -
strace 选项:
-e trace=open,close过滤事件类型;-e signal/-e network/-e file/-e desc子类;-p pid附加到运行进程;-f/-ff跟踪子进程(多文件输出file.PID);-c统计汇总;-i显示指令指针。 -
strace 显示规则:bit 掩码用符号常量(如 O_RDONLY);字符串前 32 字符(
-s改大小);结构显示摘要(-v显示完整);错误返回会跟 errno 字面量。 -
strace 显示「真系统调用」名——库包装可能合并或重命名,如 wait/waitpid/wait3 都是 wait4;execl/execv/execlp/execvp 都是 execve;getopt 显示为 rt_sigprocmask(glibc 实现在用户态直接处理)。
-
ltrace 跟踪库函数:与 strace 类似但跟踪用户级动态库调用;要 ld.so 支持
-ldl。 -
strace 与 ltrace 在 Linux 上是 ptrace 实现——BSD 用 ktrace、Solaris 用 truss;这些都是同类工具。
|
附录主旨
本附录介绍调试 Linux 系统编程必备工具——strace 与 ltrace。读者需掌握:(1) strace 输出格式(括号参数、=返回值、errno);(2) 过滤与子进程追踪选项;(3) 当 API 名称与底层系统调用不一致时(wait/exec 系列),如何对照「库函数 → syscall」映射;(4) 如何用 |
一、核心概念
本附录围绕 4 个核心概念:strace、输出格式、过滤与多进程、ltrace。
| 概念 | 定义 + 重要性 | 实现提示 |
|---|---|---|
strace 命令与 ptrace |
通过 ptrace 拦截目标进程的所有系统调用入口与出口;打印参数与返回值;最低侵入性调试手段——无需重新编译或插桩。 |
|
输出格式与符号化 |
每个调用显示为:函数名(参, 参, …) = 返回值 [errno];bit 掩码用符号、字符串按文本、结构字段摘要。 |
字符串截断 32 字符( |
过滤与子进程追踪 |
|
多进程: |
ltrace 库调用追踪 |
跟踪动态库函数调用(基于符号表);可看 |
用 |
二、详细笔记
A.1 strace 输出样例
What:strace date 展示从 execve 开始的全部系统调用。
Why:最常见的 strace 入门用法——看到「程序究竟在和内核交互什么」。
How:
$ strace date
execve("/bin/date", ["date"], [/* 114 vars */]) = 0
access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY) = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=111059, ...}) = 0
mmap2(NULL, 111059, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb7f38000
close(3) = 0
...
write(1, "Mon Jan 17 12:14:24 CET 2011\n", 29) = 29
exit_group(0) = ?
When:(1) 程序行为异常不知在哪个系统调用处死;(2) 检查权限问题(ENOENT / EACCES);(3) 排查启动慢的 init 加载。
Example:strace -e openat,read,write ./your_prog 过滤掉装载流程只看业务 I/O。
A.2 strace 参数过滤技巧
What:strace -e trace=分类,事件;分类如 file、network、signal、desc、memory。
Why:默认 strace 输出很大(启动时几百行),过滤后聚焦业务调用。
How:
| 选项 | 含义 | 例子 |
|---|---|---|
|
仅这些系统调用 |
|
|
包含 open/openat/creat 等 |
同名归类 |
|
仅 kill/sigaction/tgkill 等 |
信号排查 |
|
socket/bind/listen/sendto/recvfrom |
网络排查 |
|
fd 操作(open/close/dup/fcntl) |
fd 泄漏 |
|
仅打印退出时统计 |
找热点调用 |
|
附加到 PID 进程 |
调查运行中服务 |
|
显示 IP 寄存器 |
配合 dmesg/coredump |
|
时间戳(s.ms / 日期+ms) |
看时序 |
|
跟子进程(ff 按子 PID 分文件) |
多进程 fork |
|
输出到 file |
文件留存 |
|
字符串截断长度 |
长数据检测 |
|
显示完整结构 |
看 sockaddr 等 |
When:(1) 服务器性能调优 → -c 找热点;(2) 网络包丢失 → -e network + -i;(3) 启动卡 ld.so → readelf/strace -e openat。
Example:strace -tt -p 1234 -e network -o /tmp/trace.txt 跟踪 PID 1234 网络调用并时间戳。
A.3 库函数与系统调用的映射
What:strace 显示「真系统调用」名,库包装常合并多个:
| 库函数 | strace 显示 |
|---|---|
说明 |
wait() / waitpid() / wait3() |
wait4 |
BSD 风格包装 wait4; |
execl() / execv() / execlp() / execvp() / execle() |
execve |
全部经过 execve;glibc 内做 PATH 搜索 |
fork() |
clone(带标志位) |
Linux 上 fork 是 clone 的特例,flags=17 = SIGCHLD | |
getpid() |
(无) |
glibc 用户态缓存,无 syscall |
close()/read()/write() |
close()/read()/write() |
名称一致 |
dup() / dup2() / dup3() |
dup/dup2/dup3 |
dup2 是 atomic dup+close | |
mmap() / munmap() |
mmap/munmap |
一致 |
open() |
openat (AT_FDCWD) |
现代 glibc 全部转 openat | |
socket() |
socket |
socketcall 不再用 | |
accept() |
accept4 |
Why:知道映射关系 = 写 -e trace= 时不会卡住「为什么我写 strace wait 而没输出」。
When:(1) 想看 fork 后子进程做了什么 → -e trace=clone;(2) 排查 socket accept → -e trace=accept,accept4。
Example:strace -c -p PID 显示这个进程的所有系统调用次数与耗时排序。
A.4 ltrace 与 strace 协同
What:ltrace command 类似 strace 但跟踪动态库调用;-S 同时跟踪系统调用。
Why:(1) 库调用不是 syscall(如 printf 触发 write 前还做格式处理);(2) 库级问题(如 glib、libxml 解析)在 syscall 层看不出。
How:
$ ltrace -l 'libc.so.*:getaddrinfo' ./client
$ ltrace -S -o trace.txt ./prog # 同 strace
When:(1) 复杂动态库调用链诊断;(2) 排查 glibc / libpthread 实现 bug;(3) 高层性能分析(libc 内部调用次数)。
Example:ltrace --library=nsswitch ./prog 只跟踪 nss 解析调用。
A.5 附加已运行进程
What:strace -p PID 附加到现有进程(如可疑的网络服务);退出用 ^C。
Why:(1) 服务启动后才出错,无法 strace 启动;(2) 持续运行多线程服务低侵入调试。
How:
$ sudo strace -p 1234 -e network -tt
Process 1234 attached - interrupt to quit
recvfrom(5, ..., 1024, 0, NULL, NULL) = 32
sendto(5, ..., 32, 0, ..., ...) = 32
unprivileged 用户只能附加自己拥有的进程;set-user-ID / set-group-ID 进程禁止。
When:(1) Web 服务 hang 住 → strace 看是否阻塞某 fd;(2) 进程 CPU 高 → 加 -c 找最忙 syscall。
Example:多线程服务只附加 1 线程:先 ps -T -p PID 看 tid,再 -p tid。
三、关键图表
|
strace / ltrace 常用选项
|
|
常见库函数 → 系统调用映射
|
四、思维导图
mindmap
root((附录 A Tracing))
strace
ptrace 实现
参数符号化
错误 errno
execve 入口
输出格式
括号参数
返回值
ENOENT 等错误名
字符串 32 字符
过滤选项
trace file desc
trace network signal
trace set 子集
c 统计
p 附加
f ff 子进程
t tt ttt 时间
库包装
wait 转 wait4
exec 转 execve
open 转 openat
accept 转 accept4
ltrace
库函数追踪
dl 符号
S 联合 sys
调试场景
fd 泄漏
启动慢
信号遗漏
服务器 hang
五、重点与易错点
-
strace 影响被追踪进程性能——每次系统调用都要 ptrace 通知;高 QPS 服务下会显著拖慢;生产环境慎用。
-
附加已运行进程可能导致「clock skew」——strace 暂停被追踪进程;某些对时间敏感的服务可能超时。
-
strace 是 Linux 独有——BSD/Solaris 用 ktrace / truss;移植脚本要注意系统差异。
-
sudo 启用才能附加他人进程——kernel.yama.ptrace_scope 决定 ptrace 限制;现代 Linux 默认 1 限制只附加同用户进程。
-
set-uid 程序不能被 strace——内核禁止对 set-user-ID 进程 ptrace(除非 ruid==euid)。
-
strace 输出默认 stderr——常需
2>&1或-o file;调试管道程序容易丢失输出。 -
库函数的 strace 名 ≠ 库函数名——wait/exec/open 都不是直名;查询手册或 man -k。
-
strace 默认显示调用前的参数,调用后的 errno——开关键路径前
-e过滤能省 90% 噪音。 -
跟踪启动过程用
LD_DEBUG=files prog可看 dlopen 细节——比 strace 更聚焦装载。 -
调试 glibc 内部使用
-e trace=openat,close,mmap,brk——看 malloc 与文件 I/O 内部行为。 -
ltrace 跟踪仅对动态库函数有效——静态链接的二进制 ltrace 看不到函数调用(除非特别编译)。
-
有时需要
strace -k看 stack trace——展示每个 syscall 的 callers,方便定位程序逻辑错误。 -
网络「不发包」调试:
strace -e trace=sendto -p PID而不抓包;若 sendto 都没调用,必是用户态逻辑。 -
detect firefox / chrome 用大量 syscall——
-c看完热点,添加-P显示 fd 路径,再 thread filter 找具体线程。 -
跨章衔接:第 50 章 mmap 事件可在 strace 里看到 mmap/mprotect/munmap;第 33 章 pthread 创建看到 clone;第 24 章 fork 看到 fork/vfork/clone;第 6 章 envp 出现在 execve 第三参数。