Ethereum学习笔记 ---- 使用 Remix 调试功能理解 bytes 在 memory 中的布局
编写合约
在浏览器中打开 Remix IDE: https://remix.ethereum.org/,在 contracts
目录下创建如下合约:
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.8.0 <0.9.0;
contract MyTest {
function bytesInMemory() public pure returns (bytes memory) {
bytes memory a = "hello";
bytes memory b = "hi";
return bytes.concat(a, b);
}
function bytesArrayInMemmory(uint a) public pure returns (bytes[] memory) {
if (a == 0)
++a;
bytes[] memory result = new bytes[](a);
for(uint i = 0; i<a; i++){
result[i] = "hello world";
}
return result;
}
}
编译、部署、调用合约
调试交易
1. 调用函数 bytesInMemory()
,分析 bytes
的 Memory Layout
执行 RETURN 前的最后一刻,stack 快照如下
执行完毕时刻的 Memory Layout
{
"0x0": "0000000000000000000000000000000000000000000000000000000000000000\t????????????????????????????????",
"0x20": "0000000000000000000000000000000000000000000000000000000000000000\t????????????????????????????????",
"0x40": "0000000000000000000000000000000000000000000000000000000000000127\t????????????????????????????????",
"0x60": "0000000000000000000000000000000000000000000000000000000000000000\t????????????????????????????????",
"0x80": "0000000000000000000000000000000000000000000000000000000000000005\t????????????????????????????????",
"0xa0": "68656c6c6f000000000000000000000000000000000000000000000000000000\thello???????????????????????????",
"0xc0": "0000000000000000000000000000000000000000000000000000000000000002\t????????????????????????????????",
"0xe0": "6869000000000000000000000000000000000000000000000000000000000000\thi??????????????????????????????",
"0x100": "0000000000000000000000000000000000000000000000000000000000000007\t????????????????????????????????",
"0x120": "68656c6c6f686900000000000000000000000000000000000000000000000000\thellohi?????????????????????????",
"0x140": "0000000000002000000000000000000000000000000000000000000000000000\t?????? ?????????????????????????",
"0x160": "0000000000000768656c6c6f6869000000000000000000000000000000000000\t???????hellohi??????????????????",
"0x180": "0000000000000000000000000000000000000000000000000000000000000000\t????????????????????????????????"
}
将相关字节染上相同颜色,方便观察;黄色阴影背景部分数据为 return 返回的 ABI 编码后的结果
对 Memory Layout 的分析
mem[0x40:0x60)
:
- 空闲地址指针,值为 0x127; 从 0x127 开始存储的内容实际是返回给调用者的 ABI 编码的结果,由于不会再继续执行新命令了,所以没有更新这里的空闲地址指针。
mem[0x80:0xc0)
:
- a 在内存中的布局,前32个字节用于表示字节长度,是5;后续的5个字节是 'hello' 的字节码;后续 right-padding 为32字节的倍数。
mem[0xc0:0x100)
:
- b 在内存中的布局。
mem[0x100:0x127)
:
- bytes.concat(a, b) 拼接后的结果,字节长度为7;在对返回值进行ABI编码之前,也是 right-padding 为32字节的倍数。
mem[0x127:0x187)
:
-
将返回值进行 ABI 编码后的结果,共计 0x60 个字节 --- 对应
RETURN
命令执行时 stack 中的 【0x0127
,0x60
】; -
前 0x20 个字节的值为
0x20
,表明数据编码起始地址 offset 为0x20
; -
第二个 0x20 个字节的值为
0x07
,表明 bytes 长度为 7; -
第三个 0x20 个字节为 right-padding 后的字节编码。
2. 调用函数 bytesArrayInMemmory(4)
,分析 bytes[]
的 Memory Layout
执行 RETURN 前的最后一刻,stack 快照如下
执行完毕时刻的内存布局
{
"0x0": "0000000000000000000000000000000000000000000000000000000000000000\t????????????????????????????????",
"0x20": "0000000000000000000000000000000000000000000000000000000000000000\t????????????????????????????????",
"0x40": "0000000000000000000000000000000000000000000000000000000000000220\t??????????????????????????????? ",
"0x60": "0000000000000000000000000000000000000000000000000000000000000000\t????????????????????????????????",
"0x80": "0000000000000000000000000000000000000000000000000000000000000004\t????????????????????????????????",
"0xa0": "0000000000000000000000000000000000000000000000000000000000000120\t??????????????????????????????? ",
"0xc0": "0000000000000000000000000000000000000000000000000000000000000160\t????????????????????????????????",
"0xe0": "00000000000000000000000000000000000000000000000000000000000001a0\t??????????????????????????????? ",
"0x100": "00000000000000000000000000000000000000000000000000000000000001e0\t????????????????????????????????",
"0x120": "000000000000000000000000000000000000000000000000000000000000000b\t???????????????????????????????\u000b",
"0x140": "68656c6c6f20776f726c64000000000000000000000000000000000000000000\thello world?????????????????????",
"0x160": "000000000000000000000000000000000000000000000000000000000000000b\t???????????????????????????????\u000b",
"0x180": "68656c6c6f20776f726c64000000000000000000000000000000000000000000\thello world?????????????????????",
"0x1a0": "000000000000000000000000000000000000000000000000000000000000000b\t???????????????????????????????\u000b",
"0x1c0": "68656c6c6f20776f726c64000000000000000000000000000000000000000000\thello world?????????????????????",
"0x1e0": "000000000000000000000000000000000000000000000000000000000000000b\t???????????????????????????????\u000b",
"0x200": "68656c6c6f20776f726c64000000000000000000000000000000000000000000\thello world?????????????????????",
"0x220": "0000000000000000000000000000000000000000000000000000000000000020\t??????????????????????????????? ",
"0x240": "0000000000000000000000000000000000000000000000000000000000000004\t????????????????????????????????",
"0x260": "0000000000000000000000000000000000000000000000000000000000000080\t????????????????????????????????",
"0x280": "00000000000000000000000000000000000000000000000000000000000000c0\t????????????????????????????????",
"0x2a0": "0000000000000000000000000000000000000000000000000000000000000100\t????????????????????????????????",
"0x2c0": "0000000000000000000000000000000000000000000000000000000000000140\t????????????????????????????????",
"0x2e0": "000000000000000000000000000000000000000000000000000000000000000b\t???????????????????????????????\u000b",
"0x300": "68656c6c6f20776f726c64000000000000000000000000000000000000000000\thello world?????????????????????",
"0x320": "000000000000000000000000000000000000000000000000000000000000000b\t???????????????????????????????\u000b",
"0x340": "68656c6c6f20776f726c64000000000000000000000000000000000000000000\thello world?????????????????????",
"0x360": "000000000000000000000000000000000000000000000000000000000000000b\t???????????????????????????????\u000b",
"0x380": "68656c6c6f20776f726c64000000000000000000000000000000000000000000\thello world?????????????????????",
"0x3a0": "000000000000000000000000000000000000000000000000000000000000000b\t???????????????????????????????\u000b",
"0x3c0": "68656c6c6f20776f726c64000000000000000000000000000000000000000000\thello world?????????????????????",
"0x3e0": "0000000000000000000000000000000000000000000000000000000000000000\t????????????????????????????????"
}
将相关字节染上相同颜色,方便观察;黄色阴影背景部分数据为 return 返回的 ABI 编码后的结果
对 Memory Layout 的分析
mem[0x40:0x60)
:
- 空闲地址指针,值为 0x220;从 0x220 开始存储的内容实际是返回给调用者的 ABI 编码的结果,由于不会再继续执行新命令了,所以没有更新这里的空闲地址指针。
mem[0x80:0xa0)
:
- 动态数组 result 的元素数量,为4,因为我们是以 4 为参数调用的 bytesArrayInMemmory().
mem[0xa0:0xc0)
:
- result[0] 对应的 bytes 所在的起始地址,值为 0x120,表明 result[0] 对应的字符串起始地址在 0x120; 后续
mem[0xc0:0xe0)
、mem[0xe0:0x100)
、mem[0x100:0x120)
分别是 result[1] ~ result[3] 中存储的 bytes 起始地址的指针。
mem[0x120:0x160)
:
- result[0] 对应的 bytes 的实际编码;其中
mem[0x120:140)
是字符串的长度,mem[0x140:160)
是 right-padding 后的字符串。
后续的mem[0x160:0x1a0)
、mem[0x1a0:0x1e0)
、mem[0x1e0:0x220)
分别是 result[1] ~ result[3] 对应的 bytes 的实际编码。
mem[0x220:0x3e0)
:
-
对 result 进行 ABI 编码后的结果,共计 0x1c0 个字节 --- 对应 RETURN 命令执行时 stack 中的 【0x0220,0x1c0】。这一块数据编码是遵照 合约ABI规范 独立进行的编码,其中的 offset 与实际的 memory 地址无关,而是相对于这块数据自身计算出的 offset。
-
由于返回值类型
bytes[]
是动态类型,所以起始位置的mem[0x220:0x240)
是 head(X(1)),为 0x20,表明 0x20 个byte 后是bytes[]
的实际编码; -
mem[0x240:0x260)
= 4,表明bytes[]
有4个元素; -
mem[0x260:0x3e0)
是对bytes[]
4个元素的编码;-
mem[0x260:0x280)
= 0x80,是 result[0] 编码结果的起始位置相对于mem[0x260:0x3e0)
这块数据起始位置的 offset; -
mem[0x260:0x280)
= 0xc0,是 result[1] 编码结果的起始位置相对于mem[0x260:0x3e0)
这块数据起始位置的 offset; -
mem[0x260:0x280)
= 0x100,是 result[2] 编码结果的起始位置相对于mem[0x260:0x3e0)
这块数据起始位置的 offset; -
mem[0x260:0x280)
= 0x140,是 result[3] 编码结果的起始位置相对于mem[0x260:0x3e0)
这块数据起始位置的 offset; -
mem[0x2e0:0x320)
、mem[0x320:0x360)
、mem[0x360:0x3a0)
、mem[0x3a0:0x3e0)
分别是 result[0] ~ result[3] 的实际数据编码结果。
-