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


135

まず、この質問が長く続いたことをお詫び申し上げます。

私はIronSchemeの作者です。最近私は「ネイティブ」の.NETデバッガーを使用できるように、まともなデバッグ情報を出力するように努力しています。

これは部分的に成功していますが、歯が生える問題に直面しています。

最初の問題はステッピングに関連しています。

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

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

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

私はわざとそれぞれの表現を改行でレイアウトしています。

生成されたコードは(ILSpyを介して)C#に変換されます。

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 argで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 argでbazを呼び出すと失敗します。(null?x)それから(pair?x)。
  3. 他の引数でbazを呼び出すと失敗します。(null?x)それから(pair?x)それから(car x)そして(assertion-violation ...)

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

原因となる可能性のあるアイデアを以下に示します。

  • Tailcallsがデバッガーを混乱させる
  • 重複する場所(ここには表示されていません)は、デバッガーを混乱させます(ブレークポイントを設定するときに非常にうまく機能します)
  • ????

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。私は本当に問題を理解していません。どのようにしてnop検証を中断できますかret。それはデッドコードのようなものです(コードでさえないことを除いて)...まあ、実験は続いています。

アップデート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からtempをクリーンアップするとデバッグが中断しますが、これは簡単に切り替えることができ、それらを残してもオーバーヘッドはあまりありません。


どんなアイデアも歓迎します、私はそれらを試して、質問に結果を追加します。最初の2つのアイデアは、mdbgを試し、同じコードをC#で記述して比較します。
レッピー、2011

6
質問はなんですか?
ジョージストッカー2011

9
彼の質問は、「なぜ私の言語が正しく進んでいないのですか?」
マッケイ

1
peverifyを渡さない場合、マップされたドライブから、または多くのプラグインホスティングセットアップなど、部分的に信頼された状況では実行できません。IL、reflection.Emitまたはテキスト+ ilasmをどのように生成しますか?どちらの場合でも、常に.line 16707566,16707566:0,0 ''を自分で置くことができるので、復帰後にnopを置くことを心配する必要はありません。
Hippiehunter

@Hippiehunter:ありがとう。残念ながらnopsがないと、ステッピングは失敗します(念のため、もう一度確認します)。それは私がしなければならないと思う犠牲です。それは、VSが管理者権限なしでも実行できるようなものではありません:)ところで、DLRを介してReflection.Emitを使用します(非常にハッキングされた初期の分岐型)。
leppie

回答:


26

私はVisual Studio Debuggerチームのエンジニアです。

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

問題を正確に診断するには再現が必要になる可能性がありますが、役立つ可能性のある注意事項を以下に示します。

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

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

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

更新:

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

アップデート2:

Leppieは、接続バグhttps://connect.microsoftに記載されているhttp://www.microsoft.com/visualstudio/11/en-us/downloadsから入手できるDev11のベータ版で彼のために修正が機能することを確認しました。 com / VisualStudio / feedback / details / 684089 /

おかげで、

ルーク


どうもありがとう。必要に応じて、再現を試みます。
leppie

確認については、はい、あなたは正しいですが、検証可能性を損なうことなくステッピングの問題を解決したいと思います。しかし、言ったように、もし私がそれがランタイムをスローしないことをおそらく証明することができるなら、それを犠牲にして喜んでですVerificationException
レッピー、2011

ポイント6に関しては、この質問では単に「行ベース」にしました。デバッガーは、実際に、私が機能させるつもりのように、式を正しくイン/アウト/オーバーします:)アプローチの唯一の問題は、「外部」式がそれをカバーする場合に「内部」式にブレークポイントを設定することです。VSのデバッガーは、ブレークポイントとして最も外側の式を作成する傾向があります。
レッピー、2011

ブレークポイントの問題を切り分けたと思います。CLR2で実行しているときに、新しいコードではあるが、コードを再度実行すると、ブレークポイントが再評価されるようです。CLR4では、ブレークポイントは元のコードにのみ「固定」されます。CLR4の動作の小さなスクリーンキャスト@ screencast.com/t/eiSilNzL5Nrを作成しました。呼び出しごとに型名が変わることに注意してください。
レッピー、2011

接続に関するバグを報告しました。再現は非常に簡単です:) connect.microsoft.com/VisualStudio/feedback/details/684089
leppie

3

私はSharpDevelopデバッガチームのエンジニアです:-)

あなたは問題を解決しましたか?

SharpDevelopでデバッグしようとしましたか?.NETにバグがある場合、いくつかの回避策を実装する必要があるのでしょうか。私はこの問題を認識していません。

ILSpyでデバッグしようとしましたか?特にデバッグシンボルなし。C#コードをデバッグしますが、IL命令が適切にデバッグ可能かどうかを教えてくれます。(ただし、ILSpyデバッガーはベータ版です)

元のILコードに関する簡単なメモ:

  • .line 19,19:6,15 ''は2回発生しますか?
  • .line 20,20:7,14 ''は暗黙のシーケンスポイントで開始しません(スタックが空ではありません)。私が心配しています
  • .line 20,20:7,14 ''には、「car x」(良い)と「#f nooo x」(悪い?)のコードが含まれています
  • retの後のnopに関して。stloc、ldloc、retについてはどうですか?C#はこのトリックを使用して、retを明確なシーケンスポイントにします。

デビッド


+1フィードバックに感謝します。最初のポイントとして、コードジェネレーターからの残念な副作用ですが、害はないようです。2つ目のポイントは、これがまさに私が必要としていることですが、あなたのポイントはわかります。最後のポイントで何を意味するのかわからない。
レッピー、2009

3番目のポイントは、.line 22,22:7,40 ''がIL_0020の前にあることです。または、IL_0020の前に何かがあるはずです。それ以外の場合、コードは引き続き.line 20,20:7,14 ''としてカウントされます。4番目のポイントは、「ret、nop」が「sloc、.line、ldloc、ret」に置き換えられる可能性があることです。私は以前にそのパターンを見ました、多分それは冗長でした、しかし多分それは理由がありました。
dsrbecky 2009

テールコールが失われるため、retlocの前にstloc、ldlocを実行できません。ええと、あなたは元の出力を参照していましたか?
leppie '09 / 09/14
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.