首页 Zig MIPS 软浮点问题
文章
取消

Zig MIPS 软浮点问题

问题描述

在开发 ChinaDNS-NG 2.0 的过程中,遇到一个 MIPS 软浮点目标的链接器错误,zig 版本是 0.10.1。

1
2
3
4
5
6
$ zig build clean-all && zig build -Dtarget=mips-linux-musl -Dcpu=mips32+soft_float --verbose
...
error: ld.lld: /root/chinadns-ng/zig-cache/o/8a51af63d061459702b0b7df54c14aa6/crti.o: floating point ABI '-mdouble-float' is incompatible with target floating point ABI '-msoft-float'
error: ld.lld: /root/chinadns-ng/zig-cache/o/90b58999351700493837db929be7e2bb/libc.a(/root/chinadns-ng/zig-cache/o/ab26fe2cec5f22f6ca91dc1fa8c110b6/restore.o): floating point ABI '-mdouble-float' is incompatible with target floating point ABI '-msoft-float'
error: ld.lld: /root/chinadns-ng/zig-cache/o/b52ce46b6c877a6adf627c116590f665/crtn.o: floating point ABI '-mdouble-float' is incompatible with target floating point ABI '-msoft-float'
error: chinadns-ng:mips-linux-musl:mips32+soft_float...

大概意思是:在链接时,不能混用 软浮点ABI、硬浮点ABI 的 object。

lld 提示的这几个 object,是 musl libc 里面的几个汇编源文件产生的。

相关 issue

https://github.com/ziglang/zig/issues/11829

zig 在编译 .s、.S 汇编源文件时,没有给汇编器传递 -msoft-float 标志,导致相关 obj 用的是硬浮点。

相关源码

https://github.com/ziglang/zig/blob/0.10.1/src/Compilation.zig#L4457

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
.assembly => {
    // The Clang assembler does not accept the list of CPU features like the
    // compiler frontend does. Therefore we must hard-code the -m flags for
    // all CPU features here.
    switch (target.cpu.arch) {
        .riscv32, .riscv64 => {
            // ...
        },
        else => {
            // TODO
        },
    }
    if (target_util.clangAssemblerSupportsMcpuArg(target)) {
        if (target.cpu.model.llvm_name) |llvm_name| {
            try argv.append(try std.fmt.allocPrint(arena, "-mcpu={s}", .{llvm_name}));
        }
    }
}

注释说的很明白了,C/C++ 编译器前端 允许通过 -Xclang -target-feature -Xclang +/-CPU特征 参数来传递 CPU 特征信息,但 汇编器 不接受这种参数,因此需要手动硬编码相关 -m 编译标志。

从源码能看出,zig 0.10.1 只处理了 riscv32/64 目标的 -m 标志(奇怪的是没看到 x86、arm 目标的特殊处理,可能是不需要?),mips 的没有处理。

查看上面那个 issue,可以看到作者在今年 4 月 19 号合并了修复此问题的一个 PR,让我们看看 PR 的修改:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
switch (target.cpu.arch) {
    .riscv32, .riscv64 => {
        // ...
    },
    // 这一段就是 PR 新增的内容,处理了软浮点
    .mips, .mipsel, .mips64, .mips64el => {
        if (target.cpu.model.llvm_name) |llvm_name| {
            try argv.append(try std.fmt.allocPrint(arena, "-march={s}", .{llvm_name}));
        }

        if (std.Target.mips.featureSetHas(target.cpu.features, .soft_float)) {
            try argv.append("-msoft-float");
        }
    },
    else => {
        // TODO
    },
}

解决方法

升级到最新的 zig 版本。

其他方法

但对我来说,升级 zig 不可行,因为 zig 0.10.1 之后的版本还不支持 async 异步功能,这是由于自托管编译器的开发导致的,因为要处理的东西很多,所以 async 功能暂时被搁置了。

自托管编译器在 0.10.0 版本首次亮相,0.10.1 是 0.10.0 的错误修复版本,本身没有功能性修改。在 0.10 中,默认启用自托管编译器,但允许使用 -fstage1 切换回 Bootstrap 编译器,这样就能愉快的使用 async 了。

但是 0.11 版本已经删除了 Bootstrap 编译器相关源码,因此不支持 -fstage1 参数了。


首先想到的是从源码构建 zig 0.10.1,并打上 PR 补丁,但因为 Bootstrap 编译器是 C++ 写的,还有 Clang、LLVM 等一堆库,还开了 LTO 优化,尝试了好几次都因为 OOM 编译失败了,即使是 32G 内存也没能顶住,遂放弃。


然后我就换了个思路,看看能否在不重新编译 zig 的前提下,让其支持 mips 软浮点目标的正确编译。

看了下 zig 源码,发现 zig 在编译 C、C++、汇编 等源文件时,是通过调用 zig 自己的 clang 子命令来实现的。

那问题来了,zig 在运行时是如何找到 zig 自己的可执行文件路径的呢?这里只考虑 Linux,答案很简单,那就是读取 /proc/self/exe 软链接文件。

如果可以通过某种手段,让 zig 在运行时读到的 zig_exe_path 是指向我自己的一个程序,然后在这个程序中针对 clang 子命令的参数做特殊处理,如果是 mips 软浮点目标,并且是汇编源文件,那么就追加 -msoft-float 等标志,然后再调用真正的 zig clang 命令,问题就解决了。


没错,这就是我这篇笔记的主要目的,下面说下具体的操作过程。

修补zig可执行文件

要让 zig 读取到的 zig_exe_path 不同,有两种办法:

  • 如果 zig 是动态链接的:可以通过 LD_PRELOAD 环境变量来钩住 zig 对 readlink C 库函数的调用,如果发现参数是 /proc/self/exe,则返回特定的路径,否则调用原始的 C 库函数。
  • 如果 zig 是静态链接的:我只能想到两种办法,一种方法是使用 ptrace,监视 readlink 相关系统调用,检测到 /proc/self/exe 参数时,返回特定的路径,就像 LD_PRELOAD 方式那样;另一种方法就是将 zig 可执行文件中的 "/proc/self/exe" 字符串文字替换为其他路径(为确保不会出问题,新字符串长度要与原字符串相同),在新路径上建立软链接,指向我们的一个包装程序。

因为我的 zig 可执行文件是静态链接到 musl 的,而又因为 ptrace 方式太复杂,所以我选择第二种方法。

1
2
3
4
5
6
7
8
9
10
cd /opt/zig-linux-x86_64-0.10.1

# 复制一份,避免修改原文件
cp -af zig zig_

# 新路径随意,确保字符串长度相同就行
sed -i 's@/proc/self/exe@/opt/zig123456@g' zig_

# 建立软链接,指向我们的包装程序 (这里我用 zig_.sh)
ln -snf $PWD/zig_.sh /opt/zig123456

编写包装脚本zig_.sh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#!/bin/bash

argv=("$@")

cmd="${argv[0]}"
file="${argv[1]}"

log() {
    # 注意,必须重定向到一个文件,不能打印到 stdout,防止破坏 zig 内部逻辑
    echo "$@" >>/root/chinadns-ng/log.txt
}

#log zig_ "${argv[@]}"

# 如果是目标是mips,并且编译的是汇编源文件,那么就追加参数
if [ "$MIPS_M_ARCH" ] && [ "$cmd" = clang ] && [[ "$file" == *.s || "$file" == *.S || "$file" == *.sx ]] && [[ "${argv[*]}" == *" -target mips"* ]]; then
    argv+=("-march=$MIPS_M_ARCH") # -march参数,指定微架构级别
    ((MIPS_SOFT_FP)) && argv+=("-msoft-float") # 如果是软浮点,则追加-msoft-float
    log zig_ "${argv[@]}" # 打印一下,验证替换结果是否正确
fi

# 调用真正的 zig 程序,注意是 zig_,修补过的二进制文件
exec zig_ "${argv[@]}"

创建其他软链接

1
2
3
4
5
6
7
8
cd /opt/zig-linux-x86_64-0.10.1

# 把 zig_ 添加到 PATH
ln -snf $PWD/zig_ /usr/local/bin/zig_

# 把 zig_.sh 添加到 PATH
# 这里我就使用 zig 这个名字了
ln -snf $PWD/zig_.sh /usr/local/bin/zig

验证一下

还是用开头的那个命令,测试下,是否正常编译、链接、运行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
$ zig build clean-all && MIPS_M_ARCH=mips32 MIPS_SOFT_FP=1 zig build -Dtarget=mips-linux-musl -Dcpu=mips32+soft_float --verbose

$ file zig-out/bin/chinadns-ng:mips-linux-musl:mips32+soft_float
zig-out/bin/chinadns-ng:mips-linux-musl:mips32+soft_float: ELF 32-bit MSB executable, MIPS, MIPS32 version 1 (SYSV), statically linked, stripped

$ readelf -A zig-out/bin/chinadns-ng:mips-linux-musl:mips32+soft_float

MIPS ABI Flags Version: 0

ISA: MIPS32
GPR size: 32
CPR1 size: 0
CPR2 size: 0
FP ABI: Soft float
ISA Extension: None
ASEs:
	None
FLAGS 1: 00000001
FLAGS 2: 00000000

Static GOT:
 Canonical gp value: 000597a0

 Reserved entries:
   Address     Access    Value
  000517b0 -32752(gp) 00000000
  000517b4 -32748(gp) 80000000

 Local entries:
   Address     Access    Value
  000517b8 -32744(gp) 00041574
  000517bc -32740(gp) 0002b184
  000517c0 -32736(gp) 00041548
  000517c4 -32732(gp) 0003179c

$ qemu-mips-static -cpu 4Kc zig-out/bin/chinadns-ng:mips-linux-musl:mips32+soft_float -V
ChinaDNS-NG 2023.10.28 | openssl-3.2.0 | target:mips-linux-musl | cpu:mips32+soft_float | mode:fast | <https://github.com/zfl9/chinadns-ng>
本文由作者按照 CC BY 4.0 进行授权

WLAN 笔记

-