用 strace 排查文件与网络故障:从系统调用看程序为什么失败

发布时间:2026/11/5 9:11:27
用 strace 排查文件与网络故障:从系统调用看程序为什么失败

用 strace 排查文件与网络故障:从系统调用看程序为什么失败

前几篇已经在 Ubuntu 20.04、Buildroot 和 QEMU ARM64 中运行交叉编译的程序。本篇介绍如何使用strace观察用户态程序与 Linux 内核之间的系统调用交互,并通过两个可控失败案例定位文件打开错误和 TCP 连接错误。

本文在 QEMU guest 中启用 Buildroot 的 ARM64strace,并用一个小型测试程序分析两类可控故障:打开不存在的文件,以及连接 guest loopback 上未监听的 TCP 端口。测试不会修改现有debug_demo服务,也不访问外部网络。文中记录了配置、工具版本、程序部署和这两类故障的实测结果;其他排查建议未逐项复现,仅供参考。

一、实验流程与环境

Ubuntu 20.04 x86_64 主机 Buildroot 2025.02.8 交叉编译测试程序 menuconfig 启用目标端 strace | SSH 上传程序:127.0.0.1:2222 v QEMU ARM64 guest strace 追踪 openat() 和 connect() | 输出系统调用、返回值和 errno v Ubuntu 主机上的文章记录 对照 ENOENT 与 ECONNREFUSED,定位失败原因
项目本文沿用的设置
Ubuntu 主机Ubuntu 20.04 x86_64
Buildroot2025.02.8,~/buildroot-2025.02.8
Buildroot 输出目录~/buildroot-output-arm64-demo
外部树~/buildroot-external-demo
目标系统QEMU ARM64、BusyBox init、可写 ext2 rootfs
guest SSHDropbear,Ubuntu127.0.0.1:2222转发到 guest TCP 22

本文的strace命令均在QEMU guest中执行。Ubuntu 主机只负责配置 Buildroot、交叉编译程序和通过 SSH 上传文件。Ubuntu 主机上的 x86_64strace不能替代 guest 中的 AArch64strace来追踪该目标程序。

二、在 Buildroot 中启用 strace

若 QEMU 正在使用 rootfs 镜像,先在 guest 中正常关机,并等待 QEMU 退出。以下命令在Ubuntu 主机执行:

exportDEMO_OUT="$HOME/buildroot-output-arm64-demo"exportDEMO_EXT="$HOME/buildroot-external-demo"cd"$HOME/buildroot-2025.02.8"||exit1makeO="$DEMO_OUT"BR2_EXTERNAL="$DEMO_EXT"menuconfig

在菜单中启用:

Target packages -> Debugging, profiling and benchmark -> strace

保存并退出。确认配置选项:

grep'^BR2_PACKAGE_STRACE=y'"$DEMO_OUT/.config"

应看到BR2_PACKAGE_STRACE=y。如果此配置由外部 defconfig 管理,请将选项同步到实际使用的 defconfig,避免之后重新生成配置时丢失。不要运行make clean、distclean或重置整个 Buildroot 配置。

本轮 Ubuntu 主机实际输出如下(用户名路径以$DEMO_OUT简写):

$ grep '^BR2_PACKAGE_STRACE=y' "$DEMO_OUT/.config" BR2_PACKAGE_STRACE=y

这确认strace已在当前 Buildroot 配置中启用。

确认 QEMU 已退出后增量构建:

makeO="$DEMO_OUT"BR2_EXTERNAL="$DEMO_EXT"-j2build_status=$?printf'Build exit code: %s\n'"$build_status"

只有退出码为0才继续。构建后确认目标文件已安装:

ls-l"$DEMO_OUT/target/usr/bin/strace"

若文件不存在,检查 Buildroot 配置和构建日志,不要直接假定strace已进入镜像。

本轮 Ubuntu 主机实际输出如下(用户名以$USER简写):

$ ls -l "$DEMO_OUT/target/usr/bin/strace" -rwxr-xr-x 1 $USER $USER 1767760 Nov 2 17:51 $DEMO_OUT/target/usr/bin/strace

这确认 ARM64strace已安装到 Buildroot 目标目录;仍需在启动后的 guest 中运行strace -V,验证实际镜像包含并可执行该工具。

三、启动 guest 并确认 strace

使用前文相同的 ARM64 内核和新生成的 rootfs 启动 QEMU。QEMU 的hostfwd需保留 SSH 转发,示例:

exportDEMO_OUT="$HOME/buildroot-output-arm64-demo"/usr/bin/qemu-system-aarch64\-Mvirt\-cpucortex-a53\-smp2\-m1G\-nographic\-kernel"$DEMO_OUT/images/Image"\-append"rootwait root=/dev/vda console=ttyAMA0"\-drivefile="$DEMO_OUT/images/rootfs.ext2",if=none,format=raw,id=hd0\-devicevirtio-blk-device,drive=hd0\-netdevuser,id=net0,hostfwd=tcp:127.0.0.1:2222-:22\-devicevirtio-net-device,netdev=net0

在 QEMU guest 控制台以 root 登录,确认目标架构和 strace 版本:

uname-mstrace-V

本轮 QEMU guest 实际输出如下:

# uname -m aarch64 # strace -V strace -- version 6.13 Copyright (c) 1991-2025 The strace developers <https://strace.io>. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. Optional features enabled: no-m32-mpers

这确认运行中的 guest 为 AArch64,且 strace 6.13 已安装并可执行。如果出现strace: not found,回到 Buildroot 检查配置和镜像;确认当前启动的是刚构建的 rootfs。

四、创建 ARM64 测试程序

在Ubuntu 主机设置目录,并创建一个包含两种失败路径的 C 程序:

exportDEMO_OUT="$HOME/buildroot-output-arm64-demo"exportCORE_WORK="$HOME/strace-demo"exportSSH_KEY="$HOME/.ssh/buildroot-qemu-dropbear-ed25519"mkdir-p"$CORE_WORK"cat>"$CORE_WORK/strace_demo.c"<<'EOF' #include <arpa/inet.h> #include <errno.h> #include <fcntl.h> #include <netinet/in.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/socket.h> #include <unistd.h> static int test_file_open(void) { const char *path = "/root/strace-demo-missing/config.ini"; int fd = open(path, O_RDONLY); if (fd < 0) { perror("open config"); return EXIT_FAILURE; } close(fd); puts("open config: success"); return EXIT_SUCCESS; } static int test_tcp_connect(void) { int fd = socket(AF_INET, SOCK_STREAM, 0); struct sockaddr_in address = {0}; if (fd < 0) { perror("socket"); return EXIT_FAILURE; } address.sin_family = AF_INET; address.sin_port = htons(65000); if (inet_pton(AF_INET, "127.0.0.1", &address.sin_addr) != 1) { perror("inet_pton"); close(fd); return EXIT_FAILURE; } if (connect(fd, (struct sockaddr *)&address, sizeof(address)) < 0) { perror("connect"); close(fd); return EXIT_FAILURE; } close(fd); puts("connect: success"); return EXIT_SUCCESS; } int main(int argc, char **argv) { if (argc != 2) { fprintf(stderr, "usage: %s file|connect\n", argv[0]); return EXIT_FAILURE; } if (strcmp(argv[1], "file") == 0) { return test_file_open(); } if (strcmp(argv[1], "connect") == 0) { return test_tcp_connect(); } fprintf(stderr, "unknown mode: %s\n", argv[1]); return EXIT_FAILURE; } EOF

只需确认/root/strace-demo-missing/config.ini这个目标文件不存在;父目录是否存在不影响此例,目标文件缺失时openat()都会返回ENOENT。网络示例连接 guest 自己的127.0.0.1:65000;运行前确认该端口没有服务监听,否则选择一个未监听的高端口并同步修改源码。

五、交叉编译并上传程序

在 Ubuntu 主机找到 Buildroot 交叉编译器并编译:

exportCROSS_GCC="$(find"$DEMO_OUT/host/bin"-maxdepth1\-name'aarch64-buildroot-linux-*-gcc'-print-quit)"if[-z"$CROSS_GCC"];thenprintf'Buildroot AArch64 compiler not found\n'>&2else"$CROSS_GCC"-Wall-Wextra-O0-g3\"$CORE_WORK/strace_demo.c"-o"$CORE_WORK/strace_demo"file"$CORE_WORK/strace_demo"fi

file应显示 ARM AArch64 ELF。随后从Ubuntu 主机通过 Dropbear 上传程序:

本轮 Ubuntu 主机的实际编译产物检查如下(用户名路径以$CORE_WORK简写):

$CORE_WORK/strace_demo: ELF 64-bit LSB shared object, ARM aarch64, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-aarch64.so.1, for GNU/Linux 6.12.0, with debug_info, not stripped

这确认测试程序已交叉编译为 ARM64 ELF,并保留调试信息。

ssh-i"$SSH_KEY"-p2222root@127.0.0.1\'cat > /root/strace_demo && chmod 755 /root/strace_demo'\<"$CORE_WORK/strace_demo"

在 Ubuntu 主机通过 SSH 确认 guest 文件存在:

ssh-i"$SSH_KEY"-p2222root@127.0.0.1\'ls -l /root/strace_demo && test -x /root/strace_demo && echo executable'

本轮 Ubuntu 主机通过 SSH 查询 guest 的实际输出如下:

-rwxr-xr-x 1 root root 95376 Nov 2 18:00 /root/strace_demo executable

这确认程序已上传到 QEMU guest 并具有执行权限。

六、案例一:文件打开失败

以下命令在QEMU guest执行。确认目标路径不存在:

test!-e/root/strace-demo-missing/config.ini&&echo'test path is absent'

使用strace追踪文件相关系统调用:

strace-f-tt-T-s128\-etrace=openat,newfstatat,access\/root/strace_demofile

本轮 QEMU guest 的实际输出如下:

# test ! -e /root/strace-demo-missing/config.ini && echo 'test path is absent' test path is absent # strace -f -tt -T -s 128 \ > -e trace=openat,newfstatat,access \ > /root/strace_demo file 18:01:29.843833 openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory) <0.000971> 18:01:29.863591 openat(AT_FDCWD, "/lib64/libc.so.6", O_RDONLY|O_CLOEXEC) = 3 <0.001122> 18:01:29.919290 openat(AT_FDCWD, "/root/strace-demo-missing/config.ini", O_RDONLY) = -1 ENOENT (No such file or directory) <0.001302> open config: No such file or directory 18:01:29.961120 +++ exited with 1 +++

这里/etc/ld.so.cache的ENOENT是动态加载器探测缓存未命中;随后/lib64/libc.so.6成功打开,程序继续运行。真正对应本例故障的是/root/strace-demo-missing/config.ini的openat()返回-1 ENOENT,表示目标路径中的文件或目录不存在。先检查路径拼写、父目录和挂载位置;如果目标文件调用返回EACCES,则检查目录遍历权限、文件权限和进程身份。不要把每一条ENOENT都当作程序最终失败原因,应结合后续调用和应用输出判断。

七、案例二:TCP 连接被拒绝

确保 guest 的 loopback TCP65000没有监听者,然后运行:

strace-f-tt-T-s128\-etrace=socket,connect\/root/strace_demo connect

本轮 QEMU guest 的实际输出如下:

# strace -f -tt -T -s 128 \ > -e trace=socket,connect \ > /root/strace_demo connect 18:03:27.151727 socket(AF_INET, SOCK_STREAM, IPPROTO_IP) = 3 <0.001728> 18:03:27.182939 connect(3, {sa_family=AF_INET, sin_port=htons(65000), sin_addr=inet_addr("127.0.0.1")}, 16) = -1 ECONNREFUSED (Connection refused) <0.007116> connect: Connection refused 18:03:27.253334 +++ exited with 1 +++

这里socket()成功返回文件描述符3,失败发生在connect();本轮 guest 的 loopback 端口65000未监听,因此返回ECONNREFUSED。检查服务是否启动、监听地址和端口是否正确。若看到ETIMEDOUT,更可能是路径、防火墙或对端无响应;若程序使用非阻塞 socket,EINPROGRESS可能是正常的连接建立中状态。

strace展示的是系统调用及其返回值,不会自动告诉你整个网络路径为何不可达。DNS 解析可能在 libc 层完成,实际观察到的系统调用取决于程序使用的 API;本例使用 IPv4 数字地址,避免将 DNS 现象混入 TCP connect 案例。

八、如何缩小追踪范围

本文使用明确的系统调用列表,避免输出与问题无关的内容:

选项作用
-f跟踪子进程;对会 fork 的程序有用
-tt在每行前显示时间戳
-T显示系统调用耗时
-s 128限制字符串打印长度,避免无限增长的输出
-e trace=...只追踪指定系统调用

遇到复杂程序时,可以先用-e trace=openat,connect聚焦,再按问题增加stat、read、write、socket等调用。-c可汇总调用次数和耗时,但不适合观察单次失败调用的参数。

追踪命令的退出码通常反映被追踪程序的退出状态;上面的两个示例在故障分支返回非零是预期现象,不表示strace自身失败。可用printf 'exit=%s\n' "$?"查看上一条命令状态,但必须紧接着执行。

九、常见问题与安全边界

现象优先检查
strace: not foundBuildroot 是否启用了BR2_PACKAGE_STRACE,当前 guest 是否启动新生成的镜像
REMOTE HOST IDENTIFICATION HAS CHANGED不要关闭校验或盲目删除记录;先从 QEMU guest 控制台核对指纹。确认与 SSH 提示一致且 guest 镜像/host key 确实已更换后,才在 Ubuntu 主机执行ssh-keygen -R '[127.0.0.1]:2222',然后重连并再次核对新指纹
ptrace: Operation not permitted确认在 guest 内追踪自己启动的子进程,并检查目标内核是否启用 ptrace 支持及安全策略
文件错误不是ENOENT检查目标路径是否存在、父目录权限和程序运行用户;错误码可能为EACCES等
TCP 连接没有ECONNREFUSED检查 guest 的127.0.0.1:65000是否已有监听程序;选择未监听端口后再试
输出中没有期望的系统调用相关调用可能被过滤;检查-e trace=...列表,或临时移除过滤项

strace输出可能暴露文件路径、网络地址、进程参数及读写缓冲区。本文未追踪read/write的缓冲区内容,也不应直接对包含口令、令牌或个人数据的生产程序完整追踪。分享日志前应检查并脱敏。

本文已在 QEMU ARM64 guest 中实测文件打开失败和未监听 TCP 端口两种用户态故障;故障排查表中的其他情况未逐项复现,不代表本次实测结果。本文不覆盖内核态追踪、驱动故障、真实网卡状态或生产系统的性能结论。

十、小结

strace可以把应用层失败映射到具体系统调用、参数、返回值和 errno。文件案例中的openat() = -1 ENOENT指向路径问题;网络案例中的connect() = -1 ECONNREFUSED指向目标端口没有接受连接。结合程序上下文、guest 配置和 errno,才能从“操作失败”进一步定位到可验证的原因。