デバッガーで .NET 言語ステップを正しく実行するには質問する

デバッガーで .NET 言語ステップを正しく実行するには質問する

まず、質問が長くて申し訳ありません。

私はアイアンスキーム最近、私は「ネイティブ」.NET デバッガーを使用できるように、適切なデバッグ情報を出力することに熱心に取り組んでいます。

これは部分的には成功していますが、いくつかの初期的な問題に直面しています。

最初の問題は、ステップに関連しています。

Scheme は式言語であるため、ステートメント (または行) ベースであると思われる主要な .NET 言語とは異なり、すべてが括弧で囲まれる傾向があります。

元のコード (Scheme) は次のようになります。

(define (baz x)
  (cond
    [(null? x) 
      x]
    [(pair? x) 
      (car x)]
    [else
      (assertion-violation #f "nooo" x)]))

意図的に各式を改行して配置しました。

出力されたコードは C# に変換され (ILSpy 経由)、次のようになります。

public static object ::baz(object x)
{
  if (x == null)
  {
    return x;
  }
  if (x is Cons)
  {
    return Builtins.Car(x);
  }
  return #.ironscheme.exceptions::assertion-violation+(
     RuntimeHelpers.False, "nooo", Builtins.List(x));
}

ご覧の通り、とてもシンプルです。

注: コードが C# の条件式 (?:) に変換された場合、全体が 1 つのデバッグ ステップになることに注意してください。

以下はソースと行番号を含む IL 出力です。

  .method public static object  '::baz'(object x) cil managed
  {
    // Code size       56 (0x38)
    .maxstack  6
    .line 15,15 : 1,2 ''
//000014: 
//000015: (define (baz x)
    IL_0000:  nop
    .line 17,17 : 6,15 ''
//000016:   (cond
//000017:     [(null? x) 
    IL_0001:  ldarg.0
    IL_0002:  brtrue     IL_0009

    .line 18,18 : 7,8 ''
//000018:       x]
    IL_0007:  ldarg.0
    IL_0008:  ret

    .line 19,19 : 6,15 ''
//000019:     [(pair? x) 
    .line 19,19 : 6,15 ''
    IL_0009:  ldarg.0
    IL_000a:  isinst [IronScheme]IronScheme.Runtime.Cons
    IL_000f:  ldnull
    IL_0010:  cgt.un
    IL_0012:  brfalse    IL_0020

    IL_0017:  ldarg.0
    .line 20,20 : 7,14 ''
//000020:       (car x)]
    IL_0018:  tail.
    IL_001a:  call object [IronScheme]IronScheme.Runtime.Builtins::Car(object)
    IL_001f:  ret

    IL_0020:  ldsfld object 
         [Microsoft.Scripting]Microsoft.Scripting.RuntimeHelpers::False
    IL_0025:  ldstr      "nooo"
    IL_002a:  ldarg.0
    IL_002b:  call object [IronScheme]IronScheme.Runtime.Builtins::List(object)
    .line 22,22 : 7,40 ''
//000021:     [else
//000022:       (assertion-violation #f "nooo" x)]))
    IL_0030:  tail.
    IL_0032:  call object [ironscheme.boot]#::
       'ironscheme.exceptions::assertion-violation+'(object,object,object)
    IL_0037:  ret
  } // end of method 'eval-core(033)'::'::baz'

注記:デバッガーがメソッド全体を単純に強調表示しないようにするために、メソッドのエントリ ポイントを 1 列の幅にします。

ご覧のとおり、各式は行に正しくマッピングされます。

次に、ステップ実行に関する問題を示します (VS2010 でテストしましたが、VS2008 でも同じ/類似の問題が発生します)。

これらはIgnoreSymbolStoreSequencePoints適用されません。

  1. null 引数で baz を呼び出すと、正常に動作します。(null? x) の後に x が続きます。
  2. Cons 引数を使用して baz を呼び出すと、正常に動作します。(null? x)、次に (pair? x)、次に (car x)。
  3. 他の引数で baz を呼び出すと失敗します。(null? x)、次に (pair? x)、次に (car x)、次に (assertion-violation ...)。

適用する場合IgnoreSymbolStoreSequencePoints(推奨):

  1. null 引数で baz を呼び出すと、正常に動作します。(null? x) の後に x が続きます。
  2. Cons 引数で baz を呼び出すと失敗します。(null? x) の次に (pair? x)。
  3. 他の引数で baz を呼び出すと失敗します。(null? x)、次に (pair? x)、次に (car x)、次に (assertion-violation ...)。

また、このモードでは、一部の行 (ここには表示されていません) が誤って強調表示され、1 行ずれていることがわかります。

原因として考えられるものをいくつか挙げます。

  • 末尾呼び出しはデバッガーを混乱させる
  • 重複した場所(ここでは示されていない)はデバッガーを混乱させます(ブレークポイントを設定するときに非常によく起こります)
  • ????

2 番目ですが、これも深刻な問題は、デバッガーがブレークポイントをブレーク/ヒットできない場合があることです。

デバッガーを正しく (そして一貫して) 中断できる唯一の場所は、メソッドのエントリ ポイントです。

IgnoreSymbolStoreSequencePoints適用しないと状況は少し良くなります。

結論

VS デバッガーにバグがあるだけかもしれません :(

参考文献:

  1. CLR/.NET 言語をデバッグ可能にする

アップデート1:

Mdbg は 64 ビット アセンブリでは動作しません。そのため、これは不可能です。テストできる 32 ビット マシンがもうありません。アップデート:これは大きな問題ではないと思いますが、修正方法を知っている人はいますか?編集:はい、私は愚かです。x64 コマンド プロンプトで mdbg を起動するだけです :)

アップデート2:

C# アプリを作成し、行情報を分析してみました。

私の発見:

  • 命令の後にはbrXXXシーケンス ポイントが必要です (有効でない場合、つまり '#line hidden' の場合は を出力しますnop)。
  • あらゆるbrXXX命令の前に、「#line hidden」と を出力しますnop

ただし、これを適用しても問題は解決されません (単独では?)。

しかし、以下を追加すると、望ましい結果が得られます :)

  • の後にret、「#line hidden」と を出力しますnop

これは、が適用されていないモードを使用していますIgnoreSymbolStoreSequencePoints。適用すると、いくつかの手順はまだスキップされます:(

上記を適用した場合の IL 出力は次のとおりです。

  .method public static object  '::baz'(object x) cil managed
  {
    // Code size       63 (0x3f)
    .maxstack  6
    .line 15,15 : 1,2 ''
    IL_0000:  nop
    .line 17,17 : 6,15 ''
    IL_0001:  ldarg.0
    .line 16707566,16707566 : 0,0 ''
    IL_0002:  nop
    IL_0003:  brtrue     IL_000c

    .line 16707566,16707566 : 0,0 ''
    IL_0008:  nop
    .line 18,18 : 7,8 ''
    IL_0009:  ldarg.0
    IL_000a:  ret

    .line 16707566,16707566 : 0,0 ''
    IL_000b:  nop
    .line 19,19 : 6,15 ''
    .line 19,19 : 6,15 ''
    IL_000c:  ldarg.0
    IL_000d:  isinst     [IronScheme]IronScheme.Runtime.Cons
    IL_0012:  ldnull
    IL_0013:  cgt.un
    .line 16707566,16707566 : 0,0 ''
    IL_0015:  nop
    IL_0016:  brfalse    IL_0026

    .line 16707566,16707566 : 0,0 ''
    IL_001b:  nop
    IL_001c:  ldarg.0
    .line 20,20 : 7,14 ''
    IL_001d:  tail.
    IL_001f:  call object [IronScheme]IronScheme.Runtime.Builtins::Car(object)
    IL_0024:  ret

    .line 16707566,16707566 : 0,0 ''
    IL_0025:  nop
    IL_0026:  ldsfld object 
      [Microsoft.Scripting]Microsoft.Scripting.RuntimeHelpers::False
    IL_002b:  ldstr      "nooo"
    IL_0030:  ldarg.0
    IL_0031:  call object [IronScheme]IronScheme.Runtime.Builtins::List(object)
    .line 22,22 : 7,40 ''
    IL_0036:  tail.
    IL_0038:  call object [ironscheme.boot]#::
      'ironscheme.exceptions::assertion-violation+'(object,object,object)
    IL_003d:  ret

    .line 16707566,16707566 : 0,0 ''
    IL_003e:  nop
  } // end of method 'eval-core(033)'::'::baz'

アップデート3:

上記の「半修正」の問題。Peverify は、nopの後に が原因ですべてのメソッドでエラーを報告します。私は本当に問題を理解していません。の後に が検証を中断できるretのはなぜですか。これはデッド コードのようなものです (コードではないことを除いて)... まあ、実験は続きます。nopret

アップデート4:

自宅に戻り、「検証不可能な」コードを削除し、VS2008 で実行したところ、状況はさらに悪化しました。適切なデバッグのために検証不可能なコードを実行するのが解決策かもしれません。「リリース」モードでは、すべての出力は引き続き検証可能です。

アップデート5:

今のところ、上記のアイデアが唯一の実行可能なオプションであると判断しました。生成されたコードは検証できませんが、まだ何も見つかっていませんVerificationException。このシナリオでは、エンド ユーザーにどのような影響があるかわかりません。

ボーナスとして、2 番目の問題も解決されました。:)

ここに少しスクリーンキャスト最終的に得られた結果です。ブレークポイントに到達し、適切なステップ実行 (イン/アウト/オーバー) などを実行します。全体として、目的の効果が得られました。

しかし、私はまだこれをやり方として受け入れていません。これはあまりにもハッキーすぎる気がします。実際の問題について確認できればいいのですが。

アップデート6:

VS2010 でコードをテストするように変更したところ、いくつか問題があるようです。

  1. 最初の呼び出しが正しく実行されなくなりました。(アサーション違反...) がヒットしました。その他のケースは正常に動作します。 一部の古いコードは不要な位置を出力しました。コードを削除すると、期待どおりに動作します。:)
  2. さらに深刻なことに、プログラムの 2 回目の呼び出しでブレークポイントが失敗します (メモリ内コンパイルを使用し、アセンブリをファイルにダンプすると、ブレークポイントが再び機能するようになります)。

どちらの場合も、VS2008 では正常に動作します。主な違いは、VS2010 ではアプリケーション全体が .NET 4 用にコンパイルされ、VS2008 では .NET 2 にコンパイルされることです。どちらも 64 ビットで実行されます。

アップデート7:

前述のように、私は 64 ビットで mdbg を実行しました。残念ながら、プログラムを再実行するとブレークポイントの問題も発生し、ブレークに失敗することになりました (これは、プログラムが再コンパイルされることを意味します。つまり、同じアセンブリは使用されませんが、同じソースが引き続き使用されます)。

アップデート8:

私は持っているバグを報告したブレークポイントの問題に関しては、MS Connect サイトをご覧ください。

更新: 修正済み

アップデート9:

長い間考えた結果、デバッガーを満足させる唯一の方法は、SSA を実行して、各ステップを分離して連続的に実行することのようです。ただし、この考えはまだ証明していません。しかし、論理的に思えます。明らかに、SSA から一時ファイルをクリーンアップするとデバッグが中断されますが、これは簡単に切り替えることができ、残してもオーバーヘッドはそれほど大きくありません。

ベストアンサー1

私は Visual Studio デバッガー チームのエンジニアです。

間違っていたら訂正してください。残っている唯一の問題は、PDB から .NET 4 の動的コンパイル シンボル形式に切り替えるときに、いくつかのブレークポイントが失われることのようです。

問題を正確に診断するには再現が必要になる可能性がありますが、役立つ可能性のあるメモをいくつか示します。

  1. VS (2008+) は非管理者として実行できます
  2. 2 回目にシンボルが読み込まれるかどうかを確認します (例外または System.Diagnostic.Debugger.Break() の呼び出しを介して)
  3. シンボルがロードされると仮定して、送信できる再現版はありますか?
  4. おそらく違いは、動的にコンパイルされたコードのシンボル形式が、.NET 2 (PDB ストリーム) と .NET 4 (IL DB と呼ばれていたと思います) の間で 100% 異なることです。
  5. 「nop」はほぼ正しいようです。暗黙的なシーケンス ポイントを生成するためのルールについては以下を参照してください。
  6. 実際には、異なる行に何かを出力する必要はありません。デフォルトでは、VS は「シンボル ステートメント」をステップ実行しますが、コンパイラの作成者として「シンボル ステートメント」の意味を定義できます。したがって、各式をシンボル ファイル内で別々のものにしたい場合は、これで問題なく動作します。

JITは、次のルールに基づいて暗黙的なシーケンスポイントを作成します。1. IL nop命令 2. ILスタックの空ポイント 3. 呼び出し命令の直後のIL命令

問題を解決するために再現が必要であることが判明した場合は、接続バグを報告し、その媒体を通じてファイルを安全にアップロードできます。

アップデート:

この問題が発生している他のユーザーには、Dev11の開発者プレビューを試すことを推奨しています。http://www.microsoft.com/download/en/details.aspx?displaylang=en&id=27543フィードバックがあればコメントしてください。(目標は 4.5 にする必要があります)

アップデート2:

Leppieは、Dev11のベータ版で修正が機能することを確認しました。http://www.microsoft.com/visualstudio/11/en-us/downloads接続バグに記載されているようにマイクロソフト

ありがとう、

ルーク

おすすめ記事