* ARM64の勉強#1 -(by [[K]], 2020.08.23) ** (0) -格安スマホや適当なAndroidタブレットに、Termuxをインストールして、clangを使っていろいろ実験しました。ソースコードはnanoで入力しています。 ** (1) #include <stdio.h> #include <stdint.h> int main() { printf("%d\n", (int) sizeof (int32_t)); return 0;} } -4が表示されました。よしよし。 ** (2) #include <stdio.h> #include <stdint.h> uint32_t t[] = { 0xd2800000 | 123 << 5, 0xd65f03c0 }; // x0=123; ret; int main() { int (*fnc)() = (int (*)()) t; int i = fnc(); printf("i=%d\n", i); return 0; } -実行したら、「Segmentation fault」になってしまいました。まあ当然か・・・。 -上記の機械語については以下を参照しました。 --x0レジスタに関数の戻り値を設定すればよさそうだということは、 https://docs.microsoft.com/ja-jp/cpp/build/arm64-windows-abi-conventions?view=vs-2019 の「戻り値」のところを見ました。これはARM64-LinuxではなくてWindowsの仕様ですが、まあ戻り値くらいは同じ仕様だろうと思って、これでやってみることにしました。(3)ではこれでうまくいったので、予想は当たりました。 --x0への代入の機械語は、 https://www.mztn.org/dragon/arm6405str.html#mov の「MOVZ」のところを参考にしました。 --retの機械語は、 https://www.mztn.org/dragon/arm6408cond.html#ret の「RET」のところを参考にしました。 ** (3) #include <stdio.h> #include <stdint.h> #include <unistd.h> #include <stdlib.h> #include <sys/mman.h> int main() { uint32_t *code; posix_memalign((void **) &code, sysconf(_SC_PAGESIZE), 8); mprotect((void *) code, 8, PROT_READ | PROT_WRITE | PROT_EXEC); code[0] = 0xd2800000 | 123 << 5; // x0=123; code[1] = 0xd65f03c0; // ret; int i = ((int (*)()) code)(); printf("i=%d\n", i); return 0; } -実行したら、「i=123」と表示されました。大成功です! ** (4) //-todo: putcharではなく、myPutcharにしたほうがよさそう。そうすればマクロになっていてもうまくいく。 //-nopはd503201f #include <stdio.h> #include <stdint.h> #include <unistd.h> #include <stdlib.h> #include <sys/mman.h> int main() { uint32_t *code; posix_memalign((void **) &code, sysconf(_SC_PAGESIZE), 36); mprotect((void *) code, 36, PROT_READ | PROT_WRITE | PROT_EXEC); code[0] = 0xa9800000 | 30 | 19 << 10 | 31 << 5 | ((-16/8)&127) << 15; // x19, x30 をpush. code[1] = 0xaa000000 | 1 << 16 | 31 << 10 | 19; // x19 = x1; // ORR x19,xzr,x1 (MOV) code[2] = 0xd2800000 | 'A' << 5; // x0='A'; code[3] = 0xd63f0000 | 19 << 5; // BLR x19; code[4] = 0xd2800000 | '\n' << 5; // x0='\n'; code[5] = 0xd63f0000 | 19 << 5; // BLR x19; code[6] = 0xa8c00000 | 30 | 19 << 10 | 31 << 5 | ((+16/8)&127) << 15; // x19, x30 をpop. code[7] = 0xd2800000 | 123 << 5; // x0=123; code[8] = 0xd65f03c0; // ret; printf("%x\n", code[0]); // この行をなくすとセグメントフォールトするようになる。 int i = ((int (*)(void *)) code)(putchar); printf("i=%d\n", i); return 0; } -code上のプログラムはなぜかx1で引数を受けなければいけないし、でもcodeから呼び出すときはx0に入れなければいけない。 -なんかclangが作ったバイナリを見ていると、呼び出し処理のコード生成でミスをしているように見える。 -clangが悪いのではなく、私のプログラムが悪いのかもしれないけど・・・。 --(註)後日自分のミスだとわかりました。(4)のつづきを参照。 -もっと本番のJITに近い方法で関数ポインタを渡してみて、それで状況を整理したい。 ---- -いろいろ苦戦したメモ -最初のバージョンはセグメントフォールトで失敗。 -試行錯誤したところ、関数呼び出しさえしなければなんとか完走することを確認。 -いろいろ確認したところ、どうもハンドアセンブルは間違っていなくて、関数呼び出し規則があっていないのかもしれないと思い当たる。 -https://qiita.com/hotpepsi/items/bd1f496411a2df74b704 を見ると引数はx0~x7を経由して受け渡しするように思えるのだけど、適当なC言語プログラムをコンパイルしてobjdumpで逆アセンブルしてみると、x1~で受け渡しているように見える(最適化のせいで代入順序が入れ替わっていてややこしいのだけど)。 -ということでx0は使わないでx1からにしてみたら、セグメントフォールトにならずに済むようになった!これは大きな前進。しかしまだ狙った文字が表示できていない。 -x0で渡せばちゃんと表示されることが分かった。 ** (5) #include <stdio.h> #include <stdint.h> #include <unistd.h> #include <stdlib.h> #include <sys/mman.h> int main() { uint32_t *code; void *vp[4]; vp[0] = putchar; posix_memalign((void **) &code, sysconf(_SC_PAGESIZE), 36); mprotect((void *) code, 36, PROT_READ | PROT_WRITE | PROT_EXEC); code[0] = 0xa9800000 | 30 | 19 << 10 | 31 << 5 | ((-16/8)&127) << 15; // x19, x30 をpush. code[1] = 0xf9400000 | 0 << 10 | 0 << 5 | 19; // x19 = [x0+0*8]; code[2] = 0xd2800000 | 'A' << 5; // x0='A'; code[3] = 0xd63f0000 | 19 << 5; // BLR x19; code[4] = 0xd2800000 | '\n' << 5; // x0='\n'; code[5] = 0xd63f0000 | 19 << 5; // BLR x19; code[6] = 0xa8c00000 | 30 | 19 << 10 | 31 << 5 | ((+16/8)&127) << 15; // x19, x30 をpop. code[7] = 0xd2800000 | 123 << 5; // x0=123; code[8] = 0xd65f03c0; // ret; int i = ((int (*)(void **)) code)(vp); printf("i=%d\n", i); return 0; } -これで期待通りに素直に動くことが分かった。たぶん関数ポインタを引数に渡すと内部で混乱してしまうのだろう。 ** (6) #include <stdio.h> #include <stdint.h> #include <unistd.h> #include <stdlib.h> #include <sys/mman.h> int main() { uint32_t *code; void *vp[4]; vp[0] = putchar; posix_memalign((void **) &code, sysconf(_SC_PAGESIZE), 60); mprotect((void *) code, 60, PROT_READ | PROT_WRITE | PROT_EXEC); code[ 0] = 0xa9800000 | 31 << 5 | ((-16/8)&127) << 15 | 30 | 19 << 10; // push(x30, x19); code[ 1] = 0xa9800000 | 31 << 5 | ((-16/8)&127) << 15 | 20 | 21 << 10; // push(x20, x21); code[ 2] = 0xf9400000 | 19 | 0 << 5 | 0 << 10; // x19 = [x0+0*8]; code[ 3] = 0xd2800000 | 20 | 0x20 << 5; // x20 = 0x20; code[ 4] = 0xaa000000 | 31 << 16 | 0 | 20 << 5; // x0 = x20; code[ 5] = 0xd63f0000 | 19 << 5; // BLR(x19); code[ 6] = 0x91000000 | 20 | 20 << 5 | 1 <<10; // x20 = x20 + 1; code[ 7] = 0xf1000000 | 31 | 20 << 5 | 0x7f << 10; // CMP(x20, 0x7f); code[ 8] = 0x54000000 | 0x01 | ((-4)&524287) << 5; // b.ne $-4 code[ 9] = 0xd2800000 | 0 | 0xa << 5; // x0 = 0xa; code[10] = 0xd63f0000 | 19 << 5; // BLR(x19); code[11] = 0xa8c00000 | 31 << 5 | (+16/8)&127) << 15 | 20 | 21 << 10; // pop(x20, x21); code[12] = 0xa8c00000 | 31 << 5 | (+16/8)&127) << 15 | 30 | 19 << 10; // pop(x30, x19); code[13] = 0xd2800000 | 0 | 123 << 5; // x0=123; code[14] = 0xd65f03c0; // ret; int i = ((int (*)(void **)) code)(vp); printf("i=%d\n", i); return 0; } -これで「 !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~」が出力できる。 -成功! ** (4)のつづき code[1] = 0xaa000000 | 1 << 16 | 31 << 10 | 19; // x19 = x1; // ORR x19,xzr,x1 (MOV) -これは機械語が変だぞ。なんかいじっているうちにおかしくなったのかも。 -っていうか、それだからうまくいかなかっただけなんじゃないかな? -ということで直してやり直してみる。 #include <stdio.h> #include <stdint.h> #include <unistd.h> #include <stdlib.h> #include <sys/mman.h> int main() { uint32_t *code; posix_memalign((void **) &code, sysconf(_SC_PAGESIZE), 36); mprotect((void *) code, 36, PROT_READ | PROT_WRITE | PROT_EXEC); code[0] = 0xa9800000 | 30 | 19 << 10 | 31 << 5 | ((-16/8)&127) << 15; // x19, x30 をpush. code[1] = 0xaa000000 | 31 << 16 | 19 | 0 << 5; // x19 = x0; // ORR x19,x0,xzr (MOV) code[2] = 0xd2800000 | 'A' << 5; // x0='A'; code[3] = 0xd63f0000 | 19 << 5; // BLR x19; code[4] = 0xd2800000 | '\n' << 5; // x0='\n'; code[5] = 0xd63f0000 | 19 << 5; // BLR x19; code[6] = 0xa8c00000 | 30 | 19 << 10 | 31 << 5 | ((+16/8)&127) << 15; // x19, x30 をpop. code[7] = 0xd2800000 | 123 << 5; // x0=123; code[8] = 0xd65f03c0; // ret; int i = ((int (*)(void *)) code)(putchar); printf("i=%d\n", i); return 0; } -うまくいった! ** (9) -ここまでのまとめ |MOVZ|0xd2800000 + imm16 << 5 + reg|(2)|x0~x30にimm16を代入| |RET|0xd65f03c0|(2)|PC=x30| |MOV|0xaa000000+31<<10 + src << 16 + dst|(4)|dst=src| |push|0xa9800000+31<<5+((-16/8)&127)<<15 + reg0 + reg1 << 10|(4)|レジスタペアのストア| |pop |0xa8c00000+31<<5+((+16/8)&127)<<15 + reg0 + reg1 << 10|(4)|レジスタペアのロード| |BLR |0xd63f0000 + reg << 5|(4)|レジスタで指定する関数呼び出し| |NOP |0xd503201f||NOP| |LDR |0xf9400000 + disp/8 << 10 + base << 5 + dst|(5)|メモリからのロード| * こめんと欄 #comment