github.com/dylandreimerink/gobpfld@v0.6.1-0.20220205171531-e79c330ad608/emulator/vm.go (about)

     1  package emulator
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"strings"
     8  
     9  	"github.com/dylandreimerink/gobpfld"
    10  	"github.com/dylandreimerink/gobpfld/ebpf"
    11  )
    12  
    13  // VM is a virtual machine which can run eBPF code.
    14  type VM struct {
    15  	settings VMSettings
    16  
    17  	Registers Registers
    18  	// A slice of frames, each frame is represented by a byte slice
    19  	StackFrames []ValueMemory
    20  	// When calling into a function, R6-9 are preserved, meaning that we need to save them and retore them back
    21  	// once we return from a function call.
    22  	PreservedRegisters []Registers
    23  	HelperFunctions    []HelperFunc
    24  
    25  	// A slice of eBPF programs
    26  	Programs [][]Instruction
    27  	Maps     []Map
    28  }
    29  
    30  func NewVM(settings VMSettings) (*VM, error) {
    31  	// TODO settings validation
    32  
    33  	vm := &VM{
    34  		settings:        settings,
    35  		HelperFunctions: LinuxHelperFunctions(),
    36  		Programs: [][]Instruction{
    37  			nil, // Have index 0 be an "invalid" program, to make certain errors more apparent
    38  		},
    39  		Maps: []Map{
    40  			nil, // Have index 0 be an "invalid" map, to make certain errors more apparent
    41  		},
    42  	}
    43  
    44  	// Reset will make the VM ready to start execution of a program
    45  	vm.Reset()
    46  
    47  	return vm, nil
    48  }
    49  
    50  func (vm *VM) AddProgram(prog []ebpf.Instruction) error {
    51  	vmProg, err := Translate(prog)
    52  	if err != nil {
    53  		return fmt.Errorf("translate: %w", err)
    54  	}
    55  
    56  	vm.Programs = append(vm.Programs, vmProg)
    57  
    58  	return nil
    59  }
    60  
    61  func (vm *VM) AddRawProgram(prog []ebpf.RawInstruction) error {
    62  	inst, err := ebpf.Decode(prog)
    63  	if err != nil {
    64  		return fmt.Errorf("decode: %w", err)
    65  	}
    66  
    67  	err = vm.AddProgram(inst)
    68  	if err != nil {
    69  		return fmt.Errorf("add program: %w", err)
    70  	}
    71  
    72  	return nil
    73  }
    74  
    75  func (vm *VM) AddMap(m Map) (int, error) {
    76  	err := m.Init()
    77  	if err != nil {
    78  		return -1, fmt.Errorf("map init: %w", err)
    79  	}
    80  
    81  	vm.Maps = append(vm.Maps, m)
    82  
    83  	return len(vm.Maps) - 1, nil
    84  }
    85  
    86  func (vm *VM) AddAbstractMap(am gobpfld.AbstractMap) (int, error) {
    87  	m, err := AbstractMapToVM(am)
    88  	if err != nil {
    89  		return -1, fmt.Errorf("abstract map to VM map: %w", err)
    90  	}
    91  
    92  	index, err := vm.AddMap(m)
    93  	if err != nil {
    94  		return -1, fmt.Errorf("add map: %w", err)
    95  	}
    96  
    97  	return index, nil
    98  }
    99  
   100  func (vm *VM) SetEntrypoint(index int) error {
   101  	if index < 1 || len(vm.Programs) <= index {
   102  		return fmt.Errorf("program index out of bounds")
   103  	}
   104  
   105  	vm.Registers.PI = index
   106  
   107  	return nil
   108  }
   109  
   110  func (vm *VM) Run() error {
   111  	return vm.RunContext(context.Background())
   112  }
   113  
   114  var errInvalidProgramCount = errors.New("program counter points to non-existent instruction, bad jump of missing " +
   115  	"exit instruction")
   116  
   117  func (vm *VM) RunContext(ctx context.Context) error {
   118  	for {
   119  		stop, err := vm.Step()
   120  		if err != nil {
   121  			return err
   122  		}
   123  		if stop {
   124  			break
   125  		}
   126  
   127  		// If context was canceled or deadline exceeded, stop execution
   128  		if err = ctx.Err(); err != nil {
   129  			return vm.err(err)
   130  		}
   131  	}
   132  
   133  	return nil
   134  }
   135  
   136  // Step executes a single instruction, allowing us to "step" through the program
   137  func (vm *VM) Step() (stop bool, err error) {
   138  	if vm.Registers.PI < 1 || vm.Registers.PI >= len(vm.Programs) {
   139  		return true, fmt.Errorf("no program loaded at PI(%d)", vm.Registers.PI)
   140  	}
   141  	program := vm.Programs[vm.Registers.PI]
   142  
   143  	inst := program[vm.Registers.PC]
   144  	if vm.Registers.PC >= len(program) {
   145  		return true, fmt.Errorf("PC(%d) outside of program", vm.Registers.PC)
   146  	}
   147  
   148  	// Store the program count of the current instruction
   149  	pc := vm.Registers.PC
   150  	err = inst.Execute(vm)
   151  	if err != nil {
   152  		// If not errExit, it is a runtime error
   153  		if err != errExit {
   154  			return true, vm.err(err)
   155  		}
   156  
   157  		// TODO return from bpf-to-bpf
   158  
   159  		return true, nil
   160  	}
   161  
   162  	if len(program) <= vm.Registers.PC+1 {
   163  		// reset PC so it points to the offending instruction.
   164  		vm.Registers.PC = pc
   165  
   166  		return true, vm.err(errInvalidProgramCount)
   167  	}
   168  
   169  	// Increment the program counter
   170  	vm.Registers.PC++
   171  
   172  	return false, nil
   173  }
   174  
   175  func (vm *VM) err(err error) *VMError {
   176  	return &VMError{
   177  		VMSnapshot: vm.Clone(),
   178  		Original:   err,
   179  	}
   180  }
   181  
   182  // Clone clones the whole VM, this includes the current state of the VM. This feature can be used to create snapshots
   183  // of the VM.
   184  func (vm *VM) Clone() *VM {
   185  	clone := &VM{
   186  		settings:    vm.settings,
   187  		Programs:    make([][]Instruction, len(vm.Programs)),
   188  		StackFrames: make([]ValueMemory, len(vm.StackFrames)),
   189  	}
   190  	// Reset will make new stack frames
   191  	clone.Reset()
   192  
   193  	clone.Registers = vm.Registers.Clone()
   194  
   195  	// Copy the stack frames
   196  	for i := range clone.StackFrames {
   197  		clone.StackFrames[i] = *(vm.StackFrames[i].Clone().(*ValueMemory))
   198  	}
   199  
   200  	// Copy the programs
   201  	for i := range clone.Programs {
   202  		clone.Programs[i] = make([]Instruction, len(vm.Programs[i]))
   203  		for j := range clone.Programs[i] {
   204  			clone.Programs[i][j] = vm.Programs[i][j].Clone()
   205  		}
   206  	}
   207  
   208  	return clone
   209  }
   210  
   211  func (vm *VM) Reset() {
   212  	vm.Registers.PC = 0
   213  	vm.Registers.SF = 0
   214  	vm.Registers.R0 = newIMM(0)
   215  	vm.Registers.R1 = newIMM(0)
   216  	vm.Registers.R2 = newIMM(0)
   217  	vm.Registers.R3 = newIMM(0)
   218  	vm.Registers.R4 = newIMM(0)
   219  	vm.Registers.R5 = newIMM(0)
   220  	vm.Registers.R6 = newIMM(0)
   221  	vm.Registers.R7 = newIMM(0)
   222  	vm.Registers.R8 = newIMM(0)
   223  	vm.Registers.R9 = newIMM(0)
   224  
   225  	if vm.StackFrames == nil {
   226  		vm.StackFrames = make([]ValueMemory, vm.settings.MaxStackFrames)
   227  	}
   228  
   229  	for i := range vm.StackFrames {
   230  		vm.StackFrames[i].MemName = fmt.Sprintf("sf#%d", i)
   231  		if vm.StackFrames[i].Mapping == nil {
   232  			vm.StackFrames[i].Mapping = make([]RegisterValue, vm.settings.StackFrameSize)
   233  			continue
   234  		}
   235  		// Zero out the stack frames
   236  		for j := range vm.StackFrames[i].Mapping {
   237  			vm.StackFrames[i].Mapping[j] = nil
   238  		}
   239  	}
   240  
   241  	vm.Registers.R10 = FramePointer{
   242  		Memory:   &vm.StackFrames[0],
   243  		Offset:   0,
   244  		Readonly: true,
   245  	}
   246  }
   247  
   248  func (vm *VM) String() string {
   249  	var sb strings.Builder
   250  	sb.WriteString("Registers:\n")
   251  
   252  	r := vm.Registers
   253  	sb.WriteString(fmt.Sprintf(" PC: %d -> %s\n", r.PC, vm.Programs[r.PI][r.PC].String()))
   254  	// TODO add program name, as soon as we have that available
   255  	sb.WriteString(fmt.Sprintf(" PI: %d\n", r.PI))
   256  	sb.WriteString(fmt.Sprintf(" SF: %d\n", r.SF))
   257  	sb.WriteString(fmt.Sprintf(" r0: %s\n", r.R0))
   258  	sb.WriteString(fmt.Sprintf(" r1: %s\n", r.R1))
   259  	sb.WriteString(fmt.Sprintf(" r2: %s\n", r.R2))
   260  	sb.WriteString(fmt.Sprintf(" r3: %s\n", r.R3))
   261  	sb.WriteString(fmt.Sprintf(" r4: %s\n", r.R4))
   262  	sb.WriteString(fmt.Sprintf(" r5: %s\n", r.R5))
   263  	sb.WriteString(fmt.Sprintf(" r6: %s\n", r.R6))
   264  	sb.WriteString(fmt.Sprintf(" r7: %s\n", r.R7))
   265  	sb.WriteString(fmt.Sprintf(" r8: %s\n", r.R8))
   266  	sb.WriteString(fmt.Sprintf(" r9: %s\n", r.R9))
   267  	sb.WriteString(fmt.Sprintf("r10: %s\n", &r.R10))
   268  
   269  	return sb.String()
   270  }
   271  
   272  // A VMError is thrown by the VM and contain a copy of the state of the VM at the time of the error
   273  type VMError struct {
   274  	VMSnapshot *VM
   275  	Original   error
   276  }
   277  
   278  func (e *VMError) Error() string {
   279  	return fmt.Sprintf("vm error: %s", e.Original)
   280  }
   281  
   282  type VMSettings struct {
   283  	// StackFrameSize is the allocated size of a single stack frame
   284  	StackFrameSize int
   285  	// MaxStackFrames is the maximum number of stack frames
   286  	MaxStackFrames int
   287  }
   288  
   289  // DefaultVMSettings returns good default settings for the VM, they are based on the limitations of the Linux eBPF
   290  // implementation
   291  func DefaultVMSettings() VMSettings {
   292  	return VMSettings{
   293  		StackFrameSize: 256,
   294  		MaxStackFrames: 8,
   295  	}
   296  }