ここ最近流行しているセグメンテーション・フォールトのコード・ゴルフに便乗してみました。
乗るしかない!このビッグウェーブに
この辺に事情が詳しいです。
「C言語で…」の筆者も書いていますが、システムプログラミング言語ならポインタを使ってセグメンテーション・フォールトを起こせるのは当たり前ですね。インタープリター言語であるPythonがポインタを使わずにセグメンテーション・フォールトを起こしたからみんなが驚いたわけです。
さて、だったら私は1文字もプログラムを書かずに起こしてみませましょう。なお、OSがリッチじゃないのでセグメンテーション・フォールトは起きませんがアサーションにfailします。1文字もプログラムを書かずに!
環境
- CubeIDE 1.3.1
- Nucleo F446RE
Nucleo F446REは一番ポピュラーなNucleoですね。個人的にはG431RBの方がお気に入りですが、F446REのほうが入手が楽なのでこちらで説明します。ちなみに、G431RBでも起こせます。CORTEX-M0以外のどのコアでも起きるんじゃないかな?
手順
最初にCubeIDEでSTM32のプロジェクトを作ります。
ターゲット・セレクタが現れたら、Nucleo F446を選んでください。
プロジェクト名は何でもいいです。言語はCで実験していますが、C++でも起こせると思います。
次。Middlewareの中からFreeRTOSを選んでInterfaceをCMSIS_V1にします。多分V2でも起こせます。
「FreeRTOSを使う時にはHALのタイムベースをSysTickにしてはいけません」とHALのマニュアルに書いているので、粛々と従います。代わりのタイマーは何でもいいです。
Timebase Sourceを変更する
HALの引数チェックを行うために、Full Assertをイネーブルにします。これはちょっとわかりにくいですが、Project ManagerタブのCode Generationグループの中で設定します。
これで設定は終わりです。コードを生成しましょう。
コードを生成できたらプロジェクトをビルドします。1文字も変更していませんので当然ビルドに成功します。NucleoをUSBでPCに接続してデバッガを起動します。
この状態でmain()関数の頭でブレークしていますので、assertion_failed()関数にブレークポイントを貼り実行します。
ビンゴ!assertionに失敗してassertion_failed()関数のブレークポイントで停止します。1文字もコーディングしていないのに!
解説
一切コーディングしていないのにアサーションに失敗するとは奇怪ですが、ぶっちゃけHALのバグです。HAL内部でタイマーを初期化する際、FreeRTOSを使う場合と使わない場合ではタイマー初期化関数が違います。
HAL内部ではタイマー初期化関数が2度呼ばれることを防ぐためにグローバル変数を使っているのですが、FreeRTOS用の初期化関数のプログラマがその点を輪分かっていないため結果的に2度呼びされ、その際にアサーションに失敗しています。
詳細はgithubにあるのでそちらを読んでください。リンク先のCubeIDEバグ集を作っているときには心底うんざりしてノイローゼに落ちそうでしたが、こうして流行にも乗れたことですし作ってよかったです。
STMicroが修正してくれていたらもっとよかったです。