前回:3. メモリ、アドレス、ロード・ストア 次回:5. 分岐命令
目次(記事一覧)
※この記事はRoger Ferrer IbáñezさんのブログARM assembler in Raspberry Pi – Chapter 4の翻訳です。
ARMアセンブリコードの基礎を学んでいくと、サンプルコードがだんだん長くなっていきます。間違いやすくもなるので、GNU デバッガ(gdb
)の使い方を学ぶ価値があると思います。LinuxでC/C++を開発しているがgdb
を使用したことがないのなら、それは恥ずべきことです。gdb
を知っているのなら、この短い章でアセンブリコードを直接デバッグする方法を知ることができます。
gdb
第3章で作成したstore01
を使用します。gdb
にデバッグするプログラムを指定しましょう。
$ gdb --args ./store01
GNU gdb (GDB) 7.4.1-debian
Copyright (C) 2012 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "arm-linux-gnueabihf".
For bug reporting instructions, please see:
...
Reading symbols from /home/roger/asm/chapter03/store01...(no debugging symbols found)...done.
(gdb)
このようにgdb
の対話モードが始まります。対話モードではコマンドを通じてgdbとやり取りすることができます。組み込みのヘルプコマンドはhelp
です。詳しくはGNU Debugger Documentationを確認してください。最初に学ぶべきコマンドは、
(gdb) quit
です。ではもう一度gdb
を起動してください。この段階ではプログラムはまだ動いていません。コードにデバッグ情報が付いていないためgdb
は多くのことを伝えられませんが、アセンブリコードをデバッグするのでデバッグ情報はあまり必要ありません。プログラムを開始してはじめの一歩を踏み出しましょう。
(gdb) start
Temporary breakpoint 1 at 0x8390
Starting program: /home/roger/asm/chapter03/store01
Temporary breakpoint 1, 0x00008390 in main ()
gdb
はプログラムをmain
まで動かしました。Cライブラリの初期化処理をすべてスキップしてmain
関数の最初の命令を実行しようとしています。そこに何があるかを見てみましょう。
(gdb) disassemble
Dump of assembler code for function main:
=> 0x00008390 : ldr r1, [pc, #40] ; 0x83c0
0x00008394 : mov r3, #3
0x00008398 : str r3, [r1]
0x0000839c : ldr r2, [pc, #32] ; 0x83c4
0x000083a0 : mov r3, #4
0x000083a4 : str r3, [r2]
0x000083a8 : ldr r1, [pc, #16] ; 0x83c0
0x000083ac : ldr r1, [r1]
0x000083b0 : ldr r2, [pc, #12] ; 0x83c4
0x000083b4 : ldr r2, [r2]
0x000083b8 : add r0, r1, r2
0x000083bc : bx lr
End of assembler dump.
(※訳者の環境だと
0x000103d0 : ldr r1, [pc, #40] ; 0x10400 <addr_of_myvar1>
のように<ラベル名>
が行末に表示されました。)
もとのアセンブリコードと比べるとaddr_of_myvarX
が別の表記になっていることがわかります。後ほどの章で解説するので今はとりあえず無視します。矢印=>
はこれから実行しようとしている命令を指しています(実行はまだされていません)。では動かす前にレジスタを調べてみましょう。
(gdb) info registers r0 r1 r2 r3
r0 0x1 1
r1 0xbefff744 3204446020
r2 0xbefff74c 3204446028
r3 0x8390 33680
レジスタはp
(意味print
)を使用して操作できます。例えば、
(gdb) p $r0 = 2
$1 = 2
(gdb) info registers r0 r1 r2 r3
r0 0x2 2
r1 0xbefff744 3204446020
r2 0xbefff74c 3204446028
r3 0x8390 33680
gdb
が表示した$1
は結果を入れた識別子です。必要に応じて使用し、タイピングを省略するのに使います。今はあまり活用しませんが、複雑な式を表示させるのに便利です。
(gdb) p $1
$2 = 2
この時点で$2
も使えるようになりました。では、最初の命令を実行します。
(gdb) stepi
0x00008394 in main ()
何が起こったか確認したいのでdisassemble
を使います。
(gdb) disassemble
Dump of assembler code for function main:
0x00008390 : ldr r1, [pc, #40] ; 0x83c0
=> 0x00008394 : mov r3, #3
0x00008398 : str r3, [r1]
0x0000839c : ldr r2, [pc, #32] ; 0x83c4
0x000083a0 : mov r3, #4
0x000083a4 : str r3, [r2]
0x000083a8 : ldr r1, [pc, #16] ; 0x83c0
0x000083ac : ldr r1, [r1]
0x000083b0 : ldr r2, [pc, #12] ; 0x83c4
0x000083b4 : ldr r2, [r2]
0x000083b8 : add r0, r1, r2
0x000083bc : bx lr
End of assembler dump.
r1
に起こったことを確認します。
(gdb) info register r1
r1 0x10564 66916
予想通りに変化していますね。これはmyvar1
のアドレスです。本当にそうなのか、シンボリック名とC構文を使って確認しましょう。
(gdb) p &myvar1
$3 = ( *) 0x10564
(※訳者の環境では$3 = (<data variable, no debug info> *) 0x21024
)
すごいです!この変数に入っている値は?
(gdb) p myvar1
$4 = 0
(※訳者がやってみたところ'myvar1' has unknown type; cast it to its declared type
と出ました。
「型が不明だからキャストしろ」という意味のようですので、(gdb) p (int)myvar1
と入力したらうまくいきました。)
完璧ですね。myvar1
とmyvar2
の初期値にゼロを選んだので、この結果は期待通りです。では、次のステップです。
(gdb) stepi
0x00008398 in main ()
(gdb) disas
Dump of assembler code for function main:
0x00008390 : ldr r1, [pc, #40] ; 0x83c0
0x00008394 : mov r3, #3
=> 0x00008398 : str r3, [r1]
0x0000839c : ldr r2, [pc, #32] ; 0x83c4
0x000083a0 : mov r3, #4
0x000083a4 : str r3, [r2]
0x000083a8 : ldr r1, [pc, #16] ; 0x83c0
0x000083ac : ldr r1, [r1]
0x000083b0 : ldr r2, [pc, #12] ; 0x83c4
0x000083b4 : ldr r2, [r2]
0x000083b8 : add r0, r1, r2
0x000083bc : bx lr
End of assembler dump.
disasssemble
は省略してdisas
と書けます。r3
の値を確認しましょう。
(gdb) info registers r3
r3 0x3 3
ここまで順調ですね。さらにもう一つステップを進めます。
(gdb) stepi
0x0000839c in main ()
(gdb) disas
Dump of assembler code for function main:
0x00008390 : ldr r1, [pc, #40] ; 0x83c0
0x00008394 : mov r3, #3
0x00008398 : str r3, [r1]
=> 0x0000839c : ldr r2, [pc, #32] ; 0x83c4
0x000083a0 : mov r3, #4
0x000083a4 : str r3, [r2]
0x000083a8 : ldr r1, [pc, #16] ; 0x83c0
0x000083ac : ldr r1, [r1]
0x000083b0 : ldr r2, [pc, #12] ; 0x83c4
0x000083b4 : ldr r2, [r2]
0x000083b8 : add r0, r1, r2
0x000083bc : bx lr
End of assembler dump.
r3
に起こったことを確認します。
(gdb) p myvar1
$5 = 3
(※さきほどと同じく(gdb) p (int)myvar1
でうまくいきました。)
すごいですね。では、最後まで実行します。
(gdb) continue
Continuing.
[Inferior 1 (process 3080) exited with code 07]
今日はここまで。
次回:5. 分岐