FromNandの日記

自分的備忘録

x86アセンブラのPUSH・POPについての疑問を実験してみた!

まずひとつ目は「espをpushするときにespはpushする前に値が変更されるのか、それともpushした後に変更されるのか」という疑問です。

答えを言ってしまうとespがpushされた後に値は変更されます。当然ですけど。

つまり、espの値を表示する関数などを作りたいときにはそのままpushしてから表示すればいいことになります。かんたんですね。

 

テストコードとしては下のようなものが挙げられます。

printというのはprintfの自作バージョンです。

あと、cdeclの呼び出し規約に基づいてprint関数呼び出し直後に「addl $8, %esp」でespを調節しています。(引数は2個なので4 * 2 = 8)

stack_test1ではただ単に「pushl %esp」をしてその時のespの値を表示し、stack_test2でははじめに「movl %esp, %eax」としてeaxにespの値を保存してから「pushl %eax」としてespの値をpushしています。

なので、espの値が変更される前にespがpushされるのであれば同じ値が表示されるはずです。

.code32

.globl stack_test
.extern print

.text

stack_test:
    pushl %ebp
    movl %esp, %ebp
stack_test1:
    pushl %esp
    pushl $debug
    call print
    addl $8, %esp
stack_test2:
    movl %esp, %eax
    pushl %eax
    pushl $debug
    call print
    addl $8, %esp
stack_test_end:
    leave
    ret

.data

debug:
    .string "esp = %x\n"

 

僕の環境では実行結果は次のようになりました。やったね。

esp = FFE49228
esp = FFE49228 

 

そして2つ目は「push命令では値をpushするのが先か、espを減算するのが先か」という疑問です。(pop命令についてもその逆)
これがわからないと、例えば「pushl %eax」したあとにこの値を参照しようとしたときに「(%esp)」で参照したらいいのか「0x4(%esp)」で参照したらいいのかわからなくなってしまいます。
これも結果を先にいってしまうと、「pushはespを減算した後に値を格納」「popは値を受け取った後にespを加算」となります。

実験のコードを載せておきます。
pushしたあとに(%esp)をしらべて、予想した値になっているかを調べます。
もしpushが値を格納した後にespを減算するのであれば、これは予想と異なる値(0x12345678とは異なる値)になるはずです。
popについても同じような感じで、0x12345678以外の値が表示されれば良いことになります。

.code32

.globl stack_test
.extern print

.text

stack_test:
    pushl %ebp
    movl %esp, %ebp
stack_test_start:
    pushl $0x12345678
    movl (%esp), %eax
    pushl %eax
    pushl $debug2
    call print
    addl $0x8, %esp
    popl %eax
    movl (%esp), %eax
    pushl %eax
    pushl $debug2
    call print
    addl $0x8, %esp
stack_test_end:
    leave
    ret

.data

debug:
    .string "esp = %x\n"

debug2:
    .string "(%%esp) = %x\n"

 
このプログラムの実行結果は次のようになります。

(%esp) = 12345678
(%esp) = FFD9D4D8

結論を言ってしまうと「espをpushする際は、pushという動作でespが変更される前のespの値がpushされる」「pushでは減算した後に値をスタックに押し込み、popでは値を受け取った後に加算をする」ということがわかります。