返回论坛

EVM 内存管理机制深度解析:从字节码到数据结构

查找币 学术研究 安全研究 Web3安全 区块链安全

查找币安全研究院

钱包恢复评估 | 链上取证分析 | Web3 事件响应
以合法授权、证据保全、隐私保护和可复核流程为前提,不要求用户在线提交完整私钥或助记词。

查看研究院 研究报告中心
## 引言 在上一篇文章中,我们探讨了 EVM 如何通过函数签名和调用栈来定位需要执行的字节码。今天,我们将深入合约内存的核心机制,解析 EVM 如何管理和操作内存数据。 ## 内存数据结构:字节数组的奥秘 合约内存本质上是一个简单的字节数组,支持以 32 字节(256 位)或 1 字节(8 位)为单位进行数据存储,但读取操作必须按 32 字节的固定块进行。这种设计由以下三个核心操作码支撑: - **MSTORE (x, y)**:从内存位置 `x` 开始存储一个 32 字节的 `y` 值 - **MLOAD (x)**:从内存位置 `x` 开始加载 32 字节到调用栈 - **MSTORE8 (x, y)**:在内存位置 `x` 存储一个 1 字节的 `y` 值(取栈值的低8位) 你可以将内存位置视为数组索引。当需要写入或读取超过 1 字节的数据时,只需连续操作相邻的索引位置即可。 ## 实践验证:EVM Playground 内存操作 通过 EVM Playground 我们可以直观地观察内存操作过程。以 `MSTORE8` 为例,当我们在位置 32(0x20)写入单字节 `0x22` 时,内存会发生如下变化: **操作前内存状态:** ``` 0x00: 0000000000000000000000000000000000000000000000000000000000000000 0x20: 0000000000000000000000000000000000000000000000000000000000000000 ``` **操作后内存状态:** ``` 0x00: 0000000000000000000000000000000000000000000000000000000000000000 0x20: 2200000000000000000000000000000000000000000000000000000000000000 ``` 注意:虽然只写入 1 字节,但内存从 32 字节扩展到了 64 字节。这引出了内存扩展的 Gas 开销问题。 ## 内存扩展机制与 Gas 成本 当合约写入未触及过的内存区域时,需要支付内存扩展的 Gas 开销。内存以 32 字节为增量进行扩展,前 724 字节呈线性增长,之后呈二次方增长。具体公式为: ``` cost = (a * a) / 512 + (3 * a) / 2 ``` 其中 `a` 是合约调用中写入的最大内存位置(以 32 字节字为单位)。例如,使用 1024 字节内存时,`a = 32`。 ## 内存的字节数组特性:MLOAD 的陷阱 继续调试,当我们从位置 33(0x21)执行 `MLOAD` 时,会发现一个关键特性:`MLOAD` 会从指定位置开始连续读取 32 字节。因此,即使我们只写入 1 字节,读取时也会返回包含该字节在内的完整 32 字节数据。 ``` MLOAD(0x21) 返回: 0x2200000000000000000000000000000000000000000000000000000000000000 ``` 这验证了内存是连续的字节数组,读取操作不受写入粒度的影响。 ## 空闲内存指针:0x40 的关键作用 在合约字节码的前 5 个字节中,我们经常看到对内存位置 0x40 的操作。这个位置存储着“空闲内存指针”,用于跟踪内存中可用的起始位置。 ```solidity // 合约示例 contract Storage { uint256 number; function store(uint256 num) public { number = num; } function retrieve() public view returns (uint256) { return number; } } ``` 当合约执行时,空闲内存指针初始化为 0x80(位置 0x40 存储的值)。这个指针随着内存分配而更新,确保新数据不会覆盖已有数据。 ## 内存分配实战:变量存储与偏移 以存储数组变量 `b` 为例,其内存布局如下: 1. **分配内存**:从空闲内存指针位置开始,分配所需空间 2. **偏移计算**:使用 `ADD` 操作码将偏移值加到变量内存位置 3. **数据写入**:通过 `MSTORE` 将值写入指定位置 例如,当执行 `b[0] = 1` 时: - 空闲内存指针指向 0x120 - 偏移量为 0,直接写入位置 0x120 - `MSTORE` 将值 `0x01` 存储到该位置 函数执行结束时,内存状态显示位置 0x120-0x13f(bytes 289-320)的值从 0 变为 1,验证了赋值操作的正确性。 ## 总结与展望 通过本文,我们深入理解了 EVM 内存管理的核心机制: - 内存是字节数组,支持 1 字节和 32 字节写入 - 读取必须按 32 字节块进行 - 内存扩展有明确的 Gas 成本模型 - 空闲内存指针(0x40)是内存分配的关键 在下一篇文章中,我们将深入合约存储机制,解析存储插槽包装(slot packing)的原理,揭开存储插槽的神秘面纱。 --- **本文由查找币安全团队整理发布** *参考文献:* - *EVM Illustrated (takenobu-hs.github.io)* - *以太坊黄皮书内存扩展公式*
在论坛中查看和回复