第10章 call 和 ret 指令
10.1 ret 和 reft 指令
call 和 ret 指令都是转移指令,他们都修改 IP,或同事修改 CS 和 IP 。他们经常被共同来实现子程序的设计。
10.2 call 指令 和 根据位移 转移的call指令
段间转移 的 call 指令
转移地址 在 寄存器 中 的 call 指令
转移地址在内存 中 的 call 指令
10.7 call 和 ret 的配合使用 来 实现子程序
子程序 框架
10.8 乘法指令
10.12 寄存器的冲突问题
实验 10 解析
王爽《汇编语言》(第三版)实验10解析:https://www.cnblogs.com/nojacky/p/9523904.html
1. 显示字符串
示例代码:
assume cs:codedatasg segmentdb 'welcome to masm!', 0
datasg endscode segmentstart: mov dh, 8mov dl, 3mov cl, 2mov ax, datasgmov ds, axmov si, 0call show_strmov ax, 4c00hint 21hshow_str: push dx push cx push si ; 保护子程序寄存器中用到的寄存器; 由于主程序的限定; 这里由CPU自动为我们分配栈空间mov di, 0 ; 显示缓存区中的偏移量mov bl, dh dec bl ; bl-1才是真正的行,因为行号从0开始计数mov al, 160 mul bl ; 每行160字节 用 行数*每行偏移量 得到目标行的偏移量mov bx, ax ; mul bl之后,乘积存储在ax中,这里要转存入bx中mov al, 2 ; 列的偏移量为2,两个字节代表一列!!!mul dl ; 与行偏移量同理add bl, al ; 将列偏移量与行偏移量相加,得到指定位置的偏移量。mov ax, 0b800hmov es, ax ; 指定显示缓存区的内存位置mov al, cl ; 由于后面jcxz语句的判断要用到cx,所以我们要将; cl(颜色)先存下来。s: mov ch, 0mov cl, ds:[si] ; 首先将当前指向字符串的某个字符存入cx中jcxz ok ; 如果cx为0,则转移到ok标号执行相应代码mov es:[bx+di],cl ; 将字符传入低地址mov es:[bx+di+1],al ; 将颜色传入高地址add di, 2 ; 列偏移量为2inc si ; 字符串的偏移量为1loop s ; 不为0,继续复制ok: pop dx pop cxpop si ; 还原寄存器变量ret ; 结束子程序调用
code endsend start
运行结果:
示例代码 2:
data segmentdb 'Welcome to masm!',0
data endscode segmentassume cs:code,ds:data
start:mov dh,1 ;dh装行号(范围:1--25)mov dl,1 ;dl装列号(范围:1--80)[注:每超过80等于行号自动加1]mov cl,0cah ;cl中存放颜色属性(0cah为红底高亮闪烁绿色属性)mov ax,datamov ds,axmov si,0call show_strmov ax,4c00hint 21h show_str: ;显示字符串的子程序[定义开始]push cxpush simov al,0A0hdec dh ;行号在显存中下标从0开始,所以减1mul dhmov bx,axmov al,2mul dlsub ax,2 ;列号在显存中下标从0开始,又因为偶字节存放字符,所以减2add bx,ax ;此时bx中存放的是行与列号的偏移地址mov ax,0B800hmov es,ax ;es中存放的是显存的第0页(共0--7页)的起始的段地址mov di,0mov al,clmov ch,0
s: mov cl,ds:[si]jcxz okmov es:[bx+di],cl ;偶地址存放字符mov es:[bx+di+1],al ;奇地址存放字符的颜色属性inc siadd di,2jmp short s
ok: pop sipop cxret ;显示字符串的子程序[定义结束]
code endsend start
2. 解决除法溢出的问题
示例代码:
assume cs:code,ss:stackstack segmentdw 8 dup(0)
stack endscode segment
start:mov ax, stackmov ss, axmov sp, 10hmov ax, 4240hmov dx, 0fhmov cx, 0ah call divdwmov ax, 4c00hint 21hdivdw: ;子程序定义开始push axmov ax, dxmov dx, 0div cxmov bx, axpop axdiv cxmov cx, dxmov dx, bxret ;子程序定义结束
code endsend start
测试1:
计算:1000000/10(F4240H/0AH) 商:100000(186A0H) 余数:0
调试结果:
测试2:
计算:1000020/11(F4254H/0BH) 商:90910(1631EH) 余数:10(0AH)
调试结果:
3. 数值显示
示例代码:
assume cs:code,ds:datadata segmentdb 10 dup (0)
data endscode segment
start:mov ax,12666mov bx,datamov ds,bxmov si,0call dtoc mov dh,8mov dl,3mov cl,0cahcall show_strmov ax,4c00hint 21h
dtoc: ;数值显示的子程序定义push dxpush cxpush axpush sipush bxmov bx,0
s1: mov cx,10dmov dx,0div cx mov cx,axjcxz s2add dx,30hpush dxinc bxjmp short s1
s2: add dx,30hpush dxinc bx ;再进行一次栈操作(补充当"商为零而余数不为零"时的情况)mov cx,bxmov si,0
s3: pop axmov [si],alinc siloop s3
okay: pop bxpop sipop axpop cxpop dxret ;数值显示的子程序定义结束
show_str: ;显示字符串的子程序已经在第一题中说明,在此不再赘述。push bxpush cxpush simov al,0A0hdec dhmul dhmov bx,axmov al,2mul dlsub ax,2add bx,axmov ax,0B800hmov es,axmov di,0mov al,clmov ch,0
s: mov cl,ds:[si]jcxz okmov es:[bx+di],clmov es:[bx+di+1],alinc siadd di,2jmp short s
ok: pop sipop cxpop bxret
code ends
end start
运行结果:
详解版:
源地址:https://blog.csdn.net/include_heqile/article/details/80602772
assume cs:codedata segmentdw 123, 12666, 1, 8, 3, 38data endsascii segmentdb 100 dup(0);ascii码值,一个字节即可存储ascii endsdiv segmentdw 16 dup(0);除法溢出计算需要使用该数据段来临时保存结果div endscode segmentstart: mov bx, datamov ds, bx;ds段寄存器用来存放待处理的数据mov si, 0call dtocmov cx, 6mov ax, 0mov dh, 8show: push cxmov dl, 3mov cl, 2call show_strpop cx;我们需要更改行号来避免覆盖inc dhloop showmov ax, 4c00hint 21h dtoc: ;该子程序用于将数值型的数字转换为字符串;十进制数值转换为ASCII码值,转换关系为:ascii=10进制+30H;要想将一个十进制的整数拆分成一个一个的数值,那我们需要让这个数;除以10,然后将得到的结果依次入栈,除完之后再依次出栈,即可得到由高位到低位;的所有数值,之后将这些值加上30H,即得到其对应的ASCII码值,然后将这些;ASCII码值存放到一个数据段中,调用show_str函数,来在屏幕上显示这些数值;为了存储转换后的ASCII码值,我们需要新开辟一个数据段push axpush bxpush cxpush dxpush dspush sipush esmov ax, asciimov es, ax;使用es段寄存器来存储转换后的ascii码值mov di, 0;存储ASCII数据时用来指向ascii段中的每个内存单元 mov cx, 6loop_zone: push cx;因为内层循环会更改cx的值,所以我们需要使用栈结构来保存cx的值mov dx, 0;记录十进制数据的位数mov ax, ds:[si];ax存放被除数split: push dx;下面要用到dx寄存器,因此我们先保存dxmov cx, 0ah;cx存放除数 mov dx, 0;dx作为被除数高16位,置0div cx;32/16的除法运算,商存储在ax中,余数存储在dx中mov cx, dx;call divdw;其实用不着调用divdw,这个除法溢出问题不是真正的除法溢出问题;我们只需要将被除数凑成32位的,除数当做16位的即可;此程序返回运算后的商和余数,分别保存在ax和cx中;如果被除数大于2550,al是无法存放商的,会造成溢出,因此,我们需要调用本实验中第二个函数;专门用于解决除法溢出问题的函数,虽然程序2解决的是32/16的除法运算的溢出问题,但是对于16/8位的;除法运算也是完全适用的;由于入栈时只能使用字型数据,所以我们压入的是ax,此时需要将;无关数据,也就是al置0pop dx;取出dx更改前的值push cx;余数入栈inc dx;当循环终止的时候可以进行弹栈存储操作了,但是我们需要一个标记,来标识我们需要;弹出多少次,我们使用dx来进行存储mov cx, axadd cx, dx;ax中的值在下一次运算中一定会用到,dx中的值也有可能会用到(当被除数很大时);此时可以临时保存数据的只有cx了,因此我们直接将运算结果放到cx中;一举两得jcxz ok1;处理过程是需要循环的,循环结束的条件是商==0 ;我们只需要将执行jcxz指令即可,当cx的值位0的时候,它会自动跳转到ok1循环的jmp short split ok1: pop axadd al, 30hmov byte ptr es:[di], alinc didec dxmov cx, dxjcxz lastjmp short ok1last: ;最后一步,在数据的ASCII数据形式的最后加上一个0mov ah, 0mov byte ptr es:[di], ah inc di;从split到ok1到最后一步是对data段中第一个数据的处理;这个过程需要进行循环操作;在这个循环中,di,bx是放在循环外的pop cxadd si, 2loop loop_zonepop espop sipop dspop dxpop bxpop cxpop axretdivdw: push dspush dxpush cxpush ax mov ax, divmov ds, axmov dx, 0;由于本程序中被除数是16位,但是divdw是32/16,所以我们需要将被除数的高位补16个0,也就是将dx置0mov ax, dxmov dx, 0div cx;ax存放商,dx存放余数;根据公式,使用被除数高位除以除数得到的商×65536;*65536等价于在低位加16个0,因此操作就会变得非常简单;使用被除数高位除以除数得到的余数×65536+被除数的低位,再将得到的结果除以除数;两者的结果相加,即可得到32位/16位的无溢出结果push dx;使用栈临时保存余数mov dx, axmov ax, 0mov ds:[0], axmov ds:[2], dxpop dx;弹出余数,作为右操作数中被除数的高16位pop bx;得到被除数的低16位push bx;恢复栈顶数据,避免对主程序造成干扰;add ax, bx;将右操作数[]中的左操作数的低16位和被除数的低16位相加;但是右操作数[]中的左操作数的低16位一定是全0的,因此我们可以省略这一步;直接执行mov ax, bxmov ax, bxdiv cx;ax存放商,dx存放余数;由于左操作数的低16位一定是全0,所以不必与其相加,直接将;右操作数的低16位存储到ds:[0]内存单元即可mov ds:[0], ax;商的低16位放到ds:[0]单元中mov ds:[4], dx ;余数放到ds:[4]单元中;ds:[2]中一直保存的都是商的高16位,且没有被更改过,因此无须任何操作pop axpop cxpop dxmov ax, ds:[0];ax保存商的低16位mov dx, ds:[2];dx保存商的高16位mov cx, ds:[4];cx保存余数 pop ds;之所以要在pop ds之前将数据转移,是因为子程序divdw调用前,ds已经被使用;指向的是其他的段,如果不在pop之前转移数据,那么div段的数据就无法获取了retshow_str: push bxpush cxpush dxpush dspush espush dipush axpush si;根据上节中的框架,为了不让子程序干扰主程序中寄存器的值,将所有子程序会用到的寄存器进行压栈mov di, axmov ax, 0b800hmov es, ax;颜色区的段地址mov ax, asciimov ds, ax;待输出的ASCII码值数据段mov al, 160 mul dh;每行占160个字节,乘以行数push ax;将行计算的结果存储到栈中mov al, 2mul dl;每列占2个字节,乘以列数pop bx;将上次运算的结果(160×行数)的值转移到bx中add bx, ax ;此时的ax值为(2×列数);将两者相加,最终结果保存到bx中mov dl, cl;因为下面的跳转指令jcxz需要用到cx寄存器,故需要将cl的值先保存在dl中change: mov cl, ds:[di]mov ch, 0 inc di;我们需要记录下di的值,下一轮循环还会用到它;这样一来,我们就需要调整入栈和出栈寄存器的位置了;我们在pop di之前pop ax,然后使用ax来保存di的值jcxz ok2mov ch, dl mov es:[bx+si], cxadd si, 2jmp short changeok2: pop sipop axmov ax, dipop dipop espop dspop dxpop cxpop bx retcode endsend start
示例代码 2:
assume cs:code,ds:datadata segmentdb 10 dup(0)
data endscode segment
start:mov ax,12666mov bx,datamov ds,bxmov si,0call dtocmov dh,8mov dl,3mov cl,02hcall show_strmov ax,4c00Hint 21Hdtoc:push dxpush cxpush axpush sipush bxmov bx,0 ;bx在子程序中用来存放位数,用栈来临时存放修改后的字符dtoc00:mov cx,10d ;d表示十进制mov dx,0div cx ;除以10mov cx,ax ;得到的商赋给cxjcxz dtoc01 ;当前商为0则调到s2add dx,30H ;将余数加上30H得到相应的ASCII码push dxinc bxjmp short dtoc00dtoc01: ;当商为0时,余数为个位add dx,30Hpush dxinc bx ;再进行一次栈操作(补充当商为0而余数不为0时的情况)mov cx,bx mov si,0dtoc02: ;s3实现将栈中的数据依次出栈放到制定内存中pop axmov [si],alinc siloop dtoc02dtoc03:pop bxpop sipop axpop cxpop dxretshow_str: ;显示字符串的子程序(定义开始)push cxpush simov al,0a0H ;每行为160字节 a0H=160dec dh ;行号在显存中下标从0开始,所以减1mul dh ;相当于从(n-1)*0a0H个Byte单元开始mov bx,ax ;定位好的位置偏移地址存放在bx里面(行)mov al,2 ;每个字符占两个字节mul dl ;定位列,结果ax存放的是定位好的列的位置sub ax,2 ;列号在显存中下标从0开始,又因为偶字节存放字符,所以减2add bx,ax ;此时bx存放的是行与列的偏移地址mov ax,0b800Hmov es,axmov di,0 ;指向显存的偏移地址mov al,cl ;cl是存放颜色的参数,这时候al存放颜色了mov ch,0 ;下边cx存放的是每次准备处理的字符show_str00:mov cl,ds:[si] ;ds:[si]指向'Weclome to masm!'jcxz show_str01mov es:[bx+di],cl ;偶数地址存放字符mov es:[bx+di+1],al ;奇数地址存放颜色属性inc siadd di,2jmp short show_str00 show_str01:pop sipop cxretcode ends
end start