通过逆向和调试理解EVM #4:结束/中止执行的5种指令

通过调试理解EVM 第 4 篇,了解 结束/中止执行的5种指令

在EVM中,总共有5种方式来结束智能合约的执行。我们将在这篇文章中详细研究它们。让我们现在就开始吧!

这是通过逆向和调试理解EVM系列的第4篇,在这里你可以找到之前和接下来的部分:

1. STOP(停止)

我们将使用EVM中最简单的操作码来开始。

这是唯一一个消耗0Gas的操作码,顾名思义,它结束智能合约的执行,不返回任何数据。

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;

contract Test {
    function test() external {

    }
}

你可以拆解这个非常简单的智能合约来弄清楚发生了什么。(函数的执行从第45指令开始)

045 JUMPDEST |function signature discarded|
046 PUSH1 33 |0x33|
048 PUSH1 35 |0x35|0x33|
050 JUMP     |0x33|053 JUMPDEST |0x33|
054 JUMP     ||051 JUMPDEST ||
052 STOP     ||

在结束时经过2次跳转。内存中没有任何东西。没有数据被存储,堆栈只包含函数签名,因此没有数据被返回。

就这样简单。

2. RETURN(返回)

RETURN像STOP一样结束智能合约的执行,但与STOP不同,它也可能返回一些数据。我们将编译这个solidity代码:

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;

contract Test {
    function test() external returns(uint) {
        return(8)
    }
}

并对该函数进行反汇编:

045 JUMPDEST ||
046 PUSH1 08 |0x08|  the return value of test()
048 PUSH1 40 |0x40|0x08|
050 MLOAD    |0x80|0x08|  mload(0x40) mloads the free memory pointer
051 SWAP1    |0x08|0x80|
052 DUP2     |0x80|0x08|0x80|
053 MSTORE   |0x80|       mstore(0x80,0x08) store the return value in memory[0x80]
054 PUSH1 20 |0x20|0x80|
056 ADD      |0xa0|
057 PUSH1 40 |0x40|0xa0|
059 MLOAD    |0x80|0xa0|
060 DUP1     |0x80|0x80|0xa0|
061 SWAP2    |0xa0|0x80|0x80|
062 SUB      |0x20|0x80|
063 SWAP1    |0x80|0x20|
064 RETURN   ||

在指令45和50之间,EVM mload(0x40),它返回80。

备注:45 、50 表示第几个字节数上的指令,下面都使用这种简写方式。

在指令51和53之间, EVM mstore(0x80,0x08),80是空闲内存地址,8是test函数的返回值。

在指令54到56之间,EVM在之前的结果(80)上加上20,等于a0(20=十进制的32,因为这是一个内存插槽的大小,这里只有一个返回值)。

在指令57和62之间,它在40处重新加载内存(mload(0x40)),并将结果与0xa0(第56行的结果)相乘,即0x20。

这里没有非常有趣的东西。在指令64时,内存0x80槽中有0x08,栈中有80和20。这3个值是什么意思?

img

根据文档的内容。当被调用时:

Stack(0) = 80应包含返回数据在内存中的偏移量

Stack(1) = 20 应该包含返回数据的偏移后的大小。

这正是这个智能合约的情况,0x80和0xa0之间的内存(=80+20 的十六进制)包含函数测试的返回值(8)。

所以智能合约返回内存[Stack(0):Stack(0)+Stack(1)]

3. REVERT操作码(回退)

现在,我们来修改智能合约。

pragma solidity ^0.8.0;

contract Test {
    function test() external returns(uint) {
        revert("eight");
    }
}

你发现区别了吗?我没有使用return(),而是使用了revert(),参数是一个字符串(我不能在 "revert"中使用数字,solidity编译器不允许我编译)。

如果你调用test(),你应该看到一个错误,但调试仍然是可能的!

img

下面是test函数的反汇编:


069 JUMPDEST          ||
070 PUSH1 40          |0x40|
072 MLOAD             |0x80|
073 PUSH3 461bcd      |0x461bcd|0x80|
077 PUSH1 e5          |0xe5|0x461bcd|0x80|
079 SHL               |0x08c379a000...000|0x80|  binary shift 197 times (e5 in hex), YES a binary shift can modify hex numbers...
080 DUP2              |0x80|0x08c379a000...000|0x80|
081 MSTORE            |0x80|
082 PUSH1 20          |0x20|0x80|
084 PUSH1 04          |0x04|0x20|0x80|
086 DUP3              |0x80|0x04|0x20|0x80|
087 ADD               |0x84|0x20|0x80|
088 MSTORE            |0x80|
089 PUSH1 05          |0x05|0x80|
091 PUSH1 24          |0x24|0x05|0x80|
093 DUP3              |0x80|0x24|0x05|0x80|
094 A...

剩余50%的内容订阅专栏后可查看

0 条评论

请先 登录 后评论
翻译小组
翻译小组

首席翻译官

167 篇文章, 29224 学分