github.com/dylandreimerink/gobpfld@v0.6.1-0.20220205171531-e79c330ad608/emulator/helper_functions.go (about) 1 package emulator 2 3 import ( 4 "errors" 5 "fmt" 6 7 "github.com/dylandreimerink/gobpfld/bpfsys" 8 "github.com/dylandreimerink/gobpfld/bpftypes" 9 "github.com/dylandreimerink/gobpfld/ebpf" 10 ) 11 12 // HelperFunc are functions in go space which can be called from the eBPF VM. They are used expand eBPF capabilities 13 // without giving the VM direct access, much like a syscall in an OS context. 14 // A helper function by convention will return a single value in R0, is passed R1-R5 as arguments and should never 15 // touch R6-R9. A helper can gracefully return an error via R0, returning an error from the Go function means there 16 // is no graceful way to handle the error and will cause the VM to abort execution. 17 type HelperFunc func(vm *VM) error 18 19 // LinuxHelperFunctions returns a helper function array of helper functions which are compatible with the linux 20 // helper functions as defined in https://github.com/libbpf/libbpf/blob/master/src/bpf_helper_defs.h 21 func LinuxHelperFunctions() []HelperFunc { 22 const maxLinuxHelperNum = 191 23 funcs := make([]HelperFunc, maxLinuxHelperNum+1) 24 25 // Helper func 0 doesn't exist 26 funcs[0] = nil 27 funcs[1] = MapLookupElement 28 funcs[2] = MapUpdateElement 29 funcs[3] = MapDeleteElement 30 // ... 31 funcs[12] = TailCall 32 // ... 33 funcs[14] = GetCurrentPidTgid 34 // ... 35 funcs[25] = PerfEventOutput 36 // ... 37 funcs[87] = MapPushElement 38 funcs[88] = MapPopElement 39 funcs[89] = MapPeekElement 40 // ...191 41 42 return funcs 43 } 44 45 // MapLookupElement implements the bpf_map_lookup_element helper 46 func MapLookupElement(vm *VM) error { 47 // R1 = id/fd of the map, R2 = pointer to key value 48 m, err := regToMap(vm, vm.Registers.R1) 49 if err != nil { 50 return err 51 } 52 if m == nil { 53 return nil 54 } 55 56 val, err := m.Lookup(vm.Registers.R2) 57 if err != nil { 58 switch err { 59 case errMapKeyNoPtr, errMapValNoPtr: 60 val = efault() 61 case errMapOutOfMemory: 62 val = e2big() 63 case errMapNotImplemented: 64 val = eperm() 65 default: 66 return fmt.Errorf("lookup: %w", err) 67 } 68 } 69 70 vm.Registers.R0 = val 71 72 return nil 73 } 74 75 // MapUpdateElement implements the bpf_map_update_element helper 76 func MapUpdateElement(vm *VM) error { 77 m, err := regToMap(vm, vm.Registers.R1) 78 if err != nil { 79 return err 80 } 81 if m == nil { 82 return nil 83 } 84 85 val, err := m.Update(vm.Registers.R2, vm.Registers.R3, bpfsys.BPFAttrMapElemFlags(vm.Registers.R4.Value())) 86 if err != nil { 87 switch err { 88 case errMapKeyNoPtr, errMapValNoPtr: 89 val = efault() 90 case errMapOutOfMemory: 91 val = e2big() 92 case errMapNotImplemented: 93 val = eperm() 94 default: 95 return fmt.Errorf("update: %w", err) 96 } 97 } 98 99 vm.Registers.R0 = val 100 return nil 101 } 102 103 // MapDeleteElement implements the bpf_map_delete_element helper 104 func MapDeleteElement(vm *VM) error { 105 return errors.New("not yet implemented") 106 } 107 108 // Convert a register values passed into a helper to the actual map 109 func regToMap(vm *VM, reg RegisterValue) (Map, error) { 110 mapIdx := reg.Value() 111 112 // If R1 is a pointer, not a value, we should dereference it. 113 // Pointers can still be valid, usually they are passed when the id/fd comes from a map-in-map type map. 114 if ptr, ok := reg.(*MemoryPtr); ok { 115 // Deref as 32 bit, which is always the size of an id/fd 116 mapIdxVal, err := ptr.Deref(0, ebpf.BPF_W) 117 if err != nil { 118 return nil, fmt.Errorf("deref ptr to map idx: %w", err) 119 } 120 121 mapIdx = mapIdxVal.Value() 122 } 123 124 if mapIdx < 1 || int(mapIdx) >= len(vm.Maps) { 125 vm.Registers.R0 = newIMM(0) 126 return nil, nil 127 } 128 129 return vm.Maps[mapIdx], nil 130 } 131 132 // TailCall implements the bpf_tail_call helper 133 func TailCall(vm *VM) error { 134 // R1 = ctx, R2 = map fd(of prog_array), R3 = index in map (key for the program to execute) 135 mapIdx := vm.Registers.R2.Value() 136 if mapIdx < 1 || int(mapIdx) >= len(vm.Maps) { 137 vm.Registers.R0 = efault() 138 return nil 139 } 140 141 // This helper only works for PROG arrays 142 m := vm.Maps[mapIdx] 143 if m.GetDef().Type != bpftypes.BPF_MAP_TYPE_PROG_ARRAY { 144 vm.Registers.R0 = efault() 145 return nil 146 } 147 148 k := &MemoryPtr{ 149 Memory: &ValueMemory{ 150 Mapping: []RegisterValue{ 151 vm.Registers.R3, 152 vm.Registers.R3, 153 vm.Registers.R3, 154 vm.Registers.R3, 155 }, 156 }, 157 } 158 159 // Lookup the value 160 val := newIMM(0) 161 valReg, err := m.Lookup(k) 162 if err != nil { 163 switch err { 164 case errMapKeyNoPtr, errMapValNoPtr: 165 val = efault() 166 case errMapOutOfMemory: 167 val = e2big() 168 case errMapNotImplemented: 169 val = eperm() 170 default: 171 return fmt.Errorf("lookup: %w", err) 172 } 173 174 vm.Registers.R0 = val 175 return nil 176 } 177 178 valPtr, ok := valReg.(PointerValue) 179 if !ok { 180 return fmt.Errorf("lookup didn't return a pointer") 181 } 182 183 valVar, err := valPtr.Deref(0, ebpf.BPF_W) 184 if err != nil { 185 return fmt.Errorf("deref value pointer: %w", err) 186 } 187 188 progIdx := valVar.Value() 189 if len(vm.Programs) < int(progIdx) { 190 vm.Registers.R0 = efault() 191 return nil 192 } 193 194 if progIdx == 0 { 195 return fmt.Errorf("no program loaded at index 0") 196 } 197 198 // On success, change the current program index 199 vm.Registers.PI = int(progIdx) 200 // Change the instruction pointer to -1, since after this helper call the PC will be incremented so we will end 201 // up at a PC of 0 202 vm.Registers.PC = -1 203 // Don't reset the VM since we want to preserve the memory and register state. 204 // Not that, because the ctx was passed as the first argument to this function it lives in the R1 register 205 // where the next program will expect it to be, this is free, no need to manually set the correct ctx. 206 207 // Set the return value to 0, for success 208 vm.Registers.R0 = val 209 return nil 210 } 211 212 // GetCurrentPidTgid implements the bpf_get_current_pid_tgid helper 213 func GetCurrentPidTgid(vm *VM) error { 214 // TODO replace const value with value gotten from dynamic context of VM as soon as this feature is added. 215 return vm.Registers.Assign(ebpf.BPF_REG_0, newIMM(1234<<32+5678)) 216 } 217 218 // PerfEventOutput implements the bpf_perf_event_output helper 219 func PerfEventOutput(vm *VM) error { 220 // R1 = ctx, R2 = map index, R3 = flags, R4 = data, R5 = size 221 mapIdx := vm.Registers.R2.Value() 222 if mapIdx < 1 || int(mapIdx) >= len(vm.Maps) { 223 vm.Registers.R0 = efault() 224 return nil 225 } 226 227 m := vm.Maps[mapIdx] 228 pa, ok := m.(*PerfEventArray) 229 if !ok { 230 vm.Registers.R0 = efault() 231 return nil 232 } 233 234 val := newIMM(0) 235 err := pa.Push(vm.Registers.R4, vm.Registers.R5.Value()) 236 if err != nil { 237 switch err { 238 case errMapKeyNoPtr, errMapValNoPtr: 239 val = efault() 240 case errMapOutOfMemory: 241 val = e2big() 242 case errMapNotImplemented: 243 val = eperm() 244 default: 245 return fmt.Errorf("push: %w", err) 246 } 247 } 248 249 vm.Registers.R0 = val 250 251 return nil 252 } 253 254 // MapPushElement implements the bpf_map_push_elem helper 255 func MapPushElement(vm *VM) error { 256 m, err := regToMap(vm, vm.Registers.R1) 257 if err != nil { 258 return err 259 } 260 if m == nil { 261 return nil 262 } 263 264 val := newIMM(0) 265 err = m.Push(vm.Registers.R2, int64(m.GetDef().ValueSize)) 266 if err != nil { 267 switch err { 268 case errMapKeyNoPtr, errMapValNoPtr: 269 val = efault() 270 case errMapOutOfMemory: 271 val = e2big() 272 case errMapNotImplemented: 273 val = eperm() 274 default: 275 return fmt.Errorf("push: %w", err) 276 } 277 } 278 279 vm.Registers.R0 = val 280 return nil 281 } 282 283 // MapPopElement implements the bpf_map_pop_element helper 284 func MapPopElement(vm *VM) error { 285 // R1 = id/fd of the map, R2 = pointer to value 286 m, err := regToMap(vm, vm.Registers.R1) 287 if err != nil { 288 return err 289 } 290 if m == nil { 291 return nil 292 } 293 294 vm.Registers.R0 = newIMM(0) 295 val, err := m.Pop() 296 if err != nil { 297 switch err { 298 case errMapKeyNoPtr, errMapValNoPtr: 299 vm.Registers.R0 = efault() 300 case errMapOutOfMemory: 301 vm.Registers.R0 = e2big() 302 case errMapNotImplemented: 303 vm.Registers.R0 = eperm() 304 default: 305 return fmt.Errorf("pop: %w", err) 306 } 307 308 return nil 309 } 310 311 switch valPtr := vm.Registers.R2.(type) { 312 case *MemoryPtr: 313 // Memory pointers point to the start of a memory block 314 err := valPtr.Memory.Write(int(valPtr.Offset), val, ebpf.BPF_DW) 315 if err != nil { 316 return fmt.Errorf("write memory: %w", err) 317 } 318 319 case *FramePointer: 320 // Frame pointers point to the end of a stack frame 321 err := valPtr.Memory.Write(valPtr.Memory.Size()+int(valPtr.Offset), val, ebpf.BPF_DW) 322 if err != nil { 323 return fmt.Errorf("write memory: %w", err) 324 } 325 326 default: 327 vm.Registers.R0 = efault() 328 return nil 329 } 330 331 return nil 332 } 333 334 // MapPeekElement implements the bpf_map_peek_element helper 335 func MapPeekElement(vm *VM) error { 336 // R1 = id/fd of the map, R2 = pointer to value 337 m, err := regToMap(vm, vm.Registers.R1) 338 if err != nil { 339 return err 340 } 341 if m == nil { 342 return nil 343 } 344 345 key := newIMM(0) 346 val, err := m.Lookup(&MemoryPtr{ 347 Memory: &ValueMemory{ 348 Mapping: []RegisterValue{ 349 key, 350 key, 351 key, 352 key, 353 }, 354 }, 355 }) 356 ret := newIMM(0) 357 if err != nil { 358 switch err { 359 case errMapKeyNoPtr, errMapValNoPtr: 360 ret = efault() 361 case errMapOutOfMemory: 362 ret = e2big() 363 case errMapNotImplemented: 364 ret = eperm() 365 default: 366 return fmt.Errorf("peek: %w", err) 367 } 368 } 369 370 vm.Registers.R0 = ret 371 vm.Registers.R2 = val 372 373 return nil 374 }