汇编语言(王爽第三版)实验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           ........

 

posted @ 2017-05-21 08:41  筑基2017  阅读(1604)  评论(1)    收藏  举报