WSL2のシステムコール実装/未実装を確認する

Windows 10 Pro Insider Preview 1903, 18922.1000 をインストールしてWSL2が使えるようになった。カーネルのバージョン情報を確認する。

# uname -a
Linux DESKTOP-DN3P0KN 4.19.43-microsoft-standard #1 SMP Mon May 20 19:35:22 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux

WSLの時と同じツールを使ってWSL2のシステムコール実装/未実装を調べた結果、以下のシステムコールが未実装と判断した。

[134] uselib
[174] create_module
[176] delete_module
[177] get_kernel_syms
[178] query_module
[180] nfsservctl
[181] getpmsg
[182] putpmsg
[183] afs_syscall
[184] tuxcall
[185] security
[202] futex
[205] set_thread_area
[211] get_thread_area
[212] lookup_dcookie
[214] epoll_ctl_old
[215] epoll_wait_old
[236] vserver
[237] mbind
[238] set_mempolicy
[239] get_mempolicy
[246] kexec_load
[256] migrate_pages
[279] move_pages
[320] kexec_file_load
[323] userfaultfd

WSL2でほとんどのシステムコールが使えるようになったことがわかる。使えないのは共有ライブラリロード(uselib), カーネルモジュール操作(XXX_module), futex, TLS操作, NUMA操作, kexec, userfaultfdあたり。うちで作っているソフトウェアを動作させるために必須なメッセージキュー操作(msgXXX), syslog, mlockall が使えるようになったのは超朗報(^^)。

64bit Windows(host) - CentOS 7(target)のgdb作成手順

Windowsホスト上でCentOS 7上のプロセスをリモートデバッグするためのクロスターゲット用gdbを作ります。開発環境はWindowsホスト上の MSYS MinGW 64bit を使います。

1. GNUのサイトからgdbのソースtarball(gdb-7.6.1.tar.gz)を入手する。

2. windows-termcap.o をリンクさせないためのパッチファイルを用意する。

$ ls
gdb-7.6.1.tar.gz  gdb-mingw64-configure.patch

[gdb-mingw64-configure.patch]
--- gdb/configure.org
+++ gdb/configure
@@ -6960,7 +6960,6 @@
     ;;
   *mingw32*)
     ac_cv_search_tgetent="none required"
-    CONFIG_OBS="$CONFIG_OBS windows-termcap.o"
     ;;
 esac
 

3. gdbのソースを展開してパッチをあてる。

$ tar xvf gdb-7.6.1.tar.gz
$ cd gdb-7.6.1
$ patch -p0 < ../gdb-mingw64-configure.patch

4. gdbをビルドする。ビルドが成功すれば /opt/centos7/bin/x86_64-unknown-linux-gnu-gdb.exe ができる。

$ mkdir build
$ cd build
$ ../configure --target=x86_64-unknown-linux-gnu --disable-werror --with-expat --prefix=/opt/centos7
$ make -j4 && make install

4. CentOS7上でgdbserverを起動しておき、クロスターゲット用gdbからgdbserverにTCP/IP接続してリモートデバッグする。

EUC-JP環境でQt4を使って文字化けした件

今日はCentOS7のEUC-JP環境でQt4ベースの画面を作っていてハマった問題を紹介します。

QButtonのテキストにNEC特殊文字(①②③やⅠⅡⅢ)を含めると表示が文字化けしてしまいました。QButtonの内部ではラベル文字列をUTF16で管理していますが、NEC特殊文字は純粋なEUC-JPでは定義されないので、文字コード変換した結果がおかしくなっているのだと思います。JAVAを使っていて類似の問題を経験した時にはエンコーディングとして EUC_JP_Solaris を指定することで解決しました。Qtでも同様の設定があるだろうと高を括って調べましたが見あたりません。仕方がないのでEUC-JPをUTF16に変換する処理を自前で持ち、変換した結果をQButtonに渡すことにします。

結局、以下のようにすることで文字化けを回避できました。変換エンジンはiconv。得られたUTF16文字列を直接QButtonに渡す手段も見当たらなかったので、1文字ずつQStringに追加しています。

#include <boost/foreach.hpp>
#include <QApplication>
#include <QVBoxLayout>
#include <QPushButton>
#include <cstring>
#include <iconv.h>
using namespace std;

// NEC拡張文字を含むEUC-JP文字列をUTF-16文字列に変換する
static int convert_to_unicode(char *in, size_t in_size, char *out, size_t out_size) {
    iconv_t context = iconv_open("UTF-16", "EUC-JP-MS");
    iconv(context, &in, &in_size, &out, &out_size);
    iconv_close(context);
    return 0;
}

// メイン処理
int main(int argc, char **argv)
{
    // Main Window
    QApplication app(argc, argv);
    QWidget      window;
    QVBoxLayout  layout;
    QPushButton  button1("EUC-JP [③Ⅲ3]"); // 文字化けする
    QPushButton  button2("EUC-JP [③Ⅲ3]"); // 文字化けする
    layout.addWidget(&button1);
    layout.addWidget(&button2);
    window.setLayout(&layout);

    // Button #1 : UTF16文字コードを直接セットする
    QString name1("UTF16 [");
    name1.append(QString(0x2462)); // ③
    name1.append(QString(0x2162)); // Ⅲ
    name1.append(QString(0xFF13)); // 3
    name1.append("]");
    button1.setText(name1);

    // Button #2 : iconvでUTF16に変換した文字列をセットする
    QString name2("");
    char in[] = "iconv [③Ⅲ3]"; // EUC-JP-MS
    char16_t out[128] = { 0 };    // UTF-16
    convert_to_unicode(in, strlen(in), (char*)out, sizeof(out));
    BOOST_FOREACH(auto c, u16string(out)) name2.append(QString(c));
    button2.setText(name2);

    window.show();
    return app.exec();
}

LANG=ja_JP.eucJP が前提の世界で物作りをしていると、UTF-8を前提としている最近のインフラとの間でミスマッチが起こりやすいのです。

WSL上で音楽を再生する

WSL上で音楽を再生するコマンド(例えばmplayer)がうまく動作しない。Ubuntu 16.04LTSの場合、ここに書いてある手順でライブラリを差し替えたらうまく動作するようになった。

  • libpulse0を差し替える(libpulse0_8.0-0ubuntu3.3ppa1_amd64.deb)。
  • Windowsホスト上のpulse serverを指定する(PULSE_SERVER=tcp:localhost)。

github.com

Ubuntu 18.04LTSではpulseaudioのベースバージョンが上がっていて16.04LTS用のバイナリは適合しない。18.04LTS用のlibpulse0バイナリは見つけられなかったので以下の手順で自作した。tarballやrpmのリビルドには慣れているけどdebをリビルドしたのは今回が初めてで、数日間試行錯誤した。変な手順になっているかもしれない。

  • WSL上でのリビルドには失敗したのでXubuntu 18.04LTSの仮想マシンを用意。
  • pulseaudioのビルドに必要なパッケージをインストール。
# apt install build-essential pbuilder
# apt install build-dep pulseaudio
  • gpgキーを作成して登録(gpg, apt-key)。
  • pulseaudioのソースパッケージをダウンロード。
$ apt source pulseaudio
  • debian/patches/pulseaudio-11.1-libpulse0-for-wsl.patchを追加。
--- pulseaudio-11.1/src/pulsecore/mutex-posix.c.org     2016-08-23 21:50:11.000000000 +0900
+++ pulseaudio-11.1/src/pulsecore/mutex-posix.c 2018-11-19 15:52:54.056000000 +0900
@@ -21,6 +21,8 @@
 #include <config.h>
 #endif

+#undef HAVE_PTHREAD_PRIO_INHERIT
+
 #include <pthread.h>
 #include <errno.h>
  • debian/patches/seriesの末尾に以下を追加。
# Windows Subsystem for Linux
pulseaudio-11.1-libpulse0-for-wsl.patch
  • pulseaudioをリビルドする。
$ debuild -S -rfakeroot -kXXX
$ debuild -rfakeroot -kXXX
  • リビルドしたlibpulse0パッケージをWSLにコピーして差し替え、更新を禁止する。
# dpkg -i libpulse0_11.1-1ubuntu7.1_amd64.deb
# apt-mark hold libpulse0

これでUbuntu18.04LTSでもmplayerが使えるようになった。

WSLのシステムコール実装/未実装を確認する

Windows Subsystem for Linux を使っていると、カーネル側の機能が無いためにうまく動かないコマンドが結構あるのが気になった。

# dmesg
dmesg: カーネルバッファの読み込みに失敗しました: 関数は実装されていません

# ipcrm -q 1
ipcrm: ID の処理に失敗しました: 関数は実装されていません

ということで、各システムコールの実装/未実装を調べてみる。適当な引数でシステムコールを叩いてみて、ENOSYSで失敗すれば未実装、そうでなければ実装と判断する。

#!/usr/bin/perl
use strict;

# カーネルバージョン
print `uname -a`;

# システムコール定義ファイルを読み込む
my %table = ();
open(DEFFILE, "< /usr/include/asm/unistd_64.h") or
  open(DEFFILE, "< /usr/include/x86_64-linux-gnu/asm/unistd_64.h") or
    die("open failed : $!");
while (my $line = <DEFFILE>) {
  if ($line =~ /^#define\s+__NR_(\S+)\s+(\d+)/) { $table{$2} = $1; }
}
close(DEFFILE);

# システムコールを発行して実装/未実装を判定する
foreach my $num (sort {$a <=> $b} keys(%table)) {
  my $name = $table{$num};
  print "[$num] $name ... ";
  if ($name =~ /^(vhangup|rt_sigreturn)$/) {
    print "SKIP (unsafe)\n";
    next;
  }

  # 子プロセス側でシステムコールを発行する
  my $pid = fork;
  if ($pid == 0) {
    local $SIG{ALRM} = sub { exit 222; };
    close(STDERR);
    alarm(1);
    if ($name eq 'brk') {
      syscall($num, 0);
    } else {
      syscall($num, -1);
    }
    exit $!;
  }

  # 子プロセスの終了ステータスを判定する
  waitpid($pid, 0);
  my $e = $? >> 8;
  if ($e == 0) {
    print "OK\n";
  } elsif ($e == 222) {
    print "TIMEOUT\n";
  } elsif ($e == 38) {
    print "ENOSYS\n";
  } else {
    print "FAIL ($e)\n";
  }
}

exit 0;

結局、現在のWSLでは以下のシステムコールが未実装と思われる。

Linux XXXXXXXX 4.4.0-17134-Microsoft #345-Microsoft Wed Sep 19 17:47:00 PST 2018 x86_64 x86_64 x86_64 GNU/Linux

[27] mincore
[68] msgget
[69] msgsnd
[70] msgrcv
[71] msgctl
[103] syslog
[129] rt_sigqueueinfo
[134] uselib
[136] ustat
[139] sysfs
[148] sched_rr_get_interval
[151] mlockall
[152] munlockall
[154] modify_ldt
[156] _sysctl
[159] adjtimex
[163] acct
[167] swapon
[168] swapoff
[172] iopl
[173] ioperm
[174] create_module
[175] init_module
[176] delete_module
[177] get_kernel_syms
[178] query_module
[179] quotactl
[180] nfsservctl
[181] getpmsg
[182] putpmsg
[183] afs_syscall
[184] tuxcall
[185] security
[187] readahead
[202] futex
[205] set_thread_area
[206] io_setup
[207] io_destroy
[208] io_getevents
[209] io_submit
[210] io_cancel
[211] get_thread_area
[212] lookup_dcookie
[214] epoll_ctl_old
[215] epoll_wait_old
[216] remap_file_pages
[227] clock_settime
[236] vserver
[237] mbind
[238] set_mempolicy
[239] get_mempolicy
[240] mq_open
[241] mq_unlink
[242] mq_timedsend
[243] mq_timedreceive
[244] mq_notify
[245] mq_getsetattr
[246] kexec_load
[248] add_key
[249] request_key
[250] keyctl
[256] migrate_pages
[261] futimesat
[277] sync_file_range
[278] vmsplice
[279] move_pages
[297] rt_tgsigqueueinfo
[300] fanotify_init
[301] fanotify_mark
[303] name_to_handle_at
[304] open_by_handle_at
[305] clock_adjtime
[312] kcmp
[313] finit_module
[314] sched_setattr
[315] sched_getattr
[316] renameat2
[317] seccomp
[319] memfd_create
[320] kexec_file_load
[321] bpf
[322] execveat
[323] userfaultfd
[324] membarrier
[325] mlock2
[326] copy_file_range
[327] preadv2
[328] pwritev2
[329] pkey_mprotect
[330] pkey_alloc
[331] pkey_free
[332] statx

syslogは実害があまり無いからいいとして、メッセージキューは実装してほしい。mlockall, munlockallはスタブでも構わないから受け付けてほしい。