前言
1 B3 S. F* _0 i. S. w4 cSTM32 系列支持 MPU 内存保护单元,可用来设定内存的属性和访问权限。MPU 的应用笔记提到,将属性寄存器(MPU_RASR)配置成某一个值,在特权(Privileged permissions)和用户模式(Unprivileged permissions)的访问许可是不同的,甚至可将用户模式的权限设置成不可访问。那么,什么是 MPU 的特权模式和用户模式呢? 接下来我们在这篇文章来理解这些名词,并讨论在 STM32 MCU 代码中如何使用内存保护单元 MPU 的特权与用户模式。0 t3 Z5 R6 K. {* |. C! Y
/ l* I; [" I% B
MPU(Memory Protection Unit)/ B/ U0 y/ m- r/ k
MPU 内存保护单元可以用来使嵌入式系统更加健壮与安全。它可以阻止用户应用程序破坏系统关键数据。它可以通过将内存SRAM 区域定义成不可执行,来阻止代码注入型攻击。也可以用来改变内存的性质,例如是否允许缓存(Cache)。用来设置内存属性的 MPU_RASR 寄存器字段描述如下:
3 n. E3 h* x [/ v. A1 v6 ]3 M4 t! `/ U3 b4 |) i& _
8 E G/ \8 d5 V( A1 S2 _' P' @, G' c+ _1 X
在 MPU_RASR 中用来设置数据访问许可的 AP 字段详细设置选项如下:1 A- x: X1 Q, ~+ i* [ c( \ Q, ^ \
6 b. G1 p3 A& N
, p0 }& v: M- t
) ]5 e ]0 w( F9 X: N% e+ X9 @特权模式(Privileged mode)与用户模式(UnPrevileged mode)+ Y: {+ M# F& i5 b; g! C
特权模式与用户模式是指 Cortex 内核的执行模式。它不是 MPU 的一部分,但与 MPU 单元有关联。当代码运行在特权模式下,代码拥有所有的访问许可;而代码运行在用户模式,则访问权限受限制。限制包括在系统设计阶段就定义的可运行指令限制,可访问内存以及外设限制。也包括由 MPU 单元动态所定义的内存访问规则。
: p8 b$ Q3 f3 T" n/ W, E从特权模式进入用户模式,只需使用 MSR 指令操作 CONTROL 寄存器。但是,不可以直接从用户模式转入特权模式,必须经过一个异常处理操作模式,比如 SVC( Supervisor Calls)。在异常处理通过操作 CONTROL 寄存器,可从用户模式回到特权模式。请注意,特权线程与异常处理线程,都是在特权模式下运行。6 {: r; \2 Y& W$ n# P' [0 w. r
如下图所示:
& v8 x6 l+ w, y
2 S8 k! P% | k) `
+ C2 F" H- V3 W6 }7 L, {3 H/ _3 [3 X& `; H1 D
在代码中结合特权与用户模式使用 MPU. Y9 m) x" k a) A
1. 开发环境
& g: g( T& r6 P' O# y, t, [* y) Q 开发板: STM32 L476RG NUCLEO
6 n3 g1 ^; Z+ D) Z& X& v. H 开发工具:STM32Cube_FW_L4_V1.7.0
/ X+ O2 x1 S T @ E- b# j IAR/Keil
, p* C' K# D/ q+ `' }4 s2 }" O 注:也可以选择其他 STM32 系列并选择相应的开发板与固件库。
+ y; w& O% P9 }9 y i; r+ x7 O2. 开发目标定义/ {5 F2 O, B6 ~5 B9 H
在软件中定义一块数组,使用 MPU 将该区域配成仅可从特权模式下进行访问,并验证用户模式下访问会导致内存管理异常或者硬错误。
8 E1 Q9 Z* G( z, {- B" J H+ h2 `; _, t+ `7 A
! j0 E; R v. b8 V; H' [
5 n! U% {- [+ W+ d& y) o
3. 代码资源与整合1 @7 [6 k% W1 f
STM32 Cube 固件库提供了大量的例程供学习和重用。查找 STM32Cube_FW_L4_V1.6.0 可以发现,固件库已分别实现了特权模式与用户模式的切换(CORTEXM_ModePrivilege),和 MPU 的配置(CORTEXM_MPU)。/ j+ [* V9 C& D2 u
+ |! U+ G, Z" w x2 `& p
. H9 W6 V' q/ Q* O: G z
9 I% W+ O# C- k: g
我们需要将两个例程整合在一起来实现我们的开发目标。注意你选择其他 STM32 系列也是没有问题的,也支持 MPU 功能。复制 CORTEXM_MPU\Src\stm32_mpu.c 到 CORTEXM_ModePrivilege\Src 目录下
% g' T/ ^3 g |
% {2 b% R& I. A5 G5 V
' N5 s' y5 ? G) Z$ Q0 `; K
6 d. `! N: l( c; b, Z5 w0 @ 复制 CORTEXM_MPU\Inc\stm32_mpu.h 到 CORTEXM_ModePrivilege\Inc 目录下( F) S, y% c0 k; D; l3 z
# B* Y1 J0 v) `5 C" V# N& Q( x6 d1 U. q
& ]; d+ p8 P* R: A0 N5 J. s
% K! B( W/ `6 H- E 打开 CORTEXM_ModePrivilege 工程文件,将 stm32_mpu.c 和 stm32_mpu.h 添加到工程文件中。IAR 与 Keil 略有不同,在 IAR 下只需添加 C 文件。" `% F& l8 o8 X. W D
4. 修改与增加代码3 f# {/ p: g" v( }- {: d- q6 n
新增加的代码以红色表示
7 B+ [! p$ z3 o5 Z7 ?" Z 在主程序中包含 stm32_mpu.h
# M8 q& _6 U$ m; k- /* Includes ------------------------------------------------------------------*/2 W0 w9 F% C* K: z& V
- #include "main.h"
* b3 Z5 J! j0 b) T% p0 e - #include "stm32_mpu.h" /*added*/" H% S3 j! J3 U) K& d
- /** @addtogroup STM32L4xx_HAL_Examples
, }5 f5 k- j* g ~8 E - 0 q: w: B4 E% h
- * @{/ y6 h9 v! O7 T4 `, y* `% }
- */; w: Z9 v% N* `" @
- /** @addtogroup CORTEXM_ModePrivilege8 Z, F, E+ s; H9 P+ d4 T
- * @{
7 |3 \6 I9 I6 V9 S8 n' j - */
复制代码 @ ]7 n$ [- |
在主程序里调用 MPU 配置函数
7 ~" q$ E* i* A; a1 i8 N% ^ 要对使用的内存进行 MPU 配置,未被 MPU 配置的资源将视之为不可访问。这是 MPU_Config 必须被使用的原因。( R3 M2 B" C2 W2 Y9 ~
- /* Get the Thread mode stack used */
- v. ]: n: S/ x4 d8 c* t4 E; ? - if((__get_CONTROL() & 0x02) == SP_MAIN)
- P: U/ f% S) X# R3 s3 z - {
0 u) |) [- D7 q+ j3 F u - /* Main stack is used as the current stack */
2 q5 J! J; M( I; C - CurrentStack = SP_MAIN;
, [ `) O, }; \3 x3 [ - }
& Z) R+ X5 f3 D' Y) D* [) A& s - else
5 z5 y. N* A" g) r! b `# ?# F - {: e! B/ l0 b' n4 l4 X
- /* Process stack is used as the current stack */, _2 Z2 D7 j9 ]; b: Y# l+ O
- CurrentStack = SP_PROCESS;
( E; b3 p# `- C+ D7 ]( Q - /* Get process stack pointer value */
7 {* _6 r* ^3 w& f0 T2 o- N - PSPValue = __get_PSP();
4 Y$ d% `& L9 U - }$ ]$ j: i& n) K3 d: c6 I8 Q
- MPU_Config(); /*added*/
6 p7 y% w! F& b9 w2 L - MPU_AccessPermConfig(); /*added*/
复制代码 7 O7 z; k7 j+ X, Q# y
修改 MPU_AccessPermConfig 将数组区域配置成我们设计的权限并增加测试代码。0 o2 a: `7 D; H4 S* t
MPU_AccessPermConfig 在特权模式下运行,故测试代码不会触发异常。; {1 M* a. T# _$ H8 |
- void MPU_AccessPermConfig(void)0 Q B3 S# {. |* M& V
- {
# W* M4 A! c4 e# y# i' [6 Q - MPU_Region_InitTypeDef MPU_InitStruct;7 j& e; m4 B2 z6 z' S
- /* Configure region for PrivilegedReadOnlyArray as REGION N?, 32byte and R
7 k4 p* V0 g) o3 p, q$ q- U# `$ E - only in privileged mode */
3 K4 i0 k' \" D h1 O - /* Disable MPU */
( e5 C- I* h, e. N w - HAL_MPU_Disable();! M# y) K- I7 ?6 z- N4 F
- MPU_InitStruct.Enable = MPU_REGION_ENABLE;- n6 H3 b8 ]. x7 i
- MPU_InitStruct.BaseAddress = ARRAY_ADDRESS_START;; C! }& y7 A( i8 I# @
- MPU_InitStruct.Size = ARRAY_SIZE;
2 W |2 i9 h* |* p3 ]4 X3 ?5 e b( S1 w - MPU_InitStruct.AccessPermission = portMPU_REGION_PRIVILEGED_READ_WRITE;
- Y6 _, P" d+ { - MPU_InitStruct.IsBufferable = MPU_ACCESS_NOT_BUFFERABLE;
; B6 o& U' ], K# E! `2 Y+ @: ` - MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE;' `; C, U# g; x9 J
- MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;
- S# _( B$ j0 t9 C - MPU_InitStruct.Number = ARRAY_REGION_NUMBER;% [$ y1 N3 k/ t- N1 R" q5 h) w
- MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0;
6 O8 D6 `; [" A - MPU_InitStruct.SubRegionDisable = 0x00;
& w7 T2 w P v2 v0 E; Y - MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;* M" l4 @" l* S: ]* Q! G
- 0 m" \1 t) w# \) R9 y. n) V8 a: x
- HAL_MPU_ConfigRegion(&MPU_InitStruct);5 }' D- V2 U# `8 F0 K
- 2 v0 Z: {' y0 c5 l& ]1 G1 T$ H
- /* Enable MPU (any access not covered by any enabled region will cause a fault) */! }( U$ M) V) c# K" }( W& J
- HAL_MPU_Enable(MPU_HFNMI_PRIVDEF_NONE);" E( Q, p% r* N% ^8 J! Q3 m: |% F2 Y4 z
- /* Read from PrivilegedReadOnlyArray. This will not generate error */ [* p" ~7 A9 t6 }- {! V8 B
- if(PrivilegedReadOnlyArray[0])8 c- t1 ~" F+ K: G5 I
- {2 U( T4 V6 j$ W7 F3 `( i
- PrivilegedReadOnlyArray[0] = 'e';7 g* m* ?+ k! q2 y) a
- }/ s* {9 ?# j6 v" g9 ` w# p
- /* Uncomment the following line to write to PrivilegedReadOnlyArray. This will; W5 f8 @' l- U, I4 I/ |
- generate error */9 F( s. C% h$ V
- PrivilegedReadOnlyArray[0] = 'e';
" e2 d! L. u1 Q6 E# d' s& i# A& } - }
复制代码
% c' k4 N9 s- u! r, F 在用户模式下尝试读写数组
P9 O* k- q: D. b0 b; D/ P( X参考代码中直接访问数组,很容易被编译器优化掉而不能发挥测试作用。这里加了个判断后写的操作,使编译器不去优化它。
' G7 k+ I, x* O5 |- /* Switch Thread mode from privileged to unprivileged ------------------------*/
$ D6 r9 M' R/ Z- D5 v0 o - /* Thread mode has unprivileged access */ k2 Z1 M% R* j, A
- __set_CONTROL(THREAD_MODE_UNPRIVILEGED | SP_PROCESS);
9 t5 e# X3 ]$ |. e2 C
" S0 \! A% h* [- /* Execute ISB instruction to flush pipeline as recommended by Arm */
& ?2 F0 E9 [, y( O- r - __ISB();
! W" H& A5 _: m! A+ Y9 n+ I - /* Unprivileged access mainly affect ability to:8 n% }! B" w1 v1 [+ A
- ) p) [& M$ K0 \( |8 N2 N6 s
- - Use or not use certain instructions such as MSR fields% T! \- d% E; L4 z
- - Access System Control Space (SCS) registers such as NVIC and SysTick */ F$ I% i( i7 G$ t- q. V
- /* Check Thread mode privilege status */# b, a0 Y: r7 K
- if((__get_CONTROL() & 0x01) == THREAD_MODE_PRIVILEGED)
5 ^/ J+ b# C0 y8 P - {$ |5 e% Z" B+ S6 a5 S: N' U
- /* Thread mode has privileged access */% p; s; D) X$ f/ L0 P
- ThreadMode = THREAD_MODE_PRIVILEGED;
: V- x8 T9 e& K( o1 V - }
" K; s; r- g, q' q- Q( e6 R - else
) V# v9 R6 g/ Y - {
1 {( ^- n% X( M* y( r/ ]4 q! g' | - /* Thread mode has unprivileged access*/' F! Y8 W% V$ c, Q8 n# ~0 V) s2 c
- ThreadMode = THREAD_MODE_UNPRIVILEGED;
0 \* f! w: l+ m$ R - }
. f+ _ S; w' E4 G - if(PrivilegedReadOnlyArray[0])
E+ W V1 ~" Q \' ?8 d - {! D3 F5 H4 I3 q3 B' K/ O1 h; p
- PrivilegedReadOnlyArray[0] = 'e';
4 v" w& X. ~' e, d7 e9 } - }
+ s( Q$ R& }8 V - PrivilegedReadOnlyArray[0] = 'e';
复制代码
1 @/ [+ n0 \! _3 i+ ^ 至此,可以编译成功运行来体会特权模式与用户模式的 MPU 的不同配置。执行到主程序中新加的数组访问代码,会产生内存6 q7 D3 [5 s8 V) A( a1 P
管理异常。
% X, E6 A! [# G" [# T8 L' Y0 d9 x# l/ n& @3 e
5. 关键代码分析" e p0 K/ R5 b" J) y. ?) ]. S
从特权模式进入用户模式。直接设置 CONTROL 寄存器。
! j" ?$ U: y% i# x+ h/ R9 O- /* Switch Thread mode from privileged to unprivileged ------------------------*/
0 p$ }# V" P# `6 B# j1 r - /* Thread mode has unprivileged access */
T# ?) n$ b7 a3 \- K6 ]+ A - __set_CONTROL(THREAD_MODE_UNPRIVILEGED | SP_PROCESS);
复制代码
9 v. b+ [+ K5 R' p& J1 R3 }2 F 从用户模式回到特权模式,需要触发异常处理- k% F& ]. I% n+ \, P! y* K
- /* Generate a system call exception, and in the ISR switch back Thread mode
+ U1 G6 a: @+ Q' y0 b! ]& v. A - to privileged */1 q& F! t+ E) m+ T- z7 _0 z' k
- __SVC();
复制代码 ) g6 g5 v# s* r1 }
在异常处理中将线程回到特权模式执行6 B$ v" C+ I& }+ E' m$ d
- /**( s; a e2 g; G4 A C+ K
- * @brief This function handles SVCall exception.. H j' P7 m& f$ O9 k
- * @param None
6 Q6 {+ e# u# W+ e) Z - * @retval None! N* ~" J4 b% g2 f7 D
- */+ q, z( l5 w" D: A% a1 H
- void SVC_Handler(void). s( S% m$ N# @) m2 ?/ E: j$ m" H" W" K
- {- Y& N8 J. k" i5 L/ s' {8 Z# S
- /* Switch back Thread mode to privileged */4 F" X' R2 p. T* v0 y
- __set_CONTROL(THREAD_MODE_PRIVILEGED | SP_PROCESS);
: z3 O! ?, U% b6 E( b) b
$ d4 m. c8 q$ ~- /* Execute ISB instruction to flush pipeline as recommended by Arm */2 d& t- r H/ u
- __ISB();; Y7 l' P- C" b- p# R t
- }
复制代码
L" @: O: b7 I1 E$ D2 N 若用户模式下代码试图访问无权限数据将产生内存管理异常,从而进入下述函数。3 m! [- b5 y Y# E$ O
- /**
/ `& u3 Z. n6 E+ q! ~. g - * @brief This function handles Memory Manage exception.6 E! o& j. ^! j" F' b+ i3 C
- * @param None+ h" ?/ m, b* m; d( Y3 @' Z
- * @retval None S" L; ~. L% U1 k2 [; T
- */
- ~( Z+ A8 W& b - void MemManage_Handler(void)$ Y8 k& V; @6 m" o5 M0 O3 r" ^
- {
) ^+ V3 e3 m) y. Z$ @9 s( ] - /* Go to infinite loop when Memory Manage exception occurs */
* w, B9 A+ ]8 \0 ~- m' _3 t6 T - while (1)
" h/ k- t4 x _* ?) }2 u9 g - {
; O' l: y" D& Y" F2 j$ x - }
2 |" v% O' S$ k; [& B# H - }
复制代码
2 @5 ]% B% q% Y# ^3 @1 ~ 结论
. d: G' N) V9 i本文档介绍了 MPU 的特权与用户模式,并整合与修改已有代码进行了应用。在实际中可将 MPU 与 Cortex 运行模式结合,可使应用更加健壮与安全。
& P4 G; H4 l6 r5 t+ ?
7 d" t, B: v' o$ D! j0 {
# a$ `/ r& E# P7 x4 x' q0 h |