X64-Disassemblierung mit Anmerkungen
Die folgende sehr einfache Funktion veranschaulicht die x64-Aufrufkonvention.
int Simple(int i, int j)
{
return i*5 + j + 3;
}
Dies kompiliert in Code wie folgt:
01001080 lea eax,[rdx+rcx*4] ; eax = rdx+rcx*4
01001083 lea eax,[rcx+rax+0x3] ; eax = rcx+rax+3
01001087 ret
Die Parameter i und j werden in den Registern ecx bzw. edx übergeben. Da es nur zwei Parameter gibt, verwendet die Routine den Stapel überhaupt nicht.
Der generierte Code nutzt drei Tricks aus, von denen einer spezifisch für die x64 ist:
Der Lea-Vorgang kann verwendet werden, um eine Reihe einfacher arithmetischer Vorgänge als einzelner Vorgang auszuführen. Die erste Anweisung speichert j+i*4 in eax, und die zweite Anweisung fügt dem Ergebnis i+3 hinzu, insgesamt j+i*5+3.
Viele Vorgänge, z. B. Addition und Multiplikation, können mit zusätzlicher Genauigkeit ausgeführt und dann auf die richtige Genauigkeit abgeschnitten werden. In diesem instance verwendet der Code 64-Bit-Addition und Multiplikation. Wir können das Ergebnis sicher auf 32 Bit abschneiden.
Bei x64 erweitert jeder Vorgang, der in ein 32-Bit-Register ausgegeben wird, das Ergebnis automatisch auf null. In diesem Fall hat die Ausgabe in eax den Effekt, dass das Ergebnis auf 32 Bit abgeschnitten wird.
Rückgabewerte werden im rax-Register übergeben. In diesem Fall befindet sich das Ergebnis bereits im rax-Register , sodass die Funktion zurückgibt.
Als Nächstes betrachten wir eine komplexere Funktion, um die typische x64-Demontage zu veranschaulichen:
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;
}
Wir durchlaufen diese Funktion und die entsprechende Assembly Zeile für Zeile.
Bei der Eingabe werden die Parameter der Funktion wie folgt gespeichert:
Rcx = pdisp.
Rdx = dispid.
r8 = fUnique.
r9 = pszExe.
Denken Sie daran, dass die ersten vier Parameter in Registern übergeben werden. Da diese Funktion nur über vier Parameter verfügt, werden keine im Stapel übergeben.
Die Assembly beginnt wie folgt:
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
Die Funktion beginnt mit dem Speichern nichtflüchtiger Register und dann dem Reservieren von Stapelspeicherplatz für lokale Variablen. Anschließend werden Parameter in nichtflüchtigen Registern gespeichert. Beachten Sie, dass das Ziel der mittleren beiden Mov-Anweisungen ein 32-Bit-Register ist, sodass sie implizit null auf 64 Bit erweitert werden.
IQueryAssociations *pqa;
HRESULT hr = AssocCreate(CLSID_QueryAssociations, IID_IQueryAssociations, (void**)&pqa);
Der erste Parameter für AssocCreate ist eine 128-Bit-CLSID, die vom Wert übergeben wird. Da dies nicht in ein 64-Bit-Register passt, wird die CLSID in den Stapel kopiert, und stattdessen wird ein Zeiger auf den Stapelspeicherort übergeben.
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
Die movdqu-Anweisung überträgt 128-Bit-Werte an und aus xmmn Registern. In diesem instance verwendet der Assemblycode ihn, um die CLSID in den Stapel zu kopieren. Der Zeiger auf die CLSID wird in r8 übergeben. Die beiden anderen Argumente werden in rcx und rdx übergeben.
if (SUCCEEDED(hr)) {
01001123 test eax,eax
01001125 jl ReturnEAX (01001281)
Der Code überprüft, ob der Rückgabewert erfolgreich ist.
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
Dies ist ein indirekter Funktionsaufruf mit einer C++-Vtable. Dieser Zeiger wird in rcx als erster Parameter übergeben. Die ersten drei Parameter werden in Registern übergeben, während der letzte Parameter auf dem Stapel übergeben wird. Die Funktion reserviert 16 Bytes für die Parameter, die in Registern übergeben werden, sodass der fünfte Parameter bei rsp+0x20 beginnt.
if (SUCCEEDED(hr)) {
0100114d mov ebx,eax ; ebx = hr
0100114f test ebx,ebx ; FAILED?
01001151 jl ReleasePQA (01001274) ; jump if so
Der Code in der Assemblysprache speichert das Ergebnis in ebx und überprüft, ob es sich um einen erfolgreichen Code handelt.
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
Erneut richten wir die Parameter ein, rufen eine Funktion auf, und testen dann den Rückgabewert auf Erfolg.
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
Die idiomatische Methode zum Nullieren eines Puffers auf x64 ist identisch mit 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 wird direkt in Computercode kompiliert. Die Anweisung lock xadd führt einen atomaren Austausch aus und fügt hinzu. Das Endergebnis wird in ecx gespeichert.
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]) = ...
Da x64 die cmov-Anweisung unterstützt, kann das ?: -Konstrukt ohne Sprung kompiliert werden.
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
Dieser Code initialisiert die restlichen Member von DISPPARAMS. Beachten Sie, dass der Compiler den Speicherplatz auf dem Stapel wiederverwendet, der zuvor von der CLSID verwendet wurde.
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
Der Code richtet dann die Parameter ein und ruft die Invoke-Methode auf.
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)
Der Code beendet den aktuellen Branch der Bedingten und überspringt den else-Branch .
} 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
Der Else-Branch .
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)
Der Rückgabewert wird in rax gespeichert, und dann werden die nicht flüchtigen Register vor der Rückgabe wiederhergestellt.