前言:
这篇笔记主要学习汇编中的寻址方式和数据传送指令。
笔记中的部分解释是参考了滴水讲师的说法并加了自己的一些理解,如有问题欢迎大家指出。
编程语言:
汇编语言
以下为主题内容:
笔记参考 "杨季文《80x86汇编语言程序设计》"
8086 寻址方式
汇编指令格式:
操作码 操作数1,操作数2
我们执行汇编指令时,其中表示指令中的操作数(需要处理的数据)在哪里的方法,被称为寻址方式。
立即寻址
操作数包含在指令中,它作为指令的一部分,跟在操作码后存放在代码段中,这种操作数被称为立即数。

寄存器寻址
操作数在寄存器中,指令中指定了寄存器。

由于操作数在寄存器中,不需要通过访问内存(存储器)来取得操作数,所以采用这种寻址方式的指令执行速度较快。
直接寻址
操作数在存储器中,指令直接包含有操作数的有效地址。汇编中使用 [偏移地址]
来表示读取某个偏移地址的操作数。操作数一般存放在数据段中,所以操作数段地址默认在 ds 段中。如果要读取其他段中的操作数,就要加上段超越前缀,如 ES:[1234]
。

寄存器间接寻址
操作数的有效地址在 SI、DI、BX、BP 这四个寄存器之一中,如 mov ax,[si]
。一般情况下 SI、DI、BX 作为有效地址使用的是 DS 段寄存器,BP 使用的是 SS 段寄存器。

寄存器相对寻址
操作数的有效地址是 "基地址+立即数",使用一个基址寄存器(BX、BP)或变址寄存器(SI、DI)存储基地址。如: mov ax,[bx+4]
还可写成 mov ax,4[bx]
。

基址加变址寻址方式
操作数的有效地址是 "基地址+变址",使用一个基址寄存器(BX、BP)存储基地址和变址寄存器(SI、DI)存储偏移地址。如: mov ax,[bx+si]
还可写成 mov ax,[si][bx]
。

相对基址加变址寻址方式
操作数的有效地址是 "基地址+变址",使用一个基址寄存器(BX、BP)存储基地址;变址寄存器(SI、DI)存储变址;立即数作为固定的偏移量。如: mov ax,[bx+si+1]
还可写成 mov ax,1[si][bx]
。

代码练习
;程序名:addressasm
;功能:7种不同的寻址方式
;=======================================
assume cs:code,ds:data
X = 1234h ;X EQU 1234h(常量定义)
data segment
;val2 db 1
;al db ?
val db 1,2,3,4,5,6,7,8,9,0,1,2,5,6
val2 db 12345678h
data ends
code segment
start:
mov ax,data
mov ds,ax
;立即寻址
mov ax,X ;常量 X 编译时被转换成立即数
mov ax,5678h
;寄存器寻址
mov ax,es
mov al,dh
;直接寻址
mov ax,[1234h]
mov ax,es:[1234h]
mov ax,word ptr val
mov al,[val+1]
;间接寻址方式
mov si,offset val
mov bx,[si]
;寄存器相对寻址
mov ax,[si+2]
mov ax,2[si]
;基址加变址寻址方式
mov bx,4
mov ax,[si+bx]
mov ax,[bx][si]
mov ax,[si][bx]
lds di,val2 ;LDS 指令的功能是:读取偏移地址处的32位数据,将高 16 位作为段值传送给DS,低 16 位作为偏移地址传给DI.
;相对基址加变址寻址方式
mov bx,offset val
mov si,0
mov cx,9 ;表示执行循环次数
L1:
mov al,[bx+si+1]
mov al,[bx][si][1]
mov al,[si][bx][1]
mov al,[1][si][bx]
inc si
loop L1 ;cx减1,并跳转到标号 L1 处,直至cx为0
;
mov ax,4c00h
int 21h
code ends
end start
汇编指令


数据传送指令
数据传送指令可分为:传送指令、交换指令、地址传送指令、堆栈操作指令、标志传送指令、查表指令、输入输出指令。
除了SAHF 和 POPF 指令外,这组指令对标志寄存器没有影响。
传送指令(MOV)

CPU 内部寄存器之间的数据传送


mov si,al ;错,必须是两个相同大小寄存器才能传送数据
mov ds,es ;错,源和目的操作数不能同时都是段段寄存器
mov cs,ax ;把 ax 的值传送给代码段

错,能执行通过,但是会产生 int 中断 CDH,并再次跳转到原来的指令处。一直循环执行,导致程序崩溃。
那为什么会出现这种情况能呢?在此之前我们要从计算机的角度去思考程序如何跳转到其他地方并正确执行指令。
首先我们要先确定计算机执行程序指令的规则:程序执行指令是通过 CS:IP 来指定要执行的指令。
其次如果我们是计算机要跳到其他地方执行指令 CS:IP 要符合哪些条件,分别有如下两种情况:
要在第一个代码段 CS1 中执行指令,此时要执行正确的指令 CS 不能改变,IP 改变。
要跳转到第二个代码段 CS2 中执行指令,此时要执行正确的指令 CS 需要改变,IP 也需要改变。
回到刚才执行的指令,我们只改变了 CS 代码段,没有改变 IP。所以导致系统无法找到正确的指令,程序奔溃。
mov ax,ip
mov ip,ax ;错,指令指针 IP 不能作为源,也不能作为目的
那为什么会这样呢?很简单如果我们是计算机,想要完整的执行程序并实现程序功能,那么我们就必须按照程序的指令顺序一条指令一条指令地来执行,不能在执行过程中随意修改 IP 导致程序输出错误或者崩溃。因此 IP 一定是不能人为修改的(所有的系统都是一样),但是我们可以通过计算机的运行机制,来恶意修改 CS:IP 执行我们的代码,这个就是后面学习的内容了。
立即数传送至通用寄存器或存储单元

在写汇编程序的过程中你会用到很多的寄存器,如果你不小心把立即数传送到段寄存器,或者程序执行过程中修改了段寄存器,那么程序很容易就会崩溃。因为为了保证段寄存器得可控性,所以适当的做一些限制。
寄存器是用来存储数据,然后交给运算器处理的作用。立即数又没有这种功能,肯定会错了。
寄存器与存储器之间的数据传送

mov ax,word ptr VARB ;注意当变量类型和寄存器大小不同时,需要我们人为指定告诉计算机我要传送多大的数据到寄存器中。
mov byte ptr VARW,DL ;同上,两个操作数大小(类型)需要一样
- 除了串操作指令外,源操作数和目的操作数不能同时是存储器操作数
;错
;1、指令集里面没有这种指令
;2、所有的数据传送指令,两边都不能同时为内存操作数。
mov [si],[di]
具体为什么这样,我也不清楚。暂时先放着,后续等搞懂了再进行补充。
交换指令(XCHG)

例:
;交换 op1 和 op2 内存数据
mov bx,op1
xchg bx,op2
mov op1,bx
;类似于
mov ax,op1
mov bx,op2
mov op2,ax
mov op1,bx
XCHG 是复合指令,即使用多个简单的汇编指令组成一个 XCHG 指令。设计复合指令的目的是为了提高程序员编程的效率。
地址传送指令
LEA(Load Effective Address)

mov si,offset string ;offset 是操作符,由编译器识别解析的。在编译过程中 "offset string" 会被替换为 string 变量的偏移地址。
lea si,string ;lea 是指令,在程序加载到内存后,执行该指令时将 string 变量的偏移地址传送给 si
使用 "offset" 的缺陷:如果 string 变量是局部变量(动态内存分配),在程序执行之前还没有为其分配地址,那么编译器无法取得它的地址肯定会出错。
lea 指令的探究
lea 是用来传送内存地址的,如:lea ax,[1234]
,本质上是将中括号中的内容 "1234" 传送给 ax。而有效偏移地址范围是在 0000~FFFF 之间,也就是我可以输入其中的任意值。同时在 8086 指令集中,寻址可以使用加法运算。
那么我们是否可以使用 lea 做加法运算呢?
例如:将 "1+2+3" 的值,并传送给AX寄存器。

在 x86 计算机中,寻址方式加上了乘法运算,因此可以使用 "lea ax,[1+2*3]" 进行算数运算。
LDS(Load pointer into DS)


LES(Load pointer into ES)

堆栈操作指令




标志操作指令




从 LDS 开始后面的指令有印象就行。等后面写汇编程序,优化代码时用到的时候再说。