汇编语言(王爽第三版)实验3 编程、编译、连接、跟踪
实验3 编程、编译、连接、跟踪
(1)将下面的程序保存为t1.asm文件,将其生产可执行文件t1.exe。
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
(2)用debug跟踪t1.exe的执行过程,写出每一步骤执行后,相关寄存器中的内容和栈顶的内容。
(3)PSP的头两个字节是CD 20,用debug加载t1.exe,查看PSP的内容。
实验结果:
【1】编译、连接t1.asm汇编源程序。
1)在windows XP中,打开cmd窗口,编译t1.asm程序。如下图:
E:\assembly>masm t1.asm
Microsoft (R) MASM Compatibility Driver
Copyright (C) Microsoft Corp 1993. All rights reserved.
Invoking: ML.EXE /I. /Zm /c /Ta t1.asm
Microsoft (R) Macro Assembler Version 6.15.8803
Patched for you by promethee [ECL] in the year 2001 - enjoy
Copyright (C) Microsoft Corp 1981-2000. All rights reserved.
Assembling: t1.asm
如果没有任何错误,编译器会生成一个t1.obj的文件。
如果有语法等严重的错误,编译器会给出错误信息,你可以根据错误信息,修改源代码相关的行。
连接这个obj文件,并生成t1.exe文件:如下图:
E:\assembly>link t1.obj
Microsoft (R) Segmented Executable Linker Version 5.60.339 Dec 5 1994
Copyright (C) Microsoft Corp 1984-1993. All rights reserved.
Run File [t1.exe]: t1.exe
List File [nul.map]:
Libraries [.lib]:
Definitions File [nul.def]:
LINK : warning L4021: no stack segment
LINK : warning L4038: program has no starting address
连接讲解:
1)其中运行文件:我们输入我们将要生成的可执行程序(例如.exe)文件的名称。
List File [nul.map]: 我们回车,没有列表文件;
Libraries [.lib]: 我们回车,没有库文件
Definitions File [nul.def]: 我们回车,没有交叉定义文件
2)LINK : warning L4021: no stack segment
警告信息:没有栈段定义;
LINK : warning L4038: program has no starting address
警告信息:程序没有起始地址。
你可以根据连接程序的提示信息来修正你的源程序;一般的警告信息,我们都忽略了。
关于编译器的详细介绍,请参考相应的编译器手册。
【2】用debug跟踪t1.exe的执行过程
在命令提示符下键入:debug t1.exe
使用r命令查看寄存器信息。
-r
AX=0000 BX=0000 CX=0016 DX=0000 SP=0000 BP=0000 SI=0000 DI=0000
DS=0B55 ES=0B55 SS=0B65 CS=0B65 IP=0000 NV UP EI PL NZ NA PO NC
0B65:0000 B80020 MOV AX,2000
1)CX寄存器介绍:
cx=0016; 含义:cx寄存器是通用寄存器,也是计数寄存器,cx还有一个功能是在程序最初始记录程序代码的字节数;我们使用d命令查看它
-d cs:0
0B65:0000 B8 00 20 8E D0 BC 00 00-83 C4 0A 58 5B 50 53 58 .. ........X[PSX
0B65:0010 5B B8 00 4C CD 21 A9 02-00 75 05 2E FF 06 48 91 [..L.!...u....H.
红色标记的是执行的机器码。注意此时的0016是16进制的,也就是22个字节,你数数!
你也可以使用U命令查看汇编指令和机器码,一样。
-u cs:0
0B65:0000 B80020 MOV AX,2000
0B65:0003 8ED0 MOV SS,AX
0B65:0005 BC0000 MOV SP,0000
0B65:0008 83C40A ADD SP,+0A
0B65:000B 58 POP AX
0B65:000C 5B POP BX
0B65:000D 50 PUSH AX
0B65:000E 53 PUSH BX
0B65:000F 58 POP AX
0B65:0010 5B POP BX
0B65:0011 B8004C MOV AX,4C00
0B65:0014 CD21 INT 21
各寄存器状态值:CS=0B65 IP=0000 SS=0B65
2)执行代码
mov ax, 2000H
mov ss, ax
mov sp, 0
代码含义:将2000H段开始的内存单元创建一个栈结构。栈顶指针ss:sp指向00H。
这就意味着如果栈结构是空的,那么它的栈容量是64K。
怎么没有了那些t命令的中断例程保存的寄存器变量了?你在1FFF:0010H处,也就是它的低地址处看看有吗?
执行后:
AX=2000 BX=0000 CX=0016 DX=0000 SP=0000 BP=0000 SI=0000 DI=0000
DS=0B55 ES=0B55 SS=2000 CS=0B65 IP=0008 NV UP EI PL NZ NA PO NC
3)执行代码:
add sp, 10
代码含义:明确说明sp指向10,也就是说我们创建的栈结构空间是10个字节;
使用d命令查看ss:0内存段,发现那些久违的寄存器变量数据就存储在这里,从2000:0a向低地址存储;我们不用管他们。
执行后:
AX=2000 BX=0000 CX=0016 DX=0000 SP=000A BP=0000 SI=0000 DI=0000
DS=0B55 ES=0B55 SS=2000 CS=0B65 IP=000B NV UP EI PL NZ NA PE NC
4)执行代码:
pop ax
pop bx
代码含义:pop ax;将ss:sp指向的字单元赋值给ax=00;sp=sp+2=000A+2=000CH;
pop bx;将ss:sp指向的字单元赋值给bx=00;sp=sp+2=000C+2=000EH;
见下图中的红色标记;此时栈顶已经超界了。蓝色部分是给栈分配的内存空间。注意此时栈中有数据吗?有!就是蓝色部分,只不过是中断例程保护的寄存器变量的值。
-d ss:0
2000:0000 00 20 00 00 0B 00 65 0B-68 05 00 00 00 00 00 00 . ....e.h.......
执行后:
AX=0000 BX=0000 CX=0016 DX=0000 SP=000E BP=0000 SI=0000 DI=0000
DS=0B55 ES=0B55 SS=2000 CS=0B65 IP=000D NV UP EI PL NZ NA PE NC
使用d查看栈段内存:
-d ss:0
2000:0000 00 20 00 00 00 00 00 00-0D 00 65 0B 68 05 00 00 . ........e.h...
我们发现sp=000EH,也就是栈底向高地址发展了,栈结构占用的内存空间也变大了。
5)执行代码:
push ax
push bx
代码含义:push ax;首先sp=sp-2=000EH-2=000CH;然后将ax值压栈,此时ax=00,
push bx;首先sp=sp-2=000CH-2=000AH;然后将bx值压栈,此时bx=00,
执行后:
AX=0000 BX=0000 CX=0016 DX=0000 SP=000A BP=0000 SI=0000 DI=0000
DS=0B55 ES=0B55 SS=2000 CS=0B65 IP=000F NV UP EI PL NZ NA PE NC
使用d查看栈段内存:
-d ss:0
2000:0000 00 00 00 00 0F 00 65 0B-68 05 00 00 00 00 00 00 ......e.h.......
内存中红色标记的部分就是压栈的2个字单元。
6)执行代码:
pop ax
pop bx
代码含义:pop ax;首先将ss:sp指向的内存字单元赋值给ax,也就是2000:000aH指向的字单元(00 00);然后sp=sp+2=000AH+2=000CH;
pop bx;首先将ss:sp指向的内存字单元赋值给bx,也就是2000:000cH指向的字单元(00 00);然后sp=sp+2=000CH+2=000EH;
执行后:
AX=0000 BX=0000 CX=0016 DX=0000 SP=000E BP=0000 SI=0000 DI=0000
DS=0B55 ES=0B55 SS=2000 CS=0B65 IP=0011 NV UP EI PL NZ NA PE NC
使用d查看栈段内存:
-d ss:0
2000:0000 00 00 00 00 00 00 00 00-11 00 65 0B 68 05 00 00 ..........e.h...此时sp指向了内存单元红色的地址。
总结:
从CPU层面理解熟悉栈的结构,栈顶指针的变化;对push和pop两个指令执行后,CPU的执行步骤。
从编程角度要注意push和pop指令对栈中数据的顺序。
这个貌似sp有点变化,其他的寄存器死气沉沉的。呵呵
我们使用栈时要小心,这个例子,栈结构的内存是没有使用的,它的栈顶随意的超界,没有事情,如果有数据的话,就破坏程序了。
(3)PSP的头两个字节是CD 20,用debug加载t1.exe,查看PSP的内容。
首先要搞清楚,PSP段在什么地方?在代码段的前100H处。也就是上面的ds:0处。ds:100内存单元就是代码段的内容。你看看ds与cs值有什么联系?
PSP头的作用,请看书吧。
使用d命令查看
-d ds:0
0B55:0000 CD 20 FF 9F 00 9A F0 FE-1D F0 4F 03 68 05 8A 03 . ........O.h...
0B55:0010 68 05 17 03 68 05 57 05-01 01 01 00 02 FF FF FF h...h.W.........
0B55:0020 FF FF FF FF FF FF FF FF-FF FF FF FF 15 0B 4C 01 ..............L.
0B55:0030 28 0A 14 00 18 00 55 0B-FF FF FF FF 00 00 00 00 (.....U.........
0B55:0040 05 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
0B55:0050 CD 21 CB 00 00 00 00 00-00 00 00 00 00 20 20 20 .!...........
0B55:0060 20 20 20 20 20 20 20 20-00 00 00 00 00 20 20 20 .....
0B55:0070 20 20 20 20 20 20 20 20-00 00 00 00 00 00 00 00 ........