Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion interpreter/apmint/apmint.go
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ func nextString(rm remotememory.RemoteMemory, addr *libpf.Address, maxLen int) (
}

*addr += libpf.Address(length)
return string(raw), nil
return unsafe.String(unsafe.SliceData(raw), len(raw)), nil
Comment thread
christos68k marked this conversation as resolved.
Comment thread
florianl marked this conversation as resolved.
}

// readProcStorage reads the APM process storage from memory.
Expand Down
21 changes: 11 additions & 10 deletions interpreter/dotnet/pe.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"slices"
"sync/atomic"
"time"
"unsafe"

"github.com/elastic/go-freelru"

Expand Down Expand Up @@ -266,7 +267,7 @@ type peInfo struct {
// strings contains the preloaded strings from dotnet string heap.
// If this consumes too much memory, this could be converted to LRU and on-demand
// populated by reading the strings from attached process memory.
strings map[uint32]string
strings map[uint32]libpf.String
}

// peParser contains the needed data when reading and parsing the dotnet data from a PE file.
Expand Down Expand Up @@ -568,7 +569,7 @@ func (pp *peParser) parseCLI() error {
break
}
}
switch string(name) {
switch unsafe.String(unsafe.SliceData(name), len(name)) {
case "#Strings":
// ECMA-335 II.24.2.3 #Strings heap
pp.dotnetStrings = io.NewSectionReader(r, int64(hdr.Offset), int64(hdr.Size))
Expand All @@ -595,10 +596,10 @@ func (pp *peParser) parseCLI() error {
return nil
}

func (pp *peParser) readDotnetString(offs uint32) string {
func (pp *peParser) readDotnetString(offs uint32) libpf.String {
// Read a string from the ECMA-335 II.24.2.3 #Strings heap
if offs == 0 {
return ""
return libpf.NullString
}

// Zero terminated string. Assume maximum length of 1024 bytes.
Expand All @@ -609,17 +610,17 @@ func (pp *peParser) readDotnetString(offs uint32) string {
chunk := str[i : i+chunkSize]
n, err := pp.dotnetStrings.ReadAt(chunk, int64(offs)+int64(i))
if n == 0 && err != nil {
return ""
return libpf.NullString
}

zeroIdx := bytes.IndexByte(chunk[:n], 0)
if zeroIdx >= 0 {
return string(str[:i+zeroIdx])
return libpf.Intern(unsafe.String(unsafe.SliceData(str[:]), i+zeroIdx))
Comment thread
christos68k marked this conversation as resolved.
}
}

// Likely broken string.
return ""
return libpf.NullString
}

func (pp *peParser) readDotnetGUID(offs uint32) string {
Expand Down Expand Up @@ -697,7 +698,7 @@ func (pp *peParser) parseModuleTable() {
guidIdx := pp.readDotnetIndex(indexGUID)
pp.skipDotnetBytes(2 * pp.indexSizes[indexGUID])

pp.info.simpleName = libpf.Intern(pp.readDotnetString(nameIdx))
pp.info.simpleName = pp.readDotnetString(nameIdx)
pp.info.guid = pp.readDotnetGUID(guidIdx)
}
}
Expand Down Expand Up @@ -847,7 +848,7 @@ func (pp *peParser) parseTables() error {
return fmt.Errorf("number of Modules (%d) is unexpected", pp.tableRows[0])
}

pp.info.strings = map[uint32]string{}
pp.info.strings = map[uint32]libpf.String{}

// Precalculate the column sizes we need to know
pp.indexSizes[indexString] = getHeapSize(tablesHeader.HeapSizes&0x1 != 0)
Expand Down Expand Up @@ -1153,7 +1154,7 @@ func (pi *peInfo) resolveMethodName(methodIdx uint32) libpf.String {
}

typeSpec := &pi.typeSpecs[idx]
typeName := pi.strings[typeSpec.typeNameIdx]
typeName := pi.strings[typeSpec.typeNameIdx].String()
for typeSpec.enclosingClass != 0 {
enclosingSpec := &pi.typeSpecs[typeSpec.enclosingClass-1]
typeName = fmt.Sprintf("%s/%s", pi.strings[enclosingSpec.typeNameIdx], typeName)
Expand Down
4 changes: 2 additions & 2 deletions interpreter/hotspot/instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -204,9 +204,9 @@ func (d *hotspotInstance) getSymbol(addr libpf.Address) libpf.String {
return libpf.NullString
}
}
s := string(tmp)
s := unsafe.String(unsafe.SliceData(tmp), len(tmp))
if !util.IsValidString(s) {
log.Debugf("Extracted Hotspot symbol is invalid at 0x%x '%v'", addr, []byte(s))
log.Debugf("Extracted Hotspot symbol is invalid at 0x%x '%v'", addr, tmp)
return libpf.NullString
}
value := libpf.Intern(s)
Expand Down
2 changes: 1 addition & 1 deletion interpreter/nodev8/v8.go
Original file line number Diff line number Diff line change
Expand Up @@ -842,7 +842,7 @@ func (i *v8Instance) extractString(ptr libpf.Address, tag uint16, cb func(string
if err != nil {
return err
}
if err = cb(string(buf)); err != nil {
if err = cb(unsafe.String(unsafe.SliceData(buf), len(buf))); err != nil {
return err
}
}
Expand Down
11 changes: 1 addition & 10 deletions interpreter/perl/data.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ package perl // import "go.opentelemetry.io/ebpf-profiler/interpreter/perl"

import (
"fmt"
"sync"

"github.com/elastic/go-freelru"
log "github.com/sirupsen/logrus"
Expand Down Expand Up @@ -122,7 +121,7 @@ func (d *perlData) String() string {

func (d *perlData) Attach(_ interpreter.EbpfHandler, _ libpf.PID, bias libpf.Address,
rm remotememory.RemoteMemory) (interpreter.Instance, error) {
addrToHEK, err := freelru.New[libpf.Address, string](interpreter.LruFunctionCacheSize,
addrToHEK, err := freelru.New[libpf.Address, libpf.String](interpreter.LruFunctionCacheSize,
libpf.Address.Hash32)
if err != nil {
return nil, err
Expand All @@ -147,14 +146,6 @@ func (d *perlData) Attach(_ interpreter.EbpfHandler, _ libpf.PID, bias libpf.Add
addrToHEK: addrToHEK,
addrToCOP: addrToCOP,
addrToGV: addrToGV,
memPool: sync.Pool{
New: func() any {
// To avoid resizing of the returned byte slize we size new
// allocations to hekLenLimit.
buf := make([]byte, hekLenLimit)
return &buf
},
},
}, nil
}

Expand Down
81 changes: 31 additions & 50 deletions interpreter/perl/instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ package perl // import "go.opentelemetry.io/ebpf-profiler/interpreter/perl"
import (
"errors"
"fmt"
"sync"
"sync/atomic"
"unsafe"

Expand Down Expand Up @@ -38,17 +37,14 @@ type perlInstance struct {
bias libpf.Address

// addrToHEK maps a PERL Hash Element Key (string with hash) to a Go string
addrToHEK *freelru.LRU[libpf.Address, string]
addrToHEK *freelru.LRU[libpf.Address, libpf.String]

// addrToCOP maps a PERL Control OP (COP) structure to a perlCOP which caches data from it
addrToCOP *freelru.LRU[copKey, *perlCOP]

// addrToGV maps a PERL Glob Value (GV) aka "symbol" to its name string
addrToGV *freelru.LRU[libpf.Address, libpf.String]

// memPool provides pointers to byte arrays for efficient memory reuse.
memPool sync.Pool

// hekLen is the largest number we did see in the last reporting interval for hekLen
// in getHEK.
hekLen atomic.Uint32
Expand Down Expand Up @@ -202,78 +198,63 @@ func (i *perlInstance) GetAndResetMetrics() ([]metrics.Metric, error) {
}, nil
}

func (i *perlInstance) getHEK(addr libpf.Address) (string, error) {
func (i *perlInstance) getHEK(addr libpf.Address) (libpf.String, error) {
if addr == 0 {
return "", errors.New("null hek pointer")
return libpf.NullString, errors.New("null hek pointer")
}
if value, ok := i.addrToHEK.Get(addr); ok {
return value, nil
}
vms := &i.d.vmStructs

var buf [hekLenLimit]byte

// Read the Hash Element Key (HEK) length and readahead bytes in
// attempt to avoid second system call to read the target string.
// 128 is chosen arbitrarily as "hopefully good enough"; this value can
// be increased if it turns out to be necessary.
var buf [128]byte
if err := i.rm.Read(addr, buf[:]); err != nil {
return "", err
const hekInitialRead = 128
if err := i.rm.Read(addr, buf[:hekInitialRead]); err != nil {
return libpf.NullString, err
}
hekLen := npsr.Uint32(buf[:], vms.hek.hek_len)

// For our better understanding and future improvement we track the maximum value we get for
// hekLen and report it.
util.AtomicUpdateMaxUint32(&i.hekLen, hekLen)

if hekLen > hekLenLimit {
return "", fmt.Errorf("hek too large (%d)", hekLen)
}

syncPoolData := i.memPool.Get().(*[]byte)
if syncPoolData == nil {
return "", errors.New("failed to get memory from sync pool")
}

defer func() {
// Reset memory and return it for reuse.
for j := range hekLen {
(*syncPoolData)[j] = 0x0
if hekSize := uint32(vms.hek.hek_key) + hekLen; hekSize > hekInitialRead {
if hekSize > hekLenLimit {
return libpf.NullString, fmt.Errorf("hek too large (%d)", hekLen)
}
i.memPool.Put(syncPoolData)
}()

tmp := (*syncPoolData)[:hekLen]
// Always allocate the string separately so it does not hold the backing
// buffer that might be larger than needed
numCopied := copy(tmp, buf[vms.hek.hek_key:])
if hekLen > uint32(numCopied) {
err := i.rm.Read(addr+libpf.Address(vms.hek.hek_key+uint(numCopied)), tmp[numCopied:])
if err != nil {
return "", err
if err := i.rm.Read(addr+hekInitialRead, buf[hekInitialRead:]); err != nil {
return libpf.NullString, err
}
}
s := string(tmp)

s := unsafe.String(unsafe.SliceData(buf[vms.hek.hek_key:]), hekLen)
if !util.IsValidString(s) {
log.Debugf("Extracted invalid hek string at 0x%x '%v'", addr, []byte(s))
return "", fmt.Errorf("extracted invalid hek string at 0x%x", addr)
return libpf.NullString, fmt.Errorf("extracted invalid hek string at 0x%x", addr)
}
i.addrToHEK.Add(addr, s)
value := libpf.Intern(s)
i.addrToHEK.Add(addr, value)

return s, nil
return value, nil
}

func (i *perlInstance) getHVName(hvAddr libpf.Address) (string, error) {
func (i *perlInstance) getHVName(hvAddr libpf.Address) (libpf.String, error) {
if hvAddr == 0 {
return "", nil
return libpf.NullString, nil
}
vms := &i.d.vmStructs
hv := make([]byte, vms.sv.sizeof)
if err := i.rm.Read(hvAddr, hv); err != nil {
return "", err
return libpf.NullString, err
}
hvFlags := npsr.Uint32(hv, vms.sv.sv_flags)
if hvFlags&SVt_MASK != SVt_PVHV {
return "", errors.New("not a HV")
return libpf.NullString, errors.New("not a HV")
}

xpvhvAddr := npsr.Ptr(hv, vms.sv.sv_any)
Expand All @@ -284,14 +265,14 @@ func (i *perlInstance) getHVName(hvAddr libpf.Address) (string, error) {
end := i.rm.Uint64(xpvhvAddr + libpf.Address(vms.xpvhv.xhv_max))
xpvhvAuxAddr := arrayAddr + libpf.Address((end+1)*uint64(vms.xpvhv_aux.pointer_size))
if err := i.rm.Read(xpvhvAuxAddr, xpvhvAux); err != nil {
return "", err
return libpf.NullString, err
}
} else {
// In Perl 5.36.x.XPVHV got replaced with xpvhv_with_aux to hold this information.
// https://github.com/Perl/perl5/commit/94ee6ed79dbca73d0345b745534477e4017fb990
if err := i.rm.Read(xpvhvAddr+libpf.Address(vms.xpvhv_with_aux.xpvhv_aux),
xpvhvAux); err != nil {
return "", err
return libpf.NullString, err
}
}

Expand Down Expand Up @@ -332,27 +313,27 @@ func (i *perlInstance) getGV(gvAddr libpf.Address, nameOnly bool) (libpf.String,
// Follow the GV's "body" pointer to get the function name
xpvgvAddr := i.rm.Ptr(gvAddr + libpf.Address(vms.sv.sv_any))
hekAddr := i.rm.Ptr(xpvgvAddr + libpf.Address(vms.xpvgv.xivu_namehek))
gvName, err := i.getHEK(hekAddr)
value, err := i.getHEK(hekAddr)
if err != nil {
return libpf.NullString, err
}

if !nameOnly && gvName != "" {
if !nameOnly && value != libpf.NullString {
stashAddr := i.rm.Ptr(xpvgvAddr + libpf.Address(vms.xpvgv.xgv_stash))
packageName, err := i.getHVName(stashAddr)
if err != nil {
return libpf.NullString, err
}

// Build the qualified name
if packageName == "" {
if packageName == libpf.NullString {
// per Perl_gv_fullname4
packageName = "__ANON__"
value = libpf.Intern("__ANON__::" + value.String())
} else {
value = libpf.Intern(packageName.String() + "::" + value.String())
}
gvName = packageName + "::" + gvName
}

value := libpf.Intern(gvName)
i.addrToGV.Add(gvAddr, value)
return value, nil
}
Expand Down
2 changes: 1 addition & 1 deletion interpreter/php/php.go
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ func determinePHPVersion(ef *pfelf.File) (uint, error) {
if zeroIdx < 0 {
continue
}
version, err := versionExtract(string(rodata[idx : idx+zeroIdx]))
version, err := versionExtract(unsafe.String(unsafe.SliceData(rodata[idx:]), zeroIdx))
if err != nil {
continue
}
Expand Down