附录 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) 如何用 -p 附加运行中进程定位问题。

      一、核心概念

      本附录围绕 4 个核心概念:strace、输出格式、过滤与多进程、ltrace。

      概念 定义 + 重要性 实现提示

      strace 命令与 ptrace

      通过 ptrace 拦截目标进程的所有系统调用入口与出口;打印参数与返回值;最低侵入性调试手段——无需重新编译或插桩。

      strace command;可附加已运行进程;输出到 stderr(-o file);-f / -ff 跟子进程;-c 统计。

      输出格式与符号化

      每个调用显示为:函数名(参, 参, …​) = 返回值 [errno];bit 掩码用符号、字符串按文本、结构字段摘要。

      字符串截断 32 字符(-s strsize);结构摘要(-v 完整);错误时显示符号 errno 如 ENOENT

      过滤与子进程追踪

      -e trace=open,close 只显示指定 syscall;-e trace=file -e network -e signal 等大类;-i 显示指令指针;-t / -tt / -ttt 时间戳。

      多进程:-f 整体,附加 2>&1-ff + -o file,每个进程的 trace 进 file.PID-c 进程退出时打印统计。

      ltrace 库调用追踪

      跟踪动态库函数调用(基于符号表);可看 fopenpthread_mutex_lockgetaddrinfo 等用户态 API。

      --library 限定库;-S 同时跟踪 syscall(等于 strace + ltrace);底层符号分辨能力依赖 -ldl 与二进制带符号表。

      二、详细笔记

      A.1 strace 输出样例

      Whatstrace 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 加载。

      Examplestrace -e openat,read,write ./your_prog 过滤掉装载流程只看业务 I/O。

      A.2 strace 参数过滤技巧

      Whatstrace -e trace=分类,事件;分类如 filenetworksignaldescmemory

      Why:默认 strace 输出很大(启动时几百行),过滤后聚焦业务调用。

      How

      选项 含义 例子

      -e trace=set

      仅这些系统调用

      -e trace=open,close,read,write

      -e trace=open

      包含 open/openat/creat 等

      同名归类

      -e signal

      仅 kill/sigaction/tgkill 等

      信号排查

      -e network

      socket/bind/listen/sendto/recvfrom

      网络排查

      -e desc

      fd 操作(open/close/dup/fcntl)

      fd 泄漏

      -c

      仅打印退出时统计

      找热点调用

      -p PID

      附加到 PID 进程

      调查运行中服务

      -i

      显示 IP 寄存器

      配合 dmesg/coredump

      -t / -tt

      时间戳(s.ms / 日期+ms)

      看时序

      -f / -ff

      跟子进程(ff 按子 PID 分文件)

      多进程 fork

      -o file

      输出到 file

      文件留存

      -s strsize

      字符串截断长度

      长数据检测

      -v

      显示完整结构

      看 sockaddr 等

      When:(1) 服务器性能调优 → -c 找热点;(2) 网络包丢失 → -e network + -i;(3) 启动卡 ld.so → readelf/strace -e openat

      Examplestrace -tt -p 1234 -e network -o /tmp/trace.txt 跟踪 PID 1234 网络调用并时间戳。

      A.3 库函数与系统调用的映射

      What:strace 显示「真系统调用」名,库包装常合并多个:

      库函数 strace 显示

      说明

      wait() / waitpid() / wait3()

      wait4

      BSD 风格包装 wait4;waitid 是单独 syscall

      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

      Examplestrace -c -p PID 显示这个进程的所有系统调用次数与耗时排序。

      A.4 ltrace 与 strace 协同

      Whatltrace command 类似 strace 但跟踪动态库调用;-S 同时跟踪系统调用。

      Why:(1) 库调用不是 syscall(如 printf 触发 write 前还做格式处理);(2) 库级问题(如 gliblibxml 解析)在 syscall 层看不出。

      How

      $ ltrace -l 'libc.so.*:getaddrinfo' ./client
      $ ltrace -S -o trace.txt ./prog       # 同 strace

      When:(1) 复杂动态库调用链诊断;(2) 排查 glibc / libpthread 实现 bug;(3) 高层性能分析(libc 内部调用次数)。

      Exampleltrace --library=nsswitch ./prog 只跟踪 nss 解析调用。

      A.5 附加已运行进程

      Whatstrace -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 常用选项
      选项 用途 备选

      -p PID

      附加进程

      -o FILE

      输出到文件

      默认 stderr

      -e trace=SYMS

      过滤系统调用集

      -e signal, -e network, -e file

      -f / -ff

      跟子进程

      ff 分文件 file.PID

      -c

      退出时打印统计

      找热点

      -t / -tt / -ttt

      时间戳

      调试时序

      -i

      显示指令指针

      反汇编

      -v

      详细结构

      sockaddr 等

      -s N

      字符串截断 N

      默认 32

      -S (ltrace)

      同时跟踪 syscall

      strace + ltrace 合体

      -k (strace)

      在 stack trace 中展示 syscall 起源

      复杂路径

      常见库函数 → 系统调用映射
      库 / glibc API strace 显示

      fork

      clone (CLONE_CHILD_SETTID)

      open / openat

      openat (AT_FDCWD)

      accept

      accept4

      wait / waitpid / wait3

      wait4

      exec*

      execve

      getpid / getuid

      (no syscall)

      read / write / close

      read / write / close

      dup / dup2 / dup3

      dup / dup2 / dup3

      四、思维导图

      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

      五、重点与易错点

      1. strace 影响被追踪进程性能——每次系统调用都要 ptrace 通知;高 QPS 服务下会显著拖慢;生产环境慎用。

      2. 附加已运行进程可能导致「clock skew」——strace 暂停被追踪进程;某些对时间敏感的服务可能超时。

      3. strace 是 Linux 独有——BSD/Solaris 用 ktrace / truss;移植脚本要注意系统差异。

      4. sudo 启用才能附加他人进程——kernel.yama.ptrace_scope 决定 ptrace 限制;现代 Linux 默认 1 限制只附加同用户进程。

      5. set-uid 程序不能被 strace——内核禁止对 set-user-ID 进程 ptrace(除非 ruid==euid)。

      6. strace 输出默认 stderr——常需 2>&1-o file;调试管道程序容易丢失输出。

      7. 库函数的 strace 名 ≠ 库函数名——wait/exec/open 都不是直名;查询手册或 man -k。

      8. strace 默认显示调用前的参数,调用后的 errno——开关键路径前 -e 过滤能省 90% 噪音。

      9. 跟踪启动过程用 LD_DEBUG=files prog 可看 dlopen 细节——比 strace 更聚焦装载。

      10. 调试 glibc 内部使用 -e trace=openat,close,mmap,brk——看 malloc 与文件 I/O 内部行为。

      11. ltrace 跟踪仅对动态库函数有效——静态链接的二进制 ltrace 看不到函数调用(除非特别编译)。

      12. 有时需要 strace -k 看 stack trace——展示每个 syscall 的 callers,方便定位程序逻辑错误。

      13. 网络「不发包」调试strace -e trace=sendto -p PID 而不抓包;若 sendto 都没调用,必是用户态逻辑。

      14. detect firefox / chrome 用大量 syscall——-c 看完热点,添加 -P 显示 fd 路径,再 thread filter 找具体线程。

      15. 跨章衔接:第 50 章 mmap 事件可在 strace 里看到 mmap/mprotect/munmap;第 33 章 pthread 创建看到 clone;第 24 章 fork 看到 fork/vfork/clone;第 6 章 envp 出现在 execve 第三参数。