使用ebpf 监控golang 应用

一、背景

1、grpc 在微服务中使用情况分析

协议、微服务框架及eBPF采集支持状态

使用热度

协议

Golang框架

Java框架

协议描述

eBPF支持状态

1

HTTP

Dubbo-go、go-micro、go-zero、kratos、CloudWeGo-Kitex、Goa、Jupiter、tars-go

SpringBoot, Dubbo

https://cn.dubbo.apache.org/zh/docs3-v2/java-sdk/reference-manual/protocol

支持,hook内核函数

2

HTTPS

Dubbo-go、go-micro、go-zero、kratos、CloudWeGo-Kitex、Goa、Jupiter、tars-go

SpringBoot, Dubbo

https://cn.dubbo.apache.org/zh/docs3-v2/java-sdk/reference-manual/protocol

Go/Python支持,hook ssl库函数;
Java不支持

3

gRPC

Dubbo-go、go-micro、CloudWeGo-Kitex、Jupiter、go-zero、kratos、Goa

SpringBoot, Dubbo

https://grpc.io/
基于HTTP 2

Go/Python支持,hook grpc库函数
Java不支持

4

Thrift

Dubbo-go、CloudWeGo-Kitex

Dubbo、SpringBoot

https://thrift.apache.org/
基于TCP

暂不支持

5

Dubbo

Dubbo-go

Dubbo

https://cn.dubbo.apache.org/zh/docs3-v2/java-sdk/reference-manual/protocol

暂不支持

6

Triple

Dubbo-go

Dubbo

https://cn.dubbo.apache.org/zh/docs3-v2/java-sdk/concepts-and-architecture/triple/

暂不支持

7

Webservice

Dubbo、SpringBoot

https://cn.dubbo.apache.org/zh/docs3-v2/java-sdk/reference-manual/protocol/webservice/

暂不支持

8

Hessian

Dubbo-go

Dubbo、SpringBoot

https://cn.dubbo.apache.org/zh/docs3-v2/java-sdk/reference-manual/protocol/hessian/

暂不支持

9

RMI

Dubbo、SpringBoot

https://cn.dubbo.apache.org/zh/docs3-v2/java-sdk/reference-manual/protocol/rmi/

暂不支持

10

XDS

Dubbo-go

Dubbo

https://www.envoyproxy.io/docs/envoy/latest/api-docs/xds_protocol

暂不支持

11

TTHeader

CloudWeGo-Kitex

https://www.cloudwego.io/zh/docs/kitex/reference/transport_protocol_ttheader/

暂不支持

12

tars

tars-go

https://tarscloud.github.io/TarsDocs/base/tars-intro.html#chapter-4-1

暂不支持

使用ebpf 监控grpc-go的应用,grpc-go http2 client的处理点


func (l *loopyWriter) writeHeader(streamID uint32, endStream bool, hf []hpack.HeaderField, onWrite func()) error {
......
}

// operateHeaders takes action on the decoded headers.
func (t *http2Client) operateHeaders(frame *http2.MetaHeadersFrame) {
}

使用ebpf 监控埋点:

SEC("uprobe/google.golang.org/grpc/internal/transport.(*loopyWriter).writeHeader")
int uprobe__probe_http2_client_operate_headers(struct pt_regs* ctx) {
}

运行流程:

我们在 uprobe__probe_http2_client_operate_headers 里做一些处理,统计grpc的数据,进行遥测。编写代码过程中我们需要关注的一些事情

2、golang 程序中 t *http2Client 和  frame *http2.MetaHeadersFrame 在寄存器中的位置是如何分配的?

type MetaHeadersFrame struct {
	*HeadersFrame

	// Fields are the fields contained in the HEADERS and
	// CONTINUATION frames. The underlying slice is owned by the
	// Framer and must not be retained after the next call to
	// ReadFrame.
	//
	// Fields are guaranteed to be in the correct http2 order and
	// not have unknown pseudo header fields or invalid header
	// field names or values. Required pseudo header fields may be
	// missing, however. Use the MetaHeadersFrame.Pseudo accessor
	// method access pseudo headers.
	Fields []hpack.HeaderField

	// Truncated is whether the max header list size limit was hit
	// and Fields is incomplete. The hpack decoder state is still
	// valid, however.
	Truncated bool
}

3、http2Client 和 MetaHeadersFrame 结构体中各个成员的偏移量如何确定?

type http2Client struct {
	lastRead  int64 // Keep this field 64-bit aligned. Accessed atomically.
	ctx       context.Context
	cancel    context.CancelFunc
	ctxDone   <-chan struct{} // Cache the ctx.Done() chan.
	userAgent string
	// address contains the resolver returned address for this transport.
	// If the `ServerName` field is set, it takes precedence over `CallHdr.Host`
	// passed to `NewStream`, when determining the :authority header.
	address    resolver.Address
	md         metadata.MD
.....
}

 以 MetaHeadersFrame 为例子

 我们在开发中需要找到 正确的offset 才能正确的取到内存。

二、我们需要做什么?

1、如何确定参数在寄存器中的位置?

我们使用llvm-dwarfdump-14 查看二进制中dwarf 构成:

llvm-dwarfdump-14 grpc-client |grep "loopyWriter).writeHeader" -C 100

返回格式:

0x0018c375:   DW_TAG_subprogram
                DW_AT_name	("google.golang.org/grpc/internal/transport.(*loopyWriter).writeHeader")
                DW_AT_low_pc	(0x00000000007139a0)
                DW_AT_high_pc	(0x0000000000713df2)
                DW_AT_frame_base	(DW_OP_call_frame_cfa)
                DW_AT_decl_file	("/home/zhanglei/data/grpc-demo/vendor/google.golang.org/grpc/internal/transport/controlbuf.go")
                DW_AT_external	(0x01)

0x0018c3d2:     DW_TAG_formal_parameter
                  DW_AT_name	("l")
                  DW_AT_variable_parameter	(0x00)
                  DW_AT_decl_line	(677)
                  DW_AT_type	(0x00000000000e6bc2 "google.golang.org/grpc/internal/transport.loopyWriter *")
                  DW_AT_location	(0x001a603f: 
                     [0x00000000007139a0, 0x00000000007139fc): DW_OP_reg0 RAX
                     [0x00000000007139fc, 0x0000000000713df2): DW_OP_call_frame_cfa)

0x0018c3e0:     DW_TAG_formal_parameter
                  DW_AT_name	("streamID")
                  DW_AT_variable_parameter	(0x00)
                  DW_AT_decl_line	(677)
                  DW_AT_type	(0x0000000000082ad4 "uint32")
                  DW_AT_location	(0x001a6085: 
                     [0x00000000007139a0, 0x0000000000713a02): DW_OP_reg3 RBX
                     [0x0000000000713a02, 0x0000000000713df2): DW_OP_fbreg +8)

0x0018c3f5:     DW_TAG_formal_parameter
                  DW_AT_name	("endStream")
                  DW_AT_variable_parameter	(0x00)
                  DW_AT_decl_line	(677)
                  DW_AT_type	(0x0000000000082b87 "bool")
                  DW_AT_location	(0x001a60cc: 
                     [0x00000000007139a0, 0x0000000000713a02): DW_OP_reg2 RCX
                     [0x0000000000713a02, 0x0000000000713df2): DW_OP_fbreg +12)

0x0018c40b:     DW_TAG_formal_parameter
                  DW_AT_name	("hf")
                  DW_AT_variable_parameter	(0x00)
                  DW_AT_decl_line	(677)
                  DW_AT_type	(0x00000000000e2826 "[]golang.org/x/net/http2/hpack.HeaderField")
                  DW_AT_location	(0x001a6113: 
                     [0x00000000007139a0, 0x0000000000713a02): DW_OP_reg5 RDI, DW_OP_piece 0x8, DW_OP_reg4 RSI, DW_OP_piece 0x8, DW_OP_reg8 R8, DW_OP_piece 0x8
                     [0x0000000000713a02, 0x0000000000713a4e): DW_OP_fbreg +16, DW_OP_piece 0x8, DW_OP_fbreg +24, DW_OP_piece 0x8, DW_OP_piece 0x8
                     [0x0000000000713a4e, 0x0000000000713df2): DW_OP_piece 0x8, DW_OP_fbreg +24, DW_OP_piece 0x8, DW_OP_piece 0x8)

0x0018c41a:     DW_TAG_formal_parameter
                  DW_AT_name	("onWrite")
                  DW_AT_variable_parameter	(0x00)
                  DW_AT_decl_line	(677)
                  DW_AT_type	(0x00000000000844f2 "func()")
                  DW_AT_location	(0x001a6184: 
                     [0x00000000007139a0, 0x0000000000713a02): DW_OP_reg9 R9)

数据格式简介:

我们可以找到func (l *loopyWriter) writeHeader 的参数的所在寄存器的位置

DW_AT_name	("l")  的 [0x00000000007139a0, 0x00000000007139fc): DW_OP_reg0 RAX 在寄存器0 上
DW_AT_name	("streamID") : DW_OP_reg3 RBX 在寄存器3上
DW_AT_name	("endStream")  DW_OP_reg2 RCX 在寄存器2上
DW_AT_name	("hf") 在寄存器5 上 DW_OP_reg5 RDI, DW_OP_piece 0x8, DW_OP_reg4 RSI, DW_OP_piece 0x8, DW_OP_reg8 R8, DW_OP_piece 0x8
DW_AT_name	("onWrite")  DW_OP_reg9 R9 在寄存器9上

2、如何定位结构体中的成员

0x000e6c07:   DW_TAG_structure_type
                DW_AT_name	("google.golang.org/grpc/internal/transport.loopyWriter")
                DW_AT_byte_size	(88)
                DW_AT_GO_kind	(0x19)
                DW_AT_GO_runtime_type	(0x00000000000b9a20)

0x000e6c48:     DW_TAG_member
                  DW_AT_name	("side")
                  DW_AT_data_member_location	(0)
                  DW_AT_type	(0x00000000000e5e96 "google.golang.org/grpc/internal/transport.side")
                  DW_AT_GO_embedded_field	(0x00)

0x000e6c54:     DW_TAG_member
                  DW_AT_name	("cbuf")
                  DW_AT_data_member_location	(8)
                  DW_AT_type	(0x00000000000e681d "google.golang.org/grpc/internal/transport.controlBuffer *")
                  DW_AT_GO_embedded_field	(0x00)

0x000e6c60:     DW_TAG_member
                  DW_AT_name	("sendQuota")
                  DW_AT_data_member_location	(16)
                  DW_AT_type	(0x0000000000082ad4 "uint32")
                  DW_AT_GO_embedded_field	(0x00)

0x000e6c71:     DW_TAG_member
                  DW_AT_name	("oiws")
                  DW_AT_data_member_location	(20)
                  DW_AT_type	(0x0000000000082ad4 "uint32")
                  DW_AT_GO_embedded_field	(0x00)

0x000e6c7d:     DW_TAG_member
                  DW_AT_name	("estdStreams")
                  DW_AT_data_member_location	(24)
                  DW_AT_type	(0x00000000000e6d3c "map[uint32]*google.golang.org/grpc/internal/transport.outStream")
                  DW_AT_GO_embedded_field	(0x00)

0x000e6c90:     DW_TAG_member
                  DW_AT_name	("activeStreams")
                  DW_AT_data_member_location	(32)
                  DW_AT_type	(0x00000000000e6eaa "google.golang.org/grpc/internal/transport.outStreamList *")
                  DW_AT_GO_embedded_field	(0x00)

0x000e6ca5:     DW_TAG_member
                  DW_AT_name	("framer")
                  DW_AT_data_member_location	(40)
                  DW_AT_type	(0x00000000000e6f8a "google.golang.org/grpc/internal/transport.framer *")
                  DW_AT_GO_embedded_field	(0x00)

 以 loopyWriter 结构体为例子:

type loopyWriter struct {
	side      side
	cbuf      *controlBuffer
	sendQuota uint32
	oiws      uint32 // outbound initial window size.
	// estdStreams is map of all established streams that are not cleaned-up yet.
	// On client-side, this is all streams whose headers were sent out.
	// On server-side, this is all streams whose headers were received.
	estdStreams map[uint32]*outStream // Established streams.
	// activeStreams is a linked-list of all streams that have data to send and some
	// stream-level flow control quota.
	// Each of these streams internally have a list of data items(and perhaps trailers
	// on the server-side) to be sent out.
	activeStreams *outStreamList
	framer        *framer
	hBuf          *bytes.Buffer  // The buffer for HPACK encoding.
	hEnc          *hpack.Encoder // HPACK encoder.
	bdpEst        *bdpEstimator
	draining      bool

	// Side-specific handlers
	ssGoAwayHandler func(*goAway) (bool, error)
}

side 的偏移量是  0 

cbuf 是 8 

sendQuota 是 16 

oiws 是 20

estdStreams 是 24

activeStreams 是 32 

framer 是 40

3、如何确定接口类型地址

nm grpc-client|grep "TCPConn,net.Conn"

000000000093d280 R go.itab.*net.TCPConn,net.Conn

 发现对应的符号表地址:

nm grpc-server|grep "TCPConn,net.Conn"
000000000093bfc0 R go.itab.*net.TCPConn,net.Conn

4、使用gdb 证明这些数据

(gdb) i args
l = 0xc00011c060
streamID = 1
endStream = false
hf = {array = 0xc00007e870, len = 2, cap = 2}
onWrite = {void (void)} 0x0
~r0 = <optimised out>
(gdb) i r
rax            0xc00011c060        824634884192
rbx            0x1                 1
rcx            0x0                 0
rdx            0xc0001b29c0        824635500992
rsi            0x2                 2
rdi            0xc00007e870        824634239088
rbp            0xc000129ec8        0xc000129ec8
rsp            0xc000129e78        0xc000129e78
r8             0x2                 2
r9             0xc000021f80        824633859968
r10            0xc00007e8c0        824634239168
r11            0x1                 1
r12            0x1                 1
r13            0xffffffffffffffff  -1
r14            0xc00019d520        824635413792
r15            0x0                 0
rip            0x7160c0            0x7160c0 <google.golang.org/grpc/internal/transport.(*loopyWriter).writeHeader>
eflags         0x246               [ PF ZF IF ]
cs             0x33                51
ss             0x2b                43
ds             0x0                 0
es             0x0                 0
fs             0x0                 0
gs             0x0                 0

发现l 位于寄存器 

l -> rax

streamID -> rbx

endStream -> rcx

hf -> rdi

接口类型:

(gdb) p (*l.framer.writer).conn
$8 = {tab = 0x93bfc0 <TCPConn,net.Conn>, data = 0xc000010290}

 发现对应的符号表地址:

nm grpc-server|grep "TCPConn,net.Conn"
000000000093bfc0 R go.itab.*net.TCPConn,net.Conn

三、发现golang应用

uprober 包含了 多个rule

每个rule 又包含Selector和 Checker

Selector 用来根据版本选择对应的Hooker

Checker 用来检查 动态库或者二进制文件是否满足匹配规则

 

运行流程:

四、golang 注册ebpf 钩子

五.使用偏移量获取golang变量

定义 location 结构体:

typedef struct {
    __s64 stack_offset; // 栈上的偏移量
    __s64 _register; // 寄存器的偏移量
    __u8 in_register; //是否在寄存器上
    __u8 exists;
} location_t;

// golang 切片
typedef struct {
    location_t ptr;
    location_t len;
    location_t cap;
} slice_location_t;

golang 参数变量在寄存器的布局:

// This function was adapted from https://github.com/go-delve/delve:
// - https://github.com/go-delve/delve/blob/cd9e6c02a6ca5f0d66c1f770ee10a0d8f4419333/pkg/proc/internal/ebpf/bpf/trace.bpf.c#L43
// which is licensed under MIT.
static __always_inline int read_register(struct pt_regs* ctx, int64_t regnum, void* dest) {
    // This volatile temporary variable is need when building with clang-14,
    // or the verifier will complain that we dereference a modified context
    // pointer.
    //
    // What happened in this case, is that the compiler tried to be smart by
    // incrementing the context pointer, before jumping to code that will
    // copy the value pointed to by the new pointer to `dest`. The generated
    // code looked like this:
    //
    //      r1 += 40           // Increment the ptr
    //      goto +3 <LBB0_9>   // goto __builtin_memcpy
    //
    // What the memcpy does is deference the resulting pointer to get the
    // CPU register value (that’s where the bug was), then put it in the
    // dest location:
    //
    //      r1 = *(u64 *)(r1 + 0)  // BUG: Get the register value.
    //                             // This is the "modified context pointer"
    //      *(u64 *)(r3 + 0) = r1  // Put it in dest
    //
    // By incrementing the pointer before dereferencing it, the verifier no
    // longer considering r1 to be a pointer to the context, but as a
    // pointer to some random memory address (even though it is in the
    // memory the range of the context struct).
    //
    // What we want the compiler to generate is something like this:
    //
    //      // Switch branch:
    //      r1 = *(u64 *)(r1 + 40) // read value to tmp var
    //      goto +30 <LBB0_39>     // goto *dest = tmp
    //
    //      // *dest = tmp
    //      *(u64 *)(r3 + 0) = r1
    //
    // This volatile `tmp` variable makes the compiler generate the code above.
    volatile u64 tmp = 0;
    switch (regnum) {
        case 0: // RAX
            tmp = ctx->ax;
            break;
        case 1: // RDX
            tmp = ctx->dx;
            break;
        case 2: // RCX
            tmp = ctx->cx;
            break;
        case 3: // RBX
            tmp = ctx->bx;
            break;
        case 4: // RSI
            tmp = ctx->si;
            break;
        case 5: // RDI
            tmp = ctx->di;
            break;
        case 6: // RBP
            tmp = ctx->bp;
            break;
        case 7: // RSP
            tmp = ctx->sp;
            break;
        case 8: // R8
            tmp = ctx->r8;
            break;
        case 9: // R9
            tmp = ctx->r9;
            break;
        case 10: // R10
            tmp = ctx->r10;
            break;
        case 11: // R11
            tmp = ctx->r11;
            break;
        case 12: // R12
            tmp = ctx->r12;
            break;
        case 13: // R13
            tmp = ctx->r13;
            break;
        case 14: // R14
            tmp = ctx->r14;
            break;
        case 15: // R15
            tmp = ctx->r15;
            break;
        default:
                  return 1;
    }
    *(u64*)dest = tmp;
    return 0;
}

 读取偏移量:

static __always_inline int read_location(struct pt_regs* ctx, location_t* loc, size_t size, void* dest) {
    if (!loc->exists) {
        return 0;
    }

    if (loc->in_register) {
        if (size != REG_SIZE) {
            return 1;
        }

        return read_register(ctx, loc->_register, dest);
    } else {
        return read_stack(ctx, loc->stack_offset, size, dest);
    }
}
static __always_inline int read_stack(struct pt_regs* ctx, int64_t stack_offset, size_t size, void* dest) {
    // `ctx->sp` is correct for both x86_64 and ARM64
    uintptr_t stack_pointer = (uintptr_t) ctx->sp;
    uintptr_t address = stack_pointer + stack_offset;
    return bpf_probe_read_user(dest, size, (void*) address);
}

举一个例子:

SEC("uprobe/google.golang.org/grpc/internal/transport.(*loopyWriter).writeHeader")
int uprobe__probe_loopy_writer_write_header(struct pt_regs* ctx) {
    return 0;
}

 从map读取偏移量:

uint32_t pid = bpf_get_current_pid_tgid() >> 32;
  go_http2_symaddrs_t* symaddrs = bpf_map_lookup_elem(&http2_symaddrs_map, &pid);
  if (symaddrs == NULL) {
    log_trace("uprobe__probe_loopy_writer_write_header:writeHeader:symaddrs is NULL\n");
    return 0;
  }

 偏移量存储结构体:

typedef struct {
  // ---- itable symbols ----

  // io.Writer interface types.
  __s64 http_http2bufferedWriter;  // "go.itab.*net/http.http2bufferedWriter,io.Writer
  __s64 transport_bufWriter;  // "google.golang.org/grpc/internal/transport.bufWriter,io.Writer

  // ---- function argument locations ----

  // Arguments of net/http.(*http2Framer).WriteDataPadded.
  location_t http2Framer_WriteDataPadded_f_loc;          // 8
  location_t http2Framer_WriteDataPadded_streamID_loc;   // 16
  location_t http2Framer_WriteDataPadded_endStream_loc;  // 20
  location_t http2Framer_WriteDataPadded_data_ptr_loc;   // 24
  location_t http2Framer_WriteDataPadded_data_len_loc;   // 32

  // Arguments of golang.org/x/net/http2.(*Framer).WriteDataPadded.
  location_t http2_WriteDataPadded_f_loc;          // 8
  location_t http2_WriteDataPadded_streamID_loc;   // 16
  location_t http2_WriteDataPadded_endStream_loc;  // 20
  location_t http2_WriteDataPadded_data_ptr_loc;   // 24
  location_t http2_WriteDataPadded_data_len_loc;   // 32

  // Arguments of net/http.(*http2Framer).checkFrameOrder.
  location_t http2Framer_checkFrameOrder_fr_loc;  // 8
  location_t http2Framer_checkFrameOrder_f_loc;   // 16

  // Arguments of golang.org/x/net/http2.(*Framer).checkFrameOrder.
  location_t http2_checkFrameOrder_fr_loc;  // 8
  location_t http2_checkFrameOrder_f_loc;   // 16

  // Arguments of net/http.(*http2writeResHeaders).writeFrame.
  location_t writeFrame_w_loc;    // 8
  location_t writeFrame_ctx_loc;  // 16

  // Arguments of golang.org/x/net/http2/hpack.(*Encoder).WriteField.
  location_t WriteField_e_loc;  // 8
  // Note that the HeaderField `f` is further broken down to its name and value members.
  // This is done so we can better control the location of these members from user-space.
  // In theory, there could be an ABI that splits these two members across stack and registers.
  location_t WriteField_f_name_loc;   // 16
  location_t WriteField_f_value_loc;  // 32

  // Arguments of net/http.(*http2serverConn).processHeaders.
  location_t processHeaders_sc_loc;  // 8
  location_t processHeaders_f_loc;   // 16

  // Arguments of google.golang.org/grpc/internal/transport.(*http2Server).operateHeaders.
  location_t http2Server_operateHeaders_t_loc;      // 8
  location_t http2Server_operateHeaders_frame_loc;  // 16

  // Arguments of google.golang.org/grpc/internal/transport.(*http2Client).operateHeaders.
  location_t http2Client_operateHeaders_t_loc;      // 8
  location_t http2Client_operateHeaders_frame_loc;  // 16

  // Arguments of google.golang.org/grpc/internal/transport.(*loopyWriter).writeHeader.
  location_t writeHeader_l_loc;          // 8
  location_t writeHeader_streamID_loc;   // 16
  location_t writeHeader_endStream_loc;  // 20
  slice_location_t writeHeader_hf_loc;     // 24

  // ---- struct member offsets ----

  // Struct member offsets.
  // Naming maintains golang style: <struct>_<member>_offset
  // Note: values in comments represent known offsets, in case we need to fall back.
  //       Eventually, they should be removed, because they are not reliable.

  // Members of golang.org/x/net/http2/hpack.HeaderField.
  int32_t HeaderField_Name_offset;   // 0
  int32_t HeaderField_Value_offset;  // 16

  // Members of google.golang.org/grpc/internal/transport.http2Server.
  int32_t http2Server_conn_offset;  // 16 or 24

  // Members of google.golang.org/grpc/internal/transport.http2Client.
  int32_t http2Client_conn_offset;  // 64

  // Members of google.golang.org/grpc/internal/transport.loopyWriter.
  int32_t loopyWriter_framer_offset;  // 40

  // Members of golang.org/x/net/net/http2.Framer.
  int32_t Framer_w_offset;  // 112

  // Members of golang.org/x/net/http2.MetaHeadersFrame.
  int32_t MetaHeadersFrame_HeadersFrame_offset;  // 0
  int32_t MetaHeadersFrame_Fields_offset;        // 0

  // Members of golang.org/x/net/http2.HeadersFrame.
  int32_t HeadersFrame_FrameHeader_offset;  // 0

  // Members of golang.org/x/net/http2.FrameHeader.
  int32_t FrameHeader_Type_offset;      // 1
  int32_t FrameHeader_Flags_offset;     // 2
  int32_t FrameHeader_StreamID_offset;  // 8

  // Members of golang.org/x/net/http2.DataFrame.
  int32_t DataFrame_data_offset;  // 16

  // Members of google.golang.org/grpc/internal/transport.bufWriter.
  int32_t bufWriter_conn_offset;  // 40

  // Members of net/http.http2serverConn.
  int32_t http2serverConn_conn_offset;          // 16
  int32_t http2serverConn_hpackEncoder_offset;  // 360

  // Members of net/http.http2HeadersFrame
  int32_t http2HeadersFrame_http2FrameHeader_offset;  // 0

  // Members of net/http.http2FrameHeader.
  int32_t http2FrameHeader_Type_offset;      // 1
  int32_t http2FrameHeader_Flags_offset;     // 2
  int32_t http2FrameHeader_StreamID_offset;  // 8

  // Members of golang.org/x/net/http2.DataFrame.
  int32_t http2DataFrame_data_offset;  // 16

  // Members of net/http.http2writeResHeaders.
  int32_t http2writeResHeaders_streamID_offset;   // 0
  int32_t http2writeResHeaders_endStream_offset;  // 48

  // Members of net/http.http2MetaHeadersFrame.
  int32_t http2MetaHeadersFrame_http2HeadersFrame_offset;  // 0
  int32_t http2MetaHeadersFrame_Fields_offset;             // 8

  // Members of net/http.http2Framer.
  int32_t http2Framer_w_offset;  // 112

  // Members of net/http.http2bufferedWriter
  int32_t http2bufferedWriter_w_offset;  // 0
} go_http2_symaddrs_t;

读取偏移量:

  void* loopy_writer_ptr = NULL;
  if (read_location(ctx, &symaddrs->writeHeader_l_loc,
  sizeof(loopy_writer_ptr), &loopy_writer_ptr)) {
     log_trace("uprobe__probe_loopy_writer_write_header:5\n");
     return 0;
  }

六、如何记录错误调试

1、打日志调试

#define log_trace(fmt, ...)                                        \
    ({                                                             \
        char ____fmt[] = fmt;                                      \
        bpf_trace_printk(____fmt, sizeof(____fmt), ##__VA_ARGS__); \
    })

/* Macro to output debug logs to /sys/kernel/debug/tracing/trace_pipe
 */
#if DEBUG == 1
#define log_debug(fmt, ...)                                        \
    ({                                                             \
        char ____fmt[] = fmt;                                      \
        bpf_trace_printk(____fmt, sizeof(____fmt), ##__VA_ARGS__); \
    })
#else

使用:

log_debug("[grpc-c:lookup_version:]grpc-c version is not support;pid:%d;version:%d;\n", pid, *version);

2、记录metrics

主要记录行号和文件

#ifndef TRACER_TELEMETRY
#define TRACER_TELEMETRY

#ifndef TRACER_TELEMETRY_KEY_LIMIT
#define TRACER_TELEMETRY_KEY_LIMIT 4096
#endif

typedef struct {
    char file[TRACER_TELEMETRY_KEY_LIMIT];
    uint64_t line;
} tracer_telemetry_key;

BPF_HASH_MAP(tracer_telemetry, tracer_telemetry_key, uint64_t, 1024);
BPF_PERCPU_ARRAY_MAP(tracer_telemetry_heap, __u32, tracer_telemetry_key, 1);

static __always_inline tracer_telemetry_key* alloc_tracer_telemetry_key() {
    uint32_t kZero = 0;
    tracer_telemetry_key* value = bpf_map_lookup_elem(&tracer_telemetry_heap, &kZero);
    if (value == NULL) {
        return NULL;
    }

    value->line = 0;
    return value;
}

static __always_inline void increment_tracer_telemetry_count(const char* file, uint64_t line) {
    tracer_telemetry_key* key = alloc_tracer_telemetry_key();
    if (key == NULL) {
        return;
    }

    if (bpf_probe_read_str(&key->file, sizeof(key->file), file) == -1) {
        return;
    }

    key->line = line;

    uint64_t *val = NULL;
    val = bpf_map_lookup_elem(&tracer_telemetry, key);
    if (val == NULL) {
        log_debug("tracer:key->file:%s;file:%s\n", key->file, file);
        uint64_t tmp_value = 0;
        bpf_map_update_with_telemetry(tracer_telemetry, key, &tmp_value, BPF_NOEXIST);
        return;
    }

    val = bpf_map_lookup_elem(&tracer_telemetry, key);
    if (val == NULL) {
        return;
    }

    __sync_fetch_and_add(val, 1);
}

#ifndef INCR_TRACER_COUNT
#define  INCR_TRACER_COUNT  increment_tracer_telemetry_count(__FILE__, __LINE__)
#endif

#endif
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
分布式缓存是一种将数据缓存在多台服务器中,以提供高性能和可伸缩性的技术。其原理是在应用服务器和数据库之间添加一个缓存层,存储经常被访问的数据,以减少对数据库的访问频率。通过将缓存数据存储在多台服务器上,可以实现数据的共享和负载均衡,从而提高系统的性能和可用性。 分布式缓存的架构主要由以下组件构成: 1. 客户端应用:请求数据的应用程序。 2. 缓存层:存储数据的服务器集群,接收并缓存客户端的请求。 3. 数据源:提供原始数据的数据库或其他数据存储系统。 4. 分布式缓存协议:用于客户端和缓存层之间进行通信的协议,如Memcached协议或Redis协议。 在实现分布式缓存中,可以使用Go语言开发缓存层服务器。Go语言具有高效的并发性和网络编程能力,适合构建分布式系统。 使用Go语言实现分布式缓存的步骤如下: 1. 设计缓存数据结构:确定缓存存储的数据结构和数据访问方法。 2. 实现缓存服务器:使用Go语言编写缓存服务器代码,包括处理客户端请求、缓存数据的读写等功能。 3. 集群管理:实现监控和管理分布式缓存服务器,包括节点的添加和删除、负载均衡等功能。 4. 客户端接口:为客户端应用提供访问分布式缓存的接口,使其能够通过缓存层访问数据。 使用Go语言实现分布式缓存可以借助一些开源框架和库,如memcached、GoCache等,它们提供了丰富的功能和工具,简化了分布式缓存的开发和管理。 总之,分布式缓存通过在多台服务器上缓存数据,提供了高性能和可伸缩性,其原理是将缓存层插入应用服务器和数据源之间,通过共享和负载均衡来提高系统的性能。Go语言作为一种高效的编程语言,对于实现分布式缓存来说,具有很大的优势。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值