opcode
内联汇编中使用的所有变量都是值类型
内联汇编中,所有的类型都是uint256
solidity中,内存数据的元素总是占用32bytes倍数的大小空间
先是存储数组的元素个数,然后再是实际元素
预留内存
Solidity 预留了四个 32 字节的槽,具有特定的字节范围
1 2 3 4 5 6 7
(包括端点)使用如下: 0x00 - 0x3f(64 字节):散列方法的暂存空间 0x40 - 0x5f(32 字节):当前分配的内存大小(又名空闲内存指针) 0x60 - 0x7f(32 字节):零槽(零槽用作动态的初始值内存阵列,并且永远不应该写入) 不能保证之前没有使用过内存,因此你不能假设它的内容是零字节。没有内置的释放机制或释放分配的内存。 空闲内存指针将立即开始指向保留内存插槽之后的地址,指向0x80。尽管任何地址都可以访问(不仅仅是32字节的块),但Solidity总是以32字节块的形式使用内存,你也应该这样做:不要忘记为写入的每个新块增加32个指针!
比如下面的方法
1 2 3 4 5 6 7 8 9
function f (){ assembly { let freemem_pointer := mload(0x40) mstore(add(freemem_pointer,0x00),"36e5236fcd4c61044949678014f0d085") mstore(add(freemem_pointer,0x20),"36e5236fcd4c61044949678014f0d086") let arr1:= mload(freemem_pointer) //read first string mstore(add(freemem_pointer,0x40),arr1) //s3 } }
前面的代码在空闲内存空间中存储了两个字符串(两个内存字,每个 32 字节)。目标内存地址是通过将第一个偏移量 0 字节和第二个偏移量 0x20 添加到空闲内存指针地址(位于内存地址 0x40 中)来获得的。 在 EVM 中,内存中的前 6 个字被保留,0x40-0x50 内存字被分配给空闲内存指针。
mload&add
mem[p…(p+32))
add(x, y) :x + y
1 2 3 4 5 6 7 8 9 10 11 12 13 14
// 下面的 _input 作为形参,在内联汇编中,它是一个address,而不是类似指针的引用类型 function assemblyKeccak (bytes memory _input) public pure returns (bytes32 x) { assembly { x := keccak256(add(_input, 0x20), mload(_input)) } } // mload(_input): 获得_input这个地址的32字节的实际数据,即数组的元素个数 // 假如我们的 _input 是33字节的数据,那么内存的结构布局如下: // memory[_input] : Length 0x31(33) // memory[add(_input,0x20)]: 0x0101010101010101010101010101010101010101010101010101010101010101 // memory[add(_input,0x40)]: 0x0100000000000000000000000000000000000000000000000000000000000000 // 注意:_input是一个address,告诉在哪可以找到memory[_input] // add(_input, 0x20): 0x20是32,即跳过32字节的数据,即跳过数组的长度,直接获得数组的实际数据的address,可以把它当作_input[0]的address
sload&shr&and
sload(x):获取插槽x的数据
shr(x, y) :将 y 逻辑右移 x 位
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract test{ uint256 public xxxx = 10 & 3; //1010 & 0011 = 0010 = 2 uint256 public yyyy = 18 & 0xf; //1 0010 & 1111 = 0010 = 2 struct S { uint128 a; uint128 b; } S test1; function x() public returns (uint a, uint b, uint c){ test1 = S(5,10); // slot 0: 0x0000000000000000000000000000000a00000000000000000000000000000005 // 因为是数值类型,所以从低位排起 // 将结果3402823669209384634633746074317682114565转为16进制:a00000000000000000000000000000005 assembly { a:=sload(0) //3402823669209384634633746074317682114565 b:=sload(1) //0 c:=sload(0) //3402823669209384634633746074317682114565 } } function y() public returns (uint a, uint b, uint c, uint d, uint f, uint g){ test1 = S(5,10); assembly { let w := sload(0) a := and(w, 0xffffffffffffffffffffffffffffffff) //从低位开始按位与 b := shr(128, w) //将w逻辑右移128位,即变成了0x000000000000000000000000000000000000000000000000000000000000000a } }//a=5,b=10 }
.slot和.offset
1 2
// _left是形参 let rightAndLeftValues := sload(_left.slot) // 获取_left变量的所在的slot
1 2 3 4 5 6
uint248 _right; //状态变量 uint8 _left; //状态变量 // _left.offset = 31,即_left的实际数据是在其slot中的31bytes 偏移量之后 // 31 * 8 = 248 ,即逻辑右移了258位,把_right移除了 let leftValue := shr(mul(_left.offset, 8), rightAndLeftValues)
mstore
也就是说,将v的值赋给address=p之后的32字节
1 2 3 4
function toBytes(uint256 x) returns (bytes b) { b = new bytes(32); assembly { mstore(add(b, 32), x) } }
在 Solidity 中,bytes 是一个动态大小的字节数组:b = new bytes(32);
,这将创建一个名为b的变量,类型为bytes。我们现在知道b将被视为一个数组。
在 Solidity Assembly 中,变量是指向内存地址的指针:我们知道b会指向它的内存地址。
内存和存储以 32 字节为单位进行管理。
数组的第一个内存块存储该数组的长度:通过 3 和 4 我们现在知道b将指向它的长度,并且只有 32 个字节之后是b值开始的内存地址。
calldatacopy
下面的例子:得到calldata中的第一个形参数据
1 2 3 4 5 6 7 8
function parseMsgData() public view returns (bytes32 _address){ bytes32 _address; assembly { calldatacopy(0x0, 4, 32) _address := mload(0x0) } return _address; }
extcodesize
下面的方法确定了一件事:输入的形参这个地址,要么是EOA地址,要么会被构造器攻击
1 2 3 4 5
function isEOAorContract(address addr) returns (bool) { uint size; assembly { size := extcodesize(addr) } return size = 0; }
add&mod&addmod
1 2 3 4 5 6 7 8
function classicAddMod() public view returns (uint256 rvalue) { uint256 MAX_INT = 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff; assembly { rvalue := mod(add(MAX_INT, 1), 10) } }//return 0
先add,变成0,取模还是0
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
function opAddMod_1() public view returns (uint256 rvalue) { uint256 MAX_INT = 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff; assembly { rvalue := addmod(MAX_INT, 1, 10) } }// return 6 function opAddMod_2() public view returns (uint256 rvalue) { uint256 xxx = 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe; assembly { rvalue := addmod(xxx, 1, 10) } }// return 5
opAddMod_1:先add,变成2^256,然后mod 10,等于6
原文始发于微信公众号(ChaMd5安全团队):智能合约-内联汇编操作码