keil MDK共享库实现(symdefs符号表、svc_hander)

共享库非静态库、动态库,共享库是将函数或变量放置在flash的共享区,以备多个应用程序调用或者起到安全隔离效果,有如下实现方式:

1、使用函数指针的形式,将共享区的函数封装到一个函数指针结构体,将该结构体放在flash固定区域,应用程序通过该函数指针结构体调用共享区函数

(29条消息) 如何在MCU中使用二进制库(动态库)_strongerHuang的博客-CSDN博客

 

2、使用svc_handler机制,将共享区的函数以__svc(x)的方式声明,当应用程序调用函数时,系统就会触发svc异常,在svc异常中处理函数调用与返回,函数调用通过系统堆栈获取函数参数,函数返回值通过压栈的方式返回

nRF5 SDK软件架构及softdevice工作原理 - iini - 博客园 (cnblogs.com)

(29条消息) 一个特殊的中断:SVCall_Jin Sheng的博客-CSDN博客_c语言svcall

(29条消息) Nordic系列芯片讲解六 ( Nordic协议栈与应用层API的实现方式)_paul_zhang0932的博客-CSDN博客_svc number

 

3、利用--symdefs=syscall.sym导出共享区的函数与变量的符号位置表到syscall.sym中,应用程序链接时通过查找该文件确定函数或变量的地址

(29条消息) STM32 实现类似手机运行APP的方法(篇一:位置无关和系统函数调用)_明月清风旧的博客-CSDN博客_keil位置无关编译

(29条消息) Keil symdefs文件_deparks的博客-CSDN博客

Cortex-M3动态加载三(模块调用系统函数) - ppym - 博客园 (cnblogs.com)

 

使用1、3方法时,共享库定义的数据变量不会被系统初始化,可用如下方法初始化变量:

a、可通过初始化共享库执行环境初始化变量,即程序需要运行到共享区的main函数,然后再跳转到主APP,原理是运行到main函数时,系统已经运行完成__main函数完成执行环境初始化

b、可通过应用程序主动初始化共享库变量

如共享区定义一个变量int var; 通过符号表syscall.sym导出给应用程序,应用程序中使用__attribute__ ((weak)) int var;弱定义该变量,然后在应用程序区给该变量赋值如var=5.编译的时候编译器会从符号表syscall.sym中查找到该变量在共享区的真正地址,从而实现系统给共享区变量赋值

共享库工程linker中的misc control中加入--entry=share_lib --first=share_lib --symdefs=syscall.sym --keep=*,其中--keep=*保证定义的共享库函数不被编译器优化掉,能够正确导出到符号表

另外如果只是纯粹的产生共享库二进制文件,不需要启动文件、芯片库函数文件等,分散加载文件中需要屏蔽*.o (RESET, +First)与*(InRoot$$Sections)

其中*.o (RESET, +First)的意思是指定RESET节区的地址为启动地址,节区特性可以使用 “+FIRST” 或 “+LAST” 选项配置它要存储到的位置,FIRST 存储到区域的头部,LAST 存储到尾部。通常重要的节区会放在头部,而 CheckSum (校验和)之类的数据会放在尾部。分散文件中使用 “(RESET,+First)” 选择了 RESET 节区,并要求把它放置到本区域第一个位置,而 RESET 是工程启动代码中定义的向量表,该向量表中定义的堆栈顶和复位向量指针必须要存储在内部 FLASH 的前两个地址,这样 STM32 才能正常启动,所以必须使用 FIRST 控制它们存储到首地址。因为不需要启动代码,所以分散加载文件中需要屏蔽此句

“*(InRoot$$Sections)” 是一个链接器支持的特殊选择符号,它可以选择所有标准库里要求存储到 root 区域的节区,如 __main.o、__scatter.o 等内容。C的执行环境就是在此处的代码中产生的,如数据解压缩,已初始化数据从ROM搬运到RAM,未初始化数据区清零等,只是纯粹的产生共享库二进制文件则不需要此执行环境,所以分散加载文件中需要屏蔽此句

 

gitee示例工程:mcu share lib: MCU实现共享库 (gitee.com),使用2、3方式实现共享库

 

posted @ 2023-01-06 11:47  yeshenmeng  阅读(523)  评论(0编辑  收藏  举报