您的位置:首页 >科技 >

BIGBEECOIN-发明与运转时刻代码解析

BIGBEECOIN-发明与运转时刻代码解析

正在解构一个简略的solidity智能合约的EVM字节码。

今日,让BIGBEECOIN开端用“分而治之”的战略来拆解智能合约的杂乱代码吧。BIGBEECOIN在介绍性的前言中说过,这个反汇编的代码其实十分初级,但与原始字节码比较会比较易读。

请确保已在遵从了我在前言中介绍的操作,把BasicToken的代码在remix编译器中进行了布置。

免责声明:本文中供给的一切阐明均受我自己对transaction运作办法的解说,不代表以太坊官方定见。

现在,让咱们聚集在JUMP、JUMPI、JUMPDES、RETURN和STOP操作吗,并疏忽一切其他的操作。每逢咱们发现不是其间之一的操作码时,咱们就疏忽它,并跳到下一条指令,不要被他们干涉。

当EVM履行代码时,是自上而下的次序,代码中没有其他进口点,一向从顶部开端履行。JUMP和JUMPI能够让代码跳转。JUMP获取仓库中的最上面的值,并将履行移动到该方位的指令。可是,方针方位有必要包括JUMPDEST操作码,不然履即将失利。这样做的仅有意图是:JUMPDEST将方位标记为有用的跳转方针。JUMPI也彻底相同,但仓库的第二个方位必定不能有“0”,不然就没有跳转。所以这是一个有条件的跳转,STOP是让智能合约彻底中止的指令,RETURN则是暂停智能合约的履行,但回来EVM内存的一部分数据,这很便利。

所以,让咱们开端解说代码时考虑到一切这些。在Remix的调试器中,将“ transaction”的滑块滑到最左面。你能够运用Step Into按钮(看起来像一个向下的小箭头)并按照阐明进行操作。

前面的指令能够疏忽,直接到第11条指令,咱们找到了第一条JUMPI。假如它没有跳转,它将持续经过指令12到15并终究进入REVERT,接着将中止履行。但假如跳转,它将越过这些指令到方位16(十六进制0x0010,它在指令8被压入仓库)。指令16是一个JUMPDEST。

持续单步履行操作码,直到“ transaction”滑块一向向右。刚刚发作了许多等等,但只要在68的方位才干找到RETURN操作码(以及STOP指令69中的操作码,以防万一)。这很古怪。假如您考虑一下,本智能合约的操控流程将一向在指令15或68完毕。咱们刚刚完结它并确认没有其他或许的流程,那么剩余的指令是什么?(假如您滑动“ 指令”面板,您将看到代码在方位566处完毕)。

咱们刚刚遍历的指令集(0到69)便是所谓的合约的“创立代码”。它永久不会成为智能合约代码自身的一部分,但仅在创立智能合约的买卖期间由EVM履行一次。咱们很快就会发现,这段代码担任设置创立的合约的初始状况,以及回来其运转时代码的副本。剩余的497条指令(70到566),正如咱们所见,履行流程永久不会到达的,正是这些代码将成为已布置智能合约的一部分。

接下来,咱们对代码进行第一次拆分:把创立时的代码与运转时的代码区分隔。

创立部分

现在,咱们将深入研究代码的创立部分。

图1.解构BasicToken.sol的创立时EVM字节码

这是本文中要了解的最重要的概念。创立代码在业务中履行,该业务回来运转时代码的副本,该副本是智能合约的实践代码。正如咱们将看到的,结构函数是创立代码的一部分,而不是运转时代码的一部分。智能合约的结构函数是创立代码的一部分; 一旦布置,它将不会呈现在智能合约的代码中。

这种法力是怎么发作的?这便是咱们现在要逐渐剖析的内容。

好的。所以现在咱们的问题被简化为了解这些与创立时代码相对应的70条指令。

让咱们从头选用自上而下的办法,这次了解一切指令,而不是越过任何指令。首要,让咱们重视运用PUSH1和MSTORE操作码的指令0到2 。

图2.闲暇内存指针EVM字节码结构

PUSH1只需将一个字节压入仓库顶部,而MSTORE从仓库中抓取终究两个项并将其间一个存储在内存中:

mstore(0x40, 0x80)

| |

| What to store.

Where to store.

(in memory)

留意:上面的代码片段是Yul-ish代码。留意它是怎么从左到右耗费仓库中的元素,总是首要耗费仓库顶部的元素。

这是将数字0x80(十进制128)存储在方位0x40(十进制64)的方位。

在咱们现在评论的问题中,不必去管它,假如有必要有一个原因,我后面会解说。

现在,在Remix的Debugger选项卡中翻开Stack 以及Memory的面板,以便在逐渐检查这些指令时能够可视化。

你或许想知道:指令1和3发作了什么?PUSH是仅有由两个或多个字节组成的EVM指令。所以,PUSH 80是两条指令。所以咱们揭开了谜底:指令1是0x80,而指令3的 0x40。

接下来我会阐明从5到15的指令。

图3.non-payable检查EVM字节码结构。

在这儿,又有一大堆的新的操作码:CALLVALUE,DUP1,ISZERO,PUSH2,和REVERT。CALLVALUE推送创立业务中触及的wei的数量,DUP1仿制仓库中的第一个元素,假如仓库的最高值为零,ISZERO则将1推送到仓库,PUSH2就像PUSH1,但它将两个字节推送到仓库,而REVERT则是中止履行。

那么这儿发作了什么?在Solidity中,咱们能够像这样编写这个汇编:

if(msg.value!= 0)revert();

这段代码实践上不是咱们原始Solidity源的一部分,而是由编译器注入的,因为咱们没有将结构函数声明为payable。在Solidity的最新版别中,未清晰声明为payable的函数不能接纳以太。回来到汇编代码,在指令11的JUMPI将越过指令12到15,假如没有相关的以太币,则跳转到16。不然,REVERT将以两个参数履行为0(意味着不会回来有用的数据)。

好的!让咱们中场歇息一下,来杯咖啡。

(下一部分会有点扎手,所以最好歇息几分钟。在你再次会集专心力之前,为自己预备一杯好咖啡。确保你了解咱们到目前为止看到的内容,因为下一部分有点杂乱。)

假如您想要另一种办法来可视化咱们刚刚完结的作业,请测验运用我构建的这个简略东西:solmap。它答应您实时编译Solidity代码,然后单击EVM操作码以杰出显现相关的Solidity代码。反汇编与Remix有点不同,但你应该能够经过比较来了解它。

预备持续前进了吗?接下来是指令16到37。请持续运用Remix的调试器。(记住,remix是你的好朋友^ ^)。

图4. EVM字节码结构,用于从智能合约字节码结尾附加的代码中检索结构函数参数

前四个指令(17到20)读取方位在存储器中的任何内容0x40,并将其推送到仓库。假如你能回想起来,那应该是数字0x80。下面是推0x20(十进制32)到仓库(指令21),并仿制该值(指令23),压栈0x0217(十进制535)(指令24),终究仿制第四个值(指令27),这应该是0x80。

在检查这样的EVM指令时,能够暂时不了解发作了什么。别忧虑,它会时不时呈现在你的脑际。

在指令28,履行了 CODECOPY,它承受三个参数:方针内存方位,用来存储仿制代码,从中仿制的指令编号,以及要仿制的代码的字节数。因而,在这种情况下,0x80从坐落代码中的字节方位(535,32字节代码长度的方针方位)开端。

假如检查整个反汇编代码,有566条指令。为什么这段代码企图仿制终究32个字节的代码呢?实践上,在布置包括参数的结构函数的合约时,参数作为原始十六进制数据附加到代码的结尾(向下翻滚“阐明”面板能够检查此内容)。在这种情况下,结构函数承受一个uint256参数,因而一切这些代码所做的便是将参数从附加在代码结尾的值仿制到内存中。

这些32条指令作为反汇编代码没有意义,可是它们用原始的十六进制表明:0x0000000000000000000000000...0000000000000000000002710。当然,这是咱们在布置智能合约时传递给结构函数的十进制值10000!

你能够一步一步地在Remix中重复这一部分,确保您了解刚刚发作的工作。终究成果应该是0x00..002710的方位,看到在内存中的数字0x80。

好,开端下一部分之前,我主张来一杯威士忌歇息一下。

威士忌韶光!

为什么主张你来一杯威士忌,因为从这儿开端,都是下坡路了。

下一组指令是29到35,更新内存地址0x40的值0x80到值0xa0,能够看到,它们将值偏移了0x20(32)字节。

现在咱们能够开端了解指令0到2了。Solidity追寻称为“空内存指针”的东西:即内存中咱们能够用来存储东西的当地,确保没有人会掩盖它(除非咱们犯了过错)。因而,因为咱们将数字10000存储在旧的闲暇内存方位,咱们经过向前移动32个字节来更新闲暇存储器指针。

即使是经验丰富的Solidity开发人员在看到“闲暇内存指针”或代码时也会感到困惑,mload(0x40, 0x80),这些仅仅说,“每逢咱们写一个新条目时,咱们将从这一点开端写入内存并保存偏移记载”。

Solidity中的每个函数,当编译为EVM字节码时,将初始化此指针。

在0x00到0x40之间的内存有什么,你或许不知道。没有。Solidity保存的一段内存,核算哈希值,咱们很快就会看到,这关于映射和其他类型的动态数据是必需的。

现在,在指令37中,MLOAD从存储器读取方位0x40并根本大将咱们10000的值从内存下载到仓库中,在那里它将是新的,而且能够鄙人一组指令中运用的。

免责声明:本文由用户上传,如有侵权请联系删除!