pusha/popa 在函数 prologue/epilogue 中的用法?
Usage of pusha/popa in function prologue/epilogue?
阅读汇编教程,我看到“函数”prologue/epilogue 包含在:
push bp
mov bp, sp
---
pop bp
但我也看到一些其他教程使用 pusha
然后 popa
来保存寄存器。
那么为什么函数 prologue/epilogue 除了设置 bp 之外不执行 pusha/popa 来保存寄存器上下文?
Reading assembly tutorials, I saw that "function" prologue/epilogue...
对于真正的汇编语言(您不必遵守其他语言的调用约定),单词“函数 prologue/epilogue”没有意义。
对于“旨在遵守某些其他语言的调用约定的汇编语言”;你只需要 save/restore 一些寄存器(可能 none)。
举个例子;对于 CDECL,EAX、ECX 和 EDX 的内容可以被被调用者丢弃,并且永远不需要被被调用者 saved/restored(调用者需要保存它们);如果一个函数不使用任何其他寄存器,则被调用者也不需要保存或恢复任何其他寄存器。另请注意,“EBP 作为帧指针”是过时的垃圾(它存在是因为调试器不是很好并且在调试信息改进时变得毫无意义 - 例如 DWARF 调试信息等)。这些事情结合起来意味着像这样的事情有可接受的 CDECL 序言和结尾:
myFunction:
mov eax,12345 ;eax = returned value
ret
如果确实需要保存和恢复“很多”寄存器; pusha
很慢(微编码),一系列多个 push
指令也很慢(存储地址取决于最近修改的 ESP 中的值)。典型的方法是自己做,比如:
;Don't bother saving EAX, ECX, EDX.
sub esp,16 ;Create space to save 4 registers (but maybe more for local variables)
mov [esp],ebx
mov [esp+4],esi
mov [esp+8],edi
mov [esp+12],ebp
但是;成本是 space。在代码大小极其有限(例如“512 字节的一部分”)的引导加载程序中,聪明的程序员将使用真正的汇编语言(其中“函数 prologue/epilogue”没有意义),而初学者可能会使用 pusha
来保存 space(没有意识到他们没有理由关心其他编程语言的调用约定)。
它们不会保存 所有 寄存器,因为您并不总是需要保存所有寄存器。保存和恢复它们很慢。是的,这是一个很小的单一指令,看起来很节省,但它需要时间和堆栈 space。要了解保存的内容,请查看调用约定。
https://en.wikipedia.org/wiki/X86_calling_conventions
PUSHA/PUSHAD—推送所有通用寄存器
这些是慢指令。在 Skylake 上,PUSHA 需要 19 微指令和 8 个周期的吞吐量。 POPA 需要 18 微指令和 8c 的吞吐量。
此外,PUSHA/PUSHAD 在 64 位中无效。当 x86 扩展到 64b 时,它们被 AMD 正确地清除,然后被英特尔清除。
现代编译器反其道而行之,尽可能避免保存寄存器。 LLVM 执行称为收缩包装的分析,其中序言被向前推进以允许快速提前退出。
https://llvm.org/doxygen/ShrinkWrap_8cpp_source.html
这些太糟糕了,太糟糕了,不好,非常糟糕的指示。
阅读汇编教程,我看到“函数”prologue/epilogue 包含在:
push bp
mov bp, sp
---
pop bp
但我也看到一些其他教程使用 pusha
然后 popa
来保存寄存器。
那么为什么函数 prologue/epilogue 除了设置 bp 之外不执行 pusha/popa 来保存寄存器上下文?
Reading assembly tutorials, I saw that "function" prologue/epilogue...
对于真正的汇编语言(您不必遵守其他语言的调用约定),单词“函数 prologue/epilogue”没有意义。
对于“旨在遵守某些其他语言的调用约定的汇编语言”;你只需要 save/restore 一些寄存器(可能 none)。
举个例子;对于 CDECL,EAX、ECX 和 EDX 的内容可以被被调用者丢弃,并且永远不需要被被调用者 saved/restored(调用者需要保存它们);如果一个函数不使用任何其他寄存器,则被调用者也不需要保存或恢复任何其他寄存器。另请注意,“EBP 作为帧指针”是过时的垃圾(它存在是因为调试器不是很好并且在调试信息改进时变得毫无意义 - 例如 DWARF 调试信息等)。这些事情结合起来意味着像这样的事情有可接受的 CDECL 序言和结尾:
myFunction:
mov eax,12345 ;eax = returned value
ret
如果确实需要保存和恢复“很多”寄存器; pusha
很慢(微编码),一系列多个 push
指令也很慢(存储地址取决于最近修改的 ESP 中的值)。典型的方法是自己做,比如:
;Don't bother saving EAX, ECX, EDX.
sub esp,16 ;Create space to save 4 registers (but maybe more for local variables)
mov [esp],ebx
mov [esp+4],esi
mov [esp+8],edi
mov [esp+12],ebp
但是;成本是 space。在代码大小极其有限(例如“512 字节的一部分”)的引导加载程序中,聪明的程序员将使用真正的汇编语言(其中“函数 prologue/epilogue”没有意义),而初学者可能会使用 pusha
来保存 space(没有意识到他们没有理由关心其他编程语言的调用约定)。
它们不会保存 所有 寄存器,因为您并不总是需要保存所有寄存器。保存和恢复它们很慢。是的,这是一个很小的单一指令,看起来很节省,但它需要时间和堆栈 space。要了解保存的内容,请查看调用约定。
https://en.wikipedia.org/wiki/X86_calling_conventions
PUSHA/PUSHAD—推送所有通用寄存器
这些是慢指令。在 Skylake 上,PUSHA 需要 19 微指令和 8 个周期的吞吐量。 POPA 需要 18 微指令和 8c 的吞吐量。
此外,PUSHA/PUSHAD 在 64 位中无效。当 x86 扩展到 64b 时,它们被 AMD 正确地清除,然后被英特尔清除。
现代编译器反其道而行之,尽可能避免保存寄存器。 LLVM 执行称为收缩包装的分析,其中序言被向前推进以允许快速提前退出。
https://llvm.org/doxygen/ShrinkWrap_8cpp_source.html
这些太糟糕了,太糟糕了,不好,非常糟糕的指示。