//----------------------------------------------------------------- // notready.s // // This program illustrates use of the 'Segment-Not-Present' // processor exception, by attempting to call a procedure in // a code-segment whose 'Present' bit was not yet set. Then // our exception-handler will load the procedure's code into // the code-segment, mark the segment as 'Present', and will // return to 'retry' execution of the faulting instruction. // // to assemble: $ as notready.s -o notready.o // and to link: $ ld notready.o -T notready -o notready.b // and install: $ dd if=notready.b of=/dev/sda4 seek=1 // // NOTE: This code begins executing with CS:IP = 1000:0002. // // programmer: ALLAN CRUSE // written on: 27 SEP 2008 //----------------------------------------------------------------- .section .text #------------------------------------------------------------------ .word 0xABCD #------------------------------------------------------------------ main: .code16 mov %sp, %cs:ipltos+0 # save loader's SP value mov %ss, %cs:ipltos+2 # also loader's SS value mov %cs, %ax # address program's arena mov %ax, %ss # using SS register lea tos, %sp # establish new stack-top call build_entries_in_IDT call enter_protected_mode call execute_fault11_demo call leave_protected_mode lss %cs:ipltos, %sp # recover loader's SS:SP lret # then go back to loader #------------------------------------------------------------------ ipltos: .word 0, 0 # save the loader's SS:SP #------------------------------------------------------------------ theGDT: .quad 0x0000000000000000 # null segment-descriptor .equ sel_es, .-theGDT # vram segment's selector .quad 0x0080920B80000007 # vram segment-descriptor .equ sel_cs, .-theGDT # code segment's selector .quad 0x00009A010000FFFF # code segment-descriptor .equ sel_ds, .-theGDT # data segment's selector .quad 0x000092010000FFFF # data segment-descriptor .equ sel_CS, .-theGDT # code segment's selector .quad 0x00001A020000FFFF # code segment-descriptor .equ sel_DS, .-theGDT # data segment's selector .quad 0x000092020000FFFF # data segment-descriptor .equ limGDT, (.-theGDT)-1 # our GDT's segment-limit #------------------------------------------------------------------ #------------------------------------------------------------------ theIDT: .zero 256 * 8 # for 256 IDT-descriptors .equ limIDT, (.-theIDT)-1 # our IDT's segment-limit #------------------------------------------------------------------ regGDT: .word limGDT, theGDT, 0x0001 # image for register GDTR regIDT: .word limIDT, theIDT, 0x0001 # image for register IDTR regIVT: .word 0x03FF, 0x0000, 0x0000 # image for register IDTR #------------------------------------------------------------------ #================================================================== # # Here is the data and code for a procedure that we will relocate # to a higher memory-arena, where it will reside during execution # msg: .ascii " Hello from demand-loaded procedure " len: .short . - msg # length of message-text hue: .byte 0x20 # colors: black-on-green dst: .word (10*80+22)*2 # screen: row=10, col=22 # draw_message: # shows a message (to confirm code got relocated) mov $sel_ds, %si # address program's data mov %si, %ds # with DS register lea msg, %si # point DS:SI to message mov $sel_es, %di # address display-memory mov %di, %es # with ES register mov dst, %di # point ES:DI to dest'n cld # use forward processing mov hue, %ah # setup text-color in AH mov len, %cx # and message-size in CX nxchr: lodsb # fetch the next character stosw # store character w/color loop nxchr # again if more characters lret # returns to other segment #================================================================== #------------------------------------------------------------------ build_entries_in_IDT: # for this demo we only create one interrupt-descriptor mov $0x0B, %ebx # setup gate's ID-number lea theIDT(,%ebx,8), %di # point to gate in IDT # we setup a 286-style (16-bit) interrupt-gate (type=6) # using the SS register (because DS was not yet set up) movw $isrNPF, %ss:0(%di) # loword of entry-point movw $sel_cs, %ss:2(%di) # code-segment selector movw $0x8600, %ss:4(%di) # 16-bit Interrupt-Gate movw $0x0000, %ss:6(%di) # hiword of entry-point ret #------------------------------------------------------------------ #------------------------------------------------------------------ enter_protected_mode: cli # no 'external' interrupts mov %cr0, %eax # current machine status bts $0, %eax # set image of PE-bit mov %eax, %cr0 # enable protected-mode lgdt %cs:regGDT # establish the GDT lidt %cs:regIDT # establish the IDT mov $sel_ds, %ax # address program's stack mov %ax, %ss # using SS register ljmp $sel_cs, $pm # also reload CS register pm: # purge 'real-mode' segment-addresses -- to avoid 'bugs' xor %ax, %ax # 'null' value is OK mov %ax, %ds # in DS register mov %ax, %es # in ES register ret # back to main procedure #------------------------------------------------------------------ execute_fault11_demo: # store the current stack-pointer where our fault-handler # could get to it -- in case of an unexpected error-code mov %sp, %ss:back_pointer+0 # store SP register-value mov %ss, %ss:back_pointer+2 # store SS register-value # now use 'lcall' for transfer to a different code-segment # (it will need to use 'lret' to get back to this segment) lcall $sel_CS, $draw_message # to another code-segment # now we can use 'ret' to return to 'main' (because we got # to this procedure from 'main' using 'call', not 'lcall') ret # return to main routine #------------------------------------------------------------------ leave_protected_mode: mov $sel_ds, %ax # real-mode attributes mov %ax, %ds # in DS register mov %ax, %es # and in ES register mov %cr0, %eax # read machine's status btr $0, %eax # reset PE-bit's image mov %eax, %cr0 # and reenter real-mode ljmp $0x1000, $rm # reload CS for real-mode rm: mov %cs, %ax mov %ax, %ss # reload SS for real-mode lidt %cs:regIVT # use 'real-mode' vectors sti # allow device interrupts ret # back to main procedure #------------------------------------------------------------------ #------------------------------------------------------------------ #================================================================== #------------------------------------------------------------------ # # Here is our code that handles a 'Segment-Not-Present' exception # isrNPF: enter $0, $0 # setup stackframe access testw $0x0007, 2(%bp) # unexpected error-code? jnz back_to_main # yes, we can't handle it call initialize_high_arena # copies our code and data call mark_segment_as_ready # marks descriptor's P-bit leave # discard the stackframe add $2, %sp # discard the error-code iret #<-- now 'retry' the faulting instruction #------------------------------------------------------------------ #================================================================== #------------------------------------------------------------------ isready: .word 0 # flag to indicate if arena is initialized #------------------------------------------------------------------ initialize_high_arena: # check: we only need to 'relocate' our program-code once btw $0, %cs:isready # is code in place yet? jc initok # yes, copying not needed # copy contents of memory-segment from 0x10000 to 0x20000 pusha # must preserve registers push %ds push %es mov $sel_ds, %ax # address arena at 0x10000 mov %ax, %ds # with DS register xor %si, %si # point DS:SI to beginning mov $sel_DS, %ax # address arena at 0x20000 mov %ax, %es # with ES register xor %di, %di # point ES:DI to beginning mov $0x8000, %cx # copy the whole 64K arena cld # using forward processing rep movsw # perform the 'relocation' btsw $0, isready # mark the copying as done pop %es # restore saved registers pop %ds popa initok: ret # return to the caller #------------------------------------------------------------------ #------------------------------------------------------------------ mark_segment_as_ready: # preserve values of registers that get 'clobbered' here push %bx # must preserve registers push %ds # that we may need to use # setup register DS for addressing our Global Descriptors mov $sel_ds, %bx # address program's data mov %bx, %ds # with DS register # now mark the designated segment-descriptor as 'Present' mov 2(%bp), %bx # get CPU's 'error-code' btsw $15, theGDT+4(%bx) # set descriptor's P-bit # restore the prior values to those registers we modified pop %ds # restore saved registers pop %bx # we only used BX and DS ret # return to the caller #------------------------------------------------------------------ # We only need this procedure in case our NPF fault-handler finds # that an unexpected error-code got pushed onto its stack -- thus # an early exit back to 'main' would be appropriate for this demo # back_pointer: .word 0, 0 # for saving stack-address # back_to_main: lss %cs:back_pointer, %sp # restore saved demo-stack ret # for early exit to 'main' #------------------------------------------------------------------ .align 16 # assures stack-alignment .space 512 # allocate area for stack tos: # provides stacktop label #------------------------------------------------------------------ .end # no more to be assembled