github.com/MerlinKodo/gvisor@v0.0.0-20231110090155-957f62ecf90e/pkg/bpf/program_builder.go (about)

     1  // Copyright 2018 The gVisor Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package bpf
    16  
    17  import (
    18  	"fmt"
    19  	"math"
    20  
    21  	"github.com/MerlinKodo/gvisor/pkg/abi/linux"
    22  )
    23  
    24  const (
    25  	labelTarget       = math.MaxUint8
    26  	labelDirectTarget = math.MaxUint32
    27  )
    28  
    29  // ProgramBuilder assists with building a BPF program with jump
    30  // labels that are resolved to their proper offsets.
    31  type ProgramBuilder struct {
    32  	// Maps label names to label objects.
    33  	labels map[string]*label
    34  
    35  	// unusableLabels are labels that are added before being referenced in a
    36  	// jump. Any labels added this way cannot be referenced later in order to
    37  	// avoid backwards references.
    38  	unusableLabels map[string]bool
    39  
    40  	// Array of BPF instructions that makes up the program.
    41  	instructions []linux.BPFInstruction
    42  }
    43  
    44  // NewProgramBuilder creates a new ProgramBuilder instance.
    45  func NewProgramBuilder() *ProgramBuilder {
    46  	return &ProgramBuilder{
    47  		labels:         map[string]*label{},
    48  		unusableLabels: map[string]bool{},
    49  	}
    50  }
    51  
    52  // label contains information to resolve a label to an offset.
    53  type label struct {
    54  	// List of locations that reference the label in the program.
    55  	sources []source
    56  
    57  	// Program line when the label is located.
    58  	target int
    59  }
    60  
    61  type jmpType int
    62  
    63  const (
    64  	jDirect jmpType = iota
    65  	jTrue
    66  	jFalse
    67  )
    68  
    69  // source contains information about a single reference to a label.
    70  type source struct {
    71  	// Program line where the label reference is present.
    72  	line int
    73  
    74  	// True if label reference is in the 'jump if true' part of the jump.
    75  	// False if label reference is in the 'jump if false' part of the jump.
    76  	jt jmpType
    77  }
    78  
    79  // AddStmt adds a new statement to the program.
    80  func (b *ProgramBuilder) AddStmt(code uint16, k uint32) {
    81  	b.instructions = append(b.instructions, Stmt(code, k))
    82  }
    83  
    84  // AddJump adds a new jump to the program.
    85  func (b *ProgramBuilder) AddJump(code uint16, k uint32, jt, jf uint8) {
    86  	b.instructions = append(b.instructions, Jump(code, k, jt, jf))
    87  }
    88  
    89  // AddDirectJumpLabel adds a new jump to the program where is labelled.
    90  func (b *ProgramBuilder) AddDirectJumpLabel(labelName string) {
    91  	b.addLabelSource(labelName, jDirect)
    92  	b.AddJump(Jmp|Ja, labelDirectTarget, 0, 0)
    93  }
    94  
    95  // AddJumpTrueLabel adds a new jump to the program where 'jump if true' is a label.
    96  func (b *ProgramBuilder) AddJumpTrueLabel(code uint16, k uint32, jtLabel string, jf uint8) {
    97  	b.addLabelSource(jtLabel, jTrue)
    98  	b.AddJump(code, k, labelTarget, jf)
    99  }
   100  
   101  // AddJumpFalseLabel adds a new jump to the program where 'jump if false' is a label.
   102  func (b *ProgramBuilder) AddJumpFalseLabel(code uint16, k uint32, jt uint8, jfLabel string) {
   103  	b.addLabelSource(jfLabel, jFalse)
   104  	b.AddJump(code, k, jt, labelTarget)
   105  }
   106  
   107  // AddJumpLabels adds a new jump to the program where both jump targets are labels.
   108  func (b *ProgramBuilder) AddJumpLabels(code uint16, k uint32, jtLabel, jfLabel string) {
   109  	b.addLabelSource(jtLabel, jTrue)
   110  	b.addLabelSource(jfLabel, jFalse)
   111  	b.AddJump(code, k, labelTarget, labelTarget)
   112  }
   113  
   114  // AddLabel sets the given label name at the current location. The next instruction is executed
   115  // when the any code jumps to this label. More than one label can be added to the same location.
   116  func (b *ProgramBuilder) AddLabel(name string) error {
   117  	l, ok := b.labels[name]
   118  	if !ok {
   119  		if _, ok = b.unusableLabels[name]; ok {
   120  			return fmt.Errorf("label %q already set", name)
   121  		}
   122  		// Mark the label as unusable. This is done to catch backwards jumps.
   123  		b.unusableLabels[name] = true
   124  		return nil
   125  	}
   126  	if l.target != -1 {
   127  		return fmt.Errorf("label %q target already set: %v", name, l.target)
   128  	}
   129  	l.target = len(b.instructions)
   130  	return nil
   131  }
   132  
   133  // Instructions returns an array of BPF instructions representing the program with all labels
   134  // resolved. Return error in case label resolution failed due to an invalid program.
   135  //
   136  // N.B. Partial results will be returned in the error case, which is useful for debugging.
   137  func (b *ProgramBuilder) Instructions() ([]linux.BPFInstruction, error) {
   138  	if err := b.resolveLabels(); err != nil {
   139  		return b.instructions, err
   140  	}
   141  	return b.instructions, nil
   142  }
   143  
   144  func (b *ProgramBuilder) addLabelSource(labelName string, t jmpType) {
   145  	l, ok := b.labels[labelName]
   146  	if !ok {
   147  		l = &label{sources: make([]source, 0), target: -1}
   148  		b.labels[labelName] = l
   149  	}
   150  	l.sources = append(l.sources, source{line: len(b.instructions), jt: t})
   151  }
   152  
   153  func (b *ProgramBuilder) resolveLabels() error {
   154  	for key, v := range b.labels {
   155  		if _, ok := b.unusableLabels[key]; ok {
   156  			return fmt.Errorf("backwards reference detected for label: %q", key)
   157  		}
   158  
   159  		if v.target == -1 {
   160  			return fmt.Errorf("label target not set: %v", key)
   161  		}
   162  		if v.target >= len(b.instructions) {
   163  			return fmt.Errorf("target is beyond end of ProgramBuilder")
   164  		}
   165  		for _, s := range v.sources {
   166  			// Finds jump instruction that references the label.
   167  			inst := b.instructions[s.line]
   168  			if s.line >= v.target {
   169  				return fmt.Errorf("cannot jump backwards")
   170  			}
   171  			// Calculates the jump offset from current line.
   172  			offset := v.target - s.line - 1
   173  			// Sets offset into jump instruction.
   174  			switch s.jt {
   175  			case jDirect:
   176  				if offset > labelDirectTarget {
   177  					return fmt.Errorf("jump offset to label '%v' is too large: %v, inst: %v, lineno: %v", key, offset, inst, s.line)
   178  				}
   179  				if inst.K != labelDirectTarget {
   180  					return fmt.Errorf("jump target is not a label")
   181  				}
   182  				inst.K = uint32(offset)
   183  			case jTrue:
   184  				if offset > labelTarget {
   185  					return fmt.Errorf("jump offset to label '%v' is too large: %v, inst: %v, lineno: %v", key, offset, inst, s.line)
   186  				}
   187  				if inst.JumpIfTrue != labelTarget {
   188  					return fmt.Errorf("jump target is not a label")
   189  				}
   190  				inst.JumpIfTrue = uint8(offset)
   191  			case jFalse:
   192  				if offset > labelTarget {
   193  					return fmt.Errorf("jump offset to label '%v' is too large: %v, inst: %v, lineno: %v", key, offset, inst, s.line)
   194  				}
   195  				if inst.JumpIfFalse != labelTarget {
   196  					return fmt.Errorf("jump target is not a label")
   197  				}
   198  				inst.JumpIfFalse = uint8(offset)
   199  			}
   200  
   201  			b.instructions[s.line] = inst
   202  		}
   203  	}
   204  	b.labels = map[string]*label{}
   205  	return nil
   206  }