代碼重定位和位置無關碼——運行於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]='\0';是從rodata處取得值復制到棧地址上,所以這樣的局部字符數組不能作為返回值,而指針就不同了,直接是rodata的地址。

只要是有初始值的數組,都不是位置無關的,但是基礎局部變量比如 int a=1;這個初始值不是存放在rodata上的。數組有初始值,需要經過一步訪問rodata段地操作,rodata是絕對地址,故不是位置無關的。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM