修复 Zig TSan 找不到 linux/scc.h
新版 Linux headers 上启用 Zig ThreadSanitizer,构建在项目代码编译前就失败:
error: sub-compilation of libtsan failed
lib/libtsan/sanitizer_common/sanitizer_platform_limits_posix.cpp:160:10:
error: 'linux/scc.h' file not found
#include <linux/scc.h>
^~~~~~~~~~~~~
不是项目代码的并发问题。Zig 的 -fsanitize-thread 首次构建时会从安装目录里的源码编译一份 ThreadSanitizer runtime,以 Zig 0.16.0 为例在 <zig-lib-dir>/libtsan/sanitizer_common/sanitizer_platform_limits_posix.cpp。这份 vendored libtsan 里有三行死代码:
#include <linux/scc.h> unsigned struct_scc_modem_sz = sizeof(struct scc_modem); unsigned struct_scc_stat_sz = sizeof(struct scc_stat);
Linux 内核删掉了废弃的 SCC amateur-radio 接口,linux/scc.h 也从 UAPI headers 里消失,滚动发行版先撞上。LLVM compiler-rt 已通过 #194116 移除这三处引用,但 Zig 用的是自己的快照,要等发布包同步才算修好。
补丁
仓库里建 .mise/tasks/fix/zig-tsan:
#!/usr/bin/env bash
#MISE description="Patch Zig libtsan for Linux headers without linux/scc.h"
set -euo pipefail
if [ "$(uname -s)" != Linux ]; then
echo "Zig libtsan scc.h patch is only needed on Linux"
exit 0
fi
zig_exe=$(readlink -f "$(command -v zig)")
zig_lib_dir=${ZIG_LIB_DIR:-"$(dirname "$zig_exe")/lib"}
source_file="$zig_lib_dir/libtsan/sanitizer_common/sanitizer_platform_limits_posix.cpp"
if [ ! -f "$source_file" ]; then
echo "error: Zig libtsan source not found: $source_file" >&2
exit 1
fi
include='#include <linux/scc.h>'
modem=' unsigned struct_scc_modem_sz = sizeof(struct scc_modem);'
stats=' unsigned struct_scc_stat_sz = sizeof(struct scc_stat);'
remaining=0
for line in "$include" "$modem" "$stats"; do
count=$(grep -Fxc "$line" "$source_file" || true)
if [ "$count" -gt 1 ]; then
echo "error: unexpected duplicate in Zig libtsan: $line" >&2
exit 1
fi
remaining=$((remaining + count))
done
if [ "$remaining" -eq 0 ]; then
echo "Zig $(zig version) libtsan is already compatible with current Linux headers"
exit 0
fi
if [ "$remaining" -ne 3 ]; then
echo "error: Zig libtsan is only partially compatible; refusing to modify it" >&2
exit 1
fi
if [ ! -w "$source_file" ]; then
echo "error: Zig libtsan source is not writable: $source_file" >&2
exit 1
fi
sed -i \
-e "\\|^$include\$|d" \
-e "\\|^$modem\$|d" \
-e "\\|^$stats\$|d" \
"$source_file"
for line in "$include" "$modem" "$stats"; do
if grep -Fxq "$line" "$source_file"; then
echo "error: failed to remove obsolete Zig libtsan reference: $line" >&2
exit 1
fi
done
echo "Patched Zig $(zig version) libtsan: $source_file"
chmod +x .mise/tasks/fix/zig-tsan mise run fix:zig-tsan
任务靠 command -v zig 和 readlink 定位 mise 当前选中的 Zig,不硬编码版本目录。三行目标代码必须各出现一次才动手:都在就补,都不在就输出 already compatible 直接退出,部分存在或重复则拒绝改。所以可以重复执行,Zig 同步上游修复后也不用删。
验证
清掉独立的 TSan 构建缓存,让 runtime 用修补后的源码重新编译:
rm -rf .zig-cache/tsan mise run test:tsan
没有封装测试任务就重跑原来的 zig build,保留 -fsanitize-thread 或对应 build option。重新安装或切换 Zig 版本后 mise 会建新的工具链目录,需要再跑一次补丁。
