diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 82f7e03f5..a3751f96b 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -42,4 +42,4 @@ jobs: push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} - platforms: linux/amd64 + platforms: linux/amd64,linux/arm64 diff --git a/Dockerfile b/Dockerfile index 0f1ed7dae..8649cfe50 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,10 +1,11 @@ FROM fedora:37 as builder +ARG TARGETARCH RUN dnf install clang llvm make libbpf-devel -y -RUN curl -LO https://go.dev/dl/go1.20.linux-amd64.tar.gz && tar -C /usr/local -xzf go*.linux-amd64.tar.gz +RUN curl -LO https://go.dev/dl/go1.20.linux-$TARGETARCH.tar.gz && tar -C /usr/local -xzf go*.linux-$TARGETARCH.tar.gz ENV PATH="/usr/local/go/bin:${PATH}" WORKDIR /app COPY . . -RUN make build +RUN TARGET=$TARGETARCH make build FROM registry.fedoraproject.org/fedora-minimal:37 COPY --from=builder /app/otel-go-instrumentation / diff --git a/Makefile b/Makefile index dfec3a943..7e5ecbddc 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ REPODIR := $(shell dirname $(realpath $(firstword $(MAKEFILE_LIST)))) # Build the list of include directories to compile the bpf program BPF_INCLUDE += -I${REPODIR}/include/libbpf -BPF_INCLUDE+= -I${REPODIR}/include +BPF_INCLUDE += -I${REPODIR}/include # Tools TOOLS_MOD_DIR := ./internal/tools @@ -30,7 +30,7 @@ generate: .PHONY: build build: generate - GOOS=linux GOARCH=amd64 go build -o otel-go-instrumentation cli/main.go + GOOS=linux go build -o otel-go-instrumentation cli/main.go .PHONY: docker-build docker-build: diff --git a/include/alloc.h b/include/alloc.h index 3fff24d8c..11e1d66ca 100644 --- a/include/alloc.h +++ b/include/alloc.h @@ -52,7 +52,7 @@ static __always_inline u64 get_area_end(u64 start) { s64 partition_size = (end_addr - start_addr) / total_cpus; s32 end_index = 1; - u64 *end = (u64 *)bpf_map_lookup_elem(&alloc_map, &end_index); + u64* end = (u64*)bpf_map_lookup_elem(&alloc_map, &end_index); if (end == NULL || *end == 0) { u64 current_end_addr = start + partition_size; @@ -88,6 +88,12 @@ static __always_inline void *write_target_data(void *data, s32 size) { s32 start_index = 0; u64 updated_start = start + size; + + // align updated_start to 8 bytes + if (updated_start % 8 != 0) { + updated_start += 8 - (updated_start % 8); + } + bpf_map_update_elem(&alloc_map, &start_index, &updated_start, BPF_ANY); return target; } diff --git a/include/arguments.h b/include/arguments.h index e75937fd2..b66a90a87 100644 --- a/include/arguments.h +++ b/include/arguments.h @@ -13,6 +13,7 @@ // limitations under the License. #include "common.h" +#include "bpf_tracing.h" #include "bpf_helpers.h" #include @@ -24,23 +25,23 @@ void *get_argument_by_reg(struct pt_regs *ctx, int index) switch (index) { case 1: - return (void *)(ctx->rax); + return (void *)GO_PARAM1(ctx); case 2: - return (void *)(ctx->rbx); + return (void *)GO_PARAM2(ctx); case 3: - return (void *)(ctx->rcx); + return (void *)GO_PARAM3(ctx); case 4: - return (void *)(ctx->rdi); + return (void *)GO_PARAM4(ctx); case 5: - return (void *)(ctx->rsi); + return (void *)GO_PARAM5(ctx); case 6: - return (void *)(ctx->r8); + return (void *)GO_PARAM6(ctx); case 7: - return (void *)(ctx->r9); + return (void *)GO_PARAM7(ctx); case 8: - return (void *)(ctx->r10); + return (void *)GO_PARAM8(ctx); case 9: - return (void *)(ctx->r11); + return (void *)GO_PARAM9(ctx); default: return NULL; } @@ -48,8 +49,8 @@ void *get_argument_by_reg(struct pt_regs *ctx, int index) void *get_argument_by_stack(struct pt_regs *ctx, int index) { - void *ptr = 0; - bpf_probe_read(&ptr, sizeof(ptr), (void *)(ctx->rsp + (index * 8))); + void* ptr = 0; + bpf_probe_read(&ptr, sizeof(ptr), (void *)(PT_REGS_SP(ctx)+(index*8))); return ptr; } diff --git a/include/common.h b/include/common.h index 7316a0459..19f1fba55 100644 --- a/include/common.h +++ b/include/common.h @@ -84,41 +84,57 @@ enum #define BPF_F_INDEX_MASK 0xffffffffULL #define BPF_F_CURRENT_CPU BPF_F_INDEX_MASK -#define PT_REGS_RC(x) ((x)->rax) -struct pt_regs -{ - /* - * C ABI says these regs are callee-preserved. They aren't saved on kernel entry - * unless syscall needs a complete, fully filled "struct pt_regs". - */ - unsigned long r15; - unsigned long r14; - unsigned long r13; - unsigned long r12; - unsigned long rbp; - unsigned long rbx; - /* These regs are callee-clobbered. Always saved on kernel entry. */ - unsigned long r11; - unsigned long r10; - unsigned long r9; - unsigned long r8; - unsigned long rax; - unsigned long rcx; - unsigned long rdx; - unsigned long rsi; - unsigned long rdi; - /* - * On syscall entry, this is syscall#. On CPU exception, this is error code. - * On hw interrupt, it's IRQ number: - */ - unsigned long orig_rax; - /* Return frame for iretq */ - unsigned long rip; - unsigned long cs; - unsigned long eflags; - unsigned long rsp; - unsigned long ss; - /* top of stack page */ +#if defined(__TARGET_ARCH_x86) +struct pt_regs { + long unsigned int r15; + long unsigned int r14; + long unsigned int r13; + long unsigned int r12; + long unsigned int bp; + long unsigned int bx; + long unsigned int r11; + long unsigned int r10; + long unsigned int r9; + long unsigned int r8; + long unsigned int ax; + long unsigned int cx; + long unsigned int dx; + long unsigned int si; + long unsigned int di; + long unsigned int orig_ax; + long unsigned int ip; + long unsigned int cs; + long unsigned int flags; + long unsigned int sp; + long unsigned int ss; +}; +#elif defined(__TARGET_ARCH_arm64) +struct user_pt_regs { + __u64 regs[31]; + __u64 sp; + __u64 pc; + __u64 pstate; +}; + +struct pt_regs { + union { + struct user_pt_regs user_regs; + struct { + u64 regs[31]; + u64 sp; + u64 pc; + u64 pstate; + }; + }; + u64 orig_x0; + s32 syscallno; + u32 unused2; + u64 orig_addr_limit; + u64 pmr_save; + u64 stackframe[2]; + u64 lockdep_hardirqs; + u64 exit_rcu; }; +#endif #endif /* __VMLINUX_H__ */ diff --git a/include/go_types.h b/include/go_types.h index 622f9c626..6ad2f6474 100644 --- a/include/go_types.h +++ b/include/go_types.h @@ -54,7 +54,11 @@ static __always_inline struct go_string write_user_go_string(char *str, u32 len) new_string.len = len; // Copy new string struct to userspace - write_target_data((void *)&new_string, sizeof(new_string)); + void* res = write_target_data((void*)&new_string, sizeof(new_string)); + if (res == NULL) { + new_string.len = 0; + } + return new_string; } diff --git a/include/libbpf/bpf_tracing.h b/include/libbpf/bpf_tracing.h index d6bfbe009..46a57a02f 100644 --- a/include/libbpf/bpf_tracing.h +++ b/include/libbpf/bpf_tracing.h @@ -24,6 +24,9 @@ #elif defined(__TARGET_ARCH_sparc) #define bpf_target_sparc #define bpf_target_defined +#elif defined(__TARGET_ARCH_riscv) + #define bpf_target_riscv + #define bpf_target_defined #else /* Fall back to what the compiler says */ @@ -48,6 +51,9 @@ #elif defined(__sparc__) #define bpf_target_sparc #define bpf_target_defined +#elif defined(__riscv) && __riscv_xlen == 64 + #define bpf_target_riscv + #define bpf_target_defined #endif /* no compiler target */ #endif @@ -60,6 +66,17 @@ #if defined(__KERNEL__) || defined(__VMLINUX_H__) +#define GO_PARAM1(x) ((x)->ax) +#define GO_PARAM2(x) ((x)->bx) +#define GO_PARAM3(x) ((x)->cx) +#define GO_PARAM4(x) ((x)->di) +#define GO_PARAM5(x) ((x)->si) +#define GO_PARAM6(x) ((x)->r8) +#define GO_PARAM7(x) ((x)->r9) +#define GO_PARAM8(x) ((x)->r10) +#define GO_PARAM9(x) ((x)->r11) +#define GOROUTINE(x) ((x)->r14) + #define PT_REGS_PARM1(x) ((x)->di) #define PT_REGS_PARM2(x) ((x)->si) #define PT_REGS_PARM3(x) ((x)->dx) @@ -192,6 +209,18 @@ struct pt_regs; /* arm64 provides struct user_pt_regs instead of struct pt_regs to userspace */ struct pt_regs; #define PT_REGS_ARM64 const volatile struct user_pt_regs + +#define GO_PARAM1(x) (((PT_REGS_ARM64 *)(x))->regs[0]) +#define GO_PARAM2(x) (((PT_REGS_ARM64 *)(x))->regs[1]) +#define GO_PARAM3(x) (((PT_REGS_ARM64 *)(x))->regs[2]) +#define GO_PARAM4(x) (((PT_REGS_ARM64 *)(x))->regs[3]) +#define GO_PARAM5(x) (((PT_REGS_ARM64 *)(x))->regs[4]) +#define GO_PARAM6(x) (((PT_REGS_ARM64 *)(x))->regs[5]) +#define GO_PARAM7(x) (((PT_REGS_ARM64 *)(x))->regs[6]) +#define GO_PARAM8(x) (((PT_REGS_ARM64 *)(x))->regs[7]) +#define GO_PARAM9(x) (((PT_REGS_ARM64 *)(x))->regs[8]) +#define GOROUTINE(x) (((PT_REGS_ARM64 *)(x))->regs[28]) + #define PT_REGS_PARM1(x) (((PT_REGS_ARM64 *)(x))->regs[0]) #define PT_REGS_PARM2(x) (((PT_REGS_ARM64 *)(x))->regs[1]) #define PT_REGS_PARM3(x) (((PT_REGS_ARM64 *)(x))->regs[2]) @@ -288,6 +317,32 @@ struct pt_regs; #define PT_REGS_IP_CORE(x) BPF_CORE_READ((x), pc) #endif +#elif defined(bpf_target_riscv) + +struct pt_regs; +#define PT_REGS_RV const volatile struct user_regs_struct +#define PT_REGS_PARM1(x) (((PT_REGS_RV *)(x))->a0) +#define PT_REGS_PARM2(x) (((PT_REGS_RV *)(x))->a1) +#define PT_REGS_PARM3(x) (((PT_REGS_RV *)(x))->a2) +#define PT_REGS_PARM4(x) (((PT_REGS_RV *)(x))->a3) +#define PT_REGS_PARM5(x) (((PT_REGS_RV *)(x))->a4) +#define PT_REGS_RET(x) (((PT_REGS_RV *)(x))->ra) +#define PT_REGS_FP(x) (((PT_REGS_RV *)(x))->s5) +#define PT_REGS_RC(x) (((PT_REGS_RV *)(x))->a5) +#define PT_REGS_SP(x) (((PT_REGS_RV *)(x))->sp) +#define PT_REGS_IP(x) (((PT_REGS_RV *)(x))->epc) + +#define PT_REGS_PARM1_CORE(x) BPF_CORE_READ((PT_REGS_RV *)(x), a0) +#define PT_REGS_PARM2_CORE(x) BPF_CORE_READ((PT_REGS_RV *)(x), a1) +#define PT_REGS_PARM3_CORE(x) BPF_CORE_READ((PT_REGS_RV *)(x), a2) +#define PT_REGS_PARM4_CORE(x) BPF_CORE_READ((PT_REGS_RV *)(x), a3) +#define PT_REGS_PARM5_CORE(x) BPF_CORE_READ((PT_REGS_RV *)(x), a4) +#define PT_REGS_RET_CORE(x) BPF_CORE_READ((PT_REGS_RV *)(x), ra) +#define PT_REGS_FP_CORE(x) BPF_CORE_READ((PT_REGS_RV *)(x), fp) +#define PT_REGS_RC_CORE(x) BPF_CORE_READ((PT_REGS_RV *)(x), a5) +#define PT_REGS_SP_CORE(x) BPF_CORE_READ((PT_REGS_RV *)(x), sp) +#define PT_REGS_IP_CORE(x) BPF_CORE_READ((PT_REGS_RV *)(x), epc) + #endif #if defined(bpf_target_powerpc) diff --git a/pkg/instrumentors/bpf/github.com/gorilla/mux/probe.go b/pkg/instrumentors/bpf/github.com/gorilla/mux/probe.go index 3b6c5e629..3f5c5a03d 100644 --- a/pkg/instrumentors/bpf/github.com/gorilla/mux/probe.go +++ b/pkg/instrumentors/bpf/github.com/gorilla/mux/probe.go @@ -35,7 +35,7 @@ import ( "golang.org/x/sys/unix" ) -//go:generate go run github.com/cilium/ebpf/cmd/bpf2go -target bpfel -cc clang -cflags $CFLAGS bpf ./bpf/probe.bpf.c +//go:generate go run github.com/cilium/ebpf/cmd/bpf2go -target $TARGET -cc clang -cflags $CFLAGS bpf ./bpf/probe.bpf.c type HttpEvent struct { StartTime uint64 diff --git a/pkg/instrumentors/bpf/google/golang/org/grpc/bpf/probe.bpf.c b/pkg/instrumentors/bpf/google/golang/org/grpc/bpf/probe.bpf.c index 822da38e6..3617338fa 100644 --- a/pkg/instrumentors/bpf/google/golang/org/grpc/bpf/probe.bpf.c +++ b/pkg/instrumentors/bpf/google/golang/org/grpc/bpf/probe.bpf.c @@ -132,12 +132,12 @@ int uprobe_Http2Client_CreateHeaderFields(struct pt_regs *ctx) struct go_slice_user_ptr slice_user_ptr = {}; if (is_registers_abi) { - slice.array = (void *)ctx->rax; - slice.len = (s32)ctx->rbx; - slice.cap = (s32)ctx->rcx; - slice_user_ptr.array = &ctx->rax; - slice_user_ptr.len = &ctx->rbx; - slice_user_ptr.cap = &ctx->rcx; + slice.array = (void*) GO_PARAM1(ctx); + slice.len = (s32) GO_PARAM2(ctx); + slice.cap = (s32) GO_PARAM3(ctx); + slice_user_ptr.array = (void*) &GO_PARAM1(ctx); + slice_user_ptr.len = (void*) &GO_PARAM2(ctx); + slice_user_ptr.cap = (void*) &GO_PARAM3(ctx); } else { @@ -145,18 +145,22 @@ int uprobe_Http2Client_CreateHeaderFields(struct pt_regs *ctx) s32 slice_len_pos = 6; s32 slice_cap_pos = 7; slice.array = get_argument(ctx, slice_pointer_pos); - slice.len = (s32)get_argument(ctx, slice_len_pos); - slice.cap = (s32)get_argument(ctx, slice_cap_pos); - slice_user_ptr.array = (void *)ctx->rsp + (slice_pointer_pos * 8); - slice_user_ptr.len = (void *)ctx->rsp + (slice_len_pos * 8); - slice_user_ptr.cap = (void *)ctx->rsp + (slice_cap_pos * 8); + slice.len = (long)get_argument(ctx, slice_len_pos); + slice.cap = (long)get_argument(ctx, slice_cap_pos); + slice_user_ptr.array = (void *)(PT_REGS_SP(ctx)+(slice_pointer_pos*8)); + slice_user_ptr.len = (void *)(PT_REGS_SP(ctx)+(slice_len_pos*8)); + slice_user_ptr.cap = (void *)(PT_REGS_SP(ctx)+(slice_cap_pos*8)); } char key[11] = "traceparent"; struct go_string key_str = write_user_go_string(key, sizeof(key)); + if (key_str.len == 0) { + bpf_printk("write failed, aborting ebpf probe"); + return 0; + } // Get grpc request struct void *context_ptr = 0; - bpf_probe_read(&context_ptr, sizeof(context_ptr), (void *)(ctx->rsp + (context_pointer_pos * 8))); + bpf_probe_read(&context_ptr, sizeof(context_ptr), (void *)(PT_REGS_SP(ctx)+(context_pointer_pos*8))); void *parent_ctx = find_context_in_map(context_ptr, &context_to_grpc_events); void *grpcReq_ptr = bpf_map_lookup_elem(&context_to_grpc_events, &parent_ctx); struct grpc_request_t grpcReq = {}; diff --git a/pkg/instrumentors/bpf/google/golang/org/grpc/probe.go b/pkg/instrumentors/bpf/google/golang/org/grpc/probe.go index 6fc2b0536..fefa540f0 100644 --- a/pkg/instrumentors/bpf/google/golang/org/grpc/probe.go +++ b/pkg/instrumentors/bpf/google/golang/org/grpc/probe.go @@ -36,7 +36,7 @@ import ( "golang.org/x/sys/unix" ) -//go:generate go run github.com/cilium/ebpf/cmd/bpf2go -target bpfel -cc clang -cflags $CFLAGS bpf ./bpf/probe.bpf.c +//go:generate go run github.com/cilium/ebpf/cmd/bpf2go -target $TARGET -cc clang -cflags $CFLAGS bpf ./bpf/probe.bpf.c type GrpcEvent struct { StartTime uint64 diff --git a/pkg/instrumentors/bpf/google/golang/org/grpc/server/bpf/probe.bpf.c b/pkg/instrumentors/bpf/google/golang/org/grpc/server/bpf/probe.bpf.c index ce257e61e..424b7b53f 100644 --- a/pkg/instrumentors/bpf/google/golang/org/grpc/server/bpf/probe.bpf.c +++ b/pkg/instrumentors/bpf/google/golang/org/grpc/server/bpf/probe.bpf.c @@ -115,50 +115,7 @@ int uprobe_server_handleStream(struct pt_regs *ctx) } SEC("uprobe/server_handleStream") -int uprobe_server_handleStream_ByRegisters(struct pt_regs *ctx) -{ - void *stream_ptr = (void *)(ctx->rdi); - - // Get parent context if exists - u32 stream_id = 0; - bpf_probe_read(&stream_id, sizeof(stream_id), (void *)(stream_ptr + stream_id_pos)); - void *grpcReq_ptr = bpf_map_lookup_elem(&streamid_to_grpc_events, &stream_id); - struct grpc_request_t grpcReq = {}; - if (grpcReq_ptr != NULL) - { - bpf_probe_read(&grpcReq, sizeof(grpcReq), grpcReq_ptr); - bpf_map_delete_elem(&streamid_to_grpc_events, &stream_id); - copy_byte_arrays(grpcReq.psc.TraceID, grpcReq.sc.TraceID, TRACE_ID_SIZE); - generate_random_bytes(grpcReq.sc.SpanID, SPAN_ID_SIZE); - } - else - { - grpcReq.sc = generate_span_context(); - } - - // Set attributes - grpcReq.start_time = bpf_ktime_get_ns(); - void *method_ptr = 0; - bpf_probe_read(&method_ptr, sizeof(method_ptr), (void *)(stream_ptr + stream_method_ptr_pos)); - u64 method_len = 0; - bpf_probe_read(&method_len, sizeof(method_len), (void *)(stream_ptr + (stream_method_ptr_pos + 8))); - u64 method_size = sizeof(grpcReq.method); - method_size = method_size < method_len ? method_size : method_len; - bpf_probe_read(&grpcReq.method, method_size, method_ptr); - - // Write event - void *ctx_iface = 0; - bpf_probe_read(&ctx_iface, sizeof(ctx_iface), (void *)(stream_ptr + stream_ctx_pos)); - void *ctx_instance = 0; - bpf_probe_read(&ctx_instance, sizeof(ctx_instance), (void *)(ctx_iface + 8)); - bpf_map_update_elem(&context_to_grpc_events, &ctx_instance, &grpcReq, 0); - bpf_map_update_elem(&spans_in_progress, &ctx_instance, &grpcReq.sc, 0); - return 0; -} - -SEC("uprobe/server_handleStream") -int uprobe_server_handleStream_Returns(struct pt_regs *ctx) -{ +int uprobe_server_handleStream_Returns(struct pt_regs *ctx) { u64 stream_pos = 4; void *stream_ptr = get_argument(ctx, stream_pos); void *ctx_iface = 0; diff --git a/pkg/instrumentors/bpf/google/golang/org/grpc/server/probe.go b/pkg/instrumentors/bpf/google/golang/org/grpc/server/probe.go index 7b4e212eb..225416d55 100644 --- a/pkg/instrumentors/bpf/google/golang/org/grpc/server/probe.go +++ b/pkg/instrumentors/bpf/google/golang/org/grpc/server/probe.go @@ -35,7 +35,7 @@ import ( "golang.org/x/sys/unix" ) -//go:generate go run github.com/cilium/ebpf/cmd/bpf2go -target bpfel -cc clang -cflags $CFLAGS bpf ./bpf/probe.bpf.c +//go:generate go run github.com/cilium/ebpf/cmd/bpf2go -target $TARGET -cc clang -cflags $CFLAGS bpf ./bpf/probe.bpf.c type GrpcEvent struct { StartTime uint64 @@ -63,7 +63,7 @@ func (g *grpcServerInstrumentor) LibraryName() string { func (g *grpcServerInstrumentor) FuncNames() []string { return []string{"google.golang.org/grpc.(*Server).handleStream", - "google.golang.org/grpc/internal/transport.(*decodeState).decodeHeader"} + "google.golang.org/grpc/internal/transport.(*http2Server).operateHeaders"} } func (g *grpcServerInstrumentor) Load(ctx *context.InstrumentorContext) error { @@ -119,15 +119,8 @@ func (g *grpcServerInstrumentor) Load(ctx *context.InstrumentorContext) error { return err } - var uprobeObj *ebpf.Program - if ctx.TargetDetails.IsRegistersABI() { - uprobeObj = g.bpfObjects.UprobeServerHandleStreamByRegisters - } else { - uprobeObj = g.bpfObjects.UprobeServerHandleStream - } - - up, err := ctx.Executable.Uprobe("", uprobeObj, &link.UprobeOptions{ - Address: offset, + up, err := ctx.Executable.Uprobe("", g.bpfObjects.UprobeServerHandleStream, &link.UprobeOptions{ + Offset: offset, }) if err != nil { return err diff --git a/pkg/instrumentors/bpf/net/http/server/probe.go b/pkg/instrumentors/bpf/net/http/server/probe.go index d6f6cfa45..70ee01f84 100644 --- a/pkg/instrumentors/bpf/net/http/server/probe.go +++ b/pkg/instrumentors/bpf/net/http/server/probe.go @@ -35,7 +35,7 @@ import ( "golang.org/x/sys/unix" ) -//go:generate go run github.com/cilium/ebpf/cmd/bpf2go -target bpfel -cc clang -cflags $CFLAGS bpf ./bpf/probe.bpf.c +//go:generate go run github.com/cilium/ebpf/cmd/bpf2go -target $TARGET -cc clang -cflags $CFLAGS bpf ./bpf/probe.bpf.c type HttpEvent struct { StartTime uint64 diff --git a/pkg/instrumentors/manager.go b/pkg/instrumentors/manager.go index 956c71c26..987bd8725 100644 --- a/pkg/instrumentors/manager.go +++ b/pkg/instrumentors/manager.go @@ -28,6 +28,10 @@ import ( "github.com/open-telemetry/opentelemetry-go-instrumentation/pkg/process" ) +var ( + ErrNotAllFuncsFound = fmt.Errorf("not all functions found for instrumentation") +) + type instrumentorsManager struct { instrumentors map[string]Instrumentor done chan bool @@ -80,16 +84,17 @@ func (m *instrumentorsManager) FilterUnusedInstrumentors(target *process.TargetD } for name, inst := range m.instrumentors { - allFuncExists := true + funcsFound := 0 for _, instF := range inst.FuncNames() { - if _, exists := existingFuncMap[instF]; !exists { - allFuncExists = false - break + if _, exists := existingFuncMap[instF]; exists { + funcsFound++ } } - if !allFuncExists { - log.Logger.V(1).Info("filtering unused instrumentation", "name", name) + if funcsFound != len(inst.FuncNames()) { + if funcsFound > 0 { + log.Logger.Error(ErrNotAllFuncsFound, "some of expected functions not found - check instrumented functions", "instrumentation_name", name, "funcs_found", funcsFound, "funcs_expected", len(inst.FuncNames())) + } delete(m.instrumentors, name) } } diff --git a/pkg/process/analyze.go b/pkg/process/analyze.go index 75d263ff1..9b9e29e8c 100644 --- a/pkg/process/analyze.go +++ b/pkg/process/analyze.go @@ -16,20 +16,17 @@ package process import ( "debug/elf" - "debug/gosym" "errors" "fmt" "os" - "strings" "github.com/hashicorp/go-version" "github.com/open-telemetry/opentelemetry-go-instrumentation/pkg/log" "github.com/open-telemetry/opentelemetry-go-instrumentation/pkg/process/ptrace" - "golang.org/x/arch/x86/x86asm" ) const ( - mapSize = 15 * 1024 * 1024 + mapSize = 4096 * 6 * 1024 ) type TargetDetails struct { @@ -136,34 +133,22 @@ func (a *processAnalyzer) Analyze(pid int, relevantFuncs map[string]interface{}) EndAddr: addr + mapSize, } - var pclndat []byte - if sec := elfF.Section(".gopclntab"); sec != nil { - pclndat, err = sec.Data() - if err != nil { - return nil, err - } - } - - sec := elfF.Section(".gosymtab") - if sec == nil { - return nil, fmt.Errorf("%s section not found in target binary, make sure this is a Go application", ".gosymtab") + if err != nil { + return nil, err } - symTabRaw, err := sec.Data() - pcln := gosym.NewLineTable(pclndat, elfF.Section(".text").Addr) - symTab, err := gosym.NewTable(symTabRaw, pcln) + symbols, err := elfF.Symbols() if err != nil { return nil, err } - for _, f := range symTab.Funcs { - fName := f.Name - // fetch short path of function for vendor scene - if paths := strings.Split(fName, "/vendor/"); len(paths) > 1 { - fName = paths[1] - } + for _, f := range symbols { + if _, exists := relevantFuncs[f.Name]; exists { + offset, err := getFuncOffset(elfF, f) + if err != nil { + return nil, err + } - if _, exists := relevantFuncs[fName]; exists { - start, returns, err := a.findFuncOffset(&f, elfF) + returns, err := findFuncReturns(elfF, f, offset) if err != nil { log.Logger.V(1).Info("can't find function offset. Skipping", "function", f.Name) continue @@ -171,11 +156,11 @@ func (a *processAnalyzer) Analyze(pid int, relevantFuncs map[string]interface{}) log.Logger.V(0).Info("found relevant function for instrumentation", "function", f.Name, - "start", start, + "start", offset, "returns", returns) function := &Func{ - Name: fName, - Offset: start, + Name: f.Name, + Offset: offset, ReturnOffsets: returns, } @@ -189,44 +174,62 @@ func (a *processAnalyzer) Analyze(pid int, relevantFuncs map[string]interface{}) return result, nil } -func (a *processAnalyzer) findFuncOffset(f *gosym.Func, elfF *elf.File) (uint64, []uint64, error) { - off := f.Value - for _, prog := range elfF.Progs { - if prog.Type != elf.PT_LOAD || (prog.Flags&elf.PF_X) == 0 { - continue +func getFuncOffset(f *elf.File, symbol elf.Symbol) (uint64, error) { + var sections []*elf.Section + + for i := range f.Sections { + if f.Sections[i].Flags == elf.SHF_ALLOC+elf.SHF_EXECINSTR { + sections = append(sections, f.Sections[i]) } + } - // For more info on this calculation: stackoverflow.com/a/40249502 - if prog.Vaddr <= f.Value && f.Value < (prog.Vaddr+prog.Memsz) { - off = f.Value - prog.Vaddr + prog.Off + if len(sections) == 0 { + return 0, fmt.Errorf("function %q not found in file", symbol) + } - funcLen := f.End - f.Entry - data := make([]byte, funcLen) - _, err := prog.ReadAt(data, int64(f.Value-prog.Vaddr)) - if err != nil { - log.Logger.Error(err, "error while finding function return") - return 0, nil, err - } + var execSection *elf.Section + for m := range sections { + sectionStart := sections[m].Addr + sectionEnd := sectionStart + sections[m].Size + if symbol.Value >= sectionStart && symbol.Value < sectionEnd { + execSection = sections[m] + break + } + } - var returns []uint64 - for i := 0; i < int(funcLen); { - inst, err := x86asm.Decode(data[i:], 64) - if err != nil { - log.Logger.Error(err, "error while finding function return") - return 0, nil, err - } + if execSection == nil { + return 0, errors.New("could not find symbol in executable sections of binary") + } - if inst.Op == x86asm.RET { - returns = append(returns, off+uint64(i)) - } + return uint64(symbol.Value - execSection.Addr + execSection.Offset), nil +} - i += inst.Len - } +func findFuncReturns(elfFile *elf.File, sym elf.Symbol, functionOffset uint64) ([]uint64, error) { + textSection := elfFile.Section(".text") + if textSection == nil { + return nil, errors.New("could not find .text section in binary") + } - return off, returns, nil - } + lowPC := sym.Value + highPC := lowPC + sym.Size + offset := lowPC - textSection.Addr + buf := make([]byte, int(highPC-lowPC)) + + readBytes, err := textSection.ReadAt(buf, int64(offset)) + if err != nil { + return nil, fmt.Errorf("could not read text section: %w", err) + } + data := buf[:readBytes] + instructionIndices, err := findRetInstructions(data) + if err != nil { + return nil, fmt.Errorf("error while scanning instructions: %w", err) + } + // Add the function lowPC to each index to obtain the actual locations + newLocations := make([]uint64, len(instructionIndices)) + for i, instructionIndex := range instructionIndices { + newLocations[i] = instructionIndex + functionOffset } - return 0, nil, fmt.Errorf("prog not found") + return newLocations, nil } diff --git a/pkg/process/ret_linux_amd64.go b/pkg/process/ret_linux_amd64.go new file mode 100644 index 000000000..2c730c6d1 --- /dev/null +++ b/pkg/process/ret_linux_amd64.go @@ -0,0 +1,25 @@ +package process + +import ( + "fmt" + "golang.org/x/arch/x86/x86asm" +) + +func findRetInstructions(data []byte) ([]uint64, error) { + var returnOffsets []uint64 + index := 0 + for index < len(data) { + instruction, err := x86asm.Decode(data[index:], 64) + if err != nil { + return nil, fmt.Errorf("failed to decode x64 instruction at offset %d: %w", index, err) + } + + if instruction.Op == x86asm.RET { + returnOffsets = append(returnOffsets, uint64(index)) + } + + index += instruction.Len + } + + return returnOffsets, nil +} diff --git a/pkg/process/ret_linux_arm64.go b/pkg/process/ret_linux_arm64.go new file mode 100644 index 000000000..a29a10789 --- /dev/null +++ b/pkg/process/ret_linux_arm64.go @@ -0,0 +1,25 @@ +package process + +import ( + "golang.org/x/arch/arm64/arm64asm" +) + +const ( + // In ARM64 each instruction is 4 bytes in length + armInstructionSize = 4 +) + +func findRetInstructions(data []byte) ([]uint64, error) { + var returnOffsets []uint64 + index := 0 + for index < len(data) { + instruction, err := arm64asm.Decode(data[index:]) + if err == nil && instruction.Op == arm64asm.RET { + returnOffsets = append(returnOffsets, uint64(index)) + } + + index += armInstructionSize + } + + return returnOffsets, nil +}