石李 發表於 2022-1-27 21:12:00

《x86汇编语言:从实模式到保护模式》笔记

<h1 id="x86汇编语言笔记">x86汇编语言笔记</h1>
<h3 id="8086通用寄存器">8086通用寄存器</h3>
<blockquote>
<p>16位寄存器:AX、BX、CX、DX、SI、DI、BP、SP。</p>
<p>前4个可分为高8位和低8位来使用:AH、AL、BH、BL、CH、CL、DH、DL。</p>
</blockquote>
<h3 id="内存分段">内存分段</h3>
<blockquote>
<p>采用分段技术解决地址重定位问题,在硬件级别用两个段寄存器来支持,代码段寄存器CS和数据段寄存器DS。</p>
<p>实模式下CPU访问物理地址的方式:<code>段基址:偏移地址</code>。</p>
</blockquote>
<h3 id="8086段寄存器">8086段寄存器</h3>
<blockquote>
<ul>
<li>代码段寄存器:CS</li>
<li>数据段寄存器:DS</li>
<li>附加段寄存器:ES</li>
<li>栈段寄存器:SS</li>
</ul>
<p>如何访问指令?</p>
<blockquote>
<p>当程序开始时,CS指向代码段的起始地址,IP指令指针指向段内偏移。即<code>CS:IP</code>。</p>
<p>注意:一般没有指定段寄存器时,默认使用DS。</p>
</blockquote>
<p>如何访问数据?</p>
<blockquote>
<p>在访问内存单元时,则采用<code>DS:偏移地址</code>。</p>
</blockquote>
<p>注意1:由于8086提供20位的物理地址,所以在计算物理地址时,要将段寄存器左移4位再加上偏移地址。由此可以得出8086最大只能访问1MB的内存空间。</p>
<p>注意2:内存是以字节为单位,内存中的每一个字节都对应一个物理地址。</p>
</blockquote>
<h3 id="计算机的启动流程">计算机的启动流程</h3>
<p><img src="https://images.cnblogs.com/cnblogs_com/Lht1/2216671/o_220913043002_20220122202915.png" alt="image-20220122202902641" loading="lazy"></p>
<blockquote>
<p>注意:主引导扇区的最有两个字节是0x55和0xaa。</p>
</blockquote>
<h3 id="显存">显存</h3>
<blockquote>
<p>显存存在于显卡之中,一般CPU是通过把显存映射到<code>0xB8000~0xBFFFF</code>这部分内存空间,来直接访问显存,显卡在加电自检后会初始化为80 * 25模式,即屏幕上显示25行,每行80个字符,每屏总共2000个字符。</p>
<p>显卡通过ASCII编码来识别CPU传入显存的数据。</p>
<p>屏幕上每个字符对应着显存中的连续两个字节,前一个字节为字符的ASCII代码,后一个字节为字符的显示属性。</p>
<p><img src="https://images.cnblogs.com/cnblogs_com/Lht1/2216671/o_220913043048_20220122205324.png" alt="image-20220122205323064" loading="lazy"></p>
<p><img src="https://images.cnblogs.com/cnblogs_com/Lht1/2216671/o_220913043055_20220122205338.png" alt="image-20220122205336604" loading="lazy"></p>
</blockquote>
<p><img src="https://images.cnblogs.com/cnblogs_com/Lht1/2216671/o_220913043043_20220122204331.png" alt="image-20220122204330102" loading="lazy"></p>
<blockquote>
<p>注意:Intel的处理器不允许把一个立即数传送到段寄存器,必须通过其他寄存器来中转。</p>
</blockquote>
<h3 id="mov指令">mov指令</h3>
<pre><code class="language-assembly">mov 目的操作数, 源操作数
</code></pre>
<p>注意:mov指令不允许目的操作数和源操作数都为内存单元,并且目的操作数不能为立即数。</p>
<p>注意:内存的增长是从低地址到高地址的。</p>
<hr>
<h3 id="除法">除法</h3>
<p>无符号除法指令:<code>div</code></p>
<ul>
<li>
<p>16位除法:</p>
<blockquote>
<p>被除数放在ax寄存器中。</p>
<p>格式:</p>
<pre><code class="language-assembly">div x ;这里的x可以代表寄存器或者[内存单元]或者立即数
</code></pre>
<p></p><div class="math display">\[ax / x = s \cdots m
\]</div><p></p><p>参数:</p>
<ul>
<li>x:除数。</li>
<li>s:表示商,放在al寄存器。</li>
<li>m:表示余数,放在ah寄存器。</li>
</ul>
</blockquote>
</li>
<li>
<p>32位除法:</p>
<blockquote>
<p>被除数的高16位放在dx寄存器中,低16位放在ax中。</p>
<p>格式:</p>
<pre><code class="language-assembly">div x ;这里的x可以代表寄存器或者[内存单元]或者立即数
</code></pre>
<p></p><div class="math display">\[(dx &lt;&lt; 4 + ax) / x = s \cdots m
\]</div><p></p><p>参数:</p>
<ul>
<li>x:除数。</li>
<li>s:表示商,放在ax寄存器。</li>
<li>m:表示余数,放在dx寄存器。</li>
</ul>
</blockquote>
</li>
</ul>
<hr>
<h3 id="在段之间批量传送数据">在段之间批量传送数据</h3>
<p>使用movsb和movsw指令可以在两个段之间批量传送数据。movsb每次传送一个字节,movsw每次传送一个字。</p>
<p>格式:</p>
<pre><code class="language-assembly">; 重复执行
rep movsb ; rep指令前缀表示cx寄存器的值不为0,则重复执行该指令。
rep movsw
; 只执行一次
movsb
movsw
</code></pre>
<p>参数:</p>
<ul>
<li>源地址:<code>DS:SI</code>,即段地址由DS寄存器提供,SI寄存器提供偏移地址。</li>
<li>目的地址:<code>ES:DI</code>,即段地址由ES寄存器提供,DI寄存器提供偏移地址。</li>
<li>数量:由CX寄存器指定。</li>
</ul>
<p>传送方向:</p>
<ul>
<li>正向:从低地址到高地址,每次使SI和DI加1或2。</li>
<li>反向:从高地址到低地址,每次使SI和DI减1或2。</li>
</ul>
<hr>
<h3 id="一个特殊的寄存器flag">一个特殊的寄存器flag</h3>
<p>通过cld指令来将flag寄存器的DF位设置为0,std指令将flag寄存器的DF位设置位1。</p>
<blockquote>
<p>ZF位表示运算结果是否为0。</p>
<ul>
<li><code>1</code>:运算结果为0</li>
<li><code>0</code>:运算结果不为0</li>
</ul>
<p>DF位表示movsb/movsw指令是正向还是反向。</p>
<ul>
<li><code>0</code>:正向</li>
<li><code>1</code>:反向</li>
</ul>
</blockquote>
<p><img src="https://images.cnblogs.com/cnblogs_com/Lht1/2216671/o_220913043108_20220123204103.png" alt="image-20220123204058429" loading="lazy"></p>
<hr>
<h3 id="关于使用寄存器提供偏移地址的问题">关于使用寄存器提供偏移地址的问题</h3>
<blockquote>
<p>在8086处理器上只能使用BX、SI、DI、BP寄存器来提供偏移地址。</p>
</blockquote>
<hr>
<h3 id="加一和减一加法和减法">加一和减一、加法和减法</h3>
<p>加一和减一对应的指令为<code>inc</code>和<code>dec</code>。</p>
<p>加法和减法对应的指令为<code>add</code>和<code>sub</code>。</p>
<hr>
<h3 id="负数相关指令">负数相关指令</h3>
<ul>
<li><code>neg 寄存器</code>:将对应寄存器的值变为负数。</li>
<li><code>cbw</code>:无操作数,将AL寄存器的数值扩展到AX寄存器。</li>
<li><code>cwd</code>:无操作数,将AX寄存器的数值扩展到DX和AX寄存器中,DX存高16位,AX存低16位。</li>
</ul>
<hr>
<h3 id="jns指令">jns指令</h3>
<p>标志寄存器的SF位:当计算结果的最高位是0时,该位为0,否则为1。</p>
<p><code>jns</code>指令,当标志寄存器的SF位为0时,则进行跳转。</p>
<h3 id="标记寄存器的其他标志位">标记寄存器的其他标志位</h3>
<ul>
<li>奇偶标志位PF:当运算结果的低8位中,有偶数个1则PF为1,否则为0。</li>
<li>进位标志位CF:当进行算术运算时,如果有向最高位进位或借位,则CF为1,否则为0。</li>
<li>溢出标志位OF:如果发生溢出则为1,否则为0。</li>
</ul>
<p>指令对标志寄存器的影响:</p>
<p><img src="https://images.cnblogs.com/cnblogs_com/Lht1/2216671/o_220913043111_20220123215131.png" alt="image-20220123215129845" loading="lazy"></p>
<h3 id="条件跳转指令">条件跳转指令</h3>
<p>根据标志跳转:</p>
<ul>
<li><code>jz</code>:ZF为1则转移,<code>jnz</code>:ZF为0则转移。</li>
<li><code>jo</code>:OF为1则转移,<code>jno</code>:OF为0则转移。</li>
<li><code>jc</code>:CF为1则转移,<code>jnc</code>:CF为0则转移。</li>
<li><code>jp</code>:PF为1则转移,<code>jnp</code>:PF为0则转移。</li>
</ul>
<p>根据比较结果跳转:</p>
<p>可以通过<code>cmp</code>指令进行比较。</p>
<p><img src="https://images.cnblogs.com/cnblogs_com/Lht1/2216671/o_220913043115_20220123215702.png" alt="image-20220123215659834" loading="lazy"></p>
<p><img src="https://images.cnblogs.com/cnblogs_com/Lht1/2216671/o_220913043118_20220123215723.png" alt="image-20220123215722000" loading="lazy"></p>
<p>根据CX寄存器进行跳转:</p>
<p><code>jcxz</code>:如果CX寄存器为0,则进行跳转。</p>
<hr>
<h3 id="栈段">栈段</h3>
<p>由SS寄存器来确定段地址,SP寄存器确定偏移地址。</p>
<p>压栈:<code>push</code>指令,会将SP的内容减去操作数的字长,再把操作数压入栈中。</p>
<p>弹栈:<code>pop</code>指令,会将SP的内容加上操作数的字长,再把栈顶的数据弹出。</p>
<blockquote>
<p>注意:实模式下,栈的大小为64KB。</p>
</blockquote>
<hr>
<h3 id="寻址方式">寻址方式</h3>
<ul>
<li>
<p>寄存器寻址</p>
</li>
<li>
<p>立即寻址</p>
</li>
<li>
<p>内存寻址</p>
<ul>
<li>
<p>直接寻址</p>
</li>
<li>
<p>基址寻址:使用BX和BP寄存器进行寻址</p>
<blockquote>
<p>注意:BP寄存器默认使用SS段寄存器,使得栈内数据可以像数据段一样访问。</p>
</blockquote>
</li>
<li>
<p>变址寻址:使用SI和DI寄存器进行寻址</p>
</li>
<li>
<p>基址变址寄存器:同时使用BX/BP和SI/DI寄存器进行寻址</p>
</li>
</ul>
</li>
</ul>
<hr>
<h3 id="io端口读写方式">I/O端口读写方式</h3>
<p>I/O端口的范围:0 ~ 65535(0x0 ~ 0xffff)。</p>
<ul>
<li>
<p>读取I/O端口</p>
<pre><code class="language-assembly">in 存放位置, 端口号
</code></pre>
<blockquote>
<p>存放位置:必须是AL或AX寄存器。</p>
<p>端口号:立即数或DX寄存器。</p>
</blockquote>
</li>
<li>
<p>写入I/O端口</p>
<pre><code class="language-assembly">out 端口号, 写入数据
</code></pre>
<blockquote>
<p>端口号:立即数或DX寄存器。</p>
<p>写入数据:必须是AL或AX寄存器。</p>
</blockquote>
</li>
</ul>
<p>一些磁盘相关的知识:</p>
<ul>
<li>
<p>LBA28逻辑扇区编制法:即通过28个bit来表示逻辑扇区号,而不关心扇区的具体位置。(从0开始算起)</p>
</li>
<li>
<p>端口号:</p>
<table>
<thead>
<tr>
<th>端口号</th>
<th>含义</th>
</tr>
</thead>
<tbody>
<tr>
<td>1f2</td>
<td>表示要读取的扇区数</td>
</tr>
<tr>
<td>1f3</td>
<td>表示逻辑扇区号的0~7位</td>
</tr>
<tr>
<td>1f4</td>
<td>表示逻辑扇区号的8~15位</td>
</tr>
<tr>
<td>1f5</td>
<td>表示逻辑扇区号的16~23位</td>
</tr>
<tr>
<td>1f6</td>
<td>低4位存放逻辑扇区号的24~27位,第4位表示硬盘号(0主盘,1从盘),第6位表示扇区编制方式(0:CHS、1:LBA)</td>
</tr>
<tr>
<td>1f7</td>
<td>命令端口:写入0x20表示读硬盘请求,状态端口:第7位表示硬盘忙碌状态(0不忙,1忙碌),第3位表示硬盘数据是否准备好(1:ok,0:no),第0位表示读取数据是否出错(1出错,0没出错)</td>
</tr>
<tr>
<td>1f0</td>
<td>硬盘接口的数据端口(16位端口),当硬盘准备好后,可以从这个端口读取数据</td>
</tr>
</tbody>
</table>
<p><img src="https://images.cnblogs.com/cnblogs_com/Lht1/2216671/o_220913043122_20220124155736.png" alt="image-20220124155733782" loading="lazy"></p>
</li>
</ul>
<p><img src="https://images.cnblogs.com/cnblogs_com/Lht1/2216671/o_220913043130_20220124160019.png" alt="image-20220124160018268" loading="lazy"></p>
<hr>
<h3 id="过程调用">过程调用</h3>
<p>call指令:</p>
<blockquote>
<p>近调用的原理是先把IP寄存器的值压入栈中,再修改IP寄存器,而远调用则是分别把CS和IP寄存器的值压入栈,再修改CS和IP寄存器的值。</p>
</blockquote>
<ul>
<li>
<p>直接近调用</p>
<pre><code class="language-assembly">call 立即数
</code></pre>
</li>
<li>
<p>间接近调用</p>
<pre><code class="language-assembly">call 寄存器/[内存地址]
</code></pre>
</li>
<li>
<p>直接远调用</p>
<pre><code class="language-assembly">call 段地址:偏移地址
</code></pre>
</li>
<li>
<p>间接远调用</p>
<pre><code class="language-assembly">call far [内存地址]
</code></pre>
<blockquote>
<p>这种方式会从对应的内存地址中获取两个字的数据,前一个字作为段地址,后一个字作为偏移地址。</p>
</blockquote>
</li>
</ul>
<p>返回指令:</p>
<blockquote>
<p><code>ret</code>的原理是弹出栈中的值到IP寄存器,<code>retf</code>的原理是先弹出到IP寄存器,再弹到CS寄存器。</p>
</blockquote>
<ul>
<li><code>ret</code>:相对近返回</li>
<li><code>retf</code>:相对远返回</li>
</ul>
<hr>
<h3 id="abcshrror指令">abc、shr、ror指令</h3>
<ul>
<li><code>abc</code>:同<code>add</code>指令差不多,但是会加上CF位的值(CF是表示是否进位)。</li>
<li><code>shr</code>:逻辑右移,类似C语言中的<code>&gt;&gt;</code>。</li>
<li><code>shl</code>:逻辑左移,类似C语言中的<code>&lt;&lt;</code>。</li>
<li><code>ror</code>:循环右移,将移出的bit放到左边空缺的位置,同时送入CF标志位。</li>
<li><code>rol</code>:循环左移,将移出的bit放到右边空缺的位置,同时送入CF标志位。</li>
</ul>
<p><img src="https://images.cnblogs.com/cnblogs_com/Lht1/2216671/o_220913043134_20220124172322.png" alt="image-20220124172321064" loading="lazy"></p>
<hr>
<h3 id="无条件跳转指令">无条件跳转指令</h3>
<ul>
<li>
<p>相对短转移:立即数表示偏移量</p>
<pre><code class="language-assembly">jmp short 立即数
</code></pre>
</li>
<li>
<p>相对近转移:立即数表示偏移量</p>
<pre><code class="language-assembly">jmp near 立即数
; 或
jmp 立即数
</code></pre>
</li>
<li>
<p>间接绝对近转移:寄存器或内存地址表示偏移地址</p>
<pre><code class="language-assembly">jmp near 寄存器/[内存地址]
; 或
jmp 寄存器/[内存地址]
</code></pre>
</li>
<li>
<p>直接绝对远转移</p>
<pre><code class="language-assembly">jmp 段地址:偏移地址
</code></pre>
</li>
<li>
<p>间接绝对远转移</p>
<pre><code class="language-assembly">jmp far 寄存器/[内存地址]
</code></pre>
</li>
</ul>
<hr>
<h3 id="伪指令resb">伪指令resb</h3>
<ul>
<li>
<p><code>resb</code></p>
<blockquote>
<p>从当前位置开始,保留指定数量的字节,但不初始化它们的值。(区分db、dw、dd、dq)</p>
</blockquote>
</li>
<li>
<p><code>resw</code>:与resb相同,但是单位为字。</p>
</li>
<li>
<p><code>resd</code>:与resb相同,但是单位为双字。</p>
</li>
</ul>
<hr>
<h3 id="光标操作">光标操作</h3>
<blockquote>
<p>屏幕上光标的位置保存在显卡内部的两个光标寄存器(8位)中,合起来是一个16位数值,表示在第几个位置(<span class="math inline">\(0 \sim {(80 * 25) - 1}\)</span>)。</p>
<p>注意:文字模式下,屏幕显示80*25个字符。</p>
</blockquote>
<p>读取光标:</p>
<blockquote>
<p>步骤:</p>
<ol>
<li>向端口号0x3d4的索引寄存器指定索引,0xe是光标位置的高8位,0xf是光标位置的低8位。</li>
<li>通过端口号0x3d5的数据寄存器读取数据。</li>
</ol>
</blockquote>
<p>设置光标:</p>
<blockquote>
<p>步骤:</p>
<ol>
<li>向端口号0x3d4的索引寄存器写入光标位置的索引。</li>
<li>向端口号0x3d5的数据寄存器写入光标位置。</li>
</ol>
</blockquote>
<hr>
<h3 id="乘法">乘法</h3>
<p><code>mul</code>指令</p>
<pre><code class="language-assembly">mul 寄存器/[内存地址]
</code></pre>
<ul>
<li>8位乘法:将mul指令里指定的值乘以AL寄存器中的值,并把结果保存到AX寄存器中。</li>
<li>16位乘法:将mul指令里指定的值乘以AX寄存器中的值,并把结果的高16位保存到DX,低16位保存到AX中。</li>
</ul>
<hr>
<h3 id="cpuid指令">cpuid指令</h3>
<p>用于返回处理器的标识和特性信息。</p>
<p>在eax寄存器中指定要返回CPU信息。返回结果放在eax、ebx、ecx或edx中。</p>
<hr>
<h3 id="eflags寄存器">eflags寄存器</h3>
<p><img src="https://images.cnblogs.com/cnblogs_com/Lht1/2216671/o_220913043137_20220126165258.png" alt="image-20220126165237566" loading="lazy"></p>
<hr>
<h3 id="comvcc指令">comvcc指令</h3>
<p>可以看成<code>mov</code>指令加上条件判断功能,与<code>cmp</code>或<code>test</code>指令配合使用。</p>
<p>伪代码:</p>
<pre><code class="language-c">if (condition) {
    mov dest, source
}
</code></pre>
<hr>
<h3 id="sgdt指令">sgdt指令</h3>
<p>格式:<code>sgdt 寄存器/[内存单元]</code></p>
<p>把gdtr寄存器的内容保存到指定位置。</p>
<hr>
<h3 id="movzxmovsx指令">movzx/movsx指令</h3>
<ul>
<li>
<p>movzx:带零扩展传送指令</p>
<blockquote>
<p>格式:<code>movzx r16/r32, r8/r16/m8/m16</code></p>
<p>把指定8位/16位寄存器/内存单元的数据放到16/32位寄存器中,并且将高位设置为0。</p>
</blockquote>
</li>
<li>
<p>movsx:带符号扩展传送指令</p>
<blockquote>
<p>格式:<code>movsx r16/r32, r8/r16/m8/m16</code></p>
<p>把指定8位/16位寄存器/内存单元的数据放到16/32位寄存器中,并且将高位设置为跟符号位一致。</p>
</blockquote>
</li>
</ul>
<hr>
<h3 id="cmps指令">cmps指令</h3>
<p><code>cmp</code>指令的升级版,通过cx(16位)或ecx(32位)寄存器来指定比较次数,<code>ds:si/esi</code>寄存器指定源地址,<code>es:di/edi</code>寄存器指定目的地址,并根据eflags寄存器中的df位来决定地址变化的方向,<span class="math inline">\(df=0\)</span>则正向比较,地址递增,<span class="math inline">\(df=1\)</span>则反向比较,地址递减,如果没有加上<code>rep</code>指令前缀,则只比较一次。</p>
<pre><code class="language-assembly">cmpsb ;字节比较
cmpsw ;字比较
cmpsd ;双字比较
</code></pre>
<p>有关的<code>rep</code>指令前缀:</p>
<ul>
<li><code>rep</code>:一直重复到cx寄存器的值为0。</li>
<li><code>repz/repe</code>:一直重复到cx寄存器的值为0或比较的内容不相等。</li>
<li><code>repnz/repne</code>:一直重复到cx寄存器的值为0或比较的内容相等。</li>
</ul>
<p><img src="https://images.cnblogs.com/cnblogs_com/Lht1/2216671/o_220913043141_20220126173108.png" alt="image-20220126173106954" loading="lazy"></p>
<hr>
<h3 id="调用门">调用门</h3>
<blockquote>
<p>调用门(Call Gate)用于不同特权级的程序之间进行控制转移。本质上只是一个描述符(不同于代码段和数据段)。</p>
</blockquote>
<p><img src="https://images.cnblogs.com/cnblogs_com/Lht1/2216671/o_220913043145_20220126200040.png" alt="image-20220126200038307" loading="lazy"></p>
<p>调用门的特权级检查规则:</p>
<p><img src="https://images.cnblogs.com/cnblogs_com/Lht1/2216671/o_220913043149_20220126203514.png" alt="image-20220126203513197" loading="lazy"></p>
<hr>
<h3 id="pushfpopf指令">pushf/popf指令</h3>
<p>把16位的flags寄存器/32位的eflags寄存器压或弹栈到flag寄存器中。</p>
<hr>
<h3 id="gdtldt和tss的关系">GDT、LDT和TSS的关系</h3>
<p><img src="https://images.cnblogs.com/cnblogs_com/Lht1/2216671/o_220913043154_20220126204827.png" alt="image-20220126204826235" loading="lazy"></p>
<hr>
<h3 id="任务切换与特权级切换的区别">任务切换与特权级切换的区别</h3>
<p><img src="https://images.cnblogs.com/cnblogs_com/Lht1/2216671/o_220913043157_20220126205809.png" alt="image-20220126205808314" loading="lazy"></p>
<hr>
<h3 id="任务门">任务门</h3>
<p><img src="https://images.cnblogs.com/cnblogs_com/Lht1/2216671/o_220913043200_20220126210546.png" alt="image-20220126210543989" loading="lazy"></p>
<p><img src="https://images.cnblogs.com/cnblogs_com/Lht1/2216671/o_220913043205_20220126211112.png" alt="image-20220126211109269" loading="lazy"></p>
<blockquote>
<p>注意:任务是不可重入的。</p>
</blockquote>
<p>任务切换:</p>
<ul>
<li>
<p>中断引起:</p>
<blockquote>
<p>在保护模式下,产生中断后CPU会根据中断号查询中断描述符表,如果对应的中断描述符是一个任务门,那么就会进行任务切换。</p>
</blockquote>
</li>
<li>
<p>使用远过程调用指令引起:</p>
<blockquote>
<p>在使用远过程调用指令时,CPU会去GDT中查找对应的段描述符,如果该描述符为任务门描述符,则会发起任务切换。</p>
</blockquote>
</li>
</ul>
<hr>
<h3 id="页目录项和页表项的结构">页目录项和页表项的结构</h3>
<p><img src="https://images.cnblogs.com/cnblogs_com/Lht1/2216671/o_220913043209_20220127171115.png" alt="image-20220127171112427" loading="lazy"></p>
<p>参数解释:</p>
<table>
<thead>
<tr>
<th>参数</th>
<th>含义</th>
</tr>
</thead>
<tbody>
<tr>
<td>P</td>
<td>该页表/页是否存在于内存中,1存在,0不存在</td>
</tr>
<tr>
<td>RW</td>
<td>读写位,0该页只读,1可读可写</td>
</tr>
<tr>
<td>US</td>
<td>用户/管理位,1允许所有特权级访问,0只允许特权级为0、1、2的程序访问</td>
</tr>
<tr>
<td>PWT</td>
<td>页级通写位,与高速缓存相关</td>
</tr>
<tr>
<td>PCD</td>
<td>页级高速缓存禁止位</td>
</tr>
<tr>
<td>A</td>
<td>访问位,表示该页是否被访问过</td>
</tr>
<tr>
<td>D</td>
<td>脏位,表示该页是否被写过</td>
</tr>
<tr>
<td>PAT</td>
<td>页属性表支持位,与高速缓存相关</td>
</tr>
<tr>
<td>G</td>
<td>全局位,表示该页是否为全局性的,如果是,则一直保留在高速缓存中</td>
</tr>
<tr>
<td>AVL</td>
<td>被处理器忽略,软件可使用</td>
</tr>
</tbody>
</table>
<hr>
<h3 id="cr3寄存器的内容">CR3寄存器的内容</h3>
<p><img src="https://images.cnblogs.com/cnblogs_com/Lht1/2216671/o_220913043213_20220127175859.png" alt="image-20220127175857850" loading="lazy"></p>
<hr>
<h3 id="bts指令">bts指令</h3>
<p>将指定位置的bit设置为1,并将其旧值设置到eflags寄存器的CF位中。</p>
<p>格式:</p>
<pre><code class="language-assembly">bts r/m16, r16
bts r/m32, r32
</code></pre>
<hr>
<h3 id="保护模式下的中断和异常向量分配">保护模式下的中断和异常向量分配</h3>
<p>在实模式下,是由中断向量表定义了中断的入口地址(位于内存最低端的1KB),而保护模式下,是由中断描述符表(IDT)定义了中断的入口。(由idtr寄存器指定了中断描述符表所在位置)</p>
<p>中断描述符表里面保存了中断门、陷阱门和任务门。</p>
<p><img src="https://images.cnblogs.com/cnblogs_com/Lht1/2216671/o_220913043219_20220127203911.png" alt="image-20220127203910140" loading="lazy"></p>
<p>中断门、陷阱门:</p>
<p><img src="https://images.cnblogs.com/cnblogs_com/Lht1/2216671/o_220913043230_20220127204618.png" alt="image-20220127204617586" loading="lazy"></p>
<p>中断描述符寄存器:</p>
<p><img src="https://images.cnblogs.com/cnblogs_com/Lht1/2216671/o_220913043223_20220127204551.png" alt="image-20220127204549991" loading="lazy"></p>
<p>保护模式下的中断处理过程:</p>
<p><img src="https://images.cnblogs.com/cnblogs_com/Lht1/2216671/o_220913043235_20220127204826.png" alt="image-20220127204822859" loading="lazy"></p>
<p>保护模式下通过中断实现任务切换:</p>
<p><img src="https://images.cnblogs.com/cnblogs_com/Lht1/2216671/o_220913043238_20220127205128.png" alt="image-20220127205126792" loading="lazy"></p>
<hr>
<h3 id="bound指令">bound指令</h3>
<p>用于检查数组是否超出索引(这个数组是指源操作数所指向的内存单元里面存放了上限和下限,大小为双字)</p>
<p>格式:</p>
<pre><code class="language-assembly">bound r16, m16
bound r32, m32
</code></pre>
<hr>
<h3 id="ud2指令">ud2指令</h3>
<p>该指令无操作数,执行该指令会引发一个无效操作码异常。</p>
<p>格式:</p>
<pre><code class="language-assembly">ud2
</code></pre><br><br>
来源:https://www.cnblogs.com/yghr/p/15851258.html
頁: [1]
查看完整版本: 《x86汇编语言:从实模式到保护模式》笔记