11

.NET フレームワークのアンマネージ API を使用してインプロセスの .NET プロセスをプロファイリングする場合、StackSnapshotCallback 関数に提供されるネイティブ命令ポインターに相関する IL 命令ポインターを検索することは可能ですか?

おそらく明らかなように、現在のスタックのスナップショットを取得しており、スタック ダンプにファイルと行番号の情報を提供したいと考えています。Managed Stack Explorerは、クエリを実行してこれを行いISymUnmanagedMethod::GetSequencePointsます。これは素晴らしいことですが、シーケンス ポイントはオフセットに関連付けられており、これまでのところ、これらはメソッドの先頭からのオフセットであると想定しています (中間言語で)。

David Broman は、彼のブログ投稿Profiler stack Walking: Basics and beyondへのフォローアップ コメントで、このマッピングは を使用して実現できることを示していますICorDebugCode::GetILToNativeMapping。ただし、このインターフェイスを取得するには、別のデバッガー プロセスから自分のプロセスにアタッチする必要があるため、これは理想的ではありません。

これらのスナップショットを作成している間も、Visual Studio デバッガー内からアプリケーションを引き続き実行できるようにしたいので、その手順は避けたいと思います。出力ウィンドウの行番号をクリックして、問題のコードに移動しやすくなります。

機能は可能です....管理されたコード内で自由に行番号付きのスタックトレースを吐き出すことができます.唯一の問題は、アクセス可能かどうかです. また、パフォーマンス上の理由から、スタックの実際のダンプを遅らせる必要があるため、System::Diagnostics::StackTraceor機能を使用したくありません....そのため、後でメソッド名とコードの場所を解決するためのコストを節約することが望ましい.. System::Environment::StackTrace. ネイティブ フレームとマネージド フレームを混在させる機能。

4

2 に答える 2

8

によって提供されるネイティブ命令ポインターから中間言語メソッド オフセットに変換するには、ネイティブ命令ポインターを仮想メモリ アドレスとして提供するICorProfilerInfo2::DoStackSnapshotため、2 つの手順を実行する必要があります。DoStackSnapshotFunctionID

ステップ 1 は、命令ポインタをネイティブ コード メソッド オフセットに変換することです。(JITed メソッドの先頭からのオフセット)。これはで行うことができますICorProfilerInfo2::GetCodeInfo2

ULONG32 pcIL(0xffffffff);
HRESULT hr(E_FAIL);
COR_PRF_CODE_INFO* codeInfo(NULL);
COR_DEBUG_IL_TO_NATIVE_MAP* map(NULL);
ULONG32 cItem(0);

UINT_PTR nativePCOffset(0xffffffff);
if (SUCCEEDED(hr = pInfo->GetCodeInfo2(functioId, 0, &cItem, NULL)) &&
    (NULL != (codeInfo = new COR_PRF_CODE_INFO[cItem])))
{
    if (SUCCEEDED(hr = pInfo->GetCodeInfo2(functionId, cItem, &cItem, codeInfo)))
    {
        COR_PRF_CODE_INFO *pCur(codeInfo), *pEnd(codeInfo + cItem);
        nativePCOffset = 0;
        for (; pCur < pEnd; pCur++)
        {
            // 'ip' is the UINT_PTR passed to the StackSnapshotCallback as named in
            // the docs I am looking at 
            if ((ip >= pCur->startAddress) && (ip < (pCur->startAddress + pCur->size)))
            {
                nativePCOffset += (instructionPtr - pCur->startAddress);
                break;
            }
            else
            {
                nativePCOffset += pCur->size;
            }

        }
    }
    delete[] codeInfo; codeInfo = NULL;
}

ステップ 2. natvie コード メソッドの先頭からのオフセットを取得したら、これを使用して、 を使用して中間言語メソッドの先頭からのオフセットに変換できますICorProfilerInfo2::GetILToNativeMapping

if ((nativePCOffset != -1) &&
    SUCCEEDED(hr = pInfo->GetILToNativeMapping(functionId, 0, &cItem, NULL)) &&
    (NULL != (map = new COR_DEBUG_IL_TO_NATIVE_MAP[cItem])))
{
    if (SUCCEEDED(pInfo->GetILToNativeMapping(functionId, cItem, &cItem, map)))
    {
        COR_DEBUG_IL_TO_NATIVE_MAP* mapCurrent = map + (cItem - 1);
        for (;mapCurrent >= map; mapCurrent--)
        {
            if ((mapCurrent->nativeStartOffset <= nativePCOffset) && 
                (mapCurrent->nativeEndOffset > nativePCOffset))
            {
                pcIL = mapCurrent->ilOffset;
                break;
            }
        }
    }
    delete[] map; map = NULL;
}

これは、シンボル API を使用して、コードの場所をファイルと行番号にマップするために使用できます。

解決策を見つけるための指示をくれたMithun Shanbhagに感謝します。

于 2008-11-01T17:21:06.130 に答える
0
Console.WriteLine("StackTrace: '{0}'", Environment.StackTrace);

ビルドでシンボルが生成されることを確認してください。

議論の拡大:

おそらく明らかなように、現在のスタックのスナップショットを取得しており、スタック ダンプにファイルと行番号の情報を提供したいと考えています。

これを考えると、プロセスにアタッチしていない唯一の理由は、開発中にツールまたはその一部を簡単にデバッグできるようにするためです。その IMO は、より良い設計 (ICorDebug または w/e ) が利用可能な場合に選択しないための言い訳にはなりません。その貧弱な設計の理由は、コードが (おそらく) 外部バイナリのプロセス空間で実行され、既知の (またはさらに悪い - 未知の) 破損したプロセス状態で厄介な (「時々」まれな) 副作用 (他の誰かのデータの破損を含む) を引き起こすためです。最初はこれで十分ですが、そうでない場合でも、マルチスレッド コードなどで設計を回避する必要があるエッジ ケースがいくつかあります。

ほとんどの人は一般的に「あなたは本当に何をしようとしていますか?」と尋ねます。明らかに複雑なやり方への答えとして。ほとんどの場合、より単純で簡単な方法があります。ネイティブ コード用のスタック トレーサを作成したので、面倒になる可能性があることはわかっています。

これで、すべてが機能するようになる可能性があるので、- Just my $.02

于 2008-10-17T23:01:14.387 に答える