. : : Assembly Language : : .  |  首页  |  我提出的问题  |  我参与的问题  |  我的收藏  |  消息中心   |  游客  登录  | 
刷新 | 提问 | 未解决 | 已解决 | 精华区 | 搜索 |
  《汇编语言》论坛 ->包含多个段的程序
  管理员: assembly   [回复本贴] [收藏本贴] [管理本贴] [关闭窗口]
主题 : :  栈疑问  [待解决] 回复[ 4次 ]   点击[ 470次 ]  
lyheqs
[帖 主]   [ 发表时间:2008-11-06 21:05 ]   [引用]   [回复]   [ top ] 
荣誉值:0
信誉值:0
注册日期:2008-03-22 12:51
栈疑问的总结:

第三章 实验2的实验任务 第2个问题(P75页 )一直困扰我,在论坛里看到大家关于这个问题的讨论才渐渐有了点
        头绪,问题都集中在 "T指令的执行后,会产生中断,会将相关寄存器的内容压入堆栈,以达到保护现场的目的,为下一步执行T
        指令的执行做好准备"。

        问题虽然找到,但是不上机调试还是不能完全了解问题。

        现在先不管这个问题,真正说明这个问题的在第6章的 “6.2 在代码段中使用栈”和“6.3 将数据,代码,栈放入不同的段”这两个小章节
        中的两个程序,这两个程序的目的都一样的,就是利用栈实现这两个源程序中定义好的数据按逆序存放。

“6.2 在代码段中使用栈”小章节的程序如下:

assume cs:code

code segment
        dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h
        dw 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
        
start:        MOV ax,cs
                        MOV ss,ax
                        MOV sp,30h
                        
                        MOV bx,0d
                        MOV cx,8d
                s: PUSH cs:[bx]
                         ADD bx,2d
                         loop s
                         
                         MOV bx,0d
                         MOV cx,8d
                s0: POP cs:[bx]
                        ADD bx,2d
                        loop s0
                        
                        MOV ax,4c00h
                        int 21h
code ends

END start

“6.3 将数据,代码,栈放入不同的段”小章节的程序如下:

assume cs:code,ds:data,ss:stack

data segment
        dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h
data ends

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

code segment
                start:           MOV ax,stack
                           MOV ss,ax
                         
                           MOV sp,20h
                        
                           MOV ax,data
                           MOV ds,ax
                           
                           MOV bx,0d
                           MOV cx,8d
                 s:          PUSH [bx]
                           ADD bx,2d
                           loop s
                                 
                         MOV bx,0d
                         MOV cx,8d
                s0:         POP  [bx]
                         ADD bx,2d
                         loop s0
                           
                         MOV ax,4c00h
                         int 21h
code ends
END start

在这里我把论坛里各位兄弟的讨论做个列表说明 :

 第一 :       在debug中用 T指令 来执行一条指令(以达到调试的目的)后,debug调试器会保护现场,会以先后顺序把 标志寄存器,cs寄存器,ip寄存器,bp寄存器,ax寄存器的内容依次<压入堆栈>。那么从地址开始<压入堆栈>呢? 答案是从当前的SS:SP处,将前面提到的那些寄存器的内容依次压入堆栈。那么是谁来执行这个<压入堆栈>的操作呢? 是debug调试器,还是CPU?我个人认为是debug调试器!

        之所以将 ‘压入堆栈’ 这四个字写成  <压入堆栈>, 我个人认为这个动作是debug调试器自己的行为,不是我们所写的程序的行为,这个动作不具备真正的 'sp = sp - 2 ,然后再向 ss:sp 处压入一个字' 的行为,  我们就认为这是debug调试器的 "个人行为"吧,就是有了这个所谓的"个人行为", 我们才能单步的执行我们的编写的汇编程序。 

第二 :        只要你使用调试器debug,用T命令来单步执行你的程序,就会发生上面 第一点别表 中所描述的情形。
        我们来构建以个模拟的debug调试实例:
        
        sp = 2000h, sp = ah, 其他寄存器的内容不管。
        要执行的指令有三条,分别是:
        mov ax,bx
        push ax
        pop ax
        现在我们已进入debug调试环境,即将执行的指令是mov ax,bx
        第1步:按下 T 键,mov ax,bx 这条指令被执行,随后debug 调试器 把 标志寄存器,cs, ip, bp ,ax 寄存器从 2000:a 这个地址<压入堆栈>,注意这时sp的值是不会改变的哦,它的值还是 ah, 因为这是debug的 "个人行为", 还要注意的就是 这个<压入堆栈>的动作是向底地址方向依次压入的,一共压入了5个寄存器,每个寄存器16位,就是每个寄存器占两个字节,一共压入了10个字节。从2000:a这个内存地址起,向底地址(注意不是向高地址)像 数数那样数 10个内存单元,就是 数到2000:0为止,就刚好10个字节。在2000:0 到 2000:a 这段内存单元中的内容就是 这次 T 命令执行后,debug调试器在背后‘躲着我们干的好事'。debug调试器把标志寄存器,cs, ip, bp ,ax 寄存器的内容依次压入到 2000:0 到 2000:a 这段内存。

        第2步 : 在次按下 T 键, push ax 这条指令被执行,sp = sp - 2, 注意了 sp 的内容改变了,这才是货真价实的 压入堆栈,此时ss = 2000h, sp = 8h。然后 debug 调试器又把 标志寄存器,cs, ip, bp ,ax 寄存器的内容从 2000:8这个地址<压入堆栈>。我们的心里面再从2000:8这个地址,像数数那样 向低地址数10个内存单元,数到1fff:8 这个内存单元。1fff:8到2000:8这段内存单元又被debug依次写入了标志寄存器,cs, ip, bp ,ax 寄存器的内容。

        第3步: 在次按下 T 键, pop ax 这条指令被执行,接着  sp = sp + 2, 此时sp 的内容被改变。ss = 2000h, sp = ah。然后 debug 调试器又把 标志寄存器,cs, ip, bp ,ax 寄存器的内容从 2000:a 这个地址<压入堆栈>。又从2000:a 这个地址压入了10个字节,到 2000:0为止。2000:0 到 2000:a 这段内存单元就是被压入的上面5个寄存器的内容了。

第三 :        我们现在在给“6.2 在代码段中使用栈”小章节的程序 做个小小的手术。将‘dw 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0’ 改成 'dw 0,0,0,0,0,0,0,0',只定义8个字数据类型,MOV sp,30h 改成 MOV sp,20h ,其他部分都不修改,在编译链接,运行程序会没有错,调试程序也没有错。这里说的没有错是指不会出现什么异常处理。但是这个程序所达到的意图没有实现,什么意图?就是 ‘dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h’ 定义的数据,没有按逆序存放,不信你就调试看看。当一步一步的执行 标号 s 处的 push 指令的时候,debug调试器为了保护现场,会在每一次我们执行push指令后,在
        当前的ss:sp 指向处向底地址 压入那5个寄存器的内容,已达到中断的目的,来达到我们调试程序的目的。问题的关键是我们执行push指令的时候,
        sp的值是会减2的,ss:sp 的指向始终向底地址移动,那么debug调试器会在ss:sp 出每次向底地址写入10个字节。当我们用 T 命令一步一步执行完
        s标号的循环时,ss:sp 指向的就是 'dw 0,0,0,0,0,0,0,0' 所定义的第一个字的底地址单元,那debug 调试器为了保护现场还是会在ss:sp处向底地址写入10
        个字节,那这10个字节写在那里呢? 这个程序都是在一个code 段中的,在'dw 0,0,0,0,0,0,0,0'定义语句去前接着定义的是:‘dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h’所以这10个字节被依次写入了’dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h‘ 后面的10个字节单元, 不信的话debug调试看看。
        既然’dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h‘ 所定义的数据所在内存单元的内容已经被debug改变,那么 s0标号处的循环已经没有意义,
        以为执行完s0标号循环后,逆序排序的数据就不是我们预期所希望 的内容了。

第四 :         按照第三点说的,我们这次小手术不是没有成功了? 我个人认为还是成功的,要不是为了调试,王爽老师一定会将 ‘dw 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0’ 改成 'dw 0,0,0,0,0,0,0,0',
        只定义8个字的数据就够了。我们不调试运行的话,‘dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h‘定义的数据一定是按逆序存放的了,因为我们没有用debug调试器,
        而是在命令提示符下直接运行,运行完后,在这个过程中不会有什么中断,也就没有什么保护现场,除非程序本身有语法错误,数据绝对是按逆序存。
                
第五 :        我们现在再来对 "6.3 将数据,代码,栈放入不同的段”小章节的程序在动一个小手术, 把stack段中的'dw 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0' 改成 'dw 0,0,0,0,0,0,0,0', 再把  MOV sp,20h
        改成   MOV sp,10h, 只改这两处地方, 这个程序和6.2的程序一样,也是把数据逆序存放,还有不一样的就是 这个程序包含3个段。
        编译程序,在命令提示符下运行,没有出错。在debug程序一下,用 T 单步执行, 还是象我们上面说的,debug会在每一次 T 命令执行完过后,产生中断,进而保护现场,
        将那5个寄存器 从当前ss:sp所指向的地址开始,依次写入10个字节。依次用 T 执行s标号处的循环,当执行到 cx = 3的时候,windows弹出一个异常消息窗口,说 什么无效指令,叫你关闭执行。 
        为什么会弹出 异常消息窗口,而程序不得不中止, stack 和 data 段 的内存位置是相邻的,也就是他们是贴在一起的。debug调试器在SS:SP出写入的10个字节的数据是不断的在改变,因为 ss: sp 在改变。ss:sp的地址在改变是因为我们用T 命令循环的执行的s 标号处的 push [bx] 指令,sp 也跟着减2,那么ss:sp的指向也就不同了,
        debug就会在这个新的ss:sp地址处,向底地址依次写入10个字节。
        
        至于debug调试器为什么需要把这5个寄存器的内容在每次T命令执行完过后 都要向底地址写入这5个寄存器的内容, 我也只是知道个表面,
        我们在执行T命令的时候 ,debug调试器会从上一次的ss:sp处写入的那5个寄存器的内容 在弹回到他们各自的寄存器,为下一次单步执行在做好准备。
        想一想,万一在弹回给他们各自的数据不是正确的数据 或者是 其他什么情况,那么就会出现上面的windwos异常消息窗口!
        只所以出现异常窗口,就是所弹回的数据不是原来的数据,那是什么地方的数据,我的异常消息窗口描述的是 "cs:0000 ip:0077 op: f0 37 05 0e 02"
        0000:0077? 为什么cs:ip会指向这个地址呢,没错,弹回来的就是这个地址, 在执行到cx = 3的循环的时候, 用d命令查看dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h‘
        所在内存的内容,结果让人很吃惊, 这些内容一直都没有改变,还是和原来定义的数据是一样的,就像是 c语言中用const 限定符所定义的变量一样,不能被更改,
        是因为这段内存是单独定义在段中(在源程序中),还是因为 其他什么 ? 这点始终不明白, 还要请高手解答!
        
        还有个疑惑就是依次被写入的10个字节(5个寄存器) 的内容,在要覆盖’dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h‘相应的内容的时候,因为’dw         0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h‘的内容是 不能改变的 (调试过,内容确实没有改变 ), 那么在弹回数据的时候,相应寄存器的内容在那里去找呢,cs:0000 ip:0077是从哪里冒出来的?
        如果’dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h‘的内容可以改变,那每次T命令执行后10个字节都能依次被写入, 弹回的数据也完整无误,关键就是现在’dw         0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h‘ 这段内存更本就改变不了,那么这10个字节相应被写入的数据究竟写到了那里,还是debug知道’dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h‘的内容是不能改写的,索性就忽略相应别写入的内容,返回的时候这些被弹回的数据的内容是随机的, 或者是另外的情况,debug调试器究竟是怎么来处理这种特殊的情况呢?或者DEBUG不是智能的调试器,要我们在调试程序的时候,要考虑debug调试器的开销,就像王爽老师写的例题一样,要多为调试预留些空间,特别是在程序里涉及到栈的时候?

         

         动过小手术的“6.3 将数据,代码,栈放入不同的段”小章节的程序,在单步调试过程中的错误究竟
要怎么理解才是对的?
lwbfq
[第1楼]   [ 回复时间:2008-11-10 11:29 ]   [引用]   [回复]   [ top ] 
荣誉值:56
信誉值:0
注册日期:2008-01-19 13:58
想问一下楼主,你学到第十二章了吗?这里有对单步中断的详细解释。
[问题1]: 那么是谁来执行这个<压入堆栈>的操作呢? 是debug调试器,还是CPU?我个人认为是debug调试器! 

答:当然是DEBUG调试器了,DEBUG也是一个程序,和我们编写的程序没有任何区别,只是功能不同而已。CPU是用来执行指令的,它是执行者,DEBUG才是每步的具体操作。任何指令都得通过CPU来执行。

[问题2]:之所以将 ‘压入堆栈’ 这四个字写成  <压入堆栈>, 我个人认为这个动作是debug调试器自己的行为,不是我们所写的程序的行为,这个动作不具备真正的 'sp = sp - 2 ,然后再向 ss:sp 处压入一个字' 的行为,  我们就认为这是debug调试器的 "个人行为"吧,就是有了这个所谓的"个人行为", 我们才能单步的执行我们的编写的汇编程序。  

答:这样的理解我认为是错误的,楼主把DEBUG想象的太高深了,实际上DEBUG也是由一条一条的汇编指令组成的,它没有什么特殊可言。SS:SP是标记栈顶的唯一标识,如果DEBUG的压栈行为没有执行“SP=SP-2”,那么它通过什么来判断哪里是栈顶呢,又如何判断压入了多少字呢。或许有其它的方法,但是这个功能完全可以通过一般的压栈操作就能实现。在你用debug命令查看内存信息的时候,DEBUG程序也在被执行,它的功能就是显示当前各寄存器的信息,以及内存中的数据,它和你的程序共用一个栈空间,它的任务是显示给调试者看,而它本身的执行过程你是看不到的。

[问题3]:所在内存的内容,结果让人很吃惊, 这些内容一直都没有改变,还是和原来定义的数据是一样的,就像是 c语言中用const 限定符所定义的变量一样,不能被更改, 是因为这段内存是单独定义在段中(在源程序中),还是因为 其他什么 ? 这点始终不明白, 还要请高手解答! 

答:这是由于在虚拟8086模式下造成的,当SP=0时,再对栈进行压栈操作,此时SP-2=FFFEH,就会出现溢出,这在虚拟模式下是不允许的,所以弹出警告。但是在实DOS下是没有问题的。
lyheqs
[第2楼]   [ 回复时间:2008-11-12 18:17 ]   [引用]   [回复]   [ top ] 
荣誉值:0
信誉值:0
注册日期:2008-03-22 12:51
学习了,还在探索中!
yjc173
[第3楼]   [ 回复时间:2009-02-04 13:05 ]   [引用]   [回复]   [ top ] 
荣誉值:0
信誉值:0
注册日期:2008-10-04 12:21
楼主对栈的探讨对我帮助很大,学习了!
iamalian
[第4楼]   [ 回复时间:2009-04-01 16:45 ]   [引用]   [回复]   [ top ] 
荣誉值:0
信誉值:0
注册日期:2009-03-16 22:48
好贴,顶一个
需要登录后才能回帖 -->> 请单击此处登录
    Copyright © 2006-2024   ASMEDU.NET  All Rights Reserved