9

我用 C 语言编写虚拟机只是为了好玩。跛脚,我知道,但幸运的是我在 SO 所以希望没有人会取笑:)

我写了一个非常快的虚拟机,它读取(我自己的)ASM 行并做一些事情。现在,我只有 3 条指令:add, jmp, end. 一切都很好,能够喂线实际上很酷(这样做write_line(&prog[1], "jmp", regA, regB, 0);然后运行程序:

while (machine.code_pointer <= BOUNDS && DONE != true)
{
    run_line(&prog[machine.cp]);
}

我在 C 中使用操作码查找表(可能效率不高,但很优雅),一切似乎都正常。

我的问题更像是一个“最佳实践”问题,但我确实认为它有一个正确的答案。我正在使 VM 能够读取二进制文件(将字节存储在 中unsigned char[])并执行字节码。我的问题是:VM 的工作是确保字节码格式正确,还是编译器的工作只是确保它输出的二进制文件格式正确?

我之所以问这个问题,是因为如果有人编辑二进制文件并搞砸(删除它的任意部分等)会发生什么。显然,该程序将是错误的,并且可能无法正常工作。这甚至是VM的问题吗?我敢肯定,比我聪明得多的人已经找到了解决这些问题的方法,我只是好奇它们是什么!

4

6 回答 6

15

确保字节码格式正确是 VM 的工作还是只是编译器的工作来确保它吐出的二进制文件格式正确?

你来决定。

最佳实践是让虚拟机在执行前进行一次检查,成本与程序的大小成正比,这足以保证在执行期间不会发生任何异常情况。然后在字节码的实际执行过程中,您无需检查即可运行。然而,运行前检查的想法可能需要一些非常复杂的分析,即使是最注重性能的虚拟机也经常在运行时进行一些检查(例如:数组边界)。

对于一个爱好项目,我会保持简单,并在每次执行指令时让 VM 检查完整性。大多数指令的开销不会太大。

于 2010-05-12T00:50:21.653 回答
2

Java 中也出现了同样的问题,我记得在这种情况下,VM 确实必须进行一些检查以确保字节码格式正确。在这种情况下,由于潜在的安全问题,这实际上是一个严重的问题:如果有人可以更改 Java 字节码文件以包含编译器永远不会输出的内容(例如private从另一个类访问变量),它可能会暴露敏感的数据被保存在应用程序的内存中,或者可能允许应用程序访问不应被允许的网站,或其他东西。Java 的虚拟机包括一个字节码验证器,以尽可能确保不会发生这类事情。

现在,就您而言,除非您的自制语言流行起来,否则您不必担心安全方面的问题;毕竟,除了你之外,还有谁会破解你的程序?不过,我会说最好确保您的虚拟机至少有一个合理的失败策略,以应对字节码无效的情况。至少,如果它遇到了它不理解和无法处理的东西,它应该检测到它并失败并显示错误消息,这将使您的调试更容易。

于 2010-05-11T23:59:23.393 回答
2

解释字节码的虚拟机通常有某种方式来验证它们的输入。例如,如果类文件处于不一致状态,Java 将抛出 VerifyError

但是,听起来您正在实现一个处理器,并且由于它们往往是较低级别的,因此您可以设法使事物处于可检测的无效状态的方法较少——给它一个未定义的操作码是一种明显的方法。真正的处理器会发出信号,表明进程试图执行非法指令,操作系统会处理它(例如,Linux 用 SIGILL 杀死它)

于 2010-05-12T00:02:00.197 回答
1

如果您担心有人编辑了二进制文件,那么您的问题只有一个答案:VM 必须进行检查。这是您有机会检测到篡改的唯一方法。编译器只是创建二进制文件。它无法检测下游篡改。

于 2011-08-30T01:08:10.147 回答
0

让编译器做尽可能多的完整性检查是有意义的(因为它只需要做一次),但总会有一些静态分析无法检测到的问题,比如 [cough] 堆栈溢出、数组范围误差等。

于 2010-05-11T23:57:39.407 回答
0

我想说你的虚拟机让模拟处理器着火是合法的,只要虚拟机实现本身不会崩溃。作为 VM 实施者,您可以设置规则。但是,如果您希望虚拟硬件公司虚拟购买您的虚拟芯片,您将不得不做一些更宽容的错误:好的选择可能是引发异常(更难实现)或重置处理器(更容易)。或者,也许您只是将每个操作码都定义为有效,除了有些是“未记录的”——它们会做一些未指定的事情,而不是让您的实现崩溃。基本原理:如果(!)您的 VM 实现是同时运行多个来宾实例,那么如果一个来宾能够导致其他来宾失败,那将是非常糟糕的。

于 2010-05-14T12:20:31.310 回答