Gdev 至 Rust 移植工程(六)
从 C 到 Rust:GDEV GPU 运行时移植纪实
在 QEMU 环境进行测试
背景
前四个阶段的翻译工作已全部完成,196 个测试在无硬件环境下全部通过。接下来的目标是将编译出的动态库部署到 QEMU 模拟的 CentOS 7 虚拟机中,用真实的 C 测试程序验证能否正常调用。
测试环境由跳板机上的 CentOS 7 QEMU 模拟器提供,其中已包含原版的 libgdev.so 和 libusdaa.so,以及配套的 C 测试样例(如 launch.c)。
第一步:让 Rust 代码产出 C 可链接的 .so
为了让 C 测试程序能调用 Rust 写的 gdev 库,我们在现有代码上做了三个最小化改动:
- 启用
cdylib输出
# layer2/Cargo.toml + layer4/Cargo.toml
[lib]
crate-type = ["cdylib", "rlib"]
Rust 默认只产出 .rlib(Rust 专用格式),"cdylib" 则指示 Cargo 额外生成一个 C 兼容的动态库(.so),遵循系统 ABI,使 gcc/ld 可以直接链接。
- 暴露 C ABI 符号
新建layer2/src/ffi.rs(约 200 行),为 layer2 的 33 个内部函数加上#[no_mangle] pub unsafe extern "C" fn。例如:
#[no_mangle]
pub unsafe extern "C" fn gopen(minor: u32) -> u64 {
// 内部实现返回 Result<u64,i32>,在 FFI 边界转为 C 兼容的 u64
}
这样 C 链接器就能看到 gopen、gclose、gmalloc、glaunch 等符号,并且使用 C 的调用约定。
- 模块声明
在layer2/src/lib.rs中加入pub mod ffi;,将 FFI 入口纳入编译。
构建结果:cargo build --release 0 errors,产出了两个 .so 文件——libgdev_layer2.so 和 libgdev_layer4.so,分别对标原版的 libgdev.so 和 libusdaa.so。
第一次尝试:替换 .so 并编译测试
我们将编译好的两个 .so 传到 QEMU 虚拟机,替换掉 /usr/local/gdev/lib64 下的原版库,然后执行 make 编译 C 测试程序:
[root@qemu-ai-ep ~]# cd test/sdaa/launch2
[root@qemu-ai-ep launch2]# make
g++ -o user_test -std=c++11 -L /usr/local/gdev/lib64 -I /usr/local/gdev/include main.c launch.c parse_args.c -lusdaa -lgdev
launch.c: In function ‘int sdaa_test_launch(long unsigned int, int)’:
launch.c:97:57: warning: deprecated conversion from string constant to ‘char*’ [-Wwrite-strings]
__sdaaRegisterFunction( sdaaFatCubinHandle,"add_test");
^
/tmp/cce9af1r.o: In function `sdaa_test_launch(unsigned long, int)':
launch.c:(.text+0x2ce): undefined reference to `__sdaaRegisterFunction'
launch.c:(.text+0x2f9): undefined reference to `sdaaGetErrorString'
launch.c:(.text+0x357): undefined reference to `sdaaGetErrorString'
launch.c:(.text+0x3b5): undefined reference to `sdaaGetErrorString'
launch.c:(.text+0x419): undefined reference to `sdaaGetErrorString'
launch.c:(.text+0x47d): undefined reference to `sdaaGetErrorString'
/tmp/cce9af1r.o:launch.c:(.text+0x639): more undefined references to `sdaaGetErrorString' follow
/usr/local/gdev/lib64/libusdaa.so: undefined reference to `fstat64@GLIBC_2.33'
/usr/local/gdev/lib64/libusdaa.so: undefined reference to `pthread_key_delete@GLIBC_2.34'
/usr/local/gdev/lib64/libusdaa.so: undefined reference to `pthread_setspecific@GLIBC_2.34'
/usr/local/gdev/lib64/libusdaa.so: undefined reference to `pthread_key_create@GLIBC_2.34'
/usr/local/gdev/lib64/libusdaa.so: undefined reference to `stat64@GLIBC_2.33'
collect2: error: ld returned 1 exit status
make: *** [all] Error 1
结果是一连串的链接错误:
undefined reference to '__sdaaRegisterFunction'undefined reference to 'sdaaGetErrorString'undefined reference to 'fstat64@GLIBC_2.33'undefined reference to 'stat64@GLIBC_2.33'undefined reference to 'pthread_key_create@GLIBC_2.34'undefined reference to 'pthread_key_delete@GLIBC_2.34'undefined reference to 'pthread_setspecific@GLIBC_2.34'
问题分两类:缺失的函数符号(__sdaaRegisterFunction、sdaaGetErrorString)和 glibc 版本不匹配。
修复一:补齐缺失的 API
我们很快在 layer4/src/entry.rs 中补上了两个函数:
// 测试代码中实际调用的是双下划线版本
#[no_mangle]
pub unsafe extern "C" fn __sdaaRegisterFunction(
handle: *mut c_void,
name: *const c_char,
) {
sdaaRegisterFunction(handle, name);
}
// 错误码转字符串,供测试宏使用
#[no_mangle]
pub unsafe extern "C" fn sdaaGetErrorString(error: SdaaError) -> *const c_char {
match error {
0 => b"sdaaSuccess0".as_ptr() as *const c_char,
1 => b"sdaaErrorInvalidValue0".as_ptr() as *const c_char,
// ...
_ => b"sdaaErrorUnknown0".as_ptr() as *const c_char,
}
}
修复二:glibc 符号版本
真正的麻烦在后面。QEMU 环境是 CentOS 7,glibc 版本为 2.17(发布于 2012 年),而我们的开发机是 glibc 2.33+。Rust 标准库在编译时,宿主机 pthread.h 声明的 pthread_key_create 被标记为 @@GLIBC_2.34,这个版本标签被刻进了 Rust std 的 rlib 中,最终传染给我们的 .so。
因此,即使补齐了所有函数,链接器依然会报告:
undefined reference to `fstat64@GLIBC_2.33'
等等。这些符号在 CentOS 7 的 libc.so.6 中实际存在,只是版本标签是 @GLIBC_2.2.5。
尝试:用 .symver 创建兼容库
我们想到了一个折中方案——编写一个微型共享库 libglibc_compat.so,利用汇编器指令 .symver 为高版本符号创建别名,指向系统提供的低版本实现。
┌────────────────┐ 需要 fstat64@GLIBC_2.33
│ libgdev_layer4 │ ──────────────────────┐
└────────────────┘ ▼
┌──────────────────────┐
│ libglibc_compat.so │
│ .symver fstat64, │
│ fstat64@GLIBC_2.33 │
│ 内部调用 → │
│ fstat64@GLIBC_2.2.5 │
└──────────────────────┘
│
▼
┌──────────────────────┐
│ 系统 libc.so.6 │
│ (glibc 2.17) │
└──────────────────────┘
这一招在链接阶段确实生效了——编译通过,不再报符号未定义。
新的关卡:ELF 头部版本锁
然而,编译成功后的程序运行时直接报错:
[root@qemu-ai-ep launch2]# make
g++ -o user_test -std=c++11 -L /usr/local/gdev/lib64 -I /usr/local/gdev/include main.c launch.c parse_args.c -lgdev_layer4 -lglibc_compat
launch.c: In function ‘int sdaa_test_launch(long unsigned int, int)’:
launch.c:97:57: warning: deprecated conversion from string constant to ‘char*’ [-Wwrite-strings]
__sdaaRegisterFunction( sdaaFatCubinHandle,"add_test");
^
#g++ -o user_test -std=c++11 -L /usr/local/gdev/lib64 -I /usr/local/gdev/include add.c -lgdev_layer4 -lglibc_compat
[root@qemu-ai-ep launch2]#
[root@qemu-ai-ep launch2]#
[root@qemu-ai-ep launch2]# ./user_test
./user_test: /lib64/libc.so.6: version `GLIBC_2.18' not found (required by /usr/local/gdev/lib64/libgdev_layer4.so)
./user_test: /lib64/libc.so.6: version `GLIBC_2.25' not found (required by /usr/local/gdev/lib64/libgdev_layer4.so)
./user_test: /lib64/libc.so.6: version `GLIBC_2.28' not found (required by /usr/local/gdev/lib64/libgdev_layer4.so)
./user_test: /lib64/libc.so.6: version `GLIBC_2.30' not found (required by /usr/local/gdev/lib64/libgdev_layer4.so)
./user_test: /lib64/libc.so.6: version `GLIBC_2.33' not found (required by /usr/local/gdev/lib64/libgdev_layer4.so)
./user_test: /lib64/libc.so.6: version `GLIBC_2.34' not found (required by /usr/local/gdev/lib64/libgdev_layer4.so)
这次不是某个符号找不到,而是动态链接器 ld.so 在加载 .so 时检查其 ELF 头部中的 DT_VERNEED 段,发现文件“要求 libc.so.6 >= GLIBC_2.34”。无论有没有兼容库,版本检查不通过就直接拒绝加载。
.symver 只能给函数加上兼容标签,但无法抹去 ELF 头部中记录的最低版本需求。
目前后续解决方案:在测试环境上编译gdev
问题的根源在于:Rust 编译时链接的宿主 glibc 将其自身的版本需求写入了产出物的 .dynamic 段。要真正产出与 glibc 2.17 兼容的二进制,编译器必须直接链接 glibc 2.17 的库。
因此,最直接的方案是:在 QEMU 虚拟机(CentOS 7)上安装 Rust 工具链,将源代码传上去,本地编译。
这样产出的 libgdev_layer4.so 会自动链接到系统的 glibc 2.17,无需任何兼容层,也无需 LD_PRELOAD,零额外开销,完全符合原生部署的要求。目前qemu上与外部centos网络不联通不能安装rust环境,可能还有会有centos版本过低导致安装的rustc版本低的情况,还要继续进一步调整好环境进行测试。
更多推荐

所有评论(0)