Help us understand the problem. What is going on with this article?

OpenCV 4.0 + CUDA付きでビルドすると、unresolved external "__declspec(dllimport) bool cv::__termination" と表示される

More than 1 year has passed since last update.

はじめに

  • これは、OpenCV Advent Calendar 2018 4日目の記事です。
  • 関連記事は目次にまとめられています。
  • 昨日に続き手島が担当します。
  • なお、本記事は筆者個人の意見であり、筆者の所属組織とは無関係です。

TL;DR

  • 書きかけで公開します。 修正しました。
  • OpenCVには長らくプログラム終了時にUMatの二重解放問題が潜んでいて、OpenCV 4.0で正式に対応が入ったが、そのせいで特定条件下でCUDA付きビルドがコケることになった。
  • 具体的にはWindowsでCUDA付きでビルドしようとすると、unresolved external "__declspec(dllimport) bool cv::__termination"が発生する
  • 次リリースでは直されるが、それまでは cmake時に-DOPENCV_SKIP_DLLMAIN_GENERATION=ON をつける
  • (2018年12月26日追記)OpenCV 4.0.1がリリースされましたので、そちらでは本件修正されております

OpenCV 4.0 のリリース

OpenCV 4.0 のリリースで、個人的に最大の変更点はCUDA関連のモジュールがopencv_contribに移動になったことです。(なお、繰り返しになりますが、あくまで本記事は筆者個人の意見であり、筆者の所属組織とは無関係です)
OpenCV 4.0 をソースからCUDA付きでWindows上でビルドしようとすると、重大な罠が潜んでいますので、そちらの紹介を。

そもそも何が起きるのか

プログラムの終了時に二重解放による実行時エラーが発生するのですが、終了間際であること、確実に発生するわけではないこと、などの理由により、普段は気づかないと思います。

始まりのものがたり

手島が問題を踏み抜く

    Start 9: opencv_test_imgproc
1/1 Test #9: opencv_test_imgproc ..............***Exception: SegFault137.81 sec
  • デバッガ付きで実行して初めて分かったのですが、終了時に確かに実行時エラーが起きるのです。
    • テストプログラム事態には何ら問題が無いため、一見エラーには気づきません
    • 通常の、デバッガなしで実行した場合は、実行時エラーがプログラムの終了がほぼ同時のため、見た目には実行時エラーに気づ くことはほとんどありません。
    • しかし、ctestから実行した場合、ctest側でsegmentation faultを検知できるので明示的にSegmentation Faultが表示される訳です。

コメントをもらう

  • はじめはstaticを外せば発生が免れるので、「staticを外す」PRを出しました
  • しかし、staticに設定されている配列は、LUTを始めとする、定数の置き場でありました。
  • staticがついている場合は最初に計算されて保持されているわけですが、staticでなくなると、関数呼び出し毎にLUTを構築することになり、LUTの意味を成しません。
  • また、OpenCV メイン開発者の一人である、alalek先生から以下のコメントをもらいました。

Looks like you catched:

  • termination order fiasco: OpenCL context is destroyed / corrupted before allocated buffers.
  • or Win32 ExitProcess terrible behavior - it terminates all non-main threads unconditionally - this usually corrupt multi-threading processes / libraries (deadlock is one way).
  • Windows側の問題か、OpenCLプラットフォーム(ドライバ?)側の問題か分からないけれど、二重解放が発生しているよ、と教えてくれました。
  • OpenCL用の配列、UMatは、coreモジュールで管理されているのですが、UMat用のLUTはimgprocで管理されています。
  • imgprocの終了処理とcoreモジュールの終了処理は順序が不定で、場合によっては両方のモジュールから解放が発生するが、2度目の解放では既に領域が存在しないため、エラーになる、と。
  • 余談ですがこのalalek先生、本当に何でも知っていて、PR出す度に私が成長することになるほど、圧倒的にいろんなことを知っている方です。1

@alalek, you really know everything.

alalek先生が修正してくれる

  • 幾度かのやり取りの後、alalek先生自身が、OpenCVの各モジュールにDllMainを追加するPRを出しました。
  • cmake: add DllMain() into each OpenCV DLL by alalek · Pull Request #12791 · opencv/opencv
  • このPRのポイントは、
    • 終了処理を各モジュールごとに関数でまとめる
    • coreモジュールだけが参照していた__terminationフラグをモジュール間で共有することでUMatの二重解放を回避する
  • 点にあります。
  • 見ると分かりますが、テンプレートファイル1つをCMakeで見事に操って、最低限の修正で各モジュールにDllMainを追加しています。
  • 余力があればどこかでOpenCVで使われている華麗なCMakeのテクニックを紹介したいと思います。

が、結果としてWindows + CUDA ビルドがコケることに

  • build failure (link) with cudev in 4.0.0-beta · Issue #12865 · opencv/opencv
  • alalek先生のPR#12791により、私のissue#12750 は無事解決されました。
  • が、ここにcudevモジュールが関係してきます。
  • 8日目の記事に紹介しますが、cudevモジュールはcoreモジュールに依存していない、不思議なモジュールなのです。なので、__terminationフラグをcudevモジュールから参照しようとしても、coreモジュールに依存していないため、参照できません。
  • めちゃくちゃ不思議な関係なのですが、どうやらcudevモジュールとはそういうモジュールのようです。
  • で、extern付きで宣言したものの、実体とリンクできないため、表題のエラーが発生する訳です。
 unresolved external "__declspec(dllimport) bool cv::__termination" in DllMain

4.0のリリースと修正

迂回方法

  • (2018年12月26日追記)繰り返しになりますが、2018年12月22日(日本時間)にOpenCV 4.0.1がリリースされましたので、そちらを使えば以下迂回作業も不要です。
  • WindowsでCMakeする際にOPENCV_SKIP_DLLMAIN_GENERATION=ONを指定します。
  • Windowsではコマンドラインでcmakeを叩く人は少ないと思いますが、一応PATHを通したりフルパスを叩けば普通にcmakeが使えます。
cmake -DOPENCV_SKIP_DLLMAIN_GENERATION=ON ...
  • なお、この修正はLinuxでは必要ありません。
  • また、OpenCVをstaticライブラリとしてリンクした場合も本件は発生しません。
  • 各モジュール間で__terminationを共有するのはWindowsでの実装なので、LinuxやMacではリンカエラーは起きません

参考

終わりに

  • とりあえず差し替えました。
  • 明日は @hon_no_mushi さんの投稿でRISC-Vだとぉ!?楽しみですね!
  • 最後に、繰り返しになりますが、あくまで本記事は筆者個人の意見であり、筆者の所属組織とは無関係です

  1. "Not everything, I know only what I know"って返してくれたらホチキスとか蟹とかに出会えそうでしたが、そういうリプライはありませんでした 

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away