书中分享了很多理论,方法和案例,
基于本书,本人在此基础上再分享一些更贴近实战的案例分享。
本人公众号 嵌入式Lee(嵌入式软硬件技术:RTOS,GUI,FS,协议栈,ARM,总线,嵌入式C,开发环境 and blablaba....多年经验分享,非硬货不发,带你扒开每一个技术背后的根本原理。)
一. 前言
本文继续在前面的基础上分析Tracealyzer发送到上位机的事件信息是如何记录的。
直接从代码结合抓包数据进行分析。
二.事件记录格式
trcKernelPort.h中定义了很多事件类型,以PSF_EVENT_开头
每次记录事件,都是按照如下形式进行
先xTraceEventBegin
然后TraceEventAddxxx
然后xTraceEventEnd
其中xTraceEventBegin调用xTraceEventBeginOffline即宏TRC_EVENT_BEGIN_OFFLINE实现如下
#define TRC_EVENT_BEGIN_OFFLINE(uiEventCode, uiPayloadSize, pxEventHandle) \
( \
(xTraceEventBeginRawOffline(sizeof(TraceBaseEvent_t) + (uiPayloadSize), pxEventHandle)) == TRC_SUCCESS ? \
( \
pxTraceEventDataTable->coreEventData[TRC_CFG_GET_CURRENT_CORE()].eventCounter++, \
SET_BASE_EVENT_DATA((TraceBaseEvent_t*)(((TraceEventData_t*)*(pxEventHandle))->pvBlob), \
uiEventCode, \
(((TraceEventData_t*)*(pxEventHandle))->size - sizeof(TraceBaseEvent_t)) / sizeof(uint32_t), \
pxTraceEventDataTable->coreEventData[TRC_CFG_GET_CURRENT_CORE()].eventCounter), \
((TraceEventData_t*)*(pxEventHandle))->offset += sizeof(TraceBaseEvent_t), \
TRC_SUCCESS \
) : TRC_FAIL \
)
其中eventCounter++即递增事件计数,SET_BASE_EVENT_DATA即设置每个事件的基本信息,
事件格式,即如下包括事件ID,总的事件计数,和时间戳
/**
* @internal Trace Base Event Structure
*/
typedef struct {
uint16_t EventID; /**< */
uint16_t EventCount; /**< */
uint32_t TS; /**< */
} TraceBaseEvent_t;
其中 SET_BASE_EVENT_DATA实现如下
#define SET_BASE_EVENT_DATA(pxEvent, eventId, paramCount, eventCount) \
( \
(pxEvent)->EventID = TRC_EVENT_SET_PARAM_COUNT(eventId, paramCount), \
(pxEvent)->EventCount = TRC_EVENT_SET_EVENT_COUNT(eventCount), \
xTraceTimestampGet(&(pxEvent)->TS) \
)
事件ID,TRC_EVENT_SET_PARAM_COUNT如下高4位表示参数个数,低12位表示事件类型,即PSF_EVENT_开始的宏。
#define TRC_EVENT_SET_PARAM_COUNT(id, n) (((uint16_t)(id)) | ((((uint16_t)(n)) & 0xF) << 12))
所以可以看到事件记录格式如下
EEWORLDIMGTK1
添加了上述信息后然后调用TraceEventAddxxx添加参数,
最后调用xTraceEventEnd将事件写入到发送缓存中。
以任务切换事件为例,如果切换到执行某个任务,则回调traceTASK_SWITCHED_IN
即xTraceTaskSwitch
traceResult xTraceTaskSwitch(void *pvTask, TraceUnsignedBaseType_t uxPriority)
{
traceResult xResult = TRC_FAIL;
TraceEventHandle_t xEventHandle = 0;
void* pvCurrent = 0;
TRACE_ALLOC_CRITICAL_SECTION();
(void)pvTask;
(void)uxPriority;
if (xTraceIsRecorderEnabled() == 0)
{
return xResult;
}
TRACE_ENTER_CRITICAL_SECTION();
xTraceStateSet(TRC_STATE_IN_TASKSWITCH);
xTraceTaskGetCurrent(&pvCurrent);
if (pvCurrent != pvTask)
{
xTraceTaskSetCurrent(pvTask);
if (xTraceEventBegin(PSF_EVENT_TASK_ACTIVATE, sizeof(void*) + sizeof(uint32_t), &xEventHandle) == TRC_SUCCESS)
{
xTraceEventAddPointer(xEventHandle, pvTask);
xTraceEventAdd32(xEventHandle, (uint32_t)uxPriority);
xTraceEventEnd(xEventHandle);
xResult = TRC_SUCCESS;
}
}
xTraceStateSet(TRC_STATE_IN_APPLICATION);
TRACE_EXIT_CRITICAL_SECTION();
return xResult;
}
其中核心的事件记录处理如下
if (xTraceEventBegin(PSF_EVENT_TASK_ACTIVATE, sizeof(void*) + sizeof(uint32_t), &xEventHandle) == TRC_SUCCESS)
{
xTraceEventAddPointer(xEventHandle, pvTask);
xTraceEventAdd32(xEventHandle, (uint32_t)uxPriority);
xTraceEventEnd(xEventHandle);
xResult = TRC_SUCCESS;
}
事件类型是PSF_EVENT_TASK_ACTIVATE=0x37
参数为两个
一个(void*)类型的任务地址,即pvTask
一个uint32_t类型的任务优先级,即uxPriority
所以传入xTraceEventBegin的参数大小是sizeof(void*) + sizeof(uint32_t)即两个WORD。
xTraceEventAddPointer添加指针参数,实现如下
#define TRC_EVENT_ADD_POINTER(xEventHandle, value) \
TRC_COMMA_EXPR_TO_STATEMENT_EXPR_3( \
((void**)((TraceEventData_t*)(xEventHandle))->pvBlob)[((TraceEventData_t*)(xEventHandle))->offset / sizeof(void*)] = (value), \
((TraceEventData_t*)(xEventHandle))->offset += sizeof(void*), \
TRC_SUCCESS \
)
xTraceEventAdd32添加ige无符号32位值,实现如下
#define TRC_EVENT_ADD_32(xEventHandle, value) \
TRC_COMMA_EXPR_TO_STATEMENT_EXPR_3( \
((uint32_t*)((TraceEventData_t*)(xEventHandle))->pvBlob)[((TraceEventData_t*)(xEventHandle))->offset / sizeof(uint32_t)] = (value), \
((TraceEventData_t*)(xEventHandle))->offset += sizeof(uint32_t), \
TRC_SUCCESS \
)
xTraceEventAdd32添加ige无符号32位值,实现如下
#define TRC_EVENT_ADD_32(xEventHandle, value) \
TRC_COMMA_EXPR_TO_STATEMENT_EXPR_3( \
((uint32_t*)((TraceEventData_t*)(xEventHandle))->pvBlob)[((TraceEventData_t*)(xEventHandle))->offset / sizeof(uint32_t)] = (value), \
((TraceEventData_t*)(xEventHandle))->offset += sizeof(uint32_t), \
TRC_SUCCESS \
)
上述基本事件信息和参数都存储在pvBlob开始的区域,
最后xTraceEventEnd->xTraceEventEndOffline->xTraceStreamPortCommit将事件存入发送缓存区。
最后清除pvBlob记录,以便下次继续进行事件记录。
#define RESET_EVENT_DATA(p) \
( \
(p)->pvBlob = 0, \
(p)->size = 0, \
(p)->offset = 0 \
)
EEWORLDIMGTK2
对应抓取的包如下
0x2037表示是PSF_EVENT_TASK_ACTIVATE事件,0x00BE是事件计数值,0x00012809是时间戳,,0x081103A8是任务地址,0x00000001是任务优先级。
后面0x20EB是另外一个事件,事件参数有2个,事件ID是0xEB可以看到是PSF_EVENT_UNUSED_STACK,事件计数器0x00BF是在前一个事件基础上递增,时间戳是0x00013911,参数1是0x081103A8,参数2是0x00000FA0.
可以看到xTraceStackMonitorReport中,PSF_EVENT_UNUSED_STACK事件除了基本事件信息后面就是任务地址和栈最低MARK值,这里是0x00000FA0,说明该任务最少剩余该大小栈未使用。
if (xTraceEventBegin(PSF_EVENT_UNUSED_STACK, sizeof(void*) + sizeof(uint32_t), &xEventHandle) == TRC_SUCCESS)
{
xTraceEventAddPointer(xEventHandle, pxStackMonitorEntry->pvTask);
xTraceEventAdd32(xEventHandle, (uint32_t)pxStackMonitorEntry->uxPreviousLowWaterMark);
xTraceEventEnd(xEventHandle);
}
EEWORLDIMGTK3
三.丢事件监测
EEWORLDIMGTK4
如上图live Stream可能会显示丢事件数,其实现原理如下
#define TRC_EVENT_BEGIN_OFFLINE(uiEventCode, uiPayloadSize, pxEventHandle) \
( \
(xTraceEventBeginRawOffline(sizeof(TraceBaseEvent_t) + (uiPayloadSize), pxEventHandle)) == TRC_SUCCESS ? \
( \
pxTraceEventDataTable->coreEventData[TRC_CFG_GET_CURRENT_CORE()].eventCounter++, \
SET_BASE_EVENT_DATA((TraceBaseEvent_t*)(((TraceEventData_t*)*(pxEventHandle))->pvBlob), \
uiEventCode, \
(((TraceEventData_t*)*(pxEventHandle))->size - sizeof(TraceBaseEvent_t)) / sizeof(uint32_t), \
pxTraceEventDataTable->coreEventData[TRC_CFG_GET_CURRENT_CORE()].eventCounter), \
((TraceEventData_t*)*(pxEventHandle))->offset += sizeof(TraceBaseEvent_t), \
TRC_SUCCESS \
) : TRC_FAIL \
)
每记录一个事件时,TRC_EVENT_BEGIN_OFFLINE宏中,事件计数递增eventCounter++,然后
SET_BASE_EVENT_DATA记录到事件信息中
typedef struct {
uint16_t EventID; /**< */
uint16_t EventCount; /**< */
uint32_t TS; /**< */
} TraceBaseEvent_t;
EventCount会递增,上位机可以通过该值是否连续递增来确认是否丢事件,以及丢了多少个事件。
四. 总结
以上分析了Tracealyzer的发送到上位机的事件记录是如何组织的,基于此就可以手动解析事件信息方便调试分析。甚至可以自己开发类似的GUI或者其他测试分析工具。