⽤qemu-user在armlinux机器上运⾏amd64x86程序
1. qemu-user 是什么
本来, 对于 QEmu, 我只知道它是⼀个模拟器, 可以像 VirtualBox/VMWare 那样跑⼀个操作系统, 只不过 QEmu 可以在 AMD64 上⾯跑针对PowerPC, ARM 的操作系统, 当然, CPU 指令是解释执⾏的, 相对来说⽐较慢.
但是前⼏天折腾 CentOS/Fedora 上⾯的rpm构建⼯具时才发现, 原来 QEmu 还有⼀种运⾏⽅式, 那就是跟wine的运⾏⽅式相同: 直接运⾏程序⽂件.在这种模式下, 这个针对 PowerPC或者ARM编译的程序, 就⽐较像⼀个本地程序, 它跟本机的Linux内核打交道, 进⾏系统调⽤, 访问本地⽂件(其实是通过qemu进⾏)和本地设备.
在 QEmu 的术语中, 前⾯那种运⾏整个操作系统的⽅式, 称为"full system emulation", 在 Ubuntu/CentoS 由软件包qemu-system-xxx (⽐
如qemu-system-ppc, qemu-system-aarch64, qemu-system-arm)提供功能;后⾯这种运⾏单个程序⽂件的⽅式, 称为"user mode emulation",由软件包qemu-user或者qemu-user-static提供功能(注意没有细分为qemu-user-ppc, qemu-user-arm, 不过这也许只是因为这些模拟器⽂件都不⼤, 就揉到了⼀个包⾥⾯
.⾄于qemu-user和qemu-user-static的区别, 现在只需要知道后者是静态链接版本, ⾄于在什么场景下需要⽤到哪⼀种, 以后再来说).
1.1. 举个例⼦
这⾥举个例⼦说明⼀下应⽤场景:在树莓派 2 (CPU是armv7) 上⾯跑针对 i386 编译的linux程序.
我在命令⾏上⼯作是, 喜欢⽤⼀个叫做的⼩程序 (这类程序我以前介绍过: ), 但它的早期有个问题: 我⽇常⽤的⽐较多的Linux是在树莓派上的Raspbian 8, 但在Linux上只有amd64和386两个版本, 没有针对arm的.这个⼯具⼜是⽤Go语⾔写的, 我对这个语⾔不熟, 也不想去折腾安装⼯具链在树莓派上⾃⾏编译.于是就可以试试这条路: 跑i386的版本
$ sudo apt install qemu-user
$ wget -c 'github/junegunn/fzf-bin/releases/download/0.16.3/fzf-0.16.'
$ tar xvf fzf-0.16.
$ file fzf-0.16-3-linux_386
fzf-0.16.3-linux_386: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), statically linked, not stripped
$ qemu-i386 fzf-0.16.3-linux_386 -h
usage: fzf [options]
Search
-x, --extended        Extended-search mode
(enabled by default; +x or --no-extended to disable)
[...]
$ history | qemu-i386 fzf-0.16.3-linux_386
上⾯倒数第三个命令是检查程序⽂件fzf-0.16.3-linux_386的类型, 从结果看它的确是针对386的ELF⽂件, 并且是静态链接的;倒数第⼆个命令qemu-i386 fzf-0.16.3-linux_386 -h是试着运⾏⼀下, 程序成功地跑起来, 打印除了帮助信息.最后⼀个命令history | qemu-i386 fzf-0.16.3-linux_386是真正在使⽤fzf这个程序的功能.
2. ⽤binfmt_misc机制来让启动运⾏更⽅便
上⾯虽然把这个程序运⾏起来了, 但命令⾏上需要将qemu-i386放在前⾯, 也就是说实际启动的qemu-i386这个程序, 它再把fzf跑起来.这样并不太⽅便, 尤其fzf这个程序⼀般都不是直接使⽤, ⽽是通过, 等封装脚本来使⽤, 脚本⾥⾯准备好备选数据后再调⽤fzf程序⽂件来让⽤户挑选,我们要⼀⼀修改这些脚本就太⿇烦了.
perl/python脚本就不需要这样, 只要第⼀⾏是#!/usr/bin/perl或者#!/usr/bin/env python就可以了.我们能借⽤这个⽅法吗?fzf-0.16.3-
linux_386是个⼆进制的可执⾏程序, 我们没办法去修改所谓的"第⼀⾏";
对了, 有没有注意到, 安装wine之后, 命令⾏上直接输⼊也是可以直接启动"记事本"程序的, 并不⼀定需要才能启动, 这是怎么实现的呢?
这就需要⼀种叫做binfmt_misc的机制.
binfmt_misc是Linux内核说提供的⼀种扩展机制, 使得更多类型的⽂件得以成为"可执⾏"⽂件.Linux内核本⾝⽀持ELF、a.out、脚本(也就是上⾯所说的第⼀⾏#!指定解释器的⽅式)这集中"可执⾏⽂件".但它还提供了⼀个称为binfmt_misc的内核模块, 通过这个模块可以动态注册⼀些"可执⾏⽂件
格式",注册之后我们就可以直接"执⾏"这个程序⽂件了.
其实上⾯⽤apt install qemu-user-static安装这个包时, 它的postinstall脚本已经在binfmt_misc中注册了相应的配置, 我们可以通过下⾯的⽅式检查⼀下:
$ lsmod | grep binfmt
binfmt_misc            6306  1
$ ls /proc/sys/fs/binfmt_misc/
python2.7  qemu-cris        qemu-mips    qemu-ppc64abi32  qemu-sh4eb        qemu-x86_64
python3.4  qemu-i386        qemu-mipsel  qemu-ppc64le    qemu-sparc        register
qemu-alpha  qemu-m68k        qemu-ppc    qemu-s390x      qemu-sparc32plus  status
qemu-armeb  qemu-microblaze  qemu-ppc64  qemu-sh4        qemu-sparc64
$ cat /proc/sys/fs/binfmt_misc/qemu-i386
enabled
interpreter /usr/bin/qemu-i386-static
flags: OC
offset 0
magic 7f454c4601010100000000000000000002000300
x86架构和arm架构区别
mask fffffffffffefefffffffffffffffffffeffffff
$ xxd fzf-0.16.3-linux_386 | head -2
0000000: 7f45 4c46 0101 0100 0000 0000 0000 0000  .
0000010: 0200 0300 0100 0000 d090 0908 3400 0000  ............4...
$ ./fzf-0.16.3-linux_386 -h
usage: fzf [options]
Search
-
x, --extended        Extended-search mode
(enabled by default; +x or --no-extended to disable)
[....]
解释⼀下上⾯⼏条命令:
1. lsmod | grep binfmt: 这是检查内核模块binfmt_misc是否已经加载, 有内容输出说明已经加载了.如果没有加载, 则可以⽤modprobe
binfmt_misc来加载它(在当前的很多Linux发⾏版中, ⼀般可以通过sudo systemctl restart systemd-binfmt来启动/重启它, 修改了注册配置也可以通过这条命令来重新加载)
2. ls /proc/sys/fs/binfmt_misc/: 这是检查内核中⽬前注册了哪些格式(register和status这两个除外)
3. cat /proc/sys/fs/binfmt_misc/qemu-i386: 这是在检查我们所关⼼的与qemu-i386相关的配置, 从输出中可以看到, 对于
以7f454c4601010100000000000000000002000300开头的⽂件, 可以调⽤/usr/bin/qemu-i386-static来执⾏(各字段的详细解释可以参见)
4. xxd fzf-0.16.3-linux_386 | head -2: 这是检查⼀下我们所想运⾏的程序⽂件的开头⼏个字节是怎样的, 从输出可以看出, 它与上⾯所注册
的信息是匹配的
5. ./fzf-0.1
6.3-linux_386 -h: 这是直接运⾏了这个i386程序, 可以看到它能够正确打印出帮助信息
关于binfmt_misc的⼀些相关链接:
: 如何直接执⾏LuaJIT字节码
: 因为Go语⾔不允许第⼀⾏⽤#!/usr/bin/env go, 所以需要另外⼀个⽅法来将*.go作为脚本运⾏
3. 补充说明:现实并没有那么简单/美好
虽然在上⾯我们成功运⾏了fzf-0.16.3-linux_386, 但如果你多实验⼏个程序, 就会发现失败⼏率是⽐较⾼的.因为⼤多数程序都会环境有很多依赖, ⽐如动态库依赖、数据⽂件/配置⽂件、⼦进程调⽤、CPU扩展指令集、环境变量、设备⽂件等等, 它们的缺失或者错误都可能导致程序⽆法正常运⾏.很少
有只需要单个程序⽂件就能跑起来的(上⾯运⾏的fzf-0.16.3-linux_386是个静态链接版本, Go语⾔写的⼯具⼀般都是静态链接的).
对于动态库依赖、数据⽂件/配置⽂件这类⽂件系统层⾯的问题, 虽然表⾯上可以想办法把⽂件补齐, ⽐如Debian/Ubuntu考虑了多架构并存,但其它Linux发⾏版并没有考虑这个问题(有的考虑了x86_64与x86并存), 混合安装也会给问题定位带来诸多困难.所以在实际使⽤中, qemu-user⼤都是通过在⼀个独⽴的⽂件系统中运⾏的.关于qemu与chroot配合的话题下次再展开吧
Debian/Ubuntu的动态库都安装在.../lib/<target>-<vendor>-<abi>⽬录下, ⽐如同样⼀个动态库libncurses.so.5.9, 通
过libncurses5:armhf包提供的动态库安装在/lib/arm-linux-gnueabihf/libncurses.so.5.9,通过libncurses5:i386包提供的动态库安装在/lib/i386-linux-gnu/libncurses.so.5.9
(通过dpkg --add-architecture armhf && apt-get update && apt-get install libc6:armhf这种⽅式可以并⾏安装多种架构的包)
qemu-user有⼀个-L path选项, 可以⽤来变更动态库查路径(/set the elf interpreter prefix to 'path'/): 将程序所需要的动态库都放置到/home/bamanzi/i386-libs/lib⽬录下, 然后⽤qemu-user -L /home/bamanzi/i386-libs ./prog来启动程序, 就会优先
到/home/bamanzi/i386-libs/lib查prog所需要的动态库, ⽽不是主机⾥⾯/etc/f⾥⾯设定的路径(那些路径⾥存放的都是
针对主机的动态库, 在我这个例⼦⾥⾯, 就是针对

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。