Fetch the repository succeeded.
org 0x900
jmp CODE16_START
; 段属性的一些定义,暂时定义一些目前用得到的属性
DA_32 equ 0x4000 ; D/B = 1
DA_DR equ 0x90 ; 数据,只读
DA_DRW equ 0x92 ; 数据,可读/写
DA_DRWA equ 0x93 ; 数据,可读/写,已访问
DA_C equ 0x98 ; 代码,仅执行
DA_CR equ 0x9A ; 代码,可执行,可读
DA_CCO equ 0x9C ; 代码,一致性段,仅执行
DA_CCOR equ 0x9E ; 代码,一致性段,可执行,可读,已访问
DA_LIMIT_4K equ 0x8000 ; G = 1, 颗粒度:4K
; 段选择符属性定义
SA_RPL0 equ 0 ; RPL = 0
SA_RPL1 equ 1 ; RPL = 1
SA_RPL2 equ 2 ; RPL = 2
SA_RPL3 equ 3 ; RPL = 3
SA_TIG equ 0 ; TI = 0, GDT
SA_TIL equ 4 ; TI = 1, LDT
; 段描述符定义
%macro Descriptor 3 ; 有三个参数:段基址、段界限、段属性
dw %2 & 0xFFFF ; 段界限 1 (2 字节)
dw %1 & 0xFFFF ; 段基址 1 (2 字节)
db (%1 >> 16) & 0xFF ; 段基址 2 (1 字节)
dw ((%2 >> 8) & 0xF00) | (%3 & 0xF0FF) ; 段属性 1 + 段界限 2 + 段属性 2 (2 字节)
db (%1 >> 24) & 0xFF ; 段基址 3 (1 字节)
%endmacro ; 共 8 个字节
; 页目录表与页起始地址
PAGE_DIR_BASE0 equ 0x100000
PAGE_TAB_BASE0 equ 0x101000
PAGE_DIR_BASE1 equ 0x600000
PAGE_TAB_BASE1 equ 0x601000
; 页表相关属性
PG_P equ 1b ; 页存在属性位
PG_RW_R equ 00b ; R/W 属性位值, 读/执行
PG_RW_W equ 10b ; R/W 属性位值, 读/写/执行
PG_US_S equ 000b ; U/S 属性位值, 系统级
PG_US_U equ 100b ; U/S 属性位值, 用户级
; 全局描述符表定义
; 段基址 段界限 段属性
GDT_BASE : Descriptor 0, 0, 0
CODE32_DESC : Descriptor 0, CODE32_SEG_LEN - 1, DA_C + DA_32
VIDEO_DESC : Descriptor 0xB8000, 0xBFFFF - 0xB8000, DA_DRWA + DA_32
DATA_DESC : Descriptor 0, DATA_SEG_LEN - 1, DA_DR + DA_32
STACK32_DESC : Descriptor 0, TOP_OF_STACK32, DA_DRW + DA_32
PAGE_DIR_DESC0 : Descriptor PAGE_DIR_BASE0, 4095, DA_DRW + DA_32
PAGE_TAB_DESC0 : Descriptor PAGE_TAB_BASE0, 1023, DA_DRW + DA_32 + DA_LIMIT_4K
PAGE_DIR_DESC1 : Descriptor PAGE_DIR_BASE1, 4095, DA_DRW + DA_32
PAGE_TAB_DESC1 : Descriptor PAGE_TAB_BASE1, 1023, DA_DRW + DA_32 + DA_LIMIT_4K
FLAT_MODE_DESC : Descriptor 0, 0xFFFFF, DA_DRW + DA_32 + DA_LIMIT_4K
FLAT_MODE_C_DESC : Descriptor 0, 0xFFFFF, DA_C + DA_32 + DA_LIMIT_4K
; ...
GDT_LEN equ $ - GDT_BASE ; GDT 长度 = 当前地址 - GDT_BASE 地址
GDT_PTR :
dw GDT_LEN - 1
dd GDT_BASE
; 段选择符定义,RPL = 0; TI = 0
CODE32_SELECTOR equ (0x0001 << 3) + SA_RPL0 + SA_TIG
VIDEO_SELECTOR equ (0x0002 << 3) + SA_RPL0 + SA_TIG
DATA_SELECTOR equ (0x0003 << 3) + SA_RPL0 + SA_TIG
STACK32_SELECTOR equ (0x0004 << 3) + SA_RPL0 + SA_TIG
PAGE_DIR0_SELECTOR equ (0x0005 << 3) + SA_RPL0 + SA_TIG
PAGE_TAB0_SELECTOR equ (0x0006 << 3) + SA_RPL0 + SA_TIG
PAGE_DIR1_SELECTOR equ (0x0007 << 3) + SA_RPL0 + SA_TIG
PAGE_TAB1_SELECTOR equ (0x0008 << 3) + SA_RPL0 + SA_TIG
FLAT_MODE_SELECTOR equ (0x0009 << 3) + SA_RPL0 + SA_TIG
FLAT_MODE_C_SELECTOR equ (0x000A << 3) + SA_RPL0 + SA_TIG
msg db "Loader..." ; 使用 db 定义数据
msgLen equ $-msg ; 字符串 msg 长度
GET_MEM_ERR_DATA db "Get Memory Err" ; 使用 db 定义数据
GET_MEM_ERR_LEN equ $-GET_MEM_ERR_DATA ; 字符串 GET_MEM_ERR_DATA 长度
MEM_SIZE times 4 db 0
MEM_ARDS times 64 * 20 db 0 ; ARDS 缓冲区
[bits 16]
CODE16_START :
xor ax, ax ; xor 指令与 and or 指令类似,两个操作数的每一对对应位都应用如下操作原则:
; 如果两个位的值相同(同为 0 或同为 1),则结果位等于 0;否则结果位等于 1
; xor ax, ax 等于 ax = 0
mov ss, ax ; 栈段 ss = 0
mov ds, ax ; 数据段 ds = 0
mov es, ax ; 附加数据段 es = 0
mov sp, 0x900 ; 设置栈,跟 0x7c00 一样,0x900 地址以下也有一段安全可用内存
; 设置光标位置 (dl, dh)
mov ah, 0x02 ; AH 功能号 = 0x02,设置光标
mov dh, 0x01 ; 设置光标行号
mov dl, 0x00 ; 设置光标列号
mov bh, 0x00 ; 页码
int 0x10
; 打印 msg
mov ax, msg
mov cx, msgLen
call print
; 设置光标位置 (dl, dh)
mov ah, 0x02 ; AH 功能号 = 0x02,设置光标
mov dh, 0x02 ; 设置光标行号
mov dl, 0x00 ; 设置光标列号
mov bh, 0x00 ; 页码
int 0x10
; call detect_memory_0xe801
; call detect_memory_0xe820
call detect_memory
; 初始化段描述符中的段基址
mov esi, CODE32_START
mov edi, CODE32_DESC
call InitDescItem
mov esi, DATA_SEGMENT
mov edi, DATA_DESC
call InitDescItem
mov esi, STACK32_SEGMENT
mov edi, STACK32_DESC
call InitDescItem
; 打开 A20 地址线
in al, 0x92
or al, 0000_0010B
out 0x92, al
; 加载描述符表
;mov eax, 0
;mov ax, ds
;shl eax, 4
;add eax, GDT_BASE
;mov dword [GDT_PTR + 2], eax
lgdt [GDT_PTR]
; 通知 CPU 进入保护模式,即将 CR0 寄存器的 PE(bit0) 位置 1
mov eax, cr0
or eax, 0x01
mov cr0, eax
; 刷新流水线
; 跳转到 32 位代码段继续执行
jmp dword CODE32_SELECTOR:0
; 参数: esi --> 代码段标签
; 参数: edi --> 段描述符标签
InitDescItem:
push eax
mov eax, 0
mov ax, cs
shl eax, 4
add eax, esi
mov word [edi + 2], ax
shr eax, 16
mov byte [edi + 4], al
mov byte [edi + 7], ah
pop eax
ret
; 打印字符串
; ax : 输入参数,字符串地址
; cx : 输入参数,字符串长度
print:
; 入栈
push ax
push bp
push bx
mov bp, ax ; 保存字符串地址
mov ax, 0x1301 ; 子功能号 0x13 是写字符串
mov bx, 000fh ; 页号位 0,黑底白字
int 0x10
; 出栈
pop bx
pop bp
pop ax
ret
detect_memory_0xe801:
push eax
push ebx
push ecx
push edx
mov dword [MEM_SIZE], 0 ; 将 MEM_SIZE 清零
xor eax, eax ; 用于清 CF 标志位 CF = 0
mov eax, 0xE801
int 0x15
jc .err ; 如果 CF = 1,表示读取内存容量失败,跳转到 .err 处执行
shl eax, 10 ; eax = eax * 1024
shl ebx, 16 ; ebx = ebx * 64 * 1024
add dword [MEM_SIZE], eax
add dword [MEM_SIZE], ebx
mov ecx, 1
shl ecx, 20 ; ecx = 1MB
add dword [MEM_SIZE], ecx ; 人为增加 1M
jmp .ok
.err:
mov dword [MEM_SIZE], 0 ; 将 MEM_SIZE 清零
.ok:
pop edx
pop ecx
pop ebx
pop eax
ret
detect_memory_0xe820:
push edi
push ebx
push ecx
push edx
mov di, MEM_ARDS
mov ebx, 0
.loop:
; 固定参数
mov eax, 0xE820
mov edx, 0x534D4150
mov ecx, 20
int 0x15
jc .err
cmp dword [edi + 16], 1 ; 比较 Type 是否为 1
jne .next ; 如果不相等,跳转到 .next
mov eax, [edi] ; BaseAddrLow
add eax, [edi + 8] ; BaseAddrLow + LengthLow
cmp dword [MEM_SIZE], eax; 比较 [MEM_SIZE] 和 BaseAddrLow + LengthLow 大小
jnb .next ; 如果 [MEM_SIZE] < BaseAddrLow + LengthLow,跳转到 .next
mov dword [MEM_SIZE], eax; 此时 BaseAddrLow + LengthLow 较大,将较大的值写入 [MEM_SIZE] 中
.next:
add edi, 20
cmp ebx, 0
jne .loop ; 结束循环条件:ebx = 0
; 程序运行到这里说明 MEM_SIZE 已得到,跳转到 .ok 处执行
; 实测发现得到的 MEM_SIZE 始终比实际少 0x10000,也不知道为啥,干脆就像 e801 一样人为补一下吧
mov ecx, 1
shl ecx, 16 ; ecx = 0x10000
add dword [MEM_SIZE], ecx ; 人为增加 0x10000
jmp .ok
.err:
mov dword [MEM_SIZE], 0 ; 将 MEM_SIZE 清零
.ok:
pop edx
pop ecx
pop ebx
pop edi
ret
detect_memory:
push ax
push cx
call detect_memory_0xe820
cmp dword [MEM_SIZE], 0
jne .ok ; 如果 [MEM_SIZE] 不为 0,表明获取物理内存成功
call detect_memory_0xe801
cmp dword [MEM_SIZE], 0
jne .ok ; 如果 [MEM_SIZE] 不为 0,表明获取物理内存成功
.err:
; 打印 "Get Memory Err"
mov ax, GET_MEM_ERR_DATA
mov cx, GET_MEM_ERR_LEN
call print
jmp $ ; 死机
.ok:
pop cx
pop ax
ret
[bits 32]
CODE32_START :
; 设置栈寄存器
mov ax, STACK32_SELECTOR
mov ss, ax
mov eax, TOP_OF_STACK32
mov esp, eax
; 设置显存段
mov ax, VIDEO_SELECTOR
mov gs, ax
; 设置数据段
mov ax, DATA_SELECTOR
mov ds, ax
; mov eax, 0
; mov es, eax
mov eax, FLAT_MODE_SELECTOR
mov es, eax
mov ebp, msg2Offset
mov bl, 0x0F ; 打印属性,黑底白字
; 坐标 (0, 2)
mov dl, 0x00
mov dh, 0x02
call print_str_32
mov eax, PAGE_DIR0_SELECTOR
mov ebx, PAGE_TAB0_SELECTOR
mov ecx, PAGE_TAB_BASE0
call Init_Page_Table
mov eax, PAGE_DIR1_SELECTOR
mov ebx, PAGE_TAB1_SELECTOR
mov ecx, PAGE_TAB_BASE1
call Init_Page_Table
; 临时把 ds 设置为平坦模式(可读写)段
mov ax, FLAT_MODE_SELECTOR
mov ds, ax
mov esi, test_func1 ; 源
mov edi, 0xD01000 ; 目标
mov ecx, test_func1_len ; 循环次数
call MemCpy32
mov esi, test_func2 ; 源
mov edi, 0xE01000 ; 目标
mov ecx, test_func2_len ; 循环次数
call MemCpy32
mov ax, DATA_SELECTOR
mov ds, ax
; mov eax, 0xD01007
; mov [es:0x102004], eax ; 将数值 0xD01007 写入 0x102004 地址处
mov eax, 0x401000
mov ebx, 0xD01000
mov ecx, PAGE_DIR_BASE0
call MapAddress
; mov eax, 0xE01007
; mov [es:0x602004], eax ; 将数值 0xE01007 写入 0x106004 地址处
mov eax, 0x401000
mov ebx, 0xE01000
mov ecx, PAGE_DIR_BASE1
call MapAddress
mov eax, PAGE_DIR_BASE0
call Switch_Page_Table
; call FLAT_MODE_SELECTOR : 0x401000
call FLAT_MODE_C_SELECTOR : 0x401000
mov eax, PAGE_DIR_BASE1
call Switch_Page_Table
; call FLAT_MODE_SELECTOR : 0x401000
call FLAT_MODE_C_SELECTOR : 0x401000
jmp $
; 使用显存方式打印字符串
; ds:ebp --> 打印的数据起始地址(相对于段基址的偏移地址)
; bl --> 打印属性
; dx --> 打印起始坐标 (dl, dh)
print_str_32:
push ebp
push eax
push edi
push cx
push dx
; 循环
s:
mov cl, [ds:ebp] ; 取地址 ds:ebp 中的数据存到 cl 寄存器
cmp cl, 0 ; 比较 cl 是否为 0
je end ; 若为 0 ,就结束打印
; 根据坐标 (dl, dh) 计算出偏移量,存入 eax 中
; (80 * dh + dl)*2 ; 每行最多显示 80 个字符
mov eax, 80
mul dh
add al, dl
shl eax, 1 ; eax = eax*2
mov edi, eax ; edi :显存中的偏移量
mov ah, bl ; 显示属性
mov al, cl ; 要打印的字符
mov [gs:edi], ax ; 显示数据放入显存
inc ebp ; 自增
inc dl ; 自增
jmp s ; 循环
; 打印结束
end:
pop dx
pop cx
pop edi
pop eax
pop ebp
ret
; 功能:创建页目录表及页表
; 传入参数:
; eax --> 页目录选择子
; ebx --> 页表选择子
; ecx --> 页表基地址
Init_Page_Table:
push es
push eax ; [esp + 12]
push ebx ; [esp + 8]
push ecx ; [esp + 4]
push edi ; [esp], esp 栈顶指针,始终指向最后一次入栈处,此处 [esp] = edi
; 创建页目录
mov es, eax ; eax ==> 页目录选择子
mov edi, 0 ; es:edi ==> 页目录选择子:0
mov ecx, 1024 ; 循环 1024 次,页目录共 1024 项
mov eax, [esp + 4] ;
or eax, PG_P | PG_US_U | PG_RW_W ; eax = 第一个子页表的地址和属性
cld ; edi 自增
.creat_pde: ; 创建页目录项
stosd ; 把 eax 中的值写入 [es:edi] 指向的内存中
add eax, 4096 ; eax = eax + 4K
loop .creat_pde
; 程序执行到这里说明页目录表已经创建完成
; 创建页表
mov ax, [esp + 8] ; 页表选择子
mov es, eax
mov edi, 0 ; es:edi ==> 页表选择子:0
mov ecx, 1024*1024 ; 循环 1024*1024 次,共 1024 个页表,每个页表有 1024 个页表项
mov eax, PG_P | PG_US_U | PG_RW_W ; 只有属性,没有基地址,因为第一个页表项指向的就是物理地址 0x0 处
cld
.creat_pte: ; 创建页表项
stosd ; 把 eax 中的值写入 [es:edi] 指向的内存中
add eax, 4096 ; eax = eax + 4K
loop .creat_pte
pop edi
pop ecx
pop ebx
pop eax
pop es
ret
; 功能:切换页表
; 传入参数:
; eax --> 页目录基地址
Switch_Page_Table:
push eax
; 寄存器 cr0 的 PG 位清 0,暂时先关闭内存分页功能
mov eax, cr0
and eax, 0x7FFFFFFF ; and 按位与
mov cr0, eax
; 将页目录表首地址写入控制寄存器 cr3
mov eax, [esp] ; [esp] = Switch_Page_Table 运行前 eax
mov cr3, eax
; 寄存器 cr0 的 PG 位置 1,开启内存分页
mov eax, cr0
or eax, 0x80000000
mov cr0, eax
pop eax
ret
; ds:esi --> 源
; es:edi --> 目标
; ecx --> 循环次数,即要拷贝的字节数
MemCpy32:
push eax
push edi
push esi
push ecx
.s: mov al, [ds:esi] ; [ds:esi] : 源地址
mov [es:edi], al ; [es:edi] : 目标地址
inc edi ; edi = edi + 1
inc esi ; esi = esi +1
loop .s ; 循环次数由 ecx 决定
pop ecx
pop esi
pop edi
pop esi
ret
; es --> 平坦模式下使用,es 为平坦模式段选择子
; eax --> 虚拟地址
; ebx --> 目标物理地址
; ecx --> 页目录表基地址
MapAddress:
push edi
push esi
push eax ; [esp + 8]
push ebx ; [esp + 4]
push ecx ; [esp]
; 1. 取虚地址高 10 位, 计算子页表在页目录中的位置
mov eax, [esp + 8]
shr eax, 22
and eax, 1111111111b
shl eax, 2
; 2. 取虚地址中间 10 位, 计算物理地址在子页表中的位置
mov ebx, [esp + 8]
shr ebx, 12
and ebx, 1111111111b
shl ebx, 2
; 3. 取子页表起始地址
mov esi, [esp]
add esi, eax
mov edi, [es:esi]
and edi, 0xFFFFF000
; 4. 将目标地址写入子页表的对应位置
add edi, ebx
mov ecx, [esp + 4]
and ecx, 0xFFFFF000
or ecx, PG_P | PG_US_U | PG_RW_W
mov [es:edi], ecx
pop ecx
pop ebx
pop eax
pop esi
pop edi
ret
test_func1:
mov eax, 8
retf
test_func1_len equ $ - test_func1
test_func2:
mov eax, 9
retf
test_func2_len equ $ - test_func2
CODE32_SEG_LEN equ $-CODE32_START
DATA_SEGMENT:
msg2 db "Enter protection", 0 ; 以 0 为字符串结束标志
msg2Offset equ msg2 - DATA_SEGMENT ; msg2 在数据段 DATA_SEGMENT 中的偏移量
TEST_DATA_ABCD db "ABCD" ; 数据 "ABCD"
TEST_ABCD_OFFSET equ TEST_DATA_ABCD - DATA_SEGMENT ; "ABCD" 在数据段 DATA_SEGMENT 中的偏移量
TEST_DATA_abcd db "abcd" ; 数据 "abcd"
TEST_abcd_OFFSET equ TEST_DATA_abcd - DATA_SEGMENT ; "abcd" 在数据段 DATA_SEGMENT 中的偏移量
DATA_SEG_LEN equ $ - DATA_SEGMENT
STACK32_SEGMENT:
times 1024 * 4 db 0 ; 开辟 4K 的内存空间当做栈
STACK32_SEG_LEN equ $ - STACK32_SEGMENT
TOP_OF_STACK32 equ STACK32_SEG_LEN - 1 ; 栈顶
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。