Project

General

Profile

DetectStackOverflow » History » Version 5

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
* ![](DetectStackOverflow.png)
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
init.Number           = MPU_REGION_NUMBER0 + リージョン番号;
112
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
検証の終わったタスクのプロテクトは外してほかに回すなどの工夫で対処できるでしょう。