请选择 进入手机版 | 继续访问电脑版

第一次使用Linux内核的Tracepoint的体验

[复制链接]
谢世民 发表于 2021-1-2 19:45:13 | 显示全部楼层 |阅读模式 打印 上一主题 下一主题
我并不以为丢人,一点也不。
我是说我工作这么多年做和Linux内核相关的事,竟然在上上周才第一次使用tracepoint。
这并不希奇,我不会的东西还多着呢,比方说,我一直强调的,我不会编程,我也不会用git。
言归正传,如果这么多年我都没用过tracepoint,那么作为替代,我用什么呢?
如果我调试内核大概调试内核模块,最最最常用的方法就是在代码里加一条printk(如果是用户态步伐,我就加一条printf。),我来告诉你来由:


  • printk/printf 不需要安装任何别的依赖包。
  • printk/printf 随手可用,不需要写太多为了重用而过分设计的代码。
  • printk/printf 对不会编程的人及其友好。

所以,我钟爱printk。我不喜欢gdp/crash tool,也不喜欢kprobe/systemtap,更不喜欢bpftrace,固然,我很懂这些东西,只是我以为它们太复杂,需要影象的语法太多,就像一个无家可归的流浪汉,不喜欢携带任何负担而已。
扯了这么多,这和tracepoint有毛关系。
有关系,因为我发现在需要添加一条printk的地方添加一个tracepoint工作量差不多,且效果更好。tracepoint更像是添加了一个HOOK,你可以在外面自界说你要做什么,而不但仅拘泥于printk打印一条消息。
使用tracepoint,如果你只是想到达printk的效果,你可以在/sys/kernel/debug/tracing/trace看到你要打印的信息,如果你还想做点别的,你还可以用eBPF。
雷同《史记》太史公曰形而上学的部门在本文最后,现在先给点实际的东西。
在内核代码添加一个tracepoint简单到不必说,只是需要重新编译内核,我给一个在模块里添加tracepoint的Howto吧。
首先给出我要添加tracepoit的内核模块代码:
  1. #include #include #include #define IPPROTO_MYPROTO  123int myproto_rcv(struct sk_buff *skb){    struct udphdr *uh;    struct iphdr *iph;    iph = ip_hdr(skb);    uh = udp_hdr(skb);    printk("proto 123\n");    kfree_skb(skb);    return 0;}static const struct net_protocol myproto_protocol = {    .handler = myproto_rcv,    .no_policy = 1,    .netns_ok = 1,};int init_module(void){    int ret = 0;    ret = inet_add_protocol(&myproto_protocol, IPPROTO_MYPROTO);    if (ret) {        printk("failed\n");        return ret;    }    return 0;}void cleanup_module(void){    inet_del_protocol(&myproto_protocol, IPPROTO_MYPROTO);}int init_module(void);void cleanup_module(void);MODULE_LICENSE("GPL");
复制代码
很现实的例子,这个代码注册了一个和TCP,UDP平级的四层协议,但实际上就是UDP,只是协议号改为了123,我们的预期是当收到这样的协议包时,printk打印出端口。
我现在想用tracepoint替代printk,该怎么办呢?下面是方法。下面是新的代码:
  1. /* * 注意: * 1. CREATE_TRACE_POINTS 必须define * 2. CREATE_TRACE_POINTS 必须在头文件之前define */#define CREATE_TRACE_POINTS#include "test_tp.h"#include #include #include #define IPPROTO_MYPROTO  123int myproto_rcv(struct sk_buff *skb){    struct udphdr *uh;    struct iphdr *iph;    iph = ip_hdr(skb);    uh = udp_hdr(skb);    // 这里增加一个tracepoint点    trace_myprot_port(uh->dest, uh->source);    kfree_skb(skb);    return 0;}static const struct net_protocol myproto_protocol = {    .handler = myproto_rcv,    .no_policy = 1,    .netns_ok = 1,};int init_module(void){    int ret = 0;    ret = inet_add_protocol(&myproto_protocol, IPPROTO_MYPROTO);    if (ret) {        printk("failed\n");        return ret;    }    return 0;}void cleanup_module(void){    inet_del_protocol(&myproto_protocol, IPPROTO_MYPROTO);}int init_module(void);void cleanup_module(void);MODULE_LICENSE("GPL");
复制代码
增加了一个头文件引用而且增加了一个宏界说,别的什么都没变,是不是很简单呢。
所有和tracepoint相关的元数据界说都在头文件里:
  1. // test_tp.h#undef TRACE_SYSTEM#define TRACE_SYSTEM myprot#if !defined(_TEST_TRACE_H) || defined(TRACE_HEADER_MULTI_READ)#define _TEST_TRACE_H#include // tracepoint的界说就在这里了。可用的参数都在这里,日后debugfs输出的就是按照下面这个TP_printk的格式来的。// 如果eBPF来输出,依然要遵循这个格式取参数,下面有例子。TRACE_EVENT(myprot_port,    TP_PROTO(unsigned short dest, unsigned short source),    TP_ARGS(dest, source),    TP_STRUCT__entry(        __field(unsigned short, dest)        __field(unsigned short, source)    ),TP_fast_assign(    __entry->dest = dest;    __entry->source = source;),TP_printk("dest:%d, source:%d", __entry->dest, __entry->source));#endif/* * 需要添加以下这一坨 * 由于模块不能修改内核头文件,因此我们需要在自己的目次放头文件, * TRACE_INCLUDE_PATH 必须重新界说,而不是仅仅在内核头文件目次寻找界说。 * * 为了支持这个重界说,Makefile中必须包罗: * CFLAGS_myprot.o = -I$(src) * */#undef TRACE_INCLUDE_PATH#define TRACE_INCLUDE_PATH .#define TRACE_INCLUDE_FILE test_tp // 这就是该头文件的名字#include
复制代码
接下来是Makefile,我们只需要注意第二行:
  1. obj-m += test.o# CFLAGS_xx.o 此中 xx 为内核模块的名字CFLAGS_test.o = -DDEBUG -I$(src)
复制代码
OK,编译模块,加载之。
首先我们开启该tracepoint:
  1. echo 1 >/sys/kernel/debug/tracing/events/myprot/enable
复制代码
此时,通过raw socket发送一个协议为123的报文,我们会在debugfs看到输出:
  1. cat /sys/kernel/debug/tracing/trace# tracer: nop## entries-in-buffer/entries-written: 1/1   #P:4##                                _-----=> irqs-off#                               / _----=> need-resched#                              | / _---=> hardirq/softirq#                              || / _--=> preempt-depth#                              ||| /     delay#           TASK-PID     CPU#  ||||   TIMESTAMP  FUNCTION#              | |         |   ||||      |         |     ksoftirqd/1-16      [001] ..s. 174074.362291: myprot_port: dest:36895, source:53764
复制代码
固然,这只是模拟了printk,这只不外是把printk统一到了一个地方而已,没什么大不了的。tracepoint更有意义的是,它可以挂载eBPF步伐。现在我们可以试一下:
  1. bpftrace -l|grep tracepoint.*myprottracepoint:myprot:myprot_port
复制代码
我们可以用bpftrace简单debug一下:
  1. #!/usr/bin/bpftracetracepoint:myprot:myprot_port{        $dest = args->dest;        $source = args->source;        printf("bpftrace:dest:%d  source:%d\n",                ($source & 0xff) > 8,                ($dest & 0xff) > 8);}
复制代码
运行之,发包:
  1. ./test_bpf_tp.tpAttaching 1 probe...bpftrace:dest:1234  source:8080
复制代码
唯一要吐槽的就是为什么像ntohs这样常用的接口竟然没有builtin!
tracepoint和kprobe/kretprobe相比,优缺点分别是什么呢?为什么我不喜欢kprobe这种,我甚至不喜欢live patch,但这又是为什么。
tracepoint的唯一缺点就是它是静态的,它需要通过写代码的方式静态插入到源码的特定位置,然后重新编译之后执行才华生效,与此相反,kprobe是动态的,它在运行时就可以将HOOK动态attach到特定的位置,但很不幸,这是kprobe唯一的优点。
在任何人眼前,我都透暴露对kprobe的不满,原因和我对live patch的不满一样,那就是, 为了动态插入哪怕一句printk,维护kprobe/live patch框架的代码执行资本太高了!
你去跟踪一下ftrace框架下调用live patch新函数的代码路径有多长,你去跟踪一下krpobe/kretprobe为了执行handler,做了多少额外的事,我需要的只是handler,而handler里只是printk一些变量的值而已,我不想为了这么小的事去大动干戈,如果调用handler的路径上再遭遇一把spinlock,那事情更会及其尴尬,为了调试性能瓶颈而引入的新瓶颈高出了最初的性能瓶颈自己。在性能优化这么精致的动作上,这种 测禁绝效应 极为掣肘!
我是手艺人,我对kprobe,live patch这种在我看来完全不可控的东西很难信任。固然我也不在乎那些可以熟练操纵这些工具的人的讽刺,我有自己的办法。
如果我想patch一个函数,大概说我想在某处插入一条printk,我会手工去做,很简单,直接在特定位置修改代码为call myhandler即可,当myhandler返回,重新执行被替换的指令,这种方式非常简单且直接,我信任这种方式,当我找到了这种方式而且可以熟练运用的时候,在我看来,tracepoint和kprobe也就没有本质的区别了。
忘掉什么是kprobe,live patch,忘掉tracepoint大概比记得它们更好。
招手拦出租车或比手机打滴滴更轻便,同样,使用纸币支付也无需担心电池续航以及隐私问题…
  浙江温州皮鞋湿,下雨进水不会胖。

来源:https://blog.csdn.net/dog250/article/details/112058189
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则


专注素材教程免费分享
全国免费热线电话

18768367769

周一至周日9:00-23:00

反馈建议

27428564@qq.com 在线QQ咨询

扫描二维码关注我们

Powered by Discuz! X3.4© 2001-2013 Comsenz Inc.( 蜀ICP备2021001884号-1 )