以太坊虚拟机工作原理深入刨析(下)
字数 1072 2025-08-22 12:23:24
以太坊虚拟机(EVM)工作原理深入解析
文章前言
本文是《以太坊虚拟机工作原理深入解析》系列的下篇,将深入探讨EVM的核心机制,包括合约调用、原生合约、解释器类、指令结构、栈操作以及安全攻击等内容。
源码分析:合约调用
EVM.CallCode
EVM.CallCode函数用于执行与指定地址关联的合约代码并处理必要的转账操作:
func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte, gas uint64, value *big.Int) (ret []byte, leftOverGas uint64, err error) {
// 递归调用检查
if evm.vmConfig.NoRecursion && evm.depth > 0 {
return nil, gas, nil
}
// 调用深度限制检查
if evm.depth > int(params.CallCreateDepth) {
return nil, gas, ErrDepth
}
// 余额检查
if !evm.Context.CanTransfer(evm.StateDB, caller.Address(), value) {
return nil, gas, ErrInsufficientBalance
}
var snapshot = evm.StateDB.Snapshot()
// 检查是否为原生合约
if p, isPrecompile := evm.precompile(addr); isPrecompile {
ret, gas, err = RunPrecompiledContract(p, input, gas)
} else {
addrCopy := addr
contract := NewContract(caller, AccountRef(caller.Address()), value, gas)
contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), evm.StateDB.GetCode(addrCopy))
ret, err = run(evm, contract, input, false)
gas = contract.Gas
}
if err != nil {
evm.StateDB.RevertToSnapshot(snapshot)
if err != ErrExecutionReverted {
gas = 0
}
}
return ret, gas, err
}
RunPrecompiledContract函数
func RunPrecompiledContract(p PrecompiledContract, input []byte, suppliedGas uint64) (ret []byte, remainingGas uint64, err error) {
gasCost := p.RequiredGas(input)
if suppliedGas < gasCost {
return nil, 0, ErrOutOfGas
}
suppliedGas -= gasCost
output, err := p.Run(input)
return output, suppliedGas, err
}
EVM.DelegateCall
EVM.DelegateCall与CallCode类似,但调用上下文不同:
func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []byte, gas uint64) (ret []byte, leftOverGas uint64, err error) {
// 递归和深度检查
if evm.vmConfig.NoRecursion && evm.depth > 0 {
return nil, gas, nil
}
if evm.depth > int(params.CallCreateDepth) {
return nil, gas, ErrDepth
}
var snapshot = evm.StateDB.Snapshot()
if p, isPrecompile := evm.precompile(addr); isPrecompile {
ret, gas, err = RunPrecompiledContract(p, input, gas)
} else {
addrCopy := addr
contract := NewContract(caller, AccountRef(caller.Address()), nil, gas).AsDelegate()
contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), evm.StateDB.GetCode(addrCopy))
ret, err = run(evm, contract, input, false)
gas = contract.Gas
}
if err != nil {
evm.StateDB.RevertToSnapshot(snapshot)
if err != ErrExecutionReverted {
gas = 0
}
}
return ret, gas, err
}
EVM.StaticCall
EVM.StaticCall禁止在调用期间修改状态:
func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte, gas uint64) (ret []byte, leftOverGas uint64, err error) {
// 递归和深度检查
if evm.vmConfig.NoRecursion && evm.depth > 0 {
return nil, gas, nil
}
if evm.depth > int(params.CallCreateDepth) {
return nil, gas, ErrDepth
}
var snapshot = evm.StateDB.Snapshot()
evm.StateDB.AddBalance(addr, big0)
if p, isPrecompile := evm.precompile(addr); isPrecompile {
ret, gas, err = RunPrecompiledContract(p, input, gas)
} else {
addrCopy := addr
contract := NewContract(caller, AccountRef(addrCopy), new(big.Int), gas)
contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), evm.StateDB.GetCode(addrCopy))
ret, err = run(evm, contract, input, true)
gas = contract.Gas
}
if err != nil {
evm.StateDB.RevertToSnapshot(snapshot)
if err != ErrExecutionReverted {
gas = 0
}
}
return ret, gas, err
}
原生合约
原生合约是预先编译好的合约,当地址为原生合约时直接进入RunPrecompiledContract执行:
func (evm *EVM) precompile(addr common.Address) (PrecompiledContract, bool) {
var precompiles map[common.Address]PrecompiledContract
switch {
case evm.chainRules.IsBerlin:
precompiles = PrecompiledContractsBerlin
case evm.chainRules.IsIstanbul:
precompiles = PrecompiledContractsIstanbul
case evm.chainRules.IsByzantium:
precompiles = PrecompiledContractsByzantium
default:
precompiles = PrecompiledContractsHomestead
}
p, ok := precompiles[addr]
return p, ok
}
原生合约定义
不同版本的以太坊定义了不同的原生合约集合:
// Homestead版本
var PrecompiledContractsHomestead = map[common.Address]PrecompiledContract{
common.BytesToAddress([]byte{1}): &ecrecover{},
common.BytesToAddress([]byte{2}): &sha256hash{},
common.BytesToAddress([]byte{3}): &ripemd160hash{},
common.BytesToAddress([]byte{4}): &dataCopy{},
}
// Byzantium版本
var PrecompiledContractsByzantium = map[common.Address]PrecompiledContract{
common.BytesToAddress([]byte{1}): &ecrecover{},
common.BytesToAddress([]byte{2}): &sha256hash{},
common.BytesToAddress([]byte{3}): &ripemd160hash{},
common.BytesToAddress([]byte{4}): &dataCopy{},
common.BytesToAddress([]byte{5}): &bigModExp{eip2565: false},
common.BytesToAddress([]byte{6}): &bn256AddByzantium{},
common.BytesToAddress([]byte{7}): &bn256ScalarMulByzantium{},
common.BytesToAddress([]byte{8}): &bn256PairingByzantium{},
}
// Istanbul版本
var PrecompiledContractsIstanbul = map[common.Address]PrecompiledContract{
common.BytesToAddress([]byte{1}): &ecrecover{},
common.BytesToAddress([]byte{2}): &sha256hash{},
common.BytesToAddress([]byte{3}): &ripemd160hash{},
common.BytesToAddress([]byte{4}): &dataCopy{},
common.BytesToAddress([]byte{5}): &bigModExp{eip2565: false},
common.BytesToAddress([]byte{6}): &bn256AddIstanbul{},
common.BytesToAddress([]byte{7}): &bn256ScalarMulIstanbul{},
common.BytesToAddress([]byte{8}): &bn256PairingIstanbul{},
common.BytesToAddress([]byte{9}): &blake2F{},
}
// Berlin版本
var PrecompiledContractsBerlin = map[common.Address]PrecompiledContract{
common.BytesToAddress([]byte{1}): &ecrecover{},
common.BytesToAddress([]byte{2}): &sha256hash{},
common.BytesToAddress([]byte{3}): &ripemd160hash{},
common.BytesToAddress([]byte{4}): &dataCopy{},
common.BytesToAddress([]byte{5}): &bigModExp{eip2565: true},
common.BytesToAddress([]byte{6}): &bn256AddIstanbul{},
common.BytesToAddress([]byte{7}): &bn256ScalarMulIstanbul{},
common.BytesToAddress([]byte{8}): &bn256PairingIstanbul{},
common.BytesToAddress([]byte{9}): &blake2F{},
}
// BLS预编译合约
var PrecompiledContractsBLS = map[common.Address]PrecompiledContract{
common.BytesToAddress([]byte{10}): &bls12381G1Add{},
common.BytesToAddress([]byte{11}): &bls12381G1Mul{},
common.BytesToAddress([]byte{12}): &bls12381G1MultiExp{},
common.BytesToAddress([]byte{13}): &bls12381G2Add{},
common.BytesToAddress([]byte{14}): &bls12381G2Mul{},
common.BytesToAddress([]byte{15}): &bls12381G2MultiExp{},
common.BytesToAddress([]byte{16}): &bls12381Pairing{},
common.BytesToAddress([]byte{17}): &bls12381MapG1{},
common.BytesToAddress([]byte{18}): &bls12381MapG2{},
}
解释器类
解释器数据结构定义:
type Config struct {
Debug bool // 启用调试
Tracer Tracer // 操作码记录器
NoRecursion bool // 禁用call, callcode, delegate call和create
EnablePreimageRecording bool // 启用SHA3/keccak预映像记录
JumpTable [256]*operation // EVM指令表
EWASMInterpreter string // 外部EWASM解释器选项
EVMInterpreter string // 外部EVM解释器选项
ExtraEips []int // 要启用的额外EIPS
}
type Interpreter interface {
Run(contract *Contract, input []byte, static bool) ([]byte, error)
CanRun([]byte) bool
}
解释器创建
NewEVMInterpreter用于创建解释器:
func NewEVMInterpreter(evm *EVM, cfg Config) *EVMInterpreter {
if cfg.JumpTable[STOP] == nil {
var jt JumpTable
switch {
case evm.chainRules.IsBerlin:
jt = berlinInstructionSet
case evm.chainRules.IsIstanbul:
jt = istanbulInstructionSet
case evm.chainRules.IsConstantinople:
jt = constantinopleInstructionSet
case evm.chainRules.IsByzantium:
jt = byzantiumInstructionSet
case evm.chainRules.IsEIP158:
jt = spuriousDragonInstructionSet
case evm.chainRules.IsEIP150:
jt = tangerineWhistleInstructionSet
case evm.chainRules.IsHomestead:
jt = homesteadInstructionSet
default:
jt = frontierInstructionSet
}
for i, eip := range cfg.ExtraEips {
if err := EnableEIP(eip, &jt); err != nil {
cfg.ExtraEips = append(cfg.ExtraEips[:i], cfg.ExtraEips[i+1:]...)
log.Error("EIP activation failed", "eip", eip, "error", err)
}
}
cfg.JumpTable = jt
}
return &EVMInterpreter{
evm: evm,
cfg: cfg,
}
}
指令结构
指令操作的数据结构定义:
type (
executionFunc func(pc *uint64, interpreter *EVMInterpreter, callContext *ScopeContext) ([]byte, error)
gasFunc func(*EVM, *Contract, *Stack, *Memory, uint64) (uint64, error)
memorySizeFunc func(*Stack) (size uint64, overflow bool)
)
type operation struct {
execute executionFunc // 操作函数
constantGas uint64 // 固定gas消耗
dynamicGas gasFunc // 动态gas计算函数
minStack int // 最小栈需求
maxStack int // 最大栈限制
memorySize memorySizeFunc // 内存大小计算
halts bool // 是否停止执行
jumps bool // 是否跳转
writes bool // 是否修改状态
reverts bool // 是否回滚状态
returns bool // 是否设置返回数据
}
指令集定义
不同版本的以太坊定义了不同的指令集:
// Berlin指令集
func newBerlinInstructionSet() JumpTable {
instructionSet := newIstanbulInstructionSet()
enable2929(&instructionSet) // 访问列表
return instructionSet
}
// Istanbul指令集
func newIstanbulInstructionSet() JumpTable {
instructionSet := newConstantinopleInstructionSet()
enable1344(&instructionSet) // ChainID操作码
enable1884(&instructionSet) // 重新定价读取操作码
enable2200(&instructionSet) // 网络计量的SSTORE
return instructionSet
}
操作指令示例
func opAdd(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
x, y := scope.Stack.pop(), scope.Stack.peek()
y.Add(&x, y)
return nil, nil
}
func opSub(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
x, y := scope.Stack.pop(), scope.Stack.peek()
y.Sub(&x, y)
return nil, nil
}
func opMul(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
x, y := scope.Stack.pop(), scope.Stack.peek()
y.Mul(&x, y)
return nil, nil
}
指令解释:Run方法
解释器的核心方法Run的执行流程:
func (in *EVMInterpreter) Run(contract *Contract, input []byte, static bool) (ret []byte, err error) {
// 增加调用深度
in.evm.depth++
defer func() { in.evm.depth-- }()
// 设置只读模式
if readOnly && !in.readOnly {
in.readOnly = true
defer func() { in.readOnly = false }()
}
// 重置返回数据
in.returnData = nil
// 检查合约代码是否为空
if len(contract.Code) == {
return nil, nil
}
// 初始化变量
var (
op OpCode
mem = NewMemory()
stack = newstack()
callContext = &ScopeContext{
Memory: mem,
Stack: stack,
Contract: contract,
}
pc = uint64(0)
cost uint64
)
// 主循环
steps :=
for {
steps++
if steps%1000 == && atomic.LoadInt32(&in.evm.abort) != {
break
}
// 调试模式处理
if in.cfg.Debug {
logged, pcCopy, gasCopy = false, pc, contract.Gas
}
// 获取操作码
op = contract.GetOp(pc)
operation := in.cfg.JumpTable[op]
if operation == nil {
return nil, &ErrInvalidOpCode{opcode: op}
}
// 栈检查
if sLen := stack.len(); sLen < operation.minStack {
return nil, &ErrStackUnderflow{stackLen: sLen, required: operation.minStack}
} else if sLen > operation.maxStack {
return nil, &ErrStackOverflow{stackLen: sLen, limit: operation.maxStack}
}
// 只读模式检查
if in.readOnly && in.evm.chainRules.IsByzantium {
if operation.writes || (op == CALL && stack.Back(2).Sign() != ) {
return nil, ErrWriteProtection
}
}
// 消耗固定gas
if !contract.UseGas(operation.constantGas) {
return nil, ErrOutOfGas
}
// 计算内存需求
var memorySize uint64
if operation.memorySize != nil {
memSize, overflow := operation.memorySize(stack)
if overflow {
return nil, ErrGasUintOverflow
}
if memorySize, overflow = math.SafeMul(toWordSize(memSize), 32); overflow {
return nil, ErrGasUintOverflow
}
}
// 消耗动态gas
if operation.dynamicGas != nil {
var dynamicCost uint64
dynamicCost, err = operation.dynamicGas(in.evm, contract, stack, mem, memorySize)
cost += dynamicCost
if err != nil || !contract.UseGas(dynamicCost) {
return nil, ErrOutOfGas
}
}
// 调整内存大小
if memorySize > {
mem.Resize(memorySize)
}
// 执行操作
res, err = operation.execute(&pc, in, callContext)
if operation.returns {
in.returnData = common.CopyBytes(res)
}
// 处理执行结果
switch {
case err != nil:
return nil, err
case operation.reverts:
return res, ErrExecutionReverted
case operation.halts:
return res, nil
case !operation.jumps:
pc++
}
}
return nil, nil
}
操作指令定义
// 0x0范围 - 算术操作
const (
STOP OpCode = iota
ADD
MUL
SUB
DIV
SDIV
MOD
SMOD
ADDMOD
MULMOD
EXP
SIGNEXTEND
)
// 0x10范围 - 比较操作
const (
LT OpCode = iota + 0x10
GT
SLT
SGT
EQ
ISZERO
AND
OR
XOR
NOT
BYTE
SHL
SHR
SAR
)
// 0x20范围 - SHA3
const (
SHA3 OpCode = 0x20
)
// 0x30范围 - 环境信息
const (
ADDRESS OpCode = 0x30 + iota
BALANCE
ORIGIN
CALLER
CALLVALUE
CALLDATALOAD
CALLDATASIZE
CALLDATACOPY
CODESIZE
CODECOPY
GASPRICE
EXTCODESIZE
EXTCODECOPY
RETURNDATASIZE
RETURNDATACOPY
EXTCODEHASH
)
栈操作类
PUSH - 压栈操作
func (st *Stack) push(d *uint256.Int) {
st.data = append(st.data, *d)
}
func (st *Stack) pushN(ds ...uint256.Int) {
st.data = append(st.data, ds...)
}
POP - 出栈操作
func (st *Stack) pop() (ret uint256.Int) {
ret = st.data[len(st.data)-1]
st.data = st.data[:len(st.data)-1]
return
}
SWAP - 元素交换
func (st *Stack) swap(n int) {
st.data[st.len()-n], st.data[st.len()-1] = st.data[st.len()-1], st.data[st.len()-n]
}
DUP - 复制数据到栈顶
func (st *Stack) dup(n int) {
st.push(&st.data[st.len()-n])
}
PEEK - 检索栈顶数据
func (st *Stack) peek() *uint256.Int {
return &st.data[st.len()-1]
}
BACK - 查看指定位置的数据
func (st *Stack) Back(n int) *uint256.Int {
return &st.data[st.len()-n-1]
}
费用计算
不同操作需要消耗不同的Gas费用:
func gasCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
var (
gas uint64
transfersValue = !stack.Back(2).IsZero()
address = common.Address(stack.Back(1).Bytes20())
)
if evm.chainRules.IsEIP158 {
if transfersValue && evm.StateDB.Empty(address) {
gas += params.CallNewAccountGas
}
} else if !evm.StateDB.Exist(address) {
gas += params.CallNewAccountGas
}
if transfersValue {
gas += params.CallValueTransferGas
}
memoryGas, err := memoryGasCost(mem, memorySize)
if err != nil {
return , err
}
var overflow bool
if gas, overflow = math.SafeAdd(gas, memoryGas); overflow {
return , ErrGasUintOverflow
}
evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas, gas, stack.Back())
if err != nil {
return , err
}
if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow {
return , ErrGasUintOverflow
}
return gas, nil
}
安全攻击:短地址攻击
基本介绍
短地址攻击(Short Address Attack)是一种利用EVM参数解析漏洞的攻击方式,通过在调用合约函数时传递较短的参数来欺骗合约并获取额外资金。
攻击原理
- EVM在解析函数参数时不会检查参数的实际长度
- 当参数长度不足时,EVM会从下一个参数的高位补0
- 这会导致amount参数左移,实际转账金额被放大
攻击示例
假设调用transfer函数,正常调用数据:
0xa9059cbb # transfer函数操作码
000000000000000000000000fedcba9876543210 # 目标地址
000000000000000000000000000000000000000000000000000000000000000a # 转账金额(10)
如果目标地址省略末尾的"00":
0xa9059cbb
000000000000000000000000fedcba987654321 # 短地址(缺少最后的00)
00000000000000000000000000000000000000000000000000000000000000a # 金额
EVM会将金额的高位0补到地址中,导致金额变为:
0x0a00 = 2560 (原本是0x000a = 10)
防御措施
- 严格校验地址长度是否为20字节
- 在合约中检查参数长度
- 使用SafeMath等库进行数学运算
- 前端进行输入验证
文末小结
本文深入解析了以太坊虚拟机的核心工作机制,包括:
- 合约调用的不同类型及其实现细节
- 原生合约的识别和执行流程
- 解释器的结构和运行原理
- EVM指令集和栈操作
- Gas费用的计算方式
- 短地址攻击的原理和防御方法
理解这些底层机制对于开发安全的智能合约和进行区块链安全分析至关重要。