Claude Media
Claude CodeにSTM32をOpenOCDでデバッグさせる権限の切り方

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 disabled

OpenOCDのポートは設定段階で決まり、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
detach

monitor 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で包まれた形は、人の確認に落ちる側に倒します。守りたいのがフラッシュの中身なら、フックやサンドボックスを重ねるところまでが一組です。

参照した一次ソース

この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。

  1. code.claude.com/docs/en/permissions
  2. openocd.org/doc/html/GDB-and-OpenOCD.html
  3. openocd.org/doc/html/Server-Configuration.html
  4. openocd.org/doc/html/Flash-Commands.html
  5. openocd.org/doc/html/Running.html
  6. sourceware.org/gdb/current/onlinedocs/gdb/Mode-Options.html
この記事を共有:XはてブLinkedIn