pkg/tools/btf/linker.go (297 lines of code) (raw):
// Licensed to Apache Software Foundation (ASF) under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Apache Software Foundation (ASF) licenses this file to you under
// the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package btf
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"io"
"os"
"runtime"
"sync"
"golang.org/x/arch/arm64/arm64asm"
"golang.org/x/arch/x86/x86asm"
"github.com/apache/skywalking-rover/pkg/tools/elf"
"github.com/apache/skywalking-rover/pkg/tools/process"
"github.com/cilium/ebpf"
"github.com/cilium/ebpf/link"
"github.com/cilium/ebpf/perf"
"github.com/hashicorp/go-multierror"
)
const defaultSymbolPrefix = "sys_"
type LinkFunc func(symbol string, prog *ebpf.Program, opts *link.KprobeOptions) (link.Link, error)
type RingBufferReader func(data interface{})
var syscallPrefix string
func init() {
stat, err := process.KernelFileProfilingStat()
if err != nil {
syscallPrefix = defaultSymbolPrefix
return
}
var possiblePrefixes = []string{
defaultSymbolPrefix,
"__x64_sys_",
"__x32_compat_sys_",
"__ia32_compat_sys_",
"__arm64_sys_",
"__s390x_sys_",
"__s390_sys_",
}
found := false
for _, p := range possiblePrefixes {
if stat.FindSymbolAddress(fmt.Sprintf("%sbpf", p)) != 0 {
found = true
syscallPrefix = p
break
}
}
if !found {
syscallPrefix = "sys_"
}
}
type Linker struct {
closers []io.Closer
errors error
closeOnce sync.Once
linkedUProbes map[string]bool
}
func NewLinker() *Linker {
return &Linker{
linkedUProbes: make(map[string]bool),
}
}
type UProbeExeFile struct {
addr string
found bool
linker *Linker
realFile *link.Executable
}
func (m *Linker) AddLink(linkF LinkFunc, symbolWithPrograms map[string]*ebpf.Program) {
if e := m.AddLinkOrError(linkF, symbolWithPrograms); e != nil {
m.errors = multierror.Append(m.errors, e)
}
}
func (m *Linker) AddLinkOrError(linkF LinkFunc, symbolWithPrograms map[string]*ebpf.Program) error {
var lk link.Link
var err error
var realSym string
for symbol, p := range symbolWithPrograms {
lk, err = linkF(symbol, p, nil)
if err == nil {
realSym = symbol
break
}
}
if err != nil {
symbolNames := make([]string, 0)
for s := range symbolWithPrograms {
symbolNames = append(symbolNames, s)
}
return multierror.Append(m.errors, fmt.Errorf("open %s error: %v", symbolNames, err))
}
log.Debugf("attach to the kprobe: %s", realSym)
m.closers = append(m.closers, lk)
return nil
}
func (m *Linker) AddSysCall(call string, enter, exit *ebpf.Program) {
m.AddSysCallWithKProbe(call, link.Kprobe, enter)
m.AddSysCallWithKProbe(call, link.Kretprobe, exit)
}
func (m *Linker) AddSysCallWithKProbe(call string, linkK LinkFunc, p *ebpf.Program) {
kprobe, err := linkK(syscallPrefix+call, p, nil)
if err != nil {
m.errors = multierror.Append(m.errors, fmt.Errorf("could not attach syscall with %s: %v", "sys_"+call, err))
} else {
log.Debugf("attach to the syscall: %s", syscallPrefix+call)
m.closers = append(m.closers, kprobe)
}
}
func (m *Linker) AddTracePoint(sys, name string, p *ebpf.Program) {
l, e := link.Tracepoint(sys, name, p, nil)
if e != nil {
m.errors = multierror.Append(m.errors, fmt.Errorf("open %s error: %v", name, e))
} else {
m.closers = append(m.closers, l)
}
}
func (m *Linker) ReadEventAsync(emap *ebpf.Map, reader RingBufferReader, dataSupplier func() interface{}) {
m.ReadEventAsyncWithBufferSize(emap, reader, os.Getpagesize(), dataSupplier)
}
func (m *Linker) ReadEventAsyncWithBufferSize(emap *ebpf.Map, reader RingBufferReader, perCPUBuffer int, dataSupplier func() interface{}) {
rd, err := perf.NewReader(emap, perCPUBuffer)
if err != nil {
m.errors = multierror.Append(m.errors, fmt.Errorf("open ring buffer error: %v", err))
return
}
m.closers = append(m.closers, rd)
go func() {
for {
record, err := rd.Read()
if err != nil {
if errors.Is(err, perf.ErrClosed) {
return
}
log.Warnf("read from %s ringbuffer error: %v", emap.String(), err)
continue
}
if record.LostSamples != 0 {
log.Warnf("perf event queue(%s) full, dropped %d samples", emap.String(), record.LostSamples)
continue
}
data := dataSupplier()
if err := binary.Read(bytes.NewBuffer(record.RawSample), binary.LittleEndian, data); err != nil {
log.Warnf("parsing data from %s, raw size: %d, ringbuffer error: %v", emap.String(), len(record.RawSample), err)
continue
}
reader(data)
}
}()
}
func (m *Linker) OpenUProbeExeFile(path string) *UProbeExeFile {
executable, err := link.OpenExecutable(path)
if err != nil {
m.errors = multierror.Append(m.errors, fmt.Errorf("cannot found the execute file: %s, error: %v", path, err))
return &UProbeExeFile{
found: false,
}
}
return &UProbeExeFile{
found: true,
addr: path,
linker: m,
realFile: executable,
}
}
func (u *UProbeExeFile) AddLink(symbol string, enter, exit *ebpf.Program) {
u.AddLinkWithType(symbol, true, enter)
u.AddLinkWithType(symbol, false, exit)
}
func (u *UProbeExeFile) AddLinkWithSymbols(symbol []string, enter, exit *ebpf.Program) {
for _, s := range symbol {
u.AddLinkWithType(s, true, enter)
u.AddLinkWithType(s, false, exit)
}
}
func (u *UProbeExeFile) AddGoLink(symbol string, enter, exit *ebpf.Program, elfFile *elf.File) {
u.AddGoLinkWithType(symbol, true, enter, elfFile)
u.AddGoLinkWithType(symbol, false, exit, elfFile)
}
func (u *UProbeExeFile) AddLinkWithType(symbol string, enter bool, p *ebpf.Program) {
if !u.found {
return
}
lk, err := u.addLinkWithType0(symbol, enter, p, 0)
if err != nil {
u.linker.errors = multierror.Append(u.linker.errors, fmt.Errorf("file: %s, symbol: %s, type: %s, error: %v",
u.addr, symbol, u.parseEnterOrExitString(enter), err))
} else if lk != nil {
log.Debugf("attach to the uprobe, file: %s, symbol: %s, type: %s", u.addr, symbol, u.parseEnterOrExitString(enter))
u.linker.closers = append(u.linker.closers, lk)
}
}
func (u *UProbeExeFile) addLinkWithType0(symbol string, enter bool, p *ebpf.Program, customizeAddress uint64) (link.Link, error) {
// check already linked
uprobeIdentity := fmt.Sprintf("%s_%s_%t_%d", u.addr, symbol, enter, customizeAddress)
if u.linker.linkedUProbes[uprobeIdentity] {
log.Debugf("the uprobe already attached, so ignored. file: %s, symbol: %s, type: %s", u.addr, symbol,
u.parseEnterOrExitString(enter))
return nil, nil
}
u.linker.linkedUProbes[uprobeIdentity] = true
var fun func(symbol string, prog *ebpf.Program, opts *link.UprobeOptions) (link.Link, error)
if enter {
fun = u.realFile.Uprobe
} else {
fun = u.realFile.Uretprobe
}
var opts *link.UprobeOptions
if customizeAddress > 0 {
opts = &link.UprobeOptions{
Offset: customizeAddress,
}
}
return fun(symbol, p, opts)
}
func (u *UProbeExeFile) AddGoLinkWithType(symbol string, enter bool, p *ebpf.Program, elfFile *elf.File) {
// if is entered type of probe, then same with the other programs
if enter {
u.AddLinkWithType(symbol, enter, p)
return
}
links, err := u.addGoExitLink0(symbol, p, elfFile)
if err != nil {
u.linker.errors = multierror.Append(u.linker.errors, fmt.Errorf("file: %s, symbol: %s, type: %s, error: %v",
u.addr, symbol, u.parseEnterOrExitString(enter), err))
} else {
log.Debugf("attach to the go uprobe, file: %s, symbol: %s, type: %s", u.addr, symbol, u.parseEnterOrExitString(enter))
for _, l := range links {
u.linker.closers = append(u.linker.closers, l)
}
}
}
func (u *UProbeExeFile) addGoExitLink0(symbol string, p *ebpf.Program, elfFile *elf.File) ([]link.Link, error) {
// find the symbol
targetSymbol := elfFile.FindSymbol(symbol)
if targetSymbol == nil {
return nil, fmt.Errorf("could not found the symbol")
}
// find the symbol real data buffer
buffer, err := elfFile.ReadSymbolData(".text", targetSymbol.Location, targetSymbol.Size)
if err != nil {
return nil, fmt.Errorf("reading symbol data error: %v", err)
}
// based on the base addresses and symbol data buffer
// calculate all RET addresses
// https://github.com/iovisor/bcc/issues/1320#issuecomment-407927542
var addresses []uint64
for i := 0; i < int(targetSymbol.Size); {
var instLen int
if runtime.GOARCH == "arm64" {
inst, err := arm64asm.Decode(buffer[i:])
if err != nil {
i += 4
continue
}
if inst.Op == arm64asm.RET {
addresses = append(addresses, uint64(i))
}
instLen = 4
} else {
inst, err := x86asm.Decode(buffer[i:], 64)
if err != nil {
return nil, fmt.Errorf("error decode the function data: %v", err)
}
if inst.Op == x86asm.RET {
addresses = append(addresses, uint64(i))
}
instLen = inst.Len
}
i += instLen
}
if len(addresses) == 0 {
return nil, fmt.Errorf("could not found any return addresses")
}
log.Debugf("found reuturn addresses of the symbol, symbol: %s, size: %d", symbol, len(addresses))
var result []link.Link
for _, address := range addresses {
l, err := u.addLinkWithType0(symbol, true, p, address)
if err != nil {
return nil, err
}
result = append(result, l)
log.Debugf("attach to the return probe of the go program, symbol: %s, addresses: %d", symbol, address)
}
return result, nil
}
func (u *UProbeExeFile) parseEnterOrExitString(enter bool) string {
if enter {
return "enter"
}
return "exit"
}
func (m *Linker) HasError() error {
return m.errors
}
func (m *Linker) Close() error {
var err error
m.closeOnce.Do(func() {
for _, l := range m.closers {
if e := l.Close(); e != nil {
err = multierror.Append(err, e)
}
}
})
return err
}