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 }