Part 1 - Definitions
Part 2 - Real mode entry point
Part 3 - Protected mode entry point
Part 4 - User code (small)
Part 5 - Interrupt and exception handlers (large)
WARNINGS (One legal, one moral)
===============================
As a professional programmer, I identify with my code. This may be old, and
not how I do things now, but it is still mine. I don't mind showing this code
to others so that they can learn, and you can experiment to your heart's
content, but DO NOT USE ANY PART of this code in new software, whether
commercial or otherwise.
Finally, this code is the ultimate in unfriendliness! It assumes that the
whole machine belongs to it - including ALL RAM. This means that it should
be run on a vanilla system - no HIMEM.SYS, no memory managers, and DEFINITELY
no disk-cache programs! Your memory WILL be written over!
John Burger
I knew I was forgetting something, but I couldn't remember what it was!
It hit. To assemble the above, get it all into one file in the above order.
Then simply assemble it with
TASM PROTMODE (or whatever assembler you use - no DEFINEs needed)
then
TLINK /3 PROTMODE (or whatever. There are 32-bit fixups: Borland uses /3)
The result is a 69K-odd .EXE, 4K of which is the code, 64K is the GDT!
MOV AX,0700h+' ' ; Grey space
XOR DI,DI ; Screen pointer
MOV CX,2000 ; 2000 screen locations
REP STOSW ; Clear screen
STI ; You can start now!
;
; Initialise remaining TSSs, in case a fault happens!
;
MOV EAX,SEG TSS
SHL EAX,4
MOV BX,OFFSET MainTask
MOV CX,OFFSET TSSSize
MOV DL,Present+System+Gate386+AvailTSS
MOV DH,ByteGran
CALL AssignMem
LTR BX ; Point Task Register to it
; Use memory above 1 Meg for TSS stacks
MOV ESI,110000h ; Point to memory above 1Mb+64K
MOV EAX,SEG TSSDebug ; Initialise Debug TSS
MOV BX,OFFSET DebugTSS ; Point to descriptor table entry
MOV EBP,OFFSET DebugInt ; Starting instruction to use
CALL AssignTSS
MOV EAX,SEG TSSDouble ; Initialise Double TSS
MOV BX,OFFSET DoubleTSS ; Point to descriptor table entry
MOV EBP,OFFSET DoubleInt ; Starting instruction to use
CALL AssignTSS
MOV EAX,SEG TSSTSS ; Initialise BadTSS TSS
MOV BX,OFFSET BadTSSTSS ; Point to descriptor table entry
MOV EBP,OFFSET BadTSSInt ; Starting instruction to use
CALL AssignTSS
MOV EAX,SEG BadStackTSS ; Initialise BadStack TSS
MOV BX,OFFSET StackTSS ; Point to descriptor table entry
MOV EBP,OFFSET StackInt ; Starting instruction to use
CALL AssignTSS
MOV FS:[TaskLock],0 ; Can now enable task switching
; *** Insert any INT after this point ***
; (before this point I don't guarantee anything!)
;
; Now create each task
;
MOV BX,OFFSET UserTasks-SIZE Descriptor
MOV CX,(80/(WindowWidth+2))*(25/(WindowHeight+2)) ; No. tasks
TaskLoop:
PUSH CX
; Allocate LDT
MOV CX,OFFSET LDTLimit ; Local descriptor table size
CALL GetMem ; Allocate memory
MOV DL,Present+Memory+Writable ; As data segment
MOV DH,ByteGran
CALL AssignMem ; Modify GDT
; Initialise LDT
PUSH DS
PUSH BX ; Save GDT pointer
MOV DS,BX ; Point to LDT
MOV BX,-SIZE Descriptor
; Allocate Stack0
MOV CX,StackSize-1 ; Size of stack
CALL GetMem ; Get Stack0
MOV DL,Present+Memory+Writable ; As data segment
MOV DH,ByteGran+More64k
CALL AssignMem ; Modify LDT
; Allocate Stack3
MOV CX,StackSize-1 ; Size of stack
CALL GetMem ; Get Stack3
MOV DL,Present+Privilege3+Memory+Writable ; As data segment
MOV DH,ByteGran+More64k
CALL AssignMem ; Modify LDT
; Point to user code
MOV EAX,SEG BallCode ; Code segment
SHL EAX,4 ; As linear address
ADD BX,SIZE Descriptor ; Next descriptor
MOV CX,OFFSET CodeLimit ; Bytes of code
MOV DL,Present+Privilege3+Memory+Execable+Readable ; As code
MOV DH,ByteGran+LargeAddr
CALL AssignMem ; Modify LDT
; Allocate user data
MOV CX,OFFSET DataLimit ; Size of data
CALL GetMem ; Get TaskData
MOV DL,Present+Privilege3+Memory+Writable ; As data segment
MOV DH,ByteGran
CALL AssignMem ; Modify LDT
POP BX ; Restore GDT pointer
POP DS
ASSUME DS:GDT ; So tell assembler
MOV [BX.DescType],Present+Privilege3+System+LDT ; Is now LDT!
;
; Initialise TaskData to maintain uniqueness
; To pre-initialise data, access is needed to the two variables that define
; the window. To get this access requires loading a segment register. The
; descriptor for this segment is in the LDT for the task. So, temporarily
; point the LDT for the Main task to this LDT, load the segment register,
; and access the variables. BUT. The LDTR is NOT automatically saved on a
; task switch, and the LDT field in MainTask's TSS is zero, not this temporary
; LDT. So if any task switches happen in this critical region, on return a
; GP fault for ES will occur (points to a non-existant LDT!).
;
CLI ; INTs can stuff up LDTR
LLDT BX ; Use as LDT for now
MOV AX,OFFSET TaskData+LocalDT ; Point to allocated TaskData
MOV ES,AX ; With ES
ASSUME ES:BallData ; And tell assembler
MOV AX,FS:[Corner] ; Get global corner
MOV ES:[Top],AH ; Store as Top
MOV ES:[Left],AL ; And Left
XOR AX,AX ; Don't point to LDT
MOV ES,AX ; with ES
LLDT AX ; anymore!
STI ; LDTR safe again
ADD BYTE PTR FS:[Corner],WindowWidth+2 ; Next across
CMP BYTE PTR FS:[Corner],80-WindowWidth ; Too far?
JB SHORT AllocTSS ; Not yet
ADD BYTE PTR FS:[Corner+1],WindowHeight+2 ; Yes, so next down
MOV BYTE PTR FS:[Corner],1 ; And start again
AllocTSS:
MOV CX,TSSLimit ; Size of a TSS
CALL GetMem ; Get memory for TSS
MOV DL,Present+Memory+Writable ; As a data segment
MOV DH,ByteGran
CALL AssignMem ; Modify GDT
PUSH DS
PUSH ES
MOV DS,BX ; Point to new segment
MOV ES,BX
ASSUME DS:TSS ; And tell (fool) assembler
XOR EAX,EAX ; Zero TSS
XOR DI,DI
MOV CX,TSSSize/4
REP STOSD
MOV [ESP0],StackSize ; Initialise Stack0
MOV [SS0],OFFSET Stack0+LocalDT
MOV [EIPReg],OFFSET BallEntry ; Entry point
MOV [EFlags],3202h ; IOPL = 3!
MOV [ESPReg],StackSize
MOV [CSSeg],OFFSET TaskCode+LocalDT+3 ; DPL=3 sets CPL
MOV [SSSeg],OFFSET Stack3+LocalDT+3 ; DPL=3
MOV [DSSeg],OFFSET TaskData+LocalDT+3 ; DPL=3
MOV [LDTR],BX ; LDT is current descriptor..
SUB [LDTR],SIZE Descriptor ; ...minus 1
MOV [IOMapBase],TSSSize ; No need to worry about I/O
POP ES ; Don't point to TSS any more
POP DS ; Restore GDT pointer
ASSUME DS:GDT ; So tell assembler
MOV [BX.DescType],Present+System+Gate386+AvailTSS ; Now a TSS!
MOV FS:[LastTask],BX ; And can be switched to
POP CX ; Get task counter
DEC CX ; Any left?
JNZ TaskLoop ; Yes, so continue
MOV FS:[TaskLock],0 ; Can now start task switching
AND [MainTask.DescType],NOT Present ; But not here!
TaskEnd:
JMP TaskEnd ; Wait for keypress to leave
BackToDOS:
CLI ; Disable interrupts
; Same assembler problem: protected mode JMP
DB 0EAh ; JMP FAR MSDOSCode:BackReal
DW OFFSET BackReal,OFFSET MSDOSCode
GetMem PROC NEAR
MOV EAX,ESI ; New address
MOVZX ECX,CX ; Size to allocate
ADD ESI,ECX ; Allocated!
AND ESI,NOT 0FFh ; (Ensure on 256-byte boundary)
ADD ESI,100h ; And don't overlap!
ADD BX,SIZE Descriptor ; New descriptor too
RET
GetMem ENDP
AssignMem PROC NEAR
MOV [BX.LimitLo],CX ; Low limit
MOV [BX.BaseLo],AX ; Low base
SHR EAX,16 ; Get high base
MOV [BX.BaseMid],AL ; Middle base
MOV [BX.DescType],DL ; Type
MOV [BX.DescGran],DH ; Granularity
MOV [BX.BaseHi],AH ; High base (usually zero!)
RET
AssignMem ENDP
AssignTSS PROC NEAR
SHL EAX,4 ; Make address linear
MOV CX,OFFSET TSSLimit ; Size of segment
MOV DL,Present+Memory+Writable ; Type to allocate
MOV DH,ByteGran
CALL AssignMem ; Assign to descriptor
PUSH DS ; Point to TSS temporarily
PUSH ES
MOV DS,BX ; (Note still a data descriptor)
MOV ES,BX
ASSUME DS:TSS ; Tell (fool) assembler
XOR AX,AX ; Zero TSS
XOR DI,DI
MOV CX,TSSSize/2
REP STOSW
MOV [EIPReg],EBP ; Starting instruction
MOV [EFlags],0202h ; Flags to use
MOV [ESPReg],OFFSET StackTop ; Stack pointer to use
MOV [CSSeg],OFFSET IntCode ; Code segment to use
MOV [SSSeg],BX ; Stack to use
ADD [SSSeg],SIZE Descriptor ; (1 past TSS)
MOV [IOMapBase],TSSSize ; Don't worry about I/O
POP ES ; Don't point to TSS any more!
POP DS
ASSUME DS:GDT
MOV [BX.DescType],Present+System+Gate386+AvailTSS ; Now a TSS!
MOV CX,OFFSET StackTop ; Allocated stack size
CALL GetMem ; Next descriptor
MOV DL,Present+Memory+Writable ; Writable memory
MOV DH,ByteGran+More64k ; 32-bit access
CALL AssignMem ; Modify LDT
RET
AssignTSS ENDP
ProtLimit EQU $-1 ; Limit of ProtSeg
ProtSeg ENDS