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  }