对于作为VS加载项构建的自制调试工具,我需要:
我的第一个关于如何做到这一点的直觉在汉斯的出色答案中here。
我的第二个想法是从断点设置对另一个方法的调用,并在允许应用程序继续时执行它(如果你能看到另一种方法来做我需要的东西,请随意指出它,不过!)。
这对于WinDBG来说是微不足道的:只需使用.call即可。不幸的是,我需要在Visual Studio中执行此操作。
因此我的问题是:在VS中有什么方法可以做到这一点吗?我找不到等效的.call,也没有找到操作寄存器和堆栈的方法emulate .call myself。
答案 0 :(得分:0)
经过一番调查后,我相信这个问题的答案是:VS中没有与.call等效的内容。
唯一的解决方案是通过操纵堆栈指针,指令指针等来模拟.call的行为。这显然会有局限性,例如:我的只适用于Microsoft x64调用约定。转换为x86及其无数的调用约定留给读者练习;)
根据您的实际需要,我找到了两种方法。请记住,这是为了在下次调试对象运行时调用函数(这样您就可以进入函数,因为不支持嵌套断点)。如果你只是需要调用一个函数而不再破坏,你最好只使用立即窗口直接调用它!
简单方法:
这将欺骗VS认为当前帧是在您选择的DLL和方法中。这很有用,因为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调用约定(记录为here)并打破调用的函数。我们需要做以下事情:
(1)32字节的临时空间,用于被调用者溢出寄存器传递的前4个参数(通常;被调用者实际上可以使用它但是它喜欢)。
这是我的VS插件的简化块,用于非常基本的情况(非成员函数将一个参数设置为0而不接触太多寄存器)。超出此范围的任何内容再次留给读者练习;)
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);
}