概要
ブレークポイント(Breakpoint)とは、デバッグ時にプログラムの実行を特定の地点で一時停止させるための仕組みである。デバッガがブレークポイントを設定したアドレスにCPUの実行が到達すると、プログラムは停止し、開発者はその時点のCPUレジスタ、メモリ内容、変数値などを検査できる。
組み込み開発では、インサーキットデバッガと連携してハードウェアレベルでブレークポイントを実現する。ブレークポイントはデバッグの基本操作であり、バグの原因究明に不可欠なツールである。
歴史・背景
ブレークポイントの概念は、1960年代のバッチ処理時代にまで遡る。当時は「デバッグポイント」や「チェックポイント」とも呼ばれ、プログラムの特定箇所にジャンプ命令を挿入してデバッグ用ルーチンに分岐させる手法だった。
インタラクティブデバッガ(1970年代〜)の登場により、実行中のプログラムを動的に停止・検査する手法が確立した。Unix環境ではDBX(1979年)やADB(1980年代)などのデバッガがブレークポイント機能を提供した。GDB(GNU Debugger、1986年〜)が広く普及し、現在に至る標準的なデバッグインターフェースが形成された。
組み込みCPUへのデバッグハードウェアの搭載(JTAGの標準化:1990年、ARM CoreSightの登場:2000年代)により、ソフトウェアのブレーク命令に頼らないハードウェアブレークポイントが実用化された。
技術仕様
ブレークポイントの種類
| 種類 | 仕組み | 特徴 | 使用場面 |
|---|---|---|---|
| ハードウェアブレークポイント | CPUのデバッグハードウェアが監視 | コード改変不要、数に上限あり | フラッシュ上のコード |
| ソフトウェアブレークポイント | BKPT命令(ARM)をコードに埋め込む | 数は無制限、コード書き換えが必要 | RAM上のコード |
| ウォッチポイント(データブレークポイント) | 特定アドレスへの読み書きを監視 | 変数変更の原因特定に有効 | データアクセスのデバッグ |
| 条件付きブレークポイント | 特定の条件を満たした時のみ停止 | 特定の入力値でのみ発現するバグ | 断続的なバグの調査 |
| ログブレークポイント(トレースポイント) | 停止せずにログだけ記録 | リアルタイム性を保ちながらログ取得 | タイミング依存バグ |
ARM Cortex-Mのハードウェアブレークポイント
ARM Cortex-MシリーズはFPB(Flash Patch and Breakpoint)ユニットによりハードウェアブレークポイントを提供する:
| Cortex-Mバリアント | ハードウェアBP数 | ウォッチポイント数 |
|---|---|---|
| Cortex-M0 | 2 | 0 |
| Cortex-M0+ | 2 | 2 |
| Cortex-M3 | 6 | 4 |
| Cortex-M4 | 6 | 4 |
| Cortex-M7 | 8 | 4 |
| Cortex-M33 | 8 | 4 |
| Cortex-M55 | 8 | 4 |
ソフトウェアブレークポイントの仕組み(ARMの場合)
ソフトウェアブレークポイントは、対象アドレスのコードをBKPT命令(ARM: 0xBEAB)で置き換えることで実現される:
設定前:
0x08001234: PUSH {r4, lr} ; 元の命令
0x08001236: MOV r0, #0x42
設定後(GDBがBKPT命令を書き込む):
0x08001234: BKPT #0xAB ; デバッグ例外を発生させる命令
0x08001236: MOV r0, #0x42
CPUが0x08001234に到達 → DebugMonitor例外発生 → デバッガが制御を受け取る
動作原理
ブレークポイント設定から停止までの流れ
[IDE/GDB]
|
| 1. ブレークポイント設定コマンド
| (break main.c:42 または break 0x08001234)
v
[デバッグプローブ (J-Link等)]
|
| 2. JTAG/SWD経由でFPBレジスタに
| 停止アドレスを書き込む
v
[ターゲットCPU]
|
| 3. プログラムカウンタ(PC)が設定アドレスに到達
|
| 4. FPBハードウェアがデバッグホルト信号を発生
|
| 5. CPUがホルト状態に遷移
v
[デバッグプローブ]
|
| 6. ホルト状態をJ-LinkがホストPCに通知
v
[IDE/GDB]
7. 開発者にブレークポイント到達を通知
→ 変数・レジスタ・メモリの検査が可能
GDBでのブレークポイント操作
# ファイルと行番号でブレークポイントを設定
(gdb) break main.c:42
Breakpoint 1 at 0x8001234: file main.c, line 42.
# 関数名でブレークポイントを設定
(gdb) break sensor_read
Breakpoint 2 at 0x8002000: file sensor.c, line 15.
# アドレスで直接設定
(gdb) break *0x08003000
# 条件付きブレークポイント(i==100 の時のみ停止)
(gdb) break main.c:55 if i == 100
# ウォッチポイント(変数が変更された時に停止)
(gdb) watch sensor_value # 書き込み監視
(gdb) rwatch sensor_value # 読み込み監視
(gdb) awatch sensor_value # 読み書き両方監視
# ブレークポイント一覧
(gdb) info breakpoints
Num Type Disp Enb Address What
1 breakpoint keep y 0x08001234 main.c:42
2 breakpoint keep y 0x08002000 sensor.c:15
3 watchpoint keep y sensor_value
# ブレークポイントの無効化・削除
(gdb) disable 1 # 無効化(削除はしない)
(gdb) delete 2 # 削除
(gdb) delete # 全削除(確認あり)
ステップ実行コマンド
ブレークポイントで停止後は、以下のコマンドで実行を制御する:
# 1行実行して停止(関数呼び出しは中に入る)
(gdb) step # または s
# 1行実行して停止(関数呼び出しは飛び越える)
(gdb) next # または n
# 現在の関数を抜けるまで実行
(gdb) finish # または fin
# 次のブレークポイントまで実行
(gdb) continue # または c
# 指定行まで実行
(gdb) until main.c:100
# 特定アドレスまで実行
(gdb) advance *0x08001500
用途・ユースケース
変数の値が予期せず変わる問題の調査(ウォッチポイント活用):
// グローバル変数が誰かによって書き換えられている
volatile uint32_t g_counter = 0;
// GDBで書き込みウォッチポイントを設定
// (gdb) watch g_counter
// → g_counterに書き込んでいる箇所で自動停止
割り込みハンドラのデバッグ:
// 割り込みハンドラにブレークポイントを設定
void TIM2_IRQHandler(void) {
// (gdb) break TIM2_IRQHandler でここで停止
g_tick++;
TIM2->SR &= ~TIM_SR_UIF; // フラグクリア
}
条件付きブレークポイントで断続バグを追う:
// 1000回に1回だけバグが発生するような場合
for (int i = 0; i < 10000; i++) {
process_data(buffer[i]);
// (gdb) break main.c:XX if i == 999
// i=999の時だけ停止して状態を確認
}
RTOSでのタスク別デバッグ:
FreeRTOSなどRTOS環境ではタスクが並行実行されるため、特定タスクの実行中のみ停止する条件付きブレークポイントが有効である:
// タスクIDを条件にしたデバッグ
TaskHandle_t sensor_task_handle;
void sensor_task(void *pvParam) {
while (1) {
// (gdb) break sensor_task if xTaskGetCurrentTaskHandle() == sensor_task_handle
uint32_t val = read_sensor();
vTaskDelay(pdMS_TO_TICKS(100));
}
}
実装・開発のポイント
1. ハードウェアBPの数を節約する
ARM Cortex-Mのハードウェアブレークポイントは最大6〜8個に限られる。IDEはまずハードウェアBPを使い、上限を超えるとソフトウェアBP(BKPT命令)に切り替える。フラッシュ上のコードにはソフトウェアBPが使えない(読み取り専用のため)ことに注意が必要である。
2. ウォッチドッグとブレークポイントの問題
ブレークポイントで停止中もウォッチドッグタイマは動作し続ける。デバッグ中にウォッチドッグがタイムアウトするとシステムがリセットされてしまう。デバッグビルドではウォッチドッグを無効化するか、デバッグホルト中はウォッチドッグをフリーズさせる設定を使う:
#ifdef DEBUG
// デバッグビルドではウォッチドッグを無効化
// STM32の場合: DBGMCUレジスタでデバッグ中のWDTを停止
__HAL_DBGMCU_FREEZE_IWDG();
__HAL_DBGMCU_FREEZE_WWDG();
#endif
3. 割り込みとブレークポイントの注意点
ブレークポイントで停止中も割り込みが発生することがある(デバッガの設定による)。リアルタイム性が重要な処理(モータ制御、通信タイミング)をデバッグする場合は、停止中の割り込み動作を理解しておく必要がある。
4. printf代替としてのSemihosting
デバッガが接続されている場合、Semihosting機能を使ってホストPCにprintfの出力を転送できる(UARTが不要):
// Semihostingを有効にするとprintfがGDBコンソールに出力される
// リンクフラグに --specs=rdimon.specs を追加
#include <stdio.h>
extern void initialise_monitor_handles(void);
int main(void) {
initialise_monitor_handles();
printf("Debug output via semihosting\n");
// ...
}
他技術との比較
| 比較項目 | ブレークポイント | printfデバッグ | ロジックアナライザ |
|---|---|---|---|
| 停止の有無 | 停止する | 停止しない | 停止しない |
| リアルタイムバグ | 検出困難(停止で状況が変わる) | 軽微な影響 | 検出しやすい |
| 見える情報 | CPU内部全て | 明示的に出力したもの | 外部信号のみ |
| 設定の手間 | 少ない | コード改変が必要 | プローブ設置が必要 |
| 量産品でのデバッグ | デバッグポートが必要 | UARTが必要 | テストポイントが必要 |
インサーキットデバッガはブレークポイントを実現するためのハードウェアインターフェースを提供する。IDEはブレークポイントの設定・管理をGUIで行うためのフロントエンドである。ウォッチドッグはブレークポイントによる長時間停止時に誤動作する可能性があるため、デバッグビルドでは適切な設定が必要である。