Windows 10(64bit版)のClangでアセンブラコードを書いてみましたという話です。
Clangにおけるアセンブラコード
GCC互換です。つまりclangにGASのソースコードを渡すとコンパイルすることができます。また、clangにclang -S hoge.c
などと書いてコンパイルするとclangでディスアセンブルしたコード(.s)が得られます。つまりこいつに適当なCの関数を与えてやってGASの書き方を学べばClang用アセンブラコードを書くことができます。
アセンブラプログラミングはそんなに楽じゃない
ラベルの値をレジスタに代入することができません。ラベルの値はアドレス値ですが、アドレス値はプログラムがロードされるときに決定されるのでラベルのアドレス値を静的領域に置いてやる必要があります。レジスタにラベルの値を読ませるときは、代わりに静的領域のデータを読んでやります。(2019.1.24 追記 RIPレジスタ(プログラムカウンタ)の間接アドレッシングを使えば簡単に実現できます。これは@fujitanozomuさんの再帰的な関数hoge
のディスアセンブルによって示されていますが私はすぐにそのことに気が付くことができませんでした)ラベル同士の引き算ができない。なぜ!?すれよ!!!なぜか分かりませんがGASだとできないようです(NASMだとできるのかも…)。ラベル同士の引き算をしてくれると64bitのアドレス値を16bit以内に圧縮することができます。今回はそれを試みましたが失敗しました。16bitイミディエイトへの代入はできないが16bitの静的領域への代入は可能 (2019.1.23 @fujitanozomuさんのご指摘により修正)
作成したコード
作成したコードを示します。
#include <stdio.h>
extern void proc1(void); // proc1.sで定義されている
extern long long int debug;
int main() {
printf("start program\n");
proc1();
printf("end program\n");
return 0;
}
#
# proc1.s
#
.text
.intel_syntax noprefix
# proc1
.globl proc1
.p2align 4, 0x90
proc1:
.seh_proc proc1
push rax
push rbx
sub rsp, 40
.seh_stackalloc 40
.seh_endprologue
xor rax, rax
mov ax, word ptr [rip + toOffset]
add rax, qword ptr [rip + fromAddress]
jmp rax
from:
.p2align 8, 0x00 # 失敗する命令列
to:
add rsp, 40
pop rbx
pop rax
ret
.seh_handlerdata
.text
.seh_endproc
.bss
.data
fromAddress:
.quad from
toOffset:
.word to - from
ビルドコマンド
clang test1.c proc1.s -o test1.exe
実行結果
start program
end program
より簡約したバージョン (2019.1.24 追記)
下記コメントのhoge
関数を-masm=intel
オプションでディスアセンブルするとlea [rip + hoge]
が得られます。従ってproc1.s
のコードはproc1b.s
のように簡約することができます。
#
# proc1b.s
#
.text
.intel_syntax noprefix
# proc1
.globl proc1
.p2align 4, 0x90
proc1:
.seh_proc proc1
push rax
sub rsp, 40
.seh_stackalloc 40
.seh_endprologue
lea rax, [rip + to]
jmp rax
.p2align 8, 0x00 # 失敗する命令列
to:
add rsp, 40
pop rax
ret
.seh_handlerdata
.text
.seh_endproc
.bss
.data
静的領域ではラベル同士の演算を行うことはできるのですが、オペランドにではできないようです。たとえばlea rax, [rip + to - from]
はエラーになります。