现在的位置: 首页 > 综合 > 正文

GCC在AMD64平台下的参数传递

2017年10月31日 ⁄ 综合 ⁄ 共 2765字 ⁄ 字号 评论关闭

from: http://hi.baidu.com/bluebanboom/blog/item/381959af65ff36fbfaed5068.html

闲着没事的时候就拿自己动手写操作系统里的例子来编译,但我装的是AMD64的ubuntu,而实际上这并没带来多少好处,反倒带来不少麻烦!
在编译第五章中汇编和C混合编程的例子时遇到了麻烦,因为用的nasm不支持64位的elf格式,我就找到了yasm,yasm是nasm的完全重写,
输出格式支持64位的elf,但是编译链接后运行程序始终提示段错误。
一开始我以为可能yasm的输出与gcc的输出不一样,为了验证我的想法,我写了一个简单,不需要传递参数的函数,结果链接后的
程序居然可以运行。看来可能是参数传递上出了问题。
一开始,我用hte来反汇编看函数调用过程,但是没个比较,也不知道问题出在了哪儿,而且hte的反汇编效果很差。
还好,我还有个32位的debian而且,在debian下,程序顺利编译通过了。
我就在debian下用ida反汇编查看
两个程序反汇编的结果一对照,发现
在debian下编译的程序是通过堆栈来传递参数的
而在ubuntu amd64下编译的程序是通过寄存器来传递的参数
而原来的汇编代码是通过堆栈来处理参数,当然会出问题。
修改后的foo.asm
============================
extern choose ; int choose(int a, int b);

[section .data] ; 数据在此

num1st  dd 5
num2nd  dd 6

[section .text] ; 代码在此

global _start ; 我们必须导出 _start 这个入口,以便让链接器识别。
global myprint ; 导出这个函数为了让 bar.c 使用

_start:
 ;mov edi,3  ; ┓
 ;mov esi,4 ; ┃
 mov esi,[num2nd]
 mov edi,[num1st]
 ;push num1st
 ;push  num2nd
 call choose  ; ┣ choose(num1st, num2nd);
 add esp, 4  ; ┛

 mov ebx, 0
 mov eax, 1  ; sys_exit
 int 0x80  ; 系统调用

; void myprint(char* msg, int len)
myprint:
 ;mov edx, [esp + 8] ; len
 ;mov ecx, [esp + 4] ; msg
 mov edx, esi ; len
 mov ecx, edi
 mov ebx, 1
 mov eax, 4  ; sys_write
 int 0x80  ; 系统调用
 ret
 
================================================

然后我想知道gcc的参数传递方式,就在网上搜了一哈,找到了下边的资料了:

==========================================

版权为 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

【上篇】
【下篇】

抱歉!评论已关闭.