github.com/inspektor-gadget/inspektor-gadget@v0.28.1/pkg/uprobetracer/usdt.go (about)

     1  // Copyright 2024 The Inspektor Gadget 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 uprobetracer
    16  
    17  import (
    18  	"debug/elf"
    19  	"encoding/binary"
    20  	"errors"
    21  	"fmt"
    22  	"io"
    23  	"os"
    24  	"strings"
    25  )
    26  
    27  // For details regarding the data format of USDT notes, please refer to:
    28  // https://sourceware.org/systemtap/wiki/UserSpaceProbeImplementation
    29  const (
    30  	sdtNoteSectionName = ".note.stapsdt"
    31  	sdtBaseSectionName = ".stapsdt.base"
    32  )
    33  
    34  type noteHeader struct {
    35  	NameSize uint32
    36  	DescSize uint32
    37  	Type     uint32
    38  }
    39  
    40  type usdtAttachInfo struct {
    41  	attachAddress    uint64
    42  	semaphoreAddress uint64
    43  }
    44  
    45  func vaddr2ElfOffset(f *elf.File, addr uint64) (uint64, error) {
    46  	for _, prog := range f.Progs {
    47  		if prog.Vaddr <= addr && addr < (prog.Vaddr+prog.Memsz) {
    48  			return addr - prog.Vaddr + prog.Off, nil
    49  		}
    50  	}
    51  	return 0, fmt.Errorf("malformed elf file: elf prog containing addr %x not found", addr)
    52  }
    53  
    54  func alignUp[T int | int32 | int64 | uint | uint32 | uint64](n T, align T) T {
    55  	return (n + align - 1) / align * align
    56  }
    57  
    58  func getUsdtInfo(filepath string, attachSymbol string) (*usdtAttachInfo, error) {
    59  	parts := strings.Split(attachSymbol, ":")
    60  	if len(parts) != 2 {
    61  		return nil, fmt.Errorf("invalid USDT section name: %q", attachSymbol)
    62  	}
    63  	providerName := parts[0]
    64  	probeName := parts[1]
    65  
    66  	file, err := os.Open(filepath)
    67  	if err != nil {
    68  		return nil, fmt.Errorf("opening file %q: %w", filepath, err)
    69  	}
    70  	defer file.Close()
    71  
    72  	fileInfo, err := file.Stat()
    73  	if err != nil {
    74  		return nil, fmt.Errorf("stating file %q: %w", filepath, err)
    75  	}
    76  	if !fileInfo.Mode().IsRegular() {
    77  		return nil, fmt.Errorf("ELF file %q is not regular", filepath)
    78  	}
    79  
    80  	elfReader, err := elf.NewFile(file)
    81  	if err != nil {
    82  		return nil, fmt.Errorf("reading elf file %q: %w", filepath, err)
    83  	}
    84  	defer elfReader.Close()
    85  
    86  	noteSection := elfReader.Section(sdtNoteSectionName)
    87  	if noteSection == nil {
    88  		return nil, errors.New("USDT note section does not exist")
    89  	}
    90  	if noteSection.Type != elf.SHT_NOTE {
    91  		return nil, fmt.Errorf("section %q is not a note", sdtNoteSectionName)
    92  	}
    93  	notesReader := noteSection.Open()
    94  
    95  	baseSection := elfReader.Section(sdtBaseSectionName)
    96  	if baseSection == nil {
    97  		return nil, errors.New("USDT base section does not exist")
    98  	}
    99  	if baseSection.Type != elf.SHT_PROGBITS {
   100  		return nil, fmt.Errorf("%q is not a program defined section", sdtBaseSectionName)
   101  	}
   102  
   103  	wordSize := 4
   104  	if elfReader.Class == elf.ELFCLASS64 {
   105  		wordSize = 8
   106  	}
   107  
   108  	// walk through USDT notes, and match with providerName and probeName
   109  	// For details of the structure of ELF notes, please refer to
   110  	// https://man7.org/linux/man-pages/man5/elf.5.html, the `Notes (Nhdr)` section
   111  	for {
   112  		var header noteHeader
   113  		err = binary.Read(notesReader, elfReader.ByteOrder, &header)
   114  		if err != nil {
   115  			if errors.Is(err, io.EOF) {
   116  				break
   117  			}
   118  			return nil, fmt.Errorf("reading USDT note header: %w", err)
   119  		}
   120  
   121  		name := make([]byte, alignUp(uint64(header.NameSize), 4))
   122  		err = binary.Read(notesReader, elfReader.ByteOrder, &name)
   123  		if err != nil {
   124  			return nil, fmt.Errorf("reading USDT note name: %w", err)
   125  		}
   126  
   127  		desc := make([]byte, alignUp(uint64(header.DescSize), 4))
   128  		err = binary.Read(notesReader, elfReader.ByteOrder, &desc)
   129  		if err != nil {
   130  			return nil, fmt.Errorf("reading USDT note desc: %w", err)
   131  		}
   132  
   133  		if string(name) != "stapsdt\x00" || header.Type != 3 {
   134  			continue
   135  		}
   136  
   137  		elfLocation := elfReader.ByteOrder.Uint64(desc[:wordSize])
   138  		elfBase := elfReader.ByteOrder.Uint64(desc[wordSize : 2*wordSize])
   139  		elfSemaphore := elfReader.ByteOrder.Uint64(desc[2*wordSize : 3*wordSize])
   140  
   141  		diff := baseSection.Addr - elfBase
   142  		location, err := vaddr2ElfOffset(elfReader, elfLocation+diff)
   143  		if err != nil {
   144  			return nil, err
   145  		}
   146  
   147  		if elfSemaphore != 0 {
   148  			elfSemaphore, err = vaddr2ElfOffset(elfReader, elfSemaphore+diff)
   149  			if err != nil {
   150  				return nil, err
   151  			}
   152  		}
   153  
   154  		provider := readStringFromBytes(desc, uint32(3*wordSize))
   155  		probe := readStringFromBytes(desc, uint32(3*wordSize+len(provider)+1))
   156  		if provider == providerName && probe == probeName {
   157  			return &usdtAttachInfo{location, elfSemaphore}, nil
   158  		}
   159  	}
   160  	return nil, errors.New("no matching USDT metadata")
   161  }