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, , 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, ;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, ;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, , 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, 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, ;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, ;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, ; 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, 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, ;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