RT-Thread MSH_CMD_EXPORT分析
1. 源码分析
在rt-thread中,使用FinSH,可以支持命令行。在源码中,使用MSH_CMD_EXPORT
导出函数到对应命令。
extern void rt_show_version(void);
long version(void)
{
rt_show_version();
return 0;
}
MSH_CMD_EXPORT(version, show RT-Thread version information);
MSH_CMD_EXPORT
是一个宏:
#define MSH_CMD_EXPORT(command, desc) \
MSH_FUNCTION_EXPORT_CMD(command, command, desc)
#define MSH_FUNCTION_EXPORT_CMD(name, cmd, desc) \
const char __fsym_##cmd##_name[] rt_section(".rodata.name") = #cmd; \
const char __fsym_##cmd##_desc[] rt_section(".rodata.name") = #desc; \
rt_used const struct finsh_syscall __fsym_##cmd rt_section("FSymTab")= \
{ \
__fsym_##cmd##_name, \
__fsym_##cmd##_desc, \
(syscall_func)&name \
};
嵌套定义为MSH_FUNCTION_EXPORT_CMD
。
这里的rt_section
也是一个宏:
#define rt_section(x) __attribute__((section(x)))
在ARM中,这是编译器识别的一个符号。用来指定编译后数据存放的位置。
这里相当于是定义__fsym_version_name
和__fsym_version_desc
,将其放到.rodata.name
段中。这两个字符串分别是命令对应的名称和描述。又定义了一个结构体__fsym_version
,用来存放命令的名称,描述和函数指针。
struct finsh_syscall
{
const char *name; /* the name of system call */
#if defined(FINSH_USING_DESCRIPTION) && defined(FINSH_USING_SYMTAB)
const char *desc; /* description of system call */
#endif
syscall_func func; /* the function address of system call */
};
函数指针指向的函数和命令同名。将定义的finsh_syscall
放到FSymTab
段中。
没导出一个命令,就会在.rodata.name
段中多两个字符串,FSymTab
段中多一个struct finsh_syscall
结构体。
导出所有需要的命令后,这里FSymTab
可以看做是一个数组,元素类型是struct finsh_syscall
,长度是所有命令的总和。
2. map文件
编译时,可以指定生成.map
文件。KEIL默认会输出map文件到编译目录。
在map文件中搜索__fsym_version
,可以找到version
命令的名称和描述字符串变量的链接地址和段位置。链接地址是:0x0800ff69
和 0x0800ff71
,链接段是.rodata.name
,与前面分析一致。可以看到上面和下面确实也是其它命令的名称和描述。
还能搜索到__fsym_version
结构体的链接地址和段。地址是0x080100c4
,段是FSymTab
。这里可以看到,所有命令的结构体都存到这个段的,间隔也是正好是12个字节,和struct finsh_syscall
结构体长度一致。看这个情况,应该是照编译时的按顺序摆放所有结构体到这个段中。
这里通过编译时,将这个段的起始地址给到msh,然后通过查这个表来对比命令的名称,匹配上了,就执行相应的函数指针,从而就能够执行对应的命令的函数。
查表:
static cmd_function_t msh_get_cmd(char *cmd, int size)
{
struct finsh_syscall *index;
cmd_function_t cmd_func = RT_NULL;
for (index = _syscall_table_begin;
index < _syscall_table_end;
FINSH_NEXT_SYSCALL(index))
{
if (strncmp(index->name, cmd, size) == 0 &&
index->name[size] == '\0')
{
cmd_func = (cmd_function_t)index->func;
break;
}
}
return cmd_func;
}
_syscall_table_begin
和 _syscall_table_end
变量对应就是FSymTab
段的起始地址。
void finsh_system_function_init(const void *begin, const void *end)
{
_syscall_table_begin = (struct finsh_syscall *) begin;
_syscall_table_end = (struct finsh_syscall *) end;
}
int finsh_system_init(void)
{
extern const int FSymTab$$Base;
extern const int FSymTab$$Limit;
finsh_system_function_init(&FSymTab$$Base, &FSymTab$$Limit);
}
这里这两个全局变量找不到定义的位置。查找资料得知,FSymTab$$Base
表示FSymTab
段的开始地址,FSymTab$$Limit
表示FSymTab
段的结束地址。
参考:https://www.cnblogs.com/King-Gentleman/p/4573652.html
3. bin文件
前面分析得到了__fsym_version_name
和__fsym_version_desc
的地址,分别是0x0800ff69
和 0x0800ff71
,__fsym_version
的地址是0x080100c4
。0x08
开始的地址表示ROM上的地址,即FLASH地址空间。
打开编译生成的rtthread.bin
文件,搜索version
。version
符号出现的地址正好是ff69
,是字符串 “version”,紧接着是描述部分内容 “show RT-Thread version information”。由于是bin文件,是相对地址,因此地址前面没有0x08
。
在跳到100c4
地址:
这里开始的12个字节,对应的就是__fsym_version
结构体中各个字段的内容。注意大小端转换,命令的名称地址69 ff 00 08
,即0x0800ff69
,描述对应的地址是71 ff 00 08
,即0x0800ff71
。函数指针对应的地址是4d ea 00 08
,即0x0800ea4d
,和map文件中链接的地址一致。