首页 > 编程知识 正文

balloon的音标是什么(tmod=0x01)

时间:2023-05-06 09:03:10 阅读:105575 作者:902

在0x00文章中,我们提到了引导扇区的一个重要细节:主引导签名。

在这篇文章中,我们不要急于开始写作。让我们了解一下我们可以使用的内存空间,以及电脑刚启动后的内存分配。

在启动开始时,计算机将进入真实模式。在这种模式下,我们可以访问的内存空间只有1M(并且只能执行16位程序)。如果我们想实现一个可以访问4GB内存的32位系统,我们必须进入保护模式(64位系统应该进一步进入长模式)。

在真实模式下,内存分配如下:

1.0x ffff 0-0x ffff BIOS入口地址。CPU初始化后,将CS:IP设置为0xffff0,直接跳转到这个入口位置,开始执行这里存储的第一条指令0xea5be000f0,即jmp0xf000:0xe05b,跳转到物理地址(0xf0004)0xe05b=0xfe05b,这是一个自检程序。

2.0xf0000-0xffff BIOS程序范围。请注意!0x ffff 0-0x ffff也属于BIOS程序的范围。所以,其实BIOS程序范围是0xf0000-0xfffff。

3.0xc 8000-0x efff映射硬件适配器的ROM。

4.0xc0000-0xc7ff显示适配器BIOS。

5.0xb 8000-0xffff文本模式显示适配器空间。

6.0xb0000-0xb7fff黑白显示适配器空间。

7.0xa0000-0xffff色彩适配器空间。之后,我们将通过设置vga 13h模式来使用这个空间,并直接向这个空间写入8位,相应的显示像素将被修改。这就是我们如何实现最基本的展示。当然以上两个显示适配器空间也可以用来显示,不过我没有深究,有兴趣的话可以理解。

8.0 x9fc 00-0x9 ffff扩展BIOS数据区。

9.0x07e 00-0x9bff可用面积(约608kb)。进入引导加载程序后,我们将主引导程序(512字节)整体移动到0x90000,然后加载后来写入0x90200的加载程序,将操作系统从存储设备中取出并加载到内存中,操作系统的主要部分也会加载到这个空间(0x10000~0x8ffff)。

10.0x07c00-0x07dff启动程序。本文接下来描述的boot section . s编译的引导扇区将被加载到这个空间中运行。

11.0x00500-0x07bff可用面积(约30kb)。

12.0x00400-0x004ff BIOS数据区。

13.0x00000-0x003ff中断矢量表。中断向量表是一个非常重要的结构,存储在这部分的数据直接链接到int指令。跳转地址存储在表中。当我们第一次进入系统时,我们需要BIOS提供的一些基本功能来帮助初始化和加载系统。这时候我们会用到int指令,比如int $0x10,这意味着我们调用BIOS的视频服务。此时CPU会从中断向量表中找到0x10中断,取出地址并跳转,转移到BIOS提供的程序中执行,执行后返回。这个向量表在进入保护模式后会被我们的操作系统覆盖,但之后我们还是需要构造自己的中断向量表。

让我们开始写短靴吧。如果你第一次接触ATT集会,不要害怕。下面的程序会一一解释。

首先是开场白部分。

Code16意味着告诉汇编程序将这段代码编译成16位程序。后来写head.s的时候,会用到32位的程序,所以写。开头的代码32。global bootstart定义了全局符号,对于外部程序来说是可见的,所以在链接的时候,如果链接器在其他程序中发现了一个未定义的符号,但是发现这里有一个和global同名的符号,它就会链接。全局数据贝格、数据端、BSS贝格、bssend、textbeg、textend

#这也是全球符号的定义。下面是细分部分。文本

textbeg:数据

database :英国标准规格

Bssbeg:text,data, bss. BSS分别表示代码段、数据段和未初始化的数据段,三个xwdhm在同一个地址范围内,这意味着这个程序实际上是没有分段的。

接下来是常量定义部分。

那个。文本#文本段从这里开始。equaBOOTSEG,0x07c0。eqa用于定义常量,其中bootseg定义为0x07c0,我们稍后将通过ljmp指令将CS寄存器设置为该值。有人会问为什么不是0x07c00,这就不得不说说i386的寻址方式了:

I386在实模式(16位)下,寻址是指通过CS:IP的实际物理地址,所以当CS=0x07c0且IP=0x0000时,实际物理地址为:

(0x07c04)0x0000=0x07c00,这是开始加载主引导的地址。equ INITSEG,0x9000

#这是我们以后整体移动主引导程序的地方,CS:IP=0x90000。equ SETUPSEG,0x9020

# setup.s程序将被

载入到CS:IP=0x90200处, # 也就是主引导搬迁之后0x901ff的后面一个字节开始处 .equ SYSSEG, 0x1000 # 操作系统主要程序被加载到0x10000处,当然 # 这个时候我们是没有写操作系统主程序的,这个只是提前写一下 # 毕竟我们要在写之前先规划好空间 .equ SYSEND, 0x8000 # 这是我们操作系统程序装载的结尾地址

下面开始bootsect.s的主体程序:

ljmp $BOOTSEG,$bootstart # ljmp指令会同时对CS:IP赋值并且直接跳转到CS:IP指定的位置, # 这里我们就直接跳转到了0x07c0:bootstart即0x07c00+bootstart位置, # bootstart就是下面这个汇编代码的起始symbol, # 执行该指令之后,bootloader正式进入bootstart开始执行指令

在接触主程序之前,先提一句,at&t风格的汇编,例如mov %ax,%bx,这个操作是从ax寄存器取出数据,存到bx寄存器中,也就是说前面的是源,后面的是目(目的/目的地),这和intel风格的汇编是反过来的。

bootstart: mov $BOOTSEG,%ax mov %ax,%ds mov $INITSEG,%ax mov %ax,%es xor %di,%di xor %si,%si mov $0x100,%cx rep movsw ljmp $INITSEG,$stackset

bootstart这段代码就已经开始有点整人了(对于零基础的人来说)。这段代码的目的是,将0x07c00~0x07dff处这512个字节,整体搬到0x90000处。那么这是怎么做到的呢?

首先看到我们对ds和es寄存器进行了赋值。ds和es不能直接通过mov $数字,%ds这种形式来直接赋值,只能间接通过其他寄存器来赋值,所以我们使用了ax寄存器。那么ds寄存器存入了0x07c0,es寄存器存入了0x9000。

接下来两个xor指令,是对di和si寄存器进行清零操作的,xor运算可以快速置0,如果这一点不太清楚,可以搜索异或真值表,不难发现异或只有在两个输入不相同的时候才会输出1,而这里xor %di,%di是寄存器对自身进行按位异或,那结果必然是0。

接着我们将0x100存入cx寄存器,0x100即256。那么为什么我们要给cx赋值256呢?

下面就是重点了,rep指令是根据cx寄存器的值进行操作的,也就意味着cx是多少,rep指令以及后面这个指令(movsw)就执行多少次。那么movsw就要执行256次。movsw又是啥呢?

这个指令是对数据进行“搬运”,它的执行是根据ds:di和es:si来进行的,这个指令会从ds:di处取数据,存放到es:si处,并且在执行之后,自动对di和si加一。再加上movsw这个w后缀意味着一次搬运一个word,即2字节,所以循环256次之后,我们一共搬运了512个字节。

所以这下我们就清楚为什么前面要给ds和es赋值,并且置零di和si了,ds:di指向了0x07c00,es:si指向了0x90000,所以这段代码直接把0x07c00后面的512个字节整体移动到了0x90000处!

接着ljmp,跳转到了0x90000+stackset偏移量处,执行下面的这段代码。

stackset: mov %cs,%ax # ax=INITSEG mov %ax,%ds # ds=ax mov %ax,%es # es=ax mov %ax,%ss # ss=ax mov $0xff00,%sp # sp=0xff00

刚刚跳转到0x9000:stackset处,不难得到CS的值是0x9000即INITSEG,IP的值是stackset的偏移量。这时候我们初始化所有的段寄存器ds,es,ss(也可以包括其他的段寄存器),让他们一起赋值为0x9000。并且设置sp(stack pointer)为0xff00,这时候栈的基址就被设置在了es:sp=0x9000:0xff00=0x9ff00处。

start: mov $0x03,%ah # read cursor position xor %bh,%bh # set page 0 int $0x10 # BIOS video service mov $INITSEG,%ax mov %ax,%es # es:bp points to the string mov $sysmsg,%bp # set string address mov $0x1301,%ax # write string,move cursor mov $0x0007,%bx # page 0,black background/white characters mov $28,%cx # length of string int $0x10 # BIOS video service

这段代码提到了另外一个重点:BIOS中断调用。看到int $0x10了没,这就是前文中提到的中断调用,0x10就是中断号,在执行了这个指令之后,CPU会跳转到中断指定的位置执行对应的程序,然后返回到此处。由于我们现在还没有能够完全掌控各个设备,所以需要借助BIOS里面已经写好的程序,这时我们就是在调用BIOS内部给我们的程序。

int调用类似于call,但是它执行的是从中断向量表(前文提及到过)中对应中断号位置存储的函数地址,而不是非常直白的call,它有一个搜寻->跳转的过程。

那么既然调用的是已经写好的函数,那么函数必然会需要一些参数,BIOS中断调用使用的参数并不是像使用C调用函数的那种方法push到栈中的,而是由你自己设置一些需要用到的寄存器,来实现传参的。

比如第一个调用,0x10是BIOS显示服务,将ah(ax的高8位)设置为0x03,意思就是我们选择的是读取光标位置这个服务。然后我们将bh(bx高8位)设0,表示页号为0,然后调用0x10中断。0x10中断执行结束后,会反馈一些数据回来:

0x10中断0x03服务信息

这个反馈回来的ch cl dh dl值在接下来的0x10中断0x13号服务中会被部分使用到:

0x10中断0x13服务信息

那么根据这个传参要求,我们要设置es为0x9000,bp为字符串的起始地址,cx字符串长度,dh,dl起始行列(在0x03号服务中已经获得),al=1光标跟随移动,bl 0x07黑底白字(就是图中的属性),bh 0x00页号0。这就是这段代码的下半段所做的事情。sysmsg是字符串地址,在后面会提到,字符串长度28,调用一下0x10中断,此时屏幕上就会输出一行字:

最后一行就是我们输出的字符串

die: hlt jmp die # infinite loop

这是个死循环,我们的最简操作系统最后会执行到这里无限循环(操作系统本质上包含一个可以跳出的死循环)。hlt指令是在你的输入设备(键盘/鼠标)没有进行任何操作的时候,暂停CPU的运行,让CPU停下来“歇一歇”,不用一直执行jmp die操作。

sysmsg: .ascii "Starting Balloon System..." .byte 13,10

这里就是存放sysmsg字符串的位置,.ascii后面可以写一串字符串,后面跟着的.byte 13,10表示13号和10号字符,13号字符即回车,光标返回最前面,10号字符即换行,光标移到下一行。

.=510 signature: .word 0xaa55

这里是程序的点睛之笔,如果缺少这一段,那么程序是无法被识别为主引导的。.=510意思就是一直跳到偏移量为510处,跳过的部分默认填0,这时候留下两个字节的位置,让我们来设置主引导签名0x55,0xaa,这里之所以写成0xaa55,是因为i386在取数的时候为小端序,低地址存放的是数的高位,所以在写入文件的时候,0x55会被放置到前面。

.text textend: .data dataend: .bss bssend:

代码段结束。

要想让这段代码顺利跑起来,我们需要qemu-system-i386环境,并且需要一个链接脚本来保证链接器能生成正常的那512字节,最后还要将这512字节的bootsect文件装入一个虚拟软盘,这时候我们需要用到dd指令。链接脚本的代码如下:

OUTPUT_FORMAT(elf32-i386) OUTPUT_ARCH(i386) SECTIONS { .text 0x0000:{ *(.text) } /DISCARD/ : { } }

开头两句表示输出格式为elf32-i386,架构为i386

下面是想要的链接结果,将.text段放置到0x0000起点,我们的bootsect.s本来就只有.text段,所以整个代码会直接从0x0000处开始,保证了进入引导的时候能正确执行。保存为文件名ld_boot.ld。

接下来写一下Makefile

All: Image # 执行make All的时候,会直接生成Image文件 .PHONY=clean run-qemu # .PHONY表示伪目标,这里写的东西都不代表文件名, # 我们用的clean和run-qemu都仅仅是操作 bootsect:bootsect.s ld_hxsdl .text bootsect # 冒号后面跟着的是生成bootsect所需的基础文件, # 这里需要bootsect.s和ld_boot.ld # 生成bootsect文件,第一句是用汇编器把.s文件汇编到.o文件 # 第二句是用链接脚本将.o文件转化到elf可执行文件 # 第三句是用objcopy提取bootsect中.text段的内容,再覆盖bootsect文件 # 这样bootsect就是标准的可以被识别和运行的引导程序了 Image:bootsect - @dd if=bootsect of=Image bs=512 count=1 - @echo "Image built done" # dd可以制作一个镜像,if表示输入文件,of表示输出到文件,bs表示一块的大小 # bs=512表示一块有512字节大小,count=1表示生成1块 clean: - @rm -f *.o bootsect Image # 清理文件 run-qemu:Image - @qemu-system-i386 -boot a -fda Image # -fda表示把Image当做虚拟软盘载入qemu虚拟机

然后在控制台输入make run-qemu,就可以运行啦!最终结果就是输出一个字符串,然后进入die死循环,这就是最小的操作系统。

下一篇文章我们会对bootsect.s文件进行扩展,尝试通过0x13 BIOS存储器服务,来从软盘中读取我们需要的程序段。

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。