在Golang中利用BPF进行动态追踪

Go 语言 + libbpfgo 实战 eBPF 开发 本文介绍了如何使用Go语言结合libbpfgo进行eBPF开发, 并提供实战案例源代码下载和运行讲解。 阅读详情

Part 1 背景

Golang作为云原生领域中使用最广泛的编程技术,是我们MatrixOrigin数据库主力开发语言。Golang本身提供了pprof性能剖析工具,可以让我们快速,粗略的分析性能瓶颈,在日常开发中广泛使用。然而,随着性能调优要求不断升高,我们需要更精准的性能指标,比如某个特定golang函数的执行时间,此时pprof就无能为力了,我们必须另想办法。

第一反应,我们可以考虑人工加入计时函数,然后重新编译代码执行。该方案当然可行,但是缺点也很明显,对每个需要分析的函数都要修改代码,重新编译执行。该方案工作量比较大,也不易维护。如果是线上系统,往往无法修改代码。

那么,是否有更好的方法来测量特定函数的时延?最好是不用修改代码并重新编译。答案是:Yes。


Part 2 历史

01 Uprobe

如果想在不修改代码的前提下,实现修改代码测量的功能,那么自然而然的选择,则在程序运行时动态的修改程序的代码段,加入我们自定义逻辑。

Linux开发者们,提供了在程序运行时,动态修改程序代码的功能——uprobe。它可以修改指定汇编代码为int3指令,并把原指令保存起来。当应用程序运行到int3指令时,便会触发异常,切换到Linux内核态,这里可以执行提前好的测量逻辑。随后返回用户空间,执行最开始被int3替换并保存的指令。在应用程序看来,一切都毫无感觉。而我们的测量逻辑,记录了当前时间。如果我们在函数开始和结束,各记录一次,则可以通过计算差值,得到函数的执行时间。

02 BPF

Uprobe机制,提供了动态追踪应用程序的基础框架。然而,它依旧很难使用,测量逻辑需要写Linux内核模块,这对大多数应用程序开发者来说,使用门槛依旧很高。

随着BPF技术横空出世,Linux内核开发者们,将其整合进入了uprobe框架。从而可以使用类似C的代码,编写动态追踪逻辑。这比Linux内核模块实现,要简单多了。

03 Bpftrace

为了进一步降低uprobe + BPF 的使用门槛,开发者们设计并实现了bpftrace工具,它作为BPF的前端,利用类似于脚本语法的简单方式,编译生成BPF程序并自动加载。这在我们分析应用程序时,可以非常快速的实现动态测量程序。也可以提前准备好脚本,直接拿来使用。

Bpftrace原本设计主要是针对C语言开发的应用场景,那么它可以用在golang环境中吗?答案是可以,但是需要调整。接下来的篇幅,将介绍如何让bpftrace在golang环境中正确的使用。


Part 3 当BPF遇上Golang

01 问题

Golang作为一种编译型语言,理论上可以直接使用uprobe + BPF进行动态追踪。然而,golang与C语言相比,又有微妙的不同之处,这里用go 1.19 + x86环境为例子:

  1. Golang ABI与C语言相比,C语言函数只使用寄存器传递参数。然而golang不确定使用寄存器还是使用栈来传递函数参数(要么用寄存器,要么用栈传递,不会混合),编译器有相关算法来决定,具体算法规则可以参考golang源码的src/cmd/compile/internal-abi.md文档。golang函数传递参数的方式的不同,这意味着使用bpftrace动态追踪时,获取被追踪函数的参数,需要先确认它是利用寄存器传参,还是通过栈传参,然后调用不同的bpftace函数来获取。我个人的方法是查看该golang函数的汇编语言,人工判断。目前还没想出更智能的办法。
  2. Golang原生支持协程,它的函数栈与C语言相比,初始非常小,并且随着栈深度增加,就会发生栈扩张,这会把旧的栈复制到更大的新栈内存中。golang这一特性,意味着在C语言中,稳定使用的对函数返回的uretprobe动态追踪技术(需要修改函数栈),直接应用在golang中,当发生栈扩张时,就会导致程序错误。因此,想要正确追踪golang函数的返回,只能遍历函数的汇编代码,跟踪所有ret汇编指令。
  3. Golang的函数参数个数和顺序,不能从字面来判断。编译器会可能会进行改写。比如字符串引用,golang会把它扩张成地址和长度两个参数。目前,笔者还没有找到很好的方法可以找到改写后的参数列表。
  4. Golang所有函数都在协程中运行,这意味着函数一次执行过程中,可能会从A线程切换到B线程。而C语言的函数,在一次执行中,只会在一个线程中,直到执行完成。这意味着golang函数执行的上下文,不能用线程号来唯一的确定。幸运的是,我们依旧有办法唯一的标识golang协程上线文。每个golang协程,都有type gobuf struct {...} 实例表示。追踪 rumtime/proc.go:execute(gp *g, inheritTime bool) 函数,记录下*g,这就可以唯一的标记当前协程上下文。
  5. Golang函数符号,可能会带有(,*之类符号。bpftrace对这类情况支持并不太好,或许之后会解决。目前我们可以用地址来替换函数名,绕过这个问题。

02 实现

>>> 开源项目

https://github.com/stevenjohnstone/go-bpf-gen

很好的解决了BPF在golang中的适配问题。我们聚焦在latency.bt 脚本上,它可以测量任意golang函数的调用时延的直方图。

>>> 编译 

go build

>>> 生成对特定函数的测量脚本

./go-bpf-gen templates/latency.bt ../../matrixone/mo-service symbol="github.com/matrixorigin/matrixone/pkg/logservice.(*managedClient).Append"
BEGIN {                                                                                                                                                                                                                                                                                                                    
  printf("Hit CTRL+C to end profiling\n");                                                                                                                                                                                                                                                                                 
}                                                                                                                                                                                                                                                                                                                          
 
 
uprobe:/home/jinqinghui/matrixone/mo-service:runtime.execute {                                                                                                                                                                                                                                                             
        // map thread id to goroutine id                                                                                                                                                                                                                                                                                   
        @gids[tid] = reg("ax")                                                                                                                                                                                                                                                                                             
}                                                                                                                                                                                                                                                                                                                          
 
tracepoint:sched:sched_process_exit {                                                                                                                                                                                                                                                                                      
  delete(@gids[tid]);                                                                                                                                                                                                                                                                                                      
}                                                                                                                                                                                                                                                                                                                          
 
 
 
 
uprobe:/home/jinqinghui/matrixone/mo-service:"github.com/matrixorigin/matrixone/pkg/logservice.(*managedClient).Append" {                                                                                                                                                                                                  
        $gid = @gids[tid];                                                                                                                                                                                                                                                                                                 
        @start0[$gid, pid] = nsecs;                                                                                                                                                                                                                                                                                        
}                                                                                                                                                                                                                                                                                                                          
 
 
uprobe:/home/jinqinghui/matrixone/mo-service:"github.com/matrixorigin/matrixone/pkg/logservice.(*managedClient).Append" + 273,                                                                                                                                                                                             
uprobe:/home/jinqinghui/matrixone/mo-service:"github.com/matrixorigin/matrixone/pkg/logservice.(*managedClient).Append" + 291 {                                                                                                                                                                                            
        $gid = @gids[tid];                                                                                                                                                                                                                                                                                                 
        @durations["github.com/matrixorigin/matrixone/pkg/logservice.(*managedClient).Append"] = hist((nsecs - @start0[$gid, pid])/1000000);                                                                                                                                                                               
        delete(@start0[$gid, pid]);                                                                                                                                                                                                                                                                                        
}                                                         

该脚本的关键点:

  • 函数调用上线文的获取:trace runtime.execute()。该函数是golang运行时,用于调度协程的分派函数。通过跟踪参数,可以知道当前线程上正在调度的golang协程标识。
  • 函数参数的获取:runtime.execute()在golang 1.19中是通过寄存器传递。可以通过查看汇编确定。
  • Golang函数的返回点:遍历二进制,找到该函数的ret指令偏移量,然后和函数起始位置相加。有多少ret指令,就需要跟踪多少次。
  • 通过记录函数进入时间和返回时间,计算出时延,最后保存在直方图树结构里。

>>> BUG 

遗憾的是,该脚本在最新bpftrace中,并不能正确执行。原因在于被追踪函数含有"(" 目前bpftrace支持不了。替代方案,人工把函数名,用二进制中函数的具体地址替换即可。需要人工计算。

MO将基于go-bpf-gen项目进行改进——生成脚本时自动用函数地址替换函数名。避免人工查看汇编而带来的不便。

ebpf初探,动态追踪golang程序 文章内容基本上是https://blog.pixielabs.ai/ebpf-function-tracing/post/内容的复刻,对细节进行了补充完善 阅读详情

相关推荐

BPF: the universal in-kernel virtual machine

本文探讨了Linux内核中BPF(Berkeley Packet Filter)技术的演进与应用扩展。BPF最初作为网络数据包过滤工具,经过多次迭代已发展为支持JIT编译、64位寄存器和内核函数调用的通用虚拟机(internal BPF)。3.15内核将其分为经典BPF和内部BPF两个版本,后者性能显著提升且正向网络子系统外扩展,如用于安全计算(seccomp)和追踪过滤(tracepoint)。Alexei Starovoitov的补丁显示,用BPF替代原有追踪过滤器可使执行效率提升20倍以上。虽然当前B

mounter625的专栏 6990

Twilio Video React App故障排除手册:常见问题与解决方案的完整清单

Twilio Video React App是一款基于twilio-video.js SDK和React.js构建的协作应用,为用户提供高质量的视频会议体验。本手册将帮助你解决使用过程中可能遇到的各类问题,确保视频会议顺畅进行。 ## 连接问题排查 ### 无效令牌错误(Error Code: 20101) 当应用显示"Invalid token error"时,通常是由于身份验证令牌不正确导

gitblog_00093的博客 702

eunomia-bpf 和 wasm-bpf 项目的 3 月进展

Wasm-bpf 项目还添加了 Rust 语言的 Guest SDK 支持,并尝试添加了 Go 语言的 Guest SDK 支持。此外,该项目进行了一系列的修复和文档重构,并完善了 CI 和测试等内容。该项目还尝试使用 Wasm 组件模型,并添加了一个工具,用于添加在 wasm 模块中定义的表导出。ecli 程序也进行了完全的重写,现在是使用 Rust 编写的,已经取代了原本使用 C++ 编写的版本。该项目还更新了教程文档,使其更易于使用。在过去一个月中,该项目取得了一些新的进展,以下是这些进展的概述。

云微的blog 847

探索高效监控:gobpf——Go语言的eBPF库

[gobpf](https://github.com/iovisor/gobpf) 是一个强大的开源项目,为Go开发者提供了[BCC](https://github.com/iovisor/bcc)框架的接口以及加载和执行eBPF(Extended Berkeley Packet Filter)程序的低级工具。它允许你利用Linux内核的强大功能来实现细粒度的系统监控和性能优化。 ## 项目技术...

gitblog_00833的博客 685

8、BPF 追踪技术:从内核到用户空间的全面解析

本文全面解析了BPF追踪技术,从内核到用户空间的多种追踪方法,包括内核探针(kprobes)、追踪点(tracepoints)、用户空间探针(uprobes/uretprobes)和用户静态定义追踪点(USDTs)。文章详细介绍了这些追踪技术的基本原理、使用方法和适用场景,并通过实际代码示例展示了如何应用这些技术进行系统监控和性能分析。此外,还探讨了BPF追踪技术在不同编程语言中的应用优势,以及其在未来云计算、安全防护和人工智能等领域的广阔发展前景。

embedding5hiker的博客 69

go-seccomp-bpf:Go库,用于安装seccomp BPF系统调用过滤器

go-seccomp-bpf go-seccomp-bpf是Go(golang)的库,用于通过使用安全计算模式(也称为seccomp)在Linux 3.17及更高版本上加载系统调用筛选器。 Seccomp限制进程可以调用的系统调用。 内核公开了大多数进程不使用的大量系统调用。 通过安装seccomp过滤器,您可以限制暴露给进程的内核总数(最低特权原则)。 这样可以最大程度地减少过程中可能发现的未知漏洞的影响。 过滤器表示为Berkeley数据包过滤器(BPF)程序。 BPF程序是根据您创建的过滤策略生成的。 要求 需要Linux 3.17,因为它使用seccomp syscall以便利用SECCOMP_FILTER_FLAG_TSYNC标志将过滤器同步到所有线程。 特征 纯Go,并且没有libseccomp依赖项。 过滤器是可自定义的,可以写入白名单或黑名单。 使用SECCOMP

Golang BCC开发学习笔记--Hello world

BCC小demo系列 实现效果: 有文件打开时,打印hello world bpf中常用bpf_trace_printk 来完成print操作,该函数会将需要打印的内容输出到trace_pipe中 BPF程序注入到内核hook点后,当hook的系统函数被调用时,BPF程序就会被执行。 package main import ( "fmt" bpf "github.com/iovisor/gobpf/bcc" "os" ) import "C" const source string = ` .

susu_xi的博客 980

gobpf:进行绑定以创建BPF程序

哥布 该存储库提供了go绑定以及用于从.elf文件加载和使用eBPF程序的低级例程。 非常欢迎您的投入和贡献。 我们建议销售gobpf并固定其版本,因为在密件抄送和Linux更新和发行之后,API会定期更改。 要求 eBPF需要最新Linux内核。 一个很好的功能列表可以在这里找到: : github.com/iovisor/gobpf/bcc 安装最新发行的版本(使用软件包管理器或通过源代码构建)。 github.com/iovisor/gobpf/elf 建立ELF对象文件 要构建与elf软件包一起使用的ELF对象文件,必须使用特定的部分( SEC("...") )。 当前支持以下内容: kprobe/... cgroup/skb cgroup/sock maps/... socket... tracepoint... uprobe/... uretprob

动态追踪技术(四):基于 Linux bcc/BPF 实现 Go 程序动态追踪

2019独角兽企业重金招聘Python工程师标准>>> ...

weixin_33729196的博客 758

Go-一个框架用于运行带规则的BPF程序在Linux上作为daemon存在

Framework for running BPF programs with rules on Linux as a daemon. Container aware.

半监督的医学分割数据集(LA, Pancreas, ACDC和PROMISE12)分享

这篇文章提供了四个常用的半监督医学分割数据集,所有数据集都已经被处理成了.h5文件。下载之后就可以直接使用

qq_45691955的博客 4180

【亲测免费】 tcptracer-bpf:高效追踪TCP事件的eBPF工具

tcptracer-bpf:高效追踪TCP事件的eBPF工具 项目介绍 tcptracer-bpf 是一个基于eBPF(Extended Berkeley Packet Filter)技术的开源项目,专注于追踪TCP事件,如连接(connect)、接受(accept)和关闭(close)。该项目通过使用kprobes技术,能够在不依赖特定内核版本或配置的情况下,动态适应正在运行的内核。tcptra...

gitblog_01018的博客 561

VINS技术路线与代码详解

VINS技术路线 写在前面:本文整和自己的思路,希望对学习VINS或者VIO的同学有所帮助,如果你觉得文章写的对你的理解有一点帮助,可以推荐给周围的小伙伴们,当然,如果你有任何问题想要交流,欢迎随时探讨。话不多说,下面上正文。 VINS代码地址:https://github.com/HKUST-Aerial-Robotics/VINS-Mono 参考文档:1.VINS-Mono A R...

wangshuailpp的博客 7万+

动态追踪观测:基于 eBPF Uprobe 与 Go runtime 的用户态函数延迟统计

将 eBPF Uprobe 与 Go 程序的静态符号表分析结合,可以在不调整业务逻辑的前提下采集底层函数的调用耗时。为了避免 Go 运行时特有的动态栈对 Uretprobe 造成破坏,最佳方案是在内核态跟踪 Goroutine 的状态转换,并在函数所有的退出指令处放置拦截探针。这类零侵入式流量跟踪方法为高并发云原生组件的精细化性能分析提供了新的可能性。

m0_50889382的博客 5707

将赛灵思 SDK 工程移植到 Vitis 的分步指南

注:本文转自赛灵思中文社区论坛,源文链接在此。本文原作者为XILINX工程师。 以下为个人译文,仅供参考,如有疏漏之处,还请不吝赐教。 您是赛灵思软件开发套件 (SDK) 用户吗? 您是否正在计划将自己的工程移植到 Vitis 软件平台? 那么本篇工程移植分步指南将能祝您一臂之力。 从 2019.2 版开始,赛灵思 SDK 开发环境已统一整合到全功能一体化的 Vitis™ 统一软件平台中。 尊敬的 SDK 用户,您只需轻点几下鼠标即可体验这一功能丰富的工具! 马上开始将工程从赛灵思 S

weixin_45825101的博客 1733

【vue看板】

大屏看板提示:以下是本篇文章正文内容,下面案例可供参考提示:这里对文章进行总结:代码量较多,看板中的。

weixin_46223284的博客 2772

探索TCP事件跟踪新境界:tcptracer-bpf

探索TCP事件跟踪新境界:tcptracer-bpf tcptracer-bpf是一个强大的eBPF(Extended Berkeley Packet Filter)程序,利用内核探针(kprobes)来追踪TCP连接、接受和关闭等关键事件。这个项目被编译为一个ELF对象文件,并提供了一个简单的Go库接口,用于加载和管理ELF对象。 项目介绍 tcptracer-bpf的独特之处在于其无需依赖特定...

gitblog_00074的博客 564

大数据领域存算分离:教育行业的数据应用探索

在当今数字化时代,教育行业积累了海量的数据,如学生的学习记录、考试成绩、在线学习行为等。如何高效地存储、管理和分析这些数据,以挖掘其潜在价值,为教育决策、教学改进和学生个性化发展提供支持,成为教育行业面临的重要挑战。大数据领域的存算分离技术为解决这一问题提供了新的思路和方法。本文旨在探索存算分离技术在教育行业的数据应用,分析其原理、优势和实际应用场景,为教育行业的数据管理和分析提供参考。本文共分为十个部分。第一部分介绍背景,包括目的和范围、预期读者、文档结构概述和术语表;

AGI×大数据,开启智能时代的认知跃迁;解码AGI,赋能数据驱动的智能革命。 815

eBPF监控工具bcc系列八BPF C

在之前的bcc代码中我们知道其程序是分为两部分的,一部分是C语言,另一部分是基于Python的。本篇是关于C语言部分的。 1. 事件和参数 1.1 kprobes 使用kprobe的语法是: kprobe__kernel_function_name 其中kprobe__是前缀,用于给内核函数创建一个kprobe(内核函数调用的动态跟...

weixin_34409822的博客 2804

【VibeCoding系列】小白入门指南:模型选择、编辑器选择、项目预定全分析

开箱即用:下载安装就能用,不用配 API Key(也可以自己配)Cmd+K 快速修改:选中代码,按 Cmd+K,直接让 AI 修改Cmd+L 对话:侧边栏聊天,支持引用文件和代码@文件引用:输入 @ 就能引用项目中的文件,AI 自动读取上下文Agent 模式:告诉 AI 一个需求,它自动跨文件修改下载地址cursor.com(免费版有额度限制,足够入门)✅下载 Cursorcursor.com),安装打开✅选一个小项目(个人名片页 or Todo List)✅按照七步工作流开干。

十年老程序猿,AI 疯狂研究专家,前沿硬核知识分享 214

Lammps模拟剪切作用下的位错滑移

Lammps模拟位错与障碍物作用。在lammps中实现周期性位错阵列模型模拟剪切作用下1/2刃位错与障碍物的交互作用

Rockyoung 3461

动态追踪技术思想及应用

动态追踪技术思想及应用 分享的议题 今天我要分享的议题 :偏向于思想和应用;对于底层技术原理,由于本人也在学习中,还没有深入了解,相关资料可以 咨询 内核组(老司机)、网上资料等。 简介 静态追踪: log、metric、stats; 一个简单的示例:调试test 函数中是否存在参数为0 的场景: testDtrace.c: void test(int i) { // do sth if (i == 0) { printf("get i == 0\n"); // 当 i == 0 时,日

qq_39015563的博客 810

泛微OA自定义post接口和路径(无侵入)

泛微OA自定义post接口和路径(无侵入)

技术分享 3876

BPF简介,以及使用kubectl trace插件为kubernetes集群运行BPF程序

转载自Linux内核之旅0x00 文章楔子BPF(BerkeleyPacketFilter)本质上是一个运行在内核态的虚拟机,使得用户可以在内核中加载运行自定义程序的技术,其构想在1...

黑光技术 878

ET2012新版免狗免安装绿色软件

ET2012新版免狗免安祖昂绿色软件。,新界面,新视觉,

RU DOS版、EFI版32bit/64bit BIOS常用工具

DOS版、EFI版的RU工具,可用来查看IO、MEM、PCI、SMBIOS等资源。亲测在EFI SHELL下可用。

上一篇: MatrixOne从入门到实践07——MO-Tester
下一篇: MatrixOne 0.6.0 :首个云原生架构的HTAP数据库发布!
MatrixOrigin
博客等级 码龄5年 708粉丝 230原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值