从BIOS到MBR过程图

BIOS

SeaBIOS是一个开源的x86 BIOS,这里以SeaBIOS为例分析,对应的代码为seabios/src/romlayout.S

reset_vector

CPU上电后,IP = 0xFFF0(https://en.wikipedia.org/wiki/Reset_vector),CPU执行的第一条指令位于0xFFF0

        ORG 0xfff0 // Power-up Entry Point
        .global reset_vector
reset_vector:
        ljmpw $SEG_BIOS, $entry_post

SEG_BIOS为0xF000

#define SEG_BIOS     0xf000

entry_post

entry_post位于0xE05B

        ORG 0xe05b
entry_post:
        cmpl $0, %cs:HaveRunPost                // Check for resume/reboot
        jnz entry_resume
        ENTRY_INTO32 _cfunc32flat_handle_post   // Normal entry point

ENTRY_INTO32是一个宏定义,最后跳转到transition32,进入保护模式,其中edx保存_cfunc32flat_handle_post的地址

// Reset stack, transition to 32bit mode, and call a C function.
.macro ENTRY_INTO32 cfunc
xorw %dx, %dx
movw %dx, %ss
movl $ BUILD_STACK_ADDR , %esp
movl $ \cfunc , %edx
jmp transition32
.endm

transition32最后跳转到edx保存的地址,也就是_cfunc32flat_handle_post

// Place CPU into 32bit mode from 16bit mode.
// %edx = return location (in 32bit mode)
// Clobbers: ecx, flags, segment registers, cr0, idt/gdt
        DECLFUNC transition32
        .global transition32_nmi_off
transition32:
        // Disable irqs (and clear direction flag)
        cli
        cld

        // Disable nmi
        movl %eax, %ecx
        movl $CMOS_RESET_CODE|NMI_DISABLE_BIT, %eax
        outb %al, $PORT_CMOS_INDEX
        inb $PORT_CMOS_DATA, %al

        // enable a20
        inb $PORT_A20, %al
        orb $A20_ENABLE_BIT, %al
        outb %al, $PORT_A20
        movl %ecx, %eax

transition32_nmi_off:
        // Set segment descriptors
        lidtw %cs:pmode_IDT_info
        lgdtw %cs:rombios32_gdt_48

        // Enable protected mode
        movl %cr0, %ecx
        andl $~(CR0_PG|CR0_CD|CR0_NW), %ecx
        orl $CR0_PE, %ecx
        movl %ecx, %cr0

        // start 32bit protected mode code
        ljmpl $SEG32_MODE32_CS, $(BUILD_BIOS_ADDR + 1f)

        .code32
        // init data segments
1:      movl $SEG32_MODE32_DS, %ecx
        movw %cx, %ds
        movw %cx, %es
        movw %cx, %ss
        movw %cx, %fs
        movw %cx, %gs

        jmpl *%edx

handle_post -> dopost -> reloc_preinit -> maininit -> startBoot,startBoot通过call16_int调用0x19号中断

// Begin the boot process by invoking an int0x19 in 16bit mode.
void VISIBLE32FLAT
startBoot(void)
{
    // Clear low-memory allocations (required by PMM spec).
    memset((void*)BUILD_STACK_ADDR, 0, BUILD_EBDA_MINIMUM - BUILD_STACK_ADDR);

    dprintf(3, "Jump to int19\n");
    struct bregs br;
    memset(&br, 0, sizeof(br));
    br.flags = F_IF;
    call16_int(0x19, &br);
}

_farcall16

_farcall16 -> stack_hop_back -> __stack_hop_back -> call16,其中func为_farcall16

void VISIBLE16
_farcall16(struct bregs *callregs, u16 callregseg)
{
    if (need_hop_back()) { // 平坦模式为true,实模式为false
        stack_hop_back(_farcall16, callregs, callregseg);
        return;
    }
    ASSERT16();
    asm volatile(
        "calll __farcall16\n"
        : "+a" (callregs), "+m" (*callregs), "+d" (callregseg)
        :
        : "ebx", "ecx", "esi", "edi", "cc", "memory");
}
#define stack_hop_back(func, eax, edx) ({                               \
        extern void _cfunc16_ ##func (void);                            \
        __stack_hop_back((u32)(eax), (u32)(edx), _cfunc16_ ##func );    \
    })
// Switch back to original caller's stack and call a function.
u32
__stack_hop_back(u32 eax, u32 edx, void *func)
{
    if (!MODESEGMENT)
        return call16(eax, edx, func);
    if (!MODE16 || !on_extra_stack())
        return ((u32 (*)(u32, u32))func)(eax, edx);
    ASSERT16();
    u16 bkup_ss;
    u32 bkup_stack_pos, temp;
    asm volatile(
        // Backup stack_pos and current %ss/%esp
        "movl %6, %4\n"
        "movw %%ss, %w3\n"
        "movl %%esp, %6\n"
        // Restore original callers' %ss/%esp
        "movl -4(%4), %5\n"
        "movl %5, %%ss\n"
        "movw %%ds:-8(%4), %%sp\n"
        "movl %5, %%ds\n"
        // Call func
        "calll *%2\n"
        // Restore %ss/%esp and stack_pos
        "movw %w3, %%ds\n"
        "movw %w3, %%ss\n"
        "movl %6, %%esp\n"
        "movl %4, %6"
        : "+a" (eax), "+d" (edx), "+c" (func), "=&r" (bkup_ss)
          , "=&r" (bkup_stack_pos), "=&r" (temp), "+m" (StackPos)
        :
        : "cc", "memory");
    return eax;
}

call16跳转到transition16,进入实模式,其中edx保存call16 1f的地址

// Call a 16bit SeaBIOS function, restoring the mode from last call32().
static u32
call16(u32 eax, u32 edx, void *func)
{
    ASSERT32FLAT();
    if (getesp() > MAIN_STACK_MAX)
        panic("call16 with invalid stack\n");
    if (CONFIG_CALL32_SMM && Call16Data.method == C16_SMM)
        return call16_smm(eax, edx, func);

    extern void transition16big(void);
    extern void transition16(void);
    void *thunk = transition16;
    if (Call16Data.method == C16_BIG || in_post())
        thunk = transition16big;
    func -= BUILD_BIOS_ADDR;
    u32 stackseg = Call16Data.ss;
    asm volatile(
        // Transition to 16bit mode
        "  movl $(1f - " __stringify(BUILD_BIOS_ADDR) "), %%edx\n"
        "  jmp *%%ecx\n" // ecx保存thunk,也就是transition16的地址
        // Setup ss/esp and call func
        ASM32_SWITCH16
        "1:movl %2, %%ecx\n"
        "  shll $4, %2\n"
        "  movw %%cx, %%ss\n"
        "  subl %2, %%esp\n"
        "  movw %%cx, %%ds\n"
        "  movl %4, %%edx\n"
        "  movl %3, %%ecx\n" // ecx保存func,也就是_farcall16
        "  calll _cfunc16_call16_helper\n"
        // Return to 32bit and restore esp
        "  movl $2f, %%edx\n"
        "  jmp transition32_nmi_off\n" // 回到保护模式
        ASM32_BACK32
        "2:addl %2, %%esp\n"
        : "+a" (eax), "+c"(thunk), "+r"(stackseg)
        : "r" (func), "r" (edx)
        : "edx", "cc", "memory");
    return eax;
}

transition16最后跳转到edx保存的地址,也就是call16 1f

// Place CPU into 16bit mode from 32bit mode.
// %edx = return location (in 16bit mode)
// Clobbers: ecx, flags, segment registers, cr0, idt/gdt
        DECLFUNC transition16
        .global transition16big
        .code32
transition16:
        // Reset data segment limits
        movl $SEG32_MODE16_DS, %ecx
        movw %cx, %ds
        movw %cx, %es
        movw %cx, %ss
        movw %cx, %fs
        movw %cx, %gs

        // Jump to 16bit mode
        ljmpw $SEG32_MODE16_CS, $1f

transition16big:
        movl $SEG32_MODE16BIG_DS, %ecx
        movw %cx, %ds
        movw %cx, %es
        movw %cx, %ss
        movw %cx, %fs
        movw %cx, %gs

        ljmpw $SEG32_MODE16BIG_CS, $1f

        .code16
        // Disable protected mode
1:      movl %cr0, %ecx
        andl $~CR0_PE, %ecx
        movl %ecx, %cr0

        // far jump to flush CPU queue after transition to real mode
        ljmpw $SEG_BIOS, $2f

        // restore IDT to normal real-mode defaults
2:      lidtw %cs:rmode_IDT_info

        // Clear segment registers
        xorw %cx, %cx
        movw %cx, %fs
        movw %cx, %gs
        movw %cx, %es
        movw %cx, %ds
        movw %cx, %ss  // Assume stack is in segment 0

        jmpl *%edx

从call16 1f继续往下执行,_cfunc16_call16_helper -> _farcall16,这是第二次调用_farcall16,这一次已经是实模式,因此_farcall16直接调用__farcall16

// 16bit handler code called from call16() / call16_smm()
u32 VISIBLE16
call16_helper(u32 eax, u32 edx, u32 (*func)(u32 eax, u32 edx))
{
    u8 method = call32_post();
    u32 ret = func(eax, edx); // func为_farcall16
    call32_prep(method);
    return ret;
}

__farcall16先push flags/CS/IP,再通过iretw指令pop IP/CS/flags,跳转到_farcall16的callregs->code

// Far call a 16bit function from 16bit mode with a specified cpu register state
// %eax = address of struct bregs, %edx = segment of struct bregs
// Clobbers: %e[bc]x, %e[ds]i, flags
        DECLFUNC __farcall16
__farcall16:
        // Save %edx/%eax, %ebp
        pushl %ebp
        pushl %eax
        pushl %edx

        // Setup for iretw call
        movl %edx, %ds
        pushw %cs
        pushw $1f                       // return point
        pushw BREGS_flags(%eax)         // flags
        pushl BREGS_code(%eax)          // CS:IP

        // Load calling registers and invoke call
        RESTOREBREGS_DSEAX
        iretw                           // XXX - just do a lcalll
1:
        // Store flags, es, eax
        pushfw
        cli
        cld
        pushw %ds
        pushl %eax
        movw 0x08(%esp), %ds
        movl 0x0c(%esp), %eax
        SAVEBREGS_POP_DSEAX
        popw BREGS_flags(%eax)
        movw %ss, %cx
        movw %cx, %ds                   // Restore %ds == %ss

        // Remove %edx/%eax, restore %ebp
        popl %edx
        popl %eax
        popl %ebp

        retl

如果_farcall16的callregs->code对应的程序,在结尾恢复堆栈且执行ret指令,__farcall16从__farcall16 1f继续往下执行,依次返回_farcall16、_cfunc16_call16_helper,从_cfunc16_call16_helper继续往下执行,跳转到transition32_nmi_off,回到保护模式,继续依次返回call16、__stack_hop_back、stack_hop_back、_farcall16

call16_int

call16_int -> __call16_int,第二个参数是irq_trampoline_XX的地址,irq_trampoline_XX最终调用int指令

#define call16_int(nr, callregs) do {                           \
        extern void irq_trampoline_ ##nr (void);                \
        __call16_int((callregs), (u32)&irq_trampoline_ ##nr );  \
    } while (0)
// IRQ trampolines
        .macro IRQ_TRAMPOLINE num
        DECLFUNC irq_trampoline_0x\num
        irq_trampoline_0x\num :
        int $0x\num
        lretw
        .endm

        IRQ_TRAMPOLINE 02
        IRQ_TRAMPOLINE 05
        IRQ_TRAMPOLINE 10
        IRQ_TRAMPOLINE 13
        IRQ_TRAMPOLINE 15
        IRQ_TRAMPOLINE 16
        IRQ_TRAMPOLINE 18
        IRQ_TRAMPOLINE 19
        IRQ_TRAMPOLINE 1b
        IRQ_TRAMPOLINE 1c
        IRQ_TRAMPOLINE 4a

__call16_int -> _farcall16,传给_farcall16的callregs->code是irq_trampoline_XX的CS/IP

// Invoke a 16bit software interrupt.
void
__call16_int(struct bregs *callregs, u16 offset)
{
    callregs->code.offset = offset; // code.offset保存irq_trampoline_XX的地址
    if (!MODESEGMENT) {
        callregs->code.seg = SEG_BIOS;
        _farcall16((void*)callregs - Call16Data.ss * 16, Call16Data.ss);
        return;
    }
    callregs->code.seg = GET_SEG(CS); // code.seg保存GET_SEG(CS)
    _farcall16(callregs, GET_SEG(SS));
}

irq_trampoline_XX在结尾恢复堆栈且执行lretw指令,因此依次返回_farcall16、__call16_int、call16_int

0x19号中断

http://vitaly_filatov.tripod.com/ng/asm/asm_001.12.html
SeaBIOS的0x19号中断服务程序是handle_19

SET_IVT(0x19, FUNC16(entry_19_official));


entry_19_official:
        jmp entry_19


entry_19:
        ENTRY_INTO32 _cfunc32flat_handle_19

handle_19 -> do_boot -> boot_disk,boot_disk先通过call16_int调用0x13号中断,将MBR加载到0x7C00,再调用call_boot_entry

// Boot from a disk (either floppy or harddrive)
static void
boot_disk(u8 bootdrv, int checksig)
{
    u16 bootseg = 0x07c0;

    // Read sector
    struct bregs br;
    memset(&br, 0, sizeof(br));
    br.flags = F_IF;
    br.dl = bootdrv;
    br.es = bootseg; // es = 0x07c0,bx = 0x0000
    br.ah = 2;
    br.al = 1;
    br.cl = 1;
    call16_int(0x13, &br);

    if (br.flags & F_CF) {
        printf("Boot failed: could not read the boot disk\n\n");
        return;
    }

    if (checksig) {
        struct mbr_s *mbr = (void*)0;
        if (GET_FARVAR(bootseg, mbr->signature) != MBR_SIGNATURE) {
            printf("Boot failed: not a bootable disk\n\n");
            return;
        }
    }

    tpm_add_bcv(bootdrv, MAKE_FLATPTR(bootseg, 0), 512);

    /* Canonicalize bootseg:bootip */
    u16 bootip = (bootseg & 0x0fff) << 4; // bootip = 0x7c00
    bootseg &= 0xf000; // bootseg = 0x0000

    call_boot_entry(SEGOFF(bootseg, bootip), bootdrv);
}

0x13号中断

http://vitaly_filatov.tripod.com/ng/asm/asm_024.3.html
SeaBIOS的0x13号中断服务程序是handle_13

SET_IVT(0x13, FUNC16(entry_13_official));


entry_13_official:
        jmp entry_13


DECL_IRQ_ENTRY_ARG 13


.macro DECL_IRQ_ENTRY_ARG num
DECLFUNC entry_\num
IRQ_ENTRY_ARG \num
.endm


.macro IRQ_ENTRY_ARG num
.global entry_\num
entry_\num : // entry_13
pushl $ handle_\num // handle_13
jmp irqentry_arg
.endm


irqentry_arg:
        ENTRY_ARG_ST
        iretw


.macro ENTRY_ARG_ST
cli
cld
pushl %ecx
pushl %edx
pushl %ebx
pushl %ebp
pushl %esi
pushl %edi
pushw %es
pushw %ds
movw %ss, %cx           // Move %ss to %ds
movw %cx, %ds
movl %esp, %ebx         // Backup %esp, then zero high bits
movzwl %sp, %esp
movl 28(%esp), %ecx     // Get calling function
movl %eax, 28(%esp)     // Save %eax
movl %esp, %eax         // First arg is pointer to struct bregs
calll *%ecx // ecx保存28(%esp),也就是handle_13的地址
movl %ebx, %esp         // Restore %esp (including high bits)
POPBREGS
.endm


.macro POPBREGS
popw %ds
popw %es
popl %edi
popl %esi
popl %ebp
popl %ebx
popl %edx
popl %ecx
popl %eax
.endm

handle_13 -> handle_legacy_disk -> disk_13 -> disk_1302 -> basic_access

// Perform read/write/verify using old-style chs accesses
static void noinline
basic_access(struct bregs *regs, struct drive_s *drive_fl, u16 command)
{
    struct disk_op_s dop;
    dop.drive_fl = drive_fl;
    dop.command = command;

    u8 count = regs->al;
    u16 cylinder = regs->ch | ((((u16)regs->cl) << 2) & 0x300);
    u16 sector = regs->cl & 0x3f;
    u16 head = regs->dh;

    if (count > 128 || count == 0 || sector == 0) {
        warn_invalid(regs);
        disk_ret(regs, DISK_RET_EPARAM);
        return;
    }
    dop.count = count;

    struct chs_s chs = getLCHS(drive_fl);
    u16 nlc=chs.cylinder, nlh=chs.head, nls=chs.sector;

    // sanity check on cyl heads, sec
    if (cylinder >= nlc || head >= nlh || sector > nls) {
        warn_invalid(regs);
        disk_ret(regs, DISK_RET_EPARAM);
        return;
    }

    // translate lchs to lba
    dop.lba = (((((u32)cylinder * (u32)nlh) + (u32)head) * (u32)nls)
               + (u32)sector - 1);

    dop.buf_fl = MAKE_FLATPTR(regs->es, regs->bx);

    int status = send_disk_op(&dop);

    regs->al = dop.count;

    disk_ret(regs, status);
}

call_boot_entry

call_boot_entry -> farcall16 -> _farcall16,传给_farcall16的callregs->code是0x0000/0x7C00,跳转到0x7C00

static void
call_boot_entry(struct segoff_s bootsegip, u8 bootdrv)
{
    dprintf(1, "Booting from %04x:%04x\n", bootsegip.seg, bootsegip.offset);
    struct bregs br;
    memset(&br, 0, sizeof(br));
    br.flags = F_IF;
    br.code = bootsegip; // code保存0x0000/0x7c00
    // Set the magic number in ax and the boot drive in dl.
    br.dl = bootdrv;
    br.ax = 0xaa55;
    farcall16(&br);
}


void
farcall16(struct bregs *callregs)
{
    call16_override(0);
    _farcall16(callregs, 0);
}

SeaBIOS跳转到0x7C00,将控制权交给MBR,不再返回(0x13号中断返回,0x19号中断不返回)

MBR(boot.img)

查看boot.img的内容:
hexdump -C /boot/grub2/i386-pc/boot.img

00000000  eb 63 90 00 00 00 00 00  00 00 00 00 00 00 00 00  |.c..............|
00000010  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
00000050  00 00 00 00 00 00 00 00  00 00 00 80 01 00 00 00  |................|
00000060  00 00 00 00 ff fa eb 05  f6 c2 80 74 05 f6 c2 70  |...........t...p|
00000070  74 02 b2 80 ea 79 7c 00  00 31 c0 8e d8 8e d0 bc  |t....y|..1......|
00000080  00 20 fb a0 64 7c 3c ff  74 02 88 c2 52 be 05 7c  |. ..d|<.t...R..||
00000090  b4 41 bb aa 55 cd 13 5a  52 72 3d 81 fb 55 aa 75  |.A..U..ZRr=..U.u|
000000a0  37 83 e1 01 74 32 31 c0  89 44 04 40 88 44 ff 89  |7...t21..D.@.D..|
000000b0  44 02 c7 04 10 00 66 8b  1e 5c 7c 66 89 5c 08 66  |D.....f..\|f.\.f|
000000c0  8b 1e 60 7c 66 89 5c 0c  c7 44 06 00 70 b4 42 cd  |..`|f.\..D..p.B.|
000000d0  13 72 05 bb 00 70 eb 76  b4 08 cd 13 73 0d 5a 84  |.r...p.v....s.Z.|
000000e0  d2 0f 83 de 00 be 85 7d  e9 82 00 66 0f b6 c6 88  |.......}...f....|
000000f0  64 ff 40 66 89 44 04 0f  b6 d1 c1 e2 02 88 e8 88  |d.@f.D..........|
00000100  f4 40 89 44 08 0f b6 c2  c0 e8 02 66 89 04 66 a1  |.@.D.......f..f.|
00000110  60 7c 66 09 c0 75 4e 66  a1 5c 7c 66 31 d2 66 f7  |`|f..uNf.\|f1.f.|
00000120  34 88 d1 31 d2 66 f7 74  04 3b 44 08 7d 37 fe c1  |4..1.f.t.;D.}7..|
00000130  88 c5 30 c0 c1 e8 02 08  c1 88 d0 5a 88 c6 bb 00  |..0........Z....|
00000140  70 8e c3 31 db b8 01 02  cd 13 72 1e 8c c3 60 1e  |p..1......r...`.|
00000150  b9 00 01 8e db 31 f6 bf  00 80 8e c6 fc f3 a5 1f  |.....1..........|
00000160  61 ff 26 5a 7c be 80 7d  eb 03 be 8f 7d e8 34 00  |a.&Z|..}....}.4.|
00000170  be 94 7d e8 2e 00 cd 18  eb fe 47 52 55 42 20 00  |..}.......GRUB .|
00000180  47 65 6f 6d 00 48 61 72  64 20 44 69 73 6b 00 52  |Geom.Hard Disk.R|
00000190  65 61 64 00 20 45 72 72  6f 72 0d 0a 00 bb 01 00  |ead. Error......|
000001a0  b4 0e cd 10 ac 3c 00 75  f4 c3 00 00 00 00 00 00  |.....<.u........|
000001b0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 24 12  |..............$.|
000001c0  0f 09 00 52 be bd 7d 31  c0 cd 13 46 8a 0c 84 c9  |...R..}1...F....|
000001d0  75 0f be da 7d e8 cc ff  eb 96 46 6c 6f 70 70 79  |u...}.....Floppy|
000001e0  00 bb 00 70 8e c3 31 db  b8 01 02 b5 00 b6 00 cd  |...p..1.........|
000001f0  13 72 d4 b6 01 b5 4f e9  f1 fe 00 00 00 00 55 aa  |.r....O.......U.|
00000200

boot.img位于硬盘的第一个扇区,将硬盘的第一个扇区dump出来:
dd if=/dev/sda of=mbr.img bs=512 count=1

查看mbr.img的内容:
hexdump -C mbr.img

00000000  eb 63 90 00 00 00 00 00  00 00 00 00 00 00 00 00  |.c..............|
00000010  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
00000050  00 00 00 00 00 00 00 00  00 00 00 80 00 08 00 00  |................|
00000060  00 00 00 00 ff fa 90 90  f6 c2 80 74 05 f6 c2 70  |...........t...p|
00000070  74 02 b2 80 ea 79 7c 00  00 31 c0 8e d8 8e d0 bc  |t....y|..1......|
00000080  00 20 fb a0 64 7c 3c ff  74 02 88 c2 52 be 05 7c  |. ..d|<.t...R..||
00000090  b4 41 bb aa 55 cd 13 5a  52 72 3d 81 fb 55 aa 75  |.A..U..ZRr=..U.u|
000000a0  37 83 e1 01 74 32 31 c0  89 44 04 40 88 44 ff 89  |7...t21..D.@.D..|
000000b0  44 02 c7 04 10 00 66 8b  1e 5c 7c 66 89 5c 08 66  |D.....f..\|f.\.f|
000000c0  8b 1e 60 7c 66 89 5c 0c  c7 44 06 00 70 b4 42 cd  |..`|f.\..D..p.B.|
000000d0  13 72 05 bb 00 70 eb 76  b4 08 cd 13 73 0d 5a 84  |.r...p.v....s.Z.|
000000e0  d2 0f 83 de 00 be 85 7d  e9 82 00 66 0f b6 c6 88  |.......}...f....|
000000f0  64 ff 40 66 89 44 04 0f  b6 d1 c1 e2 02 88 e8 88  |d.@f.D..........|
00000100  f4 40 89 44 08 0f b6 c2  c0 e8 02 66 89 04 66 a1  |.@.D.......f..f.|
00000110  60 7c 66 09 c0 75 4e 66  a1 5c 7c 66 31 d2 66 f7  |`|f..uNf.\|f1.f.|
00000120  34 88 d1 31 d2 66 f7 74  04 3b 44 08 7d 37 fe c1  |4..1.f.t.;D.}7..|
00000130  88 c5 30 c0 c1 e8 02 08  c1 88 d0 5a 88 c6 bb 00  |..0........Z....|
00000140  70 8e c3 31 db b8 01 02  cd 13 72 1e 8c c3 60 1e  |p..1......r...`.|
00000150  b9 00 01 8e db 31 f6 bf  00 80 8e c6 fc f3 a5 1f  |.....1..........|
00000160  61 ff 26 5a 7c be 80 7d  eb 03 be 8f 7d e8 34 00  |a.&Z|..}....}.4.|
00000170  be 94 7d e8 2e 00 cd 18  eb fe 47 52 55 42 20 00  |..}.......GRUB .|
00000180  47 65 6f 6d 00 48 61 72  64 20 44 69 73 6b 00 52  |Geom.Hard Disk.R|
00000190  65 61 64 00 20 45 72 72  6f 72 0d 0a 00 bb 01 00  |ead. Error......|
000001a0  b4 0e cd 10 ac 3c 00 75  f4 c3 00 00 00 00 00 00  |.....<.u........|
000001b0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
000001c0  01 00 ee fe ff ff 01 00  00 00 af 1a c8 6f 00 00  |.............o..|
000001d0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
000001f0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 55 aa  |..............U.|
00000200

diff boot.img(左边)和mbr.img(右边)的前446个字节

可以看到0x5c-0x5d被改成0008,0x66-0x67被改成9090,两边不一致是因为grub在将boot.img写入MBR时修改了部分字段

我们可以在grub源码中找到修改的地方:
1、0x5c-0x5d被改成0008

// grub/util/setup.c
write_rootdev
    kernel_sector = (boot_img + GRUB_BOOT_MACHINE_KERNEL_SECTOR);

    /* FIXME: can this be skipped?  */
    *boot_drive = 0xFF;

    grub_set_unaligned64 (kernel_sector, grub_cpu_to_le64 (first_sector));

可以看到boot_img + GRUB_BOOT_MACHINE_KERNEL_SECTOR开始的8个字节被改成first_sector(core.img所在的第一个扇区),其中GRUB_BOOT_MACHINE_KERNEL_SECTOR为0x5c

#define GRUB_BOOT_MACHINE_KERNEL_SECTOR	0x5c

对于MBR分区格式,boot.img放在MBR(0号扇区),core.img放在MBR后面的扇区(从1号扇区开始,也就是左边改之前的0x0000000000000001)

对于GPT分区格式,boot.img放在MBR(0号扇区),MBR后面的扇区被GPT占用,不能再放core.img,因此core.img被移到其它地方(在我的机器上是从2048号扇区开始,也就是右边改之后的0x0000000000000800)

core.img所在的分区称为BIOS boot partition:https://en.wikipedia.org/wiki/BIOS_boot_partition

我们可以通过fdisk/parted命令查看硬盘的分区

$fdisk -l
WARNING: fdisk GPT support is currently new, and therefore in an experimental phase. Use at your own discretion.

Disk /dev/sda: 960.2 GB, 960197124096 bytes, 1875385008 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 4096 bytes
I/O size (minimum/optimal): 4096 bytes / 4096 bytes
Disk label type: gpt
Disk identifier: A45867DB-50B2-4A45-A15F-103A3073A3EC


#         Start          End    Size  Type            Name
 1         2048        10239      4M  BIOS boot       u0SLb
 2        10240      2107391      1G  EFI System      Znxzm
 3      2107392    106964991     50G  Microsoft basic D97K4
 4    106964992    111159295      2G  Microsoft basic mLFxa
 5    111159296   1875384319  841.3G  Microsoft basic muB2A
$parted /dev/sda
GNU Parted 3.1
Using /dev/sda
Welcome to GNU Parted! Type 'help' to view a list of commands.
(parted) p
Model: ATA INTEL SSDSC2KB96 (scsi)
Disk /dev/sda: 960GB
Sector size (logical/physical): 512B/4096B
Partition Table: gpt
Disk Flags:

Number  Start   End     Size    File system     Name   Flags
 1      1049kB  5243kB  4194kB                  u0SLb  bios_grub
 2      5243kB  1079MB  1074MB  ext4            Znxzm  boot
 3      1079MB  54.8GB  53.7GB  ext4            D97K4
 4      54.8GB  56.9GB  2147MB  linux-swap(v1)  mLFxa
 5      56.9GB  960GB   903GB   ext4            muB2A

(parted)

其中Type为BIOS boot(fdisk输出结果)/Flags为bios_grub(parted输出结果)的分区就是BIOS boot partition,可以看到它的开始扇区确实是2048

2、0x66-0x67被改成9090

// grub/util/setup.c
SETUP
    boot_drive_check = (grub_uint8_t *) (boot_img
					  + GRUB_BOOT_MACHINE_DRIVE_CHECK);
    /* Copy the possible DOS BPB.  */
    memcpy (boot_img + GRUB_BOOT_MACHINE_BPB_START,
	    tmp_img + GRUB_BOOT_MACHINE_BPB_START,
	    GRUB_BOOT_MACHINE_BPB_END - GRUB_BOOT_MACHINE_BPB_START);

    /* If DEST_DRIVE is a hard disk, enable the workaround, which is
       for buggy BIOSes which don't pass boot drive correctly. Instead,
       they pass 0x00 or 0x01 even when booted from 0x80.  */
    if (!allow_floppy && !grub_util_biosdisk_is_floppy (dest_dev->disk))
      {
	/* Replace the jmp (2 bytes) with double nop's.  */
	boot_drive_check[0] = 0x90;
	boot_drive_check[1] = 0x90;

可以看到boot_img + GRUB_BOOT_MACHINE_DRIVE_CHECK开始的2个字节被改成9090了,其中GRUB_BOOT_MACHINE_DRIVE_CHECK为0x66

#define GRUB_BOOT_MACHINE_DRIVE_CHECK	0x66

boot.img对应的代码为grub/grub-core/boot/i386/pc/boot.S

_start

boot.S的入口是_start,跳转到LOCAL(after_BPB)

.globl _start, start;
_start:
start:
	/*
	 * _start is loaded at 0x7c00 and is jumped to with CS:IP 0:0x7c00
	 */

	/*
	 * Beginning of the sector is compatible with the FAT/HPFS BIOS
	 * parameter block.
	 */

	jmp	LOCAL(after_BPB)               // eb 63                	jmp    0x65
	nop	/* do I care about this ??? */ // 90                   	nop

	/*
	 * This space is for the BIOS parameter block!!!!  Don't change
	 * the first jump, nor start the code anywhere but right after
	 * this area.
	 */

	.org GRUB_BOOT_MACHINE_BPB_START // 0x3
	.org 4
	scratch

BPB

BPB(BIOS Parameter Block,BIOS参数块)的范围是0x4到0x59,其中scratch包含DAP(Disk Address Packet,磁盘地址数据包)

	.macro scratch

	/* scratch space */
mode:
	.byte	0
disk_address_packet:
sectors:
	.long	0
heads:
	.long	0
cylinders:
	.word	0
sector_start:
	.byte	0
head_start:
	.byte	0
cylinder_start:
	.word	0
	/* more space... */
	.endm

0x5a-0x5b保存LOCAL(kernel_address)、0x5c-0x5f保存LOCAL(kernel_sector)、0x60-0x63保存LOCAL(kernel_sector_high)、0x64保存boot_drive

	.org GRUB_BOOT_MACHINE_BPB_END // 0x5a
	/*
	 * End of BIOS parameter block.
	 */

LOCAL(kernel_address):
	.word	GRUB_BOOT_MACHINE_KERNEL_ADDR // 0x5a-0x5b是00,80,0x8000

	.org GRUB_BOOT_MACHINE_KERNEL_SECTOR // 0x5c
LOCAL(kernel_sector):
	.long	1 // 0x5c-0x5d被grub改成00,08,00000800
LOCAL(kernel_sector_high):
	.long	0

	.org GRUB_BOOT_MACHINE_BOOT_DRIVE // 0x64
boot_drive:
	.byte 0xff	/* the disk to load kernel from */
			/* 0xff means use the boot drive */

LOCAL(after_BPB)

从LOCAL(after_BPB)往下执行,DL=0x80,跳转到real_start

LOCAL(after_BPB):

/* general setup */
	cli		/* we're not safe here! */

        /*
         * This is a workaround for buggy BIOSes which don't pass boot
         * drive correctly. If GRUB is installed into a HDD, check if
         * DL is masked correctly. If not, assume that the BIOS passed
         * a bogus value and set DL to 0x80, since this is the only
         * possible boot drive. If GRUB is installed into a floppy,
         * this does nothing (only jump).
         */
	.org GRUB_BOOT_MACHINE_DRIVE_CHECK // 0x66
boot_drive_check:
        jmp     3f	/* grub-setup may overwrite this jump */ // 0x66-0x67被grub改成90,90
        testb   $0x80, %dl
        jz      2f
3:
	/* Ignore %dl different from 0-0x0f and 0x80-0x8f.  */
	testb   $0x70, %dl
	jz      1f
2:	
        movb    $0x80, %dl
1:
	/*
	 * ljmp to the next instruction because some bogus BIOSes
	 * jump to 07C0:0000 instead of 0000:7C00.
	 */
	ljmp	$0, $real_start

从real_start往下执行,调用0x13号中断,判断是否支持LBA

real_start:

	/* set up %ds and %ss as offset from 0 */
	xorw	%ax, %ax
	movw	%ax, %ds // ds = 0
	movw	%ax, %ss // ss = 0

	/* set up the REAL stack */
	movw	$GRUB_BOOT_MACHINE_STACK_SEG, %sp // sp = 0x2000

	sti		/* we're safe again */

	/*
	 *  Check if we have a forced disk reference here
	 */
	movb   boot_drive, %al
	cmpb	$0xff, %al
	je	1f
	movb	%al, %dl
1:
	/* save drive reference first thing! */
	pushw	%dx

	/* print a notification message on the screen */
	MSG(notification_string)

	/* set %si to the disk address packet */
	movw	$disk_address_packet, %si // si指向DAP

	/* check if LBA is supported */
	movb	$0x41, %ah
	movw	$0x55aa, %bx
	int	$0x13 // 判断是否支持LBA

	/*
	 *  %dl may have been clobbered by INT 13, AH=41H.
	 *  This happens, for example, with AST BIOS 1.04.
	 */
	popw	%dx
	pushw	%dx

LOCAL(lba_mode)

对于LBA模式,调用0x13号中断,将2048号扇区(diskboot.img)加载到0x70000,跳转到LOCAL(copy_buffer)

LOCAL(lba_mode):
	xorw	%ax, %ax
	movw	%ax, 4(%si) // offset为0

	incw	%ax
	/* set the mode to non-zero */
	movb	%al, -1(%si)

	/* the blocks */
	movw	%ax, 2(%si) // 扇区数为1

	/* the size and the reserved byte */
	movw	$0x0010, (%si) // size为0x10

	/* the absolute address */
	movl	LOCAL(kernel_sector), %ebx
	movl	%ebx, 8(%si) // 0x00000800
	movl	LOCAL(kernel_sector_high), %ebx
	movl	%ebx, 12(%si) // 0x00000000,起始扇区号为0x800 = 2048

	/* the segment of buffer address */
	movw	$GRUB_BOOT_MACHINE_BUFFER_SEG, 6(%si) // segment为0x7000,segment:offset为0x70000

/*
 * BIOS call "INT 0x13 Function 0x42" to read sectors from disk into memory
 *	Call with	%ah = 0x42
 *			%dl = drive number
 *			%ds:%si = segment:offset of disk address packet
 *	Return:
 *			%al = 0x0 on success; err code on failure
 */

	movb	$0x42, %ah
	int	$0x13 // 将2048号扇区(diskboot.img)加载到0x70000

	/* LBA read is not supported, so fallback to CHS.  */
	jc	LOCAL(chs_mode)

	movw	$GRUB_BOOT_MACHINE_BUFFER_SEG, %bx // bx = 0x7000
	jmp	LOCAL(copy_buffer)

LOCAL(copy_buffer)

从LOCAL(copy_buffer)往下执行,将0x70000复制到0x8000,跳转到0x8000

LOCAL(copy_buffer):
	/*
	 * We need to save %cx and %si because the startup code in
	 * kernel uses them without initializing them.
	 */
	pusha // 8个通用寄存器(ax/bx/cx/dx/sp/bp/si/di)依次入栈
	pushw	%ds

	movw	$0x100, %cx // cx = 256
	movw	%bx, %ds // ds = 0x7000
	xorw	%si, %si // si = 0
	movw	$GRUB_BOOT_MACHINE_KERNEL_ADDR, %di // di = 0x8000
	movw	%si, %es // es = 0

	cld // DF = 0

	rep
	movsw // 将DS:SI(0x70000)复制到ES:DI(0x8000),重复执行256次,每次1个word,共1个扇区

	popw	%ds
	popa // 8个通用寄存器(di/si/bp/sp/dx/cx/bx/ax)依次出栈

	/* boot kernel */
	jmp	*(LOCAL(kernel_address)) // 跳转到0x8000

其中GRUB_BOOT_MACHINE_KERNEL_ADDR为0x8000

#define GRUB_BOOT_MACHINE_KERNEL_ADDR	(GRUB_BOOT_MACHINE_KERNEL_SEG << 4)

#define GRUB_BOOT_MACHINE_KERNEL_SEG GRUB_OFFSETS_CONCAT (GRUB_BOOT_, GRUB_MACHINE, _KERNEL_SEG)

#define GRUB_BOOT_I386_PC_KERNEL_SEG	0x800
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐