到这里我们终于可以开始动手使用机器码写程序了
不过在此之前,我们还需要做一些准备。
因为我们选择将hello world写入mbr中,而这样会覆盖掉引导信息。如果你直接在你的硬盘上操作的话,你的系统就不能启动了(尽管我们可以使用修复软件修复引导记录,但是那样很麻烦)
你需要的仅仅是一个u盘,或者储存卡+读卡器(这里便使用的储存卡)
然后是一台支持usb启动的计算机
【我怎么知道支不支持?】
只要你不是老掉牙的几十年前的计算机,都是支持的
【我们u盘里有很多文件啊,弄了这个会不会造成数据丢失啊?】
不会!我们只会修改前面引导程序的部分,不会修改后面的分区记录,所以保存的数据是不会丢失的。如果你不放心可以先备份
当然,如果你的u盘是“引导盘”,比如xxxPE装机的,由于修改了引导程序,自然就会失效……所以这类u盘谨慎尝试(大不了再制作一次嘛,或者在写程序时进行扇区备份)
如果你不想用u盘折腾,那也行。虚拟机的虚拟磁盘一样是支持的
至少vhd,vmdk之类的格式都是支持的。
我们需要对扇区直接进行操作,这需要专门的软件,但是我找了一圈,有两种编辑器,一种是好用的,一种是不好用的,在好用的里面却只有一种,那就是死贵的!
winhex貌似可以免费试用,但是界面是英文的,diskgenius是中文的,可是目前最新版不交钱不能编辑扇区
好在貌似diskgenius以前的版本是可以免费编辑扇区的,而我以前下载过,今天又从硬盘中把它翻了出来,你可以“
点我下载”
下载后直接运行就是了,你会得到这样的一个界面
插上u盘,然后选择你要写程序的硬盘(注意是硬盘不是分区)
在卷标的下面有一个“扇区编辑”,点一下,就会成这个样子
如果你选择使用虚拟机
请创建一个固定大小的单文件磁盘,不需要多大,10MB都够用了!,内存更不用说,1M足矣!操作系统选择other(其他),并且不需要添加镜像!
然后在diskgenius里面的菜单“硬盘”->“打开虚拟硬盘文件”,选择你的虚拟磁盘文件,后面的操作都是一样的!
注意右边的“Error loading operating system”是不是有些熟悉呢?没错,你选择了没有引导记录的扇区就会显示这个,其实这样的错误信息也是MBR程序显示出来的。
往下拉一点,你应该看得到“55AA”,这就是传说中的校验位,也就是512字节的地方。
现在回到最开始的地方,我们开始写程序!
注意:如果你不想手动输入而是复制机器码,请复制后右键->写入->写入hex即可
机器码可以通过
XXXXXXXXXXXXXXXXXXXXt/XXXXXXXXml#xF3查询
这里也提供一个pdf版的inter开发文档
点我下载
[hr]我们先来整理一下思路
为了便于操作显存,我们将显存从0xb8000开始作为一个段,也就是
0xb800:0x0000,将它放入段寄存器ES(其实DS也行,不过后面DS有其他的用途)
完成这一步的机器码是:
B8 00 B8,8E C0
像这样。
B8是mov指令,它将0xb800放入ax寄存器(因为是小端模式,00是低位,放在高位上)
然后我们将ax的数据送入ES寄存器,8E是mov指令(mov有好多不同的指令……),C0则代表AX。
然后,我们将从把屏幕“清屏”,就是将屏幕用黑底的空格填充,空格的ascii是0x20,黑底则是0x07。放在一起就是0x0720(小端)
我们使用循环填充空格的方法,需要在cx寄存器中放入循环的次数2000次
它是:
B9 D0 07(2000的15进制是0x07D0)
B9=B8+1,1说明目标是CX(ax是+0,所以还是B8)
然后我们将0放到bx中当作偏移地址(这里有点像c语言中的数组,第一个元素的地址就是段地址,下标则是偏移地址)
机器码为
BB 00 00
BB=B8+3,3就是BX,后面四个0不说你都知道。
接下来就是循环体了:
26 C7 07 20 07
26是说明以ES作为段寄存器,前面我们已经将他改成了0xB800。
C7是把立即数放入内存的mov指令,07代表用BX提供偏移地址,2007则是数据
然后是
81 C3 02 00
81是add指令,c3代表BX,0200就是0x02,说明这里将bx自增了2。因为上面一次性传送了2字节,所以偏移地址也要往后移动两字节
循环体到此结束,下面是
E2 F5,
E2是LOOP指令,后面的0xF5则是偏移地址,他是1Byte的有符号数,负数说明往前退,F5就会退到前面26 C7 07的地方,每执行一次,CX自动减1,直到cx为0。也就是说,会执行2000次。
接下来我们将为把字符串写入显存做准备
因为指令和字符串这样的数据是放在一起的(这种指令和数据放在一起的叫做冯诺依曼架构,还有一种数据和指令分别放在不同内存上的叫做哈弗架构。一般的计算机都是冯诺依曼架构,而CPU内的Catch则是哈弗架构)
为了将字符串写入到显存,我们需要知道字符串的位置
我们把程序加载开始的部分当作段地址,并且将它放入DS
B8 C0 07,结合前面的你应该知道这将0x07c0放入了ax,然后是8E D8
他将AX的值放入DS中
接着是
FC,这是cld指令,因为我们后面需要传送字符串,因此需要将它清0,指定字符串的传送方向。
后面是
BE 2A 00BF 00 00
这都是传送指令,BE是传送到SI,BF则是传送到DI,SI里面放的是字符串的偏移量,DI里面放的是显存中的偏移量,因为我们是从第一个字符开始写,因此偏移量就是0.
最后,我们需要再次指定循环的次数
B9 0C 00,他将hello world的长度放入cx,
0x0C是12个,即“Hello World!”一共十二个字符
倒数第二步,我们将字符串一个个的循环传送到显存,机器码为
F3 5A
F3 A5代码字符串传送,就是rep movs。Move (E)CX words from DS:[(E)SI] to ES:[(E)DI].。他会一直执行到CX为0,也就是把hello world全部写入显存。
最后一步。cpu在执行完了上面步骤后并不会停下来,如果你不进行干预的话他会一直执行下去。这不是我们希望看到的结果,因此我们需要让cpu有事情干——死循环!
死循环的机器码为
FD FF。其中FD是jmp指令,FF表示往前退1字节,也就是FD的位置。这样cpu就会不停的在这一句上跳转,也就是死循环了
程序到这里就算完了
【喂喂喂,我们怎么没有看到hello world字符串啊!】
急什么,这不是留在后面来设计吗?
前面已经详细的说了显存中显示字符的方法,这里就直接给出字符串了
48 8F 65 8F 6C 8F 6C 8F 6F 8F 20 07 57 02 6F 02 72 02 6C 02 64 02 21 02
我个人喜欢让hello 闪烁,然后world用绿色显示出来,黑底白字闪烁的属性是8F(可以看到前面每隔一个字符就是一个8F),黑底绿色不闪烁则是02
48 65 6c等则是hello world的ASCII
写到这里,我们的程序就算彻底搞定了
到这里你就应该明白DI的值为什么是2A了,你可以数数,字符串的第一个字节的偏移地址正是0x2A(以程序存放的地址作为段)
下面给出本程序完整的机器码
B800B88EC0B9D007BB000026C707200781C30200E2F5B8C0078ED8FCBE2A00BF0000B90C00F3A5E9FDFF488F658F6C8F6C8F6F8F200757026F0272026C0264022102
你也可以
下载这个包含机器码的txt
写好了后是这个样子
红色代表数据被修改了,中间那个BE只是恰好和原来的数据一样罢了
最后点击“扇区编辑”左下边的保存即可
运行程序
搞了这么久,终于可以运行我们的程序了。
对使用u盘的等引导的人
首先你需要关闭计算机,别拔了u盘,然后再开机,刚刚出画面时根据提示按相关的按键。通常是F2,del,esc等等,总之你需要选择引导的磁盘(不清楚的可以百度,或者回复问我)
选择U盘启动,然后略等一下!就是见证奇迹的时刻!
对于使用虚拟机的人
你只需要启动电源,然后就可以看结果了!