Solidity原理(五):EVM Bytecode文件结构以及如何执行

pragma solidity ^0.4.22;
contract Demo{
	uint public value1 = 0;
	uint public value2 = 0;
	
	function A(uint v) public returns(uint){
	    value1 += v;
	    return value1;
	}
	function B(uint v) public{
	    value2 += A(v);
	}
}

这篇文章上面的智能合约来做例子,由于Bytecode过长就不上传,可以将该代码贴到http://remix.ethereum.org/#optimize=false&version=soljson-v0.4.22+commit.4cb486ee.js,直接点击右侧的Details来查看Bytecode


下面开始解释一下Bytecode的结构:


从上面的图来看,Bytecode由两部分构成。第一部分的.code包含了一些smart contract初始化的代码,比如构造函数,state variable(全局变量)的赋值等操作。区块链上,这些都是EOA在部署合约时就执行完成的,在区块链浏览器,如Etherscan,都是无法看到这部分的代码的(某些开源合约会公开这部分的信息,默认是没有的)。


从.data开始,是smart contract的runtime bytecode,也就是在区块链上保存的合约的bytecode。想要获得该部分的bytecode,可以安装solidity(https://github.com/ethereum/solidity),通过命令 solc --bin-runtime filePath获得。


Remix的结构有点不太一样,是由若干个tag组成的,每个tag由若干个基本块组成。以JUMPDEST或者结束指令(RETURN,REVERT,STOP)划分。.code部分是Bytecode的入口,这部分的指令包含了所有能够被外部调用的函数的函数签名和跳转pc值。


上面的5个框分别是该合约的5个跳转函数。可能会奇怪合约就2个函数,为何会有5个可跳转函数。这5个跳转函数分别是:1. fallback(回退函数),2个public全局变量,2个public函数。

首先解释一下回退函数,在EVM中,回退函数是唯一一个未命名的函数,可以发现其他4个框前面都有一个函数签名,如第二个框的3033413B,只有fallback function没有。因此如果我们调用了一个合约中没有的函数,没有一个函数签名能满足,接下来的四个框都不会满足跳转条件,因此会通过fall to的形式执行tag 1,tag 1也就是fallback函数的开始位置。

接下来说一下什么是函数签名。函数签名是一个4byte的hash值,用来唯一标识smart contract中的函数。它是通过sha3("functionName(type1, type2)"),取前4bytes得到的。也就是说该函数签名只与函数名,函数类型有关。

总结一下.code部分,该部分包含了合约能调用的所有函数的跳转地址,从上图中提现就是tag1-5. tag 1-5分别是5个函数的起始位置。


下面用函数B为例,解释一下EVM的bytecode是如何跳转的

1. 要调用函数B,首先EVM会接受到函数签名(DAC0EB07),在.code部分中,跳转到tag 5

2. tag 5是函数B的开始部分,tag 5中有一个JUMPI,假设跳转条件满足,EVM会跳转到tag 15,如果不满足条件,则会执行PUSH, DUP1, REVERT. REVERT是终止指令,程序终于。该部分通常是用来判断一个函数是否是payable的。比如CALLVALUE指令会得到transacation是否发了Ether,如果发了ether,ISZERO的结果就会是false,因此不会执行跳转

3. 执行tag 15, 执行到最后有一个JUMP指令,会从EVM stack读出一个值, 上一个push到stack的值是tag 17,因此跳转到tag 17

4. 执行tag 17,同tag 15,tag17最后的tag 15会使pc跳转到tag14(tag 14也就是函数A的函数体部分)

5. 执行tag 14,执行到最后有一个JUMP指令,这时JUMP指令读到的是tag 17中push的tag 20

6. 执行tag 20, tag20最后的JUMP指令,执行的是tag15中的push tag 16, 因此会跳转到tag 16。

7. 执行tag 16,执行到stop指令,程序终止。


以函数A为例:

1. 要调用函数A,首先EVM会接收到函数签名(A17A9E66),在.code部分中,跳转到tag 4

2. tag 4是函数A的开始部分,假设满足JUMPI的跳转条件,则跳转到tag 12,如果不满足,则继续执行下面的三个指令

3. tag 12代表函数读取参数的过程,函数B没有参数因此没有这一部分。最后由JUMP指令跳转到tag 14

4. 执行tag 14,最后的JUMP读取到的是tag 12中的PUSH tag 13

5. 执行tag 13, tag 13最后的终止指令是RETURN,代表函数执行结束并返回值。

阅读更多

更多精彩内容