github.com/cilium/ebpf@v0.15.1-0.20240517100537-8079b37aa138/btf/strings.go (about)

     1  package btf
     2  
     3  import (
     4  	"bufio"
     5  	"bytes"
     6  	"errors"
     7  	"fmt"
     8  	"io"
     9  	"maps"
    10  	"slices"
    11  	"strings"
    12  )
    13  
    14  type stringTable struct {
    15  	base    *stringTable
    16  	offsets []uint32
    17  	prevIdx int
    18  	strings []string
    19  }
    20  
    21  // sizedReader is implemented by bytes.Reader, io.SectionReader, strings.Reader, etc.
    22  type sizedReader interface {
    23  	io.Reader
    24  	Size() int64
    25  }
    26  
    27  func readStringTable(r sizedReader, base *stringTable) (*stringTable, error) {
    28  	// When parsing split BTF's string table, the first entry offset is derived
    29  	// from the last entry offset of the base BTF.
    30  	firstStringOffset := uint32(0)
    31  	if base != nil {
    32  		idx := len(base.offsets) - 1
    33  		firstStringOffset = base.offsets[idx] + uint32(len(base.strings[idx])) + 1
    34  	}
    35  
    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 {
    53  		return nil, err
    54  	}
    55  
    56  	if len(strings) == 0 {
    57  		return nil, errors.New("string table is empty")
    58  	}
    59  
    60  	if firstStringOffset == 0 && strings[0] != "" {
    61  		return nil, errors.New("first item in string table is non-empty")
    62  	}
    63  
    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
    74  	}
    75  
    76  	return i + 1, data[:i], nil
    77  }
    78  
    79  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  	// Fast path: zero offset is the empty string, looked up frequently.
    88  	if offset == 0 && st.base == nil {
    89  		return "", nil
    90  	}
    91  
    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  	}
    98  
    99  	i, found := slices.BinarySearch(st.offsets, offset)
   100  	if !found {
   101  		return "", fmt.Errorf("offset %d isn't start of a string", offset)
   102  	}
   103  
   104  	// Set the new increment index, but only if its greater than the current.
   105  	if i > st.prevIdx+1 {
   106  		st.prevIdx = i
   107  	}
   108  
   109  	return st.strings[i], nil
   110  }
   111  
   112  // Num returns the number of strings in the table.
   113  func (st *stringTable) Num() int {
   114  	return len(st.strings)
   115  }
   116  
   117  // stringTableBuilder builds BTF string tables.
   118  type stringTableBuilder struct {
   119  	length  uint32
   120  	strings map[string]uint32
   121  }
   122  
   123  // newStringTableBuilder creates a builder with the given capacity.
   124  //
   125  // capacity may be zero.
   126  func newStringTableBuilder(capacity int) *stringTableBuilder {
   127  	var stb stringTableBuilder
   128  
   129  	if capacity == 0 {
   130  		// Use the runtime's small default size.
   131  		stb.strings = make(map[string]uint32)
   132  	} else {
   133  		stb.strings = make(map[string]uint32, capacity)
   134  	}
   135  
   136  	// Ensure that the empty string is at index 0.
   137  	stb.append("")
   138  	return &stb
   139  }
   140  
   141  // Add a string to the table.
   142  //
   143  // Adding the same string multiple times will only store it once.
   144  func (stb *stringTableBuilder) Add(str string) (uint32, error) {
   145  	if strings.IndexByte(str, 0) != -1 {
   146  		return 0, fmt.Errorf("string contains null: %q", str)
   147  	}
   148  
   149  	offset, ok := stb.strings[str]
   150  	if ok {
   151  		return offset, nil
   152  	}
   153  
   154  	return stb.append(str), nil
   155  }
   156  
   157  func (stb *stringTableBuilder) append(str string) uint32 {
   158  	offset := stb.length
   159  	stb.length += uint32(len(str)) + 1
   160  	stb.strings[str] = offset
   161  	return offset
   162  }
   163  
   164  // Lookup finds the offset of a string in the table.
   165  //
   166  // Returns an error if str hasn't been added yet.
   167  func (stb *stringTableBuilder) Lookup(str string) (uint32, error) {
   168  	offset, ok := stb.strings[str]
   169  	if !ok {
   170  		return 0, fmt.Errorf("string %q is not in table", str)
   171  	}
   172  
   173  	return offset, nil
   174  }
   175  
   176  // Length returns the length in bytes.
   177  func (stb *stringTableBuilder) Length() int {
   178  	return int(stb.length)
   179  }
   180  
   181  // AppendEncoded appends the string table to the end of the provided buffer.
   182  func (stb *stringTableBuilder) AppendEncoded(buf []byte) []byte {
   183  	n := len(buf)
   184  	buf = append(buf, make([]byte, stb.Length())...)
   185  	strings := buf[n:]
   186  	for str, offset := range stb.strings {
   187  		copy(strings[offset:], str)
   188  	}
   189  	return buf
   190  }
   191  
   192  // Copy the string table builder.
   193  func (stb *stringTableBuilder) Copy() *stringTableBuilder {
   194  	return &stringTableBuilder{
   195  		stb.length,
   196  		maps.Clone(stb.strings),
   197  	}
   198  }