github.com/SagerNet/gvisor@v0.0.0-20210707092255-7731c139d75c/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/SagerNet/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 }