我们如何在当前的 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
可以看到数字在内存中的存储顺序是完全不一样的。
评论