1
1
package btf
2
2
3
3
import (
4
- "bufio"
5
4
"bytes"
6
5
"errors"
7
6
"fmt"
8
7
"io"
9
8
"maps"
10
- "slices"
11
9
"strings"
12
10
)
13
11
14
12
type stringTable struct {
15
- base * stringTable
16
- offsets []uint32
17
- prevIdx int
18
- strings []string
13
+ base * stringTable
14
+ bytes []byte
19
15
}
20
16
21
17
// sizedReader is implemented by bytes.Reader, io.SectionReader, strings.Reader, etc.
@@ -29,89 +25,57 @@ func readStringTable(r sizedReader, base *stringTable) (*stringTable, error) {
29
25
// from the last entry offset of the base BTF.
30
26
firstStringOffset := uint32 (0 )
31
27
if base != nil {
32
- idx := len (base .offsets ) - 1
33
- firstStringOffset = base .offsets [idx ] + uint32 (len (base .strings [idx ])) + 1
28
+ firstStringOffset = uint32 (len (base .bytes ))
34
29
}
35
30
36
- // Derived from vmlinux BTF.
37
- const averageStringLength = 16
38
-
39
- n := int (r .Size () / averageStringLength )
40
- offsets := make ([]uint32 , 0 , n )
41
- strings := make ([]string , 0 , n )
42
-
43
- offset := firstStringOffset
44
- scanner := bufio .NewScanner (r )
45
- scanner .Split (splitNull )
46
- for scanner .Scan () {
47
- str := scanner .Text ()
48
- offsets = append (offsets , offset )
49
- strings = append (strings , str )
50
- offset += uint32 (len (str )) + 1
51
- }
52
- if err := scanner .Err (); err != nil {
31
+ bytes := make ([]byte , r .Size ())
32
+ if _ , err := io .ReadFull (r , bytes ); err != nil {
53
33
return nil , err
54
34
}
55
35
56
- if len (strings ) == 0 {
36
+ if len (bytes ) == 0 {
57
37
return nil , errors .New ("string table is empty" )
58
38
}
59
39
60
- if firstStringOffset == 0 && strings [ 0 ] != "" {
61
- return nil , errors .New ("first item in string table is non-empty " )
40
+ if bytes [ len ( bytes ) - 1 ] != 0 {
41
+ return nil , errors .New ("string table isn't null terminated " )
62
42
}
63
43
64
- return & stringTable {base , offsets , 0 , strings }, nil
65
- }
66
-
67
- func splitNull (data []byte , atEOF bool ) (advance int , token []byte , err error ) {
68
- i := bytes .IndexByte (data , 0 )
69
- if i == - 1 {
70
- if atEOF && len (data ) > 0 {
71
- return 0 , nil , errors .New ("string table isn't null terminated" )
72
- }
73
- return 0 , nil , nil
44
+ if firstStringOffset == 0 && bytes [0 ] != 0 {
45
+ return nil , errors .New ("first item in string table is non-empty" )
74
46
}
75
47
76
- return i + 1 , data [: i ] , nil
48
+ return & stringTable { base : base , bytes : bytes } , nil
77
49
}
78
50
79
51
func (st * stringTable ) Lookup (offset uint32 ) (string , error ) {
80
- if st .base != nil && offset <= st .base .offsets [len (st .base .offsets )- 1 ] {
81
- return st .base .lookup (offset )
82
- }
83
- return st .lookup (offset )
84
- }
85
-
86
- func (st * stringTable ) lookup (offset uint32 ) (string , error ) {
87
52
// Fast path: zero offset is the empty string, looked up frequently.
88
- if offset == 0 && st . base == nil {
53
+ if offset == 0 {
89
54
return "" , nil
90
55
}
91
56
92
- // Accesses tend to be globally increasing, so check if the next string is
93
- // the one we want. This skips the binary search in about 50% of cases.
94
- if st .prevIdx + 1 < len (st .offsets ) && st .offsets [st .prevIdx + 1 ] == offset {
95
- st .prevIdx ++
96
- return st .strings [st .prevIdx ], nil
97
- }
57
+ return st .lookupSlow (offset )
58
+ }
98
59
99
- i , found := slices .BinarySearch (st .offsets , offset )
100
- if ! found {
101
- return "" , fmt .Errorf ("offset %d isn't start of a string" , offset )
60
+ func (st * stringTable ) lookupSlow (offset uint32 ) (string , error ) {
61
+ if st .base != nil {
62
+ n := uint32 (len (st .base .bytes ))
63
+ if offset < n {
64
+ return st .base .lookupSlow (offset )
65
+ }
66
+ offset -= n
102
67
}
103
68
104
- // Set the new increment index, but only if its greater than the current.
105
- if i > st .prevIdx + 1 {
106
- st .prevIdx = i
69
+ if offset > uint32 (len (st .bytes )) {
70
+ return "" , fmt .Errorf ("offset %d is out of bounds of string table" , offset )
107
71
}
108
72
109
- return st .strings [i ], nil
110
- }
73
+ if offset > 0 && st .bytes [offset - 1 ] != 0 {
74
+ return "" , fmt .Errorf ("offset %d is not the beginning of a string" , offset )
75
+ }
111
76
112
- // Num returns the number of strings in the table.
113
- func (st * stringTable ) Num () int {
114
- return len (st .strings )
77
+ i := bytes .IndexByte (st .bytes [offset :], 0 )
78
+ return string (st .bytes [offset : offset + uint32 (i )]), nil
115
79
}
116
80
117
81
// stringTableBuilder builds BTF string tables.
0 commit comments