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

64位汇编移植小结

2013年10月09日 ⁄ 综合 ⁄ 共 2450字 ⁄ 字号 评论关闭

这里的移植工作主要针对从32位x86cpu向intel及amd的64位cpu以及 xinxp64平台。编译环境为vs 2005.net,并且主要涉及汇编语言的移植。所用汇编语言编译器为yasm。

因为对于C语言,在vs2005.net环境下可直接进行64位平台的编译,其中注意事项已有许多文章涉及,这里不再复述。

A、而对于汇编语言,首先要注意,必须为纯汇编格式(*.asm文件)或intrinsic指令格式。其次,在64位平台上,不建议使用nasm编译器(我没找到其对于64位编译的相关支持),而建议采用yasm,这个汇编编译器是在nasm的基础上产生的,可以说对nasm的功能兼容,并且支持64位编译,详细地介绍及相关下载见:http://www.tortall.net/projects/yasm/

B、x86-64较x86-32多了8个通用寄存器,而且,每个通用寄存器都是64位宽,它们是:
rax,rbx,rcx,rdx,rsi,rdi,rsp,rbp
r8,r9,r10,r11,r12,r13,r14,r15
同时,x86-64全面支持x86-32和x86-16的通用寄存器:
eax,ax,al,ah,
ebx,bx,bl,bh,

但是,在对寄存器进行入/出栈操作时只能对相应的64位寄存器入/出栈,即:
( Instructions that modify the stack (push, pop, call, ret, enter, and leave) are implicitly 64-bit. Their 32-bit counterparts are not available, but their 16-bit counterparts are. Examples in NASM syntax: 
    push eax  ; illegal instruction
    push rbx  ; 1-byte instruction
    push r11  ; 2-byte instruction with REX prefix)

C、关于x64的调用约定:

在设计调用约定时,x64 体系结构利用机会清除了现有 Win32 调用约定(如 __stdcall、__cdecl、__fastcall、_thiscall 等)的混乱。在 Win64 中,只有一个本机调用约定和 __cdecl 之类的修饰符被编译器忽略。除此之外,减少调用约定行为还为可调试性带来了好处。

您需要了解的有关 x64 调用约定的主要内容是:它与 x86 fastcall 约定的相似之处。使用 x64 约定,会将前 4 个整数参数(从左至右)传入指定的 64 位寄存器:

RCX: 1st integer argument
RDX: 2nd integer argument
R8: 3rd integer argument
R9: 4th integer argument

前 4 个以外的整数参数将传递到堆栈。该指针被视为整数参数,因此始终位于 RCX 寄存器内。对于浮点参数,前 4 个参数将传入 XMM0 到 XMM3 的寄存器,后续的浮点参数将放置到线程堆栈上。

更进一步探究调用约定,即使参数可以传入寄存器,编译器仍然可以通过消耗 RSP 寄存器在堆栈上为其预留空间。至少,每个函数必须在堆栈上预留 32 个字节(4 个 64 位值)。该空间允许将传入函数的寄存器轻松地复制到已知的堆栈位置。不要求被调用函数将输入寄存器参数溢出至堆栈,但需要时,堆栈空间预留确保它可以这样做。当然,如果要传递 4 个以上的整数参数,则必须预留相应的额外堆栈空间。

让我们看一个示例。请考虑一个将两个整数参数传递给子函数的函数。编译器不仅会将值赋给 RCX 和 RDX,还会从 RSP 堆栈指针寄存器中减去 32 个字节。在被调用函数中,可以在寄存器(RCX 和 RDX)中访问参数。如果被调用代码因其他目的而需要寄存器,可将寄存器复制到预留的 32 字节堆栈区域中。图 6 显示在传递 6 个整数参数之后的寄存器和堆栈。

图 6 传递整数

对这里的参数压栈,需要说明的是(对于整形参数):
1、第四个参数后的参数按顺序反向压栈(8字节)
2、为前四个参数预留的32字节占空间是固定的、预分配的,不用被调用函数的编写者负责,也就是说,不管你是否用到,这部分栈空间都占用了,rsp所指向的位置如上图。
所以对于上面的例子,如果在被调用函数重要访问p5,p6正确的方法是:
mov eax,[esp+32+8];p5
mov ebx,[esp+32+16];p6

 D、一个很特别的寄存器 rip,相当于x86-32的eip. 在x86-32是不可直接访问的,如mov eax,eip是错的,但在x86-64位下却可以,如 mov,rax,qword ptr [rip+100]是对的。而且,它除了是个程序计数器外,也是个“数据基地址”,有此可见,它现在是身兼两职!为什么在x86-64位下要用rip做访问数据的基地址呢?因为,在x86-64下,DS,ES,CS,SS都没有实际意义了,也就是说,它们不再参与地址计算,只是为了兼容x86-32。FS,GS还是参与地址计算,它们两个和x86-32的意义相同。由于DS,ES,CS,SS没有意义了,所以在编译动态库时,符号变量是不允许直接出现在汇编代码中的,而必须与rip一起使用,即
mov rax,[symb wrt rip]或者lea rbx,[symb wrt rip]

参考网页:
AMD64 Architecture ---http://www.tortall.net/projects/yasm/wiki/AMD64
开始进行 64 位 Windows 系统编程之前需要了解的所有信息---http://www.microsoft.com/china/MSDN/library/Windev/64bit/issuesx64.mspx?mfr=true
The history of calling conventions, part 5: amd64---http://blogs.msdn.com/oldnewthing/archive/2004/01/14/58579.aspx

抱歉!评论已关闭.