汇编语言-王爽-实验任务

扯话

开始啃汇编啦,看过CSAPP第三章后,感觉上手还是比较友好的

准备顺序大概是

王爽汇编语言 -> x86汇编语言:从实模式到保护模式 -> 操作系统

实验

准备工作

书上的环境是在windows下,由于我是使用的linux发行版deepin,所以选择了一些工具代替

试了一下,现在用win10去使用DOS估计也不是特别简单,干脆就用LINUX了

使用dosemu模拟DOS环境

安装dosemu

只需要一句简单的命令行即可安装dosemu

sudo apt-get install dosemu

安装MASM6.115.0

在下载完MASM6.11后,在HOME文件夹下解压,于是我们得到了MASM611的安装文件夹
后续安装后,使用过程出现了缺少dosxnt.exe文件的报错,google后折腾了一个小时左右,后面还是换版本了

使用的是MASM5.0

dosemu下看到的C盘和D盘只是虚拟出来的,对应的目录在~/.dosemu/drives 下。

下载解压后,将link.exe和masm.exe放到C盘下,直接使用就可以了

具体使用书上有解释,就不赘述了.

实验1 查看CPU和内存,用机器指令和汇编指令编程

  1. 使用Debug,将下面的程序段写入内存,逐条执行,观察每条指令执行后,CPU中相关寄存器的变化。
机器码 汇编指令 执行后相关寄存器变化
b8 20 4e mov ax, 4E20H AX=4E20H
05 16 14 add ax, 1416H AX=6236H
bb 00 20 mov bx, 2000H BX=2000H
01 d8 add ax, bx AX=8236H
89 c3 mov bx, ax BX=8236H
01 d8 add ax, bx AX=046CH
b8 1a 00 mov ax, 001AH AX=001AH
bb 26 00 mov bx, 0026H BX=0026H
00 d8 add al, bl AX=0040H
00 dc add ah, bl AX=2640H
00 c7 add bh, al BX=4026H
b4 00 mov ah, 0 AX=0040H
00 d8 add al, bl AX=0066H
04 9c add al, 9CH AX=0002H

提示,可用E命令和A命令以两种方式将指令写入内存。注意用T命令执行时,CS:IP的指向。

2.将下面3条指令写入从2000:0开始的内存单元中,利用这3条指令计算2的8次方。

1
2
3
4
5
6
1
2
3
mov ax, 1
add ax, ax
jmp 2000:0003

一个循环,很好理解

3.查看内存中的内容。

PC机主板上的ROM写有一个生产日期,在内存FFF00H~FFFFFH的某几个单元中,请找到这个生产日期并试图改变它。
提示,如果读者对实验的结果感到疑惑,请仔细阅读第1章中的1.15节。

用D命令能看到储存在FFF0:00F5-00FC之间,xx/xx/xx ,模拟器是可以改的,然后真实情况应该是不可以改的(猜测),应该是写死了,无写权限

实验二

  1. 使用Debug,将下面的程序段写入内存,逐条执行,根据指令执行后的实验运行情况填空。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
mov ax, ffff
mov ds, ax
mov ax, 2200
mov ss, ax
mov sp, 1000
mov ax,[0] ;ax = _____
add ax,[2] ;ax = _____
mov bx,[4] ;bx = _____
add bx,[6] ;bx = _____
push ax ;sp = _____ 修改的内存单元的地址是_____内容为_____
push bx ;sp = _____ 修改的内存单元的地址是_____内容为_____
pop ax ;sp = _____ ax = _____
pop bx ;sp = _____ bx = _____
push [4] ;sp = _____ 修改的内存单元的地址是_____内容为_____
push [6] ;sp = _____ 修改的内存单元的地址是_____内容为_____

结果

1
2
3
4
5
6
7
8
9
10
5bea
00e0
32f0
2f32
00FE 220FE 25EE
00FC 220FC 5F37
00FE 5F37
0100 25EE
00FE 220FE 3002
00FC 220FC 2F35

2.仔细观察图3.19中的实验过程,然后分析:为什么2000:0~2000:f中的内容会发生改变?

在使用T命令时,产生了中断,为了保护现场,CPU将CS和IP,此时的位置,入栈,导致了内存相关位置内容的改变。

实验三

  1. 将下面的程序保存为t1.asm文件,将其生成可执行文件t1.exe。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
assume cs:codesg
codesg segment
mov ax, 2000H
mov ss, ax
mov sp, 0
add sp, 10
pop ax
pop bx
push ax
push bx
pop ax
pop bx
mov ax, 4c00h
int 21h
codesg ends
end

这个没什么好说的….

  1. 用Debug跟踪t1.exe的执行过程,写出每一步执行后,相关寄存器中的内容和栈顶的内容。
结果
ax 2000H
ss 2000H
sp 0000H
sp 000AH
ax 0 sp 000cH
bx 0 sp 000EH
sp 000cH
sp 000AH
ax 0 sp 000cH
bx 0 sp 000EH
ax 4c00H
empty
  1. PSP头两个字节为CD 20,用debug加载t1.exe.查看PSP内容:

PSP区域地址范围为:ds:0~ds:ff.用d命令查看即可

实验四

  1. 编程,向内存0:200~0:23F依此传送数据0~63(3FH)。

见下

  1. 编程,向内存0:200~0:23F依此传送数据0~63(3FH),程序中只能使用9条指令,9条指令中包括“mov ax, 4c00h”和“int 21h”。
1
2
3
4
5
6
7
8
9
10
11
12
13
assume cs:code
code segment
mov ax, 0020h
mov ds, ax
mov bx, 0
mov cx, 64
s: mov [bx], bl ;此处注意![bx]为一字节,用bl
inc bl
loop s
mov ax, 4c00h
int 21h
code ends
end
  1. 下面的程序的功能是将“mov ax, 4c00h”之前的指令复制到内存的0:200处,补全程序,上机调试,跟踪运行结果。

下面的程序的功能是将“mov ax, 4c00h”之前的指令复制到内存的0:200处,补全程序,上机调试,跟踪运行结果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
assume cs:code
code segment
mov ax, _____
mov ds, ax
mov ax, 0020h
mov es, ax
mov bx, 0
mov cx, _____
s: mov al, [bx]
mov es:[bx], al
inc bx
loop s
mov ax, 4c00h
int 21h
code ends
end

程序入口为cs:0(ip=0),所以第一空处传递段地址为cs

而程序总代码长度可通过debug加载后通过u命令看出.先将第二空位随便设为1,加载后观察mov ax,4c00h之前代码为0000~0016h,总长度为23.

实验5 编写、调试具有多个段的程序

1.将下面的程序编译连接,用Debug加载、跟踪,然后回答问题。

答案:

data段中的数据不变。

07f3、07f4、07f2(答案不唯一)

X-2、X-1

2.将下面的程序编译连接,用Debug加载、跟踪,然后回答问题。

答案:

data段中的数据不变。

07f3、07f4、07f2(答案不唯一)

X-2、X-1

可以知道:数据段空间大小为定义数据所需的16字节的最小整数倍

([N]+1) * 16 (字节对齐)

3.将下面的程序编译连接,用Debug加载、跟踪,然后回答问题。

答案:

(3) x+3 x+4

4.如果将1. 2. 3.题中的最后一条伪指令“end start”改为“end”(也就是说,不指明程序的入口),则哪个程序仍然可以正确执行?请说明原因。

答案:
程序3,在不指明程序入口的情况下,程序默认按照顺序从头开始执行.

5.程序如下,编写code段中的代码,将a段和b段中的数据依此相加,将结果存到c段中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
start:
mov ax, a
mov ds, ax
mov ax, b
mov es, ax
mov ax, c
mov ss, ax
mov bx 0
mov cx 8
s:
mov al, [bx]
add al, es:[bx]
mov ss:[bx], al
inc bx
loop s

mov ax, 4c00H
int 21h

6.程序如下,编写code段中的代码,用push指令将a段中的前8个字型数据,逆序存储到b段中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
start:
mov ax, b
mov ss, ax
mov sp, 16
mov ax, a
mov ds, ax
mov bx, 0
mov cx, 8
s:
push [bx]
add bx,2
loop s

mov ax, 4c00H
int 21h

实验6 实践课程中的程序

1.将课程中所有讲解过的程序上机调试,用Debug跟踪其执行过程,并在过程中进一步理解所讲内容。

2.编程,完成问题7.9中的程序

解决方案有书上三种,用的是栈储存

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
start:
mov ax, codesg
mov ds, ax
moe ax, stacksg
mov ss, ax
mov sp, 16
mov bx, 0
mov cx, 4

s:
push cx
mov cx, 4
mov si, 0

s0:
mov al, [bx+si+3]
and al, 11011111b
mov [bx+si+3], al
inc si
loop s0
add bx,16

pop cx
loop s

mov ax,4c00H
int 21h

实验7 寻址方式在结构化数据访问中的应用

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
assume cs:codesg, ss:stack

data segment
db '1975', '1976', '1977', '1978', '1979', '1980', '1981', '1982', '1983'
db '1984', '1985', '1986', '1987', '1988', '1989', '1990', '1991', '1992'
db '1993', '1994', '1995'

dd 16, 22, 382, 1356, 2390, 8000, 16000, 24486, 50065, 97479, 140417, 197514
dd 345980, 590827, 803530, 1183000, 1843000, 2759000, 3753000, 4649000, 5937000

dw 3, 7, 9, 13, 28, 38, 130, 220, 476, 778, 1001, 1442, 2258, 2793, 4037, 5635, 8226
dw 11542, 14430, 15257, 17800
data ends

table segment
db 21 dup ('year summ ne ?? ')
table ends

stack segment
dw 8 dup (0) ;存放cx
stack ends

codesg segment
start:
mov ax, stack
mov ss, ax
mov sp, 16
mov ax, table
mov ds, dx
mov ax, date
mov es, ax

mov bx, 0
mov cx, 21
mov si, 0
year:
push cx
mov cx, 4
mov di, 0
s:
mov al, es:[si]
mov [bx+di], al
inc si
inc di
loop s
add bx,16
pop cx
loop s

mov cx, 21
mov bx, 0
income:
mov ax, es:[si]
mov 5[bx], ax
add si, 2
mov ax, es:[si]
mov 7[bx], ax
add si, 2
add bx, 16
loop income

mov cx, 21
mov bx, 0
staff: mov ax, es:[si]
mov [10+bx], ax
add si, 2
add bx, 16
loop staff

mov cx, 21
mov bx, 0
average:mov ax, [bx+5]
mov dx, [bx+7]
div word ptr [bx+10]
mov [13+bx], ax
add bx, 16
loop average

mov ax, 4c00h
int 21h

codesg ends
end start

实验8 分析一个奇怪的程序

分析下面的程序,在运行前思考,这个程序可以正确返回吗?
运行后再思考:为什么是这种结果?
通过这个程序加深对相关内容的理解。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
assume cs:codesg
codesg segment
mov ax, 4c00h
int 21h
start: mov ax, 0
s: nop
nop
mov di, offset s
mov si, offset s2
mov ax, cs:[si]
mov cs:[di], ax
s0: jmp short s
s1: mov ax, 0
int 21h
mov ax, 0
s2: jmp short s1
nop
codesg ends
end start

jmp short 标号、jmp near ptr 标号、jcxz 标号、loop 标号等几种汇编指令,它们对 IP的修改是根据转移目的地址和转移起始地址之间的位移来进行的。不包含转移的目的地址,而包含的是到目的地址的位移距离。方便了程序段在内存中的浮动装配。

能成功返回,通过ax将s2的地址传送到s,jmp short s实际跳转到s2,而s2中jmp short s1计算的是s1,s2之间的偏移量,然后加上s的地址,实际跳转到首地址,完成返回

实验9 根据材料编程

编程:在屏幕中间分别显示绿色、绿底红色、白底蓝色的字符串’welcome to masm!’。
编程所需的只是通过阅读、分析下面的材料获得。

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
42
43
44
45
46
assume ds:data, cs:code

data segment
db 'welcome to masm!'
data ends

code segment
start: mov ax, data
mov ds, ax
mov ax, 0B800H
mov es, ax

mov bx, 1664
mov si, 0
mov cx, 16
char1: mov al, [si]
mov ah, 10000010B
mov es:[bx], ax
add bx, 2
inc si
loop char1

mov bx, 1824
mov si, 0
mov cx, 16
char2: mov al, [si]
mov ah, 10100100B
mov es:[bx], ax
add bx, 2
inc si
loop char2

mov bx, 1984
mov si, 0
mov cx, 16
char3: mov al, [si]
mov ah, 11110001B
mov es:[bx], ax
add bx, 2
inc si
loop char3

mov ax, 4C00H
int 21H
code ends
end start

实验10 编写子程序

1.显示字符串

注意事项:

1.计算偏移地址的时候,以bx作为偏移量,ax不行

例如 es:[bx+si] 而不是 es:[ax+si]

2.子程序开始的时候把所有的寄存器入栈,方便接下来使用,结束的时候出栈

3.计算数组偏移地址的时候,用mul

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
assume cs:code,ds:data,ss:stack  

data segment
db 'welcome to masm!',0
data ends

stack segment
dw 8 dup (0)
stack ends

code segment
start: mov dh,8
mov dl,3 ;第八行,第三列

mov cl,2 ;颜色

mov ax,data
mov ds,ax ;ds指向数据段

mov si,0
call show_str

mov ax,4c00h
int 21h

show_str:
push dx
push cx
push ax
push ss
push si ;子程序开始所有寄存器入栈

mov ax,0B800h
mov es,ax ;es指向显示的起始地址

;计算行偏移量
mov al,0a0h ;160个字节一行
dec dh ;行号减一,00 - dh-1 共dh行
mul dh ;相乘 结果放在ax里
mov bx,ax ;偏移量

mov al,2 ;一列两个字符
dec dl ;列号减一,00 -dl-1 共dl列
mul al
add bx,ax ;偏移地址计算完成

mov di,0 ;di字符读取时候每次的偏移 每次加1
mov si,0

mov al,cl ;颜色暂时保存在al里 cx需要用到
s1:
mov ch,0 ;置0
mov cl,ds:[di] ;字符放在cl里
jcxz ok
mov ch,al
mov es:[bx+si],cx ;字符颜色一起放入显示位置

add si,2
inc di
jmp short s1
ok:
pop si ;子程序开始所有寄存器入栈
pop ss
pop ax
pop cx
pop dx
ret