看了赵博的《Linux内核源代码完全注释》,读了潇前辈的《操作系统引导探究》《8259A保护模式下中断编程》,受益菲浅。想起N久前看到某期开发高手上的写自己的OS的文章,不由冲动起来,我要写一个~~~~~~引导程序:)。很简单的一个引导程序,仅仅打印几个字符而已,耗费了我几个小时,学到了很多东西。
首先建立实验环境。WinXP+VMWare(linux),用VMWare自带的模拟软驱的功能做了一个软驱镜像,ok了
然后用C写个工具,把引导程序写到软盘0扇区中。并标志最后两个字节为“55 AA”
在这篇日志里我并不打算详细说明系统启动和内存分布情况,强烈推荐《操作系统引导探究》,顺便勘误,图十中的引导区应为0x07c00到0x07dff,而原图误为到0x07cff 。同时补充一点,在IBM PC机中,中断向量表中每个表项占4字节,前两字节是中断处理程序段地址,另两字节存储偏移地址。另,参考了唐朔飞老师《计算机组成原理》一书,在硬向量方式中,IBM PC机中的中断表和潇前辈提到的JMP式中断表是很普遍的两种方式。只不过PC机采用前者(清华大学《IBM-PC汇编语言程序设计》)
Ok了,进入正题。先把C代码贴出来。写入引导扇区的工具
/* the program is used to write a boot program to the boot section of
the floopy
*/
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
#include<stdio.h>
main(int argc,char* argv[])
{
int bootp,flp; /* the bootp point to the boot program,flp point to the flooy */
char buf[512]; /* data buffer */
if(argc==1)
{
printf(“Use the command like this: writetoboot bootfilenamen”);
return;
}
if((bootp=open(argv[1],O_RDONLY))==-1)
printf(“Boot file not existn”);
read(bootp,buf,510);
close(bootp);
buf[511]=0×55;
buf[512]=0xaa;
if((flp=open(“/dev/fd0″,O_WRONLY))==-1)
printf(“Read the floopy driver error,please check your diskn”);
write(flp,buf,512);
close(flp);
}
这个程序应该不难理解,主要运用了linux系统调用open read write。不明白的可以man一下看看。
接着是引导程序。最早的原始程序如下:
.text
entry start
start:
mov ax,cs !ffmsg是显示的欢迎信息,这个字符串放在了.text段里面。
mov es,ax
mov cx,#20
mov bp,#ffmsg
mov bx,#0×0007
mov ax,#0×1301
int 0×10
loop1:
jmp loop1
ffmsg:
.byte 13,10
.ascii “YY,I love you…”
.byte 13,10
用这个作为引导程序,结果是能引导(0扇区结尾是55 AA的都能引导-_-b),但是显示不出文字。这个程序主要是利用了0×10号bios中断调用。显示字符串。其中字符串地址为es:bp 而这个程序之所以错就是es段寄存器值错误。我原来以为ffmsg既然定义在.text段里,那么其段基址肯定就是cs代码段寄存器的值了,偏移地址可以通过ffmsg本身的名字获得,把cs赋给es就ok了。这样想的错误在于弄混了概念。程序中的段与段寄存器不是那么直观地对应的。
cs段寄存器和IP程序基数器联用,确定下一条指令的位置。但cs不一定指向存储这个程序的第一个内存单元。因此把cs的值赋给es的值显然是错误的。ffmsg只能表示待显示字符串首字符的偏移地址,在es地址错误的情况下。整体就错误了。
在这个基础上修改程序。很简单,系统引导的时候,引导程序会被加载到物理地址0x07c00处,所以把刚才的mov ax,cs指令换为mov ax,#0x07c00就成功了。这时es段寄存器指向了这个程序的首地址,bp指向正确的偏移地址。所以显示成功。
那么进一步考虑。CS寄存器的地址是多少呢?写程序验证
.text
entry start
start:
! yes,it works,but I want to see what is the value of CS
mov ax,#0x07c0
mov es,ax
mov cx,#20
mov bp,#ffmsg
mov bx,#0×0007
mov ax,#0×1301
int 0×10
mov dx,cs !从这开始,把cs段的值赋给dx寄存器。然后显示
add dh,#65
add dl,#65
mov ax,#0xb800
mov es,ax
seg es
mov [0],dh
seg es
mov [1],#0x1f
seg es
mov [2],dl
seg es
mov [3],#0x1f
loop1:
jmp loop1
ffmsg:
.byte 13,10
.ascii “YY,I love you…”
.byte 13,10
把cs段的值赋给dx寄存器。分别把低16位(dl)与高16位(dh)加上65(字符A的ascii码),然后送往显存(0xb8000处开始),打印出来,0x1f显示属性表示蓝底白字。这时候引导我们可以很清楚的看到,打印了两个A字符,说明cs段值为0。现在可以充分证明最早程序的错误所在。再进一步可以打印出IP的值,IP寄存器不可以直接访问,不过我们可以利用call指令使IP入栈的特性来取得IP。如下所示
.text
entry start
start:
! yes,it works,but I want to see what is the value of CS and IP
mov ax,#0x07c0
mov es,ax
mov cx,#20
mov bp,#ffmsg
mov bx,#0×0007
mov ax,#0×1301
int 0×10
mov dx,cs
add dh,#65
add dl,#65
mov ax,#0×9000 !把IP的值赋给BX寄存器
mov ss,ax !Set the stack sec pointer
call next1
next1:
pop bx !bx is the value of IP reg
mov ax,#0xb800
mov es,ax
seg es
mov [0],bh
seg es
mov [1],#0x1f
seg es
mov [2],bl
seg es
mov [3],#0x1f
loop1:
jmp loop1
ffmsg:
.byte 13,10
.ascii “YY,I love you…”
.byte 13,10
这次分别打印出bx寄存器的高位和低位,可以发现打印出了’|’与’#’两个字符,查一查对应的ascii码表,可以看出执行到next1时ip寄存器值为0x7c23。说明我们程序确实是从0x7c00处开始执行的。