Pebble Coding

ソフトウェアエンジニアによるIT技術、数学の備忘録

swiftとアセンブラ

swiftコードがどのようにアセンブラに出力されているのかをみてみましょう。
Xcodeのプロジェクトで、コマンドラインプロジェクトを生成し、以下のソースをmain.swiftに書き込みます。

import Foundation

func plus(_ a: Int64, _ b: Int64) -> (Int64)
{
    return a + b
}

let r = plus(3, 7)

print("\(r)")

Xcodeの Debug - Debug Workflow - Always Show Disassembly にチェックをつけます。

return a + b

の行にbreakpointを設定して実行してみましょう。

aaa`plus(_:_:):
    0x100001e60 <+0>:  pushq  %rbp
    0x100001e61 <+1>:  movq   %rsp, %rbp
    0x100001e64 <+4>:  movq   %rdi, -0x8(%rbp)
    0x100001e68 <+8>:  movq   %rsi, -0x10(%rbp)
->  0x100001e6c <+12>: addq   %rsi, %rdi
    0x100001e6f <+15>: seto   %al
    0x100001e72 <+18>: movq   %rdi, -0x18(%rbp)
    0x100001e76 <+22>: movb   %al, -0x19(%rbp)
    0x100001e79 <+25>: jo     0x100001e81               ; <+33> at main.swift:13
    0x100001e7b <+27>: movq   -0x18(%rbp), %rax
    0x100001e7f <+31>: popq   %rbp
    0x100001e80 <+32>: retq   
    0x100001e81 <+33>: ud2 

push %rbp
move %rsp, %rbp
は関数の先頭なので、必ずこの2行が入ります。
movq %rdi, -0x8(%rbp)
の行は第1引数の値を第2引数で指定した位置にコピーします。
movq のqは8バイト分(64bit)分のコピーを意味します。
このコマンドは%rsiレジスタの値をレジスタ%rbpが指すアドレスの8バイトマイナス方向の位置にコピーすることを意味しています。

%rbpがスタックの位置を表しており、マイナス方向がpush、プラス方向がpopです。
マイナス方向が上、プラス方向が下とイメージしておくと、スタックのイメージと合います。

ここでレジスタの値をみるには、(lldb)のところに、register read と入れます。

(lldb) register read
General Purpose Registers:
       rax = 0x0000000000000007
       rbx = 0x0000000000000000
       rcx = 0x0000000000000003
       rdx = 0x0000000000000007
       rdi = 0x0000000000000003
       rsi = 0x0000000000000007
       rbp = 0x00007ffeefbff4f0
       rsp = 0x00007ffeefbff4f0
        r8 = 0x0000000000000000
        r9 = 0x000000000007b857
       r10 = 0x00007fff944ee0c8  atexit_mutex + 24
       r11 = 0x00007fff944ee0d0  atexit_mutex + 32
       r12 = 0x0000000000000000
       r13 = 0x0000000000000000
       r14 = 0x0000000000000000
       r15 = 0x0000000000000000
       rip = 0x0000000100001e6c  aaa`aaa.plus(Swift.Int64, Swift.Int64) -> Swift.Int64 + 12 at main.swift:13
    rflags = 0x0000000000000206
        cs = 0x000000000000002b
        fs = 0x0000000000000000
        gs = 0x0000000000000000

(lldb) 

関数の第一引数の値はrdiレジスタ、第二引数の値はrsiレジスタに入ります。
rdiには3、rsiには7が入っていることが分かります。
レジスタにはサイズがあり、レジスタ名の先頭がrは64ビット、先頭eが32ビットのような感じになっていますが、
それ以外は以下を参照して下さい。

x64 Architecture | Microsoft Docs

https://ja.wikibooks.org/wiki/X86アセンブラ/x86アーキテクチャ

addqは右のレジスタに入っている値に左のレジスタに入っている値を加えます。
qは64bit演算であることを意味しています。
seto %al
はオーバーフローが発生していたら%alに1、発生していなければ0を設定します。
%alは8bitレジスタです。

joはオーバーフローが発生していたら指定アドレスに処理をジャンプします。
movq -0x18(%rbp), %rax
によって、plus関数の結果をraxレジスタにセットしています。
戻り値が一つの場合raxレジスタに値をセットして、関数の呼び元に値を伝えます。
popq %rbp
retq
は関数の終わりは必ずこの形になり、callqで呼び出した元の処理に戻ります。
ここでpopq %bpの行にプレークポイントを設定して、デバッグ続行してみましょう。
ここでもう一度レジスタの値を確認します。

(lldb) register read
General Purpose Registers:
       rax = 0x000000000000000a
       rbx = 0x0000000000000000
       rcx = 0x0000000000000003
       rdx = 0x0000000000000007
       rdi = 0x000000000000000a
       rsi = 0x0000000000000007
       rbp = 0x00007ffeefbff4f0
       rsp = 0x00007ffeefbff4f0
        r8 = 0x0000000000000000
        r9 = 0x000000000007b857
       r10 = 0x00007fff944ee0c8  atexit_mutex + 24
       r11 = 0x00007fff944ee0d0  atexit_mutex + 32
       r12 = 0x0000000000000000
       r13 = 0x0000000000000000
       r14 = 0x0000000000000000
       r15 = 0x0000000000000000
       rip = 0x0000000100001e7f  aaa`aaa.plus(Swift.Int64, Swift.Int64) -> Swift.Int64 + 31 at main.swift:13
    rflags = 0x0000000000000206
        cs = 0x000000000000002b
        fs = 0x0000000000000000
        gs = 0x0000000000000000

raxレジスタに3+7の結果である10 = 0xaが入っていることが分かります。
以外にアセンブラも読めるものであることが分かったのではないでしょうか。

戻り値が2つ以上のtupleの場合

plus関数の戻り値が(Int64, Int64)のようなタプルの場合には、戻り値がrax, rdxに入ります。

その他コマンド

leaq メモリ上の場所のアドレス値をレジスタに格納するもの

lealについて - suu-g's diary

Assembly Register Calling Convention Tutorial