简单几步-教你在交叉架构下做测试

2021-09-14   出处: null program  作/译者:Chris Wellons/binger

我喜欢在不同的环境下测试我的软件,在奇怪的平台上,有多种不同的实现。每个环境都可以提前暴露bug。C语言对此尤其擅长,因为它可以选择许多不同的编译器,并且可以在任何环境下跑起来。比如我至少可以说出7种不同的C编译器在Debian上。写可移植软件的一个好处是可以在广泛的测试环境上使用,这也是我更倾向于使用标准化平台而不是指定平台的原因之一。


然而,我已经与架构的多样性做了很长时间的斗争了。我的工作和测试几乎全都是在x86上,以及位居第二的ARM上(树莓派等)。大端机很罕见。然而,我最近掌握了一个快速方便的访问许多不同架构的窍门,我甚至不用离开我的电脑:QEMU 用户仿真。Debian及其衍生品对此支持的非常好,而且几乎不需要安装和配置。


交叉编译例子

有许多选择,我最主要的交叉测试架构是PowerPC。它是32位的big endian,而我一般用64位little endian工作,这与我平时使用的完全不匹配。我用一个Debian提供的交叉编译起和qemu-user工具。binfmt的支持尤其丝滑,这也是为什么我经常用它。 

# apt install gcc-powerpc-linux-gnu qemu-user-binfmt

binfmt_misc是一个内核模块,可以让Linux知道如何识别任何二进制格式。例如,有一个Wine binfmt,所以Linux程序可以显示地执行windows .exe库。如果是QEMU用户模式,不同架构的二进制文件会被加载进在用户模式配置的QEMU虚拟机中。在用户模式种,没有游客操作系统,代替而来的是,虚拟机会将游客系统调到主机操作系统上。

第一个包给到我的是powerpc-linux-gnu-gcc。前缀是描述指令集和系统ABI的架构组。为了试验它,我有个小的测试程序,会对他的运行环境做检查。 



#include 

int main(void)
{
    char *w = "?";
    switch (sizeof(void *)) {
    case 1: w = "8";  break;
    case 2: w = "16"; break;
    case 4: w = "32"; break;
    case 8: w = "64"; break;
    }

    char *b = "?";
    switch (*(char *)(int []){1}) {
    case 0: b = "big";    break;
    case 1: b = "little"; break;
    }

    printf("%s-bit, %s endian\n", w, b);
}

当它在本地x86-64上跑时:



$ gcc test.c
$ ./a.out
64-bit, little endian

通过QEMU在PowerPC上跑时:



$ powerpc-linux-gnu-gcc -static test.c
$ ./a.out
32-bit, big endian

感谢binfmt,让我可以运行起来,尽管PowerPC的库是一个本地的库。只需要一些环境变了的配置,我就可以假装在PowerPC上开发-当然除了仿真性能的损耗。

可是,你可能发觉我偷偷加了一个参数:-static。截止到现在,我只是展示了如何使用静态库。没有可用的动态库的加载器去运行动态链接库。幸运的是,我们通过简单的两步来修复它。第一步是安装PowerPC的动态链接: 



# apt install libc6-powerpc-cross

第二步是告诉QEMU到哪里去找到它,不幸的是,它自己找不到。 



$ export QEMU_LD_PREFIX=/usr/powerpc-linux-gnu

现在我可以不使用-static了: 

$ powerpc-linux-gnu-gcc test.c
$ ./a.out
32-bit, big endian


一个可实操的例子:还记得binitools吗?我现在准备好在这个交叉测试平台去跑fuzz-generated测试套件了。

$ git clone https://github.com/skeeto/binitools
$ cd binitools/
$ make check CC=powerpc-linux-gnu-gcc
...
PASS: 668/668

或者,如果我要经常运行make: 



$ export CC=powerpc-linux-gnu-gcc
$ make -e check

重复调用:make’s -e flag 传递环境参数,所以我不用每次都在命令行传CC=…

当你为自己的程序建好了一个测试套件之后,考虑到在定制化环境下跑测试的难度。越早跑测试,他们就会跑的越多。我遇到过许多程序,他们有着极其复杂的测试构建,甚至无法在测试套件中启动杀毒。更不必说交叉架构测试了。

依赖?也许可以用Debian multiarch support来安装这些包,但是我还没能弄清楚,你可能需要使用交叉编译器自己构建依赖。


用Go来测试

不必拘泥于C(或C++)。我也成功的用它测试交叉架构下的Go的库和程序。这其实不怎么重要,因为写不可移植的Go比C更难。- 例如:dumb pointer tricks被打上了“unsafe”的标签。可是,Go简化了交叉编译,并且是静态编译的,所以它非常非常简单。一旦你安装了qemu-user-binfmt,它就完全的透明了: 

$ GOARCH=mips64 go test

这就是交叉平台测试所需要的一切了。如果因为一些原因binfmt不工作了(WSL)或者你不想安装它,只需要一个额外的步骤(包名example): 

$ GOARCH=mips64 go test -c
$ qemu-mips64-static example.test

-c选项构建一个测试库,但不去运行它,它允许你选择在哪里和怎样运行它。

如果你希望跟C跳转到相同的代码段,它甚至可以和cgo一起运行:



package main

// #include 
// uint16_t v = 0x1234;
// char *hi = (char *)&v + 0;
// char *lo = (char *)&v + 1;
import "C"
import "fmt"

func main() {
	fmt.Printf("%02x %02x\n", *C.hi, *C.lo)
}

Go在x86-64上运行: 


$ CGO_ENABLED=1 go run example.go
34 12

通过QEMU用户模式: 



$ export CGO_ENABLED=1
$ export GOARCH=mips64
$ export CC=mips64-linux-gnuabi64-gcc
$ export QEMU_LD_PREFIX=/usr/mips64-linux-gnuabi64
$ go run example.go
12 34

看到它可以运行的这么好,真的很欣慰呀!


另一个维度

尽管多样化,所有的这些架构仍然是运行在相同的操作系统上,Linux,所以他们只是在一个维度上的变化,对于大多数程序,主要目标在x86-64 Linux,PowerPC Linux同样可以这么做,而在x86-64 OpenBSD就是另一块内容了,尽管共享了同一个架构和ABI(SystemV)。跨操作系统的测试仍然需要花时间去安装,配置,保存这些额外的主机。


<测试窝原创译文:译者binger>



声明:本文为本站编辑转载,文章版权归原作者所有。文章内容为作者个人观点,本站只提供转载参考(依行业惯例严格标明出处和作译者),目的在于传递更多专业信息,普惠测试相关从业者,开源分享,推动行业交流和进步。 如涉及作品内容、版权和其它问题,请原作者及时与本站联系(QQ:1017718740),我们将第一时间进行处理。本站拥有对此声明的最终解释权!欢迎大家通过新浪微博(@测试窝)或微信公众号(测试窝)关注我们,与我们的编辑和其他窝友交流。
230° /2301 人阅读/0 条评论 发表评论

登录 后发表评论
最新文章