王爽 《汇编语言》 读书笔记 十 CALL和RET指令
第十章 CALL和RET指令
call和ret都是转移指令,它们都修改IP,或同时修改CS和IP,常用于子程序的设计。
10.1 ret 和 retf
ret用栈中的数据,修改IP的内容,从而实现近转移
retf指令用栈中的数据,修改CS和IP的内容,从而实现远转移
ret指令
1) (IP) = ((ss)*16 + (sp))
2) (sp) = (sp) + 2
相当于cpu执行来pop IP
retf指令
1) (ip) = ((ss)*16 + (sp))
2) (sp) = (sp) + 2
3) (cs) = ((ss) * 16 + (sp))
4) (sp) = (sp) + 2
相当于cpu执行来pop IP , pop CS
以下代码执行以后将跳转到代码段第一条指令
assume cs:code
stack segment
db 16 dup (0)
stack ends
code segment
mov ax, 4c00h
int 21h
start: mov ax, stack
mov ss, ax
mov sp, 10h
mov ax, 0
push ax
mov bx, 0
ret
code ends
end start
将代码跳转至1000:0000H处
assume cs:code
stack segment
db 16 dup (0)
stack ends
code segment
start: mov ax, stack
mov ss, ax
mov sp, 10h
mov ax, 1000h
push ax
mov ax, 0
push ax
retf ; equal pop ip, pop cs
code ends
end start
10.2 call指令
call执行
1)将当前的IP或cs和IP压入栈
2)转移
call不能实现短转移,转移方法和jmp指令的原理相同。
10.3 根据位移进行转移的call指令
call 标号(将当前的IP压栈后,转移到标号处执行指令
1) (sp) = (sp) - 2
((ss)* 16 + (sp)) = (ip)
2) (ip) = (ip) + 16位位移
16位位移=标号处的地址-call指令后的第一个字节的地址;
16位位移的范围为-32768~32767,用补码表示
16位位移由编译时算出
即
push IP
jmp near ptr 标号
10.4 转移的目的地址在指令中的call指令
call far ptr 标号 实现段间转移
1) (sp) = (sp) -2
((ss)*16 + (sp)) = (cs)
(sp) = (sp) - 2
((ss)*16 + (sp)) = (ip)
2) (cs) = 标号所在的段地址
(IP) = 标号所在的偏移地址
相当于
push cs
push ip
jmp far ptr 标号
10.5 转移地址在寄存器中的call指令
call 16位 reg
(sp) = (sp) - 2
((ss)*16 + (sp)) = (ip)
(ip) = (16位reg)
即
push IP
jum 16位reg
10.6 转移地址在内存中的call指令
两种格式
1) call word ptr 内存单元地址
push IP
jmp word ptr 内存单元地址
2) call dword ptr 内存单元地址
push CS
push IP
jmp dword ptr 内存单元地址
监测点10.5
1)
assume cs:code
stack segment
dw 8 dup (0)
stack ends
code segment
start: mov ax, stack
mov ss, ax
mov sp, 10h
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
该代码执行到 call word ptr ds:[0eh]
的时候
首先
a. 执行push ip (此时的ip是指向下一条inc ax的地址这里称为A) sp = 10h -2 = 0eh ((ss)*16 + (sp)) = A
b. jmp word ptr ds:[0eh] 这里的[0eh]就是ss:sp指向的地址A 因此直接跳转至A处执行call语句后面的inc ax
然后inc 3次 ax
最后ax = 3
使用debug单步调试的时候debug程序也会使用相同的stack因此会产生奇怪的问题。
2)ax = 1, bx = 0
注意call dword ptr 会产生的几个push pop 会改变栈的值和sp的值
10.7 call 和 ret 的配合使用
利用call 和 ret 实现的子程序框架如下
assume cs:code
stack segment
dw 8 dup (0)
stack ends
code segment
main: ;
;
; call sub1
;
;
mov ax, 4c00h
int 21h
sub1: ;
;
; call sub2
;
ret
sub2: ;
;
ret
code ends
end main
10.8 mul指令
1) 两个相乘的数,要么都是8位。要么都是16位。 如果是8位一个默认在AL 另一个存放在8位reg 或内存字节单元中
如果是16位,一个默认中AX中,另一个放在16位reg或者内存字单元中
2)结果,如果是8位乘法,结果默认放在AX中; 如果是16位乘法,结果高位默认在DX,低位在AX中存放
mul reg
mul 内存单元
内存单元可以用不同的寻址方式
mul byte ptr ds:[0]
含义: (ax) = (al) * ((ds)*16 + 0)
mul word ptr [bx + si + 8]
(ax) = (ax) * ((ds)*16 + (bx) + (si) + 8) 结果的低16位
(dx) = (ax) * ((ds)*16 + (bx) + (si) + 8) 结果的高16位
计算100*10
mov al, 100
mov bl, 10
mul bl
计算 100*10000
mov ax, 100
mov bx, 10000
mul bx
10.9 模块化程序设计
10.10 参数和结果传递的问题
应如何存储子程序需要的参数和产生的返回值
1)将参数N存储在什么地方?
2)计算得到的值,存储在什么地方?
计算3次方例子 参数存放于BX 返回值存放在dx 和 ax
cube: mov ax, bx
mul bx
mul bx
ret
编程计算 data段组中第一组数据的三次方,结果报错中后面一组的dword单元中
assume cs:code
data segment
dw 1, 2, 3, 4, 5, 6, 7, 8
dd 0, 0, 0, 0, 0, 0, 0, 0
data ends
code segment
start: mov ax, data
mov ds, ax
mov si, 0 ; ds:si point to first line
mov di, 10h ; ds:di point to the second line
mov cx, 8
s: mov bx, [si]
call cube
mov [di], ax
mov [di].2, dx
add si, 2 ; ds:si point to the next data
add di, 4 ; ds:di point to the next data
loop s
mov ax, 4c00h
int 21h
cube: mov ax, bx
mul bx
mul bx
ret
code ends
end start
将批量数据的首地址放在寄存器中,
返回值也同样。
例子 将一个全是字母的字符串转换为大写
capital: and byte ptr [si], 11011111b
inc si
loop capital
ret
编程,将data段中的字符串转换为大写
assume cs:code
data segment
db 'conversation' ;12 characters
data ends
code segment
start: mov ax, data
mov ds, ax
mov si, 0
mov cx, 12
call capital
mov ax, 4c00h
int 21h
capital: and byte ptr [si], 11011111b
inc si
loop capital
ret
code ends
end start
运行结果
还可以用栈来传递参数
10.12 寄存器冲突的问题
转换一个以0结尾的字符串 ,采用jcxz
capital: mov cl, [si]
mov ch, 0
jcxz ok
and byte ptr [si], 11011111b
inc si
jmp short capital
ok: ret
将data段中的字符串转换为大写
assume cs:code
data segment
db 'word', 0
db 'unix', 0
db 'wind', 0
db 'good', 0
data ends
stack segment
db 16 dup (0)
stack ends
code segment
start: mov ax, data
mov ds, ax
mov bx, 0
mov ax, stack
mov ss, ax
mov sp, 10h
mov cx, 4
s: push cx
mov si, bx
call capital
add bx, 5
pop cx
loop s
mov ax, 4c00h
int 21h
capital: mov cl, [si]
mov ch, 0
jcxz ok
and byte ptr [si], 11011111b
inc si
jmp short capital
ok: ret
code ends
end start
运行结果
子程序等开始: 子程序中使用的寄存器入栈
子程序等内容
子程序中使用的寄存器出栈
返回
改进后的capital
capital: push cx
push si
change: mov cl, [si]
mov ch, 0
jcxz ok
and byte ptr [si], 11011111b
inc si
jmp short change
ok: pop si
pop cx
ret
实验10 编写子程序
1. 显示字符串
子程序描述
名称:show_str
功能:在指定的位置,用指定的颜色,显示一个用0结束的字符串
参数:(dh)=行号(取值范围0~24),(dl)=列号(取值范围0~79)
(cl)=颜色, ds:si指向字符串的首地址
返回:无
代码如下
assume cs:code
data segment
db 'Welcome to masm!', 0
data ends
stack segment
db 32 dup (0)
stack ends
code segment
start: mov ax, stack
mov ss, ax
mov sp, 20h
mov dh, 8
mov dl, 3
mov cl, 2
mov ax, data
mov ds, ax
mov si, 0
call show_str
mov ax, 4c00h
int 21h
show_str: push ax
push bx
push cx
push dx
push bp
push si
push di
push es
mov ax, 0b800h ; load the first address to vram
mov es, ax
mov bp, 0 ; reset bp to 0
mov ax, 0 ; reset al ah
mov al, dh
mov bl, 0a0h
mul bl ; dh x 160 = line offset
add bp, ax ; add the line offset
mov ax, 0 ; reset al ah
mov al, dl
mov bl, 2
mul bl ; dl x 2 = column offset
add bp, ax ; add the column offset
mov di, 0
mov ah, cl ; store the color info
change: mov cl, [si]
mov ch, 0
jcxz ok ; check whether pointer to '\0'
mov al, cl ; store the char
mov es:[bp][di], ax ;
inc si
add di, 2
jmp short change
ok: pop es
pop di
pop si
pop bp
pop dx
pop cx
pop bx
pop ax
ret
code ends
end start
运行结果
2 。 解决除法溢出的问题
如果执行8位除法的结果大于8位则会除法溢出
例子
assume cs:code
code segment
start: mov bh, 1
mov ax, 10000
div bh
mov ax, 4c00h
int 21h
code ends
end start
在debug中执行则提示div overflow
同样16位除法也可能存在此问题。
mov ax, 1000h
mov dx, 1
mov bx, 1
div bx
设计子程序解决除法溢出的问题divdw
子程序描述
名称:divdw
功能:进行不会产生溢出的除法运算,被除数为dword型,除数为word型,结果为dword型。
参数:
(ax) = dword型数据的低16位
(bx) = dword型数据的高16位
(cx) = 除数
返回: (dx) = 结果的高16位,(ax)=结果的低16位 (cx) = 余数
代码
assume cs:code
stack segment
db 16 dup (0)
stack ends
code segment
start: mov ax, stack
mov ss, ax
mov sp, 10h
mov ax, 4240h
mov dx, 000fh
mov cx, 0ah
call divdw
mov ax, 4c00h
int 21h
divdw: push bx
push ax ; push the low 16bit
mov ax, dx ; move the high 16bit to ax
mov dx, 0 ; set the high 16bit to zero
div cx ; calc ax / cx
; dx is mod, ax is result
mov bx, ax ; store the result to bx
pop ax ; pop up the low 16bit
div cx ; calc the low 16bit div
mov cx, dx ; store the mod
mov dx, bx ; store the high 16bit result
pop bx
ret
code ends
end start
执行结果
3.数值显示
将十进制数据在屏幕上显示出来
子程序名称:dtoc
功能:将word型数据转变为十进制数的字符串,字符串以0为结尾符。
参数:(ax)=word型数据
ds:si指向字符串的首地址
返回:无
编程将数据12666以十进制的形式显示在屏幕的第八行3列,用绿色显示出来。需要调用实验一的子程序 show_str
assume cs:code
data segment
db 10 dup (0)
data ends
stack segment
db 64 dup (0)
stack ends;
code segment
;main procedure
start: mov ax, stack
mov ss, ax
mov sp, 20h
mov ax, 12666
mov bx, data
mov ds, bx
mov si, 0
call dtoc
mov dh, 8
mov dl, 3
mov cl, 2
call show_str
mov ax, 4c00h
int 21h
;sub procedure dtoc
dtoc: push si
push di
push ax
push bx
push cx
push dx
mov di, si ; store the start of the string.
mov bx, 10
dtloop: mov dx, 0
div bx
add dx, 30h ; conver to ascii
push dx
inc si
mov cx, ax ; if result = 0 exit the loop
jcxz dtocOK
jmp short dtloop
dtocOK: mov cx, si
mov si, 0
revert: pop ax
mov ds:[si], al
inc si
loop revert
pop dx
pop cx
pop bx
pop ax
pop di
pop si
ret
;sub procedure show_str
show_str: push ax
push bx
push cx
push dx
push bp
push si
push di
push es
mov ax, 0b800h ; load the first address to vram
mov es, ax
mov bp, 0 ; reset bp to 0
mov ax, 0 ; reset al ah
mov al, dh
mov bl, 0a0h
mul bl ; dh x 160 = line offset
add bp, ax ; add the line offset
mov ax, 0 ; reset al ah
mov al, dl
mov bl, 2
mul bl ; dl x 2 = column offset
add bp, ax ; add the column offset
mov di, 0
mov ah, cl ; store the color info
change: mov cl, [si]
mov ch, 0
jcxz ok ; check whether pointer to '\0'
mov al, cl ; store the char
mov es:[bp][di], ax ;
inc si
add di, 2
jmp short change
ok: pop es
pop di
pop si
pop bp
pop dx
pop cx
pop bx
pop ax
ret
code ends
end start
运行结果
课程设计1
将Power iDea公司的数据按照 10.2 所示的格式在屏幕上显示出来
assume cs:code, ds:data, ss:stack
table segment
;offset A = 0000H
db '1975', '1976', '1977', '1978', '1979', '1980', '1981', '1982', '1983'
db '1984', '1985', '1986', '1987', '1988', '1989', '1990', '1991', '1992'
db '1993', '1994', '1995'
; the above data present the 21 x4 years
;offset A + 21x4(54H)
dd 16, 22, 382, 1356, 2390, 8000, 16000, 24486, 50065, 97479, 140417, 197514
dd 345980, 590827, 803530, 1183000, 1843000, 2759000, 3753000, 4649000, 5937000
; the above data present the 21 dword for the income
;offset A + (54H) + (54H) = A8H
dw 3, 7, 9, 13, 28, 38, 130, 220, 476, 778, 1001, 1441, 2258, 2793, 4037, 5635, 8226
dw 11542, 14430, 15257, 17800
; the above data present the number of employeer for the 21years
;offset A + A8H + 2A = D2
table ends
data segment
; 01234567890 01234567890
; ' year 123456789 X xxx
db ' year 123456789 X xxxxx', 0 ; buff of the string 37chars with 1 '\0'
data ends
stack segment
db 128 dup (0)
stack ends
code segment
;*****************************************************************************
; main procedure start
;*****************************************************************************
start: mov ax, data
mov ds, ax ; set ds to data
mov ax, table
mov es, ax ; set es to table
mov ax, stack
mov ss, ax
mov sp, 80h ; set ss and sp
mov bx, 0 ; offset for data
mov di, 0 ; the word offset of data segment
mov bp, 3 ; point to the line.
mov cx, 21
call cls ; clean the screen
s: push cx ; main loop for 21times
mov cx, 37 ; clean up the buffer
mov si, 0
mov al, ' '
call memset8
mov si, 2 ; copy the year data to buffer
mov ax, es:0[bx]
mov ds:0[si], ax
mov ax, es:2[bx]
mov ds:2[si], ax
mov ax, es:54h[bx] ; convert the incoming data to ascii
mov dx, es:56h[bx]
mov si, 0ch ; si point to incoming buffer
call dtoc ; convert digit to char
mov ax, es:0a8h[di] ; mov the number of employeer
mov dx, 0
mov si, 16h ; si point to the No. of employeer buffer
call dtoc
mov ax, es:54h[bx] ; mov the low 16bit of incoming
mov dx, es:56h[bx] ; mov the high 16bit of incoming
div word ptr es:0a8h[di] ;div the number of employeer
mov dx, 0
mov si, 20h
call dtoc
mov dx, bp ; the bp is 16bit but we only use 8bit data.
mov dh, dl ; so just copy bp to dx and copy dl to dh.
mov dl, 0 ; point the column 2
mov cl, 7 ; 0 000 0 111B = 7 set the bkcolor(black) color(white)
mov si, 0 ; point to the start of the string
call show_str
inc bp
add bx, 4
add di, 2
pop cx
loop s
mov ax, 4C00H
int 21H
;*****************************************************************************
; sub procedure dtoc
;*****************************************************************************
;sub procedure dtoc
dtoc: push si
push di
push ax
push cx
push dx
mov di, si ; store the start of the string.
dtloop: mov cx, 10
call divdw
add cx, 30h ; convert mod to ascii
push cx
inc si
mov cx, ax ; if result(ax == 0 && dx == 0) exit the loop
or cx, dx
jcxz dtocOK
jmp short dtloop
dtocOK: mov cx, si
sub cx, di ; calc the loop count = si - di
mov si, di ; point to the start of the string buff
revert: pop ax
mov ds:[si], al
inc si
loop revert
pop dx
pop cx
pop ax
pop di
pop si
ret
;*****************************************************************************
; sub procedure divdw
; effect ax, dx, cx
;*****************************************************************************
divdw: push bx
push ax ; push the low 16bit
mov ax, dx ; move the high 16bit to ax
mov dx, 0 ; set the high 16bit to zero
div cx ; calc ax / cx
; dx is mod, ax is result
mov bx, ax ; store the result to bx
pop ax ; pop up the low 16bit
div cx ; calc the low 16bit div
mov cx, dx ; store the mod
mov dx, bx ; store the high 16bit result
pop bx
ret
;*****************************************************************************
; sub memset8
; di:si point to the start of the memory
; cx is the length of the memory
; al is the value of the memory
;*****************************************************************************
memset8: push si
push ax
push cx
jcxz memret8 ; if the cx is equal to zero just return
setvalue8: mov ds:[si], al
inc si
loop setvalue8
memret8: pop cx
pop ax
pop si
ret
;*****************************************************************************
; sub memset16
; di:si point to the start of the memory
; cx is the length of the memory
; ax is the value of the memory
;*****************************************************************************
memset16: push si
push ax
push cx
jcxz memret16 ; if the cx is equal to zero just return
setvalue16: mov ds:[si], ax
add si, 2
loop setvalue16
memret16: pop cx
pop ax
pop si
ret
;*****************************************************************************
; sub show_str
;*****************************************************************************
show_str: push ax
push bx
push cx
push dx
push bp
push si
push di
push es
mov ax, 0b800h ; load the first address to vram
mov es, ax
mov bp, 0 ; reset bp to 0
mov ax, 0 ; reset al ah
mov al, dh
mov bl, 0a0h
mul bl ; dh x 160 = line offset
add bp, ax ; add the line offset
mov ax, 0 ; reset al ah
mov al, dl
mov bl, 2
mul bl ; dl x 2 = column offset
add bp, ax ; add the column offset
mov di, 0
mov ah, cl ; store the color info
change: mov cl, [si]
mov ch, 0
jcxz ok ; check whether pointer to '\0'
mov al, cl ; store the char
mov es:[bp][di], ax ;
inc si
add di, 2
jmp short change
ok: pop es
pop di
pop si
pop bp
pop dx
pop cx
pop bx
pop ax
ret
;*****************************************************************************
; sub cls.
; clean the screen
;*****************************************************************************
cls: push ds ; clean the screen
push ax
push cx
push si
mov cx, 690h
mov ax, 0b800h
mov ds, ax
mov al, ' '
mov ah, 7
mov si, 0
call memset16
pop si
pop cx
pop ax
pop ds
ret
code ends
end start
运行结果:
上一篇: 学习Mybatis框架(五)—高级映射(多表关联查询)
下一篇: java中字符输入输出流