From b64d5cfe9dca555d4a9d729481ccc870b980ebc7 Mon Sep 17 00:00:00 2001 From: Mike Goldsmith Date: Tue, 2 May 2023 17:40:11 +0100 Subject: [PATCH] [feat] Add ARM support (#82) * Add ARM64 Support Ported from https://github.com/keyval-dev/opentelemetry-go-instrumentation/pull/53 Co-authored-by: Eden Federman * update net/http probe to get args from ctx instead of goroutine * fix c file formatting * more c file formatting tidy * get http header ctx pointer for net/http instrumentation * switch back to goroutine * set bpf2go target as bpfel * revert bpf2go target to $TARGET * remove generated probe go files * set TARGET in build workflow to amd64 * add default docker image name to makefile * remove TARGET and IMG env vars in build workflow * set TARGETARCH in build workflow * tidy up build workflow * update go generate to use set targets directly * add qemu * lets see what happens with buildarch * try buildx * try buildx * testing if we need buildx setup step * remove --no-cache option * switch back to specific eBPF arch targets * tidy up docker/make files * set default target as amd64 * readd buildx * fix buildx arg * fix image name * wait for longer for e2e pods * Revert "wait for longer for e2e pods" This reverts commit 2ef3b6ebe8b02133494cde124c14002e8266a552. * apply fix for instrumenting stripped binaries ported from: https://github.com/keyval-dev/opentelemetry-go-instrumentation/pull/56 * Revert "apply fix for instrumenting stripped binaries" This reverts commit 5d5821668d93d4f28d025501daa591a24ce5acca. * use $TARGET for gin bpf2cmd * update ptrace_linux_arm * add changelog entry * extend get_goroutine to work with go <1.17 * add missing licese files * fix bad merge in Makefile * remove unnessary additional funcname for net/http instrumentor * update bpf2go generate commands to use both arm and amd * Update changelog entry Co-authored-by: Tyler Yahn * re-add comments to pt_regs struct * add build guards to findReturnInstructions arch files * unexport error const * separate imports * update mapSize description * Fix changelog entry Co-authored-by: Tyler Yahn --------- Co-authored-by: Eden Federman Co-authored-by: Purvi Kanal Co-authored-by: Robb Kidd Co-authored-by: Tyler Yahn --- .github/workflows/build.yaml | 2 +- .github/workflows/release.yaml | 2 +- CHANGELOG.md | 1 + Dockerfile | 3 +- Makefile | 11 +- include/alloc.h | 6 + include/arguments.h | 33 ++-- include/common.h | 75 ++++++--- include/go_types.h | 6 +- include/libbpf/bpf_tracing.h | 55 +++++++ .../github.com/gin-gonic/gin/bpf/probe.bpf.c | 10 +- .../gin/{bpf_bpfel.go => bpf_bpfel_arm64.go} | 6 +- .../github.com/gin-gonic/gin/bpf_bpfel_x86.go | 143 ++++++++++++++++ .../bpf/github.com/gin-gonic/gin/probe.go | 7 +- .../github.com/gorilla/mux/bpf/probe.bpf.c | 55 +++---- .../mux/{bpf_bpfel.go => bpf_bpfel_arm64.go} | 9 +- .../github.com/gorilla/mux/bpf_bpfel_x86.go | 143 ++++++++++++++++ .../bpf/github.com/gorilla/mux/probe.go | 2 +- .../google/golang/org/grpc/bpf/probe.bpf.c | 28 ++-- .../grpc/{bpf_bpfel.go => bpf_bpfel_arm64.go} | 6 +- .../google/golang/org/grpc/bpf_bpfel_x86.go | 155 ++++++++++++++++++ .../bpf/google/golang/org/grpc/probe.go | 2 +- .../golang/org/grpc/server/bpf/probe.bpf.c | 45 +---- .../{bpf_bpfel.go => bpf_bpfel_arm64.go} | 21 +-- .../golang/org/grpc/server/bpf_bpfel_x86.go | 152 +++++++++++++++++ .../google/golang/org/grpc/server/probe.go | 15 +- .../bpf/net/http/server/bpf/probe.bpf.c | 10 +- .../{bpf_bpfel.go => bpf_bpfel_arm64.go} | 6 +- .../bpf/net/http/server/bpf_bpfel_x86.go | 143 ++++++++++++++++ .../bpf/net/http/server/probe.go | 9 +- pkg/instrumentors/manager.go | 18 +- pkg/process/analyze.go | 122 +++++++------- pkg/process/ptrace/ptrace_linux_arm64.go | 4 +- pkg/process/ret_linux_amd64.go | 42 +++++ pkg/process/ret_linux_arm64.go | 41 +++++ test/e2e/gorillamux/traces.json | 28 ---- test/e2e/nethttp/traces.json | 21 --- 37 files changed, 1137 insertions(+), 300 deletions(-) rename pkg/instrumentors/bpf/github.com/gin-gonic/gin/{bpf_bpfel.go => bpf_bpfel_arm64.go} (93%) create mode 100644 pkg/instrumentors/bpf/github.com/gin-gonic/gin/bpf_bpfel_x86.go rename pkg/instrumentors/bpf/github.com/gorilla/mux/{bpf_bpfel.go => bpf_bpfel_arm64.go} (93%) create mode 100644 pkg/instrumentors/bpf/github.com/gorilla/mux/bpf_bpfel_x86.go rename pkg/instrumentors/bpf/google/golang/org/grpc/{bpf_bpfel.go => bpf_bpfel_arm64.go} (94%) create mode 100644 pkg/instrumentors/bpf/google/golang/org/grpc/bpf_bpfel_x86.go rename pkg/instrumentors/bpf/google/golang/org/grpc/server/{bpf_bpfel.go => bpf_bpfel_arm64.go} (78%) create mode 100644 pkg/instrumentors/bpf/google/golang/org/grpc/server/bpf_bpfel_x86.go rename pkg/instrumentors/bpf/net/http/server/{bpf_bpfel.go => bpf_bpfel_arm64.go} (93%) create mode 100644 pkg/instrumentors/bpf/net/http/server/bpf_bpfel_x86.go create mode 100644 pkg/process/ret_linux_amd64.go create mode 100644 pkg/process/ret_linux_arm64.go diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 1e832d264..7b61ea9c0 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -34,7 +34,7 @@ jobs: uses: actions/checkout@v3 - name: Build auto-instrumentation run: | - IMG=otel-go-instrumentation make docker-build + make docker-build offsets: runs-on: ubuntu-latest steps: diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index f609ec808..f681b89e0 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -38,4 +38,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/CHANGELOG.md b/CHANGELOG.md index 12194020f..677f6e38f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ OpenTelemetry Go Automatic Instrumentation adheres to [Semantic Versioning](http ### Added - Add [gin-gonic/gin](https://github.com/gin-gonic/gin) instrumentation. ([#100](https://github.com/open-telemetry/opentelemetry-go-instrumentation/pull/100)) +- Add ARM64 support. ([#82](https://github.com/open-telemetry/opentelemetry-go-instrumentation/pull/82)) ### Changed diff --git a/Dockerfile b/Dockerfile index e05778fba..6dbab74db 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,7 @@ FROM fedora:38 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 . . diff --git a/Makefile b/Makefile index 8ff1a189f..aafc4f1d2 100644 --- a/Makefile +++ b/Makefile @@ -10,7 +10,7 @@ OTEL_GO_MOD_DIRS := $(filter-out $(TOOLS_MOD_DIR), $(ALL_GO_MOD_DIRS)) # 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 .DEFAULT_GOAL := precommit @@ -30,6 +30,8 @@ $(TOOLS)/multimod: PACKAGE=go.opentelemetry.io/build-tools/multimod GOLICENSES = $(TOOLS)/go-licenses $(TOOLS)/go-licenses: PACKAGE=github.com/google/go-licenses +IMG_NAME ?= otel-go-instrumentation + GOLANGCI_LINT = $(TOOLS)/golangci-lint $(TOOLS)/golangci-lint: PACKAGE=github.com/golangci/golangci-lint/cmd/golangci-lint @@ -48,6 +50,7 @@ test/%: .PHONY: generate generate: export CFLAGS := $(BPF_INCLUDE) generate: go-mod-tidy +generate: go generate ./... .PHONY: go-mod-tidy @@ -68,11 +71,11 @@ golangci-lint/%: | $(GOLANGCI_LINT) .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: - docker build -t $(IMG) . + docker buildx build -t $(IMG_NAME) . .PHONY: offsets offsets: @@ -117,7 +120,7 @@ fixture-gorillamux: fixtures/gorillamux fixture-gin: fixtures/gin fixtures/%: LIBRARY=$* fixtures/%: - IMG=otel-go-instrumentation $(MAKE) docker-build + $(MAKE) docker-build cd test/e2e/$(LIBRARY) && docker build -t sample-app . kind create cluster kind load docker-image otel-go-instrumentation sample-app diff --git a/include/alloc.h b/include/alloc.h index 3fff24d8c..c494ed4d1 100644 --- a/include/alloc.h +++ b/include/alloc.h @@ -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..8d5307b04 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; } @@ -49,7 +50,7 @@ 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))); + bpf_probe_read(&ptr, sizeof(ptr), (void *)(PT_REGS_SP(ctx) + (index * 8))); return ptr; } @@ -63,8 +64,12 @@ void *get_argument(struct pt_regs *ctx, int index) return get_argument_by_stack(ctx, index); } -// In x86, current goroutine is pointed by r14, according to -// https://go.googlesource.com/go/+/refs/heads/dev.regabi/src/cmd/compile/internal-abi.md#amd64-architecture -inline void *get_goroutine_address(struct pt_regs *ctx) { - return (void *)(ctx->r14); +inline void *get_goroutine_address(struct pt_regs *ctx, int go_ctx_index) +{ + if (is_registers_abi) + { + return (void *)GOROUTINE(ctx); + } + + return get_argument_by_stack(ctx, go_ctx_index); } diff --git a/include/common.h b/include/common.h index 7316a0459..0eeb5ed59 100644 --- a/include/common.h +++ b/include/common.h @@ -84,41 +84,68 @@ 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 -{ +#if defined(__TARGET_ARCH_x86) +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; + long unsigned int r15; + long unsigned int r14; + long unsigned int r13; + long unsigned int r12; + long unsigned int bp; + long unsigned int bx; /* 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; + 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; /* * On syscall entry, this is syscall#. On CPU exception, this is error code. * On hw interrupt, it's IRQ number: */ - unsigned long orig_rax; + long unsigned int orig_ax; /* Return frame for iretq */ - unsigned long rip; - unsigned long cs; - unsigned long eflags; - unsigned long rsp; - unsigned long ss; + long unsigned int ip; + long unsigned int cs; + long unsigned int flags; + long unsigned int sp; + long unsigned int ss; /* top of stack page */ }; +#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..477ef6f3d 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/gin-gonic/gin/bpf/probe.bpf.c b/pkg/instrumentors/bpf/github.com/gin-gonic/gin/bpf/probe.bpf.c index 9ee1fe7b6..0c595e789 100644 --- a/pkg/instrumentors/bpf/github.com/gin-gonic/gin/bpf/probe.bpf.c +++ b/pkg/instrumentors/bpf/github.com/gin-gonic/gin/bpf/probe.bpf.c @@ -46,6 +46,7 @@ struct { volatile const u64 method_ptr_pos; volatile const u64 url_ptr_pos; volatile const u64 path_ptr_pos; +volatile const u64 ctx_ptr_pos; // This instrumentation attaches uprobe to the following function: // func (engine *Engine) ServeHTTP(w http.ResponseWriter, r *http.Request) @@ -79,21 +80,18 @@ int uprobe_GinEngine_ServeHTTP(struct pt_regs *ctx) { bpf_probe_read(&httpReq.path, path_size, path_ptr); // Get goroutine pointer - void *goroutine = get_goroutine_address(ctx); + void *goroutine = get_goroutine_address(ctx, ctx_ptr_pos); // Write event httpReq.sc = generate_span_context(); bpf_map_update_elem(&context_to_http_events, &goroutine, &httpReq, 0); - long res = bpf_map_update_elem(&spans_in_progress, &goroutine, &httpReq.sc, 0); + bpf_map_update_elem(&spans_in_progress, &goroutine, &httpReq.sc, 0); return 0; } SEC("uprobe/GinEngine_ServeHTTP") int uprobe_GinEngine_ServeHTTP_Returns(struct pt_regs *ctx) { - u64 request_pos = 4; - void *req_ptr = get_argument(ctx, request_pos); - void *goroutine = get_goroutine_address(ctx); - + void *goroutine = get_goroutine_address(ctx, ctx_ptr_pos); void *httpReq_ptr = bpf_map_lookup_elem(&context_to_http_events, &goroutine); struct http_request_t httpReq = {}; bpf_probe_read(&httpReq, sizeof(httpReq), httpReq_ptr); diff --git a/pkg/instrumentors/bpf/github.com/gin-gonic/gin/bpf_bpfel.go b/pkg/instrumentors/bpf/github.com/gin-gonic/gin/bpf_bpfel_arm64.go similarity index 93% rename from pkg/instrumentors/bpf/github.com/gin-gonic/gin/bpf_bpfel.go rename to pkg/instrumentors/bpf/github.com/gin-gonic/gin/bpf_bpfel_arm64.go index 2a54a5515..be4cd2a39 100644 --- a/pkg/instrumentors/bpf/github.com/gin-gonic/gin/bpf_bpfel.go +++ b/pkg/instrumentors/bpf/github.com/gin-gonic/gin/bpf_bpfel_arm64.go @@ -1,6 +1,6 @@ // Code generated by bpf2go; DO NOT EDIT. -//go:build 386 || amd64 || amd64p32 || arm || arm64 || mips64le || mips64p32le || mipsle || ppc64le || riscv64 -// +build 386 amd64 amd64p32 arm arm64 mips64le mips64p32le mipsle ppc64le riscv64 +//go:build arm64 +// +build arm64 package gin @@ -139,5 +139,5 @@ func _BpfClose(closers ...io.Closer) error { // Do not access this directly. // -//go:embed bpf_bpfel.o +//go:embed bpf_bpfel_arm64.o var _BpfBytes []byte diff --git a/pkg/instrumentors/bpf/github.com/gin-gonic/gin/bpf_bpfel_x86.go b/pkg/instrumentors/bpf/github.com/gin-gonic/gin/bpf_bpfel_x86.go new file mode 100644 index 000000000..d57a435b0 --- /dev/null +++ b/pkg/instrumentors/bpf/github.com/gin-gonic/gin/bpf_bpfel_x86.go @@ -0,0 +1,143 @@ +// Code generated by bpf2go; DO NOT EDIT. +//go:build 386 || amd64 +// +build 386 amd64 + +package gin + +import ( + "bytes" + _ "embed" + "fmt" + "io" + + "github.com/cilium/ebpf" +) + +type bpfHttpRequestT struct { + StartTime uint64 + EndTime uint64 + Method [6]int8 + Path [100]int8 + Sc bpfSpanContext + _ [6]byte +} + +type bpfSpanContext struct { + TraceID [16]uint8 + SpanID [8]uint8 +} + +// loadBpf returns the embedded CollectionSpec for bpf. +func loadBpf() (*ebpf.CollectionSpec, error) { + reader := bytes.NewReader(_BpfBytes) + spec, err := ebpf.LoadCollectionSpecFromReader(reader) + if err != nil { + return nil, fmt.Errorf("can't load bpf: %w", err) + } + + return spec, err +} + +// loadBpfObjects loads bpf and converts it into a struct. +// +// The following types are suitable as obj argument: +// +// *bpfObjects +// *bpfPrograms +// *bpfMaps +// +// See ebpf.CollectionSpec.LoadAndAssign documentation for details. +func loadBpfObjects(obj interface{}, opts *ebpf.CollectionOptions) error { + spec, err := loadBpf() + if err != nil { + return err + } + + return spec.LoadAndAssign(obj, opts) +} + +// bpfSpecs contains maps and programs before they are loaded into the kernel. +// +// It can be passed ebpf.CollectionSpec.Assign. +type bpfSpecs struct { + bpfProgramSpecs + bpfMapSpecs +} + +// bpfSpecs contains programs before they are loaded into the kernel. +// +// It can be passed ebpf.CollectionSpec.Assign. +type bpfProgramSpecs struct { + UprobeGinEngineServeHTTP *ebpf.ProgramSpec `ebpf:"uprobe_GinEngine_ServeHTTP"` + UprobeGinEngineServeHTTP_Returns *ebpf.ProgramSpec `ebpf:"uprobe_GinEngine_ServeHTTP_Returns"` +} + +// bpfMapSpecs contains maps before they are loaded into the kernel. +// +// It can be passed ebpf.CollectionSpec.Assign. +type bpfMapSpecs struct { + ContextToHttpEvents *ebpf.MapSpec `ebpf:"context_to_http_events"` + Events *ebpf.MapSpec `ebpf:"events"` + SpansInProgress *ebpf.MapSpec `ebpf:"spans_in_progress"` +} + +// bpfObjects contains all objects after they have been loaded into the kernel. +// +// It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign. +type bpfObjects struct { + bpfPrograms + bpfMaps +} + +func (o *bpfObjects) Close() error { + return _BpfClose( + &o.bpfPrograms, + &o.bpfMaps, + ) +} + +// bpfMaps contains all maps after they have been loaded into the kernel. +// +// It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign. +type bpfMaps struct { + ContextToHttpEvents *ebpf.Map `ebpf:"context_to_http_events"` + Events *ebpf.Map `ebpf:"events"` + SpansInProgress *ebpf.Map `ebpf:"spans_in_progress"` +} + +func (m *bpfMaps) Close() error { + return _BpfClose( + m.ContextToHttpEvents, + m.Events, + m.SpansInProgress, + ) +} + +// bpfPrograms contains all programs after they have been loaded into the kernel. +// +// It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign. +type bpfPrograms struct { + UprobeGinEngineServeHTTP *ebpf.Program `ebpf:"uprobe_GinEngine_ServeHTTP"` + UprobeGinEngineServeHTTP_Returns *ebpf.Program `ebpf:"uprobe_GinEngine_ServeHTTP_Returns"` +} + +func (p *bpfPrograms) Close() error { + return _BpfClose( + p.UprobeGinEngineServeHTTP, + p.UprobeGinEngineServeHTTP_Returns, + ) +} + +func _BpfClose(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 bpf_bpfel_x86.o +var _BpfBytes []byte diff --git a/pkg/instrumentors/bpf/github.com/gin-gonic/gin/probe.go b/pkg/instrumentors/bpf/github.com/gin-gonic/gin/probe.go index dbe818beb..301a77af3 100644 --- a/pkg/instrumentors/bpf/github.com/gin-gonic/gin/probe.go +++ b/pkg/instrumentors/bpf/github.com/gin-gonic/gin/probe.go @@ -36,7 +36,7 @@ import ( "go.opentelemetry.io/otel/trace" ) -//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 amd64,arm64 -cc clang -cflags $CFLAGS bpf ./bpf/probe.bpf.c // Event represents an event in the gin-gonic/gin server during an HTTP // request-response. @@ -85,6 +85,11 @@ func (h *Instrumentor) Load(ctx *context.InstrumentorContext) error { StructName: "net/http.Request", Field: "URL", }, + { + VarName: "ctx_ptr_pos", + StructName: "net/http.Request", + Field: "ctx", + }, { VarName: "path_ptr_pos", StructName: "net/url.URL", diff --git a/pkg/instrumentors/bpf/github.com/gorilla/mux/bpf/probe.bpf.c b/pkg/instrumentors/bpf/github.com/gorilla/mux/bpf/probe.bpf.c index 275510684..f4a1d4c16 100644 --- a/pkg/instrumentors/bpf/github.com/gorilla/mux/bpf/probe.bpf.c +++ b/pkg/instrumentors/bpf/github.com/gorilla/mux/bpf/probe.bpf.c @@ -18,26 +18,28 @@ char __license[] SEC("license") = "Dual MIT/GPL"; -#define MAX_SIZE 100 +#define PATH_MAX_LEN 100 +#define METHOD_MAX_LEN 6 // Longer method: DELETE #define MAX_CONCURRENT 50 struct http_request_t { u64 start_time; u64 end_time; - char method[MAX_SIZE]; - char path[MAX_SIZE]; + char method[METHOD_MAX_LEN]; + char path[PATH_MAX_LEN]; struct span_context sc; }; +// map key: pointer to the goroutine that handles the request struct { - __uint(type, BPF_MAP_TYPE_HASH); - __type(key, void*); - __type(value, struct http_request_t); - __uint(max_entries, MAX_CONCURRENT); + __uint(type, BPF_MAP_TYPE_HASH); + __type(key, void *); + __type(value, struct http_request_t); + __uint(max_entries, MAX_CONCURRENT); } context_to_http_events SEC(".maps"); struct { - __uint(type, BPF_MAP_TYPE_PERF_EVENT_ARRAY); + __uint(type, BPF_MAP_TYPE_PERF_EVENT_ARRAY); } events SEC(".maps"); // Injected in init @@ -55,52 +57,47 @@ int uprobe_GorillaMux_ServeHTTP(struct pt_regs *ctx) { httpReq.start_time = bpf_ktime_get_ns(); // Get request struct - void* req_ptr = get_argument(ctx, request_pos); + void *req_ptr = get_argument(ctx, request_pos); // Get method from request - void* method_ptr = 0; - bpf_probe_read(&method_ptr, sizeof(method_ptr), (void *)(req_ptr+method_ptr_pos)); + void *method_ptr = 0; + bpf_probe_read(&method_ptr, sizeof(method_ptr), (void *)(req_ptr + method_ptr_pos)); u64 method_len = 0; - bpf_probe_read(&method_len, sizeof(method_len), (void *)(req_ptr+(method_ptr_pos+8))); + bpf_probe_read(&method_len, sizeof(method_len), (void *)(req_ptr + (method_ptr_pos + 8))); u64 method_size = sizeof(httpReq.method); method_size = method_size < method_len ? method_size : method_len; bpf_probe_read(&httpReq.method, method_size, method_ptr); // get path from Request.URL void *url_ptr = 0; - bpf_probe_read(&url_ptr, sizeof(url_ptr), (void *)(req_ptr+url_ptr_pos)); - void* path_ptr = 0; - bpf_probe_read(&path_ptr, sizeof(path_ptr), (void *)(url_ptr+path_ptr_pos)); + bpf_probe_read(&url_ptr, sizeof(url_ptr), (void *)(req_ptr + url_ptr_pos)); + void *path_ptr = 0; + bpf_probe_read(&path_ptr, sizeof(path_ptr), (void *)(url_ptr + path_ptr_pos)); u64 path_len = 0; - bpf_probe_read(&path_len, sizeof(path_len), (void *)(url_ptr+(path_ptr_pos+8))); + bpf_probe_read(&path_len, sizeof(path_len), (void *)(url_ptr + (path_ptr_pos + 8))); u64 path_size = sizeof(httpReq.path); path_size = path_size < path_len ? path_size : path_len; bpf_probe_read(&httpReq.path, path_size, path_ptr); - // Get Request.ctx - void *ctx_iface = 0; - bpf_probe_read(&ctx_iface, sizeof(ctx_iface), (void *)(req_ptr+ctx_ptr_pos+8)); + // Get goroutine pointer + void *goroutine = get_goroutine_address(ctx, ctx_ptr_pos); // Write event httpReq.sc = generate_span_context(); - bpf_map_update_elem(&context_to_http_events, &ctx_iface, &httpReq, 0); - long res = bpf_map_update_elem(&spans_in_progress, &ctx_iface, &httpReq.sc, 0); + bpf_map_update_elem(&context_to_http_events, &goroutine, &httpReq, 0); + bpf_map_update_elem(&spans_in_progress, &goroutine, &httpReq.sc, 0); return 0; } SEC("uprobe/GorillaMux_ServeHTTP") int uprobe_GorillaMux_ServeHTTP_Returns(struct pt_regs *ctx) { - u64 request_pos = 4; - void* req_ptr = get_argument(ctx, request_pos); - void *ctx_iface = 0; - bpf_probe_read(&ctx_iface, sizeof(ctx_iface), (void *)(req_ptr+ctx_ptr_pos+8)); - - void* httpReq_ptr = bpf_map_lookup_elem(&context_to_http_events, &ctx_iface); + void *goroutine = get_goroutine_address(ctx, ctx_ptr_pos); + void *httpReq_ptr = bpf_map_lookup_elem(&context_to_http_events, &goroutine); struct http_request_t httpReq = {}; bpf_probe_read(&httpReq, sizeof(httpReq), httpReq_ptr); httpReq.end_time = bpf_ktime_get_ns(); bpf_perf_event_output(ctx, &events, BPF_F_CURRENT_CPU, &httpReq, sizeof(httpReq)); - bpf_map_delete_elem(&context_to_http_events, &ctx_iface); - bpf_map_delete_elem(&spans_in_progress, &ctx_iface); + bpf_map_delete_elem(&context_to_http_events, &goroutine); + bpf_map_delete_elem(&spans_in_progress, &goroutine); return 0; } \ No newline at end of file diff --git a/pkg/instrumentors/bpf/github.com/gorilla/mux/bpf_bpfel.go b/pkg/instrumentors/bpf/github.com/gorilla/mux/bpf_bpfel_arm64.go similarity index 93% rename from pkg/instrumentors/bpf/github.com/gorilla/mux/bpf_bpfel.go rename to pkg/instrumentors/bpf/github.com/gorilla/mux/bpf_bpfel_arm64.go index 56b9a71ee..3d8c80d1d 100644 --- a/pkg/instrumentors/bpf/github.com/gorilla/mux/bpf_bpfel.go +++ b/pkg/instrumentors/bpf/github.com/gorilla/mux/bpf_bpfel_arm64.go @@ -1,6 +1,6 @@ // Code generated by bpf2go; DO NOT EDIT. -//go:build 386 || amd64 || amd64p32 || arm || arm64 || mips64le || mips64p32le || mipsle || ppc64le || riscv64 -// +build 386 amd64 amd64p32 arm arm64 mips64le mips64p32le mipsle ppc64le riscv64 +//go:build arm64 +// +build arm64 package mux @@ -16,9 +16,10 @@ import ( type bpfHttpRequestT struct { StartTime uint64 EndTime uint64 - Method [100]int8 + Method [6]int8 Path [100]int8 Sc bpfSpanContext + _ [6]byte } type bpfSpanContext struct { @@ -138,5 +139,5 @@ func _BpfClose(closers ...io.Closer) error { // Do not access this directly. // -//go:embed bpf_bpfel.o +//go:embed bpf_bpfel_arm64.o var _BpfBytes []byte diff --git a/pkg/instrumentors/bpf/github.com/gorilla/mux/bpf_bpfel_x86.go b/pkg/instrumentors/bpf/github.com/gorilla/mux/bpf_bpfel_x86.go new file mode 100644 index 000000000..50f832a31 --- /dev/null +++ b/pkg/instrumentors/bpf/github.com/gorilla/mux/bpf_bpfel_x86.go @@ -0,0 +1,143 @@ +// Code generated by bpf2go; DO NOT EDIT. +//go:build 386 || amd64 +// +build 386 amd64 + +package mux + +import ( + "bytes" + _ "embed" + "fmt" + "io" + + "github.com/cilium/ebpf" +) + +type bpfHttpRequestT struct { + StartTime uint64 + EndTime uint64 + Method [6]int8 + Path [100]int8 + Sc bpfSpanContext + _ [6]byte +} + +type bpfSpanContext struct { + TraceID [16]uint8 + SpanID [8]uint8 +} + +// loadBpf returns the embedded CollectionSpec for bpf. +func loadBpf() (*ebpf.CollectionSpec, error) { + reader := bytes.NewReader(_BpfBytes) + spec, err := ebpf.LoadCollectionSpecFromReader(reader) + if err != nil { + return nil, fmt.Errorf("can't load bpf: %w", err) + } + + return spec, err +} + +// loadBpfObjects loads bpf and converts it into a struct. +// +// The following types are suitable as obj argument: +// +// *bpfObjects +// *bpfPrograms +// *bpfMaps +// +// See ebpf.CollectionSpec.LoadAndAssign documentation for details. +func loadBpfObjects(obj interface{}, opts *ebpf.CollectionOptions) error { + spec, err := loadBpf() + if err != nil { + return err + } + + return spec.LoadAndAssign(obj, opts) +} + +// bpfSpecs contains maps and programs before they are loaded into the kernel. +// +// It can be passed ebpf.CollectionSpec.Assign. +type bpfSpecs struct { + bpfProgramSpecs + bpfMapSpecs +} + +// bpfSpecs contains programs before they are loaded into the kernel. +// +// It can be passed ebpf.CollectionSpec.Assign. +type bpfProgramSpecs struct { + UprobeGorillaMuxServeHTTP *ebpf.ProgramSpec `ebpf:"uprobe_GorillaMux_ServeHTTP"` + UprobeGorillaMuxServeHTTP_Returns *ebpf.ProgramSpec `ebpf:"uprobe_GorillaMux_ServeHTTP_Returns"` +} + +// bpfMapSpecs contains maps before they are loaded into the kernel. +// +// It can be passed ebpf.CollectionSpec.Assign. +type bpfMapSpecs struct { + ContextToHttpEvents *ebpf.MapSpec `ebpf:"context_to_http_events"` + Events *ebpf.MapSpec `ebpf:"events"` + SpansInProgress *ebpf.MapSpec `ebpf:"spans_in_progress"` +} + +// bpfObjects contains all objects after they have been loaded into the kernel. +// +// It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign. +type bpfObjects struct { + bpfPrograms + bpfMaps +} + +func (o *bpfObjects) Close() error { + return _BpfClose( + &o.bpfPrograms, + &o.bpfMaps, + ) +} + +// bpfMaps contains all maps after they have been loaded into the kernel. +// +// It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign. +type bpfMaps struct { + ContextToHttpEvents *ebpf.Map `ebpf:"context_to_http_events"` + Events *ebpf.Map `ebpf:"events"` + SpansInProgress *ebpf.Map `ebpf:"spans_in_progress"` +} + +func (m *bpfMaps) Close() error { + return _BpfClose( + m.ContextToHttpEvents, + m.Events, + m.SpansInProgress, + ) +} + +// bpfPrograms contains all programs after they have been loaded into the kernel. +// +// It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign. +type bpfPrograms struct { + UprobeGorillaMuxServeHTTP *ebpf.Program `ebpf:"uprobe_GorillaMux_ServeHTTP"` + UprobeGorillaMuxServeHTTP_Returns *ebpf.Program `ebpf:"uprobe_GorillaMux_ServeHTTP_Returns"` +} + +func (p *bpfPrograms) Close() error { + return _BpfClose( + p.UprobeGorillaMuxServeHTTP, + p.UprobeGorillaMuxServeHTTP_Returns, + ) +} + +func _BpfClose(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 bpf_bpfel_x86.o +var _BpfBytes []byte diff --git a/pkg/instrumentors/bpf/github.com/gorilla/mux/probe.go b/pkg/instrumentors/bpf/github.com/gorilla/mux/probe.go index e0e326639..249934adc 100644 --- a/pkg/instrumentors/bpf/github.com/gorilla/mux/probe.go +++ b/pkg/instrumentors/bpf/github.com/gorilla/mux/probe.go @@ -36,7 +36,7 @@ import ( "go.opentelemetry.io/otel/trace" ) -//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 amd64,arm64 -cc clang -cflags $CFLAGS bpf ./bpf/probe.bpf.c // Event represents an event in the gorilla/mux server during an HTTP // request-response. 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..e56880266 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/bpf_bpfel.go b/pkg/instrumentors/bpf/google/golang/org/grpc/bpf_bpfel_arm64.go similarity index 94% rename from pkg/instrumentors/bpf/google/golang/org/grpc/bpf_bpfel.go rename to pkg/instrumentors/bpf/google/golang/org/grpc/bpf_bpfel_arm64.go index dd9bd7f22..5a120d29a 100644 --- a/pkg/instrumentors/bpf/google/golang/org/grpc/bpf_bpfel.go +++ b/pkg/instrumentors/bpf/google/golang/org/grpc/bpf_bpfel_arm64.go @@ -1,6 +1,6 @@ // Code generated by bpf2go; DO NOT EDIT. -//go:build 386 || amd64 || amd64p32 || arm || arm64 || mips64le || mips64p32le || mipsle || ppc64le || riscv64 -// +build 386 amd64 amd64p32 arm arm64 mips64le mips64p32le mipsle ppc64le riscv64 +//go:build arm64 +// +build arm64 package grpc @@ -151,5 +151,5 @@ func _BpfClose(closers ...io.Closer) error { // Do not access this directly. // -//go:embed bpf_bpfel.o +//go:embed bpf_bpfel_arm64.o var _BpfBytes []byte diff --git a/pkg/instrumentors/bpf/google/golang/org/grpc/bpf_bpfel_x86.go b/pkg/instrumentors/bpf/google/golang/org/grpc/bpf_bpfel_x86.go new file mode 100644 index 000000000..3e4760666 --- /dev/null +++ b/pkg/instrumentors/bpf/google/golang/org/grpc/bpf_bpfel_x86.go @@ -0,0 +1,155 @@ +// Code generated by bpf2go; DO NOT EDIT. +//go:build 386 || amd64 +// +build 386 amd64 + +package grpc + +import ( + "bytes" + _ "embed" + "fmt" + "io" + + "github.com/cilium/ebpf" +) + +type bpfGrpcRequestT struct { + StartTime uint64 + EndTime uint64 + Method [50]int8 + Target [50]int8 + Sc bpfSpanContext + Psc bpfSpanContext + _ [4]byte +} + +type bpfHeadersBuff struct{ Buff [500]uint8 } + +type bpfSpanContext struct { + TraceID [16]uint8 + SpanID [8]uint8 +} + +// loadBpf returns the embedded CollectionSpec for bpf. +func loadBpf() (*ebpf.CollectionSpec, error) { + reader := bytes.NewReader(_BpfBytes) + spec, err := ebpf.LoadCollectionSpecFromReader(reader) + if err != nil { + return nil, fmt.Errorf("can't load bpf: %w", err) + } + + return spec, err +} + +// loadBpfObjects loads bpf and converts it into a struct. +// +// The following types are suitable as obj argument: +// +// *bpfObjects +// *bpfPrograms +// *bpfMaps +// +// See ebpf.CollectionSpec.LoadAndAssign documentation for details. +func loadBpfObjects(obj interface{}, opts *ebpf.CollectionOptions) error { + spec, err := loadBpf() + if err != nil { + return err + } + + return spec.LoadAndAssign(obj, opts) +} + +// bpfSpecs contains maps and programs before they are loaded into the kernel. +// +// It can be passed ebpf.CollectionSpec.Assign. +type bpfSpecs struct { + bpfProgramSpecs + bpfMapSpecs +} + +// bpfSpecs contains programs before they are loaded into the kernel. +// +// It can be passed ebpf.CollectionSpec.Assign. +type bpfProgramSpecs struct { + UprobeClientConnInvoke *ebpf.ProgramSpec `ebpf:"uprobe_ClientConn_Invoke"` + UprobeClientConnInvokeReturns *ebpf.ProgramSpec `ebpf:"uprobe_ClientConn_Invoke_Returns"` + UprobeHttp2ClientCreateHeaderFields *ebpf.ProgramSpec `ebpf:"uprobe_Http2Client_CreateHeaderFields"` +} + +// bpfMapSpecs contains maps before they are loaded into the kernel. +// +// It can be passed ebpf.CollectionSpec.Assign. +type bpfMapSpecs struct { + AllocMap *ebpf.MapSpec `ebpf:"alloc_map"` + ContextToGrpcEvents *ebpf.MapSpec `ebpf:"context_to_grpc_events"` + Events *ebpf.MapSpec `ebpf:"events"` + HeadersBuffMap *ebpf.MapSpec `ebpf:"headers_buff_map"` + SpansInProgress *ebpf.MapSpec `ebpf:"spans_in_progress"` +} + +// bpfObjects contains all objects after they have been loaded into the kernel. +// +// It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign. +type bpfObjects struct { + bpfPrograms + bpfMaps +} + +func (o *bpfObjects) Close() error { + return _BpfClose( + &o.bpfPrograms, + &o.bpfMaps, + ) +} + +// bpfMaps contains all maps after they have been loaded into the kernel. +// +// It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign. +type bpfMaps struct { + AllocMap *ebpf.Map `ebpf:"alloc_map"` + ContextToGrpcEvents *ebpf.Map `ebpf:"context_to_grpc_events"` + Events *ebpf.Map `ebpf:"events"` + HeadersBuffMap *ebpf.Map `ebpf:"headers_buff_map"` + SpansInProgress *ebpf.Map `ebpf:"spans_in_progress"` +} + +func (m *bpfMaps) Close() error { + return _BpfClose( + m.AllocMap, + m.ContextToGrpcEvents, + m.Events, + m.HeadersBuffMap, + m.SpansInProgress, + ) +} + +// bpfPrograms contains all programs after they have been loaded into the kernel. +// +// It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign. +type bpfPrograms struct { + UprobeClientConnInvoke *ebpf.Program `ebpf:"uprobe_ClientConn_Invoke"` + UprobeClientConnInvokeReturns *ebpf.Program `ebpf:"uprobe_ClientConn_Invoke_Returns"` + UprobeHttp2ClientCreateHeaderFields *ebpf.Program `ebpf:"uprobe_Http2Client_CreateHeaderFields"` +} + +func (p *bpfPrograms) Close() error { + return _BpfClose( + p.UprobeClientConnInvoke, + p.UprobeClientConnInvokeReturns, + p.UprobeHttp2ClientCreateHeaderFields, + ) +} + +func _BpfClose(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 bpf_bpfel_x86.o +var _BpfBytes []byte diff --git a/pkg/instrumentors/bpf/google/golang/org/grpc/probe.go b/pkg/instrumentors/bpf/google/golang/org/grpc/probe.go index db86392e6..fa13540fe 100644 --- a/pkg/instrumentors/bpf/google/golang/org/grpc/probe.go +++ b/pkg/instrumentors/bpf/google/golang/org/grpc/probe.go @@ -38,7 +38,7 @@ import ( "go.opentelemetry.io/otel/trace" ) -//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 amd64,arm64 -cc clang -cflags $CFLAGS bpf ./bpf/probe.bpf.c // Event represents an event in the gRPC client during a gRPC request. type Event struct { 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/bpf_bpfel.go b/pkg/instrumentors/bpf/google/golang/org/grpc/server/bpf_bpfel_arm64.go similarity index 78% rename from pkg/instrumentors/bpf/google/golang/org/grpc/server/bpf_bpfel.go rename to pkg/instrumentors/bpf/google/golang/org/grpc/server/bpf_bpfel_arm64.go index 839c506ff..dd91b8731 100644 --- a/pkg/instrumentors/bpf/google/golang/org/grpc/server/bpf_bpfel.go +++ b/pkg/instrumentors/bpf/google/golang/org/grpc/server/bpf_bpfel_arm64.go @@ -1,6 +1,6 @@ // Code generated by bpf2go; DO NOT EDIT. -//go:build 386 || amd64 || amd64p32 || arm || arm64 || mips64le || mips64p32le || mipsle || ppc64le || riscv64 -// +build 386 amd64 amd64p32 arm arm64 mips64le mips64p32le mipsle ppc64le riscv64 +//go:build arm64 +// +build arm64 package server @@ -68,10 +68,9 @@ type bpfSpecs struct { // // It can be passed ebpf.CollectionSpec.Assign. type bpfProgramSpecs struct { - UprobeDecodeStateDecodeHeader *ebpf.ProgramSpec `ebpf:"uprobe_decodeState_decodeHeader"` - UprobeServerHandleStream *ebpf.ProgramSpec `ebpf:"uprobe_server_handleStream"` - UprobeServerHandleStreamByRegisters *ebpf.ProgramSpec `ebpf:"uprobe_server_handleStream_ByRegisters"` - UprobeServerHandleStreamReturns *ebpf.ProgramSpec `ebpf:"uprobe_server_handleStream_Returns"` + UprobeDecodeStateDecodeHeader *ebpf.ProgramSpec `ebpf:"uprobe_decodeState_decodeHeader"` + UprobeServerHandleStream *ebpf.ProgramSpec `ebpf:"uprobe_server_handleStream"` + UprobeServerHandleStreamReturns *ebpf.ProgramSpec `ebpf:"uprobe_server_handleStream_Returns"` } // bpfMapSpecs contains maps before they are loaded into the kernel. @@ -125,17 +124,15 @@ func (m *bpfMaps) Close() error { // // It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign. type bpfPrograms struct { - UprobeDecodeStateDecodeHeader *ebpf.Program `ebpf:"uprobe_decodeState_decodeHeader"` - UprobeServerHandleStream *ebpf.Program `ebpf:"uprobe_server_handleStream"` - UprobeServerHandleStreamByRegisters *ebpf.Program `ebpf:"uprobe_server_handleStream_ByRegisters"` - UprobeServerHandleStreamReturns *ebpf.Program `ebpf:"uprobe_server_handleStream_Returns"` + UprobeDecodeStateDecodeHeader *ebpf.Program `ebpf:"uprobe_decodeState_decodeHeader"` + UprobeServerHandleStream *ebpf.Program `ebpf:"uprobe_server_handleStream"` + UprobeServerHandleStreamReturns *ebpf.Program `ebpf:"uprobe_server_handleStream_Returns"` } func (p *bpfPrograms) Close() error { return _BpfClose( p.UprobeDecodeStateDecodeHeader, p.UprobeServerHandleStream, - p.UprobeServerHandleStreamByRegisters, p.UprobeServerHandleStreamReturns, ) } @@ -151,5 +148,5 @@ func _BpfClose(closers ...io.Closer) error { // Do not access this directly. // -//go:embed bpf_bpfel.o +//go:embed bpf_bpfel_arm64.o var _BpfBytes []byte diff --git a/pkg/instrumentors/bpf/google/golang/org/grpc/server/bpf_bpfel_x86.go b/pkg/instrumentors/bpf/google/golang/org/grpc/server/bpf_bpfel_x86.go new file mode 100644 index 000000000..3c8e878fb --- /dev/null +++ b/pkg/instrumentors/bpf/google/golang/org/grpc/server/bpf_bpfel_x86.go @@ -0,0 +1,152 @@ +// Code generated by bpf2go; DO NOT EDIT. +//go:build 386 || amd64 +// +build 386 amd64 + +package server + +import ( + "bytes" + _ "embed" + "fmt" + "io" + + "github.com/cilium/ebpf" +) + +type bpfGrpcRequestT struct { + StartTime uint64 + EndTime uint64 + Method [100]int8 + Sc bpfSpanContext + Psc bpfSpanContext + _ [4]byte +} + +type bpfSpanContext struct { + TraceID [16]uint8 + SpanID [8]uint8 +} + +// loadBpf returns the embedded CollectionSpec for bpf. +func loadBpf() (*ebpf.CollectionSpec, error) { + reader := bytes.NewReader(_BpfBytes) + spec, err := ebpf.LoadCollectionSpecFromReader(reader) + if err != nil { + return nil, fmt.Errorf("can't load bpf: %w", err) + } + + return spec, err +} + +// loadBpfObjects loads bpf and converts it into a struct. +// +// The following types are suitable as obj argument: +// +// *bpfObjects +// *bpfPrograms +// *bpfMaps +// +// See ebpf.CollectionSpec.LoadAndAssign documentation for details. +func loadBpfObjects(obj interface{}, opts *ebpf.CollectionOptions) error { + spec, err := loadBpf() + if err != nil { + return err + } + + return spec.LoadAndAssign(obj, opts) +} + +// bpfSpecs contains maps and programs before they are loaded into the kernel. +// +// It can be passed ebpf.CollectionSpec.Assign. +type bpfSpecs struct { + bpfProgramSpecs + bpfMapSpecs +} + +// bpfSpecs contains programs before they are loaded into the kernel. +// +// It can be passed ebpf.CollectionSpec.Assign. +type bpfProgramSpecs struct { + UprobeDecodeStateDecodeHeader *ebpf.ProgramSpec `ebpf:"uprobe_decodeState_decodeHeader"` + UprobeServerHandleStream *ebpf.ProgramSpec `ebpf:"uprobe_server_handleStream"` + UprobeServerHandleStreamReturns *ebpf.ProgramSpec `ebpf:"uprobe_server_handleStream_Returns"` +} + +// bpfMapSpecs contains maps before they are loaded into the kernel. +// +// It can be passed ebpf.CollectionSpec.Assign. +type bpfMapSpecs struct { + AllocMap *ebpf.MapSpec `ebpf:"alloc_map"` + ContextToGrpcEvents *ebpf.MapSpec `ebpf:"context_to_grpc_events"` + Events *ebpf.MapSpec `ebpf:"events"` + SpansInProgress *ebpf.MapSpec `ebpf:"spans_in_progress"` + StreamidToGrpcEvents *ebpf.MapSpec `ebpf:"streamid_to_grpc_events"` +} + +// bpfObjects contains all objects after they have been loaded into the kernel. +// +// It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign. +type bpfObjects struct { + bpfPrograms + bpfMaps +} + +func (o *bpfObjects) Close() error { + return _BpfClose( + &o.bpfPrograms, + &o.bpfMaps, + ) +} + +// bpfMaps contains all maps after they have been loaded into the kernel. +// +// It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign. +type bpfMaps struct { + AllocMap *ebpf.Map `ebpf:"alloc_map"` + ContextToGrpcEvents *ebpf.Map `ebpf:"context_to_grpc_events"` + Events *ebpf.Map `ebpf:"events"` + SpansInProgress *ebpf.Map `ebpf:"spans_in_progress"` + StreamidToGrpcEvents *ebpf.Map `ebpf:"streamid_to_grpc_events"` +} + +func (m *bpfMaps) Close() error { + return _BpfClose( + m.AllocMap, + m.ContextToGrpcEvents, + m.Events, + m.SpansInProgress, + m.StreamidToGrpcEvents, + ) +} + +// bpfPrograms contains all programs after they have been loaded into the kernel. +// +// It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign. +type bpfPrograms struct { + UprobeDecodeStateDecodeHeader *ebpf.Program `ebpf:"uprobe_decodeState_decodeHeader"` + UprobeServerHandleStream *ebpf.Program `ebpf:"uprobe_server_handleStream"` + UprobeServerHandleStreamReturns *ebpf.Program `ebpf:"uprobe_server_handleStream_Returns"` +} + +func (p *bpfPrograms) Close() error { + return _BpfClose( + p.UprobeDecodeStateDecodeHeader, + p.UprobeServerHandleStream, + p.UprobeServerHandleStreamReturns, + ) +} + +func _BpfClose(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 bpf_bpfel_x86.o +var _BpfBytes []byte 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 a0591346e..78f1491b5 100644 --- a/pkg/instrumentors/bpf/google/golang/org/grpc/server/probe.go +++ b/pkg/instrumentors/bpf/google/golang/org/grpc/server/probe.go @@ -36,7 +36,7 @@ import ( "go.opentelemetry.io/otel/trace" ) -//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 amd64,arm64 -cc clang -cflags $CFLAGS bpf ./bpf/probe.bpf.c // Event represents an event in the gRPC server during a gRPC request. type Event struct { @@ -70,7 +70,7 @@ func (g *Instrumentor) LibraryName() string { // instrumented. func (g *Instrumentor) 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"} } // Load loads all instrumentation offsets. @@ -127,15 +127,8 @@ func (g *Instrumentor) 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/bpf/probe.bpf.c b/pkg/instrumentors/bpf/net/http/server/bpf/probe.bpf.c index b49802cfe..bb724666b 100644 --- a/pkg/instrumentors/bpf/net/http/server/bpf/probe.bpf.c +++ b/pkg/instrumentors/bpf/net/http/server/bpf/probe.bpf.c @@ -49,6 +49,7 @@ struct volatile const u64 method_ptr_pos; volatile const u64 url_ptr_pos; volatile const u64 path_ptr_pos; +volatile const u64 ctx_ptr_pos; // This instrumentation attaches uprobe to the following function: // func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) @@ -83,22 +84,19 @@ int uprobe_ServerMux_ServeHTTP(struct pt_regs *ctx) bpf_probe_read(&httpReq.path, path_size, path_ptr); // Get goroutine pointer - void *goroutine = get_goroutine_address(ctx); + void *goroutine = get_goroutine_address(ctx, ctx_ptr_pos); // Write event httpReq.sc = generate_span_context(); bpf_map_update_elem(&context_to_http_events, &goroutine, &httpReq, 0); - long res = bpf_map_update_elem(&spans_in_progress, &goroutine, &httpReq.sc, 0); + bpf_map_update_elem(&spans_in_progress, &goroutine, &httpReq.sc, 0); return 0; } SEC("uprobe/ServerMux_ServeHTTP") int uprobe_ServerMux_ServeHTTP_Returns(struct pt_regs *ctx) { - u64 request_pos = 4; - void *req_ptr = get_argument(ctx, request_pos); - void *goroutine = get_goroutine_address(ctx); - + void *goroutine = get_goroutine_address(ctx, ctx_ptr_pos); void *httpReq_ptr = bpf_map_lookup_elem(&context_to_http_events, &goroutine); struct http_request_t httpReq = {}; bpf_probe_read(&httpReq, sizeof(httpReq), httpReq_ptr); diff --git a/pkg/instrumentors/bpf/net/http/server/bpf_bpfel.go b/pkg/instrumentors/bpf/net/http/server/bpf_bpfel_arm64.go similarity index 93% rename from pkg/instrumentors/bpf/net/http/server/bpf_bpfel.go rename to pkg/instrumentors/bpf/net/http/server/bpf_bpfel_arm64.go index 9b5d9f9af..8b017c446 100644 --- a/pkg/instrumentors/bpf/net/http/server/bpf_bpfel.go +++ b/pkg/instrumentors/bpf/net/http/server/bpf_bpfel_arm64.go @@ -1,6 +1,6 @@ // Code generated by bpf2go; DO NOT EDIT. -//go:build 386 || amd64 || amd64p32 || arm || arm64 || mips64le || mips64p32le || mipsle || ppc64le || riscv64 -// +build 386 amd64 amd64p32 arm arm64 mips64le mips64p32le mipsle ppc64le riscv64 +//go:build arm64 +// +build arm64 package server @@ -139,5 +139,5 @@ func _BpfClose(closers ...io.Closer) error { // Do not access this directly. // -//go:embed bpf_bpfel.o +//go:embed bpf_bpfel_arm64.o var _BpfBytes []byte diff --git a/pkg/instrumentors/bpf/net/http/server/bpf_bpfel_x86.go b/pkg/instrumentors/bpf/net/http/server/bpf_bpfel_x86.go new file mode 100644 index 000000000..8d70f5d5e --- /dev/null +++ b/pkg/instrumentors/bpf/net/http/server/bpf_bpfel_x86.go @@ -0,0 +1,143 @@ +// Code generated by bpf2go; DO NOT EDIT. +//go:build 386 || amd64 +// +build 386 amd64 + +package server + +import ( + "bytes" + _ "embed" + "fmt" + "io" + + "github.com/cilium/ebpf" +) + +type bpfHttpRequestT struct { + StartTime uint64 + EndTime uint64 + Method [6]int8 + Path [100]int8 + Sc bpfSpanContext + _ [6]byte +} + +type bpfSpanContext struct { + TraceID [16]uint8 + SpanID [8]uint8 +} + +// loadBpf returns the embedded CollectionSpec for bpf. +func loadBpf() (*ebpf.CollectionSpec, error) { + reader := bytes.NewReader(_BpfBytes) + spec, err := ebpf.LoadCollectionSpecFromReader(reader) + if err != nil { + return nil, fmt.Errorf("can't load bpf: %w", err) + } + + return spec, err +} + +// loadBpfObjects loads bpf and converts it into a struct. +// +// The following types are suitable as obj argument: +// +// *bpfObjects +// *bpfPrograms +// *bpfMaps +// +// See ebpf.CollectionSpec.LoadAndAssign documentation for details. +func loadBpfObjects(obj interface{}, opts *ebpf.CollectionOptions) error { + spec, err := loadBpf() + if err != nil { + return err + } + + return spec.LoadAndAssign(obj, opts) +} + +// bpfSpecs contains maps and programs before they are loaded into the kernel. +// +// It can be passed ebpf.CollectionSpec.Assign. +type bpfSpecs struct { + bpfProgramSpecs + bpfMapSpecs +} + +// bpfSpecs contains programs before they are loaded into the kernel. +// +// It can be passed ebpf.CollectionSpec.Assign. +type bpfProgramSpecs struct { + UprobeServerMuxServeHTTP *ebpf.ProgramSpec `ebpf:"uprobe_ServerMux_ServeHTTP"` + UprobeServerMuxServeHTTP_Returns *ebpf.ProgramSpec `ebpf:"uprobe_ServerMux_ServeHTTP_Returns"` +} + +// bpfMapSpecs contains maps before they are loaded into the kernel. +// +// It can be passed ebpf.CollectionSpec.Assign. +type bpfMapSpecs struct { + ContextToHttpEvents *ebpf.MapSpec `ebpf:"context_to_http_events"` + Events *ebpf.MapSpec `ebpf:"events"` + SpansInProgress *ebpf.MapSpec `ebpf:"spans_in_progress"` +} + +// bpfObjects contains all objects after they have been loaded into the kernel. +// +// It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign. +type bpfObjects struct { + bpfPrograms + bpfMaps +} + +func (o *bpfObjects) Close() error { + return _BpfClose( + &o.bpfPrograms, + &o.bpfMaps, + ) +} + +// bpfMaps contains all maps after they have been loaded into the kernel. +// +// It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign. +type bpfMaps struct { + ContextToHttpEvents *ebpf.Map `ebpf:"context_to_http_events"` + Events *ebpf.Map `ebpf:"events"` + SpansInProgress *ebpf.Map `ebpf:"spans_in_progress"` +} + +func (m *bpfMaps) Close() error { + return _BpfClose( + m.ContextToHttpEvents, + m.Events, + m.SpansInProgress, + ) +} + +// bpfPrograms contains all programs after they have been loaded into the kernel. +// +// It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign. +type bpfPrograms struct { + UprobeServerMuxServeHTTP *ebpf.Program `ebpf:"uprobe_ServerMux_ServeHTTP"` + UprobeServerMuxServeHTTP_Returns *ebpf.Program `ebpf:"uprobe_ServerMux_ServeHTTP_Returns"` +} + +func (p *bpfPrograms) Close() error { + return _BpfClose( + p.UprobeServerMuxServeHTTP, + p.UprobeServerMuxServeHTTP_Returns, + ) +} + +func _BpfClose(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 bpf_bpfel_x86.o +var _BpfBytes []byte diff --git a/pkg/instrumentors/bpf/net/http/server/probe.go b/pkg/instrumentors/bpf/net/http/server/probe.go index 449f0cdf4..41d825708 100644 --- a/pkg/instrumentors/bpf/net/http/server/probe.go +++ b/pkg/instrumentors/bpf/net/http/server/probe.go @@ -36,7 +36,7 @@ import ( "go.opentelemetry.io/otel/trace" ) -//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 amd64,arm64 -cc clang -cflags $CFLAGS bpf ./bpf/probe.bpf.c // Event represents an event in an HTTP server during an HTTP // request-response. @@ -68,7 +68,7 @@ func (h *Instrumentor) LibraryName() string { // FuncNames returns the function names from "net/http" that are instrumented. func (h *Instrumentor) FuncNames() []string { - return []string{"net/http.(*ServeMux).ServeHTTP", "net/http.HandlerFunc.ServeHTTP"} + return []string{"net/http.HandlerFunc.ServeHTTP"} } // Load loads all instrumentation offsets. @@ -84,6 +84,11 @@ func (h *Instrumentor) Load(ctx *context.InstrumentorContext) error { StructName: "net/http.Request", Field: "URL", }, + { + VarName: "ctx_ptr_pos", + StructName: "net/http.Request", + Field: "ctx", + }, { VarName: "path_ptr_pos", StructName: "net/url.URL", diff --git a/pkg/instrumentors/manager.go b/pkg/instrumentors/manager.go index 1515df86a..0d4e61608 100644 --- a/pkg/instrumentors/manager.go +++ b/pkg/instrumentors/manager.go @@ -29,6 +29,11 @@ import ( "go.opentelemetry.io/auto/pkg/process" ) +var ( + // Error message returned when unable to find all instrumentation functions. + errNotAllFuncsFound = fmt.Errorf("not all functions found for instrumentation") +) + // Manager handles the management of [Instrumentor] instances. type Manager struct { instrumentors map[string]Instrumentor @@ -87,16 +92,17 @@ func (m *Manager) FilterUnusedInstrumentors(target *process.TargetDetails) { } 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 f26d85560..b2aad2d13 100644 --- a/pkg/process/analyze.go +++ b/pkg/process/analyze.go @@ -16,21 +16,20 @@ package process import ( "debug/elf" - "debug/gosym" "errors" "fmt" "os" - "strings" "github.com/hashicorp/go-version" - "golang.org/x/arch/x86/x86asm" "go.opentelemetry.io/auto/pkg/log" "go.opentelemetry.io/auto/pkg/process/ptrace" ) const ( - mapSize = 15 * 1024 * 1024 + // The concurrent trace & span ID pairs lookup size in bytes. Currently set to 24mb. + // TODO: Review map size. + mapSize = 25165824 ) // TargetDetails are the details about a target function. @@ -145,37 +144,22 @@ func (a *Analyzer) Analyze(pid int, relevantFuncs map[string]interface{}) (*Targ 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") - } - symTabRaw, err := sec.Data() if err != nil { return nil, err } - 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 @@ -183,11 +167,11 @@ func (a *Analyzer) Analyze(pid int, relevantFuncs map[string]interface{}) (*Targ 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, } @@ -201,42 +185,62 @@ func (a *Analyzer) Analyze(pid int, relevantFuncs map[string]interface{}) (*Targ return result, nil } -func (a *Analyzer) findFuncOffset(f *gosym.Func, elfF *elf.File) (uint64, []uint64, error) { - 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/ptrace/ptrace_linux_arm64.go b/pkg/process/ptrace/ptrace_linux_arm64.go index 6bef2033d..7c523e8e1 100644 --- a/pkg/process/ptrace/ptrace_linux_arm64.go +++ b/pkg/process/ptrace/ptrace_linux_arm64.go @@ -29,7 +29,7 @@ const syscallInstrSize = 4 // see kernel source /include/uapi/linux/elf.h const nrPRStatus = 1 -func getIp(regs *syscall.PtraceRegs) uintptr { +func getIP(regs *syscall.PtraceRegs) uintptr { return uintptr(regs.Pc) } @@ -81,7 +81,7 @@ func (p *TracedProgram) Syscall(number uint64, args ...uint64) (uint64, error) { } instruction := make([]byte, syscallInstrSize) - ip := getIp(p.backupRegs) + ip := getIP(p.backupRegs) // most aarch64 devices are little endian // 0xd4000001 is `svc #0` to call the system call diff --git a/pkg/process/ret_linux_amd64.go b/pkg/process/ret_linux_amd64.go new file mode 100644 index 000000000..ccbddb094 --- /dev/null +++ b/pkg/process/ret_linux_amd64.go @@ -0,0 +1,42 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed 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. +//go:build 386 || amd64 +// +build 386 amd64 + +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..9448f14d2 --- /dev/null +++ b/pkg/process/ret_linux_arm64.go @@ -0,0 +1,41 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed 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. +//go:build arm64 +// +build arm64 + +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 +} diff --git a/test/e2e/gorillamux/traces.json b/test/e2e/gorillamux/traces.json index ba6adfbc6..821041bcc 100644 --- a/test/e2e/gorillamux/traces.json +++ b/test/e2e/gorillamux/traces.json @@ -24,34 +24,6 @@ ] }, "scopeSpans": [ - { - "scope": { - "name": "github.com/gorilla/mux" - }, - "spans": [ - { - "attributes": [ - { - "key": "http.method", - "value": { - "stringValue": "" - } - }, - { - "key": "http.target", - "value": { - "stringValue": "" - } - } - ], - "kind": 2, - "parentSpanId": "", - "spanId": "", - "status": {}, - "traceId": "" - } - ] - }, { "scope": { "name": "net/http" diff --git a/test/e2e/nethttp/traces.json b/test/e2e/nethttp/traces.json index a11b52ea0..4c85eab41 100644 --- a/test/e2e/nethttp/traces.json +++ b/test/e2e/nethttp/traces.json @@ -50,27 +50,6 @@ "spanId": "xxxxx", "status": {}, "traceId": "xxxxx" - }, - { - "attributes": [ - { - "key": "http.method", - "value": { - "stringValue": "" - } - }, - { - "key": "http.target", - "value": { - "stringValue": "" - } - } - ], - "kind": 2, - "parentSpanId": "", - "spanId": "", - "status": {}, - "traceId": "" } ] }