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
71 changes: 70 additions & 1 deletion asm/amd/insn.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@
// SPDX-License-Identifier: Apache-2.0

package amd // import "go.opentelemetry.io/ebpf-profiler/asm/amd"
import "bytes"
import (
"bytes"

"go.opentelemetry.io/ebpf-profiler/libpf"
"golang.org/x/arch/x86/x86asm"
)

// https://www.felixcloutier.com/x86/endbr64
var opcodeEndBr64 = []byte{0xf3, 0x0f, 0x1e, 0xfa}
Expand All @@ -18,3 +23,67 @@ func DecodeSkippable(code []byte) (ok bool, size int) {
return false, 0
}
}

// FindExternalJump decodes every instruction in the sym function and searches for
// a relative jump outside itself - to an address not covered by the sym.
// FindExternalJump returns the destination address of the relative jump outside the function or 0.
func FindExternalJump(code []byte, f *libpf.Symbol) (libpf.Address, error) {
var (
err error
inst x86asm.Inst
rip = int64(f.Address)
)
for len(code) > 0 {
if ok, l := DecodeSkippable(code); ok {
inst = x86asm.Inst{Op: x86asm.NOP, Len: l}
} else {
inst, err = x86asm.Decode(code, 64)
if err != nil {
return 0, err
}
}
rip += int64(inst.Len)
code = code[inst.Len:]
if !isJump(inst.Op) {
continue
}
if rel, ok := inst.Args[0].(x86asm.Rel); !ok {
continue
} else {
dst := rip + int64(rel)
if dst >= int64(f.Address) && dst < int64(f.Address)+int64(f.Size) {
continue
}
return libpf.Address(dst), nil
}
}
return 0, nil
}

func isJump(op x86asm.Op) bool {
switch op {
case x86asm.JA,
x86asm.JAE,
x86asm.JB,
x86asm.JBE,
x86asm.JCXZ,
x86asm.JE,
x86asm.JECXZ,
x86asm.JG,
x86asm.JGE,
x86asm.JL,
x86asm.JLE,
x86asm.JMP,
x86asm.JNE,
x86asm.JNO,
x86asm.JNP,
x86asm.JNS,
x86asm.JO,
x86asm.JP,
x86asm.JRCXZ,
x86asm.JS:
return true
default:
return false
}
}
4 changes: 2 additions & 2 deletions interpreter/php/opcache.go
Original file line number Diff line number Diff line change
Expand Up @@ -313,12 +313,12 @@ func getOpcacheJITInfo(ef *pfelf.File) (dasmBuf, dasmSize libpf.Address, err err
// and ARM64.

// We should only need 64 bytes, since this should be early in the instruction sequence.
addr, code, err := ef.SymbolData("zend_jit_unprotect", 64)
sym, 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, addr)
dasmBufPtr, dasmSizePtr, err := retrieveJITBufferPtrWrapper(code, sym.Address)
if err != nil {
return 0, 0, fmt.Errorf("failed to extract DASM pointers: %w", err)
}
Expand Down
4 changes: 2 additions & 2 deletions interpreter/php/php.go
Original file line number Diff line number Diff line change
Expand Up @@ -205,13 +205,13 @@ func recoverExecuteExJumpLabelAddress(ef *pfelf.File) (libpf.SymbolValue, error)

// The address we care about varies from being 47 bytes in to about 107 bytes in,
// so we'll cap at 128 bytes. This might need to be adjusted up in future.
addr, code, err := ef.SymbolData("execute_ex", 128)
sym, code, err := ef.SymbolData("execute_ex", 128)
if err != nil {
return libpf.SymbolValueInvalid,
fmt.Errorf("unable to read 'execute_ex': %w", err)
}

returnAddress, err := retrieveExecuteExJumpLabelAddressWrapper(code, addr)
returnAddress, err := retrieveExecuteExJumpLabelAddressWrapper(code, sym.Address)
if err != nil {
return libpf.SymbolValueInvalid,
fmt.Errorf("reading the return address from execute_ex failed (%w)",
Expand Down
67 changes: 55 additions & 12 deletions interpreter/python/python.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ import (
"unsafe"

log "github.com/sirupsen/logrus"
"go.opentelemetry.io/ebpf-profiler/asm/amd"
"go.opentelemetry.io/ebpf-profiler/nativeunwind/elfunwindinfo"

"github.com/elastic/go-freelru"

Expand Down Expand Up @@ -659,17 +661,17 @@ func (d *pythonData) readIntrospectionData(ef *pfelf.File, symbol libpf.SymbolNa
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)
sym, code, err := ef.SymbolData(symbolName, 64)
if err != nil {
return libpf.SymbolValueInvalid, fmt.Errorf("unable to read '%s': %v",
symbolName, err)
}
value, err := decodeStubArgumentWrapper(code, addr, memoryBase)
value, err := decodeStubArgumentWrapper(code, sym.Address, 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, addr, hex.Dump(code), value, err)
symbolName, sym.Address, hex.Dump(code), value, err)
}
// If base symbol (_PyRuntime) is not provided, accept any found value.
if memoryBase == 0 && value != 0 {
Expand All @@ -680,7 +682,7 @@ func decodeStub(ef *pfelf.File, memoryBase libpf.SymbolValue,
return value, nil
}
return libpf.SymbolValueInvalid, fmt.Errorf("decode stub %s 0x%x %s failed (0x%x)",
symbolName, addr, hex.Dump(code), value)
symbolName, sym.Address, hex.Dump(code), value)
}

func Loader(ebpf interpreter.EbpfHandler, info *interpreter.LoaderInfo) (interpreter.Data, error) {
Expand Down Expand Up @@ -752,7 +754,7 @@ func Loader(ebpf interpreter.EbpfHandler, info *interpreter.LoaderInfo) (interpr
autoTLSKey += 4
}

interpRanges, err := findInterpreterRanges(info)
interpRanges, err := findInterpreterRanges(info, ef)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -830,7 +832,8 @@ func Loader(ebpf interpreter.EbpfHandler, info *interpreter.LoaderInfo) (interpr
return pd, nil
}

func findInterpreterRanges(info *interpreter.LoaderInfo) (interpRanges []util.Range, err error) {
func findInterpreterRanges(info *interpreter.LoaderInfo, ef *pfelf.File,
) (interpRanges []util.Range, err error) {
// The Python main interpreter loop history in CPython git is:
//
//nolint:lll
Expand All @@ -839,14 +842,54 @@ func findInterpreterRanges(info *interpreter.LoaderInfo) (interpRanges []util.Ra
// 0b72b23fb0c v3.9 2020-03-12 _PyEval_EvalFrameDefault(PyThreadState*,PyFrameObject*,int)
// 3cebf938727 v3.6 2016-09-05 _PyEval_EvalFrameDefault(PyFrameObject*,int)
// 49fd7fa4431 v3.0 2006-04-21 PyEval_EvalFrameEx(PyFrameObject*,int)
if interpRanges, err = info.GetSymbolAsRanges("_PyEval_EvalFrameDefault"); err != nil {
interpRanges, _ = info.GetSymbolAsRanges("PyEval_EvalFrameEx")
var interp *libpf.Symbol
var code []byte
const maxCodeSize = 128 * 1024 // observed ~65k in the wild
if interp, code, err = ef.SymbolData("_PyEval_EvalFrameDefault", maxCodeSize); err != nil {
interp, code, err = ef.SymbolData("PyEval_EvalFrameEx", maxCodeSize)
}
if len(interpRanges) == 0 {
if err != nil {
return nil, errors.New("no _PyEval_EvalFrameDefault/PyEval_EvalFrameEx symbol found")
}
// TODO(korniltsev): find cold ranges
// see tools/coredump/testdata/amd64/python312-alpine320-nobuildid.json
// https://github.com/open-telemetry/opentelemetry-ebpf-profiler/issues/416
interpRanges = make([]util.Range, 0, 2)
interpRanges = append(interpRanges, util.Range{
Start: uint64(interp.Address),
End: uint64(interp.Address) + interp.Size,
})
coldRange, err := findColdRange(ef, code, interp)
if err != nil {
log.WithError(err).Warnf("failed to recover python ranges %s",
info.FileName())
}
if coldRange != (util.Range{}) {
interpRanges = append(interpRanges, coldRange)
}
return interpRanges, nil
}

// findColdRange finds a relative jump from the _PyEval_EvalFrameDefault outside itself
// (to _PyEval_EvalFrameDefault.cold symbol) and then recovers the range of the .cold
// symbol using an instance of elfunwindinfo.EhFrameTable.
// findColdRange returns the util.Range of the `.cold` symbol or an empty util.Range
// https://github.com/open-telemetry/opentelemetry-ebpf-profiler/issues/416
func findColdRange(ef *pfelf.File, code []byte, interp *libpf.Symbol) (util.Range, error) {
if ef.Machine != elf.EM_X86_64 {
return util.Range{}, nil
}
dst, err := amd.FindExternalJump(code, interp)
if err != nil || dst == 0 {
return util.Range{}, err
}
t, err := elfunwindinfo.NewEhFrameTable(ef)
if err != nil {
return util.Range{}, err
}
fde, err := t.LookupFDE(dst)
if err != nil {
return util.Range{}, err
}
return util.Range{
Start: uint64(fde.PCBegin),
End: uint64(fde.PCBegin + fde.PCRange),
}, nil
}
6 changes: 3 additions & 3 deletions libpf/pfelf/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -488,10 +488,10 @@ func (f *File) VirtualMemory(addr int64, sz, maxSize int) ([]byte, error) {
// 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) {
func (f *File) SymbolData(name libpf.SymbolName, maxCopy int) (*libpf.Symbol, []byte, error) {
sym, err := f.LookupSymbol(name)
if err != nil {
return 0, nil, err
return nil, nil, err
}
symSize := int(sym.Size)
if symSize > maxCopy {
Expand All @@ -501,7 +501,7 @@ func (f *File) SymbolData(name libpf.SymbolName, maxCopy int) (libpf.SymbolValue
}
}
data, err := f.VirtualMemory(int64(sym.Address), symSize, maxCopy)
return sym.Address, data, err
return sym, data, err
}

// ReadVirtualMemory reads bytes from given virtual address
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
{
"skip": "https://github.com/open-telemetry/opentelemetry-ebpf-profiler/issues/416",
"coredump-ref": "3eb6bae4e0089983f436d6bbd4a0b7ee0d72738eac29f15495494f53bc82263d",
"threads": [
{
Expand Down
1 change: 0 additions & 1 deletion tools/coredump/testdata/amd64/python312-alpine320.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
{
"skip": "https://github.com/open-telemetry/opentelemetry-ebpf-profiler/issues/416",
"coredump-ref": "4652115623df00987a1a480431a360edbd67b0795e9529543d40be190e37c74d",
"threads": [
{
Expand Down