2009年07月14日
Ubuntuのmingw32のsnprintfのバグ
Ubuntuのmingw32のsnprintfのバグを見つけて修正しました。
どうやら既知の問題だったようですが、もしかしたら役に立つかもしれないのでここにメモを残します。
mingw32とwineについて
Ubuntuのmingw32パッケージはWindows用のクロスコンパイルツールです。このコンパイラを使うとUbuntu上でWindowsアプリをビルドすることができます。
WineはWindowsアプリを動かすための互換レイヤです。全てのWindowsアプリを動かすことができるわけではありませんが、クロスコンパイルしたものをちょっと確認するには重宝します。
$ cat hello.c #include <stdio.h> main() { printf("Hello, world\n"); } $
$ gcc -o hello hello.c $ file hello hello: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), for
GNU/Linux 2.6.8, dynamically linked (uses shared libs), not stripped $ ./hello Hello, world $
Ubuntuではmingw32とwineのインストールは以下のコマンドでできます。
$ sudo apt-get install mingw32 wine
さきほどと同じソースファイルをクロスコンパイルしてWindowsの実行ファイルを作ってみます。
$ i586-mingw32msvc-gcc -o hello.exe hello.c $ file hello.exe hello.exe: MS-DOS executable PE for MS Windows (console) Intel 80386 32-bit $ ./hello.exe Hello, world $
hello.exe はMS-DOSの実行ファイルですが、WineのおかげでUbuntu上で実行することができました。
mingw32でのsnprintfのバグ
さて、このクロスコンパイラを便利に使っていたのですが、あるプログラムだけどうも動作がおかしくなることに気が付きました。そのプログラムが生成するファイルの内容が壊れているのです。デバッガで丸一日追いかけた結果、どうもsnprintfの動作がおかしいということが判明しました。
問題切り分けのために、単純なsnprintfのテストプログラムを試してみました。
$ cat snprintf0.c #include <stdio.h> main() { char message[20]; snprintf(message, sizeof(message), "%ld", (long)0); puts(message); } $
このプログラムをコンパイルして動かしてみます。
まずは、普通にgccで。
$ gcc -o snprintf0 snprintf0.c $ file snprintf0 snprintf0: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), for GNU/Linux 2.6.8, dynamically linked (uses shared libs), not stripped $ ./snprintf0 0 $
正しく0が表示されました。
今度はmingw32でやってみます。
$ i586-mingw32msvc-gcc -o snprintf0.exe snprintf0.c $ file snprintf0.exe snprintf0.exe: MS-DOS executable PE for MS Windows (console) Intel 80386 32-bit $ ./snprintf0.exe 4831803848261632 $
0ではなくて変な数字が表示されてしまいました。
snprintf0.exe を実際にWindowsにコピーして実行してみましたが正しくは実行できませんでした。なのでWineではなくmingwクロスコンパイラの問題だと思われます。
クロスコンパイラで使われるライブラリのソースを取り出してきて、snprintfのところを見てみると、いきなり明らかなバグを見つけてしまいました。
case 'l': if (fmt[0] == 'l') { fmt++; len = LEN_LL; } else len = LEN_LL; goto fmtloop;
このif文のelseのところはlen = LEN_LL でなくて LEN_L の誤りです。
パッチにすると以下のような感じ。
--- mingw_snprintf.c.org 2009-07-08 17:05:01.000000000 +0900 +++ mingw_snprintf.c 2009-07-08 17:05:24.000000000 +0900 @@ -465,7 +465,7 @@ len = LEN_LL; } else - len = LEN_LL; + len = LEN_L; goto fmtloop; case 'L': flag_ld++;
修正方法
Ubuntuのソースパッケージを持ってきて、ソースを修正してビルドし、必要なライブラリだけを入れ替えてみたところ問題を解決することができました。
以下に私のやった手順をあげておきます。
まずビルドに必要になるツールをインストールしておきます。
$ sudo apt-get build-dep mingw32-runtime $ sudo apt-get install fakeroot
テンポラリなディレクトリにソースを展開します。
$ mkdir tmp $ cd tmp $ apt-get source mingw32-runtime
ソースパッケージの中のアーカイブを展開します。
$ cd mingw32-runtime-3.13/ $ debian/rules unpack-stamp
ここでソースの修正をします。
$ pushd build_dir/src/mingw-runtime-3.13-20070825-1/mingwex/gdtoa/ ** エディタでソースを修正する ** ** 修正点の確認 ** $ diff -u mingw_snprintf.c.org mingw_snprintf.c --- mingw_snprintf.c.org 2009-07-13 14:16:46.000000000 +0900 +++ mingw_snprintf.c 2009-07-13 14:17:08.000000000 +0900 @@ -465,7 +465,7 @@ len = LEN_LL; } else - len = LEN_LL; + len = LEN_L; goto fmtloop; case 'L': flag_ld++; $ $ popd
tmp/mingw32-runtime-3.13のディレクトリでビルドします。
$ debian/rules build $ fakeroot debian/rules install
修正したsnprintfが含まれているライブラリを確認します。
$ cd debian/mingw32-runtime/usr/i586-mingw32msvc/lib $ i586-mingw32msvc-nm libmingwex.a |grep snprintf mingw_snprintf.o: 00001d00 T ___mingw_snprintf 00000170 T ___mingw_vsnprintf 00001d00 T _snprintf 00000170 T _vsnprintf
snprintfが含まれているライブラリだけをシステムにコピーします。
$ sudo mv /usr/i586-mingw32msvc/lib/libmingwex.a /usr/i586-mingw32msvc/lib/libmingwex.a.old $ sudo cp libmingwex.a /usr/i586-mingw32msvc/lib/
さきほどのテストプログラムで確認してみます。
$ cd ~/work $ i586-mingw32msvc-gcc -o snprintf0.exe snprintf0.c $ ./snprintf0.exe 0 $
治りました。
せっかくなのでこの修正のパッチを投げようと思ったのですが、すでに報告されていました。
https://bugs.launchpad.net/ubuntu/+source/mingw32-runtime/+bug/242089
しかし、現在(2009/7/13)のところ、まだこの修正は反映されていません。Ubuntu 9.04でもバグ有のままです。このバグで困っている方の参考になれば幸いです。