這會編譯成如下程式碼:

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 特有的:

lea 運算可用來執行一系列簡單的算術運算作為單一運算。 第一條指令將 j+i*4 儲存在 eax 中,第二條指令將 i+3 加到結果中,總共為 j+i*5+3。

  • 許多運算,例如加法和乘法,可以以額外的精度完成,然後調整到正確的精度。 在此實例中,程式碼使用 64 位元加法和乘法。 我們可以安全地將結果截斷為 32 位。

  • 在 x64 上,任何輸出到 32 位暫存器的操作都會自動將結果擴展為零。 在這種情況下,輸出到 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 位值傳輸到 xmm n 暫存器或從 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++ vtable 的間接函數呼叫。 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 中,然後在傳回之前還原非易失性暫存器。

    x64 架構

    X86-64 維基百科

  •