目的
アセンブリのpop,pushを使わず別の命令で再現することでスタックへの理解を深めることが目的です。
pushとは
レジスタの値をスタックに積む。
rspレジスタが常にスタックの一番上を指している。
push rax
は以下のような処理をする
mov rax, [str2]
sub rsp, 8
mov [rsp], rax
mov rax, [str2]
str2が指している文字列のアドレス(8バイト)をraxに格納
例:RAX = 0x0000000000002000 (8バイトなので0埋め)
sub rsp, 8
rspはスタックに値が積まれると低い方へ伸びる。値を積む為に8引き領域を確保する。
例:RSP = 0x1000 → 0x0FF8
mov [rsp], rax
rspの指しているアドレスへ値を格納する。
例:
0x0FF8 → 0x00 (RAX 下位バイト)
0x0FF9 → 0x20
0x0FFA → 0x00
0x0FFB → 0x00
0x0FFC → 0x00
0x0FFD → 0x00
0x0FFE → 0x00
0x0FFF → 0x00 (RAX 上位バイト)
※低アドレスから高アドレス に下位バイト → 上位バイト
popとは
スタックから値を1つ取り出す。
pop rbx
は以下のような処理をする
mov rbx, [rsp]
add rsp, 8
mov rbx, [rsp]
スタックの値を取り出しrbxレジスタへ格納
例:
取り出した直後はこのようになっている
アドレス 値
0x0FF8 0x00 ← RAX 下位バイト
0x0FF9 0x20
0x0FFA 0x00
0x0FFB 0x00
0x0FFC 0x00
0x0FFD 0x00
0x0FFE 0x00
0x0FFF 0x00 ← RAX 上位バイト
RSP = 0x0FF8
add rsp, 8
スタックポインタを元に戻して領域を開放
RSP = 0x1000
これで元通り!
実装
test@test-ThinkPad-X280:~/test/stack$ nasm -f elf64 stack.asm -o stack.o
test@test-ThinkPad-X280:~/test/stack$ ld -o stack stack.o
test@test-ThinkPad-X280:~/test/stack$ ./stack
global _start ; _startから実行を始める
section .data
str1 dq str1_str
str2 dq str2_str
str3 dq str3_str
str1_str db "Hello", 10, 0
str2_str db "World", 10, 0
str3_str db "Stack!", 10, 0
section .text
; ------------------------------------------------
; 文字列の長さ(バイト数)を数える
; ------------------------------------------------
strlen:
push rdi
xor rax, rax ; バイト数勘定用レジスタ初期化
.len_loop:
cmp byte [rdi + rax], 0 ; 終端文字0かどうか
je .len_done ; 0ならば処理を終了
inc rax ; rax++
jmp .len_loop
.len_done:
pop rdi
ret
; ------------------------------------------------
; 文字列を画面出力
; ------------------------------------------------
print_str:
; レジスタ退避
push rdi
push rsi
push rdx
push rcx
mov rdi, rax ; 書き込む文字列のアドレス
call strlen ; バイトが返る
mov rsi, rdi ; 書き込む文字列のアドレス
mov rdx, rax ; 書き込むバイト数
mov rax, 1 ; writeシステムコール番号1
mov rdi, 1 ; 書き込み先=標準出力(画面)
syscall
; レジスタ復元
pop rcx
pop rdx
pop rsi
pop rdi
ret
; ------------------------------------------------
; 実験コード
; A) 標準 push → 自作 pop
; B) 自作 push → 標準 pop
; C) 自作 push + 自作 pop
; ------------------------------------------------
_start:
and rsp, 0xFFFFFFFFFFFFFFF0 ; 下位4ビットを0にして16の倍数にする
; ---------- A: 標準 push -> 自作 pop ----------
mov rax, [str1] ; 文字列1のアドレスをpush
push rax
mov rbx, [rsp] ; (自作POP) スタックから値を取り出しrbxへ格納
add rsp, 8 ; 8足してスタックを8バイト分開放
mov rax, rbx ; write()呼び出し
call print_str
; ---------- B: 自作 push -> 標準 pop ----------
mov rax, [str2]
sub rsp, 8 ; 8引いて8バイト分領域を確保
mov [rsp], rax ; (自作push) 文字列2のアドレスをスタックへ値を積む
pop rax
call print_str
; ---------- C: 自作 push + 自作 pop (both inline) ----------
mov rax, [str3] ; push
sub rsp, 8
mov [rsp], rax
mov rbx, [rsp] ; pop
add rsp, 8
mov rax, rbx ;write
call print_str
; return 0
mov rax, 60 ; exitのシステムコール番号
xor rdi, rdi ; 返り値
syscall
実行結果
test@test-ThinkPad-X280:~/test/stack$ ./stack
Hello
World
Stack!