ASM-x86

ARM64の方を先にやってしまった… と言うことでx86_64ですわ x86_64向けもRosetta 2でどうにかやるのでちょっと本物とは誤差があるかもしれないと言う部分だけ許してください。

#include <stdio.h>

// 引数が 8 個ある関数
int add_all(int a, int b, int c, int d, int e, int f, int g, int h) {
    int sum = a + b + c + d + e + f + g + h;
    return sum; // ここにブレークポイントを置く
}

int main(void) {
    int result = add_all(1, 2, 3, 4, 5, 6, 7, 8);
    printf("Result: %d\n", result);
    return 0;
}

このコードを最適化無視して、デバッグ情報を付与しつつx86_64向けにコンパイル この方法だとAT&T記法になる。 ちなみにintel記法とAT&T記法がある。 書き方に少し違いがある。

$ clang -g -O0 -arch x86_64 demo.c -o demo
$ lldb demo

(lldb) b add_all
(lldb) r
(lldb) disassemble

ブレークポイントを設定して、走らせ、そこからdisassemble AT&T記法で以下の通り

demo`add_all:
    0x100000470 <+0>:  pushq  %rbp
    0x100000471 <+1>:  movq   %rsp, %rbp
    0x100000474 <+4>:  movl   0x18(%rbp), %eax
    0x100000477 <+7>:  movl   0x10(%rbp), %eax
    0x10000047a <+10>: movl   %edi, -0x4(%rbp)
    0x10000047d <+13>: movl   %esi, -0x8(%rbp)
    0x100000480 <+16>: movl   %edx, -0xc(%rbp)
    0x100000483 <+19>: movl   %ecx, -0x10(%rbp)
    0x100000486 <+22>: movl   %r8d, -0x14(%rbp)
    0x10000048a <+26>: movl   %r9d, -0x18(%rbp)
->  0x10000048e <+30>: movl   -0x4(%rbp), %eax
    0x100000491 <+33>: addl   -0x8(%rbp), %eax
    0x100000494 <+36>: addl   -0xc(%rbp), %eax
    0x100000497 <+39>: addl   -0x10(%rbp), %eax
    0x10000049a <+42>: addl   -0x14(%rbp), %eax
    0x10000049d <+45>: addl   -0x18(%rbp), %eax
    0x1000004a0 <+48>: addl   0x10(%rbp), %eax
    0x1000004a3 <+51>: addl   0x18(%rbp), %eax
    0x1000004a6 <+54>: movl   %eax, -0x1c(%rbp)
    0x1000004a9 <+57>: movl   -0x1c(%rbp), %eax
    0x1000004ac <+60>: popq   %rbp
    0x1000004ad <+61>: retq

新しい基準

pushq  %rbp

pushq(Push Quadword)はスタックにデーターを積みます。 最後のqは8B(64bit)を扱うという合図。
x86_64ではOSがアプリを立ち上げたタイミングで%rspに対してsp(スタックポインタ)の先頭アドレスを保存する。 なのでmain関数のプロセスのspの先頭アドレスはrspですが、add_all関数のスタック領域の計算のために新しく仮のspの先頭アドレスであるrbpを作る。 add_all関数のスタック領域はこれを基準にoffsetで計算を進める。

movq   %rsp, %rbp

movq(Move Quadword)はデーターをコピー。 moveq [コピー元], [コピー先] rbpは領域の確保のタイミングで動かすので基準をrspにコピーさせておく。

領域の確保