【问题标题】:Writing a VM - well formed bytecode?编写 VM - 格式良好的字节码?
【发布时间】:2010-05-11 23:51:05
【问题描述】:

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

我编写了一个非常快速的虚拟机,它可以读取(我自己的)ASM 行并执行一些操作。现在,我只有 3 条指令:addjmpend。一切都很好,能够提供线路实际上非常酷(像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的问题吗?我敢肯定,比我聪明得多的人已经找到了解决这些问题的方法,我只是好奇它们是什么!

【问题讨论】:

    标签: c assembly programming-languages vm-implementation


    【解决方案1】:

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

    你来决定。

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

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

    【讨论】:

      【解决方案2】:

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

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

      【讨论】:

      • 我从不认为将成员设为私有是一种安全工具。我的印象是,它只是一个提供良好抽象、隐藏实现细节的设计工具,但并不安全。
      • @Chris,访问private 成员本身可能不是安全问题,但想象一下编译器优化是基于对私有变量的所有引用和赋值都是已知的假设。例如,编译器可能会证明数组访问永远不会越界,因为之前与已知小于数组长度的私有变量进行了比较,因此它消除了边界检查。如果“非法”字节码使该假设无效,则缺少边界检查可能会导致安全问题。
      • 使用Reflection 你可以访问任何private 或其他成员,因此privateness 只是肤浅的,一个好的编译器不会信任它
      【解决方案3】:

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

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

      【讨论】:

        【解决方案4】:

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

        【讨论】:

          【解决方案5】:

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

          【讨论】:

            【解决方案6】:

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

            【讨论】:

              猜你喜欢
              • 2014-05-14
              • 2012-06-04
              • 2021-09-04
              • 2010-10-18
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2010-09-12
              相关资源
              最近更新 更多