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

Creative Commons
本作品采用知识共享署名

本文介绍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

1
2
3
4
5
6
7
8
9
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就在其中埋入

1
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,埋入情况如下

1
2
3
4
5
6
7
8
9
10
11
12
13
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

1
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

1
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

1
2
3
4
5
6
7
8
9
10
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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的函数造成任何影响

1
2
3
4
5
6
7
8
9
10
11
12
#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)来分析

1
2
3
4
5
6
7
#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)被展开为

1
2
3
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)来分析

1
2
3
4
5
6
7
8
9
10
11
12
13
#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)被展开为

1
sys_port_trace_type_mask_k_thread(sys_port_trace_k_thread_name_set(thread,-ENOSYS))

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

1
2
3
4
5
#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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#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

1
2
3
4
5
6
#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中调用了

1
2
3
4
5
6
7
8
9
10
11
12
#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中进行了打印

1
2
3
4
5
6
7
8
9
#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,该函数是一个弱符号函数,可以被用户覆盖

1
2
3
4
5
6
7
8
9
10
11
12
13
#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。实际实现是空函数,没有做记录

1
2
3
4
5
6
#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) {
}