windows node.js

node.js(Windows版)を自前ビルドする

謎のクラッシュ(FFIでネイティブプラグインがアクセス違反で死ぬ)を追跡するため、 VCのデバッガをアタッチするべく自前ビルドする。

node-v5.3.0.tar.xz

7zipで解凍。 python2系にpathを通してvcbuild.batをたたく。

> vcbuild.bat

v8もlibuvも全部入りらしくあっさりとほぼビルドできた。

SignTool Error: No certificates were found that met all the given criteria.
Failed to sign exe

document

https://github.com/nodejs/node-v0.x-archive/wiki/Installation#building-on-windows

debugビルドの作り方はこう。

> vcbuild.bat nosign debug

nodeをデバッグ実行

  • node-v5.3.0にnode.slnができているのでこれをVisualStudio2015で開く。
  • nodeをスタートアッププロジェクトに指定
  • nodeの設定のdebugタブで、カレントディレクトリとコマンドライン引数を指定
  • f5
例外が再現した
void CallbackInfo::DispatchToV8(callback_info *info, void *retval, void **parameters, bool dispatched) {
  Nan::HandleScope scope;

  static const char* errorMessage = "ffi fatal: callback has been garbage collected!";

  if (info->function == NULL) {
    // throw an error instead of segfaulting.
    // see: https://github.com/rbranson/node-ffi/issues/72
    if (dispatched) {
        Local<Value> errorFunctionArgv[1];
        errorFunctionArgv[0] = Nan::New<String>(errorMessage).ToLocalChecked();
        info->errorFunction->Call(1, errorFunctionArgv);
    }
    else {
      Nan::ThrowError(errorMessage);
    }
  } else {
    // invoke the registered callback function
    Local<Value> functionArgv[2];
    functionArgv[0] = WrapPointer((char *)retval, info->resultSize);
    functionArgv[1] = WrapPointer((char *)parameters, sizeof(char *) * info->argc);
    Local<Value> e = info->function->Call(2, functionArgv); // <-- ここ。functionがnullptrになってるよ!
    if (!e->IsUndefined()) {
      if (dispatched) {
        Local<Value> errorFunctionArgv[1];
        errorFunctionArgv[0] = e;
        info->errorFunction->Call(1, errorFunctionArgv);
      } else {
        Nan::ThrowError(e);
      }
    }
  }
}

ううむ。if文でnullチェックしてるのにelse側に落ちてるな。矢張りスレッドとかGCが絡んでいそうな気がするぞ。 ちょうどnode-ffiでjavascriptのコールバックを呼び出しているところなのではないか。

コールスタック
>   ffi_bindings.node!CallbackInfo::DispatchToV8(_callback_info * info, void * retval, void * * parameters, bool dispatched) 行 73   C++
    ffi_bindings.node!CallbackInfo::WatcherCallback(uv_async_s * w, int revents) 行 94   C++
    node.exe!uv_process_async_wakeup_req(uv_loop_s * loop, uv_async_s * handle, uv_req_s * req) 行 97    C

よく見るとまさにffi_bindings.node(node-ffiのネイティブプラグイン)内で問題が発生している。 呼び出しスレッドはメインスレッドか・・・。 何回も試してみると例外がむしろdllのスレッド側で起こることの方が多い。 他にWaitSingleObjectがstdcallの関数をスレッド実行しているものが4本。 ひょっとして、コールバックの頻度が高すぎなんじゃないのか・・・。

callback_infoはffiの構造体。こいつのデストラクタが呼ばれているのが確認できれば手がかりになるな。 で、callback_info->functionを解放する下記の部分がメインスレッドから呼ばれて、その後に スレッドからのコールバック呼び出しがクラッシュしているらしいことを発見。

node_modules/ffi/src/callback_info.cc
/*
 * Called when the `ffi_closure *` pointer (actually the "code" pointer) get's
 * GC'd on the JavaScript side. In this case we have to unwrap the
 * `callback_info *` struct, dispose of the JS function Persistent reference,
 * then finally free the struct.
 */

void closure_pointer_cb(char *data, void *hint) {
  callback_info *info = reinterpret_cast<callback_info *>(hint);
  // dispose of the Persistent function reference
  delete info->function;
  info->function = NULL;
  // now we can free the closure data
  ffi_closure_free(info);
}
コールスタック
>   ffi_bindings.node!closure_pointer_cb(char * data, void * hint) 行 42 C++
    node.exe!node::Buffer::CallbackInfo::WeakCallback(v8::Isolate * isolate, v8::Local<v8::Object> object) 行 159    C++

     省略

    node.exe!v8::internal::Heap::CollectGarbage(v8::internal::AllocationSpace space, const char * gc_reason, const v8::GCCallbackFlags callbackFlags) 行 556 C++

     省略

    node.exe!node::Buffer::New(v8::Isolate * isolate, char * data, unsigned int length, void (char *, void *) * callback, void * hint) 行 347    C++
    binding.node!`anonymous namespace'::WrapPointer(char * ptr, unsigned int length) 行 147  C++
    binding.node!`anonymous namespace'::ReadPointer(const Nan::FunctionCallbackInfo<v8::Value> & info) 行 261    C++
    binding.node!Nan::imp::FunctionCallbackWrapper(const v8::FunctionCallbackInfo<v8::Value> & info) 行 174  C++

なんか、GCが発動して回収されちゃっているぽい?

2015/09/20 * Nodejs GC is crashing when a C function is returning NULL

むしろこっちか

2015/11/05 * Node.js v5.0.0でnode-ffiやNodObjCが動かない

しかし、うちのnode.jsは

> node --version
v5.3.0

うむ。

むしろこっちか 2015/09/15 * ffi.Function underlying Callback is garbage collected

死ななくなった。これっぽいな。

64bit版で試してみる

これはこれで死ぬ。

まとめ

  • node-ffiのFunctionでGCに回収されてしまう書き方のところがある(FFI.Function内)
  • 32bit版のnode-ffiではSTDCALLを指定できる(ABI=2)
  • 64bit版のnode-ffiではIA64にされるのでSTDCALLは動かぬ

gypでのIA64とX64の扱いの違いがわかったら修正できるかな。 X86_WIN32の時にFFI_STDCALLを定義する修正をプルリクしてみよう。