通过逆向和调试深入EVM 第 5 篇 - EVM如何处理 if/else/for/functions
通过逆向和调试深入EVM #5 - EVM如何处理 if/else/for/functions
在这篇文章中,我们将讨论执行流程。像if/for或嵌套函数这样的语句是如何被EVM在汇编中处理的?
让我们来了解一下!
这是我们关于通过逆向和调试深入EVM 的第 5 篇,在这里你可以找到之前文章和接下来文章:
这是我们第一个关于逆向 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)。
如果我们使用一个更复杂的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
这个结构看起来与我们已经看到的东西相似。尽管相当长,但它非常简单。这只是两个不同模块的重复。
它验证stack(0)中的值是否等于一个 if else
中间语句。如果不是,它JUMP
到下一个中间条件,下一个条件继续相同的处理。。
如果是,它不JUMP
并执行SSTORE(模块2)。
通过将槽和值推入堆栈并调用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
}
}
}
}
与其他编程语言相反,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;
}
}
}
这次反汇编是比较难研...
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!