//----------------------------------------------------------------- // pgfaults.s (Adapted from our 'vrampage.s' example) // // This example illustrates several 'page-fault' error-codes // which result from attempts to access a 'Not-Present' page // or from attempts to access a 'Supervisor-Only' page while // executing code at privilege-level 3. Here the page-fault // handler displays the address of the faulting instruction, // obtained from register CR2, and the 'error-code' that was // pushed onto the ring0 stack in response to the exception. // (A user then hits any key to continue the demonstration.) // // to assemble: $ as pgfaults.s -o pgfaults.o // and to link: $ ld pgfaults.o -T ldscript -o pgfaults.b // and install: $ dd if=pgfaults.b of=/dev/sda4 seek=1 // // NOTE: This code begins executing with CS:IP = 1000:0002. // // programmer: ALLAN CRUSE // written on: 10 NOV 2008 //----------------------------------------------------------------- .equ realCS, 0x1000 # arena's segment-address .section .text #------------------------------------------------------------------ .word 0xABCD # our loader expects this #------------------------------------------------------------------ main: .code16 # begins in x86 real-mode mov %sp, %cs:ipltos+0 # preserve loader's SP mov %ss, %cs:ipltos+2 # preserve loader's SS mov %cs, %ax # address program arena mov %ax, %ss # using SS register lea tos, %sp # establish a new stack call init_IDT_descriptors call create_paging_tables call enter_protected_mode call execute_program_demo call leave_protected_mode lss %cs:ipltos, %sp # recover the former stack lret # return control to loader #------------------------------------------------------------------ ipltos: .word 0, 0 #------------------------------------------------------------------ theGDT: .quad 0x0000000000000000 # required null-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_es, (.-theGDT) # vram-segment's selector .quad 0x000092000000FFFF # vram-segment descriptor .equ sel_fs, (.-theGDT) # flat-segment's selector .quad 0x008F92000000FFFF # flat-segment descriptor .equ privCS, (.-theGDT)+0 # code-segment's selector .quad 0x00409A010000FFFF # code-segment descriptor .equ privDS, (.-theGDT)+0 # data-segment's selector .quad 0x004092010000FFFF # data-segment descriptor .equ userCS, (.-theGDT)+3 # code-segment's selector .quad 0x0040FA014000FFFF # code-segment descriptor .equ userDS, (.-theGDT)+3 # data-segment's selector .quad 0x0040F2015000FFFF # data-segment descriptor .equ userES, (.-theGDT)+3 # vram-segment's selector .quad 0x0040F2000000FFFF # vram-segment descriptor .equ userSS, (.-theGDT)+3 # stak-segment's selector .quad 0x0040F6016000FFFF # stak-segment descriptor .equ ring0, (.-theGDT) # selector for call-gate .word finish, sel_cs, 0xEC00, 0x0000 # gate-descriptor .equ selTSS, (.-theGDT) # task-segment's selector .word limTSS, theTSS, 0x8901, 0x0000 # task-descriptor .equ limGDT, (.-theGDT)-1 # our GDT's segment-limit #------------------------------------------------------------------ regGDT: .word limGDT, theGDT, 0x0001 # image for GDTR register #------------------------------------------------------------------ regCR3: .long 16*realCS + pgdir # image for CR3 register #------------------------------------------------------------------ create_paging_tables: # setup segment-register DS to address program's arena mov %cs, %ax # address paging tables mov %ax, %ds # using DS register #------------------------------------ # initialize five page-table entries #------------------------------------ # setup mapping for virtual-address 0x00000000 mov $0x00, %edi # for virtual page 0x00 mov $0xB8000, %eax # physical page-address or $0x003, %eax # present+writable+user mov %eax, pgtbl(,%edi,4) # write the table-entry # setup mapping for virtual-address 0x00010000 mov $0x10, %edi # for virtual page 0x10 mov $0x10000, %eax # physical page-address or $0x003, %eax # present and writable mov %eax, pgtbl(,%edi,4) # write the table-entry # setup mapping for virtual-address 0x00011000 mov $0x11, %edi # for virtual page 0x11 mov $0x11000, %eax # physical page-address or $0x003, %eax # present and writable mov %eax, pgtbl(,%edi,4) # write the table-entry # setup mapping for virtual-address 0x00012000 mov $0x12, %edi # for virtual page 0x12 mov $0x12000, %eax # physical page-address or $0x003, %eax # present and writable mov %eax, pgtbl(,%edi,4) # write the table-entry # setup mapping for virtual-address 0x00013000 mov $0x13, %edi # for virtual page 0x13 mov $0x13000, %eax # physical page-address or $0x003, %eax # present and writable mov %eax, pgtbl(,%edi,4) # write the table-entry #------------------------------------ # initialize one page-drectory entry #------------------------------------ # setup the page-directory to use our page-table mov $0x10000, %ebx # arena physical-address lea pgtbl(%ebx), %eax # pgtbl physical-address or $0x007, %eax # present+writable+user mov %eax, pgdir # page-directory entry 0 ret #------------------------------------------------------------------ enter_protected_mode: cli # interrupts disabled mov %cr0, %eax # current machine status bts $0, %eax # turn on PE-bit's image mov %eax, %cr0 # enable protected mode lgdt %cs:regGDT # establish our GDT lidt %cs:regIDT # establish our GDT ljmp $sel_cs, $pm # reload CS register pm: mov $sel_ds, %ax mov %ax, %ss # reload SS register xor %ax, %ax # purge invalid values mov %ax, %ds # from DS register mov %ax, %es # from ES register mov %ax, %fs # from FS register mov %ax, %gs # from GS register ret #------------------------------------------------------------------ execute_program_demo: mov %esp, %ss:tossav+0 # preserve ESP mov %ss, %ss:tossav+4 # preserve SS mov $privDS, %ax mov %ax, %ss and $0xFFFFFFF0, %esp mov %esp, %ss:theTSS+4 # setup image for ESP0 mov %ss, %ss:theTSS+8 # setup image for SS0 # setup page-directory address in control register CR3 mov %cs:ptdb, %eax # page-directory address mov %eax, %cr3 # goes into CR3 register # turn on paging (by setting bit #31 in register CR0) mov %cr0, %eax # current machine status bts $31, %eax # turn on PG-bit's image mov %eax, %cr0 # enable page-mappings jmp .+2 # flush prefetch queue # establish Task-State Segment mov $selTSS, %ax ltr %ax # now transfer control to 32-bit code in 'ring3' pushl $userSS # image for SS pushl $0 # image for ESP pushl $userCS # image for CS pushl $draw_message # image for EIP lretl # transfer to ring3 finish: # disable paging (by clearing bit #31 in register CR0) mov %cr0, %eax # current machine status btr $31, %eax # reset PG-bit's image mov %eax, %cr0 # disable page-mapping jmp .+2 # flush prefetch queue # invalidate the CPU's Translation Lookaside Buffer xor %eax, %eax # setup "dummy" value mov %eax, %cr3 # and write it to CR3 # recover the saved 16-bit stack (for return to 'main') lss %cs:tossav, %esp # recover 16-bit stack ret #------------------------------------------------------------------ tossav: .long 0, 0 # storage for SS and ESP #------------------------------------------------------------------ leave_protected_mode: mov $sel_ds, %ax # insure 'writable' 64KB mov %ax, %ds # in DS hidden cache mov %ax, %es # in ES hidden cache mov %ax, %fs # in FS hidden cache mov %ax, %gs # in GS hidden cache mov %cr0, %eax # current machine status btr $0, %eax # reset PE-bit's image mov %eax, %cr0 # disable protected-mode ljmp $realCS, $rm # reload CS register rm: mov %cs, %ax mov %ax, %ss # reload SS register lidt %cs:regIVT # real-mode vector-table sti # reenable interrupts ret #------------------------------------------------------------------ ptdb: .long 16*realCS + pgdir # pgdir: physical address #------------------------------------------------------------------ theIDT: .zero 16 * 8 # enough for 16 IDT gates .equ limIDT, (.-theIDT)-1 # our IDT's segment-limit #------------------------------------------------------------------ regIDT: .word limIDT, theIDT, 0x0001 # image for IDTR register regIVT: .word 0x03FF, 0x0000, 0x0000 # image for IDTR register #------------------------------------------------------------------ init_IDT_descriptors: # install interrupt-gate for Page-Fault exceptions mov $0x0E, %ebx # ID-number for exception lea theIDT(,%ebx,8), %di # gate-descriptor offset movw $isrPGF, %ss:0(%di) # entry-point lower-word movw $privCS, %ss:2(%di) # code-segment selector movw $0x8E00, %ss:4(%di) # 80386 interrupt-gate movw $0x0000, %ss:6(%di) # entry-point upper-word # install interrupt-gate for General Protection faults mov $0x0D, %ebx # ID-number for exception lea theIDT(,%ebx,8), %di # gate-descriptor offset movw $isrGPF, %ss:0(%di) # entry-point lower-word movw $privCS, %ss:2(%di) # code-segment selector movw $0x8E00, %ss:4(%di) # 80386 interrupt-gate movw $0x0000, %ss:6(%di) # entry-point upper-word ret #------------------------------------------------------------------ hex: .ascii "0123456789ABCDEF" # array of hex numerals msg0: .ascii " nnn=xxxxxxxx " # buffer for stack element len0: .int . - msg0 # length of message-buffer att0: .byte 0x70 # attribute for characters dst0: .int (8*80 - 15)*2 # screen-position's base names: .ascii " CR2 err EIP CS EFL" # string of element-names nelts: .int (. - names)/4 # number of stack elements #------------------------------------------------------------------ isrPGF: .code32 # our exception-handler for page-fault exceptions sub $4, %esp # allocate space for CR2 enter $0, $0 # setup error-code access pushal # preserve registers pushl %ds pushl %es # save the value found in Control Register CR2 mov %cr2, %eax # get register-value CR2 mov %eax, 4(%ebp) # save CR2-value on stack # setup segment-registers mov $privDS, %ax # address program data mov %ax, %ds # with DS register mov $sel_es, %ax # address video memory mov %ax, %es # with ES register # loop to show the stackframe and the faulting address xor %ebx, %ebx # initialize array-index nxelt: # put element-name into buffer mov names(,%ebx,4), %eax mov %eax, msg0 # put element-value into buffer mov 4(%ebp,%ebx,4), %eax lea msg0+5, %edi call eax2hex # compute the screen-position mov dst0, %edi imul $160, %ebx, %eax sub %eax, %edi # draw the buffer-contents onscreen cld lea msg0, %esi mov len0, %ecx mov att0, %ah nxchr: lodsb stosw loop nxchr inc %ebx cmp nelts, %ebx jb nxelt # wait for the user to press and release a key spin: in $0x64, %al # read keyboard status test $0x01, %al # test for OUTB=1 jz spin # spin if OUTB<>1 in $0x60, %al # read keyboard scancode test $0x80, %al # was a key released? jz spin # no, spin till released #---------------------------------------------------------- # Now, if the faulting address is below 0x00400000 (4-MB), # we will modify its entry in our page-table, as follows: # if the error-code's P-bit is clear (i.e., page was 'Not # Present'), we add a new entry into the page-table which # maps the page to our program-arena at 0x00010000; else, # if the page already was 'present', then we make it both # 'writable' and 'user-accessible'. #---------------------------------------------------------- # sanity check: is faulting-address within table's bounds? mov 4(%ebp), %eax # get the faulting address cmp $0x00400000, %eax # within page-table bounds? jb intbl # yes, we will handle it ljmp $sel_cs, $finish # else bail out from demo intbl: # prepare to modify the faulting-address's page-table entry mov $sel_fs, %ax # address 'flat' segment mov %ax, %ds # with DS register mov %cr3, %edx # page-directory address # lookup page-table address in the page-directory mov 4(%ebp), %ecx # get page-fault address and $0xFFC00000, %ecx # isolate pgdir index shr $22, %ecx # get bits 31..22 mov (%edx,%ecx,4), %ebx # page-directory entry and $0xFFFFF000, %ebx # page-table's address # compute index of page-table entry for faulting address mov 4(%ebp), %ecx # get page-fault address and $0x003FF000, %ecx # isolate pgtbl index shr $12, %ecx # get bits 21..12 # examine error-code's P-bit to determine which action btl $0, 8(%ebp) # page already present? jc setWU # yes, then W-bit,U-bit mov $0x00010000, %eax # arena physical address mov %eax, (%ebx, %ecx, 4) # new page-table entry setWU: orl $0x007, (%ebx, %ecx, 4) # set entry attributes popl %es # recover registers popl %ds popal leave # discard stackframe add $4, %esp # discard CR2 space add $4, %esp # discard error-code iret # retry faulting opcode #------------------------------------------------------------------ eax2hex: .code32 # converts EAX to hex string at DS:EDI pushal mov $8, %ecx nxnyb: rol $4, %eax mov %al, %bl and $0xF, %ebx mov hex(%ebx), %dl mov %dl, (%edi) inc %edi loop nxnyb popal ret #------------------------------------------------------------------ isrGPF: .code32 # General protection exception pushal pushl %ds pushl %es mov $sel_es, %ax mov %ax, %es xor %edi, %edi cld mov $0x50, %ecx mov $0x4F46, %ax rep stosw popl %es popl %ds popal jmp isrPGF #------------------------------------------------------------------ .align 16 # insures stack alignment .space 256 # space for stack's usage tos: # labels our top-of-stack #------------------------------------------------------------------ msg: .ascii " Hello from 'ring3' virtual memory " len: .long . - msg # number of message bytes att: .byte 0x5F # message text-attributes dst: .long (12 * 80 + 22) * 2 # message screen-position #------------------------------------------------------------------ draw_message: .code32 pushl %ds pushl %es # now write a message to the "virtual" video memory mov $userDS, %ax # address program data mov %ax, %ds # with DS register mov $userES, %ax # address segment zero mov %ax, %es # with ES register mov dst, %edi # point ES:DI to screen cld # do forward processing lea msg, %esi # point DS:SI to string mov len, %ecx # string's length in CX mov att, %ah # text's colors into AH nxpel: lodsb # fetch next character stosw # store char w/ colors loop nxpel # draw complete string popl %es popl %ds lcall $ring0, $0 # go through call-gate #------------------------------------------------------------------ .align 0x1000 # tables are page-aligned pgtbl: .zero 0x1000 # reserved for page-table pgdir: .zero 0x1000 # also for page-directory #------------------------------------------------------------------ theTSS: .long 0x00000000 # back-link (not used) .long 0x00000000 # reserved for ESP0 .long 0x00000000 # reserved for SS0 .equ limTSS, (.-theTSS)-1 # segment-limit for TSS #------------------------------------------------------------------ .end # nothing more to assemble