X64處理器架構

 

X64處理器架構(翻譯的windbg幫助文檔)

X64處理器架構

X64架構是一個向後兼容的擴展的x86。提供了和x86相同的32位模式和一個新的64位模式。
術語“x64”包括AMD 64 Intel64,他們的指令集基本是相同的。
寄存器(Registers
X64x868個通用寄存器擴展爲64位,並且增加8個新的64位寄存器。64位寄存器命名以“r”開始,例如:eax擴展爲64位就是rax8個新的64位寄存器命名爲r8r15
每個寄存器的低32位,16位,8位可以作爲操作數直接尋址,這包括向esi這樣的寄存器,以前他的低8位不可以直接尋址。下表說明了64位寄存器的地位部分在彙編語言中的命名。

64-bit register

Lower 32 bits

Lower 16 bits

Lower 8 bits

rax

eax

ax

al

rbx

ebx

bx

bl

rcx

ecx

cx

cl

rdx

edx

dx

dl

rsi

esi

si

sil

rdi

edi

di

dil

rbp

ebp

bp

bpl

rsp

esp

sp

spl

r8

r8d

r8w

r8b

r9

r9d

r9w

r9b

r10

r10d

r10w

r10b

r11

r11d

r11w

r11b

r12

r12d

r12w

r12b

r13

r13d

r13w

r13b

r14

r14d

r14w

r14b

r15

r15d

r15w

r15b

對一個32位寄存器操作會自動用零擴展填充整個64位寄存器。對8位和16位寄存器的操作不會零擴展填充高位(這是和x86兼容的)。

ax
bxcxdx的高8ahbhchdh仍就是可以尋址的,但是不能用在所有類型的操作數。

指令指針寄存器eipflags也被擴展到64位(分別爲 riprflags)。

X64
處理器也提供幾個浮點寄存器:


·8個80位的x87寄存器

·8個64位的MMX寄存器

·以前的8個128位SSE寄存器增加到16個
調用約定(Calling Conventions

跟x86不同,在x64下c/c++編譯器僅支持一種調用約定,這種調用約定利用了在x64下可用寄存器的增加。

·前四個整型值或指針參數傳給寄存器rcx,rdx,r8,和r9。調用函數在堆棧上保留空間爲這些參數。

·前四個浮點參數傳給前四個SSE寄存器xmm0-xmm3.

·調用函數在堆棧上保留空間爲傳遞給寄存器的參數。被調用函數利用這些空間將寄存器的內容存入堆棧。

·任何其他參數存入堆棧

·一個整型或指針返回值存在rax寄存器中,如果返回值是浮點則返回在xmm0中

·rax,rcx,rdx,r8-r11是要變化的

·rbx, rbp, rdi, rsi, r12-r15不變

這個調用約定跟c++是非常相似的:this指針作爲第一個隱含的參數被傳遞,後面三個參數傳遞給寄存器,剩下的存入堆棧。
尋址方式(Addressing Modes
64位模式下的尋址方式類似於x86但是不是完全相同。
·指令涉及到64位寄存器會自動執行64位精度。(例如mov rax,[rbx]是將rbx所指向的地址開始的8字節存入rax)
·一個特別的指令mov的立即數常量或常量地址已經增加爲64位,對於其他的指令立即數常量或常量指針仍就是32位。
·x64提供了一個新的rip相關的尋址模式。如果指令涉及到常量地址以rip爲偏移。例如mov rax,[addr]操作將地址addr+rip指向地址開始的8字節數存入rax。
Jmp,call,push和pop指令涉及到的指令指針和堆棧指針都爲64位在x64中。

x64 指令集

大多數x86指令在x64的64位模式下是有效的。在64位模式下一些很少用到的指令不再支持。例如:

·BCD碼算術指令:AAA,AAD,AAM,AAS,DAA,DAS

·BOUND

·PUSHAD

POPAD

·大多數的操作要處理段寄存器,例如PUSH DS 和 POP DS。(對FS和 GS段寄存器的操作仍然有效)


X64
指令集包括最近增加的x86指令例如SSE2,程序中可以自由的使用這些指令。

數據傳送(Data Transfer

X64提供新的MOV指令的變量來處理64位立即數常量或內存地址。

MOV

r,#n

r = #n

MOV

rax, m

傳送64位地址處的內容到 rax.

MOV

m, rax

傳送 rax
的內容到64位地址處

X64也提供一個新的指令符號擴展32位到64位

MOVSXD

r1, r/m

傳送 DWORD 符號擴展到 QWORD.

一般MOV操作32位子寄存器自動零擴展到64位,因此沒有MOVZXD指令。

兩個SSE指令可以用來傳送128位值(例如GUIDs)從內存到xmmn 寄存器或相反。

MOVDQA

r1/m, r2/m

  傳送128位對齊值到xmmn 寄存器,或相反

MOVDQU

r1/m, r2/m

傳送128位值(不是必須對齊)到寄存器或相反

數據轉換(Data Conversion)

CDQE

轉換 dword (eax) 爲 qword (rax).

CQO

轉換 qword (rax) 爲 oword (rdx:rax).

字符串操作(String Manipulation)

MOVSQ

將rsi指向的字符串傳送到rdi指向地址

CMPSQ

比較rsi和rdi所指向地址的字符串

SCASQ

掃描rdi指向的地址的qword並與rax比較

LODSQ

將rsi指向的地址的qword傳入rax

STOSQ

將rax的值傳入rdi指向的地址

x64反彙編

下面一個非常簡單的函數來說明x64調用約定。
int Simple(int i, int j)
{
    return i*5 + j + 3;
}

編譯後的代碼是這樣:

01001080 lea     eax,[rdx+rcx*4]        ; eax = rdx+rcx*4
01001083 lea     eax,[rcx+rax+0x3]      ; eax = rcx+rax+3
01001087 ret
ij參數被傳遞給ecxedx寄存器,由於這僅有兩個參數這個函數根本沒用堆棧。

這段生成的代碼有三個地方值得注意,其中有一個事x64特有的:
1.
lea指令被用來執行一系列的簡單算術操作,第一個將j+i*4存入eax,第二個操作加上i+3存入結果中,最後爲j+i*5+3
2.
許多操作例如加和乘,可以處理中用擴展精度,然後在舍入到正確精度。在這個例子中代碼用的是64位加和乘操作。我們可以安全的縮短結果到32位。
3.
x64中,任何輸出到32位寄存器的操作會自動零擴展到64位,在這個例子中,輸出到eax中有效的縮短到32位。
返回值被傳送到rax寄存器,在這個例子中,結果已經在rax寄存器中,因此函數直接返回。
下面我們考慮一個更復雜的函數來說明典型的x64反彙編:
HRESULT Meaningless(IDispatch *pdisp, DISPID dispid, BOOL fUnique, LPCWSTR pszExe)
{
    IQueryAssociations *pqa;
    HRESULT hr = AssocCreate(CLSID_QueryAssociations, IID_IQueryAssociations, (void**)&pqa);
    if (SUCCEEDED(hr)) {
        hr = pqa->Init(ASSOCF_INIT_BYEXENAME, pszExe, NULL, NULL);
        if (SUCCEEDED(hr)) {
            WCHAR wszName[MAX_PATH];
            DWORD cchName = MAX_PATH;
            hr = pqa->GetString(0, ASSOCSTR_FRIENDLYAPPNAME, NULL, wszName, &cchName);
            if (SUCCEEDED(hr)) {
                VARIANTARG rgvarg[2] = { 0 };
                V_VT(&rgvarg[0]) = VT_BSTR;
                V_BSTR(&rgvarg[0]) = SysAllocString(wszName);
                if (V_BSTR(&rgvarg[0])) {
                    DISPPARAMS dp;
                    LONG lUnique = InterlockedIncrement(&lCounter);
                    V_VT(&rgvarg[1]) = VT_I4;
                    V_I4(&rgvarg[1]) = fUnique ? lUnique : 0;
                    dp.rgvarg = rgvarg;
                    dp.cArgs = 2;
                    dp.rgdispidNamedArgs = NULL;
                    dp.cNamedArgs = 0;
                    hr = pdisp->Invoke(dispid, IID_NULL, 0, DISPATCH_METHOD, &dp, NULL, NULL, NULL);
                    VariantClear(&rgvarg[0]);
                    VariantClear(&rgvarg[1]);
                } else {
                    hr = E_OUTOFMEMORY;
                }
            }
        }
        pqa->Release();
    }
    return hr;
}
我們將要進入這個函數並且對每行進行反彙編。

當進入的時候這個函數的參數被存儲爲下面這樣:
  • rcx = pdisp.
  • rdx = dispid.
  • r8 = fUnique.
  • r9 = pszExe.

前四個參數被傳入寄存器中,由於這個函數僅有四個參數,沒有一個被存入堆棧中。

下面開始彙編代碼:

Meaningless:
010010e0 push    rbx                    ; save
010010e1 push    rsi                    ; save
010010e2 push    rdi                    ; save
010010e3 push    r12d                   ; save
010010e5 push    r13d                   ; save
010010e7 push    r14d                   ; save
010010e9 push    r15d                   ; save
010010eb sub     rsp,0x2c0              ; reserve stack
010010f2 mov     rbx,r9                 ; rbx = pszExe
010010f5 mov     r12d,r8d               ; r12 = fUnique (zero-extend)
010010f8 mov     r13d,edx               ; r13 = dispid  (zero-extend)
010010fb mov     rsi,rcx                ; rsi = pdisp

這個函數開始保存不可變的寄存器,然後保留堆棧空間爲局部變量,再保存參數到不可變寄存器。注意中間兩個mov指令的目的操作數是32位寄存器,因此會隱含零擴展到64位。

IQueryAssociations *pqa;
    HRESULT hr = AssocCreate(CLSID_QueryAssociations, IID_IQueryAssociations, (void**)&pqa);
AssocCreate
的第一個參數是一個128位的CLSID值,由於寄存器是64位,這個CLSID被複制到堆棧裏,被傳遞的是指向堆棧地址的指針。

010010fe movdqu  xmm0,oword ptr [CLSID_QueryAssociations (01001060)]
01001106 movdqu  oword ptr [rsp+0x60],xmm0  ; temp buffer for first parameter
0100110c lea     r8,[rsp+0x58]          ; arg3 = &pqa
01001111 lea rdx,[IID_IQueryAssociations (01001070)] ; arg2 = &IID_IQueryAssociations
01001118 lea     rcx,[rsp+0x60]         ; arg1 = &temporary
0100111d call qword ptr [_imp_AssocCreate (01001028)] ; call

movdqu
指令傳遞128位的值到xmmn寄存器或取128位值從xmmn寄存器,在這個例子的彙編代碼中它複製CLSID到堆棧裏。指向CLSID的指針傳遞到r8,其他兩個參數傳遞到rcxrdx

    if (SUCCEEDED(hr)) {

01001123 test    eax,eax
01001125 jl      ReturnEAX (01001281)

這個代碼檢查返回值是否成功。
hr = pqa->Init(ASSOCF_INIT_BYEXENAME, pszExe, NULL, NULL);

0100112b mov     rcx,[rsp+0x58]         ; arg1 = pqa
01001130 mov     rax,[rcx]              ; rax = pqa.vtbl
01001133 xor     r14d,r14d              ; r14 = 0
01001136 mov     [rsp+0x20],r14         ; arg5 = 0
0100113b xor     r9d,r9d                ; arg4 = 0
0100113e mov     r8,rbx                 ; arg3 = pszExe
01001141 mov     r15d,0x2               ; r15 = 2 (for later)
01001147 mov     edx,r15d               ; arg2 = 2 (ASSOCF_INIT_BY_EXENAME)
0100114a call    qword ptr [rax+0x18]   ; call Init method

這是一個用c++虛函數表的間接調用。This指針傳遞到rcx作爲第一個參數。前三個參數傳遞到寄存器中,最後一個參數傳遞到堆棧上。這個函數保留16字節空間爲寄存器中參數的傳遞,因此第五個參數地址在rsp+0x20

if (SUCCEEDED(hr)) {

0100114d mov     ebx,eax                ; ebx = hr
0100114f test    ebx,ebx                ; FAILED?
01001151 jl      ReleasePQA (01001274)  ; jump if so
這個彙編語言代碼保存返回值在ebx中,並且檢查返回值是否成功。

WCHAR wszName[MAX_PATH];
            DWORD cchName = MAX_PATH;
            hr = pqa->GetString(0, ASSOCSTR_FRIENDLYAPPNAME, NULL, wszName, &cchName);
            if (SUCCEEDED(hr)) {

01001157 mov     dword ptr [rsp+0x50],0x104 ; cchName = MAX_PATH
0100115f mov     rcx,[rsp+0x58]         ; arg1 = pqa
01001164 mov     rax,[rcx]              ; rax = pqa.vtbl
01001167 lea     rdx,[rsp+0x50]         ; rdx = &cchName
0100116c mov     [rsp+0x28],rdx         ; arg6 = cchName
01001171 lea     rdx,[rsp+0xb0]         ; rdx = &wszName[0]
01001179 mov     [rsp+0x20],rdx         ; arg5 = &wszName[0]
0100117e xor     r9d,r9d                ; arg4 = 0
01001181 mov     r8d,0x4                ; arg3 = 4 (ASSOCSTR_FRIENDLYNAME)
01001187 xor     edx,edx                ; arg2 = 0
01001189 call    qword ptr [rax+0x20]   ; call GetString method
0100118c mov     ebx,eax                ; ebx = hr
0100118e test    ebx,ebx                ; FAILED?
01001190 jl      ReleasePQA (01001274)  ; jump if so

再次傳遞參數調用一個函數,並且測試返回值是否成功。

VARIANTARG rgvarg[2] = { 0 };

01001196 lea     rdi,[rsp+0x82]         ; rdi = &rgvarg
0100119e xor     eax,eax                ; rax = 0
010011a0 mov     ecx,0x2e               ; rcx = sizeof(rgvarg)
010011a5 rep     stosb                  ; Zero it out
x64下對一個緩衝區清零的方法和x86是相同的。

V_VT(&rgvarg[0]) = VT_BSTR;
                V_BSTR(&rgvarg[0]) = SysAllocString(wszName);
                if (V_BSTR(&rgvarg[0])) {

010011a7 mov     word ptr [rsp+0x80],0x8 ; V_VT(&rgvarg[0]) = VT_BSTR
010011b1 lea     rcx,[rsp+0xb0]         ; arg1 = &wszName[0]
010011b9 call    qword ptr [_imp_SysAllocString (01001010)] ; call
010011bf mov     [rsp+0x88],rax         ; V_BSTR(&rgvarg[0]) = result
010011c7 test    rax,rax                ; anything allocated?
010011ca je      OutOfMemory (0100126f) ; jump if failed

                    DISPPARAMS dp;
                    LONG lUnique = InterlockedIncrement(&lCounter);

010011d0 lea     rax,[lCounter (01002000)]
010011d7 mov     ecx,0x1
010011dc lock    xadd [rax],ecx             ; interlocked exchange and add
010011e0 add     ecx,0x1
InterlockedIncrement編譯爲機器碼,lock xadd指令執行自動交換數據並且相加,最後結果存入ecx中。

                    V_VT(&rgvarg[1]) = VT_I4;
                    V_I4(&rgvarg[1]) = fUnique ? lUnique : 0;

010011e3 mov     word ptr [rsp+0x98],0x3    ; V_VT(&rgvarg[1]) = VT_I4;
010011ed mov     eax,r14d                   ; rax = 0 (r14d is still zero)
010011f0 test    r12d,r12d                  ; fUnique set?
010011f3 cmovne  eax,ecx                    ; if so, then set rax=lCounter
010011f6 mov     [rsp+0xa0],eax             ; V_I4(&rgvarg[1]) = ...
由於x64支持cmov指令,所以?:結構被編譯後沒有用調轉指令。

dp.rgvarg = rgvarg;
                    dp.cArgs = 2;
                    dp.rgdispidNamedArgs = NULL;
                    dp.cNamedArgs = 0;

010011fd lea     rax,[rsp+0x80]             ; rax = &rgvarg[0]
01001205 mov     [rsp+0x60],rax             ; dp.rgvarg = rgvarg
0100120a mov     [rsp+0x70],r15d            ; dp.cArgs = 2 (r15 is still 2)
0100120f mov     [rsp+0x68],r14             ; dp.rgdispidNamedArgs = NULL
01001214 mov     [rsp+0x74],r14d            ; dp.cNamedArgs = 0
這段代碼初始化DISPPARAMS結構剩下的成員。注意編譯器重用了先前被CLSID佔用的堆棧空間。

                    hr = pdisp->Invoke(dispid, IID_NULL, 0, DISPATCH_METHOD, &dp, NULL, NULL, NULL);

01001219 mov     rax,[rsi]                  ; rax = pdisp.vtbl
0100121c mov     [rsp+0x40],r14             ; arg9 = 0
01001221 mov     [rsp+0x38],r14             ; arg8 = 0
01001226 mov     [rsp+0x30],r14             ; arg7 = 0
0100122b lea     rcx,[rsp+0x60]             ; rcx = &dp
01001230 mov     [rsp+0x28],rcx             ; arg6 = &dp
01001235 mov     word ptr [rsp+0x20],0x1    ; arg5 = 1 (DISPATCH_METHOD)
0100123c xor     r9d,r9d                    ; arg4 = 0
0100123f lea     r8,[GUID_NULL (01001080)]  ; arg3 = &IID_NULL
01001246 mov     edx,r13d                   ; arg2 = dispid
01001249 mov     rcx,rsi                    ; arg1 = pdisp
0100124c call    qword ptr [rax+0x30]       ; call Invoke method
0100124f mov     ebx,eax                    ; hr = result

這段代碼設置參數並且調用Invoke方法。
                    VariantClear(&rgvarg[0]);
                    VariantClear(&rgvarg[1]);

01001251 lea     rcx,[rsp+0x80]             ; arg1 = &rgvarg[0]
01001259 call    qword ptr [_imp_VariantClear (01001018)]
0100125f lea     rcx,[rsp+0x98]             ; arg1 = &rgvarg[1]
01001267 call    qword ptr [_imp_VariantClear (01001018)]
0100126d jmp     ReleasePQA (01001274)
這段代碼完成當前的條件分支,並跳過else分支。

                } else {
                    hr = E_OUTOFMEMORY;
                }
            }

OutOfMemory:
0100126f mov     ebx,0x8007000e             ; hr = E_OUTOFMEMORY
        pqa->Release();
ReleasePQA:
01001274 mov     rcx,[rsp+0x58]             ; arg1 = pqa
01001279 mov     rax,[rcx]                  ; rax = pqa.vtbl
0100127c call    qword ptr [rax+0x10]       ; release
else
分支

    return hr;
}
0100127f mov     eax,ebx                    ; rax = hr (for return value)
ReturnEAX:
01001281 add     rsp,0x2c0                  ; clean up the stack
01001288 pop     r15d                       ; restore
0100128a pop     r14d                       ; restore
0100128c pop     r13d                       ; restore
0100128e pop     r12d                       ; restore
01001290 pop     rdi                        ; restore
01001291 pop     rsi                        ; restore
01001292 pop     rbx                        ; restore
01001293 ret                                ; return (do not pop arguments)

返回值存儲在rax中,然後在返回前恢復保存的寄存器。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章