41

Systemtap中内核trace事件的实现

 4 years ago
source link: https://www.codedump.info/post/20200218-linux-traceevent/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

概述

内核中定义了一系列的trace point,这些trace point在特定的内核函数中被触发调用时被记录,而对应到systemtap中就是 kernel.trace 类型的probe事件,可以使用命令来查看系统所有的trace point:

$ sudo stap -L 'kernel.trace("*")' | more
kernel.trace("9p:9p_client_req") $clnt:struct p9_client* $type:int8_t $tag:int
kernel.trace("9p:9p_client_res") $clnt:struct p9_client* $type:int8_t $tag:int $err:int
kernel.trace("9p:9p_protocol_dump") $clnt:struct p9_client* $pdu:struct p9_fcall*

换言之,通过systemtap能够对这些已经静态注册的内核调用记录点进行监控、跟踪。

以下来解释trace point在内核的实现以及与systemtap相关的内容。

数据结构

内核通过 DECLARE_TRACE 来声明一个trace point:

DECLARE_TRACE(subsys_eventname,
	TP_PROTO(int firstarg, struct task_struct *p),
	TP_ARGS(firstarg, p));

在这里:

  • subsys_eventname是定义trace事件的唯一字符串,又能拆解成两部分:subsys就是子系统的名称,而eventname是事件名称。比如下面将作为实例的 softirq_entry ,就定义了一个在 softirq 子系统中的 entry 事件。
  • TP_PROTO(int firstarg, struct task_struct *p):定义了传入trace函数的参数原型。
  • TP_ARGS(firstarg, p):定义了参数名称,其类型与TP_PROTO中的类型一一对应。

这个宏的定义如下:

// include/linux/tracepoint.h
#define DECLARE_TRACE(name, proto, args)				\
	__DECLARE_TRACE(name, PARAMS(proto), PARAMS(args),		\
			cpu_online(raw_smp_processor_id()),		\
			PARAMS(void *__data, proto),			\
			PARAMS(__data, args))

其中的宏 __DECLARE_TRACE 定义如下:

#define __DECLARE_TRACE(name, proto, args, cond, data_proto, data_args) \
	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), 0);			\
		if (IS_ENABLED(CONFIG_LOCKDEP) && (cond)) {		\
			rcu_read_lock_sched_notrace();			\
			rcu_dereference_sched(__tracepoint_##name.funcs);\
			rcu_read_unlock_sched_notrace();		\
		}							\
	}								\
	__DECLARE_TRACE_RCU(name, PARAMS(proto), PARAMS(args),		\
		PARAMS(cond), PARAMS(data_proto), PARAMS(data_args))	\
	static inline int						\
	register_trace_##name(void (*probe)(data_proto), void *data)	\
	{								\
		return tracepoint_probe_register(&__tracepoint_##name,	\
						(void *)probe, data);	\
	}								\
	static inline int						\
	register_trace_prio_##name(void (*probe)(data_proto), void *data,\
				   int prio)				\
	{								\
		return tracepoint_probe_register_prio(&__tracepoint_##name, \
					      (void *)probe, data, prio); \
	}								\
	static inline int						\
	unregister_trace_##name(void (*probe)(data_proto), void *data)	\
	{								\
		return tracepoint_probe_unregister(&__tracepoint_##name,\
						(void *)probe, data);	\
	}								\
	static inline void						\
	check_trace_callback_type_##name(void (*cb)(data_proto))	\
	{								\
	}								\
	static inline bool						\
	trace_##name##_enabled(void)					\
	{								\
		return static_key_false(&__tracepoint_##name.key);	\
	}

可以看到,这个宏做了如下的事情:

  • 声明了一个类型为 tracepoint 的结构体变量 __tracepoint_##name
  • 定义了几个相关的函数,分别用于处理trace event、注册、注销等。其中需要重点关注的是宏 trace_##name ,这里定义了对对应的traceevent进行跟踪的函数。

其中,宏里面一个字符串跟着 ##name 表示这个字符串与name的连接形成的字符串。

这里的结构体 tracepoint 定义如下:

// include/linux/tracepoint-defs.h
struct tracepoint {
	const char *name;		/* Tracepoint name */
	struct static_key key;
	int (*regfunc)(void);
	void (*unregfunc)(void);
	struct tracepoint_func __rcu *funcs;
};

该结构体中分别定义了:

  • traceevent名称。
  • 注册、注销、被触发时的处理函数。

以上只是声明了 tracepoint 结构体变量,而具体定义变量的宏是 DEFINE_TRACE

// include/linux/tracepoint.h
#define DEFINE_TRACE(name)						\
	DEFINE_TRACE_FN(name, NULL, NULL);

#define DEFINE_TRACE_FN(name, reg, unreg)				 \
	static const char __tpstrtab_##name[]				 \
	__attribute__((section("__tracepoints_strings"))) = #name;	 \
	struct tracepoint __tracepoint_##name				 \
	__attribute__((section("__tracepoints"))) =			 \
		{ __tpstrtab_##name, STATIC_KEY_INIT_FALSE, reg, unreg, NULL };\
	static struct tracepoint * const __tracepoint_ptr_##name __used	 \
	__attribute__((section("__tracepoints_ptrs"))) =		 \
		&__tracepoint_##name;

因此, DEFINE_TRACE 的作用就是:

  • __tracepoints_strings section中定义了字符串数组变量 __tpstrtab_##name ,其值为name。
  • __tracepoints section中定义了结构体tracepoint变量 __tracepoint_##name

以上解释了trace point相关的数据结构、宏、变量等,下面以一个实例来展开说明。

实例

这里以软中断被调用时的入口trace event为例,其定义如下:

DEFINE_EVENT(softirq, softirq_entry,

	TP_PROTO(unsigned int vec_nr),

	TP_ARGS(vec_nr)
);

这里的宏 DEFINE_EVENT 不过是前面 DECLARE_TRACE 宏的一个包装:

#define DEFINE_EVENT(template, name, proto, args)		\
	DECLARE_TRACE(name, PARAMS(proto), PARAMS(args))

从上面的讨论可以知道,这里声明了一个名为 __tracepoint_softirq_entrytracepoint 类型结构体。而根据我们前面对宏的展开分析, trace_##name 也就是这里展开的 trace_softirq_entry 是对这个trace event进行调用的入口,果然在 __do_softirq 函数中看到了它的身影:

// kernel/softirq.c
asmlinkage __visible void __softirq_entry __do_softirq(void)
{
  // ...
  trace_softirq_entry(vec_nr);
  // ...
}

systemtap相关

这里需要注意的另一个问题是,每个systemtap中的kernel.trace当时可以知道的参数,除了trace event本身的参数之外,还有当时所在嵌入函数内部的变量,比如这里的 softirq_entry 这个probe,在systemtap对应的tapset中是这样的:

probe softirq.entry = kernel.trace("irq_softirq_entry") !,
     		      kernel.trace("softirq_entry") ?
{
	# kernels < 2.6.37
	h = @choose_defined($h, 0)
	vec = @choose_defined($vec, 0)
	action = (@defined($h) ? @cast($h,"softirq_action","kernel<linux/interrupt.h>")->action : 0)
	# kernels >= 2.6.37
	vec_nr = @choose_defined($vec_nr, 0)
}

这里可以的变量 h 类型是在内核中的头文件 <linux/interrupt.h> 中定义的 softirq_action ,因为这个变量就是在上面的函数 __do_softirq 中定义的:

// kernel/softirq.c
asmlinkage __visible void __softirq_entry __do_softirq(void)
{
  // ...
  struct softirq_action *h;

  // ...
  trace_softirq_entry(vec_nr);
  // ...
}

所以,要看一个systemtap的kernel.trace能引用哪些变量,除了看其自身,还包括看其所嵌入函数的上下文中的变量,最好直接到对应的tapset的说明,因为-L只能打印出这个kernel.trace自身定义的变量:

$ sudo stap -L 'kernel.trace("softirq_entry")'
kernel.trace("irq:softirq_entry") $vec_nr:unsigned int

总结:

  • 内核中的trace事件以 trace_* 来命名。
  • 看到systemtap中的 'kernel.trace("xx")' ,其对应的内核代码可以使用 trace_xx 来搜索,通过阅读这个trace事件所嵌入的代码也可以或者这个probe事件能打印的变量。

参考资料


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK