//-----------------------------------------------------------------
//	whycrash.s
//
//	This program creates an exception-handler for any General
//	Protection Exceptions (Interrupt-0x0D) which will display
//	some diagnostic information (to aid us in determining the
//	cause of a system 'crash' that occurs in protected-mode).
//
//	  to assemble: $ as whycrash.s -o whycrash.o
//	  and to link: $ ld whycrash.o -T ldscript -o whycrash.b
//	  and install: $ dd if=whycrash.b of=/dev/sda4 seek=1
//
//	NOTE: This code begins ecxecuting with CS:IP = 1000:0002.
//
//	programmer: ALLAN CRUSE
//	written on: 22 SEP 2008
//-----------------------------------------------------------------



	.section	.text
#------------------------------------------------------------------
	.word	0xABCD			# programming 'signature'
#------------------------------------------------------------------
main:	.code16				# starts 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's data
	mov	%ax, %ss		#   using SS register
	lea	tos, %sp		# establish new stacktop

	call	build_interrupt_gate
	call	enter_protected_mode
	call	execute_fault13_demo
	call	leave_protected_mode

	lss	%cs:ipltos, %sp		# recover loader's SS:SP
	lret				# and exit to the loader
#------------------------------------------------------------------
ipltos:	.word	0, 0			# for saving stack-address
#------------------------------------------------------------------
theGDT:	.quad	0x0000000000000000	# required null-descriptor
	
	.equ	sel_cs, .-theGDT	# selector for code-segment
	.quad	0x00009A010000FFFF	# code-segment's descriptor

	.equ	sel_ds, .-theGDT	# selector for data-segment
	.quad	0x000092010000FFFF	# data-segment's descriptor

	.equ	sel_es, .-theGDT	# selector for vram-segment
	.quad	0x0000920B80007FFF	# vram-segment's descriptor

	.equ	limGDT, (.-theGDT)-1	# segment-limit of our GDT
#------------------------------------------------------------------
#------------------------------------------------------------------
theIDT:	.zero	256*8			# for 256 gate-descriptors
	.equ	limIDT, (.-theIDT)-1	# segment-limit of our IDT
#------------------------------------------------------------------
build_interrupt_gate:
	
	# setup gate-descriptor for General Protection Exceptions

	mov	$0x0D, %ebx		# gate-number into EBX
	lea	theIDT(,%ebx,8), %di	# point DS:DI to entry

	movw	$isrGPF, %ss:0(%di)	# loword of entry-point 
	movw	$sel_cs, %ss:2(%di)	# code-segment selector
	movw	$0x8E00, %ss:4(%di)	# gate-type=0xE (32-bit)
	movw	$0x0000, %ss:6(%di)	# hiword of entry-point

	ret
#------------------------------------------------------------------
enter_protected_mode:

	cli				# no device interrupts

	mov	%cr0, %eax		# current machine status
	bts	$0, %eax		# set image of PE-bit 
	mov	%eax, %cr0		# turn on protection
	
	lgdt	%cs:regGDT		# establish the GDT
	lidt	%cs:regIDT		# establish the IDT

	mov	$sel_ds, %ax		# address program stack
	mov	%ax, %ss		#   using SS register
	ljmp	$sel_cs, $pm		# also reload CS and IP
pm:
	ret				# back to main procedure
#------------------------------------------------------------------
leave_protected_mode:
	
	mov	$sel_ds, %ax		# real-mode limit/rights
	mov	%ax, %ds		#   into DS register
	mov	%ax, %es		#   also ES register
	mov	%ax, %fs		#   also FS register
	mov	%ax, %gs		#   also GS register

	mov	%cr0, %eax		# get machine status
	btr	$0, %eax		# reset PE-bit image
	mov	%eax, %cr0		# turn off protection

	ljmp	$0x1000, $rm		# must reload CS and IP
rm:	mov	%cs, %ax		# address program stack
	mov	%ax, %ss		#   with real-mode SS 
	
	lidt	%cs:regIVT		# restore real-mode IVT
	sti				# device interrupts ok

	ret				# back to main procedure
#------------------------------------------------------------------
#------------------------------------------------------------------
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
#------------------------------------------------------------------
execute_fault13_demo:

	# preserve the stack-address upon entry to this procedure
	# (so our fault-handler will be able to return to 'main')

	mov	%sp, %ss:tossav+0	# save SP register-value
	mov	%ss, %ss:tossav+2	# save SS register-value
	
	# Next we will try executing an impermissible instruction
	# in order to trigger a processor 'fault', and thus enter
	# our fault-handler for any General Protection Exception

	int	$0x10		#<---- no gate exists for this ID

	ret			#<---- Note: this is 'dead' code!
#------------------------------------------------------------------
tossav:	.word	0, 0			# to hold ring0's stacktop  
#------------------------------------------------------------------
#==================================================================
#------------------------------------------------------------------
isrGPF:  # Interrupt Service Routine 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)

	# setup DS and ES to address our data and video memory  

	mov	$sel_es, %ax		# address video memory
	mov	%ax, %es		#   with ES register

	mov	$sel_ds, %ax		# address program data 
	mov	%ax, %ds		#   with DS register

	# the following loop draws each stack-element onscreen

	mov	%esp, %ebp		# copy stacktop to EBP
	xor	%edx, %edx		# start element-count
nxelt:	call	draw_stack_element	# draw current element
	inc	%edx			# increment the count
	cmp	$ELTS, %edx		# all elements shown?
	jb	nxelt			# no, show another one

	lss	%cs:tossav, %sp		# load saved stack-address
	ret				#  for a return to 'main' 
#------------------------------------------------------------------
#==================================================================
#------------------------------------------------------------------
#------------------------------------------------------------------
field:	.ascii	" GS= FS= ES= DS=EDI=ESI=EBP=ESP="
	.ascii	"EBX=EDX=ECX=EAX=err=EIP= CS=EFL="
	.equ	ELTS, ( . - field )/4	# number of stack elements
#------------------------------------------------------------------
hex:	.ascii	"0123456789ABCDEF"	# list of the hex numerals
buf:	.ascii	" nnn=xxxxxxxx "	# buffer for output-string
len:	.word	. - buf			# length for output-string
hue:	.byte	0x70			# colors: black-upon-white
#------------------------------------------------------------------
draw_stack_element:  # the element-number is found in regster EDX

	mov	field(,%edx,4), %eax	# get the field's name 
	mov	%eax, buf+1		# put name into buffer

	mov	(%ebp,%edx,4), %eax	# get the field's value
	lea	buf+5, %di		# point DS:DI into buffer 
	call	eax2hex			# convert value to string

	mov	$22, %ax		# bottom-item line-number 
	sub	%dx, %ax		# minus count of elements
	imul	$160, %ax, %di		# times size of screenrow  
	sub	$30, %di		# minus right-hand indent 

	cld				# do forward processing
	lea	buf, %si		# point DS:SI to source
	mov	hue, %ah		# setup color-code in AH
	mov	len, %cx		# setup character-count
nxchr:	
	lodsb				# fetch next character
	stosw				# store char and color
	loop	nxchr			# again if other chars

	ret				# back to the caller
#------------------------------------------------------------------
eax2hex:  # converts value in EAX to hexadecimal string at DS:DI
	pushal				# preserve registers

	mov	$8, %cx			# number of nybbles
nxnyb:	rol	$4, %eax		# next nybble into AL
	mov	%al, %bl		# copy nybble into BL
	and	$0xF, %bx		# isolate nybble bits
	mov	hex(%bx), %dl		# lookup nybble digit
	mov	%dl, (%di)		# put digit in buffer
	inc	%di			# advance buffer-index
	loop	nxnyb			# again if more nybbles 

	popal				# restore registers
	ret				# back to the caller
#------------------------------------------------------------------
	.align	16			# assures stack alignment
	.space	512, 0xFF		# initialize for this demo
tos:					# label for 'top-of-stack' 
#------------------------------------------------------------------
	.end				# nothing else to assemble