硬盘和显卡的访问与控制(二)——《x86汇编语言:从实模式到保护模式》读书笔记02
上一篇博文我們講了如何看到實(shí)驗(yàn)結(jié)果,這篇博文我們著重分析源代碼。
書(shū)中作者為了說(shuō)明原理,約定了一種比較簡(jiǎn)單地用戶(hù)程序頭部格式,示意圖如下(我參考原書(shū)圖8-15繪制的,左邊的數(shù)字表示偏移地址):
所以,如果用戶(hù)程序要利用本章的源碼c08_mbr.asm生成的加載器來(lái)加載的話(huà),就應(yīng)該遵循這種頭部格式。
下面我們講解源碼c08_mbr.asm(粘貼的源代碼不一定和配書(shū)的代碼完全一樣,因?yàn)橛行┑胤轿壹恿俗⑨?#xff09;
<code class="hljs avrasm has-numbering"> <span class="hljs-comment">;代碼清單8-1</span><span class="hljs-comment">;文件名:c08_mbr.asm</span><span class="hljs-comment">;文件說(shuō)明:硬盤(pán)主引導(dǎo)扇區(qū)代碼(加載程序) </span><span class="hljs-comment">;創(chuàng)建日期:2011-5-5 18:17</span>app_lba_start equ <span class="hljs-number">100</span> <span class="hljs-comment">;聲明常數(shù)(用戶(hù)程序起始邏輯扇區(qū)號(hào))</span><span class="hljs-comment">;常數(shù)的聲明不會(huì)占用匯編地址</span>SECTION mbr align=<span class="hljs-number">16</span> vstart=<span class="hljs-number">0x7c00</span> <span class="hljs-comment">;設(shè)置堆棧段和棧指針 </span><span class="hljs-keyword">mov</span> ax,<span class="hljs-number">0</span> <span class="hljs-keyword">mov</span> ss,ax<span class="hljs-keyword">mov</span> sp,ax<span class="hljs-keyword">mov</span> ax,[cs:phy_base] <span class="hljs-comment">;計(jì)算用于加載用戶(hù)程序的邏輯段地址 </span><span class="hljs-keyword">mov</span> dx,[cs:phy_base+<span class="hljs-number">0x02</span>]<span class="hljs-keyword">mov</span> bx,<span class="hljs-number">16</span> div bx <span class="hljs-keyword">mov</span> ds,ax <span class="hljs-comment">;令DS和ES指向該段以進(jìn)行操作</span><span class="hljs-keyword">mov</span> es,ax <span class="hljs-comment">;以下讀取程序的起始部分 </span>xor di,di<span class="hljs-keyword">mov</span> si,app_lba_start <span class="hljs-comment">;程序在硬盤(pán)上的起始邏輯扇區(qū)號(hào) </span>xor bx,bx <span class="hljs-comment">;加載到DS:0x0000處 </span><span class="hljs-keyword">call</span> read_hard_disk_0<span class="hljs-comment">;以下判斷整個(gè)程序有多大</span><span class="hljs-keyword">mov</span> dx,[<span class="hljs-number">2</span>] <span class="hljs-comment">;曾經(jīng)把dx寫(xiě)成了ds,花了二十分鐘排錯(cuò) </span><span class="hljs-keyword">mov</span> ax,[<span class="hljs-number">0</span>]<span class="hljs-keyword">mov</span> bx,<span class="hljs-number">512</span> <span class="hljs-comment">;512字節(jié)每扇區(qū)</span>div bxcmp dx,<span class="hljs-number">0</span>jnz <span class="hljs-localvars">@1</span> <span class="hljs-comment">;未除盡,因此結(jié)果比實(shí)際扇區(qū)數(shù)少1 </span><span class="hljs-keyword">dec</span> ax <span class="hljs-comment">;已經(jīng)讀了一個(gè)扇區(qū),扇區(qū)總數(shù)減1 </span><span class="hljs-localvars">@1</span>:cmp ax,<span class="hljs-number">0</span> <span class="hljs-comment">;考慮實(shí)際長(zhǎng)度小于等于512個(gè)字節(jié)的情況 </span>jz direct<span class="hljs-comment">;讀取剩余的扇區(qū)</span><span class="hljs-keyword">push</span> ds <span class="hljs-comment">;以下要用到并改變DS寄存器 </span><span class="hljs-keyword">mov</span> cx,ax <span class="hljs-comment">;循環(huán)次數(shù)(剩余扇區(qū)數(shù))</span><span class="hljs-localvars">@2</span>:<span class="hljs-keyword">mov</span> ax,ds<span class="hljs-keyword">add</span> ax,<span class="hljs-number">0x20</span> <span class="hljs-comment">;得到下一個(gè)以512字節(jié)為邊界的段地址</span><span class="hljs-keyword">mov</span> ds,ax xor bx,bx <span class="hljs-comment">;每次讀時(shí),偏移地址始終為0x0000 </span><span class="hljs-keyword">inc</span> si <span class="hljs-comment">;下一個(gè)邏輯扇區(qū) </span><span class="hljs-keyword">call</span> read_hard_disk_0loop <span class="hljs-localvars">@2</span> <span class="hljs-comment">;循環(huán)讀,直到讀完整個(gè)功能程序 </span><span class="hljs-keyword">pop</span> ds <span class="hljs-comment">;恢復(fù)數(shù)據(jù)段基址到用戶(hù)程序頭部段 </span><span class="hljs-comment">;計(jì)算入口點(diǎn)代碼段基址 </span>direct:<span class="hljs-keyword">mov</span> dx,[<span class="hljs-number">0x08</span>]<span class="hljs-keyword">mov</span> ax,[<span class="hljs-number">0x06</span>]<span class="hljs-keyword">call</span> calc_segment_base<span class="hljs-keyword">mov</span> [<span class="hljs-number">0x06</span>],ax <span class="hljs-comment">;回填修正后的入口點(diǎn)代碼段基址 </span><span class="hljs-comment">;開(kāi)始處理段重定位表</span><span class="hljs-keyword">mov</span> cx,[<span class="hljs-number">0x0a</span>] <span class="hljs-comment">;需要重定位的項(xiàng)目數(shù)量</span><span class="hljs-keyword">mov</span> bx,<span class="hljs-number">0x0c</span> <span class="hljs-comment">;重定位表首地址</span>realloc:<span class="hljs-keyword">mov</span> dx,[bx+<span class="hljs-number">0x02</span>] <span class="hljs-comment">;32位地址的高16位 </span><span class="hljs-keyword">mov</span> ax,[bx]<span class="hljs-keyword">call</span> calc_segment_base<span class="hljs-keyword">mov</span> [bx],ax <span class="hljs-comment">;回填段的基址</span><span class="hljs-keyword">add</span> bx,<span class="hljs-number">4</span> <span class="hljs-comment">;下一個(gè)重定位項(xiàng)(每項(xiàng)占4個(gè)字節(jié)) </span>loop realloc <span class="hljs-keyword">jmp</span> far [<span class="hljs-number">0x04</span>] <span class="hljs-comment">;轉(zhuǎn)移到用戶(hù)程序 </span><span class="hljs-comment">;-------------------------------------------------------------------------------</span> <span class="hljs-label">read_hard_disk_0:</span> <span class="hljs-comment">;從硬盤(pán)讀取一個(gè)邏輯扇區(qū)</span><span class="hljs-comment">;輸入:DI:SI=起始邏輯扇區(qū)號(hào)</span><span class="hljs-comment">; DS:BX=目標(biāo)緩沖區(qū)地址</span><span class="hljs-keyword">push</span> ax<span class="hljs-keyword">push</span> bx<span class="hljs-keyword">push</span> cx<span class="hljs-keyword">push</span> dx<span class="hljs-keyword">mov</span> dx,<span class="hljs-number">0x1f2</span><span class="hljs-keyword">mov</span> al,<span class="hljs-number">1</span><span class="hljs-keyword">out</span> dx,al <span class="hljs-comment">;讀取的扇區(qū)數(shù)</span><span class="hljs-keyword">inc</span> dx <span class="hljs-comment">;0x1f3</span><span class="hljs-keyword">mov</span> ax,si<span class="hljs-keyword">out</span> dx,al <span class="hljs-comment">;LBA地址7~0</span><span class="hljs-keyword">inc</span> dx <span class="hljs-comment">;0x1f4</span><span class="hljs-keyword">mov</span> al,ah<span class="hljs-keyword">out</span> dx,al <span class="hljs-comment">;LBA地址15~8</span><span class="hljs-keyword">inc</span> dx <span class="hljs-comment">;0x1f5</span><span class="hljs-keyword">mov</span> ax,di<span class="hljs-keyword">out</span> dx,al <span class="hljs-comment">;LBA地址23~16</span><span class="hljs-keyword">inc</span> dx <span class="hljs-comment">;0x1f6</span><span class="hljs-keyword">mov</span> al,<span class="hljs-number">0xe0</span> <span class="hljs-comment">;LBA28模式,主盤(pán)</span><span class="hljs-keyword">or</span> al,ah <span class="hljs-comment">;LBA地址27~24</span><span class="hljs-keyword">out</span> dx,al<span class="hljs-keyword">inc</span> dx <span class="hljs-comment">;0x1f7</span><span class="hljs-keyword">mov</span> al,<span class="hljs-number">0x20</span> <span class="hljs-comment">;讀命令</span><span class="hljs-keyword">out</span> dx,al<span class="hljs-preprocessor">.waits</span>:<span class="hljs-keyword">in</span> al,dx<span class="hljs-keyword">and</span> al,<span class="hljs-number">0x88</span>cmp al,<span class="hljs-number">0x08</span>jnz <span class="hljs-preprocessor">.waits</span> <span class="hljs-comment">;不忙,且硬盤(pán)已準(zhǔn)備好數(shù)據(jù)傳輸 </span><span class="hljs-keyword">mov</span> cx,<span class="hljs-number">256</span> <span class="hljs-comment">;總共要讀取的字?jǐn)?shù)</span><span class="hljs-keyword">mov</span> dx,<span class="hljs-number">0x1f0</span><span class="hljs-preprocessor">.readw</span>:<span class="hljs-keyword">in</span> ax,dx<span class="hljs-keyword">mov</span> [bx],ax<span class="hljs-keyword">add</span> bx,<span class="hljs-number">2</span>loop <span class="hljs-preprocessor">.readw</span><span class="hljs-keyword">pop</span> dx<span class="hljs-keyword">pop</span> cx<span class="hljs-keyword">pop</span> bx<span class="hljs-keyword">pop</span> ax<span class="hljs-keyword">ret</span><span class="hljs-comment">;-------------------------------------------------------------------------------</span> <span class="hljs-label">calc_segment_base:</span> <span class="hljs-comment">;計(jì)算16位段地址</span><span class="hljs-comment">;輸入:DX:AX=32位物理地址</span><span class="hljs-comment">;返回:AX=16位段基地址 </span><span class="hljs-keyword">push</span> dx <span class="hljs-keyword">add</span> ax,[cs:phy_base]<span class="hljs-keyword">adc</span> dx,[cs:phy_base+<span class="hljs-number">0x02</span>]shr ax,<span class="hljs-number">4</span><span class="hljs-keyword">ror</span> dx,<span class="hljs-number">4</span><span class="hljs-keyword">and</span> dx,<span class="hljs-number">0xf000</span><span class="hljs-keyword">or</span> ax,dx<span class="hljs-keyword">pop</span> dx<span class="hljs-keyword">ret</span><span class="hljs-comment">;-------------------------------------------------------------------------------</span>phy_base dd <span class="hljs-number">0x10000</span> <span class="hljs-comment">;用戶(hù)程序被加載的物理起始地址</span>times <span class="hljs-number">510</span>-($-$$) db <span class="hljs-number">0</span>db <span class="hljs-number">0x55</span>,<span class="hljs-number">0xaa</span></code><ul style="" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li><li>23</li><li>24</li><li>25</li><li>26</li><li>27</li><li>28</li><li>29</li><li>30</li><li>31</li><li>32</li><li>33</li><li>34</li><li>35</li><li>36</li><li>37</li><li>38</li><li>39</li><li>40</li><li>41</li><li>42</li><li>43</li><li>44</li><li>45</li><li>46</li><li>47</li><li>48</li><li>49</li><li>50</li><li>51</li><li>52</li><li>53</li><li>54</li><li>55</li><li>56</li><li>57</li><li>58</li><li>59</li><li>60</li><li>61</li><li>62</li><li>63</li><li>64</li><li>65</li><li>66</li><li>67</li><li>68</li><li>69</li><li>70</li><li>71</li><li>72</li><li>73</li><li>74</li><li>75</li><li>76</li><li>77</li><li>78</li><li>79</li><li>80</li><li>81</li><li>82</li><li>83</li><li>84</li><li>85</li><li>86</li><li>87</li><li>88</li><li>89</li><li>90</li><li>91</li><li>92</li><li>93</li><li>94</li><li>95</li><li>96</li><li>97</li><li>98</li><li>99</li><li>100</li><li>101</li><li>102</li><li>103</li><li>104</li><li>105</li><li>106</li><li>107</li><li>108</li><li>109</li><li>110</li><li>111</li><li>112</li><li>113</li><li>114</li><li>115</li><li>116</li><li>117</li><li>118</li><li>119</li><li>120</li><li>121</li><li>122</li><li>123</li><li>124</li><li>125</li><li>126</li><li>127</li><li>128</li><li>129</li><li>130</li><li>131</li><li>132</li><li>133</li><li>134</li><li>135</li><li>136</li><li>137</li><li>138</li><li>139</li><li>140</li><li>141</li><li>142</li><li>143</li><li>144</li><li>145</li><li>146</li><li>147</li><li>148</li><li>149</li><li>150</li><li>151</li><li>152</li><li>153</li><li>154</li></ul><ul style="" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li><li>23</li><li>24</li><li>25</li><li>26</li><li>27</li><li>28</li><li>29</li><li>30</li><li>31</li><li>32</li><li>33</li><li>34</li><li>35</li><li>36</li><li>37</li><li>38</li><li>39</li><li>40</li><li>41</li><li>42</li><li>43</li><li>44</li><li>45</li><li>46</li><li>47</li><li>48</li><li>49</li><li>50</li><li>51</li><li>52</li><li>53</li><li>54</li><li>55</li><li>56</li><li>57</li><li>58</li><li>59</li><li>60</li><li>61</li><li>62</li><li>63</li><li>64</li><li>65</li><li>66</li><li>67</li><li>68</li><li>69</li><li>70</li><li>71</li><li>72</li><li>73</li><li>74</li><li>75</li><li>76</li><li>77</li><li>78</li><li>79</li><li>80</li><li>81</li><li>82</li><li>83</li><li>84</li><li>85</li><li>86</li><li>87</li><li>88</li><li>89</li><li>90</li><li>91</li><li>92</li><li>93</li><li>94</li><li>95</li><li>96</li><li>97</li><li>98</li><li>99</li><li>100</li><li>101</li><li>102</li><li>103</li><li>104</li><li>105</li><li>106</li><li>107</li><li>108</li><li>109</li><li>110</li><li>111</li><li>112</li><li>113</li><li>114</li><li>115</li><li>116</li><li>117</li><li>118</li><li>119</li><li>120</li><li>121</li><li>122</li><li>123</li><li>124</li><li>125</li><li>126</li><li>127</li><li>128</li><li>129</li><li>130</li><li>131</li><li>132</li><li>133</li><li>134</li><li>135</li><li>136</li><li>137</li><li>138</li><li>139</li><li>140</li><li>141</li><li>142</li><li>143</li><li>144</li><li>145</li><li>146</li><li>147</li><li>148</li><li>149</li><li>150</li><li>151</li><li>152</li><li>153</li><li>154</li></ul>app_lba_start equ 100 ;聲明常數(shù)(用戶(hù)程序起始邏輯扇區(qū)號(hào))
這句話(huà)作者假定用戶(hù)程序從硬盤(pán)第100扇區(qū)開(kāi)始。所以在我們把這個(gè)源文件對(duì)應(yīng)的.bin文件寫(xiě)入虛擬硬盤(pán)的時(shí)候,要從邏輯扇區(qū)100開(kāi)始寫(xiě)。
equ 類(lèi)似于C語(yǔ)言中的#define,用來(lái)定義一個(gè)常量。
一般使用格式:
符號(hào)名 EQU 表達(dá)式
作用是左邊的符號(hào)名代表右邊的表達(dá)式。
注意:不會(huì)給符號(hào)名分配存儲(chǔ)空間,符號(hào)名不能與其它符號(hào)同名,也不能被重新定義
SECTION mbr align=16 vstart=0x7c00
解釋:
NASM編譯器用SECTION或者SEGMENT來(lái)定義段。mbr是段名稱(chēng)(可以隨便起);
注意:如果整個(gè)程序都沒(méi)有段定義語(yǔ)句,那么整個(gè)程序自成一個(gè)段(這點(diǎn)好像和MASM不同哦!);
align=16 表示16字節(jié)對(duì)齊;
vstart=0x7c00,關(guān)于這個(gè),我們就不得不多說(shuō)幾句了。
==================插敘部分================
匯編地址以及標(biāo)號(hào)的本質(zhì):
1. 所謂匯編地址,就是編譯器給源程序中每條指令定義的地址,由于編譯后的程序可以在內(nèi)存中浮動(dòng)(即可以裝載在內(nèi)存中的任意位置),因此直接用絕對(duì)地址(20位的實(shí)模式下的物理內(nèi)存地址)來(lái)給源程序中的指令定位的話(huà)將不利于程序在內(nèi)存中的浮動(dòng);
2. 匯編地址定位規(guī)則:
(1)一般規(guī)則:
i. 如果在沒(méi)有使用特殊指令的一般情況下(特別是vstart指令),整個(gè)源程序中第一條指令的匯編地址為0,之后所有指令的匯編地址都是相對(duì)于整個(gè)源程序第一條指令的偏移地址,即使程序中分了很多段也是如此。在這種情況下,如果將整個(gè)源程序看做一個(gè)段的話(huà)則匯編地址就是段內(nèi)偏移地址;
ii. 在NASM中,所有的標(biāo)號(hào)實(shí)質(zhì)上就是其所在處指令的匯編地址,在編譯后會(huì)將所有標(biāo)號(hào)都替換成該匯編地址值(即立即數(shù));
(2)特殊規(guī)則:
i. 如果在定義段的時(shí)候使用了vstart偽指令,比如
“section my_segment vstart=15”,
則會(huì)提醒匯編器,該段起始指令的匯編地址是15,段內(nèi)的其它指令的匯編地址都是距該段起始指令地址的偏移量加上15;因此,vstart偽指令就是指定段的起始匯編地址;如果vstart=0,則段內(nèi)的匯編地址就是段內(nèi)的偏移地址!(這種手法經(jīng)常使用!)
ii. 使用NASM規(guī)則的標(biāo)準(zhǔn)段,是指section .data、section .text、section .bss,這三種標(biāo)準(zhǔn)段都默認(rèn)包含有vstart=0,因此段內(nèi)的指令以及標(biāo)號(hào)的匯編地址都是段內(nèi)偏移地址,并且在加載程序的時(shí)候會(huì)自動(dòng)使cs指向.text,ds指向.bss,es指向.data,而無(wú)需人手工執(zhí)行對(duì)段寄存器賦值的步驟,而對(duì)于i.中的定義段的方式則沒(méi)有這種自動(dòng)的步驟,需要親手對(duì)段寄存器進(jìn)行賦值(是這樣嗎?從網(wǎng)上搜來(lái)的,我不能肯定。)
(3) 引用標(biāo)號(hào):
i. 和MASM不一樣的是NASM大大簡(jiǎn)化了對(duì)標(biāo)號(hào)的引用,不需要再用seg和offset對(duì)標(biāo)號(hào)取段地址和偏移地址了;
ii. 在NASM中,標(biāo)號(hào)就是一個(gè)立即數(shù),而這個(gè)立即數(shù)就是匯編地址;
iii. 在NASM中不再有MASM中數(shù)據(jù)標(biāo)號(hào)的概念,也就不存在什么arr[5]之類(lèi)的內(nèi)存尋址形式了!
iv. 在NASM中所有出現(xiàn)標(biāo)號(hào)的地方都會(huì)用標(biāo)號(hào)的匯編地址替換,因此諸如mov ax, tag之類(lèi)的指令,僅僅就是將一個(gè)立即數(shù)(tag的匯編地址)傳送至ax而已,而不是取tag地址內(nèi)的數(shù)據(jù)了!如果要取標(biāo)號(hào)處內(nèi)存中的數(shù)據(jù)就必須使用[ ](類(lèi)似C語(yǔ)言中的指針運(yùn)算符*);
==================插敘結(jié)束================
處理器加電或者復(fù)位后,BIOS會(huì)執(zhí)行硬件檢測(cè)和初始化程序,如果沒(méi)有錯(cuò)誤,接下來(lái)就會(huì)進(jìn)行操作系統(tǒng)引導(dǎo)。
BIOS會(huì)根據(jù)CMOS(一塊可讀寫(xiě)的RAM芯片,保存系統(tǒng)當(dāng)前的硬件配置和用戶(hù)的設(shè)定參數(shù))里記錄的啟動(dòng)順序逐個(gè)地來(lái)嘗試加載啟動(dòng)代碼。
具體的過(guò)程是BIOS將磁盤(pán)的第一扇區(qū)(磁盤(pán)最開(kāi)始的512字節(jié),也就是主引導(dǎo)扇區(qū))載入內(nèi)存,放在0X0000:0X7C00處,然后檢查這個(gè)扇區(qū)的最后兩個(gè)字節(jié)是不是“0x55AA”,如果是則認(rèn)為這是一個(gè)有效的啟動(dòng)扇區(qū),如果不是就會(huì)嘗試下一個(gè)啟動(dòng)介質(zhì);
如果主引導(dǎo)扇區(qū)有效,則以一個(gè)段間轉(zhuǎn)移指令
jmp 0x0000:0x7c00
跳過(guò)去繼續(xù)執(zhí)行;
如果所有的啟動(dòng)介質(zhì)都判斷過(guò)后仍然沒(méi)有找到可啟動(dòng)的程序,那么BIOS會(huì)給出錯(cuò)誤提示。
所以,代碼中的vstart=0x7c00不是空穴來(lái)風(fēng),而是根據(jù)代碼被加載的實(shí)際位置決定的。
當(dāng)這段程序剛被加載到內(nèi)存后,
CS=0x0000, IP=0x7c00
如上圖所示,假設(shè)不寫(xiě)vstart=0x7c00,那么標(biāo)號(hào)“number”的偏移地址就從程序頭(認(rèn)為是0)開(kāi)始算起,為0x012e;
但是實(shí)際上“number”的段內(nèi)偏移地址是0x7d2e(0x012e+0x7c00=0x7d2e)!
為了修正這個(gè)偏移地址的差值,于是有vstart=0x7c00,也就是說(shuō)段內(nèi)所有指令的匯編地址都在原來(lái)的基礎(chǔ)上加上0x7c00.
這里還要再補(bǔ)充一點(diǎn),如果看這個(gè)源文件對(duì)應(yīng)的列表文件,是看不出來(lái)偏移地址被加了0x7c00的。
列表文件的一個(gè)截圖如下:
看到了嗎?第一條指令的匯編地址,還是從0開(kāi)始的!
而且
SECTION mbr align=16 vstart=0x7c00
這句話(huà)還是出現(xiàn)在了列表文件里。
我的理解是,列表文件僅僅是對(duì)源碼的第一遍掃描吧。在后面的掃描中,0x7c00就起作用了。
舉個(gè)例子吧,
上圖有一行
16 00000007 2EA1[CA00] mov ax,[cs:phy_base]
列表文件的末尾有
151 000000CA 00000100 phy_base dd 0x10000
也就是說(shuō) phy_base 這個(gè)標(biāo)號(hào)的匯編地址就是00CA(這時(shí)候7C00還沒(méi)有起作用)
我們?cè)倏匆幌戮幾g后的二進(jìn)制文件
在偏移為0x07的地方,對(duì)應(yīng)的指令碼是
2EA1CA7C
注意到其中的CA7C(低字節(jié)在前面)了嗎? 這個(gè)就是00CA+7C00=7CCA的結(jié)果啊!
我們繼續(xù)看代碼,
;設(shè)置堆棧段和棧指針
mov ax,0
mov ss,ax
mov sp,ax
定義棧需要兩個(gè)連續(xù)的步驟,即初始化SS和SP.
*——————-小貼士—————-
原書(shū)P158上方:處理器在設(shè)計(jì)的時(shí)候就規(guī)定,當(dāng)遇到修改段寄存器SS的指令時(shí),在這條指令和下一條指令執(zhí)行完畢期間,禁止中斷,以此來(lái)保護(hù)棧。也就是說(shuō),我們應(yīng)該在修改SS的指令之后,緊接著一條修改SP的指令。
——————————————–*
因?yàn)橐呀?jīng)設(shè)置了SP=SS=0,所以第一次執(zhí)行PUSH指令時(shí),先把SP減2,即0x0000-0x000=0xFFFE(借位被忽略);然后把內(nèi)容送入SS:SP指向的內(nèi)存單元處。如下圖所示(文章中畫(huà)的只是示意圖,不是按照比例畫(huà)的,湊合看)
代碼的末尾部分有
phy_base dd 0x10000 ;用戶(hù)程序被加載的物理起始地址
也就是說(shuō)作者安排把用戶(hù)程序加載到物理內(nèi)存0x10000處,(我們完全可以修改成別的16字節(jié)對(duì)齊的地址,只要把用戶(hù)程序加載到一個(gè)空閑的地方就可以。)
上面這幾行的意思是根據(jù)物理地址計(jì)算出邏輯段地址,[DX:AX]是被除數(shù),BX的內(nèi)容是除數(shù)(16),計(jì)算結(jié)果在A(yíng)X(對(duì)于本程序,結(jié)果就是0x1000)中。然后令DS和ES都指向這個(gè)段。
這段代碼的最后調(diào)用了過(guò)程 read_hard_disk_0,我們看一下過(guò)程調(diào)用的代碼,我在代碼中加了一些注釋:
<code class="hljs avrasm has-numbering"><span class="hljs-label">read_hard_disk_0:</span> <span class="hljs-comment">;從硬盤(pán)讀取一個(gè)邏輯扇區(qū)</span><span class="hljs-comment">;輸入:DI:SI=起始邏輯扇區(qū)號(hào)</span><span class="hljs-comment">; DS:BX=目標(biāo)緩沖區(qū)地址</span><span class="hljs-comment">;使用LBA28尋址方式</span><span class="hljs-keyword">push</span> ax<span class="hljs-keyword">push</span> bx<span class="hljs-keyword">push</span> cx<span class="hljs-keyword">push</span> dx<span class="hljs-comment">; 用到的寄存器壓棧保存</span><span class="hljs-keyword">mov</span> dx,<span class="hljs-number">0x1f2</span><span class="hljs-keyword">mov</span> al,<span class="hljs-number">1</span><span class="hljs-keyword">out</span> dx,al <span class="hljs-comment">;讀取的扇區(qū)數(shù)為1</span><span class="hljs-keyword">inc</span> dx <span class="hljs-comment">;0x1f3</span><span class="hljs-keyword">mov</span> ax,si<span class="hljs-keyword">out</span> dx,al <span class="hljs-comment">;LBA地址7~0</span><span class="hljs-keyword">inc</span> dx <span class="hljs-comment">;0x1f4</span><span class="hljs-keyword">mov</span> al,ah<span class="hljs-keyword">out</span> dx,al <span class="hljs-comment">;LBA地址15~8</span><span class="hljs-keyword">inc</span> dx <span class="hljs-comment">;0x1f5</span><span class="hljs-keyword">mov</span> ax,di<span class="hljs-keyword">out</span> dx,al <span class="hljs-comment">;LBA地址23~16</span><span class="hljs-keyword">inc</span> dx <span class="hljs-comment">;0x1f6</span><span class="hljs-keyword">mov</span> al,<span class="hljs-number">0xe0</span> <span class="hljs-comment">;LBA28模式,主盤(pán)</span><span class="hljs-keyword">or</span> al,ah <span class="hljs-comment">;LBA地址27~24</span><span class="hljs-keyword">out</span> dx,al<span class="hljs-keyword">inc</span> dx <span class="hljs-comment">;0x1f7</span><span class="hljs-keyword">mov</span> al,<span class="hljs-number">0x20</span> <span class="hljs-comment">;讀命令</span><span class="hljs-keyword">out</span> dx,al<span class="hljs-preprocessor">.waits</span>:<span class="hljs-keyword">in</span> al,dx<span class="hljs-keyword">and</span> al,<span class="hljs-number">0x88</span>cmp al,<span class="hljs-number">0x08</span>jnz <span class="hljs-preprocessor">.waits</span> <span class="hljs-comment">;不忙,且硬盤(pán)已準(zhǔn)備好數(shù)據(jù)傳輸 </span><span class="hljs-keyword">mov</span> cx,<span class="hljs-number">256</span> <span class="hljs-comment">;總共要讀取的字?jǐn)?shù)</span><span class="hljs-keyword">mov</span> dx,<span class="hljs-number">0x1f0</span><span class="hljs-preprocessor">.readw</span>:<span class="hljs-keyword">in</span> ax,dx<span class="hljs-keyword">mov</span> [bx],ax<span class="hljs-keyword">add</span> bx,<span class="hljs-number">2</span>loop <span class="hljs-preprocessor">.readw</span><span class="hljs-keyword">pop</span> dx<span class="hljs-keyword">pop</span> cx<span class="hljs-keyword">pop</span> bx<span class="hljs-keyword">pop</span> ax<span class="hljs-keyword">ret</span> </code><ul style="" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li><li>23</li><li>24</li><li>25</li><li>26</li><li>27</li><li>28</li><li>29</li><li>30</li><li>31</li><li>32</li><li>33</li><li>34</li><li>35</li><li>36</li><li>37</li><li>38</li><li>39</li><li>40</li><li>41</li><li>42</li><li>43</li><li>44</li><li>45</li><li>46</li><li>47</li><li>48</li><li>49</li><li>50</li><li>51</li><li>52</li><li>53</li><li>54</li><li>55</li></ul><ul style="" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li><li>23</li><li>24</li><li>25</li><li>26</li><li>27</li><li>28</li><li>29</li><li>30</li><li>31</li><li>32</li><li>33</li><li>34</li><li>35</li><li>36</li><li>37</li><li>38</li><li>39</li><li>40</li><li>41</li><li>42</li><li>43</li><li>44</li><li>45</li><li>46</li><li>47</li><li>48</li><li>49</li><li>50</li><li>51</li><li>52</li><li>53</li><li>54</li><li>55</li></ul>要理解這段,先看下面的示意圖(參照原書(shū)圖8-11畫(huà)的)
inc dx ;0x1f6
mov al,0xe0 ;LBA28模式,主盤(pán)
or al,ah ;LBA地址27~24
out dx,al
mov al,0xe0 表示選擇LBA模式,選擇主硬盤(pán)
注意,在調(diào)用這個(gè)過(guò)程的時(shí)候,DI:SI=起始邏輯扇區(qū)號(hào),DI的低四位是有效的,高四位應(yīng)該為0,其實(shí)這里我覺(jué)得應(yīng)該加一句,
mov al,0xe0 這句后面加一句 and ah,0x0f
目的是把DI的高四位清零,萬(wàn)一調(diào)用者忘記清零了,這樣做可以防止意外發(fā)生。
當(dāng)把起始LBA扇區(qū)號(hào)設(shè)置好后,就可以發(fā)出讀命令了。上面的代碼表示向端口0x1F7寫(xiě)入0x20,請(qǐng)求讀硬盤(pán)。
接下來(lái)等待讀請(qǐng)求完成。端口0x1F7既是命令端口,也是狀態(tài)端口。部分狀態(tài)位的含義如圖:
一旦硬盤(pán)準(zhǔn)備好了,就可以讀取數(shù)據(jù)了。0x1F0是硬盤(pán)接口的數(shù)據(jù)端口,是16位的。可以連續(xù)從這個(gè)端口讀取數(shù)據(jù)。
mov cx,256
in ax,dx
這兩句話(huà)就表示讀取了一個(gè)字的數(shù)據(jù)(16位)到AX中
現(xiàn)在我們?cè)倩氐侥遣糠执a,就很容易理解了。
xor di,di ;di清零 (因?yàn)槲覀儌魅脒壿嬌葏^(qū)號(hào)是100,不超過(guò)16 bits)mov si,app_lba_start ;程序在硬盤(pán)上的起始邏輯扇區(qū)號(hào) xor bx,bx ;加載到DS:0x0000處 call read_hard_disk_0執(zhí)行到這里,內(nèi)存大概如下圖所示:
上面這段代碼是為了把剩余的用戶(hù)程序讀到內(nèi)存里(以扇區(qū)為單位)
我們分別講解。
因?yàn)橐呀?jīng)約定了用戶(hù)程序的頭部4個(gè)字節(jié)是用戶(hù)程序的總長(zhǎng)度,所以這里取總長(zhǎng)度到[dx:ax]中,把[dx:ax]除以512,就能得到有幾個(gè)扇區(qū)。dx存放余數(shù),ax存放商。
如果dx==0,那么就把a(bǔ)x減一(因?yàn)榍懊嬉呀?jīng)讀了一個(gè)扇區(qū)),繼續(xù)執(zhí)行@1;如果dx!=0,那么剩余的扇區(qū)數(shù)就是ax,然后跳到@1;
開(kāi)始執(zhí)行@1處的代碼時(shí),ax已經(jīng)保存了還要讀取的扇區(qū)數(shù),但是這個(gè)值也有可能為0,如果為0,就不用再讀取了, jz direct就可以;如果不為0,就執(zhí)行下面的代碼。
好了,如果你覺(jué)得上面說(shuō)得不夠清楚,那么看這個(gè)簡(jiǎn)單的流程圖吧:
mov ax,ds
add ax,0x20
mov ds,ax ;這三行表示調(diào)整ds的位置,讓ds指向最后讀入的塊的末尾,也就是將要讀入的塊的開(kāi)始。其他語(yǔ)句都好理解,這里就不解釋了。
接下來(lái)是處理段的重定位表。我們要修正每個(gè)表項(xiàng)的值。
為什么要修正呢?看圖就明白了。
用戶(hù)程序在編譯的時(shí)候,每個(gè)段的段地址都是相對(duì)于程序開(kāi)頭(0)計(jì)算的。但是用戶(hù)程序被加載器加到到物理地址[phy_base]的時(shí)候,相當(dāng)于每個(gè)段的物理地址都向后偏移了[phy_base],所以我們要修正這個(gè)差值。
我們看看代碼:
add ax,[cs:phy_base]
adc dx,[cs:phy_base+0x02];
這兩句其實(shí)是做了一個(gè)20位數(shù)的加法,修正后的物理地址是[dx:ax];
shr ax,4
ror dx,4
and dx,0xf000
or ax,dx;
這四句是求出段基地址(16位),也就是邏輯段地址,結(jié)果在A(yíng)X中。然后回填到原處(僅覆蓋低16位,高16位不用管)。
為什么要求出段基地址呢?因?yàn)樵谟脩?hù)程序中,對(duì)段寄存器賦值,都是從這里引用的。
只要參考本文開(kāi)頭的用戶(hù)程序頭部示意圖,上面這段代碼不難理解。
需要說(shuō)明的是 jmp far [0x04] ;這個(gè)是16位間接絕對(duì)遠(yuǎn)轉(zhuǎn)移指令。一定要使用關(guān)鍵字far。處理器執(zhí)行這條指令的時(shí)候,會(huì)訪(fǎng)問(wèn)DS所指向的數(shù)據(jù)段,從偏移地址0x04處取出兩個(gè)字(低字是偏移地址,高字是段基址),用低字代替IP的內(nèi)容,用高字代替CS的內(nèi)容,于是就可以轉(zhuǎn)移了。
總結(jié)
以上是生活随笔為你收集整理的硬盘和显卡的访问与控制(二)——《x86汇编语言:从实模式到保护模式》读书笔记02的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 硬盘和显卡的访问与控制(一)——《x86
- 下一篇: 汇编语言对显存直接输出字符串