スタック操作でスタックポインタの位置を見る

関数を呼び出すときや呼び出し元に戻るときなどにスタックポインタというものを利用しているらしいのですが、どうもスタックポインタが具体的にメモリ上にどこら辺にあるのか出力させてみなければはっきりとわかりません。そういうことでプログラムを書いて実際にスタックポインタの値を出してみましょう。

まずはアセンブリコードとC言語のソースコードを書きます。

  • stack.asm
;; スタックのpush/popのesp(スタックポインタ)の変化の観察
;; 2005/May/13
;; $ nasmw -f win32 stack.asm

[extern _addr1]
[extern _addr2]
[extern _addr3]
[extern _addr4]
[extern _addr5]
[global	_search_address]

[BITS 32]
[section .text]

_search_address:
	mov	[_addr1], esp

	push	ebx
	mov	[_addr2], esp

	push	ebx
	mov	[_addr3], esp

	pop	ebx
	mov	[_addr4], esp

	pop	ebx
	mov	[_addr5], esp

	ret
  • stack.c
/*
 スタックのpush/popのesp(スタックポインタ)の変化の観察
 2005/May/13
 $ gcc stack.obj stack.c
*/
#include <stdio.h>
#include <stdlib.h>

extern int search_address();

int addr1 = 0;
int addr2 = 0;
int addr3 = 0;
int addr4 = 0;
int addr5 = 0;

int main(void)
{
	search_address();
	puts("search_address() called");
	
	printf("addr1 = %p\n", addr1);
	
	puts("pushed");
	printf("addr2 = %p\n", addr2);
	
	puts("pushed");
	printf("addr3 = %p\n", addr3);
	
	puts("poped");
	printf("addr4 = %p\n", addr4);
	
	puts("poped");
	printf("addr5 = %p\n", addr5);
	
	return EXIT_SUCCESS;
}

そしてコンパイルしてみます。

$ nasm -f win32 stack.asm
$ gcc stack.obj stack.c

できた実行ファイルを動かしてみます。

$ ./a
search_address() called
addr1 = 0073FDCC
pushed
addr2 = 0073FDC8
pushed
addr3 = 0073FDC4
poped
addr4 = 0073FDC8
poped
addr5 = 0073FDCC

見事にスタックポインタのメモリ上の位置がとれてます。まず、何も呼び出さないときは0073FDCCという場所にあり、pushを一回実行したら位置が-4になります。またpushしたらまた-4になります。popは逆に+4になることもわかります。

 ffffffff
     :
 0073FDCC ↓push 呼び出されたときのespの値
 0073FDC8
 0073FDC4 ↑pop
     :
 00000000

見事に実験成功です。スタックをpush/popによって操作するたびにスタックポインタの値が変化する様子を手に取るようにわかります。何も操作しないときには、引数の関係でC言語の標準の呼び出し規約は関数の第一引数は呼び出される側は[esp+4]に代入されますが、スタックを操作するとこのespが変化するためにesp+4+4*(pushした回数)としなければならないことがわかります。