This post is older than 2 years and might not be relevant anymore
More Info: Consider searching for newer posts

Stack Guard and MPU

Hi, I'm trying to get the nrf_stack_guard and nrf_mpu libraries to catch writes past the end of the stack. My stack is 8kB in size (0x2000E000-0x20010000). This is the log output after initializing the Stack Guard module:

<debug> nrf_mpu: MPU region creating (location: 0x2000E000-0x2000E07F)
<debug> nrf_mpu: MPU region 0 created (location: 0x2000E000-0x2000E07F, access: RO/RO, type: Normal, flags: XN).
<info> stack_guard: Stack Guard: 0x2000E000-0x2000E07F (usable stack area: 8064 bytes)

I'm having a little trouble understanding how the MPU works. I'd expect a write to 0x2000E000 to trigger the HardFault_Handler, but in reality nothing is triggered. The write just happens and silentlly corrupts RAM below the stack.

I'm using the nrf gcc hardfault library implementation and am able to catch NULL dereferences and other faults, so that should be correctly set up.

What am I missing?

Parents
  • For information on the MPU, you should consult the ARM Cortex-M4 user's guide:

    http://infocenter.arm.com/help/topic/com.arm.doc.dui0553b/DUI0553.pdf

    Cortex-M4 core itself (and its optional components) is designed by ARM Ltd. not Nordic, so the in-depth documentation for it won't be in any of the Nordic manuals.

    The MPU is basically a cut-down substitute for an MMU, though without the ability to do any virtual to physical mapping. The MPU provides a fixed number of region registers which can be used to define the following things:

    - Base address of the region

    - Size of the region

    - Region attributes

    The attributes and size are defined using a single register, called MPU_RASR (Region Attribute and Size Register, see section 4.4.5 in the manual).

    The attributes include things like access rights and cache behavior.

    The size part is what's important here: it's defined using 5 bits of the MPU_RASR register, and specifies a power of 2. Note that the actual power of 2 is the value of the size field plus 1.

    The minimum allowed value is 5, which represents 32 bytes (2^(4+1)), and the maximum value is 31, which represents 4GB (2^(31+1)).

    The debug output you included shows exactly what region is being guarded: 0x2000E000 to 0x2000E07F. This means you the stack guard has been set up with a base address of 0x2000E000 and a size of 128 bytes.

    What you need to do is make sure your that:

    a) Your test case is actually causing a load or store instruction to be executed (i.e. it's not being optimized out by the compiler -- the volatile keyword can help prevent this)

    b) The load or store is actually targeting an address _within_ that specific region.

    So for example, if your code is triggering a write to address 0x2000E100, then you're writing to memory on the other side of the stack guard instead of inside it, and you won't see any trap. If you have a function that uses a large amount of local variables, the compiler might emit code that adjusts the stack pointer so far beyond the end of the stack that you skip right over the guard.

    Note that it's possible to trigger a trap due to something other than an explicit load or store instruction. For example, if an exception occurs, like an interrupt, the CPU will push an exception frame onto the stack. If the stack pointer is at or near the stack guard when this happens, then this will also trigger a hard fault, because the CPU won't have enough room to save everything. This may happen more easily if you're using floating point math since in that case the CPU may also need to save the FPU registers as well.

    -Bill

Reply
  • For information on the MPU, you should consult the ARM Cortex-M4 user's guide:

    http://infocenter.arm.com/help/topic/com.arm.doc.dui0553b/DUI0553.pdf

    Cortex-M4 core itself (and its optional components) is designed by ARM Ltd. not Nordic, so the in-depth documentation for it won't be in any of the Nordic manuals.

    The MPU is basically a cut-down substitute for an MMU, though without the ability to do any virtual to physical mapping. The MPU provides a fixed number of region registers which can be used to define the following things:

    - Base address of the region

    - Size of the region

    - Region attributes

    The attributes and size are defined using a single register, called MPU_RASR (Region Attribute and Size Register, see section 4.4.5 in the manual).

    The attributes include things like access rights and cache behavior.

    The size part is what's important here: it's defined using 5 bits of the MPU_RASR register, and specifies a power of 2. Note that the actual power of 2 is the value of the size field plus 1.

    The minimum allowed value is 5, which represents 32 bytes (2^(4+1)), and the maximum value is 31, which represents 4GB (2^(31+1)).

    The debug output you included shows exactly what region is being guarded: 0x2000E000 to 0x2000E07F. This means you the stack guard has been set up with a base address of 0x2000E000 and a size of 128 bytes.

    What you need to do is make sure your that:

    a) Your test case is actually causing a load or store instruction to be executed (i.e. it's not being optimized out by the compiler -- the volatile keyword can help prevent this)

    b) The load or store is actually targeting an address _within_ that specific region.

    So for example, if your code is triggering a write to address 0x2000E100, then you're writing to memory on the other side of the stack guard instead of inside it, and you won't see any trap. If you have a function that uses a large amount of local variables, the compiler might emit code that adjusts the stack pointer so far beyond the end of the stack that you skip right over the guard.

    Note that it's possible to trigger a trap due to something other than an explicit load or store instruction. For example, if an exception occurs, like an interrupt, the CPU will push an exception frame onto the stack. If the stack pointer is at or near the stack guard when this happens, then this will also trigger a hard fault, because the CPU won't have enough room to save everything. This may happen more easily if you're using floating point math since in that case the CPU may also need to save the FPU registers as well.

    -Bill

Children
No Data
Related