自制能使用U盘引导的程序(二)

本文介绍如何使用Ubuntu环境自制U盘启动盘,并提供了一个具体的示例:通过编写和编译boot.asm及kernel.bin两个文件实现加载自定义内核。文章详细展示了从底层汇编语言到C语言的开发过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

有了前面的基础铺垫,下面就要开始写代码了。

开发环境是Ubuntu,理由是Win7下面没有类似于dd的好用的绝对扇区写入工具,Win7由于一些安全方面的限制,自己编写程序写U盘MBR总是失败,可能是我水平太菜了吧^_^


二、要写入U盘MBR的程序boot.asm

这段代码的功能就是加载kernel.bin到地址0x7e00处,然后跳转到0x7e00。写这段程序,还是乖乖的使用汇编吧。也就那么四百来个字节。

    ; boot.bin max size is 440

    org 07c00h
    mov ax, cs
    mov ds, ax
    mov es, ax
    mov ss, ax
    mov ax, 0x7c00
    mov sp, ax

    call LoadKernel

    jmp $

LoadKernel:
    mov ax, [0x7c00+0x1c6]          ;store first sector No of partition
    mov word [PartitionStartSect], ax

    mov cx, 1                       ;read first sector of partition to 0x7e00
    mov bx, 0x7e00
    mov dx, 0
    mov si, DiskAddressPacket
    call ReadSects

    mov al, byte [0x7e00+0x0d]      ;store number of sectors per cluster
    mov byte [SectorsPerCluster], al
    mov ax, word [0x7e00+0x0e]      ;store number of reserved sectors
    mov word [ReservedSectors], ax
    mov al, byte [0x7e00+0x10]      ;store number of FAT table
    mov byte [FatNumber], al
    mov ax, word [0x7e00+0x24]      ;store number of sectors per FAT table
    mov word [SectorsPerFat], ax

    mov bh, 0                       ;calculate the sector No of data area
    mov bl, [FatNumber]
    mul bx
    add ax, [ReservedSectors]
    add ax, [PartitionStartSect]
    mov word [SectIndexOfData], ax

    mov bx, 0x7e00                  ;read a cluster from data area to 0x7e00
    mov ch, 0
    mov cl, [SectorsPerCluster]
    mov dx, 0
    call ReadSects
                                     
    call GetKernelFirstCluster      ;search for kernel name and get the first cluster No in data area

    cmp dx, 0                       ;if or not success
    jnz failed_to_load
    
    sub ax, 2                       ;if success, calculate the sector No of kernel and read it to RAM(only 4KB)
    mov bh, 0
    mov bl, [SectorsPerCluster]
    mul bx
    add ax, word [SectIndexOfData]
    mov bx, 0x7e00
    call ReadSects

    jmp 0x7e00
   
failed_to_load:
    mov bp, FailedMsg
    mov cx, FailedMsgEnd-FailedMsg
    call DispStr
    ret

;read sectors function
;Input:
;1. Disk Address Packet pointer, ds:si
;2. Disk sector offset low 16 bits, ax
;3. Disk sector offset high 16 bits, dx
;4. number of sectors to read, cx
;5. buffer of data read, ds:bx
ReadSects:
    push ax
    push dx
    mov word [si + 2], cx
    mov word [si + 4], bx
    mov word [si + 8], ax
    mov word [si + 10],dx
    mov ax, 0x4200
    mov dx, 0x80
    int 13h
    pop dx
    pop ax
    ret

;Search for kernel
;Output:
;1. find kernel file or not(0 for success, 1 for failed), dx
;2. low 16 bits of first cluster, ax
GetKernelFirstCluster:
    push bx
    push si
    push di
    push cx
    mov cx, 128 ;FAT32 Directory entrys of one cluster
travel_entry:
    push cx
    xor si, si
    mov cx, 11  ;file name length
    xor di, di
strcmp:
    mov al, [bx+si]
    cmp al, [KernelName+di]
    jnz break
    inc si
    inc di
    loop strcmp
    mov dx, 0
    add bx, 0x1a
    mov ax, [bx]
    pop cx
    jmp complete
break:
    add bx, 32  ;size of directory entry
    pop cx
    loop travel_entry
    mov dx, 1
complete:
    pop cx
    pop di
    pop si
    pop bx
    ret

;Display a character
;Input:
;1. Charater to display, al
DispChar:
    push ax
    push bx
    mov ah, 0x0e
    mov bx, 0x000c
    int 10h
    pop bx
    pop ax
    ret

;Display a string
;Input:
;1. Pointer to string, bp
;2. string length, cx
DispStr:
    push ax
    push cx
    push bp
dc:
    mov al, [bp]
    call DispChar
    inc bp
    loop dc

    pop bp
    pop cx
    pop ax
    ret

PartitionStartSect  dw 0
SectorsPerCluster   db 0
ReservedSectors     dw 0
FatNumber           db 0
SectorsPerFat       dd 0
SectIndexOfData     dw 0

KernelName          db "KERNEL  BIN"
FailedMsg           db "failed to load kernel..."
FailedMsgEnd:

DiskAddressPacket:
    db 16   ;packet size
    db 0    ;reserved
    dw 0    ;block count(sector)
    dw 0    ;buffer offset
    dw 0    ;buffer segment address
    dw 0    ;block number
    dw 0
    dw 0
    dw 0


这个程序要说明的是,我使用扩展int 13h bios中断来读取U盘扇区的。

编译方式 nasm boot.asm -o boot.bin,编译出来的boot.bin不能超过440字节

写入MBR dd if=boot.bin of=/dev/sdb

然后附带一个调试时用的以16进制打印整数的函数

;Display a Integer in hex format
;Input:
;1. Low 16 bits of Integer, ax
;2. High 16 bits of Integer, dx
DispInt:
    push ax
    push bx
    push cx
    push dx

    xor cx, cx
d:
    mov bx, 16
    div bx
    cmp dx, 9
    ja char
    add dx, '0'
    jmp over

char:
    sub dx, 10
    add dx, 'A'

over:
    push dx
    inc cx
    cmp ax, 0
    jz end
    xor dx, dx
    jmp d

end:
    mov bx, 000ch
l:
    pop ax
    mov ah, 0x0e
    int 10h
    loop l

    pop dx
    pop cx
    pop bx
    pop ax
    ret


三、编写kernel.bin

kernel.bin由两个文件编译连接而成,entry.asm和main.c,entry.asm仍旧使用汇编,作用是进入保护模式,然后调用main.c中的main函数。main.c中就是清了下屏然后打印了一句问候语。

entry.asm:

global begin
begin:
    jmp start_e
[section .s16]
[bits 16]
start_e:
    mov ax, cs
    mov ds, ax
    mov es, ax
    mov ss, ax

    lgdt [GdtPtr]    ;load gdt

    cli              ;disable interrupt

    in al, 0x92      ;enable A20
    or al, 0x2
    out 0x92, al
    
    mov eax, cr0     ;enable protected mode
    or al, 1
    mov cr0, eax
    jmp dword (gdt_code-GdtTable):enter_pm    ;far jump to 32 bits code
    
[section .s32]
[bits 32]
extern main
enter_pm:
    mov ax, 16
    mov ds, ax
    mov es, ax
    mov ss, ax
    mov ax, 0
    mov fs, ax
    mov gs, ax

    call main

[section .gdt]
GdtTable:
    dd 0
    dd 0
gdt_code:
    dw 0xffff
    dw 0x0000
    db 0x00
    db 0x9a
    db 0xcf
    db 0x00
gdt_data:
    dw 0xffff
    dw 0x0000
    db 0x00
    db 0x92
    db 0xcf
    db 0x00

GdtPtr:
    dw GdtPtr-GdtTable-1
    dd GdtTable




main.c

void main()
{
    char *str = "hello world from C";
    char *p = (char *)(0xb8000);
    int i;
    for (i = 0; i < 25 * 80; i ++, p += 2)
        *p = ' ';
    for (p = (char *)(0xb8000); *str; str ++, p += 2)
        *p = *str;
    while (1);
}


最后附带一个Makefile

kernel.bin:entry.o main.o
	ld entry.o main.o -o kernel.bin -Ttext 0x7e00 -e begin --oformat binary
entry.o:entry.asm
	nasm -f elf entry.asm -o entry.o
main.o:main.c
	gcc -c main.c
cp:
	sudo cp kernel.bin /media/tao/
u:
	sudo umount /media/tao/
m:
	sudo mount /dev/sdb1 /media/tao/
run:
	sudo qemu -hda /dev/sdb -boot c
clean:
	rm *.o *.bin

从Makefile中可以看出,每次得到kernel.bin后,先把它复制到U盘,然后要卸载U盘再挂载,然后使用qemu测试才能得到结果,如过不卸载再挂载,qemu运行的结果是上一次生成的kernel.bin。可能是因为操作系统没有及时写盘,所以要卸载一下。当然这个U盘也可以拿到真机上直接启动。


(完)

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值