尝试写程序啦
第4章
工具
需要用到的工具为文本编译器(如记事本,edit),汇编语言编译程序(进行编译,连接),执行已完成程序的工具程序。在文本程序中写出来的部分成为源程序文件,经过编译器则为可执行文件,也就是从asm到exe。
而由于edit和debug等在win11上被取消了,所以安装了dosbox,把相应的工具放在了和代码一个文件夹里。至于ms-dos的相应安装以及配置过程过于繁琐,个人比较推荐dosbox。
源程序
源程序文件中的所有内容被称为源程序,源程序之所以需要经过编译器才能执行原因有二,一为需要将程序翻译为机器码,二是源程序由汇编指令和伪指令组成,机器不会运行伪指令,而是编译器会运行。源程序执行后成为程序。
1 | assume cs:codesg |
其中assume cs:codesg
,codesg segment···codesg ends
,end
是伪代码,有着不同的功能。assume
含义为假设,在计算机中所有的内存是连续的,但是在cpu执行时内存被划分为了许多的段,用来存储不同作用的数据。而这里的假设就是假设内存中某个段和源程序中某个段相关联。这个示例中则是让codesg与cs寄存器相联系。codesg segment ··· codesg ends
这是俩称多使用的伪指令,用于定义一个段,可以是代码,数据,栈等用处的段。codesg则为改段的名称,名称在被编译连接程序处理后作为一个段的地址。segment时开始的标志,ends是段结尾的标志。一个有意义的汇编程序,至少需要一个段。end
作为程序结束的标志,在编译器编译到end时会停止编译。
在汇编源程序中,数据不能以字母开头,比如A000h要写作0A000h。
编译 连接 执行
在写好源程序之后会得到一个以asm为后缀的文件,对这个文件进行编译,我使用的时masm配置到dosbox里面的在界面内输入masm 名称.asm
如果文件不在所显示的路径中要补充路径。在这之后会有三个回车的确认,第一个是确认asm的文件名,第二个是确认后续连接使用的obj后缀的文件名,第三个是生成的列表文件,第三个回车其实是确认不生成这个中间文件。在这里会有报错提醒,具体定位到源程序文件的行。
连接使用的是link,连接会生成可执行文档,同时也会在这一步将包含子函数的库函数该程序进行连接,方便调用。连接的操作则是link 名称.obj
,第一个确认是输入的文件名(我的link或许是版本问题,没有这一步),第二个是输出的exe文件名,第三个映像文件(将一系列文件做成单一格式方便下载的文件)同样是中间结果,可以不输出,第四个是连接库函数(我的例子中不需要库函数所以为空),此处的warning不影响后续使用,具体原因学会了补充。
执行时需要有一个程序来让我们的程序执行,也就是可执行文件在加载后需要一个程序将CPU的控制权交给该文件,才能让文件得到执行,并且在执行后将CPU的控制权交还,结束执行。而这个交还控制权的操作叫做程序返回。
程序返回的实现靠的是源程序中的两条指令。mov ax,4c00h int 21h
,在上面的代码的例子中,这两条指令没有在程序对数据的处理中起作用,他们的作用就体现在了程序返回上。在dos系统上这个让可执行文件执行的文件即为command.com,command根据文件名找到可执行文件后将文件中的程序加载入内存,设置CS:IP指向该程序的入口,随后CPU开始运行该程序,程序结束后又返回command。debug也是由command载入,并在调试结束后返回command。
加载程序时会将程序载入一个连续的段内,但是程序的代码与段首有256个字节的距离,这段距离被称为PSP区,主要作用是让dos和被加载系统通讯。
实验3
用debug跟进一个程序,查看寄存器内容,栈顶的内容,以及PSP的内容。
指令一览
查看ds的存储内容可以发现在代码储存空间的段首是以CD 20开头的PSP
此处因为mov sp,10
,所以sp值改变
因为ax,bx值均为0所以ss:sp指向的区域一致在增减00。
在int 21h
即将执行时需要用p指令来结束程序
第5章
[bx] & 段前缀
[bx]类似于[0],表示偏移地址,段地址默认为ds。
在masm中[1]会被直接作为1,而在debug中会作为[1]进行执行,所以为了在汇编源程序中达到偏移地址的效果,只能用[bx]作为中转,例如 mov ax,[1]
需变更为mov bx,1 ;mov ax,[bx]
或者标注段寄存器mov ax,ds:[1]
。
类似于mov ax,ds:[1]
的写法还可以拓展到各个访问内存单元的段地址中,ds,cs,ss,es等,类似于mov ax,ss:[bx]
这种写法称之为段前缀。
需要注意的是有些空间是dos在使用,占用这些空间会造成死机等后果,而dos和其他合法程序一般不会使用0:200-0:2ff的256个字节空间,较为安全,可以用debug查询存储内容以确认安全性。
loop
loop的作用可以理解为控制循环,进行的操作有两步:cx=cx-1 -> 判断cx值是否为零,否在跳转回标记处,是则退出循环。
1 | assume cs:code |
这段代码中的s即为loop指令的标记,cx表示循环执行次数为11次。
在debug中可以用p命令将循环一次执行完,用g 加偏移地址可以直接从此处执行到指定地址,类g 0012h。
实验4
主要是[bx]以及loop的运用。
以下是向内存0:200-0:23F一次传送数据0-63(3fh)。
1 | assume cs:code |
以下是将mov ax,4cooh
之前的指令复制到内存0:200处。
1 | assume cs:code |
debubg可以查看到这个代码的全场为cx的初始值,用u可以看到最后两行也就是int 21h那两行的地址,由此可以得到需要复制的代码的长度,则为17h。如下图
第6章
定义不同段
需要用到数据时我们需要在汇编源文件定义这些数据,经过编译,连接后作为程序的一部分写到可执行文件中,这样在程序被载入内存时,数据也获得了存储空间。可以在代码段中定义,比如在用dw 0123h,0456h
,dw的意思是定义字型数据。同时也可以在代码段外定义一个新的段,同样用segment标识,还需要在assume标出。
也可以定义栈,类dw 0,0,0一共16个0
,定义后将获得16个字型数据的空间,在程序中设置ss和sp就可以作为栈来使用。
在设置了其他段的情况下,执行代码需要将IP调偏移地址到代码所在,或者用start作为标志。并且在end处标明,end除了通知编译器程序结束以外还可以通知编译器程序的入口在哪里。在编译,连接以后由end start指明的程序入口被转化为一个入口地址,存储在可执行文件的描述信息里面。引用段的地址需要先将地址放入寄存器再mov。
1 | assume cs:code,ds:data,ss:stack |
实验5
1 | assume cs:code,ds:data,ss:stack |
确定以CD 20开头的PSP的位置,在100h的偏移以后就是data了
栈的定义,不仅需要先占据内存,还要在程序中赋值SS,SP
pop和push改变栈内容实录
程序返回前ss,cs,ds的内容
下一个
1 | assume cs:code,ds:data,ss:stack |
这里的data只给了俩,但是分配内存仍然是16位
下一个
1 | assume cs:code |
这里经过了三次循环,主要是为了将a,b段的诗句加在一起保存到c段,有够繁琐的这题,如下图标的就是最后的内容,确实是相加成功了
下一个
1 | assume cs:code |
这里用栈的方法做一个逆序,原数据没变,定义的栈是倒序后的结果,定义的段在内存的顺序也是按照定义的顺序来的,偏移的地址也可以按定义顺序计算。