最終更新:2023-04-11
GDB 内蔵の ARM CPU 用のシミュレータを TUI (Text User Interface) モードで使用して、ベアメタル用ソースコードのデバッグを行う手順を紹介します。C 等の高級言語ソースコードとアセンブリ言語の同時表示や、CPU レジスタの値の同時表示も出来ます。
尚、ARM 用シミュレータ内蔵 GDB を WSL の Ubuntu-20.04 上でビルドする手順は、【こちらの別記事】で紹介しています。
目次:
次の図の様に、ARM 用クロス開発環境を使用して、TUI モードで C 等の高級言語のソースコードとアセンブリ言語を同時に表示して、任意の場所にブレークポイントを設定したり、シングルステップ実行したりしてデバッグする事を目標とします。 動作確認用として次のプログラムと Makefile を使用します。 Makefile はタブキーが区切り記号となるので、8 カラム毎をタブキーに置き換え、make の動作確認を行います。尚、Mekefile 中の先頭が 次のメッセージが出力されれば、make は正常に動作しています。 ARM 用シミュレータ内蔵でビルドした GDB を、TUI (Text User Interface) モードで立ち上げます。尚、--tui を指定し忘れた場合には GDB が立ち上がり、 最初の 以上でプログラムを実行する準備が出来ましたので、main() 関数の先頭にブレークポイントを張って実行し、以降をシングルステップ実行してみます。
尚、シングルステップは 尚、b main の代わりに (画像はクリックまはたピンチで拡大します) この状態で (画像はクリックまはたピンチで拡大します) 終了は、本記事の目標:
また、ARM CPU のレジスタの内容を表示しながら、アセンブリ言語の任意の場所にブレークポイントを設定したり、シングルステップ実行したりして動作を確認することも目標とします。
前提条件:
動作確認用プログラム:
尚、ここで使用しているブートストラップは簡易的な物です。本来の C 言語の仕様 (スタティック変数のゼロ初期化やグローバル変数の使用) には対応していませんし、割り込みにも対応していません。start.s
、main.c
、iadd.c
、isub.c
、arith.h
、Makefile.tmp
の6つのファイルが出来ます。
cat << "EOF" > start.s
.equ STACK_TOP, 0x20010000
.global _start
.text
### Vector Table ###
.align 2
vect_table:
b _start
### Main Routine Call ###
.align 9
_start:
ldr sp, =STACK_TOP
bl main
nop; nop; nop; nop
b _start // Inf loop
### Padding ###
.align 2
EOF
cat << "EOF" > main.c
#include <stdint.h>
#include "arith.h"
int32_t main() {
volatile int32_t a, b;
int32_t c;
a = 4;
b = 3;
c = iadd(a, b);
if (c != 7) return 1;
c = isub(a, b);
if (c != 1) return 1;
return 0;
}
EOF
cat << "EOF" > iadd.c
#include <stdint.h>
int32_t iadd(int32_t a, int32_t b) {
int32_t c;
c = a + b;
return c;
}
EOF
cat << "EOF" > isub.c
#include <stdint.h>
int32_t isub(int32_t a, int32_t b) {
int32_t c;
c = a - b;
return c;
}
EOF
cat << "EOF" > arith.h
#include <stdint.h>
int32_t iadd(int32_t a, int32_t b);
int32_t isub(int32_t a, int32_t b);
EOF
cat << "ALLEOF" > Makefile.tmp
PROGRAM = main.out
DISASML = main.disa
INCS = arith.h
SRCS = start.s main.c iadd.c isub.c
OBJS = start.o main.o iadd.o isub.o
AS = arm-none-eabi-as
ASFLAGS = -g -gdwarf-2
TARGET_MACH = -march=armv5te+fp
CC = arm-none-eabi-gcc
CFLAGS = -g -gdwarf-2 -Og
TARGET_ARCH = -march=armv5te+fp
LD = arm-none-eabi-ld
LDFLAGS = -Bstatic -Ttext 0x0
LDLIBS = -lg_nano
OBJDUMP = arm-none-eabi-objdump
DISAFLAGS = --disassemble-all --source --section=.text
.PHONY: all
all: depend $(PROGRAM) $(DISASML)
#.s.o:
# $(AS) $(ASFLAGS) $(TARGET_MACH) -o $@ $<
#.c.o:
# $(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c -o $@ $<
#.c.o:
# $(CC) -Og $(CPPFLAGS) $(TARGET_ARCH) -S -fverbose-asm -o tmp_$*.s $<
# $(AS) $(ASFLAGS) $(TARGET_MACH) -o $@ tmp_$*.s
$(PROGRAM): $(OBJS)
$(LD) $(LDFLAGS) $^ $(LOADLIBES) $(LDLIBS) -o $@
$(DISASML): $(PROGRAM)
$(OBJDUMP) $(DISAFLAGS) $< > $@
.PHONY: clean
clean:
$(RM) *.o $(PROGRAM) $(DISASML) tmp_*.s
.PHONY: depend
depend: $(INCS) $(SRCS)
ALLEOF
プログラムの make:
#
で始まる行はコメントです。unexpand -t8 Makefile.tmp > Makefile
make
arm-none-eabi-as -g -gdwarf-2 -march=armv5te+fp -o start.o start.s
arm-none-eabi-gcc -g -gdwarf-2 -Og -march=armv5te+fp -c -o main.o main.c
arm-none-eabi-gcc -g -gdwarf-2 -Og -march=armv5te+fp -c -o iadd.o iadd.c
arm-none-eabi-gcc -g -gdwarf-2 -Og -march=armv5te+fp -c -o isub.o isub.c
arm-none-eabi-ld -Bstatic -Ttext 0x0 start.o main.o iadd.o isub.o -lg_nano -o main.out
arm-none-eabi-objdump --disassemble-all --source --section=.text main.out > main.disa
GDB を TUI モードで使用したデバッグ:
Ctrl-x
Ctrl-a
で TUI モードのオン・オフを切り替えられます。あるいはコマンドで tui enable
と指定すると TUI モードに入れます。arm-none-eabi-gdb --tui
(gdb)
のプロンプトが表示されますので、次の通りコマンドを打ちます。file main.out
target sim
load main.out
layout split
file main.out
コマンドで、シンボル情報を読み込みます。
target sim
コマンドで、デバッガをシミュレータに接続します。
load main.out
コマンドで、実行すべきファイルを main.out
に指定します。
layout split
コマンドで、TUI モードの上側に C 等の高級言語のソースコード、下側にアセンブリ言語を表示します。s
コマンドで下階層のプログラム中に入りますが、n
コマンドだと下の階層には入りません。Enter
キーだけを押すと前回と同じコマンドが繰り返されます。
尚、ブートストラップ start.s プログラムの中で無限ループしていますので、main() 関数が繰り返し実行されます。(gdb) b main
(gdb) r
(gdb) s
(gdb)
(gdb) n
...
b n
とすると、ソースリスト上の n 行目にブレークポイントを設定可能です。複数のブレークポイントを設定した場合、c
(continue) コマンドを実行すると次のブレークポイントまでが実行されます。
tui reg general
コマンドを実行すると、上側の C 等の高級言語が表示されていたフレームが、汎用レジスタ表示に切り替わります。更に、si
または ni
コマンドを実行すると、アセンブリ言語の1命令ずつを実行しながら汎用レジスタの内容を確認出来ます。2回目以降は Enter
キーを押せば前回と同様のコマンドが実行されます。尚、レジスタを同時表示する場合には、表示領域が不足がちになりますので、ターミナルのフォントを小さめにするか横幅を広げた方が快適です。横幅を広げるとレジスタが複数列に表示されます。
また、アセンブリの中にブレークポイントを設定したい場合には、例えば b *main +20
の様に設定します。ここでのキーポイントは、指定する関数の前にアスタリスク *
を付けることと、その後に指定する +n
の修飾子のプラス記号と数字の間にスペースを入れない事です。
(gdb) tui reg general
(gdb) si
(gdb)
(gdb) ...
q
コマンドを実行すると、Quit anyway? (y or n)
と表示されますので、y
と入力します。
GDB の主要なコマンドの一覧:
GDB の TUI モードのキーバインドの一覧は、次の公式ページに記載されています。
GDB の主要なコマンドの一覧を示します。
コマンド | 省略形 | 動作 |
---|---|---|
help | h | ヘルプを表示する |
help tui | h tui | TUI モード用コマンドのヘルプを表示する |
file filename | デバッグするファイルを指定し、シンボルをロードする | |
target sim | シミュレータに接続する | |
load filename | 実行するファイルをロードする | |
Ctrl-x Ctrl-a または Ctrl-x a |
- | TUI (Text User Interface) モードのオン・オフ切り替え |
tui enable | TUI モードのオン | |
tui disable | TUI モードのオフ | |
tui reg | TUI 表示可能なレジスタ群の一覧を表示する | |
tui reg general | TUI モードで汎用レジスタ群を表示する | |
layout asm | TUI モードでアセンブリ表示を行う | |
layout src | TUI モードで高級言語ソースコード表示を行う | |
layout split | TUI モードをオンし表示の分割を行う | |
Ctrl-x o |
分割した表示間のハイライトを入れ替える | |
refresh | Ctrl-l |
乱れた TUI 表示のリフレッシュを行う |
break function | b function | 関数 function() の先頭にブレークポイントを設定する |
break n | b n | 現在のスコープの n 行目にブレークポイントを設定する |
break *f +n | b *f +n | 関数 f() の +n バイト目にブレークポイントを設定する |
info | i | 各種情報を出力する (引数無しだとヘルプとなる) |
info break | i b | 設定されたブレークポイントを表示する |
run | r | プログラムを実行する (ブレークポイントまで走る) |
continue | c | 次のブレークポイントまでを実行する |
step | s | 1行ステップ実行する (下位の関数にステップインする) |
next | n | 1行ステップ実行する (下位の関数には入らない) |
stepi | si | アセンブリ命令を1つ実行する (下位関数にステップイン) |
nexti | ni | アセンブリ命令を1つ実行する (下位の関数には入らない) |
Enter のみ |
Enter |
前回と同じコマンドを繰り返す |
quit | q | 終了する |