目录
每一个函数需要分配栈空间、进一步调用其函数、保存非易失性寄存器、或者使用异常处理机制,都必须有一段序言代码(prolog),它的地址限制在解开数据(unwind)中描述,unwind数据与相应的函数地址条目相关。更多信息,参见X64异常处理文档。如有必要,prolog会将数据参数寄存器值保存在它的起始地址上(home address)(即,栈的最底下分出的一块地址,用于存放函数参数寄存器值和返回地址,称其为home地址),将非易失性寄存器的值压入栈,为局部变量和临时存储分配固定大小的栈空间,并且在可选地建立一个帧指针。相关的unwind数据必须描述prolog的行为,必要为系统撤消prolog代码的效果提供必要的信息。
假如栈上分配的固定大小超过了一页(即,超过了4096字节),则栈的分配可能会跨越一个虚拟内存的分页,因此,在分配内存之前必须要检查。基于这个目的,系统提供了一个专门的例程供prolog调用,它不会破坏任何参数栈(即,传递寄存器参数所用到的栈,也就是前面提到的home地址,又称影子内存)。
保存非易失性寄存器偏爱的方法是在分配固定栈之前将寄存器值移到栈上。假如固定栈分配在非易失性寄存器值保存之前执行,则最可能要求一个32位的位移去寻址保存寄存器的区域。(据报道,寄存器入栈和出栈一样快,并且在可预见的未来应该保持如此,尽管入栈之间存在隐含的依赖关系。) 非易失性寄存器可以以任何顺序存储。然而,在prolog中第一次使用非易失性寄存器就必须是保存其值。
1. prolog的代码(在MASM中)
典型的prolog代码可以是这样:
mov [RSP + 8], RCX
push R15
push R14
push R13
sub RSP, fixed-allocation-size
lea R13, 128[RSP]
...
prolog将参数寄存器RCX存入到其home位置,存储非易失性寄存器R13-R15到栈中,分配因定大小的栈帧,并建立一个指向128字节固定大小空间的帧指针。使用偏移量以允许使用1字节的偏移寻址固定分配区域的大部分区域。
假如固定分配区域大于等于一页内存,则在修改RSP之前,系统会调用一个帮助函数。这个帮助函数为__chkstk会探测待分配栈的范围,以确保正确地扩充了栈空间。按这种思想,前面的prolog代码可以改写为如下形式:
mov [RSP + 8], RCX
push R15
push R14
push R13
mov RAX, fixed-allocation-size
call __chkstk
sub RSP, RAX
lea R13, 128[RSP]
...
帮助函数__chkstk除了修改寄存器R10、R11和条件代码之外,不会修改别的任何寄存器的值。特别是,它将原样返回RAX,并保持所有非易失性寄存器和参数传递寄存器的值不变。
2. epilog的代码(在MASM中)
epilog存在于每个函数的出口处,然而,通常即只有一个prolog,但即可以有多个epilog。epilog代码将堆栈修剪为其固定分配大小(如有必要),释放固定栈空间,从栈中弹出先前存入栈中的非易失性寄存器的值以恢复其寄存器值,然后再返回。
epilog代码必须遵循一套严格的unwind代码的规则,才能可靠地通过异常和中断unwind。这套规则压缩了所要求的unwind数据的数量,因为,描述每个epilog不需要别的数据。相反,unwind代码可以通过向前扫描代码流来识别epilog,从而确定epilog正在执行。
假如在函数中使用了桢指针,则必须在epilog执行之前将栈修剪为其先前的固定大小。这个动作在技术上不属于epilog的范畴。例如,可以使用下面的epilog代码撤销之前使用的epilog:
lea RSP, -128[R13] ; epilogue proper starts here
add RSP, fixed-allocation-size
pop R13
pop R14
pop R15
ret
在实践中,当使用帧指针的时候,在两步动作中,没有很好的理由去调整RSP指针,因此,可以使用下面的epilog代码代替:
lea RSP, fixed-allocation-size - 128[R13]
pop R13
pop R14
pop R15
ret
这些形式仅仅是epilog的有效形式。必须由
add RSP, constant
或
lea RSP, constant[FPreg]
后接0或更多8字节寄存器弹出值外加一个返回值或者jmp指令构成。(在epilog中,只允许使用jmp语句类的一个子集,该子集仅是具有ModRM内存引用的jmp语句类,其中 ModRM mod字段值为 00)。禁止在ModRM mod字段值为01或10的epilog中使用 jmp语句。有关允许的ModRM引用的更多信息,请参阅AMD x86-64架构程序员手册第 3卷:通用和系统指令中的表 A-15。)不能出现其他代码。特别是,不能在epilog中安排任何内容,包括加载返回值。
当不使用帧指针时,epilog必须使用add RSP,constant来释放分配的固定栈空间。它不可以使用 lea RSP,constant[RSP]代替。由于存在这个限制,因此,当搜寻epilog的时候,unwind可以识别的模式更少。
遵循这些规则允许展开unwind确定当前正在执行epilog,并模拟epilog其余部分的执行,从而允许重新创建调用函数的上下文。
资料来源: