跟随子进程能看到两份缺失文件,但只有必需文件决定程序失败。

strace 解释选定的系统调用边界

strace 在选定 Linux 进程与内核之间的边界上观察系统调用和信号,不需要重新构建程序,就能展示尝试访问的路径、参数、返回值和错误。应用日志漏掉操作时,这些证据很有帮助;但它不会暴露每个用户态决定,也不会自动指出哪次失败调用导致最终结果。正确诊断仍需把边界行为与程序控制流程联系起来。 坏掉的样子 父进程可能启动子进程执行真正工作。只追踪父进程,会看到等待和退出,却漏掉子进程中的文件打开失败。使用 -f 跟随新建进程可以扩大范围,但不会自动追踪所有既有兄弟进程或请求涉及的外部服务。被选中的进程集合,本身就是证据范围的一部分,不能只看到主命令被追踪就认定全部参与者都在视野内。 错误返回不一定是根因。程序经常尝试可选文件、搜索多个位置,并从预期失败中恢复。找到第一条 ENOENT,或者退出前最后一次不成功调用,都不能替代理解应用控制流程。真正需要回答的是,这个返回值如何影响正在调查的操作。相同错误名在不同分支里,可能分别代表正常探测和不可继续的条件。 过滤与计时也会改变解释。把输出限制为文件打开,就有意排除了网络及其他活动。追踪还可能改变调度和延迟,尤其影响系统调用频繁的工作负载,没有通用固定降速倍数。过滤后的记录看起来正常,不能证明被排除操作成功,也不能证明没有追踪器时会出现完全相同的竞争时序。 快速验证 把脚本保存为 straceprobe.py,在安装 strace 的一次性 Linux 环境中运行 python3 straceprobe.py。实测使用 Linux 6.12.76、Python 3.11.2 和 strace 6.1。样本启动自己的无害程序,使用临时配置路径,并要求环境允许追踪新启动子进程: from pathlib import Path from tempfile import TemporaryDirectory import subprocess,sys child=r''' from pathlib import Path import sys try:Path(sys.argv[1]).read_bytes() except FileNotFoundError:pass try:content=Path(sys.argv[2]).read_bytes() except FileNotFoundError:sys.exit(7) assert content==b'configured' ''' parent='import subprocess,sys\nchild='+repr(child)+'\nsys.exit(subprocess.run([sys.executable,"-c",child,*sys.argv[1:]]).returncode)' with TemporaryDirectory(prefix='xbcatm-strace-') as directory: root=Path(directory);optional=root/'optional.conf';required=root/'required.conf' command=[sys.executable,'-c',parent,str(optional),str(required)] def trace(name,follow): output=root/(name+'.trace') args=['strace']+(['-f'] if follow else [])+['-tt','-T','-yy','-s','200','-e','trace=openat,close','-o',str(output),'--',*command] result=subprocess.run(args,capture_output=True,text=True,timeout=10) return result.returncode,output.read_text(),result.stderr code,unfollowed,error=trace('parent-only',False) assert code==7 and str(required) not in unfollowed,(code,error) code,followed,error=trace('with-children',True) assert code==7,(code,error) def calls(text,path):return [line for line in text.splitlines() if 'openat(' in line and '"'+str(path)+'"' in line] for path in [optional,required]: lines=calls(followed,path);assert len(lines)==1 and 'ENOENT' in lines[0],lines print('failing run:',lines[0]) print('without -f the parent trace misses the child opens; following the child exposes both missing paths') required.write_bytes(b'configured') code,recovered,error=trace('recovered',True);assert code==0,(code,error) assert 'ENOENT' in calls(recovered,optional)[0] assert ' = -1 ' not in calls(recovered,required)[0] print('successful run:',calls(recovered,optional)[0]) print('successful run:',calls(recovered,required)[0]) print('a successful application can contain ENOENT; the fixture distinguishes optional lookup from required configuration') summary=root/'summary' result=subprocess.run(['strace','-f','-c','-e','trace=openat','-o',str(summary),'--',*command],capture_output=True,text=True,timeout=10) assert result.returncode==0,result.stderr text=summary.read_text();assert 'openat' in text and 'total' in text print('syscall summary:\n'+text) 应用子进程先尝试可选配置文件,允许它不存在;随后读取第二份必需配置,缺失时以状态七退出。父进程返回子进程状态。这构成一份完整失败案例,原因可以直接从样本代码查明,不依赖工作区中根本不存在的占位应用。判断追踪结果时,已知控制流程提供了可以比较的依据。 ...

二月 14, 2028 · 1 分钟 · XB Field Notes