x86架構:分頁機制和原理


  分頁是現在CPU核心的管理內存方式,網上介紹材料很多,這里不贅述,簡單介紹一下分頁的背景和原理

  1、先說說為什么要分段

  •  實模式下程序之間不隔離,互相能直接讀寫對方內存,或跳轉到其他進程的代碼運行,導致泄密、出錯,通過分段隔離不同程序代碼對不同內存單元的讀寫權限
  •    用戶程序在內存種加載的地址不確定,通過分段對程序的數據、代碼重定位,才能在運行時正確尋址(如果沒有特殊聲明,編譯器編譯后生成文件的代碼和數據都是相對文件頭開始計算偏移的)

  2、再說說為什么要分頁?

     物理內存是有限的,主流普通PC機內存也就8G~16G,除了運行os,還要盡可能多地運行用戶程序。但現代大型的用戶程序動則大幾百M,甚至幾個G,要想“同時”把這么多的用戶程序加載到內存運行該怎么辦了?

  •    CPU的分頁機制把物理內存分割成4K大小的空間,稱為“頁”。
  •    32位的windows操作系統針對每個進程,虛擬出了4GB的進程空間。對於進程來說,低2G的空間隨便用,無需任何顧忌。那么問題又來了,不同進程很有可能用到了同樣的地址,怎么防止沖突?
  •    os會根據實際情況,把虛擬地址”掛載“到適合的物理頁。對於不同的進程,代碼種即使用了同樣的地址,os也會掛載到不同的物理內存,這些對於進程來說都是透明不可見的,也不需要關心;
  •    內存的空間是有限的,為了盡量多”並發“運行進程,os會酌情把物理頁的數據存儲到磁盤的pagefile.sys文件。當進程執行需要用到時,發現物理內存沒有,此時產生缺頁異常,os負責從磁盤取回這些數據放回內存,讓進程繼續執行;
  •  分頁可以讓段基址和limit變平坦(64位已經這樣了),段僅用來鑒權,或在32位和64位之間來回切換(利用這個特性可以讓64位的os兼容32位的應用程序,也可以將32位程序的某些重要數據,比如key、密鑰、密碼之類的放在64位模式下,達到在3環下反調試、反逆向的目的,詳細的過程見這里:https://www.bilibili.com/video/BV1SJ411K7LR
  •    windwos會對頁賦予各種屬性,比如可執行,可讀寫。可人為將頁屬性更改,比如代碼所在的頁改為不可執行、不可讀,進程運行到這種頁時產生缺頁異常。此時如果hook pagefault函數,根據異常原因分別處理:如果是執行,那么把頁屬性改成可執行,替換成自己想要執行的代碼;如果是讀取異常,那么給該線性地址掛載原物理頁。這種hook能達到隱藏鈎子的目的,能在VT下過PG保護,這就是著名的shaodw walker,詳細過程可以參考這里:https://www.bilibili.com/video/BV1Hb411n7Mw

  3、核心代碼解讀

  (1)准備PDT

  •  頁目錄物理地址0x20000開始,后續會把這個地址賦值給CR3;
  •    PDE也是32位=4字節,那么PDE大小=1024*4=4096字節,剛好是一個頁,那么PDT結尾就是0x20000+0x1000=0x21000;
      ;創建系統內核的頁目錄表PDT
         ;頁目錄表清零 
         mov ecx,1024                       ;1024個目錄項PDE
         mov ebx,0x00020000                 ;頁目錄的物理地址
         xor esi,esi
  .b1:
         mov dword [es:ebx+esi],0x00000000  ;頁目錄表項清零 
         add esi,4
         loop .b1
         
         ;在頁目錄內創建指向頁目錄自己的目錄項,最后一項指向自己,那么線性地址高20位是0xFFFFF的時候,轉成物理地址就是頁目錄自己
         mov dword [es:ebx+4092],0x00020003 

         ;在頁目錄內創建與線性地址0x00000000對應的目錄項
         mov dword [es:ebx+0],0x00021003    ;寫入目錄項(頁表的物理地址和屬性)
  •  以上代碼執行完畢后,內存圖如下:分別在PDT的首位寫入兩個地址,其他的都清零,那么問題來了,為啥要分別寫這兩個數,而不是其他的數?

         

  •   先解釋一下PDT的第一項為什么會是0x00021003

    一旦開啟分頁,所有地址都會被認為是線性地址,都會經過轉換才能獲取物理地址,這是CPU的硬件機制決定的,操作系統都要遵守,無法例外。既然0x20000~0x21000這段地址已經被用於存放PDT,那么就不應該再被寫入,避免PDT被破壞,導致線性地址映射到物理地址出錯,所以物理地址必須從0x21000開始;這里把0x21000開始的地方用來存放頁表;

  •  再解釋一下最后一個PDE為什么是0x20003

   由於業務變化多端,無法在開啟分頁前全部確定最終地址,導致很多PDT要開啟分頁后再填;那么問題又來了,一旦開啟分頁,任何線性地址都要轉換才能得到物理地址,PDT也不例外,怎么讓線性地址轉換后落入0x20000~0x21000這個物理區間了?

      來分析一種特殊的地址,前20位都是1,比如0xFFFFF200. 按照10-10-12拆分,3個偏移分別0x3ff, 0x3ff乘以4后分別是 0xffc,0xffc;

      第一次轉換:0x20000+0xffc=0x20ffc,得到0x20003;后3byte是屬性,基址就是0x20000;

      第二次轉換:0x20000+0xffc=0x20ffc,得到0x20003;后3byte是屬性,基址還是0x20000; 

   最后一次轉換:0x20000 + 0x200= 0x20200,地址還是落在0x20000~0x21000區間;所以結論就是:線性地址前20位都是1,轉成物理地址會落在PDT內部,線性地址最后12位就是PDT內的偏移;通過一些巧妙的數字設置,這里把頁目錄當成頁表在用了;

   最后12位是屬性位:

   (2)正式開始分頁前最后的准備工作:初始化PET頁表,讓其映射最低端0~1MB的物理地址;實模式下低端1MB物理地址都有用了,所以必須先把這部分地址映射,防止分頁開啟后找不到;下面有第(3)點有PDT和PET的內存表,方便理解

         ;創建與上面那個目錄項相對應的頁表,初始化頁表項 
         mov ebx,0x00021000                 ;頁表的物理地址
         xor eax,eax                        ;起始頁的物理地址 
         xor esi,esi                        ;esi=0
  .b2:       
         mov edx,eax                        ;edx=eax;  eax = 0x1000*n
         or edx,0x00000003                  ;edx=0x1000*n+3;u/s=1,不允許3環程序訪問;P=1,頁在內存種;RW=1,頁可讀可寫;                                  
         mov [es:ebx+esi*4],edx             ;登記頁的物理地址; 0x21000~0x21400都是PTE,隱射從0~1MB(256*4096=1Mb)的物理地址;
         add eax,0x1000                     ;下一個相鄰頁的物理地址 
         inc esi
         cmp esi,256                        ;僅低端1MB內存對應的頁才是有效的 
         jl .b2
         
  .b3:                                      ;其余的頁表項置為無效
         mov dword [es:ebx+esi*4],0x00000000 ;0x21400~(0x21400+(1024-256)*4=0x22000)清零;
         inc esi
         cmp esi,1024
         jl .b3 

         (3)這里 es:ebx+esi = 0xFFFFF800, 開啟分頁機制后,會映射到0x20800,同樣也賦值0x21003,指向頁表第一個位置;

     ;在頁目錄內創建與線性地址0x80000000對應的目錄項
         mov ebx,0xfffff000                 ;頁目錄自己的線性地址;高5字節都是F,低3字節就是PDT內的偏移
         mov esi,0x80000000                 ;映射的起始地址
         shr esi,22                         ;取線性地址高10位(目錄索引),esi=0x200
         shl esi,2                            ;索引乘以4得到偏移
         mov dword [es:ebx+esi],0x00021003  ;寫入目錄項(頁表的物理地址和屬性)es:ebx+esi = 0xFFFFF800

    雖說這兩個PDE都指向同一個頁表,但各自的線性地址確不同:第一個線性地址范圍0x00000000~0x000FFFFF(PDT的索引是0), 第二個線性地址的范圍是0x80000000~0x800FFFFF(PDT的索引是800);為什么要讓兩個不同的線性地址段指向同一個PTE,進而共享同一塊物理內存了? 站在應用開發角度,已經習慣了將0x80000000作為內核地址,並且各個用戶程序共享。但此時GDT已加載到0x0~0xFFFFF的低1MB空間,后續內核代碼、內核數據段、API也會加載到這1MB空間,為了兼容現有的用戶習慣,需要將0x80000000也映射到這里的物理地址;所以這里的結論:線性地址0x80000000~0x800FFFFF映射的物理地址:0x00000~0xFFFFF

              

     物理地址內容如下,這里設計就很巧妙了

  •    比如未分頁的時候物理地址0x00007e10, or 0x80000000后變成0x80007e10,經過下面PDE和PTE的轉換,線性地址0x80007e10又變回了物理地址0x00007e10,分頁開啟在在物理地址保存的各個GDT or 0x80000000 就行,其他沒任何影響,照常使用;
  •    原0x00000000~0x000FFFFF 低1MB的物理空間,分頁開啟后轉成的物理地址沒變。比如0x00007e10,當成線性地址轉換成物理地址后還是0x00007e10;
  •    巧妙之處:(1)高10位是0x000或0x800的線性地址,在PDT表中查找到0x00021003,這是PTE的起始地址;  (2)中間10位是PTD的偏移,每個偏移都乘以0x1000,比如上面的0x007,得到0x7000;(3)最后3字節是頁內偏移,所以得到的結果還是以前的物理地址0x00007e10;

                     

   (4)此時已開啟了分頁模式,所有地址都會被認為是線性地址,為了正常找到在實模式下已經存好的描述符,這里對每個描述符最高位置1,原因上面已經解釋過:這么做能讓新的線性地址經過PDE和PTE的轉換后還能變回以前的物理地址,比如線性地址0x80007e10又變回了物理地址0x00007e10

       這里把內核各個核心段的描述符最高位都置1,構建內核區域的線性地址:

     ;將GDT中的段描述符映射到線性地址0x80000000
         sgdt [pgdt] 
         mov ebx,[pgdt+2]                    ;ebx存放GDT的base
         or dword [es:ebx+0x10+4],0x80000000    ;
         or dword [es:ebx+0x18+4],0x80000000    ;內核堆棧段
         or dword [es:ebx+0x20+4],0x80000000    ;視頻顯示緩沖區
         or dword [es:ebx+0x28+4],0x80000000    ;API段
         or dword [es:ebx+0x30+4],0x80000000    ;內核數據段
         or dword [es:ebx+0x38+4],0x80000000    ;內核代碼段
         add dword [pgdt+2],0x80000000      ;GDTR也用的是線性地址 
         lgdt [pgdt]

    此刻問題又來了:這個時候不是已經開啟分頁了么?es:ebx+0x18+4 = 0x7e00+0x18+0x4= 0x7e1c,這個地址會被當成線性地址看待;如果按照10-10-12分頁,0x7e1c轉成物理地址后還是0x7e1c,描述符的最高位成功置1; 更改后的描述符 0x80cf9600`0x7c00fffe ,段基址0x80007c00,轉成物理地址后還是0x7c00;

       

   (5)API段一共提供了4個函數,在內核數據段對這4個函數都有登記,每個函數的格式:函數名(不超過256字節,不夠的填0補充)、API段內偏移、API段選擇子,這個類似於導出表;這里構造每個API函數調用們(權限控制在3環的程序訪問),然后將selector寫回原選擇子處;

      其實在API(原作者稱為sys_routine段),出了這4個,還有其他函數,比如make_gate_descriptor、set_up_gdt_descriptor、alloc_inst_a_page等,只不過這兩個函數並未在導出表列舉,一般情況下用戶程序是不知道其地址的;同時也是內核0環權限,普通3環程序也無權訪問,但還是有辦法調用,比如在windows下,做逆向時需要調用很多內核未導出函數,在驅動中完全可以根據特征碼查找這些函數的偏移地址,然后call調用,詳細可參考之前的文章:https://www.cnblogs.com/theseventhson/p/13024325.html 

     ;以下開始安裝為整個系統服務的調用門。特權級之間的控制轉移必須使用門
         mov edi,salt                       ;C-SALT表的起始位置,內核API函數導出表,有函數名稱、函數在API段內的偏移、API段的選擇子 
         mov ecx,salt_items                 ;C-SALT表的條目數量,ecx=4 
  .b4:
         push ecx   
         mov eax,[edi+256]                  ;該條目入口點的32位偏移地址;API函數的段內偏移地址 
         mov bx,[edi+260]                   ;該條目入口點的段選擇子 ;API函數所在段的選擇子
         mov cx,1_11_0_1100_000_00000B      ;特權級3的調用門(3以上的特權級才
                                            ;允許訪問),0個參數(因為用寄存器
                                            ;傳遞參數,而沒有用棧) 
         call sys_routine_seg_sel:make_gate_descriptor
         call sys_routine_seg_sel:set_up_gdt_descriptor
         mov [edi+260],cx                   ;將返回的門描述符選擇子回填
         add edi,salt_item_len              ;指向下一個C-SALT條目 
         pop ecx
         loop .b4

   (6)分配物理頁:為了簡單,這里只使用2M內存,可用512個頁;512個頁用512位保存狀態,0表示空閑,1表示使用,這512位存放在page_bit_map中;分配內存時先逐個遍歷,是0的話就占用;同時把索引號乘以0x1000就是物理地址了;

allocate_a_4k_page:                         ;分配一個4KB的頁
                                            ;輸入:無
                                            ;輸出:EAX=頁的物理地址
         push ebx
         push ecx
         push edx
         push ds
         
         mov eax,core_data_seg_sel
         mov ds,eax
         
         xor eax,eax
  .b1:                                        ;遍歷page_bit_map,找到第一個標識是0的位,說明該頁還未使用
         bts [page_bit_map],eax                ;[page_bit_map]第eax的位復制給CF,同時置1
         jnc .b2                            ;CF=0,說明找到了空閑的物理頁;物理頁索引存放在eax
         inc eax                            ;沒有找到,eax+1繼續找
         cmp eax,page_map_len*8                ;遍歷到page_bit_map末尾了嗎?
         jl .b1                                ;沒有就從頭繼續找
         
         mov ebx,message_3
         call sys_routine_seg_sel:put_string
         hlt                                ;沒有可以分配的頁,停機 
         
  .b2:
         shl eax,12                         ;eax存放了空閑的物理頁索引,乘以4096(0x1000)就是地址 
         
         pop ds
         pop edx
         pop ecx
         pop ebx
         
         ret

  (7)給指定的線性地址掛載物理頁

  • 線性地址也要求0x1000對齊
  • 這里構造新的線性地址:(1)原線性地址高10位放在新地址中間13~22位;原線性地址中間10位(13~22)放新地址低3~12位;新地址高10位置1,這樣一來,原地址高10位會作為頁目錄表的偏移,原地址中間10位作為頁表內偏移mov [esi],eax 會把找好的物理頁地址放入合適的頁表項,最終完成線性地址到物理地址的映射
alloc_inst_a_page:                          ;給指定的線性地址掛載物理頁
                                            ;層級分頁結構中
                                            ;輸入:EBX=頁的線性地址,比如0x80104000
         push eax
         push ebx
         push esi
         push ds
         
         mov eax,mem_0_4_gb_seg_sel
         mov ds,eax
         
         ;檢查該線性地址所對應的頁表是否存在;把ebx高10位作為PDT的索引查找PTE;
         mov esi,ebx                        ;esi=0x80104000
         and esi,0xffc00000                    ;只保留最高的10位,低22位清零,得到PDT的索引,esi=0x80000000
         shr esi,20                         ;高12位移到低12位:得到頁目錄索引,並乘以4,得到PTE在PDE內的偏移地址;esi=0x00000800
         or esi,0xfffff000                  ;頁目錄自身的線性地址+表內偏移;最高20位置1的線性地址,轉換成物理地址=PDT基址(這里是0x20000)+esi,相當於最低3字節就是PDT內的偏移,高20位置1確保物理地址還是落在PDT內;esi=0xfffff800

         test dword [esi],0x00000001        ;P位是否為“1”.如果PDT某項有PTE,結尾不會是0;如果是0,說明還未掛載物理頁;[esi]=0x00000003,最后4位是0011;
         jnz .b1                            ;否已經有對應的頁表
          
         ;創建該線性地址所對應的頁表 
         call allocate_a_4k_page            ;分配一個頁做為頁表 
         or eax,0x00000007                    ;該頁的屬性:U/S=1,允許3環訪問;RW=1,可讀可寫;P=1,表明有物理頁了
         mov [esi],eax                      ;在頁目錄中登記該物理地址
          
  .b1:                                        ;不論是否執行JNZ .b1,代碼最終會走到這里來
         ;開始訪問該線性地址所對應的頁表
         mov esi,ebx                        ;esi=0x80104000
         shr esi,10                            ;高22位移到低22位,esi=0x00200410
         and esi,0x003ff000                 ;只保留原線性地址高10位,也就是PDT的偏移;esi=0x00200000
         or esi,0xffc00000                  ;原線性地址最高10位保存在esi的中間10位,即11-20位;高10位置1,這樣在PDT內查的時候能得到0x21003,也就是頁表的基址;
         
         ;得到該線性地址在頁表內的對應條目(頁表項) 
         and ebx,0x003ff000                    ;ebx=0x00104000,保留原線性地址中間10位
         shr ebx,10                         ;相當於右移12位,再乘以4;原線性地址中間10位右移到低2~11位,得到頁表內的偏移;ebx=0x410
         or esi,ebx                         ;頁表項的線性地址;原線性地址的高10位、中間10位依次右移,現在是從2~20位,高11位置1;原線性地址高10位用來作為頁表的偏移,中間10位用來做頁表的偏移; esi=0xFFF00410
         call allocate_a_4k_page            ;分配一個頁,這才是要安裝的頁
         or eax,0x00000007
         mov [esi],eax 
          
         pop ds
         pop esi
         pop ebx
         pop eax
         
         retf  

  第一次傳入的線性地址是0x80101000,還查不到對應的物理頁:

 

 

  執行完mov [esi],eax后,0x8010100的線性地址被映射到了0x2b000的物理地址:

  

    (8) 在當前PDT,ebx低3字節就是頁目錄內的偏移;把底2G的頁目錄清空,根據實際情況填上用戶程序的頁目錄,再復制到其他地方,這樣不用切換CR3(一旦切換,需要新的頁目錄和頁表,但還未建設好了,CPU會拋異常的),可以利用現有的地址轉換體系;后續每創建新任務,這部分的頁目錄表都要清零;從0x20800開始的頁目錄都是映射0x80000000的線性地址,這部分屬於各個任務共享的內核

;清空當前頁目錄的前半部分(對應低2GB的局部地址空間) 
         mov ebx,0xfffff000
         xor esi,esi
  .b1:
         mov dword [es:ebx+esi*4],0x00000000
         inc esi
         cmp esi,512
         jl .b1

    運行完后,內存變成這樣:

          

 

 

   (9)所謂 “每個用戶程序都擁有4GB的虛擬空間” ,核心原理體現在這里了: 每個用戶程序都單獨定制一個頁目錄表和頁表。每個用戶程序頁目錄表的第1項到512項都映射自己的物理地址,盡管不同用戶程序同樣用低2G的線性地址,但映射的物理地址卻可以不同

     mov [0xfffffff8],ebx: 這里把存放用戶程序頁目錄表的物理地址放在內核地址頁目錄表的倒數第二項;如果有第二個用戶程序,可以放在倒數第三項,即mov [0xfffffff4],ebx   以此類推;

create_copy_cur_pdir:                       ;創建新頁目錄,並復制當前頁目錄內容
                                            ;輸入:無
                                            ;輸出:EAX=新頁目錄的物理地址 
         push ds
         push es
         push esi
         push edi
         push ebx
         push ecx
         
         mov ebx,mem_0_4_gb_seg_sel
         mov ds,ebx
         mov es,ebx
         
         call allocate_a_4k_page            
         mov ebx,eax
         or ebx,0x00000007                    ;用戶程序的頁目錄和頁表,當然是3環能訪問的,所以U/S=1;RW=1可讀可寫;P=1表明已經有物理頁
         mov [0xfffffff8],ebx                ;頁目錄表倒數第二項(最后一項已經是0x20003了)
         
         mov esi,0xfffff000                 ;ESI->當前頁目錄的線性地址
         mov edi,0xffffe000                 ;EDI->新頁目錄的線性地址,剛好指向頁目錄表的倒數第二項,存放了剛才申請的物理地址
         mov ecx,1024                       ;ECX=要復制的目錄項數
         cld
         repe movsd 
         
         pop ecx
         pop ebx
         pop edi
         pop esi
         pop es
         pop ds
         
         retf

   (10) API段的描述符和選擇子都重置並寫回,3環的用戶程序才能調用

         push edi
         push esi
         push ecx

         mov ecx,64                         ;檢索表中,每條目的比較次數 
         repe cmpsd                         ;每次比較4字節 
         jnz .b6
         mov eax,[esi]                      ;若匹配,則esi恰好指向其后的地址
         mov [es:edi-256],eax               ;將字符串改寫成偏移地址 
         mov ax,[esi+4]
         or ax,0000000000000011B            ;以用戶程序自己的特權級使用調用門
                                            ;故RPL=3 
         mov [es:edi-252],ax                ;回填調用門選擇子 

  (11)把用戶程序導入表需要的函數和內核API段的函數根據名稱一一對比,發現名稱一樣的說明匹配上了,把這些內核API的物理地址、選擇子等回填到用戶程序的導入表,當用戶程序調用API時,才能跳轉到正確的地方執行

;重定位SALT 
         mov eax,mem_0_4_gb_seg_sel         ;訪問任務的4GB虛擬地址空間時用 
         mov es,eax                         
                                                    
         mov eax,core_data_seg_sel
         mov ds,eax
      
         cld

         mov ecx,[es:0x0c]                  ;U-SALT條目數;位於用戶程序程序0x0C處 
         mov edi,[es:0x08]                  ;U-SALT在4GB空間內的偏移;位於用戶程序0x08偏移處 
  .b4:
         push ecx
         push edi
      
         mov ecx,salt_items
         mov esi,salt
  .b5:
         push edi
         push esi
         push ecx

         mov ecx,64                         ;檢索表中,每條目的比較次數 
         repe cmpsd                         ;每次比較4字節 
         jnz .b6
         mov eax,[esi]                      ;esi是內核API地址
         mov [es:edi-256],eax               ;edi是用戶程序導入表的API地址,這里把內核API地址寫入用戶程序導入表,用戶程序調用時直接跳轉到內核API處執行 
         mov ax,[esi+4]                        ;
         or ax,0000000000000011B            ;以用戶程序自己的特權級使用調用門
                                            ;故RPL=3 
         mov [es:edi-252],ax                ;回填調用門選擇子到用戶程序的導入表 

    把內核API的偏移和選擇子回填到用戶程序導入表關鍵代碼:

        

    其他代碼都是利用TSS、TR、任務門切換任務相關的。在32位下,利用TSS切換任務效率較低,需要數百個時鍾周期,所以windwos和linux並未采用該方式;64位下連intel自己都廢棄這種方式,感興趣的讀者可自行分析剩余代碼;

 

  4、分頁機制要點

  •  為了最大程度利用內存,物理頁都是挨着連續分配的,第一個頁0x00001000,第二個頁0x00002000,直到最后一個頁0xFFFFF000;不難發現物理頁地址必須以000結尾(或則說除以0x1000余數為0)
  •     一旦分頁開啟,所有地址都會被CPU當成線性地址處理,需要先轉成物理地址,這是硬件機制決定的,os也不例外,所以最初構造頁目錄表的時候有一定的技巧,比如頁目錄表最后一項指向開始,中間0x20800也指向頁表第一基址、低512個頁目錄給用戶程序使用、每個用戶程序各自賦值一份頁目錄表和頁表;
  •  有了分頁,分段就不再那么重要了(64位windows段都平坦了)。通過對頁目錄表和頁表的控制,同樣可以達到控制程序對物理內存的使用;

 

  5、為方便理解,這里梳理了一下核心的步驟和流程:

  

MBR引導代碼

core_base_address equ 0x00040000   ;常數,內核加載的起始內存地址 
         core_start_sector equ 0x00000001   ;常數,內核的起始邏輯扇區號 
         
         mov ax,cs      
         mov ss,ax
         mov sp,0x7c00
      
         ;計算GDT所在的邏輯段地址
         mov eax,[cs:pgdt+0x7c00+0x02]      ;GDT的32位物理地址
         xor edx,edx
         mov ebx,16
         div ebx                            ;分解成16位邏輯地址 

         mov ds,eax                         ;令DS指向該段以進行操作;ds=0x7e0
         mov ebx,edx                        ;段內起始偏移地址,ebx =0x00

         ;跳過0#號描述符的槽位 
         ;創建1#描述符,這是一個數據段,對應0~4GB的線性地址空間
         mov dword [ebx+0x08],0x0000ffff    ;基地址為0,段界限為0xFFFFF
         mov dword [ebx+0x0c],0x00cf9200    ;粒度為4KB,存儲器段描述符 

         ;創建保護模式下初始代碼段描述符
         mov dword [ebx+0x10],0x7c0001ff    ;基地址為0x00007c00,界限0x1FF 
         mov dword [ebx+0x14],0x00409800    ;粒度為1個字節,代碼段描述符 

         ;建立保護模式下的堆棧段描述符      ;基地址為0x00007C00,界限0xFFFFE 
         mov dword [ebx+0x18],0x7c00fffe    ;粒度為4KB 
         mov dword [ebx+0x1c],0x00cf9600
         
         ;建立保護模式下的顯示緩沖區描述符   
         mov dword [ebx+0x20],0x80007fff    ;基地址為0x000B8000,界限0x07FFF 
         mov dword [ebx+0x24],0x0040920b    ;粒度為字節
         
         ;初始化描述符表寄存器GDTR
         mov word [cs: pgdt+0x7c00],39      ;描述符表的界限   
 
         lgdt [cs: pgdt+0x7c00]
      
         in al,0x92                         ;南橋芯片內的端口 
         or al,0000_0010B
         out 0x92,al                        ;打開A20

         cli                                ;中斷機制尚未工作

         mov eax,cr0
         or eax,1
         mov cr0,eax                        ;設置PE位
      
         ;以下進入保護模式... ...
         jmp dword 0x0010:flush             ;16位的描述符選擇子:32位偏移
                                            ;清流水線並串行化處理器
         [bits 32]               
  flush:                                  
         mov eax,0x0008                     ;以前是實模式的段基址,現在重新加載保護模式的數據段(0..4GB)選擇子
         mov ds,eax
      
         mov eax,0x0018                     ;加載堆棧段選擇子 
         mov ss,eax
         xor esp,esp                        ;堆棧指針 <- 0 
         
         ;以下加載系統核心程序 
         mov edi,core_base_address 
      
         mov eax,core_start_sector
         mov ebx,edi                        ;起始地址 
         call read_hard_disk_0              ;以下讀取程序的起始部分(一個扇區) 
      
         ;以下判斷整個程序有多大
         mov eax,[edi]                      ;核心程序尺寸
         xor edx,edx 
         mov ecx,512                        ;512字節每扇區
         div ecx

         or edx,edx
         jnz @1                             ;未除盡,因此結果比實際扇區數少1 
         dec eax                            ;已經讀了一個扇區,扇區總數減1 
   @1:
         or eax,eax                         ;考慮實際長度≤512個字節的情況 
         jz setup                           ;EAX=0 ?

         ;讀取剩余的扇區
         mov ecx,eax                        ;32位模式下的LOOP使用ECX
         mov eax,core_start_sector
         inc eax                            ;從下一個邏輯扇區接着讀
   @2:
         call read_hard_disk_0
         inc eax
         loop @2                            ;循環讀,直到讀完整個內核 

 setup:                                        ;系統各個段在0x00040000內存中重定位
         mov esi,[0x7c00+pgdt+0x02]         ;不可以在代碼段內尋址pgdt,但可以
                                            ;通過4GB的段來訪問, esi=0x7e00
         ;建立公用例程段描述符
         mov eax,[edi+0x04]                 ;公用例程sys_routine代碼段起始匯編地址=0x18;edi=0x00040000
         mov ebx,[edi+0x08]                 ;核心數據段core_data匯編地址=0x01e4
         sub ebx,eax                        ;core_data緊跟着sys_routine,core_data-sys_routine得到sys_routine長度
         dec ebx                            ;core_data的前面,也就是公用例程段sys_routine界限 
         add eax,edi                        ;公用例程段基地址:sys_routine=0x18,加上0x00040000得到sys_routine在內存的地址;
         mov ecx,0x00409800                 ;字節粒度的代碼段描述符
         call make_gdt_descriptor            
         mov [esi+0x28],eax                    ;描述符低32位eax=0x001801cb,存入0x7e00+0x28處
         mov [esi+0x2c],edx                    ;描述符高32位edx=0x00409804,存入0x7e00+0x2c處
                                            ;00409804`001801cb: 段基址00040018,limit=0x01cb;4:G=0,D/B=1,L=0,AVL=0;9:p=1,DPL=00,s=1;TYPE=8是代碼段;
                                            ;在0x7e00處原描述符的末尾追加新描述符,原有描述符不變
                                            
         ;建立核心數據段描述符
         mov eax,[edi+0x08]                 ;核心數據段起始匯編地址
         mov ebx,[edi+0x0c]                 ;核心代碼段匯編地址 
         sub ebx,eax
         dec ebx                            ;核心數據段界限
         add eax,edi                        ;核心數據段基地址
         mov ecx,0x00409200                 ;字節粒度的數據段描述符 
         call make_gdt_descriptor
         mov [esi+0x30],eax
         mov [esi+0x34],edx 
      
         ;建立核心代碼段描述符
         mov eax,[edi+0x0c]                 ;核心代碼段core_code起始匯編地址
         mov ebx,[edi+0x00]                 ;程序總長度
         sub ebx,eax
         dec ebx                            ;核心代碼段界限
         add eax,edi                        ;核心代碼段基地址
         mov ecx,0x00409800                 ;字節粒度的代碼段描述符
         call make_gdt_descriptor
         mov [esi+0x38],eax
         mov [esi+0x3c],edx

         mov word [0x7c00+pgdt],63          ;描述符表的界限;  0x3f,0x7e00:高4byte是GDT基址,低2byte是limit
                                        
         lgdt [0x7c00+pgdt]                 ;保護模式新增3個段,分別對應內核3個段

         jmp far [edi+0x10]                  ;edi=0x00040000,edi+0x10=core_code
       
;-------------------------------------------------------------------------------
read_hard_disk_0:                        ;從硬盤讀取一個邏輯扇區
                                         ;EAX=邏輯扇區號
                                         ;DS:EBX=目標緩沖區地址
                                         ;返回:EBX=EBX+512 
         push eax 
         push ecx
         push edx
      
         push eax
         
         mov dx,0x1f2
         mov al,1
         out dx,al                       ;讀取的扇區數

         inc dx                          ;0x1f3
         pop eax
         out dx,al                       ;LBA地址7~0

         inc dx                          ;0x1f4
         mov cl,8
         shr eax,cl
         out dx,al                       ;LBA地址15~8

         inc dx                          ;0x1f5
         shr eax,cl
         out dx,al                       ;LBA地址23~16

         inc dx                          ;0x1f6
         shr eax,cl
         or al,0xe0                      ;第一硬盤  LBA地址27~24
         out dx,al

         inc dx                          ;0x1f7
         mov al,0x20                     ;讀命令
         out dx,al

  .waits:
         in al,dx
         and al,0x88
         cmp al,0x08
         jnz .waits                      ;不忙,且硬盤已准備好數據傳輸 

         mov ecx,256                     ;總共要讀取的字數
         mov dx,0x1f0
  .readw:
         in ax,dx
         mov [ebx],ax
         add ebx,2
         loop .readw

         pop edx
         pop ecx
         pop eax
      
         ret

;-------------------------------------------------------------------------------
make_gdt_descriptor:                     ;構造描述符
                                         ;輸入:EAX=線性基地址,比如sys_routine=0x00040018;
                                         ;      EBX=段界限,比如sys_routine=0x1e4-0x18-1=0x1cb
                                         ;      ECX=屬性(各屬性位都在原始   比如sys_routine=0x00409800
                                         ;      位置,其它沒用到的位置0) 
                                         ;返回:EDX:EAX=完整的描述符
         mov edx,eax
         shl eax,16                      ;eax從0x00040018變為0x00180000;
         or ax,bx                        ;描述符前32位(EAX)構造完畢,eax=0x001801cb;
      
         and edx,0xffff0000              ;清除基地址中無關的位 edx=0x00040000
         rol edx,8                         ;edx = 0x04000000
         bswap edx                       ;裝配基址的31~24和23~16  (80486+); edx = 0x00000004;  31-24於0-7交換,23-16與8-15交換
      
         xor bx,bx                         ;ebx=0x00000000
         or edx,ebx                      ;裝配段界限的高4位,edx=0x00000004
      
         or edx,ecx                      ;裝配屬性 edx=0x00409804
      
         ret
      
;-------------------------------------------------------------------------------
         pgdt             dw 0
                          dd 0x00007e00      ;GDT的物理地址
;-------------------------------------------------------------------------------                             
         times 510-($-$$) db 0
                          db 0x55,0xaa

內核代碼:

         ;以下常量定義部分。內核的大部分內容都應當固定
         core_code_seg_sel     equ  0x38    ;內核代碼段選擇子
         core_data_seg_sel     equ  0x30    ;內核數據段選擇子 
         sys_routine_seg_sel   equ  0x28    ;系統公共例程代碼段的選擇子 
         video_ram_seg_sel     equ  0x20    ;視頻顯示緩沖區的段選擇子
         core_stack_seg_sel    equ  0x18    ;內核堆棧段選擇子
         mem_0_4_gb_seg_sel    equ  0x08    ;整個0-4GB內存的段的選擇子

;-------------------------------------------------------------------------------
         ;以下是系統核心的頭部,用於加載核心程序 
         core_length      dd core_end       ;核心程序總長度#00

         sys_routine_seg  dd section.sys_routine.start
                                            ;系統公用例程段位置#04

         core_data_seg    dd section.core_data.start
                                            ;核心數據段位置#08

         core_code_seg    dd section.core_code.start
                                            ;核心代碼段位置#0c


         core_entry       dd start          ;核心代碼段入口點#10
                          dw core_code_seg_sel

;===============================================================================
         [bits 32]
;===============================================================================
SECTION sys_routine vstart=0                ;系統公共例程代碼段 
;-------------------------------------------------------------------------------
         ;字符串顯示例程
put_string:                                 ;顯示0終止的字符串並移動光標 
                                            ;輸入:DS:EBX=串地址
         push ecx
  .getc:
         mov cl,[ebx]
         or cl,cl
         jz .exit
         call put_char
         inc ebx
         jmp .getc

  .exit:
         pop ecx
         retf                               ;段間返回

;-------------------------------------------------------------------------------
put_char:                                   ;在當前光標處顯示一個字符,並推進
                                            ;光標。僅用於段內調用 
                                            ;輸入:CL=字符ASCII碼 
         pushad

         ;以下取當前光標位置
         mov dx,0x3d4
         mov al,0x0e
         out dx,al
         inc dx                             ;0x3d5
         in al,dx                           ;高字
         mov ah,al

         dec dx                             ;0x3d4
         mov al,0x0f
         out dx,al
         inc dx                             ;0x3d5
         in al,dx                           ;低字
         mov bx,ax                          ;BX=代表光標位置的16位數

         cmp cl,0x0d                        ;回車符?
         jnz .put_0a
         mov ax,bx
         mov bl,80
         div bl
         mul bl
         mov bx,ax
         jmp .set_cursor

  .put_0a:
         cmp cl,0x0a                        ;換行符?
         jnz .put_other
         add bx,80
         jmp .roll_screen

  .put_other:                               ;正常顯示字符
         push es
         mov eax,video_ram_seg_sel          ;0x800b8000段的選擇子
         mov es,eax
         shl bx,1
         mov [es:bx],cl
         pop es

         ;以下將光標位置推進一個字符
         shr bx,1
         inc bx

  .roll_screen:
         cmp bx,2000                        ;光標超出屏幕?滾屏
         jl .set_cursor

         push ds
         push es
         mov eax,video_ram_seg_sel
         mov ds,eax
         mov es,eax
         cld
         mov esi,0xa0                       ;小心!32位模式下movsb/w/d 
         mov edi,0x00                       ;使用的是esi/edi/ecx 
         mov ecx,1920
         rep movsd
         mov bx,3840                        ;清除屏幕最底一行
         mov ecx,80                         ;32位程序應該使用ECX
  .cls:
         mov word[es:bx],0x0720
         add bx,2
         loop .cls

         pop es
         pop ds

         mov bx,1920

  .set_cursor:
         mov dx,0x3d4
         mov al,0x0e
         out dx,al
         inc dx                             ;0x3d5
         mov al,bh
         out dx,al
         dec dx                             ;0x3d4
         mov al,0x0f
         out dx,al
         inc dx                             ;0x3d5
         mov al,bl
         out dx,al

         popad
         
         ret                                

;-------------------------------------------------------------------------------
read_hard_disk_0:                           ;從硬盤讀取一個邏輯扇區,也就是每次讀512字節;1個頁需要讀8次
                                            ;EAX=邏輯扇區號
                                            ;DS:EBX=目標緩沖區地址
                                            ;返回:EBX=EBX+512
         push eax 
         push ecx
         push edx
      
         push eax
         
         mov dx,0x1f2
         mov al,1
         out dx,al                          ;讀取的扇區數

         inc dx                             ;0x1f3
         pop eax
         out dx,al                          ;LBA地址7~0

         inc dx                             ;0x1f4
         mov cl,8
         shr eax,cl
         out dx,al                          ;LBA地址15~8

         inc dx                             ;0x1f5
         shr eax,cl
         out dx,al                          ;LBA地址23~16

         inc dx                             ;0x1f6
         shr eax,cl
         or al,0xe0                         ;第一硬盤  LBA地址27~24
         out dx,al

         inc dx                             ;0x1f7
         mov al,0x20                        ;讀命令
         out dx,al

  .waits:
         in al,dx
         and al,0x88
         cmp al,0x08
         jnz .waits                         ;不忙,且硬盤已准備好數據傳輸 

         mov ecx,256                        ;總共要讀取的字數
         mov dx,0x1f0
  .readw:
         in ax,dx
         mov [ebx],ax
         add ebx,2
         loop .readw

         pop edx
         pop ecx
         pop eax
      
         retf                               ;段間返回 

;------------------------------------------------------------------------------- 
put_hex_dword:                              ;在當前光標處以十六進制形式顯示
                                            ;一個雙字並推進光標 
                                            ;輸入:EDX=要轉換並顯示的數字
                                            ;輸出:無
         pushad
         push ds
      
         mov ax,core_data_seg_sel           ;切換到核心數據段 
         mov ds,ax
      
         mov ebx,bin_hex                    ;指向核心數據段內的轉換表
         mov ecx,8
  .xlt:    
         rol edx,4
         mov eax,edx
         and eax,0x0000000f
         xlat
      
         push ecx
         mov cl,al                           
         call put_char
         pop ecx
       
         loop .xlt
      
         pop ds
         popad
         
         retf
      
;-------------------------------------------------------------------------------
set_up_gdt_descriptor:                      ;在GDT內安裝一個新的描述符,還是在0x7e00的地方
                                            ;輸入:EDX:EAX=描述符 
                                            ;輸出:CX=描述符的選擇子
         push eax
         push ebx
         push edx

         push ds
         push es

         mov ebx,core_data_seg_sel          ;切換到核心數據段
         mov ds,ebx

         sgdt [pgdt]                        ;以便開始處理GDT

         mov ebx,mem_0_4_gb_seg_sel
         mov es,ebx

         movzx ebx,word [pgdt]              ;GDT界限
         inc bx                             ;GDT總字節數,也是下一個描述符偏移
         add ebx,[pgdt+2]                   ;下一個描述符的線性地址

         mov [es:ebx],eax                    ;
         mov [es:ebx+4],edx                    ;

         add word [pgdt],8                  ;增加一個描述符的大小

         lgdt [pgdt]                        ;對GDT的更改生效

         mov ax,[pgdt]                      ;得到GDT界限值
         xor dx,dx
         mov bx,8
         div bx                             ;除以8,去掉余數
         mov cx,ax
         shl cx,3                           ;將索引號移到正確位置

         pop es
         pop ds

         pop edx
         pop ebx
         pop eax

         retf
;-------------------------------------------------------------------------------
make_seg_descriptor:                        ;構造存儲器和系統的段描述符
                                            ;輸入:EAX=線性基地址
                                            ;      EBX=段界限
                                            ;      ECX=屬性。各屬性位都在原始
                                            ;          位置,無關的位清零 
                                            ;返回:EDX:EAX=描述符
         mov edx,eax
         shl eax,16
         or ax,bx                           ;描述符前32位(EAX)構造完畢

         and edx,0xffff0000                 ;清除基地址中無關的位
         rol edx,8
         bswap edx                          ;裝配基址的31~24和23~16  (80486+)

         xor bx,bx
         or edx,ebx                         ;裝配段界限的高4位

         or edx,ecx                         ;裝配屬性

         retf

;-------------------------------------------------------------------------------
make_gate_descriptor:                       ;構造門的描述符(調用門等)
                                            ;輸入:EAX=門代碼在段內偏移地址
                                            ;       BX=門代碼所在段的選擇子 
                                            ;       CX=段類型及屬性等(各屬
                                            ;          性位都在原始位置)
                                            ;返回:EDX:EAX=完整的描述符
         push ebx
         push ecx
      
         mov edx,eax
         and edx,0xffff0000                 ;得到偏移地址高16位 
         or dx,cx                           ;組裝屬性部分到EDX
       
         and eax,0x0000ffff                 ;得到偏移地址低16位 
         shl ebx,16                          
         or eax,ebx                         ;組裝段選擇子部分
      
         pop ecx
         pop ebx
      
         retf                                   
                             
;-------------------------------------------------------------------------------
allocate_a_4k_page:                         ;分配一個4KB的頁
                                            ;輸入:無
                                            ;輸出:EAX=頁的物理地址
         push ebx
         push ecx
         push edx
         push ds
         
         mov eax,core_data_seg_sel
         mov ds,eax
         
         xor eax,eax
  .b1:                                        ;遍歷page_bit_map,找到第一個標識是0的位,說明該頁還未使用
         bts [page_bit_map],eax                ;[page_bit_map]第eax的位復制給CF,同時置1
         jnc .b2                            ;CF=0,說明找到了空閑的物理頁;物理頁索引存放在eax
         inc eax                            ;沒有找到,eax+1繼續找
         cmp eax,page_map_len*8                ;遍歷到page_bit_map末尾了嗎?
         jl .b1                                ;沒有就從頭繼續找
         
         mov ebx,message_3
         call sys_routine_seg_sel:put_string
         hlt                                ;沒有可以分配的頁,停機 
         
  .b2:
         shl eax,12                         ;eax存放了空閑的物理頁索引,乘以4096(0x1000)就是地址 
         
         pop ds
         pop edx
         pop ecx
         pop ebx
         
         ret
         
;-------------------------------------------------------------------------------
alloc_inst_a_page:                          ;給指定的線性地址掛載物理頁
                                            ;層級分頁結構中
                                            ;輸入:EBX=頁的線性地址,比如0x80104000
         push eax
         push ebx
         push esi
         push ds
         
         mov eax,mem_0_4_gb_seg_sel
         mov ds,eax
         
         ;檢查該線性地址所對應的頁表是否存在;把ebx高10位作為PDT的索引查找PTE;
         mov esi,ebx                        ;esi=0x80104000
         and esi,0xffc00000                    ;只保留最高的10位,低22位清零,得到PDT的索引,esi=0x80000000
         shr esi,20                         ;高12位移到低12位:得到頁目錄索引,並乘以4,得到PTE在PDE內的偏移地址;esi=0x00000800
         or esi,0xfffff000                  ;頁目錄自身的線性地址+表內偏移;最高20位置1的線性地址,轉換成物理地址=PDT基址(這里是0x20000)+esi,相當於最低3字節就是PDT內的偏移,高20位置1確保物理地址還是落在PDT內;esi=0xfffff800

         test dword [esi],0x00000001        ;P位是否為“1”.如果PDT某項有PTE,結尾不會是0;如果是0,說明還未掛載物理頁;[esi]=0x00000003,最后4位是0011;
         jnz .b1                            ;否已經有對應的頁表
          
         ;創建該線性地址所對應的頁表 
         call allocate_a_4k_page            ;分配一個頁做為頁表 
         or eax,0x00000007                    ;該頁的屬性:U/S=1,允許3環訪問;RW=1,可讀可寫;P=1,表明有物理頁了
         mov [esi],eax                      ;在頁目錄中登記該物理地址
          
  .b1:                                        ;不論是否執行JNZ .b1,代碼最終會走到這里來
         ;開始訪問該線性地址所對應的頁表
         mov esi,ebx                        ;esi=0x80104000
         shr esi,10                            ;高22位移到低22位,esi=0x00200410
         and esi,0x003ff000                 ;只保留原線性地址高10位,也就是PDT的偏移;esi=0x00200000
         or esi,0xffc00000                  ;原線性地址最高10位保存在esi的中間10位,即11-20位;高10位置1,這樣在PDT內查的時候能得到0x21003,也就是頁表的基址;
         
         ;得到該線性地址在頁表內的對應條目(頁表項) 
         and ebx,0x003ff000                    ;ebx=0x00104000,保留原線性地址中間10位
         shr ebx,10                         ;相當於右移12位,再乘以4;原線性地址中間10位右移到低2~11位,得到頁表內的偏移;ebx=0x410
         or esi,ebx                         ;頁表項的線性地址;原線性地址的高10位、中間10位依次右移,現在是從2~20位,高11位置1;原線性地址高10位用來作為頁表的偏移,中間10位用來做頁表的偏移; esi=0xFFF00410
         call allocate_a_4k_page            ;分配一個頁,這才是要安裝的頁
         or eax,0x00000007
         mov [esi],eax 
          
         pop ds
         pop esi
         pop ebx
         pop eax
         
         retf  

;-------------------------------------------------------------------------------
create_copy_cur_pdir:                       ;創建新頁目錄,並復制當前頁目錄內容
                                            ;輸入:無
                                            ;輸出:EAX=新頁目錄的物理地址 
         push ds
         push es
         push esi
         push edi
         push ebx
         push ecx
         
         mov ebx,mem_0_4_gb_seg_sel
         mov ds,ebx
         mov es,ebx
         
         call allocate_a_4k_page            
         mov ebx,eax
         or ebx,0x00000007                    ;用戶程序的頁目錄和頁表,當然是3環能訪問的,所以U/S=1;RW=1可讀可寫;P=1表明已經有物理頁
         mov [0xfffffff8],ebx                ;頁目錄表倒數第二項(最后一項已經是0x20003了)
         
         mov esi,0xfffff000                 ;ESI->當前頁目錄的線性地址
         mov edi,0xffffe000                 ;EDI->新頁目錄的線性地址,剛好指向頁目錄表的倒數第二項,存放了剛才申請的物理地址
         mov ecx,1024                       ;ECX=要復制的目錄項數
         cld
         repe movsd 
         
         pop ecx
         pop ebx
         pop edi
         pop esi
         pop es
         pop ds
         
         retf
         
;-------------------------------------------------------------------------------
terminate_current_task:                     ;終止當前任務
                                            ;注意,執行此例程時,當前任務仍在
                                            ;運行中。此例程其實也是當前任務的
                                            ;一部分 
         mov eax,core_data_seg_sel
         mov ds,eax

         pushfd
         pop edx
 
         test dx,0100_0000_0000_0000B       ;測試NT位
         jnz .b1                            ;當前任務是嵌套的,到.b1執行iretd 
         jmp far [program_man_tss]          ;程序管理器任務 
  .b1: 
         iretd

sys_routine_end:

;===============================================================================
SECTION core_data vstart=0                  ;系統核心的數據段 
;------------------------------------------------------------------------------- 
         pgdt             dw  0             ;用於設置和修改GDT 
                          dd  0
                              ;為了簡化,這里只用2M內存,有512個物理頁;已經占用的置1,沒用的置0
         page_bit_map     db  0xff,0xff,0xff,0xff,0xff,0x55,0x55,0xff    ;低地址基本都用光了,高地址還空着
                          db  0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff
                          db  0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff
                          db  0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff
                          db  0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55
                          db  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
                          db  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
                          db  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
         page_map_len     equ $-page_bit_map
                          
         ;符號地址檢索表,類似於導出表,詳細記錄了可供第三方調用的函數名、函數地址
         salt:
         salt_1           db  '@PrintString'        ;從@PrintString開始,長度是12字節
                     times 256-($-salt_1) db 0        ;剩余256-12=244字節填0;API函數名最長不超過256字節
                          dd  put_string            ;函數在API段內的偏移
                          dw  sys_routine_seg_sel    ;API段的選擇子,根據后面這6字節可以直接調用API函數

         salt_2           db  '@ReadDiskData'
                     times 256-($-salt_2) db 0
                          dd  read_hard_disk_0
                          dw  sys_routine_seg_sel

         salt_3           db  '@PrintDwordAsHexString'
                     times 256-($-salt_3) db 0
                          dd  put_hex_dword
                          dw  sys_routine_seg_sel

         salt_4           db  '@TerminateProgram'
                     times 256-($-salt_4) db 0
                          dd  terminate_current_task
                          dw  sys_routine_seg_sel

         salt_item_len   equ $-salt_4
         salt_items      equ ($-salt)/salt_item_len

         message_0        db  '  Working in system core,protect mode.'
                          db  0x0d,0x0a,0

         message_1        db  '  Paging is enabled.System core is mapped to'
                          db  ' address 0x80000000.',0x0d,0x0a,0
         
         message_2        db  0x0d,0x0a
                          db  '  System wide CALL-GATE mounted.',0x0d,0x0a,0
         
         message_3        db  '********No more pages********',0
         
         message_4        db  0x0d,0x0a,'  Task switching...@_@',0x0d,0x0a,0
         
         message_5        db  0x0d,0x0a,'  Processor HALT.',0
         
        
         bin_hex          db '0123456789ABCDEF'
                                            ;put_hex_dword子過程用的查找表 

         core_buf   times 512 db 0          ;內核用的緩沖區

         cpu_brnd0        db 0x0d,0x0a,'  ',0
         cpu_brand  times 52 db 0
         cpu_brnd1        db 0x0d,0x0a,0x0d,0x0a,0

         ;任務控制塊鏈
         tcb_chain        dd  0

         ;內核信息
         core_next_laddr  dd  0x80100000    ;內核空間中下一個可分配的線性地址;每次在線性地址分配一塊內存,該值就會增加;        
         program_man_tss  dd  0             ;程序管理器的TSS描述符選擇子 
                          dw  0

core_data_end:
               
;===============================================================================
SECTION core_code vstart=0
;-------------------------------------------------------------------------------
fill_descriptor_in_ldt:                     ;在LDT內安裝一個新的描述符
                                            ;輸入:EDX:EAX=描述符
                                            ;          EBX=TCB基地址
                                            ;輸出:CX=描述符的選擇子
         push eax
         push edx
         push edi
         push ds

         mov ecx,mem_0_4_gb_seg_sel
         mov ds,ecx

         mov edi,[ebx+0x0c]                 ;獲得LDT基地址
         
         xor ecx,ecx
         mov cx,[ebx+0x0a]                  ;獲得LDT界限
         inc cx                             ;LDT的總字節數,即新描述符偏移地址
         
         mov [edi+ecx+0x00],eax
         mov [edi+ecx+0x04],edx             ;安裝描述符

         add cx,8                           
         dec cx                             ;得到新的LDT界限值 

         mov [ebx+0x0a],cx                  ;更新LDT界限值到TCB

         mov ax,cx
         xor dx,dx
         mov cx,8
         div cx
         
         mov cx,ax
         shl cx,3                           ;左移3位,並且
         or cx,0000_0000_0000_0100B         ;使TI位=1,指向LDT,最后使RPL=00 

         pop ds
         pop edi
         pop edx
         pop eax
     
         ret
      
;-------------------------------------------------------------------------------
load_relocate_program:                      ;加載並重定位用戶程序
                                            ;輸入: PUSH 邏輯扇區號
                                            ;      PUSH 任務控制塊基地址
                                            ;輸出:無 
         pushad
      
         push ds
         push es
      
         mov ebp,esp                        ;為訪問通過堆棧傳遞的參數做准備
      
         mov ecx,mem_0_4_gb_seg_sel
         mov es,ecx
      
         ;清空當前頁目錄的前半部分(對應低2GB的局部地址空間) 
         mov ebx,0xfffff000
         xor esi,esi
  .b1:
         mov dword [es:ebx+esi*4],0x00000000
         inc esi
         cmp esi,512
         jl .b1
         
         ;以下開始分配內存並加載用戶程序
         mov eax,core_data_seg_sel
         mov ds,eax                         ;切換DS到內核數據段

         mov eax,[ebp+12*4]                 ;從堆棧中取出用戶程序起始扇區號
         mov ebx,core_buf                   ;讀取程序頭部數據
         call sys_routine_seg_sel:read_hard_disk_0

         ;以下判斷整個程序有多大
         mov eax,[core_buf]                 ;程序尺寸
         mov ebx,eax
         and ebx,0xfffff000                 ;使之4KB對齊 
         add ebx,0x1000                        
         test eax,0x00000fff                ;程序的大小正好是4KB的倍數嗎? 
         cmovnz eax,ebx                     ;不是。使用湊整的結果

         mov ecx,eax
         shr ecx,12                         ;程序占用的總4KB頁數,即用戶程序需要幾個頁加載 
         
         mov eax,mem_0_4_gb_seg_sel         ;切換DS到0-4GB的段
         mov ds,eax

         mov eax,[ebp+12*4]                 ;起始扇區號
         mov esi,[ebp+11*4]                 ;從堆棧中取得TCB的基地址
  .b2:
         mov ebx,[es:esi+0x06]              ;取得可用的線性地址
         add dword [es:esi+0x06],0x1000        ;線性地址分配后加0x1000,下次從這里繼續申請新內存
         call sys_routine_seg_sel:alloc_inst_a_page

         push ecx
         mov ecx,8
  .b3:
         call sys_routine_seg_sel:read_hard_disk_0
         inc eax
         loop .b3

         pop ecx
         loop .b2

         ;在內核地址空間內創建用戶任務的TSS
         mov eax,core_data_seg_sel          ;切換DS到內核數據段
         mov ds,eax

         mov ebx,[core_next_laddr]          ;用戶任務的TSS必須在全局空間上分配 
         call sys_routine_seg_sel:alloc_inst_a_page
         add dword [core_next_laddr],4096
         
         mov [es:esi+0x14],ebx              ;在TCB中填寫TSS的線性地址 
         mov word [es:esi+0x12],103         ;在TCB中填寫TSS的界限值 
          
         ;在用戶任務的局部地址空間內創建LDT 
         mov ebx,[es:esi+0x06]              ;從TCB中取得可用的線性地址
         add dword [es:esi+0x06],0x1000
         call sys_routine_seg_sel:alloc_inst_a_page
         mov [es:esi+0x0c],ebx              ;填寫LDT線性地址到TCB中 

         ;建立程序代碼段描述符
         mov eax,0x00000000
         mov ebx,0x000fffff                 
         mov ecx,0x00c0f800                 ;4KB粒度的代碼段描述符,特權級3
         call sys_routine_seg_sel:make_seg_descriptor
         mov ebx,esi                        ;TCB的基地址
         call fill_descriptor_in_ldt
         or cx,0000_0000_0000_0011B         ;設置選擇子的特權級為3
         
         mov ebx,[es:esi+0x14]              ;從TCB中獲取TSS的線性地址
         mov [es:ebx+76],cx                 ;填寫TSS的CS域 

         ;建立程序數據段描述符
         mov eax,0x00000000
         mov ebx,0x000fffff                 
         mov ecx,0x00c0f200                 ;4KB粒度的數據段描述符,特權級3
         call sys_routine_seg_sel:make_seg_descriptor
         mov ebx,esi                        ;TCB的基地址
         call fill_descriptor_in_ldt
         or cx,0000_0000_0000_0011B         ;設置選擇子的特權級為3
         
         mov ebx,[es:esi+0x14]              ;從TCB中獲取TSS的線性地址
         mov [es:ebx+84],cx                 ;填寫TSS的DS域 
         mov [es:ebx+72],cx                 ;填寫TSS的ES域
         mov [es:ebx+88],cx                 ;填寫TSS的FS域
         mov [es:ebx+92],cx                 ;填寫TSS的GS域
         
         ;將數據段作為用戶任務的3特權級固有堆棧 
         mov ebx,[es:esi+0x06]              ;從TCB中取得可用的線性地址
         add dword [es:esi+0x06],0x1000
         call sys_routine_seg_sel:alloc_inst_a_page
         
         mov ebx,[es:esi+0x14]              ;從TCB中獲取TSS的線性地址
         mov [es:ebx+80],cx                 ;填寫TSS的SS域
         mov edx,[es:esi+0x06]              ;堆棧的高端線性地址 
         mov [es:ebx+56],edx                ;填寫TSS的ESP域 

         ;在用戶任務的局部地址空間內創建0特權級堆棧
         mov ebx,[es:esi+0x06]              ;從TCB中取得可用的線性地址
         add dword [es:esi+0x06],0x1000
         call sys_routine_seg_sel:alloc_inst_a_page

         mov eax,0x00000000
         mov ebx,0x000fffff
         mov ecx,0x00c09200                 ;4KB粒度的堆棧段描述符,特權級0
         call sys_routine_seg_sel:make_seg_descriptor
         mov ebx,esi                        ;TCB的基地址
         call fill_descriptor_in_ldt
         or cx,0000_0000_0000_0000B         ;設置選擇子的特權級為0

         mov ebx,[es:esi+0x14]              ;從TCB中獲取TSS的線性地址
         mov [es:ebx+8],cx                  ;填寫TSS的SS0域
         mov edx,[es:esi+0x06]              ;堆棧的高端線性地址
         mov [es:ebx+4],edx                 ;填寫TSS的ESP0域 

         ;在用戶任務的局部地址空間內創建1特權級堆棧
         mov ebx,[es:esi+0x06]              ;從TCB中取得可用的線性地址
         add dword [es:esi+0x06],0x1000
         call sys_routine_seg_sel:alloc_inst_a_page

         mov eax,0x00000000
         mov ebx,0x000fffff
         mov ecx,0x00c0b200                 ;4KB粒度的堆棧段描述符,特權級1
         call sys_routine_seg_sel:make_seg_descriptor
         mov ebx,esi                        ;TCB的基地址
         call fill_descriptor_in_ldt
         or cx,0000_0000_0000_0001B         ;設置選擇子的特權級為1

         mov ebx,[es:esi+0x14]              ;從TCB中獲取TSS的線性地址
         mov [es:ebx+16],cx                 ;填寫TSS的SS1域
         mov edx,[es:esi+0x06]              ;堆棧的高端線性地址
         mov [es:ebx+12],edx                ;填寫TSS的ESP1域 

         ;在用戶任務的局部地址空間內創建2特權級堆棧
         mov ebx,[es:esi+0x06]              ;從TCB中取得可用的線性地址
         add dword [es:esi+0x06],0x1000
         call sys_routine_seg_sel:alloc_inst_a_page

         mov eax,0x00000000
         mov ebx,0x000fffff
         mov ecx,0x00c0d200                 ;4KB粒度的堆棧段描述符,特權級2
         call sys_routine_seg_sel:make_seg_descriptor
         mov ebx,esi                        ;TCB的基地址
         call fill_descriptor_in_ldt
         or cx,0000_0000_0000_0010B         ;設置選擇子的特權級為2

         mov ebx,[es:esi+0x14]              ;從TCB中獲取TSS的線性地址
         mov [es:ebx+24],cx                 ;填寫TSS的SS2域
         mov edx,[es:esi+0x06]              ;堆棧的高端線性地址
         mov [es:ebx+20],edx                ;填寫TSS的ESP2域 


         ;重定位SALT 
         mov eax,mem_0_4_gb_seg_sel         ;訪問任務的4GB虛擬地址空間時用 
         mov es,eax                         
                                                    
         mov eax,core_data_seg_sel
         mov ds,eax
      
         cld

         mov ecx,[es:0x0c]                  ;U-SALT條目數 
         mov edi,[es:0x08]                  ;U-SALT在4GB空間內的偏移 
  .b4:
         push ecx
         push edi
      
         mov ecx,salt_items
         mov esi,salt
  .b5:
         push edi
         push esi
         push ecx

         mov ecx,64                         ;檢索表中,每條目的比較次數 
         repe cmpsd                         ;每次比較4字節 
         jnz .b6
         mov eax,[esi]                      ;若匹配,則esi恰好指向其后的地址
         mov [es:edi-256],eax               ;將字符串改寫成偏移地址 
         mov ax,[esi+4]
         or ax,0000000000000011B            ;以用戶程序自己的特權級使用調用門
                                            ;故RPL=3 
         mov [es:edi-252],ax                ;回填調用門選擇子 
  .b6:
      
         pop ecx
         pop esi
         add esi,salt_item_len
         pop edi                            ;從頭比較 
         loop .b5
      
         pop edi
         add edi,256
         pop ecx
         loop .b4

         ;在GDT中登記LDT描述符
         mov esi,[ebp+11*4]                 ;從堆棧中取得TCB的基地址
         mov eax,[es:esi+0x0c]              ;LDT的起始線性地址
         movzx ebx,word [es:esi+0x0a]       ;LDT段界限
         mov ecx,0x00408200                 ;LDT描述符,特權級0
         call sys_routine_seg_sel:make_seg_descriptor
         call sys_routine_seg_sel:set_up_gdt_descriptor
         mov [es:esi+0x10],cx               ;登記LDT選擇子到TCB中

         mov ebx,[es:esi+0x14]              ;從TCB中獲取TSS的線性地址
         mov [es:ebx+96],cx                 ;填寫TSS的LDT域 

         mov word [es:ebx+0],0              ;反向鏈=0
      
         mov dx,[es:esi+0x12]               ;段長度(界限)
         mov [es:ebx+102],dx                ;填寫TSS的I/O位圖偏移域 
      
         mov word [es:ebx+100],0            ;T=0
      
         mov eax,[es:0x04]                  ;從任務的4GB地址空間獲取入口點 
         mov [es:ebx+32],eax                ;填寫TSS的EIP域 

         pushfd
         pop edx
         mov [es:ebx+36],edx                ;填寫TSS的EFLAGS域 

         ;在GDT中登記TSS描述符
         mov eax,[es:esi+0x14]              ;從TCB中獲取TSS的起始線性地址
         movzx ebx,word [es:esi+0x12]       ;段長度(界限)
         mov ecx,0x00408900                 ;TSS描述符,特權級0
         call sys_routine_seg_sel:make_seg_descriptor
         call sys_routine_seg_sel:set_up_gdt_descriptor
         mov [es:esi+0x18],cx               ;登記TSS選擇子到TCB

         ;創建用戶任務的頁目錄
         ;注意!頁的分配和使用是由頁位圖決定的,可以不占用線性地址空間 
         call sys_routine_seg_sel:create_copy_cur_pdir
         mov ebx,[es:esi+0x14]              ;從TCB中獲取TSS的線性地址
         mov dword [es:ebx+28],eax          ;填寫TSS的CR3(PDBR)域
                   
         pop es                             ;恢復到調用此過程前的es段 
         pop ds                             ;恢復到調用此過程前的ds段
      
         popad
      
         ret 8                              ;丟棄調用本過程前壓入的參數 
      
;-------------------------------------------------------------------------------
append_to_tcb_link:                         ;在TCB鏈上追加任務控制塊
                                            ;輸入:ECX=TCB線性基地址
         push eax
         push edx
         push ds
         push es
         
         mov eax,core_data_seg_sel          ;令DS指向內核數據段 
         mov ds,eax
         mov eax,mem_0_4_gb_seg_sel         ;令ES指向0..4GB段
         mov es,eax
         
         mov dword [es: ecx+0x00],0         ;當前TCB指針域清零,以指示這是最
                                            ;后一個TCB
                                             
         mov eax,[tcb_chain]                ;TCB表頭指針
         or eax,eax                         ;鏈表為空?
         jz .notcb 
         
  .searc:
         mov edx,eax
         mov eax,[es: edx+0x00]
         or eax,eax               
         jnz .searc
         
         mov [es: edx+0x00],ecx
         jmp .retpc
         
  .notcb:       
         mov [tcb_chain],ecx                ;若為空表,直接令表頭指針指向TCB
         
  .retpc:
         pop es
         pop ds
         pop edx
         pop eax
         
         ret
         
;-------------------------------------------------------------------------------
start:
         mov ecx,core_data_seg_sel          ;令DS指向核心數據段 
         mov ds,ecx

         mov ecx,mem_0_4_gb_seg_sel         ;令ES指向4GB數據段 
         mov es,ecx

         mov ebx,message_0                    
         call sys_routine_seg_sel:put_string
                                         
         ;顯示處理器品牌信息 
         mov eax,0x80000002
         cpuid
         mov [cpu_brand + 0x00],eax
         mov [cpu_brand + 0x04],ebx
         mov [cpu_brand + 0x08],ecx
         mov [cpu_brand + 0x0c],edx
      
         mov eax,0x80000003
         cpuid
         mov [cpu_brand + 0x10],eax
         mov [cpu_brand + 0x14],ebx
         mov [cpu_brand + 0x18],ecx
         mov [cpu_brand + 0x1c],edx

         mov eax,0x80000004
         cpuid
         mov [cpu_brand + 0x20],eax
         mov [cpu_brand + 0x24],ebx
         mov [cpu_brand + 0x28],ecx
         mov [cpu_brand + 0x2c],edx

         mov ebx,cpu_brnd0                  ;顯示處理器品牌信息 
         call sys_routine_seg_sel:put_string
         mov ebx,cpu_brand
         call sys_routine_seg_sel:put_string
         mov ebx,cpu_brnd1
         call sys_routine_seg_sel:put_string

         ;准備打開分頁機制
         
         ;創建系統內核的頁目錄表PDT
         ;頁目錄表清零 
         mov ecx,1024                       ;1024個目錄項PDE
         mov ebx,0x00020000                 ;頁目錄的物理地址
         xor esi,esi
  .b1:
         mov dword [es:ebx+esi],0x00000000  ;頁目錄表項清零 
         add esi,4
         loop .b1
         
         ;在頁目錄內創建指向頁目錄自己的目錄項,最后一項指向自己,那么線性地址高20位是0xFFFFF的時候,轉成物理地址就是頁目錄自己
         mov dword [es:ebx+4092],0x00020003 

         ;頁目錄的第一項,內核第一個頁表的物理地址:0x00021000
         mov dword [es:ebx+0],0x00021003    ;寫入目錄項(頁表的物理地址和屬性)      

         ;創建與上面那個目錄項相對應的頁表,初始化頁表項 
         mov ebx,0x00021000                 ;頁表的物理地址
         xor eax,eax                        ;起始頁的物理地址 
         xor esi,esi                        ;esi=0
  .b2:       
         mov edx,eax                        ;edx=eax;  eax=0x1000*n
         or edx,0x00000003                  ;edx=0x1000*n+3;u/s=1,允許所有特權級別的程序訪問;                                  
         mov [es:ebx+esi*4],edx             ;登記頁的物理地址; 0x21000~0x21400都是PTE,隱射從0~1MB(256*4096=1Mb)的物理地址;
         add eax,0x1000                     ;下一個相鄰頁的物理地址 
         inc esi
         cmp esi,256                        ;僅低端1MB內存對應的頁才是有效的 
         jl .b2
         
  .b3:                                      ;其余的頁表項置為無效
         mov dword [es:ebx+esi*4],0x00000000 ;0x21400~(0x21400+(1024-256)*4=0x22000)清零;
         inc esi
         cmp esi,1024
         jl .b3 

         ;令CR3寄存器指向頁目錄,並正式開啟頁功能 
         mov eax,0x00020000                 ;PCD=PWT=0,PDT基址=0x00020000
         mov cr3,eax

         mov eax,cr0
         or eax,0x80000000
         mov cr0,eax                        ;開啟分頁機制

         ;在頁目錄內創建與線性地址0x80000000對應的目錄項,有了這個項,0x800000000才會被映射到0x21000的PET;  線性地址0x80000000~0x800FFFFF映射的物理地址:0x00000~0xFFFFF
         mov ebx,0xfffff000                 ;頁目錄自己的線性地址;高5字節都是F,低3字節就是PDT內的偏移
         mov esi,0x80000000                 ;映射的起始地址
         shr esi,22                         ;取線性地址高10位(目錄索引),esi=0x200
         shl esi,2                            ;索引乘以4得到偏移
         mov dword [es:ebx+esi],0x00021003  ;寫入目錄項(頁表的物理地址和屬性)es:ebx+esi = 0xFFFFF800
                                            
                                             
         ;將GDT中的段描述符映射到線性地址0x80000000
         sgdt [pgdt]
         
         mov ebx,[pgdt+2]                    ;ebx存放GDT的base
         
         or dword [es:ebx+0x10+4],0x80000000    ;
         or dword [es:ebx+0x18+4],0x80000000    ;內核堆棧段
         or dword [es:ebx+0x20+4],0x80000000    ;視頻顯示緩沖區
         or dword [es:ebx+0x28+4],0x80000000    ;API段
         or dword [es:ebx+0x30+4],0x80000000    ;內核數據段
         or dword [es:ebx+0x38+4],0x80000000    ;內核代碼段
         
         add dword [pgdt+2],0x80000000      ;GDTR也用的是線性地址 
         
         lgdt [pgdt]
        
         jmp core_code_seg_sel:flush        ;刷新段寄存器CS,啟用高端線性地址 
                                             
   flush:
         mov eax,core_stack_seg_sel
         mov ss,eax
         
         mov eax,core_data_seg_sel
         mov ds,eax
          
         mov ebx,message_1
         call sys_routine_seg_sel:put_string

         ;以下開始安裝為整個系統服務的調用門。特權級之間的控制轉移必須使用門
         mov edi,salt                       ;C-SALT表的起始位置,內核API函數導出表,有函數名稱、函數在API段內的偏移、API段的選擇子 
         mov ecx,salt_items                 ;C-SALT表的條目數量,ecx=4 
  .b4:
         push ecx   
         mov eax,[edi+256]                  ;該條目入口點的32位偏移地址;API函數的段內偏移地址 
         mov bx,[edi+260]                   ;該條目入口點的段選擇子 ;API函數所在段的選擇子
         mov cx,1_11_0_1100_000_00000B      ;特權級3的調用門(3以上的特權級才
                                            ;允許訪問),0個參數(因為用寄存器
                                            ;傳遞參數,而沒有用棧) 
         call sys_routine_seg_sel:make_gate_descriptor    ;返回完整的描述符,保存在EDX:EAX;
         call sys_routine_seg_sel:set_up_gdt_descriptor    ;上一步構造好的門描述符寫回GDT表
         mov [edi+260],cx                   ;將返回的門描述符選擇子回填
         add edi,salt_item_len              ;指向下一個C-SALT條目 
         pop ecx
         loop .b4

         ;對門進行測試 
         mov ebx,message_2
         call far [salt_1+256]              ;通過門顯示信息(偏移量將被忽略);salt_1+256,低4字節是段內偏移,高2字節是選擇子 
      
         ;為程序管理器的TSS分配內存空間
         mov ebx,[core_next_laddr]            ;從0x80100000開始分配,查找還沒使用的線性地址
         call sys_routine_seg_sel:alloc_inst_a_page    ;給線性地址掛載物理頁
         add dword [core_next_laddr],4096    ;線性地址增加0x1000;

         ;在程序管理器的TSS中設置必要的項目;該線性地址已經掛載物理頁,可以正常使用了 
         mov word [es:ebx+0],0              ;反向鏈=0

         mov eax,cr3
         mov dword [es:ebx+28],eax          ;登記CR3(PDBR)

         mov word [es:ebx+96],0             ;沒有LDT。處理器允許沒有LDT的任務。
         mov word [es:ebx+100],0            ;T=0
         mov word [es:ebx+102],103          ;沒有I/O位圖。0特權級事實上不需要。
         
         ;創建程序管理器的TSS描述符,並安裝到GDT中 
         mov eax,ebx                        ;TSS的起始線性地址
         mov ebx,103                        ;段長度(界限)
         mov ecx,0x00408900                 ;TSS描述符,特權級0
         call sys_routine_seg_sel:make_seg_descriptor
         call sys_routine_seg_sel:set_up_gdt_descriptor
         mov [program_man_tss+4],cx         ;保存程序管理器的TSS描述符選擇子 

         ;任務寄存器TR中的內容是任務存在的標志,該內容也決定了當前任務是誰。
         ;下面的指令為當前正在執行的0特權級任務“程序管理器”后補手續(TSS)。
         ltr cx

         ;現在可認為“程序管理器”任務正執行中

         ;創建用戶任務的任務控制塊,類似windows下的進程控制塊PCB 
         mov ebx,[core_next_laddr]    ;從0x80100000開始分配
         call sys_routine_seg_sel:alloc_inst_a_page
         add dword [core_next_laddr],4096
         
         mov dword [es:ebx+0x06],0          ;用戶任務局部空間的分配從0開始。
         mov word [es:ebx+0x0a],0xffff      ;登記LDT初始的界限到TCB中
         mov ecx,ebx
         call append_to_tcb_link            ;將此TCB添加到TCB鏈中,類似windows下EPROCESS的鏈條 
      
         push dword 50                      ;用戶程序位於邏輯50扇區
         push ecx                           ;壓入任務控制塊起始線性地址 
       
         call load_relocate_program         
      
         mov ebx,message_4
         call sys_routine_seg_sel:put_string
         
         call far [es:ecx+0x14]             ;執行任務切換。
         
         mov ebx,message_5
         call sys_routine_seg_sel:put_string

         hlt
            
core_code_end:

;-------------------------------------------------------------------------------
SECTION core_trail
;-------------------------------------------------------------------------------
core_end:

用戶程序:

program_length   dd program_end          ;程序總長度#0x00 = 0x1F88E
         entry_point      dd start                ;程序入口點#0x04 = 0x1F85B
         salt_position    dd salt_begin           ;SALT表起始偏移量#0x08 =0x10
         salt_items       dd (salt_end-salt_begin)/256 ;SALT條目數#0x0C = 0x1F8

;-------------------------------------------------------------------------------

         ;符號地址檢索表
         salt_begin:                                     

         PrintString      db  '@PrintString'        ;內核代碼會對導入表做重定位,把內核API的實際偏移、選擇子寫回,覆蓋@PrintString前6個字節,下面就可以直接通過call far [PrintString]調用內核API函數了
                     times 256-($-PrintString) db 0
                     
         TerminateProgram db  '@TerminateProgram'
                     times 256-($-TerminateProgram) db 0
;-------------------------------------------------------------------------------

         reserved  times 256*500 db 0            ;保留一個空白區,以演示分頁

;-------------------------------------------------------------------------------
         ReadDiskData     db  '@ReadDiskData'
                     times 256-($-ReadDiskData) db 0
         
         PrintDwordAsHex  db  '@PrintDwordAsHexString'
                     times 256-($-PrintDwordAsHex) db 0
         
         salt_end:

         message_0        db  0x0d,0x0a,
                          db  '  ............User task is running with '
                          db  'paging enabled!............',0x0d,0x0a,0

         space            db  0x20,0x20,0
         
;-------------------------------------------------------------------------------
      [bits 32]
;-------------------------------------------------------------------------------

start:
          
         mov ebx,message_0
         call far [PrintString]
         
         xor esi,esi
         mov ecx,88
  .b1:
         mov ebx,space
         call far [PrintString] 
         
         mov edx,[esi*4]
         call far [PrintDwordAsHex]
         
         inc esi
         loop .b1 
        
         call far [TerminateProgram]              ;退出,並將控制權返回到核心 
    
;-------------------------------------------------------------------------------
program_end:

 

 擴展一下:

  這里用PE或ELF文件重定位做個對比:windows下加載dll一般都會重定位函數call、全局變量等,這時就要依賴PE文件頭中的重定位表了,和這里的虛擬地址到物理地址的“重定位”異曲同工,都是靠某種表格映射來實現的

 

            

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM