📄 pm1.asm
字号:
;
; Load the GDTR with the base address and limit of the GDT.
;
lgdt [gdtr]
;
; Set the PE [protected mode enable] bit in register CR0 to begin the
; switch to protected mode.
;
mov eax,cr0
or al,1
mov cr0,eax
;
; We are not yet in full protected mode! Section 10.3 of the INTEL
; 80386 PROGRAMMER'S REFERENCE MANUAL 1986 states:
;
;; Immediately after setting the PE flag, the initialization code must flush
;; the processor's instruction prefetch queue by executing a JMP instruction.
;; The 80386 fetches and decodes instructions and addresses before they are
;; used; however, after a change into protected mode, the prefetched
;; instruction information (which pertains to real-address mode) is no longer
;; valid. A JMP forces the processor to discard the invalid information.
;
; It isn't really necessary to do the JMP right away, as this implies.
; It simply means that protected mode doesn't "kick in" until the segment
; registers are reloaded. Above, we set the ES segment register to 0xB800.
; This is the real-mode segment of the text video memory. With the PE bit
; still set, let's copy a message from the real-mode data segment (DS) to
; the video memory (ES).
;
lea si,[msg0] ; -> "still in real mode!"
mov di,(80 * 1 + 2) * 2 ; row 1, column 2
mov cx,38
cld
rep movsb
;
; The code above won't work in protected mode. It's there just to
; prove that setting the PE bit is not enough to enter protected mode.
;
; Now do a far jump. This reloads the CS register and flushes the
; real-mode instructions from the prefetch queue. CS is the segment
; register used for instruction fetches, so this is where the switch
; from 16-bit instructions (real-mode) to 32-bit instructions
; (protected-mode) takes place.
;
; But what goes into CS? In real mode, we use the segment address. In
; protected mode, we use a SELECTOR:
;
; MSB b14 b13 b12 b11 b10 b9 b8 b7 b6 b5 b4 b3 b2 b1 LSB
;+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
;| index | L | RPL |
;+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
;
; index is a 13-bit index into the GDT. It selects one of the possible 8192
; descriptors in the table. (Note: 8192 descriptors/table, 8 bytes/descriptor.
; The GDT is no larger than 64K.)
;
; If the L bit is set, this selector selects a descriptor from one of the
; LOCAL DESCRIPTOR TABLES (LDT), instead of the GDT. For simple code (like
; this code) or code that doesn't use LDTs, set this bit to 0.
;
; RPL is a 2-bit REQUESTOR PRIVILEGE LEVEL. For simple code: set these bits
; to zero.
;
; Here, we load the hex value 10 into CS. In binary, this is
; 0000 0000 0001 0000. The top thirteen bits are 0000000000010.
; This selector choses descriptor #2 in the GDT (the code segment
; descriptor).
;
jmp SYS_CODE_SEL:do_pm ; jumps to do_pm
;
; Real-mode interrupt 0Dh handler:
;
trap: mov ax,0xB800
mov fs,ax
mov byte [fs:0x9C],'!'
pop ax ; point stacked IP beyond...
add ax,5 ; ...the offending instruction
push ax
iret
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Alert! Now in 32-bit protected mode.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
[BITS 32]
do_pm:
;
; Now we are in 32-bit protected mode -- or are we? Besides CS, all of the
; segment registers still contain real-mode values. Let's try using real-
; mode addressing to put another message on the screen.
;
; Here's a potential problem: addressing is still 16-bit real-mode (oops,
; I gave it away!), but instructions are 32-bit. The rep movsb used below
; will read from DS:ESI and write to ES:EDI. We can either make sure the
; top 16 bits of ESI and EDI are zeroed, or use an 16-bit address size
; override prefix with 'rep movsb'.
;
xor edi,edi
xor esi,esi
lea si,[msg1] ; -> "ES, DS still real mode!"
mov di,(80 * 2 + 3) * 2 ; row 2, column 3
mov ecx,46
cld
rep movsb
;
; ARRGH, it works :) Real-mode addressing hangs on.
;
; To enable full protected-mode addressing, we must put valid protected-
; mode selectors in the DS and SS registers:
;
mov ax,SYS_DATA_SEL
mov ds,ax
mov ss,ax
;
; Can we now print out a message to let the world know we've made it?
;
; mov ax,0xB800
; mov es,ax
; mov byte [es:0],'@'
;
; This won't work (which is why it's commented out). Remember, the
; segment registers contain selectors when in protected mode. Looking
; at the GDT, we see four descriptors (and their associated selectors):
;
; NULL 0
; linear data 8 =LINEAR_SEL
; code 10 hex =SYS_CODE_SEL
; data/stack 18 hex =SYS_DATA_SEL
;
; Use of the NULL segment/selector/descriptor is forbidden. Trying to
; access video memory via the code segment/selector/descriptor might
; work, but it is a poor programming practice, and will almost certainly
; cause problems down the road. What about SYS_DATA_SEL?
;
; mov ax,SYS_DATA_SEL
; mov es,ax
; mov byte [es:0],'@'
;
; As you may have guessed, that won't work either. It writes to the lowest
; byte of the data/stack segment. Above, we set the base of the data/stack
; segment descriptor to DS * 16. Since this is a .COM file, CS=DS, and
; we end up scribbling on memory 256 bytes before the address "start".
; None of these work, either:
;
; mov ax,SYS_DATA_SEL
; mov es,ax
; mov byte [es:0xB800],'@' ; writes unknown byte at offset
; ; B800 hex of data segment
;
; mov ax,SYS_DATA_SEL
; mov es,ax
; mov byte [es:0xB8000],'@' ; writes unknown byte at offset
; ; B8000 hex of data segment
;
; OK, I've beaten the point to death. The answer lies in LINEAR_SEL.
; In real mode, the address of the text-mode video memory is B800:0000
; As a "flat" ("linear"), 32-bit address, this would be 000B8000.
; Because the descriptor referred to by LINEAR_SEL has a base segment
; address of zero, we can simply use the linear address 000B8000 with
; LINEAR_SEL to get at the video memory:
;
mov ax,LINEAR_SEL
mov es,ax
;
; Here's a useful debug tip: once you have basic pmode running, with a
; linear selector like this, you can poke single bytes into video memory
; after each questionable piece of code, to see how far the code got:
;
; questionable PM code here
mov byte [es:dword 0xB8000],'0'
; more questionable PM code here
mov byte [es:dword 0xB8002],'1'
; still more questionable PM code here
mov byte [es:dword 0xB8004],'2'
; (you get the picture)
mov byte [es:dword 0xB8006],'3'
;
; Because we've chosen the base address of the data segment so that
; addressing works the same in real mode and protected mode, we can
; refer to an address like "msg2" without grief or confusion:
;
lea esi,[msg2] ; -> "Finally in protected mode!"
;
; (though we now use 32-bit registers ESI, EDI, and ECX, instead of 16-bit
; SI, DI, and CX).
;
; With LINEAR_SEL, the screen starts at address B8000 hex. We need to
; add that value to whatever address we compute for a given cursor
; location. Notes:
; - 32-bit addresses like this are ILLEGAL in real mode (but see below)
; - We could've set the base of LINEAR_SEL to B8000 hex (and possibly
; called it VIDEO_SEL) to avoid this step. A segment with base 0,
; however, lets us easily get at other "interesting" areas of memory
; e.g. the ROMs.
;
mov edi,0xB8000 + (80 * 3 + 4) * 2 ; row 3, column 4
mov ecx,52
cld
rep movsb
;
; OK, enough of protected mode. Can we return to real-mode DOS without
; things crashing and burning? We'll try. Section 14.5 of the INTEL
; 80386 PROGRAMMER'S REFERENCE MANUAL 1986 states:
;
;; 2. Transfer control to a segment that has a limit of 64K (FFFFH). This
;; loads the CS register with the limit it needs to have in real mode.
;
; This is not enough! It implies that you could run with a 32-bit protected
; mode segment that is page-granular and has limit 16 (16 * 4K = 64K). The
; segment must also be a 16-bit segment (Default/Big bit set to zero).
; Essentially, we must switch (briefly) to 16-bit protected mode before
; going on to real mode. Fortunately, if the necessary descriptor is
; provided, that's easy to do:
;
jmp REAL_CODE_SEL:do_16
[BITS 16]
do_16:
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Now in 16-bit protected mode.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
; Going back to real mode with the data and stack segments having a limit
; of 4G (0xFFFFF and page granular) can cause problems. Referring again to
; Section 14.5 of the INTEL 80386 PROGRAMMER'S REFERENCE MANUAL 1986:
;
;; 3. Load segment registers SS, DS, ES, FS, and GS with a selector that
;; points to a descriptor containing the following values, which are
;; appropriate to real mode:
;;
;;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -