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 }