0%

8086指令基础 - 子程序指令

最近在学习 16位汇编编程,所以顺便将每日所学记录下来,一方面为了巩固学习的知识,另一方面也为同样在学习汇编开发的童鞋们提供一份参考。

运行环境:

  • 操作系统: Windows 10家庭版
  • 编译器:Windows XP Debug

子程序指令

  • 子程序是完成特定功能的一段程序
  • 当主程序需要执行这个功能是,采用call指令调用指令转移到子程序的起始处执行
  • 当运行完子程序功能,采用RET返回指令回到主程序继续执行

CALL指令

demo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
MyStack	segment	stack
db 256 dup (?)
MyStack ends

MyData segment
g_szHello db 'Hello World!',0dh,0ah,'$'
MyData ends




MyCode segment
MyPuts:

mov dx,offset g_szHello
mov ah,9
int 21h
;ret
pop ax
jmp ax

start:
mov ax,MyData
mov ds,ax
;call MyPuts
mov ax,offset Next ;模拟call指令
push ax
jmp MyPuts
Next:
mov ax,4c00h
int 21h
MyCode ends
end start

如果我们需要call的子程序中存在参数,对上述代码进行修改:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
MyStack	segment	stack
db 256 dup (?)
MyStack ends

MyData segment
g_szHello db 'Hello World!',0dh,0ah,'$'
MyData ends




MyCode segment
MyPuts:

mov dx,cx ;寄存器传参
mov ah,9
int 21h
ret

start:
mov ax,MyData
mov ds,ax
mov cx,offset g_szHello ;使用寄存器存放变量的偏移地址
call MyPuts
Next:
mov ax,4c00h
int 21h
MyCode ends
end start

以上代码会占用寄存器资源,我们可以使用栈传递

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
MyStack	segment	stack
db 256 dup (?)
MyStack ends

MyData segment
g_szHello db 'Hello World!',0dh,0ah,'$'
MyData ends




MyCode segment
MyPuts:
mov bp,sp
mov ax,[bp + 2] ;寄存器传参
mov dx,ax
mov ah,9
int 21h
ret

start:
mov ax,MyData
mov ds,ax
xor cx,cx ;int cx = 0;
LOOP_BEGIN:

cmp cx,10
jg LOOP_END ;while(cx < 10){

mov ax,offset g_szHello ;使用寄存器存放变量的偏移地址
push ax
call MyPuts
add sp,2 ;调用方负责平衡堆栈

inc cx ;cx++
jmp LOOP_BEGIN ;}

LOOP_END:
mov ax,4c00h
int 21h
MyCode ends
end start

CALL指令其实是做了两个动作,将CALL下一条指令的IP入栈,在JMP到指定的函数的地址处。

1
2
3
4
5
段内调用——入栈偏移地址IP
SP←SP-2,SS:[SP]←IP
段间调用——入栈偏移地址IP和段地址CS
SP←SP-2,SS:[SP]←IP
SP←SP-2,SS:[SP]←CS

RET

1.根据段内和段间、有无参数,分成4种类型

  • RET(或者RETN) ;无参数段内返回
  • RET(或者RETN) i16 ;有参数段内返回
  • RETF ;无参数段间返回
  • RETF i16 ;有参数段间返回

2.需要弹出CALL指令压入堆栈的返回地址
段内返回——出栈偏移地址IP
IP←SS:[SP], SP←SP+2
段间返回——出栈偏移地址IP和段地址CS
IP←SS:[SP],SP←SP+2
CS←SS:[SP],SP←SP+2

RET指令的作用其实就是POP IP(IP只有系统可以修改)
RETF指令的作用其实就是POP IP 和POP CS

段间转移必须写call far ptr labal,并且必须与retf相配合