www.xbdev.net xbdev - software development
Thursday February 23, 2017
home | about | contact | Donations

     
 

Assembly Language

What every pc speaks..1010...

 

Protected Mode (Interrupts).....

by bkenwright@xbdev.net

 

 

 

We need interrupts...and well in protected mode is a little different.  As we have descriptor tables for all our memory management and of course for all our interrupts (Interrupt Descriptor Table).  Its a little tricky to put all these things together at first, which is why I did the first part of the tutorial without IDT (Interrupt Descriptor Table), as you can get away with it.  But sometimes you want to know when somethings happened and have it trigger a piece of code...such as timing, or divide by zero or hardware plugged in etc.

 

So here is what we'll do.

 

1. Go into dos - show a little message to show our program started (i.e. hello world :)

2. Set GDT basic values

3. Set IDT values to catch a divide by zero error

4. Disable interrupts

5. Set the GDTR and IDTR Register Values

6. Turn CR0 on!....where in Protected Mode....almost

6.1/2 Put interrupts back on :)

7. Do a jump to flush the cpu pipe.

8. Where here in PM!  32 Bit

9. Do something...display a sign...character or something

10. React to a divide by zero, if it works it will display a sign

11. Its over!...End.

 

Each Interrupt Descriptor is 64bits, and as usual is all mixed up in the way its layed out...but we just have to accept it.

 

word - offset_low_word

word - selector

byte   - control parameters

byte   - access details

word - offset_high_word

 

The IDTR register definition is the same as the GDTR register, as its 48 bits and the first word is for the limit, and the next 32 bits is for the base address.

 

 

The following table shows interrupt assignments in the Pentium:

Vector number Description
0 Divide Error (Division by zero)
1 Debug Interrupt (Single step)
2 NMI Interrupt
3 Breakpoint
4 Interrupt on overflow
5 BOUND range exceeded
6 Invalid Opcode
7 Device not available (1)
8 Double fault
9 Not used in DX models and Pentium (2)
10 Invalid TSS
11 Segment not present
12 Stack exception
13 General protection fault
14 Page fault
15 Reserved
16 Floating point exception (3)
17 Alignment check (4)
18 31 Reserved on 3/486, See (5) for Pentium
32 255 Maskable, user defined interrupts

 

 

Something I came across on using the NASM assember to set the IDT values. (mega-tokyo link)

Few ways of getting the offsets into the IDT (or GDT). At assembly time you could:

Code:
dw (theOffset - CODESTART + ORGADDRESS) & 0xFFFF
dw 0x10
dw 0x8E00
dw (theOffset - CODESTART + ORGADDRESS) >> 16


Unfortunately NASM won't let you do "dw theOffset & 0xFFFF" because "theOffset" is a label not a normal number, so you have to add the extra stuff which would need to be defined beforehand. For e.g. at the start of your code put something like:

Code:
%define ORGADDRESS 0x12345678
org ORGADDRESS
bits 32
CODESTART:

This should work because subtracting a label from a label leaves a normal number, and ORGADDRESS is also a normal number, so the shift (or and) isn't working on label anymore

 

asm_code (download asm file)
; 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' assembler

; 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

; Where in dos, and we've done a simple text message to the screen to show
; our program is running... so now where going to break out of this real 16 bit
; world and get into 32 protected mode.  So lets set things up and go go go..

   cli		    ; Clear or disable interrupts

; 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 [jumpOffset],eax     ; do the same for our 32 pm addr

;-------------------------------------------------------------------------
; NEW * NEW * NEW * NEW * NEW * NEW * NEW * NEW * NEW * NEW * NEW * NEW *
;-------------------------------------------------------------------------
    add [idtr+2],eax         ; set idtr and gdtr so it points to the 'real' address in memory
    
    add [int_addr], eax     ; Set the value of int_addr (our simple interrupt function) so
                             ; it has the "physical" address
    
    mov eax, [int_addr]
    mov word [idt_start], ax ; We have to fix the values in our idt table to they
    shr eax,16               ; are set to the real memory address of our interrupt
    mov word [idt_start+6], ax ; function
    
   lidt[idtr]       ; Load IDT
   lgdt[gdtr]	    ; Load GDT

   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 far dword [jumpOffset]  ;can't just use "jmp go_pm" as where in dos!

nop                 ; ignore - no operation opcodes :)
nop

;---------------------------------------------------------------------------
;                                 32 BIT
;---------------------------------------------------------------------------
; Once we reach here where in protected mode!  32 Bit!  Where not in
; the real world (mode) anymore :)

[BITS 32]
go_pm : 
   mov ax, 0x10        ; use our datasel selector ( alternatively mov ax, datasel )
   mov ds, ax,
   mov es, ax
   mov fs, ax
   mov gs, ax
   mov ss, ax

   mov esp, 0abffh    ; we need a simple stack if where calling functions!	

; To show we've made it this far, lets poke a char into the top
; left of the graphics card memory so it shows up on the screen

   mov word [es:0xb8000], 0x742 ; Display white B in protected mode


;------------------------------------------------------------------------
; NEW * NEW * NEW * NEW * NEW * NEW * NEW * NEW * NEW * NEW * NEW * NEW *
;------------------------------------------------------------------------

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

; 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

; Just a note, we don't need to switch back on interrupts with
; the opcode sti to use the cpu interrupts :)

nop
nop


spin : jmp spin      ; Loop forever


; We use 16 bit alignment 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 - its actual value
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

gdt
nullsel equ $-gdt      ; $->current location,so nullsel = 0h
gdt0 		       ; Null descriptor,as per convention gdt0 is 0
   dd 0		       ; Each gdt entry is 8 bytes, so at 08h it is CS
   dd 0                ; In all the segment descriptor is 64 bits
codesel equ $-gdt      ; This is 8h,ie 2nd descriptor in gdt
code_gdt	       ; Code descriptor 4Gb flat segment at 0000:0000h
   dw 0x0ffff	       ; Limit 4Gb  bits 0-15 of segment descriptor
   dw 0x0000	       ; Base 0h bits 16-31 of segment descriptor (sd)
   db 0x00             ; Base addr of seg 16-23 of 32bit addr,32-39 of sd	
   db 0x09a	       ; P,DPL(2),S,TYPE(3),A->Present bit 1,Descriptor	
                       ; privilege level 0-3,Segment descriptor 1 ie code	    		                  
                       ; or data seg descriptor,Type of seg,Accessed bit
   db 0x0cf	       ; Upper 4 bits G,D,0,AVL ->1 segment len is page		                                     
                       ; granular, 1 default operation size is 32bit seg		                              
                       ; AVL : Available field for user or OS
                       ; Lower nibble bits 16-19 of segment limit
   db 0x00	       ; Base addr of seg 24-31 of 32bit addr,56-63 of sd
datasel equ $-gdt      ; ie 10h, beginning of next 8 bytes for data sd
data_gdt	       ; Data descriptor 4Gb flat seg at 0000:0000h
   dw 0x0ffff	       ; Limit 4Gb
   dw 0x0000	       ; Base 0000:0000h
   db 0x00	       ; Descriptor format same as above
   db 0x092
   db 0x0cf
   db 0x00
gdt_end


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;*********************************NEW*************************************
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

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

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

align 4
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; This is the start of our idt - its actual value
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

idt_start:
isr_00_interrupt:      ; 
   dw isr_00           ; low word offset  (i.e. isr_00 & 0xffff)
   dw 0x0008	       ; selector
   db 0x00             ; control parameters
   db 0x8E             ; access details
   dw 0                ; high word offset
                       ; (won't let us do the isr_00>>16 so we would have
                       ;  to do this at run time earlier)
idt_end:


; Offset values to our simple test interrupt
;-------------------------------------------------------------------------
int_addr :
   dd isr_00
   dw 0x08

[BITS 32]
; Make sure you include this 'bits 32' here else where in 16bit mode!
; and it causes a crash...thanks to Brandon for noticing this :)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; When an interrupt occurs we'll just end up here, calling these
; functions, of course there both the same but we can make them do 
; different things later on :)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

isr_00:
isr_20:
pushad
push es
mov ax,0x10
mov es,ax
mov byte [es:0xb8020], "U" ; poke a character into the graphics output screen
pop es
popad
iret


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


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

 

 

Crap, it doesn't work!.... you can run the above code and what you'll find is that as soon as it goes into protected mode, it reboots!  ...no excuses, no sorrys...just whoosh...and back to your startup bios etc

 

Download Asm Code (Click Here) - asm_2.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' assembler

; C:>nasm asm_2.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
    
    mov eax, [int_start]
    mov word [idt_start], ax
    shr eax,16
    mov word [idt_start+6], ax

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

  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 far dword [jumpOffset]  ;can't just use "jmp go_pm" as where in dos!


nop                 ; ignore - no operation opcodes :)
nop
;IMPORTANT
;When you do 32bit code, always do an align 4, so that  your new 32 bit
;code is aligned on the 4 byte boundary, or you could get some irratic
;results or unknown opcode errors!

    
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 :

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

nop

mov esp, 0afffh    ; we need a simple stack if where calling functions!

; to jump over a nop operation
;push eip
;call fnx ; can't use push eip, so we sneekily use call fnx which pushes eip onto the stack :)
;fnx:
;pop eax        ; 1
;add eax, 13    ; 5  0502000000
;jmp eax        ; 2  FFE0 

nop             ; 1  90
nop
nop
nop
nop


mov word [es: 0xb8000],0x740 ; put a char to the screen!...yeahh!
                             ; Where all okay here :)

; Small delay loop
; on a p4 2ghz its about 4 seconds :)
; You don't need this delay, I just was messing with my asm :)
;-------------------------------------------------------------------
mov eax, 1000
xloop:

mov ebx, 200
yloop:

mov ecx, 100 
zloop:
      dec ecx     
      jne zloop  

      dec ebx     
      jne yloop  

      dec eax     
      jne xloop  
;------------------------------------------------------------------


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

; 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

;IMPORTANT
; when you trigger an interrupt, e.g. the divide by zero, the actual
; address at which the interrupt occured is saved on the stack.  So if
; we return from our interrupt routine iret, this will return us to 
; the place that caused the interrupt.
; So in this example, we add 2 bytes to our retrun address in our
; interrupt routine, so goes to the next piece of code in the list

; Here is what the hex code and mnemonic look like
;B800000000        mov eax,0x0    (size 5 bytes)
;BB00000000        mov ebx,0x0    (size 5 bytes)
;F7F3              div ebx        (size 2 bytes)
;12 bytes

nop
nop


mov byte [es:0xb8000], "L" ; poke a character into the graphics output screen
                           ; so we know we've come back from our interrupt and
                           ; we are in our loop forever :)

nop
nop


mov ebx, 5
mov ax,0x10
mov es,ax
mov byte [es:0xb8030], "G" ; poke a character into the graphics output screen



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

; 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 		       
   dd 0		      
   dd 0                
codesel:          
   dw 0x0ffff	      
   dw 0x0000	       
   db 0x00             	
   db 0x09a	      
   db 0x0cf	       
   db 0x00	       
datasel:        
   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
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
align 4
idt_start:
   dw int_test         ; Address of our interrupt function
   dw 0x0008	       ; selector
   db 0x00             ; control parameters
   db 0x8E             ; access details
   dw 0x0              ; higher 16bits of our int address
idt_end:

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

BITS 32
; Offset values to our simple test interrupt
;-------------------------------------------------------------------------
int_start :
   dd int_test
   dw 0x08
 

align 4

; Nothing special, just a very basic interrupt function so we know something
; has happened
;-------------------------------------------------------------------------
[BITS 32]
int_test:
pushad
push es

nop  ; just ignore.. :)  no operations
nop

mov ebx, 5
mov ax,0x10
mov es,ax
mov byte [es:0xb8020], "R" ; poke a character into the graphics output screen
mov byte [es:0xb8022], "S" ; poke a character into the graphics output screen

pop es
popad

pop eax     ; get the return address on the stack
add eax, 2  ; increment it by 2, eg. div eax is 2 bytes long
push eax    ; push our new return address onto the stack so iret uses it :)
iret

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

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


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

 

 

 

 

 

 

 

 

 

 

 

------------------------------------------------------------------------------------------------------------

Further Reading / References:

Operating System Resources - http://www.nondot.org/~sabre/os/articles

 

 

 

 

 

 

 

 

 

 

 
 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.