Bootstrap

Day2 汇编语言学习与Makefile入门

helloos3例程

1. 源码解释

新的helloos.nas源码

; hello-os
; TAB=4
		ORG		0x7c00			; 指明程序的装载地址

; 以下记述用于标准的FAT12格式软盘
		JMP		entry			; 直接跳转到entry(0x7c50)
		DB		0x90
		DB		"HELLOIPL"		; 启动区名称可以是任意字符串(8字节) initial program loader
		DW		512				; 每个扇区(sector)的大小(必须是512个字节)
		DB		1				; 簇(cluster)的大小必须是512个字节
		DW		1				; FAT的起始位置(一般从第一个扇区开始)
		DB		2				; FAT的个数(必须是2)
		DW		224				; 根目录的大小(一般设置为224项)
		DW		2880			; 该磁盘的大小(必须是2880扇区)
		DB		0xf0			; 磁盘的种类(必须是0xf0)
		DW		9				; FAT的长度(必须是9扇区)
		DW		18				; 1个磁道(track)或者完整的一圈有几个扇区(必须是18)
		DW		2				; 磁头数(必须是2)
		DD		0				; 不适用分区,必须是0
		DD		2880			; 重写一次磁盘大小
		DB		0,0,0x29		; 意义不明,固定
		DD		0xffffffff		; (可能是)卷标号码
		DB		"HELLO-OS   "	; 磁盘的名称(11字节)
		DB		"FAT12   "		; 磁盘的格式名称(8字节)
		RESB	18				; 空出18字节

; 程序主体
entry:
		MOV		AX,0			; 初始化寄存器
		MOV		SS,AX
		MOV		SP,0x7c00
		MOV		DS,AX
		MOV		ES,AX

		MOV		SI,msg			; msg(0x7c74)
putloop:
		MOV		AL,[SI]
		ADD		SI,1			; SI = SI+1
		CMP		AL,0
		JE		fin				; 条件跳转(jump if equal),与CMP搭配使用,如果AL==0?,跳转到fin
		MOV		AH,0x0e			; 显示一个文字
		MOV		BX,15			; 指定字符color
		INT		0x10			; 调用显卡BIOS
		JMP		putloop
fin:
		HLT						; CPU suspend(halt),wait command
		JMP		fin				; 无限loop

msg:
		DB		0x0a, 0x0a		; 换行2次
		DB		"hello, world"
		DB		0x0a			; 换行
		DB		0

		RESB	0x7dfe-$		; 从此处写0x00直到0x7dfe

		DB		0x55, 0xaa

; 启动区以外部分的输出
		DB		0xf0, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00
		RESB	4600
		DB		0xf0, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00
		RESB	1469432

源码中:

  • ORG:用于将接下来的机器指令加载到指定的内存地址。由于制定了起始地址,因此$也由“本行之前写入多少个字节”变为“引入ORG指定起始地址的条件后,本行之前写入多少个字节”。

我对比了helloos3例程中编译出的helloos.img和helloos2中的,发现二进制级别相同,也不知道内存加载的时候是怎么识别到ORG指定的。

  • JMP:跳转,类似于C语言的goto语句。
  • entry:标签的声明,用于指定JMP的目的地。
  • MOV:赋值。例如,MOV AH,0x0e就可以认为是AH = 0x0e
  • ADD:加法指令。例如,ADD SI, 1就可以认为是SI = SI + 1。
  • CMP:比较指令。例如,CMP a, 3可以认为是if (a == 3){}
  • JE:条件跳转(jump if equal),一般作为CMP的下一句,因为要以CMP的比较结果作为条件。例如,CMP AL, 0; JE fin就可以认为是if (AL == 0) {goto fin;}
  • INT:中断指令,暂时认为是一个函数调用。计算机的BIOS中,有一些操作系统开发人员经常会用到的一些基础函数组件。INT就是就可以调用这些函数,例如,INT 0x10就是调用16号函数,用于控制显卡。
  • HLT:使CPU停止动作进入待机状态。只要外部发生变化,必然按下键盘或者移动鼠标,CPU就会醒来继续执行程序。本源码中如果使用HLT指令,CPU就会不停的执行JMP指令,这样会使CPU的loading达到100%。

程序主体的功能就是打印"hello, world",将程序主体翻译为C语言伪代码可以写作:

; 程序主体
entry:
		AX = 0;			
		SS = AX;
		SP = 0x7c00;
		DS = AX;
		ES = AX;
		SI = msg;		
putloop:
		AL = BYTE [SI];
		SI = SI + 1;			
		AL = 0x0e
		BX = 15;			
		INT 0x10;			
		goto putloop;
fin:
		HLT;						
		goto fin;				

另外,源码中出现了一些寄存器变量:

  • AX:accumulator,累加寄存器。
  • CX:counter,计数寄存器。
  • DX:data,数据寄存器。
  • BX:base,基址寄存器。
  • SP:stack pointer,栈指针寄存器。
  • BP:base pointer,基址指针寄存器。
  • SI:source index,源变址寄存器。
  • DI:destination index,目的变址寄存器。

这些寄存器是16位寄存器,因此可以存储一个16bits内容。X表示扩展(extend)的意思,就是从8bits扩展为16bits。有时使用不同的寄存器可以实现不同的功能,但是对编译完的二进制文件来说,简洁程度却是不同的。例如:
ADD CX, 0x1234编译为81 C1 34 12(四字节),而ADD AX, 0x1234编译为05 34 12(三字节)。
CPU中还有8个8bits寄存器:

  • AL:累加寄存器低位(accumulator low)
  • CL:计数寄存器低位(counter low)
  • DL:数据寄存器低位(data low)
  • BL:基址寄存器低位(base low)
  • AH:累加寄存器高位(accumulator high)
  • CH:计数寄存器高位(counter high)
  • DH:数据寄存器高位(data high)
  • BH:基址寄存器高位(base high)

对于32bits的计算机,也有32位寄存器EAX, ECX, EDX, EBX, ESP, EBP, ESI, EDI,其中的E也是来源于extend。虽然EAX是32bits寄存器,但是它的低16位就是AX,因此可以直接通过访问AX获取EAX的低16bits,如果期望获取高16bits,就需要使用移位指令把高16bits移到低16bits才能访问。
还有一些段寄存器(segment register),这些都是16bits寄存器。

  • ES:附加段寄存器(extra segment)
  • CS:代码段寄存器(code segment)
  • SS:栈段寄存器(stack segment)
  • DS:数据段寄存器(data segment)
  • FS:附加段寄存器(segment part1)
  • GS:附加段寄存器(segment part2)

MOV AL,[SI]中的方括号,甚至可以参照C语言方括号理解,就是取出括号中对应地址的内容。另一个例子,MOV BYTE [678], 123,首先根据BYTE关键字认为该句指令操作的是一个8bits空间,然后可以理解为[678]=123,即将123这个8bits数放置到地址678位置。
dataSize [addr]是一个常用组合。dataSize使用BYTE,WORD或DWORD分别指定1字节,2字节或4字节;addr可以使用数字也可以使用BX,BP,SI,DI寄存器。例如,期望将DX里的内容赋值给AL,可以写作:
MOV BX, DX
MOV AL, BYTE [BX]

2. 0x7c00地址

0x00007c00 ~ 0x00007dff共510字节,是启动区装载的内存地址。

这种内存的分布,应该就是最初设计操作系统的人定的规矩。此外,0x0地址是BIOS程序用来实现不同功能的地方;0xf0000地址附近存放着BIOS程序本身。

helloos4例程

当前仅制作512字节的启动区,把helloos.nas源码文件的后半段都删掉,只需要最初的512字节,并重命名为ipl.nas,即该源文件删掉“; 启动区以外部分的输出”。并修改编译脚本asm.bat为:

# asm.bat
..\z_tools\nask.exe ipl.nas ipl.bin ipl.lst

在输出ipl.bin的同时输出了ipl.lst,这是个列表文件(本质是一个文本文件),用来查看每个指令是如何翻译成机器指令的。本次的主输出文件为512字节,与之对应,列表文件也比较小。
在这里插入图片描述
此外,增加一个makeing.bat文件。这个脚本的功能是将所输出的ipl.bin写入磁盘映像img的开头,组成一个新的完整的磁盘映像文件helloos.img。

# makeing.bat
..\z_tools\edimg.exe   imgin:../z_tools/fdimg0at.tek   wbinimg src:ipl.bin len:512 from:0 to:0   imgout:helloos.img

helloos5例程

编写简单的makefile,自动化整个编译运行过程。
make.exe 会检查目标产物文件是否存在,还会判断源文件的修改日期以便决定是否需要重新编译产物。

首先创建一个初版的Makefile文件,不需要加扩展名后缀:

# 文件生成规则
ipl.bin : ipl.nas Makefile
	../z_tools/nask.exe ipl.nas ipl.bin ipl.lst

helloos.img : ipl.bin Makefile
	../z_tools/edimg.exe   imgin:../z_tools/fdimg0at.tek \
		wbinimg src:ipl.bin len:512 from:0 to:0   imgout:helloos.img

在该文件中,#表示该行是一个注释行。
“ipl.bin : ipl.nas Makefile”表示,如果期望制作一个ipl.bin文件,就需要先检查一下ipl.nas和Makefile是否准备好,如果存在,make工具就会自动执行下一行命令。helloos.img同理,其中“\”是换行符号。
然后编辑一个make.bat脚本用来调用make.exe程序处理Makefile文件:

# make.bat
..\z_tools\make.exe %1 %2 %3 %4 %5 %6 %7 %8 %9

make自动化脚本编辑完成后,就可以通过make -r ipl.binmake -r helloos.img分别生成ipl.bin文件和helloos.img文件。
为了更加自动化,将下面内容添加到Makefile文件中:

default :
	../z_tools/make.exe img
######################################################################
asm :
	../z_tools/make.exe -r ipl.bin

img :
	../z_tools/make.exe -r helloos.img

run :
	../z_tools/make.exe img
	copy helloos.img ..\z_tools\qemu\fdimage0.bin
	../z_tools/make.exe -C ../z_tools/qemu

install :
	../z_tools/make.exe img
	../z_tools/imgtol.com w a: helloos.img

clean :
	-del ipl.bin
	-del ipl.lst

src_only :
	../z_tools/make.exe clean
	-del helloos.img

可以简化命令输入。例如,make img的本质就是直接调用../z_tools/make.exe -r helloos.img命令,因此会生成helloos.img文件。此外,default是Makefile中的一个关键字,意思是如果只执行make命令,其后不跟任何选项参数,则默认执行这一句,所以在helloos5文件夹下,cmd执行make效果与执行make img相同。

;