In my day job and free time I frequently find myself debugging Arm Cortex-M microcontrollers (MCUs). In recent years, it has become more and more common for the cores in these MCUs to implement Armv8-M, with the Arm Cortex-M33 being a very popular variant. Armv8-M includes an optional security extension (Cortex-M Security Extension or “CMSE”), which is more commonly known by its marketing name, TrustZone.
The security extension allows for a core, or a Processing Element (PE) if using the official terminology in Arm reference manuals, to divide memory into Secure and Non-Secure regions. When executing instructions in a Secure memory region, the PE is said to be in the Secure state, and when executing in a Non-Secure region, it is said to be in the Non-Secure state.
The current stack pointer (SP / R13) register in an Armv8-M PE either
matches the Main Stack Pointer (MSP) or Process Stack Pointer (PSP),
depending on the operating mode (Handler or Thread) and the SPSEL field in the
CONTROL special-purpose register. If in Handler mode, the SP register will
always match MSP. When in Thread mode, the SP will match MSP if SPSEL is
0 or PSP if SPSEL is 1.
When the security extension is implemented, there are both *_S and *_NS
variants of some registers, inluding MSP (MSP_S and MSP_NS) and PSP
(PSP_S and PSP_NS). This expands the possible values of SP, adding an
extra dimension of the current security state. This can be useful to quickly
determine the security state when debugging a core. With the following GDB
command, the current stack pointer, as well as the Secure and Non-Secure
variants of the Main and Process Stack Pointers will be printed.
i r sp psp_ns msp_ns psp_s msp_s
If the SP matches either PSP_NS or MSP_NS, then the PE is currently in the
Non-Secure state. If it matches PSP_S or MSP_S, then the PR is currently in
the Secure state. For example, on reset in a Cortex-M33 core with the security
extension implemented, the processor will be in Thread mode and Secure state, so
SP will match MSP_S.
sp 0x20004000 0x20004000
psp_ns 0x2001ad40 0x2001ad40 <z_main_stack+4048>
msp_ns 0x0 0x0 <_vector_table>
psp_s 0x0 0x0 <_vector_table>
msp_s 0x20004000 0x20004000
As evidenced by the z_main_stack symbol, this is a Zephyr
RTOS application. Breaking at main, then
checking the the same registers expectedly results in SP matching PSP_NS
because the processor is in Thread mode and Non-Secure state and CONTROL.SPSEL
is set to 1.
sp 0x2001ad40 0x2001ad40 <z_main_stack+4048>
psp_ns 0x2001ad40 0x2001ad40 <z_main_stack+4048>
msp_ns 0x20019c30 0x20019c30 <z_idle_stacks>
psp_s 0x20001eb8 0x20001eb8 <z_main_stack-97976>
msp_s 0x20000bf8 0x20000bf8 <sector_buffers+1688>
The CONTROL.SPSEL field can be checked by printing the special-purpose
CONTROL register with a mask on the second bit, with a non-zero value
indicating that SPSEL is 1.
(gdb) print $control & 0x2
$0 = 2
While this is certainly not the only way to determine the current security state in Armv8-M cores, it can be a helpful way to do so quickly.