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)。
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 のシステムヘッダファイルの定義と重複しているとのこと。情報源:
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)。
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 のようになります。
#
# 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行目: | ブレークポイントのマーカーの設定。文字色と背景色を反転させている。 |
パソコンとの接続
デバッグの流れ
前回に引き続き『インターフェース』誌の 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
オプションでこのファイルを読み込ませることができます。
target remote localhost:3333
monitor reset init
それからopenocd
コマンドとarm-none-eabi-gdb
コマンドのエイリアスも設定しておきましょう。zsh なら ~/.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 の「さよならワイルドハーツ」というゲームにハマっている次第で、ここで一旦休憩とします。