淄博网站建设选哪家,php软件网站建设,建大型网站需要多少钱,广东seo加盟文章目录 一、x86_64架构寄存器简介二、x86_64架构帧指针FP三、示例四、保存帧指针参考资料 一、x86_64架构寄存器简介
在x86架构中#xff0c;有8个通用寄存器可用#xff1a;eax、ebx、ecx、edx、ebp、esp、esi和edi。在x86_64#xff08;x64#xff09;扩展中#xff… 文章目录 一、x86_64架构寄存器简介二、x86_64架构帧指针FP三、示例四、保存帧指针参考资料 一、x86_64架构寄存器简介
在x86架构中有8个通用寄存器可用eax、ebx、ecx、edx、ebp、esp、esi和edi。在x86_64x64扩展中这些寄存器被扩展为64位以’r’前缀代替’e’并添加了另外8个寄存器r8、r9、r10、r11、r12、r13、r14和r15。
x64架构中寄存器的数量增加了这为优化寄存器分配提供了更多机会并减少了对栈的依赖。这影响了应用程序二进制接口ABI的重要设计决策ABI定义了函数如何被调用以及参数如何在程序的不同部分之间传递。
x64 ABI中的一个重要变化是更多地使用寄存器传递函数参数。调用约定指定最多可以通过寄存器rdi、rsi、rdx、rcx、r8和r9传递6个整数或指针参数而不是将它们推入栈中。这减少了栈的使用并改善了具有少量参数的函数调用的性能。
根据ABI规范在函数中前6个整数或指针参数将被传递到寄存器中。第一个参数被放置在rdi寄存器中第二个参数放置在rsi寄存器中第三个参数放置在rdx寄存器中然后是rcx、r8和r9寄存器。只有第7个参数及以后的参数才会传递到栈上。
如下图所示函数P调用函数Q
备注第7个参数及以后的参数时保存在调用者的栈中上述函数P调用函数Q过程中如果函数Q需要大于6个的参数那么函数P调用函数Q时将参数7-n保存在自己的栈帧中。
比如 函数 fun1 调用 函数 fun2
fun2()
{}fun1()
{fun2(a,b,c,d,e,f,g,h);
}函数 fun1 调用 函数 fun2时通过寄存器最多传递6个整数指针或者整数但是fun2需要8个参数那么函数 fun1 在调用函数 fun2之前在自己的栈帧中存储好多余的参数也就是第7个参数及以后的参数。其中参数7位于位于栈顶。
函数 fun1 调用 函数 fun2的过程将参数1-6复制到对应的寄存器rdi、rsi、rdx、rcx、r8和r9把参数7-8放在函数 fun1的栈顶当参数到位后程序就可以执行call指令将控制转移到函数 fun2了。如果函数 fun2也调用了大于6个参数的函数那么也要把超出6个参数的部分保存在自己的栈帧中
更多的寄存器可用性还影响了栈帧的使用。由于有更多的通用寄存器可以更容易地将经常访问的变量保存在寄存器中减少了将它们存储在栈上的需求。这可以提高代码执行效率并可能导致更小的栈帧。
二、x86_64架构帧指针FP
在x86_64架构中函数调用过程中需要的存储空间超出寄存器能够存放的大小时就会在栈上分配空间这个空间称为函数的栈帧stack frame函数调用创建一个栈帧stack frame来存储局部变量、函数参数、返回地址和其他与函数执行相关的信息。栈帧是在程序执行期间动态地分配和管理的。
并非每个函数调用都会创建栈帧。当函数调用参数少于6个且可以通过寄存器传递以及所有局部变量都可以保存在寄存器中并且函数是叶子函数叶子函数指该函数不会调用其他函数时就不需要创建栈帧。
大多数函数调用过程中栈帧都是定长的在函数调用的开始就分配好了栈空间。
1帧指针Frame Pointer 在x86_64架构中FP通常是指Frame Pointer也称为帧指针。帧指针通常由RBP寄存器Base Pointer表示。 帧指针指向当前函数的栈帧的底部即栈帧中局部变量和函数参数的起始位置。
使用帧指针的好处是它提供了一种相对于固定参考点的偏移量访问局部变量和函数参数的方式。通过将帧指针与偏移量相结合可以准确地访问栈帧中的特定变量或参数。
通过帧指针可以访问局部变量、函数参数和返回地址等信息。
帧指针的值在函数执行过程中是稳定的不会随着栈的动态变化而改变。这使得调试器和性能分析器能够使用帧指针来构建函数调用图和跟踪变量的访问情况。
2栈指针Stack Pointer 在x86_64架构中栈指针通常由RSP寄存器Stack Pointer表示。 栈指针指向当前栈顶的位置即最新压入栈的数据所在的内存地址。 使用push指令将数据存入栈中使用pop指令将数据从栈中取出。
通过栈指针减小一个适当的量可以为没有指定初始值的数据在栈上分配空间增加栈指针来释放栈空间。
3局部变量和函数参数 每个函数调用都会在栈帧中分配一定的空间来存储局部变量和函数参数。 这些变量和参数的访问通常是相对于帧指针的偏移量来进行的。
4返回地址Return Address 在函数调用时返回地址会被压入栈中以便在函数执行完毕后返回到调用它的位置。
在跳转到被调用过程的第一条指令之前CALL指令会将RIP寄存器中的地址推送到当前栈上。这个地址被称为返回指令指针return-instruction pointer它指向在从被调用过程返回后调用过程应该从哪条指令继续执行。在从被调用过程返回时RET指令会将返回指令指针从栈中弹出并将其放回RIP寄存器中。然后调用过程的执行会继续。
RIP寄存器用于指示将要执行的下一条指令的地址。当处理器执行指令时RIP寄存器会自动递增指向下一条将要执行的指令的地址。在分支、跳转或调用指令执行时RIP寄存器的值会被改变以跳转到新的指令地址。在过程调用中CALL指令会将返回地址下一条指令地址推送到栈上而RET指令会将栈上的返回地址弹出并存储回RIP寄存器从而实现从子过程返回到调用过程。
5栈帧布局Stack Frame Layout 栈帧布局是指栈帧中各个元素的相对位置和顺序。 栈帧布局是由编译器在函数编译过程中决定的通常根据函数的局部变量和参数的需求进行分配和组织。
备注在x86_64架构下gcc没有使用优化选项时帧指针来访问栈帧的数据栈指针来分配和释放栈帧的空间。
当gcc使用 -O 优化选项时会省略帧指针即gcc的所有级别的优化-O1, -O2, -O3等都会打开-fomit-frame-pointer该选项的功能是函数调用时不保存frame指针请参考1.4节。
三、示例
long utilfunc(long a, long b, long c)
{long xx a 2;long yy b 3;long zz c 4;long sum xx yy zz;return xx * yy * zz sum;
}long myfunc(long a, long b, long c, long d,long e, long f, long g, long h)
{long xx a * b * c * d * e * f * g * h;long yy a b c d e f g h;long zz utilfunc(xx, yy, xx % yy);return zz 20;
}func1
{myfunc(a,b,c,d,e,f,g,h);
}1其中myfunc函数栈布局如下所示 myfunc函数的栈帧就是 RBP到RSPfunc1调用myfunc将参数1-6a、b、c、d、e、f复制到对应的寄存器rdi、rsi、rdx、rcx、r8和r9把参数g、h放在函数 func1的栈顶把返回地址压入到栈顶中myfunc函数的栈帧保存局部变量xx、yy、zz。
返回地址和参数g、h都是放在函数 func1的栈帧中。
根据AMD64 ABI的正式定义
%rsp指向的位置后面的128字节区域被认为是保留的不应该被信号处理程序或中断处理程序修改。因此函数可以使用这个区域作为临时数据存储区这些数据在函数调用之间不需要保留。特别是叶子函数可以将整个栈帧放在这个区域中而不需要在函数的开头和结尾调整栈指针。这个区域被称为红区red zone。
简单来说红区是一种优化策略。代码可以假设rsp下面的128字节不会被信号处理程序或中断处理程序异步破坏因此可以将其用作临时数据的存储区而无需显式地移动栈指针。这个优化的关键是最后一句话——使用红区存储数据时可以节省减少rsp和恢复rsp的两条指令。
这意味着当函数使用红区来存储临时数据时可以省略调整栈指针的指令从而提高代码的执行效率。红区的使用使得对栈指针的调整仅发生在需要保留的数据超过128字节的情况下而对于较小的临时数据可以直接使用红区无需额外操作。
需要注意的是红区的使用是可选的并且在使用时需要小心确保不会超出128字节的范围以避免与异常处理相关的问题。
2其中utilfunc函数栈布局如下所示 回想一下上面代码示例中的myfunc是如何调用另一个名为utilfunc的函数的。这样做是故意的目的是使myfunc成为非叶子从而防止编译器应用红区优化。看看utilfunc的代码这确实是一个叶函数。让我们看看使用gcc编译时它的堆栈框架是什么样子的。 由于utilfunc只有3个参数因此调用它不需要使用堆栈因为所有参数都适合寄存器。此外由于它是一个叶函数gcc选择对其所有局部变量使用红色区域。因此rsp不需要递减稍后恢复来为该数据分配空间。
四、保存帧指针
在函数执行过程中基指针rbp以及在x86上的前身ebp作为指向栈帧开头的稳定锚点在手动汇编编码和调试中非常方便。然而一段时间以前就注意到编译器生成的代码实际上并不需要它编译器可以轻松地从rsp跟踪偏移量而DWARF调试格式提供了访问栈帧的手段CFI无需使用基指针。
因此一些编译器开始省略基指针以进行积极的优化从而缩短函数的前奏和尾声并提供了一个额外的通用寄存器供使用请记住在具有有限通用寄存器集的x86上这非常有用。
gcc在x86上默认保留基指针但允许使用-fomit-frame-pointer编译标志进行优化。关于是否建议使用此标志存在争议——如果您对此感兴趣可以进行一些搜索。
无论如何AMD64 ABI引入的另一个新特性是明确将基指针作为可选项规定如下
可以通过使用%rsp栈指针来索引栈帧从而避免将%rbp用作栈帧指针的传统用法。这种技术在前奏和尾声中可以节省两条指令并提供了一个额外的通用寄存器%rbp。
gcc遵循这个建议并在进行优化编译时默认情况下在x64上省略帧指针。它提供了一个选项通过使用-fno-omit-frame-pointer标志来保留帧指针。出于清晰起见上面显示的栈帧是在没有省略帧指针的情况下生成的。
-fomit-frame-pointer省略帧指针。 -fno-omit-frame-pointer保留帧指针。
gcc在x86x86_64上默认保留基指针
x86_64架构gcc优化选项 -O 默认使用-fomit-frame-pointer编译标志进行优化省略帧指针。 -fomit-frame-pointerOmit the frame pointer in functions that dont need one. This avoids the instructions to save, set up and restore the frame pointer; on many targets it also makesan extra register available.On some targets this flag has no effect because the standard calling sequence always uses a frame pointer, so it cannot be omitted.Note that -fno-omit-frame-pointer doesnt guarantee the frame pointer is used in all functions. Several targets always omit the frame pointer in leaf functions.Enabled by default at -O and higher.-fomit-frame-pointer 是GCC编译器的一个编译选项。当启用该选项时它告诉编译器在不需要基指针的函数中省略基指针。通过省略基指针编译器避免了保存、设置和恢复基指针的指令从而使生成的代码更小、更快。
省略基指针还提供了一个额外的通用寄存器可供使用这对于具有有限通用寄存器数量的架构如x86非常有用。
然而需要注意的是该选项的效果可能因目标架构而异。在一些目标架构中标准调用序列始终使用基指针因此该选项可能没有效果基指针仍然会被使用。
值得注意的是即使不使用 -fno-omit-frame-pointer也不能保证在所有函数中都使用基指针。一些目标架构特别是在叶子函数不调用其他函数的函数中仍然会省略基指针以进行优化。
默认情况下在优化级别 -O 及更高级别时 -fomit-frame-pointer 选项会被启用意味着在优化的代码中会省略基指针。
当gcc没有使用优化选项时函数开头和结尾会有如下指令
func
{push %rbpmov %rsp,%rbpsub $0x50,%rsp......ops %rbp......pop %rbpret
}首先指令push %rbp将当前函数的基指针值压入栈中保存起来。接下来指令mov %rsp, %rbp将当前栈指针的值rsp复制到基指针rbp中将其作为新的栈帧的基准。
接下来指令sub $0x50, %rsp将栈指针向下移动为函数的局部变量和临时存储空间分配一段空间。在这个例子中它分配了80字节0x50的十六进制值的空间。
在这两条指令之后可能会有其他指令用于函数的实际操作和计算。比如通过帧指针可以访问局部变量、函数参数和返回地址等信息。
最后指令pop %rbp将之前保存在栈中的基指针值弹出并恢复原来的基指针值。然后指令ret用于从函数中返回。
这段代码的作用是在函数执行前保存基指针然后在函数执行完毕后恢复基指针并返回到调用函数的位置。它还通过减少栈指针%rsp的值来为函数的局部变量和临时存储空间分配内存。这样可以确保函数在执行期间帧指针%rbp对局部变量和其他数据的正确访问并在函数返回时释放相应的空间。
参考资料
https://accu.org/journals/overload/31/173/bendersky/