$ sudo apt install gcc-arm-none-eabi
$ apt-get install qemu-system#Sources files #Toolchain #Object files
+-----------+ +------------------+ +-----------+
| startup.c | ----> | | ----> | startup.o |
+-----------+ | arm-none-eabi-gcc| +-----------+
| |
+-----------+ ----> | | ----> +-----------+
| main.c | +------------------+ | main.o |
+-----------+ +-----------+
|
#Linker script |
+------------------+ |
| linker_script.ld | ---------------------------------+
+------------------+ |
v
+----------------+
|arm-none-eabi-ld| #Linker
+----------------+
|
v
+----------------+
| blink.elf | #Executable
+----------------+A compiler is a software program that translates source code written in a high-level programming language into a lower-level form, such as assembly code, object code, or machine code, while preserving the program's functionality.
$ gcc # native
$ g++
$ clang
$ arm-none-eabi-gcc # cross
$ aarch64-none-elf-gccA cross compiler is a compiler that runs on one platform (host) but generates executable code for a different platform (target). An ARM cross compiler can run on a Linux PC and generate binaries that execute on an ARM-based embedded system.
$ arm-none-eabi-gcc
$ aarch64-none-elf-gccA toolchain is a collection of software development tools used to build, debug, and analyze software. A typical embedded ARM toolchain includes:
$ arm-none-eabi-gcc # Compiler
$ arm-none-eabi-as # Assembler
$ arm-none-eabi-ld # Linker
$ arm-none-eabi-objcopy # ELF to BIN converter
$ arm-none-eabi-gdb # DebuggerLinker Script is a configuration file that tells the linker how to place the program into the mcu memory.
- Define the memory layout: Flash, RAM, Stack, Heap
- Specify available memory regions
- Place the code/data in the correct memory location
- Set the program entry point
- Optimize memory usage
Reference: https://sourceware.org/binutils/docs/ld/Scripts.html
The linker combines multiple object files into a single executable file.
Object files are stored in a special format called an object file format (such as ELF). Each object file contains several sections, and each section has a name and a size (e.g., .text, .data, .bss).
For each loadable output section, the linker defines two addresses:
- VMA (Virtual Memory Address): The address where the section will reside when the program is running.
- LMA (Load Memory Address): The address from which the section is loaded at startup.
- Linker scripts are text files. e.g.
stm32_flash.ld - Whitespace is generally ignored.
- Include comments in linker scripts just as in C
The simplest possible linker script has just one command: SECTION to describe the memory layout of the output file.
/*stm32_flash.ld*/
SECTIONS
{
. = 0x10000; # the code should be loaded at address 0x10000
.text : { *(.text)}
. = 0x8000000; # that the data should start at address 0x8000000
.data : { *{.data}}
.bss : { *(.bss) }
}- Symbols:
.: location counter.text,.data, and.bssare the sections of the output file*(.text)means all.textinput sections in all input files.
A microcontroller does not start executing from main() after power-up or reset. Instead, it begins execution at the reset vector, which points to the startup code (typically Reset_Handler).
- Setting initial data values in SRAM, for global variables for example.
- Zero initialization of data memory for variables that are uninitialized at load time.
- Initializing the data variables controlling heap memory, for malloc() for example.
- Performing any required system initialization.
- Calling main().
+----------------+
| blink.elf | #Executable
+----------------+
|
v
+----------------+
| OpenOCD | #Tool to download/Debug
+----------------+
|
v
+----------------+
| ST-LINK | #Debug probe
+----------------+
|
v
+--------------------------------------------------+
| STM32 FLASH |
|--------------------------------------------------|
| .isr_vector |
| .text |
| .rodata |
| Initial .data image |
+--------------------------------------------------+
|
| RESET
v
+---------------------+
| Reset_Handler() |
+---------------------+
|
+----------------------+
| Copy .data |
| Flash --> SRAM |
+----------------------+
v
+--------------------------------------------------+
| STM32 SRAM |
|--------------------------------------------------|
| .data |
| .bss <-- zero-filled |
| |
| Heap (optional) |
| |
| Stack (grows downward) |
+--------------------------------------------------+
|
v
+---------+
| main() |
+---------+
|
v
#Application Runs#Compile the source files and link them into the firmware.elf
$ arm-none-eabi-gcc \
-mcpu=cortex-m3 \
-mthumb \
-nostdlib \
-ffreestanding \
startup.c main.c \
-T stm32_flash.ld \
-Wl,-Map=firmware.map \
-o build/firmware.elf- Options:
-mcpu=cortex-m3: Generate code for the ARM Cortex-M3 processor.-mthumb: Use the Thumb instruction set.-nostdlib: Do not link the standard C runtime libraries.-ffreestanding: Compile for a freestanding environment (bare-metal system).-T stm32_flash.ld: Use the specified linker script.-Wl,-Map=firmware.map: Generate a linker map file.-o build/firmware.elf: Output executable file.
- ELF file does include symbol tables, debug information, or other metadata.
Convert the ELF executable into a raw binary image that contains only the program data that will be stored in Flash memory.
$ arm-none-eabi-objcopy \
-O binary \
build/firmware.elf \
build/firmware.binQEMU can emulate the STM32VLDISCOVERY board and load firmware
$ qemu-system-arm \
-M stm32vldiscovery \
-kernel firmware.bin-
Launch QEMU and wait for a debugger connection:
$ qemu-system-arm \ -M stm32vldiscovery \ -kernel firmware.elf \ -nographic \ -S \ -gdb tcp::1234- Options:
-M stm32vldiscovery: Emulate the STM32VLDISCOVERY board.-kernel firmware.elf: Load the firmware image.-nographic: Disable graphical output and use the terminal.-S: Pause the CPU at reset.-gdb tcp::1234: Start a GDB server on port 1234.
- Options:
-
Start GDB using the ELF file so that symbol and debug information are available:
$ arm-none-eabi-gdb firmware.elf
-
Connect to the QEMU GDB server:
target remote :1234
-
Useful commands:
monitor reset # Reset the emulated target
load # Load firmware into target memory
continue # Start execution
p counter # Inspect variables and symbols
break main # set breakpoints
continueWhen running QEMU with -nographic, use:
Ctrl + A, then X
to exit the emulator.
Other useful shortcuts:
Ctrl + A, then C Open QEMU monitor
Ctrl + A, then H Show help
Ctrl + A, then X Exit QEMU