Summary
A hard fault occurs when the compiler‑generated call to the helper routine __udivsi3 jumps into an undefined memory region on the MSPM0C1104. The fault is triggered not by an illegal division (e.g., divide‑by‑zero) but by the linker placing the division helper in a memory area that is not marked as executable, causing the CPU to fetch an invalid instruction word (.word 0xfxxxxxxx) and raise a hard fault.
Root Cause
- Missing or misplaced compiler runtime library: The GNU ARM compiler relies on
libgccfor__udivsi3; if the library is not linked or is placed in a non‑executable section, the resolver points to random flash/ram data. - Incorrect linker script memory attributes: The
FLASHregion is defined asRXbut theDEVICE_CONFIGor other custom regions may overlap the address where__udivsi3ends up, stripping the execute permission. - Optimization‑level dependent code generation: Even with
-O0the compiler emits a call to__udivsi3; with-O2it may inline or reuse the same helper, but if the helper is missing the fault appears identically. - Section placement due to
-ffunction-sections/--gc-sections: The linker may discard the__udivsi3object file if it believes the symbol is unused, leaving a dangling reference that resolves to zero‑filled or undefined memory.
Why This Happens in Real Systems
- Real‑world firmware often mixes vendor SDK libraries, custom linker scripts, and aggressive garbage collection of sections.
- The division helper is not referenced directly in source code, so static analysis tools may not flag it as a required symbol.
- Cortex‑M0+ (
armv6-m) lacks hardware division; the software routine must reside in executable memory, a requirement easily overlooked when defining custom memory regions for configuration or calibration data. - Toolchain upgrades or changes in spec files (e.g., switching from
nosys.specstonano.specs) can alter which libc/libgcc symbols are pulled in.
Real-World Impact
- Intermittent hard faults that appear only after certain compiler/linker flag changes, making debugging difficult.
- System watchdog resets or fault handlers mask the root cause, leading to prolonged field failures.
- Wasted development time chasing “divide‑by‑zero” bugs when the actual problem is a missing executable symbol.
- Potential safety‑critical consequences if the fault occurs in control loops or fault‑tolerant sections of the application.
Example or Code (if necessary and relevant)
volatile uint32_t test;
test = 24000000;
test /= 24000000;
How Senior Engineers Fix It
- Verify that
-lgcc(or the appropriate compiler runtime) appears on the linker command line; add it explicitly if using-nostdlib/-nostartfiles. - Inspect the linker script: ensure any custom RAM/ROM regions do not overlap the address range where
__udivsi3is placed, and that all code sections have theX(execute) attribute. - Use
-Wl,--print-mapor read the.mapfile to confirm__udivsi3resides inside theFLASH(RX) region and not in aRW!xor data area. - Disable garbage collection for the runtime library (
-Wl,--undefined=__udivsi3) or wrap the helper in a forced‑include object file. - Rebuild with
-specs=nosys.specs(or ensurenano.specsstill pulls inlibgcc) and confirm the division helper is present in the final binary. - Add a defensive check in code (e.g.,
if (denom) …) only after confirming the helper is correctly linked, to avoid masking the real issue.
Why Juniors Miss It
- They focus on the division operation itself and assume a hardware divide unit exists, overlooking the software helper requirement for Cortex‑M0+.
- They trust that the default toolchain flags will always pull in needed runtime symbols, missing the effect of
-nostdlib/-nostartfilesor custom spec files. - They overlook the linker map file, assuming the build succeeded if no compile‑time errors appear.
- They attribute the hard fault to stack corruption or interrupt misconfiguration without verifying the program counter at fault time.