单片机高阶技能之动态链接库技术实现的讨论

lr2131 2018-06-05 10:32:23
最近做了个单片机的小项目,遇到了flash不够用的问题,本来解决的方法确实有很多,但思来想去,非常想借用window和linux上动态链接库的思路去解决问题。单片机上分布了几个程序,IAP和2组可以被交替升级的app程序。由于它们都有一段很长的代码是相同的,我就想把它做成动态库的方式,这样flash中就只需要一份这样的代码了。而且,想到升级时也可以不用升这段代码,提高升级成功率和缩短升级时间,升级文件变小,等等优点。此外,除了省空间这个直接作用,还能帮助完成一些特殊的技能,这些都让我下定了决心要搞出来。最后花了近两个星期时间,终于是搞出来了,用到产品上也没问题。但对这套方法,我还有一些不太理解的地方,也有一些需要改进和完善的地方,以便适合更多的初学者用,想和坛子里的童鞋们多讨论下。将在后天放出完整工程源码!


演示用的CPU是STM32F407VET6,IDE请用MDK5.25,低版本的MDK打开可能会有问题。


可能有些童鞋看到这里,仍然以为我说的是用静态库的方式。请注意,静态库是不能省flash的,注意这里说的flash,是内存直接寻址的flash空间,不是指spi 的那种flash。如果这里用nand或nor,那种非常大,几乎不可能不够用了。

静态库只是为了屏蔽源码,保护源码。你有几个工程链接静态库,就会有几份拷贝,它不会省空间。此外,制作静态库,生成是*.a,或*.lib ,完全用编译器或IDE就可以搞定。但动态库,这里生成的是bin,或者是hex。而且也不是单纯的用编译器或IDE就能搞定。

如果你需要把代码加载到RAM中运行,且有多个线程需要用到这个代码,也可以用动态链接的方法,即使你需要这段代码支持可重入的特性,这也是可以做到的。

问题点:

一、一直不太明白的一点是,做静态变量重定向的R9寄存器,原则上是该填什么值?

以下贴出关键代码片段:

__global_reg(6) unsigned int *StaticVariableRelocate;
extern unsigned int *DynamicStaticVar=0;// Ö¸ÏòdllµÄ¾²Ì¬±äÁ¿
extern unsigned int HostStaticVar = 0x123455AA;

Fun DelayUs = 0;
TFun RunHostFun = 0;
NFun StaticVarInit = 0;

static unsigned int DLL_Len = 0;
static unsigned int DLL_Version = 0;

static unsigned int __attribute__((aligned(8))) DynamicRAM[SIZE_MEM_INT];
extern void DynamicLoader(unsigned int AddrDynamicLinkLib)
{
unsigned int *dst,*src;
unsigned int *RAM_ADDRESS_DLY = (unsigned int *)DynamicRAM;
unsigned int DLL_Static_len,DLL_Static_offset_addr;
unsigned int i;


DLL_Len = *((unsigned int *)AddrDynamicLinkLib);// 从DLL首地址获取文件长度
DLL_Version = *((unsigned int *)(AddrDynamicLinkLib + 4));// 获取DLL版本号
DLL_Static_offset_addr = *((unsigned int *)(AddrDynamicLinkLib + 8));// 获取动态库中静态变量区的首地址
DLL_Static_len = DLL_Len - DLL_Static_offset_addr;//动态链接库的静态区全长


//在RAM中为DLL分配静态变量区,或者说创建DLL的RW的运行域
for(i = 0,dst = RAM_ADDRESS_DLY,src = (unsigned int *)(AddrDynamicLinkLib + DLL_Static_offset_addr); i < ((DLL_Static_len / 4) + 1); i ++){
*dst ++= *src ++;
}
// 初始化宿主程序中需要用到的DLL函数地址
DelayUs = (Fun) (Name2Addr(AddrDynamicLinkLib,"DelayUs",7) + (unsigned int)AddrDynamicLinkLib);
RunHostFun = (TFun) (Name2Addr(AddrDynamicLinkLib,"RunHostFun",10) + (unsigned int)AddrDynamicLinkLib);
StaticVarInit = (NFun) (Name2Addr(AddrDynamicLinkLib,"StaticVarInit",13) + (unsigned int)AddrDynamicLinkLib);

StaticVariableRelocate = (unsigned int *)((unsigned int)RAM_ADDRESS_DLY); //重定向RW的地址,但地址值原则不明

StaticVarInit((unsigned int)LED_B_Control,&DynamicStaticVar,&HostStaticVar);
}

以上代码中StaticVariableRelocate = (unsigned int *)((unsigned int)RAM_ADDRESS_DLY);
目的是为了重定向动态库中静态变量的地址,StaticVariableRelocate 最后会关联到CPU寄存器的R9,也就是说用R9重定向的静态变量地址,但看了几本书也都没有给我说清楚这个填入的值,是怎么计算出来的。虽然这里看起来就是RAM_ADDRESS_DLY这么简单,也就是DynamicRAM这么规整的值,但实际上之前我每次改变DLL,这里都不是简单的RAM_ADDRESS_DLY的搞定了,是要有一些其他的运算式,我每次都是用仿真器反向跟踪和计算出这个值的,也没有发现规律,而且这样做也很麻烦。我觉得这个点是目前最妨碍这套动态链接库推广的地方。如果这个值能最终关联到某些由编译器更新和维护的标签,那就可以做到很傻瓜化了。不知道哪里还能找到合适的文档和书籍,解释这一点。


二、在动态库的汇编文件中,怎么样自动填充符号来保证DCD 特定值 在4倍地址处。
目前我用的是手动方式,比较笨,汇编也得也不是很多,不知道有没有更好的方法。
                                  
AREA FunctionArray, DATA, READONLY, ALIGN=0
Fun_Start
DCD 0x000000C4 ;//动态库的长度
DCD 0x00000007 ;//动态库版本
DCD 0x080040B4 - 0x08004000 ;// 静态变量的偏移地址
DCB "DelayUs"
DCB "%" ;注意要用%或&填充空缺,把下面的DCD指令顶到4倍地址处。如果是Thumb Code,用%。如果是ARM Code,用&
DCD 0x0800403C - 0x08004000 + 1 ;是Thumb Code的话,加1

DCB "RunHostFun"
DCB "%%" ;
DCD 0x08004054 - 0x08004000 + 1 ;

DCB "StaticVarInit"
DCB "%%%" ;
DCD 0x08004094 - 0x08004000 + 1 ;

END

对应的找dll的函数名和地址的函数是:

unsigned int Name2Addr(unsigned int addr,unsigned char *src,unsigned char len)
{
unsigned char *targ = (unsigned char *)(addr);
unsigned int i = 8,j;

FindHead:
for(j = 0; i < DLL_Len; i ++){//
if(src[j] == targ[i]){
j ++;
i ++;
break;
}
}
if((i + len) < DLL_Len){//

}else{
return 0;
}

for(; j < len; j ++,i++){
if(src[j] != targ[i]){
goto FindHead;
}
}

if(j == len){//
if('%' == targ[i]){ //
while(1){
if(targ[i]!= '%'){ ///
break;
}else{
i ++;
}
}
if((i+4)<DLL_Len){//
return ( (targ[i+0] << 8*0) |
(targ[i+1] << 8*1) |
(targ[i+2] << 8*2) |
(targ[i+3] << 8*3) );
}
}
}
return 0;
}



参数资料:
1.《ARM程序分析与设计》 ,其中最后一章有非常类似的实例。
2.《ARM体系结构与编程》 第二版
3. CM3权威指南

...全文
3194 23 打赏 收藏 转发到动态 举报
写回复
用AI写文章
23 条回复
切换为时间正序
请发表友善的回复…
发表回复
qq_44354899 2019-12-12
  • 打赏
  • 举报
回复
楼主,我理解的R9寄存器存放的是全局变量起始地址,也就是你想把全局变量重定位到哪里,就直接把目标空间的首地址赋值给R9就行了。这样动态加载只需要重定位data段,code段依然在flash运行。不同程序调用同一段代码只需要各自给这段程序分配一段内存用来存储全局变量以及上下文信息。
lr2131 2019-12-12
  • 打赏
  • 举报
回复
引用 22 楼 qq_44354899 的回复:
楼主,我理解的R9寄存器存放的是全局变量起始地址,也就是你想把全局变量重定位到哪里,就直接把目标空间的首地址赋值给R9就行了。这样动态加载只需要重定位data段,code段依然在flash运行。不同程序调用同一段代码只需要各自给这段程序分配一段内存用来存储全局变量以及上下文信息。
你的理解是对的!
lr2131 2019-08-05
  • 打赏
  • 举报
回复
引用 20 楼 z_space 的回复:
我所做的是用户完全不需要关心r9,写了一个专用的编译壳用来进行工程编译,能够打包工程的多个文件到一个包,用户只需要使用编译壳编译自己写的工程,和其它写程序一样,在动态加载方面基本全部交给加载平台做,用户编写的程序也是动态库,可以被其它应用使用,程序能够在运行期间动态加载,能够递归加载


老哥,厉害了!
本来我也想弄个编译插件这样方式,在IDE上弄出个更加傻瓜化的DLL支持,但自己功力也去不到那个层次,编译原理只停留在原理的层面。
虽然已经更新到第6版了,但这个也就是只能到此为止了。
z_space 2019-08-02
  • 打赏
  • 举报
回复
我所做的是用户完全不需要关心r9,写了一个专用的编译壳用来进行工程编译,能够打包工程的多个文件到一个包,用户只需要使用编译壳编译自己写的工程,和其它写程序一样,在动态加载方面基本全部交给加载平台做,用户编写的程序也是动态库,可以被其它应用使用,程序能够在运行期间动态加载,能够递归加载
z_space 2019-08-02
  • 打赏
  • 举报
回复
我在很久以前都在研究这个r9了,现在已经能够动态加载,操作系统已经加上了,还有文件系统,目前能够实现加载自举。
jobsju 2019-02-13
  • 打赏
  • 举报
回复
引用 17 楼 lr2131 的回复:
[quote=引用 13 楼 suxingtian 的回复:] 国产RTT系统听说可以动态加载,还去研究
简单看了下RTT,不错的OS啊[/quote] V4版本能否整理出来分享下?
lr2131 2018-12-27
  • 打赏
  • 举报
回复
引用 13 楼 suxingtian 的回复:
国产RTT系统听说可以动态加载,还去研究


简单看了下RTT,不错的OS啊
lr2131 2018-12-26
  • 打赏
  • 举报
回复
结贴,V4版本已经完成,因为不能放出与公司相关的业务代码,只能在以后剥离了业务源码才能公开。
如有兴趣,可阅读我的CSDN博客技术贴(还在整理中)。

V4版本已经可以应付全部与细节相关的参数,傻瓜化程度很高。
但V4版本还不是最终版,不能应付多个DLL的问题,不能应付多个DLL之间 直接和间接的调用的需求。
将在以后继续完善.
lr2131 2018-11-29
  • 打赏
  • 举报
回复
v2版本延期的,不过已经按先前预定的计划完成了,有兴趣可以去github下载源码:
https://github.com/lr6410/SCM/tree/master/Demo_DynamicLinkLibrary_Use

DLL的bin文件生成后,可以按自己定义的规则,把原数据映射成其他数据。
而Host中需要做反映射规则,在加载器加载时,再转回来。这样对DLL做了一层简单的加密。
映射规则可以和芯片的ID关联,这样即使弄出了bin文件也不能直接复刻到其他芯片上。
当然,映射规则可以做得更加复杂,甚至关联网络、关联某些带有唯一ID的外设(DS18B20的UID,网卡mac)、关联时间。
甚至自己生产的唯一ID。
规则很多,可简单可复杂,可自定义可用通用加密算法,只要转过去还能正常转回来就可以。这里就不再展开了。
然后将Host的bin和DLL的bin用工具合并(可以自己写),
或者分开,最后烧录到芯片里。
suxingtian 2018-11-27
  • 打赏
  • 举报
回复
引用 12 楼 knate 的回复:
[quote=引用 5 楼 suxingtian 的回复:]
这么好的东西竟然没啥人气,我大部分看懂了,没看懂的我再研究研究
因为我当前正在研究这东西,昨天刚看了分散加载。也发了一个帖子说明我想做的东西

我倒不觉得这是一个多好的东西.
当然我只是简单看了一下git的整个工程(下载看了),但是 电脑上没有407的pack 并没有编译成功, 下载太慢了. 懒得搞.

动态链接库这个说法其实并不准确, PC上说的DLL 是需要的时候才加载到程序空间里面运行的,
而楼主这个其实并不是, 而实际上 更好的理解应该是
ROM 里面实际存在 工程 A和B , 工程 A,B的代码都是独立生成的, 两者编译时没有依赖关系.
现在 想在工程A里面调用工程B里面的代码.
这个和一般意义上DLL是有本质区别的. 这里叫DLL调用 我觉得有哗众取宠的意味在里面
------------------------------------------
那到底能不能实现工程A里调用工程B里面的代码呢?!
实际上这肯定是可能的. 毕竟这两份代码都已经同时在ROM里了不是么!
只要工程A里面 能通过一定的手段知道工程B里面所需调用代码位置的地方就可以了!
具体这么做就不细说了, 这不是我要说的重点. 毕竟嵌入式编程 99% code和RAM是可以直接定位.怎么做,
方法真的很多,, 这里就不展开了.
最简单提一句, 函数指针在这里是个好东西


这里我只说一点, 这样的调用是有很多坑的! 而且有些时候还隐蔽.
首先,两个工程的配置不能很随意, 必须做好规划, 错开,配合好.
差异太大, A里面配置是这样的, B里面配置有很多东西不一样甚至相反的, 那出错就很正常了,
其次,即使能正常调用, 调动的函数,都尽可能做到内聚性好, 最好是输出结果只跟传入参数相关,这样出BUG几率会小一些.(这个其实即使是DLL最好也是这样)
再次, 我觉得现在来说, flash越来越不值钱了,flash大小的实际上价格上并不大,极限条件可能出现flash小的反而贵一些也不是不可能的.
为了那么一丁点flash 花力气去做一些极有可能引进BUG进来的处理,我觉得实在没有必要. 相信这也算很多人对这不是很感兴趣的主要原因吧.

好多年没上来! [/quote]

从省flash角度确实没啥用。不过可以从其他方面考虑其作用性,
suxingtian 2018-11-27
  • 打赏
  • 举报
回复
国产RTT系统听说可以动态加载,还去研究
knate 2018-11-08
  • 打赏
  • 举报
回复
引用 5 楼 suxingtian 的回复:
这么好的东西竟然没啥人气,我大部分看懂了,没看懂的我再研究研究 因为我当前正在研究这东西,昨天刚看了分散加载。也发了一个帖子说明我想做的东西
我倒不觉得这是一个多好的东西. 当然我只是简单看了一下git的整个工程(下载看了),但是 电脑上没有407的pack 并没有编译成功, 下载太慢了. 懒得搞. 动态链接库这个说法其实并不准确, PC上说的DLL 是需要的时候才加载到程序空间里面运行的, 而楼主这个其实并不是, 而实际上 更好的理解应该是 ROM 里面实际存在 工程 A和B , 工程 A,B的代码都是独立生成的, 两者编译时没有依赖关系. 现在 想在工程A里面调用工程B里面的代码. 这个和一般意义上DLL是有本质区别的. 这里叫DLL调用 我觉得有哗众取宠的意味在里面 ------------------------------------------ 那到底能不能实现工程A里调用工程B里面的代码呢?! 实际上这肯定是可能的. 毕竟这两份代码都已经同时在ROM里了不是么! 只要工程A里面 能通过一定的手段知道工程B里面所需调用代码位置的地方就可以了! 具体这么做就不细说了, 这不是我要说的重点. 毕竟嵌入式编程 99% code和RAM是可以直接定位.怎么做, 方法真的很多,, 这里就不展开了. 最简单提一句, 函数指针在这里是个好东西 这里我只说一点, 这样的调用是有很多坑的! 而且有些时候还隐蔽. 首先,两个工程的配置不能很随意, 必须做好规划, 错开,配合好. 差异太大, A里面配置是这样的, B里面配置有很多东西不一样甚至相反的, 那出错就很正常了, 其次,即使能正常调用, 调动的函数,都尽可能做到内聚性好, 最好是输出结果只跟传入参数相关,这样出BUG几率会小一些.(这个其实即使是DLL最好也是这样) 再次, 我觉得现在来说, flash越来越不值钱了,flash大小的实际上价格上并不大,极限条件可能出现flash小的反而贵一些也不是不可能的. 为了那么一丁点flash 花力气去做一些极有可能引进BUG进来的处理,我觉得实在没有必要. 相信这也算很多人对这不是很感兴趣的主要原因吧. 好多年没上来!
hzpj01 2018-10-31
  • 打赏
  • 举报
回复
看看都勉强,不能参与讨论,只能旁观。
phangbob 2018-10-09
  • 打赏
  • 举报
回复
这个帖子值得牛人来讨论啊,让大伙学习学习
worldy 2018-10-06
  • 打赏
  • 举报
回复
lz了不起

加载动态库重要的是设计一个Loader,其功能是为运行程序分配空间,从外存比如Flash或存储卡中读取运行程序,然后将其存入运行Ram,然后,将原程序的调用设置调用位置......

再设计下去,就是一个完整的操作系统了
zl_goodluck 2018-10-04
  • 打赏
  • 举报
回复
好东西,学习了,
lr2131 2018-09-30
  • 打赏
  • 举报
回复
好吧,既然有人看,不管看不看得懂,国庆后发布第2个版本:

v2版本有以下改进:
1.DLL长度使用分散加载机制提供的变量,用户不用再手动填写长度值,全交给编译器来做,傻瓜化。
2.静态变量偏移地址,用分散加载机制提供的变量自动算出,用户不用再手动写入
3.改进Name2Addr函数,用户不用手动填入%或&
4.将RW段提到Const RO和RO之间,这样,改变了代码长度,RW也不会变化,用户就不用每次通过仿真的方式逆向计算R9的值,这一点很重要!

以上改进将会在很大程度上方便使用和推广,即使不能理解动态链接库技术的童鞋们也能使用!






另外,之前我说过:“动态链接库编程,除了省空间这个直接作用,还能帮助完成一些特殊的技能”。接下来,将使用到一些奇技淫巧!

第一个技巧是加密,汇编层的加密(可能这个说法不准确),将使用自定义的规则来把编译出的so中的全部指令或部分指令变成其他东西,在加载时再转回来。这么做是为了加密,提高破解的难度和工作量!
之前看过顶象科技有个支持加密的单片机编译器,没有用过这个编译器,但个人比较怀疑用到了相似的技术。

其他后续的高阶技巧将会陆续加入。

以上公布的全部技术,将会用于量产的产品中,欢迎大家一起来讨论。


yishumei 2018-09-25
  • 打赏
  • 举报
回复
看不懂。。。目前的水平无法和楼主探讨这个问题。
suxingtian 2018-09-23
  • 打赏
  • 举报
回复
这么好的东西竟然没啥人气,我大部分看懂了,没看懂的我再研究研究
因为我当前正在研究这东西,昨天刚看了分散加载。也发了一个帖子说明我想做的东西
ljt8015 2018-08-26
  • 打赏
  • 举报
回复
牛人,膜拜下
加载更多回复(3)

27,370

社区成员

发帖
与我相关
我的任务
社区描述
硬件/嵌入开发 单片机/工控
社区管理员
  • 单片机/工控社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧