結論#
結論は前に書かれています
まず、microLib を使用しない理由は、ソースコードの挙動を制御可能にするためであり、メーカーの不適切な行為によってソース lib が変更されることはありません。また、ソースコードはクロスプラットフォームの能力を持ち、C 標準ライブラリの違いによる挙動の差異が生じることはありません。
したがって、microLib を使用しない場合、オリジナルの libc が導入され、C ソースライブラリ内の多くの相互作用関数が Semihosting コードを生成し、デバッグ中にカクカクと動作し、デバッグから外れると全く動作しなくなります。
要するに、microLib を使用しない場合、C 標準ライブラリ関数の使用には非常に慎重である必要があり、システム相互作用関数が Semihosting コードを生成することを避けるべきです。
祖先の法則は変わらない#
私がこの業界に入った時から、私の上司は printf 系列の関数の使用を厳しく禁止し、あるいはすべての libc 関数、malloc、memset、strcpyなどを禁止しました。また、標準 C ライブラリはstdint.h、stdlib.h、stdbool.hのみを導入することになりました。その結果、膨大な数の自作の文字列操作、バッファ操作、トレースログなどが生まれました。もし malloc がスレッドのスタックオーバーフローを避けるために使えるとすれば、静的メモリはバグの規模を制御可能にしますが、他の実装はなぜ使えないのでしょうか?
要するに、祖先の法則は変わらず、今でもこのようです。
手がかり#
この人物のブートローダの脆弱性紹介を見ていると(実際にはスタック操作の問題でもあります)、以下の文章を見つけました。おそらくこの祖先の法則の出所を解明できるかもしれません。
Semihosting は本当に組み込みの盲腸なのか?- 腾讯云开发者社区 - 腾讯云 (tencent.com)
検証#
以下は一部のコードです。clock (); はシステム相互作用を生成する典型的なコードです。このプロジェクトでは microLib が無効になっています。
#include <stdio.h>
#include <time.h>
int main(void)
{
// 一部の初期化コードを省略
while (1)
{
clock_t tTime = clock();
HAL_GPIO_TogglePin(GPIOH, GPIO_PIN_5);
HAL_Delay(500);
}
}
デバッグモードで実行
まず、clock () 関数に関連する逆アセンブルが行われ、0x0800037C アドレスにジャンプして clock () 関数が実行されます。
109: clock_t tTime = clock();
0x08002386 F7FDFFF9 BL.W 0x0800037C clock
0x0800238A 9001 STR r0,[sp,#0x04]
0x0800238C F6414000 MOVW r0,#0x1C00
0x08002390 F6C50002 MOVT r0,#0x5802
0x08002394 2120 MOVS r1,#0x20
clock () 関数の場所で、実際に BKPT ソフトブレークポイントが現れました。
ここでBKPTはCortex-MのBreak Point(ソフトウェアブレークポイント)命令であり、定数0xABはSemihosting専用の暗号です。もし使用しているデバッグツールがたまたま Semihosting をサポートしている場合、ソフトウェアは停止せずに正常に動作することさえあります。しかし、電源を切って再起動すると、”BKPT 命令が非デバッグモードで実行されると、Cortex-M プロセッサは Hardfault に入ります”。
0x0800037C 2100 MOVS r1,#0x00
0x0800037E 2010 MOVS r0,#0x10
0x08000380 BEAB BKPT 0xAB ;ここで停止します、これがSemihostingコードのソフトブレークポイントです
0x08000382 4905 LDR r1,[pc,#20] ; @0x08000398
0x08000384 6809 LDR r1,[r1,#0x00]
0x08000386 1A40 SUBS r0,r0,r1
最適化を開くことで回避できるか?#
実際、現代のコンパイラは非常に賢く、少しの最適化を行うことで、自分でも気づかなかった多くのバグを修正します。
O3 の状態でコンパイルしても、BKPT が発生するのは避けられません。この libc はバイナリ形式で書き込みファイルにリンクされるべきです。
どの関数を避けるべきか?#
参考文献には以下のように書かれています。
1. 標準入力 / 出力(Standard I/O)#
- printf 系列関数:例えば printf、fprintf、sprintf など、標準出力デバイス(通常はホストのコンソール)にフォーマットされた出力を行います。
- scanf 系列関数:例えば scanf、fscanf、sscanf など、標準入力デバイス(通常はホストのキーボード入力)からフォーマットされた入力を行います。
2. ファイル操作(File Operations)#
- fopen:ファイルを開く。
- fclose:ファイルを閉じる。
- fread:ファイルからデータを読み取る。
- fwrite:ファイルにデータを書き込む。
- fseek:ファイルポインタを指定位置に移動する。
- ftell:ファイルポインタの現在位置を取得する。
- fflush:ファイル出力バッファをフラッシュする。
3. 時間と日付(Time and Date)#
- time:現在の時間を取得する。
- clock:プロセッサの時間を取得する。
- difftime:2 つの時間点の時間差を計算する。
- strftime:時間と日付を文字列にフォーマットする。
4. エラー処理(Error Handling)#
- perror:標準エラー出力デバイスにエラー情報を出力する。
- strerror:エラーコードに対応するエラーメッセージ文字列を返す。
5. システムコール(System Calls)#
- exit:プログラムを終了し、ステータスコードを返す。
- system:システムコマンドを実行する(組み込みシステムではあまり使用されませんが、ホスト上でデバッグする際には役立つことがあります)。
6. その他の補助機能(Other Auxiliary Functions)#
- getenv:環境変数の値を取得する。
- putenv:環境変数を設定する(あまり一般的ではありません)。
- remove:ファイルを削除する。
- rename:ファイルの名前を変更する。
元の記事は非常に面白いので、ぜひご覧ください#
明らかに私たちはその特徴 4 に該当し、さらに過激です。
【“組み込みの盲腸炎” の潜伏と誘因】
五星上将マッカーサーはかつてこう言いました:ある検索エンジンで病気を見ても、癌は始まります。あなたのような偽専門家がSemihostingをこんなに恐ろしいものとして語り、「コンパイラがデフォルトで埋め込まれている」と言うのは、私はどうして元気に生きているのでしょうか?私はどうして今まで遭遇したことがないのでしょうか?
恕我直言、あなたは以下の特徴に該当するかもしれません:
- 大多数の場合、Arm Compiler 5を使用している;
- 大多数の場合、MicroLibをデフォルトで使用している;
- Arm Compiler 6でMicroLibを選択しないとき、「デバッグ状態ではすべて正常だが、プログラムをダウンロードして直接実行するとフリーズする」という現象に遭遇し —— そのため、小さなノートにMicroLibのみを使用することをメモしている;
- malloc以外のlibc関数を一切使用せず、printfすら含まれている;
- 使用しているプログラムテンプレートは大物が作成したものである;
- アプリケーション開発はチップメーカーが提供する例のプロジェクトに基づいている;
- RT-Threadのような「ワンストップサービス」を提供するソフトウェアプラットフォームを使用している。
私が多くのことを挙げたように見えるかもしれませんが、実際には 2 つの状況に分けられます:
- 瞎猫碰死耗子 —— 運が良い
- 誰かがあなたのために重荷を背負っている
この記事は Mix Space によって xLog に同期更新されました
元のリンクは https://www.yono233.cn/posts/shoot/24_8_13_Semihosting