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

     1  package emulator
     2  
     3  import (
     4  	"encoding/binary"
     5  	"fmt"
     6  
     7  	"github.com/dylandreimerink/gobpfld/ebpf"
     8  )
     9  
    10  // Memory represents memory which can be accessed by the eBPF VM.
    11  type Memory interface {
    12  	Name() string
    13  	Read(offset int, size ebpf.Size) (RegisterValue, error)
    14  	ReadRange(offset, count int) ([]byte, error)
    15  	Write(offset int, value RegisterValue, size ebpf.Size) error
    16  	Size() int
    17  	Clone() Memory
    18  }
    19  
    20  // ValueMemory perserves type information, this is important for the functioning of the VM. Our VM implementation has no
    21  // addessable memory slab in which pointers are assigned, rather in the type information we store pointers to actual
    22  // memory blocks. Therefor, it is important to not lose type information when pointers are written to the stack.
    23  type ValueMemory struct {
    24  	MemName string
    25  	Mapping []RegisterValue
    26  }
    27  
    28  func (vm *ValueMemory) Name() string {
    29  	return vm.MemName
    30  }
    31  
    32  func (vm *ValueMemory) Read(offset int, size ebpf.Size) (RegisterValue, error) {
    33  	if offset < 0 || offset+size.Bytes() > len(vm.Mapping) {
    34  		return nil, fmt.Errorf("attempt to read outside of memory bounds, off %d, size %d", offset, size.Bytes())
    35  	}
    36  
    37  	val := vm.Mapping[offset]
    38  	for i := offset; i < offset+size.Bytes(); i++ {
    39  		// Since we store the RegisterValues not the actual bytes, programs are not allowed to read and combine
    40  		// the bytes of stored 2 values. This is likely incorrect behavior anyway.
    41  		if vm.Mapping[i] != val {
    42  			return nil, fmt.Errorf("indicated memory is not one contiguous value")
    43  		}
    44  		// TODO what should we do if the program reads only the upper or lower 32 bits of an 64 bit value for example?
    45  		// should we get the value and bit shift it? Or just error?
    46  	}
    47  
    48  	if val == nil {
    49  		return nil, fmt.Errorf("reading from non-initialized memory")
    50  	}
    51  
    52  	return val, nil
    53  }
    54  
    55  func (vm *ValueMemory) ReadRange(offset, count int) ([]byte, error) {
    56  	if offset < 0 || offset+count > len(vm.Mapping) {
    57  		return nil, fmt.Errorf("attempt to read range outside of memory bounds, off %d, count %d", offset, count)
    58  	}
    59  
    60  	r := make([]byte, count)
    61  	for i := 0; i < count; {
    62  		v := vm.Mapping[offset+i]
    63  		if v == nil {
    64  			r[i] = 0
    65  			i++
    66  			continue
    67  		}
    68  
    69  		size := 1
    70  		for j := i + 1; j < i+8 && j < count; j++ {
    71  			if v != vm.Mapping[offset+j] {
    72  				break
    73  			}
    74  			size++
    75  		}
    76  
    77  		// Round up to nearest valid value size
    78  		switch {
    79  		case size > 4:
    80  			binary.LittleEndian.PutUint64(r[i:i+8], uint64(v.Value()))
    81  			i += 8
    82  		case size > 2:
    83  			binary.LittleEndian.PutUint32(r[i:i+4], uint32(v.Value()))
    84  			i += 4
    85  		case size > 1:
    86  			binary.LittleEndian.PutUint16(r[i:i+2], uint16(v.Value()))
    87  			i += 2
    88  		default:
    89  			r[i] = byte(v.Value())
    90  			i++
    91  		}
    92  	}
    93  
    94  	return r, nil
    95  }
    96  
    97  func (vm *ValueMemory) Write(offset int, value RegisterValue, size ebpf.Size) error {
    98  	if offset < 0 || offset+size.Bytes() > len(vm.Mapping) {
    99  		return fmt.Errorf("attempt to read outside of memory bounds, off %d, size %d", offset, size.Bytes())
   100  	}
   101  
   102  	for i := offset; i < offset+size.Bytes(); i++ {
   103  		vm.Mapping[i] = value
   104  	}
   105  
   106  	return nil
   107  }
   108  
   109  func (vm *ValueMemory) Clone() Memory {
   110  	clone := &ValueMemory{
   111  		MemName: vm.MemName,
   112  		Mapping: make([]RegisterValue, len(vm.Mapping)),
   113  	}
   114  	copy(clone.Mapping, vm.Mapping)
   115  	return clone
   116  }
   117  
   118  func (vm *ValueMemory) Size() int {
   119  	return len(vm.Mapping)
   120  }
   121  
   122  // ByteMemory is a type of memory which is backed by a []byte with no type info, all values read will be IMM.
   123  // Since the bytes may be directly loaded from ELF files with a byte order different from the host, reads and writes
   124  // will happen according to the given byte order.
   125  type ByteMemory struct {
   126  	MemName   string
   127  	ByteOrder binary.ByteOrder
   128  	Backing   []byte
   129  }
   130  
   131  func (bm *ByteMemory) Name() string {
   132  	return bm.MemName
   133  }
   134  
   135  func (bm *ByteMemory) Read(offset int, size ebpf.Size) (RegisterValue, error) {
   136  	if offset < 0 || offset+size.Bytes() > len(bm.Backing) {
   137  		return nil, fmt.Errorf("attempt to read outside of memory bounds, off %d, size %d", offset, size.Bytes())
   138  	}
   139  
   140  	if bm.ByteOrder == nil {
   141  		bm.ByteOrder = binary.LittleEndian
   142  	}
   143  
   144  	var val int64
   145  	switch size {
   146  	case ebpf.BPF_B:
   147  		val = int64(bm.Backing[offset+0])
   148  	case ebpf.BPF_H:
   149  		val = int64(bm.ByteOrder.Uint16([]byte{
   150  			bm.Backing[offset+0],
   151  			bm.Backing[offset+1],
   152  		}))
   153  	case ebpf.BPF_W:
   154  		val = int64(bm.ByteOrder.Uint32([]byte{
   155  			bm.Backing[offset+0],
   156  			bm.Backing[offset+1],
   157  			bm.Backing[offset+2],
   158  			bm.Backing[offset+3],
   159  		}))
   160  	case ebpf.BPF_DW:
   161  		val = int64(bm.ByteOrder.Uint64([]byte{
   162  			bm.Backing[offset+0],
   163  			bm.Backing[offset+1],
   164  			bm.Backing[offset+2],
   165  			bm.Backing[offset+3],
   166  			bm.Backing[offset+4],
   167  			bm.Backing[offset+5],
   168  			bm.Backing[offset+6],
   169  			bm.Backing[offset+7],
   170  		}))
   171  	}
   172  
   173  	return newIMM(val), nil
   174  }
   175  
   176  func (bm *ByteMemory) ReadRange(offset, count int) ([]byte, error) {
   177  	if offset < 0 || offset+count > len(bm.Backing) {
   178  		return nil, fmt.Errorf("attempt to read range outside of memory bounds, off %d, count %d", offset, count)
   179  	}
   180  
   181  	r := make([]byte, count)
   182  	copy(r, bm.Backing[offset:])
   183  
   184  	return r, nil
   185  }
   186  
   187  func (bm *ByteMemory) Write(offset int, value RegisterValue, size ebpf.Size) error {
   188  	if offset < 0 || offset+size.Bytes() > len(bm.Backing) {
   189  		return fmt.Errorf("attempt to read outside of memory bounds, off %d, size %d", offset, size.Bytes())
   190  	}
   191  
   192  	if bm.ByteOrder == nil {
   193  		bm.ByteOrder = binary.LittleEndian
   194  	}
   195  
   196  	v := value.Value()
   197  
   198  	switch size {
   199  	case ebpf.BPF_B:
   200  		bm.Backing[offset] = byte(v)
   201  	case ebpf.BPF_H:
   202  		bm.ByteOrder.PutUint16(bm.Backing[offset:offset+2], uint16(v))
   203  	case ebpf.BPF_W:
   204  		bm.ByteOrder.PutUint32(bm.Backing[offset:offset+4], uint32(v))
   205  	case ebpf.BPF_DW:
   206  		bm.ByteOrder.PutUint64(bm.Backing[offset:offset+8], uint64(v))
   207  	}
   208  
   209  	return nil
   210  }
   211  
   212  func (bm *ByteMemory) Clone() Memory {
   213  	clone := &ByteMemory{
   214  		MemName: bm.MemName,
   215  		Backing: make([]byte, len(bm.Backing)),
   216  	}
   217  	copy(clone.Backing, bm.Backing)
   218  	return clone
   219  }
   220  
   221  func (bm *ByteMemory) Size() int {
   222  	return len(bm.Backing)
   223  }