[编译链接]单片机分散加载机制、地址无关编译的分析(二)

发布于 2020-07-03 12:02:00

接上一篇

//汇编代码
//其中这里已经知道了:r10是始地址,r11是末地址+1(用于与r10一起判断结束),r7是始地址-1(实际上是一个基地址)
 __scatterload_null
        0x000000d6:    45da        .E      CMP      r10,r11
        0x000000d8:    d101        ..      BNE      0xde ; __scatterload_null + 8
        0x000000da:    f000f878    ..x.    BL       __rt_entry ; 0x1ce
        0x000000de:    f2af0e09    ....    ADR      lr,{pc}-7 ; 0xd7
        0x000000e2:    e8ba000f    ....    LDM      r10!,{r0-r3}
        0x000000e6:    f0130f01    ....    TST      r3,#1
        0x000000ea:    bf18        ..      IT       NE
        0x000000ec:    1afb        ..      SUBNE    r3,r7,r3
        0x000000ee:    f0430301    C...    ORR      r3,r3,#1
        0x000000f2:    4718        .G      BX       r3
;;中间的代码省略...
Region$$Table$$Base
        0x00038060:    00000055    U...    DCD    85
        0x00038064:    00000002    ....    DCD    2
        0x00038068:    00001b38    8...    DCD    6968
        0x0003806c:    00037f63    c...    DCD    229219
        0x00038070:    00000819    ....    DCD    2073
        0x00038074:    00001b3a    :...    DCD    6970
        0x00038078:    0000f5d0    ....    DCD    62928
        0x0003807c:    00037eeb    .~..    DCD    229099
        
//对应的C语言代码(也是伪代码。实际上,函数的入参都是放在r0~r3中的,所以一些细节c和对应的汇编对应不上)
//不妨做以下命名:start=r10,end=r11,base=r7
//之所以入参用int32_t,这里没什么讲究,我就是单纯的认为32bit的机器,地址是32bit的,可正可负而已!
void __scatterload_null(int32_t start, int32_t end, int32_t base)
{
    TRegionTable OnceOption;
    
    while(1) 
    {
        if(start == end)
        {
            break;
        }
        else
        {
            memcpy(&OnceOption, *start, 16);//16=4个寄存器的大小
            if( OnceOption.func & 0x1 )
            {
                OnceOption.func = base - OnceOption.func;
            }
            OnceOption.func( OnceOption.load_addr,
                             OnceOption.run_addr,
                             OnceOption.size 
                             base );
             start += 4;
        }
    }
    //这里实际上是跳转到__rt_entry()去执行
    //但在C语言里我实在没有办法描述这个过程,姑且认为__scatterload_null()函数执行完就去执行__rt_entry()
}
typedef struct
{
    int32_t     load_addr; //加载视图,就是下载到单片机中时的那个bin文件中ram数据的地址,一般在code+ro之后排列
    int32_t        run_addr;  //运行视图,就是运行时在ram中的实际地址
    int32_t        size;
    void (*func)(int32_t load_addr, int32_t run_addr, int32_t size, int32_t base);
}TRegionTable;
TRegionTable RegionTable[] @"Region$$Table$$Base" =
{
    //注意,把除了size外的三个的bit[1:0]忽略掉,这两位只起到标识作用(后面会讲到具体原因)
    { 85,    2,        6968,    __decompress$$rt2veneer,            },
    { 2073,    6970,    62928,    __scatterload_zeroinit$$rt2veneer,    },
};

//load_addr和size怎么来的,看下面:
//这个也是从相同的.S文件提取的
** Section #3 'data' (SHT_PROGBITS) [SHF_ALLOC + SHF_WRITE]
    Size   : 1988 bytes (alignment 4)
    Address: 0x000380b4
** Section #4 'bss' (SHT_NOBITS) [SHF_ALLOC + SHF_WRITE]
    Size   : 62928 bytes (alignment 4)
    Address: 0x00039bec
//会发现,6968 ≠ 1988,62928 = 62928
//造成这个的原因就是数据压缩技术。打开.map文件,一目了然。
 Code (inc.  data)       RO Data        RW Data      ZI Data  Debug   
 202804      13872      26752       6968      62928    1059524   Grand Totals
 202804      13872      26752       1988      62928    1059524   ELF Image Totals (compressed)
 202804      13872      26752       1988          0          0   ROM Totals

稍微解释一下这个转换:

//顺便介绍下Region$$Table$$Base的结构
//每16Byte为一组,分别是加载时地址,运行时地址,操作的数据个数,操作的函数
//然后根据不同的需要,构成了一个表格
//所谓不同需要,指的是:赋初始值的变量、未赋初始值的变量,然后这个可以再细分,如下所示:
//ram --- | 未赋初始值的变量 ----------------------- .bss(调用的__scatterload_zeroinit())
//        |                  | 赋初始值全部为全0---- .bss(调用的__scatterload_zeroinit())
//        | 赋初始值的变量 --- | 赋初始值全部为非0 --- .rw(调用的__decompress())
//        |                  | 赋初始值杂乱无章 ---- .rw(调用的__scatterload_copy())
//当然上述分类可能不太准确,因为__decompress()这个太特殊了,压缩与解压缩,这个东西没法说的太细

//在顺便介绍下Region$$Table$$Base怎么得到操作函数
//首先我先列这几个函数在汇编里的信息(和之前的是在一个程序里的):
__decompress$$rt2veneer
    0x000000fc:    f0100f01    ....    TST      r0,#1
    0x00000100:    bf18        ..      IT       NE
    0x00000102:    19c0        ..      ADDNE    r0,r0,r7
    0x00000104:    f0110f01    ....    TST      r1,#1
    0x00000108:    bf18        ..      IT       NE
    0x0000010a:    19c9        ..      ADDNE    r1,r1,r7
    0x0000010c:    f0110f02    ....    TST      r1,#2
    0x00000110:    bf18        ..      IT       NE
    0x00000112:    4449        ID      ADDNE    r1,r1,r9
    0x00000114:    f0210103    !...    BIC      r1,r1,#3
!!dczerorl2
__decompress
__decompress1
    0x00000118:    440a        .D      ADD      r2,r2,r1
;;中间的代码省略...
__scatterload_zeroinit$$rt2veneer
     0x00000174:    f0100f01    ....    TST      r0,#1
     0x00000178:    bf18        ..      IT       NE
     0x0000017a:    19c0        ..      ADDNE    r0,r0,r7
     0x0000017c:    f0110f01    ....    TST      r1,#1
     0x00000180:    bf18        ..      IT       NE
     0x00000182:    19c9        ..      ADDNE    r1,r1,r7
     0x00000184:    f0110f02    ....    TST      r1,#2
     0x00000188:    bf18        ..      IT       NE
     0x0000018a:    4449        ID      ADDNE    r1,r1,r9
     0x0000018c:    f0210103    !...    BIC      r1,r1,#3
 !!handler_zi
 __scatterload_zeroinit
     0x00000190:    2300        .#      MOVS     r3,#0
     
//然后从Region$$Table$$Base中提取几个参数
        0x0003805f:    XXXXXXXX    ....    DCD    XXXXXX ;;这个!!!就是前边的r7和base,就用了这个地址值!!!
Region$$Table$$Base
        0x00038060:    00000055    U...    DCD    85 ;;特别说明一下这个load_addr,跟数据排列有关,就是个偏移
        0x00038064:    00000002    ....    DCD    2
        0x00038068:    00001b38    8...    DCD    6968
        0x0003806c:    00037f63    c...    DCD    229219 ;;和这个!!!给个标号func1
        0x00038070:    00000819    ....    DCD    2073
        0x00038074:    00001b3a    :...    DCD    6970
        0x00038078:    0000f5d0    ....    DCD    62928
        0x0003807c:    00037eeb    .~..    DCD    229099 ;;和这个!!!给个标号func2
        
//然后在__scatterload_null()中有一个用让r3=r7-r3的操作: 0x000000ec:    1afb        ..      SUBNE    r3,r7,r3
//r3就是func1和func2的地址,不妨来计算一下:
//首先是func1,0x0003805f - 0x00037f63 = 0x000000fc,是__decompress$$rt2veneer()
//然后是func2,0x0003805f - 0x00037eeb = 0x00000174,是__scatterload_zeroinit$$rt2veneer()
//当然如果没有使用地址无关编译,多少会有出入,但你总能发现什么(或者简单点,你仿真着单步执行也能得到整个执行流程)

//最后再补充一句:
//func1和func2处的这两个值(乃至Region$$Table$$Base的表示地址的那两个,load_addr和run_addr)的bit[0]和bit[1]是由特殊用途的。
//bit[0]:=1表示这个值是个偏移,需要和基地址进行处理然后再使用(或作差或相加,基地址的获取方式就是我前面说的)
//bit[1]:=1表示这个值是地址无关的,即rwpi,需要再运行时指定一个基地址,就是r9

//我当初想了很久写这个的那个大神为什么要这么做,想了很久才明白:这么写真的很通用而且好理解。可能这就是我和大神的区别。
  • 以上就是完整的分散加载的执行过程。但是存在一种情况,就是在ram中定义一个数组,这个数组包含函数指针且被赋初始值,如下所示:
typedef void (*Func)( void* arg);
Func func[2] = {StartThread_Entry, StartThread_Entry};
    
int main( void )
{
    func[0](0);
    func[1](0);
    //...其他代码
}

上述代码是会编译通过的,以下就来分析一下这种情况(关于地址无关编译,我们在本节就简单带过,具体地址无关编译的实现在下一节详细介绍)。

篇幅有限,接下一篇

0 条评论

发布
问题

分享
好友