若干年前,tpu听说苹果的MacOSX也推出PC版本了,十分兴奋,赶紧下载ISO想试试看。我记得下载的版本是10.4.6吧。然后准备安装。由于硬盘前面都满了,于是找了个扩展分区安装。安装后傻眼了......不启动啊。然后才开始上网看各种文档,这才知道,MacOSX只能安装在主分区。
tpu不甘心啊,找来各种资料研究。darwin本身是开放源代码的,关于引导的文件,都可以找得到。比如chain0, boot0 boot1 boot2等。有了这些原始的资料帮助,再加上tpu很久之前就研究过MBR这些东西,于是就拼凑出了tboot这个东西(tpu's bootloader)。
tboot的原理很简单,在boot0的代码基础上,完善了扩展分区的搜索代码。这样不论系统装在哪个分区上,都可以找得到。找到之后,再加载该分区前面的boot1部分。boot1再解析分区结构,继续加载boot2。boot2完成整个OSX的加载。
boot2运行后,如果你的硬盘有多个分区,会显示一个分区列表,让你选择从哪一个分区启动。默认的选项是第一个分区。但一般OSX所在的分区不是第一个,这样每次启动你必须再手动选择一次,很是麻烦。于是tpu对boot2打了个补丁,让它把OSX所在的分区作为默认分区。这样就方便许多了。
虽然tpu为引导OSX费了这么大的劲,但OSX只在我硬盘上存在几周就消失了。用起来实在是不习惯啊。不过副产品tboot一直有很多网友在用,它确实解决了一些实际的问题,tpu觉得所作的努力还是值得的。听说现在引导工具已经是百花齐放了,比tboot各种先进。不知道tboot是否还能继续引导最新的OSX...
; tboot.s
;
; Load HFS volume on any partition(main or ext)
; writen by tpu, base on boot0 source from Darwin
;
; Set to 1 to enable obscure debug messages.
DEBUG EQU 0
; Various constants.
kBoot0Segment EQU 0x0000
kBoot0Stack EQU 0xFFF0 ; boot0 stack pointer
kBoot0LoadAddr EQU 0x7C00 ; boot0 load address
kBoot0RelocAddr EQU 0xE000 ; boot0 relocated address
kBoot2Sectors EQU 126 ; sectors to load for boot2
kBoot2Address EQU 0x0000 ; boot2 load address
kBoot2Segment EQU 0x2000 ; boot2 load segment
kMBRBuffer EQU 0x1000 ; MBR buffer address
kExtBuffer EQU 0x1200 ; MBR buffer address
kPartTableOffset EQU 0x1be
kMBRPartTable EQU kMBRBuffer + kPartTableOffset
kExtPartTable EQU kExtBuffer + kPartTableOffset
kSectorBytes EQU 512 ; sector size in bytes
kBootSignature EQU 0xAA55 ; boot sector signature
kPartCount EQU 4 ; number of paritions per table
kPartTypeBoot EQU 0xab ; boot2 partition type
kPartTypeUFS EQU 0xa8 ; UFS partition type
kPartTypeHFS EQU 0xaf ; HFS partition type
kPartTypeExtDOS EQU 0x05 ; DOS extended partition type
kPartTypeExtWin EQU 0x0f ; Windows extended partition type
kPartTypeExtLinux EQU 0x85 ; Linux extended partition type
kPartActive EQU 0x80
kDriveNumber EQU 0x80
; Format of fdisk partition entry.
; The symbol 'part_size' is automatically defined as an `EQU'
; giving the size of the structure.
struc part
.bootid: resb 1 ; bootable or not
.head: resb 1 ; starting head, sector, cylinder
.sect: resb 1 ;
.cyl: resb 1 ;
.type: resb 1 ; partition type
.endhead resb 1 ; ending head, sector, cylinder
.endsect: resb 1 ;
.endcyl: resb 1 ;
.lba: resd 1 ; starting lba
.sectors resd 1 ; size in sectors
endstruc
; Macros.
%macro DebugCharMacro 1
mov al, %1
call _putc
%endmacro
%if DEBUG
%define putc(x) DebugCharMacro x
%else
%define putc(x)
%endif
%macro puts 1
mov si, %1
call put_string
%endmacro
;--------------------------------------------------------------------------
; Start of text segment.
SEGMENT .text
ORG 0xe000 ; must match kBoot0RelocAddr
;--------------------------------------------------------------------------
; Boot code is loaded at 0:7C00h.
start:
; Set up the stack to grow down from kBoot0Segment:kBoot0Stack.
; Interrupts should be off while the stack is being manipulated.
cli ; interrupts off
xor ax, ax ; zero ax
mov ss, ax ; ss <- 0
mov sp, kBoot0Stack ; sp <- top of stack
sti ; reenable interrupts
mov es, ax ; es <- 0
mov ds, ax ; ds <- 0
; Relocate boot0 code.
mov si, kBoot0LoadAddr ; si <- source
mov di, kBoot0RelocAddr ; di <- destination
cld ; auto-increment SI and/or DI registers
mov cx, kSectorBytes/2 ; copy 256 words
repnz movsw ; repeat string move (word) operation
; Code relocated, jump to start_reloc in relocated location.
jmp 0:start_reloc
;--------------------------------------------------------------------------
; Start execution from the relocated location.
;
start_reloc:
.loop_next_drive:
; Clear drive flags.
xor eax, eax
mov [first_part], eax ; clear EBIOS LBA offset
mov [this_part], eax
%if DEBUG
putc('D')
mov al, dl
call putb
putc(' ')
%endif
; Read MBR sector to memory.
mov al, 1 ; load one sector
mov bx, kMBRBuffer ; MBR load address
xor ecx, ecx
call load
jc .next_drive
mov di, kMBRBuffer
mov si, kMBRBuffer+kPartTableOffset
cmp WORD [si + part_size * kPartCount], kBootSignature
jne .next_drive ; Signature error
; Look for the HFS partition in the MBR partition table
call find_boot ; will not return on success
.next_drive:
inc dl ; next drive number
test dl, 0x4 ; went through all 4 drives?
jz .loop_next_drive ; not yet, loop again
puts part_error_str
.hang:
hlt
jmp SHORT .hang
;--------------------------------------------------------------------------
; Find the HFS partition and load the booter from the partition.
;--------------------------------------------------------------------------
find_boot:
mov cx, 4 ; number of partition entries per table
.loop_part:
push cx
push si
%if DEBUG
mov al, [si + part.type]
call putb
putc(' ')
mov eax, [si + part.lba]
call putd
putc(10)
putc(13)
%endif
mov eax, [si + part.lba]
mov bl, [si + part.type] ; print partition type
cmp bl, kPartTypeExtLinux ; Extended Linux
je .ext_check
cmp bl, kPartTypeExtDOS ; Extended DOS
je .ext_check
cmp bl, kPartTypeExtWin ; Extended Windows(95)
jne .nonext
.ext_check:
cmp di, kMBRBuffer
jnz .ext_not_mbr
mov [first_part], eax
xor eax, eax
.ext_not_mbr:
mov [this_part], eax
; Read extended partition table
mov ecx, eax
mov al, 1 ; load one sector
mov bx, kExtBuffer ; extended load address
call load
jc .next_part
mov si, kExtBuffer+kPartTableOffset
cmp WORD [si + part_size * kPartCount], kBootSignature
jne .next_part ; Signature error
push di
mov di, kExtBuffer
call find_boot
pop di
cmp di, kMBRBuffer
jnz .next_part
xor eax, eax
mov [this_part], eax
mov [first_part], eax
.next_part:
pop si
pop cx
add si, part_size ; advance SI to next partition entry
loop .loop_part ; loop through all partition entries
.exit_find
ret
.nonext:
cmp bl, 0
je .next_part
cmp bl, kPartTypeHFS
je .found_part
cmp bl, kPartTypeUFS
je .found_part
cmp bl, kPartTypeBoot
jne .next_part
.found_part:
add eax, [this_part]
mov ecx, eax
add eax, [first_part]
mov [si+part.lba], eax
; Found boot partition, read boot sector to memory.
mov bx, kBoot0LoadAddr
mov al, 1
call load
jc .next_part ; load error, keep looking?
putc("F")
cmp WORD [bx + 510], kBootSignature
jnz .next_part
; patch boot1h
mov eax, [0x7cbb]
cmp eax, 0x20000200
jnz .no_patch
mov eax, 0x00007d40
mov [0x7cbb], eax
push si
mov si, patch_code_start
mov di, 0x7d40
mov cx, (patch_code_end-patch_code_start)
cld
repnz
movsb
pop si
.no_patch:
; Jump to partition booter. The drive number is already in register DL.
; SI is pointing to the modified partition entry.
jmp kBoot0Segment:kBoot0LoadAddr
;--------------------------------------------------------------------------
; load - Read sectors from a partition using LBA addressing.
;--------------------------------------------------------------------------
load:
pushad ; save all registers
mov bp, sp ; save current SP
;push DWORD 0 ; offset 12, upper 32-bit LBA
push ds ; For sake of saving memory,
push ds ; push DS register, which is 0.
add ecx, [first_part] ; offset 8, lower 32-bit LBA
push ecx
push es ; offset 6, memory segment
push bx ; offset 4, memory offset
xor ah, ah ; offset 3, must be 0
push ax ; offset 2, number of sectors
push WORD 16 ; offset 0-1, packet size
%if DEBUG
putc('L')
mov eax, ecx
call putd
putc(10)
putc(13)
%endif
mov si, sp
mov ah, 0x42
int 0x13
mov sp, bp ; restore SP
popad
ret
;--------------------------------------------------------------------------
; Write a string to the console.
;
; Arguments:
; DS:SI pointer to a NULL terminated string.
;
; Clobber list:
; AX, BX, SI
;
put_string
mov bx, 1 ; BH=0, BL=1 (blue)
cld ; increment SI after each lodsb call
.loop
lodsb ; load a byte from DS:SI into AL
cmp al, 0 ; Is it a NULL?
je .exit ; yes, all done
mov ah, 0xE ; INT10 Func 0xE
int 0x10 ; display byte in tty mode
jmp short .loop
.exit
ret
%if DEBUG
; Show a DWORD value.
putd
ror eax, 16
call putw
ror eax, 16
; Show a WORD value.
putw
ror ax, 8
call putb
ror ax, 8
; Show a BYTE value.
putb
ror al, 4
call put_nibble
ror al, 4
; Show 4 bit value.
put_nibble
push eax
and al, 0x0f
add al, 0x30
cmp al, 0x39
jna .pascii
add al, 0x07
.pascii
call _putc
pop eax
ret
; Show a ASCII character to the console.
_putc
pusha
mov bx, 1
mov ah, 0x0e
int 0x10
popa
ret
%endif ;DEBUG
patch_code_start:
; patch boot2: SelectBootVolume
mov di, 0x39ac
mov eax, [es:di]
cmp eax, 0x01a8da45
jne .no_patch
mov al, 2
mov [es:di+3], al
.no_patch:
; Jump to boot2. The drive number is already in register DL.
jmp kBoot2Segment:kBoot2Address + kSectorBytes
patch_code_end:
; NULL terminated strings.
part_error_str: db 'No HFS partition found', 10, 13, 0
;--------------------------------------------------------------------------
; Pad the rest of the 512 byte sized booter with zeroes. The last
; two bytes is the mandatory boot sector signature.
pad_boot
times 510-($-$$) db 0
dw kBootSignature
ABSOLUTE 0xE400
; In memory variables.
first_part resd 1 ; starting LBA of the intial extended partition.
this_part resd 1 ; starting LBA of the current extended partition.
part_base resd 1
END