diff --git a/CHANGELOG.md b/CHANGELOG.md index 4906d8e82..f8d4d9985 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ OpenTelemetry Go Automatic Instrumentation adheres to [Semantic Versioning](http - Fix bug in the `net/http` server instrumentation which always created a new span context. ([#266]https://github.com/open-telemetry/opentelemetry-go-instrumentation/pull/266) - Fix runtime panic if OTEL_GO_AUTO_TARGET_EXE is not set. ([#339](https://github.com/open-telemetry/opentelemetry-go-instrumentation/pull/339)) +- Improve eBPF context propagation stability ([#368]https://github.com/open-telemetry/opentelemetry-go-instrumentation/pull/368) ### Deprecated diff --git a/instrumentation.go b/instrumentation.go index 5be64a80a..276710e11 100644 --- a/instrumentation.go +++ b/instrumentation.go @@ -57,6 +57,13 @@ var errUndefinedTarget = fmt.Errorf("undefined target Go binary, consider settin // NewInstrumentation returns a new [Instrumentation] configured with the // provided opts. func NewInstrumentation(opts ...InstrumentationOption) (*Instrumentation, error) { + if log.Logger.IsZero() { + err := log.Init() + if err != nil { + return nil, err + } + } + c := newInstConfig(opts) if err := c.validate(); err != nil { return nil, err @@ -84,12 +91,11 @@ func NewInstrumentation(opts ...InstrumentationOption) (*Instrumentation, error) return nil, err } - if log.Logger.IsZero() { - err := log.Init() - if err != nil { - return nil, err - } + allocDetails, err := process.Allocate(pid) + if err != nil { + return nil, err } + td.AllocationDetails = allocDetails log.Logger.V(0).Info( "target process analysis completed", diff --git a/internal/include/alloc.h b/internal/include/alloc.h index df069533c..f4d94b09b 100644 --- a/internal/include/alloc.h +++ b/internal/include/alloc.h @@ -102,6 +102,12 @@ static __always_inline void *write_target_data(void *data, s32 size) void *target = (void *)start; size = bound_number(size, MIN_BUFFER_SIZE, MAX_BUFFER_SIZE); + u64 page_offset = (u64)target & 0xFFF; + u64 dist_to_next_page = 4096 - page_offset; + if (dist_to_next_page < size) + { + target += dist_to_next_page; + } long success = bpf_probe_write_user(target, data, size); if (success == 0) { diff --git a/internal/include/arguments.h b/internal/include/arguments.h index 70f42fc54..e93676e20 100644 --- a/internal/include/arguments.h +++ b/internal/include/arguments.h @@ -57,7 +57,7 @@ void *get_argument_by_stack(struct pt_regs *ctx, int index) return ptr; } -void *get_argument(struct pt_regs *ctx, int index) +void __always_inline *get_argument(struct pt_regs *ctx, int index) { if (is_registers_abi) { @@ -80,9 +80,7 @@ static __always_inline void *get_consistent_key(struct pt_regs *ctx, void *conte return (void *)GOROUTINE(ctx); } - void *ctx_ptr = 0; - bpf_probe_read(&ctx_ptr, sizeof(ctx_ptr), contextContext); - return ctx_ptr; + return contextContext; } #endif \ No newline at end of file diff --git a/internal/include/go_context.h b/internal/include/go_context.h index 701fffd17..b47552a52 100644 --- a/internal/include/go_context.h +++ b/internal/include/go_context.h @@ -16,8 +16,10 @@ #define _GO_CONTEXT_H_ #include "bpf_helpers.h" +#include "go_types.h" #define MAX_DISTANCE 10 +#define MAX_CONCURRENT_SPANS 1000 struct { @@ -41,6 +43,11 @@ static __always_inline void *get_parent_go_context(void *ctx, void *map) { void *data = ctx; for (int i = 0; i < MAX_DISTANCE; i++) { + if (data == NULL) + { + break; + } + void *found_in_map = bpf_map_lookup_elem(map, &data); if (found_in_map != NULL) { @@ -72,20 +79,82 @@ static __always_inline struct span_context *get_parent_span_context(void *ctx) { return parent_sc; } -static __always_inline void start_tracking_span(void *ctx, struct span_context *sc) { - bpf_map_update_elem(&tracked_spans, &ctx, sc, BPF_ANY); - bpf_map_update_elem(&tracked_spans_by_sc, sc, &ctx, BPF_ANY); +static __always_inline void start_tracking_span(void *contextContext, struct span_context *sc) { + long err = 0; + err = bpf_map_update_elem(&tracked_spans, &contextContext, sc, BPF_ANY); + if (err != 0) + { + bpf_printk("Failed to update tracked_spans map: %ld", err); + return; + } + + err = bpf_map_update_elem(&tracked_spans_by_sc, sc, &contextContext, BPF_ANY); + if (err != 0) + { + bpf_printk("Failed to update tracked_spans_by_sc map: %ld", err); + return; + } } -static __always_inline void stop_tracking_span(struct span_context *sc) { +static __always_inline void stop_tracking_span(struct span_context *sc, struct span_context *psc) { + if (sc == NULL) { + bpf_printk("stop_tracking_span: sc is null"); + return; + } + void *ctx = bpf_map_lookup_elem(&tracked_spans_by_sc, sc); if (ctx == NULL) { + bpf_printk("stop_tracking_span: cant find span context"); return; } - bpf_map_delete_elem(&tracked_spans, &ctx); + void *parent_ctx = ((psc == NULL) ? NULL : bpf_map_lookup_elem(&tracked_spans_by_sc, psc)); + if (parent_ctx == NULL) + { + // No parent span, delete the context + bpf_map_delete_elem(&tracked_spans, ctx); + } else + { + void *ctx_val = 0; + bpf_probe_read_user(&ctx_val, sizeof(ctx_val), ctx); + void *parent_ctx_val = 0; + bpf_probe_read_user(&parent_ctx_val, sizeof(parent_ctx_val), parent_ctx); + + if (ctx_val != parent_ctx_val) + { + // Parent with different context, delete the context + bpf_map_delete_elem(&tracked_spans, ctx); + } else { + // Parent with the same context, update the entry to point to the parent span + bpf_map_update_elem(&tracked_spans, ctx, psc, BPF_ANY); + } + } + bpf_map_delete_elem(&tracked_spans_by_sc, sc); } +// Extract the go context.Context data pointer from the function arguments +// context_pos: + // The argument position of the context.Context data pointer + // In case the context.Context is passed as an argument, + // this is the argument index of the pointer (starting from 1). + // In case the context.Context is a member of a struct, + // this is the argument index of the struct pointer (starting from 1). +// context_offset: + // In case the context.Context is a member of a struct, + // this is the offset of the context.Context member in the struct. +// passed_as_arg: + // Indicates whether context.Context is passed as an argument or is a member of a struct +static __always_inline void *get_Go_context(void *ctx, int context_pos, const volatile u64 context_offset, bool passed_as_arg) { + void *arg = get_argument(ctx, context_pos); + if (passed_as_arg) { + return arg; + } + void *ctx_addr = get_go_interface_instance((void*)(arg + context_offset)); + void *ctx_val = 0; + bpf_probe_read_user(&ctx_val, sizeof(ctx_val), ctx_addr); + return ctx_val; +} + #endif \ No newline at end of file diff --git a/internal/include/go_types.h b/internal/include/go_types.h index cf18fffa8..dfd867937 100644 --- a/internal/include/go_types.h +++ b/internal/include/go_types.h @@ -54,13 +54,25 @@ struct map_bucket { void *overflow; }; +// In Go, interfaces are represented as a pair of pointers: a pointer to the +// interface data, and a pointer to the interface table. +// See: runtime.iface in https://golang.org/src/runtime/runtime2.go +static __always_inline void* get_go_interface_instance(void *iface) +{ + return (void*)(iface + 8); +} + static __always_inline struct go_string write_user_go_string(char *str, u32 len) { // Copy chars to userspace + struct go_string new_string = {.str = NULL, .len = 0}; char *addr = write_target_data((void *)str, len); + if (addr == NULL) { + bpf_printk("write_user_go_string: failed to copy string to userspace"); + return new_string; + } // Build string struct in kernel space - struct go_string new_string = {}; new_string.str = addr; new_string.len = len; @@ -82,7 +94,7 @@ static __always_inline void append_item_to_slice(struct go_slice *slice, void *n } else { - // No room on current array - copy to new one of size item_size * (len + 1) + //No room on current array - copy to new one of size item_size * (len + 1) if (slice->len > MAX_DATA_SIZE || slice->len < 1) { return; @@ -111,19 +123,63 @@ static __always_inline void append_item_to_slice(struct go_slice *slice, void *n } void *new_array = write_target_data(map_buff, new_array_size); + if (new_array == NULL) + { + bpf_printk("append_item_to_slice: failed to copy new array to userspace"); + return; + } // Update array slice->array = new_array; long success = bpf_probe_write_user(slice_user_ptr->array, &slice->array, sizeof(slice->array)); + if (success != 0) + { + bpf_printk("append_item_to_slice: failed to update array pointer in userspace"); + return; + } // Update cap slice->cap++; success = bpf_probe_write_user(slice_user_ptr->cap, &slice->cap, sizeof(slice->cap)); + if (success != 0) + { + bpf_printk("append_item_to_slice: failed to update cap in userspace"); + return; + } } // Update len slice->len++; long success = bpf_probe_write_user(slice_user_ptr->len, &slice->len, sizeof(slice->len)); + if (success != 0) + { + bpf_printk("append_item_to_slice: failed to update len in userspace"); + return; + } } +static __always_inline bool get_go_string_from_user_ptr(void *user_str_ptr, char *dst, u64 max_len) +{ + if (user_str_ptr == NULL) + { + return false; + } + + struct go_string user_str = {0}; + long success = 0; + success = bpf_probe_read(&user_str, sizeof(struct go_string), user_str_ptr); + if (success != 0 || user_str.len < 1) + { + return false; + } + + u64 size_to_read = user_str.len > max_len ? max_len : user_str.len; + success = bpf_probe_read(dst, size_to_read, user_str.str); + if (success != 0) + { + return false; + } + + return true; +} #endif \ No newline at end of file diff --git a/internal/include/span_context.h b/internal/include/span_context.h index 07e9a7858..4395b020d 100644 --- a/internal/include/span_context.h +++ b/internal/include/span_context.h @@ -18,7 +18,6 @@ #include "utils.h" #define SPAN_CONTEXT_STRING_SIZE 55 -#define MAX_CONCURRENT_SPANS 100 struct span_context { diff --git a/internal/include/uprobe.h b/internal/include/uprobe.h index d68cbacac..655c97509 100644 --- a/internal/include/uprobe.h +++ b/internal/include/uprobe.h @@ -18,6 +18,7 @@ #include "common.h" #include "span_context.h" #include "go_context.h" +#include "go_types.h" #define BASE_SPAN_PROPERTIES \ u64 start_time; \ @@ -32,19 +33,22 @@ // 4. Submit the constructed event to the agent code using perf buffer events_map // 5. Delete the span from the uprobe_context_map // 6. Delete the span from the global active spans map -#define UPROBE_RETURN(name, event_type, ctx_struct_pos, ctx_struct_offset, uprobe_context_map, events_map) \ -SEC("uprobe/##name##") \ -int uprobe_##name##_Returns(struct pt_regs *ctx) { \ - void *req_ptr = get_argument(ctx, ctx_struct_pos); \ - void *key = get_consistent_key(ctx, (void *)(req_ptr + ctx_struct_offset)); \ - void *req_ptr_map = bpf_map_lookup_elem(&uprobe_context_map, &key); \ - event_type tmpReq = {}; \ - bpf_probe_read(&tmpReq, sizeof(tmpReq), req_ptr_map); \ - tmpReq.end_time = bpf_ktime_get_ns(); \ - bpf_perf_event_output(ctx, &events_map, BPF_F_CURRENT_CPU, &tmpReq, sizeof(tmpReq)); \ - bpf_map_delete_elem(&uprobe_context_map, &key); \ - stop_tracking_span(&tmpReq.sc); \ - return 0; \ -} +#define UPROBE_RETURN(name, event_type, uprobe_context_map, events_map, context_pos, context_offset, passed_as_arg) \ +SEC("uprobe/##name##") \ +int uprobe_##name##_Returns(struct pt_regs *ctx) { \ + void *ctx_address = get_Go_context(ctx, context_pos, context_offset, passed_as_arg); \ + void *key = get_consistent_key(ctx, ctx_address); \ + void *req_ptr_map = bpf_map_lookup_elem(&uprobe_context_map, &key); \ + if (req_ptr_map == NULL) { \ + return 0; \ + } \ + event_type tmpReq = {0}; \ + bpf_probe_read(&tmpReq, sizeof(tmpReq), req_ptr_map); \ + tmpReq.end_time = bpf_ktime_get_ns(); \ + bpf_perf_event_output(ctx, &events_map, BPF_F_CURRENT_CPU, &tmpReq, sizeof(tmpReq)); \ + bpf_map_delete_elem(&uprobe_context_map, &key); \ + stop_tracking_span(&tmpReq.sc, &tmpReq.psc); \ + return 0; \ +} #endif \ No newline at end of file diff --git a/internal/include/utils.h b/internal/include/utils.h index fe70ee30d..033acc79f 100644 --- a/internal/include/utils.h +++ b/internal/include/utils.h @@ -83,3 +83,16 @@ static __always_inline void bpf_memset(unsigned char *dst, u32 size, unsigned ch dst[i] = value; } } + +static __always_inline bool bpf_is_zero(unsigned char *buff, u32 size) +{ + for (int i = 0; i < size; i++) + { + if (buff[i] != 0) + { + return false; + } + } + + return true; +} diff --git a/internal/pkg/instrumentors/bpf/database/sql/bpf/probe.bpf.c b/internal/pkg/instrumentors/bpf/database/sql/bpf/probe.bpf.c index bff811f20..85e443a3e 100644 --- a/internal/pkg/instrumentors/bpf/database/sql/bpf/probe.bpf.c +++ b/internal/pkg/instrumentors/bpf/database/sql/bpf/probe.bpf.c @@ -63,9 +63,7 @@ int uprobe_queryDC(struct pt_regs *ctx) { } // Get parent if exists - void *context_ptr = get_argument(ctx, context_ptr_pos); - void *context_ptr_val = 0; - bpf_probe_read(&context_ptr_val, sizeof(context_ptr_val), context_ptr); + void *context_ptr_val = get_Go_context(ctx, 3, 0, true); struct span_context *span_ctx = get_parent_span_context(context_ptr_val); if (span_ctx != NULL) { // Set the parent context @@ -77,7 +75,7 @@ int uprobe_queryDC(struct pt_regs *ctx) { } // Get key - void *key = get_consistent_key(ctx, context_ptr); + void *key = get_consistent_key(ctx, context_ptr_val); bpf_map_update_elem(&sql_events, &key, &sql_request, 0); start_tracking_span(context_ptr_val, &sql_request.sc); @@ -86,4 +84,4 @@ int uprobe_queryDC(struct pt_regs *ctx) { // This instrumentation attaches uprobe to the following function: // func (db *DB) queryDC(ctx, txctx context.Context, dc *driverConn, releaseConn func(error), query string, args []any) -UPROBE_RETURN(queryDC, struct sql_request_t, 3, 0, sql_events, events) \ No newline at end of file +UPROBE_RETURN(queryDC, struct sql_request_t, sql_events, events, 3, 0, true) \ No newline at end of file diff --git a/internal/pkg/instrumentors/bpf/github.com/gin-gonic/gin/bpf/probe.bpf.c b/internal/pkg/instrumentors/bpf/github.com/gin-gonic/gin/bpf/probe.bpf.c index ade6c43ca..8108c2490 100644 --- a/internal/pkg/instrumentors/bpf/github.com/gin-gonic/gin/bpf/probe.bpf.c +++ b/internal/pkg/instrumentors/bpf/github.com/gin-gonic/gin/bpf/probe.bpf.c @@ -56,31 +56,24 @@ int uprobe_GinEngine_ServeHTTP(struct pt_regs *ctx) { // Get request struct void *req_ptr = get_argument(ctx, request_pos); + void *req_ctx_ptr = get_Go_context(ctx, 4, ctx_ptr_pos, false); // Get method from request - 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))); - 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); + if (!get_go_string_from_user_ptr((void *)(req_ptr + method_ptr_pos), httpReq.method, sizeof(httpReq.method))) { + bpf_printk("failed to get method from request"); + return 0; + } // 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)); - u64 path_len = 0; - 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); + if (!get_go_string_from_user_ptr((void *)(url_ptr + path_ptr_pos), httpReq.path, sizeof(httpReq.path))) { + bpf_printk("failed to get path from Request.URL"); + return 0; + } // Get key - void *req_ctx_ptr = 0; - bpf_probe_read(&req_ctx_ptr, sizeof(req_ctx_ptr), (void *)(req_ptr + ctx_ptr_pos)); - void *key = get_consistent_key(ctx, (void *)(req_ptr + ctx_ptr_pos)); + void *key = get_consistent_key(ctx, req_ctx_ptr); // Write event httpReq.sc = generate_span_context(); @@ -89,4 +82,4 @@ int uprobe_GinEngine_ServeHTTP(struct pt_regs *ctx) { return 0; } -UPROBE_RETURN(GinEngine_ServeHTTP, struct http_request_t, 4, ctx_ptr_pos, http_events, events) \ No newline at end of file +UPROBE_RETURN(GinEngine_ServeHTTP, struct http_request_t, http_events, events, 4, ctx_ptr_pos, false) \ No newline at end of file diff --git a/internal/pkg/instrumentors/bpf/github.com/gin-gonic/gin/bpf_bpfel_arm64.go b/internal/pkg/instrumentors/bpf/github.com/gin-gonic/gin/bpf_bpfel_arm64.go index e2aaf959e..e58fa7b54 100644 --- a/internal/pkg/instrumentors/bpf/github.com/gin-gonic/gin/bpf_bpfel_arm64.go +++ b/internal/pkg/instrumentors/bpf/github.com/gin-gonic/gin/bpf_bpfel_arm64.go @@ -76,6 +76,7 @@ type bpfProgramSpecs struct { // // It can be passed ebpf.CollectionSpec.Assign. type bpfMapSpecs struct { + AllocMap *ebpf.MapSpec `ebpf:"alloc_map"` Events *ebpf.MapSpec `ebpf:"events"` HttpEvents *ebpf.MapSpec `ebpf:"http_events"` TrackedSpans *ebpf.MapSpec `ebpf:"tracked_spans"` @@ -101,6 +102,7 @@ func (o *bpfObjects) Close() error { // // It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign. type bpfMaps struct { + AllocMap *ebpf.Map `ebpf:"alloc_map"` Events *ebpf.Map `ebpf:"events"` HttpEvents *ebpf.Map `ebpf:"http_events"` TrackedSpans *ebpf.Map `ebpf:"tracked_spans"` @@ -109,6 +111,7 @@ type bpfMaps struct { func (m *bpfMaps) Close() error { return _BpfClose( + m.AllocMap, m.Events, m.HttpEvents, m.TrackedSpans, diff --git a/internal/pkg/instrumentors/bpf/github.com/gin-gonic/gin/bpf_bpfel_x86.go b/internal/pkg/instrumentors/bpf/github.com/gin-gonic/gin/bpf_bpfel_x86.go index 40a92c783..9d8998f16 100644 --- a/internal/pkg/instrumentors/bpf/github.com/gin-gonic/gin/bpf_bpfel_x86.go +++ b/internal/pkg/instrumentors/bpf/github.com/gin-gonic/gin/bpf_bpfel_x86.go @@ -76,6 +76,7 @@ type bpfProgramSpecs struct { // // It can be passed ebpf.CollectionSpec.Assign. type bpfMapSpecs struct { + AllocMap *ebpf.MapSpec `ebpf:"alloc_map"` Events *ebpf.MapSpec `ebpf:"events"` HttpEvents *ebpf.MapSpec `ebpf:"http_events"` TrackedSpans *ebpf.MapSpec `ebpf:"tracked_spans"` @@ -101,6 +102,7 @@ func (o *bpfObjects) Close() error { // // It can be passed to loadBpfObjects or ebpf.CollectionSpec.LoadAndAssign. type bpfMaps struct { + AllocMap *ebpf.Map `ebpf:"alloc_map"` Events *ebpf.Map `ebpf:"events"` HttpEvents *ebpf.Map `ebpf:"http_events"` TrackedSpans *ebpf.Map `ebpf:"tracked_spans"` @@ -109,6 +111,7 @@ type bpfMaps struct { func (m *bpfMaps) Close() error { return _BpfClose( + m.AllocMap, m.Events, m.HttpEvents, m.TrackedSpans, diff --git a/internal/pkg/instrumentors/bpf/google.golang.org/grpc/bpf/probe.bpf.c b/internal/pkg/instrumentors/bpf/google.golang.org/grpc/bpf/probe.bpf.c index ab7986e87..df33afa65 100644 --- a/internal/pkg/instrumentors/bpf/google.golang.org/grpc/bpf/probe.bpf.c +++ b/internal/pkg/instrumentors/bpf/google.golang.org/grpc/bpf/probe.bpf.c @@ -85,10 +85,23 @@ int uprobe_ClientConn_Invoke(struct pt_regs *ctx) { // positions u64 clientconn_pos = 1; - u64 context_pos = 3; u64 method_ptr_pos = 4; u64 method_len_pos = 5; + void *context_ptr = get_Go_context(ctx, 3, 0, true); + if (context_ptr == NULL) + { + return 0; + } + // Get key + void *key = get_consistent_key(ctx, context_ptr); + void *grpcReq_ptr = bpf_map_lookup_elem(&grpc_events, &key); + if (grpcReq_ptr != NULL) + { + bpf_printk("uprobe/ClientConn_Invoke already tracked with the current context"); + return 0; + } + struct grpc_request_t grpcReq = {}; grpcReq.start_time = bpf_ktime_get_ns(); @@ -101,19 +114,14 @@ int uprobe_ClientConn_Invoke(struct pt_regs *ctx) // Read ClientConn.Target void *clientconn_ptr = get_argument(ctx, clientconn_pos); - void *target_ptr = 0; - bpf_probe_read(&target_ptr, sizeof(target_ptr), (void *)(clientconn_ptr + (clientconn_target_ptr_pos))); - u64 target_len = 0; - bpf_probe_read(&target_len, sizeof(target_len), (void *)(clientconn_ptr + (clientconn_target_ptr_pos + 8))); - u64 target_size = sizeof(grpcReq.target); - target_size = target_size < target_len ? target_size : target_len; - bpf_probe_read(&grpcReq.target, target_size, target_ptr); - - // Get parent if exists - void *context_ptr = get_argument(ctx, context_pos); - void *context_ptr_val = 0; - bpf_probe_read(&context_ptr_val, sizeof(context_ptr_val), context_ptr); - struct span_context *parent_span_ctx = get_parent_span_context(context_ptr_val); + if (!get_go_string_from_user_ptr((void*)(clientconn_ptr + clientconn_target_ptr_pos), grpcReq.target, sizeof(grpcReq.target))) + { + bpf_printk("target write failed, aborting ebpf probe"); + return 0; + } + + // Get parent if exists + struct span_context *parent_span_ctx = get_parent_span_context(context_ptr); if (parent_span_ctx != NULL) { bpf_probe_read(&grpcReq.psc, sizeof(grpcReq.psc), parent_span_ctx); @@ -125,16 +133,13 @@ int uprobe_ClientConn_Invoke(struct pt_regs *ctx) grpcReq.sc = generate_span_context(); } - // Get key - void *key = get_consistent_key(ctx, context_ptr); - // Write event bpf_map_update_elem(&grpc_events, &key, &grpcReq, 0); - start_tracking_span(context_ptr_val, &grpcReq.sc); + start_tracking_span(context_ptr, &grpcReq.sc); return 0; } -UPROBE_RETURN(ClientConn_Invoke, struct grpc_request_t, 3, 0, grpc_events, events) +UPROBE_RETURN(ClientConn_Invoke, struct grpc_request_t, grpc_events, events, 3, 0, true) // func (l *loopyWriter) headerHandler(h *headerFrame) error SEC("uprobe/loopyWriter_headerHandler") @@ -165,7 +170,7 @@ int uprobe_LoopyWriter_HeaderHandler(struct pt_regs *ctx) char tp_key[11] = "traceparent"; struct go_string key_str = write_user_go_string(tp_key, sizeof(tp_key)); if (key_str.len == 0) { - bpf_printk("write failed, aborting ebpf probe"); + bpf_printk("key write failed, aborting ebpf probe"); return 0; } @@ -173,6 +178,10 @@ int uprobe_LoopyWriter_HeaderHandler(struct pt_regs *ctx) char val[SPAN_CONTEXT_STRING_SIZE]; span_context_to_w3c_string(¤t_span_context, val); struct go_string val_str = write_user_go_string(val, sizeof(val)); + if (val_str.len == 0) { + bpf_printk("val write failed, aborting ebpf probe"); + return 0; + } struct hpack_header_field hf = {}; hf.name = key_str; hf.value = val_str; @@ -185,14 +194,10 @@ SEC("uprobe/http2Client_NewStream") int uprobe_http2Client_NewStream(struct pt_regs *ctx) { void *context_ptr = get_argument(ctx, 3); - void *context_ptr_val = 0; - bpf_probe_read(&context_ptr_val, sizeof(context_ptr_val), context_ptr); - void *httpclient_ptr = get_argument(ctx, 1); u32 nextid = 0; bpf_probe_read(&nextid, sizeof(nextid), (void *)(httpclient_ptr + (httpclient_nextid_pos))); - - struct span_context *current_span_context = get_parent_span_context(context_ptr_val); + struct span_context *current_span_context = get_parent_span_context(context_ptr); if (current_span_context != NULL) { bpf_map_update_elem(&streamid_to_span_contexts, &nextid, current_span_context, 0); } diff --git a/internal/pkg/instrumentors/bpf/google.golang.org/grpc/server/bpf/probe.bpf.c b/internal/pkg/instrumentors/bpf/google.golang.org/grpc/server/bpf/probe.bpf.c index 274c5de3a..906b8ae7a 100644 --- a/internal/pkg/instrumentors/bpf/google.golang.org/grpc/server/bpf/probe.bpf.c +++ b/internal/pkg/instrumentors/bpf/google.golang.org/grpc/server/bpf/probe.bpf.c @@ -75,6 +75,19 @@ int uprobe_server_handleStream(struct pt_regs *ctx) { u64 stream_pos = 4; void *stream_ptr = get_argument(ctx, stream_pos); + // Get key + void *ctx_iface = get_Go_context(ctx, 4, stream_ctx_pos, false); + if (ctx_iface == NULL) + { + return 0; + } + void *key = get_consistent_key(ctx, ctx_iface); + void *grpcReq_event_ptr = bpf_map_lookup_elem(&grpc_events, &key); + if (grpcReq_event_ptr != NULL) + { + bpf_printk("uprobe/server_handleStream already tracked with the current context"); + return 0; + } // Get parent context if exists u32 stream_id = 0; @@ -93,20 +106,13 @@ int uprobe_server_handleStream(struct pt_regs *ctx) 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); - - // Get key - void *ctx_iface = 0; - bpf_probe_read(&ctx_iface, sizeof(ctx_iface), (void *)(stream_ptr + stream_ctx_pos)); - void *key = get_consistent_key(ctx, (void *)(stream_ptr + stream_ctx_pos)); + // Set attributes + if (!get_go_string_from_user_ptr((void *)(stream_ptr + stream_method_ptr_pos), grpcReq.method, sizeof(grpcReq.method))) + { + bpf_printk("method write failed, aborting ebpf probe"); + return 0; + } // Write event bpf_map_update_elem(&grpc_events, &key, &grpcReq, 0); @@ -114,7 +120,7 @@ int uprobe_server_handleStream(struct pt_regs *ctx) return 0; } -UPROBE_RETURN(server_handleStream, struct grpc_request_t, 4, stream_ctx_pos, grpc_events, events) +UPROBE_RETURN(server_handleStream, struct grpc_request_t, grpc_events, events, 4, stream_ctx_pos, false) // func (d *decodeState) decodeHeader(frame *http2.MetaHeadersFrame) error SEC("uprobe/decodeState_decodeHeader") diff --git a/internal/pkg/instrumentors/bpf/net/http/client/bpf/probe.bpf.c b/internal/pkg/instrumentors/bpf/net/http/client/bpf/probe.bpf.c index c4fbe935a..e94795215 100644 --- a/internal/pkg/instrumentors/bpf/net/http/client/bpf/probe.bpf.c +++ b/internal/pkg/instrumentors/bpf/net/http/client/bpf/probe.bpf.c @@ -73,6 +73,10 @@ static __always_inline long inject_header(void* headers_ptr, struct span_context if (curr_keyvalue_count == 0) { // No key-value pairs in the Go map, need to "allocate" memory for the user bucket_ptr = write_target_data(bucket_map_value, sizeof(struct map_bucket)); + if (bucket_ptr == NULL) { + bpf_printk("inject_header: Failed to write bucket to user"); + return -1; + } // Update the buckets pointer in the hmap struct to point to newly allocated bucket res = bpf_probe_write_user(buckets_ptr_ptr, &bucket_ptr, sizeof(bucket_ptr)); if (res < 0) { @@ -95,6 +99,10 @@ static __always_inline long inject_header(void* headers_ptr, struct span_context // Prepare the key string for the user char key[W3C_KEY_LENGTH] = "traceparent"; void *ptr = write_target_data(key, W3C_KEY_LENGTH); + if (ptr == NULL) { + bpf_printk("inject_header: Failed to write key to user"); + return -1; + } bucket_map_value->keys[bucket_index] = (struct go_string) {.len = W3C_KEY_LENGTH, .str = ptr}; // Prepare the value string slice @@ -103,6 +111,7 @@ static __always_inline long inject_header(void* headers_ptr, struct span_context span_context_to_w3c_string(propagated_ctx, val); ptr = write_target_data(val, sizeof(val)); if(ptr == NULL) { + bpf_printk("inject_header: Failed to write value to user"); return -1; } @@ -110,6 +119,7 @@ static __always_inline long inject_header(void* headers_ptr, struct span_context struct go_string header_value = {.len = W3C_VAL_LENGTH, .str = ptr}; ptr = write_target_data((void*)&header_value, sizeof(header_value)); if(ptr == NULL) { + bpf_printk("inject_header: Failed to write go_string to user"); return -1; } @@ -146,9 +156,19 @@ int uprobe_HttpClient_Do(struct pt_regs *ctx) { void *req_ptr = get_argument(ctx, request_pos); // Get parent if exists - void *context_ptr = (void *)(req_ptr+ctx_ptr_pos); - void *context_ptr_val = 0; - bpf_probe_read(&context_ptr_val, sizeof(context_ptr_val), context_ptr); + void *context_ptr_val = get_Go_context(ctx, 2, ctx_ptr_pos, false); + if (context_ptr_val == NULL) + { + return 0; + } + void *key = get_consistent_key(ctx, context_ptr_val); + void *httpReq_ptr = bpf_map_lookup_elem(&http_events, &key); + if (httpReq_ptr != NULL) + { + bpf_printk("uprobe/HttpClient_Do already tracked with the current context"); + return 0; + } + struct span_context *parent_span_ctx = get_parent_span_context(context_ptr_val); if (parent_span_ctx != NULL) { bpf_probe_read(&httpReq.psc, sizeof(httpReq.psc), parent_span_ctx); @@ -158,39 +178,27 @@ int uprobe_HttpClient_Do(struct pt_regs *ctx) { httpReq.sc = generate_span_context(); } - 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))); - 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); + if (!get_go_string_from_user_ptr((void *)(req_ptr+method_ptr_pos), httpReq.method, sizeof(httpReq.method))) { + bpf_printk("uprobe_HttpClient_Do: Failed to get method from request"); + return 0; + } // 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)); - - u64 path_len = 0; - 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); + if (!get_go_string_from_user_ptr((void *)(url_ptr+path_ptr_pos), httpReq.path, sizeof(httpReq.path))) { + bpf_printk("uprobe_HttpClient_Do: Failed to get path from Request.URL"); + return 0; + } // get headers from Request void *headers_ptr = 0; bpf_probe_read(&headers_ptr, sizeof(headers_ptr), (void *)(req_ptr+headers_ptr_pos)); - u64 map_keyvalue_count = 0; - bpf_probe_read(&map_keyvalue_count, sizeof(map_keyvalue_count), headers_ptr); long res = inject_header(headers_ptr, &httpReq.sc); if (res < 0) { bpf_printk("uprobe_HttpClient_Do: Failed to inject header"); } - // Get key - void *key = get_consistent_key(ctx, context_ptr); - // Write event bpf_map_update_elem(&http_events, &key, &httpReq, 0); start_tracking_span(context_ptr_val, &httpReq.sc); @@ -199,4 +207,4 @@ int uprobe_HttpClient_Do(struct pt_regs *ctx) { // This instrumentation attaches uretprobe to the following function: // func net/http/client.Do(req *Request) -UPROBE_RETURN(HttpClient_Do, struct http_request_t, 2, ctx_ptr_pos, http_events, events) \ No newline at end of file +UPROBE_RETURN(HttpClient_Do, struct http_request_t, http_events, events, 2, ctx_ptr_pos, false) \ No newline at end of file diff --git a/internal/pkg/instrumentors/bpf/net/http/server/bpf/probe.bpf.c b/internal/pkg/instrumentors/bpf/net/http/server/bpf/probe.bpf.c index 83e8f81d8..43743354b 100644 --- a/internal/pkg/instrumentors/bpf/net/http/server/bpf/probe.bpf.c +++ b/internal/pkg/instrumentors/bpf/net/http/server/bpf/probe.bpf.c @@ -167,41 +167,47 @@ static __always_inline struct span_context *extract_context_from_req_headers(voi } // This instrumentation attaches uprobe to the following function: -// func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) -SEC("uprobe/ServerMux_ServeHTTP") -int uprobe_ServerMux_ServeHTTP(struct pt_regs *ctx) +// func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) +SEC("uprobe/HandlerFunc_ServeHTTP") +int uprobe_HandlerFunc_ServeHTTP(struct pt_regs *ctx) { + // Get key u64 request_pos = 4; + void *req_ptr = get_argument(ctx, request_pos); + void *req_ctx_ptr = get_Go_context(ctx, 4, ctx_ptr_pos, false); + if (req_ctx_ptr == NULL) + { + return 0; + } + void *key = get_consistent_key(ctx, req_ctx_ptr); + void *httpReq_ptr = bpf_map_lookup_elem(&http_events, &key); + if (httpReq_ptr != NULL) + { + bpf_printk("uprobe/HandlerFunc_ServeHTTP already tracked with the current context"); + return 0; + } + struct http_request_t httpReq = {}; httpReq.start_time = bpf_ktime_get_ns(); - // Get request struct - 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)); - u64 method_len = 0; - 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); - + if (!get_go_string_from_user_ptr((void *)(req_ptr + method_ptr_pos), httpReq.method, sizeof(httpReq.method))) { + bpf_printk("failed to get method from request"); + return 0; + } // 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)); - u64 path_len = 0; - 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); + if (!get_go_string_from_user_ptr((void *)(url_ptr + path_ptr_pos), httpReq.path, sizeof(httpReq.path))) { + bpf_printk("failed to get path from Request.URL"); + return 0; + } // Propagate context - struct span_context *parent_ctx = extract_context_from_req_headers(req_ptr + headers_ptr_pos); + struct span_context *parent_ctx = extract_context_from_req_headers((void*)(req_ptr + headers_ptr_pos)); if (parent_ctx != NULL) { + // found parent context in http headers httpReq.psc = *parent_ctx; copy_byte_arrays(httpReq.psc.TraceID, httpReq.sc.TraceID, TRACE_ID_SIZE); generate_random_bytes(httpReq.sc.SpanID, SPAN_ID_SIZE); @@ -211,15 +217,10 @@ int uprobe_ServerMux_ServeHTTP(struct pt_regs *ctx) httpReq.sc = generate_span_context(); } - // Get key - void *req_ctx_ptr = 0; - bpf_probe_read(&req_ctx_ptr, sizeof(req_ctx_ptr), (void *)(req_ptr + ctx_ptr_pos)); - void *key = get_consistent_key(ctx, (void *)(req_ptr + ctx_ptr_pos)); - // Write event bpf_map_update_elem(&http_events, &key, &httpReq, 0); start_tracking_span(req_ctx_ptr, &httpReq.sc); return 0; } -UPROBE_RETURN(ServerMux_ServeHTTP, struct http_request_t, 4, ctx_ptr_pos, http_events, events) \ No newline at end of file +UPROBE_RETURN(HandlerFunc_ServeHTTP, struct http_request_t, http_events, events, 4, ctx_ptr_pos, false) \ No newline at end of file diff --git a/internal/pkg/instrumentors/bpf/net/http/server/bpf_bpfel_arm64.go b/internal/pkg/instrumentors/bpf/net/http/server/bpf_bpfel_arm64.go index 9ae25dca9..c956cb2cd 100644 --- a/internal/pkg/instrumentors/bpf/net/http/server/bpf_bpfel_arm64.go +++ b/internal/pkg/instrumentors/bpf/net/http/server/bpf_bpfel_arm64.go @@ -68,8 +68,8 @@ type bpfSpecs struct { // // 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"` + UprobeHandlerFuncServeHTTP *ebpf.ProgramSpec `ebpf:"uprobe_HandlerFunc_ServeHTTP"` + UprobeHandlerFuncServeHTTP_Returns *ebpf.ProgramSpec `ebpf:"uprobe_HandlerFunc_ServeHTTP_Returns"` } // bpfMapSpecs contains maps before they are loaded into the kernel. @@ -129,14 +129,14 @@ func (m *bpfMaps) Close() error { // // 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"` + UprobeHandlerFuncServeHTTP *ebpf.Program `ebpf:"uprobe_HandlerFunc_ServeHTTP"` + UprobeHandlerFuncServeHTTP_Returns *ebpf.Program `ebpf:"uprobe_HandlerFunc_ServeHTTP_Returns"` } func (p *bpfPrograms) Close() error { return _BpfClose( - p.UprobeServerMuxServeHTTP, - p.UprobeServerMuxServeHTTP_Returns, + p.UprobeHandlerFuncServeHTTP, + p.UprobeHandlerFuncServeHTTP_Returns, ) } diff --git a/internal/pkg/instrumentors/bpf/net/http/server/bpf_bpfel_x86.go b/internal/pkg/instrumentors/bpf/net/http/server/bpf_bpfel_x86.go index 1bb9e8c04..b82c8fd2b 100644 --- a/internal/pkg/instrumentors/bpf/net/http/server/bpf_bpfel_x86.go +++ b/internal/pkg/instrumentors/bpf/net/http/server/bpf_bpfel_x86.go @@ -68,8 +68,8 @@ type bpfSpecs struct { // // 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"` + UprobeHandlerFuncServeHTTP *ebpf.ProgramSpec `ebpf:"uprobe_HandlerFunc_ServeHTTP"` + UprobeHandlerFuncServeHTTP_Returns *ebpf.ProgramSpec `ebpf:"uprobe_HandlerFunc_ServeHTTP_Returns"` } // bpfMapSpecs contains maps before they are loaded into the kernel. @@ -129,14 +129,14 @@ func (m *bpfMaps) Close() error { // // 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"` + UprobeHandlerFuncServeHTTP *ebpf.Program `ebpf:"uprobe_HandlerFunc_ServeHTTP"` + UprobeHandlerFuncServeHTTP_Returns *ebpf.Program `ebpf:"uprobe_HandlerFunc_ServeHTTP_Returns"` } func (p *bpfPrograms) Close() error { return _BpfClose( - p.UprobeServerMuxServeHTTP, - p.UprobeServerMuxServeHTTP_Returns, + p.UprobeHandlerFuncServeHTTP, + p.UprobeHandlerFuncServeHTTP_Returns, ) } diff --git a/internal/pkg/instrumentors/bpf/net/http/server/probe.go b/internal/pkg/instrumentors/bpf/net/http/server/probe.go index 34fc4058e..6e9b53532 100644 --- a/internal/pkg/instrumentors/bpf/net/http/server/probe.go +++ b/internal/pkg/instrumentors/bpf/net/http/server/probe.go @@ -152,7 +152,7 @@ func (h *Instrumentor) registerProbes(ctx *context.InstrumentorContext, funcName return } - up, err := ctx.Executable.Uprobe("", h.bpfObjects.UprobeServerMuxServeHTTP, &link.UprobeOptions{ + up, err := ctx.Executable.Uprobe("", h.bpfObjects.UprobeHandlerFuncServeHTTP, &link.UprobeOptions{ Address: offset, }) if err != nil { @@ -164,7 +164,7 @@ func (h *Instrumentor) registerProbes(ctx *context.InstrumentorContext, funcName h.uprobes = append(h.uprobes, up) for _, ret := range retOffsets { - retProbe, err := ctx.Executable.Uprobe("", h.bpfObjects.UprobeServerMuxServeHTTP_Returns, &link.UprobeOptions{ + retProbe, err := ctx.Executable.Uprobe("", h.bpfObjects.UprobeHandlerFuncServeHTTP_Returns, &link.UprobeOptions{ Address: ret, }) if err != nil { diff --git a/internal/pkg/instrumentors/utils/kernel.go b/internal/pkg/instrumentors/utils/kernel.go new file mode 100644 index 000000000..88428c298 --- /dev/null +++ b/internal/pkg/instrumentors/utils/kernel.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. + +package utils + +import ( + "strings" + "syscall" + + "github.com/hashicorp/go-version" +) + +func GetLinuxKernelVersion() (*version.Version, error) { + var utsname syscall.Utsname + + if err := syscall.Uname(&utsname); err != nil { + return nil, err + } + + var buf [65]byte + for i, v := range utsname.Release { + buf[i] = byte(v) + } + + ver := string(buf[:]) + if strings.Contains(ver, "-") { + ver = strings.Split(ver, "-")[0] + } + + return version.NewVersion(ver) +} diff --git a/internal/pkg/process/allocate.go b/internal/pkg/process/allocate.go new file mode 100644 index 000000000..c7d9adb2e --- /dev/null +++ b/internal/pkg/process/allocate.go @@ -0,0 +1,86 @@ +// 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. + +package process + +import ( + "fmt" + "os" + "runtime" + + "go.opentelemetry.io/auto/internal/pkg/log" + "go.opentelemetry.io/auto/internal/pkg/process/ptrace" +) + +// AllocationDetails are the details about allocated memory. +type AllocationDetails struct { + StartAddr uint64 + EndAddr uint64 +} + +// Allocate allocates memory for the instrumented process. +func Allocate(pid int) (*AllocationDetails, error) { + mapSize := uint64(os.Getpagesize() * runtime.NumCPU() * 8) + addr, err := remoteAllocate(pid, mapSize) + if err != nil { + log.Logger.Error(err, "Failed to mmap") + return nil, err + } + + log.Logger.V(0).Info("mmaped remote memory", "start_addr", fmt.Sprintf("%X", addr), + "end_addr", fmt.Sprintf("%X", addr+mapSize)) + + return &AllocationDetails{ + StartAddr: addr, + EndAddr: addr + mapSize, + }, nil +} + +func remoteAllocate(pid int, mapSize uint64) (uint64, error) { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + program, err := ptrace.NewTracedProgram(pid, log.Logger) + if err != nil { + log.Logger.Error(err, "Failed to attach ptrace", "pid", pid) + return 0, err + } + + defer func() { + log.Logger.V(0).Info("Detaching from process", "pid", pid) + err := program.Detach() + if err != nil { + log.Logger.Error(err, "Failed to detach ptrace", "pid", pid) + } + }() + fd := -1 + addr, err := program.Mmap(mapSize, uint64(fd)) + if err != nil { + log.Logger.Error(err, "Failed to mmap", "pid", pid) + return 0, err + } + + err = program.Madvise(addr, mapSize) + if err != nil { + log.Logger.Error(err, "Failed to madvise", "pid", pid) + return 0, err + } + + err = program.Mlock(addr, mapSize) + if err != nil { + log.Logger.Error(err, "Failed to mlock", "pid", pid) + return 0, err + } + + return addr, nil +} diff --git a/internal/pkg/process/analyze.go b/internal/pkg/process/analyze.go index fbcb6b704..10c4a485f 100644 --- a/internal/pkg/process/analyze.go +++ b/internal/pkg/process/analyze.go @@ -19,18 +19,10 @@ import ( "errors" "fmt" "os" - "runtime" "github.com/hashicorp/go-version" "go.opentelemetry.io/auto/internal/pkg/log" - "go.opentelemetry.io/auto/internal/pkg/process/ptrace" -) - -const ( - // 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. @@ -42,12 +34,6 @@ type TargetDetails struct { AllocationDetails *AllocationDetails } -// AllocationDetails are the details about allocated memory. -type AllocationDetails struct { - StartAddr uint64 - EndAddr uint64 -} - // Func represents a function target. type Func struct { Name string @@ -84,32 +70,6 @@ func (t *TargetDetails) GetFunctionReturns(name string) ([]uint64, error) { return nil, fmt.Errorf("could not find returns for function %s", name) } -func (a *Analyzer) remoteMmap(pid int, mapSize uint64) (uint64, error) { - runtime.LockOSThread() - defer runtime.UnlockOSThread() - program, err := ptrace.NewTracedProgram(pid, log.Logger) - if err != nil { - log.Logger.Error(err, "Failed to attach ptrace", "pid", pid) - return 0, err - } - - defer func() { - log.Logger.V(0).Info("Detaching from process", "pid", pid) - err := program.Detach() - if err != nil { - log.Logger.Error(err, "Failed to detach ptrace", "pid", pid) - } - }() - fd := -1 - addr, err := program.Mmap(mapSize, uint64(fd)) - if err != nil { - log.Logger.Error(err, "Failed to mmap", "pid", pid) - return 0, err - } - - return addr, nil -} - // Analyze returns the target details for an actively running process. func (a *Analyzer) Analyze(pid int, relevantFuncs map[string]interface{}) (*TargetDetails, error) { result := &TargetDetails{ @@ -134,19 +94,6 @@ func (a *Analyzer) Analyze(pid int, relevantFuncs map[string]interface{}) (*Targ result.GoVersion = goVersion result.Libraries = modules - addr, err := a.remoteMmap(pid, mapSize) - if err != nil { - log.Logger.Error(err, "Failed to mmap") - return nil, err - } - log.Logger.V(0).Info("mmaped remote memory", "start_addr", fmt.Sprintf("%X", addr), - "end_addr", fmt.Sprintf("%X", addr+mapSize)) - - result.AllocationDetails = &AllocationDetails{ - StartAddr: addr, - EndAddr: addr + mapSize, - } - if err != nil { return nil, err } diff --git a/internal/pkg/process/ptrace/ptrace_linux.go b/internal/pkg/process/ptrace/ptrace_linux.go index f1a2088a6..aeabb6bd3 100644 --- a/internal/pkg/process/ptrace/ptrace_linux.go +++ b/internal/pkg/process/ptrace/ptrace_linux.go @@ -22,11 +22,22 @@ import ( "syscall" "github.com/go-logr/logr" + "github.com/hashicorp/go-version" "github.com/pkg/errors" + + "go.opentelemetry.io/auto/internal/pkg/instrumentors/utils" + "go.opentelemetry.io/auto/internal/pkg/log" ) const waitPidErrorMessage = "waitpid ret value: %d" +const ( + // MADV_POPULATE_READ. + MadvisePopulateRead = 0x16 + // MADV_POPULATE_WRITE. + MadvisePopulateWrite = 0x17 +) + var threadRetryLimit = 10 // TracedProgram is a program traced by ptrace. @@ -207,5 +218,30 @@ func (p *TracedProgram) Step() error { // Mmap runs mmap syscall. func (p *TracedProgram) Mmap(length uint64, fd uint64) (uint64, error) { - return p.Syscall(syscall.SYS_MMAP, 0, length, syscall.PROT_READ|syscall.PROT_WRITE|syscall.PROT_EXEC, syscall.MAP_ANON|syscall.MAP_PRIVATE|syscall.MAP_POPULATE, fd, 0) + return p.Syscall(syscall.SYS_MMAP, 0, length, syscall.PROT_READ|syscall.PROT_WRITE, syscall.MAP_ANON|syscall.MAP_PRIVATE|syscall.MAP_POPULATE|syscall.MAP_LOCKED, fd, 0) +} + +// Madvise runs madvise syscall. +func (p *TracedProgram) Madvise(addr uint64, length uint64) error { + advice := uint64(syscall.MADV_WILLNEED) + ver, err := utils.GetLinuxKernelVersion() + if err != nil { + return errors.WithStack(err) + } + + minVersion := version.Must(version.NewVersion("5.14")) + log.Logger.V(0).Info("Detected linux kernel version", "version", ver) + if ver.GreaterThanOrEqual(minVersion) { + advice = syscall.MADV_WILLNEED | MadvisePopulateRead | MadvisePopulateWrite + } + + _, err = p.Syscall(syscall.SYS_MADVISE, addr, length, advice, 0, 0, 0) + return err +} + +// Mlock runs mlock syscall. +func (p *TracedProgram) Mlock(addr uint64, length uint64) error { + ret, err := p.Syscall(syscall.SYS_MLOCK, addr, length, 0, 0, 0, 0) + log.Logger.V(0).Info("mlock ret", "ret", ret) + return err }