xiaohaituan 发表于 2017-11-24 13:23:22

有点不太懂

当执行call指令入栈ip为多少,执行后又为多少?{:10_254:}{:10_254:}

丶忘却的年少o 发表于 2017-11-24 13:43:38

你是要具体的ip值还是要思路?
思路就是:call执行把 call 指令的后一条所在的 ip 地址入栈,回来后能直接执行后一条。当执行了call后会跳转到ds:0Eh这里,ip应该是0Eh,因为ds是stack段的地址,整个程序也是从stack开始排序的。

xiaohaituan 发表于 2017-11-24 15:16:06

丶忘却的年少o 发表于 2017-11-24 13:43
你是要具体的ip值还是要思路?
思路就是:call执行把 call 指令的后一条所在的 ip 地址入栈,回来后能直接 ...

执行call指令的两步,入栈的ip应该是inc ax 处的,而跳转jmp ds:的ip是0eh对吗?为何入栈的也是0eh啊

兰陵月 发表于 2017-11-24 16:23:05

本帖最后由 兰陵月 于 2017-11-24 17:37 编辑

xiaohaituan 发表于 2017-11-24 15:16
执行call指令的两步,入栈的ip应该是inc ax 处的,而跳转jmp ds:的ip是0eh对吗?为何入栈的也是0eh ...

这个题目的结果是AX=3,但是错误的思路得出的结果也是一样。

想错了和想正确了的答案都一样,所以我觉得这个题目出得好。

但忽略了圆括号里面的内容的同学,那将学不到一个关键的东西,

那就是真正的CALL指令的执行过程。

首先,来个图片,在以后的学习过程中一定要牢牢记住图片中的红色文字。


(一)在这个题目里,压入栈里的东东到底是多少?我不知道,你不知道,王爽也不知道。但我们知道,压入的是指令inc ax所在处的偏移地址。
(二)但是这个执行就千万要注意了,绝不是那么想当然。

想当然的想法—错误的想法:

(1)读取指令CALL WORD PTR DS:
(2)此时IP指向下一条指令。
(3)将IP的值压入栈,压入后SP指向0EH单元,其中的值IP为下一条指令的偏移地址。
(4)读取DS:的值,将其给IP后,程序跳转到CS:IP处执行。
因为IP就是下一条指令的偏移,所以程序直接跳转到INC AX处执行。

(三)正确的思路-真正正确的执行过程,这就是为什么题目里会有圆括号里那些文字的原因。
(1)读取指令CALL WORD PTR DS:
(2)CPU知道这是一个跳转指令,所以它把要跳转的目标地址暂时放到一个临时的位置
(3)执行 push 返回地址—也就是下一条指令处偏移地址的操作
(4)把临时位置处的地址给IP,跳转到这个IP地址。

为什么是这样?下面的代码是 Intel 指令手册中 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;
C/C++代码,就算看不懂,基本也能看懂个意思。
第一行就把读取的目标地址数据(这个表述不一定准确,无所谓,你知道是这个意思就行)给了tempEIP(我不能肯定是否为寄存器重命名,毕竟这是16位汇编)。
最后一行把tempEIP给了EIP。[结合16位系统实模式,请忽略32位系统的EIP指针,把它变成16位的IP指针即可]

所以这个题目实际上执行了两次call
第一次,call跳转到cs:00处,因为第一次的ds:单元里是0
第二次,call跳转到第一条inc ax处。
当然,最终结果都是AX=3。

也就是说,跳转的目标位置在读call指令的时候就已经定了,而不是push之后定的。
所谓用ds:处的值代替IP,这时候用的是临时位置处的值,而这个值是push之前的值。
其实也很好理解,CPU说:我TMD的指令都读取完了,怎么还轮得到你PUSH一下就把我读的给改了呢{:10_257:} 。

xiaohaituan 发表于 2017-11-24 16:44:21

兰陵月 发表于 2017-11-24 16:23
在这个题目里,压入栈里的东东到底是多少?

我不知道,你不知道,王爽也不知道。


那是怎么跳到inc ax这一步的呢

兰陵月 发表于 2017-11-24 16:50:11

xiaohaituan 发表于 2017-11-24 16:44
那是怎么跳到inc ax这一步的呢

我在跟你详细写帖子,慢了点,请稍候~~~~~

xiaohaituan 发表于 2017-11-24 16:52:06

兰陵月 发表于 2017-11-24 16:50
我在跟你详细写帖子,慢了点,请稍候~~~~~

谢谢啊,{:10_281:}

兰陵月 发表于 2017-11-24 17:29:07

本帖最后由 兰陵月 于 2017-11-24 17:30 编辑

xiaohaituan 发表于 2017-11-24 16:44
那是怎么跳到inc ax这一步的呢

为什么有圆括号里的内容?

总的来说,是不正常的使用堆栈导致和 DEBUG 冲突,访问到了被 DEBUG 使用过的内存。
    通常来说,使用堆栈的规则决定了:大于等于 sp 地址的内存是使用中的,小于等于 sp 地址的内存是还没有使用的。 call word ptr ds: 的时候 sp 是等于 0x10 的,ds 又等于 ss,按正常的程序来说这是一个未使用的内存。
    当这个小程序单独运行时没什么问题,因为只有这个小程序在使用这部分内存。但是它和 DEBUG 程序一起运行的时候,你们会使用同一个内存,同一个堆栈指针, DEBUG 在运行的时候也会有一些状态需要保存,DEBUG 就会把它保存在栈里面(比如程序中需要使用某个寄存器,但是寄存器的值是需要保留才能恢复到用户程序状态的,常常会出现这种操作 push ecx... 使用 ecx ...pop ecx) 。只要保证运行客户程序的时候堆栈是平衡的,DEBUG 对堆栈的使用通常是不会破坏客户程序的,因为它使用的那部分堆栈的内存是客户程序没有使用到的。但是这个小程序里面直接访问 —按正常堆栈使用时还没用到的—内存,完全超出了 DEBUG 的预期,访问到了被 DEBUG 覆盖过的内存。   
    DEBUG 会使用堆栈这个现象在你CALL 指令之前早就出现了,这也就是题目中说的不要用debug单步跟踪,单步跟踪直接导致错误结果显示。只要使用了debug,在按下 T 到 CPU 执行 call 之间, DEBUG 会接管 CPU 来运行, 这段时间它会使用栈, 所以栈里又会压入一些状态数据。等到 CPU 执行那条 call 指令的时候 ss:0e 的值也就不知道是什么内容了。
    要解决这个问题的话(实际不是解决办法,因为这跟题目本意相左了),可以将 mov sp,16 改成mov sp, 8 。然后你就可以观察到: DEBUG 只会修改 ss:0 ~ ss:8 这部分值, ss:0e 的值会一直保持为 0,你跟踪 CALL 指令的时候会得到 IP 变成 0,程序从头开始执行的结果。 当然同样由于执行 CALL时,所 push 的返回值没有在 ss:0e 这个位置了,再次执行到 CALL 的时候还会 CALL 到 0 去,就会形成死循环。

兰陵月 发表于 2017-11-24 17:44:30

随便放一点debug的源代码,你可以看到,它里面也有无数个CALL、PUSH、POP、PUSHF、PUSHA之类的与栈密切相关的指令。
debug2F:
        pushf
        cmp ax,1687h
dpmidisable:                ;set =0 if hook 2F is to be disabled
        jz @F
        popf
        jmp cs:
@@:
        call cs:
        and ax,ax
        jnz @F
        mov word ptr cs:,di
        mov word ptr cs:,es
        mov di,offset mydpmientry
        push cs
        pop es
@@:
        iret
mydpmientry:
        mov cs:,al
        call cs:
        jc @F
        call installdpmi
@@:
        retf

        .286

;--- client entered protected mode.
;--- inp: = client real-mode CS
   
installdpmi proc
        pusha
        mov bp,sp                ; = ret installdpmi, =ip, =cs
        push ds
        mov bx,cs
        mov ax,000Ah        ;get a data descriptor for DEBUG's segment
        int 31h
        jc fataldpmierr
        mov ds,ax
        mov ,cs
        mov ,ds
        mov cx,2                ;alloc 2 descriptors
        xor ax,ax
        int 31h
        jnc @F
fataldpmierr:
        mov ax,4CFFh
        int 21h

xiaohaituan 发表于 2017-11-24 21:27:54

兰陵月 发表于 2017-11-24 17:44
随便放一点debug的源代码,你可以看到,它里面也有无数个CALL、PUSH、POP、PUSHF、PUSHA之类的与栈密切相关 ...

call指令为何称为返回指令呢,是因为保存了下一条指令的ip?是不是跳转之后会返回到保存的下一条指令处,

兰陵月 发表于 2017-11-24 23:26:29

本帖最后由 兰陵月 于 2017-11-24 23:52 编辑

xiaohaituan 发表于 2017-11-24 21:27
call指令为何称为返回指令呢,是因为保存了下一条指令的ip?是不是跳转之后会返回到保存的下一条指令处,

CALL从来没有被称为过返回指令,不知道你是从何处听说?
下面是CALL指令的解释~~~







RET(近端返回)、RETF(远端返回)、IRET(中断返回)等等,这些带“RET”的指令才是返回指令。

CALL指令执行之后,跳转到目标地址,目标地址处一般来说是一个过程。
当然,我们无聊的时候随便建立一个标号用作跳转目标地址,那也是可以的。{:10_257:}
但是,我们应该不会这么无聊吧。{:10_247:}
~回到正题。{:10_256:}
过程执行完成之后,一般来说,结束处会有一个RET(用作近调用返回,处理器从栈中弹出一个字到指令指针寄存器IP中,至于是不是用POP指令,书上没说过,但是其作用与POP IP一样);或者有一个RETF(用作远调用返回,处理器分别从栈中弹出两个字到指令指针寄存器IP和代码段寄存器CS中,作用相当于POP IP,再POP CS);又或者有一个IRET(中断返回,它是远调用返回,处理器依次从栈中弹出三个字到IP、CS、FLAGS,基本作用同RETF,只不过多弹出一个字到标志寄存器,因为调用中断的时候默认操作多压入了一个字长的标志寄存器内容)。RET和RETF还可以带操作数,操作数为16位立即数,用于返回时弹出参数。
当然,RET、RETF、IRET放在过程结束处也是“一般来说”的做法{:10_257:} ,我们同样也可以无聊弄一弄不带过程返回的情况{:10_256:} ,CPU反正不明白,它反正是执行CS:IP指向的地方{:10_245:} 。当然,返回不了了的话,咱们的程序基本也就挂了。{:10_266:}

bjyfcx 发表于 2017-12-12 11:48:58

兰陵月 发表于 2017-11-24 23:26
CALL从来没有被称为过返回指令,不知道你是从何处听说?
下面是CALL指令的解释~~~



只要使用了debug,在按下 T 到 CPU 执行 call 之间, DEBUG 会接管 CPU 来运行, 这段时间它会使用栈, 所以栈里又会压入一些状态数据。等到 CPU 执行那条 call 指令的时候 ss:0e 的值也就不知道是什么内容了。

这段解释我觉得有问题, ss:0e 的值不正是cpu 压入的下一指令的偏移地址吗?跳转的时候这个位置不应该是别的内容呀

兰陵月 发表于 2017-12-12 12:39:19

你仔细阅读一下前面的解释咯
页: [1]
查看完整版本: 有点不太懂