我目前正在为脚本虚拟机编写调试器。 脚本的编译器生成调试信息,例如函数入口点,变量作用域,名称,行映射指令等。
但是,并且遇到了步骤问题。
现在,我有以下内容: 1.查找当前的IP 2.从中获取源代码行 3.获取下一个(有效)源代码行 4.获取下一个有效源行开始的IP 5.在该指令处设置临时断点
或:如果下一个源行不再属于同一个函数,请在返回地址后的下一个有效源行设置临时断点。
到目前为止,效果很好。但是,我似乎遇到跳跃问题。
例如,请使用以下代码:
n = 5; // Line A
if(n == 5) // Line B
{
foo(); // Line C
}
else
{
bar(); // Line D
--n;
}
鉴于此代码,如果我在B行并选择转发,则为断点确定的IP将在C行。但是,如果条件跳转的计算结果为false,则应将其放在行上D.因此,步骤不会在预期的位置停止(或者更确切地说,它不会停止)。
关于这个特定问题的调试器实现的信息似乎很少。但是,我找到了this。虽然这是针对Windows上的本机调试器,但理论仍然适用。
似乎作者在“实施步骤”一节中没有考虑过这个问题,因为他说:
1. The UI-threads calls CDebuggerCore::ResumeDebugging with EResumeFlag set to StepOver. This tells the debugger thread (having the debugger-loop) to put IBP on next line. 2. The debugger-thread locates next executable line and address (0x41141e), it places an IBP on that location. 3. It calls then ContinueDebugEvent, which tells the OS to continue running debuggee. 4. The BP is now hit, it passes through EXCEPTION_BREAKPOINT and reaches at EXCEPTION_SINGLE_STEP. Both these steps are same, including instruction reversal, EIP reduction etc. 5. It again calls HaltDebugging, which in turn, awaits user input.
再次:
调试器线程找到下一个可执行行和地址(0x41141e),它在该位置放置一个IBP。
但是,在涉及跳跃的情况下,这种说法似乎并不适用。
以前有人遇到过这个问题吗?如果是这样,你有任何解决方法吗?
答案 0 :(得分:0)
好吧,因为这似乎有点黑魔法,在这种特殊情况下最聪明的事情是枚举下一行开始的指令(或指令流结束+ 1),然后运行那么多再次停止之前的说明。
唯一的问题是我必须在执行CALL的情况下跟踪堆栈帧;这些说明应该在不进行计数的情况下运行。
答案 1 :(得分:0)
由于此线程首先在Google中搜索"调试器实现步骤"。我将分享我在x86架构方面的经验。
首先执行步骤:这基本上是单步执行指令并检查对应于当前EIP的行是否发生变化。 (您可以使用DIA SDK或读取矮人调试数据来查找EIP的当前行。)
在单步执行的情况下:在单步执行到下一条指令之前,您需要检查当前指令是否为CALL。如果它是一个CALL指令,则在它后面的指令上放置一个临时断点并继续执行直到执行停止(然后将其删除)。在这种情况下,您有效地在汇编级别中逐步调用函数调用,因此在源代码中也是如此。
无需管理堆栈帧(除非您需要处理单行递归函数)。这个类比也可以应用于其他架构。