LoginSignup
29
24

More than 1 year has passed since last update.

Raspberry Pi のセルフビルド環境を QEMU で作る

Last updated at Posted at 2017-11-12

動機

ハードウェアエンコーダ対応の FFmpeg をビルドしようとすると、実機ビルドはやってられない。

流れ

典型的な QEMU in chroot です。

  1. ファイルシステムを準備して
  2. QEMU を入れて
  3. chroot

用意するもの

  • Debian か Ubuntu 環境、本稿では Debian Stretch amd64

chroot 用ファイルシステムの準備

イメージのダウンロード

https://www.raspberrypi.org/downloads/raspbian/ から Raspbian Stretch Lite の zip を入手する。これを unzip すると 2017-09-07-raspbian-stretch-lite.img が出てくる。

$ file 2017-09-07-raspbian-stretch-lite.img
2017-09-07-raspbian-stretch-lite.img: DOS/MBR boot sector;
partition 1 : ID=0xc, start-CHS (0x0,130,3), end-CHS (0x5,214,7), startsector 8192, 85622 sectors;
partition 2 : ID=0x83, start-CHS (0x5,220,24), end-CHS (0xe1,120,63), startsector 94208, 3528040 sectors

イメージから tarball を作成

ダウンロードしたディスクイメージは、実機で実行することで焼いた先のデバイス(microSD とか SSD とか)のサイズいっぱいまで拡大されるようになっているが、本稿の手順ではその拡大が行われない。

ディスクイメージファイルのまま直接運用すると容量が足りなくなるのは必至なので、イメージの中身だけを tar に固めておき、ホスト側のファイルシステムに直接展開する。

新しいやり方

いきなりイメージを loopback デバイスにアタッチする。

$ sudo losetup -frP 2017-09-07-raspbian-stretch-lite.img 
  • -f: 空いている loop デバイス名を適当に探して使う
  • -r: readonly
  • -P: パーティションをスキャンして loopXpY を作る

fdisk してみるとパーティションに対応するデバイスファイルもできている。

$ sudo fdisk -l /dev/loop0
Disk /dev/loop0: 1.7 GiB, 1858076672 bytes, 3629056 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0x37665771

Device       Boot Start     End Sectors  Size Id Type
/dev/loop0p1       8192   93236   85045 41.5M  c W95 FAT32 (LBA)
/dev/loop0p2      94208 3629055 3534848  1.7G 83 Linux

Linux パーティションをどこかに適当にマウントする。

$ sudo mount -o ro /dev/loop0p2 /mnt

そしてそれを tarball に固める。このとき root で行う。

$ sudo tar cvjf ~/raspbian-stretch-lite.tar.bz2 -C /mnt .

tarball ができたら unmount して losetup -d しておく。

古いやり方

イメージ中のパーティションテーブルを調べて、Linux パーティションの先頭セクタを求める(FAT32 のほうは /boot なので使わない)。

$ /sbin/fdisk -l -u 2017-09-07-raspbian-stretch-lite.img
Disk 2017-09-07-raspbian-stretch-lite.img: 1.7 GiB, 1854590976 bytes, 3622248 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0x11eccc69

Device                                Boot Start     End Sectors  Size Id Type
2017-09-07-raspbian-stretch-lite.img1       8192   93813   85622 41.8M  c W95 FAT32 (LBA)
2017-09-07-raspbian-stretch-lite.img2      94208 3622247 3528040  1.7G 83 Linux

この出力では Linux パーティションの先頭セクタは 94208 なので、セクタサイズ 512 をかけたオフセットでループバックデバイスを作成する。

$ sudo losetup -o `expr 512 \* 94208` -f 2017-09-07-raspbian-stretch-lite.img

ループバックデバイスができたらそれをどこかに適当にマウントする。

$ sudo mount -o ro /dev/loop0 /mnt

そしてそれを tarball に固める。このとき root で行う。

$ sudo tar cvjf ~/raspbian-stretch-lite.tar.bz2 -C /mnt .

tarball ができたら unmount して losetup -d しておく。

Raspbian ファイルシステムの準備

Raspbian 用ファイルシステムを置く場所を用意し、そこに tarball を展開する。このとき root で行う。

$ mkdir -p ~/arm/raspbian
$ cd ~/arm/raspbian
$ sudo tar xpjf ~/raspbian-stretch-lite.tar.bz2

ARM 実行環境の準備

QEMU のユーザモードエミュレータの static 版をインストールする。

$ sudo apt-get install qemu-user-static

このとき binfmt の設定も自動で入るはずなので、特に interpreter 行のパスを確認しておく。

$ sudo update-binfmts --display | grep arm
qemu-arm (enabled):
 interpreter = /usr/bin/qemu-arm-static
qemu-armeb (enabled):
 interpreter = /usr/bin/qemu-armeb-static

chroot 先の整備

tarball を展開したディレクトリを / としたときに interpreter 行のパスを再現するように、qemu-arm-static をコピーしておく。

$ sudo cp /usr/bin/qemu-arm-static arm/raspbian/usr/bin/

tarball を展開した中にある /sys, /proc, /dev などにホストのそれをマウントしておく。 mount --bind でホストから持ってきてもよい。このほかにもホスト側とやり取りする必要があればそのディレクトリもマウントしておく(シンボリックリンクでは chroot をまたげないのでダメ)。

$ sudo mount -t sysfs sysfs arm/raspbian/sys
$ sudo mount -t proc proc arm/raspbian/proc
$ sudo mount -t devtmpfs udev arm/raspbian/dev
$ sudo mount -t devpts devpts arm/raspbian/dev/pts

chroot

そうして chroot で tarball を展開したディレクトリに入る。

$ sudo chroot arm/raspbian /bin/bash
root@len:/# su - pi

pi@len:~ $ uname -a
Linux len 4.9.0-3-amd64 #1 SMP Debian 4.9.30-2+deb9u1 (2017-06-18) armv7l GNU/Linux

pi@len:~ $ lsb_release -a
No LSB modules are available.
Distributor ID: Raspbian
Description:    Raspbian GNU/Linux 9.1 (stretch)
Release:        9.1
Codename:       stretch

pi@len:~ $ gcc -v
Using built-in specs.
COLLECT_GCC=/usr/bin/gcc
COLLECT_LTO_WRAPPER=/usr/lib/gcc/arm-linux-gnueabihf/6/lto-wrapper
Target: arm-linux-gnueabihf
Configured with: ../src/configure -v --with-pkgversion='Raspbian 6.3.0-18+rpi1' --with-bugurl=file:///usr/share/doc/gcc-6/README.Bugs --enable-languages=c,ada,c++,java,go,d,fortran,objc,obj-c++ --prefix=/usr --program-suffix=-6 --program-prefix=arm-linux-gnueabihf- --enable-shared --enable-linker-build-id --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --libdir=/usr/lib --enable-nls --with-sysroot=/ --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --with-default-libstdcxx-abi=new --enable-gnu-unique-object --disable-libitm --disable-libquadmath --enable-plugin --with-system-zlib --disable-browser-plugin --enable-java-awt=gtk --enable-gtk-cairo --with-java-home=/usr/lib/jvm/java-1.5.0-gcj-6-armhf/jre --enable-java-home --with-jvm-root-dir=/usr/lib/jvm/java-1.5.0-gcj-6-armhf --with-jvm-jar-dir=/usr/lib/jvm-exports/java-1.5.0-gcj-6-armhf --with-arch-directory=arm --with-ecj-jar=/usr/share/java/eclipse-ecj.jar --with-target-system-zlib --enable-objc-gc=auto --enable-multiarch --disable-sjlj-exceptions --with-arch=armv6 --with-fpu=vfp --with-float=hard --enable-checking=release --build=arm-linux-gnueabihf --host=arm-linux-gnueabihf --target=arm-linux-gnueabihf
Thread model: posix
gcc version 6.3.0 20170516 (Raspbian 6.3.0-18+rpi1)

最初のコマンドは sudo chroot arm/raspbian su - pi としてもよい。

この中で apt-get することで環境を整備していく。これで Pi 実機よりは圧倒的に速いビルド環境ができたので、あとは x264 でも FFmpeg でもビルドし放題。

種明かし

以下は昔に社内 Wiki に書いたものの転載。

binfmt-support

実行可能ファイルのヘッダを見て、必要なランタイムやインタプリタ等を準備する。
スクリプトの #! 行を見てインタプリタ経由で実行させたり、ELF バイナリのヘッダを見てダイナミックリンカ (ld-linux.so) 経由で実行させたりしているのはこの仕組み。このパッケージはこの仕組みを設定ファイルで拡張可能にする。

qemu-arm-static

ARM 用の Linux バイナリを、x86 で実行できるようにする。
ARM 機械語の部分を x86 機械語に翻訳して実行し、システムコールはホストのものを呼び出すようになっている。通常の QEMU と違ってハードウェアをエミュレートしなくて良い分、高速に動作する。
スタティックリンク版を使う理由は後述。

ELF バイナリのヘッダにはアーキテクチャを示す項目があるので、「ARM アーキテクチャな ELF ヘッダを見つけたら qemu-arm-static を使って実行する」ように binfmt に設定すれば、ARMバイナリを x86 でも透過的に実行できるようになるはずである。

しかしこれは失敗してしまう。このまま実行しようとしても ARM 版のダイナミックリンカが見つからないというエラーになってしまう。

Linux のダイナミックリンカは /lib/ld-linux.so という名前なのだが、ARM も x86 も同じ名前で参照しようとするところ、実際にこのパスにあるのは x86 版なわけなのでエラーになる。このファイルは x86 バイナリの実行に必須なため置き換えることもできない。同様の問題が libc (libc.so) にもあてはまる。

chroot

ルートディレクトリを指定のディレクトリに変更した上で、指定のコマンドを実行する。このコマンドの子孫となるプロセスは、新しいルートディレクトリを引き継ぐ。新しいルートディレクトリ以下には、コマンドの実行に必要なライブラリやディレクトリ構造が揃っていないといけない。

chroot 先のディレクトリに ARM バイナリ一式(ランタイムも)と qemu-arm-static を置くことで、以下の流れが成立する。

chroot がルートディレクトリを変更する(元のディレクトリにはもう戻れない)
  ↓
(たとえば)chroot 先の /bin/bash を実行する
  ↓
ARM バイナリなので binfmt が /usr/bin/qemu-arm-static 経由で実行しようとする
  ↓
chroot 先の /usr/bin/qemu-arm-static を実行(これは x86 バイナリ)
  ↓
ARM 版のダイナミックリンカやランタイムを chroot 先から探すようになる
  ↓
bash 起動。この bash から起動したコマンドはすべて chroot の下で動作する。

binfmt は元々のルートディレクトリのつもりで qemu-arm-static を実行しようとするが、この場合すでに chroot してしまっているので、chroot 先のディレクトリにも、binfmt の設定に書かれているパスのとおりに qemu-arm-static を配置しておく必要がある。

ARM バイナリしか入っていない chroot 先のディレクトリで x86 バイナリの qemu-arm を実行するため、qemu-arm がダイナミックリンクされていると、x86 ランタイムライブラリを chroot 先で見つけられずに実行は失敗してしまう。スタティックリンク版を使うのはこのためである。

chroot 先のディレクトリに /dev/proc, /sys がないとコマンドの実行に支障をきたすことがあるので、これらはホストのものがそのまま見えるようにしておく必要がある。シンボリックリンクでは chroot を越えることができないので、bind mount するか直接マウントする(mount -t udev devfs /path/to/arm/dev)。

29
24
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
29
24