[mbr] add LBA sector readout, as required by some BIOSes

* I'm looking at you, Dell Optiplex!
* also fix boot in case no secondary bootable drive exists
* also adds a readme
This commit is contained in:
Pete Batard 2012-03-20 20:31:54 +00:00
parent 0f57ed1684
commit 1b7f88eb99
4 changed files with 159 additions and 94 deletions

View File

@ -1,7 +1,7 @@
/********************************************************************************/
/* Rufus - The Reliable USB Formatting Utility, bootable USB MBR */
/* */
/* Copyright (c) 2011 Pete Batard <pete@akeo.ie> */
/* Copyright (c) 2012 Pete Batard <pete@akeo.ie> */
/* */
/* This program is free software; you can redistribute it and/or modify it */
/* under the terms of the GNU General Public License as published by the Free */
@ -26,6 +26,7 @@
.code16 # MBR code is executed in x86 CPU real/16 bit mode
/********************************************************************************/
/********************************************************************************/
/* Constants: */
/********************************************************************************/
@ -34,14 +35,14 @@ DOT_NUMBER = 0x04 # Number of dots to be printed before timeout
MBR_ADDR = 0x7c00 # Same address for both original MBR and copy
MBR_SIZE = 0x200
MBR_RESERVED = 0x1b8 # Start of the reserved section (partition table, etc.)
BUF_SEGMENT = 0x3000 # Segment for the buffer
PT_MAX = 0x04 # Number of partition entries in the partition table
PT_ENTRY_SIZE = 0x10 # Size of a partition entry in the partition table
INT_RTC = 0x08
INT_DSK = 0x13
/********************************************************************************/
/* mbr: this section must reside at 0x00007c00, and be exactly 512 bytes */
/* MBR: this section must reside at 0x00007c00, and be exactly 512 bytes */
/********************************************************************************/
.section main, "ax"
.globl mbr # label must be declared global for the linker
@ -65,9 +66,9 @@ mbr:
push es
mov ds, ax # Original MBR is in segment 0
mov bx, 0x0413 # McAfee thinks we are a virus if we use 413 directly...
mov ax,[bx+0]
mov ax,[bx]
dec ax # Remove 1KB from the RAM for our copy
mov [bx+0],ax
mov [bx],ax
shl ax, 6 # Convert to segment address
sub ax, MBR_ADDR>>4 # For convenience of disassembly, so that irrespective
mov es, ax # of the segment, our base address remains MBR_ADDR
@ -79,60 +80,33 @@ mbr:
# ---------------------------------------------------------------------------
# From this point forward, we are running the copy at a different segment from seg 0
# From this point forward, we are running the copy at the same base but different segment
0: mov ds, ax # AX = ES = CS, only DS points back to old seg => fix this
push es
xor ax, ax
mov es, ax
mov bx, MBR_ADDR # TODO optimize
mov ax, 0x0201
mov cx, 0x0001
mov dx, 0x0081 # Check next bootable disk
int 0x13 # DISK - READ SECTORS INTO MEMORY
# AL = number of sectors to read, CH = track, CL = sector
# DH = head, DL = drive, ES:BX -> buffer to fill
# Return: CF set on error, AH = status, AL = number of sectors read
jnb read_success
read_failure: # If we couldn't get data for second bootable, just boot USB
pop es
jmp boot_usb_no_rtc
push 0
pop es # ES remains set to segment 0 from here on
xor ebx, ebx # Sector #1 in 64 bit address mode (#0)
mov cx, 0x0001 # Sector #1 in CHS address mode (#1)
mov dx, 0x0081 # drive number (DL), track 0 (DH)
call read_sector
jb boot_usb # If we couldn't get data => just boot USB
read_success:
mov bx, offset partition_table
mov cx, PT_MAX
mov bp, offset partition_table
check_table: # check the partition table for an active (bootable) partition
cmpb es:[bp+0], 0x00
jl active_partition # 0x80 or greater means the partition is active
cmpb es:[bx], 0x00
jl found_active # 0x80 or greater means the partition is active
jnz invalid_table # anything between 0x01 and 0x7f is invalid
add bp, PT_ENTRY_SIZE # next partition
add bx, PT_ENTRY_SIZE # next partition
loop check_table
no_active:
pop es
jmp exit
active_partition:
push bp
check_one_active: # check that all subsequent partitions are zero (only one active)
add bp, PT_ENTRY_SIZE
dec cx
jz valid_active
cmpb es:[bp+0], 0x00
jz check_one_active
pop bp
invalid_table:
pop es
jmp exit
jmp boot_usb
valid_active:
pop bp
pop es
found_active:
mov si, offset prompt_string
call print_string
call print_string # Prompt the user
call flush_keyboard
set_rtc_int_vect: # Set the interrupt vector for CMOS real-time clock
@ -143,21 +117,21 @@ set_rtc_int_vect: # Set the interrupt vector for CMOS real-time clock
wait_for_keyboard:
mov ah, 0x01
int 0x16 # KEYBOARD - CHECK BUFFER, DO NOT CLEAR
jnz boot_usb # Z is clear when characters are present in the buffer
jnz boot_usb_rem_rtc # Z is clear when characters are present in the buffer
mov ah, 0x02
int 0x16 # KEYBOARD - GET SHIFT STATUS
and al, 0x04 # AL = shift status bits
jnz boot_usb
cmpw ds:counter_dot, 0x0000
cmpb ds:counter_dot, 0x00
jg short check_timeout
print_dot:
print_dot: # Every so often, we print a dot
mov si, offset dot_string
call print_string
movw ds:counter_dot, DOT_TIMEOUT
movb ds:counter_dot, DOT_TIMEOUT
check_timeout:
cmpw ds:counter_timeout, 0x0000
cmpb ds:counter_timeout, 0x00
jnz wait_for_keyboard
boot_fixed_disk: # Timeout occured => boot second bootable disk (non USB)
@ -169,25 +143,23 @@ boot_fixed_disk: # Timeout occured => boot second bootable disk (non USB)
boot_drive:
pop es
pop ds
mov dl, 0x0080 # In both case, we pretend the disk is the first bootable
mov dx, 0x0080 # In both case, we pretend the disk is the first bootable
jmp 0:MBR_ADDR
# ---------------------------------------------------------------------------
boot_usb: # Boot USB drive (0x80)
boot_usb_rem_rtc: # Boot USB drive (0x80)
call restore_rtc_vect # Remove our RTC override
boot_usb:
call flush_keyboard # Make sure the keyboard buffer is clear
boot_usb_no_rtc:
mov bx, offset partition_table
mov dx, es:[bx]
mov cx, es:[bx+2]
xor ax, ax
mov es, ax # make sure ES is set to seg 0 for copy
mov ax, 0x0201
mov bx, MBR_ADDR
int 0x13
mov dx, ds:[bx]
mov cx, ds:[bx+2]
mov ebx, ds:[bx+8] # Must come last since it modifies BX
call read_sector
jnb boot_drive
exit:
exit: # failed to read PBR from USB - exit back to BIOS
pop es
pop ds
retf
@ -197,17 +169,56 @@ exit:
/* Subroutines */
/********************************************************************************/
read_sector: # Read a single sector in either CHS or LBA mode
# EBX = LBA sector address (32 bit), CH = track
# CL = sector, DL = drive number, DH = head
pusha # save all registers
mov ah, 0x41
mov bx, 0x55aa
int 0x13
jb no_ext # failure to get ext
cmp bx, 0xaa55
jnz no_ext
test cx, 1 # is packet access supported?
jz no_ext
ext: # http://en.wikipedia.org/wiki/INT_13H#INT_13h_AH.3D42h:_Extended_Read_Sectors_From_Drive
popa
push ds
xor eax, eax
mov ds, ax # We'll use the stack for DAP, which is in seg 0
push eax # bits 32-63 of sector address (set to 0)
push ebx # bits 0-31 of sector address (EBX)
push ax # destination segment
push MBR_ADDR # destination address
inc ax
push ax # number of sectors to be read (1)
push 0x0010 # size of DAP struct
mov si, sp # DAP address (= stack)
mov ah, 0x42 # Extended Read Sectors From Drive
int 0x13
lahf
add sp,0x10
sahf
pop ds
ret
no_ext: # http://en.wikipedia.org/wiki/INT_13H#INT_13h_AH.3D02h:_Read_Sectors_From_Drive
popa
mov bx, MBR_ADDR # CL, CH, DL, DH, ES are already set
mov ax, 0x0201
int 0x13
ret
# ---------------------------------------------------------------------------
set_int_vect: # Set the interrupt vector
cli # SI = pointer to backup vector (must contain the interrupt #)
push es # DX = pointer to interrupt
xor ax, ax
mov es, ax
mov bx, ds:[si]
mov eax, es:[bx] # Backup the original vector
mov ds:[si], eax
mov es:[bx], dx
mov es:[bx+2], cs
pop es
sti
ret
@ -215,13 +226,9 @@ set_int_vect: # Set the interrupt vector
restore_rtc_vect: # Restore the interrupt vector for RTC
cli
push es
xor ax, ax
mov es, ax
mov bx, INT_RTC*4
mov eax, ds:rtc_interrupt_org
mov es:[bx], eax
pop es
sti
ret
@ -238,23 +245,15 @@ flush_keyboard: # Flush the keyboard buffer
# ---------------------------------------------------------------------------
print_string: # Print a NUL terminated string to console
push ax
push bx
ps_putchar:
print_string: # Print NUL terminated string in DS:SI to console
lodsb
cmp al, 0x00
jz ps_exit
jz 0f
mov ah, 0x0e
mov bx, 0x0007 # BH = display page, BL = foreground color
int 0x10 # VIDEO - WRITE CHARACTER AND ADVANCE CURSOR
jmp ps_putchar
ps_exit:
pop bx
pop ax
ret
jmp print_string
0: ret
# ---------------------------------------------------------------------------
@ -267,6 +266,23 @@ disk_swap: # Swap disks 0x80 and 0x81
xor dl, 0x01
0: ret
# ---------------------------------------------------------------------------
.if 0
print_hex: # Hex dump of the word at address ES:BX
mov cx, 0x04
mov dx, es:[bx]
0: rol dx, 0x04
mov ax, 0xe0f
and al, dl
daa
add al, 0xF0
adc al, 0x40
int 0x10
loop 0b
ret
.endif
/********************************************************************************/
/* Interrupt overrides */
@ -276,10 +292,10 @@ disk_swap: # Swap disks 0x80 and 0x81
rtc_interrupt:
pushf
cli
cmpw cs:counter_timeout, 0x0000
cmpb cs:counter_timeout, 0x00
jz rtc_exec_org
decw cs:counter_dot
decw cs:counter_timeout
decb cs:counter_dot
decb cs:counter_timeout
rtc_exec_org:
rtc_interrupt_org = .+1 # Same trick used by the LILO mapper
@ -315,8 +331,8 @@ dsk_interrupt_org = .+1
int13_cmd: .byte 0x00
prompt_string: .string "\r\nPress any key to boot from USB."
dot_string = .-2 # Reuse the end of previous string
counter_timeout:.word DOT_NUMBER*DOT_TIMEOUT + 1
counter_dot: .word DOT_TIMEOUT
counter_timeout:.byte DOT_NUMBER*DOT_TIMEOUT + 1
counter_dot: .byte DOT_TIMEOUT
/********************************************************************************/

Binary file not shown.

49
res/mbr/readme.txt Normal file
View File

@ -0,0 +1,49 @@
Rufus: The Reliable USB Formatting Utility - Custom MBR
# Description
This directory contains all the resources required to create an MBR that prompts
the user for boot selection, when a second bootable device (typically bootable
fixed HDD) is reported by the BIOS.
This aims at mimicking the Microsoft Windows optical installation media feature,
which may be necessary on for WinPE 2.x or earlier based installations.
# Compilation
Any gcc suite (except possibly the X-Code one on OS-X) should be able to compile
the MBR by invoking 'make'. A 'make dis', that produces a disassembly dump is
also provided for your convenience.
# Primer
The way this bootloader achieves the feature highlighted above is as follows:
1. An attempt to read the MBR of the second bootable drive (0x81) is made
through INT_13h (in either CHS or LBA mode depending on the extensions
detected)
2. If that attempts succeeds, then the partition table from the newly read MBR
is checked for an active/bootable entry.
3. If such a partition is found, a prompt is displayed to the user and an RTC
timer interrupt (INT_8h) override is added so that dots are displayed at
regular interval. Then the keyboard is checked for entry.
4. If the user presses a key, the first partition boot record from the USB is
read (according to the values found in the USB MBR partition table) and
executed
5. If no key is pressed, then an INT_13h (disk access interrupt) override is
added to masquerade the second bootable drive (0x81) as the first one (0x80)
so that the Windows second stage installer, or any other program relying on
BIOS disk access, behave as if there was no USB drive inserted.
6. In case there was a failure to read the second bootable drive's MBR, or no
active partition was detected there, the USB is booted without prompts.
# Limitations
* If you are using software RAID or a non-conventional setup, the second
bootable disk may not be accessible through the BIOS and therefore the USB
will always be booted
* If the bootable HDD uses LILO, a "LILO - Keytable read/checksum error" will
be displayed when trying to boot it.
* This MBR currently does not masquerade the bootable USB drive as secondary
(0x81) therefore an installation program ran from USB to install an OS on
an HDD may still configure that disk as the second drive, and prevent it to
properly boot later on.

View File

@ -33,7 +33,7 @@ LANGUAGE LANG_ENGLISH, SUBLANG_NEUTRAL
IDD_DIALOG DIALOGEX 12, 12, 206, 289
STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU
EXSTYLE WS_EX_APPWINDOW
CAPTION "Rufus v1.1.6.155"
CAPTION "Rufus v1.1.6.156"
FONT 8, "MS Shell Dlg", 400, 0, 0x1
BEGIN
DEFPUSHBUTTON "Start",IDC_START,94,248,50,14
@ -73,7 +73,7 @@ BEGIN
DEFPUSHBUTTON "OK",IDOK,231,175,50,14,WS_GROUP
CONTROL "<a href=""http://rufus.akeo.ie"">http://rufus.akeo.ie</a>",IDC_ABOUT_RUFUS_URL,
"SysLink",WS_TABSTOP,46,47,114,9
LTEXT "Version 1.1.6 (Build 155)",IDC_STATIC,46,19,78,8
LTEXT "Version 1.1.6 (Build 156)",IDC_STATIC,46,19,78,8
PUSHBUTTON "License...",IDC_ABOUT_LICENSE,46,175,50,14,WS_GROUP
EDITTEXT IDC_ABOUT_COPYRIGHTS,46,107,235,63,ES_MULTILINE | ES_READONLY | WS_VSCROLL
LTEXT "Report bugs or request enhancements at:",IDC_STATIC,46,66,187,8
@ -223,8 +223,8 @@ END
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 1,1,6,155
PRODUCTVERSION 1,1,6,155
FILEVERSION 1,1,6,156
PRODUCTVERSION 1,1,6,156
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
@ -241,13 +241,13 @@ BEGIN
BEGIN
VALUE "CompanyName", "akeo.ie"
VALUE "FileDescription", "Rufus"
VALUE "FileVersion", "1.1.6.155"
VALUE "FileVersion", "1.1.6.156"
VALUE "InternalName", "Rufus"
VALUE "LegalCopyright", "© 2011 Pete Batard (GPL v3)"
VALUE "LegalTrademarks", "http://www.gnu.org/copyleft/gpl.html"
VALUE "OriginalFilename", "rufus.exe"
VALUE "ProductName", "Rufus"
VALUE "ProductVersion", "1.1.6.155"
VALUE "ProductVersion", "1.1.6.156"
END
END
BLOCK "VarFileInfo"