「DAY 02」- 汇编语言学习与 Makefile 入门

  CREATED BY JENKINSBOT

第一步、编写汇编程序(day-02.nas)

; hello-os
; TAB=4

		ORG		0x7c00			; 指明程序的装载地址

; 以下这段是标准 FAT12 格式软盘专用的代码

		JMP		entry
		DB		0x90
		DB		"HELLOIPL"		; 启动区名称可以是任意字符串
		DW		512				; 每个扇区的大小,必须是 512 字节
		DB		1				; 簇(Cluster)的大小,必须为 1 个扇区
		DW		1				; FAT 的起始位置(一般从第一个扇区开始)
		DB		2				; FAT 的个数(必须为 2)
		DW		224				; 根目录的大小(一般设成 244 项)
		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
putloop:
		MOV		AL,[SI]
		ADD		SI,1			; 将 SI 加 1
		CMP		AL,0
		JE		fin
		MOV		AH,0x0e			; 显示一个文字
		MOV		BX,15			; 指定字符颜色
		INT		0x10			; 调用显卡 BIOS
		JMP		putloop
fin:
		HLT						; 让 CPU 停止,等待指令
		JMP		fin				; 无限循环

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

		RESB	0x01fe - ($ - $$)		; 使用 0x00 填充,直到 0x01fe 结束

		DB		0x55, 0xaa

第二步、编译并运行程序

nasm day-02.nas

qemu-system-i386 day-02

介绍文本编辑器

书中推荐 TeraPad 编辑器,我们使用 Emacs 编辑器。

继续开发

改写汇编程序

程序供给四部分,第一部分与第四部分需要磁盘方面的知识。这里我们先探讨第二部分与第三部分:

; hello-os
; TAB=4

	ORG 0x7c00 ; 指明程序的装载地址

; 以下是标准 FAT12 格式软盘专用的代码

	JMP entry
	DB 0x90

--- (中略) ---

; 程序主体

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

	MOV SI, msg

putloop:
	MOV	AL, [SI]
	ADD SI, 1 ; 给 SI 加 1
	CMP AL, 0

	JE fin
	MOV AH, 0x0e ; 显示一个文字
	MOV BX, 15 ; 制定字符颜色
	INT 0x10 ; 调用显卡 BIOS
	JMP putloop

fin:
	HLT ; 让 CPU 停止,等待指令
	JMP fin ; 无线循环

; 信息显示部分

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

汇编指令含义说明

ORG

origin,告诉汇编器在开始执行时机器语言指令装在到内存的哪个地址。如果没有它,有几个指令就无法正确翻译与执行。这里是内存 0x7c00 地址:内存的 0 号地址,是 BIOS 用于实现功能的地方,使用该地址会影响到 BIOS 程序,程序也会出错;内存 0xf0000 号地址也存放着 BIOS 程序,也不能使用。内存地址的使用是有规范的,0x0007c00 – 0x00007dff 是启动区内容的装载地址。

JMP

jump,跳转

entry:、putloop:、fin:、msg:

标签声明,是 JMP 指令跳转地址

MOV

move,赋值,原值不会被删除,所以 move 是不准确的说法。

MOV SI, msg:将 msg 标签的地址赋值给 SI 寄存器,msg 标签的地址是汇编器基于 ORG 指令计算出来的,这里是 0x7c74 地址。JMP entry:同理跳转到 entry 标签的地址,这里是 0x7c50 地址。

MOV AL, [SI]:此时在 SI 中保存的数值被视为内存地址,将在该内存地址中的数值赋予 AL 寄存器(即 AX 的低 8 位);再比如 MOV BYTE [678], 123 表示将 123 保存到内存地址为 678 的地方;再比如 MOV WORD [678], 123 表示将 123 保存到内存地址为 678、679 的地方,因为 WORD 表示 16 位;再比如使用 DWORD 则邻近(地址增加方向) WORD 的两个字节都会成为操作对象。

能进行内存取值操作的寄存器只有 BX、BP、SI、DI,而 AX、CX、DX、SP 未实现该功能。如果在 DX 中保存了内存地址,则需要先移动 MOV BX, DX 后再通过 MOV AL, BYTE [BX] 获取在内存中的数值。另外在 MOV 中,源数据与目的数据必须位数相同,因此 可以省略 BYTE,使用 MOV AL, [SI] 写法。

ADD

加法指令。指令 ADD SI, 1 等价 SI = SI + 1

CMP

比较指令。CPM AL, 0 即将在 AL 中的数值与 0 比较。

JE

条件跳转,jump if equal,如果相等则跳转。JE fin:如果相等则跳转 fin 标签,同要标签代表地址,该地址是由汇编器根据 ORG 指令计算而来。

INT

中断指令,用于调用 BIOS 函数。INT 0x10:调用显卡 BIOS 函数用于显示文字,需要查看官方文档以获取更多信息。

按照下面查找到的方法,然后在调用 INT 0x10 即可显示字符:

AH=0x0e;
AL=character code;
BH=0
BL=color code;
返回值:无

; 这就好像向寄存器中保存参数,然后调用函数,函数会去寄存器中取值。

HLT

halt,让 CPU 停止动作,进入休眠状态,当发生外部变化时(比如键盘按键、鼠标单击等等),CPU 会被唤醒,程序继续执行。如果不使用 HLT 指令,CPU 会进入毫无意义的无限循环。使用 HLT 使 CPU 休眠,防止 CPU 进入毫无意义的无限循环。

在使用 C 语言改写后的程序(节选)

entry:
	AX = 0
	SS = AX
	SP = 0x7c00
	DS = AX
	ES = AX
	SI = msg
putloop:
	AL = BYTE [SI]
	SI = SI + 1
	if (AL == 0) { goto fin;}
	AH = 0x0e
	BX = 15
	INT = 0x10
	goto putloop
fin:
	HTL;
	goto fin;

有了这个程序才能将在 msg 中的数据打印出来,当数据变成 0 后,停止显示,进入 HLT 休眠。

先制作启动区

考虑到以后的开发,我们不再使用汇编器制作整个磁盘镜像,而是只制作启动区(前面 512 字节)。

1)ipl.nas => ipl.bin,并附带输出的 ipl.lst 文件(描述汇编语言翻译为机器语言的过程)
2)利用磁盘镜像管理工具,将 ipl.bin 写入到空镜像中,得到 helloos.img 镜像。

Makefile 入门