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文件中链接的地址一致。



















