概要
中古ノート買ってみた。
wsl1のubuntu18.04にnasm入れてみた。
push popをtestしたら、セグメンテーションフォルトになるので、copilotに聞いてみた。
サンプルコード
.intel_syntax noprefix
.global main
.text
main:
push rbp
mov rbp, rsp
mov rsi, 255
lea rdi, fmt[rip]
mov rax, 0
push rax
call printf@PLT
pop rax
mov rax, 0
mov rsp, rbp
pop rbp
ret
.data
fmt: .string "value = %lX\n"
回答
なるほど、「何故セグフォルトになるのか」をもっと根本的に説明しますね。
🧩 原因の本質
あなたのコードでは printf を呼び出す前に 不要な push rax / pop rax を入れています。
このせいで スタックポインタ (rsp) がずれてしまい、関数呼び出し後のスタックフレームが壊れる のです。
呼び出し規約 (System V AMD64 ABI)
- 引数はレジスタで渡す (rdi, rsi, rdx, …)。
- 可変引数関数では rax に「浮動小数点引数の数」を入れる。整数だけなら rax=0。
- スタックは 16 バイト境界に揃えた状態で call しなければならない。
あなたのコードの流れ - push rbp → スタックが 8 バイト減る
- mov rbp, rsp
- mov rsi, 255 (第2引数)
- lea rdi, fmt[rip] (第1引数)
- mov rax, 0 (浮動小数点引数なし)
- push rax → スタックがさらに 8 バイト減る
- call printf@PLT
この時点で、スタックのアラインメントが 16 バイト境界からずれてしまう。
printf 内部では SSE 命令などでスタックが 16 バイト境界にあることを前提にしているため、ずれた状態で呼び出すと不正アクセスが起きてセグフォルトになります。
✅ 修正ポイント
- push rax / pop rax は不要。
- 呼び出し前にスタックを 16 バイト境界に揃えることが重要。
修正版ではこうなります:
.intel_syntax noprefix
.global main
.text
main:
push rbp
mov rbp, rsp
mov rsi, 255 # 第2引数
lea rdi, fmt[rip] # 第1引数
mov rax, 0 # 浮動小数点引数の数
call printf@PLT
mov eax, 0 # return 0
mov rsp, rbp
pop rbp
ret
.data
fmt: .string "value = %lX\n"
🎯 まとめ
- セグフォルトの原因は スタックアラインメント違反。
- push rax が余計で、printf 呼び出し時にスタックが 16 バイト境界からずれていた。
- 修正すれば正常に "value = FF" が表示される。
スタックアラインメントのルールは「関数呼び出し直前の rsp が 16 の倍数であること」です。
このルールを守らないと、printf のようなライブラリ関数が内部で SSE 命令を使った時にクラッシュします。
👉 次は「なぜ 16 バイト境界が必須なのか」について、CPU の仕組みまで掘り下げて説明しましょうか?
なので、対応策
動いた。
.intel_syntax noprefix
.global main
.text
main:
push rbp
mov rbp, rsp
mov rsi, 255
lea rdi, fmt[rip]
mov rax, 0
push rax
push rax
call printf@PLT
pop rax
pop rax
mov rax, 0
mov rsp, rbp
pop rbp
ret
.data
fmt: .string "value = %lX\n"
以上。