拾い物のコンパス

まともに書いたメモ

アセンブリ言語をコンパイル・実行してみる

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とかで使いやすい。
どちらを使うかは好みの問題だと思う。