以下链接解释了UNIX(BSD风格)和Linux的x86-32系统调用约定:
但是UNIX和Linux上的x86-64系统调用约定是什么?
AMD has an ABI specification that describes the calling convention to use on x86-64. All OSes follow it, except for Windows which has it's own x86-64 calling convention. Why?
Does anyone know the technical, historical, or political reasons for this difference, or is it purely a matter of NIHsyndrome?
I understand that different OSes may have different needs for higher level things, but that doesn't explain why for example the register parameter passing order on Windows is rcx - rdx …
在32位,我们有8个"通用"寄存器.使用64位,数量翻倍,但它似乎独立于64位变化本身.
现在,如果寄存器如此之快(没有存储器访问),为什么它们自然不存在呢?CPU构建器不应该在CPU中使用尽可能多的寄存器吗?为什么我们只有我们拥有的金额的逻辑限制是什么?
自从我上次编写手臂汇编程序以来已经有一段时间了,我对细节有点生疏.如果我从arm调用C函数,我只需要担心保存r0-r3和lr,对吧?
如果C函数使用任何其他寄存器,它是否负责保存堆栈中的那些并恢复它们?换句话说,编译器会生成代码来为C函数执行此操作.
例如,如果我在汇编程序函数中使用r10,我不必将其值放在堆栈或内存中,并在C调用后弹出/恢复它,是吗?
这是针对arm-eabi-gcc 4.3.0.
x86-64 System V ABI(用于除Windows之外的所有内容)过去常常访问http://x86-64.org/documentation/abi.pdf,但该网站现已脱离互联网.
该文件是否有新的权威主页?
我相信我理解linux x86-64 ABI如何使用寄存器和堆栈将参数传递给函数(参见之前的ABI讨论).我感到困惑的是,在函数调用中是否预期保留了哪些寄存器.也就是说,哪些寄存器被保证不被破坏?
假设 C++ 编译器为 CPU 寄存器未进行内存映射的体系结构编译了代码。并且假设同一个编译器为 CPU 寄存器保留了一些指针值。
例如,如果编译器无论出于何种原因(例如优化原因),为变量使用寄存器分配(不是谈论 register 关键字),并且我们打印对该变量的引用的值,编译器将返回其中一个保留的“地址值”。
该编译器会被视为符合标准吗?
从我所能收集到的(我还没有阅读整个事情 -工作草案,编程语言 C++ 标准),我怀疑该标准没有提到 RAM 内存或操作内存之类的东西,它定义了自己的内存模型相反,指针作为地址的表示(可能是错误的)。
现在由于寄存器也是一种内存形式,我可以想象将寄存器视为内存模型一部分的实现可能是合法的。
正如很久以前建议的那样,我总是在没有帧指针的情况下构建我的发布可执行文件(如果用/ Ox编译,这是默认的).
但是,现在我在http://research.microsoft.com/apps/pubs/default.aspx?id=81176这篇论文中读到,帧指针对性能没有太大影响.因此,完全优化它(使用/ Ox)或使用帧指针(使用/ Ox/Oy-)完全优化它并不会对性能产生重大影响.
微软似乎表示添加帧指针(/ Oy-)使调试变得更容易,但事实确实如此吗?
我做了一些实验并注意到:
关于帧指针的一般建议是什么?
使用Visual Studio 2010.
有一些调用约定(例如pascal
,stdcall
),但就我而言,C 确实使用cdecl
(C 声明)。这些约定中的每一个在调用者将参数加载到堆栈上的方式上都略有不同,分别是由哪个(调用者/被调用者)进行清理。
谈到清理,这是我的问题。我不明白:有三种不同的东西吗?
或者我应该怎么看他们?
此外,这个问题的目标基本上是可变参数函数如何在像 Pascal 这样的调用约定中工作,或者stdcall
被调用者应该在哪里清除/清理/恢复(我不知道哪个操作)堆栈 - 但他不知道有多少参数它会收到。
编辑
为什么将参数压入堆栈的顺序如此重要?您仍然拥有第一个参数(不是来自省略号的稳定参数),它为您提供有关 - 例如 - 变量参数数量的信息。并且还有“监护人”,它可以添加到省略号标点符号中,并且可以用作独立于调用约定的变量部分结束的标记。在这个链接中,如果调用者和被调用者在搞乱它们之前都保存了它们的状态,那么为什么调用者和被调用者都应该恢复这些寄存器的值?不应该只有其中一个(例如调用者)在调用函数之前将它们保存在堆栈中,仅此而已?另外,在同一个链接上
“因此,堆栈指针 ESP 可能会上下移动,但 EBP 寄存器保持固定。这很方便,因为这意味着我们始终可以将第一个参数称为 [EBP + 8],而不管在功能。”
推送的变量和局部变量在内存中是连续的。使用 EBP 推荐他们的优势在哪里?即使堆栈大小发生变化,它们之间也永远不会有一些动态偏移。
我读过的材料之一是这个站点(只是开始),以便更好地了解堆栈帧到底是什么。然后我继续 yt 并找到了这些堆栈概述和调用堆栈教程,但他们不知何故错过了我需要的部分。当你调用函数时到底发生了什么(我不明白指令“调用地址”后跟下一个指令a push
值到堆栈上,这意味着返回值)。谁来控制退货地址?呼叫者,召集者?被叫方?当被调用者返回时,程序继续执行一条指令,该指令是从寄存器中读取操作或什么?
我写了一个简单的汇编程序:
section .data
str_out db "%d ",10,0
section .text
extern printf
extern exit
global main
main:
MOV EDX, ESP
MOV EAX, EDX
PUSH EAX
PUSH str_out
CALL printf
SUB ESP, 8 ; cleanup stack
MOV EAX, EDX
PUSH EAX
PUSH str_out
CALL printf
SUB ESP, 8 ; cleanup stack
CALL exit
Run Code Online (Sandbox Code Playgroud)
我是NASM汇编程序和GCC,用于将目标文件链接到linux上的可执行文件.
本质上,该程序首先将堆栈指针的值放入寄存器EDX,然后将该寄存器的内容打印两次.但是,在第二次printf调用之后,打印到stdout的值与第一个不匹配.
这种行为似乎很奇怪.当我用EBX替换该程序中每次使用EDX时,输出的整数与预期的完全相同.我只能推断在printf函数调用期间某些时候EDX会被覆盖.
为什么会这样?如何确保我将来使用的寄存器与C lib函数不冲突?