www.xbdev.net xbdev - software development
Saturday June 24, 2017
home | about | contact | Donations

     
 

Assembly Language

What every pc speaks..1010...

 

Protected Mode - DOS Memory to 32 Bit Memory

by bkenwright@xbdev.net

 

 

When we start our demos, well my demos upto now, they've been launched from dos....and all our kernal code, protected mode code, interrupt code etc is all in the same executable....which is loaded into memory by dos...of course because dos starts us off, it loads our code into memory and we don't really have any choice where it puts it.

We can of course do a separate binary code, which we can have loaded into some specific location, such as 0xfffa for example...anything we want...this is how most kernels work in the real world...where we have a boot program and a kernel which is loaded.  Or if you do a boot disk, a certain location on the disk is read first and is loaded to a specific memory location which we know.

 

Anyhow, up until now we've not really needed to know much about this, as I've just read the location that dos has put us at at the start of our code and then modified all the function addresses and memory locations that use this address.  As shown here:

 

 

Code Snippet

d

; Run in dos (not under windows) and it will take us to 32 bit protected mode

[ORG 0x100]         ; Reserve 256 bytes for dos

[BITS 16]           ; Dos is 16 bits


; assemble using 'nasm' assemblaer

; C:>nasm asm_1.asm -o test.exe

jmp entry           ; Jump to the start of our code

msg1 db 'Where good to go..$';

jumpOffset:
    dd go_pm
    dw 0x08

entry:
 
; Display a message showing where alive!

mov dx, msg1        ; register dx=msg1
mov ah, 9           ; register ah=9 -- the print string function
int 21h             ; dos service interrupt .. looks at register ah to figure out what to do

; Thanks from Brendan, as we have to make sure our GDTR points to the actual
; memory address, add code location and dos 0x100 onto our loaded offset 

    mov eax,0
    mov ax,cs
    shl eax,4
    add [gdtr+2],eax
    add [idtr+2],eax         ; set idtr and gdtr so it points to the 'real' address in memory
    add [jumpOffset],eax     ; do the same for our 32 pm addr

    add [int_start], eax     ; Set the value of int_start (our simple interrupt function) so
                             ; it has the "physical" address
;We have one interrupt function at the bottom of the code:
;divide by zeros, interrupts, irq's ...all will call this same interrupt.
;Our main one will be intfunc, and its address is saved in memory location int_start
    
    mov eax, [int_start]
    mov word [idt_start], ax
    shr eax,16
    mov word [idt_start+6], ax

;Note that we are using this interrupt for testing...later on we can add different
;routines for the different interrupts.

;---------------------------------------------------------------------------
; Whats all this about?...
;---------------------------------------------------------------------------
; We loop through all 46 of the interrupt function addresses in our IDT and
; set them all to point to the same interupt function, which we need to
; correct because dos loads our code at some unknown address in memory and
; our IDT address values need to be the real memory address, not the offsets.

mov ecx, 46                                 ; Number of IDT Interupt Functions
mov ebx, 0                                  ; Start value for our loop counter
zloop:

    mov eax, [int_start]
    add ebx, 8
    mov word [idt_start + ebx], ax
    shr eax,16
    mov word [idt_start+6 + ebx], ax

      dec ecx     
      jne zloop                             ; if ecx is 0, end of loop
;---------------------------------------------------------------------------
...etc etc...rest of the asm code

 

 

This code works okay for our simple test code and things...but it will give us so many problems later on...as all our code locations are all wrong!  Our function addresses etc would all need to be patched and things.  A better way to fix this is to just fix our start address of our gdtr!  Instead of letting it be 0000:0000 we could use the address that dos has given us.

 

Now if you look at the above code first, then our code below you'll see its a considerable improvement.  Basically we've set our base address in our gdt segment registers for our code and data to be the values from our cs (code segment) at load time...basically the position where dos puts us.  The shifting left 4 bytes is the multiplication of 16, due to the fact that we start of in dos!  ...remember how the old dos registers work.....while in 32bit mode..its linear memory.

 

I've also tidied up our IDT value at the bottom, using a repeat macro so we don't have to put copy and paste values over and over again.  Of course you'll have to expand it later on as your kernal or mini toy operating system grows, as you'll want individual things to happen for your different interrupts....but for us here I think its tidier and easier to read now :)

 

 

Assembly Code (Click Here) - asm_1.asm

    
; Run in dos (not under windows) and it will take us to 32 bit protected mode

[ORG 0x100]         ; Reserve 256 bytes for dos

[BITS 16]           ; Dos is 16 bits

; assemble using 'nasm' assemblaer

; C:>nasm asm_1.asm -o test.exe

jmp entry           ; Jump to the start of our code

msg1 db 'Where good to go..$';

entry:
        
; Display a message showing where alive!

mov dx, msg1        ; register dx=msg1
mov ah, 9           ; register ah=9 -- the print string function
int 21h             ; dos service interrupt .. looks at register ah to figure out what to do

; Thanks from Brendan, as we have to make sure our GDTR points to the actual
; memory address, add code location and dos 0x100 onto our loaded offset 

    mov eax,0
    mov ax,cs
    shl eax,4
    mov   ebx,eax  
  
    mov   [codesel + 2],ax                                    
    mov   [codesel + 2],ax                                    
	
    shr   eax,16
    mov   [codesel + 4],al
    mov   [datasel + 4],al
    mov   [codesel + 7],ah
    mov   [datasel + 7],ah
	
    mov   eax, ebx
	
    add [gdtr+2],eax
    add [idtr+2],eax         ; set idtr and gdtr so it points to the 'real' address in memory

;---------------------------------------------------------------------------

  cli		    ; Clear or disable interrupts
  
  mov     al, 0x70
  mov     dx, 0x80
  out     dx, al      ; outb(0x80, 0x70) - disable NMI (Non Maskable Interupts)          

  lgdt[gdtr]	    ; Load GDT
  
  lidt[idtr]       ; Load IDT

  
  mov eax,cr0	    ; The lsb of cr0 is the protected mode bit
  or al,0x01	    ; Set protected mode bit
  mov cr0,eax	    ; Mov modified word to the control register

jmp   0x8:go_pm     ; The 0x8 is so we select our code segment from our gdtr


nop                 ; ignore - no operation opcodes :)
nop

align 4
;---------------------------------------------------------------------------
;                                 32 BIT
;---------------------------------------------------------------------------
; Once we reach here where in protected mode!  32 Bit!  Where not in
; the real world (mode) anymore :)
[BITS 32]
go_pm :

xor   edi,edi
xor   esi,esi

mov ax, 0x10        ; use our datasel selector ( alternatively mov ax, datasel-gdt )
mov ds, ax
mov ss, ax
mov es, ax
mov fs, ax
mov gs, ax

mov esp, 0afffh    ; we need a simple stack if where calling functions!
                   ; Such as interupt functions etc, as the return address is
                   ; put on the stack remember!


mov word [es: 0xb8000],0x740 ; put a char to the screen!...yeahh!
                             ; '@' in the top left of the screen if we made it
                             ; here okay.



; Force a call to interrupt 4!
; Int 0x4           ; We call our interrupt 4 subroutine

;-------------------------------------------------------------------------
; Divide by Zero Warning
;-------------------------------------------------------------------------
; Just remember, when we do a divide by zero, and the interupt is called,
; the return address passed to the interrupt, is in fact the address of the
; line that caused the interrupt!  So if we just return from the interrupt
; it would just keep causing the interrupt over and over again.
;-------------------------------------------------------------------------
; Do a divide by 0 error, so we force a call to our interrupt 0
;  mov eax, 0
;  mov ebx, 0
;  div ebx            ; eax divided by ebx, and stored back in eax

mov byte [es: 0xb8002], "A" ; poke a character onto our screen buffer

nop
nop

;-------------------------------------------------------------------------
; Where not in basics anymore!
;-------------------------------------------------------------------------
; This is the line where we go from simple settups, to using the computers
; interal hardward, fiddling around with the irqs and pic (Programmable
; Interupt Controller) etc.
;-------------------------------------------------------------------------

; Disable all IRQs.
;-------------------------------------------------------------------------
disable_irqs:
          mov     al, 0xFF
          mov     dx, 0x21
          out     dx, al      ; outb(0x21, 0xFF)

          mov     al, 0xFF
          mov     dx, 0xA1
          out     dx, al      ; outb(0x21, 0xFF)
          
;-------------------------------------------------------------------------          
          mov byte [es: 0xb8004], "B" ; poke a char onto screen (debug)

;-------------------------------------------------------------------------
; ReMap PICs
;-------------------------------------------------------------------------
;   PIC 1 & 2 (Master and Slave)
;   Lower 8 IRQs 0x20 onwards
;   Higher 8 IRQs 0x28 onwards
;-------------------------------------------------------------------------
remap_pics:

     ; IWC1
     ;------
          mov     al, 0x11
          out     0x20, al      ; outb(0x20, 0x11)

          mov     al, 0x11
          out     0xA0, al      ; outb(0xA0, 0x11)
 
     ; IWC2
     ;------
          mov     al, 0x20
          out     21, al      ; outb(0x21, pic1)

          mov     al, 0x28
          out     0xA1, al      ; outb(0xA1, pic2)

     ; IWC3
     ;------
          mov     al, 0x04
          out     0x21, al      ; outb(0x21, 4)

          mov     al, 0x02
          out     0xA1, al      ; outb(0xA1, 2)

     ; IWC4
     ;------
          mov     al, 0x01
          out     0x21, al      ; outb(0x21, 0x01)

          mov     al, 0x01
          out     0xA1, al      ; outb(0xA1, 0x01)
;-------------------------------------------------------------------------

          mov byte [es: 0xb8006], "C" ; poke a char onto screen (debug)
         
;-------------------------------------------------------------------------

; Every time an irq interupt occurs, we must clear it before another irq
; is sent.  Else another interupt wont' be sent till its been cleared.
; We usually call this at the end of our interupt routine.

;EOI for IRQ 0-7
mov     al, 0x20
        mov dx, 0x20
        out dx, al      ; outb(0x20, 0x20)
        
;-------------------------------------------------------------------------


;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

;-------------------------------------------------------------------------

sti              ; Interrupts back..

mov byte [es: 0xb8008], "D" ; poke a char onto screen (debug)

lp: jmp lp  ; loops here forever and ever...

; Stays in our loop forever, till something happens, such as an interrupt
; is called by pressing a key or the timer calls an interrupt etc



; We use 16 bits here - as you'll notice we use dw and dd only,
; and out data will be packed together nice and tight.

[BITS 16]
align 4

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Our GDTR register value
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

gdtr :
   dw gdt_end-gdt-1    ; Length of the gdt
   dd gdt	       ; physical address of gdt

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; This is the start of our gdt
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
align 4

gdt:    
gdt0 		        ; 0x0
   dd 0		      
   dd 0                
codesel:            ; 0x8
   dw 0x0ffff	      
   dw 0x0000	       
   db 0x00             	
   db 0x09a	      
   db 0x0cf	       
   db 0x00	       
datasel:            ; 0x10
   dw 0x0ffff	       
   dw 0x0000	      
   db 0x00	       
   db 0x092
   db 0x0cf
   db 0x00
gdt_end:

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

align 4
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Our IDTR register value
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

idtr :
   dw idt_end - idt_start - 1  ; Length of the idt
   dd idt_start                ; physical address of idt

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; This is the start of our idt - its actual value
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;-------------------------------------------------------------------------
[BITS 16]
align 4

idt_start:
%rep 256                         ; We have enough room for 256 ISRs
        dw intfunc               ; offset 15:0
        dw 0x0008                ; selector
        dw 0x8E00                ; present,ring 0,386 interrupt gate
        dw 0                     ; offset 31:16
%endrep
idt_end:

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;-------------------------------------------------------------------------
;                        Interrupt Routine
;-------------------------------------------------------------------------
[bits 32]
align 4

intfunc:
pushad
push es

nop
mov ax,0x10
mov es,ax
                           
mov byte [es: 0xb8012], "I" ; poke a character into the graphics output screen

pop es
popad
iret


;-------------------------------------------------------------------------

TIMES 0x1000-($-$$) DB 0x90    ; And of course, this will make our file size
                             ; equal to 0x1000 a nice round number -
                             ; 0x1000 ... so if you assemble the file
                             ; you should find its that size exactly.


;-------------------------------------------------------------------------

 

 

 

 

 

 

 

 

 

 

 

 

 
 Visitor: 9534626  { 209.237.238.175 } Copyright (c) 2002-2017 xbdev.net - All rights reserved.
Designated tutorial and software are the property of their respective owners.