freepeople性欧美熟妇, 色戒完整版无删减158分钟hd, 无码精品国产vα在线观看DVD, 丰满少妇伦精品无码专区在线观看,艾栗栗与纹身男宾馆3p50分钟,国产AV片在线观看,黑人与美女高潮,18岁女RAPPERDISSSUBS,国产手机在机看影片

正文內(nèi)容

一種實現(xiàn)win32消息處理處理函數(shù)的新方法基于(編輯修改稿)

2025-06-12 18:08 本頁面
 

【文章內(nèi)容簡介】 類型安全檢查這一關(guān),從而達到我們的數(shù)據(jù)轉(zhuǎn)換目的。當(dāng)然,我們通常不會這樣做,因為這樣畢竟是類型不安全的轉(zhuǎn)換,他只適用于特定的非常規(guī)的 (函數(shù)調(diào)用 )場合。 好,我們現(xiàn)在已經(jīng)得到了該成員函數(shù)的內(nèi)存地址了,下買面我們通過一個 更為奇怪的 方式來調(diào)用成員函數(shù) MemberCallDemo: foo(使用這種方式,該成員函數(shù) foo 將不能使用缺省的 __thiscall 調(diào)用約定,而必須使用 __stdcall 或__cdecl): void(__stdcall*fnFooPtr)(void*/*pThis*/,int/*a*/)= (void(__stdcall*)(void*,int))dwFooAddrPtr; fnFooPtr(amp。mcd,9);執(zhí)行上面的調(diào)用后,屏幕上依然會輸出 In MemberCallDemo: foo,a=9。這說明我們成功地調(diào)用了成員函數(shù) foo。當(dāng)然,使用這種方式使我們完全沖破了 C++的封裝原則,打壞了正常的調(diào)用規(guī)則,即使你將 foo 聲明為 private 函數(shù),我們的調(diào)用同 樣能夠成功,因為我們是通過函數(shù)地址來進行調(diào)用的。 你不禁會這樣問,在實際開發(fā)中誰會這樣做呢 ?沒錯,我估計也沒有人會這樣來調(diào)用成員函數(shù),除非在一些特定的應(yīng)用下,比如我們接下來所要做的事情。 Thunk! Thunk?這是什么意思 ?翻開《 Oxford Advanced Learner39。s Dictionary,SixthEdition》 .查不到!再使用 Kingsoft39。s PowerWord 2021,其曰 錚,鐺,鏘 ?顯然是個象聲詞。頓暈,這跟我們所要描述的簡直不挨邊啊!不知道人們?yōu)槭裁匆堰@種技術(shù)稱之為 Thunk。不管了,暫時放置一邊吧! 通常,我們所編寫的程序最終將被編譯器的轉(zhuǎn)換成計算機能夠識別的指令碼 (即機器碼 ),比如: C/C++代碼等價的匯編代碼編譯器產(chǎn)生的機器指令 ======= int k1,k2,k; k1=1; mov dword ptr[k1],1 C7 45 E0 01 00 00 00 k2=2; mov dword ptr[k2],2 C7 45 D4 02 00 00 00 k=k1+k2; mov eax,dword ptr[k1]8B 45 E0 add eax,dword ptr[k2]03 45 D4 mov dword ptr[k],eax 89 45 C8 最終, CPU執(zhí)行完指令序列 C7 45 E0 01 00 00 00 C7 45 D4 02 00 00 00 8B 45 E0 03 45 D4 89 45 C8后,就完成了上面的簡單加法操作。從這里我們似乎能夠得到啟發(fā),既然 CPU 只能夠認識機器碼,那么我們可以直接將機器碼送給 CPU 去執(zhí)行嗎 ?答案是:當(dāng)然可以,而且還非常高效!那么,怎么做到這一點呢 ?定義一個機器碼數(shù)組,然后跳轉(zhuǎn)到該數(shù)組的起始地址處開始執(zhí)行: unsigned char machine_code={ 0xC7,0x45,0xE0,0x01,0x00,0x00,0x00, 0xC7,0x45,0xD4,0x02,0x00,0x00,0x00, 0x8B,0x45,0xE0, 0x03,0x45,0xD4, 0x89,0x45,0xC8}; void*paddr=machine_code; 使用內(nèi)嵌匯編調(diào)用該機器碼: __asm {MOV EAX,dword ptr[paddr]; or mov eax,dword ptr paddr; or mov eax,paddr CALL EAX 如果使用 C 調(diào)用該機器碼,則為: void(*fn_ptr)(void)=(void(*)(void))paddr; fn_ptr();怎么樣 ?當(dāng)上面的 CALL EAX 執(zhí)行完后,變量 k 的值同樣 等于 3。但是,當(dāng) machine_code 中的指令執(zhí)行完后, CPU 將無法再回到 CALL 指令的下一條指令了!為什么啊 ?是的,因為 machine_code 中沒有返回指令的機器碼!要讓 CPU 能夠返回到正確的位置,我們必須將返回指令 RET 的機器碼 (0xC3)追加到 machine_code 的末尾,即: unsigned char machine_code={0xC7,0x45,.,0x89,0x45,0xC8,0xC3};。 這就是 Thunk!一種能讓 CPU 直接執(zhí)行我們的機器碼的技術(shù),也有人稱其為自修改代碼 (SelfModifying Code)。但這有什么用呢 ?同樣,在通常的開發(fā)中,我們不可能通過這么復(fù)雜的代碼來完成上面的簡單加法操作!誰這樣做了,那他 /她一定是個瘋子! ^_^。目前所了解的最有用的也是用得最多的就是使用Thunk 來更改棧中的參數(shù),甚至可以是棧中的返回地址,或者向棧中壓入額外的參數(shù) (就像我們的 KWIN 那樣 ),從而達到一些特殊目的。當(dāng)然,在你熟知Thunk 的原理后,你可能會想出更多的用途來,當(dāng)然,如果你想使用 Thunk 來隨意破壞當(dāng)前線程的棧數(shù)據(jù),從而直接導(dǎo)致程序或系統(tǒng)崩潰,那也不是不可能的,只要你喜歡,誰又 在乎呢 ?只要你不把這個程序拿給別人運行就行! 通常我們會使用 Thunk 來 截獲 對指定函數(shù)的調(diào)用,并在真正調(diào)用該函數(shù)之前修改調(diào)用者傳遞給他的參數(shù)或其他任何你想要做的事情,當(dāng)我們做完我們想要做的時候,我們再 跳轉(zhuǎn)到 真正需要被調(diào)用的函數(shù)中去。既然要跳轉(zhuǎn),就勢必要用到 JMP 指令。由于在 Thunk 中的代碼必須為機器指令,所以我們必須按照編譯器的工作方式將我們所需要 Thunk 完成的代碼轉(zhuǎn)換成機器指令,因此我們需要知道我們在 Thunk 所用到的指令的機器指令的編碼規(guī)則 (通常,我們在Thunk 中不可能做太多事情,了解所需 的指令的編碼規(guī)則也不是件難事 )。大家都知道, JMP 為無條件轉(zhuǎn)移指令,并且有 short、 near、 far 轉(zhuǎn)移,通常編譯器會根據(jù)目標(biāo)地址距當(dāng)前 JMP 指令的下一條指令之間的距離來決定跳轉(zhuǎn)類型。在生成機器碼時,并且編譯器會優(yōu)先考慮 short 轉(zhuǎn)移 (如果目標(biāo)地址距當(dāng)前 JMP 指令的下一條指令的距離在 128 和 127 之間 ),此時, JMP 對應(yīng)的機器碼為 0xEB。如果超出這個范圍, JMP 對應(yīng)的機器碼通常為 0xE9。當(dāng)然, JMP 還存在其他類型的跳轉(zhuǎn),如絕對地址跳轉(zhuǎn)等,相應(yīng)地也有其他形式的機器碼,如 0xFF, 0xEA。我們常用到的 只有 0xEB 和 0xE9 兩種形式。另外,需要注意的是,在機器碼中,JMP 指令后緊跟的是一個目標(biāo)地址到該條 JMP 指令的下一條指令之間的距離 (當(dāng)然,以字節(jié)為單位 ),所以,如果我們在 Thunk 中需要用到 JMP 指令,我們就必須手動計算該距離 (這也是編譯器所需要做的一件事 )。如果你已經(jīng)很了解 JMP指令的細節(jié),那么你應(yīng)該知道了下面 Thunk 的將是什么樣的結(jié)果了吧: unsigned char machine_code={0xEB,0xFE};啊,沒錯,這將是一個死循環(huán)。這一定很有趣吧!事實上他跟 JMP$是等價的。由于這里 的機器碼是 0xEB,它告訴 CPU 這是一個 short 跳轉(zhuǎn),并且, 0xFE 的最高位為 1(即負數(shù) ),所以CPU 直到它是一個向后跳轉(zhuǎn)的 JMP 指令。由于向后跳轉(zhuǎn)的,所以此時該 JMP 所能跳轉(zhuǎn)到的范圍為 128 至 1(即 0x80 至 0xFF),但是由于這時的 JMP 指令為 2個字節(jié),所以向后跳轉(zhuǎn) (從該條指令的下一條指令開始 )2 個字節(jié)后,就又回到了該條 JMP 指令的開始位置。 當(dāng)發(fā)生 Short JMP 指令時,其所能跳轉(zhuǎn)的范圍如下: 偏移量機器碼 ===== (128)0x80? (127)0x81? (3)0xFD? (2)0xFE EBShort JMP 指令 (1)0xFF XXXX為跳轉(zhuǎn)偏移量,其取值范圍可為 [0x800x7F] (0)0x00?JMP 下一條指令的開始位置 (+1)0x01? (+2)0x02? (+125)0x7D? (+126)0x7E? (+127)0x7F?好,讓我們在來看一個例子,來說明 Thunk 到底是怎樣修改棧中的參數(shù)的: void foo(int a) {printf(In foo,a=%d\n,a); } unsigned char code[9]; *((DWORD*)amp。code[0])=0x 042444FF; /*inc dword ptr[esp+4]*/ code[4]=0xe9; /*JMP*/ *((DWORD*)amp。code[5])=(DWORD)amp。foo(DWORD)amp。code[0]9; /*跳轉(zhuǎn)偏移量 */ void(*pf)(int/*a*/)=(void(*)(int))amp。code[0]; pf(6);當(dāng)執(zhí)行完 pf(6)調(diào)用后,就會得到下面的輸出: In foo,a=7(明明傳入的是 6,為什么到了 foo 中就變成了 7 了呢 ?)。怎么樣 ?我們在 Thunk 中通過強 制 CPU 執(zhí)行機器碼 0xFF, 0x44, 0x24, 0x04 來將棧中的傳入?yún)?shù) a(位于 ESP+4 處 )增加 1,從而修改了調(diào)用者傳遞過來的參數(shù)。在執(zhí)行完 INC DWORD PTR[ESP+4]后,再通過一個跳轉(zhuǎn)指令跳轉(zhuǎn)到真正的函數(shù)入口處。當(dāng)然,我們同樣不可能在實際的開發(fā)中使用這種方法進行函數(shù)調(diào)用,之所以這樣做是為了能夠更加容易的弄清楚 Thunk 到底是怎么工作的! 好了,寫了這么久,似乎我們到這里才真正進入我們的正題。上面幾節(jié)所描述的都是這一節(jié)所需的基本知識,有了以上知識,我們就能夠很容易的實現(xiàn)我們的最終目的 讓 Windows 來直接調(diào)用我們的類消息處理成員函數(shù),在這里無須使用任何靜態(tài)成員函數(shù)或全局函數(shù),所有的事情都將由我們定義的類成員函數(shù)來完成。由于 Windows 中所有的消息處理均為回調(diào)函數(shù),即它們是由操作系統(tǒng)在特定的消息發(fā)生時被系統(tǒng)調(diào)用的函數(shù),我們需要做的僅僅是定義該消息函數(shù),并將該消息函數(shù)的函數(shù)地址 告訴 Windows。既然我們能夠使用在通過其他途徑調(diào)用類中的成員函數(shù)中所描述的方法得到類成員函數(shù) (消息處理函數(shù) )的地址,那么,我們能夠直接將該成員函數(shù)地址作為一個回調(diào)函數(shù)的地址傳給操作系統(tǒng)嗎 ?很顯然,這是不可能的。 但是為什么呢 ?我想聰明的你已經(jīng)猜到,因為我們的成員函數(shù)需要類對象的 this 指針去訪問類對象中的屬性,但是Windows 是無法將相應(yīng)的類對象的 this 指針傳給我們的成員函數(shù)的!這就是我們所面臨的問題的關(guān)鍵所在!如果我們能夠解決這個類對象的 this 指針傳遞問題,即將類對象的 this 指針手動傳遞到我們的類成員函數(shù)中,那么我們的問題豈不是就解決了嗎 ?沒錯! Thunk 可以為們解決這個難題!這里 Thunk 需要解決的是將消息處理函數(shù)所在的類的實例的 this 指針 傳遞 到消息處理函數(shù)中,從前面的描述我們已經(jīng)知道, this 指針的傳遞有兩種方式,一種是通過 ECX 寄存器進行傳遞,一種是使用棧進行傳遞。 這是一種最簡單的方式,它只需我們簡單地在 Thunk 中執(zhí)行下面的指令即可: LEA ECX,this pointer JMP member functionbased message handler 使用這種方式傳遞 this 指針時,類中的消息處理函數(shù)必須使用 __thiscall 調(diào)用約定!在關(guān)于調(diào)用約定與 this 指針的傳遞中我們對調(diào)用約定有較為詳細的討論。 這是一種稍復(fù)雜的方式,使用棧傳遞 this 指針時必須確保類中的消息處理函數(shù)使用 __stdcall 調(diào)用約定,這跟通常的消息處理函數(shù) (靜態(tài)成員函數(shù)或全局函數(shù) )使用的是同一種條用約定,唯一不同的是現(xiàn)在我們使用的是類成員函數(shù)(非靜態(tài) )。之所以說他稍復(fù)雜,是因為我們要在 Thunk 中要做稍多的工作。前面我們已經(jīng)說過,我們已經(jīng)將我們定義的 Thunk 的地址作為 消息處理回調(diào)函數(shù)地址傳給了 Windows,那么,當(dāng)有消息需要處理時, Windows 就會調(diào)用我們的消息處理函數(shù),不過這時它調(diào)用的是 Thunk 中的代碼,并不是真正的我們在類中定義的消息處理函數(shù)。這時,要將 this 指針?biāo)腿氘?dāng)前棧中可不是件 容易 的事情。讓我們來看看 Windows 在調(diào)用我們的 Thunk 代碼時的棧的參數(shù)內(nèi)容: this 指針被壓入棧之前 this 指針被壓入棧之后 |||| |LPARAM||LPARAM| |||| |WPARAM||WPARAM| |||| |UINT(msg)||UINT(msg)| |||| |HWND||HWND| |||| |(Return Addr)|ESP|this
點擊復(fù)制文檔內(nèi)容
教學(xué)課件相關(guān)推薦
文庫吧 www.dybbs8.com
備案圖片鄂ICP備17016276號-1