User:Combuster/Recovered MBR
Copy of the MBR stuff for archival purposes and backup of the original after the author ragequit on it. Once the world has calmed down, I ought to reconstitute the better parts and improve the theoretical wiki parts on bootloaders.
Stuff looks safe for MBR use. All other purposes are a lot of extended truths from the original author. Discussion included for reference; use sourcecode with caution.
Goal?
I'm not sure I understand the purpose of this page, as it isn't really an article---it just seems to be the source code for a simplistic boot loader; while it may be useful to the community, I'm not sure source code listings are the best use of a wiki. Perhaps it would make more sense to provide a link to this boot loader somewhere? --Love4boobies 04:19, 30 March 2012 (CDT)
- That would probably be for the best, overall. Look at it this way: If you provide source, you should also provide maintenance and support. That's easy to do on a private project page, but somewhat difficult with a Wiki article. Plus, most of the use cases of this bootloader could be just as well covered by writing a GRUB extension, unless I'm seriously mistaken. -- Solar 04:29, 30 March 2012 (CDT)
Corrections Explained
I made some corrections, Turdus "un-corrected" them (possibly because he couldn't have known why they were made), and then I rolled back his "un-corrections" and wrote this to explain the reasons for the original corrections.
From the old preface: "If you're happy with fat32, extfs and pe, elf or even aout, they are supported by multi-boot, so leave now and use GRUB.". This is wrong because the multiboot specification says nothing about any file system or any executable file format. It could say "If you're happy with fat32, extfs and pe, elf or even aout, they are supported by GRUB" but then it'd still be a little wrong because GRUB legacy (one implementation of multiboot) has no support for PE or aout (unlike ELF). To avoid confusion it's just simpler to not say so much, especially as it's unnecessary (as this page isn't about multiboot or GRUB anyway).
From the old chainloading section: "For drive optimization, the first partition usually started on the second cylinder, leaving the remainder of the first track (a 63-sector gap) between the MBR and the first VBR.". For modern/large drives there are 63 sectors per track (due to old BIOS limits). However, for old/small drives there can be less than 63 sectors per track. For example, a 512 MiB USB flash drive could be presented as 32 sectors per track (with 128 heads and 256 cylinders). Assuming there's always 63 sectors per track is a bad idea. Note: I didn't realise this before, but if there are 63 sectors per track (the maximum) then the gap would be 62 sectors and not 63 sectors (as one of the 63 sectors is consumed by the MBR and therefore isn't part of the gap).
From the GPT section: "Intel engineers and others decided to create a new partitioning scheme in the gap after the MBR, called GPT. This however made non IBM PC compatible boot managers (occupying the same sectors) incompatible.". For GPT there's 2 partition tables for redundancy (one near the start of the disk an one near the end), and the entire partitioning scheme (including the second partition table) is not in the gap. I had also removed the "(occupying the same sectors)" part. Mostly (at the time) I was tempted to replace the majority of the history section with links to the relevant wiki pages, and compromised (by reducing unnecessary detail) instead.
--Brendan 15:37, 20 March 2012 (CDT)
One correction:
This is wrong because the multiboot specification says nothing about any file system or any executable file format.
Multiboot requires reading ELF headers for load addresses, and provides the not-so-aptly named "a.out" kludge (while nobody uses a.out) for any other format to provide similar information. I plan on doing a thorough read of the article later and fix things where necessary -- Combuster 01:05, 21 March 2012 (CDT)
I made some corrections, Turdus "un-corrected" them #2. I therefore added the disputed tag as a warning to all.
Point is, the bootloader is in essence designed with non-portability in mind. The claim is that it can be used as a VBR, but it fails that claim for any filesystem that embeds information on sector 0 - which include FAT*, HPFS, NTFS, SFS and XFS and only excludes Ext2/3 for as far as I can check. It also promotes using hardcoded continuous blocks for booting which fails when the second stage is a file that might be touched by the filesystem as well as being a good way of causing the typical starter bugs by not loading enough sectors.
I also explicitly added the floppy exclusion because it is considered a VBR by the world (apaprently minus Turdus as per the forums), and because many BIOSes don't support LBA for those devices making any attempt to use this code a shortcut for screwing up that way.
And that the device must support LBA should be obvious, especially to the writer. -- Combuster 18:14, 28 March 2012 (CDT)
Corrections:
It is not designed for FAT, HPFS, NTFS,... it would be obvious if you kept "If you're happy with fat32, extfs and pe, elf or even aout, they are supported by GRUB". Maybe pe and aout should have been left out, but the rest applies.
- "Point is, the bootloader is in essence designed with non-portability in mind."
How do you know that? It was not designed for existsing filesystems, yes, but it's portable. Tested scenarios:
- MBR (UBRL), VBR (LILO) -> Works,
- MBR (UBRL), VBR (MSDOS FAT32) -> Works,
- MBR (LILO), VBR (UBRL) -> Works.
- "It also promotes using hardcoded continuous blocks for booting which fails when the second stage is a file"
You are wrong (as usual), and you really should learn to READ. I wrote in section "2nd stage":
- "Even better, store it in a defragmented, continuous file on your filesystem"
Do you know what "defragmented, continous" means, don't you?
- "I therefore added the disputed tag as a warning to all."
Show me the details of your testing before you do that.
- "I had also removed the "(occupying the same sectors)" part." and "with links to the relevant wiki pages"
You should really read those wiki pages and specifications: http://en.wikipedia.org/wiki/GUID_Partition_Table
It's very clear that first GPT starts right after the MBR: "Partition table header (LBA 1)" Don't see any option on LBA 1 here. Also, it reads: "Partition entries starting LBA (always 2 in primary copy)"
Doesn't matter if first partition starts at 63 or 32 or whatever, the point is, you cannot use the gap after the MBR because it's for the GPT now. Rephrase the sentences if you like. And on modern disks it's not 63 (that's the past), in the last 5 years all disks we bought had partitions megabyte aligned.
- "I made some corrections, Turdus "un-corrected" them (possibly because he couldn't have known why they were made)"
Because they do not mean the same, or you've removed important information. For example:
- "If you are happy with multiboot"
How would a reader know if it's multiboot he/she is looking for? I wrote some examples on purpose.
Now it reads: "If you're happy with ELF, FAT or ext (fileformat and filesystems supported by version of multi-boot)"
Hope it's good for you. -- Turdus 12:15, 29 March 2012 (CDT)
The Wiki format does not cater well for such point-by-point replies. If you have an argument, take it to the forum. And please keep it civil. Calling each other names serves no purpose. What might be marginal on the forum is definitely out of place on a Wiki discussion page.
Advertising own code has always been met with criticism here (as I can attest); take it with style and address it instead of trying to shoot it down. Perhaps it would be better to keep the code and prose in a separate place (your project website), and merely provide a link to it in the relevant context; it's not really tutorial style, it does contain statements that are disputed. -- Solar 09:04, 29 March 2012 (CDT)
Wiki format rules understood.
About the rewrite: it's much more readble indeed, and it reflects what I originally meant, so thank you very much. I wrote only a few words explaining why BIOS Boot partition is useful.
I did not want to advertise my code on the wiki. I wanted to show how to write a 2nd stage that capable of booting via different methods, and for that I needed a working 1st stage. Never wanted to say it's the only way to do it, or anything, it's nothing more than a working example. This is now made clear in Preface, thanks.
About dispute: the usability of the code as VBR was questioned by someone who didn't even bothered to test it, or even clearly reasoning why. His argument was invalid (does not support floppies), since floppies does not have VBRs. But I accept your opinion it's disputed, so my question is, how to make this clear? All I can say is test it, and see it working (I spent endless nights to test it in different scenarios, so I know it's okay. I wouldn't have posted it here if I wasn't 100% sure). --Turdus 0926, 30 March 2012 (CST)
- Not really my bowl of fish here (and I don't feel like testing it ATM), but what about your code requiring LBA addressing? Does that work on floppies, too? Combuster says it doesn't. -- Solar 03:15, 30 March 2012 (CDT)
History
Boot Sector
When the system powers up, it determines the boot device, loads the first sector from that device (usually the first sector on the device), and executes it. On early systems, like floppy-based ones, the boot sector then proceeded to load the OS kernel directly.
Chainloading
With the advent of hard disks, drive partitioning became the rule. The partition table also resided in the boot sector, and the available space for the boot code was reduced to 440 bytes.
The code in the boot sector (now called Master Boot Record -- MBR) would parse the partition table, select one of the partitions to boot from (usually through an "active" flag), load the first sector of that partition (the Volume Boot Record -- VBR), and execute it. The VBR code would then proceed to load the OS kernel.
Since the VBR code expected to be executed from the exact same memory address as the MBR -- as if it were just loaded by the BIOS -- the MBR code had to relocate itself to a different memory location before loading the VBR code to its previous location. This was called chainloading.
LILO was an example of this approach.
Boot Managers
If you wanted a sophisticated selection menu (a "boot manager"), the 440 bytes size limit of the MBR was crippling.
Hard drive addressing at that time followed the Cylinder-Head-Sector (CHS) scheme. The MBR resided in the first sector, first head, first cylinder. For access optimization, the rest of the first cylinder was usually left unassigned, and the first partition started on the second cylinder, leaving a gap of unassigned sectors. Many boot managers took advantage of this and stored additional code in this gap. That was convenient, but risky - sometimes other software used those sectors too, potentially destroying the boot manager.
Two-Stage Bootloader
One solution to that problem was to split up the boot manager into two "stages": A first stage (residing in the MBR) loads the second stage from a partition's file system. The second stage then provides traditional "boot manager" services (a pretty selection menu, decent error handling etc.).
GRUB is an example of this approach.
Multiboot
GRUB is capable of traditional chainloading of VBR's as well as booting Linux kernels directly. But the GRUB developers additionally specified the Multiboot standard: A Multiboot-compliant kernel could be booted with protected mode already set up, kernel modules loaded to memory, a memory map provided, and several other niceties. The idea was to provide a standard where any Multiboot-loader could load any Multiboot-kernel.
ROM expansion
Another way of booting was invented for diskless systems. In this case bootloader code resides in ROM, most commonly on a network card. Instead of booting from disk, it loads the OS over the network.
It is possible to hack such a ROM, and make the BIOS load your bootloader code, which in turn loads your OS. Details can be found in the BIOS boot specification.
GPT
As disks became larger, the limits of the MBR partitioning scheme became an issue. Together with a new boot mechanism (EFI), a new partitioning scheme called GPT was devised, using the sectors in the gap after the MBR for its data.
Old MBR-based tools - like the aforementioned boot managers - could accidentially overwrite the GPT data structures. As a signal that a given device is using GPT for its partitioning, its MBR partition table marks the whole device as being one single partition of the new type 0xEE. Within the scope of GPT, the so-marked MBR is called Protective Master Boot Record.
BIOS Boot Partition
Some people find EFI overcomplicated, and wanted the ability to use a GPT-partitioned device but still boot via the old BIOS / two-stage bootloader mechanism. They introduced a special GPT partition type, the BIOS Boot Partition, which does not hold any filesystem, but is a place to store the second stage of the bootmanager.
The goal
The goal is to load a code and provide consistent environment for it in different scenarios, such as:
- MBR(UBRL) -> 2nd stage
- MBR -> VBR(UBRL) -> 2nd stage
- PMBR -> BBP -> 2nd stage
- GRUB(chainload) -> VBR -> 2nd stage
- GRUB(fs, as kernel) -> 2nd stage
- VBR(partitionless disk) -> 2nd stage
- BIOS -> expansion ROM -> 2nd stage
The boot code is responsible for finding a dedicated filesystem (marked by an active flag for example), locate standard file(s) on it (the kernel and optionally an initrd or modules), load them into memory and pass control over. To achieve this, first you have to get your boot code loaded.
I'll show you how to write a code that can be loaded by standard BIOS boot method, via Multiboot and from ROM as well.
If you use one of the standard filesystems (ext, fat etc.) I advise to read no further, simply use GRUB or other MultiBoot compliant boot loader. You can take advantage of a loader of your own only if you want to go down through the rabbit hole, and use your own file system or executable format.
Finally, the code
1st stage
This is a minimal code (440 bytes at most) that can be placed in a Master Boot Record or GPT's Protective MBR. It's loaded by the BIOS or a boot record via chainload.
The 1st stage code shown here is:
- 100% compatible with the original IBM PC master boot record code
- Same binary can be installed as MBR, PMBR or VBR as well
- Forward GPT compatible on non-EFI machines
- Loads a 2nd stage loader from any partition
- Works with harddisks and USB keys (pendrives). Floppies not supported.
- Requires LBA support, no CHS (could be a problem for BIOSes older than 10 years).
- Tested on several virtual and real machines.
stage1.asm:
;*********************************************************************
;* *
;* OS/3D - written by Zoltan Baldaszti (aka Turdus) in 2008 *
;* Compilation: fasm -d EFISIZE=1 stage1.asm ubrl.bin *
;* *
;* Universal Boot Record Loader. *
;* *
;* memory occupied: 0500-800 *
;* *
;*********************************************************************
;get default EFISIZE, PARTSIZE
;EFISIZE=EFI System partition size in kilobytes
;PARTSIZE=size of your filesystem's partition in kilobytes
include "config.inc"
;--------------------------Macros----------------------------
;the writestr mnemonic. Writes a message on screen.
macro writestr msg
{
if ~ msg eq si
push si
mov si, msg
end if
call writestrfunc
if ~ msg eq si
pop si
end if
}
;the die mnemonic. Writes a reason and reboots.
macro die msg
{
if ~ msg eq si
mov si, msg
end if
jmp diefunc
}
;2nd stage header format (64 bytes):
;offset length desc
; 0 2 expansion ROM magic (AA55h)
; 2 1 size in blocks (40h)
; 3 1 magic E9h
; 4 2 real mode entry point (relative)
; 6 2 checksum
; 7 1 revision, must be zero
; 8 18 architecture (default "x86_bios")
;26 2 pnp ptr, must be zero
;28 4 flags, must be zero
;32 32 Multiboot header (for GRUB compatibility, must contain a
; protected mode entry point)
;any format can follow.
ldr.id equ 800h ;position of 2nd stage loader
ldr.firstbyte equ 840h ;first non-header byte
ldr.executor equ 803h ;ptr to init code
ldr.loader equ 500h ;loader and partition type
ldr.drive equ 501h ;bios drive of loader
ldr.begin equ 506h ;lba of loader
ldr.bootdiskid equ 50Eh ;boot disk unique winnt id
ldr.bootbegin equ 522h ;lba of boot partition
ldr.bootsize equ 52Ah ;size of boot partition
lba_packet equ 532h
virtual at lba_packet
lbapacket.size: dw ?
lbapacket.count:dw ?
lbapacket.addr0:dw ?
lbapacket.addr1:dw ?
lbapacket.sect0:dw ?
lbapacket.sect1:dw ?
lbapacket.sect2:dw ?
lbapacket.sect3:dw ?
end virtual
;*********************************************************************
;* code *
;*********************************************************************
;-----------------BIOS called ENTRY POINT--------------------
ORG 0600h
USE16
universal_boot_record:
cli
jmp short .skipid
.system: db "UBRL" ;Universal Boot Record Loader
.version: db 0 ;version 0.0.1beta
.skipid: ;NOTE: not sure about 32 bit processor yet, so we have to
;use only 16 bit operands
;relocate our code to offset 600h
xor ax, ax
mov ss, ax
mov sp, 600h
push ax
pop es
push cs
pop ds
;find our position in memory.
call .getaddress
.getaddress: pop bx
sub bx, .getaddress-universal_boot_record
mov si, bx
cld
mov di, sp
;clear data area 500h-600h
sub di, 100h
mov cx, 80h
repnz stosw
;and copy ourselves to 600h
mov cx, 100h
repnz movsw
jmp 0:.start
.start: ;get boot drive code - original MBR and BIOS passes it in dl,
;so all bootmanagers should do.
;save drive code
mov byte [ldr.drive], dl
;initialize lba packet
mov byte [lbapacket.size], 16
mov byte [lbapacket.count], 58
mov byte [lbapacket.addr0+1], 08h
push dx
writestr .system
pop dx
;check for lba presistance - floppy not supported any more
;we use pendrive as removable media for a long time
cmp dl, byte 80h
jl .nolba
.notfloppy: mov ah, byte 41h
mov bx, word 55AAh
int 13h
jc .nolba
cmp bx, word 0AA55h
jne .nolba
test cl, byte 1
jnz .lbaok
.nolba: die lbanotf
.lbaok: ;try to load stage2 - it's a continous area on disk
;started at given sector with maximum size of 7400h bytes
;doesn't matter if we can load it or not,
;we still want to search for partitions
mov si, stage2_addr
mov di, lbapacket.sect0
push di
movsw
movsw
movsw
movsw
call loadsectorfunc
;we have to load the MBR beacuse we may be loaded
;as a volume boot record
mov byte [lbapacket.addr0+1], 07Ch
pop di
xor ax, ax
stosw
stosw
stosw
stosw
;actually we load more than 2 sectors, but it's fast and
;does not bother anybody. The code is smaller this way,
;and nevertheless pre-caching fasten up overall boot time.
call loadsectorfunc
jc .nombr
mov bp, 07DFEh
cmp word [bp], bx
je .mbrok
.nombr: die mbrnotf
.mbrok: inc byte [bp] ;mess up id
;copy disk id from MBR
;NOTE: si now points to 7DB8h
mov di, diskid
movsw
movsw
;searching for active partition (skip over 2 more zero bytes)
inc si
inc si
;check for EFI partitioning scheme (so we will not depend on
;special partition record EEh and such)
;NOTE: bp+2 is 7E00h
cmp word [bp+2], 'EF'
jne .nextpartition
cmp word [bp+4], 'I '
jne .nextpartition
;generate fake MBR partition entry for it
xor dx, dx
xor cx, cx
inc cx
jmp near .partitionok
.nextpartition: cmp si, bp
jb .ok
;indicate no boot partition found
mov byte [ldr.loader], 16
jmp near .chk
.ok: mov ax, word [si+12]
mov word [ldr.bootsize], ax
mov al, byte [si]
mov cx, word [si+8]
mov dx, word [si+10]
add si, word 16
cmp al, byte 80h ;check for active partition
jb .nextpartition
;this is a dirty hack. It allows to boot from a different disk.
;this should not be used, but we must keep compatibility with
;the M$ boot record shit. Anyway, if you use 80h, we won't
;change the device code that the BIOS gave us.
je .partitionok
mov byte [ldr.drive], al
;save the partition info
.partitionok: mov word [ldr.bootbegin], cx
mov word [ldr.bootbegin+2], dx
;load partition's (or GPT table's) first 58 sector
mov word [lbapacket.sect0], cx
mov word [lbapacket.sect1], dx
call loadsectorfunc
;do we have a 2nd stage loader?
.chk: cmp word [ldr.id], bx
jne .nostage2
cmp bzte [ldr.id+3], 0E9h
jne .nostage2
;invoke stage2 real mode code
writestr okay
jmp ldr.executor
.nostage2: ;if no 2nd stage loader, continue with standard boot mechanism
;check if it's a valid boot record
cmp word [bp], bx
jne .noos
;do not load ourself again, that would be a loop forever
cmp word [bp+2+.system-universal_boot_record], 'UB'
je .noos
writestr okay
;dl must contain bootdrive
mov dl, byte [ldr.drive]
jmp 07C00h
.noos: die osnotfound
;*********************************************************************
;* functions *
;*********************************************************************
;loads an LBA sector
loadmbr:
loadsectorfunc:
push bx
push si
mov ah, byte 42h
mov dl, byte [ldr.drive]
mov si, lba_packet
int 13h
pop si
pop bx
ret
;ds:si zero terminated string to write
writestrfunc:
lodsb
or al, al
jz .end
mov ah, byte 0Eh
mov bx, word 11
int 10h
jmp writestrfunc
.end: ret
;writes the reason, waits for a key and reboots.
diefunc:
writestr panic
call writestrfunc
mov si, found
call writestrfunc
xor ax, ax
int 16h
mov al, 0FEh
out 64h, al
; hlt
jmp far 0FFFFh:0 ;invoke BIOS POST
;*********************************************************************
;* data area *
;*********************************************************************
panic: db "-PANIC: no ",0
lbanotf: db "LBA support",0
mbrnotf: db "PMBR",0
osnotfound: db "operating system",0
found: db " found",0
okay: db ".",10,13,0
db 01B0h-($-$$) dup 0
;right before the partition table some data
stage2_addr: dd 0FFFFFFFFh,0 ;1B0h 2nd stage loader address
diskid: dd 012345678h ;1B8h WinNT expects it here
dw 0
;fake partition tables
;EFI Partition
db 0h ;bootable
db 0FEh,0FFh,0FFh ;CHS not used
db 0EEh ;fs type
db 0FEh,0FFh,0FFh ;CHS not used
dd 1 ;start LBA
dd 0ffffffffh ;end LBA
;OS/3D root, modify to your needs
db 80h ;bootable
db 0FEh,0FFh,0FFh ;CHS not used
db 03dh ;fs type
db 0FEh,0FFh,0FFh ;CHS not used
dd (EFISIZE*2)+8 ;start LBA
dd 0ffffffffh ;end LBA
db 01FEh-($-$$) dup 0
db 55h,0AAh
;*********************************************************************
;* end of Universal Boot Record *
;*********************************************************************
;*********************************************************************
;* GUID Partition Table *
;*********************************************************************
;NOTE: the image creator must calculate correct checksums.
gpt_header:
;gpt header
.signature: db "EFI PART"
.revision: dw 0,1
.size: dd 92
.crc: dd 0DEADCC32h ;not used, but should be valid
.reserved: dd 0
.primarylba: dd 1,0
.backuplba: dd 1,0 ;not used, but should be valid
.firstusable: dd 64,0
.lastusable: dd 0FFFFFFFFh,0 ;not used, but should be valid
.diskuuid: dd 1,2,3,4
.partitionlba: dd 2,0
.numentries: dd 2
.sizeentry: dd 128
.partitioncrc: dd 0DEADCC32h ;not used, but should be valid
db 0200h-($-gpt_header) dup 0
gpt_partitions:
.entry0_type: db 028h,073h,02Ah,0C1h,01Fh,0F8h,0D2h,011h
db 0BAh,04Bh,0,0A0h,0C9h,03Eh,0C9h,03Bh
.entry0_uuid: db 1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4
.entry0_start: dd 8,0
.entry0_ends: dd (EFISIZE*2)+8,0
.entry0_attrib: dd 0,0
.entry0_name: db 'E',0,'F',0,'I',0,' ',0,'s',0,'y',0,'s',0
db 't',0,'e',0,'m',0,' ',0,'p',0,'a',0,'r',0
db 't',0,'i',0,'t',0,'i',0,'o',0,'n'
db 128-($-.entry0_type) dup 0
;our test OS/3D system partition
;my stage2 code will look for BOOTDISK first, but if none found, it will use the
;first partition labeled ROOTDISK.
;modify this entry to your needs.
.entry1_type: db 'FS3D',0,1,0,0,'ROOTDISK'
.entry1_uuid: db 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16
.entry1_start: dd (EFISIZE*2)+8,0
.entry1_ends: dd (EFISIZE*2)+(PARTSIZE*2)+8,0
.entry1_attrib: dd 0,0
.entry1_name: db 'B',0,'e',0,'n',0,'d',0,'e'
db 128-($-.entry1_type) dup 0
;-----------make it logical sector (4k) long----------
db 4096-($-universal_boot_record) dup 0
2nd stage
The rest of the code can be right after the 1st stage, although it's discouraged due to compatibility issues (see section GPT). It's better to put it on a separate mbr or gpt partition (to protect it from overwriting by accident). Even better, store it in a defragmented, continuous file on your filesystem (note that being continous is essential since 1st stage cannot parse your fs). Do not forget to record the starting LBA address in 1st stage before using. This gives you the ability to use different 2nd stage code each time you reboot.
This is just a skeleton to aid you. It will not parse your filesystem, or set up the environment for your kernel. You have to do these on your own. But everything else is done so you can focus on these important matters.
stage2.asm:
;*********************************************************************
;* *
;* OS/3D - written by Zoltan Baldaszti (aka Turdus) in 2008 *
;* Compilation: fasm stage2.asm stage2.bin *
;* *
;* Example 2nd stage loader for UBRL, compatible with GRUB and *
;* BIOS boot specification 1.0.1 (expansion ROM) too. *
;* *
;* memory occupied: 800-7C00 *
;* *
;*********************************************************************
;-----------header section-------------
;UBR header
USE16
ORG 800h
loader: db 55h,0AAh ;ROM magic
db (loader_end-loader)/512 ;size in 512 blocks
.executor: jmp near realmode_start ;entry point
.checksum: dw 0 ;checksum
@@: db "Your OS name here"
db 18-($-@b) dup 0
.pnpptr: dw 0
.flags: dd 0
;GRUB header
MB_MAGIC equ 01BADB002h
MB_FLAGS equ 01000h
align 8
.mb_header: dd MB_MAGIC ;magic
dd MB_FLAGS ;flags
dd -(MB_MAGIC+MB_FLAGS) ;checksum (0-magic-flags)
dd .mb_header ;our location (GRUB should load us here)
dd 0800h ;the same... load start
dd 07C00h ;load end
dd 0h ;no bss
dd multiboot_start ;entry point
;-----------realmode-protmode stub-------------
;MEMORY LAYOUT ON EXECUTION:
;500h loader and partition type
;501h bios drive code of loader
;506h starting lba of this stage on disk
;50Eh boot disk unique winnt id
;522h starting lba of MBR boot partition/GPT table
;52Ah size of MBR boot partition/GPT table
;800h LDRF header
;7C00h MBR boot partition/GPT table preloaded (first 16 sectors)
; appropriate for chainloading
realmode_start: cld
cli
mov sp, 800h
;relocate ourself from ROM to RAM if necessary
call .getaddress
.getaddress: pop si
mov ax, cs
or ax, ax
jnz .reloc
cmp si, .getaddress
je .noreloc
.reloc: mov ds, ax
xor ax, ax
mov es, ax
mov di, loader
sub si, .getaddress-loader
mov cx, ((loader_last+8)-loader)/2
repnz movsw
xor ax, ax
mov ds, ax
jmp 0:.relocated
.relocated: ;you'll need to locate boot partition
;and load it to 7C00h, because neither
;BIOS nor UBRL have done this for you
;in this case (loaded from ROM)
.noreloc:
;do any real mode initialization here
;(e820, A20 gate etc.)
lgdt [GDT_value]
mov eax, cr0 ;enable protected mode
or al, 1
mov cr0, eax
jmp 16:protmode_start
;global descriptor table
align 8
GDT_table: dd 0, 0 ;null descriptor
dd 0000FFFFh,008F9200h ;flat ds
dd 0000FFFFh,00CF9A00h ;32-bit ring0 cs
GDT_value: dw $-GDT_table
dd GDT_table
;----------------Multiboot stub-----------------
;MEMORY LAYOUT ON EXECUTION: see Multiboot spec
USE32
multiboot_start:
cld
cli
;you'll need to locate boot partition
;and load it to 7C00h, because GRUB
;hasn't done this for you
lgdt [GDT_value]
cmp eax, 2BADB002h
je protmode_start
;no GRUB environment available?
;something really nasty happened, restart computer
mov al, 0FEh
out 64h, al
hlt
protmode_start: mov ax, 8
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ss, ax
mov esp, 800h
;do any prot mode initialization here
;(irq remap, paging etc.)
;-----------longmode stub if you want-------------
;enable long mode
;*********************************************************************
;* your loader code *
;*********************************************************************
;put your own kernel loader code here.
;parse your executable format here.
;call your kernel's main here.
;-----------padding to be multiple of 512----------
db (511-($-loader+511) mod 512) dup 0
loader_end:
;-----------BIOS checksum------------
chksum = 0
repeat $-loader
load b byte from (loader+%-1)
chksum = (chksum + b) mod 100h
end repeat
store byte (100h-chksum) at (loader.checksum)
;-----------bound check-------------
;fasm will generate an error if your code
;is bigger than it should be
db 07400h-($-loader) dup ?
Testing
UBRL in MBR
Quite straightforward, create a disk image with UBRL in MBR, and record stage2.bin's position at 1B0h.
UBRL in VBR
Create a disk image with your favourite MBR, and put UBRL in the 1st sector of a partition. You still have to record stage2.bin's position at 1B0h in MBR.
GRUB Multiboot
Specify stage2.bin as "kernel" in menu.lst.
BIOS ROM
Use bochs. Configure optional ROM, specify stage2.bin as ROM's name, and 0xC0000+(vga_bios_size rounded to 2k) as address (as of writing, bochs vga bios is 40960 bytes long, so the address will be 0xCA000). Also modify boot options to boot from "network". Ready to go!