64位汇编第一讲——64位寄存器环境和编译环境20171229

Posted DennyChenD

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了64位汇编第一讲——64位寄存器环境和编译环境20171229相关的知识,希望对你有一定的参考价值。

一.64位汇编的历史渊源
    Intel公司和AMD公司都是研发复杂指令集的公司,AMD公司整体实力比Intel公司差一些,一直以来都是Intel公司的产品主导市场,在研发64位CPU时,为了提高CPU效率,Intel公司对之前版本的CPU指令进行了大改,研发出安腾CPU IA64_CPU,这款CPU虽然效率高,但不兼容之前的版本,所以并不被市场接受,而与此同时AMD公司研发出了能兼容之前版本的64位CPU,称为AMD64 CPU,这款CPU虽然不如安腾CPU效率高,但却因为兼容老版本而占据了市场,无奈Intel公司只得重新研发能兼容老版本的64位CPU,但为了有自己的特色,一些指令和AMD64 CPU有些不同,因为64位CPU名称很多,所以一般都会统称为X64 CPU。后来Intel公司研发了能支持一些安腾CPU的AMD库类CPU,这款CPU兼容老版本,同时还提高了一些效率,目前是64位汇编主流。
 
二.64位汇编的编译环境
    微软是支持Intel公司为提高效率而对CPU大改的,因为这样的话,微软产品的效率也会大大提高,相应于安腾CPU,微软也研发好了相应的编译器。对于其它版本的64位CPU,微软的新产品自然也会支持,像VS2013是支持64位CPU的,所以可以从VS2013的安装目录下找到支持64位汇编的编译器ml64.exe和链接器link.exe。要方便使用编译器和链接器,自然得添加环境变量,不过在Win——所有程序——VS2013——VS tools中便会有很多快捷方式,其中“VS2013 x64 本机工具命令提示”便可用做命令行来编译64位的*.asm文件和链接64位的*.obj文件,至于文件夹之间的切换,同cmd中操作。另外GoASM也可用于编译和链接x64位程序,效率相对VS2013要高一些。微软的编译器和链接器为X64位逆向首选,GoASM为64开发首选。
 
三.64位汇编寄存器
    64位汇编中寄存器除了段寄存器外,其余的都是64位,即8字节,所以栈结构的入栈和出栈字节数都要求模8。相比32位汇编,64位汇编的通用寄存器在数量上多了8个,共有16个通用寄存器,其中八个是兼容32位汇编的,分别是将原来的名称e**改成了r**,如eax改成rax,其余8个分别命名为R8、R9、……R15,EIP和EFlags都改成RIP和RFlags,高32位都是0.,浮点寄存器还是64位,于32位汇编中一样,分别称为MMX0(或记为FPR0)、……、MMX7(或记为FPR7)。另外,还增加了16个128位的多媒体寄存器——XMM0、……XMM15,俗称SSE指令,XMM0等多媒体指令又是256位寄存器YMM0等的低128位,这些多媒体寄存器的出现可以是得float型数据计算非常快,一次算四个,相当于原来的两倍,广泛应用于游戏、视频和音乐中。用于调试64位程序的调试器有WndDbg和X64Dbg。rax等初始8个通用寄存器,取其中的低32位、第16位、第8位分别用相应的寄存器取便是,如rax分别是eax、ax、al,R8等后来按序号命名的寄存器取64位、低32位、低16位、低8位分别用R8、R8D、R8W、R8B等。
 
四.64位汇编写弹Hello World!窗口实例
    先看汇编代码如下:
include user32.inc
includelib User32.lib
;extern MessageBoxA:Proc 如果包了*.inc头文件,则该条指针可注释掉
.data
    g_szContent db "Hello World!",0
    g_szTitle   db "title",0
.code
Winmain Proc
    sub rsp, 28h
    xor rcx, rcx
    mov rdx, offset g_szContent
    mov r8, offset g_szTitle
 
 
    xor r9, r9
    call MessageBoxA
    add rsp, 28h
    ret
Winmain Endp
end

    在64位编译命令下,指令如下:

ml64 /c Hello.asm 
link /subsystem:windows /entry:Main Hello.obj
   
  对于以上代码,可以发现64位中参数传参方式有些不同,那到底是怎样呢?64位CPU中通用寄存器多,所以函数的头四个参数都是寄存器传参,再有多的才用栈传参,一般函数的参数在四个以下的居多,所以大多数的函数调用时都用不上栈传参了,传参的四个寄存器统一规定依次是rcx、rdx、r8、r9,如此便不用如栈传参时先入栈右边,第5个寄存器之后用栈传,仍是由先往后开始入栈,入栈指令不用push,而改用mov。64位汇编中不使用32会汇编中的任何调用约定,只按以上说的调用方法,有点像_fastcall,但是如果有用到栈传参,则都是调用函数方平栈,这也是为了迎合不定参的统一平栈方式。
    如以上程序代码中,没有用到栈传参,那为何还要在函数头部抬栈,函数尾部减栈呢?这也是为了在需要用到很多寄存器时,存放rcx、rdx、r8、r9的值,以便充分利用寄存器,当然不一定每次都会存放,只有在用其中的寄存器值时,就会将其中的值存到腾出的栈中,在整个函数中,只需头部和尾部做一次操作就好,只要抬栈和减栈的数量相等就可。其实如果仅仅保存rcx、rdx、r8、r9的值,0x20字节的空间就够,之所以要腾0x28的空间,是因为有的函数内部会使用多媒体寄存器,那是16字节的,所以要求每个函数中使用的栈大小还得模16,一个函数中调用另一个函数最初始是要压入调用函数的返回地址的,需占用8个字节,如果加上抬的0x20字节,那么被调函数中将是从0x28字节开始,0x28不模16,如果抬栈0x28,则0x28 + 8 = 0x30模16,即0x10,所以抬栈的数量一般是模8,不模16,在大的项目中函数头部一般抬0x28的栈即可,如果是抬0x38自然也行,只是有些让费,如果是抬0x18,则就需要求本函数内调的函数只能分别是两个参数了,实为不妥。
    注意当函数内抬了栈后,获得参数和局部变量时,都得从原来的地址加上泰德栈字节数,如下:
MyAdd proc
  sub rsp, 28h
  mov [rsp+30h], ecx ;原本第一个参数地址该是rsp+8h,再加28h,变成rsp+30h
  mov [rsp+38h], edx
  mov eax, ecx
  add eax, edx
  add rsp, 28h
  ret
MyAdd endp
 
五. 64位汇编和32位汇编除了上面的还有那些区别
    64位汇编中声明和定义时都不需要写参数了(对于四个参数或四个参数以下的是,五个或五个参数以上的有如何就不太明白)。64位汇编只需写区,定义变量就好,不需要如32位汇编中头部的一些声明。64位汇编链接时需要指定程序入口,而32位汇编则不用,源于函数定义上一些不同,如下:
;64位汇编
.code
Main proc 
  ret
Main endp
end
;32位汇编
.code
start:
Main proc 
  ret
Main endp
end start
    32位汇编在代码中就已通过end start指定程序入口就是start,64位汇编中没有,所以在链接时需指定程序入口。

以上是关于64位汇编第一讲——64位寄存器环境和编译环境20171229的主要内容,如果未能解决你的问题,请参考以下文章

x64汇编第一课

64位汇编器开发环境?

优化系列汇编优化技术:ARM架构64位(AARCH64)汇编优化及demo

64位汇编第二讲——64位汇编中局部变量使用及抬栈方法29171230

GCC编译

现代32位或64位x86汇编