1
00:00:08,909 --> 00:00:10,157
同学们好

2
00:00:10,471 --> 00:00:13,909
我们这一节来学习CALL和RET指令

3
00:00:14,156 --> 00:00:17,409
CALL和RET指令是属于子程序指令

4
00:00:17,784 --> 00:00:20,532
CALL指令是用在主程序当中

5
00:00:20,848 --> 00:00:22,782
用于调用子程序的

6
00:00:23,158 --> 00:00:28,533
而RET指令是用在子程序最后

7
00:00:28,784 --> 00:00:31,721
用于返回主程序的

8
00:00:31,970 --> 00:00:35,533
当然返回的位置显然应该是

9
00:00:35,782 --> 00:00:39,534
CALL调用指令下面的一条指令

10
00:00:39,781 --> 00:00:44,094
那么实现这样的一个调用返回

11
00:00:44,220 --> 00:00:49,595
实际上它是要通过一个堆栈的访问来实现的

12
00:00:49,907 --> 00:00:50,970
也就是CALL指令

13
00:00:51,345 --> 00:00:54,782
需要把我们的返回地址压入堆栈

14
00:00:55,032 --> 00:00:57,154
这是它实现的一个功能

15
00:00:57,532 --> 00:01:01,218
然后再实现跳转到目标位置

16
00:01:01,531 --> 00:01:05,718
同样RET指令也可以认为有两个功能

17
00:01:06,031 --> 00:01:10,531
一个功能是把我们的返回地址从堆栈弹出来

18
00:01:10,843 --> 00:01:15,905
第二个功能就是转移到我们的返回地址

19
00:01:16,218 --> 00:01:20,592
这中间要用到了堆栈

20
00:01:20,906 --> 00:01:25,968
堆栈当中保存的就是CALL指令

21
00:01:26,277 --> 00:01:28,279
下一条指令的地址

22
00:01:28,528 --> 00:01:36,402
具体来说CALL指令我们有三种书写的方式

23
00:01:36,714 --> 00:01:40,026
和我们的JMP指令类同

24
00:01:40,343 --> 00:01:42,778
我们可以直接调用一个label

25
00:01:43,088 --> 00:01:47,714
也就是子程序名字用label来表示调用子程序

26
00:01:47,964 --> 00:01:52,837
也可以通过寄存器来指示这个子程序的位置

27
00:01:53,151 --> 00:01:55,342
这是寄存器的间接寻址

28
00:01:55,652 --> 00:02:00,715
也还可以通过存储器间接寻址访问到子程序

29
00:02:01,027 --> 00:02:05,274
也就是子程序的这个开始位置 入口地址

30
00:02:05,587 --> 00:02:07,903
放在了存储单元当中

31
00:02:08,216 --> 00:02:10,151
我们通过调用这个存储单元

32
00:02:10,464 --> 00:02:13,587
也可以实现调用子程序

33
00:02:13,961 --> 00:02:16,901
和JMP指令也类同的

34
00:02:17,211 --> 00:02:20,400
它也分成段内的和段间的调用

35
00:02:20,714 --> 00:02:22,840
或者叫近调用 远调用

36
00:02:23,090 --> 00:02:27,654
也支持所谓的相对寻址 直接寻址或间接寻址

37
00:02:27,906 --> 00:02:34,031
返回指令也有两个形式

38
00:02:34,282 --> 00:02:40,281
一个是带i16的参数 一个是不带

39
00:02:40,594 --> 00:02:42,342
而带这个i16的参数

40
00:02:42,653 --> 00:02:45,469
实际上是又增加了一步功能

41
00:02:45,779 --> 00:02:52,904
把ESP这个堆栈的栈顶指针进行了增量

42
00:02:53,215 --> 00:02:56,715
当然很多时候我们可能不需要

43
00:02:57,093 --> 00:03:00,904
用对ESP增量的指令

44
00:03:01,092 --> 00:03:04,633
我们就直接写RET return 返回

45
00:03:04,942 --> 00:03:08,756
也就说ESP并没有改变

46
00:03:09,068 --> 00:03:11,941
或者说仅仅是RET指令本身的改变

47
00:03:12,194 --> 00:03:14,629
没有再附加上进行改变

48
00:03:14,944 --> 00:03:17,881
CALL指令 RET指令

49
00:03:18,191 --> 00:03:22,005
我们前面说了都分段内和段间

50
00:03:22,319 --> 00:03:25,065
在我们的指令助记符当中并没有区别

51
00:03:25,318 --> 00:03:29,130
但是在我们的汇编 连接过程当中

52
00:03:29,378 --> 00:03:32,756
我们的汇编程序会正确的区别

53
00:03:33,005 --> 00:03:35,067
只要我们编写的程序是正确的

54
00:03:35,256 --> 00:03:40,566
那么我们再具体的来看一看

55
00:03:40,942 --> 00:03:46,001
在段内的调用和返回指令的功能

56
00:03:46,256 --> 00:03:50,878
我们把段间的 略微复杂的我们先放一放

57
00:03:51,188 --> 00:03:55,128
先看一看段内调用和返回的（指令）

58
00:03:55,441 --> 00:03:57,816
那么实际上段内的（调用和返回）

59
00:03:58,066 --> 00:04:01,063
因为只涉及到对ESP的调整

60
00:04:01,379 --> 00:04:10,439
那么段内的调用只涉及到更改偏移地址

61
00:04:10,750 --> 00:04:17,626
这个时候我们只需要保存返回地址

62
00:04:17,878 --> 00:04:19,753
当然是通过压入堆栈

63
00:04:20,001 --> 00:04:20,877
那么压入堆栈

64
00:04:21,187 --> 00:04:24,000
我们可以用一条push指令来实现

65
00:04:24,253 --> 00:04:28,752
所以说入栈返回地址这一个功能

66
00:04:29,062 --> 00:04:32,187
就可以用一条push指令来替代

67
00:04:32,501 --> 00:04:38,874
当然压入的内容应该是CALL指令之后的

68
00:04:39,187 --> 00:04:41,127
那一条指令的地址

69
00:04:41,436 --> 00:04:42,874
那我们这里就假设了

70
00:04:43,186 --> 00:04:45,810
是用了一个next标号来代替

71
00:04:46,126 --> 00:04:51,750
那么第二个功能是实现跳转

72
00:04:52,062 --> 00:04:54,376
那么jmp指令就是实现跳转

73
00:04:54,686 --> 00:04:57,187
所以说我们完全可以用一条jmp指令

74
00:04:57,498 --> 00:05:00,309
来替代CALL指令的第二个功能

75
00:05:00,625 --> 00:05:04,000
换句话说CALL指令功能在这儿

76
00:05:04,311 --> 00:05:08,312
就可以用一条push指令和一条jmp指令来替代

77
00:05:08,560 --> 00:05:12,188
当然这样书写起来会繁琐

78
00:05:12,498 --> 00:05:21,310
所以调用指令还是非常有必要设计的

80
00:05:21,620 --> 00:05:25,122
那RET指令

81
00:05:25,436 --> 00:05:28,307
就这样的一条指令就实现了

82
00:05:28,683 --> 00:05:33,185
把堆栈的栈顶内容弹出来

83
00:05:33,558 --> 00:05:35,372
直接弹到的是EIP

84
00:05:35,684 --> 00:05:41,308
而EIP实际上就是下一条指令

85
00:05:41,620 --> 00:05:44,245
next这个标号的这个地址

86
00:05:44,559 --> 00:05:51,557
弹到EIP就是去它指示的这个位置去执行

87
00:05:51,872 --> 00:05:57,058
所以说似乎它已经包含了有JMP指令的功能了

88
00:05:57,370 --> 00:06:05,930
总之我们的CALL指令（调用）和RET返回指令

89
00:06:06,183 --> 00:06:09,307
是实现了主程序调用和子程序的返回

90
00:06:09,619 --> 00:06:12,558
其中要涉及到堆栈

91
00:06:12,808 --> 00:06:15,557
也就是把我们的返回地址压入堆栈

92
00:06:15,870 --> 00:06:18,368
不知道你理解了没有

93
00:06:18,681 --> 00:06:23,495
你不妨看一看下面这个程序片段

94
00:06:23,808 --> 00:06:29,369
第一条指令实现了对下一个标号处的调用

95
00:06:29,680 --> 00:06:38,370
而下一条指令是把堆栈顶的内容弹出到EAX

96
00:06:38,681 --> 00:06:43,056
这个时候EAX会是什么呢

97
00:06:43,366 --> 00:06:47,680
回忆一下堆栈里头的内容

98
00:06:47,993 --> 00:06:50,054
希望你能够得到答案

99
00:06:50,368 --> 00:06:55,117
好 这一节讲到这儿


