アセンブリ言語をコンパイル・実行してみる
GASとNASMで64bitプログラミングの基本をやってみた。
実行するのは基本中の基本である"hello, world"を出力するだけのプログラム。
環境
- gcc: version 5.2.0 (GCC)
- nasm: NASM version 2.11.08 compiled on Mar 24 2015
- Arch Linux x86_64: 4.2.5-1-ARCH
64bitアセンブラ基本
64bitのアセンブラにおいてはレジスタは以下のように使われる。
rax: システムコール番号
rdi: 第一引数
rsi: 第二引数
rdx: 第三引数
システムコール番号は/usr/include/asm/unistd_64.h
に書いてある。
これを今回のwriteの場合に当てはめると
rax: 0x10 (write)
rdi: 1 (stdout)
rsi: pointer of strings ("hello, world!!!")
rdx: 16 (文字列長)
GAS
GASはGNU ASのことであり、GCCでコンパイルすることができる。デフォルトではAT&T記法なので今回はIntel記法に変更してからプログラミングする。 ソースは以下の通り。
/* hello.s 64bit */ .intel_syntax noprefix .globl _start _start: // write(1, "hello, world!!!\n", 16) xor rax, rax xor rdi, rdi xor rdx, rdx inc rax inc rdi mov rsi, 0x0a212121646c726f push rsi mov rsi, 0x77202c6f6c6c6568 push rsi mov rsi, rsp push 0x10 pop rdx syscall // exit(1) push 0x3c pop rax syscall
$ gcc -nostdlib hello.s -o hello_gas $ ./hello_gas hello, world!!!
NASM
NASMはNetwide Assemblerの略であり、nasm
というコンパイラを使ってコンパイルする。
NASM自体は公式サイトからダウンロードするか各ディストリのレポジトリからインストールできる。Arch Linuxでは
$ pacman -S nasm
でインストールできる。
; hello.asm 64bit section .text global _start _start: xor rax, rax xor rdi, rdi xor rdx, rdx inc rax ; write inc rdi ; stdout mov rsi, 0x0a212121646c726f push rsi mov rsi, 0x77202c6f6c6c6568 push rsi mov rsi, rsp push 0x10 pop rdx syscall ; exit(1) push 0x3c pop rax syscall
$ nasm -f elf64 hello.asm && ld hello.o -o hello_nasm $ ./hello_nasm` hello, world!!!
両方ともこんな風にコンパイルされている。
xor rax, rax xor rdi, rdi xor rdx, rdx inc rax inc rdi movabs rsi, 0x0a212121646c726f push rsi movabs rsi, 0x77202c6f6c6c6568 push rsi push rsi mov rsi, rsp push 0x10 pop rdx syscall push 0x3c pop rax syscall
プログラムは一応NULLバイトをなくしてある。
syscallをsysenterと書いてデバッグしたのはいい思い出である。64bitではint 0x80とsysenterはサポートされていないようだ。
それぞれコメントアウトの仕方が違うのでそこは注意が必要である。
比較
GASの方はコメントの仕方がCライクであったり、pushの時に自動でBYTE変換してくれる、GCCで使えるなど、芸が細かい。
NASMの方はオブジェクトファイルが作りやすいから、CTFとかで使いやすい。
どちらを使うかは好みの問題だと思う。