From 1698fe22a63a3520a3910eb02de52054d1d8cca4 Mon Sep 17 00:00:00 2001 From: Alessio Greggi Date: Fri, 19 Jan 2024 12:23:46 +0100 Subject: [PATCH] refactor: move code to cilium/ebpf library Signed-off-by: Alessio Greggi --- ebpf/ebpf.c | 44 +++++++++---- ebpf_bpfeb.go | 125 +++++++++++++++++++++++++++++++++++++ ebpf_bpfeb.o | Bin 0 -> 3576 bytes ebpf_bpfel.go | 125 +++++++++++++++++++++++++++++++++++++ ebpf_bpfel.o | Bin 0 -> 3576 bytes go.mod | 13 +++- go.sum | 18 ++++++ main.go | 167 ++++++++++++++++++++++++++++++-------------------- utils.go | 111 +++++++++++++++++++++++++++++++++ 9 files changed, 520 insertions(+), 83 deletions(-) create mode 100644 ebpf_bpfeb.go create mode 100644 ebpf_bpfeb.o create mode 100644 ebpf_bpfel.go create mode 100644 ebpf_bpfel.o diff --git a/ebpf/ebpf.c b/ebpf/ebpf.c index e0e6271..7a27110 100644 --- a/ebpf/ebpf.c +++ b/ebpf/ebpf.c @@ -1,15 +1,31 @@ -#include +//go:build ignore + +#include +#include #include -#include +#include -BPF_PERF_OUTPUT(events); +//BPF_PERF_OUTPUT(events); +struct { + __uint(type, BPF_MAP_TYPE_PERF_EVENT_ARRAY); +} events SEC(".maps"); // data_t used to store the data received from the event struct syscall_data { // the syscall number - u32 syscall_id; + __u32 syscall_id; // tracing status (1 start, 2 stop) - u32 tracingStatus; + __u32 tracingStatus; +}; + +struct sys_enter_info { + unsigned short common_type; + unsigned char common_flags; + unsigned char common_preempt_count; + int common_pid; + + long id; + unsigned long args[6]; }; // imlement strncmp function @@ -30,24 +46,27 @@ __bpf_strncmp(const void *x, const void *y, __u64 len) { // enter_function submit the value 1 to advice // the frontend app that the function started its // execution -inline int enter_function(struct pt_regs *ctx) { +SEC("uprobe/enter_function") +inline int uprobe_enter_function(struct pt_regs *ctx) { struct syscall_data data = {}; data.tracingStatus = 1; - events.perf_submit(ctx, &data, sizeof(data)); + bpf_perf_event_output(ctx, &events, BPF_F_CURRENT_CPU, &data, sizeof(data)); return 0; } // exit_function submit the value 2 to advice // the frontend app that the function finished its // execution -inline int exit_function(struct pt_regs *ctx) { +SEC("uprobe/exit_function") +inline int uprobe_exit_function(struct pt_regs *ctx) { struct syscall_data data = {}; data.tracingStatus = 2; - events.perf_submit(ctx, &data, sizeof(data)); + bpf_perf_event_output(ctx, &events, BPF_F_CURRENT_CPU, &data, sizeof(data)); return 0; } -int start_trace(struct tracepoint__raw_syscalls__sys_enter* args) { +SEC("tp/raw_syscalls/sys_enter") +int tracepoint_raw_sys_enter(struct sys_enter_info* ctx) { struct syscall_data data = {}; char comm[16]; @@ -58,9 +77,10 @@ int start_trace(struct tracepoint__raw_syscalls__sys_enter* args) { return 1; } - int id = (int)args->id; + int id = (int)ctx->id; data.syscall_id = id; - events.perf_submit(args, &data, sizeof(data)); + bpf_perf_event_output(ctx, &events, BPF_F_CURRENT_CPU, &data, sizeof(data)); return 0; } +char __license[] SEC("license") = "Dual MIT/GPL"; diff --git a/ebpf_bpfeb.go b/ebpf_bpfeb.go new file mode 100644 index 0000000..e1b2162 --- /dev/null +++ b/ebpf_bpfeb.go @@ -0,0 +1,125 @@ +// Code generated by bpf2go; DO NOT EDIT. +//go:build arm64be || armbe || mips || mips64 || mips64p32 || ppc64 || s390 || s390x || sparc || sparc64 + +package main + +import ( + "bytes" + _ "embed" + "fmt" + "io" + + "github.com/cilium/ebpf" +) + +// loadEbpf returns the embedded CollectionSpec for ebpf. +func loadEbpf() (*ebpf.CollectionSpec, error) { + reader := bytes.NewReader(_EbpfBytes) + spec, err := ebpf.LoadCollectionSpecFromReader(reader) + if err != nil { + return nil, fmt.Errorf("can't load ebpf: %w", err) + } + + return spec, err +} + +// loadEbpfObjects loads ebpf and converts it into a struct. +// +// The following types are suitable as obj argument: +// +// *ebpfObjects +// *ebpfPrograms +// *ebpfMaps +// +// See ebpf.CollectionSpec.LoadAndAssign documentation for details. +func loadEbpfObjects(obj interface{}, opts *ebpf.CollectionOptions) error { + spec, err := loadEbpf() + if err != nil { + return err + } + + return spec.LoadAndAssign(obj, opts) +} + +// ebpfSpecs contains maps and programs before they are loaded into the kernel. +// +// It can be passed ebpf.CollectionSpec.Assign. +type ebpfSpecs struct { + ebpfProgramSpecs + ebpfMapSpecs +} + +// ebpfSpecs contains programs before they are loaded into the kernel. +// +// It can be passed ebpf.CollectionSpec.Assign. +type ebpfProgramSpecs struct { + UprobeEnterFunction *ebpf.ProgramSpec `ebpf:"uprobe_enter_function"` + UprobeExitFunction *ebpf.ProgramSpec `ebpf:"uprobe_exit_function"` + TracepointRawSysEnter *ebpf.ProgramSpec `ebpf:"tracepoint_raw_sys_enter"` +} + +// ebpfMapSpecs contains maps before they are loaded into the kernel. +// +// It can be passed ebpf.CollectionSpec.Assign. +type ebpfMapSpecs struct { + Events *ebpf.MapSpec `ebpf:"events"` +} + +// ebpfObjects contains all objects after they have been loaded into the kernel. +// +// It can be passed to loadEbpfObjects or ebpf.CollectionSpec.LoadAndAssign. +type ebpfObjects struct { + ebpfPrograms + ebpfMaps +} + +func (o *ebpfObjects) Close() error { + return _EbpfClose( + &o.ebpfPrograms, + &o.ebpfMaps, + ) +} + +// ebpfMaps contains all maps after they have been loaded into the kernel. +// +// It can be passed to loadEbpfObjects or ebpf.CollectionSpec.LoadAndAssign. +type ebpfMaps struct { + Events *ebpf.Map `ebpf:"events"` +} + +func (m *ebpfMaps) Close() error { + return _EbpfClose( + m.Events, + ) +} + +// ebpfPrograms contains all programs after they have been loaded into the kernel. +// +// It can be passed to loadEbpfObjects or ebpf.CollectionSpec.LoadAndAssign. +type ebpfPrograms struct { + UprobeEnterFunction *ebpf.Program `ebpf:"uprobe_enter_function"` + UprobeExitFunction *ebpf.Program `ebpf:"uprobe_exit_function"` + TracepointRawSysEnter *ebpf.Program `ebpf:"tracepoint_raw_sys_enter"` +} + +func (p *ebpfPrograms) Close() error { + return _EbpfClose( + p.UprobeEnterFunction, + p.UprobeExitFunction, + p.TracepointRawSysEnter, + ) +} + +func _EbpfClose(closers ...io.Closer) error { + for _, closer := range closers { + if err := closer.Close(); err != nil { + return err + } + } + return nil +} + +// Do not access this directly. +// +//go:embed ebpf_bpfeb.o +var _EbpfBytes []byte diff --git a/ebpf_bpfeb.o b/ebpf_bpfeb.o new file mode 100644 index 0000000000000000000000000000000000000000..cc22bd7c4027f32b9c86b98ebf3b66de34a20a59 GIT binary patch literal 3576 zcmbtWO^g&p6s{Us_eU010%Xy|6o_P&tg~x^N;HsR{{*wElXXczgH1EjvpdQBv^_nz z>xQ@ojCvy`M!e{nXrc)R5)aD(v&4&6jvhF8@POiho7~3ltFD^utvK;uWnRDceeYGh zdR5ic^WpUDOeP~mr%CZQ>=e=Qz!E-EQCC zP#OIqqwV{o_9>n7aE}m=5gY$c@c&`_d!Q#b2r$1zrSSt!4s2bom1r5!2d8wXoKNRr~39e^0OcxD#>8nz|jOfJSfhdz%( z=OM=+{SdZ1WB|fS=ZwvHUxKi^g47^SK=P0fG6-3Nv`k{gvHw+rshIOKgZrTKz+YGl zn>bzXPC(YmIWhr5Iqr9mUdRs!Acqn&Z<=wqe$0~|hx6b#7$UP^*5gTFgl68|ZPo=? z=YdB5Oew|l+_DXdN<9{ycWQqA)Oqi%GjC6O3+D^do+sj!Fc87zpdL3xbEWCQ2qLdi zFE>Q7QL8oTo{lhfx#}-Ats{(rpccknvC+cdCL*kq1b%*mxK_PcS*!;o)m&;s@fIvD z`H`qL>Wf>HM#Xqp#F1YN!Um@6MgIF{ldr<#5-={w0B%vZl33EM_Mr#=Vyl^-`iBJp$hy+g^c@{?BE3 z=)NTbhM@5`r&FixPyT6$&F`#(0+*ePZkw zcO2>UTZ6A#{mZ7Gf@b~v{;=74SU>kTJs-jY?9(E6h3ciZ7Wbp1EgwV9*AU0>a-@PL<1T6CnT*lwIrg!$7AMAJIVY!X5Qdb zLhJ&g?udyIH;M!kO<0h)fCUEP#w|-1EL^y#;)0#*#_zj7GvlRoGr!`O#qiaeC5mJ#m|#$U|%2}a7PJzLed z8uqI(XY0A9p!7W<@(6M7gc^^SbsC9`AU(_~C%K!wB8LYXq`&1bP0{ZK=#yBJ#dr`p z0HtK1+$wy=ybqkS?lVx<;6uyMEzm49hHitdK^strM?bE=NCeE>%=tMHFy~o}UlMh& zMxy)WE?}>yQ3+yfW8Ckceb67E;I|mo@-Xv4DO|2wLtKlZa-0OYY6HQIM_kMceqjxMjY_?^T#532eWhAUIxx2q z)}&OeEO#h{a!FH?T9}LCD#{IN;RivxBuV0NL*&9zsXpGZL}%i{I4_wT+w?aJk*+7T zMlSK&D^;5Ze54)q&&xo^%nU(3OhR8>4*TaXyebfqK+IwUNY)B#mWb{(k-R z$Nb5K*4zkl| z37d6|8yCWT`ZLr(wTZdc$(bWv?qj|Vm7Sh?ABmsGg54uJHnEj7Cd5;)r}lZ0HMk1g zcS+=!gHJnn#lcMne`@eoh&M6j2|}uT#7_cWHuxU!uB<0l3|`~wZnc%Fz?s26cleyU32b%yKC%R zPTy-L4$oHN+g+I7k})WScABzKip2{Ww()5P#|~a|u92VjPDI=Kmn}Y1do+S#Zqn-UGWYtUp|oxOfHZM(Zz=gsA^JdaAC5ZZj7# zb2%EnH{^tK!yG7U@ZG5YrYY2iox=Y4f6vx$`{)19jq?B63j^eeV|4Q~=0^E1n?&~g vV}AO%`FW1_%(bc*)}U?aH~Y_hE>1bp#0q?>9+M^ugFMuO>an~35ySrjX3)h& literal 0 HcmV?d00001 diff --git a/go.mod b/go.mod index 75bb5fa..1f24857 100644 --- a/go.mod +++ b/go.mod @@ -1,8 +1,15 @@ -module github.com/alegrey91/harpoon +module harpoon -go 1.20 +go 1.21.5 require ( - github.com/iovisor/gobpf v0.2.0 + github.com/cilium/ebpf v0.12.3 github.com/seccomp/libseccomp-golang v0.10.0 ) + +require ( + github.com/iovisor/gobpf v0.2.0 // indirect + golang.org/x/arch v0.7.0 + golang.org/x/exp v0.0.0-20230224173230-c95f2b4c22f2 // indirect + golang.org/x/sys v0.14.1-0.20231108175955-e4099bfacb8c // indirect +) diff --git a/go.sum b/go.sum index d1145aa..491bad4 100644 --- a/go.sum +++ b/go.sum @@ -1,4 +1,22 @@ +github.com/cilium/ebpf v0.12.3 h1:8ht6F9MquybnY97at+VDZb3eQQr8ev79RueWeVaEcG4= +github.com/cilium/ebpf v0.12.3/go.mod h1:TctK1ivibvI3znr66ljgi4hqOT8EYQjz1KWBfb1UVgM= +github.com/frankban/quicktest v1.14.5 h1:dfYrrRyLtiqT9GyKXgdh+k4inNeTvmGbuSgZ3lx3GhA= +github.com/frankban/quicktest v1.14.5/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/iovisor/gobpf v0.2.0 h1:34xkQxft+35GagXBk3n23eqhm0v7q0ejeVirb8sqEOQ= github.com/iovisor/gobpf v0.2.0/go.mod h1:WSY9Jj5RhdgC3ci1QaacvbFdQ8cbrEjrpiZbLHLt2s4= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/seccomp/libseccomp-golang v0.10.0 h1:aA4bp+/Zzi0BnWZ2F1wgNBs5gTpm+na2rWM6M9YjLpY= github.com/seccomp/libseccomp-golang v0.10.0/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg= +golang.org/x/arch v0.7.0 h1:pskyeJh/3AmoQ8CPE95vxHLqp1G1GfGNXTmcl9NEKTc= +golang.org/x/arch v0.7.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= +golang.org/x/exp v0.0.0-20230224173230-c95f2b4c22f2 h1:Jvc7gsqn21cJHCmAWx0LiimpP18LZmUxkT5Mp7EZ1mI= +golang.org/x/exp v0.0.0-20230224173230-c95f2b4c22f2/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= +golang.org/x/sys v0.14.1-0.20231108175955-e4099bfacb8c h1:3kC/TjQ+xzIblQv39bCOyRk8fbEeJcDHwbyxPUU2BpA= +golang.org/x/sys v0.14.1-0.20231108175955-e4099bfacb8c/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= diff --git a/main.go b/main.go index 43e4037..237f8e7 100644 --- a/main.go +++ b/main.go @@ -2,8 +2,8 @@ package main import ( "bytes" - "embed" "encoding/binary" + "errors" "flag" "fmt" "log" @@ -11,12 +11,15 @@ import ( "os/exec" "os/signal" "path" - "path/filepath" - "strings" + "syscall" - "github.com/iovisor/gobpf/bcc" + "github.com/cilium/ebpf/link" + "github.com/cilium/ebpf/perf" + "github.com/cilium/ebpf/rlimit" ) +//go:generate go run github.com/cilium/ebpf/cmd/bpf2go ebpf ebpf/ebpf.c -- -I../headers + type event struct { // syscall number SyscallID uint32 @@ -24,8 +27,6 @@ type event struct { TracingStatus uint32 } -//go:embed ebpf/* -var eBPFDir embed.FS var version = "test" func main() { @@ -62,46 +63,55 @@ func main() { os.Exit(1) } - source, _ := eBPFDir.ReadFile("ebpf/ebpf.c") - src := strings.Replace(string(source), "$CMD", filepath.Base(command[0]), -1) - bpfModule := bcc.NewModule(src, []string{}) - defer bpfModule.Close() - - uprobeFd, err := bpfModule.LoadUprobe("enter_function") - if err != nil { - log.Fatal(err) + if err := rlimit.RemoveMemlock(); err != nil { + log.Fatal("Removing memlock:", err) } - uretprobeFd, err := bpfModule.LoadUprobe("exit_function") - if err != nil { - log.Fatal(err) - } - startTrace, err := bpfModule.LoadTracepoint("start_trace") - if err != nil { - log.Fatal(err) + + objs := ebpfObjects{} + if err := loadEbpfObjects(&objs, nil); err != nil { + log.Fatalf("loading objects: %v", err) } + defer objs.Close() - err = bpfModule.AttachUprobe(command[0], *functionName, uprobeFd, -1) + // Open an ELF binary and read its symbols. + ex, err := link.OpenExecutable(command[0]) if err != nil { - log.Fatal(err) + log.Fatalf("opening executable: %s", err) } - err = bpfModule.AttachUretprobe(command[0], *functionName, uretprobeFd, -1) + + // attach "uprobe/enter_function" + upEnter, err := ex.Uprobe(*functionName, objs.UprobeEnterFunction, nil) if err != nil { - log.Fatal(err) + log.Fatalf("creating uretprobe: %s", err) } - if err := bpfModule.AttachTracepoint("raw_syscalls:sys_enter", startTrace); err != nil { - log.Fatal(err) + defer upEnter.Close() + + // for each RET instruction, attach a "uprobe/exit_function" + functionRetOffsets, err := getFunctionRetOffsets(command[0], *functionName) + for _, retOffset := range functionRetOffsets { + upExit, err := ex.Uprobe(*functionName, objs.UprobeEnterFunction, &link.UprobeOptions{ + Offset: retOffset, + }) + if err != nil { + log.Fatal(err) + } + defer upExit.Close() } - table := bcc.NewTable(bpfModule.TableId("events"), bpfModule) - channel := make(chan []byte) - - perfMap, err := bcc.InitPerfMap(table, channel, nil) + // attach "tracepoint/raw_syscalls/sys_enter" + tpSysEnter, err := link.Tracepoint("raw_syscalls", "sys_enter", objs.TracepointRawSysEnter, nil) if err != nil { - log.Fatal(err) + log.Fatalf("opening tracepoint: %s", err) } + defer tpSysEnter.Close() - c := make(chan os.Signal, 1) - signal.Notify(c, os.Interrupt) + // open a perf event reader from userspace on the PERF_EVENT_ARRAY map + // described in the eBPF C program. + rd, err := perf.NewReader(objs.Events, os.Getpagesize()) + if err != nil { + log.Fatalf("creating perf event reader: %s", err) + } + defer rd.Close() // run command that we want to trace go func() { @@ -114,44 +124,65 @@ func main() { cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr }() + stopper := make(chan os.Signal, 1) + signal.Notify(stopper, os.Interrupt, syscall.SIGTERM) + + go func() { + // wait for a signal and close the perf reader, + // which will interrupt rd.Read() and make the program exit. + <-stopper + log.Println("Received signal, exiting program..") + + if err := rd.Close(); err != nil { + log.Fatalf("closing perf event reader: %s", err) + } + }() + var syscalls []uint32 executionStarted := false - go func() { - for data := range channel { - var e event - if err := binary.Read(bytes.NewBuffer(data), binary.LittleEndian, &e); err != nil { - fmt.Printf("failed to decode received data %q: %s\n", data, err) + + var e event + for { + record, err := rd.Read() + if err != nil { + if errors.Is(err, perf.ErrClosed) { return } - switch e.TracingStatus { - case 1: - fmt.Fprintln(os.Stdout, "[+] start tracing") - executionStarted = true - case 2: - fmt.Fprintln(os.Stdout, "[+] stop tracing") - executionStarted = false - - // write to file or stdout depending on the flags passed - if *outputFile != "" { - file, _ := createFile(outputFile) - defer file.Close() - printSyscalls(file, syscalls) - } else { - printSyscalls(os.Stdout, syscalls) - } - - // send an interrupt to gracefuly shutdown the program - p, _ := os.FindProcess(os.Getpid()) - p.Signal(os.Interrupt) - default: - if executionStarted { - syscalls = append(syscalls, e.SyscallID) - } - } + log.Printf("reading from perf event reader: %s", err) + continue } - }() + if record.LostSamples != 0 { + log.Printf("perf event ring buffer full, dropped %d samples", record.LostSamples) + continue + } + if err := binary.Read(bytes.NewBuffer(record.RawSample), binary.LittleEndian, &e); err != nil { + log.Printf("failed to decode received data %q: %s\n", record, err) + continue + } + switch e.TracingStatus { + case 1: + fmt.Fprintln(os.Stdout, "[+] start tracing") + executionStarted = true + case 2: + fmt.Fprintln(os.Stdout, "[+] stop tracing") + executionStarted = false + + // write to file or stdout depending on the flags passed + if *outputFile != "" { + file, _ := createFile(outputFile) + defer file.Close() + printSyscalls(file, syscalls) + } else { + printSyscalls(os.Stdout, syscalls) + } - perfMap.Start() - <-c - perfMap.Stop() + // send an interrupt to gracefuly shutdown the program + p, _ := os.FindProcess(os.Getpid()) + p.Signal(os.Interrupt) + default: + if executionStarted { + syscalls = append(syscalls, e.SyscallID) + } + } + } } diff --git a/utils.go b/utils.go index 9f0963c..4af44c1 100644 --- a/utils.go +++ b/utils.go @@ -1,12 +1,15 @@ package main import ( + "bytes" + "debug/elf" "fmt" "io" "os" "os/user" seccomp "github.com/seccomp/libseccomp-golang" + "golang.org/x/arch/x86/x86asm" ) // printSyscalls takes an io.Writer and slice of syscall ids, @@ -43,3 +46,111 @@ func createFile(outputFile *string) (*os.File, error) { } return file, nil } + +func getFunctionOffset(elfFile string, fnName string) (uint64, error) { + // this code was taken from here: + // https://github.com/safchain/koa/blob/master/ebpf/ebpf.go#L76 + file, err := elf.Open(elfFile) + if err != nil { + return 0, err + } + + var bAddr uint64 + for _, prog := range file.Progs { + if prog.Type == elf.PT_LOAD && (prog.Flags&elf.PF_X) > 0 { + bAddr = prog.Vaddr + } + } + + symbols, err := file.Symbols() + if err != nil { + return 0, err + } + + var offset uint64 + for _, symbol := range symbols { + if symbol.Name == fnName { + offset = symbol.Value + break + } + } + + if offset < bAddr { + return 0, fmt.Errorf("Wrong symbol offset: %s", fnName) + } + offset -= bAddr + + return offset, nil +} + +func getFunctionRetOffsets(elfFile string, fnName string) ([]uint64, error) { + // this code was taken from here: + // https://github.com/cfc4n/go_uretprobe_demo/blob/master/ret_offset.go#L22 + var goSymbs []elf.Symbol + var goElf *elf.File + goElf, err := elf.Open(elfFile) + if err != nil { + return []uint64{}, err + } + goSymbs, err = goElf.Symbols() + if err != nil { + return nil, err + } + + var found bool + var symbol elf.Symbol + for _, s := range goSymbs { + if s.Name == fnName { + symbol = s + found = true + break + } + } + + if !found { + return nil, fmt.Errorf("symbol not found") + } + + section := goElf.Sections[symbol.Section] + + var elfText []byte + elfText, err = section.Data() + if err != nil { + return nil, err + } + + start := symbol.Value - section.Addr + end := start + symbol.Size + + var instHex []byte + instHex = elfText[start:end] + var offsets []uint64 + offsets, err = decodeInstruction(instHex) + if len(offsets) == 0 { + return offsets, fmt.Errorf("no RET instructions found") + } + + return offsets, nil +} + +func decodeInstruction(instHex []byte) ([]uint64, error) { + // this code was taken from here: + // https://github.com/cfc4n/go_uretprobe_demo/blob/master/ret_offset.go#L70C1-L91C2 + var offsets []uint64 + var s *bytes.Buffer + s = bytes.NewBufferString("") + for i := 0; i < len(instHex); { + inst, err := x86asm.Decode(instHex[i:], 64) + s.WriteString(fmt.Sprintf("%04X\t%s", i, inst.String())) + s.WriteString("\n") + if err != nil { + return nil, err + } + if inst.Op == x86asm.RET { + offsets = append(offsets, uint64(i)) + } + i += inst.Len + } + + return offsets, nil +}