Open15
「低レイヤを知りたい人のためのCコンパイラ作成入門」をAndroid(Aarch64)でやるメモ
ピン留めされたアイテム
Rui Ueyama氏の低レイヤを知りたい人のためのCコンパイラ作成入門をAndroid(Aarch64)上でやるメモ。当初はTermux、途中からUserLAnd。やる気が無くなったらやめる
参考: 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だそうで、スタティックリンクできない、らしい
しょうがないので、直Termux上は諦め、UserLAndに移行する
git管理
最初のcommit5 + 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
の存在を知らなかった。すごく便利
さすがにコードが長くなってきた。とりあえずは難なく実装できてる
コピペじゃ済まないようになってきた、よくできてる
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のスタックの取り扱いについて参考
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]!