![見出し画像](https://assets.st-note.com/production/uploads/images/87110880/rectangle_large_type_2_64f7834b6f4e645fa84a1bf2cfb4ecf0.png?width=1200)
コンパイラが何をしているのか、ちょっとのぞいてみよう その1
古い話で申し訳ないけれど、私がソフトウェア開発の仕事を始めた頃は、C言語レベルでデバッグできる環境はありませんでした。
そのため、いつもアセンブラでデバッグです。
お陰様で(?)、コンパイラが何をしているのかはよくわかりました。
コンパイラがなにをしているのか、気になったことはありませんか?
ちょっと覗いてみましょう。
昨今はとにかくすごい。
スマホでC言語がコンパイルできて、スマホで gdb も使えてしまいます。
これさえあれば覗き放題です。
まずコードを用意しましょう。
2つの変数を足し算する関数です。
$ cat add.c
#include <stdio.h>
int add(int a, int b);
int main()
{
int x;
x = add(1, 2);
printf("x = %d\n", x);
}
int add(int a, int b)
{
return (a + b);
}
$
では、コンパイルしましょう。
シェルのスクリプトを使います。
$ cat add.sh
cc add.c -g -o add.o
$ sh add.sh
$ ll
total 51
-rw------- 1 u0_a472 u0_a472 152 Sep 11 20:52 add.c
-rwx------ 1 u0_a472 u0_a472 6936 Sep 13 12:55 add.o*
-rwx------ 1 u0_a472 u0_a472 21 Sep 13 12:55 add.sh*
$
出来上がりました。
では、gdbで動かしてみます。
$ gdb add.o
GNU gdb (GDB) 10.1
Copyright (C) 2020 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
<中略>
Reading symbols from add.o...
(gdb)
まず、main関数まで実行します。
mainにブレークポイントを設定して。
(gdb) b main
Breakpoint 1 at 0x1700: file add.c, line 7.
(gdb)
実行します。
(gdb) run
Starting program: /data/data/com.termux/files/home/pointer/add.o
BFD: /system/bin/linker64: unknown type [0x13] section `.relr.dyn'
Breakpoint 1, main () at add.c:7
7 x = add(1, 2);
(gdb)
main関数で停止しました。
アセンブラを見てみましょう。
(gdb) a
Dump of assembler code for function main:
0x00000055555566ec <+0>: sub sp, sp, #0x20
0x00000055555566f0 <+4>: stp x29, x30, [sp, #16]
0x00000055555566f4 <+8>: add x29, sp, #0x10
0x00000055555566f8 <+12>: mov w0, #0x1 // #1
0x00000055555566fc <+16>: mov w1, #0x2 // #2
=> 0x0000005555556700 <+20>: bl 0x5555556728 <add>
0x0000005555556704 <+24>: stur w0, [x29, #-4]
0x0000005555556708 <+28>: ldur w1, [x29, #-4]
0x000000555555670c <+32>: nop
0x0000005555556710 <+36>: adr x0, 0x5555555550
0x0000005555556714 <+40>: bl 0x55555567a0 <printf@plt>
0x0000005555556718 <+44>: mov w0, wzr
0x000000555555671c <+48>: ldp x29, x30, [sp, #16]
0x0000005555556720 <+52>: add sp, sp, #0x20
0x0000005555556724 <+56>: ret
End of assembler dump.
(gdb)
「=>」は今のプログラムカウンタの位置です。
ここを実行しようとしたところで止まっています。
これは main 関数のアセンブラです。
add 関数はどうでしょう。
(gdb) a add
Dump of assembler code for function add:
0x0000005555556728 <+0>: sub sp, sp, #0x10
0x000000555555672c <+4>: str w0, [sp, #12]
0x0000005555556730 <+8>: str w1, [sp, #8]
0x0000005555556734 <+12>: ldr w8, [sp, #12]
0x0000005555556738 <+16>: ldr w9, [sp, #8]
0x000000555555673c <+20>: add w0, w8, w9
0x0000005555556740 <+24>: add sp, sp, #0x10
0x0000005555556744 <+28>: ret
End of assembler dump.
(gdb)
スマホでこんなことができるなんて。
ここまで来てアレなんですが、このアセンブラはいったいどの CPU のものなんでしょうか。
そもそも、スマホが、何の CPU を使っているのかも知らず(笑)。
調べてみると、スマホの CPU は ARM を使っていることが多いようです。
ARM のアセンブラは初めてなんですが、なんとなく見覚えのある感じだし、わからなかったらネット検索でなんとかなるのではないかという安易な考えで果敢に挑戦してみましょう。
しかしこの Linux で見えているのは、ネイティブコードなのか、はたまたエミュレーションコードなのか。
それもおいおい確認しておこう。
次にレジスタを見てみましょう。
(gdb) i r
x0 0x1 1
x1 0x2 2
x2 0x7ffffff198 549755810200
x3 0x7ffffff158 549755810136
x4 0x7ff6dd0080 549602525312
x5 0x7ff6d1f8c4 549601802436
x6 0x61642f617461642f 7017786214960948271
x7 0x742e6d6f632f6174 8371749082501177716
x8 0x0 0
x9 0x2 2
x10 0x1 1
x11 0x0 0
x12 0x0 0
x13 0x6f2e6464612f7265 8011451169428697701
x14 0x73beb86e7520578 521269146530481528
x15 0x0 0
x16 0x7ff377a130 549545550128
x17 0x7ff7f02ab8 549620558520
x18 0x7ff7e24000 549619646464
x19 0x7ffffff188 549755810184
x20 0x55555566ec 366503880428
x21 0x7ffffff198 549755810200
x22 0x1 1
x23 0x0 0
x24 0x0 0
x25 0x0 0
x26 0x0 0
x27 0x0 0
x28 0x0 0
x29 0x7ffffff110 549755810064
x30 0x7ff36ff178 549545046392
sp 0x7ffffff100 0x7ffffff100
pc 0x5555556700 0x5555556700 <main+20>
cpsr 0x80001000 [ EL=0 SSBS N ]
fpsr 0x0 0
fpcr 0x0 0
(gdb)
もう、泣けてきます(うれしくて)。
アセンブラで1ステップ実行してみます。
(gdb) si
add (a=0, b=0) at add.c:13
13 {
(gdb)
そうすると次のようになります。
add 関数の中に入ったところです。
(gdb) a
Dump of assembler code for function add:
=> 0x0000005555556728 <+0>: sub sp, sp, #0x10
0x000000555555672c <+4>: str w0, [sp, #12]
0x0000005555556730 <+8>: str w1, [sp, #8]
0x0000005555556734 <+12>: ldr w8, [sp, #12]
0x0000005555556738 <+16>: ldr w9, [sp, #8]
0x000000555555673c <+20>: add w0, w8, w9
0x0000005555556740 <+24>: add sp, sp, #0x10
0x0000005555556744 <+28>: ret
End of assembler dump.
(gdb)
次に実行される命令はこれになります。
sub sp, sp, #0x10
sub は引き算。
sp から 0x10 を引いて、結果を sp に設定します。
sp はスタックポインタ。
アセンブラにおいては重要なレジスタです。
アセンブラにおけるスタックの役割を話し出すと脱線が激しくなるのでいずれまたそのうちに。
今の sp の値を確認しておきましょう。
(gdb) i r sp
sp 0x7ffffff100 0x7ffffff100
(gdb)
1ステップ実行します。
(gdb) si
0x000000555555672c 13 {
(gdb)
もう一度 sp の内容を見てみましょう。
(gdb) i r sp
sp 0x7ffffff0f0 0x7ffffff0f0
(gdb)
確かに、 0x10 だけ引き算されています。
さて、その次は、この命令文になります。
似たようなコードが2行続いているのでまとめていきましょう。
str w0, [sp, #12]
str w1, [sp, #8]
翻訳するとこうなります。
レジスタ w0 と w1 を、スタックポインタのオフセット 12 と 8 にストアする。
絵にするとこんな感じです。
![](https://assets.st-note.com/img/1663407000341-NaIuzGpNk7.png)
レジスタ w0 と w1 はどうなっているでしょう。
レジスタ x0 と x1 は64bit ですが、 w0 と w1 はそのうちの 32bit を指し示します。
![](https://assets.st-note.com/img/1663468130909-zgM3qdtkPt.png)
ですから、x0 と x1 を見てみます。
(gdb) i r x0 x1
x0 0x1 1
x1 0x2 2
(gdb)
一方、スタックの内容はどうでしょうか。
(gdb) x/2xw 0x7ffffff0f8
0x7ffffff0f8: 0x00000000 0x00000000
(gdb)
まだ、ゼロです。
2回ステップ実行します。
(gdb) si
0x0000005555556730 13 {
(gdb) si
14 return (a + b);
(gdb)
その後にもう一度スタックの内容を見てみましょう。
(gdb) x/2xw 0x7ffffff0f8
0x7ffffff0f8: 0x00000002 0x00000001
(gdb)
確かに変わりました。
続けましょう。
次も、よく似た2行です。
ldr w8, [sp, #12]
ldr w9, [sp, #8]
翻訳します。
スタックポインタのオフセット 12 と 8 の 32 ビットのデータをレジスタ w8 と w9 にロードする。
再び、絵を書いてみるとこんな風になります。
![](https://assets.st-note.com/img/1663467548525-LzsHUGNXsg.png)
今の x8 と x9 はこうなっています。
(gdb) i r x8 x9
x8 0x0 0
x9 0x2 2
(gdb)
[sp, #8] と [sp, #12] はこの通り。
(gdb) x/2xw 0x7ffffff0f8
0x7ffffff0f8: 0x00000002 0x00000001
(gdb)
では、2ステップを実行します。
(gdb) si
0x0000005555556738 14 return (a + b);
(gdb) si
0x000000555555673c 14 return (a + b);
(gdb)
すると、レジスタはこうなります。
(gdb) i r x8 x9
x8 0x1 1
x9 0x2 2
(gdb)
x8 が 0x1 になりました。
次は、これ。
add w0, w8, w9
翻訳。
w8 と w9 を足して、結果を w0 に設定する。
現在のレジスタはこちら。
(gdb) i r x0 x8 x9
x0 0x1 1
x8 0x1 1
x9 0x2 2
(gdb)
では、やってみましょう。
(gdb) si
0x0000005555556740 14 return (a + b);
(gdb)
するとレジスタはこうなりました。
(gdb) i r x0 x8 x9
x0 0x3 3
x8 0x1 1
x9 0x2 2
(gdb)
お見事。
加算成功です。
あとは、スタックポインタを元に戻して、リターンするだけです。
(gdb) a
Dump of assembler code for function add:
=> 0x0000005555556740 <+24>: add sp, sp, #0x10
0x0000005555556744 <+28>: ret
End of assembler dump.
(gdb)
結局、コンパイラは何をしたのでしょうか。
・・・・・。
アセンブラのことばかりに話を費やしてしまった。
長くなりすぎましたので、続きは次回に・・・。