以太坊虛擬機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
相關文章
相關標籤/搜索