垦鸽鸽餐厅 發表於 2022-9-3 21:51:00

《汇编语言》学习笔记-1

<p>注:本文档为“《汇编语言(第3版) 》王爽著”阅读过程中记的笔记。</p>
<p>参考视频:通俗易懂的汇编语言(王爽老师的书)_哔哩哔哩_bilibili</p>
<h2 id="4-源程序到可执行程序过程">4 源程序到可执行程序过程</h2>
<p>一个汇编语言源程序编写到执行的过程:<img src="https://img2022.cnblogs.com/blog/1078885/202208/1078885-20220821165119745-401368203.png" alt="汇编编译执行" loading="lazy"></p>
<p>1)编写汇编源程序</p>
<p>2)先对源程序进行编译连接,编译产生目标文件;再用连接程序对目标文件进行连接,生成可在操作系统中直接运行的可执行程序。</p>
<p>可执行文件包括两个部分:</p>
<ul>
<li>程序(从源程序中的汇编指令翻译过来的机器码)和数据(源程序中定义的数据)</li>
<li>相关的描述信息(如程序有多大、占用多少内存空间)</li>
</ul>
<p>3)执行可程序文件。</p>
<p>CPU按照可执行文件中的描述信息,将可执行文件中的机器码和数据加载入内存,并进行相关的初始化(比如CS:IP执行第一条要执行的指令),然后由CPU执行程序。</p>
<p><img src="https://img2022.cnblogs.com/blog/1078885/202208/1078885-20220822224310595-1321443326.png" alt="页-10" loading="lazy"></p>
<h3 id="41-源程序">4.1 源程序</h3>
<p>一个简单的源程序:</p>
<pre><code class="language-assembly">assume cs:codesg        ; cs和codesg关联

codesg segment                ; 定义一个段codesg开始

    mov ax,0123H        ; 由CPU执行的汇编指令
    mov bx,0456H
    add ax,bx
    add ax,ax

    mov ax,4c00H        ; 程序返回
    int 21H

codesg ends                        ; 定义一个段codesg结束

end        ; 汇编程序结束标记
</code></pre>
<p>(1)<strong>伪指令:</strong>汇编语言中,包括两种指令,一种是汇编指令,一种是伪指令。汇编指令有对应的机器码的指令,可以被编译为机器指令,最终由CPU执行。伪指令没有对应的机器指令,由编译器执行,编译器根据伪指令来进行相关的编译工作。</p>
<pre><code class="language-assembly">段名 segment
        ...
段名 ends
</code></pre>
<p>segment和ends是一个成对使用的伪指令,segment和ends定义一个段,segment说明一个段开始;ends说明一个段结束。一个段必须有一个名称来标识。</p>
<p><strong>段:</strong>一个汇编程序由多个段组成,这些段用来存放代码、数据或当作栈空间来使用。指令、数据、栈,被划分到不同的段中。一个汇编程序必须至少有一个段,用来存放代码。</p>
<p>(2)end</p>
<p>end是一个汇编程序的结束标记,编译器碰到伪指令end,就结束对源程序的编译。</p>
<p>(3)assume</p>
<p>assume将有特定用途的段和相关的段寄存器关联起来。</p>
<p>(4)程序返回</p>
<p>一个程序结束后,将CPU的控制权交还给使它得以运行的程序,这个过程称为程序返回。</p>
<pre><code class="language-assembly">mov ax,4c00H        ; 程序返回
int 21H
</code></pre>
<h3 id="42-编译">4.2 编译</h3>
<p>使用masm.exe程序进行编译,有以下几种方式:</p>
<pre><code class="language-assembly">masm 文件名
然后输入文件名
</code></pre>
<p><img src="https://img2022.cnblogs.com/blog/1078885/202208/1078885-20220822222326067-2016556333.png" alt="image-20220822222325746" loading="lazy"></p>
<pre><code class="language-assembly">masm 文件名;
</code></pre>
<p><img src="https://img2022.cnblogs.com/blog/1078885/202208/1078885-20220822221932653-35707964.png" alt="image-20220822221932192" loading="lazy"></p>
<h3 id="43-连接">4.3 连接</h3>
<p>对源程序进行编译得到目标文件后,需要对目标文件进行连接,得到可执行文件。</p>
<p>连接使用的是LINK.exe程序,使用方式:</p>
<pre><code class="language-assembly">link 文件名
</code></pre>
<p><img src="https://img2022.cnblogs.com/blog/1078885/202208/1078885-20220822222605778-768555861.png" alt="image-20220822222605310" loading="lazy"></p>
<pre><code class="language-assembly">link 文件名;
</code></pre>
<p><img src="https://img2022.cnblogs.com/blog/1078885/202208/1078885-20220822222706501-156514306.png" alt="image-20220822222646029" loading="lazy"></p>
<p>连接的几个作用:</p>
<p>1)当源程序很大时,可以将它分为多个源程序文件来编译,每个源程序编译成为多个目标文件后,再用连接程序将它们连接到一起,生成一个可执行执行。</p>
<p>2)程序中调用了某个库文件中的子程序,需要将这个库文件和该程序生成的目标连接到一起,生成一个可执行文件。</p>
<p>3)一个源程序编译后,得到了存有机器码的目标文件,目标文件中的有些内容还不能直接用来生成可执行文件,连接程序将这些内容处理为最终的可执行信息。所以,在只有一个源程序文件,又不需要调用某个库中的子程序时,也必须用连接程序对目标文件进行处理,生成可执行程序。</p>
<h3 id="44-执行">4.4 执行</h3>
<p>直接执行即可:</p>
<p><img src="https://img2022.cnblogs.com/blog/1078885/202208/1078885-20220822223319895-809336144.png" alt="image-20220822223319596" loading="lazy"></p>
<p>当前执行之后没有任何输出,所以没有什么提示。</p>
<p>DOS中的程序command.com是命令解释器,也就是DOS系统的shell。</p>
<p>如果用户要执行一个程序,输入可执行文件的名称,command会根据名称找到可执行文件,然后将这个可执行文件中的程序载入内存,设置CS:IP指向程序的入口。然后command暂停运行,CPU运行程序,程序返回后,返回到command中。</p>
<h3 id="45-debug调试">4.5 debug调试</h3>
<p>可以使用debug.exe来跟踪程序的执行步骤:</p>
<p>1)debug.exe加载调试程序进入内存,进行相关的初始化后设置CS:IP指向程序的入口;CX寄存器为程序的长度。</p>
<p><img src="https://img2022.cnblogs.com/blog/1078885/202208/1078885-20220822224853238-112282084.png" alt="image-20220822224832831" loading="lazy"></p>
<p><img src="https://img2022.cnblogs.com/blog/1078885/202208/1078885-20220822225601414-1060562936.png" alt="image-20220822225541057" loading="lazy"></p>
<p>2)DOS系统中EXE文件中的程序的加载过程:</p>
<p><img src="https://img2022.cnblogs.com/blog/1078885/202208/1078885-20220822225216927-1658993646.png" alt="image-20220822225216214" loading="lazy"></p>
<p>程序加载后,DS寄存器中存放着程序所在内存区的段地址,这个内存区的偏移地址为0,则<strong>程序所在的内存区地址为DS:0</strong>。</p>
<p>内存区的前256个字节中存放的是PSP,DOS用来和程序进行通信,从256字节处向后的空间存放的是程序。</p>
<p>3)使用t命令单步执行程序中的每一条指令,并观察每条指令的执行结果。</p>
<p>执行到了int 21,使用p命令执行。</p>
<p><img src="https://img2022.cnblogs.com/blog/1078885/202208/1078885-20220822225854949-1376058788.png" alt="image-20220822225852997" loading="lazy"></p>
<p><img src="https://img2022.cnblogs.com/blog/1078885/202208/1078885-20220822225937563-1142266204.png" alt="image-20220822225917253" loading="lazy"></p>
<h2 id="5-bx和loop指令">5 和loop指令</h2>
<pre><code class="language-assembly"># 将一个内存单元的内容送入ax,长度为2字节(字单元),存放一个字,偏移地址在bx中,段地址在ds中。
mov ax,
</code></pre>
<pre><code class="language-assembly"># 将一个内存单元的内容送入al中,内存单元的长度为1字节,存放一个字节,偏移地址在bx中,段地址在ds中。
mov al,
</code></pre>
<h3 id="51-和的规定">5.1 [...]和(...)的规定</h3>
<p>[...]:<strong>汇编语法规定</strong>,表示一个内存单元</p>
<table>
<thead>
<tr>
<th>指令</th>
<th>段地址</th>
<th>偏移地址</th>
<th>操作单位</th>
</tr>
</thead>
<tbody>
<tr>
<td>mov ax, </td>
<td>在DS中</td>
<td>在中</td>
<td>字</td>
</tr>
<tr>
<td>mov al, </td>
<td>在DS中</td>
<td>在中</td>
<td>字节</td>
</tr>
<tr>
<td>mov ax, </td>
<td>在DS中</td>
<td>在中</td>
<td>字</td>
</tr>
<tr>
<td>mov al, </td>
<td>在DS中</td>
<td>在中</td>
<td>字节</td>
</tr>
</tbody>
</table>
<p>(...):<strong>为学习方便而做出的约定</strong>,表示一个内存单元或寄存器中的内容。</p>
<p>“(...)”中的元素可以是三种类型:寄存器名、段寄存器名、内存单元的物理地址(20位)。</p>
<p>"(X)"所表示的数据有两种类型:字节、字。是哪种类型由寄存器名或具体的运算决定。</p>
<table>
<thead>
<tr>
<th>描述对象</th>
<th>描述方法</th>
<th>描述对象</th>
<th>描述方法</th>
</tr>
</thead>
<tbody>
<tr>
<td>ax中的内容为0010H</td>
<td>(ax)=0010H</td>
<td>2000:1000处的内容为0010H</td>
<td>(21000H)=0010H</td>
</tr>
<tr>
<td>mov ax, 的功能</td>
<td>(ax)=((ds)*16+2)</td>
<td>mov , ax的功能</td>
<td>((ds)*16+2)=(ax)</td>
</tr>
<tr>
<td>add ax, 2的功能</td>
<td>(ax)=(ax)+2</td>
<td>add ax,bx的功能</td>
<td>(ax)=(ax)+(bx)</td>
</tr>
<tr>
<td>push ax的功能</td>
<td>(sp)=(sp)-2<br>((ss)*16+(sp))=(ax)</td>
<td>pop ax的功能</td>
<td>(ax)=((ss)*16+(sp))<br>(sp)=(sp)+2</td>
</tr>
</tbody>
</table>
<h3 id="52-符号idata表示常量">5.2 符号idata表示常量</h3>
<p>为了方便,约定idata表示常量。</p>
<pre><code class="language-assembly">mov ax,         ; 代表mov ax, 、mov ax, ...
mov bx, idata
mov ds, idata
</code></pre>
<h3 id="53-loop指令">5.3 loop指令</h3>
<p>loop指令的格式:</p>
<pre><code class="language-assembly">loop 标号
</code></pre>
<p>CPU执行loop指令时,需要进行两步:①(cx)=(cx)-1;②判断cx中的值,不为0则跳转到标号处执行程序,如果为0则向下执行。</p>
<p>比如,设(ax)=2,N*2可用N+N实现,计算2^12如下:</p>
<pre><code class="language-assembly">assume cs:code

code segment
    mov ax, 2

    mov cx, 11
s:add ax, ax        ; 标号代表一个地址,这个地址处有一条指令:add ax, ax
    loop s                ; (cx)=(cx)-1,判断cx中的值,不为0则跳转到s继续执行

    mov ax, 4c00H        ; (cx)为0,则执行下一条语句
    int 21H

code ends

end
</code></pre>
<p>debug调试时,可以使用p命令一次将循环执行完毕,直到(cx)=0为止:</p>
<p><img src="https://img2022.cnblogs.com/blog/1078885/202208/1078885-20220823213837738-12812303.png" alt="image-20220823213836157" loading="lazy"></p>
<p>也可以使用g命令,执行到某个地址处:</p>
<p><img src="https://img2022.cnblogs.com/blog/1078885/202208/1078885-20220823214011445-987705873.png" alt="image-20220823213950033" loading="lazy"></p>
<h3 id="54-debug和汇编编译器masm对指令的不同处理">5.4 debug和汇编编译器masm对指令的不同处理</h3>
<p>目的:将内存2000:0、2000:1、2000:2、2000:3单元中的数据送入al、bl、cl、dl中。</p>
<p>(1)debug.exe中编程实现</p>
<pre><code class="language-assembly">mov ax, 2000
mov ds, ax
mov al,
mov bl,
mov cl,
mov dl,
</code></pre>
<p>(2)汇编程序实现</p>
<p>错误的方式:</p>
<pre><code class="language-assembly">assume cs:code
code segment

mov ax, 2000H
mov ds, ax
mov al, ; 实际当作了mov al, 0处理
mov bl, ; 实际当作了mov bl, 1处理
mov cl, ; 实际当作了mov cl, 2处理
mov dl, ; 实际当作了mov dl, 3处理

code ends
end
</code></pre>
<p>可看出,处理方式是不一样的,汇编程序可选择的方式1:</p>
<pre><code class="language-assembly">mov ax, 2000H
mov ds, ax        ; 段地址2000H送入ds
mov bx, 0        ; 偏移地址0送入bx
mov al, ; ds:bx单元中的数据送入al
</code></pre>
<p>可选择的方式2:</p>
<pre><code class="language-assembly">assume cs:code
code segment

mov ax, 2000H        
mov ds, ax                ; 段地址2000H送入ds
mov al, ds:         ; ds:0单元中的数据送入al
mov bl, ds:         ; ds:1单元中的数据送入bl
mov cl, ds:         ; ds:2单元中的数据送入cl
mov dl, ds:         ; ds:3单元中的数据送入dl

code ends
end
</code></pre>
<p><strong>总结:</strong>(1)在汇编程序中,如果用指令访问一个内存单元,则在指令中必须使用"[...]"来表示内存单元,但如果在“[]"里用一个常量idata直接给出内存单元的偏移地址,则需要在”[]“的前面显示地给出段地址所在的段寄存器,比如:</p>
<pre><code class="language-assembly">mov al, ds:
</code></pre>
<p>(2)如果在"[]"里用寄存器,比如bx,间接给出内存单元的偏移地址,则段地址默认在ds中。</p>
<h3 id="55-loop和bx的组合使用">5.5 loop和的组合使用</h3>
<p>实验:计算ffff:0~ffff:b单元中的数据的和,结果存储在dx中。</p>
<p>实现方式:将内存单元中的8位数据赋值到一个16位寄存器ax中,再将ax中的数据加到dx上,达到两个运算对象的类型匹配并且结果不会超界。</p>
<pre><code class="language-assembly">assume cs:code

code segment

        mov ax, 0ffffH        ; 汇编语言写法,必须以0开头
        mov ds, ax        ; 设置(ds)=ffffH
        mov bx, 0        ; 初始化ds:bx指向ffff:0
       
        mov dx, 0        ; 初始化累加寄存器dx, (dx)=0
       
        mov cx, 12        ; 初始化循环计数寄存器cx, (cx)=12
       
s:         mov al,
        mov ah, 0
        add dx, ax        ; 间接向dx中加上((ds)*16+(bx))单元的数值
        inc bx                ; ds:bx指向下一个单元
        loop s
       
    mov ax, 4c00H        ; (cx)为0,则执行下一条语句
    int 21H

code ends

end
</code></pre>
<h3 id="56-段前缀">5.6 段前缀</h3>
<p>访问内存单元时,可以显示的给出内存单元的段地址所在的段寄存器。</p>
<pre><code class="language-assembly">; 将一个内存单元的内容送入ax,这个内存单元的长度为2字节,存放一个字,偏移地址在bx中,段地址在ds中
mov ax, ds:       
</code></pre>
<pre><code class="language-assembly">; 将一个内存单元的内容送入ax,这个内存单元的长度为2字节,存放一个字,偏移地址在bx中,段地址在cs中
mov ax, cs:       
</code></pre>
<pre><code class="language-assembly">; 将一个内存单元的内容送入ax,这个内存单元的长度为2字节,存放一个字,偏移地址在bx中,段地址在ss中
mov ax, ss:       
</code></pre>
<pre><code class="language-assembly">; 将一个内存单元的内容送入ax,这个内存单元的长度为2字节,存放一个字,偏移地址在bx中,段地址在es中
mov ax, es:       
</code></pre>
<pre><code class="language-assembly">; 将一个内存单元的内容送入ax,这个内存单元的长度为2字节,存放一个字,偏移地址为0,段地址在ss中
mov ax, ss:
</code></pre>
<pre><code class="language-assembly">; 将一个内存单元的内容送入ax,这个内存单元的长度为2字节,存放一个字,偏移地址为0,段地址在cs中
mov ax, cs:
</code></pre>
<p>这些用在访问内存单元的指令中,用于显示地指明内存单元的段地址的”ds:“,”cs:“,”ss:“,”es:“,在汇编语言中称为段前缀。</p>
<p>使用段前缀完成:将内存ffff:0<sub>ffff:b单元中的数据复制到0:200</sub>0:20b单元中。</p>
<pre><code class="language-assembly">assume cs:code

code segment

        mov ax, 0ffffH
        mov ds, ax        ; 设置(ds)=ffffH

        mov ax, 0020H
        mov es, ax        ; (es)=0020H
       
        mov bx, 0        ; (bx)=0,ds:bx指向ffff:0,es:bx指向0020:0
       
        mov cx, 12        ; (cx)=12,循环12次
       
s:         mov dl,         ; (dl)=((ds)*16+(bx)),将ffff:bx中的数据送入dl
        mov es:, dl        ; ((es)*16+(bx))=(dl),将dl中的数据送入0020:bx
        inc bx                        ; (bx)=(bx)+1
        loop s
       
    mov ax, 4c00H        ; (cx)为0,则执行下一条语句
    int 21H

code ends

end
</code></pre>
<h3 id="实验">实验</h3>
<p>向内存0:200<sub>0:23F依次写入数据0</sub>63(3FH)</p>
<pre><code class="language-assembly">assume cs:code

code segment
       
        mov ax, 0020H
        mov ds, ax; 段地址
    mov bx, 0   ; 初始值
        mov cx, 64        ; 循环64次
   
s:        mov ds:, bx
    inc bx
        loop s
       
    mov ax, 4c00H
    int 21H

code ends

end
</code></pre>
<h2 id="6-包含多个段的程序">6 包含多个段的程序</h2>
<p>程序需要存放数据,需要取得内存空间。</p>
<p>获取空间的方法有两种,一是加载程序的时候为程序分配;二是程序执行过程中向系统申请。本文主要讨论第一种。</p>
<p>如果需要在程序被加载的时候取得所需的空间,则必须在源程序中做出说明,通过定义段来进行内存空间的获取。</p>
<p>内存地址空间的分配一般是系统来分配的,在源程序中定义需要处理的数据,这些数据就会被编译、连接程序作为程序的一部分写到可执行文件中,当可执行文件中的程序被载入内存时,这些数据也同时被加载入内存,需要处理的数据自然就获得了存储空间。</p>
<h3 id="61-在代码段中使用数据">6.1 在代码段中使用数据</h3>
<p>实验:计算下面8个数据的和,结果存放在ax寄存器中。</p>
<pre><code class="language-txt">0123H、0456H、0789H、0abcH、0defH、0fedH、0cbaH、0987H
</code></pre>
<p>希望通过循环的方式来进行累加,在累加前,要将这些数据存储在一组地址连续的内存单元中。</p>
<pre><code class="language-assembly">assume cs:code

code segment
       
        ; 定义字型数据“define word”,这些数据是在代码段中,在代码段的开头
        dw 0123H, 0456H, 0789H, 0abcH, 0defH, 0fedH, 0cbaH, 0987H
   
    mov bx, 0
    mov ax, 0
    mov cx, 8
s:        add ax, cs: ; 数据在代码段中,偏移地址起始为0
    add bx, 2
        loop s
       
    mov ax, 4c00H
    int 21H

code ends

end
</code></pre>
<p>debug运行程序,可以看到数据存放在代码段的前16个字节:</p>
<p><img src="https://img2022.cnblogs.com/blog/1078885/202208/1078885-20220824212519959-685850834.png" alt="image-20220824212518416" loading="lazy"></p>
<p><img src="https://img2022.cnblogs.com/blog/1078885/202208/1078885-20220824212635379-1718173236.png" alt="image-20220824212634221" loading="lazy"></p>
<p>运行指令方式,<strong>需要将IP指向0010偏移地址处</strong>。</p>
<p><img src="https://img2022.cnblogs.com/blog/1078885/202208/1078885-20220824212836510-1937467692.png" alt="image-20220824212835273" loading="lazy"></p>
<p>这样比较麻烦,可以在程序中指明程序的入口位置:</p>
<pre><code class="language-assembly">assume cs:code

code segment
       
        ; 定义字型数据“define word”,这些数据是在代码段中,在代码段的开头
        dw 0123H, 0456H, 0789H, 0abcH, 0defH, 0fedH, 0cbaH, 0987H

start:
    mov bx, 0
    mov ax, 0
    mov cx, 8
s:        add ax, cs: ; 数据在代码段中,偏移地址起始为0
    add bx, 2
        loop s
       
    mov ax, 4c00H
    int 21H

code ends

end start ; 通知编译器程序的入口在start
</code></pre>
<p>通过end指明程序指令的入口,可以得到程序的框架为:</p>
<pre><code class="language-assembly">assume cs:code

code segment
        ;
        ; 数据
        ;
start:
        ;
        ; 代码
        ;

code ends

end start ; 通知编译器程序的入口在start
</code></pre>
<h3 id="62-在代码段中使用栈">6.2 在代码段中使用栈</h3>
<p>实验:利用栈,将程序中定义的数据逆序存放。</p>
<pre><code class="language-assembly">0123H、0456H、0789H、0abcH、0defH、0fedH、0cbaH、0987H
</code></pre>
<p>思路:程序运行时,定义的数据存放到CS:0~CS:F中,依次将这8个字节单元中的数据入栈,然后再依次出栈到这8个字单元中,从而实现数据的逆序存放。</p>
<p>问题:首先需要一段可作为栈的空间,这段空间可以由系统分配,可以定义在程序中通过定义数据来取得一段空间,将这段空间当作栈空间来用。</p>
<pre><code class="language-assembly">assume cs:codesg
code segment
       
        ; 定义字型数据“define word”,这些数据是在代码段中,在代码段的开头
        ; 这16个字型数据,在程序加载时将获取16个字的内存空间,存放16个数据,有多余的空间
        ; 在后面的程序中将这段空间当作栈来使用
        dw 0123H, 0456H, 0789H, 0abcH, 0defH, 0fedH, 0cbaH, 0987H
        dw 0, 0, 0, 0, 0, 0, 0, 0
       
start:
        mov ax, cs
        mov ss, ax
        mov sp 30H        ; 设置栈顶ss:sp指向cs:30,将CS:10~CS:2F当作栈使用
       
        mov bx, 0
        mov cx, 8
s:        push cs:; 压栈,从CS:30开始
        add bx, 2
        loop s                ; 将代码段0~15单元中的8个字型数据依次入栈
       
        mov bx, 0
        mov cx, 8
s0:        pop cs:        ; 出栈,放到CS:0开始
        add bx, 2
        loop s0                ; 依次出栈8个字型数据到代码段0~15单元中
       
        mov ax, 4c00H
        int 21H

codeseg ends
end start                ; 指明程序的入口在start
</code></pre>
<p><img src="https://img2022.cnblogs.com/blog/1078885/202209/1078885-20220901203928933-85349257.png" alt="image-20220901203929420" loading="lazy"></p>
<h3 id="63-将数据代码栈放入不同的段">6.3 将数据、代码、栈放入不同的段</h3>
<p>前面编程时,我们需要注意用到数据和栈,将数据和栈放到一个段里面。编程的时候,需要注意何处是数据,何处是栈,何处是代码。这样存在的问题:</p>
<p>(1)把它们放到一个段中使程序显得混乱;</p>
<p>(2)如果数据、栈和代码需要的空间超过64KB,就不能放在一个段中。</p>
<p>需要考虑如何存放数据、代码和栈?</p>
<p>可以定义需要的数据,或通过定义数据来取得栈空间。</p>
<pre><code class="language-assembly">assume cs:code, ds:data, ss:stack
data segment
        dw 0123H, 0456H, 0789H, 0abcH, 0defH, 0fedH, 0cbaH, 0987H
data ends

stack segment
        dw 0, 0, 0, 0, 0, 0, 0, 0
stack ends

code segment
start:
        ; 初始化栈段寄存器
        mov ax, stack        ; 将名称为stack的段的段地址送入ax
        mov ss, ax
        mov sp, 20H        ; 设置栈顶ss:sp指向stack:20
       
        ; 初始化数据段寄存器
        mov ax, data
        mov ds, ax        ; ds指向data段
       
        ; 入栈
        mov bx, 0        ; ds:bx指向data段中的第一个单元
        mov cs, 8
s:        push
        add bx, 2
        loop s                ; 将data段中的0~15单元中的8字型数据依次入栈
       
        ; 出栈
        mov bx, 0
        mov cx, 8
s0:        pop
        add bx, 2
        loop s0                ; 依次出栈8个字型数据到data段的0~15单元中
       
        mov ax, 4c00H
        int 21h

code ends
end start
</code></pre>
<p>代码段、数据段、栈段完全是用户的安排。如何让CPU执行段呢?</p>
<p>(1)源程序中为3个段起名,比如程序中的data、code、stack;</p>
<p>(2)使用伪指令”assume cs:code, ds:data, ss:stack“将cs、ds、ss分别和code、data、stack段相连。</p>
<p>(3)使用段的方式,比如:</p>
<pre><code class="language-assembly">mov ax, stack        ; 将名称为stack的段的段地址送入ax
mov ss, ax
mov sp, 20H                ; 设置栈顶ss:sp指向stack:2
</code></pre>
<h2 id="7-灵活的定位内存">7 灵活的定位内存</h2>
<p>前面访问内存的方式主要有:、。本章讲解一些其它方式。</p>
<h3 id="71-and和or指令">7.1 and和or指令</h3>
<p>(1)and指令:逻辑与指令,按位进行与运算。</p>
<pre><code class="language-assembly">mov al, 01100011B
and al, 00111011B
; 执行后得到al=00100011B
</code></pre>
<p>通过and指令可将操作对象的相应位设为0,其它位不变。</p>
<p>(2)or指令:逻辑或指令,按位进行或运算。</p>
<pre><code class="language-assembly">mov al, 01100011B
oral, 00111011B
; 执行后得到al=01111011B
</code></pre>
<p>通过or指令可将操作对象的相应位设位1,其它位不变。</p>
<h3 id="72-以字符形式给出的数据">7.2 以字符形式给出的数据</h3>
<p>在汇编程序中,以“......”的方式指明数据是以字符的形式给出的,编译器将把它们转换为对应的ASCLL码。</p>
<pre><code class="language-assembly">assume cs:code, ds:data
data segment
        db 'unIX'        ; 相当于'db 75H, 6EH, 49H, 58H'
        db 'foRK'        ; 相当于'db 66H, 6FH, 52H, 48H'
data ends

code segment
       
start:
        mov al, 'a'        ; 相当于'mov al, 61H'
        mov bl, 'b'        ; 相当于'mov al, 62H'
       
        mov ax, 4c00H
        int 21h
code ends
end start
</code></pre>
<h3 id="73-大小写转换问题">7.3 大小写转换问题</h3>
<p>实验:将datasg中的第一个字符串转换为大写,第二个字符串转换为小写。</p>
<pre><code class="language-assembly">assume cs:codesg, ds:datasg

datasg segment
        db 'BaSic'
        db 'iNfOrMaTiOn'
datasg ends

codesg segment
        start:
codesg ends

end start
</code></pre>
<p>思路:大写字母的ASCLL码值比小写字母的ASCLL码值小20H。</p>
<p><img src="https://img2022.cnblogs.com/blog/1078885/202209/1078885-20220901212027369-1462377566.png" alt="image-20220901212028411" loading="lazy"></p>
<p>大写字母的ASCLL码加20H,就转换为了小写,如果已经是小写,就不需要转换;</p>
<p>小写字母的ASCLL码减20H,就转换为了大写,如果已经是大写,就不需要转换。</p>
<pre><code class="language-assembly">assume cs:codesg, ds:datasg

datasg segment
        db 'BaSic'
        db 'iNfOrMaTiOn'
datasg ends

codesg segment
start:
        mov ax, datasg
    mov bx, 0
    mov cx, 5
s:        mov al,
        ; 如果(al)&gt;61H,则为小写字母的ASCLL码,则: sub al, 20H
        mov , al
        inc bx
        loop s

codesg ends

end start
</code></pre>
<p>问题:如何进行值的判断?</p>
<p>解决方式:大写字母的ASCLL码的第5位为0,小写字母的ASCLL码的第5位为1,转换为小写字母的方式为直接将第5位置为1,转换为大写字母的方式为直接将第5位置为0。</p>
<pre><code class="language-assembly">assume cs:codesg, ds:datasg

datasg segment
        db 'BaSic'
        db 'iNfOrMaTiOn'
datasg ends

codesg segment
start:
        mov ax, datasg
        mov ds, ax
    mov bx, 0
    mov cx, 5
s:        mov al,
        and al, 11011111B        ; 将ASCLL码的第5位置为0,变为大写字母
        mov , al
        inc bx
        loop s
       
        mov bx, 5
        mov cx, 11
s0:        mov al,
        or al, 00100000B        ; 将ASCLL码的第5位置为1,变为小写字母
        mov , al
        inc bx
        loop s0
       
        mov ax, 4c00h
        int 21h
       
codesg ends
end start
</code></pre>
<h3 id="74-bxidata">7.4 </h3>
<p>除了可以用来指明一个内存单元,还可以用一种更灵活的方式来指明内存:</p>
<p>表示一个内存单元,它的偏移地址为(bx)+idata(bx的数值加上idata)。</p>
<pre><code class="language-assembly">mov ax,         ; (ax)=((ds)*16+(bx)+200)
; 也可以写成
mov ax,
mov ax, 200
mov ax, .200
</code></pre>
<h3 id="75-用bxidata的方式进行数组的处理">7.5 用的方式进行数组的处理</h3>
<p>实验:将datasg中的第一个字符串转换为大写,第二个字符串转换为小写。</p>
<p>思路:将两个字符串看作两个数组,一个从0地址开始存放,另一个从5开始存放。可以使用和的方式在同一个循环中定位这两个字符串中的字符。</p>
<pre><code class="language-assembly">assume cs:codesg, ds:datasg

datasg segment
        db 'BaSic'               ; 起始地址为0
        db 'iNfOrMaTiOn' ; 起始地址为5
datasg ends

codesg segment
start:
        mov ax, datasg
        mov ds, ax
        mov bx, 0
        mov cx, 5
s:        mov al,         ; 或者写为'mov al, 0'
        and al, 11011111b
        mov , al        ; 或者写为'mov 0, al'
        mov al,         ; 或者写为'mov al, 5'
        or al, 00100000b
        mov , al        ; 或者写为'mov 5, al'
        inc bx
        loop s
       
codesg ends
end start
</code></pre>
<h3 id="76-变址寄存器si和di">7.6 变址寄存器SI和DI</h3>
<ul>
<li>si:source index,源变址寄存器;</li>
<li>di:destination index,目标变址寄存器。</li>
</ul>
<p>si和di是8086CPU中和bx功能相近的寄存器。si和di不能分为两个8位寄存器使用。</p>
<pre><code class="language-assembly">mov bx, 0
mov ax,

; 等同于
mov si, 0
mov ax,

mov di, 0
mov ax,
</code></pre>
<p>实验:用si和di实现将字符串'welcome to masm!'复制到它后面的数据区中。</p>
<pre><code class="language-assembly">codesg segment

datasg segment
        db 'welcome to masm!'        ; 起始地址;datasg
        db '................'        ; 起始地址;datasg + 16
datasg ends

start:
        mov ax, datasg
        mov ds, ax
        mov si, 0
        mov di, 16
       
        mov cx, 8
s:        mov ax,
        mov , ax
        add si, 2
        add di, 2
        loop s
       
        mov ax, 4c00h
        int 21h

codesg ends
end start
</code></pre>
<h3 id="77-bxsi和bxdi">7.7 和</h3>
<p>和的含义类似。</p>
<p>:表示一个内存单元,它的偏移地址是(bx)+(si)(即bx中的数值加上si中的数值),称为基址+变址寻址方式。</p>
<pre><code class="language-assembly">; 将一个内存单元的内容送入ax,这个内存单元的长度为2字节(字单元),存放一个字,偏移地址为bx中的数值加上si中的数值,段地址在ds中
mov ax,         ; (ax)=((ds)*16+(bx)+(si))

; 也可以写成
mov ax,
</code></pre>
<h3 id="78-bxsiidata和bxdiidata">7.8 和</h3>
<p>和类似,以为例进行讲解。</p>
<p>:表示一个内存单元,它的偏移地址为(bx)+(si)+idata</p>
<p><img src="https://img2022.cnblogs.com/blog/1078885/202209/1078885-20220903115206264-347622168.png" alt="image-20220903115205843" loading="lazy"></p>
<pre><code class="language-assembly">; 将一个内存单元的内容送入ax,这个内存单元的长度为2字节(字单元),存放一个字,偏移地址为bx中的数值加上si中的数值再加上idata,段地址在ds中。
mov ax,         ; (ax)=((ds)*16+(bx)+(si)+idata)

; 等同于下面的格式
mov ax,
mov ax,
mov ax, idata
mov ax, .idata
mov ax, .idata
</code></pre>
<h3 id="79-不同的寻址方式的灵活运用">7.9 不同的寻址方式的灵活运用</h3>
<p>几种定位内存地址的方法(寻址方式):</p>
<table>
<thead>
<tr>
<th>形式</th>
<th>名称</th>
<th>特点</th>
<th>意义</th>
<th>示例</th>
</tr>
</thead>
<tbody>
<tr>
<td></td>
<td>直接寻址</td>
<td>用一个常量表示地址</td>
<td>可用于直接定位一个内存单元</td>
<td>movax, </td>
</tr>
<tr>
<td></td>
<td>寄存器间接寻址</td>
<td>用一个变量来表示内存地址</td>
<td>可用于间接定位一个内存单元</td>
<td>mov bx, 0<br>mov ax, </td>
</tr>
<tr>
<td></td>
<td>寄存器相对寻址</td>
<td>用一个变量和常量表示地址</td>
<td>可在一个起始地址的基础上用变量间接定位一个内存单元</td>
<td>mov bx, 4<br>mov ax, </td>
</tr>
<tr>
<td></td>
<td>基址变址寻址</td>
<td>用两个变量表示地址</td>
<td></td>
<td>mov ax, </td>
</tr>
<tr>
<td></td>
<td>相对基址变址寻址</td>
<td>用两个变量和一个常量表示地址</td>
<td></td>
<td>mov ax, </td>
</tr>
</tbody>
</table>
<p>实验1:将datasg段中的每个单词的头一个字母改为大写字母。</p>
<pre><code class="language-assembly">assume cs:codesg, ds:datasg
datasg segment
        db '1.file          '
        db '2.edit          '
        db '3.switch      '
        db '4.view          '
        db '5.options       '
        db '6.help          '
datasg ends

codeseg segment
start:
        mov ax, datasg
        mov ds, ax
       
        mov bx, 0
        mov cx, 6
s:        mov al,
        and al, 11011111b
        mov , al
        add bx, 16
        loop s
       
        mov 4c00h
        int 21h
codesg ends
end start
</code></pre>
<p>实验2:将datasg段中的每个单词都改为大写字母。</p>
<p>思路:</p>
<ul>
<li>四个字符,看成一个4行8列的二位数组;</li>
<li>要修改二维数组的每一行的前3列;</li>
<li>构造4×3次的二重循环。</li>
</ul>
<pre><code class="language-assembly">assume cs:codesg, ds:datasg
datasg segment
        db 'ibm   '
        db 'dec   '
        db 'dos   '
        db 'vax   '
datasg ends

codeseg segment
start:
        mov ax, datasg
        mov ds, ax
       
        mov bx, 0        ; 每一行的起始地址
        mov cx, 4
s0:        mov dx, cx        ; 暂存cx的值
        mov si, 0
        mov cx, 3
s:        mov al,
        and al, 11011111b
        mov , al
        inc si
        loop s
        add bx, 8
        mov cx, dx        ; 恢复外层循环的计数
        loop s0
       
        mov 4c00h
        int 21h
codesg ends
end start
</code></pre>
<p>上面用寄存器dx暂存cx的值比较浪费寄存器,可以用固定的内存空间保存数据。</p>
<pre><code class="language-assembly">s0:        mov ds:, cx        ; 暂存cx的值
        mov si, 0
        mov cx, 3
s:        mov al,
        and al, 11011111b
        mov , al
        inc si
        loop s
        add bx, 8
        mov cx, ds:        ; 恢复外层循环的计数
        loop s0
</code></pre>
<p>用内存空间保存数据,可能存在内存被修改的风险,因此可以考虑<strong>用栈保存数据</strong>:</p>
<p><strong>一般来说,在需要暂存数据的时候,我们都应该使用栈。</strong></p>
<pre><code class="language-assembly">assume cs:codesg, ds:datasg

stacksg segment
        dw 0, 0, 0, 0
stacksg ends

datasg segment
        db 'ibm   '
        db 'dec   '
        db 'dos   '
        db 'vax   '
datasg ends

codeseg segment
start:
        mov ax, stacksg
        mov ss, ax
        mov sp, 8
        mov ax, datasg
        mov ds, ax
       
        mov bx, 0        ; 每一行的起始地址
        mov cx, 4
s0:        push, cx        ; 暂存cx的值,将cx压栈
        mov si, 0
        mov cx, 3
s:        mov al,
        and al, 11011111b
        mov , al
        inc si
        loop s
        add bx, 8
        pop cx                ; 恢复外层循环的计数,恢复cx
        loop s0
       
        mov 4c00h
        int 21h
codesg ends
end start
</code></pre><br><br>
来源:https://www.cnblogs.com/mrlayfolk/p/16653790.html
頁: [1]
查看完整版本: 《汇编语言》学习笔记-1