3

我目前正在为脚本虚拟机编写调试器。脚本的编译器生成调试信息,例如函数入口点、变量范围、名称、指令到行的映射等。

但是,并且遇到了跨步问题。

现在,我有以下内容: 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 行。但是,如果条件跳转的计算结果为假,则应将其放在 D 行。因为其中,跨步不会在预期位置停止(或者更确切地说,它根本不会停止)。

关于这个特定问题的调试器实现的信息似乎很少。但是,我发现了这个。虽然这是针对 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。

不过,在涉及跳跃的情况下,这种说法似乎并不成立。

有没有人遇到过这个问题?如果是这样,您对如何解决这个问题有任何提示吗?

4

2 回答 2

1

由于此线程在搜索“调试器实现跨步”时首先出现在 Google 中。我将分享我在 x86 架构方面的经验。

您首先通过实现 step into 开始:这基本上是单步执行指令并检查与当前 EIP 对应的行是否发生变化。(您可以使用 DIA SDK 或读取 dwarf 调试数据来找出 EIP 的当前行)。

在单步执行的情况下:在单步执行下一条指令之前,您需要检查当前指令是否为 CALL 指令。如果它是 CALL 指令,则在其后的指令上放置一个临时断点并继续执行直到执行停止(然后将其删除)。在这种情况下,您实际上在汇编级别和源代码中都有效地跨过了函数调用。

无需管理堆栈帧(除非您需要处理单行递归函数)。这种类比也可以应用于其他架构。

于 2015-03-09T18:11:44.320 回答
0

好的,所以由于这似乎有点黑魔法,在这种特殊情况下,最聪明的事情是枚举下一行开始的指令(或指令流结束 + 1),然后在停止之前运行那么多指令再次。

唯一的问题是我必须跟踪堆栈帧以防 CALL 被执行;在跨步的情况下,这些指令应该在不计算的情况下运行。

于 2013-10-28T23:50:59.043 回答