Cygwinネイティブなqemuをビルドする

EDIT: JITCを有効にしたらちゃんとLinuxも起動した。

EDIT: Cygwinのバグ(?)節を新設。ちょっと動きが怪しい。

qemuはCygwinでの動作をサポートしていない。といってもqemuは実はかなりポータビリティが良く、pthreadをサポートしたPOSIXプラットフォームであればかなり動作する。

今回はqemuの本家をパッチしてqemuをCygwinでビルド、ARM64のEFI Shellを動かしてみた。 ~~(Linux kernelはダメだった)~~ Linuxも動いた。

![SnapCrab_NoName_2019-11-24_16-54-12_No-00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/67446/d1fb7088-9ed9-b3e6-70de-ee4fcf089151.png)


- https://github.com/okuoku/qemu/commits/cygwin 今回作成したブランチ

```
../configure --prefix=/opt/qemu --disable-strip --enable-plugins --disable-capstone --disable-tpm
```

↑ のようにしてconfigure、ビルドできる。

```
/opt/qemu/bin/qemu-system-aarch64.exe -m 512 -cpu cortex-a57 -smp 1 -machine virt -kernel k3os-vmlinuz-arm64 -initrd k3os-initrd-arm64 -nographic -serial mon:stdio -append "console=ttyAMA0"
```

- dmesgのgist: https://gist.github.com/okuoku/9c3511af656c8d6ac0291b7f62d0eb72

# EFIシェルを起動してみる

とりあえず適当なARM64バイナリを動かしてみようということで、linaroのサイトからビルド済のEFIファームウェアをダウンロードして起動してみる。

- http://snapshots.linaro.org/components/kernel/leg-virt-tianocore-edk2-upstream/latest/QEMU-AARCH64/RELEASE_GCC5/

ここから `QEMU_EFI.fd` をダウンロードして↓のようなコマンドラインで起動すると、1分くらいしてEFI Shellが起動する。

```shell:コマンドライン
/opt/qemu/bin/qemu-system-aarch64.exe -m 256 -cpu cortex-a57 -smp 1 -machine virt -bios QEMU_EFI.fd -nographic -serial mon:stdio
```

最初にPXEで待たされるのはご愛嬌。。

```
UEFI Interactive Shell v2.2
EDK II
UEFI v2.70 (EDK II, 0x00010000)
Mapping table
     BLK0: Alias(s):
          VenHw(93E34C7E-B50E-11DF-9223-2443DFD72085,00)
Press ESC in 1 seconds to skip startup.nsh or any other key to continue.
Shell>
```

今回オプションとして `-serial mon:stdio` を渡しているので、モニタとシリアル出力が同じ端末に出ることになる。これは `CTRL+A` → `c` で切り替えができ、他のコンビネーションは `CTRL+A` + `h` で出てくる。

```
C-a h    print this help
C-a x    exit emulator
C-a s    save disk data back to file (if -snapshot)
C-a t    toggle console timestamps
C-a b    send break (magic sysrq)
C-a c    switch between console and monitor
C-a C-a  sends C-a
```

# Cygwinのバグ(?)

移植の過程でいくつかCygwinのバグっぽい挙動を見つけた。テストしたバージョンは3.0.7。

## `O_NONBLOCK` なpipe(2)がブロックする

qemuでは、非同期I/Oのイベント通知にeventfd(2)か古き良きself-pipeテクニックを使っている。このself-pipeは `O_NONBLOCK` が設定されているものの、何故かCygwinではブロックしてしまうことがある。

```c:GDBのバックトレース.c
0  0x00007ffcbe9bcc14 in ntdll!ZwWaitForMultipleObjects () from /cygdrive/c/WINDOWS/SYSTEM32/ntdll.dll
1  0x00007ffcbba67ff7 in WaitForMultipleObjectsEx () from /cygdrive/c/WINDOWS/System32/KERNELBASE.dll
2  0x00007ffcbba67ede in WaitForMultipleObjects () from /cygdrive/c/WINDOWS/System32/KERNELBASE.dll
3  0x00000001800485f5 in cygwait (object=<optimized out>, timeout=timeout@entry=0x0, mask=mask@entry=5) at /usr/src/debug/cygwin-3.0.7-1/winsup/cygwin/cygwait.cc:75
4  0x000000018006b3ba in cygwait (mask=5, howlong=4294967295, h=<optimized out>) at /usr/src/debug/cygwin-3.0.7-1/winsup/cygwin/cygwait.h:45
5  cygwait (howlong=4294967295, h=<optimized out>) at /usr/src/debug/cygwin-3.0.7-1/winsup/cygwin/cygwait.h:51
6  fhandler_base_overlapped::wait_overlapped (this=this@entry=0x1803450e8, inres=<optimized out>, writing=writing@entry=true, bytes=bytes@entry=0xff9fbe9c, nonblocking=true, len=8) at /usr/src/debug/cygwin-3.0.7-1/winsup/cygwin/fhandler.cc:2017
7  0x000000018006bb31 in fhandler_base_overlapped::raw_write (this=0x1803450e8, ptr=0x18035dcd8, len=8) at /usr/src/debug/cygwin-3.0.7-1/winsup/cygwin/fhandler.cc:2201
8  0x0000000180065dbe in fhandler_base::write (this=0x1803450e8, ptr=0x100e2eec0 <value>, len=8) at /usr/src/debug/cygwin-3.0.7-1/winsup/cygwin/fhandler.cc:912
9  0x0000000180143123 in write (fd=7, ptr=0x100e2eec0 <value>, len=8) at /usr/src/debug/cygwin-3.0.7-1/winsup/cygwin/syscalls.cc:1342
10 0x0000000180128deb in _sigfe () at sigfe.s:35
11 0x0000000100959b2d in event_notifier_set (e=e@entry=0x600181294) at /home/oku/repos/qemu/util/event_notifier-posix.c:100
12 0x000000010095606e in aio_notify (ctx=0x6001811f0) at /home/oku/repos/qemu/util/async.c:350
13 0x00000001005449bf in gt_recalc_timer (cpu=0x600219070, timeridx=1) at /home/oku/repos/qemu/target/arm/helper.c:2451
14 0x0000000100544d83 in gt_tval_write (env=0x6002221d0, timeridx=1, value=<optimized out>, ri=<optimized out>) at /home/oku/repos/qemu/target/arm/helper.c:2507
15 0x00000001005914a4 in helper_set_cp_reg64 (env=0x6002221d0, rip=0x60024dce0, value=236350) at /home/oku/repos/qemu/target/arm/op_helper.c:693
16 0x000006ffef8186de in ?? ()
```

- https://github.com/okuoku/qemu/commit/6c2e43818cf1e4cfb77945e5e783246a31dca90f

```diff
diff --git a/util/event_notifier-posix.c b/util/event_notifier-posix.c
index 00d93204f9..7092fc9e21 100644
--- a/util/event_notifier-posix.c
+++ b/util/event_notifier-posix.c
@@ -49,9 +49,15 @@ int event_notifier_init(EventNotifier *e, int active)
         if (errno != ENOSYS) {
             return -errno;
         }
+#ifdef __CYGWIN__
+        if (socketpair(AF_UNIX, SOCK_STREAM, 0, fds) < 0) {
+            return -errno;
+        }
+#else
         if (qemu_pipe(fds) < 0) {
             return -errno;
         }
+#endif
         ret = fcntl_setfl(fds[0], O_NONBLOCK);
         if (ret < 0) {
             ret = -errno;
```

ちゃんと追うのが面倒だったので、ここではsocketpair(2)でお茶を濁している。socketpairを使うと、pipeのような自分自身に接続されたfdをUNIXドメインソケットを使って得ることができる。pipeのようなバグはsocketには無いようだ。

Cygwinのコードでは、同期I/OがブロックするかどうかをWin32 `GetLastError` で検出していて、これはPOSIXの `errno` 同様にThread Local Storageであるためスレッドセーフになっていないのが原因ではないかと思う。ただコレは普通にpipeを使っていれば再現するはずなので外しているかもしれない。

## `MAP_FIXED` でマップ済みの領域を再マップできない

qemu内部のmmap(2)が `EINVAL` で失敗してしまう。こういうときはstrace( http://cygwin.net/cygwin-ug-net/strace.html )でsyscallトレースを取って調べるのが定石なのでやってみると、

```
 3290  378355 [main] qemu-system-aarch64 42663 mmap64: addr 0x0, len 67174400, prot 0x0, flags 0x22, fd -1, off 0x0
  836  379191 [main] qemu-system-aarch64 42663 mmap64: 0x6FFEB8C0000 = mmap()
   34  379225 [main] qemu-system-aarch64 42663 mmap64: addr 0x6FFEB8C0000, len 67108864, prot 0x3, flags 0x32, fd -1, off 0x0
   39  379264 [main] qemu-system-aarch64 42663 mmap_record::map_pages: map_pages (addr=0x6FFEB8C0000, len=67108864)
   49  379313 [main] qemu-system-aarch64 42663 __set_errno: bool mmap_record::map_pages(caddr_t, SIZE_T):465 setting errno 22
   40  379353 [main] qemu-system-aarch64 42663 mmap64: 0xFFFFFFFFFFFFFFFF = mmap()
```

このように、 `flags 0x32` = `MAP_ANONYMOUS | MAP_FIXED | MAP_SHARED` のようにしているところでエラー `errno 22` = `EINVAL` になっていることが判る。

これは `qemu_ram_mmap` でガードページ付きのアロケーションを実施しているところなので、ガードページをガン無視して良ければ(= **メモリ領域外部のアクセスをクラッシュにする必要が無ければ**) `MAP_FIXED` は消しても動く。

- https://github.com/okuoku/qemu/commit/7b130081ee2727b2f831329a0017408bad05b199

というわけで消してしまった。

EINVALが発生する理由はよくわからない。基本的にはこのような上書きmapは合法のはずで、Cygwin側の制約なんじゃないかという気はする。(一旦unmapしてからやる方法ではatomic性が保証できないが、qemuのユースケースではatomic性を要求していないので専用のルーチンを用意すれば解決はするはず)

# 必要なパッチ

いきなりqemuをチェックアウトしてビルドしても正常にビルドできない。今回用意した `cygwin` ブランチではいくつかパッチしている。

## Cygwinをビルド可能なプラットフォームとして認識させる

https://github.com/okuoku/qemu/commit/895ba8cd355848929a4c0219f4e723bf5b1c0c24

```diff
diff --git a/configure b/configure
index 6099be1d84..78c1154e64 100755
--- a/configure
+++ b/configure
@@ -653,6 +653,8 @@ elif check_define __NetBSD__; then
   targetos='NetBSD'
 elif check_define __APPLE__; then
   targetos='Darwin'
+elif check_define __CYGWIN__; then
+  targetos='Cygwin'
 else
   # This is a fatal error, but don't report it yet, because we
   # might be going to just print the --help text, or it might
```

Cygwinのプラットフォーム検出は `__CYGWIN__` (直球) となっている。ちなみに `__CYGWIN32__` のようなbitness定義は無いということになっている。

- https://cygwin.com/faq/faq.html#faq.programming.64bitporting-fail

> `__CYGWIN32__` is not defined in the 64 bit toolchain. This may hit a few projects which are around since before Y2K. Check your project for occurences of `__CYGWIN32__` and change them to `__CYGWIN__`, which is defined in the Cygwin toolchain since 1998, to get the same Cygwin-specific code changes done. 

## `mlockall` を諦める

Cygwinにはmlockallが無いため諦める。真面目にエミュレーションしても良いかもしれないけど多分ユースケースが無い。

```diff
diff --git a/os-posix.c b/os-posix.c
index 86cffd2c7d..2ca183f6c9 100644
--- a/os-posix.c
+++ b/os-posix.c
@@ -352,6 +352,9 @@ bool is_daemonized(void)

 int os_mlock(void)
 {
+#ifdef __CYGWIN__
+    return 0;
+#else
     int ret = 0;

     ret = mlockall(MCL_CURRENT | MCL_FUTURE);
```

## `JOB_STATUS_PAUSED` の定義の衝突

Cygwinにインスールされる `<libusb.h>` は定義の互換のために `<windows.h>` をincludeしており、これがWin32 APIの定数とqemuソースコードの `enum` との衝突を招く。

```
In file included from /usr/include/w32api/windows.h:102:0,
                 from /usr/include/libusb-1.0/libusb.h:70,
                 from /home/oku/repos/qemu/hw/usb/host-libusb.c:40:
./qapi/qapi-types-job.h:36:5: エラー: expected identifier before numeric constant
     JOB_STATUS_PAUSED,
     ^
  CC      migration/block-dirty-bitmap.o
  CC      migration/block.o
make: *** [/home/oku/repos/qemu/rules.mak:69: hw/usb/host-libusb.o] エラー 1
```

というわけで、安直に `#undef` してしまう。。

- https://github.com/okuoku/qemu/commit/0b788458541b31477cd6e95ea98134c0e31317ea

```diff
diff --git a/hw/usb/host-libusb.c b/hw/usb/host-libusb.c
index fcf48c0193..eee977bad0 100644
--- a/hw/usb/host-libusb.c
+++ b/hw/usb/host-libusb.c
@@ -38,6 +38,7 @@
 #include <poll.h>
 #endif
 #include <libusb.h>
+#undef JOB_STATUS_PAUSED
```

これはどちらかというとlibusbのヘッダの方がよくない(環境間でpull-inする定義が多すぎる)が、既存のプロジェクトを壊さないという目的上こうなっているようだ。

- https://github.com/libusb/libusb/commit/9a4249f8a104b98a15a7e3ba7ecae9a385ed9027#diff-5b02bd3a0470fa27d148e3d6b4432accR38

```c
/* 'interface' might be defined as a macro on Windows, so we need to
 * undefine it so as not to break the current libusb API, because
 * libusb_config_descriptor has an 'interface' member
 * As this can be problematic if you include windows.h after libusb.h
 * in your sources, we force windows.h to be included first. */
#if defined(_WIN32) || defined(__CYGWIN__) || defined(_WIN32_WCE)
#include <windows.h>
#if defined(interface)
#undef interface
#endif
```

## 不要な `<sys/syscall.h>` のincludeをやめる

signalfd(2)をエミュレートするのに `<sys/syscall.h>` のincludeは要らないのでifdefする。

- https://github.com/okuoku/qemu/commit/a19ec4d928f8b55a26593800466273506a6656bd

もっとも、実は今年リリースされたCygwin 3.0以降では `signalfd` をAPIとしてサポートしている( https://cygwin.com/ml/cygwin-announce/2019-02/msg00010.html )。用意されているcompat関数はsignalfdをスレッドを使ってエミュレートするものなので、ネイティブのAPIを使った方が効率的かもしれない。

このsignalfdの用法はKVMに由来する( https://github.com/okuoku/qemu/commit/dcc38d1cce61e7d986e74dcd9bff2a5efc53a87c )。libc側を使わない理由は謎。


## TCGで正しいWin64 ABIを使用する

EDIT: 追記。最初はこれをやっていなくてJITCを使うとクラッシュしていた。

Cygwinでは一般的なELFプラットフォームと異なり、Win64の呼び出し規約を使用する。このとき、破壊されるレジスタやスタックの配置はABIに合わせて対応する必要があるが、qemuは何故かこの判定を `_WIN64` のdefineで行っているので、`configure`側で追従する必要がある。

- https://github.com/okuoku/qemu/commit/3efbaa382db89cd994b53dae17f5c901d16c1fe1

```diff
diff --git a/configure b/configure
index 7b553b84e5..2145f76303 100755
--- a/configure
+++ b/configure
@@ -894,6 +894,7 @@ Linux)
   libudev="yes"
 ;;
 Cygwin)
+  QEMU_CFLAGS="-D_WIN64 $QEMU_CFLAGS"
   cygwin="yes"
 ;;
 esac
```

...実際、このWin64方式のamd64 ABIにはあんまり良い名称が無く(Windows以外ではCygwinとBIOS類くらいでしか採用例が無い)、 `_WIN64` で検出してしまうのは止むを得ない気はする。

# 外部デバイスへの対応

外部と通信できないと面白くないので、USBとEthernetには対応してみた。

## TAP-win32に対応する

qemuではL2TP(= UDP)を使ったEthernetフレーム転送をサポートしているためそっちを使えば良いが、準備が面倒なのでTAP-win32を使えるようにした。

- https://github.com/okuoku/qemu/commit/2db0cf765a2fafc708a7452392a0843981626b5c - ビルド周り
- https://github.com/okuoku/qemu/commit/49ecb0b48d1bd8dce3b38d7c59ed2fbde423a0f3 - self-pipeによる `qemu_add_wait_object` の実装

qemuのTAP-win32実装では、バッファ通知をWin32 Semaphoreで行っている。POSIXでのメインループ `util/main-loop.c` はfd以外のものを待つことができないので、Semaphoreとcygwinのfdを繋ぐものが必要になる。今回は独立スレッドを用意してself-pipeした。deleteの実装はサボっている。

## usbredir を移植する

TBD: コレは意外と面白そうなので別立てにする。

USBデバイスも使いたかったのでUSBトラフィックをTCP/IPにencapsulateするusbredirをCygwinに移植してみた。元々はqemuとKVMを使用したリモートデスクトップソリューションであるSPICEのために開発されたプロトコル( https://www.spice-space.org/usbredir.html )で、既存のUSB/IPと異なりデバイスのディスカバリのような機能が無い。

- https://github.com/okuoku/spice-usbredir/commit/7af822b0b90957a137bf1de68b00b76fd7af7b29

↑ のTAP-win32のケースと同じく、`usbredirserver` のメインループはfdしか待てないため、独立スレッドを用意してUSBイベントを一括で処理するようにしている。

SPICEでは汎用のフィルタドライバUSBDk( https://github.com/daynix/UsbDk )のサポートも提供しているが、USB-NIC程度であればZadig( https://zadig.akeo.ie/ )からインストールできるlibusbkで十分と言える。

# TODO

あんまり真面目に追ってないがそのうちやるかもしれない:

- ~~**Linuxを動かす** 。動いた方が面白いけどTCI(インタプリタ側)のバグのような気もする。。~~ やった。
- ~~**JITCするようにする** 。移植するのに何が必要なのかまだ調べてない。~~ やった。
- **capstone検出を治す**  。capstone( http://www.capstone-engine.org/ )は良い逆アセンブラで、たぶん検出側を多少修正すればそのままCygwinでも使えると思う。
- **TPMがビルドできないのを治す** 。...TPM要る。。？