windows-nt/Source/XPSP1/NT/base/mvdm/wow16/toolhelp/notify2.asm
2020-09-26 16:20:57 +08:00

644 lines
26 KiB
NASM

PAGE 60,150
;***************************************************************************
;* NOTIFY2.ASM
;*
;* Assembly code support routines used for the TOOLHELP.DLL
;* notification API
;*
;***************************************************************************
INCLUDE TOOLPRIV.INC
.286p
;** Data
sBegin DATA
globalW wCASRqFlag,0 ;Set when an CASRq INT3 has been set
globalD dwCASRqCSIP,0 ;Holds the CS:IP of the CASRq INT3
globalD lpfnOldProc,0 ;Old hook from new PTrace hook
szWinDebug DB 'WINDEBUG', 0
;** WARNING!!
;** This structure is set to the size of the largest notification
;** structure. This is currently NFYLOADSEG which is 16 bytes long.
;** If a structure is added that is longer than this or if any other
;** structure is added, this space must be increased to match!!
ReturnStruct DB 16 DUP (?)
sEnd
;** Imports
externFP GetModuleHandle
externFP RegisterPTrace
externFP OutputDebugString
externFP AllocCStoDSAlias
externFP FreeSelector
externNP HelperHandleToSel
sBegin CODE
assumes CS,CODE
assumes DS,DATA
; NotifyInit
; Called when the first app registers a notification handler.
; Hooks the Register PTrace notification.
; Returns FALSE if we couldn't initialize, TRUE otherwise
cProc NotifyInit, <NEAR,PUBLIC>, <si,di,ds>
cBegin
;** In the Windows 3.1 KERNEL, there is a special hook just for
;* TOOLHELP that lets us get PTrace stuff and still coexist
;* with old-fashioned debuggers. We can check to see if the
;* hook exists by simply checking the TOOLHELP flags
;**
test wTHFlags,TH_GOODPTRACEHOOK ;Good hook around?
jz DNI_UseRegPTrace ;Nope, use the old one
lea si,NotifyHandler ;Point to the routine
push cs ;Parameter is lpfn to callback
push si
call lpfnNotifyHook ;Hook it
mov WORD PTR lpfnOldProc[0],ax ;Save old proc
mov WORD PTR lpfnOldProc[2],dx
jmp SHORT DNI_10 ;We're in
;** Since there's no way we can see if someone else has Register
;* PTrace, we just connect and hope for the best!
;** We do check, however, to see if WINDEBUG.DLL is installed.
DNI_UseRegPTrace:
lea si,szWinDebug ;Get the name of the module
cCall GetModuleHandle, <ds,si> ;Is WINDEBUG present?
or ax,ax ;Check the handle
jnz DNI_Fail ;It's here so fail
or wTHFlags,TH_GOTOLDPTRACE ;Flag that we made the hook
lea si,NotifyHandler ;Point to our routine
cCall RegisterPTrace, <cs,si> ;Tell KERNEL to use it
;** Connect to the FatalExit hook. We currently ignore
;** the return value, thus unhooking anyone else
DNI_10: cmp WORD PTR lpfnFatalExitHook + 2,0 ;Can we hook it?
jz DNI_20 ;Can't do it
push cs ;Get the CS:IP of RIP handler
push OFFSET NotifyRIPHandler
call DWORD PTR lpfnFatalExitHook ;Tell KERNEL to insert the hook
DNI_20:
;** Return OK
mov ax,TRUE ;Return TRUE
jmp SHORT DNI_End ;Get out
DNI_Fail:
xor ax,ax ;FALSE
DNI_End:
cEnd
; NotifyUnInit
; Called when the no more apps have hooked notification handlers
; so the hook to the Register PTrace notification is no longer needed.
cProc NotifyUnInit, <NEAR,PUBLIC>, <si,di,ds>
cBegin
;** Did we have a new hook to undo?
test wTHFlags,TH_GOODPTRACEHOOK ;Do we have a new hook?
jz DNU_TryOldPTrace ;No
push WORD PTR lpfnOldProc[0] ;Get the old proc
push WORD PTR lpfnOldProc[2]
call lpfnNotifyHook ;Unhook ourself
jmp SHORT DNU_NoOldPTrace
;** Unhook the old-style hook if necessary
DNU_TryOldPTrace:
test wTHFlags,TH_GOTOLDPTRACE ;Did we have a hook?
jz DNU_NoOldPTrace ;No
push 0
push 0
call RegisterPTrace ;Call KERNEL's routine to unhook
DNU_NoOldPTrace:
;** Unhook alternate hooks
cmp WORD PTR lpfnFatalExitHook + 2,0 ;Can we unhook it?
jz DNU_NoRIP ;Can't do it
xor ax,ax ;Remove any other hooks
push ax ;NULL procedure
push ax
call DWORD PTR lpfnFatalExitHook
DNU_NoRIP:
cEnd
; NotifyHandler
; This routine is called directly by PTrace and is used to
; dispatch the notifications to all registered callbacks.
cProc NotifyHandler, <FAR,PUBLIC>
cBegin NOGEN
;** Push a register frame
;* When done, it should look like this:
;* ------------
;* | ES | [BP - 14h]
;* | DS | [BP - 12h]
;* | DI | [BP - 10h]
;* | SI | [BP - 0Eh]
;* | BP | [BP - 0Ch]
;* | SP | [BP - 0Ah]
;* | BX | [BP - 08h]
;* | DX | [BP - 06h]
;* | CX | [BP - 04h]
;* | AX | [BP - 02h]
;* BP-->| Old BP | [BP - 00h]
;* | IP | [BP + 02h]
;* | CS | [BP + 04h]
;* ------------
;**
push bp ;Make a stack frame
mov bp,sp
pusha ;Save all registers
push ds ;Save segment registers, too
push es
;** Get the data segment
mov bx,_DATA ;Get TOOLHELP data segment
mov ds,bx
;** If in 3.0 std mode and we get this wild notification 69h,
;** translate it to a inchar notification as this is what it
;** is supposed to be.
cmp ax,69h ;Bogus notification?
jne NH_NoBogusNotify ;No, don't do this
test wTHFlags,TH_WIN30STDMODE ;3.0 standard mode?
jz NH_NoBogusNotify ;No, might be valid in the future...
mov ax,NI_INCHAR ;Put in real notify value
NH_NoBogusNotify:
;** Special case notifications:
;* Notification 63h means that CtlAltSysRq was pressed. For
;* this, we want to handle as an interrupt, not a notification.
;* To do this, we set a breakpoint and set a flag so that the
;** INT3 handler knows what to do with it
cmp ax,63h ;CtlAltSysRq?
jne NH_NoCASRq ;No.
mov ax,[bp + 04h] ;Since we can't use IRET CS:IP, get
mov si,[bp + 02h] ; a safe address in KERNEL
mov WORD PTR dwCASRqCSIP[2],ax ;Save the CS:IP value
cCall AllocCStoDSAlias, <ax> ;Get a data alias to the CS
or ax,ax ;Error?
jnz @F
jmp SHORT DNH_End ;Yes, get out
@@: verw ax ;OK to write to?
jnz DNH_NoWrite ;Yes, so do it
DNH_IRETCSOK:
mov es,ax ;Point with ES
mov WORD PTR dwCASRqCSIP[0],si
mov al,es:[si] ;Get the character there
mov ah,1 ;Make sure there's something in AH
mov wCASRqFlag,ax ;Save the thing for the INT3 handler
mov BYTE PTR es:[si],0cch ;Poke the INT3 in there
mov ax,es ;Get the selector back
DNH_NoWrite:
cCall FreeSelector, <ax> ;Get rid of the alias
jmp SHORT DNH_End ;Get out. This will INT3 soon
NH_NoCASRq: ; Does not return
;** Notifications to ignore here:
;** Notification 60h is bogus and should be ignored
cmp ax,60h ;PostLoad notification?
jz DNH_End ;Yes, don't report
;** Decode the notification
cCall DecodeNotification ;Returns dwData in CX:DX, AX is wID
; BX is NOTIFYSTRUCT match flags
;** This is an entry point for notifications from sources other than
;** PTrace
DNH_Decoded:
;** Loop through callbacks
mov di,npNotifyHead ;Point to the start of the list
xor si,si ;FALSE return value is default
DNH_Loop:
push ax
mov ax,ds:[di].ns_pNext ;Save the next pointer in a global
mov npNotifyNext,ax ; so we can chain in NotifyUnregister
pop ax
or di,di ;End of list?
jz DNH_Done ;Yep. Get out
;** If the flags for this notification are zero, we always send it
or bx,bx ;Check the matching flags
jz DNH_DoIt ;Do notification
;** See if the flags match
test bx,ds:[di].ns_wFlags ;Check against the NOTIFYSTRUCT flags
jz DNH_Continue ;If zero, no match, don't do it
;** Call the user callback
DNH_DoIt:
push ax ;Save everything we need
push bx
push cx
push dx
push ax ;wID
push cx ;High word of dwData
push dx ;Low word
call DWORD PTR ds:[di].ns_lpfn ;Call the callback (PASCAL style)
mov si,ax ;Get return value in SI
pop dx ;Restore everything
pop cx
pop bx
pop ax
;** If the return value is nonzero, we don't want to give this to
;** any more callbacks
or si,si ;TRUE return value?
jnz DNH_Done ;Yes, get out
;** Get the next callback
DNH_Continue:
mov di,npNotifyNext ;Get next pointer
jmp DNH_Loop ; and loop back
;** End of callback loop.
DNH_Done:
;** If this was an InChar message but everyone ignored it, force
;** the return to be an 'i' for 'ignore' on RIPs. This i
;** only necessary in 3.0 because the 3.1 KERNEL treats 0
;** returns just like 'i'
cmp ax,NFY_INCHAR ;Is this an InChar notification?
jne DNH_Default ;No, so ignore
test wTHFlags,TH_WIN30 ;In 3.0?
jz DNH_Default ;No, don't do this
and si,0ffh ;Ignore all but low byte
or si,si ;Non-zero?
jnz DNH_Default ;Yes, return it as the character
mov si,'i' ;Instead of zero, return 'i'gnore.
DNH_Default:
mov [bp - 02h],si ;Return the return code in AX
;** Clear off the stack and exit
DNH_End:
mov npNotifyNext,0 ;No current next pointer
pop es ;Restore all registers
pop ds
popa
pop bp
retf ;Just return
cEnd NOGEN
; NotifyRIPHandler
; Gets called by KERNEL when a RIP occurs. If it returns TRUE,
; KERNEL will act like the RIP was ignored. Otherwise, the RIP
; procedes normally.
; This routine does not need to worry about saving non-C regs
cProc NotifyRIPHandler, <FAR,PUBLIC>
; parmW wExitCode
cBegin nogen
;** Clear PASCAL-style parameters
push bp ;Make a stack frame
mov bp,sp
mov bx,[bp + 6] ;Get the BP value
mov dx,[bp + 8] ;Get the Exit code
mov [bp - 2],ax ;Save it out of the way for now
mov ax,[bp + 4] ;Get the RETF CS value
mov [bp + 8],ax ;Shift down to clear parameters
mov ax,[bp + 2] ;Get the RETF IP value
mov [bp + 6],ax ;Shift down to clear parameters
mov ax,[bp + 0] ;Get the old BP value
mov [bp + 4],ax ;Shift down
add bp,4 ;Move BP down on the stack
mov sp,bp ;Point SP there too
pusha ;Save matching register frame
push ds
push es
;** Get the data segment
mov ax,_DATA ;Get TOOLHELP data segment
mov ds,ax
;** Prepare to jump into the notification handler.
;** The trick here is that if a notification callback returns
;** non-zero, the RIP has been handled. Otherwise, it has not.
;** DX holds the exit code here, BX has the old BP value
lea si,ReturnStruct ;Get a pointer to the return struct
mov WORD PTR [si].nrp_dwSize[0],SIZE NFYRIP
mov WORD PTR [si].nrp_dwSize[2],0
mov ax,ss:[bx + 4] ;Get old CS value from stack
mov [si].nrp_wCS,ax ; (BX is BP from FatalExit stack)
mov ax,ss:[bx + 2] ;Get old IP value
mov [si].nrp_wIP,ax
mov [si].nrp_wSS,ss ;Save SS:BP for stack trace
mov [si].nrp_wBP,bx
mov [si].nrp_wExitCode,dx
mov cx,ds ;Point to structure
mov dx,si
mov bx,NF_RIP ;Get the NOTIFYINFO match flags
mov ax,NFY_RIP ;TOOLHELP ID
;** Jump to the real handler
jmp DNH_Decoded ;Jump to alternate entry point
cEnd nogen
;** Helper routines
; DecodeNotification
; Decodes a notification by pointing to a static structure and filling
; this structure with notification-specific information.
; The PTrace notification ID is in AX.
; Returns the ToolHelp ID in AX
; and the dwData value is in CX:DX.
cProc DecodeNotification, <NEAR,PUBLIC>
cBegin
;** Point dwData to the structure just in case
mov cx,ds ;Get the segment value
lea dx,ReturnStruct ;Get a pointer to the return struct
xor bx,bx ;Most notifications always match
;** The stack frame looks like this:
;* ------------
;* | ES | [BP - 14h]
;* | DS | [BP - 12h]
;* | DI | [BP - 10h]
;* | SI | [BP - 0Eh]
;* | BP | [BP - 0Ch]
;* | SP | [BP - 0Ah]
;* | BX | [BP - 08h]
;* | DX | [BP - 06h]
;* | CX | [BP - 04h]
;* | AX | [BP - 02h]
;* BP-->| Old BP | [BP - 00h]
;* ------------
;**
FrameES EQU [BP - 14h]
FrameDS EQU [BP - 12h]
FrameDI EQU [BP - 10h]
FrameSI EQU [BP - 0Eh]
FrameBP EQU [BP - 0Ch]
FrameSP EQU [BP - 0Ah]
FrameBX EQU [BP - 08h]
FrameDX EQU [BP - 06h]
FrameCX EQU [BP - 04h]
FrameAX EQU [BP - 02h]
;** Check for LoadSeg
cmp ax,NI_LOADSEG ;LoadSeg?
jnz DN_10 ;No
;** LoadSeg:
;* CX is selector
;* BX is segment number
;* SI is type: Low bit set for data segment, clear for code
;* DX is instance count only for data segments
;** ES:DI module name
mov si,dx ;Point to NFYLOADSEG struct
mov ax,SIZE NFYLOADSEG ;Get the structure size
mov WORD PTR [si].nls_dwSize,ax ;Save the LOWORD of the size
mov WORD PTR [si].nls_dwSize + 2,0 ;HIWORD is zero
mov ax,FrameCX ;Get selector
mov [si].nls_wSelector,ax ;Save in structure
mov ax,FrameBX ;Get segment number
inc ax ;Segment number is 1-based
mov [si].nls_wSegNum,ax ;Save in structure
mov ax,FrameSI ;Get the segment type
mov [si].nls_wType,ax ;Put in structure
mov ax,FrameDX ;Get instance count
mov [si].nls_wcInstance,ax ;Put in structure
mov ax,FrameDI ;Get offset of module name str
mov WORD PTR [si].nls_lpstrModuleName,ax ;Save it
mov ax,FrameES ;Get segment of module name str
mov WORD PTR [si].nls_lpstrModuleName + 2,ax ;Save it
mov ax,NFY_LOADSEG ;Get the TOOLHELP ID
jmp DN_End
;** Check for FreeSeg
DN_10: cmp ax,NI_FREESEG ;FreeSeg?
jnz DN_15 ;No
;** FreeSeg:
;** BX is selector
xor cx,cx ;Clear high word
mov dx,FrameBX ;Get the selector
test wTHFlags,TH_WIN30STDMODE ;3.0 standard mode?
jz DN_FS_GotSelValue ;No, what we have is correct
mov si,FrameSP ;Point to old stack frame
mov dx, ss:[si + 6] ;Selector is 6 bytes down
lsl ax, dx
jz DN_FS_CheckLen ;Selector is OK
mov dx, FrameBX ;Revert to BX value
jmp SHORT DN_FS_GotSelValue
DN_FS_CheckLen:
cmp ax, 15 ;If the segment is 15 bytes long,
jne DN_FS_GotSelValue ; this is a bogus selector and is
; really an arena header.
push es
mov es, dx ;Get handle
cCall HelperHandleToSel, <es:[0ah]> ;Convert to selector
mov dx, ax ;Get handle out of arena header
pop es
DN_FS_GotSelValue:
mov ax,NFY_FREESEG ;Get the TOOLHELP ID
jmp DN_End
;** Check for StartDLL
DN_15: cmp ax,NI_LOADDLL
jnz DN_20
;** StartDLL:
;** CX is CS
;** BX is IP
;** SI is Module handle
mov si,dx ;Point with SI
mov ax,SIZE NFYSTARTDLL ;Get the size
mov WORD PTR [si].nsd_dwSize,ax ;Save the LOWORD of the size
mov WORD PTR [si].nsd_dwSize + 2,0 ;HIWORD is always zero
mov ax,FrameSI ;Get the hInstance
mov [si].nsd_hModule,ax ;Save in structure
mov ax,FrameCX ;Get the starting CS
mov [si].nsd_wCS,ax ;Save in structure
mov ax,FrameBX ;Get the starting IP
mov [si].nsd_wIP,ax ;Save in structure
mov ax,NFY_STARTDLL
jmp DN_End
;** Check for StartTask
DN_20: cmp ax,NI_STARTTASK ;StartTask?
jnz DN_30 ;No
;** StartTask:
;* CX is CS
;** BX is IP
mov cx,FrameCX
mov dx,FrameBX
mov ax,NFY_STARTTASK
jmp DN_End
;** Check for ExitCall
DN_30: cmp ax,NI_EXITCALL ;ExitCall
jnz DN_40 ;No
;** ExitCall:
;* Exit code is on stack somewhere if we don't have the new
;** notification handler. If we do, it's in BL.
xor cx,cx ;Clear all but low byte
xor dh,dh
test wTHFlags,TH_GOODPTRACEHOOK ;Do we have the good hook?
jz DN_DoOldHook ;Nope, grope on the stack
mov dl,BYTE PTR FrameBX ;Get the exit code
mov ax,NFY_EXITTASK ;Get the TOOLHELP ID
jmp DN_End
DN_DoOldHook:
mov si,FrameSP ;Point to old stack frame
mov dl,ss:[si + 6] ;Exit code is 6 bytes down on stack
mov ax,NFY_EXITTASK ;Get the TOOLHELP ID
jmp DN_End
;** Check for DelModule
DN_40: cmp ax,NI_DELMODULE ;DelModule?
jnz DN_60 ;No
;** DelModule:
;** ES is module handle
xor cx,cx ;Clear HIWORD
mov dx,FrameES ;Get the module handle
mov ax,NFY_DELMODULE ;Get the TOOLHELP ID
jmp DN_End
;** Check for TaskSwitchIn
DN_60: cmp ax,NI_TASKIN ;TaskSwitchIn?
jnz DN_70 ;No
;** TaskSwitchIn:
;** No data. Callback should do GetCurrentTask()
xor cx,cx ;Clear data
xor dx,dx
mov ax,NFY_TASKIN ;Get the TOOLHELP ID
mov bx,NF_TASKSWITCH ;Get the NOTIFYSTRUCT match flag
jmp DN_End
;** Check for TaskSwitchOut
DN_70: cmp ax,NI_TASKOUT ;TaskSwitchOut?
jnz DN_90 ;No
;** TaskSwitchOut:
;** No data
xor cx,cx ;Clear data
xor dx,dx
mov ax,NFY_TASKOUT ;Get the TOOLHELP ID
mov bx,NF_TASKSWITCH ;Get the NOTIFYSTRUCT match flag
jmp DN_End
;** Check for OutStr
DN_90: cmp ax,NI_OUTSTR ;OutStr?
jnz DN_100 ;No
;** OutStr:
;** ES:SI points to string to display in 3.1
;** DS:SI in 3.0
test wTHFlags,TH_WIN30 ;3.0?
jz DN_OS_Win31 ;Nope
mov cx,FrameDS ;Get the segment value
jmp SHORT @F
DN_OS_Win31:
mov cx,FrameES ;Get the segment value
@@: mov dx,FrameSI ; and the offset
mov ax,NFY_OUTSTR ;Get the TOOLHELP ID
jmp DN_End
;** Check for InChar
DN_100: cmp ax,NI_INCHAR ;InChar?
jnz DN_105 ;No
;** InChar:
;** No data passed (it wants data back in AL)
xor cx,cx ;Clear dwData
xor dx,dx
mov ax,NFY_INCHAR ;Get the TOOLHELP ID
jmp SHORT DN_End
;** NOTE: The following notifications are defined as "NEW" and
;** are NOT sent through the normal PTrace interface so as to
;** not break CodeSpew. It stack faults when
;** it is sent a notification it doesn't understand. So,
;** here we don't bother decoding any of these unless we have
;** the new (Win 3.1) style hook
DN_105: test wTHFlags,TH_GOODPTRACEHOOK ;Do we have the advanced hook?
jnz DN_110 ;Yes
jmp SHORT DN_End
;** Check for the parameter validation notifications
DN_110: cmp ax,NI_LOGERROR ;SDM_LOGERROR?
jne DN_120 ;No
;** SDM_LOGERROR:
;* CX is Error code
;** DX:BX is lpInfo
mov si,dx ;Point with SI
mov ax,SIZE NFYLOGERROR ;Get the size
mov WORD PTR [si].nle_dwSize[0],ax ;Save the LOWORD of the size
mov WORD PTR [si].nle_dwSize[2],0 ;HIWORD is always zero
mov ax,FrameCX ;Get the error code
mov [si].nle_wErrCode,ax ;Save in structure
mov ax,FrameDX ;Get the lpInfo
mov WORD PTR [si].nle_lpInfo[2],ax ;Save in structure
mov ax,FrameBX
mov WORD PTR [si].nle_lpInfo[0],ax ;Save in structure
mov ax,NFY_LOGERROR ;Get the TOOLHELP ID
jmp SHORT DN_End
DN_120: cmp ax,NI_LOGPARAMERROR ;SDM_LOGPARAMERROR?
jne DN_Unknown ;No
;** SDM_LOGPARAMERROR:
;** ES:BX points to a structure:
;** WORD wErr
;** FARPROC lpfn
;** VOID FAR* lpBadParam
mov si,dx ;Point with SI
mov ax,SIZE NFYLOGPARAMERROR ;Struct size
mov WORD PTR [si].nlp_dwSize[0],ax ;Save the LOWORD of the size
mov WORD PTR [si].nlp_dwSize[2],0 ;HIWORD is always zero
mov es,FrameES ;Point to the structure
mov bx,FrameBX
mov ax,es:[bx] ;Get wErr
mov [si].nlp_wErrCode,ax ;Save in structure
mov ax,es:[bx + 2] ;Get lpfn[0]
mov WORD PTR [si].nlp_lpfnErrorAddr[0],ax
mov ax,es:[bx + 4] ;Get lpfn[2]
mov WORD PTR [si].nlp_lpfnErrorAddr[2],ax
mov ax,es:[bx + 6] ;Get lpBadParam[0]
mov WORD PTR [si].nlp_lpBadParam[0],ax
mov ax,es:[bx + 8] ;Get lpBadParam[2]
mov WORD PTR [si].nlp_lpBadParam[2],ax
mov ax,NFY_LOGPARAMERROR ;Get the TOOLHELP ID
xor bx,bx ;Always match
jmp SHORT DN_End
;** Must be unknown, return TOOLHELP ID NFY_UNKNOWN with KERNEL value
;** in LOWORD(wData)
DN_Unknown:
mov dx,ax ;Get the notification value
mov ax,NFY_UNKNOWN ;Unknown KERNEL notification
xor cx,cx ;Clear high WORD
DN_End:
cEnd
sEnd
END