Claude CodeにSTM32をOpenOCDでデバッグさせる権限の切り方
OpenOCDのサーバー起動とgdbでの状態確認だけをpermissionsで通し、フラッシュ書き込みの入口を止める設計を、settings.jsonとgdbスクリプトの例でまとめます。
STM32の実機デバッグでは、OpenOCDがデバッグアダプターとの窓口になり、arm-none-eabi-gdbがそのサーバーにつながります。Claude Codeにこの2つを触らせると、ハングしたファームのバックトレースやレジスタを読ませる作業がかなり楽になります。
ただし同じ2つのツールで、フラッシュの書き込みもできてしまいます。「サーバーを起動して状態を読む」までを自動で通し、「書き込む」を人の確認に残すには、permissionsの書き方に少し工夫が要ります。この記事では、その切り分けをsettings.jsonとgdbスクリプトの形にします。
OpenOCDとgdbの間に、書き込みの入口はいくつあるか
OpenOCDはサーバーとして動き、接続を待ちます。gdb向けのポートは既定で3333、人がTclコマンドを打つtelnet向けは4444、機械向けのTcl RPCは6666です。gdbからは次のようにつなぎます。
openocd -f interface/ADAPTER.cfg -f board/MYBOARD.cfg
arm-none-eabi-gdb example.elf
(gdb) target extended-remote localhost:3333上は公式の書式の骨格です。アダプターとボードの設定ファイルは、手元の構成に合うものへ置き換えます。
デバッグ用の窓口に見えて、書き込みが通る入口は少なくとも4つあります。permissionsの設計はこの4つを数えるところから始まります。
| 入口 | 書き込みが通る理由 | 止める場所 |
|---|---|---|
openocd -c "program ..." | 書き込みが通る理由programは、OpenOCDを単独のプログラマーとして使う補助コマンド | 止める場所openocdの引数 |
gdbのload | 書き込みが通る理由ターゲットのフラッシュ領域に当たる部分は、vFlashパケットで書き込まれる | 止める場所gdbの引数とスクリプト |
gdbのmonitor ... | 書き込みが通る理由OpenOCDのコマンドをそのまま渡せる | 止める場所gdbのスクリプト |
| telnet(4444)とTcl RPC(6666) | 書き込みが通る理由どちらもOpenOCDのコマンドを受け付ける | 止める場所サーバー起動時のポート設定 |
2行目のloadは、OpenOCD側でgdb flash_program enableが有効なときの動きです。有効でない場合の挙動は、GDB連携の章には書かれていません。3行目のmonitorは、monitor helpでOpenOCDのコマンド一覧を引ける仕組みです。一覧にあるコマンドにはflash write_imageも含まれるため、理屈の上では、gdbにつながった時点で書き込みの手段が手元にあります。
つまり「gdbだけ許可する」「openocdだけ許可する」といった、コマンド名単位の切り分けでは足りません。引数と、読み込ませるスクリプトの中身まで含めて絞ります。
完全一致のallowで、起動とアタッチだけを通す
Bashのルールは、*を使わなければ1つの正確なコマンドだけに一致します。Bash(openocd *)と書くと-c "program fw.elf verify reset exit"も通ってしまうので、この用途では*を使いません。
もう1つ、評価順の制約があります。ルールはdeny、ask、allowの順に評価され、最初に一致したもので結果が決まります。細かいallowを書いても、広いaskには勝てません。「openocdは全部ask、起動コマンドだけallow」という書き方は成立しないのです。
そこで構成を逆にします。allowは完全一致の2本だけにし、書き込みに使う語を含む呼び出しをaskで名指しし、残りは既定の確認に任せます。Manualモードでは、読み取り専用の組み込みコマンド以外のBashは確認が出ます。
{
"permissions": {
"allow": [
"Bash(openocd -f interface/stlink.cfg -f target/stm32f4x.cfg -f debug/ports.cfg)",
"Bash(arm-none-eabi-gdb -batch -nx -x debug/inspect.gdb build/app.elf)"
],
"ask": [
"Bash(openocd *program*)",
"Bash(openocd *flash*)",
"Bash(arm-none-eabi-gdb *flash*)",
"Bash(arm-none-eabi-gdb *load*)"
],
"deny": [
"Edit(./debug/**)"
]
}
}interface/stlink.cfgとtarget/stm32f4x.cfgは例です。使うアダプターとSTM32のシリーズに合わせて差し替えます。
この設定が効く範囲を、いくつか確かめておきます。
- ラッパーは外れて照合される:
timeout 60 arm-none-eabi-gdb ...のようにtimeoutを前置しても、同じルールに一致します。ハングしたまま戻らないgdbを、時間で切れます。 - 複合コマンドは部品ごとに照合される:
openocd ... & arm-none-eabi-gdb ...のように&でつなぐと、両方が別々にルールへ一致する必要があります。2本とも完全一致のallowなので、一行で起動と接続を書けます。 - askは部分一致なので、パスにも反応する:
*flash*は、パスにflashを含むだけでも確認に寄せます。build/flash_map.elfのような名前を付けると、読み取りだけのgdbにも確認が出ます。 - allowはワークスペースの信頼後に有効になる: プロジェクトの
.claude/settings.jsonにあるallowは、フォルダーの信頼ダイアログを受け入れてから適用されます。denyとaskは制限するだけなので、信頼の影響を受けません。
読み取り専用のgdbスクリプトを人が固定する
gdb側の入口は、引数ではなくスクリプトの中身で決まります。そこで、読み取りだけを書いたdebug/inspect.gdbを人が作ります。
set pagination off
target extended-remote localhost:3333
backtrace
info registers
x/16wx $sp
detach-batchは、-xで指定したコマンドファイルを処理したら終了コード0で抜けるモードです。コマンドの実行でエラーが出ると、終了コードは0以外になります。Claude Codeはこのコードで成否を判断できます。ページ送りは止まり、確認の問い合わせにも自動で「はい」と答える設定になるため、対話待ちでハングすることもありません。
-nxは初期化ファイルを読ませない指定です。作業ディレクトリに置かれた.gdbinitの内容が、承認していない形で実行されることを防ぎます。
接続先の3333は、OpenOCDのgdbポートの既定値です。
ポートの側は、起動時に設定ファイルで絞ります。debug/ports.cfgには次の2行を書きます。
telnet port disabled
tcl port disabledOpenOCDのポートは設定段階で決まり、disabledを指定するとそのサービスは起動しません。telnetとTclの入口がなくなれば、書き込みに通じる窓口はgdbポートだけになります。
あとはこのディレクトリを、Claude Codeから書き換えられなくします。permissionsのEdit(./debug/**)のdenyです。inspect.gdbにloadが1行足されれば、完全一致のallowがそのまま書き込みを通してしまうので、ここを人の手に残す意味があります。
Claude Codeが独自の確認スクリプトを使いたいときは、scratch/などに書かせます。その場合は完全一致のallowに一致しないので、実行のたびに確認が出ます。確認が出る分、書き込みを含むスクリプトに気づけます。
書き込みは、人が手元で実行する
フラッシュへの書き込みは、askに残したまま、毎回人が内容を見て承認します。書き込み用のスクリプトをdebug/flash.gdbとして用意しておくと、ファイル名にflashが入るので、上の*flash*のaskに自然と当たります。
set pagination off
target extended-remote localhost:3333
monitor reset halt
load
compare-sections
detachmonitor reset haltでターゲットを止め、loadで書き、compare-sectionsで書いた内容を照合する流れです。OpenOCDの手引きにも、gdb flash_program enableを使えばgdbから任意のフラッシュに書けることと、検証にはcompare-sectionsを使えることが書かれています。
OpenOCD単体で書くなら、programコマンドが補助になります。書き込みの前には、ターゲットをreset initで既知の状態にし、プログラミングが終わるまで別のリセットやresumeを挟みません。この注意はFlash Commandsの章にあります。gdb経由ならターゲットの準備はgdb-flash-erase-startイベントで自動的に行われます。
CLAUDE.mdにデバッグの手順を書く
permissionsは「できること」を制限しますが、Claude Codeに手順を教える役目はCLAUDE.mdが担います。次のような記述を置くと、許可された経路を迷わず使えます。
## STM32のデバッグ
- OpenOCDは `openocd -f interface/stlink.cfg -f target/stm32f4x.cfg -f debug/ports.cfg` で起動する(バックグラウンド)
- 状態の確認は `arm-none-eabi-gdb -batch -nx -x debug/inspect.gdb build/app.elf` だけを使う
- `debug/` 配下は編集しない。別のgdbスクリプトが要るときは scratch/ に書く
- フラッシュの書き込み(load / program / flash write_image)は実行しない。必要なら人に頼む
- 接続できないときは、openocdのログに "tap/device found" があるかを先に確認する最後の行は、後述の切り分けに対応します。CLAUDE.mdの記述には強制力がないので、上の設定と組み合わせて使います。権限そのものはClaude Codeが強制するもので、プロンプトやCLAUDE.mdでは変わりません。
規則をすり抜ける形と、その先の守り
Bashのルールは、Claudeが書いたコマンドの文字列に対して照合されます。同じプログラムを別の形で呼ぶと、ask側のルールが外れます。たとえばBash(git push *)はgit -C . push origin mainを止めません。同じ理屈で、Bash(openocd *program*)も/usr/local/bin/openocd -c "program ..."のような絶対パス呼び出しや、sh -cで包んだ形には当たりません。
絶対パスで呼ばれたopenocdは、完全一致のallowにも一致しません。結果はaskでもallowでもなく、既定の確認になります。つまり人の確認は残ります。ただしautoモードのように確認を分類器に任せるモードでは、別の判定になります。モードごとの扱いは、permissionsの章を確認してください。
askやdenyは、プログラムを囲む境界ではありません。「通る形を完全一致で絞り、残りは確認に落とす」というのが、この記事の構成の前提です。
さらに強くしたいときの選択肢は2つあります。
- PreToolUseフックで中身を見る: コマンド全文を自前のロジックで検査し、
openocdやgdbを含む呼び出しのうち、書き込み系の語を含むものを終了コード2でブロックします。フックの書き方はClaude Code Hooks完全ガイドで扱っています。 - サンドボックスで範囲を絞る: コマンドの文字列に依存せず、OSの層でファイルやネットワークのアクセスを制限します。どの隔離が何を守るかはClaude Codeのサンドボックスの選び方にあります。
Editのdenyが効く範囲にも限りがあります。Claudeの組み込みのファイルツールと、sedやtee、リダイレクトのような認識されるBashコマンドには効きますが、PythonやNodeのスクリプトが内部でファイルを書き換える経路は止まりません。debug/を確実に守りたいなら、サンドボックスの書き込み制限か、ファイルを読み取り専用にするOS側の設定を重ねます。
つながらないときの切り分け
接続の失敗は、OpenOCDのログを読むところから始めます。
OpenOCDは、ターゲットとの接続が確立できなくてもgdbサーバーとtelnetサーバーを立ち上げます。サーバーが起動しているからといって、ターゲットとつながっているとは限りません。
正常なときは、ログに「tap/device found」の行が出ます。この行と警告の不在が、JTAG(またはSWD)の通信が通っている目印です。
ログにその行がなければ、ケーブルやアダプターの設定ファイル、ターゲットの設定ファイルを疑います。Linuxでは、アダプターへのアクセス権を与えるudevルールが入っていないことも原因になります。
行があるのにgdbが失敗するときは、ポートを見ます。gdb向けは既定3333ですが、複数のターゲットを持つ構成では、2つ目以降が3334、3335と続きます。debug/ports.cfgでgdbポートも変えた場合は、inspect.gdbの接続先と合わせます。
ESP32やPlatformIOなど、別のツールチェーンで同じ型の権限を組む場合は、Claude CodeでPlatformIOのファームウェアを開発する手順が参考になります。シリアルの出力を読むデバッグはClaude CodeでESP32の開発とシリアルデバッグを進める手順にあります。クラッシュ後のコアダンプからバックトレースを取る流れは、Claude Codeにコアダンプのgdbバックトレースを読ませる手順で扱っています。
まとめ
STM32のデバッグで書き込みの入口になるのは、openocd -c program、gdbのloadとmonitor、telnetとTclのポートの4つです。コマンド名で許可すると、このすべてが一緒に通ります。
完全一致のallow2本、書き込み系の語を拾うask、debug/ディレクトリのEdit denyの3点を組み合わせると、読み取りは自動、書き込みは人の確認という分担が成立します。絶対パスやsh -cで包まれた形は、人の確認に落ちる側に倒します。守りたいのがフラッシュの中身なら、フックやサンドボックスを重ねるところまでが一組です。
参照した一次ソース
この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。