Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >[译] 第 1 部分: 在生产环境中使用 eBPF 调试 Go 程序

[译] 第 1 部分: 在生产环境中使用 eBPF 调试 Go 程序

作者头像
ritchiechen
发布于 2021-01-12 15:19:12
发布于 2021-01-12 15:19:12
1.4K0
举报
文章被收录于专栏:Serverless+Serverless+

这是本系列文章的第一篇, 讲述了我们如何在生产环境中使用 eBPF 调试应用程序而无需重新编译/重新部署. 这篇文章介绍了如何使用 gobpf 和 uprobe 来为 Go 程序构建函数参数跟踪程序. 这项技术也可以扩展应用于其他编译型语言, 例如 C++, Rust 等. 本系列的后续文章将讨论如何使用 eBPF 来跟踪 HTTP/gRPC/SSL 等.

简介

在调试时, 我们通常对了解程序的状态感兴趣. 这使我们能够检查程序正在做什么, 并确定缺陷在代码中的位置. 观察状态的一种简单方法是使用调试器来捕获函数的参数. 对于 Go 程序来说, 我们经常使用 Delve 或者 GDB.

在开发环境中, Delve 和 GDB 工作得很好, 但是在生产环境中并不经常使用它们. 那些使调试器强大的特性也让它们不适合在生产环境中使用. 调试器会导致程序中断, 甚至允许修改状态, 这可能会导致软件产生意外故障.

为了更好地捕获函数参数, 我们将探索使用 eBPF(在 Linux 4.x+ 中可用) 以及高级的 Go 程序库 gobpf.

eBPF 是什么 ?

扩展的 BPF(eBPF) 是 Linux 4.x+ 里的一项内核技术. 你可以把它想像成一个运行在 Linux 内核中的轻量级的沙箱虚拟机, 可以提供对内核内存的经过验证的访问.

如下概述所示, eBPF 允许内核运行 BPF 字节码. 尽管使用的前端语言可能会有所不同, 但它通常是 C 的受限子集. 一般情况下, 使用 Clang 将 C 代码编译为 BPF 字节码, 然后验证这些字节码, 确保可以安全运行. 这些严格的验证确保了机器码不会有意或无意地破坏 Linux 内核, 并且 BPF 探针每次被触发时, 都只会执行有限的指令. 这些保证使 eBPF 可以用于性能关键的工作负载, 例如数据包过滤, 网络监控等.

从功能上讲, eBPF 允许你在某些事件(例如定时器, 网络事件或函数调用)触发时运行受限的 C 代码. 当在函数调用上触发时, 我们称这些函数为探针, 它们既可以用于内核里的函数调用(kprobe) 也可以用于用户态程序中的函数调用(uprobe). 本文重点介绍使用 uprobe 来动态跟踪函数参数.

Uprobe

uprobe 可以通过插入触发软中断的调试陷阱指令(x86 上的 int3)来拦截用户态程序. 这也是调试器的工作方式. uprobe 的流程与任何其他 BPF 程序基本相同, 如下图所示. 经过编译和验证的 BPF 程序将作为 uprobe 的一部分执行, 并且可以将结果写入缓冲区.

BPF for tracing (from Brendan Gregg)
BPF for tracing (from Brendan Gregg)

让我们看看 uprobe 是如何工作的. 要部署 uprobe 并捕获函数参数, 我们将使用这个简单的示例程序. 这个 Go 程序的相关部分如下所示.

main() 是一个简单的 HTTP 服务器, 在路径 /e 上公开单个GET 端点, 该端点使用迭代逼近来计算欧拉数(e). computeE接受单个查询参数(iterations), 该参数指定计算近似值要运行的迭代次数. 迭代次数越多, 近似值越准确, 但会消耗指令周期. 理解函数背后的数学并不是必需的. 我们只是想跟踪对computeE 的任何调用的参数.

代码语言:txt
AI代码解释
复制
// computeE computes the approximation of e by running a fixed number of iterations.
func computeE(iterations int64) float64 {
  res := 2.0
  fact := 1.0

  for i := int64(2); i < iterations; i++ {
    fact *= float64(i)
    res += 1 / fact
  }
  return res
}

func main() {
  http.HandleFunc("/e", func(w http.ResponseWriter, r *http.Request) {
    // Parse iters argument from get request, use default if not available.
    // ... removed for brevity ...
    w.Write([]byte(fmt.Sprintf("e = %0.4f\n", computeE(iters))))
  })
  // Start server...
}

要了解 uprobe 的工作原理, 让我们看一下二进制文件中如何跟踪符号. 由于 uprobe 通过插入调试陷阱指令来工作, 因此我们需要获取函数所在的地址. Linux 上的 Go 二进制文件使用 ELF 存储调试信息. 除非删除了调试数据, 否则即使在优化过的二进制文件中也可以找到这些信息. 我们可以使用 objdump 命令检查二进制文件中的符号:

代码语言:txt
AI代码解释
复制
[0] % objdump --syms app|grep computeE
00000000006609a0 g     F .text    000000000000004b              main.computeE

从这个输出中, 我们知道函数 computeE 位于地址 0x6609a0. 要看到它前后的指令, 我们可以使用 objdump 来反汇编二进制文件(通过添加 -d 选项实现). 反汇编后的代码如下:

代码语言:txt
AI代码解释
复制
[0] % objdump -d app | less
00000000006609a0 <main.computeE>:
  6609a0:       48 8b 44 24 08          mov    0x8(%rsp),%rax
  6609a5:       b9 02 00 00 00          mov    $0x2,%ecx
  6609aa:       f2 0f 10 05 16 a6 0f    movsd  0xfa616(%rip),%xmm0
  6609b1:       00
  6609b2:       f2 0f 10 0d 36 a6 0f    movsd  0xfa636(%rip),%xmm1

由此可见, 当 computeE 被调用时会发生什么. 第一条指令是 mov 0x8(%rsp), %rax. 它把 rsp 寄存器偏移 0x8 的内容移动到 rax 寄存器. 这实际上就是上面的输入参数 iterations. Go 的参数在栈上传递.

有了这些信息, 我们现在就可以继续深入, 编写代码来跟踪 computeE 的参数了.

构建跟踪程序

要捕获事件, 我们需要注册一个 uprobe 函数, 还需要一个可以读取输出的用户空间函数. 如下图所示. 我们将编写一个称为跟踪程序的二进制文件, 它负责注册 BPF 代码并读取 BPF 代码的结果. 如图所示, uprobe 简单地写入 perf buffer, 这是用于 perf 事件的 Linux 内核数据结构.

High-level overview showing the Tracer binary listening to perf events generated from the App
High-level overview showing the Tracer binary listening to perf events generated from the App

现在, 我们已了解了涉及到的各个部分, 下面让我们详细研究添加 uprobe 时发生的情况. 下图显示了 Linux 内核如何使用uprobe 修改二进制文件. 软中断指令(int3)作为第一条指令被插入 main.computeE 中. 这将导致软中断, 从而允许 Linux 内核执行我们的 BPF 函数. 然后我们将参数写入 perf buffer, 该缓冲区由跟踪程序异步读取.

Details of how a debug trap instruction is used call a BPF program
Details of how a debug trap instruction is used call a BPF program

BPF 函数相对简单, C代码如下所示. 我们注册这个函数, 每次调用 main.computeE 时都将调用它. 一旦调用, 我们只需读取函数参数并写入 perf buffer. 设置缓冲区需要很多样板代码, 可以在完整的示例中找到.

代码语言:txt
AI代码解释
复制
#include <uapi/linux/ptrace.h>

BPF_PERF_OUTPUT(trace);

inline int computeECalled(struct pt_regs *ctx) {
  // The input argument is stored in ax.
  long val = ctx->ax;
  trace.perf_submit(ctx, &val, sizeof(val));
  return 0;
}

现在我们有了一个用于 main.computeE 函数的功能完善的端到端的参数跟踪程序! 下面的视频片段展示了这一结果.

End-to-End demo
End-to-End demo

另一个很棒的事情是, 我们可以使用 GDB 来查看对二进制文件所做的修改. 在运行我们的跟踪程序之前, 我们输出地址 0x6609a0 的指令.

代码语言:txt
AI代码解释
复制
(gdb) display /4i 0x6609a0
10: x/4i 0x6609a0
   0x6609a0 <main.computeE>:    mov    0x8(%rsp),%rax
   0x6609a5 <main.computeE+5>:  mov    $0x2,%ecx
   0x6609aa <main.computeE+10>: movsd  0xfa616(%rip),%xmm0
   0x6609b2 <main.computeE+18>: movsd  0xfa636(%rip),%xmm1

而这是在我们运行跟踪程序之后. 我们可以清楚地看到, 第一个指令现在变成 int3 了.

代码语言:txt
AI代码解释
复制
(gdb) display /4i 0x6609a0
7: x/4i 0x6609a0
   0x6609a0 <main.computeE>:    int3
   0x6609a1 <main.computeE+1>:  mov    0x8(%rsp),%eax
   0x6609a5 <main.computeE+5>:  mov    $0x2,%ecx
   0x6609aa <main.computeE+10>: movsd  0xfa616(%rip),%xmm0

尽管我们为该特定示例对跟踪程序进行了硬编码, 但是这个过程是可以通用化的. Go 的许多方面(例如嵌套指针, 接口, 通道等)让这个过程变得有挑战性, 但是解决这些问题可以使用现有系统中不存在的另一种检测模式. 另外, 因为这一过程工作在二进制层面, 它也可以用于其他语言(C++, Rust 等)编译的二进制文件. 我们只需考虑它们各自 ABI 的差异.

下一步是什么 ?

使用 uprobe 进行 BPF 跟踪有其自身的优缺点. 当我们需要观察二进制程序的状态时, BPF 很有用, 甚至在连接调试器会产生问题或者坏处的环境(例如生产环境二进制程序). 最大的缺点是, 即使是最简单的程序状态的观测性, 也需要编写代码来实现. 编写和维护 BPF 代码很复杂. 没有大量高级工具, 不太可能把它当作一般的调试手段.

本文系外文翻译,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文系外文翻译,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
从零开始构建向量数据库:Milvus 的源码编译安装(二)
本篇文章接上一篇,继续聊聊向量数据库领域,知名的开源技术项目:Milvus,在不同 CPU 架构的 macOS 环境下的编译安装。
soulteary
2022/07/15
1.9K0
从零开始构建向量数据库:Milvus 的源码编译安装(二)​
本篇文章接上一篇[1],继续聊聊向量数据库领域,知名的开源技术项目:Milvus,在不同 CPU 架构的 macOS 环境下的编译安装。
soulteary
2023/03/05
2.3K0
从零开始构建向量数据库:Milvus 的源码编译安装(二)​
OSX下编译mpv(成功通过)
首先download下来mpv的代码 git clone https://github.com/mpv-player/mpv.git 然后安装ffmpeg,ffmpeg相关的编译方法在bbs.chinaffmpeg.com中可以找到 mpv代码down下来以后,可以看到目录结构如下
用户3765803
2019/03/05
2.2K0
OSX下编译mpv(成功通过)
2019-03-28 mac 用brew安装nginx
mac 用brew安装nginx 其实我主要是记录默认的几个目录的 brew install nginx Updating Homebrew... ==> Auto-updated Homebrew! Updated 3 taps (homebrew/core, homebrew/cask and caskroom/cask). ==> New Formulae buildkit gitleaks llvm@7
Albert陈凯
2019/04/01
1.5K0
【FFmpeg】在 Mac OS 中编译 FFmpeg 源码 ① ( homebrew 安装 | 通过 gitee 源安装 homebrew | 安装 FFmpeg 编译所需的软件包 )
在 Mac 系统中 homebrew 是一款 软件包管理工具 , 可以 轻松的 安装 / 卸载 / 更新 / 查看 / 搜索 软件包 , 可以简单方便地对软件包进行管理 , 无需用户 处理 复杂的依赖关系 问题 ;
韩曙亮
2024/04/09
7940
【FFmpeg】在 Mac OS 中编译 FFmpeg 源码 ① ( homebrew 安装 | 通过 gitee 源安装 homebrew | 安装 FFmpeg 编译所需的软件包 )
走进向量计算:从源码编译 OpenBLAS
不论是折腾深度学习、高性能计算,还是折腾向量数据库、相似性检索领域,在折腾的过程中,我们都可能会遇到需要 “OpenBLAS” 这个开源矩阵计算库的场景。
soulteary
2023/03/05
1.2K0
走进向量计算:从源码编译 OpenBLAS
Milvus 编译环境演进
Milvus 代码库分为了 C++ 和 Go 两个部分,Go 部分负责系统主体架构、分布式系统、存储/查询链路等,C++ 部分负责查询、索引引擎专注于单机场景下的高性能,两者之间通过 cgo 接口调用。
Zilliz RDS
2023/01/09
1.6K0
走进向量计算:从源码编译 OpenBLAS
不论是折腾深度学习、高性能计算,还是折腾向量数据库、相似性检索领域,在折腾的过程中,我们都可能会遇到需要 “OpenBLAS” 这个开源矩阵计算库的场景。
soulteary
2022/07/08
1.4K0
走进向量计算:从源码编译 OpenBLAS
【Android 音视频开发打怪升级:FFmpeg音视频编解码篇】一、FFmpeg so库编译
网上其实已经有很多的关于FFmpeg so库编译的分享,但是大部分都是直接把配置文件的内容贴出来。我想大部分取搜索 「如何编译FFmpeg so库」的人,对交叉编译这个东东都是比较陌生的。
开发的猫
2020/04/01
2K0
MinGW32和64位交叉编译环境的安装和使用「建议收藏」
CompileGraphics Magick, Boost, Botan and QT with MinGW64 under Windows 7 64
全栈程序员站长
2022/07/23
8.7K0
MinGW32和64位交叉编译环境的安装和使用「建议收藏」
从零开始构建向量数据库:Milvus 的源码编译安装(一)
我在知乎上开了一个新的专栏,想持续聊聊“向量数据库”相关的内容。本篇聊聊向量数据库领域,知名的开源技术项目:Milvus。
soulteary
2022/07/11
2.8K0
FFmpeg 开发(04):FFmpeg + OpenGLES 实现音频可视化播放
本文基于上一篇文章 FFmpeg + OpenSLES 实现音频解码播放 ,利用 FFmpeg 对一个 Mp4 文件的音频流进行解码,然后将解码后的 PCM 音频数据进行重采样,最后利用 OpenSLES 进行播放的同时,将 PCM 音频一个通道的数据实时渲染成柱状图。
字节流动
2020/07/17
1.1K0
[- 壹 FFmpeg4.2.1 -] CLion 集成 、Xcode 集成、 Android集成
吾的最终目的在移动端。但为了方便对FFmpeg的认知和调试,先在桌面把它消化一下,毕竟在Android中修改、调试都比较费事。知识殊途同归,重要的不是它在哪里,而是它能干嘛,你想拿他干嘛。 FFmpeg是c写的,在使用时需要动态链接到相应的库上。虽然用文本编辑器和命令行也能手撕代码,但IDE能让人少调些头发。这里记录一下Xcode 和 CLion 桌面、Android集成FFmpeg的方式。本篇的目标只有一个:跑起来 ---- 1.安装与配置ffmpeg 1.1:安装ffmpeg brew可以安装f
张风捷特烈
2020/04/30
1.3K0
[- 壹 FFmpeg4.2.1 -] CLion 集成 、Xcode 集成、 Android集成
CONQUEST 编译安装指南 ARM 篇
  随着近年来 AMD、Apple 等科技公司对于 ARM 芯片的研发技术的成熟,以 MacbookPro M1 为代表的 ARM 架构的普通 PC 开始进入市场。其实由于 ARM 的低功耗、高性能的优势,以 AWS、Azure 为首的云服务产商早已经推出了 ARM 服务器。当然,操作系统提供商们也对 ARM 架构的 CPU 进行了支持,比如 Ubuntu Server 就有 ARM 版本。还有像树莓派、路由器等这样的基于 ARM 芯片运行的小平台,都是 ARM 操作系统。截止现在为止,各种常用的软件、依赖库都相继支持 ARM 芯片,使得 ARM 版本的普通 PC、服务器也有了很大的发展势头。
zhonger
2022/10/28
1.1K0
Ubuntu 14.04 LTS下使用arm-linux-gcc交叉编译OpenCV 2.4.9
本文介绍了如何将OpenCV库移植到ARM平台上,包括编译工具链、依赖库、配置方法以及运行时注意事项。
剑影啸清寒
2018/01/02
9.6K1
Ubuntu 14.04 LTS下使用arm-linux-gcc交叉编译OpenCV 2.4.9
macos安装ffmpeg以及出现问题
就是freetype动态库指定的目录不存在,或许没有安装,使用brew命令安装,显示已经安装,重新卸载安装也不行 但是安装完成之后也不行,一直报错 执行命令报错:
py3study
2020/01/03
5.1K0
MIT 6.S081/Fall 2020 搭建risc-v与xv6开发调试环境
课程链接:https://pdos.csail.mit.edu/6.S081/2020/
耀耀
2022/01/24
2.4K0
从零开始构建向量数据库:Milvus 的源码编译安装(一)
我在知乎上开了一个新的专栏[1],想持续聊聊“向量数据库”相关的内容。本篇聊聊向量数据库领域,知名的开源技术项目:Milvus。
soulteary
2023/03/05
6K0
从零开始构建向量数据库:Milvus 的源码编译安装(一)
安装python的visual模块时报错
      今天在虚拟机下在学习scapy的东西,其中一个例子中需要安装一个python的visual模块,期间报了N多的错误,一个个解决其中的依赖问题,到后面被卡住了
py3study
2020/01/10
2.4K0
嵌入式Linux:编译和使用Protobuf库
Protobuf(Protocol Buffers)是由 Google 开发的一种轻量级、高效的结构化数据序列化方式,用于在不同应用之间进行数据交换和存储。它可以用于多种编程语言,并支持自动生成代码,使得数据结构定义和序列化/反序列化过程更加简洁和高效。
不脱发的程序猿
2024/05/26
7040
嵌入式Linux:编译和使用Protobuf库
推荐阅读
相关推荐
从零开始构建向量数据库:Milvus 的源码编译安装(二)
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档