《汇编语言》阅读笔记
<h1 id="汇编语言">汇编语言</h1><p>首先,我必须赞扬王爽老师,这是我见过写的最好的教科书了.</p>
<p>然后要注意,汇编语言这本书的前提是8086PC机十六位处理器.还要学x86和x64以及其他奇奇怪怪的架构的汇编.</p>
<p>再写一个这本书没有用到,但满常见的指令:lea 用来把存放相关数据的地址的</p>
<h2 id="全称">全称</h2>
<p>AH&AL=AX(accumulator):累加寄存器<br>
BH&BL=BX(base):基址寄存器<br>
CH&CL=CX(count):计数寄存器<br>
DH&DL=DX(data):数据寄存器<br>
SP(Stack Pointer):堆栈指针寄存器<br>
BP(Base Pointer):基址指针寄存器<br>
SI(Source Index):源变址寄存器<br>
DI(Destination Index):目的变址寄存器<br>
IP(Instruction Pointer):指令指针寄存器<br>
CS(Code Segment)代码段寄存器<br>
DS(Data Segment):数据段寄存器<br>
SS(Stack Segment):堆栈段寄存器<br>
ES(Extra Segment):附加段寄存器</p>
<p>FLAG标志寄存器: FLAG寄存器中存储的信息通常又被称作程序状态字(PSW)<br>
OF overflow flag 溢出标志 操作数超出机器能表示的范围表示溢出,溢出时为1.<br>
SF sign Flag 符号标志 记录运算结果的符号,结果负时为1.<br>
ZF zero flag 零标志 运算结果等于0时为1,否则为0.<br>
CF carry flag 进位标志 最高有效位产生进位时为1,否则为0.<br>
AF auxiliary carry flag 辅助进位标志 运算时,第3位向第4位产生进位时为1,否则为0.<br>
PF parity flag 奇偶标志 运算结果操作数位为1的个数为偶数个时为1,否则为0.<br>
DF direcion flag 方向标志 用于串处理.DF=1时,每次操作后使SI和DI减小.DF=0时则增大.<br>
IF interrupt flag 中断标志 IF=1时,允许CPU响应可屏蔽中断,否则关闭中断.<br>
TF trap flag 陷阱标志 用于调试单步操作.</p>
<p>psw: program status word<br>
tcon: timer control<br>
ie: interrupt enable<br>
scon: serial control<br>
EA--Effective Address:有效地址 ,即偏移地址。</p>
<p>SA--segment address</p>
<h2 id="基础知识">基础知识</h2>
<p>(1) 汇编指令是机器指令的助记符, 同机器指令一一对应。<br>
(2) 每一种CPU都有自己的汇编指令集。<br>
(3) CPU 可以直接使用的信息在存储器中存放。<br>
(4) 在存储器中指令和数据没有任何区别,都是二进制信息。<br>
(5) 存储单元从零开始顺序编号。<br>
(6) 在计算机中专门有连接 CPU 和其他芯片的导线, 通常称为总线。总线从物理上来讲, 就是一根根导线的集合。根据传送信息的不同, 总线从逻辑上又分为3类,地址总线、控制总线和数据总线。每一个CPU 芯片都有许多管脚,这些管脚和总线相连。也可以说,这些管脚引出总线。一个CPU 可以引出3 种总线的宽度标志了这个CPU 的不同方面的性能:<br>
地址总线的宽度决定了CPU 的寻址能力;<br>
数据总线的宽度决定了CPU 与其他器件进行数据传送时的一次数据传送量;<br>
控制总线的宽度决定了CPU 对系统中其他器件的控制能力。</p>
<p>出于对兼容性的考虑, 8086CPU 可以一次性处理以下两种尺寸的数据。</p>
<ul>
<li>字节: 记为byte , 一个字节由8 个加组成,可以存在8 位寄存器中。</li>
<li>字: 记为word, 一个字由两个字节组成, 这两个字节分别称为这个字的高位字节和低位字节.</li>
</ul>
<h2 id="寄存器">寄存器</h2>
<h3 id="通用寄存器">通用寄存器</h3>
<p>一个典型的CPU(此处讨论的不是某一具体的CPU) 由运算器、控制器、寄存器(CPU工作原理)等器件构成, 这些器件靠内部总线相连。</p>
<p>8086CPU 有14 个寄存器,每个寄存器有一个名称。这些寄存器是: AX 、BX 、EX 、DX 、SI 、DI 、SP 、BP 、IP 、CS 、SS 、DS 、ES 、PSW 。</p>
<p>8086CPU 的上一代CPU 中的寄存器都是8 位的, 为了保证兼容, 使原来基于上代<br>
CPU 编写的程序稍加修改就可以运行在8086 之上, 8086CPU 的AX、BX、EX 、DX 这4个寄存器都可分为两个可独立使用的8 位寄存器来用:</p>
<ul>
<li>AX 可分为AH 和AL ;</li>
<li>BX 可分为BH 和BL ;</li>
<li>EX 可分为CH 和CL ;</li>
<li>DX 可分为DH 和DL 。</li>
</ul>
<h3 id="内存地址管理">内存地址管理</h3>
<p>8086只有16位总线,但却可以寻址1Mb的大小.以下是它的做法:</p>
<p><img src="https://img2023.cnblogs.com/blog/3584580/202504/3584580-20250416194401118-581839497.png" alt="image-20250124002355445" loading="lazy"></p>
<p>如图2 . 6 所示, 当8086CPU 要读写内存时:<br>
21<br>
内存<br>
(1) CPU 中的相关部件提供两个16位的地址,一个称为段地址,另一个称为偏移地址;<br>
(2) 段地址和偏移地址通过内部总线送入一个称为地址加法器的部件;<br>
(3) 地址加法器将两个16位地址合成为一个20位的物理地址,地址加法器采用物理地址=段地址x16+偏移地址的方法用段地址和偏移地址合成物理地址;<br>
(4) 地址加法器通过内部总线将20位物理地址送入输入输出控制电路;<br>
(5) 输入输出控制电路将20位物理地址送上地址总线;<br>
(6) 20 位物理地址被地址总线传送到存储器。</p>
<p>在这里就引入了<strong>段的概念</strong>.</p>
<h3 id="cs和ip">CS和IP</h3>
<p>CS 为代码段寄存器, IP 为指令指针寄存器.</p>
<p><img src="https://img2023.cnblogs.com/blog/3584580/202504/3584580-20250416194401941-888480039.png" alt="image-20250124002849245" loading="lazy"><img src="https://img2023.cnblogs.com/blog/3584580/202504/3584580-20250416194402773-619871663.png" alt="image-20250124002856454" loading="lazy"></p>
<p><img src="https://img2023.cnblogs.com/blog/3584580/202504/3584580-20250416194404376-1864881635.png" alt="image-20250124002917839" loading="lazy"></p>
<p><img src="https://img2023.cnblogs.com/blog/3584580/202504/3584580-20250416194406142-135418935.png" alt="image-20250124002932660" loading="lazy"></p>
<p>以上可以概括为:</p>
<p>(1) 从CS:IP指向的内存单元读取指令,读取的指令进入指令缓冲器;<br>
(2) IP=IP+所读取指令的长度,从而指向下一条指令;<br>
(3) 执行指令。转到步骤(1) , 重复这个过程。</p>
<p>可用<code>jmp 段地址:偏移地址</code>来修改CSIP,也可以使用<code>jmp 合法寄存器</code>等于<code>mov IP,寄存器</code>.</p>
<h2 id="寄存器内存访问">寄存器(内存访问)</h2>
<p>CPU 要读写一个内存单元的时候, 必须先给出这个内存单元的地址, 在8086PC 中,内存地址由段地址和偏移地址组成。8086CPU 中有一个DS 寄存器, 通常用来存放要访问数据的段地址。</p>
<p>CPU 如何知道栈顶的位置?显然,也应该有相应的寄存器来存放栈顶的地址,8086CPU 中,有两个寄存器,段寄存器SS 和寄存器SP, 栈顶的段地址存放在SS中,偏移地址存放在SP 中。<strong>任意时刻,SS:SP 指向栈顶元素。</strong>push指令和pop指令执行时,CPU从SS和SP中得到栈顶的地址。</p>
<h2 id="第一个程序">第一个程序</h2>
<p>先对汇编代码的结构做个了解.</p>
<p>以下是一段汇编代码:</p>
<pre><code class="language-asm">assume cs:codesg
codesg segment
mov ax, 0123H
mov bx, 0456H
add ax, bx
add ax, ax
mov ax, 4c00H
int 21H
codesg ends
end
</code></pre>
<h3 id="伪代码">伪代码</h3>
<p><code>xxx segment</code>和<code>xxx ends</code>是一对,也是必须用到的,功能是定义一个代码段</p>
<p><code>end</code>是汇编程序结束的标志</p>
<p><code>assume</code>是关联相关的段寄存器</p>
<h3 id="程序返回代码">程序返回代码</h3>
<pre><code class="language-asm">mov ax,4c00H
int 21H ;程序返回代码
</code></pre>
<h3 id="编译与链接">编译与链接</h3>
<p>编译与链接不做了解.</p>
<h2 id="bx和loop指令">和loop指令</h2>
<h3 id="一些定义">一些定义</h3>
<p>表示的是内存单元的偏移地址</p>
<p>(BX)则表示里面的内容</p>
<p>idata表达常量</p>
<h3 id="loop">loop</h3>
<p>这是一段含loop的指令:</p>
<pre><code class="language-asm">assume cs:code
code segment
mov ax, 2
mov cx, 11
s:
add ax, ax
loop s ;cx为0则执行下一条,否则令cx减1
mov ax,4c00h
int 21h
code ends
end
</code></pre>
<h3 id="段前缀">段前缀</h3>
<p>这些出现在访问内存单元的指令中,用于显式地指明内存单元的段地址的"ds:" "cs:" "ss:" " es:",在汇编语言中称为段前缀。默认则在ds中</p>
<h2 id="包含多个段的程序">包含多个段的程序</h2>
<h3 id="代码段使用数据">代码段使用数据</h3>
<p>以下是一段asm代码:</p>
<pre><code class="language-assembly">assume cs:code
code segment
dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h
mov bx, O
mov ax, O
mov cx, 8
s:
add ax, cs:
add bx, 2
loop s
mov ax, 4c00h
int 21h
code ends
end
</code></pre>
<p>程序第一行中的“dw" 的含义是定义字型数据。dw 即“ define word "。<br>
在这里,使用dw定义了8个字型数据(数据之间以逗号分隔),它们所占的内存空间的大小为16个字节。</p>
<h3 id="入口">入口</h3>
<pre><code class="language-asm">assume cs:code
code segment
dw 0123h, 0456h, 0789h, 0abch, 0defh, 0fedh, 0cbah, 0987h
start:
mov bx, 0
mov ax, 0
mov cx , 8
s:
add ax, cs:
add bx, 2
code ends
end start
</code></pre>
<p>在程序的第一条指令的前面加上了一个标号start,而这个标号在伪指令end的后面出现。这里,我们要再次探讨end的作用。end除了通知编译器程序结束外,还可以通知编译器程序的入口在什么地方。在程序6.2中我们用end指令指明了程序的入口在标号start处,也就是说,“ mov bx,0"是程序的第一条指令。</p>
<h3 id="不同的段">不同的段</h3>
<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 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0
stack ends
code segment
start:
mov ax, stack
mov ss, ax
mov sp, 20h ;设置栈顶ss:sp 指向s tack : 20
mov ax, data
mov ds, ax
mov bx, 0
mov cx, 8
s :
push
add bx, 2
loop s
mov bx, O
mov cx, 8
s0 :
pop
add bx, 2
loop s0
mov ax, 4c00h
int 21h
code ends
end start
</code></pre>
<p>这里是定义多个段,由于有符号,所以符号可以对应段地址,比如<code>mov ax, stack</code>就是把stack的段地址放入ax中.</p>
<h2 id="更灵活的定位内存地址的方法">更灵活的定位内存地址的方法</h2>
<h3 id="字符形式的数据">字符形式的数据</h3>
<p>" db'unIX' ” 相当于“db 75H 6EH 49H 58H”.</p>
<h3 id="si和di">si和di</h3>
<p>si和di在8086机里与bx功能接近,但不能分成两个8位寄存器</p>
<h3 id="不同的寻址方式的灵活应用">不同的寻址方式的灵活应用</h3>
<p>(1) 用一个常量来表示地址, 可用于直接定位一个内存单元;<br>
(2) 用一个变量来表示内存地址, 可用于间接定位一个内存单元;<br>
(3) 用一个变量和常量表示地址,可在一个起始地址的基础上用变量间接<br>
定位一个内存单元;<br>
(4) 用两个变量表示地址;<br>
(5) 用两个变最和一个常量表示地址。</p>
<h2 id="数据处理的两个基本问题">数据处理的两个基本问题</h2>
<h3 id="warnings">Warnings</h3>
<p>mov ax, <br>
mov ax, <br>
mov ax, <br>
mov ax, <br>
mov ax, <br>
mov ax, <br>
mov ax, <br>
mov ax, <br>
mov ax, <br>
mov ax, <br>
mov ax, <br>
mov ax, <br>
下面的指令是错误的:<br>
mov ax, <br>
mov ax, </p>
<p>只要在[ ]中使用寄存器bp ,而指令中没有显性地给出段地址,段地址就默认在ss 中。比如下面的指令。<br>
mov ax, <br>
mov ax, <br>
mov ax, <br>
含义: (ax) = ((ss) *16+ (bp) )<br>
含义: (ax) = ((ss) *16+ (bp) +idata)<br>
含义: (ax) = ((ss) *16+ (bp) + (si))<br>
mov ax, 含义: (ax) = ((ss) *16+ (bp) + (si) +idata)</p>
<h3 id="寻址方式小结">寻址方式小结</h3>
<p><img src="https://img2023.cnblogs.com/blog/3584580/202504/3584580-20250416194407185-1913337635.png" alt="image-20250124105340921" loading="lazy"></p>
<h2 id="指令转移的原理">指令转移的原理</h2>
<p>可以修改IP, 或同时修改CS和IP的指令统称为转移指令。</p>
<h3 id="依据位移进行转移的jmp指令">依据位移进行转移的jmp指令</h3>
<p>jmp short 标号(转到标号处执行指令)</p>
<p>这种格式的jmp指令实现的是段内短转移,它对IP 的修改范围为-128~ 127, 也就是说,它向前转移时可以最多越过128 个字节,向后转移可以最多越过127个字节。jmp指令中的“short"符号,说明指令进行的是短转移。jmp 指令中的“标号”是代码段中的标号,指明了指令要转移的目的地,转移指令结束后,CS:IP应该指向标号处的指令。</p>
<p>在"jmp short 标号"指令所对应的机器码中, 并不包含转移的目的地址,而包含的是转移的位移。这个位移,是编译器根据汇编指令中的“ 标号”计算出来的.</p>
<p>实际上, jmp short 标号”的功能为:(IP)=(IP)+8 位位移。<br>
(1) 8 位位移=标号处的地址-jmp指令后的第一个字节的地址;<br>
(2) short 指明此处的位移为8位位移;<br>
(3) 8 位位移的范围为- 128~ 127, 用补码表示(如果你对补码还不了解, 请阅读附<br>
注2);<br>
(4) 8 位位移由编译程序在编译时算出。<br>
还有一种和“jmp short 标号”功能相近的指令格式,jmp near ptr 标号,它实现的是<br>
段内近转移。<br>
jmp near ptr 标号”的功能为: (IP)=(IP)+ 16 位位移。<br>
(1) 16 位位移=标号处的地址-jmp 指令后的第一个字节的地址;<br>
(2) near ptr 指明此处的位移为16 位位移,进行的是段内近转移;<br>
(3) 16 位位移的范围为—32768~32767, 用补码表示;<br>
(4) 16 位位移由编译程序在编译时算出。</p>
<h3 id="转移的目的地址在指令中的jmp指令">转移的目的地址在指令中的jmp指令</h3>
<p>"jmp far ptr 标号"实现的是段间转移,又称为远转移。</p>
<h3 id="转移地址在寄存器中的jmp指令">转移地址在寄存器中的jmp指令</h3>
<p>指令格式:jmp 16位reg<br>
功能:(IP)=(l6位reg)</p>
<h3 id="转移地址在内存中的jmp指令">转移地址在内存中的jmp指令</h3>
<p>(1) jmp word ptr 内存单元地址(段内转移)</p>
<p>(2) jmp dword ptr 内存单元地址(段间转移)</p>
<h3 id="有条件转移">有条件转移</h3>
<p>jcxz 当cx为zero时,短转移</p>
<p>loop其实是类似的,而且也是短转移.</p>
<h2 id="call-和ret-指令">CALL 和RET 指令</h2>
<p>跳过,已经品味得够多了.</p>
<h2 id="标识寄存器">标识寄存器</h2>
<p>FLAG标志寄存器: FLAG寄存器中存储的信息通常又被称作程序状态字(PSW)<br>
OF overflow flag 溢出标志 操作数超出机器能表示的范围表示溢出,溢出时为1.<br>
SF sign Flag 符号标志 记录运算结果的符号,结果负时为1.<br>
ZF zero flag 零标志 运算结果等于0时为1,否则为0.<br>
CF carry flag 进位标志 最高有效位产生进位时为1,否则为0.<br>
AF auxiliary carry flag 辅助进位标志 运算时,第3位向第4位产生进位时为1,否则为0.<br>
PF parity flag 奇偶标志 运算结果操作数位为1的个数为偶数个时为1,否则为0.<br>
DF direcion flag 方向标志 用于串处理.DF=1时,每次操作后使SI和DI减小.DF=0时则增大.<br>
IF interrupt flag 中断标志 IF=1时,允许CPU响应可屏蔽中断,否则关闭中断.<br>
TF trap flag 陷阱标志 用于调试单步操作.</p>
<h3 id="adc-sbb">adc sbb</h3>
<p>adc 是带进位加法指令, 它利用了CF 位上记录的进位值。<br>
指令格式: adc 操作对象1, 操作对象2<br>
功能: 操作对象1 = 操作对象1+ 操作对象2+CF<br>
比如指令adc ax,bx 实现的功能是: (ax)=(ax)+(bx)+ CF</p>
<p>sbb 是带借位减法指令,它利用了CF位上记录的借位值。<br>
指令格式: sbb 操作对象1, 操作对象2<br>
功能: 操作对象l =操作对象1-操作对象2-CF<br>
比如指令sbb ax,bx 实现的功能是: (ax)=(ax)—(bx)—CF</p>
<p>用于更大数据的高位运算</p>
<h3 id="cmp">cmp</h3>
<p>cmp 是比较指令, cmp 的功能相当于减法指令,只是不保存结果。cmp 指令执行后,将对标志寄存器产生影响。其他相关指令通过识别这些被影响的标志寄存器位来得知比较结果。<br>
cmp 指令格式:cmp 操作对象1, 操作对象2<br>
功能:计算操作对象l —操作对象2 但并不保存结果,仅仅根据计算结果对标志寄存<br>
器进行设置。<br>
比如,指令cmp ax,ax,做(ax)-(ax)的运算,结果为0, 但并不在ax中保存,仅影响<br>
flag的相关各位。指令执行后:zf=1, pf=1, sf=0, cf=0 of=0。</p>
<h3 id="相关跳转">相关跳转</h3>
<p>下面是常用的根据无符号数的比较结果进行转移的条件转移指令。</p>
<p><img src="https://img2023.cnblogs.com/blog/3584580/202504/3584580-20250416194408109-1634022924.png" alt="image-20250124114137692" loading="lazy"></p>
<p>这些指令比较常用,它们都很好记忆,它们的第一个字母都是j ,表示jump; 后面的<br>
字母表示意义如下。<br>
e : 表示equal<br>
ne: 表示not equal<br>
b: 表示below<br>
nb : 表示not below<br>
a : 表示above<br>
na: 表示not above</p>
<h3 id="popf-pushf">popf pushf</h3>
<p>压入和弹出 相关寄存器为标识寄存器</p>
<h2 id="内中断和外中断">内中断和外中断</h2>
<p>没啥好了解的,跳过</p>
<h2 id="指令">指令</h2>
<p>mov add sub div mul abc sbb</p>
<p>push pop</p>
<p>int</p>
<p>inc:令寄存器加1</p>
<p>loop</p>
<p>and:逻辑与指令,按位进行与运算。 or:逻辑或指令, 按位进行或运算。</p>
<p>db dw dd(dword) dup(<code>db 重复的次数 dup(重复的数据)</code>) offset(获取偏移值)</p>
<p>jmp</p>
<p>shl shr 位移,移出的最后以为放到CF中</p><br><br>
来源:https://www.cnblogs.com/T0fV404/p/18829456
頁:
[1]