以太坊虚拟机工作原理深入刨析(下)
字数 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.DelegateCallCallCode类似,但调用上下文不同:

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参数解析漏洞的攻击方式,通过在调用合约函数时传递较短的参数来欺骗合约并获取额外资金。

攻击原理

  1. EVM在解析函数参数时不会检查参数的实际长度
  2. 当参数长度不足时,EVM会从下一个参数的高位补0
  3. 这会导致amount参数左移,实际转账金额被放大

攻击示例

假设调用transfer函数,正常调用数据:

0xa9059cbb # transfer函数操作码
000000000000000000000000fedcba9876543210 # 目标地址
000000000000000000000000000000000000000000000000000000000000000a # 转账金额(10)

如果目标地址省略末尾的"00":

0xa9059cbb
000000000000000000000000fedcba987654321 # 短地址(缺少最后的00)
00000000000000000000000000000000000000000000000000000000000000a # 金额

EVM会将金额的高位0补到地址中,导致金额变为:

0x0a00 = 2560 (原本是0x000a = 10)

防御措施

  1. 严格校验地址长度是否为20字节
  2. 在合约中检查参数长度
  3. 使用SafeMath等库进行数学运算
  4. 前端进行输入验证

文末小结

本文深入解析了以太坊虚拟机的核心工作机制,包括:

  1. 合约调用的不同类型及其实现细节
  2. 原生合约的识别和执行流程
  3. 解释器的结构和运行原理
  4. EVM指令集和栈操作
  5. Gas费用的计算方式
  6. 短地址攻击的原理和防御方法

理解这些底层机制对于开发安全的智能合约和进行区块链安全分析至关重要。

以太坊虚拟机(EVM)工作原理深入解析 文章前言 本文是《以太坊虚拟机工作原理深入解析》系列的下篇,将深入探讨EVM的核心机制,包括合约调用、原生合约、解释器类、指令结构、栈操作以及安全攻击等内容。 源码分析:合约调用 EVM.CallCode EVM.CallCode 函数用于执行与指定地址关联的合约代码并处理必要的转账操作: RunPrecompiledContract函数 EVM.DelegateCall EVM.DelegateCall 与 CallCode 类似,但调用上下文不同: EVM.StaticCall EVM.StaticCall 禁止在调用期间修改状态: 原生合约 原生合约是预先编译好的合约,当地址为原生合约时直接进入 RunPrecompiledContract 执行: 原生合约定义 不同版本的以太坊定义了不同的原生合约集合: 解释器类 解释器数据结构定义: 解释器创建 NewEVMInterpreter 用于创建解释器: 指令结构 指令操作的数据结构定义: 指令集定义 不同版本的以太坊定义了不同的指令集: 操作指令示例 指令解释:Run方法 解释器的核心方法 Run 的执行流程: 操作指令定义 栈操作类 PUSH - 压栈操作 POP - 出栈操作 SWAP - 元素交换 DUP - 复制数据到栈顶 PEEK - 检索栈顶数据 BACK - 查看指定位置的数据 费用计算 不同操作需要消耗不同的Gas费用: 安全攻击:短地址攻击 基本介绍 短地址攻击(Short Address Attack)是一种利用EVM参数解析漏洞的攻击方式,通过在调用合约函数时传递较短的参数来欺骗合约并获取额外资金。 攻击原理 EVM在解析函数参数时不会检查参数的实际长度 当参数长度不足时,EVM会从下一个参数的高位补0 这会导致amount参数左移,实际转账金额被放大 攻击示例 假设调用 transfer 函数,正常调用数据: 如果目标地址省略末尾的"00": EVM会将金额的高位0补到地址中,导致金额变为: 防御措施 严格校验地址长度是否为20字节 在合约中检查参数长度 使用SafeMath等库进行数学运算 前端进行输入验证 文末小结 本文深入解析了以太坊虚拟机的核心工作机制,包括: 合约调用的不同类型及其实现细节 原生合约的识别和执行流程 解释器的结构和运行原理 EVM指令集和栈操作 Gas费用的计算方式 短地址攻击的原理和防御方法 理解这些底层机制对于开发安全的智能合约和进行区块链安全分析至关重要。