• 代码重定位和位置无关码——运行于nor flash


    通过前面的学习,我们知道,把可执行程序从一个位置复制到另一个位置的过程叫做重定位。

    现在有两种方式,第一种是只重定位data段到内存(sdram),为什么需要重定位?因为有些flash的写操作,不是简单地内存访问,通常我们使用sdram这个介质作为程序运行的载体。但是只重定位data段这种方式存在弊端。第一,我们的调试工具通常不支持这种分体形式(比如我们的之前的代码在0地址开始存放text和rodata段,而在间隔很远处sdram 0x30000000存放data段,这就是分体的形式)的代码;第二,这种分体方式需要能够直接运行程序的flash比如nor flash才可以工作,但是有些板子根本连nor flash都没有,那么只能通过第二种方式进行开发了。

    第二种方式是把整个程序都复制到内存(sdram),这种方式所有数据都是紧挨着的,以后我们都使用这种方式。

    现在思考一个问题,关于第二种方式,我们的bin文件是由连接脚本指定了运行地址为sdram(0x30000000)的,但是这个bin文件我们烧写在nor flash,是从0地址开始运行的,那么在nor flash上的代码就需要把整个bin文件拷贝到sdram中去,这就是重定位,但这就要求我们必须做到,在重定位之前的代码必须是位置无关码。(连接脚本指定我们程序的运行地址为0x30000000,为什么我们的在nor flash上的代码从0地址开始运行也能工作?这就是建立在我们这部分代码必须是位置无关码的基础上的,0地址处运行和0x30000000处运行达到同样效果,需要我们保证,在重定位之前,也就是复制操作没有完成之前的代码,必须是位置无关的)。

    现在修改我们之前的连接脚本和启动文件:

    SECTIONS
    {
        . = 0x30000000;
    
        . = ALIGN(4);
        .text      :
        {
          *(.text)
        }
    
        . = ALIGN(4);
        .rodata : { *(.rodata) }
    
        . = ALIGN(4);
        .data : { *(.data) }
    
        . = ALIGN(4);
        __bss_start = .;
        .bss : { *(.bss) *(.COMMON) }
        _end = .;
    }

    上面是连接脚本,我想此时应该都不需要备注了吧,基本语法。表示0x30000000处是我们的运行地址。

    更改启动文件:

        /* 重定位text, rodata, data段整个程序 */
        mov r1, #0
        ldr r2, =_start         /* 第1条指令运行时的地址 */
        ldr r3, =__bss_start    /* bss段的起始地址 */

    其中标号_start为启动文件的最开始处的标号,完整启动文件如下:

     1 .text
     2 .global _start
     3 
     4 _start:
     5 
     6     /* 关闭看门狗 */
     7     ldr r0, =0x53000000
     8     ldr r1, =0
     9     str r1, [r0]
    10 
    11     /* 设置MPLL, FCLK : HCLK : PCLK = 400m : 100m : 50m */
    12     /* LOCKTIME(0x4C000000) = 0xFFFFFFFF */
    13     ldr r0, =0x4C000000
    14     ldr r1, =0xFFFFFFFF
    15     str r1, [r0]
    16 
    17     /* CLKDIVN(0x4C000014) = 0X5, tFCLK:tHCLK:tPCLK = 1:4:8  */
    18     ldr r0, =0x4C000014
    19     ldr r1, =0x5
    20     str r1, [r0]
    21 
    22     /* 设置CPU工作于异步模式 */
    23     mrc p15,0,r0,c1,c0,0
    24     orr r0,r0,#0xc0000000   //R1_nF:OR:R1_iA
    25     mcr p15,0,r0,c1,c0,0
    26 
    27     /* 设置MPLLCON(0x4C000004) = (92<<12)|(1<<4)|(1<<0) 
    28      *  m = MDIV+8 = 92+8=100
    29      *  p = PDIV+2 = 1+2 = 3
    30      *  s = SDIV = 1
    31      *  FCLK = 2*m*Fin/(p*2^s) = 2*100*12/(3*2^1)=400M
    32      */
    33     ldr r0, =0x4C000004
    34     ldr r1, =(92<<12)|(1<<4)|(1<<0)
    35     str r1, [r0]
    36 
    37     /* 一旦设置PLL, 就会锁定lock time直到PLL输出稳定
    38      * 然后CPU工作于新的频率FCLK
    39      */
    40     
    41     
    42 
    43     /* 设置内存: sp 栈 */
    44     /* 分辨是nor/nand启动
    45      * 写0到0地址, 再读出来
    46      * 如果得到0, 表示0地址上的内容被修改了, 它对应ram, 这就是nand启动
    47      * 否则就是nor启动
    48      */
    49     mov r1, #0
    50     ldr r0, [r1] /* 读出原来的值备份 */
    51     str r1, [r1] /* 0->[0] */ 
    52     ldr r2, [r1] /* r2=[0] */
    53     cmp r1, r2   /* r1==r2? 如果相等表示是NAND启动 */
    54     ldr sp, =0x40000000+4096 /* 先假设是nor启动 */
    55     moveq sp, #4096  /* nand启动 */
    56     streq r0, [r1]   /* 恢复原来的值 */
    57 
    58     bl sdram_init    
    59 
    60     # /* 重定位data段 */
    61     # ldr r1, =data_load_add  /* data段在bin文件中的地址, 加载地址 */
    62     # ldr r2, =data_start      /* data段在重定位地址, 运行时的地址 */
    63     # ldr r3, =data_end          /* data段结束地址 */
    64     /* 重定位text, rodata, data段整个程序 */
    65     mov r1, #0
    66     ldr r2, =_start         /* 第1条指令运行时的地址 */
    67     ldr r3, =__bss_start    /* bss段的起始地址 */
    68 cpy:
    69     ldr r4, [r1]
    70     str r4, [r2]
    71     add r1, r1, #4
    72     add r2, r2, #4
    73     cmp r2, r3
    74     bcc cpy
    75 
    76 
    77     /* 清除BSS段 */
    78     ldr r1, =__bss_start
    79     ldr r2, =_end
    80     mov r3, #0
    81 clean:
    82     str r3, [r1]
    83     add r1, r1, #4
    84     cmp r1, r2
    85     bcc clean
    86 
    87     bl main
    88 
    89 halt:
    90     b halt
    91     
    View Code

    此时串口输出:

    请留意现在的打印字符的速度。

    之前我们说了,我们的代码全部重定位到sdram,需要我们在重定位之前的代码是位置无关的!而我们的启动文件最后跳转到main函数使用的是bl指令,bl指令时位置无关的,在调用main之前,我们已经完成了所有代码的重定位,此时程序已经运行在sdram上,但我们使用bl指令调用main函数,所以,此时我们的main函数,其实还是运行在nor flash中的,此时的运行速度,肯定不及sdram快,所以我们再次更改启动文件,

    bl main替换成
    ldr pc,=main

    ldr指令给pc赋值为绝对地址,此时main函数的地址是sdram上的一个地址。

    再次编译,查看打印速度。可以发现,现在的打印速度明显快于之前,这个时候,我们的代码才是运行在sdram中的。

    现在,我们更改sdram初始化函数为:

    void sdram_init2(void)
    {
        unsigned int arr[] = {
            0x22000000,     //BWSCON
            0x00000700,     //BANKCON0
            0x00000700,     //BANKCON1
            0x00000700,     //BANKCON2
            0x00000700,     //BANKCON3    
            0x00000700,     //BANKCON4
            0x00000700,     //BANKCON5
            0x18001,     //BANKCON6
            0x18001,     //BANKCON7
            0x8404f5,     //REFRESH,HCLK=12MHz:0x008e07a3,HCLK=100MHz:0x008e04f4
             0xb1,    //BANKSIZE
             0x20,    //MRSRB6
             0x20,    //MRSRB7
    
            };
        volatile unsigned int * p = (volatile unsigned int *)0x48000000;
        int i;
    
        for (i = 0; i < 13; i++)
        {
            *p = arr[i];
            p++;
        }
        
    }

    定义一个初始化的数组,此时,我们编译运行,发现程序没有打印信息输出了,这是为什么呢?

    查看反汇编:

    300004e8 <sdram_init2>:
    300004e8:    e1a0c00d     mov    ip, sp
    300004ec:    e92dd800     stmdb    sp!, {fp, ip, lr, pc}
    300004f0:    e24cb004     sub    fp, ip, #4    ; 0x4
    300004f4:    e24dd03c     sub    sp, sp, #60    ; 0x3c
    300004f8:    e59f3088     ldr    r3, [pc, #136]    ; 30000588 <.text+0x588>
    300004fc:    e24be040     sub    lr, fp, #64    ; 0x40
    30000500:    e1a0c003     mov    ip, r3
    30000504:    e8bc000f     ldmia    ip!, {r0, r1, r2, r3}
    30000508:    e8ae000f     stmia    lr!, {r0, r1, r2, r3}
    3000050c:    e8bc000f     ldmia    ip!, {r0, r1, r2, r3}
    30000510:    e8ae000f     stmia    lr!, {r0, r1, r2, r3}
    30000514:    e8bc000f     ldmia    ip!, {r0, r1, r2, r3}
    30000518:    e8ae000f     stmia    lr!, {r0, r1, r2, r3}
    3000051c:    e59c3000     ldr    r3, [ip]
    30000520:    e58e3000     str    r3, [lr]
    30000524:    e3a03312     mov    r3, #1207959552    ; 0x48000000
    30000528:    e50b3044     str    r3, [fp, #-68]
    3000052c:    e3a03000     mov    r3, #0    ; 0x0
    30000530:    e50b3048     str    r3, [fp, #-72]
    30000534:    e51b3048     ldr    r3, [fp, #-72]
    30000538:    e353000c     cmp    r3, #12    ; 0xc
    3000053c:    ca00000f     bgt    30000580 <sdram_init2+0x98>
    30000540:    e51b1044     ldr    r1, [fp, #-68]
    30000544:    e51b3048     ldr    r3, [fp, #-72]
    30000548:    e3e02033     mvn    r2, #51    ; 0x33
    3000054c:    e1a03103     mov    r3, r3, lsl #2
    30000550:    e24b000c     sub    r0, fp, #12    ; 0xc
    30000554:    e0833000     add    r3, r3, r0
    30000558:    e0833002     add    r3, r3, r2
    3000055c:    e5933000     ldr    r3, [r3]
    30000560:    e5813000     str    r3, [r1]
    30000564:    e51b3044     ldr    r3, [fp, #-68]
    30000568:    e2833004     add    r3, r3, #4    ; 0x4
    3000056c:    e50b3044     str    r3, [fp, #-68]
    30000570:    e51b3048     ldr    r3, [fp, #-72]
    30000574:    e2833001     add    r3, r3, #1    ; 0x1
    30000578:    e50b3048     str    r3, [fp, #-72]
    3000057c:    eaffffec     b    30000534 <sdram_init2+0x4c>
    30000580:    e24bd00c     sub    sp, fp, #12    ; 0xc
    30000584:    e89da800     ldmia    sp, {fp, sp, pc}
    30000588:    300007b0     strcch    r0, [r0], -r0

    红色部分出,可以看出,sdram初始化函数的时候,此时在300007b0处要保存这个地址的值给r0-r3这个四个寄存器了,注意,此时我们的sdram还没有初始化完毕呢!这样肯定出问题,现在我们看看在300007b0处到底存放的是什么:

    可以看到,在7b0处的值和数组初始化的值一一对应,而且,这是位于rodata段的,这rodata段的数据需要绝对地址访问,那么,我们的这个sdram初始化函数就不是位置无关的,所以,这样的代码不能正常运行。

    Summary:

    我们以后采取把bin文件全部重定位到sdram的方式,而且,在重定位完成之前,采用位置无关的代码编写程序(这是针对bin文件存储在nor flash上的情况)。有初始值的数组,数组的初始值放在rodata段里面,所以不是位置无关的,rodata段的数据地址已经固定,必须通过绝对地址访问。本来局部变量是存放在栈上的,但是初始值可就不是了,不要以为数组本身是个局部变量,那么数组的初始值也是直接存放在栈上的,存放在栈上的,仅仅是数组名(地址)和开辟对应的空间,具体的局部变量初始值,存放于rodata段,(这个时候编译器会在rodata段去值来初始化局部变量,这个过程会有访问rodata段的操作,不是位置无关的)注意了哟。这个你可能觉得奇怪,那么回到我之前随笔的那个问题:

    之前我在main函数中,也是局部变量,定义了上面的变量,此时的字符串“char *q”存放于哪里?当然是存放于rodata段里面,这个例子对于深入学习了C语言的人应该很熟悉,因为我们知道这样初始化了的指针,是不能改变它的值得,只是那个时候我们仅仅是知道,而现在,我们却正在一步步验证我们学习得C语言基础。同样的道理,我们也知道,通常来说,返回一个指针的局部变量会由于内存释放出现问题,但是要是返回上面那个字符串的地址,哪怕是局部变量,也不会出问题,因为它存放在rodata段,地址是绝对地址,固定了的。或许你会问,既然初始化了的局部变量的初始值是存放在rodata段中的,那为什么局部变量 char *q="char *q"可以作为return返回,而char q[]="char *q"就不可以呢?

    因为字符串很特别啊,你直接书写一个字符串,这个字符串所参与的操作其实是在操作这个字符串的地址,而这个地址,是rodata段的,属于固定地址,所以我们返回局部指针q,也可以达到目的,因为q的值已经是这个字符串的地址了,而且是一个不变的地址,而局部字符数组char q[];就不同了,q本身是存放在栈里面的,由于是字符数组,是一个萝卜一个坑一一对应于数组的,第一个字符放在数组第一个位置,此时的数组q,是存放在栈中的,栈给予它地址,作为局部变量返回,必然不再安全。而一个例子虽然它也是栈上分配的,可是字符串的地址赋值给了它,返回一个固定不变的地址,就不会有问题。

     eg:

    可以看到最后两个的地址次才是相同的,字符数组,后面的初始值虽然也是位于rodata,但是字符数组的特殊性,相当于

    p[0]='1';p[1]='2';p[2]='3';p[4]='';是从rodata处取得值复制到栈地址上,所以这样的局部字符数组不能作为返回值,而指针就不同了,直接是rodata的地址。

    只要是有初始值的数组,都不是位置无关的,但是基础局部变量比如 int a=1;这个初始值不是存放在rodata上的。数组有初始值,需要经过一步访问rodata段地操作,rodata是绝对地址,故不是位置无关的。

  • 相关阅读:
    BZOJ5321 JXOI2017加法(二分答案+贪心+堆+树状数组)
    BZOJ5089 最大连续子段和(分块)
    Codeforces 893F(主席树+dfs序)
    BZOJ5092 分割序列(贪心)
    Codeforces Round #525 Div. 2 自闭记
    364. Nested List Weight Sum II
    362. Design Hit Counter
    369. Plus One Linked List
    370. Range Addition
    366. Find Leaves of Binary Tree
  • 原文地址:https://www.cnblogs.com/yangguang-it/p/8053124.html
Copyright © 2020-2023  润新知