2

VS アドインとしてビルドされた自家製のデバッグ ツールの場合、次のことを行う必要があります。

  • アプリケーションの任意の時点で中断する
  • 別のメソッドを呼び出してそこで中断します (実行前にその場所にコードを追加せずに)
  • その 2 番目のブレークポイントで VS アドインから他のコマンドを実行する

これを行う方法についての私の最初の本能は、ハンスの優れた回答hereで壁にぶつかりました。

私の 2 番目のアイデアは、ブレークポイントから他のメソッドへの呼び出しを設定し、アプリケーションの続行が許可されたときにそれを実行することです (必要なことを行う別の方法があれば、遠慮なく指摘してください! )。

これは WinDBG では些細なことです: .call を使用して実行するだけです。残念ながら、Visual Studio でこれを行う必要があります。

したがって、私の質問:VSでこれを行う方法はありますか? .call に相当するものも、レジスタとスタックを操作して.call をエミュレートする方法も見つかりません

4

1 に答える 1

0

いくつかの調査の後、この質問に対する答えは次のとおりだと思います: VS には .call に相当するものはありません。

唯一の解決策は、スタック ポインター、命令ポインターなどを操作して .call の動作をエミュレートすることです。これには明らかに制限があります。たとえば、私のものは Microsoft x64 呼び出し規約でしか機能しません。x86 への変換とその無数の呼び出し規則は、読者の演習として残されています ;)

実際の必要性に応じて、これを行う 2 つの方法を見つけました。これは、次にデバッグ対象が実行されたときに関数を呼び出すためのものであることを忘れないでください (ネストされたブレークポイントはサポートされていないため、ブレークできるようにするためです)。再度中断せずに関数を呼び出す必要がある場合は、イミディエイト ウィンドウを使用して直接呼び出す方がはるかに優れています。


簡単な方法:

これにより、現在のフレームが選択した DLL とメソッドにあると VS に認識させることができます。これは、Expression Evaluator が停止している DLL で動作することを望まず、別の DLL で動作する必要がある場合に便利です。

警告:スタックを破壊せずに偽造しているメソッド呼び出しを実際に実行することはできません (呼び出しているメソッドが非常に単純で、非常に幸運でない限り)。

以下を使用して、イミディエイト ウィンドウを介してデバッガーで直接これを行います。

@rsp=@rsp-8
*((__int64*)$rsp)=@rip
@rip={,,<DLL to jump in.dll>}<method to call>

これで、VS は、指定した DLL とメソッドを現在のフレームとして認識します。完了したら、次のコマンドを使用して前の状態に戻します。

@rip=*((__int64*)$rsp)
@rsp=@rsp+8

これは、以下の他の方法で示されているように、EnvDTE.Debugger.GetExpression() を介してこれらのステートメントを実行することにより、VS アドインで自動化することもできます。


難しい方法:

これは、必要な DLL と関数を実際に呼び出して、後できれいに返すために機能します。それはより複雑で危険です。間違いがあると、スタックが破損します。

また、オプティマイザーが呼び出し先と呼び出し元のコードで予期しない複雑なことを行った可能性があるため、デバッグ モードとリリース モードの両方を適切に処理するのは困難です。

アイデアは、Microsoft x64 呼び出し規約 (ここに文書化されています) をエミュレートし、呼び出された関数を中断することです。次のことを行う必要があります。

  • 最初の 4 つを超えるパラメータを右から左の順にスタックにプッシュします
  • スタック上にシャドウ スペースを作成する(1)
  • 戻りアドレス、つまり RIP の現在の値をプッシュする
  • 上記と同様に、呼び出す関数のアドレスに RIP を設定します。
  • 呼び出し先が変更する可能性があり、呼び出し元が変更を予期しない可能性があるすべてのレジスタを保存します。これは基本的に、ここで「揮発性」とマークされたものをすべて保存することを意味します。
  • 呼び出し先にブレークポイントを設定する
  • デバッグ対象を実行する
  • デバッグ対象が再び壊れたら、必要な操作を実行します
  • 踏みでる
  • 保存されたレジスタを復元する
  • RSP を正しい場所に戻す (つまり、シャドウ スペースを解体する)
  • ブレークポイントを削除する

(1) 呼び出し先がレジスターによって渡される最初の 4 つの引数をスピルするための 32 バイトのスクラッチ・スペース (通常、呼び出し先は実際にこれを好きなように使用できます)。

これは、非常に基本的なケース (0 に設定された 1 つのパラメーターを取り、あまり多くのレジスターに触れない非メンバー関数) でこれを行うための私の VS アドインの簡略化されたチャンクです。これを超えるものはすべて、読者の演習として残されています ;)

EnvDTE90a.Debugger4 dbg = (EnvDTE90a.Debugger4)DTE.Debugger;
string method = "{,,dllname.dll}function";
string RAX = null, RCX = null, flags = null;

// get the address of the function to call and the address to break at (function address + a bit, to skip some prolog and help our breakpoint actually hit)
Expression expr = dbg.GetExpression3(method, dbg.CurrentThread.StackFrames.Item(1), false, false, false, 0);
string addr = expr.Value;
string addrToBreak = (UInt64.Parse(addr.Substring(2), NumberStyles.HexNumber) + 2).ToString();
if (!expr.IsValidValue)
    return;

// set a breakpoint in the function to jump into
EnvDTE.Breakpoints bpsAdded = dbg.Breakpoints.Add("", "", 0, 0, "", dbgBreakpointConditionType.dbgBreakpointConditionTypeWhenTrue, "c++", "", 0, addrToBreak, 0, dbgHitCountType.dbgHitCountTypeNone);
if (bpsAdded.Count != 1)
    return;

// set up the shadow space and parameter space
// NB: for 1 parameter : 4 words of shadow space, no further parameters... BUT, since the stack needs to be 16 BYTES aligned (i.e. 2 words) and the return address takes a single word, we need to offset by 5 !
dbg.GetExpression3("@rsp=@rsp-8*5", dbg.CurrentStackFrame, false, true, false, 0);

// set up the return address
dbg.GetExpression3("@rsp=@rsp-8*1", dbg.CurrentStackFrame, false, true, false, 0);
dbg.GetExpression3("*((__int64*)$rsp)=@rip", dbg.CurrentStackFrame, false, true, false, 0);

// save the registers
RAX = dbg.GetExpression3("@rax", dbg.CurrentStackFrame, false, true, false, 0).Value;
RCX = dbg.GetExpression3("@rcx", dbg.CurrentStackFrame, false, true, false, 0).Value;

// save the flags        
flags = dbg.GetExpression3("@efl", dbg.CurrentStackFrame, false, true, false, 0).Value;

// set up the parameter for the call
dbg.GetExpression3("@rcx=0x0", dbg.CurrentStackFrame, false, true, false, 0);

// set the instruction pointer to our target function
dbg.GetExpression3("@rip=" + addr, dbg.CurrentStackFrame, false, true, false, 0);

dbg.Go(true);

// DO SOMETHING USEFUL HERE ! ;)

dbg.StepOut(true);

// restore all registers
dbg.GetExpression3("@rax=" + RAX, dbg.CurrentStackFrame, false, true, false, 0);
dbg.GetExpression3("@rcx=" + RCX, dbg.CurrentStackFrame, false, true, false, 0);

// restore flags
dbg.GetExpression3("@efl=" + flags, dbg.CurrentStackFrame, false, true, false, 0);

// tear down the shadow space
dbg.GetExpression3("@rsp=@rsp+8*5", dbg.CurrentStackFrame, false, true, false, 0);
}
于 2013-07-02T21:05:34.683 に答える