|
@@ -2,80 +2,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
#include "java_common.h"
|
|
#include "java_common.h"
|
|
|
-static __inline int updataSocket2(struct sock_t *map_data_res, char * payload,int len,void * jbytechar_ptr,void * len_from_rbp_ptr) {
|
|
|
|
|
|
|
|
|
|
- long res = bpf_probe_write_user((void *) jbytechar_ptr, &map_data_res->payload, sizeof(map_data_res->payload));
|
|
|
|
|
- bpf_printk("sizeof(map_data_res->payload) %d\n", sizeof(map_data_res->payload));
|
|
|
|
|
- bpf_printk("sizeof(payload) %d\n", sizeof(payload));
|
|
|
|
|
- bpf_printk("&payload 0x%lx\n", &payload);
|
|
|
|
|
-
|
|
|
|
|
- if (res == 0) {
|
|
|
|
|
- bpf_printk("Successfully wrote value to user address: 0x%lx\n", jbytechar_ptr);
|
|
|
|
|
- } else {
|
|
|
|
|
- bpf_printk("Failed to write value to user address: %p, error: %ld\n", jbytechar_ptr, res);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- unsigned long new_val;
|
|
|
|
|
- new_val = len;
|
|
|
|
|
- res = bpf_probe_write_user((void *) len_from_rbp_ptr, &new_val, sizeof(new_val));
|
|
|
|
|
- if (res == 0) {
|
|
|
|
|
- bpf_printk("Successfully wrote value to user address: 0x%lx\n", len_from_rbp_ptr);
|
|
|
|
|
- } else {
|
|
|
|
|
- bpf_printk("Failed to write value to user address: %p, error: %ld\n", len_from_rbp_ptr, res);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- bpf_printk("len %d\n", len);
|
|
|
|
|
-
|
|
|
|
|
-// bpf_printk("payload %s\n", payload);
|
|
|
|
|
-// for (int i = 270; i < len; ++i) {
|
|
|
|
|
-// bpf_printk("data[%d]=%c", i,payload[i]);
|
|
|
|
|
-// if(payload[i]=='\0'){
|
|
|
|
|
-// break;
|
|
|
|
|
-// }
|
|
|
|
|
-// }
|
|
|
|
|
- return 0;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-static __inline int updataSocket(struct sock_t *map_data_res,void * jbytechar_ptr,void * len_from_rbp_ptr) {
|
|
|
|
|
-
|
|
|
|
|
- long res = bpf_probe_write_user((void *) jbytechar_ptr, &map_data_res->payload, sizeof(map_data_res->payload));
|
|
|
|
|
- if (res == 0) {
|
|
|
|
|
- bpf_printk("Successfully wrote value to user address: 0x%lx\n", jbytechar_ptr);
|
|
|
|
|
- } else {
|
|
|
|
|
- bpf_printk("Failed to write value to user address: %p, error: %ld\n", jbytechar_ptr, res);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- unsigned long new_val;
|
|
|
|
|
- new_val = map_data_res->size;
|
|
|
|
|
- res = bpf_probe_write_user((void *) len_from_rbp_ptr, &new_val, sizeof(new_val));
|
|
|
|
|
- if (res == 0) {
|
|
|
|
|
- bpf_printk("Successfully wrote value to user address: 0x%lx\n", len_from_rbp_ptr);
|
|
|
|
|
- } else {
|
|
|
|
|
- bpf_printk("Failed to write value to user address: %p, error: %ld\n", len_from_rbp_ptr, res);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- bpf_printk("Successfully %d\n", map_data_res->size);
|
|
|
|
|
-
|
|
|
|
|
- bpf_printk("Successfully %s\n", map_data_res->payload);
|
|
|
|
|
-// for (int i = 270; i < 290; ++i) {
|
|
|
|
|
-// bpf_printk("data[%d]=%c", i,map_data_res->payload[i]);
|
|
|
|
|
-// if(map_data_res->payload[i]=='\0'){
|
|
|
|
|
-// break;
|
|
|
|
|
-// }
|
|
|
|
|
-// }
|
|
|
|
|
- return 0;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-static __inline struct apm_span_context *build_sc(struct ebpf_proc_info *proc_info, char *host) {
|
|
|
|
|
|
|
+static __inline struct apm_span_context *build_sc(struct ebpf_proc_info proc_info, char *host) {
|
|
|
u32 key = 0;
|
|
u32 key = 0;
|
|
|
struct apm_span_context *cw_sc = bpf_map_lookup_elem(&apm_span_context_heap, &key);
|
|
struct apm_span_context *cw_sc = bpf_map_lookup_elem(&apm_span_context_heap, &key);
|
|
|
|
|
|
|
|
- if (cw_sc == NULL){
|
|
|
|
|
|
|
+ if (cw_sc == NULL) {
|
|
|
return NULL;
|
|
return NULL;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
struct apm_span_context *cw_psc = cw_get_parent_tracking_span();
|
|
struct apm_span_context *cw_psc = cw_get_parent_tracking_span();
|
|
|
- if(cw_psc){
|
|
|
|
|
|
|
+ if (cw_psc) {
|
|
|
copy_byte_arrays(cw_psc->trace_id, cw_sc->trace_id, APM_TRACE_ID_SIZE);
|
|
copy_byte_arrays(cw_psc->trace_id, cw_sc->trace_id, APM_TRACE_ID_SIZE);
|
|
|
}
|
|
}
|
|
|
// new spanid
|
|
// new spanid
|
|
@@ -96,10 +33,10 @@ static __inline struct apm_span_context *build_sc(struct ebpf_proc_info *proc_in
|
|
|
// __u32 tgid = pid_tgid >> 32;
|
|
// __u32 tgid = pid_tgid >> 32;
|
|
|
// struct ebpf_proc_info *proc_info =
|
|
// struct ebpf_proc_info *proc_info =
|
|
|
// bpf_map_lookup_elem(&proc_info_map, &tgid);
|
|
// bpf_map_lookup_elem(&proc_info_map, &tgid);
|
|
|
- if (proc_info) {
|
|
|
|
|
- copy_byte_arrays(proc_info->instance_id, cw_sc->instance_id, APM_APP_ID_SIZE);
|
|
|
|
|
- copy_byte_arrays(proc_info->app_id, cw_sc->app_id, APM_APP_ID_SIZE);
|
|
|
|
|
- }
|
|
|
|
|
|
|
+// if (proc_info) {
|
|
|
|
|
+ copy_byte_arrays(proc_info.instance_id, cw_sc->instance_id, APM_APP_ID_SIZE);
|
|
|
|
|
+ copy_byte_arrays(proc_info.app_id, cw_sc->app_id, APM_APP_ID_SIZE);
|
|
|
|
|
+// }
|
|
|
|
|
|
|
|
// set assumed_app_id
|
|
// set assumed_app_id
|
|
|
set_assumed_app_id_arrays(host, cw_sc->assumed_app_id, APM_ASSUMED_APP_ID_STRING_SIZE);
|
|
set_assumed_app_id_arrays(host, cw_sc->assumed_app_id, APM_ASSUMED_APP_ID_STRING_SIZE);
|
|
@@ -107,137 +44,12 @@ static __inline struct apm_span_context *build_sc(struct ebpf_proc_info *proc_in
|
|
|
return cw_sc;
|
|
return cw_sc;
|
|
|
// char header[CW_STREAM_HEADER_LEN];
|
|
// char header[CW_STREAM_HEADER_LEN];
|
|
|
// span_context_to_cw_string_stream(cw_span_context, header, '1');
|
|
// span_context_to_cw_string_stream(cw_span_context, header, '1');
|
|
|
-// bpf_printk("Successfully HEADER %s\n", header);
|
|
|
|
|
|
|
+// cw_bpf_debug("Successfully HEADER %s\n", header);
|
|
|
// return header;
|
|
// return header;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-static __inline struct sock_t* buildHeader(struct sock_t *map_data) {
|
|
|
|
|
-
|
|
|
|
|
- int key = 0;
|
|
|
|
|
- struct sock_t *map_data_res = bpf_map_lookup_elem(&socket_res, &key);
|
|
|
|
|
-
|
|
|
|
|
- if (!map_data_res) {
|
|
|
|
|
- bpf_printk("Failed to lookup socket_heap");
|
|
|
|
|
- return NULL;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
-// __builtin_memset(map_data_res, 0, sizeof(struct sock_t));
|
|
|
|
|
-// char header[5] = "cwt\r\n";
|
|
|
|
|
-
|
|
|
|
|
-// char header[CW_STREAM_HEADER_LEN] = "cwtrace: 00:00:1015481350055581:5450531005555981:5610250100539899:304775019cd3218a304775019cd3218a:1001025098564810:140acc88cde8773f\r\n";
|
|
|
|
|
- struct apm_span_context *cw_span_context = bpf_map_lookup_elem(&apm_span_context_heap, &key);
|
|
|
|
|
-
|
|
|
|
|
- if (cw_span_context == NULL){
|
|
|
|
|
- return NULL;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- struct apm_span_context *cw_psc = cw_get_parent_tracking_span();
|
|
|
|
|
- if(cw_psc){
|
|
|
|
|
- copy_byte_arrays(cw_psc->trace_id, cw_span_context->trace_id, APM_TRACE_ID_SIZE);
|
|
|
|
|
- // new spanid
|
|
|
|
|
- generate_random_bytes(cw_span_context->span_id, APM_SPAN_ID_SIZE);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // set host_id/appid
|
|
|
|
|
- u32 k0 = 0;
|
|
|
|
|
- struct trace_conf_t *trace_conf = trace_conf_map__lookup(&k0);
|
|
|
|
|
- if (trace_conf) {
|
|
|
|
|
- for (int i = 0; i < 8; ++i) {
|
|
|
|
|
-// cw_bpf_debug("[Client] host_id:%02x", trace_conf->host_id[i]);
|
|
|
|
|
- }
|
|
|
|
|
- copy_byte_arrays(trace_conf->host_id, cw_span_context->host_id, APM_HOST_ID_SIZE);
|
|
|
|
|
- copy_byte_arrays(trace_conf->app_id, cw_span_context->app_id, APM_APP_ID_SIZE);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- __u64 pid_tgid = bpf_get_current_pid_tgid();
|
|
|
|
|
- __u32 tgid = pid_tgid >> 32;
|
|
|
|
|
- struct ebpf_proc_info *proc_info =
|
|
|
|
|
- bpf_map_lookup_elem(&proc_info_map, &tgid);
|
|
|
|
|
- if (proc_info) {
|
|
|
|
|
- for (int i = 0; i < 8; ++i) {
|
|
|
|
|
-// cw_bpf_debug("[Client] instance_id:%02x", proc_info->instance_id[i]);
|
|
|
|
|
- }
|
|
|
|
|
- copy_byte_arrays(proc_info->instance_id, cw_span_context->instance_id, APM_APP_ID_SIZE);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // set assumed_app_id
|
|
|
|
|
- set_assumed_app_id_arrays("111", cw_span_context->assumed_app_id, APM_ASSUMED_APP_ID_STRING_SIZE);
|
|
|
|
|
- cw_save_current_tracking_span(cw_span_context);
|
|
|
|
|
-
|
|
|
|
|
- char header[CW_STREAM_HEADER_LEN];
|
|
|
|
|
- span_context_to_cw_string_stream(cw_span_context, header, '1');
|
|
|
|
|
- bpf_printk("Successfully HEADER %s\n", header);
|
|
|
|
|
-
|
|
|
|
|
-// bpf_probe_read(map_data_res->payload, map_data->header_offset_idx, map_data->payload);
|
|
|
|
|
-// bpf_printk("Successfully %s\n", data);
|
|
|
|
|
-// return 1;
|
|
|
|
|
-#pragma unroll
|
|
|
|
|
- for (int i = 0; i < MAX_LEN; i++) {
|
|
|
|
|
- if (i < map_data->header_offset_idx) {
|
|
|
|
|
- map_data_res->payload[i] = map_data->payload[i];
|
|
|
|
|
- } else {
|
|
|
|
|
- if (i == map_data->header_offset_idx) {
|
|
|
|
|
-#pragma unroll
|
|
|
|
|
- for (int k = 0; k < CW_STREAM_HEADER_LEN; k++) {
|
|
|
|
|
- int tmp_len = i + k;
|
|
|
|
|
- if (tmp_len < MAX_LEN) {
|
|
|
|
|
- map_data_res->payload[tmp_len] = header[k];
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- int tmp_len = i + CW_STREAM_HEADER_LEN;
|
|
|
|
|
- if (tmp_len < MAX_LEN) {
|
|
|
|
|
- map_data_res->payload[tmp_len] = map_data->payload[i];
|
|
|
|
|
- if (map_data->payload[i] == '\0')
|
|
|
|
|
- break;
|
|
|
|
|
-// bpf_printk("map_data->payload: %c->i=%d, offindex %d", map_data_res->payload[i], i, map_data->header_offset_idx);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- map_data_res->size = map_data->size + CW_STREAM_HEADER_LEN;
|
|
|
|
|
-
|
|
|
|
|
- return map_data_res;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-static __inline int insertHeader(struct sock_t *map_data,void * jbytechar_ptr,void * len_from_rbp_ptr) {
|
|
|
|
|
-
|
|
|
|
|
- struct sock_t *map_data_res= buildHeader(map_data);
|
|
|
|
|
- if (map_data_res == NULL) {
|
|
|
|
|
- bpf_printk("Failed to lookup socket_heap");
|
|
|
|
|
- return -1;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /*修改部分*/
|
|
|
|
|
- return updataSocket2(map_data_res, map_data_res->payload, map_data_res->size, jbytechar_ptr, len_from_rbp_ptr);
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
SEC("uprobe/Java_java_net_SocketOutputStream_socketWrite0")
|
|
SEC("uprobe/Java_java_net_SocketOutputStream_socketWrite0")
|
|
|
int uprobe_Java_java_net_SocketOutputStream_socketWrite0(struct pt_regs *ctx) {
|
|
int uprobe_Java_java_net_SocketOutputStream_socketWrite0(struct pt_regs *ctx) {
|
|
|
-// char subs2[HEADER_LEN +1];
|
|
|
|
|
-// int a =0;
|
|
|
|
|
-// struct apm_span_context *cw_sc2 = build_sc();
|
|
|
|
|
-//// struct apm_span_context *cw_sc2 = bpf_map_lookup_elem(&apm_span_context_heap, &a);
|
|
|
|
|
-//
|
|
|
|
|
-// if (cw_sc2 == NULL){
|
|
|
|
|
-// return 0;
|
|
|
|
|
-// }
|
|
|
|
|
-///*111*/
|
|
|
|
|
-//// char subs[HEADER_LEN +1];
|
|
|
|
|
-// span_context_to_cw_string_stream(cw_sc2, subs2, '1');
|
|
|
|
|
-// bpf_printk("%s",subs2);
|
|
|
|
|
-//
|
|
|
|
|
-// struct stream_header_t *stream_header1 = bpf_map_lookup_elem(&stream_header_heap, &a);
|
|
|
|
|
-// if (!stream_header1) {
|
|
|
|
|
-// bpf_printk("Failed to lookup socket_heap\n");
|
|
|
|
|
-// return 1;
|
|
|
|
|
-// }
|
|
|
|
|
-// bpf_probe_read_user(stream_header1->header, sizeof(stream_header1->header), (void *) (subs2));
|
|
|
|
|
-// bpf_printk("stream_header1->header------%s",stream_header1->header);
|
|
|
|
|
-//
|
|
|
|
|
-// return 0;
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
|
|
|
|
|
// 捕获第六个参数 data_count
|
|
// 捕获第六个参数 data_count
|
|
|
int data_count = PT_REGS_PARM6(ctx);
|
|
int data_count = PT_REGS_PARM6(ctx);
|
|
@@ -245,43 +57,45 @@ int uprobe_Java_java_net_SocketOutputStream_socketWrite0(struct pt_regs *ctx) {
|
|
|
return 0;
|
|
return 0;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ cw_bpf_debug("[java client] data_count:%d\n", data_count);
|
|
|
|
|
|
|
|
-// void *len_from_rsp_ptr = (void *) PT_REGS_RSP(ctx) - 65640 +8 ;
|
|
|
|
|
- void *len_from_rbp_ptr = (void *) PT_LEN_REGS(ctx) ;
|
|
|
|
|
-// void *jbytearray_from_rbp_p_p_p = (void *) (PT_REGS_RBP(ctx)) - 0x10050;
|
|
|
|
|
|
|
+ void *len_from_rbp_ptr = (void *) PT_LEN_REGS(ctx);
|
|
|
|
|
|
|
|
-// long res;
|
|
|
|
|
void *len_ptr = 0;
|
|
void *len_ptr = 0;
|
|
|
cw_bpf_debug("[java client] address: len_from_rbp_ptr<0x%lx>\n", len_from_rbp_ptr);
|
|
cw_bpf_debug("[java client] address: len_from_rbp_ptr<0x%lx>\n", len_from_rbp_ptr);
|
|
|
-// bpf_printk("len_from_rsp_ptr address: 0x%lx\n", len_from_rsp_ptr);
|
|
|
|
|
|
|
|
|
|
bpf_probe_read(&len_ptr, sizeof(len_ptr), (void *) len_from_rbp_ptr);
|
|
bpf_probe_read(&len_ptr, sizeof(len_ptr), (void *) len_from_rbp_ptr);
|
|
|
cw_bpf_debug("[java client] [len_ptr] before addr<0x%lx>, %d \n", len_from_rbp_ptr, len_ptr);
|
|
cw_bpf_debug("[java client] [len_ptr] before addr<0x%lx>, %d \n", len_from_rbp_ptr, len_ptr);
|
|
|
|
|
|
|
|
-// bpf_probe_read(&len_ptr, sizeof(len_ptr), (void *) len_from_rbp_ptr);
|
|
|
|
|
-// cw_bpf_debug("[len_ptr]after addr<0x%lx>, %d \n", len_from_rbp_ptr, len_ptr);
|
|
|
|
|
-
|
|
|
|
|
- if ( (long) len_ptr != data_count ){
|
|
|
|
|
|
|
+ if ((long) len_ptr != data_count) {
|
|
|
cw_bpf_debug("[java client] [len_ptr] check error.");
|
|
cw_bpf_debug("[java client] [len_ptr] check error.");
|
|
|
return 0;
|
|
return 0;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ int key = 0;
|
|
|
|
|
+ struct sock_t *map_data = bpf_map_lookup_elem(&socket_heap, &key);
|
|
|
|
|
+ if (!map_data) {
|
|
|
|
|
+ cw_bpf_debug("[java client] Failed to lookup socket_heap\n");
|
|
|
|
|
+ return 1;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
// 获取jbytearray
|
|
// 获取jbytearray
|
|
|
- __u32 tgid = (__u32)(bpf_get_current_pid_tgid() >> 32);
|
|
|
|
|
- struct ebpf_proc_info *proc_info = bpf_map_lookup_elem(&proc_info_map, &tgid);
|
|
|
|
|
- if (!proc_info) {
|
|
|
|
|
|
|
+ __u32 tgid = (__u32) (bpf_get_current_pid_tgid() >> 32);
|
|
|
|
|
+ map_data->proc_info = bpf_map_lookup_elem(&proc_info_map, &tgid);
|
|
|
|
|
+ if (!map_data->proc_info) {
|
|
|
return 0;
|
|
return 0;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- cw_bpf_debug("[java client] code_type:%d\n", proc_info->code_type);
|
|
|
|
|
|
|
+ cw_bpf_debug("[java client] code_type:%d\n", map_data->proc_info->code_type);
|
|
|
void *jbytearray_ptr = NULL;
|
|
void *jbytearray_ptr = NULL;
|
|
|
|
|
+
|
|
|
// 1003 是 javaAOT 的
|
|
// 1003 是 javaAOT 的
|
|
|
- if (proc_info->code_type == CodeTypeJavaAot) {
|
|
|
|
|
- jbytearray_ptr = (void *) (PT_REGS_SP(ctx)) + 0x8 + 0x10060+0x20;
|
|
|
|
|
- } else{
|
|
|
|
|
|
|
+ if (map_data->proc_info->code_type == CodeTypeJavaAot) {
|
|
|
|
|
+ jbytearray_ptr = (void *) (PT_REGS_SP(ctx)) + 0x8 + 0x10060 + 0x20;
|
|
|
|
|
+ } else {
|
|
|
jbytearray_ptr = (void *) PT_REGS_PARM4(ctx);
|
|
jbytearray_ptr = (void *) PT_REGS_PARM4(ctx);
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
cw_bpf_debug("[java client] jbytechar_ptr_from_rcx <0x%lx>", jbytearray_ptr);
|
|
cw_bpf_debug("[java client] jbytechar_ptr_from_rcx <0x%lx>", jbytearray_ptr);
|
|
|
|
|
|
|
|
unsigned long jbytechar_head_ptr;
|
|
unsigned long jbytechar_head_ptr;
|
|
@@ -300,14 +114,7 @@ int uprobe_Java_java_net_SocketOutputStream_socketWrite0(struct pt_regs *ctx) {
|
|
|
return 0;
|
|
return 0;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 定义 Map 键值和 Map 数据结构
|
|
|
|
|
- int key = 0;
|
|
|
|
|
- struct sock_t *map_data = bpf_map_lookup_elem(&socket_heap, &key);
|
|
|
|
|
- if (!map_data) {
|
|
|
|
|
- cw_bpf_debug("[java client] Failed to lookup socket_heap\n");
|
|
|
|
|
- return 1;
|
|
|
|
|
- }
|
|
|
|
|
-// __builtin_memset(map_data, 0, sizeof(struct sock_t));
|
|
|
|
|
|
|
+
|
|
|
// 读取用户空间数据到 map_data->payload
|
|
// 读取用户空间数据到 map_data->payload
|
|
|
void *jbytechar_ptr = (void *) (jbytechar_head_ptr + 16);
|
|
void *jbytechar_ptr = (void *) (jbytechar_head_ptr + 16);
|
|
|
cw_bpf_debug("[java client] [jbytechar_ptr] <0x%lx>", jbytechar_ptr);
|
|
cw_bpf_debug("[java client] [jbytechar_ptr] <0x%lx>", jbytechar_ptr);
|
|
@@ -326,99 +133,99 @@ int uprobe_Java_java_net_SocketOutputStream_socketWrite0(struct pt_regs *ctx) {
|
|
|
return -1;
|
|
return -1;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- long host_native = 0x203a74736f480a0dLL; // 小端序下的 "\r\nHost:" ('0d' '0a' '48' '6f' '73' '74' '3a' '20')
|
|
|
|
|
// 查找 Header 开始位置
|
|
// 查找 Header 开始位置
|
|
|
|
|
+ long header_start_native = 0x0a0d312e312f5054LL; // 小端序下的 "TP/1.1\r\n" (0x54 0x50 0x2f 0x31 0x2e 0x31 0x0d 0x0a)
|
|
|
#pragma clang loop unroll(full)
|
|
#pragma clang loop unroll(full)
|
|
|
- for (int i = 0; i < 200 - 5; i++) {
|
|
|
|
|
-// if (map_data->payload[i] == '1' &&
|
|
|
|
|
-// map_data->payload[i + 1] == '.' &&
|
|
|
|
|
-// map_data->payload[i + 2] == '1' &&
|
|
|
|
|
-// map_data->payload[i + 3] == '\r' &&
|
|
|
|
|
-// map_data->payload[i + 4] == '\n') {
|
|
|
|
|
-// map_data->header_offset_idx = i + 5;
|
|
|
|
|
-// break;
|
|
|
|
|
-// }
|
|
|
|
|
- long data = *(long long *)(map_data->payload + i);
|
|
|
|
|
- if(data == host_native) {
|
|
|
|
|
- map_data->host_offset_idx = i + 8;
|
|
|
|
|
- map_data->header_offset_idx = i + 2;
|
|
|
|
|
|
|
+ for (u32 i = 0; i < 200 - 8; i++) {
|
|
|
|
|
+ long data = *(long long *) (map_data->payload + i);
|
|
|
|
|
+ if (data == header_start_native) {
|
|
|
|
|
+ map_data->header_offset_idx = i + 8;
|
|
|
break;
|
|
break;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
if (map_data->header_offset_idx == 0) {
|
|
if (map_data->header_offset_idx == 0) {
|
|
|
- return -1;
|
|
|
|
|
|
|
+ cw_bpf_debug("[java client] header_offset_idx error:\n");
|
|
|
|
|
+ return 1;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ map_data->end_str_len = data_count - map_data->header_offset_idx + 1;
|
|
|
|
|
+ u32 copy_size = L7_IOVEC_BUF_SIZE;
|
|
|
|
|
+ if (map_data->end_str_len > copy_size) {
|
|
|
|
|
+ u32 chunk = (u32) map_data->end_str_len / copy_size;
|
|
|
|
|
+ if (chunk > MAX_CHUNK) {
|
|
|
|
|
+ cw_bpf_debug("[java client] Exceeding the max_chunk:%d need_chunk:%d\n", MAX_CHUNK, chunk);
|
|
|
|
|
+ return 1;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ cw_bpf_debug("[java client] header_offset_idx:%d\n", map_data->header_offset_idx);
|
|
|
|
|
+
|
|
|
|
|
+ // tail payload保存
|
|
|
|
|
+ u32 tail_key = 1;
|
|
|
|
|
+ char *tail_payload = bpf_map_lookup_elem(&large_array_map, &tail_key);
|
|
|
|
|
+ if (!tail_payload) {
|
|
|
|
|
+ cw_bpf_debug("[java client ]header ->end %s\n", tail_payload);
|
|
|
|
|
+ return 1;
|
|
|
}
|
|
}
|
|
|
|
|
+ __builtin_memset(tail_payload, 0, sizeof(char *));
|
|
|
|
|
|
|
|
|
|
+ bpf_probe_read_user_str(tail_payload, JAVA_MAX_BUFFER_SIZE,
|
|
|
|
|
+ (void *) (jbytechar_ptr + map_data->header_offset_idx));
|
|
|
|
|
+ // host查询
|
|
|
|
|
+ bpf_tail_call(ctx, &NAME(progs_jmp_up_map), PROG_DATA_JAVA_FIND_HOST_UP_IDX);
|
|
|
|
|
+ return 0;
|
|
|
|
|
|
|
|
/*设置初始值*/
|
|
/*设置初始值*/
|
|
|
|
|
+ /*
|
|
|
|
|
+ * debug
|
|
|
char *full_payload= bpf_map_lookup_elem(&large_array_map, &key);
|
|
char *full_payload= bpf_map_lookup_elem(&large_array_map, &key);
|
|
|
if (!full_payload)
|
|
if (!full_payload)
|
|
|
return -1;
|
|
return -1;
|
|
|
// 读取完整http
|
|
// 读取完整http
|
|
|
bpf_probe_read_user_str(full_payload, JAVA_MAX_BUFFER_SIZE, jbytechar_ptr);
|
|
bpf_probe_read_user_str(full_payload, JAVA_MAX_BUFFER_SIZE, jbytechar_ptr);
|
|
|
cw_bpf_debug("[java client]origin full_payload %s\n", full_payload);
|
|
cw_bpf_debug("[java client]origin full_payload %s\n", full_payload);
|
|
|
-
|
|
|
|
|
|
|
+*/
|
|
|
|
|
|
|
|
// 尾部payload
|
|
// 尾部payload
|
|
|
- u32 key2 = 1;
|
|
|
|
|
- char *tail_payload = bpf_map_lookup_elem(&large_array_map, &key2);
|
|
|
|
|
- if (!tail_payload) {
|
|
|
|
|
- cw_bpf_debug("[java client ]header ->end %s\n", tail_payload);
|
|
|
|
|
- return -1;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- u32 copy_size = L7_IOVEC_BUF_SIZE;
|
|
|
|
|
- long end_str_len = bpf_probe_read_user_str(tail_payload, JAVA_MAX_BUFFER_SIZE,
|
|
|
|
|
- (void *) (jbytechar_ptr + map_data->header_offset_idx));
|
|
|
|
|
- cw_bpf_debug("[java client] end_str_len %d\n", end_str_len);
|
|
|
|
|
- map_data->end_str_len = end_str_len;
|
|
|
|
|
- if (end_str_len > copy_size) {
|
|
|
|
|
- u32 chunk = (u32) end_str_len / copy_size;
|
|
|
|
|
- if (chunk > MAX_CHUNK) {
|
|
|
|
|
- cw_bpf_debug("[java client] Exceeding the max_chunk:%d need_chunk:%d\n", MAX_CHUNK, chunk);
|
|
|
|
|
- return -1;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- u32 insert_pos = map_data->header_offset_idx;
|
|
|
|
|
-
|
|
|
|
|
-// if (chunk > MAX_CHUNK) {
|
|
|
|
|
-// cw_bpf_debug("Exceeding the max_chunk:%d need_chunk:%d\n", MAX_CHUNK, chunk);
|
|
|
|
|
|
|
+// u32 key2 = 1;
|
|
|
|
|
+// char *tail_payload = bpf_map_lookup_elem(&large_array_map, &key2);
|
|
|
|
|
+// if (!tail_payload) {
|
|
|
|
|
+// cw_bpf_debug("[java client ]header ->end %s\n", tail_payload);
|
|
|
// return -1;
|
|
// return -1;
|
|
|
// }
|
|
// }
|
|
|
-
|
|
|
|
|
- // 挪动数据以腾出空间插入子串
|
|
|
|
|
- // 插入header后的结束位置
|
|
|
|
|
-// int tail_offset_idx = map_data->header_offset_idx + CW_STREAM_HEADER_LEN -1;
|
|
|
|
|
-
|
|
|
|
|
-// char subs[HEADER_LEN +1] = "cwtrace: 00:00:1015481350055581:5450531005555981:5610250100539899:304775019cd3218a304775019cd3218a:1001025098564810:140acc88cde8773f\r\n\0";
|
|
|
|
|
-
|
|
|
|
|
-// char subs[HEADER_LEN +1];
|
|
|
|
|
-// struct apm_span_context *cw_sc = build_sc();
|
|
|
|
|
-
|
|
|
|
|
-/*111*/
|
|
|
|
|
-// char subs[HEADER_LEN +1];
|
|
|
|
|
-// span_context_to_cw_string_stream(cw_sc, subs, '1');
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
|
|
+//
|
|
|
|
|
+// u32 copy_size = L7_IOVEC_BUF_SIZE;
|
|
|
|
|
+// long end_str_len = bpf_probe_read_user_str(tail_payload, JAVA_MAX_BUFFER_SIZE,
|
|
|
|
|
+// (void *) (jbytechar_ptr + map_data->header_offset_idx));
|
|
|
|
|
+// cw_bpf_debug("[java client] end_str_len %d\n", end_str_len);
|
|
|
|
|
+// map_data->end_str_len = end_str_len;
|
|
|
|
|
+
|
|
|
|
|
+// u32 copy_size = L7_IOVEC_BUF_SIZE;
|
|
|
|
|
+// if (end_str_len > copy_size) {
|
|
|
|
|
+// u32 chunk = (u32) end_str_len / copy_size;
|
|
|
|
|
+// if (chunk > MAX_CHUNK) {
|
|
|
|
|
+// cw_bpf_debug("[java client] Exceeding the max_chunk:%d need_chunk:%d\n", MAX_CHUNK, chunk);
|
|
|
|
|
+// return -1;
|
|
|
|
|
+// }
|
|
|
|
|
+// }
|
|
|
|
|
|
|
|
|
|
|
|
|
- char host[30];
|
|
|
|
|
- bpf_probe_read(&host,sizeof host,map_data->payload + map_data->host_offset_idx);
|
|
|
|
|
- host[29]='\0';
|
|
|
|
|
- cw_bpf_debug("[java client] http host %s\n", host);
|
|
|
|
|
|
|
|
|
|
- char tmp_header[CW_STREAM_HEADER_LEN];
|
|
|
|
|
- struct apm_span_context *cw_sc = build_sc(proc_info, host);
|
|
|
|
|
- if (cw_sc == NULL){
|
|
|
|
|
- return 0;
|
|
|
|
|
- }
|
|
|
|
|
- span_context_to_cw_string_stream(cw_sc, tmp_header, '1');
|
|
|
|
|
|
|
+// char host[30];
|
|
|
|
|
+// bpf_probe_read(&host, sizeof host, map_data->payload + map_data->host_offset_idx);
|
|
|
|
|
+// host[29] = '\0';
|
|
|
|
|
+// cw_bpf_debug("[java client] http host %s\n", host);
|
|
|
|
|
+// char tmp_header[CW_STREAM_HEADER_LEN];
|
|
|
|
|
+// struct apm_span_context *cw_sc = build_sc(proc_info, host);
|
|
|
|
|
+// if (cw_sc == NULL){
|
|
|
|
|
+// return 0;
|
|
|
|
|
+// }
|
|
|
|
|
+// span_context_to_cw_string_stream(cw_sc, map_data->header_stream, '1');
|
|
|
|
|
+// return 1;
|
|
|
// cw_bpf_debug("header:[%s],sizeof %d ",map_data->header_stream,sizeof(map_data->header_stream));
|
|
// cw_bpf_debug("header:[%s],sizeof %d ",map_data->header_stream,sizeof(map_data->header_stream));
|
|
|
|
|
|
|
|
|
|
|
|
|
- bpf_probe_read(map_data->header_stream, sizeof(map_data->header_stream), (void *) (tmp_header));
|
|
|
|
|
|
|
+// bpf_probe_read(map_data->header_stream, sizeof(map_data->header_stream), (void *) (tmp_header));
|
|
|
// cw_bpf_debug("stream_header1->header------%s",map_data->header_stream);
|
|
// cw_bpf_debug("stream_header1->header------%s",map_data->header_stream);
|
|
|
|
|
|
|
|
// if (insert_pos >= 0 && insert_pos <= JAVA_MAX_BUFFER_SIZE - sizeof(map_data->header_stream)) {
|
|
// if (insert_pos >= 0 && insert_pos <= JAVA_MAX_BUFFER_SIZE - sizeof(map_data->header_stream)) {
|
|
@@ -426,35 +233,104 @@ int uprobe_Java_java_net_SocketOutputStream_socketWrite0(struct pt_regs *ctx) {
|
|
|
// }
|
|
// }
|
|
|
|
|
|
|
|
|
|
|
|
|
- int tail_offset_idx = insert_pos + sizeof(map_data->header_stream) - 1;
|
|
|
|
|
-
|
|
|
|
|
|
|
|
|
|
// 修改长度
|
|
// 修改长度
|
|
|
- long new_val;
|
|
|
|
|
- new_val = map_data->size + HEADER_LEN;
|
|
|
|
|
|
|
+// long new_val = map_data->size + HEADER_LEN;
|
|
|
|
|
+//
|
|
|
|
|
+// long res = bpf_probe_write_user((void *) len_from_rbp_ptr, &new_val, sizeof(new_val));
|
|
|
|
|
+// if (res < 0) {
|
|
|
|
|
+// cw_bpf_debug("[java client] Failed to write value to user address: %p, error: %ld\n", len_from_rbp_ptr, res);
|
|
|
|
|
+//// cw_bpf_debug("Failed to write len to user address: %p\n", len_from_rbp_ptr);
|
|
|
|
|
+// return -1;
|
|
|
|
|
+// }
|
|
|
|
|
|
|
|
- long res = bpf_probe_write_user((void *) len_from_rbp_ptr, &new_val, sizeof(new_val));
|
|
|
|
|
- if (res < 0) {
|
|
|
|
|
- cw_bpf_debug("[java client] Failed to write value to user address: %p, error: %ld\n", len_from_rbp_ptr, res);
|
|
|
|
|
-// cw_bpf_debug("Failed to write len to user address: %p\n", len_from_rbp_ptr);
|
|
|
|
|
- return -1;
|
|
|
|
|
|
|
+// u32 insert_pos = map_data->header_offset_idx;
|
|
|
|
|
+// u32 tail_offset_idx = insert_pos + sizeof(map_data->header_stream) - 1;
|
|
|
|
|
+//
|
|
|
|
|
+// // 单次
|
|
|
|
|
+// if (end_str_len <= copy_size) {
|
|
|
|
|
+// // 写头
|
|
|
|
|
+// bpf_probe_write_user((void *) jbytechar_ptr + insert_pos, &map_data->header_stream, sizeof(map_data->header_stream));
|
|
|
|
|
+// // 写尾
|
|
|
|
|
+// bpf_probe_write_user((void *) jbytechar_ptr + tail_offset_idx, tail_payload, L7_IOVEC_BUF_SIZE);
|
|
|
|
|
+// }
|
|
|
|
|
+//
|
|
|
|
|
+// // 多批
|
|
|
|
|
+// if (end_str_len > copy_size) {
|
|
|
|
|
+//// bpf_tail_call(ctx, &jmp_table1, 1);
|
|
|
|
|
+// bpf_tail_call(ctx, &NAME(progs_jmp_up_map), PROG_DATA_JAVA_UPDATE_HEADER_UP_IDX);
|
|
|
|
|
+// }
|
|
|
|
|
+
|
|
|
|
|
+ bpf_tail_call(ctx, &NAME(progs_jmp_up_map), PROG_DATA_JAVA_UPDATE_HEADER_UP_IDX);
|
|
|
|
|
+
|
|
|
|
|
+ return 0;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+//bpf_prog_up_build_header
|
|
|
|
|
+PROGUP(java_find_host)(struct pt_regs *ctx) {
|
|
|
|
|
+ u32 k0 = 0;
|
|
|
|
|
+ struct sock_t *map_data = bpf_map_lookup_elem(&socket_heap, &k0);
|
|
|
|
|
+ if (!map_data) {
|
|
|
|
|
+ cw_bpf_debug("[java client] Failed to lookup socket_heap\n");
|
|
|
|
|
+ return 1;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ // host查询
|
|
|
|
|
+ long host_native = 0x203a74736f480a0dLL; // 小端序下的 "\r\nHost: " ('0d' '0a' '48' '6f' '73' '74' '3a' '20')
|
|
|
|
|
+#pragma clang loop unroll(full)
|
|
|
|
|
+ for (u32 i = 0; i < 900 - 8; i++) {
|
|
|
|
|
+ long data = *(long long *) (map_data->payload + i);
|
|
|
|
|
+ if (data == host_native) {
|
|
|
|
|
+ map_data->host_offset_idx = i + 8;
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ if (map_data->host_offset_idx == 0) {
|
|
|
|
|
+ cw_bpf_debug("[java client] host_offset_idx error:\n");
|
|
|
|
|
+ return 1;
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- // 单次
|
|
|
|
|
- if (end_str_len <= copy_size) {
|
|
|
|
|
- // 写头
|
|
|
|
|
- bpf_probe_write_user((void *) jbytechar_ptr + insert_pos, &map_data->header_stream, sizeof(map_data->header_stream));
|
|
|
|
|
- // 写尾
|
|
|
|
|
- bpf_probe_write_user((void *) jbytechar_ptr + tail_offset_idx, tail_payload, L7_IOVEC_BUF_SIZE);
|
|
|
|
|
|
|
+
|
|
|
|
|
+ bpf_probe_read_user(&map_data->host, sizeof map_data->host, map_data->payload + map_data->host_offset_idx);
|
|
|
|
|
+ // Build Header
|
|
|
|
|
+ bpf_tail_call(ctx, &NAME(progs_jmp_up_map), PROG_DATA_JAVA_BUILD_HEADER_UP_IDX);
|
|
|
|
|
+ return 0;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// bpf_prog_up_build_header
|
|
|
|
|
+PROGUP(java_build_header)(struct pt_regs *ctx) {
|
|
|
|
|
+ u32 k0 = 0;
|
|
|
|
|
+ struct sock_t *map_data = bpf_map_lookup_elem(&socket_heap, &k0);
|
|
|
|
|
+ if (!map_data) {
|
|
|
|
|
+ cw_bpf_debug("[java client] Failed to lookup socket_heap\n");
|
|
|
|
|
+ return 1;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 多批
|
|
|
|
|
- if (end_str_len > copy_size) {
|
|
|
|
|
-// bpf_tail_call(ctx, &jmp_table1, 1);
|
|
|
|
|
- bpf_tail_call(ctx, &NAME(progs_jmp_up_map), PROG_DATA_JAVA_UPDATE_HEADER_UP_IDX);
|
|
|
|
|
|
|
+ if (!map_data->proc_info)
|
|
|
|
|
+ return 1;
|
|
|
|
|
+
|
|
|
|
|
+ struct ebpf_proc_info proc_info;
|
|
|
|
|
+ bpf_probe_read(&proc_info, sizeof(struct ebpf_proc_info), map_data->proc_info);
|
|
|
|
|
+
|
|
|
|
|
+ char host[30];
|
|
|
|
|
+ bpf_probe_read(&host, sizeof host, map_data->host);
|
|
|
|
|
+ host[29] = '\0';
|
|
|
|
|
+ for (u32 i = 0; i < 30; i++) {
|
|
|
|
|
+ if (host[i] == '\r') {
|
|
|
|
|
+ host[i] = '\0';
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
+ cw_bpf_debug("[java client] http host %s\n", map_data->host);
|
|
|
|
|
|
|
|
|
|
+ struct apm_span_context *cw_sc = build_sc(proc_info, host);
|
|
|
|
|
+ if (cw_sc == NULL) {
|
|
|
|
|
+ return 0;
|
|
|
|
|
+ }
|
|
|
|
|
+ span_context_to_cw_string_stream(cw_sc, map_data->header_stream, '1');
|
|
|
|
|
+ cw_bpf_debug("[java client] header:[%s],sizeof %d ", map_data->header_stream, sizeof(map_data->header_stream));
|
|
|
|
|
+ // 更新Header
|
|
|
|
|
+ bpf_tail_call(ctx, &NAME(progs_jmp_up_map), PROG_DATA_JAVA_UPDATE_HEADER_UP_IDX);
|
|
|
return 0;
|
|
return 0;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -479,27 +355,43 @@ PROGUP(java_update_header)(struct pt_regs *ctx) {
|
|
|
u32 insert_pos = map_data->header_offset_idx;
|
|
u32 insert_pos = map_data->header_offset_idx;
|
|
|
u32 tail_offset_idx = insert_pos + sizeof(map_data->header_stream) - 1;
|
|
u32 tail_offset_idx = insert_pos + sizeof(map_data->header_stream) - 1;
|
|
|
u32 end_str_len = map_data->end_str_len;
|
|
u32 end_str_len = map_data->end_str_len;
|
|
|
- void * jbytechar_ptr = map_data->payload_char_p;
|
|
|
|
|
|
|
+ void *jbytechar_ptr = map_data->payload_char_p;
|
|
|
|
|
+ void *len_from_rbp_ptr = map_data->payload_len_p;
|
|
|
|
|
|
|
|
if (!jbytechar_ptr) {
|
|
if (!jbytechar_ptr) {
|
|
|
return -1;
|
|
return -1;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ long new_val = map_data->size + HEADER_LEN;
|
|
|
|
|
+
|
|
|
|
|
+ long res = bpf_probe_write_user((void *) len_from_rbp_ptr, &new_val, sizeof(new_val));
|
|
|
|
|
+ if (res < 0) {
|
|
|
|
|
+ cw_bpf_debug("[java client] Failed to write value to user address: %p, error: %ld\n", len_from_rbp_ptr, res);
|
|
|
|
|
+ return -1;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 单次
|
|
|
|
|
+ if (end_str_len <= copy_size) {
|
|
|
|
|
+ // 写头
|
|
|
|
|
+ bpf_probe_write_user((void *) jbytechar_ptr + insert_pos, &map_data->header_stream,
|
|
|
|
|
+ sizeof(map_data->header_stream));
|
|
|
|
|
+ // 写尾
|
|
|
|
|
+ bpf_probe_write_user((void *) jbytechar_ptr + tail_offset_idx, tail_payload, L7_IOVEC_BUF_SIZE);
|
|
|
|
|
+ return 0;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
// 多批
|
|
// 多批
|
|
|
u32 chunk = end_str_len / copy_size;
|
|
u32 chunk = end_str_len / copy_size;
|
|
|
- bpf_probe_write_user((void *) jbytechar_ptr + insert_pos, &map_data->header_stream, sizeof(map_data->header_stream));
|
|
|
|
|
|
|
+ bpf_probe_write_user((void *) jbytechar_ptr + insert_pos, &map_data->header_stream,
|
|
|
|
|
+ sizeof(map_data->header_stream));
|
|
|
for (u32 i = 0; i < MAX_CHUNK; i++) {
|
|
for (u32 i = 0; i < MAX_CHUNK; i++) {
|
|
|
if (i <= chunk) {
|
|
if (i <= chunk) {
|
|
|
tail_offset_idx = insert_pos + sizeof(map_data->header_stream) - 1 + i * copy_size;
|
|
tail_offset_idx = insert_pos + sizeof(map_data->header_stream) - 1 + i * copy_size;
|
|
|
- bpf_probe_write_user((void *) jbytechar_ptr + tail_offset_idx, tail_payload + i * copy_size, L7_IOVEC_BUF_SIZE);
|
|
|
|
|
|
|
+ bpf_probe_write_user((void *) jbytechar_ptr + tail_offset_idx, tail_payload + i * copy_size,
|
|
|
|
|
+ L7_IOVEC_BUF_SIZE);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
-//
|
|
|
|
|
-// long new_val;
|
|
|
|
|
-// new_val = map_data->size + HEADER_LEN;
|
|
|
|
|
-//
|
|
|
|
|
-// long res = bpf_probe_write_user((void *) len_from_rbp_ptr, &new_val, sizeof(new_val));
|
|
|
|
|
-// if (res < 0) cw_bpf_debug("Failed to write value to user address: %p, error: %ld\n", len_from_rbp_ptr, res);
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
return 0;
|
|
|
}
|
|
}
|