Hello everyone, I am Pi Ziheng, a serious technical guy. Today, Pi Ziheng will talk to you about the linker file in embedded development .
In the previous lesson "Source Files (.c/.h/.s)" , Pi Ziheng systematically introduced source files to everyone. Source files are typical input files in embedded projects. So are there other types of input files? Since Pi Ziheng asked this question, the answer must be yes. The linker file that Pi Ziheng is going to talk about today is another type of input file.
As the name implies, the linker file is a file used in the linking stage of embedded engineering. After the source file is compiled (it is already machine-readable binary machine code data), it needs to go through the linker to organize the binary data in an orderly manner to form the final binary executable file, which will eventually be downloaded into the non-volatile memory inside the chip. The linker file is used to instruct the linker how to organize the compiled binary data.
Linker files are closely related to IDE. This article takes IAR EWARM as an example to introduce linker files. The linker files under other IDEs can be applied by analogy.
1. Section in embedded system
Before talking about linker files, Pi Ziheng must first clarify a very important concept in embedded systems - section. So what is a section? The C or assembly source files we write are all kinds of application codes. These codes can be divided into many categories according to their functions, such as constants, variables, functions, stacks, etc., and a collection of the same type of code is a section. The basic unit for the linker to organize data during linking is a section. So how many types of sections are there in a typical embedded system? The following lists all the default sections in IAR. Those common sections will be mentioned in the subsequent introduction of linker files.
//常见Section
.bss // Holds zero-initialized static and global variables.
CSTACK // Holds the stack used by C or C++ programs.
.data // Holds static and global initialized variables.
.data_init // Holds initial values for .data sections when the linker directive initialize is used.
HEAP // Holds the heap used for dynamically allocated data.
.intvec // Holds the reset vector table
.noinit // Holds __no_init static and global variables.
.rodata // Holds constant data.
.text // Holds the program code.
.textrw // Holds __ramfunc declared program code.
.textrw_init // Holds initializers for the .textrw declared section.
//较冷僻Section
.exc.text // Holds exception-related code.
__iar_tls.$$DATA // Holds initial values for TLS variables.
.iar.dynexit // Holds the atexit table.
.init_array // Holds a table of dynamic initialization functions.
IRQ_STACK // Holds the stack for interrupt requests, IRQ, and exceptions.
.preinit_array // Holds a table of dynamic initialization functions.
.prepreinit_array // Holds a table of dynamic initialization functions.
Veneer$$CMSE // Holds secure gateway veneers.
//更冷僻Section
.debug // Contains debug information in the DWARF format
.iar.debug // Contains supplemental debug information in an IAR format
.comment // Contains the tools and command lines used for building the file
.rel or .rela // Contains ELF relocation information
.symtab // Contains the symbol table for a file
.strtab // Contains the names of the symbol in the symbol table
.shstrtab // Contains the names of the sections.
Note: For a detailed explanation of the above section, please refer to the Section reference in the document \IAR Systems\Embedded Workbench xxx\arm\doc\EWARM_DevelopmentGuide.ENU.pdf in the IAR software installation directory.
2. Parsing linker files
Now that you know the concept of section, you can start to understand the linker file in depth. What is a linker file? The linker file is written in the syntax specified by the IDE and is used to instruct the linker to allocate the storage location of each section in the embedded system memory. As we all know, the embedded system memory is mainly divided into two categories: ROM (non-volatile) and RAM (volatile), so these corresponding sections are also divided into two types of attributes according to the different storage locations: readonly and readwrite. In fact, the work of the linker file is to put the readonly section into ROM and the readwrite section into RAM.
So how do you write a linker file for a project? As mentioned earlier, linker files also have syntax, and this syntax is specified by the IDE, so you must first master the syntax rules set by the IDE. The linker file syntax rules are relatively simple, and the most commonly used keywords are the following 8:
// 动词类关键字
define // 定义各种空间范围、长度
initialize // 设置section初始化方法
place in // 放置section于某region中(具体地址由链接器分配)
place at // 放置section于某绝对地址处
// 名词类关键字
symbol // 各种空间范围、长度的标识
memory // 整个ARM内存空间的标识
region // 在整个ARM内存空间中划分某region空间的标识
block // 多个section的集合块的标识
Note: For a detailed explanation of the above linker syntax, please refer to the section The linker configuration file in the document \IAR Systems\Embedded Workbench xxx\arm\doc\EWARM_DevelopmentGuide.ENU.pdf in the IAR software installation directory.
Now we can happily start writing linker files. Can't wait? Come on, it only takes three steps. Let's do it.
Here we assume that the MCU physical space is: ROM (0x0 - 0x1ffff), RAM (0x10000000 - 0x1000ffff), and the linker requirements that Pi Ziheng wants to write are as follows:
-
The interrupt vector table must be placed at the ROM start address 0x0 and must be 256-byte aligned
-
The size of STACK is 8KB, the size of HEAP is 1KB, and must be 8-byte aligned.
-
SATCK must be placed at the RAM start address 0x10000000
-
The remaining sections are placed in the correct region, and the specific space is automatically allocated by the linker
2.1 Defining physical space
In the first step, we define three non-overlapping spaces: ROM_region, RAM_region, and STACK_region. ROM_region corresponds to the real ROM space, and RAM_region and STACK_region are combined into the real RAM space.
// 定义物理空间边界
define symbol __ICFEDIT_region_ROM_start__ = 0x00000000;
define symbol __ICFEDIT_region_ROM_end__ = __ICFEDIT_region_ROM_start__ + (128*1024 - 1);
define symbol __ICFEDIT_region_RAM_start__ = 0x10000000;
define symbol __ICFEDIT_region_RAM_end__ = __ICFEDIT_region_RAM_start__ + (64*1024 - 1);
define symbol __ICFEDIT_intvec_start__ = __ICFEDIT_region_ROM_start__;
// 定义堆栈长度
define symbol __ICFEDIT_size_cstack__ = (8*1024);
define symbol __ICFEDIT_size_heap__ = (1*1024);
// 定义各region具体空间范围
define memory mem with size = 4G;
define region ROM_region = mem:[from __ICFEDIT_region_ROM_start__ to __ICFEDIT_region_ROM_end__];
define region STACK_region = mem:[from __ICFEDIT_region_RAM_start__ to __ICFEDIT_region_RAM_start__ + __ICFEDIT_size_cstack__ - 1];
define region RAM_region = mem:[from __ICFEDIT_region_RAM_start__ + __ICFEDIT_size_cstack__ to __ICFEDIT_region_RAM_end__];
2.2 Define section collection
The second step is to customize the section collection block. Careful friends can see that the curly braces on the right contain the system default sections introduced in the previous section. We will group sections with the same attributes into a block to facilitate the next step of placement.
// 定义堆栈块及其属性
define block CSTACK with alignment = 8, size = __ICFEDIT_size_cstack__ { };
define block HEAP with alignment = 8, size = __ICFEDIT_size_heap__ { };
// 定义section集合块
define block Vectors with alignment=256 { readonly section .intvec };
define block CodeRelocate { section .textrw_init };
define block CodeRelocateRam { section .textrw };
define block ApplicationFlash { readonly, block CodeRelocate };
define block ApplicationRam { readwrite, block CodeRelocateRam, block HEAP };
Some friends may wonder why we need to define the two blocks CodeRelocate and CodeRelocateRam? Logically, the sections corresponding to these two blocks can be put into ApplicationFlash and ApplicationRam respectively, so why do we need to do this? Friends who have carefully read the source file of the previous class of Pi Ziheng will definitely know the answer. In the startup.c file introduced in that class, there is a function called init_data_bss(). This function will complete the function of initializing the CodeRelocateRam block. It looks for the name of the CodeRelocate segment, which looks clearer and easier to understand than the system default name textrw.
2.3 Placement of section collection
The third step is to place those section assembly blocks. There is an initialize manually statement before placing the assembly block. Why are there these statements? It still has to be combined with the init_data_bss() function in the startup.c file mentioned earlier. This function is the initialization of the data and bss segments implemented by the developer himself, so here you need to notify the IDE that you don’t need to do the initialization work for me anymore.
// 设置初始化方法
initialize manually { readwrite };
initialize manually { section .data};
initialize manually { section .textrw };
do not initialize { section .noinit };
// 放置section集合块
place at start of ROM_region { block Vectors };
//place at address mem:__ICFEDIT_intvec_start__ { block Vectors };
place in ROM_region { block ApplicationFlash };
place in RAM_region { block ApplicationRam };
place in STACK_region { block CSTACK };
Of course, if you want the IDE to automatically initialize the data, bss, and textrw segments for you, you can replace the initialize manually statement with the following statement.
initialize by copy { readwrite, section .textrw };
After setting the initialization method, it is time to place the section collection block. There are two main placement methods, place in and place at. The former is used to specify the space block placement (without specifying the specific address), and the latter is to specify the specific address placement.
At this point, a basic linker file is done. Isn’t it so easy?
Extra 1: Custom section
For those who have read this far with patience, Pi Ziheng must give a big reward. The previous part is about how to deal with the system default segment, so is it possible to customize the segment in the code? Imagine that you have such a requirement, you need to open a 1KB updateable data area in your application, you want to specify this data area to the address range of 0x18000 - 0x183ff, you need to define a 4-byte read-only config block constant in the application to point to the first address of this updateable data area (this config block will only be updated by an external debugger or bootloader), how to do it?
// C文件中
/////////////////////////////////////////////////////
// 用@操作符指定变量myConfigBlock[4]放进自定义.myBuffer section
const uint8_t myConfigBlock[4] @ ".myBuffer" = {0x00, 0x01, 0x02, 0x03};
// Linker文件中
/////////////////////////////////////////////////////
// 自定义指定的mySection_region,并把.myBuffer放到这个region
define region mySection_region = mem:[from 0x0x18000 to 0x183ff];
place at start of mySection_region { readonly section .myBuffer };
The above has achieved the goal of putting constants in the code into a custom segment? So how do you put functions in the code into a custom segment? Read on
// C文件中
/////////////////////////////////////////////////////
// 用#pragma location指定函数myFunction()放进自定义.myTask section
#pragma location = ".myTask"
void myFunction(void)
{
__NOP();
}
// Linker文件中
/////////////////////////////////////////////////////
// 把.myTask放到mySection_region
place in mySection_region { readonly section .myTask };
It seems that the work is done. There is one last thing to note. If myConfigBlock is not referenced in the code, the IDE may ignore this variable when linking (IDE thinks it is useless, so it optimizes it). So how can the IDE force link myConfigBlock? IAR has left a backdoor. In the Keep symbols input box in options->Linker->Input tab, fill in the object name you want to force link (note that it is the object name in the code, not the custom segment name in the linker file).
Note: For more details about the extra content, please refer to the Pragma directives section in the document \IAR Systems\Embedded Workbench xxx\arm\doc\EWARM_DevelopmentGuide.ENU.pdf in the IAR software installation directory.
At this point, the introduction of linker files in embedded development has been completed. Where is the applause~~~