前言
得力于 msh 我们可以在 rt-thread 运行的时候执行一些内置命令,查看系统运行状态。
但是对于一个嵌入式开发工程师,有这些是远远不够的。更多时候,我们想知道一些更细节的东西。
比如,线程控制块结构体在内存中的数据,或者某个外设几个寄存器的值,或者某个链表,从一个节点找到下一个节点,进而找到链表上所有链表节点。
实现几个小工具
目前已经实现的有:
od_mem - show memory value in hex
od_thread - dump thread
od_sem - dump semaphore
od_event - dump event
od_mutex - dump mutex
od_mailbox - dump mail box
od_msgqueue - dump message queue
od_memheap - dump memory heap
od_memheap_item - dump memory heap item
od_mempool - dump memory pool
od_timer - dump timer
其中,第一个 od_mem 有帮助信息,其它的暂时没有,具体使用请看下面详解。
od_mem
msh >od_mem
Usage: od_mem [-gl]
<start address> start address of memory[hex]
<end address> end address of memory[hex]
[-g <group size>] group size[4]
[-l <line size>] line size[32]
使用很简单,给定起始地址(开始地址必须小于结束地址),gl 两个参数使用默认值也可以。
msh >od_mem 0xE000E010 0xE000E01F
0xE000E010 07000000 3F900200 A22F0100 3E490040 ....?.......>I.@
注:请勿尝试读取非法内存地址,这将引起 Hard Fault!!!
od_sem
下面以 od_sem 为例,说明后面几个命令怎么和 od_mem 配合使用,来在线调试内核。
单独执行 od_sem 命令,列出当前所有对象名,对象首地址,以及信号量的值。
msh >od_sem
sem addr value
-------- | ---------- | -----
shrx 0x2000A4D8 0
GuiSem 0x20009E84 0
sdram 0x20001FC4 1
heap 0x20004128 1
后面带上信号量对象名,可以有如下更详细的输出信息。
msh >od_sem shrx
0x2000A4D8 73687278 00000000 81000000 909E0020 2C0F0020 ECA40020 ECA40020 00000000 shrx........... ,.. ... ... ....
name: shrx
type: 0x81
flag: 0x00
prev object: 0x20000F2C
next object: 0x20009E90
prev suspend: 0x2000A4EC
next suspend: 0x2000A4EC
value: 0x0000
首先是 rt_semaphore 结构体在内存中的十六进制格式显示。然后是所有 rt_semaphore 对象元素的值,包括对象名,类型,初始化 flags,对象链表,挂起线程链表等等。
其中 “prev suspend” “next suspend” 是等待当前信号量对象的所有线程列表。’shrx’ 首地址是 ‘0x2000A4D8’, ‘0x2000A4EC’ 恰恰指向了 ‘shrx’ 说明没有任何线程等待这个信号量。
注:’0x2000A4D8’ 和 ‘0x2000A4EC’ 的值差见下文。
进一步,通过使用 od_mem ,查看 0x20000F2C 0x20009E90 内存附近有什么。
msh >od_mem 0x20000F20 0x20000F4C
0x20000F20 A45D0020 80000000 01000000 E4A40020 34410020 20000000 02000000 24470020 .]. ........... 4A. .......$G.
0x20000F40 8C1D0020 24000000 03000000 4C ... $.......L
msh >od_mem 0x20009E84 0x20009EB0
0x20009E84 47756953 656D0000 01010000 D01F0020 E4A40020 989E0020 989E0020 00000000 GuiSem......... ... ... ... ....
0x20009EA4 A11EA01E E4400020 F89E0020 6C .....[url=home.php?mod=space&uid=2018223]@.[/url] ... l
注:’0x20000F2C’ 和 ‘0x20000F20’ 的值差见下文,下同。
可以看出来,0x20009E84(0x20009E90 - 0xC) 这个位置是一个叫 “GuiSem” 的对象,而 0x20000F20 这个位置没有明显的名称,大胆猜测,这里是一个链表头。
继续使用 od_sem 查看下去,
msh >od_sem heap
0x20004128 68656170 00000000 81010000 2C0F0020 D01F0020 3C410020 3C410020 01000000 heap........,.. ... <A. <A. ....
name: heap
type: 0x81
flag: 0x01
prev object: 0x20001FD0
next object: 0x20000F2C
prev suspend: 0x2000413C
next suspend: 0x2000413C
value: 0x0001
heap 对象的 ‘next object’ 的值和 shrx 对象的 ‘prev object’ 完全相等。这说明刚好是同一个链表的头节点。
od_thread
类似的,可以查看线程的。
msh >od_thread
thread addr pri init pri flags stack addr stack size sp tick timer
-------- | ---------- | --- | -------- | -------- | ---------- | ---------- | ---------- | ---- | ----------
tshell 0x2000A700 30 30 0x00 0x2000A798 0x00000800 0x2000AE34 3 0x2000A74C
ledtick 0x2000A328 30 30 0x00 0x2000A3C0 0x00000100 0x2000A43C 2 0x2000A374
tidle0 0x20003664 31 31 0x00 0x200036E4 0x00000800 0x20003E8C 5 0x200036B0
timer 0x20004248 30 30 0x00 0x200042C8 0x00000400 0x2000466C 10 0x20004294
main 0x20005D98 16 16 0x00 0x20005E30 0x00004000 0x20009CF4 17 0x20005DE4
msh >od_thread timer
0x20004248 74696D65 72000000 80000000 A45D0020 70360020 5C420020 5C420020 6C460020 timer........]. p6. \B. \B. lF.
0x20004268 9FAA0108 00000000 C8420020 00040000 00000000 021E1E00 00000040 00000000 .........B. ...............@....
0x20004288 00000000 0A000000 0A000000 74696D65 72000000 89000000 F05D0020 BC360020 ............timer........]. .6.
0x200042A8 A8420020 A8420020 69A20108 48420020 00000000 00000000 00000000 00000000 .B. .B. i...HB. ................
name: timer
type: 0x80
flag: 0x00
prev object: 0x20003670
next object: 0x20005DA4
prev thread: 0x2000425C
next thread: 0x2000425C
sp: 0x2000466C
entry: 0x0801AA9F
parameter: 0x00000000
stack addr: 0x200042C8
stack size: 0x00000400
error: 0
stat: 2
curr prior: 30
init prior: 30
value: 0
value: 0
init tick: 10
value: 10
根据每一个线程的 “stack addr” “stack size”,再借助 “od_mem” 命令,可以查看每一个线程的线程栈使用情况。不需要用仿真器打断点必须停下来看了。
查看外设寄存器
通过查看手册可以得知,SysTick 的值是 0xE000E010,
msh >od_mem 0xE000E010 0xE000E01F
0xE000E010 07000000 3F900200 A22F0100 3E490040 ....?.......>I.@
SysTick_Type 定义为
typedef struct
{
__IOM uint32_t CTRL; /*!< Offset: 0x000 (R/W) SysTick Control and Status Register */
__IOM uint32_t LOAD; /*!< Offset: 0x004 (R/W) SysTick Reload Value Register */
__IOM uint32_t VAL; /*!< Offset: 0x008 (R/W) SysTick Current Value Register */
__IM uint32_t CALIB; /*!< Offset: 0x00C (R/ ) SysTick Calibration Register */
} SysTick_Type;
由此可知,SysTick->CTRL = 0x00000007,SysTick->LOAD = 0x0002903F,SysTick->,SysTick->LOAD = 0x0002903F = 0x00012FA2
其它外设寄存器依此类推。
全局变量
不能通过全局变量名称查看变量的值,除非你知道那个变量在内存中的地址。
rt-thread 中的几种对象指针偏移计算方法
由 struct rt_object 定义可知
struct rt_object
{
char name[RT_NAME_MAX];
rt_uint8_t type;
rt_uint8_t flag;
#ifdef RT_USING_MODULE
void *module_id;
#endif
rt_list_t list;
};
typedef struct rt_object *rt_object_t; /**< Type for kernel objects. */
list 元素和 rt_object 结构体首地址有个偏移,这个偏移大小是 RT_NAME_MAX + 4 [+ 4]。
所以,上文 0x20000F2C - 0xC = 0x20000F20,0x20009E90 - 0xC = 0x20009E84。
又 struct rt_ipc_object 结构体定义
struct rt_ipc_object
{
struct rt_object parent;
rt_list_t suspend_thread;
};
信号量等同步消息机制对象的 ‘suspend_thread’ 和 对象首地址偏移大小是 sizeof(struct rt_object)。
所以,0x2000A4D8 + 0x14 = 0x2000A4EC。
同理,可以计算出 struct rt_thread 中的 list 和 tlist 两个元素相对于首地址偏移大小。
结尾
这次有这个想法,一个是想添加一种调试的方式,另一个是想使用这种方式,减少暂停系统运行的前提下可以查看内核资源,可以查看某些外设配置。虽然不够直观,但也算是一种在无法停止系统运行的状况下,窥探系统的无奈之举。
特别声明:获取内核对象过程中没有使用任何关中断保护。不能确保在打印某个对象信息的过程中,该对象被意外修改或者删除了。但是,考虑此工具仅用于 debug ,为了将内存占用和对系统影响降到最低,不做关中断,不对异变内存做缓存处理。
鉴于此,使用过程中可能存在数据异常,需要重复几次,排除异常数据。
目前不能十分确定,在遍历链表的过程中,是否会引起 CPU 异常等严重后果。
PS
新增头文件,引出 c 调用接口,摆脱 finsh 可以在代码中的任意位置使用这些功能。
添加 -h 和 -a 两个命令行参数。前一个显示帮助信息,后一个用于打印出内核对象链表上所有节点对象的关键信息。
原作者:出出啊