Disassembly x64 yang diannotasi
Fungsi yang sangat sederhana berikut mengilustrasikan konvensi panggilan x64.
int Simple(int i, int j)
{
return i*5 + j + 3;
}
Ini mengkompilasi ke kode seperti ini:
01001080 lea eax,[rdx+rcx*4] ; eax = rdx+rcx*4
01001083 lea eax,[rcx+rax+0x3] ; eax = rcx+rax+3
01001087 ret
Parameter i dan j masing-masing diteruskan dalam daftar ecx dan edx . Karena hanya ada dua parameter, rutinitas tidak menggunakan tumpukan sama sekali.
Kode tertentu yang dihasilkan mengeksploitasi tiga trik, salah satunya khusus untuk x64:
Operasi lea dapat digunakan untuk melakukan serangkaian operasi aritmatika sederhana sebagai operasi tunggal. Instruksi pertama menyimpan j+i*4 di eax, dan instruksi kedua menambahkan i+3 ke hasilnya, dengan total j+i*5+3.
Banyak operasi, seperti penambahan dan perkalian, dapat dilakukan dengan presisi ekstra, dan kemudian dipotong ke presisi yang benar. Dalam hal ini, kode menggunakan penambahan dan perkalian 64-bit. Kita dapat dengan aman memotong hasilnya menjadi 32 bit.
Pada x64, operasi apa pun yang menghasilkan register 32-bit secara otomatis tidak memperluas hasil. Dalam hal ini, output ke eax memiliki efek memotong hasil menjadi 32 bit.
Nilai yang dikembalikan diteruskan dalam register rax . Dalam hal ini, hasilnya sudah dalam register rax , sehingga fungsi kembali.
Selanjutnya kita mempertimbangkan fungsi yang lebih rumit untuk menunjukkan pembongkaran x64 yang khas:
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;
}
Kita akan melalui fungsi ini dan perakitan yang setara baris demi baris.
Saat dimasukkan, parameter fungsi disimpan sebagai berikut:
rcx = pdisp.
Rdx = dispid.
r8 = fUnique.
r9 = pszExe.
Ingat bahwa empat parameter pertama diteruskan dalam register. Karena fungsi ini hanya memiliki empat parameter, tidak ada yang diteruskan pada tumpukan.
Perakitan dimulai sebagai berikut:
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
Fungsi ini dimulai dengan menyimpan register nonvolatile, dan kemudian menyimpan ruang tumpukan untuk variabel lokal. Kemudian menyimpan parameter dalam register nonvolatile. Perhatikan bahwa tujuan instruksi dua mov tengah adalah register 32-bit, sehingga secara implisit tidak diperpanjang hingga 64 bit.
IQueryAssociations *pqa;
HRESULT hr = AssocCreate(CLSID_QueryAssociations, IID_IQueryAssociations, (void**)&pqa);
Parameter pertama untuk AssocCreate adalah CLSID 128-bit yang diteruskan oleh nilai. Karena ini tidak cocok dalam register 64-bit, CLSID disalin ke tumpukan, dan pointer ke lokasi tumpukan diteruskan sebagai gantinya.
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
Instruksi movdqu mentransfer nilai 128-bit ke dan dari xmmn register. Dalam hal ini, kode assembly menggunakannya untuk menyalin CLSID ke tumpukan. Pointer ke CLSID diteruskan dalam r8. Dua argumen lainnya diteruskan dalam rcx dan rdx.
if (SUCCEEDED(hr)) {
01001123 test eax,eax
01001125 jl ReturnEAX (01001281)
Kode memeriksa untuk melihat apakah nilai yang dikembalikan berhasil.
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
Ini adalah panggilan fungsi tidak langsung menggunakan Vtable C++. Pointer ini diteruskan dalam rcx sebagai parameter pertama. Tiga parameter pertama diteruskan dalam register, sementara parameter akhir diteruskan pada tumpukan. Fungsi ini mencadangkan 16 byte untuk parameter yang diteruskan dalam register, sehingga parameter kelima dimulai pada rsp+0x20.
if (SUCCEEDED(hr)) {
0100114d mov ebx,eax ; ebx = hr
0100114f test ebx,ebx ; FAILED?
01001151 jl ReleasePQA (01001274) ; jump if so
Kode bahasa perakitan menyimpan hasilnya dalam ebx, dan memeriksa untuk melihat apakah itu kode keberhasilan.
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
Sekali lagi, kami menyiapkan parameter dan memanggil fungsi, lalu menguji nilai pengembalian untuk keberhasilan.
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
Metode idiomatik untuk nol buffer pada x64 sama dengan 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 dikompilasi langsung ke kode mesin. Instruksi kunci xadd melakukan pertukaran atomik dan menambahkan. Hasil akhirnya disimpan dalam 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]) = ...
Karena x64 mendukung instruksi cmov , konstruksi ?: dapat dikompilasi tanpa menggunakan lompatan.
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
Kode ini menginisialisasi anggota DISPPARAMS lainnya. Perhatikan bahwa pengkompilasi menggunakan kembali ruang pada tumpukan yang sebelumnya digunakan oleh 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
Kode kemudian menyiapkan parameter dan memanggil metode 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)
Kode menyelesaikan cabang kondisional saat ini, dan melompati cabang lain .
} 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
Cabang lainnya .
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)
Nilai yang dikembalikan disimpan dalam raks, lalu register non-volatil dipulihkan sebelum dikembalikan.