Bare-metal startup
SkillDev toolsUse when writing reset-to-main startup code, vector tables, VTOR, .data/.bss init, stack setup, startup.s, or crt0 for Cortex-M/RISC-V. Not for the bootloader jump: use bootloaders-embedded.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the Bare-metal startup skill
What this skill tells your AI
The instructions your AI receives, as published by outlinedriven/outline-driven-development in .devin/skills/baremetal-startup/SKILL.md and read by ahel’s review.
Contract
| Field | Bound contract |
|---|---|
| Trigger | Firmware never reaches main(), a new MCU is being brought up without a vendor HAL, or a custom startup.s, Reset_Handler, or crt0 is being written or debugged on Cortex-M or RISC-V. |
| Authority | Read-only: emits startup code, linker symbol requirements, and diagnostics to chat; the user places them in the project. Rollback is not needed because no file is written. No remote mutation. |
| Side effect | Chat output only. |
| Done | The startup path from the reset vector to main() is stated step by step, the vector table layout and the .data and .bss loops are given for the target core, and every linker symbol the code uses is listed with the linker script line that defines it. |
Inputs
- Target core (Cortex-M0+, M3, M4F, M7, or RISC-V RV32 or RV64) and the vendor part.
- The linker script, or at least its
MEMORYregions and the symbols it exports. - Whether the firmware boots directly from flash or is launched by a bootloader at an offset.
- Which C runtime is linked (newlib, newlib-nano, none) and whether C++ static constructors exist.
Procedure
-
State the boot sequence for the core before writing code. On Cortex-M the hardware loads
SPfrom vector 0 and branches to vector 1,Reset_Handler. On RISC-V the core starts at the reset address withspundefined, so the first instruction sets it. Done when: the sequence is written as an ordered list from power-on tomain().Cortex-M vector table head:
Index Entry 0 Initial stack pointer ( _estack)1 Reset_Handler2 NMI_Handler3 HardFault_Handler4 and up Fault handlers, then device IRQ handlers in NVIC order -
Write the vector table and
Reset_Handlerfor Cortex-M. Copy.datafrom its load address in flash to its run address in RAM, zero.bss, then callSystemInitandmain. Compare addresses with an unsigned branch (bcc), because addresses are unsigned. Done when: the assembly assembles for the target and the three symbol pairs come from the linker script..syntax unified .thumb .section .isr_vector,"a",%progbits .global g_pfnVectors g_pfnVectors: .word _estack .word Reset_Handler .word NMI_Handler .word HardFault_Handler /* remaining fault and device IRQ vectors from the vendor CMSIS header order */ .section .text.Reset_Handler .thumb_func .global Reset_Handler Reset_Handler: ldr r0, =_sidata /* .data load address in flash */ ldr r1, =_sdata /* .data run address in RAM */ ldr r2, =_edata b copy_check copy_data: ldr r3, [r0], #4 str r3, [r1], #4 copy_check: cmp r1, r2 bcc copy_data ldr r2, =_sbss ldr r4, =_ebss movs r3, #0 b zero_check zero_bss: str r3, [r2], #4 zero_check: cmp r2, r4 bcc zero_bss bl SystemInit bl main b . -
Define the linker symbols the startup code names.
_estackis the top of RAM,_sidatais the load address of.data, and_sdata,_edata,_sbss,_ebssbracket the sections. Done when: each symbol appears in the linker script and.datacarriesAT> FLASHso its load and run addresses differ._estack = ORIGIN(RAM) + LENGTH(RAM); _sidata = LOADADDR(.data); -
Relocate the vector table when the image does not sit at the default boot address. Write
SCB->VTOR, then__DSB()and__ISB(). The table base must be aligned to its own size rounded up to a power of two, with a minimum of 128 bytes on ARMv7-M; the mask below is the ARMv7-MTBLOFFfield and a table with many vectors needs coarser alignment. Done when:VTORholds the address of this image'sg_pfnVectorsand an interrupt reaches this image's handler.void relocate_vector_table(uint32_t base) { SCB->VTOR = base & 0xFFFFFF80U; /* ARMv7-M TBLOFF field; align to table size */ __DSB(); __ISB(); } -
Write the RISC-V entry when the target is RISC-V. Set
sp, zero.bsswithswon RV32 orsdon RV64, setmtvecto the trap handler, and callmain. Done when: the entry assembles for the target XLEN andmtvecpoints at a valid handler before interrupts are enabled..section .text.entry .global _start _start: la sp, _stack_top la t0, _bss_start la t1, _bss_end clear_bss: beq t0, t1, bss_done sw zero, 0(t0) /* sd on RV64 */ addi t0, t0, 4 /* 8 on RV64 */ j clear_bss bss_done: la t0, trap_handler csrw mtvec, t0 call main j . -
Assign the remaining crt0 duties and confirm who owns each. Done when: every row below has an owner in the project.
Task Owner Copy .datafrom flash to RAMReset_HandlerZero .bssReset_HandlerInitial stack Vector 0 on Cortex-M; explicit spload on RISC-VHeap ( _sbrk)Optional; newlib syscall or a custom allocator C++ static constructors __libc_init_array()from newlib, called beforemainFPU enable on Cortex-M4F and M7 SystemInit, by writingSCB->CPACRto grant CP10 and CP11 full access before any float instruction -
Keep
mainfrom returning. Theb .afterbl maincatches a return; the body ofmainnormally ends in a loop or__WFI(). Done when: a return frommainlands in a known loop and not in the next bytes of flash. -
Verify on the target: halt at
Reset_Handlerwith the debugger, step the copy and zero loops, and inspect a known initialized global and a known zero global at the first line ofmain. Done when: both globals hold their expected values.
Failure and recovery
| Symptom | Cause | Fix |
|---|---|---|
| HardFault on the first instruction | Vector 0 does not point into RAM | Set _estack to ORIGIN(RAM) + LENGTH(RAM). |
| Initialized globals read as garbage | .data never copied | Add the copy loop and define _sidata with LOADADDR(.data). |
| Zero-initialized globals are non-zero | .bss never zeroed | Add the zero loop over _sbss to _ebss. |
| Interrupts run the wrong handler or fault | VTOR points at another image's table | Write SCB->VTOR to this image's table and issue __DSB(); __ISB();. |
| Crash on the first float instruction | FPU not enabled on M4F or M7 | Set CP10 and CP11 in SCB->CPACR in SystemInit. |
C++ constructor crashes before main | Constructors ran before clocks or peripherals were ready | Call SystemInit before __libc_init_array. |
Execution falls off the end of main | No loop after main | Loop or __WFI() in main; keep b . after the call. |
Output
Startup code for the target core (vector table, Reset_Handler or _start, .data and .bss loops), the list of linker symbols it requires with their defining lines, the VTOR relocation routine when the image is offset, and a debugger checklist for confirming globals at the first line of main.
Signals
- GitHub stars
- 52
- Forks
- 9
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
baremetal-startup- Source
- github.com/outlinedriven/outline-driven-development