NetBSDでLinuxバイナリエミュレーションしてみる

遅ればせながら[NetBSD Advent Calendar 2018](https://qiita.com/advent-calendar/2018/netbsd) 19日目の記事です。
今日はNetBSDでLinuxバイナリをエミュレーションして動かす手順を紹介しようと思います。

## NetBSDでのLinuxバイナリエミュレーション

NetBSDでは、Linuxバイナリを直接実行するためのエミュレーションレイヤが用意されています。
設定手順はNetBSDドキュメントの"Chapter 10. Linux emulation]の章に記載されています。

 * [The NetBSD Guide](https://www.netbsd.org/docs/guide/en/index.html)
   * [Chapter 10. Linux emulation](https://www.netbsd.org/docs/guide/en/chap-linux.html)

## Linuxバイナリを実行する環境を作る

基本的には[Chapter 10. Linux emulation](https://www.netbsd.org/docs/guide/en/chap-linux.html)の内容をそのまま実施するだけでLinuxバイナリの実行環境が構築できます。

今回は実際にドキュメントで紹介されている、Linux版のAcrobat Readerを動かす例を試してみましょう。

### 必要なカーネルオプション

[ドキュメント](https://www.netbsd.org/docs/guide/en/chap-linux.html)には、以下のカーネルオプションを有効にする必要がある旨が書かれていますが、NetBSD-amd64環境では、GENERICカーネル(デフォルトのカーネル)で既に有効化された状態になっています。

```
option COMPAT_LINUX
option EXEC_ELF32
```

また、どのLinuxカーネルバージョンに相当するかは、 `sysctl` コマンドで確認できます。今回の例では `Linux-3.11.6` のようです。

```sh
# sysctl -a | grep -i linux
emul.linux.kern.ostype = Linux
emul.linux.kern.osrelease = 3.11.6
emul.linux.kern.osversion = #1 SMP PREEMPT Thu Oct 24 16:23:02 UTC 2013
emul.linux32.kern.ostype = Linux
emul.linux32.kern.osrelease = 3.11.6
emul.linux32.kern.osversion = #1 SMP PREEMPT Thu Oct 24 16:23:02 UTC 2013
```

### カーネルモジュールをロードする

Linuxバイナリエミュレーション機能を提供するカーネルモジュールをロードします。
こちらもNetBSD-amd64な環境では、デフォルトでロードされる設定になっているようです。

```sh 
# modload compat_linux
# modload exec_elf32
#
# modstat | grep '^compat_linux'
compat_linux            exec     builtin  -        1       - compat,compat_ossaudio,sysv_ipc,exec_elf64
compat_linux32          exec     builtin  -        0       - compat_linux,exec_elf32,compat_netbsd32,compat_netbsd32_sy\
svipc
```

## Linuxバイナリが参照する共有ライブラリの設定・インストール

NetBSDやLinuxの実行バイナリフォーマットに用いられている[ELF(Executable and Linkable Format)](https://ja.wikipedia.org/wiki/Executable_and_Linkable_Format)では、libc等のライブラリを共有ライブラリの形で別ファイルに分けておくことができます。そのためNetBSDでLinuxバイナリを実行する際には、Linuxの共有ライブラリをあらかじめインストールしておく必要があります。

NetBSDでのLinuxバイナリエミュレーションでは、SUSEのインストールCDで提供されている `suse100_base` というパッケージが必要なります。が、便利なことにこれらのパッケージは以下のNetBSDパッケージによりインストールと必要なディレクトリ構成の構築まで行ってくれます。


```sh 
# pkg_add -v suse_base suse_compat suse_x11
```

Linuxバイナリの実行時には `/etc` や `/proc` 以下のファイルが参照されますが、このままだとNetBSDのファイルを参照されてしまいます。そのため、 `/emul` というディレクトリを別途用意し、その中にLinuxバイナリ向けの `/etc` や `/proc` を用意し、そちらを参照させる形になります。これらの設定は、前述のパッケージがインストールされる際に行われます。

とりあえず、これでLinuxバイナリエミュレーションのための準備が整いました。

## Linux版 Acrobat Readerをインストールする

### pkgsrcを用意する

NetBSDにはpackageとpkgsrcという機構があり、packageはバイナリのパッケージインストール(RedHat/CentOSでいうところのrpm/yum)とパッケージをソースからビルド・インストールするpkgsrcがあります。

今回はpkgsrcを使用してAcrobat Readerをインストールします。まずはpkgsrcの入手と展開です。
以下のコマンドで `/usr/src/pkgsrc` ディレクトリ以下にpkgsrcのファイルが展開されます。

```sh 
# curl -O http://ftp.jaist.ac.jp/pub/NetBSD/packages/current-src/pkgsrc.tgz
# tar zxvf pkgsrc.tgz -C /usr
```

後は単に `make` コマンドを実行するだけです。
ただし、Acrobat Readerの場合は `/etc/mk.conf` にライセンスを受け入れる旨の記述を追記しておく必要があります。

```sh
# echo 'ACCEPTABLE_LICENSES+=adobe-acrobat-license' >> /etc/mk.conf
# unset PKG_PATH
#
# cd /usr/pkgsrc/print/acroread7/
# make
# make install
```

これでLinux版 Acrobat Readerがインストールできました。

![sample6.gif](https://qiita-image-store.s3.amazonaws.com/0/61137/8945938c-ce07-a201-7556-7bef09aed3b3.gif)

## 自分でコンパイルしたLinuxバイナリも動かしてみる

もう一つ試しに、自分でコンパイルしたLinuxバイナリも動かしてみましょう。
Linux環境(Ubuntu 14.04.2 LTS(32bit))でコンパイルしたHello,WorldプログラムをNetBSD上で動かしてみます。

```sh 
# # NetBSDはamd64な環境
# uname -a
NetBSD nbsd8tmp 8.0 NetBSD 8.0 (GENERIC) #0: Tue Jul 17 14:59:51 UTC 2018  mkrepro@mkrepro.NetBSD.org:/usr/src/sys/arch/amd64/compile/GENERIC amd64
#
# # 実行するLinuxバイナリは、32bitバイナリ
# file hello
hello: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.24, BuildID[sha1]=8aa0745ddc1c3160fee8da580b84ecda40910eb9, with debug_info, not stripped
#
# # NetBSDのバイナリは以下のような表示になる
# file /bin/echo
/bin/echo: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /libexec/ld.elf_so, for NetBSD 8.0, not stripped
```

普通にLinuxバイナリが実行できます。

```sh
# ./hello
hello,world.
```

ちなみに、Linuxエミュレーション環境を設定していないNetBSDでは、以下のような結果になります。

```sh
$ file hello
hello: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.24, BuildID[sha1]=8aa0745ddc1c3160fee8da580b84ecda40910eb9, with debug_info, not stripped
$ ./hello
-bash: ./hello: No such file or directory
```

さらに `ktrace` ( `strace` のBSD版的なコマンド)で呼ばれているシステムコールをトレースしてみましょう。

```sh
# ktrace ./hello
hello,world.
# kdump ktrace.out
```

以下のような出力になっています。カーネル的には `netbsd32_read()` 等のシステムコールに対応するカーネル関数に置き換えられて実行されているようです。この動作を見ると、エミュレーションというよりかは、互換レイヤーというイメージの方が実態に近いですね。

```
  3602      1 ktrace   EMUL  "netbsd"
  3602      1 ktrace   CALL  execve(0x7f7fff87bf07,0x7f7fff87b960,0x7f7fff87b970)
  3602      1 ktrace   NAMI  "/root/./hello"
  3602      1 ktrace   NAMI  "/emul/linux32"
  3602      1 ktrace   NAMI  "/emul/linux32/lib/ld-linux.so.2"
  3602   3602 hello    EMUL  "linux32"
  3602   3602 hello    RET   execve -1 errno -2 No such file or directory
  3602   3602 hello    CALL  brk(0)
  3602   3602 hello    RET   brk 134524928/0x804b000
  3602   3602 hello    CALL  uname(0xbffff9a2)
  3602   3602 hello    RET   uname 0
  3602   3602 hello    CALL  netbsd32_access(0xfbef6c3f,4)
  3602   3602 hello    NAMI  "/emul/linux32/etc/ld.so.preload"
  3602   3602 hello    NAMI  "/etc/ld.so.preload"
  3602   3602 hello    RET   netbsd32_access -1 errno -2 No such file or directory
  3602   3602 hello    CALL  open(0xfbef7737,0x80000,0)
  3602   3602 hello    NAMI  "/emul/linux32/etc/ld.so.cache"
  3602   3602 hello    RET   open 3
  3602   3602 hello    CALL  fstat64(3,0xbffff608)
  3602   3602 hello    RET   fstat64 0
  3602   3602 hello    CALL  mmap2(0,0x45ba,1,2,3,0)
  3602   3602 hello    RET   mmap2 -68325376/0xfbed7000
  3602   3602 hello    CALL  netbsd32_close(3)
  3602   3602 hello    RET   netbsd32_close 0
  3602   3602 hello    CALL  open(0xfbeda153,0x80000,0xfbed7aa8)
  3602   3602 hello    NAMI  "/emul/linux32/lib/libc.so.6"
  3602   3602 hello    RET   open 3
  3602   3602 hello    CALL  netbsd32_read(3,0xbffff738,0x200)
  3602   3602 hello    GIO   fd 3 read 512 bytes
       "\^?ELF\^A\^A\^A\0\0\0\0\0\0\0\0\0\^C\0\^C\0\^A\0\0\0@\M^X\^A\0004\0\0\
	\0hK\^W\0\0\0\0\0004\0 \0\n\0(\0D\0A\0\^F\0\0\0004\0\0\0004\0\0\0004\0\
	\0\0@\^A\0\0@\^A\0\0\^E\0\0\0\^D\0\0\0\^C\0\0\0\240\M^V\^S\0\240\M^V\
	\^S\0\240\M^V\^S\0\^S\0\0\0\^S\0\0\0\^D\0\0\0\^A\0\0\0\^A\0\0\0\0\0\0\
	\0\0\0\0\0\0\0\0\0\M-x\b\^W\0\M-x\b\^W\0\^E\0\0\0\0\^P\0\0\^A\0\0\0\
	\M-D\^Q\^W\0\M-D\^Q\^W\0\M-D\^Q\^W\0\M-X,\0\0\M^XX\0\0\^F\0\0\0\0\^P\0\
	\0\^B\0\0\0\M^H-\^W\0\M^H-\^W\0\M^H-\^W\0\M-x\0\0\0\M-x\0\0\0\^F\0\0\0\
	\^D\0\0\0\^D\0\0\0t\^A\0\0t\^A\0\0t\^A\0\0D\0\0\0D\0\0\0\^D\0\0\0\^D\0\
	\0\0\a\0\0\0\M-D\^Q\^W\0\M-D\^Q\^W\0\M-D\^Q\^W\0\b\0\0\0L\0\0\0\^D\0\0\
	\0\^D\0\0\0P\M-etd\M-4\M^V\^S\0\M-4\M^V\^S\0\M-4\M^V\^S\0\^\q\0\0\^\q\
	\0\0\^D\0\0\0\^D\0\0\0Q\M-etd\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\
	\^F\0\0\0\^D\0\0\0R\M-etd\M-D\^Q\^W\0\M-D\^Q\^W\0\M-D\^Q\^W\0<\^^\0\0<\
	\^^\0\0\^D\0\0\0\^A\0\0\0\^D\0\0\0\^T\0\0\0\^C\0\0\0GNU\0K\M-s\M-'\M-P\
	\M-S\M-]\M-y\M-0\^Bm\M-01\M-(\rj4\M-0\M-@\M-X[\^D\0\0\0\^P\0\0\0\^A\0\
	\0\0GNU\0\0\0\0\0\^B\0\0\0\^F\0\0\0\^P\0\0\0\M-s\^C\0\0\n\0\0\0\0\^B\0\
	\0\^N\0\0\0\2400\^PD\M^@ \^B\^A\M^L\^C\M-f\M^PAE\M^H\0\M^D\0\b\0E\M^@\
	\0`\M-@\M^@\0\f\M^J\f\0\^A0\0\b@2\b\M-.\^D\M^HH6l\240\^V0\0&\M^D\M^@\
	\M^N\^D\bB$"
  3602   3602 hello    RET   netbsd32_read 512/0x200
  3602   3602 hello    CALL  fstat64(3,0xbffff668)
  3602   3602 hello    RET   fstat64 0
  3602   3602 hello    CALL  mmap2(0,0x1000,3,0x22,0xffffffff,0)
  3602   3602 hello    RET   mmap2 -68329472/0xfbed6000
  3602   3602 hello    CALL  mmap2(0,0x176a5c,5,0x802,3,0)
  3602   3602 hello    RET   mmap2 -69865472/0xfbd5f000
  3602   3602 hello    CALL  mmap2(0xfbed0000,0x3000,3,0x812,3,0x171)
  3602   3602 hello    RET   mmap2 -68354048/0xfbed0000
  3602   3602 hello    CALL  mmap2(0xfbed3000,0x2a5c,3,0x32,0xffffffff,0)
  3602   3602 hello    RET   mmap2 -68341760/0xfbed3000
  3602   3602 hello    CALL  netbsd32_close(3)
  3602   3602 hello    RET   netbsd32_close 0
  3602   3602 hello    CALL  mmap2(0,0x1000,3,0x22,0xffffffff,0)
  3602   3602 hello    RET   mmap2 -69869568/0xfbd5e000
  3602   3602 hello    CALL  set_thread_area(0xbffffb28)
  3602   3602 hello    RET   set_thread_area 0
  3602   3602 hello    CALL  mprotect(0xfbed0000,0x2000,1)
  3602   3602 hello    RET   mprotect 0
  3602   3602 hello    CALL  mprotect(0x8049000,0x1000,1)
  3602   3602 hello    RET   mprotect 0
  3602   3602 hello    CALL  mprotect(0xfbefd000,0x1000,1)
  3602   3602 hello    RET   mprotect 0
  3602   3602 hello    CALL  netbsd32_munmap(0xfbed7000,0x45ba)
  3602   3602 hello    RET   netbsd32_munmap 0
  3602   3602 hello    CALL  fstat64(1,0xbffffb20)
  3602   3602 hello    RET   fstat64 0
  3602   3602 hello    CALL  ioctl(1,_IO('T',0x1,0),0xbffffa68)
  3602   3602 hello    RET   ioctl 0
  3602   3602 hello    CALL  mmap2(0,0x1000,3,0x22,0xffffffff,0)
  3602   3602 hello    RET   mmap2 -68308992/0xfbedb000
  3602   3602 hello    CALL  netbsd32_write(1,0xfbedb000,0xd)
  3602   3602 hello    GIO   fd 1 wrote 13 bytes
       "hello,world.\n"
  3602   3602 hello    RET   netbsd32_write 13/0xd
  3602   3602 hello    CALL  exit_group(0)
```

## まとめ

NetBSDでLinuxバイナリエミュレーション機能を使用し、Linuxバイナリを動かすための手順を紹介しました。
エミュレーションといいつつも、実態はLinuxのシステムコールをNetBSDのものに置き換えて呼び出しており、互換レイヤーというイメージの方が近いようです。