本文阐述在计算机引导程序中可以用来复制内核代码的程序,平台:centos,语言:AT&T格式的x86汇编,虚拟机:bochs,编译器:gcc。
设定内核代码紧跟在512字节的boot程序后面
利用bios复制程序:
movw $0x9000,%ax //将要写入的位置的段地址存入es
movw %ax,%es //由于没有直接将操作数存入es的指令,所以需要通过ax中转
movb $0,%ch //ch存入要读取的柱面,这里是第一个柱面
movb $0,%dh //dh存入磁头号,在软盘中,如果是双面软盘,有两个磁头0号和1号,在这里,内核程序处于0号磁头的读取范围
movb $2,%cl //cl存入扇区号,在软盘中,一个扇区是256字节,boot程序占了软盘的前512字节,即0和1扇区。内核紧跟在boot之后,所以这里将扇区2存入cl
movb $0x02,%ah //ah存入要调用的功能,功能0x2为读操作
movb $1,%al //al存入要读取的扇区数,这里假设内核的大小只有1个扇区(即256字节,但一般是不可能这么小的)
movw $0,%bx //bx为待写入的偏移地址,es:bx共同指向了要写入的地址
movb $0x00,%dl //dl存入驱动器号,软盘为0x0~0x7f,硬盘为0x80~0xff
int $0x13 //使用int指令调用13号软中断
以上代码完成了从软盘(或硬盘)读入数据到内存,但要使读入的程序可以工作,就要一定要记得设置数据段寄存器,然后再跳转到执行的地址:
movw $0x9000,%ax
movw %ax,%ds //使用ax对数据段寄存器操作
movw $0,%bx //ds:bx共同指向数据段
ljmp $0x9000,$0 //段间跳转要使用长跳转指令,ljmp的作用就相当于修改cs:ip(cs为代码段寄存器),使其指向下一个要执行的指令,同时我们也可以推测出jmp指令就是修改指令指针寄存器ip
完整程序如下,共分为2个部分,boot.s和kernel.s
boot.s:
.code16
jmp _start
//这里储存要显示的信息
.section .data
warnning:
.ascii "Load the kernel!\n\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\0"
.section .text
.globl _start
//fat12格式,这一步可以不用设定
.byte 0x90
.ascii "HARIBOTE"
.word 512
.byte 1
.word 1
.byte 2
.word 224
.word 2880
.byte 0xf0
.word 9,18,2
.int 0,2880
.byte 0,0,0x29
.int 0xffffffff
.ascii "HARIBOTEOS "
.ascii "FAT12 "
.int 0,0,0,0
.byte 0,0
_start:
//关中断
cli
cld
//设置段寄存器
xorw %ax,%ax
movw %ax,%es
movw %ax,%ss
movw %ax,%ds
//加载内核至0x90000
movw $0x9000,%ax
movw %ax,%es
movb $0,%ch
movb $0,%dh
movb $2,%cl
movb $0x02,%ah
movb $1,%al
movw $0,%bx
movb $0x00,%dl
int $0x13
pushw $warnning |
| //提示用户正在加载内核 |
call print
//设定数据段
movw $0x9000,%ax
movw %ax,%ds
movw $0,%bx
//跳转到0x90000处
ljmp $0x9000,$0
fin:
hlt
jmp fin
/**********************调用的函数************************/
//显示一段字符串,使用pushw推入字符串首地址
.type print,@function
print:
.code16
pushw %bp
movw %sp,%bp
movw 4(%bp),%si
putloop:
movb (%si),%al
cmp $0,%al
je exit
movb $0x0e,%ah
movw $15,%bx
int $0x10
incw %si
jmp putloop
exit:
movw %bp,%sp
popw %bp
ret
kernel.s:
//这部分代码将被放到镜像的前512字节之后
.code16
.section .data
msg:
.ascii "\nhello,lindorx\b\b\b\b\b\b\b\b\b\b\b\b\b\n\0"
.section .text
.globl entry
entry:
pushw $msg
call print
spin:
hlt
jmp spin
/**********************调用的函数************************/
//显示一段字符
.type print,@function
print:
.code16
pushw %bp
movw %sp,%bp
movw 4(%bp),%si
putloop:
movb (%si),%al
cmp $0,%al
je fin
movb $0x0e,%ah
movw $15,%bx
int $0x10
incw %si
jmp putloop
fin:
movw %bp,%sp
popw %bp
ret
makefile部分:
F1=boot.o boot.out kernel.o kernel.out
F2=lindorx.img boot kernel
IMAGES=lindorx.img
KERNEL_SIZE=2799
SIZE=2880
DD_BS=512
#boot为引导部分,kernel为主要的内核程序,这段代码创建一个空白镜像文件,然后写入引导和内核
$(IMAGES):boot kernel
dd if=/dev/zero of=$(IMAGES) bs=$(DD_BS) count=$(SIZE)
dd if=boot of=$(IMAGES) conv=notrunc
dd if=kernel of=$(IMAGES) conv=notrunc bs=$(DD_BS) count=$(KERNEL_SIZE) skip=0 seek=1
#本段代码截取出编译好的引导程序中的引导程序部分,扩充为512字节,并将最后两字节设置为AA55,生成引导文件boot
boot:boot.out sign.pl
objcopy -S -O binary -j .text -j .data boot.out boot.bin
cp boot.bin boot
perl sign.pl boot
-rm -rf boot.bin
#将编译好的程序中的内核程序截取出来,生成文件kernel
kernel:kernel.out
objcopy -S -O binary -j .text -j .data kernel.out kernel
#dd if=/dev/zero of=kernel bs=$(DD_BS) count=$(KERNEL_SIZE)
#dd if=kernel.bin of= kernel conv=notrunc
#链接引导程序中间文件
boot.out:boot.o
ld -m elf_i386 -N -e _start -Ttext 0x7c00 -o boot.out boot.o
#链接内核程序中间文件
kernel.out:kernel.o
ld -m elf_i386 -N -e entry -Ttext 0 -o kernel.out kernel.o
#编译所有汇编程序(.s文件),生成中间文件(.o文件)
%.o:%.s
gcc -nostdinc -fno-stack-protector -fno-tree-ch -Wall -Wno-format -Wno-unused -Werror -gstabs -m32 -fno-omit-frame-pointer -DJOS_KERNEL -gstabs -c -o $@ $<
#调用qemu测试镜像
qemu:$(IMAGES)
qemu-system-i386 $(IMAGES)
bochs:$(IMAGES)
bochs -f bochsrc
#clean用来清除所有中间文件
clean:
-rm -rf $(F1)
#clean-all用来清除所有中间文件及二进制引导程序,内核程序,内核镜像
clean-all:
-rm -rf $(F1) $(F2)
生成时需要用到的文件sign.pl(来自mit6.828课程):
#!/usr/bin/perl
open(BB, $ARGV[0]) || die "open $ARGV[0]: $!";
binmode BB;
my $buf;
read(BB, $buf, 1000);
$n = length($buf);
if($n > 510){
print STDERR "boot block too large: $n bytes (max 510)\n";
exit 1;
}
print STDERR "boot block is $n bytes (max 510)\n";
$buf .= "\0" x (510-$n);
$buf .= "\x55\xAA";
open(BB, ">$ARGV[0]") || die "open >$ARGV[0]: $!";
binmode BB;
print BB $buf;
close BB;
结果如下: