[syslinux] [PATCH 1/1] v2 Add Diagnostic MBR for trouble-shooting

TJ ubuntu at tjworld.net
Mon Mar 30 05:28:32 PDT 2009


---
 mbr/Makefile   |    6 +-
 mbr/mbr-diag.S |  372 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 377 insertions(+), 1 deletions(-)
 create mode 100644 mbr/mbr-diag.S

diff --git a/mbr/Makefile b/mbr/Makefile
index 0bdf7e3..b9d743d 100644
--- a/mbr/Makefile
+++ b/mbr/Makefile
@@ -17,7 +17,7 @@
 topdir = ..
 include $(topdir)/MCONFIG.embedded
 
-all:	mbr.bin gptmbr.bin isohdpfx.bin
+all:	mbr.bin gptmbr.bin isohdpfx.bin mbr-diag.bin
 
 .PRECIOUS: %.o
 %.o: %.S
@@ -31,6 +31,10 @@ mbr.bin: mbr.elf checksize.pl
 	$(OBJCOPY) -O binary $< $@
 	$(PERL) checksize.pl $@ 440
 
+mbr-diag.bin: mbr-diag.elf checksize.pl
+	$(OBJCOPY) -O binary $< $@
+	$(PERL) checksize.pl mbr-diag.bin 440
+
 isohdpfx.bin: isohdpfx.elf checksize.pl
 	$(OBJCOPY) -O binary $< $@
 	$(PERL) checksize.pl $@ 432
diff --git a/mbr/mbr-diag.S b/mbr/mbr-diag.S
new file mode 100644
index 0000000..c428d28
--- /dev/null
+++ b/mbr/mbr-diag.S
@@ -0,0 +1,372 @@
+/*
+ Diagnostic Master Boot Record
+ Copyright 2009 TJ <linux at tjworld.net> <ubuntu at tjworld.net>
+ Licensed on the terms of the GNU General Public License, version 2
+
+ This is a diagnsotic master boot record (MBR) with the sole purpose of 
+ reporting the BIOS values received at boot-time for addressing mode, drive-number,
+ device geometry, active partition and magic bytes of active partition boot sector.
+
+ It is an aid to determining why some devices fail to boot correctly when
+ using the 'default' MBR.
+
+ NOTE: If a shift key is held down at boot, CHS addressing mode is forced
+
+ It is also an educational tool and commented in a way which should increase
+ a user's understanding of both assembly programming and the boot process.
+
+ The jump-off web page for the GNU documentation is http://www.gnu.org/software/binutils/
+ The current (binutils 2.19.1) GNU 'as' documentation is at: http://sourceware.org/binutils/docs-2.19/as/index.html
+
+ In comments 'doc:' refers to the binutils 'as' documentation section.
+
+ This code uses the 'AT & T' form of instructions: op [[source] [destination]] doc: 9.13.3
+ Ops explicitly use the size suffix to prevent GNU 'as' creating 32-bit op-codes. doc: 9.13.12
+
+ Due to the severe space constraints the output uses 1-character description codes to prefix each printed value.
+ Hex values are suffixed with 'h' (saves 1 byte against using 0x).
+
+ Description Codes:
+	L | C  				LBA or CHS addressing mode
+	D drive number		BIOS-reported drive number
+	C cylinders			Geometry of drive according to BIOS
+	H heads
+	S sectors
+	P partition			active partition number (first partition flagged active). '?' if no active partition
+	O offset			absolute sector offset of active partition . '????????' if no active partition
+	M magic				magic bytes of active partition boot sector (sector <offset> as read by BIOS).
+						'????' if no active partition. Value is reset to 0xDEAD before the sector is read
+						to avoid inheriting the MBR magic on error
+	E error				error code returned by BIOS 'read sector' interrupt (0x02 or 0x42, int 0x13).
+						'??' if no active partition.
+
+ Examples:
+	L D80h C3D9h HFFh S3Fh P1 O00000020h MAA55h E00h
+	C D80h C3D9h HFFh S3Fh P1 O00000020h M0000h E04h
+
+ Usage:
+	# set device to write diagnostic MBR to
+	DEV=/dev/sdc
+	# back-up existing MBR
+	sudo dd if=$DEV of=mbr-backup.bin bs=440 count=1
+	# write diagnostic MBR to device
+	sudo dd if=mbr-diag.bin of=$DEV
+	# example: test in a virtual machine (uses sudo because it is accessing raw device)
+	sudo kvm -m 128 -hda $DEV
+	# restore original MBR
+	sudo dd if=mbr-backup.bin of=$DEV
+
+*/
+
+	.code16					/* generate 16-bit code. doc: 9.13.12 */
+	.text					/* sub-section (default 0). doc: 7.109 */
+
+video_page 		= 0x462			/* I/O port for video controller page selection (bitmask 0x07) */
+flag_active		= 0x80			/* partition table entry active (bootable) flag */
+
+stack 			= 0x7C00		/* top of stack is immediately below where the BIOS loaded this MBR */
+pnp_header		= (stack-4)		/* The Plug'n'Play header received from BIOS in ES:DI */
+drive_number	= (stack-6) 	/* The boot device as reported by BIOS in DL */
+cylinders		= (stack-8)
+heads			= (stack-10)
+sectors			= (stack-12)
+sectors_cylinder= (stack-16)
+
+	.section ".bootsec", "a", @nobits 	/* assemble into; ELF: allocatable, no data. doc: 7.95 */
+	.global bootsec			/* externally visible. doc: 7.56 */
+bootsec:
+	.space 512				/* set 512 bytes to zero. doc: 7.102 */
+
+	.text
+	.global _start
+_start:
+
+	/* All code from here until the relocation operation does nothing that relies */
+	/* on the value of the instruction pointer */
+	cli						/* disable interrupts */
+	xorw	%ax, %ax		/* reset to 0 */
+	movw	%ax, %ds		/* reset data segment base */
+	movw	%ax, %ss		/* reset stack segment base */
+	movw	$stack, %sp		/* set stack pointer */
+	movw	%sp, %si		/* address where BIOS loaded MBR, used as source address for relocation */
+	pushw	%es				/* save Plug'n'Play header provided by BIOS in ES:DI */
+	pushw	%di
+	pushw	%dx				/* save drive number provided by BIOS in DL */
+	movw	%ax, %es		/* reset extra segment base */
+	sti						/* enable interrupts */
+	cld						/* increment direction for relocation operation */
+relocate:
+	movw	$_start, %di	/* destination address for relocation */
+	movw	$(512/2), %cx	/* number of words to relocate */
+	rep; movsw				/* repeat the move CX times, incrementing SI and DI after each move */
+
+	ljmpw	$0, $get_shift_keys		/* far jump to ensure CS:IP are set correctly to 0000:0600+get_shift_keys */
+							/* rather than the current CS:IP which will be based on 0x7C00 */
+
+get_shift_keys:				/* Provide a way for the user to force CHS addressing mode by pressing either Shift key */
+	movb	$0x02, %ah		/* function: get keyboard shift flags */
+	int		$0x16			/* read keys; shift flags returned in AL */
+	testb	$0x03, %al		/* left or right shift-keys pressed? */
+	jnz		use_chs_addressing		/* if so, skip detection of BIOS type */
+
+detect_bios_type:
+							/* try to use the BIOS extension for LBA (Logical Block Addressing). */
+	movb	(drive_number), %dl
+	movw	$0x55aa, %bx	/* required */
+	movb	$0x41, %ah		/* query presence of extended BIOS functions (EBIOS) */
+	int		$0x13			/* call BIOS */
+	jc		use_chs_addressing		/* carry flag will be set on return if extensions are NOT supported */
+	cmpw	$0xaa55, %bx	/* bytes returned swapped if successful */
+	jne		use_chs_addressing
+	shrw	%cx				/* Shift right once into carry flag. Bit 0: extended (LBA) disk functions available */
+	jnc		use_chs_addressing
+	jmp		use_lba_addressing
+
+use_chs_addressing:
+	movb	$0x02, read_op	/* set the BIOS function to use when reading CHS sectors */
+	movb	$'C', (msg_address_mode)	/* over-write 'L' with 'C' in boot message */
+
+use_lba_addressing:
+	/* convert drive-number to two hex characters and insert them into the boot message */
+	movw	(drive_number), %dx
+	pushw	%dx				/* preserve for use by calculate_geometry */
+	movw	$msg_drive_number, %di
+	movb	$0x02, %cl		/* write two nibbles */
+	call 	write_hex_value
+
+calculate_geometry:
+	popw	%dx				/* retrieve drive number */
+	movb	$0x08, %ah
+	int		$0x13			/* get drive parameters; returns CH LSB of max cyl., CL(7-6) MSb max cyl., */
+							/* CL(5-0) max sector, DH max head, DL drive qty, ES:DI floppy parameter table */
+	movb	%ch, %bl		/* transfer maximum cylinder number */
+	movb	%cl, %bh
+	shrb	$6, %bh			/* move bits into correct positions */
+	pushw	%bx				/* preserve maximum cylinder number */
+	incb	%dh				/* convert zero-based maximum to quantity */
+	movzbw	%dh, %ax		/* DH = maximum head number */
+	pushw	%ax				/* preserve maximum head number */
+	andw	$0x3f, %cx		/* mask to enforce maximum sector number 63 */
+	pushw	%cx				/* preserve sector count */
+	incw	%ax				/* convert zero-based maximum head number to quantity */
+	mulw	%cx				/* sectors per cylinder = heads*sectors */
+	pushw	%dx				/* preserve sectors per cylinder DX:AX */
+	pushw	%ax
+
+msg_format_cylinders:
+	movw	$msg_cylinders, %di
+	movw	(cylinders), %dx
+	movb	$0x03, %cl		/* write three nibbles */
+	call 	write_hex_value
+
+msg_format_heads:
+	movw	$msg_heads, %di
+	movw	(heads), %dx
+	movb	$0x02, %cl		/* write two nibbles */
+	push	%cx				/* preserve for sectors */
+	call	write_hex_value
+
+msg_format_sectors:
+	movw	$msg_sectors, %di
+	movw	(sectors), %dx
+	popw	%cx				/* write two nibbles */
+	call	write_hex_value
+
+partition_table_scan:
+	movw $partition_table, %si
+	movw	$0x04, %cx		/* number of table entries */
+
+partition_entry_next:
+	testb	$flag_active, (%si)	/* is active (bootable) flag set? */
+	jnz		msg_partition_active
+
+	addw	$16, %si			/* next entry */
+	loopw	partition_entry_next
+
+	jmp		print_boot_message	/*	no active parition, so don't try to read a sector */
+
+msg_partition_active:
+	movb	$0x05, %ch		/* convert CL countdown to partition number */
+	subb	%cl, %ch		/*  CL:partition translations 4:1, 3:2, 2:3, 1:4  */
+	movb	%ch, %dl		/* value to write is partition number [1-4] */
+	movw 	$msg_partition, %di
+	movb	$0x01, %cl		/* one nibble */
+	call	write_hex_value
+
+partition_get_offset:
+	movl	8(%si), %edx	/* absolute starting sector (DWORD: partition_table[8]) */
+	pushl 	%edx			/* preserve for use in partition_read_sector */
+	rorl	$16, %edx		/* swap low and high WORDs - high WORD needed in DX first */
+	movw 	$msg_partition_offset_high, %di		/* high WORD of DWORD */
+	movb	$0x04, %cl		/* four nibbles */
+	pushw	%cx				/* use again for low WORD */
+	call	write_hex_value
+
+	/* at this point DI should be pointing to first character of low WORD in the string */
+	shrl	$16, %edx		/* shift high WORD into low WORD */
+	popw	%cx				/* use again for low WORD */
+	call	write_hex_value
+
+partition_read_sector:
+	movw	$0xDEAD, %cx	/* over-write current 'magic' indicator in sector buffer */
+							/* this ensures that if read_sector fails the MBR magic isn't still there */
+	movw	%cx, (bootsec + 510)
+	popl	%eax			/* retrieve boot sector of active partition into buffer */
+	call	read_sector
+	pushw	%ax				/* preserve read error code (0 = success) */
+
+msg_partition_magic:
+	movw	$msg_boot_sector_magic, %di
+	movb	$0x04, %cl					/* four nibbles */
+	movw	(bootsec + 510), %dx		/* get last two bytes of sector */
+	call	write_hex_value
+
+msg_read_error_code:
+	movw	$msg_read_error, %di
+	movb	$0x02, %cl		/* two nibbles */
+	popw	%dx				/* retrieve read error code */
+	shrw	$8, %dx			/* error code in high byte needs to be in low byte for writing */
+	call	write_hex_value
+
+print_boot_message:
+	mov		$msg_boot, %si
+	call	print			/* print the boot message */
+
+stop:
+	hlt						/* stop nicely; don't burn the CPU up */
+	jmp		stop
+
+/*
+ * read_sector: read a single sector pointed to by %eax to 0x7c00.
+ * CF is set on error.  All registers saved.
+ */
+read_sector:
+	xorl	%edx, %edx		/* reset to 0 */
+							/* create a disk address packet structure on the stack */
+	pushl	%edx			/* most significant double-word of LBA (upper part of 48-bit LBAs) */
+	pushl	%eax			/* least significant double-word of LBA */
+	pushw	%es				/* buffer segment */
+	movw	$bootsec, %bx	/* address of buffer that will receive the sector (should be 0x7C00) */
+	pushw	%bx				/* buffer offset */
+	pushw	$1				/* sector count always 1 */
+	pushw	$16				/* size of disk address packet */
+	movw	%sp, %si		/* address of disk address packet (on stack) */
+
+	testb $0x42, read_op	/* using LBa addressing? */
+	jz read_common			/* Use LBA extended addressing */
+
+read_sector_chs:
+	divl	(sectors_cylinder)	/* LBA in DX:AX inherited from above */
+	shlb	$6, %ah			/* quotient is the cylinder number */
+	movb	%ah, %cl		/* cylinder high bits (7-6) */
+	movb	%al, %ch		/* cylinder low bits (7-0) */
+	xchgw	%dx, %ax		/* remainder is the sector-offset into the cylinder */
+	divb	(sectors)
+	movb	%al, %dh		/* head number */
+	orb		%ah, %cl		/* sector offset ORed into CL since CL(7-6) already contain two high bits of cylinder number */
+	incw	%cx				/* sector counts start at 1 not 0 */
+
+read_common:
+read_op = .+2				/* address of operand to be moved into AH (0x42 in the following instruction) */
+	movw	$0x4201, %ax	/* default is 0x42 (LBA) but may be over-written with 0x02 (CHS). AL = number of sectors */
+	movb	(drive_number), %dl
+	int		$0x13			/* read disk */
+
+	addw	$16, %sp		/* discard disk address packet */
+	ret
+
+/* write value as hex.
+   writes CL (max 4) hex digits representing the value of DX starting at DI
+   The choice of nibbles is right-aligned so the least-significant will always be processed.
+*/
+write_hex_value:
+	xorb	%ch, %ch		/* reset to 0 to ensure CX loop uses only CL value passed in */
+
+write_hex_next:
+	pushw	%dx				/* preserve value */
+	cmpb	$0x02, %cl		/* is this the high byte? */
+	jle		write_hex_byte
+	movb	%dh, %dl		/* move high-byte into low byte for processing */
+
+write_hex_byte:
+	btw		$0x00, %cx		/* low nibble? (CX = 4,2: high nibble. CX = 3,1: low nibble) */
+	jc		write_hex_nibble
+
+write_hex_high_nibble:
+	shrb	$4, %dl			/* high nibble required; shift into low nibble */
+
+write_hex_nibble:
+	andb	$0x0F, %dl		/* mask to retain only the low nibble */
+	call	hex_ascii_format
+	movb	%dl, (%di)		/* over-write character in string */
+	incw	%di				/* next character */
+	pop		%dx				/* retrieve value for next iteration */
+	loopw write_hex_next
+
+	ret
+
+/* low nibble of DL contains value to be converted. returns ASCII code in DL */
+hex_ascii_format:
+	addb	$'0', %dl		/* offset into ASCII table; value 0 == ASCII "0" */
+	cmpb	$'9', %dl
+	jle		nibble_set		/* value is 0-9; no need to adjust */
+	addb	$('A' - ':'), %dl			/* adjust 0x0a-0x0f to map to ASCII characters 'A' to 'F' */
+nibble_set:
+	ret
+
+/* print a zero-terminated string of characters whose address is in SI */
+print:
+	cld						/* move left-to-right in string */
+	movb	(video_page), %bh	/* get current page from video controller's memory-mapped I/O register */
+	movb	$0x07, %bl		/* colour attribute */
+	movb	$0x0e, %ah		/* BIOS function: display char */
+
+print_next:
+	lodsb					/* load a character into AL */
+	orb		%al, %al		/* is it the string zero terminator? */
+	jz		print_end		/* finished */
+
+	call	print_char
+	jmp		print_next		/* next character */
+
+print_char:
+	int		$0x10			/* display */
+	ret
+
+print_end:
+	ret
+
+msg_boot:
+	.ascii	"DIAG "
+msg_address_mode:
+	.ascii "L"			/* These entries make up ONE zero-terminated string */
+	.ascii	" D"
+msg_drive_number:
+	.ascii	"??h"			/* ?? will be over-written with drive number */
+	.ascii 	" C"
+msg_cylinders:
+	.ascii	"???h"
+	.ascii	" H"
+msg_heads:
+	.ascii	"??h"
+	.ascii	" S"
+msg_sectors:
+	.ascii	"??h"
+	.ascii " P"
+msg_partition:
+	.ascii	"?"
+	.ascii	" O"
+msg_partition_offset_high:
+	.ascii	"????"
+msg_partition_offset_low:
+	.ascii	"????h"
+	.ascii	" M"
+msg_boot_sector_magic:
+	.ascii	"????h"
+	.ascii	" E"
+msg_read_error:
+	.ascii	"??h"
+	.asciz	"\r\n"
+
+partition_table = _start + 446	/* location of partition table */
+
-- 
1.6.0.4




More information about the Syslinux mailing list