6

私は自分のc#アプリケーションのハンズオフログメカニズムに取り組んでいます。

これが私がそれをどのように見せたいかです:

関数は関数をa(arg1, arg2, arg 3.....)呼び出します。関数は、スタックトレース(これはを介して実行できます)を検出できる関数を呼び出し、スタックトレース内の各関数(および)が呼び出される値を呼び出します。b(arg4,arg5,arg6....)log()Environment.StackTraceab

デバッグおよびリリースモード(または、少なくともデバッグモード)で動作させたい。

これは.netで行うことができますか?

4

4 に答える 4

10

おそらく不可能です:

が呼び出されるまでに、ba によって使用されるスタック内のスペースarg1(IL スタック、つまり、スタックに入れられることさえなかった可能性がありますが、呼び出しで登録されていた可能性があります) は、 によって引き続き使用されることが保証されていませんarg1

拡張により、arg1が参照型である場合、それが参照するオブジェクトは、 の呼び出し後に使用されない場合、ガベージ コレクションされていないことが保証されませんb

編集

あなたのコメントは、あなたがこれを理解していないことを示唆しており、まだ可能であると考えているため、もう少し詳しく説明します。

ジッターによって使用される呼び出し規則は、関連する標準の仕様では指定されていないため、実装者は自由に改善できます。実際、32 ビット バージョンと 64 ビット バージョン、およびリリースによって異なります。

ただし、MS 関係者の記事では、使用されている規則が__fastcall規則に似ていることが示唆されています。への呼び出しaでは、コードが実行されているコアのarg1ECX レジスター* とarg2EDX レジスター (32 ビット x86、amd64 でさらに多くの引数が登録されていると仮定して簡略化しています) に入れられます。arg3スタックにプッシュされ、実際にメモリに存在します。

この時点では、arg1arg2が存在するメモリ ロケーションはなく、CPU レジスタにのみ存在することに注意してください。

メソッド自体を実行する過程で、必要に応じてレジスタとメモリが使用されます。とb呼ばれます。

今、 ifaが必要になるarg1arg2、呼び出す前にそれをプッシュする必要がありますb。しかし、そうでない場合はそうではありません - そして、この必要性を減らすために物事を並べ替えるかもしれません. 逆に、これらのレジスターは、この時点ですでに別の目的で使用されている可能性があります。ジッターは愚かではないため、スタックにレジスターまたはスロットが必要で、メソッドの残りの部分で使用されていないものが 1 つある場合は、次のようになります。そのスペースを再利用します。(さらに言えば、これより上のレベルでは、C# コンパイラは、生成された IL が使用する仮想スタック内のスロットを再利用します)。

したがって、bが呼び出されると、arg4レジスタ ECX に配置され、arg5EDX に配置さarg6れ、スタックにプッシュされます。この時点でarg1andarg2 は存在せず、リサイクルされてトイレットペーパーになった後に本を読むことができないのと同じように、それらが何であったかを知ることはできません.

(興味深いことに、メソッドが同じ位置で同じ引数を使用して別のメソッドを呼び出すことは非常に一般的です。この場合、ECX と EDX はそのままにしておくことができます)。

次に、bサイズに応じて、その戻り値を EAX レジスター、または EDX:EAX ペア、または EAX が指すメモリに入れ、戻り値をaそのレジスターに入れる前に、さらに作業を行います。

これは、最適化が行われていないことを前提としています。実際にbは、まったく呼び出されなかった可能性がありますが、そのコードがインライン化された可能性があります。この場合、値がレジスタ内にあるのかスタックにあるのか - 後者の場合、それらがスタック上にあった場所は、bの署名とは何の関係もなく、関連する値がaのへの別の「呼び出し」の場合、またはfromへの別の「呼び出しb」の場合でさえも異なります。別の、さらに別の別の方法でインライン化されています。たとえば、baabarg4別の呼び出しによって返された値から直接取得されたものであり、この時点で EAX レジスタにある可能性がありますarg5が、ECX にあるのは と同じでarg1ありarg6、によって使用されているスタック空間の途中にあったためaです。

もう 1 つの可能性は、への呼び出しbが削除されたテール コールだったということです。への呼び出しは、スタックにプッシュするのではなく、(またはその他の可能性)bによってすぐに戻り値が返されるため、使用されている値abyaはその場で置き換えられ、戻りアドレスが変更され、 return frombが を呼び出したメソッドにジャンプして戻るようaになり、作業の一部がスキップされます (そして、代わりにスタックをオーバーフローする一部の関数型スタイルのアプローチが機能する程度までメモリ使用量が削減されます)そして実際にうまく機能します)。この場合、 への呼び出し中に、 へbのパラメータaは、スタックにあったものも含めて完全になくなっている可能性があります。

この最後のケースを最適化と見なすべきかどうかについては、非常に議論の余地があります。一部の言語は、(スタックをオーバーフローする代わりに) まったく機能する場合でも、優れたパフォーマンスを提供し、そうでなければ恐ろしいパフォーマンスを提供するため、実行に大きく依存しています。

他のあらゆる種類の最適化が存在する可能性があります。他にもあらゆる種類の最適化が必要です。.NET チームまたは Mono チームが、私のコードを高速化したり、メモリの使用量を減らしたりして、それ以外は同じように動作する場合、私が何かをしなくても、文句を言うことはありません!

そしてそれは、最初に C# を書いた人がパラメーターの値を決して変更しなかったことを前提としていますが、これは確かに真実ではありません。次のコードを検討してください。

IEnumerable<T> RepeatedlyInvoke(Func<T> factory, int count)
{
  if(count < 0)
    throw new ArgumentOutOfRangeException();
  while(count-- != 0)
    yield return factory();
}

C# コンパイラとジッターが、上記の方法でパラメーターが変更されないことを保証できるほど無駄な方法で設計されていたとしてもcount、 の呼び出し内から何が既に行われていたかをどのように知ることができますfactoryか? 最初の呼び出しでも違いますが、上記のような奇妙なコードではありません。

つまり、要約すると:

  1. ジッター: パラメータはしばしば登録されます。x86 では 2 つのポインター、参照、または整数パラメーターがレジスターに配置され、amd64 では 4 つのポインター、参照、または整数パラメーターと 4 つの浮動小数点パラメーターがレジスターに配置されることが期待できます。それらには、それらを読み取る場所がありません。
  2. ジッター: スタック上のパラメーターはしばしば上書きされます。
  3. ジッター: 実際の呼び出しはまったくない可能性があるため、パラメーターはどこにでもある可能性があるため、パラメーターを探す場所がありません。
  4. ジッター: 「呼び出し」は、最後のフレームと同じフレームを再利用している可能性があります。
  5. コンパイラ: IL は、ローカルのスロットを再利用する場合があります。
  6. 人間: プログラマーはパラメーター値を変更できます。

そのすべてから、いったい何が何であったかを知ることarg1ができるのでしょうか?

ここで、ガベージ コレクションの存在を追加します。arg1これらすべてにもかかわらず、とにかく魔法のように何が何であるかを知ることができると想像してみてください. それがヒープ上のオブジェクトへの参照であった場合、上記のすべてがスタック上でアクティブな参照がこれ以上ないことを意味する場合、これが確実に発生することは明らかであるため、それでも役に立たない可能性があります。 GC が作動すると、オブジェクトが収集された可能性があります。したがって、魔法のように手に入れることができるのは、もはや存在しないものへの参照だけです。実際、ヒープ内の領域が別の何かに使用されている可能性が非常に高く、bang はフレームワーク全体の型安全性全体に行きます!

IL を取得するリフレクションに匹敵するものではありません。

  1. IL は、特定の時点での単なる状態ではなく、静的です。同様に、お気に入りの本を初めて読んだときの反応を取り戻すよりも、図書館からお気に入りの本を入手する方がはるかに簡単です。
  2. とにかく、ILはインライン化などの影響を反映していません。呼び出しが実際に使用されるたびにインライン化され、リフレクションを使用してMethodBodyそのメソッドの を取得した場合、通常はインライン化されているという事実は関係ありません。

プロファイリング、AOP、およびインターセプトに関する他の回答の提案は、あなたが得る限り近いものです。

*実際にthisは、インスタンス メンバーに対する本当の最初のパラメーターです。これを指摘し続ける必要がないように、すべてが静的であると仮定しましょう。

于 2012-08-24T21:04:25.727 に答える
3

.netでは不可能です。実行時に、JITter はスタックの代わりに CPU レジスタを使用してメソッド パラメータを格納したり、スタック内の初期 (渡された) 値を書き換えたりする場合があります。そのため、ソース コードの任意の時点でパラメーターをログに記録できるようにすることは、.net にとって非常にパフォーマンス コストが高くなります。

私の知る限り、一般的にできる唯一の方法は、.net CLR プロファイリング API を使用することです。(たとえば、Typemock フレームワークはそのようなことを行うことができ、CLR プロファイリング API を使用します)

仮想関数/プロパティ (インターフェイス メソッド/プロパティを含む) 呼び出しをインターセプトするだけでよい場合は、任意のインターセプト フレームワーク (Unity や Castle など) を使用できます。

.net プロファイリング API に関する情報がいくつかあります。

MSDN マガジン

MSDN ブログ

ブライアン・ロングのブログ

于 2012-08-24T21:08:41.633 に答える
1

タイプモックまたは ICorDebug マジックがなければ、おそらく発生しないでしょう。StackFrameクラスでさえ、パラメーターではなく、ソースに関する情報を取得できるメンバーのみをリストします。

ただし、目的の機能は、メソッドのログを記録する IntelliTrace として存在します。レビューに必要なものをフィルタリングできます。

于 2012-08-24T21:17:54.997 に答える
1

これは C# では不可能です。AOP アプローチを使用し、各メソッドが呼び出されたときにメソッド引数のログを実行する必要があります。このようにして、ロギング コードを一元化して再利用可能にし、引数のロギングが必要なメソッドをマークするだけで済みます。

これは、 PostSharpのような AOP フレームワークを使用して簡単に達成できると思います。

于 2012-08-24T21:09:57.437 に答える