コネタ。

https://github.com/ashinn/chibi-scheme/issues/302 make test-all` will fail at `tests/ffi/ffi-tests.scm` on Cygwin



yunibaseをCygwinに移植していてchibi-schemeのtest-allがCygwinで通らないのを見つけた。これは端的に言えばCygwinあるあるで、普通のLinuxでは普通に可能なことがCygwinではできないケースと言える。

Chibi-schemeの当該テストコードは、

FFI用のライブラリを .stub ファイルから .DLLにコンパイル ロード テスト 削除

をいくつかのライブラリに亘って繰り返している。このとき、1. の .stubから.DLLを生成する過程ではコンパイラを起動するためにfork()→exec()することになる。

( define-syntax test-ffi ( syntax-rules () (( test-ffi name-expr decls tests ... ) ( let* (( name name-expr ) ( stub-file ( string-append "tests/ffi/" name ".stub" )) ( c-file ( string-append "tests/ffi/" name ".c" )) ( lib-file ( string-append "tests/ffi/" name *shared-object-extension* ))) ( call-with-output-file stub-file ( lambda ( out ) ( write ' decls out ) ( newline out ))) ( let (( res ( system ★ DLLを生成 "./chibi-scheme" "tools/chibi-ffi" "-c" "-f" ( string-append "-O0 -L. -Iinclude" ( cond-expand ( boehm-gc " -DSEXP_USE_BOEHM=1 -I/opt/local/include" ) ( else "" ))) stub-file ))) ( cond (( zero? ( cadr res )) ★ ビルドに成功した？ ( let (( orig-failures ( test-failure-count ))) ( load lib-file ) ★ DLLをロード tests ... ★ テストを実行 ( cond (( = orig-failures ( test-failure-count )) ★ テストが成功なら中間ファイルを消す ( delete-file stub-file ) ( delete-file c-file ))) ( delete-file lib-file ))) ★ DLLを消去

このとき、chibi-schemeのFFIには(というか普通のFFIには)ライブラリのアンロードが無いため、プロセスのメモリ空間にはDLLが残ったままになってしまう。

Linux等の普通のPOSIXでは、ファイルが消えた状態でもfork()でファイルデスクリプタを複製するようなケースでは他のプロセスからもアクセスを継続できる。このため大きな問題にはならない。

しかし、Cygwinのfork()でできるプロセスはアドレスレイアウトを再現しただけの完全に新しいプロセスであるため、新しくファイルのopen()を行うのと等価になり ロードされていたDLLがopenできない → mmapできない → アドレス空間レイアウトを再現できない という流れでfork()に失敗してしまう。

0 [main] chibi-scheme 311940 child_info_fork::abort: unable to map C:\cygwin64\home\oku\repos2\chibi-scheme\tests\ffi\basic.dll, Win32 error 126

個人的にはこの手のCygwinのメッセージはあんまり信頼していないのでstraceコマンドで確認するようにしている。

15 64828174 [main] chibi-scheme 299940 frok::parent: copying data/bss of a linked dll 38 64828212 [main] chibi-scheme 299940 child_copy: linked dll data - hp 0x198 low 0x5CFFC7000, high 0x5CFFCCCC8, res 1 21 64828233 [main] chibi-scheme 299940 child_copy: linked dll bss - hp 0x198 low 0x5CFFD7000, high 0x5CFFD7230, res 1 15 64828248 [main] chibi-scheme 299940 child_copy: done 14 64828262 [main] chibi-scheme 299940 resume_child: signalled child 419 64828264 [main] chibi-scheme 302196 sync_with_parent: awake 24 64828286 [main] chibi-scheme 299940 child_info::sync: n 2, waiting for subproc_ready(0x150) and child process(0x198) 27 64828291 [main] chibi-scheme 302196 sync_with_parent: no problems 11 64828302 [main] chibi-scheme 302196 frok::child: child is running. pid 302196, ppid 299940, stack here 0x23C3C8 11 64828313 [main] chibi-scheme 302196 frok::child: hParent 0x178, load_dlls 1 66 64828379 [main] chibi-scheme 302196 child_info_fork::abort: unable to map C:\cygwin64\home\oku\repos2\chibi-scheme\tests\ffi\basic.dll, Win32 error 126 --- Process 302196 thread 302200 exited with status 0x800000 --- Process 302196 exited with status 0x800000 12276 64840562 [waitproc] chibi-scheme 299940 pinfo::maybe_set_exit_code_from_windows: pid 302196, exit value - old 0x0, windows 0x800000, cygwin 0x8000000 151 64840713 [main] chibi-scheme 299940 child_info::sync: pid 302196, WFMO returned 1, exit_code 0x800000, res 0 121 64840834 [waitproc] chibi-scheme 299940 sig_send: sendsig 0x84, pid 299940, signal 20, its_me 1 83 64840917 [main] chibi-scheme 299940 frok::parent: returning -1 83 64841000 [waitproc] chibi-scheme 299940 sig_send: Not waiting for sigcomplete. its_me 1 signal 20 70 64841070 [main] chibi-scheme 299940 sig_send: sendsig 0x84, pid 299940, signal -73, its_me 1 116 64841186 [sig] chibi-scheme 299940 sigpacket::process: signal 20 processing 117 64841303 [main] chibi-scheme 299940 sig_send: wakeup 0x110

... frok??

最近のCygwinにはposix_spawn()が有るためそれを使うのが望ましい気もするが、Cygwinのposix_spawnは導入が本当に最近(2013年)なので微妙かもしれない。