none
::LoadLibrary()を用いずに自前でロードしたDLLをデバッグする方法について RRS feed

  • 質問

  • ::LoadLibrary()を用いずに自前でDLLをロードし、プロセス固有のモジュールリストに登録されないので自前のモジュールリストで管理しています。
    動作は問題ないのですが、ロードするDLLのコードにブレークポイントを設定しても実行が停止しなくて困っています。
    __asm int 3 にてブレークポイントを設定した場合でもブレークポイント例外ウィンドウは表示されますが、中断を選択してもロードしたDLLのコードが表示されません。

    システムを介さないDLLロードなので対応した.PDBが読み込まれないからでしょうか?
    デバッガーのメカニズムは殆ど知らないのですが原因や解決策をご助言いただきたいです。
    宜しくお願い致します。

    【開発環境】
    Windows7SP1
    VisualC++ 2015 Community(ローカルWindowsデバッガー)

    【確認した事】
    デバッグ情報形式 /Zi
    フレームポインタ /Oy-
    最適化 /od
    2016年9月23日 6:23

すべての返信

  • 回答ではありません。あしからず。

    (1)__asm int 3より  __debugbreak() の方が良いかもしれません。
    (2)「デバッグ」メニューの「プロセスにアタッチ(P)」で表示されるDLGの
      「選択可能なプロセス」の一覧に「ない」もののデバッグは多分できません。
    (3)pdbとdllは同じフォルダに置かないとだめかもしれません。
    (4)・・・無いとは思いますが、
      dllとexeの名称が同じ場合、先に生成されたdllのpdbが、
      後で生成されるexeのpdbで上書きされてしまいます(警告されません)。
      当然dllのデバッグは不可能となります。

    以上、思いついたまま並べてみました。

    2016年9月23日 8:20
  • デバッガーが自動的に読み込むことができるシンボルは、モジュールリストに表れているものだけです。それ以外はDLLを読み込んだアドレスが不明ですからシンボルを特定できないのは自明です。

    その上で、WinDbgであれば.reloadコマンドで明示的にアドレスを指定してシンボルを読み込ませることができます。

    2016年9月23日 8:37
  • LoadLibrary() API を使用せずに自前でロードした DLL のコードを、どのように仮想メモリ上にマップしているのかがわからないので、何とも言えませんが。。。
    私の (足りない脳みその) 理解では、デバッガがシンボル ファイルを読み込んでくれるのは、CS レジスタ (コード セグメント) で管理されるバイナリ (コード) に対してだけのような気が。。。
    もし自前でロードした DLL のコードを、SS レジスタ (スタック セグメント) や DS, ES, FS, GS 等のレジスタ (データ セグメント) で管理される仮想メモリ上にマップしてるのであれば、ソース コード レベルでのデバッグは難しいと思います。
    自前でロードした DLL のコードを、CS レジスタ管理下の仮想メモリ上にマップしている場合でも、すべてのバイナリ コードをマップしていない、つまり自前 DLL の一部を部分的にマップししているような場合は、やっぱりシンボル ファイルは正しく読み込まれないと思います。

    で、無理だったらどーするか。。。
    とりあえず思いつくのは、コンパイル オプション "/FAs" でリスティング ファイルを生成させ、cod ファイル内に記録されたソース コードとアセンブリ コードの対応リストを見ながら頑張る。。。ぐらいしか思い浮かばない。
    ---------------------------------
    /FA、/Fa (リスティング ファイル)
    https://msdn.microsoft.com/ja-jp/library/367y26c6.aspx?f=255&MSPPError=-2147217396
    ---------------------------------

    2016年9月23日 9:28
  • デバッガに詳しいわけでもないですが ...

    LoadLibrary 使わず自前ロードという話なので、デバッガにモジュールシンボル情報を渡せば済むとは思えませんね。
    素直にアセンブラレベルでのデバッグしかないのではないでしょうか?

    「DLL のコードにブレークポイントを設定しても」というのは C ソース上ですよね?

    アドレスレベルデバッグで、自前ロードしたアドレスにブレイクポイントを設定すれば停止できませんか?
    どこがデバッグしたい場所なのかを調べるのも お馬鹿 様の言ってるような手段が必要ですけど、デバッガ制御の情報を募ったり探すよりは現実的かと ...。

    2016年9月24日 7:14
  • ご助言ありがとうございます。
    お礼が遅くなってしまい申し訳ありません。
    4項目とも確認いたしました。
    2016年9月26日 7:01
  • ご助言ありがとうございます。
    お礼が遅くなってしまい申し訳ありません。
    WinDbgの使い方は学習中ですが.reload /fで試してしみようと考えています。
    2016年9月26日 7:32
  • ご助言ありがとうございます。
    お礼が遅くなってしまい申し訳ありません。

    newで確保した領域にDLLファイルを読み込んで加工 → CMemFileにアタッチ →
    CreateFileMapping() → MapViewOfFile()とマッピングしています。

    newで確保した領域をマッピングしているのでデータセグメントという事になるでしょうか?
    どの様にすればコードセグメントになるのでしょうか?
    ロード時にVirtualProtect()でセグメント毎にPAGE_EXECUTEなどのプロテクトはしています。
    2016年9月26日 10:39
  • ご助言ありがとうございます。
    お礼が遅くなってしまい申し訳ありません。
    4項目とも確認いたしました。
    2016年9月26日 10:40
  • 「DLL のコードにブレークポイントを設定しても」というのは C ソース上ですよね?

    ご助言ありがとうございます。<br>
    お礼が遅くなってしまい申し訳ありません。<br>
    ソースコードレベルでのデバッグを希望でしていますが難しい場合は、<br>
    Debug構成の時だけ::LoadLibraryを用いようかと思っています。<br>
    2016年9月26日 10:52
  • 前スレッドでも若干感じましたが、質問者さんの求めていることが身の丈に合っていないという自覚はあるのでしょうか?

    前回と同様で、今回も先に回答したように技術的には可能です。ただし、実現するにはVisual StudioデバッガーではなくWinDbgを使用する必要がある、それだけのことです。

    2016年9月26日 22:08
  • > どの様にすればコードセグメントになるのでしょうか?
    自分で試したことが無いのでわかりません。
    ただ「newで確保した領域にDLLファイルを読み込んで」とのことから、データ セグメントになっているのでは。。。と思います。
    (プロセス ヒープやプライベート ヒープが、コード セグメント上に確保されるとは思えないので。)
    コード セグメントとしてメモリを割り当てたいのであれば、VirtualAllocEx() API を使うべきでは。。。(試した訳ではないので、正しいかどうかはわかりません。)

    先の返信にも書きましたが、自前でロードした DLL ファイルは分割せずに、連続した1つのブロックの仮想メモリ上にマップされているのでしょうか?
    分割していたり、使用するルーチン毎に部分的にロードしている場合は、シンボル ファイルとのマッチングができないので、無理だと思います。
    デバッガ上で確認すればわかることですが、任意のプロセス空間内にロードされる exe, dll 等のバイナリ ファイルは、分割せずに一括して仮想メモリ上にマップされいます。
    そしてデバッガは、個々のファイルのファイル ヘッダー等の情報から、対応するシンボル ファイルを識別しているようです。
    なので、exe, dll 等のバイナリ ファイルのファイル ヘッダーに記録されている、各ルーチンへのオフセット情報が一致しない限り、シンボル ファイルは認識されないと思います。

    例えば。。。
    マルウェア等がよく使う手法として、Code Injection による API Hook がありますが、この場合、仮に該当 DLL のシンボル ファイルが存在したとしても、一致させることはできません。
    シンボル ファイルが無い状態でデバッガで任意のスレッドのコール スタックを確認すると、個々のフレームは「モジュール名+先頭からのオフセット値」で表示されます (シンボルが一致していれば、「<モジュール名>!<ルーチン名>+オフセット値」) が、Code Injection 部分がコールスタックに介在する場合、モジュール名は表示されず (仮想アドレス空間の) アドレス値しか表示してくれません。
    これは該当コード部分を、バイナリ ファイルにより提供されているコードと認識出来ないためだと考えられます。
    Visual Studio 2015 でもコールスタックは確認できると思いますが、自前でロードした DLL 内のルーチンがコールされたときのコール スタックで、その「自前でロードした DLL 名」が表示されないのであれば、WinDBG を使っても無理だと思います。

    ちなみに。。。
    Visual Studio 2015 でも ".reload" コマンドはサポートされていると思います。
    以前 Visual Studio 2013 で、ライブ デバッグ (カーネル モード) やダンプ解析 (プロセス ダンプ/カーネル ダンプ) を行ったとき、フツーに ".reload" コマンド 等のコマンドは使えていました。
    もっとも、Visual Studio は重すぎたので、すぐに WinDBG に戻っちゃいましたけど。
    エディションの違いとかで、違いがあるのかもしれませんが。。。


    • 編集済み お馬鹿 2016年9月27日 9:51 一部修正
    2016年9月27日 3:09