通过逆向和调试深入EVM #5 - EVM如何处理 if/else/for/functions

通过逆向和调试深入EVM 第 5 篇 - EVM如何处理 if/else/for/functions

通过逆向和调试深入EVM #5 - EVM如何处理 if/else/for/functions

在这篇文章中,我们将讨论执行流程。像if/for或嵌套函数这样的语句是如何被EVM在汇编中处理的?

让我们来了解一下!

这是我们关于通过逆向和调试深入EVM 的第 5 篇,在这里你可以找到之前文章和接下来文章:

1. 汇编中的IF/ELSE

这是我们第一个关于逆向 if/else 语句的例子,在没有优化器的情况下编译它,并以x=true调用函数flow()

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

contract Test {
    uint value = 0;
    function flow(bool x) external {
        if (x) {
            value = 4;
        } else {
            value = 9;
        }
    }
}

下面是该函数的完整反汇编代码:

062 JUMPDEST |0x01|stack after arguments discarded|
063 DUP1     |0x01|0x01|
064 ISZERO   |0x00|0x01|
065 PUSH1 4b |0x4b|0x00|0x01|
067 JUMPI    |0x01|
068 PUSH1 04 |0x04|0x01|
070 PUSH1 00 |0x00|0x04|0x01|
072 SSTORE   |0x01|
073 POP      
074 JUMP 
075 JUMPDEST 
076 PUSH1 09 
078 PUSH1 00 
080 SSTORE 
081 POP 
082 JUMP

当一个函数被调用时,它的参数每次都被放在堆栈中(我们将“稍后证明”),所以在EVM中x=true=1(因此 false=0 ),那么堆栈在Stack(0)包含1。

在第63和64字节的指令,堆栈被复制,ISZERO指令被调用。

备注:第x字节上的指令,后文简称 :指令 x.

该指令显然是在验证Stack(0)=0,如果是,那么1被推入堆栈,否则0被推入堆栈。

由于Stack(0)=1,那么0被推到堆栈中 | 0x00 | 0x01 |

之后4b也被推到了堆栈中。堆栈为 | 0x4b | 0x00 | 0x01 | , 然后JUMPI被调用

由于Stack(1)=0,EVM不会跳到 4b。

因此,我们可以很容易地推断出,如果堆栈中的第一个参数是0,那么EVM将跳到4b(十进制的75),否则EVM将继续执行流程。

在指令68和74之间,我们已经知道发生了什么:EVM将4存储在槽号0中。在指令75和81之间的代码相同:EVM将9存储在槽号0中。

在这两个 "结果 "之后,EVM都跳到了3C,执行结束。

事实上,每次有JUMPI指令时,在solidity中都有一个对应的IF语句(或WHILE/FOR)。

2. 汇编中的ELSE IF

如果我们使用一个更复杂的if语句呢?这次会有更多的 "else",但汇编代码会不会更复杂呢?

(剧透:其实没有)

编译这段代码(没有优化器)和solidity 0.8.7,用你想要的任何值调用流程。

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

contract Test {
    uint value = 0;
    function flow(uint x) external {
        if (x == 1) {
            value = 4;
        } else if (x == 2) {
            value = 9;
        } else if (x == 3) {
            value = 14;
        } else if (x == 4) {
            value = 19;
        } else {
            value = 24;
        }
    }
}

像往常一样,让我们来拆解这个函数 :

062 JUMPDEST 
063 DUP1 
064 PUSH1 01 
066 EQ 
067 ISZERO 
068 PUSH1 4e 
070 JUMPI 
071 PUSH1 04 
073 PUSH1 00 
075 SSTORE 
076 POP 
077 JUMP 
078 JUMPDEST 
079 DUP1 
080 PUSH1 02 
082 EQ 
083 ISZERO 
084 PUSH1 5e 
086 JUMPI 
087 PUSH1 09 
089 PUSH1 00 
091 SSTORE 
092 POP 
093 JUMP 
094 JUMPDEST 
095 DUP1 
096 PUSH1 03 
098 EQ 
099 ISZERO 
100 PUSH1 6e 
102 JUMPI 
103 PUSH1 0e 
105 PUSH1 00 
107 SSTORE 
108 POP 
109 JUMP 
110 JUMPDEST 
111 DUP1 
112 PUSH1 04 
114 EQ 
115 ISZERO 
116 PUSH1 7e 
118 JUMPI 
119 PUSH1 13 
121 PUSH1 00 
123 SSTORE 
124 POP 
125 JUMP 
126 JUMPDEST 
127 PUSH1 18 
129 PUSH1 00 
131 SSTORE 
132 POP 
133 JUMP

这个结构看起来与我们已经看到的东西相似。尽管相当长,但它非常简单。这只是两个不同模块的重复。

  1. 中间条件块(63-70、79-86、95-102、111-118)。

它验证stack(0)中的值是否等于一个 if else中间语句。如果不是,它JUMP到下一个中间条件,下一个条件继续相同的处理。。 如果是,它不JUMP 并执行SSTORE(模块2)。

  1. 我们已经非常熟悉了 SSTORE, 保存值到存储槽。

通过将槽和值推入堆栈并调用SSTORE。 一旦完成,EVM JUMP到另一个位置并结束执行。

如果所有的条件都不满足(i不等于1或2或3或4),则在指令127和133之间触发else语句,这是最后一个SSTORE块,但没有任何条件。

事实上,整个结构非常类似于EVM执行开始时的函数选择器,以选择符合函数签名的代码。(我们在第一篇里看到了)

总结一下这段代码,else if语句可以翻译成solidity中的多个嵌套的if,这和我们使用else if的结果完全一样。

if (x == 1) {
    // do something
} else {
    if (x == 2) {
        // do something
    } else {
        if (x == 3) {
            // do something
        } else {
            if (x == 4) {
                // do something
            } else {
                // do something
            }   
        }
    }
}

3. 汇编中的For循环

与其他编程语言相反,For语句在solidity中没有被广泛使用。

主要原因是,需要for语句的功能往往需要大量的Gas来执行功能,这使得智能合约无法使用。这与while语句的情况大致相同。

下面是我们要研究的代码,编译它,部署并用x=10调用。

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

contract Test {
    uint value = 0;
    function flow(uint x) external {
        for (uint i = 0; i < x; i++) {
            value += i;
        }
    }
}

这次反汇编是比较难研...

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

1 条评论

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

首席翻译官

167 篇文章, 29224 学分