RT-Thread的Finsh实现学习

学习原因

        工作中,使用同事开发的调试软件,输入参数打印的函数名就可以打印参数,但看不到代码实现,只能用自己微薄的知识积累去猜一下,之前尝试过,专门写一个函数,去解析编译生成的map文件,就可以通过函数名去找到函数的地址,然后用函数指针去运行就可以了,最近工作之余在学习RT-Thread的内核源码,刚好看到了Finsh组件,可以在命令行输入函数名运行,所以就简单学习了下;

自定义MSH命令

举例引入

        如下是最简单的“hello world"函数, 在函数定义下面有一行宏,纸面意思是Finsh函数导出,下面对宏进行分析:

void hello(void)
{
    printf("hello RT-Thread!\n");
}
FINSH_FUNCTION_EXPORT(hello, say hello world);

宏定义说明

        先看下RT-Thread官方文档对如下宏定义的说明:

FINSH_FUNCTION_EXPORT(hello, say hello world);

 

宏定义源码分析

#define FINSH_FUNCTION_EXPORT(name, desc)   \
    FINSH_FUNCTION_EXPORT_CMD(name, name, desc)

/* else分支下的宏定义 */
#define FINSH_FUNCTION_EXPORT_CMD(name, cmd, desc)                      \
        const char __fsym_##cmd##_name[] SECTION(".rodata.name") = #cmd;    \
        const char __fsym_##cmd##_desc[] SECTION(".rodata.name") = #desc;   \
        RT_USED const struct finsh_syscall __fsym_##cmd SECTION("FSymTab")= \
        {                           \
            __fsym_##cmd##_name,    \
            __fsym_##cmd##_desc,    \
            (syscall_func)&name     \
        };

/* 上面宏定义中调用的宏的定义 */
#define SECTION(x)    __attribute__((section(x)))
#define RT_USED       __attribute__((used))

/* 上面宏定义中使用的结构体定义 */
struct finsh_syscall
{
    const char*     name;       /* the name of system call */
    const char*     desc;       /* description of system call */
    syscall_func    func;       /* the function address of system call */
};

/* 上面宏定义中使用的函数指针定义 */
typedef long (*syscall_func)(void);

宏展开后的真相

        可以看到展开之后,是一个finsh_syscall结构体类型的变量"__fsym_hello",在后续章节可以得到证实;

FINSH_FUNCTION_EXPORT(hello, say hello world);

/* 展开之后 */
const char __fsym_hello_name[] __attribute__((section(".rodata.name"))) = "hello";              \
const char __fsym_hello_desc[] __attribute__((section(".rodata.name"))) = "say hello world";    \
__attribute__((used)) const struct finsh_syscall __fsym_hello __attribute__((section("FSymTab"))) = \
{                            \
    __fsym_hello_name,       \
    __fsym_hello_desc,       \
    (syscall_func)&hello     \
};

代码编译

        代码编译是在Ubuntu系统下,如下主要说明链接脚本文件生成和代码编译

链接文件生成

         指令如下:

ld --verbose

生成信息如下,红框中的内容需要删除掉,最后一行红框中的内容也需要删除,否则编译出错

代码编译

        代码编译是在Ubuntu系统下编译,编译指令如下:

gcc main.c -T link.lds -o main -Wl,-Map,test.map

其中"link.lds"链接时指定的链接文件,在上一个小节中生成,"test.map"是编译生成的map文件;

MAP文件分析

        分析编译生成的map文件,如下是”__fsym_hello_name“和”__fsym_hello_desc“的段属性:

        如下是"__fsym_hello"的段属性:

通过如上的分析,说明前面的宏定义分析是合理的,展开后的变量在编译生成的map文件中存在;

链接文件分析

        本章节第一小节说明了链接文件的生成过程,”.rodata“段是链接脚本生成的时候就有的,需要在".text"段添加"FSymTab"段,需要添加的代码如下:

. = ALIGN(8);
    __fsymtab_start = .;
    KEEP(*(FSymTab))
    __fsymtab_end = .; 
. = ALIGN(8);

添加完成后的".text" 段如下:

主要关注下:"__fsymtab_start"和"__fsymtab_end",是"FSymTab"段的首地址变量和尾地址变量,可以在代码中使用;如果不添加如上代码段,在代码中就无法使用这2个变量,同时也无法编译通过;

代码实现分析

        在板级代码中分析,在初始化任务中会调用finsh_system_init()函数,其注释是:初始化Finsh,所以就从这个代码入手分析,摘取了主要代码如下:

初始化关键变量

void finsh_system_init(void)
{
    extern const int __fsymtab_start;
    extern const int __fsymtab_end;

	finsh_system_function_init(&__fsymtab_start, &__fsymtab_end);
}

/* 全局变量声明 */
struct finsh_syscall *_syscall_table_begin  = NULL;
struct finsh_syscall *_syscall_table_end    = NULL;

/* 函数调用 */
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;
}

帮助信息函数

 如上代码主要是设置了"_syscall_table_begin"和“_syscall_table_end”这2个全局变量,然后根据这2个全局变量找下是咋用的,找到了函数msh_help(),这个函数也导出了,在命令行输入也可以运行,并输出help帮助信息;

nt msh_help(int argc, char **argv)
{
    rt_kprintf("RT-Thread shell commands:\n");
    {
        struct finsh_syscall *index;

        for (index = _syscall_table_begin;
                index < _syscall_table_end;
                FINSH_NEXT_SYSCALL(index))
        {
            if (strncmp(index->name, "__cmd_", 6) != 0) continue;
#if defined(FINSH_USING_DESCRIPTION) && defined(FINSH_USING_SYMTAB)
            rt_kprintf("%-16s - %s\n", &index->name[6], index->desc);
#else
            rt_kprintf("%s ", &index->name[6]);
#endif
        }
    }
    rt_kprintf("\n");

    return 0;
}
FINSH_FUNCTION_EXPORT_ALIAS(msh_help, __cmd_help, RT-Thread shell help.);

/* for循环里面用到的宏定义 */
#define FINSH_NEXT_SYSCALL(index)  index=finsh_syscall_next(index)

/* 宏定义里面调用的函数 */
struct finsh_syscall* finsh_syscall_next(struct finsh_syscall* call)
{
    unsigned int *ptr;
    ptr = (unsigned int*) (call + 1);
    while ((*ptr == 0) && ((unsigned int*)ptr < (unsigned int*) _syscall_table_end))
        ptr ++;

    return (struct finsh_syscall*)ptr;
}

MSH命令执行过程

在finsh_system_init()函数里面创建了个任务,猜想分析应该是用来执行shell指令的,相关的代码如下,需要说明下,如下代码就大概看了下,可能有问题,请勿完全相信:

int finsh_system_init(void)
{
	rt_thread_t tid = &finsh_thread;
	result = rt_thread_init(&finsh_thread,
                            FINSH_THREAD_NAME,
                            finsh_thread_entry, 
							RT_NULL,
                            &finsh_thread_stack[0], 
							sizeof(finsh_thread_stack),
                            FINSH_THREAD_PRIORITY, 
							10);
	rt_thread_startup(tid);
}

void finsh_thread_entry(void *parameter)
{
	while (1)
    {
        ch = finsh_getchar();
		msh_exec(shell->line, shell->line_position);
	}
	
}

int msh_exec(char *cmd, rt_size_t length)
{
	if (_msh_exec_cmd(cmd, length, &cmd_ret) == 0)
    {
        return cmd_ret;
    }
}

static int _msh_exec_cmd(char *cmd, rt_size_t length, int *retp)
{
	cmd_function_t cmd_func;
	
	cmd_func = msh_get_cmd(cmd, cmd0_size);
	
	*retp = cmd_func(argc, argv);
}

简单代码实现

        MSH部分的源代码

        根据以上的分析,写了个简单的测试程序如下:

void finsh_system_init(void)
{
    extern const int __fsymtab_start;
    extern const int __fsymtab_end;

    unsigned int addr_offset = (char *)&__fsymtab_start + 8;

    //_syscall_table_begin = (struct finsh_syscall *) &__fsymtab_start;
    /* 看map表偏移了8byte,指向了FSymTab段第一个变量的首地址 */
    _syscall_table_begin = (struct finsh_syscall *) addr_offset;        
    _syscall_table_end = (struct finsh_syscall *) &__fsymtab_end;

    printf("addr_offset                   = %x\n",addr_offset);

    printf("&__fsymtab_start              = %x\n",&__fsymtab_start);
    printf("&__fsymtab_end                = %x\n",&__fsymtab_end);

    printf("_syscall_table_begin          = %x\n",_syscall_table_begin);
    printf("_syscall_table_end            = %x\n",_syscall_table_end);

    printf("syscall_table length          = %ld\n\n\n",((unsigned int)&__fsymtab_end) - ((unsigned int)&__fsymtab_start));
}

void msh_help()
{
    struct finsh_syscall *index;

    for (index = _syscall_table_begin;index < _syscall_table_end; FINSH_NEXT_SYSCALL(index))
    {
        printf("%-8s \t - %s\n", &index->name[0], index->desc); 
        index->func();
        printf("\n\n");
    }
}

代码分析

        在finsh_system_init()函数中,_syscall_table_begin指向的是"FSymTab"段第一个变量的首地址,而源代码中指向的是"FSymTab"段的首地址变量,这块应该是和地址对齐有关,因时间等原因没仔细分析,查看map文件后,简单的把地址做了个便宜;

        在源码的msh_help()函数中,遍历了"FSymTab"段中的各变量,打印函数名及函数描述信息,所以我增加了一行index->func(),这一行就可以调用导出的函数;

MAP文件分析

        如下是编译生成的map文件中,"FSymTab"段的地址信息,其中段的首地址变量和尾地址变量的地址用红框标注,首尾地址之间的是2个段变量,其实就是前面宏展开后的变量;可以看到段首地址变量的地址和绿框中第一个段变量的地址差了8,所以在代码里面强制偏移了8;同时注意到,绿框中2个变量的首地址相差32byte,因为”finsh_syscall“结构体的大小是32byte;

要导出的函数

        上一小节的map文件中,有2个变量,这2个变量就是测试代码中导出的2个函数:

void rt_show_version(void)
{
    printf("\n \\ | /\n");
    printf("- RT -     Thread Operating System\n");
    printf(" / | \\     %d.%d.%d build %s\n",
               3, 0, 05, __DATE__);
    printf(" 2006 - 2018 Copyright by rt-thread team\n");
}
FINSH_FUNCTION_EXPORT(rt_show_version, show rt_thread version);

void hello(void)
{
    printf("hello RT-Thread!\n");
}
FINSH_FUNCTION_EXPORT(hello, say hello world);

同时在主函数里面只需要调用finsh_system_init()和 msh_help()即可

运行结果

        代码运行结果如下,”finsh_syscall“结构体的大小是32byte;导出的函数rt_show_version()和hello()也正常运行了;地址也和map文件完全一致;

  • 18
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值