Skip to main content
A cup of beer
  1. Posts/

汇编语言学习笔记-ch7-10

·414 words·2 mins

分不同段存储数据或程序 #

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,无操作数(因为目标地址在栈中)