前言
1 W' Y* b# v9 b! C$ @STM32 系列支持 MPU 内存保护单元,可用来设定内存的属性和访问权限。MPU 的应用笔记提到,将属性寄存器(MPU_RASR)配置成某一个值,在特权(Privileged permissions)和用户模式(Unprivileged permissions)的访问许可是不同的,甚至可将用户模式的权限设置成不可访问。那么,什么是 MPU 的特权模式和用户模式呢? 接下来我们在这篇文章来理解这些名词,并讨论在 STM32 MCU 代码中如何使用内存保护单元 MPU 的特权与用户模式。
( ~9 V% j/ A( q8 {3 ~
5 k) B7 i: B# D2 x$ T. zMPU(Memory Protection Unit)" M* f" |1 r5 d
MPU 内存保护单元可以用来使嵌入式系统更加健壮与安全。它可以阻止用户应用程序破坏系统关键数据。它可以通过将内存SRAM 区域定义成不可执行,来阻止代码注入型攻击。也可以用来改变内存的性质,例如是否允许缓存(Cache)。用来设置内存属性的 MPU_RASR 寄存器字段描述如下:3 R1 g% z& S5 M6 k5 \; @( T
* {; F' t, [7 y- G
2 A t- F2 r, _0 W$ ~2 X7 q
, k' b8 f* A2 `% `1 [9 M2 ~: W在 MPU_RASR 中用来设置数据访问许可的 AP 字段详细设置选项如下:! {; ], M$ C2 N. q# w9 f
2 D+ T8 s; R8 T n) N
5 V( V, o, r9 u/ S1 e8 P
$ O7 F9 ^1 R, b `+ j5 b$ s特权模式(Privileged mode)与用户模式(UnPrevileged mode)
% N) a5 ^0 {3 A; x) w7 v特权模式与用户模式是指 Cortex 内核的执行模式。它不是 MPU 的一部分,但与 MPU 单元有关联。当代码运行在特权模式下,代码拥有所有的访问许可;而代码运行在用户模式,则访问权限受限制。限制包括在系统设计阶段就定义的可运行指令限制,可访问内存以及外设限制。也包括由 MPU 单元动态所定义的内存访问规则。. B5 F- i3 X) ~# D
从特权模式进入用户模式,只需使用 MSR 指令操作 CONTROL 寄存器。但是,不可以直接从用户模式转入特权模式,必须经过一个异常处理操作模式,比如 SVC( Supervisor Calls)。在异常处理通过操作 CONTROL 寄存器,可从用户模式回到特权模式。请注意,特权线程与异常处理线程,都是在特权模式下运行。7 Q1 ]5 ~! Y3 i+ G, M! m1 Z! M
如下图所示:
$ w8 u7 q, ]3 A/ k7 Y2 y3 `8 T( t7 \8 B ?/ T/ y: L
7 H5 n9 t& z1 H
" |: d; C7 N: p4 \在代码中结合特权与用户模式使用 MPU: u5 \9 d6 c" }; S( R2 N/ [! |
1. 开发环境5 \% D( \- p G) e: C5 h/ ^
开发板: STM32 L476RG NUCLEO
, {7 }" R' [% i& ^1 C4 M& ` 开发工具:STM32Cube_FW_L4_V1.7.0
8 c% v* q. N5 T& f IAR/Keil
; f! D9 j4 f& C6 | N6 \4 m9 V 注:也可以选择其他 STM32 系列并选择相应的开发板与固件库。3 d7 u! D) @8 D1 N. p& H
2. 开发目标定义6 q' ?' O( A4 |+ U
在软件中定义一块数组,使用 MPU 将该区域配成仅可从特权模式下进行访问,并验证用户模式下访问会导致内存管理异常或者硬错误。
' K' M, j3 \8 q7 |7 p" a4 b; s1 \% O S: p+ z2 l. l9 F0 J
% W0 ~2 `7 c' W- \& ~0 J, j( n
& `# k y5 j/ c9 V( U _4 b' I3. 代码资源与整合4 x8 i: z- K, {* S! w9 V+ N
STM32 Cube 固件库提供了大量的例程供学习和重用。查找 STM32Cube_FW_L4_V1.6.0 可以发现,固件库已分别实现了特权模式与用户模式的切换(CORTEXM_ModePrivilege),和 MPU 的配置(CORTEXM_MPU)。+ S0 L2 L/ d8 v) X4 d
% t1 D7 n0 G9 f! k- D
% o4 f& R" f4 c
1 b1 l o: X" F1 s; J0 \8 T: L
我们需要将两个例程整合在一起来实现我们的开发目标。注意你选择其他 STM32 系列也是没有问题的,也支持 MPU 功能。复制 CORTEXM_MPU\Src\stm32_mpu.c 到 CORTEXM_ModePrivilege\Src 目录下
. { j6 @% I) O e4 ~: L, I: x! D- A0 L
; W" p; A- e7 d z8 K" j) ^' [
9 k1 c' J3 U; {$ ?
复制 CORTEXM_MPU\Inc\stm32_mpu.h 到 CORTEXM_ModePrivilege\Inc 目录下( N; O# G0 b H$ y/ |7 l" J& F# M
2 w' K) A' g" C) j6 ?- `: _) H; Q2 M* k% D
, P6 ?0 t& t' h" m' p
# }( [1 j8 a1 k 打开 CORTEXM_ModePrivilege 工程文件,将 stm32_mpu.c 和 stm32_mpu.h 添加到工程文件中。IAR 与 Keil 略有不同,在 IAR 下只需添加 C 文件。, ?6 y% `4 G3 B7 {6 p" g# y& C
4. 修改与增加代码
2 \# c. W% R6 G2 z" f 新增加的代码以红色表示
4 t) H- @0 k6 x E9 g3 [$ n 在主程序中包含 stm32_mpu.h% t# T4 E F2 u$ J9 s7 q% F" w. K
- /* Includes ------------------------------------------------------------------*/# H1 {# \' [4 }
- #include "main.h"
. M4 z" k9 U! Y1 q7 h( T - #include "stm32_mpu.h" /*added*/0 ^4 A/ _) F, h
- /** @addtogroup STM32L4xx_HAL_Examples
& o/ Y: M% ]( H3 S$ f3 ~ - ; W/ W; H: L2 {# ]. W
- * @{
7 q) g' B* b3 R& F - */, w" q S7 e5 G _
- /** @addtogroup CORTEXM_ModePrivilege0 j" a$ H5 Y& \) @4 o
- * @{4 v" p* \. M; I( }
- */
复制代码 " L9 d3 l4 [- y) [ R' N
在主程序里调用 MPU 配置函数' V6 V" \( C! A, W, k
要对使用的内存进行 MPU 配置,未被 MPU 配置的资源将视之为不可访问。这是 MPU_Config 必须被使用的原因。7 U1 e5 K* `/ V9 q5 O
- /* Get the Thread mode stack used */
" [7 A% X' K& e0 ]6 K# J" r - if((__get_CONTROL() & 0x02) == SP_MAIN)
/ _2 x( q+ X y! k# l a3 g - {
) }: {4 O/ C/ G; T+ J, i& m3 e - /* Main stack is used as the current stack */
" {* q: I3 j) k6 S x - CurrentStack = SP_MAIN;" k7 a; @& k9 u) D( i% U
- }
1 \8 n4 ]" |: L W5 K/ n$ Y$ b - else
* V( m$ m7 U1 L4 l, Y& p - {9 \7 C9 |2 t) ?" X' [
- /* Process stack is used as the current stack */3 W; s {0 a: k- k1 T
- CurrentStack = SP_PROCESS;5 I: g6 W, B5 p' o; ?
- /* Get process stack pointer value */
1 K6 J; A& X2 F+ L0 h( ~ - PSPValue = __get_PSP();( F5 u6 o6 D- f* b8 _
- }6 i6 ]6 [3 ^- \! p# W% Q# x
- MPU_Config(); /*added*/! @+ i8 E% c6 b, J* P
- MPU_AccessPermConfig(); /*added*/
复制代码
3 H4 [; s0 f# d y0 }! R 修改 MPU_AccessPermConfig 将数组区域配置成我们设计的权限并增加测试代码。- y U/ D' I0 O) U
MPU_AccessPermConfig 在特权模式下运行,故测试代码不会触发异常。
1 e5 v( P: h) V+ w P7 Z- void MPU_AccessPermConfig(void)
1 \6 b t- X0 p. }* X6 \ - {9 x2 `: [. ^3 G, _% ?& E
- MPU_Region_InitTypeDef MPU_InitStruct;6 y$ W6 f* ?$ f4 [6 ]' t
- /* Configure region for PrivilegedReadOnlyArray as REGION N?, 32byte and R) ]5 |, q2 S0 {, a3 x* q+ O
- only in privileged mode */$ u- {* [. | c0 z, x" y* n [
- /* Disable MPU */
+ t5 Y+ A! B9 N o, H* F$ e( ^ - HAL_MPU_Disable();2 G& [/ e F) R, L! R
- MPU_InitStruct.Enable = MPU_REGION_ENABLE;
* b& u7 ]+ N! i' | - MPU_InitStruct.BaseAddress = ARRAY_ADDRESS_START;
7 V* S( S3 }4 w" X& e - MPU_InitStruct.Size = ARRAY_SIZE;
4 O+ K; C0 m. S- \0 v" { - MPU_InitStruct.AccessPermission = portMPU_REGION_PRIVILEGED_READ_WRITE;( z' I6 w. Z" F D1 J" R/ d
- MPU_InitStruct.IsBufferable = MPU_ACCESS_NOT_BUFFERABLE;! R) V4 Y& t( e
- MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE;
0 `5 A- O- {4 Q' c4 d - MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;
+ [4 ?- u+ N& {8 @. P - MPU_InitStruct.Number = ARRAY_REGION_NUMBER;
9 N9 ^/ G C/ I4 e" r# t3 P - MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0;$ t# D' M4 m; A7 d8 C# Q" e
- MPU_InitStruct.SubRegionDisable = 0x00;/ ~8 T& r1 i6 p
- MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;% \1 m0 i' ~6 w) K& g
7 t6 |8 j9 M0 |# D0 S9 |+ W- HAL_MPU_ConfigRegion(&MPU_InitStruct);+ m; f; C& W$ M- Y9 z2 Z/ a4 K
+ ]4 \ r! t$ o* w% ^/ ?$ K- /* Enable MPU (any access not covered by any enabled region will cause a fault) */ I( c3 ~9 h( @4 w I c/ Q
- HAL_MPU_Enable(MPU_HFNMI_PRIVDEF_NONE);
\; z. Q1 j" |, Y; z - /* Read from PrivilegedReadOnlyArray. This will not generate error */0 W+ a& I7 M3 S5 V- E# W
- if(PrivilegedReadOnlyArray[0])
9 | ^8 n3 u2 p - {
. [ [7 M2 r8 k: d3 j9 h* j. Z# k - PrivilegedReadOnlyArray[0] = 'e';
7 W9 ~' B* ?+ c/ O( @3 ] - }$ W! i; u: @. _8 D
- /* Uncomment the following line to write to PrivilegedReadOnlyArray. This will
% p& f3 m2 ?4 j# m$ R, h - generate error *// w6 v7 C! H+ `- l H3 B
- PrivilegedReadOnlyArray[0] = 'e';) p/ U: n5 @: E- l( ]4 R
- }
复制代码
/ [3 P) \3 d& ]8 z& {. e' M 在用户模式下尝试读写数组
% V) \- K1 {( C6 [+ @# U参考代码中直接访问数组,很容易被编译器优化掉而不能发挥测试作用。这里加了个判断后写的操作,使编译器不去优化它。
8 c; l9 p* M4 i. `3 K7 \- /* Switch Thread mode from privileged to unprivileged ------------------------*/
- _/ c' f" K7 j2 N: i - /* Thread mode has unprivileged access */
( U- v ?7 w' U- ]; C3 ~ - __set_CONTROL(THREAD_MODE_UNPRIVILEGED | SP_PROCESS);( K3 t% L- ]+ ^* F9 ^
- / Y% k; o0 I+ A9 c# u
- /* Execute ISB instruction to flush pipeline as recommended by Arm */
, D4 d) L3 s8 p+ y0 O - __ISB();
" g6 \( [. j4 L! s - /* Unprivileged access mainly affect ability to:. t% a; T* }# _( u/ U3 O
- 6 P6 }( Q g. Y1 G+ }& R, S
- - Use or not use certain instructions such as MSR fields
" o) v- q& K- |3 r9 L( R! D/ e - - Access System Control Space (SCS) registers such as NVIC and SysTick */
) G' j4 |' `9 d( K# j - /* Check Thread mode privilege status */& i, J# r, w' Y8 {
- if((__get_CONTROL() & 0x01) == THREAD_MODE_PRIVILEGED)" A8 Y( [- ]& Z$ e
- {
- j" F# M5 ?- B# `$ A/ L" o - /* Thread mode has privileged access */. R$ U0 i' o; w( Y
- ThreadMode = THREAD_MODE_PRIVILEGED;
% v. h, S8 J* k1 R) _ - }
5 @0 y0 c* j! a2 l - else3 l% w4 ^& Z* X" |1 @6 S- w8 Z
- {7 G* H- S+ P" D' S
- /* Thread mode has unprivileged access*/
6 [/ ~) a( [4 H c' k+ x6 H( J - ThreadMode = THREAD_MODE_UNPRIVILEGED;
9 n' X. j: }. w - }
4 P7 p3 ?6 V% T3 W/ J - if(PrivilegedReadOnlyArray[0])
9 I' w" P6 D6 ^% F1 p4 B' i$ w - {
; g# O3 e5 l+ \: W - PrivilegedReadOnlyArray[0] = 'e';
, P$ ~) ]) l9 C. F$ { - }; I. A" T+ O! l- x g8 ?9 X: `
- PrivilegedReadOnlyArray[0] = 'e';
复制代码
x7 T/ N! ~# H- j, a% X 至此,可以编译成功运行来体会特权模式与用户模式的 MPU 的不同配置。执行到主程序中新加的数组访问代码,会产生内存" ~$ O t+ x* c$ K5 Y2 D
管理异常。- ] S l6 v& U: l5 S, e
/ f; R, ^% ]' |' n! J
5. 关键代码分析
4 d& r+ `' z. P) z; I& T$ H 从特权模式进入用户模式。直接设置 CONTROL 寄存器。& G9 D$ C/ m0 e
- /* Switch Thread mode from privileged to unprivileged ------------------------*/
' V; d0 L$ t8 h# R - /* Thread mode has unprivileged access */
& w8 L' Z9 N0 [4 o7 y - __set_CONTROL(THREAD_MODE_UNPRIVILEGED | SP_PROCESS);
复制代码 q, B0 i& ~0 f- x
从用户模式回到特权模式,需要触发异常处理
' Y2 c ?4 Q6 p7 R- /* Generate a system call exception, and in the ISR switch back Thread mode
; c$ B. O) I/ k. ~" m& L! |# n - to privileged */. H8 \, f2 N4 O' n
- __SVC();
复制代码 . M# y3 k- {2 ^
在异常处理中将线程回到特权模式执行
) a& |5 {5 R' B- /**
/ C2 W) `0 K; m) k; B2 I - * @brief This function handles SVCall exception." m/ N. O7 ~- S/ }+ \0 W9 d- l7 h( {
- * @param None- G& W% l! f8 E" j, {6 S Z
- * @retval None( [0 ~2 }( E1 L! \
- */% H. U9 ]" u. @3 e5 F
- void SVC_Handler(void)
& _# F& p* w1 V - {
0 }) J7 P4 | U1 j! C - /* Switch back Thread mode to privileged */4 y5 F, D9 X0 z
- __set_CONTROL(THREAD_MODE_PRIVILEGED | SP_PROCESS);% z$ o0 b$ g7 d8 Q2 o! l; D
- ) S: C! M& c* \' z8 [ Q
- /* Execute ISB instruction to flush pipeline as recommended by Arm */4 s! I7 C- L, G; E$ n) `' U1 D
- __ISB();
& U- t8 R& p6 E - }
复制代码
' d1 k. j! f+ A5 Y [, T 若用户模式下代码试图访问无权限数据将产生内存管理异常,从而进入下述函数。
2 F0 u8 k, Q$ I6 q s" s% W# ]' O- /**
: r/ r# v; n( J% b - * @brief This function handles Memory Manage exception.6 Y7 z& n3 |: r; t& \
- * @param None* O! ]& z2 H4 V+ F5 y* f
- * @retval None7 s5 m9 G4 Z% o# l5 w
- */; o4 X5 ^& ]2 h% c+ B1 r5 R
- void MemManage_Handler(void)
3 q) o5 \+ a0 {, ^* Z - {$ j- c6 l2 g7 u! L! ~; ^
- /* Go to infinite loop when Memory Manage exception occurs */
2 ^8 b& G) I0 m: N- C- t; D9 o - while (1)
! }1 T$ @2 t1 N - {
9 r# h: t" R, j6 w3 ^ - }
7 r# c# v0 I' d7 s - }
复制代码 ( b8 p( Q0 q8 s
结论
+ z8 {0 T; F5 s) P8 Q! }本文档介绍了 MPU 的特权与用户模式,并整合与修改已有代码进行了应用。在实际中可将 MPU 与 Cortex 运行模式结合,可使应用更加健壮与安全。
+ r/ y4 H, |' [9 r8 O0 B' \6 Y, @0 f* U$ B
7 F& h2 G# f) `- p6 V5 e |