我描述了一種通用的方式產生你this answer想要的任何的thunk代碼。讓我們爲你的情況重做它,只是爲了好玩。
假設爲你的類定義:
struct YourClass {
LRESULT YourMemberFunc(int nCode, WPARAM wParam, LPARAM lParam);
};
基本上你寫你的thunk在C++中有佔位符的實際地址:
LRESULT CALLBACK CallWndProc(int nCode, WPARAM wParam, LPARAM lParam) {
YourClass *x = reinterpret_cast<YourClass*>(0x1122112211221122);
__int64 im = 0x3344334433443344;
LRESULT (YourClass::*m)(int,WPARAM,LPARAM) = *reinterpret_cast<LRESULT (YourClass::**)(int,WPARAM,LPARAM)>(&im);
return (x->*m)(nCode, wParam, lParam);
}
,並調用它的方式,以防止編譯以內聯該呼叫:
int main() {
LRESULT (CALLBACK *volatile fp)(int, WPARAM, LPARAM) = CallWndProc;
fp(0, 0, 0);
}
在釋放中編譯並查看生成的程序集(在Visual Studio中,看到調試過程中的裝配窗口,打開「顯示代碼字節」):
4D 8B C8 mov r9,r8
4C 8B C2 mov r8,rdx
8B D1 mov edx,ecx
48 B9 22 11 22 11 22 11 22 11 mov rcx,1122112211221122h
48 B8 44 33 44 33 44 33 44 33 mov rax,3344334433443344h
48 FF E0 jmp rax
這將是您的thunk,與44 33 44 33 44 33 44 33
與指針替換爲您的會員(&YourClass::YourMemberFunc
)和22 11 22 11 22 11 22 11
在運行時用指向實際對象實例的指針替換。
發生了什麼事情在thunk的說明:
在64位調用約定(其中只有一個Windows下),前四個參數傳遞在rcx, rdx, r8, r9
寄存器,在這個秩序,從左到右。所以,當我們的thunk被調用,我們有
rcx = nCode, rdx = wParam, r8 = lParam
對於成員函數有控股this
指針隱式的第一個參數,所以在進入YourMemberFunc
時,我們必須有
rcx = this, rdx = nCode, r8 = wParam, r9 = lParam
編譯器生成的代碼正是這樣做的調整:它轉移r8 -> r9, rdx -> r8, ecx -> edx
,然後將我們的佔位符this = 1122112211221122
指定爲rcx
。現在它已經設置了參數,它可以繼續間接跳轉到函數本身。 rax
用於保存返回值,因此它不必在函數調用中保留。這就是爲什麼它在這裏用來臨時保存目標地址的原因,這爲尾部呼叫優化提供了機會(一次呼叫/返回對由單次跳轉代替)。
爲什麼我們必須做間接調用?否則我們會得到一個相對跳躍。但是我們不能使用硬編碼的相對跳轉,因爲thunk將被複制到內存中的不同地址!因此,我們在運行時設置絕對地址,並進行間接跳轉。
HTH
這聽起來很不對頭,你應該只對不屬於你的窗口使用鉤子。 –