Stack pointer / Frame pointer 在 x86 Assembly 操作的簡易分析

這幾天在複習 compiler 有關 runtime environment 的章節,讀到 activation record 的時候發現對 frame pointer 的概念有點忘記了,於是就決定來做一點小實驗。

Toy example

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
int foo(int a, int b) {
    int x, y;
    x = 123;
    y = a + b;
    return y;
}

int main() {
    foo(111, 222);
}

x86 Assembly

把 assembly 編出來看一下 stack 操作的過程:

1
$ gcc -S -m32 test.c

可以拿到:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
	.file	"test.c"
	.text
	.globl	foo
	.type	foo, @function
foo:
.LFB0:
	.cfi_startproc
	pushl	%ebp
	.cfi_def_cfa_offset 8
	.cfi_offset 5, -8
	movl	%esp, %ebp
	.cfi_def_cfa_register 5
	subl	$16, %esp
	movl	$123, -4(%ebp)
	movl	8(%ebp), %edx
	movl	12(%ebp), %eax
	addl	%edx, %eax
	movl	%eax, -8(%ebp)
	movl	-8(%ebp), %eax
	leave
	.cfi_restore 5
	.cfi_def_cfa 4, 4
	ret
	.cfi_endproc
.LFE0:
	.size	foo, .-foo
	.globl	main
	.type	main, @function
main:
.LFB1:
	.cfi_startproc
	pushl	%ebp
	.cfi_def_cfa_offset 8
	.cfi_offset 5, -8
	movl	%esp, %ebp
	.cfi_def_cfa_register 5
	pushl	$222
	pushl	$111
	call	foo
	addl	$8, %esp
	movl	$0, %eax
	leave
	.cfi_restore 5
	.cfi_def_cfa 4, 4
	ret
	.cfi_endproc
.LFE1:
	.size	main, .-main
	.ident	"GCC: (GNU) 10.2.1 20201125 (Red Hat 10.2.1-9)"
	.section	.note.GNU-stack,"",@progbits

把一些不重要的資訊精簡後,可以得到:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
foo:
	pushl	%ebp
	movl	%esp, %ebp
	subl	$16, %esp
	movl	$123, -4(%ebp)
	movl	8(%ebp), %edx
	movl	12(%ebp), %eax
	addl	%edx, %eax
	movl	%eax, -8(%ebp)
	movl	-8(%ebp), %eax
	leave
	ret
main:
	pushl	%ebp
	movl	%esp, %ebp
	pushl	$222
	pushl	$111
	call	foo
	addl	$8, %esp
	movl	$0, %eax
	leave
	ret

Analysis

Before foo

  1. 一開始在 main 被呼叫前,會在 glibc 的 _start function 內,假設 SP 現在指到 0x1924 FP 指在 0x2000
  2. 接著保存 FP 的位址,將 FP 指到新的 SP 的位址
  3. 將 foo 要的參數 push 進 stack
  4. 呼叫 foo,保存 return address 在 stack

foo

  1. 進到 foo
  2. 保存 main 的 FP 的位址,將 FP 指到新的 SP 的位址
  3. SP - 16
  4. 算 x 與 y,透過 FP 拿到 local variables 的位址
  5. 將 return value 保存在 %eax
  6. leave 將 SP 指回 FP,並 pop,FP 回到 main 原先呼叫的位址

After foo

  1. 回到 main
  2. SP 指向 FP 的位址
  3. leave 將 SP 指回 FP,並 pop,FP 回到 _start 原先呼叫的位址

comments powered by Disqus