本次笔记内容:
10.栈与过程调用的机器表示-1
11.栈与过程调用的机器表示-2
12.实验
首先复习了上节课内容。
消除部分数据相关是有必要的,为了提高效率,可以使用 Partial Register Stall 等技术。
条件跳转指令可能对流水线效率造成伤害。
栈底 |
---|
↑ Increasing Addresses |
↓ Stack Grows Down |
栈顶指针 %esp 栈顶 |
pushl Src
栈底 |
---|
↑ Increasing Addresses |
↓ Stack Grows Down |
%esp 本来指向这里,压栈后-4,指向下面 |
栈顶指针 %esp 栈顶 |
popl Dest
栈底 |
---|
↑ Increasing Addresses |
↓ Stack Grows Down |
栈顶指针 %esp 栈顶 |
%esp 本来指向这里,出栈后+4,指向上面 |
过程调用指令:call label,将返回地址压入栈,跳转至label。
返回地址:call指令的下一条地址。汇编实例如下。
804854e: e8 3d 06 00 00 call 8048b90 <main> 8048553: 50 pushl %eax
Return address = 0x8048553
过程返回指令:ret,跳转至栈顶的返回地址。
我理解,其作用为,执行 call 后面的函数,执行结束后,在回到本线程来。call即,我在执行前,先把当前线程执行到哪里了,做个标记,压栈。
支持递归:
栈的工作规律:
每个过程实例在栈中维护一个栈帧(stack frame)。
栈帧(stack frame)存储内容:
栈帧的分配与释放:
过程调用时栈的变化:
当前栈真的内容(自“顶”向下)
父过程的栈帧中与当前过程相关的内容:
Caller Frame | … |
---|---|
Caller Frame | Arguments |
Caller Frame | Return Addr |
栈帧指针(%esp) | Old %ebp |
Saved Registers + Local Variables | |
栈顶指针(%esp) | Argument Build |
如上图,当前%ebp还是父过程%ebp,因此Setup现将其存储,留着以后恢复。
之后将当前(新的)%ebp指向旧的%ebp,即设好之后工作的基址。
之后push %ebx,因为尽管父过程可能用%ebx,为了安全,要保存一下。
当然,也不是所有的实例的寄存器都要存。以后讲。
抽象的堆栈和实际的栈的对应关系如上图。
Finish在调用结束后,将父过程恢复。
过程yoo调用who:
做一个软件层面的约定:哪些寄存器由调用者保存,哪些由被调用者保存。
如何使用寄存器作为程序的临时存储?
yoo: ... movl $15213, %edx call who addl %edx, %eax ... ret who: ... movl 8(%ebp), %edx addl $91125, %edx ... ret
如上例,%edx可能被yoo和who同时重复保存恢复,因此作出约定:
使用惯例:
8个Registers:
int rfact(int x) { int rval; if (x <= 1) return 1; rval = rfact(x - 1); return reval * x; }
寄存器使用情况:
.globl rfact .type rfact, @function rfact: pushl %ebp movl %esp, %ebp pushl %ebx # Set up movl 8(%ebp), %ebx cmpl $1, %ebx jle .L78 leal -1(%ebx), %eax pushl %eax call rfact imull %ebx jmp .L79 .align 4 .L78: movl $1, %eax .L79: movl -4(%ebp), %ebx movl %ebp, %esp popl %ebp ret
// Recursive Procedure void s_helper(int x, int *accum) { if (x <= 1) return; else { int z = *accum * x; *accum = z; s_helper(x - 1, accum); } } // Top-Level Call int sfact(int x) { int val = 1; s_helper(x, &val); return val; }
首先,创建指针,如下图。
如上图,可以认识到,在编程中不能把临时变量的地址return。
之所以将%esp增加16 bytes,是因为很多机器(x86-32)中要求栈16 bytes对齐。
接下来,传递指针。
因此,如上图,在使用指针时,就如上图:
程序栈:
相关指令与寄存器使用惯例:
寄存器 | 惯例 | 寄存器 | 惯例 |
---|---|---|---|
%rax | Return Value | %r8 | Argument #5 |
%rbx | Callee Saved | %r9 | Argument #6 |
%rcx | Argument #4 | %r10 | Callee Saved |
%rdx | Argument #3 | %r11 | Used for linking |
%rsi | Argument #2 | %r12 | C: Callee Saved |
%rdi | Argument #1 | %r13 | Callee Saved |
%rsp | Stack Pointer | %r14 | Callee Saved |
%rbp | Callee Saved | %r15 | Callee Saved |
过程参数(不超过6个)通过寄存器传递:
所有对于栈帧内容的访问都是基于%esp完成的:
void swap(long *xp, long *yp) { long t0 = *xp; long t1 = *yp; *xp = t1; *yp = t0; }
swap: movq (%rdi), %rdx movq (%rsi), %rax movq %rax, (%rdi) movq %rdx, (%rsi) ret
参数由寄存器传递:
无需任何栈操作:
/* Swap, using local array */ void swap_a(long *xp, long *yp) { volatile long loc[2]; loc[0] = *xp; loc[1] = *yp; *xp = loc[1]; *yp = loc[0]; }
其中,使用 volatile关键字 强制使用栈空间,但在实际使用中没有修改栈顶寄存器(%rsp)。
swap_a: movq (%rdi), %rax movq %rax, -24(%rsp) movq (%rsi), %rax movq %rax, -16(%rsp) movq -16(%rsp), %rax movq %rax, (%rdi) movq -24(%rsp), %rax movq %rax, (%rsi) ret
long scount = 0; /* Swap a[i] & a[i+1] */ void swap_ele_se(long a[], int i) { swap(&a[i], &a[i+1]); scount++; }
swap_ele_se: movslq %esi, %rsi # Sign extend i leaq (%rdi, %rsi, 8), %rdi # &a[i] leaq 8(%rdi), %rsi # &a[i+1] call swap # swap() incq scount(%rip) # scount++; ret
incq scont(%rip) 是把变量加1。
在x86下引入新寻址方式:
为什么swap_ele_se没有分配栈帧?
因为(除返回值外)没有私有数据来保留,用不着。
long scount = 0; /* Swap a[i] & a[i+1] */ void swap_ele(long a[], int i) { swap(&a[i], &a[i+1]); }
swap_ele: movslq %esi, %rsi # Sign extend i leaq (%rdi, %rsi, 8), %rdi # &a[i] leaq 8(%rdi), %rsi # &a[i+1] jmp swap # swap
使用jmp指令调用过程,可以是因为对栈没有什么变化。
long sum = 0; /* Swap a[i] & a[i+1] */ void swap_ele_su(long a[], int i) { swap(&a[i], &a[i+1]; sum += a[i]; }
swap_ele_su: movq %rbx, -16(%rsp) movslq %esi, %rbx movq %r12, -8(%rsp) movq %rdi, %r12 leaq (%rdi, %rbx, 8), %rdi subq $16, %rsp leaq 8(%rdi), %rsi call swap movq (%r12, %rbx, 8), %rax addq %rax, sum(%rip) movq (%rsp), %rbx movq 8(%rsp), %r12 addq $16, %rsp ret
两个,BombLab与BufLab。