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);
#elsert_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文件完全一致;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/34493.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

名侦探李先生第一话:谁是真正的凶手(只出现一次的数字相关题解(力扣)+位操作符回忆)

引子&#xff1a;我们在之前的案子中破解过基础的单身狗问题&#xff0c;那面对更有挑战的案子&#xff0c;且看李先生如何破局&#xff0c;那下凶手&#xff01; 复习&#xff1a; 1&#xff0c;位操作符&#xff1a; 正整数原&#xff0c;反&#xff0c;补码都相同 首位是…

RocketMQ如何添加JVM监控

这里是小奏,觉得文章不错可以关注公众号小奏技术 JVM监控选型 本次JVM监控我们采用prometheus官方提供的jmx_exporter来实现 RocketMQJVM开发 整体目录 1. 新增agent目录 我们在distribution目录新增一个agent模块&#xff0c;然后添加两个文件 jmx_prometheus_javaagent-…

NtripShare2024年第二季度主要技术进展

NtripShare Cloud GNSS解算云平台方面 1、解算引擎增加根据卫星多路径效应自动剔除卫星的算法。 2、解算引擎增加解算时间段限制&#xff08;发现贵州某地在晚12点周期性效果变差&#xff09;。 3、增加2000坐标至地方坐标系转换的支持(七参数、四参数、TGO高程拟合&#x…

[数据集][目标检测]棉花检测数据集VOC+YOLO格式389张1类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;389 标注数量(xml文件个数)&#xff1a;389 标注数量(txt文件个数)&#xff1a;389 标注类别…

办理北京公司注册地址异常变更要求和流程

在北京注册公司时选择注册地址是非常重要的一环&#xff0c;注册地址不仅体现在营业执照上&#xff0c;在网上也有公示信息&#xff0c;一般选用的是商用地址和商住两用地址&#xff0c;在公司经营过程中&#xff0c;因为经营需要变更注册地址&#xff0c;也要依法变更&#xf…

低价可转债崩盘,发生了什么?

下跌不在于“出库”&#xff0c;甚至不在于“风险”。问题更多在于交易层面&#xff0c;何时能积聚更多的左侧资金并成功过渡至右侧。 低价券怎么了&#xff1f; 如果说6月初主要是小微盘品种的退市风险&#xff0c;后来是一些评级下调的品种&#xff0c;到本周&#xff0c;已…

ONLYOFFICE 桌面编辑器 8.1重磅来袭:全新功能提升您的办公效率

文章目录 前言ONLYOFFICE 桌面编辑器8.1一、PDF编辑&#xff1a;告别“头痛”时刻二、幻灯片版式&#xff1a;秒变“设计大师”三、无缝切换&#xff1a;办公界的“快速通道”四、语言支持&#xff1a;全球通吃的“翻译官”五、 隐藏“连接到云”板块&#xff1a;摆脱“云”的束…

26届软件工程生大二末的学期总结

一、前言&#x1f680;&#x1f680;&#x1f680; ☀️ 要不断的、反复的&#xff0c;爱上这个普通的自己。 本文简介&#xff1a;本人是大二软件工程专业&#xff0c;大二即将结束步入大三&#xff0c;这篇文章作为我的个人小笔记&#xff0c;只想在这里记录当下的心情与学习…

MySQL之可扩展性(一)

可扩展性 概述 有些应用仅仅适用于一台或少数几台服务器&#xff0c;那么哪些可扩展性建议是和这些应用相关的呢&#xff1f;大多数人从不会维护超大规模的系统&#xff0c;并且通常也无法效仿在主流大公司所使用的策略。选择一个合适的策略能够大大地节约时间和金钱。 MySQL…

图片转pdf,图片转pdf在线转换,在线图片转pdf

图片转PDF&#xff0c;听起来似乎是一个简单的操作&#xff0c;但实际上&#xff0c;它涉及到许多细节和技巧。有时候我们需要将图片转换为PDF格式&#xff0c;以便于分享、打印或保存。那么&#xff0c;如何将图片转换成PDF呢&#xff1f;接下来&#xff0c;我将为您详细介绍几…

【产品经理】订单处理9-台账库存管理

在订单处理过程中&#xff0c;台账库存的具体设计怎么做&#xff1f; 在订单处理过程中&#xff0c;分配仓库成功后要扣除仓库库存并计算商品缺货情况&#xff0c;仓库库存就是台账库存。 1&#xff0c;台账库存是针对某个仓库的库存&#xff0c;且台账库存只计算此商品SKU的库…

如何通过JDBC获取数据库连接 , 实现对单表数据增、删、改 ,实现对单表数据查询以及根据别名查询处理结果集扩展

1、获取连接 能够通过JDBC获取数据库连接 讲解 API介绍 java.sql.DriverManager类中有如下方法获取数据库连接 static Connection getConnection(String url, String user, String password) 连接到给定数据库 URL &#xff0c;并返回连接。 参数说明 String url&#x…

使用深度相机D435i+YOLOv8实现物体三维坐标实时显示

一、获取相机内参 下列指令为获取相机内参指令&#xff0c;输入此指令前需要获得相机的深度帧和彩色帧数据。 如何使用vsCode打开intel D435i深度相机 # 获取相机内参 depth_intrinsics depth_frame.profile.as_video_stream_profile().intrinsics color_intrinsics color…

Tomcat多实例配置

目录 一. 复制程序文件 二. 启动tomcat多实例 三. Tomcat多实例负载均衡 多实例&#xff08;多进程&#xff09;&#xff1a;同一个程序启动多次&#xff0c;分为两种情况: 第一种&#xff1a;一台机器跑多个站点&#xff1b; 第二种&#xff1a;一个机器跑一个站点多个实…

代码随想录训练营第十六天 513找树左下角的值 112路径总和I 113路径总和II 106从中序和后序遍历序列构造二叉树

第一题&#xff1a; 原题链接&#xff1a;513. 找树左下角的值 - 力扣&#xff08;LeetCode&#xff09; 思路&#xff1a;用回溯的思想&#xff1a; 这题就是求最大深度&#xff0c;当遍历到第一个最大深度的时候&#xff0c;记录下的节点值就是最左边的元素。 参数和返回…

提取图像主色调

依赖 Pillow 库。 提取图像主色调&#xff0c;直接上代码&#xff1a; from PIL import Imagedef extract_main_color(img_path: str, delta_h: float 0.3) -> str:"""获取图像主色调Args:img_path: 输入图像的路径delta_h: 像素色相和平均色相做减法的绝…

MIL图像处理那些事:定义感兴趣区域ROI的两种方法(示例项目C#源码)

文章目录 效果展示第一种方法:通过鼠标框选GetROIForm构造函数如何缩放--MdispZoom的使用Ctr+滚轮缩放放大两倍:如何平移--MdispPan的使用双击返回ROI第二种方法:直接编辑ROI框显示ROI示例项目C#源码(百度网盘)本示例提供两种方法定义感兴趣区域ROI 效果展示 第一种方法:通过鼠…

测试基础15:测试用例设计方法-场景设计(流程分析)

课程大纲 1、定义 系统多个功能串联形成业务流程&#xff0c;不仅需要验证正确的主流程&#xff0c;而且需要验证各个功能点各种异常情况。 2、应用场景 与因果图&判定表方法的相似之处&#xff1a;界面需手动填写的输入框少&#xff0c;基本只需选择有限的几个&#xff08…

Jenkins定时构建自动化(四):Python 的 argparse 模块

目录 一、主要功能和用途 二、核心类和方法 三、总结 四、argparse模块示例 Jenkins定时构建自动化(一)&#xff1a;Jenkins下载安装配置-CSDN博客 Jenkins定时构建自动化(二)&#xff1a;Jenkins的定时构建-CSDN博客 Jenkins定时构建自动化(三)&#xff1a;手动定时构建…

[FreeRTOS 基础知识] 信号量 概念

文章目录 信号量定义信号量特性 信号量定义 信号量是一个抽象的数据类型&#xff0c;通常包含一个整数值以及一个等待该值变为正数的任务列表&#xff08;也称为等待队列&#xff09;。信号量的整数值代表了系统中某种资源的可用数量。 在操作系统中信号量用于控制对共享资源访…