硬汉嵌入式论坛

 找回密码
 立即注册
查看: 4511|回复: 5
收起左侧

[客户分享] 单片机高阶技能之动态链接库技术实现(转)

[复制链接]

10

主题

40

回帖

70

积分

初级会员

积分
70
发表于 2018-8-27 09:11:18 | 显示全部楼层 |阅读模式
本帖最后由 ljt8015 于 2018-8-27 09:12 编辑

最近做了个单片机的小项目,遇到了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寄存器,原则上是该填什么值?
以下贴出关键代码片段:
C/C++ code


__global_reg(6) unsigned int *StaticVariableRelocate;
extern unsigned int *DynamicStaticVar=0;// Ö¸Ïòdllμľ2쬱äá¿
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的搞定了,


是要有一些其他的运算式,我每次都是用仿真器反向跟踪和计算出这个值的,也没有发现规律,而且这样做也很麻烦。我觉得这个点是目前最妨碍这套动态链接库推广的地方。如果这个值能


最终关联到某些由编译器更新和维护的标签,那就可以做到很傻瓜化了。不知道哪里还能找到合适的文档和书籍,解释这一点。

回复

使用道具 举报

10

主题

40

回帖

70

积分

初级会员

积分
70
 楼主| 发表于 2018-8-27 09:13:19 | 显示全部楼层

二、在动态库的汇编文件中,怎么样自动填充符号来保证DCD 特定值   在4倍地址处。
目前我用的是手动方式,比较笨,汇编也得也不是很多,不知道有没有更好的方法。
C/C++ code

                                   
                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的函数名和地址的函数是:
C/C++ code

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权威指南

回复

使用道具 举报

10

主题

40

回帖

70

积分

初级会员

积分
70
 楼主| 发表于 2018-8-27 09:13:53 | 显示全部楼层

GitHub链接:
https://github.com/lr6410/SCM/tr ... amicLinkLibrary_Use

在百度网盘和CSDN资源,这两个链接上发布的工程,没有带ReadMe.txt。
但GitHub里补充了,这里贴出来文本:
C/C++ code

版本说明:
v1版本   支持几乎完整功能的动态链接库技术实现。
具体有以下特性的支持:
1.支持DLL中含有自己定义的静态变量
2.支持Host指针方式读写DLL中的静态变量
3.支持DLL调用Host中的函数
4.支持DLL指针方式读写Host中的静态变量
5.DLL支持加载域和运行域的ROPI和RWPI
6.DLL支持函数可重入机制



技术实现的基本原理:

1.DLL中定义的静态变量,初始值存储在加载域中,但分配空间的责任要交给Host,也就是说。由宿主程序从RAM中分配静态变量的存储空间给DLL的静态变量s。加载过程由动态链接库加载器完成。加载变量运行域的同时,就把DLL中静态变量的初始值赋值过去了。
2.Host调用DLL中的函数,可以间接的访问到DLL中的静态变量,但这样效率太低。为解决这个问题,Host需要“直接”访问DLL中的静态变量,但在加载静态区的时候,Host才知道具体的地址,在Host编译时是不知道的,除非绑死RAM地址!但绑死RAM地址会遇到一些问题,且不方便。所以只能通过指针的方式,而不可能真正直接的访问静态变量,这样静态区地址可以做到每次不一样。加载器在加载完RW后,要通过设置R9寄存器来完成静态变量地址重定位,这是RWPI的关键步骤。而ROPI却不需要这样的步骤。
3.DLL调用Host中函数的方法,在DLL的中事先定义函数指针,在加载器加载DLL中的静态变量区时,将Host中的函数地址传入,写入DLL的函数指针中,之后就可以调用了。
4.DLL读写Host中的静态变量,思路和上面的2大致一样,也要在DLL中事先定义好变量指针,在加载器加载时,将地址传入赋值,之后就可以读写了。
5.DLL支持加载域和运行域的ROPI和RWPI,这样能最大程度上灵活自由的放置DLL的代码和数据到不同的地方,而不用担心受到限制。
6.这里说的可重入机制,不是指临界区的那种不可重入的性质,而是指多个线程都需要访问这个DLL,但又希望每个线程有自己的实体,更细致的来说也就是,各自访问的DLL静态变量值是独立的,不受其他线程影响。解决方法是,各个线程各自分配独立的静态区,各自用各自的R9重定位静态变量区。就像UCOS2中由用户定义数组,OS使用数组作为线程调度的栈空间。RO可共享,也可独立加载到自己的RAM中


注意:
1.如果DLL中的函数需要的临界区加解锁中函数是由宿主程序提供的,如果不能在DLL中定义加解锁,请使用第3特性完成该功能!在DLL中调用Host提供的加解锁函数!
2.暂时我还没有试过DLL中使用未初始化的静态变量的情况,也就是说DLL中没有ZI段。如果可以,请将未初始化的静态变量初始化成默认零类型值,不要有ZI段。
3.暂时没试过DLL生成ARM Code的情况,所以目前v1版本用在ARMv7-M版本以下的CPU上,可能会有问题。请尽量设置DLL工程,让编译器编译成Thumb Code。

DLL工程相关设置:
1.C/C++页,要勾选ROPI和RWPI。可以的话也请勾选ELF项,这样map文件中某些信息会更细致的展开。
2.ASM页,要勾选ROPI和RWPI
3.Linker页,由于DLL没有自己main,且运行域的排列也要调整,用-ro-base和-rw-base,几乎不可能完成灵活、自由的设置,请使用分散加载脚本,可参考本工程中的sct文件。




关联量的手动设置:
assmbly文件中:
1.DLL的长度,请查看map文件,获取DLL的长度,或者直接查看生成的bin文件大小,转换成16进制数,写入assmbly.s的第一个DCD中
2.请自行维护自己的DLL版本,宿主程序兼容DLL的支持,就依靠这个版本号。填入第2个DCD
3.静态变量在DLL中的偏移地址,请查看map文件中,首个RW section的地址,然后减掉DLL的基地址,填入第3个DCD
4.将需要引出的DLL函数,函数名填入 第1个DCB
5.由于地址对齐问题,如果DCD的地址不是4的整数倍,汇编器会自动填0,保证DCD对齐到4倍地址值。
  而我这里的Name2Addr没有设计好,需要手工填写这些填0的地方,请使用DCB指令,带“%”填入
6.在map文件中查看对应的函数地址,同上,减去DLL基地址得到偏移地址,但因ARMv7-M的指令集要求,Thmub模式的函数地址,在写入PC时最后一位是1
  所以这里手动加1,不加1可能进入硬件错误。


其他函数引出都如此添加。
重复 4、5、6


动态库文件:
1.在Host中被DLL访问的函数,请先定义对应函数类型的函数指针。具体参考源码
2.在Host中被DLL访问的变量,请先定义对应类型的指针变量。具体参考源码


DLL工程中的sct文件
1.用汇编文件的方式,强制要求把含有函数名和地址的映射表排到文件头,把DLL的长度,版本,以及静态变量区的偏移量都按事先定义好的顺序存放到这里
2.将RO段的加载域紧跟其后,没有特别的要求。另外,工程编译时警告没有入口点,但DLL本身并不需要入口点。有的朋友熟悉用法,可自行添加命令,少掉这个警告。但我自己简单的试了下,在编译命令中手工指定某个函数为入口点,虽然少了两个警告,但其他的函数却被优化掉不编译,一时想不起来怎么要求编译器不优化掉没被调用的函数。所以只好还原成不带入口,只好先让编译器警告没有入口点了。
3.有个警告说最后一行不是换行的,请不要搭理它!



宿主工程中的加载器代码:
1.请注意R9寄存器的使用,也就是__global_reg(6)。ROPI和RWPI实现上不同之处,就是ROPI可以全交给IDE,RWPI却有个重定向的寄存器R9,需要加载器完成。最终到了程序员这里,需要程序员设置合适的值。
2.给DLL静态变量分配的空间,请注意地址对齐,至少要4字节对齐,请使用MDK提供的关键字 __attribute__((aligned(8))) 来修饰,这里用4不用8,可能也行。
0  0
回复

使用道具 举报

2

主题

569

回帖

575

积分

金牌会员

积分
575
发表于 2018-8-27 13:20:13 | 显示全部楼层
可以让单片机安装各种APP了
回复

使用道具 举报

1

主题

20

回帖

23

积分

新手上路

积分
23
发表于 2018-8-27 13:58:59 | 显示全部楼层
没有看懂,看来够复杂的;如果重定位有很多需要考虑,如何保障每个都正确
回复

使用道具 举报

0

主题

5

回帖

5

积分

新手上路

积分
5
发表于 2023-8-21 08:30:05 | 显示全部楼层
:)
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|小黑屋|Archiver|手机版|硬汉嵌入式论坛

GMT+8, 2024-4-28 14:56 , Processed in 0.273951 second(s), 24 queries .

Powered by Discuz! X3.4 Licensed

Copyright © 2001-2023, Tencent Cloud.

快速回复 返回顶部 返回列表