天天看點

OllyDbg學習---深入了解消息驅動原理(2)

    筆者先要申明,這節知識都是筆者根據自己的知識積累和觀察做出的猜測。既然是猜測,那麼肯定有不準确的地方。不過筆者還是對自己的推論和猜測很有信心。同時感謝我的搭檔劉飛,關于這節内容,我們倆經過了比較深入的讨論,最後一緻感覺就現在的想法比較的合理,比較的說得通。是以我寫出來與大家分享。

    Windows核心是靠IRP來驅動,而Windows視窗應用程式靠的就是消息來驅動。所謂的消息驅動其意思就是靠消息作為紐帶來運作。

    需要注意的是,一個視窗隻能對應一個線程,用VC編寫過應用程式的讀者肯定對消息循環的while語句再熟悉不過,這個循環語句其實就是個死循環,這樣這個視窗線程永遠不會退出除非接收到WM_QUIT消息。

    這裡涉及到幾個和消息有關的函數,分别是GetMessage,TranslateMessage,DispatchMessage

    另外涉及幾個和預設視窗過程有關的函數,分别是DefDlgProcA,DefWindowProc,DefMDIChildProc,DefFrameProc

    現在讀者肯定迫不及待的會問,這節和OD的學習有必然聯系麼?!!我很負責的告訴你,有!因為OD中會涉及到下消息斷點。我們必須了解消息驅動原理。不然,筆者絕對相信你會很茫然看到突然中斷到一個陌生的地方,而這個地方的代碼你完全不曉得是幹什麼的。

    我們開始。我們運作一個程式,最外面的大架構是一個視窗,其内部的按鈕,文本框等等也都是視窗。需要注意的是,我們最熟悉不過的那個最外層視窗有消息循環。内部的按鈕,文本框卻沒有消息循環!也就是說,最外層的視窗對應一個線程,而裡面的按鈕,文本框等等所有的“子視窗”都不會有線程與其對應,這些“子視窗”對應的回調函數将來都運作在“父視窗”的線程上下文中,定位更準确點的話應該說是運作在DispatchMessage函數上下文中。

    筆者先來描述下關于消息的整個過程。

    1,每個視窗都會對應一個線程,并且一個線程有且隻能和一個視窗綁定,如果某個線程和視窗進行綁定,那麼系統就再會為這個線程準備一個消息隊列。如果此線程沒有和視窗綁定,那麼就是一個普通的線程,系統也不會多此一舉為他加上消息列隊

    2,系統會維護一種資訊,此資訊記錄了螢幕上所有的視窗(包括按鈕,文本框等等)的區域的坐标範圍。并且還會為這些視窗編上号,這些編号就是視窗句柄。需要注意的是,句柄這個東西沒有準确的定義,根據情況的不同而不同,具體我下一節來介紹。拿滑鼠為例,當你按下滑鼠,系統就會捕獲此時此刻滑鼠被按下時的坐标,再檢視滑鼠指針坐标落在哪個視窗的區域範圍内。這樣一來系統就會知道哪個按鈕被按下,同時系統會建構一個消息資料結構

     Type MSG

       hwnd As Long       這個裡面的值就是此視窗的句柄(滑鼠坐标落在其内)

       message As Long     此時此刻應該是 WM_LBUTTONDOWN

       lParam As Long    

       time As Long             

       pt As POINTAPI    坐标,就是滑鼠被按下時指針的坐标

    End Type

 并且會把這個結構放入系統消息列隊裡。

   3,每個綁定了視窗的線程都會通過GetMessage函數從系統消息列隊中取出消息,其判斷依據就是視窗句柄和坐标,如果發現系統列隊中某個消息的視窗句柄對應的視窗包含在自身範圍内,那麼就拿過來。如果不包含在自身内,那麼就無視。

   4,TranslateMessage的作用是把接收過來的消息加工一下,比如說把WM_KEYDOWN和WM_KEYUP消息組合成一起稱為WM_CHAR消息。

   5,接下來就是DispatchMessage函數,這個函數非常的重要,首先,和上面2個函數一樣,他們都存在于USER32.DLL中。此函數會分析得到的消息的結構進而得到此結構第一個參數值(視窗句柄),并且調用此按鈕的預設回調函數。說要說明的是,由于按鈕,文本框等控件對于程式開發人員來說都是很常用的并且這些控件的功能往往是固定,不會有特别稀奇古怪的附加功能,是以這些控件的回調函數也就可以固定下來,微軟想法和我一樣,同時,微軟為了節約程式員的負擔幹脆把這些回調函數封裝起來并固定存放在USER32.DLL中。需要注意的是,這些函數是非導出函數,除非你用OD跟蹤,不然你還确實無法知道此回調函數在哪。這當然是後話。

   6,從上面的描述你可以發現,像按鈕這樣的控件,其對應的回調函數已經固化在USER32.DLL中,那麼DispatchMessage函數就去調用這個函數。

   7,我們還知道,其實按鈕這些東西都是作為資源存在于PE結構中,就某個按鈕而言,你在寫程式的時候也僅僅描述了以下幾個資訊,首先是明确表示你描述的東東的确是個按鈕,其次就是按鈕的坐标,其他幾乎沒有做任何描述,你并沒有告訴系統此按鈕的回調函數。系統是這樣工作的:當程式運作的時候,系統要分析PE結構裡所有的資源,通過資源裡的描述得到具體的資訊,拿按鈕資源為例,當系統通過PE得到這個按鈕的資訊之後就會在核心中建構這個按鈕的核心對象,此核心對象裡的某個成員變量就會指向USER32.DLL裡的某個位址,這個位址就是按鈕的預設回調函數的首位址,以上這個步驟是系統自動完成的。當然,此按鈕的核心對象裡還會包含其他一些資訊,比如坐标,資源号什麼的。也就是因為按鈕的核心對象裡包含了回調函數的首位址,是以DispatchMessage函數才能調用!不然DispatchMessage函數壓根不會知道從哪擷取按鈕回調函數的首位址!

   8,你可以發現,當你下滑鼠斷點之後按F9,再嘗試按下某個按鈕後都會中斷在USER32領空的某個代碼處。你反彙編USER32後會發現這段代碼并沒有導出函數與其對應。是以可以判斷按鈕的預設回調函數的确沒有被導出且确實存在于USER32庫裡。按鈕的回調函數要做的是獲得WM_LBUTTONDOWN消息并且分析之,随後生成WM_COMMAND消息(帶上BN_CLICKED參數和本按鈕的句柄)發送給主視窗的線程消息列隊中,之後GetMessage得到這個WM_COMMAND消息并通過DispatchMessage函數調用主視窗的預設回調函數來處理這樣一個消息,處理好了之後生成WM_COMMAND(帶上按鈕資源号)的消息再次發到主視窗的消息列隊中。接下來DispatchMessage函數調用的将會是使用者定義好了的視窗過程函數。這樣一來按鈕和主視窗的聯系就建立了。

  好了,這就是消息循環的細節。下節開始介紹句柄。

後記:

子窗體和父窗體的關系:

繼續閱讀