cの関数とassembler

#include <stdio.h>

void hello(void){
    printf("hello\n");
}

int main(void) {

    hello();
    return 0;
}
|hello| PROC
|$LN3|
        stp         fp,lr,[sp,#-0x10]!
        mov         fp,sp
        adrp        x8,|$SG4980|
        add         x0,x8,|$SG4980|
        bl          printf
        ldp         fp,lr,[sp],#0x10
        ret

        ENDP  ; |hello|

|main|  PROC
|$LN3|
        stp         fp,lr,[sp,#-0x10]!
        mov         fp,sp
        bl          hello
        mov         w0,#0
        ldp         fp,lr,[sp],#0x10
        ret

        ENDP  ; |main|

関数の場合は、hello()はassembler上ではmainと分離され、bl helloで呼び出すようになっている。すなわち、関数ごとにassembleすれば良いのね。なるほど

cの配列とassembler

#include <stdio.h>

int main(void) {

    int nums[10];
    nums[2] = 15;
    printf("%d\n", nums[2]);
    return 0;
}
|main|  PROC
|$LN3|
        stp         fp,lr,[sp,#-0x10]!
        mov         fp,sp
        bl          __security_push_cookie
        sub         sp,sp,#0x20
        mov         x8,#4
        mov         x9,#2
        mul         x9,x8,x9
        mov         x8,sp
        add         x9,x8,x9
        mov         w8,#0xF
        str         w8,[x9]
        mov         x8,#4
        mov         x9,#2
        mul         x9,x8,x9
        mov         x8,sp
        add         x8,x8,x9
        ldr         w1,[x8]
        adrp        x8,|$SG4981|
        add         x0,x8,|$SG4981|
        bl          printf
        mov         w0,#0
        add         sp,sp,#0x20
        bl          __security_pop_cookie
        ldp         fp,lr,[sp],#0x10
        ret

        ENDP  ; |main|

値を変えながらassembleしてみると、mov x8,#4のところはnum[2], [10], num[15]でも変わらないのね。mul x9, x8、x9の挙動がいまいちわからんが

cの構造体とassembler

#include <stdio.h>

typedef char String[1024];

int main(void) {

    typedef struct {
        String name;
        int hp;
        int attack;
    } Monster;

    Monster seiryu = {"青竜", 100, 15};
    printf(seiryu.name, seiryu.hp, seiryu.attack);
    return 0;
}
|$LN3|
        stp         fp,lr,[sp,#-0x10]!
        mov         fp,sp
        bl          __security_push_cookie
        sub         sp,sp,#0x410
        add         x8,sp,#8
        str         x8,[sp]
        ldr         x9,[sp]
        adrp        x8,|$SG4988|
        add         x8,x8,|$SG4988|
        ldr         w10,[x8]
        str         w10,[x9]
        ldrsh       w10,[x8,#4]
        strh        w10,[x9,#4]
        ldrsb       w8,[x8,#6]
        strb        w8,[x9,#6]
        add         x0,sp,#0xF
        mov         x2,#0x3F9
        mov         w1,#0
        bl          memset
        mov         w8,#0x64
        str         w8,[sp,#0x408]
        mov         w8,#0xF
        str         w8,[sp,#0x40C]
        mov         w0,#0
        add         sp,sp,#0x410
        bl          __security_pop_cookie
        ldp         fp,lr,[sp],#0x10
        ret

        ENDP  ; |main|

もう少しシンプルにする

int main(void) {

    typedef struct {
        String name;
    } Monster;

    Monster seiryu = {"青竜"};
    // printf(seiryu.name, seiryu.hp, seiryu.attack);
    return 0;
}
|main|  PROC
|$LN3|
        stp         fp,lr,[sp,#-0x10]!
        mov         fp,sp
        bl          __security_push_cookie
        sub         sp,sp,#0x400
        add         x8,sp,#8
        str         x8,[sp]
        ldr         x9,[sp]
        adrp        x8,|$SG4986|
        add         x8,x8,|$SG4986|
        ldr         w10,[x8]
        str         w10,[x9]
        ldrsh       w10,[x8,#4]
        strh        w10,[x9,#4]
        ldrsb       w8,[x8,#6]
        strb        w8,[x9,#6]
        add         x0,sp,#0xF
        mov         x2,#0x3F9
        mov         w1,#0
        bl          memset
        mov         w0,#0
        add         sp,sp,#0x400
        bl          __security_pop_cookie
        ldp         fp,lr,[sp],#0x10
        ret

x8, x9に入れたものをw10にstr, ldr
ldrshは16ビットにstr

構造体定義だと複雑なことをするのね

cのfor文、while文とassembler

### for

int main(void) {

    for(int i = 0; i < 5; i++) {
        printf("hello\n");
    }

    return 0;
}
        stp         fp,lr,[sp,#-0x20]!
        mov         fp,sp
        mov         w8,#0
        str         w8,[sp,#0x10]
        b           |$LN4@main|
|$LN2@main|
        ldr         w8,[sp,#0x10]
        add         w8,w8,#1
        str         w8,[sp,#0x10]
|$LN4@main|
        ldr         w8,[sp,#0x10]
        cmp         w8,#5
        bge         |$LN3@main|
        adrp        x8,|$SG4984|
        add         x0,x8,|$SG4984|
        bl          printf
        b           |$LN2@main|
|$LN3@main|
        mov         w0,#0
        ldp         fp,lr,[sp],#0x20
        ret

for文は、その記述の通り、0でmov, strして、その後ad w8 w8 #1として、cmpで5より小さいか判定してprintfしていますね。c言語だとi++より先にi<10と書いているが、assemblerではi++が上に来るのが興味深い ### while [code] #include <stdio.h> int main(void) { int count = 0; while (count < 5) { printf("hello\n"); count++; } return 0; } [/code] [code] |main| PROC |$LN5| stp fp,lr,[sp,#-0x20]! mov fp,sp mov w8,#0 str w8,[sp,#0x10] |$LN2@main| ldr w8,[sp,#0x10] cmp w8,#5 bge |$LN3@main| adrp x8,|$SG4983| add x0,x8,|$SG4983| bl printf ldr w8,[sp,#0x10] add w8,w8,#1 str w8,[sp,#0x10] b |$LN2@main| |$LN3@main| mov w0,#0 ldp fp,lr,[sp],#0x20 ret [/code] whileの場合も内部処理的にはcmpして、そのまま処理するか、bgeでjumpするかを記述しており、forと似たような処理となっている。 compilerを作る際には、ASTで条件式に応じてラベル出力まで出力しないといけないのか。中間言語生成のところが結構考えないといけないのかもしれない。 アセンブラ自体は単純で、高級言語の予約語がそれぞれ機能的になっていることがわかる。

cのif文とassembler

#include <stdio.h>
#include <stdbool.h>

int main(void) {

    bool tenki = true;

    if(tenki == true) 
        printf("晴です\n");
    else 
        printf("雨です\n");
    
    return 0;
}
|main|  PROC
|$LN5|
        stp         fp,lr,[sp,#-0x20]!
        mov         fp,sp
        mov         w8,#1
        strb        w8,[sp,#0x10]
        ldrb        w8,[sp,#0x10]
        mov         w8,w8
        cmp         w8,#1
        bne         |$LN2@main|
        adrp        x8,|$SG4984|
        add         x0,x8,|$SG4984|
        bl          printf
        b           |$LN3@main|
|$LN2@main|
        adrp        x8,|$SG4985|
        add         x0,x8,|$SG4985|
        bl          printf
|$LN3@main|
        mov         w0,#0
        ldp         fp,lr,[sp],#0x20
        ret

cmp : レジスタからレジスタ、またはレジスタから定数の減算を行って、演算結果に対応した条件フラグを設定
-> compareか?

bne: bはジャンプなので、neで異なる場合は$LN2@mainにjumpとなっていることがわかりますね。
条件分岐でelseの場合はニーモニックにlabelをつけて、そこへjumpするように出力していることがわかります。

cの文字列とassembler

#include <stdio.h>

typedef char String[1024];

int main(void) {

    String name = "ドラゴン";
    printf("私は%sです\n", name);
    return 0;
}
|main|  PROC
|$LN3|
        stp         fp,lr,[sp,#-0x10]!
        mov         fp,sp
        bl          __security_push_cookie
        sub         sp,sp,#0x400
        add         x8,sp,#8
        str         x8,[sp]
        ldr         x9,[sp]
        adrp        x8,|$SG4982|
        add         x8,x8,|$SG4982|
        ldr         x10,[x8]
        str         x10,[x9]
        ldr         w10,[x8,#8]
        str         w10,[x9,#8]
        ldrsb       w8,[x8,#0xC]
        strb        w8,[x9,#0xC]
        add         x0,sp,#0x15
        mov         x2,#0x3F3
        mov         w1,#0
        bl          memset
        add         x1,sp,#8
        adrp        x8,|$SG4983|
        add         x0,x8,|$SG4983|
        bl          printf
        mov         w0,#0
        add         sp,sp,#0x400
        bl          __security_pop_cookie
        ldp         fp,lr,[sp],#0x10
        ret

なぜ64ビットのx10と32ビットのw8を使用しているか不明
blはサブルーチン呼び出し

代入とassembler

c

#include <stdio.h>
// mainブロック
int main(void) {

    int age;
    age = 20;
    printf("私は%d歳です\n", age);
    return 0;
}

assembler

|main|  PROC
|$LN3|
        stp         fp,lr,[sp,#-0x20]!
        mov         fp,sp
        mov         w8,#0x14
        str         w8,[sp,#0x10]
        ldr         w1,[sp,#0x10]
        adrp        x8,|$SG4981|
        add         x0,x8,|$SG4981|
        bl          printf
        mov         w0,#0
        ldp         fp,lr,[sp],#0x20
        ret

str
ストア命令 (STR) はレジスタに格納されている値をメモリに書き込み

ageをdoubleにするとこうなる
ラベルでldrしている

|$LN4|
        stp         fp,lr,[sp,#-0x20]!
        mov         fp,sp
        ldr         d16,|$LN3@main|
        str         d16,[sp,#0x10]
        ldr         x1,[sp,#0x10]
        adrp        x8,|$SG4981|
        add         x0,x8,|$SG4981|
        bl          printf
        mov         w0,#0
        ldp         fp,lr,[sp],#0x20
        ret
        nop
|$LN3@main|
        DCFD         20.5

DCFD and DCFDU: The DCFD directive allocates memory for word-aligned double-precision floating-point numbers, and defines the initial runtime contents of the memory. DCFDU is the same, except that the memory alignment is arbitrary.
https://developer.arm.com/documentation/dui0801/l/Directives-Reference

型によってassemblerのオペランドが異なっていることがわかる

printfとassembler

c

#include <stdio.h>
int main(void) {

    printf("テキスト1");
    printf("テキスト2");
    return 0;
}

assembler

|main|  PROC
|$LN3|
        stp         fp,lr,[sp,#-0x10]!
        mov         fp,sp
        adrp        x8,|$SG4980|
        add         x0,x8,|$SG4980|
        bl          printf
        adrp        x8,|$SG4981|
        add         x0,x8,|$SG4981|
        bl          printf
        mov         w0,#0
        ldp         fp,lr,[sp],#0x10
        ret

STP
Store Pair of Registers calculates an address from a base register value and an immediate offset, and stores two 32-bit words or two 64-bit doublewords to the calculated address, from two registers. For information about memory accesses, see Load/Store addressing modes.
命令は 2つのレジスタ (Xt1、Xt2) の内容を、メモリアドレスを保持するレジスタ (Xn) にオフセットの値を加えたアドレスを先頭とするメモリに書き込み、レジスタをスタックに退避する

adrp
プログラムカウンタ (PC) に ±4GB の範囲のオフセットを加えたアドレスを 4KBを単位としたページアドレスに変換して、指定したレジスタ(Xd) に書き込みます。 ADRP命令のオフセットの値には immHi の19ビットと immL の2ビットを加えた21ビットの範囲の整数を 4096倍(12bit) した整数が指定できるため、PCの値±4GB の範囲の 4KB を単位としたページアドレスを指定できます。

bl
ンク付分岐命令の BL は、プログラムカウンタの値にオフセットを加えた メモリアドレスに分岐(ジャンプ)します。 分岐先の命令に付けたラベルを分岐先として指定しますが、 アセンブラが「BL」命令のアドレスとラベルのアドレスから、 プログラムカウンタ相対オフセットを計算します。 オフセットの値は内部的には26ビットの符号付整数ですが、 プログラムカウンタの値は常に4の倍数であることを利用するため、 分岐先は ±128MB の範囲が可能

lr リンクレジスタ
L x30レジスタ 関数コールした時の戻り番地を記憶
fp フレームポインタ
L x29レジスター
プログラムカウンター(PC)
L 次に実行される命令のアドレスを保持するレジスタです
スタックポインター(SP)
L スタックのトップを指すレジスタです
ステータスレジスター(NZCV)
L 直前の演算の結果に関するフラグを保持するレジスタです

x86アセンブリ

アセンブリを学ぶメリット

CPU を支配できる.
コンパイラより高速なコードを書ける
文法が簡単である.
バイナリ (実行できるプログラム) が小さい.
まったく新しい OS を作るには必須の知識である.
ウイルスに負けない体力を養える.
コンパイラやインタプリタの作者になれる.
Linux カーネルの機能を理解できる.
C のポインタが簡単に理解できる.

OS(ブートローダー、カーネル、デーモン、シェル、デスクトップマネージャ、アプリケーション)、カーネル、インタプリタ、コンパイラ周りを理解するには必須のスキルだということがわかる。

hello.asm

  ;------------------------------------
  ; hello.asm
  ;------------------------------------
  section .text
  global _start

  msg             db   'hello, world', 0x0A
  msglen          equ  $ - msg

  _start:
                  mov    ecx, msg       ; 文字列の場所を指定
                  mov    edx, msglen    ; 文字列の長さを設定
                  mov    eax, 4         ; 出力のシステムコール
                  mov    ebx, 1         ; 標準出力を指定
                  int    0x80           ; システムコール実行
                  mov    eax, 1         ; 終了のシステムコール
                  mov    ebx, 0         ; 正常終了の 0 に設定
                  int    0x80           ;  システムコール実行
  ;------------------------------------

プログラムを終了するサブルーチン

Exit:
               mov    eax, 1         ; sys_exit
               mov    ebx, 0         ; exit with code 0
               int    0x80

異常終了

;------------------------------------
; exit with ebx
ExitN:
               mov    ebx, eax       ; exit with code ebx
               mov    eax, 1         ; sys_exit
               int    0x80

writeのシステムコール

;------------------------------------
; print string to stdout
; eax : top address
; edx : no of put char
OutString:
               pusha
               mov    ecx, eax
               mov    eax, SYS_write
               mov    ebx, 1         ; to stdout
               int    0x80
               popa
               ret
;------------------------------------
; get length of asciiz string
; eax : top address
; eax : return length
StrLen:
               push   ecx
               push   edi
               mov    edi, eax
               push   eax
               xor    eax, eax
               mov    ecx, 0xFFFF  ; no more than 65k chars.
         repne scasb
               pop    ecx
               sub    edi, ecx
               mov    eax, edi
               pop    edi
               pop    ecx
               ret

;------------------------------------
; print asciiz string
; eax : pointer to string 
OutAsciiZ:
               push   edx
               push   eax
               call   StrLen
               mov    edx, eax
               pop    eax
               call   OutString
               pop    edx
               ret