本文介绍Zephyr tracing系统的公共配置项和调用框架代码分析。
Zephyr支持5种Trace Tool,虽然每种tool的实现都不一样, 但从内核代码埋入tracing API到实际调用到tool是共用的,本文对共用部分进行分析。
配置
CONFIG_TRACING
配置启用tracing系统,如果为n
,tracing系统将不会被编译进zephyr
下面6项是多选1:TRACING_NONE
: 不使用Trace toolCONFIG_PERCEPIO_TRACERECORDER
: 使用trace recoderSEGGER_SYSTEMVIEW
: 使用systemviewTRACING_CTF
: 使用CTSTRACING_TEST
: 使用testTRACING_USER
: 使用user
下面17项是对tracing对象的选择,默认为选中,当配置为n时将不会在该对象中埋入hook APICONFIG_SYSCALL_TRACING
: trace系统调用,目前没有实现CONFIG_TRACING_THREAD
: trace thread,sys_port_trace_type_mask_k_threadCONFIG_TRACING_WORK
: trace workCONFIG_TRACING_ISR
: trace ISRCONFIG_TRACING_SEMAPHORE
: trace信号量CONFIG_TRACING_MUTEX
: trace互斥量CONFIG_TRACING_CONDVAR
: trace条件变量CONFIG_TRACING_QUEUE
: trace queueCONFIG_TRACING_FIFO
: trace FIFOCONFIG_TRACING_LIFO
: trace LIFOCONFIG_TRACING_STACK
: trace stackCONFIG_TRACING_MESSAGE_QUEUE
: trace消息列队CONFIG_TRACING_MAILBOX
: trace邮箱CONFIG_TRACING_PIPE
: trace管道CONFIG_TRACING_HEAP
: trace heapCONFIG_TRACING_MEMORY_SLAB
: trace slabCONFIG_TRACING_TIMER
: trace timer
框架代码
hook宏
所有被trace内核对象中都是调用下面9个宏进行hook1
2
3
4
5
6
7
8
9SYS_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
13int32_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的初始化,埋入下面hook1
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,埋入下面hook1
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,埋入下面hook1
2
3
4
5
6
7
8
9
10void *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,埋入下面hook1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25bool 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
3do { \
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_out1
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_OnTaskStopExec1
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) {
}