使用makefile文件
那么用批命令来编译程序是不是就够用了?还不够,这里我们要使用批命令+makefile文件方法。
首先介绍如何写makefile。
打开notepad++写入以下内容:
######################
#声明要编译的所有组成,这里的ya是本工程名称,可以取任何名字,这里就用ya
######################
ya:out/Helloworld.exe out/first.com
#开始对各部分编译
out/Helloworld.exe:code/Helloworld.c
gcc code/Helloworld.c -o out/Helloworld.exe
out/first.com:code/first.asm
nasm code/first.asm -o out/first.com
######################
注意倒数一、三行前面的空格是按Tab键,否则出错:makefile:7: *** missing separator. stop
将此文件保存在D:\GX\ya目录下,文件名就是Makefile
将djgpp.bat的内容改为:djredir -o 30.txt -eo make
双击djgpp.bat编译。
现在的目录结构是这样:
boot程序
看看有名的boot程序
电脑开始启动后,首先在磁盘0号磁道0号扇区处寻找boot程序,这个程序只有512字节大小,如果是以0x55,0xAA两个字节结尾,电脑将认为这是一个boot程序,然后电脑将这个程序读入内存地址0xfc00:0000处开始执行。
一个最简单的boot.asm
[BITS16] ;;编译成16位的指令
[ORG0x7C00]
po:
jmp po
times510-($-$$)db0
db0x55
db0xAA
将它保存到code目录下
我们分析一下这个程序
这个程序什么都没有做,但是有完整的boot格式,以0x55,0xAA两个字节结尾,那么程序还有510个字节。
$表示本指令地址 $$表示程序开始地址
times 510-($-$$) db 0 就是填入510-(本指令地址-程序开始地址)这么多个0
我们给它中间加点语句,让它在屏幕上显示Hello World
[BITS16]
[ORG0x7c00]
jmp main
ns db0x48,0x65,0x6C,0x6C,0x6F,0x20,0x77,0x6F,0x72,0x6C,0x64,0x21, ;这几个是hello world!的ASCII码
main:
movax,cs
movds,ax
moves,ax
movcx,12 ;循环12次
movbx,0 ;从数组[0]开始
movah,0eh
next:
moval,[ns+bx]
int10h
incbx
deccx
jnz next ;cx不为0则继续
po:
jmp po
times510-($-$$)db0
db0x55
db0xAA
这里我们生成可执行文件格式
nasm code/boot.asm -o out/boot.bin
相应的修改makefile文件,这个大家会了吧,使用批命令+makefile生成boot.bin
下面我们将在虚拟机上模拟boot文件从软盘上启动的效果。
安装bochs虚拟机
Bochs可以实现虚拟软盘、虚拟硬盘、显示器、网卡等硬件模拟。模拟网卡就是我们首先要实现的目标。bochs的一个最大好处就是可以调试程序。
从Bochs官网http://sourceforge.net/projects/bochs/files/bochs/2.6.8/下载Bochs2.6.8.exe,这个就是为win32准备的。
安装到D:\GX目录下。生成目录D:\GX\Bochs-2.6.8
在Bochs-2.6.8目录下新建myos目录,这个目录将是我们模拟软盘文件所在地方。
运行bochs需要自己编辑两个程序:bat文件及bochsrc.bxrc,前者是批处理命令,后者是bochs要求的配置文件
1.bat文件内容:
cd "D:\GX\Bochs-2.6.8\myos"
..\bochs -q -f bochsrc.bxrc
放在D:\GX\Bochs-2.6.8\myos目录下。
bochsrc.bxrc内容:
###############################################################
# bochsrc.txt file for flopy image.
###############################################################
# how much memory the emulated machine will have
megs: 32
# filename of ROM images
romimage: file=../BIOS-bochs-latest
vgaromimage: file=../VGABIOS-lgpl-latest
# what disk images will be used
floppya: 1_44=a.img, status=inserted
# choose the boot disk.
boot: a
# where do we send log messages?
log: bochsout.txt
# the mouse
mouse: enabled=0
从bochs运行第一个启动盘
准备
安装完成后在它的目录中找到一个名为bximage.exe的程序,这个程序可以为我们创建磁盘镜像文件。
运行bximage.exe,它为我们创建了一个名为a.img的文件,这个文件是一张容量为1.44m的软盘的镜像。
please choose one 输入1
第二个问题输入fd
第三个、第四个问题直接回车
生成一个a.img软盘镜像,将a.img放到D:\GX\Bochs-2.6.8\myos目录下。
用磁盘编辑工具查看a.img内容,为1474560字节,全是0。
现在的目录结构是这样:
前面已经编译了一个名为boot.bin的文件,大小正好为512字节。
用磁盘编辑工具打开这个文件,最后两个字节为55和aa。
然后,用磁盘编辑工具编辑器打开a.img,把boot.bin中的内容全部复制到其中。按下面方法:
(在boot.bin里)编辑——全部复制——标准。然后光标放在a.img的00处,最左上角,编辑——剪贴板数据——写入(W)
剪贴板数据将写入在偏移量0——确定
这样就覆盖了前512字节——0x000到0x1ff字节。总字节数仍然是1474560字节。
插入数据后的a.img:
文件——保存
OK,我们的启动软盘就制作好了。双击D:\GX\Bochs-2.6.8\myos目录下的1.bat,启动盘运行并显示Hello world!
bochs调试方法
建立一个批命令文件2.bat,内容:
cd "D:\GX\Bochs-2.6.8\myos"
..\bochsdbg -q -f bochsrc.bxrc
放在D:\GX\Bochs-2.6.8\myos目录下。
(使用bochsdbg.exe 将bochsdbg文件复制到img与bochsrc.bxrc所在文件夹中,双击bochsdbg 这个办法在同时安装2.6与2.6.8时会有冲突,选择使用批命令方法)选择start->c
b 设置断点
比如:b 0x7c00 在0x7c00处设置断点
c continue 命令运行到断点
s step 单步执行
p 跳过这一步
比如运行到call .55 而你不想查看这个子程序的运行,直接用p + 回车就可以跳过。
h help
s n 执行n步
u /20 在当前语句下反汇编20句
u /20 0x7c00 :反汇编内存0x7c00处,反汇编的长度是20
x /20 0x7c00: 以16进制的形式从内存的0x7c00开始显示20个字的数据
r 显示寄存器值
新建目录:D:\GX\ya\final 这里用来存放我们软盘的映像文件a.img
辅助工具
为了得到a.img我们要运行bximage.exe,然后用磁盘编辑工具打开复制数据,在开发过程中会多次重复这些过程,每次都要手工操作很麻烦,下面编写几个辅助工具代替手工操作
辅助工具一:creat_img.c 用来生成映像文件a.img
#include <iostream>
#include <stdio.h>
#include <string>
#include<stdlib.h>
//argv[1]=目标文件创立镜像文件, 内容全为0,大小1474560
using namespace std;
int main (int argc,char* argv[])
{
cout<<"reference argc is "<< argc << endl;
int i;
for(i =1; i <= argc; i++)
{
printf("argv[%d]=%s\n",i,argv[i]);
}
FILE *tof;
tof = fopen( argv[1],"rb"); //只读方式打开一个binary目标文件
if( tof ==NULL) // 第一次需创建目标文件
{
printf("\nerror on open file,will creat new file\n");
tof = fopen (argv[1],"wb");
fseek(tof,1474559, SEEK_SET);//相当于写入那么多0
putc(0, tof); // 写第1474560字节,1.44M软盘大小
}
printf("done\n");
fclose(tof); //关文件禁止读写才能对文件删除及改名
system("pause"); //为避免运行窗口关闭以便看清内容
return0;
}
把creat_img.c文件放在code目录里。
相应的须修改makefile文件:
######################
#声明要编译的所有组成,这里的ya是本工程名称,可以取任何名字,这里就用ya
######################
ya:out/boot.bin out/creat_img.exe A
#开始对各部分编译,注意不是空格是Tab键
out/boot.bin:code/boot.asm
nasm code/boot.asm -o out/boot.bin
# 制作内核映象文件
out/creat_img.exe:code/creat_img.c
gpp code/creat_img.c -o out/creat_img.exe
# 执行dos命令,在final目录下生成a.img文件
A:
out/creat_img.exe final/a.img
######################
运行djgpp.bat,可以看到在final目录下生成了一个叫a.img、大小为1.44M的空白文件,目前a.img可以认为是一个空白盘,还不能起引导作用。
辅助工具二:write_in_img.c 用来向a.img中写入数据。
#include <iostream>
#include <stdio.h>
#include <string>
#include <stdlib.h>
//argv[1]=目标文件 argv[2]=源文件 argv[3]=写入偏移量 在DOS下用法: write.exe a.img kernelloader.bin 512
using namespace std;
long get_file_size( FILE *fp )
{
if( fp ==NULL)return-1;
fseek(fp,0L, SEEK_END );
return ftell( fp );
}
int main (int argc,char* argv[])
{
int i;
int ch;
int k = atoi(argv[3]); // 转换得到写入偏移量
FILE *fromf,*tof,*temp;
fromf = fopen( argv[2],"rb"); //只读方式打开一个binary源文件
tof = fopen( argv[1],"rb"); //只读方式打开一个binary目标文件
if( tof ==NULL) // 第一次需创建目标文件
{
printf("\nerror on open file,use creat_img.exe to creat img file\n");
exit(0);
}
temp = fopen("final\\t.img","ab"); //追加方式打开中间文件,如不存在就新建
fseek(tof,0L, SEEK_SET ); //从新定位到文件开头
for(int i =0; i < k; i++) // 保留目标文件第一部分
{
ch = getc( tof );
putc(ch, temp);
}
long ja = get_file_size( fromf );
fseek(fromf,0L, SEEK_SET ); //定位到源文件开头
for(int i =0; i < ja; i++) // 复制文件第二部分
{
ch = getc( fromf );
putc(ch, temp);
}
fseek ( tof, ja+k, SEEK_SET ); //指针指向从文件头开始ja+K个字节
ch = getc( tof );
i =0;
while( ch != EOF ) // 复制最后的第三部分
{
putc(ch, temp); // 写入文件
ch = getc( tof );
i++;
}
fclose(tof); //关文件禁止读写才能对文件删除及改名
fclose(fromf);
fclose(temp);
remove(argv[1]);
rename("final\\t.img",argv[1]);
return0;
}
把write_in_img.c文件放在code目录里。
相应的须修改makefile文件:
######################
#声明要编译的所有组成,这里的ya是本工程名称,可以取任何名字,这里就用ya
######################
ya:out/boot.bin out/creat_img.exe out/write_in_img.exe A B
#开始对各部分编译,注意不是空格是Tab键
out/boot.bin:code/boot.asm
nasm code/boot.asm -o out/boot.bin
# 制作内核映象文件
out/creat_img.exe:code/creat_img.c
gpp code/creat_img.c -o out/creat_img.exe
# 执行dos命令,在final目录下生成a.img文件
A:
out/creat_img.exe final/a.img
# 写入文件,argv[1]=目标文件 argv[2]=源文件 argv[3]=写入偏移量
#在DOS下用法: write.exe a.img boot.bin 0
out/write_in_img.exe:code/write_in_img.c
gpp code/write_in_img.c -o out/write_in_img.exe
# 执行dos命令,向a.img写入代码,内容是boot.bin
#写入磁盘位置从0偏移量起始,占1个扇区512字节
B:
out/write_in_img.exe final/a.img out/boot.bin 0
######################
运行djgpp.bat,现在final目录下生成了a.img,不再是一个空白盘,而是一个启动盘。将a.img复制到D:\gx\Bochs-2.6.8\myos目录下,双击1.bat,是不是显示Hello world?说明这两个辅助工具起作用了,免去了我们的手工操作。
运行djgpp.bat实际上是运行了makefile文件,出现DOS窗口后需要按回车键才能关闭DOS窗口。