0%

汇编语言4-6自学回顾

尝试写程序啦

第4章

工具

需要用到的工具为文本编译器(如记事本,edit),汇编语言编译程序(进行编译,连接),执行已完成程序的工具程序。在文本程序中写出来的部分成为源程序文件,经过编译器则为可执行文件,也就是从asm到exe。
而由于edit和debug等在win11上被取消了,所以安装了dosbox,把相应的工具放在了和代码一个文件夹里。至于ms-dos的相应安装以及配置过程过于繁琐,个人比较推荐dosbox。

源程序

源程序文件中的所有内容被称为源程序,源程序之所以需要经过编译器才能执行原因有二,一为需要将程序翻译为机器码,二是源程序由汇编指令和伪指令组成,机器不会运行伪指令,而是编译器会运行。源程序执行后成为程序。

1
2
3
4
5
6
7
8
9
10
11
12
assume cs:codesg

codesg segment
mov ax,0123h
mov bx,0456h
add ax,bx

mov ax,4c00h
int 21h
codesg ends

end

其中assume cs:codesgcodesg segment···codesg endsend是伪代码,有着不同的功能。
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
2
3
4
5
6
7
8
9
10
11
12
assume cs:code
code segment
mov ax,2
mov cx,11

s: add ax,ax
loop s

mov ax,4c00h
int 21h
code ends
end

这段代码中的s即为loop指令的标记,cx表示循环执行次数为11次。
在debug中可以用p命令将循环一次执行完,用g 加偏移地址可以直接从此处执行到指定地址,类g 0012h。

实验4

主要是[bx]以及loop的运用。
以下是向内存0:200-0:23F一次传送数据0-63(3fh)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
assume cs:code
code segment
mov bx,0020h
mov ds,bx
mov ax,0h
mov cx,64

s: mov [ax],al //用al是因为这里的地址要求按字节传送数据,ax会覆盖
inc al
loop s

mov ax,4c00h
int 21h

code ends
end

以下是将mov ax,4cooh之前的指令复制到内存0:200处。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
assume cs:code
code segment
mov ax,cs //cs是代码的起点
mov ds,ax
mov ax,0020h
mov es,ax
mov bx,0
mov cx,17h //这里的17h是在试验后得出的

s: mov al,[bx]
mov es:[bx],al
inc bx
loop s

mov ax,4c00h
int 21h

code ends
end

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
2
3
4
5
6
7
8
9
assume cs:code,ds:data,ss:stack
.....
code segment
dw...
start:
.....
code ends

emd start

实验5

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
assume cs:code,ds:data,ss:stack
data segment
dw 0123h,0456h,0789h,0abch,0defh,0cbah,0987h
data ends

stack segment
dw 0,0,0,0,0,0,0,0
stack ends

code segment
starts: mov ax,stack
mov ss,ax
mov sp,16

mov ax,data
mov ds,ax

push ds:[0]
push ds:[2]
pop ds:[2]
pop ds:[0]

mov ax,4c00h
int 21h
code ends

end starts

确定以CD 20开头的PSP的位置,在100h的偏移以后就是data了

栈的定义,不仅需要先占据内存,还要在程序中赋值SS,SP


pop和push改变栈内容实录

程序返回前ss,cs,ds的内容

下一个

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
assume cs:code,ds:data,ss:stack

data segment
dw 0123h,0456h
data ends

stack segment
dw 0,0
stack ends

code segment
start:
mov ax,stack
mov ss,ax
mov sp,16

mov ax,data
mov ds,ax

push ds:[0]
push ds:[2]
pop ds:[2]
pop ds:[0]

mov ax,4c00h
int 21h
code ends
end start

这里的data只给了俩,但是分配内存仍然是16位

下一个

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
assume cs:code
a segment
db 1,2,3,4,5,6,7,8
a ends

b segment
db 1,2,3,4,5,6,7,8
b ends

c segment
db 0,0,0,0,0,0,0,0
c ends

code segment
start:
mov ax,a
mov es,ax
mov ax,c
mov ds,ax
mov bx,0
mov cx,8

s1: mov ax,es:[bx]
mov [bx],ax
add bx,2
loop s1

mov ax,b
mov es,ax
mov bx,0
mov cx,8

s2: mov ax,es:[bx]
add [bx],ax
add bx,2
loop s2

mov ax,4c00h
int 21h
code ends
end start

这里经过了三次循环,主要是为了将a,b段的诗句加在一起保存到c段,有够繁琐的这题,如下图标的就是最后的内容,确实是相加成功了

下一个

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
assume cs:code

a segment

dw 1,2,3,4,5,6,7,8

a ends

b segment

dw 0,0,0,0,0,0,0,0

b ends

code segment

start: mov ax,b
mov ss,ax
mov sp,16
mov ax,a
mov ds,ax
mov ax,0
mov cx,8


s: push [bx] .这里只能用bx,不能用ax
add bx,2
loop s

mov ax,4c00h
int 21h

code ends

end start

这里用栈的方法做一个逆序,原数据没变,定义的栈是倒序后的结果,定义的段在内存的顺序也是按照定义的顺序来的,偏移的地址也可以按定义顺序计算。