5
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

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

Last updated at Posted at 2019-11-24

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

../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"

EFIシェルを起動してみる

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

ここから QEMU_EFI.fd をダウンロードして↓のようなコマンドラインで起動すると、1分くらいしてEFI 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+Ac で切り替えができ、他のコンビネーションは 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ではブロックしてしまうことがある。

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 ?? ()
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 は消しても動く。

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

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

必要なパッチ

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

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

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定義は無いということになっている。

__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 --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 してしまう。。

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する定義が多すぎる)が、既存のプロジェクトを壊さないという目的上こうなっているようだ。

/* '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する。

もっとも、実は今年リリースされた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側で追従する必要がある。

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を使えるようにした。

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と異なりデバイスのディスカバリのような機能が無い。

↑ の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要る。。?
5
1
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
5
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?