6-uboot relocation介绍
[uboot] (番外篇)uboot relocation介紹
2016年11月05日 21:29:22閱讀數(shù):1844以下例子都以project X項目tiny210(s5pv210平臺,armv7架構(gòu))為例
[uboot] uboot流程系列:?
[project X] tiny210(s5pv210)上電啟動流程(BL0-BL2)?
[uboot] (第一章)uboot流程——概述?
[uboot] (第二章)uboot流程——uboot-spl編譯流程
========================================================================================================
一、relocate介紹
1、uboot的relocate
uboot的relocate動作就是指uboot的重定向動作,也就是將uboot自身鏡像拷貝到ddr上的另外一個位置的動作。
2、uboot為什么要進行relocate
考慮以下問題?
* 在某些情況下,uboot是在某些只讀存儲器上運行,比如ROM、nor flash等等。需要將這部分代碼拷貝到DDR上才能完整運行uboot。?
(當然,如果我們在spl階段就把uboot拷貝到ddr上,就不會有這種情況。但是uboot本身就是要考慮各種可能性)?
* 一般會把kernel放在ddr的低端地址上。
考慮到以上情況,uboot的relocation動作會把自己本身relocate到ddr上(前提是在SPL的過程中或者在dram_init中已經(jīng)對ddr進行初始化了),并且會relocate到ddr的頂端地址使之不會和kernel的沖突。
3、uboot的一些注意事項
- 既然uboot會把自身relocate到ddr的其他位置上,那么相當于執(zhí)行地址也會發(fā)生變化。也就是要求uboot既要能在relocate正常執(zhí)行,也要能在relocate之后正常執(zhí)行。這就涉及到uboot需要使用“位置無關(guān)代碼”技術(shù),也就是Position independent code技術(shù)。
二、“位置無關(guān)代碼”介紹及其原理
1、什么是“位置無關(guān)代碼”
“位置無關(guān)代碼”是指無論代碼加載到內(nèi)存上的什么地址上,都可以被正常運行。也就是當加載地址和連接地址不一樣時,CPU也可以通過相對尋址獲得到正確的指令地址。
2、如何生成“位置無關(guān)代碼”
(1)生成位置無關(guān)代碼分成兩部分?
* 首先是編譯源文件的時候,需要將其編譯成位置無關(guān)代碼,主要通過gcc的-fpic選項(也有可能是fPIC,fPIE, mword-relocations選項)?
* 其次是連接時要將其連接成一個完整的位置無關(guān)的可執(zhí)行文件,主要通過ld的-fpie選項
(2)ARM在如何生成“位置無關(guān)代碼”?
* 編譯PIC代碼?
在《[uboot] (第四章)uboot流程——uboot編譯流程》中,我們知道gcc的編譯選項如下:
- 1
重點關(guān)注“-mword-relocations -fno-pic”。?
由于使用pic時movt / movw指令會硬編碼16bit的地址域,而uboot的relocation并不支持這個,?
所以arm平臺使用mword-relocations來生成位置無關(guān)代碼。-fno-pic則表示不使用pic。?
如下./arch/arm/config.mk
- 1
- 2
- 3
- 4
- 5
- 生成PIE可執(zhí)行文件?
在《[uboot] (第四章)uboot流程——uboot編譯流程》中,我們知道ld的連接選項如下:
- 1
-pie選項用于生成PIE位置無關(guān)可執(zhí)行文件。
3、“位置無關(guān)代碼”原理
這里只是個人根據(jù)實驗的一些看法。?
“位置無關(guān)代碼”主要是通過使用一些只會使用相對地址的指令實現(xiàn),比如“b”、“bl”、“l(fā)dr”、“adr”等等。?
對于一些絕對地址符號(例如已經(jīng)初始化的全局變量),會將其以label的形式放在每個函數(shù)的代碼實現(xiàn)的末端。?
同時,在鏈接的過程中,會把這些label的地址統(tǒng)一維護在.rel.dyn段中,當relocation的時候,方便對這些地址的fix。
綜上,個人覺得,既然使用絕對地址,那么就是說并不是完全的代碼無關(guān),而是說可以通過調(diào)整絕對地址符號的label表來實現(xiàn)代碼的搬移。如果不做relocate或者在relocate之前還是需要加載到連接地址的位置上,這里只是個人看法!!!?
個人也挺迷惑的,不知道對不對,這里希望有知道答案的大神給個意見。
4、.rel.dyn段介紹和使用
前面也說了:?
對于一些絕對地址符號(例如已經(jīng)初始化的全局變量),會將其以label的形式放在每個函數(shù)的代碼實現(xiàn)的末端。?
同時,在鏈接的過程中,會把這些label的地址統(tǒng)一維護在.rel.dyn段中,當relocation的時候,方便對這些地址的fix。?
這邊簡單的給個例子:?
u-boot/common/board_f.c中
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
通過如下命令對編譯生成的u-boot
arm-none-linux-gnueabi-objdump -D u-boot > uboot_objdump.txt- 1
board_init_f和init_sequence_f相關(guān)的連接地址如下:
Disassembly of section .text: 23e08428 <board_init_f>: 23e08438: e59f000c ldr r0, [pc, #12] ; 23e0844c <board_init_f+0x24> // 通過ldr r0, [pc, #12],相當于是ldr r0,[23e0844c] , // 也就是通過后面的label項,獲得了init_sequence_f的地址。23e0844c: 23e35dcc mvncs r5, #204, 26 ; 0x3300 // 23e0844c: 23e35dcc 是一個label項,23e0844c表示這個label的地址,23e35dcc表示這個label里面的值,也就是全局變量23e35dcc的地址。Disassembly of section .data: 23e35dcc <init_sequence_f>: // 全局變量init_sequence_f的地址在23e35dcc Disassembly of section .rel.dyn: 23e37b88: 23e0844c mvncs r8, #76, 8 ; 0x4c000000 23e37b8c: 00000017 andeq r0, r0, r7, lsl r0 // 把init_sequence_f的label的地址存在.rel.dyn段中,方便后續(xù)relocation的時候,對label中的絕對變量地址進行整理修改。- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
各個符號的地址意義
- 23e08428,是board_init_f的地址
- 23e35dcc,是init_sequence_f的地址
- 23e0844c,是board_init_f為init_sequence_f做的label的地址,所以其值是init_sequence_f的地址,也就是23e35dcc
- 23e37b88,把init_sequence_f的label的地址存放在.rel.dyn段中的這個位置
根據(jù)上述對全局變量的尋址進行簡單的說明?
當board_init_f讀取init_sequence_f時,會通過相對偏移獲取init_sequence_f的label的地址(23e0844c),再從23e0844c中獲取到init_sequence_f的地址(23e35dcc)。
綜上,當uboot對自身進行relocate之后,此時全局變量的絕對地址已經(jīng)發(fā)生變化,如果函數(shù)按照原來的label去獲取全局變量的地址的時候,這個地址其實是relocate之前的地址。因此,在relocate的過程中需要對全局變量的label中的地址值進行修改,所以uboot將這些label的地址全部維護在.rel.dyn段中,然后再統(tǒng)一對.rel.dyn段指向的label進行修改。后續(xù)代碼可以看出來。
三、uboot relocate代碼介紹
1、uboot relocate地址和布局。
前面已經(jīng)說明,uboot的relocation動作會把自己本身relocate到ddr上(前提是在SPL的過程中或者在dram_init中已經(jīng)對ddr進行初始化了),并且會relocate到ddr的頂端地址使之不會和kernel的沖突。?
但是relocate過程中,并不是直接把uboot直接放到ddr的頂端位置,而是會有一定的布局,預(yù)留一些空間給其他一些需要固定空間的功能使用。
- uboot relocate從高地址到低地址布局如下(并不是所有的區(qū)域都是需要的,可以根據(jù)宏定義來確定),注意,對應(yīng)區(qū)域的size在這個時候都是確定的,不會發(fā)生變化了。
| prom頁表區(qū)域 | 8192byte |
| logbuffer | LOGBUFF_RESERVE |
| pram區(qū)域 | CONFIG_PRAM<<10 |
| round_4k | 用于4kb對齊 |
| mmu頁表區(qū)域 | PGTABLE_SIZE |
| video buffer | 不關(guān)心。但是是確定的。不會隨著代碼變化 |
| lcd buffer | 不關(guān)心。但是是確定的。不會隨著代碼變化 |
| trace buffer | CONFIG_TRACE_BUFFER_SIZE |
| uboot代碼區(qū)域 | gd->mon_len,并且對齊4KB對齊 |
| malloc內(nèi)存池 | TOTAL_MALLOC_LEN |
| Board Info區(qū)域 | sizeof(bd_t) |
| 新global_data區(qū)域 | sizeof(gd_t) |
| fdt區(qū)域 | gd->fdt_size |
| 對齊 | 16b對齊 |
| 堆棧區(qū)域 | 無限制 |
2、relocate代碼流程
主要是分成如下流程?
* 對relocate進行空間規(guī)劃?
* 計算uboot代碼空間到relocation的位置的偏移?
* relocate舊的global_data到新的global_data的空間上?
* relocate舊的uboot代碼空間到新的空間上去?
* 修改relocate之后全局變量的label。(不懂的話參考第二節(jié))?
* relocate中斷向量表
(1)首先看一下relocate的整體代碼?
去掉無關(guān)代碼的代碼如下:?
arch/arm/lib/crt0.S
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
注意上面的注釋,從relocate_code返回之后就已經(jīng)在新的uboot代碼空間中運行了。
這里簡單地說明一下board_init_f:
static init_fnc_t init_sequence_f[] = { #ifdef CONFIG_SANDBOXsetup_ram_buf, #endifsetup_mon_len, #ifdef CONFIG_OF_CONTROLfdtdec_setup, #endif #ifdef CONFIG_TRACEtrace_early_init, ... } // 可以看出init_sequence_f是一個函數(shù)指針數(shù)組void board_init_f(ulong boot_flags) {if (initcall_run_list(init_sequence_f)) // 在這里會init_sequence_f里面的函數(shù)hang(); }- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
(2)對relocate進行空間規(guī)劃?
布局已經(jīng)在上面說過了。?
其規(guī)劃只要體現(xiàn)在gd一些指針的設(shè)置,如下面所示
——————————————————— <—–(gd->ram_top)?
| 最高的區(qū)域?
———————————————————?
| ……?
———————————————————?
| uboot代碼區(qū)域?
——————————————————— <—–(gd->relocaddr)?
| ……?
———————————————————?
| Board Info區(qū)域?
——————————————————— <—–(gd->bd)?
| 新global_data區(qū)域?
——————————————————— <—–(gd->new_gd)?
| fdt區(qū)域?
——————————————————— <—–(gd->new_fdt)?
| …..?
——————————————————— <—–(gd->start_addr_sp)?
| 堆棧區(qū)域?
———————————————————
在board_init_f中,會依次執(zhí)行init_sequence_f數(shù)組里面函數(shù)。其中,和relocate空間規(guī)劃的函數(shù)如下:
static init_fnc_t init_sequence_f[] = {setup_dest_addr, #if defined(CONFIG_SPARC)reserve_prom, #endif #if defined(CONFIG_LOGBUFFER) && !defined(CONFIG_ALT_LB_ADDR)reserve_logbuffer, #endif #ifdef CONFIG_PRAMreserve_pram, #endifreserve_round_4k, #if !(defined(CONFIG_SYS_ICACHE_OFF) && defined(CONFIG_SYS_DCACHE_OFF)) && \defined(CONFIG_ARM)reserve_mmu, #endif #ifdef CONFIG_DM_VIDEOreserve_video, #else # ifdef CONFIG_LCDreserve_lcd, # endif/* TODO: Why the dependency on CONFIG_8xx? */ # if defined(CONFIG_VIDEO) && (!defined(CONFIG_PPC) || defined(CONFIG_8xx)) && \!defined(CONFIG_ARM) && !defined(CONFIG_X86) && \!defined(CONFIG_BLACKFIN) && !defined(CONFIG_M68K)reserve_legacy_video, # endif #endif /* CONFIG_DM_VIDEO */reserve_trace, #if !defined(CONFIG_BLACKFIN)reserve_uboot, #endif #ifndef CONFIG_SPL_BUILDreserve_malloc,reserve_board, #endifsetup_machine,reserve_global_data,reserve_fdt,reserve_arch,reserve_stacks,- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
代碼里面都是一些簡單的減法以及指針的設(shè)置??梢詤⒖忌鲜觥皡^(qū)域布局”和指針設(shè)置自己看一下代碼,這里不詳細說明。?
這里說明一下setup_dest_addr,也就是一些指針的初始化。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
(3)計算uboot代碼空間到relocation的位置的偏移?
同樣在board_init_f中,調(diào)用init_sequence_f數(shù)組里面的setup_reloc實現(xiàn)。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
(4)relocate舊的global_data到新的global_data的空間上?
同樣在board_init_f中,調(diào)用init_sequence_f數(shù)組里面的setup_reloc實現(xiàn)。
- 1
- 2
- 3
- 4
- 5
(5)relocate舊的uboot代碼空間到新的空間上去?
代碼在relocate_code中,上述(1)中可以知道此時的r0是uboot的新的地址空間。?
主要目的是把__image_copy_start到__image_copy_end的代碼空間拷貝到新的uboot地址空間中。?
關(guān)于__image_copy_start和__image_copy_end可以看《[uboot] (第四章)uboot流程——uboot編譯流程》
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
(6)修改relocate之后全局變量的label?
需要先完全理解第二節(jié)““位置無關(guān)代碼”介紹及其原理”?
主要目的是修改label中的地址。?
這里復(fù)習(xí)一下:?
* 絕對地址符號的地址會放在label中提供位置無關(guān)代碼使用?
* label的地址會放在.rel.dyn段中?
綜上,當uboot對自身進行relocate之后,此時全局變量的絕對地址已經(jīng)發(fā)生變化,如果函數(shù)按照原來的label去獲取全局變量的地址的時候,這個地址其實是relocate之前的地址。因此,在relocate的過程中需要對全局變量的label中的地址值進行修改,所以uboot將這些label的地址全部維護在.rel.dyn段中,然后再統(tǒng)一對.rel.dyn段指向的label進行修改。后續(xù)代碼可以看出來。?
.rel.dyn段部分示例如下:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
可以看出.rel.dyn段用了8個字節(jié)來描述一個label,其中,高4字節(jié)是label地址標識0x17,低4字節(jié)就是label的地址。?
所以需要先判斷l(xiāng)abel地址標識是否正確,然后再根據(jù)第四字節(jié)獲取label,對label中的符號地址進行修改。
代碼如下:
ENTRY(relocate_code)/** fix .rel.dyn relocations*/ldr r2, =__rel_dyn_start /* r2 <- SRC &__rel_dyn_start */ldr r3, =__rel_dyn_end /* r3 <- SRC &__rel_dyn_end */ // __rel_dyn段是由鏈接器生成的。 // 把__rel_dyn_start放到r2中,把__rel_dyn_end放到r3中fixloop:ldmia r2!, {r0-r1} /* (r0,r1) <- (SRC location,fixup) */ // 從__rel_dyn_start開始,加載兩個字節(jié)到r0和r1中,高字節(jié)存在r1中表示標志,低字節(jié)存在r0中,表示label地址。and r1, r1, #0xffcmp r1, #23 /* relative fixup? */ // 比較高4字節(jié)是否等于0x17bne fixnext // 不等于的話,說明不是描述label地址,進行下一次循環(huán)// label在relocate uboot的時候也已經(jīng)復(fù)制到了新的uboot地址空間了!!! // 這里要注意,是對新的uboot地址空間label進行修改!!!/* relative fix: increase location by offset */add r0, r0, r4 // 獲取新的uboot地址空間的label地址, // 因為r0存的是舊地址空間的label地址,而新地址空間的label地址就是在舊地址空間的label地址加上偏移得到 // r4就是relocate offset,也就是新舊地址空間的偏移ldr r1, [r0] // 從label中獲取絕對地址符號的地址,存放在r1中add r1, r1, r4str r1, [r0] // 根據(jù)前面的描述,我們的目的就是要fix label中絕對地址符號的地址,也就是將其修改為新地址空間的地址 // 所以為r1加上偏移之后,重新存儲到label中。 // 后面CPU就可以根據(jù)LABEL在新uboot的地址空間中尋址到正確的符號。fixnext:cmp r2, r3blo fixloop- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
(7)relocate中斷向量表?
前面在《[uboot] (第四章)uboot流程——uboot編譯流程》中已經(jīng)分析了,異常中斷向量表的定義如下?
arch/arm/lib/vectors.S
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
我們知道arm的異常中斷向量表需要復(fù)制到0x00000000處或者0xFFFF0000處(不知道的建議網(wǎng)上度娘一下)。?
當uboot進行relocate之后,其異常處理函數(shù)的地址也發(fā)生了變化,因此,我們需要把新的異常中斷向量表復(fù)制到0x00000000處或者0xFFFF0000處。?
這部分操作就是在relocate_vectors中進行。
異常中斷向量表在uboot代碼空間中的地址如下:
23e00000 <__image_copy_start>: 23e00000: ea0000be b 23e00300 <reset> 23e00004: e59ff014 ldr pc, [pc, #20] ; 23e00020 <_undefined_instruction> 23e00008: e59ff014 ldr pc, [pc, #20] ; 23e00024 <_software_interrupt> 23e0000c: e59ff014 ldr pc, [pc, #20] ; 23e00028 <_prefetch_abort> 23e00010: e59ff014 ldr pc, [pc, #20] ; 23e0002c <_data_abort> 23e00014: e59ff014 ldr pc, [pc, #20] ; 23e00030 <_not_used> 23e00018: e59ff014 ldr pc, [pc, #20] ; 23e00034 <_irq> 23e0001c: e59ff014 ldr pc, [pc, #20] ; 23e00038 <_fiq> // 可以看出以下是異常終端向量表 23e00020 <_undefined_instruction>: 23e00020: 23e00060 mvncs r0, #96 ; 0x60 // 其中,23e00020存放的是未定義指令處理函數(shù)的地址,也就是23e00060 // 以下以此類推23e00024 <_software_interrupt>: 23e00024: 23e000c0 mvncs r0, #192 ; 0xc0 23e00028 <_prefetch_abort>: 23e00028: 23e00120 mvncs r0, #8 23e0002c <_data_abort>: 23e0002c: 23e00180 mvncs r0, #3223e00030 <_not_used>: 23e00030: 23e001e0 mvncs r0, #56 ; 0x38 23e00034 <_irq>: 23e00034: 23e00240 mvncs r0, #4 23e00038 <_fiq>: 23e00038: 23e002a0 mvncs r0, #10 23e0003c: deadbeef cdple 14, 10, cr11, cr13, cr15, {7}23e00040 <IRQ_STACK_START_IN>:- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
所以異常中斷向量表就是從偏移0x20開始的32個字節(jié)。
代碼如下(去除掉無關(guān)代碼部分):
ENTRY(relocate_vectors)/** Copy the relocated exception vectors to the* correct address* CP15 c1 V bit gives us the location of the vectors:* 0x00000000 or 0xFFFF0000.*/ @@ 注意看注釋,通過cp15協(xié)處理器的c1寄存器的V標志來判斷cpu從什么位置獲取中斷向量表, @@ 換句話說,就是中斷向量表應(yīng)該被復(fù)制到什么地方!!!ldr r0, [r9, #GD_RELOCADDR] /* r0 = gd->relocaddr */ @@ 獲取uboot新地址空間的起始地址,存放到r0寄存器中mrc p15, 0, r2, c1, c0, 0 /* V bit (bit[13]) in CP15 c1 */ands r2, r2, #(1 << 13)ldreq r1, =0x00000000 /* If V=0 */ldrne r1, =0xFFFF0000 /* If V=1 */ @@ 獲取cp15協(xié)處理器的c1寄存器的V標志,當V=0時,cpu從0x00000000獲取中斷向量表,當V=1時,cpu從0xFFFF0000獲取中斷向量表 @@ 將該地址存在r1中l(wèi)dmia r0!, {r2-r8,r10}stmia r1!, {r2-r8,r10} @@ 前面說了異常中斷向量表就是從偏移0x20開始的32個字節(jié)。 @@ 所以這里是過濾掉前面的0x20個字節(jié)(32個字節(jié),8*4) @@ 但是不明白為什么還要stmia r1!, {r2-r8,r10},理論上只需要讓r0的值產(chǎn)生0x20的偏移就可以了才對???不明白。@@ 經(jīng)過上述兩行代碼之后,此時r0的值已經(jīng)偏移了0x20了ldmia r0!, {r2-r8,r10}stmia r1!, {r2-r8,r10} @@ 繼續(xù)從0x20開始,獲取32個字節(jié),存儲到r1指向的地址,也就是cpu獲取中斷向量表的地址 @@ r2-r8,r10表示從r2到r8寄存器和r10寄存器,一個8個寄存器,每個寄存器有4個字節(jié),所以就從r0指向的地址處獲取到了32個字節(jié) @@ 再把 {r2-r8,r10}的值存放到r1指向的地址,也就是cpu獲取中斷向量表的地址bx lr @@ 返回 ENDPROC(relocate_vectors)- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
經(jīng)過上述,uboot relocate就完成了。
《新程序員》:云原生和全面數(shù)字化實踐50位技術(shù)專家共同創(chuàng)作,文字、視頻、音頻交互閱讀總結(jié)
以上是生活随笔為你收集整理的6-uboot relocation介绍的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 5-global_data介绍
- 下一篇: uboot流程——uboot启动流程