汇编语言学习笔记-ch7-10
·414 words·2 mins
Table of Contents
分不同段存储数据或程序 #
assume cs:code,ds:data,ss:stack ;指定寄存器代表的内容(虽然就算指定了,编译器也不会自动给ds,ss赋值,需要在下面的cs段中自己进行操作)
data segment
dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h ;共有8个字长的数据,16B
data ends
stack segment
dw 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 ;共有16字长的数据,32B
stack ends
code segment
start:
mov ax,stack ;start是标号,在最后end start时指出其为程序的入口地址。stack也是标号,在编译时,程序把stack这个单词替换为其地址。
;我们知道dx寄存器中存放了整个程序在内存空间的入口地址的段地址形式,假设当前dx是075AH,那么整个程序从076AH开始,stack就应该是076BH,因为其大小为16B,所以段地址应该+1.
mov ss,ax ;设置栈段地址寄存器
mov sp,20h ;设置栈指针寄存器(栈空,所以是20h,因为栈的大小是32B)
mov ax,data ;同上,data是标号,代表data的开始段地址。其应该等于dx即076AH,因为数据段在程序一开始就定义了。
mov ds,ax ;数据段寄存器,同样赋值
mov bx,0 ;数据指针置0即可
mov cx,8 ;先设置cx的值,表示循环8次
s:
push [bx]
add bx,2 ;迭代器+2.因为栈是对字进行操作,而非字节。
loop s
mov bx,0 ;清空迭代器
mov cx,8
s0:
pop [bx] ;程序的作用是将data段的数据反向。
add bx,2
loop s0
;接下来是程序返回
mov ax,4c00h
int 21h
code ends
end start ;指定程序的入口地址
- 段名就是段地址。
- 因为它代表一个段,所以以段地址的形式给出。
- assume没有什么作用,但是你也得写上。
- assume作为伪指令,并不会给CPU传递什么信息。因此写了之后,程序开始执行,CPU也不会自动给你指定的寄存器赋段地址。
- 要记得,整个程序真正开始的段地址是dx内的地址+0010H。
为什么要分多段写程序 #
- 因为在8086中,由于寄存器限制,一个段的大小最大64KB,如果它既有程序,又有数据,还有栈,那么很可能空间不够分配的。
- 所以,定义多个段,就可以定义多个上限为64KB的空间了。
- 而且,用段写程序,会更加简洁明了。不至于混杂在一起。
and和or指令 #
- 实际上就是按位与、或。和C语言中没有任何区别。
- 用法:参数1是寄存器,参数2是操作数。意思是把操作数做按位运算后放到目标寄存器中。
字符 #
-
可以用单引号括起来定义数据,那么编译器就会把它翻译成ASCII码。
dw 'a','b','c','d'
-
相应地,如果从内存中手动读取,那么右侧的ASCII码显示形式也会显示出这些字符。其他位置是
点
。
si和di寄存器(变址寄存器)和bx(基址寄存器) #
- si:源
变址
寄存器,di:目的变址
寄存器。 i
的意思是index,因此它是存放变量的。或者也可以解释为iterator
。- si和di寄存器虽然是一个字长,但是不支持高、低8位分割。
- 所谓源、目的,实际上是对应了数据的定点传送。例如从一块内存往另一块内存拷贝数据,就可以有源、目的的概念。
寻址方式 #
- 寻址方式的原理:地址加法器对各个量进行相加,得出物理地址。
- 地址加法器把它们对应的段地址左移4位,然后直接加上各个项。
- 以[idata+bx+si]为例,其中idata、(bx)、(si)==是平等的==,但是其段地址是得先左移4位的。
- 注意EA、SA,前者是偏移地址,后者是段地址。
- 注意,所有的寻址方式都是有默认的段地址的,就连[idata+bx+si]都还得加个ds才能变成物理地址。
- 除了bp外,其他几个的段地址寄存器都是ds。
寻址方式 | 汇编 | 形式化描述 | 特点 | 用途 | 注意 |
---|---|---|---|---|---|
直接寻址 | [idata] |
EA=idata,SA=(ds) | 直接给出内存单元的地址 | 读取数据 | **注意其缺省给定的段地址是ss,**相当于ds:[idata] |
寄存器间接寻址 | [di]、[bp] 等 |
EA=(di),SA=(ds),或EA=(bp),SA=**(ss)**等 | 根据寄存器中给出的地址,和其缺省给定的段地址,通过地址加法器访问内存单元 | 读取数据 | 注意其缺省给定的段地址(注意,bx,si,di的默认段地址是ds,但是bp的是ss) |
相对寻址 | [idata+si][idata+di][idata+bx] |
例如EA=idata+(si),SA=ds | 常量+变址寄存器或基址寄存器(充当变址;来用了) | 一维数组 | 可以写成idata[bx]的形式,或idata.bx的形式。即以一个地址常量作为数组名或结构体名称,然后以数组下标bx或结构体项目偏移量bx作为迭代器。 |
基址变址寻址 | [bx+si][bx+di] |
例如EA=(bx)+(si),SA=ds | 基址寄存器+变址寄存器 | 二维数组 | 可以写成[bx][si] 的形式,这能表达一个二维数组。 |
相对基址变址寻址 | [idata+bx+si][idata+bx+di] |
例如EA=(bx)+(si)+idata,SA=ds | 常量+基址寄存器+变址寄存器 | / | / |
实现多重循环 #
- 考虑一个二重循环:因为循环次数都是放在cx中的,如果从外层进入内层循环,势必要修改cx的值,内层循环结束后,cx变成0,那么外层循环也结束了。所以我们无法实现多重循环。
- 考虑可以把cx暂存起来:
- 如果放到其他寄存器,那么寄存器作为珍贵的资源,常常不能满足数量需求,一般三层循环就不能再多了;
- 如果直接考虑放到内存中,则还得记录其地址,比较麻烦;
- 所以使用栈。因为只要我们想到和嵌套有关的,一定要考虑栈。
- 具体做法就是进入内层前压栈,退出内层后立马弹栈。
mov cx,9 ;外层循环9次
s:
;s do something...
push cx ;进入内层前立马保护现场
mov cx,7 ;内层循环7次
s0:
;s0 do something...
loop s0
pop cx ;退出内层后立马恢复现场
loop s
描述性符号reg、sreg #
- sreg即segment register。reg即register。
- sreg只包括ss,ds,es,cs。其他都不是段寄存器!!!
- 为了更加形式化,不具体到某个寄存器,可以使用这些描述符。
bp寄存器 #
- bp寄存器提供了一种以非栈方式访问栈空间的方式。
- 使用bp,则默认其以ss为基地址。
- 即:[bp]等价于ss:[bp]。
机器指令所需的数据在哪里? #
- CPU内部
- 例如各寄存器、指令缓冲器IR。
- 注意,立即数就是通过IR给出的。
- 内存
- 读取内存单元获得数据。
- 端口
- 从外设获得数据。
idata #
- 即==instant data==,立即数。
如何确定待操作数据的字长 #
- 可以以操作寄存器的类别给出:例如移动到bx中就一定是word长度。
- 可以以
X Ptr
的形式显式指定:在给出地址前,紧跟着写X Ptr
。- 好处:可以显式地指定是字单元地址还是字节单元地址。
mov word ptr ds:[0],1 ;按字长写入,则这里的地址是字单元地址
mov byte ptr ds:[0],1 ;按字节长写入,则这里的地址是字节单元地址
- 另外,有些指令自己就指定了操作的数据长度
- 例如:push和pop指令只能对字长度数据进行操作。
dup #
- 使用格式:重复次数+dup+重复数据。
- 例如:
dw 5 dup(1,2,3)
就是把(1,2,3)
这段数据重复5次,定义出来就是(1,2,3,1,2,3,1,2,3,1,2,3,1,2,3)
。 - dup支持嵌套。例如:
dw dup 2 (1,3,5 dup(1,4,2,1))
,也就是(1,3,1,4,2,1,1,4,2,1,1,4,2,1,1,4,2,1,1,4,2,1,1,3,1,4,2,1,1,4,2,1,1,4,2,1,1,4,2,1,1,4,2,1)
offset伪指令(操作符) #
- 作用:获取标号的相对于代码段开始处的偏移地址。
- 注意不是绝对地址,而是偏移地址。因为只要程序还没运行,你不可能知道其绝对地址。
Runtime运行时 #
- Runtime是指程序运行过程中的一些特性。程序的行为并不是能完全由hard-coded决定的。
- 例如我利用offest将一段标号处的代码复制到另一个标号处,并执行那个标号。
- 在运行前,程序本来不应该拥有这段代码,这就是运行时。
关于转移指令JMP系列、条件转移指令JZ系列、循环指令loop、子程序调用与返回call和ret(f) #
- 转移指令的转移范围包括段内短转移(以1B表示偏移地址,修改IP)、段内近转移(以2B表示IP的值,修改IP)、段间远转移(以2B表示CS值,2B表示IP值,修改CS和IP)。
- loop指令属于段内短转移。
名称 | 类别 | 机器指令包含的信息 | 作用 | 原理 | 指令例 |
---|---|---|---|---|---|
jmp short 标号 | 段内短转移 | 段内相对偏移量 | IP+=8位位移(补码表示) | 在编译阶段算出偏移量,与IP做运算 | EB 03(03是一个字节,即8位偏移量补码) |
jmp near ptr 标号 | 段内近转移 | 段内相对偏移量 | IP+=16位位移(补码表示) | 在编译阶段算出偏移量,与IP做运算 | EB 03 AA(03 AA是两个字节,即16位偏移量补码) |
jmp 寄存器名 | 段内近转移 | 存放IP的寄存器号 | IP=16位段内绝对偏移地址 | 直接给IP赋值 | / |
jmp word ptr 字地址 | 段内近转移 | 存放IP的内存字地址 | IP=16位段内绝对偏移地址 | 直接给IP赋值 | / |
jmp far ptr 标号 | 段间远转移 | 实际目标段地址CS和目标段内偏移地址IP | IP=16位偏移地址;CS=16位段地址. | 直接给CS和IP赋值 | EA 0B 01 BD 0B(较高地址的字:BD 0B是CS;较低地址的字:0B 01是IP。) |
jmp dword ptr 双字地址 | 段间远转移 | 存放CS:IP的内存双字地址 | IP=16位偏移地址;CS=16位段地址. | 直接给CS和IP赋值 | 注意,在内存单元中,较高地址字还是存放CS,较底地址字还是存放IP。 |
jcxz 标号 | 段内短转移 | 段内相对偏移量 | IP+=8位位移(补码表示) | 在编译阶段算出偏移量。执行到这一句时,查看cx寄存器是否为0,如果为0,那么与IP做运算以修改IP的值,否则什么也不做。 | EB 03(03是一个字节,即8位偏移量补码) |
loop标号 | 段内短转移 | 段内相对偏移量 | IP+=8位位移(补码表示) | 在编译阶段算出偏移量。执行到这一句时,查看cx寄存器是否为0,如果为0,那么与IP做运算以修改IP的值,否则什么也不做。 | EB 03(03是一个字节,即8位偏移量补码) |
call 标号 | 段内近转移 | 段内相对偏移量(2B) | IP+=16位位移(补码表示) | 相当于push IP,然后jmp near ptr 标号。 | EB 03 AA(03 AA是两个字节,即16位偏移量补码) |
call far ptr 标号 | 段间远转移 | 实际目标段地址CS和目标段内偏移地址IP | IP=16位偏移地址;CS=16位段地址. | 相当于先push CS,再push IP(因为CS的地址更大,所以是这个顺序。),再jmp far ptr 标号。 | EA 0B 01 BD 0B(较高地址的字:BD 0B是CS;较低地址的字:0B 01是IP。) |
call 寄存器名 | 段内近转移 | 存放IP的寄存器号 | IP=16位段内绝对偏移地址 | 相当于先push IP,再jmp 寄存器名。 | / |
call word ptr | 段内近转移 | 存放IP的内存字地址 | IP=16位段内绝对偏移地址 | 相当于先push IP,再jmp word ptr 标号。 | / |
call dword ptr | 段间远转移 | 存放CS:IP的内存双字地址 | IP=16位偏移地址;CS=16位段地址. | 相当于先push CS,再push IP(因为CS的地址更大,所以是这个顺序。),再jmp dword ptr 标号。 | 注意,在内存单元中,较高地址字还是存放CS,较底地址字还是存放IP。 |
ret | 段内近转移 | 无(因为目标地址在栈中) | IP=16位段内绝对偏移地址 | 相当于pop IP。 | ret,无操作数(因为目标地址在栈中) |
retf | 段间远转移 | 无(因为目标地址在栈中) | IP=16位偏移地址;CS=16位段地址. | 相当于先pop IP,再pop CS。(因为CS的地址更大,所以是这个顺序。) | retf,无操作数(因为目标地址在栈中) |