github.com/cilium/ebpf@v0.10.0/btf/strings.go (about) 1 package btf 2 3 import ( 4 "bufio" 5 "bytes" 6 "errors" 7 "fmt" 8 "io" 9 "strings" 10 ) 11 12 type stringTable struct { 13 base *stringTable 14 offsets []uint32 15 strings []string 16 } 17 18 // sizedReader is implemented by bytes.Reader, io.SectionReader, strings.Reader, etc. 19 type sizedReader interface { 20 io.Reader 21 Size() int64 22 } 23 24 func readStringTable(r sizedReader, base *stringTable) (*stringTable, error) { 25 // When parsing split BTF's string table, the first entry offset is derived 26 // from the last entry offset of the base BTF. 27 firstStringOffset := uint32(0) 28 if base != nil { 29 idx := len(base.offsets) - 1 30 firstStringOffset = base.offsets[idx] + uint32(len(base.strings[idx])) + 1 31 } 32 33 // Derived from vmlinux BTF. 34 const averageStringLength = 16 35 36 n := int(r.Size() / averageStringLength) 37 offsets := make([]uint32, 0, n) 38 strings := make([]string, 0, n) 39 40 offset := firstStringOffset 41 scanner := bufio.NewScanner(r) 42 scanner.Split(splitNull) 43 for scanner.Scan() { 44 str := scanner.Text() 45 offsets = append(offsets, offset) 46 strings = append(strings, str) 47 offset += uint32(len(str)) + 1 48 } 49 if err := scanner.Err(); err != nil { 50 return nil, err 51 } 52 53 if len(strings) == 0 { 54 return nil, errors.New("string table is empty") 55 } 56 57 if firstStringOffset == 0 && strings[0] != "" { 58 return nil, errors.New("first item in string table is non-empty") 59 } 60 61 return &stringTable{base, offsets, strings}, nil 62 } 63 64 func splitNull(data []byte, atEOF bool) (advance int, token []byte, err error) { 65 i := bytes.IndexByte(data, 0) 66 if i == -1 { 67 if atEOF && len(data) > 0 { 68 return 0, nil, errors.New("string table isn't null terminated") 69 } 70 return 0, nil, nil 71 } 72 73 return i + 1, data[:i], nil 74 } 75 76 func (st *stringTable) Lookup(offset uint32) (string, error) { 77 if st.base != nil && offset <= st.base.offsets[len(st.base.offsets)-1] { 78 return st.base.lookup(offset) 79 } 80 return st.lookup(offset) 81 } 82 83 func (st *stringTable) lookup(offset uint32) (string, error) { 84 i := search(st.offsets, offset) 85 if i == len(st.offsets) || st.offsets[i] != offset { 86 return "", fmt.Errorf("offset %d isn't start of a string", offset) 87 } 88 89 return st.strings[i], nil 90 } 91 92 func (st *stringTable) Length() int { 93 if len(st.offsets) == 0 || len(st.strings) == 0 { 94 return 0 95 } 96 97 last := len(st.offsets) - 1 98 return int(st.offsets[last]) + len(st.strings[last]) + 1 99 } 100 101 func (st *stringTable) Marshal(w io.Writer) error { 102 for _, str := range st.strings { 103 _, err := io.WriteString(w, str) 104 if err != nil { 105 return err 106 } 107 _, err = w.Write([]byte{0}) 108 if err != nil { 109 return err 110 } 111 } 112 return nil 113 } 114 115 // search is a copy of sort.Search specialised for uint32. 116 // 117 // Licensed under https://go.dev/LICENSE 118 func search(ints []uint32, needle uint32) int { 119 // Define f(-1) == false and f(n) == true. 120 // Invariant: f(i-1) == false, f(j) == true. 121 i, j := 0, len(ints) 122 for i < j { 123 h := int(uint(i+j) >> 1) // avoid overflow when computing h 124 // i ≤ h < j 125 if !(ints[h] >= needle) { 126 i = h + 1 // preserves f(i-1) == false 127 } else { 128 j = h // preserves f(j) == true 129 } 130 } 131 // i == j, f(i-1) == false, and f(j) (= f(i)) == true => answer is i. 132 return i 133 } 134 135 // stringTableBuilder builds BTF string tables. 136 type stringTableBuilder struct { 137 length uint32 138 strings map[string]uint32 139 } 140 141 // newStringTableBuilder creates a builder with the given capacity. 142 // 143 // capacity may be zero. 144 func newStringTableBuilder() *stringTableBuilder { 145 stb := &stringTableBuilder{0, make(map[string]uint32)} 146 // Ensure that the empty string is at index 0. 147 stb.append("") 148 return stb 149 } 150 151 // newStringTableBuilderFromTable creates a new builder from an existing string table. 152 func newStringTableBuilderFromTable(contents *stringTable) *stringTableBuilder { 153 stb := &stringTableBuilder{0, make(map[string]uint32, len(contents.strings)+1)} 154 stb.append("") 155 156 for _, str := range contents.strings { 157 if str != "" { 158 stb.append(str) 159 } 160 } 161 162 return stb 163 } 164 165 // Add a string to the table. 166 // 167 // Adding the same string multiple times will only store it once. 168 func (stb *stringTableBuilder) Add(str string) (uint32, error) { 169 if strings.IndexByte(str, 0) != -1 { 170 return 0, fmt.Errorf("string contains null: %q", str) 171 } 172 173 offset, ok := stb.strings[str] 174 if ok { 175 return offset, nil 176 } 177 178 return stb.append(str), nil 179 } 180 181 func (stb *stringTableBuilder) append(str string) uint32 { 182 offset := stb.length 183 stb.length += uint32(len(str)) + 1 184 stb.strings[str] = offset 185 return offset 186 } 187 188 // Lookup finds the offset of a string in the table. 189 // 190 // Returns an error if str hasn't been added yet. 191 func (stb *stringTableBuilder) Lookup(str string) (uint32, error) { 192 offset, ok := stb.strings[str] 193 if !ok { 194 return 0, fmt.Errorf("string %q is not in table", str) 195 } 196 197 return offset, nil 198 199 } 200 201 // Length returns the length in bytes. 202 func (stb *stringTableBuilder) Length() int { 203 return int(stb.length) 204 } 205 206 // Marshal a string table into its binary representation. 207 func (stb *stringTableBuilder) Marshal() []byte { 208 buf := make([]byte, stb.Length()) 209 stb.MarshalBuffer(buf) 210 return buf 211 } 212 213 // Marshal a string table into a pre-allocated buffer. 214 // 215 // The buffer must be at least of size Length(). 216 func (stb *stringTableBuilder) MarshalBuffer(buf []byte) { 217 for str, offset := range stb.strings { 218 n := copy(buf[offset:], str) 219 buf[offset+uint32(n)] = 0 220 } 221 }