4.權限特權轉移代碼

  • 核心文件
  • 用戶文件
  • 引導文件

核心文件

	;------------------------新增--------------------------------; 本文件涉及了權限, 將使用調用門描述符來處理 低權限到高權限的轉移;------------------------權限----------------------------
;此文件延用上個CORE.asm. 并做出一些修改
;由于此文件涉及了特權, RPL,DPL,CPL, 因此有些地方會額外做出說明
;RPL: CPU提供的一個參數,由操作系統來做主,意思是希望以哪種特權去訪問
;CPL: 當前正在執行的代碼段的特權級 , 也是描述符中的DPL (只是在不同狀態下的名稱)
;DPL: 描述符的DPL;-----------------------調用門描述符----------------------------
;上個CORE.ASM中, 加載用戶程序后,為用戶創建的描述符DPL=0.為用戶填充重定位地址表的段選擇子RPL=0
;用戶程序相當于工作在特權0上. 此文件中,將把用戶程序設定為特權3.
;由于當前用戶程序的特權級是3,因此 用戶程序地址重定位表也需要全部修改
;function段描述符C=0,是非一致性代碼段,想要直接調用非一致性代碼段: CPL=DPL.必須是同級, 但此時用戶程序CPL=3
;本文件使用調用門:Call Gate 來處理這個問題
;調用門(Call Gate)就是一個描述符,格式:
; 31  ~  16            15  14 ~ 13  12     11 ~ 8          7 ~  5  4  ~  0
; 段內偏移高16位        P    DPL     S:0   TYPE(1100)        000     參數個數
; 31 ~  16         15 ~ 0
; 段選擇子       段內偏移低16位; 調用門 TYPE 固定1100
; 調用門描述符的段選擇子和偏移地址都是已知的某個代碼段的選擇子和偏移
; 描述符的參數個數 在符號地址表中給出每個過程需要的參數個數; 調用門的作用就是: 間接訪問某個高特權代碼段(通過段選擇子)的某個過程(偏移地址)
; 通過描述符結構可以看出來, 調用門就是為一個代碼段中的某一個過程服務的.
; 對于本文件就是: 一個調用門對應 function段的一個過程; 使用調用門也是有權限的 : 門描述符的DPL.
;   1.只有當 當前調用者的CPL<=門DPL,RPL <= 門DPL才能訪問門描述符
;   2.對于一致性代碼段: CPL>=目標代碼段DPL, 不論JMP,CALL, 轉移后CPL不變,棧不變
;   3.對于非一致性代碼段: 
;       3.1. CALL指令要求: CPL >= 目標代碼段DPL , CPL變成目標代碼段DPL, 切換棧
;       3.2. JMP 指令: CPL = 目標代碼段DPL , CPL不變,棧不變; 使用調用門: call far 調用門選擇子, jmp far 調用門選擇子
; 由于目標段的偏移地址和目標段選擇子已經在門描述符中, 一旦檢查通過后
; 根據門描述符中的段選擇子的TI位,找到對應的段描述符, 把段描述符加載到cs (這里的意思是調用門可以放在GDT或LDT)
; 最后根據段描述符的基址+ 門描述符的偏移 開始執行代碼;------------------------------ 門描述符,TSS描述符和LDT描述符---------------
; 調用門描述符,TSS描述符和LDT描述符 都是系統段描述符(S位=0)
; 調用門TYPE:1100, LDT TYPE:0010 , TSS TYPE:1001;------------------------------TSS,LDT--------------; LDT:局部描述符表. 類似GDT,用于存放描述符. 每個任務的描述符分開管理,都在自己的LDT中
; TSS是一種結構,用來表示一個任務,用于任務的切換,也用于特權棧的切換(低特權到高特權的代碼段運行); 要使用TSS,必須為TSS創建描述符,且必須存放在GDT中
; 要使用LDT,必須為LDT創建描述符,且必須存放在GDT中
; LDT與GDT一樣需要 16位界限,32位基址. 但跟加載GDT的指令lgdt不一樣的是,加載LDT指令:[lldt LDT段選擇子]
; 也就是16位界限與32位基址被存放在描述符中; LDT描述符,TSS描述符需要被存放在GDT中.
; GDT并不是一個段描述符, GDT就是一個線性地址上存放著描述符的結構
; LDT描述符的屬性:S=0,TYPE=0010
; TSS描述符的屬性:S=0,TYPE=1001( 1011 表示繁忙) , 只要當前CPL <= TSS描述符的DPL,就可以訪問此描述符,這意味著只要 CPL<=TSS.DPL 就可以調度任務; CPU廠商建議使用LDT,TSS來管理一個任務
; 實際情況是TSS是必須的, 畢竟TSS 表示這個任務.  切換任務需要用到TSS, 特權級棧改變也需要TSS
; 而LDT并不是必須品,是否使用LDT取決于你. (可以把任務的段描述符扔在GDT中); TR寄存器指明當前任務, LTR 指令用于加載一個TSS段選擇子 到TR , 同時把描述符中的TYPE從1001變成1011(繁忙狀態)
; 此時雖然TSS是繁忙狀態, TR也準備好了, 但任務并沒有切換; LTR指令: LTR TSS段選擇子 => TR: TSS段選擇子; LLDT 指令用于加載一個LDT 到 LDTR寄存器
; LLDT指令: LLDT LDT段選擇子   => LDTR: LDT段選擇子
; 一旦LLDT 加載完成,  此局部描述符表就生效了, 可以訪問此LDT中的描述符了;------------------------------;------------------------符號地址表的修改--------------------------------------
; section data中的 : symbol_table_addr_begin
; 每一項后都增加一個dw字段的門描述符, 在內核啟動后,會為所有的符號地址表中的過程創建調用門描述符
; 在與用戶程序的符號匹配后, 原來的做法是將 偏移地址和段選擇子 填充到用戶地址表中
; 現在將 已經創建的好的調用門選擇子 寫入到用戶地址表中;--------------------------------------------------------------;---------------------新增過程:create_call_gated---------------------------
; 為符號地址表中每個過程創建 調用門描述符, 并增加到GDT中
; 同時將 調用門的選擇子 填充到自己的地址表中, 用于把 選擇子 在與用戶程序匹配后寫入到用戶程序地址表中
; 調用門描述符的DPL 根據參數來決定,當前是: 3 (用戶程序特權)
; 由于 門選擇子 不一定給誰用, 因此一開始在 自己地址表中的門選擇子 默認RPL=0
; 給用戶程序用的時候, 將修改RPL
;
;--------------------------------------------------------------;---------------------新增LDT----------------------------
;LDT是局部描述符表, 每個任務都可以有
;LDT專門用于管理每一個任務的段描述符, 而不是像之前那樣把所有的描述符全部扔在GDT中
;GDT全局唯一, LDT可以有很多. 
;為了跟蹤每一個LDT, 一旦任務的段描述符在LDT中全部存放好后,也就是確定了LDT的界限后
;需要為LDT創建一個描述符(S位=0), 這是一種系統段描述符
;LDT的描述符結構完全與段描述符一致, 唯一需要注意的是 S:0, TYPE:0010
;由于每一個任務,都維護了一塊TCB,用來跟蹤任務的所有基本信息
;因此下面為LDT創建描述符的時候, 只需要注意 LDT的屬性設置即可
;下面代碼LDT的屬性: P=1,S=0,TYPE=0010,DPL=0(只能給特權0訪問)
;LDT屬性:0x00008200;---------------------;---------------------;----------------- 新增TSS ------------------------
; TSS 是一種結構 .  用于表示一個任務, 也用于特權棧的切換(低特權到高特權的調用)
; 為了使用TSS, 需要為TSS創建一個描述符,并把描述符存放在GDT中
; TSS 在切換任務的時候會把當前任務的所有寄存器保存在自己的結構中,以便在下一次恢復的時候繼續執行代碼
; TR 寄存器 表示當前任務, TR 保存著TSS的 段選擇子
; 使用LTR 來加載TSS段選擇子 到TR寄存器;-----------------;-----------------;----------------------新增TCB,任務控制塊--------------------------------
;為每個任務(一個用戶程序), 單獨創建一塊內存空間(并不是讀取用戶程序的內存),管理此任務,為了TSS和LDT
;此任務塊中包含了 此用戶程序的基本信息, 一個任務塊對應一個用戶程序
;由于可以多任務,因此新過程:create_tcb_and_append_to_tcblinklist
;把每一個TCB連接在一起,形成鏈表;
;TCB結構:
;0x00: 下一個TCB地址
;0x04:狀態 ; 0x06:程序基址
;0x0a:LDT 界限 ; 0x0c:LDT基址
;0x10:LDT 選擇子
;0x12:TSS界限,0x14:TSS基址,0x18:TSS 選擇子
;0x44:用戶頭部段選擇子;0x1A:特權0 棧長度,以4K為單位; 通過這個數可以用 : 0xfffff - 此長度 => 棧段的界限
;0x1E:特權0, 棧基址
;0x22:特權0, 棧選擇子
;0x24:特權0, 棧esp;0x28:特權1 棧長度
;0x2c:特權1 棧基址
;0x30:特權1 棧選擇子
;0x32:特權1 棧esp;0x36:特權2 棧長度
;0x3a:特權2 棧基址
;0x3e:特權2 棧選擇子
;0x40:特權2 棧esp
;--------------------------------------------------------------;-------------------------------加載用戶程序的流程修改-------------------------------
;在之前通過 load_app 加載用戶程序,為用戶程序創建的段描述符全部存放在GDT中
;現在將把段描述符存放在LDT中, LDT:局部描述符表
;讓每一個用戶程序各管各的, 分別獨立在自己的LDT, GDT只放內核程序的東西
;LDT可以有很多個, 不像GDT只有一個
;LDT 對應的寄存器是LDTR, 每當CPU運行此任務時,就會切換LDTR,讓LDTR指向當前的LDT
;因此當前 每加載一個用戶程序就會為其創建一個新的LDT,并把用戶程序的段描述符全放在自己的LDT中
;LDT與GDT一樣, 需要基址(4字節)和界限(2字節),因此1個LDT最多可以有8192個描述符
;唯一的不同就是LDT可以有多個,GDT只有一個;流程:
;1.用戶程序的讀取
;2.為用戶程序創建TCB任務控制塊,創建LDT
;3.為用戶程序建立描述符,并存放在LDT中
;4.為用戶程序建立特權棧,特權棧的描述符存放在LDT中,特權棧的基本信息填充在TCB中
;5.為用戶程序匹配符號表,并把門調用選擇子填充到用戶地址表中
;6.LDT界限確認(也就是為用戶程序創建完所有需要的描述符)完畢后,為LDT建立系統段描述符,存放在GDT中
;7.創建TSS 以表示一個任務,以及TSS描述符(存放在GDT中)
;8. lldt ldt段選擇子, ltr TSS段選擇子
;8.1 加載LDT到LDTR, 使LDT生效, 這樣就可以訪問用戶段描述符了
;8.2 讓TR指向當前任務(用戶程序),但并沒有開始調度此任務
;9.通過調用門的返回流程,特權棧的切換的方式, 從特權0轉移到特權3;-----------------------------------------------------------;-------------------------------保留上個文件的舊東西------------------
;當前程序繼承MBR的棧
;為了使用方便定義一些已知的段選擇子.當然也可以從下面的頭部間接獲取;地址對齊的一些操作:
;如果要與4字節(2^2)對齊,可以測試最低2位是否都為0,如果是則必定對齊
;如果要與512(2^9)對齊,可以測試最低9位是否都為0,如果是則必定對齊
;例如要對4字節對齊:
;   mov eax,5 ; test eax,3(11B); 結果是1,沒有對齊
;可以這么做: 
;   mov eax,5
;   mov ebx,eax         ;把ebx向上取整為能被4所整除的數
;   and ebx,0xfffffffc  ;強制低2位都為0
;   add ebx,4           ;這個數必能被4整除, 相當于向上取整
;   test eax,11B        ;如果為0則能整除,為1則無法整除
;   cmovnz  eax,ebx     ;條件賦值, c(條件)mov(賦值)nz(不為0則賦值,否則不會賦值);*關于棧的內存分配*:
;1.;從前面MBR中棧的分配: 基址:0x7c00, 段界限:0xffffe,G=1(以4k為單位);實際段界限:(0xffffe+1)*4096-1+1 = 0xFFFFF000;實際最小偏移:0x7c00 + 0xFFFFF000 = 0x(1)00006C00 由于環繞特性,最高位舍去,最終:0x6c00;esp最大:0xffffffff, 則最大偏移: 0x7c00 + 0xffffffff = 0x(1)00007BFF,最高位舍去:0x7bff;最小:0x6c00, 最大:0x7bff. 可以看到這塊4k內存,都在基址:0x7c00下面;可以看到 棧一般情況下,都會因為環繞特性,其實際內存位置都在基址下面
;2.
;   下面alloc_mem是一個分配內存的段間過程,方向向上,像數組一樣劃空間
;   如果是給方向向上的段,可以工作的很好,但如果是給棧劃空間,就需要配合建立描述符的過程一起工作了
;   假設棧請求分配內存: ;       2.1 alloc_mem返回地址:0x100000, 給棧劃分4k(0x1000)空間;       2.2 下一個可用地址:0x100000+0x1000=0x101000
;   現在如果把0x100000 當成段描述的段基址,會發生錯誤
;   根據上面,棧的地址空間 一般(具體情況具體分析)情況都會在 段基址的下面
;   alloc_mem返回的地址空間應該在:0x100000 ~ 100FFF , 共計 4096 字節,這些屬于棧
;   而0x100000的下面并不屬于棧,如果把0x100000當成段基址,將覆蓋0x100000之前的內存數據
;   因此,段基址應該是: 0x100000 + 4k(0x1000) = 0x101000
;   這樣從0x100000 ~ 0x100FFF 都是棧空間;--------------------
;常量定義;重定位表中的每一項段信息占用12字節
REALLOC_TABLE_EACH_ITEM_BYTES EQU 12;符號信息表每項占用16字節
SYMBOL_TABLE_EACH_ITEM_BYTES equ 16;地址表每項占用10字節
SYMBOL_TABLE_ADDR_EACH_ITEM_BYTES EQU 10;用戶符號信息表每項占用12字節
USER_SYMBOL_TABLE_EACH_ITEM_BYTES EQU 12;用戶程序扇區號
USER_APP_SECTOR EQU 100 ;在MBR中創建的所有描述符DPL=0, 對應的選擇子RPL=0
;MBR中定義
SEL_4G_DATA equ 0x18    ;數據段
SEL_STACK EQU 0x20      ;棧
SEL_0XB8000 EQU 0x10    ;顯存
SEL_MBR EQU 0X08        ;MBR段;內核代碼段和數據段所有描述符的DPL=0,選擇子RPL=0
;當前程序的,由MBR創建
SEL_HEADER EQU 0x28 ; 頭部段選擇子
SEL_CODE EQU 0x30   ;代碼段
SEL_DATA EQU 0X38   ;數據段
SEL_FUNC EQU 0X40   ;函數段;--------------------;function 函數段內大部分都是段間過程調用
;每個過程結尾都是 retf (pop eip, pop cs)
;每個retf 后都加了立即數,恢復棧,不用自己恢復esp
;調用function段的段間過程需要 : call SEL_FUNC:過程名 => push cs, push eip;重定位表含當前程序的4個段信息
;每個段信息包含段界限,段基址,段屬性,每個屬性4字節,3個屬性算一項共12字節[bits 32]
section header vstart=0 align=16;程序長度app_len dd tail_end     ;0x00;入口點偏移,段地址;當此程序被加載后,物理段地址被替換成段選擇子entry   dd start                ;0x04dd section.code.start   ;0x08;重定位表有幾項realloc_table_len dd  (table_end - table_start)/REALLOC_TABLE_EACH_ITEM_BYTES ;0x0c;重定位表;重定位表中存放了每個段的 : 段基址,段界限(段長度-1),段屬性;被加載程序處理后,所有的段基址都將被替換成段選擇子table_start:;頭部段seg_header_len dd header_end-1  ;段界限, 0x10seg_header_addr dd section.header.start ;段基址, 0x14seg_header_attr dd 0x00409200           ;段屬性, 0x18;代碼段seg_code_len dd code_end-1      ;段界限,0x1cseg_code_addr dd section.code.start ;段基址,0x20seg_code_attr dd 0x00409800         ;段屬性,0x24;數據段seg_data_len dd data_end-1      ; 段界限,0x28seg_data_addr dd section.data.start ;段基址,0x2cseg_data_attr dd 0x00409200         ;段屬性,0x30;函數段seg_function_len  dd function_end-1 ;段界限,0x34seg_function_addr dd section.function.start ;段基址,0x38seg_function_attr dd 0x00409800     ;屬性,0x3ctable_end:;----------------;以下段選擇子由mbr傳遞過來;4G數據段seg_4g_data dd 0        ;0x40;棧段seg_stack   dd 0        ;0x44;顯存段seg_0xb8000 dd 0        ;0x48;MBR段seg_mbr     dd 0        ;0x4c
header_end:section code vstart=0 align=16start:;當前棧段,ss:0x20,繼承使用了MBR的棧;切換DS, 直接使用上面定義的常量,省的去頭部段拿了mov eax,SEL_DATAmov ds,eax;顯示消息,內核啟動mov ebx,first_msg_done - first_msg  ;字符串長度push ebxpush dspush dword first_msg            ;起始地址;call 段選擇子:過程名;具體過程:;1.push cs, push eip;2.根據SEL_FUNC,獲取索引8 *8 + GDTR提供的GDT起始地址:; 2.1  地址: 8*8 + 0x7e00 ,檢查此地址是否越界(GDT的界限); 2.2  獲取此地址的段描述符,加載到cs高速緩沖區;3.查看print偏移地址是否在此段內,越界檢查 (描述符的界限);4.mov eip, printcall SEL_FUNC:print;add esp,0x0c . 不需要手動還原棧,段間調用都加了retf N 來恢復棧;-------------新增為所有的符號地址表過程創建調用門描述符----------------;   此處的調用門描述符加入到了GDT中push dword 3                        ;參數,為用戶創建的特權3調用門描述符call SEL_FUNC:create_call_gated     ;創建調用門描述符;---------------------------------;加載用戶程序;push USER_APP_SECTORcall SEL_FUNC:load_app;顯示加載完畢信息mov ebx,loaded_msg_done - loaded_msg    ;長度push ebxpush dspush dword loaded_msgcall SEL_FUNC:print;保存棧頂, 用戶程序回來后恢復mov [ds:stack_top], esp;---------------------轉移到用戶程序的修改------------------------------;在之前, 直接跳轉到用戶程序中執行, 是因為用戶程序也是特權0(DPL=0);但現在用戶所有的段描述符DPL=3.;特權是無法從高往低轉移的;但可以模擬調用門的方式來 假裝 當前特權0的代碼段是從用戶(CPL=3)的代碼段轉移過來的;首先模擬用戶程序是一個任務, 需要加載TSS,LTD. ;這些信息都在TCB中, 當前第一個TCB在數據段中的tcb_header;0x10:LDT 選擇子;0x18:TSS 選擇子mov ebx,SEL_4G_DATAmov es,ebxmov ebx,[ds:tcb_header] ;TCB地址;一旦加載完成, 此局部描述符表立馬生效. 可訪問LDT中的任何描述符了lldt [es:ebx + 0x10]    ;加載LDT段選擇子. 對應LDTR寄存器;加載TSS到TR, 確定任務ltr [es:ebx + 0x18]     ;加載tss    . 對應TR寄存器; 手動切換到TSS中的特權棧0 , 從TCB中比較容易獲取, 反正都是同一個;0x22:特權0, 棧選擇子;0x24:特權0, 棧espmov ax, [es:ebx + 0x22]    ;特權0 棧選擇子mov ss, axmov esp, [es:ebx + 0x24]    ;根據高權限到低權限的返回方式:;特權0 的棧中需要  特權3的ss,  特權3的esp , [參數1,參數2] ,  特權3的cs ,  特權3的eip. 這里沒參數; 特權3的段選擇子, 都在用戶頭部段中mov eax, [es:ebx + 0x44]    ;獲取用戶頭部段選擇子mov ds, eax                 ;切換到用戶頭部段去獲取;模擬特權級改變的遠返回, 注意push的順序push dword [ds:0x38]         ;用戶sspush dword 0                 ;用戶esppush dword [ds:0x08]        ;用戶cspush dword [ds:0x04]        ;用戶起始地址   , 用戶入口點;由于棧中的cs.rpl > 當前CPL, 因此是一個特權遠轉移(需要 pop eip pop cs pop esp pop ss);注意這個從高到低的特權級轉移, 轉移到用戶的入口點,沒有參數,如果轉移到一個有參數的過程,必須加上參數retf                    ;----------------以前的----------------    ;獲取頭部段選擇子.進行跳轉;   mov eax,[ds:user_header_selector];   mov es,eax;   jmp far [es:0x04]  
;----------------exit_process:;------用戶程序退出后應該還需要把分配的內存收回;------用戶描述符全部刪除, 修改gdt_size, 重新加載gdt,這里全部省略;恢復自己的數據段mov eax,SEL_DATAmov ds,eax;恢復棧mov ebx,[ds:stack_top]mov eax,SEL_STACKmov ss,eaxmov esp,ebx;喊一句話mov ebx,(back_msg_done - back_msg)push ebxpush dspush dword back_msgcall SEL_FUNC:printhltcode_end:section function vstart=0 align=16;退出
exit:push SEL_CODEpush exit_processretf;創建并追加TCB到鏈表中
;返回: eax 當前TCB地址
create_tcb_and_append_to_tcblinklist:push ebp mov ebp , espcall SEL_FUNC:create_tcb    ;返回eax為新的TCB起始地址;追加push eaxcall SEL_FUNC:append_to_tcb_linklist    ;追加到鏈表mov esp,ebppop ebpretf;創建一塊內存給TCB使用
;返回: eax , 可用地址
create_tcb:push ebpmov ebp,esppush esmov eax,SEL_4G_DATAmov es,eaxmov eax, 0x48           ;為TCB分配0x48字節push eax                ;需要分配多少字節call SEL_FUNC:alloc_mem ;返回eax 為可用地址;為新創建的TCB清空頭部4個字節mov dword [es:eax],0pop esmov esp,ebppop ebpretf;追加到TCB鏈表中
;參數: TCB地址(4字節地址)
append_to_tcb_linklist:push ebpmov ebp,esppush esipush eaxpush ebxpush dspush esmov esi,[ebp + 12]  ;可用地址mov eax,SEL_4G_DATAmov es,eax          ;es指向4Gmov eax,SEL_DATAmov ds,eax          ;ds指向自己數據段;獲取鏈表首地址;如果鏈表為空,直接把當前TCB賦值;如果鏈表不為空,則找到最后一個TCB,修改其TCB頭為新增的TCB地址mov eax,[ds:tcb_header]     or eax,eax          ;判斷是否為空鏈表jnz .not_empty;為空mov [ds:tcb_header],esijmp .append_to_tcb_linklist_done;不為空的情況.not_empty:mov ebx,eax         mov eax,[es:ebx]    ;獲取4字節地址,查看是否為空,eax指向下一個地址or eax,eaxjnz .not_empty      mov [es:ebx],esi    ;找到最后一個TCB控制塊,在首4字節處賦值.append_to_tcb_linklist_done:pop espop dspop ebxpop eaxpop esimov esp,ebppop ebpretf 4;根據符號表創建特權3的所有調用門描述符,用于特權級之間的調用
;參數:調用門DPL
create_call_gated:push ebpmov ebp,esppush dspush ebxpush ecxpush edxpush eaxpush esi;指向自己的數據段,獲取符號地址表中的數據來創建門描述符mov ebx,SEL_DATA    mov ds,ebx;指向地址表mov ebx,symbol_table_addr_begin ;地址表項數mov ecx, [ds:symbol_table_addr_len];調用門DPLmov esi,[ebp + 12]and esi,11B       ; 僅保證最后2位有效shl esi,13        ; 左移到描述符DPL的位置;為地址表每項創建調用門描述符.begin_create_call_gated_loop:;用于屬性的構造xor edx,edxxor eax,eax;為每個門描述符構造屬性,高32位中的低16位;P(1),DPL(此過程的參數),參數個數. 這3個是需要自己填充的. 參數個數在地址表中已經填寫好mov ax,[ds:ebx+0x06]    ;獲取地址表中的參數個數mov dx,100_0_1100_000_00000B or dx,si                    ; P,DPL 填充完畢or dx,ax                    ; 屬性合成完push word dxpush dword [ds:ebx]     ;過程偏移push word [ds:ebx + 0x04]     ;段選擇子call create_one_call_gated    ;創建一個調用門描述符, 返回edx:eaxpush edxpush eaxcall SEL_FUNC:add_to_gdt    ;把門描述符加入GDT, 返回eax(ax有效位) 門描述符選擇子mov [ds:ebx+0x08],ax        ;把調用門描述符選擇子填充到 地址表中add ebx,SYMBOL_TABLE_ADDR_EACH_ITEM_BYTESloop .begin_create_call_gated_looppop esipop eaxpop edxpop ecxpop ebxpop dsmov esp,ebppop ebpretf 4;根據參數條件創建一個調用門描述符
;參數: 目標段選擇子(2字節), 目標過程偏移地址(4字節), 門描述符屬性(2字節)
;棧中位置: 8                    10                  14
;返回: edx:eax   門描述符8字節
create_one_call_gated:push ebpmov ebp,esppush esixor esi,esimov edx,[ebp + 10]  ;偏移地址mov eax,edxand eax,0x0000ffff  ;保留低16位and edx,0xffff0000  ;保留高16位mov si,[ebp + 14]   ;門屬性or dx,si            ;高32位合成完畢mov si,word [ebp + 8]   ;段選擇子shl esi,16or eax,esi          ;低32位pop esimov esp,ebppop ebpret 8;加載一個程序
;參數:用戶程序的起始扇區號
load_app:push ebpmov ebp,esppushadpush espush ds;------------新增tcb 任務控制塊,用于跟蹤每個task-------------;創建一個TCB 來管理此任務call SEL_FUNC:create_tcb_and_append_to_tcblinklist ;返回eax , 新增TCB的地址mov esi,eax;----------------------------------mov eax,SEL_4G_DATAmov es,eax;----------------------為每個任務(用戶程序)創建一個專屬的LDT------;這里為LDT分配80字節的空間, 也就是每個用戶程序最多10個段描述符push dword 80call SEL_FUNC:alloc_mem     ;返回eax,LDT的可用地址;給TCB初始化 任務基本信息mov word [es:esi + 0x0A], 0xffff    ;初始LDT界限:0xffff,當前為空,實際大小(0xffff+1)=0字節mov [es:esi + 0x0c],eax             ;初始化LDT基址;----------------------------;首先讀取一個扇區,獲取用戶程序的頭部信息;由于需要動態給用戶程序分配內存地址,因此不再直接把首個扇區讀到指定內存地址;加載用戶頭部的緩沖區在 自己的data段 : user_header_buffer 定義了512字節mov eax,SEL_DATAmov ds,eaxmov ebx,user_header_bufferpush ds                 ;段選擇子push ebx                ;偏移push dword [ebp + 12]  ;扇區號call SEL_FUNC:read_sector;獲取程序多長mov eax,[ds:user_header_buffer] ;長度xor edx,edxmov ecx,512div ecx;計算還需要讀取幾個扇區or edx,edx          ;是否有余數,有余數則+1jz .begin_alloc_meminc eax             ;有余數;為用戶程序分配內存地址.begin_alloc_mem:mov ecx,eax         ;備份扇區數;計算字節數xor edx,edxmov ebx,512mul ebx             ;計算字節數, eax 32位4G空間足夠,不需要edxpush eax            ;需要分配的字節數call SEL_FUNC:alloc_mem     ;返回eax 為可用的起始地址,用戶程序將被加載到這mov edi,eax                 ;保存一份起始地址mov [es:esi+0x06],edi       ;保存到TCB中;把用戶程序讀取到 eax為起始地址的內存空間中push SEL_4G_DATA            ;4g 空間    push eax                    ;用戶程序起始地址push ecx                    ;扇區數量push dword [ebp + 12]        ;起始扇區號call SEL_FUNC:read_user_app ;讀取整個用戶程序;讀完用戶程序,需要給程序的每個段創建描述符,才能讓用戶程序運行起來;此處將修改,之前是把描述符全部放在GDT中,現在把所有的描述符放在LDT中;------------------修改----------------;用戶程序的起始地址,LDT 都可以從TCB中獲取push dword SEL_4G_DATA              ;段選擇子push esi                            ;TCB地址call SEL_FUNC:create_user_ldt_and_stack;------------------;----------之前的----------------;push SEL_4G_DATA        ;段選擇子;push edi                ;用戶程序被加載的起始地址; call SEL_FUNC:create_user_gdt   ;為用戶程序創建描述符和棧空間;------------------;用戶程序符號表處理;之前把 偏移地址, 段選擇子 填充到用戶地址表中, 現在將把已創建的調用門選擇子填充進去push dword SEL_4G_DATA      ;段選擇子push esi                    ;TCB地址call SEL_FUNC:realloc_user_app_symbol_table;為用戶程序創建額外的棧,push dword SEL_4G_DATA  ;段選擇子push esi                ;TCB地址push dword 3            ;為用戶創建額外的棧(指定對應特權的CPL即可)call SEL_FUNC:create_extra_stack_for_cpl    ;至此,用戶的段描述符,棧描述符, 特權棧描述符, 全部已經存放到LDT中;接下來需要讓CPU認識LDT,就需要把LDT存放在GDT中;GDT全局唯一,LDT每個任務一個;為LDT創建描述符call SEL_FUNC:create_LDT_descriptor;到這里;1.用戶程序讀取完畢;2.TCB任務控制塊創建完,LDT創建完;3.為用戶程序建立描述符,并存放在LDT中;4.為用戶程序建立特權棧,特權棧的描述符存放在LDT中,特權棧的基本信息填充在TCB中;5.為用戶程序匹配符號表,并把門調用選擇子填充到用戶地址表中;6.LDT界限確認(也就是為用戶程序創建完所有需要的描述符)完畢后,為LDT建立系統段描述符,存放在GDT中;接下來需要創建TSS(任務狀態段)call SEL_FUNC:create_tsspop dspop espopadmov esp,ebppop ebpretf 4;創建TSS
;參數: TCB地址, 段選擇子create_tss:push ebpmov ebp,esppush ebxpush espush eaxpush edxpush edimov ebx,[ebp + 16]  ;段選擇子mov es,ebxmov ebx,[ebp + 12]  ;TCB地址;為TSS分配內存空間.TSS需要104字節;TCB中的位置 : 0x12:TSS界限,0x14:TSS基址,0x18:TSS 選擇子push dword 104  ;104字節call SEL_FUNC:alloc_mem     ;返回eaxmov [es:ebx + 0x14], eax    ;TSS基址 寫入TCBmov word [es:ebx + 0x12], 103     ;tss界限 寫入TCBmov edi,eax                 ;備份;為TSS創建描述符,描述符只能存放在GDT中;TSS基址,界限都有了,還缺屬性,TSS描述符是系統段描述符(S=0),TYPE=1001(1011表示繁忙),這里設置DPL=0mov edx,0x00008900      ;DPL=0,只能由特權0去調度任務push eax                ;基址push dword 103          ;界限push edx                ;屬性call SEL_FUNC:make_gd   ;返回edx:eaxpush edxpush eaxcall SEL_FUNC:add_to_gdt    ;返回ax 段選擇子;ax 保存到TCB中mov [es:ebx + 0x18],ax;為TSS初始化mov word [es:edi], 0     ;previous task, 前一個任務:0. 沒有前一個任務mov byte [es:edi + 100],0   ;T位=0; debug 調試用;把之前創建的特權棧給TSS賦值;TCB中保存的特權棧 位置:;0x22:特權0, 棧選擇子;0x24:特權0, 棧esp;0x30:特權1 棧選擇子;0x32:特權1 棧esp;0x3e:特權2 棧選擇子;0x40:特權2 棧esp;特權0 賦值mov eax, [es:ebx + 0x24]   ;espmov [es:edi + 4], eaxmov ax,[es:ebx + 0x22]      ;段選擇子mov [es:edi + 8],ax;特權1mov eax,[es:ebx + 0x32]     ;espmov [es:edi + 12],eaxmov ax,[es:ebx + 0x30]     ;段選擇子mov [es:edi + 16],ax;特權2mov eax,[es:ebx + 0x40] ;espmov [es:edi + 20],eaxmov ax,[es:ebx + 0x3e]  ;段選擇子mov [es:edi + 24],ax;把LDT 賦值到TSS;  ;TCB 中: 0x10:LDT 選擇子mov ax, [es:ebx + 0x10]mov [es:edi + 96],ax  ;TSS IO位圖,直接填寫TSS的界限 ;先無視此位mov word [es:edi+ 102] , 103pop edipop edxpop eaxpop espop ebxmov esp,ebppop ebpretf 8;為LDT創建描述符,存放在GDT中
;參數: TCB地址, 段選擇子
;棧中位置: 8     12
;返回: eax (ax有效位) , LDT的段選擇子
create_LDT_descriptor:push ebpmov ebp , esppush espush ebxpush edxpush eaxpush ecxmov ebx,[ebp + 12]  ;段選擇子mov es,ebxmov ebx , [ebp + 8] ;TCB地址;TCB中有LDT的基址,界限mov edx,[es:ebx + 0x0c] ;LDT基址movzx eax, word [es:ebx + 0x0a] ;界限;LDT的屬性:D=1, P=1,S=0,TYPE=0010,DPL=0. 只能給特權0訪問mov ecx,0x00008200push edx        ;段基址push eax        ;段界限push ecx        ;段屬性call SEL_FUNC:make_gd       ;返回edx:eaxpush edxpush eaxcall SEL_FUNC:add_to_gdt    ;加入到GDT中, 返回LDT段選擇子;填充TCB中的LDT選擇子mov [es:ebx+0x10],ax        ;設置LDT段選擇子pop ecxpop eaxpop edxpop ebxpop esmov esp,ebppop ebpret 8; 為用戶程序創建額外的棧
; 根據CPL來創建額外的棧, 例如用戶CPL=3, 則需要額外創建特權為 :0,1,2 棧; CPL=1,則創建特權0的棧
;參數: 特權級別, TCB地址, TCB段選擇子
;棧中位置: 12       16      20
create_extra_stack_for_cpl:push ebpmov ebp,esppushadpush esmov ecx,[ebp + 20]  ;段選擇子mov es,ecxmov ebx,[ebp + 16]      ;TCBmov ecx,[ebp + 12]      ;特權級別,也是需要循環的次數;檢查特權級別是否 > 3, < 1 則不做處理test ecx,3jg .create_extra_stack_for_cpl_donetest ecx,1jl .create_extra_stack_for_cpl_donemov edi,0       ;計數器,也用作DPL;每個棧的基礎屬性是:0x00c09600, G=1,B=1,P=1,S=1,TYPE=0110;棧的DPL 可以根據 edi, 再左移13位  : or 0x00c09600 , ( edi << 13 );默認每個棧4k. 如需改變,可通過增加參數    ;TCB中每個棧信息需要14字節,起始地址0x1A;0x1A:特權0 棧長度,以4K為單位; 通過這個數可以用 : 0xfffff - 此長度 => 棧段的界限;0x1E:特權0, 棧基址;0x22:特權0, 棧選擇子;0x24:特權0, 棧espmov esi,0x1A        ;TCB中 特權0 棧的首個位置.begin_create_extra_stack:push 0x1000             ;固定每個棧4Kcall SEL_FUNC:alloc_mem ;返回eaxadd eax,0x1000          ;棧的基址需要加上0x1000字節數mov [es:ebx + esi + 4], eax   ;在TCB中設置棧基址;為棧構造段屬性. 還缺DPL才能合成mov edx,edishl edx,13or edx,0x00c09600       ;合成屬性;為棧創建描述符push eax            ;段基址push 0xffffe        ;段界限push edx            ;段屬性call SEL_FUNC:make_gd   ;返回edx:eax;把描述符放在TCB的LDT中push es             ;段選擇子push ebx            ;TCB地址push edx            ;描述符高32位push eax            ;描述符低32位call SEL_FUNC:add_to_ldt_with_tcb       ;返回ax 段選擇子;修改選擇子RPL, 根據當前的DPL來修改or ax,di;把選擇子放在TCB中mov [es:ebx + esi + 8] , ax;設置棧ESPmov dword [es:ebx + esi + 10],0;設置棧的長度mov dword [es:ebx + esi], 1   ;固定每個棧4K長度inc edi                 ;加增計數器,也是DPLadd esi,14              ;指向下一個棧區loop .begin_create_extra_stack.create_extra_stack_for_cpl_done:pop espopadmov esp,ebppop ebpretf  12;比較字符串
;參數:目標偏移,目標段選擇子,    原偏移,  原段選擇子 , 字符串長度
;棧中的位置:12       16          20        24        28
;返回:eax , 0:不相等, 1:相等
compare_string:push ebpmov ebp,esppushfdpush espush dspush esipush edipush ecxpush edxmov eax,[ebp + 16]      ;目標段選擇子mov es,eaxmov edi,[ebp + 12]      ;目標偏移mov eax,[ebp + 24]      ;原段選擇子mov ds,eax  mov esi,[ebp + 20]      ;原偏移mov ecx,[ebp + 28]      ;字符串長度mov edx,1xor eax,eaxcldrepe cmpsbcmovz eax,edx             ;匹配成功.compare_string_done:pop edxpop ecxpop edipop esipop dspop espopfdmov esp,ebppop ebpretf 20;符號表字符串比較
;參數:用戶信息表地址偏移,用戶可用的段選擇子,    原信息表偏移,  原信息表段選擇子 
;棧中的位置:8                   12                16        20
;返回:eax , 0:不相等, 1:相等
symbol_table_item_compare_string:push ebpmov ebp,esppushfdpush espush dspush edipush esimov eax,[ebp + 20]      ;原信息表段選擇子 mov ds,eaxmov esi,[ebp + 16]      ;原信息表偏移mov eax,[ebp + 12]      ;目標信息表段選擇子mov es,eaxmov edi,[ebp + 8]       ;目標信息表地址偏移xor eax,eax;先比較字符串長度;4字節比較. 比較esi,edi指向的字符串長度,每一項首地址都是4字節的字符串長度push edipush esicldcmpsd               ;比較后 esi+=4, edi+=4. 會自動增加pop esipop edi               jnz .symbol_table_item_compare_string_done;長度一致,進行字符串比較push dword [es:edi]     ; 首4個字節為字符串長度push ds           ;原段push dword [ds:esi+4]   ;原偏移在每項首地址的后4個字節,存放了實際字符串的地址push es           ;目標段push dword [es:edi+4]   ;目標偏移,偏移+4,存放字符串的地址call SEL_FUNC:compare_string    ;比較字符串.symbol_table_item_compare_string_done:pop esipop edipop dspop espopfdmov esp,ebppop ebpret 16;比較用戶符號信息表
;參數:用戶地址表起始地址, 用戶信息表地址,4G段選擇子
;棧中位置: 8                12              16             
;拿用戶信息表的一條與當前可以導出的信息表全部項進行比較
symbol_table_item_compare_and_fill:push ebpmov ebp,esppushadpushfdpush espush dsmov eax,[ebp + 16]      ;段選擇子mov es,eax             mov edi,[ebp + 12]      ;用戶信息表地址mov eax,SEL_DATAmov ds,eax                  ;ds指向自己數據段mov esi,symbol_table_begin  ;指向自己的信息表首項地址mov ecx,[ds:symbol_table_len]  ;自己的信息表共幾項;循環當前可導出的符號表每一項.begin_comapre_string:push ds           ;原信息表段選擇子push esi                ;原信息表偏移地址push es           ;4g段選擇子push edi                 ;用戶信息表偏移(首)地址call symbol_table_item_compare_stringor eax,eaxjnz .matched      ;匹配成功,跳轉add esi,SYMBOL_TABLE_EACH_ITEM_BYTESloop .begin_comapre_stringjmp .symbol_table_item_compare_and_fill_done    ;沒匹配到;匹配成功
.matched:push ds       ;原段選擇子push esi      ;原信息表偏移地址push es       ;4G選擇子push edi      ;用戶信息表偏移地址push dword [ebp + 8]   ;用戶地址表call symbol_table_item_fill_addr    ;填充地址.symbol_table_item_compare_and_fill_done:pop dspop espopfdpopadmov esp,ebppop ebpret 12;填充地址
;參數:用戶地址表,用戶信息表地址,用戶可用的段選擇子(4G), 原信息表地址, 原段選擇子
;棧中位置: 8            12              16          20          24
symbol_table_item_fill_addr:push ebpmov ebp , esppushadpush espush dsmov eax,[ebp + 24]      ;原段選擇子mov ds,eax  mov esi,[ebp + 20]      ;原信息表地址mov eax,[ebp + 16]      ;用戶段選擇子mov es,eaxmov edi,[ebp + 12]       ;用戶信息表地址mov edx,[ebp + 8]       ;用戶地址表首地址;當前可導出的信息表結構:; 字符串長度 , 4字節; 字符串偏移地址, 4字節; 字符串對應的過程地址, 4字節    -> 這個位置是要獲取的, 原信息表起始地址+8; 索引 , 4字節;可導出的地址表結構:;dd : 偏移           ;0x00;dw : 段選擇子        ;0x04      -> 原來將偏移地址和段選擇子填充到用戶地址表中;dw : 參數個數        ;0x06;dw : 調用門選擇子    ;0x08      ->現在修改成將門選擇子 填充進用戶地址表中mov ebx,[ds:esi + 8]        ;內核數據段的地址表項,此偏移地址存放著偏移地址,段選擇子;根據用戶信息表項內的索引,確定用戶地址表的位置,然后進行填充調用門選擇子mov ecx,[es:edi + 8]        ;用戶信息表項的索引shl ecx,3                   ;用戶地址表每一項占用8字節(偏移,調用門段選擇子)xor eax,eaxmov ax,[ds:ebx + 8]         ;調用門的選擇子;調用門的RPL修改成3or ax,11B;填充到用戶地址表中mov dword [es:edx + ecx + 4], eax;偏移地址填充0即可mov dword [es:edx + ecx],0;  mov eax,[ds:ebx]            ;獲取過程偏移地址;  mov [es:edx + ecx],eax      ;在用戶地址表中存放偏移地址;  movzx eax, word [ds:ebx + 4]       ;過程的段選擇子, 內核定義的地址表中段選擇子是2個字節;  mov [es:edx + ecx + 4], eax  ;在用戶地址表中存放段選擇子pop dspop espopadmov esp,ebppop ebpret 20;用戶符號表處理
;參數: TCB地址,4g段選擇子
realloc_user_app_symbol_table:push ebpmov ebp,esppushadpush esmov ebx,[ebp + 16]  ;段選擇子mov es,ebxmov eax,[ebp + 12]  ;TCB地址mov ebx,[es:eax + 0x06] ;用戶程序起始地址;比較過程:;拿用戶程序的符號信息表與自己數據段中的符號信息表中的每一項比較;如果匹配,則把自己數據段中的符號地址表(偏移,段選擇子)填充到用戶的符號地址表中;用戶符號信息表起始位置 0x3cmov ecx,[es:ebx + 0x3c]mov esi,[es:ebx + 0x40] ;   用戶符號信息表首項地址mov edi,[es:ebx + 0x44] ;   用戶符號地址表起始地址add esi,ebx             ;加上用戶起始地址偏移add edi,ebx.compare_start:push es             ;段選擇子push esi            ;用戶符號信息表首項地址push edi            ;用戶符號地址表起始地址call symbol_table_item_compare_and_filladd esi,USER_SYMBOL_TABLE_EACH_ITEM_BYTES   ;指向下一個符號信息表內的起始地址loop .compare_startpop espopadmov esp,ebppop ebpretf 8;為用戶創建描述符,把描述符放入ldt中
;參數: TCB地址, 段選擇子
create_user_ldt_and_stack:push ebpmov ebp,esppushadpush esmov ebx,[ebp + 16]  ;段選擇子mov es,ebx              ;指向4Gmov esi,[ebp + 12]  ;TCB地址mov ebx,[es:esi + 0x06] ;用戶程序起始地址mov ecx,[es:ebx + 0x0c] ;用戶程序的重定位表項數mov edi,0x10            ;用戶程序頭部重定位表起始地址;處理重定位表.process_realloc_table:mov eax,[es:ebx + edi + 4]  ;段基址add eax,ebx                 ;實際段基址push eaxpush dword [es:ebx + edi]         ;段界限;當前的段屬性是自己寫在用戶程序中的. 無論用戶DPL是什么,在這里強制DPL=3; 3左移13位 = 0x6000mov eax, [es:ebx + edi + 8]     ;段屬性or eax,0x6000                   ;強制DPL=3push eaxcall SEL_FUNC:make_gd       ;返回edx:eax    push SEL_4G_DATA        ;4G選擇子push esi                ;TCB基址push edx                ;描述符高32位push eax                ;低32位call SEL_FUNC:add_to_ldt_with_tcb   ;返回eax,ax:段選擇子;段選擇子寫到用戶程序重定位表中的段基址處or eax,11B  ;修改RPL=3;寫回用戶頭部mov [es:ebx+edi + 4] , eax      add edi,USER_SYMBOL_TABLE_EACH_ITEM_BYTES   ;指向下一項loop .process_realloc_table;替換入口點的代碼段基址mov eax,[es:ebx + 0x20] ;代碼段選擇子mov [es:ebx+0x08],eax;把頭部段選擇子保存到TCBmov eax, [es:ebx+0x14]  ;頭部段選擇子mov [es:esi + 0x44],eax;創建棧push es         ;段選擇子push esi        ;TCB地址push dword 3          ;棧的DPLpush dword 1          ;放入LDTcall SEL_FUNC:create_stack_with_parampop espopadmov esp,ebppop ebpretf 8;創建stack,之前的create_stack直接把描述符存放在GDT中.因此重寫一個,根據參數來決定stack的位置
;由于用戶程序只指定以4K單位的棧,因此棧屬性只涉及到DPL
;參數:棧描述符加入GDT還是LDT(0:GDT,1:LDT),棧DPL,TCB地址,TCB所屬段選擇子
;棧中位置: 12                             16    20     24
create_stack_with_param:push ebp mov ebp,esppushadpush esmov ebx,[ebp + 24]  ;段選擇子mov es,ebxmov ebx,[ebp + 20]  ;TCB地址mov edi,[es:ebx + 0x06 ]    ;用戶程序起始地址mov eax,[es:edi + 0x34 ]    ;用戶程序定義的棧大小(以4K為單位)mov ecx,0xfffff             ;棧界限最大值sub ecx,eax                 ;實際棧界限, 實際棧的最小偏移;確定棧的屬性,默認屬性 : G=1,B=1,P=1,S=1,TYPE=0110, 只剩DPL,需要參數來決定;DPL在位13-14mov edx, 0x00c09600     ;棧的默認屬性,DPL=0mov esi,[ebp + 16]      ;指定的DPLshl esi,13or edx,esi              ;棧屬性合成;為棧分配內存空間shl eax,12      ;以4K為單位: 乘以4096; 實際字節數mov esi,eax     ;備份棧的字節數,后續需要加上push eaxcall SEL_FUNC:alloc_mem ;返回eax: 地址add eax,esi         ;棧基址;為棧創建描述符push eax            ;段基址push ecx            ;段界限push edx            ;段屬性call SEL_FUNC:make_gd       ;返回edx:eaxmov ecx,[ebp + 12]      ;加入GDT,還是LDTor ecx,ecx  jnz .store_in_ldt;加入GDTpush edxpush eaxcall SEL_FUNC:add_to_gdt    ;增加到GDT中,返回eax 段選擇子jmp .create_stack_with_param_done.store_in_ldt:;加入LDTpush es                 ;4G選擇子push ebx                ;TCB基址push edx                ;描述符高32位push eax                ;低32位call SEL_FUNC:add_to_ldt_with_tcb   ;返回eax,ax:段選擇子.create_stack_with_param_done:;修改段選擇子的RPL=3or eax,11B;把段選擇子寫回用戶程序mov [es:edi + 0x38], eaxpop espopadmov esp,ebppop ebpretf  16;把描述符加入到TCB的LDT中
;參數:描述符低32位,高32位, TCB基址, 段選擇子(特指4G,除非既能訪問TCB,也能訪問LDT)
;棧中位置: 12       16      20      24
;返回:eax (ax有效位) , 段選擇子
add_to_ldt_with_tcb:push ebpmov ebp,esppush espush edipush ebxpush ecxpush edxmov edi,[ebp + 24]  ; 段選擇子mov es,edimov edi,[ebp + 20]  ; TCB地址xor ecx,ecxmov ebx,[es:edi + 0x0c] ;LDT基址mov cx,word [es:edi + 0x0a] ;LDT界限mov edx,ecx              ;備份界限inc cx                  ;界限+1為下一個描述符可存放的位置add ebx,ecx             ;在TCB中的LDT中存放描述符mov ecx,[ebp + 12]  ; 低32位mov [es:ebx], ecxmov ecx, [ebp + 16] ;高32位mov [es:ebx + 4],ecx;增加LDT的界限( 界限:2字節)add dx,8mov word [es:edi+0x0a],dx;計算段選擇子, 界限/8shr dx,3        ; 除8shl dx,3        ;左移3位;在LDT中,TI位=1or dx,100Bmov eax,edxpop edxpop ecxpop ebxpop edipop esmov esp,ebppop ebpretf 16;為用戶程序創建描述符
;參數: 用戶程序被加載的起始地址, 段選擇子
create_user_gdt:push ebpmov ebp,esppushadpush esmov eax,[ebp + 16]  ;段選擇子mov es,eax  mov ebx,[ebp + 12]  ;被加載的起始地址, 指向頭部mov ecx,[es:ebx + 0x0c] ;獲取重定位表項數mov esi,0x10            ;用戶程序頭部重定位表的起始地址;處理重定位表.process_realloc_table:mov eax,[es:ebx+esi+4]          ;段基址add eax,ebx                     ;實際段基址push eax                        ;段基址push dword [es:ebx + esi]       ;段界限push dword [es:ebx + esi + 8]   ;段屬性call SEL_FUNC:make_gd           ;返回描述符, edx:eaxpush edx                        ;高32位push eax                        ;低32位call SEL_FUNC:add_to_gdt        ;加入到GDT中,返回ax(段選擇子);把段選擇子寫入用戶程序中mov [es:ebx + esi + 4],eax;指向重定位表中的下一項add esi,USER_SYMBOL_TABLE_EACH_ITEM_BYTES   loop .process_realloc_table;重定位表處理完畢;把入口點的段基址替換成段選擇子mov eax,[es:ebx+0x20]   ;此處在上面重定位表中已經替換完成mov [es:ebx+0x08] , eax;為用戶程序創建棧空間push espush ebxcall SEL_FUNC:create_stack  ;創建棧,并構建棧描述符加入到GDT中pop espopadmov esp,ebppop ebpretf 8;創建棧
;參數:用戶程序起始地址, 段選擇子
create_stack:push ebpmov ebp, esppushadpush esmov eax,[ebp + 16]  ;段選擇子mov es,eax  mov ebx,[ebp + 12]  ;被加載的起始地址, 指向頭部;ecx:指定棧的界限mov ecx,0xfffff     ;最大界限mov eax,[es:ebx + 0x34] ;獲取棧指定的大小sub ecx,eax    ;減去棧指定的長度,這樣就得到了棧的最小偏移;edx:指定棧的屬性mov edx,0x00c09600  ;G=1,E=1,方向往下的數據段;下面調用alloc_mem 來分配一個段基址;首先確定需要分配多大的空間, 根據上面的eax獲取到的以4K為單位的數值*4096即可;4096=(2^12).因此左移12次shl eax,12mov esi,eax                 ;備份,等地址返回后需要用到push eaxcall SEL_FUNC:alloc_mem     ;返回eax, 可用地址;把基址提高N字節,此地址相當于下一個alloc_mem分配的內存地址;把此地址當成段基址add eax,esi                 ;由于地址環繞特性,如果不這樣做會覆蓋掉前面的內存數據,具體在上面已經寫過了;為棧創建描述符push eax            ;段基址push ecx            ;段界限push edx            ;段屬性call SEL_FUNC:make_gd       ;返回edx:eaxpush edxpush eaxcall SEL_FUNC:add_to_gdt    ;增加到GDT中,返回eax 段選擇子;把段選擇子寫回用戶程序mov [es:ebx + 0x38], eaxpop espopadmov esp,ebppop ebpretf 8;把8字節的描述符加入到GDT中
;參數:低32位,高32位
;返回eax, (ax有效位)段選擇子
add_to_gdt:push ebpmov ebp,esppush espush dspush esipush ebxmov esi,SEL_DATAmov ds,esi          ;指向自己的數據段,獲取用于獲取 GDT_SIZE ,GDT_BASEmov esi,SEL_4G_DATAmov es,esi          ;指向4G,用于增加描述符sgdt [ds:gdt_size]  ;存放GDTR所存的值,6字節xor esi,esixor ebx,ebxmov bx, word [ds:gdt_size]   ;獲取GDT界限inc bx                  ;指向下一個可存放的偏移地址mov esi,[ds:gdt_base]   ;GDT起始地址add esi,ebx             ;可存放描述符的地址;增加描述符到GDTmov ebx,[ebp + 12] ;低32位描述符mov [es:esi],ebxmov ebx,[ebp + 16]  ;高32位mov [es:esi + 4],ebxadd word [ds:gdt_size],8 ;增加界限lgdt [ds:gdt_size]  ;重載GDT, 使之生效;構造描述符的段選擇子;獲取界限, 除以8(右移3位) , 得到索引xor eax,eaxmov ax,[ds:gdt_size]   shr ax,3               ;得到索引;由于當前是在GDT中,TI位置0,DPL忽略,因此左移3位即可shl ax,3pop ebxpop esipop dspop esmov esp,ebppop ebpretf 8;根據:段基址,段界限,段屬性創建一個描述符, 此過程改個名字比較好
;參數: 段屬性, 段界限, 段基址
;返回: edx:eax 一個8字節描述符,edx高32位,eax低32位
make_gd:push ebpmov ebp,esppush ecxpush ebxmov eax,[ebp + 20]  ;段基址mov ebx,[ebp + 16]  ;段界限(低20位有效位)mov ecx,[ebp + 12]  ;段屬性mov edx,eax        shl eax,16      ;保留低16位段基址or ax,bx        ;eax 描述符低32位合成完畢and edx,0xffff0000  ;保留高16位rol edx,8           ;循環左移,把高8位移動到低8位bswap edx           ;交換低地址和高地址. 段基址位置存放完畢and ebx,0x000f0000  ;段界限保留高4位or edx,ebx          ;段界限合成完or edx,ecx          ;組合屬性pop ebxpop ecxmov esp,ebppop ebpretf 0x0c;讀取整個用戶程序
;參數: 起始扇區號,扇區數量,程序被加載的起始地址,目標內存段選擇子(注:此選擇子一般情況是SEL_4G_DATA)
read_user_app:push ebpmov ebp,esppushadpush esmov ecx,[ebp + 16]      ;扇區數量mov edx,[ebp + 12]      ;起始扇區號mov ebx,[ebp + 20]      ;程序被加載的起始地址 , 是內存分配出來的地址mov eax,[ebp + 24]      ;段選擇子mov es,eax              ;一般情況是SEL_4G_DATA. 指向4G空間.read_one_sector:push es                 ;段選擇子push ebx                ;偏移, ebx在read_sector中自增push edx                ;扇區號call SEL_FUNC:read_sectorinc edx                 ;讀取下一個扇區loop .read_one_sectorpop espopadmov esp,ebppop ebpretf 0x10;分配一塊內存
;參數: 需要多少字節
;返回: eax :一個以4字節對齊的內存起始地址
alloc_mem:push ebpmov ebp,esppush dspush ebxpush ecxmov eax,[ebp + 12]  ;參數mov ebx,SEL_DATAmov ds,ebxmov ebx,[ds:user_mem_base_addr] ;獲取可用的內存地址, ebx 此地址用于返回add eax,ebx                     ;計算下一個可用地址;讓下一個內存地址是4字節對齊的, 也就是能被4整除的,即低2位都是0mov ecx,eax     ;確保ecx一定能被4整除and ecx,0xfffffffc  ;強制低2位都是0add ecx,4           ;這個數一定能被4整除;看看eax是否能被4整除, and eax,3, 如果不為0說明無法被4整除test eax,3          ;3的二進制:11cmovnz eax,ecx      ;不為0,則把能整除的ecx賦值給eax;為下一個可用地址賦值mov [ds:user_mem_base_addr],eaxxchg eax,ebxpop ecxpop ebxpop dsmov esp,ebppop ebpretf 4
;讀取扇區
;參數: 扇區號,目標偏移地址, 目標段選擇子
;返回 ebx 以一個可寫入的地址;端口: 0x1f2 用來設置扇區數量
;0x1f3 - 0x1f6  用來設置扇區號, 其中0x1f6只有低4位是最后的扇區號
;0x1f7 用來控制讀/寫, 反饋狀態
;0x1f0 用來讀取數據 
;28位扇區號,LBA模式
read_sector:push ebpmov ebp,esppush eaxpush espush edxpush ecxmov eax,[ebp + 20]  ;段選擇子mov es,eaxmov ebx,[ebp + 16]  ;偏移地址mov eax,[ebp + 12]  ;扇區號push eaxmov dx,0x1f2    ;設置扇區數mov al,1out dx,al       inc dx          ;0x1f3pop eaxout dx,al       inc dx          ;0x1f4shr eax,8       ;去掉低8位out dx,alinc dx          ;0x1f5shr eax,8out dx,alinc dx          ;0x1f6shr eax,8and al,0000_1111B   ;保留低4位有效位or al,0xe0          ;1110_000B , LBA模式,從主盤讀out dx,alinc dx          ;0x1f7mov al,0x20     ;讀取狀態out dx,al;查看硬盤狀態.read_disk_status:in al,dxand al,1000_1000b   ;檢查是否可讀cmp al,0000_1000b   ;如果第三位為1,則可讀jnz .read_disk_status   ;否則繼續檢查;從0x1f0中開始讀取mov dx,0x1f0mov ecx,256.begin_read:in ax,dxmov [es:ebx],axadd ebx,2loop .begin_readpop ecxpop edxpop espop eaxmov esp,ebppop ebpretf 0x0c;打印到顯存 
;參數:偏移地址,段選擇子,字符串長度
print:push ebpmov ebp,esppush ds push espush esipush edipush ecxpush eaxmov esi,[ebp + 12]   ;偏移mov ecx,[ebp + 16]  ;段選擇子mov eax,[ebp + 8]   ;用戶csarpl cx,ax          ;修改RPL, 如果 數據段RPL < 用戶cs的RPL 則修改成用戶cs的RPLmov ds,ecxmov ecx,[ebp + 20]  ;長度;獲取最后一次的打印位置mov eax,SEL_DATAmov es,eaxxor edi,edimov di,[es:print_pos]      ;從此偏移開始打印;設置顯存段mov eax,SEL_0XB8000mov es,eax.print_loop:mov al,[ds:esi]mov [es:edi],alinc dimov byte [es:edi],0x07inc esiinc diloop .print_loop.print_done:mov eax,SEL_DATAmov es,eaxmov [es:print_pos],dipop eaxpop ecxpop edipop esipop espop dsmov esp,ebppop ebpretf 0x0c   ;pop eip , pop csfunction_end:section data vstart=0 align=16first_msg db 'fuck !'first_msg_done:loaded_msg db 'loaded!!!'loaded_msg_done:back_msg db 'Im back!!!!!!!'back_msg_done:;棧頂,備份. 為了跳轉回來后還原stack_top dd 0;print_pos : 當前打印的位置print_pos dw 0;tcb 鏈表tcb_header dd 0;由于加載用戶程序后會添加描述符,需要更改gdt_size;sgdt 指令用于存放gdt數據gdt_size    dw 0    ;界限gdt_base    dd 0    ;GDT起始地址;用戶程序被加載到的地址,1M開始處user_mem_base_addr  dd  0x100000;用戶頭部段選擇子,用于后續跳轉user_header_selector    dd  0;用于讀取用戶程序首個扇區user_header_buffer times 512 db 0;符號信息表的項數symbol_table_len dd (symbol_table_end - symbol_table_begin) / SYMBOL_TABLE_EACH_ITEM_BYTES;可導出可被連接(用戶程序可見可用)的符號信息表symbol_table_begin:print_info: dd (read_sector_string - print_string)   ;字符串長度(用于比較)dd print_string      ;字符串的偏移(用于匹配)dd print_addr        ;過程地址dd 0                 ;索引 (這里沒用到)read_sector_info:    dd (make_gd_string - read_sector_string)dd read_sector_string   dd  read_sector_addrdd  1make_gd_info:    dd (exit_string - make_gd_string)dd make_gd_stringdd make_gd_addrdd 2exit_info:       dd (compare_string_string - exit_string)dd exit_stringdd exit_addrdd 3compare_string_info:dd (symbol_table_string_end - compare_string_string)dd compare_string_stringdd compare_string_addrdd 4symbol_table_end:;字符串表symbol_table_string_begin:print_string db 'print'read_sector_string db 'read_sector'make_gd_string db 'make_gd'exit_string db 'exit'compare_string_string db 'compare_string'symbol_table_string_end:;地址表長度symbol_table_addr_len dd (symbol_table_addr_end-symbol_table_addr_begin)/SYMBOL_TABLE_ADDR_EACH_ITEM_BYTES;地址表symbol_table_addr_begin:print_addr  dd  print           ;偏移dw  SEL_FUNC        ;段選擇子dw  3               ;參數個數dw  0               ;待填充的門描述符選擇子read_sector_addr dd read_sectordw SEL_FUNCdw 3           ;3個參數dw  0               ;待填充的門描述符選擇子make_gd_addr    dd make_gddw SEL_FUNCdw 3            dw  0               ;待填充的門描述符選擇子exit_addr   dd  exitdw SEL_FUNCdw 0dw  0               ;待填充的門描述符選擇子compare_string_addr dd compare_stringdw SEL_FUNCdw 5dw  0               ;待填充的門描述符選擇子symbol_table_addr_end:data_end:section tail
tail_end:

用戶文件


;定義常量
;每一項重定位段信息占用12字節
USER_REALLOC_TABLE_EACH_ITEM_BYTES EQU 12;符號表每一項占用12字節
USER_APP_EACH_TABLE_ITEM_BYTES EQU 12;符號地址表每一項占用8字節      
USER_APP_EACH_TABLE_ITEM_ADDR_BYTES equ 8;棧段由內核分配,并寫入選擇子到seg_stack_addr;
;seg_stack_len 提示描述符的G位(以4K還是1字節為單位);用戶程序如果需要調用內核程序提供的API,則需要符號表幫忙,根據字符串匹配來確定對應的調用門選擇子:偏移地址
;符號表分為2塊:
;1.符號信息表
;   1.符號信息表內部又由2部分組成: 符號信息,符號字符串表
;   2.遍歷符號信息,可匹配字符串以及獲取對應的索引,一旦匹配成功則在地址表中寫入(偏移,調用門選擇子)
;2.符號地址表,每一項占8個字節: (4字節)偏移地址:(4字節,僅低2字節有效)調用門選擇子
;   根據信息表內的索引, 索引*8 即可獲取過程的偏移,調用門選擇子[bits 32]
section header vstart=0 align=16;程序長度app_len dd tail_end     ;0x00;入口點entry   dd start        ;0x04dd section.code.start   ;0x08;重定位表項數;0x0crealloc_table_len dd  (table_end - table_begin) / USER_REALLOC_TABLE_EACH_ITEM_BYTES;重定位表;被加載后,所有段基址都會被替換成段選擇子table_begin:;頭部段seg_header_len dd header_end-1 ;段界限, 0x10seg_header_addr dd section.header.start ;段基址, 0x14seg_header_attr dd 0x0040f200           ;段屬性,0x18 , DPL=3;代碼段seg_code_len dd code_end-1  ;段界限,0x1cseg_code_addr dd    section.code.start  ;段基址,0x20seg_code_attr   dd  0x0040f800      ;段屬性,0x24    ,DPL=3;數據段seg_data_len    dd data_end-1   ;段界限,0x28seg_data_addr   dd  section.data.start  ;段基址,0x2cseg_data_attr   dd 0x0040f200       ;段屬性,0x30    ,DPL=3table_end:;棧段由內核程序幫忙分配;分配完后由內核程序寫入;用戶程序在固定位置有占位即可;內核將用最大界限值:0xfffff - 1(這里的數字), 來計算分配多少字節seg_stack_len   dd  1   ; 0x34 , 以4K為單位, 這里的1相當于4096字節,與界限相呼應seg_stack_addr  dd  0   ; 0x38, 由內核寫入棧段選擇子;0x3c;符號信息表長度symbol_table_len dd (symbol_table_string_start-symbol_table_begin) / USER_APP_EACH_TABLE_ITEM_BYTES;0x40;符號信息表起始位置symbol_table_info_start dd symbol_table_begin   ;0x44;符號地址表起始位置symbol_table_addr_start dd symbol_table_addr_begin;用戶程序符號信息表symbol_table_begin:print_info:  dd (exit - print ) ;字符串長度,用于比較dd print    ;字符串的偏移地址dd 0        ;對應地址表的索引exit_info:dd (symbol_table_string_end - exit)dd exit     ;字符串偏移地址dd 1        ;索引symbol_table_string_start:print db 'print'exit db 'exit'symbol_table_string_end:symbol_table_end:;用戶程序符號地址表;times N db 0 占位;匹配成功后 會將 調用門選擇子 填充到此表中;根據print,exit對應的索引,放入指定位置;每一項都是 偏移地址:調用門選擇子 , 注:偏移地址全是0symbol_table_addr_begin:times  ((symbol_table_string_start-symbol_table_begin)/USER_APP_EACH_TABLE_ITEM_BYTES)*USER_APP_EACH_TABLE_ITEM_ADDR_BYTES db 0symbol_table_addr_end:header_end:section code vstart=0 align=16start:;用戶程序開始執行;從內核跳轉過來, es段指向頭部;切換棧段mov eax,[es:seg_stack_addr]mov ss,eaxxor esp,esp;切換數據段mov eax,[es:seg_data_addr]mov ds,eax;顯示用戶程序自己的消息;print 偏移地址,段選擇子 在 符號地址表:0;print 回傳遞ds, 也就是 print 會訪問 用戶ds. ;避免不了會在print過程中使用 mov ds,用戶ds,因此在print過程中使用arplpush dword (user_msg_end - user_msg)push dspush dword user_msgcall far [es:symbol_table_addr_begin + 0];跳轉 exit,將回到core中;exit的索引為1, 1 * 8 為其偏移位置;出錯jmp  far [es:symbol_table_addr_begin + 8]code_end:section data vstart=0 align=16user_msg db 'user is running~~'user_msg_end:
data_end:section tail 
tail_end:

引導文件

	;MBR本身占用512字節,起始地址0x7c00
;GDT的位置:0x7c00+0x200(512) = 0x7e00
;GDT的界限:2的16次方:2^16=64k=0x10000
;因此啟動程序的位置放在: 0x7e00+0x10000 = 0x17e00
;啟動程序所在的邏輯扇區固定在0x01;GDT界限:2^16, 每個段描述符8個字節,最多可容納8192個段描述符
;每當加載一個段描述符偽指令: mov es,10_000 ; 
;就會根據提供索引*8+GDT基地址 去獲取此地址的段描述符,如果地址超出了(GDT基地址+GDT界限)則非法;之前已經說過段界限的計算方式,這里再說明一下
;向上的段界限:指的是最大偏移 , 向下的段界限:特指<棧>, 是最小偏移
;段界限是一個數量單位,需要根據G位來計算實際的段界限,G:0 以字節為單位, G:1 以4k為單位
;拿下面的顯存段描述符來說明:界限:0xffff,G=0. 段界限: (0xffff+1)*1-1,最終的段界限:段基地址+段界限:0xb8000 + (0xffff+1)*1-1
;下面棧段,段基址:0x7c00,段界限:0xffffe,G=1. 段界限:(0xffffe+1)*4096-1+1, 最后需要加上段基址:0x7c00+( (0xffffe+1)*4096-1+1 );;section.段名.start 是從文件首(位置0)開始計算的實際物理段地址,相當于從內存地址0開始的物理段地址
;在實模式中,有20位的地址空間
;   1.段地址(section.段名.start)需要4個字節來存放,低20位有效位,假設段地址:0x1000
;   2.如果要計算邏輯段地址,則需要加上程序起始地址, 假設程序起始地址:0x10000
;       1.起始地址:0x10000 低2字節 + 段地址 低2字節 : 0x0000 + 0x1000 = 0x1000
;       2.起始地址:0x10000 高2字節 + 段地址 高2字節 : 0x0001 + 0x0000 = 0x0001, 此加法使用adc,別忘記進位
;       3.mov ax,0x1000, mov dx,0x0001
;       3.邏輯段地址 / 16, 因此低2字節: shr ax,4 = 0x100
;       5.高2字節中只有低4位是有效位(共20位), or ax,dx => 0x1100;在32位保護模式中,有32位地址空間.物理段地址需要4字節存放
;保護模式中不需要計算邏輯段地址,而是要創建一個描述符
;描述符的基地址: section.段名.start(內存地址為0開始的物理地址) + 程序起始地址: section.段名.start+CORE_BASE_ADDR;
;段界限:可以在每個段的結尾增加一個標號來計算每個段的長度, 根據段長度-1 => 界限
;段屬性:根據每個段是什么類型,自己在頭部給出
;根據上述3個屬性可以合成一個描述符MBR_START_ADDR equ 0x7c00   ;MBR起始地址
CORE_BASE_ADDR equ 0x17e00  ;啟動程序的起始地址
CORE_SECTOR equ 0x01        ;啟動程序所在的扇區SECTION mbr vstart=0;實模式;首先在GDT中創建一些需要用到的段描述符;GDT_BASE 被定義成一個4字節的線性地址;獲取GDT的段地址,偏移地址mov ax,[cs:GDT_BASE + MBR_START_ADDR]        ;獲取GDT低16位mov dx,[cs:GDT_BASE + MBR_START_ADDR + 2]    ;GDT高16位mov bx,16div bx;ax:段地址, dx:偏移mov ds,ax       ;GDT段地址: 0x07e0mov bx,dx       ;偏移:0;創建第一個描述符:啞元,0號描述符;每個段描述符占用8個字節mov dword [ds:bx + 0x00],0mov dword [ds:bx + 0x04],0;創建一個代碼段描述符, 基地址:0x7c00, 界限:0x01ff , G=0 字節為粒度, D=1 使用32位操作數;實際最大界限: 0x7c00 + ((0x01ff + 1)*1-1);此段地址0x7c00,與MBR起始地址一樣, 主要是為了執行后面的[bits 32]編譯的32位保護模式下的代碼mov dword [ds:bx + 0x08] , 0x7c0001ffmov dword [ds:bx + 0x0c] , 0x00409800;創建一個顯存段描述符,用于輸出;段基地址:0xb8000,界限:0xffff,G=0, 64k;實際最大界限: 0xb8000 + ((0xffff+1)*1-1)mov dword [ds:bx + 0x10], 0x8000ffffmov dword [ds:bx + 0x14], 0x0040920b;創建一個指向4G內存的數據段,以便訪問任何一個位置;段基地址:0x0, 界限:0xfffff, G=1(以4K為單位);實際最大界限:0x0 + (0xfffff+1)*4k-1mov dword [ds:bx + 0x18],0x0000ffffmov dword [ds:bx + 0x1c],0x00cf9200;創建棧段;段基址: 0x7c00, 段界限:0xffffe,G=1 以4K為單位, B=1 32位操作數,即使用esp;棧的段界限指的是最小偏移;段界限:(0xffffe+1)*4096-1+1 = 0xFFFFF000;實際最終段界限: 0x7c00 + ( (0xffffe+1)*4096-1+1 ) = 0x7c00 + 0xFFFFF000 = 0x6c00;最小偏移:0x6c00 , 最高地址: 0x7c00 + 0xffff_ffff(esp) = 0x7bff;每當 push ,pop, int, iret ,call 等需要操作棧的指令時,都會對越界進行檢查;例如: push eax;需要檢查: 最小偏移地址 <=  (esp - 4) mov dword [ds:bx + 0x20],0x7c00fffemov dword [ds:bx + 0x24],0x00cf9600;設置GDT的界限mov word [cs:GDT_SIZE + MBR_START_ADDR], 39 ; 5個段描述符 : 5*8 -1;設置GDTR, 共6個字節,低2字節GDT_SIZE, 高4字節GDT_BASElgdt [cs:GDT_SIZE + MBR_START_ADDR] ;打開A20地址線;使用0x92端口,把此端口的位1置1in al,0x92or al,0x02out 0x92,al;屏蔽中斷cli;開啟CR0的PE位,PE位在位0mov eax,cr0or eax,1mov cr0 ,eax;一旦開啟PE位,當前在16位保護模式下運行了;當前在16位保護模式中的D位還是0, 想進入32位保護模式,需要加載一個D=1的段描述符,因此需要使用jmp;dword 用于修飾偏移地址:into_protect_mode, 使用4字節的偏移地址;由于當前還是16位,因此會在機器碼前加上前綴0x66, 運行時會使用32位操作數;0x08是段選擇子: 01_000B, 段選擇子2個字節,高13位是索引,2^13=8192,對應GDT最大描述符個數;段選擇子傳遞給cs的過程,偽指令: mov cs, 0x08:;   1.根據 ( 索引 * 8 ) + GDT_BASE 算出地址;   2.查看此地址是否越界( GDT_BASE + GDT_SIZE);   3.未越界則把對應描述符加載到cs的高速緩沖區中;以下面為例:;   1. 0x08 = 01_000B, 索引為1;   2. (1*8)+0x7e00 = 0x7e08, 不越界;   3. 從0x7e08處獲取8個字節加載到cs高速緩沖區中;;相當于偽指令: mov cs,0x08 ; mov eip, into_protect_mode;最后由于段描述符被加載后D=1,因此使用eip, 即 eip = into_protect_modejmp dword 0x08:into_protect_mode;從這里開始將使用32位的操作數,由于當前的CS段描述符D=1
;bits 32 以32位編譯代碼, 默認情況是bits 16以16位編譯
;如果當前沒有bits 32的情況下, 而描述符的D位又為1, 即以32位方式執行16位的指令或許會發生錯誤
;16位的默認操作數是2字節,32位默認操作數為4字節. 即使有些編譯好的指令看上去一樣,但在運行時還是會發生錯誤
[bits 32];以32位操作數來執行指令into_protect_mode:;設置棧段mov eax,0x20mov ss,eaxxor esp,esp;設置數據段,指向4G空間mov eax,0x18mov ds,eax;讀取啟動程序第一個扇區,加載到CORE_BASE_ADDR處mov ebx,CORE_BASE_ADDR  ;寫到ds:ebxmov edi,ebx         ;備份mov eax,CORE_SECTOR     ;指定起始扇區號call read_sector        ;讀取首個扇區, 參數:eax , 寫入到 ds:ebxmov eax,[ds:edi]        ;獲取程序長度mov ecx,512xor edx,edxdiv ecx                 ;總字節數/512, 查看還要讀取幾個扇區or edx,edx              ;余數為0 ?jnz .checkdec eax                 ;余數為0, -1首個已讀的扇區.check:or eax,eax              ;是否讀完 ?jz  .read_done          ;讀完則處理一步,否則繼續讀扇區mov ecx, eax            ;剩余扇區數mov eax, CORE_SECTOR    
.read_left:inc eax                 ;下一個扇區號call read_sector        ;讀取扇區, ebx在read_sector中自增,因此不用管loop .read_left;全部讀完
.read_done:;為所有的段創建段描述符;程序的重定位表中已經把 段基址, 段界限, 段屬性 全部提供;程序被加載的位置:CORE_BASE_ADDR;因此實際的段基址為: CORE_BASE_ADDR + 段基址;當前ds:指向4Gmov ecx,[ds:CORE_BASE_ADDR + 0x0c]         ;重定位表長度(有幾項)mov edi,0x10                               ;重定位表起始地址;保存ebp 原值push ebpmov ebp,0x28                               ;安裝描述符的起始地址,實模式中最后一個地址:0x20;處理重定位表;這里的ebp:把描述符的地址當成段選擇子來使用,只是湊巧,因為后3位(TI,DPL)都是0..process_realloc_table:mov ebx,[ds:CORE_BASE_ADDR + edi]       ;段界限mov eax,[ds:CORE_BASE_ADDR + edi + 4]   ;段基址add eax,CORE_BASE_ADDR                  ;實際段基址mov esi,[ds:CORE_BASE_ADDR + edi + 8]   ;段屬性;合成描述符call make_gd    ;返回edx:eax ;增加到GDT中mov esi,[ds:MBR_START_ADDR + GDT_BASE]  ;GDT起始位置mov [ds:esi + ebp],eax                  ;描述符低32位mov [ds:esi + ebp + 4],edx              ;描述符高32位;把程序頭部的段基址替換成段選擇子mov [ds:CORE_BASE_ADDR + edi + 4],ebpadd ebp,0x08                            ;下一個安裝段描述符的位置add edi,0x0c                            ;下一個段項loop .process_realloc_table;恢復ebppop ebp;修改入口點的段基址,替換成段選擇子mov eax,[ds:CORE_BASE_ADDR + 0x20]mov [ds:CORE_BASE_ADDR + 0x08],eax;傳遞顯存段,棧段,4G數據段的段選擇子mov dword [ds:CORE_BASE_ADDR + 0x40],0x18 ;4g數據段mov dword [ds:CORE_BASE_ADDR + 0x44],0x20 ;棧段mov dword [ds:CORE_BASE_ADDR + 0x48],0x10 ;顯存段mov dword [ds:CORE_BASE_ADDR + 0x4c],0x08 ;MBR段;修改GDT段界限mov word [ds:MBR_START_ADDR + GDT_SIZE], 71 ;9*8-1;重載GDT,使其生效lgdt [ds:MBR_START_ADDR + GDT_SIZE]     ;跳轉到內核程序中,此程序作廢;CORE_BASE_ADDR + 0x04 是目標程序的入口點, 存放了偏移地址,段選擇子;jmp far 用于段間轉移, 將從目標地址處獲取6個字節,低地址:偏移地址, 高地址:段選擇子jmp far [ds:CORE_BASE_ADDR + 0x04];===================================================================================
;參數: eax 段基址, esi 屬性 , ebx 段界限
;返回: edx:eax 描述符 edx 高32位, eax 低32位;合成一個描述符
;段基址:32位, 段界限:20位
make_gd:mov edx,eaxshl eax,16      ;保留低16位,用于合成低地址的32位描述符or ax,bx        ;低32位描述符組合完成and edx,0xffff0000  ;確保高16位基地址rol edx,8           ;;循環左移, 把高8位移動到低8位bswap edx           ;低8位和高8位交換. 至此高32位中的段基地址處理完成and ebx,0x000f0000  ;段界限的低16位已經在上面處理完成,只剩高4位是有效位or edx,ebx          ;段界限處理完成or edx,esi          ;合成屬性ret;===================================================================================
;讀取一個扇區
;參數 : eax 邏輯扇區號 (為了減少傳參)
;默認寫入到 ds:ebx;需要用到的端口號 0x1f2 ~ 0x1f7 , 0x1f0
;端口 0x1f2 = 設置扇區數量
;LBA28有28位 扇區號
;0x1f3 ~ 0x1f6 設置邏輯扇區號, 這些端口都是8位端口,因此只能傳送一個字節來滿足28位扇區號
;0x1f6端口只有低4位是端口號,高前3位111表示LBA模式,后1位表示主盤(0)從盤(1)
;0x1f7 用于讀寫命令,以及讀取硬盤狀態
;一般情況下先檢測0x1f7的狀態,檢測第7位(是否繁忙)和第3位(準備交換數據)的狀態
;即檢測 : 1000_1000b  與 0x1f7讀取回來的狀態, 如果 1000_1000b and 狀態 = 0000_1000b 則可以讀寫
;0x1f0用于讀寫數據, 這是一個16位端口,一次可以讀取2個字節
read_sector:push eaxpush edxpush ecx;保存扇區號push eax;設置扇區數量mov dx,0x1f2mov al,1out dx,al;設置28位扇區號,端口:0x1f3 ~ 0x1f6pop eaxinc dx  ;0x1f3out dx,alinc dx  ;0x1f4shr eax,8out dx,alinc dx  ;0x1f5shr eax,8out dx,alinc dx  ;0x1f6shr eax,8   ;只有低4位是邏輯扇區號有效位and al,0000_1111B   ;確保高4位0or al,0xe0          ;最低位:主盤,高3位:LBA模式 , 1110Bout dx,alinc dx  ;0x1f7  mov al,0x20 ;讀取命令out dx,al;讀取硬盤狀態.read_disk_status:in al,dx        ;從0x1f7讀取狀態and al,1000_1000b   ;是否可以讀取? 第3位如果是1則可以讀取了cmp al,0000_1000b   ;檢測第三位的狀態,如果相等說明可以讀取jnz .read_disk_status   ;不想等則繼續等待;開始讀取扇區,從0x1f0讀取數據,這是一個16位端口,一次讀取2個字節mov dx,0x1f0mov ecx,256     ;一次讀取2個字節, 256*2=512.begin_read:in ax,dxmov [ds:ebx],ax ;把讀取到的2個字節復制到指定位置add ebx,2loop .begin_readpop ecxpop edxpop eaxretGDT_SIZE dw 0           ;GDT界限
GDT_BASE dd 0x7e00      ;GDT的起始地址times 510-($-$$) db 0
db 0x55,0xaa

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/211702.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/211702.shtml
英文地址,請注明出處:http://en.pswp.cn/news/211702.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

[linux] 解壓縮xz

在Linux命令行中解壓縮.xz文件&#xff0c;你可以使用以下幾種方法&#xff1a; 使用unxz工具&#xff1a; unxz filename.xz 這個命令會將filename.xz解壓縮為一個同名的未壓縮文件。如果原文件有其他的擴展名&#xff08;如.tar.xz&#xff09;&#xff0c;那么這個擴展名會被…

關于洛谷P1007最快的方法

P1007 獨木橋 - 洛谷 | 計算機科學教育新生態 (luogu.com.cn) 題目背景 戰爭已經進入到緊要時間。你是運輸小隊長&#xff0c;正在率領運輸部隊向前線運送物資。運輸任務像做題一樣的無聊。你希望找些刺激&#xff0c;于是命令你的士兵們到前方的一座獨木橋上欣賞風景&#xf…

智能儀表板DevExpress Dashboard v23.1 - 支持自定義樣式創建

使用DevExpress Analytics Dashboard&#xff0c;再選擇合適的UI元素&#xff08;圖表、數據透視表、數據卡、計量器、地圖和網格&#xff09;&#xff0c;刪除相應參數、值和序列的數據字段&#xff0c;就可以輕松地為執行主管和商業用戶創建有洞察力、信息豐富的、跨平臺和設…

STM32 配置TIM定時中斷常用庫函數

單片機學習&#xff01; 目錄 ?編輯 1. 函數TIM_DeInit 2. 函數TIM_TimeBaseInit 配置時基單元 3. 函數TIM_TimeBaseStructInit 4. 函數TIM_Cmd 運行控制 5. 函數TIM_ITConfig 中斷輸出控制 6. 時基單元的時鐘選擇函數 6.1 函數TIM_InternalClockConfig 6.2 函數 TIM…

Configuring environment||ROS2環境配置

Goal: This tutorial will show you how to prepare your ROS 2 environment. Tutorial level: Beginner Time: 5 minutes ROS 2 relies on the notion &#xff08;concept&#xff09;of combining workspaces using the shell environment. “Workspace” is a ROS term …

C++進階篇8---智能指針

一、引言 為什么需要智能指針&#xff1f; 在上一篇異常中&#xff0c;關于內存釋放&#xff0c;我們提到過一個問題---當我們申請資源之后&#xff0c;由于異常的執行&#xff0c;代碼可能直接跳過資源的釋放語句到達catch&#xff0c;從而造成內存的泄露&#xff0c;對于這種…

C# Winform 日志系統

目錄 一、效果 1.刷新日志效果 2.單獨日志的分類 3.保存日志的樣式 二、概述 三、日志系統API 1.字段 Debug.IsScrolling Debug.Version Debug.LogMaxLen Debug.LogTitle Debug.IsConsoleShowLog 2.方法 Debug.Log(string) Debug.Log(string, params object[]) …

數據結構之內部排序

目錄 7-1 直接插入排序 輸入格式: 輸出格式: 輸入樣例: 輸出樣例: 7-2 尋找大富翁 輸入格式: 輸出格式: 輸入樣例: 輸出樣例: 7-3 PAT排名匯總 輸入格式: 輸出格式: 輸入樣例: 輸出樣例: 7-4 點贊狂魔 輸入格式&#xff1a; 輸出格式&#xff1a; 輸入樣例&a…

RabbitMQ在國內為什么沒有那么流行?

MQ&#xff08;消息隊列&#xff09;的世界。MQ&#xff0c;就像是一個巨大的郵局&#xff0c;負責在不同服務或應用間傳遞消息。它可以幫助我們解耦系統&#xff0c;提高性能&#xff0c;還能做到異步處理和流量削峰。 基本使用 RabbitMQ是一個開源的消息代理和隊列服務器&a…

spring boot + uniapp 微信公眾號 jsapi 支付

后端支付類 package com.ruoyi.coupon.payment;import com.google.gson.Gson; import com.ruoyi.coupon.payment.dto.PayParamJsapiDto; import com.ruoyi.coupon.payment.dto.RefundParam; import com.ruoyi.coupon.service.ICouponConfigService; import com.wechat.pay.jav…

FFmpeg抽取視頻h264數據重定向

根據視頻重定向技術解析中的 截獲解碼視頻流的思路&#xff0c;首先需要解決如何輸出視頻碼流的問題。 目前只針對h264碼流進行獲取&#xff0c;步驟如下&#xff1a; 打開mp4文件并創建一個空文件用于存儲H264數據 提取一路視頻流資源 循環讀取流中所有的包(AVPacket),為…

redis中使用pipeline批量處理請求提升系統性能

在操作數據庫時&#xff0c;為了加快程序的執行速度&#xff0c;在新增或更新數據時&#xff0c;可以通過批量提交的方式來減少應用和數據庫間的傳輸次數&#xff1b;在redis中也有這樣的技術實現批量處理&#xff0c;也就是管道——Pipeline。它也是通過批量提交數據的方式來實…

線程安全3--wait和notify

文章目錄 wait and notify&#xff08;等待通知機制notify補充 wait and notify&#xff08;等待通知機制 引入wait notify就是為了能夠從應用層面上&#xff0c;干預到多個不同線程代碼的執行順序&#xff0c;這里說的干預&#xff0c;不是影響系統的線程調度策略&#xff08…

uni-app應用設置 可以根據手機屏幕旋轉進行 (橫/豎) 屏切換

首先 我們打開項目的 manifest.json 在左側導航欄中找到 源碼視圖 然后找到 app-plus 配置 在下面加上 "orientation": [//豎屏正方向"portrait-primary",//豎屏反方向"portrait-secondary",//橫屏正方向"landscape-primary",//橫屏…

第57天:django學習(六)

模版之過濾器 語法&#xff1a; {{obj|filter__name:param}} 變量名字|過濾器名稱&#xff1a;變量 default 如果一個變量是false或者為空&#xff0c;使用給定的默認值。否則&#xff0c;使用變量的值。例如&#xff1a; {{ value|default:"nothing"}} length …

IDEA啟動應用時報錯:錯誤: 找不到或無法加載主類 @C:\Users\xxx\AppData\Local\Temp\idea_arg_filexxx

IDEA啟動應用時報錯&#xff0c;詳細錯誤消息如下&#xff1a; C:\devel\jdk1.8.0_201\bin\java.exe -agentlib:jdwptransportdt_socket,address127.0.0.1:65267,suspendy,servern -XX:TieredStopAtLevel1 -noverify -Dspring.output.ansi.enabledalways -Dcom.sun.management…

基于以太坊的智能合約開發Solidity(事件日志篇)

//聲明版本號&#xff08;程序中的版本號要和編譯器版本號一致&#xff09; pragma solidity ^0.5.17; //合約 contract EventTest {//狀態變量uint public Variable;//構造函數constructor() public{Variable 100;}event ValueChanged(uint newValue); //事件聲明event Log(…

ElasticSearch之cat plugins API

命令樣例如下&#xff1a; curl -X GET "https://localhost:9200/_cat/plugins?vtrue&pretty" --cacert $ES_HOME/config/certs/http_ca.crt -u "elastic:ohCxPHQBEs5*lo7F9"執行結果輸出如下&#xff1a; name component version…

class064 Dijkstra算法、分層圖最短路【算法】

class064 Dijkstra算法、分層圖最短路【算法】 算法講解064【必備】Dijkstra算法、分層圖最短路 code1 743. 網絡延遲時間 // Dijkstra算法模版&#xff08;Leetcode&#xff09; // 網絡延遲時間 // 有 n 個網絡節點&#xff0c;標記為 1 到 n // 給你一個列表 times&…

法律服務網站建設效果如何

律師事務所及法律知識咨詢機構等往往是眾多人群需求的服務&#xff0c;服務多樣化及內容多元化&#xff0c;市場中也有大量品牌&#xff0c;在實際消費服務中大多以本地事務所為主&#xff0c;而線上咨詢服務則一般沒有區域限制&#xff0c;同行增多及人們知識獲取渠道增加&…