When working with a Cortex-M3 microcontroller, it is crucial to understand how to differentiate between valid and invalid addresses. This will allow you to properly utilize the address space and avoid accessing restricted or non-existent memory locations which could lead to crashes or undefined behavior. Generally, valid addresses fall within mapped regions of memory while invalid ones do not.
Understanding the Cortex-M3 Memory Map
The Cortex-M3 has a complex memory map with different regions mapped for different purposes. At a high level, the main memory areas are:
- Code region for program code (flash memory)
- SRAM region for data
- Peripheral region for registers of on-chip peripherals
- System region for registers like the SysTick timer
Each region has specific base and size values that determine the valid address range. Trying to access an address outside these ranges results in a fault. It’s important to check your microcontroller’s reference manual for the exact memory map.
For example, on a sample Cortex-M3 the code region may be at 0x00000000-0x0007FFFF (512KB), SRAM at 0x20000000-0x2000FFFF (64KB), peripherals 0x40000000-0x5FFFFFFF (1GB) and system region 0xE0000000-0xE00FFFFF (1MB).
Using Section Attributes
One way the compiler helps differentiate valid/invalid addresses is by using section attributes. Sections attributes are used to annotate code and data so the linker can place them appropriately.
Some common section attributes on Cortex-M3 are:
- @”.text” – for code memory region
- @”.data” – for initialized SRAM variables
- @”.bss” – uninitialized SRAM variables
- @”.rodata” – const qualified read only data
By using the correct section attributes, the linker will only allocate those variables/code in valid memory regions for those types. Trying to place data in code memory or code in SRAM will raise errors during link time if section attributes are used properly.
Watching Out For Stack and Heap
The stack and heap are two memory constructs that dynamically grow and shrink during runtime. The stack holds local variables, function parameters, return addresses while the heap is used for dynamic memory allocation.
Both the stack and heap can encounter invalid memory accesses if they grow too large. For stack, local arrays or deep recursion can cause it to exceed its allocated memory and corrupt other data. For heap, allocating more memory than available space will lead to overflow.
So for both stack and heap, it’s important to:
- Properly size their memory during initialization
- Use stack/heap overflow protection if available
- Carefully check for large stack variables or memory allocations
This will help prevent accidental corruption from exceeding valid memory boundaries.
Utilizing MPU and MMU for Memory Protection
The Memory Protection Unit (MPU) and Memory Management Unit (MMU) are hardware features on Cortex-M3 that can be used to enforce valid memory access. The MPU sets configurable rules for which memory regions can be accessed and which mode (privilege level) is needed. This prevents accidental writes or corruption.
The MMU maps virtual addresses to physical and provides an additional layer of access control. Properly configuring MPU/MMU regions can detect invalid accesses which generate faults. Handling these faults allows recovery versus crashes from invalid access. Some ways to leverage MPU/MMU protections:
- Make code and read-only data unwriteable
- Make peripheral memory privileged execution only
- Use multiple MPU regions to protect each memory area appropriately
- Map SRAM into virtual address space with MMU
Using Assertion Library to Check Addresses
Incorporating assertion checks for addresses being within expected ranges can help detect invalid accesses during debugging and testing. An assertion library like CMSIS-CORE’s arm_assert.h provides useful macros for address range checks:
- ARM_ASSERT_WITHIN_RANGE – Checks single address against min/max bounds
- ARM_ASSERT_ALIGNED – Checks for aligned address
- ARM_ASSERT_FITS_WITHIN – Checks full address range fits within min/max
Adding assertions to validate passed pointers, array accesses, and other address computations provides runtime checking to complement static analysis. Failed assertions immediately highlight issues vs subtle memory corruption.
Performing Manual Address Checking
There are times when manually checking for valid addresses is needed, especially when interacting with memory-mapped peripherals. Some common approaches are:
- Compare address against base/bounds of peripheral
- Mask address then compare expected bits
- Check aligned address for 32-bit or 16-bit accesses
- Sanitize user input addresses before usage
Manual validation requires knowing the memory map details but gives the most flexibility. Often addresses are stored in variables so combining manual checks with assertions can provide robust address validation.
Sticking to Aligned Access
Unaligned memory access to peripherals or data arrays can result in faults or incorrect behavior on Cortex-M3. Aligning accesses to match data size is safer:
- 32-bit variables on 4-byte boundaries
- 16-bit variables on 2-byte boundaries
- 8-bit variables on any byte
The compiler aligns global/static data properly but stack variables may need explicit alignment. Unaligned access is usually not an issue for SRAM but is vital when accessing registers and hardware.
Avoiding Common Addressing Pitfalls
Some other ways invalid addresses can sneak into Cortex-M3 code include:
- Pointer arithmetic going past arrays
- Mismatched pointer types
- Off-by-one errors with for loop boundaries
- Uninitialized pointers
- Race conditions from tasks/interrupts
Carefully reviewing pointer arithmetic and loop iterators can reveal these issues. Static analysis tools like Polyspace or Astrée can also detect many invalid memory access patterns.
Configuring Debugger Memory Access Warnings
Development tools like debuggers can be configured to halt or warn on invalid memory accesses:
- Halt on unaligned access options
- Halt on access to reserved addresses
- Flash error on write to flash memory
- Break on memory access errors
Slowing execution and diagnosing invalid accesses during debug sessions complements static checking and hardware protections to reinforce address validation.
Summary
Validating addresses is a crucial aspect of Cortex-M3 development. Leveraging the compiler, hardware protections like MPU/MMU, debuggers, assertions, and manual checks provides layers of defense against invalid accesses. Rules for aligned access, careful use of stack/heap, and avoiding common pitfalls also helps eliminate many address-related bugs.
With robust address validation, Cortex-M3 software can confidently access memory without crashes, corruption, or undefined behavior. This builds more stable and secure embedded systems.
Understanding the valid memory map, using appropriate attributes, checking alignments, assertions for runtime debugging, and proper configuration of hardware protections/debug tools provides a comprehensive approach. As Cortex-M3 applications become more complex, these techniques enable proper utilization of the address space during development and in deployed systems.
Additional Questions
Here are some additional questions readers may have about finding valid vs invalid addresses on a Cortex-M3:
How do I know where to locate my Cortex-M3 code and data?
Check the microcontroller reference manual for the memory map, which shows valid regions for code and data. Usually code goes in flash memory while initialized and zero init data go in SRAM. Also utilize linker section attributes like .text, .data, and .bss to allocate properly.
What causes stack overflow errors on Cortex-M3?
Deep function call recursion, large stack arrays, and interrupt usage can grow the stack beyond its allocated memory range. Set a stack overflow threshold via the Stack Overflow Checking register if available. Also be wary of large stack variables.
How do I determine valid peripheral addresses?
The peripheral memory map defines the base address and address block size for each peripheral. Make sure to access registers only within those ranges. The SVD files describe valid registers for each peripheral.
What issues can unaligned memory access cause?
Unaligned access to variables and peripherals may be unsupported and cause faults or read incorrect data. Align variables and accesses to match the size, i.e. 32-bit variables on 4 byte boundaries. Use compiler directives if needed.
How can I best leverage the MPU on my Cortex-M3?
Configure MPU regions to separate memory areas for code, SRAM, peripherals, etc. Make flash read-only and SRAM non-executable. Use privileged mode for peripherals. Violations generate faults to detect invalid access.
What compiler options help detect invalid addresses?
Compile with pointer safety (-fno-strict-aliasing), bounds checking (-fbounds-check), and stack protection (-fstack-protector) enabled. Make sure to validate compiler placed checks don’t impact performance.
Conclusion
This covers the key techniques for differentiating between valid and invalid addresses on a Cortex-M3 microcontroller. Proper memory maps, alignment, hardware protections, assertions, and access patterns all contribute to robust address validation. Always refer to the reference manual and utilize available tools/libraries to reinforce addressing within your software.