diff --git a/interpreter/nodev8/v8.go b/interpreter/nodev8/v8.go index 3353a699d..28bfa4ac6 100644 --- a/interpreter/nodev8/v8.go +++ b/interpreter/nodev8/v8.go @@ -1884,7 +1884,7 @@ func (d *v8Data) Attach(ebpf interpreter.EbpfHandler, pid libpf.PID, _ libpf.Add func (d *v8Data) Unload(_ interpreter.EbpfHandler) { } -func (d *v8Data) readIntrospectionData(ef *pfelf.File, syms libpf.SymbolFinder) error { +func (d *v8Data) readIntrospectionData(ef *pfelf.File) error { // Read the variables from the pfelf.File so we avoid failures if the process // exists during extraction of the introspection data. rm := ef.GetRemoteMemory() @@ -1921,7 +1921,7 @@ func (d *v8Data) readIntrospectionData(ef *pfelf.File, syms libpf.SymbolFinder) if memberVal.Kind() == reflect.Bool { s = "v8dbg_parent_" + className + "__" + memberName } - addr, err := syms.LookupSymbolAddress(libpf.SymbolName(s)) + addr, err := ef.LookupSymbolAddress(libpf.SymbolName(s)) if err != nil { log.Debugf("V8: %s = not found", s) if classType.Name == "FrameType" { @@ -2146,18 +2146,14 @@ func Loader(ebpf interpreter.EbpfHandler, info *interpreter.LoaderInfo) (interpr var vers [3]uint32 for i, sym := range []string{"major", "minor", "build"} { - var addr libpf.SymbolValue - var raw [4]byte // Resolve and read "v8::internal::Versions::XXXXXX_E" + var val []byte sym = fmt.Sprintf("_ZN2v88internal7Version6%s_E", sym) - addr, err = ef.LookupSymbolAddress(libpf.SymbolName(sym)) - if err == nil { - _, err = ef.ReadVirtualMemory(raw[:], int64(addr)) - } + _, val, err = ef.SymbolData(libpf.SymbolName(sym), 4) if err != nil { - return nil, fmt.Errorf("symbol '%s': %v", sym, err) + return nil, fmt.Errorf("unable to read '%s': %v", sym, err) } - vers[i] = npsr.Uint32(raw[:], 0) + vers[i] = npsr.Uint32(val, 0) } version := vers[0]*0x1000000 + vers[1]*0x10000 + vers[2] @@ -2167,19 +2163,11 @@ func Loader(ebpf interpreter.EbpfHandler, info *interpreter.LoaderInfo) (interpr vers[0], vers[1], vers[2]) } - var syms libpf.SymbolFinder - syms, err = ef.ReadDynamicSymbols() - if err != nil { - // Dynamic section does not exists for core dumps. Use the pfelf as - // symbol finder then. - syms = ef - } - d := &v8Data{ version: version, } - addr, err := syms.LookupSymbolAddress("_ZN2v88internal8Snapshot19DefaultSnapshotBlobEv") + addr, err := ef.LookupSymbolAddress("_ZN2v88internal8Snapshot19DefaultSnapshotBlobEv") if err == nil { // If there is a big stack delta soon after v8::internal::Snapshot::DefaultSnapshotBlob() // assume it is the V8 snapshot data. @@ -2192,7 +2180,7 @@ func Loader(ebpf interpreter.EbpfHandler, info *interpreter.LoaderInfo) (interpr } } - sym, err := syms.LookupSymbol("_ZN2v88internal11interpreter9Bytecodes14kBytecodeSizesE") + sym, err := ef.LookupSymbol("_ZN2v88internal11interpreter9Bytecodes14kBytecodeSizesE") if err == nil && sym.Size%3 == 0 && sym.Size < 3*256 { // Symbol v8::internal::interpreter::Bytecodes::kBytecodeSizes: // static const uint8_t Bytecodes::kBytecodeSizes[3][kBytecodeCount]; @@ -2215,7 +2203,7 @@ func Loader(ebpf interpreter.EbpfHandler, info *interpreter.LoaderInfo) (interpr } // load introspection data - if err = d.readIntrospectionData(ef, syms); err != nil { + if err = d.readIntrospectionData(ef); err != nil { return nil, err } diff --git a/interpreter/php/opcache.go b/interpreter/php/opcache.go index 5e5107643..a7de6bf34 100644 --- a/interpreter/php/opcache.go +++ b/interpreter/php/opcache.go @@ -311,18 +311,14 @@ func getOpcacheJITInfo(ef *pfelf.File) (dasmBuf, dasmSize libpf.Address, err err // Note: zend_jit_unprotect was chosen because it immediately calls mprotect with // dasm_buf as the first parameter, which should be in a register for both x86-64 // and ARM64. - zendJit, err := ef.LookupSymbolAddress("zend_jit_unprotect") - if err != nil { - return 0, 0, err - } // We should only need 64 bytes, since this should be early in the instruction sequence. - code := make([]byte, 64) - if _, err = ef.ReadVirtualMemory(code, int64(zendJit)); err != nil { - return 0, 0, err + addr, code, err := ef.SymbolData("zend_jit_unprotect", 64) + if err != nil { + return 0, 0, fmt.Errorf("unable to read 'zend_jit_unprotect': %w", err) } - dasmBufPtr, dasmSizePtr, err := retrieveJITBufferPtrWrapper(code, zendJit) + dasmBufPtr, dasmSizePtr, err := retrieveJITBufferPtrWrapper(code, addr) if err != nil { return 0, 0, fmt.Errorf("failed to extract DASM pointers: %w", err) } diff --git a/interpreter/php/php.go b/interpreter/php/php.go index 98a97d287..ddb9dcccd 100644 --- a/interpreter/php/php.go +++ b/interpreter/php/php.go @@ -202,21 +202,16 @@ func recoverExecuteExJumpLabelAddress(ef *pfelf.File) (libpf.SymbolValue, error) // executor function, has been such at least since PHP7.0. This is guaranteed // to be the vm executor function in PHP JIT'd code, since the JIT is (currently) // inoperable with overridden execute_ex's - executeExAddr, err := ef.LookupSymbolAddress("execute_ex") - if err != nil { - return libpf.SymbolValueInvalid, - fmt.Errorf("could not find execute_ex: %w", err) - } // The address we care about varies from being 47 bytes in to about 107 bytes in, - // so we'll read 128 bytes. This might need to be adjusted up in future. - code := make([]byte, 128) - if _, err = ef.ReadVirtualMemory(code, int64(executeExAddr)); err != nil { + // so we'll cap at 128 bytes. This might need to be adjusted up in future. + addr, code, err := ef.SymbolData("execute_ex", 128) + if err != nil { return libpf.SymbolValueInvalid, - fmt.Errorf("could not read from executeExAddr: %w", err) + fmt.Errorf("unable to read 'execute_ex': %w", err) } - returnAddress, err := retrieveExecuteExJumpLabelAddressWrapper(code, executeExAddr) + returnAddress, err := retrieveExecuteExJumpLabelAddressWrapper(code, addr) if err != nil { return libpf.SymbolValueInvalid, fmt.Errorf("reading the return address from execute_ex failed (%w)", @@ -233,23 +228,17 @@ func determineVMKind(ef *pfelf.File) (uint, error) { // This is a publicly exposed function in PHP that returns the VM type // This has been implemented in PHP since at least 7.2 - vmKindAddr, err := ef.LookupSymbolAddress("zend_vm_kind") - if err != nil { - return 0, fmt.Errorf("zend_vm_kind not found: %w", err) - } // We should only need around 32 bytes here, since this function should be // really short (e.g a mov and a ret). - code := make([]byte, 32) - if _, err = ef.ReadVirtualMemory(code, int64(vmKindAddr)); err != nil { - return 0, fmt.Errorf("could not read from zend_vm_kind: %w", err) + _, code, err := ef.SymbolData("zend_vm_kind", 64) + if err != nil { + return 0, fmt.Errorf("unable to read 'zend_vm_kind': %w", err) } - vmKind, err := retrieveZendVMKindWrapper(code) if err != nil { return 0, fmt.Errorf("an error occurred decoding zend_vm_kind: %w", err) } - return vmKind, nil } diff --git a/interpreter/python/python.go b/interpreter/python/python.go index 2d7cc7563..65f9ceadd 100644 --- a/interpreter/python/python.go +++ b/interpreter/python/python.go @@ -656,28 +656,20 @@ func (d *pythonData) readIntrospectionData(ef *pfelf.File, symbol libpf.SymbolNa // decodeStub will resolve a given symbol, extract the code for it, and analyze // the code to resolve specified argument parameter to the first jump/call. -func decodeStub( - ef *pfelf.File, - memoryBase libpf.SymbolValue, - symbolName libpf.SymbolName, -) (libpf.SymbolValue, error) { - codeAddress, err := ef.LookupSymbolAddress(symbolName) +func decodeStub(ef *pfelf.File, memoryBase libpf.SymbolValue, + symbolName libpf.SymbolName) (libpf.SymbolValue, error) { + // Read and decode the code for the symbol + addr, code, err := ef.SymbolData(symbolName, 64) if err != nil { - return libpf.SymbolValueInvalid, fmt.Errorf("lookup %s failed: %v", + return libpf.SymbolValueInvalid, fmt.Errorf("unable to read '%s': %v", symbolName, err) } - - code := make([]byte, 64) - if _, err = ef.ReadVirtualMemory(code, int64(codeAddress)); err != nil { - return libpf.SymbolValueInvalid, fmt.Errorf("reading %s 0x%x code failed: %v", - symbolName, codeAddress, err) - } - value, err := decodeStubArgumentWrapper(code, codeAddress, memoryBase) + value, err := decodeStubArgumentWrapper(code, addr, memoryBase) // Sanity check the value range and alignment if err != nil || value%4 != 0 { return libpf.SymbolValueInvalid, fmt.Errorf("decode stub %s 0x%x %s failed (0x%x): %v", - symbolName, codeAddress, hex.Dump(code), value, err) + symbolName, addr, hex.Dump(code), value, err) } // If base symbol (_PyRuntime) is not provided, accept any found value. if memoryBase == 0 && value != 0 { @@ -688,7 +680,7 @@ func decodeStub( return value, nil } return libpf.SymbolValueInvalid, fmt.Errorf("decode stub %s 0x%x %s failed (0x%x)", - symbolName, codeAddress, hex.Dump(code), value) + symbolName, addr, hex.Dump(code), value) } func Loader(ebpf interpreter.EbpfHandler, info *interpreter.LoaderInfo) (interpreter.Data, error) { diff --git a/interpreter/ruby/ruby.go b/interpreter/ruby/ruby.go index 26410fa87..ac12c18f6 100644 --- a/interpreter/ruby/ruby.go +++ b/interpreter/ruby/ruby.go @@ -12,6 +12,7 @@ import ( "regexp" "runtime" "strconv" + "strings" "sync" "sync/atomic" "unsafe" @@ -784,19 +785,16 @@ func (r *rubyInstance) GetAndResetMetrics() ([]metrics.Metric, error) { // determineRubyVersion looks for the symbol ruby_version and extracts version // information from its value. func determineRubyVersion(ef *pfelf.File) (uint32, error) { - sym, err := ef.LookupSymbol("ruby_version") + _, memory, err := ef.SymbolData("ruby_version", 64) if err != nil { - return 0, fmt.Errorf("symbol ruby_version not found: %v", err) + return 0, fmt.Errorf("unable to read 'ruby_version': %v", err) } - memory := make([]byte, 5) - if _, err := ef.ReadVirtualMemory(memory, int64(sym.Address)); err != nil { - return 0, fmt.Errorf("failed to read process memory at 0x%x:%v", - sym.Address, err) + versionString := strings.TrimRight(unsafe.String(unsafe.SliceData(memory), len(memory)), "\x00") + matches := rubyVersionRegex.FindStringSubmatch(versionString) + if len(matches) < 3 { + return 0, fmt.Errorf("failed to parse version string: '%s'", versionString) } - - matches := rubyVersionRegex.FindStringSubmatch(string(memory)) - major, _ := strconv.Atoi(matches[1]) minor, _ := strconv.Atoi(matches[2]) release, _ := strconv.Atoi(matches[3]) diff --git a/libpf/pfelf/file.go b/libpf/pfelf/file.go index 7cecbd4fd..a0d98d287 100644 --- a/libpf/pfelf/file.go +++ b/libpf/pfelf/file.go @@ -69,6 +69,9 @@ type File struct { // ehFrame is a pointer to the PT_GNU_EH_FRAME segment of the ELF ehFrame *Prog + // loadData is a slice of pointers to the PT_LOAD data segments of the ELF. + loadData []*Prog + // ROData is a slice of pointers to the read-only data segments of the ELF // These are sorted so that segments marked as "read" appear before those // marked as "read-execute" @@ -231,6 +234,8 @@ func newFile(r io.ReaderAt, closer io.Closer, f.Progs = make([]Prog, hdr.Phnum) virtualBase := ^uint64(0) + numROData := 0 + numLoad := 0 for i, ph := range progs { p := &f.Progs[i] p.ProgHeader = elf.ProgHeader{ @@ -249,12 +254,24 @@ func newFile(r io.ReaderAt, closer io.Closer, if p.Vaddr < virtualBase { virtualBase = p.Vaddr } - andFlags := p.Flags & (elf.PF_R | elf.PF_W | elf.PF_X) - if andFlags == elf.PF_R || andFlags == (elf.PF_R|elf.PF_X) { + if p.isRoData() { + numROData++ + } + numLoad++ + } + } + f.loadData = make([]*Prog, 0, numLoad) + f.ROData = make([]*Prog, 0, numROData) + for i := range progs { + p := &f.Progs[i] + if p.Type == elf.PT_LOAD { + f.loadData = append(f.loadData, p) + if p.isRoData() { f.ROData = append(f.ROData, p) } } } + if loadAddress != 0 { // Calculate the bias for coredump files f.bias = libpf.Address(loadAddress - virtualBase) @@ -379,10 +396,6 @@ func (f *File) LoadSections() error { // Load the section name string table strsh := f.Sections[hdr.Shstrndx] - if strsh.FileSize >= 1024*1024 { - return fmt.Errorf("section headers string table too large (%d)", - strsh.FileSize) - } strtab, err := strsh.Data(maxBytesLargeSection) if err != nil { return err @@ -418,20 +431,67 @@ func (f *File) Section(name string) *Section { return nil } +// findVirtualAddressProg determines the Prog header containing the virtual address. +func (f *File) findVirtualAddressProg(addr uint64) *Prog { + // Search for the Program header that contains the start address. + for _, ph := range f.loadData { + if addr >= ph.Vaddr && addr < ph.Vaddr+ph.Memsz { + return ph + } + } + return nil +} + +// VirtualMemory returns a slice for the request data at a virtual address. +// The slice may point to mmapped data or be a newly allocated slice. +// The maxSize is the limit for allocating the memory from heap. +func (f *File) VirtualMemory(addr int64, sz, maxSize int) ([]byte, error) { + if sz == 0 { + return nil, nil + } + if ph := f.findVirtualAddressProg(uint64(addr)); ph != nil { + offset := addr - int64(ph.Vaddr) + if offset+int64(sz) <= int64(ph.Filesz) { + if mapping, ok := ph.elfReader.(*mmap.ReaderAt); ok { + return mapping.Subslice(int(ph.Off)+int(offset), sz) + } + } + if sz > maxSize { + return nil, fmt.Errorf("virtual memory area too large (%d) to copy", sz) + } + buf := make([]byte, sz) + n, err := ph.ReadAt(buf, offset) + return buf[:n], err + } + return nil, fmt.Errorf("no matching segment for 0x%x", uint64(addr)) +} + +// SymbolData returns the data associated with given dynamic symbol. +// The backing mmapped data is returned if possible, otherwise a maximum of +// maxCopy bytes of the symbol data will read to newly allocated buffer. +func (f *File) SymbolData(name libpf.SymbolName, maxCopy int) (libpf.SymbolValue, []byte, error) { + sym, err := f.LookupSymbol(name) + if err != nil { + return 0, nil, err + } + symSize := int(sym.Size) + if symSize > maxCopy { + // Truncate read size if not memory mapped data. + if _, ok := f.elfReader.(*mmap.ReaderAt); !ok { + symSize = maxCopy + } + } + data, err := f.VirtualMemory(int64(sym.Address), symSize, maxCopy) + return sym.Address, data, err +} + // ReadVirtualMemory reads bytes from given virtual address func (f *File) ReadVirtualMemory(p []byte, addr int64) (int, error) { if len(p) == 0 { return 0, nil } - for _, ph := range f.Progs { - // Search for the Program header that contains the start address. - // ReadVirtualMemory() supports ReadAt() style indication of reading - // less bytes then requested, so addr+len(p) can be an address beyond - // the segment and ReadAt() will give short read. - if ph.Type == elf.PT_LOAD && uint64(addr) >= ph.Vaddr && - uint64(addr) < ph.Vaddr+ph.Memsz { - return ph.ReadAt(p, addr-int64(ph.Vaddr)) - } + if ph := f.findVirtualAddressProg(uint64(addr)); ph != nil { + return ph.ReadAt(p, addr-int64(ph.Vaddr)) } return 0, fmt.Errorf("no matching segment for 0x%x", uint64(addr)) } @@ -664,6 +724,15 @@ func (f *File) CRC32() (int32, error) { return int32(h.Sum32()), nil } +// isRoData determine if this program header is read-only data. +func (ph *Prog) isRoData() bool { + if ph.Type != elf.PT_LOAD { + return false + } + andFlags := ph.Flags & (elf.PF_R | elf.PF_W | elf.PF_X) + return andFlags == elf.PF_R || andFlags == (elf.PF_R|elf.PF_X) +} + // ReadAt implements the io.ReaderAt interface func (ph *Prog) ReadAt(p []byte, off int64) (n int, err error) { // First load as much as possible from the disk @@ -706,8 +775,7 @@ func (ph *Prog) Open() io.ReadSeeker { // Data loads the whole program header referenced data, and returns it as slice. func (ph *Prog) Data(maxSize uint) ([]byte, error) { - mapping, ok := ph.elfReader.(*mmap.ReaderAt) - if ok { + if mapping, ok := ph.elfReader.(*mmap.ReaderAt); ok { return mapping.Subslice(int(ph.Off), int(ph.Filesz)) } @@ -752,8 +820,7 @@ func (sh *Section) Data(maxSize uint) ([]byte, error) { return nil, errors.New("compressed sections not supported") } - mapping, ok := sh.elfReader.(*mmap.ReaderAt) - if ok { + if mapping, ok := sh.elfReader.(*mmap.ReaderAt); ok { return mapping.Subslice(int(sh.Offset), int(sh.FileSize)) } @@ -789,14 +856,14 @@ func (f *File) readAndMatchSymbol(n uint32, name libpf.SymbolName) (libpf.Symbol f.symbolsAddr+int64(n)*symSz); err != nil { return libpf.Symbol{}, false } - slen := len(name) + 1 - sname := make([]byte, slen) - if _, err := f.ReadVirtualMemory(sname, f.stringsAddr+int64(sym.Name)); err != nil { + slen := len(name) + sname, err := f.VirtualMemory(f.stringsAddr+int64(sym.Name), slen+1, maxBytesSmallSection) + if err != nil { return libpf.Symbol{}, false } // Verify that name matches - if sname[slen-1] != 0 || libpf.SymbolName(sname[:slen-1]) != name { + if sname[slen] != 0 || unsafe.String(unsafe.SliceData(sname), slen) != string(name) { return libpf.Symbol{}, false } @@ -932,43 +999,48 @@ func (f *File) LookupSymbolAddress(symbol libpf.SymbolName) (libpf.SymbolValue, return s.Address, nil } -// loadSymbolTable reads given symbol table -func (f *File) loadSymbolTable(name string) (*libpf.SymbolMap, error) { +// visitSymbolTable visits all symbols in the given symbol table. +func (f *File) visitSymbolTable(name string, visitor func(libpf.Symbol)) error { symTab := f.Section(name) if symTab == nil { - return nil, fmt.Errorf("failed to read %v: section not present", name) + return fmt.Errorf("failed to read %v: section not present", name) } if symTab.Link >= uint32(len(f.Sections)) { - return nil, fmt.Errorf("failed to read %v strtab: link %v out of range", + return fmt.Errorf("failed to read %v strtab: link %v out of range", name, symTab.Link) } strTab := f.Sections[symTab.Link] strs, err := strTab.Data(maxBytesLargeSection) if err != nil { - return nil, fmt.Errorf("failed to read %v: %v", strTab.Name, err) + return fmt.Errorf("failed to read %v: %v", strTab.Name, err) } syms, err := symTab.Data(maxBytesLargeSection) if err != nil { - return nil, fmt.Errorf("failed to read %v: %v", name, err) + return fmt.Errorf("failed to read %v: %v", name, err) } - symMap := libpf.SymbolMap{} symSz := int(unsafe.Sizeof(elf.Sym64{})) for i := 0; i < len(syms); i += symSz { sym := (*elf.Sym64)(unsafe.Pointer(&syms[i])) - name, ok := getString(strs, int(sym.Name)) - if !ok { - continue + if name, ok := getString(strs, int(sym.Name)); ok { + visitor(libpf.Symbol{ + Name: libpf.SymbolName(name), + Address: libpf.SymbolValue(sym.Value), + Size: sym.Size, + }) } - symMap.Add(libpf.Symbol{ - Name: libpf.SymbolName(name), - Address: libpf.SymbolValue(sym.Value), - Size: sym.Size, - }) } - symMap.Finalize() + return nil +} - return &symMap, nil +// loadSymbolTable reads given symbol table +func (f *File) loadSymbolTable(name string) (*libpf.SymbolMap, error) { + symMap := &libpf.SymbolMap{} + if err := f.visitSymbolTable(name, func(s libpf.Symbol) { symMap.Add(s) }); err != nil { + return nil, err + } + symMap.Finalize() + return symMap, nil } // ReadSymbols reads the full dynamic symbol table from the ELF @@ -981,6 +1053,11 @@ func (f *File) ReadDynamicSymbols() (*libpf.SymbolMap, error) { return f.loadSymbolTable(".dynsym") } +// VisitDynamicSymbols iterates through the dynamic symbol table +func (f *File) VisitDynamicSymbols(visitor func(libpf.Symbol)) error { + return f.visitSymbolTable(".dynsym", visitor) +} + // DynString returns the strings listed for the given tag in the file's dynamic // program header. func (f *File) DynString(tag elf.DynTag) ([]string, error) { diff --git a/libpf/pfelf/internal/mmap/mmap.go b/libpf/pfelf/internal/mmap/mmap.go index 468ca3771..f99327ad6 100644 --- a/libpf/pfelf/internal/mmap/mmap.go +++ b/libpf/pfelf/internal/mmap/mmap.go @@ -12,11 +12,6 @@ import ( "unsafe" ) -var ( - // ErrInvalRequest indicates that the requested data exceeds the available mapped data. - ErrInvalRequest = errors.New("invalid request") -) - // ReaderAt reads a memory-mapped file. // // Like any io.ReaderAt, clients can execute parallel ReadAt calls, but it is @@ -67,8 +62,8 @@ func (r *ReaderAt) ReadAt(p []byte, off int64) (int, error) { // Subslice returns a subset of the mmaped backed data. func (r *ReaderAt) Subslice(offset, length int) ([]byte, error) { if offset+length > r.Len() { - return nil, fmt.Errorf("requested data %d at 0x%x exceeds %d: %w", - length, offset, length, ErrInvalRequest) + return nil, fmt.Errorf("requested data %x-%x exceeds %x: %w", + offset, offset+length, r.Len(), io.EOF) } return unsafe.Slice((*byte)(unsafe.Pointer(&r.data[offset])), length), nil } diff --git a/libpf/pfelf/internal/mmap/mmap_test.go b/libpf/pfelf/internal/mmap/mmap_test.go index b0c8932ab..dccc95dde 100644 --- a/libpf/pfelf/internal/mmap/mmap_test.go +++ b/libpf/pfelf/internal/mmap/mmap_test.go @@ -1,49 +1,42 @@ package mmap_test import ( - "errors" "fmt" + "io" "os" "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "go.opentelemetry.io/ebpf-profiler/libpf/pfelf/internal/mmap" ) func TestMmap_Subslice(t *testing.T) { f, err := os.CreateTemp(t.TempDir(), t.Name()+".testfile") - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) defer os.Remove(f.Name()) // Write some testData into the file. - testData := "data-for-the-test" + testData := []byte("data-for-the-test") fmt.Fprintf(f, "%s", testData) mf, err := mmap.Open(f.Name()) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) defer mf.Close() t.Run("invalid subslice", func(t *testing.T) { // Try to access data out of scope from the data // in the backing file. _, err := mf.Subslice(1024, 1024) - if !errors.Is(err, mmap.ErrInvalRequest) { - t.Fatalf("expected %v but got %v", mmap.ErrInvalRequest, err) - } + assert.ErrorIs(t, err, io.EOF) }) t.Run("valid subslice", func(t *testing.T) { // Try to access data out within the scope of // len(testData). res, err := mf.Subslice(9, 8) - if err != nil { - t.Fatalf("expected no error but got %v", err) - } - if string(res) != testData[9:] { - t.Fatalf("expected '%s' but got '%s'", testData[9:], string(res)) + if assert.NoError(t, err) { + assert.Equal(t, res, testData[9:]) } }) } diff --git a/nativeunwind/elfunwindinfo/elfehframe.go b/nativeunwind/elfunwindinfo/elfehframe.go index a61b54597..b9b37c0ce 100644 --- a/nativeunwind/elfunwindinfo/elfehframe.go +++ b/nativeunwind/elfunwindinfo/elfehframe.go @@ -883,8 +883,8 @@ func isSignalTrampoline(efCode *pfelf.File, fde *fdeInfo) bool { if fde.ipLen != uintptr(len(sigretCode)) { return false } - fdeCode := make([]byte, len(sigretCode)) - if _, err := efCode.ReadVirtualMemory(fdeCode, int64(fde.ipStart)); err != nil { + fdeCode, err := efCode.VirtualMemory(int64(fde.ipStart), len(sigretCode), 64) + if err != nil { return false } return bytes.Equal(fdeCode, sigretCode) diff --git a/nativeunwind/elfunwindinfo/elfgopclntab.go b/nativeunwind/elfunwindinfo/elfgopclntab.go index 40d01faf2..b4bdc73cc 100644 --- a/nativeunwind/elfunwindinfo/elfgopclntab.go +++ b/nativeunwind/elfunwindinfo/elfgopclntab.go @@ -320,8 +320,8 @@ func extractGoPclntab(ef *pfelf.File) (data []byte, err error) { if start >= end { return nil, fmt.Errorf("invalid .gopclntab symbols: %v-%v", start, end) } - data = make([]byte, end-start) - if _, err := ef.ReadVirtualMemory(data, int64(start)); err != nil { + data, err = ef.VirtualMemory(int64(start), int(end-start), maxBytesGoPclntab) + if err != nil { return nil, fmt.Errorf("failed to load .gopclntab via symbols: %v", err) } } diff --git a/processmanager/synthdeltas.go b/processmanager/synthdeltas.go index 0bd41fd2f..2d05d7476 100644 --- a/processmanager/synthdeltas.go +++ b/processmanager/synthdeltas.go @@ -37,13 +37,7 @@ func createVDSOSyntheticRecordNone(_ *pfelf.File) sdtypes.IntervalData { func createVDSOSyntheticRecordArm64(ef *pfelf.File) sdtypes.IntervalData { deltas := sdtypes.StackDeltaArray{} deltas = append(deltas, sdtypes.StackDelta{Address: 0, Info: sdtypes.UnwindInfoLR}) - - symbols, err := ef.ReadDynamicSymbols() - if err != nil { - return sdtypes.IntervalData{} - } - - symbols.VisitAll(func(sym libpf.Symbol) { + _ = ef.VisitDynamicSymbols(func(sym libpf.Symbol) { addr := uint64(sym.Address) if sym.Name == "__kernel_rt_sigreturn" { deltas = append( @@ -55,7 +49,7 @@ func createVDSOSyntheticRecordArm64(ef *pfelf.File) sdtypes.IntervalData { } // Determine if LR is on stack code := make([]byte, sym.Size) - if _, err = ef.ReadVirtualMemory(code, int64(sym.Address)); err != nil { + if _, err := ef.ReadVirtualMemory(code, int64(sym.Address)); err != nil { return } diff --git a/tpbase/libc.go b/tpbase/libc.go index b5e3bc5f0..fa057a401 100644 --- a/tpbase/libc.go +++ b/tpbase/libc.go @@ -93,27 +93,21 @@ func IsPotentialTSDDSO(filename string) bool { // ExtractTSDInfo extracts the introspection data for pthread thread specific data. func ExtractTSDInfo(ef *pfelf.File) (*TSDInfo, error) { - sym, err := ef.LookupSymbol("__pthread_getspecific") + _, code, err := ef.SymbolData("__pthread_getspecific", 2048) if err != nil { - sym, err = ef.LookupSymbol("pthread_getspecific") + _, code, err = ef.SymbolData("pthread_getspecific", 2048) if err != nil { - return nil, fmt.Errorf("no getspecific function: %s", err) + return nil, fmt.Errorf("unable to read 'pthread_getspecific': %s", err) } } - if sym.Size < 8 { - return nil, fmt.Errorf("getspecific function size is %d", sym.Size) - } - - code := make([]byte, sym.Size) - if _, err = ef.ReadVirtualMemory(code, int64(sym.Address)); err != nil { - return nil, fmt.Errorf("failed to read getspecific function: %s", err) + if len(code) < 8 { + return nil, fmt.Errorf("getspecific function size is %d", len(code)) } info, err := ExtractTSDInfoNative(code) if err != nil { return nil, fmt.Errorf("failed to extract getspecific data: %s", err) } - return &info, nil }