Created
January 24, 2022 20:10
-
-
Save florianl/5d9cc9dbb3822e03f6f65a073ffbedbb to your computer and use it in GitHub Desktop.
eBPF program on perf event in Go
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"errors" | |
"log" | |
"os" | |
"os/signal" | |
"strconv" | |
"syscall" | |
"github.com/cilium/ebpf" | |
"github.com/cilium/ebpf/asm" | |
"github.com/cilium/ebpf/perf" | |
"github.com/cilium/ebpf/rlimit" | |
"golang.org/x/sys/unix" | |
) | |
func main() { | |
if len(os.Args) != 2 { | |
log.Fatalf("Expected: ./%s <PID>\n", os.Args[0]) | |
} | |
pid, err := strconv.Atoi(os.Args[1]) | |
if err != nil { | |
log.Fatalf("Failed to parse pid from '%s': %v", os.Args[1], err) | |
} | |
// Subscribe to signals for terminating the program. | |
stopper := make(chan os.Signal, 1) | |
signal.Notify(stopper, os.Interrupt, syscall.SIGTERM) | |
// Allow the current process to lock memory for eBPF resources. | |
if err := rlimit.RemoveMemlock(); err != nil { | |
log.Fatalf("Failed to update rlimit: %v", err) | |
} | |
// Create a perf event array for the kernel to write perf records to. | |
// These records will be read by userspace below. | |
events, err := ebpf.NewMap(&ebpf.MapSpec{ | |
Type: ebpf.PerfEventArray, | |
Name: "my_perf_array", | |
}) | |
if err != nil { | |
log.Fatalf("Creating perf event array: %s", err) | |
} | |
defer events.Close() | |
// Open a perf reader from userspace into the perf event array | |
// created earlier. | |
rd, err := perf.NewReader(events, os.Getpagesize()) | |
if err != nil { | |
log.Fatalf("Creating event reader: %s", err) | |
} | |
defer rd.Close() | |
// Close the reader when the process receives a signal, which will exit | |
// the read loop. | |
go func() { | |
<-stopper | |
rd.Close() | |
}() | |
// Metadata for the eBPF program used in this example. | |
var progSpec = &ebpf.ProgramSpec{ | |
Name: "my_perf_event_prog", // non-unique name, will appear in `bpftool prog list` while attached | |
Type: ebpf.PerfEvent, // only PerfEvent programs can be attached to perf events | |
License: "GPL", // license must be GPL for calling kernel helpers like perf_event_output | |
} | |
// Minimal program that writes the static value '123' to the perf ring on | |
// each event. Note that this program refers to the file descriptor of | |
// the perf event array created above, which needs to be created prior to the | |
// program being verified by and inserted into the kernel. | |
progSpec.Instructions = asm.Instructions{ | |
// store the integer 123 at FP[-8] | |
asm.Mov.Imm(asm.R2, 123), | |
asm.StoreMem(asm.RFP, -8, asm.R2, asm.Word), | |
// load registers with arguments for call of FnPerfEventOutput | |
asm.LoadMapPtr(asm.R2, events.FD()), // file descriptor of the perf event array | |
asm.LoadImm(asm.R3, 0xffffffff, asm.DWord), | |
asm.Mov.Reg(asm.R4, asm.RFP), | |
asm.Add.Imm(asm.R4, -8), | |
asm.Mov.Imm(asm.R5, 4), | |
// call FnPerfEventOutput, an eBPF kernel helper | |
asm.FnPerfEventOutput.Call(), | |
// set exit code to 0 | |
asm.Mov.Imm(asm.R0, 0), | |
asm.Return(), | |
} | |
// Instantiate and insert the program into the kernel. | |
prog, err := ebpf.NewProgram(progSpec) | |
if err != nil { | |
log.Fatalf("Creating ebpf program: %s", err) | |
} | |
defer prog.Close() | |
log.Println("Waiting for events..") | |
ev, err := unix.PerfEventOpen( | |
&unix.PerfEventAttr{ | |
Type: unix.PERF_TYPE_SOFTWARE, | |
Config: unix.PERF_COUNT_SW_CPU_CLOCK, | |
Sample_type: unix.PERF_SAMPLE_RAW, | |
Sample: 1, | |
Wakeup: 1, | |
}, | |
pid, | |
0, | |
-1, | |
unix.PERF_FLAG_FD_CLOEXEC, | |
) | |
if err != nil { | |
log.Fatalf("Failed to create the perf event: %v", err) | |
} | |
defer func() { | |
if err := unix.Close(ev); err != nil { | |
log.Fatalf("Failed to close perf event: %v", err) | |
} | |
}() | |
// attach ebpf to perf event | |
if err := unix.IoctlSetInt(ev, unix.PERF_EVENT_IOC_SET_BPF, prog.FD()); err != nil { | |
log.Fatalf("Failed to attach eBPF program to perf event: %v", err) | |
} | |
// start perf event | |
if err := unix.IoctlSetInt(ev, unix.PERF_EVENT_IOC_ENABLE, 0); err != nil { | |
log.Fatalf("Failed to enable perf event: %v", err) | |
} | |
defer func() { | |
if err := unix.IoctlSetInt(ev, unix.PERF_EVENT_IOC_DISABLE, 0); err != nil { | |
log.Fatalf("Failed to disable perf event: %v", err) | |
} | |
}() | |
for { | |
record, err := rd.Read() | |
if err != nil { | |
if errors.Is(err, perf.ErrClosed) { | |
log.Println("Received signal, exiting..") | |
return | |
} | |
log.Printf("Reading from reader: %s", err) | |
continue | |
} | |
log.Println("Record:", record) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment