C 言語プログラムを実行するまでになにがおこなわれるか

url: https://www.gaio.co.jp/gaioclub/compiler_blog10/
title: "【第10回】スタックフレーム | ガイオ・テクノロジー  ソフト検証ツール/モデルベース/エンジニアリングサービス プロバイダー"
description: "ガイオ・テクノロジー株式会社は、自動車開発の新しいトレンドに対応するソフトウェア開発・検証を徹底支援します。"
host: www.gaio.co.jp
favicon: https://www.gaio.co.jp/favicon.ico

ARM64 (AArch64)

Imgur

https://www.gaio.co.jp/gaioclub/compiler_blog10/ の図をお借りして考えます。 図のように画像の上部が低アドレス 0x00000000, 画像の下部が高アドレス 0xFFFFFFFF と仮定します。

func1 関数内部から func2 関数を呼び出したとき、 新たに func2 のスタックフレームが確保されます。 具体的には func2 のスタックフレームのサイズを としたとき、 という操作がおこなわれます。

そして、下記のような領域が用意されます。

stack-protector カナリアはオプショナルです。

0x00000000(低アドレス)
||||----------||||
 
------------------ ← (sp: stack pointer)
(Margin: スタックフレームを 16byte 単位にするための余白)
------------------
**ローカル領域**
- ローカル変数
- 引数の一時的な保存
------------------ ← (x29: frame pointer の基準点)
StackCanary (Optional)
- バッファオーバーフローでメモリを書き換えられたか?をチェックする用
------------------
x29 (フレームポインタ)
-----------------
x30 (リターンアドレス)
-----------------
(Func1 の StackFrame)
-----------------
 
||||----------||||
0xFFFFFFFF(高アドレス)

example

(lldb) dis
main`add:
    0x100000510 <+0>:  sub    sp, sp, #0x20
    0x100000514 <+4>:  stp    x29, x30, [sp, #0x10]
    0x100000518 <+8>:  add    x29, sp, #0x10
    0x10000051c <+12>: stur   w0, [x29, #-0x4]
    0x100000520 <+16>: str    w1, [sp, #0x8]
    0x100000524 <+20>: adrp   x8, 8
    0x100000528 <+24>: add    x8, x8, #0x0 ; globalW
    0x10000052c <+28>: ldr    w8, [x8]
    0x100000530 <+32>: str    w8, [sp, #0x4]
    0x100000534 <+36>: ldur   w0, [x29, #-0x4]
    0x100000538 <+40>: bl     0x1000004f8    ; doubled at add.c:5
->  0x10000053c <+44>: ldr    w8, [sp, #0x4]
    0x100000540 <+48>: add    w8, w8, w0
    0x100000544 <+52>: ldr    w9, [sp, #0x8]
    0x100000548 <+56>: add    w0, w8, w9
    0x10000054c <+60>: ldp    x29, x30, [sp, #0x10]
    0x100000550 <+64>: add    sp, sp, #0x20
    0x100000554 <+68>: ret

上記のようなアセンブリコードを考えます。 ここでは 0x20 = 32 byte のスタックフレームが確保されます。

このとき、以下のようなスタックフレームの構成になります。

(sp + 0x00) : 余白
(sp + 0x04) : 4 bytes -> globalW の値 を一時的に格納した場所 (str w8, [sp,#0x4])
(sp + 0x08) : 4 bytes -> w1 (y) を一時的に格納した場所 (str w1, [sp,#0x8])
(sp + 0x0c) : 4 bytes -> w0 (x) を一時的に格納した場所 (stur w0, [x29,#-0x4] => x29-4 = sp+0x0c)
(sp + 0x10) : 8 bytes -> saved x29 (old FP)  (stp x29,x30,[sp,#0x10])
(sp + 0x18) : 8 bytes -> saved x30 (old LR / return address)

sp + 0x00 から sp + 0x03 が未使用になっています。 これは AArch64 では $sp が 16 バイトの倍数になる = 末尾 0 になることを期待しています。 今回のスタックフレーム必要サイズが 4 * 3 + 8 * 2 = 28 = 0x1c ですが、 16 バイトの倍数でないため 0x20 になるよう 4byte 余白をスタックフレームに追加しているようです。

x86-64

x86-64 は AArch64 とはまったく構成や仕組みが異なります。