radiance/pkg/sbpf/loader/relocate.go

187 lines
4.6 KiB
Go

package loader
import (
"debug/elf"
"encoding/binary"
"fmt"
"go.firedancer.io/radiance/pkg/sbpf"
)
// relocate applies ELF relocations (for syscalls and position-independent code).
func (l *Loader) relocate() error {
l.funcs = make(map[uint32]int64)
if err := l.fixupRelativeCalls(); err != nil {
return err
}
if err := l.applyDynamicRelocs(); err != nil {
return err
}
if err := l.getEntrypoint(); err != nil {
return err
}
return nil
}
func (l *Loader) fixupRelativeCalls() error {
// TODO does invariant text.size%8 == 0 hold?
insCount := l.textRange.len() / sbpf.SlotSize
buf := l.getRange(l.textRange)
for i := uint64(0); i < insCount; i++ {
off := i * sbpf.SlotSize
slot := sbpf.GetSlot(buf[off : off+sbpf.SlotSize])
isCall := slot.Op() == sbpf.OpCall && slot.Imm() != -1
if !isCall {
continue
}
target := int64(i) + 1 + int64(slot.Imm())
if target < 0 || target >= int64(insCount) {
return fmt.Errorf("call ins out of bounds")
}
hash, err := l.registerFunc(uint64(target))
if err != nil {
return err
}
var newImm [4]byte
binary.LittleEndian.PutUint32(newImm[:], hash)
copy(buf[off+4:off+8], newImm[:])
}
return nil
}
func (l *Loader) registerFunc(target uint64) (uint32, error) {
hash := sbpf.PCHash(target)
// TODO check for collision with syscalls
//if _, ok := l.funcs[hash]; ok {
// return 0, fmt.Errorf("symbol hash collision for func at=%d hash=%#08x", target, hash)
//}
l.funcs[hash] = int64(target)
return hash, nil
}
func (l *Loader) applyDynamicRelocs() error {
iter := l.relocsIter
if iter == nil {
return nil
}
for iter.Next() && iter.Err() == nil {
reloc := iter.Item()
if err := l.applyReloc(&reloc); err != nil {
return err
}
}
return iter.Err()
}
func (l *Loader) applyReloc(reloc *elf.Rel64) error {
// TODO rOff is not checked
// Need to have a virtual write target here
rOff := reloc.Off
rType := R_BPF(elf.R_TYPE64(reloc.Info))
rSym := elf.R_SYM64(reloc.Info)
switch rType {
case R_BPF_64_64:
sym, err := l.getDynsym(rSym)
if err != nil {
return err
}
// Add immediate as offset to symbol
relAddr := binary.LittleEndian.Uint32(l.program[rOff+4 : rOff+8])
addr := clampAddUint64(sym.Value, uint64(relAddr))
if addr < sbpf.VaddrProgram {
addr += sbpf.VaddrProgram
}
// Write to imm field of two slots
binary.LittleEndian.PutUint32(l.program[rOff+4:rOff+8], uint32(addr))
binary.LittleEndian.PutUint32(l.program[rOff+12:rOff+16], uint32(addr>>32))
case R_BPF_64_RELATIVE:
if l.textRange.contains(rOff) {
immLow := binary.LittleEndian.Uint32(l.program[rOff+4 : rOff+8])
immHi := binary.LittleEndian.Uint32(l.program[rOff+12 : rOff+16])
addr := (uint64(immHi) << 32) | uint64(immLow)
if addr == 0 {
return fmt.Errorf("invalid R_BPF_64_RELATIVE")
}
if addr < sbpf.VaddrProgram {
addr += sbpf.VaddrProgram
}
// Write to imm field of two slots
binary.LittleEndian.PutUint32(l.program[rOff+4:rOff+8], uint32(addr))
binary.LittleEndian.PutUint32(l.program[rOff+12:rOff+16], uint32(addr>>32))
} else {
var addr uint64
if l.eh.Flags == EF_SBF_V2 {
addr = binary.LittleEndian.Uint64(l.program[rOff : rOff+8])
if addr < sbpf.VaddrProgram {
addr += sbpf.VaddrProgram
}
} else {
// lol
addr = uint64(binary.LittleEndian.Uint32(l.program[rOff+4 : rOff+8]))
addr = clampAddUint64(addr, sbpf.VaddrProgram)
}
binary.LittleEndian.PutUint64(l.program[rOff:rOff+8], addr)
}
case R_BPF_64_32:
sym, err := l.getDynsym(rSym)
if err != nil {
return err
}
name, err := l.getDynstr(sym.Name)
if err != nil {
return err
}
var hash uint32
if elf.ST_TYPE(sym.Info) == elf.STT_FUNC && sym.Value != 0 {
// Function call
if !l.textRange.contains(sym.Value) {
return fmt.Errorf("out-of-bounds R_BPF_64_32 function ref")
}
target := (sym.Value - l.textRange.min) / 8
hash, err = l.registerFunc(target)
if err != nil {
return fmt.Errorf("R_BPF_64_32 function ref: %w", err)
}
} else {
// Syscall
hash = sbpf.SymbolHash(name)
// TODO check whether syscall is known
}
binary.LittleEndian.PutUint32(l.program[rOff+4:rOff+8], hash)
default:
return fmt.Errorf("unsupported reloc type %d", rType)
}
return nil
}
func (l *Loader) getEntrypoint() error {
offset := l.eh.Entry - l.shText.Addr
if offset%sbpf.SlotSize != 0 {
return fmt.Errorf("invalid entrypoint")
}
l.entrypoint = offset / sbpf.SlotSize
return nil
}
// Relocation types for eBPF.
type R_BPF int
const (
R_BPF_NONE R_BPF = 0
R_BPF_64_64 R_BPF = 1
R_BPF_64_RELATIVE R_BPF = 8
R_BPF_64_32 R_BPF = 10
)