DetectStackOverflow » History » Version 6
mole lord, 09/14/2020 01:35 PM
1 | 1 | mole lord | # DetectStackOverflow |
---|---|---|---|
2 | |||
3 | {{>toc}} |
||
4 | |||
5 | ## FreeRTOSスケジューラ起動前のスタックオーバフロー検出 |
||
6 | |||
7 | STM32F4には Core Coupled Memory (以降CCM) が 0x1000_0000 番地にあります。(リファレンスマニュアルを参照) |
||
8 | リンカスクリプトをいじってCCMの先頭部分にスタック領域を配置すれば、スタックオーバフローがRAMが存在しない領域へのWriteとなり、 Hard Fault を起こします。 |
||
9 | |||
10 | 乱暴ですが、Hard Faultをもってスタックオーバフロー検出ができます。 |
||
11 | (グローバル変数を破壊して摩訶不思議な挙動をされるよりはよいです) |
||
12 | |||
13 | CCMRAMにスタックを置くデメリットは、スタック上の値をDMACが扱えなくなることですが、 |
||
14 | |||
15 | 具体的には、 STM32F429ZITx_FLASH.ld を開いて、 |
||
16 | |||
17 | ~~~ c |
||
18 | /* Highest address of the user mode stack */ |
||
19 | _estack = 0x20030000; /* end of RAM */ |
||
20 | /* Generate a link error if heap and stack don't fit into RAM */ |
||
21 | _Min_Heap_Size = 0x0; /* required amount of heap */ |
||
22 | _Min_Stack_Size = 0x400; /* required amount of stack */ |
||
23 | ~~~ |
||
24 | |||
25 | を以下のように書き換えます。 |
||
26 | |||
27 | ~~~ c |
||
28 | /* Generate a link error if heap and stack don't fit into RAM */ |
||
29 | _Min_Heap_Size = 0x0; /* required amount of heap */ |
||
30 | _Min_Stack_Size = 0x400; /* required amount of stack */ |
||
31 | /* Highest address of the user mode stack */ |
||
32 | _estack = 0x10000000 + _Min_Stack_Size; |
||
33 | ~~~ |
||
34 | |||
35 | また、._user_heap_stackの部分を削除し、 |
||
36 | |||
37 | ~~~ c |
||
38 | /* User_heap_stack section, used to check that there is enough RAM left */ |
||
39 | ._user_heap_stack : |
||
40 | { |
||
41 | . = ALIGN(8); |
||
42 | PROVIDE ( end = . ); |
||
43 | PROVIDE ( _end = . ); |
||
44 | . = . + _Min_Heap_Size; |
||
45 | . = . + _Min_Stack_Size; |
||
46 | . = ALIGN(8); |
||
47 | } >RAM |
||
48 | ~~~ |
||
49 | |||
50 | その代わりに `>CCMRAM` に挿入します。 |
||
51 | |||
52 | ~~~ c |
||
53 | .ccmram : |
||
54 | { |
||
55 | . = ALIGN(4); |
||
56 | _sccmram = .; /* create a global symbol at ccmram start */ |
||
57 | . = ALIGN(8); |
||
58 | _user_heap_stack = .; |
||
59 | PROVIDE ( end = . ); |
||
60 | PROVIDE ( _end = . ); |
||
61 | . = . + _Min_Heap_Size; |
||
62 | . = . + _Min_Stack_Size; |
||
63 | . = ALIGN(8); |
||
64 | *(.ccmram) |
||
65 | *(.ccmram*) |
||
66 | |||
67 | . = ALIGN(4); |
||
68 | _eccmram = .; /* create a global symbol at ccmram end */ |
||
69 | } >CCMRAM |
||
70 | ~~~ |
||
71 | |||
72 | 動作確認は、無限再帰の関数を作ってそれをmain()から呼び出してやることで行えます。 |
||
73 | |||
74 | ~~~ c |
||
75 | static int32_t |
||
76 | recurse(int32_t x) |
||
77 | { |
||
78 | return recurse(x + 1); |
||
79 | } |
||
80 | |||
81 | int main() |
||
82 | { |
||
83 | ... |
||
84 | int32_t x = recurse(0); |
||
85 | int32_t i; |
||
86 | for (i = 0; i < x; i++) ; |
||
87 | ... |
||
88 | ~~~ |
||
89 | |||
90 | ## FreeRTOSのタスクのスタックオーバフロー検出 |
||
91 | |||
92 | 5 | mole lord | FreeRTOS自体にもスタックオーバフロー検出の仕組みが備わってはいます。しかし、いまいち信用ならないので、Memory Protection Unit(以降MPU)を使って書き込み禁止領域を作ることで検出を行います。 |
93 | 1 | mole lord | |
94 | 4 | mole lord | *  |
95 | |||
96 | 2 | mole lord | デメリットは、スタック領域1つごとに32バイトの使用不可領域ができることと、スタックの開始アドレスを32バイト境界にしか配置できないことです。 |
97 | それと引き換えに、RTOSを使うときに一番厄介な「なんだかわからないがソフトを修正したらいきなり挙動がおかしくなった、変更されないはずのグローバル変数が書き換わった」などの、スタックオーバフローによる不思議な動作に悩まされなくなります。 |
||
98 | 1 | mole lord | |
99 | 2 | mole lord | リポジトリの SHA-1: d4c1e95 の common/molelord/MoleMem.c に実装があります。 |
100 | |||
101 | ~~~ c |
||
102 | HAL_MPU_Disable(); |
||
103 | MPU_Region_InitTypeDef init = {0}; |
||
104 | init.Enable = MPU_REGION_ENABLE; |
||
105 | init.BaseAddress = プロテクトしたいアドレス; |
||
106 | init.Size = MPU_REGION_SIZE_32B; |
||
107 | init.AccessPermission = MPU_REGION_PRIV_RO_URO; |
||
108 | init.IsBufferable = MPU_ACCESS_BUFFERABLE; |
||
109 | init.IsCacheable = MPU_ACCESS_CACHEABLE; |
||
110 | init.IsShareable = MPU_ACCESS_SHAREABLE; |
||
111 | 6 | mole lord | init.Number = MPU_REGION_NUMBER0 + リージョン番号(0~7); |
112 | 2 | mole lord | init.TypeExtField = MPU_TEX_LEVEL0; |
113 | init.SubRegionDisable = 0x00; |
||
114 | init.DisableExec = MPU_INSTRUCTION_ACCESS_DISABLE; |
||
115 | HAL_MPU_ConfigRegion(&init); |
||
116 | HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT); |
||
117 | ~~~ |
||
118 | |||
119 | MPUの使い方は AN4838 Managing memory protection unit in STM32 MCU の p.14 Setting the MPU with cube HAL にあります。 |
||
120 | AccessPermission に MPU_REGION_PRIV_RO_URO を設定しているので、そこへのライトアクセスが発生すると |
||
121 | MemManage_Handler()が呼び出されます。 |
||
122 | |||
123 | プロテクトできる領域は8つまでなので、タスクが9つ以上ある場合は取捨選択が必要です。 |
||
124 | 検証の終わったタスクのプロテクトは外してほかに回すなどの工夫で対処できるでしょう。 |