76 ;如下用簡單的示例來幫助闡述32位保護模式下的堆棧操做 77 mov cx,00000000000_11_000B ;加載堆棧段選擇子 78 mov ss,cx 79 mov esp,0x7c00
第77~79行用來初始保護模式下的棧。棧段描述符是GDT中第3個(從0開始數)描述符,這個描述符的線性基地址是0x0000_0000,段界限是0x0000_7a00,粒度是字節,B=1,屬於可讀可寫、向下擴展的數據段。學習
我在博文數據段描述符和代碼段描述符(一)——《x86彙編語言:從實模式到保護模式》讀書筆記10中已經說過,spa
對於向上擴展的段(E=0),邏輯地址中的偏移值範圍能夠從0到(界限值*粒度);.net
對於向下擴展的段(E=1),邏輯地址中的偏移範圍能夠從(界限值*粒度)到0xFFFF(當B=0時)或者0xFFFF_FFFF(當B=1時)。指針
因此,對於描述符中的棧段,偏移範圍是0x0000_7a00~0xffff_ffff. 仔細琢磨一下,這和咱們的想法不是那麼一致,由於代碼79行,令ESP的初值是0x7c00,也就是說,咱們本打算定義一個偏移範圍是0x0000_7a00~0x0000_7c00的棧段。code
由於線性基地址是0x0000_0000,也就是說描述符定義的棧段,實際能夠訪問的物理空間是0x0000_7a00~0xffff_ffff,可是咱們卻但願這個棧能夠訪問的物理空間是0x0000_7a00~0x0000_7c00。示意圖(根據原書的圖11-14改編而成)以下圖所示。雖然這個棧不完美,可是不用擔憂,咱們會在後面的學習中用更好的方法來建立棧。blog
隱式的棧操做(如push、pop、call、ret、iret等)涉及兩個段,一個是指令所在的代碼段,一個是棧段。以前的博文咱們說過,對於可執行代碼段,字符串
D=1:默認是32位地址和32位或8位的操做數get
D=0:默認是16位地址和16位或8位的操做數it
注意:指令前綴0x66能夠用來選擇非默認值的操做數大小;前綴0x67能夠用來選擇非默認值的地址大小class
對於棧段,
B=1:棧指針使用ESP
B=0:棧指針使用SP
就本文的實驗代碼,其代碼段描述符的D位是1,其棧段描述符的B位也是1. 因此,當進行隱式的棧操做時,默認是32位操做數(好比壓棧的時候,壓入的是雙字),且用ESP進行操做。
因此,下面的代碼就用來驗證這個事實。
81 mov ebp,esp ;保存堆棧指針 82 push byte '.' ;壓入當即數(字節) 83 84 sub ebp,4 85 cmp ebp,esp ;判斷壓入當即數時,ESP是否減4 86 jnz ghalt 87 pop eax 88 mov [0x1e],al ;顯示句點 89 90 ghalt: 91 hlt ;已經禁止中斷,將不會被喚醒
在閱讀這段代碼的時候,我多少有點懷疑:書上說的究竟是不是真的?我想經過實踐來檢驗:探究一下PUSH指令在16位模式和32位模式下的執行規律。通過一番折騰,終於有告終果。請參考個人博文 16位模式/32位模式下PUSH指令探究——《x86彙編語言:從實模式到保護模式》讀書筆記16
第81行,複製esp的值給ebp;第82行,壓入一個字節(byte關鍵字不能省略);理論上,把ebp的值減去4後(第84行),應該和此時esp的值相等。爲了證實這一點,第85行比較ebp和esp的值,若是不相等,就跳轉到91行執行停機指令;若是相等,就把字符「.」顯示在以前的字符串後面。
OK,第11章的內容已經學習完了。最後咱們看一下代碼的運行結果吧,結果就是在屏幕的左上角顯示「Protect mode OK.」
(完)