用 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 |
| Buildroot | 2025.02.8,~/buildroot-2025.02.8 |
| Buildroot 输出目录 | ~/buildroot-output-arm64-demo |
| 外部树 | ~/buildroot-external-demo |
| 目标系统 | QEMU ARM64、BusyBox init、可写 ext2 rootfs |
| guest SSH | Dropbear,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"fifile应显示 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 found | Buildroot 是否启用了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,才能从“操作失败”进一步定位到可验证的原因。