//----------------------------------------------------------------- // usedebug.s (An enhancement of our 'tryelf32.s' demo) // // Here we have replicated the functionality of our earlier // 'tryelf32.s' demo-program, but then we incorporated four // additional code-fragments which together have added // a 'single-step' debugging capability: // // 1) we install a trap-gate for the Single-Step exception // 2) we append exception-handling code for that exception // 3) we now set the TF-bit in EFLAGS upon entering ring3 // 4) we disable any outstanding breakpoints upon exiting // // As a consequence of these additions, we now are able to // single-step through instructions in the '.text' section // of our 'linuxapp.o' linkable ELF application-demo which // we had earlier installed in our CS 630 disk-partition: // // $ dd if=linuxapp.o of=/dev/sda4 seek=65 // // ------------------------------------------------------- // DEBUGGING EXERCISE: When you try this demo program, you // will observe that a General Protection Exception occurs // after a few steps. Can you diagnose its cause and then // eliminate it as an undesired flaw in our demonstration? // ------------------------------------------------------- // // to assemble: $ as usedebug.s -o usedebug.o // and to link: $ ld usedebug.o -T ldscript -o usedebug.b // and install: $ dd if=usedebug.b of=/dev/sda4 seek=1 // // NOTE: This code begins executing with CS:IP = 1000:0002. // // programmer: ALLAN CRUSE // written on: 23 OCT 2008 //----------------------------------------------------------------- .section .text #------------------------------------------------------------------ .word 0xABCD # our program 'signature' #------------------------------------------------------------------ main: .code16 # begins in x86 real-mode mov %sp, %cs:ldrtos+0 # save loader's SP value mov %ss, %cs:ldrtos+2 # save loader's SP value mov %cs, %ax # address program's data mov %ax, %ss # with SS register lea tos, %sp # establish new stacktop call initialize_OS_tables call enter_protected_mode call execute_program_demo call leave_protected_mode lss %cs:ldrtos, %sp # recover loader's stack lret # and exit to the loader #------------------------------------------------------------------ ldrtos: .word 0, 0 # holds the loader's SS:SP #------------------------------------------------------------------ theTSS: .long 0, 0, 0 # 80386 Task-State Segment .equ limTSS, (.-theTSS)-1 # this TSS's segment-limit #------------------------------------------------------------------ #------------------------------------------------------------------ theLDT: .equ userCS, (.-theLDT)+7 # selector for ring3 code .word 0x0023, 0x8034, 0xFA01, 0x0040 # code-descriptor .equ userDS, (.-theLDT)+7 # selector for ring3 data .word 0x0015, 0x8058, 0xF201, 0x0040 # data-descriptor .equ userSS, (.-theLDT)+7 # selector for ring3 stak .word 0x0000, 0x0000, 0xF602, 0x00C0 # down-descriptor .equ limLDT, (.-theLDT)-1 # this LDT's segment-limit #------------------------------------------------------------------ theGDT: .word 0x0000, 0x0000, 0x0000, 0x0000 # null-descriptor .equ sel_bs, (.-theGDT)+0 # selector for BIOS data .word 0x00FF, 0x0400, 0x9200, 0x0000 # data-descriptor .equ sel_cs, (.-theGDT)+0 # selector for 16bit code .word 0xFFFF, 0x0000, 0x9A01, 0x0000 # code-descriptor .equ sel_ds, (.-theGDT)+0 # selector for 16bit data .word 0xFFFF, 0x0000, 0x9201, 0x0000 # data-descriptor .equ sel_es, (.-theGDT)+0 # selector for 16bit data .word 0x0007, 0x8000, 0x920B, 0x0080 # vram-descriptor .equ selTSS, (.-theGDT)+0 # TSS's segment-selector .word limTSS, theTSS, 0x8901, 0x0000 # TSS-descriptor .equ selLDT, (.-theGDT)+0 # LDT's segment-selector .word limLDT, theLDT, 0x8201, 0x0000 # LDT-descriptor .equ privCS, (.-theGDT)+0 # selector for 32bit code .word 0xFFFF, 0x0000, 0x9A01, 0x0040 # code-descriptor .equ privDS, (.-theGDT)+0 # selector for 32bit data .word 0xFFFF, 0x0000, 0x9201, 0x0040 # data-descriptor .equ limGDT, (.-theGDT)-1 # this GDT's segment-limit #------------------------------------------------------------------ theIDT: .space 256 * 8 # for 256 gate-descriptors .equ limIDT, (.-theIDT)-1 # this IDT's segment_limit #------------------------------------------------------------------ regGDT: .word limGDT, theGDT, 0x0001 # image for GDTR register regIDT: .word limIDT, theIDT, 0x0001 # image for IDTR register regIVT: .word 0x03FF, 0x0000, 0x0000 # image for IDTR register #------------------------------------------------------------------ initialize_OS_tables: # initialize IDT descriptor for gate 0x01 mov $0x01, %ebx # ID-number for the gate <-- added lea theIDT(, %ebx, 8), %di # gate's offset-address <-- added movw $isrDBG, %ss:0(%di) # entry-point's loword <-- added movw $privCS, %ss:2(%di) # 32-bit code-selector <-- added movw $0xEF00, %ss:4(%di) # 32-bit trap-gate <-- added movw $0x0000, %ss:6(%di) # entry-point's hiword <-- added # initialize IDT descriptor for gate 0x80 mov $0x80, %ebx # ID-number for the gate lea theIDT(, %ebx, 8), %di # gate's offset-address movw $isrSVC, %ss:0(%di) # entry-point's loword movw $privCS, %ss:2(%di) # 32-bit code-selector movw $0xEF00, %ss:4(%di) # 32-bit trap-gate movw $0x0000, %ss:6(%di) # entry-point's hiword # initialize IDT descriptor for gate 0x0D mov $0x0D, %ebx # ID-number for the gate lea theIDT(, %ebx, 8), %di # gate's offset-address movw $isrGPF, %ss:0(%di) # entry-point's loword movw $privCS, %ss:2(%di) # 32-bit code-selector movw $0x8E00, %ss:4(%di) # 32-bit interrupt-gate movw $0x0000, %ss:6(%di) # entry-point's hiword ret #------------------------------------------------------------------ enter_protected_mode: cli mov %cr0, %eax bts $0, %eax mov %eax, %cr0 lgdt %cs:regGDT lidt %cs:regIDT ljmp $sel_cs, $pm pm: mov $sel_ds, %ax mov %ax, %ss xor %ax, %ax mov %ax, %ds mov %ax, %es mov %ax, %fs mov %ax, %gs ret #------------------------------------------------------------------ tossav: .long 0, 0 # preserves stack-address #------------------------------------------------------------------ execute_program_demo: # save our 16-bit stack-address (for return to 'main') mov %esp, %ss:tossav+0 # save caller's ESP value mov %ss, %ss:tossav+4 # save caller's SS value and $~0x3, %esp # insure dword alignment mov %esp, %ss:theTSS+4 # initialize ESP0 field movl $privDS, %ss:theTSS+8 # initialize SS0 field mov $selTSS, %ax # address Task-State ltr %ax # with TR register mov $selLDT, %ax # address our LDT lldt %ax # with LDTR register # initialize segment-registers DS and ES for ring3 data mov $userDS, %ax mov %ax, %ds mov %ax, %es # initialize general registers xor %eax, %eax xor %ebx, %ebx xor %ecx, %ecx xor %edx, %edx xor %ebp, %ebp xor %esi, %esi xor %edi, %edi # setup our stack for a transiton to 32-bit code in ring3 pushl $userSS # push image for SS pushl $0 # push image for ESP pushl $userCS # push image for CS pushl $0 # push image for EIP # set the TF-bit in FLAGS register just prior to 'lret' pushf # push current FLAGS <-- added btsw $8, (%esp) # set image of TF-bit <-- added popf # pop modified FLAGS <-- added lretl # transfer to ring3 ring0: lss %cs:tossav, %esp # recover saved stacktop ret # for return to 'main' #------------------------------------------------------------------ leave_protected_mode: mov $sel_ds, %ax mov %ax, %ds mov %ax, %es mov %ax, %fs mov %ax, %gs mov %cr0, %eax btr $0, %eax mov %eax, %cr0 ljmp $0x1000, $rm rm: mov %cs, %ax mov %ax, %ss lidt %cs:regIVT sti ret #------------------------------------------------------------------ #================================================================== .code32 # assemble for 32-bit mode #================================================================== #------------------------------------------------------------------ sys_call_table: .long do_nothing # system-call 0 .long do_exit # system-call 1 .long do_nothing # system-call 2 .long do_nothing # system-call 3 .long do_write # system-call 4 .equ NR_SYSTEM_CALLS, (.-sys_call_table)/4 #------------------------------------------------------------------ isrSVC: # Our handler for any SuperVisor Calls via interrupt-0x80 cmp $NR_SYSTEM_CALLS, %eax # is ID-number valid? jb okID # yes, keep ID-number xor %eax, %eax # else wipe ID-number okID: jmp *%cs:sys_call_table(, %eax, 4) # to SVC routine #------------------------------------------------------------------ #------------------------------------------------------------------ do_nothing: # this routine is for any unimplemented system-calls mov $-1, %eax # setup error-code in EAX iret # resume the calling task #------------------------------------------------------------------ do_exit: # here we transfer control back to our USE16 segment # disable any debug 'breakpoints' that may be outstanding xor %eax, %eax # clear general register <-- added mov %eax, %dr7 # and load zero into DR7 <-- added ljmp $sel_cs, $ring0 # back to 16-bit code #------------------------------------------------------------------ do_write: # # EXPECTS: EBX = device ID-number # ECX = offset of message # EDX = length of message # enter $0, $0 pushal push %ds push %es # check for valid device ID-number cmp $1, %ebx je wrok movl $-1, -4(%ebp) jmp wrxx wrok: # fetch and process ascii-codes mov $sel_es, %ax mov %ax, %es cld mov -8(%ebp), %esi # message-offset mov -12(%ebp), %ecx # message-length nxchr: lodsb call write_ascii_tty loop nxchr # return-value in EAX is number of characters written mov -12(%ebp), %eax mov %eax, -4(%ebp) # move CRT cursor to screen-location following message call sync_crt_cursor wrxx: pop %es pop %ds popal leave iret #------------------------------------------------------------------ # equates for control-codes that require special handling .equ ASCII_BACKSPACE, 0x08 .equ ASCII_LINE_FEED, 0x0A .equ ASCII_CARR_RETN, 0x0D #------------------------------------------------------------------ #------------------------------------------------------------------ write_ascii_tty: # writes char from AL to current cursor-location call get_cursor_locn # DH=row, DL=col, EBX=page # certain ASCII control-codes receive special handling cmp $ASCII_CARR_RETN, %al je do_cr cmp $ASCII_LINE_FEED, %al je do_lf cmp $ASCII_BACKSPACE, %al je do_bs # otherwise write character and attribute to the screen call compute_vram_offset mov $0x07, %ah stosw # then adjust the cursor-coordinates (row, col) in (DH,DL) inc %dl cmp $80, %dl jb ttyxx xor %dl, %dl jmp do_lf do_bs: or %dl, %dl jz ttyxx dec %dl call compute_vram_offset mov $0x0720, %ax stosw jmp ttyxx do_cr: xor %dl, %dl jmp ttyxx do_lf: inc %dh cmp $25, %dh jb ttyxx dec %dh call scroll_vpage_up ttyxx: call set_cursor_locn ret #------------------------------------------------------------------ get_cursor_locn: # returns cursor-locn in (DH,DL), page in EBX push %eax push %ds mov $sel_bs, %ax mov %ax, %ds mov (0x62), %bl movzx %bl, %ebx mov 0x50(,%ebx,2), %dx pop %ds pop %eax ret #------------------------------------------------------------------ set_cursor_locn: # sets cursor's (row,col) from (DH,DL) push %eax push %ds mov $sel_bs, %ax mov %ax, %ds mov (0x62), %bl movzx %bl, %ebx mov %dx, 0x50(,%ebx,2) pop %ds pop %eax ret #------------------------------------------------------------------ sync_crt_cursor: push %eax push %ebx push %edx push %ds mov $sel_bs, %ax mov %ax, %ds mov (0x62), %bl # current page movzx %bl, %ebx mov 0x50(,%ebx,2), %dx # current page's cursor # update hardware cursor imul $2048, %bx mov $80, %al mul %dh add %dl, %al adc $0, %ah add %ax, %bx mov $0x03D4, %dx mov $0x0E, %al mov %bh, %ah out %ax, %dx mov $0x0F, %al mov %bl, %ah out %ax, %dx pop %ds pop %edx pop %ebx pop %eax ret #------------------------------------------------------------------ scroll_vpage_up: # expects video page-number in EBX pushal push %ds push %es # copy rows 1 through 24 onto rows 0 through 23 mov $sel_es, %ax mov %ax, %ds mov %ax, %es cld imul $4096, %ebx, %edi lea 160(%edi), %esi mov $1920, %ecx rep movsw # then erase row 24 (by filling it with blank characters) mov $0x0720, %ax mov $80, %ecx rep stosw pop %es pop %ds popal ret #------------------------------------------------------------------ compute_vram_offset: push %ebx imul $4096, %ebx, %edi movzx %dh, %ebx imul $160, %ebx add %ebx, %edi movzx %dl, %ebx imul $2, %ebx add %ebx, %edi pop %ebx ret #------------------------------------------------------------------ # During debugging, we added the following code (which we adapted # from our earlier 'whycrash.s' demo-program, and we found it was # very helpful, so we've kept it, even though it likely won't get # executed in the absence of protected-mode programming 'bugs'. #------------------------------------------------------------------ field: .ascii " GS= FS= ES= DS=EDI=ESI=EBP=ESP=" .ascii "EBX=EDX=ECX=EAX=err=EIP= CS=EFL=" .ascii "ESP= SS=" .equ N_ELTS, (.-field)/4 # number of stack elements #------------------------------------------------------------------ hex: .ascii "0123456789ABCDEF" # list of the hex numerals buf: .ascii " nnn=xxxxxxxx " # buffer for output string len: .int . - buf # length for output string att: .byte 0x70 # colors: black upon white #------------------------------------------------------------------ draw_stack_element: # the element-number is found in register EDX mov field(,%edx,4), %eax # get the element's name mov %eax, buf+1 # put name into buffer mov (%ebp, %edx, 4), %eax # get the field's value lea buf+5, %edi # point DS:EDI to field call eax2hex # convert value to string mov $22, %eax # bottom-item line-number sub %edx, %eax # minus element's number imul $160, %eax, %edi # times size of screenrow sub $30, %edi # minus right-hand indent cld # do forward processing lea buf, %esi # point DS:ESI to buffer mov att, %ah # setup color-code in AH mov len, %ecx # setup character-count nxpel: lodsb # fetch next character stosw # store char and color loop nxpel # again if chars remain ret # back to the caller #------------------------------------------------------------------ eax2hex: # converts value in EAX to hexadecimal 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 # Exception-handler for General Protection Faults pushal # push general registers pushl %ds # push DS (as longword) pushl %es # push ES (as longword) pushl %fs # push FS (as longword) pushl %gs # push GS (as longword) mov $sel_es, %ax mov %ax, %es mov $sel_ds, %ax mov %ax, %ds # the following loop draws each stack-element onscreen mov %esp, %ebp xor %edx, %edx nxelt: call draw_stack_element inc %edx cmp $N_ELTS, %edx jb nxelt ljmp $sel_cs, $ring0 # bail out of this demo #------------------------------------------------------------------ .align 16 .space 512 tos: #================================================================== #=========== TRAP-HANDLER FOR SINGLE-STEP EXCEPTIONS ============ #================================================================== name: .ascii "DR7=DR6=DR3=DR2=DR1=DR0=" .ascii " GS= FS= ES= DS=" .ascii "EDI=ESI=EBP=ESP=EBX=EDX=ECX=EAX=" .ascii "EIP= CS=EFL=ESP= SS=" .equ RNUM, ( . - name )/4 # number of array entries info: .ascii " nnn=xxxxxxxx " # buffer for hex display .equ ILEN, . - info # length of outbup buffer color: .byte 0x50 # fg=black, bg=magenta #----------------------------------------------------------------- isrDBG: .code32 pushal # push general registers mov %esp, %ebp # setup stackframe access pushl $0 # push doubleword zero mov %ds, (%esp) # store DS in low word pushl $0 # push doubleword zero mov %es, (%esp) # store ES in low word pushl $0 # push doubleword zero mov %fs, (%esp) # store FS in low word pushl $0 # push doubleword zero mov %gs, (%esp) # store GS in low word # ok, let's display the Debug Registers, too mov %dr0, %eax push %eax # push value from DR0 mov %dr1, %eax push %eax # push value from DR1 mov %dr2, %eax push %eax # push value from DR2 mov %dr3, %eax push %eax # push value from DR3 mov %dr6, %eax push %eax # push value from DR6 mov %dr7, %eax push %eax # push value from DR7 push %ds # preseve task's selectors push %es # found in DS and ES # examine the Debug Status Register DR6 mov %dr6, %eax # examine register DR6 test $0x0000000F, %eax # any breakpoints? jz nobpt # no, keep RF-flag btsl $16, 40(%ebp) # else set RF-flag nobpt: # examine instruction at saved CS:EIP address lds 0x20(%ebp), %esi # point DS:ESI to retn-addr mov (%esi), %eax # fetch next opcode-bytes # set breakpoint trap after any 'int-nn' instruction cmp $0xCD, %al # opcode is 'int-nn'? jne nobrk # no, don't set breakpoint add $2, %esi # else point past 'int-nn' #----------------------------------------------------- # compute linear-address of the instruction at DS:ESI #----------------------------------------------------- # Step 1: Pick the selector's descriptor-table lea theGDT, %ebx # EBX = offset for GDT mov %ds, %ecx # copy selector to ECX bt $2, %ecx # is TI-bit clear? jnc useEBX # yes, lookup in GDT lea theLDT, %ebx # else lookup in LFT useEBX: # Step 2: Extract the descriptor's base-address and $0xFFF8, %ecx # isolate selector-index mov %cs:0(%ebx, %ecx), %eax # descriptor[31..0] mov %cs:4(%ebx, %ecx), %al # descriptor[39..32] mov %cs:7(%ebx, %ecx), %ah # descriptor[63..54] rol $16, %eax # segment's base-address # Step 3: Setup the instruction-breakpoint in DR0 add %eax, %esi # add segbase to offset mov %esi, %dr0 # breakpoint into DR0 # Step 4: Activate the code-breakpoint in register DR0 mov %dr7, %eax # get current DR7 settings and $0xFFF0FFFC, %eax # clear the G0 and L0 bits or $0x00000001, %eax # enable L0 code-breakpoint mov %eax, %dr7 # update settings in DR7 nobrk: # ok, here we display our stack-frame (with labels) mov $sel_es, %ax # address video memory mov %ax, %es # with ES register mov $sel_ds, %ax # address this segment mov %ax, %ds # with DS register cld # do forward processing xor %ebx, %ebx # counter starts from 0 nxreg: # store next field-label in the info-buffer mov name(, %ebx, 4), %eax # fetch register label mov %eax, info+1 # store register label # store next field-value in the info-buffer lea info+5, %edi # point to output field mov -40(%ebp, %ebx, 4), %eax # fetch register value call eax2hex # store value as hex # compute screen-location for this element mov $23, %eax # bottom item line-number sub %ebx, %eax # minus the item's number imul $160, %eax, %edi # times screen-row's size add $100, %edi # and indent to column 50 # transfer info-buffer onto the screen lea info, %esi # point to info buffer mov color, %ah # setup color attribute mov $ILEN, %ecx # setup message length nxchx: lodsb # fetch message character stosw # store char and color loop nxchx # transfer entire string inc %ebx # increment the iterator cmp $RNUM, %ebx # maximum value reached? jl nxreg # no, show next register # now await the release of a user's keypress kbwait: in $0x64, %al # poll keyboard status test $0x01, %al # new scancode ready? jz kbwait # no, continue polling in $0x60, %al # input the new scancode test $0x80, %al # was a key released? jz kbwait # no, wait for a release # restore the suspended task's registers, and resume pop %es # restore saved selectors pop %ds # for registers DS and ES mov %ebp, %esp # discard other stack data popal # restore saved registers iret # resume interrupted work #----------------------------------------------------------------- .end