LoginSignup
34
39

More than 5 years have passed since last update.

適当なx86_64の勉強の仕方

Posted at
#include <stdio.h>

void print_i(int i) {
  printf("%d\n", i);
}

int main() {
  print_i(1);
}

この辺をコンパイルしてみると、こんなのが作れます。

%rax %rbx %rcx %rdx %rdi %rsi %rsb %rbp 等の64bitのレジスタがあります。%raxはアキュムレータと呼ばれます。x86_64の呼び出し規約では関数の返り値が%raxに入れる事になっています。

引数も、6個くらいまでは、レジスタで渡すようになっています。それ以上の場合はスタックに積んで渡します。

x86の場合は呼び出し規約にも寄りますが、基本的にレジスタ数が少ないのでスタックに引数を積んで呼び出す事になります。

        ; プログラムのエリアの始まり
        .section        __TEXT,__text,regular,pure_instructions

        ; _print_iは他のプログラムからも見えます
        .globl  _print_i
        ; アラインのおまじない
        .align  4, 0x90

; 関数の始まり
; %edi に引数が入ってくるディシディシrdi rsi rdx rcx rbx rexとかだったはず
; Cのソース書いてコンパイルしてみれば分かる
; rspがスタックポインタでマシンのスタックトップを示してます。
; rbpがベースポインタといって、関数開始時のスタックポインタの位置を保存します。
; ベースポインタから上下で、ローカル変数だったり、呼び出し元の変数だったりします。
_print_i:
        pushq   %rbp
        movq    %rsp, %rbp
        subq    $16, %rsp
        ; スタックを16バイト取るおまじない

        movl    %edi, %eax
        movl    %eax, -4(%rbp)
        ; 引数をメモリに保存

        movl    -4(%rbp), %eax
        ; 引数をメモリから取り出し

        ; これ以降は可変長引数の呼び出しのゴニョゴニョ
        xorb    %cl, %cl
        leaq    L_.str(%rip), %rdx
        movq    %rdx, %rdi
        movl    %eax, %esi
        movb    %cl, %al
        callq   _printf

        ; ここから、スタックを元に戻すおまじない
        addq    $16, %rsp
        popq    %rbp
        ret ; 関数から戻る


; ここからはメイン関数
        .globl  _main
        .align  4, 0x90
_main:
        pushq   %rbp
        movq    %rsp, %rbp
        subq    $16, %rsp
        ; ここまでスタック確保


        ; 引数%ed1に1を入れて _print_iを呼び出し
        movl    $1, %eax
        movl    %eax, %edi
        callq   _print_i

        ; リターン値を設定する リターン値は %eaxに入るのです。
        movl    $0, -8(%rbp)
        movl    -8(%rbp), %eax
        movl    %eax, -4(%rbp)
        movl    -4(%rbp), %eax

        ; ここからスタック解放
        addq    $16, %rsp
        popq    %rbp
        ret

        ; データ用のセクションを開始して、%d\nをラベル付きでもつ
        .section        __TEXT,__cstring,cstring_literals
L_.str:
        .asciz   "%d\n"

で、この辺を理解したら、1+1をする関数を作って、アセンブラを見て見る。1-1も見てみる。1*1を、1/1をってやっていくと、それぞれに対応するアセンブラが何かがわかります。

int add(int a, int b) { return a + b; }
int sub(int a, int b) { return a - b; }
int mul(int a, int b) { return a * b; }
int div(int a, int b) { return a / b; }

引数の数に寄ってどう変わるかも、書いてコンパイルすれば分かります。

int p0() { return 0; }
int p1(int a1) { return a1; }
int p2(int a1,int a2) { return a1+a2; }
int p3(int a1,int a2,int a3) { return a1+a2+a3; }
:

char,short,int,long,double,float, unsigned char, unsigned short, unsigned int, unsigned longについて、同じように足し算等を書いてみるとその他の型について理解出来るはずです。

char add(char a, char b) { return a + b; }
34
39
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
34
39