首页 / 资讯中心 / 文章详情

Linux strace命令实战:系统调用追踪与线上故障排查指南

Linux strace命令实战:系统调用追踪与线上故障排查指南 ★ FEATURED ARTICLE
1. 为什么每个Linux老手都把strace当成最后的底牌刚接手一台线上服务器服务起不来日志里只有一句干巴巴的Connection refused没有任何堆栈没有任何线索。你查了配置文件、看了端口占用、重启了三遍服务问题依旧。这时候有经验的人会默默敲下一行命令几秒钟后指着屏幕说它卡在读取一个不存在的配置文件上了。这行命令就是strace。strace是Linux系统下一款用来追踪进程系统调用和信号的工具。说人话就是它能告诉你一个程序在运行过程中到底向操作系统内核发出了哪些请求、收到了什么回应、在哪里卡住了、在哪里报错了。程序报错不可怕可怕的是它不告诉你为什么报错。strace就是那个逼着程序把实话说出来的工具。这篇文章适合谁看如果你是在Linux环境下做开发、运维、测试的从业者遇到过程序行为诡异但日志沉默的情况那这篇内容就是为你准备的。如果你刚接触Linux只会用ls、cd、top这几个命令也不用担心我会从最基础的概念讲起逐步过渡到实战场景保证你能跟着操作。我自己的经历是刚工作那会儿排查一个服务启动超时的问题折腾了大半天最后一位前辈用strace跟了不到十秒就定位到了——程序在尝试连接一个已经下线的配置中心地址每次连接超时30秒重试三次所以启动要90多秒。日志里什么都没打因为连接超时被程序内部静默处理了。从那以后strace就成了我工具箱里排在前三位的必备工具。接下来的内容我会从系统调用的基本概念讲起然后带你跑通第一个strace命令再深入到过滤、统计、跟踪多进程等进阶用法最后用几个真实的排障场景把知识点串起来。每个环节我都会解释为什么要这样做而不是只丢给你一堆参数让你背。2. 系统调用程序与内核之间的那扇门2.1 用户态和内核态的边界到底在哪要理解strace在做什么得先搞清楚一个基础概念系统调用System Call。现代操作系统把内存分成了两个区域用户空间和内核空间。你写的应用程序跑在用户空间权限受限不能直接操作硬件、不能直接读写磁盘、不能直接发网络包。当程序需要做这些事情的时候它必须通过系统调用向内核申请由内核代为执行。打个比方你住在一个高档小区里家里什么都有但你不能随便出小区大门。你要寄快递、要买菜、要修水管都得通过物业前台去协调。系统调用就是你跟物业前台之间的那个窗口——你递进去一张申请单前台处理完把结果递出来。常见的系统调用包括openat/open打开文件read/write读写文件描述符socket/connect创建套接字、建立连接fork/clone创建进程或线程execve执行新程序mmap内存映射stat/fstat获取文件状态信息一个程序从启动到退出会发出成百上千次系统调用。每一次调用都有输入参数、返回值、可能的错误码。strace做的事情就是把这些调用完整地记录下来展示给你看。2.2 strace的输出到底在说什么先看一个最简单的例子。运行strace ls你会看到类似这样的输出execve(/usr/bin/ls, [ls], 0x7ffd5e8f3a20 /* 23 vars */) 0 brk(NULL) 0x55a8f2c1a000 access(/etc/ld.so.preload, R_OK) -1 ENOENT (No such file or directory) openat(AT_FDCWD, /etc/ld.so.cache, O_RDONLY|O_CLOEXEC) 3 fstat(3, {st_modeS_IFREG|0644, st_size72384, ...}) 0 mmap(NULL, 72384, PROT_READ, MAP_PRIVATE, 3, 0) 0x7f3a2c1e0000 close(3) 0 ... write(1, file1.txt file2.txt\n, 21) 21 close(1) 0 exit_group(0) ?每一行的格式是系统调用名(参数列表) 返回值。几个关键点execve是程序启动的第一步内核加载可执行文件并开始执行。access检查文件是否存在返回-1 ENOENT表示文件不存在。注意这里返回了错误但程序并没有崩溃说明程序对这个错误做了处理。openat打开动态链接库缓存文件返回3这是文件描述符。write把文件列表写到了标准输出文件描述符1返回实际写入的字节数21。exit_group表示进程退出。注意strace默认会把输出写到标准错误stderr所以如果你用strace ls output.txtls的结果会进文件但strace的追踪信息还是会打在终端上。要同时保存追踪信息用strace -o trace.log ls。2.3 为什么系统调用层面的信息比日志更有价值应用程序的日志是开发者想让你看到的信息。但程序在运行过程中实际做的事情远比日志记录的多。日志可能告诉你正在连接数据库但strace会告诉你它先读了/etc/hosts然后发了DNS查询拿到IP后尝试TCP连接连接被拒绝然后重试了三次每次间隔5秒。这些细节在日志里往往被简化成一句数据库连接失败。但真正导致问题的原因可能藏在任何一个环节里——DNS解析慢、防火墙规则、端口写错、连接池配置不合理等等。strace的价值就在于它不经过应用程序的翻译直接把最原始的系统调用行为摆在你面前。程序不会撒谎系统调用也不会。3. 从零开始跑通第一个strace3.1 安装与权限准备大多数Linux发行版默认没有安装strace。安装方式# Debian/Ubuntu系 sudo apt-get install strace # RHEL/CentOS系 sudo yum install strace # 或者用dnf sudo dnf install strace安装完成后直接运行strace会提示你需要指定要追踪的命令。最基本的用法就是strace 命令。但这里有一个权限问题需要说清楚。strace依赖于ptrace系统调用普通用户只能追踪自己有权限的进程。如果要追踪其他用户的进程或者系统服务需要root权限或者具备CAP_SYS_PTRACE能力。提示在某些生产环境的安全策略下ptrace可能被禁用。如果你运行strace时看到ptrace: Operation not permitted之类的错误先检查/proc/sys/kernel/yama/ptrace_scope的值。值为1时只允许追踪子进程值为2时只允许root追踪值为0时无限制。修改这个值需要评估安全影响。3.2 追踪一个命令的完整生命周期我们从一个简单的场景开始追踪cat命令读取一个文件的过程。strace cat /etc/hostname输出会很长因为cat启动时需要加载动态链接库、初始化各种运行时环境。如果你只想看跟文件读取相关的调用可以用-e参数过滤strace -e traceopenat,read,write,close cat /etc/hostname这样输出就清爽多了你能清楚地看到cat打开了/etc/hostname读取了内容写到了标准输出然后关闭了文件描述符。这里有一个实用技巧-e trace后面可以跟多个调用名用逗号分隔。常用的组合包括过滤类别参数写法适用场景文件操作-e tracefile排查文件找不到、权限不足网络操作-e tracenetwork排查连接失败、超时进程操作-e traceprocess排查fork/exec相关问题信号操作-e tracesignal排查进程被信号杀死内存映射-e tracemmap,munmap排查内存分配问题描述符操作-e tracedesc排查文件描述符泄漏3.3 让输出变得可读几个必会的参数strace的原始输出信息量很大但可读性一般。以下几个参数能大幅提升阅读体验-f跟踪子进程和线程。很多服务会fork出子进程或者创建多线程不加-f的话你只能看到主进程的调用。加上之后每一行前面会多一个PID前缀方便区分是哪个进程发出的调用。-t/-tt/-ttt加上时间戳。-t显示到秒-tt显示到微秒-ttt显示Unix时间戳。排查性能问题时非常有用能精确看到每个调用花了多长时间。-T显示每个调用的耗时。输出格式类似0.000123单位是秒。这个参数配合-t使用能快速定位到哪个调用是性能瓶颈。-s指定字符串的最大显示长度。默认只显示32个字符超过部分用...省略。排查路径问题时经常需要加大这个值比如-s 256。-y显示文件描述符对应的路径。这个参数非常实用它会把read(3, ...)变成read(3/etc/config.ini, ...)让你一眼看出操作的是哪个文件。-p附加到正在运行的进程。格式是strace -p PID。调试已经在跑的服务时必用。-o输出到文件。格式是strace -o /tmp/trace.log 命令。追踪信息量大时写文件比刷屏更合适。把这些参数组合起来一个典型的排查命令长这样strace -f -tt -T -s 256 -y -o /tmp/service_trace.log ./my_service这条命令会跟踪所有子进程和线程显示微秒级时间戳显示每个调用的耗时字符串显示到256字符显示文件描述符路径把结果写到日志文件。4. 过滤与统计从海量调用中捞出关键信息4.1 用-e filter精准定位问题域strace的输出动辄几千行如果一行行看效率极低。-e系列参数就是用来做减法的。除了前面提到的-e trace按类别过滤还有几个实用的过滤方式按路径过滤-e traceopenat -P /etc/myapp/config.ini-P参数指定只追踪操作特定路径的系统调用。当你怀疑某个配置文件被反复读取或者读取失败时这个参数能直接锁定目标。按返回值过滤-e statusfailed只显示失败的系统调用。这个参数在排查哪里出错了时特别高效因为成功的调用通常不是问题所在。按信号过滤-e signalSIGSEGV只追踪特定信号。排查段错误时可以配合-e tracesignal一起使用。我个人的习惯是第一遍先用-e tracefile快速扫一遍文件操作看看有没有明显的文件不存在或权限不足第二遍用-e tracenetwork看网络连接情况如果还没找到线索再放开过滤看全量输出。4.2 -c统计模式一眼看出谁在拖后腿strace -c会以统计报表的形式输出而不是逐行打印。输出格式类似% time seconds usecs/call calls errors syscall ------ ----------- ----------- --------- --------- ---------------- 45.23 0.123456 123 1000 read 30.11 0.082345 82 1000 write 15.67 0.042789 42 1000 openat 5.23 0.014278 14 1000 close 3.76 0.010267 10 1000 fstat ------ ----------- ----------- --------- --------- ---------------- 100.00 0.273135 5000 total这个报表告诉你哪个系统调用被调用了最多次、总共花了多少时间、平均每次多少微秒、有多少次出错。排查性能问题时-c模式是第一步。如果发现某个调用占了大量时间再针对性地用-e trace去细看。注意-c模式下strace本身的开销会影响统计结果。因为每次系统调用都要被拦截和记录实际耗时会被放大。所以-c的结果更适合用来做相对比较而不是绝对性能测量。如果要做精确的性能分析应该用perf或者ftrace。4.3 时间戳与耗时定位卡顿的精确位置排查服务响应慢这类问题时时间信息是关键。-tt -T组合能给你每个调用的精确开始时间和执行耗时。举个例子你追踪一个HTTP服务的请求处理过程发现某个请求花了3秒才返回。用strace -f -tt -T -p PID追踪后你可能会看到这样的片段15:32:01.123456 read(5/etc/resolv.conf, ...) 1024 0.000045 15:32:01.123567 socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP) 6 0.000012 15:32:01.123589 connect(6, {sa_familyAF_INET, sin_porthtons(53), ...}) 0 0.000008 15:32:01.123612 sendto(6, ......, 32, ...) 32 0.000015 15:32:04.123789 recvfrom(6, ...) 48 3.000177最后一行显示recvfrom花了3秒才返回说明DNS查询超时了。问题定位完成。如果没有-T参数你只能看到调用顺序但不知道哪个调用慢。加上-T之后耗时超过阈值的调用一目了然。4.4 跟踪多进程服务的正确姿势现代服务很少是单进程的。Nginx有master和worker很多应用会fork子进程处理请求Java服务更是线程一大堆。这时候-f参数是必须的。但-f的输出会混在一起PID前缀虽然能区分但阅读起来还是费劲。有两个改善方法方法一用-ff把每个进程的输出写到单独的文件。格式是strace -ff -o /tmp/trace ./service会生成/tmp/trace.PID这样的文件每个进程一个。方法二用-f -o配合grep过滤特定PID。先跑一遍拿到PID列表然后针对性地看某个PID的调用。还有一个容易忽略的点strace附加到多线程进程时默认只跟踪主线程。要跟踪所有线程必须加-f。这个坑我踩过——追踪一个Java服务时发现主线程什么也没干所有工作都在线程池里不加-f完全看不到有效信息。5. 实战场景strace能解决哪些真实问题5.1 场景一服务启动失败日志无输出这是最经典的strace应用场景。程序启动就退出日志文件是空的systemctl status只显示failed。排查思路strace -f -tt -s 256 -o /tmp/startup_trace.log ./my_service然后看日志的最后几十行。程序退出前的最后几个系统调用往往就是问题所在。常见发现openat(AT_FDCWD, /etc/myapp/config.ini, O_RDONLY) -1 ENOENT配置文件路径写错了或者文件确实不存在。openat(AT_FDCWD, /var/log/myapp/, O_WRONLY|O_CREAT, 0644) -1 EACCES日志目录没有写权限。connect(3, {sa_familyAF_INET, sin_porthtons(3306), ...}) -1 ECONNREFUSED数据库连不上。execve(/usr/bin/python3, ...) -1 ENOENT解释器路径不对。有一次我遇到一个服务启动就退出strace显示它在openat一个/proc/sys/net/ipv4/ip_local_port_range文件时返回了EACCES。原因是容器运行时把这个文件挂载成了只读而程序启动时需要读取并修改它。问题根源找到后调整容器配置就解决了。5.2 场景二程序卡死不知道卡在哪程序不退出但也不响应请求。top看CPU占用很低jstack如果是Java也看不出所以然。这时候用strace -p PID附加到进程上观察它当前在做什么。如果输出停在某个调用上不动了那就是卡住的位置。常见的卡死点read一个管道或socket对端没有发数据也没有关闭连接。connect一个不可达的地址TCP握手超时。futex等待锁另一个线程持有锁但死锁了。wait4等待子进程退出但子进程变成了僵尸。提示用strace -p附加到进程时如果进程处于D状态不可中断睡眠strace可能也会卡住。这时候可以加-e tracenetwork缩小范围或者用-p配合timeout命令限制追踪时间。5.3 场景三文件描述符泄漏排查服务运行一段时间后报Too many open files但不知道是哪里泄漏的。用strace -f -e traceopenat,close -p PID追踪一段时间然后统计openat和close的次数。如果openat明显多于close说明有文件描述符没有被正确关闭。更精确的做法是用-y参数显示文件描述符对应的路径然后看哪些文件被反复打开但没有关闭。strace -f -y -e traceopenat,close -p PID 21 | grep -E openat|close | tail -100如果发现某个配置文件或日志文件被反复打开每次都是新的文件描述符但对应的close调用很少那泄漏点就找到了。5.4 场景四网络连接超时的根因分析服务调用外部接口超时但不确定是DNS问题、网络问题还是对端问题。strace -f -tt -T -e tracenetwork -p PID观察输出如果卡在connect上说明TCP握手没有完成可能是网络不通或对端没监听。如果卡在sendtoDNS查询上说明DNS解析有问题。如果connect很快返回但recvfrom很慢说明对端处理慢。如果看到connect返回EINPROGRESS然后poll等待很久说明是非阻塞连接超时。我处理过一个案例服务调用某个内部API偶尔超时。strace显示connect调用在大部分情况下都是毫秒级返回但偶尔会卡住5秒。进一步排查发现是DNS解析偶尔走了一个响应很慢的DNS服务器。后来在/etc/hosts里加了静态解析问题就消失了。6. 进阶技巧与避坑指南6.1 性能开销strace不是免费的strace通过ptrace机制拦截系统调用每次拦截都会导致进程上下文切换开销不小。在高并发场景下strace可能让程序性能下降几倍甚至几十倍。所以有几个原则不要在高峰期对生产环境的核心服务做长时间strace。尽量用-e trace缩小追踪范围减少拦截次数。用-c做统计时先跑短时间采样不要一直挂着。如果只是想知道系统调用的大致分布考虑用perf trace替代开销更小。提示strace的输出量可能非常大。一个繁忙的服务每秒可能产生几万行追踪日志。用-o写文件时确保磁盘空间充足并且追踪时间不要太长。6.2 容器环境下的特殊处理在容器里用strace有几个坑坑一权限不足。默认的容器安全配置可能禁止ptrace。需要在启动容器时加--cap-addSYS_PTRACE或者调整seccomp配置。坑二PID命名空间。容器内的PID和宿主机上的PID不一样。在容器内用strace -p附加进程时用的是容器内的PID。如果要追踪容器内进程但从宿主机操作需要先找到宿主机上对应的PID。坑三文件路径。容器内的文件系统是隔离的strace显示的文件路径是容器内的路径。排查文件不存在问题时要确认是在容器内还是宿主机上找文件。6.3 输出太大怎么办截断与采样策略当追踪信息量太大时有几个策略策略一用-e trace做减法。这是最有效的方法。先确定问题域只追踪相关的系统调用类别。策略二用-e statusfailed只看失败。如果问题是某个操作失败了这个参数能直接过滤掉大量成功的调用。策略三用timeout限制追踪时间。比如timeout 10 strace -f -o /tmp/trace.log ./service只追踪10秒。策略四用-s限制字符串长度。默认32字符如果不需要看完整路径保持默认即可。策略五追踪一段时间后用grep做后处理。比如只看包含ENOENT或EACCES的行。6.4 几个容易踩的坑坑一忘记加-f。追踪多线程服务时不加-f只能看到主线程会误以为程序什么都没干。坑二把strace的输出和程序的输出混在一起。strace默认输出到stderr程序输出到stdout。如果重定向不当两者会混在一起。用-o分开写是最稳妥的。坑三在strace下运行setuid程序。strace会阻止setuid权限的提升导致程序行为异常。这不是strace的bug而是安全机制。坑四忽略strace本身对时序的影响。有些并发问题在strace下不会复现因为strace改变了进程的调度时序。如果问题只在没有strace时出现可以考虑用ltrace或者perf替代。坑五追踪Java服务时被大量futex调用淹没。Java的线程同步大量使用futexstrace输出里全是futex调用。这时候用-e trace!futex排除掉或者用-e tracenetwork,file只看关心的部分。7. 我的个人经验什么时候该用strace什么时候不该用用了这么多年strace我总结出一个判断标准当程序的行为和你的预期不一致但程序本身不告诉你原因时用strace。当程序已经明确告诉你哪里错了只是你还没看日志时先看日志。strace最擅长的是黑盒场景——你不了解程序的内部实现或者程序没有输出足够的日志。它能把程序与操作系统之间的所有交互摊开给你看这是任何日志框架都做不到的。但它也有明显的短板。strace只能看到系统调用层面看不到应用程序内部的函数调用。如果问题出在应用逻辑层面比如算法错误、数据结构问题strace帮不上忙。这时候需要用gdb、ltrace或者应用层的profiler。另外strace的输出解读需要一定的经验。同样的ENOENT错误可能是配置文件真的不存在也可能是程序在探测可选路径属于正常行为。分清楚预期内的错误和预期外的错误是有效使用strace的关键。最后分享一个我常用的组合命令适合快速排查服务启动问题strace -f -tt -T -s 256 -y -e tracefile,network,process -o /tmp/quick_trace.log ./my_service跑完之后先看最后50行再看所有包含 -1的行基本上80%的启动问题都能定位到。剩下的20%再根据具体情况调整过滤条件深入排查。这个工具的学习曲线不算陡但真正用好需要积累。建议你在自己的开发环境里多练手拿一些开源软件做实验看看它们启动时都做了哪些系统调用。练得多了看到一行strace输出就能大致判断出程序在干什么、哪里可能有问题。这种直觉是看多少篇教程都换不来的。
阅读完成 · 觉得有帮助?
咨询建站