assume cs:code
stack segment
dw 8 dup (0)
stack ends
code segment
start:
mov ax,stack
mov ss,ax
mov sp,16
mov ds,ax
mov ax,0
call word ptr ds:[0EH]
inc ax
inc ax
inc ax
mov ax,4c00h
int 21h
code ends
end start
问题(1)
我发现call word ptr ds:[0EH]执行后,程序又跳转到了第一条语句,而不是下一条Inc ax。书中讲到call语句相当于:push ip,jmp word ptr 内存地址,即先把ip=11存入ds:[0eh],也就是ss:[0eh],再执行jmp时从栈中[0eh]处取出ip=11.而实际程序却是先跳到了第一条语句,说明是先jmp,取出栈中[0eh]处(开始时为0)后push ip的,与书中讲的有矛盾,那么call word ptr ds:[0EH]这条语句到底具体是如何执行的?
问题(2)
我用debug执行上面的代码,到call指令之前我看了一下ss:[0eh]存的数据为01a3。当call执行完了ss:[0eh]存的数据变为1100没错, 但是ip却变为了7302,既不是01a3也不是0011呢,这是为什么呢,7302是从哪来的?
请高手们多多指点,谢谢了!
------解决思路----------------------
1. 直接运行,第一次运行 call 是到第一条语句,因为 ss:[0eh] 那里应该是 0000。但这个 call 指令会将下面的 inc ax 的地址 0011 作为返回地址压栈到 ss:[0eh] 处,这样再次从开始第一条指令运行到 call 指令时,就会转到 0011 处的 inc ax 上继续向下运行了。
2. 如上面一开始就强调的,这个代码必须“直接运行”,不能单步或中断,因为这些操作都会使用到用户堆栈从而改写了 ss:[0eh] 处的内容,导致在运行到 call 时不会转到 0000 处而致执行流程跑飞失控;这就是你说的“IP却变为7302 ”;7302 的来源,应该是中断压栈的标记寄存器留下来的吧。
------解决思路----------------------
下面是节选的 Inter 指令手册中 Call 指令执行流程的一部分:
....
tempEIP ← DEST(Offset);
....
IF tempEIP is non-canonical
THEN #GP(0); FI;
....
IF OperandSize = 16
THEN
Push(CS);
Push(IP);
CS ← DEST(CodeSegmentSelector);
(* Segment descriptor information also loaded *)
CS(RPL) ← CPL;
EIP ← tempEIP;
....
CALL 指令执行的时候,首先会把跳转的目标地址读到一个临时的位置,然后执行 push 返回地址的操作,然后在跳转到刚刚读的地址。 所以,这个你是不能用两条组合指令来模拟 CALL 指令的过程的。
------解决思路----------------------
第 2 个问题: 总的来说,是 不正常的使用堆栈导致和 DEBUG 冲突,访问到了被 DEBUG 使用过的内存。
通常来说,使用堆栈的规则决定了: 大于等于 sp 地址的内存是使用中的,小于等于 sp 地址的内存是还没有使用的。你在 call word ptr ds:[0EH] 的时候 sp 是等于 0x10 的,ds 又等于 ss,按正常的程序来说这是一个未使用的内存。(相当于在 C 语言里使用一个未初始化的变量,值是不确定的)
当你的程序单独运行时没什么问题,因为只有你的程序在使用你的这部分内存。但是和 DEBUG 程序一起运行的时候,你们会使用同一个内存,同一个堆栈指针, DEBUG 在运行的时候也会有一些状态需要保存,DEBUG 就会把它保存在栈里面(比如程序中需要使用某个寄存器,但是寄存器的值是需要保留才能恢复到用户程序状态的,常常会出现这种操作 push ecx ... 使用 ecx ... pop ecx) 只要保证运行客户程序的时候堆栈是平的,DEBUG 对堆栈的使用通常是不会破坏客户程序的,因为它使用的那部分堆栈的内存是客户程序没有使用到的。但是你这里直接访问 按正常堆栈使用时还没用到的 内存,完全超出了 DEBUG 的预期,访问到了被 DEBUG 覆盖过的内存。
其实,DEBUG 会使用堆栈这个现象在你 CALL 指令之前早就出现了,”我用debug执行上面的代码,到call指令之前我看了一下ss:[0eh]存的数据为01a3。“ 你的堆栈是初始化为 0 的,中间的代码又没有改变堆栈上的值,为什么到 call 指令这个地方的时候 ss:[0eh] 会变成 01a3 ??
在你按下 T 到 CPU 执行 call [0EH] 之间, DEBUG 还会接管 CPU 来运行, 这段时间它又使用了堆栈, 所以到 CPU 执行那条 call 指令的时候 ss:0e 的值也已经不是 01a3 了。
所以,要解决你这个问题,只需要 mov sp,16 改成 mov sp, 8 就可以观察到: DEBUG 只会修改 ss:0 ~ ss:8 这部分值, ss:0e 的值会一直保持为 0,你跟踪 CALL 指令的时候会得到 IP 变成 0,程序从头开始执行的结果。 但是由于你 CALL所 push 的返回值没有在 ss:0e 这个位置了,再次执行到 CALL 的时候还会 CALL 到 0 去,形成死循环。
最后,附上 DEBUG 程序的源代码,以供参考:http://www.ibiblio.org/pub/micro/pc-stuff/freedos/files/dos/debug/DEBUG125.zip