GAS(AT&Tアセンブリ)でfizzbuzzを実装する
GAS(AT&Tアセンブリ)でfizzbuzzを実装するために、実際に書いたコードとつまづいたポインタを解説していく。
開発環境
- gcc (Alpine 5.3.0) 5.3.0
- alpine 3.4 (Linux 64bit)
上記の環境を構築するためにdockerを使用している。
実際に書いたコード
まずは実際に書いたコードを下記に示す。(長いけど)
gcc -o fizzbuzz fizzbuzz.s
でコンパイラして./fizzbuzz
で実行すれば、1から60までの整数でfizzbuzzを実行してくれる。
.set LOOP_END, 60
.set LOOP_START, 1
.set FIZZBUZZ_NUM, 15
.set FIZZ_NUM, 3
.set BUZZ_NUM, 5
.set EXIT, 60
.set EXIT_CODE, 1
.data
fizz:
.string "Fizz"
buzz:
.string "Buzz"
fizzbuzz:
.string "FizzBuzz"
num_str:
.string "%d\n"
end_string:
.string "\nComplete FizzBuzz!!"
.text
.globl main
fizz_write:
leaq fizz(%rip), %rdi
call puts@PLT
jmp loop_inc
buzz_write:
leaq buzz(%rip), %rdi
call puts@PLT
jmp loop_inc
fizzbuzz_write:
leaq fizzbuzz(%rip), %rdi
call puts@PLT
jmp loop_inc
num_write:
leaq num_str(%rip), %rdi
movq %r13, %rsi
xor %rax, %rax
call printf@PLT
jmp loop_inc
loop_process:
cmpq %r13, %r12
jl end_process
movq $0, %rdx
movq %r13, %rax
movq $FIZZBUZZ_NUM, %r14
divq %r14
cmpq $0, %rdx
je fizzbuzz_write
movq $0, %rdx
movq %r13, %rax
movq $FIZZ_NUM, %r14
divq %r14
cmpq $0, %rdx
je fizz_write
movq $0, %rdx
movq %r13, %rax
movq $BUZZ_NUM, %r14
divq %r14
cmpq $0, %rdx
je buzz_write
movq $0, %rdx
movq $0, %rax
jmp num_write
loop_inc:
inc %r13
jmp loop_process
main:
movq $LOOP_START, %r13 /* r13をloopカウントにする */
movq $LOOP_END, %r12
jmp loop_process
end_process:
leaq end_string(%rip), %rdi
call puts@PLT
movq $EXIT, %rax /* exit */
movq $EXIT_CODE, %rdi
syscall
ret
アセンブリでprintfを使う時のポイント
C言語では頻繁に使うprintf
だが、アセンブリで使う場合はC言語よりも注意する点が多くある。
- printfを呼び出す時は
call printf
ではなく、call printf@PLT
とする %r11
レジスタに値を格納しない
アセンブリでC言語の標準ライブラリ(printfなど)を使う場合は、call printf@PLT
という風に末尾に@PLT
をつけること。
PLTとは、procedure linkage tableの略で動的リンクを実現するためのもの。
通常、アセンブリでcall printf
の命令を書いた時は、最終的に「メモリ番号100にジャンプしてね」という風に具体的なメモリ番号を示した機械語に直される。そのためコードを変更した時にメモリ位置がずれるため、全てのコードをコンパイルし直す必要がある。
一方、PLTを使った動的リンクでは、コンパイル時に「どのメモリ番号にジャンプするか」を決めるのではなく、実行時に linkage tableを調べて、「call printfの時はメモリ番号何番目に飛べば良い」というのをチェックするため、仮にコードを書き直してもコンパイルし直す必要がなくなるメリットがある。
参考:assembly - What does @plt mean here? - Stack Overflow
また、64bitのCPUには「システム関数を読んだ時にr11レジスタ
の値が保持されない性質がある。printf
やputs
などのC言語の標準ライブラリ関数は内部でシステム関数を呼んでいるので、r11レジスタの扱いには注意すること。