我正在C中编写虚拟机只是为了好玩。 Lame,我知道,但幸运的是我已经开始了,所以希望没有人会取笑:)
我写了一个非常快速的虚拟机,它可以读取(我自己的)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的问题吗?我确信比我聪明的人已经找到解决这些问题的方法,我只是好奇他们是什么!
答案 0 :(得分:15)
VM的工作是确保字节码格式正确,还是仅仅是编译器的工作,以确保它吐出的二进制文件格式正确?
你可以决定。
最佳做法是让VM在执行前进行单次检查,与程序大小成比例,这是复杂的,可以保证在执行过程中不会发生任何问题。然后在实际执行字节码期间,运行时不进行任何检查。 但是,运行前检查的想法可能需要一些非常复杂的分析,即使是性能最敏感的VM通常也会在运行时进行一些检查(例如:数组边界)。
对于一个爱好项目,我会保持简单,并在每次执行指令时让VM检查健全。大多数指令的开销不会太大。
答案 1 :(得分:2)
同样的问题出现在Java中,我记得,在这种情况下,VM必须进行一些检查以确保字节码格式良好。在这种情况下,由于存在安全问题的可能性,它实际上是一个严重的问题:如果有人可以更改Java字节码文件以包含编译器永远不会输出的内容(例如从另一个类访问private
变量),它可能会暴露应用程序内存中保存的敏感数据,或者可能允许应用程序访问不应该被允许的网站或其他内容。 Java的虚拟机包含一个字节码验证器,以尽可能确保不会发生这些事情。
现在,在您的情况下,除非您的自制语言起步并变得流行,否则安全方面是您不必担心的事情;毕竟,除了你之外,谁会破解你的程序?不过,我认为最好确保你的虚拟机至少在字节码无效时有一个合理的失败策略。至少,如果遇到它无法理解且无法处理的事情,它应该检测到并失败并显示错误消息,这将使您的调试更容易。
答案 2 :(得分:2)
解释字节码的虚拟机通常有一些验证其输入的方法;例如,如果类文件处于不一致状态,Java将抛出VerifyError
然而,听起来你正在实现一个处理器,并且因为它们往往是较低级别的,所以你可以设法将事物处于可检测的无效状态的方式更少 - 给它一个未定义的操作码是一种显而易见的方法。真正的处理器将发出信号表明该进程试图执行非法指令,并且操作系统将处理它(例如,Linux用SIGILL杀死它)
答案 3 :(得分:1)
如果您担心有人编辑了二进制文件,那么您的问题只有一个答案:VM必须进行检查。这是你有机会发现篡改的唯一方法。编译器只是创建二进制文件。它无法检测下游篡改。
答案 4 :(得分:0)
让编译器尽可能多地进行健全性检查是有意义的(因为它只需要执行一次),但是总会出现静态分析无法检测到的问题,例如[cough] stack溢出,数组范围错误等。
答案 5 :(得分:0)
我认为只要VM实现本身没有崩溃,你的虚拟机让模拟处理器着火是合法的。作为VM实现者,您可以设置规则。但是,如果您希望虚拟硬件公司虚拟购买您的虚拟芯片,您将不得不做一些更容易出错的事情:好的选择可能是引发异常(更难实现)或重置处理器(更容易)。或者你可能只是定义每个操作码是有效的,除了一些是“未记录的” - 他们做了一些未指定的事情,而不是崩溃你的实现。理由:如果(!)您的VM实现是同时运行多个guest虚拟机实例,那么如果一个guest虚拟机能够导致其他guest虚拟机失败,则会非常糟糕。