Detailed analysis of uboot relocation principles

Source: Internet
Author: User

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!









Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.