UCOSII任务挂起点跟踪

一、目的

      一般来说ARM的IDE/调试器不提供UCOSII多任务任务调用函数调用关系的查询,本文旨在提供一种查看UCOSII任务调用堆栈的方法。

二、UCOSII任务切换简介

      UCOSII任务切换方式有两种,一种是通过触发软中断来切换,另一种是直接跳转(分中断里跳转和任务执行中跳转)。无论怎样切换,都是把当前执行的任务内容压入当前任务的堆栈中去,然后设置任务SP的值为优先级最高的任务的堆栈,然后出栈恢复寄存器为最高优先级的任务挂起前的值,亦即还原任务状态,让最高优先级的任务跑下去。而保存任务挂起前的寄存器的值的堆栈,就是我们跟踪任务的调用关系的工具。

      无论哪种方式的切换,最终都是要切换到要执行的任务,亦即把要执行的任务的堆栈出栈,这样,我们可以获知寄存器在任务堆栈里的位置,我们看切换最后面的一点代码就可以获知。例如,我们可以从下面代码获知,挂起任务堆栈里的顺序是:

CPSR,R0,R1...R12,LR,PC。

 LDMFD   SP!, {R0}                                           ;    Pop new task's CPSR,
    MSR     SPSR_cxsf, R0

;    Pop new task's context.
    LDMFD   SP!, {R0-R12, LR}
    LDMFD   SP!, {PC}^

      UCOSII的任务堆栈保存在每个任务控制块里面,如下所示,其成员OSTCBStkPtr就是任务堆栈指针:

/*
*********************************************************************************************************
*                                          TASK CONTROL BLOCK
*********************************************************************************************************
*/

typedef struct os_tcb {
    OS_STK          *OSTCBStkPtr;           /* Pointer to current top of stack                         */

#if OS_TASK_CREATE_EXT_EN > 0u
    void            *OSTCBExtPtr;           /* Pointer to user definable data for TCB extension        */
    OS_STK          *OSTCBStkBottom;        /* Pointer to bottom of stack                              */
    INT32U           OSTCBStkSize;          /* Size of task stack (in number of stack elements)        */
    INT16U           OSTCBOpt;              /* Task options as passed by OSTaskCreateExt()             */
    INT16U           OSTCBId;               /* Task ID (0..65535)                                      */
#endif

    struct os_tcb   *OSTCBNext;             /* Pointer to next     TCB in the TCB list                 */
    struct os_tcb   *OSTCBPrev;             /* Pointer to previous TCB in the TCB list                 */

#if (OS_EVENT_EN)
    OS_EVENT        *OSTCBEventPtr;         /* Pointer to          event control block                 */
#endif

#if (OS_EVENT_EN) && (OS_EVENT_MULTI_EN > 0u)
    OS_EVENT       **OSTCBEventMultiPtr;    /* Pointer to multiple event control blocks                */
#endif

#if ((OS_Q_EN > 0u) && (OS_MAX_QS > 0u)) || (OS_MBOX_EN > 0u)
    void            *OSTCBMsg;              /* Message received from OSMboxPost() or OSQPost()         */
#endif

#if (OS_FLAG_EN > 0u) && (OS_MAX_FLAGS > 0u)
#if OS_TASK_DEL_EN > 0u
    OS_FLAG_NODE    *OSTCBFlagNode;         /* Pointer to event flag node                              */
#endif
    OS_FLAGS         OSTCBFlagsRdy;         /* Event flags that made task ready to run                 */
#endif

    INT32U           OSTCBDly;              /* Nbr ticks to delay task or, timeout waiting for event   */
    INT8U            OSTCBStat;             /* Task      status                                        */
    INT8U            OSTCBStatPend;         /* Task PEND status                                        */
    INT8U            OSTCBPrio;             /* Task priority (0 == highest)                            */

    INT8U            OSTCBX;                /* Bit position in group  corresponding to task priority   */
    INT8U            OSTCBY;                /* Index into ready table corresponding to task priority   */
    OS_PRIO          OSTCBBitX;             /* Bit mask to access bit position in ready table          */
    OS_PRIO          OSTCBBitY;             /* Bit mask to access bit position in ready group          */

#if OS_TASK_DEL_EN > 0u
    INT8U            OSTCBDelReq;           /* Indicates whether a task needs to delete itself         */
#endif

#if OS_TASK_PROFILE_EN > 0u
    INT32U           OSTCBCtxSwCtr;         /* Number of time the task was switched in                 */
    INT32U           OSTCBCyclesTot;        /* Total number of clock cycles the task has been running  */
    INT32U           OSTCBCyclesStart;      /* Snapshot of cycle counter at start of task resumption   */
    OS_STK          *OSTCBStkBase;          /* Pointer to the beginning of the task stack              */
    INT32U           OSTCBStkUsed;          /* Number of bytes used from the stack                     */
#endif

#if OS_TASK_NAME_EN > 0u
    INT8U           *OSTCBTaskName;
#endif

#if OS_TASK_REG_TBL_SIZE > 0u
    INT32U           OSTCBRegTbl[OS_TASK_REG_TBL_SIZE];
#endif

		INT8U 			need_free_stack;
} OS_TCB;

三、函数调用指令简介

      函数调用一般都是在函数开始处把函数里用到的寄存器和LR压入栈,函数内容处理完后,再出栈到寄存器和PC,返回调用处。

四、方法

       通过二、三的介绍,我们知道对于不是正在执行的任务,其每一级的执行状态是保存到其自身的堆栈里的,调用关系完全可以通过任务堆栈来跟踪。具体操作如下:

       a.通过反编译工具把axf文件转换成汇编文件,用以查看压入栈的寄存器个数和地址对应的函数名;

       b.遍历任务控制块(保存在OSTCBPrioTbl里),打印其堆栈指针;

       c.根据二里介绍查看任务堆栈寄存器位置和个数的方法,打印出任务挂起压入堆栈的寄存器,找到切换前的PC;

       d.查看PC值处的汇编代码,可以找到它是在哪里(哪个函数)被切换的出去的;

       e.如果需要跟踪它是被哪个函数调用的,可以向上查找,统计被压入栈的寄存器个数,直至函数入口处,确定LR在这一块里偏移;

       f.打印栈里该函数压入栈的寄存器出来,根据压入栈LR的偏移,获取LR的值,就可以确定它是被哪个函数调用的;

       g.重复e-f,可以找出所有的调用关系。

五、注意事项

      大部分UCOSII的堆栈都是配置为向下增长的,所以知道压入栈的个数后,获取寄存器的值用的偏移要按照出栈的顺序来。

你可能感兴趣的:(编译原理,UCOS)