我们如何在当前的 PC 上模拟一个大端序系统呢?当然你可以用 QEMU 来完全模拟一个其他架构的计算机,但显然这样的效率比较低。其实可以利用 QEMU-User 来实现 chroot 进入一个与宿主机完全不同架构的环境,这是依靠 QEMU-User 与 binfmt 实现的。

先来看看这个神奇的电脑吧。

 1lscpu
 2Architecture:        mips
 3CPU op-mode(s):      32-bit, 64-bit
 4Byte Order:          Big Endian
 5Address sizes:       45 bits physical, 48 bits virtual
 6CPU(s):              8
 7On-line CPU(s) list: 0-7
 8Thread(s) per core:  1
 9Core(s) per socket:  1
10Socket(s):           8
11NUMA node(s):        1
12Vendor ID:           GenuineIntel
13CPU family:          6
14Model:               158
15Model name:          Intel(R) Core(TM) i7-8750H CPU @ 2.20GHz
16Stepping:            10
17CPU MHz:             2208.000
18BogoMIPS:            4416.00
19Virtualization:      VT-x
20L1d cache:           32K
21L1i cache:           32K
22L2 cache:            256K
23L3 cache:            9216K
24NUMA node0 CPU(s):   0-7
25Flags:               fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ss syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon nopl xtopology tsc_reliable nonstop_tsc cpuid tsc_known_freq pni pclmulqdq vmx ssse3 fma cx16 pcid sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand hypervisor lahf_lm abm 3dnowprefetch cpuid_fault invpcid_single pti ssbd ibrs ibpb stibp tpr_shadow vnmi ept vpid fsgsbase tsc_adjust bmi1 avx2 smep bmi2 invpcid rdseed adx smap clflushopt xsaveopt xsavec xgetbv1 xsaves arat md_clear flush_l1d arch_capabilities

CPU 是 Intel 的 CPU,支持的指令集也显然都是 x86 那一套,但这是一块 MIPS 架构的 Intel CPU?

CS:APP 上有关于字节序的介绍,而且也推荐我们找一些大端的计算机来尝试。但现在我们能接触到的最多的大端电脑是 PowerPC Mac,无论是装一台虚拟机还是买一台真机都显然太不合适了。

我曾经看到有种方法可以在普通 PC 上调试其他架构程序的方法,是利用到了 QEMU-User,QEMU-User 可以只仿真用户态程序,这为我们的操作提供了不少便利。

但一般的程序都需要链接系统库,我又不想搭建一套交叉编译工具链,这显然把简单的问题搞复杂了,因此我们打算起一个新的 rootfs,chroot 进去来获得一套完整的 MIPS (这货是大端序) 的环境。

安装 QEMU-User

由于我们需要在 chroot 环境下使用,最好是安装静态编译版本的 QEMU-User,这两者在 Ubuntu 的源中都有。

1sudo apt install qemu-user-static

这一步会自动安装 binfmt 作为依赖,且帮我们自动配置好 binfmt,不需要我们手动配置。

非常简单的,我们可以通过 qemu-arch-static 来使用其他架构的程序,或此时直接运行其他架构的程序就可以了。

准备 chroot 环境

由于小众架构支持的系统其实不多,加上目前大端 CPU 早已不是主流,比较现代化的 Linux 中只有 Debian 10 还支持 MIPS 架构,因此我打算使用 debootstrap 工具来构建一个最小化的 Debian rootfs

首先安装工具

1sudo apt install debootstrap debian-keyring

使用 debootstrap 构建最小 rootfs

1sudo mkdir /mnt/mips_root
2sudo debootstrap --arch=mips buster /mnt/mips_root

最后将主机的 /dev 以及 proc 和 sysfs 挂载进 chroot 中

1sudo mount --bind /dev /mnt/mips_root/dev
2sudo mount -t proc proc /mnt/mios_root/proc
3sudo mount -t sysfs sysfs /mnt/mios_root/sys

注意,每次重启后,要进入 chroot 环境,都要记得先挂载以上 Filesystem。

配置环境与验证

接下来就可以 chroot 进新环境了,注意使用特权用户进行:

1sudo chroot /mnt/mips_root

接下来即可正常使用了,使用起来就像原生的 MIPS 环境一样。但在这之前,我们还需要进行一些系统初始配置。例如安装 locales 配置 i18n。

1apt install locales 

接下来就可以安装编译环境了。

1apt install build-essential

即可看到我们使用的是原生的 MIPS 编译器

 1$ gcc -v
 2Using built-in specs.
 3COLLECT_GCC=gcc
 4COLLECT_LTO_WRAPPER=/usr/lib/gcc/mips-linux-gnu/8/lto-wrapper
 5Target: mips-linux-gnu
 6Configured with: ../src/configure -v --with-pkgversion='Debian 8.3.0-6' --with-bugurl=file:///usr/share/doc/gcc-8/README.Bugs --enable-languages=c,ada,c++,go,d,fortran,objc,obj-c++ --prefix=/usr --with-gcc-major-version-only --program-suffix=-8 --program-prefix=mips-linux-gnu- --enable-shared --enable-linker-build-id --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --libdir=/usr/lib --enable-nls --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --with-default-libstdcxx-abi=new --enable-gnu-unique-object --disable-libitm --disable-libsanitizer --disable-libquadmath --disable-libquadmath-support --enable-plugin --enable-default-pie --with-system-zlib --disable-libphobos --enable-multiarch --disable-werror --enable-multilib --with-arch-32=mips32r2 --with-fp-32=xx --with-lxc1-sxc1=no --enable-targets=all --with-arch-64=mips64r2 --enable-checking=release --build=mips-linux-gnu --host=mips-linux-gnu --target=mips-linux-gnu
 7Thread model: posix
 8gcc version 8.3.0 (Debian 8.3.0-6) 
 9$ file /usr/bin/gcc
10/usr/bin/gcc: symbolic link to gcc-8
11$ file /usr/bin/gcc-8
12/usr/bin/gcc-8: symbolic link to mips-linux-gnu-gcc-8

我们对 CS:APP 中的示例程序编译,可以得到一个检验程序。

以上是在 x86_64 系统下的结果

 1calling show_twocomp
 2 39 30
 3 c7 cf
 4Calling simple_show_a
 5 21
 6 21 43
 7 21 43 65
 8Calling simple_show_b
 9 78
10 78 56
11 78 56 34
12Calling float_eg
13For x = 3490593
14 21 43 35 00
15 84 0c 55 4a
16For x = 3510593
17 41 91 35 00
18 04 45 56 4a
19Calling string_ueg
20 41 42 43 44 45 46
21Calling string_leg
22 61 62 63 64 65 66

以下则是 MIPS 系统下的结果

 1calling show_twocomp
 2 30 39
 3 cf c7
 4Calling simple_show_a
 5 87
 6 87 65
 7 87 65 43
 8Calling simple_show_b
 9 12
10 12 34
11 12 34 56
12Calling float_eg
13For x = 3490593
14 00 35 43 21
15 4a 55 0c 84
16For x = 3510593
17 00 35 91 41
18 4a 56 45 04
19Calling string_ueg
20 41 42 43 44 45 46
21Calling string_leg
22 61 62 63 64 65 66

可以看到数字在内存中的存储顺序是完全不一样的。