首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >虚拟机字节码到底是什么?

虚拟机字节码到底是什么?
EN

Software Engineering用户
提问于 2014-03-03 11:31:57
回答 4查看 2.6K关注 0票数 2

我想为一种非常简单的语言开发一个非常简单的编译器。编译器将把代码编译成一些基本的字节码。稍后,虚拟机程序将运行字节码,并执行该程序。

关于字节码实际上是什么,我有几个问题:

  • 字节码是否必须是二进制形式的,也就是由1和0序列组成的?或者它可以包括更多的数字,甚至单词?
  • 最常见的情况是,程序的字节码是如何组成的?我对字节码将包含什么以及如何使用我的编译器编写字节码的一个想法是:编译器将扫描开发人员的代码,并将每个命令转换为某些字节码等效的命令。例如,print命令将在字节码中转换为1。稍后,当VM运行字节码时,字节码中的每个元素将被转换为程序中的某些执行。例如,如果VM扫描字节码并根据1来,它将打印某些内容。这种做法常见吗?有效吗?什么是最常见的方法,如何和什么字节码构成?
EN

回答 4

Software Engineering用户

回答已采纳

发布于 2014-03-03 11:39:18

  • 没有必要去二进制,你可以写文本和直接解释。为了提高效率,大多数VM将使用基于八进制的二进制文件。
  • 这完全取决于设计师。但是通常是普通的指令(加2个数字,GOTOs,.)将比不常见的函数更短(调用函数,引发异常,.)一种方法是让操作码的前几位决定这个家族(算术,比较,跳跃,.)在指令中,接下来的几个位将是特定的指令,最后一个位将指示使用的操作数(如果适用)。
票数 4
EN

Software Engineering用户

发布于 2014-03-03 12:33:51

实际上,您所做的是设计新的计算机指令,以及作为程序实现的新计算机(您的虚拟机)。

通常,指令(即字节码是什么)有一个初始部分,由解释器检查,以确定下一步将发生什么。例如,正如您所建议的,1可能意味着打印以下字符串。2可能意味着添加以下两个整数。

当您计算出您需要的指令时,您会发现您需要一种保存数据的方法(称为内存组织),以及一种组织指令的方法。指令是做非常复杂的事情,还是非常简单的事情?如果他们做复杂的事情(打印这个字符串),那么就需要很多,但是数据可以用一种更简单的方式来组织。如果指令做简单的事情(将这个整数加载到工作区域1),那么指令就会减少,但是数据组织将需要支持更丰富的结构和更多的数据类型。

一开始很简单,比如说1字节的操作码.当你解决这个问题的时候,你会看到很多版本的代码。

好好享受吧。你将比你想象的更多地了解计算机的工作原理。

票数 3
EN

Software Engineering用户

发布于 2014-03-03 14:17:33

对于一个简单的例子,让我们来看看PHP的Zend引擎。这是一个相对简单的VM,完全在内存中工作,因此不必涉及序列化等。

在PHP中,字节码由167个不同的操作码组成,它们可以在一个标题中找到:http://lxr.php.net/xref/PHP_主干/zend/zend_vm_opcodes.h

所有这些操作码都存储在数据结构中,并以数组的形式存储一些元信息(表示为指向第一个元素的指针)。

代码语言:javascript
运行
复制
struct _zend_op_array {
    /* ... */
    zend_op *opcodes;
    /* ... */
 }

然后,每个操作都将它自己的数据结构存储在其中:

代码语言:javascript
运行
复制
struct _zend_op {
    opcode_handler_t handler;
    znode_op op1;
    znode_op op2;
    znode_op result;
    ulong extended_value;
    uint lineno;
    zend_uchar opcode;
    zend_uchar op1_type;
    zend_uchar op2_type;
    zend_uchar result_type;
};

在这里我们可以看到很多东西:

  • 操作码长列表中的操作码编号存储在opcode字段中。
  • 每个操作都有两个操作数(op1op2),并且有一个返回值(result)。
  • 操作数和返回值有类型,它们指示它们是表示变量、常量还是临时值。在PHP的实现中,它们存储在不同的地方,并以不同的方式被访问。
  • 有一个extended_value,用来存储更多的信息,例如在foreach循环中。
  • 源文件中用于报告错误的行号(文件名存储在上面的zend_op_array结构中)
  • handler是一个小的优化,直接指向实现这个优化的函数。

在编译脚本时,编译器将创建这些数据结构,并确保操作数和返回值显示到相同的位置,因此,对于像foo(bar())这样的调用,对bar的调用的返回值将与操作码的操作数相同,该操作数将在函数参数堆栈上推送参数。等等-争论堆!?您可能会问:由于每个操作码只接受两个操作数,所以我们不能直接传递函数参数,但是首先ZEND_SEND_*操作填充堆栈,然后使用该堆栈执行ZEND_DO_FCALL/ZEND_DO_FCALL_BY_NAME操作。

使用vld工具,我们可以转储这个已编译的表单:

代码语言:javascript
运行
复制
php -dextension=modules/vld.so -dvld.active -r 'foo(bar());'
Finding entry points
Branch analysis from position: 0
Return found
filename:       Command line code
function name:  (null)
number of ops:  6
compiled vars:  none
line     # *  op                           fetch          ext  return  operands
---------------------------------------------------------------------------------
   1     0  >   INIT_FCALL_BY_NAME                                       'foo', 'foo'
         1      INIT_FCALL_BY_NAME                                       'bar', 'bar'
         2      DO_FCALL_BY_NAME                              0          
         3      SEND_VAR_NO_REF                               4          $0
         4      DO_FCALL_BY_NAME                              1          
         5    > RETURN                                                   null

branch: #  0; line:     1-    1; sop:     0; eop:     5
path #1: 0, 

下一步是执行。这本质上就是这个循环(缩短):

代码语言:javascript
运行
复制
    while (1) {
            if ((ret = OPLINE->handler(execute_data TSRMLS_CC)) > 0) {
                    switch (ret) {
                            case 1:
                                    return;
                                    /* ... */
                            default:
                                    break;
                    }
            }

    }

OPLINE表示我们的zend_op结构数组的当前元素。每个op都有一个可执行的处理程序。(为了查看正确的循环:它是在zend_vm_execute.h中由zend_vm_gen.php生成的zend_vm_execute.skl和zend_vm_def.h),如果处理程序返回一个脚本/函数/.我们目前正在执行返回,这样循环就结束了。

可以像这样定义一个简单的操作码处理程序(请参阅zend_vm_def.h):

代码语言:javascript
运行
复制
ZEND_VM_HANDLER(1, ZEND_ADD, CONST|TMP|VAR|CV, CONST|TMP|VAR|CV)
{
        USE_OPLINE
        zend_free_op free_op1, free_op2;

        SAVE_OPLINE();
        fast_add_function(&EX_T(opline->result.var).tmp_var,
                GET_OP1_ZVAL_PTR(BP_VAR_R),
                GET_OP2_ZVAL_PTR(BP_VAR_R) TSRMLS_CC);
        FREE_OP1();
        FREE_OP2();
        CHECK_EXCEPTION();
        ZEND_VM_NEXT_OPCODE();
}

此代码看起来类似于C,但已填充到前面提到的gen脚本中。我们看到这是操作码1,它的名字是ZEND_ADD,它代表一个加法。它需要两个操作数,它们都可以是任意类型的(常量、临时变量、任意变量、编译器缓存变量)。在操作结束时,指针被设置为数组中的下一个操作码。

票数 1
EN
页面原文内容由Software Engineering提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://softwareengineering.stackexchange.com/questions/231054

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档