Zephyr tracing系统--2 配置和调用框架

本文介绍Zephyr tracing系统的公共配置项和调用框架代码分析。

Zephyr支持5种Trace Tool,虽然每种tool的实现都不一样, 但从内核代码埋入tracing API到实际调用到tool是共用的,本文对共用部分进行分析。

配置 链接到标题

CONFIG_TRACING 配置启用tracing系统,如果为n,tracing系统将不会被编译进zephyr 下面6项是多选1: TRACING_NONE: 不使用Trace tool CONFIG_PERCEPIO_TRACERECORDER : 使用trace recoder SEGGER_SYSTEMVIEW: 使用systemview TRACING_CTF: 使用CTS TRACING_TEST: 使用test TRACING_USER: 使用user 下面17项是对tracing对象的选择,默认为选中,当配置为n时将不会在该对象中埋入hook API CONFIG_SYSCALL_TRACING : trace系统调用,目前没有实现 CONFIG_TRACING_THREAD : trace thread,sys_port_trace_type_mask_k_thread CONFIG_TRACING_WORK : trace work CONFIG_TRACING_ISR : trace ISR CONFIG_TRACING_SEMAPHORE : trace信号量 CONFIG_TRACING_MUTEX : trace互斥量 CONFIG_TRACING_CONDVAR : trace条件变量 CONFIG_TRACING_QUEUE : trace queue CONFIG_TRACING_FIFO : trace FIFO CONFIG_TRACING_LIFO : trace LIFO CONFIG_TRACING_STACK : trace stack CONFIG_TRACING_MESSAGE_QUEUE : trace消息列队 CONFIG_TRACING_MAILBOX : trace邮箱 CONFIG_TRACING_PIPE : trace管道 CONFIG_TRACING_HEAP : trace heap CONFIG_TRACING_MEMORY_SLAB : trace slab CONFIG_TRACING_TIMER : trace timer

框架代码 链接到标题

hook宏 链接到标题

所有被trace内核对象中都是调用下面9个宏进行hook

SYS_PORT_TRACING_FUNC(type, func, ...)
SYS_PORT_TRACING_FUNC_ENTER(type, func, ...)
SYS_PORT_TRACING_FUNC_BLOCKING(type, func, ...)
SYS_PORT_TRACING_FUNC_EXIT(type, func, ...)
SYS_PORT_TRACING_OBJ_INIT(obj_type, obj, ...)
SYS_PORT_TRACING_OBJ_FUNC(obj_type, func, obj, ...)
SYS_PORT_TRACING_OBJ_FUNC_ENTER(obj_type, func, obj, ...)
SYS_PORT_TRACING_OBJ_FUNC_BLOCKING(obj_type, func, obj, timeout, ...)
SYS_PORT_TRACING_OBJ_FUNC_EXIT(obj_type, func, obj, ...)

这9个宏可以份为两组,一组是普通函数得hook,一组是内核对象函数得hook, 两组宏都支持变参,可以记录被trace函数的参数

普通函数的hook 链接到标题

用于hook一般的内核函数:

  • 线程相关函数
  • 调度相关函数
  • poll相关函数
  • timeout相关函数
  • 电源管理相关函数:目前电源管理虽然有插入hook,但最后并没有实现

各个宏的含义 ** SYS_PORT_TRACING_FUNC(type, func, …) ** 跟踪函数被调用次数和何时调用,类型为type功能为func, 例如要跟踪k_thread_user_mode_enter,其type是k_thread,函数功能是user_mode_enter就在其中埋入

SYS_PORT_TRACING_FUNC(k_thread, user_mode_enter);

** SYS_PORT_TRACING_FUNC_ENTER(type, func, …) ** ** SYS_PORT_TRACING_FUNC_EXIT(type, func, ...) **: 跟踪函数进入和退出的时机点,类型为type功能为func,例如要跟踪线程sleep函数z_impl_k_usleep,其type是k_thread`,函数功能是usleep,sleep的时间为us,埋入情况如下

int32_t z_impl_k_usleep(int us)
{
	int32_t ticks;

	SYS_PORT_TRACING_FUNC_ENTER(k_thread, usleep, us);

	ticks = k_us_to_ticks_ceil64(us);
	ticks = z_tick_sleep(ticks);

	SYS_PORT_TRACING_FUNC_EXIT(k_thread, usleep, us, k_ticks_to_us_floor64(ticks));

	return k_ticks_to_us_floor64(ticks);
}

SYS_PORT_TRACING_FUNC_BLOCKING(type, func, …) 跟踪函数被bloking的时机点,类型为type功能为func, 目前Zephyr内核未使用该种trace。

内核对象函数得hook 链接到标题

用于hook内核对象:mutex, semaphore, msgq, pipes, queue, timer, fifo, lifo, work, stack, mailbox, condvar, heap, slab

SYS_PORT_TRACING_OBJ_INIT(obj_type, obj, …) 跟踪内核对象obj初始化的时机,类型为obj_type,例如要跟踪mutex的初始化,埋入下面hook

SYS_PORT_TRACING_OBJ_INIT(k_mutex, mutex, 0);

SYS_PORT_TRACING_OBJ_FUNC(obj_type, func, obj, …) 跟踪内核对象obj的函数被调用时机,类型为type功能为func,例如要跟踪z_impl_k_timer_start,埋入下面hook

SYS_PORT_TRACING_OBJ_FUNC(k_timer, start, timer);

SYS_PORT_TRACING_OBJ_FUNC_ENTER(obj_type, func, obj, …) SYS_PORT_TRACING_OBJ_FUNC_EXIT(obj_type, func, obj, …) 跟踪内核对象obj的函数进入和退出时机点,类型为type功能为func,例如要跟踪k_malloc,埋入下面hook

void *k_malloc(size_t size)
{
	SYS_PORT_TRACING_OBJ_FUNC_ENTER(k_heap_sys, k_malloc, _SYSTEM_HEAP);

	void *ret = k_aligned_alloc(sizeof(void *), size);

	SYS_PORT_TRACING_OBJ_FUNC_EXIT(k_heap_sys, k_malloc, _SYSTEM_HEAP, ret);

	return ret;
}

SYS_PORT_TRACING_OBJ_FUNC_BLOCKING(obj_type, func, obj, timeout, …) 跟踪内核对象obj的函数block时机点,类型为type功能为func,例如要跟踪k_malloc,埋入下面hook

bool k_work_flush(struct k_work *work,
		  struct k_work_sync *sync)
{

	SYS_PORT_TRACING_OBJ_FUNC_ENTER(k_work, flush, work);

	struct z_work_flusher *flusher = &sync->flusher;
	k_spinlock_key_t key = k_spin_lock(&lock);

	bool need_flush = work_flush_locked(work, flusher);

	k_spin_unlock(&lock, key);

	/* If necessary wait until the flusher item completes */
	if (need_flush) {
		//在block等待前埋入hook
		SYS_PORT_TRACING_OBJ_FUNC_BLOCKING(k_work, flush, work, K_FOREVER);

		k_sem_take(&flusher->sem, K_FOREVER);
	}

	SYS_PORT_TRACING_OBJ_FUNC_EXIT(k_work, flush, work, need_flush);

	return need_flush;
}

宏的展开 链接到标题

tracing的头文件都放在include\tracing下,和宏相关的放在tracing_macros.h中。 当配置CONFIG_TRACING=n时,所有的宏都变为如下,经过编译器优化后不会产生任何代码,因此不会对被hook的函数造成任何影响

#ifndef CONFIG_TRACING

#define SYS_PORT_TRACING_FUNC(type, func, ...) do { } while (false)
#define SYS_PORT_TRACING_FUNC_ENTER(type, func, ...) do { } while (false)
#define SYS_PORT_TRACING_FUNC_BLOCKING(type, func, ...) do { } while (false)
#define SYS_PORT_TRACING_FUNC_EXIT(type, func, ...) do { } while (false)
#define SYS_PORT_TRACING_OBJ_INIT(obj_type, obj, ...) do { } while (false)
#define SYS_PORT_TRACING_OBJ_FUNC(obj_type, func, obj, ...) do { } while (false)
#define SYS_PORT_TRACING_OBJ_FUNC_ENTER(obj_type, func, obj, ...) do { } while (false)
#define SYS_PORT_TRACING_OBJ_FUNC_BLOCKING(obj_type, func, obj, ...) do { } while (false)
#define SYS_PORT_TRACING_OBJ_FUNC_EXIT(obj_type, func, obj, ...) do { } while (false)
#else

在CONFIG_TRACING=y时,9个宏的展开模式都一样,这里选择其中两种进行分析

普通函数的hook 链接到标题

普通函数的hook由宏直接展开为调用函数,以SYS_PORT_TRACING_FUNC(k_thread, switched_in)来分析

#define SYS_PORT_TRACING_FUNC(type, func, ...) \
	do { \
		_SYS_PORT_TRACING_FUNC(type, func)(__VA_ARGS__); \
	} while (false)
	
#define _SYS_PORT_TRACING_FUNC(name, func) \
	sys_port_trace_ ## name ## _ ## func

SYS_PORT_TRACING_FUNC(k_thread, switched_in)被展开为

do { \
		sys_port_trace_k_thread_switched_in(); \
	} while (false)

trace tool需要实现sys_port_trace_k_thread_switched_in这个函数才能达到tracing的目的

内核对象函数得hook 链接到标题

相比较于普通函数,内核对象的hook多了一层判断,以SYS_PORT_TRACING_OBJ_FUNC(k_thread, name_set, thread, -ENOSYS)来分析

#define SYS_PORT_TRACING_OBJ_FUNC(obj_type, func, obj, ...) \
	do { \
		SYS_PORT_TRACING_TYPE_MASK(obj_type, \
			_SYS_PORT_TRACING_OBJ_FUNC(obj_type, func)(obj, ##__VA_ARGS__)); \
	} while (false)
#define SYS_PORT_TRACING_TYPE_MASK(type, trace_call) \
	_SYS_PORT_TRACING_TYPE_MASK(type)(trace_call)

#define _SYS_PORT_TRACING_TYPE_MASK(type) \
	sys_port_trace_type_mask_ ## type

#define _SYS_PORT_TRACING_OBJ_FUNC(name, func) \
	sys_port_trace_ ## name ## _ ## func

SYS_PORT_TRACING_OBJ_FUNC(k_thread, name_set, thread, -ENOSYS)被展开为

sys_port_trace_type_mask_k_thread(sys_port_trace_k_thread_name_set(thread,-ENOSYS))

通过CONFIG_TRACING_THREAD来选定,是否要真正的trace,不同的内核对象会有不同的config来配置

#if defined(CONFIG_TRACING_THREAD)
	#define sys_port_trace_type_mask_k_thread(trace_call) trace_call
#else
	#define sys_port_trace_type_mask_k_thread(trace_call)
#endif

如果不配置sys_port_trace_type_mask_k_thread(sys_port_trace_k_thread_name_set(thread,-ENOSYS))变为NULL,相当于没有埋入hook,如果配置则变为sys_port_trace_k_thread_name_set(thread,-ENOSYS). trace tool需要实现sys_port_trace_k_thread_switched_in这个函数

Trace函数对接 链接到标题

从前面的分析可以看到所有埋入的hook宏,最后都会一一对应到不同的函数,在zephyr这些函数是以宏的形式声明在tracing.h中,在该文件中有所有tracing API的原型和说明,这里就不一一列出了。 Trace函数和tool对接采用了宏定义的方式,如果没有配置tool,则为空的宏,tracing不会生效,例如下面的sys_port_trace_k_thread_switched_out

#if defined CONFIG_SEGGER_SYSTEMVIEW
#include "tracing_sysview.h"

#elif defined CONFIG_TRACING_CTF
#include "tracing_ctf.h"

#elif defined CONFIG_TRACING_TEST
#include "tracing_test.h"

#elif defined CONFIG_TRACING_USER
#include "tracing_user.h"

#else
#define sys_port_trace_k_thread_foreach_enter()
....
#define sys_port_trace_k_thread_switched_out()

#if defined CONFIG_PERCEPIO_TRACERECORDER
#include "tracing_tracerecorder.h"
#else
void sys_trace_isr_enter(void);
void sys_trace_isr_exit(void);
void sys_trace_isr_exit_to_scheduler(void);
void sys_trace_idle(void);
#endif
#endif

当有配置tracing tool时,就会走到不同的头文件中,使用其实现,还是以sys_port_trace_k_thread_foreach_enter为例: 在tracing_sysview.h中调用了SEGGER_SYSVIEW_OnTaskStopExec

#define sys_port_trace_k_thread_switched_out() sys_trace_k_thread_switched_out()

void sys_trace_k_thread_switched_out(void)
{
	SEGGER_SYSVIEW_OnTaskStopExec();
}

在tracing_ctf.h中调用了

#define sys_port_trace_k_thread_switched_out() sys_trace_k_thread_switched_out()

void sys_trace_k_thread_switched_out(void)
{
	ctf_bounded_string_t name = { "unknown" };
	struct k_thread *thread;

	thread = k_current_get();
	_get_thread_name(thread, &name);

	ctf_top_thread_switched_out((uint32_t)(uintptr_t)thread, name);
}

在tracing_test.h中进行了打印

#define sys_port_trace_k_thread_switched_out() sys_trace_k_thread_switched_out()

void sys_trace_k_thread_switched_out(void)
{
	struct k_thread *thread;

	thread = k_current_get();
	TRACING_STRING("%s: %p\n", __func__, thread);
}

在tracing_user.h中调用了sys_trace_thread_switched_out_user,该函数是一个弱符号函数,可以被用户覆盖

#define sys_port_trace_k_thread_switched_out() sys_trace_k_thread_switched_out()

void __weak sys_trace_thread_switched_out_user(struct k_thread *thread) {}
void sys_trace_k_thread_switched_out(void)
{
	int key = irq_lock();

	__ASSERT_NO_MSG(nested_interrupts == 0);

	sys_trace_thread_switched_out_user(k_current_get());

	irq_unlock(key);
}

“tracing_tracerecorder.h"比较特殊,和tracing.h中的空宏是放在一起的,因此头文件内会先进行undef,再重新define。实际实现是空函数,没有做记录

#undef sys_port_trace_k_thread_switched_out
#define sys_port_trace_k_thread_switched_out() 								   \
    sys_trace_k_thread_switched_out()

void sys_trace_k_thread_switched_out(void) {
}