tmytのらくがき

個人の日記レベルです

WSLで32bitなELFが動いた(ただしQEMU)

TL;DR

  • binfmtとQEMUで32bitなELFを実行する
  • WSLのbinfmtはOCフラグがサポートされてない
  • sudo update-binfmts --install qemu-i386 /usr/bin/qemu-i386-static --magic '\x7f\x45\x4c\x46\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x03\x00' --mask '\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff'

背景

はぁ…WSLにbinfmt_miscなんで実装されないんだろ…と思いながらUserVoiceを眺めていたところ、よくよく調べたらWSL空間からWin32のバイナリを呼び出す仕組みを実現するためにRS2の時点で実装されていたのでした。なんと1年以上前…当時Win32 PEを実行するのにMZヘッダが見つかったら/initに実行を委譲するよ。みたいな記事を読んだはずなのにすっかり忘れていた。

binfmtが使えるならi386 ELFのmaskを設定してやれば動くんじゃないの…と思ったのが6時前。思い立ってしまったら気になって眠れなくなったのでやってみました。

手順

  1. qemu-user-staticをインストール
  2. update-binfmtsでハンドラを登録

まずqemu-user-staticをインストール。

$ sudo apt-get install qemu-user-static

qemu-user-static用のbinfmtが自動登録されるけど、これにi386はない*1のでハンドラ手動登録。

$ sudo update-binfmts --install qemu-i386 /usr/bin/qemu-i386-static --magic '\x7f\x45\x4c\x46\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x03\x00' --mask '\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff'

うじゃうじゃたくさん書いてあるのはELFのヘッダを検出するマジックとマスク。ELFの仕様ともともとあったqemu-user-staticのマジックを眺めながらいい感じにしたらこうなりました。

なぜかこうするとうまく動くようになるんですが、どうやらWSLのbinfmt_miscはOCフラグというのに対応していないらしい。こうやってやるといい感じに動くらしい*2

動作確認

せっかくなので32bit ELFなバイナリを作って実行してみる。

$ sudo apt-get install build-essential libc6-dev-i386
$ vi test.c
#include<stdio.h>

int main(){
  puts("Hello World");
  return 0;
}
$ gcc -m32 test.c
$ file a.out
a.out: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=1e13e8c9bba4368d713095984853243c85b65768, not stripped
$ ./a.out
Hello World

ちなみにupdate-binfmtsしない場合はこうなる。

$ ./a.out
-bash: ./a.out: cannot execute binary file: Exec format error

おまけ

WSLのインスタンスが全部終了するとbinfmtのエントリが消し飛ぶので何らかの方法で復元してください

*1:普通のx86_64ならi386バイナリが直接実行できるので登録する意味がない

*2:https://github.com/Microsoft/WSL/issues/2620#issuecomment-374490404