以太坊虚拟机EVM执行原理

以太坊虚拟机简介

  以太坊虚拟机(environment virtual machine,简称EVM),做用是将智能合约代码编译成可在以太坊上执行的机器码,并提供智能合约的运行环境。它是一个对外彻底隔离的沙盒环境,在运行期间不能访问网络、文件,即便不一样合约之间也有有限的访问权限。
  尽管区块链是可编程的,可是因为其脚本系统支持的功能有限,基于区块链作应用开发是一件颇有难度的事情。而以太坊是基于区块链底层技术进行封装,完善,其中很重要的一个革新就是以太坊虚拟机及面向合约的高级编程语言solidity,这使得开发者能够专一于应用自己,更方便、快捷的开发去中心化应用程序,同时也大大下降了开发难度。git

以太坊EVM的特色

  • EVM是一种基于栈的虚拟机(区别于基于寄存器的虚拟机),用于编译、执行智能合约
  • EVM是图灵完备的(图灵完备是指:具备无限存储能力的通用物理机器或编程语言,简单来讲就是能够解决一切可计算的问题)
  • EVM是一个彻底隔离的环境,在运行期间不能访问网络、文件,即便不一样合约之间也有有限的访问权限
  • 操做数栈调用深度为1024
  • 机器码长度一个字节,最多能够有256个操做码

什么是基于栈的虚拟机

  以太坊虚拟机是一种基于栈的虚拟机,因此要弄清以太坊虚拟机原理,咱们就必须了解什么是基于栈的虚拟机。首先咱们来介绍下虚拟机须要实现的功能:github

  • 取指令,其中指令来源于内存
  • 译码,决定指令类型(执行何种操做)。另外译码的过程要包括从内存中取操做数
  • 执行。指令译码后,被虚拟机执行(其实最终都会借助于物理机资源)

虚拟机分为两种:基于栈的虚拟机和基于寄存器的虚拟机。基于栈的虚拟机有几个重要的特性:实现简单、可移植,这也是为何以太坊选择了基于栈的虚拟机。编程

在基于栈的虚拟机中,有个重要的概念:操做数栈,数据存取为先进先出。全部的操做都是直接与操做数栈直接交互,例如:取数据、存数据、执行操做等。这样有一个好处:能够无视具体的物理机器架构,特别是寄存器,可是缺点也很明显,速度慢,不管什么操做都须要通过操做数栈。segmentfault

咱们举个简单的例子来讲明基于栈的虚拟机是如何执行操做的,例如咱们须要执行a = b + c的运算,那么在基于栈的虚拟机上会编译生成相似于下面的字节码指令:数组

I2: LOAD b
I1: LOAD c
I3: ADD
I4: STORE a

具体的执行流程为:网络

  1. 从内存中加载变量b到操做数栈
  2. 从内存中加载变量c到操做数栈
  3. 从操做数栈弹出前两个元素,执行累加
  4. 将计算后的数值压入栈顶
  5. 将栈顶的数值取出放入内存中

以太坊虚拟机如何执行智能合约

上面咱们简单介绍了基于栈的虚拟机是如何执行操做的,以太坊虚拟机的执行过程也是相似,咱们来详细介绍下。以以下的智能合约为例架构

pragma solidity ^0.4.0;

contract test {

    uint public c;

    function add(uint a) public returns (uint){
        uint b = 100;
        c = a + b;
        return c;
    }

}

使用solc编译编译该文件,执行命令为:solc --asm test.sol,生成的字节码以下,示例使用的solc版本为0.4.25,solc版本不一样编译后的字节码也可能会有所差别编程语言

接下来咱们使用stack:[]表示操做数栈,左侧是栈顶,store:{}表示局部变量表。这里咱们以add函数为例,来解析EVM执行过程,因为编译后的内容不少,咱们只截取add函数对应的字节码函数

======= test.sol:test =======
EVM assembly:
  /* "test.sol":25:177  contract test {... */
  mstore(0x40, 0x80)
  callvalue
  ...
tag_1:
  /* "test.sol":25:177  contract test {... */
  pop
  dataSize(sub_0)
  dup1
  dataOffset(sub_0)
  0x0
  codecopy
  0x0
  return
stop

sub_0: assembly {
     ...
        /* "test.sol":66:174  function add(uint a) public returns (uint){... */
    tag_6:
        /* "test.sol":103:107  uint */
        // push 0 到栈的第1位,stack:[0]
      0x0
        /* "test.sol":118:124  uint b */
        // 复制栈的第1位压入栈顶,stack:[0, 0]
      dup1
        /* "test.sol":127:130  100 */
        // push 100 到栈的第1位,stack:[100, 0, 0]
      0x64
        /* "test.sol":118:130  uint b = 100 */
        // 将第2位和栈顶元素互换,stack:[0, 100, 0]
      swap1
        // 弹出栈顶元素,stack:[100, 0]
      pop
        /* "test.sol":148:149  b */
        // 复制栈的第1位压入栈顶,stack:[100, 100, 0]
      dup1
        /* "test.sol":144:145  a */
        // a的值在运行时才能肯定
        // 复制栈的第4位压入栈顶,stack:[x, 100, 100, 0]
      dup4
        /* "test.sol":144:149  a + b */
        // 取出栈顶的2个元素执行add操做,将结果压入栈顶,stack:[x+100, 100, 0]
      add
        /* "test.sol":140:141  c */
        // push 0 到栈的第1位,stack:[0, x+100, 100, 0]
      0x0
        /* "test.sol":140:149  c = a + b */
        // 复制栈的第2位压入栈顶,stack:[x+100, 0, x+100, 100, 0]
      dup2
        // 将第2位和栈顶元素互换,stack:[0, x+100, x+100, 100, 0]
      swap1
        // 取出栈顶前2位放入局部变量表中,stack:[x+100, 100, 0], store:{0: x+100}
      sstore
        // 弹出栈顶元素,stack:[100, 0]
      pop
        /* "test.sol":166:167  c */
        // 从局部变量表中取出第1个元素压入栈顶,stack:[x+100, 100, 0]
      sload(0x0)
        /* "test.sol":159:167  return c */
        // 将第3位和栈顶元素互换,stack:[0, 100, x+100]
      swap2
        // 弹出栈顶元素,stack:[100, x+100]
      pop
        /* "test.sol":66:174  function add(uint a) public returns (uint){... */
        // 弹出栈顶元素,stack:[x+100]
      pop
      swap2
      swap1
      pop
      jump    // out
        /* "test.sol":46:59  uint public c */
    tag_9:
      sload(0x0)
      dup2
      jump    // out

    auxdata: 0xa165627a7a723058208ca38ce847598da0c3a86f7e...
}

以太坊虚拟机字节码

在以太坊EVM中,字节码长度被限定在一个字节之内,也就是说最多能够有256个操做码,目前已经定义了144个操做码,还有100多个操做码能够扩展。
完整的操做码能够查看 以太坊EVM操做码
各操做码对应的指令能够查看 以太坊EVM操做码详细指令学习

为了方便查看,将部分指令的弹栈数、压栈数、Gas消耗整理为一行,左侧是操做码的字节码,对应的数组第一个值是操做码,第二个为弹栈数,第三个为压栈数,第四个为Gas消耗

opcodes = {
    0x00: ['STOP', 0, 0, 0],
    0x01: ['ADD', 2, 1, 3],
    0x02: ['MUL', 2, 1, 5],
    0x03: ['SUB', 2, 1, 3],
    0x04: ['DIV', 2, 1, 5],
    0x05: ['SDIV', 2, 1, 5],
    0x06: ['MOD', 2, 1, 5],
    0x07: ['SMOD', 2, 1, 5],
    0x08: ['ADDMOD', 3, 1, 8],
    0x09: ['MULMOD', 3, 1, 8],
    0x0a: ['EXP', 2, 1, 10],
    0x15: ['ISZERO', 1, 1, 3],
    0x16: ['AND', 2, 1, 3],
    0x17: ['OR', 2, 1, 3],
    0x18: ['XOR', 2, 1, 3],
    0x19: ['NOT', 1, 1, 3],
    0x1a: ['BYTE', 2, 1, 3],
    0x20: ['SHA3', 2, 1, 30],
    0x30: ['ADDRESS', 0, 1, 2],
    0x31: ['BALANCE', 1, 1, 20],  # now 400
    0x32: ['ORIGIN', 0, 1, 2],
    0x33: ['CALLER', 0, 1, 2],
    0x34: ['CALLVALUE', 0, 1, 2],
    0x35: ['CALLDATALOAD', 1, 1, 3],
    0x36: ['CALLDATASIZE', 0, 1, 2],
    0x37: ['CALLDATACOPY', 3, 0, 3],
    0x38: ['CODESIZE', 0, 1, 2],
    0x39: ['CODECOPY', 3, 0, 3],
    0x3a: ['GASPRICE', 0, 1, 2],
    0x3d: ['RETURNDATASIZE', 0, 1, 2],
    0x3e: ['RETURNDATACOPY', 3, 0, 3],
    0x40: ['BLOCKHASH', 1, 1, 20],
    0x41: ['COINBASE', 0, 1, 2],
    0x42: ['TIMESTAMP', 0, 1, 2],
    0x43: ['NUMBER', 0, 1, 2],
    0x44: ['DIFFICULTY', 0, 1, 2],
    0x45: ['GASLIMIT', 0, 1, 2],
    0x50: ['POP', 1, 0, 2], // 从栈顶弹出一个元素
    0x51: ['MLOAD', 1, 1, 3],
    0x52: ['MSTORE', 2, 0, 3],
    0x53: ['MSTORE8', 2, 0, 3],
    0x54: ['SLOAD', 1, 1, 50],
    0x55: ['SSTORE', 2, 0, 0],
    0x56: ['JUMP', 1, 0, 8],
    0x57: ['JUMPI', 2, 0, 10],
    0x58: ['PC', 0, 1, 2],
    0x59: ['MSIZE', 0, 1, 2],
    0x5a: ['GAS', 0, 1, 2],
    0x5b: ['JUMPDEST', 0, 0, 1],
    0x60: ['PUSH1', 0, 1, 3], // 把第i个元素压入栈顶
    ......
    0x7f: ['PUSH32', 0, 1, 3],
    0x80: ['DUP1', 1, 2, 3], // 把第i个元素复制一份压入栈顶
    ...... z
    0x8f: ['DUP32', 16, 17, 3],
    0x90: ['SWAP1', 2, 2, 3], // 将栈顶的元素和第i+1个元素进行交换
    ......
    0x9f: ['SWAP32', 17, 17, 3],
    0xa0: ['LOG0', 2, 0, 375],
    0xa1: ['LOG1', 3, 0, 750],
    0xa2: ['LOG2', 4, 0, 1125],
    0xa3: ['LOG3', 5, 0, 1500],
    0xa4: ['LOG4', 6, 0, 1875],
}

以操做码add(0x1)为例: 0x01: ['ADD', 2, 1, 3]`,具体含义以下

0x01 表示操做码对应的数值,
2 表示从栈顶弹出的元素个数
1 表示计算完以后压栈数
3 表示执行该操做须要花费gas数



欢迎订阅「K叔区块链」 - 专一于区块链技术学习

博客地址: http://www.jouypub.com
简书主页: https://www.jianshu.com/u/756c9c8ae984
segmentfault主页: https://segmentfault.com/blog/jouypub
腾讯云主页: https://cloud.tencent.com/developer/column/72548
相关文章
相关标签/搜索