An arm usage fault is an exception that occurs when the ARM processor attempts to execute an instruction in a way that is not permitted by the architecture. This usually indicates a bug or error in the software code running on the processor.
Some common causes of arm usage faults include:
- Attempting to execute an undefined instruction – The instruction code is invalid and not recognized by the ARM architecture.
- Accessing a coprocessor register without enabling the coprocessor – Trying to use a coprocessor like the FPU without first enabling it.
- Attempting to switch to an invalid processor mode – Trying to change to a processor mode that is not supported on the given ARM core.
- Accessing an unaligned memory address – ARM generally requires aligned memory accesses for performance.
- Violating memory access permissions – Trying to execute code in a read-only memory region for example.
- Using an invalid memory address – Dereferencing a NULL or uninitialized pointer.
- Encountering a stack overflow – Pushing more data on the stack than available memory.
The ARM architecture provides extensive support for catching these types of errors. When an arm usage fault occurs, the processor immediately stops normal execution and jumps to a pre-configured exception handler routine. This allows the system to gracefully recover, terminate the faulty application, or take other actions as needed.
Types of ARM Usage Faults
The ARM architecture defines several different types of usage faults that can occur during instruction execution:
- Undefined instruction – An attempt was made to execute an instruction that is not defined in the ARM instruction set. This usually indicates a bug in the program code.
- Invalid state – The processor attempted to execute an instruction that is not valid for the current processor state or mode. For example, trying to access the PC (program counter) directly in User mode.
- Invalid PC load – An invalid value was loaded into the PC, causing it to point to an invalid memory address.
- coprocessor – An attempt was made to access a disabled or non-existent coprocessor.
- Unaligned access – The processor tried to perform a memory access that was not aligned to the required boundary.
- Divide by zero – Executing a divide instruction with a denominator of zero.
The ARM architecture provides fault status registers that can be checked after a usage fault to determine exactly which type occurred. This information is useful for debugging the root cause of the error.
Usage Fault Exception Handling
When an ARM usage fault occurs, the processor stops normal instruction execution and jumps to a usage fault exception handler. This handler is typically part of the system firmware or OS kernel.
The usage fault handler needs to perform several tasks:
- Save processor context – The fault handler first saves important processor registers like R0-R12 and the LR (link register) on the stack. These need to be preserved for debuggability or possible recovery.
- Log fault information – Details about the fault are logged to help with debugging. This includes the fault status registers, program counter, and stack backtrace.
- Take recovery action – Based on the system design, the handler may terminate the faulty application, reboot the system, or attempt to recover and continue executing. Simple embedded systems often just get stuck in the handler.
- Return to thread – If recovery is successful, the handler can restore the saved registers and resume execution. If not, it indicates an unrecoverable error.
The ARM Cortex-M series of processors used in microcontrollers have dedicated stack pointers and link registers for each exception handler. This facilitates saving context and returning after handling the usage fault.
Debugging ARM Usage Faults
Debugging ARM usage faults requires understanding the exception handling mechanism and making use of the information provided in the fault status registers. Some techniques for debugging include:
- Examine the fault status registers – These indicate the specific type of usage fault, and may include an instruction fault address.
- Check the exception stack frame – This provides the processor context at the time of the fault.
- Scan the exception handler logs – May provide additional clues like the program counter.
- Set breakpoints at exception handlers – Halt execution when a usage fault occurs.
- Single step through handler – Step through instruction by instruction to watch register changes.
- Inspect stack backtrace – Trace call stack to identify the calling code that caused the issue.
- Reproduce on emulator – Emulate the code on QEMU to easily test and debug.
For software issues, the key is tracing backwards from the point of the usage fault to try and determine the root cause in the application code. Hardware issues like memory failures can also result in usage faults.
Usage Faults on ARM Cortex-M
The Cortex-M series of ARM processors used in most microcontrollers have dedicated usage fault exception support. Some key aspects include:
- Configurable priority – Usage faults can be set to different priorities or preemption levels.
- Dedicated stack pointers – Each exception handler gets its own stack for context saving.
- Automated context saving – Hardware automatically saves registers on the stack.
- Return address registers – Allow easy return to the main program after handling the fault.
- Debug support – Cortex debug unit provides debug breakpoints on faults.
- HardFault handler – Catches escalated faults when configured handlers are missing.
These mechanisms simplify usage fault handling on Cortex-M. The microcontroller firmware defines the actual HardFault and MemManage handlers tailored for that specific system.
Preventing ARM Usage Faults
There are a variety of software coding techniques that can help prevent usage faults from occurring in ARM applications:
- Carefully validate input values – Check parameters passed to functions for validity.
- Use bounds checking – Verify array indexes and pointer accesses are within allowed ranges.
- Initialize variables – Uninitialized locals/pointers are a common source of faults.
- Align variables – Use directives to align globals and stack variables to their access requirements.
- Avoid undefined behavior – Strictly adhere to language specifications.
- Use stack protection – Guard against stack overflows that corrupt return addresses.
- Enable compiler warnings – Turn on additional warning messages about questionable code.
- Use assertions – Check assumptions and constraints during runtime.
Performing security hardening of code also applies here, like avoiding buffer overflows. Rigorously testing ARM applications using fuzzing and static analysis tools can also help catch issues before deployment. Of course, not all usage faults are preventable – hardware issues and unexpected corner cases can still arise in the field.
Usage Faults vs Bus Faults
ARM processors can generate both usage faults and bus faults during instruction execution. The key differences are:
- Usage Fault – An instruction execution issue, caused by software.
- Bus Fault – A memory access issue, often caused by hardware.
Some examples that illustrate the distinction:
- Executing an undefined instruction causes a usage fault – software issue.
- Loading an unaligned address causes a bus fault – hardware issue.
- Accessing invalid memory address causes a bus fault – could be hardware or software.
- Calling a coprocessor incorrectly causes a usage fault – software issue.
- Stack overflow causes a usage fault – software issue.
So in summary, usage faults are caused by the instruction execution, while bus faults are caused by the associated memory accesses. The fault status registers indicate the specific source in each case.
Summary
ARM usage faults provide an effective way to catch invalid instruction execution errors during runtime. Configurable exception handlers allow these issues to be logged, recovered from, or identified during debugging. Usage faults are distinct from bus faults in that they are caused by the software itself, rather than invalid memory accesses. Proper coding techniques, testing, and debugging strategies can help identify and eliminate common ARM usage fault sources to improve system stability and security.