Linux 调试之 TRACE_EVENT
创始人
2024-04-23 19:47:49
0

文章目录

  • 前言
  • 一、TRACE_EVENT简介
  • 二、TRACE_EVENT() 结构
    • 2.1 TRACE_EVENT简介
    • 2.2 trace_sched_switch示例
  • 三、The header file
  • 参考资料

前言

在Linux的整个历史中,人们一直希望在内核中添加静态跟踪点,即记录内核中特定位置的数据以供以后检索的函数。与Ftrace函数跟踪程序不同,跟踪点可以记录的不仅仅是输入的函数。跟踪点可以记录函数的局部变量。随着时间的推移,已经尝试了各种添加跟踪点的策略,TRACE_EVENT()宏是添加内核跟踪点的最新方法。

一、TRACE_EVENT简介

trace markers:添加一个非常低的开销 tracer hook ,尽管trace markers通过使用精心制作的宏解决了性能问题,但跟踪标记将记录的信息以printf格式嵌入核心内核中的位置,但是这让核心内核代码看起来像是分散在各处的调试代码。

tracepoints:跟踪点在内核代码中包含一个函数调用,当启用该函数时,将调用一个回调函数,将跟踪点的参数传递给该函数,就像使用这些参数调用回调函数一样。这比trace markers要好得多,因为它允许传递回调函数可以取消引用的类型转换指针,而不是标记接口,trace markers需要回调函数解析字符串。使用 tracepoints ,回调函数可以有效地从结构中获取所需的任何内容。

尽管这是对跟踪标记的改进,但对于开发人员来说,为他们想要添加的每个跟踪点创建回调,以便跟踪程序输出其数据仍然太过乏味。内核需要一种更自动化的方式来将跟踪程序连接到跟踪点。这将需要自动创建回调并格式化其数据,就像跟踪标记所做的那样,但应该在回调中完成,而不是在内核代码中的跟踪点位置。

为了解决自动跟踪点的问题,TRACE_EVENT()宏应运而生,该宏专门用于允许开发人员向其子系统添加跟踪点,并使Ftrace能够自动跟踪它们。开发人员不需要了解Ftrace的工作原理,他们只需要使用TRACE_EVENT()宏创建跟踪点。此外,他们需要遵循一些指南来创建头文件,这样他们就可以完全访问Ftrace跟踪程序。TRACE_EVENT()宏设计的另一个目的是不将其耦合到Ftrace或任何其他跟踪器。它对使用它的跟踪程序是不可知的,现在很明显,perf、LTTng和SystemTap也使用了TRACE_EVENT()。

二、TRACE_EVENT() 结构

2.1 TRACE_EVENT简介

自动跟踪点具有必须满足的各种要求:
(1)必须创建一个可以放置在内核代码中的跟踪点。
(2)必须创建一个可以挂接到该跟踪点的回调函数。
(3)回调函数必须能够以最快的方式将传递给它的数据记录到跟踪环缓冲区中。
(4)必须创建一个函数,该函数可以解析记录到环形缓冲区中的数据,并将其转换为跟踪程序可以向用户显示的人类可读格式。

为了实现上述要求,TRACE_EVENT()宏被分为六个组件,它们对应于宏的参数:

#define TRACE_EVENT(name, proto, args, struct, assign, print)	\DECLARE_TRACE(name, PARAMS(proto), PARAMS(args))

name:要创建的跟踪点的名称。
prototype:跟踪点回调的原型
args:与原型匹配的参数。
struct:跟踪程序可以使用(但不需要)存储传递到跟踪点的数据的结构。
assign:已类似于 C-like 的方式 将数据分配给结构。
print:以可读的ASCII格式输出结构的方法。

比如:sched_switch的跟踪点。

2.2 trace_sched_switch示例

下面将使用该定义来描述TRACE_EVENT()宏的每个部分。

// linux-3.10/include/trace/events/sched.h/** Tracepoint for task switches, performed by the scheduler:*/
TRACE_EVENT(sched_switch,TP_PROTO(struct task_struct *prev,struct task_struct *next),TP_ARGS(prev, next),TP_STRUCT__entry(__array(	char,	prev_comm,	TASK_COMM_LEN	)__field(	pid_t,	prev_pid			)__field(	int,	prev_prio			)__field(	long,	prev_state			)__array(	char,	next_comm,	TASK_COMM_LEN	)__field(	pid_t,	next_pid			)__field(	int,	next_prio			)),TP_fast_assign(memcpy(__entry->next_comm, next->comm, TASK_COMM_LEN);__entry->prev_pid	= prev->pid;__entry->prev_prio	= prev->prio;__entry->prev_state	= __trace_sched_switch_state(prev);memcpy(__entry->prev_comm, prev->comm, TASK_COMM_LEN);__entry->next_pid	= next->pid;__entry->next_prio	= next->prio;),TP_printk("prev_comm=%s prev_pid=%d prev_prio=%d prev_state=%s%s ==> next_comm=%s next_pid=%d next_prio=%d",__entry->prev_comm, __entry->prev_pid, __entry->prev_prio,__entry->prev_state & (TASK_STATE_MAX-1) ?__print_flags(__entry->prev_state & (TASK_STATE_MAX-1), "|",{ 1, "S"} , { 2, "D" }, { 4, "T" }, { 8, "t" },{ 16, "Z" }, { 32, "X" }, { 64, "x" },{ 128, "K" }, { 256, "W" }, { 512, "P" }) : "R",__entry->prev_state & TASK_STATE_MAX ? "+" : "",__entry->next_comm, __entry->next_pid, __entry->next_prio)
);

除第一个参数外的所有参数都用另一个宏(TP_PROTO、TP_ARGS、TP_STRUCT__entry、TP_fast_assign和TP_printk)封装。这些宏提供了更多的处理控制,并且允许在TRACE_EVENT()宏中使用逗号。

Name:第一个参数是名称。

TRACE_EVENT(sched_switch,

这是用于调用此跟踪点的名称。实际使用的跟踪点在名称前加上trace_前缀(即trace_sched_switch),比如:

#define CREATE_TRACE_POINTS
#include /*** prepare_task_switch - prepare to switch tasks* @rq: the runqueue preparing to switch* @prev: the current task that is being switched out* @next: the task we are going to switch to.** This is called with the rq lock held and interrupts off. It must* be paired with a subsequent finish_task_switch after the context* switch.** prepare_task_switch sets up locking and calls architecture specific* hooks.*/
static inline void
prepare_task_switch(struct rq *rq, struct task_struct *prev,struct task_struct *next)
{//使用 tracepoint:sched_switchtrace_sched_switch(prev, next);sched_info_switch(prev, next);perf_event_task_sched_out(prev, next);fire_sched_out_preempt_notifiers(prev, next);prepare_lock_switch(rq, next);prepare_arch_switch(next);
}

Prototype:下一个参数是原型。

	TP_PROTO(struct task_struct *prev,struct task_struct *next),

编写原型时,就像直接声明跟踪点一样:

trace_sched_switch(struct task_struct *prev, struct task_struct *next),

它被用作添加到内核代码中的跟踪点和回调函数的原型。请记住,跟踪点调用回调函数,就像在跟踪点的位置调用回调函数一样。

Arguments:第三个参数是原型使用的参数。

TP_ARGS(prev, next),

这似乎很奇怪,但它不仅是TRACE_EVENT()宏所需要的,它也是下面的跟踪点基础结构所需要的。跟踪点代码在激活时将调用回调函数(可以为给定的跟踪点分配多个回调)。
创建跟踪点的宏必须同时访问原型和参数。以下是跟踪点宏完成此操作所需的说明:

struct tracepoint_func {void *func;void *data;
};struct tracepoint {const char *name;		/* Tracepoint name */struct static_key key;void (*regfunc)(void);void (*unregfunc)(void);struct tracepoint_func __rcu *funcs;
};extern struct tracepoint __tracepoint_##name;			\
static inline void trace_##name(proto)				\
{								\if (static_key_false(&__tracepoint_##name.key))		\__DO_TRACE(&__tracepoint_##name,		\TP_PROTO(data_proto),			\TP_ARGS(data_args),			\TP_CONDITION(cond),,);			\
}	

Structure:第四个参数有点复杂。

TP_STRUCT__entry(__array(	char,	prev_comm,	TASK_COMM_LEN	)__field(	pid_t,	prev_pid			)__field(	int,	prev_prio			)__field(	long,	prev_state			)__array(	char,	next_comm,	TASK_COMM_LEN	)__field(	pid_t,	next_pid			)__field(	int,	next_prio			)
),

此参数描述将存储在跟踪器环形缓冲区中的数据的结构布局。结构的每个元素都由另一个宏定义。这些宏用于自动创建结构,与功能不同。请注意,宏之间没有任何分隔符(没有逗号或分号)。
sched_switch跟踪点使用的宏包括:
__array(type,name,len)-这定义了一个数组项,相当于int name[len];其中类型为int,数组的名称为array,数组中的项数为len。
__field(type,name):这定义了一个普通的结构元素,比如in tvar;其中类型为int,名称为var。
还有其他元素宏将在后面的文章中描述。sched_switch跟踪点的定义将生成如下结构:

struct {char   prev_comm[TASK_COMM_LEN];pid_t  prev_pid;int    prev_prio;long   prev_state;char   next_comm[TASK_COMM_LEN];pid_t  next_pid;int    next_prio;
};

Assignment:第五个参数定义了将参数中的数据保存到环形缓冲区的方式。

TP_fast_assign(memcpy(__entry->next_comm, next->comm, TASK_COMM_LEN);__entry->prev_pid	= prev->pid;__entry->prev_prio	= prev->prio;__entry->prev_state	= __trace_sched_switch_state(prev);memcpy(__entry->prev_comm, prev->comm, TASK_COMM_LEN);__entry->next_pid	= next->pid;__entry->next_prio	= next->prio;
),

TP_fast_assign()中的代码是正常的C代码。特殊变量__entry表示指向由TP_STRUCT__entry定义的结构类型的指针,并直接指向环形缓冲区。TP_fast_assign用于填充TP_STRUCT__entry中创建的所有字段。然后可以使用TP_PROTO和TP_ARGS定义的参数的变量名将适当的数据分配到__entry结构中。

Print:最后一个参数定义如何使用printk()打印TP_STRUCT__entry结构中的字段。

TP_printk("prev_comm=%s prev_pid=%d prev_prio=%d prev_state=%s%s ==> next_comm=%s next_pid=%d next_prio=%d",__entry->prev_comm, __entry->prev_pid, __entry->prev_prio,__entry->prev_state & (TASK_STATE_MAX-1) ?__print_flags(__entry->prev_state & (TASK_STATE_MAX-1), "|",{ 1, "S"} , { 2, "D" }, { 4, "T" }, { 8, "t" },{ 16, "Z" }, { 32, "X" }, { 64, "x" },{ 128, "K" }, { 256, "W" }, { 512, "P" }) : "R",__entry->prev_state & TASK_STATE_MAX ? "+" : "",__entry->next_comm, __entry->next_pid, __entry->next_prio)

变量__entry再次用于引用指向包含数据的结构的指针。格式字符串与任何其他printf格式一样。__print_flags()是TRACE_EVENT()附带的一组帮助函数的一部分。

Format file:sched_switch TRACE_EVENT()宏以/sys/kernel/debug/tracking/events/sched/sched_switch/format格式生成以下格式文件:

[root@localhost ~]# cat /sys/kernel/debug/tracing/events/sched/sched_switch/format
name: sched_switch
ID: 326
format:field:unsigned short common_type;       offset:0;       size:2; signed:0;field:unsigned char common_flags;       offset:2;       size:1; signed:0;field:unsigned char common_preempt_count;       offset:3;       size:1; signed:0;field:int common_pid;   offset:4;       size:4; signed:1;field:char prev_comm[16];       offset:8;       size:16;        signed:1;field:pid_t prev_pid;   offset:24;      size:4; signed:1;field:int prev_prio;    offset:28;      size:4; signed:1;field:long prev_state;  offset:32;      size:8; signed:1;field:char next_comm[16];       offset:40;      size:16;        signed:1;field:pid_t next_pid;   offset:56;      size:4; signed:1;field:int next_prio;    offset:60;      size:4; signed:1;print fmt: "prev_comm=%s prev_pid=%d prev_prio=%d prev_state=%s%s ==> next_comm=%s next_pid=%d next_prio=%d", REC->prev_comm, REC->prev_pid, REC->prev_prio, REC->prev_state & (1024-1) ? __print_flags(REC->prev_state & (1024-1), "|", { 1, "S"} , { 2, "D" }, { 4, "T" }, { 8, "t" }, { 16, "Z" }, { 32, "X" }, { 64, "x" }, { 128, "K" }, { 256, "W" }, { 512, "P" }) : "R", REC->prev_state & 1024 ? "+" : "", REC->next_comm, REC->next_pid, REC->next_prio

与进程调度有关的其他 tracepoints 点:

[root@localhost ~]# cat /sys/kernel/debug/tracing/events/sched/
enable                       sched_move_numa/             sched_process_free/          sched_stat_runtime/          sched_switch/
filter                       sched_pi_setprio/            sched_process_hang/          sched_stat_sleep/            sched_wait_task/
sched_kthread_stop/          sched_process_exec/          sched_process_wait/          sched_stat_wait/             sched_wake_idle_without_ipi/
sched_kthread_stop_ret/      sched_process_exit/          sched_stat_blocked/          sched_stick_numa/            sched_wakeup/
sched_migrate_task/          sched_process_fork/          sched_stat_iowait/           sched_swap_numa/             sched_wakeup_new/

请注意,格式文件中的__entry替换为REC。第一组字段(common_*)不是来自TRACE_EVENT()宏,而是由创建此格式文件的Ftrace添加到所有事件中,其他跟踪程序可以添加不同的字段。格式文件为用户空间工具提供解析包含sched_switch项的二进制输出所需的信息。

三、The header file

内核中的跟踪点都是定义在该目录下:

linux-3.10/include/trace/events/

在这里插入图片描述
与任务调度相关的 tracepoints 点都是在该目录下:

linux-3.10/include/trace/events/sched.h#undef TRACE_SYSTEM
#define TRACE_SYSTEM sched#if !defined(_TRACE_SCHED_H) || defined(TRACE_HEADER_MULTI_READ)
#define _TRACE_SCHED_H#include 
#include 
#include /** Tracepoint for calling kthread_stop, performed to end a kthread:*/
TRACE_EVENT(sched_kthread_stop,TP_PROTO(struct task_struct *t),TP_ARGS(t),TP_STRUCT__entry(__array(	char,	comm,	TASK_COMM_LEN	)__field(	pid_t,	pid			)),TP_fast_assign(memcpy(__entry->comm, t->comm, TASK_COMM_LEN);__entry->pid	= t->pid;),TP_printk("comm=%s pid=%d", __entry->comm, __entry->pid)
);/** Tracepoint for the return value of the kthread stopping:*/
TRACE_EVENT(sched_kthread_stop_ret,TP_PROTO(int ret),TP_ARGS(ret),TP_STRUCT__entry(__field(	int,	ret	)),TP_fast_assign(__entry->ret	= ret;),TP_printk("ret=%d", __entry->ret)
);
......

TRACE_EVENT()头中的第一行不是常规的 #ifdef _TRACE_SCHED_H ,而是:

#undef TRACE_SYSTEM
#define TRACE_SYSTEM sched#if !defined(_TRACE_SCHED_H) || defined(TRACE_HEADER_MULTI_READ)
#define _TRACE_SCHED_H

此示例用于调度程序跟踪事件,其他事件的 headfile 将使用sched和_trace_sched_H以外的其他内容。TRACE_HEADER_MULTI_READ测试允许多次包含此文件;这对于TRACE_EVENT()宏的处理非常重要。还必须为文件定义TRACE_SYSTEM,并且必须在#if的保护范围之外。TRACE_SYSTEM定义文件中TRACE_EVENT()宏所属的组。这也是事件将在debugfs tracing/events/ 目录中分组的目录名。此分组对Ftrace很重要,因为它允许用户按组启用或禁用事件。
然后,该文件包含TRACE_EVENT()宏内容所需的任何 headers 。(例如#include)。
The tracepoint.h file 也要求:

#include 

现在可以使用 TRACE_EVENT() 宏定义所有跟踪事件。请在TRACE_EVENT()宏上方包含描述跟踪点的注释。查看include/trace/events/sched.h作为示例。文件结尾为:

#endif /* _TRACE_SCHED_H *//* This part must be outside protection */
#include 

define_trace.h是创建跟踪点的 all the magic 所在。关于这个文件如何工作的解释将留给另一篇文章。现在,只要知道这个文件必须包含在跟踪头文件的底部,不受#endif的保护就足够了。

要使用跟踪点,必须包含 headerfile ,但在包含跟踪之前,一个C文件还必须定义CREATE_TRACE_POINTS。这将导致define_trace.h来创建生成跟踪事件所需的必要函数,如下所示:

// linux-3.10/kernel/sched/core.c#define CREATE_TRACE_POINTS
#include 

如果另一个文件需要使用跟踪文件中定义的跟踪点,那么它只需要包含跟踪文件,而不需要定义CREATE_TRACE_POINTS了。为同一头文件多次定义它将在生成时导致链接器错误。比如:

// linux-3.10/kernel/fork.c#include // 这里的 CREATE_TRACE_POINTS 是生成与 task 有关的 tracepoint
#define CREATE_TRACE_POINTS
#include 

代码中使用的跟踪点与TRACE_EVENT()宏中定义的一样:

/*** prepare_task_switch - prepare to switch tasks* @rq: the runqueue preparing to switch* @prev: the current task that is being switched out* @next: the task we are going to switch to.** This is called with the rq lock held and interrupts off. It must* be paired with a subsequent finish_task_switch after the context* switch.** prepare_task_switch sets up locking and calls architecture specific* hooks.*/
static inline void
prepare_task_switch(struct rq *rq, struct task_struct *prev,struct task_struct *next)
{trace_sched_switch(prev, next);sched_info_switch(prev, next);perf_event_task_sched_out(prev, next);fire_sched_out_preempt_notifiers(prev, next);prepare_lock_switch(rq, next);prepare_arch_switch(next);
}

参考资料

Linux 3.10.0

https://lwn.net/Articles/379903/

相关内容

热门资讯

win32应用程序下载-Win... 嘿,伙计们!今天咱们聊聊那个让人又爱又恨的话题——Win32应用程序下载!是不是每次听到这个词儿,你...
肛瘘手术 长时间流脓-肛瘘手术... 哎呀,说到这个肛瘘手术啊,真是让我哭笑不得!手术前,我还以为只是个小打小闹,没想到手术后,我竟然成了...
surface book 2 ... 哎呀,说到SurfaceBook2的这支笔,我的心都要跳出来了!它不仅仅是支笔,简直是我的灵感喷泉,...
温一医流程-温一医看病流程:人... 哎呀,说到温一医的流程,真是让人又爱又恨!你知道的,每次去那里,感觉就像走进了一个大迷宫,到处都是人...
云计算操作系统 下载-云计算操... 哎呀呀,说到这云计算操作系统下载,我这心里就五味杂陈的。你知道吗,现在满大街都是云计算的广告,好像不...
win7官方纯净版下载-Win... 嘿,大家好!今天咱们聊聊那个让人又爱又恨的Win7系统,特别是那个传说中的“官方纯净版”。你可能听说...
重庆中等职业学校排名大揭秘,你... 哎呀,说到重庆的中等职业学校排名,我的心就扑通扑通跳个不停!这可是关系到千千万万学子未来的大事啊!每...
三星手机id码怎么查看-三星手... 嘿,大家好!今天咱们聊聊怎么查看三星手机的ID码。别担心,这事儿没你想的那么复杂,跟我来,轻松搞定!...
freebsd zfs-Fre... 嘿,朋友们!今天我要跟你们聊一聊我在FreeBSD上捣鼓ZFS的那些事儿。这可不是普通的存储系统,这...
hpusbfw.exe:惠普电... 哎呀,说到这个hpusbfw.exe,我真是又爱又恨!每次打开电脑,它就在后台默默工作,像个勤劳的小...
朝阳医院好挂号吗-朝阳医院挂号... 哎呀,说到朝阳医院挂号,我这心里五味杂陈啊!前几天我那小侄子发烧,急得我直跺脚,赶紧上网查了查朝阳医...
win8安装网络打印机-Win... 大家好,今天咱们聊聊在Win8系统上装网络打印机这件小事。别担心,虽然听起来有点技术流,但跟着我一步...
全志a63跑分-全志 A63 ... 嘿,大家好!今天咱们来聊聊那个让人又爱又恨的全志A63跑分!你知道的,这玩意儿在各种设备上跑来跑去,...
电脑蓝屏重装系统步骤-电脑突然... 哎呀,真是急死人了!电脑突然蓝屏,屏幕上一堆白字,简直就像外星文,看都看不懂!这时候,你是不是也想砸...
rally本田-RallyHo... 哎呀,说到RallyHonda,我的心就开始砰砰跳!这可不是一般的赛车,这是激情、速度和梦想的化身啊...
杨阳洋哮喘非常静距离-一位普通... 大家好,我是一个普通的妈妈,今天我想和大家聊聊一个让我心痛的话题——我的儿子杨阳洋的哮喘。这不仅仅是...
家用省电王-智能插座:小个头大... 哎呀,说到我家的这个小家伙,真是让我又爱又惊喜!你们知道吗,它就是我家的“小省电王”,自从有了它,我...
smc.exe是什么进程-电脑... 大家好,我是一个对电脑里那些乱七八糟进程感到无比头疼的小白用户。今天,我要来扒一扒那个经常在任务管理...
2024一键还原win7系统-... 嘿,朋友们!今天我要来聊聊那个让我爱不释手的功能——一键还原Win7系统!你知道吗,有一次我的电脑简...
先锋图书管理:知识海洋与创意摇... 嘿,朋友们,今天咱们聊聊我心中的宝贝——先锋图书管理!这可不是简单的书堆,这是知识的海洋,是创意的摇...