鱼C论坛

 找回密码
 立即注册
查看: 2888|回复: 12

[已解决]有点不太懂

[复制链接]
发表于 2017-11-24 13:23:22 From FishC Mobile | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能^_^

您需要 登录 才可以下载或查看,没有账号?立即注册

x
15115004852921217611692.jpg 当执行call指令入栈ip为多少,执行后又为多少?
最佳答案
2017-11-24 16:23:05
本帖最后由 兰陵月 于 2017-11-24 17:37 编辑

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


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

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

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

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

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

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

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

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


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


为什么是这样?下面的代码是 Intel 指令手册中 Call 指令执行流程的一部分。
  1. tempEIP ← DEST(Offset);

  2. ....
  3. IF tempEIP is non-canonical
  4.   THEN #GP(0); FI;

  5. ....
  6. IF OperandSize = 16
  7.     THEN
  8.      Push(CS);
  9.      Push(IP);
  10.      CS ← DEST(CodeSegmentSelector);
  11.      (* Segment descriptor information also loaded *)
  12.      CS(RPL) ← CPL;
  13.      EIP ← tempEIP;
复制代码

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

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


也就是说,跳转的目标位置在读call指令的时候就已经定了,而不是push之后定的。
所谓用ds:[0eH]处的值代替IP,这时候用的是临时位置处的值,而这个值是push之前的值。
其实也很好理解,CPU说:我TMD的指令都读取完了,怎么还轮得到你PUSH一下就把我读的给改了呢
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

发表于 2017-11-24 13:43:38 | 显示全部楼层
你是要具体的ip值还是要思路?
思路就是:call执行把 call 指令的后一条所在的 ip 地址入栈,回来后能直接执行后一条。当执行了call后会跳转到ds:0Eh这里,ip应该是0Eh,因为ds是stack段的地址,整个程序也是从stack开始排序的。
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2017-11-24 15:16:06 From FishC Mobile | 显示全部楼层
丶忘却的年少o 发表于 2017-11-24 13:43
你是要具体的ip值还是要思路?
思路就是:call执行把 call 指令的后一条所在的 ip 地址入栈,回来后能直接 ...

执行call指令的两步,入栈的ip应该是inc ax 处的,而跳转jmp ds:[0eh]的ip是0eh对吗?为何入栈的也是0eh啊
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2017-11-24 16:23:05 | 显示全部楼层    本楼为最佳答案   
本帖最后由 兰陵月 于 2017-11-24 17:37 编辑

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


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

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

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

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

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

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

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

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


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


为什么是这样?下面的代码是 Intel 指令手册中 Call 指令执行流程的一部分。
  1. tempEIP ← DEST(Offset);

  2. ....
  3. IF tempEIP is non-canonical
  4.   THEN #GP(0); FI;

  5. ....
  6. IF OperandSize = 16
  7.     THEN
  8.      Push(CS);
  9.      Push(IP);
  10.      CS ← DEST(CodeSegmentSelector);
  11.      (* Segment descriptor information also loaded *)
  12.      CS(RPL) ← CPL;
  13.      EIP ← tempEIP;
复制代码

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

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


也就是说,跳转的目标位置在读call指令的时候就已经定了,而不是push之后定的。
所谓用ds:[0eH]处的值代替IP,这时候用的是临时位置处的值,而这个值是push之前的值。
其实也很好理解,CPU说:我TMD的指令都读取完了,怎么还轮得到你PUSH一下就把我读的给改了呢
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2017-11-24 16:44:21 From FishC Mobile | 显示全部楼层
兰陵月 发表于 2017-11-24 16:23
在这个题目里,压入栈里的东东到底是多少?

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

那是怎么跳到inc ax这一步的呢
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2017-11-24 16:50:11 | 显示全部楼层
xiaohaituan 发表于 2017-11-24 16:44
那是怎么跳到inc ax这一步的呢

我在跟你详细写帖子,慢了点,请稍候~~~~~
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2017-11-24 16:52:06 From FishC Mobile | 显示全部楼层
兰陵月 发表于 2017-11-24 16:50
我在跟你详细写帖子,慢了点,请稍候~~~~~

谢谢啊,
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 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:[0EH] 的时候 sp 是等于 0x10 的,ds 又等于 ss,按正常的程序来说这是一个未使用的内存。
    当这个小程序单独运行时没什么问题,因为只有这个小程序在使用这部分内存。但是它和 DEBUG 程序一起运行的时候,你们会使用同一个内存,同一个堆栈指针, DEBUG 在运行的时候也会有一些状态需要保存,DEBUG 就会把它保存在栈里面(比如程序中需要使用某个寄存器,但是寄存器的值是需要保留才能恢复到用户程序状态的,常常会出现这种操作 push ecx  ... 使用 ecx ...  pop ecx) 。只要保证运行客户程序的时候堆栈是平衡的,DEBUG 对堆栈的使用通常是不会破坏客户程序的,因为它使用的那部分堆栈的内存是客户程序没有使用到的。但是这个小程序里面直接访问 —按正常堆栈使用时还没用到的—内存,完全超出了 DEBUG 的预期,访问到了被 DEBUG 覆盖过的内存。   
    DEBUG 会使用堆栈这个现象在你CALL 指令之前早就出现了,这也就是题目中说的不要用debug单步跟踪,单步跟踪直接导致错误结果显示。只要使用了debug,在按下 T 到 CPU 执行 call [0EH]  之间, 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 去,就会形成死循环。
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2017-11-24 17:44:30 | 显示全部楼层
随便放一点debug的源代码,你可以看到,它里面也有无数个CALL、PUSH、POP、PUSHF、PUSHA之类的与栈密切相关的指令。
  1. debug2F:
  2.         pushf
  3.         cmp ax,1687h
  4. dpmidisable:                ;set [IP+1]=0 if hook 2F is to be disabled
  5.         jz @F
  6.         popf
  7.         jmp cs:[oldi2f]
  8. @@:
  9.         call cs:[oldi2f]
  10.         and ax,ax
  11.         jnz @F
  12.         mov word ptr cs:[dpmientry+0],di
  13.         mov word ptr cs:[dpmientry+2],es
  14.         mov di,offset mydpmientry
  15.         push cs
  16.         pop es
  17. @@:
  18.         iret
  19. mydpmientry:
  20.         mov cs:[dpmi32],al
  21.         call cs:[dpmientry]
  22.         jc @F
  23.         call installdpmi
  24. @@:
  25.         retf

  26.         .286

  27. ;--- client entered protected mode.
  28. ;--- inp: [sp+4] = client real-mode CS
  29.    
  30. installdpmi proc
  31.         pusha
  32.         mov bp,sp                ;[bp+16] = ret installdpmi, [bp+18]=ip, [bp+20]=cs
  33.         push ds
  34.         mov bx,cs
  35.         mov ax,000Ah        ;get a data descriptor for DEBUG's segment
  36.         int 31h
  37.         jc fataldpmierr
  38.         mov ds,ax
  39.         mov [cssel],cs
  40.         mov [dssel],ds
  41.         mov cx,2                ;alloc 2 descriptors
  42.         xor ax,ax
  43.         int 31h
  44.         jnc @F
  45. fataldpmierr:
  46.         mov ax,4CFFh
  47.         int 21h
复制代码
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2017-11-24 21:27:54 From FishC Mobile | 显示全部楼层
兰陵月 发表于 2017-11-24 17:44
随便放一点debug的源代码,你可以看到,它里面也有无数个CALL、PUSH、POP、PUSHF、PUSHA之类的与栈密切相关 ...

call指令为何称为返回指令呢,是因为保存了下一条指令的ip?是不是跳转之后会返回到保存的下一条指令处,
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2017-11-24 23:26:29 | 显示全部楼层
本帖最后由 兰陵月 于 2017-11-24 23:52 编辑
xiaohaituan 发表于 2017-11-24 21:27
call指令为何称为返回指令呢,是因为保存了下一条指令的ip?是不是跳转之后会返回到保存的下一条指令处,


CALL从来没有被称为过返回指令,不知道你是从何处听说?
下面是CALL指令的解释~~~
001.png
002.png
003.png
004.png
005.png
006.png

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


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

想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

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

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

这段解释我觉得有问题, ss:0e 的值不正是cpu 压入的下一指令的偏移地址吗?跳转的时候这个位置不应该是别的内容呀
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2017-12-12 12:39:19 | 显示全部楼层
你仔细阅读一下前面的解释咯
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Archiver|鱼C工作室 ( 粤ICP备18085999号-1 | 粤公网安备 44051102000585号)

GMT+8, 2024-3-29 07:25

Powered by Discuz! X3.4

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表