Fiddle で C のメモリを free した時にやらかした話
備忘録。
少し前に Fiddle を使って C 関数を呼び出す Ruby プログラムを書いた時にやらかした時のメモ。
Fiddle とは
とてもざっくりというと dlopen/dlsym のラッパー。以下は C の strlen を Ruby から呼び出す例がドキュメントにある。
起きたこと
以下のような C ライブラリを Ruby から呼び出す必要があった。
// ライブラリが提供する構造体 struct foo_t; int main(void) { // ライブラリ内で malloc & 初期化した foo_t を取得 foo_t *foo = new_foo(); // foo を介してライブラリの機能を使う work_with_foo(foo); // アプリケーション側の責務として取得した foo を free で開放する必要がある free(foo); }
これを Ruby から Fiddle 経由で使うために(だいぶ簡易化しているが)以下のようなコードを書いた。((実際には Fiddle::Importer を使った))
require 'Fiddle' # libfoo のラッパー module LibFoo extend Fiddle::Importer dlload 'libfoo.so' extern 'void *new_foo(void)' extern 'void work_with_foo(void *)' end # free を呼び出すための libc のラッパー module LibC extend Fiddle::Importer dlload 'libc.so.6' extern 'void free(void *)' end # foo ライブラリを利用する foo = LibFoo.new_foo LibFoo.work_with_foo(foo) # free で開放 LibC.free(foo)
動きそうだが、最後の LibC.free の呼び出しで abort してしまった。
何が原因だったか
abort する Ruby プロセスを確認したところ、malloc/free が libc から libjemalloc に置き換わっていることがわかった(LD_PRELOAD を使っていた)。つまり以下のような動きになっていた。
LibFoo.new_foo内部のmalloc呼び出しはプログラムがリンクしている libjemalloc のmallocが呼び出されるLibC.freeは明示的に libc のfreeが呼び出される
つまり jemalloc が管理しているメモリを libc の free で解放しようとして死んでいた。
どうするべきか/どうあるべきか
実際にこの問題に遭遇した時はプログラムが libc と libjemalloc のどちらを使っているかを判断して、Fiddle でオープンする共有ライブラリを使い分けるようにした。
Fiddle.free というそれっぽい関数もあるが、これは Fiddle.malloc で確保したメモリにしか使ってはならないので断念。(おそらく Ruby インタプリタ内部のメモリ使用量管理と絡んでいる?)
そもそもの話として「アプリケーションコードに free 呼び出す責務がある」というライブラリ側の仕様が微妙な気もする。「開放処理も delete_foo などでラップする」「ライブラリ内部で使うアロケータを利用者が指定できるようにする」などの仕様の方がいいかもしれない。