公開日: 2023年10月26日

GDB dashboard で Raspberry Pi Pico の L チカをデバッグする (前編)

前回の記事で取り上げた Raspberry Pi Pico (以下 Pico) の L チカプログラムを GDB と GDB dashboard を使ってデバッグしてみます。Eclipse でのデバッグも強力なのだろうと想像しますが (使ったことがないけど…)、テキストユーザーインターフェース好きな当サイトとしては、GDB の使い勝手を劇的に改善する GDB dashboard を推してゆきたいと思います。なお、GDB 本体については前々回 、Pico 用の Debug Probe については前回の記事で取り上げていますので、そちらを参照してください。

GDB dashboard とは

素の GDB では、ブレークしたときに表示する変数やレジスタの内容をコマンドラインから指示する必要があり、なかなか面倒くさいです。GDB dashboard を導入すれば、そのような GDB の使い勝手の悪さが劇的に向上します。ブレークしたときに表示したい内容をあらかじめ設定しておくと、それらをコンパクトにまとめて表示してくれます。表示される情報としてはアセンブリコード、ブレークポイントの一覧、メモリダンプ、レジスタ、ローカル変数などがあり、それぞれ表示 / 非表示を選択できたり、別の端末エミュレータに表示させたりといった設定が可能です (図 1)。

図 1: GDB dashboard
図 1: GDB dashboard

GDB dashboard の実体は GDB の設定ファイルに Python API を使って書かれているので、設定ファイルをダウンロードするだけで簡単に導入することができます。

OpenOCD のインストール

早速 GDB dashboard を導入したいところですが、Raspberry Pi 公式サイトの Debug Probe に関するページ

を眺めると「まず OpenOCD をインストールせよ」と書かれています。これに従いましょう。FreeBSD の ports/packages にも収録されていますが、RP2040 (Pico に搭載されているマイコン) をサポートしていない感じだったので、ソースコードからビルドすることにします。

まず、依存するパッケージを ports/packages からインストールします。過不足があるかもしれませんが、いまのところ次の 6 パッケージで問題なさそうです。

# pkg install -y gcc automake autoconf texinfo libtool libftdi1

OpenOCD のソースコードを GitHub から clone してきます。

% cd ~/pico/repo/
% git clone https://github.com/raspberrypi/openocd.git --branch rp2040 --recursive --depth=1

FreeBSD の場合は、ここで openocd/src/flash/nor/rp2040.c に修正を入れます。このファイルで定義されているPAGE_SIZEが FreeBSD のシステムヘッダファイルの定義と重複しているとのこと。情報源:

openocd/src/flash/nor/rp2040.c (差分)
diff -u src/flash/nor/rp2040.c.old src/flash/nor/rp2040.c
--- src/flash/nor/rp2040.c.old	2023-10-01 02:09:05.837133000 +0900
+++ src/flash/nor/rp2040.c	2023-10-01 02:09:38.330127000 +0900
@@ -147,7 +147,7 @@
 #define BLOCK_SIZE (1ul << 16)
 #define BLOCK_ERASE_CMD 0xd8
 #define SECTOR_SIZE 4096
-#define PAGE_SIZE 256
+#define WRITE_PAGE_SIZE 256

 struct rp2040_flash_bank {
 	int probed;
@@ -214,7 +214,7 @@
 	struct working_area *bounce;
 	int err = ERROR_OK;

-	if (offset % PAGE_SIZE) {
+	if (offset % WRITE_PAGE_SIZE) {
 		LOG_ERROR("RP2040 flash writes must be page-aligned (%d bytes). Can't continue", PAGE_SIZE);
 		return ERROR_TARGET_UNALIGNED_ACCESS;
 	}

修正したらビルドしてインストールします。

% ./bootstrap
% ./configure
% gmake
# gmake install

ちなみにアンインストールするにはgmake uninstallを実行します。

GDB dashboard のインストールと設定

インストール

インストールはとても簡単です。次のようにして配布元から .gdbinit を取得し、自分のホームディレクトリに置くだけです (wget は標準ではインストールされていないので、ports/packages からインストールしてください)。

% cd
% wget https://raw.githubusercontent.com/cyrus-and/gdb-dashboard/master/.gdbinit

また、GDB dashboard の中でソースコードのシンタックスハイライトを行いたい場合は Pygments という Python のパッケージも必要です。私の環境にはいつの間にかインストールされていましたが、もしされていなければ ports/packages から py39-pygments をインストールしておいてください。

設定ファイルの置き場所

GDB 関連の設定は通常 ~/.gdbinit に書くものらしいですが、先ほどダウンロードしてきた ~/.gdbinit には GDB dashboard の本体が書かれており、そのままそっとしておきたいところ。GDB dashboard の Wiki によると GDB dashboard が起動時に読みにいくディレクトリが何ヵ所かあるようなので、そこに設定ファイルを置くのが良さそうです。というわけでここでは ~/.config/gdb-dashboard/ に init という名前でファイル用意し、そこに GDB や GDB dashboard の設定を書いていくことにします。

スタイル設定

GDB dashboard の外観をカスタマイズするうえで有用と思われる設定項目をまとめておきます (図 2,3)。

図 2
図 2
図 3
図 3

GDB dashboard の表示は図 2 のように「Assembly」「Breakpoints」「Memory」などいくつかのエリアに分かれます。これらのエリアは「モジュール」と呼ばれます。モジュール間の区切りは「primary divider」、モジュール内の区切りは「secondary divider」と呼ばれます。図 2 の例では Memory モジュールの中が表示番地ごとに区切られていますが、これが secondary divider です。また、モジュールの中には図 2 の Expressions や History のように中身が空の場合がありますが、これを「OFF 状態」、空でない場合を「ON 状態」と呼び、それぞれに別の色を指定することができます。以上を私の調べたかぎりでまとめると、次の表のようになります。

項目意味初期値備考
ansi

ANSI エスケープコードによる出力の有効/無効

True

divider_fill_char_primary

primary divider に使用する文字

'-'

divider_fill_char_secondary

secondary divider に使用する文字

'-'

divider_fill_style_primary

primary divider の色

'36'

Cyan

divider_fill_style_secondary

secondary divider の色

'90'

Bright black

divider_label_align_right

divider ラベルの位置を右寄せにする

False

divider_label_margin

divider ラベルと区切り線の間に入れるスペースの数

1

divider_label_skip

divider ラベルの画面端からの表示位置

3

divider_label_style_off_primary

OFF 状態の primary divider ラベルの色

'33'

Yellow

divider_label_style_off_secondary

OFF 状態の secondary divider ラベルの色

'90'

Bright black

divider_label_style_on_primary

ON 状態の primary divider ラベルの色

'1;33'

Bright yellow

divider_label_style_on_secondary

ON 状態の secondary divider ラベルの色

'1;37'

Bright white

prompt_not_running

ターゲットプログラムを動かせる状態ではないときのプロンプト

'\\[\\e[90m\\]>>>\\[\\e[0m\\]'

Bright black

prompt_running

ターゲットプログラムを動かせる状態にあるときのプロンプト

'\\[\\e[1;35m\\]>>>\\[\\e[0m\\]'

Magenta

style_critical

ブレークポイントなど

'31'

Red

style_error

エラー表示

'1;37'

Bright red

style_high

変数名やメモリダンプのキャラクタ表示

'1;37'

Bright white

style_low

メモリの番地、レジスタ名、ソースコードの行番号など

'90'

Bright black

style_selected_1

変化があったレジスタ値、ソースコードの現在行など

'1;32'

Bright green

style_selected_2

詳細不明。assembly、stack などのモジュールに出現する

'32'

Green

syntax_highlighting

ソースコードの強調表示

'monokai'

すべての設定項目を見るには、GDB のコマンドプロンプトからhelp dashboard -styleまたはhelp dashboard モジュール名 -styleを実行してください。

なお、表中のシングルクォーテーションで囲まれた数字は ANSI エスケープシーケンスの SGR パラメータによる色の指定です。例えば '36' はシアン、'33' は黄色、'1;33' や '93' は明るい黄色、といった具合です。ただ、シアンだの黄色だのといっても、実際には使用する端末エミュレータのカラースキームによってだいぶん印象が変わるので、なんだかしっくり来ないという場合はそちらを見直したほうがいいかもしれません。下記の記事も参考にしてください。

設定例

~/.config/gdb-dashboard/init のサンプルを示します。これを適用すれば図 1 のようになります。

~/.config/gdb-dashboard/init
#
# GDB general configuration
#
set style enabled off
set confirm 0
alias db = dashboard
alias dbs = dashboard -style
alias dbm = dashboard memory watch
alias dbe = dashboard expressions watch

#
# GDB dashboard configuration
#
dashboard -layout assembly breakpoints expressions !history memory registers source stack !threads variables
dashboard -style syntax_highlighting ''
dashboard -style prompt_running '\\[\\e[32m\\](gdb)\\[\\e[0m\\]'
dashboard -style prompt_not_running '(gdb)'
dashboard -style divider_fill_char_primary '─'
dashboard -style divider_fill_style_primary ''
dashboard -style divider_fill_char_secondary '─'
dashboard -style divider_fill_style_secondary '36'
dashboard -style divider_label_style_on_primary ''
dashboard -style divider_label_style_off_primary ''
dashboard -style divider_label_style_on_secondary ''
dashboard -style divider_label_style_off_secondary ''
dashboard -style divider_label_margin 1
dashboard -style divider_label_skip 3
dashboard -style style_selected_1 '33'
dashboard -style style_selected_2 ''
dashboard -style style_high ''
dashboard -style style_low '37'
dashboard -style style_critical '7'
4行目:GDB によるシンタックスハイライトを無効にする。
5行目:GDB 終了時に確認を行わない。
6~9行目:コマンドのエイリアス設定。
14行目:モジュールごとの表示/非表示の設定。頭に「!」をつけると非表示になる。
15行目:シンタックスハイライトのテーマを指定する。'' を指定すると無効になる。
16~31行目:図 2,3 および上の表を参照。
32行目:ブレークポイントのマーカーの設定。文字色と背景色を反転させている。

パソコンとの接続

Debug Probe に付属の 3-pin debug to 3-pin debug ケーブルで Pico と Debug Probe をつなぎ、両方を USB ケーブルでパソコンに接続します (Pico 側も給電のために接続が必要です)。ちなみに Debug Probe には USB ケーブルも付属しています。

図 4: Pico と Debug Probe の接続
図 4: Pico と Debug Probe の接続

デバッグの流れ

前回に引き続き『インターフェース』誌の L チカプログラムを使ってデバッグのおおまかな流れを見ていきます。まず端末エミュレータを開き、root 権限で OpenOCD を起動します。

# openocd -f interface/cmsis-dap.cfg -f target/rp2040.cfg -c "adapter speed 5000"

次に別の端末エミュレータを開き、サンプルプログラムのビルド用ディレクトリに移動して、gmakeコマンドでビルドします。

% cd ~/pico/repo/interface_trykernel/build_part2/sect_3/
% gmake

ビルドに成功すると build ディレクトリの下に blink.elf が得られるので、それを引数として GDB を起動します (UF2 ファイルではなく ELF ファイルを指定することに注意)。

% arm-none-eabi-gdb build/blink.elf

GDB のプロンプトが表示されたら、下記のようにいくつかの初期設定コマンドを打ったあと、loadコマンド (文末のおまけも参照) でプログラムを Pico のフラッシュメモリにロードします。

(gdb) target remote localhost:3333
(gdb) monitor reset init
(gdb) load
Loading section boot2, size 0x100 lma 0x10000000
Loading section .text, size 0xae8 lma 0x10000100
Loading section .ARM.exidx, size 0x8 lma 0x10000be8
Start address 0x10000854, load size 3056
Transfer rate: 607 bytes/sec, 1018 bytes/write.

そしてcontinueコマンドを実行するとプログラムが走りだします。LED もチカチカを開始します。プログラムを停止したいときは Ctrl-c キーを押してください。

(gdb) continue
Continuing.
^C
(gdb)

デバッグ開始時のタイプ量を減らそう

これで Pico のデバッグができるようになりましたが、毎回これだけのコマンドを打ち込むのは大変なので、タイプ量を減らせるように工夫します。

まず、GDB 起動後の初期設定をスクリプト化します。GDB 全般の設定というよりはターゲットシステム寄りの設定なので、プロジェクト固有のディレクトリに置くのが良いと思います。ここでは interface_trykernel/build_part2/sect_3 に置くことにし、名前を .gdbinit.pico としておきます。arm-none-eabi-gdbコマンドの-xオプションでこのファイルを読み込ませることができます。

interface_trykernel/build_part2/sect_3/.gdbinit.pico
target remote localhost:3333
monitor reset init

それからopenocdコマンドとarm-none-eabi-gdbコマンドのエイリアスも設定しておきましょう。zsh なら ~/.zshrc に次のように書きます。

~/.zshrc
alias sudo='sudo '
alias oo='openocd -f interface/cmsis-dap.cfg -f target/rp2040.cfg -c "adapter speed 5000"'
alias pgdb='arm-none-eabi-gdb -q -x ./.gdbinit.pico'

GDB を起動するディレクトリに .gdbinit.pico が存在すれば-xオプションでそれを読み込むようにしています。存在しなければ起動時にその旨が表示されます。ついでに-qオプションを指定して起動メッセージを抑制しています。これで次からはそれぞれ

% sudo oo
% pgdb build/blink.elf
(gdb) load
(gdb) continue

とするだけでよくなり、だいぶん楽になります。

おまけ: load コマンドの使いどころ

正直なところloadコマンドが何をやっているのかよく理解していません。デバッガを起動するたびにloadしなくちゃいけないんでしょうか?Pico の公式ドキュメント Getting started with Raspberry Pi Pico の 6.3 節には、loadコマンドは ELF ファイルをフラッシュメモリに展開するととれる記述があります。だとするとデバッグイメージはフラッシュメモリに保持されるので、ELF ファイルに変更がなければ毎回loadする必要はなさそうです。本当にフラッシュメモリに書き込まれるのか、下記のように確認してみました。まず GDB を起動してloadします。

% arm-none-eabi-gdb build/blink.elf
(gdb) load
Loading section boot2, size 0x100 lma 0x10000000
Loading section .text, size 0xae8 lma 0x10000100
Loading section .ARM.exidx, size 0x8 lma 0x10000be8
Start address 0x10000854, load size 3056
Transfer rate: 609 bytes/sec, 1018 bytes/write.

ロード中のメッセージを見ると .text セクションが外部フラッシュメモリの 0x10000100 番地から 0xAE8 バイト分の領域に展開されることがわかります。そこで、dump memoryコマンドを使って、この領域をファイルにダンプします。

(gdb) dump mem text.bin 0x10000100 0x10000be8

別の端末エミュレータを開いて、ダンプしたファイルをテキスト表示してみます。

% hexdump -C text.bin
00000000  00 00 04 20 55 08 00 10  9d 08 00 10 9d 08 00 10
00000010  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00
00000020  00 00 00 00 00 00 00 00  00 00 00 00 9d 08 00 10
...
00000ac0  00 0a 08 39 1b 09 98 42  01 d3 00 09 04 39 02 a2
00000ad0  10 5c 40 18 70 47 c0 46  04 03 02 02 01 01 01 01
00000ae0  00 00 00 00 00 00 00 00

元データである ELF ファイルの .text セクションも読み出してみます。

% arm-none-eabi-readelf -x .text build/blink.elf

Hex dump of section '.text':
  0x10000100 00000420 55080010 9d080010 9d080010 ... U...........
  0x10000110 00000000 00000000 00000000 00000000 ................
  0x10000120 00000000 00000000 00000000 9d080010 ................
  ...
  0x10000bc0 000a0839 1b099842 01d30009 043902a2 ...9...B.....9..
  0x10000bd0 105c4018 7047c046 04030202 01010101 .\@.pG.F........
  0x10000be0 00000000 00000000                   ........

目視で確認しただけですが、元の ELF データと一致しているとみて良さそうです。念のため Pico の電源をオフオンして再度ダンプしたところ、電源オフオン前のデータと一致しました (当然といえば当然ですが)。というわけで、ELF ファイルに変更がないかぎりは、1 度ロードしてしまえば電源を入れ直しても再ロードする必要はなさそうです、というお話でした。

休憩

デバッグのおおまかな流れがわかったところで、実際に GDB dashboard を使ってデバッグする様子を書いていきたいところですが、目下 Nintendo Switch の「さよならワイルドハーツ」というゲームにハマっている次第で、ここで一旦休憩とします。

Raspberry Pi Pico 実験室