//----------------------------------------------------------------- // tryelf32.s // // This example shows how we can execute the program-code in // an Elf32 linkable object-file (named 'hello.o'), assuming // it has been pre-installed on our boot-media at sector 65: // // install using: $ dd if=hello.o of=/dev/sda4 seek=65 // // (Hence its file-image will reside at address 0x00018000.) // The information needed here to create segment descriptors // for this Elf32 file's program code and data was extracted // using our 'elfinfo.cpp' utility, as follows: // // .text: base_address=0x00018034, seg_limit=0x00022 // .data: base_address=0x00018058, seg_limit=0x0000C // // In addition, we created a downward-growing stack-segment. // // to assemble: $ as tryelf32.s -o tryelf32.o // and to link: $ ld tryelf32.o -T ldscript -o tryelf32.b // and install: $ dd if=tryelf32.b of=/dev/sda4 seek=1 // // NOTE: This code begins executing with CS:IP = 1000:0002. // // programmer: ALLAN CRUSE // written on: 12 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 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 # transfer control to 32-bit code at privilege-level 3 pushl $userSS # push image for SS pushl $0 # push image for ESP pushl $userCS # push image for CS pushl $0 # push image for EIP 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 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: #------------------------------------------------------------------