Building Bare-Metal ARM Systems with GNU: Part 3
In this part I move on to describe the GNU linker script for a
bare-metal ARM project. The code accompanying this article is available
online at the Embedded.com's Downloadable Code page
The linker script must match the startup code described in Part 2 of this article for all the section names and other linker symbols. The linker script cannot be generic, because it must define the specific memory map of the target device, as well as other application-specific information.
The linker script is therefore named here blinky.ld, which corresponds to the Blinky example application that blinks the 4 user LEDs of the AT91SAM7S-EK board. The C version of the example for this article is located in the c_blinky directory, while the C++ version in the cpp_blinky directory.
|Listing 1 Linker script for the Blinky example application (AT91SAM7S64 MCU).|
Listing 1 above shows the linker script for the Blinky example application. The script is almost identical for C and C++ versions, with the minor differences discussed later in this section. The highlights of the linker script are as follows:
(1) The OUTPUT_FORMAT directive specifies the format of the output image (elf32, little-endian, ARM)
(2) OUTPUT_ARCH specifies the target machine architecture.
(3) ENTRY explicitly specifies the first instruction to execute in a program
(4) The MEMORY command describes the location and size of blocks of memory in the target.
(5) The region ROM corresponds to the on-chip flash of the AT91SAM7S64 device. It can contain read-only and executable sections (rx), it starts at 0x00100000 and is 64KB in size.
(6) The region RAM corresponds to the on-chip SRAM of the AT91SAM7S64 device. It can contain read-only, read-write and executable sections (rwx), it starts at 0x00200000 and is 16KB in size.
(7) The following symbols denote the sizes of the ARM stacks. You need to adjust the sizes for your particular application. The C-stack cannot be zero.
(8) The SECTIONS command opens the definition of all the sections for the linker.
(9) The .reset section contains the startup code (including the ARM vectors) and must be located as the first section in ROM.
(10) This line locates all .text sections from the startup.o object module.
(11) The section size is aligned to the 4-byte boundary
(12) This section is loaded directly to the ROM region defined in the MEMORY command.
(13) The .ramvect section contains the RAM-based ARM vector table and the secondary jump table and must be loaded as the first section in RAM
(14) The ARM vector table and the secondary jump table have known size of 0x40 bytes. The current location counter is simply incremented to reserve 0x40 bytes for the section.
(15) The .ramvect section goes into the RAM region.
(16) The .fastcode section is used for RAM-based code, which needs to be loaded to ROM, but copied and executed from RAM.
(17) The .fastcode section has different load memory address (LMA) than the virtual memory address (VMA). The symbol __fastcode_load corresponds to the LMA in ROM and is needed by the startup code to copy the section from ROM to RAM.
(18) The __fastcode_start symbol corresponds to the VMA of the .fastcode section and is needed by the startup code to copy the section from ROM to RAM.
(19) The .glue_7t and .glue_7 sections are synthesized by the compiler when you specify the ARM-THUMB interworking option. The sections contain the "call veneers" between THUMB and ARM code and are accessed frequently by every call between ARM and THUMB. It's typically advantageous to place this small amount of hot-spot code in RAM.
(20) The .text.fastcode section is assigned explicitly to individual functions in the C/C++ code by means of the __attribute__ ((section (".text.fastcode"))) command.
(21) The GNU compiler is also capable of placing each function in the separate section named after the function (requires specifying the option -ffunction-sections). This allows you to be very selective and to place individual functions (e.g. the function Blinky_shift()) in RAM.
NOTE: The C++ compiler performs function name-mangling and you need to consult the map file to figure out the section name assigned to a given function. For example, the class method Blinky::shift() is placed in the section .text._ZN6Blinky5shiftEv)
(22) You can place more hot-spot functions in RAM during the fine-tuning stage of the project.
(23) The .fastcode section is located in RAM, but is loaded at the ROM address.
(24) The .text section is for code and read-only data accessed in place.
(25) If you repeat sections already located in the .fastcode section, the earlier location will take precedence. However, if you decide to remove these sections from .fastcode, they will be located per the second specification.
(26) The following sections are synthesized by the GNU C++ compiler and are used for static constructors and destructors.
(27) The section .rodata is used for read-only (constant) data, such as look-up tables. Just as code, you might choose to place some frequently accessed constants in RAM by locating these sections in the .fastcode section.
(28) The .text section is located and loaded to ROM.
(29) The .ARM.exidx section is used for C++ exception handling. It is located here for completeness. Bare-metal ARM projects typically cannot afford the overhead associated with C++ exceptions handling.
(30) The .data section contains initialized data.
(31) The .data section is located in RAM, but is loaded to ROM and copied to RAM during startup.
(32) The .bss section contains uninitialized data. The C/C++ standard requires that this section must be cleared at startup.
(33) The .bss section is located in RAM only.
(34) The .stack section contains all the stacks. The section is initialized with a given bit-pattern at startup.
(35) The ARM GNU toolset uses full descending stack. Therefore the linker script provides only the top of stack symbols to initialize the various ARM stack pointers. In particular the C stack (SYS stack) is allocated at the end of the .stack section.
(36) The .stack section is located in RAM.
(37) The symbols _end, __end, and end are used to set up the beginning of the heap, if the heap is used.
(38) The following sections are for the debugger only and are never loaded to the target.
Coming Up Next
Next in Part 4, I'll describe the C and C++ compiler options as well as how to minimize the overhead of C++ using the GNU toolchain. Stay tuned.
Miro Samek, Ph.D., is president of Quantum Leaps, LLC. He can be contacted at email@example.com.