Skip to main content Why Reset Entry, Startup Code, and Exception Vectors Depend on Architecture | IoT Worker

Why Reset Entry, Startup Code, and Exception Vectors Depend on Architecture

When a device does not start, application logs are often the first place people look.

Some failures happen much earlier: there is no serial output, main() is never reached, a HardFault fires immediately after reset, a bootloader jumps to the kernel and nothing happens, or an exception handler is never reached at the expected address. These are usually not business-logic bugs. The CPU has not yet entered a stable software execution environment.

The safest first model for early boot is this: reset entry decides where the CPU gets its first instruction, startup code builds the minimal runtime environment, and exception vectors decide where the CPU jumps when faults or interrupts occur. The architecture defines entry and state; the chip and firmware define mapping, loading, and handoff details.

power-on / reset
-> reset entry or boot ROM
-> startup code
-> vector / exception table setup
-> C runtime or next-stage entry
-> main / RTOS / kernel / bootloader

So a startup failure is not simply “why did the program not run?” A better question is whether the CPU fetched from the right entry, whether startup code prepared the execution environment, and whether exception vectors point to valid handlers.

After Reset, the CPU Only Knows Its Entry Contract

After power-on or reset, a CPU does not search flash for a function called main(). It begins execution from an initial location defined by architecture and chip conventions.

That entry may be:

  • a reset vector at a fixed address
  • a reset handler address stored in a vector table
  • internal boot ROM
  • a boot medium selected by boot-mode pins, fuses, or security policy
  • a next-stage entry loaded by earlier firmware

On Cortex-M class MCUs, a common model is that reset reads the initial stack pointer and reset-handler address from a vector table. On more complex application processors or SoCs, the first code may be boot ROM, then SPL, bootloader, secure firmware, OpenSBI, a kernel, or an RTOS. RISC-V systems often begin in machine-mode firmware before handing control to supervisor mode or a kernel entry.

The details differ, but the engineering judgment is the same: if the entry is wrong, later code never gets a fair chance to run.

One of the most common early-boot errors is that the code was built as if it ran at one address, but was loaded at another.

Separate at least three addresses:

  • link address: the address used by the linker when generating symbols and branch targets
  • load address: where boot ROM, a debugger, or a bootloader places the image in memory
  • execution address: where the CPU actually fetches instructions

On simple MCUs, code often executes directly from a flash-mapped address. The linker script, vector-table location, and programming address must match. On complex systems, an image may live in storage first, then be loaded into SRAM or DRAM. Some code may start at one address and relocate itself to another.

When these addresses do not line up, symptoms include:

  • reset jumps to empty memory or invalid instructions
  • global variable addresses are wrong
  • function calls branch to the wrong location
  • exception vectors still point to an old address
  • the bootloader loads successfully but there is no output after the jump

This is not a C syntax issue. It is a mismatch among linker script, image format, loader behavior, and the CPU’s fetch address.

Startup Code Builds the Minimal Runtime Environment

main() is not the natural entry point after reset. C code needs several conditions before it can run normally.

Startup code commonly:

  • sets the stack pointer
  • sets the vector table or exception-entry base
  • initializes required clocks and memory
  • disables or configures the watchdog
  • copies .data into RAM
  • clears .bss
  • initializes minimal C/C++ runtime state
  • jumps to main(), an RTOS, a bootloader, or a kernel entry

The order matters. Without a stack, normal C function calls are unsafe. If DRAM is not initialized, the stack and global data cannot safely live there. If .bss is not cleared, global zero-initialized variables may contain garbage. If the vector table is wrong, an early exception may jump to the wrong address.

Startup code is often short, but its job is to create the conditions required by later software.

Exception Vectors Decide Where Faults Go

Exceptions and interrupts are not normal function calls. The CPU needs an entry table or entry rule to find a handler when a fault, interrupt, system call, or other exception occurs.

Different architectures and profiles organize this differently:

  • Cortex-M commonly uses a vector table with an entry address for each exception or interrupt
  • AArch64 uses an exception-vector base and entries split by exception level, source, and stack state
  • RISC-V uses trap-vector, cause, epc, and related registers to organize trap entry and cause reporting

The register names and layouts differ, but the engineering problems are similar: wrong vector location, invalid alignment, bad entry code, or corrupted exception-return state can make the system look as if it randomly jumped away.

Early boot is especially fragile because serial output, logs, debuggers, memory, and stack may not be fully usable. A very early fault with a wrong vector table may appear only as silence, reset loops, or a dead system.

Boot ROM and Bootloader Handoff the Entry

Complex devices usually do not begin directly from the final firmware or kernel. Boot ROM and bootloaders participate in entry selection and next-stage handoff.

Boot ROM may:

  • select SPI NOR, eMMC, SD, USB, or UART based on boot mode
  • load a small SPL or bootloader
  • perform early security checks
  • set minimal hardware state
  • jump to a contracted entry

A bootloader may then:

  • initialize DRAM
  • load kernel, Device Tree, initramfs, or RTOS image
  • prepare boot arguments
  • select partitions and rollback policy
  • enter the privilege level or CPU state expected by the next stage

So when a bootloader jumps but the kernel does not start, check the handoff contract: image format, load address, entry address, Device Tree or boot arguments, cache/MMU state, and current privilege level.

ARM and RISC-V Differences Show Up as Boot Contracts

Comparing ARM and RISC-V boot should not stop at one question such as “which address does it start from?”

The useful comparison is the boot contract:

  • what state is the CPU in after reset?
  • where does first-stage code live?
  • is the initial stack provided by hardware or set by software?
  • how is the exception vector or trap vector established?
  • what privilege level or mode is active?
  • what are the default MMU, cache, and interrupt states?
  • how are next-stage entry parameters passed?
  • who defines the firmware-to-kernel interface?

Cortex-M bare metal, Cortex-A Linux, AArch64 with TF-A, and RISC-V with OpenSBI and Linux all have different boot paths. Their common rule is that every stage must satisfy the entry conditions of the next stage.

Evidence for Early-Boot Debugging

Early-boot debugging should collect evidence about how far the CPU got.

Check in order:

  • power, reset, and clock meet chip requirements
  • boot-mode pins or fuses select the intended boot medium
  • the image is programmed or loaded at the intended address
  • link, load, and execution addresses agree
  • reset handler or entry symbol is correct
  • stack points to usable memory
  • .data and .bss are initialized correctly
  • vector table or exception entry is at the right location
  • early fault cause, PC, LR/return address, and accessed address are visible
  • bootloader handoff entry, parameters, and CPU state match the next-stage contract

If there is no serial output at all, use GPIO toggles, JTAG single-step, trace, early memory markers, or boot-ROM error codes to confirm whether the CPU reached a stage. Do not start by assuming the application logic is wrong.

What to Remember

Reset entry, startup code, and exception vectors are not template details in a startup file. They decide whether the CPU can move from hardware reset state into a usable software state.

Startup debugging begins with three questions:

  • where does the CPU fetch its first instruction?
  • did startup code establish stack, memory, runtime state, and next-stage entry conditions?
  • where will the CPU jump if an exception or interrupt occurs?

Only after those are stable do main(), an RTOS, a bootloader, a kernel, and application code become meaningful places to debug. Otherwise, many “the program did not run” failures have already happened before the program truly began.