目的
x86 16bitモードではCS:IPで実行中のアドレスを指します。
このCS:IPを変更する命令は主に3つあります。
jmp:単純に別のアドレスへ跳ぶ
call:関数呼び出しに使う。跳んだ先の処理が終わるとretで呼び出し元へ返る
int:主にBIOSやMS-DOSの割り込みに使用する
今回はこれらの実行時のスタックの変化を観察します。
x86 16bit 実行時のスタック挙動
| 命令 | スタックに積まれる値 | 備考 |
|---|---|---|
| jmp | なし | 単純ジャンプ。呼び出し元の情報は積まれない |
| call | IP(次の命令のアドレス) | Far call(セグメントを跨ぐ) の場合は CS も積まれる |
| int | FLAGS、CS、IP | 割り込み処理へ跳ぶ前に自動で積まれる |
実験用ソース
※実機で検証する前にqemuで動作確認をしているので注釈中にそのままコマンドを残しています。
jmp
7C00
7C00
; nasm -f bin jmp.asm -o jmp.bin
; qemu-system-i386 -drive format=raw,file=jmp.bin
[org 0x7C00]
bits 16
start:
cli
xor ax, ax
mov ds, ax
mov ss, ax
mov sp, 0x07C00
sti
; 呼び出し前のSP表示
mov ax, sp
call print_ax_hex
jmp testsg
testsg:
; 呼び出し後のSP表示
mov ax, sp
call print_ax_hex
.hlt_loop:
hlt
jmp .hlt_loop
%include "util.asm"
buf db '0000'
times 510-($-$$) db 0
dw 0xAA55
call
7C00
7BFE
7C13
7C00
呼び出す前のSP=7C00
関数内のSP=7BFE
スタックに積まれた値=7C13
返った後のSP=7C00
call後SPが7C00→7BFEへ変化していることから2バイト(レジスタ1つ分)積まれていることが分かる。
スタック中の値7C13からIPが積まれていることが分かり、16ビットモードの仕様と一致する。
; nasm -f bin call.asm -o call.bin
; qemu-system-i386 -drive format=raw,file=call.bin
[org 0x7C00]
bits 16
start:
cli
xor ax, ax
mov ds, ax
mov ss, ax
mov sp, 0x07C00
sti
; 呼び出し前のSP表示
mov ax, sp
call print_ax_hex
call testsg
; 返った後のSP表示
mov ax, sp
call print_ax_hex
.hlt_loop:
hlt
jmp .hlt_loop
testsg:
; 呼び出し後のSP表示
mov ax, sp
call print_ax_hex
pop bx ; IP取り出し
mov ax, bx
call print_ax_hex
push bx
ret
%include "util.asm"
buf db '0000'
times 510-($-$$) db 0
dw 0xAA55
far call
7C00
7BFC
7C2D
0000
7C00
呼び出す前のSP=7C00
関数内のSP=7BFC
スタックに積まれた値=7C2D
スタックに積まれた値=0000
返った後のSP=7C00
call後SPが7C00→7BFCへ変化していることから4バイト(レジスタ2つ分)積まれていることが分かる。
スタック中の値7C2D,0000からIP及びCSが積まれていることが分かり、16ビットモードの仕様と一致する。
; nasm -f bin callfar.asm -o callfar.bin
; qemu-system-i386 -drive format=raw,file=callfar.bin
org 0x7C00
bits 16
start:
cli
xor ax, ax
mov ds, ax
mov ss, ax
mov sp, 0x07C00
sti
; Far Call 用コードを 0x1000:0x0000 に複製
mov si, far_label_source
mov cx, far_label_end - far_label_source
mov di, 0
mov ax, 0x1000
mov es, ax
.copy_loop:
mov al, ds:[si]
mov es:[di], al
inc si
inc di
loop .copy_loop
; SP を表示 (call前)
mov ax, sp
call print_ax_hex
; Far Call
call 0x1000:0x0000
call print_ax_hex ; spは既にaxへ格納済み
; IP
mov ax,bx
call print_ax_hex
; CS
mov ax,cx
call print_ax_hex
; SP を表示 (call後)
mov ax, sp
call print_ax_hex
.hlt_loop:
hlt
jmp .hlt_loop
; Far Call 先のコード(複製用)
far_label_source:
mov ax, sp
pop bx ; IP取り出し
pop cx ; CS取り出し
push cx ;呼び出し元へ返る為にスタックを直す
push bx
retf
far_label_end:
%include "util.asm"
buf db '0000',0
; -----------------------
times 510-($-$$) db 0
dw 0xAA55
int
7C00
7BFA
7C1E
0000
F246
7C00
呼び出す前のSP=7C00
関数内のSP=7BFA
スタックに積まれた値=7C1E
スタックに積まれた値=0000
スタックに積まれた値=F246
返った後のSP=7C00
call後SPが7C00→7BFAへ変化していることから6バイト(レジスタ3つ分)積まれていることが分かる。
スタック中の値7C2D,0000,0246からIP,CS及びフラグレジスタが積まれていることが分かり、16ビットモードの仕様と一致する。
; nasm -f bin int.asm -o int.bin
; qemu-system-i386 -drive format=raw,file=int.bin
[org 0x7C00]
bits 16
start:
cli
xor ax, ax
mov ds, ax
mov es, ax
; 0x80番の割り込みにハンドラをセット
mov word [0x80*4], handler ; IP
mov word [0x80*4+2], cs ; CS
sti
; main SP
mov ss, ax
mov sp, 0x7C00
; 呼び出し前のSP表示
mov ax, sp
call print_ax_hex
; INT 0x80 実行
int 0x80
call print_ax_hex
; IP
mov ax,bx
call print_ax_hex
; CS
mov ax,cx
call print_ax_hex
; FLAG
mov ax,dx
call print_ax_hex
; 返った後のSP表示
mov ax, sp
call print_ax_hex
.hlt_loop:
hlt
jmp .hlt_loop
; 割り込みハンドラ
handler:
; 割り込み時のSP表示
mov ax, sp
pop bx ; IP
pop cx ; CS
pop dx ; FLAG
push dx ;呼び出し元へ返る為にスタックを直す
push cx
push bx
iret
%include "util.asm"
buf db '0000',0
; -----------------------
times 510-($-$$) db 0
dw 0xAA55
レジスタ値表示関数(全ソース共通)
; AX を16進4桁で表示(BIOSで表示)
print_ax_hex:
push ax
push bx
push cx
push dx
push si
mov bx, ax
lea si, [buf+3]
mov cx, 4
.next_nibble:
mov al, bl
and al, 0x0F
cmp al, 10
jb .digit
add al, 'A'-10
jmp .store
.digit:
add al, '0'
.store:
mov [si], al
shr bx, 4
dec si
loop .next_nibble
; ---- BIOSで1文字ずつ表示 ----
mov si, buf
.print_loop:
mov al, [si]
mov ah, 0x0E
int 0x10
inc si
cmp si, buf+4
jne .print_loop
; 改行
mov al, 0x0D
mov ah, 0x0E
int 0x10
mov al, 0x0A
int 0x10
pop si
pop dx
pop cx
pop bx
pop ax
ret
値まとめ
| 命令 | SP 呼び出し前 | スタックに積まれた値 | SP 関数内 | SP 返った後 |
|---|---|---|---|---|
| jmp | 7C00 | 無し | 7C00 | 7C00 |
| call | 7C00 | 7C13 (IP) | 7BFE | 7C00 |
| far call | 7C00 | 7C2D (IP), 0000 (CS) | 7BFC | 7C00 |
| int | 7C00 | 7C1E (IP), 0000 (CS), F246(FLAGS) | 7BFA | 7C00 |
実行環境
・PC: Pocket8086
・CPU: 8086
・BIOS: PC-XT System BIOS
・OS: MS-DOS 6.22
おまけ
現代のPCでもBIOS設定を変えると直接起動可能です。





