EVM字节码是怎么表示函数的?

Posted by 何嘉浩 on October 18, 2018

You may find interesting:


2018.1.18区块链论文讨论班通知


Nothing at stake problem of PoS

EVM字节码是怎么表示函数的?

​ EVM 字节码是低级语言,函数的概念在这里面是不复存在的。但是通过对EVM字节码的观察,我们还是窥到solidity编译器是怎样一步一步把函数编译成EVM字节码的。书接上回,我们分析完EVM是怎么完成函数选择这个过程的,但是执行跳转到后到底会发生什么?

​ 这里继续以上节的事例来分析。我们智能合约假设经历函数选择器跳转至tag 3(也就是test2())函数的执行过程中。

tag 3			function test2() public{\n    ...
      JUMPDEST 			function test2() public{\n    ...
      CALLVALUE 			function test2() public{\n    ...
      DUP1 			olidity ^
      ISZERO 			a 
      PUSH [tag] 8			a 
      JUMPI 			a 
      PUSH 0			r
      DUP1 			o
      REVERT 			.18;\n
  1. tag 3 这部分主要是用于处理函数是否有 payable 这个属性的。solidity规定当函数并没有声明payable的时候,若用户通过这个函数往合约发送了以太币,这个函数是不会执行,并且最终会执行回滚操作。所有在line4会有ISZERO这个判断条件,用于判断用户是否会发送了以太币。若否,则进入下一步。这里需要注意的是,若函数显示声明了函数是payable,是不会出现这个过程的。
tag 8			a 
      JUMPDEST 			a 
      POP 			function test2(uint256 b) publ...
      PUSH [tag] 9			function test2(uint256 b) publ...
      PUSH 4			function test2(uint256 b) publ...
      DUP1 			function test2(uint256 b) publ...
      CALLDATASIZE 			function test2(uint256 b) publ...
      SUB 			function test2(uint256 b) publ...
      DUP2 			function test2(uint256 b) publ...
      ADD 			function test2(uint256 b) publ...
      SWAP1 			function test2(uint256 b) publ...
      DUP1 			function test2(uint256 b) publ...
      DUP1 			function test2(uint256 b) publ...
      CALLDATALOAD 			function test2(uint256 b) publ...
      SWAP1 			function test2(uint256 b) publ...
      PUSH 20			function test2(uint256 b) publ...
      ADD 			function test2(uint256 b) publ...
      SWAP1 			function test2(uint256 b) publ...
      SWAP3 			function test2(uint256 b) publ...
      SWAP2 			function test2(uint256 b) publ...
      SWAP1 			function test2(uint256 b) publ...
      POP 			function test2(uint256 b) publ...
      POP 			function test2(uint256 b) publ...
      POP 			function test2(uint256 b) publ...
      PUSH [tag] 10			function test2(uint256 b) publ...
      JUMP 			function test2(uint256 b) publ...

​ 2. 接着函数跳转到tag 8。tag 8主要是分为下面几个部分:

  • 执行一系列的POP操作,维持堆栈平衡

  • 往栈中压入函数执行完毕后应该返回的地址(这里指的是tag 9)

  • 处理输入的参数

  • 跳转至函数体中

    (其实当函数需要调用合约内部函数的时候,也是做类似的操作)

    tag 10			function test2(uint256 b) publ...
          JUMPDEST 			function test2(uint256 b) publ...
          PUSH 0			a
          DUP1 			a
          DUP2 			a++
          SLOAD 			a++
          DUP1 			a++
          SWAP3 			a++
          SWAP2 			a++
          SWAP1 			a++
          PUSH 1			a++
          ADD 			a++
          SWAP2 			a++
          SWAP1 			a++
          POP 			a++
          SSTORE 			a++
          POP 			a++
          POP 			function test2(uint256 b) publ...
          JUMP [out]			function test2(uint256 b) publ...
    
    1. 接下来就会进入到函数体执行函数体tag10逐条进行指令(这里主要是从存储中读出 a,递增,再保存进存储中),接下来跳入至之前已经压入栈的tag 9。因为这里并没有定义返回值,所有直接执行STOP结束函数。若是函数定义了返回值,就会在这个代码块里面处理返回值,并且最终以RETURN指令作为结束。
    tag 9			function test2(uint256 b) publ...
          JUMPDEST 			function test2(uint256 b) publ...
          STOP 			function test2(uint256 b) publ..