Microcontroller Scatter-loading Mechanism, Address-Independent Compilation Analysis (I)

Created at 2020-12-10 19:02:06

Background

  • I've wrote a blog that simply explained the address-independent compilation of ropi and rwpi technology, simply with an os I am currently using (based on RT-Thread), some of the details are not clear, so I am going to further introduce the address-independent compilation in this article.
  • I prefer technology sharing, arguing that technology should be open for everyone for mutual progress.
  • Because the length of a single article is limited to 10,000 characters, so I can only split the article into several articles.

The decentralized loading mechanism of Microcontroller

  • This time still take the Microcontroller as an example, because I am more familiar with the Microcontroller, and compared to FPGA, DSP, Intel and other CPUs, Microcontroller is really relatively simple).
  • Below I take a Microcontroller that based on arm Cortex-M4 architecture as the object of analysis (we can analogy to stm32f407.

1.Decentralized loading

Decentralized loading actually dictates how a Microcontroller (or machine) prepares the operating environment before and after the official power-on execution, which refers primarily to how variable addresses are allocated.
It may be inappropriate to say how variable addresses are allocated, but I really can't find a noun to describe this scenario: Relocate variables from flash or rom to ram space.

"Relocation" is referred as the following considerations:

  • ram is a volatile storage medium, data will be lost when power is off, and the operating environment of the single-chip microcomputer will be power off at any time (for example, the program under stm32 is downloaded to sram). Therefore, you must first save a copy of these data in flash or rom, and copy a copy from rom every time you power on. I am used to calling this kind of copying "moving";
  • And this kind of "moving" is not a brainless "copy". For the different characteristics of the data (such as initial value assignment, initial value assignment, initial value 0, etc.), this "copy method" is different, that is, some are copied, some are decompressed, some are initialized with 0, and so on. So I think it is more appropriate to call "relocation".

    The following describes this "relocating process" in as much detail as possible.

a. First, generate a .S file, which is disassembly

#### a. First, generate a .S file, which is disassembly

**The following are examples of KEIL! **

-Use the fromelf.exe tool that comes with KEIL (this tool is located in the KEIL installation directory, for example: `D:\MDK-ARM V5.25\Keil_v5\ARM\ARM\ARMCC\bin` and `D:\MDK- ARM V5.25\Keil_v5\ARM\ARM\ARMCLANG\bin`, these two are tools under two different compilation chains. For Cortex-M, there is no difference between the two. For details, you can Baidu: "** The difference between armcc and armclang. **”).
-Then compile and link in KEIL IDE to generate an .axf file.
-Use the command: **fromelf -c !L --output @LS** to generate the .S file (note that when generating the .axf file, check the debugging information, so that the generated .S file is easier to read. The method is shown below). About why you want to check Debug Infomation, you can learn the format content of the following axf file, and about the usage and format of fromelf file, you can go to Baidu.

**If you follow my procedure exactly, you will get a .S file with the same name as the .axf file. **

We are using KEIL for example

  • Use the fromelf.exe tool that comes with KEIL (this tool is located in the KEIL installation directory, for example: D:\MDK-ARM V5.25\Keil_v5\ARM\ARM\ARMCC\bin and D:\MDK- ARM V5.25\Keil_v5\ARM\ARM\ARMCLANG\bin, these two are tools under two different compilation chains. For Cortex-M, there is no difference between the two.
  • Then compile and link in KEIL IDE to generate an .axf file.
  • Use the command: fromelf -c !L --output @LS to generate the .S file (note that when generating the .axf file, check the debugging information, so that the generated .S file is easier to read. The method is shown below). About why you want to check Debug Infomation, you can learn the format content of the following axf file, and about the usage and format of fromelf file, you can go to Baidu.

    If you follow my procedure exactly, you will get a .S file with the same name as the .axf file.

b. Introduce the power-on execution process

For the power-on process under bare metal, I will not set the program separately for bare metal. Here is just a brief description of the process:

  • After power on, it will trigger the reset mechanism of the reset circuit (essentially it is a RC, the capacitor is turned on at the beginning of power on, triggering a low level for a period of time, and then the capacitor is fully charged and maintained at high level)
  • After reset, the PC pointer of the ARM core jumps to 0x0000_0000 for execution.
  • According to the ARM manual, 0x0000_0000 puts the stack pointer in order, resets the interrupt service function address, and so on.
  • Set the top of the stack, get the address of the reset interrupt service function, and then execute it (this can be found in the startup file, usually named startup_xxx.s).
  • Enter the reset interrupt service function, the __main() function will be executed (this is not the main() function we saw, this is a built-in function of the compiler, used to call some initialization operation functions, and then call our main Function, return the right to use).
  • In the __main() function, there are generally two functions: __scatterload() and __rt_entry(), one for scatter loading and one for system library initialization (such as __rt_lib_init(), The dynamic loading process is implemented in this function).
  • Then the main() function is called by __rt_entry() to jump to c language.
  • In KEIL, $Sub$$main and $Super$$main, these two are used to call some operations before the main() function.
    -The above completes the entire power-on process.

c. Scatter loading process

!!!main
    __main
        0x000000c0:    f000f802    ....    BL       __scatterload ; 0xc8
        0x000000c4:    f000f883    ....    BL       __rt_entry ; 0x1ce
    !!!scatter
    __scatterload
    __scatterload_rt2
    __scatterload_rt2_thumb_only
        0x000000c8:    a00a        ..      ADR      r0,{pc}+0x2c ; 0xf4
        0x000000ca:    e8900c00    ....    LDM      r0,{r10,r11}
        0x000000ce:    4482        .D      ADD      r10,r10,r0
        0x000000d0:    4483        .D      ADD      r11,r11,r0
        0x000000d2:    f1aa0701    ....    SUB      r7,r10,#1
    __scatterload_null
        0x000000d6:    45da        .E      CMP      r10,r11
        0x000000d8:    d101        ..      BNE      0xde ; __scatterload_null + 8
        0x000000da:    f000f878    ..x.    BL       __rt_entry ; 0x1ce
        0x000000de:    f2af0e09    ....    ADR      lr,{pc}-7 ; 0xd7
        0x000000e2:    e8ba000f    ....    LDM      r10!,{r0-r3}
        0x000000e6:    f0130f01    ....    TST      r3,#1
        0x000000ea:    bf18        ..      IT       NE
        0x000000ec:    1afb        ..      SUBNE    r3,r7,r3
        0x000000ee:    f0430301    C...    ORR      r3,r3,#1
        0x000000f2:    4718        .G      BX       r3
    $d
        0x000000f4:    00037f6c    l...    DCD    229228
        0x000000f8:    00037f8c    ....    DCD    229260

;;中间的代码省略...

Region$$Table$$Base
        0x00038060:    00000055    U...    DCD    85
        0x00038064:    00000002    ....    DCD    2
        0x00038068:    00001b38    8...    DCD    6968
        0x0003806c:    00037f63    c...    DCD    229219
        0x00038070:    00000819    ....    DCD    2073
        0x00038074:    00001b3a    :...    DCD    6970
        0x00038078:    0000f5d0    ....    DCD    62928
        0x0003807c:    00037eeb    .~..    DCD    229099

The above code is directly from the process of __main() jumps to the __main() function, first execute the __scatterload() function, here is the process of scatter loading.

The following is the analysis:

  • Execute to the __main() function (actually in the assembly world, this is a label. There is not much difference between a function and a variable. It is nothing more than some binary data with special meaning in the address space).
  • Jump to execute 0x000000c0: f000f802 .... BL __scatterload; 0xc8, you only need to see BL 0xc8, the other information is all mixed information (I said that the generated .axf needs to be With debugging information, you will see the words BL __scatterload; 0xc8 here, __scatterload() is actually extracted from the symbol table in the .axf file).
  • Then execute the __scatterload() function (the code below is exactly the same as the one in the previous section).
!!!main
    __main
        0x000000c0:    f000f802    ....    BL       __scatterload ; 0xc8
        0x000000c4:    f000f883    ....    BL       __rt_entry ; 0x1ce
    !!!scatter
    __scatterload
    __scatterload_rt2
    __scatterload_rt2_thumb_only
        0x000000c8:    a00a        ..      ADR      r0,{pc}+0x2c ; 0xf4
        0x000000ca:    e8900c00    ....    LDM      r0,{r10,r11}
        0x000000ce:    4482        .D      ADD      r10,r10,r0
        0x000000d0:    4483        .D      ADD      r11,r11,r0
        0x000000d2:    f1aa0701    ....    SUB      r7,r10,#1
    __scatterload_null
        0x000000d6:    45da        .E      CMP      r10,r11
        0x000000d8:    d101        ..      BNE      0xde ; __scatterload_null + 8
        0x000000da:    f000f878    ..x.    BL       __rt_entry ; 0x1ce
        0x000000de:    f2af0e09    ....    ADR      lr,{pc}-7 ; 0xd7
        0x000000e2:    e8ba000f    ....    LDM      r10!,{r0-r3}
        0x000000e6:    f0130f01    ....    TST      r3,#1
        0x000000ea:    bf18        ..      IT       NE
        0x000000ec:    1afb        ..      SUBNE    r3,r7,r3
        0x000000ee:    f0430301    C...    ORR      r3,r3,#1
        0x000000f2:    4718        .G      BX       r3
    $d
        0x000000f4:    00037f6c    l...    DCD    229228
        0x000000f8:    00037f8c    ....    DCD    229260

;;中间的代码省略...

Region$$Table$$Base
        0x00038060:    00000055    U...    DCD    85
        0x00038064:    00000002    ....    DCD    2
        0x00038068:    00001b38    8...    DCD    6968
        0x0003806c:    00037f63    c...    DCD    229219
        0x00038070:    00000819    ....    DCD    2073
        0x00038074:    00001b3a    :...    DCD    6970
        0x00038078:    0000f5d0    ....    DCD    62928
        0x0003807c:    00037eeb    .~..    DCD    229099

It is very clear to see the code from 0x000000c8 to 0x000000d2, which actually obtains the start address of the data block Region$$Table$$Base (r10 is the start address, r11 is the end address + 1, r7 is the starting address -1. It just uses the relocation technology to know the address of Region$$Table$$Base when linking, and then come back to modify the value at $d, so this code uses relative Address offset, in short, is the 0 filled in $d when compiling, and the real value is only known when linking).
Then execute sequentially to __scatterload_null(), __scatterload_nul()l is essentially a loop, as if it is executed using the data in Region$$Table$$Base, in order to facilitate understanding, I wrote a similar function in C code:

Check out the next article.

0 Answer

Create
Post