用 qemu-user 在arm linux機器上運行amd64/x86程序


1. qemu-user 是什么

本來, 對於 QEmu, 我只知道它是一個模擬器, 可以像 VirtualBox/VMWare 那樣跑一個操作系統, 只不過 QEmu 可以在 AMD64 上面跑針對 PowerPC, ARM 的操作系統, 當然, CPU 指令是解釋執行的, 相對來說比較慢.

但是前幾天折騰 CentOS/Fedora 上面的rpm構建工具mock時才發現, 原來 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-userqemu-user-static的區別, 現在只需要知道后者是靜態鏈接版本, 至於在什么場景下需要用到哪一種, 以后再來說).

1.1. 舉個例子

這里舉個例子說明一下應用場景:在樹莓派 2 (CPU是armv7) 上面跑針對 i386 編譯的linux程序.

我在命令行上工作是, 喜歡用一個叫做 fzf 的小程序 (這類程序我以前介紹過: 命令行上的narrowing(隨着輸入逐步減少備選項)工具 - 巴蠻子 - 博客園 ), 但它的早期有個問題: 我日常用的比較多的Linux是在樹莓派上的Raspbian 8, 但fzf自己提供的預編譯版本在Linux上只有amd64386兩個版本, 沒有針對arm的.這個工具又是用Go語言寫的, 我對這個語言不熟, 也不想去折騰安裝工具鏈在樹莓派上自行編譯.於是就可以試試這條路: 跑i386的版本

$ sudo apt install qemu-user

$ wget -c 'https://github.com/junegunn/fzf-bin/releases/download/0.16.3/fzf-0.16.3-linux_386.tgz'

$ tar xvf fzf-0.16.3-linux_386.tgz

$ 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-tmux, fkill等封裝腳本來使用, 腳本里面准備好備選數據后再調用fzf程序文件來讓用戶挑選, 我們要一一修改這些腳本就太麻煩了.

perl/python腳本就不需要這樣, 只要第一行是#!/usr/bin/perl或者#!/usr/bin/env python就可以了.我們能借用這個方法嗎?fzf-0.16.3-linux_386是個二進制的可執行程序, 我們沒辦法去修改所謂的"第一行";

對了, 有沒有注意到, 安裝wine之后, 命令行上直接輸入notepad.exe也是可以直接啟動"記事本"程序的, 並不一定需要wine notepad.exe才能啟動, 這是怎么實現的呢?

這就需要一種叫做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
mask fffffffffffefefffffffffffffffffffeffffff

$ xxd fzf-0.16.3-linux_386 | head -2
0000000: 7f45 4c46 0101 0100 0000 0000 0000 0000  .ELF............
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/: 這是檢查內核中目前注冊了哪些格式(registerstatus這兩個除外)
  3. cat /proc/sys/fs/binfmt_misc/qemu-i386: 這是在檢查我們所關心的與qemu-i386相關的配置, 從輸出中可以看到, 對於以7f454c4601010100000000000000000002000300開頭的文件, 可以調用/usr/bin/qemu-i386-static來執行(各字段的詳細解釋可以參見binfmt_misc - Wikipedia
  4. xxd fzf-0.16.3-linux_386 | head -2: 這是檢查一下我們所想運行的程序文件的開頭幾個字節是怎樣的, 從輸出可以看出, 它與上面所注冊的信息是匹配的
  5. ./fzf-0.16.3-linux_386 -h: 這是直接運行了這個i386程序, 可以看到它能夠正確打印出幫助信息

關於binfmt_misc的一些相關鏈接:

3. 補充說明:現實並沒有那么簡單/美好

雖然在上面我們成功運行了fzf-0.16.3-linux_386, 但如果你多實驗幾個程序, 就會發現失敗幾率是比較高的.因為大多數程序都會環境有很多依賴, 比如動態庫依賴、數據文件/配置文件、子進程調用、CPU擴展指令集、環境變量、設備文件等等, 它們的缺失或者錯誤都可能導致程序無法正常運行.很少有只需要單個程序文件就能跑起來的(上面運行的fzf-0.16.3-linux_386是個靜態鏈接版本, Go語言寫的工具一般都是靜態鏈接的).

對於動態庫依賴、數據文件/配置文件這類文件系統層面的問題, 雖然表面上可以想辦法把文件補齊, 比如Debian/Ubuntu考慮了多架構並存, 但其它Linux發行版並沒有考慮這個問題(有的考慮了x86_64與x86並存), 混合安裝也會給問題定位帶來諸多困難.所以在實際使用中, qemu-user大都是通過chroot在一個獨立的文件系統中運行的.關於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/ld.so.conf里面設定的路徑(那些路徑里存放的都是針對主機的動態庫, 在我這個例子里面, 就是針對


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM