Open15

「低レイヤを知りたい人のためのCコンパイラ作成入門」をAndroid(Aarch64)でやるメモ

lsコマンドの逆アセンブル結果

~ $ aarch64-linux-android-objdump -d /bin/ls
/bin/ls:     file format elf64-littleaarch64

Disassembly of section .text:

000000000002b000 <.text>:
   2b000:       910003e0        mov     x0, sp
   2b004:       14000001        b       2b008 <__libc_init@plt-0x41288>
   2b008:       d100c3ff        sub     sp, sp, #0x30
   2b00c:       a9027bfd        stp     x29, x30, [sp, #32]
   2b010:       910083fd        add     x29, sp, #0x20
   2b014:       f0000208        adrp    x8, 6e000 <prlimit@plt+0xa40>
   2b018:       f0000209        adrp    x9, 6e000 <prlimit@plt+0xa40>
...
test1.c
int main() {
  return 42;
}

に相当するアセンブリ

test2.s
.text
    .global main
main:
    mov x0, #42
    mov x8, #93
    svc #0

mov x8, #93 というのは、syscallをexitにしてるとか。確定的な情報を調べきれず、とりあえずおまじないと思っておく

test3.c
int plus(int x, int y) { 
  return x + y;
}
int main() {
   return plus(3, 4);
}

に相当するアセンブリ

test3.s
.text
    .global main

plus:
    add x2, x2, x1
    mov x0, x2
    ret

main:
    mov x1, #3
    mov x2, #4
    bl  plus
    mov x8, #93
    svc #0

add x0, x2, x1でもいいと思うけど、本の表記に合わせた

kcc.c
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char **argv) {
  if (argc != 2) {
    fprintf(stderr, "引数の個数が正しくありません\n");
    return 1;
  }

  printf(".text\n");
  printf("  .globl main\n");
  printf("main:\n");
  printf("  mov x0, #%d\n", atoi(argv[1]));
  printf("  mov x8, #93\n");
  printf("  svc #0\n");
  return 0;
}

名前は kcc とした。Kagamoc C Compilerの略

動く。スマホでポチポチ大変だけど、動くとすごく楽しい

Makefileを書き、make をしたところ、libcが無いとエラー。どうやら、TermuxのlibcはGNUではなくBionicだそうで、スタティックリンクできない、らしい

https://github.com/termux/termux-packages/issues/2798

しょうがないので、直Termux上は諦め、UserLAndに移行する

5 + 20 - 4 のアセンブリ

tmp.s
.text
    .global main
main:
    mov x0, #5
    add x0, x0, #20
    sub x0, x0, #4
    mov x8, #93
    svc #0

足し算と引き算を追加

kcc.c
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char **argv) {
  if (argc != 2) {
    fprintf(stderr, "引数の個数が正しくありません\n");
    return 1;
  }

  char *p = argv[1];

  printf(".text\n");
  printf("  .globl main\n");
  printf("main:\n");
  printf("  mov x0, #%d\n", strtol(p, &p, 10));

  while (*p) {
    if (*p == '+') {
      p++;
      printf("  add x0, x0, #%d\n", strtol(p, &p, 10));
      continue;
    }

    if (*p == '-') {
      p++;
      printf("  sub x0, x0, #%d\n", strtol(p, &p, 10));
      continue;
    }

    fprintf(stderr, "予期しないエラーです: '%c'\n", *p);
    return 1;
  }

  printf("  mov x8, #93\n");
  printf("  svc #0\n");
  return 0;
}

恥ずかしながら strtol の存在を知らなかった。すごく便利

https://github.com/kagamoc/kcc/commit/5fc68ac0df92a177f2650d2ca4766e61e07f3f29
Hidden comment

Aarch64でスタックにpush/popする命令は、8バイトの2つのレジスタ x0 x1 に対して、

// push {x0, x1}
stp x0, x1, [sp, #-16]!
// pop {x0, x1}
ldp x0, x1, [sp], #16

または、レジスタ x0 を16バイトとして、

// push x0
str x0, [sp, #-16]!
// pop x0
ldr x0, [sp], #16

という二通りある。1つのレジスタを8バイトとしてpush/popできない理由は、スタティックポインタ sp は必ず16で割れないといけないかららしい。

Aarch64のスタックの取り扱いについて参考

https://www.mztn.org/dragon/arm6405str.html#ldp
https://community.arm.com/developer/ip-products/processors/b/processors-ip-blog/posts/using-the-stack-in-aarch64-implementing-push-and-pop

1+2を、Aarch64をスタックマシンと見立ててコンパイルする。

コード未検証

mov x0, #1
mov x1, #2

// 左辺と右辺をプッシュ
str x0, [sp, #-16]!
str x1, [sp, #-16]!

// 左辺と右辺をx0とx1にポップして足す
ldr x0, [sp], #16
ldr x1, [sp], #16
add x0, x0, x0

// 足した結果をスタックにプッシュ
str x0, [sp, #-16]!
作成者以外のコメントは許可されていません