diff --git a/0-introduce/index.html b/0-introduce/index.html index ca1f462..efccd0b 100644 --- a/0-introduce/index.html +++ b/0-introduce/index.html @@ -83,7 +83,7 @@ diff --git a/1-helloworld/index.html b/1-helloworld/index.html index c1bc57f..07de2f5 100644 --- a/1-helloworld/index.html +++ b/1-helloworld/index.html @@ -83,7 +83,7 @@ diff --git a/10-hardirqs/index.html b/10-hardirqs/index.html index 0e3a1f5..c5964eb 100644 --- a/10-hardirqs/index.html +++ b/10-hardirqs/index.html @@ -83,7 +83,7 @@ diff --git a/11-bootstrap/index.html b/11-bootstrap/index.html index 2e9be41..03d66ad 100644 --- a/11-bootstrap/index.html +++ b/11-bootstrap/index.html @@ -83,7 +83,7 @@ diff --git a/13-tcpconnlat/index.html b/13-tcpconnlat/index.html index 2a0a774..4b07f81 100644 --- a/13-tcpconnlat/index.html +++ b/13-tcpconnlat/index.html @@ -83,7 +83,7 @@ diff --git a/14-tcpstates/index.html b/14-tcpstates/index.html index 562e979..5d2ee6e 100644 --- a/14-tcpstates/index.html +++ b/14-tcpstates/index.html @@ -83,7 +83,7 @@ diff --git a/15-javagc/index.html b/15-javagc/index.html index 6fd40b1..a4e39ac 100644 --- a/15-javagc/index.html +++ b/15-javagc/index.html @@ -83,7 +83,7 @@ diff --git a/16-memleak/index.html b/16-memleak/index.html index 97eeaef..79a49da 100644 --- a/16-memleak/index.html +++ b/16-memleak/index.html @@ -83,7 +83,7 @@ diff --git a/17-biopattern/index.html b/17-biopattern/index.html index b0d93cb..3e88b46 100644 --- a/17-biopattern/index.html +++ b/17-biopattern/index.html @@ -83,7 +83,7 @@ diff --git a/18-further-reading/index.html b/18-further-reading/index.html index 72de609..6c12d66 100644 --- a/18-further-reading/index.html +++ b/18-further-reading/index.html @@ -83,7 +83,7 @@ diff --git a/19-lsm-connect/index.html b/19-lsm-connect/index.html index 92ff4d3..cb14962 100644 --- a/19-lsm-connect/index.html +++ b/19-lsm-connect/index.html @@ -83,7 +83,7 @@ diff --git a/2-kprobe-unlink/index.html b/2-kprobe-unlink/index.html index 772c149..adaaa4f 100644 --- a/2-kprobe-unlink/index.html +++ b/2-kprobe-unlink/index.html @@ -83,7 +83,7 @@ diff --git a/20-tc/index.html b/20-tc/index.html index cf82084..4d9d889 100644 --- a/20-tc/index.html +++ b/20-tc/index.html @@ -83,7 +83,7 @@ diff --git a/22-android/index.html b/22-android/index.html index 97f73f9..e06c886 100644 --- a/22-android/index.html +++ b/22-android/index.html @@ -83,7 +83,7 @@ diff --git a/23-http/index.html b/23-http/index.html index edd6ec6..ad620e4 100644 --- a/23-http/index.html +++ b/23-http/index.html @@ -83,7 +83,7 @@ diff --git a/24-hide/index.html b/24-hide/index.html index 08ce921..3be0d1e 100644 --- a/24-hide/index.html +++ b/24-hide/index.html @@ -83,7 +83,7 @@ diff --git a/25-signal/index.html b/25-signal/index.html index 60feb76..3a2a263 100644 --- a/25-signal/index.html +++ b/25-signal/index.html @@ -83,7 +83,7 @@ @@ -299,7 +299,7 @@ int bpf_dos(struct trace_event_raw_sys_enter *ctx)
./ecc signal.bpf.c signal.h
使用方式:
-sudo ./ecli package.json
+$ sudo ./ecli package.json
这个程序会对任何试图使用 ptrace 系统调用的程序,例如 strace,发出 SIG_KILL 信号。
一旦 eBPF 程序开始运行,你可以通过运行以下命令进行测试:
diff --git a/26-sudo/index.html b/26-sudo/index.html
index e9b7ec3..dc61224 100644
--- a/26-sudo/index.html
+++ b/26-sudo/index.html
@@ -83,7 +83,7 @@
diff --git a/27-replace/index.html b/27-replace/index.html
index 130e4d0..fb73cfd 100644
--- a/27-replace/index.html
+++ b/27-replace/index.html
@@ -83,7 +83,7 @@
diff --git a/28-detach/index.html b/28-detach/index.html
index 7e74cc2..e393bc9 100644
--- a/28-detach/index.html
+++ b/28-detach/index.html
@@ -83,7 +83,7 @@
diff --git a/29-sockops/index.html b/29-sockops/index.html
index 3dd12ff..39acd97 100644
--- a/29-sockops/index.html
+++ b/29-sockops/index.html
@@ -83,7 +83,7 @@
diff --git a/3-fentry-unlink/index.html b/3-fentry-unlink/index.html
index 0c99000..2b539ca 100644
--- a/3-fentry-unlink/index.html
+++ b/3-fentry-unlink/index.html
@@ -83,7 +83,7 @@
diff --git a/30-sslsniff/index.html b/30-sslsniff/index.html
index c855166..09a4352 100644
--- a/30-sslsniff/index.html
+++ b/30-sslsniff/index.html
@@ -83,7 +83,7 @@
diff --git a/4-opensnoop/index.html b/4-opensnoop/index.html
index 2932e70..b9fc28f 100644
--- a/4-opensnoop/index.html
+++ b/4-opensnoop/index.html
@@ -83,7 +83,7 @@
diff --git a/404.html b/404.html
index 2686048..3aa8d3d 100644
--- a/404.html
+++ b/404.html
@@ -84,7 +84,7 @@
diff --git a/5-uprobe-bashreadline/index.html b/5-uprobe-bashreadline/index.html
index 317c511..94a66a2 100644
--- a/5-uprobe-bashreadline/index.html
+++ b/5-uprobe-bashreadline/index.html
@@ -83,7 +83,7 @@
diff --git a/6-sigsnoop/index.html b/6-sigsnoop/index.html
index 341b38c..d201b3b 100644
--- a/6-sigsnoop/index.html
+++ b/6-sigsnoop/index.html
@@ -83,7 +83,7 @@
diff --git a/7-execsnoop/index.html b/7-execsnoop/index.html
index 1c93e6f..effc917 100644
--- a/7-execsnoop/index.html
+++ b/7-execsnoop/index.html
@@ -83,7 +83,7 @@
diff --git a/8-exitsnoop/index.html b/8-exitsnoop/index.html
index 5bdf22e..b70e685 100644
--- a/8-exitsnoop/index.html
+++ b/8-exitsnoop/index.html
@@ -83,7 +83,7 @@
diff --git a/9-runqlat/index.html b/9-runqlat/index.html
index 8c544ec..62a85bf 100644
--- a/9-runqlat/index.html
+++ b/9-runqlat/index.html
@@ -83,7 +83,7 @@
diff --git a/bcc-documents/kernel-versions.html b/bcc-documents/kernel-versions.html
index 57689ad..08181c5 100644
--- a/bcc-documents/kernel-versions.html
+++ b/bcc-documents/kernel-versions.html
@@ -83,7 +83,7 @@
diff --git a/bcc-documents/kernel_config.html b/bcc-documents/kernel_config.html
index 66c33db..2bdce0a 100644
--- a/bcc-documents/kernel_config.html
+++ b/bcc-documents/kernel_config.html
@@ -83,7 +83,7 @@
diff --git a/bcc-documents/reference_guide.html b/bcc-documents/reference_guide.html
index ce383fe..154500a 100644
--- a/bcc-documents/reference_guide.html
+++ b/bcc-documents/reference_guide.html
@@ -83,7 +83,7 @@
diff --git a/bcc-documents/special_filtering.html b/bcc-documents/special_filtering.html
index 812fed3..d2aa2bf 100644
--- a/bcc-documents/special_filtering.html
+++ b/bcc-documents/special_filtering.html
@@ -83,7 +83,7 @@
diff --git a/bcc-documents/tutorial.html b/bcc-documents/tutorial.html
index 2299b60..aad9bf6 100644
--- a/bcc-documents/tutorial.html
+++ b/bcc-documents/tutorial.html
@@ -83,7 +83,7 @@
diff --git a/bcc-documents/tutorial_bcc_python_developer.html b/bcc-documents/tutorial_bcc_python_developer.html
index 4162615..744ec06 100644
--- a/bcc-documents/tutorial_bcc_python_developer.html
+++ b/bcc-documents/tutorial_bcc_python_developer.html
@@ -83,7 +83,7 @@
diff --git a/index.html b/index.html
index 8225e89..656d390 100644
--- a/index.html
+++ b/index.html
@@ -83,7 +83,7 @@
diff --git a/print.html b/print.html
index dab17b6..afa5ed5 100644
--- a/print.html
+++ b/print.html
@@ -84,7 +84,7 @@
@@ -5418,7 +5418,7 @@ int bpf_dos(struct trace_event_raw_sys_enter *ctx)
./ecc signal.bpf.c signal.h
使用方式:
-sudo ./ecli package.json
+$ sudo ./ecli package.json
这个程序会对任何试图使用 ptrace 系统调用的程序,例如 strace,发出 SIG_KILL 信号。
一旦 eBPF 程序开始运行,你可以通过运行以下命令进行测试:
diff --git a/searchindex.js b/searchindex.js
index 590ce3a..514f27c 100644
--- a/searchindex.js
+++ b/searchindex.js
@@ -1 +1 @@
-Object.assign(window.search, {"doc_urls":["0-introduce/index.html#ebpf-入门开发实践教程零介绍-ebpf-的基本概念常见的开发工具","0-introduce/index.html#1-ebpf简介安全和有效地扩展内核","0-introduce/index.html#ebpf-的未来内核的-javascript-可编程接口","0-introduce/index.html#2-关于如何学习-ebpf-相关的开发的一些建议","0-introduce/index.html#ebpf-入门5-7h","0-introduce/index.html#了解如何开发-ebpf-程序10-15h","0-introduce/index.html#3-如何使用ebpf编程","0-introduce/index.html#编写-ebpf-程序","0-introduce/index.html#bcc","0-introduce/index.html#ebpf-go-library","0-introduce/index.html#libbpf","0-introduce/index.html#eunomia-bpf","0-introduce/index.html#参考资料","1-helloworld/index.html#ebpf-入门开发实践教程一hello-world基本框架和开发流程","1-helloworld/index.html#ebpf开发环境准备与基本开发流程","1-helloworld/index.html#安装必要的软件和工具","1-helloworld/index.html#下载安装-eunomia-bpf-开发工具","1-helloworld/index.html#hello-world---minimal-ebpf-program","1-helloworld/index.html#ebpf-程序的基本框架","1-helloworld/index.html#tracepoints","1-helloworld/index.html#github-模板轻松构建-ebpf-项目和开发环境","1-helloworld/index.html#总结","2-kprobe-unlink/index.html#ebpf-入门开发实践教程二在-ebpf-中使用-kprobe-监测捕获-unlink-系统调用","2-kprobe-unlink/index.html#kprobes-技术背景","2-kprobe-unlink/index.html#kprobe-示例","2-kprobe-unlink/index.html#总结","3-fentry-unlink/index.html#ebpf-入门开发实践教程三在-ebpf-中使用-fentry-监测捕获-unlink-系统调用","3-fentry-unlink/index.html#fentry","3-fentry-unlink/index.html#总结","4-opensnoop/index.html#ebpf-入门开发实践教程四在-ebpf-中捕获进程打开文件的系统调用集合使用全局变量过滤进程-pid","4-opensnoop/index.html#在-ebpf-中捕获进程打开文件的系统调用集合","4-opensnoop/index.html#使用全局变量在-ebpf-中过滤进程-pid","4-opensnoop/index.html#总结","5-uprobe-bashreadline/index.html#ebpf-入门开发实践教程五在-ebpf-中使用--uprobe-捕获-bash-的-readline-函数调用","5-uprobe-bashreadline/index.html#什么是uprobe","5-uprobe-bashreadline/index.html#使用-uprobe-捕获-bash-的-readline-函数调用","5-uprobe-bashreadline/index.html#总结","6-sigsnoop/index.html#ebpf-入门开发实践教程六捕获进程发送信号的系统调用集合使用-hash-map-保存状态","6-sigsnoop/index.html#sigsnoop","6-sigsnoop/index.html#总结","7-execsnoop/index.html#ebpf-入门实践教程七捕获进程执行事件通过-perf-event-array-向用户态打印输出","7-execsnoop/index.html#perf-buffer","7-execsnoop/index.html#execsnoop","7-execsnoop/index.html#总结","8-exitsnoop/index.html#ebpf-入门开发实践教程八在-ebpf-中使用-exitsnoop-监控进程退出事件使用-ring-buffer-向用户态打印输出","8-exitsnoop/index.html#ring-buffer","8-exitsnoop/index.html#ebpf-ringbuf-vs-ebpf-perfbuf","8-exitsnoop/index.html#exitsnoop","8-exitsnoop/index.html#compile-and-run","8-exitsnoop/index.html#总结","9-runqlat/index.html#ebpf-入门开发实践教程九捕获进程调度延迟以直方图方式记录","9-runqlat/index.html#runqlat-原理","9-runqlat/index.html#runqlat-代码实现","9-runqlat/index.html#runqlatbpfc","9-runqlat/index.html#runqlath","9-runqlat/index.html#编译运行","9-runqlat/index.html#总结","10-hardirqs/index.html#ebpf-入门开发实践教程十在-ebpf-中使用-hardirqs-或-softirqs-捕获中断事件","10-hardirqs/index.html#hardirqs-和-softirqs-是什么","10-hardirqs/index.html#实现原理","10-hardirqs/index.html#hardirqs-代码实现","10-hardirqs/index.html#运行代码","10-hardirqs/index.html#总结","11-bootstrap/index.html#ebpf-入门开发实践教程十一在-ebpf-中使用-libbpf-开发用户态程序并跟踪-exec-和-exit-系统调用","11-bootstrap/index.html#libbpf-库以及为什么需要使用它","11-bootstrap/index.html#什么是-bootstrap","11-bootstrap/index.html#bootstrap","11-bootstrap/index.html#内核态-ebpf-程序-bootstrapbpfc","11-bootstrap/index.html#用户态bootstrapc","11-bootstrap/index.html#安装依赖","11-bootstrap/index.html#编译运行","11-bootstrap/index.html#总结","13-tcpconnlat/index.html#ebpf入门开发实践教程十三统计-tcp-连接延时并使用-libbpf-在用户态处理数据","13-tcpconnlat/index.html#背景","13-tcpconnlat/index.html#tcpconnlat-工具概述","13-tcpconnlat/index.html#tcp-连接原理","13-tcpconnlat/index.html#tcpconnlat-的-ebpf-实现","13-tcpconnlat/index.html#tcp_v4_connect-函数解析","13-tcpconnlat/index.html#内核态代码","13-tcpconnlat/index.html#用户态数据处理","13-tcpconnlat/index.html#编译运行","13-tcpconnlat/index.html#总结","14-tcpstates/index.html#ebpf入门实践教程十四记录-tcp-连接状态与-tcp-rtt","14-tcpstates/index.html#tcprtt-与-tcpstates","14-tcpstates/index.html#tcpstate","14-tcpstates/index.html#定义-bpf-maps","14-tcpstates/index.html#追踪-tcp-连接状态变化","14-tcpstates/index.html#更新时间戳","14-tcpstates/index.html#tcprtt","14-tcpstates/index.html#编译运行","14-tcpstates/index.html#总结","15-javagc/index.html#ebpf-入门实践教程十五使用-usdt-捕获用户态-java-gc-事件耗时","15-javagc/index.html#usdt-介绍","15-javagc/index.html#用户层面的追踪机制用户级动态跟踪和-usdt","15-javagc/index.html#java-gc-介绍","15-javagc/index.html#ebpf-实现机制","15-javagc/index.html#内核态程序","15-javagc/index.html#用户态程序","15-javagc/index.html#安装依赖","15-javagc/index.html#编译运行","15-javagc/index.html#总结","16-memleak/index.html#ebpf-入门实践教程十六编写-ebpf-程序-memleak-监控内存泄漏","16-memleak/index.html#背景及其重要性","16-memleak/index.html#调试内存泄漏的挑战","16-memleak/index.html#ebpf-的作用","16-memleak/index.html#memleak-的实现原理","16-memleak/index.html#内核态-ebpf-程序实现","16-memleak/index.html#memleak-内核态-ebpf-程序实现","16-memleak/index.html#用户态程序","16-memleak/index.html#编译运行","16-memleak/index.html#总结","17-biopattern/index.html#ebpf-入门实践教程十七编写-ebpf-程序统计随机顺序磁盘-io","17-biopattern/index.html#随机顺序磁盘-io","17-biopattern/index.html#biopattern","17-biopattern/index.html#ebpf-biopattern-实现原理","17-biopattern/index.html#用户态代码","17-biopattern/index.html#总结","18-further-reading/index.html#更多的参考资料","19-lsm-connect/index.html#ebpf-入门实践教程使用-lsm-进行安全检测防御","19-lsm-connect/index.html#背景","19-lsm-connect/index.html#lsm-概述","19-lsm-connect/index.html#确认-bpf-lsm-是否可用","19-lsm-connect/index.html#编写-ebpf-程序","19-lsm-connect/index.html#编译运行","19-lsm-connect/index.html#总结","19-lsm-connect/index.html#参考","20-tc/index.html#ebpf-入门实践教程二十使用-ebpf-进行-tc-流量控制","20-tc/index.html#背景","20-tc/index.html#tc-概述","20-tc/index.html#编写-ebpf-程序","20-tc/index.html#编译运行","20-tc/index.html#总结","20-tc/index.html#参考","22-android/index.html#在-andorid-上使用-ebpf-程序","22-android/index.html#背景","22-android/index.html#测试环境","22-android/index.html#环境搭建","22-android/index.html#工具构建","22-android/index.html#结果","22-android/index.html#成功案例","22-android/index.html#一些可能的报错原因","22-android/index.html#总结","22-android/index.html#参考","23-http/index.html#http","30-sslsniff/index.html#ebpf-实践教程使用-uprobe-捕获多种库的-ssltls-明文数据","30-sslsniff/index.html#背景知识","30-sslsniff/index.html#ssl-和-tls","30-sslsniff/index.html#tls-的工作原理","30-sslsniff/index.html#ebpf-和-uprobe","30-sslsniff/index.html#用户态库","30-sslsniff/index.html#openssl-api-分析","30-sslsniff/index.html#1-ssl_read-函数","30-sslsniff/index.html#2-ssl_write-函数","30-sslsniff/index.html#ebpf-内核态代码编写","30-sslsniff/index.html#数据结构","30-sslsniff/index.html#hook-函数","30-sslsniff/index.html#hook到握手过程","30-sslsniff/index.html#用户态辅助代码分析与解读","30-sslsniff/index.html#1-支持的库挂载","30-sslsniff/index.html#2-详细挂载逻辑","30-sslsniff/index.html#编译与运行","30-sslsniff/index.html#启动-sslsniff","30-sslsniff/index.html#执行-curl-命令","30-sslsniff/index.html#sslsniff-输出","30-sslsniff/index.html#显示延迟和握手过程","30-sslsniff/index.html#16进制输出","30-sslsniff/index.html#总结","29-sockops/index.html#ebpf-sockops-示例","29-sockops/index.html#利用-ebpf-的-sockops-进行性能优化","29-sockops/index.html#运行样例","29-sockops/index.html#编译-ebpf-程序","29-sockops/index.html#加载-ebpf-程序","29-sockops/index.html#运行--iperf3--服务器","29-sockops/index.html#运行--iperf3--客户端","29-sockops/index.html#收集追踪","29-sockops/index.html#卸载-ebpf-程序","29-sockops/index.html#参考资料","24-hide/index.html#ebpf-开发实践使用-ebpf-隐藏进程或文件信息","24-hide/index.html#背景知识与实现机制","24-hide/index.html#内核态-ebpf-程序实现","24-hide/index.html#用户态-ebpf-程序实现","24-hide/index.html#编译运行隐藏-pid","24-hide/index.html#总结","25-signal/index.html#ebpf-入门实践教程用-bpf_send_signal-发送信号终止恶意进程","25-signal/index.html#使用场景","25-signal/index.html#现有方案的不足","25-signal/index.html#新方案的优势","25-signal/index.html#内核态代码分析","25-signal/index.html#代码分析","25-signal/index.html#1-数据结构定义-signalh","25-signal/index.html#2-ebpf-程序-signalbpfc","25-signal/index.html#编译运行","25-signal/index.html#总结","25-signal/index.html#参考资料","26-sudo/index.html#使用-ebpf-添加-sudo-用户","26-sudo/index.html#参考资料","27-replace/index.html#使用-ebpf-替换任意程序读取或写入的文本","27-replace/index.html#参考资料","28-detach/index.html#在用户态应用退出后运行-ebpf-程序ebpf-程序的生命周期","28-detach/index.html#ebpf-程序的生命周期","28-detach/index.html#运行","28-detach/index.html#参考资料","30-sslsniff/index.html#ebpf-实践教程使用-uprobe-捕获多种库的-ssltls-明文数据","30-sslsniff/index.html#背景知识","30-sslsniff/index.html#ssl-和-tls","30-sslsniff/index.html#tls-的工作原理","30-sslsniff/index.html#ebpf-和-uprobe","30-sslsniff/index.html#用户态库","30-sslsniff/index.html#openssl-api-分析","30-sslsniff/index.html#1-ssl_read-函数","30-sslsniff/index.html#2-ssl_write-函数","30-sslsniff/index.html#ebpf-内核态代码编写","30-sslsniff/index.html#数据结构","30-sslsniff/index.html#hook-函数","30-sslsniff/index.html#hook到握手过程","30-sslsniff/index.html#用户态辅助代码分析与解读","30-sslsniff/index.html#1-支持的库挂载","30-sslsniff/index.html#2-详细挂载逻辑","30-sslsniff/index.html#编译与运行","30-sslsniff/index.html#启动-sslsniff","30-sslsniff/index.html#执行-curl-命令","30-sslsniff/index.html#sslsniff-输出","30-sslsniff/index.html#显示延迟和握手过程","30-sslsniff/index.html#16进制输出","30-sslsniff/index.html#总结","bcc-documents/kernel-versions.html#linux-内核版本的-bpf-功能","bcc-documents/kernel-versions.html#ebpf支持","bcc-documents/kernel-versions.html#jit编译","bcc-documents/kernel-versions.html#主要特性","bcc-documents/kernel-versions.html#程序类型","bcc-documents/kernel-versions.html#map-types--aka--表格-在-bcc-术语中","bcc-documents/kernel-versions.html#map-类型","bcc-documents/kernel-versions.html#map-userspace-api","bcc-documents/kernel-versions.html#xdp","bcc-documents/kernel-versions.html#程序类型-1","bcc-documents/kernel_config.html#bpf-特性的内核配置","bcc-documents/kernel_config.html#与-bpf-相关的内核配置","bcc-documents/reference_guide.html#bcc-参考指南","bcc-documents/reference_guide.html#目录","bcc-documents/reference_guide.html#bpf-c","bcc-documents/reference_guide.html#events--arguments","bcc-documents/reference_guide.html#1-kprobes","bcc-documents/reference_guide.html#2-kretprobes","bcc-documents/reference_guide.html#3-tracepoints","bcc-documents/reference_guide.html#4-uprobes","bcc-documents/reference_guide.html#6-usdt探测点","bcc-documents/reference_guide.html#7-原始跟踪点","bcc-documents/reference_guide.html#8-系统调用跟踪点","bcc-documents/reference_guide.html#9-kfuncs","bcc-documents/reference_guide.html#10-kretfuncs","bcc-documents/reference_guide.html#11-lsm-probes","bcc-documents/reference_guide.html#12-bpf迭代器","bcc-documents/reference_guide.html#数据","bcc-documents/reference_guide.html#1-bpf_probe_read_kernel","bcc-documents/reference_guide.html#2-bpf_probe_read_kernel_strshell","bcc-documents/reference_guide.html#3-bpf_ktime_get_ns","bcc-documents/reference_guide.html#4-bpf_get_current_pid_tgid","bcc-documents/reference_guide.html#5-bpf_get_current_uid_gid","bcc-documents/reference_guide.html#6-bpf_get_current_comm","bcc-documents/reference_guide.html#7-bpf_get_current_task","bcc-documents/reference_guide.html#8-bpf_log2l","bcc-documents/reference_guide.html#9-bpf_get_prandom_u32","bcc-documents/reference_guide.html#10-bpf_probe_read_user","bcc-documents/reference_guide.html#11-bpf_probe_read_user_str","bcc-documents/reference_guide.html#12-bpf_get_ns_current_pid_tgid","bcc-documents/reference_guide.html#调试","bcc-documents/reference_guide.html#1-bpf_override_return","bcc-documents/reference_guide.html#输出","bcc-documents/reference_guide.html#1-bpf_trace_printk","bcc-documents/reference_guide.html#2-bpf_perf_output","bcc-documents/reference_guide.html#3-perf_submit","bcc-documents/reference_guide.html#4-perf_submit_skb","bcc-documents/reference_guide.html#5-bpf_ringbuf_output","bcc-documents/reference_guide.html#6-ringbuf_output","bcc-documents/reference_guide.html#7-ringbuf_reserve","bcc-documents/reference_guide.html#8-ringbuf_submit","bcc-documents/reference_guide.html#9-ringbuf_discard","bcc-documents/reference_guide.html#maps","bcc-documents/reference_guide.html#1-bpf_table","bcc-documents/reference_guide.html#2-bpf_hash","bcc-documents/reference_guide.html#3-bpf_array","bcc-documents/reference_guide.html#4-bpf_histogram","bcc-documents/reference_guide.html#5-bpf_stack_trace","bcc-documents/reference_guide.html#6-bpf_perf_array","bcc-documents/reference_guide.html#7-bpf_percpu_hash","bcc-documents/reference_guide.html#8-bpf_percpu_array","bcc-documents/reference_guide.html#9-bpf_lpm_trie","bcc-documents/reference_guide.html#10-bpf_prog_array","bcc-documents/reference_guide.html#11-bpf_devmap","bcc-documents/reference_guide.html#12-bpf_cpumap","bcc-documents/reference_guide.html#13-bpf_xskmap","bcc-documents/reference_guide.html#14-bpf_array_of_maps","bcc-documents/reference_guide.html#15-bpf_hash_of_maps","bcc-documents/reference_guide.html#16-bpf_stack","bcc-documents/reference_guide.html#17-bpf_queue","bcc-documents/reference_guide.html#18-bpf_sockhash","bcc-documents/reference_guide.html#19-maplookup","bcc-documents/reference_guide.html#20-maplookup_or_try_init","bcc-documents/reference_guide.html#21-mapdelete","bcc-documents/reference_guide.html#22-mapupdate","bcc-documents/reference_guide.html#23-mapinsert","bcc-documents/reference_guide.html#24-mapincrement","bcc-documents/reference_guide.html#25-mapget_stackid","bcc-documents/reference_guide.html#26-mapperf_read","bcc-documents/reference_guide.html#27-mapcall","bcc-documents/reference_guide.html#28-mapredirect_map","bcc-documents/reference_guide.html#29-mappush","bcc-documents/reference_guide.html#30-mappop","bcc-documents/reference_guide.html#31-mappeek","bcc-documents/reference_guide.html#32-mapsock_hash_update","bcc-documents/reference_guide.html#33-mapmsg_redirect_hash","bcc-documents/reference_guide.html#34-mapsk_redirect_hash","bcc-documents/reference_guide.html#许可证","bcc-documents/reference_guide.html#rewriter","bcc-documents/reference_guide.html#bcc-python","bcc-documents/reference_guide.html#初始化","bcc-documents/reference_guide.html#1-bpf","bcc-documents/reference_guide.html#事件","bcc-documents/reference_guide.html#1-attach_kprobe","bcc-documents/reference_guide.html#2-attach_kretprobe","bcc-documents/reference_guide.html#3-attach_tracepoint","bcc-documents/reference_guide.html#4-attach_uprobe","bcc-documents/reference_guide.html#5-attach_uretprobe","bcc-documents/reference_guide.html#6-usdtenable_probe","bcc-documents/reference_guide.html#7-attach_raw_tracepoint","bcc-documents/reference_guide.html#8-attach_raw_socket","bcc-documents/reference_guide.html#9-attach_xdp","bcc-documents/reference_guide.html#10-attach_func","bcc-documents/reference_guide.html#12-detach_kprobe","bcc-documents/reference_guide.html#13-detach_kretprobe","bcc-documents/reference_guide.html#调试输出","bcc-documents/reference_guide.html#1-trace_print","bcc-documents/reference_guide.html#2-trace_fields","bcc-documents/reference_guide.html#输出-api","bcc-documents/reference_guide.html#1-perf_buffer_poll","bcc-documents/reference_guide.html#2-ring_buffer_poll","bcc-documents/reference_guide.html#3-ring_buffer_consume","bcc-documents/reference_guide.html#map-apis","bcc-documents/reference_guide.html#1-get_table","bcc-documents/reference_guide.html#2-open_perf_buffer","bcc-documents/reference_guide.html#4-values","bcc-documents/reference_guide.html#5-clear","bcc-documents/reference_guide.html#6-items_lookup_and_delete_batch","bcc-documents/reference_guide.html#7-items_lookup_batch","bcc-documents/reference_guide.html#8-items_delete_batch","bcc-documents/reference_guide.html#9-items_update_batch","bcc-documents/reference_guide.html#11-print_linear_hist语法-tableprint_linear_histval_typevalue-section_headerbucket-ptr-section_print_fnnone","bcc-documents/reference_guide.html#12-open_ring_buffer","bcc-documents/reference_guide.html#13-push","bcc-documents/reference_guide.html#14-pop","bcc-documents/reference_guide.html#15-peek","bcc-documents/reference_guide.html#辅助方法","bcc-documents/reference_guide.html#1-ksym","bcc-documents/reference_guide.html#2-ksymname","bcc-documents/reference_guide.html#3-sym","bcc-documents/reference_guide.html#4-num_open_kprobes","bcc-documents/reference_guide.html#5-get_syscall_fnname","bcc-documents/reference_guide.html#bpf-错误","bcc-documents/reference_guide.html#1-invalid-mem-access","bcc-documents/reference_guide.html#2-无法从专有程序调用-gpl-only-函数","bcc-documents/reference_guide.html#环境变量","bcc-documents/reference_guide.html#1-内核源代码目录","bcc-documents/reference_guide.html#2-内核版本覆盖","bcc-documents/special_filtering.html#特殊过滤","bcc-documents/special_filtering.html#按-cgroups过滤","bcc-documents/special_filtering.html#按命名空间选择挂载点进行过滤","bcc-documents/tutorial.html#bcc-教程","bcc-documents/tutorial.html#可观察性","bcc-documents/tutorial.html#0-使用bcc之前","bcc-documents/tutorial.html#1-性能分析","bcc-documents/tutorial.html#2-使用通用工具进行可观察性","bcc-documents/tutorial.html#网络","bcc-documents/tutorial_bcc_python_developer.html#bcc-python-开发者教程","bcc-documents/tutorial_bcc_python_developer.html#可观测性","bcc-documents/tutorial_bcc_python_developer.html#第1课-你好世界","bcc-documents/tutorial_bcc_python_developer.html#第二课-sys_sync","bcc-documents/tutorial_bcc_python_developer.html#第三课-hello_fieldspy","bcc-documents/tutorial_bcc_python_developer.html#lesson-4-sync_timingpy","bcc-documents/tutorial_bcc_python_developer.html#第5课-sync_countpy","bcc-documents/tutorial_bcc_python_developer.html#第6课-disksnooppy","bcc-documents/tutorial_bcc_python_developer.html#lesson-7-hello_perf_outputpy","bcc-documents/tutorial_bcc_python_developer.html#第八课-sync_perf_outputpy","bcc-documents/tutorial_bcc_python_developer.html#第九课-bitehistpy","bcc-documents/tutorial_bcc_python_developer.html#lesson-10-disklatencypy-lesson-11-vfsreadlatpy","bcc-documents/tutorial_bcc_python_developer.html#lesson-12-urandomreadpy","bcc-documents/tutorial_bcc_python_developer.html#第13课-disksnooppy已修复","bcc-documents/tutorial_bcc_python_developer.html#第14课-strlen_countpy","bcc-documents/tutorial_bcc_python_developer.html#第15课nodejs_http_serverpy","bcc-documents/tutorial_bcc_python_developer.html#第16课-task_switchc","bcc-documents/tutorial_bcc_python_developer.html#第17课-进一步研究","bcc-documents/tutorial_bcc_python_developer.html#网络"],"index":{"documentStore":{"docInfo":{"0":{"body":0,"breadcrumbs":3,"title":2},"1":{"body":4,"breadcrumbs":3,"title":2},"10":{"body":10,"breadcrumbs":2,"title":1},"100":{"body":17,"breadcrumbs":3,"title":0},"101":{"body":4,"breadcrumbs":5,"title":3},"102":{"body":2,"breadcrumbs":2,"title":0},"103":{"body":5,"breadcrumbs":2,"title":0},"104":{"body":48,"breadcrumbs":3,"title":1},"105":{"body":12,"breadcrumbs":3,"title":1},"106":{"body":0,"breadcrumbs":3,"title":1},"107":{"body":564,"breadcrumbs":4,"title":2},"108":{"body":88,"breadcrumbs":2,"title":0},"109":{"body":61,"breadcrumbs":2,"title":0},"11":{"body":50,"breadcrumbs":3,"title":2},"110":{"body":19,"breadcrumbs":2,"title":0},"111":{"body":5,"breadcrumbs":6,"title":3},"112":{"body":22,"breadcrumbs":4,"title":1},"113":{"body":80,"breadcrumbs":4,"title":1},"114":{"body":287,"breadcrumbs":5,"title":2},"115":{"body":154,"breadcrumbs":3,"title":0},"116":{"body":23,"breadcrumbs":3,"title":0},"117":{"body":4,"breadcrumbs":0,"title":0},"118":{"body":9,"breadcrumbs":3,"title":2},"119":{"body":14,"breadcrumbs":1,"title":0},"12":{"body":19,"breadcrumbs":1,"title":0},"120":{"body":24,"breadcrumbs":2,"title":1},"121":{"body":19,"breadcrumbs":3,"title":2},"122":{"body":103,"breadcrumbs":2,"title":1},"123":{"body":122,"breadcrumbs":1,"title":0},"124":{"body":20,"breadcrumbs":1,"title":0},"125":{"body":10,"breadcrumbs":1,"title":0},"126":{"body":0,"breadcrumbs":5,"title":3},"127":{"body":15,"breadcrumbs":2,"title":0},"128":{"body":39,"breadcrumbs":3,"title":1},"129":{"body":112,"breadcrumbs":3,"title":1},"13":{"body":11,"breadcrumbs":6,"title":3},"130":{"body":75,"breadcrumbs":2,"title":0},"131":{"body":15,"breadcrumbs":2,"title":0},"132":{"body":6,"breadcrumbs":2,"title":0},"133":{"body":13,"breadcrumbs":4,"title":2},"134":{"body":60,"breadcrumbs":2,"title":0},"135":{"body":19,"breadcrumbs":2,"title":0},"136":{"body":76,"breadcrumbs":2,"title":0},"137":{"body":10,"breadcrumbs":2,"title":0},"138":{"body":2,"breadcrumbs":2,"title":0},"139":{"body":440,"breadcrumbs":2,"title":0},"14":{"body":0,"breadcrumbs":4,"title":1},"140":{"body":45,"breadcrumbs":2,"title":0},"141":{"body":35,"breadcrumbs":2,"title":0},"142":{"body":5,"breadcrumbs":2,"title":0},"143":{"body":1,"breadcrumbs":3,"title":1},"144":{"body":5,"breadcrumbs":5,"title":3},"145":{"body":0,"breadcrumbs":2,"title":0},"146":{"body":12,"breadcrumbs":4,"title":2},"147":{"body":15,"breadcrumbs":3,"title":1},"148":{"body":6,"breadcrumbs":4,"title":2},"149":{"body":3,"breadcrumbs":2,"title":0},"15":{"body":28,"breadcrumbs":3,"title":0},"150":{"body":6,"breadcrumbs":4,"title":2},"151":{"body":26,"breadcrumbs":4,"title":2},"152":{"body":28,"breadcrumbs":4,"title":2},"153":{"body":5,"breadcrumbs":3,"title":1},"154":{"body":35,"breadcrumbs":2,"title":0},"155":{"body":166,"breadcrumbs":3,"title":1},"156":{"body":174,"breadcrumbs":3,"title":1},"157":{"body":3,"breadcrumbs":2,"title":0},"158":{"body":42,"breadcrumbs":3,"title":1},"159":{"body":213,"breadcrumbs":3,"title":1},"16":{"body":65,"breadcrumbs":5,"title":2},"160":{"body":2,"breadcrumbs":2,"title":0},"161":{"body":3,"breadcrumbs":3,"title":1},"162":{"body":13,"breadcrumbs":3,"title":1},"163":{"body":20,"breadcrumbs":3,"title":1},"164":{"body":37,"breadcrumbs":2,"title":0},"165":{"body":14,"breadcrumbs":3,"title":1},"166":{"body":14,"breadcrumbs":2,"title":0},"167":{"body":0,"breadcrumbs":3,"title":2},"168":{"body":21,"breadcrumbs":3,"title":2},"169":{"body":1,"breadcrumbs":1,"title":0},"17":{"body":152,"breadcrumbs":8,"title":5},"170":{"body":23,"breadcrumbs":2,"title":1},"171":{"body":51,"breadcrumbs":2,"title":1},"172":{"body":4,"breadcrumbs":2,"title":1},"173":{"body":9,"breadcrumbs":2,"title":1},"174":{"body":49,"breadcrumbs":1,"title":0},"175":{"body":2,"breadcrumbs":2,"title":1},"176":{"body":3,"breadcrumbs":1,"title":0},"177":{"body":4,"breadcrumbs":3,"title":2},"178":{"body":12,"breadcrumbs":1,"title":0},"179":{"body":661,"breadcrumbs":2,"title":1},"18":{"body":17,"breadcrumbs":4,"title":1},"180":{"body":145,"breadcrumbs":2,"title":1},"181":{"body":178,"breadcrumbs":2,"title":1},"182":{"body":10,"breadcrumbs":1,"title":0},"183":{"body":9,"breadcrumbs":3,"title":2},"184":{"body":4,"breadcrumbs":1,"title":0},"185":{"body":5,"breadcrumbs":1,"title":0},"186":{"body":8,"breadcrumbs":1,"title":0},"187":{"body":5,"breadcrumbs":1,"title":0},"188":{"body":0,"breadcrumbs":1,"title":0},"189":{"body":22,"breadcrumbs":3,"title":2},"19":{"body":5,"breadcrumbs":4,"title":1},"190":{"body":134,"breadcrumbs":4,"title":3},"191":{"body":44,"breadcrumbs":1,"title":0},"192":{"body":7,"breadcrumbs":1,"title":0},"193":{"body":4,"breadcrumbs":1,"title":0},"194":{"body":17,"breadcrumbs":4,"title":2},"195":{"body":2,"breadcrumbs":2,"title":0},"196":{"body":35,"breadcrumbs":2,"title":1},"197":{"body":2,"breadcrumbs":1,"title":0},"198":{"body":3,"breadcrumbs":5,"title":2},"199":{"body":28,"breadcrumbs":4,"title":1},"2":{"body":40,"breadcrumbs":3,"title":2},"20":{"body":43,"breadcrumbs":5,"title":2},"200":{"body":34,"breadcrumbs":3,"title":0},"201":{"body":4,"breadcrumbs":3,"title":0},"202":{"body":5,"breadcrumbs":5,"title":3},"203":{"body":0,"breadcrumbs":2,"title":0},"204":{"body":12,"breadcrumbs":4,"title":2},"205":{"body":15,"breadcrumbs":3,"title":1},"206":{"body":6,"breadcrumbs":4,"title":2},"207":{"body":3,"breadcrumbs":2,"title":0},"208":{"body":6,"breadcrumbs":4,"title":2},"209":{"body":26,"breadcrumbs":4,"title":2},"21":{"body":41,"breadcrumbs":3,"title":0},"210":{"body":28,"breadcrumbs":4,"title":2},"211":{"body":5,"breadcrumbs":3,"title":1},"212":{"body":35,"breadcrumbs":2,"title":0},"213":{"body":166,"breadcrumbs":3,"title":1},"214":{"body":174,"breadcrumbs":3,"title":1},"215":{"body":3,"breadcrumbs":2,"title":0},"216":{"body":42,"breadcrumbs":3,"title":1},"217":{"body":213,"breadcrumbs":3,"title":1},"218":{"body":2,"breadcrumbs":2,"title":0},"219":{"body":3,"breadcrumbs":3,"title":1},"22":{"body":14,"breadcrumbs":6,"title":4},"220":{"body":13,"breadcrumbs":3,"title":1},"221":{"body":20,"breadcrumbs":3,"title":1},"222":{"body":37,"breadcrumbs":2,"title":0},"223":{"body":14,"breadcrumbs":3,"title":1},"224":{"body":14,"breadcrumbs":2,"title":0},"225":{"body":0,"breadcrumbs":7,"title":2},"226":{"body":2,"breadcrumbs":6,"title":1},"227":{"body":48,"breadcrumbs":6,"title":1},"228":{"body":164,"breadcrumbs":5,"title":0},"229":{"body":225,"breadcrumbs":5,"title":0},"23":{"body":58,"breadcrumbs":3,"title":1},"230":{"body":0,"breadcrumbs":9,"title":4},"231":{"body":163,"breadcrumbs":6,"title":1},"232":{"body":81,"breadcrumbs":8,"title":3},"233":{"body":962,"breadcrumbs":6,"title":1},"234":{"body":145,"breadcrumbs":5,"title":0},"235":{"body":0,"breadcrumbs":5,"title":1},"236":{"body":100,"breadcrumbs":5,"title":1},"237":{"body":4,"breadcrumbs":4,"title":1},"238":{"body":238,"breadcrumbs":3,"title":0},"239":{"body":2,"breadcrumbs":5,"title":2},"24":{"body":221,"breadcrumbs":3,"title":1},"240":{"body":0,"breadcrumbs":5,"title":2},"241":{"body":34,"breadcrumbs":5,"title":2},"242":{"body":16,"breadcrumbs":5,"title":2},"243":{"body":34,"breadcrumbs":5,"title":2},"244":{"body":39,"breadcrumbs":5,"title":2},"245":{"body":23,"breadcrumbs":5,"title":2},"246":{"body":48,"breadcrumbs":4,"title":1},"247":{"body":36,"breadcrumbs":4,"title":1},"248":{"body":20,"breadcrumbs":5,"title":2},"249":{"body":22,"breadcrumbs":5,"title":2},"25":{"body":19,"breadcrumbs":2,"title":0},"250":{"body":31,"breadcrumbs":6,"title":3},"251":{"body":36,"breadcrumbs":5,"title":2},"252":{"body":0,"breadcrumbs":3,"title":0},"253":{"body":10,"breadcrumbs":5,"title":2},"254":{"body":13,"breadcrumbs":5,"title":2},"255":{"body":5,"breadcrumbs":5,"title":2},"256":{"body":9,"breadcrumbs":5,"title":2},"257":{"body":8,"breadcrumbs":5,"title":2},"258":{"body":17,"breadcrumbs":5,"title":2},"259":{"body":22,"breadcrumbs":5,"title":2},"26":{"body":10,"breadcrumbs":6,"title":4},"260":{"body":6,"breadcrumbs":5,"title":2},"261":{"body":3,"breadcrumbs":5,"title":2},"262":{"body":8,"breadcrumbs":5,"title":2},"263":{"body":11,"breadcrumbs":5,"title":2},"264":{"body":18,"breadcrumbs":5,"title":2},"265":{"body":0,"breadcrumbs":3,"title":0},"266":{"body":19,"breadcrumbs":5,"title":2},"267":{"body":0,"breadcrumbs":3,"title":0},"268":{"body":7,"breadcrumbs":5,"title":2},"269":{"body":29,"breadcrumbs":5,"title":2},"27":{"body":198,"breadcrumbs":3,"title":1},"270":{"body":13,"breadcrumbs":5,"title":2},"271":{"body":10,"breadcrumbs":5,"title":2},"272":{"body":16,"breadcrumbs":5,"title":2},"273":{"body":9,"breadcrumbs":5,"title":2},"274":{"body":5,"breadcrumbs":5,"title":2},"275":{"body":7,"breadcrumbs":5,"title":2},"276":{"body":8,"breadcrumbs":5,"title":2},"277":{"body":0,"breadcrumbs":4,"title":1},"278":{"body":17,"breadcrumbs":5,"title":2},"279":{"body":15,"breadcrumbs":5,"title":2},"28":{"body":24,"breadcrumbs":2,"title":0},"280":{"body":12,"breadcrumbs":5,"title":2},"281":{"body":15,"breadcrumbs":5,"title":2},"282":{"body":11,"breadcrumbs":5,"title":2},"283":{"body":16,"breadcrumbs":5,"title":2},"284":{"body":18,"breadcrumbs":5,"title":2},"285":{"body":13,"breadcrumbs":5,"title":2},"286":{"body":18,"breadcrumbs":5,"title":2},"287":{"body":13,"breadcrumbs":5,"title":2},"288":{"body":10,"breadcrumbs":5,"title":2},"289":{"body":13,"breadcrumbs":5,"title":2},"29":{"body":10,"breadcrumbs":4,"title":3},"290":{"body":8,"breadcrumbs":5,"title":2},"291":{"body":16,"breadcrumbs":5,"title":2},"292":{"body":15,"breadcrumbs":5,"title":2},"293":{"body":20,"breadcrumbs":5,"title":2},"294":{"body":20,"breadcrumbs":5,"title":2},"295":{"body":27,"breadcrumbs":5,"title":2},"296":{"body":4,"breadcrumbs":5,"title":2},"297":{"body":5,"breadcrumbs":5,"title":2},"298":{"body":3,"breadcrumbs":5,"title":2},"299":{"body":4,"breadcrumbs":5,"title":2},"3":{"body":1,"breadcrumbs":3,"title":2},"30":{"body":154,"breadcrumbs":2,"title":1},"300":{"body":4,"breadcrumbs":5,"title":2},"301":{"body":13,"breadcrumbs":5,"title":2},"302":{"body":12,"breadcrumbs":5,"title":2},"303":{"body":4,"breadcrumbs":5,"title":2},"304":{"body":39,"breadcrumbs":5,"title":2},"305":{"body":52,"breadcrumbs":5,"title":2},"306":{"body":6,"breadcrumbs":5,"title":2},"307":{"body":3,"breadcrumbs":5,"title":2},"308":{"body":4,"breadcrumbs":5,"title":2},"309":{"body":12,"breadcrumbs":5,"title":2},"31":{"body":106,"breadcrumbs":3,"title":2},"310":{"body":9,"breadcrumbs":5,"title":2},"311":{"body":65,"breadcrumbs":5,"title":2},"312":{"body":97,"breadcrumbs":3,"title":0},"313":{"body":0,"breadcrumbs":4,"title":1},"314":{"body":0,"breadcrumbs":5,"title":2},"315":{"body":0,"breadcrumbs":3,"title":0},"316":{"body":74,"breadcrumbs":5,"title":2},"317":{"body":0,"breadcrumbs":3,"title":0},"318":{"body":6,"breadcrumbs":5,"title":2},"319":{"body":7,"breadcrumbs":5,"title":2},"32":{"body":28,"breadcrumbs":1,"title":0},"320":{"body":45,"breadcrumbs":5,"title":2},"321":{"body":26,"breadcrumbs":5,"title":2},"322":{"body":15,"breadcrumbs":5,"title":2},"323":{"body":13,"breadcrumbs":5,"title":2},"324":{"body":4,"breadcrumbs":5,"title":2},"325":{"body":28,"breadcrumbs":5,"title":2},"326":{"body":45,"breadcrumbs":5,"title":2},"327":{"body":31,"breadcrumbs":5,"title":2},"328":{"body":7,"breadcrumbs":5,"title":2},"329":{"body":7,"breadcrumbs":5,"title":2},"33":{"body":10,"breadcrumbs":8,"title":5},"330":{"body":0,"breadcrumbs":3,"title":0},"331":{"body":14,"breadcrumbs":5,"title":2},"332":{"body":20,"breadcrumbs":5,"title":2},"333":{"body":7,"breadcrumbs":4,"title":1},"334":{"body":13,"breadcrumbs":5,"title":2},"335":{"body":9,"breadcrumbs":5,"title":2},"336":{"body":10,"breadcrumbs":5,"title":2},"337":{"body":0,"breadcrumbs":5,"title":2},"338":{"body":4,"breadcrumbs":5,"title":2},"339":{"body":81,"breadcrumbs":5,"title":2},"34":{"body":0,"breadcrumbs":4,"title":1},"340":{"body":1,"breadcrumbs":5,"title":2},"341":{"body":14,"breadcrumbs":5,"title":2},"342":{"body":24,"breadcrumbs":5,"title":2},"343":{"body":24,"breadcrumbs":5,"title":2},"344":{"body":2,"breadcrumbs":5,"title":2},"345":{"body":62,"breadcrumbs":5,"title":2},"346":{"body":59,"breadcrumbs":9,"title":6},"347":{"body":56,"breadcrumbs":5,"title":2},"348":{"body":3,"breadcrumbs":5,"title":2},"349":{"body":3,"breadcrumbs":5,"title":2},"35":{"body":204,"breadcrumbs":6,"title":3},"350":{"body":3,"breadcrumbs":5,"title":2},"351":{"body":0,"breadcrumbs":3,"title":0},"352":{"body":6,"breadcrumbs":5,"title":2},"353":{"body":6,"breadcrumbs":5,"title":2},"354":{"body":9,"breadcrumbs":5,"title":2},"355":{"body":10,"breadcrumbs":5,"title":2},"356":{"body":9,"breadcrumbs":5,"title":2},"357":{"body":5,"breadcrumbs":4,"title":1},"358":{"body":71,"breadcrumbs":7,"title":4},"359":{"body":25,"breadcrumbs":5,"title":2},"36":{"body":23,"breadcrumbs":3,"title":0},"360":{"body":0,"breadcrumbs":3,"title":0},"361":{"body":4,"breadcrumbs":4,"title":1},"362":{"body":11,"breadcrumbs":4,"title":1},"363":{"body":0,"breadcrumbs":2,"title":0},"364":{"body":106,"breadcrumbs":3,"title":1},"365":{"body":75,"breadcrumbs":2,"title":0},"366":{"body":5,"breadcrumbs":3,"title":1},"367":{"body":0,"breadcrumbs":2,"title":0},"368":{"body":30,"breadcrumbs":4,"title":2},"369":{"body":530,"breadcrumbs":3,"title":1},"37":{"body":10,"breadcrumbs":5,"title":3},"370":{"body":278,"breadcrumbs":3,"title":1},"371":{"body":0,"breadcrumbs":2,"title":0},"372":{"body":6,"breadcrumbs":6,"title":2},"373":{"body":1,"breadcrumbs":4,"title":0},"374":{"body":55,"breadcrumbs":5,"title":1},"375":{"body":11,"breadcrumbs":5,"title":1},"376":{"body":63,"breadcrumbs":5,"title":1},"377":{"body":87,"breadcrumbs":7,"title":3},"378":{"body":0,"breadcrumbs":6,"title":2},"379":{"body":98,"breadcrumbs":6,"title":2},"38":{"body":256,"breadcrumbs":3,"title":1},"380":{"body":125,"breadcrumbs":7,"title":3},"381":{"body":1,"breadcrumbs":5,"title":1},"382":{"body":92,"breadcrumbs":5,"title":1},"383":{"body":104,"breadcrumbs":10,"title":6},"384":{"body":158,"breadcrumbs":7,"title":3},"385":{"body":3,"breadcrumbs":6,"title":2},"386":{"body":126,"breadcrumbs":6,"title":2},"387":{"body":112,"breadcrumbs":5,"title":1},"388":{"body":80,"breadcrumbs":6,"title":2},"389":{"body":10,"breadcrumbs":5,"title":1},"39":{"body":30,"breadcrumbs":2,"title":0},"390":{"body":1,"breadcrumbs":4,"title":0},"4":{"body":27,"breadcrumbs":4,"title":3},"40":{"body":16,"breadcrumbs":7,"title":4},"41":{"body":4,"breadcrumbs":5,"title":2},"42":{"body":187,"breadcrumbs":4,"title":1},"43":{"body":28,"breadcrumbs":3,"title":0},"44":{"body":9,"breadcrumbs":8,"title":5},"45":{"body":8,"breadcrumbs":5,"title":2},"46":{"body":14,"breadcrumbs":8,"title":5},"47":{"body":195,"breadcrumbs":4,"title":1},"48":{"body":99,"breadcrumbs":5,"title":2},"49":{"body":19,"breadcrumbs":3,"title":0},"5":{"body":31,"breadcrumbs":4,"title":3},"50":{"body":12,"breadcrumbs":4,"title":1},"51":{"body":85,"breadcrumbs":4,"title":1},"52":{"body":0,"breadcrumbs":4,"title":1},"53":{"body":495,"breadcrumbs":4,"title":1},"54":{"body":26,"breadcrumbs":4,"title":1},"55":{"body":225,"breadcrumbs":3,"title":0},"56":{"body":17,"breadcrumbs":3,"title":0},"57":{"body":17,"breadcrumbs":6,"title":4},"58":{"body":2,"breadcrumbs":4,"title":2},"59":{"body":25,"breadcrumbs":2,"title":0},"6":{"body":11,"breadcrumbs":3,"title":2},"60":{"body":348,"breadcrumbs":3,"title":1},"61":{"body":27,"breadcrumbs":2,"title":0},"62":{"body":21,"breadcrumbs":2,"title":0},"63":{"body":9,"breadcrumbs":8,"title":5},"64":{"body":35,"breadcrumbs":4,"title":1},"65":{"body":19,"breadcrumbs":4,"title":1},"66":{"body":6,"breadcrumbs":4,"title":1},"67":{"body":565,"breadcrumbs":5,"title":2},"68":{"body":574,"breadcrumbs":4,"title":1},"69":{"body":24,"breadcrumbs":3,"title":0},"7":{"body":9,"breadcrumbs":2,"title":1},"70":{"body":77,"breadcrumbs":3,"title":0},"71":{"body":8,"breadcrumbs":3,"title":0},"72":{"body":11,"breadcrumbs":6,"title":3},"73":{"body":13,"breadcrumbs":3,"title":0},"74":{"body":4,"breadcrumbs":4,"title":1},"75":{"body":21,"breadcrumbs":4,"title":1},"76":{"body":51,"breadcrumbs":5,"title":2},"77":{"body":322,"breadcrumbs":4,"title":1},"78":{"body":385,"breadcrumbs":3,"title":0},"79":{"body":169,"breadcrumbs":3,"title":0},"8":{"body":6,"breadcrumbs":2,"title":1},"80":{"body":53,"breadcrumbs":3,"title":0},"81":{"body":22,"breadcrumbs":3,"title":0},"82":{"body":12,"breadcrumbs":9,"title":4},"83":{"body":80,"breadcrumbs":7,"title":2},"84":{"body":189,"breadcrumbs":6,"title":1},"85":{"body":10,"breadcrumbs":7,"title":2},"86":{"body":12,"breadcrumbs":6,"title":1},"87":{"body":131,"breadcrumbs":5,"title":0},"88":{"body":179,"breadcrumbs":6,"title":1},"89":{"body":342,"breadcrumbs":5,"title":0},"9":{"body":6,"breadcrumbs":4,"title":3},"90":{"body":17,"breadcrumbs":5,"title":0},"91":{"body":10,"breadcrumbs":7,"title":4},"92":{"body":5,"breadcrumbs":4,"title":1},"93":{"body":165,"breadcrumbs":4,"title":1},"94":{"body":10,"breadcrumbs":5,"title":2},"95":{"body":3,"breadcrumbs":4,"title":1},"96":{"body":182,"breadcrumbs":3,"title":0},"97":{"body":200,"breadcrumbs":3,"title":0},"98":{"body":24,"breadcrumbs":3,"title":0},"99":{"body":51,"breadcrumbs":3,"title":0}},"docs":{"0":{"body":"","breadcrumbs":"介绍 eBPF 的基本概念、常见的开发工具 » eBPF 入门开发实践教程零:介绍 eBPF 的基本概念、常见的开发工具","id":"0","title":"eBPF 入门开发实践教程零:介绍 eBPF 的基本概念、常见的开发工具"},"1":{"body":"eBPF 是一项革命性的技术,起源于 Linux 内核,可以在操作系统的内核中运行沙盒程序。它被用来安全和有效地扩展内核的功能,而不需要改变内核的源代码或加载内核模块。eBPF 通过允许在操作系统内运行沙盒程序,应用程序开发人员可以在运行时,可编程地向操作系统动态添加额外的功能。然后,操作系统保证安全和执行效率,就像在即时编译(JIT)编译器和验证引擎的帮助下进行本地编译一样。eBPF 程序在内核版本之间是可移植的,并且可以自动更新,从而避免了工作负载中断和节点重启。 今天,eBPF被广泛用于各类场景:在现代数据中心和云原生环境中,可以提供高性能的网络包处理和负载均衡;以非常低的资源开销,做到对多种细粒度指标的可观测性,帮助应用程序开发人员跟踪应用程序,为性能故障排除提供洞察力;保障应用程序和容器运行时的安全执行,等等。可能性是无穷的,而 eBPF 在操作系统内核中所释放的创新才刚刚开始[3]。","breadcrumbs":"介绍 eBPF 的基本概念、常见的开发工具 » 1. eBPF简介:安全和有效地扩展内核","id":"1","title":"1. eBPF简介:安全和有效地扩展内核"},"10":{"body":"libbpf-bootstrap是一个基于libbpf库的BPF开发脚手架,从其 github 上可以得到其源码。 libbpf-bootstrap综合了BPF社区过去多年的实践,为开发者提了一个现代化的、便捷的工作流,实 现了一次编译,重复使用的目的。 基于libbpf-bootstrap的BPF程序对于源文件有一定的命名规则, 用于生成内核态字节码的bpf文件以.bpf.c结尾,用户态加载字节码的文件以.c结尾,且这两个文件的 前缀必须相同。 基于libbpf-bootstrap的BPF程序在编译时会先将*.bpf.c文件编译为 对应的.o文件,然后根据此文件生成skeleton文件,即*.skel.h,这个文件会包含内核态中定义的一些 数据结构,以及用于装载内核态代码的关键函数。在用户态代码include此文件之后调用对应的装载函数即可将 字节码装载到内核中。同样的,libbpf-bootstrap也有非常完备的入门教程,用户可以在 该处 得到详细的入门操作介绍。","breadcrumbs":"介绍 eBPF 的基本概念、常见的开发工具 » libbpf","id":"10","title":"libbpf"},"100":{"body":"通过本篇 eBPF 入门实践教程,我们学习了如何使用 eBPF 和 USDT 动态跟踪和分析 Java 的垃圾回收(GC)事件。我们了解了如何在用户态应用程序中设置 USDT 跟踪点,以及如何编写 eBPF 程序来捕获这些跟踪点的信息,从而更深入地理解和优化 Java GC 的行为和性能。 此外,我们也介绍了一些关于 Java GC、USDT 和 eBPF 的基础知识和实践技巧,这些知识和技巧对于想要在网络和系统性能分析领域深入研究的开发者来说是非常有价值的。 如果您希望学习更多关于 eBPF 的知识和实践,可以访问我们的教程代码仓库 https://github.com/eunomia-bpf/bpf-developer-tutorial 或网站 https://eunomia.dev/zh/tutorials/ 以获取更多示例和完整的教程。","breadcrumbs":"使用 USDT 捕获用户态 Java GC 事件耗时 » 总结","id":"100","title":"总结"},"101":{"body":"eBPF(扩展的伯克利数据包过滤器)是一项强大的网络和性能分析工具,被广泛应用在 Linux 内核上。eBPF 使得开发者能够动态地加载、更新和运行用户定义的代码,而无需重启内核或更改内核源代码。 在本篇教程中,我们将探讨如何使用 eBPF 编写 Memleak 程序,以监控程序的内存泄漏。","breadcrumbs":"编写 eBPF 程序 Memleak 监控内存泄漏 » eBPF 入门实践教程十六:编写 eBPF 程序 Memleak 监控内存泄漏","id":"101","title":"eBPF 入门实践教程十六:编写 eBPF 程序 Memleak 监控内存泄漏"},"102":{"body":"内存泄漏是计算机编程中的一种常见问题,其严重程度不应被低估。内存泄漏发生时,程序会逐渐消耗更多的内存资源,但并未正确释放。随着时间的推移,这种行为会导致系统内存逐渐耗尽,从而显著降低程序及系统的整体性能。 内存泄漏有多种可能的原因。这可能是由于配置错误导致的,例如程序错误地配置了某些资源的动态分配。它也可能是由于软件缺陷或错误的内存管理策略导致的,如在程序执行过程中忘记释放不再需要的内存。此外,如果一个应用程序的内存使用量过大,那么系统性能可能会因页面交换(swapping)而大幅下降,甚至可能导致应用程序被系统强制终止(Linux 的 OOM killer)。","breadcrumbs":"编写 eBPF 程序 Memleak 监控内存泄漏 » 背景及其重要性","id":"102","title":"背景及其重要性"},"103":{"body":"调试内存泄漏问题是一项复杂且挑战性的任务。这涉及到详细检查应用程序的配置、内存分配和释放情况,通常需要应用专门的工具来帮助诊断。例如,有一些工具可以在应用程序启动时将 malloc() 函数调用与特定的检测工具关联起来,如 Valgrind memcheck,这类工具可以模拟 CPU 来检查所有内存访问,但可能会导致应用程序运行速度大大减慢。另一个选择是使用堆分析器,如 libtcmalloc,它相对较快,但仍可能使应用程序运行速度降低五倍以上。此外,还有一些工具,如 gdb,可以获取应用程序的核心转储并进行后处理以分析内存使用情况。然而,这些工具通常在获取核心转储时需要暂停应用程序,或在应用程序终止后才能调用 free() 函数。","breadcrumbs":"编写 eBPF 程序 Memleak 监控内存泄漏 » 调试内存泄漏的挑战","id":"103","title":"调试内存泄漏的挑战"},"104":{"body":"在这种背景下,eBPF 的作用就显得尤为重要。eBPF 提供了一种高效的机制来监控和追踪系统级别的事件,包括内存的分配和释放。通过 eBPF,我们可以跟踪内存分配和释放的请求,并收集每次分配的调用堆栈。然后,我们可以分 析这些信息,找出执行了内存分配但未执行释放操作的调用堆栈,这有助于我们找出导致内存泄漏的源头。这种方式的优点在于,它可以实时地在运行的应用程序中进行,而无需暂停应用程序或进行复杂的前后处理。 memleak eBPF 工具可以跟踪并匹配内存分配和释放的请求,并收集每次分配的调用堆栈。随后,memleak 可以打印一个总结,表明哪些调用堆栈执行了分配,但是并没有随后进行释放。例如,我们运行命令: # ./memleak -p $(pidof allocs)\nAttaching to pid 5193, Ctrl+C to quit.\n[11:16:33] Top 2 stacks with outstanding allocations: 80 bytes in 5 allocations from stack main+0x6d [allocs] __libc_start_main+0xf0 [libc-2.21.so] [11:16:34] Top 2 stacks with outstanding allocations: 160 bytes in 10 allocations from stack main+0x6d [allocs] __libc_start_main+0xf0 [libc-2.21.so] 运行这个命令后,我们可以看到分配但未释放的内存来自于哪些堆栈,并且可以看到这些未释放的内存的大小和数量。 随着时间的推移,很显然,allocs 进程的 main 函数正在泄漏内存,每次泄漏 16 字节。幸运的是,我们不需要检查每个分配,我们得到了一个很好的总结,告诉我们哪个堆栈负责大量的泄漏。","breadcrumbs":"编写 eBPF 程序 Memleak 监控内存泄漏 » eBPF 的作用","id":"104","title":"eBPF 的作用"},"105":{"body":"在基本层面上,memleak 的工作方式类似于在内存分配和释放路径上安装监控设备。它通过在内存分配和释放函数中插入 eBPF 程序来达到这个目标。这意味着,当这些函数被调用时,memleak 就会记录一些重要信息,如调用者的进程 ID(PID)、分配的内存地址以及分配的内存大小等。当释放内存的函数被调用时,memleak 则会在其内部的映射表(map)中删除相应的内存分配记录。这种机制使得 memleak 能够准确地追踪到哪些内存块已被分配但未被释放。 对于用户态的常用内存分配函数,如 malloc 和 calloc 等,memleak 利用了用户态探测(uprobe)技术来实现监控。uprobe 是一种用于用户空间应用程序的动态追踪技术,它可以在运行时不修改二进制文件的情况下在任意位置设置断点,从而实现对特定函数调用的追踪。 对于内核态的内存分配函数,如 kmalloc 等,memleak 则选择使用了 tracepoint 来实现监控。Tracepoint 是一种在 Linux 内核中提供的动态追踪技术,它可以在内核运行时动态地追踪特定的事件,而无需重新编译内核或加载内核模块。","breadcrumbs":"编写 eBPF 程序 Memleak 监控内存泄漏 » memleak 的实现原理","id":"105","title":"memleak 的实现原理"},"106":{"body":"","breadcrumbs":"编写 eBPF 程序 Memleak 监控内存泄漏 » 内核态 eBPF 程序实现","id":"106","title":"内核态 eBPF 程序实现"},"107":{"body":"memleak 的内核态 eBPF 程序包含一些用于跟踪内存分配和释放的关键函数。在我们深入了解这些函数之前,让我们首先观察 memleak 所定义的一些数据结构,这些结构在其内核态和用户态程序中均有使用。 #ifndef __MEMLEAK_H\n#define __MEMLEAK_H #define ALLOCS_MAX_ENTRIES 1000000\n#define COMBINED_ALLOCS_MAX_ENTRIES 10240 struct alloc_info { __u64 size; // 分配的内存大小 __u64 timestamp_ns; // 分配时的时间戳,单位为纳秒 int stack_id; // 分配时的调用堆栈ID\n}; union combined_alloc_info { struct { __u64 total_size : 40; // 所有未释放分配的总大小 __u64 number_of_allocs : 24; // 所有未释放分配的总次数 }; __u64 bits; // 结构的位图表示\n}; #endif /* __MEMLEAK_H */ 这里定义了两个主要的数据结构:alloc_info 和 combined_alloc_info。 alloc_info 结构体包含了一个内存分配的基本信息,包括分配的内存大小 size、分配发生时的时间戳 timestamp_ns,以及触发分配的调用堆栈 ID stack_id。 combined_alloc_info 是一个联合体(union),它包含一个嵌入的结构体和一个 __u64 类型的位图表示 bits。嵌入的结构体有两个成员:total_size 和 number_of_allocs,分别代表所有未释放分配的总大小和总次数。其中 40 和 24 分别表示 total_size 和 number_of_allocs这两个成员变量所占用的位数,用来限制其大小。通过这样的位数限制,可以节省combined_alloc_info结构的存储空间。同时,由于total_size和number_of_allocs在存储时是共用一个unsigned long long类型的变量bits,因此可以通过在成员变量bits上进行位运算来访问和修改total_size和number_of_allocs,从而避免了在程序中定义额外的变量和函数的复杂性。 接下来,memleak 定义了一系列用于保存内存分配信息和分析结果的 eBPF 映射(maps)。这些映射都以 SEC(\".maps\") 的形式定义,表示它们属于 eBPF 程序的映射部分。 const volatile size_t min_size = 0;\nconst volatile size_t max_size = -1;\nconst volatile size_t page_size = 4096;\nconst volatile __u64 sample_rate = 1;\nconst volatile bool trace_all = false;\nconst volatile __u64 stack_flags = 0;\nconst volatile bool wa_missing_free = false; struct { __uint(type, BPF_MAP_TYPE_HASH); __type(key, pid_t); __type(value, u64); __uint(max_entries, 10240);\n} sizes SEC(\".maps\"); struct { __uint(type, BPF_MAP_TYPE_HASH); __type(key, u64); /* address */ __type(value, struct alloc_info); __uint(max_entries, ALLOCS_MAX_ENTRIES);\n} allocs SEC(\".maps\"); struct { __uint(type, BPF_MAP_TYPE_HASH); __type(key, u64); /* stack id */ __type(value, union combined_alloc_info); __uint(max_entries, COMBINED_ALLOCS_MAX_ENTRIES);\n} combined_allocs SEC(\".maps\"); struct { __uint(type, BPF_MAP_TYPE_HASH); __type(key, u64); __type(value, u64); __uint(max_entries, 10240);\n} memptrs SEC(\".maps\"); struct { __uint(type, BPF_MAP_TYPE_STACK_TRACE); __type(key, u32);\n} stack_traces SEC(\".maps\"); static union combined_alloc_info initial_cinfo; 这段代码首先定义了一些可配置的参数,如 min_size, max_size, page_size, sample_rate, trace_all, stack_flags 和 wa_missing_free,分别表示最小分配大小、最大分配大小、页面大小、采样率、是否追踪所有分配、堆栈标志和是否工作在缺失释放(missing free)模式。 接着定义了五个映射: sizes:这是一个哈希类型的映射,键为进程 ID,值为 u64 类型,存储每个进程的分配大小。 allocs:这也是一个哈希类型的映射,键为分配的地址,值为 alloc_info 结构体,存储每个内存分配的详细信息。 combined_allocs:这是另一个哈希类型的映射,键为堆栈 ID,值为 combined_alloc_info 联合体,存储所有未释放分配的总大小和总次数。 memptrs:这也是一个哈希类型的映射,键和值都为 u64 类型,用于在用户空间和内核空间之间传递内存指针。 stack_traces:这是一个堆栈追踪类型的映射,键为 u32 类型,用于存储堆栈 ID。 以用户态的内存分配追踪部分为例,主要是挂钩内存相关的函数调用,如 malloc, free, calloc, realloc, mmap 和 munmap,以便在调用这些函数时进行数据记录。在用户态,memleak 主要使用了 uprobes 技术进行挂载。 每个函数调用被分为 \"enter\" 和 \"exit\" 两部分。\"enter\" 部分记录的是函数调用的参数,如分配的大小或者释放的地址。\"exit\" 部分则主要用于获取函数的返回值,如分配得到的内存地址。 这里,gen_alloc_enter, gen_alloc_exit, gen_free_enter 是实现记录行为的函数,他们分别用于记录分配开始、分配结束和释放开始的相关信息。 函数原型示例如下: SEC(\"uprobe\")\nint BPF_KPROBE(malloc_enter, size_t size)\n{ // 记录分配开始的相关信息 return gen_alloc_enter(size);\n} SEC(\"uretprobe\")\nint BPF_KRETPROBE(malloc_exit)\n{ // 记录分配结束的相关信息 return gen_alloc_exit(ctx);\n} SEC(\"uprobe\")\nint BPF_KPROBE(free_enter, void *address)\n{ // 记录释放开始的相关信息 return gen_free_enter(address);\n} 其中,malloc_enter 和 free_enter 是分别挂载在 malloc 和 free 函数入口处的探针(probes),用于在函数调用时进行数据记录。而 malloc_exit 则是挂载在 malloc 函数的返回处的探针,用于记录函数的返回值。 这些函数使用了 BPF_KPROBE 和 BPF_KRETPROBE 这两个宏来声明,这两个宏分别用于声明 kprobe(内核探针)和 kretprobe(内核返回探针)。具体来说,kprobe 用于在函数调用时触发,而 kretprobe 则是在函数返回时触发。 gen_alloc_enter 函数是在内存分配请求的开始时被调用的。这个函数主要负责在调用分配内存的函数时收集一些基本的信息。下面我们将深入探讨这个函数的实现。 static int gen_alloc_enter(size_t size)\n{ if (size < min_size || size > max_size) return 0; if (sample_rate > 1) { if (bpf_ktime_get_ns() % sample_rate != 0) return 0; } const pid_t pid = bpf_get_current_pid_tgid() >> 32; bpf_map_update_elem(&sizes, &pid, &size, BPF_ANY); if (trace_all) bpf_printk(\"alloc entered, size = %lu\\n\", size); return 0;\n} SEC(\"uprobe\")\nint BPF_KPROBE(malloc_enter, size_t size)\n{ return gen_alloc_enter(size);\n} 首先,gen_alloc_enter 函数接收一个 size 参数,这个参数表示请求分配的内存的大小。如果这个值不在 min_size 和 max_size 之间,函数将直接返回,不再进行后续的操作。这样可以使工具专注于追踪特定范围的内存分配请求,过滤掉不感兴趣的分配请求。 接下来,函数检查采样率 sample_rate。如果 sample_rate 大于1,意味着我们不需要追踪所有的内存分配请求,而是周期性地追踪。这里使用 bpf_ktime_get_ns 获取当前的时间戳,然后通过取模运算来决定是否需要追踪当前的内存分配请求。这是一种常见的采样技术,用于降低性能开销,同时还能够提供一个代表性的样本用于分析。 之后,函数使用 bpf_get_current_pid_tgid 函数获取当前进程的 PID。注意这里的 PID 实际上是进程和线程的组合 ID,我们通过右移 32 位来获取真正的进程 ID。 函数接下来更新 sizes 这个 map,这个 map 以进程 ID 为键,以请求的内存分配大小为值。BPF_ANY 表示如果 key 已存在,那么更新 value,否则就新建一个条目。 最后,如果启用了 trace_all 标志,函数将打印一条信息,说明发生了内存分配。 BPF_KPROBE 宏用于 最后定义了 BPF_KPROBE(malloc_enter, size_t size),它会在 malloc 函数被调用时被 BPF uprobe 拦截执行,并通过 gen_alloc_enter 来记录内存分配大小。 我们刚刚分析了内存分配的入口函数 gen_alloc_enter,现在我们来关注这个过程的退出部分。具体来说,我们将讨论 gen_alloc_exit2 函数以及如何从内存分配调用中获取返回的内存地址。 static int gen_alloc_exit2(void *ctx, u64 address)\n{ const pid_t pid = bpf_get_current_pid_tgid() >> 32; struct alloc_info info; const u64* size = bpf_map_lookup_elem(&sizes, &pid); if (!size) return 0; // missed alloc entry __builtin_memset(&info, 0, sizeof(info)); info.size = *size; bpf_map_delete_elem(&sizes, &pid); if (address != 0) { info.timestamp_ns = bpf_ktime_get_ns(); info.stack_id = bpf_get_stackid(ctx, &stack_traces, stack_flags); bpf_map_update_elem(&allocs, &address, &info, BPF_ANY); update_statistics_add(info.stack_id, info.size); } if (trace_all) { bpf_printk(\"alloc exited, size = %lu, result = %lx\\n\", info.size, address); } return 0;\n}\nstatic int gen_alloc_exit(struct pt_regs *ctx)\n{ return gen_alloc_exit2(ctx, PT_REGS_RC(ctx));\n} SEC(\"uretprobe\")\nint BPF_KRETPROBE(malloc_exit)\n{ return gen_alloc_exit(ctx);\n} gen_alloc_exit2 函数在内存分配操作完成时被调用,这个函数接收两个参数,一个是上下文 ctx,另一个是内存分配函数返回的内存地址 address。 首先,它获取当前线程的 PID,然后使用这个 PID 作为键在 sizes 这个 map 中查找对应的内存分配大小。如果没有找到(也就是说,没有对应的内存分配操作的入口),函数就会直接返回。 接着,函数清除 info 结构体的内容,并设置它的 size 字段为之前在 map 中找到的内存分配大小。并从 sizes 这个 map 中删除相应的元素,因为此时内存分配操作已经完成,不再需要这个信息。 接下来,如果 address 不为 0(也就是说,内存分配操作成功了),函数就会进一步收集一些额外的信息。首先,它获取当前的时间戳作为内存分配完成的时间,并获取当前的堆栈跟踪。这些信息都会被储存在 info 结构体中,并随后更新到 allocs 这个 map 中。 最后,函数调用 update_statistics_add 更新统计数据,如果启用了所有内存分配操作的跟踪,函数还会打印一些关于内存分配操作的信息。 请注意,gen_alloc_exit 函数是 gen_alloc_exit2 的一个包装,它将 PT_REGS_RC(ctx) 作为 address 参数传递给 gen_alloc_exit2。在我们的讨论中,我们刚刚提到在gen_alloc_exit2函数中,调用了update_statistics_add` 函数以更新内存分配的统计数据。下面我们详细看一下这个函数的具体实现。 static void update_statistics_add(u64 stack_id, u64 sz)\n{ union combined_alloc_info *existing_cinfo; existing_cinfo = bpf_map_lookup_or_try_init(&combined_allocs, &stack_id, &initial_cinfo); if (!existing_cinfo) return; const union combined_alloc_info incremental_cinfo = { .total_size = sz, .number_of_allocs = 1 }; __sync_fetch_and_add(&existing_cinfo->bits, incremental_cinfo.bits);\n} update_statistics_add 函数接收两个参数:当前的堆栈 ID stack_id 以及内存分配的大小 sz。这两个参数都在内存分配事件中收集到,并且用于更新内存分配的统计数据。 首先,函数尝试在 combined_allocs 这个 map 中查找键值为当前堆栈 ID 的元素,如果找不到,就用 initial_cinfo(这是一个默认的 combined_alloc_info 结构体,所有字段都为零)来初始化新的元素。 接着,函数创建一个 incremental_cinfo,并设置它的 total_size 为当前内存分配的大小,设置 number_of_allocs 为 1。这是因为每次调用 update_statistics_add 函数都表示有一个新的内存分配事件发生,而这个事件的内存分配大小就是 sz。 最后,函数使用 __sync_fetch_and_add 函数原子地将 incremental_cinfo 的值加到 existing_cinfo 中。请注意这个步骤是线程安全的,即使有多个线程并发地调用 update_statistics_add 函数,每个内存分配事件也能正确地记录到统计数据中。 总的来说,update_statistics_add 函数实现了内存分配统计的更新逻辑,通过维护每个堆栈 ID 的内存分配总量和次数,我们可以深入了解到程序的内存分配行为。 在我们对内存分配的统计跟踪过程中,我们不仅要统计内存的分配,还要考虑内存的释放。在上述代码中,我们定义了一个名为 update_statistics_del 的函数,其作用是在内存释放时更新统计信息。而 gen_free_enter 函数则是在进程调用 free 函数时被执行。 static void update_statistics_del(u64 stack_id, u64 sz)\n{ union combined_alloc_info *existing_cinfo; existing_cinfo = bpf_map_lookup_elem(&combined_allocs, &stack_id); if (!existing_cinfo) { bpf_printk(\"failed to lookup combined allocs\\n\"); return; } const union combined_alloc_info decremental_cinfo = { .total_size = sz, .number_of_allocs = 1 }; __sync_fetch_and_sub(&existing_cinfo->bits, decremental_cinfo.bits);\n} update_statistics_del 函数的参数为堆栈 ID 和要释放的内存块大小。函数首先在 combined_allocs 这个 map 中使用当前的堆栈 ID 作为键来查找相应的 combined_alloc_info 结构体。如果找不到,就输出错误信息,然后函数返回。如果找到了,就会构造一个名为 decremental_cinfo 的 combined_alloc_info 结构体,设置它的 total_size 为要释放的内存大小,设置 number_of_allocs 为 1。然后使用 __sync_fetch_and_sub 函数原子地从 existing_cinfo 中减去 decremental_cinfo 的值。请注意,这里的 number_of_allocs 是负数,表示减少了一个内存分配。 static int gen_free_enter(const void *address)\n{ const u64 addr = (u64)address; const struct alloc_info *info = bpf_map_lookup_elem(&allocs, &addr); if (!info) return 0; bpf_map_delete_elem(&allocs, &addr); update_statistics_del(info->stack_id, info->size); if (trace_all) { bpf_printk(\"free entered, address = %lx, size = %lu\\n\", address, info->size); } return 0;\n} SEC(\"uprobe\")\nint BPF_KPROBE(free_enter, void *address)\n{ return gen_free_enter(address);\n} 接下来看 gen_free_enter 函数。它接收一个地址作为参数,这个地址是内存分配的结果,也就是将要释放的内存的起始地址。函数首先在 allocs 这个 map 中使用这个地址作为键来查找对应的 alloc_info 结构体。如果找不到,那么就直接返回,因为这意味着这个地址并没有被分配过。如果找到了,那么就删除这个元素,并且调用 update_statistics_del 函数来更新统计数据。最后,如果启用了全局追踪,那么还会输出一条信息,包括这个地址以及它的大小。 在我们追踪和统计内存分配的同时,我们也需要对内核态的内存分配和释放进行追踪。在Linux内核中,kmem_cache_alloc函数和kfree函数分别用于内核态的内存分配和释放。 SEC(\"tracepoint/kmem/kfree\")\nint memleak__kfree(void *ctx)\n{ const void *ptr; if (has_kfree()) { struct trace_event_raw_kfree___x *args = ctx; ptr = BPF_CORE_READ(args, ptr); } else { struct trace_event_raw_kmem_free___x *args = ctx; ptr = BPF_CORE_READ(args, ptr); } return gen_free_enter(ptr);\n} 上述代码片段定义了一个函数memleak__kfree,这是一个bpf程序,会在内核调用kfree函数时执行。首先,该函数检查是否存在kfree函数。如果存在,则会读取传递给kfree函数的参数(即要释放的内存块的地址),并保存到变量ptr中;否则,会读取传递给kmem_free函数的参数(即要释放的内存块的地址),并保存到变量ptr中。接着,该函数会调用之前定义的gen_free_enter函数来处理该内存块的释放。 SEC(\"tracepoint/kmem/kmem_cache_alloc\")\nint memleak__kmem_cache_alloc(struct trace_event_raw_kmem_alloc *ctx)\n{ if (wa_missing_free) gen_free_enter(ctx->ptr); gen_alloc_enter(ctx->bytes_alloc); return gen_alloc_exit2(ctx, (u64)(ctx->ptr));\n} 这段代码定义了一个函数 memleak__kmem_cache_alloc,这也是一个bpf程序,会在内核调用 kmem_cache_alloc 函数时执行。如果标记 wa_missing_free 被设置,则调用 gen_free_enter 函数处理可能遗漏的释放操作。然后,该函数会调用 gen_alloc_enter 函数来处理内存分配,最后调用gen_alloc_exit2函数记录分配的结果。 这两个 bpf 程序都使用了 SEC 宏定义了对应的 tracepoint,以便在相应的内核函数被调用时得到执行。在Linux内核中,tracepoint 是一种可以在内核中插入的静态钩子,可以用来收集运行时的内核信息,它在调试和性能分析中非常有用。 在理解这些代码的过程中,要注意 BPF_CORE_READ 宏的使用。这个宏用于在 bpf 程序中读取内核数据。在 bpf 程序中,我们不能直接访问内核内存,而需要使用这样的宏来安全地读取数据。","breadcrumbs":"编写 eBPF 程序 Memleak 监控内存泄漏 » memleak 内核态 eBPF 程序实现","id":"107","title":"memleak 内核态 eBPF 程序实现"},"108":{"body":"在理解 BPF 内核部分之后,我们转到用户空间程序。用户空间程序与BPF内核程序紧密配合,它负责将BPF程序加载到内核,设置和管理BPF map,以及处理从BPF程序收集到的数据。用户态程序较长,我们这里可以简要参考一下它的挂载点。 int attach_uprobes(struct memleak_bpf *skel)\n{ ATTACH_UPROBE_CHECKED(skel, malloc, malloc_enter); ATTACH_URETPROBE_CHECKED(skel, malloc, malloc_exit); ATTACH_UPROBE_CHECKED(skel, calloc, calloc_enter); ATTACH_URETPROBE_CHECKED(skel, calloc, calloc_exit); ATTACH_UPROBE_CHECKED(skel, realloc, realloc_enter); ATTACH_URETPROBE_CHECKED(skel, realloc, realloc_exit); ATTACH_UPROBE_CHECKED(skel, mmap, mmap_enter); ATTACH_URETPROBE_CHECKED(skel, mmap, mmap_exit); ATTACH_UPROBE_CHECKED(skel, posix_memalign, posix_memalign_enter); ATTACH_URETPROBE_CHECKED(skel, posix_memalign, posix_memalign_exit); ATTACH_UPROBE_CHECKED(skel, memalign, memalign_enter); ATTACH_URETPROBE_CHECKED(skel, memalign, memalign_exit); ATTACH_UPROBE_CHECKED(skel, free, free_enter); ATTACH_UPROBE_CHECKED(skel, munmap, munmap_enter); // the following probes are intentinally allowed to fail attachment // deprecated in libc.so bionic ATTACH_UPROBE(skel, valloc, valloc_enter); ATTACH_URETPROBE(skel, valloc, valloc_exit); // deprecated in libc.so bionic ATTACH_UPROBE(skel, pvalloc, pvalloc_enter); ATTACH_URETPROBE(skel, pvalloc, pvalloc_exit); // added in C11 ATTACH_UPROBE(skel, aligned_alloc, aligned_alloc_enter); ATTACH_URETPROBE(skel, aligned_alloc, aligned_alloc_exit); return 0;\n} 在这段代码中,我们看到一个名为attach_uprobes的函数,该函数负责将uprobes(用户空间探测点)挂载到内存分配和释放函数上。在Linux中,uprobes是一种内核机制,可以在用户空间程序中的任意位置设置断点,这使得我们可以非常精确地观察和控制用户空间程序的行为。 这里,每个内存相关的函数都通过两个uprobes进行跟踪:一个在函数入口(enter),一个在函数退出(exit)。因此,每当这些函数被调用或返回时,都会触发一个uprobes事件,进而触发相应的BPF程序。 在具体的实现中,我们使用了ATTACH_UPROBE和ATTACH_URETPROBE两个宏来附加uprobes和uretprobes(函数返回探测点)。每个宏都需要三个参数:BPF程序的骨架(skel),要监视的函数名,以及要触发的BPF程序的名称。 这些挂载点包括常见的内存分配函数,如malloc、calloc、realloc、mmap、posix_memalign、memalign、free等,以及对应的退出点。另外,我们也观察一些可能的分配函数,如valloc、pvalloc、aligned_alloc等,尽管它们可能不总是存在。 这些挂载点的目标是捕获所有可能的内存分配和释放事件,从而使我们的内存泄露检测工具能够获取到尽可能全面的数据。这种方法可以让我们不仅能跟踪到内存分配和释放,还能得到它们发生的上下文信息,例如调用栈和调用次数,从而帮助我们定位和修复内存泄露问题。 注意,一些内存分配函数可能并不存在或已弃用,比如valloc、pvalloc等,因此它们的附加可能会失败。在这种情况下,我们允许附加失败,并不会阻止程序的执行。这是因为我们更关注的是主流和常用的内存分配函数,而这些已经被弃用的函数往往在实际应用中较少使用。 完整的源代码: https://github.com/eunomia-bpf/bpf-developer-tutorial/tree/main/src/16-memleak 参考: https://github.com/iovisor/bcc/blob/master/libbpf-tools/memleak.c","breadcrumbs":"编写 eBPF 程序 Memleak 监控内存泄漏 » 用户态程序","id":"108","title":"用户态程序"},"109":{"body":"$ make\n$ sudo ./memleak using default object: libc.so.6\nusing page size: 4096\ntracing kernel: true\nTracing outstanding memory allocs... Hit Ctrl-C to end\n[17:17:27] Top 10 stacks with outstanding allocations:\n1236992 bytes in 302 allocations from stack 0 [] 1 [] 2 [] 3 [] 4 [] 5 [] 6 [] \n...","breadcrumbs":"编写 eBPF 程序 Memleak 监控内存泄漏 » 编译运行","id":"109","title":"编译运行"},"11":{"body":"开发、构建和分发 eBPF 一直以来都是一个高门槛的工作,使用 BCC、bpftrace 等工具开发效率高、可移植性好,但是分发部署时需要安装 LLVM、Clang等编译环境,每次运行的时候执行本地或远程编译过程,资源消耗较大;使用原生的 CO-RE libbpf时又需要编写不少用户态加载代码来帮助 eBPF 程序正确加载和从内核中获取上报的信息,同时对于 eBPF 程序的分发、管理也没有很好地解决方案。 eunomia-bpf 是一个开源的 eBPF 动态加载运行时和开发工具链,是为了简化 eBPF 程序的开发、构建、分发、运行而设计的,基于 libbpf 的 CO-RE 轻量级开发框架。 使用 eunomia-bpf ,可以: 在编写 eBPF 程序或工具时只编写内核态代码,自动获取内核态导出信息,并作为模块动态加载; 使用 WASM 进行用户态交互程序的开发,在 WASM 虚拟机内部控制整个 eBPF 程序的加载和执行,以及处理相关数据; eunomia-bpf 可以将预编译的 eBPF 程序打包为通用的 JSON 或 WASM 模块,跨架构和内核版本进行分发,无需重新编译即可动态加载运行。 eunomia-bpf 由一个编译工具链和一个运行时库组成, 对比传统的 BCC、原生 libbpf 等框架,大幅简化了 eBPF 程序的开发流程,在大多数时候只需编写内核态代码,即可轻松构建、打包、发布完整的 eBPF 应用,同时内核态 eBPF 代码保证和主流的 libbpf, libbpfgo, libbpf-rs 等开发框架的 100% 兼容性。需要编写用户态代码的时候,也可以借助 Webassembly 实现通过多种语言进行用户态开发。和 bpftrace 等脚本工具相比, eunomia-bpf 保留了类似的便捷性, 同时不仅局限于 trace 方面, 可以用于更多的场景, 如网络、安全等等。 eunomia-bpf 项目 Github 地址: https://github.com/eunomia-bpf/eunomia-bpf gitee 镜像: https://gitee.com/anolis/eunomia","breadcrumbs":"介绍 eBPF 的基本概念、常见的开发工具 » eunomia-bpf","id":"11","title":"eunomia-bpf"},"110":{"body":"通过本篇 eBPF 入门实践教程,您已经学习了如何编写 Memleak eBPF 监控程序,以实时监控程序的内存泄漏。您已经了解了 eBPF 在内存监控方面的应用,学会了使用 BPF API 编写 eBPF 程序,创建和使用 eBPF maps,并且明白了如何用 eBPF 工具监测和分析内存泄漏问题。我们展示了一个详细的例子,帮助您理解 eBPF 代码的运行流程和原理。 您可以访问我们的教程代码仓库 https://github.com/eunomia-bpf/bpf-developer-tutorial 或网站 https://eunomia.dev/zh/tutorials/ 以获取更多示例和完整的教程。 接下来的教程将进一步探讨 eBPF 的高级特性,我们会继续分享更多有关 eBPF 开发实践的内容。希望这些知识和技巧能帮助您更好地了解和使用 eBPF,以解决实际工作中遇到的问题。","breadcrumbs":"编写 eBPF 程序 Memleak 监控内存泄漏 » 总结","id":"110","title":"总结"},"111":{"body":"eBPF(扩展的伯克利数据包过滤器)是 Linux 内核中的一种新技术,允许用户在内核空间中执行自定义程序,而无需更改内核代码。这为系统管理员和开发者提供了强大的工具,可以深入了解和监控系统的行为,从而进行优化。 在本篇教程中,我们将探索如何使用 eBPF 编写程序来统计随机和顺序的磁盘 I/O。磁盘 I/O 是计算机性能的关键指标之一,特别是在数据密集型应用中。","breadcrumbs":"编写 eBPF 程序 Biopattern 统计随机/顺序磁盘 I/O » eBPF 入门实践教程十七:编写 eBPF 程序统计随机/顺序磁盘 I/O","id":"111","title":"eBPF 入门实践教程十七:编写 eBPF 程序统计随机/顺序磁盘 I/O"},"112":{"body":"随着技术的进步和数据量的爆炸性增长,磁盘 I/O 成为了系统性能的关键瓶颈。应用程序的性能很大程度上取决于其如何与存储层进行交互。因此,深入了解和优化磁盘 I/O,特别是随机和顺序的 I/O,变得尤为重要。 随机 I/O :随机 I/O 发生在应用程序从磁盘的非连续位置读取或写入数据时。这种 I/O 模式的主要特点是磁盘头需要频繁地在不同的位置之间移动,导致其通常比顺序 I/O 的速度慢。典型的产生随机 I/O 的场景包括数据库查询、文件系统的元数据操作以及虚拟化环境中的并发任务。 顺序 I/O :与随机 I/O 相反,顺序 I/O 是当应用程序连续地读取或写入磁盘上的数据块。这种 I/O 模式的优势在于磁盘头可以在一个方向上连续移动,从而大大提高了数据的读写速度。视频播放、大型文件的下载或上传以及连续的日志记录都是产生顺序 I/O 的典型应用。 为了实现存储性能的最优化,了解随机和顺序的磁盘 I/O 是至关重要的。例如,随机 I/O 敏感的应用程序在 SSD 上的性能通常远超于传统硬盘,因为 SSD 在处理随机 I/O 时几乎没有寻址延迟。相反,对于大量顺序 I/O 的应用,如何最大化磁盘的连续读写速度则更为关键。 在本教程的后续部分,我们将详细探讨如何使用 eBPF 工具来实时监控和统计这两种类型的磁盘 I/O。这不仅可以帮助我们更好地理解系统的 I/O 行为,还可以为进一步的性能优化提供有力的数据支持。","breadcrumbs":"编写 eBPF 程序 Biopattern 统计随机/顺序磁盘 I/O » 随机/顺序磁盘 I/O","id":"112","title":"随机/顺序磁盘 I/O"},"113":{"body":"Biopattern 可以统计随机/顺序磁盘I/O次数的比例。 首先,确保你已经正确安装了 libbpf 和相关的工具集,可以在这里找到对应的源代码: bpf-developer-tutorial 导航到 biopattern 的源代码目录,并使用 make 命令进行编译: cd ~/bpf-developer-tutorial/src/17-biopattern\nmake 编译成功后,你应该可以在当前目录下看到 biopattern 的可执行文件。基本的运行命令如下: sudo ./biopattern [interval] [count] 例如,要每秒打印一次输出,并持续10秒,你可以运行: $ sudo ./biopattern 1 10\nTracing block device I/O requested seeks... Hit Ctrl-C to end.\nDISK %RND %SEQ COUNT KBYTES\nsr0 0 100 3 0\nsr1 0 100 8 0\nsda 0 100 1 4\nsda 100 0 26 136\nsda 0 100 1 4 输出列的含义如下: DISK:被追踪的磁盘名称。 %RND:随机 I/O 的百分比。 %SEQ:顺序 I/O 的百分比。 COUNT:在指定的时间间隔内的 I/O 请求次数。 KBYTES:在指定的时间间隔内读写的数据量(以 KB 为单位)。 从上述输出中,我们可以得出以下结论: sr0 和 sr1 设备在观测期间主要进行了顺序 I/O,但数据量很小。 sda 设备在某些时间段内只进行了随机 I/O,而在其他时间段内只进行了顺序 I/O。 这些信息可以帮助我们了解系统的 I/O 模式,从而进行针对性的优化。","breadcrumbs":"编写 eBPF 程序 Biopattern 统计随机/顺序磁盘 I/O » Biopattern","id":"113","title":"Biopattern"},"114":{"body":"首先,让我们看一下 biopattern 的核心 eBPF 内核态代码: #include \n#include \n#include \n#include \"biopattern.h\"\n#include \"maps.bpf.h\"\n#include \"core_fixes.bpf.h\" const volatile bool filter_dev = false;\nconst volatile __u32 targ_dev = 0; struct { __uint(type, BPF_MAP_TYPE_HASH); __uint(max_entries, 64); __type(key, u32); __type(value, struct counter);\n} counters SEC(\".maps\"); SEC(\"tracepoint/block/block_rq_complete\")\nint handle__block_rq_complete(void *args)\n{ struct counter *counterp, zero = {}; sector_t sector; u32 nr_sector; u32 dev; if (has_block_rq_completion()) { struct trace_event_raw_block_rq_completion___x *ctx = args; sector = BPF_CORE_READ(ctx, sector); nr_sector = BPF_CORE_READ(ctx, nr_sector); dev = BPF_CORE_READ(ctx, dev); } else { struct trace_event_raw_block_rq_complete___x *ctx = args; sector = BPF_CORE_READ(ctx, sector); nr_sector = BPF_CORE_READ(ctx, nr_sector); dev = BPF_CORE_READ(ctx, dev); } if (filter_dev && targ_dev != dev) return 0; counterp = bpf_map_lookup_or_try_init(&counters, &dev, &zero); if (!counterp) return 0; if (counterp->last_sector) { if (counterp->last_sector == sector) __sync_fetch_and_add(&counterp->sequential, 1); else __sync_fetch_and_add(&counterp->random, 1); __sync_fetch_and_add(&counterp->bytes, nr_sector * 512); } counterp->last_sector = sector + nr_sector; return 0;\n} char LICENSE[] SEC(\"license\") = \"GPL\"; 全局变量定义 const volatile bool filter_dev = false; const volatile __u32 targ_dev = 0; 这两个全局变量用于设备过滤。filter_dev 决定是否启用设备过滤,而 targ_dev 是我们想要追踪的目标设备的标识符。 BPF map 定义: struct { __uint(type, BPF_MAP_TYPE_HASH); __uint(max_entries, 64); __type(key, u32); __type(value, struct counter); } counters SEC(\".maps\"); 这部分代码定义了一个 BPF map,类型为哈希表。该映射的键是设备的标识符,而值是一个 counter 结构体,用于存储设备的 I/O 统计信息。 追踪点函数: SEC(\"tracepoint/block/block_rq_complete\") int handle__block_rq_complete(void *args) { struct counter *counterp, zero = {}; sector_t sector; u32 nr_sector; u32 dev; if (has_block_rq_completion()) { struct trace_event_raw_block_rq_completion___x *ctx = args; sector = BPF_CORE_READ(ctx, sector); nr_sector = BPF_CORE_READ(ctx, nr_sector); dev = BPF_CORE_READ(ctx, dev); } else { struct trace_event_raw_block_rq_complete___x *ctx = args; sector = BPF_CORE_READ(ctx, sector); nr_sector = BPF_CORE_READ(ctx, nr_sector); dev = BPF_CORE_READ(ctx, dev); } if (filter_dev && targ_dev != dev) return 0; counterp = bpf_map_lookup_or_try_init(&counters, &dev, &zero); if (!counterp) return 0; if (counterp->last_sector) { if (counterp->last_sector == sector) __sync_fetch_and_add(&counterp->sequential, 1); else __sync_fetch_and_add(&counterp->random, 1); __sync_fetch_and_add(&counterp->bytes, nr_sector * 512); } counterp->last_sector = sector + nr_sector; return 0; } 在 Linux 中,每次块设备的 I/O 请求完成时,都会触发一个名为 block_rq_complete 的追踪点。这为我们提供了一个机会,通过 eBPF 来捕获这些事件,并进一步分析 I/O 的模式。 主要逻辑分析: 提取 I/O 请求信息 :从传入的参数中获取 I/O 请求的相关信息。这里有两种可能的上下文结构,取决于 has_block_rq_completion 的返回值。这是因为不同版本的 Linux 内核可能会有不同的追踪点定义。无论哪种情况,我们都从上下文中提取出扇区号 (sector)、扇区数量 (nr_sector) 和设备标识符 (dev)。 设备过滤 :如果启用了设备过滤 (filter_dev 为 true),并且当前设备不是目标设备 (targ_dev),则直接返回。这允许用户只追踪特定的设备,而不是所有设备。 统计信息更新 : - 查找或初始化统计信息 :使用 bpf_map_lookup_or_try_init 函数查找或初始化与当前设备相关的统计信息。如果映射中没有当前设备的统计信息,它会使用 zero 结构体进行初始化。 - 判断 I/O 模式 :根据当前 I/O 请求与上一个 I/O 请求的扇区号,我们可以判断当前请求是随机的还是顺序的。如果两次请求的扇区号相同,那么它是顺序的;否则,它是随机的。然后,我们使用 __sync_fetch_and_add 函数更新相应的统计信息。这是一个原子操作,确保在并发环境中数据的一致性。 - 更新数据量 :我们还更新了该设备的总数据量,这是通过将扇区数量 (nr_sector) 乘以 512(每个扇区的字节数)来实现的。 - 更新最后一个 I/O 请求的扇区号 :为了下一次的比较,我们更新了 last_sector 的值。 在 Linux 内核的某些版本中,由于引入了一个新的追踪点 block_rq_error,追踪点的命名和结构发生了变化。这意味着,原先的 block_rq_complete 追踪点的结构名称从 trace_event_raw_block_rq_complete 更改为 trace_event_raw_block_rq_completion。这种变化可能会导致 eBPF 程序在不同版本的内核上出现兼容性问题。 为了解决这个问题,biopattern 工具引入了一种机制来动态检测当前内核使用的是哪种追踪点结构,即 has_block_rq_completion 函数。 定义两种追踪点结构 : struct trace_event_raw_block_rq_complete___x { dev_t dev; sector_t sector; unsigned int nr_sector; } __attribute__((preserve_access_index)); struct trace_event_raw_block_rq_completion___x { dev_t dev; sector_t sector; unsigned int nr_sector; } __attribute__((preserve_access_index)); 这里定义了两种追踪点结构,分别对应于不同版本的内核。每种结构都包含设备标识符 (dev)、扇区号 (sector) 和扇区数量 (nr_sector)。 动态检测追踪点结构 : static __always_inline bool has_block_rq_completion() { if (bpf_core_type_exists(struct trace_event_raw_block_rq_completion___x)) return true; return false; } has_block_rq_completion 函数使用 bpf_core_type_exists 函数来检测当前内核是否存在 trace_event_raw_block_rq_completion___x 结构。如果存在,函数返回 true,表示当前内核使用的是新的追踪点结构;否则,返回 false,表示使用的是旧的结构。在对应的 eBPF 代码中,会根据两种不同的定义分别进行处理,这也是适配不同内核版本之间的变更常见的方案。","breadcrumbs":"编写 eBPF 程序 Biopattern 统计随机/顺序磁盘 I/O » eBPF Biopattern 实现原理","id":"114","title":"eBPF Biopattern 实现原理"},"115":{"body":"biopattern 工具的用户态代码负责从 BPF 映射中读取统计数据,并将其展示给用户。通过这种方式,系统管理员可以实时监控每个设备的 I/O 模式,从而更好地理解和优化系统的 I/O 性能。 主循环: /* main: poll */ while (1) { sleep(env.interval); err = print_map(obj->maps.counters, partitions); if (err) break; if (exiting || --env.times == 0) break; } 这是 biopattern 工具的主循环,它的工作流程如下: 等待 :使用 sleep 函数等待指定的时间间隔 (env.interval)。 打印映射 :调用 print_map 函数打印 BPF 映射中的统计数据。 退出条件 :如果收到退出信号 (exiting 为 true) 或者达到指定的运行次数 (env.times 达到 0),则退出循环。 打印映射函数: static int print_map(struct bpf_map *counters, struct partitions *partitions) { __u32 total, lookup_key = -1, next_key; int err, fd = bpf_map__fd(counters); const struct partition *partition; struct counter counter; struct tm *tm; char ts[32]; time_t t; while (!bpf_map_get_next_key(fd, &lookup_key, &next_key)) { err = bpf_map_lookup_elem(fd, &next_key, &counter); if (err < 0) { fprintf(stderr, \"failed to lookup counters: %d\\n\", err); return -1; } lookup_key = next_key; total = counter.sequential + counter.random; if (!total) continue; if (env.timestamp) { time(&t); tm = localtime(&t); strftime(ts, sizeof(ts), \"%H:%M:%S\", tm); printf(\"%-9s \", ts); } partition = partitions__get_by_dev(partitions, next_key); printf(\"%-7s %5ld %5ld %8d %10lld\\n\", partition ? partition->name : \"Unknown\", counter.random * 100L / total, counter.sequential * 100L / total, total, counter.bytes / 1024); } lookup_key = -1; while (!bpf_map_get_next_key(fd, &lookup_key, &next_key)) { err = bpf_map_delete_elem(fd, &next_key); if (err < 0) { fprintf(stderr, \"failed to cleanup counters: %d\\n\", err); return -1; } lookup_key = next_key; } return 0; } print_map 函数负责从 BPF 映射中读取统计数据,并将其打印到控制台。其主要逻辑如下: 遍历 BPF 映射 :使用 bpf_map_get_next_key 和 bpf_map_lookup_elem 函数遍历 BPF 映射,获取每个设备的统计数据。 计算总数 :计算每个设备的随机和顺序 I/O 的总数。 打印统计数据 :如果启用了时间戳 (env.timestamp 为 true),则首先打印当前时间。接着,打印设备名称、随机 I/O 的百分比、顺序 I/O 的百分比、总 I/O 数量和总数据量(以 KB 为单位)。 清理 BPF 映射 :为了下一次的统计,使用 bpf_map_get_next_key 和 bpf_map_delete_elem 函数清理 BPF 映射中的所有条目。","breadcrumbs":"编写 eBPF 程序 Biopattern 统计随机/顺序磁盘 I/O » 用户态代码","id":"115","title":"用户态代码"},"116":{"body":"在本教程中,我们深入探讨了如何使用 eBPF 工具 biopattern 来实时监控和统计随机和顺序的磁盘 I/O。我们首先了解了随机和顺序磁盘 I/O 的重要性,以及它们对系统性能的影响。接着,我们详细介绍了 biopattern 的工作原理,包括如何定义和使用 BPF maps,如何处理不同版本的 Linux 内核中的追踪点变化,以及如何在 eBPF 程序中捕获和分析磁盘 I/O 事件。 您可以访问我们的教程代码仓库 https://github.com/eunomia-bpf/bpf-developer-tutorial 或网站 https://eunomia.dev/zh/tutorials/ 以获取更多示例和完整的教程。 完整代码: https://github.com/eunomia-bpf/bpf-developer-tutorial/tree/main/src/17-biopattern bcc 工具: https://github.com/iovisor/bcc/blob/master/libbpf-tools/biopattern.c","breadcrumbs":"编写 eBPF 程序 Biopattern 统计随机/顺序磁盘 I/O » 总结","id":"116","title":"总结"},"117":{"body":"可以在这里找到更多关于 eBPF 的信息: https://github.com/zoidbergwill/awesome-ebpf https://ebpf.io/","breadcrumbs":"更多的参考资料 » 更多的参考资料","id":"117","title":"更多的参考资料"},"118":{"body":"eBPF (扩展的伯克利数据包过滤器) 是一项强大的网络和性能分析工具,被广泛应用在 Linux 内核上。eBPF 使得开发者能够动态地加载、更新和运行用户定义的代码,而无需重启内核或更改内核源代码。这个特性使得 eBPF 能够提供极高的灵活性和性能,使其在网络和系统性能分析方面具有广泛的应用。安全方面的 eBPF 应用也是如此,本文将介绍如何使用 eBPF LSM(Linux Security Modules)机制实现一个简单的安全检查程序。","breadcrumbs":"使用 LSM 进行安全检测防御 » eBPF 入门实践教程:使用 LSM 进行安全检测防御","id":"118","title":"eBPF 入门实践教程:使用 LSM 进行安全检测防御"},"119":{"body":"LSM 从 Linux 2.6 开始成为官方内核的一个安全框架,基于此的安全实现包括 SELinux 和 AppArmor 等。在 Linux 5.7 引入 BPF LSM 后,系统开发人员已经能够自由地实现函数粒度的安全检查能力,本文就提供了这样一个案例:限制通过 socket connect 函数对特定 IPv4 地址进行访问的 BPF LSM 程序。(可见其控制精度是很高的)","breadcrumbs":"使用 LSM 进行安全检测防御 » 背景","id":"119","title":"背景"},"12":{"body":"eBPF 介绍: https://ebpf.io/ BPF Compiler Collection (BCC): https://github.com/iovisor/bcc eunomia-bpf: https://github.com/eunomia-bpf/eunomia-bpf 您还可以访问我们的教程代码仓库 https://github.com/eunomia-bpf/bpf-developer-tutorial 或网站 https://eunomia.dev/zh/tutorials/ 以获取更多示例和完整的教程源代码。我们会继续分享更多有关 eBPF 开发实践的内容,帮助您更好地理解和掌握 eBPF 技术。","breadcrumbs":"介绍 eBPF 的基本概念、常见的开发工具 » 参考资料","id":"12","title":"参考资料"},"120":{"body":"LSM(Linux Security Modules)是 Linux 内核中用于支持各种计算机安全模型的框架。LSM 在 Linux 内核安全相关的关键路径上预置了一批 hook 点,从而实现了内核和安全模块的解耦,使不同的安全模块可以自由地在内核中加载/卸载,无需修改原有的内核代码就可以加入安全检查功能。 在过去,使用 LSM 主要通过配置已有的安全模块(如 SELinux 和 AppArmor)或编写自己的内核模块;而在 Linux 5.7 引入 BPF LSM 机制后,一切都变得不同了:现在,开发人员可以通过 eBPF 编写自定义的安全策略,并将其动态加载到内核中的 LSM 挂载点,而无需配置或编写内核模块。 现在 LSM 支持的 hook 点包括但不限于: 对文件的打开、创建、删除和移动等; 文件系统的挂载; 对 task 和 process 的操作; 对 socket 的操作(创建、绑定 socket,发送和接收消息等); 更多 hook 点可以参考 lsm_hooks.h 。","breadcrumbs":"使用 LSM 进行安全检测防御 » LSM 概述","id":"120","title":"LSM 概述"},"121":{"body":"首先,请确认内核版本高于 5.7。接下来,可以通过 $ cat /boot/config-$(uname -r) | grep BPF_LSM\nCONFIG_BPF_LSM=y 判断是否内核是否支持 BPF LSM。上述条件都满足的情况下,可以通过 $ cat /sys/kernel/security/lsm\nndlock,lockdown,yama,integrity,apparmor 查看输出是否包含 bpf 选项,如果输出不包含(像上面的例子),可以通过修改 /etc/default/grub: GRUB_CMDLINE_LINUX=\"lsm=ndlock,lockdown,yama,integrity,apparmor,bpf\" 并通过 update-grub2 命令更新 grub 配置(不同系统的对应命令可能不同),然后重启系统。","breadcrumbs":"使用 LSM 进行安全检测防御 » 确认 BPF LSM 是否可用","id":"121","title":"确认 BPF LSM 是否可用"},"122":{"body":"// lsm-connect.bpf.c\n#include \"vmlinux.h\"\n#include \n#include \n#include char LICENSE[] SEC(\"license\") = \"GPL\"; #define EPERM 1\n#define AF_INET 2 const __u32 blockme = 16843009; // 1.1.1.1 -> int SEC(\"lsm/socket_connect\")\nint BPF_PROG(restrict_connect, struct socket *sock, struct sockaddr *address, int addrlen, int ret)\n{ // Satisfying \"cannot override a denial\" rule if (ret != 0) { return ret; } // Only IPv4 in this example if (address->sa_family != AF_INET) { return 0; } // Cast the address to an IPv4 socket address struct sockaddr_in *addr = (struct sockaddr_in *)address; // Where do you want to go? __u32 dest = addr->sin_addr.s_addr; bpf_printk(\"lsm: found connect to %d\", dest); if (dest == blockme) { bpf_printk(\"lsm: blocking %d\", dest); return -EPERM; } return 0;\n} 这是一段 C 实现的 eBPF 内核侧代码,它会阻碍所有试图通过 socket 对 1.1.1.1 的连接操作,其中: SEC(\"lsm/socket_connect\") 宏指出该程序期望的挂载点; 程序通过 BPF_PROG 宏定义(详情可查看 tools/lib/bpf/bpf_tracing.h ); restrict_connect 是 BPF_PROG 宏要求的程序名; ret 是该挂载点上(潜在的)当前函数之前的 LSM 检查程序的返回值; 整个程序的思路不难理解: 首先,若其他安全检查函数返回值不为 0(不通过),则无需检查,直接返回不通过; 接下来,判断是否为 IPV4 的连接请求,并比较试图连接的地址是否为 1.1.1.1; 若请求地址为 1.1.1.1 则拒绝连接,否则允许连接; 在程序运行期间,所有通过 socket 的连接操作都会被输出到 /sys/kernel/debug/tracing/trace_pipe。","breadcrumbs":"使用 LSM 进行安全检测防御 » 编写 eBPF 程序","id":"122","title":"编写 eBPF 程序"},"123":{"body":"通过容器编译: docker run -it -v `pwd`/:/src/ ghcr.io/eunomia-bpf/ecc-`uname -m`:latest 或是通过 ecc 编译: $ ecc lsm-connect.bpf.c\nCompiling bpf object...\nPacking ebpf object and config into package.json... 并通过 ecli 运行: sudo ecli run package.json 接下来,可以打开另一个 terminal,并尝试访问 1.1.1.1: $ ping 1.1.1.1\nping: connect: Operation not permitted\n$ curl 1.1.1.1\ncurl: (7) Couldn't connect to server\n$ wget 1.1.1.1\n--2023-04-23 08:41:18-- (try: 2) http://1.1.1.1/\nConnecting to 1.1.1.1:80... failed: Operation not permitted.\nRetrying. 同时,我们可以查看 bpf_printk 的输出: $ sudo cat /sys/kernel/debug/tracing/trace_pipe ping-7054 [000] d...1 6313.430872: bpf_trace_printk: lsm: found connect to 16843009 ping-7054 [000] d...1 6313.430874: bpf_trace_printk: lsm: blocking 16843009 curl-7058 [000] d...1 6316.346582: bpf_trace_printk: lsm: found connect to 16843009 curl-7058 [000] d...1 6316.346584: bpf_trace_printk: lsm: blocking 16843009 wget-7061 [000] d...1 6318.800698: bpf_trace_printk: lsm: found connect to 16843009 wget-7061 [000] d...1 6318.800700: bpf_trace_printk: lsm: blocking 16843009 完整源代码: https://github.com/eunomia-bpf/bpf-developer-tutorial/tree/main/src/19-lsm-connect","breadcrumbs":"使用 LSM 进行安全检测防御 » 编译运行","id":"123","title":"编译运行"},"124":{"body":"本文介绍了如何使用 BPF LSM 来限制通过 socket 对特定 IPv4 地址的访问。我们可以通过修改 GRUB 配置文件来开启 LSM 的 BPF 挂载点。在 eBPF 程序中,我们通过 BPF_PROG 宏定义函数,并通过 SEC 宏指定挂载点;在函数实现上,遵循 LSM 安全检查模块中 \"cannot override a denial\" 的原则,并根据 socket 连接请求的目的地址对该请求进行限制。 如果您希望学习更多关于 eBPF 的知识和实践,可以访问我们的教程代码仓库 https://github.com/eunomia-bpf/bpf-developer-tutorial 或网站 https://eunomia.dev/zh/tutorials/ 以获取更多示例和完整的教程。","breadcrumbs":"使用 LSM 进行安全检测防御 » 总结","id":"124","title":"总结"},"125":{"body":"https://github.com/leodido/demo-cloud-native-ebpf-day https://aya-rs.dev/book/programs/lsm/#writing-lsm-bpf-program","breadcrumbs":"使用 LSM 进行安全检测防御 » 参考","id":"125","title":"参考"},"126":{"body":"","breadcrumbs":"使用 eBPF 进行 tc 流量控制 » eBPF 入门实践教程二十:使用 eBPF 进行 tc 流量控制","id":"126","title":"eBPF 入门实践教程二十:使用 eBPF 进行 tc 流量控制"},"127":{"body":"Linux 的流量控制子系统(Traffic Control, tc)在内核中存在了多年,类似于 iptables 和 netfilter 的关系,tc 也包括一个用户态的 tc 程序和内核态的 trafiic control 框架,主要用于从速率、顺序等方面控制数据包的发送和接收。从 Linux 4.1 开始,tc 增加了一些新的挂载点,并支持将 eBPF 程序作为 filter 加载到这些挂载点上。","breadcrumbs":"使用 eBPF 进行 tc 流量控制 » 背景","id":"127","title":"背景"},"128":{"body":"从协议栈上看,tc 位于链路层,其所在位置已经完成了 sk_buff 的分配,要晚于 xdp。为了实现对数据包发送和接收的控制,tc 使用队列结构来临时保存并组织数据包,在 tc 子系统中对应的数据结构和算法控制机制被抽象为 qdisc(Queueing discipline),其对外暴露数据包入队和出队的两个回调接口,并在内部隐藏排队算法实现。在 qdisc 中我们可以基于 filter 和 class 实现复杂的树形结构,其中 filter 被挂载到 qdisc 或 class 上用于实现具体的过滤逻辑,返回值决定了该数据包是否属于特定 class。 当数据包到达顶层 qdisc 时,其入队接口被调用,其上挂载的 filter 被依次执行直到一个 filter 匹配成功;此后数据包被送入该 filter 指向的 class,进入该 class 配置的 qdisc 处理流程中。tc 框架提供了所谓 classifier-action 机制,即在数据包匹配到特定 filter 时执行该 filter 所挂载的 action 对数据包进行处理,实现了完整的数据包分类和处理机制。 现有的 tc 为 eBPF 提供了 direct-action 模式,它使得一个作为 filter 加载的 eBPF 程序可以返回像 TC_ACT_OK 等 tc action 的返回值,而不是像传统的 filter 那样仅仅返回一个 classid 并把对数据包的处理交给 action 模块。现在,eBPF 程序可以被挂载到特定的 qdisc 上,并完成对数据包的分类和处理动作。","breadcrumbs":"使用 eBPF 进行 tc 流量控制 » tc 概述","id":"128","title":"tc 概述"},"129":{"body":"#include \n#include \n#include \n#include #define TC_ACT_OK 0\n#define ETH_P_IP 0x0800 /* Internet Protocol packet */ /// @tchook {\"ifindex\":1, \"attach_point\":\"BPF_TC_INGRESS\"}\n/// @tcopts {\"handle\":1, \"priority\":1}\nSEC(\"tc\")\nint tc_ingress(struct __sk_buff *ctx)\n{ void *data_end = (void *)(__u64)ctx->data_end; void *data = (void *)(__u64)ctx->data; struct ethhdr *l2; struct iphdr *l3; if (ctx->protocol != bpf_htons(ETH_P_IP)) return TC_ACT_OK; l2 = data; if ((void *)(l2 + 1) > data_end) return TC_ACT_OK; l3 = (struct iphdr *)(l2 + 1); if ((void *)(l3 + 1) > data_end) return TC_ACT_OK; bpf_printk(\"Got IP packet: tot_len: %d, ttl: %d\", bpf_ntohs(l3->tot_len), l3->ttl); return TC_ACT_OK;\n} char __license[] SEC(\"license\") = \"GPL\"; 这段代码定义了一个 eBPF 程序,它可以通过 Linux TC(Transmission Control)来捕获数据包并进行处理。在这个程序中,我们限定了只捕获 IPv4 协议的数据包,然后通过 bpf_printk 函数打印出数据包的总长度和 Time-To-Live(TTL)字段的值。 需要注意的是,我们在代码中使用了一些 BPF 库函数,例如 bpf_htons 和 bpf_ntohs 函数,它们用于进行网络字节序和主机字节序之间的转换。此外,我们还使用了一些注释来为 TC 提供附加点和选项信息。例如,在这段代码的开头,我们使用了以下注释: /// @tchook {\"ifindex\":1, \"attach_point\":\"BPF_TC_INGRESS\"}\n/// @tcopts {\"handle\":1, \"priority\":1} 这些注释告诉 TC 将 eBPF 程序附加到网络接口的 ingress 附加点,并指定了 handle 和 priority 选项的值。关于 libbpf 中 tc 相关的 API 可以参考 patchwork 中的介绍。 总之,这段代码实现了一个简单的 eBPF 程序,用于捕获数据包并打印出它们的信息。","breadcrumbs":"使用 eBPF 进行 tc 流量控制 » 编写 eBPF 程序","id":"129","title":"编写 eBPF 程序"},"13":{"body":"在本篇博客中,我们将深入探讨eBPF(Extended Berkeley Packet Filter)的基本框架和开发流程。eBPF是一种在Linux内核上运行的强大网络和性能分析工具,它为开发者提供了在内核运行时动态加载、更新和运行用户定义代码的能力。这使得开发者可以实现高效、安全的内核级别的网络监控、性能分析和故障排查等功能。 本文是eBPF入门开发实践教程的第二篇,我们将重点关注如何编写一个简单的eBPF程序,并通过实际例子演示整个开发流程。在阅读本教程之前,建议您先学习第一篇教程,以便对eBPF的基本概念有个大致的了解。 在开发eBPF程序时,有多种开发框架可供选择,如 BCC(BPF Compiler Collection)libbpf、cilium/ebpf、eunomia-bpf 等。虽然不同工具的特点各异,但它们的基本开发流程大致相同。在接下来的内容中,我们将深入了解这些流程,并以 Hello World 程序为例,带领读者逐步掌握eBPF开发的基本技巧。 本教程将帮助您了解eBPF程序的基本结构、编译和加载过程、用户空间与内核空间的交互方式以及调试与优化技巧。通过学习本教程,您将掌握eBPF开发的基本知识,并为后续进一步学习和实践奠定坚实的基础。","breadcrumbs":"eBPF Hello World,基本框架和开发流程 » eBPF 入门开发实践教程一:Hello World,基本框架和开发流程","id":"13","title":"eBPF 入门开发实践教程一:Hello World,基本框架和开发流程"},"130":{"body":"通过容器编译: docker run -it -v `pwd`/:/src/ ghcr.io/eunomia-bpf/ecc-`uname -m`:latest 或是通过 ecc 编译: $ ecc tc.bpf.c\nCompiling bpf object...\nPacking ebpf object and config into package.json... 并通过 ecli 运行: sudo ecli run ./package.json 可以通过如下方式查看程序的输出: $ sudo cat /sys/kernel/debug/tracing/trace_pipe node-1254811 [007] ..s1 8737831.671074: 0: Got IP packet: tot_len: 79, ttl: 64 sshd-1254728 [006] ..s1 8737831.674334: 0: Got IP packet: tot_len: 79, ttl: 64 sshd-1254728 [006] ..s1 8737831.674349: 0: Got IP packet: tot_len: 72, ttl: 64 node-1254811 [007] ..s1 8737831.674550: 0: Got IP packet: tot_len: 71, ttl: 64","breadcrumbs":"使用 eBPF 进行 tc 流量控制 » 编译运行","id":"130","title":"编译运行"},"131":{"body":"本文介绍了如何向 TC 流量控制子系统挂载 eBPF 类型的 filter 来实现对链路层数据包的排队处理。基于 eunomia-bpf 提供的通过注释向 libbpf 传递参数的方案,我们可以将自己编写的 tc BPF 程序以指定选项挂载到目标网络设备,并借助内核的 sk_buff 结构对数据包进行过滤处理。 如果您希望学习更多关于 eBPF 的知识和实践,可以访问我们的教程代码仓库 https://github.com/eunomia-bpf/bpf-developer-tutorial 或网站 https://eunomia.dev/zh/tutorials/ 以获取更多示例和完整的教程。","breadcrumbs":"使用 eBPF 进行 tc 流量控制 » 总结","id":"131","title":"总结"},"132":{"body":"http://just4coding.com/2022/08/05/tc/ https://arthurchiao.art/blog/understanding-tc-da-mode-zh/","breadcrumbs":"使用 eBPF 进行 tc 流量控制 » 参考","id":"132","title":"参考"},"133":{"body":"本文主要记录了笔者在 Android Studio Emulator 中测试高版本 Android Kernel 对基于 libbpf 的 CO-RE 技术支持程度的探索过程、结果和遇到的问题。 测试采用的方式是在 Android Shell 环境下构建 Debian 环境,并基于此尝试构建 eunomia-bpf 工具链、运行其测试用例。","breadcrumbs":"在 Android 上使用 eBPF 程序 » 在 Andorid 上使用 eBPF 程序","id":"133","title":"在 Andorid 上使用 eBPF 程序"},"134":{"body":"截至目前(2023-04),Android 还未对 eBPF 程序的动态加载做出较好的支持,无论是以 bcc 为代表的带编译器分发方案,还是基于 btf 和 libbpf 的 CO-RE 方案,都在较大程度上离不开 Linux 环境的支持,无法在 Android 系统上很好地运行 [1] 。 虽然如此,在 Android 平台上尝试 eBPF 也已经有了一些成功案例,除谷歌官方提供的修改 Android.bp 以将 eBPF 程序随整个系统一同构建并挂载的方案 [2] ,也有人提出基于 Android 内核构建 Linux 环境进而运行 eBPF 工具链的思路,并开发了相关工具。 目前已有的资料,大多基于 adeb/eadb 在 Android 内核基础上构建 Linux 沙箱,并对 bcc 和 bpftrace 相关工具链进行测试,而对 CO-RE 方案的测试工作较少。在 Android 上使用 bcc 工具目前有较多参考资料,如: SeeFlowerX: https://blog.seeflower.dev/category/eBPF/ evilpan: https://bbs.kanxue.com/thread-271043.htm 其主要思路是利用 chroot 在 Android 内核上运行一个 Debian 镜像,并在其中构建整个 bcc 工具链,从而使用 eBPF 工具。如果想要使用 bpftrace,原理也是类似的。 事实上,高版本的 Android 内核已支持 btf 选项,这意味着 eBPF 领域中新兴的 CO-RE 技术也应当能够运用到基于 Android 内核的 Linux 系统中。本文将基于此对 eunomia-bpf 在模拟器环境下进行测试运行。 eunomia-bpf 是一个结合了 libbpf 和 WebAssembly 技术的开源项目,旨在简化 eBPF 程序的编写、编译和部署。该项目可被视作 CO-RE 的一种实践方式,其核心依赖是 libbpf,相信对 eunomia-bpf 的测试工作能够为其他 CO-RE 方案提供参考。","breadcrumbs":"在 Android 上使用 eBPF 程序 » 背景","id":"134","title":"背景"},"135":{"body":"Android Emulator(Android Studio Flamingo | 2022.2.1) AVD: Pixel 6 Android Image: Tiramisu Android 13.0 x86_64(5.15.41-android13-8-00055-g4f5025129fe8-ab8949913)","breadcrumbs":"在 Android 上使用 eBPF 程序 » 测试环境","id":"135","title":"测试环境"},"136":{"body":"[3] 从 eadb 仓库 的 releases 页面获取 debianfs-amd64-full.tar.gz 作为 Linux 环境的 rootfs,同时还需要获取该项目的 assets 目录来构建环境; 从 Android Studio 的 Device Manager 配置并启动 Android Virtual Device; 通过 Android Studio SDK 的 adb 工具将 debianfs-amd64-full.tar.gz 和 assets 目录推送到 AVD 中: ./adb push debianfs-amd64-full.tar.gz /data/local/tmp/deb.tar.gz ./adb push assets /data/local/tmp/assets 通过 adb 进入 Android shell 环境并获取 root 权限: ./adb shell su 在 Android shell 中构建并进入 debian 环境: mkdir -p /data/eadb mv /data/local/tmp/assets/* /data/eadb mv /data/local/tmp/deb.tar.gz /data/eadb/deb.tar.gz rm -r /data/local/tmp/assets chmod +x /data/eadb/device-* /data/eadb/device-unpack /data/eadb/run /data/eadb/debian 至此,测试 eBPF 所需的 Linux 环境已经构建完毕。此外,在 Android shell 中(未进入 debian 时)可以通过 zcat /proc/config.gz 并配合 grep 查看内核编译选项。 目前,eadb 打包的 debian 环境存在 libc 版本低,缺少的工具依赖较多等情况;并且由于内核编译选项不同,一些 eBPF 功能可能也无法使用。","breadcrumbs":"在 Android 上使用 eBPF 程序 » 环境搭建","id":"136","title":"环境搭建"},"137":{"body":"在 debian 环境中将 eunomia-bpf 仓库 clone 到本地,具体的构建过程,可以参考仓库的 build.md 。在本次测试中,笔者选用了 ecc 编译生成 package.json 的方式,该工具的构建和使用方式请参考 仓库页面 。 在构建过程中,可能需要自行安装包括但不限于 curl,pkg-config,libssl-dev 等工具。","breadcrumbs":"在 Android 上使用 eBPF 程序 » 工具构建","id":"137","title":"工具构建"},"138":{"body":"有部分 eBPF 程序可以成功在 Android 上运行,但也会有部分应用因为种种原因无法成功被执行。","breadcrumbs":"在 Android 上使用 eBPF 程序 » 结果","id":"138","title":"结果"},"139":{"body":"bootstrap 运行输出如下: TIME PID PPID EXIT_CODE DURATION_NS COMM FILENAME EXIT_EVENT\n09:09:19 10217 479 0 0 sh /system/bin/sh 0\n09:09:19 10217 479 0 0 ps /system/bin/ps 0\n09:09:19 10217 479 0 54352100 ps 1\n09:09:21 10219 479 0 0 sh /system/bin/sh 0\n09:09:21 10219 479 0 0 ps /system/bin/ps 0\n09:09:21 10219 479 0 44260900 ps 1 tcpstates 开始监测后在 Linux 环境中通过 wget 下载 Web 页面: TIME SADDR DADDR SKADDR TS_US DELTA_US PID OLDSTATE NEWSTATE FAMILY SPORT DPORT TASK\n09:07:46 0x4007000200005000000000000f02000a 0x5000000000000f02000a8bc53f77 18446635827774444352 3315344998 0 10115 7 2 2 0 80 wget\n09:07:46 0x40020002d98e50003d99f8090f02000a 0xd98e50003d99f8090f02000a8bc53f77 18446635827774444352 3315465870 120872 0 2 1 2 55694 80 swapper/0\n09:07:46 0x40010002d98e50003d99f8090f02000a 0xd98e50003d99f8090f02000a8bc53f77 18446635827774444352 3315668799 202929 10115 1 4 2 55694 80 wget\n09:07:46 0x40040002d98e50003d99f8090f02000a 0xd98e50003d99f8090f02000a8bc53f77 18446635827774444352 3315670037 1237 0 4 5 2 55694 80 swapper/0\n09:07:46 0x40050002000050003d99f8090f02000a 0x50003d99f8090f02000a8bc53f77 18446635827774444352 3315670225 188 0 5 7 2 55694 80 swapper/0\n09:07:47 0x400200020000bb01565811650f02000a 0xbb01565811650f02000a6aa0d9ac 18446635828348806592 3316433261 0 2546 2 7 2 49970 443 ChromiumNet\n09:07:47 0x400200020000bb01db794a690f02000a 0xbb01db794a690f02000aea2afb8e 18446635827774427776 3316535591 0 1469 2 7 2 37386 443 ChromiumNet 开始检测后在 Android Studio 模拟界面打开 Chrome 浏览器并访问百度页面: TIME SADDR DADDR SKADDR TS_US DELTA_US PID OLDSTATE NEWSTATE FAMILY SPORT DPORT TASK\n07:46:58 0x400700020000bb01000000000f02000a 0xbb01000000000f02000aeb6f2270 18446631020066638144 192874641 0 3305 7 2 2 0 443 NetworkService\n07:46:58 0x40020002d28abb01494b6ebe0f02000a 0xd28abb01494b6ebe0f02000aeb6f2270 18446631020066638144 192921938 47297 3305 2 1 2 53898 443 NetworkService\n07:46:58 0x400700020000bb01000000000f02000a 0xbb01000000000f02000ae7e7e8b7 18446631020132433920 193111426 0 3305 7 2 2 0 443 NetworkService\n07:46:58 0x40020002b4a0bb0179ff85e80f02000a 0xb4a0bb0179ff85e80f02000ae7e7e8b7 18446631020132433920 193124670 13244 3305 2 1 2 46240 443 NetworkService\n07:46:58 0x40010002b4a0bb0179ff85e80f02000a 0xb4a0bb0179ff85e80f02000ae7e7e8b7 18446631020132433920 193185397 60727 3305 1 4 2 46240 443 NetworkService\n07:46:58 0x40040002b4a0bb0179ff85e80f02000a 0xb4a0bb0179ff85e80f02000ae7e7e8b7 18446631020132433920 193186122 724 3305 4 5 2 46240 443 NetworkService\n07:46:58 0x400500020000bb0179ff85e80f02000a 0xbb0179ff85e80f02000ae7e7e8b7 18446631020132433920 193186244 122 3305 5 7 2 46240 443 NetworkService\n07:46:59 0x40010002d01ebb01d0c52f5c0f02000a 0xd01ebb01d0c52f5c0f02000a51449c27 18446631020103553856 194110884 0 5130 1 8 2 53278 443 ThreadPoolForeg\n07:46:59 0x400800020000bb01d0c52f5c0f02000a 0xbb01d0c52f5c0f02000a51449c27 18446631020103553856 194121000 10116 3305 8 7 2 53278 443 NetworkService\n07:46:59 0x400700020000bb01000000000f02000a 0xbb01000000000f02000aeb6f2270 18446631020099513920 194603677 0 3305 7 2 2 0 443 NetworkService\n07:46:59 0x40020002d28ebb0182dd92990f02000a 0xd28ebb0182dd92990f02000aeb6f2270 18446631020099513920 194649313 45635 12 2 1 2 53902 443 ksoftirqd/0\n07:47:00 0x400700020000bb01000000000f02000a 0xbb01000000000f02000a26f6e878 18446631020132433920 195193350 0 3305 7 2 2 0 443 NetworkService\n07:47:00 0x40020002ba32bb01e0e09e3a0f02000a 0xba32bb01e0e09e3a0f02000a26f6e878 18446631020132433920 195206992 13642 0 2 1 2 47666 443 swapper/0\n07:47:00 0x400700020000bb01000000000f02000a 0xbb01000000000f02000ae7e7e8b7 18446631020132448128 195233125 0 3305 7 2 2 0 443 NetworkService\n07:47:00 0x40020002b4a8bb0136cac8dd0f02000a 0xb4a8bb0136cac8dd0f02000ae7e7e8b7 18446631020132448128 195246569 13444 3305 2 1 2 46248 443 NetworkService\n07:47:00 0xf02000affff00000000000000000000 0x1aca06cffff00000000000000000000 18446631019225912320 195383897 0 947 7 2 10 0 80 Thread-11\n07:47:00 0x40010002b4a8bb0136cac8dd0f02000a 0xb4a8bb0136cac8dd0f02000ae7e7e8b7 18446631020132448128 195421584 175014 3305 1 4 2 46248 443 NetworkService\n07:47:00 0x40040002b4a8bb0136cac8dd0f02000a 0xb4a8bb0136cac8dd0f02000ae7e7e8b7 18446631020132448128 195422361 777 3305 4 5 2 46248 443 NetworkService\n07:47:00 0x400500020000bb0136cac8dd0f02000a 0xbb0136cac8dd0f02000ae7e7e8b7 18446631020132448128 195422450 88 3305 5 7 2 46248 443 NetworkService\n07:47:01 0x400700020000bb01000000000f02000a 0xbb01000000000f02000aea2afb8e 18446631020099528128 196321556 0 1315 7 2 2 0 443 ChromiumNet","breadcrumbs":"在 Android 上使用 eBPF 程序 » 成功案例","id":"139","title":"成功案例"},"14":{"body":"在开始编写eBPF程序之前,我们需要准备一个合适的开发环境,并了解eBPF程序的基本开发流程。本部分将详细介绍这些内容。","breadcrumbs":"eBPF Hello World,基本框架和开发流程 » eBPF开发环境准备与基本开发流程","id":"14","title":"eBPF开发环境准备与基本开发流程"},"140":{"body":"opensnoop 例如 opensnoop 工具,可以在 Android 上成功构建,但运行报错: libbpf: failed to determine tracepoint 'syscalls/sys_enter_open' perf event ID: No such file or directory\nlibbpf: prog 'tracepoint__syscalls__sys_enter_open': failed to create tracepoint 'syscalls/sys_enter_open' perf event: No such file or directory\nlibbpf: prog 'tracepoint__syscalls__sys_enter_open': failed to auto-attach: -2\nfailed to attach skeleton\nError: BpfError(\"load and attach ebpf program failed\") 后经查看发现内核未开启 CONFIG_FTRACE_SYSCALLS 选项,导致无法使用 syscalls 的 tracepoint。","breadcrumbs":"在 Android 上使用 eBPF 程序 » 一些可能的报错原因","id":"140","title":"一些可能的报错原因"},"141":{"body":"在 Android shell 中查看内核编译选项可以发现 CONFIG_DEBUG_INFO_BTF 默认是打开的,在此基础上 eunomia-bpf 项目提供的 example 已有一些能够成功运行的案例,例如可以监测 exec 族函数的执行和 tcp 连接的状态。 对于无法运行的一些,原因主要是以下两个方面: 内核编译选项未支持相关 eBPF 功能; eadb 打包的 Linux 环境较弱,缺乏必须依赖; 目前在 Android 系统中使用 eBPF 工具基本上仍然需要构建完整的 Linux 运行环境,但 Android 内核本身对 eBPF 的支持已较为全面,本次测试证明较高版本的 Android 内核支持 BTF 调试信息和依赖 CO-RE 的 eBPF 程序的运行。 Android 系统 eBPF 工具的发展需要官方新特性的加入,目前看来通过 Android APP 直接使用 eBPF 工具需要的工作量较大,同时由于 eBPF 工具需要 root 权限,普通 Android 用户的使用会面临较多困难。 如果希望学习更多关于 eBPF 的知识和实践,可以访问我们的教程代码仓库 https://github.com/eunomia-bpf/bpf-developer-tutorial 或网站 https://eunomia.dev/zh/tutorials/ 以获取更多示例和完整的教程。","breadcrumbs":"在 Android 上使用 eBPF 程序 » 总结","id":"141","title":"总结"},"142":{"body":"https://source.android.google.cn/docs/core/architecture/kernel/bpf [1] : https://mp.weixin.qq.com/s/mul4n5D3nXThjxuHV7GpMA [3] : https://blog.seeflower.dev/archives/138/","breadcrumbs":"在 Android 上使用 eBPF 程序 » 参考","id":"142","title":"参考"},"143":{"body":"TODO","breadcrumbs":"使用 eBPF 追踪 HTTP 请求或其他七层协议 » http","id":"143","title":"http"},"144":{"body":"随着TLS在现代网络环境中的广泛应用,跟踪微服务RPC消息已经变得愈加棘手。传统的流量嗅探技术常常受限于只能获取到加密后的数据,导致无法真正观察到通信的原始内容。这种限制为系统的调试和分析带来了不小的障碍。 但现在,我们有了新的解决方案。使用 eBPF 技术,通过其能力在用户空间进行探测,提供了一种方法重新获得明文数据,使得我们可以直观地查看加密前的通信内容。然而,每个应用可能使用不同的库,每个库都有多个版本,这种多样性给跟踪带来了复杂性。 在本教程中,我们将带您了解一种跨多种用户态 SSL/TLS 库的 eBPF 追踪技术,它不仅可以同时跟踪 GnuTLS 和 OpenSSL 等用户态库,而且相比以往,大大降低了对新版本库的维护工作。","breadcrumbs":"使用 uprobe 捕获多种库的 SSL/TLS 明文数据 » eBPF 实践教程:使用 uprobe 捕获多种库的 SSL/TLS 明文数据","id":"144","title":"eBPF 实践教程:使用 uprobe 捕获多种库的 SSL/TLS 明文数据"},"145":{"body":"在深入本教程的主题之前,我们需要理解一些核心概念,这些概念将为我们后面的讨论提供基础。","breadcrumbs":"使用 uprobe 捕获多种库的 SSL/TLS 明文数据 » 背景知识","id":"145","title":"背景知识"},"146":{"body":"SSL (Secure Sockets Layer): 由 Netscape 在 1990 年代早期开发,为网络上的两台机器之间提供数据加密传输。然而,由于某些已知的安全问题,SSL的使用已被其后继者TLS所替代。 TLS (Transport Layer Security): 是 SSL 的继任者,旨在提供更强大和更安全的数据加密方式。TLS 工作通过一个握手过程,在这个过程中,客户端和服务器之间会选择一个加密算法和相应的密钥。一旦握手完成,数据传输开始,所有数据都使用选择的算法和密钥加密。","breadcrumbs":"使用 uprobe 捕获多种库的 SSL/TLS 明文数据 » SSL 和 TLS","id":"146","title":"SSL 和 TLS"},"147":{"body":"Transport Layer Security (TLS) 是一个密码学协议,旨在为计算机网络上的通信提供安全性。它主要目标是通过密码学,例如证书的使用,为两个或更多通信的计算机应用程序提供安全性,包括隐私(机密性)、完整性和真实性。TLS 由两个子层组成:TLS 记录协议和TLS 握手协议。 握手过程 当客户端与启用了TLS的服务器连接并请求建立安全连接时,握手过程开始。握手允许客户端和服务器通过不对称密码来建立连接的安全性参数,完整流程如下: 初始握手 :客户端连接到启用了TLS的服务器,请求安全连接,并提供它支持的密码套件列表(加密算法和哈希函数)。 选择密码套件 :从提供的列表中,服务器选择它也支持的密码套件和哈希函数,并通知客户端已做出的决定。 提供数字证书 :通常,服务器接下来会提供形式为数字证书的身份验证。此证书包含服务器名称、信任的证书授权机构(为证书的真实性提供担保)以及服务器的公共加密密钥。 验证证书 :客户端在继续之前确认证书的有效性。 生成会话密钥 :为了生成用于安全连接的会话密钥,客户端有以下两种方法: 使用服务器的公钥加密一个随机数(PreMasterSecret)并将结果发送到服务器(只有服务器才能使用其私钥解密);双方然后使用该随机数生成一个独特的会话密钥,用于会话期间的数据加密和解密。 使用 Diffie-Hellman 密钥交换(或其变体椭圆曲线DH)来安全地生成一个随机且独特的会话密钥,用于加密和解密,该密钥具有前向保密的额外属性:即使在未来公开了服务器的私钥,也不能用它来解密当前的会话,即使第三方拦截并记录了会话。 一旦上述步骤成功完成,握手过程便结束,加密的连接开始。此连接使用会话密钥进行加密和解密,直到连接关闭。如果上述任何步骤失败,则TLS握手失败,连接将不会建立。 OSI模型中的TLS TLS 和 SSL 不完全适合 OSI 模型或 TCP/IP 模型的任何单一层次。TLS 在“某些可靠的传输协议(例如,TCP)之上运行”,这意味着它位于传输层之上。它为更高的层提供加密,这通常是表示层的功能。但是,使用TLS 的应用程序通常视其为传输层,即使使用TLS的应用程序必须积极控制启动 TLS 握手和交换的认证证书的处理。","breadcrumbs":"使用 uprobe 捕获多种库的 SSL/TLS 明文数据 » TLS 的工作原理","id":"147","title":"TLS 的工作原理"},"148":{"body":"eBPF (Extended Berkeley Packet Filter): 是一种内核技术,允许用户在内核空间中运行预定义的程序,不需要修改内核源代码或重新加载模块。它创建了一个桥梁,使得用户空间和内核空间可以交互,从而为系统监控、性能分析和网络流量分析等任务提供了无前例的能力。 uprobes 是eBPF的一个重要特性,允许我们在用户空间应用程序中动态地插入探测点,特别适用于跟踪SSL/TLS库中的函数调用。","breadcrumbs":"使用 uprobe 捕获多种库的 SSL/TLS 明文数据 » eBPF 和 uprobe","id":"148","title":"eBPF 和 uprobe"},"149":{"body":"SSL/TLS协议的实现主要依赖于用户态库。以下是一些常见的库: OpenSSL: 一个开源的、功能齐全的加密库,广泛应用于许多开源和商业项目中。 BoringSSL: 是Google维护的OpenSSL的一个分支,重点是简化和优化,适用于Google的需求。 GnuTLS: 是GNU项目的一部分,提供了SSL,TLS和DTLS协议的实现。与OpenSSL和BoringSSL相比,GnuTLS在API设计、模块结构和许可证上有所不同。","breadcrumbs":"使用 uprobe 捕获多种库的 SSL/TLS 明文数据 » 用户态库","id":"149","title":"用户态库"},"15":{"body":"要开发eBPF程序,您需要安装以下软件和工具: Linux 内核:由于eBPF是内核技术,因此您需要具备较新版本的Linux内核(推荐4.8及以上版本),以支持eBPF功能。 LLVM 和 Clang:这些工具用于编译eBPF程序。安装最新版本的LLVM和Clang可以确保您获得最佳的eBPF支持。 eBPF 程序主要由两部分构成:内核态部分和用户态部分。内核态部分包含 eBPF 程序的实际逻辑,用户态部分负责加载、运行和监控内核态程序。 当您选择了合适的开发框架后,如BCC(BPF Compiler Collection)、libbpf、cilium/ebpf或eunomia-bpf等,您可以开始进行用户态和内核态程序的开发。以BCC工具为例,我们将介绍eBPF程序的基本开发流程: 安装BCC工具:根据您的Linux发行版,按照BCC官方文档的指南安装BCC工具和相关依赖。 编写eBPF程序(C语言):使用C语言编写一个简单的eBPF程序,例如Hello World程序。该程序可以在内核空间执行并完成特定任务,如统计网络数据包数量。 编写用户态程序(Python或C等):使用Python、C等语言编写用户态程序,用于加载、运行eBPF程序以及与之交互。在这个程序中,您需要使用BCC提供的API来加载和操作内核态的eBPF程序。 编译eBPF程序:使用BCC工具,将C语言编写的eBPF程序编译成内核可以执行的字节码。BCC会在运行时动态从源码编译eBPF程序。 加载并运行eBPF程序:在用户态程序中,使用BCC提供的API加载编译好的eBPF程序到内核空间,然后运行该程序。 与eBPF程序交互:用户态程序通过BCC提供的API与eBPF程序交互,实现数据收集、分析和展示等功能。例如,您可以使用BCC API读取eBPF程序中的map数据,以获取网络数据包统计信息。 卸载eBPF程序:当不再需要eBPF程序时,用户态程序应使用BCC API将其从内核空间卸载。 调试与优化:使用 bpftool 等工具进行eBPF程序的调试和优化,提高程序性能和稳定性。 通过以上流程,您可以使用BCC工具开发、编译、运行和调试eBPF程序。请注意,其他框架(如libbpf、cilium/ebpf和eunomia-bpf)的开发流程大致相似但略有不同,因此在选择框架时,请参考相应的官方文档和示例。 通过这个过程,你可以开发出一个能够在内核中运行的 eBPF 程序。eunomia-bpf 是一个开源的 eBPF 动态加载运行时和开发工具链,它的目的是简化 eBPF 程序的开发、构建、分发、运行。它基于 libbpf 的 CO-RE 轻量级开发框架,支持通过用户态 WASM 虚拟机控制 eBPF 程序的加载和执行,并将预编译的 eBPF 程序打包为通用的 JSON 或 WASM 模块进行分发。我们会使用 eunomia-bpf 进行演示。","breadcrumbs":"eBPF Hello World,基本框架和开发流程 » 安装必要的软件和工具","id":"15","title":"安装必要的软件和工具"},"150":{"body":"OpenSSL 是一个广泛应用的开源库,提供了 SSL 和 TLS 协议的完整实现,并广泛用于各种应用程序中以确保数据传输的安全性。其中,SSL_read() 和 SSL_write() 是两个核心的 API 函数,用于从 TLS/SSL 连接中读取和写入数据。本章节,我们将深入这两个函数,帮助你理解其工作机制。","breadcrumbs":"使用 uprobe 捕获多种库的 SSL/TLS 明文数据 » OpenSSL API 分析","id":"150","title":"OpenSSL API 分析"},"151":{"body":"当我们想从一个已建立的 SSL 连接中读取数据时,可以使用 SSL_read 或 SSL_read_ex 函数。函数原型如下: int SSL_read_ex(SSL *ssl, void *buf, size_t num, size_t *readbytes);\nint SSL_read(SSL *ssl, void *buf, int num); SSL_read 和 SSL_read_ex 试图从指定的 ssl 中读取最多 num 字节的数据到缓冲区 buf 中。成功时,SSL_read_ex 会在 *readbytes 中存储实际读取到的字节数。","breadcrumbs":"使用 uprobe 捕获多种库的 SSL/TLS 明文数据 » 1. SSL_read 函数","id":"151","title":"1. SSL_read 函数"},"152":{"body":"当我们想往一个已建立的 SSL 连接中写入数据时,可以使用 SSL_write 或 SSL_write_ex 函数。 函数原型: int SSL_write_ex(SSL *s, const void *buf, size_t num, size_t *written);\nint SSL_write(SSL *ssl, const void *buf, int num); SSL_write 和 SSL_write_ex 会从缓冲区 buf 中将最多 num 字节的数据写入到指定的 ssl 连接中。成功时,SSL_write_ex 会在 *written 中存储实际写入的字节数。","breadcrumbs":"使用 uprobe 捕获多种库的 SSL/TLS 明文数据 » 2. SSL_write 函数","id":"152","title":"2. SSL_write 函数"},"153":{"body":"在我们的例子中,我们使用 eBPF 来 hook ssl_read 和 ssl_write 函数,从而在数据读取或写入 SSL 连接时执行自定义操作。","breadcrumbs":"使用 uprobe 捕获多种库的 SSL/TLS 明文数据 » eBPF 内核态代码编写","id":"153","title":"eBPF 内核态代码编写"},"154":{"body":"首先,我们定义了一个数据结构 probe_SSL_data_t 用于在内核态和用户态之间传输数据: #define MAX_BUF_SIZE 8192\n#define TASK_COMM_LEN 16 struct probe_SSL_data_t { __u64 timestamp_ns; // 时间戳(纳秒) __u64 delta_ns; // 函数执行时间 __u32 pid; // 进程 ID __u32 tid; // 线程 ID __u32 uid; // 用户 ID __u32 len; // 读/写数据的长度 int buf_filled; // 缓冲区是否填充完整 int rw; // 读或写(0为读,1为写) char comm[TASK_COMM_LEN]; // 进程名 __u8 buf[MAX_BUF_SIZE]; // 数据缓冲区 int is_handshake; // 是否是握手数据\n};","breadcrumbs":"使用 uprobe 捕获多种库的 SSL/TLS 明文数据 » 数据结构","id":"154","title":"数据结构"},"155":{"body":"我们的目标是 hook 到 SSL_read 和 SSL_write 函数。我们定义了一个函数 SSL_exit 来处理这两个函数的返回值。该函数会根据当前进程和线程的 ID,确定是否需要追踪并收集数据。 static int SSL_exit(struct pt_regs *ctx, int rw) { int ret = 0; u32 zero = 0; u64 pid_tgid = bpf_get_current_pid_tgid(); u32 pid = pid_tgid >> 32; u32 tid = (u32)pid_tgid; u32 uid = bpf_get_current_uid_gid(); u64 ts = bpf_ktime_get_ns(); if (!trace_allowed(uid, pid)) { return 0; } /* store arg info for later lookup */ u64 *bufp = bpf_map_lookup_elem(&bufs, &tid); if (bufp == 0) return 0; u64 *tsp = bpf_map_lookup_elem(&start_ns, &tid); if (!tsp) return 0; u64 delta_ns = ts - *tsp; int len = PT_REGS_RC(ctx); if (len <= 0) // no data return 0; struct probe_SSL_data_t *data = bpf_map_lookup_elem(&ssl_data, &zero); if (!data) return 0; data->timestamp_ns = ts; data->delta_ns = delta_ns; data->pid = pid; data->tid = tid; data->uid = uid; data->len = (u32)len; data->buf_filled = 0; data->rw = rw; data->is_handshake = false; u32 buf_copy_size = min((size_t)MAX_BUF_SIZE, (size_t)len); bpf_get_current_comm(&data->comm, sizeof(data->comm)); if (bufp != 0) ret = bpf_probe_read_user(&data->buf, buf_copy_size, (char *)*bufp); bpf_map_delete_elem(&bufs, &tid); bpf_map_delete_elem(&start_ns, &tid); if (!ret) data->buf_filled = 1; else buf_copy_size = 0; bpf_perf_event_output(ctx, &perf_SSL_events, BPF_F_CURRENT_CPU, data, EVENT_SIZE(buf_copy_size)); return 0;\n} 这里的 rw 参数标识是读还是写。0 代表读,1 代表写。 数据收集流程 获取当前进程和线程的 ID,以及当前用户的 ID。 通过 trace_allowed 判断是否允许追踪该进程。 获取起始时间,以计算函数的执行时间。 尝试从 bufs 和 start_ns maps 中查找相关的数据。 如果成功读取了数据,则创建或查找 probe_SSL_data_t 结构来填充数据。 将数据从用户空间复制到缓冲区,并确保不超过预定的大小。 最后,将数据发送到用户空间。 注意:我们使用了两个用户返回探针 uretprobe 来分别 hook SSL_read 和 SSL_write 的返回: SEC(\"uretprobe/SSL_read\")\nint BPF_URETPROBE(probe_SSL_read_exit) { return (SSL_exit(ctx, 0)); // 0 表示读操作\n} SEC(\"uretprobe/SSL_write\")\nint BPF_URETPROBE(probe_SSL_write_exit) { return (SSL_exit(ctx, 1)); // 1 表示写操作\n}","breadcrumbs":"使用 uprobe 捕获多种库的 SSL/TLS 明文数据 » Hook 函数","id":"155","title":"Hook 函数"},"156":{"body":"在 SSL/TLS 中,握手(handshake)是一个特殊的过程,用于在客户端和服务器之间建立安全的连接。为了分析此过程,我们 hook 到了 do_handshake 函数,以跟踪握手的开始和结束。 进入握手 我们使用 uprobe 为 do_handshake 设置一个 probe: SEC(\"uprobe/do_handshake\")\nint BPF_UPROBE(probe_SSL_do_handshake_enter, void *ssl) { u64 pid_tgid = bpf_get_current_pid_tgid(); u32 pid = pid_tgid >> 32; u32 tid = (u32)pid_tgid; u64 ts = bpf_ktime_get_ns(); u32 uid = bpf_get_current_uid_gid(); if (!trace_allowed(uid, pid)) { return 0; } /* store arg info for later lookup */ bpf_map_update_elem(&start_ns, &tid, &ts, BPF_ANY); return 0;\n} 这段代码的主要功能如下: 获取当前的 pid, tid, ts 和 uid。 使用 trace_allowed 检查进程是否被允许追踪。 将当前时间戳存储在 start_ns 映射中,用于稍后计算握手过程的持续时间。 退出握手 同样,我们为 do_handshake 的返回设置了一个 uretprobe: SEC(\"uretprobe/do_handshake\")\nint BPF_URETPROBE(probe_SSL_do_handshake_exit) { u32 zero = 0; u64 pid_tgid = bpf_get_current_pid_tgid(); u32 pid = pid_tgid >> 32; u32 tid = (u32)pid_tgid; u32 uid = bpf_get_current_uid_gid(); u64 ts = bpf_ktime_get_ns(); int ret = 0; /* use kernel terminology here for tgid/pid: */ u32 tgid = pid_tgid >> 32; /* store arg info for later lookup */ if (!trace_allowed(tgid, pid)) { return 0; } u64 *tsp = bpf_map_lookup_elem(&start_ns, &tid); if (tsp == 0) return 0; ret = PT_REGS_RC(ctx); if (ret <= 0) // handshake failed return 0; struct probe_SSL_data_t *data = bpf_map_lookup_elem(&ssl_data, &zero); if (!data) return 0; data->timestamp_ns = ts; data->delta_ns = ts - *tsp; data->pid = pid; data->tid = tid; data->uid = uid; data->len = ret; data->buf_filled = 0; data->rw = 2; data->is_handshake = true; bpf_get_current_comm(&data->comm, sizeof(data->comm)); bpf_map_delete_elem(&start_ns, &tid); bpf_perf_event_output(ctx, &perf_SSL_events, BPF_F_CURRENT_CPU, data, EVENT_SIZE(0)); return 0;\n} 此函数的逻辑如下: 获取当前的 pid, tid, ts 和 uid。 使用 trace_allowed 再次检查是否允许追踪。 查找 start_ns 映射中的时间戳,用于计算握手的持续时间。 使用 PT_REGS_RC(ctx) 获取 do_handshake 的返回值,判断握手是否成功。 查找或初始化与当前线程关联的 probe_SSL_data_t 数据结构。 更新数据结构的字段,包括时间戳、持续时间、进程信息等。 通过 bpf_perf_event_output 将数据发送到用户态。 我们的 eBPF 代码不仅跟踪了 ssl_read 和 ssl_write 的数据传输,还特别关注了 SSL/TLS 的握手过程。这些信息对于深入了解和优化安全连接的性能至关重要。 通过这些 hook 函数,我们可以获得关于握手成功与否、握手所需的时间以及相关的进程信息的数据。这为我们提供了关于系统 SSL/TLS 行为的深入见解,可以帮助我们在需要时进行更深入的分析和优化。","breadcrumbs":"使用 uprobe 捕获多种库的 SSL/TLS 明文数据 » Hook到握手过程","id":"156","title":"Hook到握手过程"},"157":{"body":"在 eBPF 的生态系统中,用户态和内核态代码经常协同工作。内核态代码负责数据的采集,而用户态代码则负责设置、管理和处理这些数据。在本节中,我们将解读上述用户态代码如何配合 eBPF 追踪 SSL/TLS 交互。","breadcrumbs":"使用 uprobe 捕获多种库的 SSL/TLS 明文数据 » 用户态辅助代码分析与解读","id":"157","title":"用户态辅助代码分析与解读"},"158":{"body":"上述代码片段中,根据环境变量 env 的设定,程序可以选择针对三种常见的加密库(OpenSSL、GnuTLS 和 NSS)进行挂载。这意味着我们可以在同一个工具中对多种库的调用进行追踪。 为了实现这一功能,首先利用 find_library_path 函数确定库的路径。然后,根据库的类型,调用对应的 attach_ 函数来将 eBPF 程序挂载到库函数上。 if (env.openssl) { char *openssl_path = find_library_path(\"libssl.so\"); printf(\"OpenSSL path: %s\\n\", openssl_path); attach_openssl(obj, \"/lib/x86_64-linux-gnu/libssl.so.3\"); } if (env.gnutls) { char *gnutls_path = find_library_path(\"libgnutls.so\"); printf(\"GnuTLS path: %s\\n\", gnutls_path); attach_gnutls(obj, gnutls_path); } if (env.nss) { char *nss_path = find_library_path(\"libnspr4.so\"); printf(\"NSS path: %s\\n\", nss_path); attach_nss(obj, nss_path); } 这里主要包含 OpenSSL、GnuTLS 和 NSS 三个库的挂载逻辑。NSS 是为组织设计的一套安全库,支持创建安全的客户端和服务器应用程序。它们最初是由 Netscape 开发的,现在由 Mozilla 维护。其他两个库前面已经介绍过了,这里不再赘述。","breadcrumbs":"使用 uprobe 捕获多种库的 SSL/TLS 明文数据 » 1. 支持的库挂载","id":"158","title":"1. 支持的库挂载"},"159":{"body":"具体的 attach 函数如下: #define __ATTACH_UPROBE(skel, binary_path, sym_name, prog_name, is_retprobe) \\ do { \\ LIBBPF_OPTS(bpf_uprobe_opts, uprobe_opts, .func_name = #sym_name, \\ .retprobe = is_retprobe); \\ skel->links.prog_name = bpf_program__attach_uprobe_opts( \\ skel->progs.prog_name, env.pid, binary_path, 0, &uprobe_opts); \\ } while (false) int attach_openssl(struct sslsniff_bpf *skel, const char *lib) { ATTACH_UPROBE_CHECKED(skel, lib, SSL_write, probe_SSL_rw_enter); ATTACH_URETPROBE_CHECKED(skel, lib, SSL_write, probe_SSL_write_exit); ATTACH_UPROBE_CHECKED(skel, lib, SSL_read, probe_SSL_rw_enter); ATTACH_URETPROBE_CHECKED(skel, lib, SSL_read, probe_SSL_read_exit); if (env.latency && env.handshake) { ATTACH_UPROBE_CHECKED(skel, lib, SSL_do_handshake, probe_SSL_do_handshake_enter); ATTACH_URETPROBE_CHECKED(skel, lib, SSL_do_handshake, probe_SSL_do_handshake_exit); } return 0;\n} int attach_gnutls(struct sslsniff_bpf *skel, const char *lib) { ATTACH_UPROBE_CHECKED(skel, lib, gnutls_record_send, probe_SSL_rw_enter); ATTACH_URETPROBE_CHECKED(skel, lib, gnutls_record_send, probe_SSL_write_exit); ATTACH_UPROBE_CHECKED(skel, lib, gnutls_record_recv, probe_SSL_rw_enter); ATTACH_URETPROBE_CHECKED(skel, lib, gnutls_record_recv, probe_SSL_read_exit); return 0;\n} int attach_nss(struct sslsniff_bpf *skel, const char *lib) { ATTACH_UPROBE_CHECKED(skel, lib, PR_Write, probe_SSL_rw_enter); ATTACH_URETPROBE_CHECKED(skel, lib, PR_Write, probe_SSL_write_exit); ATTACH_UPROBE_CHECKED(skel, lib, PR_Send, probe_SSL_rw_enter); ATTACH_URETPROBE_CHECKED(skel, lib, PR_Send, probe_SSL_write_exit); ATTACH_UPROBE_CHECKED(skel, lib, PR_Read, probe_SSL_rw_enter); ATTACH_URETPROBE_CHECKED(skel, lib, PR_Read, probe_SSL_read_exit); ATTACH_UPROBE_CHECKED(skel, lib, PR_Recv, probe_SSL_rw_enter); ATTACH_URETPROBE_CHECKED(skel, lib, PR_Recv, probe_SSL_read_exit); return 0;\n} 我们进一步观察 attach_ 函数,可以看到它们都使用了 ATTACH_UPROBE_CHECKED 和 ATTACH_URETPROBE_CHECKED 宏来实现具体的挂载逻辑。这两个宏分别用于设置 uprobe(函数入口)和 uretprobe(函数返回)。 考虑到不同的库有不同的 API 函数名称(例如,OpenSSL 使用 SSL_write,而 GnuTLS 使用 gnutls_record_send),所以我们需要为每个库写一个独立的 attach_ 函数。 例如,在 attach_openssl 函数中,我们为 SSL_write 和 SSL_read 设置了 probe。如果用户还希望追踪握手的延迟 (env.latency) 和握手过程 (env.handshake),那么我们还会为 SSL_do_handshake 设置 probe。 在eBPF生态系统中,perf_buffer是一个用于从内核态传输数据到用户态的高效机制。这对于内核态eBPF程序来说是十分有用的,因为它们不能直接与用户态进行交互。使用perf_buffer,我们可以在内核态eBPF程序中收集数据,然后在用户态异步地读取这些数据。我们使用 perf_buffer__poll 函数来读取内核态上报的数据,如下所示: while (!exiting) { err = perf_buffer__poll(pb, PERF_POLL_TIMEOUT_MS); if (err < 0 && err != -EINTR) { warn(\"error polling perf buffer: %s\\n\", strerror(-err)); goto cleanup; } err = 0; } 最后,在 print_event 函数中,我们将数据打印到标准输出: // Function to print the event from the perf buffer\nvoid print_event(struct probe_SSL_data_t *event, const char *evt) { ... if (buf_size != 0) { if (env.hexdump) { // 2 characters for each byte + null terminator char hex_data[MAX_BUF_SIZE * 2 + 1] = {0}; buf_to_hex((uint8_t *)buf, buf_size, hex_data); printf(\"\\n%s\\n\", s_mark); for (size_t i = 0; i < strlen(hex_data); i += 32) { printf(\"%.32s\\n\", hex_data + i); } printf(\"%s\\n\\n\", e_mark); } else { printf(\"\\n%s\\n%s\\n%s\\n\\n\", s_mark, buf, e_mark); } }\n} 完整的源代码可以在这里查看: https://github.com/eunomia-bpf/bpf-developer-tutorial/tree/main/src/30-sslsniff","breadcrumbs":"使用 uprobe 捕获多种库的 SSL/TLS 明文数据 » 2. 详细挂载逻辑","id":"159","title":"2. 详细挂载逻辑"},"16":{"body":"可以通过以下步骤下载和安装 eunomia-bpf: 下载 ecli 工具,用于运行 eBPF 程序: $ wget https://aka.pw/bpf-ecli -O ecli && chmod +x ./ecli\n$ ./ecli -h\nUsage: ecli [--help] [--version] [--json] [--no-cache] url-and-args 下载编译器工具链,用于将 eBPF 内核代码编译为 config 文件或 WASM 模块: $ wget https://github.com/eunomia-bpf/eunomia-bpf/releases/latest/download/ecc && chmod +x ./ecc\n$ ./ecc -h\neunomia-bpf compiler\nUsage: ecc [OPTIONS] [EXPORT_EVENT_HEADER]\n.... 也可以使用 docker 镜像进行编译: $ docker run -it -v `pwd`/:/src/ ghcr.io/eunomia-bpf/ecc-`uname -m`:latest # 使用 docker 进行编译。`pwd` 应该包含 *.bpf.c 文件和 *.h 文件。\nexport PATH=PATH:~/.eunomia/bin\nCompiling bpf object...\nPacking ebpf object and config into /src/package.json...","breadcrumbs":"eBPF Hello World,基本框架和开发流程 » 下载安装 eunomia-bpf 开发工具","id":"16","title":"下载安装 eunomia-bpf 开发工具"},"160":{"body":"要开始使用 sslsniff,首先要进行编译: make 完成后,请按照以下步骤操作:","breadcrumbs":"使用 uprobe 捕获多种库的 SSL/TLS 明文数据 » 编译与运行","id":"160","title":"编译与运行"},"161":{"body":"在一个终端中,执行以下命令来启动 sslsniff: sudo ./sslsniff","breadcrumbs":"使用 uprobe 捕获多种库的 SSL/TLS 明文数据 » 启动 sslsniff","id":"161","title":"启动 sslsniff"},"162":{"body":"在另一个终端中,执行: curl https://example.com 正常情况下,你会看到类似以下的输出: Example Domain ... ... ","breadcrumbs":"使用 uprobe 捕获多种库的 SSL/TLS 明文数据 » 执行 CURL 命令","id":"162","title":"执行 CURL 命令"},"163":{"body":"当执行 curl 命令后,sslsniff 会显示以下内容: READ/RECV 0.132786160 curl 47458 1256 ----- DATA ----- ... Example Domain
...