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位匯編中沒有,所以在鏈接時需指定程序入口。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM