荣誉值: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 将数据,代码,栈放入不同的段”小章节的程序,在单步调试过程中的错误究竟
要怎么理解才是对的? | | |