美文网首页
【内核加载123:裸机c语言,hello world】

【内核加载123:裸机c语言,hello world】

作者: 月下蓑衣江湖夜雨 | 来源:发表于2020-10-27 00:14 被阅读0次

【bootsect.asm】

; 符号定义 ------------------------------------------------------------------------------------------------------------------------------------------------------
BaseOfLoader            equ   09000h                  ; LOADER.BIN 被加载到的位置 ----  段地址
OffsetOfLoader          equ   0100h                   ; LOADER.BIN 被加载到的位置 ----  偏移地址
BeginSectorOfLoader     equ   2                       ; loader起始扇区放在2位置
SectorNumsOfLoader      equ   15                      ; loader占用扇区数

BaseOfKernelFile        equ   08000h                  ; KERNEL.BIN 被加载到的位置 ----  段地址
OffsetOfKernelFile      equ   0h                      ; KERNEL.BIN 被加载到的位置 ----  偏移地址
BeginSectorOfKernelFile equ   20                      ; KernelFile起始扇区放在2位置
SectorNumsOfKernelFile  equ   15                      ; KernelFile占用扇区数
; 符号定义 ------------------------------------------------------------------------------------------------------------------------------------------------------
; 符号定义结束

  org 7c00h
  mov ax, cs
  mov ds, ax
  mov es, ax
  
  mov dh,0 
  mov dl,0
  call SetLn  
  
  mov ax, BootMsg1
  mov cx, 18
  call DspStr
  
  mov dh,1 
  mov dl,0
  call SetLn
  
  mov ax, BootMsg2
  mov cx, 18
  call DspStr
  
  push es
  push bx
  
  ;加载loader
  mov ax, BaseOfLoader
  mov es, ax
  mov bx, OffsetOfLoader
  
  mov ah, 02h
  mov al, SectorNumsOfLoader
  mov ch, 00h
  mov cl, BeginSectorOfLoader
  mov dh, 00h
  mov dl, 80h
  
  int 13h
  
  ;加载内核
  mov ax, BaseOfKernelFile
  mov es, ax
  mov bx, OffsetOfKernelFile
  
  mov ah, 02h
  mov al, SectorNumsOfKernelFile
  mov ch, 00h
  mov cl, BeginSectorOfKernelFile
  mov dh, 00h
  mov dl, 80h
  
  int 13h
  
  pop bx
  pop es
  
  ;跳转到loader
  jmp BaseOfLoader:OffsetOfLoader

SetLn:
  mov ah,2 
  mov bh,0 
  ;mov dh,光标行号 
  ;mov dl,光标列号 
  int 10h 
  
  ret  

DspStr:
  mov bp, ax
  mov ax, 01301h
  mov bx, 000ch
  mov dl, 0
  int 10h

  ret
  
BootMsg1:             db  "[boot] load loader"
BootMsg2:             db  "[boot] load kernel"  

times 510 - ($ - $$) db  0
dw 0xaa55

【loader.asm】

; 符号定义 ------------------------------------------------------------------------------------------------------------------------------------------------------
BaseOfLoader            equ   09000h                  ; LOADER.BIN 被加载到的位置 ----  段地址
OffsetOfLoader          equ   0100h                   ; LOADER.BIN 被加载到的位置 ----  偏移地址
BeginSectorOfLoader     equ   2                       ; loader起始扇区放在2位置
SectorNumsOfLoader      equ   15                      ; loader占用扇区数

BaseOfKernelFile        equ   08000h                  ; KERNEL.BIN 被加载到的位置 ----  段地址
OffsetOfKernelFile      equ   0h                      ; KERNEL.BIN 被加载到的位置 ----  偏移地址
BeginSectorOfKernelFile equ   20                      ; KernelFile起始扇区放在2位置
SectorNumsOfKernelFile  equ   15                      ; KernelFile占用扇区数

BaseOfKernelFilePhyAddr equ   BaseOfKernelFile * 10h
KernelEntryPointPhyAddr equ   030400h                 ; 注意:1、必须与 MAKEFILE 中参数 -Ttext 的值相等!!
                                                      ;       2、这是个地址而非仅仅是个偏移,如果 -Ttext 的值为 0x400400,则它的值也应该是 0x400400。
                                                      
BaseOfStack             equ   0100h
BaseOfLoaderPhyAddr     equ   BaseOfLoader * 10h ;;;;????????????????????????????????????????????     ; LOADER.BIN 被加载到的位置 ----  物理地址 (= BaseOfLoader * 10h)                                                  

DA_CR       equ 9Ah ; 存在的可执行可读代码段属性值    
DA_DRW      equ 92h ; 存在的可读写数据段属性值
DA_32       equ 4000h   ; 32 位段     
DA_LIMIT_4K equ 8000h   ; 段界限粒度为 4K 字节
DA_DPL3     equ   60h   ; DPL = 3
SA_RPL3         equ     3
; 符号定义 ------------------------------------------------------------------------------------------------------------------------------------------------------
; 符号定义结束
; 符号定义 ------------------------------------------------------------------------------------------------------------------------------------------------------
; 符号定义结束

;GDT结构定义
%macro Descriptor 3
    dw  %2 & 0FFFFh                           ;段界限1  (2字节)
    dw  %1 & 0FFFFh                           ;段基址1  (2字节)
    db  (%1 >> 16) & 0FFh                     ;段基址2  (1字节)
    dw  ((%2 >> 8) & 0F00h) | (%3 & 0F0FFh)   ;属性1 + 段界限2 + 属性2 (2字节)
    db  (%1 >> 24) & 0FFh                     ;段基址3  (1字节)
%endmacro ;共8字节

org 0100h
jmp LABEL_BEGIN

[SECTION  .gdt]
; GDT ------------------------------------------------------------------------------------------------------------------------------------------------------------
;                                              段基址            段界限                      属性
LABEL_GDT:              Descriptor             0,                    0,                          0                          ; 空描述符
LABEL_DESC_FLAT_C:      Descriptor             0,                    0fffffh,        DA_CR  | DA_32 | DA_LIMIT_4K           ; 0 ~ 4G
LABEL_DESC_FLAT_RW:     Descriptor             0,                    0fffffh,        DA_DRW | DA_32 | DA_LIMIT_4K           ; 0 ~ 4G
LABEL_DESC_VIDEO:       Descriptor             0B8000h,              0ffffh,         DA_DRW | DA_DPL3                       ; 显存首地址
; GDT ------------------------------------------------------------------------------------------------------------------------------------------------------------
; GDT结束

GdtLen  equ  $ - LABEL_GDT   ;GDT长度
GdtPtr  dw   GdtLen          ;GDT界限
        db   0               ;GDT基地址

; GDT 选择子 ----------------------------------------------------------------------------------
SelectorFlatC       equ LABEL_DESC_FLAT_C   - LABEL_GDT
SelectorFlatRW      equ LABEL_DESC_FLAT_RW  - LABEL_GDT
SelectorVideo       equ LABEL_DESC_VIDEO    - LABEL_GDT + SA_RPL3
; GDT 选择子 ----------------------------------------------------------------------------------
;END of [SECTION .gdt]

[SECTION  .s16]
[BITS  16]
LABEL_BEGIN:
    mov ax, cs
    mov ds, ax
    mov es, ax
    mov ss, ax
    mov sp, BaseOfStack
    
    mov dh,2 
    mov dl,0
    call SetLn

    mov ax, LoaderMsg
    mov cx, 27
    call DspStr
    
    ;加载gdtr
    lgdt  [GdtPtr]

    ;关中断
    cli
    
    ;打开地址线A20
    in  al,  92h
    or  al,  00000010b
    out 92h, al
    
    ;准备切换到保护模式
    mov  eax,  cr0
    or   eax,  1
    mov  cr0,  eax
    
    ;真正进入保护模式
    jmp dword SelectorFlatC:(BaseOfLoaderPhyAddr+LABEL_PM_START)    ;执行这一句会把SelectorCode32装入cs,并跳转到SelectCode32:0处

SetLn:
  mov ah,2 
  mov bh,0 
  ;mov dh,光标行号 
  ;mov dl,光标列号 
  int 10h 
  
  ret   

DspStr:
  mov bp, ax
  mov ax, 01301h
  mov bx, 000ch
  mov dl, 0
  int 10h

  ret
  
LoaderMsg:   db  "[loader](real mode) dsp str"
        
[SECTION .s32]  ;32位代码段,由实模式跳入
ALIGN  32
[BITS  32]
LABEL_PM_START:
    mov  ax,  SelectorVideo
    mov  gs,  ax
    mov  ax,  SelectorFlatRW
    mov  ds,  ax
    mov  es,  ax
    mov  fs,  ax
    mov  ss,  ax
    mov  esp, TopOfStack
    
    mov  edi, (80*10 + 0) * 2   ;屏幕第10行,第0列
    mov  ah,  0Ch               ;0000:黑底  1100:红字
    mov  al,  'K'
    mov  [gs:edi], ax 
    
    jmp $
    
    ;;call InitKernel

    ;;jmp   SelectorFlatC:KernelEntryPointPhyAddr   ; 正式进入内核 *
    
; InitKernel ---------------------------------------------------------------------------------
; 将 KERNEL.BIN 的内容经过整理对齐后放到新的位置
; --------------------------------------------------------------------------------------------
InitKernel: ; 遍历每一个 Program Header,根据 Program Header 中的信息来确定把什么放进内存,放到什么位置,以及放多少。
    xor esi, esi
    mov cx, word [BaseOfKernelFilePhyAddr + 2Ch]; ┓ ecx <- pELFHdr->e_phnum
    movzx   ecx, cx                             ; ┛
    mov esi, [BaseOfKernelFilePhyAddr + 1Ch]    ; esi <- pELFHdr->e_phoff
    add esi, BaseOfKernelFilePhyAddr            ; esi <- OffsetOfKernel + pELFHdr->e_phoff
.Begin:
    mov eax, [esi + 0]
    cmp eax, 0                                  ; PT_NULL
    jz  .NoAction
    push    dword [esi + 010h]                  ; size  ┓
    mov eax, [esi + 04h]                        ;       ┃
    add eax, BaseOfKernelFilePhyAddr            ;       ┣ ::memcpy( (void*)(pPHdr->p_vaddr),
    push    eax                                 ; src   ┃       uchCode + pPHdr->p_offset,
    push    dword [esi + 08h]                   ; dst   ┃       pPHdr->p_filesz;
    call    MemCpy                              ;       ┃
    add esp, 12                                 ;       ┛
.NoAction:
    add esi, 020h                               ; esi += pELFHdr->e_phentsize
    dec ecx
    jnz .Begin

    ret
; InitKernel ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^


; ------------------------------------------------------------------------
; 内存拷贝,仿 memcpy
; ------------------------------------------------------------------------
; void* MemCpy(void* es:pDest, void* ds:pSrc, int iSize);
; ------------------------------------------------------------------------
MemCpy:
    push    ebp
    mov     ebp, esp

    push    esi
    push    edi
    push    ecx

    mov edi, [ebp + 8]  ; Destination
    mov esi, [ebp + 12] ; Source
    mov ecx, [ebp + 16] ; Counter
.1:
    cmp ecx, 0      ; 判断计数器
    jz  .2          ; 计数器为零时跳出

    mov al, [ds:esi]        ; ┓
    inc esi                 ; ┃
                            ; ┣ 逐字节移动
    mov byte [es:edi], al   ; ┃
    inc edi                 ; ┛

    dec ecx     ; 计数器减一
    jmp .1      ; 循环
.2:
    mov eax, [ebp + 8]  ; 返回值

    pop ecx
    pop edi
    pop esi
    mov esp, ebp
    pop ebp

    ret         ; 函数结束,返回
; MemCpy 结束-------------------------------------------------------------    
;END of [SECTION .s32]


; SECTION .data1 之开始 ---------------------------------------------------------------------------------------------
[SECTION .data1]
ALIGN   32
LABEL_DATA:
; 堆栈就在数据段的末尾
StackSpace: times   1000h   db  0
TopOfStack  equ     BaseOfLoaderPhyAddr + $ ; 栈顶
; SECTION .data1 之结束 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
;GDT结构定义
%macro Descriptor 3
    dw  %2 & 0FFFFh                           ;段界限1  (2字节)
    dw  %1 & 0FFFFh                           ;段基址1  (2字节)
    db  (%1 >> 16) & 0FFh                     ;段基址2  (1字节)
    dw  ((%2 >> 8) & 0F00h) | (%3 & 0F0FFh)   ;属性1 + 段界限2 + 属性2 (2字节)
    db  (%1 >> 24) & 0FFh                     ;段基址3  (1字节)
%endmacro ;共8字节

org 0100h
jmp LABEL_BEGIN

[SECTION  .gdt]
;GDT
LABEL_GDT:           Descriptor 0, 0, 0                            ;空描述符
LABEL_DESC_CODE32:   Descriptor 0, SegCode32Len - 1, DA_C + DA_32  ;代码段,32位
LABEL_DESC_VIDEO:    Descriptor 0B8000h, 0ffffh,  DA_DRW           ;显存首地址
; GDT结束

GdtLen  equ  $ - LABEL_GDT   ;GDT长度
GdtPtr  dw   GdtLen          ;GDT界限
        dd   0               ;GDT基地址

;GDT选择子     
SelectorCode32    equ  LABEL_DESC_CODE32  - LABEL_GDT
SelectorVideo     equ  LABEL_DESC_VIDEO   - LABEL_GDT
;END of [SECTION .gdt]

[SECTION  .s16]
[BITS  16]
LABEL_BEGIN:
    mov  ax,  cs
    mov  ds,  ax
    mov  es,  ax
    mov  ss,  ax
    mov  sp,  0100h

    ;初始化32位代码段描述符
    xor  eax,  eax
    mov  ax, cs
    shl  eax, 4
    add  eax, LABEL_SEG_CODE32
    mov  word [LABEL_DESC_CODE32 + 2], ax
    shr  eax, 16
    mov  byte [LABEL_DESC_CODE32 + 4], al
    mov  byte [LABEL_DESC_CODE32 + 7], ah

    ;为加载gdtr做准备
    xor  eax, eax
    mov  ax,  ds
    shl  eax,  4
    add  eax, LABEL_GDT            ; eax <- gdt 基地址
    mov  dword [GdtPtr + 2], eax   ;[GdtPtr +2]<-gdt基地址

    ;加载gdtr
    lgdt  [GdtPtr]

    ;关中断
    cli
    
    ;打开地址线A20
    in  al,  92h
    or  al,  00000010b
    out 92h, al
    
    ;准备切换到保护模式
    mov  eax,  cr0
    or   eax,  1
    mov  cr0,  eax

    ;真正进入保护模式
    jmp  dword  SelectorCode32:0    ;执行这一句会把SelectorCode32装入cs,并跳转到SelectCode32:0处
        
[SECTION .s32]  ;32位代码段,由实模式跳入
[BITS  32]

LABEL_SEG_CODE32:
    mov  ax,  SelectorVideo
    mov  gs,  ax                ;视频段选择子(目的)
    
    mov  edi, (80*2 + 0) * 2   ;屏幕第10行,第0列
    mov  ah,  0Ch               ;0000:黑底  1100:红字
    mov  al,  'z'
    mov  [gs:edi], ax 
    mov  edi, (80*2 + 1) * 2   ;屏幕第10行,第0列
    mov  ah,  0Ch               ;0000:黑底  1100:红字
    mov  al,  'k'
    mov  [gs:edi], ax 
    
    ;到此为止
    jmp $
    
SegCode32Len  equ  $ - LABEL_SEG_CODE32
DA_32         equ  4000h    ; 32 位段 0100 0000 0000 0000
DA_C          equ  98h      ; 存在的只执行代码段属性值 1001 1000
DA_DRW        equ  92h      ; 存在的可读写数据段属性值 1001 0010
;END of [SECTION .s32]
保护模式打印

如此跳转不行!

BaseOfLoader            equ   09000h                  ; LOADER.BIN 被加载到的位置 ----  段地址
BaseOfLoaderPhyAddr     equ   BaseOfLoader * 10h ;;;;????????????????????????????????????????????     ; LOADER.BIN 被加载到的位置 ----  物理地址 (= BaseOfLoader * 10h)    

;GDT结构定义
%macro Descriptor 3
    dw  %2 & 0FFFFh                           ;段界限1  (2字节)
    dw  %1 & 0FFFFh                           ;段基址1  (2字节)
    db  (%1 >> 16) & 0FFh                     ;段基址2  (1字节)
    dw  ((%2 >> 8) & 0F00h) | (%3 & 0F0FFh)   ;属性1 + 段界限2 + 属性2 (2字节)
    db  (%1 >> 24) & 0FFh                     ;段基址3  (1字节)
%endmacro ;共8字节

org 0100h
jmp LABEL_BEGIN

[SECTION  .gdt]
;GDT
LABEL_GDT:           Descriptor 0, 0, 0                            ;空描述符
LABEL_DESC_CODE32:   Descriptor 0, SegCode32Len - 1, DA_C + DA_32  ;代码段,32位
LABEL_DESC_VIDEO:    Descriptor 0B8000h, 0ffffh,  DA_DRW           ;显存首地址
; GDT结束

GdtLen  equ  $ - LABEL_GDT   ;GDT长度
GdtPtr  dw   GdtLen          ;GDT界限
        dd   0               ;GDT基地址

;GDT选择子     
SelectorCode32    equ  LABEL_DESC_CODE32  - LABEL_GDT
SelectorVideo     equ  LABEL_DESC_VIDEO   - LABEL_GDT
;END of [SECTION .gdt]

[SECTION  .s16]
[BITS  16]
LABEL_BEGIN:
    mov  ax,  cs
    mov  ds,  ax
    mov  es,  ax
    mov  ss,  ax
    mov  sp,  0100h

    ;初始化32位代码段描述符
    ;xor  eax,  eax
    ;mov  ax, cs
    ;shl  eax, 4
    ;add  eax, LABEL_SEG_CODE32
    ;mov  word [LABEL_DESC_CODE32 + 2], ax
    ;shr  eax, 16
    ;mov  byte [LABEL_DESC_CODE32 + 4], al
    ;mov  byte [LABEL_DESC_CODE32 + 7], ah

    ;为加载gdtr做准备
    xor  eax, eax
    mov  ax,  ds
    shl  eax,  4
    add  eax, LABEL_GDT            ; eax <- gdt 基地址
    mov  dword [GdtPtr + 2], eax   ;[GdtPtr +2]<-gdt基地址

    ;加载gdtr
    lgdt  [GdtPtr]

    ;关中断
    cli
    
    ;打开地址线A20
    in  al,  92h
    or  al,  00000010b
    out 92h, al
    
    ;准备切换到保护模式
    mov  eax,  cr0
    or   eax,  1
    mov  cr0,  eax

    ;真正进入保护模式
    ;jmp  dword  SelectorCode32:0    ;执行这一句会把SelectorCode32装入cs,并跳转到SelectCode32:0处
    jmp  dword  SelectorCode32:(BaseOfLoaderPhyAddr+LABEL_SEG_CODE32)
        
[SECTION .s32]  ;32位代码段,由实模式跳入
[BITS  32]

LABEL_SEG_CODE32:
    mov  ax,  SelectorVideo
    mov  gs,  ax                ;视频段选择子(目的)
    
    mov  edi, (80*2 + 0) * 2   ;屏幕第10行,第0列
    mov  ah,  0Ch               ;0000:黑底  1100:红字
    mov  al,  'z'
    mov  [gs:edi], ax 
    mov  edi, (80*2 + 1) * 2   ;屏幕第10行,第0列
    mov  ah,  0Ch               ;0000:黑底  1100:红字
    mov  al,  'k'
    mov  [gs:edi], ax 
    
    ;到此为止
    jmp $
    
SegCode32Len  equ  $ - LABEL_SEG_CODE32
DA_32         equ  4000h    ; 32 位段 0100 0000 0000 0000
DA_C          equ  98h      ; 存在的只执行代码段属性值 1001 1000
DA_DRW        equ  92h      ; 存在的可读写数据段属性值 1001 0010
;END of [SECTION .s32]

应该是段的长度不行,保护模式

BaseOfLoader            equ   09000h                  ; LOADER.BIN 被加载到的位置 ----  段地址
BaseOfLoaderPhyAddr     equ   BaseOfLoader * 10h ;;;;????????????????????????????????????????????     ; LOADER.BIN 被加载到的位置 ----  物理地址 (= BaseOfLoader * 10h)    

;GDT结构定义
%macro Descriptor 3
    dw  %2 & 0FFFFh                           ;段界限1  (2字节)
    dw  %1 & 0FFFFh                           ;段基址1  (2字节)
    db  (%1 >> 16) & 0FFh                     ;段基址2  (1字节)
    dw  ((%2 >> 8) & 0F00h) | (%3 & 0F0FFh)   ;属性1 + 段界限2 + 属性2 (2字节)
    db  (%1 >> 24) & 0FFh                     ;段基址3  (1字节)
%endmacro ;共8字节

org 0100h
jmp LABEL_BEGIN

[SECTION  .gdt]
;GDT
LABEL_GDT:           Descriptor 0, 0, 0                            ;空描述符
LABEL_DESC_CODE32:   Descriptor 0,0fffffh, DA_C + DA_32  ;代码段,32位
LABEL_DESC_VIDEO:    Descriptor 0B8000h, 0ffffh,  DA_DRW           ;显存首地址
; GDT结束

GdtLen  equ  $ - LABEL_GDT   ;GDT长度
GdtPtr  dw   GdtLen          ;GDT界限
        dd   0               ;GDT基地址

;GDT选择子     
SelectorCode32    equ  LABEL_DESC_CODE32  - LABEL_GDT
SelectorVideo     equ  LABEL_DESC_VIDEO   - LABEL_GDT
;END of [SECTION .gdt]

[SECTION  .s16]
[BITS  16]
LABEL_BEGIN:
    mov  ax,  cs
    mov  ds,  ax
    mov  es,  ax
    mov  ss,  ax
    mov  sp,  0100h

    ;初始化32位代码段描述符
    ;xor  eax,  eax
    ;mov  ax, cs
    ;shl  eax, 4
    ;add  eax, LABEL_SEG_CODE32
    ;mov  word [LABEL_DESC_CODE32 + 2], ax
    ;shr  eax, 16
    ;mov  byte [LABEL_DESC_CODE32 + 4], al
    ;mov  byte [LABEL_DESC_CODE32 + 7], ah

    ;为加载gdtr做准备
    xor  eax, eax
    mov  ax,  ds
    shl  eax,  4
    add  eax, LABEL_GDT            ; eax <- gdt 基地址
    mov  dword [GdtPtr + 2], eax   ;[GdtPtr +2]<-gdt基地址

    ;加载gdtr
    lgdt  [GdtPtr]

    ;关中断
    cli
    
    ;打开地址线A20
    in  al,  92h
    or  al,  00000010b
    out 92h, al
    
    ;准备切换到保护模式
    mov  eax,  cr0
    or   eax,  1
    mov  cr0,  eax

    ;真正进入保护模式
    ;jmp  dword  SelectorCode32:0    ;执行这一句会把SelectorCode32装入cs,并跳转到SelectCode32:0处
    jmp  dword  SelectorCode32:(BaseOfLoaderPhyAddr+LABEL_SEG_CODE32)
        
[SECTION .s32]  ;32位代码段,由实模式跳入
[BITS  32]

LABEL_SEG_CODE32:
    mov  ax,  SelectorVideo
    mov  gs,  ax                ;视频段选择子(目的)
    
    mov  edi, (80*2 + 0) * 2   ;屏幕第10行,第0列
    mov  ah,  0Ch               ;0000:黑底  1100:红字
    mov  al,  'z'
    mov  [gs:edi], ax 
    mov  edi, (80*2 + 1) * 2   ;屏幕第10行,第0列
    mov  ah,  0Ch               ;0000:黑底  1100:红字
    mov  al,  'k'
    mov  [gs:edi], ax
    mov  edi, (80*2 + 2) * 2   ;屏幕第10行,第0列
    mov  ah,  0Ch               ;0000:黑底  1100:红字
    mov  al,  'd'
    mov  [gs:edi], ax   
    
    ;到此为止
    jmp $
    
SegCode32Len  equ  $ - LABEL_SEG_CODE32
DA_32         equ  4000h    ; 32 位段 0100 0000 0000 0000
DA_C          equ  98h      ; 存在的只执行代码段属性值 1001 1000
DA_DRW        equ  92h      ; 存在的可读写数据段属性值 1001 0010
;END of [SECTION .s32]
跳转

因此,之前的跳转地址是对的,但是段的长度不对!保护模式下,偏移地址大于段的长度是不行的!
更改1

BaseOfLoader            equ   09000h                  ; LOADER被加载到的位置 ----  段地址
BaseOfLoaderPhyAddr     equ   BaseOfLoader * 10h      ; 被加载段的物理地址    

DA_32                   equ   4000h                   ; 32 位段 0100 0000 0000 0000
DA_C                    equ   98h                     ; 存在的只执行代码段属性值 1001 1000
DA_DRW                  equ   92h                     ; 存在的可读写数据段属性值 1001 0010

;GDT结构定义
%macro Descriptor 3
    dw  %2 & 0FFFFh                           ;段界限1   (2字节)
    dw  %1 & 0FFFFh                           ;段基址1  (2字节)
    db  (%1 >> 16) & 0FFh                     ;段基址2  (1字节)
    dw  ((%2 >> 8) & 0F00h) | (%3 & 0F0FFh)   ;属性1 + 段界限2 + 属性2 (2字节)
    db  (%1 >> 24) & 0FFh                     ;段基址3  (1字节)
%endmacro ;共8字节

org 0100h
jmp LABEL_BEGIN

[SECTION  .gdt]
;GDT
LABEL_GDT:           Descriptor 0, 0, 0                            ;空描述符
LABEL_DESC_FLAT_C:   Descriptor 0,0fffffh, DA_C + DA_32            ;代码段,32位
LABEL_DESC_FLAT_RW:  Descriptor 0,0fffffh, DA_DRW | DA_32          ; 0 ~ 4G
LABEL_DESC_VIDEO:    Descriptor 0B8000h, 0ffffh,  DA_DRW           ;显存首地址
; GDT结束

GdtLen  equ  $ - LABEL_GDT   ;GDT长度
GdtPtr  dw   GdtLen          ;GDT界限
        dd   0               ;GDT基地址

;GDT选择子     
SelectorFlatC    equ  LABEL_DESC_FLAT_C   - LABEL_GDT
SelectorFlatRW   equ  LABEL_DESC_FLAT_RW  - LABEL_GDT
SelectorVideo    equ  LABEL_DESC_VIDEO    - LABEL_GDT
;END of [SECTION .gdt]

[SECTION  .s16]
[BITS  16]
LABEL_BEGIN:
    mov  ax,  cs
    mov  ds,  ax
    mov  es,  ax
    mov  ss,  ax
    mov  sp,  0100h

    ;代码段的段基址就设置为0,而对齐LABEL_PM_START的物理地址
    ;;;;;;初始化32位代码段描述符
    ;;;;;;xor  eax,  eax
    ;;;;;;mov  ax, cs
    ;;;;;;shl  eax, 4
    ;;;;;;add  eax, LABEL_PM_START
    ;;;;;;mov  word [LABEL_DESC_FLAT_C + 2], ax
    ;;;;;;shr  eax, 16
    ;;;;;;mov  byte [LABEL_DESC_FLAT_C + 4], al
    ;;;;;;mov  byte [LABEL_DESC_FLAT_C + 7], ah

    ;为加载gdtr做准备
    xor  eax, eax
    mov  ax,  ds
    shl  eax,  4
    add  eax, LABEL_GDT            ;eax <- gdt 基地址
    mov  dword [GdtPtr + 2], eax   ;[GdtPtr +2]<-gdt基地址

    ;加载gdtr
    lgdt  [GdtPtr]

    ;关中断
    cli
    
    ;打开地址线A20
    in  al,  92h
    or  al,  00000010b
    out 92h, al
    
    ;准备切换到保护模式
    mov  eax,  cr0
    or   eax,  1
    mov  cr0,  eax

    ;真正进入保护模式
    ;jmp  dword  SelectorCode32:0                                     ; 执行这一句会把SelectorCode32装入cs,并跳转到SelectCode32:0处
    jmp  dword  SelectorFlatC:(BaseOfLoaderPhyAddr + LABEL_PM_START)  ; 段基址是0,所以偏移地址,是实际的物理地址
        
[SECTION .s32]  ;32位代码段,由实模式跳入
[BITS  32]

LABEL_PM_START:
    mov  ax,  SelectorVideo
    mov  gs,  ax                ;视频段选择子(目的)
    
    mov  edi, (80*2 + 0) * 2    ;屏幕第2行,第0列
    mov  ah,  0Ch               ;0000:黑底  1100:红字
    mov  al,  'z'
    mov  [gs:edi], ax 
    
    mov  edi, (80*2 + 1) * 2    ;屏幕第2行,第1列
    mov  al,  'k'
    mov  [gs:edi], ax
    
    mov  edi, (80*2 + 2) * 2    ;屏幕第2行,第2列
    mov  al,  'd'
    mov  [gs:edi], ax   
    
    mov  edi, (80*2 + 3) * 2    ;屏幕第2行,第3列
    mov  al,  '2'
    mov  [gs:edi], ax   
    
    ;到此为止
    jmp $
;END of [SECTION .s32]
ok
BaseOfLoader            equ   09000h                  ; LOADER被加载到的位置 ----  段地址
BaseOfLoaderPhyAddr     equ   BaseOfLoader * 10h      ; 被加载段的物理地址 

BaseOfKernelFile        equ   08000h                  ; KERNEL.BIN 被加载到的位置 ----  段地址 
BaseOfKernelFilePhyAddr equ   BaseOfKernelFile * 10h
KernelEntryPointPhyAddr equ   030400h                 ; 注意:1、必须与 MAKEFILE 中参数 -Ttext 的值相等!!
                                                      ;       2、这是个地址而非仅仅是个偏移,如果 -Ttext 的值为 0x400400,则它的值也应该是 0x400400。  

DA_32                   equ   4000h                   ; 32 位段 0100 0000 0000 0000
DA_C                    equ   98h                     ; 存在的只执行代码段属性值 1001 1000
DA_DRW                  equ   92h                     ; 存在的可读写数据段属性值 1001 0010

;GDT结构定义
%macro Descriptor 3
    dw  %2 & 0FFFFh                           ;段界限1   (2字节)
    dw  %1 & 0FFFFh                           ;段基址1  (2字节)
    db  (%1 >> 16) & 0FFh                     ;段基址2  (1字节)
    dw  ((%2 >> 8) & 0F00h) | (%3 & 0F0FFh)   ;属性1 + 段界限2 + 属性2 (2字节)
    db  (%1 >> 24) & 0FFh                     ;段基址3  (1字节)
%endmacro ;共8字节

org 0100h
jmp LABEL_BEGIN

[SECTION  .gdt]
;GDT
LABEL_GDT:           Descriptor 0, 0, 0                            ;空描述符
LABEL_DESC_FLAT_C:   Descriptor 0,0fffffh, DA_C + DA_32            ;代码段,32位
LABEL_DESC_FLAT_RW:  Descriptor 0,0fffffh, DA_DRW | DA_32          ; 0 ~ 4G
LABEL_DESC_VIDEO:    Descriptor 0B8000h, 0ffffh,  DA_DRW           ;显存首地址
; GDT结束

GdtLen  equ  $ - LABEL_GDT   ;GDT长度
GdtPtr  dw   GdtLen          ;GDT界限
        dd   0               ;GDT基地址

;GDT选择子     
SelectorFlatC    equ  LABEL_DESC_FLAT_C   - LABEL_GDT
SelectorFlatRW   equ  LABEL_DESC_FLAT_RW  - LABEL_GDT
SelectorVideo    equ  LABEL_DESC_VIDEO    - LABEL_GDT
;END of [SECTION .gdt]

[SECTION  .s16]
[BITS  16]
LABEL_BEGIN:
    mov  ax,  cs
    mov  ds,  ax
    mov  es,  ax
    mov  ss,  ax
    mov  sp,  0100h

    ;代码段的段基址就设置为0,而对齐LABEL_PM_START的物理地址
    ;;;;;;初始化32位代码段描述符
    ;;;;;;xor  eax,  eax
    ;;;;;;mov  ax, cs
    ;;;;;;shl  eax, 4
    ;;;;;;add  eax, LABEL_PM_START
    ;;;;;;mov  word [LABEL_DESC_FLAT_C + 2], ax
    ;;;;;;shr  eax, 16
    ;;;;;;mov  byte [LABEL_DESC_FLAT_C + 4], al
    ;;;;;;mov  byte [LABEL_DESC_FLAT_C + 7], ah

    ;为加载gdtr做准备
    xor  eax, eax
    mov  ax,  ds
    shl  eax,  4
    add  eax, LABEL_GDT            ;eax <- gdt 基地址
    mov  dword [GdtPtr + 2], eax   ;[GdtPtr +2]<-gdt基地址

    ;加载gdtr
    lgdt  [GdtPtr]

    ;关中断
    cli
    
    ;打开地址线A20
    in  al,  92h
    or  al,  00000010b
    out 92h, al
    
    ;准备切换到保护模式
    mov  eax,  cr0
    or   eax,  1
    mov  cr0,  eax

    ;真正进入保护模式
    ;jmp  dword  SelectorCode32:0                                     ; 执行这一句会把SelectorCode32装入cs,并跳转到SelectCode32:0处
    jmp  dword  SelectorFlatC:(BaseOfLoaderPhyAddr + LABEL_PM_START)  ; 段基址是0,所以偏移地址,是实际的物理地址
        
[SECTION .s32]  ;32位代码段,由实模式跳入
[BITS  32]

LABEL_PM_START:
    mov  esp, TopOfStack
    
    mov  ax,  SelectorVideo
    mov  gs,  ax                ;视频段选择子(目的)
    
    mov  edi, (80*2 + 0) * 2    ;屏幕第2行,第0列
    mov  ah,  0Ch               ;0000:黑底  1100:红字
    mov  al,  'z'
    mov  [gs:edi], ax 
    
    mov  edi, (80*2 + 1) * 2    ;屏幕第2行,第1列
    mov  al,  'k'
    mov  [gs:edi], ax
    
    mov  edi, (80*2 + 2) * 2    ;屏幕第2行,第2列
    mov  al,  'd'
    mov  [gs:edi], ax   
    
    mov  edi, (80*2 + 3) * 2    ;屏幕第2行,第3列
    mov  al,  '2'
    mov  [gs:edi], ax   
    
    ;到此为止
    ;jmp $
    call InitKernel
    jmp SelectorFlatC:KernelEntryPointPhyAddr   ; 正式进入内核 *
    
; InitKernel ---------------------------------------------------------------------------------
; 将 KERNEL.BIN 的内容经过整理对齐后放到新的位置
; --------------------------------------------------------------------------------------------
InitKernel: ; 遍历每一个 Program Header,根据 Program Header 中的信息来确定把什么放进内存,放到什么位置,以及放多少。
    xor esi, esi
    mov cx, word [BaseOfKernelFilePhyAddr + 2Ch]; ┓ ecx <- pELFHdr->e_phnum
    movzx   ecx, cx                             ; ┛
    mov esi, [BaseOfKernelFilePhyAddr + 1Ch]    ; esi <- pELFHdr->e_phoff
    add esi, BaseOfKernelFilePhyAddr            ; esi <- OffsetOfKernel + pELFHdr->e_phoff
.Begin:
    mov eax, [esi + 0]
    cmp eax, 0                                  ; PT_NULL
    jz  .NoAction
    push    dword [esi + 010h]                  ; size  ┓
    mov eax, [esi + 04h]                        ;       ┃
    add eax, BaseOfKernelFilePhyAddr            ;       ┣ ::memcpy( (void*)(pPHdr->p_vaddr),
    push    eax                                 ; src   ┃       uchCode + pPHdr->p_offset,
    push    dword [esi + 08h]                   ; dst   ┃       pPHdr->p_filesz;
    call    MemCpy                              ;       ┃
    add esp, 12                                 ;       ┛
.NoAction:
    add esi, 020h                               ; esi += pELFHdr->e_phentsize
    dec ecx
    jnz .Begin

    ret
; InitKernel ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^


; ------------------------------------------------------------------------
; 内存拷贝,仿 memcpy
; ------------------------------------------------------------------------
; void* MemCpy(void* es:pDest, void* ds:pSrc, int iSize);
; ------------------------------------------------------------------------
MemCpy:
    push    ebp
    mov     ebp, esp

    push    esi
    push    edi
    push    ecx

    mov edi, [ebp + 8]  ; Destination
    mov esi, [ebp + 12] ; Source
    mov ecx, [ebp + 16] ; Counter
.1:
    cmp ecx, 0      ; 判断计数器
    jz  .2          ; 计数器为零时跳出

    mov al, [ds:esi]        ; ┓
    inc esi                 ; ┃
                            ; ┣ 逐字节移动
    mov byte [es:edi], al   ; ┃
    inc edi                 ; ┛

    dec ecx     ; 计数器减一
    jmp .1      ; 循环
.2:
    mov eax, [ebp + 8]  ; 返回值

    pop ecx
    pop edi
    pop esi
    mov esp, ebp
    pop ebp

    ret         ; 函数结束,返回
; MemCpy 结束-------------------------------------------------------------    
;END of [SECTION .s32]

; SECTION .data1 之开始 ---------------------------------------------------------------------------------------------
[SECTION .data1]
ALIGN   32
LABEL_DATA:
; 堆栈就在数据段的末尾
StackSpace: times   1000h   db  0
TopOfStack  equ     BaseOfLoaderPhyAddr + $ ; 栈顶
; SECTION .data1 之结束 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

不行!!!
【挂在InitKernel】

读取elf文件

https://blog.csdn.net/edonlii/article/details/8779075
【读取ELF文件头】 readelf -h kernel.bin

readelf -h kernel.bin

【显示程序头表】readelf -l kernel.bin


readelf -l kernel.bin

【读取节头表】readelf -S kernel.bin


readelf -S kernel.bin

https://blog.csdn.net/mergerly/article/details/94585901

ELF文件格式

32位ELF文件中常用的数据格式

名称 大小 对齐 用途
Elf32_Addr 4 4 无符号程序地址
Elf32_Half 2 2 无符号中等整数
Elf32_Off 4 4 无符号文件偏移
Elf32_SWord 4 4 有符号大整数
Elf32_Word 4 4 无符号大整数
unsigned char 1 1 无符号小整数

ELF Header头结构


#define EI_NIDENT 16
typedef struct {
       unsigned char e_ident[EI_NIDENT];    16 // ELF的一些标识信息,前四位为.ELF,其他的信息比如大小端等
       ELF32_Half e_type;         2       
       ELF32_Half e_machine;     2            // 文件的目标体系架构,比如ARM
       ELF32_Word e_version;  4
       ELF32__Addr e_entry;  4
       ELF32_Off e_phoff;  4
       ELF32_Off e_shoff;   4
       ELF32_Word e_flags;  4
       ELF32_Half e_ehsize;  2
       ELF32_Half e_phentsize;  2
       ELF32_Half e_phnum;    2 // + 44 程序头部表的数量
       ELF32_Half e_shentsize;
       ELF32_Half e_shnum;
       ELF32_Half e_shstrndx;
}Elf32_Ehdr;

实模式下分段主要是为了扩展寻址空间,16位—>20位;
保护模式下,分段是必选的,分页是可选的;
分段的主要目的是权限,只读权限,写权限,执行权限(分级);

typedef struct {  
    Elf32_Word p_type;           4//此数组元素描述的段的类型,或者如何解释此数组元素的信息。 
    Elf32_Off  p_offset;          4 //此成员给出从文件头到该段第一个字节的偏移
    Elf32_Addr p_vaddr;         4//此成员给出段的第一个字节将被放到内存中的虚拟地址
    Elf32_Addr p_paddr;        4//此成员仅用于与物理地址相关的系统中。System V忽略所有应用程序的物理地址信息。
    Elf32_Word p_filesz;         4//此成员给出段在文件映像中所占的字节数。可以为0。
    Elf32_Word p_memsz;     4//此成员给出段在内存映像中占用的字节数。可以为0。
    Elf32_Word p_flags;         4//此成员给出与段相关的标志。
    Elf32_Word p_align;        4//此成员给出段在文件中和内存中如何对齐。
} Elf32_phdr;

https://www.cnblogs.com/joey-hua/archive/2016/04/17/5401760.html

BaseOfLoader            equ   09000h                  ; LOADER被加载到的位置 ----  段地址
BaseOfLoaderPhyAddr     equ   BaseOfLoader * 10h      ; 被加载段的物理地址 

BaseOfKernelFile        equ   08000h                  ; KERNEL.BIN 被加载到的位置 ----  段地址 
BaseOfKernelFilePhyAddr equ   BaseOfKernelFile * 10h
KernelEntryPointPhyAddr equ   030400h                 ; 注意:1、必须与 MAKEFILE 中参数 -Ttext 的值相等!!
                                                      ;       2、这是个地址而非仅仅是个偏移,如果 -Ttext 的值为 0x400400,则它的值也应该是 0x400400。  

DA_32                   equ   4000h                   ; 32 位段 0100 0000 0000 0000
DA_C                    equ   98h                     ; 存在的只执行代码段属性值 1001 1000
DA_DRW                  equ   92h                     ; 存在的可读写数据段属性值 1001 0010

;GDT结构定义
%macro Descriptor 3
    dw  %2 & 0FFFFh                           ;段界限1   (2字节)
    dw  %1 & 0FFFFh                           ;段基址1  (2字节)
    db  (%1 >> 16) & 0FFh                     ;段基址2  (1字节)
    dw  ((%2 >> 8) & 0F00h) | (%3 & 0F0FFh)   ;属性1 + 段界限2 + 属性2 (2字节)
    db  (%1 >> 24) & 0FFh                     ;段基址3  (1字节)
%endmacro ;共8字节

org 0100h
jmp LABEL_BEGIN

[SECTION  .gdt]
;GDT
LABEL_GDT:           Descriptor 0, 0, 0                            ;空描述符
LABEL_DESC_FLAT_C:   Descriptor 0,0fffffh, DA_C + DA_32            ;代码段,32位
LABEL_DESC_FLAT_RW:  Descriptor 0,0fffffh, DA_DRW | DA_32          ; 0 ~ 4G
LABEL_DESC_VIDEO:    Descriptor 0B8000h, 0ffffh,  DA_DRW           ;显存首地址
; GDT结束

GdtLen  equ  $ - LABEL_GDT   ;GDT长度
GdtPtr  dw   GdtLen          ;GDT界限
        dd   0               ;GDT基地址

;GDT选择子     
SelectorFlatC    equ  LABEL_DESC_FLAT_C   - LABEL_GDT
SelectorFlatRW   equ  LABEL_DESC_FLAT_RW  - LABEL_GDT
SelectorVideo    equ  LABEL_DESC_VIDEO    - LABEL_GDT
;END of [SECTION .gdt]

[SECTION  .s16]
[BITS  16]
LABEL_BEGIN:
    mov  ax,  cs
    mov  ds,  ax
    mov  es,  ax
    mov  ss,  ax
    mov  sp,  0100h

    ;代码段的段基址就设置为0,而对齐LABEL_PM_START的物理地址
    ;;;;;;初始化32位代码段描述符
    ;;;;;;xor  eax,  eax
    ;;;;;;mov  ax, cs
    ;;;;;;shl  eax, 4
    ;;;;;;add  eax, LABEL_PM_START
    ;;;;;;mov  word [LABEL_DESC_FLAT_C + 2], ax
    ;;;;;;shr  eax, 16
    ;;;;;;mov  byte [LABEL_DESC_FLAT_C + 4], al
    ;;;;;;mov  byte [LABEL_DESC_FLAT_C + 7], ah

    ;为加载gdtr做准备
    xor  eax, eax
    mov  ax,  ds
    shl  eax,  4
    add  eax, LABEL_GDT            ;eax <- gdt 基地址
    mov  dword [GdtPtr + 2], eax   ;[GdtPtr +2]<-gdt基地址

    ;加载gdtr
    lgdt  [GdtPtr]

    ;关中断
    cli
    
    ;打开地址线A20
    in  al,  92h
    or  al,  00000010b
    out 92h, al
    
    ;准备切换到保护模式
    mov  eax,  cr0
    or   eax,  1
    mov  cr0,  eax

    ;真正进入保护模式
    ;jmp  dword  SelectorCode32:0                                     ; 执行这一句会把SelectorCode32装入cs,并跳转到SelectCode32:0处
    jmp  dword  SelectorFlatC:(BaseOfLoaderPhyAddr + LABEL_PM_START)  ; 段基址是0,所以偏移地址,是实际的物理地址
        
[SECTION .s32]  ;32位代码段,由实模式跳入
[BITS  32]

LABEL_PM_START:
    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    mov  ax,  SelectorFlatRW
    mov  ds,  ax
    mov  es,  ax
    mov  fs,  ax
    mov  ss,  ax
    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    mov  esp, TopOfStack
    
    mov  ax,  SelectorVideo
    mov  gs,  ax                ;视频段选择子(目的)
    
    mov  edi, (80*2 + 0) * 2    ;屏幕第2行,第0列
    mov  ah,  0Ch               ;0000:黑底  1100:红字
    mov  al,  'z'
    mov  [gs:edi], ax 
    
    mov  edi, (80*2 + 1) * 2    ;屏幕第2行,第1列
    mov  al,  'k'
    mov  [gs:edi], ax
    
    mov  edi, (80*2 + 2) * 2    ;屏幕第2行,第2列
    mov  al,  'd'
    mov  [gs:edi], ax   
    
    mov  edi, (80*2 + 3) * 2    ;屏幕第2行,第3列
    mov  al,  '2'
    mov  [gs:edi], ax   
    
    ;到此为止
    ;jmp $
    call InitKernel
    jmp SelectorFlatC:KernelEntryPointPhyAddr   ; 正式进入内核 *
    
; InitKernel ---------------------------------------------------------------------------------
; 将 KERNEL.BIN 的内容经过整理对齐后放到新的位置
; --------------------------------------------------------------------------------------------
InitKernel: ; 遍历每一个 Program Header,根据 Program Header 中的信息来确定把什么放进内存,放到什么位置,以及放多少。
    xor esi, esi
    mov cx, word [BaseOfKernelFilePhyAddr + 2Ch]; ┓ ecx <- pELFHdr->e_phnum
    movzx   ecx, cx                             ; ┛
    mov esi, [BaseOfKernelFilePhyAddr + 1Ch]    ; esi <- pELFHdr->e_phoff
    add esi, BaseOfKernelFilePhyAddr            ; esi <- OffsetOfKernel + pELFHdr->e_phoff
.Begin:
    mov eax, [esi + 0]
    cmp eax, 0                                  ; PT_NULL
    jz  .NoAction
    push    dword [esi + 010h]                  ; size  ┓
    mov eax, [esi + 04h]                        ;       ┃
    add eax, BaseOfKernelFilePhyAddr            ;       ┣ ::memcpy( (void*)(pPHdr->p_vaddr),
    push    eax                                 ; src   ┃       uchCode + pPHdr->p_offset,
    push    dword [esi + 08h]                   ; dst   ┃       pPHdr->p_filesz;
    call    MemCpy                              ;       ┃
    add esp, 12                                 ;       ┛
.NoAction:
    add esi, 020h                               ; esi += pELFHdr->e_phentsize
    dec ecx
    jnz .Begin

    ret
; InitKernel ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^


; ------------------------------------------------------------------------
; 内存拷贝,仿 memcpy
; ------------------------------------------------------------------------
; void* MemCpy(void* es:pDest, void* ds:pSrc, int iSize);
; ------------------------------------------------------------------------
MemCpy:
    push    ebp
    mov     ebp, esp

    push    esi
    push    edi
    push    ecx

    mov edi, [ebp + 8]  ; Destination
    mov esi, [ebp + 12] ; Source
    mov ecx, [ebp + 16] ; Counter
.1:
    cmp ecx, 0      ; 判断计数器
    jz  .2          ; 计数器为零时跳出

    mov al, [ds:esi]        ; ┓
    inc esi                 ; ┃
                            ; ┣ 逐字节移动
    mov byte [es:edi], al   ; ┃
    inc edi                 ; ┛

    dec ecx     ; 计数器减一
    jmp .1      ; 循环
.2:
    mov eax, [ebp + 8]  ; 返回值

    pop ecx
    pop edi
    pop esi
    mov esp, ebp
    pop ebp

    ret         ; 函数结束,返回
; MemCpy 结束-------------------------------------------------------------    
;END of [SECTION .s32]

; SECTION .data1 之开始 ---------------------------------------------------------------------------------------------
[SECTION .data1]
ALIGN   32
LABEL_DATA:
; 堆栈就在数据段的末尾
StackSpace: times   1000h   db  0
TopOfStack  equ     BaseOfLoaderPhyAddr + $ ; 栈顶
; SECTION .data1 之结束 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
成功进入C语言世界

前面的问题在于在初始化内核之前,没有将选择可读写的段,导致在代码段内进行拷贝出错!就是下面的代码:

LABEL_PM_START:
    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    mov  ax,  SelectorFlatRW
    mov  ds,  ax
    mov  es,  ax
    mov  fs,  ax
    mov  ss,  ax
    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

代码解读:
https://www.cnblogs.com/joey-hua/archive/2016/04/17/5401760.html

BaseOfLoader            equ   09000h                  ; LOADER被加载到的位置 ----  段地址
BaseOfLoaderPhyAddr     equ   BaseOfLoader * 10h      ; 被加载段的物理地址 

BaseOfKernelFile        equ   08000h                  ; KERNEL.BIN 被加载到的位置 ----  段地址 
BaseOfKernelFilePhyAddr equ   BaseOfKernelFile * 10h
KernelEntryPointPhyAddr equ   030400h                 ; 注意:1、必须与 MAKEFILE 中参数 -Ttext 的值相等!!
                                                      ;       2、这是个地址而非仅仅是个偏移,如果 -Ttext 的值为 0x400400,则它的值也应该是 0x400400。  

DA_32                   equ   4000h                   ; 32 位段 0100 0000 0000 0000
DA_C                    equ   98h                     ; 存在的只执行代码段属性值 1001 1000
DA_DRW                  equ   92h                     ; 存在的可读写数据段属性值 1001 0010

;GDT结构定义
%macro Descriptor 3
    dw  %2 & 0FFFFh                           ;段界限1   (2字节)
    dw  %1 & 0FFFFh                           ;段基址1  (2字节)
    db  (%1 >> 16) & 0FFh                     ;段基址2  (1字节)
    dw  ((%2 >> 8) & 0F00h) | (%3 & 0F0FFh)   ;属性1 + 段界限2 + 属性2 (2字节)
    db  (%1 >> 24) & 0FFh                     ;段基址3  (1字节)
%endmacro ;共8字节

org 0100h
jmp LABEL_BEGIN

[SECTION  .gdt]
;GDT
LABEL_GDT:           Descriptor 0, 0, 0                            ;空描述符
LABEL_DESC_FLAT_C:   Descriptor 0,0fffffh, DA_C + DA_32            ;代码段,32位
LABEL_DESC_FLAT_RW:  Descriptor 0,0fffffh, DA_DRW | DA_32          ; 0 ~ 4G
LABEL_DESC_VIDEO:    Descriptor 0B8000h, 0ffffh,  DA_DRW           ;显存首地址
; GDT结束

GdtLen  equ  $ - LABEL_GDT   ;GDT长度
GdtPtr  dw   GdtLen          ;GDT界限
        dd   0               ;GDT基地址

;GDT选择子     
SelectorFlatC    equ  LABEL_DESC_FLAT_C   - LABEL_GDT
SelectorFlatRW   equ  LABEL_DESC_FLAT_RW  - LABEL_GDT
SelectorVideo    equ  LABEL_DESC_VIDEO    - LABEL_GDT
;END of [SECTION .gdt]

[SECTION  .s16]
[BITS  16]
LABEL_BEGIN:
    mov  ax,  cs
    mov  ds,  ax
    mov  es,  ax
    mov  ss,  ax
    mov  sp,  0100h

    ;代码段的段基址就设置为0,而对齐LABEL_PM_START的物理地址
    ;;;;;;初始化32位代码段描述符
    ;;;;;;xor  eax,  eax
    ;;;;;;mov  ax, cs
    ;;;;;;shl  eax, 4
    ;;;;;;add  eax, LABEL_PM_START
    ;;;;;;mov  word [LABEL_DESC_FLAT_C + 2], ax
    ;;;;;;shr  eax, 16
    ;;;;;;mov  byte [LABEL_DESC_FLAT_C + 4], al
    ;;;;;;mov  byte [LABEL_DESC_FLAT_C + 7], ah

    ;为加载gdtr做准备
    xor  eax, eax
    mov  ax,  ds
    shl  eax,  4
    add  eax, LABEL_GDT            ;eax <- gdt 基地址
    mov  dword [GdtPtr + 2], eax   ;[GdtPtr +2]<-gdt基地址

    ;加载gdtr
    lgdt  [GdtPtr]

    ;关中断
    cli
    
    ;打开地址线A20
    in  al,  92h
    or  al,  00000010b
    out 92h, al
    
    ;准备切换到保护模式
    mov  eax,  cr0
    or   eax,  1
    mov  cr0,  eax

    ;真正进入保护模式
    ;jmp  dword  SelectorCode32:0                                     ; 执行这一句会把SelectorCode32装入cs,并跳转到SelectCode32:0处
    jmp  dword  SelectorFlatC:(BaseOfLoaderPhyAddr + LABEL_PM_START)  ; 段基址是0,所以偏移地址,是实际的物理地址
        
[SECTION .s32]  ;32位代码段,由实模式跳入
[BITS  32]

LABEL_PM_START:
    mov  esp, TopOfStack
    
    mov  ax,  SelectorVideo
    mov  gs,  ax                ;视频段选择子(目的)
    
    mov  edi, (80*2 + 0) * 2    ;屏幕第2行,第0列
    mov  ah,  0Ch               ;0000:黑底  1100:红字
    mov  al,  'z'
    mov  [gs:edi], ax 
    
    mov  edi, (80*2 + 1) * 2    ;屏幕第2行,第1列
    mov  al,  'k'
    mov  [gs:edi], ax
    
    mov  edi, (80*2 + 2) * 2    ;屏幕第2行,第2列
    mov  al,  'd'
    mov  [gs:edi], ax   
    
    mov  edi, (80*2 + 3) * 2    ;屏幕第2行,第3列
    mov  al,  '2'
    mov  [gs:edi], ax   
    
    ;到此为止
    mov  edi, (80*2 + 4) * 2    ;屏幕第2行,第3列
    mov  al,  '3'
    mov  [gs:edi], ax   
    
    call InitKernel
    
    mov  edi, (80*2 + 5) * 2    ;屏幕第2行,第3列
    mov  al,  '4'
    mov  [gs:edi], ax 
    
    jmp $
    ;jmp    SelectorFlatC:KernelEntryPointPhyAddr   ; 正式进入内核 *
    
; InitKernel ---------------------------------------------------------------------------------
; 将 KERNEL.BIN 的内容经过整理对齐后放到新的位置
; --------------------------------------------------------------------------------------------
InitKernel: ; 遍历每一个 Program Header,根据 Program Header 中的信息来确定把什么放进内存,放到什么位置,以及放多少。
    xor esi, esi                                ; ESI:ESI称为源变址寄存器,通常存放 要处理的数据的内存地址。EDI:EDI称为目的变址寄存器,通常存放处理后的数据的内存地址。
    mov cx, word [BaseOfKernelFilePhyAddr + 2Ch]; 程序头部表的数量 ┓ ecx <- pELFHdr->e_phnum 2Ch =44  内核偏移地址是0,BaseOfKernelFilePhyAddr就是内核文件加载的物理地址  word 16位
    movzx   ecx, cx                             ; ┛
    mov esi, [BaseOfKernelFilePhyAddr + 1Ch]    ; esi <- pELFHdr->e_phoff  1c=28            程序头部表偏移地址
    add esi, BaseOfKernelFilePhyAddr            ; esi <- OffsetOfKernel + pELFHdr->e_phoff  esi程序头部表地址
.Begin:
    mov eax, [esi + 0]                          ; p_type
    cmp eax, 0                                  ; PT_NULL 此数组元素未用。结构中其他成员都是未定义的。   eax - 0
    jz  .NoAction                               ; 等于跳转
    push    dword [esi + 010h]                  ; size  ┓   010 = 16                          段在文件映像中所占的字节数
    mov eax, [esi + 04h]                        ;       ┃   04h = 4                           从文件头到该段第一个字节的偏移,段内容偏移地址 
    add eax, BaseOfKernelFilePhyAddr            ;       ┣ ::memcpy( (void*)(pPHdr->p_vaddr),  段内容物理地址
    push    eax                                 ; src   ┃       uchCode + pPHdr->p_offset,
    push    dword [esi + 08h]                   ; dst   ┃       pPHdr->p_filesz;              段的第一个字节将被放到内存中的虚拟地址 
    call    MemCpy                              ;       ┃
    add esp, 12                                 ;       ┛                                     PUSH 指令首先减少 ESP 的值,所以这里指向了原来的位置
.NoAction:
    add esi, 020h                               ; 020h = 32 esi += pELFHdr->e_phentsize   esi指向下一个表项
    dec ecx                                     ; 程序头部表的数量 自减
    jnz .Begin                                  ; jump if not zero 结果不为零则转移

    ret
; InitKernel ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^


; ------------------------------------------------------------------------
; 内存拷贝,仿 memcpy
; ------------------------------------------------------------------------
; void* MemCpy(void* es:pDest, void* ds:pSrc, int iSize);
; ------------------------------------------------------------------------
MemCpy:
    push    ebp                              ; ESP是栈顶指针  ,而EBP只是存取某时刻的栈顶指针,以方便对栈的操作,如获取函数参数、局部变量等。
    mov     ebp, esp                         ; 调用call指令的时候会自动把eip压入栈中,MemCpy第一句把ebp压栈,所以现在栈内情况如下图
                                             ; 解释了下面+8 +12 +16

    push    esi
    push    edi
    push    ecx

    mov edi, [ebp + 8]  ; Destination
    mov esi, [ebp + 12] ; Source
    mov ecx, [ebp + 16] ; Counter
.1:
    cmp ecx, 0      ; 判断计数器
    jz  .2          ; 计数器为零时跳出

    mov al, [ds:esi]        ; ┓
    inc esi                 ; ┃
                            ; ┣ 逐字节移动
    mov byte [es:edi], al   ; ┃
    inc edi                 ; ┛

    dec ecx     ; 计数器减一
    jmp .1      ; 循环
.2:
    mov eax, [ebp + 8]  ; 返回值

    pop ecx
    pop edi
    pop esi
    mov esp, ebp
    pop ebp

    ret         ; 函数结束,返回
; MemCpy 结束-------------------------------------------------------------    
;END of [SECTION .s32]

; SECTION .data1 之开始 ---------------------------------------------------------------------------------------------
[SECTION .data1]
ALIGN   32
LABEL_DATA:
; 堆栈就在数据段的末尾
StackSpace: times   1000h   db  0
TopOfStack  equ     BaseOfLoaderPhyAddr + $ ; 栈顶
; SECTION .data1 之结束 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

在不分页的情况下,虚拟地址就是物理地址,所以我们只要把elf文件的各个段放到位置就可以了;
那c语言编译后的代码是不是自己管理自己的段呢?gdt肯定不行,权限?ldt?可以反汇编看看。

内核

编译命令

nasm -f elf -o kernel.o  kernel.asm
nasm -f elf -o klib.o  klib.asm
gcc -m32 -c -o start.o start.c
ld -m elf_i386 -s -Ttext 0x30400 -o kernel.bin  kernel.o  start.o  klib.o

kernel.asm

SELECTOR_KERNEL_CS  equ 8

; 导入函数
extern  cstart

; 导入全局变量
extern  gdt_ptr


[SECTION .bss]
StackSpace      resb    2 * 1024
StackTop:       ; 栈顶

[section .text] ; 代码在此

global _start   ; 导出 _start

_start:
    ; 此时内存看上去是这样的(更详细的内存情况在 LOADER.ASM 中有说明):
    ;              ┃                                    ┃
    ;              ┃                 ...                ┃
    ;              ┣━━━━━━━━━━━━━━━━━━┫
    ;              ┃■■■■■■Page  Tables■■■■■■┃
    ;              ┃■■■■■(大小由LOADER决定)■■■■┃ PageTblBase
    ;    00101000h ┣━━━━━━━━━━━━━━━━━━┫
    ;              ┃■■■■Page Directory Table■■■■┃ PageDirBase = 1M
    ;    00100000h ┣━━━━━━━━━━━━━━━━━━┫
    ;              ┃□□□□ Hardware  Reserved □□□□┃ B8000h ← gs
    ;       9FC00h ┣━━━━━━━━━━━━━━━━━━┫
    ;              ┃■■■■■■■LOADER.BIN■■■■■■┃ somewhere in LOADER ← esp
    ;       90000h ┣━━━━━━━━━━━━━━━━━━┫
    ;              ┃■■■■■■■KERNEL.BIN■■■■■■┃
    ;       80000h ┣━━━━━━━━━━━━━━━━━━┫
    ;              ┃■■■■■■■■KERNEL■■■■■■■┃ 30400h ← KERNEL 入口 (KernelEntryPointPhyAddr)
    ;       30000h ┣━━━━━━━━━━━━━━━━━━┫
    ;              ┋                 ...                ┋
    ;              ┋                                    ┋
    ;           0h ┗━━━━━━━━━━━━━━━━━━┛ ← cs, ds, es, fs, ss
    ;
    ;
    ; GDT 以及相应的描述符是这样的:
    ;
    ;                     Descriptors               Selectors
    ;              ┏━━━━━━━━━━━━━━━━━━┓
    ;              ┃         Dummy Descriptor           ┃
    ;              ┣━━━━━━━━━━━━━━━━━━┫
    ;              ┃         DESC_FLAT_C    (0~4G)     ┃   8h = cs
    ;              ┣━━━━━━━━━━━━━━━━━━┫
    ;              ┃         DESC_FLAT_RW   (0~4G)     ┃  10h = ds, es, fs, ss
    ;              ┣━━━━━━━━━━━━━━━━━━┫
    ;              ┃         DESC_VIDEO                 ┃  1Bh = gs
    ;              ┗━━━━━━━━━━━━━━━━━━┛
    ;
    ; 注意! 在使用 C 代码的时候一定要保证 ds, es, ss 这几个段寄存器的值是一样的
    ; 因为编译器有可能编译出使用它们的代码, 而编译器默认它们是一样的. 比如串拷贝操作会用到 ds 和 es.
    ;
    ;


    ; 把 esp 从 LOADER 挪到 KERNEL
    mov esp, StackTop   ; 堆栈在 bss 段中
    call    cstart      ; 在此函数中改变了gdt_ptr,让它指向新的GDT
    jmp $

klib.asm

[SECTION .data]

disp_pos            dd  0

[SECTION .text]

; 导出函数
global  DspStr

; ========================================================================
;                  void DspStr(char * info);
; ========================================================================
DspStr:
    push    ebp
    mov ebp, esp

    mov esi, [ebp + 8]  ; pszInfo
    mov edi, [disp_pos]
    mov ah, 0Fh
.1:
    lodsb
    test    al, al
    jz  .2
    cmp al, 0Ah ; 是回车吗?
    jnz .3
    push    eax
    mov eax, edi
    mov bl, 160
    div bl
    and eax, 0FFh
    inc eax
    mov bl, 160
    mul bl
    mov edi, eax
    pop eax
    jmp .1
.3:
    mov [gs:edi], ax
    add edi, 2
    jmp .1

.2:
    mov [disp_pos], edi

    pop ebp
    ret

start.c

#define PUBLIC      /* PUBLIC is the opposite of PRIVATE */
#define PRIVATE static  /* PRIVATE x limits the scope of x */

typedef void    (*t_pf_int_handler) ();

PUBLIC void DspStr(char * pszInfo);

/*======================================================================*
                            cstart
 *======================================================================*/
PUBLIC void cstart()
{
    DspStr("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n-----\"cstart\" begins(zhangkai dev os)-----\n");
}

进入c语言后,这里没有重新操作段的。
这一块,应该在loader里面做呢?还是在内核里面做?

相关文章

网友评论

      本文标题:【内核加载123:裸机c语言,hello world】

      本文链接:https://www.haomeiwen.com/subject/lponmktx.html