を見て、どのレベルでその設定が行われているんだろうって思って
調べてみた。
c99の場合
c99では 0になるようなので、先にそちらから確認します。
以下のコマンドでコンパイルを行います。
% gcc -g -std=c99 -c sample.c
得られたオブジェクトファイルを逆アセンブルします。
% objdump -d sample.o L sample.o: file format elf64-x86-64 Disassembly of section .text: 0000000000000000 <main>: 0: 55 push %rbp 1: 48 89 e5 mov %rsp,%rbp 4: b8 00 00 00 00 mov $0x0,%eax 9: c9 leaveq a: c3 retq
中身は何もない mainですが, eaxレジスタに 0が格納されています。
eaxレジスタは関数の戻り値を格納するレジスタです.(Calling Conventionが
cdeclの場合. Linux GCCでは何も指定しなければ cdeclである)
'return 0'と明示的に書かなくても仕様に準拠するために、
コンパイラがそのコードを挿入してくれることがわかります。
c89(ANSI C)
一方 c89で同様のことをしてみます。コンパイルは以下のコマンドになります。
% gcc -g -std=c89 -c sample.c
逆アセンブルは同じコマンドになります。
% objdump -d sample.o sample.o: file format elf64-x86-64 Disassembly of section .text: 0000000000000000 <main>: 0: 55 push %rbp 1: 48 89 e5 mov %rsp,%rbp 4: c9 leaveq 5: c3 retq
eaxレジスタには何も設定していません。入力したコードの通り
戻り値が何もないということがわかります。
補足
厳密にいうと $?に設定される値は main関数の戻り値ではなく、
_exit or exit_groupシステムコールの引数になります。
そのあたりを確認してみましょう。
以下のコマンドでコンパイルします。
% gcc -g -std=c99 -static sample.c
'-static'とすることが重要です。初期設定をして main関数を呼び出したり、
exitするオブジェクトファイルを静的にリンクしてしまいます。
GDBを起動し, _exitにbreakpointを設定します。ここでいう _exitは
libcの関数のものを示します。そして _exitまで走らせます。
% gdb a.out (gdb) break _exit (gdb) run
どのような命令列かを確認します。システムコールを発行する命令は
syscallになります. また x86_64ではシステムコール番号が %eax,
第一引数が %rdiレジスタに格納されます.
(gdb) x/20i $pc => 0x40d310 <_exit>: movslq %edi,%rdx 0x40d313 <_exit+3>: mov $0xffffffffffffffd0,%r9 0x40d31a <_exit+10>: mov $0xe7,%r8d 0x40d320 <_exit+16>: mov $0x3c,%esi 0x40d325 <_exit+21>: jmp 0x40d340 <_exit+48> 0x40d327 <_exit+23>: jmp 0x40d330 <_exit+32> 0x40d329 <_exit+25>: nop 0x40d32a <_exit+26>: nop 0x40d32b <_exit+27>: nop 0x40d32c <_exit+28>: nop 0x40d32d <_exit+29>: nop 0x40d32e <_exit+30>: nop 0x40d32f <_exit+31>: nop 0x40d330 <_exit+32>: mov %rdx,%rdi 0x40d333 <_exit+35>: mov %esi,%eax 0x40d335 <_exit+37>: syscall 0x40d337 <_exit+39>: cmp $0xfffffffffffff000,%rax 0x40d33d <_exit+45>: ja 0x40d358 <_exit+72> 0x40d33f <_exit+47>: hlt 0x40d340 <_exit+48>: mov %rdx,%rdi
syscall命令までプログラムカウンタを進めます。進めすぎないように
ステップ毎に命令を表示するようにします。なおステートメントレベル
ではなく、命令レベルでステップ実行をする場合は 'step(s)'では
なく 'stepi(si)'を使います。
(gdb) display/i $pc (gdb) # syscall命令まで stepi(or si). 到達すれば以下が表示されるはず => 0x40d346 <_exit+54>: syscall
ここでレジスタを %raxと %rdiレジスタの値を確認します.
(gdb) printf "rax=%d, rdi=%x\n", $rax, $rdi rax=231, rdi=0
となりました. x86_64でシステムコール番号 231は exit_groupです.
(システムコール番号は /usr/include/asm/unistd_64.hで確認できます。)
また rdiが 0なので、$?の値が 0になることがわかります。