专栏名称: 阿里开发者
阿里巴巴官方技术号,关于阿里的技术创新均将呈现于此
目录
相关文章推荐
汽车最前线  ·  最低9万不到,综合续航1300km+,这些国 ... ·  14 小时前  
汽车商业评论  ·  健忘的汽车设计 ·  4 天前  
51好读  ›  专栏  ›  阿里开发者

谈谈优雅的钩子--bpftrace

阿里开发者  · 公众号  ·  · 2024-05-19 20:12

正文

阿里妹导读


bpftrace是一个内核跟踪工具,简单来说就是在函数上挂个钩子,挂上钩子后就可以将函数的入参和返回值取出来再放入程序进行二次编程,最终能让程序按照我们的意图来对函数进行观测。

一、引言

C语言挂科可以说是一辈子的耻辱,走在路上都感觉有人在小声议论:“哎,就是他,那个人C语言挂过科”。这也是我一直不敢碰内核的原因,但如今时代不一样了,有了AI的帮助,看源码会相对容易一些,我们这些学渣也能摸一摸内核了。

二、理解概念


1、内核空间和用户空间

入职面试的时候,背诵了一些内核调优的参数和场景,希望在面试过程中加分。
我自信满满:“平时我还会对系统进行一些内核参数的调优”。
正常面试官可能发问:“那你调过哪些内核参数”。我都准备开始背诵了。
结果面试官问:“那你说下啥是内核空间,啥是用户空间。”
我:“。。。。。。”
请看看通义的解释:


2、系统调用

请看通义的解释*2:


在对内核空间、用户空间和系统调用有一个大概认知后,我们再去学习内核知识会更容易。直接阅读源码肯定是低效的,通过具体的问题切入是最快的,而分析内核的就少不了工具,说了半天终于说到了我们今天的主角:bpftrace。

三、语法学习

bpftrace是一个内核跟踪工具,简单来说就是在函数上挂个钩子,挂上钩子后就可以将函数的入参和返回值取出来再放入程序进行二次编程,最终能让程序按照我们的意图来对函数进行观测。既然涉及编程就会有语法,这里我们罗列一些必要的语法,想了解更全面的语法请移步:https://github.com/bpftrace/bpftrace/blob/master/man/adoc/bpftrace.adoc
内核的函数不是所有都可以支持挂钩子的,哪一些可以挂可以通过-l参数来列举,同时还支持*模糊查询:

这里可以看到我写了tracepoint和kprobe两个关键字,用过其他内核跟踪工具的同学应该对他俩很熟悉。tracepoint是系统开发人员在编写内核函数时就已经预留好的钩子,kprobe是动态挂钩,也可以理解成临时挂钩,即使tracepoint没有对这个函数预留钩子,kprobe一般也都能在这个函数上进行挂钩。相比于kprobe,tracepoint虽然范围更小但却更安全,且tracepoint在观测系统调用时更方便。一般有kprobe,会对应一个kretprobe,kprobe是在函数入参的时候挂钩子,kretprobe是在函数返回值的时候挂钩子。

bpftrace语法结构分三部分:

(1)、kprobe:__vmalloc:这个模块是表明钩子挂在哪个函数上。
(2)、/comm=="ping"/:这个模块是过滤器,这里过滤的是进程名为ping的信息,comm是bpftrace的关键字,还有其他很多关键字。
(3)、{printf("%d\n",arg0);}:这里arg0表示函数传入的第一个参数。
直接添加-e参数是可以编写一行代码,但如果遇到需要编写多行复杂程序时就不好操作了,这时候将代码写到.bt文件中会更方便:
BEGIN是在钩子追踪前发生的,END是追踪后发生的,为什么中间的“第二行代码”会打印多次呢,这是因为每调用一次vfs_read就会执行一次printf:
bpftrace可以使用kstack关键字打印堆栈:
bpftrace可以使用ntop()函数将十进制IP地址数字转换成点分十进制IP地址:

四、应用场景

学了两三个语法规则就可以上手试试了,别等到语法全部学完,那样很容易中途放弃。


1、内核网络丢包怎么定位原因?

网络是一个大方向,科班出身的网工都是从网络协议开始学,然后再学到数据中心,最后可能学网络架构。大部分网工的成长历程都不涉及内核网络,主要也是内核网络学习成本较高。网络丢包是常见的问题,有经验的网工都可以解决网络设备间的丢包,但当数据包确定是丢在linux操作系统内时,网工就比较发愁了,接下来我们就通过bpftrace解决这一问题,中途你定会有所收获。
开始之前我们要先树立一个目标,既然是定位丢包原因,那从内核层面上看,肯定要定位丢在哪个函数里面,所以我们的定的初步回显目标大概是这样的:
丢包在xx函数;
丢包在xxx函数;
丢包在xxxx函数;
如果只是丢几个数据包,这样显示没问题,但如果数据量大就不好找想要的结果了,所以至少要有一个辅助搜索的索引,最好的方法就是加上源目IP,基于此我们再细化下回显目标:
192.168.1.1->192.168.2.2 丢包在xx函数
这样显示就很清晰了,也方便搜索。接下来我们只需要用bpftrace把丢包的源模目IP和丢包函数展示出来即可。
拿源目IP前先引入三个概念:skb、comsume_skb和kfree_skb。skb是内核中存网络数据最基本的数据结构,这个数据是存在内存,存在内存就一定要释放,不然就会把系统内存打爆。释放skb的方式有两种,一种是正常的包通过comsume_skb释放,另一种就是丢包通过kfree_skb释放,所以我们要查丢包就聚焦在kfree_skb。
首先kfree_skb这个函数没有返回值,只有入参,传入的是skb。这个skb里面能看到哪些有用的信息呢?我们去翻源码会发现里面的信息太多了,不要被它迷惑,保持初心,我们就是来找源目IP而已。基于此我们找到了head和network_header,head指向的是整个数据包缓冲区的起始地址,是一个绝对地址。但network_header存储的是网络层头部相对于skb->head的偏移量(offset),是一个相对地址。当我们想拿到网络层头部的信息时,我们需要拿到它的绝对地址,也就是head这个绝对地址加上network_header这个相对地址(即head+network),如果要放到skb结构体中去套用就是skb->head + skb->network_header,获取到了网络层头部就能拿到源目IP了。
我们可以打印出来看看(struct iphdr在 里面,struct sk_buff在 里面,in.h请忽略):
打印出来怎么是一串数字,不是我们想要的IP地址,难道哪里搞错了,这时候我们就要进一步看下iphdr这个结构体了:
的确是有源目IP地址的,只不过是其中的一部分,我们需要准确的取出来,而不是一股脑全部显示出来,显示出来的其实也是数字,要转换成点分十进制需要用到ntop:
这样显示源目IP就对了:
接下来就只需要把kstack加进去就行了:






请到「今天看啥」查看全文