版权为 win_hate 所有, 转载请保留作者名字
我这段时间要把以前的一个 x86_32 的 linux 程序移植到 x86_64(AMD) 的 linux 环境里. 由于写的是数学算法, 64 与 32 位有很大不同, 代码实际上要重写. 看了点资料后, 觉得 AMD64 的扩展于以前 16 到 32 位的扩展很类似, e**, 扩展为 r**, 此外还多了8个通用寄存器 r8~r15.指令格式与32位的极为相似. 我觉得比较容易, 所以没再仔细看, 就开始动手写了.
我的程序由若干个汇编模块于与若干个c模块构成, 很多c模块要调用汇编模块. 作为试验, 我先写了个简单的汇编函数, 然后用c来调用. 结果算出来的值始终是错误的. 这令我很恼火, 因为函数很简单, 没有多少出错的余地. 后来我把程序反汇编出来, 错误马上浮现出来了, 函数的参数居然是通过寄存器来传递的. 我凭以前的经验, 从堆栈里取参数, 算出的结果当然不对了. 我以前不是没碰到过用寄存器传递参数的情况, 但所在的环境都不是 pc. 在 x86_32/linux 中, 即使用 -O3 优化选项, gcc 仍通过栈来传递参数的.
所以我们现在知道, 在 x86_64/linux/gcc3.2 中, 即使不打开优化选项, 函数的参数也会通过寄存器来传递, 这肯定是阔了的表现(通用寄存器多了).
我试验了多个参数的情况,发现一般规则为, 当参数少于7个时, 参数从左到右放入寄存器: rdi, rsi, rdx, rcx, r8, r9。当参数为 7 个以上时, 前 6 个与前面一样, 但后面的依次从 "右向左" 放入栈中。
例如:
CODE
(1) 参数个数少于7个:
f (a, b, c, d, e, f);
a->%rdi, b->%rsi, c->%rdx, d->%rcx, e->%r8, f->%r9
g (a, b)
a->%rdi, b->%rsi
有趣的是, 实际上将参数放入寄存器的语句是从右到左处理参数表的, 这点与32位的时候一致.
CODE
2) 参数个数大于 7 个的时候
H(a, b, c, d, e, f, g);
a->%rdi, b->%rsi, c->%rdx, d->%rcx, e->%rax
g->8(%esp)
f->(%esp)
call H
易失寄存器:
%rax, %rcx, %rdx, %rsi, %rdi, %r8, %r9 为易失寄存器, 被调用者不必恢复它们的值。
显然,这里出现的寄存器大多用于参数传递了, 值被改掉也无妨。而 %rax, %rdx 常用于
数值计算, %rcx 常用于循环计数,它们的值是经常改变的。其它的寄存器为非易失的,也
就是 rbp, rbx, rsp, r10~r15 的值如果在汇编模块中被改变了,在退出该模块时,必须将
其恢复。
教训:
用汇编写模块, 然后与 c 整合, 一定要搞清楚编译器的行为, 特别是参数传递的方式. 此外, 我现在比较担心的一点是, 将来如果要把程序移植到 WIN/VC 环境怎么办? 以前我用cygwin的gcc来处理汇编模块, 用vc来处理c模块, 只需要很少改动. 现在的问题是, 如果VC用不同的参数传递方式, 那我不就麻烦了?
补充:
前面的参数 a, b, c, d 等, 都是整数, 长整数, 或指针, 也就是说, 能放到寄存器里头的. 如果你要传递一个很大的结构, 我估计编译器也只能通过栈来传递了.
环境为 AMD Athlon64, Mandrak linux 9.2, GCC3.3.1
另可参考
http://en.wikipedia.org/wiki/X86_calling_conventions
分享到:
相关推荐
- 在启动Linux内核之前,Bootloader会设置好CPU的寄存器,包括r0、r1和r2等,其中r0通常用于存储内核的入口地址,而r1和r2则用于传递启动参数和内核基地址等信息。 3. **环境变量配置**: - Bootloader需要配置...
8. **kernel加载与启动**:介绍如何将Linux kernel映像加载到内存并启动,包括内核参数传递和启动过程。 9. **调试技巧**:提供U-Boot的调试方法,如使用串口、JTAG工具进行问题定位。 10. **实战案例**:通过实际...
- **用户空间到内核系统调用接口设计**:设计用于高效传递五个或更多参数(通过寄存器)。这类似于RISC OS的约定,但不使用条件码来指示错误。 - **浮点运算处理**:通过FP指令集进行浮点运算。如果存在FPU则由其...
- **性能提升**:例如,过程参数现在通过寄存器传递而非堆栈,大大减少了内存读写操作的数量,从而提高了性能。 #### 3. **x86-64 架构的关键特点** - **寄存器数量增加**:x86-64 架构采用了 16 个通用寄存器,...
了解并遵循正确的调用约定至关重要,因为不同的约定规定了参数传递方式、返回值处理以及谁负责清理栈。比如,C标准函数通常使用cdecl约定,而系统级函数可能使用stdcall约定。汇编代码需要按照C函数的约定来设置...
在讨论调用约定的同时,这些示例也为理解函数参数传递、堆栈操作和递增顺序等概念提供了实践案例。 最后,由于文档中存在OCR技术的原因导致的文字识别错误,需要开发者仔细识别代码,并确保代码在实际使用时是准确...
X86-64是由AMD提出的,能够兼容32位系统,并引入了新的特性,如64位计算模式,允许32位操作系统运行64位应用程序,这推动了编译器如GCC的进步。 **寄存器详解** X86-64架构中,寄存器的数量和功能都有所扩展。通用...
1. **环境准备**: 确保aarch64平台上的依赖库已安装,例如jemalloc、gcc等。 2. **解压**: 解压`redis6.2.7`压缩包。 3. **配置**: 根据需求修改redis.conf配置文件。 4. **编译安装**: 执行编译脚本来编译和安装...
在C/C++编程中,`#pragma`指令是一种编译器特定的预处理器指令,用于向编译器传递非标准的信息或控制编译器的行为。虽然`#pragma`指令不是C/C++语言标准的一部分,但大多数现代编译器(如GCC、Clang、MSVC等)都支持...
3. **函数调用约定**:详细介绍了栈帧的构建与销毁、参数传递方式等内容,这些都是实现高效函数调用的关键。 4. **名称修饰与解修饰**:解释了不同编译器下函数名称的处理方式,这对于跨编译器的代码兼容性尤为...
在x86-64架构下,函数调用和栈帧的管理是高效且复杂的。首先,我们要了解这个架构下的通用寄存器及其用途。x86-64提供了16个64位通用寄存器,其中%rax通常用于存储函数的返回值,同时也参与乘法和除法运算。在imul...
5. **编译多个源文件**:当一个程序由多个源文件组成时,可以将所有源文件作为参数传递给GCC。 ```sh gcc -o test first.c second.c third.c ``` #### 四、GCC的高级功能 - **优化选项**:GCC提供了多种优化...
在x64环境下,这个过程需要遵循x64架构的规则和约定,包括寄存器使用、参数传递、堆栈管理和指令编码等。 首先,x64架构的寄存器分配比32位的x86更为丰富。通用寄存器从8个扩展到16个,分别是RAX、RBX、RCX、RDX、...
例如,通过使用`int 0x80` 或 `sysenter` 指令来发起Linux系统调用,并解释每个系统调用号对应的函数及其参数传递方式。 在实际应用中,汇编语言常用于编写性能敏感的代码段,例如初始化程序、中断处理程序、设备...
在MSVC-x86-64编译的代码中,函数调用可能会使用不同的寄存器传递参数,并且栈的管理也有所不同。 三、结论 这个LITE版本的《Reverse Engineering for Beginners》为初学者提供了一个快速了解逆向工程基础知识的...
你可以通过传递参数来定制配置,例如指定安装路径、启用或禁用特定功能。 4. **编译**:执行`make`命令来编译源代码。这将生成可执行文件和库。如果编译过程中遇到问题,检查错误信息并解决依赖项或配置问题。 5. ...
对于Windows用户,压缩包内的“python-3.8.2.amd64_3670678904.exe”是Python 3.8.2的64位安装程序,AMD64指的是它适用于基于AMD64架构的处理器,实际上也兼容Intel的x86_64架构。这个安装程序将包含Python解释器、...
3. **函数**:定义和调用函数,参数传递,以及函数重载和递归。 4. **面向对象编程**:类、对象、继承、多态、封装和抽象。 5. **模板**:泛型编程,用于创建可处理不同数据类型的函数或类。 6. **异常处理**:try-...