TITLE r2xfer.asm .386P OPTION SEGMENT:USE16 .MODEL LARGE COMMENT $ This file contains code and data that handles the case where a R3 code contains a call to a R2 entry point. In OS/2, only the selector part (call gate) of the call destination is meaningful. The OS/2 SS replaces the original call with a call to a jump table entry in the R2XFER code segment (which is the second code segment of DOSCALLS.DLL). The R2XFER code segment contains code that emulates the stack changing when transferring protection rings. The size of this code is limited to a predefined size since it is followed by other data structures which are set at run time by the loader. The limit is currently set to 0x200 (which can be changed anytime later if it is found that 0x200 is not sufficient to handle the ring transfer emulation code). The code generated by this file is followed in memory by an array of Ring 2 info which is prepared by the OS/2 SS loader. The array is variable length and each entry contains the following information: struct _R2CallInfo { UCHAR R2CallNearInst; // Opcode of the call relative near inst (0xE8) USHORT R2CommonEntry; // This field will contain the relative offset to 0H UCHAR R2BytesToCopy; // Total size of proc parameters to copy between stacks USHORT R2EntryPointOff; // Offset of entry address of R2 routine USHORT R2EntryPointSel; // Selector of entry address of R2 routine } R2CallInfo; When a ring 3 routine calls a ring 2 routine (which the OS/2 SS loader changes to a call to the appropriate R2CallInfo entry, which does another call to the CommonR2Xfer routine) the R3 stack looks upon entry to CommonR2Xfer: param1 param2 param3 return-addr-r3 CS IP return-addr-r2 IP <- SP We want to start the R2 code with an R2 stack in the following format: <- TOS <16 bytes free> These free bytes are a local work-around for the PMNTPrint() accesses to the stack. Old SS Old SP 0 These 2 zeroes are used to catch (by trap) any access 0 of the ring 2 code to the ring 3 stack param1 param2 param3 return-addr-r3 CS IP <- SS:SP This file also defines a data segment that is used to hold information of the OS/2 SS ring 2 stacks. This data segment is the 3'rd segment of DOSCALLS.DLL The data is organized as an array with each entry representing the ring 2 stack of the appropriate thread. Each entry in the array has the following format: struct _R2Stacks { USHORT R2StackSize; SEL R2StackSel; } R2Stacks[256]; Entry 0 is not used since there is no thread 0. OS/2 supports 255 threads so this array contains enough entries. An entry of the array is initialized when the CommonR2Xfer routine is called by an OS/2 thread for the first time, with same ThreadID as the index of the enrty. OS/2 allocates 512 bytes as the initial size of the R2 stack and so do we. $ R2Stacks STRUCT 1 R2InitSP WORD 512 ; 0:512 indicates that no R2 stack exists R2StackSel WORD 0 R2Stacks ENDS R2CallInfo STRUCT 1 R2bytesToCopy BYTE ? R2EntryPoint DWORD ? R2CallInfo ENDS EXTERN DosAllocSeg:FAR .CODE PUBLIC CommonR2Xfer CommonR2Xfer PROC FAR ; Push all registered involved in the emulation. The order of the push ; was selected to allow easy exchange of stacks. push BP mov BP,SP push AX push ES push DI push SI push BX push CX push DX ; The stack of the calling process is now: ; ; param1 SP+26 BP+12 ; param2 SP+24 BP+10 ; param3 SP+22 BP+8 ; return-addr-r3 CS SP+20 BP+6 ; IP SP+18 BP+4 ; return-addr-r2 IP SP+16 BP+2 ; BP SP+14 BP ; AX SP+12 BP-2 ; ES SP+10 BP-4 ; DI SP+8 BP-6 ; SI SP+6 BP-8 ; BX SP+4 BP-10 ; CX SP+2 BP-12 ; DX SP BP-14 mov BX,FS:6 ; Get current thread ID. FS points to the LINFOSEG shl BX,2 ; add BX,2 ; BX points now to SEL part of the R2 TOS (Top Of Stack) mov AX,SEG R2StacksInfo mov ES,AX ; mov AX,ES:[BX] ; AX contains the SEL of the R2 TOS or AX,AX ; Verify that the thread has a R2 stack jnz R2CheckIfCalledFromR2; If yes, skip creation of R2 Stack ; ; Create R2 Stack ; push 512 ; Allocate stack with size of 512 bytes (same as OS/2) push ES ; ES:BX contain the address of the Stack entry push BX ; push 0 ; Flags for SEG_NONSHARED call DosAllocSeg ; Allocate a R2 stack segment or AX,AX ; Check if the allocation succeeded jz R2StackExists ; Yes. pop DX ; No. What to do? Restore all registers and return... pop CX ; pop BX ; pop SI ; pop DI ; pop ES ; pop AX ; pop BP ; add SP,2 ; Ignore the return to the R2CallInfo entry retf ; Just return... ; ; Check if we call a ring 2 function from within ring 2. ; If yes, no need to change stacks. ; AX contains the ring 2 stack selector ; R2CheckIfCalledFromR2: mov CX,SS ; Get current Stack Segment cmp CX,AX ; Compare with the R2 SS jne R2StackExists ; Not the same. Change stacks. mov SI,[BP+2] ; SI contains a pointer to the R2BytesToCopy field ; in the call table entry mov EAX,CS:[SI].R2CallInfo.R2EntryPoint ; EAX contains the Address of the R2 destination pop DX ; Restore registers. pop CX ; pop BX ; pop SI ; pop DI ; pop ES ; push word ptr [BP] ; The old BP is going to be overwritten... mov [BP],EAX ; Old BP and return-addr-r2 are overwritten. pop BP ; Retsore Old BP from Stack pop AX ; retf ; return to the R2 code ; ; A R2 stack exists or was created ; R2StackExists: ; ; The calls to DosXXX preserve all registers except AX, ; so BX was not changed and we can use it ; les DI,ES:[BX-2] ; ES:DI points to the top of the R2 stack sub DI,4+4+16 ; allocate place for old SS:SP, 2 zeroes and 16 free bytes mov ES:[DI+6],SS ; Save old SS mov BX,BP ; add BX,4 ; 8-4 (the 4 in the R3 stack is used to move there ; the return address to the function in R3 that ; called the R2 routine). mov SI,[BP+2] ; SI contains a pointer to the R2BytesToCopy field ; in the call table entry mov EDX,CS:[SI].R2CallInfo.R2EntryPoint ; EDX contains the Address of the R2 destination movzx CX,CS:[SI].R2CallInfo.R2BytesToCopy ; CX contains the # of bytes to copy jcxz no_params mov AX,CX ; save a copy of the number of parameters add BX,CX ; Assume as if the R3 params were removed mov ES:[DI+4],BX ; Save old sp mov word ptr ES:[DI],0 ; Clear the R3 access trap catch bytes mov word ptr ES:[DI+2],0 ; sub DI,CX ; DI points to lowest address for parameters of ; the R2 stack lea SI,[BP+8] ; source of the copy in the R3 stack cld ; shr CX,1 rep movs word ptr ES:[DI],SS:[SI] ; copy all the arguments sub DI,AX ; DI now points to the begining of the lowest R2 ; arguments jmp short params_copied no_params: xor AX,AX ; Remember number of parameters mov ES:[DI+4],BX ; Save old sp (no need to add CX) mov word ptr ES:[DI],0 ; Clear the R3 access trap catch bytes mov word ptr ES:[DI+2],0 ; ; ; The R2 stack looks now like this: ; ; <16 free bytes> ; Old SS ; Old SP ; 0 ; 0 ; param1 ; param2 ; param3 <- ES:DI ; params_copied: sub DI,16 ; DI points at the lowest parameter on the R2 stack mov ES:[DI+14],CS ; save return address from R2 procedure to this code mov ES:[DI+12], OFFSET R2CodeRet mov ES:[DI+8],EDX ; put on the R2 stack the address of the R2 procedure ; ; The R2 stack looks now like this: ; ; <16 free bytes> ; Old SS ; Old SP ; 0 ; 0 ; param1 ; param2 ; param3 ; CS of this code DI+14 ; Offset of R2CodeRet DI+12 ; ring-2-entry CS DI+10 ; IP DI+8 ; free DI+6 ; free DI+4 ; free DI+2 ; free <- ES:DI ; ; ; copy the R3 return address over the first parameter pushed onto the R3 stack ; mov EDX,[BP+4] ; old CS:IP mov BX,BP ; add BX,AX ; Add # of parameter bytes mov SS:[BX+4],EDX ; save return address over first parameters ; ; The R3 stacks looks now like this: ; ; return-addr-r3 CS SP+26 BP+12 ; return-addr-r3 IP SP+24 BP+10 ; param3 SP+22 BP+8 ; return-addr-r3 CS SP+20 BP+6 ; IP SP+18 BP+4 ; return-addr-r2 IP SP+16 BP+2 ; BP SP+14 BP ; AX SP+12 BP-2 ; ES SP+10 BP-4 ; DI SP+8 BP-6 ; SI SP+6 BP-8 ; BX SP+4 BP-10 ; CX SP+2 BP-12 ; DX SP BP-14 ; pop DX ; pop CX ; pop BX ; pop SI ; mov EAX,[BP-2] ; EAX contains original BP:AX mov ES:[DI+4],EAX ; mov EAX,[BP-6] ; EAX contains original ES:DI mov ES:[DI],EAX ; ; ; The R2 stack looks now like this: ; ; <16 free bytes> ; Old SS ; Old SP ; 0 ; 0 ; param1 ; param2 ; param3 ; free ; free ; ring-2-entry CS ; IP ; original BP ; original AX ; original ES ; original DI <- ES:DI ; mov SP,DI ; exchange the stack to the R2 stack mov AX,ES ; mov SS,AX ; pop DI ; Restore all registers pop ES ; pop AX ; pop BP ; ; ; The R2 stack looks now like this: ; ; <16 free bytes> ; Old SS ; Old SP ; 0 ; 0 ; param1 ; param2 ; param3 ; CS of this code ; Offset of R2CodeRet from beginning of segment ; ring-2-entry CS ; IP <- SS:ESP ; retf ; call the R2 actual code ; ; Here we return from the R2 code ; R2CodeRet: push BP mov BP,SP push AX push ES push DI mov DI,FS:6 ; Index of thread shl DI,2 mov AX, SEG R2StacksInfo mov ES,AX les DI,ES:[DI] ; ES:DI point to Top of R2 stack les DI,ES:[DI-(4+16)] ; ES:DI contain the current R3 stack pointer sub DI,8 ; DI points below return address to R3 mov EAX,[BP-2] ; move using EAX old BP:AX from R2 stack to R3 stack mov ES:[DI+4],EAX ; mov EAX,[BP-6] ; move using EAX ES:DI from R2 stack to R3 stack mov ES:[DI],EAX ; mov [BP],ES ; mov [BP-2],DI ; lss SP,[BP-2] ; pop DI ; pop ES ; pop AX ; pop BP ; retf IF ($-CommonR2Xfer) GT 200H ; ; The size of the code segment exceeds 200H ; The ldr assumes that it is not greater than 200H ; If you have reached here and increased the value above 200H ; you have to modify also the OS/2 SS file ldr\ldrinit.c ; .ERR SizeOfSegmentExceeds200H ENDIF CommonR2Xfer ENDP ; ; Next statement is used to make the size of the segment exactly 64K ; It causes the linker to issue warning L4020: ; ; segment: code segment size exceeds 64K-36 ; ; Ignore the warning! ; ORG 0FFFFH ; This statement causes the linker to create ; a segment virtual size of 64K .DATA R2StacksInfo R2Stacks 256 DUP (<>) .CODE THUNK16 PUBLIC DOSCALLBACK DOSCALLBACK PROC FAR ; The ring 2 stack upon entry: ; ; Target Callee (segment) ; Target Callee (offset) ; Ret Address (segment) ; Ret Address (offset) <- SP ; ; ; Move to ring 3 and change the stack to ring 3 stack ; ; Preserve reDIsters accross the call push BP mov BP,SP push AX push ES push DI push DS push SI ; R2 stack is now: ; ; Target Callee (segment) BP+8 ; Target Callee (offset) BP+6 ; Ret Address (segment) BP+4 ; Ret Address (offset) BP+2 ; old BP <- BP ; AX ; ES ; DI ; DS ; SI <- SP mov DI,FS:6 ; Get current thread ID. FS points to the LINFOSEG shl DI,2 ; DI points now to SEL:OFF of the R2 TOS (Top Of Stack) mov AX,SEG R2StacksInfo mov ES,AX ; lds SI,ES:[DI] ; DS:SI contain the R2 TOS mov AX,SS cmp AX,DS:[SI-18] ; Check if we are on the R3 ring jne doscallback_R2 ; The current ring is R3 pop SI pop DS pop DI pop ES pop AX call dword ptr [BP+6] pop BP retf 4 doscallback_R2: push SI ; Save OS2 TOS lds SI,DS:[SI-20] ; DS:SI contain the R3 current stack mov ES:[DI],BP ; Set a new ring 2 TOS for ring 3 -> ring 2 add word ptr ES:[DI],4 ; transitions. ; ; set a return address to the DosRetForward() API ; sub SI,10 ; Allocate space on the ring 3 stack mov AX, SEG DosRetForward mov DS:[SI+8],AX mov AX,OFFSET DosRetForward mov DS:[SI+6],AX mov AX,[BP+8] ; Target Callee (segment) mov DS:[SI+4],AX ; mov AX,[BP+6] ; Target Callee (offset) mov DS:[SI+2],AX ; mov AX,[BP] ; old BP mov DS:[SI],AX ; ; ; move the R2 return address to a new location on the R2 stack ; so that we can return back to the R2 function that called DosCallBack() ; mov AX,[BP+4] ; Ret address (segment) mov [BP+8],AX ; mov AX,[BP+2] ; Ret address (offset) mov [BP+6],AX ; ; ; Save old R2 TOS in the R2 stacks data structure ; pop word ptr [BP+4] ; ; save ring 3 SS:SP for task switch (done by the LSS instruction below) ; mov [BP],SI mov [BP+2],DS pop SI ; Restore saved reDIster pop DS pop DI pop ES pop AX lss SP,[BP] ; switch stacks pop BP retf ; perform call to user routine in ring 3 DOSCALLBACK ENDP DOSRETFORWARD PROC FAR sub SP,2 ; allocate space on the ring 3 stack push BP mov BP,SP push AX push ES push DI push DS push SI mov DI,FS:6 ; Get current thread ID. FS points to the LINFOSEG shl DI,2 ; DI points now to SEL:OFF of the R2 TOS (Top Of Stack) mov AX,SEG R2StacksInfo mov ES,AX ; lds SI,ES:[DI] ; DS:SI contain the R2 TOS mov AX,DS:[SI] ; ring 2 prev TOS mov ES:[DI],AX mov AX,[BP] ; Get old BP and save it on the ring 2 stack mov DS:[SI],AX ; ; save ring 2 SS:SP for task switch (done by the LSS instruction below) ; mov [BP+2],DS mov [BP],SI pop SI pop DS pop DI pop ES pop AX lss SP,[BP] pop BP retf ; return to the ring 2 routine that called DosCallBack() DOSRETFORWARD ENDP END