青无 發表於 2023-10-28 20:59:00

8086 汇编语言知识点梳理

<h1 id="基础知识">基础知识</h1>
<h3 id="机器语言">机器语言</h3>
<ul>
<li>
<p>机器语言是机器指令的集合</p>
<ul>
<li>
<p>机器指令是一台机器可以正确执行的命令</p>
</li>
<li>
<p>机器指令由一串二进制数表示</p>
</li>
</ul>
</li>
</ul>
<h2 id="汇编语言">汇编语言</h2>
<ul>
<li>汇编语言的主体是汇编指令
<ul>
<li>汇编指令是机器指令便于记忆的书写格式</li>
<li>汇编指令是机器指令的<mark>助记符</mark></li>
</ul>
</li>
</ul>
<h3 id="汇编语言的组成">汇编语言的组成</h3>
<ul>
<li>汇编指令:机器码的助记符,有对应的机器码</li>
<li>伪指令::没有对应的机器码,由编译器执行,计算器并不执行</li>
<li>其他符号:如 + 、- 、* 、/ 等,由编译器识别,没有对应的机器码</li>
</ul>
<h3 id="汇编语言的种类">汇编语言的种类</h3>
<ul>
<li>8086汇编(8086处理器是<mark>16bit</mark>的CPU)</li>
<li>Win32汇编</li>
<li>Win64汇编</li>
<li>AT&amp;T汇编(Mac、iOS模拟器)</li>
<li>ARM汇编(嵌入式、iOS真机)</li>
</ul>
<blockquote>
<p>汇编无法得到高级语言,因为不同高级语言在汇编上是相同的</p>
</blockquote>
<h2 id="总线">总线</h2>
<p>总线是一根根导线的集合</p>
<blockquote>
<p>每一个CPU新片都有许多管脚,这些管脚和总线相连,CPU通过总线和外部器件进行交互。</p>
</blockquote>
<h3 id="总线的分类">总线的分类</h3>
<h4 id="地址总线寻址找到地址对应的存储空间">地址总线:寻址,找到地址对应的存储空间</h4>
<p>地址总线决定了CPU的寻址能力,8086地址总线宽度是<strong>20</strong>,所以它的寻址能力是1M $(2^{20})$</p>
<blockquote>
<p>寻址能力的计算:首先明白总线就是导线,导线能够传递的是电信号,电信号分为两种:高电平信号、低电平信号,高电平信号即 1,低电平信号即 0。假如总线总线的宽度是 3 ,那么 3 根导线高电平为 1 ,低电平为 0 ,它们最大能够传递的值只有 $2^3$ 种:000,001,010,011,100,101,110,111。</p>
</blockquote>
<h4 id="数据总线传递cpu和内存之间传递具体数据">数据总线:传递,CPU和内存之间传递具体数据</h4>
<p>数据总线决定了CPU单次数据的传送量,也就是数据传送的速度。8086的数据总线宽度是16,所以单次最大能够传递2个字节的数据。</p>
<blockquote>
<p>单次数据传送量的计算:数据总线的宽度是16,同地址线一样,16根线代表16位0或1,即16位二进制数据,一次最多能够传送16个二进制位。一个字节是8位,16位即2个字节。所以8086单次能够传递的最大数据量就是2个字节。</p>
</blockquote>
<blockquote>
<p>8088的数据总线宽度是8,8086的数据总线宽度是16,分别向内存中写入89D8H时(89D8H即16进制的89D8,汇编语言中末尾加H代码16进制)。一个16进制代表4个二进制位,两个16进制代表8个二进制位即1个字节,四个16进制即2个字节。因为8088数据线宽度是8,一次只能传递一个字节,所以8088传递89D8H需要传2次,第一次传D8,第二次传89。而8086只需要一次就能够将89D8传递完成。</p>
</blockquote>
<h4 id="控制总线控制告诉内存需要进行读还是写操作">控制总线:控制,告诉内存需要进行读还是写操作</h4>
<p>控制总线决定了CPU的控制能力,代表CPU有多少种控制能力。</p>
<h3 id="cpu从内存中读取数据的步骤">CPU从内存中读取数据的步骤</h3>
<ol>
<li>CPU通过 地址线 找到需要读取数据的地址</li>
<li>通过 控制线 告诉内存进行读取操作</li>
<li>内存通过 数据线 返回数据给CPU</li>
</ol>
<h3 id="cpu-如何控制外设">CPU 如何控制外设</h3>
<p>CPU通过总线向接口卡发送命令,接口卡根据命令控制外设工作</p>
<h2 id="内存">内存</h2>
<p>所有的内存单元都有唯一的地址,这个地址叫做物理地址。</p>
<p>8086CPU的地址总线是20根,那么它能够访问的内存空间的地址值范围即 0x00000 - 0xFFFFF(上面已经说明过,一个16进制位=4个二进制位),通过这个范围可以定位$2^{20}$ 个不同的内存单元,所以8006的内存空间大小为1M。</p>
<p><img src="https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/6/13/16b4f43b4b734f82~tplv-t2oaga2asx-jj-mark:3024:0:0:0:q75.awebp"></p>
<ul>
<li>0x00000 - 0x9FFFF:主存储空间,可读可写</li>
<li>0xA0000 - 0xBFFFF:显存地址空间,数据显示在显示器上</li>
<li>0xC0000 - 0xFFFFF:ROM,只读</li>
</ul>
<h2 id="8086的寻址方式">8086的寻址方式</h2>
<p>上面提到8086的地址总线宽度为20,寻址能力为1M,但是实际上8086是一个16位架构的CPU,它内部能够一次性处理、传输、暂存的数据只有16位。这就意味这8086实际上只能够直接送出16的地址,但是它的地址总线宽度又是20位,意味这这样就有4位是无法使用的,它的实际寻址能力只能够是64KB。那么它是如何做到实现1M的寻址能力呢,具体步骤如下:</p>
<ol>
<li>CPU中的相关部件提供两个16的地址,一个成为段地址,一个成为偏移地址。</li>
<li>段地址和偏移地址通过内部总线送入地址加法器。</li>
<li>地址加法器将两个16位地址合成一个20位的物理地址。</li>
<li>地址加法器通过内部总线将20位物理地址送入输入输出控制电路。</li>
<li>输入输出控制电路将20位物理地址送入地址总线。</li>
<li>20位的物理地址被地址总线送到内存。</li>
</ol>
<h2 id="字节与字">字节与字</h2>
<p>汇编语言没有数据类型的概念,它是直接操作内存的,汇编语言的数据存储单位有两个:</p>
<ol>
<li>字节:byte,1个字节由8bit组成,可以存储在8位寄存器中。</li>
<li>字:word,1个字由2个字节组成,这两个字节分别成高字节和低字节。</li>
</ol>
<h1 id="寄存器">寄存器</h1>
<p>寄存器是CPU非常重要的部件,可以通过改变寄存器的值来实现对程序的控制。不同CPU的寄存器个数和结构一般都不相同,下面是8086CPU寄存器的结构,8086CPU有14个寄存器,所有寄存器都是16位的。</p>
<img src="https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/6/13/16b4fdd82280e57c~tplv-t2oaga2asx-jj-mark:3024:0:0:0:q75.awebp">
<h4 id="字在寄存器中的存储">字在寄存器中的存储</h4>
<p><strong>在CPU中,16位寄存器存储一个字,高八位存储高位字节,低八位存储地位字节</strong></p>
<p><strong>在内存中,字的地位字节存放在低地址单元,高位字节存放在高地址单元</strong></p>
<h3 id="数据寄存器">数据寄存器</h3>
<p>数据寄存器由AX(Accumulator Register)、BX(Base Address Register)、CX(Count Register)、DX(Data Register)组成,虽然上图里边每个每一个寄存器都分成了两块,但它依然是一个寄存器。</p>
<p>由于8086之前的CPU是8位的架构,所以8086为了兼容8位的程序,每个16位数据寄存器都可以当作两个单独的8位寄存器来使用。AX寄存器可以分成两个独立的8位寄存器,高8位为AH,低8位为AL。BX、CX、DX同理。</p>
<p>除了四个数据寄存器之外,其它的寄存器<strong>均不可以</strong>分为两个独立的8位寄存器。<em>独立的意思是:当AH和AL做为8位寄存器使用时,可以看作它们是互不相关的,形式上可以看作两个完全独立的寄存器。</em></p>
<p>既然数据寄存器可以当作两个独立的寄存器,那么它们既可以用整个寄存器的16位存放一个数据,也可以高8位和低8位分别存放一个数据共存放两个数组。</p>
<h4 id="axaccumulate">AX(accumulate)</h4>
<p>AX 作为累加器用,所以它是算术运算的主要寄存器。在乘、除等指令中指定用来存放操作数。</p>
<p>另外,所有的I/O指令都使用这一寄存器与外部设备传送信息。</p>
<h4 id="bxbase">BX(base)</h4>
<p>BX 在计算存储器地址时,它经常用作基址寄存器。</p>
<h4 id="cxcount">CX(count)</h4>
<p>CX 常用来保存计数值,如在移位指令、循环和串处理指令中用作隐含的计数器。</p>
<h4 id="dxdata">DX(data)</h4>
<p>DX 一般在作双字长运算时把DX和AX组合在一起存放一个双字长数,DX用来存放高位字。</p>
<p>此外,对某些I/O操作,DX可用来存放I/O的端口地址。</p>
<h3 id="段寄存器segment-register">段寄存器(Segment Register)</h3>
<p>前面关于8086的寻址方式里边提到,8086需要16位的段地址和偏移地址合成20位地址,其中的段地址就由段寄存器提供。段寄存器一共有四个,每个段寄存器的作用都不相同。</p>
<h4 id="cs-代码段寄存器code-segment-register">CS 代码段寄存器(Code Segment Register)</h4>
<p>CS和IP配合使用,它们指示了CPU当前要读取指令的地址。任何时候,8086CPU都会将CS:IP指向的指令做为下一条需要取出执行的指令。</p>
<p>指令执行的过程:</p>
<ol>
<li>
<p>从CS:IP指向的代码段内存单元读取指令,读取的指令进入指令缓冲器。</p>
</li>
<li>
<p>IP = IP+读取指令的长度,进而可以读取下一条指令。</p>
</li>
<li>
<p>返回步骤1。</p>
</li>
</ol>
<blockquote>
<p>在内存或者磁盘上中,指令和数据没有任何区别,都是二进制信息。 CPU在工作时,有时候把信息当作指令,有时候看作数据,同样的信息赋予不同的意义。</p>
</blockquote>
<blockquote>
<p>CPU根据什么将内存中的数据信息当作指令? 通过CS:IP指向的内存单元内容看作指令。</p>
</blockquote>
<h4 id="ds-数据段寄存器data-segment-register">DS 数据段寄存器(Data Segment Register)</h4>
<p>DS是用来操作内存时提供段地址的,假如需要将内存中10000H 存入1122H,直接这样写是不可以的:</p>
<pre><code class="language-assembly">mov 1000H:,1122H
</code></pre>
<p>因为汇编语言又如下要求:</p>
<ol>
<li>不能直接给内存地址赋值,必须通过DS:[偏移地址]指向内存。</li>
<li>不能直接给DS赋值,需要通过寄存器中转</li>
</ol>
<p>正确做法是:</p>
<pre><code class="language-assembly">mov ax,1000H
mov ds,ax

mov ,1122H
</code></pre>
<h4 id="ss-堆栈段寄存器stack-segment-register">SS 堆栈段寄存器(Stack Segment Register)</h4>
<p>配合SP使用,SS:SP指向栈顶元素</p>
<h3 id="其他寄存器">其他寄存器</h3>
<h4 id="si-源变址寄存器source-index-register">SI 源变址寄存器(Source Index Register)</h4>
<p>一般与DS联用,用来确定数据段中某一单元的地址</p>
<p>与自动增量和自动减量的功能,方便用于变址</p>
<h4 id="di-目的变址寄存器destination-index-register">DI 目的变址寄存器(Destination Index Register)</h4>
<p>与SI相同</p>
<h4 id="bp-基址指针寄存器base-pointer-register">BP 基址指针寄存器(Base Pointer Register)</h4>
<p>可以作为堆栈区中的一个基地址以便访问堆栈中的信息</p>
<h3 id="8086专用寄存器">8086专用寄存器</h3>
<h4 id="ip-指令指针寄存器instruction-pointer-register">IP 指令指针寄存器(Instruction Pointer Register)</h4>
<p>它用来存放代码段中的偏移地址。在程序运行的过程中,它始终指向下一条指令的首地址,它与段寄存器CS联用确定下一条指令的物理地址。当这一地址送到存储器后,控制器可以取得下一条要执行的指令,而控制器一旦取得这条指令就马上修改IP的内容,使它指向下一条指令的首地址。</p>
<p>可见,计算机就是用IP寄存器来控制指令序列的执行流程的,因此IP寄存器是计算机中很重要的一个控制寄存器。</p>
<h4 id="sp-堆栈指针寄存器stack-pointer-register">SP 堆栈指针寄存器(Stack Pointer Register)</h4>
<p>存放栈顶的偏移地址</p>
<h4 id="flags-标志寄存器--psw-程序状态寄存器program-status-word-register">FLAGS 标志寄存器 / PSW 程序状态寄存器(Program Status Word Register)</h4>
<p>存放条件码标志,控制标志和系统标志</p>
<h5 id="条件码标志">条件码标志</h5>
<p>条件码标志用来记录程序中运行结果的状态信息,它们是根据有关指令的运行结果由CPU自动设置的。由于这些状态信息往往作为后续条件转移指令的转移控制条件,所以称为条件码。它包括以下6位:</p>
<ul>
<li>OF 溢出标志(overflow flag)
<ul>
<li>在进行<strong>有符号数运算</strong>过程中,如操作数超出了机器能表示的范围称为溢出。
<ul>
<li>有溢出,OF = 1OV</li>
<li>无溢出,OF = 0NV</li>
</ul>
</li>
</ul>
</li>
<li>SF 符号标志(sign flag)
<ul>
<li>将结果视为<strong>有符号数</strong></li>
<li>记录运算结果的符号(符号位)
<ul>
<li>符号位为1时(负数),SF = 1NG</li>
<li>符号位为0时(非负),SF = 0PL</li>
</ul>
</li>
</ul>
</li>
<li>ZF 零标志(zero flag)
<ul>
<li>指令的运算结果是否是0
<ul>
<li>结果是 0 ,ZF = 1ZR
<ul>
<li>1 表示 ”逻辑真“</li>
</ul>
</li>
<li>结果不是0,ZF = 0NZ
<ul>
<li>0 表示 ”逻辑假“</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>CF 进位标志(carry flag)
<ul>
<li>进行<strong>无符号数运算</strong>时从最高有效位产生了进位值,或从更高位产生了借位值
<ul>
<li>有进位或借位,CF = 1CY</li>
<li>无进位或借位,CF = 0NC</li>
</ul>
</li>
</ul>
</li>
<li>PF 奇偶标志(parity flag)
<ul>
<li>用来为机器中传送信息时可能产生的代码出错情况提供检验条件。</li>
<li>记录指令执行后结果所有二进制位中 1 的个数
<ul>
<li>1的个数为偶数,PF = 1PE</li>
<li>1的个数为奇数,PF = 0PO</li>
</ul>
</li>
</ul>
</li>
<li>AF 辅助进位标志(auxiliary carry flag)</li>
</ul>
<h5 id="控制标志">控制标志</h5>
<p>控制标志位为方向标志(direction flag,DF),在串处理指令中控制处理信息的方向用。</p>
<ul>
<li>当DF位为1时,每次操作后使变址寄存器SI和DI减小,这样就使串处理从高地址向低地址方向处理。</li>
<li>当DF位为0时,则使SI和DI增大,使串处理从低地址向高地址方向处理</li>
</ul>
<h5 id="系统标志">系统标志</h5>
<p>系统标志位可以用于I/O、可屏蔽中断、程序调试、任务切换和系统工作方式等的控制。一般应用程序不必关心或修改这些位的状态,只有系统程序员或需要编制低层I/O 设备控制等程序时才需要访问其中的有关位。</p>
<ul>
<li>陷阱标志(trap flag,TF),用于调试时的单步方式操作。当TF位为1时,每条指令执行完后产生陷阱,由系统控制计算机;当TF位为0时,CPU正常工作,不产生陷阱。</li>
<li>中断标志(interrupt flag,IF),当IF位为1时,允许CPU响应可屏蔽中断请求,否则关闭中断。</li>
<li>I/O特权级(I/O privilege level,.IOPI),在保护模式下,用于控制对I/O地址空间的访问。<br>
<img src="https://img2023.cnblogs.com/blog/3076565/202310/3076565-20231028205336370-2020999033.png"></li>
</ul>
<h2 id="规范与约定">规范与约定</h2>
<h3 id="_">[...]</h3>
<p>(汇编语法规定)表示一个内存单元</p>
<p>一个内存单元的描述:</p>
<ul>
<li>内存单元的地址
<ul>
<li>段地址</li>
<li>偏移地址</li>
</ul>
</li>
<li>内存单元的长度(类型)</li>
</ul>
<h3 id="_-1">(...)</h3>
<p>(为了学习方便规定)表示一个内存单元或寄存器中的内容</p>
<h3 id="idata">idata</h3>
<p>表示常量,某个字符</p>
<h1 id="访问内存">访问内存</h1>
<p>为了访问内存,我们可以使用这四个寄存器:<strong>BX,SI,DI,BP</strong></p>
<p>我们可以使用如下方式访问对应地址:</p>
<p><img src="https://img2023.cnblogs.com/blog/3076565/202310/3076565-20231028205730014-1544448025.png"></p>
<blockquote>
<p>除了BP寄存器,其他寄存器默认均使用DS寄存器,BP使用SS寄存器</p>
</blockquote>
<p><img src="https://img2023.cnblogs.com/blog/3076565/202310/3076565-20231028205722992-363833211.png"></p>
<blockquote>
<p>BP不能和BX一起作为偏移地址,SI不能和DI一起作为偏移地址</p>
</blockquote>
<p>段寄存器中的值成为 段地址 ,通用寄存器中的值称为 偏移地址</p>
<h3 id="类型">类型</h3>
<p>为了告诉编译器数据类型,需要使用以下前缀:</p>
<h4 id="byte-ptr">byte ptr</h4>
<p>字节</p>
<h4 id="word-ptr">word ptr</h4>
<p>字</p>
<h2 id="mov-指令">MOV 指令</h2>
<p>mov 指令将第二个操作数(源)复制到第一个操作数上(目标)</p>
<ul>
<li>源操作数可以是立即数,通用寄存器或内存地址</li>
<li>目标操作数可以是通用寄存器或内存地址</li>
<li>两个操作数必须是同一数据类型</li>
</ul>
<p>MOV 指令支持以下指令形式:</p>
<ul>
<li>MOV REG,memory</li>
<li>MOV memory,REG</li>
<li>MOV REG,REG</li>
<li>MOV memory,immediate</li>
<li>MOV REG,immediate</li>
</ul>
<p><strong>REG</strong>:AX,BX,CX,DX,AH,AL,BH,BL,CH,CL,DH,DL,DI,SI,BP,SP</p>
<p><strong>memory</strong>:,,variable</p>
<p><strong>immediate</strong>: 5, -24, 3Fh, 10001101b</p>
<p>对于段寄存器只有以下指令可以使用:</p>
<ul>
<li>MOV SREG (<s>CS</s>) ,memory</li>
<li>MOV memory,SREG</li>
<li>MOV,REG,SREG</li>
<li>MOV SREG,REG</li>
</ul>
<p><strong>SREG</strong>: DS, ES, SS, and <strong>only as second operand</strong>: CS.</p>
<pre><code class="language-assembly">; 假设内存10000H原始值: 1122H
; 8086是小端模式,高字节放在高地址,低字节放在低地址
; 1000:000022
; 1000:000111

; 准备修改10000H位置的值
mov ax, 1000H
mov ds, ax

; 1000:000066
; 1000:000111
; 修改后10000H: 1166H
mov , 66h

; 1000:000066
; 1000:000111
; 修改后10000H: 1166H
mov byte ptr , 66h

; 1000:000066
; 1000:000100
; 修改后10000H: 0066H
mov word ptr , 66h
</code></pre>
<h2 id="寻址方式">寻址方式</h2>
<p>当数据存放在内存中的时候,我们可以用多种方式来给定这个内存单元的偏移地址,这种定位内存单元的方法一般称为寻址方式。</p>
<h3 id="数据有关的寻址方式">数据有关的寻址方式</h3>
<h5 id="隐含寻址">隐含寻址</h5>
<p>隐含寻址就是指令中不指明操作数,但隐含在操作码中。如乘法指令(MUL src)</p>
<h5 id="立即数寻址immediate-addressing">立即数寻址(immediate addressing)</h5>
<p>操作数直接包含在指令中,紧跟在操作码之后的寻址方式称为立即寻址方式,把该操作数称为立即数。</p>
<ul>
<li>可以是8位数或16位数</li>
<li>常用来给寄存器赋值,不执行总线周期,速度快</li>
<li>只能用于源操作数</li>
</ul>
<p>例:</p>
<p><code>MOV AL,2CH</code></p>
<p><code>MOV AX,2C40H</code></p>
<h5 id="寄存器寻址">寄存器寻址</h5>
<p>操作数包含在CPU内的某个寄存器中,指令直接给出寄存器名。</p>
<ul>
<li>
<p>对16位的操作数,寄存器可以是:AX,BX,CX,DX和SI,DI,SP,BP</p>
<p>对8位的操作数,寄存器可以是:AL,BL,CL,DL和AH,BH,CH,DH</p>
</li>
<li>
<p>源操作数和目的操作数都可用</p>
</li>
<li>
<p>不执行总线周期,执行速度快</p>
</li>
</ul>
<p>例:</p>
<p><code>INC CX</code></p>
<p><code>MOV AX,BX</code></p>
<h4 id="存储器寻址">存储器寻址</h4>
<p>除以上三种寻址方式外,以下各种寻址方式的操作数都在存储器中,其操作数称为存储器操作数。</p>
<p>由于80X86对内存采用分段管理,因此由以下寻址方式得到的只是有效地址(简写为EA-effective address,在IBM PC中就是操作数地址的偏移量部分)。</p>
<p>有效地址可以由以下四种成分组成:</p>
<ul>
<li>
<p>位移量(displacement)是存放在指令中的一个8位、16位或32位的数,它是一个地址。</p>
</li>
<li>
<p>基址(base)是存放在基址寄存器中的内容。通常用来指向数据段中数组或字符串的首地址。</p>
</li>
<li>
<p>变址(index)是存放在变址寄存器中的内容。通常用来访问数组中的某个元素或字符串中的某个字符。</p>
</li>
</ul>
<p>EA = 基址 + 变址 + 位移量</p>
<h5 id="直接寻址">直接寻址</h5>
<p>操作数的有效地址直接包含在指令中的寻址方式。</p>
<p>有效地址存放在代码段的指令操作码之后,但操作数本身在存储器中,所以必须先求出操作数的物理地址。这种寻址方式常用于存取简单变量。</p>
<ul>
<li>如果没有指定段超越前缀默认操作数在数据段</li>
<li><strong>编译后的程序与debug中表现不同,会将直接转换为idata,因此需要加上段前缀</strong></li>
</ul>
<p>例:<br>
<code>MOV AL, </code></p>
<blockquote>
<p>由于在汇编语言中用符号表示地址,所以指令“MOV AL,VAR”中的源操作数寻址方式是直接寻址,有时也写做“MOV AL,”</p>
</blockquote>
<h5 id="寄存器间接寻址">寄存器间接寻址</h5>
<p>操作数的<strong>有效地址</strong>在基址寄存器BX、BP或变址寄存器SI、DI中,而操作数在存储器中的寻址方式</p>
<ul>
<li>
<p>能用作间址寄存器的寄存器:BX, BP, SI, DI</p>
</li>
<li>
<p>BP为间址寄存器时,操作数在堆栈段中,其余为数据段</p>
</li>
</ul>
<p>例:</p>
<p><code>MOV AX, </code></p>
<h5 id="寄存器相对寻址">寄存器相对寻址</h5>
<p>也称为直接变址寻址方式。操作数的有效地址是一个基址(BX、BP)或变址(SI、DI)寄存器的内容和指令中给定的一个位移量(disp)之和。有效地址由2部分组成。</p>
<ul>
<li>
<p>EA = (基址/变址寄存器) + 0/8/16位位移量</p>
</li>
<li>
<p>段地址对应BX/SI/DI寄存器默认是DS,对应BP寄存器默认是SS,可用段超越前缀改变。</p>
</li>
</ul>
<p>例:<br>
<code>MOV AX, </code></p>
<h5 id="基址变址寻址">基址变址寻址</h5>
<p>操作数的有效地址是一个基址寄存器(BX、BP)和一个变址寄存器(SI、DI)的内容之和。缺省使用段寄存器的情况由基址寄存器决定。</p>
<ul>
<li>
<p>EA=BX/BP+SI/DI</p>
</li>
<li>
<p>段基址对应BX基址寄存器默认是DS,对应BP基址寄存器默认是SS,可用段超越前缀改变。</p>
</li>
</ul>
<p>例:<br>
<code>MOV AX, ← → MOV AX, DS: </code></p>
<h5 id="相对基址变址寻址">相对基址变址寻址</h5>
<p>操作数的有效地址是一个基址和一个变址寄存器的内容和指令中给定的一个位移量之和。有效地址由三部分组成。缺省使用段寄存器的情况由基址寄存器决定。</p>
<ul>
<li>
<p>EA=BX/BP+SI/DI+8/16位位移量</p>
</li>
<li>
<p>段地址对应BX基址寄存器默认是DS,对应BP基址寄存器默认是SS,可用段超越前缀改变。</p>
</li>
</ul>
<p>例:<br>
<code>MOV AX, 06H ← → MOV AX, DS:</code></p>
<h3 id="与转移地址有关的寻址方式">与转移地址有关的寻址方式</h3>
<p>这种寻址方式用来确定转移指令及CALL指令的专项地址</p>
<p><img src="https://img2023.cnblogs.com/blog/3076565/202310/3076565-20231028205505335-2132671588.png"></p>
<p><mark>非考点,略</mark></p>
<h2 id="变量">变量</h2>
<p>变量是一种内存地址</p>
<p>8086的汇编器支持两种类型的变量:BYTE 和 WORD</p>
<p>声明一个变量的方式如下</p>
<pre><code class="language-assembly">name DB value                ;DB 表示字节 Define Byte

name DW value         ;DW 表示单字 Define Word

name DD value         ;DD 表示双字 Define Double(Word)

; name 可以是任何字符和/或数字的组合,但是它必须以一个字符开始
; 可以声明一个未命名的变量(变量会拥有一个地址,但是没有名字)

;value 可以是任何数字或者是 '?' 表示未初始化
</code></pre>
<h3 id="数组">数组</h3>
<p>数组可以被视为一系列变量,下面是一些数组声明的例子:</p>
<pre><code class="language-assembly">a DB 48h, 65h, 6Ch, 6Ch, 6Fh, 00h
b DB 'Hello', 0
</code></pre>
<p><img src="https://yassinebridi.github.io/asm-docs/img/array.gif"></p>
<p>你可以通过方括号来访问数组中的任何元素</p>
<pre><code class="language-assembly">MOV AL,a
</code></pre>
<p>你还可以使用任何内存索引寄存器 <strong>BX,SI,DI,BP</strong> 来访问其中的元素</p>
<pre><code class="language-assembly">MOV SI,3
MOV AL,a
</code></pre>
<h4 id="dup">DUP</h4>
<p>如果需要声明一个一个大数组,可以使用DUP操作符</p>
<p><code>number DUP (value(s))</code></p>
<p>number —— 需要声明的复制(任何常数)</p>
<p>value —— 将要复制的值</p>
<blockquote>
<p>如果你想要声明一个比255更大或比-128更小的值,你可以使用DW取代DB。注意:DW 不能被用来声明字符串</p>
</blockquote>
<h3 id="获得变量的地址leaoffset">获得变量的地址(LEA,OFFSET)</h3>
<p>你可以通过两种方式获得变量的地址:LEA(Load Effective Address)指令和它的替代品 OFFSET 操作符</p>
<p>LEA 同时还能让你获得索引变量的地址。</p>
<p>LEA和OFFSET最后都会被编译为同一种机器语言:</p>
<p><code>MOV BX,num</code></p>
<p>num是一个16进制的偏移量</p>
<blockquote>
<p>注意:只有以下寄存器可以被用在方括号内(作为内存指针):<strong>BX,SI,DI,BP</strong></p>
</blockquote>
<h4 id="lea">LEA</h4>
<ul>
<li>
<p>格式:<code>LEA REG,SRC</code></p>
</li>
<li>
<p>操作:(REG) &lt;- SRC</p>
</li>
</ul>
<p><strong>目的操作数不能使用段寄存器,源操作数可使用任一存储器寻址方式</strong></p>
<p><img src="https://img2023.cnblogs.com/blog/3076565/202310/3076565-20231028205248351-1657486941.png"></p>
<pre><code class="language-assembly">ORG 100h
MOV    AL, VAR1            ; 将VAR1的值送入AL
LEA    BX, VAR1            ; 获取VAR1的地址,并存入BX
MOV    BYTE PTR , 44h    ; 修改VAR1的值为44h
MOV    AL, VAR1            ; VAR 送入 AL
RET
VAR1   DB22h
END
</code></pre>
<h4 id="ldsleslfslgslss">LDS,LES,LFS,LGS,LSS</h4>
<p>LDS(load DS with pointer) 指针送寄存器和DS</p>
<p>LES(load ES with pointer) 指针送寄存器和ES</p>
<p>LFS(load FS with pointer) 指针送寄存器和FS</p>
<p>LGS(load GS with pointer) 指针送寄存器和GS</p>
<p>LSS(Load SS with Pointer) 指针送寄存器和SS</p>
<ul>
<li>
<p>这一组指令完成把地址送到指定的寄存器中</p>
</li>
<li>
<p>格式:同LEA</p>
</li>
<li>
<p>操作:(以LDS为例):</p>
</li>
</ul>
<p>​                (REG) &lt;- (SRC)</p>
<p>​                (SREG) &lt;- (SRC + 2)</p>
<p>​        或   (SREG)&lt;- (SRC + 4)</p>
<p><strong>源操作数只能使用存储器寻址方式</strong></p>
<p><strong>当指令指定的是16位寄存器时,把该存储单元中存放的16位偏移地址装入该寄存器中,然后把(SRC+2)中的16位数装入指令指定的段寄存器中,32为改为(SRC+4)</strong></p>
<p><strong>目的寄存器不允许使用段寄存器,LFS、LGS、LSS只能用于386以后的机型中</strong></p>
<p><strong>不影响标志位</strong></p>
<h4 id="offset">OFFSET</h4>
<pre><code class="language-assembly">ORG 100h
MOV    AL, VAR1         
MOV    BX, OFFSET VAR1   
MOV    BYTE PTR , 44h   
MOV    AL, VAR1            
RET
VAR1   DB22h
END
</code></pre>
<p>如果想要知道某条语句的地址,可以通过标号的方式获取</p>
<pre><code class="language-assembly">S:MOV AX,BX
MOV AX,OFFSET S

P:MOV AX,OFFSET P
</code></pre>
<p>利用这种方法可以实现指令在程序运行中的复制:</p>
<pre><code class="language-assembly">assume cs:code
code segment
s:mov ax,bx
mov si,offset s
mov di,offset s0
mov ax,cs:
mov cs:,ax
s0:nop ;nop 为占位符,占一个字节
   nop
code ends
ends
</code></pre>
<h2 id="常量">常量</h2>
<ul>
<li>常量和变量一样,但是他们只在编译程序时存在,为了定义常量,我们需要用到EQU指令</li>
</ul>
<h3 id="equ">EQU</h3>
<ul>
<li>
<p>格式:<code>name EQU &lt;expression&gt;</code></p>
</li>
<li>
<p>功能:声明常量</p>
</li>
</ul>
<h1 id="内中断">内中断</h1>
<p>中断可以被看作是一系列功能。这些功能使编程变得更加简单,而不需要编写代码来打印字符,你可以简单地调用中断,它将为您=你完成一切。还有一些与磁盘驱动器和其他硬件一起工作的中断功能。我们将这些功能称为软中断。</p>
<p>中断也可以被不同的硬件触发,这些被称为硬中断。目前我们只关心软中断。</p>
<p>要进行软中断,有一个INT指令,它有非常简单的语法:</p>
<p>​        <code>INT value</code></p>
<p>value 的值可以是0 ~ 255(或 0 ~ 0FFH)</p>
<ul>
<li>一般我们使用十六进制数</li>
</ul>
<p>要使用中断的子功能,需要在调用中断前设置寄存器</p>
<ul>
<li>通常使用AH寄存器,但有时候会使用其他寄存器</li>
</ul>
<h2 id="常见的中断值对应功能">常见的中断值对应功能:</h2>
<h3 id="int-21">INT 21</h3>
<ul>
<li>
<p>AX对应值为:</p>
<ul>
<li>
<p>4C:带返回码结束,AL = 返回码</p>
</li>
<li>
<p>07:键盘输入(无回显)</p>
</li>
<li>
<p>01:键盘输入并回显 , AL = 输入字符</p>
</li>
<li>
<p>00:程序终止</p>
</li>
<li>
<p>02:显示输出,DL = 输出字符</p>
</li>
</ul>
</li>
</ul>
<h1 id="运算">运算</h1>
<h2 id="inc">INC</h2>
<ul>
<li>格式:<code>INC opr</code></li>
<li>操作:opr = opr + 1</li>
</ul>
<h2 id="dec">DEC</h2>
<ul>
<li>格式:<code>DEC opr</code></li>
<li>操作:opr = opr - 1</li>
</ul>
<h2 id="add">ADD</h2>
<ul>
<li>格式:<code>ADD opr1,opr2</code>
<ul>
<li>opr1:memory,register</li>
<li>opr2:memory,register,immediate</li>
</ul>
</li>
<li>操作:opr1 = opr1 + opr2</li>
</ul>
<h2 id="adc">ADC</h2>
<ul>
<li>格式:<code>ADC opr1,opr2</code></li>
<li>操作:opr1 = opr1 + opr2 + CF</li>
</ul>
<h2 id="sub">SUB</h2>
<ul>
<li>格式:<code>SUB opr1,opr2</code></li>
<li>操作:opr1 = opr1 - opr2</li>
</ul>
<h2 id="sbb">SBB</h2>
<ul>
<li>格式:<code>SUB opr1,opr2</code></li>
<li>操作:opr1 = opr1 - opr2 - CF</li>
</ul>
<h2 id="div">DIV</h2>
<ul>
<li>格式:<code>DIV opr</code>
<ul>
<li>opr:memory,register</li>
</ul>
</li>
<li>被除数:(默认)放在AX或DX和AX中
<ul>
<li>8位除数
<ul>
<li>被除数放在AX,商放入AL,余数放入AH</li>
</ul>
</li>
<li>16位除数
<ul>
<li>被除数高位放在DX,低位放在AX,商放入AX,余数放入DX</li>
</ul>
</li>
</ul>
</li>
</ul>
<h2 id="mul">MUL</h2>
<ul>
<li>格式:<code>MUL opr</code>
<ul>
<li>opr:memory,register</li>
</ul>
</li>
<li>被乘数:(默认)放在AL或AX中
<ul>
<li>8位乘数
<ul>
<li>被乘数放在AL,结果放入AX</li>
</ul>
</li>
<li>16位乘数
<ul>
<li>被乘数放在AX,结果高位放在DX,低位放在AX</li>
</ul>
</li>
</ul>
</li>
</ul>
<h1 id="栈">栈</h1>
<h2 id="push">PUSH</h2>
<ul>
<li>格式:<code>PUSH src</code>
<ul>
<li>src 可以是:
<ul>
<li>寄存器:AX,BX,CX,DX,DI,SI,BP,SP</li>
<li>段寄存器:DS,ES,SS,CS</li>
<li>内存单元</li>
<li>立即数(仅80186以后的CPU可以)</li>
</ul>
</li>
</ul>
</li>
<li>功能:将一个<strong>16位数</strong>放入栈中</li>
<li>原理:SP = SP - 2, 将src的值写入此时SP指向的内存单元</li>
<li>SS:SP此时指向新栈顶</li>
</ul>
<h2 id="pop">POP</h2>
<ul>
<li>格式:<code>POP des</code>
<ul>
<li>des 可以是
<ul>
<li>寄存器:AX,BX,CX,DX,DI,SI,BP,SP</li>
<li>段寄存器:DS,ES,SS(除了CS)</li>
<li>内存单元</li>
</ul>
</li>
</ul>
</li>
<li>原理:将此时SP指向的内存单元中的值写入des,SP = SP + 2</li>
<li>SS:SP 此时指向新栈顶</li>
</ul>
<h1 id="流控制">流控制</h1>
<h2 id="jmp">JMP</h2>
<ul>
<li>基础语法:<code>JMP label</code>
<ul>
<li>label 指程序中存在的表示,一般在行首以”label:“的形式出现
<ul>
<li>label标记了对应行/后续一条指令</li>
</ul>
</li>
</ul>
</li>
</ul>
<h3 id="短转移">短转移</h3>
<ul>
<li>格式:<code>JMP short label</code></li>
<li>功能:(IP) = (IP) + val(8 bits)</li>
<li>原理
<ul>
<li>8位位移 = 标号处地址 - jmp 指令后<strong>第一个字节</strong>的地址</li>
<li>short 指明此处位移为8位位移
<ul>
<li>8位位移的范围为 -128 ~ 127,用补码表示</li>
<li>由编译程序在编译时算出</li>
</ul>
</li>
</ul>
</li>
</ul>
<h3 id="近转移">近转移</h3>
<ul>
<li>格式:<code>JMP near ptr label</code></li>
<li>功能:(IP) = (IP) + val(16 bits)</li>
<li>原理
<ul>
<li>16位位移 = 标号处地址 - jmp 指令后<strong>第一个字节</strong>的地址</li>
<li>near ptr 指明此处位移为16位位移,进行的是<strong>段内</strong>近转移
<ul>
<li>16位位移的范围为 -32769 ~ 32767,用补码表示</li>
<li>由编译程序在编译时算出</li>
</ul>
</li>
</ul>
</li>
</ul>
<h3 id="远转移">远转移</h3>
<ul>
<li>格式:<code>JMP far ptr label</code></li>
<li>功能:CS:IP = (label)</li>
<li>原理
<ul>
<li>直接跳转到目的地址</li>
<li>段间原转移</li>
</ul>
</li>
</ul>
<h3 id="转移地址在寄存器中的jmp指令">转移地址在寄存器中的JMP指令</h3>
<ul>
<li>格式:<code>jmp reg</code></li>
<li>功能: IP = (reg)</li>
</ul>
<h3 id="转移地址在内存中的jmp指令">转移地址在内存中的JMP指令</h3>
<h4 id="段内转移">段内转移</h4>
<ul>
<li>
<p>格式:<code>JMP word ptr adr</code></p>
</li>
<li>
<p>功能:从内存单元地址处开始存放着<strong>一个字</strong>,是转移的**目的偏移地址****</p>
</li>
</ul>
<h4 id="段间转移">段间转移</h4>
<ul>
<li>格式:<code>JMP dword ptr adr</code></li>
<li>功能:从内存单元地址处开始存着<strong>两个字</strong>,高地址处的字是转移的<strong>目的段地址</strong>,低地址处是转移的<strong>目的偏移地址</strong></li>
</ul>
<h2 id="jcxz">JCXZ</h2>
<ul>
<li>
<p>格式:<code>JCXZ label</code></p>
</li>
<li>
<p>功能:</p>
<ul>
<li>如果(cx) = 0,则转移到标号处执行
<ul>
<li>(IP) = (IP) + 8位位移
<ul>
<li>8位位移 = 标号处地址 - jcxz指令后<strong>第一个字节</strong>的地址</li>
</ul>
</li>
</ul>
</li>
<li>否则什么也不做(程序向下执行)</li>
</ul>
</li>
<li>
<p>是有条件转移指令</p>
<ul>
<li>所有有条件转移指令都是短转移</li>
<li>对IP的修改范围都为-128~127</li>
<li>在对应的机器码中包含转移的位移,而不是目的地址</li>
</ul>
</li>
</ul>
<h2 id="loop">LOOP</h2>
<ul>
<li>格式:<code>LOOP label</code></li>
<li>指令操作:
<ol>
<li>(cx) = (cx) - 1</li>
<li>(cx) != 0时,转移到标号处执行,否则向下执行</li>
</ol>
</li>
<li>是相对位移</li>
<li>反指令为:
<ul>
<li><code>DEC CX</code> + <code>JCXZ</code></li>
</ul>
</li>
</ul>
<h3 id="loope">LOOPE</h3>
<ul>
<li>
<p>指令操作:</p>
<ol>
<li>(cx) = (cx) - 1</li>
<li>(cx) != 0 且 zf = 1 (Equal) 时,转移到标号处执行,否则向下执行</li>
</ol>
</li>
<li>
<p>反指令为:<code>LOOPNE</code></p>
</li>
</ul>
<h3 id="loopz">LOOPZ</h3>
<ul>
<li>指令操作:
<ol>
<li>(cx) = (cx) - 1</li>
<li>(cx) != 0 且 zf = 0 时,转移到标号处执行,否则向下执行</li>
</ol>
</li>
<li>反指令为:<code>LOOPNZ</code></li>
</ul>
<h2 id="其他有条件短转移">其他有条件短转移</h2>
<h4 id="单个标志位检测">单个标志位检测</h4>
<table>
<thead>
<tr>
<th style="text-align: center">指令</th>
<th style="text-align: center">作用</th>
<th>条件</th>
<th>反指令</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: center">JZ,JE</td>
<td style="text-align: center">Jump if Zero(Euqal)(<font color="red">=</font>)</td>
<td>ZF = 1</td>
<td>JNZ,JNE</td>
</tr>
<tr>
<td style="text-align: center">JC,JB,JNAE</td>
<td style="text-align: center">Jump if Carry(Below,Not Above Equal)(<font color="red">&lt;</font>)</td>
<td>CF = 1</td>
<td>JNC,JNB,JNE</td>
</tr>
<tr>
<td style="text-align: center">JS</td>
<td style="text-align: center">Jump if Sign (Negative)</td>
<td>SF = 1</td>
<td>JNS</td>
</tr>
<tr>
<td style="text-align: center">JO</td>
<td style="text-align: center">Jump if Overflow</td>
<td>OF = 1</td>
<td>JNO</td>
</tr>
<tr>
<td style="text-align: center">JPE,JP</td>
<td style="text-align: center">Jump if Parity Even</td>
<td>PF = 1</td>
<td>JPO,JNP</td>
</tr>
</tbody>
</table>
<h4 id="有符号数的条件检测">有符号数的条件检测:</h4>
<table>
<thead>
<tr>
<th style="text-align: center">指令</th>
<th style="text-align: center">作用</th>
<th style="text-align: center">条件</th>
<th style="text-align: center">反指令</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: center">JE,JZ</td>
<td style="text-align: center">Jump if Euqal(<font color="red">=</font>)<br>Jump if Zero</td>
<td style="text-align: center">ZF = 1</td>
<td style="text-align: center">JNZ,JNE</td>
</tr>
<tr>
<td style="text-align: center">JG,JNLE</td>
<td style="text-align: center">Jump if Greater(<font color="red">&gt;</font>)<br>Jump if Not Less or Equal(<font color="red">not &lt;=</font>)</td>
<td style="text-align: center">ZF = 0 and SF = OF</td>
<td style="text-align: center">JNG,JLE</td>
</tr>
<tr>
<td style="text-align: center">JL,JNGE</td>
<td style="text-align: center">Jump if Less(<font color="red">&lt;</font>)<br>Jump if Not Greater or Equal(<font color="red">not &gt;=</font>)</td>
<td style="text-align: center">SF != OF</td>
<td style="text-align: center">JNL,JGE</td>
</tr>
</tbody>
</table>
<h4 id="无符号数的条件检测">无符号数的条件检测</h4>
<table>
<thead>
<tr>
<th style="text-align: center">指令</th>
<th style="text-align: center">作用</th>
<th style="text-align: center">条件</th>
<th style="text-align: center">反指令</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: center">JE,JZ</td>
<td style="text-align: center">Jump if Euqal(<font color="red">=</font>)<br>Jump if Zero</td>
<td style="text-align: center">ZF = 1</td>
<td style="text-align: center">JNZ,JNE</td>
</tr>
<tr>
<td style="text-align: center">JA,JNBE</td>
<td style="text-align: center">Jump if Above(<font color="red">&gt;</font>)<br>Jump if Not Below or Equal(<font color="red">not &lt;=</font>)</td>
<td style="text-align: center">CF = 0 and ZF = 0</td>
<td style="text-align: center">JNA,JBE</td>
</tr>
<tr>
<td style="text-align: center">JB,JNAE,JC</td>
<td style="text-align: center">Jump if Below(<font color="red">&lt;</font>)<br>Jump if Not Above or Equal(<font color="red">not &gt;=</font>)<br>Jump if Carry</td>
<td style="text-align: center">CF = 1</td>
<td style="text-align: center">JNB,JAE,JNC</td>
</tr>
</tbody>
</table>
<h2 id="cmp">CMP</h2>
<p>通常我们使用CMP来进行比较</p>
<p>格式:<code>CMP oprand1,oprand2</code></p>
<p>它将 openrand1 和 oprand2 相减,但是不保存结果,只根据结果来改变符号位</p>
<h2 id="call-与-ret">CALL 与 RET</h2>
<h3 id="call">CALL</h3>
<ul>
<li>格式:<code>CALL label</code></li>
<li>操作:
<ol>
<li>将当前IP或CS和IP压入栈中</li>
<li>转移到标号处执行指令</li>
</ol>
</li>
<li>16位位移 = 标号处地址 - CALL指令后<strong>第一个字节</strong>的地址
<ul>
<li>16位位移范围为-32768~32767</li>
</ul>
</li>
</ul>
<h4 id="call-far-ptr">CALL far ptr</h4>
<ul>
<li>
<p>格式:<code>CALL far ptr label</code></p>
</li>
<li>
<p>操作:</p>
<ol>
<li>(sp) = (sp) - 2</li>
<li>((ss) x 16 + (sp)) = (CS)</li>
<li>(sp) = (sp) - 2</li>
<li>((ss) x 16 + (sp)) = (IP)</li>
</ol>
</li>
<li>
<p>(CS) = 标号所在的段地址</p>
<p>(IP) = 标号所在的偏移地址</p>
</li>
<li>
<p>相当于</p>
<pre><code class="language-assembly">push CS
push IP
jmp far ptr label
</code></pre>
</li>
</ul>
<h4 id="转移地址在寄存器中的call指令">转移地址在寄存器中的CALL指令</h4>
<ul>
<li>
<p>格式:<code>CALL reg</code></p>
</li>
<li>
<p>操作:</p>
<ol>
<li>(sp) = (sp) - 2</li>
<li>((ss) x 16 + (sp)) = (IP)</li>
</ol>
</li>
<li>
<p>(IP) = (reg)</p>
</li>
<li>
<p>相当于</p>
<pre><code class="language-assembly">push IP
jmp reg
</code></pre>
</li>
</ul>
<h4 id="转移地址在内存中的call指令">转移地址在内存中的CALL指令</h4>
<h5 id="段内转移-1">段内转移</h5>
<ul>
<li>
<p>格式:<code>CALL word ptr adr</code></p>
</li>
<li>
<p>相当于</p>
<pre><code class="language-assembly">push IP
jmp word ptr adr
</code></pre>
</li>
</ul>
<h5 id="段间转移-1">段间转移</h5>
<ul>
<li>
<p>格式:<code>CALL dword ptr adr</code></p>
</li>
<li>
<p>相当于</p>
<pre><code class="language-assembly">push CS
push IP
jmp dword ptr adr
</code></pre>
</li>
</ul>
<h3 id="ret">RET</h3>
<ul>
<li>格式:<code>RET</code></li>
<li>功能:用栈中的数据,修改IP的内容,从而实现近转移</li>
<li>相当于 <code>pop IP</code></li>
</ul>
<h4 id="retf">RETF</h4>
<ul>
<li>
<p>格式:<code>RETF</code></p>
</li>
<li>
<p>功能:用栈中的数据,修改CS和IP的内容,从而实现远转移</p>
</li>
<li>
<p>相当于</p>
<pre><code class="language-assembly">pop IP
pop CS
</code></pre>
</li>
</ul><br><br>
来源:https://www.cnblogs.com/stypro/p/17794633.html
頁: [1]
查看完整版本: 8086 汇编语言知识点梳理