Uboot has been porting uboot recently. There are many things worth learning about in uboot. We have summarized the uboot startup process before. However, a very core function of uboot has not been carefully studied, is the relocation function of uboot.
The relocation function of uboot has been studied over the past few days and shared with you.
The so-called relocation is relocation. After uboot runs, it will copy its code to another location of the SDRAM to continue running.This is described in the uboot Startup Process Analysis.
However, based on previous understanding, a complete and runable binfile, the link address specified during link, the load address, and the runtime address should be the same.
The running address after relocation is different from the loading address, especially the link address. Will the arm addressing be faulty?
The new version of uboot is different from the old version of uboot, regardless of the location of the uboot load ADDR (Entry pointer). After the new version of uboot starts, the accountant calculates an address near the top of the SDRAM, copy your code to this address and continue running.
I personally think that uboot has two purposes: one is to free up the low-end space for the kernel, to prevent the kernel from extracting and overwriting the uboot, and the other is to start the static storage (spiflash nandflash, this relocation is required.
However, there will be a problem. After relocation, the uboot running address is inconsistent with its link address. compiler will determine the absolute address of the variables and functions in the link, the link address load address must be the same as the running address,
In this case, arm finds the address before relocation when addressing these variable functions, so that relocation is meaningless!
Of course uboot won't be like this. Let's analyze how to address the relocation in uboot. Before learning, I had three questions:
(1) how to make addressing calls to Functions
(2) how to perform addressing (read/write) on global variables)
(3) how to operate other variables or function addresses stored in global pointer variables after relocation
To clarify these three problems, I can understand the relocation principle.
To clarify this, add the following code to a uboot file:
void test_func(void){ printf("test func\n");}static void * test_func_val = test_func;static int test_val = 10; void rel_dyn_test(){ test_val = 20; printf("test = 0x%x\n", test_func); printf("test_func = 0x%x\n", test_func_val); test_func();}
The rel_dyn_test function contains the three cases of function pointer variable assignment function calling. Addressing must be tracing at the Assembly level. disassembly is performed after compilation, get the u-boot.dump (objdump uses the-D option to disassemble all sections)
Find the rel_dyn_test function, as shown below:
80e9d3cc <test_func>:80e9d3cc: e59f0000 ldr r0, [pc, #0] ; 80e9d3d4 <test_func+0x8>80e9d3d0: eaffc2fb b 80e8dfc4 <printf>80e9d3d4: 80eb1c39 .word 0x80eb1c3980e9d3d8 <rel_dyn_test>:80e9d3d8: e59f202c ldr r2, [pc, #44] ; 80e9d40c <rel_dyn_test+0x34>80e9d3dc: e3a03014 mov r3, #20 ; 0x14 80e9d3e0: e92d4010 push {r4, lr}80e9d3e4: e59f1024 ldr r1, [pc, #36] ; 80e9d410 <rel_dyn_test+0x38>80e9d3e8: e5823000 str r3, [r2]80e9d3ec: e59f0020 ldr r0, [pc, #32] ; 80e9d414 <rel_dyn_test+0x3c>80e9d3f0: ebffc2f3 bl 80e8dfc4 <printf>80e9d3f4: e59f301c ldr r3, [pc, #28] ; 80e9d418 <rel_dyn_test+0x40>80e9d3f8: e59f001c ldr r0, [pc, #28] ; 80e9d41c <rel_dyn_test+0x44>80e9d3fc: e5931000 ldr r1, [r3]80e9d400: ebffc2ef bl 80e8dfc4 <printf>80e9d404: e8bd4010 pop {r4, lr}80e9d408: eaffffef b 80e9d3cc <test_func>80e9d40c: 80eb75c0 .word 0x80eb75c080e9d410: 80e9d3cc .word 0x80e9d3cc80e9d414: 80eb1c44 .word 0x80eb1c4480e9d418: 80eaa54c .word 0x80eaa54c80e9d41c: 80eb1c51 .word 0x80eb1c51
...
Data Section
80eb75c0 <test_val>:80eb75c0: 0000000a .word 0x0000000a
...
80eaa54c <test_func_val>:80eaa54c: 80e9d3cc .word 0x80e9d3cc
After rel_dyn_test disassembly, a portion of the memory space starting from 0x80e9d40c is added. The comparison shows that the value of the memory space address is the address of the test_val test_func_val variable required by the function.
According to online materials, the memory space of the variable address stored at the end of these functions is label (automatically allocated by the compiler)
Command lines for analysis.
LDR R2, [PC, #44] ========> r2 = [PC + 0x2c] ======> r2 = [0x80e9d3e0 + 0x2c] ==== => r2 = [0x80e9d40c]
Note that due to the arm assembly line mechanism, the current Pc value is the current address plus 8 bytes
In this way, R2 obtains the value of 0x80e9d40c address 0x80eb75c0, which is the value of test_val.
MoV R3, #20 ======> R3 = 20
Corresponding to the C function, this should be prepared for test_val = 20. Skip the next two commands and find
STR R3, [R2]
Obviously, the number 20 is saved to 0x80eb75c0, which is also test_val.
The three commands indicate that arm's addressing of the variable test_val is as follows:
(1) store the address of the variable test_val in the label at the end of the function (the memory space is automatically allocated by the compiler, not by humans)
(2) obtain the variable address on the function end label based on PC relative addressing
(3) read and write the test_val variable address.
Let's take a look at several commands.
LDR R3, [PC, #28] ====> R3 = [0x80e9d3fc + 0x1c] ====> R3 = [0x80e9d418] ==> R3 = 0x80eaa54c
LDR R1, [R3] ====> R1 = [0x80eaa54c] ====> R1 = 0x80e9d3cc
0x80e9d3cc: the entry address of test_func. Here is the value of printf printing test_func_val.
It can be seen that the addressing of function pointer variables is the same as that of common variables.
Next, let's look at the function call. We can see that for printf and test_func, the commands BL and B are used for redirection. These two commands are all relative addressing (PC + offset)
This indicates that the arm calls a function using the relative addressing command BL or B, which is irrelevant to the absolute address of the function.
You already know the addressing methods for these three situations, so you need to think about what changes will happen after relocation.
After rel_dyn_test relocation, we can imagine that the function call is still normal, because BL or B is used as a relative jump command.
But there is a problem with variable addressing, and there is no problem in the first two steps of addressing,The variable address in the label at the end is obtained through relative addressing, but the obtained variable address is the absolute address determined during link!
The addressing of pointer variables is more problematic,
First, like common variable addressing, the variable address in the memory space at the end is the absolute address of the link. Furthermore, the variable pointer or function pointer stored in the pointer variable is also the absolute address determined during the link, this value also changes after relocation!
How does uboot handle these situations? More accurately, how should compiler and uboot work together to deal with these situations?
The PIC location-independent code is used here, which is generated by specifying the compilation option-FPIC or-fpie for the compiler,
In this way, the target file generated by compilation contains the information required by the PIC.-FPIC and-fpie are the PIC compilation options of GCC. LD also has the PIC connection option-pie. To obtain a complete PIC executable file, you must specify the-pie option for the LD when connecting to the target file,
Check the uboot compilation options and find the following in arch/ARM/config. mk:
# needed for relocationLDFLAGS_u-boot += -pie
Uboot only specifies-pie to lD, but does not specify-FPIC or-fpie to GCC.
Specify-pie, and a rel. Dyn segment will appear in the uboot compiled by pie,Uboot achieves the perfect relocation through Rel. Dyn!
Look at the rel. Dyn segment in the u-boot.dump, as shown below:
Disassembly of section .rel.dyn:80eb7d54 <__rel_dyn_end-0x5c10>:80eb7d54: 80e80020 rschi r0, r8, r0, lsr #3280eb7d58: 00000017 andeq r0, r0, r7, lsl r080eb7d5c: 80e80024 rschi r0, r8, r4, lsr #3280eb7d60: 00000017 andeq r0, r0, r7, lsl r080eb7d64: 80e80028 rschi r0, r8, r8, lsr #3280eb7d68: 00000017 andeq r0, r0, r7, lsl r0。。。<span style="color:#FF0000;">80eba944: 80e9d40c rschi sp, r9, ip, lsl #880eba948: 00000017 andeq r0, r0, r7, lsl r080eba94c: 80e9d410 rschi sp, r9, r0, lsl r480eba950: 00000017 andeq r0, r0, r7, lsl r080eba954: 80e9d414 rschi sp, r9, r4, lsl r480eba958: 00000017 andeq r0, r0, r7, lsl r080eba95c: 80e9d418 rschi sp, r9, r8, lsl r480eba960: 00000017 andeq r0, r0, r7, lsl r080eba964: 80e9d41c rschi sp, r9, ip, lsl r480eba968: 00000017 andeq r0, r0, r7, lsl r0</span>。。。。
Have you noticed that the label address storing the global variable address at the end of rel_dyn_test is also stored here? What is the purpose,
Let's take a look at how uboot's core function relocate_code implements its own relocation,
In ARCH/ARM/lib/relocate. s
ENTRY(relocate_code) ldr r1, =__image_copy_start /* r1 <- SRC &__image_copy_start */ subs r4, r0, r1 /* r4 <- relocation offset */ beq relocate_done /* skip relocation */ ldr r2, =__image_copy_end /* r2 <- SRC &__image_copy_end */copy_loop: ldmia r1!, {r10-r11} /* copy from source address [r1] */ stmia r0!, {r10-r11} /* copy to target address [r0] */ cmp r1, r2 /* until source end address [r2] */ blo copy_loop /* * fix .rel.dyn relocations */ ldr r2, =__rel_dyn_start /* r2 <- SRC &__rel_dyn_start */ ldr r3, =__rel_dyn_end /* r3 <- SRC &__rel_dyn_end */fixloop: ldmia r2!, {r0-r1} /* (r0,r1) <- (SRC location,fixup) */ and r1, r1, #0xff cmp r1, #23 /* relative fixup? */ bne fixnext /* relative fix: increase location by offset */ add r0, r0, r4 ldr r1, [r0] add r1, r1, r4 str r1, [r0]fixnext: cmp r2, r3 blo fixlooprelocate_done:
Copy data between _ image_copy_start and _ image_copy_end as described in the uboot startup process.
Let's take a look at arm's link script, in arch/ARM/CPU/u-boot.lds, as follows:
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")OUTPUT_ARCH(arm)ENTRY(_start)SECTIONS{ . = 0x00000000; . = ALIGN(4); .text : { *(.__image_copy_start) CPUDIR/start.o (.text*) *(.text*) } . = ALIGN(4); .rodata : { *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*))) } . = ALIGN(4); .data : { *(.data*) } . = ALIGN(4); . = .; . = ALIGN(4); .u_boot_list : {<pre name="code" class="cpp"> KEEP(*(SORT(.u_boot_list*))); } . = ALIGN(4); .image_copy_end : { *(.__image_copy_end) } .rel_dyn_start : { *(.__rel_dyn_start) } .rel.dyn : { *(.rel*) } .rel_dyn_end : { *(.__rel_dyn_end) } .end : { *(.__end) } _image_binary_end = .;
We can see that _ image_copy_start --- end contains the text data rodata segment, but not rel_dyn.
Continue to read the relocate_code function, copy the data between _ image_copy_start ---- end, but do not copy the rel. Dyn segment.
First, get the _ rel_dyn_start address to R2, and store the value of two 4-byte addresses on the start address in R0 R1.
Judge that the value in R1 is 8 bits low. If it is 0x17, add the value in R0 to relocation offset.
Obtain the value of this R0 value as the address and store it in R1.
Add relocation offset to the value of R1 and save it back to the address where the value of R0 is used.
In this loop, until _ rel_dyn_end.
In this way, read some reading ports. Here is an example of rel_dyn_test.
The above section Rel. Dyn contains the following section:
80eba944: 80e9d40c rschi sp, r9, ip, lsl #880eba948: 00000017 andeq r0, r0, r7, lsl r0
According to the above analysis, we can determine that the second four bytes is 0x17, and the R0 is stored as 0x80e9d40c. This is the label address at the end of rel_dyn_test,
Add the relocation offset to r0, And the label at the end of rel_dyn_test after the relocation is reached.
Obtain the value of R0 in R1, 0x80eb75c0. We can see that this value is the first address of the variable test_val.
Add relocation offset to R1 and write it back to R0. This means to add the test_val address to the offset variable and write it back to the label at the end of rel_dyn_test after relocation.
In this way, after relocate_code is complete, let's look at the addressing to test_val. The third step of addressing gets the modified relocation ADDR. In this way, we can get the test_val value after relocation!
The addressing of common variables is like this. What about pointer variables, such as test_func_val?
The steps for getting the address after test_func_val relocation are the same as above, but when we get the value of test_func_val, we should note that this variable stores the test_func pointer, which was previously 0x80e9d3cc, the relocation changes, so the value of test_func_val should also change. What should I do?
The method is the same. You can find the following section in Rel. Dyn:
80ebc18c: 80eaa54c rschi sl, sl, ip, asr #1080ebc190: 00000017 andeq r0, r0, r7, lsl r0
The address test_func_val is stored above. According to the relocate_code operation, the value on 80eaa54c + offset should also be + offset.
This solves the problem. The test_func_val value is the address after test_func is changed to relocation.
For more information on the Internet, here, the second 4-byte (0x17) of every rel Section (8 bytes) in Rel. Dyn is a label type r_arm_relative,
After the relocate_code of uboot above, the addressing of the three questions we proposed can work normally.
There is another question: who decides which labels are put in rel. in Dyn, especially for the variables storing pointers, how to distinguish them. In this case, it seems that the compiler's lD is used to complete the work and put all the labels that require relocate into Rel. the Dyn segment is really awesome compiler!
To sum up, we can see that,
Use the compiler with the-pie option to store the address of the value to be relocate (global variable address function entry address) in Rel. in the dyn segment, the uboot runtime relocate_code traverses Rel. dyn segment, according to rel. the value stored in Dyn is relocate the value on the address (these values + offset) to modify all the variables that require relocate!.... There are still some interfaces...
Note that, in the entire relocate_code of uboot, Rel. Dyn is not copied, nor modified. The value of Rel. Dyn + offset is the address value!
After checking the online information, compiler adds the-FPIC or-fpie option during CC to generate a got (Global Offset Table) in the target file ), store the value of relocate in this file in got. The label at the end of the function stores the offset of got and the offset of the variable. The variable addressing first finds the got address based on the relative addressing of the label at the end, and the location of the variable address in got to determine the variable address. In this way, the value in got is modified for the target file, and the offset of the variable address is modified, and the relocation is completed.
When the-pie option is added to the LD, The got will be merged into the rel. Dyn segment, and the uboot will modify the value of the relocation according to the rel. Dyn segment in the relocate_code.
In uboot, LD uses-pie and CC does not use-FPIC or-fpie. Got is not generated in the target file. The addressing in the function is the absolute address of the variable directly stored in the label at the end, but this label also exists in Rel. in Dyn, uboot depends on Rel. modify the value on the label in the dyn segment to complete the relocation.
This not only saves the got segment of each target file, but also does not need to address got. You can directly modify the variable address stored by the label at the end of the function!
This is the relocation of uboot!