Solidity字节码Bytecode的理解

Tattoo

发布日期: 2020-08-14 14:53:07 浏览量: 173
评分:
star star star star star star star star star star
*转载请注明来自write-bug.com

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明
原文链接:
https://blog.csdn.net/Programmer_CJC/article/details/80217672
https://blog.csdn.net/Programmer_CJC/article/details/80218649
https://blog.csdn.net/Programmer_CJC/article/details/80219720

最近在学习solidity方面的知识,看到这几篇博文主要介绍evm bytecode的,通俗易懂,故转载分享。

一、从Bytecode角度分析,EVM如何在基本块之间跳转

1.1 BasicBlock

在解释EVM是如何执行之前,先来解释一下BasicBlock(基本块)。一个基本块由一系列的指令构成,有一个入口和一个出口,入口就是第一个指令,出口就是最后一个指令。

出口的类型有:

  • 条件跳转(JUMPI)

  • 非条件跳转(JUMP)

  • 结束指令(RETURN,REVERT)

  • 什么都没有,直接fall to下一个block

1.1.1 条件跳转(JUMPI)

EVM中条件跳转的指令是JUMPI,它会从stack中读取2个元素,分别代表跳转的条件和pc(programmer counter)。下面是一个JUMPI的跳转例子:

  • Block1: JUMPDEST CALLVALUE ISZERO PUSH2 0x0100 JUMPI

  • Block2: PUSH1 0x00 DUP1 REVERT

Block1由5个指令构成(PUSH2 0x0100是一个), JUMPDEST表明这个Block是一个跳转的起始位置,CALLVALUE代表从transaction中获得的值,比如用户发送的ether额度,就可以用该指令获得。ISZERO判断CALLVALUE获得的值是否为0, PUSH指令向stack中放入了一个值。最后执行到JUMPI,它从条件中读取了两个值:

  • ISZERO(CALLVALUE)

  • 0x0100

如果满足1,则跳转到0x0100指向的block, 否则继续执行下一个BasicBlock(Block2) (PS: 如果满足条件跳转之后,执行完跳转的Block会继续往下执行Block2,执行的方式是深度优先遍历的方式)。

1.1.2 非条件跳转(JUMP)

EVM中的非条件跳转由JUMP指令触发, 每次执行到JUMP指令时,都会从stack读出1个值,表示要跳转的pc。和JUMPI指令类似,执行完跳转块后,也会继续向下执行,执行方式是深度优先遍历。

1.1.3 Fall to

EVM的某些基本块没有跳转指令也没有结束指令,对于这些指令,执行完最后一个指令后会继续执行下一个指令。当然对于条件跳转来说,也会有fall to的情况。如在条件跳转中举的例子,在执行完Block1之后,会继续执行Block2。或者Block1的JUMPI跳转条件不满足,也会继续执行Block2。

二、EVM Bytecode文件结构以及如何执行

该小节用一个具体的smart contract以及对应的指令来具体解释EVM bytecode的文件结构以及bytecode如何执行。

  1. pragma solidity ^0.4.22;
  2. contract Demo{
  3. uint public value1 = 0;
  4. uint public value2 = 0;
  5. function A(uint v) public returns(uint){
  6. value1 += v;
  7. return value1;
  8. }
  9. function B(uint v) public{
  10. value2 += A(v);
  11. }
  12. }

上面的智能合约来做例子,由于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(programmer counter)值。

上面的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是如何跳转的

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

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

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

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

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

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

  • 执行tag 16,执行到stop指令,程序终止

以函数A为例

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

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

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

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

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

三、用solc编译smart contract,用evm反编译bytecode

首先需要安装solc和evm:

编译一个smart contract可以通过指令来得到bytecode:

  1. solc --bin-runtime filepath

反编译bytecode可以通过:

  1. evm --dissam bytecodeFilePath

反编译以后的文件如下:

前面的数字就是pc(programmer counter), 以20行的指令为例,0x008d代表21行的JUMPI跳转的pc值是141.

solc还有下面几个非常好用的指令,可以获得合约的ast,asm(汇编码),opcode,bin,abi,函数签名等:

上传的附件

发送私信

人生最好的三个词:久别重逢,失而复得,虚惊一场

93
文章数
34
评论数
最近文章
eject