大众比阳光好 發表於 2022-9-12 16:07:00

《汇编语言》学习笔记-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)&lt;(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)&gt;(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)&lt;(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)&gt;(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)&lt;(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)&lt;(bx)</td>
<td>(ax)-(bx)为负,且不溢出</td>
<td>S-Flag=1且O-Flag=0</td>
</tr>
<tr>
<td>大于</td>
<td>(ax)&gt;(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-&gt;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]
查看完整版本: 《汇编语言》学习笔记-3