《汇编语言》学习笔记-3
<p>注:本文档为“《汇编语言(第3版) 》王爽著”阅读过程中记的笔记。</p><p>参考视频:通俗易懂的汇编语言(王爽老师的书)_哔哩哔哩_bilibili</p>
<h2 id="11-标志寄存器">11 标志寄存器</h2>
<p>CPU内部的标记寄存器具有以下作用:</p>
<ol>
<li>用来存储相关指令的某些执行结果;</li>
<li>用来为CPU执行相关指令提供行为依据;</li>
<li>用来控制CPU的相关工作方式。</li>
</ol>
<p>8086CPU的标志寄存器是16位的,其中存储的信息通常被称为程序状态字(PSW)。</p>
<p>flag寄存器的按位起作用的,每一个位有专门的含义,记录特定的信息。</p>
<p><img src="https://img2022.cnblogs.com/blog/1078885/202209/1078885-20220906221637311-1706634939.png" alt="image-20220906221636292" loading="lazy"></p>
<p>其中,第1、3、5、12、13、14、15位没有使用。</p>
<p>debug.exe的的标志寄存器:</p>
<p><img src="https://img2022.cnblogs.com/blog/1078885/202209/1078885-20220908084452749-1842493125.png" alt="image-20220908084452170" loading="lazy"></p>
<p>直接访问标志寄存器的方法:</p>
<ul>
<li>pushf:将标志寄存器的值压栈</li>
<li>popf:从栈中弹出数据,送入标志寄存器中。</li>
</ul>
<h3 id="111-z-flagzero-flag标志">11.1 Z-Flag(Zero Flag)标志</h3>
<p>flag的第6位是Z-Flag,零标志位。它记录相关指令执行后,其结果是否位0。</p>
<ul>
<li>如果结果为0,那么Z-Flag=1,’1‘表示逻辑真;</li>
<li>如果结果不为0,那么Z-Flag=0,’0‘表示逻辑假。</li>
</ul>
<p>举例:</p>
<pre><code class="language-assembly">mov ax, 1
sub ax, 1 ; 执行后,结果为0,Z-Flag=1
mov ax, 2
sub ax, 1 ; 执行后,结果不为0,Z-Flag=0
</code></pre>
<p><img src="https://img2022.cnblogs.com/blog/1078885/202209/1078885-20220906230915963-1574336272.png" alt="image-20220906230914912" loading="lazy"></p>
<p>8086CPU指令集中,有些指令影响标志寄存器,比如:add、sub、mul、div、inc、or、and等,大部分是运算指令,进行逻辑或算术运算。</p>
<p>有些指令对标志寄存器没有影响,比如:pop、push、mov,大部分是传送指令。</p>
<h3 id="112-p-flagparity-flag标志">11.2 P-Flag(Parity Flag)标志</h3>
<p>flag的第2位是PF位,奇偶校验位,它记录相关指令执行后,其结果的所有bit位中1的个数是否为偶数。</p>
<ul>
<li>如果1的个数为偶数,P-Flag=1;</li>
<li>如果1的个数为奇数,P-Flag=0。</li>
</ul>
<p>举例:</p>
<pre><code class="language-assembly">mov al, 1
add al, 10 ; 执行后,结果为11,P-Flag=0
mov al, 1
or al, 2 ; 执行后,结果为3,P-Flag=1
</code></pre>
<p><img src="https://img2022.cnblogs.com/blog/1078885/202209/1078885-20220906232121825-258926366.png" alt="image-20220906232120990" loading="lazy"></p>
<p>debug.exe中:pe表示偶校验,po表示奇校验。</p>
<h3 id="113-s-flagsign-flag标志">11.3 S-Flag(Sign Flag)标志</h3>
<p>flag的第7位是S-Flag,符号标志位,它记录相关指令执行后,其结果是否为负。</p>
<ul>
<li>如果结果为负,S-Flag=1;</li>
<li>如果结果非负,S-Flag=0。</li>
</ul>
<p>计算机中通常<strong>用补码来表示有符号数据</strong>,计算机中的一个数据可以看作是有符号数,也可以看作无符号数。比如:</p>
<pre><code class="language-assembly">00000001B:可看作无符号数1,或者符号数+1;
10000001B:可以看作为无符号数129,或者有符号数-127。
</code></pre>
<p>CPU执行add等运算指令时,已经包含了正负两种含义,也将得到用同一种信息来记录的两种结果,关键在于程序需要哪一种结果。</p>
<p>S-Flag标志就是CPU对<strong>有符号数运算结果</strong>的一种记录,它记录数据的正负。在我们将数据当作有符号数来运算的时候,可以通过它来得知结果的正负。如果我们将数据当作无符号数来运算,S-Flag的值没有意义。</p>
<p>也就是说,CPU在执行add等指令时,是必然要影响到SF标志位的值,是否需要这个值,看指令所进行的运算。</p>
<pre><code class="language-assembly">mov al, 10000001B
add al, 1 ; 执行结果为10000010B,S-Flag=1,表示如果指令进行的是有符号运算,则结果为负。
mov al, 10000001B
add al, 01111111B ; 执行后,结果为0,SF=0,如果指令进行的是有符号数运算,则结果非负。
</code></pre>
<p>某些指令将影响标志寄存器中的多个标志位,这些被影响的标记位比较全面的记录了指令的执行结果,为相关的处理提供了所需的依据。</p>
<p><img src="https://img2022.cnblogs.com/blog/1078885/202209/1078885-20220906234243872-134651953.png" alt="image-20220906234243006" loading="lazy"></p>
<p>debug.exe中:ng表示负数,pl表示正数。</p>
<h3 id="114-c-flagcarry-flag标志">11.4 C-Flag(Carry Flag)标志</h3>
<p>flag的第0位是CF,进位标志位。一般情况下,<strong>在进行无符号数运算的时候</strong>,它记录了运算结果的最高位有效位向更高位的进位值,或从更高位的借位值。</p>
<p>CF记录指令执行后,</p>
<ul>
<li>有进位或借位,C-Flag=1;</li>
<li>无进位或借位,C-Flag=0。</li>
</ul>
<p>对于位数为N的无符号数来说,其对应的二进制信息的最高位,即第N-1位,就是它的最高有效位,而假想存在的第N位,就是相当于最高有效位的更高位。</p>
<p><img src="https://img2022.cnblogs.com/blog/1078885/202209/1078885-20220907213840740-1798376691.png" alt="image-20220907213840060" loading="lazy"></p>
<pre><code class="language-assembly">mov al, 98H
add al, al ; 执行后,(al)=30H,CF=1,CF记录了从最高有效位向更高位的进位值
add al, al ; 执行后,(al)=60H,CF=0,CF记录了从最高有效位向更高位的进位值
</code></pre>
<p><img src="https://img2022.cnblogs.com/blog/1078885/202209/1078885-20220907214545371-875566159.png" alt="image-20220907214545079" loading="lazy"></p>
<p>当两个数据做减法的时候,有可能向更高位借位。C-Flag也可以用来记录这个借位值。</p>
<pre><code class="language-assembly">mov al, 97h
sub al, 98h ; 执行后,(al)=FFH,CF=1,CF记录了向更高位的借位值
sub al, al ; 执行后,(al)=0,CF=0,CF记录了向更高位的借位值
</code></pre>
<h3 id="115-o-flagoverflow-flag标志">11.5 O-Flag(Overflow Flag)标志</h3>
<p>溢出:在进行有符号数运算的时候,如结果超过了机器所能表示的范围称为溢出。</p>
<p>机器所能表示的范围:比如指令运算的结果用8位寄存器或内存单元存放,对于8位有符号数据,范围就是-128~127;16位有符号数据,范围就是-32768~32767。</p>
<p>如果运算结果(有符号数)超出了机器所能表达的范围,将产生溢出。</p>
<pre><code class="language-assembly">mov al, 98
add al, 99 ; 执行后将产生溢出,执行结果为197,超过了127
mov al, 0F0h ; F0H,为有符号数-16的补码
add al, 088H ; 88H,为有符号数-120的补码,执行后结果为-136,溢出
</code></pre>
<p>由于<strong>在进行有符号数运算的时候</strong>,可能发生溢出而造成错误的结果,则CPU需要对指令执行后是否产生溢出进行记录。</p>
<p>O-Flag就是溢出标记位,一般情况下,O-Flag记录了有符号数运算的结果是否发生了溢出。</p>
<ul>
<li>如果发生溢出,O-Flag=1;</li>
<li>如果没有溢出,O-Flag=0。</li>
</ul>
<p>C-Flag和O-Flag的区别:C-Flag是对无符号数运算有意义的标志位,O-Flag是对有符号数运算有意义的标志位。</p>
<pre><code class="language-assembly">mov al, 98
add al, 99 ; 执行结果197(C5H),C-Flag=0,O-Flag=1
mov al, 0F0H
add al, 88H ; 执行结果178H,C-Flag=1,O-Flag=1((-16)+(-120)=-136)
</code></pre>
<p>C-Flag和O-Flag的区别:</p>
<ul>
<li>C-Flag是对无符号数运算有意义的进/借位标志位;</li>
<li>O-Flag是对有符号数运算有意义的溢出标志位。</li>
</ul>
<h3 id="116-adc指令">11.6 adc指令</h3>
<p>adc是带进位加法指令,利用C-Flag上记录的进位值。</p>
<p>指令格式:</p>
<pre><code class="language-assembly">adc 操作对象1, 操作对象2
</code></pre>
<p>功能:操作对象1=操作对象1+操作对象2+C-Flag</p>
<p>举例:</p>
<pre><code class="language-assembly">adc ax, bx ; (ax)=(ax)+(bx)+C-Flag
mov ax, 2
mov bx, 1
sub bx, ax ; (C-Flag)=1
adc ax, 1 ; (ax)=4,相当于(ax)+1+C-Flag=2+1+1=4
mov ax, 1
add ax, ax
adc ax, 3 ; (ax)=5,相当于(ax)+1+C-Flag=2+3+0=4
mov al, 98H
add al, al ; 98H+98H=130H
adc al, 3 ; (ax)=34H,相当于(al)+3+C-Flag=30H+3+1=34H
</code></pre>
<p>C-Flag的值的含义:在执行adc指令的时候CF的含义是由adc指令前面的指令决定的,如果C-Flag的值是被sub指令设置的,那么它的含义就是借位值;如果是被add指令设置的,那么它的含义就是进位值。</p>
<p><strong>加法可以分两步来进行:①低位相加;②高位相加再加上低位相加产生的进位值。</strong></p>
<p>CPU提供adc指令的目的:进行加法的第二步运算,adc指令和add指令相配合就可以对更大的数据进行加法运算。</p>
<p>实验:计算1EF000H+201000H,结果放在ax(高16位)和bx(低16位)中。</p>
<p>思路:由于两个数据都大于16位,用add指令无法进行计算,将计算分为2步进行,先将低16位相加,然后将高16位和进位值相加。</p>
<pre><code class="language-assembly">mov ax, 001EH
mov bx, 0F000H
add bx, 1000H ; 先进行低16位相加
adc ax, 0020H ; 再进行高位相加
</code></pre>
<p>实验:计算1EF0001000H+2010001EF0H,结果放在ax(高16位)、bx(次高16位)、cx(低16位)中。</p>
<p>思路:</p>
<ol>
<li>先将低16位相加,完成后,C-Flag中记录本次相加的进位值;</li>
<li>再将次高16位和C-Flag(来自低16位的进位值)相加,完成后,C-Flag中记录本次相加的进位值;</li>
<li>最后高16位和C-Flag(来自次高16位的进位值)相加,完成后,C-Flag中记录本次相加的进位值。</li>
</ol>
<pre><code class="language-assembly">mov ax, 001EH
mov bx, 0F000H
mov cx, 1000H
add cx, 1EF0H
adc bx, 1000H
adc ax, 0020H
</code></pre>
<p>实验:128位的数据相加</p>
<p><img src="https://img2022.cnblogs.com/blog/1078885/202209/1078885-20220911143756063-804085608.png" alt="image-20220911143756288" loading="lazy"></p>
<h3 id="117-sbb指令">11.7 sbb指令</h3>
<p>sbb是带借位减法指令,它利用了C-Flag位上记录的借位值。</p>
<p>指令格式:</p>
<pre><code class="language-assembly">sbb 操作对象1, 操作对象2
</code></pre>
<p>功能:操作对象1=操作对象1-操作对象2-C-Flag</p>
<pre><code class="language-assembly">sbb ax, bx ; (ax)=(ax) - (bx) - C-Flag
</code></pre>
<p>sbb指令执行后,将对C-Flag位进行设置。利用sbb指令可以对任意大的数据进行减法运算。</p>
<p>实验:计算003E1000H-00202000H,结果放在ax,bx中。</p>
<pre><code class="language-assembly">mov bx, 1000H
mov ax, 003EH
sub bx, 2000H ; 借位值存放在C-Flag
sbb ax, 0020H
</code></pre>
<h3 id="118-cmp指令">11.8 cmp指令</h3>
<p>cmp是比较指令,功能相当于减法指令,只是不保存结果。</p>
<p>cmp指令执行后,将对标志寄存器产生影响。其它相关指令通过识别这些被影响的标志寄存器来得知比较结果。</p>
<p>指令格式:</p>
<pre><code class="language-assembly">cmp 操作对象1, 操作对象2
</code></pre>
<p>功能:计算操作对象1-操作对象2,但并不保存结果,仅仅根据计算结果对标志寄存器进行设置。</p>
<p>CPU在执行cmp指令时,包含两种含义:进行无符号数运算和进行有符号数运算。</p>
<p><strong>(1)进行无符号数的比较</strong></p>
<pre><code class="language-assembly">cmp ax, bx ; 逻辑含义是比较ax和bx中的值
</code></pre>
<table>
<thead>
<tr>
<th>比较关系</th>
<th>(ax) ? (bx)</th>
<th>(ax) - (bx)的特点</th>
<th>标志寄存器</th>
</tr>
</thead>
<tbody>
<tr>
<td>等于</td>
<td>(ax)=(bx)</td>
<td>(ax)-(bx)=0</td>
<td>Z-Flag=1</td>
</tr>
<tr>
<td>不等于</td>
<td>(ax)≠(bx)</td>
<td>(ax)-(bx)≠0</td>
<td>Z-Flag=0</td>
</tr>
<tr>
<td>小于</td>
<td>(ax)<(bx)</td>
<td>(ax)-(bx)将产生借位</td>
<td>C-Flag=1</td>
</tr>
<tr>
<td>大于等于</td>
<td>(ax)≥(bx)</td>
<td>(ax)-(bx)不必借位</td>
<td>C-Flag=0</td>
</tr>
<tr>
<td>大于</td>
<td>(ax)>(bx)</td>
<td>(ax)-(bx)既不借位,结果又不为0</td>
<td>C-Flag=0并且Z-Flag=0</td>
</tr>
<tr>
<td>小于等于</td>
<td>(ax)≤(bx)</td>
<td>(ax)-(bx)或者借位,或者结果为0</td>
<td>C-Flag=1或Z-Flag=1</td>
</tr>
</tbody>
</table>
<p><strong>(2)进行有符号数的比较</strong></p>
<p>问题:逻辑上真正的正负和实际结果的正负带来的问题。</p>
<pre><code class="language-assembly">mov ah, 08AH
mov bh, 070H
cmp ah, bh ; 结果S-Flag=0
</code></pre>
<p>运算(ah)-(bh)实际得到的结果是1AH,但在逻辑上,结果应该是:(-118)-112=-230。S-Flag不能说明在逻辑上运算所得到的结果。</p>
<pre><code class="language-assembly">cmp ah, bh
</code></pre>
<ul>
<li>如果S-Flag=1,而O-Flag=0。</li>
</ul>
<p>O-Flag=0,说明没有溢出,逻辑上真正的正负=实际结果的正负;</p>
<p>S-Flag=1,说明实际结果为负,所以逻辑上真正的结果为负,所以(ah)<(bh)。</p>
<ul>
<li>如果S-Flag=1,而O-Flag=1。</li>
</ul>
<p>O-Flag=1,说明有溢出,逻辑上真正的正负≠实际结果的正负;</p>
<p>S-Flag=1,说明实际结果为负。实际结果为负,而又有溢出。</p>
<p><strong>如果因为溢出导致了实际结果为负,那么逻辑上真正的结果必然为正。</strong>所以(ah)>(bh)。</p>
<ul>
<li>如果S-Flag=0,而O-Flag=1。</li>
</ul>
<p>O-Flag=1,说明有溢出,逻辑上真正的正负≠实际结果的正负;</p>
<p>S-Flag=1,说明实际结果为非负。而O-Flag=1说明有溢出,则结果非0,所以实际结果为正。</p>
<p><strong>如果因为溢出导致了实际结果为正,那么逻辑上真正的结果必然为负。</strong>所以(ah)<(bh)。</p>
<ul>
<li>如果S-Flag=0,而O-Flag=0。</li>
</ul>
<p>O-Flag=0,说明没有溢出,逻辑上真正的正负=实际结果的正负;</p>
<p>S-Flag=0,说明实际结果非负,所以逻辑上真正的结果为非负,所以(ah)≥(bh)。</p>
<table>
<thead>
<tr>
<th>比较关系</th>
<th>(ax) ? (bx)</th>
<th>(ax) - (bx)的特点</th>
<th>标志寄存器</th>
</tr>
</thead>
<tbody>
<tr>
<td>等于</td>
<td>(ax)=(bx)</td>
<td>(ax)-(bx)=0</td>
<td>Z-Flag=1</td>
</tr>
<tr>
<td>不等于</td>
<td>(ax)≠(bx)</td>
<td>(ax)-(bx)≠0</td>
<td>Z-Flag=0</td>
</tr>
<tr>
<td>小于</td>
<td>(ax)<(bx)</td>
<td>(ax)-(bx)为负,且不溢出</td>
<td>S-Flag=1且O-Flag=0</td>
</tr>
<tr>
<td>大于</td>
<td>(ax)>(bx)</td>
<td>(ax)-(bx)为负,且有溢出</td>
<td>S-Flag=1且O-Flag=1</td>
</tr>
<tr>
<td>大于等于</td>
<td>(ax)≥(bx)</td>
<td>(ax)-(bx)为非负,且无溢出</td>
<td>S-Flag=0且O-Flag=0</td>
</tr>
<tr>
<td>小于等于</td>
<td>(ax)≤(bx)</td>
<td>(ax)-(bx)为非负,且有溢出</td>
<td>S-Flag=0且O-Flag=1</td>
</tr>
</tbody>
</table>
<h3 id="119-检测比较结果的条件转移指令">11.9 检测比较结果的条件转移指令</h3>
<p>“转移”:指的是能够修改IP;</p>
<p>"条件":指的是可以根据某种条件,决定是否修改IP。</p>
<p>因为cmp指令可以同时进行两种比较,无符号数比较和有符号数比较,所以根据cmp指令的比较结果进行转移的指令也分为两种:根据无符号数的比较结果进行转移的条件转移指令(检测Z-Flag和C-Flag的值)和根据有符号数的比较结果进行转移的条件转移指令(检测S-Flag、O-Flag和Z-Flag)。</p>
<p>常用的根据无符号数的比较结果进行转移的条件转移指令:</p>
<table>
<thead>
<tr>
<th>指令</th>
<th>含义</th>
<th>检测的相关标志位</th>
</tr>
</thead>
<tbody>
<tr>
<td>je</td>
<td>等于则转移(equal)</td>
<td>Z-Flag=1</td>
</tr>
<tr>
<td>jne</td>
<td>不等于则转移(not equal)</td>
<td>Z-Flag=0</td>
</tr>
<tr>
<td>jb</td>
<td>低于则转移(below)</td>
<td>C-Flag=1</td>
</tr>
<tr>
<td>jnb</td>
<td>不低于则转移(not below)</td>
<td>C-Flag=0</td>
</tr>
<tr>
<td>ja</td>
<td>高于则转移(above)</td>
<td>C-Flag=0且Z-Flag=0</td>
</tr>
<tr>
<td>jna</td>
<td>不高于则转移(not above)</td>
<td>C-Flag=1或Z-Flag=1</td>
</tr>
</tbody>
</table>
<p>根据单个标志位转移的指令:</p>
<table>
<thead>
<tr>
<th>指令</th>
<th>含义</th>
<th>检测的相关标志位</th>
</tr>
</thead>
<tbody>
<tr>
<td>jz</td>
<td>结果为0(Zero)</td>
<td>Z-Flag=1</td>
</tr>
<tr>
<td>jnz</td>
<td>结果不为0(Not Zero)</td>
<td>Z-Flag=0</td>
</tr>
<tr>
<td>js</td>
<td>结果为负(Sign)</td>
<td>S-Flag=1</td>
</tr>
<tr>
<td>jns</td>
<td>结果非负(Not Sign)</td>
<td>S-Flag=0</td>
</tr>
<tr>
<td>jo</td>
<td>结果溢出(Overflow)</td>
<td>O-Flag=1</td>
</tr>
<tr>
<td>jno</td>
<td>结果非溢出(Not Overflow)</td>
<td>O-Flag=0</td>
</tr>
<tr>
<td>jp</td>
<td>奇偶位为1(Parity)</td>
<td>P-Flag=1</td>
</tr>
<tr>
<td>jnp</td>
<td>奇偶位不为1(Not Parity)</td>
<td>P-Flag=0</td>
</tr>
<tr>
<td>jc</td>
<td>有借位(Carry)</td>
<td>C-Flag=1</td>
</tr>
<tr>
<td>jnc</td>
<td>无借位(Not Carry)</td>
<td>C-Flag=0</td>
</tr>
</tbody>
</table>
<p>根据有符号数比较结果进行转移的指令:</p>
<table>
<thead>
<tr>
<th>指令</th>
<th>含义</th>
<th>测试条件</th>
</tr>
</thead>
<tbody>
<tr>
<td>jl/jnge</td>
<td>小于则转移(less)</td>
<td>S-Flag=1且O-Flag=0</td>
</tr>
<tr>
<td>jnl/jge</td>
<td>不小于则转移(greater equal)</td>
<td>S-Flag=0且O-Flag=0</td>
</tr>
<tr>
<td>jle/jnp</td>
<td>小于等于则转移(less equal)</td>
<td>S-Flag=0且O-Flag=1</td>
</tr>
<tr>
<td>jnle/jg</td>
<td>大于则转移(greater)</td>
<td>S-Flag=1且O-Flag=1</td>
</tr>
</tbody>
</table>
<p>实验:如果(ah)=(bh),则(ah)=(ah)+(ah),否则(ah)=(ah)+(bh)</p>
<pre><code class="language-assembly">; 双分支结构的实现
cmp ah, bh
je s
add ah, bh
jmp short ok
s:
add ah, ah
ok: ret
</code></pre>
<p>条件转移指令的使用:jxxx系列指令和cmp指令配置,构造条件转移指令:</p>
<ul>
<li>不必再考虑cmp指令对相关标志位的影响和jxxx指令对相关标志位的检测。</li>
<li>可以直接考虑cmp和jxxx指令配置使用时表现出来的逻辑含义。</li>
</ul>
<p>实验:统计data段中各种值(值为8,值大于8,值小于8)数据的个数。</p>
<pre><code class="language-assembly">assume cs:codesg
data segment
db 8, 11, 8, 1, 8, 5, 63, 38
data ends
codesg segment
mov ax, data
mov ds, ax
mov bx, 0
mov ax, 0
mov cx, 8
s: cmp byte ptr , 8
jne next ; 判断不等于8
inc ax ; 相等加1
next:
inc bx
loop s
mov ax, 4c00h
int 21h
codesg ends
end start
</code></pre>
<p>判断大于8的数:</p>
<pre><code class="language-assembly">s: cmp ptr , 8
jna next
inc ax
</code></pre>
<p>判断小于8的数的:</p>
<pre><code class="language-assembly">s: cmp ptr , 8
jnb next
inc ax
</code></pre>
<h3 id="1110-d-flagdirection-flag标志和串传送指令">11.10 D-Flag(Direction Flag)标志和串传送指令</h3>
<p>flag的第10位是D-Flag,方向标志位。在串处理指令中,控制每次操作后si、di的递减。</p>
<pre><code class="language-assembly">D-Flag=0 ; 每次操作后si、di递增
D-Flag=1 ; 每次操作后si、di递减
</code></pre>
<p>(1)串传送指令:</p>
<pre><code class="language-assembly">movsb
</code></pre>
<p>功能:执行movsb指令相当于进行下面几步操作:</p>
<ol>
<li>
<p>((es)*16+(di)*16)=((ds)*16+(si))</p>
</li>
<li>
<p>如果D-Flag=0,则(si)=(si)+1、(di)=(di)+1</p>
<p>如果D-Flag=1,则(si)=(si)-1、(di)=(di)-1</p>
</li>
</ol>
<p>也就是将ds:si执行的内存单元中的字节送入es:di中,然后根据标志寄存器D-Flag的值,将si和di递增或递减。</p>
<p>(2)串传送一个字的指令:</p>
<pre><code class="language-assembly">movsw
</code></pre>
<p>功能:将ds:si执行的内存单元中的字送入es:di中,然后根据标志寄存器D-Flag的值,将si和di递增2或递减2。</p>
<p>(3)rep指令(和movsb或movsw配合使用)</p>
<pre><code class="language-assembly">rep movsb
rep movsw
</code></pre>
<p>功能:根据cx的值,重复执行后面的串行传送指令,可以循环实现(cx)个字符(字节或字)的传送。</p>
<p>用汇编语法描述rep movsb的功能:</p>
<pre><code class="language-assembly">s: movsb
loop s
</code></pre>
<p>(4)对D-Flag位进行配置的指令</p>
<p>cld指令(clear):将标志寄存器的D-Flag位置为0</p>
<p>std指令(setup):将标志寄存器的D-Flag位置为1</p>
<p>实验:将data段中的第一个字符串复制到它后面的空间中。</p>
<pre><code class="language-assembly">data segment
db 'Welcome to masm!'
db 16 dup (0)
data ends
code segment
mov ax, data
mov ds, ax
mov si, 0 ; ds:si指向ds:0
mov es, ax
mov di, 16 ; es:di指向ds:10H
mov cx, 16 ; 循环16次
cld ; 设置D-Flag=0,正向传送
rep movsb
code ends
</code></pre>
<p>实验:将F000H段中的最后16个字符串复制到data段中。</p>
<pre><code class="language-assembly">data segment
db 16 dup (0)
data ends
code segment
mov ax, 0F000H
mov ds, ax
mov si, 0FFFFH ; ds:si指向F000:FFFF
mov ax, data
mov es, ax
mov di, 15 ; es:di指向data:000F
mov cx, 16
std ; 设置D-Flag=1,逆向传送
rep movsb
code ends
</code></pre>
<h3 id="1111-pushf和popf">11.11 pushf和popf</h3>
<p>pushf:将标志寄存器的值压栈。</p>
<p>popf:从栈中弹出数据,送入标志寄存器中。</p>
<p>pushf和popf为直接访问标志寄存器的一种方法。</p>
<p><img src="https://img2022.cnblogs.com/blog/1078885/202209/1078885-20220911163122898-877464156.png" alt="image-20220911163123366" loading="lazy"></p>
<h2 id="14-端口">14 端口</h2>
<h3 id="143-移位指令">14.3 移位指令</h3>
<h4 id="1431-shl和shr指令">14.3.1 shl和shr指令</h4>
<p>shl和shr是逻辑移位指令。</p>
<p>(1)shl是逻辑左移指令</p>
<pre><code class="language-assembly">shl opr cnt ; opr: 操作数 cnt:移位数
</code></pre>
<p>功能为:</p>
<ol>
<li>将一个寄存器或内存单元中的数据向左移位;</li>
<li>将最后移出的一位写入C-Flag中;</li>
<li>最低位用0补充。</li>
</ol>
<pre><code class="language-assembly">mov al, 01001000b
shl al, 1 ; 将al中的数据左移一位,结果(al)=10010000,C-Flag=0
shl al, 1 ; 将al中的数据左移一位,结果(al)=00100000,C-Flag=1
</code></pre>
<p>如果移动位数大于1时,必须将移动位数放在cl中:</p>
<pre><code class="language-assembly">mov al, 01010001b
mov cl, 3
shl al, cl ; 移位后,(al)=10001000b,C-Flag=0
</code></pre>
<p>(2)shr是逻辑右移指令,功能和shl相反:</p>
<ol>
<li>将一个寄存器或内存单元中的数据向右移位;</li>
<li>将最后移出的一位写入C-Flag中;</li>
<li>最高位用0补充。</li>
</ol>
<pre><code class="language-assembly">mov al, 10000001b
shr al, 1 ; 将al中的数据右移一位,结果(al)=01000000b,CF=1
shr al, 1 ; 将al中的数据右移一位,结果(al)=00100000b,CF=0
</code></pre>
<p>如果移动位数大于1时,必须将移动位数放在cl中:</p>
<pre><code class="language-assembly">mov al, 01010001b
mov cl, 3
shr al, cl ; (al)=00001010b,最后移出的是0,C-Flag=0
</code></pre>
<p><img src="https://img2022.cnblogs.com/blog/1078885/202209/1078885-20220911201115379-1896784740.png" alt="image-20220911201115939" loading="lazy"></p>
<h2 id="16-直接定址表">16 直接定址表</h2>
<h3 id="161-描述了单位长度的标号">16.1 描述了单位长度的标号</h3>
<p>之前在代码段中使用标号标记指令、数据、段的起始地方方式:</p>
<pre><code class="language-assembly">code segment
a: db 1, 2, 3, 4, 5, 6, 7, 8
b: dw 0
start:
... ...
code ends
</code></pre>
<p>code、a、b、start都是标号,仅仅表示了内存单元的地址。</p>
<p>使用标号表示内存单元的地址和内存单元的长度:</p>
<pre><code class="language-assembly">code segment
a db 1, 2, 3, 4, 5, 6, 7, 8
b dw 0
start:
... ...
code ends
</code></pre>
<p>标号a:描述了从地址code:0开始,以后的内存单元都是字节单元;</p>
<p>标号b:描述了从地址code:8开始,以后的内存单元都是字单元。</p>
<p>因为这种标号包含了对单元长度的描述,所以在指令中,可以代表一个段中的内存单元。</p>
<pre><code class="language-assembly">; b代表内存单元是字单元
mov ax, b ; 相当于mov ax, cs:
mov b, 2 ; 相当于mov word ptr cs:, 2
inc b ; 相当于inc word ptr cs:
</code></pre>
<p>将这种标号称为数据标号,它标记了存储数据的单元的地址和长度,不同于仅仅表示地址的地址标号。</p>
<h3 id="162-在其他段中使用数据标号">16.2 在其他段中使用数据标号</h3>
<p>一般不将数据定义在代码段,而定义到其它段中。在其它段中,也可以使用数据标号来描述存储数据的单元的地址和长度。</p>
<p>注:在后面加”:“的地址标号,只能在代码段中使用。</p>
<p>实验:将data段中a标号处的8个数据累加,结果存储到b标号处的字中。</p>
<pre><code class="language-assembly">8assume cs:code, ds:data
data segment
a db 1, 2, 3, 4, 5, 6, 7, 8 ; 地址:code:0,以后的内存单元都是字节
b dw 0 ; 地址:code:8,以后的内存单元都是字
data ends
code segment
mov ax, data
mov ds, ax ; ds指向data段
mov si, 0
mov cx, 8
s: mov al, a ; mov al, cs:0
mov ah, 0
add b, ax ; 编译为add , ax
inc si
loop s
mov ax, 4c00h
int 21h
code ends
end
</code></pre>
<p>可以将标号当作数据来定义,此时,编译器将标号所表示的地址当作数据的值:</p>
<pre><code class="language-assembly">data segment
a db 1, 2, 3, 4, 5, 6, 7, 8
b dw 0
c dw a, b ; 相当于:c dw offset a, offset b
data ends
data segment
a db 1, 2, 3, 4, 5, 6, 7, 8
b dw 0
c dd a, b ; 相当于:c dw offset a, seg a, offset b, seg b
data ends
</code></pre>
<p>seg操作符:取得某一个标号的段地址。</p>
<h3 id="162-显示的原理">16.2 显示的原理</h3>
<p>8086CPU显示的原理,由于后面会用到,这里提前进行说明。</p>
<p>原理:屏幕上的内容=显存中的数据</p>
<p><strong>8086CPU显存地址空间为:A0000H~BFFFFH,总共128KB。</strong>其中B8000H~BFFFFH共32KB的空间,是80*32(25行*80列)彩色字符模式第0页的显示缓冲区。</p>
<p><img src="https://img2022.cnblogs.com/blog/1078885/202209/1078885-20220912154227350-153991424.png" alt="页-2" loading="lazy"></p>
<h3 id="163-直接定址表">16.3 直接定址表</h3>
<p>实验:编写子程序,以十六进制的形式在屏幕中间显示给定的字节型数据(例如2BH)。</p>
<p>分析:一个字节需要两个十六进制数码来表示,子程序需要在屏幕上显示两个ASCLL字符。</p>
<ul>
<li>
<p>数值0~9和字符‘0’~‘9’之间的映射关系:数值+30H=对应字符的ASCLL值。</p>
</li>
<li>
<p>数值10~15和‘A’~'F'之间的映射关系:数值+37H=对应字符的ASCLL值。</p>
</li>
</ul>
<p>可以看出,数值0~15和字符'0'~'F'之间没有一致的映射关系存在,我们应该在它们之间建立新的映射关系。</p>
<p>具体做法:建立一张表,表中依次存储字符‘0’~‘F’,通过数值0~15直接查找到对应的字符。</p>
<pre><code class="language-assembly">assume cs:code
code segment
start:
mov al, 2bH
call showbyte
mov ax, 4c00h
int 21h
; 用al传送要显示的数据
showbyte:
jmp short show
table db '0123456789ABCDEF' ; 字符表
show:
push bx
push es
push cx
mov ah, al
mov cl, 4
shr ah, cl ; 右移4位,ah中得到高4位的值,(ah)=20h
and al, 00001111b ; al中为低4位的值,(al)=0bh
mov bl, ah
mov bh, 0 ; (bx)=2
mov ah, table ; 用高4位的值作为相对于table的偏移,取得对应的字符
mov bx, 0b800h ; 要改写的地址空间
mov es, bx
mov es:, ah ; es:='2'
mov bl, al
mov bh, 0 ; (bx)=b
mov al, table ; 用低4位的值作为相对于table的偏移,取得对应的字符
mov es:, al ; es:='B'
pop cx
pop es
pop bx
ret
code ends
end start
</code></pre>
<p>求解思路:利用表,在两个数据集合之间建立一种映射关系,用查表的方法根据给出的数据得到其在另一集合中的对应数据。</p>
<p>优点:算法清晰和简洁、加快运算速度、使程序易于扩充。</p>
<p>实验:编写一个子程序,计算sin(x),x∈P{0°, 30°, 60°, 90°, 120°, 150°, 180°},并在屏幕中间显示计算结果。</p>
<p>思路:将sin(x)的结果存储到一张表中,然后用角度值差表,找到对应的sin(x)的值。</p>
<pre><code class="language-assembly">assume cs:code
code segment
start:
mov al, 60
call showsin
mov ax, 4c00h
int 21h
; 用al传送要显示的数据
showsin:
jmp short show
; 字符串偏移地址表
table dw ag0 ag30 ag60 ag90 ag120 ag150 ag180 ; 字符串偏移地址表
ag0 db '0'0 ; sin(0)对应字符串
ag30db '0.5'0 ; sin(30)对应字符串
ag60db '0.866'0 ; sin(60)对应字符串
ag90db '1'0 ; sin(90)对应字符串
ag120 db '0.866'0 ; sin(120)对应字符串
ag150 db '0.5'0 ; sin(150)对应字符串
ag180 db '0'0 ; sin(180)对应字符串
show:
; 寄存器压栈
push bx
push es
push si
mov bx, 0b800h
mov es, bx
; 用角度值/30作为相对于table的偏移,取得对应的字符串的偏移地址,放在bx中
mov ah, 0
mov bl, 30
div bl ; 30/30=1->table, 60/30=2
mov bl, al
mov bh, 0
add bx, bx ; 表的索引为dw类型
mov bx, table
; 显示sin(x)对应的字符串
mov si, 160*12+40*2
shows:
mov ah, cs:
cmp ah, 0
je showret
mov es:, ah
inc bx
add si, 2
jmp short shows
showret:
pop si
pop es
pop bx
ret
code ends
end start
</code></pre>
<h3 id="164-程序入口地址的直接定址表">16.4 程序入口地址的直接定址表</h3>
<p>可以在直接定址表中存储子程序的地址,从而方便的实现不同子程序的调用。</p>
<p>实验:实现一个子程序setscreen,为显示输出提供如下功能:1)清屏;2)设置前景色;3)设置背景色;4)向上滚动一行。</p>
<p>解决方式:</p>
<ul>
<li>将4个功能写成4个子程序;</li>
<li>将这些功能子程序的入口地址存储在一个表中,它们在表中的位置和功能号相对应;对应关系为:</li>
<li>功能号*2=对应的功能子程序在地址表中的偏移。</li>
</ul>
<p>子程序入口参数说明:</p>
<ol>
<li>用ah寄存器传递功能号(0:表示清屏;1:表示设置前景色;2:表示设置背景色;3:表示向上滚动一行);</li>
<li>对2、3号功能,用al传递颜色值,al∈{0, 1, 2, 3, 4, 5, 6, 7}。</li>
</ol>
<pre><code class="language-assembly">; 主程序
assume cs:code
code segment
start:
; 主程序
; setscreen子程序
; 各功能实现
code ends
end start
</code></pre>
<pre><code class="language-assembly">; 实现一个子程序setscreen,为显示输出提供如下功能:
; 1)清屏;
; 2)设置前景色;
; 3)设置背景色;
; 4)向上滚动一行。
assume cs:code
code segment
start:
mov ah, 3 ; ah传递功能号
mov al, 3 ; al传送颜色值
call setscreen
mov ax, 4c00h
int 21h
setscreen:
jmp short set
table dw sub1, sub2, sub3, sub4 ; 字符串偏移地址表
set:
push bx
cmp ah, 3
ja sret ; 检查参数合法性
mov bl, ah
mov bh, 0
add bx, bx ; dw类型,占2个字节
call word ptr table ; 调用子函数
sret:
pop bx
ret
; 清屏,将显存中当前屏幕中的字符设置为空格符
sub1:
push bx
push cx
push es
mov bx, 0b800h
mov es, bx
mov bx, 0
mov cx, 2000 ; 屏幕共25行*80列
sub1s:
mov byte ptr es:, ' '
add bx, 2
loop sub1s
pop es
pop cx
pop bx
ret
; 设置前景色:设置显存中奇地址的属性字节的第0、1、2位
sub2:
push bx
push cx
push es
mov bx, 0b800h
mov es, bx
mov bx, 1
mov cx, 2000 ; 屏幕共25行*80列
sub2s:
and byte ptr es:, 11111000b
or es:, al ; 属性字节低3位是前景色
add bx, 2
loop sub2s
pop es
pop cx
pop bx
ret
; 设置前景色:设置显存中奇地址的属性字节的第4、5、6位
sub3:
push bx
push cx
push es
mov cl, 4
shl al, cl
mov bx, 0b800h
mov es, bx
mov bx, 1
mov cx, 2000 ; 屏幕共25行*80列
sub3s:
and byte ptr es:, 10001111b
or es:, al ; 属性字节低3位是前景色
add bx, 2
loop sub3s
pop es
pop cx
pop bx
ret
; 向上滚动一行:依次将第n+1行的内容复制到第n行处,最后一行为空
sub4:
push cx
push si
push di
push es
push ds
mov si, 0b800h
mov es, si
mov ds, si
mov si, 160 ; ds:si指向第n+1行
mov di, 0 ; es:di指向第n行
cld
mov cx, 24 ; 共复制24行
; 复制前24行
sub4s:
push cx
mov cx, 160
rep movsb ; 复制
pop cx
loop sub4s
; 清空最后一行
mov cx, 80
mov si, 0
sub4s1:
mov byte ptr es:, ' '
add si, 2
loop sub4s1
pop ds
pop es
pop di
pop si
pop cx
ret
code ends
end start
</code></pre><br><br>
来源:https://www.cnblogs.com/mrlayfolk/p/16686439.html
頁:
[1]