Raspberry Pi ZeroでのJTAGデバッグ


Raspberry Pi ZeroをJTAG接続デバッグするのを試したことが無かったなあと思い、やってみました。
以前Interface誌の記事を見ながらRaspberry Pi2をJTAGデバッグしてみたときは、結構苦労した記憶があるのですが、今回は割とすんなり進みました。

JTAG接続のためのアダプタは、Raspberry Pi2で試したときのものをそのまま使います。
ソフトウェアは、今回はInterface誌の付録DVDではなく、Ubuntu 16.04のOpenOCDとarm-none-eabi-gdbを使いました。

また、Raspberry PiはJTAGを使うためにはGPIOピンの設定をする必要がありますが、これはdwelch67さんのリポジトリにあるソフトウェアを使いました。

raspberrypi/armjtag at master · dwelch67/raspberrypi

コンパイル済みの「armjtag.bin」をSDカードにコピーし、config.txtでこのファイルをカーネルに指定します。
armjtag.binはGPIOを設定した後は無限ループですが、GPIOさえ設定できていれば、デバッグしたいプログラムはJTAG経由で流し込むことができます。

デバッグ対象のソフトウェアは、最適化無し・シンボル情報つきでコンパイルしておく必要があります。
そのためのGCCのフラグは

-O0 -gdwarf-2

です。

今回はMicroPythonをデバッグしてみることにしました。
Makefileを修正して、

$ make DEBUG=1

でデバッグ用イメージを生成できるようにしました。

これをJTAGでデバッグするには、

・JTAGを有効にしたイメージ(何でもよいが上記のarmjtag.binを利用)でRaspberry Piを起動
・JTAGインタフェースでPCと接続し、OpenOCDを起動(cfgファイルは上記のdwelch67さんのリポジトリにあります)

$ openocd -f jlink.cfg -f raspi.cfg 
Open On-Chip Debugger 0.9.0 (2018-01-24-01:05)
Licensed under GNU GPL v2
For bug reports, read
	http://openocd.org/doc/doxygen/bugs.html
adapter speed: 1000 kHz
none separate
Info : auto-selecting first available session transport "jtag". To override use 'transport select '.
raspi.arm
Info : J-Link ARM V8 compiled Dec  1 2009 11:42:48
Info : J-Link caps 0xb9ff7bbf
Info : J-Link hw version 80000
Info : J-Link hw type J-Link
Info : J-Link max mem block 9576
Info : J-Link configuration
Info : USB-Address: 0xff
Info : Kickstart power on JTAG-pin 19: 0x0
Info : Vref = 3.319 TCK = 1 TDI = 0 TDO = 0 TMS = 0 SRST = 1 TRST = 1
Info : J-Link JTAG Interface ready
Info : clock speed 1000 kHz
Info : JTAG tap: raspi.arm tap/device found: 0x07b7617f (mfg: 0x0bf, part: 0x7b76, ver: 0x0)
Info : found ARM1176
Info : raspi.arm: hardware has 6 breakpoints, 2 watchpoints

・別のウインドウでarm-none-eabi-gdbを起動し、OpenOCDのサーバ(localhost:3333)へ接続

$ arm-none-eabi-gdb
(gdb) target remote localhost:3333
Remote debugging using localhost:3333
0x00000000 in ?? ()
(gdb)

・いったんRaspberry Piで実行中のソフトウエアを再開して停止する

(gdb) continue
Continuing.
WARNING! The target is already running. All changes GDB did to registers will be discarded! Waiting for target to halt.
^C
Program received signal SIGINT, Interrupt.
0x00008114 in nlr_jump (val=0x1395f263) at ../micropython/py/nlrthumb.c:87
87	    MP_NLR_JUMP_HEAD(val, top)
(gdb)

・gdbにデバッグしたいソフトウェアを読み込む

(gdb) file build/firmware.elf
A program is being debugged already.
Are you sure you want to change the file? (y or n) y
Load new symbol table from "build/firmware.elf"? (y or n) y
Reading symbols from build/firmware.elf...done.
(gdb)

・ソフトウェアをJTAG経由でRasperry Piに転送する

(gdb) load
Loading section .init, size 0x38 lma 0x8000
Loading section .text, size 0x60940 lma 0x8040
Loading section .rodata, size 0xac7c lma 0x68980
Loading section .rodata.str1.4, size 0x228 lma 0x735fc
Loading section .ARM.exidx, size 0x8 lma 0x73824
Loading section .data, size 0x3f5 lma 0x73840
Start address 0x8040, load size 441369
Transfer rate: 34 KB/sec, 3940 bytes/write.
(gdb)

・デバッグする(ブレークポイントの設定やステップ実行等)

(gdb) b arm_main
Breakpoint 1 at 0x4cbd4: file main.c, line 127.
(gdb) p _start
$1 = {} 0x8000 <_start>
(gdb) jump _start
Line 0 is not in `nlr_push_tail'.  Jump anyway? (y or n) y
Continuing at 0x8000.
Breakpoint 1, arm_main (r0=3818913792, id=3138, atag=0x0) at main.c:127
127	    bool use_qemu = false;
(gdb) 

という感じで比較的スムーズに進みましたが、注意点としてはTAG経由で起動した場合、メイン関数に渡される引数がブートローダで起動した場合と異なるという点です。
ARM boot tagへのポインタも渡されません。(このアドレスは0x100と決まっていますので、渡されなくても問題ないのですが、コードの修正は行う必要がありました。)

ともかく、ブレークポイントが置けたりステップ実行ができるのは、やはり便利ですね。
おかげでUSB周りのバグ(Soft rebootに失敗する)が取れました。
Deinitialise時にroot hubのdevice numberを0に戻していないのが原因でした。

コメント