123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416 |
- package asm
- import (
- "encoding/binary"
- "fmt"
- "io"
- "math"
- "strings"
- "github.com/pkg/errors"
- )
- // InstructionSize is the size of a BPF instruction in bytes
- const InstructionSize = 8
- // Instruction is a single eBPF instruction.
- type Instruction struct {
- OpCode OpCode
- Dst Register
- Src Register
- Offset int16
- Constant int64
- Reference string
- Symbol string
- }
- // Sym creates a symbol.
- func (ins Instruction) Sym(name string) Instruction {
- ins.Symbol = name
- return ins
- }
- // Unmarshal decodes a BPF instruction.
- func (ins *Instruction) Unmarshal(r io.Reader, bo binary.ByteOrder) (uint64, error) {
- var bi bpfInstruction
- err := binary.Read(r, bo, &bi)
- if err != nil {
- return 0, err
- }
- ins.OpCode = bi.OpCode
- ins.Dst = bi.Registers.Dst()
- ins.Src = bi.Registers.Src()
- ins.Offset = bi.Offset
- ins.Constant = int64(bi.Constant)
- if !bi.OpCode.isDWordLoad() {
- return InstructionSize, nil
- }
- var bi2 bpfInstruction
- if err := binary.Read(r, bo, &bi2); err != nil {
- // No Wrap, to avoid io.EOF clash
- return 0, errors.New("64bit immediate is missing second half")
- }
- if bi2.OpCode != 0 || bi2.Offset != 0 || bi2.Registers != 0 {
- return 0, errors.New("64bit immediate has non-zero fields")
- }
- ins.Constant = int64(uint64(uint32(bi2.Constant))<<32 | uint64(uint32(bi.Constant)))
- return 2 * InstructionSize, nil
- }
- // Marshal encodes a BPF instruction.
- func (ins Instruction) Marshal(w io.Writer, bo binary.ByteOrder) (uint64, error) {
- if ins.OpCode == InvalidOpCode {
- return 0, errors.New("invalid opcode")
- }
- isDWordLoad := ins.OpCode.isDWordLoad()
- cons := int32(ins.Constant)
- if isDWordLoad {
- // Encode least significant 32bit first for 64bit operations.
- cons = int32(uint32(ins.Constant))
- }
- bpfi := bpfInstruction{
- ins.OpCode,
- newBPFRegisters(ins.Dst, ins.Src),
- ins.Offset,
- cons,
- }
- if err := binary.Write(w, bo, &bpfi); err != nil {
- return 0, err
- }
- if !isDWordLoad {
- return InstructionSize, nil
- }
- bpfi = bpfInstruction{
- Constant: int32(ins.Constant >> 32),
- }
- if err := binary.Write(w, bo, &bpfi); err != nil {
- return 0, err
- }
- return 2 * InstructionSize, nil
- }
- // RewriteMapPtr changes an instruction to use a new map fd.
- //
- // Returns an error if the fd is invalid, or the instruction
- // is incorrect.
- func (ins *Instruction) RewriteMapPtr(fd int) error {
- if !ins.OpCode.isDWordLoad() {
- return errors.Errorf("%s is not a 64 bit load", ins.OpCode)
- }
- if fd < 0 {
- return errors.New("invalid fd")
- }
- ins.Src = R1
- ins.Constant = int64(fd)
- return nil
- }
- // Format implements fmt.Formatter.
- func (ins Instruction) Format(f fmt.State, c rune) {
- if c != 'v' {
- fmt.Fprintf(f, "{UNRECOGNIZED: %c}", c)
- return
- }
- op := ins.OpCode
- if op == InvalidOpCode {
- fmt.Fprint(f, "INVALID")
- return
- }
- // Omit trailing space for Exit
- if op.JumpOp() == Exit {
- fmt.Fprint(f, op)
- return
- }
- fmt.Fprintf(f, "%v ", op)
- switch cls := op.Class(); cls {
- case LdClass, LdXClass, StClass, StXClass:
- switch op.Mode() {
- case ImmMode:
- fmt.Fprintf(f, "dst: %s imm: %d", ins.Dst, ins.Constant)
- case AbsMode:
- fmt.Fprintf(f, "imm: %d", ins.Constant)
- case IndMode:
- fmt.Fprintf(f, "dst: %s src: %s imm: %d", ins.Dst, ins.Src, ins.Constant)
- case MemMode:
- fmt.Fprintf(f, "dst: %s src: %s off: %d imm: %d", ins.Dst, ins.Src, ins.Offset, ins.Constant)
- case XAddMode:
- fmt.Fprintf(f, "dst: %s src: %s", ins.Dst, ins.Src)
- }
- case ALU64Class, ALUClass:
- fmt.Fprintf(f, "dst: %s ", ins.Dst)
- if op.ALUOp() == Swap || op.Source() == ImmSource {
- fmt.Fprintf(f, "imm: %d", ins.Constant)
- } else {
- fmt.Fprintf(f, "src: %s", ins.Src)
- }
- case JumpClass:
- switch jop := op.JumpOp(); jop {
- case Call:
- if ins.Src == R1 {
- // bpf-to-bpf call
- fmt.Fprint(f, ins.Constant)
- } else {
- fmt.Fprint(f, BuiltinFunc(ins.Constant))
- }
- default:
- fmt.Fprintf(f, "dst: %s off: %d ", ins.Dst, ins.Offset)
- if op.Source() == ImmSource {
- fmt.Fprintf(f, "imm: %d", ins.Constant)
- } else {
- fmt.Fprintf(f, "src: %s", ins.Src)
- }
- }
- }
- if ins.Reference != "" {
- fmt.Fprintf(f, " <%s>", ins.Reference)
- }
- }
- // Instructions is an eBPF program.
- type Instructions []Instruction
- func (insns Instructions) String() string {
- return fmt.Sprint(insns)
- }
- // RewriteMapPtr rewrites all loads of a specific map pointer to a new fd.
- //
- // Returns an error if the symbol isn't used, see IsUnreferencedSymbol.
- func (insns Instructions) RewriteMapPtr(symbol string, fd int) error {
- if symbol == "" {
- return errors.New("empty symbol")
- }
- found := false
- for i := range insns {
- ins := &insns[i]
- if ins.Reference != symbol {
- continue
- }
- if err := ins.RewriteMapPtr(fd); err != nil {
- return err
- }
- found = true
- }
- if !found {
- return &unreferencedSymbolError{symbol}
- }
- return nil
- }
- // SymbolOffsets returns the set of symbols and their offset in
- // the instructions.
- func (insns Instructions) SymbolOffsets() (map[string]int, error) {
- offsets := make(map[string]int)
- for i, ins := range insns {
- if ins.Symbol == "" {
- continue
- }
- if _, ok := offsets[ins.Symbol]; ok {
- return nil, errors.Errorf("duplicate symbol %s", ins.Symbol)
- }
- offsets[ins.Symbol] = i
- }
- return offsets, nil
- }
- // ReferenceOffsets returns the set of references and their offset in
- // the instructions.
- func (insns Instructions) ReferenceOffsets() map[string][]int {
- offsets := make(map[string][]int)
- for i, ins := range insns {
- if ins.Reference == "" {
- continue
- }
- offsets[ins.Reference] = append(offsets[ins.Reference], i)
- }
- return offsets
- }
- func (insns Instructions) marshalledOffsets() (map[string]int, error) {
- symbols := make(map[string]int)
- marshalledPos := 0
- for _, ins := range insns {
- currentPos := marshalledPos
- marshalledPos += ins.OpCode.marshalledInstructions()
- if ins.Symbol == "" {
- continue
- }
- if _, ok := symbols[ins.Symbol]; ok {
- return nil, errors.Errorf("duplicate symbol %s", ins.Symbol)
- }
- symbols[ins.Symbol] = currentPos
- }
- return symbols, nil
- }
- // Format implements fmt.Formatter.
- //
- // You can control indentation of symbols by
- // specifying a width. Setting a precision controls the indentation of
- // instructions.
- // The default character is a tab, which can be overriden by specifying
- // the ' ' space flag.
- func (insns Instructions) Format(f fmt.State, c rune) {
- if c != 's' && c != 'v' {
- fmt.Fprintf(f, "{UNKNOWN FORMAT '%c'}", c)
- return
- }
- // Precision is better in this case, because it allows
- // specifying 0 padding easily.
- padding, ok := f.Precision()
- if !ok {
- padding = 1
- }
- indent := strings.Repeat("\t", padding)
- if f.Flag(' ') {
- indent = strings.Repeat(" ", padding)
- }
- symPadding, ok := f.Width()
- if !ok {
- symPadding = padding - 1
- }
- if symPadding < 0 {
- symPadding = 0
- }
- symIndent := strings.Repeat("\t", symPadding)
- if f.Flag(' ') {
- symIndent = strings.Repeat(" ", symPadding)
- }
- // Figure out how many digits we need to represent the highest
- // offset.
- highestOffset := 0
- for _, ins := range insns {
- highestOffset += ins.OpCode.marshalledInstructions()
- }
- offsetWidth := int(math.Ceil(math.Log10(float64(highestOffset))))
- offset := 0
- for _, ins := range insns {
- if ins.Symbol != "" {
- fmt.Fprintf(f, "%s%s:\n", symIndent, ins.Symbol)
- }
- fmt.Fprintf(f, "%s%*d: %v\n", indent, offsetWidth, offset, ins)
- offset += ins.OpCode.marshalledInstructions()
- }
- return
- }
- // Marshal encodes a BPF program into the kernel format.
- func (insns Instructions) Marshal(w io.Writer, bo binary.ByteOrder) error {
- absoluteOffsets, err := insns.marshalledOffsets()
- if err != nil {
- return err
- }
- num := 0
- for i, ins := range insns {
- switch {
- case ins.OpCode.JumpOp() == Call && ins.Constant == -1:
- // Rewrite bpf to bpf call
- offset, ok := absoluteOffsets[ins.Reference]
- if !ok {
- return errors.Errorf("instruction %d: reference to missing symbol %s", i, ins.Reference)
- }
- ins.Constant = int64(offset - num - 1)
- case ins.OpCode.Class() == JumpClass && ins.Offset == -1:
- // Rewrite jump to label
- offset, ok := absoluteOffsets[ins.Reference]
- if !ok {
- return errors.Errorf("instruction %d: reference to missing symbol %s", i, ins.Reference)
- }
- ins.Offset = int16(offset - num - 1)
- }
- n, err := ins.Marshal(w, bo)
- if err != nil {
- return errors.Wrapf(err, "instruction %d", i)
- }
- num += int(n / InstructionSize)
- }
- return nil
- }
- type bpfInstruction struct {
- OpCode OpCode
- Registers bpfRegisters
- Offset int16
- Constant int32
- }
- type bpfRegisters uint8
- func newBPFRegisters(dst, src Register) bpfRegisters {
- return bpfRegisters((src << 4) | (dst & 0xF))
- }
- func (r bpfRegisters) Dst() Register {
- return Register(r & 0xF)
- }
- func (r bpfRegisters) Src() Register {
- return Register(r >> 4)
- }
- type unreferencedSymbolError struct {
- symbol string
- }
- func (use *unreferencedSymbolError) Error() string {
- return fmt.Sprintf("unreferenced symbol %s", use.symbol)
- }
- // IsUnreferencedSymbol returns true if err was caused by
- // an unreferenced symbol.
- func IsUnreferencedSymbol(err error) bool {
- _, ok := err.(*unreferencedSymbolError)
- return ok
- }
|