Skip to content

Reference18 min read

Toolchain Cheat Sheet

Everything here assumes the GNU Arm toolchain (arm-none-eabi-*), OpenOCD 0.12 or later, and the STM32F4 Discovery. Replace build/firmware.elf with your own output path.

FlagMeaning
-mcpu=cortex-m4Generate and schedule code for the Cortex-M4
-mthumbThumb instruction set (the only one Cortex-M runs)
-mfloat-abi=hardUse FPU instructions and pass floats in FPU registers
-mfloat-abi=softfpUse FPU instructions but pass floats in core registers (link-compatible with soft)
-mfloat-abi=softNo FPU instructions at all (default if unspecified)
-mfpu=fpv4-sp-d16The Cortex-M4F FPU: single precision, 16 double registers
-mcpu=cortex-m0plus, -mcpu=cortex-m7, -mcpu=cortex-m33Other cores (M7 FPU: -mfpu=fpv5-d16 or fpv5-sp-d16)

All objects and libraries in one link must use the same float ABI.

FlagMeaning
-std=c11 / -std=gnu11ISO C11 / C11 with GNU extensions
-Wall -Wextra -WpedanticBaseline warnings
-WerrorWarnings become errors
-Wshadow -Wconversion -Wdouble-promotion -Wformat=2 -WundefStrongly recommended extras (see C Quick Reference)
-ffreestandingDo not assume a hosted environment; main is not special, builtins not assumed
-fno-builtinDo not replace calls like memcpy with inline builtins (it does not stop GCC from turning loops into memcpy/memset calls)
-fno-commonTentative definitions in multiple files become link errors (default since GCC 10)
FlagMeaning
-O0No optimisation; debuggable, large, slow
-OgOptimise without hurting debugging; good debug-build default
-O1, -O2, -O3Increasing speed optimisation; -O2 is the usual release choice
-OsOptimise for size
-OzOptimise harder for size (GCC 12+, Clang)
-fltoLink-time optimisation across files (pass at compile and link)
-g / -g3Debug info / debug info including macro definitions
-ffunction-sections -fdata-sectionsEach function and object in its own section, so --gc-sections can drop unused ones
-fstack-usageWrite a .su file with each function’s stack frame size
-fanalyzerGCC static analyser (slow, finds leaks, null derefs, double frees)
-fstack-protector-strongStack canaries (needs a __stack_chk_fail and guard)
-fno-strict-aliasingDisable type-based alias optimisation
-fwrapvDefine signed overflow as wrapping
-save-tempsKeep .i and .s intermediate files
FlagMeaning
-EStop after preprocessing
-SStop after compiling, write assembly .s
-cStop after assembling, write object .o
-o fileOutput file name
-IdirAdd include search directory
-DNAME=value / -UNAMEDefine / undefine a macro
-MMD -MPWrite .d dependency files for make, with phony targets for headers
-dM -EDump all predefined macros
FlagMeaning
-T link.ldUse this linker script
--specs=nano.specsLink newlib-nano (smaller printf, malloc)
--specs=nosys.specsLink stub system calls that return errors
-nostartfilesDo not link the toolchain’s crt0 startup files
-nostdlibLink no standard libraries or startup files (add -lgcc back if needed)
-u _printf_floatPull in float support for newlib-nano printf
-Wl,--gc-sectionsDiscard unreferenced sections
-Wl,-Map=build/firmware.mapWrite a map file
-Wl,--print-memory-usagePrint region usage after linking
-Wl,--crefAdd a cross-reference table to the map file
-Wl,--wrap=mallocRedirect malloc to __wrap_malloc; the original is __real_malloc
-Wl,--undefined=symForce sym to be linked
-Wl,--no-warn-rwx-segmentsSilence the binutils 2.39+ RWX segment warning
-lm, -lc, -lgccMath library, C library, compiler support library
CommandShows
arm-none-eabi-gcc -dM -E - < /dev/nullAll predefined macros (add -mcpu flags to see target ones)
arm-none-eabi-gcc -print-multi-libAvailable library variants for different flags
arm-none-eabi-gcc -print-file-name=libc.aWhich libc.a will be linked for the given flags
arm-none-eabi-gcc -Q --help=optimizers -O2Optimisations enabled at -O2
arm-none-eabi-gcc -vConfiguration, version, search paths
TaskCommand
Disassemble everythingarm-none-eabi-objdump -d build/firmware.elf
Disassemble with interleaved sourcearm-none-eabi-objdump -d -S build/firmware.elf
Disassemble one functionarm-none-eabi-objdump -d --disassemble=main build/firmware.elf
Disassemble without raw bytesarm-none-eabi-objdump -d --no-show-raw-insn build/firmware.elf
Section headers (VMA, LMA, size)arm-none-eabi-objdump -h build/firmware.elf
Symbol tablearm-none-eabi-objdump -t build/firmware.elf
Dump a section’s raw bytesarm-none-eabi-objdump -s -j .isr_vector build/firmware.elf
Symbols sorted by addressarm-none-eabi-nm -n build/firmware.elf
Largest symbols firstarm-none-eabi-nm -S --size-sort -r build/firmware.elf
Only undefined symbols in an objectarm-none-eabi-nm -u main.o
Section sizes, Berkeley formatarm-none-eabi-size build/firmware.elf
Section sizes, per section, hexarm-none-eabi-size -A -x build/firmware.elf
ELF headerarm-none-eabi-readelf -h build/firmware.elf
Section tablearm-none-eabi-readelf -S build/firmware.elf
Program headers (what gets loaded where)arm-none-eabi-readelf -l build/firmware.elf
Symbolsarm-none-eabi-readelf -s build/firmware.elf
Arm build attributes (float ABI, CPU)arm-none-eabi-readelf -A main.o
ELF to raw binaryarm-none-eabi-objcopy -O binary build/firmware.elf build/firmware.bin
ELF to Intel HEXarm-none-eabi-objcopy -O ihex build/firmware.elf build/firmware.hex
Extract one sectionarm-none-eabi-objcopy -O binary -j .isr_vector build/firmware.elf vec.bin
Strip debug infoarm-none-eabi-objcopy --strip-debug build/firmware.elf stripped.elf
Embed a binary file as an objectarm-none-eabi-objcopy -I binary -O elf32-littlearm -B arm logo.raw logo.o
Address to function and linearm-none-eabi-addr2line -e build/firmware.elf -f -i 0x080002A4
Printable stringsarm-none-eabi-strings build/firmware.elf
Archive membersarm-none-eabi-ar t libdrivers.a
Create a static libraryarm-none-eabi-ar rcs libdrivers.a uart.o gpio.o

size output columns: text is code plus read-only data (all in flash); data is initialised RAM data, whose initial values are also stored in flash; bss is zeroed RAM. Flash used = text + data. RAM used (static) = data + bss.

The .bin file from objcopy -O binary spans from the lowest to the highest loadable address. A section placed in RAM with a flash load address is handled correctly; a section accidentally given a RAM load address produces a file hundreds of megabytes long.

ENTRY(Reset_Handler)
MEMORY
{
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 1024K
RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 128K
CCMRAM (rw) : ORIGIN = 0x10000000, LENGTH = 64K
}
_estack = ORIGIN(RAM) + LENGTH(RAM);
_stack_size = 4K;
_heap_size = 0;
SECTIONS
{
.isr_vector : {
. = ALIGN(4);
KEEP(*(.isr_vector))
. = ALIGN(4);
} > FLASH
.text : {
*(.text*)
*(.rodata*)
KEEP(*(.init))
KEEP(*(.fini))
. = ALIGN(4);
_etext = .;
} > FLASH
.ARM.exidx : {
*(.ARM.exidx* .gnu.linkonce.armexidx.*)
} > FLASH
_sidata = LOADADDR(.data);
.data : {
. = ALIGN(4);
_sdata = .;
*(.data*)
*(.ramfunc*)
. = ALIGN(4);
_edata = .;
} > RAM AT> FLASH
.bss (NOLOAD) : {
. = ALIGN(4);
_sbss = .;
*(.bss*)
*(COMMON)
. = ALIGN(4);
_ebss = .;
} > RAM

> RAM AT> FLASH gives .data a run address (VMA) in RAM and a load address (LMA) in flash. Startup code copies _edata - _sdata bytes from _sidata to _sdata and zeroes _sbss to _ebss.

.noinit (NOLOAD) : {
. = ALIGN(4);
*(.noinit*)
. = ALIGN(4);
} > RAM
.ccmram (NOLOAD) : {
. = ALIGN(4);
*(.ccmram*)
. = ALIGN(4);
} > CCMRAM
._stack_check (NOLOAD) : {
. = ALIGN(8);
. = . + _heap_size;
. = . + _stack_size;
. = ALIGN(8);
} > RAM
/DISCARD/ : {
*(.ARM.attributes)
}
}
ASSERT(_ebss + _heap_size + _stack_size <= _estack, "RAM overflow: stack and heap do not fit")

Discarding .ARM.attributes is optional and only removes metadata; omit that block if a tool complains.

#include <stdint.h>
__attribute__((section(".noinit"))) uint32_t crash_count; /* survives reset */
__attribute__((section(".ccmram"))) static uint8_t work[4096]; /* fast, no DMA */
__attribute__((section(".ramfunc"), noinline))
void flash_program_word(uint32_t addr, uint32_t value); /* runs from RAM */
CommandShortEffect
break main / break file.c:42bBreakpoint at function or line
tbreak mainOne-shot breakpoint
break f if x == 3Conditional breakpoint
info breakpointsi bList breakpoints and watchpoints
delete 2 / disable 2dRemove or disable breakpoint 2
runrStart the program (host only)
continuecResume
next / stepn / sNext line, stepping over / into calls
nexti / stepini / siNext machine instruction
finishRun until the current function returns, print return value
until 50 / advance 50Run to line 50
backtracebtCall stack
frame 2 / up / downfSelect stack frame
info locals / info argsLocal variables, arguments
print x / print/x xpPrint a value, in hex
print arr / print *p@8Whole array / 8 elements from pointer
print sizeof(struct s)Evaluate any C expression
ptype var / whatis varShow a type
display/x varPrint var after every step
display/i $pcShow the next instruction after every step
set var x = 5Modify a variable
x/16xb addrExamine 16 bytes in hex
x/8xw addrExamine 8 words in hex
x/4i $pcDisassemble 4 instructions at pc
x/s strPrint a C string at an address
watch varStop when var is written
rwatch var / awatch varStop on read / on any access
info registersi rCore registers
info line *0x080002A4Source line for an address
list *0x080002A4Source around an address
disassemble /s mainDisassembly with source
tui enable / layout asm / layout splitText UI with source and assembly panes
info frameDetailed frame info, saved registers
quitqExit
CommandEffect
arm-none-eabi-gdb build/firmware.elfStart gdb with symbols (gdb-multiarch on Debian/Ubuntu)
target extended-remote :3333Connect to OpenOCD’s gdb server
monitor reset haltReset the chip and halt at the reset vector
monitor reset initReset, halt, and run the target’s init script
loadFlash the ELF’s loadable sections
compare-sectionsVerify flash contents match the ELF
hbreak funcForce a hardware breakpoint (6 on Cortex-M4)
p/x $pc, p/x $sp, p/x $lr, p/x $xpsrCore registers
p/x $msp, p/x $psp, p/x $primask, p/x $basepri, p/x $controlSpecial registers (names depend on the target description)
p/x *(uint32_t *)0x40020C14Read a peripheral register (GPIOD_ODR)
set *(uint32_t *)0x40020C18 = 0x1000Write a peripheral register (GPIOD_BSRR: PD12 high)
x/8xw 0x08000000Vector table: initial SP, Reset, NMI, HardFault …
p/x *(uint32_t *)0xE000ED28CFSR after a fault
monitor mdw 0x40023830Read a word through OpenOCD, bypassing gdb’s cache
monitor mww 0x40023830 0x8Write a word through OpenOCD
monitor halt / monitor resumeHalt or resume via OpenOCD
set mem inaccessible-by-default offAllow reads of addresses not described by the memory map
detachLeave the target running and disconnect

A .gdbinit in the project directory (enable it with set auto-load safe-path / in ~/.gdbinit, or narrower):

target extended-remote :3333
monitor reset halt
load
break main
continue
TaskCommand
Start the gdb server for the Discoveryopenocd -f board/stm32f4discovery.cfg
Equivalent explicit configopenocd -f interface/stlink.cfg -f target/stm32f4x.cfg
Flash an ELF, verify, reset, exitopenocd -f board/stm32f4discovery.cfg -c "program build/firmware.elf verify reset exit"
Flash a raw binary (needs an address)openocd -f board/stm32f4discovery.cfg -c "program build/firmware.bin 0x08000000 verify reset exit"
Mass eraseopenocd -f board/stm32f4discovery.cfg -c "init; reset halt; stm32f4x mass_erase 0; exit"
Connect under reset (firmware disabled SWD or sleeps)openocd -f board/stm32f4discovery.cfg -c "reset_config srst_only srst_nogate connect_assert_srst"
Lower the SWD clockadd -c "adapter speed 1000" before other commands

Ports: 3333 gdb, 4444 telnet, 6666 Tcl.

Telnet console (telnet localhost 4444) or monitor in gdb

Section titled “Telnet console (telnet localhost 4444) or monitor in gdb”
CommandEffect
reset halt / reset run / reset initReset and halt / run / run init script
halt / resume / stepCore control
reg / reg pcShow all registers / one register
mdw 0x20000000 8Display 8 words
mdb addr 16 / mdh addr 4Display bytes / halfwords
mww addr valueWrite a word
bp 0x08000200 2 hw / rbp 0x08000200Set / remove hardware breakpoint
wp 0x20000010 4 w / rwp 0x20000010Set / remove write watchpoint
flash banks / flash info 0Flash bank list, sector layout
flash erase_sector 0 5 7Erase sectors 5 to 7 of bank 0
flash write_image erase build/firmware.elfErase as needed and write
verify_image build/firmware.elfCompare flash with the file
dump_image ram.bin 0x20000000 0x20000Save 128 KB of SRAM to a file
stm32f4x unlock 0Remove read protection (triggers a mass erase)
stm32f4x lock 0Set read protection level 1
targetsList targets and their state
shutdownExit OpenOCD

Run renode, then type at the monitor prompt (or put the lines in a .resc script and run renode script.resc).

CommandEffect
mach create "disco"Create a machine
machine LoadPlatformDescription @platforms/boards/stm32f4_discovery-kit.replLoad the STM32F4 Discovery model
sysbus LoadELF @build/firmware.elfLoad firmware (path relative to where Renode started, or absolute)
showAnalyzer sysbus.usart2Open a terminal window on USART2
start / pauseRun / pause emulation
emulation RunFor "00:00:02"Run for 2 seconds of virtual time
machine ResetReset the machine
machine StartGdbServer 3333Accept gdb on port 3333 (target remote :3333)
sysbus.cpu PCShow the program counter
sysbus.cpu StepExecute one instruction
sysbus ReadDoubleWord 0x40020C14Read a 32-bit word from the bus
sysbus WriteDoubleWord 0x40020C18 0x1000Write a 32-bit word
sysbus LogPeripheralAccess sysbus.gpioPortD trueLog every access to a peripheral
logLevel 0Most verbose logging (3 = errors only)
peripheralsList peripherals in the loaded platform
include @other.rescRun another script
quitExit

A minimal .resc script:

mach create "disco"
machine LoadPlatformDescription @platforms/boards/stm32f4_discovery-kit.repl
sysbus LoadELF @build/firmware.elf
showAnalyzer sysbus.usart2
machine StartGdbServer 3333
start

Peripheral names (sysbus.usart2, sysbus.gpioPortD) come from the platform file; list them with peripherals if a name does not resolve.

TaskCommand
List machinesqemu-system-arm -M help
Run an ELF on the STM32F405 model, UART on the terminalqemu-system-arm -M netduinoplus2 -nographic -kernel build/firmware.elf
Wait for gdb on port 1234add -s -S, then target remote :1234 in gdb
Exit -nographicCtrl+A then X
Makefile
TARGET := firmware
BUILD := build
SRCS := src/main.c src/startup.c
LDSCRIPT := link.ld
CROSS := arm-none-eabi-
CC := $(CROSS)gcc
OBJCOPY := $(CROSS)objcopy
SIZE := $(CROSS)size
CPU := -mcpu=cortex-m4 -mthumb -mfloat-abi=hard -mfpu=fpv4-sp-d16
CFLAGS := $(CPU) -std=c11 -Og -g3 -ffunction-sections -fdata-sections \
-Wall -Wextra -Wpedantic -Iinclude -MMD -MP
LDFLAGS := $(CPU) -T $(LDSCRIPT) --specs=nano.specs --specs=nosys.specs \
-Wl,--gc-sections -Wl,-Map=$(BUILD)/$(TARGET).map \
-Wl,--print-memory-usage
OBJS := $(SRCS:%.c=$(BUILD)/%.o)
DEPS := $(OBJS:.o=.d)
.PHONY: all clean flash debug size
all: $(BUILD)/$(TARGET).elf $(BUILD)/$(TARGET).bin $(BUILD)/$(TARGET).hex
$(BUILD)/%.o: %.c
@mkdir -p $(dir $@)
$(CC) $(CFLAGS) -c $< -o $@
$(BUILD)/$(TARGET).elf: $(OBJS) $(LDSCRIPT)
$(CC) $(LDFLAGS) $(OBJS) -o $@
$(SIZE) $@
$(BUILD)/$(TARGET).bin: $(BUILD)/$(TARGET).elf
$(OBJCOPY) -O binary $< $@
$(BUILD)/$(TARGET).hex: $(BUILD)/$(TARGET).elf
$(OBJCOPY) -O ihex $< $@
flash: $(BUILD)/$(TARGET).elf
openocd -f board/stm32f4discovery.cfg -c "program $< verify reset exit"
debug: $(BUILD)/$(TARGET).elf
arm-none-eabi-gdb -ex "target extended-remote :3333" \
-ex "monitor reset halt" -ex "load" $<
size: $(BUILD)/$(TARGET).elf
$(SIZE) -A -x $<
clean:
rm -rf $(BUILD)
-include $(DEPS)

Recipe lines must start with a tab character. -MMD -MP makes the compiler write .d files so editing a header rebuilds the files that include it; -include $(DEPS) reads them and ignores missing ones on the first build. For make debug, start OpenOCD in another terminal first.

VariableMeaning
$@The target being built
$<The first prerequisite
$^All prerequisites, deduplicated
$(dir $@)Directory part of the target
$*The stem matched by % in a pattern rule
:= / = / ?= / +=Immediate / deferred / set if unset / append
cmake/arm-none-eabi.cmake
set(CMAKE_SYSTEM_NAME Generic)
set(CMAKE_SYSTEM_PROCESSOR arm)
set(CMAKE_C_COMPILER arm-none-eabi-gcc)
set(CMAKE_ASM_COMPILER arm-none-eabi-gcc)
set(CMAKE_OBJCOPY arm-none-eabi-objcopy)
set(CMAKE_SIZE arm-none-eabi-size)
# A bare-metal test executable cannot link without a linker script
set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY)
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
CMakeLists.txt
cmake_minimum_required(VERSION 3.20)
project(firmware C ASM)
set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED ON)
set(CMAKE_C_EXTENSIONS OFF)
set(CPU_FLAGS -mcpu=cortex-m4 -mthumb -mfloat-abi=hard -mfpu=fpv4-sp-d16)
set(LINKER_SCRIPT ${CMAKE_SOURCE_DIR}/link.ld)
add_executable(firmware src/main.c src/startup.c)
set_target_properties(firmware PROPERTIES SUFFIX ".elf")
target_include_directories(firmware PRIVATE include)
target_compile_options(firmware PRIVATE
${CPU_FLAGS}
-ffunction-sections -fdata-sections
-Wall -Wextra -Wpedantic
)
target_link_options(firmware PRIVATE
${CPU_FLAGS}
-T ${LINKER_SCRIPT}
--specs=nano.specs --specs=nosys.specs
-Wl,--gc-sections
-Wl,-Map=$<TARGET_FILE_DIR:firmware>/firmware.map
-Wl,--print-memory-usage
)
set_property(TARGET firmware APPEND PROPERTY LINK_DEPENDS ${LINKER_SCRIPT})
add_custom_command(TARGET firmware POST_BUILD
COMMAND ${CMAKE_OBJCOPY} -O binary $<TARGET_FILE:firmware>
$<TARGET_FILE_DIR:firmware>/firmware.bin
COMMAND ${CMAKE_SIZE} $<TARGET_FILE:firmware>
)
TaskCommand
Configure (Debug, Ninja)cmake -B build -G Ninja -DCMAKE_TOOLCHAIN_FILE=cmake/arm-none-eabi.cmake -DCMAKE_BUILD_TYPE=Debug
Configure (Release)same with -DCMAKE_BUILD_TYPE=Release (adds -O3 -DNDEBUG; use MinSizeRel for -Os)
Buildcmake --build build
Verbose build (see every command)cmake --build build --verbose
Export compile database for editorsadd -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
Cleancmake --build build --target clean

LINK_DEPENDS makes CMake relink when the linker script changes; without it, edits to link.ld are silently ignored until something else changes.