1

After some months of investigation, I have accomplished my goal to switch to 32-bit mode, now I was wondering if I could go back, following the: OSDevWiki Real Mode Page I need a 16-bit Protected Mode data selector but I don't know how to make one. Can someone tell me how?

Code:

bootloader.asm:

; A boot  sector  that  enters 32-bit  protected  mode.
[org 0x7c00]
mov bp, 0x9000          ; Set  the  stack.
mov sp, bp
mov bx, MSG_REAL_MODE
call  print_string
call  switch_to_pm      
jmp $

%include "print_string.asm"
%include "gdt.asm"
%include "print_string_pm.asm"
%include "switch_to_pm.asm"
[bits  32]

; This is  where we  arrive  after  switching  to and  initialising  protected  mode.
BEGIN_PM:
    mov ebx , MSG_PROT_MODE
    call  print_string_pm    ; Use  our 32-bit  print  routine.

    jmp $                      ; Hang.

; Global  variables
MSG_REAL_MODE   db "Started  in 16-bit  Real  Mode", 
MSG_PROT_MODE   db "Successfully  landed  in 32-bit  Protected  Mode", 0

; Bootsector  padding
times  510-($-$$) db 0
dw 0xaa55

switch_to_pm.asm:

[bits  16]
; Switch  to  protected  
switch_to_pm:
cli     ; We must  switch  of  interrupts  until  we have
        ; set -up the  protected  mode  interrupt  vector
        ; otherwise  interrupts  will  run  riot.
lgdt [gdt_descriptor]   ; Load  our  global  descriptor  table , which  defines
                        ; the  protected  mode  segments (e.g.  for  code  and  data)
mov eax , cr0           ; To make  the  switch  to  protected  mode , we set
or eax , 0x1            ; the  first  bit of CR0 , a control  register
mov cr0 ,eax

jmp  CODE_SEG:init_pm   ; Make a far  jump (i.e. to a new  segment) to our 32-bit
                        ; code.   This  also  forces  the  CPU to  flush  its  cache  of
                        ; pre -fetched  and real -mode  decoded  instructions , which  can
                        ; cause  problems.

[bits  32]
; Initialise  registers  and  the  stack  once in PM.
init_pm:
mov ax, DATA_SEG         ; Now in PM, our  old  segments  are  meaningless ,
mov ds, ax               ; so we  point  our  segment  registers  to the
mov ss, ax               ; data  selector  we  defined  in our  GDT
mov es, ax
mov fs, ax
mov gs, ax

mov ebp , 0x90000        ; Update  our  stack  position  so it is  right
mov esp , ebp             ; at the  top of the  free  space.
call  BEGIN_PM            ; Finally , call  some well -known  label

GDT.asm:

; GDT
gdt_start:

gdt_null: ; the  mandatory  null  descriptor
dd 0x0   ; ’dd’ means  define  double  word (i.e. 4 bytes)
dd 0x0
gdt_code: ; the  code  segment  descriptor
; base=0x0, limit=0xfffff ,
; 1st  flags: (present )1 (privilege )00 (descriptor  type)1 -> 1001b
; type  flags: (code)1 (conforming )0 (readable )1 (accessed )0 -> 1010b
; 2nd  flags: (granularity )1 (32-bit  default )1 (64-bit  seg)0 (AVL)0 -> 1100b
dw 0xffff     ; Limit (bits  0-15)
dw 0x0        ; Base (bits  0-15)
db 0x0        ; Base (bits  16 -23)
db  10011010b ; 1st flags , type  flags
db  11001111b ; 2nd flags , Limit (bits  16-19)
db 0x0        ; Base (bits  24 -31)

gdt_data: ;the  data  segment  descriptor
; Same as code  segment  except  for  the  type  flags:
; type  flags: (code)0 (expand  down)0 (writable )1 (accessed )0 -> 0010b
dw 0xffff     ; Limit (bits  0-15)
dw 0x0        ; Base (bits  0-15)
db 0x0        ; Base (bits  16 -23)
db  10010010b ; 1st flags , type  flags
db  11001111b ; 2nd flags , Limit (bits  16-19)
db 0x0        ; Base (bits  24 -31)

gdt_end:        ; The  reason  for  putting a label  at the  end of the
                ; GDT is so we can  have  the  assembler  calculate
                ; the  size of the  GDT  for  the GDT  decriptor (below)

; GDT  descriptior
gdt_descriptor:
dw  gdt_end  - gdt_start  - 1   ; Size of our GDT , always  less  one
                                ; of the  true  size
dd  gdt_start                   ; Start  address  of our  GDT

; Define  some  handy  constants  for  the  GDT  segment  descriptor  offsets , which
; are  what  segment  registers  must  contain  when in  protected  mode.  For  example ,
; when we set DS = 0x10 in PM , the  CPU  knows  that we mean it to use  the
; segment  described  at  offset 0x10 (i.e. 16  bytes) in our GDT , which in our
; case is the  DATA  segment  (0x0 -> NULL; 0x08  -> CODE; 0x10  -> DATA)
CODE_SEG  equ  gdt_code  - gdt_start
DATA_SEG  equ  gdt_data  - gdt_start

print_string_pm.asm:

[bits  32]
; Define  some  constants
VIDEO_MEMORY  equ 0xb8000
WHITE_ON_BLACK  equ 0x0f

; prints a null -terminated  string  pointed  to by EDX
print_string_pm:
pusha
mov edx , VIDEO_MEMORY   ; Set  edx to the  start  of vid  mem.

print_string_pm_loop:
mov al, [ebx]            ; Store  the  char at EBX in AL
mov ah, WHITE_ON_BLACK ; Store  the  attributes  in AH

cmp al, 0           ; if (al == 0), at end of string , so
je print_string_pm_done             ; jump to done

mov [edx], ax       ; Store  char  and  attributes  at  current
                    ; character  cell.
add ebx , 1         ; Increment  EBX to the  next  char in  string.
add edx , 2         ; Move to next  character  cell in vid  mem-

jmp  print_string_pm_loop   ; loop  around  to  print  the  next  char.

print_string_pm_done :
popa
ret                  ; Return  from  the  function

print_string.asm:

[bits 16]

print_string:           ; Routine: output string in SI to screen
    mov si, bx  ; Put string position into SI
    mov ah, 0Eh     ; int 10h 'print char' function

repeat_loop:
    lodsb           ; Get character from string
    cmp al, 0
    je print_string_done        ; If char is zero, end of string
    int 10h         ; Otherwise, print it
    jmp repeat_loop

print_string_done :
popa
ret                  ; Return  from  the  function
JeffLee
  • 111
  • 1
  • 10
  • There is example code on the site you cited. I don't really understand why they do all this effort of setting up 16 bit selectors just to never use them again. This seems somewhat nonsensical to me. You should be able to just disable paging and protected mode and then long jump to real mode code if I haven't missed anything. – fuz Jan 24 '20 at 10:12
  • 1
    @fuz: The example on that page doesn't actually show the necessary 16-bit data segment GDT entry the OP is asking about. But it does link to https://wiki.osdev.org/GDT which documents GDT entries, answering the question. I think the point of loading the segment bases/limits is to enter true Real Mode with 64k segment limits, not leave the limits set to whatever (Unreal Mode). Remember, segment bases and limits are set when you mov a selector into a segment reg (in protected mode), and the limit doesn't get changed by `mov Sreg, r/m` in Real Mode. – Peter Cordes Jan 24 '20 at 10:15
  • @PeterCordes Huch? I always thought the segment limit was set in real mode, too. Interesting! – fuz Jan 24 '20 at 10:22
  • @fuz: yeah, it's a pretty obscure bit of trivial these days, and not what I assumed either until someone (probably MichaelPetch) corrected me that Unreal Mode isn't that brittle. It's only relevant if you use 32-bit addressing modes in real mode, or if the previous segment limit happened to be smaller than 64k. – Peter Cordes Jan 24 '20 at 10:26
  • Could someone give an example of a 16-bit Protected Mode data selector or some example of another code? (Or some instructions about it) I know the basics of assembly and of OS development, I can understand some technical terms, but I'm not an expert, so I can't finish understanding how can I switch back to Real Mode. – JeffLee Jan 24 '20 at 11:08
  • 1
    @JeffLee It should have base = 0, limit = 0xffff, Pr = 1, S = 1, Ex = 0, DC = 0, RW = 1, Gr = 0, Sz = 0. The other values don't matter. – fuz Jan 24 '20 at 11:28
  • @fuz If you make a solution putting that on assembly code(to be sure other users can read it without any problem(you can put exactly that in the solution and i'ts going to be correct too(you can choose to put it or not))) I'll mark the answer as correct. Thanks for your help. – JeffLee Jan 24 '20 at 11:37
  • 2
    I'm confused. First you don't show all the code as you snipped out parts you thought are irrelevant but ARE relevant. The code you do show doesn't seem to actually enter true 32-bit protected mode. It enters quasi 16-bit protected mode to set up unreal mode. Can you post all of the code please, because from what I see you likely aren't in 32-bit protected mode at all (and just unreal mode which is real mode with a 4GiB limit&not the usual 64KiB). Some of your code looks similar to this SO question: https://stackoverflow.com/questions/40223669/changing-to-unreal-mode-processor-crash – Michael Petch Jan 24 '20 at 14:10
  • Well... Sorry guys, I was confused, I never heard about Unreal mode, and you are right Michael Petch... I am in unreal mode... not in Protected... Thank you for telling me that... I'll change the title then... So can anyone tell me how to exit this mode? Thank you all, and I apologize for the hole confusion. – JeffLee Jan 24 '20 at 15:12
  • @Peter Cordes: According to my recollection you cannot depend on `mov` or `pop` to a segment register in (Un)Real 86 Mode not resetting the segment limit to 64 KiB. Can you provide more information on whether the limit is preserved by some implementations? – ecm Jan 24 '20 at 17:47
  • 2
    @ecm : I can tell you that pop/mov doesn't change the limit field of the descriptor cache for the segment register. Those instructions only modify the segment register value and it also updates the descriptor cache for that segment with a new computed base (base=segmentval<<4). Limit is untouched. Unreal mode may be turned off by other software that enters protected mode and comes back which may not use the same limits. This was the reason why it was preferable in DOS to do Unreal mode on demand through a #GP/IRQ5 handler if there was any chance something changed the descriptor cache limit. – Michael Petch Jan 24 '20 at 18:22
  • My previous comment applies to running in legacy/real mode. In protected mode (or long mode) an update to a selector (segment register) reloads the segment descriptor cache with the values in the descriptor table (GDT/LDT). That includes the base and limit. That differs from the behaviour in real mode. In v8086 mode the limit is always fixed to 64KiB (unreal mode is not supported in v8086 mode as a consequence) – Michael Petch Jan 24 '20 at 18:31
  • 3
    @ecm : There is an old article by R. Collins that describes the actual behaviours (including oddities between some processors regarding the CS segment register). http://www.rcollins.org/ddj/Aug98/Aug98.html . – Michael Petch Jan 24 '20 at 18:45
  • 1
    Sorry for all the confusion, I'm going to edit the example above to make it better. – JeffLee Jan 26 '20 at 09:38
  • I hope that's a better example and that it's a minimal reproducible example as I can't understand exactly what it is. – JeffLee Jan 26 '20 at 09:47
  • @fuz Could you please post your comment as a solution? As it's exactly what I was looking for. It worked after adquiring a little bit of knowlodge about GDT's. – JeffLee Jan 27 '20 at 13:21
  • @JeffLee Oh yeah. I totally forget about following up with this question, sorry for that. You could also go ahead and write up an answer on your own with the code you actually ended up using. – fuz Jan 27 '20 at 13:36
  • @fuz As you're the creator of the comment I thought you might want to put the solution by yourself to earn reputation if you want I can do it too. As you want. – JeffLee Jan 28 '20 at 08:06
  • I have more than enough reputation and I think if you write an answer yourself, it's going to be better than whatever I'm going to come up with as you have verified that it actually solves your problem. – fuz Jan 28 '20 at 10:03
  • @fuz One of you might want to post a solution so that it can help others though! – Alice F Aug 11 '21 at 16:35

0 Answers0