land/vendor/github.com/go-interpreter/wagon/disasm/disasm.go

470 lines
15 KiB
Go

// Copyright 2017 The go-interpreter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package disasm provides functions for disassembling WebAssembly bytecode.
package disasm
import (
"bytes"
"encoding/binary"
"errors"
"io"
"math"
"github.com/go-interpreter/wagon/internal/stack"
"github.com/go-interpreter/wagon/wasm"
"github.com/go-interpreter/wagon/wasm/leb128"
ops "github.com/go-interpreter/wagon/wasm/operators"
)
// Instr describes an instruction, consisting of an operator, with its
// appropriate immediate value(s).
type Instr struct {
Op ops.Op
// Immediates are arguments to an operator in the bytecode stream itself.
// Valid value types are:
// - (u)(int/float)(32/64)
// - wasm.BlockType
Immediates []interface{}
NewStack *StackInfo // non-nil if the instruction creates or unwinds a stack.
Block *BlockInfo // non-nil if the instruction starts or ends a new block.
Unreachable bool // whether the operator can be reached during execution
// IsReturn is true if executing this instruction will result in the
// function returning. This is true for branches (br, br_if) to
// the depth <max_relative_depth> + 1, or the return operator itself.
// If true, NewStack for this instruction is nil.
IsReturn bool
// If the operator is br_table (ops.BrTable), this is a list of StackInfo
// fields for each of the blocks/branches referenced by the operator.
Branches []StackInfo
}
// StackInfo stores details about a new stack created or unwinded by an instruction.
type StackInfo struct {
StackTopDiff int64 // The difference between the stack depths at the end of the block
PreserveTop bool // Whether the value on the top of the stack should be preserved while unwinding
IsReturn bool // Whether the unwind is equivalent to a return
}
// BlockInfo stores details about a block created or ended by an instruction.
type BlockInfo struct {
Start bool // If true, this instruction starts a block. Else this instruction ends it.
Signature wasm.BlockType // The block signature
// Indices to the accompanying control operator.
// For 'if', this is the index to the 'else' operator.
IfElseIndex int
// For 'else', this is the index to the 'if' operator.
ElseIfIndex int
// The index to the `end' operator for if/else/loop/block.
EndIndex int
// For end, it is the index to the operator that starts the block.
BlockStartIndex int
}
// Disassembly is the result of disassembling a WebAssembly function.
type Disassembly struct {
Code []Instr
MaxDepth int // The maximum stack depth that can be reached while executing this function
}
func (d *Disassembly) checkMaxDepth(depth int) {
if depth > d.MaxDepth {
d.MaxDepth = depth
}
}
func pushPolymorphicOp(indexStack [][]int, index int) {
indexStack[len(indexStack)-1] = append(indexStack[len(indexStack)-1], index)
}
func isInstrReachable(indexStack [][]int) bool {
return len(indexStack[len(indexStack)-1]) == 0
}
var ErrStackUnderflow = errors.New("disasm: stack underflow")
// Disassemble disassembles the given function. It also takes the function's
// parent module as an argument for locating any other functions referenced by
// fn.
func Disassemble(fn wasm.Function, module *wasm.Module) (*Disassembly, error) {
code := fn.Body.Code
reader := bytes.NewReader(code)
disas := &Disassembly{}
// A stack of int arrays holding indices to instructions that make the stack
// polymorphic. Each block has its corresponding array. We start with one
// array for the root stack
blockPolymorphicOps := [][]int{[]int{}}
// a stack of current execution stack depth values, so that the depth for each
// stack is maintained indepepdently for calculating discard values
stackDepths := &stack.Stack{}
stackDepths.Push(0)
blockIndices := &stack.Stack{} // a stack of indices to operators which start new blocks
curIndex := 0
var lastOpReturn bool
for {
op, err := reader.ReadByte()
if err == io.EOF {
break
} else if err != nil {
return nil, err
}
logger.Printf("stack top is %d", stackDepths.Top())
opStr, err := ops.New(op)
if err != nil {
return nil, err
}
instr := Instr{
Op: opStr,
Immediates: [](interface{}){},
}
if op == ops.End || op == ops.Else {
// There are two possible cases here:
// 1. The corresponding block/if/loop instruction
// *is* reachable, and an instruction somewhere in this
// block (and NOT in a nested block) makes the stack
// polymorphic. In this case, this end/else is reachable.
//
// 2. The corresponding block/if/loop instruction
// is *not* reachable, which makes this end/else unreachable
// too.
isUnreachable := blockIndices.Len() != len(blockPolymorphicOps)-1
instr.Unreachable = isUnreachable
} else {
instr.Unreachable = !isInstrReachable(blockPolymorphicOps)
}
logger.Printf("op: %s, unreachable: %v", opStr.Name, instr.Unreachable)
if !opStr.Polymorphic && !instr.Unreachable {
top := int(stackDepths.Top())
top -= len(opStr.Args)
stackDepths.SetTop(uint64(top))
if top < -1 {
return nil, ErrStackUnderflow
}
if opStr.Returns != wasm.ValueType(wasm.BlockTypeEmpty) {
top++
stackDepths.SetTop(uint64(top))
}
disas.checkMaxDepth(top)
}
switch op {
case ops.Unreachable:
pushPolymorphicOp(blockPolymorphicOps, curIndex)
case ops.Drop:
if !instr.Unreachable {
stackDepths.SetTop(stackDepths.Top() - 1)
}
case ops.Select:
if !instr.Unreachable {
stackDepths.SetTop(stackDepths.Top() - 2)
}
case ops.Return:
if !instr.Unreachable {
stackDepths.SetTop(stackDepths.Top() - uint64(len(fn.Sig.ReturnTypes)))
}
pushPolymorphicOp(blockPolymorphicOps, curIndex)
lastOpReturn = true
case ops.End, ops.Else:
// The max depth reached while execing the current block
curDepth := stackDepths.Top()
blockStartIndex := blockIndices.Pop()
blockSig := disas.Code[blockStartIndex].Block.Signature
instr.Block = &BlockInfo{
Start: false,
Signature: blockSig,
}
if op == ops.End {
instr.Block.BlockStartIndex = int(blockStartIndex)
disas.Code[blockStartIndex].Block.EndIndex = curIndex
} else { // ops.Else
instr.Block.ElseIfIndex = int(blockStartIndex)
disas.Code[blockStartIndex].Block.IfElseIndex = int(blockStartIndex)
}
// The max depth reached while execing the last block
// If the signature of the current block is not empty,
// this will be incremented.
// Same with ops.Br/BrIf, we subtract 2 instead of 1
// to get the depth of the *parent* block of the branch
// we want to take.
prevDepthIndex := stackDepths.Len() - 2
prevDepth := stackDepths.Get(prevDepthIndex)
if op != ops.Else && blockSig != wasm.BlockTypeEmpty && !instr.Unreachable {
stackDepths.Set(prevDepthIndex, prevDepth+1)
disas.checkMaxDepth(int(stackDepths.Get(prevDepthIndex)))
}
if !lastOpReturn {
elemsDiscard := int(curDepth) - int(prevDepth)
if elemsDiscard < -1 {
return nil, ErrStackUnderflow
}
instr.NewStack = &StackInfo{
StackTopDiff: int64(elemsDiscard),
PreserveTop: blockSig != wasm.BlockTypeEmpty,
}
logger.Printf("discard %d elements, preserve top: %v", elemsDiscard, instr.NewStack.PreserveTop)
} else {
instr.NewStack = &StackInfo{}
}
logger.Printf("setting new stack for %s block (%d)", disas.Code[blockStartIndex].Op.Name, blockStartIndex)
disas.Code[blockStartIndex].NewStack = instr.NewStack
if !instr.Unreachable {
blockPolymorphicOps = blockPolymorphicOps[:len(blockPolymorphicOps)-1]
}
stackDepths.Pop()
if op == ops.Else {
stackDepths.Push(0)
blockIndices.Push(uint64(curIndex))
if !instr.Unreachable {
blockPolymorphicOps = append(blockPolymorphicOps, []int{})
}
}
case ops.Block, ops.Loop, ops.If:
sig, err := leb128.ReadVarint32(reader)
if err != nil {
return nil, err
}
logger.Printf("if, depth is %d", stackDepths.Top())
stackDepths.Push(stackDepths.Top())
// If this new block is unreachable, its
// entire instruction sequence is unreachable
// as well. To make sure that isInstrReachable
// returns the correct value, we don't push a new
// array to blockPolymorphicOps.
if !instr.Unreachable {
// Therefore, only push a new array if this instruction
// is reachable.
blockPolymorphicOps = append(blockPolymorphicOps, []int{})
}
instr.Block = &BlockInfo{
Start: true,
Signature: wasm.BlockType(sig),
}
blockIndices.Push(uint64(curIndex))
instr.Immediates = append(instr.Immediates, wasm.BlockType(sig))
case ops.Br, ops.BrIf:
depth, err := leb128.ReadVarUint32(reader)
if err != nil {
return nil, err
}
instr.Immediates = append(instr.Immediates, depth)
if int(depth) == blockIndices.Len() {
instr.IsReturn = true
} else {
curDepth := stackDepths.Top()
// whenever we take a branch, the stack is unwound
// to the height of stack of its *parent* block, which
// is why we subtract 2 instead of 1.
// prevDepth holds the height of the stack when
// the block that we branch to started.
prevDepth := stackDepths.Get(stackDepths.Len() - 2 - int(depth))
elemsDiscard := int(curDepth) - int(prevDepth)
if elemsDiscard < 0 {
return nil, ErrStackUnderflow
}
// No need to subtract 2 here, we are getting the block
// we need to branch to.
index := blockIndices.Get(blockIndices.Len() - 1 - int(depth))
instr.NewStack = &StackInfo{
StackTopDiff: int64(elemsDiscard),
PreserveTop: disas.Code[index].Block.Signature != wasm.BlockTypeEmpty,
}
}
if op == ops.Br {
pushPolymorphicOp(blockPolymorphicOps, curIndex)
}
case ops.BrTable:
if !instr.Unreachable {
stackDepths.SetTop(stackDepths.Top() - 1)
}
targetCount, err := leb128.ReadVarUint32(reader)
if err != nil {
return nil, err
}
instr.Immediates = append(instr.Immediates, targetCount)
for i := uint32(0); i < targetCount; i++ {
entry, err := leb128.ReadVarUint32(reader)
if err != nil {
return nil, err
}
instr.Immediates = append(instr.Immediates, entry)
var info StackInfo
if int(entry) == blockIndices.Len() {
info.IsReturn = true
} else {
curDepth := stackDepths.Top()
branchDepth := stackDepths.Get(stackDepths.Len() - 2 - int(entry))
elemsDiscard := int(curDepth) - int(branchDepth)
logger.Printf("Curdepth %d branchDepth %d discard %d", curDepth, branchDepth, elemsDiscard)
if elemsDiscard < 0 {
return nil, ErrStackUnderflow
}
index := blockIndices.Get(blockIndices.Len() - 1 - int(entry))
info.StackTopDiff = int64(elemsDiscard)
info.PreserveTop = disas.Code[index].Block.Signature != wasm.BlockTypeEmpty
}
instr.Branches = append(instr.Branches, info)
}
defaultTarget, err := leb128.ReadVarUint32(reader)
if err != nil {
return nil, err
}
instr.Immediates = append(instr.Immediates, defaultTarget)
var info StackInfo
if int(defaultTarget) == blockIndices.Len() {
info.IsReturn = true
} else {
curDepth := stackDepths.Top()
branchDepth := stackDepths.Get(stackDepths.Len() - 2 - int(defaultTarget))
elemsDiscard := int(curDepth) - int(branchDepth)
if elemsDiscard < 0 {
return nil, ErrStackUnderflow
}
index := blockIndices.Get(blockIndices.Len() - 1 - int(defaultTarget))
info.StackTopDiff = int64(elemsDiscard)
info.PreserveTop = disas.Code[index].Block.Signature != wasm.BlockTypeEmpty
}
instr.Branches = append(instr.Branches, info)
pushPolymorphicOp(blockPolymorphicOps, curIndex)
case ops.Call, ops.CallIndirect:
index, err := leb128.ReadVarUint32(reader)
if err != nil {
return nil, err
}
instr.Immediates = append(instr.Immediates, index)
if op == ops.CallIndirect {
reserved, err := leb128.ReadVarUint32(reader)
if err != nil {
return nil, err
}
instr.Immediates = append(instr.Immediates, reserved)
}
if !instr.Unreachable {
var sig *wasm.FunctionSig
top := int(stackDepths.Top())
if op == ops.CallIndirect {
if module.Types == nil {
return nil, errors.New("missing types section")
}
sig = &module.Types.Entries[index]
top--
} else {
sig = module.GetFunction(int(index)).Sig
}
top -= len(sig.ParamTypes)
top += len(sig.ReturnTypes)
stackDepths.SetTop(uint64(top))
disas.checkMaxDepth(top)
}
case ops.GetLocal, ops.SetLocal, ops.TeeLocal, ops.GetGlobal, ops.SetGlobal:
index, err := leb128.ReadVarUint32(reader)
if err != nil {
return nil, err
}
instr.Immediates = append(instr.Immediates, index)
if !instr.Unreachable {
top := stackDepths.Top()
switch op {
case ops.GetLocal, ops.GetGlobal:
top++
stackDepths.SetTop(top)
disas.checkMaxDepth(int(top))
case ops.SetLocal, ops.SetGlobal:
top--
stackDepths.SetTop(top)
case ops.TeeLocal:
// stack remains unchanged for tee_local
}
}
case ops.I32Const:
i, err := leb128.ReadVarint32(reader)
if err != nil {
return nil, err
}
instr.Immediates = append(instr.Immediates, i)
case ops.I64Const:
i, err := leb128.ReadVarint64(reader)
if err != nil {
return nil, err
}
instr.Immediates = append(instr.Immediates, i)
case ops.F32Const:
var i uint32
// TODO(vibhavp): Switch to a reflect-free method in the future
// for reading off the bytestream.
err := binary.Read(reader, binary.LittleEndian, &i)
if err != nil {
return nil, err
}
instr.Immediates = append(instr.Immediates, math.Float32frombits(i))
case ops.F64Const:
var i uint64
// TODO(vibhavp): Switch to a reflect-free method in the future
// for reading off the bytestream.
err := binary.Read(reader, binary.LittleEndian, &i)
if err != nil {
return nil, err
}
instr.Immediates = append(instr.Immediates, math.Float64frombits(i))
case ops.I32Load, ops.I64Load, ops.F32Load, ops.F64Load, ops.I32Load8s, ops.I32Load8u, ops.I32Load16s, ops.I32Load16u, ops.I64Load8s, ops.I64Load8u, ops.I64Load16s, ops.I64Load16u, ops.I64Load32s, ops.I64Load32u, ops.I32Store, ops.I64Store, ops.F32Store, ops.F64Store, ops.I32Store8, ops.I32Store16, ops.I64Store8, ops.I64Store16, ops.I64Store32:
// read memory_immediate
flags, err := leb128.ReadVarUint32(reader)
if err != nil {
return nil, err
}
instr.Immediates = append(instr.Immediates, flags)
offset, err := leb128.ReadVarUint32(reader)
if err != nil {
return nil, err
}
instr.Immediates = append(instr.Immediates, offset)
case ops.CurrentMemory, ops.GrowMemory:
res, err := leb128.ReadVarUint32(reader)
if err != nil {
return nil, err
}
instr.Immediates = append(instr.Immediates, uint8(res))
}
if op != ops.Return {
lastOpReturn = false
}
disas.Code = append(disas.Code, instr)
curIndex++
}
if logging {
for _, instr := range disas.Code {
logger.Printf("%v %v", instr.Op.Name, instr.NewStack)
}
}
return disas, nil
}