アセンブリ言語について少し学ぶ

書き出し

アセンブリ言語について少し学ぶ。 メモ程度のもの。

本文

コマンド

# nasmは Netwide Assemblerの略称
nasm -f<format> <in> -o <out>
#optin
#-f : format
# リンク用のGNUコマンド
ld -o <out> <in>

メモ

hello.asm

global _start

section .data
message:db 'hello, world!',10

section .text
_start:
    mov rax, 1
    mov rdi, 1
    mov rsi, message
    mov rdx, 15
    syscall
    mov rax, 60
    xor rdi, rdi
    syscall

この場合、.data(global 変数)db(data byte)の変数mesageを配置している。 また命令セクションである.textに_start命令を配置し、syscallの呼び出し準備を行う。

syscall呼び出し時のレジスタ内部

今回自分の中で確認するために、はじめのwriteのコール時のシステムコールを元に解釈していく。

レジスタ 意味 内容
rax 1 system call system call number
rdi 1 第一引数 discription(書き込み先)
rsi message 第二引数 文字列の先頭アドレス
rdx 14 第三引数 書き込むバイト数

C言語で表すとこのような表示になる。

#include <unistd.h>
ssize_t write(int fd , const void * buf , size_t count );

syscall実行時にraxからwriteが実行され、引数として先頭からrdi,rsi,rdxが配置される。

変数の定義

表記 意味
db data byte|1byte
dw data word|2byte
dd data double word|4byte
dq data quad word|8byte

section .data
example1:db 1, 1, 2, 3, 5, 7
example2:times 999 db 1
example3:dw 999

times はn回文cmdを繰り返すことを意味する。

変数(data)は、どのセクションの内側でも作ることができる。 CPUから見ればデータも一つの命令となるので解釈される。

ニーモニック

実際にelfファイルにし、確認してみようと思う。

次のような内容になっており。しっかりと.data.textにセクションが割り当てられていることがわかる。

$ objdump -D hello
hello:     ファイル形式 elf64-x86-64


セクション .text の逆アセンブル:

00000000004000b0 <_start>:
  4000b0:       b8 01 00 00 00          mov    $0x1,%eax
  4000b5:       bf 01 00 00 00          mov    $0x1,%edi
  4000ba:       48 be d8 00 60 00 00    movabs $0x6000d8,%rsi
  4000c1:       00 00 00
  4000c4:       ba 0f 00 00 00          mov    $0xf,%edx
  4000c9:       0f 05                   syscall
  4000cb:       b8 3c 00 00 00          mov    $0x3c,%eax
  4000d0:       48 31 ff                xor    %rdi,%rdi
  4000d3:       0f 05                   syscall

セクション .data の逆アセンブル:

00000000006000d8 <message>:
  6000d8:       68 65 6c 6c 6f          pushq  $0x6f6c6c65
  6000dd:       2c 20                   sub    $0x20,%al
  6000df:       77 6f                   ja     600150 <_end+0x68>
  6000e1:       72 6c                   jb     60014f <_end+0x67>
  6000e3:       64 21 0a                and    %ecx,%fs:(%rdx)

局所ラベルの利用とレジスタの出力

section .data
codes:
    db '0123456789ABCDEF'

section .text
global _start
_start:
    mov rax, 0x1122334455667788
    mov rdi, 1
    mov rdx, 1
    mov rcx, 64
    jmp .loop
    ;このstartで、rax内に値が存在する状況を生成。

.loop:
    ; 16文字の出力を行うためにこのloopで出力を行う。
    ; 先の_startで設定したカウンタレジスタ内に16バイト分の数値を設定し、それを減算することにより文字列を出力する。
    push rax    
    ; raxの値を退避させることにより、値を保持。
    sub rcx, 4  
    sar rax, cl
    and rax, 0xf
    lea rsi, [codes + rax]
    ;[codes + rax]は相対アドレスを意味する。
    mov rax, 1
    push rcx
    syscall
    pop rcx
    pop rax
    test rcx, rcx
    jnz .loop
    ; testで0でないことを確認してから、.loopへ飛ばす。
    ; 文字数がなくなった時点でloop処理を終了し、exitする。
    mov rax, 60
    xor rdi, rdi
    syscall

.loopのように先頭にピリオドを配置することにより局所的なラベルとして機能する。

今回の場合_start.loopという扱いになる。

$ objdump -D ./print_rax

./print_rax:     ファイル形式 elf64-x86-64


セクション .text の逆アセンブル:

00000000004000b0 <_start>:
  4000b0:       48 b8 88 77 66 55 44    movabs $0x1122334455667788,%rax
  4000b7:       33 22 11
  4000ba:       bf 01 00 00 00          mov    $0x1,%edi

  4000bf:       ba 01 00 00 00          mov    $0x1,%edx
  4000c4:       b9 40 00 00 00          mov    $0x40,%ecx
  4000c9:       eb 00                   jmp    4000cb <_start.loop>

00000000004000cb <_start.loop>:
  4000cb:       50                      push   %rax
  4000cc:       48 83 e9 04             sub    $0x4,%rcx
  4000d0:       48 d3 f8                sar    %cl,%rax
  4000d3:       48 83 e0 0f             and    $0xf,%rax
  4000d7:       48 8d b0 f8 00 60 00    lea    0x6000f8(%rax),%rsi
  4000de:       b8 01 00 00 00          mov    $0x1,%eax
  4000e3:       51                      push   %rcx
  4000e4:       0f 05                   syscall
  4000e6:       59                      pop    %rcx
  4000e7:       58                      pop    %rax
  4000e8:       48 85 c9                test   %rcx,%rcx
  4000eb:       75 de                   jne    4000cb <_start.loop>
  4000ed:       b8 3c 00 00 00          mov    $0x3c,%eax
  4000f2:       48 31 ff                xor    %rdi,%rdi
  4000f5:       0f 05                   syscall

セクション .data の逆アセンブル:

00000000006000f8 <codes>:
  6000f8:       30 31                   xor    %dh,(%rcx)
  6000fa:       32 33                   xor    (%rbx),%dh
  6000fc:       34 35                   xor    $0x35,%al
  6000fe:       36                      ss
  6000ff:       37                      (bad)
  600100:       38 39                   cmp    %bh,(%rcx)
  600102:       41                      rex.B
  600103:       42                      rex.X
  600104:       43                      rex.XB
  600105:       44                      rex.R
  600106:       45                      rex.RB
  600107:       46                      rex.RX

関数のcall

いままではラベルを利用して、jampしそこで実行されるなどを行ってきたが、実際に関数をコールしたいと思う。

push rip
jmp <address>

このようにコールする際は、ripをstack内に保存し、ジャンプする。

関数の利用

実際に関数を利用し、printを実装する。

アセンブラで関数を実装するのには特にラベルとの違うところはない。

主に、異なる箇所は、retやpushによる値の保存を行っているところであろう。

section .data

demo1: dq 0x1122334455667788
demo2: db 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88
demo3: db '0123456789abcdef'
newline_char: db 10

section .text
global _start

print_newline:
    ; いつもの初期設定
    mov rax, 1
    mov rdi, 1
    mov rsi, newline_char
    mov rdx, 1
    syscall
    ret

print_hex:
    mov rax, rdi
    mov rdi, 1
    mov rdx, 1
    mov rcx, 64
    ; rax をシフトするビット数

iterate:
    push rax
    sub rcx, 4
    sar rax, cl
    ; 右にシフト回転させる
    and rax, 0xf
    ; 下位4ビット以外を初期化
    lea rsi, [codes + rax]
    ; 16進数の文字コードを取得
    mov rax, 1
    push rcx ; write自体がrcxを破壊するので退避する。
    syscall
    pop rcx
    pop rax
    test rcx, rcx
    jnz iterate
    ret

_start:
    mov rdi, [demo1]
    ; [demo1]がdemo1の先頭アドレスをを与える。
    call print_hex
    call print_newline

    mov rdi, [demo2]
    call print_hex
    call print_newline

    mov rdi, [demo3]
    call print_hex
    call print_newline

    mov rax, 60
    xor rdi, rdi
    syscall

何かあれば追記する。