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
bx cx dx 的高 8 ah bh ch dh 仍就是可以寻址的,但是不能用在所有类型的操作数。

指令指针寄存器 eip flags 也被扩展到 64 位(分别为 rip rflags )。

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中,然后在返回前恢复保存的寄存器。

猜你喜欢

转载自blog.csdn.net/oddxyz/article/details/6803213