失眠网,内容丰富有趣,生活中的好帮手!
失眠网 > windows消息处理过程及消息钩子

windows消息处理过程及消息钩子

时间:2023-02-11 06:17:22

相关推荐

windows消息处理过程及消息钩子

应用层发消息: 发送消息过程 SendMessage(user32.dll)->SendMessageWorker,先检查有没有hook消息钩子,有的话调用CsSendMessage,进入消息钩子过滤函数。

没有的话,看是不是系统消息,是的话在Message表中找到对应msg id的索引值,通过索引值在在gapfnScSendMessage数组中找到对应的消息处理函数

如果是NtUserMessageCall的话,则进驱动处理消息。

进驱动层发消息: 进入NtUserMessageCall后,如果是系统消息,同上,在gapfnMessageCall数组中找到对应的处理函数。比如msg id为0x112,最小化窗口消息,

调用过程为NtUserfnNCDESTORY->xxxWrapSendMessage->xxxSendMessageTimeout->xxxInterSendMsgEx,在这个函数中,先通过AllocSMS分配一个消息结构体,

在将消息加入到目标线程的pSMSReceiveList链表中,代表目标线程有要接受的消息,然后调用SetWakeBit唤醒目标线程,激发目标线程的EventQueueServer,然后等待

自己的EventQueueServer被激发。然后调用xxxSleepThread是自己处于等待状态。在等待前,调用xxxReceiveMessage检查是否有hook消息钩子,如果有的话,回调CallClientProc

->xxxHkCallHook 分发处理消息钩子函数。 自己会setevent,while死循环阻塞在KeWaitForSingleObject等待。在此函数中,先检查是哪种互斥对象,如果对象被激活,则调用处理函数。否则KiSwapThread通知cpu切换为别的线程运行。调用KiComputeWaitInterval继续等待。

安装钩子过程:进入驱动调用NtUserSetWindowsHookEx->zzzSetWindowsHookEx修改目标线程的ptiThread->aphkStart[nFilterType + 1]钩子数组。

键盘按键:ioapic寄存器接收,读出index和哪个cpu处理,local apic发给键盘中断处理例程,再传给键盘端口驱动,端口类驱动。==win32k的RawInputThread读顶层类设备状态,读出按键扫描码,经过底层键盘钩子处理,匹配是win,alt+tab键等对应处理,再匹配普通字符,发给顶层窗口的线程,进入wm_char处理

消息优先级:QS_SENDMESSAGE > QS_POSTMESSAGE > QS_QUIT > QS_INPUT > QS_PAINT > QS_TIMER

msg: 112

user消息用的是win32k自己的一套通信方式,最多就用了个event来同步

跨线程的WM_COPYDATA没有使用共享内存,反而复制了两次数据

发送者SendMessage->xxxSendMessageTimeout->xxxInterSendMsgEx(UserAllocPoolWithQuota分配内核内存,将用户数据复制到内核空间)->SetWakeBit唤醒接受者->SetWakeBit等待应答

接受者xxxReceiveMessage->XXXSENDMESSAGETOCLIENT(宏)->ScSendMessageSMS(也是宏)->SfnCOPYDATA(sender side)->CaptureCallbackData(把数据从内核空间复制到用户空间)->KeUserModeCallback(转到用户模式)->SfnCOPYDATA(receiver side)->窗口过程->回到内核模式,应答发送者

回调函数什么时候被调用, 每个回调函数的情况都不一样, 就如你老板说的, 是系统定义好的, 对方(系统/库)对这个回调函数的文档说明一般只讲个大概不会讲细节, 你想弄清楚"触发机制", 那就去看对方的源码, 具体在哪些情况下哪些代码会call你的callback函数. 如果对方文档不详源码不给, 那就要靠自己的经验去猜去反汇编.

1) comp

comp可算是最简单的callback, 你调用qsort时把comp(你/crt库之间的callback)作为参数, 告诉对方(crt库)比较大小时call你这个comp. 接下去qsort立即触发comp, 反复调用comp直至qsort返回.

[] Vc7\crt\src\qsort.c

void __cdecl qsort (

void *base,

size_t num,

size_t width,

int (__cdecl *comp)(const void *, const void *)

)

{

for (;;) {

if (mid > loguy) {

do {

loguy += width;

} while (loguy < mid && comp(loguy, mid) <= 0);

}

if (mid <= loguy) {

do {

loguy += width;

} while (loguy <= hi && comp(loguy, mid) <= 0);

}

}

}

2) ThreadProc

你调用AfxBeginThread时把ThreadProc(你/mfc库之间的callback)作为参数, 告诉对方(mfc库)线程开始执行时call你这个ThreadProc.

AfxBeginThread把ThreadProc作为CWinThread内部参数保存起来, 调用成员函数CWinThread::CreateThread. CWinThread::CreateThread调用_beginthreadex时把_AfxThreadEntry(mfc库/crt库之间的callback)作为参数.

_beginthreadex把_AfxThreadEntry放在_ptiddata ptd, 调用Windows API CreateThread时把_threadstartex(crt库/系统之间的callback)作为参数.

CreateThread按原参数(加上hProcess)调用CreateRemoteThread. CreateRemoteThread把lpStartAddress(AfxBeginThread是_threadstartex, 如果你直接用Windows API CreateThread, 则是你的ThreadProc)放在CONTEXT ThreadContext. 并把ThreadContext.Eip设置为BaseThreadStartThunk(子系统/子系统之间的callback).

AfxBeginThread (pfnThreadProc = ThreadProc, pParam = Param)

CWinThread::CWinThread (m_pfnThreadProc = pfnThreadProc = ThreadProc, m_pThreadParams = pParam = Param)

CWinThread::CreateThread (startup.pThread = this)

_beginthreadex (ptd->_initaddr = initialcode = _AfxThreadEntry, ptd->_initarg = argument = startup, CREATE_SUSPENDED)

CreateThread (lpStartAddress = _threadstartex, lpParameter = ptd) (user mode/kernel mode切换)

CreateRemoteThread (ThreadContext.Eip = BaseThreadStartThunk, ThreadContext.Eax = lpStartAddress, ThreadContext.Ebx = lpParameter)

BaseCreateStack

BaseInitializeContext

NtCreateThread (ThreadContext = &ThreadContext, CreateSuspended = TRUE)

PspCreateThread (ThreadContext = ThreadContext, StartRoutine = NULL, StartContext = NULL, Thread->StartAddress = ThreadContext->Eip)

ObCreateObject 创建线程对象

MmCreateKernelStack

KeInitializeThread (SystemRoutine = PspUserThreadStartup, StartRoutine = NULL, StartContext = ThreadContext->Eip, ContextFrame = ThreadContext)

KeInitializeApc (SuspendApc, KernelRoutine = KiSuspendNop, NormalRoutine = KiSuspendThread, NormalContext = NULL, ApcMode = KernelMode)

KeInitializeSemaphore (SuspendSemaphore, Semaphore->Header.SignalState = 0, Semaphore->Limit = 2)

KiInitializeContextThread (Thread->PreviousMode = TrFrame->PreviousPreviousMode = UserMode, SwitchFrame->RetAddr = KiThreadStartup)

KeContextToKframes

KeSuspendThread (if CreateSuspended)

KiInsertQueueApc (SuspendApc)

KeReadyThread

KiReadyThread 把线程对象转到就绪状态

CsrClientCallServer

NtResumeThread (if !CREATE_SUSPENDED)

KeResumeThread (Thread->SuspendSemaphore.Header.SignalState += 1)

KiWaitTest (if Thread->SuspendSemaphore.Header.WaitListHead not empty)

KiWaitSatisfyAny (Thread->SuspendSemaphore.Header.SignalState -= 1)

KiUnwaitThread

KiReadyThread (Thread->State = Ready)

ResumeThread

WaitForSingleObject (startup.hEvent)

SuspendThread (if CREATE_SUSPENDED)

SetEvent (startup.hEvent2)

|_

| SwitchFrame

|_

| PSystemRoutine (PspUserThreadStartup)

|_

| PStartRoutine (NULL)

|_

| PStartContext (BaseThreadStartThunk)

|_

| PUserContextFlag (1)

|_

| TrFrame (copy from ThreadContext)

|_

| NpxFrame

|_

Thread->InitialStack

AfxBeginThread到达KiReadyThread之后就逐级返回. 线程切换轮到新线程, 新线程先处理SuspendApc, 等待SuspendSemaphore.

如果在处理SuspendApc之前, CreateRemoteThread已调用NtResumeThread, 那么SuspendSemaphore立即满足, 无需等待, 立即执行KiThreadStartup.

反之, 则转到等待状态, 直到CreateRemoteThread调用NtResumeThread之后, 又转到就绪状态. 线程切换又轮到新线程, 开始执行KiThreadStartup.

AfxBeginThread创建的线程总是CREATE_SUSPENDED, 在_beginthreadex返回后再ResumeThread, 在_AfxThreadEntry callback后再SuspendThread(如果你给AfxBeginThread参数有CREATE_SUSPENDED).

KiSuspendNop

KiSuspendThread

KeWaitForSingleObject (SuspendSemaphore, WaitMode = KernelMode, Alertable = FALSE, Timeout = NULL)

KiWaitSatisfyOther (if Semaphore->Header.SignalState > 0, Semaphore->Header.SignalState -= 1)

KiSwapThread (else, Thread->State = Waiting)

SwapContext

KiThreadStartup

PspUserThreadStartup

KeInitializeApc (StartApc, KernelRoutine = PspNullSpecialApc, NormalRoutine = PspSystemDll.LoaderInitRoutine, NormalContext = NULL, ApcMode = UserMode)

KeInsertQueueApc (StartApc, SystemArgument1 = PspSystemDll.DllBase)

KiInsertQueueApc (StartApc)

DbgkCreateThread

KiServiceExit2 (TFrame = &TrFrame, Thread->Tcb.Alerted = 0)

KiDeliverApc (StartApc, PreviousMode = UserMode, ExceptionFrame = NULL, TrapFrame = TFrame, Thread->TrapFrame = TFrame)

PspNullSpecialApc

ExFreePool

KiInitializeUserApc (TrapFrame = TFrame, TFrame->Eip = (ULONG)KeUserApcDispatcher, TFrame->HardwareEsp = UserStack)

KeContextFromKframes

iretd (esp = &TFrame->Eip)

|_UserStack

| NormalRoutine (PspSystemDll.LoaderInitRoutine)

|_

| NormalContext (NULL)

|_

| SystemArgument1 (PspSystemDll.DllBase)

|_

| SystemArgument2 (NULL)

|_

| ContextFrame (copy from ThreadContext)

|_

ThreadContext.Esp

KeUserApcDispatcher (UserStartApc)

KiUserApcDispatcher

PspSystemDll.LoaderInitRoutine

LdrInitializeThunk (NormalContext = &ContextFrame)

LdrpInitialize (Context = NormalContext)

LdrpInitializeThread

LdrpAllocateTls

LdrpCallInitRoutine (DLL_THREAD_ATTACH)

LdrpCallTlsInitializers (DLL_THREAD_ATTACH)

NtContinue (ContextRecord = &ContextFrame, TestAlert = TRUE) (user mode/kernel mode切换)

KiContinue (TrFrame->Eip = ContextRecord->Eip)

KiContinuePreviousModeUser

KeContextToKframes

KeTestAlertThread

KiServiceExit2

BaseThreadStartThunk (eax = ThreadContext.Eax = lpStartAddress = _threadstartex, ebx = ThreadContext.Ebx = lpParameter = ptd)

BaseThreadStart (lpStartAddress = _threadstartex, lpParameter = ptd)

CsrNewThread

NtRegisterThreadTerminatePort

_threadstartex (ptd = ptd)

_callthreadstartex (ptd->_initaddr = _AfxThreadEntry, ptd->_initarg = startup)

_AfxThreadEntry (pStartup = startup, pThread = pStartup->pThread, pThread->m_pfnThreadProc = ThreadProc, pThread->m_pThreadParams = Param)

AfxInitThread

SetWindowsHookEx (WH_MSGFILTER)

SetEvent (pStartup->hEvent)

WaitForSingleObject (pStartup->hEvent2)

ThreadProc (lpParameter = Param) 你/mfc库之间的callback绕了一大圈终于被调用

AfxEndThread

_endthreadex

ExitThread

3) WndProc

WndProc的情况就复杂多了, 前几天我只是想弄清楚窗口移动时几个消息的先后顺序和作用, 花了好几天时间才管见一斑. 要是把WndProc被调用(几百个消息各种情况下)的"触发机制"全弄清楚了, 也就是对Windows的窗口子系统了如指掌了.

xxxMoveWindow/xxxSetWindowPlacement

xxxSetWindowPos

xxxSetWindowPos/xxxMinMaximize

InternalBeginDeferWindowPos

_DeferWindowPos

xxxEndDeferWindowPosEx

xxxCalcValidRects

WM_WINDOWPOSCHANGING (if !SWP_NOSENDCHANGING)

xxxDefWindowProc

xxxAdjustSize (if !SWP_NOSIZE)

xxxInitSendValidateMinMaxInfo

WM_GETMINMAXINFO

WM_NCCALCSIZE (if !SWP_NOSIZE || SWP_FRAMECHANGED)

xxxDefWindowProc

xxxCalcClientRect

GetCaptionHeight

GetWindowBorders

xxxMenuBarCompute

xxxDoSyncPaint

xxxSendChangedMsgs

WM_WINDOWPOSCHANGED (if !SWP_NOCHANGE)

xxxDefWindowProc

xxxHandleWindowPosChanged

WM_MOVE (if !SWP_NOCLIENTMOVE)

xxxSendSizeMessage (if !SWP_NOCLIENTSIZE)

WM_SIZE

WM_NCLBUTTONDOWN

xxxDefWindowProc

xxxDWP_NCMouse

WM_SYSCOMMAND (SC_SIZE + ht - HTSIZEFIRST + WMSZ_SIZEFIRST)

WM_NCLBUTTONUP

xxxDefWindowProc

xxxDWP_NCMouse

xxxHandleNCMouseGuys

WM_SYSCOMMAND

WM_SYSCOMMAND (SC_SIZE + ht - HTSIZEFIRST + WMSZ_SIZEFIRST)

xxxDefWindowProc

xxxSysCommand

xxxSysCommand (SC_RESTORE/SC_MINIMIZE/SC_MAXIMIZE)

xxxShowWindow

xxxMinMaximize

CkptRestore

InternalBeginDeferWindowPos

_DeferWindowPos

xxxEndDeferWindowPosEx (SWP_DRAWFRAME | SWP_NOCOPYBITS | SWP_STATECHANGE)

xxxSysCommand (SC_SIZE/SC_MOVE)

xxxMoveSize

xxxInitSendValidateMinMaxInfo

WM_GETMINMAXINFO

xxxMS_FlushWigglies

bSetDevDragWidth

xxxDrawDragRect

WM_ENTERSIZEMOVE

xxxCapture

xxxMS_TrackMove

xxxMS_TrackMove (WM_MOUSEMOVE)

xxxTM_MoveDragRect

WM_SIZING

xxxDrawDragRect

xxxMS_TrackMove (WM_LBUTTONUP)

xxxTM_MoveDragRect

xxxDrawDragRect

xxxReleaseCapture

xxxSetWindowPos

WM_EXITSIZEMOVE

3.1) SendMessage

SendMessage的消息SDK文档称之为nonqueued messages, 没放到消息队列(mlPost/mlInput)里面, GetMessage不会得到SendMessage的消息.

mlPost是PostMessage的消息队列, 比如WM_HOTKEY, WM_TIMER, WM_LOGONNOTIFY. WM_QUIT一般没存放在队列(Desktop/console窗口例外), 是直接放到xxxReadPostMessage的参数.

mlInput是键盘鼠标和系统事件的消息队列. psmsReceiveList是异线程SendMessage的SMS(SendMessage Structure)队列. GetMessage只取回mlPost/mlInput的消息, SMS是另外处理的.

xxxInternalGetMessage

while (TRUE) {

while (QS_SENDMESSAGE) xxxReceiveMessage (pti->psmsReceiveList)

if (QS_POSTMESSAGE) xxxReadPostMessage (pti->mlPost) break

if (QS_INPUT|QS_EVENT) xxxScanSysQueue (pti->pq->mlInput) break

xxxSleepThread

}

#define QS_ALLINPUT (QS_INPUT | QS_POSTMESSAGE | QS_TIMER | QS_PAINT | QS_HOTKEY | QS_SENDMESSAGE)

typedef struct tagTHREADINFO { ...

PQ pq; // keyboard and mouse input queue

PSMSpsmsReceiveList; // SMSs to be processed

MLISTmlPost; // posted message list.

} THREADINFO;

* Message Queue structure.

typedef struct tagQ { ...

MLIST mlInput;// raw mouse and key message list.

} Q, *PQ;

SendMessage如果同线程, 是直接call WndProc. 如果异线程, 一般是调用NtUserMessageCall(特殊的消息是调用fnCOPYGLOBALDATA/fnEMGETSEL这些系统函数). NtUserMessageCall又根据不同的消息分别调用45个NtUserfn*系统函数. 比如WM_SETTEXT是调用NtUserfnINSTRINGNULL, 进而调用xxxInterSendMsgEx.

xxxInterSendMsgEx构建一个sms(SendMessage Structure), 把消息复制到sms, 如果消息有指针参数, 比如WM_SETTEXT的lParam是字符指针, 就分配系统内存把指针所指的内容复制过来, 把sms加到系统gpsmsList队列首, 再把sms加到ptiReceiver->psmsReceiveList队列尾. 接着设置目标线程的QS_SENDMESSAGE标志, 激发目标线程的EventQueueServer, 然后等待自己的EventQueueServer.

目标线程一般多在消息循环的GetMessage中等待自己的EventQueueServer(如果正在处理消息, 处理完仍是回到GetMessage), EventQueueServer激发, 导致KeWaitForSingleObject返回, xxxSleepThread返回, 进入xxxReceiveMessage, 把sms从ptiReceiver->psmsReceiveList队列(从头到尾逐个处理)移除, 调用xxxSendMessageToClient进入usermode call WndProc再返回kernel mode, 接着设置源线程的QS_SMSREPLY标志, 激发源线程的EventQueueServer, 然后继续自己的消息循环.

源线程的EventQueueServer激发, 导致KeWaitForSingleObject返回, xxxSleepThread返回到xxxInterSendMsgEx, 把sms从系统gpsmsList队列移除, 释放系统内存和sms, 逐级返回到SendMessage.

SendMessage (同线程)

SendMessageWorker

UserCallWinProcCheckWow

InternalCallWinProc

WndProc

SendMessage (异线程)

SendMessageWorker

NtUserMessageCall (user mode/kernel mode切换)

EnterCrit

NtUserfnINSTRINGNULL (WM_SETTEXT)

RtlInitLargeUnicodeString

xxxWrapSendMessage (xParam = 0)

xxxSendMessageTimeout (fuFlags = SMTO_NORMAL, uTimeout = 0, lpdwResult = NULL)

xxxInterSendMsgEx (pism = 0, lRet = psms->lRet)

AllocSMS (psms->spwnd = pwnd, psms->ptiCallBackSender = NULL, psms->flags = 0)

HeavyAllocPool (dwFlags = DAP_USEQUOTA)

HMAssignmentLock (psms->spwnd)

SetWakeBit (pti = ptiReceiver, wWakeBit = QS_SENDMESSAGE)

KeSetEvent (pti->pEventQueueServer, PriorityIncrement = 2, WaitImmediatelyFollowed = FALSE)

KeSetKernelStackSwapEnable (Enable = FALSE)

xxxSleepThread (fsWakeMask = QS_SMSREPLY, Timeout = 0, fInputIdle = FALSE)

ClearQueueServerEvent

KeClearEvent(ptiCurrent->pEventQueueServer)

LeaveCrit

KeWaitForSingleObject (ptiCurrent->pEventQueueServer)

EnterCrit

KeSetKernelStackSwapEnable (Enable = OldState)

SetWakeBit (pti = ptiSender, wWakeBit = QS_SMSREPLY)

UnlinkSendListSms (ppsmsUnlink = NULL)

HMAssignmentUnlock (psms->spwnd)

UserFreePool

FreeSMS

LeaveCrit

xxxInternalGetMessage

xxxSleepThread (fsWakeMask = QS_ALLINPUT | QS_EVENT | QS_ALLPOSTMESSAGE, Timeout = 0, fInputIdle = TRUE)

ClearQueueServerEvent

KeClearEvent(ptiCurrent->pEventQueueServer)

LeaveCrit

KeWaitForSingleObject (ptiCurrent->pEventQueueServer, WaitReason = WrUserRequest, WaitMode = UserMode, Alertable = FALSE, Timeout = NULL)

EnterCrit

xxxReceiveMessage

xxxSendMessageToClient

SfnINSTRINGNULL

AllocCallbackMessage

CaptureCallbackData

LeaveCrit

KeUserModeCallback (ApiNumber = 0x1B)

KiGetUserModeStackAddress

KiCallUserMode ([esp].TsEip = KiUserCallbackDispatcher)

KiServiceExit

KiSystemCallExitBranch

KiSystemCallExit2

sysexit (kernel mode进入user mode)

KiUserCallbackDispatcher

__fnINSTRING

FixupCallbackPointers

DispatchClientMessage

UserCallWinProcCheckWow

InternalCallWinProc

WndProc

XyCallbackReturn

int 2b (user mode返回kernel mode)

KiCallbackReturn

EnterCrit

SetWakeBit(pti = ptiSender, wWakeBit = QS_SMSREPLY)

KeSetEvent (pti->pEventQueueServer)

----写于-11-3

如果觉得《windows消息处理过程及消息钩子》对你有帮助,请点赞、收藏,并留下你的观点哦!

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。