初次体验汇编程序

初次体验汇编程序
https://zhuanlan.zhihu.com/p/100755422
https://weread.qq.com/web/reader/38732220718ff5cf3877215k6f4322302126f4922f45dec
好,现在就让我们马上来写一个汇编程序,用它来生成一个跟刚才完全一样的helloos.img吧。我们这次使用的汇编语言编译器是笔者自己开发的,名为“nask”,其中的很多语法都模仿了自由软件里享有盛名的汇编器“NASM”,不过在“NASM”的基础之上又提高了自动优化能力。
nask.exe helloos.nas helloos.img
    DB    0xeb, 0x4e, 0x90, 0x48, 0x45, 0x4c, 0x4c, 0x4f
    DB    0x49, 0x50, 0x4c, 0x00, 0x02, 0x01, 0x01, 0x00
    DB    0x02, 0xe0, 0x00, 0x40, 0x0b, 0xf0, 0x09, 0x00
    DB    0x12, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00
    DB    0x40, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x29, 0xff
    DB    0xff, 0xff, 0xff, 0x48, 0x45, 0x4c, 0x4c, 0x4f
    DB    0x2d, 0x4f, 0x53, 0x20, 0x20, 0x20, 0x46, 0x41
    DB    0x54, 0x31, 0x32, 0x20, 0x20, 0x20, 0x00, 0x00
    RESB    16
    DB    0xb8, 0x00, 0x00, 0x8e, 0xd0, 0xbc, 0x00, 0x7c
    DB    0x8e, 0xd8, 0x8e, 0xc0, 0xbe, 0x74, 0x7c, 0x8a
    DB    0x04, 0x83, 0xc6, 0x01, 0x3c, 0x00, 0x74, 0x09
    DB    0xb4, 0x0e, 0xbb, 0x0f, 0x00, 0xcd, 0x10, 0xeb
    DB    0xee, 0xf4, 0xeb, 0xfd, 0x0a, 0x0a, 0x68, 0x65
    DB    0x6c, 0x6c, 0x6f, 0x2c, 0x20, 0x77, 0x6f, 0x72
    DB    0x6c, 0x64, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00
    RESB    368
    DB    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x55, 0xaa
    DB    0xf0, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00
    RESB    4600
    DB    0xf0, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00
    RESB    1469432
DB指令是“define byte”的缩写,也就是往文件里直接写入1个字节的指令。笔者喜欢用大写字母来写汇编指令,但小写的“db”也是一样的。
在汇编语言的世界里,这个指令是程序员的杀手锏,也就是说只要有了DB指令,我们就可以用它做出任何数据(甚至是程序)。所以可以说,没有用汇编语言做不出来的文件。文本文件也好,图像文件也好,只要能叫上名的文件,我们都能用汇编语言写出来。而其他的语言(比如C语言)就没有这么万能。

RESB指令是“reserve byte”的略写,如果想要从现在的地址开始空出10个字节来,就可以写成RESB 10,意思是我们预约了这10个字节(大家可以想象成在对号入座的火车里,预订了10个连号座位的情形)。而且nask不仅仅是把指定的地址空出来,它还会在空出来的地址上自动填入0x00,所以我们这次用这个指令就可以输出很多的0x00,省得我们自己去写18万行程序了,真是帮了个大忙。

用NASM编译也差不多,就不用RESB了。
当输入机器代码的时候,实际上我们是在输入ASCII码。但是输入ASCII码也不是简单的一件事,例如上面的程序中,我们就有一段需要输入N的N次方个"00"的痛苦回忆。聪明的人发明了汇编语言之后,需要输入多少个“00”就变成了一句话的事儿。比如 times 10000 db 0就代表输入的是1万个“00”,这就使我们的工作变得相当的容易。但是我们额外需要做的事情就是配置一个专门的软件来把times 10000 db 0这句话翻译成1万个“00”,这个软件就叫汇编器。

基于这样的思路,我们把上面的二进制机器代码用汇编语言来表达就变成了如下格式:
db 0xB8,0x00,0xB8,0x8E,0xD8,0xC6,0x06,0x00,0x00,0x68,0xC6,0x06,0x02,0x00,0x65,0xC6
db 0x06,0x04,0x00,0x6C,0xC6,0x06,0x06,0x00,0x6C,0xC6,0x06,0x08,0x00,0x6F,0xC6,0x06
db 0x0A,0x00,0x2C,0xC6,0x06,0x0C,0x00,0x77,0xC6,0x06,0x0E,0x00,0x6F,0xC6,0x06,0x10
db 0x00,0x72,0xC6,0x06,0x12,0x00,0x6C,0xC6,0x06,0x14,0x00,0x64,0xEB,0xFE


times 510-($-$$) db 0

db 0x55,0xaa

times 1474560-($-$$) db 0
我用的汇编语言是NASM,将上述汇编程序命名为:bootasm1.asm,将它编译成二进制文件:bootasm1.bin。

NASM bootasm1.asm  -o  bootasm1.bin
这一次,借助汇编语言,我们用10行内程序就简单的代替了之前的百多万行编辑过程,大大节约了工作量,可以说是迈进了一大步。


------------------------------------
D:\myos\NASM\NASM bootasm1.asm  -o  bootasm1.bin
copy bootasm1.bin D:\myos\30days\tolset\z_tools\qemu\fdimage0.bin
make -C D:\myos\30days\tolset\z_tools\qemu
--------------------------------------
但是,这个过程也存在明显的不足,那就是上面一大堆的DB语句。其实,你也根本不明白它究竟代表什么意思。虽然,计算机成功的启动了,但是你仍然是稀里糊涂的。但无论如何,稍微有点经验的人会知道,上面的程序虽然都是汇编语言编写的,但其实并没有起到汇编语言真正的作用,因为它只是利用了汇编语言的“伪指令”简单粗暴的往文件里面堆砌了一堆二进制而已,并没有真正使用到汇编语言的核心程序代码。

刚才我们把程序变成了短短的22行,这成果令人欣喜。不过还有一点不足就是很难看出这些程序是干什么的,所以我们下面就来稍微改写一下,让别人也能看懂。改写后的源文件增加到了48行
; hello-os
; TAB=4

; 以下是标准FAT12格式软盘专用代码
DB        0xeb, 0x4e, 0x90
DB        "HELLOIPL"
DW        512                ; 每个扇区大小
        DB        1                ; 簇大小
        DW        1                ; FAT起始地址
        DB        2                ; FAT个数
        DW        224                ; 根目录大小
        DW        2880            ; 磁盘大小
        DB        0xf0            ; 磁盘种类
        DW        9                ; FAT长度
        DW        18                ; 1个磁道18扇区
        DW        2                ; 磁头数
        DD        0                ; 不使用分区0
        DD        2880            ; 重写一次磁盘大小
        DB        0,0,0x29        ; 意义不明
        DD        0xffffffff        ; 卷标
        DB        "HELLO-OS   "    ; 磁盘名称
        DB        "FAT12   "        ; 磁盘格式名称
        RESB    18                ; 空出18字节
        
; 程序主体

        DB        0xb8, 0x00, 0x00, 0x8e, 0xd0, 0xbc, 0x00, 0x7c
        DB        0x8e, 0xd8, 0x8e, 0xc0, 0xbe, 0x74, 0x7c, 0x8a
        DB        0x04, 0x83, 0xc6, 0x01, 0x3c, 0x00, 0x74, 0x09
        DB        0xb4, 0x0e, 0xbb, 0x0f, 0x00, 0xcd, 0x10, 0xeb
        DB        0xee, 0xf4, 0xeb, 0xfd        
        
        
; 信息显示部分

        DB        0x0a, 0x0a        ; 换行
        DB        "hello, world"
        DB        0x0a            ; 改行
        DB        0

        RESB    0x1fe-$            ; 填写0x00

        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
        
这里有几点新内容,我们逐一来看一下。首先是“; ”命令,这是个注释命令,相当于C语言或是C++中的“//”。正是因为有它,我们才可以在源代码里加入很多注释。
其次是DB指令的新用法。我们居然可以直接用它写字符串。在写字符串的时候,汇编语言会自动地查找字符串中每一个字符所对应的编码,然后把它们一个字节一个字节地排列起来。这个功能非常方便,也就是说,当我们想要变更输出信息的时候,就再也不用自己去查字符编码表了。

再有就是DW指令和DD指令,它们分别是“define word”和“define double-word”的缩写,是DB指令的“堂兄弟”。word的本意是“单词”,但在计算机汇编语言的世界里,word指的是“16位”的意思,也就是2个字节。“double-word”是“32位”的意思,也就是4个字节。

对了,差点忘记说RESB 0x1fe-$了。这个美元符号的意思如果不讲,恐怕谁也搞不明白,它是一个变量,可以告诉我们这一行现在的字节数(如果严格来说,有时候它还会有别的意思,关于这一点我们明天再讲)。在这个程序里,我们已经在前面输出了132字节,所以这里的$就是132。因此nask先用0x1fe减去132,得出378这一结果,然后连续输出378个字节的0x00。

那这里我们为什么不直接写378,而非要用$呢?这是因为如果将显示信息从“hello, world”变成“this is a pen.”的话,中间要输出0x00的字节数也会随之变化。换句话说,我们必须保证软盘的第510字节(即第0x1fe字节)开始的地方是55 AA。如果在程序里使用美元符号($)的话,汇编语言会自动计算需要输出多少个00,我们也就可以很轻松地改写输出信息了。

FAT12格式...(FAT12 Format)用Windows或MS-DOS格式化出来的软盘就是这种格式。我们的helloos也采用了这种格式,其中容纳了我们开发的操作系统。这个格式兼容性好,在Windows上也能用,而且剩余的磁盘空间还可以用来保存自己喜欢的文件。

启动区..........(boot sector)软盘第一个的扇区称为启动区。那么什么是扇区呢?计算机读写软盘的时候,并不是一个字节一个字节地读写的,而是以512字节为一个单位进行读写。因此,软盘的512字节就称为一个扇区。一张软盘的空间共有1440KB,也就是1474560字节,除以512得2880,这也就是说一张软盘共有2880个扇区。那为什么第一个扇区称为启动区呢?那是因为计算机首先从最初一个扇区开始读软盘,然后去检查这个扇区最后2个字节的内容。如果这最后2个字节不是0x55 AA,计算机会认为这张盘上没有所需的启动程序,就会报一个不能启动的错误。(也许有人会问为什么一定是0x55 AA呢?那是当初的设计者随便定的,笔者也没法解释)。如果计算机确认了第一个扇区的最后两个字节正好是0x55 AA,那它就认为这个扇区的开头是启动程序,并开始执行这个程序。

IPL.........…....initial program loader的缩写。启动程序加载器。启动区只有区区512字节,实际的操作系统不像hello-os这么小,根本装不进去。所以几乎所有的操作系统,都是把加载操作系统本身的程序放在启动区里的。有鉴于此,有时也将启动区称为IPL。但hello-os没有加载程序的功能,所以HELLOIPL这个名字不太顺理成章。如果有人正义感特别强,觉得“这是撒谎造假,万万不能容忍!”,那也可以改成其他的名字。但是必须起一个8字节的名字,如果名字长度不到8字节的话,需要在最后补上空格。

启动..........….(boot)boot这个词本是长靴(boots)的单数形式。它与计算机的启动有什么关系呢?一般应该将启动称为start的。实际上,boot这个词是bootstrap的缩写,原指靴子上附带的便于拿取的靴带。

--------

在上面的汇编程序中,你已经开始看到“hello, world”的影子了,但是你并不明白是什么语句控制了计算机启动后屏幕上显示“hello, world”。那么我们接下来通过真正的汇编语言程序代码实现:

mov ax, 0b800h
mov ds, ax

mov byte [0x00],'h'
mov byte [0x02],'e'
mov byte [0x04],'l'
mov byte [0x06],'l'
mov byte [0x08],'o'
mov byte [0x0a],','
mov byte [0x0c],'w'
mov byte [0x0e],'o'
mov byte [0x10],'r'
mov byte [0x12],'l'
mov byte [0x14],'d'
jmp $

times 510-($-$$) db 0
db 0x55,0xaa

times 1474560-($-$$) db 0

将上述汇编程序命名为:bootasm2.asm。最后,将它编译成二进制文件:bootasm2.bin。可以看出,编译之后生成的二进制文件刚好是1.44MB

很明显,这段汇编程序就把我们的目标表达得相当的清楚:是把“hello, world”这个信息用MOV 指令全部往某个地方送,那是往哪里送呢?显然,是往显示器送(打印)

D:\myos\NASM\NASM bootasm2.asm  -o  bootasm2.bin


-------------------
 汇编语言高级制作方法

上面小结的汇编程序虽然我们能轻易读懂,但是给编程者带来一定的操作难度。可以看出,我们为了显示某个字母,我们还得把数据挨个送到内存地址从0b800h开始的地方。为了显示一个字符,编程人员得直接操作内存,这样的效率比较低并且不够简易。这样,计算机制造者为了方便我们,对一些非常常用的设备操作又做了一次封装,这就是BIOS调用。简单的说,我们要想操作显示器,就可以通过汇编语言来直接调用BIOS中断。所以,我们就有了以下更加高阶的写法:
mbrseg         equ    7c0h     ;启动扇区存放段地址

jmp   start
message:       db 'hello,world','$'

start:
mov   ax,mbrseg
mov   ds,ax
mov   si,message
call  printstr
jmp   $

printstr:                  ;显示指定的字符串, 以'$'为结束标记
      mov al,[si]
      cmp al,'$'
      je disover
      mov ah,0eh
      int 10h
      inc si
      jmp printstr
disover:
      ret

times 510-($-$$) db 0
db 0x55,0xaa

times 1474560-($-$$) db 0
这个程序,就比上面的都容易读懂了:
(a)它具备了高级语言的函数调用功能:比如上面的字符串显示就调用了一个自己编写的函数:printstr,而在这个函数里我们又调用了系统自带的BIOS中断来显示字符。

(b)把要显示的字符全部集中放在一起,在汇编里面一般叫数据区。

可以看出,思想其实就是尽量模块化。
将上述汇编程序命名为:bootasm3.asm。最后,将它编译成二进制文件:bootasm3.bin。同样,我们用二进制文件:bootasm3.bin来装入软盘并启动计算机,也能成功。
----
D:\myos\NASM\NASM bootasm3.asm  -o  bootasm3.bin

为啥这次打印的位置正确?前面的直接显示在第一行。看来bios中断还是比直接用显存强。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值