
干过几年 Linux 运维和后台开发之后我对“常用 Linux Debug 命令”的态度发生了明显变化。刚入行总觉得它们是一组要背的选项背不住就查文档干得久了才发现真正值钱的不是命令本身而是敲下去之后你能不能读懂系统在说什么。strace、gdb、tcpdump、perf 这些工具本质上是给一个不停运转的黑盒子装传感器你要做的不是收集数据而是从数据里还原出问题现场。这篇东西不是命令字典而是我平时排查问题时真正会用的那一套组合拳。适合正在被线上问题折磨、又不知道从哪下手的同学也适合刚接触后端或运维、想建立排查思路的新人。1. 先摸清家底系统状态与进程排查1.1 从 top 到 htop别只看那几根进度条我见过太多人打开 top第一眼只看 CPU 那一行的数字红了就喊机器负载高。其实 top 第一行的 load average 三个数才是需要先读的信息它对应 1 分钟、5 分钟、15 分钟的平均运行队列长度。很多人把 load 高直接等同于 CPU 忙这是错的。负载衡量的是可运行线程和不可中断睡眠D 状态线程的总数所以磁盘 IO 卡死了load 一样能飙到几十但 CPU 利用率可能很低。看 CPU 状态行时我会先分清楚 us、sy、wa、id 四类。us 高说明用户态计算在烧 CPUsy 高说明系统调用或内核态路径是瓶颈wa 高说明 CPU 在等磁盘 IOid 高但 load 高多半是 IO 或者锁在拖后腿。按 1 键可以展开每个逻辑核多核机器如果只有几个核跑满说明热点可能集中在线程调度或某条数据路径上。top 里按 shiftP 按 CPU 排序、shiftM 按内存排序这俩键我几乎天天用。但 top 的 CPU 百分比其实是采样间隔内的瞬时值默认大概 3 秒一刷数字跳来跳去不适合判断趋势。要看连续趋势我会用top -d 1配合脚本记录或者干脆用 htop。htop 在树形视图下能直接看出父子进程和线程关系颜色也友好但最小化服务器上不一定装。我的习惯是ssh 上去第一件事敲 top 粗筛需要看线程树或者交互操作再切 htop。实际案例里我碰过一次数据库节点 load 飙到 50 但 top 显示 CPU id 还有 50% 的情况当时差点被“CPU 还有余量”骗过去。后来发现是多块磁盘中的一块故障大量 IO 线程卡进 D 状态负载自然下不来。这种场景下光盯 CPU 数字会耽误很长时间必须把 load、wa、D 状态进程数连起来看方向才能对。1.2 ps 与 pidstat从“这一刻”到“这一段时间”ps aux 最好先别记全列重点看 STAT、RSS、%CPU 这三列。STAT 里的 R 是运行状态S 是睡眠D 是不可中断睡眠Z 是僵尸。D 状态的进程最麻烦它在内核里等 IOkill 不掉只能用 strace 挂上去看它卡在哪个系统调用上或者查存储底层是不是出了问题。Z 状态说明父进程没调用 wait 回收这种一般是编程 bug找到父进程处理就行。我踩过的一个坑是把 ps 里的 %CPU 当实时值用。实际上 ps 的 %CPU 是进程启动以来的平均 CPU 时间占单核的百分比进程刚启动时瞬时打满 100%这个数字可能只有个位数。所以要看实时 CPU用 pidstat 更靠谱。pidstat 1 5 -p 某个PID可以每秒采样一次连续 5 轮能看到真实波动。加-t会细分到线程加-r还能看缺页和内存占用变化排查线程内死循环非常有用。判断内存占用时RSS 是物理内存占用VSZ 是虚拟地址空间大小。有些程序 VSZ 巨大但 RSS 很小比如 JVM 默认预留了大量地址空间这是正常现象不用一看到 VSZ 大就以为内存泄漏。真正的内存问题要看 RSS 持续增长和 vmstat 里的换页指标。1.3 vmstat 与 sar内存换页和历史的黄金搭档vmstat 1 5是内存和调度问题排查的第一个命令。我主要看四组procs 里的 r 和 b、swap 里的 si 和 so、cpu 里的 wa。r 是可运行线程数长期大于 CPU 核数就是 CPU 饱和b 是阻塞在内核队列里的进程数b 持续不为 0 基本是 IO 在拖si/so 只要持续不为 0说明系统正在反复做 swap 换入换出内存告急。vmstat 指标含义重点关注条件r可运行线程数持续超过 CPU 核数说明 CPU 饱和b阻塞在内核队列的进程数持续不为 0排查 IO 和锁si / so从 swap 换入 / 换出持续不为 0说明真实换页waCPU 等待 IO 时间占比过高时配合 iostat 看磁盘顺带说一个容易误判的点free里 swap 使用量不为 0 并不等于内存不足只要 si/so 两个计数器是 0说明没有实际的换页行为swap 空间只是被临时占着。判断内存压力看 si/so 变化比看 swpd 大小更靠谱。sar 这类历史数据工具我单独列出来说是因为它解决了“昨晚三点发生了什么”这种疑难问题。sar -q看历史负载sar -r看内存sar -d看磁盘默认由 sysstat 包的 cron 任务每小时收集一次。没有历史数据的时候就算你命令再熟练也只能对着当前快照干瞪眼。所以我的建议是新机器装完系统的第一件事把 sysstat 装上并确认采集任务在跑这比装任何花哨监控都值。2. 追踪系统调用与程序行为strace 与 ltrace2.1 strace 的入门参数一定要记组合strace 是我线上排障用到最多的命令没有之一。它的原理很简单通过 ptrace 机制拦截目标进程的每个系统调用把名称、参数、返回值、耗时都打出来。核心参数组合我固定在用strace -f -tt -T -e tracenetwork -p 12345-f表示跟随线程多线程程序必须加不然只跟踪主线程。-tt输出微秒级时间戳排查“哪一步慢”时这是关键依据。-T显示每个系统调用的耗时瞬间能看出慢点在哪里。-e trace是过滤器network、file、desc、signal、process 都常用。-o指定输出文件避免和业务日志混在一起也防止终端刷爆。实操里最常见的误区是 strace 不加-f结果子线程里的系统调用一个都看不到排查方向全错。另一个误区是直接裸 strace 高并发进程输出文件疯狂膨胀几分钟就能吃掉几个 GB 磁盘。所以线上用 strace 前一定要想清楚要不要过滤要不要限定输出文件要不要限制时间窗口。2.2 一个连接失败案例讲透读 strace 的方法模拟一个很常见的场景某后端服务连接 Redis 时不时超时。我先 strace 挂上去输出里看到这样几行connect(7, {sa_familyAF_INET, sin_porthtons(6379), sin_addrinet_addr(10.0.0.5)}, 16) -1 EINPROGRESS (Operation now in progress) poll([{fd7, eventsPOLLOUT}], 1, 30000) 1 ([{fd7, reventsPOLLOUT}]) getsockopt(7, SOL_SOCKET, SO_ERROR, [0], [4]) 0这几行意味着什么connect 返回 EINPROGRESS 是正常的说明非阻塞连接正在建立poll 等到了 POLLOUTgetsockopt 取到的 SO_ERROR 是 0说明 TCP 层连接实际上成功了。但业务还是超时这时候问题多半不在本进程而在服务端或者网络中间环节。我会立刻用后面要讲的 ss 和 tcpdump 去看连接状态和报文。反过来如果 strace 里 connect 直接返回 ETIMEDOUT那基本是 SYN 没得到响应重点查防火墙和对端监听。读 strace 输出的方法总结成一句话先看返回值和 errno再看耗时列。返回值是 -1 的调用括号里都有 errno 解释耗时列里长时间卡在一个调用上就是瓶颈所在。加个-y参数可以让每个 fd 直接输出对应的 socket 信息或文件路径省去自己猜。2.3 ltrace看库函数调用别和 strace 混为一谈strace 管系统调用ltrace 管动态库函数。两者有重叠但场景不同。我常用 ltrace 查内存和字符串相关热点比如ltrace -e mallocfree ./app它能统计程序高频调用 malloc/free 的次数很多内存暴涨问题从这里能找到突破点。但我必须说两个限制第一静态编译的程序用不了 ltrace第二现代 glibc 里不少函数会被内联优化掉或者最终直达系统调用这时候 trace 不到是正常的不代表程序没有调用。在生产环境ltrace 的替代速度很快。库函数调用的开销比系统调用还频繁挂上 ltrace 之后程序性能会断崖式下跌。我一般只在测试环境或者明确怀疑某个库存在异常时用线上宁愿多花一点时间上 bpftrace 的 USDT 探针。2.4 strace 的性能损耗实测过才有概念有次我为了追一个网关进程的偶发超时直接strace -p挂上去结果本来稳定的网关 CPU 从 5% 飙到 90% 多。原因很简单ptrace 拦截每个系统调用都会让进程停一下等于每个 read/write 都被按了暂停键。所以对高并发进程我强烈建议先跑一轮汇总strace -f -c -p 12345-c只统计调用次数和耗时分布不输出每个调用详情几秒钟就能看出来哪个系统调用是热点然后再决定要不要针对性地用-e trace继续看。如果需要全量输出限定时间窗口比如跑 10 秒就 CtrlC别让它无限跑。如果连汇总都怕影响业务那就直接上 bpftrace 或者 perf trace这类基于内核事件的工具开销小得多。这个取舍我在后面第六部分会再展开。3. 网络问题定位从 ss 到 tcpdump3.1 现代网络排查首选 ss顺手把 netstat 换掉很多教程还在让你netstat -anpt但 netstat 遍历 /proc 的方式在大连接数下特别慢ss 直接走 netlink速度和信息量都碾压它。排查监听端口和连接状态我永远先敲ss -tanlp-t是 TCP-a是所有状态-n不做域名反解-l只看监听-p显示进程。合起来能快速回答两个问题这个端口到底有没有服务在听如果有进程是谁看连接状态时有几类情况需要特别敏感。SYN_SENT 大量积压说明客户端发出的 SYN 没人理对端可能没监听、防火墙 DROP 了或者中间网络丢包。TIME_WAIT 多一般不用慌是四次挥手的正常残留。但如果有几十万个 TIME_WAIT内核连接表会被拖累可以看net.ipv4.tcp_tw_reuse和连接回收策略不过不要无脑开tcp_tw_recycle这个参数在新内核里引发的坑比省的内存更大。LISTEN 状态下 Recv-Q 和 Send-Q 的意义有个经典坑。LISTEN 的 Send-Q 显示的是当前 accept 队列的长度上限Recv-Q 是已经积压但还没被 accept 的连接数。如果 Recv-Q 长期顶着上限说明应用处理新连接的速度跟不上这时候该上 gdb 或 strace 看应用到底卡在哪。ss 状态常见含义排查方向SYN_SENT 多对端无响应防火墙、对端监听、丢包TIME_WAIT 多连接正常关闭残留内核参数不盲目开 recycleESTAB 但 Recv-Q 大应用读不及时检查应用逻辑、锁、IOLISTEN 的 Recv-Q 积压accept 队列已满应用处理连接速度不够3.2 tcpdump 抓包别靠猜把报文抓出来看网络问题的最终裁决者是报文不是日志。tcpdump 最常用的参数我固定在用tcpdump -i any -nn -s 0 port 8080 -w /tmp/xx.pcap-i any比指定 eth0 安全因为不少服务跑在 docker 网桥或 loopback 上指定单网卡容易漏-nn必须加否则抓包时每个 IP 都要做反解慢且乱-s 0捕获完整包不能只抓包头-w写成 pcap 文件现场只做采集分析放到电脑上用 Wireshark 或 tshark 看。排连接超时我最喜欢看的输出是 SYN 重传。如果只见客户端疯狂发 SYN却没有 SYN-ACK九成是中间链路把 SYN 丢了或者对端防火墙丢包。如果对端回了 SYN-ACK 但客户端一直不 ACK那就是客户端侧的问题。如果对端直接回 RST十有八九是端口没监听或者防火墙 REJECT 策略。这些结论从业务日志根本看不出来但抓包 10 秒钟就能确定方向。常用的过滤表达式也很关键。host 10.0.0.5过滤单个 IPtcp port 80过滤端口tcp[13] 2 ! 0只看 SYN 包。抓 HTTP 调试时加-A可以直接看明文内容。说一个抓包时的坑tcpdump 自己的抓包缓冲也可能丢包。在高流量端口上抓包看到报文不连续别急着下结论加-B 4096加大 buffer 再试。另外抓包别覆盖太久现场采集完立刻开分析不然 pcap 文件很快撑爆磁盘。3.3 lsof端口“被占”到底是哪种占法lsof 是排查端口冲突和“文件删不掉”的利器。查端口占用lsof -i:8080如果输出显示有进程 LISTEN那是真占用如果看到一堆 TIME_WAIT 状态的旧连接那是连接残留SO_REUSEADDR 可以解决。有时候你明明 kill 了进程端口还是提示被占八成是子进程没退干净或者处于 FIN_WAIT/TIME_WAIT 状态用 lsof-i:8080再看命名空间和进程号一层层慢慢找。lsof 另一个高频用法是lsof -p PID列出进程打开的所有文件描述符排查 fd 泄漏特别好用。有个典型场景磁盘明明删了大文件df 却还是满的是因为有进程仍然持有这个文件的句柄。lsof L1能看到所有被删除但还被打开的文件找到那个进程重启或者让它释放 fd空间就回来了。3.4 一次“客户端报超时”的完整排查链路我把网络排查的固定路径总结一下顺序很重要。第一步ss -tanp | grep 目标端口确认连接是 ESTAB 还是 SYN_SENT。如果是 SYN_SENT第二步直接tcpdump -i any -nn host 对端IP抓包确认 SYN 有没有出网、对端有没有回包。第三步查对端ss 看监听tcpdump 抓对端网卡看包是否进来。第四步查中间设备比如防火墙规则。这套链路我测过无数次绝大多数网络超时在 20 分钟内能定位到具体环节比打开业务代码一行行猜快得多。关键是每一步都得先有证据再下结论不要看到超时日志就去改客户端的超时时间那是在掩盖问题而不是解决问题。4. 动态调试与崩溃分析gdb 的核心用法4.1 让崩溃现场开口说话先配好 core dump程序一崩溃就“什么都没留下”多半不是程序没问题而是你的系统压根没生成 core。默认很多发行版ulimit -c是 0也就是禁止生成核心转储文件。排查前先确认ulimit -c unlimited cat /proc/sys/kernel/core_patterncore_pattern 决定了 core 文件写到哪、叫什么名字。我习惯设置成/var/core/%e.%p.%t.core其中%e是程序名、%p是 PID、%t是时间戳这样不会覆盖老文件。要永久生效把这一行写进/etc/sysctl.conf并sysctl -p。有一点要注意如果 core_pattern 里出现 systemd-coredump 或 apport 字样说明 core 被交给了外部服务接管并不一定落在当前目录得先看 systemd-coredump 的存储位置再去找文件。生产环境开 core 之前先想清楚磁盘容量一个内存占用 10G 的进程core 文件可能比内存还大别因为抓崩溃现场把磁盘写满。我见过一个团队开了 core 之后两天没清理服务器磁盘告警反而引发了更大的事故。所以开 core 要配套清理策略比如用 logrotate 或定时任务。4.2 gdb 常用命令组合bt 之外还要会这几招拿到 core 文件之后gdb -q ./app /var/core/app.12345.1700000000.core进去先敲btbacktrace看栈帧。绝大多数崩溃栈顶那个帧就是出事地点。但有两种情况不能盲信 bt一是栈被写坏调用栈全是乱码二是问题线程本身可能不是崩溃线程多线程程序里另一个线程改坏了共享数据崩溃只是“被崩”。乱码堆栈怎么办看info registers确认寄存器的值合不合理再用x/i $pc把当前指令反汇编出来。调试被 strip 过的二进制时只有地址没有函数名这种场景只能靠符号表文件或者重新编译一份带-g的版本。用-O0 -g重新编译的另一个好处是变量不会被优化掉调试体验完全不同。gdb 命令用途我的常用组合bt查看调用栈崩溃后第一件事info threads列出所有线程配合 thread apply all btframe N切换栈帧在 bt 之后逐层看info args / info locals查看参数和局部变量判断传入值是否异常print / x/4gx打印变量、查看内存检查指针内容在栈帧里切换frame N然后info args看函数参数、info locals看局部变量再用print打印关键变量。我习惯把怀疑的指针用x/4gx直接打内存看内容往往能发现指针明显是错误的地址。4.3 多线程程序调试条件断点和全线程堆栈多线程崩溃第一步是info threads看所有线程然后thread apply all bt把所有线程的栈打出来这一步能看到其他线程当时在干什么。比如主线程在等锁后台线程在写一个队列而崩溃栈正好卡在队列操作上基本可以锁定并发访问问题。调试运行时状态用条件断点很关键。break file.c:120 if x 5只有满足条件才停不然一个热路径里的断点会让程序几乎卡死。附带说一个编译优化的坑用-O2编译的代码变量可能被优化到寄存器甚至完全消失gdb 里 print 一个变量会提示 not available。这时候不要怀疑 gdb 坏了重新编译一个-O0 -g的版本去复现。如果你面对的是已经上线的服务没有 core、也没法轻易重新编译那至少可以用thread apply all bt拿到线程栈“并发修改共享数据”这类问题在栈里往往有明显特征比如两个线程同时卡在同一个锁或者同一个链表操作函数上。4.4 attach 到运行中进程能救急但别乱来线上问题如果没有 core可以用gdb -p PID直接附到正在运行的进程上。但有个前置条件内核参数kernel.yama.ptrace_scope默认可能是 1只允许进程 attach 自己的子进程容器里还需要 CAP_SYS_PTRACE 权限。attach 动作本身会把进程暂停所以在线上高峰期做这件事有风险我自己宁可选择快速 batch 模式gdb -p 12345 -batch -ex bt -ex info threads -ex quit-batch执行完指定命令立即退出进程暂停时间控制在秒级以内。类似技术手段在低峰期或故障演练环境练习几次别在第一次故障时临时学。5. 日志与内核诊断dmesg 与 journalctl5.1 dmesg内核环形缓冲区里常常藏着关键答案当进程“神秘消失”的时候很多人第一反应翻应用日志翻半天一无所获。我的习惯是先敲dmesg -T | tail。内核的环形缓冲区记录了 OOM killer 杀进程、硬件 IO 错误、段错误地址、网卡异常等信息。最典型的例子应用进程突然没了dmesg 里一行 Out of memory: Killed process 就会告诉你它是被内存压力干掉的而不是正常退出。dmesg -T把时间戳转成可读时间格式是很重要的细节。排查“进程什么时候没的”看这个时间和业务日志的断点对齐能快速确定是不是同一个时刻。如果系统里用 systemd 管理journalctl -k也能看到同样的内核日志只是入口不同。有一条经验我想多说一句不要等到进程消失才看 dmesg。平时巡检时随手看一眼很多硬件层的小毛病比如 NVMe 盘报错、网卡链路抖动都会先在这里出现。等到真出大故障再来查可能已经被大量新日志淹没了。5.2 journalctlsystemd 时代必会的过滤姿势journalctl 的核心优势是结构化过滤。排障时我最常用的三组journalctl -u myservice --since 30 min ago journalctl -u myservice -f journalctl -p err -n 100-u指定 unit--since圈定时间窗-f实时跟踪-p err只看 error 级别。还有一个容易被忽略的细节journal 默认写内存重启就没了。想持久保存需要存在/var/log/journal目录很多发行版默认不创建。我建议新机器上机后立刻手动建这个目录并重启 journald不信你可以试试看重启后所有历史日志就消失了。日志量控制也是必修课。默认 journald 不受限制地写长时间高输出日志的服务能把 /var/log 撑爆。在/etc/systemd/journald.conf里设置SystemMaxUse1G、SystemKeepFree20%、RateLimitIntervalSec5s、RateLimitBurst10000改完重启 systemd-journald。这四条我几乎每台服务器都会配至今没遇到过日志目录把根分区写满的故障。另外journalctl 还支持按进程号过滤journalctl _PID12345排查某个具体进程的行为时很有用。配合_COMM按程序名过滤、-o json-pretty输出结构化 JSON写自动化分析脚本也很方便。5.3 日志时间不同步排查方向就会偏跨机器对照日志时时区不一致是个隐形的坑。我听说过一次排查持续一整晚没有结果最后发现 A 机器日志是 UTCB 机器是 CST时间根本对不上。排查任何“两个节点之间谁先谁后”的问题第一步用timedatectl确认时区最好统一 UTC或者在命令里直接用--since 2024-01-02 15:04:05这类 ISO 时间避免歧义。还有一个更坑的场景虚拟机重启后硬件时钟不准journal 里两个事件之间的时间戳会跳变。如果发现日志时间线断裂先检查是否发生过时钟跳变用journalctl --list-boots能看到每次启动的起止时间把日志分成 boot 阶段看比瞪着跳跃的时间戳猜要有效得多。6. 进阶手段性能剖析与动态追踪6.1 perf采样式性能剖析不打断程序perf 是我想重点推荐的一个工具很多同学觉得它高不可攀其实核心用法就三件事perf top看热点perf record采样perf report看报告。perf record -F 99 -g -p 12345 -- sleep 30-F 99表示每秒采样 99 次比 1000Hz 更不干扰业务-g记录调用栈-- sleep 30让采样 30 秒自动结束。采样结束后perf report能看到每个函数占的采样比例。但默认输出不够直观我习惯把它转成火焰图perf script导出数据再用 stackcollapse-perf.pl 压缩堆栈、flamegraph.pl 生成 SVG。这一套在浏览器里拉开看热点路径一眼就能定位瓶颈函数。容器环境跑 perf 有个前提需要足够的权限或者内核开启了 perf_event 限制。很多容器默认没有 CAP_PERFMON跑perf top会报权限错误这是环境限制不一定是命令用错了。排查容器内性能问题时更可行的方案是到宿主机上按进程 PID 做采样。6.2 bpftrace一行脚本解决“想查又不敢乱动”bpftrace 是目前排查动态问题最舒服的方式不用 attach、不停进程、不产生大量日志。它的典型用法是用 tracepoint 统计事件频率bpftrace -e tracepoint:syscalls:sys_enter_openat { [comm] count(); }这条命令统计每秒哪些进程在调用 openat输出按数量排序几秒钟就能看出谁在疯狂打开文件。同类思路还能统计 TCP 连接、锁等待、内存分配等。bcc 工具集里的 opensnoop、execsnoop、tcplife 也建议装上它们是现成的一行脚本排查效果立竿见影。但 bpftrace 对内核版本和权限有要求内核需要开启 BPF 支持一般 4.x 以上才比较完整并且需要 root 或者 CAP_BPF 能力。老内核上别硬试老老实实回去用 strace 或者 auditd。kprobe 类探针的函数名会随内核版本变化写之前先用bpftrace -l查一下当前可用的探针名避免脚本跑出空白结果。6.3 我见过的三种“假 debug”行为引以为戒排查技术之外我更想提醒的是思路问题。第一种问题复现后先重启。重启确实能把现场清掉但也会把证据清掉根因没找到下次故障周期只会更短。第二种只盯着业务日志不查内核日志和系统调用。业务日志是应用自己写的可能因为缓冲、异步、异常分支漏掉关键信息而 dmesg、strace 看到的是硬事实。第三种看到 CPU 高就 kill 进程。死循环可能只出现在某一个线程直接 kill 会丢掉全部上下文。正确做法是top -H找到线程再 perf 采样确认是计算密集还是锁等待。这套内容背后有一个统一的策略从外到内、从粗到细、从系统到代码。系统层信息先圈定范围进程层信息定位线程最后再用调试器和动态追踪看具体的调用路径。思路比命令列表重要得多命令只是你理解系统的工具。我个人在实际操作中的体会是把上面这些命令固化成一套“现场采集脚本”特别值。我自己的做法是在每台服务器上放一个 debug-collect.sh出问题时先跑一轮自动收集 dmesg、ss、vmstat、最近日志、关键进程的线程栈10 秒钟把现场快照留下来然后再慢慢分析。很多崩溃现场如果错过前 10 秒之后想复现可能就要等上几天。这个脚本本身不复杂就是把本文提到的命令按顺序串了一遍但对追查偶发问题帮助极大。如果你目前只记得零散命令建议先照着这篇文章的章节顺序把自己最常用的流程写下来下次直接执行你会发现排查速度至少快一倍。