| name | strace-syscall-diagnosis |
| description | 基于 strace/ltrace 的系统调用级故障诊断技能(双轨:调用轨迹 + 内核语义)。当用户提到 strace、ltrace、 系统调用、syscall、EACCES、ENOENT、ENOMEM、EAGAIN、进程卡住、futex 阻塞、epoll 超时、 read 卡顿、库函数追踪等关键词时,必须使用本技能。覆盖场景:syscall 错误码模式识别(EACCES/ENOENT/ EAGAIN/ENOMEM)、慢 syscall 定位(futex/epoll_wait/read 阻塞)、库函数调用链异常追踪、 进程 hang/blocked 在 syscall、文件描述符泄漏、资源泄漏模式等系统调用级故障。 即使用户只说"进程卡住了/系统调用很慢/函数调用异常"也要触发本技能。 支持内核源码级根因分析(有源码时必须走双轨并行并交叉验证)。
|
系统调用级故障深度诊断(双轨:调用轨迹 + 内核语义)
第一节:故障目录结构
strace_case/ # 故障文件夹
├── src/ # 【可选,优先】内核/应用源码路径,存在时走源码分析主路径
│ ├── fs/ # 文件系统相关 syscall 源码
│ └── ...
├── strace_output/ # strace 抓取的原始输出
│ ├── strace_raw.log # 完整 syscall 日志
│ ├── strace_summary.txt # -c 汇总统计
│ └── ltrace_raw.log # 库函数调用日志
├── perf_data/ # perf 采样数据(如有,配合 strace 交叉分析)
└── proc_snapshot/ # /proc 快照(fd 数量、状态等)
bash scripts/01_baseline_info.sh <pid_or_command>
第二节:分析策略(并行双轨,交叉验证)
调用轨迹分析和内核语义分析应同时进行,而非二选一。 两条轨道相互独立推进,最终交叉比对以确认根因。
┌─────────────────────────────────────────────────────────────────┐
│ 并行双轨分析模型 │
│ │
│ 轨道一:调用轨迹分析(逆向) 轨道二:内核语义分析(正向) │
│ ────────────────────────── ──────────────────────── │
│ 从 strace/ltrace 输出出发, 从 syscall 定义/内核实现出发, │
│ 识别 syscall 模式和异常 正向追踪调用语义与代码逻辑 │
│ │
│ 回答:哪些 syscall 异常? 回答:为什么会出现这些 │
│ 错误码/耗时/频次如何? syscall 行为? │
│ 代码/配置原因? │
│ │
│ ↓ ↓ │
│ └────────────── 交叉验证 ────────────┘ │
│ │
│ 见:第三节(统一分析流程:分支决策→双轨并行→交叉验证→输出) │
└─────────────────────────────────────────────────────────────────┘
两条轨道的分工与互补:
| 调用轨迹轨道 | 内核语义轨道 |
|---|
| 优势 | 真实的 syscall 时序、精确耗时、错误码分布、频次统计、参数值可见 | 完整因果模型、内核 syscall 实现路径可见、errno 语义明确、资源生命周期可分析 |
| 局限 | 只有调用轨迹,内核内部决策过程不可见;strace 本身有性能开销 | 需要内核版本精确匹配;复杂场景需结合应用源码理解 |
| 典型盲区 | 异步 I/O 和信号驱动的 syscall 不完整、strace 丢失子进程 | errno EAGAIN 可能正常也可能异常,需结合重试次数上下文 |
何时两条轨道都必须做:有源码时,两条轨道必须同时进行,最终通过交叉验证收敛到高置信度结论。
何时只能走调用轨迹轨道:无源码时,仅走调用轨迹分析,但应明确标注分析局限性,并在结论中说明哪些环节依赖推断。
第三节:统一分析流程(分支决策 → 双轨并行 → 交叉验证 → 输出)
执行约束:strace 采集默认超时 30 秒,特殊场景可调整。
Step 1:启动(基线信息收集 + 分支推荐)
运行:
bash scripts/01_baseline_info.sh [pid|command] [duration]
记录输出中的四类关键信息(后续所有步骤都围绕它们推进):
- 目标进程/命令:PID、进程名、启动参数、运行时间
- 系统调用总览:syscall 总数、各类 syscall 频次、错误码分布
- 慢 syscall 列表:按耗时降序排列的 Top-N 慢 syscall
- 异常线索:EACCES/ENOENT/EAGAIN/ENOMEM 等错误码出现频率、超标调用、资源泄漏模式
Step 2:故障类型定界(选择分支脚本一键跑)
按 Step 1 输出推荐,执行对应分支脚本:
bash scripts/branch_X_xxx.sh [output_dir]
说明:每个 branch_*.sh 已内置调用轨迹侧的检查命令序列(覆盖 T1–T4),并在检测到源码时给出内核/应用侧追踪指引(覆盖 S0–S5)。
若 Step 1 输出推荐多个分支脚本,必须按输出顺序全部执行,不可只选其一。
脚本对应执行参考如下:
分析结果
├─ EACCES/EPERM 错误频繁 → 权限问题阻塞进程 → 分支A: Syscall 错误码模式
├─ ENOENT 错误频繁 → 文件/路径不存在 → 分支A: Syscall 错误码模式
├─ EAGAIN/EWOULDBLOCK 多 → 资源暂时不可用/重试 → 分支A: Syscall 错误码模式
├─ ENOMEM 出现 → 内存分配失败 → 分支A: Syscall 错误码模式
├─ futex 耗时 > 100ms → 锁竞争/线程阻塞严重 → 分支B: 慢 Syscall 定位
├─ epoll_wait 超时频繁 → IO 事件等待异常 → 分支B: 慢 Syscall 定位
├─ read/write 耗时 > 1s → IO 阻塞/磁盘瓶颈 → 分支B: 慢 Syscall 定位
├─ open 耗时 > 100ms → 文件系统/存储慢 → 分支B: 慢 Syscall 定位
├─ ltrace 显示库函数调用异常慢 → 库函数级性能问题 → 分支C: 库函数追踪
├─ ltrace 显示调用路径与预期不符 → 逻辑错误/版本差异 → 分支C: 库函数追踪
├─ FD 数量持续增长不放 → 文件描述符泄漏 → 分支D: FD/资源泄漏
├─ FD 类型分析显示大量未关闭 → open/close 不配对 → 分支D: FD/资源泄漏
├─ mmap 区域持续增长 → 内存映射泄漏 → 分支D: FD/资源泄漏
├─ connect ECONNREFUSED → 目标端口未监听 → 分支E: 网络 Syscall 异常
├─ connect ETIMEDOUT → 网络不可达/防火墙 → 分支E: 网络 Syscall 异常
├─ ECONNRESET/EPIPE 频繁 → 对端异常断开 → 分支E: 网络 Syscall 异常
├─ EADDRINUSE → 端口被占用 → 分支E: 网络 Syscall 异常
├─ EINTR 频繁出现 → syscall 被信号中断 → 分支F: 信号/中断模式
├─ SIGPIPE 频繁 → 写入关闭的管道/socket → 分支F: 信号/中断模式
├─ fork/clone 次数极高 → 进程/线程创建风暴 → 分支G: 进程生命周期
├─ execve 失败 → 路径/权限/解释器问题 → 分支G: 进程生命周期
├─ 僵尸进程持续增长 → 父进程未 wait → 分支G: 进程生命周期
└─ 无明确异常 → 通用 syscall 健康检查 → check_syscall.sh
Step 3:调用轨迹逆向(回答"哪些 syscall 异常 + 错误码/耗时/频次如何")
在分支脚本输出基础上,完成并固化四步证据链:
- T1 调用现场还原:确认异常 syscall 类型、错误码、耗时、调用参数
- T2 模式时序重建:按时间序列重建异常 syscall 的触发模式——是持续出现还是突发、频次是否增长
- T3 进程级归因:定位异常 syscall 所属的线程/协程/代码路径(结合线程 ID、堆栈)
- T4 独立归因:仅基于 strace/ltrace 客观数据,给出"异常 syscall 是什么、首次出现在哪个时间点、当前状态是否在恶化"
输出(供后续交叉验证使用):
异常类别:<错误码模式/慢调用/库追踪>
Top syscall:<syscall_name> count=<次数> errors=<错误数> max_time=<最耗时>
关键错误码:<errno> count=<次数> 首次出现=<timestamp>
最慢调用:<syscall_name> duration=<耗时> pid/tid=<id>
调用轨迹归因假设:<一句话>
Step 4:内核语义正向(有源码时必做;回答"为什么会这样 + 内核/应用代码原因")
S0:内核版本与配置验证
uname -r
cat /proc/kallsyms | grep sys_call_table
sysctl fs.file-max fs.nr_open kernel.threads-max kernel.pid_max
S1–S2:以异常 syscall 为入口,完成"调用-内核-语义对齐"
- S1 锚定入口:取 Step 3 的异常 syscall 名与错误码,定位到对应内核 syscall 实现函数
- S2 对齐确认:以内核 syscall 实现路径为准理解错误码如何在代码中产生
常见理解陷阱与处理:
| 认知误区 | 实际内核行为 | 应对方式 |
|---|
| EAGAIN 总是异常 | 很多非阻塞 IO 正常返回 EAGAIN | 检查重试次数和间隔 |
| futex 耗时高=锁争用 | futex(FUTEX_WAIT) 正常休眠也计算耗时 | 结合调度延迟、锁持有时间判断 |
| ENOENT=文件被删 | 也可能是符号链接断链、挂载点消失 | 检查完整路径解析链 |
| ENOMEM=内存不足 | 也可能是 cgroup 限制、ulimit 限制 | 检查 cgroup memory.max、ulimit -a |
S3:调用栈逐层追踪(不允许省略链路)
原则:从 syscall 入口向上追溯至应用代码路径,逐层完成三件事:
① 找到对应的内核 syscall 实现函数与应用调用点
② 用 strace 参数验证各层输入输出是否异常
③ 判断:该层是"表现层"还是"根因层"
表现层 vs 根因层必须严格区分:表现层是异常传播的终点,根因层是异常最初引入点。
典型分层(自底向上):
| 层次 | 组件 | 典型异常 | 角色 |
|---|
| 应用逻辑 | 用户代码 | 错误参数、不当重试 | 常为根因触发层 |
| 库函数 | glibc/jemalloc/libpthread | 无效参数封装、缓存未命中 | 中转层 |
| syscall 入口 | 内核 sys_xxx() | 参数校验失败 | 表现层 |
| VFS/协议栈 | 内核 fs/net/ipc | 权限检查、资源短缺 | 表现层或根因层 |
| 设备驱动 | 内核 driver | IO 超时 | 根因层 |
S4:数据流溯源(异常 syscall 生命周期追踪)
围绕 Step 3 的"异常 syscall",在源码中追踪:
应用调用 → 库函数封装 → syscall 入口 → 内核实现 → 错误/资源检查 → 返回
重点审查的根因高发区:
- 错误码忽略:应用调用 syscall 后未检查返回值,直接使用未初始化的资源
- 重试逻辑缺陷:EAGAIN 重试次数过多或间隔过短导致忙等
- 资源泄漏:open/accept 后缺少 close、mmap 后缺少 munmap
- 竞争条件:多个线程/进程同时操作同一 fd 或文件
- 配置上限:
ulimit -n 太小、fs.file-max 耗尽、pid 耗尽
S5:反事实验证(强制;不能止步于"找到可疑 syscall")
用内核语义假设正向推演,并与 strace 现象逐条对齐:
✓ 推演的错误码 == strace 中的 errno?
✓ 推演的调用路径 == strace 中的 syscall 序列?
✓ 推演的耗时模式 == strace 中的耗时分布?
三条全 ✓ 才能判定"根因确认"。
内核语义轨道输出格式:
内核版本:<version>
关键 syscall:<syscall_name>
错误码:<errno> 含义:<strerror>
异常行为:<例如:EAGAIN 每 10ms 重试一次, 持续 30 秒>
根因类型:[应用缺陷 | 配置限制 | 资源泄漏 | 竞争条件 | IO 瓶颈]
因果链:[触发事件] → [内核行为] → [errno/耗时] → [系统表现]
Step 5:交叉验证(双轨汇合,冲突仲裁,置信度收敛)
对每条证据做对齐检查:
| 验证维度 | 调用轨迹结论 | 内核语义结论 | 是否吻合? |
|---|
| 异常 syscall | syscall 名 + errno + 耗时 | 内核实现返回此 errno 的逻辑 | □ 吻合 □ 不符 |
| 关键参数 | fd/文件路径/标志位 | 对应的内核参数校验路径 | □ 吻合 □ 不符 |
| 时间序列 | 异常在 时刻出现 | 对应事件在 时刻发生 | □ 吻合 □ 不符 |
| 根因层 | 应用/库层行为 | 应用/库层首次引入异常 | □ 吻合 □ 不符 |
| 资源状态 | fd 数/内存使用趋势 | 资源上限/泄漏检测 | □ 吻合 □ 不符 |
不一致时的仲裁原则:
异常 syscall/参数:优先信任 strace(客观量测数据)
根因层不符:保留多假设并补证据(检查线程堆栈、perf 采样)
置信度收敛:
- 高:两轨完全吻合 + 反事实验证通过
- 中:两轨基本吻合,但有 1 个维度依赖推断;或仅完成调用轨迹轨道
- 低:两轨存在矛盾且无法解释;或证据链缺失超过两环节
- 需进一步排查:syscall 行为异常但应用和内核代码均无明显缺陷
常见误判陷阱(用于复核结论质量):
- EAGAIN 大量出现 ≠ 异常:非阻塞 IO 模式下 EAGAIN 是正常流量控制
- strace 耗时 ≠ 实际调用耗时:strace 本身 ptrace 机制引入可感知延迟(10-100us/call)
- futex 高耗时 ≠ 锁竞争:也可能是 FUTEX_WAIT 正常休眠等待唤醒
- ENOMEM ≠ 系统 OOM:也可能是 cgroup 限制、overcommit 拒绝、或 ulimit -v 限制
应用/配置问题优先,但以下情况提升内核/硬件怀疑优先级:
① syscall 行为随机(同参数有时成功有时失败)
② 所有应用同时出现同类 syscall 异常
③ dmesg 有对应硬件/驱动错误日志
→ 参考分支 A(持久化错误码)加 dmesg 分析
Step 6:最终输出(按第九节模板落盘)
将 Step 3/4/5 的输出填入第九节报告结构,并显式写清:结论、证据链、排除项、修复建议、验证建议。
第四节:轨道一 —— 调用轨迹分析(逆向推理)
本节已合并进第三节的统一流程(Step 3)。
第五节:轨道二 —— 内核语义分析(正向追踪)
本节已合并进第三节的统一流程(Step 4)。
第六节:交叉验证与结论收敛(双轨汇合)
本节已合并进第三节的统一流程(Step 5)。
第七节:故障类型决策树(两条轨道共用)
本节内容已合并进第三节的统一流程(Step 2)。
第八节:注意事项与置信度评级
本节内容已合并进第三节的统一流程(Step 5)。
第九节:最终报告结构
## 故障概要
故障模式:<Syscall 错误码 / 慢 Syscall / 库函数异常>
置信度:<高/中/低/需进一步排查>
分析轨道:[双轨(轨迹 + 语义)| 单轨(仅轨迹,无源码)]
内核版本:<版本号>
## 调用轨迹轨道结论
目标进程:PID=<pid> Name=<name> 运行时间=<duration>
Syscall 总览:total=<count> errors=<err_count> error_rate=<%>
Top 慢 syscall(按耗时):<syscall> <time>ms
Top 高频错误码:<errno> count=<count> 含义=<strerror>
Top 高频 syscall:<syscall> count=<count>
资源状态:fd=<当前> / <上限> 线程=<当前> / <上限>
调用轨迹归因假设:<一句话>
## 内核语义轨道结论(有源码时填写)
根因代码/配置位置:<配置项/源文件:line>
根因类型:[应用缺陷 | 配置限制 | 资源泄漏 | 竞争条件 | IO瓶颈]
缺陷描述:<精确描述>
触发条件:<需要什么前置状态>
因果链:[触发事件] → [内核行为] → [errno/耗时] → [系统表现]
## 交叉验证结果(有源码时填写)
异常 syscall 吻合: □ 是 □ 否(差异说明:<...>)
关键参数吻合: □ 是 □ 否(差异说明:<...>)
时间序列吻合: □ 是 □ 否(差异说明:<...>)
根因层吻合: □ 是 □ 否(差异说明:<...>)
综合判断:<两轨结论是否一致,若有矛盾如何解释>
## 完整因果链(双轨收敛后)
[触发条件] → [根因] → [内核行为]
→ [异常 syscall] → [系统表现]
## 排除的替代假设
- <假设X>:排除原因 <...>
## 修复建议
最小修复(立即可做):
<具体修复命令或代码修改>
根本修复(设计层面):
<应用层重构/内核参数调整/硬件升级等方向>
## 验证建议
<如何确认根因 + 如何验证修复有效>
第十节:参考文件
references/strace_commands.md:strace/ltrace 命令速查手册
references/syscall_reference.md:常见 syscall 错误码与内核源码路径参考
references/analysis_patterns.md:常见 syscall 故障模式速查