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.
Flag Meaning -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.
Flag Meaning -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)
Flag Meaning -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
Flag Meaning -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
Flag Meaning -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
Command Shows 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
Task Command Disassemble everything arm-none-eabi-objdump -d build/firmware.elfDisassemble with interleaved source arm-none-eabi-objdump -d -S build/firmware.elfDisassemble one function arm-none-eabi-objdump -d --disassemble=main build/firmware.elfDisassemble without raw bytes arm-none-eabi-objdump -d --no-show-raw-insn build/firmware.elfSection headers (VMA, LMA, size) arm-none-eabi-objdump -h build/firmware.elfSymbol table arm-none-eabi-objdump -t build/firmware.elfDump a section’s raw bytes arm-none-eabi-objdump -s -j .isr_vector build/firmware.elfSymbols sorted by address arm-none-eabi-nm -n build/firmware.elfLargest symbols first arm-none-eabi-nm -S --size-sort -r build/firmware.elfOnly undefined symbols in an object arm-none-eabi-nm -u main.oSection sizes, Berkeley format arm-none-eabi-size build/firmware.elfSection sizes, per section, hex arm-none-eabi-size -A -x build/firmware.elfELF header arm-none-eabi-readelf -h build/firmware.elfSection table arm-none-eabi-readelf -S build/firmware.elfProgram headers (what gets loaded where) arm-none-eabi-readelf -l build/firmware.elfSymbols arm-none-eabi-readelf -s build/firmware.elfArm build attributes (float ABI, CPU) arm-none-eabi-readelf -A main.oELF to raw binary arm-none-eabi-objcopy -O binary build/firmware.elf build/firmware.binELF to Intel HEX arm-none-eabi-objcopy -O ihex build/firmware.elf build/firmware.hexExtract one section arm-none-eabi-objcopy -O binary -j .isr_vector build/firmware.elf vec.binStrip debug info arm-none-eabi-objcopy --strip-debug build/firmware.elf stripped.elfEmbed a binary file as an object arm-none-eabi-objcopy -I binary -O elf32-littlearm -B arm logo.raw logo.oAddress to function and line arm-none-eabi-addr2line -e build/firmware.elf -f -i 0x080002A4Printable strings arm-none-eabi-strings build/firmware.elfArchive members arm-none-eabi-ar t libdrivers.aCreate a static library arm-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.
FLASH (rx) : ORIGIN = 0x08000000 , LENGTH = 1024K
RAM (rwx) : ORIGIN = 0x20000000 , LENGTH = 128K
CCMRAM (rw) : ORIGIN = 0x10000000 , LENGTH = 64K
_estack = ORIGIN (RAM) + LENGTH (RAM);
*(.ARM.exidx* .gnu.linkonce.armexidx.*)
_sidata = LOADADDR ( .data );
> 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.
._stack_check ( NOLOAD ) : {
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.
__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 */
Command Short Effect 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
Command Effect 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 faultmonitor 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
Task Command Start the gdb server for the Discovery openocd -f board/stm32f4discovery.cfgEquivalent explicit config openocd -f interface/stlink.cfg -f target/stm32f4x.cfgFlash an ELF, verify, reset, exit openocd -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 erase openocd -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 clock add -c "adapter speed 1000" before other commands
Ports: 3333 gdb, 4444 telnet, 6666 Tcl.
Command Effect 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).
Command Effect 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:
machine LoadPlatformDescription @platforms/boards/stm32f4_discovery-kit.repl
sysbus LoadELF @build/firmware.elf
showAnalyzer sysbus.usart2
machine StartGdbServer 3333
Peripheral names (sysbus.usart2, sysbus.gpioPortD) come from the platform file; list them with peripherals if a name does not resolve.
Task Command List machines qemu-system-arm -M helpRun an ELF on the STM32F405 model, UART on the terminal qemu-system-arm -M netduinoplus2 -nographic -kernel build/firmware.elfWait for gdb on port 1234 add -s -S, then target remote :1234 in gdb Exit -nographic Ctrl+A then X
SRCS := src/main.c src/startup.c
OBJCOPY := $( CROSS ) objcopy
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 \
OBJS := $( SRCS:%.c= $( BUILD ) /%.o )
.PHONY : all clean flash debug size
all : $( BUILD ) / $( TARGET ) .elf $( BUILD ) / $( TARGET ) .bin $( BUILD ) / $( TARGET ) .hex
$( CC ) $( CFLAGS ) -c $< -o $@
$( BUILD ) / $( TARGET ) .elf: $( OBJS ) $( LDSCRIPT )
$( CC ) $( LDFLAGS ) $( OBJS ) -o $@
$( BUILD ) / $( TARGET ) .bin: $( BUILD ) / $( TARGET ) .elf
$( OBJCOPY ) -O binary $< $@
$( BUILD ) / $( TARGET ) .hex: $( BUILD ) / $( TARGET ) .elf
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
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.
Variable Meaning $@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
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)
cmake_minimum_required ( VERSION 3.20)
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
-ffunction-sections -fdata-sections
target_link_options(firmware PRIVATE
--specs=nano.specs --specs=nosys.specs
-Wl,-Map=$<TARGET_FILE_DIR:firmware>/firmware.map
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>
Task Command Configure (Debug, Ninja) cmake -B build -G Ninja -DCMAKE_TOOLCHAIN_FILE=cmake/arm-none-eabi.cmake -DCMAKE_BUILD_TYPE=DebugConfigure (Release) same with -DCMAKE_BUILD_TYPE=Release (adds -O3 -DNDEBUG; use MinSizeRel for -Os) Build cmake --build buildVerbose build (see every command) cmake --build build --verboseExport compile database for editors add -DCMAKE_EXPORT_COMPILE_COMMANDS=ON Clean cmake --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.